diff --git a/docs/modernization/build-inventory.md b/docs/modernization/build-inventory.md index eef9615..17061bf 100644 --- a/docs/modernization/build-inventory.md +++ b/docs/modernization/build-inventory.md @@ -228,8 +228,11 @@ Known local toolchain state: names directly. It also owns the PanoPainter shader uniform catalog and legacy hash mapping used by `Shader` active-uniform discovery and the uniform uniqueness check. Legacy `Shader` program use/delete, uniform writes, and - attribute-location lookup now consume tested dispatch contracts here; - compile/link and active-uniform discovery are still retained legacy GL calls. + attribute-location lookup now consume tested dispatch contracts here. + Legacy shader source compilation, shader deletion, program attach/link, + attribute rebinding, active-uniform count/enumeration, and uniform-location + discovery now consume tested dispatch contracts as well, leaving the retained + shader utility with thin GL adapter functions. App OpenGL initialization debug severity, debug output, GL info string, renderer API viewport/scissor rect conversion, default depth/program-point/ line-smooth state, blend factor/equation, and UI render-target RGBA8 format diff --git a/docs/modernization/roadmap.md b/docs/modernization/roadmap.md index 766f4ae..0d4891f 100644 --- a/docs/modernization/roadmap.md +++ b/docs/modernization/roadmap.md @@ -1120,9 +1120,11 @@ Results: names used by `Shader`, preserves the legacy hash ids, and rejects empty, unnamed, null-name, mismatched-hash, and duplicate-name catalogs. Legacy `Shader` program use/delete, uniform writes, and attribute-location - lookups now execute through tested `pp_renderer_gl` dispatch contracts; - shader compile/link and active-uniform discovery remain the next shader - boundary work. + lookups now execute through tested `pp_renderer_gl` dispatch contracts. + Legacy shader source compilation, shader deletion, program attach/link, + attribute rebinding, active-uniform count/enumeration, and uniform-location + discovery also execute through tested `pp_renderer_gl` dispatch contracts, + leaving only thin GL adapter functions in the retained `Shader` utility. - `pp_renderer_gl_command_plan_tests` covers the headless OpenGL command planner for recorded render-pass clear masks/values, viewport/scissor state, blend/depth/sampler state, texture format mapping, mesh/draw primitive modes, diff --git a/src/renderer_gl/opengl_capabilities.cpp b/src/renderer_gl/opengl_capabilities.cpp index c978576..70b7c71 100644 --- a/src/renderer_gl/opengl_capabilities.cpp +++ b/src/renderer_gl/opengl_capabilities.cpp @@ -1,4 +1,5 @@ #include "renderer_gl/opengl_capabilities.h" +#include "renderer_gl/shader_bindings.h" #include #include @@ -1030,6 +1031,190 @@ pp::foundation::Result get_opengl_attribute_location( dispatch.get_attrib_location(program_id, attribute_name)); } +pp::foundation::Result compile_opengl_shader_source( + std::uint32_t stage, + const char* source, + char* info_log, + std::int32_t info_log_capacity, + OpenGlShaderCompileDispatch dispatch) noexcept +{ + if (dispatch.create_shader == nullptr + || dispatch.shader_source == nullptr + || dispatch.compile_shader == nullptr + || dispatch.get_shader_integer == nullptr + || dispatch.get_shader_info_log == nullptr) { + return pp::foundation::Result::failure( + pp::foundation::Status::invalid_argument("OpenGL shader compile dispatch callbacks must not be null")); + } + + if (stage == 0U || source == nullptr || source[0] == '\0' || info_log == nullptr || info_log_capacity <= 0) { + return pp::foundation::Result::failure( + pp::foundation::Status::invalid_argument("OpenGL shader compile parameters are invalid")); + } + + const auto shader_id = dispatch.create_shader(stage); + if (shader_id == 0U) { + return pp::foundation::Result::failure( + pp::foundation::Status::out_of_range("OpenGL shader allocation returned id 0")); + } + + const char* const sources[] { source }; + dispatch.shader_source(shader_id, 1, sources); + dispatch.compile_shader(shader_id); + + OpenGlShaderCompileInfo info { + .shader_id = shader_id, + }; + dispatch.get_shader_integer(shader_id, shader_compile_status_query(), &info.compile_status); + dispatch.get_shader_info_log(shader_id, info_log_capacity, &info.info_log_length, info_log); + return pp::foundation::Result::success(info); +} + +pp::foundation::Status delete_opengl_shader( + std::uint32_t shader_id, + OpenGlShaderDeleteDispatch dispatch) noexcept +{ + if (dispatch.delete_shader == nullptr) { + return pp::foundation::Status::invalid_argument("OpenGL shader delete dispatch callback must not be null"); + } + + if (shader_id == 0U) { + return pp::foundation::Status::success(); + } + + dispatch.delete_shader(shader_id); + return pp::foundation::Status::success(); +} + +pp::foundation::Result link_opengl_shader_program( + std::uint32_t vertex_shader_id, + std::uint32_t fragment_shader_id, + std::span attribute_bindings, + char* info_log, + std::int32_t info_log_capacity, + OpenGlProgramLinkDispatch dispatch) noexcept +{ + if (dispatch.create_program == nullptr + || dispatch.attach_shader == nullptr + || dispatch.delete_shader == nullptr + || dispatch.link_program == nullptr + || dispatch.get_attrib_location == nullptr + || dispatch.bind_attrib_location == nullptr + || dispatch.get_program_integer == nullptr + || dispatch.get_program_info_log == nullptr) { + return pp::foundation::Result::failure( + pp::foundation::Status::invalid_argument("OpenGL program link dispatch callbacks must not be null")); + } + + if (vertex_shader_id == 0U + || fragment_shader_id == 0U + || attribute_bindings.empty() + || info_log == nullptr + || info_log_capacity <= 0) { + return pp::foundation::Result::failure( + pp::foundation::Status::invalid_argument("OpenGL program link parameters are invalid")); + } + + const auto program_id = dispatch.create_program(); + if (program_id == 0U) { + return pp::foundation::Result::failure( + pp::foundation::Status::out_of_range("OpenGL program allocation returned id 0")); + } + + dispatch.attach_shader(program_id, vertex_shader_id); + dispatch.attach_shader(program_id, fragment_shader_id); + dispatch.delete_shader(vertex_shader_id); + dispatch.delete_shader(fragment_shader_id); + dispatch.link_program(program_id); + + for (const auto& binding : attribute_bindings) { + if (binding.name != nullptr && dispatch.get_attrib_location(program_id, binding.name) != -1) { + dispatch.bind_attrib_location(program_id, binding.location, binding.name); + } + } + + OpenGlProgramLinkInfo info { + .program_id = program_id, + }; + dispatch.link_program(program_id); + dispatch.get_program_integer(program_id, program_link_status_query(), &info.link_status); + dispatch.get_program_info_log(program_id, info_log_capacity, &info.info_log_length, info_log); + return pp::foundation::Result::success(info); +} + +pp::foundation::Result query_opengl_program_integer( + std::uint32_t program_id, + std::uint32_t query, + OpenGlProgramIntegerDispatch dispatch) noexcept +{ + if (dispatch.get_program_integer == nullptr) { + return pp::foundation::Result::failure( + pp::foundation::Status::invalid_argument("OpenGL program integer dispatch callback must not be null")); + } + + if (program_id == 0U || query == 0U) { + return pp::foundation::Result::failure( + pp::foundation::Status::invalid_argument("OpenGL program integer parameters are invalid")); + } + + std::int32_t value = 0; + dispatch.get_program_integer(program_id, query, &value); + return pp::foundation::Result::success(value); +} + +pp::foundation::Result get_opengl_active_uniform( + std::uint32_t program_id, + std::uint32_t index, + char* name, + std::int32_t name_capacity, + OpenGlActiveUniformDispatch dispatch) noexcept +{ + if (dispatch.get_active_uniform == nullptr) { + return pp::foundation::Result::failure( + pp::foundation::Status::invalid_argument("OpenGL active uniform dispatch callback must not be null")); + } + + if (program_id == 0U || name == nullptr || name_capacity <= 0) { + return pp::foundation::Result::failure( + pp::foundation::Status::invalid_argument("OpenGL active uniform parameters are invalid")); + } + + OpenGlActiveUniformInfo info {}; + dispatch.get_active_uniform( + program_id, + index, + name_capacity, + &info.length, + &info.size, + &info.type, + name); + if (info.length >= 0 && info.length < name_capacity) { + name[info.length] = '\0'; + } else { + name[name_capacity - 1] = '\0'; + } + return pp::foundation::Result::success(info); +} + +pp::foundation::Result get_opengl_uniform_location( + std::uint32_t program_id, + const char* uniform_name, + OpenGlUniformLocationDispatch dispatch) noexcept +{ + if (dispatch.get_uniform_location == nullptr) { + return pp::foundation::Result::failure( + pp::foundation::Status::invalid_argument("OpenGL uniform-location dispatch callback must not be null")); + } + + if (program_id == 0U || uniform_name == nullptr || uniform_name[0] == '\0') { + return pp::foundation::Result::failure( + pp::foundation::Status::invalid_argument("OpenGL uniform-location parameters are invalid")); + } + + return pp::foundation::Result::success( + dispatch.get_uniform_location(program_id, uniform_name)); +} + std::uint32_t extension_count_query() noexcept { return gl_num_extensions; diff --git a/src/renderer_gl/opengl_capabilities.h b/src/renderer_gl/opengl_capabilities.h index 37c1ec3..458cdee 100644 --- a/src/renderer_gl/opengl_capabilities.h +++ b/src/renderer_gl/opengl_capabilities.h @@ -9,6 +9,8 @@ namespace pp::renderer::gl { +struct OpenGlAttributeBinding; + struct OpenGlRuntime { bool desktop_gl = false; bool gles = false; @@ -148,6 +150,24 @@ struct OpenGlTexture2DReadbackResult { bool pixels_read = false; }; +struct OpenGlShaderCompileInfo { + std::uint32_t shader_id = 0; + std::int32_t compile_status = 0; + std::int32_t info_log_length = 0; +}; + +struct OpenGlProgramLinkInfo { + std::uint32_t program_id = 0; + std::int32_t link_status = 0; + std::int32_t info_log_length = 0; +}; + +struct OpenGlActiveUniformInfo { + std::int32_t length = 0; + std::int32_t size = 0; + std::uint32_t type = 0; +}; + struct OpenGlFramebufferRect { std::int32_t x0 = 0; std::int32_t y0 = 0; @@ -232,6 +252,47 @@ using OpenGlUniformMatrix4fvFn = void (*)( using OpenGlUniform1iFn = void (*)(std::int32_t location, std::int32_t value) noexcept; using OpenGlUniform1fFn = void (*)(std::int32_t location, float value) noexcept; using OpenGlGetAttribLocationFn = std::int32_t (*)(std::uint32_t program, const char* name) noexcept; +using OpenGlCreateShaderFn = std::uint32_t (*)(std::uint32_t stage) noexcept; +using OpenGlShaderSourceFn = void (*)( + std::uint32_t shader, + std::int32_t count, + const char* const* sources) noexcept; +using OpenGlCompileShaderFn = void (*)(std::uint32_t shader) noexcept; +using OpenGlGetShaderIntegerFn = void (*)( + std::uint32_t shader, + std::uint32_t query, + std::int32_t* value) noexcept; +using OpenGlGetShaderInfoLogFn = void (*)( + std::uint32_t shader, + std::int32_t capacity, + std::int32_t* length, + char* info_log) noexcept; +using OpenGlDeleteShaderFn = void (*)(std::uint32_t shader) noexcept; +using OpenGlCreateProgramFn = std::uint32_t (*)() noexcept; +using OpenGlAttachShaderFn = void (*)(std::uint32_t program, std::uint32_t shader) noexcept; +using OpenGlLinkProgramFn = void (*)(std::uint32_t program) noexcept; +using OpenGlBindAttribLocationFn = void (*)( + std::uint32_t program, + std::uint32_t location, + const char* name) noexcept; +using OpenGlGetProgramIntegerFn = void (*)( + std::uint32_t program, + std::uint32_t query, + std::int32_t* value) noexcept; +using OpenGlGetProgramInfoLogFn = void (*)( + std::uint32_t program, + std::int32_t capacity, + std::int32_t* length, + char* info_log) noexcept; +using OpenGlGetActiveUniformFn = void (*)( + std::uint32_t program, + std::uint32_t index, + std::int32_t capacity, + std::int32_t* length, + std::int32_t* size, + std::uint32_t* type, + char* name) noexcept; +using OpenGlGetUniformLocationFn = std::int32_t (*)(std::uint32_t program, const char* name) noexcept; using OpenGlBindFramebufferFn = void (*)(std::uint32_t target, std::uint32_t framebuffer) noexcept; using OpenGlBindTextureFn = void (*)(std::uint32_t target, std::uint32_t texture) noexcept; using OpenGlBindSamplerFn = void (*)(std::uint32_t unit, std::uint32_t sampler) noexcept; @@ -482,6 +543,41 @@ struct OpenGlAttributeLocationDispatch { OpenGlGetAttribLocationFn get_attrib_location = nullptr; }; +struct OpenGlShaderCompileDispatch { + OpenGlCreateShaderFn create_shader = nullptr; + OpenGlShaderSourceFn shader_source = nullptr; + OpenGlCompileShaderFn compile_shader = nullptr; + OpenGlGetShaderIntegerFn get_shader_integer = nullptr; + OpenGlGetShaderInfoLogFn get_shader_info_log = nullptr; +}; + +struct OpenGlShaderDeleteDispatch { + OpenGlDeleteShaderFn delete_shader = nullptr; +}; + +struct OpenGlProgramLinkDispatch { + OpenGlCreateProgramFn create_program = nullptr; + OpenGlAttachShaderFn attach_shader = nullptr; + OpenGlDeleteShaderFn delete_shader = nullptr; + OpenGlLinkProgramFn link_program = nullptr; + OpenGlGetAttribLocationFn get_attrib_location = nullptr; + OpenGlBindAttribLocationFn bind_attrib_location = nullptr; + OpenGlGetProgramIntegerFn get_program_integer = nullptr; + OpenGlGetProgramInfoLogFn get_program_info_log = nullptr; +}; + +struct OpenGlProgramIntegerDispatch { + OpenGlGetProgramIntegerFn get_program_integer = nullptr; +}; + +struct OpenGlActiveUniformDispatch { + OpenGlGetActiveUniformFn get_active_uniform = nullptr; +}; + +struct OpenGlUniformLocationDispatch { + OpenGlGetUniformLocationFn get_uniform_location = nullptr; +}; + [[nodiscard]] OpenGlCapabilities detect_opengl_capabilities( std::span extensions, OpenGlRuntime runtime) noexcept; @@ -603,6 +699,36 @@ struct OpenGlAttributeLocationDispatch { std::uint32_t program_id, const char* attribute_name, OpenGlAttributeLocationDispatch dispatch) noexcept; +[[nodiscard]] pp::foundation::Result compile_opengl_shader_source( + std::uint32_t stage, + const char* source, + char* info_log, + std::int32_t info_log_capacity, + OpenGlShaderCompileDispatch dispatch) noexcept; +[[nodiscard]] pp::foundation::Status delete_opengl_shader( + std::uint32_t shader_id, + OpenGlShaderDeleteDispatch dispatch) noexcept; +[[nodiscard]] pp::foundation::Result link_opengl_shader_program( + std::uint32_t vertex_shader_id, + std::uint32_t fragment_shader_id, + std::span attribute_bindings, + char* info_log, + std::int32_t info_log_capacity, + OpenGlProgramLinkDispatch dispatch) noexcept; +[[nodiscard]] pp::foundation::Result query_opengl_program_integer( + std::uint32_t program_id, + std::uint32_t query, + OpenGlProgramIntegerDispatch dispatch) noexcept; +[[nodiscard]] pp::foundation::Result get_opengl_active_uniform( + std::uint32_t program_id, + std::uint32_t index, + char* name, + std::int32_t name_capacity, + OpenGlActiveUniformDispatch dispatch) noexcept; +[[nodiscard]] pp::foundation::Result get_opengl_uniform_location( + std::uint32_t program_id, + const char* uniform_name, + OpenGlUniformLocationDispatch dispatch) noexcept; [[nodiscard]] std::uint32_t extension_count_query() noexcept; [[nodiscard]] std::uint32_t extension_string_name() noexcept; diff --git a/src/shader.cpp b/src/shader.cpp index 3c315e4..176fae7 100644 --- a/src/shader.cpp +++ b/src/shader.cpp @@ -88,6 +88,125 @@ std::int32_t get_opengl_attrib_location(std::uint32_t program, const char* name) return static_cast(glGetAttribLocation(static_cast(program), name)); } +std::uint32_t create_opengl_shader(std::uint32_t stage) noexcept +{ + return static_cast(glCreateShader(static_cast(stage))); +} + +void set_opengl_shader_source( + std::uint32_t shader, + std::int32_t count, + const char* const* sources) noexcept +{ + glShaderSource( + static_cast(shader), + static_cast(count), + reinterpret_cast(sources), + nullptr); +} + +void compile_opengl_shader(std::uint32_t shader) noexcept +{ + glCompileShader(static_cast(shader)); +} + +void query_opengl_shader_integer( + std::uint32_t shader, + std::uint32_t query, + std::int32_t* value) noexcept +{ + glGetShaderiv( + static_cast(shader), + static_cast(query), + reinterpret_cast(value)); +} + +void get_opengl_shader_info_log( + std::uint32_t shader, + std::int32_t capacity, + std::int32_t* length, + char* info_log) noexcept +{ + glGetShaderInfoLog( + static_cast(shader), + static_cast(capacity), + reinterpret_cast(length), + reinterpret_cast(info_log)); +} + +void delete_opengl_shader(std::uint32_t shader) noexcept +{ + glDeleteShader(static_cast(shader)); +} + +std::uint32_t create_opengl_program() noexcept +{ + return static_cast(glCreateProgram()); +} + +void attach_opengl_shader(std::uint32_t program, std::uint32_t shader) noexcept +{ + glAttachShader(static_cast(program), static_cast(shader)); +} + +void link_opengl_program(std::uint32_t program) noexcept +{ + glLinkProgram(static_cast(program)); +} + +void bind_opengl_attrib_location(std::uint32_t program, std::uint32_t location, const char* name) noexcept +{ + glBindAttribLocation(static_cast(program), static_cast(location), name); +} + +void query_opengl_program_integer( + std::uint32_t program, + std::uint32_t query, + std::int32_t* value) noexcept +{ + glGetProgramiv( + static_cast(program), + static_cast(query), + reinterpret_cast(value)); +} + +void get_opengl_program_info_log( + std::uint32_t program, + std::int32_t capacity, + std::int32_t* length, + char* info_log) noexcept +{ + glGetProgramInfoLog( + static_cast(program), + static_cast(capacity), + reinterpret_cast(length), + reinterpret_cast(info_log)); +} + +void get_opengl_active_uniform( + std::uint32_t program, + std::uint32_t index, + std::int32_t capacity, + std::int32_t* length, + std::int32_t* size, + std::uint32_t* type, + char* name) noexcept +{ + glGetActiveUniform( + static_cast(program), + static_cast(index), + static_cast(capacity), + reinterpret_cast(length), + reinterpret_cast(size), + reinterpret_cast(type), + reinterpret_cast(name)); +} + +std::int32_t get_opengl_uniform_location(std::uint32_t program, const char* name) noexcept +{ + return static_cast(glGetUniformLocation(static_cast(program), name)); +} + } std::map ShaderManager::m_shaders; @@ -247,113 +366,212 @@ bool Shader::create(const std::string& vertex, const std::string& fragment) bool ret = true; App::I->render_task([this, &ret, vertex, fragment] { - GLint status; static char infolog[4096]; - int infolen; - const GLchar* source; - - auto vs = glCreateShader(vertex_shader_stage()); - if (!vs) + const auto vertex_shader = pp::renderer::gl::compile_opengl_shader_source( + pp::renderer::gl::vertex_shader_stage(), + vertex.c_str(), + infolog, + static_cast(sizeof(infolog)), + pp::renderer::gl::OpenGlShaderCompileDispatch { + .create_shader = create_opengl_shader, + .shader_source = set_opengl_shader_source, + .compile_shader = compile_opengl_shader, + .get_shader_integer = query_opengl_shader_integer, + .get_shader_info_log = get_opengl_shader_info_log, + }); + if (!vertex_shader.ok()) { ret = false; + LOG("Shader::create() vertex compile failed because: %s", vertex_shader.status().message); return; } - source = vertex.c_str(); - glShaderSource(vs, 1, &source, nullptr); - glCompileShader(vs); - glGetShaderiv(vs, shader_compile_status_query(), &status); - glGetShaderInfoLog(vs, sizeof(infolog), &infolen, infolog); - if (infolen > 0) + if (vertex_shader.value().info_log_length > 0) { LOG("\nVERTEX SHADER: %s", m_path.c_str()); parse_error(infolog, vertex); } - if (status == 0) + if (vertex_shader.value().compile_status == 0) { - glDeleteShader(vs); + const auto status = pp::renderer::gl::delete_opengl_shader( + vertex_shader.value().shader_id, + pp::renderer::gl::OpenGlShaderDeleteDispatch { + .delete_shader = delete_opengl_shader, + }); + if (!status.ok()) + LOG("Shader::create() vertex shader cleanup failed because: %s", status.message); ret = false; return; } - auto fs = glCreateShader(fragment_shader_stage()); - if (!fs) + const auto fragment_shader = pp::renderer::gl::compile_opengl_shader_source( + pp::renderer::gl::fragment_shader_stage(), + fragment.c_str(), + infolog, + static_cast(sizeof(infolog)), + pp::renderer::gl::OpenGlShaderCompileDispatch { + .create_shader = create_opengl_shader, + .shader_source = set_opengl_shader_source, + .compile_shader = compile_opengl_shader, + .get_shader_integer = query_opengl_shader_integer, + .get_shader_info_log = get_opengl_shader_info_log, + }); + if (!fragment_shader.ok()) { - glDeleteShader(vs); + const auto status = pp::renderer::gl::delete_opengl_shader( + vertex_shader.value().shader_id, + pp::renderer::gl::OpenGlShaderDeleteDispatch { + .delete_shader = delete_opengl_shader, + }); + if (!status.ok()) + LOG("Shader::create() vertex shader cleanup failed because: %s", status.message); ret = false; + LOG("Shader::create() fragment compile failed because: %s", fragment_shader.status().message); return; } - source = fragment.c_str(); - glShaderSource(fs, 1, &source, nullptr); - glCompileShader(fs); - glGetShaderiv(fs, shader_compile_status_query(), &status); - glGetShaderInfoLog(fs, sizeof(infolog), &infolen, infolog); - if (infolen > 0) + if (fragment_shader.value().info_log_length > 0) { LOG("\nFRAGMENT SHADER: %s", m_path.c_str()); parse_error(infolog, fragment); } - if (status == 0) + if (fragment_shader.value().compile_status == 0) { - glDeleteShader(vs); - glDeleteShader(fs); + const auto vertex_cleanup = pp::renderer::gl::delete_opengl_shader( + vertex_shader.value().shader_id, + pp::renderer::gl::OpenGlShaderDeleteDispatch { + .delete_shader = delete_opengl_shader, + }); + const auto fragment_cleanup = pp::renderer::gl::delete_opengl_shader( + fragment_shader.value().shader_id, + pp::renderer::gl::OpenGlShaderDeleteDispatch { + .delete_shader = delete_opengl_shader, + }); + if (!vertex_cleanup.ok()) + LOG("Shader::create() vertex shader cleanup failed because: %s", vertex_cleanup.message); + if (!fragment_cleanup.ok()) + LOG("Shader::create() fragment shader cleanup failed because: %s", fragment_cleanup.message); ret = false; return; } - auto ps = glCreateProgram(); - if (!ps) + const auto program = pp::renderer::gl::link_opengl_shader_program( + vertex_shader.value().shader_id, + fragment_shader.value().shader_id, + pp::renderer::gl::panopainter_shader_attribute_bindings(), + infolog, + static_cast(sizeof(infolog)), + pp::renderer::gl::OpenGlProgramLinkDispatch { + .create_program = create_opengl_program, + .attach_shader = attach_opengl_shader, + .delete_shader = delete_opengl_shader, + .link_program = link_opengl_program, + .get_attrib_location = get_opengl_attrib_location, + .bind_attrib_location = bind_opengl_attrib_location, + .get_program_integer = query_opengl_program_integer, + .get_program_info_log = get_opengl_program_info_log, + }); + if (!program.ok()) { - glDeleteShader(vs); - glDeleteShader(fs); + const auto vertex_cleanup = pp::renderer::gl::delete_opengl_shader( + vertex_shader.value().shader_id, + pp::renderer::gl::OpenGlShaderDeleteDispatch { + .delete_shader = delete_opengl_shader, + }); + const auto fragment_cleanup = pp::renderer::gl::delete_opengl_shader( + fragment_shader.value().shader_id, + pp::renderer::gl::OpenGlShaderDeleteDispatch { + .delete_shader = delete_opengl_shader, + }); + if (!vertex_cleanup.ok()) + LOG("Shader::create() vertex shader cleanup failed because: %s", vertex_cleanup.message); + if (!fragment_cleanup.ok()) + LOG("Shader::create() fragment shader cleanup failed because: %s", fragment_cleanup.message); ret = false; + LOG("Shader::create() program link failed because: %s", program.status().message); return; } - glAttachShader(ps, vs); - glAttachShader(ps, fs); - glDeleteShader(vs); - glDeleteShader(fs); - - glLinkProgram(ps); - for (const auto& binding : pp::renderer::gl::panopainter_shader_attribute_bindings()) - { - if (glGetAttribLocation(ps, binding.name) != -1) - glBindAttribLocation(ps, static_cast(binding.location), binding.name); - } - - glLinkProgram(ps); - glGetProgramiv(ps, program_link_status_query(), &status); - glGetProgramInfoLog(ps, sizeof(infolog), &infolen, infolog); - if (infolen > 0) + if (program.value().info_log_length > 0) LOG("LINK SHADER: %s\n%s", m_path.c_str(), infolog); - if (status == 0) + if (program.value().link_status == 0) { - glDeleteProgram(ps); + const auto status = pp::renderer::gl::delete_opengl_program( + program.value().program_id, + pp::renderer::gl::OpenGlProgramDeleteDispatch { + .use_program = use_opengl_program, + .delete_program = delete_opengl_program, + }); + if (!status.ok()) + LOG("Shader::create() program cleanup failed because: %s", status.message); ret = false; return; } + const auto cleanup_program = [](std::uint32_t program_id) noexcept { + const auto status = pp::renderer::gl::delete_opengl_program( + program_id, + pp::renderer::gl::OpenGlProgramDeleteDispatch { + .use_program = use_opengl_program, + .delete_program = delete_opengl_program, + }); + if (!status.ok()) + LOG("Shader::create() program cleanup failed because: %s", status.message); + }; + // Parse shader uniforms { - GLint count; - GLint size; // size of the variable - GLenum type; // type of the variable (float, vec3 or mat4, etc) - const GLsizei bufSize = 64; // maximum name length - GLchar name[bufSize]; // variable name in GLSL - GLsizei length; // name length - glGetProgramiv(ps, active_uniform_count_query(), &count); + const auto uniform_count = pp::renderer::gl::query_opengl_program_integer( + program.value().program_id, + pp::renderer::gl::active_uniform_count_query(), + pp::renderer::gl::OpenGlProgramIntegerDispatch { + .get_program_integer = query_opengl_program_integer, + }); + if (!uniform_count.ok()) + { + LOG("Shader::create() uniform discovery failed because: %s", uniform_count.status().message); + cleanup_program(program.value().program_id); + ret = false; + return; + } + constexpr std::int32_t bufSize = 64; // maximum name length + char uniform_name[bufSize]; // variable name in GLSL + const auto count = uniform_count.value(); for (int i = 0; i < count; i++) { - glGetActiveUniform(ps, (GLuint)i, bufSize, &length, &size, &type, name); - name[length] = 0; - kShaderUniform id = static_cast(pp::renderer::gl::shader_uniform_id(name)); + const auto uniform = pp::renderer::gl::get_opengl_active_uniform( + program.value().program_id, + static_cast(i), + uniform_name, + bufSize, + pp::renderer::gl::OpenGlActiveUniformDispatch { + .get_active_uniform = get_opengl_active_uniform, + }); + if (!uniform.ok()) + { + LOG("Shader::create() active uniform discovery failed because: %s", uniform.status().message); + cleanup_program(program.value().program_id); + ret = false; + return; + } + kShaderUniform id = static_cast(pp::renderer::gl::shader_uniform_id(uniform_name)); if (m_umap.find(id) != m_umap.end()) - LOG("UNIFORM ALREADY DEFINED: %s", name); - m_umap[id] = glGetUniformLocation(ps, name); - //printf("Uniform #%d Type: %u Name: %s Loc: %d\n", i, type, name, glGetUniformLocation(ps, name)); + LOG("UNIFORM ALREADY DEFINED: %s", uniform_name); + const auto location = pp::renderer::gl::get_opengl_uniform_location( + program.value().program_id, + uniform_name, + pp::renderer::gl::OpenGlUniformLocationDispatch { + .get_uniform_location = get_opengl_uniform_location, + }); + if (!location.ok()) + { + LOG("Shader::create() uniform location failed because: %s", location.status().message); + cleanup_program(program.value().program_id); + ret = false; + return; + } + m_umap[id] = location.value(); } } - prog = ps; + prog = program.value().program_id; }); return ret; diff --git a/tests/renderer_gl/capabilities_tests.cpp b/tests/renderer_gl/capabilities_tests.cpp index 27c3e08..529b034 100644 --- a/tests/renderer_gl/capabilities_tests.cpp +++ b/tests/renderer_gl/capabilities_tests.cpp @@ -2,6 +2,7 @@ #include "renderer_gl/shader_bindings.h" #include "test_harness.h" +#include #include #include #include @@ -111,6 +112,29 @@ struct RecordedOpenGlUniformScalarCall { bool is_float = false; }; +struct RecordedOpenGlShaderSourceCall { + std::uint32_t shader = 0; + std::int32_t count = 0; + std::string_view source; +}; + +struct RecordedOpenGlProgramAttachCall { + std::uint32_t program = 0; + std::uint32_t shader = 0; +}; + +struct RecordedOpenGlAttribBindCall { + std::uint32_t program = 0; + std::uint32_t location = 0; + std::string_view name; +}; + +struct RecordedOpenGlActiveUniformCall { + std::uint32_t program = 0; + std::uint32_t index = 0; + std::int32_t capacity = 0; +}; + std::vector recorded_state_calls; std::vector recorded_string_queries; std::vector recorded_clear_calls; @@ -138,9 +162,27 @@ std::vector recorded_uniform_vector_calls; std::vector recorded_uniform_scalar_calls; std::vector recorded_attrib_location_programs; std::vector recorded_attrib_location_names; +std::vector recorded_created_shader_stages; +std::vector recorded_shader_source_calls; +std::vector recorded_compiled_shaders; +std::vector recorded_deleted_shaders; +std::vector recorded_shader_integer_queries; +std::vector recorded_created_programs; +std::vector recorded_program_attach_calls; +std::vector recorded_linked_programs; +std::vector recorded_attrib_bind_calls; +std::vector recorded_program_integer_queries; +std::vector recorded_active_uniform_calls; +std::vector recorded_uniform_location_programs; +std::vector recorded_uniform_location_names; std::uint32_t next_texture_id = 91U; std::uint32_t next_framebuffer_id = 44U; std::uint32_t next_sampler_id = 71U; +std::uint32_t next_shader_id = 301U; +std::uint32_t next_program_id = 401U; +std::int32_t configured_shader_compile_status = 1; +std::int32_t configured_program_link_status = 1; +std::int32_t configured_active_uniform_count = 2; std::uint32_t configured_framebuffer_status = 0x8CD5U; void record_enable(std::uint32_t state) noexcept @@ -592,6 +634,164 @@ std::int32_t record_get_attrib_location(std::uint32_t program, const char* name) return -1; } +std::uint32_t record_create_shader(std::uint32_t stage) noexcept +{ + recorded_created_shader_stages.push_back(stage); + return next_shader_id++; +} + +void record_shader_source(std::uint32_t shader, std::int32_t count, const char* const* sources) noexcept +{ + recorded_shader_source_calls.push_back(RecordedOpenGlShaderSourceCall { + .shader = shader, + .count = count, + .source = sources != nullptr && sources[0] != nullptr ? std::string_view { sources[0] } : std::string_view {}, + }); +} + +void record_compile_shader(std::uint32_t shader) noexcept +{ + recorded_compiled_shaders.push_back(shader); +} + +void record_get_shader_integer(std::uint32_t shader, std::uint32_t query, std::int32_t* value) noexcept +{ + recorded_shader_integer_queries.push_back(query); + if (value != nullptr) { + *value = configured_shader_compile_status; + } + (void)shader; +} + +void record_get_shader_info_log( + std::uint32_t shader, + std::int32_t capacity, + std::int32_t* length, + char* info_log) noexcept +{ + constexpr std::string_view log_text { "shader note" }; + if (length != nullptr) { + *length = static_cast(log_text.size()); + } + if (info_log != nullptr && capacity > 0) { + const auto copied = std::min(capacity - 1, static_cast(log_text.size())); + std::memcpy(info_log, log_text.data(), static_cast(copied)); + info_log[copied] = '\0'; + } + (void)shader; +} + +void record_delete_shader(std::uint32_t shader) noexcept +{ + recorded_deleted_shaders.push_back(shader); +} + +std::uint32_t record_create_program() noexcept +{ + recorded_created_programs.push_back(next_program_id); + return next_program_id++; +} + +void record_attach_shader(std::uint32_t program, std::uint32_t shader) noexcept +{ + recorded_program_attach_calls.push_back(RecordedOpenGlProgramAttachCall { + .program = program, + .shader = shader, + }); +} + +void record_link_program(std::uint32_t program) noexcept +{ + recorded_linked_programs.push_back(program); +} + +void record_bind_attrib_location(std::uint32_t program, std::uint32_t location, const char* name) noexcept +{ + recorded_attrib_bind_calls.push_back(RecordedOpenGlAttribBindCall { + .program = program, + .location = location, + .name = name == nullptr ? std::string_view {} : std::string_view { name }, + }); +} + +void record_get_program_integer(std::uint32_t program, std::uint32_t query, std::int32_t* value) noexcept +{ + recorded_program_integer_queries.push_back(query); + if (value == nullptr) { + return; + } + if (query == 0x8B82U) { + *value = configured_program_link_status; + } else if (query == 0x8B86U) { + *value = configured_active_uniform_count; + } else { + *value = -1; + } + (void)program; +} + +void record_get_program_info_log( + std::uint32_t program, + std::int32_t capacity, + std::int32_t* length, + char* info_log) noexcept +{ + constexpr std::string_view log_text { "program note" }; + if (length != nullptr) { + *length = static_cast(log_text.size()); + } + if (info_log != nullptr && capacity > 0) { + const auto copied = std::min(capacity - 1, static_cast(log_text.size())); + std::memcpy(info_log, log_text.data(), static_cast(copied)); + info_log[copied] = '\0'; + } + (void)program; +} + +void record_get_active_uniform( + std::uint32_t program, + std::uint32_t index, + std::int32_t capacity, + std::int32_t* length, + std::int32_t* size, + std::uint32_t* type, + char* name) noexcept +{ + recorded_active_uniform_calls.push_back(RecordedOpenGlActiveUniformCall { + .program = program, + .index = index, + .capacity = capacity, + }); + const std::string_view uniform_name = index == 0U ? std::string_view { "mvp" } : std::string_view { "tex" }; + if (length != nullptr) { + *length = static_cast(uniform_name.size()); + } + if (size != nullptr) { + *size = 1; + } + if (type != nullptr) { + *type = 0x8B5CU; + } + if (name != nullptr && capacity > 0) { + const auto copied = std::min(capacity - 1, static_cast(uniform_name.size())); + std::memcpy(name, uniform_name.data(), static_cast(copied)); + name[copied] = '\0'; + } +} + +std::int32_t record_get_uniform_location(std::uint32_t program, const char* name) noexcept +{ + recorded_uniform_location_programs.push_back(program); + recorded_uniform_location_names.push_back(name == nullptr ? std::string_view {} : std::string_view { name }); + if (name != nullptr && std::string_view { name } == std::string_view { "mvp" }) { + return 4; + } + if (name != nullptr && std::string_view { name } == std::string_view { "tex" }) { + return 7; + } + return -1; +} + void detects_common_extension_capabilities(pp::tests::Harness& h) { constexpr std::array extensions { @@ -2256,6 +2456,321 @@ void rejects_invalid_program_uniform_dispatch(pp::tests::Harness& h) PP_EXPECT(h, invalid_attrib_name.status().code == pp::foundation::StatusCode::invalid_argument); } +void compiles_shader_source_through_dispatch(pp::tests::Harness& h) +{ + recorded_created_shader_stages.clear(); + recorded_shader_source_calls.clear(); + recorded_compiled_shaders.clear(); + recorded_shader_integer_queries.clear(); + next_shader_id = 301U; + configured_shader_compile_status = 1; + std::array info_log {}; + + const auto shader = pp::renderer::gl::compile_opengl_shader_source( + 0x8B31U, + "void main(){}", + info_log.data(), + static_cast(info_log.size()), + pp::renderer::gl::OpenGlShaderCompileDispatch { + .create_shader = record_create_shader, + .shader_source = record_shader_source, + .compile_shader = record_compile_shader, + .get_shader_integer = record_get_shader_integer, + .get_shader_info_log = record_get_shader_info_log, + }); + + PP_EXPECT(h, shader.ok()); + PP_EXPECT(h, shader.value().shader_id == 301U); + PP_EXPECT(h, shader.value().compile_status == 1); + PP_EXPECT(h, shader.value().info_log_length == 11); + PP_EXPECT(h, std::string_view { info_log.data() } == std::string_view("shader note")); + PP_EXPECT(h, recorded_created_shader_stages.size() == 1U); + PP_EXPECT(h, recorded_created_shader_stages[0] == 0x8B31U); + PP_EXPECT(h, recorded_shader_source_calls.size() == 1U); + PP_EXPECT(h, recorded_shader_source_calls[0].shader == 301U); + PP_EXPECT(h, recorded_shader_source_calls[0].count == 1); + PP_EXPECT(h, recorded_shader_source_calls[0].source == std::string_view("void main(){}")); + PP_EXPECT(h, recorded_compiled_shaders.size() == 1U); + PP_EXPECT(h, recorded_compiled_shaders[0] == 301U); + PP_EXPECT(h, recorded_shader_integer_queries.size() == 1U); + PP_EXPECT(h, recorded_shader_integer_queries[0] == 0x8B81U); +} + +void rejects_invalid_shader_compile_dispatch(pp::tests::Harness& h) +{ + std::array info_log {}; + const auto missing_dispatch = pp::renderer::gl::compile_opengl_shader_source( + 0x8B31U, + "void main(){}", + info_log.data(), + static_cast(info_log.size()), + pp::renderer::gl::OpenGlShaderCompileDispatch { + .create_shader = record_create_shader, + .shader_source = record_shader_source, + }); + const auto empty_source = pp::renderer::gl::compile_opengl_shader_source( + 0x8B31U, + "", + info_log.data(), + static_cast(info_log.size()), + pp::renderer::gl::OpenGlShaderCompileDispatch { + .create_shader = record_create_shader, + .shader_source = record_shader_source, + .compile_shader = record_compile_shader, + .get_shader_integer = record_get_shader_integer, + .get_shader_info_log = record_get_shader_info_log, + }); + const auto missing_log = pp::renderer::gl::compile_opengl_shader_source( + 0x8B31U, + "void main(){}", + nullptr, + static_cast(info_log.size()), + pp::renderer::gl::OpenGlShaderCompileDispatch { + .create_shader = record_create_shader, + .shader_source = record_shader_source, + .compile_shader = record_compile_shader, + .get_shader_integer = record_get_shader_integer, + .get_shader_info_log = record_get_shader_info_log, + }); + + PP_EXPECT(h, !missing_dispatch.ok()); + PP_EXPECT(h, missing_dispatch.status().code == pp::foundation::StatusCode::invalid_argument); + PP_EXPECT(h, !empty_source.ok()); + PP_EXPECT(h, empty_source.status().code == pp::foundation::StatusCode::invalid_argument); + PP_EXPECT(h, !missing_log.ok()); + PP_EXPECT(h, missing_log.status().code == pp::foundation::StatusCode::invalid_argument); +} + +void deletes_shader_through_dispatch(pp::tests::Harness& h) +{ + recorded_deleted_shaders.clear(); + + const auto delete_status = pp::renderer::gl::delete_opengl_shader( + 31U, + pp::renderer::gl::OpenGlShaderDeleteDispatch { + .delete_shader = record_delete_shader, + }); + const auto delete_zero_status = pp::renderer::gl::delete_opengl_shader( + 0U, + pp::renderer::gl::OpenGlShaderDeleteDispatch { + .delete_shader = record_delete_shader, + }); + + PP_EXPECT(h, delete_status.ok()); + PP_EXPECT(h, delete_zero_status.ok()); + PP_EXPECT(h, recorded_deleted_shaders.size() == 1U); + PP_EXPECT(h, recorded_deleted_shaders[0] == 31U); +} + +void links_shader_program_through_dispatch(pp::tests::Harness& h) +{ + recorded_created_programs.clear(); + recorded_program_attach_calls.clear(); + recorded_deleted_shaders.clear(); + recorded_linked_programs.clear(); + recorded_attrib_location_programs.clear(); + recorded_attrib_location_names.clear(); + recorded_attrib_bind_calls.clear(); + recorded_program_integer_queries.clear(); + next_program_id = 401U; + configured_program_link_status = 1; + std::array info_log {}; + + const auto program = pp::renderer::gl::link_opengl_shader_program( + 101U, + 103U, + pp::renderer::gl::panopainter_shader_attribute_bindings(), + info_log.data(), + static_cast(info_log.size()), + pp::renderer::gl::OpenGlProgramLinkDispatch { + .create_program = record_create_program, + .attach_shader = record_attach_shader, + .delete_shader = record_delete_shader, + .link_program = record_link_program, + .get_attrib_location = record_get_attrib_location, + .bind_attrib_location = record_bind_attrib_location, + .get_program_integer = record_get_program_integer, + .get_program_info_log = record_get_program_info_log, + }); + + PP_EXPECT(h, program.ok()); + PP_EXPECT(h, program.value().program_id == 401U); + PP_EXPECT(h, program.value().link_status == 1); + PP_EXPECT(h, program.value().info_log_length == 12); + PP_EXPECT(h, std::string_view { info_log.data() } == std::string_view("program note")); + PP_EXPECT(h, recorded_created_programs.size() == 1U); + PP_EXPECT(h, recorded_created_programs[0] == 401U); + PP_EXPECT(h, recorded_program_attach_calls.size() == 2U); + PP_EXPECT(h, recorded_program_attach_calls[0].shader == 101U); + PP_EXPECT(h, recorded_program_attach_calls[1].shader == 103U); + PP_EXPECT(h, recorded_deleted_shaders.size() == 2U); + PP_EXPECT(h, recorded_deleted_shaders[0] == 101U); + PP_EXPECT(h, recorded_deleted_shaders[1] == 103U); + PP_EXPECT(h, recorded_linked_programs.size() == 2U); + PP_EXPECT(h, recorded_linked_programs[0] == 401U); + PP_EXPECT(h, recorded_linked_programs[1] == 401U); + PP_EXPECT(h, recorded_attrib_bind_calls.size() == 1U); + PP_EXPECT(h, recorded_attrib_bind_calls[0].program == 401U); + PP_EXPECT(h, recorded_attrib_bind_calls[0].location == 1U); + PP_EXPECT(h, recorded_attrib_bind_calls[0].name == std::string_view("uvs")); + PP_EXPECT(h, recorded_program_integer_queries.size() == 1U); + PP_EXPECT(h, recorded_program_integer_queries[0] == 0x8B82U); +} + +void rejects_invalid_shader_program_link_dispatch(pp::tests::Harness& h) +{ + std::array info_log {}; + const auto missing_dispatch = pp::renderer::gl::link_opengl_shader_program( + 1U, + 2U, + pp::renderer::gl::panopainter_shader_attribute_bindings(), + info_log.data(), + static_cast(info_log.size()), + pp::renderer::gl::OpenGlProgramLinkDispatch { + .create_program = record_create_program, + .attach_shader = record_attach_shader, + }); + const auto zero_shader = pp::renderer::gl::link_opengl_shader_program( + 0U, + 2U, + pp::renderer::gl::panopainter_shader_attribute_bindings(), + info_log.data(), + static_cast(info_log.size()), + pp::renderer::gl::OpenGlProgramLinkDispatch { + .create_program = record_create_program, + .attach_shader = record_attach_shader, + .delete_shader = record_delete_shader, + .link_program = record_link_program, + .get_attrib_location = record_get_attrib_location, + .bind_attrib_location = record_bind_attrib_location, + .get_program_integer = record_get_program_integer, + .get_program_info_log = record_get_program_info_log, + }); + const auto missing_log = pp::renderer::gl::link_opengl_shader_program( + 1U, + 2U, + pp::renderer::gl::panopainter_shader_attribute_bindings(), + nullptr, + static_cast(info_log.size()), + pp::renderer::gl::OpenGlProgramLinkDispatch { + .create_program = record_create_program, + .attach_shader = record_attach_shader, + .delete_shader = record_delete_shader, + .link_program = record_link_program, + .get_attrib_location = record_get_attrib_location, + .bind_attrib_location = record_bind_attrib_location, + .get_program_integer = record_get_program_integer, + .get_program_info_log = record_get_program_info_log, + }); + + PP_EXPECT(h, !missing_dispatch.ok()); + PP_EXPECT(h, missing_dispatch.status().code == pp::foundation::StatusCode::invalid_argument); + PP_EXPECT(h, !zero_shader.ok()); + PP_EXPECT(h, zero_shader.status().code == pp::foundation::StatusCode::invalid_argument); + PP_EXPECT(h, !missing_log.ok()); + PP_EXPECT(h, missing_log.status().code == pp::foundation::StatusCode::invalid_argument); +} + +void discovers_program_uniforms_through_dispatch(pp::tests::Harness& h) +{ + recorded_program_integer_queries.clear(); + recorded_active_uniform_calls.clear(); + recorded_uniform_location_programs.clear(); + recorded_uniform_location_names.clear(); + configured_active_uniform_count = 2; + std::array name {}; + + const auto count = pp::renderer::gl::query_opengl_program_integer( + 401U, + pp::renderer::gl::active_uniform_count_query(), + pp::renderer::gl::OpenGlProgramIntegerDispatch { + .get_program_integer = record_get_program_integer, + }); + const auto first = pp::renderer::gl::get_opengl_active_uniform( + 401U, + 0U, + name.data(), + static_cast(name.size()), + pp::renderer::gl::OpenGlActiveUniformDispatch { + .get_active_uniform = record_get_active_uniform, + }); + const auto location = pp::renderer::gl::get_opengl_uniform_location( + 401U, + name.data(), + pp::renderer::gl::OpenGlUniformLocationDispatch { + .get_uniform_location = record_get_uniform_location, + }); + + PP_EXPECT(h, count.ok()); + PP_EXPECT(h, count.value() == 2); + PP_EXPECT(h, first.ok()); + PP_EXPECT(h, first.value().length == 3); + PP_EXPECT(h, first.value().size == 1); + PP_EXPECT(h, first.value().type == 0x8B5CU); + PP_EXPECT(h, std::string_view { name.data() } == std::string_view("mvp")); + PP_EXPECT(h, location.ok()); + PP_EXPECT(h, location.value() == 4); + PP_EXPECT(h, recorded_program_integer_queries.size() == 1U); + PP_EXPECT(h, recorded_program_integer_queries[0] == 0x8B86U); + PP_EXPECT(h, recorded_active_uniform_calls.size() == 1U); + PP_EXPECT(h, recorded_active_uniform_calls[0].program == 401U); + PP_EXPECT(h, recorded_active_uniform_calls[0].index == 0U); + PP_EXPECT(h, recorded_uniform_location_names.size() == 1U); + PP_EXPECT(h, recorded_uniform_location_names[0] == std::string_view("mvp")); +} + +void rejects_invalid_uniform_discovery_dispatch(pp::tests::Harness& h) +{ + std::array name {}; + const auto missing_program_integer = pp::renderer::gl::query_opengl_program_integer( + 1U, + pp::renderer::gl::active_uniform_count_query(), + pp::renderer::gl::OpenGlProgramIntegerDispatch {}); + const auto invalid_program_integer = pp::renderer::gl::query_opengl_program_integer( + 0U, + pp::renderer::gl::active_uniform_count_query(), + pp::renderer::gl::OpenGlProgramIntegerDispatch { + .get_program_integer = record_get_program_integer, + }); + const auto missing_active = pp::renderer::gl::get_opengl_active_uniform( + 1U, + 0U, + name.data(), + static_cast(name.size()), + pp::renderer::gl::OpenGlActiveUniformDispatch {}); + const auto invalid_active_buffer = pp::renderer::gl::get_opengl_active_uniform( + 1U, + 0U, + nullptr, + static_cast(name.size()), + pp::renderer::gl::OpenGlActiveUniformDispatch { + .get_active_uniform = record_get_active_uniform, + }); + const auto missing_uniform_location = pp::renderer::gl::get_opengl_uniform_location( + 1U, + "mvp", + pp::renderer::gl::OpenGlUniformLocationDispatch {}); + const auto invalid_uniform_name = pp::renderer::gl::get_opengl_uniform_location( + 1U, + "", + pp::renderer::gl::OpenGlUniformLocationDispatch { + .get_uniform_location = record_get_uniform_location, + }); + + PP_EXPECT(h, !missing_program_integer.ok()); + PP_EXPECT(h, missing_program_integer.status().code == pp::foundation::StatusCode::invalid_argument); + PP_EXPECT(h, !invalid_program_integer.ok()); + PP_EXPECT(h, invalid_program_integer.status().code == pp::foundation::StatusCode::invalid_argument); + PP_EXPECT(h, !missing_active.ok()); + PP_EXPECT(h, missing_active.status().code == pp::foundation::StatusCode::invalid_argument); + PP_EXPECT(h, !invalid_active_buffer.ok()); + PP_EXPECT(h, invalid_active_buffer.status().code == pp::foundation::StatusCode::invalid_argument); + PP_EXPECT(h, !missing_uniform_location.ok()); + PP_EXPECT(h, missing_uniform_location.status().code == pp::foundation::StatusCode::invalid_argument); + PP_EXPECT(h, !invalid_uniform_name.ok()); + PP_EXPECT(h, invalid_uniform_name.status().code == pp::foundation::StatusCode::invalid_argument); +} + void updates_texture_2d_through_dispatch(pp::tests::Harness& h) { recorded_binding_calls.clear(); @@ -3043,6 +3558,13 @@ int main() harness.run("sets_scalar_uniforms_through_dispatch", sets_scalar_uniforms_through_dispatch); harness.run("queries_attribute_location_through_dispatch", queries_attribute_location_through_dispatch); harness.run("rejects_invalid_program_uniform_dispatch", rejects_invalid_program_uniform_dispatch); + harness.run("compiles_shader_source_through_dispatch", compiles_shader_source_through_dispatch); + harness.run("rejects_invalid_shader_compile_dispatch", rejects_invalid_shader_compile_dispatch); + harness.run("deletes_shader_through_dispatch", deletes_shader_through_dispatch); + harness.run("links_shader_program_through_dispatch", links_shader_program_through_dispatch); + harness.run("rejects_invalid_shader_program_link_dispatch", rejects_invalid_shader_program_link_dispatch); + harness.run("discovers_program_uniforms_through_dispatch", discovers_program_uniforms_through_dispatch); + harness.run("rejects_invalid_uniform_discovery_dispatch", rejects_invalid_uniform_discovery_dispatch); harness.run("updates_texture_2d_through_dispatch", updates_texture_2d_through_dispatch); harness.run("generates_texture_2d_mipmaps_through_dispatch", generates_texture_2d_mipmaps_through_dispatch); harness.run("reads_back_texture_2d_through_framebuffer_dispatch", reads_back_texture_2d_through_framebuffer_dispatch);