Sign in to follow this  
nickme

GLSL:how to pass values to fragment shader.

Recommended Posts

hi

 

this is my first program in GLSL.  I found a website that can generate a mandelbrot via the fragment shader.

 

http://nuclear.mutantstargoat.com/articles/sdr_fract/

 

I am interested in generating a julia subset.  I wondered how to pass the value of c which ranged from -2.5-2.5 for x axis and -1.2 to 1.2 in y axis.

 

i had previously able to write a julia and Mandelbrot program in fixed function pipeline api but now wanted to learn about GLSL.

 

thanks in advance.

Edited by nickme

Share this post


Link to post
Share on other sites

In Fixed Functional pipeline, i would used two loop to output the fractals.

 

for (cx = -2.5sx = left; cx < 2.5; sx++, cx += deltaX)

      for (cy = -1.2, sy = bottom; cy < 1.2; sy++, cy += deltaY) {

          color = Julia(cx, cy);

          glTexCoord2d(color/iter, 0.0);

          glVertex2D(sx, sy);

      }

 

in GLSL, i have no clues how to pass cx, cy.

 

my fragment shader:

 

// Julia set
#version 450
uniform sampler2D tex;
uniform vec2 c;
uniform float iter;
 
void main() {
    vec2 z;
vec2 color = vec2(0.0, 0.0);
 
    z.x = 3.0 * (gl_TexCoord[0].x - 0.5);
    z.y = 2.0 * (gl_TexCoord[0].y - 0.5);
 
    int i;
    for(; color.x < iter; color.x++) {
        float x = (z.x * z.x - z.y * z.y) + c.x;
        float y = (z.y * z.x + z.x * z.y) + c.y;
 
        if((x * x + y * y) > 4.0) break;
        z.x = x;
        z.y = y;
    }
color.x = color.x / iter;
 
    gl_FragColor =texture2D(tex, color.st);
}
 

thanks

Edited by nickme

Share this post


Link to post
Share on other sites

Only recently did my first OpenGL experiments, so please don't blindly assume what I write is correct.

 

Your "uniform" variables are the way to transfer values from the program to the GPU. Afaik these are sort of constants for the GPU. You set them before you run the computation, and the GPU only reads them.

 

You need to declare the variables in the shader program like you did with "uniform vec2 c;" (A better name than "c" is probably a good idea.)

In the code, you query its location

GLint cLocation = glGetUniformLocation(program, "c");

(I always do this after selecting the shader program with "glUseProgram", not sure if this is required.)
 

You can set the value with a "glUniform*" call. There are many such functions, the one you need depends on the type of the shader variable. For "c" (a vec2) it can be

glUniform2f(cLocation, 1.0f, 2.0f);

The 1.0f, and 2.0f can be replaced by other values of course. This glUniform takes 2 reals, if you have a vector (as in an array of float) in your source code, there are also glUniform calls to transfer such arrays in one call.

 

Hope this helps. For loads of details, see http://learnopengl.com/#!Getting-started/Shaders

 

 

PS To get "code layout" in your post, select the code, and press "<>" in the toolbar.

Edited by Alberth

Share this post


Link to post
Share on other sites

hi Alberth,

 

thanks for reply.

 

I know that I can do what you said by using glUniform2f() to pass values to shader, but then I also has to use 2 forloop to pass them to shader, that defeats the purpose of using GLSL; there is no parallism at all. and that is what i am looking for:parallism

Share this post


Link to post
Share on other sites

PS To get "code layout" in your post, select the code, and press "<>" in the toolbar.

 

I do not understand what do you mean by code layout, I do not understand the context, can you elaborate?

 

thanks 

Share this post


Link to post
Share on other sites

hi Alberth,

 

thanks for reply.

 

I know that I can do what you said by using glUniform2f() to pass values to shader, but then I also has to use 2 forloop to pass them to shader, that defeats the purpose of using GLSL; there is no parallism at all. and that is what i am looking for:parallism

 

 

