Steps to reproduce
Using Flutter GPU (package:flutter_gpu):
- Create a
RenderPass with a depth attachment.
- Call
renderPass.setDepthWriteEnable(false).
- Draw two or more overlapping translucent primitives that are not depth-sorted against each other, for example a single instanced draw of overlapping camera-facing quads (additive billboards), with depth compare set to
lessEqual.
- Observe the blended result.
This was found while building an instanced particle/billboard renderer on top of Flutter GPU (the flutter_scene package). The root cause is in engine/src/flutter/lib/gpu/render_pass.cc: the native implementation of setDepthWriteEnable ignores its enable argument and always enables depth writes (see Code sample). So setDepthWriteEnable(false) is a no-op and depth writes stay on for every draw.
The bug is masked for ordinary translucent geometry that is drawn back-to-front, because each successive (nearer) draw passes the lessEqual test and the stuck-on depth write is harmless there. It becomes visible when overlapping translucent fragments are not in front-to-back order within the depth test, for example multiple overlapping instances in one instanced draw, which then self-occlude in draw order.
Expected results
setDepthWriteEnable(false) disables depth writes for subsequent draws. Overlapping translucent fragments do not occlude one another via the depth buffer, so unsorted overlapping instances (additive billboards) blend smoothly while still being occluded by previously drawn opaque geometry via the depth test.
Actual results
Depth writes remain enabled regardless of the argument. Overlapping instances in a single instanced translucent draw self-occlude in instance order, producing a stepped/creased blend instead of a smooth additive sum. Setting the depth compare to always (no depth test) hides the artifact, confirming it is depth driven.
Code sample
The native binding ignores its enable parameter (engine/src/flutter/lib/gpu/render_pass.cc, around line 560):
void InternalFlutterGpu_RenderPass_SetDepthWriteEnable(
flutter::gpu::RenderPass* wrapper,
bool enable) {
auto& depth = wrapper->GetDepthAttachmentDescriptor();
depth.depth_write_enabled = true; // ignores `enable`
}
It should honor the argument:
depth.depth_write_enabled = enable;
Dart side that exercises it:
final commandBuffer = gpu.gpuContext.createCommandBuffer();
final renderPass = commandBuffer.createRenderPass(renderTarget);
renderPass.bindPipeline(pipeline);
renderPass.setDepthCompareOperation(gpu.CompareFunction.lessEqual);
renderPass.setDepthWriteEnable(false); // no effect: depth writes stay enabled
// ... bind vertex/index/uniforms ...
renderPass.drawIndexed(indexCount, instanceCount: n); // overlapping instances self-occlude
Verified locally: changing the line to depth.depth_write_enabled = enable; and rebuilding the engine makes the overlapping instanced translucent draw blend correctly while opaque geometry still occludes the sprites. I am happy to send the one-line PR.
Flutter Doctor output
flutter doctor -v
[✓] Flutter (Channel master, 3.46.0-1.0.pre-322, on macOS 15.5 24F74 darwin-arm64, locale en-US)
• Flutter version 3.46.0-1.0.pre-322 on channel master at /Users/bdero/projects/flutter/flutter
• Upstream repository [email protected]:flutter/flutter.git
• Framework revision 47215701bc (2 days ago), 2026-06-27 02:19:27 -0400
• Engine revision 47215701bc
• Dart version 3.13.0 (build 3.13.0-254.0.dev)
• DevTools version 2.59.0
[✓] Xcode - develop for iOS and macOS (Xcode 16.4)
• Xcode at /Applications/Xcode.app/Contents/Developer
• Build 16F6
• CocoaPods version 1.16.2
[✓] Connected device (3 available)
• macOS (desktop) • macos • darwin-arm64 • macOS 15.5 24F74 darwin-arm64
[✓] Network resources
• All expected network resources are available.
Reproduced on the Metal backend (macOS). The offending line is present on flutter/flutter master.
Steps to reproduce
Using Flutter GPU (
package:flutter_gpu):RenderPasswith a depth attachment.renderPass.setDepthWriteEnable(false).lessEqual.This was found while building an instanced particle/billboard renderer on top of Flutter GPU (the flutter_scene package). The root cause is in
engine/src/flutter/lib/gpu/render_pass.cc: the native implementation ofsetDepthWriteEnableignores itsenableargument and always enables depth writes (see Code sample). SosetDepthWriteEnable(false)is a no-op and depth writes stay on for every draw.The bug is masked for ordinary translucent geometry that is drawn back-to-front, because each successive (nearer) draw passes the
lessEqualtest and the stuck-on depth write is harmless there. It becomes visible when overlapping translucent fragments are not in front-to-back order within the depth test, for example multiple overlapping instances in one instanced draw, which then self-occlude in draw order.Expected results
setDepthWriteEnable(false)disables depth writes for subsequent draws. Overlapping translucent fragments do not occlude one another via the depth buffer, so unsorted overlapping instances (additive billboards) blend smoothly while still being occluded by previously drawn opaque geometry via the depth test.Actual results
Depth writes remain enabled regardless of the argument. Overlapping instances in a single instanced translucent draw self-occlude in instance order, producing a stepped/creased blend instead of a smooth additive sum. Setting the depth compare to
always(no depth test) hides the artifact, confirming it is depth driven.Code sample
The native binding ignores its
enableparameter (engine/src/flutter/lib/gpu/render_pass.cc, around line 560):It should honor the argument:
Dart side that exercises it:
Verified locally: changing the line to
depth.depth_write_enabled = enable;and rebuilding the engine makes the overlapping instanced translucent draw blend correctly while opaque geometry still occludes the sprites. I am happy to send the one-line PR.Flutter Doctor output
flutter doctor -v
Reproduced on the Metal backend (macOS). The offending line is present on
flutter/fluttermaster.