Support AHARDWAREBUFFER_FORMAT_BLOB buffers

by mapping BLOBs to `Buffer`s instead of `ColorBuffer`s and by
adding the needed plumbing for reads and updates.

Note: this is not a fully complete solution as technically a
guest not running with ANGLE could still have native Vulkan
users which import the BLOB AHBs. This has not yet been observed
so this might be okay for now.

Bug: b/234513607
Test: launch_cvd --gpu_mode=gfxstream (with WIP ANGLE)
Test: cts -m CtsNativeHardwareTestCases
Test: ANGLE's `ExternalBufferTestES31.*/ES3_1_Vulkan` tests
Change-Id: I782559e5a57cadc9568d46b2e0949924a6493810
diff --git a/stream-servers/FrameBuffer.cpp b/stream-servers/FrameBuffer.cpp
index 077a490..b4f6fd0 100644
--- a/stream-servers/FrameBuffer.cpp
+++ b/stream-servers/FrameBuffer.cpp
@@ -1599,12 +1599,34 @@
     return handle;
 }
 
+void FrameBuffer::createBufferWithHandle(uint64_t size, HandleType handle) {
+    {
+        AutoLock mutex(m_lock);
+        AutoLock colorBufferMapLock(m_colorBufferMapLock);
+
+        // Check for handle collision
+        if (m_buffers.count(handle) != 0) {
+            GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER))
+                << "Buffer already exists with handle " << handle;
+        }
+
+        handle = createBufferWithHandleLocked(size, handle);
+        if (!handle) {
+            return;
+        }
+    }
+
+    if (m_displayVk || m_guestUsesAngle) {
+        goldfish_vk::setupVkBuffer(handle, /* vulkanOnly */ true,
+                                   VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+    }
+}
+
 HandleType FrameBuffer::createBufferWithHandleLocked(int p_size,
                                                      HandleType handle) {
     if (m_buffers.count(handle) != 0) {
-        // emugl::emugl_crash_reporter(
-        //         "FATAL: buffer with handle %u already exists", handle);
-        // abort();
+        GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER))
+            << "Buffer already exists with handle " << handle;
     }
 
     BufferPtr buffer(Buffer::create(p_size, handle, m_colorBufferHelper));
@@ -2201,6 +2223,23 @@
     return true;
 }
 
+void FrameBuffer::readBuffer(HandleType handle, uint64_t offset, uint64_t size, void* bytes) {
+    if (m_guestUsesAngle) {
+        goldfish_vk::readBufferToBytes(handle, offset, size, bytes);
+        return;
+    }
+
+    AutoLock mutex(m_lock);
+
+    BufferPtr buffer = findBuffer(handle);
+    if (!buffer) {
+        ERR("Failed to read buffer: buffer %d not found.", handle);
+        return;
+    }
+
+    buffer->read(offset, size, bytes);
+}
+
 void FrameBuffer::readColorBuffer(HandleType p_colorbuffer,
                                   int x,
                                   int y,
@@ -2336,6 +2375,24 @@
                       nullptr);
 }
 
+bool FrameBuffer::updateBuffer(HandleType p_buffer, uint64_t offset, uint64_t size, void* bytes) {
+    if (m_guestUsesAngle) {
+        return goldfish_vk::updateBufferFromBytes(p_buffer, offset, size, bytes);
+    }
+
+    AutoLock mutex(m_lock);
+
+    BufferPtr buffer = findBuffer(p_buffer);
+    if (!buffer) {
+        ERR("Failed to update buffer: buffer %d not found.", p_buffer);
+        return false;
+    }
+
+    buffer->subUpdate(offset, size, bytes);
+
+    return true;
+}
+
 bool FrameBuffer::updateColorBuffer(HandleType p_colorbuffer,
                                     int x,
                                     int y,
@@ -3441,6 +3498,16 @@
     }
 }
 
