Route canvas blend gate through paint renderer
This commit is contained in:
@@ -355,6 +355,7 @@ if(PP_BUILD_APP)
|
||||
pp_assets
|
||||
pp_document
|
||||
pp_paint
|
||||
pp_paint_renderer
|
||||
pp_renderer_api
|
||||
pp_project_warnings)
|
||||
if(TARGET pp_renderer_gl)
|
||||
@@ -392,6 +393,7 @@ if(PP_BUILD_APP)
|
||||
pp_assets
|
||||
pp_document
|
||||
pp_paint
|
||||
pp_paint_renderer
|
||||
pp_renderer_api
|
||||
pp_project_warnings)
|
||||
if(TARGET pp_renderer_gl)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# PanoPainter Capability Map
|
||||
|
||||
Status: live
|
||||
Last updated: 2026-06-02
|
||||
Last updated: 2026-06-03
|
||||
|
||||
This map is the preservation checklist for the modernization. When a component
|
||||
is extracted, update the relevant rows with the owning component, test label,
|
||||
@@ -37,7 +37,7 @@ and validation command.
|
||||
| PPBR import/export | brush panel/dialog | `pp_assets`, `pp_panopainter_ui` | Round-trip fixture |
|
||||
| Stroke sampling | `Stroke`, `Canvas` | `pp_paint` | Property tests for spacing, pressure, jitter |
|
||||
| Dual brush/pattern behavior | `Brush`, shaders | `pp_paint`, `pp_paint_renderer` | Stroke-alpha CPU reference, dual/pattern feedback planning, GPU golden |
|
||||
| Blend modes | GLSL include files, layer rendering | `pp_paint`, `pp_paint_renderer` | Final RGBA and stroke-alpha CPU reference vectors, fixed-function/framebuffer-fetch/ping-pong stroke composite planning, and GPU parity |
|
||||
| Blend modes | GLSL include files, layer rendering | `pp_paint`, `pp_paint_renderer` | Final RGBA and stroke-alpha CPU reference vectors, fixed-function/framebuffer-fetch/ping-pong stroke composite planning, live draw-merge gate coverage, and GPU parity |
|
||||
| Erase/flood fill/masks | `Canvas`, modes, shaders | `pp_document`, `pp_paint_renderer` | Edge masks, alpha lock, dirty rects |
|
||||
|
||||
## Layers And Animation
|
||||
|
||||
@@ -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, but live adapters still construct legacy `NodePanelFloating` panels, mutate legacy panel nodes, clear `CanvasModeGrid`, reset `NodeCanvas` camera state, open legacy shortcuts UI, and call the iOS SonarPen bridge directly | Preserve current Tools menu behavior while UI shell actions move toward app/UI/platform services | `pp_app_core_tools_menu_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, but the live adapter 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, and history/canvas commands now hand off through `HistoryUiServices` and `DocumentCanvasClearServices`, but the live adapter 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, but live stroke compositing still uses the legacy OpenGL canvas path | Preserve current painting behavior while the renderer boundary matures for OpenGL parity and later Vulkan/Metal experiments | `pp_renderer_api_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` | 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. The live `Canvas::draw_merge` shader-blend gate now calls `pp_paint_renderer::plan_stroke_composite` for the existing layer and primary-brush blend decision, but live stroke compositing, dual-brush feedback, and pattern feedback still use the legacy OpenGL canvas path | Preserve current painting behavior while the renderer boundary matures for OpenGL parity and later Vulkan/Metal experiments | `pp_renderer_api_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 |
|
||||
|
||||
## Closed Debt
|
||||
|
||||
|
||||
@@ -873,8 +873,11 @@ stroke composite plan that decides whether a stroke/layer blend can use
|
||||
fixed-function blending or needs framebuffer-fetch/ping-pong destination
|
||||
feedback. `pano_cli plan-stroke-composite` exposes the same decision for
|
||||
automation, including layer blend, stroke blend, dual-brush, and pattern-blend
|
||||
inputs. Live canvas stroke execution is still legacy OpenGL and remains tracked
|
||||
by DEBT-0036 until the app calls through this paint-renderer boundary.
|
||||
inputs. Live `Canvas::draw_merge` now uses this planner for its existing
|
||||
shader-blend gate for layer and primary-brush blend modes while preserving the
|
||||
legacy trigger policy; actual canvas stroke execution, dual-brush feedback, and
|
||||
pattern feedback are still legacy OpenGL and remain tracked by DEBT-0036 until
|
||||
the app calls through renderer services for the whole compositing path.
|
||||
The existing renderer classes are not yet fully
|
||||
behind the renderer interfaces.
|
||||
|
||||
@@ -1595,6 +1598,10 @@ Results:
|
||||
- Canvas layer merge rendering and explicit layer-merge compositing now route
|
||||
depth/blend state, active texture units, fallback 2D texture unbinds, and
|
||||
merge framebuffer copy targets through the renderer GL backend mapping.
|
||||
- Canvas draw-merge shader-blend selection now consumes the extracted
|
||||
`pp_paint_renderer` stroke composite planner for current layer and primary
|
||||
brush blend modes, while preserving legacy OpenGL compositing execution under
|
||||
DEBT-0036.
|
||||
- Canvas equirectangular import drawing and depth export rendering now route
|
||||
depth/blend state and active texture units through the renderer GL backend
|
||||
mapping.
|
||||
|
||||
112
src/canvas.cpp
112
src/canvas.cpp
@@ -4,6 +4,7 @@
|
||||
#include "app.h"
|
||||
#include "texture.h"
|
||||
#include "node_progress_bar.h"
|
||||
#include "paint_renderer/compositor.h"
|
||||
#include "renderer_gl/opengl_capabilities.h"
|
||||
#include <thread>
|
||||
#include <algorithm>
|
||||
@@ -43,6 +44,104 @@ GLenum rgba_pixel_format()
|
||||
return static_cast<GLenum>(pp::renderer::gl::rgba_pixel_format());
|
||||
}
|
||||
|
||||
bool to_paint_blend_mode(int value, pp::paint::BlendMode& out) noexcept
|
||||
{
|
||||
switch (value) {
|
||||
case 0: out = pp::paint::BlendMode::normal; return true;
|
||||
case 1: out = pp::paint::BlendMode::multiply; return true;
|
||||
case 2: out = pp::paint::BlendMode::screen; return true;
|
||||
case 3: out = pp::paint::BlendMode::color_dodge; return true;
|
||||
case 4: out = pp::paint::BlendMode::overlay; return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool to_stroke_blend_mode(int value, pp::paint::StrokeBlendMode& out) noexcept
|
||||
{
|
||||
switch (value) {
|
||||
case 0: out = pp::paint::StrokeBlendMode::normal; return true;
|
||||
case 1: out = pp::paint::StrokeBlendMode::multiply; return true;
|
||||
case 2: out = pp::paint::StrokeBlendMode::subtract; return true;
|
||||
case 3: out = pp::paint::StrokeBlendMode::darken; return true;
|
||||
case 4: out = pp::paint::StrokeBlendMode::overlay; return true;
|
||||
case 5: out = pp::paint::StrokeBlendMode::color_dodge; return true;
|
||||
case 6: out = pp::paint::StrokeBlendMode::color_burn; return true;
|
||||
case 7: out = pp::paint::StrokeBlendMode::linear_burn; return true;
|
||||
case 8: out = pp::paint::StrokeBlendMode::hard_mix; return true;
|
||||
case 9: out = pp::paint::StrokeBlendMode::linear_height; return true;
|
||||
case 10: out = pp::paint::StrokeBlendMode::height; return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
pp::renderer::RenderDeviceFeatures canvas_stroke_composite_features() noexcept
|
||||
{
|
||||
return pp::renderer::RenderDeviceFeatures {
|
||||
.framebuffer_fetch = ShaderManager::ext_framebuffer_fetch,
|
||||
.texture_copy = !ShaderManager::ext_framebuffer_fetch,
|
||||
};
|
||||
}
|
||||
|
||||
bool stroke_composite_plan_needs_shader_blend(
|
||||
int width,
|
||||
int height,
|
||||
pp::paint::BlendMode layer_blend_mode,
|
||||
pp::paint::StrokeBlendMode stroke_blend_mode) noexcept
|
||||
{
|
||||
const auto plan = pp::paint_renderer::plan_stroke_composite(
|
||||
canvas_stroke_composite_features(),
|
||||
pp::paint_renderer::StrokeCompositeRequest {
|
||||
.extent = pp::renderer::Extent2D {
|
||||
.width = static_cast<std::uint32_t>(std::max(width, 0)),
|
||||
.height = static_cast<std::uint32_t>(std::max(height, 0)),
|
||||
},
|
||||
.layer_blend_mode = layer_blend_mode,
|
||||
.stroke_blend_mode = stroke_blend_mode,
|
||||
});
|
||||
return plan ? plan.value().complex_blend : true;
|
||||
}
|
||||
|
||||
bool draw_merge_needs_shader_blend(
|
||||
int width,
|
||||
int height,
|
||||
const std::vector<std::shared_ptr<Layer>>& layers,
|
||||
const Brush* brush) noexcept
|
||||
{
|
||||
for (const auto& layer : layers) {
|
||||
if (!layer) {
|
||||
continue;
|
||||
}
|
||||
pp::paint::BlendMode layer_blend = pp::paint::BlendMode::normal;
|
||||
if (!to_paint_blend_mode(layer->m_blend_mode, layer_blend)) {
|
||||
if (layer->m_blend_mode != 0) {
|
||||
return true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (stroke_composite_plan_needs_shader_blend(
|
||||
width,
|
||||
height,
|
||||
layer_blend,
|
||||
pp::paint::StrokeBlendMode::normal)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (brush) {
|
||||
pp::paint::StrokeBlendMode stroke_blend = pp::paint::StrokeBlendMode::normal;
|
||||
if (!to_stroke_blend_mode(brush->m_blend_mode, stroke_blend)) {
|
||||
return brush->m_blend_mode != 0;
|
||||
}
|
||||
return stroke_composite_plan_needs_shader_blend(
|
||||
width,
|
||||
height,
|
||||
pp::paint::BlendMode::normal,
|
||||
stroke_blend);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
GLenum unsigned_byte_component_type()
|
||||
{
|
||||
return static_cast<GLenum>(pp::renderer::gl::unsigned_byte_component_type());
|
||||
@@ -1086,14 +1185,11 @@ void Canvas::draw_merge(bool draw_checkerboard, std::array<bool, 6> faces /*= SI
|
||||
auto ortho = glm::ortho<float>(-0.5f, 0.5f, -0.5f, 0.5f, -1.f, 1.f);
|
||||
const auto& b = m_current_stroke->m_brush;
|
||||
|
||||
// check if any layer use blend, otherwise draw directly on main framebuffer
|
||||
bool use_blend = false;
|
||||
for (auto& l : m_layers)
|
||||
{
|
||||
use_blend |= l->m_blend_mode != 0;
|
||||
}
|
||||
if (Canvas::I->m_current_stroke)
|
||||
use_blend |= Canvas::I->m_current_stroke->m_brush->m_blend_mode != 0;
|
||||
const bool use_blend = draw_merge_needs_shader_blend(
|
||||
m_width,
|
||||
m_height,
|
||||
m_layers,
|
||||
m_current_stroke ? m_current_stroke->m_brush.get() : nullptr);
|
||||
|
||||
// if not using shader blend, use gl rasterizer blend
|
||||
glDisable(depth_test_state());
|
||||
|
||||
Reference in New Issue
Block a user