[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);