From 9971b2b7f2293b5f8fc78b8676f7bbd9db76791d Mon Sep 17 00:00:00 2001 From: omigamedev Date: Wed, 3 Jun 2026 06:15:51 +0200 Subject: [PATCH] Route GL state snapshot through renderer GL --- docs/modernization/build-inventory.md | 5 +- docs/modernization/roadmap.md | 5 + src/renderer_gl/opengl_capabilities.cpp | 67 ++++++ src/renderer_gl/opengl_capabilities.h | 56 ++++- src/util.cpp | 156 +++++++++++--- tests/renderer_gl/capabilities_tests.cpp | 264 +++++++++++++++++++++++ 6 files changed, 516 insertions(+), 37 deletions(-) diff --git a/docs/modernization/build-inventory.md b/docs/modernization/build-inventory.md index 05b8327..850bdea 100644 --- a/docs/modernization/build-inventory.md +++ b/docs/modernization/build-inventory.md @@ -465,8 +465,9 @@ Known local toolchain state: color/buffer dispatch consumed by `App::clear`, tested app UI viewport/scissor dispatch consumed by `App::draw` and `App::vr_draw_ui`, tested generic capability/buffer-clear dispatch consumed by VR draw state - setup, plus renderer API to OpenGL token mapping and command-planning - contracts used by the OpenGL parity work. + setup, tested saved-state snapshot/restore dispatch consumed by the retained + `gl_state` utility, 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 c2f01ff..c93abad 100644 --- a/docs/modernization/roadmap.md +++ b/docs/modernization/roadmap.md @@ -535,6 +535,11 @@ VR draw blend/depth state transitions and depth-buffer clears now use generic tested `pp_renderer_gl` capability and clear dispatch contracts, reducing direct OpenGL execution in the retained VR app path without changing state restore behavior. +The retained `gl_state` save/restore utility now snapshots and restores through +tested `pp_renderer_gl` saved-state dispatch contracts, covering capability +state, viewport, clear color, framebuffer/program bindings, active texture, +2D texture slots, samplers, and cube-map binding without changing the legacy +utility's public fields. 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 2478964..dcbe184 100644 --- a/src/renderer_gl/opengl_capabilities.cpp +++ b/src/renderer_gl/opengl_capabilities.cpp @@ -245,6 +245,73 @@ pp::foundation::Status apply_panopainter_initial_state(OpenGlStateDispatch dispa return pp::foundation::Status::success(); } +pp::foundation::Result snapshot_opengl_state(OpenGlStateSnapshotDispatch dispatch) noexcept +{ + if (dispatch.is_enabled == nullptr + || dispatch.get_integer == nullptr + || dispatch.get_float == nullptr + || dispatch.active_texture == nullptr) { + return pp::foundation::Result::failure( + pp::foundation::Status::invalid_argument("OpenGL state snapshot dispatch callbacks must not be null")); + } + + OpenGlSavedState state {}; + state.blend_enabled = dispatch.is_enabled(blend_state()); + state.depth_test_enabled = dispatch.is_enabled(depth_test_state()); + state.scissor_test_enabled = dispatch.is_enabled(scissor_test_state()); + dispatch.get_integer(viewport_query(), state.viewport.data()); + dispatch.get_float(color_clear_value_query(), state.clear_color.data()); + dispatch.get_integer(current_program_query(), &state.program); + dispatch.get_integer(draw_framebuffer_binding_query(), &state.draw_framebuffer); + dispatch.get_integer(read_framebuffer_binding_query(), &state.read_framebuffer); + dispatch.get_integer(active_texture_query(), &state.active_texture); + dispatch.get_integer(texture_binding_cube_map_query(), &state.cube_map_binding); + + for (std::size_t i = 0; i < state.texture_2d_bindings.size(); ++i) { + dispatch.active_texture(active_texture_unit(static_cast(i))); + dispatch.get_integer(texture_binding_2d_query(), &state.texture_2d_bindings[i]); + dispatch.get_integer(sampler_binding_query(), &state.sampler_bindings[i]); + } + + return pp::foundation::Result::success(state); +} + +pp::foundation::Status restore_opengl_state( + const OpenGlSavedState& state, + OpenGlStateRestoreDispatch dispatch) noexcept +{ + if (dispatch.enable == nullptr + || dispatch.disable == nullptr + || dispatch.viewport == nullptr + || dispatch.clear_color == nullptr + || dispatch.bind_framebuffer == nullptr + || dispatch.use_program == nullptr + || dispatch.active_texture == nullptr + || dispatch.bind_texture == nullptr + || dispatch.bind_sampler == nullptr) { + return pp::foundation::Status::invalid_argument("OpenGL state restore dispatch callbacks must not be null"); + } + + (state.blend_enabled != 0U ? dispatch.enable : dispatch.disable)(blend_state()); + (state.depth_test_enabled != 0U ? dispatch.enable : dispatch.disable)(depth_test_state()); + (state.scissor_test_enabled != 0U ? dispatch.enable : dispatch.disable)(scissor_test_state()); + dispatch.viewport(state.viewport[0], state.viewport[1], state.viewport[2], state.viewport[3]); + dispatch.clear_color(state.clear_color[0], state.clear_color[1], state.clear_color[2], state.clear_color[3]); + dispatch.bind_framebuffer(draw_framebuffer_target(), static_cast(state.draw_framebuffer)); + dispatch.bind_framebuffer(read_framebuffer_target(), static_cast(state.read_framebuffer)); + dispatch.use_program(static_cast(state.program)); + + for (std::size_t i = 0; i < state.texture_2d_bindings.size(); ++i) { + dispatch.active_texture(active_texture_unit(static_cast(i))); + dispatch.bind_texture(texture_2d_target(), static_cast(state.texture_2d_bindings[i])); + dispatch.bind_sampler(static_cast(i), static_cast(state.sampler_bindings[i])); + } + + dispatch.active_texture(static_cast(state.active_texture)); + dispatch.bind_texture(texture_cube_map_target(), static_cast(state.cube_map_binding)); + return pp::foundation::Status::success(); +} + pp::foundation::Result query_opengl_runtime_info( OpenGlRuntimeInfoDispatch dispatch) noexcept { diff --git a/src/renderer_gl/opengl_capabilities.h b/src/renderer_gl/opengl_capabilities.h index 20b1f98..e3f318b 100644 --- a/src/renderer_gl/opengl_capabilities.h +++ b/src/renderer_gl/opengl_capabilities.h @@ -124,9 +124,36 @@ struct OpenGlInitialState { std::uint32_t alpha_equation = 0; }; +struct OpenGlSavedState { + std::uint8_t blend_enabled = 0; + std::uint8_t depth_test_enabled = 0; + std::uint8_t scissor_test_enabled = 0; + std::array viewport {}; + std::array clear_color {}; + std::array texture_2d_bindings {}; + std::array sampler_bindings {}; + std::int32_t cube_map_binding = 0; + std::int32_t program = 0; + std::int32_t draw_framebuffer = 0; + std::int32_t read_framebuffer = 0; + std::int32_t active_texture = 0; +}; + using OpenGlCapabilityFn = void (*)(std::uint32_t state) noexcept; +using OpenGlIsEnabledFn = std::uint8_t (*)(std::uint32_t state) noexcept; +using OpenGlGetIntegerFn = void (*)(std::uint32_t name, std::int32_t* value) noexcept; +using OpenGlGetFloatFn = void (*)(std::uint32_t name, float* value) noexcept; +using OpenGlActiveTextureFn = void (*)(std::uint32_t texture_unit) noexcept; +using OpenGlClearColorFn = void (*)(float r, float g, float b, float a) noexcept; +using OpenGlClearFn = void (*)(std::uint32_t mask) noexcept; +using OpenGlViewportFn = void (*)(std::int32_t x, std::int32_t y, std::int32_t width, std::int32_t height) noexcept; +using OpenGlScissorFn = void (*)(std::int32_t x, std::int32_t y, std::int32_t width, std::int32_t height) noexcept; 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 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; struct OpenGlStateDispatch { OpenGlCapabilityFn enable = nullptr; @@ -135,6 +162,25 @@ struct OpenGlStateDispatch { OpenGlBlendEquationSeparateFn blend_equation_separate = nullptr; }; +struct OpenGlStateSnapshotDispatch { + OpenGlIsEnabledFn is_enabled = nullptr; + OpenGlGetIntegerFn get_integer = nullptr; + OpenGlGetFloatFn get_float = nullptr; + OpenGlActiveTextureFn active_texture = nullptr; +}; + +struct OpenGlStateRestoreDispatch { + OpenGlCapabilityFn enable = nullptr; + OpenGlCapabilityFn disable = nullptr; + OpenGlViewportFn viewport = nullptr; + OpenGlClearColorFn clear_color = nullptr; + OpenGlBindFramebufferFn bind_framebuffer = nullptr; + OpenGlUseProgramFn use_program = nullptr; + OpenGlActiveTextureFn active_texture = nullptr; + OpenGlBindTextureFn bind_texture = nullptr; + OpenGlBindSamplerFn bind_sampler = nullptr; +}; + struct OpenGlRuntimeInfo { const char* version = ""; const char* vendor = ""; @@ -153,11 +199,6 @@ struct OpenGlDefaultClear { std::uint32_t mask = 0; }; -using OpenGlClearColorFn = void (*)(float r, float g, float b, float a) noexcept; -using OpenGlClearFn = void (*)(std::uint32_t mask) noexcept; -using OpenGlViewportFn = void (*)(std::int32_t x, std::int32_t y, std::int32_t width, std::int32_t height) noexcept; -using OpenGlScissorFn = void (*)(std::int32_t x, std::int32_t y, std::int32_t width, std::int32_t height) noexcept; - struct OpenGlClearDispatch { OpenGlClearColorFn clear_color = nullptr; OpenGlClearFn clear = nullptr; @@ -194,6 +235,11 @@ struct OpenGlBufferClearDispatch { OpenGlCapabilities capabilities) noexcept; [[nodiscard]] OpenGlInitialState panopainter_initial_state() noexcept; [[nodiscard]] pp::foundation::Status apply_panopainter_initial_state(OpenGlStateDispatch dispatch) noexcept; +[[nodiscard]] pp::foundation::Result snapshot_opengl_state( + OpenGlStateSnapshotDispatch dispatch) noexcept; +[[nodiscard]] pp::foundation::Status restore_opengl_state( + const OpenGlSavedState& state, + OpenGlStateRestoreDispatch dispatch) noexcept; [[nodiscard]] pp::foundation::Result query_opengl_runtime_info( OpenGlRuntimeInfoDispatch dispatch) noexcept; [[nodiscard]] OpenGlDefaultClear panopainter_default_clear() noexcept; diff --git a/src/util.cpp b/src/util.cpp index 1288f93..f7f99d7 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -5,6 +5,70 @@ #include "app.h" #include "renderer_gl/opengl_capabilities.h" +namespace { + +std::uint8_t is_opengl_enabled(std::uint32_t state) noexcept +{ + return static_cast(glIsEnabled(static_cast(state))); +} + +void query_opengl_integer(std::uint32_t name, std::int32_t* value) noexcept +{ + glGetIntegerv(static_cast(name), reinterpret_cast(value)); +} + +void query_opengl_float(std::uint32_t name, float* value) noexcept +{ + glGetFloatv(static_cast(name), value); +} + +void set_opengl_active_texture(std::uint32_t texture_unit) noexcept +{ + glActiveTexture(static_cast(texture_unit)); +} + +void enable_opengl_state(std::uint32_t state) noexcept +{ + glEnable(static_cast(state)); +} + +void disable_opengl_state(std::uint32_t state) noexcept +{ + glDisable(static_cast(state)); +} + +void set_opengl_viewport(std::int32_t x, std::int32_t y, std::int32_t width, std::int32_t height) noexcept +{ + glViewport(static_cast(x), static_cast(y), static_cast(width), static_cast(height)); +} + +void set_opengl_clear_color(float r, float g, float b, float a) noexcept +{ + glClearColor(r, g, b, a); +} + +void bind_opengl_framebuffer(std::uint32_t target, std::uint32_t framebuffer) noexcept +{ + glBindFramebuffer(static_cast(target), static_cast(framebuffer)); +} + +void use_opengl_program(std::uint32_t program) noexcept +{ + glUseProgram(static_cast(program)); +} + +void bind_opengl_texture(std::uint32_t target, std::uint32_t texture) noexcept +{ + glBindTexture(static_cast(target), static_cast(texture)); +} + +void bind_opengl_sampler(std::uint32_t unit, std::uint32_t sampler) noexcept +{ + glBindSampler(static_cast(unit), static_cast(sampler)); +} + +} + template<> std::vector poly_remove_duplicate(const std::vector& v, const float tollerance) { @@ -727,41 +791,73 @@ void parallel_for(size_t nb_elements, std::function functor, boo void gl_state::save() { assert(App::I->is_render_thread()); - blend = glIsEnabled(pp::renderer::gl::blend_state()); - depth_test = glIsEnabled(pp::renderer::gl::depth_test_state()); - scissor_test = glIsEnabled(pp::renderer::gl::scissor_test_state()); - glGetIntegerv(pp::renderer::gl::viewport_query(), vp); - glGetFloatv(pp::renderer::gl::color_clear_value_query(), cc); - glGetIntegerv(pp::renderer::gl::current_program_query(), &program); - glGetIntegerv(pp::renderer::gl::draw_framebuffer_binding_query(), &fbd); - glGetIntegerv(pp::renderer::gl::read_framebuffer_binding_query(), &fbr); - glGetIntegerv(pp::renderer::gl::active_texture_query(), &active_tex); - glGetIntegerv(pp::renderer::gl::texture_binding_cube_map_query(), &cube); - for (int i = 0; i < 10; ++i) - { - glActiveTexture(pp::renderer::gl::active_texture_unit(static_cast(i))); - glGetIntegerv(pp::renderer::gl::texture_binding_2d_query(), tex + i); - glGetIntegerv(pp::renderer::gl::sampler_binding_query(), sampler + i); + + const auto snapshot = pp::renderer::gl::snapshot_opengl_state( + pp::renderer::gl::OpenGlStateSnapshotDispatch { + .is_enabled = is_opengl_enabled, + .get_integer = query_opengl_integer, + .get_float = query_opengl_float, + .active_texture = set_opengl_active_texture, + }); + if (!snapshot.ok()) { + LOG("OpenGL state snapshot failed: %s", snapshot.status().message); + return; } + + const auto& state = snapshot.value(); + blend = static_cast(state.blend_enabled); + depth_test = static_cast(state.depth_test_enabled); + scissor_test = static_cast(state.scissor_test_enabled); + for (std::size_t i = 0; i < state.viewport.size(); ++i) { + vp[i] = static_cast(state.viewport[i]); + cc[i] = state.clear_color[i]; + } + for (std::size_t i = 0; i < state.texture_2d_bindings.size(); ++i) { + tex[i] = static_cast(state.texture_2d_bindings[i]); + sampler[i] = static_cast(state.sampler_bindings[i]); + } + cube = static_cast(state.cube_map_binding); + program = static_cast(state.program); + fbd = static_cast(state.draw_framebuffer); + fbr = static_cast(state.read_framebuffer); + active_tex = static_cast(state.active_texture); } void gl_state::restore() { assert(App::I->is_render_thread()); - blend ? glEnable(pp::renderer::gl::blend_state()) : glDisable(pp::renderer::gl::blend_state()); - depth_test ? glEnable(pp::renderer::gl::depth_test_state()) : glDisable(pp::renderer::gl::depth_test_state()); - scissor_test ? glEnable(pp::renderer::gl::scissor_test_state()) : glDisable(pp::renderer::gl::scissor_test_state()); - glViewport(vp[0], vp[1], vp[2], vp[3]); - glClearColor(cc[0], cc[1], cc[2], cc[3]); - glBindFramebuffer(pp::renderer::gl::draw_framebuffer_target(), fbd); - glBindFramebuffer(pp::renderer::gl::read_framebuffer_target(), fbr); - glUseProgram(program); - for (int i = 0; i < 10; ++i) - { - glActiveTexture(pp::renderer::gl::active_texture_unit(static_cast(i))); - glBindTexture(pp::renderer::gl::texture_2d_target(), tex[i]); - glBindSampler(i, sampler[i]); + + pp::renderer::gl::OpenGlSavedState state {}; + state.blend_enabled = blend; + state.depth_test_enabled = depth_test; + state.scissor_test_enabled = scissor_test; + for (std::size_t i = 0; i < state.viewport.size(); ++i) { + state.viewport[i] = static_cast(vp[i]); + state.clear_color[i] = cc[i]; } - glActiveTexture(active_tex); - glBindTexture(pp::renderer::gl::texture_cube_map_target(), cube); + for (std::size_t i = 0; i < state.texture_2d_bindings.size(); ++i) { + state.texture_2d_bindings[i] = static_cast(tex[i]); + state.sampler_bindings[i] = static_cast(sampler[i]); + } + state.cube_map_binding = static_cast(cube); + state.program = static_cast(program); + state.draw_framebuffer = static_cast(fbd); + state.read_framebuffer = static_cast(fbr); + state.active_texture = static_cast(active_tex); + + const auto status = pp::renderer::gl::restore_opengl_state( + state, + pp::renderer::gl::OpenGlStateRestoreDispatch { + .enable = enable_opengl_state, + .disable = disable_opengl_state, + .viewport = set_opengl_viewport, + .clear_color = set_opengl_clear_color, + .bind_framebuffer = bind_opengl_framebuffer, + .use_program = use_opengl_program, + .active_texture = set_opengl_active_texture, + .bind_texture = bind_opengl_texture, + .bind_sampler = bind_opengl_sampler, + }); + if (!status.ok()) + LOG("OpenGL state restore failed: %s", status.message); } diff --git a/tests/renderer_gl/capabilities_tests.cpp b/tests/renderer_gl/capabilities_tests.cpp index b2ba7a6..f97f4da 100644 --- a/tests/renderer_gl/capabilities_tests.cpp +++ b/tests/renderer_gl/capabilities_tests.cpp @@ -24,11 +24,29 @@ struct RecordedOpenGlStateCall { std::uint32_t second = 0; }; +struct RecordedOpenGlBindingCall { + enum class Kind { + bind_framebuffer, + use_program, + active_texture, + bind_texture, + bind_sampler, + }; + + Kind kind = Kind::bind_framebuffer; + std::uint32_t first = 0; + std::uint32_t second = 0; +}; + std::vector recorded_state_calls; std::vector recorded_string_queries; std::vector recorded_clear_calls; std::vector recorded_viewport_calls; std::vector recorded_scissor_calls; +std::vector recorded_integer_queries; +std::vector recorded_float_queries; +std::vector recorded_active_texture_calls; +std::vector recorded_binding_calls; void record_enable(std::uint32_t state) noexcept { @@ -116,6 +134,108 @@ void record_scissor(std::int32_t x, std::int32_t y, std::int32_t width, std::int }); } +std::uint8_t record_is_enabled(std::uint32_t state) noexcept +{ + if (state == 0x0BE2U) { + return 1U; + } + if (state == 0x0B71U) { + return 0U; + } + if (state == 0x0C11U) { + return 1U; + } + return 0U; +} + +void record_get_integer(std::uint32_t name, std::int32_t* value) noexcept +{ + recorded_integer_queries.push_back(name); + switch (name) { + case 0x0BA2U: + value[0] = 2; + value[1] = 4; + value[2] = 640; + value[3] = 320; + break; + case 0x8B8DU: + *value = 42; + break; + case 0x8CA6U: + *value = 7; + break; + case 0x8CAAU: + *value = 9; + break; + case 0x84E0U: + *value = 0x84C4; + break; + case 0x8514U: + *value = 88; + break; + case 0x8069U: + *value = 100 + static_cast(recorded_active_texture_calls.size()); + break; + case 0x8919U: + *value = 200 + static_cast(recorded_active_texture_calls.size()); + break; + default: + *value = -1; + break; + } +} + +void record_get_float(std::uint32_t name, float* value) noexcept +{ + recorded_float_queries.push_back(name); + if (name == 0x0C22U) { + value[0] = 0.25F; + value[1] = 0.5F; + value[2] = 0.75F; + value[3] = 1.0F; + } +} + +void record_active_texture(std::uint32_t texture_unit) noexcept +{ + recorded_active_texture_calls.push_back(texture_unit); +} + +void record_bind_framebuffer(std::uint32_t target, std::uint32_t framebuffer) noexcept +{ + recorded_binding_calls.push_back(RecordedOpenGlBindingCall { + .kind = RecordedOpenGlBindingCall::Kind::bind_framebuffer, + .first = target, + .second = framebuffer, + }); +} + +void record_use_program(std::uint32_t program) noexcept +{ + recorded_binding_calls.push_back(RecordedOpenGlBindingCall { + .kind = RecordedOpenGlBindingCall::Kind::use_program, + .first = program, + }); +} + +void record_bind_texture(std::uint32_t target, std::uint32_t texture) noexcept +{ + recorded_binding_calls.push_back(RecordedOpenGlBindingCall { + .kind = RecordedOpenGlBindingCall::Kind::bind_texture, + .first = target, + .second = texture, + }); +} + +void record_bind_sampler(std::uint32_t unit, std::uint32_t sampler) noexcept +{ + recorded_binding_calls.push_back(RecordedOpenGlBindingCall { + .kind = RecordedOpenGlBindingCall::Kind::bind_sampler, + .first = unit, + .second = sampler, + }); +} + void detects_common_extension_capabilities(pp::tests::Harness& h) { constexpr std::array extensions { @@ -794,6 +914,146 @@ void rejects_incomplete_app_initialization_state_dispatch(pp::tests::Harness& h) PP_EXPECT(h, status.code == pp::foundation::StatusCode::invalid_argument); } +void snapshots_legacy_gl_state(pp::tests::Harness& h) +{ + recorded_integer_queries.clear(); + recorded_float_queries.clear(); + recorded_active_texture_calls.clear(); + + const auto result = pp::renderer::gl::snapshot_opengl_state( + pp::renderer::gl::OpenGlStateSnapshotDispatch { + .is_enabled = record_is_enabled, + .get_integer = record_get_integer, + .get_float = record_get_float, + .active_texture = record_active_texture, + }); + + PP_EXPECT(h, result.ok()); + const auto& state = result.value(); + PP_EXPECT(h, state.blend_enabled == 1U); + PP_EXPECT(h, state.depth_test_enabled == 0U); + PP_EXPECT(h, state.scissor_test_enabled == 1U); + PP_EXPECT(h, state.viewport[0] == 2); + PP_EXPECT(h, state.viewport[1] == 4); + PP_EXPECT(h, state.viewport[2] == 640); + PP_EXPECT(h, state.viewport[3] == 320); + PP_EXPECT(h, state.clear_color[0] == 0.25F); + PP_EXPECT(h, state.clear_color[1] == 0.5F); + PP_EXPECT(h, state.clear_color[2] == 0.75F); + PP_EXPECT(h, state.clear_color[3] == 1.0F); + PP_EXPECT(h, state.program == 42); + PP_EXPECT(h, state.draw_framebuffer == 7); + PP_EXPECT(h, state.read_framebuffer == 9); + PP_EXPECT(h, state.active_texture == 0x84C4); + PP_EXPECT(h, state.cube_map_binding == 88); + PP_EXPECT(h, state.texture_2d_bindings[0] == 101); + PP_EXPECT(h, state.texture_2d_bindings[9] == 110); + PP_EXPECT(h, state.sampler_bindings[0] == 201); + PP_EXPECT(h, state.sampler_bindings[9] == 210); + PP_EXPECT(h, recorded_float_queries.size() == 1U); + PP_EXPECT(h, recorded_active_texture_calls.size() == 10U); + PP_EXPECT(h, recorded_active_texture_calls[0] == 0x84C0U); + PP_EXPECT(h, recorded_active_texture_calls[9] == 0x84C9U); +} + +void rejects_incomplete_gl_state_snapshot_dispatch(pp::tests::Harness& h) +{ + const auto result = pp::renderer::gl::snapshot_opengl_state( + pp::renderer::gl::OpenGlStateSnapshotDispatch { + .is_enabled = record_is_enabled, + .get_integer = record_get_integer, + .get_float = record_get_float, + }); + + PP_EXPECT(h, !result.ok()); + PP_EXPECT(h, result.status().code == pp::foundation::StatusCode::invalid_argument); +} + +void restores_legacy_gl_state(pp::tests::Harness& h) +{ + recorded_state_calls.clear(); + recorded_viewport_calls.clear(); + recorded_clear_calls.clear(); + recorded_active_texture_calls.clear(); + recorded_binding_calls.clear(); + + pp::renderer::gl::OpenGlSavedState state {}; + state.blend_enabled = 1U; + state.depth_test_enabled = 0U; + state.scissor_test_enabled = 1U; + state.viewport = { 3, 6, 800, 600 }; + state.clear_color = { 0.1F, 0.2F, 0.3F, 0.4F }; + state.program = 12; + state.draw_framebuffer = 21; + state.read_framebuffer = 22; + state.active_texture = 0x84C7; + state.cube_map_binding = 77; + for (std::size_t i = 0; i < state.texture_2d_bindings.size(); ++i) { + state.texture_2d_bindings[i] = 300 + static_cast(i); + state.sampler_bindings[i] = 400 + static_cast(i); + } + + const auto status = pp::renderer::gl::restore_opengl_state( + state, + pp::renderer::gl::OpenGlStateRestoreDispatch { + .enable = record_enable, + .disable = record_disable, + .viewport = record_viewport, + .clear_color = record_clear_color, + .bind_framebuffer = record_bind_framebuffer, + .use_program = record_use_program, + .active_texture = record_active_texture, + .bind_texture = record_bind_texture, + .bind_sampler = record_bind_sampler, + }); + + PP_EXPECT(h, status.ok()); + PP_EXPECT(h, recorded_state_calls.size() == 3U); + PP_EXPECT(h, recorded_state_calls[0].kind == RecordedOpenGlStateCall::Kind::enable); + PP_EXPECT(h, recorded_state_calls[0].first == 0x0BE2U); + PP_EXPECT(h, recorded_state_calls[1].kind == RecordedOpenGlStateCall::Kind::disable); + PP_EXPECT(h, recorded_state_calls[1].first == 0x0B71U); + PP_EXPECT(h, recorded_state_calls[2].kind == RecordedOpenGlStateCall::Kind::enable); + PP_EXPECT(h, recorded_state_calls[2].first == 0x0C11U); + PP_EXPECT(h, recorded_viewport_calls.size() == 1U); + PP_EXPECT(h, recorded_viewport_calls[0].width == 800); + PP_EXPECT(h, recorded_clear_calls.size() == 1U); + PP_EXPECT(h, recorded_clear_calls[0].color[2] == 0.3F); + PP_EXPECT(h, recorded_binding_calls.size() == 24U); + PP_EXPECT(h, recorded_binding_calls[0].kind == RecordedOpenGlBindingCall::Kind::bind_framebuffer); + PP_EXPECT(h, recorded_binding_calls[0].first == 0x8CA9U); + PP_EXPECT(h, recorded_binding_calls[0].second == 21U); + PP_EXPECT(h, recorded_binding_calls[1].kind == RecordedOpenGlBindingCall::Kind::bind_framebuffer); + PP_EXPECT(h, recorded_binding_calls[1].first == 0x8CA8U); + PP_EXPECT(h, recorded_binding_calls[1].second == 22U); + PP_EXPECT(h, recorded_binding_calls[2].kind == RecordedOpenGlBindingCall::Kind::use_program); + PP_EXPECT(h, recorded_binding_calls[2].first == 12U); + PP_EXPECT(h, recorded_active_texture_calls.size() == 11U); + PP_EXPECT(h, recorded_active_texture_calls[0] == 0x84C0U); + PP_EXPECT(h, recorded_active_texture_calls[9] == 0x84C9U); + PP_EXPECT(h, recorded_active_texture_calls[10] == 0x84C7U); + PP_EXPECT(h, recorded_binding_calls[3].kind == RecordedOpenGlBindingCall::Kind::bind_texture); + PP_EXPECT(h, recorded_binding_calls[3].first == 0x0DE1U); + PP_EXPECT(h, recorded_binding_calls[3].second == 300U); + PP_EXPECT(h, recorded_binding_calls[23].kind == RecordedOpenGlBindingCall::Kind::bind_texture); + PP_EXPECT(h, recorded_binding_calls[23].first == 0x8513U); + PP_EXPECT(h, recorded_binding_calls[23].second == 77U); +} + +void rejects_incomplete_gl_state_restore_dispatch(pp::tests::Harness& h) +{ + const auto status = pp::renderer::gl::restore_opengl_state( + pp::renderer::gl::OpenGlSavedState {}, + pp::renderer::gl::OpenGlStateRestoreDispatch { + .enable = record_enable, + .disable = record_disable, + .viewport = record_viewport, + }); + + PP_EXPECT(h, !status.ok()); + PP_EXPECT(h, status.code == pp::foundation::StatusCode::invalid_argument); +} + void queries_app_runtime_info(pp::tests::Harness& h) { recorded_string_queries.clear(); @@ -1433,6 +1693,10 @@ int main() harness.run("maps_app_initialization_parameters", maps_app_initialization_parameters); harness.run("applies_app_initialization_state", applies_app_initialization_state); harness.run("rejects_incomplete_app_initialization_state_dispatch", rejects_incomplete_app_initialization_state_dispatch); + harness.run("snapshots_legacy_gl_state", snapshots_legacy_gl_state); + harness.run("rejects_incomplete_gl_state_snapshot_dispatch", rejects_incomplete_gl_state_snapshot_dispatch); + harness.run("restores_legacy_gl_state", restores_legacy_gl_state); + harness.run("rejects_incomplete_gl_state_restore_dispatch", rejects_incomplete_gl_state_restore_dispatch); harness.run("queries_app_runtime_info", queries_app_runtime_info); harness.run("rejects_incomplete_app_runtime_info_dispatch", rejects_incomplete_app_runtime_info_dispatch); harness.run("clears_app_default_target", clears_app_default_target);