Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

[Impeller] Start stroke tessellation in compute #39543

Merged
merged 13 commits into from
Feb 28, 2023
1 change: 1 addition & 0 deletions ci/licenses_golden/excluded_files
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@
../../../flutter/impeller/geometry/geometry_unittests.h
../../../flutter/impeller/image/README.md
../../../flutter/impeller/playground
../../../flutter/impeller/renderer/compute_subgroup_unittests.cc
../../../flutter/impeller/renderer/compute_unittests.cc
../../../flutter/impeller/renderer/device_buffer_unittests.cc
../../../flutter/impeller/renderer/host_buffer_unittests.cc
Expand Down
2 changes: 2 additions & 0 deletions ci/licenses_golden/licenses_flutter
Original file line number Diff line number Diff line change
Expand Up @@ -1088,6 +1088,7 @@ ORIGIN: ../../../flutter/impeller/compiler/shader_lib/impeller/color.glsl + ../.
ORIGIN: ../../../flutter/impeller/compiler/shader_lib/impeller/constants.glsl + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/compiler/shader_lib/impeller/gaussian.glsl + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/compiler/shader_lib/impeller/gradient.glsl + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/compiler/shader_lib/impeller/path.glsl + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/compiler/shader_lib/impeller/texture.glsl + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/compiler/shader_lib/impeller/transform.glsl + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/compiler/shader_lib/impeller/types.glsl + ../../../flutter/LICENSE
Expand Down Expand Up @@ -3584,6 +3585,7 @@ FILE: ../../../flutter/impeller/compiler/shader_lib/impeller/color.glsl
FILE: ../../../flutter/impeller/compiler/shader_lib/impeller/constants.glsl
FILE: ../../../flutter/impeller/compiler/shader_lib/impeller/gaussian.glsl
FILE: ../../../flutter/impeller/compiler/shader_lib/impeller/gradient.glsl
FILE: ../../../flutter/impeller/compiler/shader_lib/impeller/path.glsl
FILE: ../../../flutter/impeller/compiler/shader_lib/impeller/texture.glsl
FILE: ../../../flutter/impeller/compiler/shader_lib/impeller/transform.glsl
FILE: ../../../flutter/impeller/compiler/shader_lib/impeller/types.glsl
Expand Down
40 changes: 33 additions & 7 deletions impeller/compiler/compiler.cc
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
#include <array>
#include <filesystem>
#include <memory>
#include <optional>
#include <sstream>
#include <string>
#include <utility>

#include "flutter/fml/paths.h"
Expand All @@ -25,16 +27,36 @@ const uint32_t kFragBindingBase = 128;
const size_t kNumUniformKinds =
static_cast<int>(shaderc_uniform_kind::shaderc_uniform_kind_buffer) + 1;

static uint32_t ParseMSLVersion(const std::string& msl_version) {
std::stringstream sstream(msl_version);
std::string version_part;
uint32_t major = 1;
uint32_t minor = 2;
uint32_t patch = 0;
if (std::getline(sstream, version_part, '.')) {
major = std::stoi(version_part);
if (std::getline(sstream, version_part, '.')) {
minor = std::stoi(version_part);
if (std::getline(sstream, version_part, '.')) {
patch = std::stoi(version_part);
}
}
}
if (major < 1 || minor < 2) {
std::cerr << "--metal-version version must be at least 1.2. Have "
<< msl_version << std::endl;
}
return spirv_cross::CompilerMSL::Options::make_msl_version(major, minor,
patch);
}

static CompilerBackend CreateMSLCompiler(const spirv_cross::ParsedIR& ir,
const SourceOptions& source_options) {
auto sl_compiler = std::make_shared<spirv_cross::CompilerMSL>(ir);
spirv_cross::CompilerMSL::Options sl_options;
sl_options.platform =
TargetPlatformToMSLPlatform(source_options.target_platform);
// If this version specification changes, the GN rules that process the
// Metal to AIR must be updated as well.
sl_options.msl_version =
spirv_cross::CompilerMSL::Options::make_msl_version(1, 2);
sl_options.msl_version = ParseMSLVersion(source_options.metal_version);
sl_options.use_framebuffer_fetch_subpasses = true;
sl_compiler->set_msl_options(sl_options);

Expand Down Expand Up @@ -357,9 +379,9 @@ Compiler::Compiler(const fml::Mapping& source_mapping,
shaderc_optimization_level::shaderc_optimization_level_performance);
spirv_options.SetTargetEnvironment(
shaderc_target_env::shaderc_target_env_vulkan,
shaderc_env_version::shaderc_env_version_vulkan_1_0);
shaderc_env_version::shaderc_env_version_vulkan_1_1);
spirv_options.SetTargetSpirv(
shaderc_spirv_version::shaderc_spirv_version_1_0);
shaderc_spirv_version::shaderc_spirv_version_1_3);
Comment on lines +382 to +384
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@chinmaygarde or @iskakaushik - is this ok?

