scale2x / scale3x shaders


sebt3

homebrew player (P. & C.)
Joined
Sep 9, 2008
Messages
4,886
Age
43
Location
France
Website
sebt3.openpandora.org
Hi there,


I do have written these 2 shaders. But they are _way_ _too_ _slow_ to be anything usefull :(


Anyway here they are :


scale2x :

Vert :



Code:
attribute vec4 a_position;

attribute vec2 a_texCoord0;

varying vec2 v_texCoord[5];

varying vec2 pos;

uniform vec4 u_param;

void main()

{

    	vec2 osx = vec2(1.0/u_param.x, 0.0);

    	vec2 osy = vec2(0.0, 1.0/u_param.y);

    	gl_Position =  a_position;

    	v_texCoord[0] = a_texCoord0;

    	v_texCoord[1] = v_texCoord[0] - osx;

    	v_texCoord[2] = v_texCoord[0] + osx;

    	v_texCoord[3] = v_texCoord[0] - osy;

    	v_texCoord[4] = v_texCoord[0] + osy;

    	pos = v_texCoord[0].xy * u_param.xy;

}





Fragment : B) || (p.y< 0.5 && D == H))) E = D;

if(p.x< 0.5 && p.y>=0.5 && D == B) E = D; // E0

if(p.x< 0.5 && p.y< 0.5 && D == H) E = D; // E2

// if(p.x>=0.5 && ((p.y>=0.5 && B == F) || (p.y< 0.5 && H == F))) E = F;

if(p.x>=0.5 && p.y>=0.5 && B == F) E = F; // E1

if(p.x>=0.5 && p.y< 0.5 && H == F) E = F; // E3

}

gl_FragColor = E;

}

Code:
precision mediump float;

varying vec2 v_texCoord[5];

varying vec2 pos;

uniform sampler2D s_texture0;

uniform vec4 u_param;

void main()

