From 8af165e4f4093a44fb07348967448b59767edeb7 Mon Sep 17 00:00:00 2001 From: Brandon DeRosier Date: Tue, 24 Sep 2024 22:27:28 -0700 Subject: [PATCH 1/8] [Flutter GPU] Add missing MSAA stuff. --- lib/gpu/context.cc | 5 ++ lib/gpu/context.h | 4 + lib/gpu/lib/src/command_buffer.dart | 8 +- lib/gpu/lib/src/context.dart | 16 ++++ lib/gpu/lib/src/render_pass.dart | 87 ++++++++++++++++++-- lib/gpu/render_pass.cc | 16 +++- lib/gpu/render_pass.h | 3 +- testing/dart/gpu_test.dart | 120 +++++++++++++++++++++------- 8 files changed, 221 insertions(+), 38 deletions(-) diff --git a/lib/gpu/context.cc b/lib/gpu/context.cc index c0c3345ce8487..b360711e55fc9 100644 --- a/lib/gpu/context.cc +++ b/lib/gpu/context.cc @@ -111,3 +111,8 @@ extern int InternalFlutterGpu_Context_GetMinimumUniformByteAlignment( flutter::gpu::Context* wrapper) { return impeller::DefaultUniformAlignment(); } + +extern bool InternalFlutterGpu_Context_GetSupportsOffscreenMSAA( + flutter::gpu::Context* wrapper) { + return wrapper->GetContext()->GetCapabilities()->SupportsOffscreenMSAA(); +} diff --git a/lib/gpu/context.h b/lib/gpu/context.h index b482ae479938b..852b0d3569f02 100644 --- a/lib/gpu/context.h +++ b/lib/gpu/context.h @@ -74,6 +74,10 @@ FLUTTER_GPU_EXPORT extern int InternalFlutterGpu_Context_GetMinimumUniformByteAlignment( flutter::gpu::Context* wrapper); +FLUTTER_GPU_EXPORT +extern bool InternalFlutterGpu_Context_GetSupportsOffscreenMSAA( + flutter::gpu::Context* wrapper); + } // extern "C" #endif // FLUTTER_LIB_GPU_CONTEXT_H_ diff --git a/lib/gpu/lib/src/command_buffer.dart b/lib/gpu/lib/src/command_buffer.dart index 09cae10b8fa75..20c0137ce69aa 100644 --- a/lib/gpu/lib/src/command_buffer.dart +++ b/lib/gpu/lib/src/command_buffer.dart @@ -9,13 +9,15 @@ part of flutter_gpu; typedef CompletionCallback = void Function(bool success); base class CommandBuffer extends NativeFieldWrapperClass1 { + final GpuContext _gpuContext; + /// Creates a new CommandBuffer. - CommandBuffer._(GpuContext gpuContext) { - _initialize(gpuContext); + CommandBuffer._(this._gpuContext) { + _initialize(_gpuContext); } RenderPass createRenderPass(RenderTarget renderTarget) { - return RenderPass._(this, renderTarget); + return RenderPass._(_gpuContext, this, renderTarget); } void submit({CompletionCallback? completionCallback}) { diff --git a/lib/gpu/lib/src/context.dart b/lib/gpu/lib/src/context.dart index 217b387314908..a0a0513468afe 100644 --- a/lib/gpu/lib/src/context.dart +++ b/lib/gpu/lib/src/context.dart @@ -46,6 +46,18 @@ base class GpuContext extends NativeFieldWrapperClass1 { return _getMinimumUniformByteAlignment(); } + /// Whether the backend supports multisample anti-aliasing for offscreen + /// color and stencil attachments. A subset of OpenGLES-only devices do not + /// support this functionality. + /// + /// Any texture created via [createTexture] is an offscreen texture. + /// There is currently no way to render directly against the "onscreen" + /// texture that the framework renders to, so all Flutter GPU textures are + /// "offscreen". + bool get doesSupportOffscreenMSAA { + return _getSupportsOffscreenMSAA(); + } + /// Allocates a new region of GPU-resident memory. /// /// The [storageMode] must be either [StorageMode.hostVisible] or @@ -134,6 +146,10 @@ base class GpuContext extends NativeFieldWrapperClass1 { @Native)>( symbol: 'InternalFlutterGpu_Context_GetMinimumUniformByteAlignment') external int _getMinimumUniformByteAlignment(); + + @Native)>( + symbol: 'InternalFlutterGpu_Context_GetSupportsOffscreenMSAA') + external bool _getSupportsOffscreenMSAA(); } /// The default graphics context. diff --git a/lib/gpu/lib/src/render_pass.dart b/lib/gpu/lib/src/render_pass.dart index 95d7b5f317add..f0af57e99486c 100644 --- a/lib/gpu/lib/src/render_pass.dart +++ b/lib/gpu/lib/src/render_pass.dart @@ -21,6 +21,32 @@ base class ColorAttachment { Texture texture; Texture? resolveTexture; + + void _assertValid() { + if (resolveTexture != null) { + assert(resolveTexture!.format == texture.format, + "ColorAttachment MSAA resolve texture must have the same format as the texture"); + assert( + resolveTexture!.width == texture.width && + resolveTexture!.height == texture.height, + "ColorAttachment MSAA resolve texture must have the same dimensions as the texture"); + assert(resolveTexture!.sampleCount == 1, + "ColorAttachment MSAA resolve texture must have a sample count of 1"); + assert(texture.sampleCount > 1, + "ColorAttachment must have a sample count greater than 1 when a MSAA resolve texture is set"); + assert( + storeAction == StoreAction.multisampleResolve || + storeAction == StoreAction.storeAndMultisampleResolve, + "ColorAttachment StoreAction must be multisampleResolve or storeAndMultisampleResolve when a resolve texture is set"); + assert(resolveTexture!.storageMode != StorageMode.deviceTransient, + "ColorAttachment MSAA resolve texture must not have a storage mode of deviceTransient"); + } + + assert( + texture.storageMode != StorageMode.deviceTransient || + loadAction != LoadAction.load, + "ColorAttachment loadAction must not be load when the texture has a storage mode of deviceTransient"); + } } base class DepthStencilAttachment { @@ -43,6 +69,43 @@ base class DepthStencilAttachment { int stencilClearValue; Texture texture; + Texture? resolveTexture; + + void _assertValid() { + if (resolveTexture != null) { + assert(resolveTexture!.format == texture.format, + "DepthStencilAttachment MSAA resolve texture must have the same format as the texture"); + assert( + resolveTexture!.width == texture.width && + resolveTexture!.height == texture.height, + "DepthStencilAttachment MSAA resolve texture must have the same dimensions as the texture"); + assert(resolveTexture!.sampleCount == 1, + "DepthStencilAttachment MSAA resolve texture must have a sample count of 1"); + assert(texture.sampleCount > 1, + "DepthStencilAttachment must have a sample count greater than 1 when a MSAA resolve texture is set"); + assert( + depthStoreAction == StoreAction.multisampleResolve || + depthStoreAction == StoreAction.storeAndMultisampleResolve, + "DepthStencilAttachment depthStoreAction must be multisampleResolve or storeAndMultisampleResolve when a resolve texture is set"); + assert( + stencilStoreAction == StoreAction.multisampleResolve || + stencilStoreAction == StoreAction.storeAndMultisampleResolve, + "DepthStencilAttachment stencilStoreAction must be multisampleResolve or storeAndMultisampleResolve when a resolve texture is set"); + assert(resolveTexture!.storageMode != StorageMode.deviceTransient, + "DepthStencilAttachment MSAA resolve texture must not have a storage mode of deviceTransient"); + } + + if (texture.storageMode == StorageMode.deviceTransient) { + assert( + texture.storageMode != StorageMode.deviceTransient || + depthLoadAction != LoadAction.load, + "DepthStencilAttachment depthLoadAction must not be load when the texture has a storage mode of deviceTransient"); + assert( + texture.storageMode != StorageMode.deviceTransient || + stencilLoadAction != LoadAction.load, + "DepthStencilAttachment stencilLoadAction must not be load when the texture has a storage mode of deviceTransient"); + } + } } base class StencilConfig { @@ -117,13 +180,25 @@ base class RenderTarget { colorAttachments: [colorAttachment], depthStencilAttachment: depthStencilAttachment); + _assertValid() { + for (final color in colorAttachments) { + color._assertValid(); + } + if (depthStencilAttachment != null) { + depthStencilAttachment!._assertValid(); + } + } + final List colorAttachments; final DepthStencilAttachment? depthStencilAttachment; } base class RenderPass extends NativeFieldWrapperClass1 { /// Creates a new RenderPass. - RenderPass._(CommandBuffer commandBuffer, RenderTarget renderTarget) { + RenderPass._(GpuContext gpuContext, CommandBuffer commandBuffer, + RenderTarget renderTarget) { + renderTarget._assertValid(); + _initialize(); String? error; for (final (index, color) in renderTarget.colorAttachments.indexed) { @@ -150,7 +225,8 @@ base class RenderPass extends NativeFieldWrapperClass1 { ds.stencilLoadAction.index, ds.stencilStoreAction.index, ds.stencilClearValue, - ds.texture); + ds.texture, + ds.resolveTexture); if (error != null) { throw Exception(error); } @@ -309,8 +385,8 @@ base class RenderPass extends NativeFieldWrapperClass1 { Texture? resolveTexture); @Native< - Handle Function( - Pointer, Int, Int, Float, Int, Int, Int, Pointer)>( + Handle Function(Pointer, Int, Int, Float, Int, Int, Int, + Pointer, Handle)>( symbol: 'InternalFlutterGpu_RenderPass_SetDepthStencilAttachment') external String? _setDepthStencilAttachment( int depthLoadAction, @@ -319,7 +395,8 @@ base class RenderPass extends NativeFieldWrapperClass1 { int stencilLoadAction, int stencilStoreAction, int stencilClearValue, - Texture texture); + Texture texture, + Texture? resolveTexture); @Native, Pointer)>( symbol: 'InternalFlutterGpu_RenderPass_Begin') diff --git a/lib/gpu/render_pass.cc b/lib/gpu/render_pass.cc index 70d68d0e78081..a6a93a8096237 100644 --- a/lib/gpu/render_pass.cc +++ b/lib/gpu/render_pass.cc @@ -255,13 +255,24 @@ Dart_Handle InternalFlutterGpu_RenderPass_SetDepthStencilAttachment( int stencil_load_action, int stencil_store_action, int stencil_clear_value, - flutter::gpu::Texture* texture) { + flutter::gpu::Texture* texture, + Dart_Handle resolve_texture_wrapper) { + flutter::gpu::Texture* resolve_texture = nullptr; + + if (!Dart_IsNull(resolve_texture_wrapper)) { + resolve_texture = tonic::DartConverter::FromDart( + resolve_texture_wrapper); + } + { impeller::DepthAttachment desc; desc.load_action = flutter::gpu::ToImpellerLoadAction(depth_load_action); desc.store_action = flutter::gpu::ToImpellerStoreAction(depth_store_action); desc.clear_depth = depth_clear_value; desc.texture = texture->GetTexture(); + if (resolve_texture) { + desc.resolve_texture = resolve_texture->GetTexture(); + } wrapper->GetRenderTarget().SetDepthAttachment(desc); } { @@ -271,6 +282,9 @@ Dart_Handle InternalFlutterGpu_RenderPass_SetDepthStencilAttachment( flutter::gpu::ToImpellerStoreAction(stencil_store_action); desc.clear_stencil = stencil_clear_value; desc.texture = texture->GetTexture(); + if (resolve_texture) { + desc.resolve_texture = resolve_texture->GetTexture(); + } wrapper->GetRenderTarget().SetStencilAttachment(desc); } diff --git a/lib/gpu/render_pass.h b/lib/gpu/render_pass.h index da9840d62699f..3b1873d73b38b 100644 --- a/lib/gpu/render_pass.h +++ b/lib/gpu/render_pass.h @@ -137,7 +137,8 @@ extern Dart_Handle InternalFlutterGpu_RenderPass_SetDepthStencilAttachment( int stencil_load_action, int stencil_store_action, int stencil_clear_value, - flutter::gpu::Texture* texture); + flutter::gpu::Texture* texture, + Dart_Handle resolve_texture_wrapper); FLUTTER_GPU_EXPORT extern Dart_Handle InternalFlutterGpu_RenderPass_Begin( diff --git a/testing/dart/gpu_test.dart b/testing/dart/gpu_test.dart index 508e7e08d9e6f..2fee673093eff 100644 --- a/testing/dart/gpu_test.dart +++ b/testing/dart/gpu_test.dart @@ -60,6 +60,7 @@ RenderPassState createSimpleRenderPass({Vector4? clearColor}) { final gpu.Texture? depthStencilTexture = gpu.gpuContext.createTexture( gpu.StorageMode.deviceTransient, 100, 100, format: gpu.gpuContext.defaultDepthStencilFormat); + assert(depthStencilTexture != null); final gpu.CommandBuffer commandBuffer = gpu.gpuContext.createCommandBuffer(); @@ -74,6 +75,70 @@ RenderPassState createSimpleRenderPass({Vector4? clearColor}) { return RenderPassState(renderTexture, commandBuffer, renderPass); } +RenderPassState createSimpleRenderPassWithMSAA() { + // Create transient MSAA attachments, which will live entirely in tile memory + // for most GPUs. + + final gpu.Texture? renderTexture = gpu.gpuContext.createTexture( + gpu.StorageMode.deviceTransient, 100, 100, + format: gpu.gpuContext.defaultColorFormat, sampleCount: 4); + assert(renderTexture != null); + + final gpu.Texture? depthStencilTexture = gpu.gpuContext.createTexture( + gpu.StorageMode.deviceTransient, 100, 100, + format: gpu.gpuContext.defaultDepthStencilFormat, sampleCount: 4); + assert(depthStencilTexture != null); + + // Create the single-sample resolve texture that live in DRAM and will be + // drawn to the screen. + + final gpu.Texture? resolveTexture = gpu.gpuContext.createTexture( + gpu.StorageMode.devicePrivate, 100, 100, + format: gpu.gpuContext.defaultColorFormat); + assert(resolveTexture != null); + + final gpu.CommandBuffer commandBuffer = gpu.gpuContext.createCommandBuffer(); + + final gpu.RenderTarget renderTarget = gpu.RenderTarget.singleColor( + gpu.ColorAttachment( + texture: renderTexture!, + resolveTexture: resolveTexture, + storeAction: gpu.StoreAction.multisampleResolve), + depthStencilAttachment: + gpu.DepthStencilAttachment(texture: depthStencilTexture!)); + + final gpu.RenderPass renderPass = + commandBuffer.createRenderPass(renderTarget); + + return RenderPassState(resolveTexture!, commandBuffer, renderPass); +} + +void drawTriangle(RenderPassState state, Vector4 color) { + final gpu.RenderPipeline pipeline = createUnlitRenderPipeline(); + + state.renderPass.bindPipeline(pipeline); + + final gpu.HostBuffer transients = gpu.gpuContext.createHostBuffer(); + final gpu.BufferView vertices = transients.emplace(float32([ + -0.5, 0.5, // + 0.0, -0.5, // + 0.5, 0.5, // + ])); + final gpu.BufferView vertInfoData = + transients.emplace(unlitUBO(Matrix4.identity(), color)); + state.renderPass.bindVertexBuffer(vertices, 3); + + // TODO(bdero): Overwrite bindings with the same slot so we don't need to clear. + // https://github.com/flutter/flutter/issues/155335 + state.renderPass.clearBindings(); + + final gpu.UniformSlot vertInfo = + pipeline.vertexShader.getUniformSlot('VertInfo'); + state.renderPass.bindUniform(vertInfo, vertInfoData); + + state.renderPass.draw(); +} + void main() async { final ImageComparer comparer = await ImageComparer.create(); @@ -337,34 +402,7 @@ void main() async { // Renders a green triangle pointing downwards. test('Can render triangle', () async { final state = createSimpleRenderPass(); - - final gpu.RenderPipeline pipeline = createUnlitRenderPipeline(); - state.renderPass.bindPipeline(pipeline); - - // Configure blending with defaults (just to test the bindings). - state.renderPass.setColorBlendEnable(true); - state.renderPass.setColorBlendEquation(gpu.ColorBlendEquation()); - - final gpu.HostBuffer transients = gpu.gpuContext.createHostBuffer(); - final gpu.BufferView vertices = transients.emplace(float32([ - -0.5, 0.5, // - 0.0, -0.5, // - 0.5, 0.5, // - ])); - final gpu.BufferView vertInfoData = transients.emplace(float32([ - 1, 0, 0, 0, // mvp - 0, 1, 0, 0, // mvp - 0, 0, 1, 0, // mvp - 0, 0, 0, 1, // mvp - 0, 1, 0, 1, // color - ])); - state.renderPass.bindVertexBuffer(vertices, 3); - - final gpu.UniformSlot vertInfo = - pipeline.vertexShader.getUniformSlot('VertInfo'); - state.renderPass.bindUniform(vertInfo, vertInfoData); - state.renderPass.draw(); - + drawTriangle(state, Colors.lime); state.commandBuffer.submit(); final ui.Image image = state.renderTexture.asImage(); @@ -411,6 +449,32 @@ void main() async { await comparer.addGoldenImage(image, 'flutter_gpu_test_triangle_polygon_mode.png'); }, skip: !impellerEnabled); + // Renders a green triangle pointing downwards, with 4xMSAA. + test('Can render triangle with MSAA', () async { + final state = createSimpleRenderPassWithMSAA(); + drawTriangle(state, Colors.lime); + state.commandBuffer.submit(); + + final ui.Image image = state.renderTexture.asImage(); + await comparer.addGoldenImage(image, 'flutter_gpu_test_triangle_msaa.png'); + }, skip: !(impellerEnabled && gpu.gpuContext.doesSupportOffscreenMSAA)); + + test( + 'Rendering with MSAA throws exception when offscreen MSAA is not supported', + () async { + try { + final state = createSimpleRenderPassWithMSAA(); + drawTriangle(state, Colors.lime); + state.commandBuffer.submit(); + fail('Exception not thrown when offscreen MSAA is not supported.'); + } catch (e) { + expect( + e.toString(), + contains( + 'The backend does not support multisample anti-aliasing for offscreen color and stencil attachments')); + } + }, skip: gpu.gpuContext.doesSupportOffscreenMSAA); + // Renders a hollow green triangle pointing downwards. test('Can render hollowed out triangle using stencil ops', () async { final state = createSimpleRenderPass(); From 4a2974885dce230c81a38c7875d732419c478352 Mon Sep 17 00:00:00 2001 From: Brandon DeRosier Date: Tue, 8 Oct 2024 15:11:13 -0700 Subject: [PATCH 2/8] Use exceptions for errors --- lib/gpu/lib/src/render_pass.dart | 129 ++++++++++++++++++------------- 1 file changed, 74 insertions(+), 55 deletions(-) diff --git a/lib/gpu/lib/src/render_pass.dart b/lib/gpu/lib/src/render_pass.dart index f0af57e99486c..6213f66373347 100644 --- a/lib/gpu/lib/src/render_pass.dart +++ b/lib/gpu/lib/src/render_pass.dart @@ -22,30 +22,35 @@ base class ColorAttachment { Texture texture; Texture? resolveTexture; - void _assertValid() { + void _validate() { if (resolveTexture != null) { - assert(resolveTexture!.format == texture.format, - "ColorAttachment MSAA resolve texture must have the same format as the texture"); - assert( - resolveTexture!.width == texture.width && - resolveTexture!.height == texture.height, - "ColorAttachment MSAA resolve texture must have the same dimensions as the texture"); - assert(resolveTexture!.sampleCount == 1, - "ColorAttachment MSAA resolve texture must have a sample count of 1"); - assert(texture.sampleCount > 1, - "ColorAttachment must have a sample count greater than 1 when a MSAA resolve texture is set"); - assert( - storeAction == StoreAction.multisampleResolve || - storeAction == StoreAction.storeAndMultisampleResolve, - "ColorAttachment StoreAction must be multisampleResolve or storeAndMultisampleResolve when a resolve texture is set"); - assert(resolveTexture!.storageMode != StorageMode.deviceTransient, - "ColorAttachment MSAA resolve texture must not have a storage mode of deviceTransient"); + if (resolveTexture!.format != texture.format) { + throw Exception( + "ColorAttachment MSAA resolve texture must have the same format as the texture"); + } + if (resolveTexture!.width != texture.width || + resolveTexture!.height != texture.height) { + throw Exception( + "ColorAttachment MSAA resolve texture must have the same dimensions as the texture"); + } + if (resolveTexture!.sampleCount != 1) { + throw Exception( + "ColorAttachment MSAA resolve texture must have a sample count of 1"); + } + if (texture.sampleCount <= 1) { + throw Exception( + "ColorAttachment must have a sample count greater than 1 when a MSAA resolve texture is set"); + } + if (storeAction != StoreAction.multisampleResolve && + storeAction != StoreAction.storeAndMultisampleResolve) { + throw Exception( + "ColorAttachment StoreAction must be multisampleResolve or storeAndMultisampleResolve when a resolve texture is set"); + } + if (resolveTexture!.storageMode == StorageMode.deviceTransient) { + throw Exception( + "ColorAttachment MSAA resolve texture must not have a storage mode of deviceTransient"); + } } - - assert( - texture.storageMode != StorageMode.deviceTransient || - loadAction != LoadAction.load, - "ColorAttachment loadAction must not be load when the texture has a storage mode of deviceTransient"); } } @@ -71,39 +76,50 @@ base class DepthStencilAttachment { Texture texture; Texture? resolveTexture; - void _assertValid() { + void _validate() { if (resolveTexture != null) { - assert(resolveTexture!.format == texture.format, - "DepthStencilAttachment MSAA resolve texture must have the same format as the texture"); - assert( - resolveTexture!.width == texture.width && - resolveTexture!.height == texture.height, - "DepthStencilAttachment MSAA resolve texture must have the same dimensions as the texture"); - assert(resolveTexture!.sampleCount == 1, - "DepthStencilAttachment MSAA resolve texture must have a sample count of 1"); - assert(texture.sampleCount > 1, - "DepthStencilAttachment must have a sample count greater than 1 when a MSAA resolve texture is set"); - assert( - depthStoreAction == StoreAction.multisampleResolve || - depthStoreAction == StoreAction.storeAndMultisampleResolve, - "DepthStencilAttachment depthStoreAction must be multisampleResolve or storeAndMultisampleResolve when a resolve texture is set"); - assert( - stencilStoreAction == StoreAction.multisampleResolve || - stencilStoreAction == StoreAction.storeAndMultisampleResolve, - "DepthStencilAttachment stencilStoreAction must be multisampleResolve or storeAndMultisampleResolve when a resolve texture is set"); - assert(resolveTexture!.storageMode != StorageMode.deviceTransient, - "DepthStencilAttachment MSAA resolve texture must not have a storage mode of deviceTransient"); + if (resolveTexture!.format != texture.format) { + throw Exception( + "DepthStencilAttachment MSAA resolve texture must have the same format as the texture"); + } + if (resolveTexture!.width != texture.width || + resolveTexture!.height != texture.height) { + throw Exception( + "DepthStencilAttachment MSAA resolve texture must have the same dimensions as the texture"); + } + if (resolveTexture!.sampleCount != 1) { + throw Exception( + "DepthStencilAttachment MSAA resolve texture must have a sample count of 1"); + } + if (texture.sampleCount <= 1) { + throw Exception( + "DepthStencilAttachment must have a sample count greater than 1 when a MSAA resolve texture is set"); + } + if (depthStoreAction != StoreAction.multisampleResolve && + depthStoreAction != StoreAction.storeAndMultisampleResolve) { + throw Exception( + "DepthStencilAttachment depthStoreAction must be multisampleResolve or storeAndMultisampleResolve when a resolve texture is set"); + } + if (stencilStoreAction != StoreAction.multisampleResolve && + stencilStoreAction != StoreAction.storeAndMultisampleResolve) { + throw Exception( + "DepthStencilAttachment stencilStoreAction must be multisampleResolve or storeAndMultisampleResolve when a resolve texture is set"); + } + if (resolveTexture!.storageMode == StorageMode.deviceTransient) { + throw Exception( + "DepthStencilAttachment MSAA resolve texture must not have a storage mode of deviceTransient"); + } } if (texture.storageMode == StorageMode.deviceTransient) { - assert( - texture.storageMode != StorageMode.deviceTransient || - depthLoadAction != LoadAction.load, - "DepthStencilAttachment depthLoadAction must not be load when the texture has a storage mode of deviceTransient"); - assert( - texture.storageMode != StorageMode.deviceTransient || - stencilLoadAction != LoadAction.load, - "DepthStencilAttachment stencilLoadAction must not be load when the texture has a storage mode of deviceTransient"); + if (depthLoadAction == LoadAction.load) { + throw Exception( + "DepthStencilAttachment depthLoadAction must not be load when the texture has a storage mode of deviceTransient"); + } + if (stencilLoadAction == LoadAction.load) { + throw Exception( + "DepthStencilAttachment stencilLoadAction must not be load when the texture has a storage mode of deviceTransient"); + } } } } @@ -180,12 +196,12 @@ base class RenderTarget { colorAttachments: [colorAttachment], depthStencilAttachment: depthStencilAttachment); - _assertValid() { + _validate() { for (final color in colorAttachments) { - color._assertValid(); + color._validate(); } if (depthStencilAttachment != null) { - depthStencilAttachment!._assertValid(); + depthStencilAttachment!._validate(); } } @@ -197,7 +213,10 @@ base class RenderPass extends NativeFieldWrapperClass1 { /// Creates a new RenderPass. RenderPass._(GpuContext gpuContext, CommandBuffer commandBuffer, RenderTarget renderTarget) { - renderTarget._assertValid(); + assert(() { + renderTarget._validate(); + return true; + }()); _initialize(); String? error; From 10ef5b4ad3a015b6841075d8a47dad030db33f6f Mon Sep 17 00:00:00 2001 From: Brandon DeRosier Date: Tue, 8 Oct 2024 15:14:07 -0700 Subject: [PATCH 3/8] Remove depthstencil resolve --- lib/gpu/lib/src/render_pass.dart | 45 +++----------------------------- lib/gpu/render_pass.cc | 16 +----------- lib/gpu/render_pass.h | 3 +-- 3 files changed, 6 insertions(+), 58 deletions(-) diff --git a/lib/gpu/lib/src/render_pass.dart b/lib/gpu/lib/src/render_pass.dart index 6213f66373347..5782271072769 100644 --- a/lib/gpu/lib/src/render_pass.dart +++ b/lib/gpu/lib/src/render_pass.dart @@ -74,43 +74,8 @@ base class DepthStencilAttachment { int stencilClearValue; Texture texture; - Texture? resolveTexture; void _validate() { - if (resolveTexture != null) { - if (resolveTexture!.format != texture.format) { - throw Exception( - "DepthStencilAttachment MSAA resolve texture must have the same format as the texture"); - } - if (resolveTexture!.width != texture.width || - resolveTexture!.height != texture.height) { - throw Exception( - "DepthStencilAttachment MSAA resolve texture must have the same dimensions as the texture"); - } - if (resolveTexture!.sampleCount != 1) { - throw Exception( - "DepthStencilAttachment MSAA resolve texture must have a sample count of 1"); - } - if (texture.sampleCount <= 1) { - throw Exception( - "DepthStencilAttachment must have a sample count greater than 1 when a MSAA resolve texture is set"); - } - if (depthStoreAction != StoreAction.multisampleResolve && - depthStoreAction != StoreAction.storeAndMultisampleResolve) { - throw Exception( - "DepthStencilAttachment depthStoreAction must be multisampleResolve or storeAndMultisampleResolve when a resolve texture is set"); - } - if (stencilStoreAction != StoreAction.multisampleResolve && - stencilStoreAction != StoreAction.storeAndMultisampleResolve) { - throw Exception( - "DepthStencilAttachment stencilStoreAction must be multisampleResolve or storeAndMultisampleResolve when a resolve texture is set"); - } - if (resolveTexture!.storageMode == StorageMode.deviceTransient) { - throw Exception( - "DepthStencilAttachment MSAA resolve texture must not have a storage mode of deviceTransient"); - } - } - if (texture.storageMode == StorageMode.deviceTransient) { if (depthLoadAction == LoadAction.load) { throw Exception( @@ -244,8 +209,7 @@ base class RenderPass extends NativeFieldWrapperClass1 { ds.stencilLoadAction.index, ds.stencilStoreAction.index, ds.stencilClearValue, - ds.texture, - ds.resolveTexture); + ds.texture); if (error != null) { throw Exception(error); } @@ -404,8 +368,8 @@ base class RenderPass extends NativeFieldWrapperClass1 { Texture? resolveTexture); @Native< - Handle Function(Pointer, Int, Int, Float, Int, Int, Int, - Pointer, Handle)>( + Handle Function( + Pointer, Int, Int, Float, Int, Int, Int, Pointer)>( symbol: 'InternalFlutterGpu_RenderPass_SetDepthStencilAttachment') external String? _setDepthStencilAttachment( int depthLoadAction, @@ -414,8 +378,7 @@ base class RenderPass extends NativeFieldWrapperClass1 { int stencilLoadAction, int stencilStoreAction, int stencilClearValue, - Texture texture, - Texture? resolveTexture); + Texture texture); @Native, Pointer)>( symbol: 'InternalFlutterGpu_RenderPass_Begin') diff --git a/lib/gpu/render_pass.cc b/lib/gpu/render_pass.cc index a6a93a8096237..70d68d0e78081 100644 --- a/lib/gpu/render_pass.cc +++ b/lib/gpu/render_pass.cc @@ -255,24 +255,13 @@ Dart_Handle InternalFlutterGpu_RenderPass_SetDepthStencilAttachment( int stencil_load_action, int stencil_store_action, int stencil_clear_value, - flutter::gpu::Texture* texture, - Dart_Handle resolve_texture_wrapper) { - flutter::gpu::Texture* resolve_texture = nullptr; - - if (!Dart_IsNull(resolve_texture_wrapper)) { - resolve_texture = tonic::DartConverter::FromDart( - resolve_texture_wrapper); - } - + flutter::gpu::Texture* texture) { { impeller::DepthAttachment desc; desc.load_action = flutter::gpu::ToImpellerLoadAction(depth_load_action); desc.store_action = flutter::gpu::ToImpellerStoreAction(depth_store_action); desc.clear_depth = depth_clear_value; desc.texture = texture->GetTexture(); - if (resolve_texture) { - desc.resolve_texture = resolve_texture->GetTexture(); - } wrapper->GetRenderTarget().SetDepthAttachment(desc); } { @@ -282,9 +271,6 @@ Dart_Handle InternalFlutterGpu_RenderPass_SetDepthStencilAttachment( flutter::gpu::ToImpellerStoreAction(stencil_store_action); desc.clear_stencil = stencil_clear_value; desc.texture = texture->GetTexture(); - if (resolve_texture) { - desc.resolve_texture = resolve_texture->GetTexture(); - } wrapper->GetRenderTarget().SetStencilAttachment(desc); } diff --git a/lib/gpu/render_pass.h b/lib/gpu/render_pass.h index 3b1873d73b38b..da9840d62699f 100644 --- a/lib/gpu/render_pass.h +++ b/lib/gpu/render_pass.h @@ -137,8 +137,7 @@ extern Dart_Handle InternalFlutterGpu_RenderPass_SetDepthStencilAttachment( int stencil_load_action, int stencil_store_action, int stencil_clear_value, - flutter::gpu::Texture* texture, - Dart_Handle resolve_texture_wrapper); + flutter::gpu::Texture* texture); FLUTTER_GPU_EXPORT extern Dart_Handle InternalFlutterGpu_RenderPass_Begin( From f64cd0d5a4a8ecadfb16bf60f826eea5c45f5c38 Mon Sep 17 00:00:00 2001 From: Brandon DeRosier Date: Fri, 18 Oct 2024 18:23:43 -0700 Subject: [PATCH 4/8] Gracefully fallback to not using MSAA when normal MSAA is not supported. --- lib/gpu/context.cc | 9 ++++++++- lib/gpu/context.h | 2 ++ lib/gpu/render_pass.cc | 9 +++++++++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/lib/gpu/context.cc b/lib/gpu/context.cc index b360711e55fc9..7204ac443c375 100644 --- a/lib/gpu/context.cc +++ b/lib/gpu/context.cc @@ -10,11 +10,18 @@ #include "flutter/lib/ui/ui_dart_state.h" #include "fml/make_copyable.h" #include "impeller/core/platform.h" +#include "impeller/renderer/context.h" #include "tonic/converter/dart_converter.h" namespace flutter { namespace gpu { +bool SupportsNormalOffscreenMSAA(const impeller::Context& context) { + auto& capabilities = context.GetCapabilities(); + return capabilities->SupportsOffscreenMSAA() && + !capabilities->SupportsImplicitResolvingMSAA(); +} + IMPLEMENT_WRAPPERTYPEINFO(flutter_gpu, Context); std::shared_ptr Context::default_context_; @@ -114,5 +121,5 @@ extern int InternalFlutterGpu_Context_GetMinimumUniformByteAlignment( extern bool InternalFlutterGpu_Context_GetSupportsOffscreenMSAA( flutter::gpu::Context* wrapper) { - return wrapper->GetContext()->GetCapabilities()->SupportsOffscreenMSAA(); + return flutter::gpu::SupportsNormalOffscreenMSAA(*wrapper->GetContext()); } diff --git a/lib/gpu/context.h b/lib/gpu/context.h index 852b0d3569f02..4dbabcb5e432d 100644 --- a/lib/gpu/context.h +++ b/lib/gpu/context.h @@ -13,6 +13,8 @@ namespace flutter { namespace gpu { +bool SupportsNormalOffscreenMSAA(const impeller::Context& context); + class Context : public RefCountedDartWrappable { DEFINE_WRAPPERTYPEINFO(); FML_FRIEND_MAKE_REF_COUNTED(Context); diff --git a/lib/gpu/render_pass.cc b/lib/gpu/render_pass.cc index 70d68d0e78081..1efc53f09a526 100644 --- a/lib/gpu/render_pass.cc +++ b/lib/gpu/render_pass.cc @@ -20,6 +20,7 @@ #include "impeller/renderer/pipeline.h" #include "impeller/renderer/pipeline_descriptor.h" #include "impeller/renderer/pipeline_library.h" +#include "lib/gpu/context.h" #include "lib/ui/ui_dart_state.h" #include "tonic/converter/dart_converter.h" @@ -242,6 +243,14 @@ Dart_Handle InternalFlutterGpu_RenderPass_SetColorAttachment( tonic::DartConverter::FromDart( resolve_texture_wrapper); desc.resolve_texture = resolve_texture->GetTexture(); + + // If the backend doesn't support normal MSAA, gracefully fallback to + // rendering without MSAA. + if (!flutter::gpu::SupportsNormalOffscreenMSAA(*wrapper->GetContext())) { + desc.texture = desc.resolve_texture; + desc.resolve_texture = nullptr; + desc.store_action = impeller::StoreAction::kStore; + } } wrapper->GetRenderTarget().SetColorAttachment(desc, color_attachment_index); return Dart_Null(); From 29bd4c4d3fa712d28d0a274e9b68b9c7a94a0e77 Mon Sep 17 00:00:00 2001 From: Brandon DeRosier Date: Fri, 18 Oct 2024 19:52:39 -0700 Subject: [PATCH 5/8] Remove clearBindings hack --- testing/dart/gpu_test.dart | 4 ---- 1 file changed, 4 deletions(-) diff --git a/testing/dart/gpu_test.dart b/testing/dart/gpu_test.dart index 2fee673093eff..d26826e917150 100644 --- a/testing/dart/gpu_test.dart +++ b/testing/dart/gpu_test.dart @@ -128,10 +128,6 @@ void drawTriangle(RenderPassState state, Vector4 color) { transients.emplace(unlitUBO(Matrix4.identity(), color)); state.renderPass.bindVertexBuffer(vertices, 3); - // TODO(bdero): Overwrite bindings with the same slot so we don't need to clear. - // https://github.com/flutter/flutter/issues/155335 - state.renderPass.clearBindings(); - final gpu.UniformSlot vertInfo = pipeline.vertexShader.getUniformSlot('VertInfo'); state.renderPass.bindUniform(vertInfo, vertInfoData); From 930646fe7ce12423dfd3c48022359ecefc569b46 Mon Sep 17 00:00:00 2001 From: Brandon DeRosier Date: Mon, 21 Oct 2024 16:17:24 -0700 Subject: [PATCH 6/8] The RenderPass isn't initialized yet, so pass the gpuContext to SetColorAttachment. --- lib/gpu/lib/src/render_pass.dart | 5 ++++- lib/gpu/render_pass.cc | 3 ++- lib/gpu/render_pass.h | 1 + 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/gpu/lib/src/render_pass.dart b/lib/gpu/lib/src/render_pass.dart index 5782271072769..02e373c328f58 100644 --- a/lib/gpu/lib/src/render_pass.dart +++ b/lib/gpu/lib/src/render_pass.dart @@ -187,6 +187,7 @@ base class RenderPass extends NativeFieldWrapperClass1 { String? error; for (final (index, color) in renderTarget.colorAttachments.indexed) { error = _setColorAttachment( + gpuContext, index, color.loadAction.index, color.storeAction.index, @@ -346,6 +347,7 @@ base class RenderPass extends NativeFieldWrapperClass1 { @Native< Handle Function( + Pointer, Pointer, Int, Int, @@ -357,6 +359,7 @@ base class RenderPass extends NativeFieldWrapperClass1 { Pointer, Handle)>(symbol: 'InternalFlutterGpu_RenderPass_SetColorAttachment') external String? _setColorAttachment( + GpuContext context, int colorAttachmentIndex, int loadAction, int storeAction, @@ -491,7 +494,7 @@ base class RenderPass extends NativeFieldWrapperClass1 { symbol: 'InternalFlutterGpu_RenderPass_SetPrimitiveType') external void _setPrimitiveType(int primitiveType); - @Native, Int)>( + @Native, Int)>( symbol: 'InternalFlutterGpu_RenderPass_SetWindingOrder') external void _setWindingOrder(int windingOrder); diff --git a/lib/gpu/render_pass.cc b/lib/gpu/render_pass.cc index 1efc53f09a526..fda1dbbd23852 100644 --- a/lib/gpu/render_pass.cc +++ b/lib/gpu/render_pass.cc @@ -223,6 +223,7 @@ void InternalFlutterGpu_RenderPass_Initialize(Dart_Handle wrapper) { Dart_Handle InternalFlutterGpu_RenderPass_SetColorAttachment( flutter::gpu::RenderPass* wrapper, + flutter::gpu::Context* context, int color_attachment_index, int load_action, int store_action, @@ -246,7 +247,7 @@ Dart_Handle InternalFlutterGpu_RenderPass_SetColorAttachment( // If the backend doesn't support normal MSAA, gracefully fallback to // rendering without MSAA. - if (!flutter::gpu::SupportsNormalOffscreenMSAA(*wrapper->GetContext())) { + if (!flutter::gpu::SupportsNormalOffscreenMSAA(*context->GetContext())) { desc.texture = desc.resolve_texture; desc.resolve_texture = nullptr; desc.store_action = impeller::StoreAction::kStore; diff --git a/lib/gpu/render_pass.h b/lib/gpu/render_pass.h index da9840d62699f..865e375ac7955 100644 --- a/lib/gpu/render_pass.h +++ b/lib/gpu/render_pass.h @@ -118,6 +118,7 @@ extern void InternalFlutterGpu_RenderPass_Initialize(Dart_Handle wrapper); FLUTTER_GPU_EXPORT extern Dart_Handle InternalFlutterGpu_RenderPass_SetColorAttachment( flutter::gpu::RenderPass* wrapper, + flutter::gpu::Context* context, int color_attachment_index, int load_action, int store_action, From fe133820c531f0e16122cbf5f6a6d0b3f53a9940 Mon Sep 17 00:00:00 2001 From: Brandon DeRosier Date: Mon, 21 Oct 2024 16:45:38 -0700 Subject: [PATCH 7/8] Correct test skip logic --- testing/dart/gpu_test.dart | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/testing/dart/gpu_test.dart b/testing/dart/gpu_test.dart index d26826e917150..864620b1be7c7 100644 --- a/testing/dart/gpu_test.dart +++ b/testing/dart/gpu_test.dart @@ -442,7 +442,8 @@ void main() async { state.commandBuffer.submit(); final ui.Image image = state.renderTexture.asImage(); - await comparer.addGoldenImage(image, 'flutter_gpu_test_triangle_polygon_mode.png'); + await comparer.addGoldenImage( + image, 'flutter_gpu_test_triangle_polygon_mode.png'); }, skip: !impellerEnabled); // Renders a green triangle pointing downwards, with 4xMSAA. @@ -469,7 +470,7 @@ void main() async { contains( 'The backend does not support multisample anti-aliasing for offscreen color and stencil attachments')); } - }, skip: gpu.gpuContext.doesSupportOffscreenMSAA); + }, skip: !(impellerEnabled && gpu.gpuContext.doesSupportOffscreenMSAA)); // Renders a hollow green triangle pointing downwards. test('Can render hollowed out triangle using stencil ops', () async { @@ -608,13 +609,20 @@ void main() async { final gpu.HostBuffer transients = gpu.gpuContext.createHostBuffer(); final gpu.BufferView vertices = transients.emplace(float32([ - 1.0, 0.0, - 0.5, 0.8, - -0.5, 0.8, - -1.0, 0.0, - -0.5, -0.8, - 0.5, -0.8, - 1.0, 0.0 + 1.0, + 0.0, + 0.5, + 0.8, + -0.5, + 0.8, + -1.0, + 0.0, + -0.5, + -0.8, + 0.5, + -0.8, + 1.0, + 0.0 ])); final gpu.BufferView vertInfoData = transients.emplace(float32([ 1, 0, 0, 0, // mvp @@ -633,6 +641,7 @@ void main() async { state.commandBuffer.submit(); final ui.Image image = state.renderTexture.asImage(); - await comparer.addGoldenImage(image, 'flutter_gpu_test_hexgon_line_strip.png'); + await comparer.addGoldenImage( + image, 'flutter_gpu_test_hexgon_line_strip.png'); }, skip: !impellerEnabled); } From 00ee6f575e29efa200a6abcf1e6fa881be8dd31b Mon Sep 17 00:00:00 2001 From: Brandon DeRosier Date: Mon, 21 Oct 2024 17:21:53 -0700 Subject: [PATCH 8/8] Fix skip logic more --- testing/dart/gpu_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/dart/gpu_test.dart b/testing/dart/gpu_test.dart index 864620b1be7c7..47c0c7d1ea838 100644 --- a/testing/dart/gpu_test.dart +++ b/testing/dart/gpu_test.dart @@ -470,7 +470,7 @@ void main() async { contains( 'The backend does not support multisample anti-aliasing for offscreen color and stencil attachments')); } - }, skip: !(impellerEnabled && gpu.gpuContext.doesSupportOffscreenMSAA)); + }, skip: !(impellerEnabled && !gpu.gpuContext.doesSupportOffscreenMSAA)); // Renders a hollow green triangle pointing downwards. test('Can render hollowed out triangle using stencil ops', () async {