Hope Game
Logo Glusoft
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.

GLSL shader

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> 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 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 initilize 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 initilisation 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.
// 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 the project : SDLShader.7z


Dark theme