From e496b1ed26bd2dd30a844d09c26bc25ecea8f1bf Mon Sep 17 00:00:00 2001 From: Brandon DeRosier Date: Tue, 24 Sep 2024 17:52:11 -0700 Subject: [PATCH] [Flutter GPU] Add CullMode. (flutter/engine#55409) Part of https://github.com/flutter/flutter/issues/155636. New golden `flutter_gpu_test_cull_mode` will draw a red triangle if the CullMode isn't working: ![flutter_gpu_test_cull_mode](https://github.com/user-attachments/assets/cbdf804e-1608-4352-9aa1-d5d9223f3c1a) --- engine/src/flutter/lib/gpu/formats.h | 21 ++++++++ .../src/flutter/lib/gpu/lib/src/formats.dart | 6 +++ .../flutter/lib/gpu/lib/src/render_pass.dart | 8 +++ engine/src/flutter/lib/gpu/render_pass.cc | 12 +++++ engine/src/flutter/lib/gpu/render_pass.h | 7 +++ engine/src/flutter/testing/dart/gpu_test.dart | 54 +++++++++++++++++++ 6 files changed, 108 insertions(+) diff --git a/engine/src/flutter/lib/gpu/formats.h b/engine/src/flutter/lib/gpu/formats.h index 8c703032ad..a70d608889 100644 --- a/engine/src/flutter/lib/gpu/formats.h +++ b/engine/src/flutter/lib/gpu/formats.h @@ -501,6 +501,27 @@ constexpr impeller::StencilOperation ToImpellerStencilOperation(int value) { static_cast(value)); } +enum class FlutterGPUCullMode { + kNone, + kFrontFace, + kBackFace, +}; + +constexpr impeller::CullMode ToImpellerCullMode(FlutterGPUCullMode value) { + switch (value) { + case FlutterGPUCullMode::kNone: + return impeller::CullMode::kNone; + case FlutterGPUCullMode::kFrontFace: + return impeller::CullMode::kFrontFace; + case FlutterGPUCullMode::kBackFace: + return impeller::CullMode::kBackFace; + } +} + +constexpr impeller::CullMode ToImpellerCullMode(int value) { + return ToImpellerCullMode(static_cast(value)); +} + } // namespace gpu } // namespace flutter diff --git a/engine/src/flutter/lib/gpu/lib/src/formats.dart b/engine/src/flutter/lib/gpu/lib/src/formats.dart index 9ee89bb6cc..2bd1b5ddd7 100644 --- a/engine/src/flutter/lib/gpu/lib/src/formats.dart +++ b/engine/src/flutter/lib/gpu/lib/src/formats.dart @@ -135,6 +135,12 @@ enum PrimitiveType { point, } +enum CullMode { + none, + frontFace, + backFace, +} + enum CompareFunction { /// Comparison test never passes. never, diff --git a/engine/src/flutter/lib/gpu/lib/src/render_pass.dart b/engine/src/flutter/lib/gpu/lib/src/render_pass.dart index 8e382a2b48..c3b7ab7814 100644 --- a/engine/src/flutter/lib/gpu/lib/src/render_pass.dart +++ b/engine/src/flutter/lib/gpu/lib/src/render_pass.dart @@ -264,6 +264,10 @@ base class RenderPass extends NativeFieldWrapperClass1 { targetFace.index); } + void setCullMode(CullMode cullMode) { + _setCullMode(cullMode.index); + } + void draw() { if (!_draw()) { throw Exception("Failed to append draw"); @@ -402,6 +406,10 @@ base class RenderPass extends NativeFieldWrapperClass1 { int writeMask, int target_face); + @Native, Int)>( + symbol: 'InternalFlutterGpu_RenderPass_SetCullMode') + external void _setCullMode(int cullMode); + @Native)>( symbol: 'InternalFlutterGpu_RenderPass_Draw') external bool _draw(); diff --git a/engine/src/flutter/lib/gpu/render_pass.cc b/engine/src/flutter/lib/gpu/render_pass.cc index c01c554b94..068dc68a75 100644 --- a/engine/src/flutter/lib/gpu/render_pass.cc +++ b/engine/src/flutter/lib/gpu/render_pass.cc @@ -76,6 +76,10 @@ impeller::VertexBuffer& RenderPass::GetVertexBuffer() { return vertex_buffer_; } +impeller::PipelineDescriptor& RenderPass::GetPipelineDescriptor() { + return pipeline_descriptor_; +} + bool RenderPass::Begin(flutter::gpu::CommandBuffer& command_buffer) { render_pass_ = command_buffer.GetCommandBuffer()->CreateRenderPass(render_target_); @@ -558,6 +562,14 @@ void InternalFlutterGpu_RenderPass_SetStencilConfig( } } +void InternalFlutterGpu_RenderPass_SetCullMode( + flutter::gpu::RenderPass* wrapper, + int cull_mode) { + impeller::PipelineDescriptor& pipeline_descriptor = + wrapper->GetPipelineDescriptor(); + pipeline_descriptor.SetCullMode(flutter::gpu::ToImpellerCullMode(cull_mode)); +} + bool InternalFlutterGpu_RenderPass_Draw(flutter::gpu::RenderPass* wrapper) { return wrapper->Draw(); } diff --git a/engine/src/flutter/lib/gpu/render_pass.h b/engine/src/flutter/lib/gpu/render_pass.h index 7367d0fc5b..89ccf0a55d 100644 --- a/engine/src/flutter/lib/gpu/render_pass.h +++ b/engine/src/flutter/lib/gpu/render_pass.h @@ -52,6 +52,8 @@ class RenderPass : public RefCountedDartWrappable { impeller::VertexBuffer& GetVertexBuffer(); + impeller::PipelineDescriptor& GetPipelineDescriptor(); + bool Begin(flutter::gpu::CommandBuffer& command_buffer); void SetPipeline(fml::RefPtr pipeline); @@ -241,6 +243,11 @@ extern void InternalFlutterGpu_RenderPass_SetStencilConfig( int write_mask, int target); +FLUTTER_GPU_EXPORT +extern void InternalFlutterGpu_RenderPass_SetCullMode( + flutter::gpu::RenderPass* wrapper, + int cull_mode); + FLUTTER_GPU_EXPORT extern bool InternalFlutterGpu_RenderPass_Draw( flutter::gpu::RenderPass* wrapper); diff --git a/engine/src/flutter/testing/dart/gpu_test.dart b/engine/src/flutter/testing/dart/gpu_test.dart index 8810cd3063..b3099cf89f 100644 --- a/engine/src/flutter/testing/dart/gpu_test.dart +++ b/engine/src/flutter/testing/dart/gpu_test.dart @@ -11,6 +11,7 @@ import 'dart:typed_data'; import 'dart:ui' as ui; import 'package:test/test.dart'; +import 'package:vector_math/vector_math.dart'; import '../../lib/gpu/lib/gpu.dart' as gpu; @@ -21,6 +22,16 @@ ByteData float32(List values) { return Float32List.fromList(values).buffer.asByteData(); } +ByteData unlitUBO(Matrix4 mvp, Vector4 color) { + return float32([ + mvp[0], mvp[1], mvp[2], mvp[3], // + mvp[4], mvp[5], mvp[6], mvp[7], // + mvp[8], mvp[9], mvp[10], mvp[11], // + mvp[12], mvp[13], mvp[14], mvp[15], // + color.r, color.g, color.b, color.a, + ]); +} + gpu.RenderPipeline createUnlitRenderPipeline() { final gpu.ShaderLibrary? library = gpu.ShaderLibrary.fromAsset('test.shaderbundle'); @@ -425,4 +436,47 @@ void main() async { await comparer.addGoldenImage( image, 'flutter_gpu_test_triangle_stencil.png'); }, skip: !impellerEnabled); + + test('Drawing respects cull mode', () async { + final state = createSimpleRenderPass(); + + final gpu.RenderPipeline pipeline = createUnlitRenderPipeline(); + state.renderPass.bindPipeline(pipeline); + + state.renderPass.setColorBlendEnable(true); + state.renderPass.setColorBlendEquation(gpu.ColorBlendEquation()); + + final gpu.HostBuffer transients = gpu.gpuContext.createHostBuffer(); + // Counter-clockwise triangle. + final List triangle = [ + -0.5, 0.5, // + 0.0, -0.5, // + 0.5, 0.5, // + ]; + final gpu.BufferView vertices = transients.emplace(float32(triangle)); + + void drawTriangle(Vector4 color) { + final gpu.BufferView vertInfoUboFront = + transients.emplace(unlitUBO(Matrix4.identity(), color)); + + final gpu.UniformSlot vertInfo = + pipeline.vertexShader.getUniformSlot('VertInfo'); + // 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(); + state.renderPass.bindVertexBuffer(vertices, 3); + state.renderPass.bindUniform(vertInfo, vertInfoUboFront); + state.renderPass.draw(); + } + + state.renderPass.setCullMode(gpu.CullMode.frontFace); + drawTriangle(Colors.lime); + state.renderPass.setCullMode(gpu.CullMode.backFace); + drawTriangle(Colors.red); + + state.commandBuffer.submit(); + + final ui.Image image = state.renderTexture.asImage(); + await comparer.addGoldenImage(image, 'flutter_gpu_test_cull_mode.png'); + }, skip: !impellerEnabled); }