Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Shader validation #5

Merged
merged 7 commits into from
Jul 10, 2022
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;
JuanDiegoMontoya marked this conversation as resolved.
Show resolved Hide resolved

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;
JuanDiegoMontoya marked this conversation as resolved.
Show resolved Hide resolved

// 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;
JuanDiegoMontoya marked this conversation as resolved.
Show resolved Hide resolved
}

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());
JuanDiegoMontoya marked this conversation as resolved.
Show resolved Hide resolved
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