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