+BufferPtr FrameBuffer::findBuffer(HandleType p_buffer) {
+    AutoLock colorBufferMapLock(m_colorBufferMapLock);
+    BufferMap::iterator b(m_buffers.find(p_buffer));
+    if (b == m_buffers.end()) {
+        return nullptr;
+    } else {
+        return b->second.buffer;
+    }
+}
+
 void FrameBuffer::registerProcessCleanupCallback(void* key, std::function<void()> cb) {
     AutoLock mutex(m_lock);
     RenderThreadInfo* tInfo = RenderThreadInfo::get();
diff --git a/stream-servers/FrameBuffer.h b/stream-servers/FrameBuffer.h
index 3b46ede..7a94bb9 100644
--- a/stream-servers/FrameBuffer.h
+++ b/stream-servers/FrameBuffer.h
@@ -234,10 +234,8 @@
     // Variant of createColorBuffer except with a particular
     // handle already assigned. This is for use with
     // virtio-gpu's RESOURCE_CREATE ioctl.
-    void createColorBufferWithHandle(int p_width, int p_height,
-                                     GLenum p_internalFormat,
-                                     FrameworkFormat p_frameworkFormat,
-                                     HandleType handle);
+    void createColorBufferWithHandle(int p_width, int p_height, GLenum p_internalFormat,
+                                     FrameworkFormat p_frameworkFormat, HandleType handle);
 
     // Create a new data Buffer instance from this display instance.
     // The buffer will be backed by a VkBuffer and VkDeviceMemory (if Vulkan
@@ -247,6 +245,11 @@
     // memory.
     HandleType createBuffer(uint64_t size, uint32_t memoryProperty);
 
+    // Variant of createBuffer except with a particular handle already
+    // assigned and using device local memory. This is for use with
+    // virtio-gpu's RESOURCE_CREATE ioctl for BLOB resources.
+    void createBufferWithHandle(uint64_t size, HandleType handle);
+
     // Call this function when a render thread terminates to destroy all
     // the remaining contexts it created. Necessary to avoid leaking host
     // contexts when a guest application crashes, for example.
@@ -331,6 +334,14 @@
     // Returns true on success, false on failure.
     bool bindColorBufferToRenderbuffer(HandleType p_colorbuffer);
 
+    // Read the content of a given Buffer into client memory.
+    // |p_buffer| is the Buffer's handle value.
+    // |offset| and |size| are the position and size of a slice of the buffer
+    // that will be read.
+    // |bytes| is the address of a caller-provided buffer that will be filled
+    // with the buffer data.
+    void readBuffer(HandleType p_buffer, uint64_t offset, uint64_t size, void* bytes);
+
     // Read the content of a given ColorBuffer into client memory.
     // |p_colorbuffer| is the ColorBuffer's handle value. Similar
     // to glReadPixels(), this can be a slow operation.
@@ -367,6 +378,14 @@
                                           uint32_t texture_type,
                                           uint32_t* textures);
 
+    // Update the content of a given Buffer from client data.
+    // |p_buffer| is the Buffer's handle value.
+    // |offset| and |size| are the position and size of a slice of the buffer
+    // that will be updated.
+    // |bytes| is the address of a caller-provided buffer containing the new
+    // buffer data.
+    bool updateBuffer(HandleType p_buffer, uint64_t offset, uint64_t size, void* pixels);
+
     // Update the content of a given ColorBuffer from client data.
     // |p_colorbuffer| is the ColorBuffer's handle value. Similar
     // to glReadPixels(), this can be a slow operation.
@@ -544,6 +563,7 @@
     void onLastColorBufferRef(uint32_t handle);
     ContextHelper* getColorBufferHelper() { return m_colorBufferHelper; }
     ColorBufferPtr findColorBuffer(HandleType p_colorbuffer);
+    BufferPtr findBuffer(HandleType p_buffer);
 
     void registerProcessCleanupCallback(void* key,
                                         std::function<void()> callback);
diff --git a/stream-servers/RendererImpl.cpp b/stream-servers/RendererImpl.cpp
index ee23f75..bfb5ea4 100644
--- a/stream-servers/RendererImpl.cpp
+++ b/stream-servers/RendererImpl.cpp
@@ -13,20 +13,18 @@
 // limitations under the License.
 #include "RendererImpl.h"
 
-#include "RenderChannelImpl.h"
-#include "RenderThread.h"
-
-#include "base/System.h"
-#include "snapshot/common.h"
-#include "host-common/logging.h"
-
-#include "FenceSync.h"
-#include "FrameBuffer.h"
+#include <assert.h>
 
 #include <algorithm>
 #include <utility>
 
