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

Commit 5c810cf

Browse files
committed
[dart:ui] Adds a reusable FragmentShader
1 parent 7a03a54 commit 5c810cf

11 files changed

Lines changed: 603 additions & 56 deletions

File tree

lib/ui/dart_ui.cc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include "flutter/lib/ui/painting/color_filter.h"
1919
#include "flutter/lib/ui/painting/engine_layer.h"
2020
#include "flutter/lib/ui/painting/fragment_program.h"
21+
#include "flutter/lib/ui/painting/fragment_shader.h"
2122
#include "flutter/lib/ui/painting/gradient.h"
2223
#include "flutter/lib/ui/painting/image.h"
2324
#include "flutter/lib/ui/painting/image_descriptor.h"
@@ -67,6 +68,7 @@ typedef CanvasPath Path;
6768
V(Canvas::Create, 6) \
6869
V(ColorFilter::Create, 1) \
6970
V(FragmentProgram::Create, 1) \
71+
V(ReusableFragmentShader::Create, 4) \
7072
V(Gradient::Create, 1) \
7173
V(ImageFilter::Create, 1) \
7274
V(ImageShader::Create, 1) \
@@ -167,6 +169,8 @@ typedef CanvasPath Path;
167169
V(EngineLayer, dispose, 1) \
168170
V(FragmentProgram, initFromAsset, 2) \
169171
V(FragmentProgram, shader, 4) \
172+
V(ReusableFragmentShader, Dispose, 1) \
173+
V(ReusableFragmentShader, SetSampler, 3) \
170174
V(Gradient, initLinear, 6) \
171175
V(Gradient, initRadial, 8) \
172176
V(Gradient, initSweep, 9) \

lib/ui/painting.dart

Lines changed: 151 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1399,9 +1399,10 @@ class Paint {
13991399
}
14001400
set shader(Shader? value) {
14011401
assert(() {
1402-
if (value is ImageShader) {
1403-
assert(!value.debugDisposed, 'Attempted to set a disposed shader to $this');
1404-
}
1402+
assert(
1403+
value == null || !value.debugDisposed,
1404+
'Attempted to set a disposed shader to $this',
1405+
);
14051406
return true;
14061407
}());
14071408
_ensureObjectsInitialized()[_kShaderIndex] = value;
@@ -3682,6 +3683,37 @@ class Shader extends NativeFieldWrapperClass1 {
36823683
/// or extended directly.
36833684
@pragma('vm:entry-point')
36843685
Shader._();
3686+
3687+
bool _debugDisposed = false;
3688+
3689+
/// Whether [dispose] has been called.
3690+
///
3691+
/// This must only be used when asserts are enabled. Otherwise, it will throw.
3692+
bool get debugDisposed {
3693+
late bool disposed;
3694+
assert(() {
3695+
disposed = _debugDisposed;
3696+
return true;
3697+
}());
3698+
return disposed;
3699+
}
3700+
3701+
/// Release the resources used by this object. The object is no longer usable
3702+
/// after this method is called.
3703+
///
3704+
/// The underlying memory allocated by this object will be retained beyond
3705+
/// this call if it is still needed by another object that has not been
3706+
/// disposed. For example, a [Picture] that has not been disposed that
3707+
/// refers to an [ImageShader] may keep its underlying resources alive.
3708+
///
3709+
/// Classes that override this method must call `super.dispose()`.
3710+
void dispose() {
3711+
assert(() {
3712+
assert(!_debugDisposed);
3713+
_debugDisposed = true;
3714+
return true;
3715+
}());
3716+
}
36853717
}
36863718

36873719
/// Defines what happens at the edge of a gradient or the sampling of a source image
@@ -4056,55 +4088,29 @@ class ImageShader extends Shader {
40564088
}
40574089
}
40584090

4091+
@override
4092+
void dispose() {
4093+
super.dispose();
4094+
_dispose();
4095+
}
4096+
40594097
@FfiNative<Void Function(Handle)>('ImageShader::Create')
40604098
external void _constructor();
40614099

40624100
@FfiNative<Handle Function(Pointer<Void>, Pointer<Void>, Int32, Int32, Int32, Handle)>('ImageShader::initWithImage')
40634101
external String? _initWithImage(_Image image, int tmx, int tmy, int filterQualityIndex, Float64List matrix4);
40644102

