Skip to content

Commit

Permalink
Reland: Adds a reusable FragmentShader (flutter#35846)
Browse files Browse the repository at this point in the history
  • Loading branch information
zanderso authored Aug 31, 2022
1 parent 3dc4e49 commit 9b14b25
Show file tree
Hide file tree
Showing 11 changed files with 590 additions and 62 deletions.
4 changes: 4 additions & 0 deletions lib/ui/dart_ui.cc
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include "flutter/lib/ui/painting/color_filter.h"
#include "flutter/lib/ui/painting/engine_layer.h"
#include "flutter/lib/ui/painting/fragment_program.h"
#include "flutter/lib/ui/painting/fragment_shader.h"
#include "flutter/lib/ui/painting/gradient.h"
#include "flutter/lib/ui/painting/image.h"
#include "flutter/lib/ui/painting/image_descriptor.h"
Expand Down Expand Up @@ -67,6 +68,7 @@ typedef CanvasPath Path;
V(Canvas::Create, 6) \
V(ColorFilter::Create, 1) \
V(FragmentProgram::Create, 1) \
V(ReusableFragmentShader::Create, 4) \
V(Gradient::Create, 1) \
V(ImageFilter::Create, 1) \
V(ImageShader::Create, 1) \
Expand Down Expand Up @@ -167,6 +169,8 @@ typedef CanvasPath Path;
V(EngineLayer, dispose, 1) \
V(FragmentProgram, initFromAsset, 2) \
V(FragmentProgram, shader, 4) \
V(ReusableFragmentShader, Dispose, 1) \
V(ReusableFragmentShader, SetSampler, 3) \
V(Gradient, initLinear, 6) \
V(Gradient, initRadial, 8) \
V(Gradient, initSweep, 9) \
Expand Down
146 changes: 109 additions & 37 deletions lib/ui/painting.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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.
///
Expand Down Expand Up @@ -4263,6 +4272,69 @@ 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].
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();
_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
Expand Down
7 changes: 7 additions & 0 deletions lib/ui/painting/fragment_program.cc
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,13 @@ fml::RefPtr<FragmentShader> FragmentProgram::shader(Dart_Handle shader,
std::move(uniform_data)));
}

std::shared_ptr<DlColorSource> FragmentProgram::MakeDlColorSource(
sk_sp<SkData> float_uniforms,
const std::vector<std::shared_ptr<DlColorSource>>& children) {
return DlColorSource::MakeRuntimeEffect(runtime_effect_, std::move(children),
std::move(float_uniforms));
}

void FragmentProgram::Create(Dart_Handle wrapper) {
auto res = fml::MakeRefCounted<FragmentProgram>();
res->AssociateWithDartWrapper(wrapper);
Expand Down
7 changes: 7 additions & 0 deletions lib/ui/painting/fragment_program.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

#include "flutter/lib/ui/dart_wrapper.h"
#include "flutter/lib/ui/painting/fragment_shader.h"
#include "flutter/lib/ui/painting/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"
Expand All @@ -16,6 +17,8 @@

namespace flutter {

class FragmentShader;

class FragmentProgram : public RefCountedDartWrappable<FragmentProgram> {
DEFINE_WRAPPERTYPEINFO();
FML_FRIEND_MAKE_REF_COUNTED(FragmentProgram);
Expand All @@ -30,6 +33,10 @@ class FragmentProgram : public RefCountedDartWrappable<FragmentProgram> {
Dart_Handle uniforms_handle,
Dart_Handle samplers);

std::shared_ptr<DlColorSource> MakeDlColorSource(
sk_sp<SkData> float_uniforms,
const std::vector<std::shared_ptr<DlColorSource>>& children);

private:
FragmentProgram();
sk_sp<SkRuntimeEffect> runtime_effect_;
Expand Down
72 changes: 72 additions & 0 deletions lib/ui/painting/fragment_shader.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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);

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);
}

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
Loading

0 comments on commit 9b14b25

Please sign in to comment.