4.0: Image Processing

This tutorial shows how to read and write into textures in a Compute Shader.

Here we will build 2 separate textures, the first will be the source from which the compute shader has to read, and the second texture will contain the processed texture data.

This tutorial wont process the image data, we will do that in later tutorials

Here we are just copying the image data from texture 1 to texture 2.

Compute Shader:

#version 450 core

layout (local_size_x = 1, local_size_y = 1, local_size_z = 1) in;

uniform readonly layout (rgba8) image2D img1;

uniform writeonly image2D img2;

void main() {

ivec2 texel = ivec2(gl_GlobalInvocationID.xy);

vec4 color = imageLoad(img1, texel);

imageStore(img2, texel, color);

}

To be able to read from a texture, we have use a uniform "image variable":

(NOTE: we have to define the internal format of the texture we want to read, here: rgba8)

uniform readonly layout (rgba8) image2D img1;

    • "readonly" = memory qualifier, allows the compute shader only to read

    • "layout (rgba8)" = internal format of the texture

https://www.opengl.org/wiki/Image_Load_Store#Format_qualifiers

To read a texel from a texture, we call:

vec4 color = imageLoad(img1, texel);

https://www.opengl.org/wiki/Image_Load_Store#Image_load

Complete Source Code:

Main.cpp

#include <GL/glew.h>

#include <GLFW/glfw3.h>

#include <glm/glm.hpp>

#include <iostream>

#include <string>

#include "Shader.h"

using namespace std;

using namespace glm;

// main functions

void Initialize();

void Render();

void CleanUp();

GLFWwindow* window = nullptr;

unsigned int program_compute = 0;

unsigned int computeshader = 0;

unsigned int program_render = 0;

unsigned int vertexshader = 0;

unsigned int fragmentshader = 0;

unsigned int vertexarray = 0;

unsigned int vertexbuffer = 0;

unsigned int texture_read = 0;

unsigned int texture_draw = 0;

unsigned int texturewidth = 2;

unsigned int textureheight = 5;

string computeshader_source = {

"#version 450 core\n"

"layout (local_size_x = 1, local_size_y = 1, local_size_z = 1) in;"

"uniform readonly layout (rgba8) image2D img1;"

"uniform writeonly image2D img2;"

"void main() {"

"ivec2 texel = ivec2(gl_GlobalInvocationID.xy);"

"vec4 color = imageLoad(img1, texel);"

"imageStore(img2, texel, color);"

"}"

};

string vertexshader_source = {

"#version 450 core\n"

"in layout (location = 0) vec3 in_position;"

"in layout (location = 1) vec2 in_texcoord;"

"out vec2 texcoord;"

"void main() {"

"gl_Position = vec4(in_position, 1);"

"texcoord = in_texcoord;"

"}"

};

string fragmentshader_source = {

"#version 450 core\n"

"in vec2 texcoord;"

"uniform sampler2D tex1;"

"out layout (location = 0) vec4 out_color;"

"void main() {"

"out_color = texture(tex1, texcoord);"

"}"

};

struct Vertex {

vec3 Position;

vec2 TexCoord;

};

int main(void)

{

/* Initialize the library */

if (!glfwInit())

return -1;

/* Create a windowed mode window and its OpenGL context */

window = glfwCreateWindow(640, 480, "Hello World", NULL, NULL);

if (!window)

{

glfwTerminate();

return -1;

}

/* Make the window's context current */

glfwMakeContextCurrent(window);

/* Initialize GLEW */

if (glewInit() != GLEW_OK)

{

glfwTerminate();

return -1;

}

Initialize();

/* Loop until the user closes the window */

while (!glfwWindowShouldClose(window))

{

/* Render here */

Render();

/* Swap front and back buffers */

glfwSwapBuffers(window);

/* Poll for and process events */

glfwPollEvents();

}

CleanUp();

glfwTerminate();

return 0;

}

void Initialize()

