From b2668ab3c31fea2a061b5f7dfd34cb551c21f4f1 Mon Sep 17 00:00:00 2001 From: Nick Fisher Date: Wed, 2 Sep 2026 00:17:40 +0800 Subject: [PATCH 1/2] fix: preserve typed data view bounds in FFI uploads --- thermion_dart/lib/src/bindings/src/ffi.dart | 14 +++--- thermion_dart/test/ffi_typed_data_test.dart | 54 +++++++++++++++++++++ 2 files changed, 61 insertions(+), 7 deletions(-) create mode 100644 thermion_dart/test/ffi_typed_data_test.dart diff --git a/thermion_dart/lib/src/bindings/src/ffi.dart b/thermion_dart/lib/src/bindings/src/ffi.dart index 11ede77d3..a1ac10a31 100644 --- a/thermion_dart/lib/src/bindings/src/ffi.dart +++ b/thermion_dart/lib/src/bindings/src/ffi.dart @@ -213,36 +213,36 @@ extension DartBigIntExtension on int { extension Float32ListExtension on Float32List { Uint8List asUint8List() { - return this.buffer.asUint8List(this.offsetInBytes); + return buffer.asUint8List(offsetInBytes, lengthInBytes); } } -extension Int16ListExtension on Int32List { +extension Int16ListExtension on Int16List { Uint8List asUint8List() { - return this.buffer.asUint8List(this.offsetInBytes); + return buffer.asUint8List(offsetInBytes, lengthInBytes); } } extension Int32ListExtension on Int32List { Uint8List asUint8List() { - return this.buffer.asUint8List(this.offsetInBytes); + return buffer.asUint8List(offsetInBytes, lengthInBytes); } } extension UInt16ListExtension on Uint16List { Uint8List asUint8List() { - return this.buffer.asUint8List(this.offsetInBytes); + return buffer.asUint8List(offsetInBytes, lengthInBytes); } } extension Uint32ListExtension on Uint32List { Uint8List asUint8List() { - return this.buffer.asUint8List(this.offsetInBytes); + return buffer.asUint8List(offsetInBytes, lengthInBytes); } } extension TypedDataListExtension on TypedData { Uint8List asUint8List() { - return this.buffer.asUint8List(this.offsetInBytes); + return buffer.asUint8List(offsetInBytes, lengthInBytes); } } diff --git a/thermion_dart/test/ffi_typed_data_test.dart b/thermion_dart/test/ffi_typed_data_test.dart new file mode 100644 index 000000000..ac153c0e8 --- /dev/null +++ b/thermion_dart/test/ffi_typed_data_test.dart @@ -0,0 +1,54 @@ +import 'package:test/test.dart'; +import 'package:thermion_dart/src/bindings/src/ffi.dart'; + +void main() { + group('TypedData asUint8List', () { + test('preserves Float32List view bounds', () { + final values = Float32List.fromList([1, 2, 3, 4]); + final view = Float32List.sublistView(values, 1, 3); + + _expectMatchingBytes(view, view.asUint8List()); + }); + + test('preserves Int16List view bounds', () { + final values = Int16List.fromList([1, 2, 3, 4]); + final view = Int16List.sublistView(values, 1, 3); + + _expectMatchingBytes(view, view.asUint8List()); + }); + + test('preserves Int32List view bounds', () { + final values = Int32List.fromList([1, 2, 3, 4]); + final view = Int32List.sublistView(values, 1, 3); + + _expectMatchingBytes(view, view.asUint8List()); + }); + + test('preserves Uint16List view bounds', () { + final values = Uint16List.fromList([1, 2, 3, 4]); + final view = Uint16List.sublistView(values, 1, 3); + + _expectMatchingBytes(view, view.asUint8List()); + }); + + test('preserves Uint32List view bounds', () { + final values = Uint32List.fromList([1, 2, 3, 4]); + final view = Uint32List.sublistView(values, 1, 3); + + _expectMatchingBytes(view, view.asUint8List()); + }); + + test('generic TypedData conversion preserves view bounds', () { + final values = Float64List.fromList([1, 2, 3, 4]); + final view = Float64List.sublistView(values, 1, 3); + + _expectMatchingBytes(view, view.asUint8List()); + }); + }); +} + +void _expectMatchingBytes(TypedData view, Uint8List actual) { + final expected = view.buffer.asUint8List(view.offsetInBytes, view.lengthInBytes); + expect(actual, orderedEquals(expected)); + expect(actual.lengthInBytes, view.lengthInBytes); +} From 802b7ed69b06b97ec5da992a7d46f94284216f3f Mon Sep 17 00:00:00 2001 From: Nick Fisher Date: Wed, 2 Sep 2026 00:23:49 +0800 Subject: [PATCH 2/2] fix: bound remaining typed buffer views --- .../materials_and_textures/lib/main.dart | 3 ++- examples/flutter/picking/lib/main.dart | 3 ++- .../src/implementation/ffi_filament_app.dart | 2 +- thermion_dart/test/depth_tests.dart | 6 +++++- thermion_dart/test/gltf_animation_tests.dart | 2 +- thermion_dart/test/projection_tests.dart | 18 +++++++++++++---- thermion_dart/test/texture_tests.dart | 4 ++-- .../test/ubershader_material_tests.dart | 4 ++-- .../view_dependent_texture_mapping_tests.dart | 20 ++++++++++++++++--- .../test/wireframe_renderable_test.dart | 8 ++++++-- .../src/thermion_flutter_plugin_native.dart | 2 +- .../src/thermion_flutter_plugin_web.dart | 2 +- 12 files changed, 54 insertions(+), 20 deletions(-) diff --git a/examples/flutter/materials_and_textures/lib/main.dart b/examples/flutter/materials_and_textures/lib/main.dart index 1add2c625..2e4c927ab 100644 --- a/examples/flutter/materials_and_textures/lib/main.dart +++ b/examples/flutter/materials_and_textures/lib/main.dart @@ -117,7 +117,8 @@ class _MyHomePageState extends State { var imageBuffer = await rootBundle.load("assets/background.png"); - var imageData = imageBuffer.buffer.asUint8List(imageBuffer.offsetInBytes); + var imageData = imageBuffer.buffer.asUint8List( + imageBuffer.offsetInBytes, imageBuffer.lengthInBytes); _image = await FilamentApp.instance!.decodeImage(imageData); diff --git a/examples/flutter/picking/lib/main.dart b/examples/flutter/picking/lib/main.dart index bf4507b07..70b2d9334 100644 --- a/examples/flutter/picking/lib/main.dart +++ b/examples/flutter/picking/lib/main.dart @@ -45,7 +45,8 @@ class _MyHomePageState extends State { _thermionViewer!.view.setTransparentPickingEnabled(true); // var matData = await rootBundle.load("assets/picking_index0.filamat"); // var mat = await FilamentApp.instance! - // .createMaterial(matData.buffer.asUint8List(matData.offsetInBytes)); + // .createMaterial(matData.buffer.asUint8List( + // matData.offsetInBytes, matData.lengthInBytes)); // var mi = await mat.createInstance(); // await mi.setParameterFloat4("baseColorFactor", 1, 0, 0, 1); // await mi.setParameterInt("baseColorIndex", -1); diff --git a/thermion_dart/lib/src/filament/src/implementation/ffi_filament_app.dart b/thermion_dart/lib/src/filament/src/implementation/ffi_filament_app.dart index 7a31cc966..0d7ad4f4b 100644 --- a/thermion_dart/lib/src/filament/src/implementation/ffi_filament_app.dart +++ b/thermion_dart/lib/src/filament/src/implementation/ffi_filament_app.dart @@ -1119,7 +1119,7 @@ class FFIFilamentApp extends FilamentApp { for (var j = 0; j < raw.length; j++) { floats[j] = raw[j] / 255.0; } - out = floats.buffer.asUint8List(); + out = floats.asUint8List(); } else { out = Uint8List.fromList(raw); } diff --git a/thermion_dart/test/depth_tests.dart b/thermion_dart/test/depth_tests.dart index f76dc0975..71adef8b8 100644 --- a/thermion_dart/test/depth_tests.dart +++ b/thermion_dart/test/depth_tests.dart @@ -123,8 +123,12 @@ void main() async { swapChain: swapChain, pixelDataFormat: PixelDataFormat.R, ); + final pixelBuffer = pixelBuffers[view]!; checkMinMaxPixelValues( - pixelBuffers[view]!.buffer.asFloat32List(), + pixelBuffer.buffer.asFloat32List( + pixelBuffer.offsetInBytes, + pixelBuffer.lengthInBytes ~/ Float32List.bytesPerElement, + ), viewportDimensions.width, viewportDimensions.height, ); diff --git a/thermion_dart/test/gltf_animation_tests.dart b/thermion_dart/test/gltf_animation_tests.dart index 7a3913374..29c32943e 100644 --- a/thermion_dart/test/gltf_animation_tests.dart +++ b/thermion_dart/test/gltf_animation_tests.dart @@ -317,5 +317,5 @@ Uint8List _buildMorphWeightAnimGlb() { out.setUint32(cursor, bin.length + binPad, Endian.little); out.setUint32(cursor + 4, 0x004E4942, Endian.little); // 'BIN\0' out.buffer.asUint8List(cursor + 8, bin.length).setAll(0, bin); - return out.buffer.asUint8List(); + return out.buffer.asUint8List(out.offsetInBytes, out.lengthInBytes); } diff --git a/thermion_dart/test/projection_tests.dart b/thermion_dart/test/projection_tests.dart index 36debfcb1..becd16d31 100644 --- a/thermion_dart/test/projection_tests.dart +++ b/thermion_dart/test/projection_tests.dart @@ -102,9 +102,13 @@ void main() async { await cube.setMaterialInstanceAt(ubershader); final data = await projectedImage.getData(); - data.setRange(0, data.length, projected.buffer.asFloat32List()); + final projectedFloats = projected.buffer.asFloat32List( + projected.offsetInBytes, + projected.lengthInBytes ~/ Float32List.bytesPerElement, + ); + data.setRange(0, data.length, projectedFloats); - images.add(projected.buffer.asFloat32List()); + images.add(projectedFloats); await projectedTexture.setLinearImage( projectedImage, @@ -179,8 +183,14 @@ void main() async { final data = await projectedImage.getData(); data.setRange(0, data.length, blendedImage); - await savePixelBufferToBmp(blendedImage.buffer.asUint8List(), width, - height, "${testHelper.outDirPath}/blended.bmp", + await savePixelBufferToBmp( + blendedImage.buffer.asUint8List( + blendedImage.offsetInBytes, + blendedImage.lengthInBytes, + ), + width, + height, + "${testHelper.outDirPath}/blended.bmp", hasAlpha: true, isFloat: true); // Update the texture with the blended image diff --git a/thermion_dart/test/texture_tests.dart b/thermion_dart/test/texture_tests.dart index ff39e4b85..9412a360b 100644 --- a/thermion_dart/test/texture_tests.dart +++ b/thermion_dart/test/texture_tests.dart @@ -245,7 +245,7 @@ void main() async { await texture.setImage( 0, - initialBuffer.buffer.asUint8List(), + initialBuffer.buffer.asUint8List(initialBuffer.offsetInBytes, initialBuffer.lengthInBytes), textureSize, textureSize, PixelDataFormat.RGBA, @@ -274,7 +274,7 @@ void main() async { // Apply the paint to a sub-region of the texture await texture.setImage( 0, - paintBuffer.buffer.asUint8List(), + paintBuffer.buffer.asUint8List(paintBuffer.offsetInBytes, paintBuffer.lengthInBytes), paintSize, paintSize, PixelDataFormat.RGBA, diff --git a/thermion_dart/test/ubershader_material_tests.dart b/thermion_dart/test/ubershader_material_tests.dart index 1f52ac165..3d849b0d1 100644 --- a/thermion_dart/test/ubershader_material_tests.dart +++ b/thermion_dart/test/ubershader_material_tests.dart @@ -84,7 +84,7 @@ void main() async { await texture.setImage( 0, - redF32.buffer.asUint8List(redF32.offsetInBytes), + redF32.buffer.asUint8List(redF32.offsetInBytes, redF32.lengthInBytes), 24, 24, // await red.getChannels(), @@ -93,7 +93,7 @@ void main() async { ); await texture.setImage( 1, - greenF32.buffer.asUint8List(greenF32.offsetInBytes), + greenF32.buffer.asUint8List(greenF32.offsetInBytes, greenF32.lengthInBytes), 12, 12, // await green.getChannels(), diff --git a/thermion_dart/test/view_dependent_texture_mapping_tests.dart b/thermion_dart/test/view_dependent_texture_mapping_tests.dart index ed8d39f78..32a982dcd 100644 --- a/thermion_dart/test/view_dependent_texture_mapping_tests.dart +++ b/thermion_dart/test/view_dependent_texture_mapping_tests.dart @@ -112,6 +112,7 @@ void main() async { ]); var byteBuffer = pixelBuffer.buffer.asUint8List( pixelBuffer.offsetInBytes, + pixelBuffer.lengthInBytes, ); await texture.setImage3D( 0, @@ -173,7 +174,11 @@ void main() async { await cube.setMaterialInstanceAt(vdtmMi); - final pixelData = (await image.getData()).buffer.asUint8List(); + final imageData = await image.getData(); + final pixelData = imageData.buffer.asUint8List( + imageData.offsetInBytes, + imageData.lengthInBytes, + ); for (int i = 0; i < cameraPositions.length; i++) { await camera.lookAt(cameraPositions[i]); await texture.setImage3D( @@ -278,13 +283,22 @@ void main() async { height, 4, 1, - projectedPixelBuffer.buffer.asUint8List(), + projectedPixelBuffer.buffer.asUint8List( + projectedPixelBuffer.offsetInBytes, + projectedPixelBuffer.lengthInBytes, + ), PixelDataFormat.RGBA, PixelDataType.FLOAT); final data = await projectedImage.getData(); data.setRange( - 0, data.length, projectedPixelBuffer.buffer.asFloat32List()); + 0, + data.length, + projectedPixelBuffer.buffer.asFloat32List( + projectedPixelBuffer.offsetInBytes, + projectedPixelBuffer.lengthInBytes ~/ Float32List.bytesPerElement, + ), + ); await projectedTexture.setLinearImage( projectedImage, PixelDataFormat.RGBA, diff --git a/thermion_dart/test/wireframe_renderable_test.dart b/thermion_dart/test/wireframe_renderable_test.dart index 655c89cbd..a52e7b7fd 100644 --- a/thermion_dart/test/wireframe_renderable_test.dart +++ b/thermion_dart/test/wireframe_renderable_test.dart @@ -64,14 +64,18 @@ void main() async { ..setUint32(8, totalLength, Endian.little) ..setUint32(12, paddedJsonLength, Endian.little) ..setUint32(16, 0x4e4f534a, Endian.little); - final glb = glbData.buffer.asUint8List(); + final glb = glbData.buffer.asUint8List(glbData.offsetInBytes, glbData.lengthInBytes); glb.setRange(20, 20 + jsonBytes.length, jsonBytes); glb.fillRange(20 + jsonBytes.length, 20 + paddedJsonLength, 0x20); final binaryHeaderOffset = 20 + paddedJsonLength; glbData ..setUint32(binaryHeaderOffset, 28, Endian.little) ..setUint32(binaryHeaderOffset + 4, 0x004e4942, Endian.little); - glb.setRange(binaryHeaderOffset + 8, totalLength, binary.buffer.asUint8List()); + glb.setRange( + binaryHeaderOffset + 8, + totalLength, + binary.buffer.asUint8List(binary.offsetInBytes, binary.lengthInBytes), + ); await expectLater( viewer.loadGltfFromBuffer( diff --git a/thermion_flutter/thermion_flutter/lib/src/platform/src/thermion_flutter_plugin_native.dart b/thermion_flutter/thermion_flutter/lib/src/platform/src/thermion_flutter_plugin_native.dart index 29a49f30c..c457b0f4b 100644 --- a/thermion_flutter/thermion_flutter/lib/src/platform/src/thermion_flutter_plugin_native.dart +++ b/thermion_flutter/thermion_flutter/lib/src/platform/src/thermion_flutter_plugin_native.dart @@ -36,7 +36,7 @@ class ThermionFlutterPluginImpl extends ThermionFlutterPlugin { path = path.replaceAll('asset://', ''); } final asset = await rootBundle.load(path); - return asset.buffer.asUint8List(asset.offsetInBytes); + return asset.buffer.asUint8List(asset.offsetInBytes, asset.lengthInBytes); } /// Serialises concurrent [initialize] calls. diff --git a/thermion_flutter/thermion_flutter/lib/src/platform/src/thermion_flutter_plugin_web.dart b/thermion_flutter/thermion_flutter/lib/src/platform/src/thermion_flutter_plugin_web.dart index f7e6fd687..6a48201e1 100644 --- a/thermion_flutter/thermion_flutter/lib/src/platform/src/thermion_flutter_plugin_web.dart +++ b/thermion_flutter/thermion_flutter/lib/src/platform/src/thermion_flutter_plugin_web.dart @@ -241,7 +241,7 @@ class ThermionFlutterPluginImpl extends ThermionFlutterPlugin path = path.replaceAll("asset://", ""); } var asset = await rootBundle.load(path); - return asset.buffer.asUint8List(asset.offsetInBytes); + return asset.buffer.asUint8List(asset.offsetInBytes, asset.lengthInBytes); } static void _tick(JSNumber timestamp) {