I'm not sure why we'd be targetting Vulkan 1.0 here but Vulkan 1.1 above.

I'm also not sure I understand the implications of using SPIR-V version 1.0 here. I need 1.3 for subgroups. I think it's probably ok but don't know where to look it up.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it's not ok all the time, I can parameterize this the way I did with the MSL version above.

break;
case TargetPlatform::kRuntimeStageMetal:
case TargetPlatform::kRuntimeStageGLES:
Expand Down Expand Up @@ -437,7 +459,11 @@ Compiler::Compiler(const fml::Mapping& source_mapping,
<< ShaderCErrorToString(spv_result_->GetCompilationStatus())
<< ". " << spv_result_->GetNumErrors() << " error(s) and "
<< spv_result_->GetNumWarnings() << " warning(s).";
if (spv_result_->GetNumErrors() > 0 || spv_result_->GetNumWarnings() > 0) {
// It should normally be enough to check that there are errors or warnings,
// but some cases result in no errors or warnings and still have an error
// message. If there's a message we should print it.
if (spv_result_->GetNumErrors() > 0 || spv_result_->GetNumWarnings() > 0 ||
!spv_result_->GetErrorMessage().empty()) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the part that fixes flutter/flutter#120241

COMPILER_ERROR_NO_PREFIX << spv_result_->GetErrorMessage();
}
return;
Expand Down
1 change: 1 addition & 0 deletions impeller/compiler/impellerc_main.cc
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ bool Main(const fml::CommandLine& command_line) {
switches.entry_point);
options.json_format = switches.json_format;
options.gles_language_version = switches.gles_language_version;
options.metal_version = switches.metal_version;

Reflector::Options reflector_options;
reflector_options.target_platform = switches.target_platform;
Expand Down
1 change: 1 addition & 0 deletions impeller/compiler/shader_lib/impeller/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ copy("impeller") {
"constants.glsl",
"gaussian.glsl",
"gradient.glsl",
"path.glsl",
"texture.glsl",
"transform.glsl",
"types.glsl",
Expand Down
68 changes: 68 additions & 0 deletions impeller/compiler/shader_lib/impeller/path.glsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// 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.

#ifndef PATH_GLSL_
#define PATH_GLSL_

#define MOVE 0
#define LINE 1
#define QUAD 2
#define CUBIC 3

struct LineData {
vec2 p1;
vec2 p2;
};

struct QuadData {
vec2 p1;
vec2 cp;
vec2 p2;
};

struct CubicData {
vec2 p1;
vec2 cp1;
vec2 cp2;
vec2 p2;
};

struct Position {
uint index;
uint count;
};

/// Solve for point on a quadratic Bezier curve defined by starting point `p1`,
/// control point `cp`, and end point `p2` at time `t`.
vec2 QuadraticSolve(QuadData quad, float t) {
return (1.0 - t) * (1.0 - t) * quad.p1 + //
2.0 * (1.0 - t) * t * quad.cp + //
t * t * quad.p2;
}

vec2 CubicSolve(CubicData cubic, float t) {
return (1. - t) * (1. - t) * (1. - t) * cubic.p1 + //
3 * (1. - t) * (1. - t) * t * cubic.cp1 + //
3 * (1. - t) * t * t * cubic.cp2 + //
t * t * t * cubic.p2;
}

/// Used to approximate quadratic curves using parabola.
///
/// See
/// https://raphlinus.github.io/graphics/curves/2019/12/23/flatten-quadbez.html
float ApproximateParabolaIntegral(float x) {
float d = 0.67;
return x / (1.0 - d + sqrt(sqrt(pow(d, 4) + 0.25 * x * x)));
}

bool isfinite(float f) {
return !isnan(f) && !isinf(f);
}

float Cross(vec2 p1, vec2 p2) {
return p1.x * p2.y - p1.y * p2.x;
}

#endif
1 change: 1 addition & 0 deletions impeller/compiler/source_options.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ struct SourceOptions {
uint32_t gles_language_version = 100;
std::vector<std::string> defines;
bool json_format = false;
std::string metal_version;

SourceOptions();

Expand Down
2 changes: 2 additions & 0 deletions impeller/compiler/switches.cc
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,8 @@ Switches::Switches(const fml::CommandLine& command_line)
gles_language_version(
stoi(command_line.GetOptionValueWithDefault("gles-language-version",
"0"))),
metal_version(
command_line.GetOptionValueWithDefault("metal-version", "1.2")),
entry_point(
command_line.GetOptionValueWithDefault("entry-point", "main")) {
auto language =
Expand Down
1 change: 1 addition & 0 deletions impeller/compiler/switches.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ struct Switches {
bool json_format;
SourceLanguage source_language = SourceLanguage::kUnknown;
uint32_t gles_language_version;
std::string metal_version;
std::string entry_point;

Switches();
Expand Down
23 changes: 23 additions & 0 deletions impeller/fixtures/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,22 @@
import("//flutter/impeller/tools/impeller.gni")
import("//flutter/testing/testing.gni")

if (impeller_enable_vulkan || impeller_enable_metal) {
impeller_shaders("shader_subgroup_fixtures") {
enable_opengles = false
name = "subgroup_fixtures"

# Only need 2.1 for desktop. Running on iOS would require 2.4
metal_version = "2.1"

shaders = [
"cubic_to_quads.comp",
"quad_polyline.comp",
"stroke.comp",
]
}
}

impeller_shaders("shader_fixtures") {
name = "fixtures"

Expand Down Expand Up @@ -70,10 +86,12 @@ test_fixtures("file_fixtures") {
"bay_bridge.jpg",
"blue_noise.png",
"boston.jpg",
"cubic_to_quads.comp",
"embarcadero.jpg",
"flutter_logo_baked.glb",
"kalimba.jpg",
"multiple_stages.hlsl",
"quad_polyline.comp",
"resources_limit.vert",
"sample.comp",
"sample.frag",
Expand All @@ -85,6 +103,7 @@ test_fixtures("file_fixtures") {
"sa%m#ple.vert",
"stage1.comp",
"stage2.comp",
"stroke.comp",
"struct_def_bug.vert",
"table_mountain_nx.png",
"table_mountain_ny.png",
Expand Down Expand Up @@ -117,4 +136,8 @@ group("fixtures") {
":scene_fixtures",
":shader_fixtures",
]

if (impeller_enable_vulkan || impeller_enable_metal) {
public_deps += [ ":shader_subgroup_fixtures" ]
}
}
83 changes: 83 additions & 0 deletions impeller/fixtures/cubic_to_quads.comp
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// 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.
#extension GL_KHR_shader_subgroup_arithmetic : enable

layout(local_size_x = 512, local_size_y = 1) in;
layout(std430) buffer;

#include <impeller/path.glsl>

layout(binding = 0) readonly buffer Cubics {
uint count;
CubicData data[];
}
cubics;

layout(binding = 1) buffer Quads {
uint count;
QuadData data[];
}
quads;

uniform Config {
float accuracy;
}
config;

shared uint quad_counts[512];
shared uint count_sums[512];

void main() {
uint ident = gl_GlobalInvocationID.x;
if (ident >= cubics.count) {
return;
}

// The maximum error, as a vector from the cubic to the best approximating
// quadratic, is proportional to the third derivative, which is constant
// across the segment. Thus, the error scales down as the third power of
// the number of subdivisions. Our strategy then is to subdivide `t` evenly.
//
// This is an overestimate of the error because only the component
// perpendicular to the first derivative is important. But the simplicity is
// appealing.

// This magic number is the square of 36 / sqrt(3).
// See: http://caffeineowl.com/graphics/2d/vectorial/cubic2quad01.html
float max_hypot2 = 432.0 * config.accuracy * config.accuracy;

CubicData cubic = cubics.data[ident];

vec2 err_v = 3.0 * (cubic.cp2 - cubic.cp1) + cubic.p1 - cubic.p2;
float err = dot(err_v, err_v);
float quad_count = max(1., ceil(pow(err * (1.0 / max_hypot2), 1. / 6.0)));

quad_counts[ident] = uint(quad_count);

barrier();
count_sums[ident] = subgroupInclusiveAdd(quad_counts[ident]);

quads.count = count_sums[cubics.count - 1];
for (uint i = 0; i < quad_count; i++) {
float t0 = i / quad_count;
float t1 = (i + 1) / quad_count;

// calculate the subsegment
vec2 sub_p1 = CubicSolve(cubic, t0);
vec2 sub_p2 = CubicSolve(cubic, t1);
QuadData quad = QuadData(3.0 * (cubic.cp1 - cubic.p1), //
3.0 * (cubic.cp2 - cubic.cp1), //
3.0 * (cubic.p2 - cubic.cp2));
float sub_scale = (t1 - t0) * (1.0 / 3.0);
vec2 sub_cp1 = sub_p1 + sub_scale * QuadraticSolve(quad, t0);
vec2 sub_cp2 = sub_p2 - sub_scale * QuadraticSolve(quad, t1);

vec2 quad_p1x2 = 3.0 * sub_cp1 - sub_p1;
vec2 quad_p2x2 = 3.0 * sub_cp2 - sub_p2;
uint offset = count_sums[ident] - uint(quad_count);
quads.data[offset + i] = QuadData(sub_p1, //
((quad_p1x2 + quad_p2x2) / 4.0), //
sub_p2);
}
}
Loading