diff --git a/docs/modernization/build-inventory.md b/docs/modernization/build-inventory.md index b9ff23e..1118c55 100644 --- a/docs/modernization/build-inventory.md +++ b/docs/modernization/build-inventory.md @@ -468,9 +468,10 @@ Known local toolchain state: setup, tested saved-state snapshot/restore dispatch consumed by the retained `gl_state` utility, tested texture lifecycle/readback dispatch consumed by the retained `Texture2D` utility, tested framebuffer blit/readback dispatch - consumed by retained `RTT` resize/copy/readback paths, plus renderer API to - OpenGL token mapping and command-planning contracts used by the OpenGL parity - work. + consumed by retained `RTT` resize/copy/readback paths, tested framebuffer + bind/restore dispatch consumed by retained `RTT` render-target pass entry + and exit paths, plus renderer API to OpenGL token mapping and command-planning + contracts used by the OpenGL parity work. - `pano_cli plan-cloud-upload` exposes `pp_app_core` cloud upload availability, new-document warning, publish prompt, and save-before-upload planning as JSON; the live cloud upload command consumes the same start contract before diff --git a/docs/modernization/roadmap.md b/docs/modernization/roadmap.md index 7883f36..13066f1 100644 --- a/docs/modernization/roadmap.md +++ b/docs/modernization/roadmap.md @@ -547,6 +547,9 @@ resource lifecycle path behind the renderer backend boundary. Legacy `RTT` resize/copy blits and byte/float framebuffer readbacks now execute through tested `pp_renderer_gl` framebuffer dispatch contracts with draw/read framebuffer binding restore handled by the backend helper. +Legacy `RTT::bindFramebuffer` and `RTT::unbindFramebuffer` now use tested +`pp_renderer_gl` draw/read framebuffer binding snapshot and restore contracts, +moving render-target pass entry/exit state management behind the backend. Windows RenderDoc frame capture hooks now also dispatch through `PlatformServices`, keeping capture integration in the platform service while leaving non-Windows adapters as no-ops. diff --git a/src/renderer_gl/opengl_capabilities.cpp b/src/renderer_gl/opengl_capabilities.cpp index dd11878..186d47b 100644 --- a/src/renderer_gl/opengl_capabilities.cpp +++ b/src/renderer_gl/opengl_capabilities.cpp @@ -684,6 +684,36 @@ pp::foundation::Status readback_opengl_framebuffer( return pp::foundation::Status::success(); } +pp::foundation::Result bind_opengl_framebuffer_for_draw_read( + std::uint32_t framebuffer, + OpenGlFramebufferBindDispatch dispatch) noexcept +{ + if (dispatch.get_integer == nullptr || dispatch.bind_framebuffer == nullptr) { + return pp::foundation::Result::failure( + pp::foundation::Status::invalid_argument("OpenGL framebuffer bind dispatch callbacks must not be null")); + } + + OpenGlFramebufferBindingState binding {}; + dispatch.get_integer(draw_framebuffer_binding_query(), &binding.draw_framebuffer); + dispatch.get_integer(read_framebuffer_binding_query(), &binding.read_framebuffer); + dispatch.bind_framebuffer(draw_framebuffer_target(), framebuffer); + dispatch.bind_framebuffer(read_framebuffer_target(), framebuffer); + return pp::foundation::Result::success(binding); +} + +pp::foundation::Status restore_opengl_framebuffer_binding( + OpenGlFramebufferBindingState binding, + OpenGlFramebufferRestoreDispatch dispatch) noexcept +{ + if (dispatch.bind_framebuffer == nullptr) { + return pp::foundation::Status::invalid_argument("OpenGL framebuffer restore dispatch callback must not be null"); + } + + dispatch.bind_framebuffer(draw_framebuffer_target(), static_cast(binding.draw_framebuffer)); + dispatch.bind_framebuffer(read_framebuffer_target(), static_cast(binding.read_framebuffer)); + 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 425d912..bd68010 100644 --- a/src/renderer_gl/opengl_capabilities.h +++ b/src/renderer_gl/opengl_capabilities.h @@ -167,6 +167,11 @@ struct OpenGlFramebufferReadback { void* pixels = nullptr; }; +struct OpenGlFramebufferBindingState { + std::int32_t draw_framebuffer = 0; + std::int32_t read_framebuffer = 0; +}; + struct OpenGlWindowsWglContextConfig { std::array context_attributes {}; std::array pixel_format_attributes {}; @@ -381,6 +386,15 @@ struct OpenGlFramebufferReadbackDispatch { OpenGlReadPixelsFn read_pixels = nullptr; }; +struct OpenGlFramebufferBindDispatch { + OpenGlGetIntegerFn get_integer = nullptr; + OpenGlBindFramebufferFn bind_framebuffer = nullptr; +}; + +struct OpenGlFramebufferRestoreDispatch { + OpenGlBindFramebufferFn bind_framebuffer = nullptr; +}; + [[nodiscard]] OpenGlCapabilities detect_opengl_capabilities( std::span extensions, OpenGlRuntime runtime) noexcept; @@ -437,6 +451,12 @@ struct OpenGlFramebufferReadbackDispatch { [[nodiscard]] pp::foundation::Status readback_opengl_framebuffer( OpenGlFramebufferReadback readback, OpenGlFramebufferReadbackDispatch dispatch) noexcept; +[[nodiscard]] pp::foundation::Result bind_opengl_framebuffer_for_draw_read( + std::uint32_t framebuffer, + OpenGlFramebufferBindDispatch dispatch) noexcept; +[[nodiscard]] pp::foundation::Status restore_opengl_framebuffer_binding( + OpenGlFramebufferBindingState binding, + OpenGlFramebufferRestoreDispatch dispatch) noexcept; [[nodiscard]] std::uint32_t extension_count_query() noexcept; [[nodiscard]] std::uint32_t extension_string_name() noexcept; diff --git a/src/rtt.cpp b/src/rtt.cpp index b0ba592..5e0e9b1 100644 --- a/src/rtt.cpp +++ b/src/rtt.cpp @@ -24,26 +24,6 @@ namespace { return static_cast(pp::renderer::gl::framebuffer_target()); } -[[nodiscard]] GLenum draw_framebuffer_target() noexcept -{ - return static_cast(pp::renderer::gl::draw_framebuffer_target()); -} - -[[nodiscard]] GLenum read_framebuffer_target() noexcept -{ - return static_cast(pp::renderer::gl::read_framebuffer_target()); -} - -[[nodiscard]] GLenum draw_framebuffer_binding_query() noexcept -{ - return static_cast(pp::renderer::gl::draw_framebuffer_binding_query()); -} - -[[nodiscard]] GLenum read_framebuffer_binding_query() noexcept -{ - return static_cast(pp::renderer::gl::read_framebuffer_binding_query()); -} - void query_opengl_integer(std::uint32_t name, std::int32_t* value) noexcept { glGetIntegerv(static_cast(name), reinterpret_cast(value)); @@ -437,10 +417,19 @@ void RTT::bindFramebuffer() #endif } #endif // _DEBUG - glGetIntegerv(draw_framebuffer_binding_query(), &oldDFboID); - glGetIntegerv(read_framebuffer_binding_query(), &oldRFboID); - glBindFramebuffer(draw_framebuffer_target(), fboID); - glBindFramebuffer(read_framebuffer_target(), fboID); + const auto binding = pp::renderer::gl::bind_opengl_framebuffer_for_draw_read( + static_cast(fboID), + pp::renderer::gl::OpenGlFramebufferBindDispatch { + .get_integer = query_opengl_integer, + .bind_framebuffer = bind_opengl_framebuffer, + }); + if (!binding.ok()) { + LOG("RTT::bindFramebuffer() failed because: %s", binding.status().message); + return; + } + + oldDFboID = static_cast(binding.value().draw_framebuffer); + oldRFboID = static_cast(binding.value().read_framebuffer); bound = true; } @@ -449,8 +438,19 @@ void RTT::unbindFramebuffer() assert(App::I->is_render_thread()); if (!bound) return; - glBindFramebuffer(draw_framebuffer_target(), oldDFboID); - glBindFramebuffer(read_framebuffer_target(), oldRFboID); + const auto status = pp::renderer::gl::restore_opengl_framebuffer_binding( + pp::renderer::gl::OpenGlFramebufferBindingState { + .draw_framebuffer = oldDFboID, + .read_framebuffer = oldRFboID, + }, + pp::renderer::gl::OpenGlFramebufferRestoreDispatch { + .bind_framebuffer = bind_opengl_framebuffer, + }); + if (!status.ok()) { + LOG("RTT::unbindFramebuffer() failed because: %s", status.message); + return; + } + oldRFboID = 0; oldDFboID = 0; bound = false; diff --git a/tests/renderer_gl/capabilities_tests.cpp b/tests/renderer_gl/capabilities_tests.cpp index 2e5fbcd..58fe653 100644 --- a/tests/renderer_gl/capabilities_tests.cpp +++ b/tests/renderer_gl/capabilities_tests.cpp @@ -1939,6 +1939,69 @@ void rejects_invalid_framebuffer_readbacks(pp::tests::Harness& h) PP_EXPECT(h, missing_pixels.code == pp::foundation::StatusCode::invalid_argument); } +void binds_framebuffer_draw_read_through_dispatch(pp::tests::Harness& h) +{ + recorded_integer_queries.clear(); + recorded_binding_calls.clear(); + + const auto binding = pp::renderer::gl::bind_opengl_framebuffer_for_draw_read( + 29U, + pp::renderer::gl::OpenGlFramebufferBindDispatch { + .get_integer = record_get_integer, + .bind_framebuffer = record_bind_framebuffer, + }); + + PP_EXPECT(h, binding.ok()); + PP_EXPECT(h, binding.value().draw_framebuffer == 7); + PP_EXPECT(h, binding.value().read_framebuffer == 9); + PP_EXPECT(h, recorded_integer_queries.size() == 2U); + PP_EXPECT(h, recorded_integer_queries[0] == 0x8CA6U); + PP_EXPECT(h, recorded_integer_queries[1] == 0x8CAAU); + PP_EXPECT(h, recorded_binding_calls.size() == 2U); + PP_EXPECT(h, recorded_binding_calls[0].first == 0x8CA9U); + PP_EXPECT(h, recorded_binding_calls[0].second == 29U); + PP_EXPECT(h, recorded_binding_calls[1].first == 0x8CA8U); + PP_EXPECT(h, recorded_binding_calls[1].second == 29U); +} + +void restores_framebuffer_draw_read_through_dispatch(pp::tests::Harness& h) +{ + recorded_binding_calls.clear(); + + const auto status = pp::renderer::gl::restore_opengl_framebuffer_binding( + pp::renderer::gl::OpenGlFramebufferBindingState { + .draw_framebuffer = 31, + .read_framebuffer = 37, + }, + pp::renderer::gl::OpenGlFramebufferRestoreDispatch { + .bind_framebuffer = record_bind_framebuffer, + }); + + PP_EXPECT(h, status.ok()); + PP_EXPECT(h, recorded_binding_calls.size() == 2U); + PP_EXPECT(h, recorded_binding_calls[0].first == 0x8CA9U); + PP_EXPECT(h, recorded_binding_calls[0].second == 31U); + PP_EXPECT(h, recorded_binding_calls[1].first == 0x8CA8U); + PP_EXPECT(h, recorded_binding_calls[1].second == 37U); +} + +void rejects_incomplete_framebuffer_binding_dispatch(pp::tests::Harness& h) +{ + const auto binding = pp::renderer::gl::bind_opengl_framebuffer_for_draw_read( + 29U, + pp::renderer::gl::OpenGlFramebufferBindDispatch { + .get_integer = record_get_integer, + }); + const auto restore = pp::renderer::gl::restore_opengl_framebuffer_binding( + pp::renderer::gl::OpenGlFramebufferBindingState {}, + pp::renderer::gl::OpenGlFramebufferRestoreDispatch {}); + + PP_EXPECT(h, !binding.ok()); + PP_EXPECT(h, binding.status().code == pp::foundation::StatusCode::invalid_argument); + PP_EXPECT(h, !restore.ok()); + PP_EXPECT(h, restore.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( @@ -2345,6 +2408,9 @@ int main() harness.run("rejects_invalid_framebuffer_blits", rejects_invalid_framebuffer_blits); harness.run("reads_back_framebuffer_through_dispatch", reads_back_framebuffer_through_dispatch); harness.run("rejects_invalid_framebuffer_readbacks", rejects_invalid_framebuffer_readbacks); + 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("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);