3. Draw Image

In this tutorial we write into a texture using a Compute Shader.

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

Compute Shader:

#version 450 core

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

uniform writeonly image2D img1;

void main() {

ivec2 texel = ivec2(gl_GlobalInvocationID.xy);

vec4 color = vec4(0, 1, 0, 1) * (texel.x + 1) / imageSize(img1).x * (texel.y + 1) / imageSize(img1).y;

imageStore(img1, texel, color);

}

An uniform "Image Variable" is necessary to have access to a texture:

(like a uniform "sampler" for the Fragment Shader when rendering primitives)

uniform writeonly image2D img1;

    • "writeonly" = memory qualifier, allows the compute shader only to write into the texture

    • "image2D" = 2D texture is accessed

    • "img1" = just the variable name

The texture we want to access has to be bound to a certain "image unit":

(like to a "texture unit" when sampling from it in a fragment shader)

unsigned int image_unit = 2;

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

https://www.opengl.org/wiki/GLAPI/glBindImageTexture

Now we connect the image variable "img1" in the compute shader to the image unit where the texture is bound to:

int location = glGetUniformLocation(program_compute, "img1");

glProgramUniform1i(program_compute, location, image_unit);

The texture itself is build like that:

glBindTexture(GL_TEXTURE_2D, texture);

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);

Here we use "glTexStorage2D(...)" to allocate memory for the texture.

https://www.opengl.org/wiki/GLAPI/glTexStorage2D

When everything is set up, we invoke the compute shader:

glUseProgram(program_compute);

glDispatchCompute(texturewidth, textureheight, 1);

glUseProgram(0);

texturewidth = 2

textureheight = 7

So the compute shader will be invoked 2 x 7 x 1 = 14 times, each invocation can be considered as separate threads.

Each invocation has its unique "uvec3 gl_GlobalInvocationID", we use that to determine in which texel the invocation has to write into.

ivec2 texel = ivec2(gl_GlobalInvocationID.xy);

We write a certain color into the texture like this:

imageStore(img1, texel, color);

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

We can query the size of the texture by calling:

ivec2 size = imageSize(img1);

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 = 0;

unsigned int texturewidth = 2;

unsigned int textureheight = 7;

string computeshader_source = {

"#version 450 core\n"

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

"uniform writeonly image2D img1;"

"void main() {"

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

"vec4 color = vec4(0, 1, 0, 1) * (texel.x + 1) / imageSize(img1).x * (texel.y + 1) / imageSize(img1).y;"

"imageStore(img1, 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);

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

// 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 texture

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

glBindTexture(GL_TEXTURE_2D, texture);

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 to sampler in fragment shader

unsigned int texture_unit = 1;

glBindTextureUnit(texture_unit, texture);

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

glProgramUniform1i(program_render, location, texture_unit);

// connect texture to image variable in compute shader

unsigned int image_unit = 2;

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

location = glGetUniformLocation(program_compute, "img1");

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 7 x 1

= 14 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 ... 6

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);

}

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);

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";

}