Skip to content

[Flutter GPU] DeviceBuffer.flush() with default arguments flushes a range of SIZE_MAX bytes #194152

Description

@dzolotov

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;
}
  1. Create a host-visible DeviceBuffer, write vertices into it with overwrite, and call flush() with no arguments.
  2. Bind it as a vertex buffer and draw a triangle with any unlit pipeline.
  3. 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::Flush stores {offset, SIZE_MAX} as the dirty range, and the next bind calls glBufferSubData(target, offset, SIZE_MAX, ...). SIZE_MAX as a GLsizeiptr is -1, so GL returns GL_INVALID_VALUE and uploads nothing. A debug engine aborts on the GL error check.
  • On Metal (macOS, managed storage), didModifyRange: gets NSMakeRange(offset, SIZE_MAX), a range that runs past the end of the buffer.
  • On Vulkan, vmaFlushAllocation(..., offset, SIZE_MAX) happens to work, because SIZE_MAX equals VK_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 glBufferSubData

Suggested 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)

Activity

  1. suryaKommana2662 commented on Oct 12, 2026

    @suryaKommana2662

    @dzolotov Thanks for the report. 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