Merge "gfxstream: Replace GuestUsesAngle nomenclature with GuestVulkanOnly." into main
diff --git a/guest/meson.build b/guest/meson.build
index 9c3041a..143fab9 100644
--- a/guest/meson.build
+++ b/guest/meson.build
@@ -35,7 +35,7 @@
 thread_dep = dependency('threads')
 
 if with_guest_test
-  rutabaga_gfx_ffi_dep = dependency('rutabaga_gfx_ffi')
+  virtgpu_kumquat_dep = dependency('virtgpu_kumquat_ffi')
 endif
 
 #===============#
diff --git a/guest/platform/kumquat/VirtGpuKumquat.h b/guest/platform/kumquat/VirtGpuKumquat.h
new file mode 100644
index 0000000..624dece
--- /dev/null
+++ b/guest/platform/kumquat/VirtGpuKumquat.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "VirtGpu.h"
+#include "virtgpu_kumquat/virtgpu_kumquat_ffi.h"
+
+class VirtGpuKumquatResource : public std::enable_shared_from_this<VirtGpuKumquatResource>,
+                               public VirtGpuResource {
+   public:
+    VirtGpuKumquatResource(struct virtgpu_kumquat* virtGpu, uint32_t blobHandle,
+                           uint32_t resourceHandle, uint64_t size);
+    ~VirtGpuKumquatResource();
+
+    uint32_t getResourceHandle() const override;
+    uint32_t getBlobHandle() const override;
+    int wait() override;
+
+    VirtGpuResourceMappingPtr createMapping(void) override;
+    int exportBlob(struct VirtGpuExternalHandle& handle) override;
+
+    int transferFromHost(uint32_t x, uint32_t y, uint32_t w, uint32_t h) override;
+    int transferToHost(uint32_t x, uint32_t y, uint32_t w, uint32_t h) override;
+
+   private:
+    // Not owned.  Really should use a ScopedFD for this, but doesn't matter since we have a
+    // singleton deviceimplemenentation anyways.
+    struct virtgpu_kumquat* mVirtGpu = nullptr;
+    ;
+
+    uint32_t mBlobHandle;
+    uint32_t mResourceHandle;
+    uint64_t mSize;
+};
+
+class VirtGpuKumquatResourceMapping : public VirtGpuResourceMapping {
+   public:
+    VirtGpuKumquatResourceMapping(VirtGpuResourcePtr blob, uint8_t* ptr, uint64_t size);
+    ~VirtGpuKumquatResourceMapping(void);
+
+    uint8_t* asRawPtr(void) override;
+
+   private:
+    VirtGpuResourcePtr mBlob;
+    uint8_t* mPtr;
+    uint64_t mSize;
+};
+
+class VirtGpuKumquatDevice : public VirtGpuDevice {
+   public:
+    VirtGpuKumquatDevice(enum VirtGpuCapset capset, int fd = -1);
+    virtual ~VirtGpuKumquatDevice();
+
+    virtual int64_t getDeviceHandle(void);
+
+    virtual struct VirtGpuCaps getCaps(void);
+
+    VirtGpuResourcePtr createBlob(const struct VirtGpuCreateBlob& blobCreate) override;
+    VirtGpuResourcePtr createResource(uint32_t width, uint32_t height, uint32_t virglFormat,
+                                      uint32_t target, uint32_t bind, uint32_t bpp) override;
+
+    virtual VirtGpuResourcePtr importBlob(const struct VirtGpuExternalHandle& handle);
+    virtual int execBuffer(struct VirtGpuExecBuffer& execbuffer, const VirtGpuResource* blob);
+
+   private:
+    struct virtgpu_kumquat* mVirtGpu = nullptr;
+    ;
+    struct VirtGpuCaps mCaps;
+};
diff --git a/guest/platform/kumquat/VirtGpuKumquatBlob.cpp b/guest/platform/kumquat/VirtGpuKumquatBlob.cpp
new file mode 100644
index 0000000..1a9c99e
--- /dev/null
+++ b/guest/platform/kumquat/VirtGpuKumquatBlob.cpp
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <cutils/log.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <unistd.h>
+
+#include <cerrno>
+#include <cstring>
+
+#include "VirtGpuKumquat.h"
+#include "virtgpu_kumquat/virtgpu_kumquat_ffi.h"
+
+VirtGpuKumquatResource::VirtGpuKumquatResource(struct virtgpu_kumquat* virtGpu, uint32_t blobHandle,
+                                               uint32_t resourceHandle, uint64_t size)
+    : mVirtGpu(virtGpu), mBlobHandle(blobHandle), mResourceHandle(resourceHandle), mSize(size) {}
+
+VirtGpuKumquatResource::~VirtGpuKumquatResource() {
+    struct drm_kumquat_resource_unref unref {
+        .bo_handle = mBlobHandle, .pad = 0,
+    };
+
+    int ret = virtgpu_kumquat_resource_unref(mVirtGpu, &unref);
+    if (ret) {
+        ALOGE("Closed failed with : [%s, blobHandle %u, resourceHandle: %u]", strerror(errno),
+              mBlobHandle, mResourceHandle);
+    }
+}
+
+uint32_t VirtGpuKumquatResource::getBlobHandle() const { return mBlobHandle; }
+
+uint32_t VirtGpuKumquatResource::getResourceHandle() const { return mResourceHandle; }
+
+VirtGpuResourceMappingPtr VirtGpuKumquatResource::createMapping() {
+    int ret;
+    struct drm_kumquat_map map {
+        .bo_handle = mBlobHandle, .ptr = NULL, .size = mSize,
+    };
+
+    ret = virtgpu_kumquat_resource_map(mVirtGpu, &map);
+    if (ret < 0) {
+        ALOGE("Mapping failed with %s", strerror(errno));
+        return nullptr;
+    }
+
+    return std::make_shared<VirtGpuKumquatResourceMapping>(shared_from_this(), (uint8_t*)map.ptr,
+                                                           mSize);
+}
+
+int VirtGpuKumquatResource::exportBlob(struct VirtGpuExternalHandle& handle) {
+    int ret;
+    struct drm_kumquat_resource_export exp = {0};
+
+    exp.bo_handle = mBlobHandle;
+
+    ret = virtgpu_kumquat_resource_export(mVirtGpu, &exp);
+    if (ret) {
+        ALOGE("Failed to export blob with %s", strerror(errno));
+        return ret;
+    }
+
+    handle.osHandle = static_cast<int64_t>(exp.os_handle);
+    handle.type = static_cast<VirtGpuHandleType>(exp.handle_type);
+    return 0;
+}
+
+int VirtGpuKumquatResource::wait() {
+    int ret;
+    struct drm_kumquat_wait wait = {
+        .handle = mBlobHandle,
+        .flags = 0,
+    };
+
+    ret = virtgpu_kumquat_wait(mVirtGpu, &wait);
+    if (ret < 0) {
+        ALOGE("Wait failed with %s", strerror(errno));
+        return ret;
+    }
+
+    return 0;
+}
+
+int VirtGpuKumquatResource::transferToHost(uint32_t x, uint32_t y, uint32_t w, uint32_t h) {
+    int ret;
+    struct drm_kumquat_transfer_to_host xfer = {0};
+
+    xfer.box.x = x;
+    xfer.box.y = y;
+    xfer.box.w = w;
+    xfer.box.h = h;
+    xfer.box.d = 1;
+    xfer.bo_handle = mBlobHandle;
+
+    ret = virtgpu_kumquat_transfer_to_host(mVirtGpu, &xfer);
+    if (ret < 0) {
+        ALOGE("Transfer to host failed with %s", strerror(errno));
+        return ret;
+    }
+
+    return 0;
+}
+
+int VirtGpuKumquatResource::transferFromHost(uint32_t x, uint32_t y, uint32_t w, uint32_t h) {
+    int ret;
+    struct drm_kumquat_transfer_from_host xfer = {0};
+
+    xfer.box.x = x;
+    xfer.box.y = y;
+    xfer.box.w = w;
+    xfer.box.h = h;
+    xfer.box.d = 1;
+    xfer.bo_handle = mBlobHandle;
+
+    ret = virtgpu_kumquat_transfer_from_host(mVirtGpu, &xfer);
+    if (ret < 0) {
+        ALOGE("Transfer from host failed with %s", strerror(errno));
+        return ret;
+    }
+
+    return 0;
+}
diff --git a/guest/platform/kumquat/VirtGpuKumquatBlobMapping.cpp b/guest/platform/kumquat/VirtGpuKumquatBlobMapping.cpp
new file mode 100644
index 0000000..e4869db
--- /dev/null
+++ b/guest/platform/kumquat/VirtGpuKumquatBlobMapping.cpp
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "VirtGpuKumquat.h"
+
+VirtGpuKumquatResourceMapping::VirtGpuKumquatResourceMapping(VirtGpuResourcePtr blob, uint8_t* ptr,
+                                                             uint64_t size)
+    : mBlob(blob), mPtr(ptr), mSize(size) {}
+
+VirtGpuKumquatResourceMapping::~VirtGpuKumquatResourceMapping(void) { return; }
+
+uint8_t* VirtGpuKumquatResourceMapping::asRawPtr(void) { return mPtr; }
diff --git a/guest/platform/kumquat/VirtGpuKumquatDevice.cpp b/guest/platform/kumquat/VirtGpuKumquatDevice.cpp
new file mode 100644
index 0000000..7de219f
--- /dev/null
+++ b/guest/platform/kumquat/VirtGpuKumquatDevice.cpp
@@ -0,0 +1,247 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <cutils/log.h>
+#include <pthread.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <cerrno>
+#include <cstring>
+#include <fstream>
+#include <string>
+
+#include "VirtGpuKumquat.h"
+#include "virtgpu_gfxstream_protocol.h"
+#include "virtgpu_kumquat/virtgpu_kumquat_ffi.h"
+
+#define PARAM(x) \
+    (struct VirtGpuParam) { x, #x, 0 }
+
+static inline uint32_t align_up(uint32_t n, uint32_t a) { return ((n + a - 1) / a) * a; }
+
+VirtGpuKumquatDevice::VirtGpuKumquatDevice(enum VirtGpuCapset capset, int fd)
+    : VirtGpuDevice(capset) {
+    struct VirtGpuParam params[] = {
+        PARAM(VIRTGPU_KUMQUAT_PARAM_3D_FEATURES),
+        PARAM(VIRTGPU_KUMQUAT_PARAM_CAPSET_QUERY_FIX),
+        PARAM(VIRTGPU_KUMQUAT_PARAM_RESOURCE_BLOB),
+        PARAM(VIRTGPU_KUMQUAT_PARAM_HOST_VISIBLE),
+        PARAM(VIRTGPU_KUMQUAT_PARAM_CROSS_DEVICE),
+        PARAM(VIRTGPU_KUMQUAT_PARAM_CONTEXT_INIT),
+        PARAM(VIRTGPU_KUMQUAT_PARAM_SUPPORTED_CAPSET_IDs),
+        PARAM(VIRTGPU_KUMQUAT_PARAM_EXPLICIT_DEBUG_NAME),
+        PARAM(VIRTGPU_KUMQUAT_PARAM_CREATE_GUEST_HANDLE),
+    };
+
+    int ret;
+    struct drm_kumquat_get_caps get_caps = {0};
+    struct drm_kumquat_context_init init = {0};
+    struct drm_kumquat_context_set_param ctx_set_params[3] = {{0}};
+    const char* processName = nullptr;
+
+    memset(&mCaps, 0, sizeof(struct VirtGpuCaps));
+
+#ifdef __ANDROID__
+    processName = getprogname();
+#endif
+
+    ret = virtgpu_kumquat_init(&mVirtGpu);
+    if (ret) {
+        ALOGV("Failed to init virtgpu kumquat");
+        return;
+    }
+
+    for (uint32_t i = 0; i < kParamMax; i++) {
+        struct drm_kumquat_getparam get_param = {0};
+        get_param.param = params[i].param;
+        get_param.value = (uint64_t)(uintptr_t)&params[i].value;
+
+        ret = virtgpu_kumquat_get_param(mVirtGpu, &get_param);
+        if (ret) {
+            ALOGV("virtgpu backend not enabling %s", params[i].name);
+            continue;
+        }
+
+        mCaps.params[i] = params[i].value;
+    }
+
+    get_caps.cap_set_id = static_cast<uint32_t>(capset);
+    switch (capset) {
+        case kCapsetGfxStreamVulkan:
+            get_caps.size = sizeof(struct vulkanCapset);
+            get_caps.addr = (unsigned long long)&mCaps.vulkanCapset;
+            break;
+        case kCapsetGfxStreamMagma:
+            get_caps.size = sizeof(struct magmaCapset);
+            get_caps.addr = (unsigned long long)&mCaps.magmaCapset;
+            break;
+        case kCapsetGfxStreamGles:
+            get_caps.size = sizeof(struct vulkanCapset);
+            get_caps.addr = (unsigned long long)&mCaps.glesCapset;
+            break;
+        case kCapsetGfxStreamComposer:
+            get_caps.size = sizeof(struct vulkanCapset);
+            get_caps.addr = (unsigned long long)&mCaps.composerCapset;
+            break;
+        default:
+            get_caps.size = 0;
+    }
+
+    ret = virtgpu_kumquat_get_caps(mVirtGpu, &get_caps);
+    if (ret) {
+        // Don't fail get capabilities just yet, AEMU doesn't use this API
+        // yet (b/272121235);
+        ALOGE("DRM_IOCTL_VIRTGPU_KUMQUAT_GET_CAPS failed with %s", strerror(errno));
+    }
+
+    // We always need an ASG blob in some cases, so always define blobAlignment
+    if (!mCaps.vulkanCapset.blobAlignment) {
+        mCaps.vulkanCapset.blobAlignment = 4096;
+    }
+
+    ctx_set_params[0].param = VIRTGPU_KUMQUAT_CONTEXT_PARAM_NUM_RINGS;
+    ctx_set_params[0].value = 2;
+    init.num_params = 1;
+
+    if (capset != kCapsetNone) {
+        ctx_set_params[init.num_params].param = VIRTGPU_KUMQUAT_CONTEXT_PARAM_CAPSET_ID;
+        ctx_set_params[init.num_params].value = static_cast<uint32_t>(capset);
+        init.num_params++;
+    }
+
+    if (mCaps.params[kParamExplicitDebugName] && processName) {
+        ctx_set_params[init.num_params].param = VIRTGPU_KUMQUAT_CONTEXT_PARAM_DEBUG_NAME;
+        ctx_set_params[init.num_params].value = reinterpret_cast<uint64_t>(processName);
+        init.num_params++;
+    }
+
+    init.ctx_set_params = (unsigned long long)&ctx_set_params[0];
+    ret = virtgpu_kumquat_context_init(mVirtGpu, &init);
+    if (ret) {
+        ALOGE(
+            "DRM_IOCTL_VIRTGPU_KUMQUAT_CONTEXT_INIT failed with %s, continuing without context...",
+            strerror(errno));
+    }
+}
+
+VirtGpuKumquatDevice::~VirtGpuKumquatDevice() { virtgpu_kumquat_finish(&mVirtGpu); }
+
+struct VirtGpuCaps VirtGpuKumquatDevice::getCaps(void) { return mCaps; }
+
+int64_t VirtGpuKumquatDevice::getDeviceHandle(void) { return -1; }
+
+VirtGpuResourcePtr VirtGpuKumquatDevice::createResource(uint32_t width, uint32_t height,
+                                                        uint32_t virglFormat, uint32_t target,
+                                                        uint32_t bind, uint32_t bpp) {
+    struct drm_kumquat_resource_create_3d create = {
+        .target = target,
+        .format = virglFormat,
+        .bind = bind,
+        .width = width,
+        .height = height,
+        .depth = 1U,
+        .array_size = 1U,
+        .last_level = 0,
+        .nr_samples = 0,
+        .size = width * height * bpp,
+        .stride = width * bpp,
+    };
+
+    int ret = virtgpu_kumquat_resource_create_3d(mVirtGpu, &create);
+    if (ret) {
+        ALOGE("DRM_IOCTL_VIRTGPU_KUMQUAT_RESOURCE_CREATE failed with %s", strerror(errno));
+        return nullptr;
+    }
+
+    return std::make_shared<VirtGpuKumquatResource>(mVirtGpu, create.bo_handle, create.res_handle,
+                                                    static_cast<uint64_t>(create.size));
+}
+
+VirtGpuResourcePtr VirtGpuKumquatDevice::createBlob(const struct VirtGpuCreateBlob& blobCreate) {
+    int ret;
+    struct drm_kumquat_resource_create_blob create = {0};
+
+    create.size = blobCreate.size;
+    create.blob_mem = blobCreate.blobMem;
+    create.blob_flags = blobCreate.flags;
+    create.blob_id = blobCreate.blobId;
+    create.cmd = (uint64_t)(uintptr_t)blobCreate.blobCmd;
+    create.cmd_size = blobCreate.blobCmdSize;
+
+    ret = virtgpu_kumquat_resource_create_blob(mVirtGpu, &create);
+    if (ret < 0) {
+        ALOGE("DRM_VIRTGPU_KUMQUAT_RESOURCE_CREATE_BLOB failed with %s", strerror(errno));
+        return nullptr;
+    }
+
+    return std::make_shared<VirtGpuKumquatResource>(mVirtGpu, create.bo_handle, create.res_handle,
+                                                    blobCreate.size);
+}
+
+VirtGpuResourcePtr VirtGpuKumquatDevice::importBlob(const struct VirtGpuExternalHandle& handle) {
+    int ret;
+    struct drm_kumquat_resource_import resource_import = {0};
+
+    resource_import.os_handle = static_cast<uint64_t>(handle.osHandle);
+    resource_import.handle_type = static_cast<uint32_t>(handle.type);
+
+    ret = virtgpu_kumquat_resource_import(mVirtGpu, &resource_import);
+    if (ret < 0) {
+        ALOGE("DRM_VIRTGPU_KUMQUAT_RESOURCE_IMPORT failed with %s", strerror(errno));
+        return nullptr;
+    }
+
+    return std::make_shared<VirtGpuKumquatResource>(
+        mVirtGpu, resource_import.bo_handle, resource_import.res_handle, resource_import.size);
+    return nullptr;
+}
+
+int VirtGpuKumquatDevice::execBuffer(struct VirtGpuExecBuffer& execbuffer,
+                                     const VirtGpuResource* blob) {
+    int ret;
+    struct drm_kumquat_execbuffer exec = {0};
+    uint32_t blobHandle;
+
+    exec.flags = execbuffer.flags;
+    exec.size = execbuffer.command_size;
+    exec.ring_idx = execbuffer.ring_idx;
+    exec.command = (uint64_t)(uintptr_t)(execbuffer.command);
+    exec.fence_fd = -1;
+
+    if (blob) {
+        blobHandle = blob->getBlobHandle();
+        exec.bo_handles = (uint64_t)(uintptr_t)(&blobHandle);
+        exec.num_bo_handles = 1;
+    }
+
+    ret = virtgpu_kumquat_execbuffer(mVirtGpu, &exec);
+    if (ret) {
+        ALOGE("DRM_IOCTL_VIRTGPU_KUMQUAT_EXECBUFFER failed: %s", strerror(errno));
+        return ret;
+    }
+
+    if (execbuffer.flags & kFenceOut) {
+        execbuffer.handle.osHandle = exec.fence_fd;
+        execbuffer.handle.type = kFenceHandleSyncFd;
+    }
+
+    return 0;
+}
+
+VirtGpuDevice* createPlatformVirtGpuDevice(enum VirtGpuCapset capset, int fd) {
+    return new VirtGpuKumquatDevice(capset, fd);
+}
diff --git a/guest/platform/kumquat/VirtGpuKumquatSync.cpp b/guest/platform/kumquat/VirtGpuKumquatSync.cpp
new file mode 100644
index 0000000..d152bff
--- /dev/null
+++ b/guest/platform/kumquat/VirtGpuKumquatSync.cpp
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "VirtGpuKumquatSync.h"
+
+#include <unistd.h>
+
+namespace gfxstream {
+
+VirtGpuKumquatSyncHelper::VirtGpuKumquatSyncHelper() {}
+
+int VirtGpuKumquatSyncHelper::wait(int syncFd, int timeoutMilliseconds) {
+    (void)syncFd;
+    (void)timeoutMilliseconds;
+    return -1;
+}
+
+int VirtGpuKumquatSyncHelper::dup(int syncFd) { return ::dup(syncFd); }
+
+int VirtGpuKumquatSyncHelper::close(int syncFd) { return ::close(syncFd); }
+
+SyncHelper* createPlatformSyncHelper() { return new VirtGpuKumquatSyncHelper(); }
+
+}  // namespace gfxstream
diff --git a/guest/platform/kumquat/VirtGpuKumquatSync.h b/guest/platform/kumquat/VirtGpuKumquatSync.h
new file mode 100644
index 0000000..c8b89e6
--- /dev/null
+++ b/guest/platform/kumquat/VirtGpuKumquatSync.h
@@ -0,0 +1,32 @@
+// Copyright 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either expresso or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+
+#include "Sync.h"
+
+namespace gfxstream {
+
+class VirtGpuKumquatSyncHelper : public SyncHelper {
+   public:
+    VirtGpuKumquatSyncHelper();
+
+    int wait(int syncFd, int timeoutMilliseconds) override;
+
+    int dup(int syncFd) override;
+
+    int close(int syncFd) override;
+};
+
+}  // namespace gfxstream
diff --git a/guest/platform/kumquat/meson.build b/guest/platform/kumquat/meson.build
new file mode 100644
index 0000000..bd1b4f9
--- /dev/null
+++ b/guest/platform/kumquat/meson.build
@@ -0,0 +1,18 @@
+# Copyright 2022 Android Open Source Project
+# SPDX-License-Identifier: MIT
+
+files_lib_platform = files(
+  '../VirtGpu.cpp',
+  'VirtGpuKumquatDevice.cpp',
+  'VirtGpuKumquatBlobMapping.cpp',
+  'VirtGpuKumquatBlob.cpp',
+  'VirtGpuKumquatSync.cpp',
+)
+
+lib_platform = static_library(
+   'platform',
+   files_lib_platform,
+   cpp_args: gfxstream_guest_args,
+   include_directories: [inc_platform, inc_android_compat],
+   dependencies: virtgpu_kumquat_dep,
+)
diff --git a/guest/platform/meson.build b/guest/platform/meson.build
index 0561499..ba0f6d6 100644
--- a/guest/platform/meson.build
+++ b/guest/platform/meson.build
@@ -3,7 +3,7 @@
 
 inc_platform = include_directories('include')
 if with_guest_test
-  subdir('rutabaga')
+  subdir('kumquat')
 else
   subdir('linux')
   subdir('stub')
diff --git a/guest/platform/rutabaga/meson.build b/guest/platform/rutabaga/meson.build
deleted file mode 100644
index 112d216..0000000
--- a/guest/platform/rutabaga/meson.build
+++ /dev/null
@@ -1,20 +0,0 @@
-# Copyright 2022 Android Open Source Project
-# SPDX-License-Identifier: MIT
-
-files_lib_platform = files(
-  '../VirtGpu.cpp',
-  'RutabagaLayer.cpp',
-  'RutabagaVirtGpuBlob.cpp',
-  'RutabagaVirtGpuBlobMapping.cpp',
-  'RutabagaVirtGpuSyncHelper.cpp',
-  'RutabagaVirtGpuDevice.cpp',
-)
-
-lib_platform = static_library(
-   'platform',
-   files_lib_platform,
-   cpp_args: gfxstream_guest_args,
-   include_directories: [inc_platform, inc_android_compat],
-   link_with: [lib_android_compat],
-   dependencies: [drm_dep, rutabaga_gfx_ffi_dep],
-)
diff --git a/host/Buffer.cpp b/host/Buffer.cpp
index b60a1ec..84459db 100644
--- a/host/Buffer.cpp
+++ b/host/Buffer.cpp
@@ -138,4 +138,12 @@
     return false;
 }
 