You don't need the loops. I wrote a post earlier but didn't click submit when I realised you were doing Julia Sets (where the c value would remain the same and thus Alberth's answer was enough). For your Julia Sets it's this bit that calculates the z value (or c if you were doing Mandelbrot):

    z.x = 3.0 * (gl_TexCoord[0].x - 0.5);
    z.y = 2.0 * (gl_TexCoord[0].y - 0.5);

Normally you would set set your c value in your loops (for Mandelbrot at least) but this isn't possible to do in a pixel shader because the pixel doesn't really know where it is. It doesn't know if it is the first pixel, the 5th, the last. It doesn't know if it is in the top row, along the left edge or any of that useful stuff you will need. Also you are not in control of any looping any more so you can't give any of those values. Fortunately what you can do is give the shader code the information it needs to be able to calculate the values itself.

 

Because of how the quad has been setup with texture coordinates, you can use those texture coordinates in the pixel shader to work out where on the quad you are. For example, the top left of the quad has texture coordinates (0, 1). In the pixel shader then you will have:

z.x = 3.0 * (gl_TexCoord[0].x - 0.5);
z.y = 2.0 * (gl_TexCoord[0].y - 0.5);

becomes:

z.x = 3.0 * (0 - 0.5) = -1.5
z.y = 2.0 * (1 - 0.5) = 1

Thus the z value for that pixel becomes -1.5, 1.

Consider what happens in the middle of the quad; the texture coordinates are 0.5, 0.5 and so:

z.x = 3.0 * (0.5 - 0.5) = 0
z.y = 2.0 * (0.5 - 0.5) = 0

z becomes 0, 0 which makes sense since that is the center.

 

So using texture coordinates (and scale, center as in the Mandelbrot version) the shader is able to calculate it's z (or c) value rather than you having to pass it for each pixel (which would be much slower since you'd have to pass it using a texture possibly).

 

For the Julia Set the 'c' value will remain the same for every pixel so Alberth's answer should be enough fot hat since you only need to do it once.

 

I think it might be of benefit to you if you read up on how shaders work and how they interpolate the output of the vertex shader for each fragment.

 

This approach does have some downsides too. It uses floats so you'll be limited to have far you can magnify your image and it also does a lot of work each frame (it is rebuilding the entire thing each time). A better approach might be to render it to a texture once and then simply show that texture each time afterwards using a basic shader.

Edited by Nanoha

Share this post


Link to post
Share on other sites

I do not understand what do you mean by code layout, I do not understand the context, can you elaborate?
Did you notice the boxes in my post that contains code lines?

 

That is "code layout" in the forum. It selects a non-proportional font (all letters are equally wide), and adds syntax colouring to the text. Blocks of code, like your shader code become more readable if you put them in a box too.

Share this post


Link to post
Share on other sites
This approach does have some downsides too. It uses floats so you'll be limited to have far you can magnify your image and it also does a lot of work each frame (it is rebuilding the entire thing each time). A better approach might be to render it to a texture once and then simply show that texture each time afterwards using a basic shader.

 

 

yes!  in my FFP program, i used GLdouble for the cx and cy, ...etc variables and I can only mag into 10 or so times before it just all be come zeros.

 

I tested the render window with the right - left and if it is less than 1.0e-13, then I will go back to the the first coord.

 

It still not working for me.  My screen just turned black after i ran it. here is the codes in my main function.  I tested it with a texture with and showed that It can access the texture, but the screen just showed a black screen. 

