When a Flutter GPU RenderPass.setViewport is called with a non-zero x (or y) offset, the Vulkan backend renders as if x = 0 and y = 0. The width and height are honored, but the X and Y offsets are silently dropped.
The bug was originally observed via the golden diff for the Can render portion of the triangle using viewport test in #185879, which I incorrectly attributed to the OpenGLES backend on first inspection. Investigation shows that the OpenGLES (and Metal) backends are correct, and the Vulkan backend is the one dropping the offsets.
Root cause
engine/src/flutter/impeller/renderer/backend/vulkan/render_pass_vk.cc:389-397:
void RenderPassVK::SetViewport(Viewport viewport) {
vk::Viewport viewport_vk = vk::Viewport()
.setWidth(viewport.rect.GetWidth())
.setHeight(-viewport.rect.GetHeight())
.setY(viewport.rect.GetHeight())
.setMinDepth(0.0f)
.setMaxDepth(1.0f);
command_buffer_vk_.setViewport(0, 1, &viewport_vk);
}
The chained-setter call never invokes .setX(viewport.rect.GetX()). vk::Viewport() default-initializes x to 0.0f, so the user's X is silently dropped. Y is also effectively dropped: the code calls setY(viewport.rect.GetHeight()) for the negative-height Y-flip, never reading viewport.rect.GetY().
This has been present since e7be989feb1c ("Reland: Encode directly to command buffer.", Jonah Williams, 2024-01-19).
Repro
The Can render portion of the triangle using viewport test in engine/src/flutter/testing/dart/gpu_test.dart exercises this:
final RenderPassState state = createSimpleRenderPass(); // 100x100 render texture
state.renderPass.setViewport(gpu.Viewport(x: 25, width: 50, height: 100));
// ... draw a triangle with NDC vertices (-1,-1), (0,1), (1,-1)
The triangle should fill the middle 50px column of the texture (apex at x=50, base from x=25 to x=75). On OpenGLES and Metal, that is what renders. On Vulkan the triangle renders at x=0..50, shifted left by exactly the requested 25px X offset.
The existing Vulkan golden in Skia Gold for flutter_gpu_test_viewport.png (hash ab61456e8267e29b58d9101be5692480) shows the buggy left-shifted output. The OpenGLES output (hash 80e8823fc0e56785a5c29672186ac9b0) is the correct centered render.
How this stayed hidden
Fix
Add the two missing setters in RenderPassVK::SetViewport:
.setX(viewport.rect.GetX())
.setY(viewport.rect.GetY() + viewport.rect.GetHeight()) // for the negative-height Y-flip
When a Flutter GPU
RenderPass.setViewportis called with a non-zerox(ory) offset, the Vulkan backend renders as ifx = 0andy = 0. The width and height are honored, but the X and Y offsets are silently dropped.The bug was originally observed via the golden diff for the
Can render portion of the triangle using viewporttest in #185879, which I incorrectly attributed to the OpenGLES backend on first inspection. Investigation shows that the OpenGLES (and Metal) backends are correct, and the Vulkan backend is the one dropping the offsets.Root cause
engine/src/flutter/impeller/renderer/backend/vulkan/render_pass_vk.cc:389-397:The chained-setter call never invokes
.setX(viewport.rect.GetX()).vk::Viewport()default-initializesxto0.0f, so the user's X is silently dropped. Y is also effectively dropped: the code callssetY(viewport.rect.GetHeight())for the negative-height Y-flip, never readingviewport.rect.GetY().This has been present since
e7be989feb1c("Reland: Encode directly to command buffer.", Jonah Williams, 2024-01-19).Repro
The
Can render portion of the triangle using viewporttest inengine/src/flutter/testing/dart/gpu_test.dartexercises this:The triangle should fill the middle 50px column of the texture (apex at x=50, base from x=25 to x=75). On OpenGLES and Metal, that is what renders. On Vulkan the triangle renders at x=0..50, shifted left by exactly the requested 25px X offset.
The existing Vulkan golden in Skia Gold for
flutter_gpu_test_viewport.png(hashab61456e8267e29b58d9101be5692480) shows the buggy left-shifted output. The OpenGLES output (hash80e8823fc0e56785a5c29672186ac9b0) is the correct centered render.How this stayed hidden
xoryinSetViewport.float_typeregression. Re-enabling it on OpenGLES (in [Flutter GPU] Carry vec_size & columns in shader bundle uniform metadata #185879) exposes this Vulkan bug, because the new (correct) OpenGLES rendering doesn't match the (buggy) Vulkan reference.Fix
Add the two missing setters in
RenderPassVK::SetViewport:.setX(viewport.rect.GetX()) .setY(viewport.rect.GetY() + viewport.rect.GetHeight()) // for the negative-height Y-flip