-#include <assert.h>
+#include "FenceSync.h"
+#include "FrameBuffer.h"
+#include "RenderChannelImpl.h"
+#include "RenderThread.h"
+#include "base/System.h"
+#include "host-common/logging.h"
+#include "snapshot/common.h"
 
 namespace emugl {
 
@@ -491,6 +489,10 @@
 }
 
 static struct AndroidVirtioGpuOps sVirtioGpuOps = {
+        .create_buffer_with_handle =
+                [](uint64_t size, uint32_t handle) {
+                    FrameBuffer::getFB()->createBufferWithHandle(size, handle);
+                },
         .create_color_buffer_with_handle =
                 [](uint32_t width,
                    uint32_t height,
@@ -505,10 +507,22 @@
                 [](uint32_t handle) {
                     FrameBuffer::getFB()->openColorBuffer(handle);
                 },
+        .close_buffer =
+                [](uint32_t handle) {
+                    FrameBuffer::getFB()->closeBuffer(handle);
+                },
         .close_color_buffer =
                 [](uint32_t handle) {
                     FrameBuffer::getFB()->closeColorBuffer(handle);
                 },
+        .update_buffer =
+                [](uint32_t handle,
+                   uint64_t offset,
+                   uint64_t size,
+                   void* bytes) {
+                    FrameBuffer::getFB()->updateBuffer(
+                            handle, offset, size, bytes);
+                },
         .update_color_buffer =
                 [](uint32_t handle,
                    int x,
@@ -521,6 +535,14 @@
                     FrameBuffer::getFB()->updateColorBuffer(
                             handle, x, y, width, height, format, type, pixels);
                 },
+        .read_buffer =
+                [](uint32_t handle,
+                   uint64_t offset,
+                   uint64_t size,
+                   void* bytes) {
+                    FrameBuffer::getFB()->readBuffer(
+                            handle, offset, size, bytes);
+                },
         .read_color_buffer =
                 [](uint32_t handle,
                    int x,
diff --git a/stream-servers/virgl_hw.h b/stream-servers/virgl_hw.h
index 22fd0c4..d30e810 100644
--- a/stream-servers/virgl_hw.h
+++ b/stream-servers/virgl_hw.h
@@ -340,6 +340,8 @@
  */
 #define VIRGL_BIND_STAGING       (1 << 19)
 #define VIRGL_BIND_SHARED        (1 << 20)
+#define VIRGL_BIND_PREFER_EMULATED_BGRA (1 << 21)
+#define VIRGL_BIND_LINEAR (1 << 22)
 
 #define VIRGL_RESOURCE_Y_0_TOP (1 << 0)
 #endif
diff --git a/stream-servers/virtio-gpu-gfxstream-renderer.cpp b/stream-servers/virtio-gpu-gfxstream-renderer.cpp
index e3f435c..d023cb0 100644
--- a/stream-servers/virtio-gpu-gfxstream-renderer.cpp
+++ b/stream-servers/virtio-gpu-gfxstream-renderer.cpp
@@ -167,6 +167,16 @@
     bool hasAddressSpaceHandle;
 };
 
+enum class ResType {
+    // Used as a communication channel between the guest and the host
+    // which does not need an allocation on the host GPU.
+    PIPE,
+    // Used as a GPU data buffer.
+    BUFFER,
+    // Used as a GPU texture.
+    COLOR_BUFFER,
+};
+
 struct PipeResEntry {
     virgl_renderer_resource_create_args args;
     iovec* iov;
@@ -180,6 +190,7 @@
     uint64_t blobId;
     uint32_t hvSlot;
     uint32_t caching;
+    ResType type;
 };
 
 static inline uint32_t align_up(uint32_t n, uint32_t a) {
@@ -948,26 +959,48 @@
 #define PIPE_BIND_COMMAND_ARGS_BUFFER  (1 << 17) /* pipe_draw_info.indirect */
 #define PIPE_BIND_QUERY_BUFFER         (1 << 18) /* get_query_result_resource */
 
-
-    void handleCreateResourceGraphicsUsage(
-            struct virgl_renderer_resource_create_args *args,
-            struct iovec *iov, uint32_t num_iovs) {
-
-        if (args->target == PIPE_BUFFER) {
-            // Nothing to handle; this is generic pipe usage.
-            return;
+    ResType getResourceType(const struct virgl_renderer_resource_create_args& args) const {
+        if (args.target == PIPE_BUFFER) {
+            return ResType::PIPE;
         }
 
+        if (args.format != VIRGL_FORMAT_R8_UNORM) {
+            return ResType::COLOR_BUFFER;
+        }
+        if (args.bind & VIRGL_BIND_SAMPLER_VIEW) {
+            return ResType::COLOR_BUFFER;
+        }
+        if (args.bind & VIRGL_BIND_RENDER_TARGET) {
+            return ResType::COLOR_BUFFER;
+        }
+        if (args.bind & VIRGL_BIND_SCANOUT) {
+            return ResType::COLOR_BUFFER;
+        }
+        if (args.bind & VIRGL_BIND_CURSOR) {
+            return ResType::COLOR_BUFFER;
+        }
+        if (!(args.bind & VIRGL_BIND_LINEAR)) {
+            return ResType::COLOR_BUFFER;
+        }
+
+        return ResType::BUFFER;
+    }
+
+    void handleCreateResourceBuffer(struct virgl_renderer_resource_create_args* args) {
+        mVirtioGpuOps->create_buffer_with_handle(args->width * args->height, args->handle);
+    }
+
+    void handleCreateResourceColorBuffer(struct virgl_renderer_resource_create_args* args) {
         // corresponds to allocation of gralloc buffer in minigbm
         VGPLOG("w h %u %u resid %u -> rcCreateColorBufferWithHandle",
                args->width, args->height, args->handle);
-        uint32_t glformat = virgl_format_to_gl(args->format);
-        uint32_t fwkformat = virgl_format_to_fwk_format(args->format);
-        mVirtioGpuOps->create_color_buffer_with_handle(
-            args->width, args->height, glformat, fwkformat, args->handle);
+
+        const uint32_t glformat = virgl_format_to_gl(args->format);
+        const uint32_t fwkformat = virgl_format_to_fwk_format(args->format);
+        mVirtioGpuOps->create_color_buffer_with_handle(args->width, args->height, glformat,
+                                                       fwkformat, args->handle);
         mVirtioGpuOps->set_guest_managed_color_buffer_lifetime(true /* guest manages lifetime */);
-        mVirtioGpuOps->open_color_buffer(
-            args->handle);
+        mVirtioGpuOps->open_color_buffer(args->handle);
     }
 
     int createResource(
@@ -976,7 +1009,17 @@
 
         VGPLOG("handle: %u. num iovs: %u", args->handle, num_iovs);
 
-        handleCreateResourceGraphicsUsage(args, iov, num_iovs);
+        const auto resType = getResourceType(*args);
+        switch (resType) {
+            case ResType::PIPE:
+                break;
+            case ResType::BUFFER:
+                handleCreateResourceBuffer(args);
+                break;
+            case ResType::COLOR_BUFFER:
+                handleCreateResourceColorBuffer(args);
+                break;
+        }
 
         PipeResEntry e;
         e.args = *args;
@@ -986,6 +1029,7 @@
         e.hvaSize = 0;
         e.blobId = 0;
         e.hvSlot = 0;
+        e.type = resType;
         allocResource(e, iov, num_iovs);
 
         AutoLock lock(mLock);
@@ -993,11 +1037,6 @@
         return 0;
     }
 
-    void handleUnrefResourceGraphicsUsage(PipeResEntry* res, uint32_t resId) {
-        if (res->args.target == PIPE_BUFFER) return;
-        mVirtioGpuOps->close_color_buffer(resId);
-    }
-
     void unrefResource(uint32_t toUnrefId) {
         AutoLock lock(mLock);
         VGPLOG("handle: %u", toUnrefId);
@@ -1015,8 +1054,16 @@
         }
 
         auto& entry = it->second;
-
-        handleUnrefResourceGraphicsUsage(&entry, toUnrefId);
+        switch (entry.type) {
+            case ResType::PIPE:
+                break;
+            case ResType::BUFFER:
+                mVirtioGpuOps->close_buffer(toUnrefId);
+                break;
+            case ResType::COLOR_BUFFER:
+                mVirtioGpuOps->close_color_buffer(toUnrefId);
+                break;
+        }
 
         if (entry.linear) {
             free(entry.linear);
@@ -1081,56 +1128,145 @@
         VGPLOG("done");
     }
 
-    bool handleTransferReadGraphicsUsage(
-        PipeResEntry* res, uint64_t offset, virgl_box* box) {
-        // PIPE_BUFFER: Generic pipe usage
-        if (res->args.target == PIPE_BUFFER) return true;
+    int handleTransferReadPipe(PipeResEntry* res, uint64_t offset, virgl_box* box) {
+        if (res->type != ResType::PIPE) {
+            GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER))
+                << "Resource " << res->args.handle << " is not a PIPE resource.";
+            return -1;
+        }
 
-        // Others: Gralloc transfer read operation
+        // Do the pipe service op here, if there is an associated hostpipe.
+        auto hostPipe = res->hostPipe;
+        if (!hostPipe) return -1;
+
+        auto ops = ensureAndGetServiceOps();
+
+        size_t readBytes = 0;
+        size_t wantedBytes = readBytes + (size_t)box->w;
+
+        while (readBytes < wantedBytes) {
+            GoldfishPipeBuffer buf = {
+                ((char*)res->linear) + box->x + readBytes,
+                wantedBytes - readBytes,
+            };
+            auto status = ops->guest_recv(hostPipe, &buf, 1);
+
+            if (status > 0) {
+                readBytes += status;
+            } else if (status != kPipeTryAgain) {
+                return EIO;
+            }
+        }
+
+        return 0;
+    }
+
+    int handleTransferWritePipe(PipeResEntry* res, uint64_t offset, virgl_box* box) {
+        if (res->type != ResType::PIPE) {
+            GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER))
+                << "Resource " << res->args.handle << " is not a PIPE resource.";
+            return -1;
+        }
+
+        // Do the pipe service op here, if there is an associated hostpipe.
+        auto hostPipe = res->hostPipe;
+        if (!hostPipe) {
+            VGPLOG("No hostPipe");
+            return -1;
+        }
+
+        VGPLOG("resid: %d offset: 0x%llx hostpipe: %p", res->args.handle,
+               (unsigned long long)offset, hostPipe);
+
+        auto ops = ensureAndGetServiceOps();
+
+        size_t writtenBytes = 0;
+        size_t wantedBytes = (size_t)box->w;
+
+        while (writtenBytes < wantedBytes) {
+            GoldfishPipeBuffer buf = {
+                ((char*)res->linear) + box->x + writtenBytes,
+                wantedBytes - writtenBytes,
+            };
+
+            // guest_send can now reallocate the pipe.
+            void* hostPipeBefore = hostPipe;
+            auto status = ops->guest_send(&hostPipe, &buf, 1);
+            if (hostPipe != hostPipeBefore) {
+                resetPipe((GoldfishHwPipe*)(uintptr_t)(res->ctxId), hostPipe);
+                auto it = mResources.find(res->args.handle);
+                res = &it->second;
+            }
+
+            if (status > 0) {
+                writtenBytes += status;
+            } else if (status != kPipeTryAgain) {
+                return EIO;
+            }
+        }
+
+        return 0;
+    }
+
+    int handleTransferReadBuffer(PipeResEntry* res, uint64_t offset, virgl_box* box) {
+        if (res->type != ResType::BUFFER) {
+            GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER))
+                << "Resource " << res->args.handle << " is not a BUFFER resource.";
+            return -1;
+        }
+
+        mVirtioGpuOps->read_buffer(res->args.handle, 0, res->args.width * res->args.height,
+                                   res->linear);
+        return 0;
+    }
+
+    int handleTransferWriteBuffer(PipeResEntry* res, uint64_t offset, virgl_box* box) {
+        if (res->type != ResType::BUFFER) {
+            GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER))
+                << res->args.handle << " is not a BUFFER resource.";
+            return -1;
+        }
+
+        mVirtioGpuOps->update_buffer(res->args.handle, 0, res->args.width * res->args.height,
+                                     res->linear);
+        return 0;
+    }
+
+    void handleTransferReadColorBuffer(PipeResEntry* res, uint64_t offset, virgl_box* box) {
+        if (res->type != ResType::COLOR_BUFFER) {
+            GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER))
+                << "Resource " << res->args.handle << " is not a COLOR_BUFFER resource.";
+            return;
+        }
+
         auto glformat = virgl_format_to_gl(res->args.format);
         auto gltype = gl_format_to_natural_type(glformat);
 
         // We always xfer the whole thing again from GL
         // since it's fiddly to calc / copy-out subregions
         if (virgl_format_is_yuv(res->args.format)) {
-            mVirtioGpuOps->read_color_buffer_yuv(
-                res->args.handle,
-                0, 0,
-                res->args.width, res->args.height,
-                res->linear, res->linearSize);
+            mVirtioGpuOps->read_color_buffer_yuv(res->args.handle, 0, 0, res->args.width,
+                                                 res->args.height, res->linear, res->linearSize);
         } else {
-            mVirtioGpuOps->read_color_buffer(
-                res->args.handle,
-                0, 0,
-                res->args.width, res->args.height,
-                glformat,
-                gltype,
-                res->linear);
+            mVirtioGpuOps->read_color_buffer(res->args.handle, 0, 0, res->args.width,
+                                             res->args.height, glformat, gltype, res->linear);
         }