int main()
{
	restart_gl_log();		// starting print gl.log header
	
	if (!glfwInit()) 
		quit("start GLFW3\n", NULL);

	window = glfwCreateWindow(wsizeX, wsizeY, "Hello Julia", NULL, NULL);
	if (!window) 
		quit("open window with GLFW3\n", NULL);
	
	glfwMakeContextCurrent(window);
	/* start GLEW extension handler */
	glewExperimental = GL_TRUE;
	glewInit();

	glEnable(GL_TEXTURE_2D);
//	vs = loadShaderFromFile("vertshader.vert", GL_VERTEX_SHADER);
//	fs = loadShaderFromFile("donotin.frag", GL_FRAGMENT_SHADER);
	fs = loadShaderFromFile("Julia.frag", GL_FRAGMENT_SHADER);

	shader_programme = glCreateProgram();
//	glAttachShader(shader_programme, vs);
	glAttachShader(shader_programme, fs);
	glLinkProgram(shader_programme);
	if (!check_program_link(shader_programme, vs, fs)) {
		quit("Program shader", NULL);
	}

	fclose(file);	// close gl.log file.
	init();
	glViewport(0, 0, wsizeX, wsizeY);
	glLoadIdentity();
	glClearColor(1.0, 0.0, 0.3, 1.0);
	while (!glfwWindowShouldClose(window)) 
	{
		glEnable(GL_TEXTURE_2D);	
		glClear(GL_COLOR_BUFFER_BIT);
		glUseProgram(shader_programme);

		glActiveTexture(GL_TEXTURE0);
		glBindTexture(GL_TEXTURE_2D, palette_ID);
		glUniform1i(glGetUniformLocation(shader_programme, "tex"), 0);

		glUniform2f(glGetUniformLocation(shader_programme, "cval"), 0.74540541, 0.1130063);

		glUniform1f(glGetUniformLocation(shader_programme, "iter"), 256.0);
	
		glBindTexture(GL_TEXTURE_2D, palette_ID);
		glActiveTexture(GL_TEXTURE0);

		draw_quad();

//		drawTexturedRect(-1.2, 0.3, 1, 1, palette_ID);

		glfwPollEvents();
		glfwSwapBuffers(window);
		
		if (GLFW_PRESS == glfwGetKey(window, GLFW_KEY_ESCAPE)) {
			glfwSetWindowShouldClose(window, 1);
		}
	}

	glfwTerminate();
	return 0;
}

I changed the variable c to cval, it is not a typo.

 

thanks in advance.

Edited by nickme

Share this post


Link to post
Share on other sites

Can you post your up to date shader too? 

 

If you put this at the end of your fragment shader main function does it display your pallet?

gl_FragColor = texture2D(tex, gl_TexCoord);
Edited by Nanoha

Share this post


Link to post
Share on other sites

I just noticed that my vertex shader is not working correctly.  I just donno what is wrong.  

#version 450
void main()
{
    //Process vertex
    gl_Position = gl_Vertex;
}

thanks

Share this post


Link to post
Share on other sites

