diff --git a/lib/gpu/lib/src/render_pass.dart b/lib/gpu/lib/src/render_pass.dart index 6dd60ab548a85..92e498eb55346 100644 --- a/lib/gpu/lib/src/render_pass.dart +++ b/lib/gpu/lib/src/render_pass.dart @@ -162,6 +162,27 @@ 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 +367,14 @@ base class RenderPass extends NativeFieldWrapperClass1 { _setScissor(scissor.x, scissor.y, scissor.width, scissor.height); } + 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 setCullMode(CullMode cullMode) { _setCullMode(cullMode.index); } @@ -506,6 +535,16 @@ base class RenderPass extends NativeFieldWrapperClass1 { int width, int height); + @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, Int)>( symbol: 'InternalFlutterGpu_RenderPass_SetCullMode') external void _setCullMode(int cullMode); diff --git a/lib/gpu/render_pass.cc b/lib/gpu/render_pass.cc index 3c00d4039ed10..71b8659bd25de 100644 --- a/lib/gpu/render_pass.cc +++ b/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/lib/gpu/render_pass.h b/lib/gpu/render_pass.h index 5c540f761817b..5fe4ac04a43bf 100644 --- a/lib/gpu/render_pass.h +++ b/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. @@ -250,6 +251,17 @@ extern void InternalFlutterGpu_RenderPass_SetScissor( 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, int cull_mode); diff --git a/testing/dart/gpu_test.dart b/testing/dart/gpu_test.dart index 1317a5fbb4f6e..f8df73c9e5afd 100644 --- a/testing/dart/gpu_test.dart +++ b/testing/dart/gpu_test.dart @@ -810,4 +810,79 @@ void main() async { 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); }