[graphite] Create & cache descriptor sets

* Move static ds_type_enum_to_vk_ds function to be a static member of VulkanDescriptorSet.h for better accessibility

* Flesh out VulkanDescriptorSet class

* Add VulkanResourceProvider::findOrCreateDescriptorSet which takes in a span of descriptor types & counts

Change-Id: Ide6d1954e3932948a81a1d7ea49ebeebbe5259c1
Bug: b/274762860
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/691516
Commit-Queue: Nicolette Prevost <nicolettep@google.com>
Reviewed-by: Jim Van Verth <jvanverth@google.com>
diff --git a/src/gpu/graphite/vk/VulkanDescriptorPool.cpp b/src/gpu/graphite/vk/VulkanDescriptorPool.cpp
index 3462387..d2ec165 100644
--- a/src/gpu/graphite/vk/VulkanDescriptorPool.cpp
+++ b/src/gpu/graphite/vk/VulkanDescriptorPool.cpp
@@ -13,26 +13,6 @@
 
 namespace skgpu::graphite {
 
-namespace { // anonymous namespace
-static VkDescriptorType ds_type_enum_to_vk_ds(DescriptorType type) {
-    switch (type) {
-        case DescriptorType::kUniformBuffer:
-            return VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
-        case DescriptorType::kTextureSampler:
-            return VK_DESCRIPTOR_TYPE_SAMPLER;
-        case DescriptorType::kTexture:
-            return VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE;
-        case DescriptorType::kCombinedTextureSampler:
-            return VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
-        case DescriptorType::kStorageBuffer:
-            return VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
-        case DescriptorType::kInputAttachment:
-            return VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT;
-    }
-    SkUNREACHABLE;
-}
-} // anonymous namespace
-
 sk_sp<VulkanDescriptorPool> VulkanDescriptorPool::Make(
         const VulkanSharedContext* context,
         SkSpan<DescTypeAndCount> requestedDescCounts) {
@@ -42,7 +22,13 @@
     }
 
     // For each requested descriptor type and count, create a VkDescriptorPoolSize struct which
-    // specifies the descriptor type and quantity for pool creation.
+    // specifies the descriptor type and quantity for pool creation. Multiple pool size structures
+    // may contain the same descriptor type - the pool will be created with enough storage for the
+    // total number of descriptors of each type. Source:
+    // https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#VkDescriptorPoolSize
+    // Note: The kMaxNumDescriptors limit could be evaded since we do not currently track and check
+    // the cumulative quantities of each type of descriptor, but this is an internal call and it is
+    // highly unexpected for us to exceed this limit in practice.
     skia_private::STArray<kDescriptorTypeCount, VkDescriptorPoolSize> poolSizes;
     for (size_t i = 0; i < requestedDescCounts.size(); i++) {
         SkASSERT(requestedDescCounts[i].count > 0);
@@ -55,7 +41,7 @@
         VkDescriptorPoolSize* poolSize = &poolSizes.at(i);
         memset(poolSize, 0, sizeof(VkDescriptorPoolSize));
         // Map each DescriptorSetType to the appropriate backend VkDescriptorType
-        poolSize->type = ds_type_enum_to_vk_ds(requestedDescCounts[i].type);
+        poolSize->type = VulkanDescriptorSet::DsTypeEnumToVkDs(requestedDescCounts[i].type);
         // Create a pool large enough to accommodate the maximum possible number of descriptor sets
         poolSize->descriptorCount = requestedDescCounts[i].count * kMaxNumSets;
     }
diff --git a/src/gpu/graphite/vk/VulkanDescriptorSet.cpp b/src/gpu/graphite/vk/VulkanDescriptorSet.cpp
index 45b2489..62ee831 100644
--- a/src/gpu/graphite/vk/VulkanDescriptorSet.cpp
+++ b/src/gpu/graphite/vk/VulkanDescriptorSet.cpp
@@ -12,12 +12,60 @@
 
 namespace skgpu::graphite {
 
+sk_sp<VulkanDescriptorSet> VulkanDescriptorSet::Make(const VulkanSharedContext* ctxt,
+                                                     sk_sp<VulkanDescriptorPool> pool,
+                                                     const VkDescriptorSetLayout* layout) {
+    VkDescriptorSet descSet;
+
+    VkDescriptorSetAllocateInfo dsAllocateInfo;
+    memset(&dsAllocateInfo, 0, sizeof(VkDescriptorSetAllocateInfo));
+    dsAllocateInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
+    dsAllocateInfo.pNext = nullptr;
+    dsAllocateInfo.descriptorPool = *(pool->descPool());
+    dsAllocateInfo.descriptorSetCount = VulkanDescriptorPool::kMaxNumSets;
+    dsAllocateInfo.pSetLayouts = layout;
+
+    VkResult result;
+    VULKAN_CALL_RESULT(ctxt->interface(),
+                       result,
+                       AllocateDescriptorSets(ctxt->device(),
+                                              &dsAllocateInfo,
+                                              &descSet));
+    if (result != VK_SUCCESS) {
+        return nullptr;
+    }
+    return sk_sp<VulkanDescriptorSet>(new VulkanDescriptorSet(ctxt, descSet, pool));
+}
+
+VkDescriptorType VulkanDescriptorSet::DsTypeEnumToVkDs(DescriptorType type) {
+    switch (type) {
+        case DescriptorType::kUniformBuffer:
+            return VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
+        case DescriptorType::kTextureSampler:
+            return VK_DESCRIPTOR_TYPE_SAMPLER;
+        case DescriptorType::kTexture:
+            return VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE;
+        case DescriptorType::kCombinedTextureSampler:
+            return VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
+        case DescriptorType::kStorageBuffer:
+            return VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
+        case DescriptorType::kInputAttachment:
+            return VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT;
+    }
+    SkUNREACHABLE;
+}
+
 VulkanDescriptorSet::VulkanDescriptorSet(const VulkanSharedContext* ctxt,
+                                         VkDescriptorSet set,
                                          sk_sp<VulkanDescriptorPool> pool)
         : Resource(ctxt, Ownership::kOwned, skgpu::Budgeted::kNo, /*gpuMemorySize=*/0)
-        , fPool (pool)
-        , fIsAvailable (true) {
+        , fDescSet (set)
+        , fPool (pool) {
     fPool->ref();
 }
 
+void VulkanDescriptorSet::freeGpuData() {
+    fPool->unref();
+}
+
 } // namespace skgpu::graphite
diff --git a/src/gpu/graphite/vk/VulkanDescriptorSet.h b/src/gpu/graphite/vk/VulkanDescriptorSet.h
index bc4b89b..23bb895 100644
--- a/src/gpu/graphite/vk/VulkanDescriptorSet.h
+++ b/src/gpu/graphite/vk/VulkanDescriptorSet.h
@@ -10,6 +10,7 @@
 
 #include "src/gpu/graphite/Resource.h"
 
+#include "src/gpu/graphite/Descriptors.h"
 #include "src/gpu/graphite/vk/VulkanGraphiteUtilsPriv.h"
 
 namespace skgpu::graphite {
@@ -20,33 +21,31 @@
 /**
  * Wrapper around VkDescriptorSet which maintains a reference to its descriptor pool. Once the ref
  * count on that pool is 0, it will be destroyed.
+ *
+ * TODO: Track whether a descriptor set is available for use or if it is already in use elsewhere.
 */
 class VulkanDescriptorSet : public Resource {
 public:
-    VulkanDescriptorSet(const VulkanSharedContext*, sk_sp<VulkanDescriptorPool>);
+    static sk_sp<VulkanDescriptorSet> Make(const VulkanSharedContext*,
+                                           sk_sp<VulkanDescriptorPool>,
+                                           const VkDescriptorSetLayout*);
+
+    static VkDescriptorType DsTypeEnumToVkDs(DescriptorType type);
+
+    VulkanDescriptorSet(const VulkanSharedContext*, VkDescriptorSet, sk_sp<VulkanDescriptorPool>);
 
     VkDescriptorSetLayout layout() const { return fDescLayout; }
 
-    bool isAvailable() const { return fIsAvailable; }
-
-    VkDescriptorSet descriptorSet() {
-        SkASSERT(fIsAvailable);
-        fIsAvailable = false;
-        return fDescSet;
-    }
-
-    void setAvailability(bool isAvailable) {
-        fIsAvailable = isAvailable;
-    }
+    VkDescriptorSet descriptorSet() { return fDescSet; }
 
 private:
-    void freeGpuData() override {
-        //TODO: Implement.
-    }
+    void freeGpuData() override;
 
     VkDescriptorSet             fDescSet;
+    // Have this class hold on to a reference of the descriptor pool. When a pool's reference count
+    // is 0, that means all the descriptor sets that came from that pool are no longer needed, so
+    // the pool can safely be destroyed.
     sk_sp<VulkanDescriptorPool> fPool;
-    bool                        fIsAvailable;
     VkDescriptorSetLayout       fDescLayout;
 };
 } // namespace skgpu::graphite
diff --git a/src/gpu/graphite/vk/VulkanResourceProvider.cpp b/src/gpu/graphite/vk/VulkanResourceProvider.cpp
index 97eaa6e..f2ee7f4 100644
--- a/src/gpu/graphite/vk/VulkanResourceProvider.cpp
+++ b/src/gpu/graphite/vk/VulkanResourceProvider.cpp
@@ -7,6 +7,7 @@
 
 #include "src/gpu/graphite/vk/VulkanResourceProvider.h"
 
+#include "include/core/SkSpan.h"
 #include "include/gpu/graphite/BackendTexture.h"
 #include "src/gpu/graphite/Buffer.h"
 #include "src/gpu/graphite/ComputePipeline.h"
@@ -15,6 +16,8 @@
 #include "src/gpu/graphite/Texture.h"
 #include "src/gpu/graphite/vk/VulkanBuffer.h"
 #include "src/gpu/graphite/vk/VulkanCommandBuffer.h"
+#include "src/gpu/graphite/vk/VulkanDescriptorPool.h"
+#include "src/gpu/graphite/vk/VulkanDescriptorSet.h"
 #include "src/gpu/graphite/vk/VulkanGraphicsPipeline.h"
 #include "src/gpu/graphite/vk/VulkanSampler.h"
 #include "src/gpu/graphite/vk/VulkanSharedContext.h"
@@ -22,6 +25,54 @@
 
 namespace skgpu::graphite {
 
+namespace { // Anonymous namespace
+
+VkDescriptorSetLayout desc_type_count_to_desc_set_layout(
+        const VulkanSharedContext* ctxt,
+        SkSpan<DescTypeAndCount> requestedDescriptors) {
+
+    VkDescriptorSetLayout layout;
+    skia_private::STArray<kDescriptorTypeCount, VkDescriptorSetLayoutBinding> bindingLayouts;
+
+    for (size_t i = 0, j = 0; i < requestedDescriptors.size(); i++) {
+        if (requestedDescriptors[i].count != 0) {
+            VkDescriptorSetLayoutBinding* layoutBinding = &bindingLayouts.at(j++);
+            memset(layoutBinding, 0, sizeof(VkDescriptorSetLayoutBinding));
+            layoutBinding->binding = 0;
+            layoutBinding->descriptorType =
+                    VulkanDescriptorSet::DsTypeEnumToVkDs(requestedDescriptors[i].type);
+            layoutBinding->descriptorCount = requestedDescriptors[i].count;
+            // TODO: Obtain layout binding stage flags from visibility (vertex or shader)
+            layoutBinding->stageFlags = 0;
+            // TODO: Optionally set immutableSamplers here.
+            layoutBinding->pImmutableSamplers = nullptr;
+        }
+    }
+
+    VkDescriptorSetLayoutCreateInfo layoutCreateInfo;
+    memset(&layoutCreateInfo, 0, sizeof(VkDescriptorSetLayoutCreateInfo));
+    layoutCreateInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
+    layoutCreateInfo.pNext = nullptr;
+    layoutCreateInfo.flags = 0;
+    layoutCreateInfo.bindingCount = bindingLayouts.size();
+    layoutCreateInfo.pBindings = &bindingLayouts.front();
+
+    VkResult result;
+    VULKAN_CALL_RESULT(ctxt->interface(),
+                       result,
+                       CreateDescriptorSetLayout(ctxt->device(),
+                                                 &layoutCreateInfo,
+                                                 nullptr,
+                                                 &layout));
+    if (result != VK_SUCCESS) {
+        SkDebugf("Failed to create VkDescriptorSetLayout\n");
+        layout = VK_NULL_HANDLE;
+    }
+    return layout;
+}
+
+} // Anonymous namespace
+
 VulkanResourceProvider::VulkanResourceProvider(SharedContext* sharedContext,
                                                SingleOwner* singleOwner,
                                                uint32_t recorderID)
@@ -72,4 +123,68 @@
     return {};
 }
 
+VulkanDescriptorSet* VulkanResourceProvider::findOrCreateDescriptorSet(
+        SkSpan<DescTypeAndCount> requestedDescriptors) {
+    GraphiteResourceKey key;
+    // TODO(nicolettep): Optimize key structure. It is horrendously inefficient but functional.
+    // Fow now, have each descriptor type and quantity take up an entire uint32_t, with an
+    // additional uint32_t added to include a unique identifier for different descriptor sets that
+    // have the same set layout.
+    static const int kNum32DataCnt = (kDescriptorTypeCount * 2) + 1;
+    static const ResourceType kType = GraphiteResourceKey::GenerateResourceType();
+
+    Resource* descSet = nullptr;
+    // Search for available descriptor sets by assembling the last part of the key with a unique set
+    // ID (which ranges from 0 to kMaxNumSets - 1). Start the search at 0 and continue until an
+    // available set is found.
+    // TODO(nicolettep): Explore ways to optimize this traversal.
+    for (uint32_t i = 0; i < VulkanDescriptorPool::kMaxNumSets; i++) {
+        GraphiteResourceKey::Builder builder(&key, kType, kNum32DataCnt, Shareable::kNo);
+        // Assemble the base component of a descriptor set key which is determined by the type and
+        // quantity of requested descriptors.
+        for (size_t j = 0, k = 0; k < requestedDescriptors.size(); j = j + 2, k++) {
+            builder[j+1] = static_cast<uint32_t>(requestedDescriptors[k].type);
+            builder[j] = requestedDescriptors[k].count;
+        }
+        builder[kNum32DataCnt - 1] = i;
+        builder.finish();
+
+        if ((descSet = fResourceCache->findAndRefResource(key, skgpu::Budgeted::kNo))) {
+            // A non-null resource pointer indicates we have found an available descriptor set.
+            return static_cast<VulkanDescriptorSet*>(descSet);
+        }
+        key.reset();
+    }
+
+    // If we did not find an existing avilable desc set, allocate sets with the appropriate layout
+    // and add them to the cache.
+    auto pool = VulkanDescriptorPool::Make(this->vulkanSharedContext(), requestedDescriptors);
+    VkDescriptorSetLayout layout = desc_type_count_to_desc_set_layout(
+            this->vulkanSharedContext(),
+            requestedDescriptors);
+
+    // Store the key of the first descriptor set so it can be easily accessed later.
+    GraphiteResourceKey firstSetKey;
+    // Allocate the maximum number of sets so they can be easily accessed as needed from the cache.
+    for (int i = 0; i < VulkanDescriptorPool::kMaxNumSets ; i++) {
+        GraphiteResourceKey::Builder builder(&key, kType, kNum32DataCnt, Shareable::kNo);
+        descSet = VulkanDescriptorSet::Make(this->vulkanSharedContext(), pool, &layout).get();
+        // Assemble the base component of a descriptor set key which is determined by the type and
+        // quantity of requested descriptors.
+        for (size_t j = 0, k = 0; k < requestedDescriptors.size(); j = j + 2, k++) {
+            builder[j+1] = static_cast<uint32_t>(requestedDescriptors[k].type);
+            builder[j] = requestedDescriptors[k].count;
+        }
+        builder[kNum32DataCnt - 1] = i;
+        builder.finish();
+        descSet->setKey(key);
+        fResourceCache->insertResource(descSet);
+        if (i == 0) {
+            firstSetKey = key;
+        }
+        key.reset();
+    }
+    descSet = fResourceCache->findAndRefResource(firstSetKey, skgpu::Budgeted::kNo);
+    return descSet ? static_cast<VulkanDescriptorSet*>(descSet) : nullptr;
+}
 } // namespace skgpu::graphite
diff --git a/src/gpu/graphite/vk/VulkanResourceProvider.h b/src/gpu/graphite/vk/VulkanResourceProvider.h
index c5815d4..b2b3265 100644
--- a/src/gpu/graphite/vk/VulkanResourceProvider.h
+++ b/src/gpu/graphite/vk/VulkanResourceProvider.h
@@ -11,10 +11,12 @@
 #include "src/gpu/graphite/ResourceProvider.h"
 
 #include "include/gpu/vk/VulkanTypes.h"
+#include "src/gpu/graphite/Descriptors.h"
 
 namespace skgpu::graphite {
 
 class VulkanCommandBuffer;
+class VulkanDescriptorSet;
 class VulkanSharedContext;
 
 class VulkanResourceProvider final : public ResourceProvider {
@@ -41,6 +43,8 @@
 
     BackendTexture onCreateBackendTexture(SkISize dimensions, const TextureInfo&) override;
     void onDeleteBackendTexture(BackendTexture&) override {}
+
+    VulkanDescriptorSet* findOrCreateDescriptorSet(SkSpan<DescTypeAndCount>);
 };
 
 } // namespace skgpu::graphite