Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
.idea/
cmake-build-debug/
/build
3 changes: 3 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ option(GL_VALIDATION_LAYER_BUILD_TESTS "Build tests for the OpenGL Validation La
add_library(gl_validation_layer
src/context.cpp
src/shader.cpp
include/gl_layer/context.h
include/gl_layer/private/context.h
include/gl_layer/private/types.h
)

target_include_directories(gl_validation_layer PUBLIC include/)
Expand Down
11 changes: 10 additions & 1 deletion include/gl_layer/context.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,22 @@
extern "C" {
#endif

typedef struct ContextGLFunctions
{
// TODO: replace the types in the parameters with proper platform-detected ones
// TODO: evaluate whether * should be replaced with something like APIENTRYP in glad
void (*GetActiveUniform)(unsigned, unsigned, int, int*, int*, unsigned*, char*);
int (*GetUniformLocation)(unsigned, const char*);
void (*GetProgramiv)(unsigned, unsigned, int*);
}ContextGLFunctions;

/**
* @brief Initialize the OpenGL Validation Layer
* @param gl_version_major OpenGL context major version.
* @param gl_version_minor OpenGL context minor version.
* @return 0 on success, any other value on error.
*/
int gl_layer_init(unsigned int gl_version_major, unsigned int gl_version_minor);
int gl_layer_init(unsigned int gl_version_major, unsigned int gl_version_minor, const ContextGLFunctions* gl_functions);

/**
* @brief Terminate the OpenGL Validation Layer.
Expand Down
9 changes: 8 additions & 1 deletion include/gl_layer/private/context.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ namespace gl_layer {

class Context {
public:
explicit Context(Version version);
explicit Context(Version version, const ContextGLFunctions* gl_functions);

void set_output_callback(GLLayerOutputFun callback, void* user_data);

Expand All @@ -20,13 +20,20 @@ class Context {
void glAttachShader(unsigned int program, unsigned int shader);

void glGetProgramiv(unsigned int handle, GLenum param, int* params);
void glLinkProgram(GLuint program);
void glUseProgram(unsigned int handle);
void glDeleteProgram(GLuint program);

void validate_program_bound(std::string_view func_name);
bool validate_program_status(GLuint program);

private:
Version gl_version;

GLLayerOutputFun output_fun = nullptr;
void* output_user_data = nullptr;
ContextGLFunctions gl;
GLuint bound_program = 0;

std::unordered_map<unsigned int, Shader> shaders{};
std::unordered_map<unsigned int, Program> programs{};
Expand Down
41 changes: 37 additions & 4 deletions include/gl_layer/private/types.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,40 @@
#define GL_VALIDATION_LAYER_TYPES_H_

#include <vector>
#include <unordered_map>
#include <cstdint>

namespace gl_layer {

using GLenum = unsigned int;
using GLenum = std::uint32_t;
using GLuint = std::uint32_t;
using GLint = std::int32_t;
using GLsizei = std::int32_t;
using GLchar = char;
using GLfloat = float;
using GLboolean = bool;

enum GLShaderInfoParam {
GL_SHADER_TYPE = 0x8B4F,
GL_DELETE_STATUS = 0x8B80,
GL_COMPILE_STATUS = 0x8B81,
GL_LINK_STATUS = 0x8B82,
GL_INFO_LOG_LENGTH = 0x8B84,
GL_SHADER_SOURCE_LENGTH = 0x8B88
GL_SHADER_SOURCE_LENGTH = 0x8B88,
GL_ACTIVE_UNIFORM_MAX_LENGTH = 0x8B87,
GL_ACTIVE_UNIFORMS = 0x8B86
};

enum class CompileStatus {
UNCHECKED = -1,
FAILED = 0,
OK = 1,
};

enum class LinkStatus {
UNCHECKED = -1,
FAILED = 0,
OK = 1,
};

const char* enum_str(GLenum v);
Expand All @@ -23,20 +45,31 @@ struct Version {
unsigned int minor = 0;
};

struct UniformInfo {
GLint array_size;
GLenum type;
// std::string name // the name is probably not useful for us

bool operator==(const UniformInfo& other) const {
return array_size == other.array_size && type == other.type;
}
};

// Represents a shader returned by glCreateShader
struct Shader {
unsigned int handle {};
// If this is -1, this means the compile status was never checked by the host application.
// This is an error that should be reported.
int compile_status = -1;
CompileStatus compile_status = CompileStatus::UNCHECKED;
};

// Represents a shader program returned by glCreateProgram
struct Program {
unsigned int handle {};
std::vector<unsigned int> shaders {};
std::unordered_map<GLint, UniformInfo> uniforms {};
// If this is -1, this means the status was never checked by the host application.
int link_status = -1;
LinkStatus link_status = LinkStatus::UNCHECKED;
};

}
Expand Down
20 changes: 16 additions & 4 deletions src/context.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ static void default_output_func(const char* text, void* = nullptr) {
printf("%s\n", text);
}

Context::Context(Version version) : gl_version(version) {
Context::Context(Version version, const ContextGLFunctions* gl_functions)
: gl_version(version), gl(*gl_functions) {
output_fun = &default_output_func;
}

Expand All @@ -42,12 +43,16 @@ Context* g_context = nullptr;

} // namespace gl_layer

static bool is_func(const char* name, std::string_view func) {
static bool is_func(std::string_view name, std::string_view func) {
return func == name;
}

int gl_layer_init(unsigned int gl_version_major, unsigned int gl_version_minor) {
gl_layer::g_context = new gl_layer::Context(gl_layer::Version{ gl_version_major, gl_version_minor });
static bool func_has(std::string_view name, std::string_view substr) {
return name.find(substr) != std::string_view::npos;
}

int gl_layer_init(unsigned int gl_version_major, unsigned int gl_version_minor, const ContextGLFunctions* gl_functions) {
gl_layer::g_context = new gl_layer::Context(gl_layer::Version{ gl_version_major, gl_version_minor }, gl_functions);
if (gl_layer::g_context) return 0;

return -1;
Expand All @@ -63,6 +68,10 @@ void gl_layer_callback(const char* name, void* func_ptr, int num_args, ...) {
return;
}

using namespace gl_layer; // GL types

std::string_view name = name_c;

va_list args;
va_start(args, num_args);

Expand All @@ -86,6 +95,9 @@ void gl_layer_callback(const char* name, void* func_ptr, int num_args, ...) {
} else if (is_func(name, "glUseProgram")) {
unsigned int program = va_arg(args, unsigned int);
gl_layer::g_context->glUseProgram(program);
} else if (is_func(name, "glLinkProgram")) {
unsigned int program = va_arg(args, unsigned int);
gl_layer::g_context->glLinkProgram(program);
}

va_end(args);
Expand Down
110 changes: 97 additions & 13 deletions src/shader.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#include <gl_layer/context.h>
#include <gl_layer/private/context.h>

#include <memory>

namespace gl_layer {

Expand All @@ -14,7 +14,7 @@ void Context::glCompileShader(unsigned int handle) {
return;
}

shaders.insert({ handle, Shader { handle } });
shaders.emplace(handle, Shader{ handle });
}

void Context::glGetShaderiv(unsigned int handle, GLenum param, int* params) {
Expand All @@ -27,7 +27,7 @@ void Context::glGetShaderiv(unsigned int handle, GLenum param, int* params) {
return;
}

it->second.compile_status = *params;
it->second.compile_status = static_cast<CompileStatus>(*params);
}
}

Expand All @@ -39,9 +39,9 @@ void Context::glAttachShader(unsigned int program, unsigned int shader) {
return;
}

if (it->second.compile_status == -1) {
if (it->second.compile_status == CompileStatus::UNCHECKED) {
output_fmt("glAttachShader(program = %u, shader = %u): Always check shader compilation status before trying to use the object.", program, shader);
} else if (it->second.compile_status == false) {
} else if (it->second.compile_status == CompileStatus::FAILED) {
output_fmt("glAttachShader(program = %u, shader = %u): Attached shader has a compilation error.", program, shader);
}

Expand All @@ -67,26 +67,110 @@ void Context::glGetProgramiv(unsigned int handle, GLenum param, int* params) {
return;
}

it->second.link_status = *params;
it->second.link_status = static_cast<LinkStatus>(*params);
}
}

void Context::glLinkProgram(GLuint program)
{
auto program_it = programs.find(program);
if (program_it == programs.end()) {
output_fmt("glLinkProgram(program = %u): Invalid program handle.", program);
return;
}

auto& programInfo = program_it->second;

// Store all active uniforms in the program (location, array size, and type)
GLint uniform_count{};
gl.GetProgramiv(program, GL_ACTIVE_UNIFORMS, &uniform_count);
if (uniform_count > 0)
{
GLint max_name_len{};
gl.GetProgramiv(program, GL_ACTIVE_UNIFORM_MAX_LENGTH, &max_name_len);

auto uniform_name = std::make_unique<char[]>(max_name_len);

for (int i = 0; i < uniform_count; i++)
{
GLsizei uniform_name_length{};
UniformInfo uniform_info{};
gl.GetActiveUniform(
program,
i,
max_name_len,
&uniform_name_length,
&uniform_info.array_size,
&uniform_info.type,
uniform_name.get());

auto loc = gl.GetUniformLocation(program, uniform_name.get());

programInfo.uniforms.emplace(loc, uniform_info);
}
}
}

void Context::glUseProgram(unsigned int handle) {
auto it = programs.find(handle);
if (it == programs.end()) {
output_fmt("glUseProgram(program = %u): Invalid program handle.", handle);
if (handle == 0) {
bound_program = 0;
return;
}

if (!validate_program_status(handle)) {
return;
}

if (it->second.link_status == -1) {
output_fmt("glUseProgram(program = %u): Always check program link status before trying to use the object.", handle);
// TODO: add optional performance warning for rebinding the same program
//if (handle == bound_program) {
// output_fmt("glUseProgram(program = %u): Program is already bound.", handle);
//}

bound_program = handle;
}

void Context::glDeleteProgram(GLuint program)
{
auto it = programs.find(program);
if (it == programs.end()) {
output_fmt("glUseProgram(program = %u): Invalid program handle.", program);
return;
}

if (it->second.link_status == false) {
output_fmt("glUseProgram(program = %u): Program has a linker error.", handle);
// note: this does not change which program is bound, even if it becomes invalid here

programs.erase(it);
}

void Context::validate_program_bound(std::string_view func_name)
{
if (bound_program == 0) {
output_fmt("%s: No program bound.", func_name.data());
return;
}
}

bool Context::validate_program_status(GLuint program)
{
auto it = programs.find(program);
if (it == programs.end()) {
output_fmt("glUseProgram(program = %u): Invalid program handle.", program);
return false;
}

if (it->second.link_status == LinkStatus::UNCHECKED) {
output_fmt("glUseProgram(program = %u): Always check program link status before trying to use the object.", program);
return false;
}

if (it->second.link_status == LinkStatus::FAILED) {
output_fmt("glUseProgram(program = %u): Program has a linker error.", program);
return false;
}

return true;
}
}
}

}
Loading