diff --git a/display_list/display_list_color_source.h b/display_list/display_list_color_source.h index 95892f41c053d..1e5f545554a72 100644 --- a/display_list/display_list_color_source.h +++ b/display_list/display_list_color_source.h @@ -705,7 +705,11 @@ class DlRuntimeEffectColorSource final : public DlColorSource { } std::vector> sk_samplers(samplers_.size()); for (size_t i = 0; i < samplers_.size(); i++) { - sk_samplers[i] = samplers_[i]->skia_object(); + auto sampler = samplers_[i]; + if (sampler == nullptr) { + return nullptr; + } + sk_samplers[i] = sampler->skia_object(); } auto ref = new std::shared_ptr>(uniform_data_); diff --git a/display_list/display_list_color_source_unittests.cc b/display_list/display_list_color_source_unittests.cc index 8e9044b3223b2..5c9a62abd8697 100644 --- a/display_list/display_list_color_source_unittests.cc +++ b/display_list/display_list_color_source_unittests.cc @@ -974,5 +974,14 @@ TEST(DisplayListColorSource, RuntimeEffect) { TestNotEquals(source2, source3, "SkRuntimeEffect differs"); } +TEST(DisplayListColorSource, RuntimeEffectWithNullSampler) { + std::shared_ptr source1 = + DlColorSource::MakeRuntimeEffect( + kTestRuntimeEffect1, {nullptr}, + std::make_shared>()); + + ASSERT_EQ(source1->skia_object(), nullptr); +} + } // namespace testing } // namespace flutter diff --git a/impeller/display_list/display_list_dispatcher.cc b/impeller/display_list/display_list_dispatcher.cc index 3e555aeaf418f..80678248e858b 100644 --- a/impeller/display_list/display_list_dispatcher.cc +++ b/impeller/display_list/display_list_dispatcher.cc @@ -442,6 +442,9 @@ void DisplayListDispatcher::setColorSource( std::vector texture_inputs; for (auto& sampler : samplers) { + if (sampler == nullptr) { + return; + } auto* image = sampler->asImage(); if (!sampler->asImage()) { UNIMPLEMENTED; diff --git a/lib/ui/dart_ui.cc b/lib/ui/dart_ui.cc index 595988a19bb66..5199821389311 100644 --- a/lib/ui/dart_ui.cc +++ b/lib/ui/dart_ui.cc @@ -175,6 +175,7 @@ typedef CanvasPath Path; V(FragmentProgram, initFromAsset, 2) \ V(ReusableFragmentShader, Dispose, 1) \ V(ReusableFragmentShader, SetSampler, 3) \ + V(ReusableFragmentShader, ValidateSamplers, 1) \ V(Gradient, initLinear, 6) \ V(Gradient, initRadial, 8) \ V(Gradient, initSweep, 9) \ diff --git a/lib/ui/painting.dart b/lib/ui/painting.dart index 7dbb9b62d6beb..5e96751c289ff 100644 --- a/lib/ui/painting.dart +++ b/lib/ui/painting.dart @@ -1409,6 +1409,14 @@ class Paint { ); return true; }()); + assert(() { + if (value is FragmentShader) { + if (!value._validateSamplers()) { + throw Exception('Invalid FragmentShader ${value._debugName ?? ''}: missing sampler'); + } + } + return true; + }()); _ensureObjectsInitialized()[_kShaderIndex] = value; } @@ -4151,10 +4159,16 @@ class FragmentProgram extends NativeFieldWrapperClass1 { _constructor(); final String result = _initFromAsset(assetKey); if (result.isNotEmpty) { - throw result; // ignore: only_throw_errors + throw Exception(result); } + assert(() { + _debugName = assetKey; + return true; + }()); } + String? _debugName; + // TODO(zra): Document custom shaders on the website and add a link to it // here. https://github.com/flutter/flutter/issues/107929. /// Creates a fragment program from the asset with key [assetKey]. @@ -4222,7 +4236,7 @@ class FragmentProgram extends NativeFieldWrapperClass1 { external String _initFromAsset(String assetKey); /// Returns a fresh instance of [FragmentShader]. - FragmentShader fragmentShader() => FragmentShader._(this); + FragmentShader fragmentShader() => FragmentShader._(this, debugName: _debugName); } /// A [Shader] generated from a [FragmentProgram]. @@ -4239,7 +4253,7 @@ class FragmentProgram extends NativeFieldWrapperClass1 { /// are required to exist simultaneously, they must be obtained from two /// different calls to [FragmentProgram.fragmentShader]. class FragmentShader extends Shader { - FragmentShader._(FragmentProgram program) : super._() { + FragmentShader._(FragmentProgram program, { String? debugName }) : _debugName = debugName, super._() { _floats = _constructor( program, program._uniformFloatCount, @@ -4247,9 +4261,10 @@ class FragmentShader extends Shader { ); } - static final Float32List _kEmptyFloat32List = Float32List(0); + final String? _debugName; - late Float32List _floats; + static final Float32List _kEmptyFloat32List = Float32List(0); + Float32List _floats = _kEmptyFloat32List; /// Sets the float uniform at [index] to [value]. void setFloat(int index, double value) { @@ -4284,6 +4299,9 @@ class FragmentShader extends Shader { @FfiNative, Handle, Handle)>('ReusableFragmentShader::SetSampler') external void _setSampler(int index, ImageShader sampler); + @FfiNative)>('ReusableFragmentShader::ValidateSamplers') + external bool _validateSamplers(); + @FfiNative)>('ReusableFragmentShader::Dispose') external void _dispose(); } diff --git a/lib/ui/painting/fragment_program.cc b/lib/ui/painting/fragment_program.cc index 3e814e1753dfe..97536caf75215 100644 --- a/lib/ui/painting/fragment_program.cc +++ b/lib/ui/painting/fragment_program.cc @@ -2,7 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include #include #include @@ -78,7 +77,6 @@ std::string FragmentProgram::initFromAsset(const std::string& asset_name) { if (Dart_IsError(ths)) { Dart_PropagateError(ths); } - Dart_Handle result = Dart_SetField(ths, tonic::ToDart("_samplerCount"), Dart_NewInteger(sampled_image_count)); if (Dart_IsError(result)) { diff --git a/lib/ui/painting/fragment_shader.cc b/lib/ui/painting/fragment_shader.cc index f1243ea971d6d..38f62ef3d507e 100644 --- a/lib/ui/painting/fragment_shader.cc +++ b/lib/ui/painting/fragment_shader.cc @@ -2,7 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include #include #include @@ -53,6 +52,15 @@ Dart_Handle ReusableFragmentShader::Create(Dart_Handle wrapper, float_count); } +bool ReusableFragmentShader::ValidateSamplers() { + for (auto i = 0u; i < samplers_.size(); i += 1) { + if (samplers_[i] == nullptr) { + return false; + } + } + return true; +} + void ReusableFragmentShader::SetSampler(Dart_Handle index_handle, Dart_Handle sampler_handle) { uint64_t index = tonic::DartConverter::FromDart(index_handle); diff --git a/lib/ui/painting/fragment_shader.h b/lib/ui/painting/fragment_shader.h index da7af7dfb1ea6..5b948e80dea0c 100644 --- a/lib/ui/painting/fragment_shader.h +++ b/lib/ui/painting/fragment_shader.h @@ -36,6 +36,8 @@ class ReusableFragmentShader : public Shader { void SetSampler(Dart_Handle index, Dart_Handle sampler); + bool ValidateSamplers(); + void Dispose(); // |Shader| diff --git a/testing/dart/fragment_shader_test.dart b/testing/dart/fragment_shader_test.dart index ed12e9939ed9e..eca105ec9d394 100644 --- a/testing/dart/fragment_shader_test.dart +++ b/testing/dart/fragment_shader_test.dart @@ -62,6 +62,25 @@ void main() async { } }); + test('FragmentShader with sampler asserts if sampler is missing when assigned to paint', () async { + if (!assertsEnabled) { + return; + } + final FragmentProgram program = await FragmentProgram.fromAsset( + 'blue_green_sampler.frag.iplr', + ); + final FragmentShader fragmentShader = program.fragmentShader(); + + try { + Paint().shader = fragmentShader; + fail('Expected to throw'); + } catch (err) { + expect(err.toString(), contains('Invalid FragmentShader blue_green_sampler.frag.iplr')); + } finally { + fragmentShader.dispose(); + } + }); + test('Disposed FragmentShader on Paint', () async { final FragmentProgram program = await FragmentProgram.fromAsset( 'blue_green_sampler.frag.iplr',