[graphite] Add Buffer and MtlBuffer classes.

Bug: skia:12466
Change-Id: I3252f36c3f78642c55f127379ceba99b4845822e
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/458717
Reviewed-by: Michael Ludwig <michaelludwig@google.com>
Reviewed-by: Jim Van Verth <jvanverth@google.com>
Commit-Queue: Greg Daniel <egdaniel@google.com>
diff --git a/experimental/graphite/include/private/GraphiteTypesPriv.h b/experimental/graphite/include/private/GraphiteTypesPriv.h
index 99f7f80..13ddf21 100644
--- a/experimental/graphite/include/private/GraphiteTypesPriv.h
+++ b/experimental/graphite/include/private/GraphiteTypesPriv.h
@@ -9,10 +9,19 @@
 #define skgpu_GraphiteTypesPriv_DEFINED
 
 #include "experimental/graphite/include/GraphiteTypes.h"
+#include "include/core/SkMath.h"
 
 namespace skgpu {
 
 /**
+ *  align up to a power of 2
+ */
+static inline constexpr size_t AlignTo(size_t x, size_t alignment) {
+    SkASSERT(alignment && SkIsPow2(alignment));
+    return (x + alignment - 1) & ~(alignment - 1);
+}
+
+/**
  * Is the Texture renderable or not
  */
 enum class Renderable : bool {
@@ -26,6 +35,28 @@
     kDepthStencil,
 };
 
+/**
+ * What a GPU buffer will be used for
+ */
+enum class BufferType {
+    kVertex,
+    kIndex,
+    kXferCpuToGpu,
+    kXferGpuToCpu,
+    kUniform,
+};
+static const int kBufferTypeCount = static_cast<int>(BufferType::kUniform) + 1;
+
+/**
+ * When creating the memory for a resource should we use a memory type that prioritizes the
+ * effeciency of GPU reads even if it involves extra work to write CPU data to it. For example, we
+ * would want this for buffers that we cache to read the same data many times on the GPU.
+ */
+enum class PrioritizeGpuReads : bool {
+    kNo = false,
+    kYes = true,
+};
+
 } // namespace skgpu
 
 #endif // skgpu_GraphiteTypesPriv_DEFINED
