diff --git a/docs/modernization/build-inventory.md b/docs/modernization/build-inventory.md index 7e39651..5534d1d 100644 --- a/docs/modernization/build-inventory.md +++ b/docs/modernization/build-inventory.md @@ -383,9 +383,15 @@ Known local toolchain state: `NodeStrokePreview` brush preview rendering also consumes backend-owned depth/scissor/blend state, viewport/clear-color queries, active texture unit execution, fallback 2D texture unbinds, 2D texture targets, copy - targets, and sampler filters/wraps. Retained `Canvas` stroke/thumbnail paths - and `NodeCanvas` panorama rendering use the same tested active-texture - dispatch for texture-unit switches. + targets, and sampler filters/wraps. Its live stroke-mixer and brush-preview + viewport, scissor, and depth/blend state changes now execute through tested + `pp_renderer_gl` dispatch with only local OpenGL adapter endpoints retained. + Retained `Canvas` stroke/thumbnail/object/export paths and `NodeCanvas` + panorama rendering use the same tested active-texture dispatch for + texture-unit switches, and their live viewport, scissor, and generic + depth/blend/scissor capability changes now route through the same backend + dispatch contracts. Retained desktop HMD eye rendering also routes viewport + execution through tested backend dispatch. Legacy `Texture2D`, `TextureManager`, `Sampler`, and `RTT` public headers no longer expose raw OpenGL enum defaults; default texture formats, sampler filters/wraps, and render-target formats resolve through backend-owned @@ -639,6 +645,8 @@ Known local toolchain state: masked color clear with color-write-mask restore, and texture-bind dispatch, tested active-texture dispatch consumed by retained Canvas, NodeCanvas, and NodeStrokePreview texture-unit switches, + tested viewport/scissor/capability dispatch consumed by retained Canvas, + NodeCanvas, NodeStrokePreview, and HMD render-state paths, tested pixel-buffer allocation/readback/map/unmap/delete dispatch consumed by retained `PBO` recording readbacks, tested framebuffer-to-texture 2D copy dispatch consumed by retained canvas/UI paint paths, tested framebuffer diff --git a/docs/modernization/debt.md b/docs/modernization/debt.md index db994a4..25cc331 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; 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. The retained `copy_framebuffer_to_texture_2d` utility bridge now routes 2D framebuffer-to-texture copies through tested `pp_renderer_gl` dispatch, retained `RTT::create`/`RTT::destroy` render-target texture parameter setup, optional depth renderbuffer allocation, framebuffer allocation/attachment/status checks, binding restore, and resource deletion now route through tested `pp_renderer_gl` dispatch, retained RTT clear, masked clear with color-write-mask restore, and texture bind/unbind now route through tested `pp_renderer_gl` dispatch, and retained Canvas, NodeCanvas, and NodeStrokePreview texture-unit switches now route through tested active-texture dispatch, but actual live stroke rasterization, dual-brush compositing, pattern feedback math, thumbnail layer compositing, brush-preview compositing, the retained cube-map framebuffer copy, 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-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. The retained `copy_framebuffer_to_texture_2d` utility bridge now routes 2D framebuffer-to-texture copies through tested `pp_renderer_gl` dispatch, retained `RTT::create`/`RTT::destroy` render-target texture parameter setup, optional depth renderbuffer allocation, framebuffer allocation/attachment/status checks, binding restore, and resource deletion now route through tested `pp_renderer_gl` dispatch, retained RTT clear, masked clear with color-write-mask restore, and texture bind/unbind now route through tested `pp_renderer_gl` dispatch, retained Canvas, NodeCanvas, and NodeStrokePreview texture-unit switches now route through tested active-texture dispatch, and retained Canvas, NodeCanvas, NodeStrokePreview, and desktop HMD viewport/scissor/capability execution now route through tested `pp_renderer_gl` dispatch adapters, but actual live stroke rasterization, dual-brush compositing, pattern feedback math, thumbnail layer compositing, brush-preview compositing, the retained cube-map framebuffer copy, 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.*`, and retained `PBO` allocation/readback/map/unmap/delete operations now route through tested `pp_renderer_gl` dispatch, but the bridge still owns legacy recording thread startup/shutdown, platform recorded-file cleanup, progress UI, retained `App::rec_loop` readback call sites, 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`; `pp_renderer_gl_capabilities_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 scheduling, 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 e82a786..55280ec 100644 --- a/docs/modernization/roadmap.md +++ b/docs/modernization/roadmap.md @@ -1108,9 +1108,17 @@ active texture units to `pp_renderer_gl`. state, viewport/clear-color queries, active texture unit execution, fallback 2D texture unbinds, 2D texture targets, copy targets, sampler filters/wraps, and destination-feedback copy/fetch decisions to `pp_renderer_gl` and -`pp_paint_renderer`. Retained `Canvas` stroke/thumbnail paths and `NodeCanvas` +`pp_paint_renderer`. Its live stroke-mixer and brush-preview viewport, +scissor, and depth/blend state changes now also execute through tested +`pp_renderer_gl` dispatch with only local OpenGL adapter endpoints retained. +Retained `Canvas` stroke/thumbnail/object/export paths and `NodeCanvas` panorama rendering use the same tested active-texture dispatch for their -texture-unit switches. +texture-unit switches, and their live viewport, scissor, and generic +depth/blend/scissor capability changes now route through the same backend +dispatch contracts. +Desktop HMD eye rendering now routes eye framebuffer viewport changes through +the tested `pp_renderer_gl` viewport dispatch while platform VR SDK bridges +remain isolated for later platform-shell extraction. Legacy `Texture2D`, `TextureManager`, `Sampler`, and `RTT` public headers no longer expose raw OpenGL enum defaults; default texture formats, sampler filters/wraps, and render-target formats are resolved through backend-owned @@ -2005,6 +2013,8 @@ Results: blend/depth state snapshots and restores, depth clears, active texture units, and fallback 2D texture unbinds through the renderer GL backend mapping; platform VR SDK bridges remain isolated for later platform-shell extraction. + Eye framebuffer viewport execution in the retained HMD path also routes + through tested `pp_renderer_gl` viewport dispatch. - Canvas mode overlay, mask, and transform paths now route generic OpenGL blend/depth state, active texture units, 2D framebuffer-to-texture copy dispatch, RGBA8 readback formats, and RTT-backed transform history region @@ -2013,6 +2023,8 @@ Results: viewport/clear/blend/depth/scissor state, color clears, active texture units, fallback 2D texture unbinds, 2D framebuffer-to-texture copy dispatch, and RGBA8 render-target formats through the renderer GL backend mapping. + Its live viewport and generic blend/depth/scissor capability changes now + execute through tested `pp_renderer_gl` dispatch adapters. - Canvas resource setup now routes stroke-buffer RGBA8/RGBA16F/RGBA32F formats, flood-fill texture upload format/type, brush/stencil/mix sampler filters and wraps, and cube-strip import channel formats through the renderer @@ -2021,7 +2033,8 @@ Results: - Early canvas draw helpers now route pick readbacks, stroke mixer depth/scissor and blend state, saved viewport/clear-state queries, active texture units, fallback 2D texture unbinds, and stroke background copy targets through the - renderer GL backend mapping. + renderer GL backend mapping. Stroke mixer viewport/scissor execution also + routes through the tested backend dispatch contract. - Canvas stroke commit now routes saved viewport/clear/blend state, history readbacks, active texture units, fallback 2D texture unbinds, and layer compositing copy targets through the renderer GL backend mapping; the @@ -2062,6 +2075,10 @@ Results: 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. +- Retained Canvas, NodeCanvas, NodeStrokePreview, and HMD viewport/scissor/ + capability execution now compiles through the renderer GL backend dispatch + adapters with `pp_legacy_paint_document`, `pp_panopainter_ui`, and + `panopainter_app`. - 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 45b660a..05cdfa7 100644 --- a/src/canvas.cpp +++ b/src/canvas.cpp @@ -198,6 +198,74 @@ void unbind_texture_2d() LOG("Canvas texture unbind dispatch failed because: %s", status.message); } +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_scissor(std::int32_t x, std::int32_t y, std::int32_t width, std::int32_t height) noexcept +{ + glScissor(static_cast(x), static_cast(y), static_cast(width), static_cast(height)); +} + +void apply_canvas_viewport(std::int32_t x, std::int32_t y, std::int32_t width, std::int32_t height) +{ + const auto status = pp::renderer::gl::apply_opengl_viewport( + pp::renderer::gl::OpenGlViewportRect { + .x = x, + .y = y, + .width = width, + .height = height, + }, + pp::renderer::gl::OpenGlViewportDispatch { + .viewport = set_opengl_viewport, + }); + if (!status.ok()) + LOG("Canvas viewport dispatch failed because: %s", status.message); +} + +void apply_canvas_scissor(std::int32_t x, std::int32_t y, std::int32_t width, std::int32_t height) +{ + const auto status = pp::renderer::gl::apply_opengl_scissor_rect( + pp::renderer::gl::OpenGlScissorRect { + .enabled = 1U, + .x = x, + .y = y, + .width = width, + .height = height, + }, + pp::renderer::gl::OpenGlScissorDispatch { + .enable = enable_opengl_state, + .disable = disable_opengl_state, + .scissor = set_opengl_scissor, + }); + if (!status.ok()) + LOG("Canvas scissor dispatch failed because: %s", status.message); +} + +void apply_canvas_capability(std::uint32_t state, bool enabled) +{ + const auto status = pp::renderer::gl::apply_opengl_capability( + state, + enabled, + pp::renderer::gl::OpenGlCapabilityDispatch { + .enable = enable_opengl_state, + .disable = disable_opengl_state, + }); + if (!status.ok()) + LOG("Canvas capability dispatch failed because: %s", status.message); +} + void gen_opengl_renderbuffers(std::uint32_t count, std::uint32_t* ids) noexcept { glGenRenderbuffers(static_cast(count), reinterpret_cast(ids)); @@ -427,12 +495,12 @@ void Canvas::stroke_draw_mix(const glm::vec2& bb_min, const glm::vec2& bb_sz) m_mixer.bindFramebuffer(); - glViewport(0, 0, m_mixer.getWidth(), m_mixer.getHeight()); - glDisable(depth_test_state()); - glEnable(scissor_test_state()); - glDisable(blend_state()); + apply_canvas_viewport(0, 0, m_mixer.getWidth(), m_mixer.getHeight()); + apply_canvas_capability(depth_test_state(), false); + apply_canvas_capability(scissor_test_state(), true); + apply_canvas_capability(blend_state(), false); - glScissor(bb_min.x, bb_min.y, bb_sz.x, bb_sz.y); + apply_canvas_scissor(bb_min.x, bb_min.y, bb_sz.x, bb_sz.y); auto layer_index = m_current_layer_idx; for (int plane_index = 0; plane_index < 6; plane_index++) @@ -718,7 +786,7 @@ void Canvas::stroke_draw() const auto& dual_brush = m_dual_stroke->m_brush; auto ortho_proj = glm::ortho(0.f, (float)m_width, 0.f, (float)m_height, -1.f, 1.f); - glViewport(0, 0, m_width, m_height); + apply_canvas_viewport(0, 0, m_width, m_height); m_sampler_brush.bind(0); m_sampler_nearest.bind(1); @@ -733,7 +801,7 @@ void Canvas::stroke_draw() const auto stroke_feedback = canvas_destination_feedback_plan(m_width, m_height); const bool copy_stroke_destination = !stroke_feedback.reads_destination_color; - glDisable(blend_state()); + apply_canvas_capability(blend_state(), false); ShaderManager::use(kShader::Stroke); ShaderManager::u_int(kShaderUniform::Tex, 0); // brush if (copy_stroke_destination) @@ -907,7 +975,7 @@ void Canvas::stroke_draw() m_sampler_nearest.unbind(); m_sampler_stencil.unbind(); - glViewport(vp[0], vp[1], vp[2], vp[3]); + apply_canvas_viewport(vp[0], vp[1], vp[2], vp[3]); glClearColor(cc[0], cc[1], cc[2], cc[3]); if (m_commit_delayed) @@ -1032,8 +1100,8 @@ void Canvas::stroke_commit() App::I->title_update(); // prepare common states - glViewport(0, 0, m_width, m_height); - glDisable(blend_state()); + apply_canvas_viewport(0, 0, m_width, m_height); + apply_canvas_capability(blend_state(), false); const auto& b = m_current_stroke->m_brush; @@ -1190,8 +1258,8 @@ void Canvas::stroke_commit() } // restore viewport and clear color states - blend ? glEnable(blend_state()) : glDisable(blend_state()); - glViewport(vp[0], vp[1], vp[2], vp[3]); + blend ? apply_canvas_capability(blend_state(), true) : apply_canvas_capability(blend_state(), false); + apply_canvas_viewport(vp[0], vp[1], vp[2], vp[3]); glClearColor(cc[0], cc[1], cc[2], cc[3]); set_active_texture_unit(0); @@ -1226,7 +1294,7 @@ void Canvas::draw_merge(bool draw_checkerboard, std::array faces /*= SI { assert(App::I->is_render_thread()); - glViewport(0, 0, m_width, m_height); + apply_canvas_viewport(0, 0, m_width, m_height); auto ortho = glm::ortho(-0.5f, 0.5f, -0.5f, 0.5f, -1.f, 1.f); const auto& b = m_current_stroke->m_brush; @@ -1239,7 +1307,7 @@ void Canvas::draw_merge(bool draw_checkerboard, std::array faces /*= SI const bool copy_blend_destination = use_blend && !blend_gate.reads_destination_color; // if not using shader blend, use gl rasterizer blend - glDisable(depth_test_state()); + apply_canvas_capability(depth_test_state(), false); for (int plane_index = 0; plane_index < 6; plane_index++) { @@ -1251,7 +1319,7 @@ void Canvas::draw_merge(bool draw_checkerboard, std::array faces /*= SI if (use_blend) { - glDisable(blend_state()); + apply_canvas_capability(blend_state(), false); m_layers_merge.rtt(plane_index).clear(); } else @@ -1263,7 +1331,7 @@ void Canvas::draw_merge(bool draw_checkerboard, std::array faces /*= SI ShaderManager::u_mat4(kShaderUniform::MVP, ortho); m_plane.draw_fill(); } - glEnable(blend_state()); + apply_canvas_capability(blend_state(), true); } for (int layer_index = 0; layer_index < m_layers.size(); layer_index++) @@ -1434,7 +1502,7 @@ void Canvas::draw_merge(bool draw_checkerboard, std::array faces /*= SI // draw the grid behind the layers using a temporary copy if (use_blend) { - glEnable(blend_state()); + apply_canvas_capability(blend_state(), true); //draw the grid if (draw_checkerboard) @@ -1595,8 +1663,8 @@ void Canvas::layer_merge(int source_idx, int dest_idx) // m_layer index App::I->render_task([&] { // prepare common states - glViewport(0, 0, m_width, m_height); - glDisable(blend_state()); + apply_canvas_viewport(0, 0, m_width, m_height); + apply_canvas_capability(blend_state(), false); for (int i = 0; i < 6; i++) { @@ -2017,7 +2085,7 @@ void Canvas::import_equirectangular_thread(std::string file_path, std::shared_pt Plane plane; plane.create<1>(2, 2); draw_objects([&](const glm::mat4& camera, const glm::mat4& proj, int i) { - glDisable(depth_test_state()); + apply_canvas_capability(depth_test_state(), false); tex.update(img.m_data.get() + indices[i] * stride); m_sampler.bind(0); set_active_texture_unit(0); @@ -2038,7 +2106,7 @@ void Canvas::import_equirectangular_thread(std::string file_path, std::shared_pt Sphere sphere; sphere.create<64, 64>(2.f); draw_objects([&](const glm::mat4& camera, const glm::mat4& proj, int i) { - glDisable(depth_test_state()); + apply_canvas_capability(depth_test_state(), false); m_sampler.bind(0); set_active_texture_unit(0); tex.bind(); @@ -2174,9 +2242,9 @@ void Canvas::export_depth_thread(std::string file_name) rtt.bindFramebuffer(); rtt.clear({ 0, 0, 0, 1 }); - glEnable(blend_state()); - glDisable(depth_test_state()); - glViewport(0, 0, rtt.getWidth(), rtt.getHeight()); + apply_canvas_capability(blend_state(), true); + apply_canvas_capability(depth_test_state(), false); + apply_canvas_viewport(0, 0, rtt.getWidth(), rtt.getHeight()); for (int plane_index = 0; plane_index < 6; plane_index++) { auto plane_mvp_z = proj * camera * @@ -2209,9 +2277,9 @@ void Canvas::export_depth_thread(std::string file_name) { rtt.bindFramebuffer(); rtt.clear({ 0, 0, 0, 1 }); - glEnable(blend_state()); - glDisable(depth_test_state()); - glViewport(0, 0, rtt.getWidth(), rtt.getHeight()); + apply_canvas_capability(blend_state(), true); + apply_canvas_capability(depth_test_state(), false); + apply_canvas_viewport(0, 0, rtt.getWidth(), rtt.getHeight()); for (int layer_index = 0; layer_index < m_layers.size(); layer_index++) { for (int plane_index = 0; plane_index < 6; plane_index++) @@ -2901,7 +2969,7 @@ Image Canvas::thumbnail_generate(int w, int h) auto blend = glIsEnabled(blend_state()); // prepare common states - glViewport(0, 0, w, h); + apply_canvas_viewport(0, 0, w, h); RTT fb; fb.create(w, h); @@ -2919,7 +2987,7 @@ Image Canvas::thumbnail_generate(int w, int h) fb.clear({ 1, 1, 1, 0 }); for (int i = 0; i < 6; i++) { - glDisable(blend_state()); + apply_canvas_capability(blend_state(), false); auto plane_mvp = proj * m_mv * m_plane_transform[i] * glm::translate(glm::vec3(0, 0, -1)); ShaderManager::use(kShader::TextureBlend); @@ -2969,7 +3037,7 @@ Image Canvas::thumbnail_generate(int w, int h) m_face_plane.draw_fill(); // now blend with the background - glEnable(blend_state()); + apply_canvas_capability(blend_state(), true); ShaderManager::use(kShader::Texture); ShaderManager::u_int(kShaderUniform::Tex, 0); ShaderManager::u_mat4(kShaderUniform::MVP, glm::ortho(-.5f, .5f, -.5f, .5f, -1.f, 1.f)); @@ -2988,8 +3056,8 @@ Image Canvas::thumbnail_generate(int w, int h) blendtex.destroy(); // restore viewport and clear color states - blend ? glEnable(blend_state()) : glDisable(blend_state()); - glViewport(vp[0], vp[1], vp[2], vp[3]); + blend ? apply_canvas_capability(blend_state(), true) : apply_canvas_capability(blend_state(), false); + apply_canvas_viewport(vp[0], vp[1], vp[2], vp[3]); glClearColor(cc[0], cc[1], cc[2], cc[3]); set_active_texture_unit(0); }); @@ -3036,8 +3104,8 @@ void Canvas::draw_objects_direct(std::function +namespace { + +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 apply_hmd_viewport(std::int32_t x, std::int32_t y, std::int32_t width, std::int32_t height) +{ + const auto status = pp::renderer::gl::apply_opengl_viewport( + pp::renderer::gl::OpenGlViewportRect { + .x = x, + .y = y, + .width = width, + .height = height, + }, + pp::renderer::gl::OpenGlViewportDispatch { + .viewport = set_opengl_viewport, + }); + if (!status.ok()) + LOG("HMD viewport dispatch failed because: %s", status.message); +} + +} + std::map ViveController::m_mask { { ViveController::kButton::Trigger, ViveController::kButtonMask::TriggerBit }, { ViveController::kButton::Pad, ViveController::kButtonMask::PadBit }, @@ -181,7 +207,7 @@ void Vive::Draw() { m_eyes[eye].bindFramebuffer(); m_eyes[eye].clear(); - glViewport(0, 0, m_eyes[eye].getWidth(), m_eyes[eye].getHeight()); + apply_hmd_viewport(0, 0, m_eyes[eye].getWidth(), m_eyes[eye].getHeight()); if (on_draw) on_draw(m_proj[eye], m_view[eye], m_pose); diff --git a/src/node_canvas.cpp b/src/node_canvas.cpp index 389295f..eb35a2f 100644 --- a/src/node_canvas.cpp +++ b/src/node_canvas.cpp @@ -52,6 +52,50 @@ void unbind_texture_2d() LOG("NodeCanvas texture unbind dispatch failed because: %s", status.message); } +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 apply_node_canvas_viewport(std::int32_t x, std::int32_t y, std::int32_t width, std::int32_t height) +{ + const auto status = pp::renderer::gl::apply_opengl_viewport( + pp::renderer::gl::OpenGlViewportRect { + .x = x, + .y = y, + .width = width, + .height = height, + }, + pp::renderer::gl::OpenGlViewportDispatch { + .viewport = set_opengl_viewport, + }); + if (!status.ok()) + LOG("NodeCanvas viewport dispatch failed because: %s", status.message); +} + +void apply_node_canvas_capability(std::uint32_t state, bool enabled) +{ + const auto status = pp::renderer::gl::apply_opengl_capability( + state, + enabled, + pp::renderer::gl::OpenGlCapabilityDispatch { + .enable = enable_opengl_state, + .disable = disable_opengl_state, + }); + if (!status.ok()) + LOG("NodeCanvas capability dispatch failed because: %s", status.message); +} + pp::renderer::RenderDeviceFeatures node_canvas_stroke_composite_features() noexcept { return ShaderManager::render_device_features(); @@ -245,7 +289,7 @@ void NodeCanvas::draw() auto depth = glIsEnabled(pp::renderer::gl::depth_test_state()); auto scissor = glIsEnabled(pp::renderer::gl::scissor_test_state()); - glDisable(pp::renderer::gl::scissor_test_state()); + apply_node_canvas_capability(pp::renderer::gl::scissor_test_state(), false); glm::ivec4 c = (glm::ivec4)glm::vec4(box.x, (int)(vp[3] - box.y - box.w), box.z, box.w); @@ -286,13 +330,13 @@ void NodeCanvas::draw() m_rtt.bindFramebuffer(); glClearColor(1, 1, 0, 0); glClear(pp::renderer::gl::framebuffer_color_buffer_mask()); - glViewport(0, 0, m_rtt.getWidth(), m_rtt.getHeight()); + apply_node_canvas_viewport(0, 0, m_rtt.getWidth(), m_rtt.getHeight()); } else { glClearColor(1, 1, 1, 0); glClear(pp::renderer::gl::framebuffer_color_buffer_mask()); - glViewport(c.x + App::I->off_x, c.y + App::I->off_y, c.z, c.w); + apply_node_canvas_viewport(c.x + App::I->off_x, c.y + App::I->off_y, c.z, c.w); } // NOTE: draw_merge has been disabled for worst performance @@ -301,7 +345,7 @@ void NodeCanvas::draw() if (draw_merged) { - glDisable(pp::renderer::gl::blend_state()); + apply_node_canvas_capability(pp::renderer::gl::blend_state(), false); // draw the grid for (int plane_index = 0; plane_index < 6; plane_index++) { @@ -347,7 +391,7 @@ void NodeCanvas::draw() if (use_blend) { - glViewport(0, 0, m_cache_rtt.getWidth(), m_cache_rtt.getHeight()); + apply_node_canvas_viewport(0, 0, m_cache_rtt.getWidth(), m_cache_rtt.getHeight()); m_cache_rtt.bindFramebuffer(); m_cache_rtt.clear({ 1, 1, 1, 0 }); } @@ -369,8 +413,8 @@ void NodeCanvas::draw() } // if not using shader blend, use gl rasterizer blend - use_blend ? glDisable(pp::renderer::gl::blend_state()) : glEnable(pp::renderer::gl::blend_state()); - glDisable(pp::renderer::gl::depth_test_state()); + use_blend ? apply_node_canvas_capability(pp::renderer::gl::blend_state(), false) : apply_node_canvas_capability(pp::renderer::gl::blend_state(), true); + apply_node_canvas_capability(pp::renderer::gl::depth_test_state(), false); const auto& b = m_canvas->m_current_stroke->m_brush; @@ -589,15 +633,15 @@ void NodeCanvas::draw() { m_cache_rtt.unbindFramebuffer(); if (m_density != 1.f) - glViewport(0, 0, m_rtt.getWidth(), m_rtt.getHeight()); + apply_node_canvas_viewport(0, 0, m_rtt.getWidth(), m_rtt.getHeight()); else - glViewport(c.x + App::I->off_x, c.y + App::I->off_y, c.z, c.w); + apply_node_canvas_viewport(c.x + App::I->off_x, c.y + App::I->off_y, c.z, c.w); } // draw the grid behind the layers using a temporary copy if (use_blend) { - glEnable(pp::renderer::gl::blend_state()); + apply_node_canvas_capability(pp::renderer::gl::blend_state(), true); //draw the grid for (int plane_index = 0; plane_index < 6; plane_index++) @@ -625,7 +669,7 @@ void NodeCanvas::draw() } } - glDisable(pp::renderer::gl::depth_test_state()); + apply_node_canvas_capability(pp::renderer::gl::depth_test_state(), false); if (m_canvas->m_smask_active || m_canvas->m_current_mode == kCanvasMode::Copy || m_canvas->m_current_mode == kCanvasMode::Cut) { @@ -641,7 +685,7 @@ void NodeCanvas::draw() ShaderManager::u_int(kShaderUniform::Tex, 0); ShaderManager::u_vec2(kShaderUniform::PatternOffset, m_outline_pan); set_active_texture_unit(0); - glEnable(pp::renderer::gl::blend_state()); + apply_node_canvas_capability(pp::renderer::gl::blend_state(), true); //draw the cube faces for (int plane_index = 0; plane_index < 6; plane_index++) @@ -676,7 +720,7 @@ void NodeCanvas::draw() glClearColor(1, 1, 1, 0); glClear(pp::renderer::gl::framebuffer_color_buffer_mask()); - glViewport(c.x + App::I->off_x, c.y + App::I->off_y, c.z, c.w); + apply_node_canvas_viewport(c.x + App::I->off_x, c.y + App::I->off_y, c.z, c.w); // draw the canvas m_sampler_nearest.bind(0); @@ -689,10 +733,10 @@ void NodeCanvas::draw() m_rtt.unbindTexture(); } - scissor ? glEnable(pp::renderer::gl::scissor_test_state()) : glDisable(pp::renderer::gl::scissor_test_state()); - blend ? glEnable(pp::renderer::gl::blend_state()) : glDisable(pp::renderer::gl::blend_state()); - depth ? glEnable(pp::renderer::gl::depth_test_state()) : glDisable(pp::renderer::gl::depth_test_state()); - glViewport(vp[0], vp[1], vp[2], vp[3]); + scissor ? apply_node_canvas_capability(pp::renderer::gl::scissor_test_state(), true) : apply_node_canvas_capability(pp::renderer::gl::scissor_test_state(), false); + blend ? apply_node_canvas_capability(pp::renderer::gl::blend_state(), true) : apply_node_canvas_capability(pp::renderer::gl::blend_state(), false); + depth ? apply_node_canvas_capability(pp::renderer::gl::depth_test_state(), true) : apply_node_canvas_capability(pp::renderer::gl::depth_test_state(), false); + apply_node_canvas_viewport(vp[0], vp[1], vp[2], vp[3]); glClearColor(cc[0], cc[1], cc[2], cc[3]); } diff --git a/src/node_stroke_preview.cpp b/src/node_stroke_preview.cpp index 583a7e6..f4d53fc 100644 --- a/src/node_stroke_preview.cpp +++ b/src/node_stroke_preview.cpp @@ -72,6 +72,74 @@ void unbind_texture_2d() LOG("NodeStrokePreview texture unbind dispatch failed because: %s", status.message); } +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_scissor(std::int32_t x, std::int32_t y, std::int32_t width, std::int32_t height) noexcept +{ + glScissor(static_cast(x), static_cast(y), static_cast(width), static_cast(height)); +} + +void apply_stroke_preview_viewport(std::int32_t x, std::int32_t y, std::int32_t width, std::int32_t height) +{ + const auto status = pp::renderer::gl::apply_opengl_viewport( + pp::renderer::gl::OpenGlViewportRect { + .x = x, + .y = y, + .width = width, + .height = height, + }, + pp::renderer::gl::OpenGlViewportDispatch { + .viewport = set_opengl_viewport, + }); + if (!status.ok()) + LOG("NodeStrokePreview viewport dispatch failed because: %s", status.message); +} + +void apply_stroke_preview_scissor(std::int32_t x, std::int32_t y, std::int32_t width, std::int32_t height) +{ + const auto status = pp::renderer::gl::apply_opengl_scissor_rect( + pp::renderer::gl::OpenGlScissorRect { + .enabled = 1U, + .x = x, + .y = y, + .width = width, + .height = height, + }, + pp::renderer::gl::OpenGlScissorDispatch { + .enable = enable_opengl_state, + .disable = disable_opengl_state, + .scissor = set_opengl_scissor, + }); + if (!status.ok()) + LOG("NodeStrokePreview scissor dispatch failed because: %s", status.message); +} + +void apply_stroke_preview_capability(std::uint32_t state, bool enabled) +{ + const auto status = pp::renderer::gl::apply_opengl_capability( + state, + enabled, + pp::renderer::gl::OpenGlCapabilityDispatch { + .enable = enable_opengl_state, + .disable = disable_opengl_state, + }); + if (!status.ok()) + LOG("NodeStrokePreview capability dispatch failed because: %s", status.message); +} + } std::atomic_int NodeStrokePreview::s_instances{ 0 }; @@ -155,12 +223,12 @@ void NodeStrokePreview::stroke_draw_mix(const glm::vec2& bb_min, const glm::vec2 m_rtt_mixer.bindFramebuffer(); - glViewport(0, 0, m_rtt_mixer.getWidth(), m_rtt_mixer.getHeight()); - glDisable(pp::renderer::gl::depth_test_state()); - glEnable(pp::renderer::gl::scissor_test_state()); - glDisable(pp::renderer::gl::blend_state()); + apply_stroke_preview_viewport(0, 0, m_rtt_mixer.getWidth(), m_rtt_mixer.getHeight()); + apply_stroke_preview_capability(pp::renderer::gl::depth_test_state(), false); + apply_stroke_preview_capability(pp::renderer::gl::scissor_test_state(), true); + apply_stroke_preview_capability(pp::renderer::gl::blend_state(), false); - glScissor( + apply_stroke_preview_scissor( static_cast(bb_min.x), static_cast(bb_min.y), static_cast(bb_sz.x), @@ -339,7 +407,7 @@ void NodeStrokePreview::draw_stroke_immediate() glm::vec2 size = { m_rtt.getWidth(), m_rtt.getHeight() }; glm::mat4 ortho_proj = glm::ortho(0, size.x, 0, size.y, -1, 1); - glViewport(0, 0, m_rtt.getWidth(), m_rtt.getHeight()); + apply_stroke_preview_viewport(0, 0, m_rtt.getWidth(), m_rtt.getHeight()); m_rtt.bindFramebuffer(); m_rtt.clear(); m_sampler_mipmap.bind(0); @@ -418,7 +486,7 @@ void NodeStrokePreview::draw_stroke_immediate() if (b->m_pattern_flipx) patt_scale.x *= -1.f; if (b->m_pattern_flipy) patt_scale.y *= -1.f; - glDisable(pp::renderer::gl::blend_state()); + apply_stroke_preview_capability(pp::renderer::gl::blend_state(), false); ShaderManager::use(kShader::Stroke); ShaderManager::u_int(kShaderUniform::Tex, 0); // brush const auto stroke_feedback = stroke_preview_destination_feedback_plan(m_rtt.getWidth(), m_rtt.getHeight()); @@ -600,7 +668,7 @@ void NodeStrokePreview::draw_stroke_immediate() m_rtt.unbindFramebuffer(); - glViewport(vp[0], vp[1], vp[2], vp[3]); + apply_stroke_preview_viewport(vp[0], vp[1], vp[2], vp[3]); glClearColor(cc[0], cc[1], cc[2], cc[3]); }