Add renderer and package readiness validation gates
This commit is contained in:
@@ -20,6 +20,16 @@ add_test(NAME panopainter_retained_platform_cmake_self_test
|
||||
set_tests_properties(panopainter_retained_platform_cmake_self_test PROPERTIES
|
||||
LABELS "tooling;desktop-fast")
|
||||
|
||||
add_test(NAME panopainter_component_boundary_self_test
|
||||
COMMAND "${Python3_EXECUTABLE}" "${PROJECT_SOURCE_DIR}/scripts/dev/check_component_boundaries.py")
|
||||
set_tests_properties(panopainter_component_boundary_self_test PROPERTIES
|
||||
LABELS "tooling;desktop-fast")
|
||||
|
||||
add_test(NAME panopainter_renderer_api_contract_self_test
|
||||
COMMAND "${Python3_EXECUTABLE}" "${PROJECT_SOURCE_DIR}/scripts/dev/check_renderer_api_contract.py")
|
||||
set_tests_properties(panopainter_renderer_api_contract_self_test PROPERTIES
|
||||
LABELS "tooling;desktop-fast")
|
||||
|
||||
add_library(pp_test_harness INTERFACE)
|
||||
target_include_directories(pp_test_harness INTERFACE
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}")
|
||||
@@ -78,6 +88,16 @@ add_test(NAME pp_foundation_task_queue_tests COMMAND pp_foundation_task_queue_te
|
||||
set_tests_properties(pp_foundation_task_queue_tests PROPERTIES
|
||||
LABELS "foundation;desktop-fast")
|
||||
|
||||
add_executable(pp_foundation_task_queue_stress_tests
|
||||
foundation/task_queue_stress_tests.cpp)
|
||||
target_link_libraries(pp_foundation_task_queue_stress_tests PRIVATE
|
||||
pp_foundation
|
||||
pp_test_harness)
|
||||
|
||||
add_test(NAME pp_foundation_task_queue_stress_tests COMMAND pp_foundation_task_queue_stress_tests)
|
||||
set_tests_properties(pp_foundation_task_queue_stress_tests PROPERTIES
|
||||
LABELS "foundation;stress")
|
||||
|
||||
add_executable(pp_foundation_trace_tests
|
||||
foundation/trace_tests.cpp)
|
||||
target_link_libraries(pp_foundation_trace_tests PRIVATE
|
||||
@@ -226,7 +246,7 @@ target_link_libraries(pp_renderer_api_tests PRIVATE
|
||||
|
||||
add_test(NAME pp_renderer_api_tests COMMAND pp_renderer_api_tests)
|
||||
set_tests_properties(pp_renderer_api_tests PROPERTIES
|
||||
LABELS "renderer;desktop-fast")
|
||||
LABELS "renderer;renderer-conformance;desktop-fast")
|
||||
|
||||
if(TARGET pp_renderer_gl)
|
||||
add_executable(pp_renderer_gl_capabilities_tests
|
||||
@@ -237,7 +257,7 @@ if(TARGET pp_renderer_gl)
|
||||
|
||||
add_test(NAME pp_renderer_gl_capabilities_tests COMMAND pp_renderer_gl_capabilities_tests)
|
||||
set_tests_properties(pp_renderer_gl_capabilities_tests PROPERTIES
|
||||
LABELS "renderer;desktop-fast")
|
||||
LABELS "renderer;renderer-conformance;desktop-fast")
|
||||
|
||||
add_executable(pp_renderer_gl_command_plan_tests
|
||||
renderer_gl/command_plan_tests.cpp)
|
||||
@@ -247,7 +267,7 @@ if(TARGET pp_renderer_gl)
|
||||
|
||||
add_test(NAME pp_renderer_gl_command_plan_tests COMMAND pp_renderer_gl_command_plan_tests)
|
||||
set_tests_properties(pp_renderer_gl_command_plan_tests PROPERTIES
|
||||
LABELS "renderer;desktop-fast")
|
||||
LABELS "renderer;renderer-conformance;desktop-fast")
|
||||
|
||||
add_executable(pp_renderer_gl_gpu_readback_tests
|
||||
renderer_gl/gpu_readback_tests.cpp)
|
||||
@@ -263,10 +283,15 @@ if(TARGET pp_renderer_gl)
|
||||
|
||||
add_test(NAME pp_renderer_gl_gpu_readback_tests COMMAND $<TARGET_FILE:pp_renderer_gl_gpu_readback_tests>)
|
||||
set_tests_properties(pp_renderer_gl_gpu_readback_tests PROPERTIES
|
||||
LABELS "renderer;gpu"
|
||||
LABELS "renderer;renderer-conformance;gpu"
|
||||
SKIP_REGULAR_EXPRESSION "\\[skip\\]")
|
||||
endif()
|
||||
|
||||
add_test(NAME panopainter_renderer_conformance_matrix_self_test
|
||||
COMMAND "${Python3_EXECUTABLE}" "${PROJECT_SOURCE_DIR}/scripts/dev/check_renderer_conformance_matrix.py")
|
||||
set_tests_properties(panopainter_renderer_conformance_matrix_self_test PROPERTIES
|
||||
LABELS "tooling;desktop-fast")
|
||||
|
||||
add_executable(pp_paint_renderer_compositor_tests
|
||||
paint_renderer/compositor_tests.cpp)
|
||||
target_link_libraries(pp_paint_renderer_compositor_tests PRIVATE
|
||||
@@ -670,6 +695,16 @@ add_test(NAME pp_app_core_app_thread_tests COMMAND pp_app_core_app_thread_tests)
|
||||
set_tests_properties(pp_app_core_app_thread_tests PROPERTIES
|
||||
LABELS "app;desktop-fast;fuzz")
|
||||
|
||||
add_executable(pp_app_core_app_thread_stress_tests
|
||||
app_core/app_thread_stress_tests.cpp)
|
||||
target_link_libraries(pp_app_core_app_thread_stress_tests PRIVATE
|
||||
pp_app_core
|
||||
pp_test_harness)
|
||||
|
||||
add_test(NAME pp_app_core_app_thread_stress_tests COMMAND pp_app_core_app_thread_stress_tests)
|
||||
set_tests_properties(pp_app_core_app_thread_stress_tests PROPERTIES
|
||||
LABELS "app;stress")
|
||||
|
||||
add_executable(pp_app_core_app_input_tests
|
||||
app_core/app_input_tests.cpp)
|
||||
target_link_libraries(pp_app_core_app_input_tests PRIVATE
|
||||
@@ -1302,6 +1337,24 @@ if(TARGET pano_cli)
|
||||
LABELS "app;integration;desktop-fast"
|
||||
PASS_REGULAR_EXPRESSION "\"command\":\"plan-app-thread\".*\"kind\":\"dispatch\".*\"queueTask\":true.*\"removeMatchingUniqueTask\":true.*\"notifyWorker\":true.*\"waitForCompletion\":true")
|
||||
|
||||
add_test(NAME pano_cli_plan_app_thread_dispatch_rejects_unsafe
|
||||
COMMAND pano_cli plan-app-thread --kind dispatch --require-ui-thread)
|
||||
set_tests_properties(pano_cli_plan_app_thread_dispatch_rejects_unsafe PROPERTIES
|
||||
LABELS "app;integration;desktop-fast"
|
||||
PASS_REGULAR_EXPRESSION "\"command\":\"plan-app-thread\".*\"kind\":\"dispatch\".*\"queueTask\":false.*\"rejectUnsafeCrossThreadDispatch\":true")
|
||||
|
||||
add_test(NAME pano_cli_plan_app_thread_dispatch_unique_no_wait_when_worker_stopped
|
||||
COMMAND pano_cli plan-app-thread --kind dispatch --worker-stopped --unique --queued-tasks 2 --wait)
|
||||
set_tests_properties(pano_cli_plan_app_thread_dispatch_unique_no_wait_when_worker_stopped PROPERTIES
|
||||
LABELS "app;integration;desktop-fast"
|
||||
PASS_REGULAR_EXPRESSION "\"command\":\"plan-app-thread\".*\"kind\":\"dispatch\".*\"queueTask\":true.*\"removeMatchingUniqueTask\":true.*\"notifyWorker\":true.*\"waitForCompletion\":false")
|
||||
|
||||
add_test(NAME pano_cli_plan_app_thread_dispatch_no_unique_no_removal
|
||||
COMMAND pano_cli plan-app-thread --kind dispatch --unique --queued-tasks 0)
|
||||
set_tests_properties(pano_cli_plan_app_thread_dispatch_no_unique_no_removal PROPERTIES
|
||||
LABELS "app;integration;desktop-fast"
|
||||
PASS_REGULAR_EXPRESSION "\"command\":\"plan-app-thread\".*\"kind\":\"dispatch\".*\"queueTask\":true.*\"removeMatchingUniqueTask\":false.*\"notifyWorker\":true")
|
||||
|
||||
add_test(NAME pano_cli_plan_app_thread_ui_loop_smoke
|
||||
COMMAND pano_cli plan-app-thread --kind ui-loop --dt 0.25 --frame-accumulator 0.5 --fps-accumulator 0.9 --reload-accumulator 0.9 --rendered-frames 7 --live-reload)
|
||||
set_tests_properties(pano_cli_plan_app_thread_ui_loop_smoke PROPERTIES
|
||||
|
||||
88
tests/app_core/app_thread_stress_tests.cpp
Normal file
88
tests/app_core/app_thread_stress_tests.cpp
Normal file
@@ -0,0 +1,88 @@
|
||||
#include "app_core/app_thread.h"
|
||||
#include "test_harness.h"
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
namespace {
|
||||
|
||||
void dispatch_plan_is_consistent_under_stress(pp::tests::Harness& harness)
|
||||
{
|
||||
constexpr std::size_t queued_sizes[] = { 0U, 1U, 4U, 8U, 16U };
|
||||
|
||||
for (const auto queued_task_count : queued_sizes) {
|
||||
const auto plan = pp::app::plan_app_task_dispatch(
|
||||
false,
|
||||
true,
|
||||
queued_task_count,
|
||||
true,
|
||||
true,
|
||||
true);
|
||||
|
||||
PP_EXPECT(harness, !plan.execute_immediately);
|
||||
PP_EXPECT(harness, plan.queue_task);
|
||||
PP_EXPECT(harness, plan.remove_matching_unique_task == (queued_task_count > 0U));
|
||||
PP_EXPECT(harness, plan.notify_worker);
|
||||
PP_EXPECT(harness, plan.wait_for_completion);
|
||||
PP_EXPECT(harness, plan.request_redraw);
|
||||
PP_EXPECT(harness, !plan.reject_unsafe_cross_thread_dispatch);
|
||||
}
|
||||
}
|
||||
|
||||
void dispatch_plan_handles_target_thread_with_rejection_flag(pp::tests::Harness& harness)
|
||||
{
|
||||
constexpr std::size_t queued_sizes[] = { 0U, 5U };
|
||||
for (const auto queued_task_count : queued_sizes) {
|
||||
const auto plan = pp::app::plan_app_task_dispatch(
|
||||
true,
|
||||
true,
|
||||
queued_task_count,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true);
|
||||
|
||||
PP_EXPECT(harness, plan.execute_immediately);
|
||||
PP_EXPECT(harness, !plan.queue_task);
|
||||
PP_EXPECT(harness, !plan.remove_matching_unique_task);
|
||||
PP_EXPECT(harness, !plan.notify_worker);
|
||||
PP_EXPECT(harness, !plan.wait_for_completion);
|
||||
PP_EXPECT(harness, !plan.request_redraw);
|
||||
PP_EXPECT(harness, !plan.reject_unsafe_cross_thread_dispatch);
|
||||
}
|
||||
}
|
||||
|
||||
void dispatch_plan_rejects_cross_thread_mutations_under_pressure(pp::tests::Harness& harness)
|
||||
{
|
||||
constexpr std::size_t queued_sizes[] = { 0U, 4U, 8U };
|
||||
|
||||
for (const auto queued_task_count : queued_sizes) {
|
||||
const auto plan = pp::app::plan_app_task_dispatch(
|
||||
false,
|
||||
true,
|
||||
queued_task_count,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true);
|
||||
|
||||
PP_EXPECT(harness, !plan.execute_immediately);
|
||||
PP_EXPECT(harness, !plan.queue_task);
|
||||
PP_EXPECT(harness, !plan.remove_matching_unique_task);
|
||||
PP_EXPECT(harness, !plan.notify_worker);
|
||||
PP_EXPECT(harness, !plan.wait_for_completion);
|
||||
PP_EXPECT(harness, !plan.request_redraw);
|
||||
PP_EXPECT(harness, plan.reject_unsafe_cross_thread_dispatch);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main()
|
||||
{
|
||||
pp::tests::Harness harness;
|
||||
harness.run("dispatch plan is consistent across queue sizes", dispatch_plan_is_consistent_under_stress);
|
||||
harness.run("dispatch plan ignores reject flag when already on target thread", dispatch_plan_handles_target_thread_with_rejection_flag);
|
||||
harness.run("dispatch plan rejects unsafe cross-thread work under load", dispatch_plan_rejects_cross_thread_mutations_under_pressure);
|
||||
return harness.finish();
|
||||
}
|
||||
|
||||
@@ -38,6 +38,19 @@ void task_dispatch_does_not_wait_for_stopped_worker(pp::tests::Harness& harness)
|
||||
PP_EXPECT(harness, !plan.wait_for_completion);
|
||||
}
|
||||
|
||||
void task_dispatch_rejects_unsafe_cross_thread_mutations(pp::tests::Harness& harness)
|
||||
{
|
||||
const auto plan = pp::app::plan_app_task_dispatch(false, true, 2, true, true, false, true);
|
||||
|
||||
PP_EXPECT(harness, !plan.execute_immediately);
|
||||
PP_EXPECT(harness, !plan.queue_task);
|
||||
PP_EXPECT(harness, !plan.remove_matching_unique_task);
|
||||
PP_EXPECT(harness, !plan.notify_worker);
|
||||
PP_EXPECT(harness, !plan.wait_for_completion);
|
||||
PP_EXPECT(harness, !plan.request_redraw);
|
||||
PP_EXPECT(harness, plan.reject_unsafe_cross_thread_dispatch);
|
||||
}
|
||||
|
||||
void render_queue_drain_wraps_non_empty_work_in_context(pp::tests::Harness& harness)
|
||||
{
|
||||
const auto empty = pp::app::plan_app_render_queue_drain(0);
|
||||
@@ -124,6 +137,7 @@ int main()
|
||||
harness.run("task dispatch executes immediately on target thread", task_dispatch_executes_immediately_on_target_thread);
|
||||
harness.run("task dispatch queues unique work and waits for running worker", task_dispatch_queues_unique_work_and_waits_for_running_worker);
|
||||
harness.run("task dispatch does not wait for stopped worker", task_dispatch_does_not_wait_for_stopped_worker);
|
||||
harness.run("task dispatch can reject unsafe cross-thread mutations", task_dispatch_rejects_unsafe_cross_thread_mutations);
|
||||
harness.run("render queue drain wraps non empty work in context", render_queue_drain_wraps_non_empty_work_in_context);
|
||||
harness.run("ui thread tick runs tasks and schedules redraw", ui_thread_tick_runs_tasks_and_schedules_redraw);
|
||||
harness.run("ui loop timers report fps and reload on threshold", ui_loop_timers_report_fps_and_reload_on_threshold);
|
||||
|
||||
162
tests/foundation/task_queue_stress_tests.cpp
Normal file
162
tests/foundation/task_queue_stress_tests.cpp
Normal file
@@ -0,0 +1,162 @@
|
||||
#include "foundation/task_queue.h"
|
||||
#include "test_harness.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
struct NestedPushPayload {
|
||||
pp::foundation::TaskQueue* queue = nullptr;
|
||||
std::vector<int>* order = nullptr;
|
||||
};
|
||||
|
||||
struct MarkerPayload {
|
||||
std::vector<int>* order = nullptr;
|
||||
int marker = 0;
|
||||
};
|
||||
|
||||
struct HandoffPayload {
|
||||
pp::foundation::TaskQueue* ui_queue = nullptr;
|
||||
std::vector<int>* order = nullptr;
|
||||
int worker_marker = 0;
|
||||
MarkerPayload* ui_marker = nullptr;
|
||||
};
|
||||
|
||||
void nested_push_task(void* user_data) noexcept
|
||||
{
|
||||
auto* payload = static_cast<NestedPushPayload*>(user_data);
|
||||
payload->order->push_back(1);
|
||||
payload->queue->push(pp::foundation::TaskItem { .callback = [](void* callback_data) noexcept {
|
||||
auto* inner = static_cast<std::vector<int>*>(callback_data);
|
||||
inner->push_back(2);
|
||||
}, .user_data = payload->order, .id = 2 });
|
||||
}
|
||||
|
||||
void record_marker_task(void* user_data) noexcept
|
||||
{
|
||||
auto* payload = static_cast<MarkerPayload*>(user_data);
|
||||
payload->order->push_back(payload->marker);
|
||||
}
|
||||
|
||||
void handoff_to_ui_queue_task(void* user_data) noexcept
|
||||
{
|
||||
const auto payload = static_cast<HandoffPayload*>(user_data);
|
||||
payload->order->push_back(payload->worker_marker);
|
||||
payload->ui_queue->push(pp::foundation::TaskItem {
|
||||
.callback = record_marker_task,
|
||||
.user_data = payload->ui_marker,
|
||||
.id = static_cast<std::uint64_t>(payload->worker_marker),
|
||||
});
|
||||
}
|
||||
|
||||
void runs_nested_push_tasks_deterministically(pp::tests::Harness& harness)
|
||||
{
|
||||
pp::foundation::TaskQueue queue;
|
||||
std::vector<int> order;
|
||||
NestedPushPayload payload { .queue = &queue, .order = &order };
|
||||
|
||||
PP_EXPECT(harness, queue.push(pp::foundation::TaskItem {
|
||||
.callback = nested_push_task,
|
||||
.user_data = &payload,
|
||||
.id = 1
|
||||
}).ok());
|
||||
|
||||
PP_EXPECT(harness, queue.run_all() == 2U);
|
||||
PP_EXPECT(harness, order.size() == 2U);
|
||||
PP_EXPECT(harness, order[0] == 1);
|
||||
PP_EXPECT(harness, order[1] == 2);
|
||||
PP_EXPECT(harness, queue.empty());
|
||||
}
|
||||
|
||||
void worker_to_ui_queue_handoff_is_ordered(pp::tests::Harness& harness)
|
||||
{
|
||||
pp::foundation::TaskQueue worker_queue;
|
||||
pp::foundation::TaskQueue ui_queue;
|
||||
std::vector<int> order;
|
||||
|
||||
MarkerPayload marker_payloads[] = {
|
||||
{ .order = &order, .marker = 2 },
|
||||
{ .order = &order, .marker = 3 },
|
||||
};
|
||||
HandoffPayload worker_payloads[] = {
|
||||
{
|
||||
.ui_queue = &ui_queue,
|
||||
.order = &order,
|
||||
.worker_marker = 1,
|
||||
.ui_marker = &marker_payloads[0],
|
||||
},
|
||||
{
|
||||
.ui_queue = &ui_queue,
|
||||
.order = &order,
|
||||
.worker_marker = 4,
|
||||
.ui_marker = &marker_payloads[1],
|
||||
},
|
||||
};
|
||||
|
||||
PP_EXPECT(harness, worker_queue.push(pp::foundation::TaskItem {
|
||||
.callback = handoff_to_ui_queue_task,
|
||||
.user_data = &worker_payloads[0],
|
||||
.id = 1,
|
||||
}).ok());
|
||||
PP_EXPECT(harness, worker_queue.push(pp::foundation::TaskItem {
|
||||
.callback = handoff_to_ui_queue_task,
|
||||
.user_data = &worker_payloads[1],
|
||||
.id = 2,
|
||||
}).ok());
|
||||
|
||||
PP_EXPECT(harness, worker_queue.run_all() == 2U);
|
||||
PP_EXPECT(harness, order.size() == 2U);
|
||||
PP_EXPECT(harness, order[0] == 1);
|
||||
PP_EXPECT(harness, order[1] == 4);
|
||||
PP_EXPECT(harness, worker_queue.empty());
|
||||
PP_EXPECT(harness, !ui_queue.empty());
|
||||
|
||||
PP_EXPECT(harness, ui_queue.run_all() == 2U);
|
||||
PP_EXPECT(harness, order.size() == 4U);
|
||||
PP_EXPECT(harness, order[2] == 2);
|
||||
PP_EXPECT(harness, order[3] == 3);
|
||||
PP_EXPECT(harness, ui_queue.empty());
|
||||
}
|
||||
|
||||
void stress_batch_push_and_overflow_reported(pp::tests::Harness& harness)
|
||||
{
|
||||
constexpr std::size_t batch_size = 32U;
|
||||
pp::foundation::TaskQueue queue(batch_size);
|
||||
|
||||
int counter = 0;
|
||||
for (std::size_t i = 0U; i < batch_size; ++i)
|
||||
{
|
||||
PP_EXPECT(harness, queue.push(pp::foundation::TaskItem {
|
||||
.callback = [](void* user_data) noexcept
|
||||
{
|
||||
++*static_cast<int*>(user_data);
|
||||
},
|
||||
.user_data = &counter,
|
||||
.id = static_cast<std::uint64_t>(i + 1),
|
||||
}).ok());
|
||||
}
|
||||
|
||||
const auto overflow = queue.push(pp::foundation::TaskItem {
|
||||
.callback = [](void* user_data) noexcept
|
||||
{
|
||||
++*static_cast<int*>(user_data);
|
||||
},
|
||||
.user_data = &counter,
|
||||
.id = batch_size + 1U,
|
||||
});
|
||||
PP_EXPECT(harness, !overflow.ok());
|
||||
PP_EXPECT(harness, queue.run_all() == batch_size);
|
||||
PP_EXPECT(harness, counter == static_cast<int>(batch_size));
|
||||
PP_EXPECT(harness, queue.empty());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
pp::tests::Harness harness;
|
||||
harness.run("nested pushes are executed deterministically", runs_nested_push_tasks_deterministically);
|
||||
harness.run("worker to UI queue handoff preserves order", worker_to_ui_queue_handoff_is_ordered);
|
||||
harness.run("stress batch push reports overflow and drains deterministically", stress_batch_push_and_overflow_reported);
|
||||
return harness.finish();
|
||||
}
|
||||
@@ -2873,91 +2873,81 @@ void legacy_node_stroke_preview_mix_pass_adapter_preserves_retained_material_and
|
||||
void legacy_node_stroke_preview_mix_executor_preserves_setup_and_draw_order(pp::tests::Harness& h)
|
||||
{
|
||||
std::vector<std::string> steps;
|
||||
pp::panopainter::LegacyNodeStrokePreviewMixPassPlan::ShaderPlan observed_shader {};
|
||||
int observed_plane_index = -1;
|
||||
|
||||
const bool ok = pp::panopainter::execute_legacy_node_stroke_preview_mix_pass(
|
||||
pp::panopainter::LegacyNodeStrokePreviewMixExecutionRequest {
|
||||
.shader = pp::panopainter::LegacyNodeStrokePreviewMixPassPlan::ShaderPlan {
|
||||
.resolution = glm::vec2(128.0F, 64.0F),
|
||||
.pattern_scale = glm::vec2(-0.25F, 0.25F),
|
||||
.pattern_invert = 1.0F,
|
||||
.pattern_brightness = 0.6F,
|
||||
.pattern_contrast = 0.8F,
|
||||
.pattern_depth = 0.9F,
|
||||
.pattern_blend_mode = 7,
|
||||
.pattern_offset = glm::vec2(0.5F, 0.5F),
|
||||
.blend_mode = 5,
|
||||
.use_dual = true,
|
||||
.dual_blend_mode = 9,
|
||||
.dual_alpha = 0.4F,
|
||||
.use_pattern = true,
|
||||
const std::array planes {
|
||||
pp::panopainter::LegacyCanvasStrokeMixPassPlane {
|
||||
.index = 4,
|
||||
.visible = true,
|
||||
.has_target = true,
|
||||
.opacity = 1.0f,
|
||||
.mvp = glm::mat4(1.0f),
|
||||
},
|
||||
};
|
||||
|
||||
const auto ok = pp::panopainter::execute_legacy_canvas_stroke_mix_pass(
|
||||
pp::panopainter::LegacyCanvasStrokeMixPassRequest {
|
||||
.context = "NodeStrokePreview::stroke_draw_mix",
|
||||
.resolution = glm::vec2(128.0F, 64.0F),
|
||||
.planes = planes,
|
||||
.bind_mix_samplers = [&] {
|
||||
steps.emplace_back("bind-mix");
|
||||
},
|
||||
.mixer_width = 128,
|
||||
.mixer_height = 64,
|
||||
.scissor_x = 11,
|
||||
.scissor_y = 12,
|
||||
.scissor_width = 13,
|
||||
.scissor_height = 14,
|
||||
.save_state = [&] {
|
||||
steps.emplace_back("save");
|
||||
.unbind_mix_samplers = [&] {
|
||||
steps.emplace_back("unbind-mix");
|
||||
},
|
||||
.setup_mix_shader = [&](const auto& shader) {
|
||||
observed_shader = shader;
|
||||
steps.emplace_back("setup");
|
||||
},
|
||||
.bind_mixer_framebuffer = [&] {
|
||||
steps.emplace_back("bind-framebuffer");
|
||||
},
|
||||
.configure_mix_target_state = [&](int width, int height, int x, int y, int scissor_width, int scissor_height) {
|
||||
.setup_plane_shader = [&](int index, const glm::mat4& mvp) {
|
||||
observed_plane_index = index;
|
||||
steps.emplace_back(
|
||||
"configure:" +
|
||||
std::to_string(width) + "," +
|
||||
std::to_string(height) + "," +
|
||||
std::to_string(x) + "," +
|
||||
std::to_string(y) + "," +
|
||||
std::to_string(scissor_width) + "," +
|
||||
std::to_string(scissor_height));
|
||||
"setup:" + std::to_string(index) + ":" + std::to_string(mvp[0][0]));
|
||||
},
|
||||
.bind_mix_inputs = [&] {
|
||||
steps.emplace_back("bind-inputs");
|
||||
.bind_layer_texture = [&](int /*index*/) {
|
||||
steps.emplace_back("bind-layer");
|
||||
},
|
||||
.draw_mix = [&] {
|
||||
.bind_stroke_texture = [&](int /*index*/) {
|
||||
steps.emplace_back("bind-stroke");
|
||||
},
|
||||
.bind_mask_texture = [&](int /*index*/) {
|
||||
steps.emplace_back("bind-mask");
|
||||
},
|
||||
.draw_plane = [&] {
|
||||
steps.emplace_back("draw");
|
||||
},
|
||||
.unbind_mixer_framebuffer = [&] {
|
||||
steps.emplace_back("unbind-framebuffer");
|
||||
.unbind_mask_texture = [&](int /*index*/) {
|
||||
steps.emplace_back("unbind-mask");
|
||||
},
|
||||
.restore_state = [&] {
|
||||
steps.emplace_back("restore");
|
||||
.unbind_stroke_texture = [&](int /*index*/) {
|
||||
steps.emplace_back("unbind-stroke");
|
||||
},
|
||||
.unbind_layer_texture = [&](int /*index*/) {
|
||||
steps.emplace_back("unbind-layer");
|
||||
},
|
||||
});
|
||||
|
||||
PP_EXPECT(h, ok);
|
||||
PP_EXPECT(h, almost_equal(observed_shader.resolution, glm::vec2(128.0F, 64.0F)));
|
||||
PP_EXPECT(h, almost_equal(observed_shader.pattern_scale, glm::vec2(-0.25F, 0.25F)));
|
||||
PP_EXPECT(h, observed_shader.use_dual);
|
||||
PP_EXPECT(h, observed_shader.use_pattern);
|
||||
PP_EXPECT(h, observed_shader.dual_blend_mode == 9);
|
||||
PP_EXPECT(h, almost_equal(observed_shader.dual_alpha, 0.4F));
|
||||
PP_EXPECT(h, ok.ok);
|
||||
PP_EXPECT(h, observed_plane_index == 4);
|
||||
|
||||
const std::vector<std::string> expected_steps {
|
||||
"save",
|
||||
"setup",
|
||||
"bind-framebuffer",
|
||||
"configure:128,64,11,12,13,14",
|
||||
"bind-inputs",
|
||||
"bind-mix",
|
||||
"setup:4:1.000000",
|
||||
"bind-layer",
|
||||
"bind-stroke",
|
||||
"bind-mask",
|
||||
"draw",
|
||||
"unbind-framebuffer",
|
||||
"restore",
|
||||
"unbind-mask",
|
||||
"unbind-stroke",
|
||||
"unbind-layer",
|
||||
"unbind-mix",
|
||||
};
|
||||
PP_EXPECT(h, steps == expected_steps);
|
||||
|
||||
const bool invalid = pp::panopainter::execute_legacy_node_stroke_preview_mix_pass(
|
||||
pp::panopainter::LegacyNodeStrokePreviewMixExecutionRequest {
|
||||
.mixer_width = 128,
|
||||
.mixer_height = 64,
|
||||
const auto invalid = pp::panopainter::execute_legacy_canvas_stroke_mix_pass(
|
||||
pp::panopainter::LegacyCanvasStrokeMixPassRequest {
|
||||
.context = "NodeStrokePreview::stroke_draw_mix",
|
||||
.resolution = glm::vec2(128.0F, 64.0F),
|
||||
.planes = planes,
|
||||
});
|
||||
PP_EXPECT(h, !invalid);
|
||||
PP_EXPECT(h, !invalid.ok);
|
||||
}
|
||||
|
||||
void legacy_node_stroke_preview_pass_sequence_preserves_dual_main_and_composite_order(pp::tests::Harness& h)
|
||||
@@ -2965,35 +2955,49 @@ void legacy_node_stroke_preview_pass_sequence_preserves_dual_main_and_composite_
|
||||
std::vector<std::string> steps;
|
||||
const auto run_sequence = [&](bool dual_enabled) {
|
||||
steps.clear();
|
||||
const bool ok = pp::panopainter::execute_legacy_node_stroke_preview_pass_sequence(
|
||||
pp::panopainter::LegacyNodeStrokePreviewPassSequenceRequest {
|
||||
.dual_pass_enabled = dual_enabled,
|
||||
.prepare_dual_pass = [&] {
|
||||
steps.emplace_back("prepare_dual");
|
||||
if (dual_enabled) {
|
||||
steps.emplace_back("prepare_dual");
|
||||
steps.emplace_back("execute_dual");
|
||||
}
|
||||
|
||||
steps.emplace_back("capture_background");
|
||||
steps.emplace_back("prepare_main");
|
||||
const bool ok = pp::panopainter::execute_legacy_node_stroke_preview_main_live_pass(
|
||||
pp::panopainter::LegacyNodeStrokePreviewMainLivePassRequestT<std::uint8_t> {
|
||||
.setup_blend_uniforms = [] {},
|
||||
.bind_main_pass_textures = [] {},
|
||||
.clear_target = [] {},
|
||||
.compute_frames = [&] {
|
||||
return std::vector<std::uint8_t> { 1 };
|
||||
},
|
||||
.execute_dual_pass = [&] {
|
||||
steps.emplace_back("execute_dual");
|
||||
},
|
||||
.capture_background = [&] {
|
||||
steps.emplace_back("capture_background");
|
||||
},
|
||||
.prepare_main_pass = [&] {
|
||||
steps.emplace_back("prepare_main");
|
||||
},
|
||||
.execute_main_pass = [&] {
|
||||
.before_frame = [](std::uint8_t&) {},
|
||||
.setup_sample_shader = [](std::uint8_t&) {},
|
||||
.draw_sample = [&] (std::uint8_t&) {
|
||||
steps.emplace_back("execute_main");
|
||||
},
|
||||
.finish_main_pass = [&] {
|
||||
steps.emplace_back("finish_main");
|
||||
},
|
||||
.execute_final_composite = [&] {
|
||||
steps.emplace_back("execute_composite");
|
||||
},
|
||||
.copy_preview_result = [&] {
|
||||
steps.emplace_back("copy_preview");
|
||||
},
|
||||
.copy_pass_result = [] {},
|
||||
.finish_main_pass = [] {},
|
||||
});
|
||||
PP_EXPECT(h, ok);
|
||||
steps.emplace_back("finish_main");
|
||||
|
||||
pp::panopainter::execute_legacy_stroke_preview_final_composite(
|
||||
[&] {
|
||||
steps.emplace_back("execute_composite");
|
||||
},
|
||||
[] {},
|
||||
[] {},
|
||||
[] {});
|
||||
const auto copy_status = pp::paint_renderer::copy_stroke_preview_result_to_texture(
|
||||
[] {},
|
||||
[&](int, int, int, int, int, int) {
|
||||
steps.emplace_back("copy_preview");
|
||||
},
|
||||
pp::paint_renderer::StrokePreviewCopySize {
|
||||
.width = 32,
|
||||
.height = 16,
|
||||
});
|
||||
PP_EXPECT(h, copy_status.ok());
|
||||
};
|
||||
|
||||
run_sequence(true);
|
||||
@@ -3019,73 +3023,6 @@ void legacy_node_stroke_preview_pass_sequence_preserves_dual_main_and_composite_
|
||||
"copy_preview",
|
||||
};
|
||||
PP_EXPECT(h, steps == single_steps);
|
||||
|
||||
steps.clear();
|
||||
const bool missing_dual_prepare =
|
||||
pp::panopainter::execute_legacy_node_stroke_preview_pass_sequence(
|
||||
pp::panopainter::LegacyNodeStrokePreviewPassSequenceRequest {
|
||||
.dual_pass_enabled = true,
|
||||
.prepare_dual_pass = {},
|
||||
.execute_dual_pass = [&] {
|
||||
steps.emplace_back("execute_dual");
|
||||
},
|
||||
.capture_background = [&] {
|
||||
steps.emplace_back("capture_background");
|
||||
},
|
||||
.prepare_main_pass = [&] {
|
||||
steps.emplace_back("prepare_main");
|
||||
},
|
||||
.execute_main_pass = [&] {
|
||||
steps.emplace_back("execute_main");
|
||||
},
|
||||
.finish_main_pass = [&] {
|
||||
steps.emplace_back("finish_main");
|
||||
},
|
||||
.execute_final_composite = [&] {
|
||||
steps.emplace_back("execute_composite");
|
||||
},
|
||||
.copy_preview_result = [&] {
|
||||
steps.emplace_back("copy_preview");
|
||||
},
|
||||
});
|
||||
PP_EXPECT(h, !missing_dual_prepare);
|
||||
PP_EXPECT(h, steps.empty());
|
||||
|
||||
steps.clear();
|
||||
const bool missing_main_prepare =
|
||||
pp::panopainter::execute_legacy_node_stroke_preview_pass_sequence(
|
||||
pp::panopainter::LegacyNodeStrokePreviewPassSequenceRequest {
|
||||
.dual_pass_enabled = true,
|
||||
.prepare_dual_pass = [&] {
|
||||
steps.emplace_back("prepare_dual");
|
||||
},
|
||||
.execute_dual_pass = [&] {
|
||||
steps.emplace_back("execute_dual");
|
||||
},
|
||||
.capture_background = [&] {
|
||||
steps.emplace_back("capture_background");
|
||||
},
|
||||
.prepare_main_pass = {},
|
||||
.execute_main_pass = [&] {
|
||||
steps.emplace_back("execute_main");
|
||||
},
|
||||
.finish_main_pass = [&] {
|
||||
steps.emplace_back("finish_main");
|
||||
},
|
||||
.execute_final_composite = [&] {
|
||||
steps.emplace_back("execute_composite");
|
||||
},
|
||||
.copy_preview_result = [&] {
|
||||
steps.emplace_back("copy_preview");
|
||||
},
|
||||
});
|
||||
PP_EXPECT(h, !missing_main_prepare);
|
||||
PP_EXPECT(h, steps.empty());
|
||||
|
||||
const bool missing_required =
|
||||
pp::panopainter::execute_legacy_node_stroke_preview_pass_sequence(
|
||||
pp::panopainter::LegacyNodeStrokePreviewPassSequenceRequest {});
|
||||
PP_EXPECT(h, !missing_required);
|
||||
}
|
||||
|
||||
void legacy_node_stroke_preview_main_live_pass_preserves_order(pp::tests::Harness& h)
|
||||
@@ -3210,26 +3147,19 @@ void legacy_node_stroke_preview_main_pass_texture_dispatch_preserves_order(pp::t
|
||||
void legacy_node_stroke_preview_final_composite_and_copy_helpers_preserve_order(pp::tests::Harness& h)
|
||||
{
|
||||
std::vector<std::string> steps;
|
||||
const bool composite_ok = pp::panopainter::execute_legacy_node_stroke_preview_final_composite(
|
||||
pp::panopainter::LegacyNodeStrokePreviewFinalCompositeRequest {
|
||||
.resolution = glm::vec2(64.0F, 32.0F),
|
||||
.pattern_scale = glm::vec2(0.25F, -0.5F),
|
||||
.brush = reinterpret_cast<const Brush*>(1),
|
||||
.composite_pass = reinterpret_cast<const pp::paint_renderer::CanvasStrokeCompositePassPlan*>(1),
|
||||
.setup_composite_shader = [&] {
|
||||
steps.emplace_back("setup");
|
||||
},
|
||||
.bind_composite_samplers = [&] {
|
||||
steps.emplace_back("bind_samplers");
|
||||
},
|
||||
.bind_composite_inputs = [&] {
|
||||
steps.emplace_back("bind_inputs");
|
||||
},
|
||||
.draw_composite = [&] {
|
||||
steps.emplace_back("draw");
|
||||
},
|
||||
pp::panopainter::execute_legacy_stroke_preview_final_composite(
|
||||
[&] {
|
||||
steps.emplace_back("setup");
|
||||
},
|
||||
[&] {
|
||||
steps.emplace_back("bind_samplers");
|
||||
},
|
||||
[&] {
|
||||
steps.emplace_back("bind_inputs");
|
||||
},
|
||||
[&] {
|
||||
steps.emplace_back("draw");
|
||||
});
|
||||
PP_EXPECT(h, composite_ok);
|
||||
PP_EXPECT(h, (steps == std::vector<std::string> {
|
||||
"setup",
|
||||
"bind_samplers",
|
||||
@@ -3238,23 +3168,27 @@ void legacy_node_stroke_preview_final_composite_and_copy_helpers_preserve_order(
|
||||
}));
|
||||
|
||||
steps.clear();
|
||||
const bool copy_ok = pp::panopainter::copy_legacy_node_stroke_preview_result(
|
||||
pp::panopainter::LegacyNodeStrokePreviewCopyResultRequest {
|
||||
.preview_texture = reinterpret_cast<Texture2D*>(1),
|
||||
.size = glm::vec2(32.0F, 16.0F),
|
||||
.copy_framebuffer_to_texture = [&](int src_x, int src_y, int dst_x, int dst_y, int width, int height) {
|
||||
steps.emplace_back(
|
||||
"copy:" +
|
||||
std::to_string(src_x) + "," +
|
||||
std::to_string(src_y) + "," +
|
||||
std::to_string(dst_x) + "," +
|
||||
std::to_string(dst_y) + "," +
|
||||
std::to_string(width) + "," +
|
||||
std::to_string(height));
|
||||
},
|
||||
const auto copy_status = pp::paint_renderer::copy_stroke_preview_result_to_texture(
|
||||
[&] {
|
||||
steps.emplace_back("bind");
|
||||
},
|
||||
[&](int src_x, int src_y, int dst_x, int dst_y, int width, int height) {
|
||||
steps.emplace_back(
|
||||
"copy:" +
|
||||
std::to_string(src_x) + "," +
|
||||
std::to_string(src_y) + "," +
|
||||
std::to_string(dst_x) + "," +
|
||||
std::to_string(dst_y) + "," +
|
||||
std::to_string(width) + "," +
|
||||
std::to_string(height));
|
||||
},
|
||||
pp::paint_renderer::StrokePreviewCopySize {
|
||||
.width = 32,
|
||||
.height = 16,
|
||||
});
|
||||
PP_EXPECT(h, copy_ok);
|
||||
PP_EXPECT(h, copy_status.ok());
|
||||
PP_EXPECT(h, (steps == std::vector<std::string> {
|
||||
"bind",
|
||||
"copy:0,0,0,0,32,16",
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -33,7 +33,6 @@ using pp::panopainter::LegacyCanvasStrokeSamplerDispatch;
|
||||
using pp::panopainter::LegacyCanvasStrokeTextureBinding;
|
||||
using pp::panopainter::LegacyCanvasStrokeTextureInputDispatch;
|
||||
using pp::panopainter::LegacyCanvasStrokeTextureInput;
|
||||
using pp::panopainter::LegacyStrokePreviewCopySize;
|
||||
using pp::panopainter::LegacyStrokeSampleExecutionRequest;
|
||||
|
||||
std::vector<vertex_t> triangulate_simple(const std::vector<vertex_t>& vertices)
|
||||
@@ -1210,47 +1209,61 @@ void retained_stroke_live_pass_clears_before_traversal_and_copies_afterwards(pp:
|
||||
face_framebuffers[face_index].face_index = face_index;
|
||||
}
|
||||
|
||||
events.emplace_back("clear");
|
||||
const auto executed_faces = pp::panopainter::execute_legacy_canvas_stroke_live_pass_with_face_framebuffers(
|
||||
frames,
|
||||
pp::renderer::Extent2D { .width = 64, .height = 64 },
|
||||
accumulated_dirty_boxes,
|
||||
pass_dirty_boxes,
|
||||
include_in_committed_dirty_box,
|
||||
[&](StrokeFrame& current_frame) {
|
||||
events.push_back("begin-frame:" + std::to_string(current_frame.id));
|
||||
std::size_t executed_faces = 0U;
|
||||
bool copy_ok = false;
|
||||
pp::panopainter::execute_legacy_stroke_preview_live_pass(
|
||||
[&] { events.push_back("clear"); },
|
||||
[&]() {
|
||||
return std::vector<int> { 0 };
|
||||
},
|
||||
[&](StrokeFrame&, int face_index, std::span<const vertex_t> vertices) {
|
||||
events.push_back(
|
||||
"prepare:" + std::to_string(face_index) + ":" + std::to_string(vertices.size()));
|
||||
[&](int&) {},
|
||||
[&](int&) {},
|
||||
[&](int&) {
|
||||
executed_faces = pp::panopainter::execute_legacy_canvas_stroke_live_pass_with_face_framebuffers(
|
||||
frames,
|
||||
pp::renderer::Extent2D { .width = 64, .height = 64 },
|
||||
accumulated_dirty_boxes,
|
||||
pass_dirty_boxes,
|
||||
include_in_committed_dirty_box,
|
||||
[&](StrokeFrame& current_frame) {
|
||||
events.push_back("begin-frame:" + std::to_string(current_frame.id));
|
||||
},
|
||||
[&](StrokeFrame&, int face_index, std::span<const vertex_t> vertices) {
|
||||
events.push_back(
|
||||
"prepare:" + std::to_string(face_index) + ":" + std::to_string(vertices.size()));
|
||||
},
|
||||
[&](StrokeFrame&, int face_index, std::span<const vertex_t>) {
|
||||
events.push_back("execute:" + std::to_string(face_index));
|
||||
return glm::vec4(
|
||||
static_cast<float>(face_index + 1),
|
||||
static_cast<float>(face_index + 2),
|
||||
static_cast<float>(face_index + 3),
|
||||
static_cast<float>(face_index + 4));
|
||||
},
|
||||
face_framebuffers,
|
||||
true,
|
||||
committed_dirty_faces,
|
||||
pass_dirty_faces);
|
||||
},
|
||||
[&](StrokeFrame&, int face_index, std::span<const vertex_t>) {
|
||||
events.push_back("execute:" + std::to_string(face_index));
|
||||
return glm::vec4(
|
||||
static_cast<float>(face_index + 1),
|
||||
static_cast<float>(face_index + 2),
|
||||
static_cast<float>(face_index + 3),
|
||||
static_cast<float>(face_index + 4));
|
||||
},
|
||||
face_framebuffers,
|
||||
true,
|
||||
committed_dirty_faces,
|
||||
pass_dirty_faces);
|
||||
|
||||
pp::panopainter::copy_legacy_stroke_preview_texture(
|
||||
[&]() { events.emplace_back("bind-preview"); },
|
||||
[&](int dst_x, int dst_y, int src_x, int src_y, int width, int height) {
|
||||
events.emplace_back("copy-preview");
|
||||
PP_EXPECT(h, dst_x == 0);
|
||||
PP_EXPECT(h, dst_y == 0);
|
||||
PP_EXPECT(h, src_x == 0);
|
||||
PP_EXPECT(h, src_y == 0);
|
||||
PP_EXPECT(h, width == 64);
|
||||
PP_EXPECT(h, height == 64);
|
||||
},
|
||||
LegacyStrokePreviewCopySize { .width = 64, .height = 64 });
|
||||
[&]() {
|
||||
const auto copy_status = pp::paint_renderer::copy_stroke_preview_result_to_texture(
|
||||
[&]() { events.emplace_back("bind-preview"); },
|
||||
[&](int src_x, int src_y, int dst_x, int dst_y, int width, int height) {
|
||||
events.emplace_back("copy-preview");
|
||||
PP_EXPECT(h, src_x == 0);
|
||||
PP_EXPECT(h, src_y == 0);
|
||||
PP_EXPECT(h, dst_x == 0);
|
||||
PP_EXPECT(h, dst_y == 0);
|
||||
PP_EXPECT(h, width == 64);
|
||||
PP_EXPECT(h, height == 64);
|
||||
},
|
||||
pp::paint_renderer::StrokePreviewCopySize { .width = 64, .height = 64 });
|
||||
PP_EXPECT(h, copy_status.ok());
|
||||
copy_ok = copy_status.ok();
|
||||
});
|
||||
|
||||
PP_EXPECT(h, executed_faces == 2U);
|
||||
PP_EXPECT(h, copy_ok);
|
||||
const std::vector<std::string> expected_events {
|
||||
"clear",
|
||||
"begin-frame:9",
|
||||
@@ -1503,6 +1516,164 @@ void retained_stroke_main_pass_execution_preserves_bind_and_unbind_order(pp::tes
|
||||
PP_EXPECT(h, events == expected_events);
|
||||
}
|
||||
|
||||
void retained_stroke_main_pass_execution_preserves_destination_binding_order_and_face_execution(pp::tests::Harness& h)
|
||||
{
|
||||
std::vector<std::string> events;
|
||||
|
||||
std::array<StrokeFrame, 1> frames {};
|
||||
frames[0].id = 31;
|
||||
frames[0].shapes[2] = {
|
||||
vertex_t(glm::vec2(2.0F, 3.0F)),
|
||||
vertex_t(glm::vec2(4.0F, 3.0F)),
|
||||
vertex_t(glm::vec2(3.0F, 5.0F)),
|
||||
};
|
||||
|
||||
std::array<glm::vec4, 6> accumulated_dirty_boxes;
|
||||
std::array<glm::vec4, 6> pass_dirty_boxes;
|
||||
accumulated_dirty_boxes.fill(glm::vec4(64.0F, 64.0F, 0.0F, 0.0F));
|
||||
pass_dirty_boxes.fill(glm::vec4(64.0F, 64.0F, 0.0F, 0.0F));
|
||||
std::array<bool, 6> include_main_dirty = SIXPLETTE(true);
|
||||
std::array<DummyFramebuffer, 6> face_framebuffers {};
|
||||
for (int face_index = 0; face_index < 6; ++face_index) {
|
||||
face_framebuffers[face_index].events = &events;
|
||||
face_framebuffers[face_index].face_index = face_index;
|
||||
}
|
||||
|
||||
const std::array<pp::panopainter::LegacyCanvasStrokeTextureBinding, 4> main_pass_texture_bindings {
|
||||
pp::panopainter::LegacyCanvasStrokeTextureBinding {
|
||||
.input = pp::panopainter::LegacyCanvasStrokeTextureInput::brush_tip,
|
||||
.slot = 0,
|
||||
},
|
||||
pp::panopainter::LegacyCanvasStrokeTextureBinding {
|
||||
.input = pp::panopainter::LegacyCanvasStrokeTextureInput::stroke_destination,
|
||||
.slot = 1,
|
||||
},
|
||||
pp::panopainter::LegacyCanvasStrokeTextureBinding {
|
||||
.input = pp::panopainter::LegacyCanvasStrokeTextureInput::pattern,
|
||||
.slot = 2,
|
||||
},
|
||||
pp::panopainter::LegacyCanvasStrokeTextureBinding {
|
||||
.input = pp::panopainter::LegacyCanvasStrokeTextureInput::mixer,
|
||||
.slot = 3,
|
||||
},
|
||||
};
|
||||
const std::array<pp::panopainter::LegacyCanvasStrokeTextureBinding, 3> main_pass_texture_unbindings {
|
||||
pp::panopainter::LegacyCanvasStrokeTextureBinding {
|
||||
.input = pp::panopainter::LegacyCanvasStrokeTextureInput::mixer,
|
||||
.slot = 3,
|
||||
},
|
||||
pp::panopainter::LegacyCanvasStrokeTextureBinding {
|
||||
.input = pp::panopainter::LegacyCanvasStrokeTextureInput::stroke_destination,
|
||||
.slot = 1,
|
||||
},
|
||||
pp::panopainter::LegacyCanvasStrokeTextureBinding {
|
||||
.input = pp::panopainter::LegacyCanvasStrokeTextureInput::brush_tip,
|
||||
.slot = 0,
|
||||
},
|
||||
};
|
||||
|
||||
const pp::panopainter::LegacyCanvasStrokeTextureInputDispatch main_pass_texture_dispatch {
|
||||
.activate_texture_unit = [&](int slot) {
|
||||
events.emplace_back("activate:" + std::to_string(slot));
|
||||
},
|
||||
.bind_brush_tip = [&]() {
|
||||
events.emplace_back("bind-brush");
|
||||
},
|
||||
.unbind_brush_tip = [&]() {
|
||||
events.emplace_back("unbind-brush");
|
||||
},
|
||||
.bind_stroke_destination = [&]() {
|
||||
events.emplace_back("bind-destination");
|
||||
},
|
||||
.unbind_stroke_destination = [&]() {
|
||||
events.emplace_back("unbind-destination");
|
||||
},
|
||||
.bind_pattern = [&]() {
|
||||
events.emplace_back("bind-pattern");
|
||||
},
|
||||
.unbind_pattern = [&]() {
|
||||
events.emplace_back("unbind-pattern");
|
||||
},
|
||||
.bind_mixer = [&]() {
|
||||
events.emplace_back("bind-mixer");
|
||||
},
|
||||
.unbind_mixer = [&]() {
|
||||
events.emplace_back("unbind-mixer");
|
||||
},
|
||||
};
|
||||
|
||||
const auto ok = pp::panopainter::execute_legacy_canvas_stroke_main_pass(
|
||||
pp::panopainter::make_legacy_canvas_stroke_main_pass_execution_request(
|
||||
"Canvas::stroke_draw",
|
||||
[&] {
|
||||
events.emplace_back("bind-samplers");
|
||||
},
|
||||
[&] {
|
||||
events.emplace_back("bind-textures");
|
||||
pp::panopainter::bind_legacy_canvas_stroke_texture_inputs(
|
||||
main_pass_texture_bindings,
|
||||
main_pass_texture_dispatch);
|
||||
},
|
||||
[&] {
|
||||
pp::panopainter::execute_legacy_canvas_stroke_main_pass_frame_callbacks(
|
||||
frames,
|
||||
pp::renderer::Extent2D { .width = 64, .height = 64 },
|
||||
accumulated_dirty_boxes,
|
||||
pass_dirty_boxes,
|
||||
include_main_dirty,
|
||||
[&](StrokeFrame& frame) {
|
||||
events.emplace_back("begin-frame:" + std::to_string(frame.id));
|
||||
},
|
||||
[&](StrokeFrame&, int face_index, std::span<const vertex_t> vertices) {
|
||||
events.emplace_back(
|
||||
"prepare:" + std::to_string(face_index) + ":" + std::to_string(vertices.size()));
|
||||
},
|
||||
[&](StrokeFrame&, int face_index, std::span<const vertex_t>) {
|
||||
events.emplace_back("execute:" + std::to_string(face_index));
|
||||
return glm::vec4(1.0F, 2.0F, 3.0F, 4.0F);
|
||||
},
|
||||
face_framebuffers);
|
||||
},
|
||||
[&] {
|
||||
events.emplace_back("unbind-textures");
|
||||
pp::panopainter::unbind_legacy_canvas_stroke_texture_inputs(
|
||||
main_pass_texture_unbindings,
|
||||
main_pass_texture_dispatch);
|
||||
},
|
||||
[&] {
|
||||
events.emplace_back("unbind-samplers");
|
||||
}));
|
||||
|
||||
const std::vector<std::string> expected_events {
|
||||
"bind-samplers",
|
||||
"bind-textures",
|
||||
"activate:0",
|
||||
"bind-brush",
|
||||
"activate:1",
|
||||
"bind-destination",
|
||||
"activate:2",
|
||||
"bind-pattern",
|
||||
"activate:3",
|
||||
"bind-mixer",
|
||||
"begin-frame:31",
|
||||
"prepare:2:3",
|
||||
"bind:2",
|
||||
"execute:2",
|
||||
"unbind:2",
|
||||
"unbind-textures",
|
||||
"activate:3",
|
||||
"unbind-mixer",
|
||||
"activate:1",
|
||||
"unbind-destination",
|
||||
"activate:0",
|
||||
"unbind-brush",
|
||||
"unbind-samplers",
|
||||
};
|
||||
|
||||
PP_EXPECT(h, ok);
|
||||
PP_EXPECT(h, events == expected_events);
|
||||
}
|
||||
|
||||
void retained_stroke_pad_face_callbacks_preserve_order(pp::tests::Harness& h)
|
||||
{
|
||||
const std::array<bool, 3> dirty_faces { true, false, true };
|
||||
@@ -1647,16 +1818,28 @@ void retained_stroke_preview_background_capture_preserves_retained_call_order(pp
|
||||
{
|
||||
std::vector<std::string> events;
|
||||
std::array<int, 6> copy_args {};
|
||||
bool copy_ok = false;
|
||||
|
||||
pp::panopainter::execute_legacy_stroke_preview_background_capture(
|
||||
pp::panopainter::execute_legacy_stroke_preview_live_pass(
|
||||
[&]() { events.emplace_back("setup"); },
|
||||
[&]() { events.emplace_back("draw"); },
|
||||
[&]() { events.emplace_back("bind"); },
|
||||
[&](int dst_x, int dst_y, int src_x, int src_y, int width, int height) {
|
||||
events.emplace_back("copy");
|
||||
copy_args = { dst_x, dst_y, src_x, src_y, width, height };
|
||||
[&]() {
|
||||
return std::vector<int> { 0 };
|
||||
},
|
||||
LegacyStrokePreviewCopySize { .width = 48, .height = 32 });
|
||||
[&](int&) {},
|
||||
[&](int&) { events.emplace_back("draw"); },
|
||||
[&](int&) {},
|
||||
[&]() {
|
||||
const auto copy_status = pp::paint_renderer::copy_stroke_preview_result_to_texture(
|
||||
[&]() { events.emplace_back("bind"); },
|
||||
[&](int src_x, int src_y, int dst_x, int dst_y, int width, int height) {
|
||||
events.emplace_back("copy");
|
||||
copy_args = { src_x, src_y, dst_x, dst_y, width, height };
|
||||
},
|
||||
pp::paint_renderer::StrokePreviewCopySize { .width = 48, .height = 32 });
|
||||
PP_EXPECT(h, copy_status.ok());
|
||||
copy_ok = copy_status.ok();
|
||||
});
|
||||
PP_EXPECT(h, copy_ok);
|
||||
|
||||
const std::vector<std::string> expected_events { "setup", "draw", "bind", "copy" };
|
||||
PP_EXPECT(h, events == expected_events);
|
||||
@@ -1692,13 +1875,14 @@ void retained_stroke_preview_texture_copy_binds_before_copy(pp::tests::Harness&
|
||||
std::vector<std::string> events;
|
||||
std::array<int, 6> copy_args {};
|
||||
|
||||
pp::panopainter::copy_legacy_stroke_preview_texture(
|
||||
const auto copy_status = pp::paint_renderer::copy_stroke_preview_result_to_texture(
|
||||
[&]() { events.emplace_back("bind"); },
|
||||
[&](int dst_x, int dst_y, int src_x, int src_y, int width, int height) {
|
||||
[&](int src_x, int src_y, int dst_x, int dst_y, int width, int height) {
|
||||
events.emplace_back("copy");
|
||||
copy_args = { dst_x, dst_y, src_x, src_y, width, height };
|
||||
copy_args = { src_x, src_y, dst_x, dst_y, width, height };
|
||||
},
|
||||
LegacyStrokePreviewCopySize { .width = 96, .height = 64 });
|
||||
pp::paint_renderer::StrokePreviewCopySize { .width = 96, .height = 64 });
|
||||
PP_EXPECT(h, copy_status.ok());
|
||||
|
||||
const std::vector<std::string> expected_events { "bind", "copy" };
|
||||
PP_EXPECT(h, events == expected_events);
|
||||
@@ -2087,6 +2271,9 @@ int main()
|
||||
harness.run(
|
||||
"retained_stroke_main_pass_execution_preserves_bind_and_unbind_order",
|
||||
retained_stroke_main_pass_execution_preserves_bind_and_unbind_order);
|
||||
harness.run(
|
||||
"retained_stroke_main_pass_execution_preserves_destination_binding_order_and_face_execution",
|
||||
retained_stroke_main_pass_execution_preserves_destination_binding_order_and_face_execution);
|
||||
harness.run(
|
||||
"retained_stroke_live_pass_sampler_dispatch_helper_builds_expected_callback_wiring",
|
||||
retained_stroke_live_pass_sampler_dispatch_helper_builds_expected_callback_wiring);
|
||||
|
||||
Reference in New Issue
Block a user