diff --git a/experimental/graphite/src/Buffer.cpp b/experimental/graphite/src/Buffer.cpp
new file mode 100644
index 0000000..fdca838
--- /dev/null
+++ b/experimental/graphite/src/Buffer.cpp
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2021 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "experimental/graphite/src/Buffer.h"
+
+namespace skgpu {
+
+void* Buffer::map() {
+    if (!this->isMapped()) {
+        this->onMap();
+    }
+    return fMapPtr;
+}
+
+void Buffer::unmap() {
+    SkASSERT(this->isMapped());
+    this->onUnmap();
+    fMapPtr = nullptr;
+}
+
+} // namespace skgpu
+
diff --git a/experimental/graphite/src/Buffer.h b/experimental/graphite/src/Buffer.h
new file mode 100644
index 0000000..edb142e
--- /dev/null
+++ b/experimental/graphite/src/Buffer.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2021 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef skgpu_Buffer_DEFINED
+#define skgpu_Buffer_DEFINED
+
+#include "experimental/graphite/include/private/GraphiteTypesPriv.h"
+#include "include/core/SkRefCnt.h"
+
+namespace skgpu {
+
+class Buffer : public SkRefCnt {
+public:
+    size_t size() const { return fSize; }
+
+    void* map();
+    void unmap();
+
+    bool isMapped() const { return fMapPtr; }
+
+protected:
+    Buffer(size_t size, BufferType type, PrioritizeGpuReads prioritizeGpuReads)
+        : fSize(size), fType(type), fPrioritizeGpuReads(prioritizeGpuReads) {}
+
+    void* fMapPtr = nullptr;
+
+private:
+    virtual void onMap() = 0;
+    virtual void onUnmap() = 0;
+
+    // TODO: Remove these getters once we start using fType and fPrioritizeGpuReads in key
+    // generation. For now this silences compiler unused member warnings.
+    BufferType bufferType() const { return fType; }
+    PrioritizeGpuReads prioritizeGpuReads() const { return fPrioritizeGpuReads; }
+
+    size_t             fSize;
+    BufferType         fType;
+    PrioritizeGpuReads fPrioritizeGpuReads;
+};
+
+} // namespace skgpu
+
+#endif // skgpu_Buffer_DEFINED
+
diff --git a/experimental/graphite/src/mtl/MtlBuffer.h b/experimental/graphite/src/mtl/MtlBuffer.h
new file mode 100644
index 0000000..d110961
--- /dev/null
+++ b/experimental/graphite/src/mtl/MtlBuffer.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2021 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef skgpu_MtlBuffer_DEFINED
+#define skgpu_MtlBuffer_DEFINED
+
+#include "experimental/graphite/src/Buffer.h"
+
+#include "experimental/graphite/include/mtl/MtlTypes.h"
+
+#import <Metal/Metal.h>
+
+namespace skgpu::mtl {
+
+class Gpu;
+
+class Buffer : public skgpu::Buffer {
+public:
+    sk_sp<Buffer> Make(const Gpu*, size_t size, BufferType type, PrioritizeGpuReads);
+
+private:
+    Buffer(size_t size, BufferType type, PrioritizeGpuReads, sk_cfp<id<MTLBuffer>>);
+
+    void onMap() override;
+    void onUnmap() override;
+
+    sk_cfp<id<MTLBuffer>> fBuffer;
+};
+
+} // namespace skgpu::mtl
+
+#endif // skgpu_MtlBuffer_DEFINED
+
diff --git a/experimental/graphite/src/mtl/MtlBuffer.mm b/experimental/graphite/src/mtl/MtlBuffer.mm
new file mode 100644
index 0000000..05ff8a5
--- /dev/null
+++ b/experimental/graphite/src/mtl/MtlBuffer.mm
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2021 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "experimental/graphite/src/mtl/MtlBuffer.h"
+
+#include "experimental/graphite/src/mtl/MtlGpu.h"
+
+namespace skgpu::mtl {
+
+#ifdef SK_ENABLE_MTL_DEBUG_INFO
+NSString* kBufferTypeNames[kBufferTypeCount] = {
+    @"Vertex",
+    @"Index",
+    @"Xfer CPU to GPU",
+    @"Xfer GPU to CPU",
+    @"Uniform",
+};
+#endif
+
+sk_sp<Buffer> Buffer::Make(const Gpu* gpu,
+                           size_t size,
+                           BufferType type,
+                           PrioritizeGpuReads prioritizeGpuReads) {
+    if (size <= 0) {
+        return nullptr;
+    }
+
+    const Caps& mtlCaps = gpu->mtlCaps();
+
+    NSUInteger options = 0;
+    if (@available(macOS 10.11, iOS 9.0, *)) {
+        if (prioritizeGpuReads == PrioritizeGpuReads::kNo) {
+#ifdef SK_BUILD_FOR_MAC
+            if (mtlCaps.isMac()) {
+                options |= MTLResourceStorageModeManaged;
+            } else {
+                SkASSERT(mtlCaps.isApple());
+                options |= MTLResourceStorageModeShared;
+            }
+#else
+            options |= MTLResourceStorageModeShared;
+#endif
+        } else {
+            options |= MTLResourceStorageModePrivate;
+        }
+    }
+
+    size = AlignTo(size, mtlCaps.getMinBufferAlignment());
+    sk_cfp<id<MTLBuffer>> buffer([gpu->device() newBufferWithLength: size options: options]);
+#ifdef SK_ENABLE_MTL_DEBUG_INFO
+    (*buffer).label = kBufferTypeNames[(int)type];
+#endif
+
+    return sk_sp<Buffer>(new Buffer(size, type, prioritizeGpuReads, std::move(buffer)));
+}
+
+Buffer::Buffer(size_t size,
+               BufferType type,
+               PrioritizeGpuReads prioritizeGpuReads,
+               sk_cfp<id<MTLBuffer>> buffer)
+        : skgpu::Buffer(size, type, prioritizeGpuReads)
+        , fBuffer(std::move(buffer)) {}
+
+void Buffer::onMap() {
+    SkASSERT(fBuffer);
+    SkASSERT(!this->isMapped());
+
+    if ((*fBuffer).storageMode == MTLStorageModePrivate) {
+        return;
+    }
+
+    fMapPtr = static_cast<char*>((*fBuffer).contents);
+}
+
+void Buffer::onUnmap() {
+    SkASSERT(fBuffer);
+    SkASSERT(this->isMapped());
+#ifdef SK_BUILD_FOR_MAC
+    if ((*fBuffer).storageMode == MTLStorageModeManaged) {
+        [*fBuffer didModifyRange: NSMakeRange(0, this->size())];
+    }
+#endif
+    fMapPtr = nullptr;
+}
+
+} // namespace skgpu::mtl
+
diff --git a/experimental/graphite/src/mtl/MtlCaps.h b/experimental/graphite/src/mtl/MtlCaps.h
index 3db340c..296e77d 100644
--- a/experimental/graphite/src/mtl/MtlCaps.h
+++ b/experimental/graphite/src/mtl/MtlCaps.h
@@ -29,6 +29,11 @@
                                                          uint32_t sampleCount,
                                                          Protected) override;
 
+    bool isMac() const { return fGPUFamily == GPUFamily::kMac; }
+    bool isApple()const  { return fGPUFamily == GPUFamily::kApple; }
+
+    size_t getMinBufferAlignment() const { return this->isMac() ? 4 : 1; }
+
 private:
     void initGPUFamily(const id<MTLDevice>);
 
@@ -40,8 +45,6 @@
         kMac,
         kApple,
     };
-    bool isMac() const { return fGPUFamily == GPUFamily::kMac; }
-    bool isApple()const  { return fGPUFamily == GPUFamily::kApple; }
     static bool GetGPUFamily(id<MTLDevice> device, GPUFamily* gpuFamily, int* group);
     static bool GetGPUFamilyFromFeatureSet(id<MTLDevice> device, GPUFamily* gpuFamily,
                                            int* group);
diff --git a/gn/graphite.gni b/gn/graphite.gni
index 0116a34..d3dfb47 100644
--- a/gn/graphite.gni
+++ b/gn/graphite.gni
@@ -17,6 +17,8 @@
   "$_include/SkStuff.h",
   "$_include/TextureInfo.h",
   "$_include/private/GraphiteTypesPriv.h",
+  "$_src/Buffer.cpp",
+  "$_src/Buffer.h",
   "$_src/Caps.cpp",
   "$_src/Caps.h",
   "$_src/CommandBuffer.cpp",
@@ -69,6 +71,8 @@
   "$_include/mtl/MtlBackendContext.h",
   "$_include/mtl/MtlTypes.h",
   "$_include/private/MtlTypesPriv.h",
+  "$_src/mtl/MtlBuffer.h",
+  "$_src/mtl/MtlBuffer.mm",
   "$_src/mtl/MtlCaps.h",
   "$_src/mtl/MtlCaps.mm",
   "$_src/mtl/MtlCommandBuffer.h",