GP2X Trenki's Software Renderer Tutorial


Thanks Trenki! Texturing works just like I thought it would :) Those varyings are really handy.
 
Hi Trenki. I was seeing some big slowdowns when I had fullscreen coverages (ie, I was drawing a lot of big triangles) so I stuck my single_fragment directly in the affine_span to avoid the function call overhead. My app went from 25 -> 45 fps with that one tiny change. I haven't really looked at optimizing beyond that but thought you might be interested.
 
dockthepod said:
Hi Trenki. I was seeing some big slowdowns when I had fullscreen coverages (ie, I was drawing a lot of big triangles) so I stuck my single_fragment directly in the affine_span to avoid the function call overhead. My app went from 25 -> 45 fps with that one tiny change. I haven't really looked at optimizing beyond that but thought you might be interested.
Interesting. This should normally not be the case since the compiler would inline the single_fragment function. Was the single_fragment function declared inline and/or defined inside the fragment shader class? How exactly did you move the function directly into affine_span? Are you sure you didn't change anything else?

Please let me know and maybe have a look at both, the single_fragment and the new affine_span function. You can also PM me if you like.
 
Last edited by a moderator:
Maybe his compile options won't allow any optimalizations?

Got any pointers where I can learn a bit more about 3D stuff. All this 'pipeline' and 'shaders' is new for me. I'm also wondering what the '.w' part is in "out.w = 1 << 16;"
I've played a bit with openGL, and understand a bit of 3D vector math. But that's about it. (Really interrested in seeing what I can do with your code, but it looks like it requires a bit more knowlage then basic openGL)
 
Trenki said:
dockthepod said:
Hi Trenki. I was seeing some big slowdowns when I had fullscreen coverages (ie, I was drawing a lot of big triangles) so I stuck my single_fragment directly in the affine_span to avoid the function call overhead. My app went from 25 -> 45 fps with that one tiny change. I haven't really looked at optimizing beyond that but thought you might be interested.
Interesting. This should normally not be the case since the compiler would inline the single_fragment function. Was the single_fragment function declared inline and/or defined inside the fragment shader class? How exactly did you move the function directly into affine_span? Are you sure you didn't change anything else?

Please let me know and maybe have a look at both, the single_fragment and the new affine_span function. You can also PM me if you like.



I'm using your example code as a starting point so my single_fragment is inside of my shader class. To move it over, i just did something like this ->

CODE

#if 0
unsigned short d = fd.z >> 16;
// if (depth16 < d) return 0;
depth16 = d;

int s = std::min(std::max(fd.varyings[0] >> 16, 0), 31);
color16 = (s << 11) | (s << 6) | s;

const int write_mask = 3; // FragmentShader::single_fragment(fd, color16, depth16);
#else
const int write_mask = FragmentShader::single_fragment(fd, color16, depth16);
#endif



I made two builds just flipping that #if to check the difference. I'll play around with it some more tonight after work.
 
Last edited by a moderator:
Yeah, compile options are very important. I always use -O3 and -funroll-loops and I noticed the open2x devkit with gcc 4.1.1 compiles better than devkitGP2X on Windows with gcc 4.0.2.

