Glusoft

How to use a GLSL shader with SDL?

In this tutorial we will load a GLSL shader who simulate rain with SDL, and SDL_gpu.

Shader class

To help with the loading of the shader we will create a class Shader :

class Shader {
public:
// Constructor 
Shader(const std::string& id, const std::string& v_str, const std::string& f_str);

// Destructor
~Shader() {}

// Add an image
void addImg(std::string path); 

// Free the image used in the shader
void freeImg(); 

// Add a variable to the vector 
void addVariable(std::string idV);

// Get the location of a variable
Uint32 getVar(std::string idV); 

// Getter for the id
std::string getId() { return id; }

// Set the image to the shader
void setImgShader();

// Activate the shader
void activate();


private:
// Shader Id
std::string id;

// v vertice shader, f fragment shader, p shader program
Uint32 v, f, l;

 // Array of pair (id variable, location in the shader)
std::vector<std::pair<std::string, Uint32>> variables;

// Shader attributes and uniform locations
GPU_ShaderBlock block;

// Image for the shader
GPU_Image *img;
};

The first thing to do is to load the shader for that we will first load the vertex shader, then the fragment shader and create a shader program after linking them.

The Shader block contains all the attributes and uniforms used in the shader.

Shader(const std::string& id, const std::string& v_str, const std::string& f_str) : id(id), img(NULL)) {
v = GPU_LoadShader(GPU_VERTEX_SHADER, v_str.c_str();
	
if (!v)
	std::cout << "Failed to load vertex shader: " << GPU_GetShaderMessage() << "\n";

f = GPU_LoadShader(GPU_FRAGMENT_SHADER, f_str.c_str());

if (!f)
	std::cout << "Failed to load fragment shader: " << GPU_GetShaderMessage() << "\n";

l = GPU_LinkShaders(v, f);

if (!l)
	std::cout << "Failed to link shader program: " << GPU_GetShaderMessage() << "\n";

block = GPU_LoadShaderBlock(p, "gpu_Vertex", "gpu_TexCoord", NULL, "gpu_ModelViewProjectionMatrix");
}

The destructor is empty we will free the image with freeImg():

void freeImg() {
GPU_FreeImage(img);
}

The shader need to have input data such as time, resolution and texture, for that wee keep the name and the location in a vector.

So we need getters and setters for the variables.

void addVariable(std::string idV) {
Uint32 location = GPU_GetUniformLocation(p, idV.c_str());
variables.push_back(std::make_pair(idV, location));
}

Uint32 getVar(std::string idV) {
auto it = std::find_if(variables.begin(), variables.end(), [idV](std::pair<std::string, Uint32> p) { return p.first == idV; });

if (it != variables.end())
	return it->second;

return (Uint32)(-1);
}

For the texture we need to load the image.

void addImg(std::string path) {
img = GPU_LoadImage(path.c_str());

GPU_SetSnapMode(img, GPU_SNAP_NONE);
GPU_SetWrapMode(img, GPU_WRAP_REPEAT, GPU_WRAP_REPEAT);
}

And to set the image to the shader we will use the function:

void setImgShader() {
GPU_SetShaderImage(img, getVar("tex1"), 1);
}

To render the shader we will need to activate it before rendering the texture.

void activate() {
GPU_ActivateShaderProgram(p, &block);
}

The main function

The first thing to do is to initialize SDL and OpenGL, create the window and load the image.

SDL_Init(SDL_INIT_VIDEO);

float widthScreen = 1920.f;
float heightScreen = 1080.f;

GPU_Target *window = GPU_InitRenderer(GPU_RENDERER_OPENGL_3, widthScreen, heightScreen, GPU_DEFAULT_INIT_FLAGS);

if (window == NULL || ogl_LoadFunctions() == ogl_LOAD_FAILED) {
std::cout << "error initialization OpenGL\n";
}

GPU_Image *field = GPU_LoadImage("field.png");

After that we can start the initialization of the shader.

Shader shad("rain", "v1.vert", "rain.frag");
shad.addVariable("tex0");
shad.addVariable("tex1");
shad.addVariable("globalTime");
shad.addVariable("resolution");
shad.addImg("channel0.psd");

And we are done for the initialization, if you want you can download the vertex shader, frament shader and texture.

The next thing to do is start the main loop for the event and the rendering.
We need to be able to exit the app when pressing the escape key.

SDL_Event event;

bool done = 0;

while (!done) {
while (SDL_PollEvent(&event)) {
	if (event.type == SDL_QUIT)
		done = 1;
	else if (event.type == SDL_KEYDOWN) {
		if (event.key.keysym.sym == SDLK_ESCAPE)
			done = 1;
	}
}

An then we can do the rendering. For the rendering we need to activate the shader, then pass the uniform variable. Finally, render the shader on the screen with GPU_Blit.

// Clear the window
GPU_Clear(window); 

// Activate the shader
shad.activate(); 

// Set uniform variables
GLfloat time = (GLfloat)SDL_GetTicks();
GPU_SetUniformf(shad.getVar("globalTime"), time);
shad.setImgShader();
GPU_SetUniformfv(shad.getVar("resolution"), 2, 1, resolution);

// Render the texture
GPU_Blit(field, NULL, window, field->w / 2.f, field->h / 2.f);

// Desactivate the shader
GPU_DeactivateShaderProgram(); 

// Flush the window
GPU_Flip(window);

The last thing to do is close the loop and free the textures.

}
shad.freeImg();
GPU_FreeImage(field);

GPU_Quit();

return 0;

You can download full source code of the project for a glsl shader with SDL : SDLShader.7z