diff --git a/docs/modernization/build-inventory.md b/docs/modernization/build-inventory.md index 8b1deea..b8de284 100644 --- a/docs/modernization/build-inventory.md +++ b/docs/modernization/build-inventory.md @@ -797,7 +797,10 @@ Known warnings after the current CMake app build: 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. + flags are updated. `RenderDeviceFeatures` now includes float32-linear + filtering, and live canvas, diagnostics, and grid lightmap/bake decisions + consume that renderer-neutral snapshot rather than reading retained + `ShaderManager::ext_*` fields directly. - `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 10c44ab..373ccc1 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::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-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; that feature snapshot now includes float32-linear filtering, so canvas stroke texture format selection, renderer diagnostics, grid lightmap render planning, and grid bake target selection no longer read `ShaderManager::ext_*` flags directly. `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, brush-preview compositing, and the retained `ShaderManager::ext_*` compatibility fields 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 716516f..c891186 100644 --- a/docs/modernization/roadmap.md +++ b/docs/modernization/roadmap.md @@ -1113,6 +1113,12 @@ The OpenGL shader initialization path now stores a renderer-neutral `RenderDeviceFeatures` snapshot converted by `pp_renderer_gl`, and those live canvas gates consume that snapshot instead of rebuilding feature flags from individual `ShaderManager` extension booleans. +`RenderDeviceFeatures` now carries the float32-linear-filtering bit as well, +so the canvas stroke texture format decision, renderer diagnostics, and grid +lightmap/bake target selection all consume the renderer-neutral feature +snapshot instead of reading `ShaderManager::ext_*` flags directly. The retained +extension booleans are now limited to the shader-manager compatibility adapter +and legacy logging. `pp_paint_renderer::plan_canvas_stroke_feedback` now models the current stroke shader's required destination feedback without changing the legacy shader math. Live `Canvas::stroke_draw` consumes that plan for main-brush, dual-brush, and diff --git a/src/app_layout.cpp b/src/app_layout.cpp index 15dc218..dea04f2 100644 --- a/src/app_layout.cpp +++ b/src/app_layout.cpp @@ -1526,7 +1526,7 @@ void App::initLayout() const auto renderer_diagnostics = pp::app::plan_renderer_diagnostics({ .framebuffer_fetch = renderer_features.framebuffer_fetch, .float32_render_targets = renderer_features.float32_render_targets, - .float32_linear_filtering = ShaderManager::ext_float32_linear, + .float32_linear_filtering = renderer_features.float32_linear_filtering, .float16_render_targets = renderer_features.float16_render_targets, }); diff --git a/src/canvas.cpp b/src/canvas.cpp index 02b6656..3689bbc 100644 --- a/src/canvas.cpp +++ b/src/canvas.cpp @@ -19,9 +19,10 @@ namespace { GLint current_canvas_stroke_internal_format() { - if (ShaderManager::ext_float32_linear) + const auto renderer_features = ShaderManager::render_device_features(); + if (renderer_features.float32_linear_filtering) return static_cast(pp::renderer::gl::rgba32f_internal_format()); - if (ShaderManager::ext_float16) + if (renderer_features.float16_render_targets) return static_cast(pp::renderer::gl::rgba16f_internal_format()); return static_cast(pp::renderer::gl::rgba8_internal_format()); } diff --git a/src/node_panel_grid.cpp b/src/node_panel_grid.cpp index 8749754..274fe12 100644 --- a/src/node_panel_grid.cpp +++ b/src/node_panel_grid.cpp @@ -100,10 +100,11 @@ void NodePanelGrid::init_controls() m_render->on_click = [this](Node*) { + const auto renderer_features = ShaderManager::render_device_features(); const auto plan = pp::app::plan_grid_lightmap_render( static_cast(m_hm_image.data()), - ShaderManager::ext_float32, - ShaderManager::ext_float16, + renderer_features.float32_render_targets, + renderer_features.float16_render_targets, get_texres(), get_samples()); if (!plan) @@ -356,11 +357,12 @@ void NodePanelGrid::bake_uvs() return; RTT fb; - if (ShaderManager::ext_float32) + const auto renderer_features = ShaderManager::render_device_features(); + if (renderer_features.float32_render_targets) { fb.create(m_texture.size().x, m_texture.size().y, -1, pp::renderer::gl::rgba32f_internal_format()); } - else if (ShaderManager::ext_float16) + else if (renderer_features.float16_render_targets) { fb.create(m_texture.size().x, m_texture.size().y, -1, pp::renderer::gl::rgba16f_internal_format()); } diff --git a/src/renderer_api/renderer_api.h b/src/renderer_api/renderer_api.h index 9bebcf3..e170eb7 100644 --- a/src/renderer_api/renderer_api.h +++ b/src/renderer_api/renderer_api.h @@ -240,6 +240,7 @@ struct RenderDeviceFeatures { bool frame_capture = false; bool float16_render_targets = false; bool float32_render_targets = false; + bool float32_linear_filtering = false; }; struct PaintFeedbackPlan { diff --git a/src/renderer_gl/opengl_capabilities.cpp b/src/renderer_gl/opengl_capabilities.cpp index 1cbc2a1..c9a5e19 100644 --- a/src/renderer_gl/opengl_capabilities.cpp +++ b/src/renderer_gl/opengl_capabilities.cpp @@ -225,6 +225,7 @@ pp::renderer::RenderDeviceFeatures render_device_features(OpenGlCapabilities cap .frame_capture = true, .float16_render_targets = capabilities.float16_textures, .float32_render_targets = capabilities.float32_textures, + .float32_linear_filtering = capabilities.float32_linear, }; } diff --git a/tests/renderer_api/renderer_api_tests.cpp b/tests/renderer_api/renderer_api_tests.cpp index ea4d79f..88a554d 100644 --- a/tests/renderer_api/renderer_api_tests.cpp +++ b/tests/renderer_api/renderer_api_tests.cpp @@ -610,6 +610,7 @@ public: .frame_capture = true, .float16_render_targets = true, .float32_render_targets = true, + .float32_linear_filtering = true, }; } @@ -1761,6 +1762,7 @@ void renderer_interfaces_support_backend_neutral_dispatch(pp::tests::Harness& h) PP_EXPECT(h, features.frame_capture); PP_EXPECT(h, features.float16_render_targets); PP_EXPECT(h, features.float32_render_targets); + PP_EXPECT(h, features.float32_linear_filtering); PP_EXPECT(h, device.trace()->begin_scope("renderer", "dispatch").ok()); PP_EXPECT(h, device.trace()->marker("renderer", "begin").ok()); PP_EXPECT(h, device.trace()->end_scope().ok()); diff --git a/tests/renderer_gl/capabilities_tests.cpp b/tests/renderer_gl/capabilities_tests.cpp index 36fd777..6639d2f 100644 --- a/tests/renderer_gl/capabilities_tests.cpp +++ b/tests/renderer_gl/capabilities_tests.cpp @@ -1008,6 +1008,7 @@ void detects_common_extension_capabilities(pp::tests::Harness& h) PP_EXPECT(h, features.frame_capture); PP_EXPECT(h, !features.float16_render_targets); PP_EXPECT(h, !features.float32_render_targets); + PP_EXPECT(h, !features.float32_linear_filtering); } void treats_desktop_gl_float_rendering_as_core(pp::tests::Harness& h) @@ -1023,6 +1024,7 @@ void treats_desktop_gl_float_rendering_as_core(pp::tests::Harness& h) const auto features = pp::renderer::gl::render_device_features(capabilities); PP_EXPECT(h, features.float16_render_targets); PP_EXPECT(h, features.float32_render_targets); + PP_EXPECT(h, features.float32_linear_filtering); } void detects_feature_state_from_extension_capabilities(pp::tests::Harness& h) @@ -1043,6 +1045,7 @@ void detects_feature_state_from_extension_capabilities(pp::tests::Harness& h) 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); + PP_EXPECT(h, !feature_state.features.float32_linear_filtering); } void detects_desktop_feature_state_without_extensions(pp::tests::Harness& h) @@ -1057,6 +1060,7 @@ void detects_desktop_feature_state_without_extensions(pp::tests::Harness& h) 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); + PP_EXPECT(h, feature_state.features.float32_linear_filtering); } void detects_gles_texture_float_extensions(pp::tests::Harness& h) @@ -1964,6 +1968,7 @@ void query_capability_detection_preserves_webgl_float_policy(pp::tests::Harness& 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); + PP_EXPECT(h, !result.value().feature_state.features.float32_linear_filtering); } void rejects_incomplete_capability_detection_dispatch(pp::tests::Harness& h)