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 8ef52556d0..86a7fadbef 100644 --- a/engine/src/flutter/lib/gpu/lib/src/render_pass.dart +++ b/engine/src/flutter/lib/gpu/lib/src/render_pass.dart @@ -168,6 +168,34 @@ base class Scissor { } } +base class DepthRange { + DepthRange({this.zNear = 0.0, this.zFar = 1.0}); + + double zNear; + double zFar; +} + +base class Viewport { + Viewport({ + this.x = 0, + this.y = 0, + this.width = 0, + this.height = 0, + DepthRange? depthRange = null, + }) : this.depthRange = depthRange ?? DepthRange(); + + int x, y, width, height; + DepthRange depthRange; + + void _validate() { + if (x < 0 || y < 0 || width < 0 || height < 0) { + throw Exception( + "Invalid values for viewport. All values should be positive.", + ); + } + } +} + base class RenderTarget { const RenderTarget({ this.colorAttachments = const [], @@ -346,6 +374,21 @@ base class RenderPass extends NativeFieldWrapperClass1 { _setDepthWriteEnable(enable); } + void setViewport(Viewport viewport) { + assert(() { + viewport._validate(); + return true; + }()); + _setViewport( + viewport.x, + viewport.y, + viewport.width, + viewport.height, + viewport.depthRange.zNear, + viewport.depthRange.zFar, + ); + } + void setDepthCompareOperation(CompareFunction compareFunction) { _setDepthCompareOperation(compareFunction.index); } @@ -508,6 +551,18 @@ base class RenderPass extends NativeFieldWrapperClass1 { int lengthInBytes, ); + @Native, Int, Int, Int, Int, Float, Float)>( + symbol: 'InternalFlutterGpu_RenderPass_SetViewport', + ) + external void _setViewport( + int x, + int y, + int width, + int height, + double depthRangeZNear, + double depthRangeZFar, + ); + @Native< Bool Function( Pointer, diff --git a/engine/src/flutter/lib/gpu/render_pass.cc b/engine/src/flutter/lib/gpu/render_pass.cc index 3c00d4039e..71b8659bd2 100644 --- a/engine/src/flutter/lib/gpu/render_pass.cc +++ b/engine/src/flutter/lib/gpu/render_pass.cc @@ -218,6 +218,10 @@ bool RenderPass::Draw() { render_pass_->SetStencilReference(stencil_reference); + if (viewport.has_value()) { + render_pass_->SetViewport(viewport.value()); + } + if (scissor.has_value()) { render_pass_->SetScissor(scissor.value()); } @@ -559,6 +563,27 @@ void InternalFlutterGpu_RenderPass_SetScissor(flutter::gpu::RenderPass* wrapper, wrapper->scissor = impeller::TRect::MakeXYWH(x, y, width, height); } +void InternalFlutterGpu_RenderPass_SetViewport( + flutter::gpu::RenderPass* wrapper, + int x, + int y, + int width, + int height, + float z_near, + float z_far) { + auto rect = impeller::TRect::MakeXYWH(x, y, width, height); + + auto depth_range = impeller::DepthRange(); + depth_range.z_near = z_near; + depth_range.z_far = z_far; + + auto viewport = impeller::Viewport(); + viewport.rect = rect; + viewport.depth_range = depth_range; + + wrapper->viewport = viewport; +} + 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 5c540f7618..0873841930 100644 --- a/engine/src/flutter/lib/gpu/render_pass.h +++ b/engine/src/flutter/lib/gpu/render_pass.h @@ -81,6 +81,7 @@ class RenderPass : public RefCountedDartWrappable { uint32_t stencil_reference = 0; std::optional> scissor; + std::optional viewport; // Helper flag to determine whether the vertex_count should override the // element count. The index count takes precedent. @@ -249,6 +250,16 @@ extern void InternalFlutterGpu_RenderPass_SetScissor( int width, int height); +FLUTTER_GPU_EXPORT +extern void InternalFlutterGpu_RenderPass_SetViewport( + flutter::gpu::RenderPass* wrapper, + int x, + int y, + int width, + int height, + float z_near, + float z_far); + 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 41af26798a..4e0b007800 100644 --- a/engine/src/flutter/testing/dart/gpu_test.dart +++ b/engine/src/flutter/testing/dart/gpu_test.dart @@ -810,4 +810,71 @@ void main() async { expect(e.toString(), contains('Invalid values for scissor. All values should be positive.')); } }, skip: !impellerEnabled); + + test('RenderPass.setViewport doesnt throw for valid values', () async { + final state = createSimpleRenderPass(); + + state.renderPass.setViewport(gpu.Viewport(x: 25, width: 50, height: 100)); + state.renderPass.setViewport(gpu.Viewport(width: 50, height: 100)); + }, skip: !impellerEnabled); + + test('RenderPass.setViewport throws for invalid values', () async { + final state = createSimpleRenderPass(); + + try { + state.renderPass.setViewport(gpu.Viewport(x: -1, width: 50, height: 100)); + fail('Exception not thrown for invalid viewport.'); + } catch (e) { + expect(e.toString(), contains('Invalid values for viewport. All values should be positive.')); + } + + try { + state.renderPass.setViewport(gpu.Viewport(width: 50, height: -100)); + fail('Exception not thrown for invalid viewport.'); + } catch (e) { + expect(e.toString(), contains('Invalid values for viewport. All values should be positive.')); + } + }, skip: !impellerEnabled); + + // Renders the middle part triangle using viewport. + test('Can render portion of the triangle using viewport', () 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 viewport. + state.renderPass.setViewport(gpu.Viewport(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_viewport.png'); + }, skip: !impellerEnabled); }