diff --git a/docs/modernization/build-inventory.md b/docs/modernization/build-inventory.md index 9796783..8b1deea 100644 --- a/docs/modernization/build-inventory.md +++ b/docs/modernization/build-inventory.md @@ -793,7 +793,11 @@ Known warnings after the current CMake app build: so app shader startup asks the backend for desktop GL/GLES/WebGL policy instead of carrying local platform branches. It also owns headless-tested OpenGL extension enumeration through `query_opengl_extensions`, moving the - extension count/string query loop out of `app_shaders.cpp`. + extension count/string query loop out of `app_shaders.cpp`. Startup feature + negotiation now uses `query_opengl_capability_detection`, so extension + enumeration, runtime capability policy, and renderer-neutral feature + conversion are validated together before the retained `ShaderManager` static + flags are updated. - `pp_legacy_ui_core` is an object-library containment boundary because the retained base `Node` controls still depend on legacy renderer and app headers. It should shrink as layout parsing, colors, generic controls, and diff --git a/docs/modernization/debt.md b/docs/modernization/debt.md index 460a772..10c44ab 100644 --- a/docs/modernization/debt.md +++ b/docs/modernization/debt.md @@ -53,7 +53,7 @@ agent or engineer to remove them without reconstructing context from chat. | DEBT-0033 | Open | Modernization | Tools menu planning and direct command execution dispatch now consume pure `pp_app_core` through `App::init_menu_tools`, `pano_cli plan-tools-menu`, `pano_cli plan-tools-panel`, and the `ToolsMenuServices` boundary, and direct command execution is centralized in `src/legacy_app_shell_services.*`; SonarPen availability/startup now routes through `PlatformServices`, but live adapters still construct legacy `NodePanelFloating` panels, mutate legacy panel nodes, clear `CanvasModeGrid`, reset `NodeCanvas` camera state, open legacy shortcuts UI, and rely on the legacy platform adapter for the retained iOS SonarPen bridge | Preserve current Tools menu behavior while UI shell actions move toward app/UI/platform services | `pp_app_core_tools_menu_tests`; `pp_platform_api_tests`; `pano_cli plan-tools-menu --command shortcuts`; `pano_cli plan-tools-panel --panel layers`; `pano_cli plan-tools-panel --panel animation --already-visible`; `ctest --preset desktop-fast --build-config Debug` | Tools panel creation, submenu routing, grid clear, camera reset, shortcuts dialog, and SonarPen dispatch are owned by injected app/UI/platform services with `App::init_menu_tools` acting only as a UI adapter and no legacy Tools adapter | | DEBT-0034 | Open | Modernization | About menu command planning and execution dispatch now consume pure `pp_app_core` through `App::init_menu_about`, `pano_cli plan-about-menu`, and the `AboutMenuServices` boundary, and live execution is centralized in `src/legacy_app_shell_services.*`, but the bridge still opens legacy About/manual/what's-new dialogs, invokes the injected crash hook, and runs the legacy Canvas stroke performance test directly | Preserve About menu behavior while dialogs and diagnostics move toward app/UI/platform services | `pp_app_core_about_menu_tests`; `pano_cli plan-about-menu --command news --version-major 2 --version-minor 5 --version-fix 7`; `pano_cli plan-about-menu --command performance --no-canvas`; `ctest --preset desktop-fast --build-config Debug` | About/manual/what's-new dialog dispatch, crash-test dispatch, and performance-test execution are owned by injected app/UI/platform services with `App::init_menu_about` acting only as a UI adapter and no legacy About adapter | | DEBT-0035 | Open | Modernization | Main toolbar/status command planning and execution dispatch now consume pure `pp_app_core` through `App::init_toolbar_main`, `pano_cli plan-main-toolbar`, and the `MainToolbarServices` boundary, history/canvas commands now hand off through `HistoryUiServices` and `DocumentCanvasClearServices`, and live execution is centralized in `src/legacy_app_shell_services.*`, but the bridge still opens legacy open/save/settings/message-box dialogs and delegates to legacy history/canvas adapters | Preserve reachable toolbar/status behavior while app shell commands move toward app/document/UI services | `pp_app_core_main_toolbar_tests`; `pano_cli plan-main-toolbar --command undo --undo-count 2`; `pano_cli plan-main-toolbar --command clear-canvas --no-canvas`; `ctest --preset desktop-fast --build-config Debug` | Open/save/settings/message-box routing, undo/redo/clear-history execution, and canvas-clear execution are owned by injected app/document/UI services with `App::init_toolbar_main` acting only as a UI adapter and no legacy toolbar adapter | -| DEBT-0036 | Open | Modernization | `pp_renderer_api`, `pp_paint_renderer`, `pano_cli plan-paint-feedback`, and `pano_cli plan-stroke-composite` can choose backend-neutral complex paint feedback strategies for fixed-function blending, framebuffer-fetch-capable renderers, or ping-pong render targets. OpenGL extension detection now stores `pp::renderer::RenderDeviceFeatures` through `ShaderManager`, using `pp_renderer_gl::render_device_features` as the backend conversion point. `pp_paint_renderer::plan_canvas_blend_gate` owns the compatibility mapping from persisted layer/brush blend indices to the extracted stroke-composite planner, and live `Canvas::draw_merge` plus `NodeCanvas` panorama rendering both call it with the stored renderer-neutral feature set for their existing shader-blend gates and destination-copy versus framebuffer-fetch decisions. `pp_paint_renderer::plan_canvas_stroke_feedback` also owns the current destination-feedback decision, and live `Canvas::stroke_draw`, thumbnail layer blending, and `NodeStrokePreview` brush-preview rendering use it for framebuffer-fetch versus destination-copy decisions. Actual live stroke rasterization, dual-brush compositing, pattern feedback math, thumbnail layer compositing, and brush-preview compositing still use legacy OpenGL canvas/UI execution | Preserve current painting behavior while the renderer boundary matures for OpenGL parity and later Vulkan/Metal experiments | `pp_renderer_api_tests`; `pp_renderer_gl_capabilities_tests`; `pp_paint_renderer_compositor_tests`; `pano_cli plan-paint-feedback --framebuffer-fetch --explicit-transitions --render-only`; `pano_cli plan-paint-feedback --texture-copy`; `pano_cli plan-stroke-composite --stroke-blend 10 --framebuffer-fetch --explicit-transitions --render-only`; `pano_cli plan-stroke-composite --layer-blend 4 --dual-blend --texture-copy`; `ctest --preset desktop-fast --build-config Debug`; `cmake --build --preset windows-msvc-default --config Debug --target PanoPainter` | Live stroke/layer compositing chooses its feedback path through `pp_paint_renderer` and renderer services, with OpenGL golden parity and Vulkan/Metal lab tests covering framebuffer-fetch and ping-pong behavior | +| DEBT-0036 | Open | Modernization | `pp_renderer_api`, `pp_paint_renderer`, `pano_cli plan-paint-feedback`, and `pano_cli plan-stroke-composite` can choose backend-neutral complex paint feedback strategies for fixed-function blending, framebuffer-fetch-capable renderers, or ping-pong render targets. OpenGL extension detection now stores `pp::renderer::RenderDeviceFeatures` through `ShaderManager`, using `pp_renderer_gl::query_opengl_capability_detection`, `detect_opengl_feature_state`, and `render_device_features` as the backend conversion point. `pp_paint_renderer::plan_canvas_blend_gate` owns the compatibility mapping from persisted layer/brush blend indices to the extracted stroke-composite planner, and live `Canvas::draw_merge` plus `NodeCanvas` panorama rendering both call it with the stored renderer-neutral feature set for their existing shader-blend gates and destination-copy versus framebuffer-fetch decisions. `pp_paint_renderer::plan_canvas_stroke_feedback` also owns the current destination-feedback decision, and live `Canvas::stroke_draw`, thumbnail layer blending, and `NodeStrokePreview` brush-preview rendering use it for framebuffer-fetch versus destination-copy decisions. Actual live stroke rasterization, dual-brush compositing, pattern feedback math, thumbnail layer compositing, and brush-preview compositing still use legacy OpenGL canvas/UI execution | Preserve current painting behavior while the renderer boundary matures for OpenGL parity and later Vulkan/Metal experiments | `pp_renderer_api_tests`; `pp_renderer_gl_capabilities_tests`; `pp_paint_renderer_compositor_tests`; `pano_cli plan-paint-feedback --framebuffer-fetch --explicit-transitions --render-only`; `pano_cli plan-paint-feedback --texture-copy`; `pano_cli plan-stroke-composite --stroke-blend 10 --framebuffer-fetch --explicit-transitions --render-only`; `pano_cli plan-stroke-composite --layer-blend 4 --dual-blend --texture-copy`; `ctest --preset desktop-fast --build-config Debug`; `cmake --build --preset windows-msvc-default --config Debug --target PanoPainter` | Live stroke/layer compositing chooses its feedback path through `pp_paint_renderer` and renderer services, with OpenGL golden parity and Vulkan/Metal lab tests covering framebuffer-fetch and ping-pong behavior | | DEBT-0037 | Open | Modernization | Recording lifecycle/export planning and execution dispatch now consume pure `pp_app_core` through `App::rec_start`, `App::rec_stop`, `App::rec_clear`, `App::rec_export`, `pano_cli plan-recording-session`, and the `RecordingServices` boundary; live execution is centralized in `src/legacy_recording_services.*`, but the bridge still owns legacy recording thread startup/shutdown, platform recorded-file cleanup, progress UI, PBO readback through `App::rec_loop`, and `MP4Encoder::write_mp4` execution | Preserve current timelapse/MP4 behavior while recording moves toward app/document/renderer/video services | `pp_app_core_document_recording_tests`; `pano_cli plan-recording-session --running --frame-count 12`; `pano_cli plan-recording-session --platform-clears-files`; `ctest --preset desktop-fast --build-config Debug` | Recording thread lifecycle, frame readback, platform cleanup, progress reporting, and MP4 writing are owned by injected app/renderer/video services with `App` methods acting only as adapters | | DEBT-0038 | Open | Modernization | Cloud upload/browse/bulk planning and execution dispatch now consume pure `pp_app_core` through `App::cloud_upload`, `App::cloud_upload_all`, `App::cloud_browse`, `pano_cli plan-cloud-upload`, `pano_cli plan-cloud-upload-all`, `pano_cli plan-cloud-browse`, and the `CloudServices` boundary; live execution is centralized in `src/legacy_cloud_services.*`, the app-owned `upload`/`download`/license curl helpers now ask `PlatformServices` for the Android TLS-verification bypass policy, and retained `Asset::open_url`, `LogRemote::net_init`, and `NodeDialogCloud::load_thumbs_thread` curl sites consume the `pp_platform_api` default TLS policy helper instead of spelling Android branches locally, but the bridge still uses legacy save-before-upload, app-owned curl helpers instead of an injected network service, progress/message UI, OpenGL context guarding, `NodeDialogCloud`, `Canvas` project open, layer refresh, and `ActionManager` reset | Preserve current cloud behavior while cloud/network/document import flows move toward app/document/platform services | `pp_app_core_document_cloud_tests`; `pp_platform_api_tests`; `pano_cli plan-cloud-upload --new-document --unsaved`; `pano_cli plan-cloud-browse --selected-file demo.ppi`; `pano_cli plan-cloud-upload-all --file-count 3`; `ctest --preset desktop-fast --build-config Debug` | Cloud upload/download, TLS policy, save-before-upload, progress reporting, cloud browse dialog, downloaded project opening, layer refresh, OpenGL context ownership, and action-history reset are owned by injected app/document/network/platform/renderer services with `App` methods acting only as adapters | | DEBT-0039 | Open | Modernization | Document-open planning and execution dispatch now consume pure `pp_app_core` through `App::open_document`, `pano_cli plan-open-route`, `DocumentOpenServices`, and `src/legacy_document_open_services.*`, but the bridge still opens ABR/PPBR import prompts before delegating import execution to `src/legacy_brush_package_import_services.*`, applies unsaved-project discard prompts, calls legacy project-open execution, refreshes layer UI, updates the app title, and clears legacy history directly | Preserve current file-open/import behavior while document loading and brush import move toward app/document/asset/UI services | `pp_app_core_document_route_tests`; `pp_app_core_document_session_tests`; `pano_cli plan-open-route --path D:/Paint/Scenes/demo.ppi --unsaved`; `pano_cli plan-open-route --path D:/Paint/Brushes/clouds.ABR --unsaved`; `ctest --preset desktop-fast --build-config Debug` | Brush import prompting, project-open execution, unsaved-project discard prompting, layer refresh, title updates, and history clearing are owned by injected app/document/asset/UI services with `App::open_document` acting only as an adapter | diff --git a/docs/modernization/roadmap.md b/docs/modernization/roadmap.md index 61d2d98..716516f 100644 --- a/docs/modernization/roadmap.md +++ b/docs/modernization/roadmap.md @@ -680,6 +680,13 @@ OpenGL extension enumeration now also lives in `pp_renderer_gl` through a dispatch-tested `query_opengl_extensions` helper; shader startup still logs and applies the resulting feature flags, but the GL extension query loop is no longer app-owned. +OpenGL shader startup feature negotiation now also flows through +`pp_renderer_gl::query_opengl_capability_detection` and +`detect_opengl_feature_state`, so extension enumeration, desktop/GLES/WebGL +capability policy, and renderer-neutral feature conversion are tested together +behind the backend boundary. `App::initShaders` remains a legacy adapter that +copies the backend-owned feature snapshot into retained `ShaderManager` static +flags until `ShaderManager` itself becomes an OpenGL backend service. Prepared-file save/download handoff is now also part of the service contract, so iOS/Web export completion routes through `PlatformServices` after the app writes the temporary/exported payload. diff --git a/src/app_shaders.cpp b/src/app_shaders.cpp index 949538c..76ab9a4 100644 --- a/src/app_shaders.cpp +++ b/src/app_shaders.cpp @@ -6,15 +6,14 @@ namespace { -[[nodiscard]] pp::renderer::gl::OpenGlCapabilities shader_manager_capabilities() noexcept +void apply_shader_manager_feature_state(pp::renderer::gl::OpenGlFeatureState feature_state) noexcept { - pp::renderer::gl::OpenGlCapabilities capabilities; - capabilities.framebuffer_fetch = ShaderManager::ext_framebuffer_fetch; - capabilities.map_buffer_alignment = ShaderManager::ext_map_aligned; - capabilities.float32_textures = ShaderManager::ext_float32; - capabilities.float32_linear = ShaderManager::ext_float32_linear; - capabilities.float16_textures = ShaderManager::ext_float16; - return capabilities; + ShaderManager::ext_framebuffer_fetch = feature_state.capabilities.framebuffer_fetch; + ShaderManager::ext_map_aligned = feature_state.capabilities.map_buffer_alignment; + ShaderManager::ext_float32 = feature_state.capabilities.float32_textures; + ShaderManager::ext_float32_linear = feature_state.capabilities.float32_linear; + ShaderManager::ext_float16 = feature_state.capabilities.float16_textures; + ShaderManager::set_render_device_features(feature_state.features); } void query_gl_integer(std::uint32_t name, std::int32_t* value) noexcept @@ -40,42 +39,28 @@ void App::initShaders() #endif // _DEBUG render_task([] { - const auto extensions_result = pp::renderer::gl::query_opengl_extensions( + const auto detection_result = pp::renderer::gl::query_opengl_capability_detection( pp::renderer::gl::OpenGlExtensionQueryDispatch { .get_integer = query_gl_integer, .get_string_indexed = query_gl_string_indexed, - }); - if (!extensions_result.ok()) { - LOG("OpenGL extension query failed: %s", extensions_result.status().message); + }, + pp::renderer::gl::opengl_runtime_for_current_build()); + if (!detection_result.ok()) { + LOG("OpenGL capability detection failed: %s", detection_result.status().message); return; } - const auto& extension_storage = extensions_result.value(); - std::vector extension_views; - extension_views.reserve(extension_storage.size()); - for (const auto& extension : extension_storage) { - extension_views.push_back(extension); + const auto& detection = detection_result.value(); + for (const auto& extension : detection.extensions) { LOG("EXT: %s", extension.c_str()); } - const auto runtime = pp::renderer::gl::opengl_runtime_for_current_build(); - const auto capabilities = pp::renderer::gl::detect_opengl_capabilities(extension_views, runtime); - ShaderManager::ext_framebuffer_fetch = capabilities.framebuffer_fetch; - ShaderManager::ext_map_aligned = capabilities.map_buffer_alignment; - ShaderManager::ext_float32 = capabilities.float32_textures; - ShaderManager::ext_float32_linear = capabilities.float32_linear; - ShaderManager::ext_float16 = capabilities.float16_textures; - ShaderManager::set_render_device_features(pp::renderer::gl::render_device_features(capabilities)); + apply_shader_manager_feature_state(detection.feature_state); }); - if (pp::renderer::gl::opengl_runtime_for_current_build().desktop_gl) { - // In OpenGL 3.3 these should be already available. - ShaderManager::ext_float32_linear = true; - ShaderManager::ext_float32 = true; - ShaderManager::ext_float16 = true; - } - ShaderManager::set_render_device_features( - pp::renderer::gl::render_device_features(shader_manager_capabilities())); + apply_shader_manager_feature_state(pp::renderer::gl::detect_opengl_feature_state( + std::span {}, + pp::renderer::gl::opengl_runtime_for_current_build())); LOG("Shader Extension shader_framebuffer_fetch: %s", ShaderManager::ext_framebuffer_fetch ? "enabled" : "disabled"); diff --git a/src/renderer_gl/opengl_capabilities.cpp b/src/renderer_gl/opengl_capabilities.cpp index a04ef7a..1cbc2a1 100644 --- a/src/renderer_gl/opengl_capabilities.cpp +++ b/src/renderer_gl/opengl_capabilities.cpp @@ -228,6 +228,17 @@ pp::renderer::RenderDeviceFeatures render_device_features(OpenGlCapabilities cap }; } +OpenGlFeatureState detect_opengl_feature_state( + std::span extensions, + OpenGlRuntime runtime) noexcept +{ + const auto capabilities = detect_opengl_capabilities(extensions, runtime); + return OpenGlFeatureState { + .capabilities = capabilities, + .features = render_device_features(capabilities), + }; +} + OpenGlInitialState panopainter_initial_state() noexcept { return OpenGlInitialState { @@ -369,6 +380,28 @@ pp::foundation::Result> query_opengl_extensions( return pp::foundation::Result>::success(std::move(extensions)); } +pp::foundation::Result query_opengl_capability_detection( + OpenGlExtensionQueryDispatch dispatch, + OpenGlRuntime runtime) +{ + auto extensions_result = query_opengl_extensions(dispatch); + if (!extensions_result.ok()) { + return pp::foundation::Result::failure(extensions_result.status()); + } + + auto extensions = std::move(extensions_result.value()); + std::vector extension_views; + extension_views.reserve(extensions.size()); + for (const auto& extension : extensions) { + extension_views.push_back(extension); + } + + return pp::foundation::Result::success(OpenGlCapabilityDetectionResult { + .extensions = std::move(extensions), + .feature_state = detect_opengl_feature_state(extension_views, runtime), + }); +} + OpenGlDefaultClear panopainter_default_clear() noexcept { return OpenGlDefaultClear { diff --git a/src/renderer_gl/opengl_capabilities.h b/src/renderer_gl/opengl_capabilities.h index 9465506..c50a68c 100644 --- a/src/renderer_gl/opengl_capabilities.h +++ b/src/renderer_gl/opengl_capabilities.h @@ -27,6 +27,16 @@ struct OpenGlCapabilities { bool float16_textures = false; }; +struct OpenGlFeatureState { + OpenGlCapabilities capabilities; + pp::renderer::RenderDeviceFeatures features; +}; + +struct OpenGlCapabilityDetectionResult { + std::vector extensions; + OpenGlFeatureState feature_state; +}; + struct OpenGlPixelFormat { std::uint32_t internal_format = 0; std::uint32_t pixel_format = 0; @@ -689,6 +699,12 @@ struct OpenGlMeshDeleteDispatch { [[nodiscard]] OpenGlRuntime opengl_runtime_for_current_build() noexcept; [[nodiscard]] pp::renderer::RenderDeviceFeatures render_device_features( OpenGlCapabilities capabilities) noexcept; +[[nodiscard]] OpenGlFeatureState detect_opengl_feature_state( + std::span extensions, + OpenGlRuntime runtime) noexcept; +[[nodiscard]] pp::foundation::Result query_opengl_capability_detection( + OpenGlExtensionQueryDispatch dispatch, + OpenGlRuntime runtime); [[nodiscard]] OpenGlInitialState panopainter_initial_state() noexcept; [[nodiscard]] pp::foundation::Status apply_panopainter_initial_state(OpenGlStateDispatch dispatch) noexcept; [[nodiscard]] pp::foundation::Result snapshot_opengl_state( diff --git a/tests/renderer_gl/capabilities_tests.cpp b/tests/renderer_gl/capabilities_tests.cpp index f2e890d..36fd777 100644 --- a/tests/renderer_gl/capabilities_tests.cpp +++ b/tests/renderer_gl/capabilities_tests.cpp @@ -1025,6 +1025,40 @@ void treats_desktop_gl_float_rendering_as_core(pp::tests::Harness& h) PP_EXPECT(h, features.float32_render_targets); } +void detects_feature_state_from_extension_capabilities(pp::tests::Harness& h) +{ + constexpr std::array extensions { + "GL_EXT_shader_framebuffer_fetch", + "GL_ARB_map_buffer_alignment", + }; + + const auto feature_state = pp::renderer::gl::detect_opengl_feature_state( + extensions, + pp::renderer::gl::OpenGlRuntime {}); + + PP_EXPECT(h, feature_state.capabilities.framebuffer_fetch); + PP_EXPECT(h, feature_state.capabilities.map_buffer_alignment); + PP_EXPECT(h, !feature_state.capabilities.float32_textures); + PP_EXPECT(h, feature_state.features.framebuffer_fetch); + PP_EXPECT(h, feature_state.features.texture_copy); + PP_EXPECT(h, feature_state.features.render_target_blit); + PP_EXPECT(h, !feature_state.features.float32_render_targets); +} + +void detects_desktop_feature_state_without_extensions(pp::tests::Harness& h) +{ + const auto feature_state = pp::renderer::gl::detect_opengl_feature_state( + {}, + pp::renderer::gl::OpenGlRuntime { .desktop_gl = true }); + + PP_EXPECT(h, !feature_state.capabilities.framebuffer_fetch); + PP_EXPECT(h, feature_state.capabilities.float32_textures); + PP_EXPECT(h, feature_state.capabilities.float32_linear); + PP_EXPECT(h, feature_state.capabilities.float16_textures); + PP_EXPECT(h, feature_state.features.float32_render_targets); + PP_EXPECT(h, feature_state.features.float16_render_targets); +} + void detects_gles_texture_float_extensions(pp::tests::Harness& h) { constexpr std::array extensions { @@ -1890,6 +1924,58 @@ void rejects_incomplete_extension_query_dispatch(pp::tests::Harness& h) PP_EXPECT(h, result.status().code == pp::foundation::StatusCode::invalid_argument); } +void queries_app_capability_detection(pp::tests::Harness& h) +{ + recorded_integer_queries.clear(); + recorded_indexed_string_queries.clear(); + recorded_extension_count = 3; + + const auto result = pp::renderer::gl::query_opengl_capability_detection( + pp::renderer::gl::OpenGlExtensionQueryDispatch { + .get_integer = record_get_integer, + .get_string_indexed = record_indexed_string_query, + }, + pp::renderer::gl::OpenGlRuntime {}); + + PP_EXPECT(h, result.ok()); + PP_EXPECT(h, recorded_integer_queries.size() == 1U); + PP_EXPECT(h, recorded_integer_queries[0] == 0x821DU); + PP_EXPECT(h, recorded_indexed_string_queries.size() == 3U); + PP_EXPECT(h, result.value().extensions.size() == 3U); + PP_EXPECT(h, result.value().extensions[0] == "GL_EXT_shader_framebuffer_fetch"); + PP_EXPECT(h, result.value().feature_state.capabilities.framebuffer_fetch); + PP_EXPECT(h, result.value().feature_state.capabilities.map_buffer_alignment); + PP_EXPECT(h, !result.value().feature_state.capabilities.float32_textures); + PP_EXPECT(h, result.value().feature_state.features.framebuffer_fetch); +} + +void query_capability_detection_preserves_webgl_float_policy(pp::tests::Harness& h) +{ + recorded_extension_count = 3; + + const auto result = pp::renderer::gl::query_opengl_capability_detection( + pp::renderer::gl::OpenGlExtensionQueryDispatch { + .get_integer = record_get_integer, + .get_string_indexed = record_indexed_string_query, + }, + pp::renderer::gl::OpenGlRuntime { .gles = true, .web = true }); + + PP_EXPECT(h, result.ok()); + PP_EXPECT(h, result.value().feature_state.capabilities.framebuffer_fetch); + PP_EXPECT(h, !result.value().feature_state.capabilities.float32_textures); + PP_EXPECT(h, !result.value().feature_state.features.float32_render_targets); +} + +void rejects_incomplete_capability_detection_dispatch(pp::tests::Harness& h) +{ + const auto result = pp::renderer::gl::query_opengl_capability_detection( + pp::renderer::gl::OpenGlExtensionQueryDispatch {}, + pp::renderer::gl::OpenGlRuntime {}); + + PP_EXPECT(h, !result.ok()); + PP_EXPECT(h, result.status().code == pp::foundation::StatusCode::invalid_argument); +} + void clears_app_default_target(pp::tests::Harness& h) { recorded_clear_calls.clear(); @@ -4185,6 +4271,8 @@ int main() pp::tests::Harness harness; harness.run("detects_common_extension_capabilities", detects_common_extension_capabilities); harness.run("treats_desktop_gl_float_rendering_as_core", treats_desktop_gl_float_rendering_as_core); + harness.run("detects_feature_state_from_extension_capabilities", detects_feature_state_from_extension_capabilities); + harness.run("detects_desktop_feature_state_without_extensions", detects_desktop_feature_state_without_extensions); harness.run("detects_gles_texture_float_extensions", detects_gles_texture_float_extensions); harness.run("ignores_gles_texture_extensions_for_webgl_runtime", ignores_gles_texture_extensions_for_webgl_runtime); harness.run("classifies_current_build_opengl_runtime", classifies_current_build_opengl_runtime); @@ -4219,6 +4307,9 @@ int main() harness.run("converts_null_extension_names_to_empty_strings", converts_null_extension_names_to_empty_strings); harness.run("treats_negative_extension_count_as_empty", treats_negative_extension_count_as_empty); harness.run("rejects_incomplete_extension_query_dispatch", rejects_incomplete_extension_query_dispatch); + harness.run("queries_app_capability_detection", queries_app_capability_detection); + harness.run("query_capability_detection_preserves_webgl_float_policy", query_capability_detection_preserves_webgl_float_policy); + harness.run("rejects_incomplete_capability_detection_dispatch", rejects_incomplete_capability_detection_dispatch); harness.run("clears_app_default_target", clears_app_default_target); harness.run("rejects_incomplete_app_clear_dispatch", rejects_incomplete_app_clear_dispatch); harness.run("applies_viewport_dispatch", applies_viewport_dispatch);