Merge "Snapshot descriptor set contents" into main
diff --git a/common/end2end/GfxstreamEnd2EndVkSnapshotPipelineTests.cpp b/common/end2end/GfxstreamEnd2EndVkSnapshotPipelineTests.cpp
index b9cc69f..09943bf 100644
--- a/common/end2end/GfxstreamEnd2EndVkSnapshotPipelineTests.cpp
+++ b/common/end2end/GfxstreamEnd2EndVkSnapshotPipelineTests.cpp
@@ -12,6 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include <android-base/expected.h>
+
 #include <string>
 
 #include "GfxstreamEnd2EndTestUtils.h"
@@ -46,12 +48,21 @@
     vkhpp::UniqueImageView imageView;
 };
 
+struct BufferInfo {
+    vkhpp::UniqueBuffer buffer;
+    vkhpp::UniqueDeviceMemory memory;
+};
+
 class GfxstreamEnd2EndVkSnapshotPipelineTest : public GfxstreamEnd2EndTest {
    protected:
     vkhpp::UniqueRenderPass createRenderPass(vkhpp::Device device);
     std::unique_ptr<ImageInfo> createColorAttachment(vkhpp::PhysicalDevice physicalDevice,
                                                      vkhpp::Device device);
     std::unique_ptr<PipelineInfo> createPipeline(vkhpp::Device device);
+    VkExpected<BufferInfo> createAndPopulateBuffer(vkhpp::PhysicalDevice physicalDevice,
+                                                   vkhpp::Device device,
+                                                   vkhpp::BufferUsageFlags usage, const void* data,
+                                                   uint64_t dataSize);
     static const uint32_t kFbWidth = 32;
     static const uint32_t kFbHeight = 32;
 };
@@ -65,12 +76,56 @@
         std::chrono::duration_cast<std::chrono::nanoseconds>(duration).count());
 }
 