+std::optional<ManagedDescriptorInfo> Buffer::exportBlob() {
+    if (!mBufferVk) {
+        return std::nullopt;
+    }
+
+    return mBufferVk->exportBlob();
+}
+
 }  // namespace gfxstream
diff --git a/host/Buffer.h b/host/Buffer.h
index 414ff05..cf2b15e 100644
--- a/host/Buffer.h
+++ b/host/Buffer.h
@@ -16,6 +16,7 @@
 
 #include <memory>
 
+#include "BlobManager.h"
 #include "Handle.h"
 #include "aemu/base/files/Stream.h"
 #include "snapshot/LazySnapshotObj.h"
@@ -57,6 +58,7 @@
 
     void readToBytes(uint64_t offset, uint64_t size, void* outBytes);
     bool updateFromBytes(uint64_t offset, uint64_t size, const void* bytes);
+    std::optional<ManagedDescriptorInfo> exportBlob();
 
    private:
     Buffer(HandleType handle, uint64_t size);
diff --git a/host/ColorBuffer.cpp b/host/ColorBuffer.cpp
index 1664680..159f8e2 100644
--- a/host/ColorBuffer.cpp
+++ b/host/ColorBuffer.cpp
@@ -455,6 +455,14 @@
     return mColorBufferVk->waitSync();
 }
 
