I have some code that essentially draws a column on the screen of a wall in a raycasting-type 3d engine. I am trying to optimize it, as it takes about 10 milliseconds do draw a million pixels using this, and the vast majority of game time is spent in this loop. However, I don't quite understand what's occurring, particularly the recasting (I modified the "pixel manipulation" sample code from the SDL documentation). "canvas" is the surface I am drawing to, and "hello" is the surface containing the texture for the column.

    int c = (curcol)* canvas->format->BytesPerPixel;
    void *canvaspixels = canvas->pixels;
    Uint16 texpitch = hello->pitch;
    int lim = (drawheight +startdraw) * canvpitch +c + (int) canvaspixels;
    Uint8 *k = (Uint8 *)hello->pixels + (hit)* hello->format->BytesPerPixel;

    for (int j= (startdraw)*(canvpitch)+c + (int) canvaspixels; (j< lim); j+= canvpitch){
        Uint8 *q = (Uint8 *) ((int(h))*(texpitch)+k);
        *(Uint32 *)j = *(Uint32 *)q;
        h += s;

We have void pointers (not sure how those are even represented), 8, 16, and 32 bit ints (h and s are floats), all being intermingled, and while it works, it is quite confusing.


Don't try to handle arbitrary surface formats inside your loop. If for some reason you can't just assume a fixed surface format, write wholly separate loops for each format. Write one loop that handles 24-bit source textures on a 24-bit output, a 32-bit source on a 24-bit output, etc., as needed. Libraries like liboil are meant to make this much easier if you need to handle a very large variety of formats, though I've no experience with that particular library myself.

You could also consider using SIMD instructions where appropriate. Also, you could probably split the screen into separate batches and use threading. You're massively under-utilizing your hardware with such a naive approach. Removing all the heavy casting might also help a lot; on a 32-bit machine, use nothing but 32-bit values in loops like these. Convertizing back and forth between 8-bit, 16-bit, and 32-bit values is not free. That also plays back into the "don't try to support arbitrary formats" inside your loop piece of advice. If you know your loop is handling only a particular format, you can make it much much faster. (16-bit or 32-bit is a wise choice; 24-bit does not align well).

There's also the obvious bit about how you're not using the GPU. Even if you want to avoid modern polygonal rendering, you could write this raycasting renderer as a compute shader. Which of course implicitly gives you SIMD and threading, but on a much larger and more capable scale than the CPU can provide.

As a bit of speculation: trying to use an early 90's rendering technique for a - by 90's standards - massive resolution (1,000,000 pixels is huge compared to the resolutions raycasting was used for, around 320x240 to 640x480, iirc). Also recall that these older games typically ran at a max of 16-bit color if not 8-bit paletted formats. The algorithm simply wasn't meant for today's market expectations. CPUs are significantly faster, of course, but I simply wouldn't be surprised if what you're trying to do is unfeasible; could be wrong.

So far as your note about void pointers, they're "represented" the same as any other pointer. There is no difference between a void* and an int* until you try to dereference the pointer or perform pointer arithmetic. Essentially, int* p; int i = p[20]; is roughly equivalent to void* p; int i = *(int*)((char*)p + 20 * sizeof(int));, excepting any potential aliasing violations (casting pointers like you're doing is technically undefined behavior, though most compilers do exactly what you'd expect).

| improve this answer | |

What do you mean by 'recasting'? If you just mean that the same Uint32* cast appears on both sides of an assignment then that's just to ensure that q, which is a UInt8 pointer, gets read from as if it were a UInt32 pointer, and written into j, which is an int, but needs to be treated as a UInt32 pointer (to accept the value from q). But it's just copying a 32-bit value across. The pitch values tell the routine how far to move to read/write the next value. That's about it really.

I have no idea why an int is being cast to a pointer - that looks like bad code to me - but it's not something that will affect the speed.

I would be surprised if you can optimise that loop as it looks fairly optimal to me. (Although drawing columns like that is rarely optimal, which is why blitting typically does it by rows.)

| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.