{

    	vec4 E = texture2D(s_texture0, v_texCoord[0]);

    	vec4 D = texture2D(s_texture0, v_texCoord[1]);

    	vec4 F = texture2D(s_texture0, v_texCoord[2]);

    	vec4 H = texture2D(s_texture0, v_texCoord[3]);

    	vec4 B = texture2D(s_texture0, v_texCoord[4]);

    	vec2 p = fract(pos);

    	if (B != H && D != F) {

//          	if(p.x< 0.5 && ((p.y>=0.5 && D ==

Note that the commented lines should do the same thing as the 2 lines bellow them but dont... :wacko:


scale3x :
Vertice :

Code:
attribute vec4 a_position;

attribute vec2 a_texCoord0;

varying vec2 v_texCoord[9];

uniform vec4 u_param;

void main()

{

    	vec2 osx = vec2(1.0/u_param.x, 0.0);

    	vec2 osy = vec2(0.0, 1.0/u_param.y);

    	gl_Position =  a_position;

    	v_texCoord[0] = a_texCoord0;                	// E

    	v_texCoord[1] = v_texCoord[0] - osx;        	// D

    	v_texCoord[2] = v_texCoord[0] + osx;        	// F

    	v_texCoord[3] = v_texCoord[0]   	- osy;  	// H

    	v_texCoord[4] = v_texCoord[0]   	+ osy;  	// B

    	v_texCoord[5] = v_texCoord[0] - osx + osy;  	// A

    	v_texCoord[6] = v_texCoord[0] + osx + osy;  	// C

    	v_texCoord[7] = v_texCoord[0] - osx - osy;  	// G

    	v_texCoord[8] = v_texCoord[0] + osx - osy;  	// I

}
Fragment :

Code:
precision mediump float;

varying vec2 v_texCoord[9];

uniform sampler2D s_texture0;

uniform vec4 u_param;

/*

A B C

D E F

G H I

k0k1

k2k3

E0E1E2

E3E4E5

E6E7E8

*/

void main()

{

    	vec2 p = fract(v_texCoord[0].xy * u_param.xy);

    	vec4 E = texture2D(s_texture0, v_texCoord[0]);

    	vec4 D = texture2D(s_texture0, v_texCoord[1]);

    	vec4 F = texture2D(s_texture0, v_texCoord[2]);

    	vec4 H = texture2D(s_texture0, v_texCoord[3]);

    	vec4 B = texture2D(s_texture0, v_texCoord[4]);

    	vec4 A = texture2D(s_texture0, v_texCoord[5]);

    	vec4 C = texture2D(s_texture0, v_texCoord[6]);

    	vec4 G = texture2D(s_texture0, v_texCoord[7]);

    	vec4 I = texture2D(s_texture0, v_texCoord[8]);

    	vec4 r = E;

    	if (D == B && B != F && D != H) {                                       		//=k0=

            	if(p.x< 0.33        	&& p.y>=0.66)                   		r = D;  // E0

            	if(p.x>=0.33&&p.x< 0.66 && p.y>=0.66        	&& E != C)  	r = B;  // E1

            	if(p.x< 0.33        	&& p.y>=0.33&&p.y< 0.66 && E != G)  	r = D;  // E3

    	}

    	if (B == F && B != D && F != H) {                                       		//=k1=

            	if(p.x>=0.66        	&& p.y>=0.66)                   		r = F;  // E2

            	if(p.x>=0.33&&p.x< 0.66 && p.y>=0.66        	&& E != A)  	r = B;  // E1

            	if(p.x>=0.66        	&& p.y>=0.33&&p.y< 0.66 && E != I)  	r = F;  // E5

    	}

    	if (D == H && D != B && H != F) {                                       		//=k2=

            	if(p.x< 0.33        	&& p.y< 0.33)                   		r = D;  // E6

            	if(p.x< 0.33        	&& p.y>=0.33&&p.y< 0.66 && E != A)  	r = D;  // E3

            	if(p.x>=0.33&&p.x< 0.66 && p.y< 0.33        	&& E != I)  	r = H;  // E7

    	}

    	if (H == F && D != H && B != F) {                                       		//=k3=

            	if(p.x>=0.66        	&& p.y< 0.33)                   		r = F;  // E8

            	if(p.x>=0.66        	&& p.y>=0.33&&p.y< 0.66 && E != C)  	r = F;  // E5

            	if(p.x>=0.33&&p.x< 0.66 && p.y< 0.33        	&& E != G)  	r = H;  // E7

    	}

    	gl_FragColor = r;

}




I tried to optimize them, but that's the best I can get. They are at least as slow as their CPU counter-part.


PS : I'm doing more this as a warning/starting point for the next man as I spend some days doing so and the result are... not satisfying at least...
 
Last edited by a moderator:
Awww... How do they perform on the dekstop?
Not tested, but as I know epsxe is using a similar shader for scale2x, I guess fast enough.


EDIT : I just tested a 2xSaI shader as I've been told that conditionnal in shader are bad, and 2xSaI can be implemented without a single test.... Still too _way_ slow (about the same as scale2x :( )
 
Last edited by a moderator:
Probably not a big impact (and might already be optimized by the compiler anyway), but couldn't you do the scale2x conditions as if-else if-else if... instead of if-if-if...? Looks like no two of those conditions will apply at the same time. Could save you some condition checking. Also this will probably help even less, but you could do away with the E variable and just substitute it with gl_FragColor to do away with one assignment. Then move the texture2D(s_texture0, v_texCoord[0]); to a else block after the if blocks (both of them), saves you another useless assignment.


I really don't know if these will have any measurable impact, but something to try at least :)


EDIT:



Code:
precision mediump float;

varying vec2 v_texCoord[5];

varying vec2 pos;

uniform sampler2D s_texture0;

uniform vec4 u_param;

void main()

{

    	vec4 D = texture2D(s_texture0, v_texCoord[1]);

    	vec4 F = texture2D(s_texture0, v_texCoord[2]);

    	if (D == F) {

            	gl_FragColor = texture2D(s_texture0, v_texCoord[0]);

    	} else {

            	vec4 H = texture2D(s_texture0, v_texCoord[3]);

            	vec4 B = texture2D(s_texture0, v_texCoord[4]);

            	if (B == H) {

                    	gl_FragColor = texture2D(s_texture0, v_texCoord[0]);

            	} else {

                    	vec2 p = fract(pos);

                    	if(p.x< 0.5 && p.y>=0.5 && D ==  gl_FragColor = D; // E0

                    	else if(p.x< 0.5 && p.y< 0.5 && D == H) gl_FragColor = D; // E2

                    	else if(p.x>=0.5 && p.y>=0.5 && B == F) gl_FragColor = F; // E1

                    	else if(p.x>=0.5 && p.y< 0.5 && H == F) gl_FragColor = F; // E3

                    	else gl_FragColor = texture2D(s_texture0, v_texCoord[0]);

            	}

    	}

}
[/CODE]


EDIT2: Let's save some sampling while we're at it (now it's ugly, too!)


EDIT3: Removed extra "else"
 
Last edited by a moderator:
First thing, I'm curious what performance you're getting on a pure pass-through identity filter. This is being applied to a virtual framebuffer that's constantly being updated, right? It seems that the best method for doing this is using bc-cat (http://processors.wiki.ti.com/index.php/OpenGLES_Texture_Streaming_-_bc-cat_User_Guide) but I don't know if anyone has built it for Pandora.


Unfortunately, I can tell you off the bat that getting 60FPS for 800x480 with this shader is impossible if you need all of the samplers. With 5 texture accesses you're already exceeding the theoretical maximum fill-rate of the SGX530 at 110MHz (you'd be limited around 57FPS, but I'm sure nothing close to that is attainable). That's for the scale2x fragment shader, the scale3x one is obviously much worse.


The compiler is probably going to turn some conditionals into predicates and keep some as conditionals. SGX can handle real flow control changes with single thread granularity, but we don't really know what the overhead is.. and I have a feeling that making samplers conditional may make them slower. Worth a shot though I guess.
 
I know about bc-cat and I was about to try to get it running (aka get that kernel drivers first). But I decided to test with the passthrow shader first and I was realy surprised to get zelda3T (my testing pet) working fullspeed.


When I'll be back from work I'll :


- test B-ZaR implementation


- provide a 3T test build with switchable shaders (pass-through, scale2x and 2xSaI)


- bench my flip function with both shaders
 
I don't know much about shaders but if branching is a problem you may consider something like this:



Code:
vec4 tmp1 = p.x < 0.5 ? D : F;

vec4 tmp2 = p.y < 0.5 ? H : B;

vec4 tmp3 = D == F || H == B ? E : tmp1;

gl_FragColor = tmp1 == tmp2 ? tmp3 : E;

It should be equivalent. Any compiler should be smart enough to produce branch free code out of this and it is only 10 instructions total.
 
Thanks FSO works nicely ;)


But even with that, my flip function cost (for snes resultion pushed to fullscreen) :


- passthrough : 10-14ms


- 2xSaI : 62-65ms


- scale2x : 160-180ms (FSO implemention)

2xSaI fragment shader :

B) ,dt);

vec3 color = (k1*(H+ B) +k2*(D+F)+0.001*E)/(2.0*(k1+k2)+0.001);

gl_FragColor.xyz = color;

}