The out.w coordinate is the fourth coordinate of a homogenous point in 3D. It is normally computed when you do modelview_projection_matrix * vertex where vertex normally is (x, y, z, 1). mvp * vertex gives you a homogenous vector (x', y', z', w) after transformation. This allows one to have perspective transformations. out.w = 1 << 16; simply sets w to 1 (in fixed point 16.16). w will end up beeing 1 whenever you do an orthographic projection whereas with a perspective projection w can become any value.

The renderer requires substantions knowledge of the "Graphics Pipeline". To learn more anbout it you may try "Graphics Pipeline", "Rendering Pipeline", "3D Pipeline" in Google. Also the OpenGL specification describes the pipeline to some extent.
 
I'm playing with it and like it a lot. I'm understanding how it works and got a CAL3D model shown on the screen, well kind of, I'm still fighting with the matrices. At least I can see the model, and can scale it using a matrix, and apply the model view projection matrix...more or less. At least I'm improving :lol:

<--------EDIT------->
Well I got it working!

But two problems:

1- I have upside down textures. Inverted. Don't know how to spell it.
2-There is some kind of...is it called clipping? Is like some polygons from the back overlaping polygons on the front.
 
efegea said:
I'm playing with it and like it a lot. I'm understanding how it works and got a CAL3D model shown on the screen, well kind of, I'm still fighting with the matrices. At least I can see the model, and can scale it using a matrix, and apply the model view projection matrix...more or less. At least I'm improving :lol:

<--------EDIT------->
Well I got it working!

But two problems:

1- I have upside down textures. Inverted. Don't know how to spell it.
2-There is some kind of...is it called clipping? Is like some polygons from the back overlaping polygons on the front.
1. The Cal3D model probably has the texture coordinates stored in a way that suits OpenGL with (0, 0) at the bottom left corner while "normal" images are stored top down and thus have (0,0) in the upper left. The simplest way to fix this is to modify the t (y) texture coordinate y' = 1 - y. This can be done in the vertex shader or after loading the model.

2. Polygons in the back overlapping polygons in the front indicates a wrong depth test. You need to have a depth surface and do the correct depth test in the fragment shader yourself. Look at the cow demo for a depth testing example.
 
Last edited by a moderator:
I hacked on this a bit more last night. I can't figure out why my fragment shader is so slow. I profiled my game and I spend 90% (literally) of the time in affine_span. What compile options should I use? I've tried 02, 03, unroll-loops, etc. No dice.
 
dockthepod said:
I hacked on this a bit more last night. I can't figure out why my fragment shader is so slow. I profiled my game and I spend 90% (literally) of the time in affine_span. What compile options should I use? I've tried 02, 03, unroll-loops, etc. No dice.
Have you confirmed that putting the fragment shader source directly into the affine_span function actually makes things faster? I doubt it does so. In the last source fragment you posted you had commented out the depth test which of course would have made it faster but also would not have given the same results.

I always compile with -O3 and -funroll-loops. If you are using my sources as base you should also "#define GP2X" as this makes it use the mmuhack. You really want to use the mmuhack and probably also the ramhack (included in my cow demo) to make things as fast as possible.

You can best test your fragment shader by drawing a fullscreen quad and see how long it takes. A single texture mapped quad takes 5.2ms with fixed texture size and 6.1ms with variable texture size. You can compare your numbers with this.

You can also PM me your fragment shader and I will have a look at it.

EDIT: You should also #define NDEBUG via the command line in a release build, so that all asserts are removed from the source.
 
Last edited by a moderator:
Thanks Trenki :) I'll play around some more when I get home. I'm confident that it can go much faster with the right settings. The good news is your renderer has allowed me to make great progress on the game I've been kicking around in my head for a long while now. Thanks again for release it!
 
Trenki said:
1. The Cal3D model probably has the texture coordinates stored in a way that suits OpenGL with (0, 0) at the bottom left corner while "normal" images are stored top down and thus have (0,0) in the upper left. The simplest way to fix this is to modify the t (y) texture coordinate y' = 1 - y. This can be done in the vertex shader or after loading the model.

2. Polygons in the back overlapping polygons in the front indicates a wrong depth test. You need to have a depth surface and do the correct depth test in the fragment shader yourself. Look at the cow demo for a depth testing example.
Thanks Trenki! :)

That was so easy to fix. Now I can see the model without artifacts. Thanks!

A small video of what I got :D
http://personales.ya.com/efegea/cal3d_trenki_renderer.avi
But that on my athlon64 3400, I have not GP2X. I got 75 fps as average.
 
Last edited by a moderator:
Nice video! 75fps seems too low though on a AMD64 3400 as the gp2x is 10-15 times slower. Did you compile with full optimization? How many triangles does the model have? When you want good performance on the gp2x you should get at least 200-250fps on your system.
 
Trenki said:
Nice video! 75fps seems too low though on a AMD64 3400 as the gp2x is 10-15 times slower. Did you compile with full optimization? How many triangles does the model have? When you want good performance on the gp2x you should get at least 200-250fps on your system.
Is that possibly being limited by vsync? 75MHz is a pretty normal number for refresh rate.
 
