diff --git a/docs/modernization/build-inventory.md b/docs/modernization/build-inventory.md index 1118c55..3fed924 100644 --- a/docs/modernization/build-inventory.md +++ b/docs/modernization/build-inventory.md @@ -212,6 +212,9 @@ Known local toolchain state: border-color parameter mapping used by legacy `Sampler`, plus renderer API sampler filter/address-mode to OpenGL token mapping including mirrored-repeat and aggregate renderer API sampler-state to OpenGL min/mag/wrap mapping. + Legacy `TextureCube` allocation/bind/delete and legacy `Sampler` + create/configure/border/bind/unbind calls now consume those resource dispatch + contracts directly from the retained app utilities. The PanoPainter shader attribute binding catalog, shader stage tokens, compile/link status queries, active-uniform count query, and matrix-uniform transpose token used diff --git a/docs/modernization/roadmap.md b/docs/modernization/roadmap.md index 13066f1..8528d21 100644 --- a/docs/modernization/roadmap.md +++ b/docs/modernization/roadmap.md @@ -1109,6 +1109,10 @@ Results: Sampler parameter validation covers wrap S/T/R plus min/mag filter ordering used by legacy `Sampler::set` and `Sampler::set_filter`, plus the desktop border-color parameter name used by `Sampler::set_border`. + Legacy `TextureCube` allocation/bind/delete and `Sampler` + create/configure/border/bind/unbind paths now execute through tested + `pp_renderer_gl` dispatch contracts, keeping cube-map and sampler resource + lifecycle reachable without a live GL context. Shader attribute binding catalog validation covers the current `pos`, `uvs`, `uvs2`, `col`, and `nor` bindings and rejects empty, unnamed, null-name, and duplicate-name catalogs while preserving legacy shared locations. Shader diff --git a/src/renderer_gl/opengl_capabilities.cpp b/src/renderer_gl/opengl_capabilities.cpp index 186d47b..fe04afd 100644 --- a/src/renderer_gl/opengl_capabilities.cpp +++ b/src/renderer_gl/opengl_capabilities.cpp @@ -480,6 +480,65 @@ pp::foundation::Status delete_opengl_texture_2d( return pp::foundation::Status::success(); } +pp::foundation::Status delete_opengl_texture_objects( + std::span texture_ids, + OpenGlTexture2DDeleteDispatch dispatch) noexcept +{ + if (dispatch.delete_textures == nullptr) { + return pp::foundation::Status::invalid_argument("OpenGL texture delete dispatch callback must not be null"); + } + + if (texture_ids.empty()) { + return pp::foundation::Status::success(); + } + + dispatch.delete_textures(static_cast(texture_ids.size()), texture_ids.data()); + return pp::foundation::Status::success(); +} + +pp::foundation::Result allocate_opengl_texture_cube( + OpenGlTextureCubeAllocation allocation, + OpenGlTextureCubeAllocationDispatch dispatch) noexcept +{ + if (dispatch.gen_textures == nullptr + || dispatch.bind_texture == nullptr + || dispatch.tex_image_2d == nullptr) { + return pp::foundation::Result::failure( + pp::foundation::Status::invalid_argument( + "OpenGL cube texture allocation dispatch callbacks must not be null")); + } + + if (allocation.resolution <= 0 + || allocation.internal_format == 0 + || allocation.pixel_format == 0U + || allocation.component_type == 0U) { + return pp::foundation::Result::failure( + pp::foundation::Status::invalid_argument("OpenGL cube texture allocation parameters are invalid")); + } + + std::uint32_t texture_id = 0U; + dispatch.gen_textures(1U, &texture_id); + if (texture_id == 0U) { + return pp::foundation::Result::failure( + pp::foundation::Status::out_of_range("OpenGL cube texture allocation returned id 0")); + } + + dispatch.bind_texture(texture_cube_map_target(), texture_id); + for (std::uint32_t face_index = 0U; face_index < 6U; ++face_index) { + dispatch.tex_image_2d( + cube_map_allocation_face_texture_target(face_index), + 0, + allocation.internal_format, + allocation.resolution, + allocation.resolution, + 0, + allocation.pixel_format, + allocation.component_type, + nullptr); + } + return pp::foundation::Result::success(texture_id); +} + pp::foundation::Status bind_opengl_texture_2d( std::uint32_t texture_id, OpenGlTexture2DBindDispatch dispatch) noexcept @@ -492,6 +551,18 @@ pp::foundation::Status bind_opengl_texture_2d( return pp::foundation::Status::success(); } +pp::foundation::Status bind_opengl_texture_cube( + std::uint32_t texture_id, + OpenGlTexture2DBindDispatch dispatch) noexcept +{ + if (dispatch.bind_texture == nullptr) { + return pp::foundation::Status::invalid_argument("OpenGL cube texture bind dispatch callback must not be null"); + } + + dispatch.bind_texture(texture_cube_map_target(), texture_id); + return pp::foundation::Status::success(); +} + pp::foundation::Status update_opengl_texture_2d( OpenGlTexture2DUpdate update, OpenGlTexture2DUpdateDispatch dispatch) noexcept @@ -714,6 +785,104 @@ pp::foundation::Status restore_opengl_framebuffer_binding( return pp::foundation::Status::success(); } +pp::foundation::Result create_opengl_sampler( + std::span parameters, + OpenGlSamplerCreateDispatch dispatch) noexcept +{ + if (dispatch.gen_samplers == nullptr || dispatch.sampler_parameter_i == nullptr) { + return pp::foundation::Result::failure( + pp::foundation::Status::invalid_argument("OpenGL sampler create dispatch callbacks must not be null")); + } + + if (parameters.empty()) { + return pp::foundation::Result::failure( + pp::foundation::Status::invalid_argument("OpenGL sampler parameters are invalid")); + } + + for (const auto parameter : parameters) { + if (parameter.name == 0U) { + return pp::foundation::Result::failure( + pp::foundation::Status::invalid_argument("OpenGL sampler parameter name is invalid")); + } + } + + std::uint32_t sampler_id = 0U; + dispatch.gen_samplers(1U, &sampler_id); + if (sampler_id == 0U) { + return pp::foundation::Result::failure( + pp::foundation::Status::out_of_range("OpenGL sampler allocation returned id 0")); + } + + const auto status = set_opengl_sampler_parameters( + sampler_id, + parameters, + OpenGlSamplerParameterDispatch { + .sampler_parameter_i = dispatch.sampler_parameter_i, + }); + if (!status.ok()) { + return pp::foundation::Result::failure(status); + } + + return pp::foundation::Result::success(sampler_id); +} + +pp::foundation::Status set_opengl_sampler_parameters( + std::uint32_t sampler_id, + std::span parameters, + OpenGlSamplerParameterDispatch dispatch) noexcept +{ + if (dispatch.sampler_parameter_i == nullptr) { + return pp::foundation::Status::invalid_argument("OpenGL sampler parameter dispatch callback must not be null"); + } + + if (sampler_id == 0U || parameters.empty()) { + return pp::foundation::Status::invalid_argument("OpenGL sampler parameters are invalid"); + } + + for (const auto parameter : parameters) { + if (parameter.name == 0U) { + return pp::foundation::Status::invalid_argument("OpenGL sampler parameter name is invalid"); + } + dispatch.sampler_parameter_i( + sampler_id, + parameter.name, + static_cast(parameter.value)); + } + return pp::foundation::Status::success(); +} + +pp::foundation::Status set_opengl_sampler_border_color( + std::uint32_t sampler_id, + std::uint32_t parameter, + const float* rgba, + OpenGlSamplerBorderDispatch dispatch) noexcept +{ + if (dispatch.sampler_parameter_fv == nullptr) { + return pp::foundation::Status::invalid_argument( + "OpenGL sampler border color dispatch callback must not be null"); + } + + if (sampler_id == 0U || parameter == 0U || rgba == nullptr) { + return pp::foundation::Status::invalid_argument("OpenGL sampler border color parameters are invalid"); + } + + dispatch.sampler_parameter_fv(sampler_id, parameter, rgba); + return pp::foundation::Status::success(); +} + +pp::foundation::Status bind_opengl_sampler_object( + std::uint32_t unit, + std::uint32_t sampler_id, + OpenGlSamplerBindDispatch dispatch) noexcept +{ + if (dispatch.bind_sampler == nullptr) { + return pp::foundation::Status::invalid_argument("OpenGL sampler bind dispatch callback must not be null"); + } + + dispatch.bind_sampler(unit, sampler_id); + return pp::foundation::Status::success(); +} + 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 bd68010..28aba6c 100644 --- a/src/renderer_gl/opengl_capabilities.h +++ b/src/renderer_gl/opengl_capabilities.h @@ -119,6 +119,13 @@ struct OpenGlTexture2DAllocation { const void* data = nullptr; }; +struct OpenGlTextureCubeAllocation { + std::int32_t resolution = 0; + std::int32_t internal_format = 0; + std::uint32_t pixel_format = 0; + std::uint32_t component_type = 0; +}; + struct OpenGlTexture2DUpdate { std::uint32_t texture_id = 0; std::int32_t width = 0; @@ -216,6 +223,14 @@ using OpenGlUseProgramFn = void (*)(std::uint32_t program) 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; +using OpenGlSamplerParameteriFn = void (*)( + std::uint32_t sampler, + std::uint32_t parameter, + std::int32_t value) noexcept; +using OpenGlSamplerParameterfvFn = void (*)( + std::uint32_t sampler, + std::uint32_t parameter, + const float* values) noexcept; using OpenGlGenObjectsFn = void (*)(std::uint32_t count, std::uint32_t* ids) noexcept; using OpenGlDeleteObjectsFn = void (*)(std::uint32_t count, const std::uint32_t* ids) noexcept; using OpenGlTexImage2DFn = void (*)( @@ -349,6 +364,12 @@ struct OpenGlTexture2DDeleteDispatch { OpenGlDeleteObjectsFn delete_textures = nullptr; }; +struct OpenGlTextureCubeAllocationDispatch { + OpenGlGenObjectsFn gen_textures = nullptr; + OpenGlBindTextureFn bind_texture = nullptr; + OpenGlTexImage2DFn tex_image_2d = nullptr; +}; + struct OpenGlTexture2DBindDispatch { OpenGlBindTextureFn bind_texture = nullptr; }; @@ -395,6 +416,23 @@ struct OpenGlFramebufferRestoreDispatch { OpenGlBindFramebufferFn bind_framebuffer = nullptr; }; +struct OpenGlSamplerCreateDispatch { + OpenGlGenObjectsFn gen_samplers = nullptr; + OpenGlSamplerParameteriFn sampler_parameter_i = nullptr; +}; + +struct OpenGlSamplerParameterDispatch { + OpenGlSamplerParameteriFn sampler_parameter_i = nullptr; +}; + +struct OpenGlSamplerBorderDispatch { + OpenGlSamplerParameterfvFn sampler_parameter_fv = nullptr; +}; + +struct OpenGlSamplerBindDispatch { + OpenGlBindSamplerFn bind_sampler = nullptr; +}; + [[nodiscard]] OpenGlCapabilities detect_opengl_capabilities( std::span extensions, OpenGlRuntime runtime) noexcept; @@ -433,9 +471,18 @@ struct OpenGlFramebufferRestoreDispatch { [[nodiscard]] pp::foundation::Status delete_opengl_texture_2d( std::uint32_t texture_id, OpenGlTexture2DDeleteDispatch dispatch) noexcept; +[[nodiscard]] pp::foundation::Status delete_opengl_texture_objects( + std::span texture_ids, + OpenGlTexture2DDeleteDispatch dispatch) noexcept; +[[nodiscard]] pp::foundation::Result allocate_opengl_texture_cube( + OpenGlTextureCubeAllocation allocation, + OpenGlTextureCubeAllocationDispatch dispatch) noexcept; [[nodiscard]] pp::foundation::Status bind_opengl_texture_2d( std::uint32_t texture_id, OpenGlTexture2DBindDispatch dispatch) noexcept; +[[nodiscard]] pp::foundation::Status bind_opengl_texture_cube( + std::uint32_t texture_id, + OpenGlTexture2DBindDispatch dispatch) noexcept; [[nodiscard]] pp::foundation::Status update_opengl_texture_2d( OpenGlTexture2DUpdate update, OpenGlTexture2DUpdateDispatch dispatch) noexcept; @@ -457,6 +504,22 @@ struct OpenGlFramebufferRestoreDispatch { [[nodiscard]] pp::foundation::Status restore_opengl_framebuffer_binding( OpenGlFramebufferBindingState binding, OpenGlFramebufferRestoreDispatch dispatch) noexcept; +[[nodiscard]] pp::foundation::Result create_opengl_sampler( + std::span parameters, + OpenGlSamplerCreateDispatch dispatch) noexcept; +[[nodiscard]] pp::foundation::Status set_opengl_sampler_parameters( + std::uint32_t sampler_id, + std::span parameters, + OpenGlSamplerParameterDispatch dispatch) noexcept; +[[nodiscard]] pp::foundation::Status set_opengl_sampler_border_color( + std::uint32_t sampler_id, + std::uint32_t parameter, + const float* rgba, + OpenGlSamplerBorderDispatch dispatch) noexcept; +[[nodiscard]] pp::foundation::Status bind_opengl_sampler_object( + std::uint32_t unit, + std::uint32_t sampler_id, + OpenGlSamplerBindDispatch dispatch) noexcept; [[nodiscard]] std::uint32_t extension_count_query() noexcept; [[nodiscard]] std::uint32_t extension_string_name() noexcept; diff --git a/src/texture.cpp b/src/texture.cpp index f71aa9b..346dc6b 100644 --- a/src/texture.cpp +++ b/src/texture.cpp @@ -9,11 +9,6 @@ namespace { -[[nodiscard]] GLenum texture_cube_map_target() noexcept -{ - return static_cast(pp::renderer::gl::texture_cube_map_target()); -} - void gen_opengl_textures(std::uint32_t count, std::uint32_t* ids) noexcept { glGenTextures(static_cast(count), reinterpret_cast(ids)); @@ -139,6 +134,61 @@ void read_opengl_pixels( pixels); } +void gen_opengl_samplers(std::uint32_t count, std::uint32_t* ids) noexcept +{ +#if USE_SAMPLER + glGenSamplers(static_cast(count), reinterpret_cast(ids)); +#else + for (std::uint32_t i = 0U; i < count; ++i) { + ids[i] = 0U; + } +#endif +} + +void set_opengl_sampler_parameter_i( + std::uint32_t sampler, + std::uint32_t parameter, + std::int32_t value) noexcept +{ +#if USE_SAMPLER + glSamplerParameteri( + static_cast(sampler), + static_cast(parameter), + static_cast(value)); +#else + (void)sampler; + (void)parameter; + (void)value; +#endif +} + +void set_opengl_sampler_parameter_fv( + std::uint32_t sampler, + std::uint32_t parameter, + const float* values) noexcept +{ +#if USE_SAMPLER && !defined(__GLES__) + glSamplerParameterfv( + static_cast(sampler), + static_cast(parameter), + values); +#else + (void)sampler; + (void)parameter; + (void)values; +#endif +} + +void bind_opengl_sampler(std::uint32_t unit, std::uint32_t sampler) noexcept +{ +#if USE_SAMPLER + glBindSampler(static_cast(unit), static_cast(sampler)); +#else + (void)unit; + (void)sampler; +#endif +} + } std::map TextureManager::m_textures; @@ -176,27 +226,25 @@ bool TextureCube::create(int resolution) noexcept { destroy(); m_resolution = resolution; - glGenTextures(1, &m_cubetex_id); - - if (!m_cubetex_id) - return; - - glBindTexture(texture_cube_map_target(), m_cubetex_id); const auto format = pp::renderer::gl::texture_format_for_channel_count(4U); - const auto component_type = static_cast(pp::renderer::gl::unsigned_byte_component_type()); - for (GLuint i = 0; i < 6; i++) + const auto texture = pp::renderer::gl::allocate_opengl_texture_cube( + pp::renderer::gl::OpenGlTextureCubeAllocation { + .resolution = m_resolution, + .internal_format = static_cast(format.internal_format), + .pixel_format = format.pixel_format, + .component_type = pp::renderer::gl::unsigned_byte_component_type(), + }, + pp::renderer::gl::OpenGlTextureCubeAllocationDispatch { + .gen_textures = gen_opengl_textures, + .bind_texture = bind_opengl_texture, + .tex_image_2d = upload_opengl_texture_2d, + }); + if (!texture.ok()) { - glTexImage2D( - static_cast(pp::renderer::gl::cube_map_allocation_face_texture_target(i)), - 0, - static_cast(format.internal_format), - m_resolution, - m_resolution, - 0, - static_cast(format.pixel_format), - component_type, - nullptr); + LOG("TextureCube::create() failed because: %s", texture.status().message); + return; } + m_cubetex_id = static_cast(texture.value()); }); return m_cubetex_id != 0; } @@ -207,8 +255,17 @@ void TextureCube::destroy() noexcept { App::I->render_task([f=m_faces, id=m_cubetex_id] { - glDeleteTextures(static_cast(f.size()), f.data()); - glDeleteTextures(1, &id); + std::array texture_ids {}; + for (std::size_t i = 0U; i < f.size(); ++i) + texture_ids[i] = static_cast(f[i]); + texture_ids[f.size()] = static_cast(id); + const auto status = pp::renderer::gl::delete_opengl_texture_objects( + texture_ids, + pp::renderer::gl::OpenGlTexture2DDeleteDispatch { + .delete_textures = delete_opengl_textures, + }); + if (!status.ok()) + LOG("TextureCube::destroy() failed because: %s", status.message); }); m_cubetex_id = 0; m_faces.fill(0); @@ -219,7 +276,13 @@ void TextureCube::destroy() noexcept void TextureCube::bind() const noexcept { assert(App::I->is_render_thread()); - glBindTexture(texture_cube_map_target(), m_cubetex_id); + const auto status = pp::renderer::gl::bind_opengl_texture_cube( + static_cast(m_cubetex_id), + pp::renderer::gl::OpenGlTexture2DBindDispatch { + .bind_texture = bind_opengl_texture, + }); + if (!status.ok()) + LOG("TextureCube::bind() failed because: %s", status.message); } bool TextureManager::load(const char* path, bool generate_mipmaps) @@ -525,15 +588,22 @@ bool Sampler::create(GLint filter, GLint wrap) bool ret = false; App::I->render_task([this, &ret, filter, wrap] { -#if USE_SAMPLER - glGenSamplers(1, &id); -#endif // USE_SAMPLER - if (id == 0) + const auto parameters = pp::renderer::gl::sampler_parameters_for_filter_wrap( + static_cast(filter), + static_cast(wrap)); + const auto sampler = pp::renderer::gl::create_opengl_sampler( + parameters, + pp::renderer::gl::OpenGlSamplerCreateDispatch { + .gen_samplers = gen_opengl_samplers, + .sampler_parameter_i = set_opengl_sampler_parameter_i, + }); + if (!sampler.ok()) { ret = false; + LOG("Sampler::create() failed because: %s", sampler.status().message); return; } - set(filter, wrap); + id = static_cast(sampler.value()); ret = true; }); return ret; @@ -555,14 +625,17 @@ void Sampler::set(GLint filter, GLint wrap) { App::I->render_task([=] { -#if USE_SAMPLER - for (const auto parameter : pp::renderer::gl::sampler_parameters_for_filter_wrap( - static_cast(filter), - static_cast(wrap))) - { - glSamplerParameteri(id, static_cast(parameter.name), static_cast(parameter.value)); - } -#endif // USE_SAMPLER + const auto parameters = pp::renderer::gl::sampler_parameters_for_filter_wrap( + static_cast(filter), + static_cast(wrap)); + const auto status = pp::renderer::gl::set_opengl_sampler_parameters( + static_cast(id), + parameters, + pp::renderer::gl::OpenGlSamplerParameterDispatch { + .sampler_parameter_i = set_opengl_sampler_parameter_i, + }); + if (!status.ok()) + LOG("Sampler::set() failed because: %s", status.message); }); } @@ -582,41 +655,57 @@ void Sampler::set_filter(GLint filter_min, GLint filter_mag) { App::I->render_task([=] { -#if USE_SAMPLER - for (const auto parameter : pp::renderer::gl::sampler_filter_parameters( - static_cast(filter_min), - static_cast(filter_mag))) - { - glSamplerParameteri(id, static_cast(parameter.name), static_cast(parameter.value)); - } -#endif // USE_SAMPLER + const auto parameters = pp::renderer::gl::sampler_filter_parameters( + static_cast(filter_min), + static_cast(filter_mag)); + const auto status = pp::renderer::gl::set_opengl_sampler_parameters( + static_cast(id), + parameters, + pp::renderer::gl::OpenGlSamplerParameterDispatch { + .sampler_parameter_i = set_opengl_sampler_parameter_i, + }); + if (!status.ok()) + LOG("Sampler::set_filter() failed because: %s", status.message); }); } void Sampler::set_border(glm::vec4 rgba) { App::I->render_task([this, rgba] { -#if USE_SAMPLER && !defined(__GLES__) - glSamplerParameterfv( - id, - static_cast(pp::renderer::gl::sampler_border_color_parameter_name()), - glm::value_ptr(rgba)); -#endif // USE_SAMPLER + const auto status = pp::renderer::gl::set_opengl_sampler_border_color( + static_cast(id), + pp::renderer::gl::sampler_border_color_parameter_name(), + glm::value_ptr(rgba), + pp::renderer::gl::OpenGlSamplerBorderDispatch { + .sampler_parameter_fv = set_opengl_sampler_parameter_fv, + }); + if (!status.ok()) + LOG("Sampler::set_border() failed because: %s", status.message); }); } void Sampler::bind(int unit) const { assert(App::I->is_render_thread()); current_unit = unit; -#if USE_SAMPLER - glBindSampler(unit, id); -#endif // USE_SAMPLER + const auto status = pp::renderer::gl::bind_opengl_sampler_object( + static_cast(unit), + static_cast(id), + pp::renderer::gl::OpenGlSamplerBindDispatch { + .bind_sampler = bind_opengl_sampler, + }); + if (!status.ok()) + LOG("Sampler::bind() failed because: %s", status.message); } void Sampler::unbind() { assert(App::I->is_render_thread()); -#if USE_SAMPLER - glBindSampler(current_unit, 0); -#endif // USE_SAMPLER + const auto status = pp::renderer::gl::bind_opengl_sampler_object( + static_cast(current_unit), + 0U, + pp::renderer::gl::OpenGlSamplerBindDispatch { + .bind_sampler = bind_opengl_sampler, + }); + if (!status.ok()) + LOG("Sampler::unbind() failed because: %s", status.message); } diff --git a/tests/renderer_gl/capabilities_tests.cpp b/tests/renderer_gl/capabilities_tests.cpp index 58fe653..43d9acc 100644 --- a/tests/renderer_gl/capabilities_tests.cpp +++ b/tests/renderer_gl/capabilities_tests.cpp @@ -84,6 +84,18 @@ struct RecordedOpenGlBlitFramebufferCall { std::uint32_t filter = 0; }; +struct RecordedOpenGlSamplerParameterCall { + std::uint32_t sampler = 0; + std::uint32_t parameter = 0; + std::int32_t value = 0; +}; + +struct RecordedOpenGlSamplerBorderCall { + std::uint32_t sampler = 0; + std::uint32_t parameter = 0; + const float* values = nullptr; +}; + std::vector recorded_state_calls; std::vector recorded_string_queries; std::vector recorded_clear_calls; @@ -103,8 +115,12 @@ std::vector recorded_framebuffer_attach std::vector recorded_framebuffer_status_queries; std::vector recorded_read_pixels_calls; 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::uint32_t next_texture_id = 91U; std::uint32_t next_framebuffer_id = 44U; +std::uint32_t next_sampler_id = 71U; std::uint32_t configured_framebuffer_status = 0x8CD5U; void record_enable(std::uint32_t state) noexcept @@ -447,6 +463,38 @@ void record_blit_framebuffer( }); } +void record_gen_samplers(std::uint32_t count, std::uint32_t* ids) noexcept +{ + recorded_generated_sampler_counts.push_back(count); + for (std::uint32_t i = 0U; i < count; ++i) { + ids[i] = next_sampler_id + i; + } +} + +void record_sampler_parameter_i( + std::uint32_t sampler, + std::uint32_t parameter, + std::int32_t value) noexcept +{ + recorded_sampler_parameter_calls.push_back(RecordedOpenGlSamplerParameterCall { + .sampler = sampler, + .parameter = parameter, + .value = value, + }); +} + +void record_sampler_parameter_fv( + std::uint32_t sampler, + std::uint32_t parameter, + const float* values) noexcept +{ + recorded_sampler_border_calls.push_back(RecordedOpenGlSamplerBorderCall { + .sampler = sampler, + .parameter = parameter, + .values = values, + }); +} + void detects_common_extension_capabilities(pp::tests::Harness& h) { constexpr std::array extensions { @@ -1626,6 +1674,276 @@ void deletes_and_binds_texture_2d_through_dispatch(pp::tests::Harness& h) PP_EXPECT(h, recorded_binding_calls[0].second == 27U); } +void deletes_texture_objects_through_dispatch(pp::tests::Harness& h) +{ + recorded_deleted_textures.clear(); + + constexpr std::array textures { 0U, 7U, 11U }; + const auto status = pp::renderer::gl::delete_opengl_texture_objects( + textures, + pp::renderer::gl::OpenGlTexture2DDeleteDispatch { + .delete_textures = record_delete_textures, + }); + + PP_EXPECT(h, status.ok()); + PP_EXPECT(h, recorded_deleted_textures.size() == 3U); + PP_EXPECT(h, recorded_deleted_textures[0] == 0U); + PP_EXPECT(h, recorded_deleted_textures[1] == 7U); + PP_EXPECT(h, recorded_deleted_textures[2] == 11U); +} + +void allocates_texture_cube_through_dispatch(pp::tests::Harness& h) +{ + recorded_generated_texture_counts.clear(); + recorded_binding_calls.clear(); + recorded_texture_image_calls.clear(); + next_texture_id = 121U; + + const auto texture = pp::renderer::gl::allocate_opengl_texture_cube( + pp::renderer::gl::OpenGlTextureCubeAllocation { + .resolution = 64, + .internal_format = 0x8058, + .pixel_format = 0x1908U, + .component_type = 0x1401U, + }, + pp::renderer::gl::OpenGlTextureCubeAllocationDispatch { + .gen_textures = record_gen_textures, + .bind_texture = record_bind_texture, + .tex_image_2d = record_tex_image_2d, + }); + + PP_EXPECT(h, texture.ok()); + PP_EXPECT(h, texture.value() == 121U); + PP_EXPECT(h, recorded_generated_texture_counts.size() == 1U); + PP_EXPECT(h, recorded_generated_texture_counts[0] == 1U); + PP_EXPECT(h, recorded_binding_calls.size() == 1U); + PP_EXPECT(h, recorded_binding_calls[0].kind == RecordedOpenGlBindingCall::Kind::bind_texture); + PP_EXPECT(h, recorded_binding_calls[0].first == 0x8513U); + PP_EXPECT(h, recorded_binding_calls[0].second == 121U); + PP_EXPECT(h, recorded_texture_image_calls.size() == 6U); + for (std::size_t i = 0U; i < recorded_texture_image_calls.size(); ++i) { + PP_EXPECT(h, recorded_texture_image_calls[i].target == 0x8515U + static_cast(i)); + PP_EXPECT(h, recorded_texture_image_calls[i].width == 64); + PP_EXPECT(h, recorded_texture_image_calls[i].height == 64); + PP_EXPECT(h, recorded_texture_image_calls[i].internal_format == 0x8058); + PP_EXPECT(h, recorded_texture_image_calls[i].pixel_format == 0x1908U); + PP_EXPECT(h, recorded_texture_image_calls[i].component_type == 0x1401U); + PP_EXPECT(h, recorded_texture_image_calls[i].data == nullptr); + } +} + +void rejects_invalid_texture_cube_allocation(pp::tests::Harness& h) +{ + const auto missing_dispatch = pp::renderer::gl::allocate_opengl_texture_cube( + pp::renderer::gl::OpenGlTextureCubeAllocation { + .resolution = 1, + .internal_format = 0x8058, + .pixel_format = 0x1908U, + .component_type = 0x1401U, + }, + pp::renderer::gl::OpenGlTextureCubeAllocationDispatch { + .gen_textures = record_gen_textures, + .bind_texture = record_bind_texture, + }); + const auto invalid_resolution = pp::renderer::gl::allocate_opengl_texture_cube( + pp::renderer::gl::OpenGlTextureCubeAllocation { + .resolution = 0, + .internal_format = 0x8058, + .pixel_format = 0x1908U, + .component_type = 0x1401U, + }, + pp::renderer::gl::OpenGlTextureCubeAllocationDispatch { + .gen_textures = record_gen_textures, + .bind_texture = record_bind_texture, + .tex_image_2d = record_tex_image_2d, + }); + + PP_EXPECT(h, !missing_dispatch.ok()); + PP_EXPECT(h, missing_dispatch.status().code == pp::foundation::StatusCode::invalid_argument); + PP_EXPECT(h, !invalid_resolution.ok()); + PP_EXPECT(h, invalid_resolution.status().code == pp::foundation::StatusCode::invalid_argument); +} + +void binds_texture_cube_through_dispatch(pp::tests::Harness& h) +{ + recorded_binding_calls.clear(); + + const auto status = pp::renderer::gl::bind_opengl_texture_cube( + 33U, + pp::renderer::gl::OpenGlTexture2DBindDispatch { + .bind_texture = record_bind_texture, + }); + + PP_EXPECT(h, status.ok()); + PP_EXPECT(h, recorded_binding_calls.size() == 1U); + PP_EXPECT(h, recorded_binding_calls[0].kind == RecordedOpenGlBindingCall::Kind::bind_texture); + PP_EXPECT(h, recorded_binding_calls[0].first == 0x8513U); + PP_EXPECT(h, recorded_binding_calls[0].second == 33U); +} + +void creates_sampler_through_dispatch(pp::tests::Harness& h) +{ + recorded_generated_sampler_counts.clear(); + recorded_sampler_parameter_calls.clear(); + next_sampler_id = 81U; + const auto parameters = pp::renderer::gl::sampler_parameters_for_filter_wrap(0x2601U, 0x812FU); + + const auto sampler = pp::renderer::gl::create_opengl_sampler( + parameters, + pp::renderer::gl::OpenGlSamplerCreateDispatch { + .gen_samplers = record_gen_samplers, + .sampler_parameter_i = record_sampler_parameter_i, + }); + + PP_EXPECT(h, sampler.ok()); + PP_EXPECT(h, sampler.value() == 81U); + PP_EXPECT(h, recorded_generated_sampler_counts.size() == 1U); + PP_EXPECT(h, recorded_generated_sampler_counts[0] == 1U); + PP_EXPECT(h, recorded_sampler_parameter_calls.size() == parameters.size()); + for (std::size_t i = 0U; i < parameters.size(); ++i) { + PP_EXPECT(h, recorded_sampler_parameter_calls[i].sampler == 81U); + PP_EXPECT(h, recorded_sampler_parameter_calls[i].parameter == parameters[i].name); + PP_EXPECT(h, recorded_sampler_parameter_calls[i].value == static_cast(parameters[i].value)); + } +} + +void sets_sampler_parameters_through_dispatch(pp::tests::Harness& h) +{ + recorded_sampler_parameter_calls.clear(); + const auto parameters = pp::renderer::gl::sampler_filter_parameters(0x2600U, 0x2601U); + + const auto status = pp::renderer::gl::set_opengl_sampler_parameters( + 17U, + parameters, + pp::renderer::gl::OpenGlSamplerParameterDispatch { + .sampler_parameter_i = record_sampler_parameter_i, + }); + + PP_EXPECT(h, status.ok()); + PP_EXPECT(h, recorded_sampler_parameter_calls.size() == 2U); + PP_EXPECT(h, recorded_sampler_parameter_calls[0].sampler == 17U); + PP_EXPECT(h, recorded_sampler_parameter_calls[0].parameter == 0x2801U); + PP_EXPECT(h, recorded_sampler_parameter_calls[0].value == 0x2600); + PP_EXPECT(h, recorded_sampler_parameter_calls[1].parameter == 0x2800U); + PP_EXPECT(h, recorded_sampler_parameter_calls[1].value == 0x2601); +} + +void sets_sampler_border_color_through_dispatch(pp::tests::Harness& h) +{ + recorded_sampler_border_calls.clear(); + constexpr std::array rgba { 0.25F, 0.5F, 0.75F, 1.0F }; + + const auto status = pp::renderer::gl::set_opengl_sampler_border_color( + 19U, + 0x1004U, + rgba.data(), + pp::renderer::gl::OpenGlSamplerBorderDispatch { + .sampler_parameter_fv = record_sampler_parameter_fv, + }); + + PP_EXPECT(h, status.ok()); + PP_EXPECT(h, recorded_sampler_border_calls.size() == 1U); + PP_EXPECT(h, recorded_sampler_border_calls[0].sampler == 19U); + PP_EXPECT(h, recorded_sampler_border_calls[0].parameter == 0x1004U); + PP_EXPECT(h, recorded_sampler_border_calls[0].values == rgba.data()); +} + +void binds_sampler_through_dispatch(pp::tests::Harness& h) +{ + recorded_binding_calls.clear(); + + const auto bind_status = pp::renderer::gl::bind_opengl_sampler_object( + 3U, + 23U, + pp::renderer::gl::OpenGlSamplerBindDispatch { + .bind_sampler = record_bind_sampler, + }); + const auto unbind_status = pp::renderer::gl::bind_opengl_sampler_object( + 3U, + 0U, + pp::renderer::gl::OpenGlSamplerBindDispatch { + .bind_sampler = record_bind_sampler, + }); + + PP_EXPECT(h, bind_status.ok()); + PP_EXPECT(h, unbind_status.ok()); + PP_EXPECT(h, recorded_binding_calls.size() == 2U); + PP_EXPECT(h, recorded_binding_calls[0].kind == RecordedOpenGlBindingCall::Kind::bind_sampler); + PP_EXPECT(h, recorded_binding_calls[0].first == 3U); + PP_EXPECT(h, recorded_binding_calls[0].second == 23U); + PP_EXPECT(h, recorded_binding_calls[1].kind == RecordedOpenGlBindingCall::Kind::bind_sampler); + PP_EXPECT(h, recorded_binding_calls[1].first == 3U); + PP_EXPECT(h, recorded_binding_calls[1].second == 0U); +} + +void rejects_invalid_sampler_dispatch(pp::tests::Harness& h) +{ + constexpr std::array parameters { + pp::renderer::gl::OpenGlTextureParameter { .name = 0x2801U, .value = 0x2601U }, + }; + constexpr std::array invalid_parameters { + pp::renderer::gl::OpenGlTextureParameter { .name = 0U, .value = 0x2601U }, + }; + constexpr std::array rgba { 1.0F, 1.0F, 1.0F, 1.0F }; + + const auto missing_create_dispatch = pp::renderer::gl::create_opengl_sampler( + parameters, + pp::renderer::gl::OpenGlSamplerCreateDispatch { + .gen_samplers = record_gen_samplers, + }); + const auto empty_create_parameters = pp::renderer::gl::create_opengl_sampler( + {}, + pp::renderer::gl::OpenGlSamplerCreateDispatch { + .gen_samplers = record_gen_samplers, + .sampler_parameter_i = record_sampler_parameter_i, + }); + const auto zero_sampler_parameters = pp::renderer::gl::set_opengl_sampler_parameters( + 0U, + parameters, + pp::renderer::gl::OpenGlSamplerParameterDispatch { + .sampler_parameter_i = record_sampler_parameter_i, + }); + const auto invalid_parameter_name = pp::renderer::gl::set_opengl_sampler_parameters( + 7U, + invalid_parameters, + pp::renderer::gl::OpenGlSamplerParameterDispatch { + .sampler_parameter_i = record_sampler_parameter_i, + }); + const auto null_border = pp::renderer::gl::set_opengl_sampler_border_color( + 7U, + 0x1004U, + nullptr, + pp::renderer::gl::OpenGlSamplerBorderDispatch { + .sampler_parameter_fv = record_sampler_parameter_fv, + }); + const auto zero_border_parameter = pp::renderer::gl::set_opengl_sampler_border_color( + 7U, + 0U, + rgba.data(), + pp::renderer::gl::OpenGlSamplerBorderDispatch { + .sampler_parameter_fv = record_sampler_parameter_fv, + }); + const auto missing_bind_dispatch = pp::renderer::gl::bind_opengl_sampler_object( + 0U, + 7U, + pp::renderer::gl::OpenGlSamplerBindDispatch {}); + + PP_EXPECT(h, !missing_create_dispatch.ok()); + PP_EXPECT(h, missing_create_dispatch.status().code == pp::foundation::StatusCode::invalid_argument); + PP_EXPECT(h, !empty_create_parameters.ok()); + PP_EXPECT(h, empty_create_parameters.status().code == pp::foundation::StatusCode::invalid_argument); + PP_EXPECT(h, !zero_sampler_parameters.ok()); + PP_EXPECT(h, zero_sampler_parameters.code == pp::foundation::StatusCode::invalid_argument); + PP_EXPECT(h, !invalid_parameter_name.ok()); + PP_EXPECT(h, invalid_parameter_name.code == pp::foundation::StatusCode::invalid_argument); + PP_EXPECT(h, !null_border.ok()); + PP_EXPECT(h, null_border.code == pp::foundation::StatusCode::invalid_argument); + PP_EXPECT(h, !zero_border_parameter.ok()); + PP_EXPECT(h, zero_border_parameter.code == pp::foundation::StatusCode::invalid_argument); + PP_EXPECT(h, !missing_bind_dispatch.ok()); + PP_EXPECT(h, missing_bind_dispatch.code == pp::foundation::StatusCode::invalid_argument); +} + void updates_texture_2d_through_dispatch(pp::tests::Harness& h) { recorded_binding_calls.clear(); @@ -2399,6 +2717,15 @@ int main() harness.run("allocates_texture_2d_through_dispatch", allocates_texture_2d_through_dispatch); harness.run("rejects_invalid_texture_2d_allocation", rejects_invalid_texture_2d_allocation); harness.run("deletes_and_binds_texture_2d_through_dispatch", deletes_and_binds_texture_2d_through_dispatch); + harness.run("deletes_texture_objects_through_dispatch", deletes_texture_objects_through_dispatch); + harness.run("allocates_texture_cube_through_dispatch", allocates_texture_cube_through_dispatch); + harness.run("rejects_invalid_texture_cube_allocation", rejects_invalid_texture_cube_allocation); + harness.run("binds_texture_cube_through_dispatch", binds_texture_cube_through_dispatch); + harness.run("creates_sampler_through_dispatch", creates_sampler_through_dispatch); + harness.run("sets_sampler_parameters_through_dispatch", sets_sampler_parameters_through_dispatch); + 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("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);