I just started working on my first 2D game project with SDL, and I am using a mix of software rendering (the game is low-res) and hardware-accelerated rendering on textures for speed. I suppose that in theory I could also try to implement the software rendering portions as GPU shaders, but I have 0 experience using shaders and would probably only consider this after the project is finalized due to the effort involved.

From a purely technical perspective, my understanding is that the software rendering approach is basically infeasible when using OpenGL, because the only way to update texture data is to reupload the texture from system RAM to VRAM via the graphics driver, which is what SDL_UpdateTexture() does and what is dreadfully slow. Direct3D on the other hand allows the CPU to directly access the GPU texture data (this is what happens when calling SDL_LockTexture() when using the D3D11 driver), and performance is generally quite good.

The other important advantage of Direct3D in my case is that it allows to alternate between CPU accesses via locking and rendering to the texture using the GPU.

However in SDL, textures with CPU access (SDL_TEXTUREACCESS_STREAMING) and render-to-texture capability (SDL_TEXTUREACCESS_TARGET) are mutually exclusive. So I cannot, to my knowledge, render to a texture and then modify the contents of the the texture via locking, which is what I need in many instances. (The reverse, writing pixels to a texture with the CPU and then rendering to it via the GPU is of course possible by simply rendering a SDL_TEXTUREACCESS_STREAMING texture to a SDL_TEXTUREACCESS_TARGET texture and continuing from there).

I suppose one of the reasons for this decision in SDL is that when using the OpenGL renderer, where “locking” of a texture is basically implemented by keeping a copy of the texture in system ram, which is reuploaded when “unlocking” the texture, the system ram texture may become outdated when the GPU texture is being rendered to. However, in SDL_LockTexture() it already states that “As an optimization, the pixels made available for editing don’t necessarily contain the old texture data.”, so currently this is not guaranteed anyway and I am not entirely sure if this limitation is only by accident.

Anyway, currently I am considering three options:

  • Modifying the D3D11 SDL_CreateTexture() function to allow for both flags SDL_TEXTUREACCESS_STREAMING and SDL_TEXTUREACCESS_TARGET
  • Porting the project from SDL to D3D
  • Writing my own renderer using shaders and only using SDL for basic I/O (although I don’t really know how to do that yet)

I just wanted to post this here to see if somebody has any suggestions for me, or even might shed a bit more light on why SDL_TEXTUREACCESS_STREAMING and SDL_TEXTUREACCESS_TARGET are mutually exclusive.


Is speed important when modifying the contents of your target texture? If not, you can use SDL_RenderReadPixels() to read it, but that can be very slow on some platforms.

Unfortunately yes, I am looking for something I can do in real time.

I noticed that my initial explanation turned out quite verbose. Basically I would like to do something like this:

// Render to ‘tex’ using the GPU
SDL_SetRenderTarget(renderer, tex)

SDL_RenderCopy(renderer, …);

// Render to ‘tex’ using the CPU (special pixel-wise effects like gradients, curves, complex blending etc.)
SDL_LockTexture(tex, NULL, &pixels, &pitch);

pixels[n] = …


// Rendering finished

Indeed if speed was irrelevant, I could perform the GPU rendering part on the CPU as well, or download the GPU rendered texture data using SDL_RenderReadPixels() and then continue with the downloaded data on the CPU, but even doing this once (I might end up wanting to do this even two or three times a frame) will probably destroy the framerate.

When I have wanted to do similar things I have always managed to find solutions using SDL’s existing rendering functions, making it unnecessary to ‘read back’ the contents of the texture. So for example:

Linear gradients can be achieved quite easily by scaling. For example you can create a source texture consisting of just two pixels, one with the color wanted at one extreme of the gradient and the other at the other extreme. Then use SDL_RenderCopy() or SDL_RenderCopyEx() (if the gradient isn’t horizontal or vertical) to stretch it to the final size, using linear interpolation.

I use SDL2_gfx (plus my own extensions for anti-aliased plotting) to plot shapes such as polygons, ellipses, Bézier curves etc. This ultimately writes to the texture using SDL_RenderDrawLine() or SDL_RenderDrawPoint() which in my experience can be acceptably fast so long as batching is enabled.

I have not yet discovered anything that I cannot achieve using SDL_ComposeCustomBlendMode(). Even ‘exclusive-or’ plotting (or at least something close), which I had believed to be impossible, is achievable.

Thank you for all these suggestions! Indeed on the second thought there are a couple of things of things I might be able to port to the SDL rendering functions.

The 2-pixel gradient solution sounds quite interesting to me. In particular, I would like to fill quadrangles with a 2D gradient. If I somehow manage to compute the correct uv coordinates for such a texture, it might just work.

There are some other things where I have no idea how to do it without software rendering. For instance, I have some 2d water with some pseudo-refraction effect. But maybe there is way to precompute some textures, or I might end up going the shader route anyway for performance. Anyway, I suppose I just have to try harder for now. :slight_smile:

If you have a suitable browser (most desktop browsers work, but not Safari) you can see a linear gradient created ‘live’ using this technique here. I’m also using a custom blend mode for masking when combining the ‘reflected’ sectors.

After reading the word “BASIC”, of course I could not help having a very thorough look at your website and had to smile a lot. I just converted my current project from BlitzBasic to C/SDL due to language limitations, and even though I never used BBC BASIC (too young, wrong country), I noticed that you even offer a translator from QBASIC, with which I started. I also enjoyed all the trivia and the connection between BBC BASIC and ARM. Thank you very much for your work!

1 Like