blob: dd5a1324eaeaabac3885abc5422b8cfb789c3b42 [file] [log] [blame]
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <numeric>
#include "flutter/fml/synchronization/waitable_event.h"
#include "flutter/fml/time/time_point.h"
#include "flutter/testing/testing.h"
#include "gmock/gmock.h"
#include "impeller/base/strings.h"
#include "impeller/entity/contents/content_context.h"
#include "impeller/fixtures/cubic_to_quads.comp.h"
#include "impeller/fixtures/golden_heart.h"
#include "impeller/fixtures/quad_polyline.comp.h"
#include "impeller/fixtures/sample.comp.h"
#include "impeller/fixtures/stage1.comp.h"
#include "impeller/fixtures/stage2.comp.h"
#include "impeller/fixtures/stroke.comp.h"
#include "impeller/geometry/path.h"
#include "impeller/geometry/path_component.h"
#include "impeller/playground/compute_playground_test.h"
#include "impeller/renderer/command_buffer.h"
#include "impeller/renderer/compute_command.h"
#include "impeller/renderer/compute_pipeline_builder.h"
#include "impeller/renderer/formats.h"
#include "impeller/renderer/pipeline_library.h"
#include "impeller/renderer/render_pass.h"
namespace impeller {
namespace testing {
using ComputeSubgroupTest = ComputePlaygroundTest;
INSTANTIATE_COMPUTE_SUITE(ComputeSubgroupTest);
TEST_P(ComputeSubgroupTest, HeartCubicsToStrokeVertices) {
using CS = CubicToQuadsComputeShader;
using QS = QuadPolylineComputeShader;
using SS = StrokeComputeShader;
auto context = GetContext();
ASSERT_TRUE(context);
ASSERT_TRUE(context->GetDeviceCapabilities().SupportsComputeSubgroups());
auto cmd_buffer = context->CreateCommandBuffer();
auto pass = cmd_buffer->CreateComputePass();
ASSERT_TRUE(pass && pass->IsValid());
static constexpr size_t kCubicCount = 6;
static constexpr Scalar kAccuracy = .1;
auto quads = CreateHostVisibleDeviceBuffer<CS::Quads<kCubicCount * 10>>(
context, "Quads");
// TODO(dnfield): Size this buffer more accurately.
auto polyline =
CreateHostVisibleDeviceBuffer<QS::Polyline<kCubicCount * 10 * 10>>(
context, "polyline");
auto vertex_buffer_count =
CreateHostVisibleDeviceBuffer<SS::VertexBufferCount>(context,
"VertexBufferCount");
// TODO(dnfield): Size this buffer more accurately.
auto vertex_buffer = CreateHostVisibleDeviceBuffer<
SS::VertexBuffer<kCubicCount * 10 * 10 * 4>>(context, "VertexBuffer");
{
using CubicPipelineBuilder = ComputePipelineBuilder<CS>;
auto pipeline_desc =
CubicPipelineBuilder::MakeDefaultPipelineDescriptor(*context);
ASSERT_TRUE(pipeline_desc.has_value());
auto compute_pipeline =
context->GetPipelineLibrary()->GetPipeline(pipeline_desc).Get();
ASSERT_TRUE(compute_pipeline);
pass->SetGridSize(ISize(1024, 1));
pass->SetThreadGroupSize(ISize(1024, 1));
ComputeCommand cmd;
cmd.label = "Cubic To Quads";
cmd.pipeline = compute_pipeline;
CS::Config config{.accuracy = kAccuracy};
CS::BindConfig(cmd, pass->GetTransientsBuffer().EmplaceUniform(config));
CS::Cubics<kCubicCount> gpu_cubics;
gpu_cubics.count = kCubicCount;
for (size_t i = 0; i < kCubicCount; i++) {
gpu_cubics.data[i] = {
golden_heart_cubics[i].p1, golden_heart_cubics[i].cp1,
golden_heart_cubics[i].cp2, golden_heart_cubics[i].p2};
}
CS::BindCubics(
cmd, pass->GetTransientsBuffer().EmplaceStorageBuffer(gpu_cubics));
CS::BindQuads(cmd, quads->AsBufferView());
ASSERT_TRUE(pass->AddCommand(std::move(cmd)));
}
{
using QuadPipelineBuilder = ComputePipelineBuilder<QS>;
auto pipeline_desc =
QuadPipelineBuilder::MakeDefaultPipelineDescriptor(*context);
ASSERT_TRUE(pipeline_desc.has_value());
auto compute_pipeline =
context->GetPipelineLibrary()->GetPipeline(pipeline_desc).Get();
ASSERT_TRUE(compute_pipeline);
pass->SetGridSize(ISize(1024, 1));
pass->SetThreadGroupSize(ISize(1024, 1));
ComputeCommand cmd;
cmd.label = "Quads to Polyline";
cmd.pipeline = compute_pipeline;
QS::Config config{.tolerance = kDefaultCurveTolerance};
QS::BindConfig(cmd, pass->GetTransientsBuffer().EmplaceUniform(config));
QS::BindQuads(cmd, quads->AsBufferView());
QS::BindPolyline(cmd, polyline->AsBufferView());
ASSERT_TRUE(pass->AddCommand(std::move(cmd)));
}
{
using StrokePipelineBuilder = ComputePipelineBuilder<SS>;
auto pipeline_desc =
StrokePipelineBuilder::MakeDefaultPipelineDescriptor(*context);
ASSERT_TRUE(pipeline_desc.has_value());
auto compute_pipeline =
context->GetPipelineLibrary()->GetPipeline(pipeline_desc).Get();
ASSERT_TRUE(compute_pipeline);
pass->SetGridSize(ISize(1024, 1));
pass->SetThreadGroupSize(ISize(1024, 1));
ComputeCommand cmd;
cmd.label = "Compute Stroke";
cmd.pipeline = compute_pipeline;
SS::Config config{.width = 1.0f, .cap = 1, .join = 1, .miter_limit = 4.0f};
SS::BindConfig(cmd, pass->GetTransientsBuffer().EmplaceUniform(config));
SS::BindPolyline(cmd, polyline->AsBufferView());
SS::BindVertexBufferCount(cmd, vertex_buffer_count->AsBufferView());
SS::BindVertexBuffer(cmd, vertex_buffer->AsBufferView());
ASSERT_TRUE(pass->AddCommand(std::move(cmd)));
}
ASSERT_TRUE(pass->EncodeCommands());
fml::AutoResetWaitableEvent latch;
ASSERT_TRUE(cmd_buffer->SubmitCommands([&latch, quads, polyline,
vertex_buffer_count, vertex_buffer](
CommandBuffer::Status status) {
EXPECT_EQ(status, CommandBuffer::Status::kCompleted);
auto* q = reinterpret_cast<CS::Quads<kCubicCount * 10>*>(
quads->AsBufferView().contents);
EXPECT_EQ(q->count, golden_heart_quads.size());
for (size_t i = 0; i < q->count; i++) {
EXPECT_LT(std::abs(golden_heart_quads[i].p1.x - q->data[i].p1.x), 1e-3);
EXPECT_LT(std::abs(golden_heart_quads[i].p1.y - q->data[i].p1.y), 1e-3);
EXPECT_LT(std::abs(golden_heart_quads[i].cp.x - q->data[i].cp.x), 1e-3);
EXPECT_LT(std::abs(golden_heart_quads[i].cp.y - q->data[i].cp.y), 1e-3);
EXPECT_LT(std::abs(golden_heart_quads[i].p2.x - q->data[i].p2.x), 1e-3);
EXPECT_LT(std::abs(golden_heart_quads[i].p2.y - q->data[i].p2.y), 1e-3);
}
auto* p = reinterpret_cast<QS::Polyline<kCubicCount * 10 * 10>*>(
polyline->AsBufferView().contents);
EXPECT_EQ(p->count, golden_heart_points.size());
for (size_t i = 0; i < p->count; i++) {
EXPECT_LT(std::abs(p->data[i].x - golden_heart_points[i].x), 1e-3);
EXPECT_LT(std::abs(p->data[i].y - golden_heart_points[i].y), 1e-3);
}
auto* v = reinterpret_cast<SS::VertexBuffer<kCubicCount * 10 * 10 * 4>*>(
vertex_buffer->AsBufferView().contents);
auto* v_count = reinterpret_cast<SS::VertexBufferCount*>(
vertex_buffer_count->AsBufferView().contents);
EXPECT_EQ(v_count->count, golden_heart_vertices.size());
for (size_t i = 0; i < v_count->count; i += 1) {
EXPECT_LT(std::abs(golden_heart_vertices[i].x - v->position[i].x), 1e-3);
EXPECT_LT(std::abs(golden_heart_vertices[i].y - v->position[i].y), 1e-3);
}
latch.Signal();
}));
latch.Wait();
auto callback = [&](RenderPass& pass) -> bool {
ContentContext renderer(context);
if (!renderer.IsValid()) {
return false;
}
using VS = SolidFillPipeline::VertexShader;
using FS = SolidFillPipeline::FragmentShader;
Command cmd;
cmd.label = "Draw Stroke";
cmd.stencil_reference = 0; // entity.GetStencilDepth();
ContentContextOptions options;
options.sample_count = pass.GetRenderTarget().GetSampleCount();
options.color_attachment_pixel_format =
pass.GetRenderTarget().GetRenderTargetPixelFormat();
options.has_stencil_attachment =
pass.GetRenderTarget().GetStencilAttachment().has_value();
options.blend_mode = BlendMode::kSourceIn; // entity.GetBlendMode();
options.primitive_type = PrimitiveType::kTriangleStrip;
options.stencil_compare = CompareFunction::kEqual;
options.stencil_operation = StencilOperation::kIncrementClamp;
cmd.pipeline = renderer.GetSolidFillPipeline(options);
auto count = reinterpret_cast<SS::VertexBufferCount*>(
vertex_buffer_count->AsBufferView().contents)
->count;
auto& host_buffer = pass.GetTransientsBuffer();
std::vector<uint16_t> indices(count);
std::iota(std::begin(indices), std::end(indices), 0);
VertexBuffer render_vertex_buffer{
.vertex_buffer = vertex_buffer->AsBufferView(),
.index_buffer = host_buffer.Emplace(
indices.data(), count * sizeof(uint16_t), alignof(uint16_t)),
.index_count = count,
.index_type = IndexType::k16bit};
cmd.BindVertices(render_vertex_buffer);
VS::FrameInfo frame_info;
auto world_matrix = Matrix::MakeScale(GetContentScale());
frame_info.mvp =
Matrix::MakeOrthographic(pass.GetRenderTargetSize()) * world_matrix;
VS::BindFrameInfo(cmd,
pass.GetTransientsBuffer().EmplaceUniform(frame_info));
FS::FragInfo frag_info;
frag_info.color = Color::Red().Premultiply();
FS::BindFragInfo(cmd, pass.GetTransientsBuffer().EmplaceUniform(frag_info));
if (!pass.AddCommand(std::move(cmd))) {
return false;
}
return true;
};
ASSERT_TRUE(OpenPlaygroundHere(callback));
}
TEST_P(ComputeSubgroupTest, QuadsToPolyline) {
using QS = QuadPolylineComputeShader;
auto context = GetContext();
ASSERT_TRUE(context);
ASSERT_TRUE(context->GetDeviceCapabilities().SupportsComputeSubgroups());
auto cmd_buffer = context->CreateCommandBuffer();
auto pass = cmd_buffer->CreateComputePass();
ASSERT_TRUE(pass && pass->IsValid());
static constexpr size_t kQuadCount = 26;
static constexpr size_t kPolylineCount = 1024;
QS::Quads<kQuadCount> quads;
quads.count = kQuadCount;
for (size_t i = 0; i < kQuadCount; i++) {
quads.data[i] = {golden_heart_quads[i].p1, golden_heart_quads[i].cp,
golden_heart_quads[i].p2};
}
auto polyline = CreateHostVisibleDeviceBuffer<QS::Polyline<kPolylineCount>>(
context, "polyline");
{
using QuadPipelineBuilder = ComputePipelineBuilder<QS>;
auto pipeline_desc =
QuadPipelineBuilder::MakeDefaultPipelineDescriptor(*context);
ASSERT_TRUE(pipeline_desc.has_value());
auto compute_pipeline =
context->GetPipelineLibrary()->GetPipeline(pipeline_desc).Get();
ASSERT_TRUE(compute_pipeline);
pass->SetGridSize(ISize(1024, 1));
pass->SetThreadGroupSize(ISize(1024, 1));
ComputeCommand cmd;
cmd.label = "Quads to Polyline";
cmd.pipeline = compute_pipeline;
QS::Config config{.tolerance = kDefaultCurveTolerance};
QS::BindConfig(cmd, pass->GetTransientsBuffer().EmplaceUniform(config));
QS::BindQuads(cmd, pass->GetTransientsBuffer().EmplaceStorageBuffer(quads));
QS::BindPolyline(cmd, polyline->AsBufferView());
ASSERT_TRUE(pass->AddCommand(std::move(cmd)));
}
ASSERT_TRUE(pass->EncodeCommands());
fml::AutoResetWaitableEvent latch;
ASSERT_TRUE(cmd_buffer->SubmitCommands(
[&latch, polyline](CommandBuffer::Status status) {
EXPECT_EQ(status, CommandBuffer::Status::kCompleted);
auto* p = reinterpret_cast<QS::Polyline<kPolylineCount>*>(
polyline->AsBufferView().contents);
EXPECT_EQ(p->count, golden_heart_points.size());
for (size_t i = 0; i < p->count; i++) {
EXPECT_LT(std::abs(p->data[i].x - golden_heart_points[i].x), 1e-3);
EXPECT_LT(std::abs(p->data[i].y - golden_heart_points[i].y), 1e-3);
}
latch.Signal();
}));
latch.Wait();
}
} // namespace testing
} // namespace impeller