Plan live stroke rasterization boundaries
This commit is contained in:
@@ -18,6 +18,13 @@ agent or engineer to remove them without reconstructing context from chat.
|
|||||||
|
|
||||||
## Recent Reductions
|
## Recent Reductions
|
||||||
|
|
||||||
|
- 2026-06-12: DEBT-0036 was narrowed again. Live `Canvas::stroke_draw`
|
||||||
|
destination feedback now consumes a named `CanvasStrokeRasterizationPlan`
|
||||||
|
through `plan_legacy_canvas_stroke_rasterization`, and `pp_paint_renderer`
|
||||||
|
owns pure material/pass planning for stroke pattern, mixer, dual-brush, and
|
||||||
|
final composite texture/uniform intent. Retained OpenGL stroke execution
|
||||||
|
still lives in `Canvas`, but feedback/material decisions now have tested
|
||||||
|
renderer-facing plan boundaries.
|
||||||
- 2026-06-12: DEBT-0036 was narrowed again. The opt-in `desktop-gpu`
|
- 2026-06-12: DEBT-0036 was narrowed again. The opt-in `desktop-gpu`
|
||||||
preset now owns a real OpenGL readback golden gate through
|
preset now owns a real OpenGL readback golden gate through
|
||||||
`pp_renderer_gl_gpu_readback_tests`, validating a deterministic 1x1 clear
|
`pp_renderer_gl_gpu_readback_tests`, validating a deterministic 1x1 clear
|
||||||
|
|||||||
@@ -1332,9 +1332,16 @@ catalog now consumed by the legacy OpenGL app initialization path.
|
|||||||
OpenGL capability detection for framebuffer fetch, map-buffer alignment, and
|
OpenGL capability detection for framebuffer fetch, map-buffer alignment, and
|
||||||
float texture support. It also owns the OpenGL texture upload-type mapping used
|
float texture support. It also owns the OpenGL texture upload-type mapping used
|
||||||
by legacy `Texture2D` and `RTT` creation, RGBA pixel-format mapping used by
|
by legacy `Texture2D` and `RTT` creation, RGBA pixel-format mapping used by
|
||||||
`RTT` texture allocation, plus image channel-count to texture
|
`RTT` texture allocation, plus image channel-count to texture format mapping
|
||||||
format mapping for `Texture2D` image uploads and framebuffer status naming for
|
for `Texture2D` image uploads and framebuffer status naming for `RTT` and
|
||||||
`RTT` and `Texture2D` diagnostics. It also owns renderer API texture-format to
|
`Texture2D` diagnostics. Live stroke rasterization has started moving toward renderer
|
||||||
|
services: `Canvas::stroke_draw` now consumes a named
|
||||||
|
`CanvasStrokeRasterizationPlan` through a legacy adapter boundary for
|
||||||
|
destination feedback/copy decisions, and `pp_paint_renderer` owns pure
|
||||||
|
stroke material/pass planning for pattern, mixer, dual-brush, and final
|
||||||
|
composite texture/uniform intent. Actual retained OpenGL draw execution remains
|
||||||
|
in `Canvas` under `DEBT-0036`.
|
||||||
|
It also owns renderer API texture-format to
|
||||||
OpenGL internal/pixel/component token mapping, including depth-stencil formats,
|
OpenGL internal/pixel/component token mapping, including depth-stencil formats,
|
||||||
for future backend texture objects. `Texture2D` 2D texture binding, upload,
|
for future backend texture objects. `Texture2D` 2D texture binding, upload,
|
||||||
mipmap generation, framebuffer readback setup, and update component-type tokens
|
mipmap generation, framebuffer readback setup, and update component-type tokens
|
||||||
|
|||||||
@@ -495,10 +495,9 @@ Done Checks:
|
|||||||
|
|
||||||
### LATER-003 - Live Stroke Rasterization Through Renderer Services
|
### LATER-003 - Live Stroke Rasterization Through Renderer Services
|
||||||
|
|
||||||
Status: Blocked
|
Status: Ready
|
||||||
Score: +5 renderer boundary and OpenGL parity
|
Score: +5 renderer boundary and OpenGL parity
|
||||||
Debt: `DEBT-0036`
|
Debt: `DEBT-0036`
|
||||||
Blocked By: `RND-001`, `RND-002`, `RND-003`, and at least one GPU golden gate
|
|
||||||
|
|
||||||
Done Checks:
|
Done Checks:
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include "canvas.h"
|
#include "canvas.h"
|
||||||
#include "app.h"
|
#include "app.h"
|
||||||
#include "legacy_gl_renderbuffer_dispatch.h"
|
#include "legacy_gl_renderbuffer_dispatch.h"
|
||||||
|
#include "legacy_canvas_stroke_services.h"
|
||||||
#include "legacy_ui_gl_dispatch.h"
|
#include "legacy_ui_gl_dispatch.h"
|
||||||
#include "legacy_ui_overlay_services.h"
|
#include "legacy_ui_overlay_services.h"
|
||||||
#include "app_core/document_canvas.h"
|
#include "app_core/document_canvas.h"
|
||||||
@@ -43,25 +44,21 @@ pp::renderer::RenderDeviceFeatures canvas_render_device_features() noexcept
|
|||||||
return ShaderManager::render_device_features();
|
return ShaderManager::render_device_features();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pp::paint_renderer::CanvasStrokeRasterizationPlan canvas_stroke_rasterization_plan(
|
||||||
|
int width,
|
||||||
|
int height) noexcept
|
||||||
|
{
|
||||||
|
return pp::panopainter::plan_legacy_canvas_stroke_rasterization(
|
||||||
|
canvas_render_device_features(),
|
||||||
|
width,
|
||||||
|
height);
|
||||||
|
}
|
||||||
|
|
||||||
pp::paint_renderer::CanvasStrokeFeedbackPlan canvas_destination_feedback_plan(
|
pp::paint_renderer::CanvasStrokeFeedbackPlan canvas_destination_feedback_plan(
|
||||||
int width,
|
int width,
|
||||||
int height) noexcept
|
int height) noexcept
|
||||||
{
|
{
|
||||||
const auto plan = pp::paint_renderer::plan_canvas_stroke_feedback(
|
return canvas_stroke_rasterization_plan(width, height).feedback;
|
||||||
canvas_render_device_features(),
|
|
||||||
pp::renderer::Extent2D {
|
|
||||||
.width = static_cast<std::uint32_t>(std::max(width, 0)),
|
|
||||||
.height = static_cast<std::uint32_t>(std::max(height, 0)),
|
|
||||||
});
|
|
||||||
if (plan) {
|
|
||||||
return plan.value();
|
|
||||||
}
|
|
||||||
|
|
||||||
pp::paint_renderer::CanvasStrokeFeedbackPlan fallback;
|
|
||||||
fallback.compatibility_fallback = true;
|
|
||||||
fallback.path = pp::paint_renderer::StrokeCompositePath::ping_pong_textures;
|
|
||||||
fallback.requires_auxiliary_texture = true;
|
|
||||||
return fallback;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pp::paint_renderer::CanvasBlendGatePlan draw_merge_blend_gate_plan(
|
pp::paint_renderer::CanvasBlendGatePlan draw_merge_blend_gate_plan(
|
||||||
@@ -667,8 +664,8 @@ void Canvas::stroke_draw()
|
|||||||
if (brush->m_pattern_flipx) patt_scale.x *= -1.f;
|
if (brush->m_pattern_flipx) patt_scale.x *= -1.f;
|
||||||
if (brush->m_pattern_flipy) patt_scale.y *= -1.f;
|
if (brush->m_pattern_flipy) patt_scale.y *= -1.f;
|
||||||
|
|
||||||
const auto stroke_feedback = canvas_destination_feedback_plan(m_width, m_height);
|
const auto stroke_rasterization = canvas_stroke_rasterization_plan(m_width, m_height);
|
||||||
const bool copy_stroke_destination = !stroke_feedback.reads_destination_color;
|
const bool copy_stroke_destination = stroke_rasterization.copy_stroke_destination;
|
||||||
|
|
||||||
apply_canvas_capability(blend_state(), false);
|
apply_canvas_capability(blend_state(), false);
|
||||||
ShaderManager::use(kShader::Stroke);
|
ShaderManager::use(kShader::Stroke);
|
||||||
|
|||||||
35
src/legacy_canvas_stroke_services.h
Normal file
35
src/legacy_canvas_stroke_services.h
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "paint_renderer/compositor.h"
|
||||||
|
#include "renderer_api/renderer_api.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace pp::panopainter {
|
||||||
|
|
||||||
|
[[nodiscard]] inline pp::paint_renderer::CanvasStrokeRasterizationPlan plan_legacy_canvas_stroke_rasterization(
|
||||||
|
pp::renderer::RenderDeviceFeatures features,
|
||||||
|
int width,
|
||||||
|
int height) noexcept
|
||||||
|
{
|
||||||
|
const auto plan = pp::paint_renderer::plan_canvas_stroke_rasterization(
|
||||||
|
features,
|
||||||
|
pp::renderer::Extent2D {
|
||||||
|
.width = static_cast<std::uint32_t>(std::max(width, 0)),
|
||||||
|
.height = static_cast<std::uint32_t>(std::max(height, 0)),
|
||||||
|
});
|
||||||
|
if (plan) {
|
||||||
|
return plan.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
pp::paint_renderer::CanvasStrokeRasterizationPlan fallback;
|
||||||
|
fallback.feedback.compatibility_fallback = true;
|
||||||
|
fallback.feedback.path = pp::paint_renderer::StrokeCompositePath::ping_pong_textures;
|
||||||
|
fallback.feedback.requires_auxiliary_texture = true;
|
||||||
|
fallback.copy_stroke_destination = true;
|
||||||
|
fallback.compatibility_fallback = true;
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace pp::panopainter
|
||||||
@@ -1186,6 +1186,56 @@ pp::foundation::Result<StrokeCompositePlan> plan_stroke_composite(
|
|||||||
return pp::foundation::Result<StrokeCompositePlan>::success(plan);
|
return pp::foundation::Result<StrokeCompositePlan>::success(plan);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CanvasStrokeMaterialPlan plan_canvas_stroke_material(CanvasStrokeMaterialRequest request) noexcept
|
||||||
|
{
|
||||||
|
CanvasStrokeMaterialPlan plan;
|
||||||
|
|
||||||
|
auto bind = [&plan](CanvasStrokeTextureRole role, std::uint8_t slot) noexcept {
|
||||||
|
if (plan.texture_binding_count >= plan.texture_bindings.size()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
plan.texture_bindings[plan.texture_binding_count] = CanvasStrokeTextureBindingPlan {
|
||||||
|
.role = role,
|
||||||
|
.slot = slot,
|
||||||
|
};
|
||||||
|
++plan.texture_binding_count;
|
||||||
|
};
|
||||||
|
|
||||||
|
bind(CanvasStrokeTextureRole::main_brush_tip, 0);
|
||||||
|
|
||||||
|
plan.stroke_pass.uses_destination_feedback = request.destination_feedback_needed;
|
||||||
|
if (request.destination_feedback_needed) {
|
||||||
|
bind(CanvasStrokeTextureRole::destination_feedback, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
plan.stroke_pass.uses_pattern = request.pattern_enabled && request.pattern_eachsample;
|
||||||
|
if (plan.stroke_pass.uses_pattern) {
|
||||||
|
bind(CanvasStrokeTextureRole::pattern, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
plan.stroke_pass.uses_mixer = request.wet_blend || request.mix_blend || request.noise_enabled;
|
||||||
|
if (plan.stroke_pass.uses_mixer) {
|
||||||
|
bind(CanvasStrokeTextureRole::mixer, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
plan.dual_pass.enabled = request.dual_brush_enabled;
|
||||||
|
plan.dual_pass.uses_pattern = false;
|
||||||
|
if (request.dual_brush_enabled) {
|
||||||
|
bind(CanvasStrokeTextureRole::dual_brush_tip, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
plan.composite_pass.use_dual = request.dual_brush_enabled;
|
||||||
|
plan.composite_pass.use_pattern = request.pattern_enabled && !request.pattern_eachsample;
|
||||||
|
plan.composite_pass.dual_blend_mode = request.dual_blend_mode;
|
||||||
|
plan.composite_pass.pattern_blend_mode = request.pattern_blend_mode;
|
||||||
|
plan.composite_pass.dual_alpha = request.dual_alpha;
|
||||||
|
if (plan.composite_pass.use_pattern && !plan.stroke_pass.uses_pattern) {
|
||||||
|
bind(CanvasStrokeTextureRole::pattern, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
return plan;
|
||||||
|
}
|
||||||
|
|
||||||
pp::foundation::Result<CanvasBlendGatePlan> plan_canvas_blend_gate(
|
pp::foundation::Result<CanvasBlendGatePlan> plan_canvas_blend_gate(
|
||||||
pp::renderer::RenderDeviceFeatures features,
|
pp::renderer::RenderDeviceFeatures features,
|
||||||
CanvasBlendGateRequest request) noexcept
|
CanvasBlendGateRequest request) noexcept
|
||||||
@@ -1301,6 +1351,26 @@ pp::foundation::Result<CanvasStrokeFeedbackPlan> plan_canvas_stroke_feedback(
|
|||||||
return pp::foundation::Result<CanvasStrokeFeedbackPlan>::success(fallback);
|
return pp::foundation::Result<CanvasStrokeFeedbackPlan>::success(fallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pp::foundation::Result<CanvasStrokeRasterizationPlan> plan_canvas_stroke_rasterization(
|
||||||
|
pp::renderer::RenderDeviceFeatures features,
|
||||||
|
pp::renderer::Extent2D extent) noexcept
|
||||||
|
{
|
||||||
|
const auto feedback = plan_canvas_stroke_feedback(features, extent);
|
||||||
|
if (!feedback) {
|
||||||
|
return pp::foundation::Result<CanvasStrokeRasterizationPlan>::failure(feedback.status());
|
||||||
|
}
|
||||||
|
|
||||||
|
CanvasStrokeRasterizationPlan plan;
|
||||||
|
plan.feedback = feedback.value();
|
||||||
|
plan.copy_stroke_destination = !plan.feedback.reads_destination_color;
|
||||||
|
plan.can_route_feedback_through_renderer =
|
||||||
|
plan.feedback.reads_destination_color
|
||||||
|
|| plan.feedback.requires_texture_copy
|
||||||
|
|| plan.feedback.requires_render_target_blit;
|
||||||
|
plan.compatibility_fallback = plan.feedback.compatibility_fallback;
|
||||||
|
return pp::foundation::Result<CanvasStrokeRasterizationPlan>::success(plan);
|
||||||
|
}
|
||||||
|
|
||||||
const char* stroke_composite_path_name(StrokeCompositePath path) noexcept
|
const char* stroke_composite_path_name(StrokeCompositePath path) noexcept
|
||||||
{
|
{
|
||||||
switch (path) {
|
switch (path) {
|
||||||
|
|||||||
@@ -56,6 +56,59 @@ struct StrokeCompositePlan {
|
|||||||
bool requires_explicit_transition = false;
|
bool requires_explicit_transition = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class CanvasStrokeTextureRole : std::uint8_t {
|
||||||
|
main_brush_tip,
|
||||||
|
destination_feedback,
|
||||||
|
pattern,
|
||||||
|
mixer,
|
||||||
|
dual_brush_tip,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CanvasStrokeTextureBindingPlan {
|
||||||
|
CanvasStrokeTextureRole role = CanvasStrokeTextureRole::main_brush_tip;
|
||||||
|
std::uint8_t slot = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CanvasStrokeMaterialRequest {
|
||||||
|
bool destination_feedback_needed = false;
|
||||||
|
bool pattern_enabled = false;
|
||||||
|
bool pattern_eachsample = false;
|
||||||
|
bool wet_blend = false;
|
||||||
|
bool mix_blend = false;
|
||||||
|
bool noise_enabled = false;
|
||||||
|
bool dual_brush_enabled = false;
|
||||||
|
int dual_blend_mode = 0;
|
||||||
|
int pattern_blend_mode = 0;
|
||||||
|
float dual_alpha = 1.0F;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CanvasStrokeShaderPassPlan {
|
||||||
|
bool uses_destination_feedback = false;
|
||||||
|
bool uses_pattern = false;
|
||||||
|
bool uses_mixer = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CanvasStrokeDualPassPlan {
|
||||||
|
bool enabled = false;
|
||||||
|
bool uses_pattern = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CanvasStrokeCompositePassPlan {
|
||||||
|
bool use_dual = false;
|
||||||
|
bool use_pattern = false;
|
||||||
|
int dual_blend_mode = 0;
|
||||||
|
int pattern_blend_mode = 0;
|
||||||
|
float dual_alpha = 1.0F;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CanvasStrokeMaterialPlan {
|
||||||
|
CanvasStrokeShaderPassPlan stroke_pass {};
|
||||||
|
CanvasStrokeDualPassPlan dual_pass {};
|
||||||
|
CanvasStrokeCompositePassPlan composite_pass {};
|
||||||
|
std::array<CanvasStrokeTextureBindingPlan, 5> texture_bindings {};
|
||||||
|
std::size_t texture_binding_count = 0;
|
||||||
|
};
|
||||||
|
|
||||||
struct CanvasBlendGateRequest {
|
struct CanvasBlendGateRequest {
|
||||||
pp::renderer::Extent2D extent {};
|
pp::renderer::Extent2D extent {};
|
||||||
std::span<const int> layer_blend_modes;
|
std::span<const int> layer_blend_modes;
|
||||||
@@ -89,6 +142,13 @@ struct CanvasStrokeFeedbackPlan {
|
|||||||
bool compatibility_fallback = false;
|
bool compatibility_fallback = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct CanvasStrokeRasterizationPlan {
|
||||||
|
CanvasStrokeFeedbackPlan feedback {};
|
||||||
|
bool copy_stroke_destination = true;
|
||||||
|
bool can_route_feedback_through_renderer = false;
|
||||||
|
bool compatibility_fallback = false;
|
||||||
|
};
|
||||||
|
|
||||||
struct DocumentFaceCompositeRequest {
|
struct DocumentFaceCompositeRequest {
|
||||||
const pp::document::CanvasDocument* document = nullptr;
|
const pp::document::CanvasDocument* document = nullptr;
|
||||||
std::size_t frame_index = 0;
|
std::size_t frame_index = 0;
|
||||||
@@ -318,6 +378,9 @@ export_document_animation_frames_equirectangular_pngs(
|
|||||||
pp::renderer::RenderDeviceFeatures features,
|
pp::renderer::RenderDeviceFeatures features,
|
||||||
StrokeCompositeRequest request) noexcept;
|
StrokeCompositeRequest request) noexcept;
|
||||||
|
|
||||||
|
[[nodiscard]] CanvasStrokeMaterialPlan plan_canvas_stroke_material(
|
||||||
|
CanvasStrokeMaterialRequest request) noexcept;
|
||||||
|
|
||||||
[[nodiscard]] pp::foundation::Result<CanvasBlendGatePlan> plan_canvas_blend_gate(
|
[[nodiscard]] pp::foundation::Result<CanvasBlendGatePlan> plan_canvas_blend_gate(
|
||||||
pp::renderer::RenderDeviceFeatures features,
|
pp::renderer::RenderDeviceFeatures features,
|
||||||
CanvasBlendGateRequest request) noexcept;
|
CanvasBlendGateRequest request) noexcept;
|
||||||
@@ -326,6 +389,10 @@ export_document_animation_frames_equirectangular_pngs(
|
|||||||
pp::renderer::RenderDeviceFeatures features,
|
pp::renderer::RenderDeviceFeatures features,
|
||||||
pp::renderer::Extent2D extent) noexcept;
|
pp::renderer::Extent2D extent) noexcept;
|
||||||
|
|
||||||
|
[[nodiscard]] pp::foundation::Result<CanvasStrokeRasterizationPlan> plan_canvas_stroke_rasterization(
|
||||||
|
pp::renderer::RenderDeviceFeatures features,
|
||||||
|
pp::renderer::Extent2D extent) noexcept;
|
||||||
|
|
||||||
[[nodiscard]] const char* stroke_composite_path_name(StrokeCompositePath path) noexcept;
|
[[nodiscard]] const char* stroke_composite_path_name(StrokeCompositePath path) noexcept;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ using pp::paint::Rgba;
|
|||||||
using pp::paint::StrokeBlendMode;
|
using pp::paint::StrokeBlendMode;
|
||||||
using pp::assets::decode_png_rgba8;
|
using pp::assets::decode_png_rgba8;
|
||||||
using pp::paint_renderer::CanvasBlendGateRequest;
|
using pp::paint_renderer::CanvasBlendGateRequest;
|
||||||
|
using pp::paint_renderer::CanvasStrokeMaterialRequest;
|
||||||
|
using pp::paint_renderer::CanvasStrokeTextureRole;
|
||||||
using pp::paint_renderer::DocumentFaceCompositeRequest;
|
using pp::paint_renderer::DocumentFaceCompositeRequest;
|
||||||
using pp::paint_renderer::DocumentFrameCompositeRequest;
|
using pp::paint_renderer::DocumentFrameCompositeRequest;
|
||||||
using pp::paint_renderer::LayerCompositeView;
|
using pp::paint_renderer::LayerCompositeView;
|
||||||
@@ -26,6 +28,8 @@ using pp::paint_renderer::composite_document_frame;
|
|||||||
using pp::paint_renderer::export_document_depth_pngs;
|
using pp::paint_renderer::export_document_depth_pngs;
|
||||||
using pp::paint_renderer::plan_canvas_blend_gate;
|
using pp::paint_renderer::plan_canvas_blend_gate;
|
||||||
using pp::paint_renderer::plan_canvas_stroke_feedback;
|
using pp::paint_renderer::plan_canvas_stroke_feedback;
|
||||||
|
using pp::paint_renderer::plan_canvas_stroke_material;
|
||||||
|
using pp::paint_renderer::plan_canvas_stroke_rasterization;
|
||||||
using pp::paint_renderer::plan_document_depth_export_render;
|
using pp::paint_renderer::plan_document_depth_export_render;
|
||||||
using pp::paint_renderer::plan_stroke_composite;
|
using pp::paint_renderer::plan_stroke_composite;
|
||||||
using pp::paint_renderer::stroke_composite_path_name;
|
using pp::paint_renderer::stroke_composite_path_name;
|
||||||
@@ -49,6 +53,19 @@ bool near(float a, float b)
|
|||||||
return std::fabs(a - b) < 0.0001F;
|
return std::fabs(a - b) < 0.0001F;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool has_texture_binding(
|
||||||
|
const pp::paint_renderer::CanvasStrokeMaterialPlan& plan,
|
||||||
|
CanvasStrokeTextureRole role,
|
||||||
|
std::uint8_t slot)
|
||||||
|
{
|
||||||
|
for (std::size_t i = 0; i < plan.texture_binding_count; ++i) {
|
||||||
|
if (plan.texture_bindings[i].role == role && plan.texture_bindings[i].slot == slot) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<std::uint8_t> solid_rgba8(
|
std::vector<std::uint8_t> solid_rgba8(
|
||||||
std::uint32_t width,
|
std::uint32_t width,
|
||||||
std::uint32_t height,
|
std::uint32_t height,
|
||||||
@@ -1559,6 +1576,87 @@ void rejects_bad_stroke_composite_plans(pp::tests::Harness& h)
|
|||||||
PP_EXPECT(h, stroke_composite_path_name(static_cast<StrokeCompositePath>(255)) == std::string_view("unknown"));
|
PP_EXPECT(h, stroke_composite_path_name(static_cast<StrokeCompositePath>(255)) == std::string_view("unknown"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void plans_canvas_stroke_material_passes(pp::tests::Harness& h)
|
||||||
|
{
|
||||||
|
const auto simple = plan_canvas_stroke_material(CanvasStrokeMaterialRequest {});
|
||||||
|
PP_EXPECT(h, simple.texture_binding_count == 1U);
|
||||||
|
PP_EXPECT(h, has_texture_binding(simple, CanvasStrokeTextureRole::main_brush_tip, 0));
|
||||||
|
PP_EXPECT(h, !simple.stroke_pass.uses_destination_feedback);
|
||||||
|
PP_EXPECT(h, !simple.stroke_pass.uses_pattern);
|
||||||
|
PP_EXPECT(h, !simple.stroke_pass.uses_mixer);
|
||||||
|
PP_EXPECT(h, !simple.dual_pass.enabled);
|
||||||
|
PP_EXPECT(h, !simple.composite_pass.use_dual);
|
||||||
|
PP_EXPECT(h, !simple.composite_pass.use_pattern);
|
||||||
|
|
||||||
|
const auto eachsample = plan_canvas_stroke_material(
|
||||||
|
CanvasStrokeMaterialRequest {
|
||||||
|
.destination_feedback_needed = true,
|
||||||
|
.pattern_enabled = true,
|
||||||
|
.pattern_eachsample = true,
|
||||||
|
.wet_blend = true,
|
||||||
|
.noise_enabled = true,
|
||||||
|
});
|
||||||
|
PP_EXPECT(h, eachsample.stroke_pass.uses_destination_feedback);
|
||||||
|
PP_EXPECT(h, eachsample.stroke_pass.uses_pattern);
|
||||||
|
PP_EXPECT(h, eachsample.stroke_pass.uses_mixer);
|
||||||
|
PP_EXPECT(h, !eachsample.composite_pass.use_pattern);
|
||||||
|
PP_EXPECT(h, has_texture_binding(eachsample, CanvasStrokeTextureRole::main_brush_tip, 0));
|
||||||
|
PP_EXPECT(h, has_texture_binding(eachsample, CanvasStrokeTextureRole::destination_feedback, 1));
|
||||||
|
PP_EXPECT(h, has_texture_binding(eachsample, CanvasStrokeTextureRole::pattern, 2));
|
||||||
|
PP_EXPECT(h, has_texture_binding(eachsample, CanvasStrokeTextureRole::mixer, 3));
|
||||||
|
|
||||||
|
const auto composite_pattern = plan_canvas_stroke_material(
|
||||||
|
CanvasStrokeMaterialRequest {
|
||||||
|
.pattern_enabled = true,
|
||||||
|
.pattern_eachsample = false,
|
||||||
|
.pattern_blend_mode = 6,
|
||||||
|
});
|
||||||
|
PP_EXPECT(h, !composite_pattern.stroke_pass.uses_pattern);
|
||||||
|
PP_EXPECT(h, composite_pattern.composite_pass.use_pattern);
|
||||||
|
PP_EXPECT(h, composite_pattern.composite_pass.pattern_blend_mode == 6);
|
||||||
|
PP_EXPECT(h, has_texture_binding(composite_pattern, CanvasStrokeTextureRole::pattern, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
void plans_canvas_stroke_dual_material_intent(pp::tests::Harness& h)
|
||||||
|
{
|
||||||
|
const auto dual = plan_canvas_stroke_material(
|
||||||
|
CanvasStrokeMaterialRequest {
|
||||||
|
.pattern_enabled = true,
|
||||||
|
.pattern_eachsample = true,
|
||||||
|
.dual_brush_enabled = true,
|
||||||
|
.dual_blend_mode = 3,
|
||||||
|
.pattern_blend_mode = 4,
|
||||||
|
.dual_alpha = 0.625F,
|
||||||
|
});
|
||||||
|
|
||||||
|
PP_EXPECT(h, dual.stroke_pass.uses_pattern);
|
||||||
|
PP_EXPECT(h, dual.dual_pass.enabled);
|
||||||
|
PP_EXPECT(h, !dual.dual_pass.uses_pattern);
|
||||||
|
PP_EXPECT(h, dual.composite_pass.use_dual);
|
||||||
|
PP_EXPECT(h, !dual.composite_pass.use_pattern);
|
||||||
|
PP_EXPECT(h, dual.composite_pass.dual_blend_mode == 3);
|
||||||
|
PP_EXPECT(h, dual.composite_pass.pattern_blend_mode == 4);
|
||||||
|
PP_EXPECT(h, near(dual.composite_pass.dual_alpha, 0.625F));
|
||||||
|
PP_EXPECT(h, has_texture_binding(dual, CanvasStrokeTextureRole::dual_brush_tip, 4));
|
||||||
|
|
||||||
|
const auto dual_composite_pattern = plan_canvas_stroke_material(
|
||||||
|
CanvasStrokeMaterialRequest {
|
||||||
|
.pattern_enabled = true,
|
||||||
|
.pattern_eachsample = false,
|
||||||
|
.mix_blend = true,
|
||||||
|
.dual_brush_enabled = true,
|
||||||
|
});
|
||||||
|
PP_EXPECT(h, !dual_composite_pattern.stroke_pass.uses_pattern);
|
||||||
|
PP_EXPECT(h, dual_composite_pattern.stroke_pass.uses_mixer);
|
||||||
|
PP_EXPECT(h, dual_composite_pattern.dual_pass.enabled);
|
||||||
|
PP_EXPECT(h, !dual_composite_pattern.dual_pass.uses_pattern);
|
||||||
|
PP_EXPECT(h, dual_composite_pattern.composite_pass.use_dual);
|
||||||
|
PP_EXPECT(h, dual_composite_pattern.composite_pass.use_pattern);
|
||||||
|
PP_EXPECT(h, has_texture_binding(dual_composite_pattern, CanvasStrokeTextureRole::mixer, 3));
|
||||||
|
PP_EXPECT(h, has_texture_binding(dual_composite_pattern, CanvasStrokeTextureRole::dual_brush_tip, 4));
|
||||||
|
PP_EXPECT(h, has_texture_binding(dual_composite_pattern, CanvasStrokeTextureRole::pattern, 2));
|
||||||
|
}
|
||||||
|
|
||||||
void plans_canvas_blend_gate_from_persisted_indices(pp::tests::Harness& h)
|
void plans_canvas_blend_gate_from_persisted_indices(pp::tests::Harness& h)
|
||||||
{
|
{
|
||||||
const std::vector<int> normal_layers { 0, 0, 0 };
|
const std::vector<int> normal_layers { 0, 0, 0 };
|
||||||
@@ -1738,6 +1836,49 @@ void canvas_stroke_feedback_preserves_legacy_fallback(pp::tests::Harness& h)
|
|||||||
PP_EXPECT(h, invalid.status().code == StatusCode::invalid_argument);
|
PP_EXPECT(h, invalid.status().code == StatusCode::invalid_argument);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void plans_canvas_stroke_rasterization_boundary(pp::tests::Harness& h)
|
||||||
|
{
|
||||||
|
const Extent2D extent { .width = 32, .height = 16 };
|
||||||
|
const auto fetch = plan_canvas_stroke_rasterization(
|
||||||
|
RenderDeviceFeatures { .framebuffer_fetch = true },
|
||||||
|
extent);
|
||||||
|
PP_EXPECT(h, fetch);
|
||||||
|
if (fetch) {
|
||||||
|
PP_EXPECT(h, fetch.value().feedback.path == StrokeCompositePath::framebuffer_fetch);
|
||||||
|
PP_EXPECT(h, !fetch.value().copy_stroke_destination);
|
||||||
|
PP_EXPECT(h, fetch.value().can_route_feedback_through_renderer);
|
||||||
|
PP_EXPECT(h, !fetch.value().compatibility_fallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto copy = plan_canvas_stroke_rasterization(
|
||||||
|
RenderDeviceFeatures { .texture_copy = true },
|
||||||
|
extent);
|
||||||
|
PP_EXPECT(h, copy);
|
||||||
|
if (copy) {
|
||||||
|
PP_EXPECT(h, copy.value().feedback.path == StrokeCompositePath::ping_pong_textures);
|
||||||
|
PP_EXPECT(h, copy.value().copy_stroke_destination);
|
||||||
|
PP_EXPECT(h, copy.value().can_route_feedback_through_renderer);
|
||||||
|
PP_EXPECT(h, !copy.value().compatibility_fallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto fallback = plan_canvas_stroke_rasterization(
|
||||||
|
RenderDeviceFeatures {},
|
||||||
|
extent);
|
||||||
|
PP_EXPECT(h, fallback);
|
||||||
|
if (fallback) {
|
||||||
|
PP_EXPECT(h, fallback.value().feedback.path == StrokeCompositePath::ping_pong_textures);
|
||||||
|
PP_EXPECT(h, fallback.value().copy_stroke_destination);
|
||||||
|
PP_EXPECT(h, !fallback.value().can_route_feedback_through_renderer);
|
||||||
|
PP_EXPECT(h, fallback.value().compatibility_fallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto invalid = plan_canvas_stroke_rasterization(
|
||||||
|
RenderDeviceFeatures { .texture_copy = true },
|
||||||
|
Extent2D { .width = 0, .height = 16 });
|
||||||
|
PP_EXPECT(h, !invalid.ok());
|
||||||
|
PP_EXPECT(h, invalid.status().code == StatusCode::invalid_argument);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int main()
|
int main()
|
||||||
@@ -1772,9 +1913,12 @@ int main()
|
|||||||
harness.run("detects_feedback_requirements", detects_feedback_requirements);
|
harness.run("detects_feedback_requirements", detects_feedback_requirements);
|
||||||
harness.run("plans_stroke_composite_paths", plans_stroke_composite_paths);
|
harness.run("plans_stroke_composite_paths", plans_stroke_composite_paths);
|
||||||
harness.run("rejects_bad_stroke_composite_plans", rejects_bad_stroke_composite_plans);
|
harness.run("rejects_bad_stroke_composite_plans", rejects_bad_stroke_composite_plans);
|
||||||
|
harness.run("plans_canvas_stroke_material_passes", plans_canvas_stroke_material_passes);
|
||||||
|
harness.run("plans_canvas_stroke_dual_material_intent", plans_canvas_stroke_dual_material_intent);
|
||||||
harness.run("plans_canvas_blend_gate_from_persisted_indices", plans_canvas_blend_gate_from_persisted_indices);
|
harness.run("plans_canvas_blend_gate_from_persisted_indices", plans_canvas_blend_gate_from_persisted_indices);
|
||||||
harness.run("canvas_blend_gate_preserves_legacy_fallbacks", canvas_blend_gate_preserves_legacy_fallbacks);
|
harness.run("canvas_blend_gate_preserves_legacy_fallbacks", canvas_blend_gate_preserves_legacy_fallbacks);
|
||||||
harness.run("plans_canvas_stroke_feedback_paths", plans_canvas_stroke_feedback_paths);
|
harness.run("plans_canvas_stroke_feedback_paths", plans_canvas_stroke_feedback_paths);
|
||||||
harness.run("canvas_stroke_feedback_preserves_legacy_fallback", canvas_stroke_feedback_preserves_legacy_fallback);
|
harness.run("canvas_stroke_feedback_preserves_legacy_fallback", canvas_stroke_feedback_preserves_legacy_fallback);
|
||||||
|
harness.run("plans_canvas_stroke_rasterization_boundary", plans_canvas_stroke_rasterization_boundary);
|
||||||
return harness.finish();
|
return harness.finish();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user