gl: WIP shader abstraction
This commit is contained in:
parent
17e50fc79e
commit
a61e07bd54
|
@ -53,6 +53,9 @@ GLuint compileShaderFile(GLenum type, char const *path);
|
|||
Returns the shader ID, 0 in case of error. Errors are logged. */
|
||||
GLuint compileShaderSource(GLenum type, char const *code, ssize_t size);
|
||||
|
||||
/* Link a program. This function takes an array of shader IDs. */
|
||||
GLuint link(GLuint *shaders, int count);
|
||||
|
||||
/* Link a program. This function takes a 0-terminated list of shaders IDs and
|
||||
links them into a new program. Returns the new program's ID, or 0 in case
|
||||
of error. Errors are logged. */
|
||||
|
|
|
@ -0,0 +1,330 @@
|
|||
//---------------------------------------------------------------------------//
|
||||
// ," /\ ", Azur: A game engine for CASIO fx-CG and PC //
|
||||
// | _/__\_ | Designed by Lephe' and the Planète Casio community. //
|
||||
// "._`\/'_." License: MIT <https://opensource.org/licenses/MIT> //
|
||||
//---------------------------------------------------------------------------//
|
||||
// azur.gl.shaders: Abstraction of OpenGL 3.3 program objects
|
||||
//
|
||||
// This header provides the ShaderProgram class as an abstraction for OpenGL
|
||||
// shaders. Each shader comes with its own uniform parameters, vertex
|
||||
// attributes, textures, and potentially other settings; ie., it encapsulates
|
||||
// all of the OpenGL state needed to properly run the program.
|
||||
//
|
||||
// ShaderProgram provides the generic mechanisms for using a shader (ie. it has
|
||||
// all the downwards-facing interfaces), but it only exposes a low-level vertex
|
||||
// buffer for describing draw commands. Hence, the intended way to use it is to
|
||||
// subclass it for each shader, and add shader-specific high-level drawing
|
||||
// functions to the derived class.
|
||||
// TODO: Mention how drawing functions add new vertices to the VAO
|
||||
// Access to low-level OpenGL components is provided in case advanced GL things
|
||||
// are needed, but that shouldn't be the case for basic use.
|
||||
//
|
||||
// The initialization of a ShaderProgram goes as follows:
|
||||
// 1. Create a ShaderProgram object (directly or through a derived class);
|
||||
// 2. Load code with one or more calls to addSourceFile(), addSourceText();
|
||||
// 3. Compile the program with compile().
|
||||
//
|
||||
// At this stage, if no error occurred the shader has source code and its
|
||||
// static configuration can be set:
|
||||
// 4. Specify the attribute format (see below).
|
||||
// 5. Load relevant textures or state that are the same for all frames.
|
||||
//
|
||||
// Once that's done, the shader can be used in rendering frames:
|
||||
// 6. Set up frame-specific uniforms, resources, etc.
|
||||
// 7. Queue draw calls.
|
||||
//
|
||||
// TODO: Provide a ShaderPipeline for automatically switching between programs
|
||||
// and checking whether the flow is correct.
|
||||
//
|
||||
// The attribute format describes per-vertex data in the form of a structure
|
||||
// layout, and is basically a meta-level reflection of the structure type used
|
||||
// for VertexAttr. It tells OpenGL how to read the raw vertex data buffer.
|
||||
//---
|
||||
|
||||
#pragma once
|
||||
#include <azur/gl/gl.h>
|
||||
#include <glm/glm.hpp>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
namespace azur::gl {
|
||||
|
||||
template<typename VertexAttr>
|
||||
class ShaderProgram
|
||||
{
|
||||
public:
|
||||
/* Create an empty shader program with no code and no settings. This is an
|
||||
inert call which does nothing, and especially no OpenGL calls. */
|
||||
ShaderProgram() = default;
|
||||
~ShaderProgram();
|
||||
|
||||
/* OpenGL objects cannot be copied, and we don't care about moving yet. */
|
||||
ShaderProgram(ShaderProgram const &) = delete;
|
||||
ShaderProgram(ShaderProgram &&) = delete;
|
||||
|
||||
/*** Initialization ***/
|
||||
|
||||
/* Initialize the shader by allocating some OpenGL objects. */
|
||||
void init();
|
||||
|
||||
/* Add a piece of source code taken from a file. */
|
||||
bool addSourceFile(GLuint type, std::string const &file);
|
||||
/* Add a piece of source given directly as a string. */
|
||||
void addSourceText(GLuint type, std::string const &code)
|
||||
{
|
||||
m_code[type] += code;
|
||||
}
|
||||
void addSourceText(GLuint type, char const *code, ssize_t size = -1)
|
||||
{
|
||||
m_code[type] += std::string {code, size >= 0 ? size : strlen(code)};
|
||||
}
|
||||
|
||||
/* Compile all provided code. */
|
||||
bool compile();
|
||||
|
||||
/* Check whether the program has been compiled successfully. */
|
||||
bool isCompiled() const
|
||||
{
|
||||
return (m_prog != 0);
|
||||
}
|
||||
|
||||
/*** Configuration ***/
|
||||
|
||||
/* Set uniforms. */
|
||||
void setUniform(char const *name, float f);
|
||||
void setUniform(char const *name, float f1, float f2);
|
||||
void setUniform(char const *name, float f1, float f2, float f3);
|
||||
void setUniform(char const *name, glm::vec2 const &v2);
|
||||
void setUniform(char const *name, glm::vec3 const &v3);
|
||||
void setUniform(char const *name, glm::vec4 const &v4);
|
||||
void setUniform(char const *name, glm::mat2 const &m2);
|
||||
void setUniform(char const *name, glm::mat3 const &m3);
|
||||
void setUniform(char const *name, glm::mat4 const &m4);
|
||||
|
||||
/*** Generating draw commands ***/
|
||||
|
||||
/* Add a vertex to the list of vertices for this frame. */
|
||||
void addVertex(VertexAttr &&attr)
|
||||
{
|
||||
m_vertices.push_pack(std::move(attr));
|
||||
}
|
||||
|
||||
/* Add three new vertices (makes a triangle in GL_TRIANGLES mode). */
|
||||
void addVertexTriangle(
|
||||
VertexAttr const &a1, VertexAttr const &a2, VertexAttr const &a3)
|
||||
{
|
||||
m_vertices.push_back(a1);
|
||||
m_vertices.push_back(a2);
|
||||
m_vertices.push_back(a3);
|
||||
}
|
||||
|
||||
/* Add two side-sharing triangles (makes a quad in GL_TRIANGLES mode).
|
||||
VertexAttr must be copyable. */
|
||||
void addVertexQuad(VertexAttr const &a1, VertexAttr const &a2,
|
||||
VertexAttr const &a3, VertexAttr const &a4)
|
||||
{
|
||||
m_vertices.push_back(a1);
|
||||
m_vertices.push_back(a2);
|
||||
m_vertices.push_back(a3);
|
||||
m_vertices.push_back(a2);
|
||||
m_vertices.push_back(a3);
|
||||
m_vertices.push_back(a4);
|
||||
}
|
||||
|
||||
/* Direct access to vertex list, to facilitate faster methods of building a
|
||||
vertex list for any given frame. */
|
||||
std::vector<VertexAttr> &vertices()
|
||||
{
|
||||
return m_vertices;
|
||||
}
|
||||
|
||||
/* Queue a draw call. Indices should be within the range of vertices pushed
|
||||
to the VAO at the time of the call. Info for this call is kept in memory
|
||||
until the rendering phase, so the VAO and the order should be valid
|
||||
until the end of the frame.
|
||||
|
||||
With queueDrawArrays, the vertices at positions [first .. first+count)
|
||||
of the VBO are used to render. With queueDrawElements, the vertices at
|
||||
positions ind[0], .., ind[count-1] are used. Depending on mode, these
|
||||
are combined in pairs, triples, or other ways to render primitives. */
|
||||
void queueDrawArrays(GLenum mode, GLint first, GLsizei count);
|
||||
void queueDrawElements(GLenum mode, GLsizei count, uint8_t const *ind);
|
||||
void queueDrawElements(GLenum mode, GLsizei count, uint16_t const *ind);
|
||||
void queueDrawElements(GLenum mode, GLsizei count, uint32_t const *ind);
|
||||
|
||||
/*** Executing commands ***/
|
||||
|
||||
/* Load buffer data; this is usually executed once at the beginning of the
|
||||
frame. (This is normally called by ShaderPipeline::render().) */
|
||||
void loadBuffer();
|
||||
|
||||
/* Switch to this shader program. This should be called before executing a
|
||||
draw command if the previous shader to run was different. (This is
|
||||
normally called by ShaderPipeline::render().) */
|
||||
void useProgram();
|
||||
|
||||
|
||||
protected:
|
||||
/* Program ID */
|
||||
GLuint m_prog = 0;
|
||||
/* Vertex Array Object and Vertex Buffer Object with parameters */
|
||||
GLuint m_vao = 0, m_vbo = 0;
|
||||
/* Size of the VBO on the GPU */
|
||||
size_t m_vboSize = 0;
|
||||
|
||||
private:
|
||||
/* Map from program type (vertex/etc/fragment shader) to code during the
|
||||
construction phase. */
|
||||
std::map<GLuint, std::string> m_code;
|
||||
/* List of vertices during rendering. */
|
||||
std::vector<VertexAttr> m_vertices;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
void ShaderProgram<T>::init()
|
||||
{
|
||||
glGenVertexArrays(1, &m_vao);
|
||||
glGenBuffers(1, &m_vbo);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
ShaderProgram<T>::~ShaderProgram()
|
||||
{
|
||||
glDeleteProgram(m_prog);
|
||||
glDeleteBuffers(1, &m_vbo);
|
||||
glDeleteVertexArrays(1, &m_vao);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
bool ShaderProgram<T>::addSourceFile(
|
||||
GLuint type, std::string const &file)
|
||||
{
|
||||
extern char *load_file(char const *, size_t *);
|
||||
char *data = load_file(file.c_str(), nullptr);
|
||||
if(!data)
|
||||
return false;
|
||||
|
||||
m_code[type] += data;
|
||||
delete[] data;
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
bool ShaderProgram<T>::compile()
|
||||
{
|
||||
/* List of shader descriptors obtained from OpenGL. */
|
||||
std::vector<GLuint> m_shaders;
|
||||
bool success = true;
|
||||
|
||||
for(auto const &[type, code]: m_code) {
|
||||
GLuint id = compileShaderSource(type, code.c_str(), -1);
|
||||
if(id != 0)
|
||||
m_shaders.push_back(id);
|
||||
else
|
||||
success = false;
|
||||
}
|
||||
|
||||
// TODO: No link error detection?
|
||||
m_prog = success ? link(m_shaders.data(), m_shaders.size()) : 0;
|
||||
|
||||
for(auto id: m_shaders)
|
||||
glDeleteShader(id);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
/* TODO: Complete state associated with each program:
|
||||
1. Uniforms (glUniform*)
|
||||
2. Vertex array (glBindVertexArray)
|
||||
3. Textures (glActiveTexture, glBindTexture)
|
||||
4. Other state (glEnable, glDisable, glBlendFunc)
|
||||
Render with glDrawArrays or glDrawElements.
|
||||
|
||||
TODO: Can we minimize switching (eg. texture switching)? */
|
||||
|
||||
template<typename T>
|
||||
void ShaderProgram<T>::setUniform(char const *name, float f)
|
||||
{
|
||||
glUniform1f(glGetUniformLocation(m_prog, name), f);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void ShaderProgram<T>::setUniform(char const *name, float f1, float f2)
|
||||
{
|
||||
glUniform2f(glGetUniformLocation(m_prog, name), f1, f2);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void ShaderProgram<T>::setUniform(
|
||||
char const *name, float f1, float f2, float f3)
|
||||
{
|
||||
glUniform3f(glGetUniformLocation(m_prog, name), f1, f2, f3);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void ShaderProgram<T>::setUniform(char const *name, glm::vec2 const &v2)
|
||||
{
|
||||
glUniform2fv(glGetUniformLocation(m_prog, name), 1, &v2[0]);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void ShaderProgram<T>::setUniform(char const *name, glm::vec3 const &v3)
|
||||
{
|
||||
glUniform3fv(glGetUniformLocation(m_prog, name), 1, &v3[0]);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void ShaderProgram<T>::setUniform(char const *name, glm::vec4 const &v4)
|
||||
{
|
||||
glUniform4fv(glGetUniformLocation(m_prog, name), 1, &v4[0]);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void ShaderProgram<T>::setUniform(char const *name, glm::mat2 const &m2)
|
||||
{
|
||||
glUniformMatrix2fv(
|
||||
glGetUniformLocation(m_prog, name), 1, GL_FALSE, &m2[0][0]);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void ShaderProgram<T>::setUniform(char const *name, glm::mat3 const &m3)
|
||||
{
|
||||
glUniformMatrix3fv(
|
||||
glGetUniformLocation(m_prog, name), 1, GL_FALSE, &m3[0][0]);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void ShaderProgram<T>::setUniform(char const *name, glm::mat4 const &m4)
|
||||
{
|
||||
glUniformMatrix4fv(
|
||||
glGetUniformLocation(m_prog, name), 1, GL_FALSE, &m4[0][0]);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void ShaderProgram<T>::loadBuffer()
|
||||
{
|
||||
glBindBuffer(GL_ARRAY_BUFFER, m_vbo);
|
||||
|
||||
/* If the size of the VBO is too small or much larger than needed, resize
|
||||
it; otherwise, simply swap the data without reallocating */
|
||||
if(m_vboSize < m_vertices.size() || m_vboSize > m_vertices.size() * 4) {
|
||||
glBufferData(GL_ARRAY_BUFFER, sizeof(T) * m_vertices.size(),
|
||||
m_vertices.data(), GL_DYNAMIC_DRAW);
|
||||
m_vboSize = m_vertices.size();
|
||||
}
|
||||
else {
|
||||
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(T) * m_vertices.size(),
|
||||
m_vertices.data());
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void ShaderProgram<T>::useProgram()
|
||||
{
|
||||
glUseProgram(m_prog);
|
||||
glBindVertexArray(m_vao);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, m_vbo);
|
||||
}
|
||||
|
||||
} /* namespace azur::gl */
|
|
@ -13,10 +13,17 @@
|
|||
#include <string.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
extern char const *azur_glsl__vs_prelude_gles2;
|
||||
extern char const *azur_glsl__vs_prelude_gl3;
|
||||
extern char const *azur_glsl__fs_prelude_gles2;
|
||||
extern char const *azur_glsl__fs_prelude_gl3;
|
||||
|
||||
namespace azur::gl {
|
||||
|
||||
/* Read the full contents of a file into the heap. Returns an malloc'd pointer
|
||||
on success, NULL if an error occurs. */
|
||||
// TODO: Move the load_file() function to a more convenient fs util header
|
||||
static char *load_file(char const *path, size_t *out_size)
|
||||
char *load_file(char const *path, size_t *out_size)
|
||||
{
|
||||
char *contents = NULL;
|
||||
long size = 0;
|
||||
|
@ -40,13 +47,6 @@ load_file_end:
|
|||
return contents;
|
||||
}
|
||||
|
||||
extern char const *azur_glsl__vs_prelude_gles2;
|
||||
extern char const *azur_glsl__vs_prelude_gl3;
|
||||
extern char const *azur_glsl__fs_prelude_gles2;
|
||||
extern char const *azur_glsl__fs_prelude_gl3;
|
||||
|
||||
namespace azur::gl {
|
||||
|
||||
char const *errorString(GLenum ec)
|
||||
{
|
||||
static char str[32];
|
||||
|
@ -157,7 +157,7 @@ GLuint compileShaderSource(GLenum type, char const *code, ssize_t size)
|
|||
return compileShader(type, code, size, "<inline>");
|
||||
}
|
||||
|
||||
static GLuint link(GLuint *shaders, int count)
|
||||
GLuint link(GLuint *shaders, int count)
|
||||
{
|
||||
GLuint prog = glCreateProgram();
|
||||
if(prog == 0) {
|
||||
|
|
Loading…
Reference in New Issue