If your shader is failing to compile you can get some info on why by using [url=https://www.khronos.org/opengles/sdk/docs/man/xhtml/glGetShaderInfoLog.xml]glGetShaderInfoLog[/url]

Share this post


Link to post
Share on other sites

I changed the vert shader to the following but still no cigar:(

#version 450

void main()
{
   gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}



In my debug version(used just a donotin.frag)  i omit attaching vs to program, it works as expected.  when i linked it to the program, it just showed a black screen.

// donotin.frag
#version 450
uniform sampler2D tex;
void main() 
{
	vec2 t;
	t = vec2(gl_TexCoord[0].s, 1.0 - gl_TexCoord[0].t);
	gl_FragColor = texture2D(tex, t.st);
}
Edited by nickme

Share this post


Link to post
Share on other sites
I think it might be of benefit to you if you read up on how shaders work and how they interpolate the output of the vertex shader for each fragment.  

 

I tried to read from books and web tutorials, but I was not able to find the answer, that is the reason i post this topic.

 

thanks 


If your shader is failing to compile you can get some info on why by using glGetShaderInfoLog

hi  Nanoha,

 

It compiled fine, there are no errors from glsl.  I know how to get the infolog.  But somehow the window is black when i linked vertshader.vert with the shader_program.

 

now my main() :

int main()
{
	restart_gl_log();		// starting print gl.log header
	
	if (!glfwInit()) 
		quit("start GLFW3\n", NULL);

	window = glfwCreateWindow(wsizeX, wsizeY, "Hello Julia", NULL, NULL);
	if (!window) 
		quit("open window with GLFW3\n", NULL);
	
	glfwMakeContextCurrent(window);
	/* start GLEW extension handler */
	glewExperimental = GL_TRUE;
	glewInit();

	glEnable(GL_TEXTURE_2D);
//	vs = loadShaderFromFile("vertshader.vert", GL_VERTEX_SHADER);//
//	fs = loadShaderFromFile("donotin.frag", GL_FRAGMENT_SHADER);
	fs = loadShaderFromFile("Julia.frag", GL_FRAGMENT_SHADER);

	shader_programme = glCreateProgram();
//	glAttachShader(shader_programme, vs);
	glAttachShader(shader_programme, fs);
	glLinkProgram(shader_programme);
	if (!check_program_link(shader_programme, vs, fs)) {
		quit("Program shader", NULL);
	}

	fclose(file);	// close gl.log file.
	init();
	glViewport(0, 0, wsizeX, wsizeY);
	glLoadIdentity();
	glClearColor(1.0, 0.0, 0.3, 1.0);
	while (!glfwWindowShouldClose(window)) 
	{
		glEnable(GL_TEXTURE_2D);	
		glClear(GL_COLOR_BUFFER_BIT);
		glUseProgram(shader_programme);

		glActiveTexture(GL_TEXTURE0);
		glBindTexture(GL_TEXTURE_2D, palette_ID);
		glUniform1i(glGetUniformLocation(shader_programme, "tex"), 0);

		glUniform2f(glGetUniformLocation(shader_programme, "cval"), 
					0.74540541, 0.1130063);

		glUniform1f(glGetUniformLocation(shader_programme, "iter"), 256.0);
	
		glBindTexture(GL_TEXTURE_2D, palette_ID);
		glActiveTexture(GL_TEXTURE0);

		draw_quad();

//		drawTexturedRect(-1.2, 0.3, 1, 1, palette_ID);

		glfwPollEvents();
		glfwSwapBuffers(window);
		
		if (GLFW_PRESS == glfwGetKey(window, GLFW_KEY_ESCAPE)) {
			glfwSetWindowShouldClose(window, 1);
		}
	}

	glfwTerminate();
	return 0;
}
Edited by nickme

Share this post


Link to post
Share on other sites

There's a few common mistakes [url=https://www.opengl.org/wiki/GLSL_:_common_mistakes]here[/url] that might be worth looking at. You have to provide a vertex shader but since you have commented it out, then in this bit you are passing in whatever value vs was originally:

    if (!check_program_link(shader_programme, vs, fs)) {
        quit("Program shader", NULL);
    }

So you need to get that vertex shader compiling. What kind of error log do you get when you try to compile it? Also, what version of OpenGL are you using? I think your problem is that you are using this tutorial which is quite old but you are then using more up to date things which don't have some of the stuff that they had available. I'm thinking that glsl version 4.5 doesn't have a gl_Vertex input any more so that might be why it doesn't work. 

 

This is the spec for it: https://www.opengl.org/registry/doc/GLSLangSpec.4.50.pdf but it is pretty nasty reading. It does have a compatibility section which reads:

7.2 Compatibility Profile Vertex Shader Built-In Inputs The following predeclared input names can be used from within a vertex shader to access the current values of OpenGL state when using the compatibility profile.

in vec4 gl_Color;

in vec4 gl_SecondaryColor;

in vec3 gl_Normal;

in vec4 gl_Vertex;

 

But how you enable the compatibility profile I have no idea. You could try lowering the version you are targeting to one that still has gl_Vertex available or you can start using vertex attributes (the modern way of doing things). Try setting the version as 100.

Share this post


Link to post
Share on other sites
void init() {
	glEnable(GL_TEXTURE_2D);
//	palette_ID = LoadTexture2D("bryrb256d.png");
//	palette_ID = LoadTexture2D("newpattern.jpg");
	palette_ID = LoadTexture2D("bryrb1d.jpg");
//	palette_ID = LoadTexture2D("party.jpg");
	glBindTexture(GL_TEXTURE_2D, palette_ID);
	glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
}

int main()
{
	restart_gl_log();		// starting print gl.log header
	
	if (!glfwInit()) 
		quit("start GLFW3\n", NULL);

	window = glfwCreateWindow(wsizeX, wsizeY, "Hello Julia", NULL, NULL);
	if (!window) 
		quit("open window with GLFW3\n", NULL);
	
	glfwMakeContextCurrent(window);
	/* start GLEW extension handler */
	glewExperimental = GL_TRUE;
	glewInit();

	glEnable(GL_TEXTURE_2D);
	vs = loadShaderFromFile("vertshader.vert", GL_VERTEX_SHADER);//
//	fs = loadShaderFromFile("donotin.frag", GL_FRAGMENT_SHADER);
	fs = loadShaderFromFile("Julia.frag", GL_FRAGMENT_SHADER);

	shader_programme = glCreateProgram();
	glAttachShader(shader_programme, vs);
	glAttachShader(shader_programme, fs);
	glLinkProgram(shader_programme);
	if (!check_program_link_status(shader_programme, vs, fs)) {
		quit("Program shader", NULL);
	}

	fclose(file);	// close gl.log file.
	init();
	glViewport(0, 0, wsizeX, wsizeY);
	glClearColor(0.0, 0.0, 0.3, 1.0);
	while (!glfwWindowShouldClose(window)) 
	{
		glEnable(GL_TEXTURE_2D);	
		glClear(GL_COLOR_BUFFER_BIT);
		glUseProgram(shader_programme);

		glActiveTexture(GL_TEXTURE0);
		glBindTexture(GL_TEXTURE_2D, palette_ID);
		glUniform1i(glGetUniformLocation(shader_programme, "tex"), 0);

		glUniform2f(glGetUniformLocation(shader_programme, "cval"), 
					0.74540541, 0.1130063);

		glUniform1f(glGetUniformLocation(shader_programme, "iter"), 256.0);
	
		draw_quad();

		glfwPollEvents();
		glfwSwapBuffers(window);
		
		if (GLFW_PRESS == glfwGetKey(window, GLFW_KEY_ESCAPE)) {
			glfwSetWindowShouldClose(window, 1);
		}
	}

	glfwTerminate();
	return 0;
} 

the picture that my program does not looked anything like the image in the website, is it because my cval are different?  thanks for everything.

hi Nanoha,

 

I am able to make it show something, but it only worked with party.jpg file, i don't understand why, do you have any idea?

Share this post


Link to post
Share on other sites

That image does look correct for the values you gave, good job. There are a few Julia Sets shown [url=https://en.wikipedia.org/wiki/Julia_set#Quadratic_polynomials]here[/url] with their 'c' values if you want something to compare to and see if yours are correct.  I'm not sure why it only works with your party.jpg, what happens when you try to use one of the other ones? Without being able to see what your party.jpg is it's hard for me to say if it looks correct or not.

Share this post


Link to post
Share on other sites

hi Nanoha,

 

let's get to the problem of not having a working vertex shader.  If i did not link the vs to shader_programme, it worked, but if i linked it, the screen is black.  I want to solve this problem because, if i dont, I can not use vertex shader at all for the rest of my programming, period.

 

Nanoha, you mentioned that I should change the version number to 100.  I tested that and find that when i changed both vs and fs to version 100, fs would not compiled.  if i let fs stay in version 450, then vs and fs were of different version and it did not work.

 

Is there a work-around for pass thru a gl_position to some other variable without the gl_vertex?

 

thanks in advance.

Share this post


Link to post
Share on other sites

hi

 

I used the following vertex shader but still does not work:

#version 450

void main()
{
   gl_Position = ftransform();
   //gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}

:(

 

even through it compiled without error.

Edited by nickme

Share this post


Link to post
Share on other sites

This is tricky because you have a lot of things going on all at once and a lot of older code mixed with a lot of newer code. ftransform() might not even exist in version 4.5 for example. I think it would probably be a very good idea if you followed a modern tutorial that just draws a single triangle to the screen. Once you have that you can make it a quad and once you have that you can put your Julia shader back in.

 

There's a bunch of tutorials here:

http://ogldev.atspace.co.uk/index.html

Take a look at tutorials 3, 4 and 5.

 

Is there a work-around for pass thru a gl_position to some other variable without the gl_vertex?

The tutorials above will show you how to do this.

Edited by Nanoha

Share this post


Link to post
Share on other sites

This approach does have some downsides too. It uses floats so you'll be limited to have far you can magnify your image and it also does a lot of work each frame (it is rebuilding the entire thing each time). A better approach might be to render it to a texture once and then simply show that texture each time afterwards using a basic shader.

 

I heard that before but do not know what that meant.   Can you elaborate?

 

thanks 

Share this post


Link to post
Share on other sites

 

This approach does have some downsides too. It uses floats so you'll be limited to have far you can magnify your image and it also does a lot of work each frame (it is rebuilding the entire thing each time). A better approach might be to render it to a texture once and then simply show that texture each time afterwards using a basic shader.

 

I heard that before but do not know what that meant.   Can you elaborate?

 

thanks 

 

 

Not sure which part of what I have said there that you are referring to.

 

For the floats part, when you zoom in far enough your image will start to look very blocky, I'm not sure when this will happen but you can zoom in a great deal more if you start to use doubles. If the version of OpenGL/GLSL supports doubles in shaders then it will be easy enough to change later. Earlier versions (and the ES versions) don't have support for doubles but I think you will be ok.

 

According to wikipedia you'd need OpenGL 4.0 or above to use doubles.

 

For the render to texture comment. Each time you draw one frame your shader is recreating the entire image, this is potentially a lot of work. Once the image is created you don't really need to build it again unless something changes (the location, the scale etc). The idea then is to build it once, store that image and then keep showing the copy each time instead of remaking it. For that you will need to know how to 'Render to texture'. Right now you have been loading textures from jpg files but it's possible to draw onto them just like you draw onto the screen. First you would set your Julia shader, set the 'render target' (the texture to draw on) and then draw the frame. Then your image is stored in the texture. The next time around you just draw that same texture onto the screen instead. You would probably have to follow a tutorial for this as it can be quite confusing.

Edited by Nanoha

Share this post


Link to post
Share on other sites

hi

 

I am able to print out the shader and program info log:

 


#version 450
in vec2 tcoord;
in vec3 vertex_position;

void main() {
  gl_Position = vec4(vertex_position, 1.0);
}


 ***** test.vert compiled with no error. *****



// Julia set fragment shader

#version 450
uniform sampler2D tex;
uniform vec2 cval;
uniform float iter;

void main() {
    vec2 z;
	float color = 0.0;

    z.x = 6.0 * (gl_TexCoord[0].x - 0.5);
    z.y = 3.0 * (gl_TexCoord[0].y - 0.5);

    int i;
    for(; color < iter; color++) {
        float x = (z.x * z.x - z.y * z.y) + cval.x;
        float y = (z.y * z.x + z.x * z.y) + cval.y;

        if((x * x + y * y) > 4.0) break;
        z.x = x;
        z.y = y;
    }
	vec2 cc = vec2(color / iter, 1.0);
    gl_FragColor =texture2D(tex, cc.st);
}


 ***** Julia.frag compiled with no error. *****

program infolog:

 

GL_LINK_STATUS = 1
GL_ATTACHED_SHADERS = 2
GL_ACTIVE_ATTRIBUTES = 1
  0) type: vec3                 name: vertex_position      location: 0
  0) type: vec2                 name: cval                 location: 0
  1) type: float                name: iter                 location: 1
  2) type: sampler2D            name: tex                  location: 2
 
linking status = 1, is that a success or failed?
vertex_position and cval are both in loc 0, is that a problem?
 
thanks
Edited by nickme

Share this post


Link to post
Share on other sites

linking status = 1, is that a success or failed?

 

I'm not sure, where is that value coming from? What function returns that?

 

 

vertex_position and cval are both in loc 0, is that a problem?

 

 

That sounds ok, one is a uniform and one is an attribute so them both being 0 is acceptable.

 

What I do notice is you are using gl_TexCoord in your fragment, that probably no longer exists either so that has to go. GL_ACTIVE_ATTRIBUTES = 1, this should read 2 (you have both vertex_position and tcoord). The reason tcoord isn't being registered is because it's not being used in your vertex shader so it's being removed during compilation. 

 

I can't give exact advice because I'm used to using an older version myself but to fix it you need to do something along the lines of:

Add a varying/out tex coord to your vertex shader

#version 450
in vec2 tcoord;
in vec3 vertex_position;
 
varying vec2 fragTexCoord; // this might have to be 'out' instead out 'varying'

void main() 
{
   fragTexCoord = tcoord;
   gl_Position = vec4(vertex_position, 1.0);

}

And then you add a similar thing to the top of your fragment shader

varying vec2 fragTexCoord; // this might have to be 'in' instead of varying

 

and then each time you have gl_TexCoord[0] just replace it with fragTexCoord.

 

Looks like you are making some progress though, good work so far.

Edited by Nanoha

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this