Code:
void main()

{

    	vec3 E = texture2D(s_texture0, v_texCoord[0]).xyz;

    	vec3 D = texture2D(s_texture0, v_texCoord[1]).xyz;

    	vec3 F = texture2D(s_texture0, v_texCoord[2]).xyz;

    	vec3 H = texture2D(s_texture0, v_texCoord[3]).xyz;

    	vec3 B = texture2D(s_texture0, v_texCoord[4]).xyz;

    	vec3 dt = vec3(1.0,1.0,1.0);

    	float k1=dot(abs(D-F),dt);

    	float k2=dot(abs(H-



Also here is the flip function :



Code:
void ESS_Flip() {

    	int w = p2(src->w);

    	int h = p2(src->h);

    	Uint32 p = SDL_GetTicks();

    	// Load the texture into SGX memory

    	if (texID == -1) {

            	GL_CHECK(glGenTextures(1, &texID));

    	}

    	GL_CHECK(glBindTexture(GL_TEXTURE_2D, texID));

    	//GL_CHECK(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, w, h, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, src->pixels ));

    	glTexSubImage2D( GL_TEXTURE_2D,0,0,0, (int)param[2], (int)param[3], GL_RGB, GL_UNSIGNED_SHORT_5_6_5, src->pixels);

    	GL_CHECK(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST));

    	GL_CHECK(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST));

    	// Draw here

    	GL_CHECK(glEnable(GL_TEXTURE_2D));

    	GL_CHECK(glUseProgram(prg));

    	GL_CHECK(glActiveTexture(GL_TEXTURE0));

    	GL_CHECK(glBindTexture(GL_TEXTURE_2D, texID));

    	GL_CHECK(glUniform1f(texID, 0));

    	GL_CHECK(glVertexAttribPointer(a_position, 3, GL_FLOAT, GL_FALSE, 0, &vtx));

    	GL_CHECK(glVertexAttribPointer(a_texCoord0, 2, GL_FLOAT, GL_FALSE, 0, &texCoord));

    	GL_CHECK(glUniform4f(u_param, param[0], param[1], param[2], param[3]));

    	glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_BYTE, &indexData);

    	EGL_SwapBuffers();

    	glDisable(GL_TEXTURE_2D);

    	GL_CHECK(glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT));

    	printf("Flip : %d\n", SDL_GetTicks() - p);

}

