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 15389579b7..6dd60ab548 100644 --- a/engine/src/flutter/lib/gpu/lib/src/render_pass.dart +++ b/engine/src/flutter/lib/gpu/lib/src/render_pass.dart @@ -150,6 +150,18 @@ base class SamplerOptions { SamplerAddressMode heightAddressMode; } +base class Scissor { + Scissor({this.x = 0, this.y = 0, this.width = 0, this.height = 0}); + + int x, y, width, height; + + void _validate() { + if (x < 0 || y < 0 || width < 0 || height < 0) { + throw Exception("Invalid values for scissor. All values should be positive."); + } + } +} + base class RenderTarget { const RenderTarget( {this.colorAttachments = const [], @@ -326,6 +338,14 @@ base class RenderPass extends NativeFieldWrapperClass1 { targetFace.index); } + void setScissor(Scissor scissor) { + assert(() { + scissor._validate(); + return true; + }()); + _setScissor(scissor.x, scissor.y, scissor.width, scissor.height); + } + void setCullMode(CullMode cullMode) { _setCullMode(cullMode.index); } @@ -478,6 +498,14 @@ base class RenderPass extends NativeFieldWrapperClass1 { int writeMask, int target_face); + @Native, Int, Int, Int, Int)>( + symbol: 'InternalFlutterGpu_RenderPass_SetScissor') + external void _setScissor( + int x, + int y, + int width, + int height); + @Native, Int)>( symbol: 'InternalFlutterGpu_RenderPass_SetCullMode') external void _setCullMode(int cullMode); diff --git a/engine/src/flutter/lib/gpu/render_pass.cc b/engine/src/flutter/lib/gpu/render_pass.cc index 637f7446e5..3987d823f5 100644 --- a/engine/src/flutter/lib/gpu/render_pass.cc +++ b/engine/src/flutter/lib/gpu/render_pass.cc @@ -207,6 +207,10 @@ bool RenderPass::Draw() { render_pass_->SetStencilReference(stencil_reference); + if (scissor.has_value()) { + render_pass_->SetScissor(scissor.value()); + } + bool result = render_pass_->Draw().ok(); return result; @@ -536,6 +540,14 @@ void InternalFlutterGpu_RenderPass_SetStencilReference( wrapper->stencil_reference = static_cast(stencil_reference); } +void InternalFlutterGpu_RenderPass_SetScissor(flutter::gpu::RenderPass* wrapper, + int x, + int y, + int width, + int height) { + wrapper->scissor = impeller::TRect::MakeXYWH(x, y, width, height); +} + void InternalFlutterGpu_RenderPass_SetStencilConfig( flutter::gpu::RenderPass* wrapper, int stencil_compare_operation, diff --git a/engine/src/flutter/lib/gpu/render_pass.h b/engine/src/flutter/lib/gpu/render_pass.h index 1b33a5ebae..08dd2ee902 100644 --- a/engine/src/flutter/lib/gpu/render_pass.h +++ b/engine/src/flutter/lib/gpu/render_pass.h @@ -74,6 +74,7 @@ class RenderPass : public RefCountedDartWrappable { size_t element_count = 0; uint32_t stencil_reference = 0; + std::optional> scissor; // Helper flag to determine whether the vertex_count should override the // element count. The index count takes precedent. @@ -234,6 +235,14 @@ extern void InternalFlutterGpu_RenderPass_SetStencilConfig( int write_mask, int target); +FLUTTER_GPU_EXPORT +extern void InternalFlutterGpu_RenderPass_SetScissor( + flutter::gpu::RenderPass* wrapper, + int x, + int y, + int width, + int height); + FLUTTER_GPU_EXPORT extern void InternalFlutterGpu_RenderPass_SetCullMode( 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 c1d45183f9..1317a5fbb4 100644 --- a/engine/src/flutter/testing/dart/gpu_test.dart +++ b/engine/src/flutter/testing/dart/gpu_test.dart @@ -736,4 +736,78 @@ void main() async { await comparer.addGoldenImage( image, 'flutter_gpu_test_hexgon_line_strip.png'); }, skip: !impellerEnabled); + + // Renders the middle part triangle using scissor. + test('Can render portion of the triangle using scissor', () 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()); + + // Set primitive type. + state.renderPass.setPrimitiveType(gpu.PrimitiveType.triangle); + + // Set scissor. + state.renderPass.setScissor(gpu.Scissor(x: 25, width: 50, height: 100)); + + final gpu.HostBuffer transients = gpu.gpuContext.createHostBuffer(); + final gpu.BufferView vertices = transients.emplace(float32([ + -1.0, + -1.0, + 0.0, + 1.0, + 1.0, + -1.0])); + 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(); + + state.commandBuffer.submit(); + + final ui.Image image = state.renderTexture.asImage(); + await comparer.addGoldenImage( + image, 'flutter_gpu_test_scissor.png'); + }, skip: !impellerEnabled); + + test('RenderPass.setScissor doesnt throw for valid values', + () async { + final state = createSimpleRenderPass(); + + state.renderPass.setScissor(gpu.Scissor(x: 25, width: 50, height: 100)); + state.renderPass.setScissor(gpu.Scissor(width: 50, height: 100)); + }, skip: !impellerEnabled); + + test('RenderPass.setScissor throws for invalid values', () async { + final state = createSimpleRenderPass(); + + try { + state.renderPass.setScissor(gpu.Scissor(x: -1, width: 50, height: 100)); + fail('Exception not thrown for invalid scissor.'); + } catch (e) { + expect(e.toString(), + contains('Invalid values for scissor. All values should be positive.')); + } + + try { + state.renderPass.setScissor(gpu.Scissor(width: 50, height: -100)); + fail('Exception not thrown for invalid scissor.'); + } catch (e) { + expect(e.toString(), + contains('Invalid values for scissor. All values should be positive.')); + } + }, skip: !impellerEnabled); }