-
Notifications
You must be signed in to change notification settings - Fork 6k
[dart:ui] Adds a reusable FragmentShader #35253
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1399,9 +1399,10 @@ class Paint { | |
| } | ||
| set shader(Shader? value) { | ||
| assert(() { | ||
| if (value is ImageShader) { | ||
| assert(!value.debugDisposed, 'Attempted to set a disposed shader to $this'); | ||
| } | ||
| assert( | ||
| value == null || !value.debugDisposed, | ||
| 'Attempted to set a disposed shader to $this', | ||
| ); | ||
| return true; | ||
| }()); | ||
| _ensureObjectsInitialized()[_kShaderIndex] = value; | ||
|
|
@@ -3682,6 +3683,37 @@ class Shader extends NativeFieldWrapperClass1 { | |
| /// or extended directly. | ||
| @pragma('vm:entry-point') | ||
| Shader._(); | ||
|
|
||
| bool _debugDisposed = false; | ||
|
|
||
| /// Whether [dispose] has been called. | ||
| /// | ||
| /// This must only be used when asserts are enabled. Otherwise, it will throw. | ||
| bool get debugDisposed { | ||
| late bool disposed; | ||
| assert(() { | ||
| disposed = _debugDisposed; | ||
| return true; | ||
| }()); | ||
| return disposed; | ||
| } | ||
|
|
||
| /// Release the resources used by this object. The object is no longer usable | ||
| /// after this method is called. | ||
| /// | ||
| /// The underlying memory allocated by this object will be retained beyond | ||
| /// this call if it is still needed by another object that has not been | ||
| /// disposed. For example, a [Picture] that has not been disposed that | ||
| /// refers to an [ImageShader] may keep its underlying resources alive. | ||
| /// | ||
| /// Classes that override this method must call `super.dispose()`. | ||
| void dispose() { | ||
| assert(() { | ||
| assert(!_debugDisposed); | ||
| _debugDisposed = true; | ||
| return true; | ||
| }()); | ||
| } | ||
| } | ||
|
|
||
| /// Defines what happens at the edge of a gradient or the sampling of a source image | ||
|
|
@@ -4056,55 +4088,29 @@ class ImageShader extends Shader { | |
| } | ||
| } | ||
|
|
||
| @override | ||
| void dispose() { | ||
| super.dispose(); | ||
| _dispose(); | ||
| } | ||
|
|
||
| @FfiNative<Void Function(Handle)>('ImageShader::Create') | ||
| external void _constructor(); | ||
|
|
||
| @FfiNative<Handle Function(Pointer<Void>, Pointer<Void>, Int32, Int32, Int32, Handle)>('ImageShader::initWithImage') | ||
| external String? _initWithImage(_Image image, int tmx, int tmy, int filterQualityIndex, Float64List matrix4); | ||
|
|
||
| bool _debugDisposed = false; | ||
|
|
||
| /// Whether [dispose] has been called. | ||
| /// | ||
| /// This must only be used when asserts are enabled. Otherwise, it will throw. | ||
| bool get debugDisposed { | ||
| late bool disposed; | ||
| assert(() { | ||
| disposed = _debugDisposed; | ||
| return true; | ||
| }()); | ||
| return disposed; | ||
| } | ||
|
|
||
| /// Release the resources used by this object. The object is no longer usable | ||
| /// after this method is called. | ||
| /// | ||
| /// The underlying memory allocated by this object will be retained beyond | ||
| /// this call if it is still needed by another object that has not been | ||
| /// disposed. For example, an [Picture] that has not been disposed that | ||
| /// refers to this [ImageShader] may keep its underlying resources alive. | ||
| void dispose() { | ||
| assert(() { | ||
| assert(!_debugDisposed); | ||
| _debugDisposed = true; | ||
| return true; | ||
| }()); | ||
| _dispose(); | ||
| } | ||
|
|
||
| /// This can't be a leaf call because the native function calls Dart API | ||
| /// (Dart_SetNativeInstanceField). | ||
| @FfiNative<Void Function(Pointer<Void>)>('ImageShader::dispose') | ||
| external void _dispose(); | ||
| } | ||
|
|
||
| /// An instance of [FragmentProgram] creates [Shader] objects (as used by [Paint.shader]) that run SPIR-V code. | ||
| /// An instance of [FragmentProgram] creates [Shader] objects (as used by | ||
| /// [Paint.shader]). | ||
| /// | ||
| /// 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) | ||
| /// | ||
| class FragmentProgram extends NativeFieldWrapperClass1 { | ||
| @pragma('vm:entry-point') | ||
| FragmentProgram._fromAsset(String assetKey) { | ||
|
|
@@ -4181,6 +4187,9 @@ class FragmentProgram extends NativeFieldWrapperClass1 { | |
| @FfiNative<Handle Function(Pointer<Void>, Handle)>('FragmentProgram::initFromAsset') | ||
| external String _initFromAsset(String assetKey); | ||
|
|
||
| /// Returns a fresh instance of [FragmentShader]. | ||
| FragmentShader fragmentShader() => FragmentShader._(this); | ||
|
|
||
| /// Constructs a [Shader] object suitable for use by [Paint.shader] with | ||
| /// the given uniforms. | ||
| /// | ||
|
|
@@ -4263,6 +4272,111 @@ class FragmentProgram extends NativeFieldWrapperClass1 { | |
| external Handle _shader(_FragmentShader shader, Float32List floatUniforms, List<ImageShader> samplerUniforms); | ||
| } | ||
|
|
||
| /// A [Shader] generated from a [FragmentProgram]. | ||
| /// | ||
| /// Instances of this class can be obtained from the | ||
| /// [FragmentProgram.fragmentShader] method. The float uniforms list is | ||
| /// initialized to the size expected by the shader and is zero-filled. Uniforms | ||
| /// of float type can then be set by calling [setFloat]. Sampler uniforms are | ||
| /// set by calling [setSampler]. | ||
| /// | ||
| /// A [FragmentShader] can be re-used, and this is an efficient way to avoid | ||
| /// allocating and re-initializing the uniform buffer and samplers. However, | ||
| /// if two [FragmentShader] objects with different float uniforms or samplers | ||
| /// are required to exist simultaneously, they must be obtained from two | ||
| /// different calls to [FragmentProgram.fragmentShader]. | ||
| /// | ||
| /// Consider a fragment shader with uniforms like the following: | ||
| /// | ||
| /// ```glsl | ||
| /// layout (location = 0) uniform float a; | ||
| /// layout (location = 1) uniform vec2 b; | ||
| /// layout (location = 2) uniform vec3 c; | ||
| /// layout (location = 3) uniform mat2x2 d; | ||
| /// layout (location = 4) uniform sampler2d s; | ||
| /// ``` | ||
| /// | ||
| /// If this shader is loaded into a [FragmentProgram] object `program`, a | ||
| /// [FragmentShader] object can be constructed as follows: | ||
| /// ```dart | ||
| /// ImageShader sampler; | ||
| /// final FragmentShader shader = program.fragmentShader() | ||
| /// ..setFloat(0, 0.0) | ||
| /// ..setFloat(1, 1.0) | ||
| /// ..setFloat(2, 2.0) | ||
| /// ..setFloat(3, 3.0) | ||
| /// ..setFloat(4, 4.0) | ||
| /// ..setFloat(5, 5.0) | ||
| /// ..setFloat(6, 6.0) | ||
| /// ..setFloat(7, 7.0) | ||
| /// ..setFloat(8, 8.8) | ||
| /// ..setFloat(9, 9.0) | ||
| /// ..setSampler(0, sampler); | ||
| /// | ||
| /// Paint paint = Paint()..shader = shader; | ||
| /// ``` | ||
| /// | ||
| /// The uniforms will then be set as follows: | ||
| /// | ||
| /// a: 0.0 | ||
| /// b: [1.0, 2.0] | ||
| /// c: [3.0, 4.0, 5.0] | ||
| /// d: [6.0, 7.0, 8.0, 9.0] // 2x2 matrix in column-major order | ||
| /// | ||
| /// On subsequent frames, if only the uniform `a` is required to vary, then | ||
| /// only `shader.setFloat(0, newValue)` needs to be called. The other uniforms | ||
| /// will retain their values. The sampler uniforms will also persist in the same | ||
| /// way. | ||
| class FragmentShader extends Shader { | ||
| FragmentShader._(FragmentProgram program) : super._() { | ||
| _floats = _constructor( | ||
| program, | ||
| program._uniformFloatCount, | ||
| program._samplerCount, | ||
| ); | ||
| } | ||
|
|
||
| static final Float32List _kEmptyFloat32List = Float32List(0); | ||
|
|
||
| late Float32List _floats; | ||
|
|
||
| /// Sets the float uniform at [index] to [value]. | ||
| void setFloat(int index, double value) { | ||
| assert(!debugDisposed, 'Tried to accesss uniforms on a disposed Shader: $this'); | ||
| _floats[index] = value; | ||
| } | ||
|
|
||
| /// Sets the sampler uniform at [index] to [sampler]. | ||
| /// | ||
| /// All the sampler uniforms that a shader expects must be provided or the | ||
| /// results will be undefined. | ||
| void setSampler(int index, ImageShader sampler) { | ||
| assert(!debugDisposed, 'Tried to access uniforms on a disposed Shader: $this'); | ||
| _setSampler(index, sampler); | ||
| } | ||
|
|
||
| /// Releases the native resources held by the [FragmentShader]. | ||
| /// | ||
| /// After this method is called, calling methods on the shader, or attaching | ||
| /// it to a [Paint] object will fail with an exception. Calling [dispose] | ||
| /// twice will also result in an exception being thrown. | ||
| @override | ||
| void dispose() { | ||
| super.dispose(); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: call super.dispose last, since it might rip something out from under you
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, hm, probably should call _dispose last since it deletes the native peer though...
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right. |
||
| _floats = _kEmptyFloat32List; | ||
| _dispose(); | ||
| } | ||
|
|
||
| @FfiNative<Handle Function(Handle, Handle, Handle, Handle)>('ReusableFragmentShader::Create') | ||
| external Float32List _constructor(FragmentProgram program, int floatUniforms, int samplerUniforms); | ||
|
|
||
| @FfiNative<Void Function(Pointer<Void>, Handle, Handle)>('ReusableFragmentShader::SetSampler') | ||
| external void _setSampler(int index, ImageShader sampler); | ||
|
|
||
| @FfiNative<Void Function(Pointer<Void>)>('ReusableFragmentShader::Dispose') | ||
| external void _dispose(); | ||
| } | ||
|
|
||
| @pragma('vm:entry-point') | ||
| class _FragmentShader extends Shader { | ||
| /// This class is created by the engine and should not be instantiated | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,6 +7,7 @@ | |
| #include "flutter/lib/ui/painting/fragment_shader.h" | ||
|
|
||
| #include "flutter/lib/ui/dart_wrapper.h" | ||
| #include "flutter/lib/ui/painting/fragment_program.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" | ||
|
|
@@ -48,4 +49,75 @@ FragmentShader::FragmentShader( | |
|
|
||
| FragmentShader::~FragmentShader() = default; | ||
|
|
||
| IMPLEMENT_WRAPPERTYPEINFO(ui, ReusableFragmentShader); | ||
|
|
||
| ReusableFragmentShader::ReusableFragmentShader( | ||
| fml::RefPtr<FragmentProgram> program, | ||
| uint64_t float_count, | ||
| uint64_t sampler_count) | ||
| : program_(program), | ||
| uniform_data_(SkData::MakeUninitialized( | ||
| (float_count + 2 * sampler_count) * sizeof(float))), | ||
| samplers_(sampler_count), | ||
| float_count_(float_count) {} | ||
|
|
||
| Dart_Handle ReusableFragmentShader::Create(Dart_Handle wrapper, | ||
| Dart_Handle program, | ||
| Dart_Handle float_count_handle, | ||
| Dart_Handle sampler_count_handle) { | ||
| auto* fragment_program = | ||
| tonic::DartConverter<FragmentProgram*>::FromDart(program); | ||
| uint64_t float_count = | ||
| tonic::DartConverter<uint64_t>::FromDart(float_count_handle); | ||
| uint64_t sampler_count = | ||
| tonic::DartConverter<uint64_t>::FromDart(sampler_count_handle); | ||
|
Comment on lines
+68
to
+73
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: if you type these in the declaration tonic will just do this for you.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's how I had it at first, but the integer parameters were getting the value of the |
||
|
|
||
| auto res = fml::MakeRefCounted<ReusableFragmentShader>( | ||
| fml::Ref(fragment_program), float_count, sampler_count); | ||
| res->AssociateWithDartWrapper(wrapper); | ||
|
|
||
| void* raw_uniform_data = | ||
| reinterpret_cast<void*>(res->uniform_data_->writable_data()); | ||
| return Dart_NewExternalTypedData(Dart_TypedData_kFloat32, raw_uniform_data, | ||
| float_count); | ||
zanderso marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| void ReusableFragmentShader::SetSampler(Dart_Handle index_handle, | ||
| Dart_Handle sampler_handle) { | ||
| uint64_t index = tonic::DartConverter<uint64_t>::FromDart(index_handle); | ||
| ImageShader* sampler = | ||
| tonic::DartConverter<ImageShader*>::FromDart(sampler_handle); | ||
| if (index >= samplers_.size()) { | ||
| Dart_ThrowException(tonic::ToDart("Sampler index out of bounds")); | ||
| } | ||
|
|
||
| // ImageShaders can hold a preferred value for sampling options and | ||
| // developers are encouraged to use that value or the value will be supplied | ||
| // by "the environment where it is used". The environment here does not | ||
| // contain a value to be used if the developer did not specify a preference | ||
| // when they constructed the ImageShader, so we will use kNearest which is | ||
| // the default filterQuality in a Paint object. | ||
| DlImageSampling sampling = DlImageSampling::kNearestNeighbor; | ||
| auto* uniform_floats = | ||
| reinterpret_cast<float*>(uniform_data_->writable_data()); | ||
| samplers_[index] = sampler->shader(sampling); | ||
| uniform_floats[float_count_ + 2 * index] = sampler->width(); | ||
| uniform_floats[float_count_ + 2 * index + 1] = sampler->height(); | ||
| } | ||
|
|
||
| std::shared_ptr<DlColorSource> ReusableFragmentShader::shader( | ||
| DlImageSampling sampling) { | ||
| FML_CHECK(program_); | ||
| return program_->MakeDlColorSource(uniform_data_, samplers_); | ||
| } | ||
|
|
||
| void ReusableFragmentShader::Dispose() { | ||
| uniform_data_.reset(); | ||
| program_ = nullptr; | ||
| samplers_.clear(); | ||
| ClearDartWrapper(); | ||
| } | ||
|
|
||
| ReusableFragmentShader::~ReusableFragmentShader() = default; | ||
|
|
||
| } // namespace flutter | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another approach, if the ergonomics of
setFloataren't to your liking. Just create a view of the original object:There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I trust the VM's ability to inline the simple wrapper that's currently in the PR. Adding more indirection would make me start to worry...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no prob. might be possible to do "for free" someday with views when/if they come out: