Plan live stroke rasterization boundaries

This commit is contained in:
2026-06-12 22:13:21 +02:00
parent 57c6128d11
commit 81726d30a5
8 changed files with 348 additions and 22 deletions

View File

@@ -15,6 +15,8 @@ using pp::paint::Rgba;
using pp::paint::StrokeBlendMode;
using pp::assets::decode_png_rgba8;
using pp::paint_renderer::CanvasBlendGateRequest;
using pp::paint_renderer::CanvasStrokeMaterialRequest;
using pp::paint_renderer::CanvasStrokeTextureRole;
using pp::paint_renderer::DocumentFaceCompositeRequest;
using pp::paint_renderer::DocumentFrameCompositeRequest;
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::plan_canvas_blend_gate;
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_stroke_composite;
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;
}
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::uint32_t width,
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"));
}
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)
{
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);
}
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()
@@ -1772,9 +1913,12 @@ int main()
harness.run("detects_feedback_requirements", detects_feedback_requirements);
harness.run("plans_stroke_composite_paths", plans_stroke_composite_paths);
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("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("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();
}