-// A blue triangle
-const float kVertexData[] = {
-    -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f,
-    0.0f,  0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f,
+// Full screen blue rectangle
+const float kFullscreenBlueRectangleVertexData[] = {
+    // clang-format off
+    /*pos=*/ -1.0f, -1.0f, 0.0f, 1.0f, /*color=*/ 0.0f,  0.0f,  1.0f, 1.0f,
+    /*pos=*/  1.0f, -1.0f, 0.0f, 1.0f, /*color=*/ 0.0f,  0.0f,  1.0f, 1.0f,
+    /*pos=*/  1.0f,  1.0f, 0.0f, 1.0f, /*color=*/ 0.0f,  0.0f,  1.0f, 1.0f,
+    /*pos=*/  1.0f,  1.0f, 0.0f, 1.0f, /*color=*/ 0.0f,  0.0f,  1.0f, 1.0f,
+    /*pos=*/ -1.0f,  1.0f, 0.0f, 1.0f, /*color=*/ 0.0f,  0.0f,  1.0f, 1.0f,
+    /*pos=*/ -1.0f, -1.0f, 0.0f, 1.0f, /*color=*/ 0.0f,  0.0f,  1.0f, 1.0f,
+    // clang-format on
 };
 
+VkExpected<BufferInfo> GfxstreamEnd2EndVkSnapshotPipelineTest::createAndPopulateBuffer(
+    vkhpp::PhysicalDevice physicalDevice, vkhpp::Device device, vkhpp::BufferUsageFlags usage,
+    const void* data, uint64_t dataSize) {
+    const vkhpp::BufferCreateInfo vertexBufferCreateInfo = {
+        .size = dataSize,
+        .usage = usage,
+        .sharingMode = vkhpp::SharingMode::eExclusive,
+    };
+    auto vertexBuffer = VK_EXPECT_RV(device.createBufferUnique(vertexBufferCreateInfo));
+
+    vkhpp::MemoryRequirements vertexBufferMemoryRequirements{};
+    device.getBufferMemoryRequirements(*vertexBuffer, &vertexBufferMemoryRequirements);
+
+    const auto vertexBufferMemoryType = utils::getMemoryType(
+        physicalDevice, vertexBufferMemoryRequirements,
+        vkhpp::MemoryPropertyFlagBits::eHostVisible | vkhpp::MemoryPropertyFlagBits::eHostCoherent);
+    if (vertexBufferMemoryType == -1) {
+        return android::base::unexpected(vkhpp::Result::eErrorOutOfHostMemory);
+    }
+    // Vertex memory
+    const vkhpp::MemoryAllocateInfo vertexBufferMemoryAllocateInfo = {
+        .allocationSize = vertexBufferMemoryRequirements.size,
+        .memoryTypeIndex = vertexBufferMemoryType,
+    };
+    auto vertexBufferMemory =
+        VK_EXPECT_RV(device.allocateMemoryUnique(vertexBufferMemoryAllocateInfo));
+    device.bindBufferMemory(*vertexBuffer, *vertexBufferMemory, 0);
+    void* mapped;
+    device.mapMemory(*vertexBufferMemory, 0, VK_WHOLE_SIZE, vkhpp::MemoryMapFlags{}, &mapped);
+    memcpy(mapped, data, dataSize);
+    device.unmapMemory(*vertexBufferMemory);
+
+    BufferInfo res;
+    res.buffer = std::move(vertexBuffer);
+    res.memory = std::move(vertexBufferMemory);
+    return res;
+}
+
 vkhpp::UniqueRenderPass GfxstreamEnd2EndVkSnapshotPipelineTest::createRenderPass(
     vkhpp::Device device) {
     vkhpp::AttachmentDescription colorAttachmentDescription = {
@@ -103,11 +158,26 @@
     std::unique_ptr<PipelineInfo> res(new PipelineInfo);
     res->renderPass = createRenderPass(device);
 
-    vkhpp::DescriptorSetLayoutCreateInfo descriptorSetLayoutInfo = {};
+    vkhpp::DescriptorSetLayoutBinding bindings[1] = {
+        {
+            .binding = 0,
+            .descriptorType = vkhpp::DescriptorType::eUniformBuffer,
+            .descriptorCount = 1,
+            .stageFlags = vkhpp::ShaderStageFlagBits::eFragment,
+        },
+    };
+    vkhpp::DescriptorSetLayoutCreateInfo descriptorSetLayoutInfo = {
+        .bindingCount = 1,
+        .pBindings = bindings,
+    };
     res->descriptorSetLayout =
         device.createDescriptorSetLayoutUnique(descriptorSetLayoutInfo).value;
-    res->pipelineLayout =
-        device.createPipelineLayoutUnique(vkhpp::PipelineLayoutCreateInfo{}).value;
+    res->pipelineLayout = device
+                              .createPipelineLayoutUnique(vkhpp::PipelineLayoutCreateInfo{
+                                  .setLayoutCount = 1,
+                                  .pSetLayouts = &res->descriptorSetLayout.get(),
+                              })
+                              .value;
 
     vkhpp::ShaderModuleCreateInfo vertexShaderModuleCreateInfo = {
         .codeSize = sizeof(kSimpleShaderVert),
@@ -361,11 +431,7 @@
 
 TEST_P(GfxstreamEnd2EndVkSnapshotPipelineWithMultiSamplingTest, CanSubmitQueue) {
     TypicalVkTestEnvironment testEnvironment = VK_ASSERT(SetUpTypicalVkTestEnvironment());
-    auto& instance = testEnvironment.instance;
-    auto& physicalDevice = testEnvironment.physicalDevice;
-    auto& device = testEnvironment.device;
-    auto& queue = testEnvironment.queue;
-    auto queueFamilyIndex = testEnvironment.queueFamilyIndex;
+    auto& [instance, physicalDevice, device, queue, queueFamilyIndex] = testEnvironment;
 
     auto pipelineInfo = createPipeline(device.get());
 
@@ -489,6 +555,10 @@
     waitResult = device->waitForFences(*fence, VK_TRUE, 3000000000L);
     ASSERT_THAT(waitResult, IsVkSuccess());
 
+    if (GetParam().samples != 1) {
+        return;
+    }
+
     std::vector<uint32_t> dst(kFbWidth * kFbHeight);
     utils::readImageData(*colorAttachmentInfo->image, kFbWidth, kFbHeight,
                          vkhpp::ImageLayout::eColorAttachmentOptimal, dst.data(),
@@ -498,6 +568,395 @@
     }
 }
 
+TEST_P(GfxstreamEnd2EndVkSnapshotPipelineTest, CanSnapshotDescriptors) {
+    TypicalVkTestEnvironment testEnvironment = VK_ASSERT(SetUpTypicalVkTestEnvironment());
+    auto& [instance, physicalDevice, device, queue, queueFamilyIndex] = testEnvironment;
+
+    auto pipelineInfo = createPipeline(device.get());
+    auto vertexBufferInfo = createAndPopulateBuffer(
+        physicalDevice, device.get(), vkhpp::BufferUsageFlagBits::eVertexBuffer,
+        kFullscreenBlueRectangleVertexData, sizeof(kFullscreenBlueRectangleVertexData));
+
+    auto colorAttachmentInfo = createColorAttachment(physicalDevice, device.get());
+    ASSERT_THAT(colorAttachmentInfo->image, IsValidHandle());
+    ASSERT_THAT(colorAttachmentInfo->memory, IsValidHandle());
+    ASSERT_THAT(colorAttachmentInfo->imageView, IsValidHandle());
+
+    // Descriptor
+    std::vector<vkhpp::DescriptorPoolSize> sizes = {
+        {
+            .type = vkhpp::DescriptorType::eUniformBuffer,
+            .descriptorCount = 10,
+        },
+    };
+    vkhpp::DescriptorPoolCreateInfo descriptorPoolCreateInfo = {
+        .maxSets = 10,
+        .poolSizeCount = static_cast<uint32_t>(sizes.size()),
+        .pPoolSizes = sizes.data(),
+    };
+    auto descriptorPool = device->createDescriptorPoolUnique(descriptorPoolCreateInfo).value;
+    ASSERT_THAT(descriptorPool, IsValidHandle());
+
+    const std::vector<vkhpp::DescriptorSetLayout> descriptorSetLayouts(
+        1, *pipelineInfo->descriptorSetLayout);
+
+    vkhpp::DescriptorSetAllocateInfo descriptorSetAllocateInfo = {
+        .descriptorPool = *descriptorPool,
+        .descriptorSetCount = 1,
+        .pSetLayouts = descriptorSetLayouts.data(),
+    };
+    auto descriptorSets = device->allocateDescriptorSetsUnique(descriptorSetAllocateInfo);
+    EXPECT_THAT(descriptorSets.result, Eq(vkhpp::Result::eSuccess));
+    auto descriptorSet = *descriptorSets.value[0];
+
+    // A uniform for red color
+    float kColor1[] = {1.0f, 0.0f, 0.0f, 0.0f};
+    auto uniformBufferInfo = createAndPopulateBuffer(physicalDevice, device.get(),
+                                                     vkhpp::BufferUsageFlagBits::eUniformBuffer,
+                                                     kColor1, sizeof(kColor1));
+
+    std::vector<vkhpp::WriteDescriptorSet> writeDescriptorSets;
+    std::vector<vkhpp::DescriptorBufferInfo> bufferInfos;
+    bufferInfos.emplace_back(vkhpp::DescriptorBufferInfo{
+        .buffer = *uniformBufferInfo->buffer,
+        .offset = 0,
+        .range = VK_WHOLE_SIZE,
+    });
+    writeDescriptorSets.emplace_back(vkhpp::WriteDescriptorSet{
+        .dstSet = descriptorSet,
+        .dstBinding = 0,
+        .descriptorCount = 1,
+        .descriptorType = vkhpp::DescriptorType::eUniformBuffer,
+        .pBufferInfo = bufferInfos.data(),
+    });
+    device->updateDescriptorSets(writeDescriptorSets, nullptr);
+
+    const std::vector<vkhpp::ImageView> attachments(1, *colorAttachmentInfo->imageView);
+    vkhpp::FramebufferCreateInfo framebufferCreateInfo = {
+        .renderPass = *pipelineInfo->renderPass,
+        .attachmentCount = 1,
+        .pAttachments = attachments.data(),
+        .width = kFbWidth,
+        .height = kFbHeight,
+        .layers = 1,
+    };
+    auto framebuffer = device->createFramebufferUnique(framebufferCreateInfo).value;
+    ASSERT_THAT(framebuffer, IsValidHandle());
+
+    auto fence = device->createFenceUnique(vkhpp::FenceCreateInfo()).value;
+    ASSERT_THAT(fence, IsValidHandle());
+
+    const vkhpp::CommandPoolCreateInfo commandPoolCreateInfo = {
+        .flags = vkhpp::CommandPoolCreateFlagBits::eResetCommandBuffer,
+        .queueFamilyIndex = queueFamilyIndex,
+    };
+
+    auto commandPool = device->createCommandPoolUnique(commandPoolCreateInfo).value;
+    ASSERT_THAT(commandPool, IsValidHandle());
+
+    const vkhpp::CommandBufferAllocateInfo commandBufferAllocateInfo = {
+        .level = vkhpp::CommandBufferLevel::ePrimary,
+        .commandPool = *commandPool,
+        .commandBufferCount = 1,
+    };
+
+    auto commandBuffers = device->allocateCommandBuffersUnique(commandBufferAllocateInfo).value;
+    ASSERT_THAT(commandBuffers, Not(IsEmpty()));
+    auto commandBuffer = std::move(commandBuffers[0]);
+    ASSERT_THAT(commandBuffer, IsValidHandle());
+
+    vkhpp::ClearColorValue clearColor(std::array<float, 4>{0.0f, 0.0f, 0.0f, 1.0f});
+    vkhpp::ClearValue clearValue{
+        .color = clearColor,
+    };
+    vkhpp::RenderPassBeginInfo renderPassBeginInfo{
+        .renderPass = *pipelineInfo->renderPass,
+        .framebuffer = *framebuffer,
+        .renderArea = vkhpp::Rect2D(vkhpp::Offset2D(0, 0), vkhpp::Extent2D(kFbWidth, kFbHeight)),
+        .clearValueCount = 1,
+        .pClearValues = &clearValue,
+    };
+
+    // Descriptor updates are cached on the guest, for testing purpose we need to submit a queue to
+    // commit descriptor updates.
+
+    const vkhpp::CommandBufferBeginInfo commandBufferBeginInfo = {
+        .flags = vkhpp::CommandBufferUsageFlagBits::eOneTimeSubmit,
+    };
+
+    commandBuffer->begin(commandBufferBeginInfo);
+    const vkhpp::ImageMemoryBarrier colorAttachmentBarrier{
+        .oldLayout = vkhpp::ImageLayout::eUndefined,
+        .newLayout = vkhpp::ImageLayout::eColorAttachmentOptimal,
+        .dstAccessMask = vkhpp::AccessFlagBits::eColorAttachmentRead |
+                         vkhpp::AccessFlagBits::eColorAttachmentWrite,
+        .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+        .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+        .image = *colorAttachmentInfo->image,
+        .subresourceRange =
+            {
+                .aspectMask = vkhpp::ImageAspectFlagBits::eColor,
+                .levelCount = 1,
+                .layerCount = 1,
+            },
+    };
+
+    commandBuffer->pipelineBarrier(
+        vkhpp::PipelineStageFlagBits::eTopOfPipe | vkhpp::PipelineStageFlagBits::eTransfer,
+        vkhpp::PipelineStageFlagBits::eColorAttachmentOutput, vkhpp::DependencyFlags(), nullptr,
+        nullptr, colorAttachmentBarrier);
+
+    commandBuffer->beginRenderPass(renderPassBeginInfo, vkhpp::SubpassContents::eInline);
+    commandBuffer->bindPipeline(vkhpp::PipelineBindPoint::eGraphics, *pipelineInfo->pipeline);
+    commandBuffer->bindDescriptorSets(vkhpp::PipelineBindPoint::eGraphics,
+                                      *pipelineInfo->pipelineLayout, 0, descriptorSet, nullptr);
+    commandBuffer->bindVertexBuffers(0, {*vertexBufferInfo->buffer}, {0});
+    commandBuffer->setViewport(0, vkhpp::Viewport(0.0f, 0.0f, static_cast<float>(kFbWidth),
+                                                  static_cast<float>(kFbHeight), 0.0f, 1.0f));
+    commandBuffer->setScissor(
+        0, vkhpp::Rect2D(vkhpp::Offset2D(0, 0), vkhpp::Extent2D(kFbWidth, kFbHeight)));
+    commandBuffer->draw(6, 1, 0, 0);
+    commandBuffer->endRenderPass();
+    commandBuffer->end();
+
+    std::vector<vkhpp::CommandBuffer> commandBufferHandles;
+    commandBufferHandles.push_back(*commandBuffer);
+
+    const vkhpp::SubmitInfo submitInfo = {
+        .commandBufferCount = static_cast<uint32_t>(commandBufferHandles.size()),
+        .pCommandBuffers = commandBufferHandles.data(),
+    };
+    queue.submit(submitInfo, *fence);
+    auto waitResult = device->waitForFences(*fence, VK_TRUE, 3000000000L);
+    ASSERT_THAT(waitResult, IsVkSuccess());
+    commandBuffer->reset();
+
+    // Clear the rendering
+    commandBuffer->begin(commandBufferBeginInfo);
+    commandBuffer->beginRenderPass(renderPassBeginInfo, vkhpp::SubpassContents::eInline);
+    commandBuffer->bindPipeline(vkhpp::PipelineBindPoint::eGraphics, *pipelineInfo->pipeline);
+    vkhpp::ClearAttachment clearAttachment{
+        .aspectMask = vkhpp::ImageAspectFlagBits::eColor,
+        .colorAttachment = 0,
+        .clearValue = clearValue,
+    };
+    vkhpp::ClearRect clearRect{
+        .rect = vkhpp::Rect2D(vkhpp::Offset2D(0, 0), vkhpp::Extent2D(kFbWidth, kFbHeight)),
+        .baseArrayLayer = 0,
+        .layerCount = 1,
+    };
+    commandBuffer->clearAttachments(1, &clearAttachment, 1, &clearRect);
+    commandBuffer->endRenderPass();
+    commandBuffer->end();
+
+    device->resetFences(1, &fence.get());
+    queue.submit(submitInfo, *fence);
+    waitResult = device->waitForFences(*fence, VK_TRUE, 3000000000L);
+    ASSERT_THAT(waitResult, IsVkSuccess());
+    commandBuffer->reset();
+
+    SnapshotSaveAndLoad();
+
+    // Redraw after snapshot, verify descriptors keep their value
+    // Command buffer snapshot is not implemented yet, so we need to re-make the command buffer.
+    commandBuffer->begin(commandBufferBeginInfo);
+    commandBuffer->beginRenderPass(renderPassBeginInfo, vkhpp::SubpassContents::eInline);
+    commandBuffer->bindPipeline(vkhpp::PipelineBindPoint::eGraphics, *pipelineInfo->pipeline);
+    commandBuffer->bindDescriptorSets(vkhpp::PipelineBindPoint::eGraphics,
+                                      *pipelineInfo->pipelineLayout, 0, descriptorSet, nullptr);
+    commandBuffer->bindVertexBuffers(0, {*vertexBufferInfo->buffer}, {0});
+    commandBuffer->setViewport(0, vkhpp::Viewport(0.0f, 0.0f, static_cast<float>(kFbWidth),
+                                                  static_cast<float>(kFbHeight), 0.0f, 1.0f));
+    commandBuffer->setScissor(
+        0, vkhpp::Rect2D(vkhpp::Offset2D(0, 0), vkhpp::Extent2D(kFbWidth, kFbHeight)));
+    commandBuffer->draw(6, 1, 0, 0);
+    commandBuffer->endRenderPass();
+    commandBuffer->end();
+
+    device->resetFences(1, &fence.get());
+    queue.submit(submitInfo, *fence);
+    waitResult = device->waitForFences(*fence, VK_TRUE, 3000000000L);
+    ASSERT_THAT(waitResult, IsVkSuccess());
+
+    std::vector<uint32_t> dst(kFbWidth * kFbHeight);
+    utils::readImageData(*colorAttachmentInfo->image, kFbWidth, kFbHeight,
+                         vkhpp::ImageLayout::eColorAttachmentOptimal, dst.data(),
+                         dst.size() * sizeof(uint32_t), testEnvironment);
+    for (int i = 0; i < dst.size(); i++) {
+        // The shader adds a blue color (from vertex buffer) with a red color (from uniform) and get
+        // purple.
+        ASSERT_THAT(dst[i], Eq(0xffff00ff));
+    }
+}
+
+TEST_P(GfxstreamEnd2EndVkSnapshotPipelineTest, DeleteBufferAfterWriteDescriptor) {
+    TypicalVkTestEnvironment testEnvironment = VK_ASSERT(SetUpTypicalVkTestEnvironment());
+    auto& [instance, physicalDevice, device, queue, queueFamilyIndex] = testEnvironment;
+
+    auto pipelineInfo = createPipeline(device.get());
+    auto vertexBufferInfo = createAndPopulateBuffer(
+        physicalDevice, device.get(), vkhpp::BufferUsageFlagBits::eVertexBuffer,
+        kFullscreenBlueRectangleVertexData, sizeof(kFullscreenBlueRectangleVertexData));
+
+    auto colorAttachmentInfo = createColorAttachment(physicalDevice, device.get());
+    ASSERT_THAT(colorAttachmentInfo->image, IsValidHandle());
+    ASSERT_THAT(colorAttachmentInfo->memory, IsValidHandle());
+    ASSERT_THAT(colorAttachmentInfo->imageView, IsValidHandle());
+
+    // Descriptor
+    std::vector<vkhpp::DescriptorPoolSize> sizes = {
+        {
+            .type = vkhpp::DescriptorType::eUniformBuffer,
+            .descriptorCount = 10,
+        },
+    };
+    vkhpp::DescriptorPoolCreateInfo descriptorPoolCreateInfo = {
+        .maxSets = 10,
+        .poolSizeCount = static_cast<uint32_t>(sizes.size()),
+        .pPoolSizes = sizes.data(),
+    };
+    auto descriptorPool = device->createDescriptorPoolUnique(descriptorPoolCreateInfo).value;
+    ASSERT_THAT(descriptorPool, IsValidHandle());
+
+    const std::vector<vkhpp::DescriptorSetLayout> descriptorSetLayouts(
+        1, *pipelineInfo->descriptorSetLayout);
+
+    vkhpp::DescriptorSetAllocateInfo descriptorSetAllocateInfo = {
+        .descriptorPool = *descriptorPool,
+        .descriptorSetCount = 1,
+        .pSetLayouts = descriptorSetLayouts.data(),
+    };
+    auto descriptorSets = device->allocateDescriptorSetsUnique(descriptorSetAllocateInfo);
+    EXPECT_THAT(descriptorSets.result, Eq(vkhpp::Result::eSuccess));
+    auto descriptorSet = *descriptorSets.value[0];
+
+    // A uniform for red color
+    float kColor1[] = {1.0f, 0.0f, 0.0f, 0.0f};
+    auto uniformBufferInfo = createAndPopulateBuffer(physicalDevice, device.get(),
+                                                     vkhpp::BufferUsageFlagBits::eUniformBuffer,
+                                                     kColor1, sizeof(kColor1));
+
+    std::vector<vkhpp::WriteDescriptorSet> writeDescriptorSets;
+    std::vector<vkhpp::DescriptorBufferInfo> bufferInfos;
+    bufferInfos.emplace_back(vkhpp::DescriptorBufferInfo{
+        .buffer = *uniformBufferInfo->buffer,
+        .offset = 0,
+        .range = VK_WHOLE_SIZE,
+    });
+    writeDescriptorSets.emplace_back(vkhpp::WriteDescriptorSet{
+        .dstSet = descriptorSet,
+        .dstBinding = 0,
+        .descriptorCount = 1,
+        .descriptorType = vkhpp::DescriptorType::eUniformBuffer,
+        .pBufferInfo = bufferInfos.data(),
+    });
+    device->updateDescriptorSets(writeDescriptorSets, nullptr);
+
+    const std::vector<vkhpp::ImageView> attachments(1, *colorAttachmentInfo->imageView);
+    vkhpp::FramebufferCreateInfo framebufferCreateInfo = {
+        .renderPass = *pipelineInfo->renderPass,
+        .attachmentCount = 1,
+        .pAttachments = attachments.data(),
+        .width = kFbWidth,
+        .height = kFbHeight,
+        .layers = 1,
+    };
+    auto framebuffer = device->createFramebufferUnique(framebufferCreateInfo).value;
+    ASSERT_THAT(framebuffer, IsValidHandle());
+
+    auto fence = device->createFenceUnique(vkhpp::FenceCreateInfo()).value;
+    ASSERT_THAT(fence, IsValidHandle());
+
+    const vkhpp::CommandPoolCreateInfo commandPoolCreateInfo = {
+        .flags = vkhpp::CommandPoolCreateFlagBits::eResetCommandBuffer,
+        .queueFamilyIndex = queueFamilyIndex,
+    };
+
+    auto commandPool = device->createCommandPoolUnique(commandPoolCreateInfo).value;
+    ASSERT_THAT(commandPool, IsValidHandle());
+
+    const vkhpp::CommandBufferAllocateInfo commandBufferAllocateInfo = {
+        .level = vkhpp::CommandBufferLevel::ePrimary,
+        .commandPool = *commandPool,
+        .commandBufferCount = 1,
+    };
+
+    auto commandBuffers = device->allocateCommandBuffersUnique(commandBufferAllocateInfo).value;
+    ASSERT_THAT(commandBuffers, Not(IsEmpty()));
+    auto commandBuffer = std::move(commandBuffers[0]);
+    ASSERT_THAT(commandBuffer, IsValidHandle());
+
+    vkhpp::ClearColorValue clearColor(std::array<float, 4>{0.0f, 0.0f, 0.0f, 1.0f});
+    vkhpp::ClearValue clearValue{
+        .color = clearColor,
+    };
+    vkhpp::RenderPassBeginInfo renderPassBeginInfo{
+        .renderPass = *pipelineInfo->renderPass,
+        .framebuffer = *framebuffer,
+        .renderArea = vkhpp::Rect2D(vkhpp::Offset2D(0, 0), vkhpp::Extent2D(kFbWidth, kFbHeight)),
+        .clearValueCount = 1,
+        .pClearValues = &clearValue,
+    };
+
+    // Descriptor updates are cached on the guest, for testing purpose we need to submit a queue to
+    // commit descriptor updates.
+
+    const vkhpp::CommandBufferBeginInfo commandBufferBeginInfo = {
+        .flags = vkhpp::CommandBufferUsageFlagBits::eOneTimeSubmit,
+    };
+
+    commandBuffer->begin(commandBufferBeginInfo);
+    const vkhpp::ImageMemoryBarrier colorAttachmentBarrier{
+        .oldLayout = vkhpp::ImageLayout::eUndefined,
+        .newLayout = vkhpp::ImageLayout::eColorAttachmentOptimal,
+        .dstAccessMask = vkhpp::AccessFlagBits::eColorAttachmentRead |
+                         vkhpp::AccessFlagBits::eColorAttachmentWrite,
+        .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+        .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+        .image = *colorAttachmentInfo->image,
+        .subresourceRange =
+            {
+                .aspectMask = vkhpp::ImageAspectFlagBits::eColor,
+                .levelCount = 1,
+                .layerCount = 1,
+            },
+    };
+
+    commandBuffer->pipelineBarrier(
+        vkhpp::PipelineStageFlagBits::eTopOfPipe | vkhpp::PipelineStageFlagBits::eTransfer,
+        vkhpp::PipelineStageFlagBits::eColorAttachmentOutput, vkhpp::DependencyFlags(), nullptr,
+        nullptr, colorAttachmentBarrier);
+
+    commandBuffer->beginRenderPass(renderPassBeginInfo, vkhpp::SubpassContents::eInline);
+    commandBuffer->bindPipeline(vkhpp::PipelineBindPoint::eGraphics, *pipelineInfo->pipeline);
+    commandBuffer->bindDescriptorSets(vkhpp::PipelineBindPoint::eGraphics,
+                                      *pipelineInfo->pipelineLayout, 0, descriptorSet, nullptr);
+    commandBuffer->bindVertexBuffers(0, {*vertexBufferInfo->buffer}, {0});
+    commandBuffer->setViewport(0, vkhpp::Viewport(0.0f, 0.0f, static_cast<float>(kFbWidth),
+                                                  static_cast<float>(kFbHeight), 0.0f, 1.0f));
+    commandBuffer->setScissor(
+        0, vkhpp::Rect2D(vkhpp::Offset2D(0, 0), vkhpp::Extent2D(kFbWidth, kFbHeight)));
+    commandBuffer->draw(6, 1, 0, 0);
+    commandBuffer->endRenderPass();
+    commandBuffer->end();
+
+    std::vector<vkhpp::CommandBuffer> commandBufferHandles;
+    commandBufferHandles.push_back(*commandBuffer);
+
+    const vkhpp::SubmitInfo submitInfo = {
+        .commandBufferCount = static_cast<uint32_t>(commandBufferHandles.size()),
+        .pCommandBuffers = commandBufferHandles.data(),
+    };
+    queue.submit(submitInfo, *fence);
+    auto waitResult = device->waitForFences(*fence, VK_TRUE, 3000000000L);
+    ASSERT_THAT(waitResult, IsVkSuccess());
+    commandBuffer->reset();
+
+    vertexBufferInfo->buffer.reset();
+    // Descriptor snapshot should not crash after underlying buffer is deleted.
+    SnapshotSaveAndLoad();
+}
+
 INSTANTIATE_TEST_CASE_P(GfxstreamEnd2EndTests, GfxstreamEnd2EndVkSnapshotPipelineTest,
                         ::testing::ValuesIn({
                             TestParams{
diff --git a/common/end2end/simple_shader.frag b/common/end2end/simple_shader.frag
index b8b6f56..a3e42b1 100644
--- a/common/end2end/simple_shader.frag
+++ b/common/end2end/simple_shader.frag
@@ -14,11 +14,15 @@
 
 #version 450
 
+layout (binding = 0) uniform UniformBuffer {
+  vec4 color1;
+};
+
 layout (location = 0) in vec4 color;
 
 layout (location = 0) out vec4 outColor;
 
 void main()
 {
-  outColor = color;
+  outColor = color + color1;
 }
\ No newline at end of file
diff --git a/common/end2end/simple_shader_frag.h b/common/end2end/simple_shader_frag.h
index cbb84f0..974d127 100644
--- a/common/end2end/simple_shader_frag.h
+++ b/common/end2end/simple_shader_frag.h
@@ -20,17 +20,26 @@
 #include <stdint.h>
 
 const uint32_t kSimpleShaderFrag[] = {
-    0x07230203, 0x00010000, 0x000d000b, 0x0000000d, 0x00000000, 0x00020011, 0x00000001, 0x0006000b,
+    0x07230203, 0x00010000, 0x000d000b, 0x00000016, 0x00000000, 0x00020011, 0x00000001, 0x0006000b,
     0x00000001, 0x4c534c47, 0x6474732e, 0x3035342e, 0x00000000, 0x0003000e, 0x00000000, 0x00000001,
     0x0007000f, 0x00000004, 0x00000004, 0x6e69616d, 0x00000000, 0x00000009, 0x0000000b, 0x00030010,
     0x00000004, 0x00000007, 0x00030003, 0x00000002, 0x000001c2, 0x000a0004, 0x475f4c47, 0x4c474f4f,
     0x70635f45, 0x74735f70, 0x5f656c79, 0x656e696c, 0x7269645f, 0x69746365, 0x00006576, 0x00080004,
     0x475f4c47, 0x4c474f4f, 0x6e695f45, 0x64756c63, 0x69645f65, 0x74636572, 0x00657669, 0x00040005,
     0x00000004, 0x6e69616d, 0x00000000, 0x00050005, 0x00000009, 0x4374756f, 0x726f6c6f, 0x00000000,
-    0x00040005, 0x0000000b, 0x6f6c6f63, 0x00000072, 0x00040047, 0x00000009, 0x0000001e, 0x00000000,
-    0x00040047, 0x0000000b, 0x0000001e, 0x00000000, 0x00020013, 0x00000002, 0x00030021, 0x00000003,
-    0x00000002, 0x00030016, 0x00000006, 0x00000020, 0x00040017, 0x00000007, 0x00000006, 0x00000004,
-    0x00040020, 0x00000008, 0x00000003, 0x00000007, 0x0004003b, 0x00000008, 0x00000009, 0x00000003,
-    0x00040020, 0x0000000a, 0x00000001, 0x00000007, 0x0004003b, 0x0000000a, 0x0000000b, 0x00000001,
-    0x00050036, 0x00000002, 0x00000004, 0x00000000, 0x00000003, 0x000200f8, 0x00000005, 0x0004003d,
-    0x00000007, 0x0000000c, 0x0000000b, 0x0003003e, 0x00000009, 0x0000000c, 0x000100fd, 0x00010038};
\ No newline at end of file
+    0x00040005, 0x0000000b, 0x6f6c6f63, 0x00000072, 0x00060005, 0x0000000d, 0x66696e55, 0x426d726f,
+    0x65666675, 0x00000072, 0x00050006, 0x0000000d, 0x00000000, 0x6f6c6f63, 0x00003172, 0x00030005,
+    0x0000000f, 0x00000000, 0x00040047, 0x00000009, 0x0000001e, 0x00000000, 0x00040047, 0x0000000b,
+    0x0000001e, 0x00000000, 0x00050048, 0x0000000d, 0x00000000, 0x00000023, 0x00000000, 0x00030047,
+    0x0000000d, 0x00000002, 0x00040047, 0x0000000f, 0x00000022, 0x00000000, 0x00040047, 0x0000000f,
+    0x00000021, 0x00000000, 0x00020013, 0x00000002, 0x00030021, 0x00000003, 0x00000002, 0x00030016,
+    0x00000006, 0x00000020, 0x00040017, 0x00000007, 0x00000006, 0x00000004, 0x00040020, 0x00000008,
+    0x00000003, 0x00000007, 0x0004003b, 0x00000008, 0x00000009, 0x00000003, 0x00040020, 0x0000000a,
+    0x00000001, 0x00000007, 0x0004003b, 0x0000000a, 0x0000000b, 0x00000001, 0x0003001e, 0x0000000d,
+    0x00000007, 0x00040020, 0x0000000e, 0x00000002, 0x0000000d, 0x0004003b, 0x0000000e, 0x0000000f,
+    0x00000002, 0x00040015, 0x00000010, 0x00000020, 0x00000001, 0x0004002b, 0x00000010, 0x00000011,
+    0x00000000, 0x00040020, 0x00000012, 0x00000002, 0x00000007, 0x00050036, 0x00000002, 0x00000004,
+    0x00000000, 0x00000003, 0x000200f8, 0x00000005, 0x0004003d, 0x00000007, 0x0000000c, 0x0000000b,
+    0x00050041, 0x00000012, 0x00000013, 0x0000000f, 0x00000011, 0x0004003d, 0x00000007, 0x00000014,
+    0x00000013, 0x00050081, 0x00000007, 0x00000015, 0x0000000c, 0x00000014, 0x0003003e, 0x00000009,
+    0x00000015, 0x000100fd, 0x00010038};
\ No newline at end of file
diff --git a/host/vulkan/VkDecoderGlobalState.cpp b/host/vulkan/VkDecoderGlobalState.cpp
index bf18f23..785b1f4 100644
--- a/host/vulkan/VkDecoderGlobalState.cpp
+++ b/host/vulkan/VkDecoderGlobalState.cpp
@@ -528,6 +528,141 @@
             releaseSnapshotStateBlock(&stateBlock);
         }
 
+        // snapshot descriptors
+        std::vector<VkDescriptorPool> sortedBoxedDescriptorPools;
+        for (const auto& descriptorPoolIte : mDescriptorPoolInfo) {
+            auto boxed =
+                unboxed_to_boxed_non_dispatchable_VkDescriptorPool(descriptorPoolIte.first);
+            sortedBoxedDescriptorPools.push_back(boxed);
+        }
+        std::sort(sortedBoxedDescriptorPools.begin(), sortedBoxedDescriptorPools.end());
+        for (const auto& boxedDescriptorPool : sortedBoxedDescriptorPools) {
+            auto unboxedDescriptorPool = unbox_VkDescriptorPool(boxedDescriptorPool);
+            const DescriptorPoolInfo& poolInfo = mDescriptorPoolInfo[unboxedDescriptorPool];
+
+            for (uint64_t poolId : poolInfo.poolIds) {
+                DispatchableHandleInfo<uint64_t>* setHandleInfo = sBoxedHandleManager.get(poolId);
+                bool allocated = setHandleInfo->underlying != 0;
+                stream->putByte(allocated);
+                if (!allocated) {
+                    continue;
+                }
+
+                const DescriptorSetInfo& descriptorSetInfo =
+                    mDescriptorSetInfo[(VkDescriptorSet)setHandleInfo->underlying];
+                VkDescriptorSetLayout boxedLayout =
+                    unboxed_to_boxed_non_dispatchable_VkDescriptorSetLayout(
+                        descriptorSetInfo.unboxedLayout);
+                stream->putBe64((uint64_t)boxedLayout);
+                // Count all valid descriptors.
+                //
+                // There is a use case where user can create an image, write it to a descriptor,
+                // read/write the image by committing a command, then delete the image without
+                // unbinding the descriptor. For example:
+                //
+                // T1: create "vkimage1" (original)
+                // T2: update binding1 of vkdescriptorset1 with vkimage1
+                // T3: draw
+                // T4: delete "vkimage1" (original)
+                // T5: create "vkimage1" (recycled)
+                // T6: snapshot load
+                //
+                // At the point of the snapshot, the original vk image has been invalidated,
+                // thus we cannot call vkUpdateDescriptorSets for it, and need to remove it
+                // from the snapshot.
+                //
+                // The current implementation bases on smart pointers. A descriptor set info
+                // holds weak pointers to their underlying resources (image, image view, buffer).
+                // On snapshot load, we check if any of the smart pointers are invalidated.
+                //
+                // An alternative approach has been discussed by, instead of using smart
+                // pointers, checking valid handles on snapshot save. This approach has the
+                // advantage that it reduces number of smart pointer allocations. After discussion
+                // we concluded that there is at least one corner case that will break the
+                // alternative approach. That is when the user deletes a bound vkimage and creates
+                // a new vkimage. The driver is free to reuse released handles, thus we might
+                // end up having a new vkimage with the same handle as the old one (see T5 in the
+                // example), and think the binding is still valid. And if we bind the new image
+                // regardless, we might hit a Vulkan validation error because the new image might
+                // have the "usage" flag that is unsuitable to bind to descriptors.
+                std::vector<std::pair<int, int>> validWriteIndices;
+                for (int bindingIdx = 0; bindingIdx < descriptorSetInfo.allWrites.size();
+                     bindingIdx++) {
+                    for (int bindingElemIdx = 0;
+                         bindingElemIdx < descriptorSetInfo.allWrites[bindingIdx].size();
+                         bindingElemIdx++) {
+                        const auto& entry = descriptorSetInfo.allWrites[bindingIdx][bindingElemIdx];
+                        if (entry.writeType == DescriptorSetInfo::DescriptorWriteType::Empty) {
+                            continue;
+                        }
+                        int dependencyObjCount =
+                            descriptorDependencyObjectCount(entry.descriptorType);
+                        if (entry.alives.size() < dependencyObjCount) {
+                            continue;
+                        }
+                        bool isValid = true;
+                        for (const auto& alive : entry.alives) {
+                            isValid &= !alive.expired();
+                            if (!isValid) {
+                                break;
+                            }
+                        }
+                        if (!isValid) {
+                            continue;
+                        }
+                        validWriteIndices.push_back(std::make_pair(bindingIdx, bindingElemIdx));
+                    }
+                }
+                stream->putBe64(validWriteIndices.size());
+                // Save all valid descriptors
+                for (const auto& idx : validWriteIndices) {
+                    const auto& entry = descriptorSetInfo.allWrites[idx.first][idx.second];
+                    stream->putBe32(idx.first);
+                    stream->putBe32(idx.second);
+                    stream->putBe32(entry.writeType);
+                    // entry.descriptorType might be redundant.
+                    stream->putBe32(entry.descriptorType);
+                    switch (entry.writeType) {
+                        case DescriptorSetInfo::DescriptorWriteType::ImageInfo: {
+                            VkDescriptorImageInfo imageInfo = entry.imageInfo;
+                            // Get the unboxed version
+                            imageInfo.imageView =
+                                descriptorTypeContainsImage(entry.descriptorType)
+                                    ? unboxed_to_boxed_non_dispatchable_VkImageView(
+                                          imageInfo.imageView)
+                                    : VK_NULL_HANDLE;
+                            imageInfo.sampler =
+                                descriptorTypeContainsSampler(entry.descriptorType)
+                                    ? unboxed_to_boxed_non_dispatchable_VkSampler(imageInfo.sampler)
+                                    : VK_NULL_HANDLE;
+                            stream->write(&imageInfo, sizeof(imageInfo));
+                        } break;
+                        case DescriptorSetInfo::DescriptorWriteType::BufferInfo: {
+                            VkDescriptorBufferInfo bufferInfo = entry.bufferInfo;
+                            // Get the unboxed version
+                            bufferInfo.buffer =
+                                unboxed_to_boxed_non_dispatchable_VkBuffer(bufferInfo.buffer);
+                            stream->write(&bufferInfo, sizeof(bufferInfo));
+                        } break;
+                        case DescriptorSetInfo::DescriptorWriteType::BufferView: {
+                            // Get the unboxed version
+                            VkBufferView bufferView =
+                                unboxed_to_boxed_non_dispatchable_VkBufferView(entry.bufferView);
+                            stream->write(&bufferView, sizeof(bufferView));
+                        } break;
+                        case DescriptorSetInfo::DescriptorWriteType::InlineUniformBlock:
+                        case DescriptorSetInfo::DescriptorWriteType::AccelerationStructure:
+                            // TODO
+                            GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER))
+                                << "Encountered pending inline uniform block or acceleration "
+                                   "structure "
+                                   "desc write, abort (NYI)";
+                        default:
+                            break;
+                    }
+                }
+            }
+        }
         mSnapshotState = SnapshotState::Normal;
     }
 
@@ -539,6 +674,7 @@
         // destroy all current internal data structures
         clear();
         mSnapshotState = SnapshotState::Loading;
+        android::base::BumpPool bumpPool;
         // this part will replay in the decoder
         snapshot()->load(stream, gfxLogger, healthMonitor);
         // load mapped memory
@@ -609,6 +745,105 @@
             releaseSnapshotStateBlock(&stateBlock);
         }
 
+        // snapshot descriptors
+        std::vector<VkDescriptorPool> sortedBoxedDescriptorPools;
+        for (const auto& descriptorPoolIte : mDescriptorPoolInfo) {
+            auto boxed =
+                unboxed_to_boxed_non_dispatchable_VkDescriptorPool(descriptorPoolIte.first);
+            sortedBoxedDescriptorPools.push_back(boxed);
+        }
+        sort(sortedBoxedDescriptorPools.begin(), sortedBoxedDescriptorPools.end());
+        for (const auto& boxedDescriptorPool : sortedBoxedDescriptorPools) {
+            auto unboxedDescriptorPool = unbox_VkDescriptorPool(boxedDescriptorPool);
+            const DescriptorPoolInfo& poolInfo = mDescriptorPoolInfo[unboxedDescriptorPool];
+
+            std::vector<VkDescriptorSetLayout> layouts;
+            std::vector<uint64_t> poolIds;
+            std::vector<VkWriteDescriptorSet> writeDescriptorSets;
+            std::vector<uint32_t> writeStartingIndices;
+
+            // Temporary structures for the pointers in VkWriteDescriptorSet.
+            // Use unique_ptr so that the pointers don't change when vector resizes.
+            std::vector<std::unique_ptr<VkDescriptorImageInfo>> tmpImageInfos;
+            std::vector<std::unique_ptr<VkDescriptorBufferInfo>> tmpBufferInfos;
+            std::vector<std::unique_ptr<VkBufferView>> tmpBufferViews;
+
+            for (uint64_t poolId : poolInfo.poolIds) {
+                bool allocated = stream->getByte();
+                if (!allocated) {
+                    continue;
+                }
+                poolIds.push_back(poolId);
+                writeStartingIndices.push_back(writeDescriptorSets.size());
+                VkDescriptorSetLayout boxedLayout = (VkDescriptorSetLayout)stream->getBe64();
+                layouts.push_back(unbox_VkDescriptorSetLayout(boxedLayout));
+                uint64_t validWriteCount = stream->getBe64();
+                for (int write = 0; write < validWriteCount; write++) {
+                    uint32_t binding = stream->getBe32();
+                    uint32_t arrayElement = stream->getBe32();
+                    DescriptorSetInfo::DescriptorWriteType writeType =
+                        static_cast<DescriptorSetInfo::DescriptorWriteType>(stream->getBe32());
+                    VkDescriptorType descriptorType =
+                        static_cast<VkDescriptorType>(stream->getBe32());
+                    VkWriteDescriptorSet writeDescriptorSet = {
+                        .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
+                        .dstSet = (VkDescriptorSet)poolId,
+                        .dstBinding = binding,
+                        .dstArrayElement = arrayElement,
+                        .descriptorCount = 1,
+                        .descriptorType = descriptorType,
+                    };
+                    switch (writeType) {
+                        case DescriptorSetInfo::DescriptorWriteType::ImageInfo: {
+                            tmpImageInfos.push_back(std::make_unique<VkDescriptorImageInfo>());
+                            writeDescriptorSet.pImageInfo = tmpImageInfos.back().get();
+                            VkDescriptorImageInfo& imageInfo = *tmpImageInfos.back();
+                            stream->read(&imageInfo, sizeof(imageInfo));
+                            imageInfo.imageView = descriptorTypeContainsImage(descriptorType)
+                                                      ? unbox_VkImageView(imageInfo.imageView)
+                                                      : 0;
+                            imageInfo.sampler = descriptorTypeContainsSampler(descriptorType)
+                                                    ? unbox_VkSampler(imageInfo.sampler)
+                                                    : 0;
+                        } break;
+                        case DescriptorSetInfo::DescriptorWriteType::BufferInfo: {
+                            tmpBufferInfos.push_back(std::make_unique<VkDescriptorBufferInfo>());
+                            writeDescriptorSet.pBufferInfo = tmpBufferInfos.back().get();
+                            VkDescriptorBufferInfo& bufferInfo = *tmpBufferInfos.back();
+                            stream->read(&bufferInfo, sizeof(bufferInfo));
+                            bufferInfo.buffer = unbox_VkBuffer(bufferInfo.buffer);
+                        } break;
+                        case DescriptorSetInfo::DescriptorWriteType::BufferView: {
+                            tmpBufferViews.push_back(std::make_unique<VkBufferView>());
+                            writeDescriptorSet.pTexelBufferView = tmpBufferViews.back().get();
+                            VkBufferView& bufferView = *tmpBufferViews.back();
+                            stream->read(&bufferView, sizeof(bufferView));
+                            bufferView = unbox_VkBufferView(bufferView);
+                        } break;
+                        case DescriptorSetInfo::DescriptorWriteType::InlineUniformBlock:
+                        case DescriptorSetInfo::DescriptorWriteType::AccelerationStructure:
+                            // TODO
+                            GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER))
+                                << "Encountered pending inline uniform block or acceleration "
+                                   "structure "
+                                   "desc write, abort (NYI)";
+                        default:
+                            break;
+                    }
+                    writeDescriptorSets.push_back(writeDescriptorSet);
+                }
+            }
+            std::vector<uint32_t> whichPool(poolIds.size(), 0);
+            std::vector<uint32_t> pendingAlloc(poolIds.size(), true);
+
+            const auto& device = poolInfo.device;
+            const auto& deviceInfo = android::base::find(mDeviceInfo, device);
+            VulkanDispatch* dvk = dispatch_VkDevice(deviceInfo->boxed);
+            on_vkQueueCommitDescriptorSetUpdatesGOOGLE(
+                &bumpPool, dvk, device, 1, &unboxedDescriptorPool, poolIds.size(), layouts.data(),
+                poolIds.data(), whichPool.data(), pendingAlloc.data(), writeStartingIndices.data(),
+                writeDescriptorSets.size(), writeDescriptorSets.data());
+        }
         mSnapshotState = SnapshotState::Normal;
     }
 
