Repository navigation
[Flutter GPU] DeviceBuffer.flush() with default arguments flushes a range of SIZE_MAX bytes #194152
Description
Steps to reproduce
DeviceBuffer.flush (lib/gpu/lib/src/buffer.dart) documents that a lengthInBytes of -1, the default, flushes the entire buffer:
void flush({int offsetInBytes = 0, int lengthInBytes = -1}) {
...
_flush(offsetInBytes, lengthInBytes);
}The native side (lib/gpu/device_buffer.cc) passes the value straight into an impeller::Range, whose fields are size_t:
bool InternalFlutterGpu_DeviceBuffer_Flush(
flutter::gpu::DeviceBuffer* device_buffer,
int offset_in_bytes,
int size_in_bytes) {
device_buffer->GetBuffer()->Flush(
impeller::Range(offset_in_bytes, size_in_bytes));
return true;
}- Create a host-visible
DeviceBuffer, write vertices into it withoverwrite, and callflush()with no arguments. - Bind it as a vertex buffer and draw a triangle with any unlit pipeline.
- Run with Impeller on OpenGL ES.
Expected results
The whole buffer is flushed and the triangle is drawn, on every backend.
Actual results
flush() becomes Flush(Range{0, SIZE_MAX}), and each backend does something different with it:
- On OpenGL ES,
DeviceBufferGLES::Flushstores{offset, SIZE_MAX}as the dirty range, and the next bind callsglBufferSubData(target, offset, SIZE_MAX, ...).SIZE_MAXas aGLsizeiptris -1, so GL returnsGL_INVALID_VALUEand uploads nothing. A debug engine aborts on the GL error check. - On Metal (macOS, managed storage),
didModifyRange:getsNSMakeRange(offset, SIZE_MAX), a range that runs past the end of the buffer. - On Vulkan,
vmaFlushAllocation(..., offset, SIZE_MAX)happens to work, becauseSIZE_MAXequalsVK_WHOLE_SIZE.
I reproduced the abort with flutter_tester_opengles on macOS; Metal and Vulkan (SwiftShader) draw the triangle. Android runs the same DeviceBufferGLES path, but I haven't run it on a device, and I haven't run a release engine, where the upload would be skipped without the abort.
Program output (flutter_tester_opengles, debug engine)
[FATAL:flutter/impeller/renderer/backend/gles/proc_table_gles.h(44)]
Fatal GL Error GL_INVALID_VALUE(1281) encountered on call to glBufferSubDataSuggested fix
Normalize -1 in one place. On the native side, treat a negative length as "to the end of the buffer", and reject offsets and lengths outside the buffer (with the same offset bound as the Dart checks) instead of building an oversized Range. That also covers callers that reach the native entry point without going through the Dart checks. The native call already returns a bool, but Dart declares it as returning void, so a refusal is lost; it should be Bool, and flush should throw on false, as overwrite does.
Code sample
The relevant part
final ByteData vertices = Float32List.fromList(
<double>[-0.5, 0.5, 0.0, -0.5, 0.5, 0.5]).buffer.asByteData();
final gpu.DeviceBuffer buffer = gpu.gpuContext.createDeviceBuffer(
gpu.StorageMode.hostVisible, vertices.lengthInBytes);
buffer.overwrite(vertices);
buffer.flush(); // defaults: offsetInBytes 0, lengthInBytes -1
// Bind as a vertex buffer and draw a triangle with any unlit pipeline,
// then read the render target back.
renderPass.bindVertexBuffer(
gpu.BufferView(buffer, offsetInBytes: 0, lengthInBytes: vertices.lengthInBytes));
renderPass.draw(3);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. device_buffer.cc and DeviceBuffer.flush are the same on current main.
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)Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fields🤔 Needs Triage
Activity
suryaKommana2662 commentedon Oct 12, 2026
@dzolotov Thanks for the report. routing this to
team-fluttergpufor further investigation.