flutter / flutter/flutter

HostBuffer.emplace overflows DeviceBuffer near block end (off-by-one in size check)

Open
#186,526 1 comment 0 reactions 0 assignees View on GitHub
engine flutter-gpu team-fluttergpu
Dominant language
Dart
Stars
179k
Forks
31.1k
PR merge metrics
PR metrics pending

Description

### Steps to reproduce

1. In a `flame_3d` ^0.2.0 app, render a 3D scene with enough draw calls and uniform writes per frame that the per-frame `HostBuffer` cursor approaches the 1 MB block boundary (e.g. ~330+ surfaces using a material with a `Lights` uniform of 784 bytes, plus VertexInfo, JointMatrices, Material, AmbientLight, Camera blocks).
2. Rotate the camera to a heading at which the cursor lands inside the "danger window" `[blockLengthInBytes - bytes.lengthInBytes, blockLengthInBytes)`.

### Expected results

The HostBuffer allocates a new 1 MB block when the upcoming write would overflow the current block.

### Actual results

`DeviceBuffer.overwrite` fails because the write extends past `blockLengthInBytes`, producing dark screen and throwing error in console:
```
══╡ EXCEPTION CAUGHT BY RENDERING LIBRARY ╞═════════════════════════════════════════════════════════
The following _Exception was thrown during paint():
Exception: Failed to write range (offset=1023488, length=784) to HostBuffer-managed DeviceBuffer
(frame=0, buffer=0, offset=1024272).

The relevant error-causing widget was:
RenderGameWidget
RenderGameWidget:file:////.pub-cache/hosted/pub.dev/flame-1.37.0/lib/src/game/game_widget/game_widget.dart:356:36

When the exception was thrown, this was the stack:
#0 HostBuffer.emplace (package:flutter_gpu/src/buffer.dart:306:7)
#1 GraphicsDevice.bindUniform (package:flame_3d/src/graphics/graphics_device.dart:174:47)
#2 Shader.bind (package:flame_3d/src/resources/shader/shader.dart:105:18)
#3 GraphicsDevice.bindMaterial (package:flame_3d/src/graphics/graphics_device.dart:169:29)
#4 GraphicsDevice.bindSurface (package:flame_3d/src/graphics/graphics_device.dart:137:5)
#5 Mesh.bind (package:flame_3d/src/resources/mesh/mesh.dart:29:14)
#6 GraphicsDevice.bindMesh (package:flame_3d/src/graphics/graphics_device.dart:131:10)
#7 MeshComponent.bind (package:flame_3d/src/components/mesh_component.dart:34:9)
#8 Object3D.renderTree (package:flame_3d/src/components/object_3d.dart:74:7)
#9 Component.renderChild (package:flame/src/components/core/component.dart:596:11)
#10 Component.renderTree (package:flame/src/components/core/component.dart:622:9)
#11 World.renderFromCamera (package:flame/src/camera/world.dart:33:11)
#12 World3D.renderFromCamera (package:flame_3d/src/camera/world_3d.dart:64:11)
#13 CameraComponent.renderTree.renderWorld (package:flame/src/camera/camera_component.dart:208:18)
#14 CameraComponent.renderTree (package:flame/src/camera/camera_component.dart:232:11)
#15 FlameGame.renderTree (package:flame/src/game/flame_game.dart:168:17)
#16 FlameGame.render (package:flame/src/game/flame_game.dart:158:7)
#17 GameRenderBox.paint (package:flame/src/game/game_render_box.dart:149:10)
#18 RenderObject._paintWithContext (package:flutter/src/rendering/object.dart:3429:7)
#19 PaintingContext._repaintCompositedChild (package:flutter/src/rendering/object.dart:180:11)
#20 PaintingContext.repaintCompositedChild (package:flutter/src/rendering/object.dart:125:5)
#21 PipelineOwner.flushPaint (package:flutter/src/rendering/object.dart:1325:31)
#22 PipelineOwner.flushPaint (package:flutter/src/rendering/object.dart:1335:15)
#23 RendererBinding.drawFrame (package:flutter/src/rendering/binding.dart:631:23)
#24 WidgetsBinding.drawFrame (package:flutter/src/widgets/binding.dart:1304:13)
#25 RendererBinding._handlePersistentFrameCallback (package:flutter/src/rendering/binding.dart:495:5)
#26 SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:1430:15)
#27 SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:1345:9)
#28 SchedulerBinding._handleDrawFrame (package:flutter/src/scheduler/binding.dart:1198:5)
#29 _invoke (dart:ui/hooks.dart:356:13)
#30 PlatformDispatcher._drawFrame (dart:ui/platform_dispatcher.dart:444:5)
#31 _drawFrame (dart:ui/hooks.dart:328:31)

The following RenderObject was being processed when the exception was fired: GameRenderBox#6b7e1:
needs compositing
creator: RenderGameWidget ← Listener ← _GestureSemantics ← RawGestureDetector ← MouseRegion ←
Listener ← Stack ← FutureBuilder ← LayoutBuilder ← DecoratedBox ← Directionality ←
MouseRegion ← ⋯
parentData: (can use size)
constraints: BoxConstraints(0.0<=w<=1614.0, 0.0<=h<=977.0)
layer: OffsetLayer#118cf
size: Size(1614.0, 977.0)
This RenderObject has no descendants.
```