+std::optional<ManagedDescriptorInfo> ColorBuffer::exportBlob() {
+    if (!mColorBufferVk) {
+        return std::nullopt;
+    }
+
+    return mColorBufferVk->exportBlob();
+}
+
 #if GFXSTREAM_ENABLE_HOST_GLES
 bool ColorBuffer::glOpBlitFromCurrentReadBuffer() {
     if (!mColorBufferGl) {
diff --git a/host/ColorBuffer.h b/host/ColorBuffer.h
index 43a3330..ba6a9ad 100644
--- a/host/ColorBuffer.h
+++ b/host/ColorBuffer.h
@@ -16,6 +16,7 @@
 
 #include <memory>
 
+#include "BlobManager.h"
 #include "BorrowedImage.h"
 #include "FrameworkFormats.h"
 #include "Handle.h"
@@ -94,6 +95,7 @@
     bool importNativeResource(void* nativeResource, uint32_t type, bool preserveContent);
 
     int waitSync();
+    std::optional<ManagedDescriptorInfo> exportBlob();
 
 #if GFXSTREAM_ENABLE_HOST_GLES
     GLuint glOpGetTexture();
diff --git a/host/FrameBuffer.cpp b/host/FrameBuffer.cpp
index 2c4cd66..cd02bab 100644
--- a/host/FrameBuffer.cpp
+++ b/host/FrameBuffer.cpp
@@ -2980,6 +2980,28 @@
     return colorBuffer->waitSync();
 }
 
+std::optional<ManagedDescriptorInfo> FrameBuffer::exportColorBuffer(HandleType colorBufferHandle) {
+    AutoLock mutex(m_lock);
+
+    ColorBufferPtr colorBuffer = findColorBuffer(colorBufferHandle);
+    if (!colorBuffer) {
+        return std::nullopt;
+    }
+
+    return colorBuffer->exportBlob();
+}
+
+std::optional<ManagedDescriptorInfo> FrameBuffer::exportBuffer(HandleType bufferHandle) {
+    AutoLock mutex(m_lock);
+
+    BufferPtr buffer = findBuffer(bufferHandle);
+    if (!buffer) {
+        return std::nullopt;
+    }
+
+    return buffer->exportBlob();
+}
+
 #if GFXSTREAM_ENABLE_HOST_GLES
 HandleType FrameBuffer::getEmulatedEglWindowSurfaceColorBufferHandle(HandleType p_surface) {
     AutoLock mutex(m_lock);
diff --git a/host/FrameBuffer.h b/host/FrameBuffer.h
index c606470..52104df 100644
--- a/host/FrameBuffer.h
+++ b/host/FrameBuffer.h
@@ -26,6 +26,7 @@
 #include <unordered_map>
 #include <unordered_set>
 
+#include "BlobManager.h"
 #include "Buffer.h"
 #include "ColorBuffer.h"
 #include "Compositor.h"
@@ -498,6 +499,8 @@
     bool invalidateColorBufferForVk(HandleType colorBufferHandle);
 
     int waitSyncColorBuffer(HandleType colorBufferHandle);
+    std::optional<ManagedDescriptorInfo> exportColorBuffer(HandleType colorBufferHandle);
+    std::optional<ManagedDescriptorInfo> exportBuffer(HandleType bufferHandle);
 
 #if GFXSTREAM_ENABLE_HOST_GLES
     // Retrieves the color buffer handle associated with |p_surface|.
diff --git a/host/vulkan/BufferVk.cpp b/host/vulkan/BufferVk.cpp
index 46f4540..a8ec4f3 100644
--- a/host/vulkan/BufferVk.cpp
+++ b/host/vulkan/BufferVk.cpp
@@ -45,5 +45,21 @@
     return updateBufferFromBytes(mHandle, offset, size, bytes);
 }
 
+std::optional<ManagedDescriptorInfo> BufferVk::exportBlob() {
+    uint32_t streamHandleType = 0;
+    auto vkHandle = getBufferExtMemoryHandle(mHandle, &streamHandleType);
+    if (vkHandle != VK_EXT_MEMORY_HANDLE_INVALID) {
+        ManagedDescriptor descriptor(dupExternalMemory(vkHandle));
+        return ManagedDescriptorInfo{
+            .descriptor = std::move(descriptor),
+            .handleType = streamHandleType,
+            .caching = 0,
+            .vulkanInfoOpt = std::nullopt,
+        };
+    } else {
+        return std::nullopt;
+    }
+}
+
 }  // namespace vk