{

// create all objects

//------------------------------------------------------------------------------------------------------------------------

program_compute = glCreateProgram();

program_render = glCreateProgram();

vertexshader = glCreateShader(GL_VERTEX_SHADER);

fragmentshader = glCreateShader(GL_FRAGMENT_SHADER);

computeshader = glCreateShader(GL_COMPUTE_SHADER);

glGenVertexArrays(1, &vertexarray);

glGenBuffers(1, &vertexbuffer);

glGenTextures(1, &texture_read);

glGenTextures(1, &texture_draw);

//------------------------------------------------------------------------------------------------------------------------

// setup programs

//------------------------------------------------------------------------------------------------------------------------

// compute

CompileShader(computeshader, computeshader_source);

LinkProgram(program_compute, { computeshader });

// render

CompileShader(vertexshader, vertexshader_source);

CompileShader(fragmentshader, fragmentshader_source);

LinkProgram(program_render, { vertexshader, fragmentshader });

//------------------------------------------------------------------------------------------------------------------------

// setup vertex array

//------------------------------------------------------------------------------------------------------------------------

glBindVertexArray(vertexarray);

glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);

glVertexAttribPointer(0, 3, GL_FLOAT, false, sizeof(Vertex), (void*)(0));

glVertexAttribPointer(1, 2, GL_FLOAT, false, sizeof(Vertex), (void*)(sizeof(vec3)));

glBindBuffer(GL_ARRAY_BUFFER, 0);

glEnableVertexAttribArray(0);

glEnableVertexAttribArray(1);

glBindVertexArray(0);

//------------------------------------------------------------------------------------------------------------------------

// setup vertex buffer

//------------------------------------------------------------------------------------------------------------------------

// quad

Vertex vertices[] = {

// x y z u v

{ { -0.9f, -0.9f, 0 },{ 0, 0 } },

{ { +0.9f, -0.9f, 0 },{ 1, 0 } },

{ { +0.9f, +0.9f, 0 },{ 1, 1 } },

{ { -0.9f, +0.9f, 0 },{ 0, 1 } },

};

glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);

glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

glBindBuffer(GL_ARRAY_BUFFER, 0);

//------------------------------------------------------------------------------------------------------------------------

// setup textures ("read" and "draw")

//------------------------------------------------------------------------------------------------------------------------

// 2 x 5 texture data

unsigned char texture_read_data[] = {

0xFF, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, // red green

0x00, 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, // blue cyan

0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, // magenta yellow

0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0xFF, // white black

0x50, 0x50, 0x50, 0xFF, 0xA0, 0xA0, 0xA0, 0xFF, // gray gray (brighter)

};

glBindTexture(GL_TEXTURE_2D, texture_read);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, texturewidth, textureheight);

glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, texturewidth, textureheight, GL_RGBA, GL_UNSIGNED_BYTE, texture_read_data);

glBindTexture(GL_TEXTURE_2D, 0);

glBindTexture(GL_TEXTURE_2D, texture_draw);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, texturewidth, textureheight);

glBindTexture(GL_TEXTURE_2D, 0);

// connect "texture_draw" to sampler in fragment shader

unsigned int texture_unit = 1;

glBindTextureUnit(texture_unit, texture_draw);

int location = glGetUniformLocation(program_render, "tex1");

glProgramUniform1i(program_render, location, texture_unit);

// connect "texture_read" to image variable in compute shader

unsigned int image_unit = 2;

glBindImageTexture(image_unit, texture_read, 0, false, 0, GL_READ_ONLY, GL_RGBA8);

location = glGetUniformLocation(program_compute, "img1");

glProgramUniform1i(program_compute, location, image_unit);

// connect "texture_draw" to image variable in compute shader

image_unit = 3;

glBindImageTexture(image_unit, texture_draw, 0, false, 0, GL_WRITE_ONLY, GL_RGBA8);

location = glGetUniformLocation(program_compute, "img2");

glProgramUniform1i(program_compute, location, image_unit);

//------------------------------------------------------------------------------------------------------------------------

// invoke compute shader

//------------------------------------------------------------------------------------------------------------------------

glUseProgram(program_compute);

glDispatchCompute(texturewidth, textureheight, 1);

glUseProgram(0);

/*

invokes the compute shader ...

texturewidth x textureheight x 1

= 2 x 5 x 1

= 10 times (once per texel)

each invocation can be identified by its "uvec3 gl_GlobalInvocationID"

in this case:

gl_GlobalInvocationID.x will be 0 ... 1

gl_GlobalInvocationID.y will be 0 ... 4

gl_GlobalInvocationID.z will be 0

x and y will be used to access a certain texel in the texture

*/

//------------------------------------------------------------------------------------------------------------------------

}

void Render()

{

glClearColor(0.5f, 0.5f, 0.5f, 0); // background = gray

glClear(GL_COLOR_BUFFER_BIT);

// render quad

glUseProgram(program_render);

glBindVertexArray(vertexarray);

glDrawArrays(GL_QUADS, 0, 4);

glBindVertexArray(0);

glUseProgram(0);

}

