[Flutter GPU] Add DeviceBuffer.flush & GpuContext.getMinimumUniformByteAlignment. (flutter/engine#53620)

Part of https://github.com/flutter/flutter/issues/150953.

Provide a way to get the required minimum uniform byte alignment when referencing uniform blocks in a device buffer. Allow the user to explicitly flush DeviceBuffers (necessary for devices without shared memory).
This commit is contained in:
Brandon DeRosier 2024-09-17 13:31:50 -07:00 committed by GitHub
parent 54b7424efd
commit 759d173073
7 changed files with 79 additions and 1 deletions

View File

@ -9,6 +9,7 @@
#include "flutter/lib/gpu/formats.h"
#include "flutter/lib/ui/ui_dart_state.h"
#include "fml/make_copyable.h"
#include "impeller/core/platform.h"
#include "tonic/converter/dart_converter.h"
namespace flutter {
@ -105,3 +106,8 @@ extern int InternalFlutterGpu_Context_GetDefaultDepthStencilFormat(
->GetCapabilities()
->GetDefaultDepthStencilFormat()));
}
extern int InternalFlutterGpu_Context_GetMinimumUniformByteAlignment(
flutter::gpu::Context* wrapper) {
return impeller::DefaultUniformAlignment();
}

View File

@ -70,6 +70,10 @@ FLUTTER_GPU_EXPORT
extern int InternalFlutterGpu_Context_GetDefaultDepthStencilFormat(
flutter::gpu::Context* wrapper);
FLUTTER_GPU_EXPORT
extern int InternalFlutterGpu_Context_GetMinimumUniformByteAlignment(
flutter::gpu::Context* wrapper);
} // extern "C"
#endif // FLUTTER_LIB_GPU_CONTEXT_H_

View File

@ -99,3 +99,12 @@ bool InternalFlutterGpu_DeviceBuffer_Overwrite(
return device_buffer->Overwrite(tonic::DartByteData(source_byte_data),
destination_offset_in_bytes);
}
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;
}

View File

@ -62,6 +62,12 @@ extern bool InternalFlutterGpu_DeviceBuffer_Overwrite(
Dart_Handle source_byte_data,
int destination_offset_in_bytes);
FLUTTER_GPU_EXPORT
extern bool InternalFlutterGpu_DeviceBuffer_Flush(
flutter::gpu::DeviceBuffer* wrapper,
int offset_in_bytes,
int size_in_bytes);
} // extern "C"
#endif // FLUTTER_LIB_GPU_DEVICE_BUFFER_H_

View File

@ -93,11 +93,15 @@ base class DeviceBuffer extends NativeFieldWrapperClass1 with Buffer {
symbol: 'InternalFlutterGpu_DeviceBuffer_InitializeWithHostData')
external bool _initializeWithHostData(GpuContext gpuContext, ByteData data);
/// Overwrite a range of bytes in the already created [DeviceBuffer].
/// Overwrite a range of bytes within an existing [DeviceBuffer].
///
/// This method can only be used if the [DeviceBuffer] was created with
/// [StorageMode.hostVisible]. An exception will be thrown otherwise.
///
/// After new writes have been staged, the [DeviceBuffer.flush] should be
/// called prior to accessing the data. Otherwise, the updated data will not
/// be copied to the GPU on devices that don't have host coherent memory.
///
/// The entire length of [sourceBytes] will be copied into the [DeviceBuffer],
/// starting at byte index [destinationOffsetInBytes] in the [DeviceBuffer].
/// If performing this copy would result in an out of bounds write to the
@ -119,6 +123,37 @@ base class DeviceBuffer extends NativeFieldWrapperClass1 with Buffer {
@Native<Bool Function(Pointer<Void>, Handle, Int)>(
symbol: 'InternalFlutterGpu_DeviceBuffer_Overwrite')
external bool _overwrite(ByteData bytes, int destinationOffsetInBytes);
/// Flush the contents of the [DeviceBuffer] to the GPU.
///
/// This method can only be used if the [DeviceBuffer] was created with
/// [StorageMode.hostVisible]. An exception will be thrown otherwise.
///
/// If [lengthInBytes] is set to -1, the entire buffer will be flushed.
///
/// On devices with coherent host memory (memory shared between the CPU and
/// GPU), this method is a no-op.
void flush({int offsetInBytes = 0, int lengthInBytes = -1}) {
if (storageMode != StorageMode.hostVisible) {
throw Exception(
'DeviceBuffer.flush can only be used with DeviceBuffers that are host visible');
}
if (offsetInBytes < 0 || offsetInBytes >= sizeInBytes) {
throw Exception('offsetInBytes must be within the bounds of the buffer');
}
if (lengthInBytes < -1) {
throw Exception('lengthInBytes must be either positive or -1');
}
if (lengthInBytes != -1 && offsetInBytes + lengthInBytes > sizeInBytes) {
throw Exception(
'The provided range must not be too large to fit within the buffer');
}
_flush(offsetInBytes, lengthInBytes);
}
@Native<Void Function(Pointer<Void>, Int, Int)>(
symbol: 'InternalFlutterGpu_DeviceBuffer_Flush')
external void _flush(int offsetInBytes, int lengthInBytes);
}
/// [HostBuffer] is a [Buffer] which is allocated on the host (native CPU

View File

@ -40,6 +40,12 @@ base class GpuContext extends NativeFieldWrapperClass1 {
return PixelFormat.values[_getDefaultDepthStencilFormat()];
}
/// The minimum alignment required when referencing uniform blocks stored in a
/// `DeviceBuffer`.
int get minimumUniformByteAlignment {
return _getMinimumUniformByteAlignment();
}
/// Allocates a new region of GPU-resident memory.
///
/// The [storageMode] must be either [StorageMode.hostVisible] or
@ -124,6 +130,10 @@ base class GpuContext extends NativeFieldWrapperClass1 {
@Native<Int Function(Pointer<Void>)>(
symbol: 'InternalFlutterGpu_Context_GetDefaultDepthStencilFormat')
external int _getDefaultDepthStencilFormat();
@Native<Int Function(Pointer<Void>)>(
symbol: 'InternalFlutterGpu_Context_GetMinimumUniformByteAlignment')
external int _getMinimumUniformByteAlignment();
}
/// The default graphics context.

View File

@ -67,6 +67,11 @@ void main() async {
}
});
test('GpuContext.minimumUniformByteAlignment', () async {
final int alignment = gpu.gpuContext.minimumUniformByteAlignment;
expect(alignment, greaterThanOrEqualTo(16));
}, skip: !impellerEnabled);
test('HostBuffer.emplace', () async {
final gpu.HostBuffer hostBuffer = gpu.gpuContext.createHostBuffer();
@ -96,6 +101,7 @@ void main() async {
final bool success = deviceBuffer!
.overwrite(Int8List.fromList(<int>[0, 1, 2, 3]).buffer.asByteData());
deviceBuffer.flush();
expect(success, true);
}, skip: !impellerEnabled);
@ -107,6 +113,7 @@ void main() async {
final bool success = deviceBuffer!.overwrite(
Int8List.fromList(<int>[0, 1, 2, 3]).buffer.asByteData(),
destinationOffsetInBytes: 1);
deviceBuffer.flush();
expect(success, false);
}, skip: !impellerEnabled);
@ -120,6 +127,7 @@ void main() async {
deviceBuffer!.overwrite(
Int8List.fromList(<int>[0, 1, 2, 3]).buffer.asByteData(),
destinationOffsetInBytes: -1);
deviceBuffer.flush();
fail('Exception not thrown for negative destination offset.');
} catch (e) {
expect(