Add retained stroke execution helper tests
This commit is contained in:
@@ -277,6 +277,30 @@ add_test(NAME pp_paint_renderer_compositor_tests COMMAND pp_paint_renderer_compo
|
||||
set_tests_properties(pp_paint_renderer_compositor_tests PROPERTIES
|
||||
LABELS "renderer;paint;desktop-fast")
|
||||
|
||||
add_executable(pp_paint_renderer_stroke_execution_tests
|
||||
paint_renderer/stroke_execution_tests.cpp)
|
||||
target_link_libraries(pp_paint_renderer_stroke_execution_tests PRIVATE
|
||||
pp_paint_renderer
|
||||
pp_test_harness)
|
||||
target_include_directories(pp_paint_renderer_stroke_execution_tests PRIVATE
|
||||
${PP_LEGACY_INCLUDE_DIRS})
|
||||
target_compile_definitions(pp_paint_renderer_stroke_execution_tests PRIVATE
|
||||
ENUM_BITFIELDS_NOT_SUPPORTED
|
||||
UNICODE
|
||||
_UNICODE
|
||||
_CRT_SECURE_NO_WARNINGS
|
||||
_SCL_SECURE_NO_WARNINGS
|
||||
_SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING
|
||||
_SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING
|
||||
_CONSOLE
|
||||
WITH_CURL=1)
|
||||
target_precompile_headers(pp_paint_renderer_stroke_execution_tests PRIVATE
|
||||
"${PROJECT_SOURCE_DIR}/src/pch.h")
|
||||
|
||||
add_test(NAME pp_paint_renderer_stroke_execution_tests COMMAND pp_paint_renderer_stroke_execution_tests)
|
||||
set_tests_properties(pp_paint_renderer_stroke_execution_tests PROPERTIES
|
||||
LABELS "renderer;paint;desktop-fast")
|
||||
|
||||
add_executable(pp_platform_api_tests
|
||||
platform_api/platform_services_tests.cpp)
|
||||
target_link_libraries(pp_platform_api_tests PRIVATE
|
||||
|
||||
382
tests/paint_renderer/stroke_execution_tests.cpp
Normal file
382
tests/paint_renderer/stroke_execution_tests.cpp
Normal file
@@ -0,0 +1,382 @@
|
||||
#include "pch.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cfloat>
|
||||
#include <condition_variable>
|
||||
#include <cstdio>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
#include <future>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <random>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "legacy_canvas_stroke_execution_services.h"
|
||||
#include "test_harness.h"
|
||||
|
||||
using pp::panopainter::LegacyCanvasStrokePadExecutionRequest;
|
||||
using pp::panopainter::LegacyCanvasStrokePadFace;
|
||||
using pp::panopainter::LegacyCanvasStrokeTextureBinding;
|
||||
using pp::panopainter::LegacyCanvasStrokeTextureInput;
|
||||
using pp::panopainter::LegacyStrokeSampleExecutionRequest;
|
||||
|
||||
namespace {
|
||||
|
||||
bool nearly_equal(float a, float b)
|
||||
{
|
||||
return std::fabs(a - b) < 0.0001F;
|
||||
}
|
||||
|
||||
std::array<vertex_t, 4> make_quad_vertices()
|
||||
{
|
||||
return {
|
||||
vertex_t(glm::vec2(10.0F, 20.0F)),
|
||||
vertex_t(glm::vec2(10.0F, 30.0F)),
|
||||
vertex_t(glm::vec2(30.0F, 30.0F)),
|
||||
vertex_t(glm::vec2(30.0F, 20.0F)),
|
||||
};
|
||||
}
|
||||
|
||||
std::array<pp::paint_renderer::CanvasStrokePoint, 4> make_sample_points()
|
||||
{
|
||||
return {
|
||||
pp::paint_renderer::CanvasStrokePoint { .x = 10.0F, .y = 20.0F },
|
||||
pp::paint_renderer::CanvasStrokePoint { .x = 10.0F, .y = 30.0F },
|
||||
pp::paint_renderer::CanvasStrokePoint { .x = 30.0F, .y = 30.0F },
|
||||
pp::paint_renderer::CanvasStrokePoint { .x = 30.0F, .y = 20.0F },
|
||||
};
|
||||
}
|
||||
|
||||
void retained_stroke_texture_inputs_bind_and_unbind_in_declared_order(pp::tests::Harness& h)
|
||||
{
|
||||
const std::array<LegacyCanvasStrokeTextureBinding, 4> bindings {
|
||||
LegacyCanvasStrokeTextureBinding { .input = LegacyCanvasStrokeTextureInput::brush_tip, .slot = 1 },
|
||||
LegacyCanvasStrokeTextureBinding { .input = LegacyCanvasStrokeTextureInput::stroke_destination, .slot = 0 },
|
||||
LegacyCanvasStrokeTextureBinding { .input = LegacyCanvasStrokeTextureInput::mixer, .slot = 3 },
|
||||
LegacyCanvasStrokeTextureBinding { .input = LegacyCanvasStrokeTextureInput::pattern, .slot = 4 },
|
||||
};
|
||||
|
||||
std::vector<std::pair<LegacyCanvasStrokeTextureInput, int>> bound;
|
||||
std::vector<std::pair<LegacyCanvasStrokeTextureInput, int>> unbound;
|
||||
|
||||
pp::panopainter::bind_legacy_canvas_stroke_texture_inputs(
|
||||
bindings,
|
||||
[&](LegacyCanvasStrokeTextureInput input, int slot) {
|
||||
bound.emplace_back(input, slot);
|
||||
});
|
||||
pp::panopainter::unbind_legacy_canvas_stroke_texture_inputs(
|
||||
bindings,
|
||||
[&](LegacyCanvasStrokeTextureInput input, int slot) {
|
||||
unbound.emplace_back(input, slot);
|
||||
});
|
||||
|
||||
PP_EXPECT(h, bound.size() == bindings.size());
|
||||
PP_EXPECT(h, unbound.size() == bindings.size());
|
||||
for (std::size_t index = 0; index < bindings.size(); ++index) {
|
||||
PP_EXPECT(h, bound[index].first == bindings[index].input);
|
||||
PP_EXPECT(h, bound[index].second == bindings[index].slot);
|
||||
PP_EXPECT(h, unbound[index] == bound[index]);
|
||||
}
|
||||
}
|
||||
|
||||
void retained_stroke_sample_executor_copies_destination_and_expands_quads(pp::tests::Harness& h)
|
||||
{
|
||||
const auto vertices = make_quad_vertices();
|
||||
const auto sample_points = make_sample_points();
|
||||
|
||||
std::vector<std::string> events;
|
||||
std::vector<vertex_t> uploaded_vertices;
|
||||
std::array<int, 6> copy_args {};
|
||||
|
||||
const auto result = pp::panopainter::execute_legacy_canvas_stroke_sample(
|
||||
LegacyStrokeSampleExecutionRequest {
|
||||
.context = "test",
|
||||
.target_size = glm::vec2(64.0F, 64.0F),
|
||||
.vertices = vertices,
|
||||
.sample_points = sample_points,
|
||||
.copy_stroke_destination = true,
|
||||
.bind_destination_texture = [&]() { events.emplace_back("bind"); },
|
||||
.copy_framebuffer_to_destination_texture =
|
||||
[&](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 };
|
||||
},
|
||||
.unbind_destination_texture = [&]() { events.emplace_back("unbind"); },
|
||||
.upload_brush_vertices = [&](std::span<const vertex_t> uploaded) {
|
||||
events.emplace_back("upload");
|
||||
uploaded_vertices.assign(uploaded.begin(), uploaded.end());
|
||||
},
|
||||
.draw_brush_shape = [&]() { events.emplace_back("draw"); },
|
||||
});
|
||||
|
||||
PP_EXPECT(h, result.ok);
|
||||
PP_EXPECT(h, result.copy_position == glm::ivec2(9, 19));
|
||||
PP_EXPECT(h, result.copy_size == glm::ivec2(22, 12));
|
||||
const std::vector<std::string> expected_events { "bind", "copy", "upload", "draw", "unbind" };
|
||||
PP_EXPECT(h, nearly_equal(result.dirty_bounds.x, 9.0F));
|
||||
PP_EXPECT(h, nearly_equal(result.dirty_bounds.y, 19.0F));
|
||||
PP_EXPECT(h, nearly_equal(result.dirty_bounds.z, 31.0F));
|
||||
PP_EXPECT(h, nearly_equal(result.dirty_bounds.w, 31.0F));
|
||||
PP_EXPECT(h, events == expected_events);
|
||||
PP_EXPECT(h, copy_args[0] == 9);
|
||||
PP_EXPECT(h, copy_args[1] == 19);
|
||||
PP_EXPECT(h, copy_args[2] == 9);
|
||||
PP_EXPECT(h, copy_args[3] == 19);
|
||||
PP_EXPECT(h, copy_args[4] == 22);
|
||||
PP_EXPECT(h, copy_args[5] == 12);
|
||||
PP_EXPECT(h, uploaded_vertices.size() == 6U);
|
||||
PP_EXPECT(h, nearly_equal(uploaded_vertices[0].pos.x, vertices[0].pos.x));
|
||||
PP_EXPECT(h, nearly_equal(uploaded_vertices[1].pos.y, vertices[1].pos.y));
|
||||
PP_EXPECT(h, nearly_equal(uploaded_vertices[2].pos.x, vertices[2].pos.x));
|
||||
PP_EXPECT(h, nearly_equal(uploaded_vertices[3].pos.x, vertices[0].pos.x));
|
||||
PP_EXPECT(h, nearly_equal(uploaded_vertices[4].pos.x, vertices[2].pos.x));
|
||||
PP_EXPECT(h, nearly_equal(uploaded_vertices[5].pos.y, vertices[3].pos.y));
|
||||
}
|
||||
|
||||
void retained_stroke_sample_executor_unbinds_and_skips_draw_when_bounds_are_empty(pp::tests::Harness& h)
|
||||
{
|
||||
const auto vertices = make_quad_vertices();
|
||||
|
||||
int bind_calls = 0;
|
||||
int copy_calls = 0;
|
||||
int unbind_calls = 0;
|
||||
int upload_calls = 0;
|
||||
int draw_calls = 0;
|
||||
|
||||
const auto result = pp::panopainter::execute_legacy_canvas_stroke_sample(
|
||||
LegacyStrokeSampleExecutionRequest {
|
||||
.context = "test",
|
||||
.target_size = glm::vec2(64.0F, 64.0F),
|
||||
.vertices = vertices,
|
||||
.sample_points = {},
|
||||
.copy_stroke_destination = true,
|
||||
.bind_destination_texture = [&]() { ++bind_calls; },
|
||||
.copy_framebuffer_to_destination_texture =
|
||||
[&](int, int, int, int, int, int) { ++copy_calls; },
|
||||
.unbind_destination_texture = [&]() { ++unbind_calls; },
|
||||
.upload_brush_vertices = [&](std::span<const vertex_t>) { ++upload_calls; },
|
||||
.draw_brush_shape = [&]() { ++draw_calls; },
|
||||
});
|
||||
|
||||
PP_EXPECT(h, !result.ok);
|
||||
PP_EXPECT(h, bind_calls == 1);
|
||||
PP_EXPECT(h, copy_calls == 0);
|
||||
PP_EXPECT(h, unbind_calls == 1);
|
||||
PP_EXPECT(h, upload_calls == 0);
|
||||
PP_EXPECT(h, draw_calls == 0);
|
||||
}
|
||||
|
||||
struct StrokeFrame {
|
||||
int id = -1;
|
||||
std::array<std::vector<vertex_t>, 6> shapes {};
|
||||
};
|
||||
|
||||
struct DummyFramebuffer {
|
||||
std::vector<std::string>* events = nullptr;
|
||||
int face_index = -1;
|
||||
|
||||
void bindFramebuffer()
|
||||
{
|
||||
events->push_back("bind:" + std::to_string(face_index));
|
||||
}
|
||||
|
||||
void unbindFramebuffer()
|
||||
{
|
||||
events->push_back("unbind:" + std::to_string(face_index));
|
||||
}
|
||||
};
|
||||
|
||||
void retained_stroke_live_pass_with_face_framebuffers_preserves_order_and_dirty_tracking(pp::tests::Harness& h)
|
||||
{
|
||||
StrokeFrame frame;
|
||||
frame.id = 7;
|
||||
frame.shapes[0] = {
|
||||
vertex_t(glm::vec2(0.0F, 0.0F)),
|
||||
vertex_t(glm::vec2(1.0F, 0.0F)),
|
||||
vertex_t(glm::vec2(1.0F, 1.0F)),
|
||||
};
|
||||
frame.shapes[2] = {
|
||||
vertex_t(glm::vec2(2.0F, 2.0F)),
|
||||
vertex_t(glm::vec2(3.0F, 2.0F)),
|
||||
vertex_t(glm::vec2(3.0F, 3.0F)),
|
||||
};
|
||||
|
||||
std::array<StrokeFrame, 1> frames { frame };
|
||||
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));
|
||||
accumulated_dirty_boxes[2] = glm::vec4(7.0F, 8.0F, 9.0F, 10.0F);
|
||||
pass_dirty_boxes[2] = glm::vec4(20.0F, 21.0F, 22.0F, 23.0F);
|
||||
std::array<bool, 6> include_in_committed_dirty_box { true, true, false, true, true, true };
|
||||
std::array<bool, 6> committed_dirty_faces {};
|
||||
std::array<bool, 6> pass_dirty_faces {};
|
||||
|
||||
std::vector<std::string> events;
|
||||
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 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));
|
||||
},
|
||||
[&](StrokeFrame&, int face_index, std::span<const vertex_t>) {
|
||||
events.push_back("prepare:" + std::to_string(face_index));
|
||||
},
|
||||
[&](StrokeFrame&, int face_index, std::span<const vertex_t>) {
|
||||
events.push_back("execute:" + std::to_string(face_index));
|
||||
if (face_index == 0) {
|
||||
return glm::vec4(1.0F, 2.0F, 3.0F, 4.0F);
|
||||
}
|
||||
return glm::vec4(10.0F, 11.0F, 12.0F, 13.0F);
|
||||
},
|
||||
face_framebuffers,
|
||||
true,
|
||||
committed_dirty_faces,
|
||||
pass_dirty_faces);
|
||||
|
||||
PP_EXPECT(h, executed_faces == 2U);
|
||||
const std::vector<std::string> expected_events {
|
||||
"begin-frame:7",
|
||||
"prepare:0",
|
||||
"bind:0",
|
||||
"execute:0",
|
||||
"unbind:0",
|
||||
"prepare:2",
|
||||
"bind:2",
|
||||
"execute:2",
|
||||
"unbind:2",
|
||||
};
|
||||
PP_EXPECT(h, events == expected_events);
|
||||
PP_EXPECT(h, accumulated_dirty_boxes[0].x <= 1.0F);
|
||||
PP_EXPECT(h, accumulated_dirty_boxes[0].y <= 2.0F);
|
||||
PP_EXPECT(h, nearly_equal(accumulated_dirty_boxes[0].z, 3.0F));
|
||||
PP_EXPECT(h, nearly_equal(accumulated_dirty_boxes[0].w, 4.0F));
|
||||
PP_EXPECT(h, nearly_equal(pass_dirty_boxes[0].x, 1.0F));
|
||||
PP_EXPECT(h, nearly_equal(pass_dirty_boxes[0].w, 4.0F));
|
||||
PP_EXPECT(h, committed_dirty_faces[0]);
|
||||
PP_EXPECT(h, pass_dirty_faces[0]);
|
||||
PP_EXPECT(h, nearly_equal(accumulated_dirty_boxes[2].x, 7.0F));
|
||||
PP_EXPECT(h, nearly_equal(accumulated_dirty_boxes[2].y, 8.0F));
|
||||
PP_EXPECT(h, nearly_equal(accumulated_dirty_boxes[2].z, 9.0F));
|
||||
PP_EXPECT(h, nearly_equal(accumulated_dirty_boxes[2].w, 10.0F));
|
||||
PP_EXPECT(h, nearly_equal(pass_dirty_boxes[2].x, 10.0F));
|
||||
PP_EXPECT(h, nearly_equal(pass_dirty_boxes[2].y, 11.0F));
|
||||
PP_EXPECT(h, nearly_equal(pass_dirty_boxes[2].z, 12.0F));
|
||||
PP_EXPECT(h, nearly_equal(pass_dirty_boxes[2].w, 13.0F));
|
||||
PP_EXPECT(h, !committed_dirty_faces[2]);
|
||||
PP_EXPECT(h, pass_dirty_faces[2]);
|
||||
}
|
||||
|
||||
void retained_stroke_pad_executor_copies_destination_for_dirty_faces_only(pp::tests::Harness& h)
|
||||
{
|
||||
const std::array<bool, 3> dirty_faces { true, false, true };
|
||||
const std::array<glm::vec4, 3> pass_dirty_boxes {
|
||||
glm::vec4(5.0F, 10.0F, 20.0F, 30.0F),
|
||||
glm::vec4(0.0F, 0.0F, 0.0F, 0.0F),
|
||||
glm::vec4(60.0F, 15.0F, 70.0F, 25.0F),
|
||||
};
|
||||
const auto faces = pp::panopainter::make_legacy_canvas_stroke_pad_faces(dirty_faces, pass_dirty_boxes);
|
||||
|
||||
std::vector<std::string> events;
|
||||
std::vector<pp::paint_renderer::CanvasStrokeCopyRegion> copy_regions;
|
||||
|
||||
const auto result = pp::panopainter::execute_legacy_canvas_stroke_pad_faces(
|
||||
LegacyCanvasStrokePadExecutionRequest {
|
||||
.context = "test",
|
||||
.extent = pp::renderer::Extent2D { .width = 100, .height = 80 },
|
||||
.faces = std::span<const LegacyCanvasStrokePadFace>(faces),
|
||||
.copy_stroke_destination = true,
|
||||
.upload_pad_vertices = [&](std::span<const vertex_t> vertices) {
|
||||
events.emplace_back("upload:" + std::to_string(vertices.size()));
|
||||
},
|
||||
.begin_face = [&](int face_index) {
|
||||
events.emplace_back("begin:" + std::to_string(face_index));
|
||||
},
|
||||
.bind_destination_texture = [&](int face_index) {
|
||||
events.emplace_back("bind:" + std::to_string(face_index));
|
||||
},
|
||||
.copy_framebuffer_to_destination_texture =
|
||||
[&](const pp::paint_renderer::CanvasStrokeCopyRegion& copy_region) {
|
||||
events.emplace_back("copy");
|
||||
copy_regions.push_back(copy_region);
|
||||
},
|
||||
.unbind_destination_texture = [&](int face_index) {
|
||||
events.emplace_back("unbind:" + std::to_string(face_index));
|
||||
},
|
||||
.draw_pad = [&]() { events.emplace_back("draw"); },
|
||||
.finish_face = [&](int face_index) {
|
||||
events.emplace_back("finish:" + std::to_string(face_index));
|
||||
},
|
||||
});
|
||||
|
||||
PP_EXPECT(h, result.ok);
|
||||
PP_EXPECT(h, result.padded_faces == 2U);
|
||||
PP_EXPECT(h, faces[0].index == 0);
|
||||
PP_EXPECT(h, faces[1].index == 1);
|
||||
PP_EXPECT(h, faces[2].index == 2);
|
||||
PP_EXPECT(h, faces[0].dirty);
|
||||
PP_EXPECT(h, !faces[1].dirty);
|
||||
PP_EXPECT(h, faces[2].dirty);
|
||||
const std::vector<std::string> expected_events {
|
||||
"upload:6",
|
||||
"begin:0",
|
||||
"bind:0",
|
||||
"copy",
|
||||
"draw",
|
||||
"unbind:0",
|
||||
"finish:0",
|
||||
"upload:6",
|
||||
"begin:2",
|
||||
"bind:2",
|
||||
"copy",
|
||||
"draw",
|
||||
"unbind:2",
|
||||
"finish:2",
|
||||
};
|
||||
PP_EXPECT(h, events == expected_events);
|
||||
PP_EXPECT(h, copy_regions.size() == 2U);
|
||||
PP_EXPECT(h, copy_regions[0].x == 0);
|
||||
PP_EXPECT(h, copy_regions[0].y == 0);
|
||||
PP_EXPECT(h, copy_regions[0].width == 40);
|
||||
PP_EXPECT(h, copy_regions[0].height == 50);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main()
|
||||
{
|
||||
pp::tests::Harness harness;
|
||||
harness.run(
|
||||
"retained_stroke_texture_inputs_bind_and_unbind_in_declared_order",
|
||||
retained_stroke_texture_inputs_bind_and_unbind_in_declared_order);
|
||||
harness.run(
|
||||
"retained_stroke_sample_executor_copies_destination_and_expands_quads",
|
||||
retained_stroke_sample_executor_copies_destination_and_expands_quads);
|
||||
harness.run(
|
||||
"retained_stroke_sample_executor_unbinds_and_skips_draw_when_bounds_are_empty",
|
||||
retained_stroke_sample_executor_unbinds_and_skips_draw_when_bounds_are_empty);
|
||||
harness.run(
|
||||
"retained_stroke_live_pass_with_face_framebuffers_preserves_order_and_dirty_tracking",
|
||||
retained_stroke_live_pass_with_face_framebuffers_preserves_order_and_dirty_tracking);
|
||||
harness.run(
|
||||
"retained_stroke_pad_executor_copies_destination_for_dirty_faces_only",
|
||||
retained_stroke_pad_executor_copies_destination_for_dirty_faces_only);
|
||||
return harness.finish();
|
||||
}
|
||||
Reference in New Issue
Block a user