Replace FragmentShader with FragmentShaderBuilder (flutter/engine#29231)

This commit is contained in:
Christopher Crawford
2021-10-27 14:38:02 -04:00
committed by GitHub
parent 2c8702d096
commit 31e6e6c23e
10 changed files with 293 additions and 143 deletions

View File

@@ -362,6 +362,8 @@ FILE: ../../../flutter/lib/ui/painting/color_filter.cc
FILE: ../../../flutter/lib/ui/painting/color_filter.h
FILE: ../../../flutter/lib/ui/painting/engine_layer.cc
FILE: ../../../flutter/lib/ui/painting/engine_layer.h
FILE: ../../../flutter/lib/ui/painting/fragment_program.cc
FILE: ../../../flutter/lib/ui/painting/fragment_program.h
FILE: ../../../flutter/lib/ui/painting/fragment_shader.cc
FILE: ../../../flutter/lib/ui/painting/fragment_shader.h
FILE: ../../../flutter/lib/ui/painting/gradient.cc

View File

@@ -30,6 +30,8 @@ source_set("ui") {
"painting/color_filter.h",
"painting/engine_layer.cc",
"painting/engine_layer.h",
"painting/fragment_program.cc",
"painting/fragment_program.h",
"painting/fragment_shader.cc",
"painting/fragment_shader.h",
"painting/gradient.cc",

View File

@@ -13,7 +13,7 @@
#include "flutter/lib/ui/painting/codec.h"
#include "flutter/lib/ui/painting/color_filter.h"
#include "flutter/lib/ui/painting/engine_layer.h"
#include "flutter/lib/ui/painting/fragment_shader.h"
#include "flutter/lib/ui/painting/fragment_program.h"
#include "flutter/lib/ui/painting/gradient.h"
#include "flutter/lib/ui/painting/image.h"
#include "flutter/lib/ui/painting/image_descriptor.h"
@@ -67,7 +67,7 @@ void DartUI::InitForGlobal() {
DartRuntimeHooks::RegisterNatives(g_natives);
EngineLayer::RegisterNatives(g_natives);
FontCollection::RegisterNatives(g_natives);
FragmentShader::RegisterNatives(g_natives);
FragmentProgram::RegisterNatives(g_natives);
ImageDescriptor::RegisterNatives(g_natives);
ImageFilter::RegisterNatives(g_natives);
ImageShader::RegisterNatives(g_natives);

View File

@@ -3751,108 +3751,128 @@ class ImageShader extends Shader {
void _initWithImage(_Image image, int tmx, int tmy, int filterQualityIndex, Float64List matrix4) native 'ImageShader_initWithImage';
}
/// A shader (as used by [Paint.shader]) that runs provided SPIR-V code.
/// An instance of [FragmentProgram] creates [Shader] objects (as used by [Paint.shader]) that run SPIR-V code.
///
/// This API is in beta and does not yet work on web.
/// See https://github.com/flutter/flutter/projects/207 for roadmap.
///
/// [A current specification of valid SPIR-V is here.](https://github.com/flutter/engine/blob/main/lib/spirv/README.md)
///
/// When initializing or updating the `floatUniforms`, the length of float
/// uniforms must match the total number of floats defined as uniforms in
/// the shader. They will be updated in the order that they are defined.
///
/// For example, if there are 3 uniforms: 1 of type float, 1 type float2/vec2,
/// and 1 of type vec3/float3, and 1 mat2x2 then the length of `floatUniforms`
/// must be 10.
///
/// The uniforms could be updated as follows:
///
/// Consider the following snippit of GLSL code.
///
/// ```
/// layout (location = 0) uniform float a;
/// layout (location = 1) uniform vec2 b;
/// layout (location = 2) uniform vec3 c;
/// layout (location = 3) uniform mat2x2 d;
/// ```
///
/// After being compiled to SPIR-V using [shaderc](https://github.com/google/shaderc)
/// and provided to the constructor, `floatUniforms` must always have a length
/// of 10. One per float-component of each uniform.
///
/// Dart code to update uniforms.
///
/// `shader.update(floatUniforms: Float32List.fromList([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]));`
///
/// Results of shader uniforms.
///
/// a: 1
/// b: [2, 3]
/// c: [4, 5, 6]
/// d: [7, 8, 9, 10] // 2x2 matrix in column-major order
///
class FragmentShader extends Shader {
class FragmentProgram extends NativeFieldWrapperClass1 {
// TODO(chriscraws): Add `List<Shader>? children` as a parameter to the
// constructor and to [update].
// https://github.com/flutter/flutter/issues/85240
/// Creates a fragment shader from SPIR-V byte data as an input.
/// Creates a fragment program from SPIR-V byte data as an input.
///
/// One instance should be created per SPIR-V input. The constructed object
/// should then be reused via the [shader] method to create [Shader] objects
/// that can be used by [Shader.paint].
///
/// [A current specification of valid SPIR-V is here.](https://github.com/flutter/engine/blob/master/lib/spirv/README.md)
/// SPIR-V not meeting this specification will throw an exception.
///
/// `floatUniforms` can be passed optionally to initialize the shader's
/// uniforms. If they are not initially set, they will default
/// to 0. They can later be updated by invoking the [update] method.
///
/// `floatUniforms` must be sized correctly, or an [ArgumentError] will
/// be thrown. See [FragmentShader] docs for details.
///
/// The compilation of a shader gets more expensive the more complicated the source is.
/// Because of this, it is reccommended to construct a FragmentShader asynchrounously,
/// outside of a widget's `build` method, to minimize the chance of UI jank.
/// Performance of shader-compilation is platform dependent and is not
/// well-specified. Because of this, it is reccommended to construct
/// `FragmentProgram` asynchronously, outside of a widget's `build`
/// method; this will minimize the chance of UI jank.
@pragma('vm:entry-point')
FragmentShader({
FragmentProgram({
required ByteBuffer spirv,
Float32List? floatUniforms,
bool debugPrint = false,
}) : super._() {
}) {
_constructor();
final spv.TranspileResult result = spv.transpile(
spirv,
spv.TargetLanguage.sksl,
);
_uniformFloatCount = result.uniformFloatCount;
_init(result.src, debugPrint);
update(floatUniforms: floatUniforms ?? Float32List(_uniformFloatCount));
_uniformFloatCount = result.uniformFloatCount;
}
late final int _uniformFloatCount;
void _constructor() native 'FragmentShader_constructor';
void _init(String sksl, bool debugPrint) native 'FragmentShader_init';
void _constructor() native 'FragmentProgram_constructor';
void _init(String sksl, bool debugPrint) native 'FragmentProgram_init';
/// Updates the uniform values that are supplied to the [FragmentShader]
/// and refreshes the shader.
// TODO(chriscraws): Add `List<ImageShader>? children` as a parameter to [build].
// https://github.com/flutter/flutter/issues/85240
/// Constructs a [Shader] object suitable for use by [Paint.shader] with
/// the given uniforms.
///
/// `floatUniforms` must be sized correctly, or an [ArgumentError] will
/// be thrown. See [FragmentShader] docs for details.
/// This method is suitable to be called synchronously within a widget's
/// `build` method or from [CustomPainter.paint].
///
/// This method will aquire additional fields as [FragmentShader] is
/// implemented further.
void update({
required Float32List floatUniforms,
/// `floatUniforms` can be passed optionally to initialize the shader's
/// uniforms. If they are not set they will each default to 0.
///
/// When initializing `floatUniforms`, the length of float uniforms must match
/// the total number of floats defined as uniforms in the shader, or an
/// [ArgumentError] will be thrown. Details are below.
///
/// Consider the following snippit of GLSL code.
///
/// ```
/// layout (location = 0) uniform float a;
/// layout (location = 1) uniform vec2 b;
/// layout (location = 2) uniform vec3 c;
/// layout (location = 3) uniform mat2x2 d;
/// ```
///
/// When compiled to SPIR-V and provided to the constructor, `floatUniforms`
/// must have a length of 10. One per float-component of each uniform.
///
/// `program.shader(floatUniforms: Float32List.fromList([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]));`
///
/// The uniforms will be set as follows:
///
/// a: 1
/// b: [2, 3]
/// c: [4, 5, 6]
/// d: [7, 8, 9, 10] // 2x2 matrix in column-major order
///
/// Once a [Shader] is built, uniform values cannot be changed. Instead,
/// [shader] must be called again with new uniform values.
Shader shader({
Float32List? floatUniforms,
}) {
if (floatUniforms == null) {
floatUniforms = Float32List(_uniformFloatCount);
}
if (floatUniforms.length != _uniformFloatCount) {
throw ArgumentError(
'FragmentShader floatUniforms size: ${floatUniforms.length} must match given shader uniform count: $_uniformFloatCount.');
}
_update(floatUniforms);
final _FragmentShader shader = _FragmentShader(this, Float32List.fromList(floatUniforms));
_shader(shader, floatUniforms);
return shader;
}
void _update(Float32List floatUniforms) native 'FragmentShader_update';
void _shader(_FragmentShader shader, Float32List floatUniforms) native 'FragmentProgram_shader';
}
@pragma('vm:entry-point')
class _FragmentShader extends Shader {
/// This class is created by the engine and should not be instantiated
/// or extended directly.
///
/// To create a [_FragmentShader], use a [FragmentProgram].
_FragmentShader(this._builder, this._floatUniforms) : super._();
final FragmentProgram _builder;
final Float32List _floatUniforms;
@override
bool operator ==(Object other) {
if (identical(this, other))
return true;
if (other.runtimeType != runtimeType)
return false;
return other is _FragmentShader
&& other._builder == _builder
&& _listEquals<double>(other._floatUniforms, _floatUniforms);
}
@override
int get hashCode => hashValues(_builder, hashList(_floatUniforms));
}
/// Defines how a list of points is interpreted when drawing a set of triangles.

View File

@@ -0,0 +1,74 @@
// 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 <iostream>
#include "flutter/lib/ui/painting/fragment_program.h"
#include "flutter/lib/ui/dart_wrapper.h"
#include "flutter/lib/ui/ui_dart_state.h"
#include "third_party/skia/include/core/SkString.h"
#include "third_party/tonic/converter/dart_converter.h"
#include "third_party/tonic/dart_args.h"
#include "third_party/tonic/dart_binding_macros.h"
#include "third_party/tonic/dart_library_natives.h"
#include "third_party/tonic/typed_data/typed_list.h"
using tonic::ToDart;
namespace flutter {
static void FragmentProgram_constructor(Dart_NativeArguments args) {
DartCallConstructor(&FragmentProgram::Create, args);
}
IMPLEMENT_WRAPPERTYPEINFO(ui, FragmentProgram);
#define FOR_EACH_BINDING(V) \
V(FragmentProgram, init) \
V(FragmentProgram, shader)
FOR_EACH_BINDING(DART_NATIVE_CALLBACK)
void FragmentProgram::RegisterNatives(tonic::DartLibraryNatives* natives) {
natives->Register(
{{"FragmentProgram_constructor", FragmentProgram_constructor, 1, true},
FOR_EACH_BINDING(DART_REGISTER_NATIVE)});
}
void FragmentProgram::init(std::string sksl, bool debugPrintSksl) {
SkRuntimeEffect::Result result =
SkRuntimeEffect::MakeForShader(SkString(sksl));
runtime_effect_ = result.effect;
if (runtime_effect_ == nullptr) {
Dart_ThrowException(tonic::ToDart(
std::string("Invalid SkSL:\n") + sksl.c_str() +
std::string("\nSkSL Error:\n") + result.errorText.c_str()));
return;
}
if (debugPrintSksl) {
FML_DLOG(INFO) << std::string("debugPrintSksl:\n") + sksl.c_str();
}
}
fml::RefPtr<FragmentShader> FragmentProgram::shader(
Dart_Handle shader,
const tonic::Float32List& uniforms) {
auto sk_shader = runtime_effect_->makeShader(
SkData::MakeWithCopy(uniforms.data(),
uniforms.num_elements() * sizeof(float)),
0, 0, nullptr, false);
return FragmentShader::Create(shader, std::move(sk_shader));
}
fml::RefPtr<FragmentProgram> FragmentProgram::Create() {
return fml::MakeRefCounted<FragmentProgram>();
}
FragmentProgram::FragmentProgram() = default;
FragmentProgram::~FragmentProgram() = default;
} // namespace flutter

View File

@@ -0,0 +1,45 @@
// 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 FLUTTER_LIB_UI_PAINTING_FRAGMENT_PROGRAM_H_
#define FLUTTER_LIB_UI_PAINTING_FRAGMENT_PROGRAM_H_
#include "flutter/lib/ui/dart_wrapper.h"
#include "flutter/lib/ui/painting/fragment_shader.h"
#include "third_party/skia/include/effects/SkRuntimeEffect.h"
#include "third_party/tonic/dart_library_natives.h"
#include "third_party/tonic/typed_data/typed_list.h"
#include <string>
#include <vector>
namespace tonic {
class DartLibraryNatives;
} // namespace tonic
namespace flutter {
class FragmentProgram : public RefCountedDartWrappable<FragmentProgram> {
DEFINE_WRAPPERTYPEINFO();
FML_FRIEND_MAKE_REF_COUNTED(FragmentProgram);
public:
~FragmentProgram() override;
static fml::RefPtr<FragmentProgram> Create();
void init(std::string sksl, bool debugPrintSksl);
fml::RefPtr<FragmentShader> shader(Dart_Handle shader,
const tonic::Float32List& uniforms);
static void RegisterNatives(tonic::DartLibraryNatives* natives);
private:
FragmentProgram();
sk_sp<SkRuntimeEffect> runtime_effect_;
};
} // namespace flutter
#endif // FLUTTER_LIB_UI_PAINTING_FRAGMENT_PROGRAM_H_

View File

@@ -19,60 +19,35 @@ using tonic::ToDart;
namespace flutter {
static void FragmentShader_constructor(Dart_NativeArguments args) {
DartCallConstructor(&FragmentShader::Create, args);
}
IMPLEMENT_WRAPPERTYPEINFO(ui, FragmentShader);
#define FOR_EACH_BINDING(V) \
V(FragmentShader, init) \
V(FragmentShader, update)
FOR_EACH_BINDING(DART_NATIVE_CALLBACK)
// Since _FragmentShader is a private class, we can't use
// IMPLEMENT_WRAPPERTYPEINFO
static const tonic::DartWrapperInfo kDartWrapperInfo_ui_FragmentShader = {
"ui",
"_FragmentShader",
sizeof(FragmentShader),
};
const tonic::DartWrapperInfo& FragmentShader::dart_wrapper_info_ =
kDartWrapperInfo_ui_FragmentShader;
void FragmentShader::RegisterNatives(tonic::DartLibraryNatives* natives) {
natives->Register(
{{"FragmentShader_constructor", FragmentShader_constructor, 1, true},
FOR_EACH_BINDING(DART_REGISTER_NATIVE)});
natives->Register({});
}
sk_sp<SkShader> FragmentShader::shader(SkSamplingOptions sampling) {
// TODO(antrob): Use sampling?
// https://github.com/flutter/flutter/issues/88303
// Sampling options are ignored, since sampling options don't make sense for
// generative shaders.
return shader_;
}
void FragmentShader::init(std::string sksl, bool debugPrintSksl) {
SkRuntimeEffect::Result result =
SkRuntimeEffect::MakeForShader(SkString(sksl));
runtime_effect_ = result.effect;
if (runtime_effect_ == nullptr) {
Dart_ThrowException(tonic::ToDart(
std::string("Invalid SkSL:\n") + sksl.c_str() +
std::string("\nSkSL Error:\n") + result.errorText.c_str()));
return;
}
if (debugPrintSksl) {
FML_DLOG(INFO) << std::string("debugPrintSksl:\n") + sksl.c_str();
}
fml::RefPtr<FragmentShader> FragmentShader::Create(Dart_Handle dart_handle,
sk_sp<SkShader> shader) {
auto fragment_shader = fml::MakeRefCounted<FragmentShader>(std::move(shader));
fragment_shader->AssociateWithDartWrapper(dart_handle);
return fragment_shader;
}
// TODO(https://github.com/flutter/flutter/issues/85240):
// Add `Dart_Handle children` as a paramter.
void FragmentShader::update(const tonic::Float32List& uniforms) {
shader_ = runtime_effect_->makeShader(
SkData::MakeWithCopy(uniforms.data(),
uniforms.num_elements() * sizeof(float)),
0, 0, nullptr, false);
}
fml::RefPtr<FragmentShader> FragmentShader::Create() {
return fml::MakeRefCounted<FragmentShader>();
}
FragmentShader::FragmentShader() = default;
FragmentShader::FragmentShader(sk_sp<SkShader> shader)
: shader_(std::move(shader)) {}
FragmentShader::~FragmentShader() = default;

View File

@@ -29,26 +29,16 @@ class FragmentShader : public Shader {
public:
~FragmentShader() override;
static fml::RefPtr<FragmentShader> Create();
static fml::RefPtr<FragmentShader> Create(Dart_Handle dart_handle,
sk_sp<SkShader> shader);
sk_sp<SkShader> shader(SkSamplingOptions) override;
void init(std::string sksl, bool debugPrintSksl);
void update(const tonic::Float32List& uniforms);
static void RegisterNatives(tonic::DartLibraryNatives* natives);
private:
FragmentShader();
FragmentShader(sk_sp<SkShader> shader);
void setShader();
// Since the shader source cannot be updated, the effect can be
// created once and re-used.
sk_sp<SkRuntimeEffect> runtime_effect_;
// A new shader is created every time update is called.
sk_sp<SkShader> shader_;
};

View File

@@ -789,16 +789,16 @@ class ImageDescriptor {
}
}
class FragmentShader extends Shader {
FragmentShader({
class FragmentProgram {
FragmentProgram({
required ByteBuffer spirv, // ignore: avoid_unused_constructor_parameters
Float32List? floatUniforms, // ignore: avoid_unused_constructor_parameters
bool debugPrint = false, // ignore: avoid_unused_constructor_parameters
}) : super._() {
throw UnsupportedError('FragmentShader is not supported for the CanvasKit or HTML renderers.');
}) {
throw UnsupportedError('FragmentProgram is not supported for the CanvasKit or HTML renderers.');
}
void update({Float32List? floatUniforms}) =>
throw UnsupportedError('FragmentShader is not supported for the CanvasKit or HTML renderers.');
Shader shader({
required Float32List floatUniforms,
}) => throw UnsupportedError('FragmentProgram is not supported for the CanvasKit or HTML renderers.');
}

View File

@@ -16,13 +16,15 @@ import 'shader_test_file_utils.dart';
void main() {
test('throws exception for invalid shader', () {
final ByteBuffer invalidBytes = Uint8List.fromList(<int>[1, 2, 3, 4, 5]).buffer;
expect(() => FragmentShader(spirv: invalidBytes), throws);
expect(() => FragmentProgram(spirv: invalidBytes), throws);
});
test('simple shader renders correctly', () async {
final Uint8List shaderBytes = await spvFile('general_shaders', 'functions.spv').readAsBytes();
final FragmentShader shader = FragmentShader(
final FragmentProgram program = FragmentProgram(
spirv: shaderBytes.buffer,
);
final Shader shader = program.shader(
floatUniforms: Float32List.fromList(<double>[1]),
);
_expectShaderRendersGreen(shader);
@@ -30,18 +32,20 @@ void main() {
test('shader with functions renders green', () {
final ByteBuffer spirv = spvFile('general_shaders', 'functions.spv').readAsBytesSync().buffer;
final FragmentShader shader = FragmentShader(
final FragmentProgram program = FragmentProgram(
spirv: spirv,
);
final Shader shader = program.shader(
floatUniforms: Float32List.fromList(<double>[1]),
);
_expectShaderRendersGreen(shader);
});
test('shader with uniforms renders and updates correctly', () async {
test('shader with uniforms renders correctly', () async {
final Uint8List shaderBytes = await spvFile('general_shaders', 'uniforms.spv').readAsBytes();
final FragmentShader shader = FragmentShader(spirv: shaderBytes.buffer);
final FragmentProgram program = FragmentProgram(spirv: shaderBytes.buffer);
shader.update(
final Shader shader = program.shader(
floatUniforms: Float32List.fromList(<double>[
0.0, // iFloatUniform
0.25, // iVec2Uniform.x
@@ -75,6 +79,42 @@ void main() {
expect(supportedOpShaders.isNotEmpty, true);
_expectShadersRenderGreen(supportedOpShaders);
_expectShadersHaveOp(supportedOpShaders, false /* glsl ops */);
test('equality depends on floatUniforms', () {
final ByteBuffer spirv = spvFile('general_shaders', 'simple.spv')
.readAsBytesSync().buffer;
final FragmentProgram program = FragmentProgram(spirv: spirv);
final Float32List ones = Float32List.fromList(<double>[1]);
final Float32List zeroes = Float32List.fromList(<double>[0]);
{
final a = program.shader(floatUniforms: ones);
final b = program.shader(floatUniforms: ones);
expect(a, b);
expect(a.hashCode, b.hashCode);
}
{
final a = program.shader(floatUniforms: ones);
final b = program.shader(floatUniforms: zeroes);
expect(a, notEquals(b));
expect(a.hashCode, notEquals(b.hashCode));
}
});
test('equality depends on spirv', () {
final ByteBuffer spirvA = spvFile('general_shaders', 'simple.spv')
.readAsBytesSync().buffer;
final ByteBuffer spirvB = spvFile('general_shaders', 'uniforms.spv')
.readAsBytesSync().buffer;
final FragmentProgram programA = FragmentProgram(spirv: spirvA);
final FragmentProgram programB = FragmentProgram(spirv: spirvB);
final a = programA.shader();
final b = programB.shader();
expect(a, notEquals(b));
expect(a.hashCode, notEquals(b.hashCode));
});
}
// Expect that all of the spirv shaders in this folder render green.
@@ -83,8 +123,10 @@ void main() {
void _expectShadersRenderGreen(Map<String, ByteBuffer> shaders) {
for (final String key in shaders.keys) {
test('$key renders green', () {
final FragmentShader shader = FragmentShader(
final FragmentProgram program = FragmentProgram(
spirv: shaders[key]!,
);
final Shader shader = program.shader(
floatUniforms: Float32List.fromList(<double>[1]),
);
_expectShaderRendersGreen(shader);
@@ -138,7 +180,7 @@ void _expectShaderHasOp(ByteBuffer spirv, String filename, bool glsl) {
}
// Expects that a spirv shader only outputs the color green.
Future<void> _expectShaderRendersGreen(FragmentShader shader) async {
Future<void> _expectShaderRendersGreen(Shader shader) async {
final ByteData renderedBytes = (await _imageByteDataFromShader(
shader: shader,
imageDimension: _shaderImageDimension,
@@ -149,7 +191,7 @@ Future<void> _expectShaderRendersGreen(FragmentShader shader) async {
}
Future<ByteData?> _imageByteDataFromShader({
required FragmentShader shader,
required Shader shader,
int imageDimension = 100,
}) async {
final PictureRecorder recorder = PictureRecorder();