-
-        return false;
     }
 
-    bool handleTransferWriteGraphicsUsage(
-        PipeResEntry* res, uint64_t offset, virgl_box* box) {
-        // PIPE_BUFFER: Generic pipe usage
-        if (res->args.target == PIPE_BUFFER) return true;
+    void handleTransferWriteColorBuffer(PipeResEntry* res, uint64_t offset, virgl_box* box) {
+        if (res->type != ResType::COLOR_BUFFER) {
+            GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER))
+                << "Resource " << res->args.handle << " is not a COLOR_BUFFER resource.";
+            return;
+        }
 
-        // Others: Gralloc transfer read operation
         auto glformat = virgl_format_to_gl(res->args.format);
         auto gltype = gl_format_to_natural_type(glformat);
 
         // We always xfer the whole thing again to GL
         // since it's fiddly to calc / copy-out subregions
-        mVirtioGpuOps->update_color_buffer(
-            res->args.handle,
-            0, 0,
-            res->args.width, res->args.height,
-            glformat,
-            gltype,
-            res->linear);
-
-        return false;
+        mVirtioGpuOps->update_color_buffer(res->args.handle, 0, 0, res->args.width,
+                                           res->args.height, glformat, gltype, res->linear);
     }
 
     int transferReadIov(int resId, uint64_t offset, virgl_box* box, struct iovec* iov, int iovec_cnt) {
@@ -1146,38 +1282,27 @@
         auto it = mResources.find(resId);
         if (it == mResources.end()) return EINVAL;
 
+        int ret = 0;
+
         auto& entry = it->second;
+        switch (entry.type) {
+            case ResType::PIPE:
+                ret = handleTransferReadPipe(&entry, offset, box);
+                break;
+            case ResType::BUFFER:
+                ret = handleTransferReadBuffer(&entry, offset, box);
+                break;
+            case ResType::COLOR_BUFFER:
+                handleTransferReadColorBuffer(&entry, offset, box);
+                break;
+        }
 
-        if (handleTransferReadGraphicsUsage(
-            &entry, offset, box)) {
-            // Do the pipe service op here, if there is an associated hostpipe.
-            auto hostPipe = entry.hostPipe;
-            if (!hostPipe) return -1;
-
-            auto ops = ensureAndGetServiceOps();
-
-            size_t readBytes = 0;
-            size_t wantedBytes = readBytes + (size_t)box->w;
-
-            while (readBytes < wantedBytes) {
-                GoldfishPipeBuffer buf = {
-                    ((char*)entry.linear) + box->x + readBytes,
-                    wantedBytes - readBytes,
-                };
-                auto status = ops->guest_recv(hostPipe, &buf, 1);
-
-                if (status > 0) {
-                    readBytes += status;
-                } else if (status != kPipeTryAgain) {
-                    return EIO;
-                }
-            }
+        if (ret != 0) {
+            return ret;
         }
 
         VGPLOG("Linear first word: %d", *(int*)(entry.linear));
 
-        int syncRes;
-
         if (iovec_cnt) {
             PipeResEntry e = {
                 entry.args,
@@ -1186,16 +1311,13 @@
                 entry.linear,
                 entry.linearSize,
             };
-            syncRes =
-                sync_iov(&e, offset, box, LINEAR_TO_IOV);
+            ret = sync_iov(&e, offset, box, LINEAR_TO_IOV);
         } else {
-            syncRes =
-                sync_iov(&entry, offset, box, LINEAR_TO_IOV);
+            ret = sync_iov(&entry, offset, box, LINEAR_TO_IOV);
         }
 
         VGPLOG("done");
-
-        return syncRes;
+        return ret;
     }
 
     int transferWriteIov(int resId, uint64_t offset, virgl_box* box, struct iovec* iov, int iovec_cnt) {
@@ -1206,8 +1328,8 @@
         if (it == mResources.end()) return EINVAL;
 
         auto& entry = it->second;
-        int syncRes;
 
+        int ret = 0;
         if (iovec_cnt) {
             PipeResEntry e = {
                 entry.args,
@@ -1216,52 +1338,29 @@
                 entry.linear,
                 entry.linearSize,
             };
-            syncRes = sync_iov(&e, offset, box, IOV_TO_LINEAR);
+            ret = sync_iov(&e, offset, box, IOV_TO_LINEAR);
         } else {
-            syncRes = sync_iov(&entry, offset, box, IOV_TO_LINEAR);
+            ret = sync_iov(&entry, offset, box, IOV_TO_LINEAR);
         }
 
-        if (handleTransferWriteGraphicsUsage(&entry, offset, box)) {
-            // Do the pipe service op here, if there is an associated hostpipe.
-            auto hostPipe = entry.hostPipe;
-            if (!hostPipe) {
-                VGPLOG("No hostPipe");
-                return syncRes;
-            }
+        if (ret != 0) {
+            return ret;
+        }
 
-            VGPLOG("resid: %d offset: 0x%llx hostpipe: %p", resId,
-                   (unsigned long long)offset, hostPipe);
-
-            auto ops = ensureAndGetServiceOps();
-
-            size_t writtenBytes = 0;
-            size_t wantedBytes = (size_t)box->w;
-
-            while (writtenBytes < wantedBytes) {
-                GoldfishPipeBuffer buf = {
-                    ((char*)entry.linear) + box->x + writtenBytes,
-                    wantedBytes - writtenBytes,
-                };
-
-                // guest_send can now reallocate the pipe.
-                void* hostPipeBefore = hostPipe;
-                auto status = ops->guest_send(&hostPipe, &buf, 1);
-                if (hostPipe != hostPipeBefore) {
-                    resetPipe((GoldfishHwPipe*)(uintptr_t)(entry.ctxId), hostPipe);
-                    it = mResources.find(resId);
-                    entry = it->second;
-                }
-
-                if (status > 0) {
-                    writtenBytes += status;
-                } else if (status != kPipeTryAgain) {
-                    return EIO;
-                }
-            }
+        switch (entry.type) {
+            case ResType::PIPE:
+                ret = handleTransferWritePipe(&entry, offset, box);
+                break;
+            case ResType::BUFFER:
+                ret = handleTransferWriteBuffer(&entry, offset, box);
+                break;
+            case ResType::COLOR_BUFFER:
+                handleTransferWriteColorBuffer(&entry, offset, box);
+                break;
         }
 
         VGPLOG("done");
-        return syncRes;
+        return ret;
     }
 
     void attachResource(uint32_t ctxId, uint32_t resId) {
diff --git a/stream-servers/virtio_gpu_ops.h b/stream-servers/virtio_gpu_ops.h
index 448b7c8..4aa2cff 100644
--- a/stream-servers/virtio_gpu_ops.h
+++ b/stream-servers/virtio_gpu_ops.h
@@ -15,6 +15,10 @@
 
 #include <functional>
 
+/* virtio-gpu interface for buffers
+ * (triggered by minigbm/egl calling virtio-gpu ioctls) */
+typedef void (*create_buffer_with_handle_t)(uint64_t size, uint32_t handle);
+
 /* virtio-gpu interface for color buffers
  * (triggered by minigbm/egl calling virtio-gpu ioctls) */
 typedef void (*create_color_buffer_with_handle_t)(
@@ -66,10 +70,13 @@
         uint32_t* textures);
 
 typedef void (*open_color_buffer_t)(uint32_t handle);
+typedef void (*close_buffer_t)(uint32_t handle);
 typedef void (*close_color_buffer_t)(uint32_t handle);
+typedef void (*update_buffer_t)(uint32_t handle, uint64_t offset, uint64_t sizeToRead, void* bytes);
 typedef void (*update_color_buffer_t)(
     uint32_t handle, int x, int y, int width, int height,
     uint32_t format, uint32_t type, void* pixels);
+typedef void (*read_buffer_t)(uint32_t handle, uint64_t offset, uint64_t sizeToRead, void* bytes);
 typedef void (*read_color_buffer_t)(
     uint32_t handle, int x, int y, int width, int height,
     uint32_t format, uint32_t type, void* pixels);
@@ -109,10 +116,14 @@
 typedef bool (*platform_destroy_shared_egl_context_t)(void* context);
 
 struct AndroidVirtioGpuOps {
+    create_buffer_with_handle_t create_buffer_with_handle;
     create_color_buffer_with_handle_t create_color_buffer_with_handle;
     open_color_buffer_t open_color_buffer;
+    close_buffer_t close_buffer;
     close_color_buffer_t close_color_buffer;
+    update_buffer_t update_buffer;
     update_color_buffer_t update_color_buffer;
+    read_buffer_t read_buffer;
     read_color_buffer_t read_color_buffer;
     read_color_buffer_yuv_t read_color_buffer_yuv;
     post_color_buffer_t post_color_buffer;
diff --git a/stream-servers/vulkan/VkCommonOperations.cpp b/stream-servers/vulkan/VkCommonOperations.cpp
index 4035271..9788941 100644
--- a/stream-servers/vulkan/VkCommonOperations.cpp
+++ b/stream-servers/vulkan/VkCommonOperations.cpp
@@ -1431,6 +1431,7 @@
 
 static VkFormat glFormat2VkFormat(GLint internalformat) {
     switch (internalformat) {
+        case GL_R8:
         case GL_LUMINANCE:
             return VK_FORMAT_R8_UNORM;
         case GL_RGB:
@@ -1457,8 +1458,9 @@
         case GL_BGRA_EXT:
         case GL_BGRA8_EXT:
             return VK_FORMAT_B8G8R8A8_UNORM;
-            ;
         default:
+            VK_COMMON_ERROR("Unhandled format %d, falling back to VK_FORMAT_R8G8B8A8_UNORM",
+                            internalformat);
             return VK_FORMAT_R8G8B8A8_UNORM;
     }
 };
@@ -2537,6 +2539,177 @@
     return infoPtr->memory.exportedHandle;
 }
 
+bool readBufferToBytes(uint32_t bufferHandle, uint64_t offset, uint64_t size, void* outBytes) {
+    if (!sVkEmulation || !sVkEmulation->live) {
+        VK_COMMON_ERROR("VkEmulation not available.");
+        return false;
+    }
+
+    auto vk = sVkEmulation->dvk;
+
+    AutoLock lock(sVkEmulationLock);
+
+    auto bufferInfo = android::base::find(sVkEmulation->buffers, bufferHandle);
+    if (!bufferInfo) {
+        VK_COMMON_ERROR("Failed to read from Buffer:%d, not found.", bufferHandle);
+        return false;
+    }
+
+    const auto& stagingBufferInfo = sVkEmulation->staging;
+    if (size > stagingBufferInfo.size) {
+        VK_COMMON_ERROR("Failed to read from Buffer:%d, staging buffer too small.", bufferHandle);
+        return false;
+    }
+
+    const VkCommandBufferBeginInfo beginInfo = {
+        .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
+        .pNext = nullptr,
+        .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
+    };
+
+    VkCommandBuffer commandBuffer = sVkEmulation->commandBuffer;
+
+    VK_CHECK(vk->vkBeginCommandBuffer(commandBuffer, &beginInfo));
+
+    const VkBufferCopy bufferCopy = {
+        .srcOffset = offset,
+        .dstOffset = 0,
+        .size = size,
+    };
+    vk->vkCmdCopyBuffer(commandBuffer, bufferInfo->buffer, stagingBufferInfo.buffer, 1,
+                        &bufferCopy);
+
+    VK_CHECK(vk->vkEndCommandBuffer(commandBuffer));
+
+    const VkSubmitInfo submitInfo = {
+        .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
+        .pNext = nullptr,
+        .waitSemaphoreCount = 0,
+        .pWaitSemaphores = nullptr,
+        .pWaitDstStageMask = nullptr,
+        .commandBufferCount = 1,
+        .pCommandBuffers = &commandBuffer,
+        .signalSemaphoreCount = 0,
+        .pSignalSemaphores = nullptr,
+    };
+
+    {
+        android::base::AutoLock lock(*sVkEmulation->queueLock);
+        VK_CHECK(vk->vkQueueSubmit(sVkEmulation->queue, 1, &submitInfo,
+                                   sVkEmulation->commandBufferFence));
+    }
+
+    static constexpr uint64_t ANB_MAX_WAIT_NS = 5ULL * 1000ULL * 1000ULL * 1000ULL;
+
+    VK_CHECK(vk->vkWaitForFences(sVkEmulation->device, 1, &sVkEmulation->commandBufferFence,
+                                 VK_TRUE, ANB_MAX_WAIT_NS));
+
+    VK_CHECK(vk->vkResetFences(sVkEmulation->device, 1, &sVkEmulation->commandBufferFence));
+
+    const VkMappedMemoryRange toInvalidate = {
+        .sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE,
+        .pNext = nullptr,
+        .memory = stagingBufferInfo.memory.memory,
+        .offset = 0,
+        .size = size,
+    };
+
+    VK_CHECK(vk->vkInvalidateMappedMemoryRanges(sVkEmulation->device, 1, &toInvalidate));
+
+    const void* srcPtr = reinterpret_cast<const void*>(
+        reinterpret_cast<const char*>(stagingBufferInfo.memory.mappedPtr));
+    void* dstPtr = outBytes;
+    void* dstPtrOffset = reinterpret_cast<void*>(reinterpret_cast<char*>(dstPtr) + offset);
+    std::memcpy(dstPtrOffset, srcPtr, size);
+
+    return true;
+}
+
+bool updateBufferFromBytes(uint32_t bufferHandle, uint64_t offset, uint64_t size, void* bytes) {
+    if (!sVkEmulation || !sVkEmulation->live) {
+        VK_COMMON_ERROR("VkEmulation not available.");
+        return false;
+    }
+
+    auto vk = sVkEmulation->dvk;
+
+    AutoLock lock(sVkEmulationLock);
+
+    auto bufferInfo = android::base::find(sVkEmulation->buffers, bufferHandle);
+    if (!bufferInfo) {
+        VK_COMMON_ERROR("Failed to update Buffer:%d, not found.", bufferHandle);
+        return false;
+    }
+
+    const auto& stagingBufferInfo = sVkEmulation->staging;
+    if (size > stagingBufferInfo.size) {
+        VK_COMMON_ERROR("Failed to update Buffer:%d, staging buffer too small.", bufferHandle);
+        return false;
+    }
+
+    const void* srcPtr = bytes;
+    const void* srcPtrOffset =
+        reinterpret_cast<const void*>(reinterpret_cast<const char*>(srcPtr) + offset);
+    void* dstPtr = stagingBufferInfo.memory.mappedPtr;
+    std::memcpy(dstPtr, srcPtrOffset, size);
+
+    const VkMappedMemoryRange toFlush = {
+        .sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE,
+        .pNext = nullptr,
+        .memory = stagingBufferInfo.memory.memory,
+        .offset = 0,
+        .size = size,
+    };
+    VK_CHECK(vk->vkFlushMappedMemoryRanges(sVkEmulation->device, 1, &toFlush));
+
+    const VkCommandBufferBeginInfo beginInfo = {
+        .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
+        .pNext = nullptr,
+        .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
+    };
+
+    VkCommandBuffer commandBuffer = sVkEmulation->commandBuffer;
+
+    VK_CHECK(vk->vkBeginCommandBuffer(commandBuffer, &beginInfo));
+
+    const VkBufferCopy bufferCopy = {
+        .srcOffset = 0,
+        .dstOffset = offset,
+        .size = size,
+    };
+    vk->vkCmdCopyBuffer(commandBuffer, stagingBufferInfo.buffer, bufferInfo->buffer, 1,
+                        &bufferCopy);
+
+    VK_CHECK(vk->vkEndCommandBuffer(commandBuffer));
+
+    const VkSubmitInfo submitInfo = {
+        .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
+        .pNext = nullptr,
+        .waitSemaphoreCount = 0,
+        .pWaitSemaphores = nullptr,
+        .pWaitDstStageMask = nullptr,
+        .commandBufferCount = 1,
+        .pCommandBuffers = &commandBuffer,
+        .signalSemaphoreCount = 0,
+        .pSignalSemaphores = nullptr,
+    };
+
+    {
+        android::base::AutoLock lock(*sVkEmulation->queueLock);
+        VK_CHECK(vk->vkQueueSubmit(sVkEmulation->queue, 1, &submitInfo,
+                                   sVkEmulation->commandBufferFence));
+    }
+
+    static constexpr uint64_t ANB_MAX_WAIT_NS = 5ULL * 1000ULL * 1000ULL * 1000ULL;
+
+    VK_CHECK(vk->vkWaitForFences(sVkEmulation->device, 1, &sVkEmulation->commandBufferFence,
+                                 VK_TRUE, ANB_MAX_WAIT_NS));
+
+    VK_CHECK(vk->vkResetFences(sVkEmulation->device, 1, &sVkEmulation->commandBufferFence));
+
+    return true;
+}
+
 VkExternalMemoryHandleTypeFlags transformExternalMemoryHandleTypeFlags_tohost(
     VkExternalMemoryHandleTypeFlags bits) {
     VkExternalMemoryHandleTypeFlags res = bits;
diff --git a/stream-servers/vulkan/VkCommonOperations.h b/stream-servers/vulkan/VkCommonOperations.h
index 709fd1d..4580463 100644
--- a/stream-servers/vulkan/VkCommonOperations.h
+++ b/stream-servers/vulkan/VkCommonOperations.h
@@ -409,6 +409,9 @@
 bool teardownVkBuffer(uint32_t bufferHandle);
 VK_EXT_MEMORY_HANDLE getBufferExtMemoryHandle(uint32_t bufferHandle);
 
+bool readBufferToBytes(uint32_t bufferHandle, uint64_t offset, uint64_t size, void* outBytes);
+bool updateBufferFromBytes(uint32_t bufferHandle, uint64_t offset, uint64_t size, void* bytes);
+
 VkExternalMemoryHandleTypeFlags transformExternalMemoryHandleTypeFlags_tohost(
     VkExternalMemoryHandleTypeFlags bits);