@@ -1668,6 +1903,13 @@
                                const VkAllocationCallbacks* pAllocator, VkBuffer* pBuffer) {
         auto device = unbox_VkDevice(boxed_device);
         auto vk = dispatch_VkDevice(boxed_device);
+        VkBufferCreateInfo localCreateInfo;
+        if (snapshotsEnabled()) {
+            localCreateInfo = *pCreateInfo;
+            localCreateInfo.usage |=
+                VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
+            pCreateInfo = &localCreateInfo;
+        }
 
         VkResult result = vk->vkCreateBuffer(device, pCreateInfo, pAllocator, pBuffer);
 
@@ -2663,7 +2905,20 @@
         auto& setInfo = mDescriptorSetInfo[descriptorSet];
 
         setInfo.pool = pool;
+        setInfo.unboxedLayout = setLayout;
         setInfo.bindings = setLayoutInfo->bindings;
+        for (size_t i = 0; i < setInfo.bindings.size(); i++) {
+            VkDescriptorSetLayoutBinding dslBinding = setInfo.bindings[i];
+            int bindingIdx = dslBinding.binding;
+            if (setInfo.allWrites.size() <= bindingIdx) {
+                setInfo.allWrites.resize(bindingIdx + 1);
+            }
+            setInfo.allWrites[bindingIdx].resize(dslBinding.descriptorCount);
+            for (auto& write : setInfo.allWrites[bindingIdx]) {
+                write.descriptorType = dslBinding.descriptorType;
+                write.dstArrayElement = 0;
+            }
+        }
 
         poolInfo->allocedSetsToBoxed[descriptorSet] = (VkDescriptorSet)boxedDescriptorSet;
         applyDescriptorSetAllocationLocked(*poolInfo, setInfo.bindings);
@@ -2759,6 +3014,117 @@
                                        const VkWriteDescriptorSet* pDescriptorWrites,
                                        uint32_t descriptorCopyCount,
                                        const VkCopyDescriptorSet* pDescriptorCopies) {
+        if (snapshotsEnabled()) {
+            for (uint32_t writeIdx = 0; writeIdx < descriptorWriteCount; writeIdx++) {
+                const VkWriteDescriptorSet& descriptorWrite = pDescriptorWrites[writeIdx];
+                auto ite = mDescriptorSetInfo.find(descriptorWrite.dstSet);
+                if (ite == mDescriptorSetInfo.end()) {
+                    continue;
+                }
+                DescriptorSetInfo& descriptorSetInfo = ite->second;
+                auto& table = descriptorSetInfo.allWrites;
+                VkDescriptorType descType = descriptorWrite.descriptorType;
+                uint32_t dstBinding = descriptorWrite.dstBinding;
+                uint32_t dstArrayElement = descriptorWrite.dstArrayElement;
+                uint32_t descriptorCount = descriptorWrite.descriptorCount;
+
+                uint32_t arrOffset = dstArrayElement;
+
+                if (isDescriptorTypeImageInfo(descType)) {
+                    for (uint32_t writeElemIdx = 0; writeElemIdx < descriptorCount;
+                         ++writeElemIdx, ++arrOffset) {
+                        // Descriptor writes wrap to the next binding. See
+                        // https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkWriteDescriptorSet.html
+                        if (arrOffset >= table[dstBinding].size()) {
+                            ++dstBinding;
+                            arrOffset = 0;
+                        }
+                        auto& entry = table[dstBinding][arrOffset];
+                        entry.imageInfo = descriptorWrite.pImageInfo[writeElemIdx];
+                        entry.writeType = DescriptorSetInfo::DescriptorWriteType::ImageInfo;
+                        entry.descriptorType = descType;
+                        entry.alives.clear();
+                        if (descriptorTypeContainsImage(descType)) {
+                            auto* imageViewInfo =
+                                android::base::find(mImageViewInfo, entry.imageInfo.imageView);
+                            entry.alives.push_back(imageViewInfo->alive);
+                        }
+                        if (descriptorTypeContainsSampler(descType)) {
+                            auto* samplerInfo =
+                                android::base::find(mSamplerInfo, entry.imageInfo.sampler);
+                            entry.alives.push_back(samplerInfo->alive);
+                        }
+                    }
+                } else if (isDescriptorTypeBufferInfo(descType)) {
+                    for (uint32_t writeElemIdx = 0; writeElemIdx < descriptorCount;
+                         ++writeElemIdx, ++arrOffset) {
+                        if (arrOffset >= table[dstBinding].size()) {
+                            ++dstBinding;
+                            arrOffset = 0;
+                        }
+                        auto& entry = table[dstBinding][arrOffset];
+                        entry.bufferInfo = descriptorWrite.pBufferInfo[writeElemIdx];
+                        entry.writeType = DescriptorSetInfo::DescriptorWriteType::BufferInfo;
+                        entry.descriptorType = descType;
+                        entry.alives.clear();
+                        auto* bufferInfo =
+                            android::base::find(mBufferInfo, entry.bufferInfo.buffer);
+                        entry.alives.push_back(bufferInfo->alive);
+                    }
+                } else if (isDescriptorTypeBufferView(descType)) {
+                    for (uint32_t writeElemIdx = 0; writeElemIdx < descriptorCount;
+                         ++writeElemIdx, ++arrOffset) {
+                        if (arrOffset >= table[dstBinding].size()) {
+                            ++dstBinding;
+                            arrOffset = 0;
+                        }
+                        auto& entry = table[dstBinding][arrOffset];
+                        entry.bufferView = descriptorWrite.pTexelBufferView[writeElemIdx];
+                        entry.writeType = DescriptorSetInfo::DescriptorWriteType::BufferView;
+                        entry.descriptorType = descType;
+                        // TODO: check alive
+                        ERR("%s: Snapshot for texel buffer view is incomplete.\n", __func__);
+                    }
+                } else if (isDescriptorTypeInlineUniformBlock(descType)) {
+                    const VkWriteDescriptorSetInlineUniformBlock* descInlineUniformBlock =
+                        static_cast<const VkWriteDescriptorSetInlineUniformBlock*>(
+                            descriptorWrite.pNext);
+                    while (descInlineUniformBlock &&
+                           descInlineUniformBlock->sType !=
+                               VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET_INLINE_UNIFORM_BLOCK) {
+                        descInlineUniformBlock =
+                            static_cast<const VkWriteDescriptorSetInlineUniformBlock*>(
+                                descInlineUniformBlock->pNext);
+                    }
+                    if (!descInlineUniformBlock) {
+                        GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER))
+                            << __func__ << ": did not find inline uniform block";
+                        return;
+                    }
+                    auto& entry = table[dstBinding][0];
+                    entry.inlineUniformBlock = *descInlineUniformBlock;
+                    entry.inlineUniformBlockBuffer.assign(
+                        static_cast<const uint8_t*>(descInlineUniformBlock->pData),
+                        static_cast<const uint8_t*>(descInlineUniformBlock->pData) +
+                            descInlineUniformBlock->dataSize);
+                    entry.writeType = DescriptorSetInfo::DescriptorWriteType::InlineUniformBlock;
+                    entry.descriptorType = descType;
+                    entry.dstArrayElement = dstArrayElement;
+                } else if (isDescriptorTypeAccelerationStructure(descType)) {
+                    // TODO
+                    // Look for pNext inline uniform block or acceleration structure.
+                    // Append new DescriptorWrite entry that holds the buffer
+                    ERR("%s: Ignoring Snapshot for emulated write for descriptor type 0x%x\n",
+                        __func__, descType);
+                }
+            }
+            // TODO: bookkeep pDescriptorCopies
+            // Our primary use case vkQueueCommitDescriptorSetUpdatesGOOGLE does not use
+            // pDescriptorCopies. Thus skip its implementation for now.
+            if (descriptorCopyCount) {
+                ERR("%s: Snapshot does not support descriptor copy yet\n");
+            }
+        }
         bool needEmulateWriteDescriptor = false;
         // c++ seems to allow for 0-size array allocation
         std::unique_ptr<bool[]> descriptorWritesNeedDeepCopy(new bool[descriptorWriteCount]);