So branches are bad in a shader, but sampling too :D
 
Last edited by a moderator:
62-65ms is about 17-19 USSE cycles. If you count the number of operations in the shader this isn't very surprising. I can count as many as 14 vec3 operations operations and 5 samplers. Both USSEs can work on the ALU operations but you can only get one TMU result per cycle. Depending on the datatype those vector operations may take multiple cycles.


Are the vec3s lowp? Its type could make a big difference.
 
Using vec3 instead of vec4, the scale2x code reach 125-130ms. Not usable (far from it) but interesting

current Fragment shader :



Code:
precision mediump float;

varying vec2 v_texCoord[5];

varying vec2 pos;

uniform sampler2D s_texture0;

uniform vec4 u_param;

void main()

{

    	vec3 E = texture2D(s_texture0, v_texCoord[0]).xyz;

    	vec3 D = texture2D(s_texture0, v_texCoord[1]).xyz;

    	vec3 F = texture2D(s_texture0, v_texCoord[2]).xyz;

    	vec3 H = texture2D(s_texture0, v_texCoord[3]).xyz;

    	vec3 B = texture2D(s_texture0, v_texCoord[4]).xyz;

    	vec2 p = fract(pos);

    	vec3 tmp1 = p.x < 0.5 ? D : F;

    	vec3 tmp2 = p.y < 0.5 ? H : B;

    	vec3 tmp3 = D == F || H == B ? E : tmp1;

    	gl_FragColor.xyz = tmp1 == tmp2 ? tmp3 : E;

}
 
Last edited by a moderator:
Ive seen that x ? y : z type of code before, how does it work? How can it create bransh free code? Im abusing if else like crazy, but on normal cpus it dosnt seem to be very slow, arm cpus arent out of order type are they? Cos thats were youd need no branching to boost speed?
 
?: tertiary operator is a shorthand for if. The return value of the statement depends on the first parameter (before "?"). If it's true, the statement's value is the second paramenter (between "?" and ":"). Otherwise it's the third parameter (after ":").



Code:
int a = 1 == 2 ? 10 : 20; // a = 20

int b = true || false ? 10 : 20; // b = 10


