blob: dd657b1a624748f0e06e6f4750adec8dac2dcf6b [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 <future>
#include <numeric>
#include "compute_tessellator.h"
#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/core/formats.h"
#include "impeller/core/host_buffer.h"
#include "impeller/display_list/skia_conversions.h"
#include "impeller/entity/contents/content_context.h"
#include "impeller/fixtures/golden_paths.h"
#include "impeller/fixtures/sample.comp.h"
#include "impeller/fixtures/stage1.comp.h"
#include "impeller/fixtures/stage2.comp.h"
#include "impeller/geometry/path.h"
#include "impeller/geometry/path_builder.h"
#include "impeller/geometry/path_component.h"
#include "impeller/playground/compute_playground_test.h"
#include "impeller/renderer/command_buffer.h"
#include "impeller/renderer/compute_pipeline_builder.h"
#include "impeller/renderer/compute_tessellator.h"
#include "impeller/renderer/path_polyline.comp.h"
#include "impeller/renderer/pipeline_library.h"
#include "impeller/renderer/render_pass.h"
#include "impeller/renderer/stroke.comp.h"
#include "third_party/imgui/imgui.h"
#include "third_party/skia/include/utils/SkParsePath.h"
namespace impeller {
namespace testing {
using ComputeSubgroupTest = ComputePlaygroundTest;
INSTANTIATE_COMPUTE_SUITE(ComputeSubgroupTest);
TEST_P(ComputeSubgroupTest, CapabilitiesSuportSubgroups) {
auto context = GetContext();
ASSERT_TRUE(context);
ASSERT_TRUE(context->GetCapabilities()->SupportsComputeSubgroups());
}
TEST_P(ComputeSubgroupTest, PathPlayground) {
// Renders stroked SVG paths in an interactive playground.
using SS = StrokeComputeShader;
auto context = GetContext();
ASSERT_TRUE(context);
ASSERT_TRUE(context->GetCapabilities()->SupportsComputeSubgroups());
char svg_path_data[16384] =
"M140 20 "
"C73 20 20 74 20 140 "
"c0 135 136 170 228 303 "
"88-132 229-173 229-303 "
"0-66-54-120-120-120 "
"-48 0-90 28-109 69 "
"-19-41-60-69-108-69z";
size_t vertex_count = 0;
Scalar stroke_width = 1.0;
auto vertex_buffer = CreateHostVisibleDeviceBuffer<SS::VertexBuffer<2048>>(
context, "VertexBuffer");
auto vertex_buffer_count =
CreateHostVisibleDeviceBuffer<SS::VertexBufferCount>(context,
"VertexCount");
auto callback = [&](RenderPass& pass) -> bool {
::memset(vertex_buffer_count->OnGetContents(), 0,
sizeof(SS::VertexBufferCount));
::memset(vertex_buffer->OnGetContents(), 0, sizeof(SS::VertexBuffer<2048>));
const auto* main_viewport = ImGui::GetMainViewport();
ImGui::SetNextWindowPos(
ImVec2(main_viewport->WorkPos.x + 650, main_viewport->WorkPos.y + 20));
ImGui::Begin("Path data", nullptr, ImGuiWindowFlags_AlwaysAutoResize);
ImGui::InputTextMultiline("Path", svg_path_data,
IM_ARRAYSIZE(svg_path_data));
ImGui::DragFloat("Stroke width", &stroke_width, .1, 0.0, 25.0);
SkPath sk_path;
if (SkParsePath::FromSVGString(svg_path_data, &sk_path)) {
std::promise<bool> promise;
auto future = promise.get_future();
auto path = skia_conversions::ToPath(sk_path);
auto host_buffer = HostBuffer::Create(context->GetResourceAllocator());
auto status =
ComputeTessellator{}
.SetStrokeWidth(stroke_width)
.Tessellate(path, *host_buffer, context,
DeviceBuffer::AsBufferView(vertex_buffer),
DeviceBuffer::AsBufferView(vertex_buffer_count),
[vertex_buffer_count, &vertex_count,
&promise](CommandBuffer::Status status) {
vertex_count =
reinterpret_cast<SS::VertexBufferCount*>(
vertex_buffer_count->OnGetContents())
->count;
promise.set_value(
status == CommandBuffer::Status::kCompleted);
});
switch (status) {
case ComputeTessellator::Status::kCommandInvalid:
ImGui::Text("Failed to submit compute job (invalid command)");
break;
case ComputeTessellator::Status::kTooManyComponents:
ImGui::Text("Failed to submit compute job (too many components) ");
break;
case ComputeTessellator::Status::kOk:
break;
}
if (!future.get()) {
ImGui::Text("Failed to submit compute job.");
return false;
}
if (vertex_count > 0) {
ImGui::Text("Vertex count: %zu", vertex_count);
}
} else {
ImGui::Text("Failed to parse path data");
}
ImGui::End();
ContentContext renderer(context, nullptr);
if (!renderer.IsValid()) {
return false;
}
using VS = SolidFillPipeline::VertexShader;
pass.SetCommandLabel("Draw Stroke");
pass.SetStencilReference(0);
ContentContextOptions options;
options.sample_count = pass.GetRenderTarget().GetSampleCount();
options.color_attachment_pixel_format =
pass.GetRenderTarget().GetRenderTargetPixelFormat();
options.has_depth_stencil_attachments =
pass.GetRenderTarget().GetStencilAttachment().has_value();
options.blend_mode = BlendMode::kSourceIn;
options.primitive_type = PrimitiveType::kTriangleStrip;
options.stencil_mode =
ContentContextOptions::StencilMode::kLegacyClipIncrement;
pass.SetPipeline(renderer.GetSolidFillPipeline(options));
auto count = reinterpret_cast<SS::VertexBufferCount*>(
vertex_buffer_count->OnGetContents())
->count;
pass.SetVertexBuffer(
VertexBuffer{.vertex_buffer = DeviceBuffer::AsBufferView(vertex_buffer),
.vertex_count = count,
.index_type = IndexType::kNone});
VS::FrameInfo frame_info;
auto world_matrix = Matrix::MakeScale(GetContentScale());
frame_info.mvp = pass.GetOrthographicTransform() * world_matrix;
frame_info.color = Color::Red().Premultiply();
VS::BindFrameInfo(
pass, renderer.GetTransientsBuffer().EmplaceUniform(frame_info));
return pass.Draw().ok();
};
ASSERT_TRUE(OpenPlaygroundHere(callback));
}
TEST_P(ComputeSubgroupTest, LargePath) {
// The path in here is large enough to highlight issues around exceeding
// subgroup size.
using SS = StrokeComputeShader;
auto context = GetContext();
ASSERT_TRUE(context);
ASSERT_TRUE(context->GetCapabilities()->SupportsComputeSubgroups());
size_t vertex_count = 0;
Scalar stroke_width = 1.0;
auto vertex_buffer = CreateHostVisibleDeviceBuffer<SS::VertexBuffer<2048>>(
context, "VertexBuffer");
auto vertex_buffer_count =
CreateHostVisibleDeviceBuffer<SS::VertexBufferCount>(context,
"VertexCount");
auto complex_path =
PathBuilder{}
.MoveTo({359.934, 96.6335})
.CubicCurveTo({358.189, 96.7055}, {356.436, 96.7908},
{354.673, 96.8895})
.CubicCurveTo({354.571, 96.8953}, {354.469, 96.9016},
{354.367, 96.9075})
.CubicCurveTo({352.672, 97.0038}, {350.969, 97.113},
{349.259, 97.2355})
.CubicCurveTo({349.048, 97.2506}, {348.836, 97.2678},
{348.625, 97.2834})
.CubicCurveTo({347.019, 97.4014}, {345.407, 97.5299},
{343.789, 97.6722})
.CubicCurveTo({343.428, 97.704}, {343.065, 97.7402},
{342.703, 97.7734})
.CubicCurveTo({341.221, 97.9086}, {339.736, 98.0505},
{338.246, 98.207})
.CubicCurveTo({337.702, 98.2642}, {337.156, 98.3292},
{336.612, 98.3894})
.CubicCurveTo({335.284, 98.5356}, {333.956, 98.6837},
{332.623, 98.8476})
.CubicCurveTo({332.495, 98.8635}, {332.366, 98.8818},
{332.237, 98.8982})
.LineTo({332.237, 102.601})
.LineTo({321.778, 102.601})
.LineTo({321.778, 100.382})
.CubicCurveTo({321.572, 100.413}, {321.367, 100.442},
{321.161, 100.476})
.CubicCurveTo({319.22, 100.79}, {317.277, 101.123},
{315.332, 101.479})
.CubicCurveTo({315.322, 101.481}, {315.311, 101.482},
{315.301, 101.484})
.LineTo({310.017, 105.94})
.LineTo({309.779, 105.427})
.LineTo({314.403, 101.651})
.CubicCurveTo({314.391, 101.653}, {314.379, 101.656},
{314.368, 101.658})
.CubicCurveTo({312.528, 102.001}, {310.687, 102.366},
{308.846, 102.748})
.CubicCurveTo({307.85, 102.955}, {306.855, 103.182}, {305.859, 103.4})
.CubicCurveTo({305.048, 103.579}, {304.236, 103.75},
{303.425, 103.936})
.LineTo({299.105, 107.578})
.LineTo({298.867, 107.065})
.LineTo({302.394, 104.185})
.LineTo({302.412, 104.171})
.CubicCurveTo({301.388, 104.409}, {300.366, 104.67},
{299.344, 104.921})
.CubicCurveTo({298.618, 105.1}, {297.89, 105.269}, {297.165, 105.455})
.CubicCurveTo({295.262, 105.94}, {293.36, 106.445},
{291.462, 106.979})
.CubicCurveTo({291.132, 107.072}, {290.802, 107.163},
{290.471, 107.257})
.CubicCurveTo({289.463, 107.544}, {288.455, 107.839},
{287.449, 108.139})
.CubicCurveTo({286.476, 108.431}, {285.506, 108.73},
{284.536, 109.035})
.CubicCurveTo({283.674, 109.304}, {282.812, 109.579},
{281.952, 109.859})
.CubicCurveTo({281.177, 110.112}, {280.406, 110.377},
{279.633, 110.638})
.CubicCurveTo({278.458, 111.037}, {277.256, 111.449},
{276.803, 111.607})
.CubicCurveTo({276.76, 111.622}, {276.716, 111.637},
{276.672, 111.653})
.CubicCurveTo({275.017, 112.239}, {273.365, 112.836},
{271.721, 113.463})
.LineTo({271.717, 113.449})
.CubicCurveTo({271.496, 113.496}, {271.238, 113.559},
{270.963, 113.628})
.CubicCurveTo({270.893, 113.645}, {270.822, 113.663},
{270.748, 113.682})
.CubicCurveTo({270.468, 113.755}, {270.169, 113.834},
{269.839, 113.926})
.CubicCurveTo({269.789, 113.94}, {269.732, 113.957},
{269.681, 113.972})
.CubicCurveTo({269.391, 114.053}, {269.081, 114.143},
{268.756, 114.239})
.CubicCurveTo({268.628, 114.276}, {268.5, 114.314},
{268.367, 114.354})
.CubicCurveTo({268.172, 114.412}, {267.959, 114.478},
{267.752, 114.54})
.CubicCurveTo({263.349, 115.964}, {258.058, 117.695},
{253.564, 119.252})
.CubicCurveTo({253.556, 119.255}, {253.547, 119.258},
{253.538, 119.261})
.CubicCurveTo({251.844, 119.849}, {250.056, 120.474},
{248.189, 121.131})
.CubicCurveTo({248, 121.197}, {247.812, 121.264}, {247.621, 121.331})
.CubicCurveTo({247.079, 121.522}, {246.531, 121.715},
{245.975, 121.912})
.CubicCurveTo({245.554, 122.06}, {245.126, 122.212},
{244.698, 122.364})
.CubicCurveTo({244.071, 122.586}, {243.437, 122.811},
{242.794, 123.04})
.CubicCurveTo({242.189, 123.255}, {241.58, 123.472},
{240.961, 123.693})
.CubicCurveTo({240.659, 123.801}, {240.357, 123.909},
{240.052, 124.018})
.CubicCurveTo({239.12, 124.351}, {238.18, 124.687}, {237.22, 125.032})
.LineTo({237.164, 125.003})
.CubicCurveTo({236.709, 125.184}, {236.262, 125.358},
{235.81, 125.538})
.CubicCurveTo({235.413, 125.68}, {234.994, 125.832},
{234.592, 125.977})
.CubicCurveTo({234.592, 125.977}, {234.591, 125.977},
{234.59, 125.977})
.CubicCurveTo({222.206, 130.435}, {207.708, 135.753},
{192.381, 141.429})
.CubicCurveTo({162.77, 151.336}, {122.17, 156.894}, {84.1123, 160})
.LineTo({360, 160})
.LineTo({360, 119.256})
.LineTo({360, 106.332})
.LineTo({360, 96.6307})
.CubicCurveTo({359.978, 96.6317}, {359.956, 96.6326},
{359.934, 96.6335})
.Close()
.TakePath();
auto callback = [&](RenderPass& pass) -> bool {
::memset(vertex_buffer_count->OnGetContents(), 0,
sizeof(SS::VertexBufferCount));
::memset(vertex_buffer->OnGetContents(), 0, sizeof(SS::VertexBuffer<2048>));
ContentContext renderer(context, nullptr);
if (!renderer.IsValid()) {
return false;
}
ComputeTessellator{}
.SetStrokeWidth(stroke_width)
.Tessellate(
complex_path, renderer.GetTransientsBuffer(), context,
DeviceBuffer::AsBufferView(vertex_buffer),
DeviceBuffer::AsBufferView(vertex_buffer_count),
[vertex_buffer_count, &vertex_count](CommandBuffer::Status status) {
vertex_count = reinterpret_cast<SS::VertexBufferCount*>(
vertex_buffer_count->OnGetContents())
->count;
});
using VS = SolidFillPipeline::VertexShader;
pass.SetCommandLabel("Draw Stroke");
pass.SetStencilReference(0);
ContentContextOptions options;
options.sample_count = pass.GetRenderTarget().GetSampleCount();
options.color_attachment_pixel_format =
pass.GetRenderTarget().GetRenderTargetPixelFormat();
options.has_depth_stencil_attachments =
pass.GetRenderTarget().GetStencilAttachment().has_value();
options.blend_mode = BlendMode::kSourceIn;
options.primitive_type = PrimitiveType::kTriangleStrip;
options.stencil_mode =
ContentContextOptions::StencilMode::kLegacyClipIncrement;
pass.SetPipeline(renderer.GetSolidFillPipeline(options));
auto count = reinterpret_cast<SS::VertexBufferCount*>(
vertex_buffer_count->OnGetContents())
->count;
pass.SetVertexBuffer(
VertexBuffer{.vertex_buffer = DeviceBuffer::AsBufferView(vertex_buffer),
.vertex_count = count,
.index_type = IndexType::kNone});
VS::FrameInfo frame_info;
auto world_matrix = Matrix::MakeScale(GetContentScale());
frame_info.mvp = pass.GetOrthographicTransform() * world_matrix;
frame_info.color = Color::Red().Premultiply();
VS::BindFrameInfo(
pass, renderer.GetTransientsBuffer().EmplaceUniform(frame_info));
return pass.Draw().ok();
};
ASSERT_TRUE(OpenPlaygroundHere(callback));
}
TEST_P(ComputeSubgroupTest, QuadAndCubicInOnePath) {
using SS = StrokeComputeShader;
auto context = GetContext();
ASSERT_TRUE(context);
ASSERT_TRUE(context->GetCapabilities()->SupportsComputeSubgroups());
auto vertex_buffer = CreateHostVisibleDeviceBuffer<SS::VertexBuffer<2048>>(
context, "VertexBuffer");
auto vertex_buffer_count =
CreateHostVisibleDeviceBuffer<SS::VertexBufferCount>(context,
"VertexBufferCount");
auto path = PathBuilder{}
.AddCubicCurve({140, 20}, {73, 20}, {20, 74}, {20, 140})
.AddQuadraticCurve({20, 140}, {93, 90}, {100, 42})
.TakePath();
auto tessellator = ComputeTessellator{};
fml::AutoResetWaitableEvent latch;
auto host_buffer = HostBuffer::Create(context->GetResourceAllocator());
auto status = tessellator.Tessellate(
path, *host_buffer, context, DeviceBuffer::AsBufferView(vertex_buffer),
DeviceBuffer::AsBufferView(vertex_buffer_count),
[&latch](CommandBuffer::Status status) {
EXPECT_EQ(status, CommandBuffer::Status::kCompleted);
latch.Signal();
});
ASSERT_EQ(status, ComputeTessellator::Status::kOk);
auto callback = [&](RenderPass& pass) -> bool {
ContentContext renderer(context, nullptr);
if (!renderer.IsValid()) {
return false;
}
using VS = SolidFillPipeline::VertexShader;
pass.SetCommandLabel("Draw Stroke");
pass.SetStencilReference(0);
ContentContextOptions options;
options.sample_count = pass.GetRenderTarget().GetSampleCount();
options.color_attachment_pixel_format =
pass.GetRenderTarget().GetRenderTargetPixelFormat();
options.has_depth_stencil_attachments =
pass.GetRenderTarget().GetStencilAttachment().has_value();
options.blend_mode = BlendMode::kSourceIn;
options.primitive_type = PrimitiveType::kTriangleStrip;
options.stencil_mode =
ContentContextOptions::StencilMode::kLegacyClipIncrement;
pass.SetPipeline(renderer.GetSolidFillPipeline(options));
auto count = reinterpret_cast<SS::VertexBufferCount*>(
vertex_buffer_count->OnGetContents())
->count;
pass.SetVertexBuffer(
VertexBuffer{.vertex_buffer = DeviceBuffer::AsBufferView(vertex_buffer),
.vertex_count = count,
.index_type = IndexType::kNone});
VS::FrameInfo frame_info;
auto world_matrix = Matrix::MakeScale(GetContentScale());
frame_info.mvp = pass.GetOrthographicTransform() * world_matrix;
frame_info.color = Color::Red().Premultiply();
VS::BindFrameInfo(
pass, renderer.GetTransientsBuffer().EmplaceUniform(frame_info));
return pass.Draw().ok();
};
ASSERT_TRUE(OpenPlaygroundHere(callback));
// The latch is down here because it's expected that on Metal the backend
// will take care of synchronizing the buffer between the compute and render
// pass usages, since it's not MTLHeap allocated. However, if playgrounds
// are disabled, no render pass actually gets submitted and we need to do a
// CPU latch here.
latch.Wait();
auto vertex_count = reinterpret_cast<SS::VertexBufferCount*>(
vertex_buffer_count->OnGetContents())
->count;
EXPECT_EQ(vertex_count, golden_cubic_and_quad_points.size());
auto vertex_buffer_data =
reinterpret_cast<SS::VertexBuffer<2048>*>(vertex_buffer->OnGetContents());
for (size_t i = 0; i < vertex_count; i++) {
EXPECT_LT(std::abs(golden_cubic_and_quad_points[i].x -
vertex_buffer_data->position[i].x),
1e-3);
EXPECT_LT(std::abs(golden_cubic_and_quad_points[i].y -
vertex_buffer_data->position[i].y),
1e-3);
}
}
} // namespace testing
} // namespace impeller