-}  // namespace gfxstream
\ No newline at end of file
+}  // namespace gfxstream
diff --git a/host/vulkan/BufferVk.h b/host/vulkan/BufferVk.h
index 9016fd7..1df6b55 100644
--- a/host/vulkan/BufferVk.h
+++ b/host/vulkan/BufferVk.h
@@ -15,6 +15,8 @@
 #include <memory>
 #include <vector>
 
+#include "BlobManager.h"
+
 namespace gfxstream {
 namespace vk {
 
@@ -28,6 +30,8 @@
 
     bool updateFromBytes(uint64_t offset, uint64_t size, const void* bytes);
 
+    std::optional<ManagedDescriptorInfo> exportBlob();
+
    private:
     BufferVk(uint32_t handle);
 
diff --git a/host/vulkan/ColorBufferVk.cpp b/host/vulkan/ColorBufferVk.cpp
index 9461510..cd82b15 100644
--- a/host/vulkan/ColorBufferVk.cpp
+++ b/host/vulkan/ColorBufferVk.cpp
@@ -80,5 +80,19 @@
 
 int ColorBufferVk::waitSync() { return waitSyncVkColorBuffer(mHandle); }
 
+std::optional<ManagedDescriptorInfo> ColorBufferVk::exportBlob() {
+    auto info = exportColorBufferMemory(mHandle);
+    if (info) {
+        return ManagedDescriptorInfo{
+            .descriptor = std::move((*info).descriptor),
+            .handleType = (*info).streamHandleType,
+            .caching = 0,
+            .vulkanInfoOpt = std::nullopt,
+        };
+    } else {
+        return std::nullopt;
+    }
+}
+
 }  // namespace vk
 }  // namespace gfxstream
diff --git a/host/vulkan/ColorBufferVk.h b/host/vulkan/ColorBufferVk.h
index 81e1e42..e7187b9 100644
--- a/host/vulkan/ColorBufferVk.h
+++ b/host/vulkan/ColorBufferVk.h
@@ -17,6 +17,7 @@
 #include <memory>
 #include <vector>
 
+#include "BlobManager.h"
 #include "FrameworkFormats.h"
 #include "aemu/base/files/Stream.h"
 
@@ -43,6 +44,7 @@
     void onSave(android::base::Stream* stream);
 
     int waitSync();
+    std::optional<ManagedDescriptorInfo> exportBlob();
 
    private:
     ColorBufferVk(uint32_t handle);