Mixing SDL3 basic renderer and shaders

I’m currently working on a game that uses SDL2 and the rendering is limited to painting simple textures, all in 2D. I don’t need anything more than simple texture rendering, so the built-in SDL2 renderer is more than enough.

When SDL3 is released in stable, I will update my game sources. One of the new features from SDL3 that I would like to use are shaders — I would like to use them to apply filters to the texture with the final image of the game frame (blur, glow, lower number of colors, etc.).

Is SDL3 designed in such a way that on one hand you can use the basic renderer, but also use shaders to modify the texture content?

3 Likes

You mean this?

Actually, my question doesn’t make much sense, considering that SDL_Texture is not the same as SDL_GPUTexture. And if so, I can’t just do all the rendering with 2D render and then pass SDL_Texture to the shader for processing.

I mean, I can’t do it directly, but I can always copy SDL_Texture data to SDL_GPUTexture, execute a shader, and then copy that data back to SDL_Texture. That means you can have all your rendering based on 2D render, and only use SDL_GPUDevice to access shaders. Sounds silly, but it solves the problem.

If GPU API ever gets wrapper functions that allow rendering simple things with single function calls (like current SDL_RenderTexture, SDL_RenderLine etc.), then on the one hand it will be easy to render such primitives (for someone who doesn’t have time to learn how rendering works and/or doesn’t want to write tons of lines of code), and on the other hand, advanced things will still be possible to do (in the way it is currently possible with GPU API).

To summarize, if I want to use shaders, I will have to rewrite my game’s code anyway and replace the 2D render API with the GPU API. Unfortunately, each function call from the 2D render API will require a bunch of instructions from the GPU API, which is why the above-mentioned wrappers would be a huge convenience.

2 Likes

Honestly, GPU API looks awesome, but its entry threshold is much higher than 2D render API. IMO the best solution would be to implement SDL render API on top of GPU API and remove hardware rendering from SDL render API (leaving only software rendering).

For example, I am not familiar with 3D rendering, I have never done it because I did not need it and I still do not need it. The only thing I need is rendering 2D textures on 2D textures, with color/alpha modulation. That is why I use 2D render API, because I do not have to spend time learning all this GPU magic and sculpting in backend APIs (like OpenGL API) — I call one/a few functions and that’s it. Unfortunately, 2D render API does not support shaders, and I will need them to apply simple filters on the final texture of the game frame (bloom, distortion, custom color palette etc.).

That is why 2D render was created, so that everyone could use hardware rendering, without having to delve into how the GPU works at all. It would be very good if the same could be done with GPU API.

2 Likes

This will be possible in SDL 3.4.0, here’s an example of screen effects using the SDL GPU 2D renderer:

4 Likes

You don’t even know how happy this news makes me. Thank you! :star_struck:

From what I see in the test program, rendering with a custom shader is done using the SDL_RenderTexture function:

So this method requires two textures. What if I wanted to use my own data buffer as the source data for the shader, not the texture?

So for example, I have a buffer that is an array of structures and each structure contains a few fields with different parameters, and the shader would use this data to render something on the renderer’s target texture. This way it would be possible to provide the shader with more data, instead of just color data.

I’m just asking out of curiosity, because something like this would provide a lot of possibilities while still remaining with the 2D render API.

I haven’t tried it, but it should work if you supply custom data to your shader effect and then use SDL_RenderFillRect(). SDL_RenderTexture() is useful if you want a texture automatically included, but if you’re already supplying the data your shader needs, it’s not necessary.

Given the test program in the repo, how can custom data be passed to the shader effect? I can’t figure it out.

Just in case, what I mean is that this buffer with custom data will be modified in every game frame, so rendering each frame must provide a buffer with new content to the shader.

In theory I could do this now, except I would have to use a streamable texture as a buffer for custom data, transfer data via SDL_LockTexture and SDL_UnlockTexture, and then (like in the test program) call SDL_RenderTexture with that texture-buffer. But that’s more of a workaround than a sensible and clean solution.

If the buffer is small enough (say, a few hundred bytes) you could push it to a fragment uniform, like the demo does for the various effects shader data.

SDL GPU requires you to explicitly copy data from the CPU to the GPU (you can’t just directly bind a CPU buffer to use in a shader or draw command), which if exposed via SDL_Renderer would probably still involve creating a GPU buffer and then every time you want to change it having to call something similar to SDL_Lock/UnlockTexture() (SDL_Lock/UnlockGPUBuffer or some such)

1 Like

Yes, but it’s not just the SDL GPU that requires it — the 2D renderer API as well.

I was thinking about sending additional data to the GPU and rendering the frame texture using the shader, because my buffer is relatively small (336×240×4 bytes, 315KiB), so instead of calculating the target color components on the CPU side, I could send a buffer with parameters that the shader would simply use to calculate the colors of the output pixels.

Currently, I calculate the pixel components on the CPU (using multiple threads), then use SDL_LockTexture and SDL_UnlockTexture to copy this buffer to the texture and then use this texture for rendering the frame in the window. The problem is that sending it to the GPU using SDL_LockTexture and SDL_UnlockTexture is surprisingly expensive — it takes 0.4ms for OpenGL and a staggering 2.5ms for Direct3D 12 on my Intel® Core™ i5-9500T. It’s a bit strange that such a small amount of data takes so long to transfer.

I suspect that transferring such a buffer using the GPU API will also be similarly time-consuming, so it will be more profitable to calculate the final pixel colors in those threads (on the CPU side) and send the final buffer consisting of the target RGBA components to the GPU.

Which is because to use this new functionality you have to use the 2D renderer’s SDL GPU backend (but all GPU APIs work this way AFAIK).

Digging into it a little more, it looks like the render state description you have to provide in order to use your own shaders has fields to specify your own buffers, but (for now) you have to create the buffers using direct SDL GPU code and upload data to it with SDL GPU’s transfer buffers.

I was more referring to the fact that since textures are allocated in VRAM, to do anything with them you need to transfer data between RAM↔VRAM. But thanks for elaborating on the topic.

I’ll hold off on ideas for now and just wait for SDL 3.4.0 to be released so I can see the final API and play around with it. :wink:

1 Like