Last edited by a moderator:
I was thinking so. My screen refresh rate is 75hz. But as far as I know there is not any code for enabling/disablig vsync in my code.


Oh ok, SDL_flip waits for vsync. That was the bug. I have to use SDL_UpdateRect I think..


EDIT: it's weird, changed it and the framerate is still the same
 
Trenki said:
Yeah, compile options are very important. I always use -O3 and -funroll-loops and I noticed the open2x devkit with gcc 4.1.1 compiles better than devkitGP2X on Windows with gcc 4.0.2.
Does -O3 always generate correct code? (I've had some issues with it with some GCC 4)

QUOTE
The out.w coordinate is the fourth coordinate of a homogenous point in 3D. It is normally computed when you do modelview_projection_matrix * vertex where vertex normally is (x, y, z, 1). mvp * vertex gives you a homogenous vector (x', y', z', w) after transformation. This allows one to have perspective transformations. out.w = 1 << 16; simply sets w to 1 (in fixed point 16.16). w will end up beeing 1 whenever you do an orthographic projection whereas with a perspective projection w can become any value.
Ok thanks. Silly question from my side I guess, but if you set out.w to the same as z (limit from 1 to 0xFFFF, in 16.16 format) would you get simple perspective projection then? Or do I need to calculate the projection matrix for that? (My matrix math knowlage is very low :p)

QUOTE
The renderer requires substantions knowledge of the "Graphics Pipeline". To learn more anbout it you may try "Graphics Pipeline", "Rendering Pipeline", "3D Pipeline" in Google. Also the OpenGL specification describes the pipeline to some extent.

I'm reading this one right now: http://www.extremetech.com/article2/0,1697,9722,00.asp seems to cover quite a bit (to much for the GP2x I recon ;)) just letting anyone else with interrest know.
 
Last edited by a moderator:
efegea said:
I was thinking so. My screen refresh rate is 75hz. But as far as I know there is not any code for enabling/disablig vsync in my code.
Oh ok, SDL_flip waits for vsync. That was the bug. I have to use SDL_UpdateRect I think..


EDIT: it's weird, changed it and the framerate is still the same



SDL (at least on Windows) does not give you a hardware surface, so I don't know if they even do vsync with SW_SURFACEs. I circumvented vsync by only requesting a single buffered surface and rendering to another SDL_Surface and blitting the result to the screen in the end. This also works on the gp2x and can make things a lot faster as you don't waste time waiting for vsync. The little tearing is actually pretty unnoticable.
 
Last edited by a moderator:
Trenki said:
SDL (at least on Windows) does not give you a hardware surface, so I don't know if they even do vsync with SW_SURFACEs. I circumvented vsync by only requesting a single buffered surface and rendering to another SDL_Surface and blitting the result to the screen in the end. This also works on the gp2x and can make things a lot faster as you don't waste time waiting for vsync. The little tearing is actually pretty unnoticable.
I do that. I use the same code as in your cow example. But it's still at 75fps. I think it's the vsync but perhaps it isn't.

That when my cpu is at 2400Mhz, when I reduce the speed to 1000Mhz, the framerate drops to 45fps
 
Last edited by a moderator:
@efegea: I don't know what you are doing exactly but on Windows there should not be any vsync at all (at least not for my cow demo which you use as base code). What compiler are you using by the way? When I compile my cow demo with VC++ in Release mode I get 73-75fps on Windows Vista AMD Turion64 X2 2.2Ghz while I get 255-270fps when I compile with GCC 4.2.1 with -O3 -funroll-loops -DNDEBUG. This definitly shows that GCC is better.

@Daid: Simply setting out.w to z may produce a perspective effect but probably one which you do not want. You should construct a normal perspective projection matrix and multiply the vertex with it and assign the result to out.w. You can use my vector_math library for that. It has all the necessary functions.
 
I'm on 64 bit gentoo linux, AMD Athlon64 3400 2.4Ghz, GCC 4.1.2 with -O3 -funroll-loops -DNDEBUG, 125fps on your cow demo
 
Back
Top