blob: fb0d3ff44b6476e164c4d18f3f366018574ec010 [file] [log] [blame] [edit]
// Copyright (C) 2024 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 "VirtioGpuFrontend.h"
#include <vulkan/vulkan.h>
#include "FrameBuffer.h"
#include "FrameworkFormats.h"
#include "VkCommonOperations.h"
#include "aemu/base/ManagedDescriptor.hpp"
#include "aemu/base/memory/SharedMemory.h"
#include "aemu/base/threads/WorkerThread.h"
#include "gfxstream/host/Tracing.h"
#include "host-common/AddressSpaceService.h"
#include "host-common/address_space_device.h"
#include "host-common/address_space_device_control_ops.h"
#include "host-common/opengles.h"
#include "virtgpu_gfxstream_protocol.h"
namespace gfxstream {
namespace host {
namespace {
using android::base::DescriptorType;
using android::base::SharedMemory;
#ifdef GFXSTREAM_BUILD_WITH_SNAPSHOT_FRONTEND_SUPPORT
using gfxstream::host::snapshot::VirtioGpuContextSnapshot;
using gfxstream::host::snapshot::VirtioGpuFrontendSnapshot;
using gfxstream::host::snapshot::VirtioGpuResourceSnapshot;
#endif // ifdef GFXSTREAM_BUILD_WITH_SNAPSHOT_FRONTEND_SUPPORT
struct VirtioGpuCmd {
uint32_t op;
uint32_t cmdSize;
unsigned char buf[0];
} __attribute__((packed));
static uint64_t convert32to64(uint32_t lo, uint32_t hi) {
return ((uint64_t)lo) | (((uint64_t)hi) << 32);
}
} // namespace
class CleanupThread {
public:
using GenericCleanup = std::function<void()>;
CleanupThread()
: mWorker([](CleanupTask task) {
return std::visit(
[](auto&& work) {
using T = std::decay_t<decltype(work)>;
if constexpr (std::is_same_v<T, GenericCleanup>) {
work();
return android::base::WorkerProcessingResult::Continue;
} else if constexpr (std::is_same_v<T, Exit>) {
return android::base::WorkerProcessingResult::Stop;
}
},
std::move(task));
}) {
mWorker.start();
}
~CleanupThread() { stop(); }
// CleanupThread is neither copyable nor movable.
CleanupThread(const CleanupThread& other) = delete;
CleanupThread& operator=(const CleanupThread& other) = delete;
CleanupThread(CleanupThread&& other) = delete;
CleanupThread& operator=(CleanupThread&& other) = delete;
void enqueueCleanup(GenericCleanup command) { mWorker.enqueue(std::move(command)); }
void stop() {
mWorker.enqueue(Exit{});
mWorker.join();
}
private:
struct Exit {};
using CleanupTask = std::variant<GenericCleanup, Exit>;
android::base::WorkerThread<CleanupTask> mWorker;
};
VirtioGpuFrontend::VirtioGpuFrontend() = default;
int VirtioGpuFrontend::init(void* cookie, gfxstream::host::FeatureSet features,
stream_renderer_fence_callback fence_callback) {
stream_renderer_debug("cookie: %p", cookie);
mCookie = cookie;
mFeatures = features;
mFenceCallback = fence_callback;
mAddressSpaceDeviceControlOps = get_address_space_device_control_ops();
if (!mAddressSpaceDeviceControlOps) {
stream_renderer_error("Could not get address space device control ops!");
return -EINVAL;
}
mVirtioGpuTimelines = VirtioGpuTimelines::create(true);
mVirtioGpuTimelines = VirtioGpuTimelines::create(true);
#if !defined(_WIN32)
mPageSize = getpagesize();
#endif
mCleanupThread.reset(new CleanupThread());
return 0;
}
void VirtioGpuFrontend::teardown() { mCleanupThread.reset(); }
int VirtioGpuFrontend::resetPipe(VirtioGpuContextId contextId, GoldfishHostPipe* hostPipe) {
stream_renderer_debug("reset pipe for context %u to hostpipe %p", contextId, hostPipe);
auto contextIt = mContexts.find(contextId);
if (contextIt == mContexts.end()) {
stream_renderer_error("failed to reset pipe: context %u not found.", contextId);
return -EINVAL;
}
auto& context = contextIt->second;
context.hostPipe = hostPipe;
// Also update any resources associated with it
auto contextResourcesIt = mContextResources.find(contextId);
if (contextResourcesIt == mContextResources.end()) {
return 0;
}
const auto& contextResources = contextResourcesIt->second;
for (auto resourceId : contextResources) {
auto resourceIt = mResources.find(resourceId);
if (resourceIt == mResources.end()) {
stream_renderer_error("failed to reset pipe: resource %d not found.", resourceId);
return -EINVAL;
}
auto& resource = resourceIt->second;
resource.SetHostPipe(hostPipe);
}
return 0;
}
int VirtioGpuFrontend::createContext(VirtioGpuCtxId ctx_id, uint32_t nlen, const char* name,
uint32_t context_init) {
std::string contextName(name, nlen);
stream_renderer_debug("ctxid: %u len: %u name: %s", ctx_id, nlen, contextName.c_str());
auto ops = ensureAndGetServiceOps();
auto hostPipe =
ops->guest_open_with_flags(reinterpret_cast<GoldfishHwPipe*>(ctx_id), 0x1 /* is virtio */);
if (!hostPipe) {
stream_renderer_error("failed to create hw pipe!");
return -EINVAL;
}
std::unordered_map<uint32_t, uint32_t> map;
std::unordered_map<uint32_t, struct stream_renderer_resource_create_args> blobMap;
VirtioGpuContext res = {
std::move(contextName), // contextName
context_init, // capsetId
ctx_id, // ctxId
hostPipe, // hostPipe
map, // resourceId --> ASG handle map
blobMap, // blobId -> resource create args
};
stream_renderer_debug("initial host pipe for ctxid %u: %p", ctx_id, hostPipe);
mContexts[ctx_id] = res;
android_onGuestGraphicsProcessCreate(ctx_id);
return 0;
}
int VirtioGpuFrontend::destroyContext(VirtioGpuCtxId contextId) {
stream_renderer_debug("ctxid: %u", contextId);
auto contextIt = mContexts.find(contextId);
if (contextIt == mContexts.end()) {
stream_renderer_error("could not find context handle %u", contextId);
return -EINVAL;
}
auto& context = contextIt->second;
for (auto const& [resourceId, handle] : context.addressSpaceHandles) {
// Note: this can hang as is but this has only been observed to
// happen during shutdown. See b/329287602#comment8.
mAddressSpaceDeviceControlOps->destroy_handle(handle);
}
auto hostPipe = context.hostPipe;
if (!hostPipe) {
stream_renderer_error("0 is not a valid hostpipe");
return -EINVAL;
}
auto ops = ensureAndGetServiceOps();
ops->guest_close(hostPipe, GOLDFISH_PIPE_CLOSE_GRACEFUL);
android_cleanupProcGLObjects(contextId);
mContexts.erase(contextIt);
return 0;
}
int VirtioGpuFrontend::setContextAddressSpaceHandleLocked(VirtioGpuCtxId ctxId, uint32_t handle,
uint32_t resourceId) {
auto ctxIt = mContexts.find(ctxId);
if (ctxIt == mContexts.end()) {
stream_renderer_error("ctx id %u is not found", ctxId);
return -EINVAL;
}
auto& ctxEntry = ctxIt->second;
ctxEntry.addressSpaceHandles[resourceId] = handle;
return 0;
}
uint32_t VirtioGpuFrontend::getAddressSpaceHandleLocked(VirtioGpuCtxId ctxId, uint32_t resourceId) {
auto ctxIt = mContexts.find(ctxId);
if (ctxIt == mContexts.end()) {
stream_renderer_error("ctx id %u is not found", ctxId);
return -EINVAL;
}
auto& ctxEntry = ctxIt->second;
if (!ctxEntry.addressSpaceHandles.count(resourceId)) {
stream_renderer_error("ASG context with resource id %u", resourceId);
return -EINVAL;
}
return ctxEntry.addressSpaceHandles[resourceId];
}
#define DECODE(variable, type, input) \
type variable = {}; \
memcpy(&variable, input, sizeof(type));
int VirtioGpuFrontend::addressSpaceProcessCmd(VirtioGpuCtxId ctxId, uint32_t* dwords) {
DECODE(header, gfxstream::gfxstreamHeader, dwords)
switch (header.opCode) {
case GFXSTREAM_CONTEXT_CREATE: {
DECODE(contextCreate, gfxstream::gfxstreamContextCreate, dwords)
auto resourceIt = mResources.find(contextCreate.resourceId);
if (resourceIt == mResources.end()) {
stream_renderer_error("ASG coherent resource %u not found",
contextCreate.resourceId);
return -EINVAL;
}
auto& resource = resourceIt->second;
void* resourceHva = nullptr;
uint64_t resourceHvaSize = 0;
if (resource.Map(&resourceHva, &resourceHvaSize) != 0) {
stream_renderer_error("failed to create ASG: failed to map resource %u",
contextCreate.resourceId);
return -EINVAL;
}
auto contextIt = mContexts.find(ctxId);
if (contextIt == mContexts.end()) {
stream_renderer_error("ctx id %u not found", ctxId);
return -EINVAL;
}
auto& context = contextIt->second;
std::string name = context.name + "-" + std::to_string(contextCreate.resourceId);
// Note: resource ids can not be used as ASG handles because ASGs may outlive the
// containing resource due asynchronous ASG destruction.
uint32_t handle = mAddressSpaceDeviceControlOps->gen_handle();
struct AddressSpaceCreateInfo createInfo = {
.handle = handle,
.type = android::emulation::VirtioGpuGraphics,
.createRenderThread = true,
.externalAddr = resourceHva,
.externalAddrSize = resourceHvaSize,
.virtioGpuContextId = ctxId,
.virtioGpuCapsetId = context.capsetId,
.contextName = name.c_str(),
.contextNameSize = static_cast<uint32_t>(context.name.size()),
};
mAddressSpaceDeviceControlOps->create_instance(createInfo);
if (setContextAddressSpaceHandleLocked(ctxId, handle, contextCreate.resourceId)) {
return -EINVAL;
}
break;
}
case GFXSTREAM_CONTEXT_PING: {
DECODE(contextPing, gfxstream::gfxstreamContextPing, dwords)
struct android::emulation::AddressSpaceDevicePingInfo ping = {0};
ping.metadata = ASG_NOTIFY_AVAILABLE;
mAddressSpaceDeviceControlOps->ping_at_hva(
getAddressSpaceHandleLocked(ctxId, contextPing.resourceId), &ping);
break;
}
default:
break;
}
return 0;
}
int VirtioGpuFrontend::submitCmd(struct stream_renderer_command* cmd) {
if (!cmd) return -EINVAL;
void* buffer = reinterpret_cast<void*>(cmd->cmd);
VirtioGpuRing ring = VirtioGpuRingGlobal{};
stream_renderer_debug("ctx: % u, ring: %s buffer: %p dwords: %d", cmd->ctx_id,
to_string(ring).c_str(), buffer, cmd->cmd_size);
if (!buffer) {
stream_renderer_error("error: buffer null");
return -EINVAL;
}
if (cmd->cmd_size < 4) {
stream_renderer_error("error: not enough bytes (got %d)", cmd->cmd_size);
return -EINVAL;
}
DECODE(header, gfxstream::gfxstreamHeader, buffer);
switch (header.opCode) {
case GFXSTREAM_CONTEXT_CREATE:
case GFXSTREAM_CONTEXT_PING:
case GFXSTREAM_CONTEXT_PING_WITH_RESPONSE: {
GFXSTREAM_TRACE_EVENT(GFXSTREAM_TRACE_STREAM_RENDERER_CATEGORY,
"GFXSTREAM_CONTEXT_[CREATE|PING]");
if (addressSpaceProcessCmd(cmd->ctx_id, (uint32_t*)buffer)) {
return -EINVAL;
}
break;
}
case GFXSTREAM_CREATE_EXPORT_SYNC: {
GFXSTREAM_TRACE_EVENT(GFXSTREAM_TRACE_STREAM_RENDERER_CATEGORY,
"GFXSTREAM_CREATE_EXPORT_SYNC");
DECODE(exportSync, gfxstream::gfxstreamCreateExportSync, buffer)
uint64_t sync_handle = convert32to64(exportSync.syncHandleLo, exportSync.syncHandleHi);
stream_renderer_debug("wait for gpu ring %s", to_string(ring).c_str());
auto taskId = mVirtioGpuTimelines->enqueueTask(ring);
#if GFXSTREAM_ENABLE_HOST_GLES
gfxstream::FrameBuffer::getFB()->asyncWaitForGpuWithCb(
sync_handle, [this, taskId] { mVirtioGpuTimelines->notifyTaskCompletion(taskId); });
#endif
break;
}
case GFXSTREAM_CREATE_EXPORT_SYNC_VK:
case GFXSTREAM_CREATE_IMPORT_SYNC_VK: {
GFXSTREAM_TRACE_EVENT(GFXSTREAM_TRACE_STREAM_RENDERER_CATEGORY,
"GFXSTREAM_CREATE_[IMPORT|EXPORT]_SYNC_VK");
// The guest sync export assumes fence context support and always uses
// VIRTGPU_EXECBUF_RING_IDX. With this, the task created here must use
// the same ring as the fence created for the virtio gpu command or the
// fence may be signaled without properly waiting for the task to complete.
ring = VirtioGpuRingContextSpecific{
.mCtxId = cmd->ctx_id,
.mRingIdx = 0,
};
DECODE(exportSyncVK, gfxstream::gfxstreamCreateExportSyncVK, buffer)
uint64_t device_handle =
convert32to64(exportSyncVK.deviceHandleLo, exportSyncVK.deviceHandleHi);
uint64_t fence_handle =
convert32to64(exportSyncVK.fenceHandleLo, exportSyncVK.fenceHandleHi);
stream_renderer_debug("wait for gpu ring %s", to_string(ring).c_str());
auto taskId = mVirtioGpuTimelines->enqueueTask(ring);
gfxstream::FrameBuffer::getFB()->asyncWaitForGpuVulkanWithCb(
device_handle, fence_handle,
[this, taskId] { mVirtioGpuTimelines->notifyTaskCompletion(taskId); });
break;
}
case GFXSTREAM_CREATE_QSRI_EXPORT_VK: {
GFXSTREAM_TRACE_EVENT(GFXSTREAM_TRACE_STREAM_RENDERER_CATEGORY,
"GFXSTREAM_CREATE_QSRI_EXPORT_VK");
// The guest QSRI export assumes fence context support and always uses
// VIRTGPU_EXECBUF_RING_IDX. With this, the task created here must use
// the same ring as the fence created for the virtio gpu command or the
// fence may be signaled without properly waiting for the task to complete.
ring = VirtioGpuRingContextSpecific{
.mCtxId = cmd->ctx_id,
.mRingIdx = 0,
};
DECODE(exportQSRI, gfxstream::gfxstreamCreateQSRIExportVK, buffer)
uint64_t image_handle =
convert32to64(exportQSRI.imageHandleLo, exportQSRI.imageHandleHi);
stream_renderer_debug("wait for gpu vk qsri ring %u image 0x%llx",
to_string(ring).c_str(), (unsigned long long)image_handle);
auto taskId = mVirtioGpuTimelines->enqueueTask(ring);
gfxstream::FrameBuffer::getFB()->asyncWaitForGpuVulkanQsriWithCb(
image_handle,
[this, taskId] { mVirtioGpuTimelines->notifyTaskCompletion(taskId); });
break;
}
case GFXSTREAM_RESOURCE_CREATE_3D: {
GFXSTREAM_TRACE_EVENT(GFXSTREAM_TRACE_STREAM_RENDERER_CATEGORY,
"GFXSTREAM_RESOURCE_CREATE_3D");
DECODE(create3d, gfxstream::gfxstreamResourceCreate3d, buffer)
struct stream_renderer_resource_create_args rc3d = {0};
rc3d.target = create3d.target;
rc3d.format = create3d.format;
rc3d.bind = create3d.bind;
rc3d.width = create3d.width;
rc3d.height = create3d.height;
rc3d.depth = create3d.depth;
rc3d.array_size = create3d.arraySize;
rc3d.last_level = create3d.lastLevel;
rc3d.nr_samples = create3d.nrSamples;
rc3d.flags = create3d.flags;
auto ctxIt = mContexts.find(cmd->ctx_id);
if (ctxIt == mContexts.end()) {
stream_renderer_error("ctx id %u is not found", cmd->ctx_id);
return -EINVAL;
}
auto& ctxEntry = ctxIt->second;
if (ctxEntry.blobMap.count(create3d.blobId)) {
stream_renderer_error("blob ID already in use");
return -EINVAL;
}
ctxEntry.blobMap[create3d.blobId] = rc3d;
break;
}
case GFXSTREAM_ACQUIRE_SYNC: {
GFXSTREAM_TRACE_EVENT(GFXSTREAM_TRACE_STREAM_RENDERER_CATEGORY,
"GFXSTREAM_ACQUIRE_SYNC");
DECODE(acquireSync, gfxstream::gfxstreamAcquireSync, buffer);
auto ctxIt = mContexts.find(cmd->ctx_id);
if (ctxIt == mContexts.end()) {
stream_renderer_error("ctx id %u is not found", cmd->ctx_id);
return -EINVAL;
}
auto& ctxEntry = ctxIt->second;
if (ctxEntry.latestFence) {
stream_renderer_error("expected latest fence to empty");
return -EINVAL;
}
auto syncDescriptorInfoOpt = ExternalObjectManager::get()->removeSyncDescriptorInfo(
cmd->ctx_id, acquireSync.syncId);
if (syncDescriptorInfoOpt) {
ctxEntry.latestFence = std::make_shared<gfxstream::SyncDescriptorInfo>(
std::move(*syncDescriptorInfoOpt));
} else {
stream_renderer_error("failed to get sync descriptor info");
return -EINVAL;
}
break;
}
case GFXSTREAM_PLACEHOLDER_COMMAND_VK: {
GFXSTREAM_TRACE_EVENT(GFXSTREAM_TRACE_STREAM_RENDERER_CATEGORY,
"GFXSTREAM_PLACEHOLDER_COMMAND_VK");
// Do nothing, this is a placeholder command
break;
}
default:
return -EINVAL;
}
return 0;
}
int VirtioGpuFrontend::createFence(uint64_t fence_id, const VirtioGpuRing& ring) {
stream_renderer_debug("fenceid: %llu ring: %s", (unsigned long long)fence_id,
to_string(ring).c_str());
struct {
FenceCompletionCallback operator()(const VirtioGpuRingGlobal&) {
return [frontend = mFrontend, fenceId = mFenceId] {
struct stream_renderer_fence fence = {0};
fence.fence_id = fenceId;
fence.flags = STREAM_RENDERER_FLAG_FENCE;
frontend->mFenceCallback(frontend->mCookie, &fence);
};
}
FenceCompletionCallback operator()(const VirtioGpuRingContextSpecific& ring) {
return [frontend = mFrontend, fenceId = mFenceId, ring] {
struct stream_renderer_fence fence = {0};
fence.fence_id = fenceId;
fence.flags = STREAM_RENDERER_FLAG_FENCE | STREAM_RENDERER_FLAG_FENCE_RING_IDX;
fence.ctx_id = ring.mCtxId;
fence.ring_idx = ring.mRingIdx;
frontend->mFenceCallback(frontend->mCookie, &fence);
};
}
VirtioGpuFrontend* mFrontend;
VirtioGpuTimelines::FenceId mFenceId;
} visitor{
.mFrontend = this,
.mFenceId = fence_id,
};
FenceCompletionCallback callback = std::visit(visitor, ring);
if (!callback) {
return -EINVAL;
}
mVirtioGpuTimelines->enqueueFence(ring, fence_id, std::move(callback));
return 0;
}
int VirtioGpuFrontend::acquireContextFence(uint32_t ctx_id, uint64_t fenceId) {
auto ctxIt = mContexts.find(ctx_id);
if (ctxIt == mContexts.end()) {
stream_renderer_error("ctx id %u is not found", ctx_id);
return -EINVAL;
}
auto& ctxEntry = ctxIt->second;
if (ctxEntry.latestFence) {
mSyncMap[fenceId] = ctxEntry.latestFence;
ctxEntry.latestFence = nullptr;
} else {
stream_renderer_error("Failed to acquire sync descriptor");
return -EINVAL;
}
return 0;
}
void VirtioGpuFrontend::poll() { mVirtioGpuTimelines->poll(); }
int VirtioGpuFrontend::createResource(struct stream_renderer_resource_create_args* args,
struct iovec* iov, uint32_t num_iovs) {
auto resourceOpt = VirtioGpuResource::Create(args, iov, num_iovs);
if (!resourceOpt) {
stream_renderer_error("Failed to create resource %u.", args->handle);
return -EINVAL;
}
mResources[args->handle] = std::move(*resourceOpt);
return 0;
}
void VirtioGpuFrontend::unrefResource(uint32_t toUnrefId) {
stream_renderer_debug("handle: %u", toUnrefId);
auto it = mResources.find(toUnrefId);
if (it == mResources.end()) return;
for (auto& ctxIdResources : mContextResources) {
detachResourceLocked(ctxIdResources.first, toUnrefId);
}
auto& resource = it->second;
resource.Destroy();
mResources.erase(it);
}
int VirtioGpuFrontend::attachIov(int resourceId, struct iovec* iov, int num_iovs) {
stream_renderer_debug("resource:%d numiovs: %d", resourceId, num_iovs);
auto it = mResources.find(resourceId);
if (it == mResources.end()) {
stream_renderer_error("failed to attach iov: resource %u not found.", resourceId);
return ENOENT;
}
auto& resource = it->second;
resource.AttachIov(iov, num_iovs);
return 0;
}
void VirtioGpuFrontend::detachIov(int resourceId) {
stream_renderer_debug("resource:%d", resourceId);
auto it = mResources.find(resourceId);
if (it == mResources.end()) {
stream_renderer_error("failed to detach iov: resource %u not found.", resourceId);
return;
}
auto& resource = it->second;
resource.DetachIov();
}
namespace {
std::optional<std::vector<struct iovec>> AsVecOption(struct iovec* iov, int iovec_cnt) {
if (iovec_cnt > 0) {
std::vector<struct iovec> ret;
ret.reserve(iovec_cnt);
for (int i = 0; i < iovec_cnt; i++) {
ret.push_back(iov[i]);
}
return ret;
}
return std::nullopt;
}
} // namespace
int VirtioGpuFrontend::transferReadIov(int resId, uint64_t offset, stream_renderer_box* box,
struct iovec* iov, int iovec_cnt) {
auto it = mResources.find(resId);
if (it == mResources.end()) {
stream_renderer_error("Failed to transfer: failed to find resource %d.", resId);
return EINVAL;
}
auto& resource = it->second;
auto ops = ensureAndGetServiceOps();
return resource.TransferRead(ops, offset, box, AsVecOption(iov, iovec_cnt));
}
int VirtioGpuFrontend::transferWriteIov(int resId, uint64_t offset, stream_renderer_box* box,
struct iovec* iov, int iovec_cnt) {
auto it = mResources.find(resId);
if (it == mResources.end()) {
stream_renderer_error("Failed to transfer: failed to find resource %d.", resId);
return EINVAL;
}
auto& resource = it->second;
auto ops = ensureAndGetServiceOps();
auto result = resource.TransferWrite(ops, offset, box, AsVecOption(iov, iovec_cnt));
if (result.status != 0) return result.status;
if (result.contextPipe) {
resetPipe(result.contextId, result.contextPipe);
}
return 0;
}
void VirtioGpuFrontend::getCapset(uint32_t set, uint32_t* max_size) {
switch (set) {
case VIRTGPU_CAPSET_GFXSTREAM_VULKAN:
*max_size = sizeof(struct gfxstream::vulkanCapset);
break;
case VIRTGPU_CAPSET_GFXSTREAM_MAGMA:
*max_size = sizeof(struct gfxstream::magmaCapset);
break;
case VIRTGPU_CAPSET_GFXSTREAM_GLES:
*max_size = sizeof(struct gfxstream::glesCapset);
break;
case VIRTGPU_CAPSET_GFXSTREAM_COMPOSER:
*max_size = sizeof(struct gfxstream::composerCapset);
break;
default:
stream_renderer_error("Incorrect capability set specified (%u)", set);
}
}
void VirtioGpuFrontend::fillCaps(uint32_t set, void* caps) {
switch (set) {
case VIRTGPU_CAPSET_GFXSTREAM_VULKAN: {
struct gfxstream::vulkanCapset* capset =
reinterpret_cast<struct gfxstream::vulkanCapset*>(caps);
memset(capset, 0, sizeof(*capset));
capset->protocolVersion = 1;
capset->ringSize = 12288;
capset->bufferSize = 1048576;
auto vk_emu = gfxstream::vk::getGlobalVkEmulation();
if (vk_emu && vk_emu->live && vk_emu->representativeColorBufferMemoryTypeInfo) {
capset->colorBufferMemoryIndex =
vk_emu->representativeColorBufferMemoryTypeInfo->guestMemoryTypeIndex;
}
if (mFeatures.VulkanBatchedDescriptorSetUpdate.enabled) {
capset->vulkanBatchedDescriptorSetUpdate=1;
}
capset->noRenderControlEnc = 1;
capset->blobAlignment = mPageSize;
if (vk_emu && vk_emu->live) {
capset->deferredMapping = 1;
}
#if GFXSTREAM_UNSTABLE_VULKAN_DMABUF_WINSYS
capset->alwaysBlob = 1;
#endif
#if GFXSTREAM_UNSTABLE_VULKAN_EXTERNAL_SYNC
capset->externalSync = 1;
#endif
memset(capset->virglSupportedFormats, 0, sizeof(capset->virglSupportedFormats));
struct FormatWithName {
uint32_t format;
const char* name;
};
#define MAKE_FORMAT_AND_NAME(x) \
{ \
x, #x \
}
static const FormatWithName kPossibleFormats[] = {
MAKE_FORMAT_AND_NAME(VIRGL_FORMAT_B5G6R5_UNORM),
MAKE_FORMAT_AND_NAME(VIRGL_FORMAT_B8G8R8A8_UNORM),
MAKE_FORMAT_AND_NAME(VIRGL_FORMAT_B8G8R8X8_UNORM),
MAKE_FORMAT_AND_NAME(VIRGL_FORMAT_NV12),
MAKE_FORMAT_AND_NAME(VIRGL_FORMAT_P010),
MAKE_FORMAT_AND_NAME(VIRGL_FORMAT_R10G10B10A2_UNORM),
MAKE_FORMAT_AND_NAME(VIRGL_FORMAT_R16_UNORM),
MAKE_FORMAT_AND_NAME(VIRGL_FORMAT_R16G16B16A16_FLOAT),
MAKE_FORMAT_AND_NAME(VIRGL_FORMAT_R8_UNORM),
MAKE_FORMAT_AND_NAME(VIRGL_FORMAT_R8G8_UNORM),
MAKE_FORMAT_AND_NAME(VIRGL_FORMAT_R8G8B8_UNORM),
MAKE_FORMAT_AND_NAME(VIRGL_FORMAT_R8G8B8A8_UNORM),
MAKE_FORMAT_AND_NAME(VIRGL_FORMAT_R8G8B8X8_UNORM),
MAKE_FORMAT_AND_NAME(VIRGL_FORMAT_YV12),
MAKE_FORMAT_AND_NAME(VIRGL_FORMAT_Z16_UNORM),
MAKE_FORMAT_AND_NAME(VIRGL_FORMAT_Z24_UNORM_S8_UINT),
MAKE_FORMAT_AND_NAME(VIRGL_FORMAT_Z24X8_UNORM),
MAKE_FORMAT_AND_NAME(VIRGL_FORMAT_Z32_FLOAT_S8X24_UINT),
MAKE_FORMAT_AND_NAME(VIRGL_FORMAT_Z32_FLOAT),
};
#undef MAKE_FORMAT_AND_NAME
stream_renderer_info("Format support:");
for (std::size_t i = 0; i < std::size(kPossibleFormats); i++) {
const FormatWithName& possibleFormat = kPossibleFormats[i];
GLenum possibleFormatGl = virgl_format_to_gl(possibleFormat.format);
const bool supported =
gfxstream::FrameBuffer::getFB()->isFormatSupported(possibleFormatGl);
stream_renderer_info(" %s: %s", possibleFormat.name,
(supported ? "supported" : "unsupported"));
set_virgl_format_supported(capset->virglSupportedFormats, possibleFormat.format,
supported);
}
break;
}
case VIRTGPU_CAPSET_GFXSTREAM_MAGMA: {
struct gfxstream::magmaCapset* capset =
reinterpret_cast<struct gfxstream::magmaCapset*>(caps);
capset->protocolVersion = 1;
capset->ringSize = 12288;
capset->bufferSize = 1048576;
capset->blobAlignment = mPageSize;
break;
}
case VIRTGPU_CAPSET_GFXSTREAM_GLES: {
struct gfxstream::glesCapset* capset =
reinterpret_cast<struct gfxstream::glesCapset*>(caps);
capset->protocolVersion = 1;
capset->ringSize = 12288;
capset->bufferSize = 1048576;
capset->blobAlignment = mPageSize;
break;
}
case VIRTGPU_CAPSET_GFXSTREAM_COMPOSER: {
struct gfxstream::composerCapset* capset =
reinterpret_cast<struct gfxstream::composerCapset*>(caps);
capset->protocolVersion = 1;
capset->ringSize = 12288;
capset->bufferSize = 1048576;
capset->blobAlignment = mPageSize;
break;
}
default:
stream_renderer_error("Incorrect capability set specified");
}
}
void VirtioGpuFrontend::attachResource(uint32_t ctxId, uint32_t resId) {
stream_renderer_debug("ctxid: %u resid: %u", ctxId, resId);
auto resourcesIt = mContextResources.find(ctxId);
if (resourcesIt == mContextResources.end()) {
std::vector<VirtioGpuResourceId> ids;
ids.push_back(resId);
mContextResources[ctxId] = ids;
} else {
auto& ids = resourcesIt->second;
auto idIt = std::find(ids.begin(), ids.end(), resId);
if (idIt == ids.end()) ids.push_back(resId);
}
// Associate the host pipe of the resource entry with the host pipe of
// the context entry. That is, the last context to call attachResource
// wins if there is any conflict.
auto ctxEntryIt = mContexts.find(ctxId);
auto resEntryIt = mResources.find(resId);
if (ctxEntryIt == mContexts.end() || resEntryIt == mResources.end()) return;
stream_renderer_debug("hostPipe: %p", ctxEntryIt->second.hostPipe);
resEntryIt->second.SetHostPipe(ctxEntryIt->second.hostPipe);
resEntryIt->second.AttachToContext(ctxId);
}
void VirtioGpuFrontend::detachResource(uint32_t ctxId, uint32_t toUnrefId) {
stream_renderer_debug("ctxid: %u resid: %u", ctxId, toUnrefId);
detachResourceLocked(ctxId, toUnrefId);
}
int VirtioGpuFrontend::getResourceInfo(uint32_t resourceId,
struct stream_renderer_resource_info* info) {
stream_renderer_debug("resource: %u", resourceId);
if (!info) {
stream_renderer_error("Failed to get info: invalid info struct.");
return EINVAL;
}
auto resourceIt = mResources.find(resourceId);
if (resourceIt == mResources.end()) {
stream_renderer_error("Failed to get info: failed to find resource %d.", resourceId);
return ENOENT;
}
auto& resource = resourceIt->second;
return resource.GetInfo(info);
}
void VirtioGpuFrontend::flushResource(uint32_t res_handle) {
auto taskId = mVirtioGpuTimelines->enqueueTask(VirtioGpuRingGlobal{});
gfxstream::FrameBuffer::getFB()->postWithCallback(
res_handle, [this, taskId](std::shared_future<void> waitForGpu) {
waitForGpu.wait();
mVirtioGpuTimelines->notifyTaskCompletion(taskId);
});
}
int VirtioGpuFrontend::createBlob(uint32_t contextId, uint32_t resourceId,
const struct stream_renderer_create_blob* createBlobArgs,
const struct stream_renderer_handle* handle) {
auto contextIt = mContexts.find(contextId);
if (contextIt == mContexts.end()) {
stream_renderer_error("failed to create blob resource %u: context %u missing.", resourceId,
contextId);
return -EINVAL;
}
auto& context = contextIt->second;
std::optional<struct stream_renderer_resource_create_args> createArgs;
auto blobIt = context.blobMap.find(createBlobArgs->blob_id);
if (blobIt != context.blobMap.end()) {
createArgs = blobIt->second;
context.blobMap.erase(blobIt);
}
auto resourceOpt =
VirtioGpuResource::Create(mFeatures, mPageSize, contextId, resourceId,
createArgs ? &*createArgs : nullptr, createBlobArgs, handle);
if (!resourceOpt) {
stream_renderer_error("failed to create blob resource %u.", resourceId);
return -EINVAL;
}
mResources[resourceId] = std::move(*resourceOpt);
return 0;
}
int VirtioGpuFrontend::resourceMap(uint32_t resourceId, void** hvaOut, uint64_t* sizeOut) {
stream_renderer_debug("resource: %u", resourceId);
if (mFeatures.ExternalBlob.enabled) {
stream_renderer_error("Failed to map resource: external blob enabled.");
return -EINVAL;
}
auto it = mResources.find(resourceId);
if (it == mResources.end()) {
if (hvaOut) *hvaOut = nullptr;
if (sizeOut) *sizeOut = 0;
stream_renderer_error("Failed to map resource: unknown resource id %s.", resourceId);
return -EINVAL;
}
auto& resource = it->second;
return resource.Map(hvaOut, sizeOut);
}
int VirtioGpuFrontend::resourceUnmap(uint32_t resourceId) {
stream_renderer_debug("resource: %u", resourceId);
auto it = mResources.find(resourceId);
if (it == mResources.end()) {
stream_renderer_error("Failed to map resource: unknown resource id %s.", resourceId);
return -EINVAL;
}
// TODO(lfy): Good place to run any registered cleanup callbacks.
// No-op for now.
return 0;
}
int VirtioGpuFrontend::platformImportResource(int res_handle, int res_info, void* resource) {
auto it = mResources.find(res_handle);
if (it == mResources.end()) return -EINVAL;
bool success =
gfxstream::FrameBuffer::getFB()->platformImportResource(res_handle, res_info, resource);
return success ? 0 : -1;
}
void* VirtioGpuFrontend::platformCreateSharedEglContext() {
void* ptr = nullptr;
#if GFXSTREAM_ENABLE_HOST_GLES
ptr = gfxstream::FrameBuffer::getFB()->platformCreateSharedEglContext();
#endif
return ptr;
}
int VirtioGpuFrontend::platformDestroySharedEglContext(void* context) {
bool success = false;
#if GFXSTREAM_ENABLE_HOST_GLES
success = gfxstream::FrameBuffer::getFB()->platformDestroySharedEglContext(context);
#endif
return success ? 0 : -1;
}
int VirtioGpuFrontend::waitSyncResource(uint32_t res_handle) {
auto resourceIt = mResources.find(res_handle);
if (resourceIt == mResources.end()) {
stream_renderer_error("waitSyncResource could not find resource: %d", res_handle);
return -EINVAL;
}
auto& resource = resourceIt->second;
return resource.WaitSyncResource();
}
int VirtioGpuFrontend::resourceMapInfo(uint32_t resourceId, uint32_t* map_info) {
stream_renderer_debug("resource: %u", resourceId);
auto resourceIt = mResources.find(resourceId);
if (resourceIt == mResources.end()) {
stream_renderer_error("Failed to get resource map info: unknown resource %d.", resourceId);
return -EINVAL;
}
const auto& resource = resourceIt->second;
return resource.GetCaching(map_info);
}
int VirtioGpuFrontend::exportBlob(uint32_t resourceId, struct stream_renderer_handle* handle) {
stream_renderer_debug("resource: %u", resourceId);
auto resourceIt = mResources.find(resourceId);
if (resourceIt == mResources.end()) {
stream_renderer_error("Failed to export blob: unknown resource %d.", resourceId);
return -EINVAL;
}
auto& resource = resourceIt->second;
return resource.ExportBlob(handle);
}
int VirtioGpuFrontend::exportFence(uint64_t fenceId, struct stream_renderer_handle* handle) {
auto it = mSyncMap.find(fenceId);
if (it == mSyncMap.end()) {
return -EINVAL;
}
auto& entry = it->second;
DescriptorType rawDescriptor;
auto rawDescriptorOpt = entry->descriptor.release();
if (rawDescriptorOpt)
rawDescriptor = *rawDescriptorOpt;
else
return -EINVAL;
handle->handle_type = entry->handleType;
#ifdef _WIN32
handle->os_handle = static_cast<int64_t>(reinterpret_cast<intptr_t>(rawDescriptor));
#else
handle->os_handle = static_cast<int64_t>(rawDescriptor);
#endif
return 0;
}
int VirtioGpuFrontend::vulkanInfo(uint32_t resourceId,
struct stream_renderer_vulkan_info* vulkanInfo) {
auto resourceIt = mResources.find(resourceId);
if (resourceIt == mResources.end()) {
stream_renderer_error("failed to get vulkan info: failed to find resource %d", resourceId);
return -EINVAL;
}
auto& resource = resourceIt->second;
return resource.GetVulkanInfo(vulkanInfo);
}
#ifdef CONFIG_AEMU
void VirtioGpuFrontend::setServiceOps(const GoldfishPipeServiceOps* ops) { mServiceOps = ops; }
#endif // CONFIG_AEMU
void VirtioGpuFrontend::detachResourceLocked(uint32_t ctxId, uint32_t toUnrefId) {
stream_renderer_debug("ctxid: %u resid: %u", ctxId, toUnrefId);
auto it = mContextResources.find(ctxId);
if (it == mContextResources.end()) return;
std::vector<VirtioGpuResourceId> withoutRes;
for (auto resId : it->second) {
if (resId != toUnrefId) {
withoutRes.push_back(resId);
}
}
mContextResources[ctxId] = withoutRes;
auto resourceIt = mResources.find(toUnrefId);
if (resourceIt == mResources.end()) return;
auto& resource = resourceIt->second;
resource.DetachFromContext();
auto ctxIt = mContexts.find(ctxId);
if (ctxIt != mContexts.end()) {
auto& ctxEntry = ctxIt->second;
if (ctxEntry.addressSpaceHandles.count(toUnrefId)) {
uint32_t asgHandle = ctxEntry.addressSpaceHandles[toUnrefId];
mCleanupThread->enqueueCleanup([this, asgBlob = resource.ShareRingBlob(), asgHandle]() {
mAddressSpaceDeviceControlOps->destroy_handle(asgHandle);
});
ctxEntry.addressSpaceHandles.erase(toUnrefId);
}
}
}
inline const GoldfishPipeServiceOps* VirtioGpuFrontend::ensureAndGetServiceOps() {
if (mServiceOps) return mServiceOps;
mServiceOps = goldfish_pipe_get_service_ops();
return mServiceOps;
}
#ifdef GFXSTREAM_BUILD_WITH_SNAPSHOT_FRONTEND_SUPPORT
int VirtioGpuFrontend::snapshot(gfxstream::host::snapshot::VirtioGpuFrontendSnapshot& outSnapshot) {
for (const auto& [contextId, context] : mContexts) {
auto contextSnapshotOpt = SnapshotContext(context);
if (!contextSnapshotOpt) {
stream_renderer_error("Failed to snapshot context %d", contextId);
return -1;
}
(*outSnapshot.mutable_contexts())[contextId] = std::move(*contextSnapshotOpt);
}
for (const auto& [resourceId, resource] : mResources) {
auto resourceSnapshotOpt = resource.Snapshot();
if (!resourceSnapshotOpt) {
stream_renderer_error("Failed to snapshot resource %d", resourceId);
return -1;
}
(*outSnapshot.mutable_resources())[resourceId] = std::move(*resourceSnapshotOpt);
}
return 0;
}
int VirtioGpuFrontend::restore(const VirtioGpuFrontendSnapshot& snapshot) {
mContexts.clear();
mResources.clear();
for (const auto& [contextId, contextSnapshot] : snapshot.contexts()) {
auto contextOpt = RestoreContext(contextSnapshot);
if (!contextOpt) {
stream_renderer_error("Failed to restore context %d", contextId);
return -1;
}
mContexts.emplace(contextId, std::move(*contextOpt));
}
for (const auto& [resourceId, resourceSnapshot] : snapshot.resources()) {
auto resourceOpt = VirtioGpuResource::Restore(resourceSnapshot);
if (!resourceOpt) {
stream_renderer_error("Failed to restore resource %d", resourceId);
return -1;
}
mResources.emplace(resourceId, std::move(*resourceOpt));
}
return 0;
}
#endif // ifdef GFXSTREAM_BUILD_WITH_SNAPSHOT_FRONTEND_SUPPORT
} // namespace host
} // namespace gfxstream