@@ -5733,8 +6099,7 @@
                 return (VkDescriptorSet)(setHandleInfo->underlying);
             }
         } else {
-            // Snapshot doesn't really replay the commands to allocate those descriptors.
-            if (pendingAlloc || snapshotsEnabled()) {
+            if (pendingAlloc) {
                 VkDescriptorSet allocedSet;
                 VkDescriptorSetAllocateInfo dsAi = {
                     VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, 0, pool, 1, &setLayout,
@@ -5774,7 +6139,21 @@
             GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER))
                 << "queue " << queue << "(boxed: " << boxed_queue << ") with no device registered";
         }
+        on_vkQueueCommitDescriptorSetUpdatesGOOGLE(
+            pool, vk, device, descriptorPoolCount, pDescriptorPools, descriptorSetCount,
+            pDescriptorSetLayouts, pDescriptorSetPoolIds, pDescriptorSetWhichPool,
+            pDescriptorSetPendingAllocation, pDescriptorWriteStartingIndices,
+            pendingDescriptorWriteCount, pPendingDescriptorWrites);
+    }
 
+    void on_vkQueueCommitDescriptorSetUpdatesGOOGLE(
+        android::base::BumpPool* pool, VulkanDispatch* vk, VkDevice device,
+        uint32_t descriptorPoolCount, const VkDescriptorPool* pDescriptorPools,
+        uint32_t descriptorSetCount, const VkDescriptorSetLayout* pDescriptorSetLayouts,
+        const uint64_t* pDescriptorSetPoolIds, const uint32_t* pDescriptorSetWhichPool,
+        const uint32_t* pDescriptorSetPendingAllocation,
+        const uint32_t* pDescriptorWriteStartingIndices, uint32_t pendingDescriptorWriteCount,
+        const VkWriteDescriptorSet* pPendingDescriptorWrites) {
         std::vector<VkDescriptorSet> setsToUpdate(descriptorSetCount, nullptr);
 
         bool didAlloc = false;
@@ -5805,7 +6184,6 @@
                 } else {
                     writeEndIndex = pDescriptorWriteStartingIndices[i + 1];
                 }
