Sometimes when working with SDL you may want to have custom effects that run fullscreen for every pixel. In some cases you can just do that on the CPU side, but that quickly becomes a CPU bottleneck as the resolution increases.
One solution to avoid that is to use shaders. The most common way to add shaders to SDL is to use OpenGL, which is multiplatform. Because of that you can write shaders once and then just run those same shaders in all the platforms that you are compiling for. However, in a few cases, you may want to have a custom shaders that works only for DirectX when your code is running on Windows. Some reasons you may want that are:
- you are targetting old or weird platforms that have glitches in their OpenGL support;
- you prefer to write HLSL code and are only running your program on Windows;
- you want the best performance possible when running on Windows;
- you want features that are easier to implement on HLSL when compared to GLSL (which is used in OpenGL).
The Idea
The main idea is to define the same D3D_RenderData
that is used by SDL’s
Direct3d9 driver. This way we can then access everything that’s inside it. In
our case, the piece of information we want to acces is the D3D device.
typedef struct
{
// ...
} D3D_RenderData;
// ... very code, many line ...
IDirect3DDevice9* device = reinterpret_cast<D3D_RenderData*>(renderer->driverdata)->device;
Then we can simply call the normal D3D functions using our device to set the shader:
assert(D3D_OK == IDirect3DDevice9_CreatePixelShader(device, g_ps21_main, &shader));
// ...
assert(D3D_OK == IDirect3DDevice9_SetPixelShader(device, shader));
Compiling HLSL Shaders
To compile HLSL shaders we use fxc.exe
, which is part of the DirectX SDK (it
can be downloaded from Microsoft).
For example, if we have a fractal.hlsl
that we want to compile to a binary and
put that binary in a C array in a header file, we can do that by this command:
fxc /O3 /T ps_2_a fractal.hlsl /Fh fractal.h
Notice we have to set the shader profile (with /T
) to ps_2_a
(it could also
be ps_2_0
or ps_2_b
, but the first is the least limited). This is
because SDL’s driver for Direct3D9 does not setup a vertex shader, so we need to
either use the version 2 or go to 3 and also make a vertex shader, but then we
would need to worry about what exactly we are passing to that vertex shader and
in the case of passing textures we would probably need to drop SDL_Renderer
altogether and almosts write a new custom driver for SDL. At that point it is
probably better to drop SDL and just go with full Windows + DirectX.
Example Project on GitHub
I’ve created a full example project on GitHub, which compiles and produces the beautiful Mandelbrot fractal you saw in the screenshots.