[Flutter GPU] Add DeviceBuffer. (#47699)
Part of http://flutter.dev/go/impeller-dart
Resolves https://github.com/flutter/flutter/issues/130924.
Resolves https://github.com/flutter/flutter/issues/130925.
Create and upload data to host visible device buffers. Commands should allow for binding either HostBuffers (which eventually resolve to DeviceBuffers) or DeviceBuffers. There's a `Buffer` mixin to allow for expressing this in `BufferView`, but this may end up changing once I actually add Commands and need to solve the puzzle.
diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter
index 7fc0ed2..78f1bb8 100644
--- a/ci/licenses_golden/licenses_flutter
+++ b/ci/licenses_golden/licenses_flutter
@@ -3593,6 +3593,8 @@
ORIGIN: ../../../flutter/impeller/typographer/typographer_context.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/gpu/context.cc + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/gpu/context.h + ../../../flutter/LICENSE
+ORIGIN: ../../../flutter/lib/gpu/device_buffer.cc + ../../../flutter/LICENSE
+ORIGIN: ../../../flutter/lib/gpu/device_buffer.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/gpu/export.cc + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/gpu/export.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/gpu/host_buffer.cc + ../../../flutter/LICENSE
@@ -3600,6 +3602,7 @@
ORIGIN: ../../../flutter/lib/gpu/lib/gpu.dart + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/gpu/lib/src/buffer.dart + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/gpu/lib/src/context.dart + ../../../flutter/LICENSE
+ORIGIN: ../../../flutter/lib/gpu/lib/src/formats.dart + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/gpu/lib/src/smoketest.dart + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/gpu/smoketest.cc + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/gpu/smoketest.h + ../../../flutter/LICENSE
@@ -6356,6 +6359,8 @@
FILE: ../../../flutter/impeller/typographer/typographer_context.h
FILE: ../../../flutter/lib/gpu/context.cc
FILE: ../../../flutter/lib/gpu/context.h
+FILE: ../../../flutter/lib/gpu/device_buffer.cc
+FILE: ../../../flutter/lib/gpu/device_buffer.h
FILE: ../../../flutter/lib/gpu/export.cc
FILE: ../../../flutter/lib/gpu/export.h
FILE: ../../../flutter/lib/gpu/host_buffer.cc
@@ -6363,6 +6368,7 @@
FILE: ../../../flutter/lib/gpu/lib/gpu.dart
FILE: ../../../flutter/lib/gpu/lib/src/buffer.dart
FILE: ../../../flutter/lib/gpu/lib/src/context.dart
+FILE: ../../../flutter/lib/gpu/lib/src/formats.dart
FILE: ../../../flutter/lib/gpu/lib/src/smoketest.dart
FILE: ../../../flutter/lib/gpu/smoketest.cc
FILE: ../../../flutter/lib/gpu/smoketest.h
diff --git a/impeller/fixtures/dart_tests.dart b/impeller/fixtures/dart_tests.dart
index 7e21342..01d810e 100644
--- a/impeller/fixtures/dart_tests.dart
+++ b/impeller/fixtures/dart_tests.dart
@@ -33,3 +33,48 @@
assert(view1.offsetInBytes >= 4);
assert(view1.lengthInBytes == 4);
}
+
+@pragma('vm:entry-point')
+void canCreateDeviceBuffer() {
+ final gpu.DeviceBuffer? deviceBuffer =
+ gpu.gpuContext.createDeviceBuffer(gpu.StorageMode.hostVisible, 4);
+ assert(deviceBuffer != null);
+ assert(deviceBuffer!.sizeInBytes == 4);
+}
+
+@pragma('vm:entry-point')
+void canOverwriteDeviceBuffer() {
+ final gpu.DeviceBuffer? deviceBuffer =
+ gpu.gpuContext.createDeviceBuffer(gpu.StorageMode.hostVisible, 4);
+ assert(deviceBuffer != null);
+ final bool success = deviceBuffer!
+ .overwrite(Int8List.fromList(<int>[0, 1, 2, 3]).buffer.asByteData());
+ assert(success);
+}
+
+@pragma('vm:entry-point')
+void deviceBufferOverwriteFailsWhenOutOfBounds() {
+ final gpu.DeviceBuffer? deviceBuffer =
+ gpu.gpuContext.createDeviceBuffer(gpu.StorageMode.hostVisible, 4);
+ assert(deviceBuffer != null);
+ final bool success = deviceBuffer!.overwrite(
+ Int8List.fromList(<int>[0, 1, 2, 3]).buffer.asByteData(),
+ destinationOffsetInBytes: 1);
+ assert(!success);
+}
+
+@pragma('vm:entry-point')
+void deviceBufferOverwriteThrowsForNegativeDestinationOffset() {
+ final gpu.DeviceBuffer? deviceBuffer =
+ gpu.gpuContext.createDeviceBuffer(gpu.StorageMode.hostVisible, 4);
+ assert(deviceBuffer != null);
+ String? exception;
+ try {
+ deviceBuffer!.overwrite(
+ Int8List.fromList(<int>[0, 1, 2, 3]).buffer.asByteData(),
+ destinationOffsetInBytes: -1);
+ } catch (e) {
+ exception = e.toString();
+ }
+ assert(exception!.contains('destinationOffsetInBytes must be positive'));
+}
diff --git a/impeller/renderer/backend/gles/texture_gles.cc b/impeller/renderer/backend/gles/texture_gles.cc
index 1db7aab..8d6bdc2 100644
--- a/impeller/renderer/backend/gles/texture_gles.cc
+++ b/impeller/renderer/backend/gles/texture_gles.cc
@@ -73,6 +73,7 @@
<< " would exceed max supported size of " << max_size << ".";
return;
}
+
is_valid_ = true;
}
diff --git a/impeller/renderer/renderer_dart_unittests.cc b/impeller/renderer/renderer_dart_unittests.cc
index b889346..63922a0 100644
--- a/impeller/renderer/renderer_dart_unittests.cc
+++ b/impeller/renderer/renderer_dart_unittests.cc
@@ -111,19 +111,28 @@
ASSERT_TRUE(result);
}
-TEST_P(RendererDartTest, CanEmplaceHostBuffer) {
- auto isolate = GetIsolate();
- bool result = isolate->RunInIsolateScope([]() -> bool {
- if (tonic::CheckAndHandleError(
- ::Dart_Invoke(Dart_RootLibrary(),
- tonic::ToDart("canEmplaceHostBuffer"), 0, nullptr))) {
- return false;
- }
- return true;
- });
+#define DART_TEST_CASE(name) \
+ TEST_P(RendererDartTest, name) { \
+ auto isolate = GetIsolate(); \
+ bool result = isolate->RunInIsolateScope([]() -> bool { \
+ if (tonic::CheckAndHandleError(::Dart_Invoke( \
+ Dart_RootLibrary(), tonic::ToDart(#name), 0, nullptr))) { \
+ return false; \
+ } \
+ return true; \
+ }); \
+ ASSERT_TRUE(result); \
+ }
- ASSERT_TRUE(result);
-}
+/// These test entries correspond to Dart functions located in
+/// `flutter/impeller/fixtures/dart_tests.dart`
+
+DART_TEST_CASE(canEmplaceHostBuffer);
+DART_TEST_CASE(canCreateDeviceBuffer);
+
+DART_TEST_CASE(canOverwriteDeviceBuffer);
+DART_TEST_CASE(deviceBufferOverwriteFailsWhenOutOfBounds);
+DART_TEST_CASE(deviceBufferOverwriteThrowsForNegativeDestinationOffset);
} // namespace testing
} // namespace impeller
diff --git a/lib/gpu/BUILD.gn b/lib/gpu/BUILD.gn
index 689e2f3..625a11c 100644
--- a/lib/gpu/BUILD.gn
+++ b/lib/gpu/BUILD.gn
@@ -34,6 +34,8 @@
sources = [
"context.cc",
"context.h",
+ "device_buffer.cc",
+ "device_buffer.h",
"export.cc",
"export.h",
"host_buffer.cc",
diff --git a/lib/gpu/device_buffer.cc b/lib/gpu/device_buffer.cc
new file mode 100644
index 0000000..cc4f8b4
--- /dev/null
+++ b/lib/gpu/device_buffer.cc
@@ -0,0 +1,93 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "flutter/lib/gpu/device_buffer.h"
+
+#include "dart_api.h"
+#include "fml/mapping.h"
+#include "impeller/core/device_buffer.h"
+#include "impeller/core/device_buffer_descriptor.h"
+#include "impeller/core/formats.h"
+#include "impeller/core/platform.h"
+#include "impeller/core/range.h"
+#include "third_party/tonic/typed_data/dart_byte_data.h"
+#include "tonic/converter/dart_converter.h"
+
+namespace flutter {
+
+IMPLEMENT_WRAPPERTYPEINFO(gpu, DeviceBuffer);
+
+DeviceBuffer::DeviceBuffer(
+ std::shared_ptr<impeller::DeviceBuffer> device_buffer)
+ : device_buffer_(std::move(device_buffer)) {}
+
+DeviceBuffer::~DeviceBuffer() = default;
+
+bool DeviceBuffer::Overwrite(const tonic::DartByteData& source_bytes,
+ size_t destination_offset_in_bytes) {
+ if (!device_buffer_->CopyHostBuffer(
+ reinterpret_cast<const uint8_t*>(source_bytes.data()),
+ impeller::Range(0, source_bytes.length_in_bytes()),
+ destination_offset_in_bytes)) {
+ return false;
+ }
+ return true;
+}
+
+} // namespace flutter
+
+//----------------------------------------------------------------------------
+/// Exports
+///
+
+bool InternalFlutterGpu_DeviceBuffer_Initialize(Dart_Handle wrapper,
+ flutter::Context* gpu_context,
+ int storage_mode,
+ int size_in_bytes) {
+ impeller::DeviceBufferDescriptor desc;
+ desc.storage_mode = static_cast<impeller::StorageMode>(storage_mode);
+ desc.size = size_in_bytes;
+ auto device_buffer =
+ gpu_context->GetContext()->GetResourceAllocator()->CreateBuffer(desc);
+ if (!device_buffer) {
+ FML_LOG(ERROR) << "Failed to create device buffer.";
+ return false;
+ }
+
+ auto res =
+ fml::MakeRefCounted<flutter::DeviceBuffer>(std::move(device_buffer));
+ res->AssociateWithDartWrapper(wrapper);
+
+ return true;
+}
+
+bool InternalFlutterGpu_DeviceBuffer_InitializeWithHostData(
+ Dart_Handle wrapper,
+ flutter::Context* gpu_context,
+ Dart_Handle byte_data) {
+ auto data = tonic::DartByteData(byte_data);
+ auto mapping = fml::NonOwnedMapping(reinterpret_cast<uint8_t*>(data.data()),
+ data.length_in_bytes());
+ auto device_buffer =
+ gpu_context->GetContext()->GetResourceAllocator()->CreateBufferWithCopy(
+ mapping);
+ if (!device_buffer) {
+ FML_LOG(ERROR) << "Failed to create device buffer with copy.";
+ return false;
+ }
+
+ auto res =
+ fml::MakeRefCounted<flutter::DeviceBuffer>(std::move(device_buffer));
+ res->AssociateWithDartWrapper(wrapper);
+
+ return true;
+}
+
+bool InternalFlutterGpu_DeviceBuffer_Overwrite(
+ flutter::DeviceBuffer* device_buffer,
+ Dart_Handle source_byte_data,
+ int destination_offset_in_bytes) {
+ return device_buffer->Overwrite(tonic::DartByteData(source_byte_data),
+ destination_offset_in_bytes);
+}
diff --git a/lib/gpu/device_buffer.h b/lib/gpu/device_buffer.h
new file mode 100644
index 0000000..932c56a
--- /dev/null
+++ b/lib/gpu/device_buffer.h
@@ -0,0 +1,60 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#pragma once
+
+#include "flutter/lib/gpu/context.h"
+#include "flutter/lib/gpu/export.h"
+#include "flutter/lib/ui/dart_wrapper.h"
+#include "impeller/core/device_buffer_descriptor.h"
+#include "third_party/tonic/typed_data/dart_byte_data.h"
+
+namespace flutter {
+
+class DeviceBuffer : public RefCountedDartWrappable<DeviceBuffer> {
+ DEFINE_WRAPPERTYPEINFO();
+ FML_FRIEND_MAKE_REF_COUNTED(DeviceBuffer);
+
+ public:
+ explicit DeviceBuffer(std::shared_ptr<impeller::DeviceBuffer> device_buffer);
+
+ ~DeviceBuffer() override;
+
+ bool Overwrite(const tonic::DartByteData& source_bytes,
+ size_t destination_offset_in_bytes);
+
+ private:
+ std::shared_ptr<impeller::DeviceBuffer> device_buffer_;
+
+ FML_DISALLOW_COPY_AND_ASSIGN(DeviceBuffer);
+};
+
+} // namespace flutter
+
+//----------------------------------------------------------------------------
+/// Exports
+///
+
+extern "C" {
+
+FLUTTER_GPU_EXPORT
+extern bool InternalFlutterGpu_DeviceBuffer_Initialize(
+ Dart_Handle wrapper,
+ flutter::Context* gpu_context,
+ int storage_mode,
+ int size_in_bytes);
+
+FLUTTER_GPU_EXPORT
+extern bool InternalFlutterGpu_DeviceBuffer_InitializeWithHostData(
+ Dart_Handle wrapper,
+ flutter::Context* gpu_context,
+ Dart_Handle byte_data);
+
+FLUTTER_GPU_EXPORT
+extern bool InternalFlutterGpu_DeviceBuffer_Overwrite(
+ flutter::DeviceBuffer* wrapper,
+ Dart_Handle source_byte_data,
+ int destination_offset_in_bytes);
+
+} // extern "C"
diff --git a/lib/gpu/lib/gpu.dart b/lib/gpu/lib/gpu.dart
index b12b4a5..7801429 100644
--- a/lib/gpu/lib/gpu.dart
+++ b/lib/gpu/lib/gpu.dart
@@ -11,7 +11,12 @@
/// * [Flutter GPU Wiki page](https://github.com/flutter/flutter/wiki/Flutter-GPU).
library flutter_gpu;
+import 'dart:ffi';
+import 'dart:nativewrappers';
+import 'dart:typed_data';
+
export 'src/smoketest.dart';
-export 'src/context.dart';
-export 'src/buffer.dart';
+part 'src/formats.dart';
+part 'src/context.dart';
+part 'src/buffer.dart';
diff --git a/lib/gpu/lib/src/buffer.dart b/lib/gpu/lib/src/buffer.dart
index 6864f20..9794e56 100644
--- a/lib/gpu/lib/src/buffer.dart
+++ b/lib/gpu/lib/src/buffer.dart
@@ -2,14 +2,14 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-import 'dart:ffi';
-import 'dart:nativewrappers';
-import 'dart:typed_data';
+// ignore_for_file: public_member_api_docs
+
+part of flutter_gpu;
/// A reference to a byte range within a GPU-resident [Buffer].
class BufferView {
/// The buffer of this view.
- final HostBuffer buffer;
+ final Buffer buffer;
/// The start of the view, in bytes starting from the beginning of the
/// [buffer].
@@ -23,6 +23,74 @@
{required this.offsetInBytes, required this.lengthInBytes});
}
+/// A buffer that can be referenced by commands on the GPU.
+mixin Buffer {}
+
+/// [DeviceBuffer] is a region of memory allocated on the device heap
+/// (GPU-resident memory).
+base class DeviceBuffer extends NativeFieldWrapperClass1 with Buffer {
+ bool _valid = false;
+ get isValid {
+ return _valid;
+ }
+
+ /// Creates a new DeviceBuffer.
+ DeviceBuffer._initialize(
+ GpuContext gpuContext, StorageMode storageMode, int sizeInBytes)
+ : storageMode = storageMode,
+ sizeInBytes = sizeInBytes {
+ _valid = _initialize(gpuContext, storageMode.index, sizeInBytes);
+ }
+
+ /// Creates a new host visible DeviceBuffer with data copied from the host.
+ DeviceBuffer._initializeWithHostData(GpuContext gpuContext, ByteData data)
+ : storageMode = StorageMode.hostVisible,
+ sizeInBytes = data.lengthInBytes {
+ _valid = _initializeWithHostData(gpuContext, data);
+ }
+
+ final StorageMode storageMode;
+ final int sizeInBytes;
+
+ /// Wrap with native counterpart.
+ @Native<Bool Function(Handle, Pointer<Void>, Int, Int)>(
+ symbol: 'InternalFlutterGpu_DeviceBuffer_Initialize')
+ external bool _initialize(
+ GpuContext gpuContext, int storageMode, int sizeInBytes);
+
+ /// Wrap with native counterpart.
+ @Native<Bool Function(Handle, Pointer<Void>, Handle)>(
+ symbol: 'InternalFlutterGpu_DeviceBuffer_InitializeWithHostData')
+ external bool _initializeWithHostData(GpuContext gpuContext, ByteData data);
+
+ /// Overwrite a range of bytes in the already created [DeviceBuffer].
+ ///
+ /// This method can only be used if the [DeviceBuffer] was created with
+ /// [StorageMode.hostVisible]. An exception will be thrown otherwise.
+ ///
+ /// 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
+ /// buffer, then the write will not be attempted and will fail.
+ ///
+ /// Returns [true] if the write was successful, or [false] if the write
+ /// failed due to an internal error.
+ bool overwrite(ByteData sourceBytes, {int destinationOffsetInBytes = 0}) {
+ if (storageMode != StorageMode.hostVisible) {
+ throw Exception(
+ 'DeviceBuffer.overwrite can only be used with DeviceBuffers that are host visible');
+ }
+ if (destinationOffsetInBytes < 0) {
+ throw Exception('destinationOffsetInBytes must be positive');
+ }
+ return _overwrite(sourceBytes, destinationOffsetInBytes);
+ }
+
+ @Native<Bool Function(Pointer<Void>, Handle, Int)>(
+ symbol: 'InternalFlutterGpu_DeviceBuffer_Overwrite')
+ external bool _overwrite(ByteData bytes, int destinationOffsetInBytes);
+}
+
/// [HostBuffer] is a [Buffer] which is allocated on the host (native CPU
/// resident memory) and lazily uploaded to the GPU. A [HostBuffer] can be
/// safely mutated or extended at any time on the host, and will be
@@ -35,7 +103,7 @@
/// Different platforms have different data alignment requirements for accessing
/// device buffer data. The [HostBuffer] takes these requirements into account
/// and automatically inserts padding between emplaced data if necessary.
-base class HostBuffer extends NativeFieldWrapperClass1 {
+base class HostBuffer extends NativeFieldWrapperClass1 with Buffer {
/// Creates a new HostBuffer.
HostBuffer() {
_initialize();
diff --git a/lib/gpu/lib/src/context.dart b/lib/gpu/lib/src/context.dart
index a0efc58..41ed410 100644
--- a/lib/gpu/lib/src/context.dart
+++ b/lib/gpu/lib/src/context.dart
@@ -2,8 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-import 'dart:ffi';
-import 'dart:nativewrappers';
+part of flutter_gpu;
/// A handle to a graphics context. Used to create and manage GPU resources.
///
@@ -18,6 +17,35 @@
}
}
+ /// Allocates a new region of GPU-resident memory.
+ ///
+ /// 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) {
+ if (storageMode == StorageMode.deviceTransient) {
+ throw Exception(
+ 'DeviceBuffers cannot be set to StorageMode.deviceTransient');
+ }
+ DeviceBuffer result =
+ DeviceBuffer._initialize(this, storageMode, sizeInBytes);
+ return result.isValid ? result : null;
+ }
+
+ /// Allocates a new region of host-visible GPU-resident memory, initialized
+ /// with the given [data].
+ ///
+ /// Given that the buffer will be immediately populated with [data] uploaded
+ /// 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) {
+ DeviceBuffer result = DeviceBuffer._initializeWithHostData(this, data);
+ return result.isValid ? result : null;
+ }
+
/// Associates the default Impeller context with this Context.
@Native<Handle Function(Handle)>(
symbol: 'InternalFlutterGpu_Context_InitializeDefault')
diff --git a/lib/gpu/lib/src/formats.dart b/lib/gpu/lib/src/formats.dart
new file mode 100644
index 0000000..0f0b6a37
--- /dev/null
+++ b/lib/gpu/lib/src/formats.dart
@@ -0,0 +1,28 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+part of flutter_gpu;
+
+/// Specifies where an allocation resides and how it may be used.
+enum StorageMode {
+ /// Allocations can be mapped onto the hosts address space and also be used by
+ /// the device.
+ hostVisible,
+
+ /// Allocations can only be used by the device. This location is optimal for
+ /// use by the device. If the host needs to access these allocations, the
+ /// data must first be copied into a host visible allocation.
+ devicePrivate,
+
+ /// Used by the device for temporary render targets. These allocations cannot
+ /// be copied to or from other allocations. This storage mode is only valid
+ /// for Textures.
+ ///
+ /// These allocations reside in tile memory which has higher bandwidth, lower
+ /// latency and lower power consumption. The total device memory usage is
+ /// also lower as a separate allocation does not need to be created in
+ /// device memory. Prefer using these allocations for intermediates like depth
+ /// and stencil buffers.
+ deviceTransient,
+}
diff --git a/lib/gpu/lib/src/smoketest.dart b/lib/gpu/lib/src/smoketest.dart
index 1a77d43..b7cebff 100644
--- a/lib/gpu/lib/src/smoketest.dart
+++ b/lib/gpu/lib/src/smoketest.dart
@@ -33,7 +33,7 @@
external void _constructor();
/// This is a method that will supply a pointer to the C data counterpart when
- /// calling the gunction
+ /// calling the function
@Native<Void Function(Pointer<Void>, Int)>(
symbol: 'InternalFlutterGpuTestClass_Method')
external void coolMethod(int something);