Skip to content

[Flutter GPU] RenderPass accepts invalid attachments, buffer ranges and scissors without an error #194155

Description

@dzolotov

Steps to reproduce

Pass any of these to RenderPass (code sample below):

  1. An attachment texture created with enableRenderTargetUsage: false.
  2. Color and depth-stencil attachments with different sample counts.
  3. A color format (for example r8g8b8a8UNormInt) as the DepthStencilAttachment texture, or a depth-stencil format as a color attachment.
  4. mipLevel > 0 on a backend where GpuContext.doesSupportFramebufferRenderMipmap is false (OpenGL ES).
  5. bindVertexBuffer / bindIndexBuffer with a BufferView that ends past its buffer, or has a negative offset. (bindUniform already rejects this natively.)
  6. drawIndexed(indexCount) with more indices than the bound index buffer holds.
  7. setColorBlendEnable / setColorBlendEquation with a colorAttachmentIndex that has no attachment.
  8. setScissor with a rectangle larger than the render target.
  9. A RenderTarget with no attachments at all.

RenderTarget._validateAttachments (lib/gpu/lib/src/render_pass.dart) checks mip levels, slices and attachment sizes, and ColorAttachment._validate checks MSAA resolve setups, but nothing checks the cases above. On the engine side their checks are missing or only debug validation logs.

Expected results

Each call throws in Dart with a message that names the problem, in debug and release builds alike.

Actual results

Dart doesn't throw, and in a release build these are undefined behavior. What happens next depends on the backend: nothing visible, wrong rendering, a GL error, or an abort. drawIndexed with too many indices reads past the end of the index buffer. Blend state for a missing attachment makes the native side create a color descriptor for that index, so the pipeline is built with an attachment the render pass doesn't have. A render target with no attachments crashes Vulkan (SwiftShader) with SIGSEGV.

On top of that, the checks that do exist in ColorAttachment._validate and DepthStencilAttachment._validate (resolve texture format, size and sample count, transient load actions) only run inside an assert, so release builds skip them.

Program output (flutter_tester, Metal, MTL_DEBUG_LAYER=1)
_MTLDebugValidateRenderPassDescriptorAndTrackAttachments: failed assertion
`RenderPass Descriptor Validation
Texture at colorAttachment[0] has usage (0x01) which doesn't specify
MTLTextureUsageRenderTarget (0x04)'

-[MTLDebugRenderCommandEncoder setScissorRect:]: failed assertion
`Set Scissor Rect Validation
(rect.x(50) + rect.width(1000))(1050) must be <= render pass width(100)'

Suggested fix

Check these in Dart, where the checks also run in release builds: RenderTarget._validateAttachments rejects items 1 to 4 and 9, the buffer binds and drawIndexed throw a RangeError for out-of-range views and counts, the blend setters throw for a missing attachment index, and setScissor throws when the rectangle doesn't lie within the render target (a bug in the caller, so I'd rather report it than clamp it quietly). The existing attachment checks run unconditionally instead of inside an assert.

Related: #186768 is about passing native error detail back to Dart; these checks stop the call before native code. #193867 (pass lifecycle) may touch the same files. #192535 and #192536 cover the C++ Vulkan case of a render target with no color attachment; a depth-only target would still be allowed here.

Code sample

The relevant part
final gpu.Texture noRT = gpu.gpuContext.createTexture(
    gpu.StorageMode.devicePrivate, 16, 16, enableRenderTargetUsage: false);
commandBuffer.createRenderPass(
    gpu.RenderTarget.singleColor(gpu.ColorAttachment(texture: noRT))); // no error

final gpu.RenderPass pass = ...; // 100x100 target
pass.setScissor(gpu.Scissor(x: 50, y: 0, width: 1000, height: 1000)); // no error

final gpu.DeviceBuffer buffer =
    gpu.gpuContext.createDeviceBuffer(gpu.StorageMode.hostVisible, 64);
pass.bindVertexBuffer(
    gpu.BufferView(buffer, offsetInBytes: 32, lengthInBytes: 64)); // no error

pass.bindIndexBuffer(threeIndices, gpu.IndexType.int16);
pass.drawIndexed(4); // reads past the index buffer

pass.setColorBlendEnable(true, colorAttachmentIndex: 1); // single attachment

commandBuffer.createRenderPass(const gpu.RenderTarget()); // Vulkan: SIGSEGV

Flutter Doctor output

Found in an engine built from source: macOS arm64, host_debug_unopt_arm64, flutter_tester and flutter_tester_opengles, Vulkan through SwiftShader. render_pass.dart has changed around these methods on current main (#190400), but none of these checks exist there.

Doctor output
[✓] Flutter (Channel stable, 3.47.0, on macOS 27.2 26B5091g darwin-arm64)
    • Framework revision 4cf2416426, 2026-08-11
    • Engine revision 5f77625673
    • Dart version 3.13.0
[!] Xcode - develop for iOS and macOS (Xcode 27.0)

Activity

  1. added
    in triagePresently being triaged by the triage team
    c: fatal crashCrashes that terminate the process
    and removed
    in triagePresently being triaged by the triage team
    c: fatal crashCrashes that terminate the process
    on Oct 12, 2026
  2. puneetkukreja98 commented on Oct 12, 2026

    @puneetkukreja98
    Contributor

    @dzolotov Thanks for reporting this, routing this to team-fluttergpu for further investigation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    • Status
      🤔 Needs Triage

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions