From acdaf3bb8e94922462b4a7ed41ff4e312a88a3cd Mon Sep 17 00:00:00 2001 From: omigamedev Date: Wed, 3 Jun 2026 06:53:51 +0200 Subject: [PATCH] Route shader runtime calls through renderer GL --- docs/modernization/build-inventory.md | 5 +- docs/modernization/roadmap.md | 4 + src/renderer_gl/opengl_capabilities.cpp | 147 +++++++++++ src/renderer_gl/opengl_capabilities.h | 83 ++++++ src/shader.cpp | 178 +++++++++++-- tests/renderer_gl/capabilities_tests.cpp | 317 +++++++++++++++++++++++ 6 files changed, 717 insertions(+), 17 deletions(-) diff --git a/docs/modernization/build-inventory.md b/docs/modernization/build-inventory.md index 3fed924..eef9615 100644 --- a/docs/modernization/build-inventory.md +++ b/docs/modernization/build-inventory.md @@ -227,7 +227,10 @@ Known local toolchain state: `Shader` no longer spells GL enum 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. App OpenGL initialization debug severity, debug output, GL info string, + 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. + 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 tokens are cataloged and tested here too, including the legacy convert command diff --git a/docs/modernization/roadmap.md b/docs/modernization/roadmap.md index 8528d21..766f4ae 100644 --- a/docs/modernization/roadmap.md +++ b/docs/modernization/roadmap.md @@ -1119,6 +1119,10 @@ Results: uniform catalog validation covers the 43 legacy uniform 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. - `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 fe04afd..c978576 100644 --- a/src/renderer_gl/opengl_capabilities.cpp +++ b/src/renderer_gl/opengl_capabilities.cpp @@ -883,6 +883,153 @@ pp::foundation::Status bind_opengl_sampler_object( return pp::foundation::Status::success(); } +pp::foundation::Status use_opengl_program( + std::uint32_t program_id, + OpenGlProgramUseDispatch dispatch) noexcept +{ + if (dispatch.use_program == nullptr) { + return pp::foundation::Status::invalid_argument("OpenGL program use dispatch callback must not be null"); + } + + dispatch.use_program(program_id); + return pp::foundation::Status::success(); +} + +pp::foundation::Status delete_opengl_program( + std::uint32_t program_id, + OpenGlProgramDeleteDispatch dispatch) noexcept +{ + if (dispatch.use_program == nullptr || dispatch.delete_program == nullptr) { + return pp::foundation::Status::invalid_argument("OpenGL program delete dispatch callbacks must not be null"); + } + + if (program_id == 0U) { + return pp::foundation::Status::success(); + } + + dispatch.use_program(default_framebuffer_id()); + dispatch.delete_program(program_id); + return pp::foundation::Status::success(); +} + +pp::foundation::Status set_opengl_uniform_vec4( + std::int32_t location, + const float* values, + OpenGlUniformVec4Dispatch dispatch) noexcept +{ + if (dispatch.uniform_4fv == nullptr) { + return pp::foundation::Status::invalid_argument("OpenGL vec4 uniform dispatch callback must not be null"); + } + + if (values == nullptr) { + return pp::foundation::Status::invalid_argument("OpenGL vec4 uniform values must not be null"); + } + + dispatch.uniform_4fv(location, 1, values); + return pp::foundation::Status::success(); +} + +pp::foundation::Status set_opengl_uniform_vec3( + std::int32_t location, + const float* values, + OpenGlUniformVec3Dispatch dispatch) noexcept +{ + if (dispatch.uniform_3fv == nullptr) { + return pp::foundation::Status::invalid_argument("OpenGL vec3 uniform dispatch callback must not be null"); + } + + if (values == nullptr) { + return pp::foundation::Status::invalid_argument("OpenGL vec3 uniform values must not be null"); + } + + dispatch.uniform_3fv(location, 1, values); + return pp::foundation::Status::success(); +} + +pp::foundation::Status set_opengl_uniform_vec2( + std::int32_t location, + const float* values, + OpenGlUniformVec2Dispatch dispatch) noexcept +{ + if (dispatch.uniform_2fv == nullptr) { + return pp::foundation::Status::invalid_argument("OpenGL vec2 uniform dispatch callback must not be null"); + } + + if (values == nullptr) { + return pp::foundation::Status::invalid_argument("OpenGL vec2 uniform values must not be null"); + } + + dispatch.uniform_2fv(location, 1, values); + return pp::foundation::Status::success(); +} + +pp::foundation::Status set_opengl_uniform_mat4( + std::int32_t location, + const float* values, + OpenGlUniformMat4Dispatch dispatch) noexcept +{ + if (dispatch.uniform_matrix_4fv == nullptr) { + return pp::foundation::Status::invalid_argument("OpenGL mat4 uniform dispatch callback must not be null"); + } + + if (values == nullptr) { + return pp::foundation::Status::invalid_argument("OpenGL mat4 uniform values must not be null"); + } + + dispatch.uniform_matrix_4fv( + location, + 1, + static_cast(matrix_uniform_not_transposed()), + values); + return pp::foundation::Status::success(); +} + +pp::foundation::Status set_opengl_uniform_int( + std::int32_t location, + std::int32_t value, + OpenGlUniformIntDispatch dispatch) noexcept +{ + if (dispatch.uniform_1i == nullptr) { + return pp::foundation::Status::invalid_argument("OpenGL int uniform dispatch callback must not be null"); + } + + dispatch.uniform_1i(location, value); + return pp::foundation::Status::success(); +} + +pp::foundation::Status set_opengl_uniform_float( + std::int32_t location, + float value, + OpenGlUniformFloatDispatch dispatch) noexcept +{ + if (dispatch.uniform_1f == nullptr) { + return pp::foundation::Status::invalid_argument("OpenGL float uniform dispatch callback must not be null"); + } + + dispatch.uniform_1f(location, value); + return pp::foundation::Status::success(); +} + +pp::foundation::Result get_opengl_attribute_location( + std::uint32_t program_id, + const char* attribute_name, + OpenGlAttributeLocationDispatch dispatch) noexcept +{ + if (dispatch.get_attrib_location == nullptr) { + return pp::foundation::Result::failure( + pp::foundation::Status::invalid_argument( + "OpenGL attribute-location dispatch callback must not be null")); + } + + if (program_id == 0U || attribute_name == nullptr || attribute_name[0] == '\0') { + return pp::foundation::Result::failure( + pp::foundation::Status::invalid_argument("OpenGL attribute-location parameters are invalid")); + } + + return pp::foundation::Result::success( + dispatch.get_attrib_location(program_id, attribute_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 28aba6c..37c1ec3 100644 --- a/src/renderer_gl/opengl_capabilities.h +++ b/src/renderer_gl/opengl_capabilities.h @@ -220,6 +220,18 @@ using OpenGlScissorFn = void (*)(std::int32_t x, std::int32_t y, std::int32_t wi using OpenGlBlendFuncFn = void (*)(std::uint32_t source_factor, std::uint32_t destination_factor) noexcept; using OpenGlBlendEquationSeparateFn = void (*)(std::uint32_t color_equation, std::uint32_t alpha_equation) noexcept; using OpenGlUseProgramFn = void (*)(std::uint32_t program) noexcept; +using OpenGlDeleteProgramFn = void (*)(std::uint32_t program) noexcept; +using OpenGlUniform4fvFn = void (*)(std::int32_t location, std::int32_t count, const float* values) noexcept; +using OpenGlUniform3fvFn = void (*)(std::int32_t location, std::int32_t count, const float* values) noexcept; +using OpenGlUniform2fvFn = void (*)(std::int32_t location, std::int32_t count, const float* values) noexcept; +using OpenGlUniformMatrix4fvFn = void (*)( + std::int32_t location, + std::int32_t count, + std::uint8_t transpose, + const float* values) noexcept; +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 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; @@ -433,6 +445,43 @@ struct OpenGlSamplerBindDispatch { OpenGlBindSamplerFn bind_sampler = nullptr; }; +struct OpenGlProgramUseDispatch { + OpenGlUseProgramFn use_program = nullptr; +}; + +struct OpenGlProgramDeleteDispatch { + OpenGlUseProgramFn use_program = nullptr; + OpenGlDeleteProgramFn delete_program = nullptr; +}; + +struct OpenGlUniformVec4Dispatch { + OpenGlUniform4fvFn uniform_4fv = nullptr; +}; + +struct OpenGlUniformVec3Dispatch { + OpenGlUniform3fvFn uniform_3fv = nullptr; +}; + +struct OpenGlUniformVec2Dispatch { + OpenGlUniform2fvFn uniform_2fv = nullptr; +}; + +struct OpenGlUniformMat4Dispatch { + OpenGlUniformMatrix4fvFn uniform_matrix_4fv = nullptr; +}; + +struct OpenGlUniformIntDispatch { + OpenGlUniform1iFn uniform_1i = nullptr; +}; + +struct OpenGlUniformFloatDispatch { + OpenGlUniform1fFn uniform_1f = nullptr; +}; + +struct OpenGlAttributeLocationDispatch { + OpenGlGetAttribLocationFn get_attrib_location = nullptr; +}; + [[nodiscard]] OpenGlCapabilities detect_opengl_capabilities( std::span extensions, OpenGlRuntime runtime) noexcept; @@ -520,6 +569,40 @@ struct OpenGlSamplerBindDispatch { std::uint32_t unit, std::uint32_t sampler_id, OpenGlSamplerBindDispatch dispatch) noexcept; +[[nodiscard]] pp::foundation::Status use_opengl_program( + std::uint32_t program_id, + OpenGlProgramUseDispatch dispatch) noexcept; +[[nodiscard]] pp::foundation::Status delete_opengl_program( + std::uint32_t program_id, + OpenGlProgramDeleteDispatch dispatch) noexcept; +[[nodiscard]] pp::foundation::Status set_opengl_uniform_vec4( + std::int32_t location, + const float* values, + OpenGlUniformVec4Dispatch dispatch) noexcept; +[[nodiscard]] pp::foundation::Status set_opengl_uniform_vec3( + std::int32_t location, + const float* values, + OpenGlUniformVec3Dispatch dispatch) noexcept; +[[nodiscard]] pp::foundation::Status set_opengl_uniform_vec2( + std::int32_t location, + const float* values, + OpenGlUniformVec2Dispatch dispatch) noexcept; +[[nodiscard]] pp::foundation::Status set_opengl_uniform_mat4( + std::int32_t location, + const float* values, + OpenGlUniformMat4Dispatch dispatch) noexcept; +[[nodiscard]] pp::foundation::Status set_opengl_uniform_int( + std::int32_t location, + std::int32_t value, + OpenGlUniformIntDispatch dispatch) noexcept; +[[nodiscard]] pp::foundation::Status set_opengl_uniform_float( + std::int32_t location, + float value, + OpenGlUniformFloatDispatch dispatch) noexcept; +[[nodiscard]] pp::foundation::Result get_opengl_attribute_location( + std::uint32_t program_id, + const char* attribute_name, + OpenGlAttributeLocationDispatch 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 443e18d..3c315e4 100644 --- a/src/shader.cpp +++ b/src/shader.cpp @@ -35,9 +35,57 @@ namespace { return static_cast(pp::renderer::gl::active_uniform_count_query()); } -[[nodiscard]] GLboolean matrix_uniform_not_transposed() noexcept +void use_opengl_program(std::uint32_t program) noexcept { - return static_cast(pp::renderer::gl::matrix_uniform_not_transposed()); + glUseProgram(static_cast(program)); +} + +void delete_opengl_program(std::uint32_t program) noexcept +{ + glDeleteProgram(static_cast(program)); +} + +void set_opengl_uniform_4fv(std::int32_t location, std::int32_t count, const float* values) noexcept +{ + glUniform4fv(static_cast(location), static_cast(count), values); +} + +void set_opengl_uniform_3fv(std::int32_t location, std::int32_t count, const float* values) noexcept +{ + glUniform3fv(static_cast(location), static_cast(count), values); +} + +void set_opengl_uniform_2fv(std::int32_t location, std::int32_t count, const float* values) noexcept +{ + glUniform2fv(static_cast(location), static_cast(count), values); +} + +void set_opengl_uniform_matrix_4fv( + std::int32_t location, + std::int32_t count, + std::uint8_t transpose, + const float* values) noexcept +{ + glUniformMatrix4fv( + static_cast(location), + static_cast(count), + static_cast(transpose), + values); +} + +void set_opengl_uniform_1i(std::int32_t location, std::int32_t value) noexcept +{ + glUniform1i(static_cast(location), static_cast(value)); +} + +void set_opengl_uniform_1f(std::int32_t location, float value) noexcept +{ + glUniform1f(static_cast(location), value); +} + +std::int32_t get_opengl_attrib_location(std::uint32_t program, const char* name) noexcept +{ + return static_cast(glGetAttribLocation(static_cast(program), name)); } } @@ -65,14 +113,12 @@ std::string Shader::read(const std::string& path) std::string data((char*)a.read_all(), a.m_len); // split path - std::string name, base, ext; + std::string base; std::regex reg_path(R"((.*)[\\/]([^\\/]+)\.(\w+)$)"); std::smatch m; if (std::regex_search(path, m, reg_path)) { base = m[1].str(); - name = m[2].str(); - ext = m[3].str(); } for (const auto& l : split(data, '\n')) @@ -319,8 +365,14 @@ void Shader::destroy() { App::I->render_task_async([prog=prog] { - glUseProgram(0); - glDeleteProgram(prog); + const auto status = pp::renderer::gl::delete_opengl_program( + static_cast(prog), + pp::renderer::gl::OpenGlProgramDeleteDispatch { + .use_program = use_opengl_program, + .delete_program = delete_opengl_program, + }); + if (!status.ok()) + LOG("Shader::destroy() failed because: %s", status.message); }); prog = 0; } @@ -329,54 +381,148 @@ void Shader::destroy() void Shader::use() { - glUseProgram(prog); + const auto status = pp::renderer::gl::use_opengl_program( + static_cast(prog), + pp::renderer::gl::OpenGlProgramUseDispatch { + .use_program = use_opengl_program, + }); + if (!status.ok()) + LOG("Shader::use() failed because: %s", status.message); } void Shader::u_vec4(kShaderUniform id, const glm::vec4& v) { if (m_umap.count(id) == 0) LOG("UNIFORM vec4 %d NOT FOUND in shader %d", (int)id, (int)name) - else glUniform4fv(m_umap[id], 1, glm::value_ptr(v)); + else + { + const auto status = pp::renderer::gl::set_opengl_uniform_vec4( + static_cast(m_umap[id]), + glm::value_ptr(v), + pp::renderer::gl::OpenGlUniformVec4Dispatch { + .uniform_4fv = set_opengl_uniform_4fv, + }); + if (!status.ok()) + LOG("Shader::u_vec4() failed because: %s", status.message); + } } void Shader::u_vec3(kShaderUniform id, const glm::vec3& v) { if (m_umap.count(id) == 0) LOG("UNIFORM vec3 %d NOT FOUND in shader %d", (int)id, (int)name) - else glUniform3fv(m_umap[id], 1, glm::value_ptr(v)); + else + { + const auto status = pp::renderer::gl::set_opengl_uniform_vec3( + static_cast(m_umap[id]), + glm::value_ptr(v), + pp::renderer::gl::OpenGlUniformVec3Dispatch { + .uniform_3fv = set_opengl_uniform_3fv, + }); + if (!status.ok()) + LOG("Shader::u_vec3() failed because: %s", status.message); + } } void Shader::u_vec2(kShaderUniform id, const glm::vec2& v) { if (m_umap.count(id) == 0) LOG("UNIFORM vec2 %d NOT FOUND in shader %d", (int)id, (int)name) - else glUniform2fv(m_umap[id], 1, glm::value_ptr(v)); + else + { + const auto status = pp::renderer::gl::set_opengl_uniform_vec2( + static_cast(m_umap[id]), + glm::value_ptr(v), + pp::renderer::gl::OpenGlUniformVec2Dispatch { + .uniform_2fv = set_opengl_uniform_2fv, + }); + if (!status.ok()) + LOG("Shader::u_vec2() failed because: %s", status.message); + } } void Shader::u_mat4(kShaderUniform id, const glm::mat4& m) { if (m_umap.count(id) == 0) LOG("UNIFORM mat4 %d NOT FOUND in shader %d", (int)id, (int)name) - else glUniformMatrix4fv(m_umap[id], 1, matrix_uniform_not_transposed(), glm::value_ptr(m)); + else + { + const auto status = pp::renderer::gl::set_opengl_uniform_mat4( + static_cast(m_umap[id]), + glm::value_ptr(m), + pp::renderer::gl::OpenGlUniformMat4Dispatch { + .uniform_matrix_4fv = set_opengl_uniform_matrix_4fv, + }); + if (!status.ok()) + LOG("Shader::u_mat4() failed because: %s", status.message); + } } void Shader::u_int(kShaderUniform id, int i) { if (m_umap.count(id) == 0) LOG("UNIFORM int %d NOT FOUND in shader %d", (int)id, (int)name) else - glUniform1i(m_umap[id], i); + { + const auto status = pp::renderer::gl::set_opengl_uniform_int( + static_cast(m_umap[id]), + static_cast(i), + pp::renderer::gl::OpenGlUniformIntDispatch { + .uniform_1i = set_opengl_uniform_1i, + }); + if (!status.ok()) + LOG("Shader::u_int() failed because: %s", status.message); + } } void Shader::u_int(const char* uniform_name, int i) { - glUniform1i(glGetAttribLocation(prog, uniform_name), i); + const auto location = pp::renderer::gl::get_opengl_attribute_location( + static_cast(prog), + uniform_name, + pp::renderer::gl::OpenGlAttributeLocationDispatch { + .get_attrib_location = get_opengl_attrib_location, + }); + if (!location.ok()) + { + LOG("Shader::u_int(name) lookup failed because: %s", location.status().message); + return; + } + const auto status = pp::renderer::gl::set_opengl_uniform_int( + location.value(), + static_cast(i), + pp::renderer::gl::OpenGlUniformIntDispatch { + .uniform_1i = set_opengl_uniform_1i, + }); + if (!status.ok()) + LOG("Shader::u_int(name) failed because: %s", status.message); } void Shader::u_float(kShaderUniform id, float f) { if (m_umap.count(id) == 0) LOG("UNIFORM float %d NOT FOUND in shader %d", (int)id, (int)name) - else glUniform1f(m_umap[id], f); + else + { + const auto status = pp::renderer::gl::set_opengl_uniform_float( + static_cast(m_umap[id]), + f, + pp::renderer::gl::OpenGlUniformFloatDispatch { + .uniform_1f = set_opengl_uniform_1f, + }); + if (!status.ok()) + LOG("Shader::u_float() failed because: %s", status.message); + } } GLint Shader::GetAttribLocation(const char* attribute_name) { - return glGetAttribLocation(prog, attribute_name); + const auto location = pp::renderer::gl::get_opengl_attribute_location( + static_cast(prog), + attribute_name, + pp::renderer::gl::OpenGlAttributeLocationDispatch { + .get_attrib_location = get_opengl_attrib_location, + }); + if (!location.ok()) + { + LOG("Shader::GetAttribLocation() failed because: %s", location.status().message); + return -1; + } + return static_cast(location.value()); } bool ShaderManager::load(kShader id, const std::string& path) diff --git a/tests/renderer_gl/capabilities_tests.cpp b/tests/renderer_gl/capabilities_tests.cpp index 43d9acc..27c3e08 100644 --- a/tests/renderer_gl/capabilities_tests.cpp +++ b/tests/renderer_gl/capabilities_tests.cpp @@ -96,6 +96,21 @@ struct RecordedOpenGlSamplerBorderCall { const float* values = nullptr; }; +struct RecordedOpenGlUniformFloatVectorCall { + std::int32_t location = 0; + std::int32_t count = 0; + const float* values = nullptr; + std::uint8_t transpose = 0; + std::uint32_t component_count = 0; +}; + +struct RecordedOpenGlUniformScalarCall { + std::int32_t location = 0; + std::int32_t int_value = 0; + float float_value = 0.0F; + bool is_float = false; +}; + std::vector recorded_state_calls; std::vector recorded_string_queries; std::vector recorded_clear_calls; @@ -118,6 +133,11 @@ std::vector recorded_blit_framebuffer_calls; std::vector recorded_generated_sampler_counts; std::vector recorded_sampler_parameter_calls; std::vector recorded_sampler_border_calls; +std::vector recorded_deleted_programs; +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::uint32_t next_texture_id = 91U; std::uint32_t next_framebuffer_id = 44U; std::uint32_t next_sampler_id = 71U; @@ -495,6 +515,83 @@ void record_sampler_parameter_fv( }); } +void record_delete_program(std::uint32_t program) noexcept +{ + recorded_deleted_programs.push_back(program); +} + +void record_uniform_4fv(std::int32_t location, std::int32_t count, const float* values) noexcept +{ + recorded_uniform_vector_calls.push_back(RecordedOpenGlUniformFloatVectorCall { + .location = location, + .count = count, + .values = values, + .component_count = 4U, + }); +} + +void record_uniform_3fv(std::int32_t location, std::int32_t count, const float* values) noexcept +{ + recorded_uniform_vector_calls.push_back(RecordedOpenGlUniformFloatVectorCall { + .location = location, + .count = count, + .values = values, + .component_count = 3U, + }); +} + +void record_uniform_2fv(std::int32_t location, std::int32_t count, const float* values) noexcept +{ + recorded_uniform_vector_calls.push_back(RecordedOpenGlUniformFloatVectorCall { + .location = location, + .count = count, + .values = values, + .component_count = 2U, + }); +} + +void record_uniform_matrix_4fv( + std::int32_t location, + std::int32_t count, + std::uint8_t transpose, + const float* values) noexcept +{ + recorded_uniform_vector_calls.push_back(RecordedOpenGlUniformFloatVectorCall { + .location = location, + .count = count, + .values = values, + .transpose = transpose, + .component_count = 16U, + }); +} + +void record_uniform_1i(std::int32_t location, std::int32_t value) noexcept +{ + recorded_uniform_scalar_calls.push_back(RecordedOpenGlUniformScalarCall { + .location = location, + .int_value = value, + }); +} + +void record_uniform_1f(std::int32_t location, float value) noexcept +{ + recorded_uniform_scalar_calls.push_back(RecordedOpenGlUniformScalarCall { + .location = location, + .float_value = value, + .is_float = true, + }); +} + +std::int32_t record_get_attrib_location(std::uint32_t program, const char* name) noexcept +{ + recorded_attrib_location_programs.push_back(program); + recorded_attrib_location_names.push_back(name == nullptr ? std::string_view {} : std::string_view { name }); + if (name != nullptr && std::string_view { name } == std::string_view { "uvs" }) { + return 1; + } + return -1; +} + void detects_common_extension_capabilities(pp::tests::Harness& h) { constexpr std::array extensions { @@ -1944,6 +2041,221 @@ void rejects_invalid_sampler_dispatch(pp::tests::Harness& h) PP_EXPECT(h, missing_bind_dispatch.code == pp::foundation::StatusCode::invalid_argument); } +void uses_and_deletes_programs_through_dispatch(pp::tests::Harness& h) +{ + recorded_binding_calls.clear(); + recorded_deleted_programs.clear(); + + const auto use_status = pp::renderer::gl::use_opengl_program( + 91U, + pp::renderer::gl::OpenGlProgramUseDispatch { + .use_program = record_use_program, + }); + const auto delete_status = pp::renderer::gl::delete_opengl_program( + 91U, + pp::renderer::gl::OpenGlProgramDeleteDispatch { + .use_program = record_use_program, + .delete_program = record_delete_program, + }); + const auto delete_zero_status = pp::renderer::gl::delete_opengl_program( + 0U, + pp::renderer::gl::OpenGlProgramDeleteDispatch { + .use_program = record_use_program, + .delete_program = record_delete_program, + }); + + PP_EXPECT(h, use_status.ok()); + PP_EXPECT(h, delete_status.ok()); + PP_EXPECT(h, delete_zero_status.ok()); + PP_EXPECT(h, recorded_binding_calls.size() == 2U); + PP_EXPECT(h, recorded_binding_calls[0].kind == RecordedOpenGlBindingCall::Kind::use_program); + PP_EXPECT(h, recorded_binding_calls[0].first == 91U); + PP_EXPECT(h, recorded_binding_calls[1].kind == RecordedOpenGlBindingCall::Kind::use_program); + PP_EXPECT(h, recorded_binding_calls[1].first == 0U); + PP_EXPECT(h, recorded_deleted_programs.size() == 1U); + PP_EXPECT(h, recorded_deleted_programs[0] == 91U); +} + +void sets_vector_uniforms_through_dispatch(pp::tests::Harness& h) +{ + recorded_uniform_vector_calls.clear(); + constexpr std::array values { + 1.0F, 2.0F, 3.0F, 4.0F, + 5.0F, 6.0F, 7.0F, 8.0F, + 9.0F, 10.0F, 11.0F, 12.0F, + 13.0F, 14.0F, 15.0F, 16.0F, + }; + + const auto vec4_status = pp::renderer::gl::set_opengl_uniform_vec4( + 4, + values.data(), + pp::renderer::gl::OpenGlUniformVec4Dispatch { + .uniform_4fv = record_uniform_4fv, + }); + const auto vec3_status = pp::renderer::gl::set_opengl_uniform_vec3( + 3, + values.data(), + pp::renderer::gl::OpenGlUniformVec3Dispatch { + .uniform_3fv = record_uniform_3fv, + }); + const auto vec2_status = pp::renderer::gl::set_opengl_uniform_vec2( + 2, + values.data(), + pp::renderer::gl::OpenGlUniformVec2Dispatch { + .uniform_2fv = record_uniform_2fv, + }); + const auto mat4_status = pp::renderer::gl::set_opengl_uniform_mat4( + -1, + values.data(), + pp::renderer::gl::OpenGlUniformMat4Dispatch { + .uniform_matrix_4fv = record_uniform_matrix_4fv, + }); + + PP_EXPECT(h, vec4_status.ok()); + PP_EXPECT(h, vec3_status.ok()); + PP_EXPECT(h, vec2_status.ok()); + PP_EXPECT(h, mat4_status.ok()); + PP_EXPECT(h, recorded_uniform_vector_calls.size() == 4U); + PP_EXPECT(h, recorded_uniform_vector_calls[0].location == 4); + PP_EXPECT(h, recorded_uniform_vector_calls[0].count == 1); + PP_EXPECT(h, recorded_uniform_vector_calls[0].values == values.data()); + PP_EXPECT(h, recorded_uniform_vector_calls[0].component_count == 4U); + PP_EXPECT(h, recorded_uniform_vector_calls[1].component_count == 3U); + PP_EXPECT(h, recorded_uniform_vector_calls[2].component_count == 2U); + PP_EXPECT(h, recorded_uniform_vector_calls[3].location == -1); + PP_EXPECT(h, recorded_uniform_vector_calls[3].transpose == 0U); + PP_EXPECT(h, recorded_uniform_vector_calls[3].component_count == 16U); +} + +void sets_scalar_uniforms_through_dispatch(pp::tests::Harness& h) +{ + recorded_uniform_scalar_calls.clear(); + + const auto int_status = pp::renderer::gl::set_opengl_uniform_int( + 7, + 42, + pp::renderer::gl::OpenGlUniformIntDispatch { + .uniform_1i = record_uniform_1i, + }); + const auto float_status = pp::renderer::gl::set_opengl_uniform_float( + -1, + 0.25F, + pp::renderer::gl::OpenGlUniformFloatDispatch { + .uniform_1f = record_uniform_1f, + }); + + PP_EXPECT(h, int_status.ok()); + PP_EXPECT(h, float_status.ok()); + PP_EXPECT(h, recorded_uniform_scalar_calls.size() == 2U); + PP_EXPECT(h, recorded_uniform_scalar_calls[0].location == 7); + PP_EXPECT(h, recorded_uniform_scalar_calls[0].int_value == 42); + PP_EXPECT(h, !recorded_uniform_scalar_calls[0].is_float); + PP_EXPECT(h, recorded_uniform_scalar_calls[1].location == -1); + PP_EXPECT(h, recorded_uniform_scalar_calls[1].float_value == 0.25F); + PP_EXPECT(h, recorded_uniform_scalar_calls[1].is_float); +} + +void queries_attribute_location_through_dispatch(pp::tests::Harness& h) +{ + recorded_attrib_location_programs.clear(); + recorded_attrib_location_names.clear(); + + const auto location = pp::renderer::gl::get_opengl_attribute_location( + 11U, + "uvs", + pp::renderer::gl::OpenGlAttributeLocationDispatch { + .get_attrib_location = record_get_attrib_location, + }); + const auto missing = pp::renderer::gl::get_opengl_attribute_location( + 11U, + "missing", + pp::renderer::gl::OpenGlAttributeLocationDispatch { + .get_attrib_location = record_get_attrib_location, + }); + + PP_EXPECT(h, location.ok()); + PP_EXPECT(h, location.value() == 1); + PP_EXPECT(h, missing.ok()); + PP_EXPECT(h, missing.value() == -1); + PP_EXPECT(h, recorded_attrib_location_programs.size() == 2U); + PP_EXPECT(h, recorded_attrib_location_programs[0] == 11U); + PP_EXPECT(h, recorded_attrib_location_names[0] == std::string_view("uvs")); + PP_EXPECT(h, recorded_attrib_location_names[1] == std::string_view("missing")); +} + +void rejects_invalid_program_uniform_dispatch(pp::tests::Harness& h) +{ + constexpr std::array values { 1.0F, 2.0F, 3.0F, 4.0F }; + + const auto missing_use = pp::renderer::gl::use_opengl_program( + 1U, + pp::renderer::gl::OpenGlProgramUseDispatch {}); + const auto missing_delete = pp::renderer::gl::delete_opengl_program( + 1U, + pp::renderer::gl::OpenGlProgramDeleteDispatch { + .use_program = record_use_program, + }); + const auto missing_vec4 = pp::renderer::gl::set_opengl_uniform_vec4( + 1, + values.data(), + pp::renderer::gl::OpenGlUniformVec4Dispatch {}); + const auto null_vec3 = pp::renderer::gl::set_opengl_uniform_vec3( + 1, + nullptr, + pp::renderer::gl::OpenGlUniformVec3Dispatch { + .uniform_3fv = record_uniform_3fv, + }); + const auto missing_vec2 = pp::renderer::gl::set_opengl_uniform_vec2( + 1, + values.data(), + pp::renderer::gl::OpenGlUniformVec2Dispatch {}); + const auto null_mat4 = pp::renderer::gl::set_opengl_uniform_mat4( + 1, + nullptr, + pp::renderer::gl::OpenGlUniformMat4Dispatch { + .uniform_matrix_4fv = record_uniform_matrix_4fv, + }); + const auto missing_int = pp::renderer::gl::set_opengl_uniform_int( + 1, + 2, + pp::renderer::gl::OpenGlUniformIntDispatch {}); + const auto missing_float = pp::renderer::gl::set_opengl_uniform_float( + 1, + 2.0F, + pp::renderer::gl::OpenGlUniformFloatDispatch {}); + const auto missing_attrib_dispatch = pp::renderer::gl::get_opengl_attribute_location( + 1U, + "pos", + pp::renderer::gl::OpenGlAttributeLocationDispatch {}); + const auto invalid_attrib_name = pp::renderer::gl::get_opengl_attribute_location( + 1U, + "", + pp::renderer::gl::OpenGlAttributeLocationDispatch { + .get_attrib_location = record_get_attrib_location, + }); + + PP_EXPECT(h, !missing_use.ok()); + PP_EXPECT(h, missing_use.code == pp::foundation::StatusCode::invalid_argument); + PP_EXPECT(h, !missing_delete.ok()); + PP_EXPECT(h, missing_delete.code == pp::foundation::StatusCode::invalid_argument); + PP_EXPECT(h, !missing_vec4.ok()); + PP_EXPECT(h, missing_vec4.code == pp::foundation::StatusCode::invalid_argument); + PP_EXPECT(h, !null_vec3.ok()); + PP_EXPECT(h, null_vec3.code == pp::foundation::StatusCode::invalid_argument); + PP_EXPECT(h, !missing_vec2.ok()); + PP_EXPECT(h, missing_vec2.code == pp::foundation::StatusCode::invalid_argument); + PP_EXPECT(h, !null_mat4.ok()); + PP_EXPECT(h, null_mat4.code == pp::foundation::StatusCode::invalid_argument); + PP_EXPECT(h, !missing_int.ok()); + PP_EXPECT(h, missing_int.code == pp::foundation::StatusCode::invalid_argument); + PP_EXPECT(h, !missing_float.ok()); + PP_EXPECT(h, missing_float.code == pp::foundation::StatusCode::invalid_argument); + PP_EXPECT(h, !missing_attrib_dispatch.ok()); + PP_EXPECT(h, missing_attrib_dispatch.status().code == pp::foundation::StatusCode::invalid_argument); + PP_EXPECT(h, !invalid_attrib_name.ok()); + PP_EXPECT(h, invalid_attrib_name.status().code == pp::foundation::StatusCode::invalid_argument); +} + void updates_texture_2d_through_dispatch(pp::tests::Harness& h) { recorded_binding_calls.clear(); @@ -2726,6 +3038,11 @@ int main() harness.run("sets_sampler_border_color_through_dispatch", sets_sampler_border_color_through_dispatch); harness.run("binds_sampler_through_dispatch", binds_sampler_through_dispatch); harness.run("rejects_invalid_sampler_dispatch", rejects_invalid_sampler_dispatch); + harness.run("uses_and_deletes_programs_through_dispatch", uses_and_deletes_programs_through_dispatch); + harness.run("sets_vector_uniforms_through_dispatch", sets_vector_uniforms_through_dispatch); + 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("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);