void CleanUp()

{

// delete all objects

glDeleteProgram(program_compute);

glDeleteProgram(program_render);

glDeleteShader(vertexshader);

glDeleteShader(fragmentshader);

glDeleteShader(computeshader);

glDeleteVertexArrays(1, &vertexarray);

glDeleteBuffers(1, &vertexbuffer);

glDeleteTextures(1, &texture_read);

glDeleteTextures(1, &texture_draw);

}

Shader.h

#pragma once

#include <string>

#include <list>

// shader functions

std::string LoadTextFile(const std::string& filepath);

std::string ShaderTypeName(unsigned int shader);

bool CompileShader(unsigned int shader, const std::string& sourcecode);

std::string ShaderInfoLog(unsigned int shader);

bool LinkProgram(unsigned int program, const std::list<unsigned int>& shaderlist);

std::string ProgramInfoLog(unsigned int program);

Shader.cpp

#include "Shader.h"

#include <iostream>

#include <fstream>

#include <GL/glew.h>

std::string LoadTextFile(const std::string & filepath)

{

std::string result(""), line;

std::fstream f(filepath, std::ios::in);

while (f.good())

{

std::getline(f, line);

result += line + '\n';

}

return result;

}

std::string ShaderTypeName(unsigned int shader)

{

if (glIsShader(shader))

{

int type;

glGetShaderiv(shader, GL_SHADER_TYPE, &type);

if (type == GL_VERTEX_SHADER)

return "Vertex Shader";

if (type == GL_TESS_CONTROL_SHADER)

return "Tessellation Control Shader";

if (type == GL_TESS_EVALUATION_SHADER)

return "Tessellation Evaluation Shader";

if (type == GL_GEOMETRY_SHADER)

return "Geometry Shader";

if (type == GL_FRAGMENT_SHADER)

return "Fragment Shader";

if (type == GL_COMPUTE_SHADER)

return "Compute Shader";

}

return "invalid shader";

}

bool CompileShader(unsigned int shader, const std::string& sourcecode)

{

if (!glIsShader(shader))

{

std::cout << "ERROR: shader compilation failed, no valid shader specified" << std::endl;

return false;

}

if (sourcecode.empty())

{

std::cout << "ERROR: shader compilation failed, no source code specified (" << ShaderTypeName(shader) << ")" << std::endl;

return false;

}

// array of source code components

const char* sourcearray[] = { sourcecode.c_str() };

// set source code

glShaderSource(shader, 1, sourcearray, NULL);

// compile shaders

glCompileShader(shader);

// check compile status

int status;

glGetShaderiv(shader, GL_COMPILE_STATUS, &status);

// successfully compiled shader

if (status == GL_TRUE)

return true;

// show compile errors

std::cout << "ERROR: shader compilation failed (" << ShaderTypeName(shader) << ")" << std::endl << ShaderInfoLog(shader) << std::endl;

return false;

}

std::string ShaderInfoLog(unsigned int shader)

{

if (glIsShader(shader))

{

int logsize;

char infolog[1024] = { 0 };

glGetShaderInfoLog(shader, 1024, &logsize, infolog);

return std::string(infolog);

}

return "invalid shader";

}

bool LinkProgram(unsigned int program, const std::list<unsigned int>& shaderlist)

{

if (!glIsProgram(program))

{

std::cout << "ERROR: shader linking failed, no valid program specified" << std::endl;

return false;

}

// attach all shaders to the program

for (auto& shader : shaderlist)

{

if (glIsShader(shader))

glAttachShader(program, shader);

}

// link program

glLinkProgram(program);

// detach all shaders again

for (auto& shader : shaderlist)

{

if (glIsShader(shader))

glDetachShader(program, shader);

}

int status;

glGetProgramiv(program, GL_LINK_STATUS, &status);

// successfully linked program

if (status == GL_TRUE)

return true;

// show link errors

std::cout << "ERROR: shader linking failed" << std::endl << ProgramInfoLog(program) << std::endl;

return false;

}

std::string ProgramInfoLog(unsigned int program)

{

if (glIsProgram(program))

{

int logsize;

char infolog[1024] = { 0 };

glGetProgramInfoLog(program, 1024, &logsize, infolog);

return std::string(infolog);

}

return "invalid program";

}