Branching makes code slower on some platforms because it messes up pipelining (processing multiple sequential instructions partly in parallel) inside the processor. In the case of shaders, which are typically short programs run a lot of times messing up pipelining can affect performance by a visible margin. You shouldn't need to think about this when doing normal code at all.


I'm not quite sure how the tertiary shorthand if operator is optimized to not require branching, however.


(disclaimer: the above may contain errors, which I would like to be pointed out by someone who can explain it better)
 
I use that alot in my php stuff when assigning a variable which could be 2 different values.



Code:
$a = ($x==$y)? '10'/*if true*/ : '120'/*if false*/;

thats just a random example but its pretty much the same as B-ZaR explained, not sure if this has any effect the speed of my scripts but its quick and neat to write.


you cant use this to replace "elseif" or "switch" statements however.
 
In C, ?: is called the "conditional operator".. since it's the only ternary operator (three arguments) it's often just called "the ternary operator."


It isn't a shorthand for if, because it actually evaluates to something, so can be used as a value inside an expression. Also unlike if, the branches can't contain compound statements. But like if, only the taken branch will be evaluated. Since there aren't compound statements it's harder to properly sequence side effects, but you can still have them. Therefore, in order to create a branch-free version of this code you have to predicate everything, unless you can determine there are no side-effects, in which case you just have to have a mechanism for picking the result. If you don't have predication in hardware you're probably better off outputting a branch. This is no different for the analysis a compiler would do on an if statement.


From what I understand, OpenCL, which calls it "ternary selection", behaves the same for scalars, so it'd be up to the compiler to generate branches. But for vectors, "? a : b : c" is equivalent to select(a, b, c), which means that both b and c will be evaluated and therefore it will be branch-less. The select function will return a vector where each element is selected from b or c depending on if that element in a is true or false (determined by the MSB). ISAs that don't have full instruction predication will often at least have select instructions, for instance SSE and ARM/NEON have it. But even if it isn't there you can do it with just a few instructions, depending on how the selector is stored.


Personally I never use the operator in my C code, I guess I just can't stand the appearance of it.. but nothing is really stopping me or anyone else from making a macro to hide it.
 
Last edited by a moderator:
Yes, ternary. Sorry for the mixup. I have seen it (or variants in other languages) called "shorthand if" though. Wikipedia lists "inline if" as a common name, but also refers to the term "shorthand if", though I do recognize the differences. Maybe the name refers to being a shorter version of the common "if(condition) x = 1; else x = 2;" (insert line breaks and curly braces for your reading pleasure) pattern. Dunno, I've heard the name several times though.


Anyway, thanks for clearing this up :)
 
you cant use this to replace "elseif" or "switch" statements however.



Code:
a ? (b ? (c ? d : e) : f) : g;


Syntax along these lines can actually be quite handy, for example when setting up a reference (you have to define the reference and point it at something on the same line, you can't define on one line and set up what it points to later) so it is not crazily uncommon to see stuff like:





Code:
const MyDataStructure & rData = ( i == 0 ) ? kZeroData : i > 100 ? kLargeData : kNormalData;


Which is basically (in pseudo):


if i = 0: rData = kZeroData


else if i > 100: rData = kLargeData


else rData = kNormalData


Anyway!


Steve
 
In some architectures having instructions for conditional selection or swapping of registers (for example some DSP hardware and also the SPUs in the PS3), ternary operator doesn't always need to be coded as if/else. Just a conditional select and you have avoided a branch. Some times even, calculating both ways and making the selection of the result works faster than branching and calculating only one way.


Unfortunately, I know nothing about GPU shaders, so I don't know if they have conditional select/swap instructions.
 
Since (scalar) ternary can have side effects they can't automatically be turned into both branch + select. So it's just like if/then/else. There's no reason why a compiler could implement one using selects and not the other.


Shader languages literally have select instructions, so it'd be pretty lame if GPU hardware didn't support it directly.
 
Back
Top