diff --git a/docs/modernization/build-inventory.md b/docs/modernization/build-inventory.md index 8bd1d74..f406c96 100644 --- a/docs/modernization/build-inventory.md +++ b/docs/modernization/build-inventory.md @@ -286,7 +286,9 @@ Known local toolchain state: render-target texture parameters, texture/renderbuffer targets, depth format, framebuffer targets, binding queries, attachment points, and completion status used by `RTT::create` and framebuffer bind/restore paths, plus RTT - clear color/depth masks, renderer API render-pass color/depth/stencil + clear color/depth masks. Canvas object-drawing depth renderbuffer + allocation/storage/delete and framebuffer depth attach/detach also execute + through tested dispatch contracts here. Renderer API render-pass color/depth/stencil clear-mask and clear-value mapping, and color-write-mask query tokens. `RTT` no longer spells GL enum names directly. It also validates renderer API primitive-topology to OpenGL draw-mode mapping, Shape @@ -421,9 +423,9 @@ Known local toolchain state: backend-owned depth/blend state and active texture units. Canvas thumbnail generation and object-drawing helpers also consume backend-owned saved viewport/clear/blend state, active texture units, - readback format/type, framebuffer copy targets, and renderbuffer/depth - attachment parameters; `src/canvas.cpp` no longer contains raw `GL_*` - constants. + readback format/type, framebuffer copy targets, and depth renderbuffer + allocation plus framebuffer depth attach/detach dispatch contracts; + `src/canvas.cpp` no longer contains raw `GL_*` constants. Windows desktop OpenGL context creation now consumes a tested `windows_wgl_core_context_3_3_config()` catalog from `pp_renderer_gl` instead of owning active WGL context/pixel-format attribute literals in `main.cpp`. @@ -610,7 +612,9 @@ Known local toolchain state: the retained `Texture2D` utility, tested framebuffer blit/readback dispatch consumed by retained `RTT` resize/copy/readback paths, tested framebuffer bind/restore dispatch consumed by retained `RTT` render-target pass entry - and exit paths, tested convert-command state dispatch consumed by + and exit paths, tested depth renderbuffer allocation/delete and framebuffer + depth attach/detach dispatch consumed by canvas object-drawing helpers, + tested convert-command state dispatch consumed by `App::cmd_convert`, tested render platform hint dispatch consumed by `WindowsPlatformServices` and the retained macOS legacy fallback, tested debug-output state dispatch consumed by `WindowsPlatformServices`, plus diff --git a/docs/modernization/roadmap.md b/docs/modernization/roadmap.md index 05ffd4d..bff039a 100644 --- a/docs/modernization/roadmap.md +++ b/docs/modernization/roadmap.md @@ -999,7 +999,9 @@ to OpenGL token mapping, plus the default render-target texture parameters, texture/renderbuffer targets, depth format, framebuffer targets, binding queries, attachment points, and completion status used by `RTT::create` and framebuffer bind/restore paths, also live in -`pp_renderer_gl`. RTT clear color/depth masks, renderer API render-pass +`pp_renderer_gl`. Depth renderbuffer allocation/storage/delete and framebuffer +depth attach/detach sequences used by canvas object-drawing helpers now execute +through tested `pp_renderer_gl` dispatch contracts. RTT clear color/depth masks, renderer API render-pass color/depth/stencil clear-mask and clear-value mapping, and color-write-mask query tokens also live in `pp_renderer_gl`. `RTT` no longer spells GL enum names directly. Renderer API primitive-topology to OpenGL draw-mode mapping, mesh index-type @@ -2021,9 +2023,9 @@ Results: mapping. - Canvas thumbnail generation and object-drawing helpers now route saved viewport/clear/blend state, active texture units, readback format/type, - framebuffer copy targets, and renderbuffer/depth attachment parameters through - the renderer GL backend mapping; `src/canvas.cpp` no longer contains raw - `GL_*` constants. + framebuffer copy targets, and depth renderbuffer allocation plus framebuffer + depth attach/detach through tested renderer GL backend dispatch contracts; + `src/canvas.cpp` no longer contains raw `GL_*` constants. - Windows desktop OpenGL context creation now consumes a tested `windows_wgl_core_context_3_3_config()` catalog from `pp_renderer_gl`, moving the active WGL context/pixel-format attribute literals out of the platform diff --git a/src/canvas.cpp b/src/canvas.cpp index 3689bbc..e5da0ea 100644 --- a/src/canvas.cpp +++ b/src/canvas.cpp @@ -135,26 +135,6 @@ GLenum blend_state() return static_cast(pp::renderer::gl::blend_state()); } -GLenum renderbuffer_target() -{ - return static_cast(pp::renderer::gl::renderbuffer_target()); -} - -GLenum depth_component24_format() -{ - return static_cast(pp::renderer::gl::depth_component24_format()); -} - -GLenum framebuffer_target() -{ - return static_cast(pp::renderer::gl::framebuffer_target()); -} - -GLenum framebuffer_depth_attachment() -{ - return static_cast(pp::renderer::gl::framebuffer_depth_attachment()); -} - GLint texture_filter_linear() { return static_cast(pp::renderer::gl::linear_texture_filter()); @@ -195,6 +175,86 @@ void unbind_texture_2d() glBindTexture(texture_2d_target(), 0); } +void gen_opengl_renderbuffers(std::uint32_t count, std::uint32_t* ids) noexcept +{ + glGenRenderbuffers(static_cast(count), reinterpret_cast(ids)); +} + +void delete_opengl_renderbuffers(std::uint32_t count, const std::uint32_t* ids) noexcept +{ + glDeleteRenderbuffers(static_cast(count), reinterpret_cast(ids)); +} + +void bind_opengl_renderbuffer(std::uint32_t target, std::uint32_t renderbuffer) noexcept +{ + glBindRenderbuffer(static_cast(target), static_cast(renderbuffer)); +} + +void set_opengl_renderbuffer_storage( + std::uint32_t target, + std::uint32_t internal_format, + std::int32_t width, + std::int32_t height) noexcept +{ + glRenderbufferStorage( + static_cast(target), + static_cast(internal_format), + static_cast(width), + static_cast(height)); +} + +void attach_opengl_framebuffer_renderbuffer( + std::uint32_t target, + std::uint32_t attachment, + std::uint32_t renderbuffer_target, + std::uint32_t renderbuffer) noexcept +{ + glFramebufferRenderbuffer( + static_cast(target), + static_cast(attachment), + static_cast(renderbuffer_target), + static_cast(renderbuffer)); +} + +GLuint allocate_canvas_depth_renderbuffer(int width, int height) +{ + const auto result = pp::renderer::gl::allocate_opengl_depth_renderbuffer( + width, + height, + pp::renderer::gl::OpenGlDepthRenderbufferAllocationDispatch { + .gen_renderbuffers = gen_opengl_renderbuffers, + .bind_renderbuffer = bind_opengl_renderbuffer, + .renderbuffer_storage = set_opengl_renderbuffer_storage, + }); + if (!result.ok()) { + LOG("OpenGL canvas depth renderbuffer allocation failed: %s", result.status().message); + return 0U; + } + return static_cast(result.value()); +} + +void attach_canvas_depth_renderbuffer(GLuint renderbuffer) +{ + const auto status = pp::renderer::gl::attach_opengl_depth_renderbuffer( + static_cast(renderbuffer), + pp::renderer::gl::OpenGlDepthRenderbufferAttachmentDispatch { + .framebuffer_renderbuffer = attach_opengl_framebuffer_renderbuffer, + }); + if (!status.ok()) + LOG("OpenGL canvas depth renderbuffer attachment failed: %s", status.message); +} + +void delete_canvas_renderbuffer(GLuint renderbuffer) +{ + const auto status = pp::renderer::gl::delete_opengl_renderbuffer( + static_cast(renderbuffer), + pp::renderer::gl::OpenGlRenderbufferDeleteDispatch { + .delete_renderbuffers = delete_opengl_renderbuffers, + }); + if (!status.ok()) + LOG("OpenGL canvas renderbuffer delete failed: %s", status.message); +} + } @@ -2963,29 +3023,25 @@ void Canvas::draw_objects_direct(std::function allocate_opengl_depth_renderbuffer( + std::int32_t width, + std::int32_t height, + OpenGlDepthRenderbufferAllocationDispatch dispatch) noexcept +{ + if (dispatch.gen_renderbuffers == nullptr + || dispatch.bind_renderbuffer == nullptr + || dispatch.renderbuffer_storage == nullptr) { + return pp::foundation::Result::failure( + pp::foundation::Status::invalid_argument( + "OpenGL depth renderbuffer allocation dispatch callbacks must not be null")); + } + + if (width <= 0 || height <= 0) { + return pp::foundation::Result::failure( + pp::foundation::Status::invalid_argument("OpenGL depth renderbuffer dimensions are invalid")); + } + + std::uint32_t renderbuffer_id = 0U; + dispatch.gen_renderbuffers(1U, &renderbuffer_id); + if (renderbuffer_id == 0U) { + return pp::foundation::Result::failure( + pp::foundation::Status::out_of_range("OpenGL renderbuffer allocation returned id 0")); + } + + dispatch.bind_renderbuffer(renderbuffer_target(), renderbuffer_id); + dispatch.renderbuffer_storage(renderbuffer_target(), depth_component24_format(), width, height); + dispatch.bind_renderbuffer(renderbuffer_target(), default_framebuffer_id()); + return pp::foundation::Result::success(renderbuffer_id); +} + +pp::foundation::Status delete_opengl_renderbuffer( + std::uint32_t renderbuffer_id, + OpenGlRenderbufferDeleteDispatch dispatch) noexcept +{ + if (dispatch.delete_renderbuffers == nullptr) { + return pp::foundation::Status::invalid_argument("OpenGL renderbuffer delete callback must not be null"); + } + + if (renderbuffer_id == 0U) { + return pp::foundation::Status::success(); + } + + dispatch.delete_renderbuffers(1U, &renderbuffer_id); + return pp::foundation::Status::success(); +} + +pp::foundation::Status attach_opengl_depth_renderbuffer( + std::uint32_t renderbuffer_id, + OpenGlDepthRenderbufferAttachmentDispatch dispatch) noexcept +{ + if (dispatch.framebuffer_renderbuffer == nullptr) { + return pp::foundation::Status::invalid_argument( + "OpenGL depth renderbuffer attachment callback must not be null"); + } + + dispatch.framebuffer_renderbuffer( + framebuffer_target(), + framebuffer_depth_attachment(), + renderbuffer_target(), + renderbuffer_id); + return pp::foundation::Status::success(); +} + pp::foundation::Result create_opengl_sampler( std::span parameters, OpenGlSamplerCreateDispatch dispatch) noexcept diff --git a/src/renderer_gl/opengl_capabilities.h b/src/renderer_gl/opengl_capabilities.h index 6902551..954be8d 100644 --- a/src/renderer_gl/opengl_capabilities.h +++ b/src/renderer_gl/opengl_capabilities.h @@ -364,8 +364,14 @@ using OpenGlGetActiveUniformFn = void (*)( 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 OpenGlBindRenderbufferFn = void (*)(std::uint32_t target, std::uint32_t renderbuffer) 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 OpenGlRenderbufferStorageFn = void (*)( + std::uint32_t target, + std::uint32_t internal_format, + std::int32_t width, + std::int32_t height) noexcept; using OpenGlSamplerParameteriFn = void (*)( std::uint32_t sampler, std::uint32_t parameter, @@ -427,6 +433,11 @@ using OpenGlFramebufferTexture2DFn = void (*)( std::uint32_t texture_target, std::uint32_t texture, std::int32_t level) noexcept; +using OpenGlFramebufferRenderbufferFn = void (*)( + std::uint32_t target, + std::uint32_t attachment, + std::uint32_t renderbuffer_target, + std::uint32_t renderbuffer) noexcept; using OpenGlCheckFramebufferStatusFn = std::uint32_t (*)(std::uint32_t target) noexcept; using OpenGlReadPixelsFn = void (*)( std::int32_t x, @@ -608,6 +619,20 @@ struct OpenGlFramebufferRestoreDispatch { OpenGlBindFramebufferFn bind_framebuffer = nullptr; }; +struct OpenGlDepthRenderbufferAllocationDispatch { + OpenGlGenObjectsFn gen_renderbuffers = nullptr; + OpenGlBindRenderbufferFn bind_renderbuffer = nullptr; + OpenGlRenderbufferStorageFn renderbuffer_storage = nullptr; +}; + +struct OpenGlRenderbufferDeleteDispatch { + OpenGlDeleteObjectsFn delete_renderbuffers = nullptr; +}; + +struct OpenGlDepthRenderbufferAttachmentDispatch { + OpenGlFramebufferRenderbufferFn framebuffer_renderbuffer = nullptr; +}; + struct OpenGlSamplerCreateDispatch { OpenGlGenObjectsFn gen_samplers = nullptr; OpenGlSamplerParameteriFn sampler_parameter_i = nullptr; @@ -813,6 +838,16 @@ struct OpenGlMeshDeleteDispatch { [[nodiscard]] pp::foundation::Status restore_opengl_framebuffer_binding( OpenGlFramebufferBindingState binding, OpenGlFramebufferRestoreDispatch dispatch) noexcept; +[[nodiscard]] pp::foundation::Result allocate_opengl_depth_renderbuffer( + std::int32_t width, + std::int32_t height, + OpenGlDepthRenderbufferAllocationDispatch dispatch) noexcept; +[[nodiscard]] pp::foundation::Status delete_opengl_renderbuffer( + std::uint32_t renderbuffer_id, + OpenGlRenderbufferDeleteDispatch dispatch) noexcept; +[[nodiscard]] pp::foundation::Status attach_opengl_depth_renderbuffer( + std::uint32_t renderbuffer_id, + OpenGlDepthRenderbufferAttachmentDispatch dispatch) noexcept; [[nodiscard]] pp::foundation::Result create_opengl_sampler( std::span parameters, OpenGlSamplerCreateDispatch dispatch) noexcept; diff --git a/tests/renderer_gl/capabilities_tests.cpp b/tests/renderer_gl/capabilities_tests.cpp index 7379541..f842c5b 100644 --- a/tests/renderer_gl/capabilities_tests.cpp +++ b/tests/renderer_gl/capabilities_tests.cpp @@ -63,6 +63,13 @@ struct RecordedOpenGlFramebufferAttachmentCall { std::int32_t level = 0; }; +struct RecordedOpenGlRenderbufferStorageCall { + std::uint32_t target = 0; + std::uint32_t internal_format = 0; + std::int32_t width = 0; + std::int32_t height = 0; +}; + struct RecordedOpenGlReadPixelsCall { std::int32_t x = 0; std::int32_t y = 0; @@ -184,6 +191,10 @@ std::vector recorded_generated_framebuffer_counts; std::vector recorded_deleted_framebuffers; std::vector recorded_framebuffer_attachment_calls; std::vector recorded_framebuffer_status_queries; +std::vector recorded_generated_renderbuffer_counts; +std::vector recorded_deleted_renderbuffers; +std::vector recorded_bound_renderbuffers; +std::vector recorded_renderbuffer_storage_calls; std::vector recorded_read_pixels_calls; std::vector recorded_blit_framebuffer_calls; std::vector recorded_generated_sampler_counts; @@ -220,6 +231,7 @@ std::vector recorded_vertex_attrib_pointe std::vector recorded_mesh_draw_calls; std::uint32_t next_texture_id = 91U; std::uint32_t next_framebuffer_id = 44U; +std::uint32_t next_renderbuffer_id = 81U; std::uint32_t next_sampler_id = 71U; std::uint32_t next_shader_id = 301U; std::uint32_t next_program_id = 401U; @@ -532,6 +544,41 @@ void record_delete_framebuffers(std::uint32_t count, const std::uint32_t* ids) n } } +void record_gen_renderbuffers(std::uint32_t count, std::uint32_t* ids) noexcept +{ + recorded_generated_renderbuffer_counts.push_back(count); + for (std::uint32_t i = 0U; i < count; ++i) { + ids[i] = next_renderbuffer_id + i; + } +} + +void record_delete_renderbuffers(std::uint32_t count, const std::uint32_t* ids) noexcept +{ + for (std::uint32_t i = 0U; i < count; ++i) { + recorded_deleted_renderbuffers.push_back(ids[i]); + } +} + +void record_bind_renderbuffer(std::uint32_t target, std::uint32_t renderbuffer) noexcept +{ + recorded_bound_renderbuffers.push_back(target); + recorded_bound_renderbuffers.push_back(renderbuffer); +} + +void record_renderbuffer_storage( + std::uint32_t target, + std::uint32_t internal_format, + std::int32_t width, + std::int32_t height) noexcept +{ + recorded_renderbuffer_storage_calls.push_back(RecordedOpenGlRenderbufferStorageCall { + .target = target, + .internal_format = internal_format, + .width = width, + .height = height, + }); +} + void record_framebuffer_texture_2d( std::uint32_t target, std::uint32_t attachment, @@ -548,6 +595,20 @@ void record_framebuffer_texture_2d( }); } +void record_framebuffer_renderbuffer( + std::uint32_t target, + std::uint32_t attachment, + std::uint32_t renderbuffer_target, + std::uint32_t renderbuffer) noexcept +{ + recorded_framebuffer_attachment_calls.push_back(RecordedOpenGlFramebufferAttachmentCall { + .target = target, + .attachment = attachment, + .texture_target = renderbuffer_target, + .texture = renderbuffer, + }); +} + std::uint32_t record_check_framebuffer_status(std::uint32_t target) noexcept { recorded_framebuffer_status_queries.push_back(target); @@ -4060,6 +4121,99 @@ void rejects_incomplete_framebuffer_binding_dispatch(pp::tests::Harness& h) PP_EXPECT(h, restore.code == pp::foundation::StatusCode::invalid_argument); } +void allocates_attaches_and_deletes_depth_renderbuffer(pp::tests::Harness& h) +{ + recorded_generated_renderbuffer_counts.clear(); + recorded_bound_renderbuffers.clear(); + recorded_renderbuffer_storage_calls.clear(); + recorded_framebuffer_attachment_calls.clear(); + recorded_deleted_renderbuffers.clear(); + next_renderbuffer_id = 81U; + + const auto renderbuffer = pp::renderer::gl::allocate_opengl_depth_renderbuffer( + 64, + 32, + pp::renderer::gl::OpenGlDepthRenderbufferAllocationDispatch { + .gen_renderbuffers = record_gen_renderbuffers, + .bind_renderbuffer = record_bind_renderbuffer, + .renderbuffer_storage = record_renderbuffer_storage, + }); + const auto attach = pp::renderer::gl::attach_opengl_depth_renderbuffer( + renderbuffer.ok() ? renderbuffer.value() : 0U, + pp::renderer::gl::OpenGlDepthRenderbufferAttachmentDispatch { + .framebuffer_renderbuffer = record_framebuffer_renderbuffer, + }); + const auto detach = pp::renderer::gl::attach_opengl_depth_renderbuffer( + 0U, + pp::renderer::gl::OpenGlDepthRenderbufferAttachmentDispatch { + .framebuffer_renderbuffer = record_framebuffer_renderbuffer, + }); + const auto deleted = pp::renderer::gl::delete_opengl_renderbuffer( + renderbuffer.ok() ? renderbuffer.value() : 0U, + pp::renderer::gl::OpenGlRenderbufferDeleteDispatch { + .delete_renderbuffers = record_delete_renderbuffers, + }); + + PP_EXPECT(h, renderbuffer.ok()); + PP_EXPECT(h, renderbuffer.value() == 81U); + PP_EXPECT(h, recorded_generated_renderbuffer_counts.size() == 1U); + PP_EXPECT(h, recorded_generated_renderbuffer_counts[0] == 1U); + PP_EXPECT(h, recorded_bound_renderbuffers.size() == 4U); + PP_EXPECT(h, recorded_bound_renderbuffers[0] == 0x8D41U); + PP_EXPECT(h, recorded_bound_renderbuffers[1] == 81U); + PP_EXPECT(h, recorded_bound_renderbuffers[2] == 0x8D41U); + PP_EXPECT(h, recorded_bound_renderbuffers[3] == 0U); + PP_EXPECT(h, recorded_renderbuffer_storage_calls.size() == 1U); + PP_EXPECT(h, recorded_renderbuffer_storage_calls[0].target == 0x8D41U); + PP_EXPECT(h, recorded_renderbuffer_storage_calls[0].internal_format == 0x81A6U); + PP_EXPECT(h, recorded_renderbuffer_storage_calls[0].width == 64); + PP_EXPECT(h, recorded_renderbuffer_storage_calls[0].height == 32); + PP_EXPECT(h, attach.ok()); + PP_EXPECT(h, detach.ok()); + PP_EXPECT(h, recorded_framebuffer_attachment_calls.size() == 2U); + PP_EXPECT(h, recorded_framebuffer_attachment_calls[0].target == 0x8D40U); + PP_EXPECT(h, recorded_framebuffer_attachment_calls[0].attachment == 0x8D00U); + PP_EXPECT(h, recorded_framebuffer_attachment_calls[0].texture_target == 0x8D41U); + PP_EXPECT(h, recorded_framebuffer_attachment_calls[0].texture == 81U); + PP_EXPECT(h, recorded_framebuffer_attachment_calls[1].texture == 0U); + PP_EXPECT(h, deleted.ok()); + PP_EXPECT(h, recorded_deleted_renderbuffers.size() == 1U); + PP_EXPECT(h, recorded_deleted_renderbuffers[0] == 81U); +} + +void rejects_invalid_depth_renderbuffer_dispatch(pp::tests::Harness& h) +{ + const auto missing_allocate_dispatch = pp::renderer::gl::allocate_opengl_depth_renderbuffer( + 64, + 32, + pp::renderer::gl::OpenGlDepthRenderbufferAllocationDispatch { + .gen_renderbuffers = record_gen_renderbuffers, + }); + const auto invalid_dimensions = pp::renderer::gl::allocate_opengl_depth_renderbuffer( + 0, + 32, + pp::renderer::gl::OpenGlDepthRenderbufferAllocationDispatch { + .gen_renderbuffers = record_gen_renderbuffers, + .bind_renderbuffer = record_bind_renderbuffer, + .renderbuffer_storage = record_renderbuffer_storage, + }); + const auto missing_attach_dispatch = pp::renderer::gl::attach_opengl_depth_renderbuffer( + 81U, + pp::renderer::gl::OpenGlDepthRenderbufferAttachmentDispatch {}); + const auto missing_delete_dispatch = pp::renderer::gl::delete_opengl_renderbuffer( + 81U, + pp::renderer::gl::OpenGlRenderbufferDeleteDispatch {}); + + PP_EXPECT(h, !missing_allocate_dispatch.ok()); + PP_EXPECT(h, missing_allocate_dispatch.status().code == pp::foundation::StatusCode::invalid_argument); + PP_EXPECT(h, !invalid_dimensions.ok()); + PP_EXPECT(h, invalid_dimensions.status().code == pp::foundation::StatusCode::invalid_argument); + PP_EXPECT(h, !missing_attach_dispatch.ok()); + PP_EXPECT(h, missing_attach_dispatch.code == pp::foundation::StatusCode::invalid_argument); + PP_EXPECT(h, !missing_delete_dispatch.ok()); + PP_EXPECT(h, missing_delete_dispatch.code == pp::foundation::StatusCode::invalid_argument); +} + void maps_renderer_viewports_and_scissors(pp::tests::Harness& h) { const auto viewport = pp::renderer::gl::viewport_for_renderer_viewport( @@ -4513,6 +4667,8 @@ int main() harness.run("binds_framebuffer_draw_read_through_dispatch", binds_framebuffer_draw_read_through_dispatch); harness.run("restores_framebuffer_draw_read_through_dispatch", restores_framebuffer_draw_read_through_dispatch); harness.run("rejects_incomplete_framebuffer_binding_dispatch", rejects_incomplete_framebuffer_binding_dispatch); + harness.run("allocates_attaches_and_deletes_depth_renderbuffer", allocates_attaches_and_deletes_depth_renderbuffer); + harness.run("rejects_invalid_depth_renderbuffer_dispatch", rejects_invalid_depth_renderbuffer_dispatch); harness.run("maps_renderer_viewports_and_scissors", maps_renderer_viewports_and_scissors); harness.run("maps_renderer_blend_state_tokens", maps_renderer_blend_state_tokens); harness.run("maps_renderer_color_write_masks", maps_renderer_color_write_masks);