4065-
bool _debugDisposed = false;
4066-
4067-
/// Whether [dispose] has been called.
4068-
///
4069-
/// This must only be used when asserts are enabled. Otherwise, it will throw.
4070-
bool get debugDisposed {
4071-
late bool disposed;
4072-
assert(() {
4073-
disposed = _debugDisposed;
4074-
return true;
4075-
}());
4076-
return disposed;
4077-
}
4078-
4079-
/// Release the resources used by this object. The object is no longer usable
4080-
/// after this method is called.
4081-
///
4082-
/// The underlying memory allocated by this object will be retained beyond
4083-
/// this call if it is still needed by another object that has not been
4084-
/// disposed. For example, an [Picture] that has not been disposed that
4085-
/// refers to this [ImageShader] may keep its underlying resources alive.
4086-
void dispose() {
4087-
assert(() {
4088-
assert(!_debugDisposed);
4089-
_debugDisposed = true;
4090-
return true;
4091-
}());
4092-
_dispose();
4093-
}
4094-
40954103
/// This can't be a leaf call because the native function calls Dart API
40964104
/// (Dart_SetNativeInstanceField).
40974105
@FfiNative<Void Function(Pointer<Void>)>('ImageShader::dispose')
40984106
external void _dispose();
40994107
}
41004108