-
                 for (uint32_t j = writeStartIndex; j < writeEndIndex; ++j) {
                     writeDescriptorSetsForHostDriver[j].dstSet = setsToUpdate[i];
                 }
@@ -6897,6 +7275,18 @@
                (descType == VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT);
     }
 
+    bool descriptorTypeContainsImage(VkDescriptorType descType) {
+        return (descType == VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER) ||
+               (descType == VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE) ||
+               (descType == VK_DESCRIPTOR_TYPE_STORAGE_IMAGE) ||
+               (descType == VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT);
+    }
+
+    bool descriptorTypeContainsSampler(VkDescriptorType descType) {
+        return (descType == VK_DESCRIPTOR_TYPE_SAMPLER) ||
+               (descType == VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER);
+    }
+
     bool isDescriptorTypeBufferInfo(VkDescriptorType descType) {
         return (descType == VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER) ||
                (descType == VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC) ||
@@ -6909,6 +7299,34 @@
                (descType == VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER);
     }
 
+    bool isDescriptorTypeInlineUniformBlock(VkDescriptorType descType) {
+        return descType == VK_DESCRIPTOR_TYPE_INLINE_UNIFORM_BLOCK_EXT;
+    }
+
+    bool isDescriptorTypeAccelerationStructure(VkDescriptorType descType) {
+        return descType == VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR;
+    }
+
+    int descriptorDependencyObjectCount(VkDescriptorType descType) {
+        switch (descType) {
+            case VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER:
+                return 2;
+            case VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE:
+            case VK_DESCRIPTOR_TYPE_STORAGE_IMAGE:
+            case VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT:
+            case VK_DESCRIPTOR_TYPE_SAMPLER:
+            case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER:
+            case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC:
+            case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER:
+            case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC:
+            case VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER:
+            case VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER:
+                return 1;
+            default:
+                return 0;
+        }
+    }
+
     struct DescriptorUpdateTemplateInfo {
         VkDescriptorUpdateTemplateCreateInfo createInfo;
         std::vector<VkDescriptorUpdateTemplateEntry> linearizedTemplateEntries;
diff --git a/host/vulkan/VkDecoderInternalStructs.h b/host/vulkan/VkDecoderInternalStructs.h
index b81c50c..eab6656 100644
--- a/host/vulkan/VkDecoderInternalStructs.h
+++ b/host/vulkan/VkDecoderInternalStructs.h
@@ -231,6 +231,7 @@
     VkDeviceMemory memory = 0;
     VkDeviceSize memoryOffset = 0;
     VkDeviceSize size;
+    std::shared_ptr<bool> alive{new bool(true)};
 };
 
 struct ImageInfo {
@@ -251,6 +252,7 @@
 
     // Color buffer, provided via vkAllocateMemory().
     std::optional<HandleType> boundColorBuffer;
+    std::shared_ptr<bool> alive{new bool(true)};
 };
 
 struct SamplerInfo {
@@ -271,6 +273,7 @@
     SamplerInfo(const SamplerInfo& other) { *this = other; }
     SamplerInfo(SamplerInfo&& other) = delete;
     SamplerInfo& operator=(SamplerInfo&& other) = delete;
+    std::shared_ptr<bool> alive{new bool(true)};
 };
 
 struct FenceInfo {
@@ -331,7 +334,36 @@
 };
 
 struct DescriptorSetInfo {
+    enum DescriptorWriteType {
+        Empty = 0,
+        ImageInfo = 1,
+        BufferInfo = 2,
+        BufferView = 3,
+        InlineUniformBlock = 4,
+        AccelerationStructure = 5,
+    };
+
+    struct DescriptorWrite {
+        VkDescriptorType descriptorType;
+        DescriptorWriteType writeType = DescriptorWriteType::Empty;
+        uint32_t dstArrayElement;  // Only used for inlineUniformBlock and accelerationStructure.
+
+        union {
+            VkDescriptorImageInfo imageInfo;
+            VkDescriptorBufferInfo bufferInfo;
+            VkBufferView bufferView;
+            VkWriteDescriptorSetInlineUniformBlockEXT inlineUniformBlock;
+            VkWriteDescriptorSetAccelerationStructureKHR accelerationStructure;
+        };
+
+        std::vector<uint8_t> inlineUniformBlockBuffer;
+        // Weak pointer(s) to detect if all objects on dependency chain are alive.
+        std::vector<std::weak_ptr<bool>> alives;
+    };
+
     VkDescriptorPool pool;
+    VkDescriptorSetLayout unboxedLayout = 0;
+    std::vector<std::vector<DescriptorWrite>> allWrites;
     std::vector<VkDescriptorSetLayoutBinding> bindings;
 };
 
diff --git a/host/vulkan/VkDecoderSnapshotUtils.cpp b/host/vulkan/VkDecoderSnapshotUtils.cpp
index 921d075..8cdad81 100644
--- a/host/vulkan/VkDecoderSnapshotUtils.cpp
+++ b/host/vulkan/VkDecoderSnapshotUtils.cpp
@@ -584,7 +584,7 @@
     VkBufferCreateInfo bufferCreateInfo = {
         .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
         .size = static_cast<VkDeviceSize>(bufferInfo->size),
-        .usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT,
+        .usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
         .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
     };
     VkBuffer stagingBuffer;