Add stroke composite feedback planner

This commit is contained in:
2026-06-03 18:07:08 +02:00
parent 94a6877e7c
commit 2ec11e5099
9 changed files with 531 additions and 3 deletions

View File

@@ -2,14 +2,24 @@
#include "test_harness.h"
#include <cmath>
#include <string_view>
#include <vector>
using pp::foundation::StatusCode;
using pp::paint::BlendMode;
using pp::paint::Rgba;
using pp::paint::StrokeBlendMode;
using pp::paint_renderer::LayerCompositeView;
using pp::paint_renderer::StrokeCompositePath;
using pp::paint_renderer::StrokeCompositeRequest;
using pp::paint_renderer::composite_layer;
using pp::paint_renderer::plan_stroke_composite;
using pp::paint_renderer::stroke_composite_path_name;
using pp::paint_renderer::stroke_composite_requires_feedback;
using pp::renderer::Extent2D;
using pp::renderer::RenderDeviceFeatures;
using pp::renderer::TextureFormat;
using pp::renderer::TextureUsage;
namespace {
@@ -97,6 +107,158 @@ void rejects_invalid_sizes_and_opacity(pp::tests::Harness& h)
PP_EXPECT(h, bad_extent.code == StatusCode::invalid_argument);
}
void detects_feedback_requirements(pp::tests::Harness& h)
{
PP_EXPECT(h, !stroke_composite_requires_feedback(
BlendMode::normal,
StrokeBlendMode::normal,
false,
false));
PP_EXPECT(h, stroke_composite_requires_feedback(
BlendMode::multiply,
StrokeBlendMode::normal,
false,
false));
PP_EXPECT(h, stroke_composite_requires_feedback(
BlendMode::normal,
StrokeBlendMode::overlay,
false,
false));
PP_EXPECT(h, stroke_composite_requires_feedback(
BlendMode::normal,
StrokeBlendMode::normal,
true,
false));
PP_EXPECT(h, stroke_composite_requires_feedback(
BlendMode::normal,
StrokeBlendMode::normal,
false,
true));
}
void plans_stroke_composite_paths(pp::tests::Harness& h)
{
const StrokeCompositeRequest simple {
.extent = Extent2D { .width = 64, .height = 32 },
.target_usage = TextureUsage::render_target,
};
const auto fixed = plan_stroke_composite(
RenderDeviceFeatures {},
simple);
PP_EXPECT(h, fixed);
if (fixed) {
PP_EXPECT(h, fixed.value().path == StrokeCompositePath::fixed_function_blend);
PP_EXPECT(h, !fixed.value().complex_blend);
PP_EXPECT(h, !fixed.value().reads_destination_color);
PP_EXPECT(h, fixed.value().target_bytes == 8192U);
PP_EXPECT(h, fixed.value().estimated_working_bytes == 8192U);
}
const StrokeCompositeRequest complex_fetch {
.extent = Extent2D { .width = 32, .height = 16 },
.target_usage = TextureUsage::render_target,
.stroke_blend_mode = StrokeBlendMode::height,
};
const auto fetch = plan_stroke_composite(
RenderDeviceFeatures {
.framebuffer_fetch = true,
.explicit_texture_transitions = true,
},
complex_fetch);
PP_EXPECT(h, fetch);
if (fetch) {
PP_EXPECT(h, fetch.value().path == StrokeCompositePath::framebuffer_fetch);
PP_EXPECT(h, fetch.value().complex_blend);
PP_EXPECT(h, fetch.value().reads_destination_color);
PP_EXPECT(h, !fetch.value().requires_auxiliary_texture);
PP_EXPECT(h, fetch.value().requires_explicit_transition);
PP_EXPECT(h, fetch.value().target_bytes == 2048U);
}
const StrokeCompositeRequest complex_copy {
.extent = Extent2D { .width = 32, .height = 16 },
.layer_blend_mode = BlendMode::overlay,
.dual_brush_blend = true,
};
const auto copy = plan_stroke_composite(
RenderDeviceFeatures { .texture_copy = true },
complex_copy);
PP_EXPECT(h, copy);
if (copy) {
PP_EXPECT(h, copy.value().path == StrokeCompositePath::ping_pong_textures);
PP_EXPECT(h, copy.value().requires_auxiliary_texture);
PP_EXPECT(h, copy.value().requires_texture_copy);
PP_EXPECT(h, !copy.value().requires_render_target_blit);
PP_EXPECT(h, copy.value().target_bytes == 2048U);
PP_EXPECT(h, copy.value().auxiliary_bytes == 2048U);
PP_EXPECT(h, copy.value().estimated_working_bytes == 4096U);
}
const auto blit = plan_stroke_composite(
RenderDeviceFeatures { .render_target_blit = true },
StrokeCompositeRequest {
.extent = Extent2D { .width = 32, .height = 16 },
.pattern_blend = true,
});
PP_EXPECT(h, blit);
if (blit) {
PP_EXPECT(h, blit.value().path == StrokeCompositePath::ping_pong_textures);
PP_EXPECT(h, !blit.value().requires_texture_copy);
PP_EXPECT(h, blit.value().requires_render_target_blit);
}
}
void rejects_bad_stroke_composite_plans(pp::tests::Harness& h)
{
const auto unsupported = plan_stroke_composite(
RenderDeviceFeatures {},
StrokeCompositeRequest {
.extent = Extent2D { .width = 32, .height = 16 },
.layer_blend_mode = BlendMode::multiply,
});
const auto missing_usage = plan_stroke_composite(
RenderDeviceFeatures { .texture_copy = true },
StrokeCompositeRequest {
.extent = Extent2D { .width = 32, .height = 16 },
.target_usage = TextureUsage::render_target,
.layer_blend_mode = BlendMode::multiply,
});
const auto depth = plan_stroke_composite(
RenderDeviceFeatures { .texture_copy = true },
StrokeCompositeRequest {
.extent = Extent2D { .width = 32, .height = 16 },
.target_format = TextureFormat::depth24_stencil8,
.layer_blend_mode = BlendMode::multiply,
});
const auto bad_blend = plan_stroke_composite(
RenderDeviceFeatures { .texture_copy = true },
StrokeCompositeRequest {
.extent = Extent2D { .width = 32, .height = 16 },
.layer_blend_mode = static_cast<BlendMode>(255),
});
const auto bad_stroke_blend = plan_stroke_composite(
RenderDeviceFeatures { .texture_copy = true },
StrokeCompositeRequest {
.extent = Extent2D { .width = 32, .height = 16 },
.stroke_blend_mode = static_cast<StrokeBlendMode>(255),
});
PP_EXPECT(h, !unsupported.ok());
PP_EXPECT(h, unsupported.status().code == StatusCode::invalid_argument);
PP_EXPECT(h, !missing_usage.ok());
PP_EXPECT(h, missing_usage.status().code == StatusCode::invalid_argument);
PP_EXPECT(h, !depth.ok());
PP_EXPECT(h, depth.status().code == StatusCode::invalid_argument);
PP_EXPECT(h, !bad_blend.ok());
PP_EXPECT(h, bad_blend.status().code == StatusCode::invalid_argument);
PP_EXPECT(h, !bad_stroke_blend.ok());
PP_EXPECT(h, bad_stroke_blend.status().code == StatusCode::invalid_argument);
PP_EXPECT(h, stroke_composite_path_name(StrokeCompositePath::fixed_function_blend) == std::string_view("fixed_function_blend"));
PP_EXPECT(h, stroke_composite_path_name(StrokeCompositePath::framebuffer_fetch) == std::string_view("framebuffer_fetch"));
PP_EXPECT(h, stroke_composite_path_name(StrokeCompositePath::ping_pong_textures) == std::string_view("ping_pong_textures"));
PP_EXPECT(h, stroke_composite_path_name(static_cast<StrokeCompositePath>(255)) == std::string_view("unknown"));
}
}
int main()
@@ -105,5 +267,8 @@ int main()
harness.run("composites_visible_layer_with_opacity", composites_visible_layer_with_opacity);
harness.run("invisible_and_zero_opacity_layers_are_noops", invisible_and_zero_opacity_layers_are_noops);
harness.run("rejects_invalid_sizes_and_opacity", rejects_invalid_sizes_and_opacity);
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);
return harness.finish();
}