4101-
/// An instance of [FragmentProgram] creates [Shader] objects (as used by [Paint.shader]) that run SPIR-V code.
4109+
/// An instance of [FragmentProgram] creates [Shader] objects (as used by
4110+
/// [Paint.shader]).
41024111
///
41034112
/// This API is in beta and does not yet work on web.
41044113
/// See https://github.com/flutter/flutter/projects/207 for roadmap.
4105-
///
4106-
/// [A current specification of valid SPIR-V is here.](https://github.com/flutter/engine/blob/main/lib/spirv/README.md)
4107-
///
41084114
class FragmentProgram extends NativeFieldWrapperClass1 {
41094115
@pragma('vm:entry-point')
41104116
FragmentProgram._fromAsset(String assetKey) {
@@ -4181,6 +4187,9 @@ class FragmentProgram extends NativeFieldWrapperClass1 {
41814187
@FfiNative<Handle Function(Pointer<Void>, Handle)>('FragmentProgram::initFromAsset')
41824188
external String _initFromAsset(String assetKey);
41834189

4190+
/// Returns a fresh instance of [FragmentShader].
4191+
FragmentShader fragmentShader() => FragmentShader._(this);
4192+
41844193
/// Constructs a [Shader] object suitable for use by [Paint.shader] with
41854194
/// the given uniforms.
41864195
///
@@ -4263,6 +4272,111 @@ class FragmentProgram extends NativeFieldWrapperClass1 {
42634272
external Handle _shader(_FragmentShader shader, Float32List floatUniforms, List<ImageShader> samplerUniforms);
42644273
}
42654274

4275+
/// A [Shader] generated from a [FragmentProgram].
4276+
///
4277+
/// Instances of this class can be obtained from the
4278+
/// [FragmentProgram.fragmentShader] method. The float uniforms list is
4279+
/// initialized to the size expected by the shader and is zero-filled. Uniforms
4280+
/// of float type can then be set by calling [setFloat]. Sampler uniforms are
4281+
/// set by calling [setSampler].
4282+
///
4283+
/// A [FragmentShader] can be re-used, and this is an efficient way to avoid
4284+
/// allocating and re-initializing the uniform buffer and samplers. However,
4285+
/// if two [FragmentShader] objects with different float uniforms or samplers
4286+
/// are required to exist simultaneously, they must be obtained from two
4287+
/// different calls to [FragmentProgram.fragmentShader].
4288+
///
4289+
/// Consider a fragment shader with uniforms like the following:
4290+
///
4291+
/// ```glsl
4292+
/// layout (location = 0) uniform float a;
4293+
/// layout (location = 1) uniform vec2 b;
4294+
/// layout (location = 2) uniform vec3 c;
4295+
/// layout (location = 3) uniform mat2x2 d;
4296+
/// layout (location = 4) uniform sampler2d s;
4297+
/// ```
4298+
///
4299+
/// If this shader is loaded into a [FragmentProgram] object `program`, a
4300+
/// [FragmentShader] object can be constructed as follows:
4301+
/// ```dart
4302+
/// ImageShader sampler;
4303+
/// final FragmentShader shader = program.fragmentShader()
4304+
/// ..setFloat(0, 0.0)
4305+
/// ..setFloat(1, 1.0)
4306+
/// ..setFloat(2, 2.0)
4307+
/// ..setFloat(3, 3.0)
4308+
/// ..setFloat(4, 4.0)
4309+
/// ..setFloat(5, 5.0)
4310+
/// ..setFloat(6, 6.0)
4311+
/// ..setFloat(7, 7.0)
4312+
/// ..setFloat(8, 8.8)
4313+
/// ..setFloat(9, 9.0)
4314+
/// ..setSampler(0, sampler);
4315+
///
4316+
/// Paint paint = Paint()..shader = shader;
4317+
/// ```
4318+
///
4319+
/// The uniforms will then be set as follows:
4320+
///
4321+
/// a: 0.0
4322+
/// b: [1.0, 2.0]
4323+
/// c: [3.0, 4.0, 5.0]
4324+
/// d: [6.0, 7.0, 8.0, 9.0] // 2x2 matrix in column-major order
4325+
///
4326+
/// On subsequent frames, if only the uniform `a` is required to vary, then
4327+
/// only `shader.setFloat(0, newValue)` needs to be called. The other uniforms
4328+
/// will retain their values. The sampler uniforms will also persist in the same
4329+
/// way.
4330+
class FragmentShader extends Shader {
4331+
FragmentShader._(FragmentProgram program) : super._() {
4332+
_floats = _constructor(
4333+
program,
4334+
program._uniformFloatCount,
4335+
program._samplerCount,
4336+
);
4337+
}
4338+
4339+
static final Float32List _kEmptyFloat32List = Float32List(0);
4340+
4341+
late Float32List _floats;
4342+
4343+
/// Sets the float uniform at [index] to [value].
4344+
void setFloat(int index, double value) {
4345+
assert(!debugDisposed, 'Tried to accesss uniforms on a disposed Shader: $this');
4346+
_floats[index] = value;
4347+
}
4348+
4349+
/// Sets the sampler uniform at [index] to [sampler].
4350+
///
4351+
/// All the sampler uniforms that a shader expects must be provided or the
4352+
/// results will be undefined.
4353+
void setSampler(int index, ImageShader sampler) {
4354+
assert(!debugDisposed, 'Tried to access uniforms on a disposed Shader: $this');
4355+
_setSampler(index, sampler);
4356+
}
4357+
4358+
/// Releases the native resources held by the [FragmentShader].
4359+
///
4360+
/// After this method is called, calling methods on the shader, or attaching
4361+
/// it to a [Paint] object will fail with an exception. Calling [dispose]
4362+
/// twice will also result in an exception being thrown.
4363+
@override
4364+
void dispose() {
4365+
super.dispose();
4366+
_floats = _kEmptyFloat32List;
4367+
_dispose();
4368+
}
4369+
4370+
@FfiNative<Handle Function(Handle, Handle, Handle, Handle)>('ReusableFragmentShader::Create')
4371+
external Float32List _constructor(FragmentProgram program, int floatUniforms, int samplerUniforms);
4372+
4373+
@FfiNative<Void Function(Pointer<Void>, Handle, Handle)>('ReusableFragmentShader::SetSampler')
4374+
external void _setSampler(int index, ImageShader sampler);
4375+
4376+
@FfiNative<Void Function(Pointer<Void>)>('ReusableFragmentShader::Dispose')
4377+
external void _dispose();
4378+
}
4379+
42664380
@pragma('vm:entry-point')
42674381
class _FragmentShader extends Shader {
42684382
/// This class is created by the engine and should not be instantiated

lib/ui/painting/fragment_program.cc

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,13 @@ fml::RefPtr<FragmentShader> FragmentProgram::shader(Dart_Handle shader,
133133
std::move(uniform_data)));
134134
}
135135

136+
std::shared_ptr<DlColorSource> FragmentProgram::MakeDlColorSource(
137+
sk_sp<SkData> float_uniforms,
138+
const std::vector<std::shared_ptr<DlColorSource>>& children) {
139+
return DlColorSource::MakeRuntimeEffect(runtime_effect_, std::move(children),
140+
std::move(float_uniforms));
141+
}
142+
136143
void FragmentProgram::Create(Dart_Handle wrapper) {
137144
auto res = fml::MakeRefCounted<FragmentProgram>();
138145
res->AssociateWithDartWrapper(wrapper);

lib/ui/painting/fragment_program.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
#include "flutter/lib/ui/dart_wrapper.h"
99
#include "flutter/lib/ui/painting/fragment_shader.h"
10+
#include "flutter/lib/ui/painting/shader.h"
1011
#include "third_party/skia/include/effects/SkRuntimeEffect.h"
1112
#include "third_party/tonic/dart_library_natives.h"
1213
#include "third_party/tonic/typed_data/typed_list.h"
@@ -16,6 +17,8 @@
1617

1718
namespace flutter {
1819

20+
class FragmentShader;
21+
1922
class FragmentProgram : public RefCountedDartWrappable<FragmentProgram> {
2023
DEFINE_WRAPPERTYPEINFO();
2124
FML_FRIEND_MAKE_REF_COUNTED(FragmentProgram);
@@ -30,6 +33,10 @@ class FragmentProgram : public RefCountedDartWrappable<FragmentProgram> {
3033
Dart_Handle uniforms_handle,
3134
Dart_Handle samplers);
3235

36+
std::shared_ptr<DlColorSource> MakeDlColorSource(
37+
sk_sp<SkData> float_uniforms,
38+
const std::vector<std::shared_ptr<DlColorSource>>& children);
39+
3340
private:
3441
FragmentProgram();
3542
sk_sp<SkRuntimeEffect> runtime_effect_;

lib/ui/painting/fragment_shader.cc

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include "flutter/lib/ui/painting/fragment_shader.h"
88

99
#include "flutter/lib/ui/dart_wrapper.h"
10+
#include "flutter/lib/ui/painting/fragment_program.h"
1011
#include "flutter/lib/ui/ui_dart_state.h"
1112
#include "third_party/skia/include/core/SkString.h"
1213
#include "third_party/tonic/converter/dart_converter.h"
@@ -48,4 +49,75 @@ FragmentShader::FragmentShader(
4849

4950
FragmentShader::~FragmentShader() = default;
5051

52+
IMPLEMENT_WRAPPERTYPEINFO(ui, ReusableFragmentShader);
53+
54+
ReusableFragmentShader::ReusableFragmentShader(
55+
fml::RefPtr<FragmentProgram> program,
56+
uint64_t float_count,
57+
uint64_t sampler_count)
58+
: program_(program),
59+
uniform_data_(SkData::MakeUninitialized(
60+
(float_count + 2 * sampler_count) * sizeof(float))),
61+
samplers_(sampler_count),
62+
float_count_(float_count) {}
63+
64+
Dart_Handle ReusableFragmentShader::Create(Dart_Handle wrapper,
65+
Dart_Handle program,
66+
Dart_Handle float_count_handle,
67+
Dart_Handle sampler_count_handle) {
68+
auto* fragment_program =
69+
tonic::DartConverter<FragmentProgram*>::FromDart(program);
70+
uint64_t float_count =
71+
tonic::DartConverter<uint64_t>::FromDart(float_count_handle);
72+
uint64_t sampler_count =
73+
tonic::DartConverter<uint64_t>::FromDart(sampler_count_handle);
74+
75+
auto res = fml::MakeRefCounted<ReusableFragmentShader>(
76+
fml::Ref(fragment_program), float_count, sampler_count);
77+
res->AssociateWithDartWrapper(wrapper);
78+
79+
void* raw_uniform_data =
80+
reinterpret_cast<void*>(res->uniform_data_->writable_data());
81+
return Dart_NewExternalTypedData(Dart_TypedData_kFloat32, raw_uniform_data,
82+
float_count);
83+
}
84+
85+
void ReusableFragmentShader::SetSampler(Dart_Handle index_handle,
86+
Dart_Handle sampler_handle) {
87+
uint64_t index = tonic::DartConverter<uint64_t>::FromDart(index_handle);
88+
ImageShader* sampler =
89+
tonic::DartConverter<ImageShader*>::FromDart(sampler_handle);
90+
if (index >= samplers_.size()) {
91+
Dart_ThrowException(tonic::ToDart("Sampler index out of bounds"));
92+
}
93+
94+
// ImageShaders can hold a preferred value for sampling options and
95+
// developers are encouraged to use that value or the value will be supplied
96+
// by "the environment where it is used". The environment here does not
97+
// contain a value to be used if the developer did not specify a preference
98+
// when they constructed the ImageShader, so we will use kNearest which is
99+
// the default filterQuality in a Paint object.
100+
DlImageSampling sampling = DlImageSampling::kNearestNeighbor;
101+
auto* uniform_floats =
102+
reinterpret_cast<float*>(uniform_data_->writable_data());
103+
samplers_[index] = sampler->shader(sampling);
104+
uniform_floats[float_count_ + 2 * index] = sampler->width();
105+
uniform_floats[float_count_ + 2 * index + 1] = sampler->height();
106+
}
107+
108+
std::shared_ptr<DlColorSource> ReusableFragmentShader::shader(
109+
DlImageSampling sampling) {
110+
FML_CHECK(program_);
111+
return program_->MakeDlColorSource(uniform_data_, samplers_);
112+
}
113+
114+
void ReusableFragmentShader::Dispose() {
115+
uniform_data_.reset();
116+
program_ = nullptr;
117+
samplers_.clear();
118+
ClearDartWrapper();
119+
}
120+
121+
ReusableFragmentShader::~ReusableFragmentShader() = default;
122+
51123
} // namespace flutter

0 commit comments

Comments
 (0)