[Flutter GPU] Breaking: Use exceptions for resource creation errors. (#162104)
Resolves https://github.com/flutter/flutter/issues/143891. This patch includes breaking changes, but this API is still in preview. * Breaking: Rename `Texture.GetBaseMipLevelSizeInBytes` to `Texture.getBaseMipLevelSizeInBytes`. * Breaking: Make `Texture.overwrite` throw exception instead of returning false. * Non-breaking: Make `DeviceBuffer`/`Texture` creation throw exceptions instead of returning nullables. We can incrementally add more specific exceptions for resource creation failure.
This commit is contained in:
parent
806772b528
commit
039d0db698
@ -66,17 +66,16 @@ ByteData float32(List<double> values) {
|
||||
|
||||
@pragma('vm:entry-point')
|
||||
void canCreateRenderPassAndSubmit(int width, int height) {
|
||||
final gpu.Texture? renderTexture = gpu.gpuContext.createTexture(
|
||||
final gpu.Texture renderTexture = gpu.gpuContext.createTexture(
|
||||
gpu.StorageMode.devicePrivate,
|
||||
width,
|
||||
height,
|
||||
);
|
||||
assert(renderTexture != null);
|
||||
|
||||
final gpu.CommandBuffer commandBuffer = gpu.gpuContext.createCommandBuffer();
|
||||
|
||||
final gpu.RenderTarget renderTarget = gpu.RenderTarget.singleColor(
|
||||
gpu.ColorAttachment(texture: renderTexture!),
|
||||
gpu.ColorAttachment(texture: renderTexture),
|
||||
);
|
||||
final gpu.RenderPass encoder = commandBuffer.createRenderPass(renderTarget);
|
||||
|
||||
|
@ -244,9 +244,6 @@ base class HostBuffer {
|
||||
StorageMode.hostVisible,
|
||||
length,
|
||||
);
|
||||
if (buffer == null) {
|
||||
throw Exception('Failed to allocate DeviceBuffer of length $length');
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
|
@ -63,8 +63,8 @@ base class GpuContext extends NativeFieldWrapperClass1 {
|
||||
/// The [storageMode] must be either [StorageMode.hostVisible] or
|
||||
/// [StorageMode.devicePrivate], otherwise an exception will be thrown.
|
||||
///
|
||||
/// Returns [null] if the [DeviceBuffer] creation failed.
|
||||
DeviceBuffer? createDeviceBuffer(StorageMode storageMode, int sizeInBytes) {
|
||||
/// Throws an exception if the [DeviceBuffer] creation failed.
|
||||
DeviceBuffer createDeviceBuffer(StorageMode storageMode, int sizeInBytes) {
|
||||
if (storageMode == StorageMode.deviceTransient) {
|
||||
throw Exception(
|
||||
'DeviceBuffers cannot be set to StorageMode.deviceTransient',
|
||||
@ -75,7 +75,10 @@ base class GpuContext extends NativeFieldWrapperClass1 {
|
||||
storageMode,
|
||||
sizeInBytes,
|
||||
);
|
||||
return result.isValid ? result : null;
|
||||
if (!result.isValid) {
|
||||
throw Exception('DeviceBuffer creation failed');
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Allocates a new region of host-visible GPU-resident memory, initialized
|
||||
@ -85,10 +88,13 @@ base class GpuContext extends NativeFieldWrapperClass1 {
|
||||
/// from the host, the [StorageMode] of the new [DeviceBuffer] is
|
||||
/// automatically set to [StorageMode.hostVisible].
|
||||
///
|
||||
/// Returns [null] if the [DeviceBuffer] creation failed.
|
||||
DeviceBuffer? createDeviceBufferWithCopy(ByteData data) {
|
||||
/// Throws an exception if the [DeviceBuffer] creation failed.
|
||||
DeviceBuffer createDeviceBufferWithCopy(ByteData data) {
|
||||
DeviceBuffer result = DeviceBuffer._initializeWithHostData(this, data);
|
||||
return result.isValid ? result : null;
|
||||
if (!result.isValid) {
|
||||
throw Exception('DeviceBuffer creation failed');
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Creates a bump allocator that managed a [DeviceBuffer] block list.
|
||||
@ -102,8 +108,8 @@ base class GpuContext extends NativeFieldWrapperClass1 {
|
||||
|
||||
/// Allocates a new texture in GPU-resident memory.
|
||||
///
|
||||
/// Returns [null] if the [Texture] creation failed.
|
||||
Texture? createTexture(
|
||||
/// Throws an exception if the [Texture] creation failed.
|
||||
Texture createTexture(
|
||||
StorageMode storageMode,
|
||||
int width,
|
||||
int height, {
|
||||
@ -127,7 +133,10 @@ base class GpuContext extends NativeFieldWrapperClass1 {
|
||||
enableShaderReadUsage,
|
||||
enableShaderWriteUsage,
|
||||
);
|
||||
return result.isValid ? result : null;
|
||||
if (!result.isValid) {
|
||||
throw Exception('Texture creation failed');
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Create a new command buffer that can be used to submit GPU commands.
|
||||
|
@ -73,7 +73,7 @@ base class Texture extends NativeFieldWrapperClass1 {
|
||||
return _bytesPerTexel();
|
||||
}
|
||||
|
||||
int GetBaseMipLevelSizeInBytes() {
|
||||
int getBaseMipLevelSizeInBytes() {
|
||||
return bytesPerTexel * width * height;
|
||||
}
|
||||
|
||||
@ -86,21 +86,23 @@ base class Texture extends NativeFieldWrapperClass1 {
|
||||
/// level, otherwise an exception will be thrown. The size of the base mip
|
||||
/// level is always `width * height * bytesPerPixel`.
|
||||
///
|
||||
/// Returns [true] if the write was successful, or [false] if the write
|
||||
/// failed due to an internal error.
|
||||
bool overwrite(ByteData sourceBytes) {
|
||||
/// Throws an exception if the write failed due to an internal error.
|
||||
void overwrite(ByteData sourceBytes) {
|
||||
if (storageMode != StorageMode.hostVisible) {
|
||||
throw Exception(
|
||||
'Texture.overwrite can only be used with Textures that are host visible',
|
||||
);
|
||||
}
|
||||
int baseMipSize = GetBaseMipLevelSizeInBytes();
|
||||
int baseMipSize = getBaseMipLevelSizeInBytes();
|
||||
if (sourceBytes.lengthInBytes != baseMipSize) {
|
||||
throw Exception(
|
||||
'The length of sourceBytes (bytes: ${sourceBytes.lengthInBytes}) must exactly match the size of the base mip level (bytes: ${baseMipSize})',
|
||||
);
|
||||
}
|
||||
return _overwrite(sourceBytes);
|
||||
bool success = _overwrite(sourceBytes);
|
||||
if (!success) {
|
||||
throw Exception("Texture overwrite failed");
|
||||
}
|
||||
}
|
||||
|
||||
ui.Image asImage() {
|
||||
|
@ -52,26 +52,24 @@ class RenderPassState {
|
||||
|
||||
/// Create a simple RenderPass with simple color and depth-stencil attachments.
|
||||
RenderPassState createSimpleRenderPass({Vector4? clearColor}) {
|
||||
final gpu.Texture? renderTexture = gpu.gpuContext.createTexture(
|
||||
final gpu.Texture renderTexture = gpu.gpuContext.createTexture(
|
||||
gpu.StorageMode.devicePrivate,
|
||||
100,
|
||||
100,
|
||||
);
|
||||
assert(renderTexture != null);
|
||||
|
||||
final gpu.Texture? depthStencilTexture = gpu.gpuContext.createTexture(
|
||||
final gpu.Texture depthStencilTexture = gpu.gpuContext.createTexture(
|
||||
gpu.StorageMode.deviceTransient,
|
||||
100,
|
||||
100,
|
||||
format: gpu.gpuContext.defaultDepthStencilFormat,
|
||||
);
|
||||
assert(depthStencilTexture != null);
|
||||
|
||||
final gpu.CommandBuffer commandBuffer = gpu.gpuContext.createCommandBuffer();
|
||||
|
||||
final gpu.RenderTarget renderTarget = gpu.RenderTarget.singleColor(
|
||||
gpu.ColorAttachment(texture: renderTexture!, clearValue: clearColor),
|
||||
depthStencilAttachment: gpu.DepthStencilAttachment(texture: depthStencilTexture!),
|
||||
gpu.ColorAttachment(texture: renderTexture, clearValue: clearColor),
|
||||
depthStencilAttachment: gpu.DepthStencilAttachment(texture: depthStencilTexture),
|
||||
);
|
||||
|
||||
final gpu.RenderPass renderPass = commandBuffer.createRenderPass(renderTarget);
|
||||
@ -83,49 +81,46 @@ RenderPassState createSimpleRenderPassWithMSAA() {
|
||||
// Create transient MSAA attachments, which will live entirely in tile memory
|
||||
// for most GPUs.
|
||||
|
||||
final gpu.Texture? renderTexture = gpu.gpuContext.createTexture(
|
||||
final gpu.Texture renderTexture = gpu.gpuContext.createTexture(
|
||||
gpu.StorageMode.deviceTransient,
|
||||
100,
|
||||
100,
|
||||
format: gpu.gpuContext.defaultColorFormat,
|
||||
sampleCount: 4,
|
||||
);
|
||||
assert(renderTexture != null);
|
||||
|
||||
final gpu.Texture? depthStencilTexture = gpu.gpuContext.createTexture(
|
||||
final gpu.Texture depthStencilTexture = gpu.gpuContext.createTexture(
|
||||
gpu.StorageMode.deviceTransient,
|
||||
100,
|
||||
100,
|
||||
format: gpu.gpuContext.defaultDepthStencilFormat,
|
||||
sampleCount: 4,
|
||||
);
|
||||
assert(depthStencilTexture != null);
|
||||
|
||||
// Create the single-sample resolve texture that live in DRAM and will be
|
||||
// drawn to the screen.
|
||||
|
||||
final gpu.Texture? resolveTexture = gpu.gpuContext.createTexture(
|
||||
final gpu.Texture resolveTexture = gpu.gpuContext.createTexture(
|
||||
gpu.StorageMode.devicePrivate,
|
||||
100,
|
||||
100,
|
||||
format: gpu.gpuContext.defaultColorFormat,
|
||||
);
|
||||
assert(resolveTexture != null);
|
||||
|
||||
final gpu.CommandBuffer commandBuffer = gpu.gpuContext.createCommandBuffer();
|
||||
|
||||
final gpu.RenderTarget renderTarget = gpu.RenderTarget.singleColor(
|
||||
gpu.ColorAttachment(
|
||||
texture: renderTexture!,
|
||||
texture: renderTexture,
|
||||
resolveTexture: resolveTexture,
|
||||
storeAction: gpu.StoreAction.multisampleResolve,
|
||||
),
|
||||
depthStencilAttachment: gpu.DepthStencilAttachment(texture: depthStencilTexture!),
|
||||
depthStencilAttachment: gpu.DepthStencilAttachment(texture: depthStencilTexture),
|
||||
);
|
||||
|
||||
final gpu.RenderPass renderPass = commandBuffer.createRenderPass(renderTarget);
|
||||
|
||||
return RenderPassState(resolveTexture!, commandBuffer, renderPass);
|
||||
return RenderPassState(resolveTexture, commandBuffer, renderPass);
|
||||
}
|
||||
|
||||
void drawTriangle(RenderPassState state, Vector4 color) {
|
||||
@ -228,23 +223,21 @@ void main() async {
|
||||
}, skip: !impellerEnabled);
|
||||
|
||||
test('GpuContext.createDeviceBuffer', () async {
|
||||
final gpu.DeviceBuffer? deviceBuffer = gpu.gpuContext.createDeviceBuffer(
|
||||
final gpu.DeviceBuffer deviceBuffer = gpu.gpuContext.createDeviceBuffer(
|
||||
gpu.StorageMode.hostVisible,
|
||||
4,
|
||||
);
|
||||
assert(deviceBuffer != null);
|
||||
|
||||
expect(deviceBuffer!.sizeInBytes, 4);
|
||||
expect(deviceBuffer.sizeInBytes, 4);
|
||||
}, skip: !impellerEnabled);
|
||||
|
||||
test('DeviceBuffer.overwrite', () async {
|
||||
final gpu.DeviceBuffer? deviceBuffer = gpu.gpuContext.createDeviceBuffer(
|
||||
final gpu.DeviceBuffer deviceBuffer = gpu.gpuContext.createDeviceBuffer(
|
||||
gpu.StorageMode.hostVisible,
|
||||
4,
|
||||
);
|
||||
assert(deviceBuffer != null);
|
||||
|
||||
final bool success = deviceBuffer!.overwrite(
|
||||
final bool success = deviceBuffer.overwrite(
|
||||
Int8List.fromList(<int>[0, 1, 2, 3]).buffer.asByteData(),
|
||||
);
|
||||
deviceBuffer.flush();
|
||||
@ -252,13 +245,12 @@ void main() async {
|
||||
}, skip: !impellerEnabled);
|
||||
|
||||
test('DeviceBuffer.overwrite fails when out of bounds', () async {
|
||||
final gpu.DeviceBuffer? deviceBuffer = gpu.gpuContext.createDeviceBuffer(
|
||||
final gpu.DeviceBuffer deviceBuffer = gpu.gpuContext.createDeviceBuffer(
|
||||
gpu.StorageMode.hostVisible,
|
||||
4,
|
||||
);
|
||||
assert(deviceBuffer != null);
|
||||
|
||||
final bool success = deviceBuffer!.overwrite(
|
||||
final bool success = deviceBuffer.overwrite(
|
||||
Int8List.fromList(<int>[0, 1, 2, 3]).buffer.asByteData(),
|
||||
destinationOffsetInBytes: 1,
|
||||
);
|
||||
@ -267,14 +259,13 @@ void main() async {
|
||||
}, skip: !impellerEnabled);
|
||||
|
||||
test('DeviceBuffer.overwrite throws for negative destination offset', () async {
|
||||
final gpu.DeviceBuffer? deviceBuffer = gpu.gpuContext.createDeviceBuffer(
|
||||
final gpu.DeviceBuffer deviceBuffer = gpu.gpuContext.createDeviceBuffer(
|
||||
gpu.StorageMode.hostVisible,
|
||||
4,
|
||||
);
|
||||
assert(deviceBuffer != null);
|
||||
|
||||
try {
|
||||
deviceBuffer!.overwrite(
|
||||
deviceBuffer.overwrite(
|
||||
Int8List.fromList(<int>[0, 1, 2, 3]).buffer.asByteData(),
|
||||
destinationOffsetInBytes: -1,
|
||||
);
|
||||
@ -286,15 +277,10 @@ void main() async {
|
||||
}, skip: !impellerEnabled);
|
||||
|
||||
test('GpuContext.createTexture', () async {
|
||||
final gpu.Texture? texture = gpu.gpuContext.createTexture(
|
||||
gpu.StorageMode.hostVisible,
|
||||
100,
|
||||
100,
|
||||
);
|
||||
assert(texture != null);
|
||||
final gpu.Texture texture = gpu.gpuContext.createTexture(gpu.StorageMode.hostVisible, 100, 100);
|
||||
|
||||
// Check the defaults.
|
||||
expect(texture!.coordinateSystem, gpu.TextureCoordinateSystem.renderToTexture);
|
||||
expect(texture.coordinateSystem, gpu.TextureCoordinateSystem.renderToTexture);
|
||||
expect(texture.width, 100);
|
||||
expect(texture.height, 100);
|
||||
expect(texture.storageMode, gpu.StorageMode.hostVisible);
|
||||
@ -304,33 +290,25 @@ void main() async {
|
||||
expect(texture.enableShaderReadUsage, true);
|
||||
expect(!texture.enableShaderWriteUsage, true);
|
||||
expect(texture.bytesPerTexel, 4);
|
||||
expect(texture.GetBaseMipLevelSizeInBytes(), 40000);
|
||||
expect(texture.getBaseMipLevelSizeInBytes(), 40000);
|
||||
}, skip: !impellerEnabled);
|
||||
|
||||
test('Texture.overwrite', () async {
|
||||
final gpu.Texture? texture = gpu.gpuContext.createTexture(gpu.StorageMode.hostVisible, 2, 2);
|
||||
assert(texture != null);
|
||||
final gpu.Texture texture = gpu.gpuContext.createTexture(gpu.StorageMode.hostVisible, 2, 2);
|
||||
|
||||
const ui.Color red = ui.Color.fromARGB(0xFF, 0xFF, 0, 0);
|
||||
const ui.Color green = ui.Color.fromARGB(0xFF, 0, 0xFF, 0);
|
||||
final bool success = texture!.overwrite(
|
||||
texture.overwrite(
|
||||
Int32List.fromList(<int>[red.value, green.value, green.value, red.value]).buffer.asByteData(),
|
||||
);
|
||||
|
||||
expect(success, true);
|
||||
}, skip: !impellerEnabled);
|
||||
|
||||
test('Texture.overwrite throws for wrong buffer size', () async {
|
||||
final gpu.Texture? texture = gpu.gpuContext.createTexture(
|
||||
gpu.StorageMode.hostVisible,
|
||||
100,
|
||||
100,
|
||||
);
|
||||
assert(texture != null);
|
||||
final gpu.Texture texture = gpu.gpuContext.createTexture(gpu.StorageMode.hostVisible, 100, 100);
|
||||
|
||||
const ui.Color red = ui.Color.fromARGB(0xFF, 0xFF, 0, 0);
|
||||
try {
|
||||
texture!.overwrite(
|
||||
texture.overwrite(
|
||||
Int32List.fromList(<int>[red.value, red.value, red.value, red.value]).buffer.asByteData(),
|
||||
);
|
||||
fail('Exception not thrown for wrong buffer size.');
|
||||
@ -345,29 +323,23 @@ void main() async {
|
||||
}, skip: !impellerEnabled);
|
||||
|
||||
test('Texture.asImage returns a valid ui.Image handle', () async {
|
||||
final gpu.Texture? texture = gpu.gpuContext.createTexture(
|
||||
gpu.StorageMode.hostVisible,
|
||||
100,
|
||||
100,
|
||||
);
|
||||
assert(texture != null);
|
||||
final gpu.Texture texture = gpu.gpuContext.createTexture(gpu.StorageMode.hostVisible, 100, 100);
|
||||
|
||||
final ui.Image image = texture!.asImage();
|
||||
final ui.Image image = texture.asImage();
|
||||
expect(image.width, 100);
|
||||
expect(image.height, 100);
|
||||
}, skip: !impellerEnabled);
|
||||
|
||||
test('Texture.asImage throws when not shader readable', () async {
|
||||
final gpu.Texture? texture = gpu.gpuContext.createTexture(
|
||||
final gpu.Texture texture = gpu.gpuContext.createTexture(
|
||||
gpu.StorageMode.hostVisible,
|
||||
100,
|
||||
100,
|
||||
enableShaderReadUsage: false,
|
||||
);
|
||||
assert(texture != null);
|
||||
|
||||
try {
|
||||
texture!.asImage();
|
||||
texture.asImage();
|
||||
fail('Exception not thrown when not shader readable.');
|
||||
} catch (e) {
|
||||
expect(
|
||||
@ -457,8 +429,11 @@ void main() async {
|
||||
// purposes of testing this error.
|
||||
final gpu.UniformSlot vertInfo = pipeline.vertexShader.getUniformSlot('VertInfo');
|
||||
|
||||
final gpu.Texture texture =
|
||||
gpu.gpuContext.createTexture(gpu.StorageMode.deviceTransient, 100, 100)!;
|
||||
final gpu.Texture texture = gpu.gpuContext.createTexture(
|
||||
gpu.StorageMode.deviceTransient,
|
||||
100,
|
||||
100,
|
||||
);
|
||||
|
||||
try {
|
||||
state.renderPass.bindTexture(vertInfo, texture);
|
||||
@ -495,7 +470,7 @@ void main() async {
|
||||
0, 0, 0, 1, // mvp
|
||||
0, 1, 0, 1, // color
|
||||
]);
|
||||
final uniformBuffer = gpu.gpuContext.createDeviceBufferWithCopy(vertInfoData)!;
|
||||
final uniformBuffer = gpu.gpuContext.createDeviceBufferWithCopy(vertInfoData);
|
||||
final gooduniformBufferView = gpu.BufferView(
|
||||
uniformBuffer,
|
||||
offsetInBytes: 0,
|
||||
|
Loading…
x
Reference in New Issue
Block a user