### Code sample

Code sample

```dart
import 'dart:typed_data';

import 'package:flutter/material.dart';
import 'package:flutter_gpu/gpu.dart';

void main() {
WidgetsFlutterBinding.ensureInitialized();

print(
'minimumUniformByteAlignment = '
'${gpuContext.minimumUniformByteAlignment} bytes',
);
print(
'blockLengthInBytes (default) = '
'${HostBuffer.kDefaultBlockLengthInBytes} bytes',
);

final hostBuffer = gpuContext.createHostBuffer();

// Step 1: fill the cursor to (blockLength - 256) in a single emplace.
// 1023744 is a multiple of all common alignments (16/32/64/128/256), so
// the next emplace's padding will be 0 regardless of platform.
print('Step 1: emplace(ByteData(1023744)) → cursor → 1023744');
hostBuffer.emplace(ByteData(1023744));

// Step 2: emplace 728 bytes. The buggy bounds check passes (1023744 <
// 1024000), but the write extends to 1024472, overflowing the 1024000-byte
// block. DeviceBuffer.overwrite returns false and HostBuffer.emplace
// throws.
print('Step 2: emplace(ByteData(728)) — throws...');
hostBuffer.emplace(ByteData(728));

// Run a minimal app so the engine stays alive long enough for the print
// output to flush. Tap the screen to exit.
runApp(const _BugApp());
}

class _BugApp extends StatelessWidget {
const _BugApp();

@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: Text(
'HostBuffer.emplace overflow reproduction.\n'
'See console output.\n\n'
'Expected exception:\n'
' Failed to write range (offset=1023744, length=728) to\n'
' HostBuffer-managed DeviceBuffer\n'
' (frame=0, buffer=0, offset=1024472).',
textAlign: TextAlign.center,
),
),
),
);
}
}

```

### Screenshots or Video

Screenshots / Video demonstration

https://youtu.be/xWQsWn2huNg

### Logs

Logs

```console
[Paste your logs here]
```

### Flutter Doctor output

Doctor output

```console
Doctor summary (to see all details, run flutter doctor -v):
[!] Flutter (Channel stable, 3.41.9, on macOS 15.7.5 24G624 darwin-arm64, locale en-NL)
! Warning: `dart` on your path resolves to /opt/homebrew/Cellar/dart/3.11.0/libexec/bin/dart, which is not inside your current Flutter SDK checkout at /Users/savinmax/flutter. Consider adding /Users/savinmax/flutter/bin to the front of your path.
[✗] Android toolchain - develop for Android devices
✗ Unable to locate Android SDK.
Install Android Studio from: https://developer.android.com/studio/index.html
On first launch it will assist you in installing the Android SDK components.
(or visit https://flutter.dev/to/macos-android-setup for detailed instructions).
If the Android SDK has been installed to a custom location, please use
`flutter config --android-sdk` to update to that location.

[✓] Xcode - develop for iOS and macOS (Xcode 16.2)
[✓] Chrome - develop for the web
[✓] Connected device (2 available)
[✓] Network resources

! Doctor found issues in 2 categories.
```

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.