/* * 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 "RutabagaLayer.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Blueprint and Meson builds place things differently #if defined(ANDROID) #include "rutabaga_gfx_ffi.h" #else #include "rutabaga_gfx/rutabaga_gfx_ffi.h" #endif namespace gfxstream { namespace { constexpr const uint32_t kInvalidContextId = 0; std::vector Split(const std::string& s, const std::string& delimiters) { if (delimiters.empty()) { return {}; } std::vector result; size_t base = 0; size_t found; while (true) { found = s.find_first_of(delimiters, base); result.push_back(s.substr(base, found - base)); if (found == s.npos) break; base = found + 1; } return result; } std::string Join(const std::vector& things, const std::string& separator) { if (things.empty()) { return ""; } std::ostringstream result; result << *things.begin(); for (auto it = std::next(things.begin()); it != things.end(); ++it) { result << separator << *it; } return result.str(); } } // namespace class EmulatedVirtioGpu::EmulatedVirtioGpuImpl { public: EmulatedVirtioGpuImpl(); ~EmulatedVirtioGpuImpl(); bool Init(bool withGl, bool withVk, const std::string& features, EmulatedVirtioGpu* parent); bool GetCaps(uint32_t capsetId, uint32_t guestCapsSize, uint8_t* capset); std::optional CreateContext(uint32_t contextInit); void DestroyContext(uint32_t contextId); uint8_t* Map(uint32_t resourceId); void Unmap(uint32_t resourceId); int SubmitCmd(uint32_t contextId, uint32_t cmdSize, void* cmd, uint32_t ringIdx, VirtioGpuFenceFlags fenceFlags, uint32_t& fenceId, std::optional blobResourceId); int Wait(uint32_t resourceId); int TransferFromHost(uint32_t contextId, uint32_t resourceId, uint32_t transferOffset, uint32_t transferSize); int TransferFromHost(uint32_t contextId, uint32_t resourceId, uint32_t x, uint32_t y, uint32_t w, uint32_t h); int TransferToHost(uint32_t contextId, uint32_t resourceId, uint32_t transferOffset, uint32_t transferSize); int TransferToHost(uint32_t contextId, uint32_t resourceId, uint32_t x, uint32_t y, uint32_t w, uint32_t h); std::optional CreateBlob(uint32_t contextId, uint32_t blobMem, uint32_t blobFlags, uint64_t blobId, uint64_t blobSize); std::optional CreateVirglBlob(uint32_t contextId, uint32_t width, uint32_t height, uint32_t virglFormat, uint32_t target, uint32_t bind, uint32_t size); void DestroyResource(uint32_t contextId, uint32_t resourceId); void SnapshotSave(const std::string& directory); void SnapshotRestore(const std::string& directory); uint32_t CreateEmulatedFence(); void SignalEmulatedFence(uint32_t fenceId); int WaitOnEmulatedFence(int fenceAsFileDescriptor, int timeoutMilliseconds); private: struct VirtioGpuTaskContextAttachResource { uint32_t contextId; uint32_t resourceId; }; struct VirtioGpuTaskContextDetachResource { uint32_t contextId; uint32_t resourceId; }; struct VirtioGpuTaskCreateContext { uint32_t contextId; uint32_t contextInit; std::string contextName; }; struct VirtioGpuTaskCreateBlob { uint32_t contextId; uint32_t resourceId; struct rutabaga_create_blob params; }; struct VirtioGpuTaskCreateResource { uint32_t contextId; uint32_t resourceId; uint8_t* resourceBytes; struct rutabaga_create_3d params; }; struct VirtioGpuTaskDestroyContext { uint32_t contextId; }; struct VirtioGpuTaskMap { uint32_t resourceId; std::promise resourceMappedPromise; }; struct VirtioGpuTaskUnmap { uint32_t resourceId; }; struct VirtioGpuTaskSubmitCmd { uint32_t contextId; std::vector commandBuffer; }; struct VirtioGpuTaskTransferToHost { uint32_t contextId; uint32_t resourceId; uint32_t x; uint32_t y; uint32_t w; uint32_t h; }; struct VirtioGpuTaskTransferFromHost { uint32_t contextId; uint32_t resourceId; uint32_t x; uint32_t y; uint32_t w; uint32_t h; }; struct VirtioGpuTaskUnrefResource { uint32_t resourceId; }; struct VirtioGpuTaskSnapshotSave { std::string directory; }; struct VirtioGpuTaskSnapshotRestore { std::string directory; }; using VirtioGpuTask = std::variant; struct VirtioGpuFence { uint32_t fenceId; uint32_t ringIdx; }; struct VirtioGpuTaskWithWaitable { uint32_t contextId; VirtioGpuTask task; std::promise taskCompletedSignaler; std::optional fence; }; std::shared_future EnqueueVirtioGpuTask( uint32_t contextId, VirtioGpuTask task, std::optional fence = std::nullopt); void DoTask(VirtioGpuTaskContextAttachResource task); void DoTask(VirtioGpuTaskContextDetachResource task); void DoTask(VirtioGpuTaskCreateContext task); void DoTask(VirtioGpuTaskCreateBlob task); void DoTask(VirtioGpuTaskCreateResource task); void DoTask(VirtioGpuTaskDestroyContext task); void DoTask(VirtioGpuTaskMap task); void DoTask(VirtioGpuTaskUnmap task); void DoTask(VirtioGpuTaskSubmitCmd task); void DoTask(VirtioGpuTaskTransferFromHost task); void DoTask(VirtioGpuTaskTransferToHost task); void DoTask(VirtioGpuTaskWithWaitable task); void DoTask(VirtioGpuTaskUnrefResource task); void DoTask(VirtioGpuTaskSnapshotSave task); void DoTask(VirtioGpuTaskSnapshotRestore task); void RunVirtioGpuTaskProcessingLoop(); std::atomic mNextContextId{1}; std::atomic mNextVirtioGpuResourceId{1}; std::atomic mNextVirtioGpuFenceId{1}; std::atomic_bool mShuttingDown{false}; std::mutex mTasksMutex; std::queue mTasks; enum class EmulatedResourceType { kBlob, kPipe, }; struct EmulatedResource { EmulatedResourceType type; std::mutex pendingWaitablesMutex; std::vector> pendingWaitables; // For non-blob resources, the guest shadow memory. std::unique_ptr guestBytes; // For mappable blob resources, the host memory once it is mapped. std::shared_future mappedHostBytes; // For resources with iovecs. Test layer just needs 1. struct iovec iovec; }; std::mutex mResourcesMutex; std::unordered_map mResources; EmulatedResource* CreateResource(uint32_t resourceId, EmulatedResourceType resourceType) { std::lock_guard lock(mResourcesMutex); auto [it, created] = mResources.emplace( std::piecewise_construct, std::forward_as_tuple(resourceId), std::forward_as_tuple()); if (!created) { ALOGE("Created resource %" PRIu32 " twice?", resourceId); } EmulatedResource* resource = &it->second; resource->type = resourceType; return resource; } EmulatedResource* GetResource(uint32_t resourceId) { std::lock_guard lock(mResourcesMutex); auto it = mResources.find(resourceId); if (it == mResources.end()) { return nullptr; } return &it->second; } void DeleteResource(uint32_t resourceId) { std::lock_guard lock(mResourcesMutex); mResources.erase(resourceId); } struct EmulatedFence { std::promise signaler; std::shared_future waitable; }; std::mutex mVirtioGpuFencesMutex; std::unordered_map mVirtioGpuFences; std::thread mWorkerThread; struct rutabaga* mRutabaga = nullptr; std::unordered_map> mCapsets; }; EmulatedVirtioGpu::EmulatedVirtioGpuImpl::EmulatedVirtioGpuImpl() : mWorkerThread([this]() { RunVirtioGpuTaskProcessingLoop(); }) {} EmulatedVirtioGpu::EmulatedVirtioGpuImpl::~EmulatedVirtioGpuImpl() { mShuttingDown = true; mWorkerThread.join(); if (mRutabaga) { rutabaga_finish(&mRutabaga); mRutabaga = nullptr; } } namespace { void WriteFenceTrampoline(uint64_t cookie, const struct rutabaga_fence* fence) { auto* gpu = reinterpret_cast(cookie); gpu->SignalEmulatedFence(fence->fence_id); } } // namespace bool EmulatedVirtioGpu::EmulatedVirtioGpuImpl::Init(bool withGl, bool withVk, const std::string& features, EmulatedVirtioGpu* parent) { int32_t ret; uint64_t capsetMask = 0; uint32_t numCapsets = 0; struct rutabaga_builder builder = {0}; if (withGl) { capsetMask |= (1 << RUTABAGA_CAPSET_GFXSTREAM_GLES); } if (withVk) { capsetMask |= (1 << RUTABAGA_CAPSET_GFXSTREAM_VULKAN); } builder.user_data = static_cast(reinterpret_cast(parent)), builder.fence_cb = WriteFenceTrampoline; builder.capset_mask = capsetMask; builder.wsi = RUTABAGA_WSI_SURFACELESS; if (!features.empty()) { builder.renderer_features = features.c_str(); } ret = rutabaga_init(&builder, &mRutabaga); if (ret) { ALOGE("Failed to initialize rutabaga"); return false; } ret = rutabaga_get_num_capsets(mRutabaga, &numCapsets); if (ret) { ALOGE("Failed to get number of capsets"); return false; } for (uint32_t i = 0; i < numCapsets; i++) { uint32_t capsetId, capsetVersion, capsetSize; std::vector capsetData; ret = rutabaga_get_capset_info(mRutabaga, i, &capsetId, &capsetVersion, &capsetSize); if (ret) { ALOGE("Failed to get capset info"); return false; } capsetData.resize(capsetSize); ret = rutabaga_get_capset(mRutabaga, capsetId, capsetVersion, (uint8_t*)capsetData.data(), capsetSize); if (ret) { ALOGE("Failed to get capset"); return false; } mCapsets[capsetId] = capsetData; } return true; } bool EmulatedVirtioGpu::EmulatedVirtioGpuImpl::GetCaps(uint32_t capsetId, uint32_t guestCapsSize, uint8_t* capset) { if (mCapsets.count(capsetId) == 0) return false; auto capsetData = mCapsets[capsetId]; auto copySize = std::min(static_cast(guestCapsSize), capsetData.size()); memcpy(capset, capsetData.data(), copySize); return true; } std::optional EmulatedVirtioGpu::EmulatedVirtioGpuImpl::CreateContext( uint32_t contextInit) { const uint32_t contextId = mNextContextId++; VirtioGpuTaskCreateContext task = { .contextId = contextId, .contextInit = contextInit, .contextName = "EmulatedVirtioGpu Context " + std::to_string(contextId), }; EnqueueVirtioGpuTask(contextId, std::move(task)); return contextId; } void EmulatedVirtioGpu::EmulatedVirtioGpuImpl::DestroyContext(uint32_t contextId) { VirtioGpuTaskDestroyContext task = { .contextId = contextId, }; EnqueueVirtioGpuTask(contextId, std::move(task)); } uint8_t* EmulatedVirtioGpu::EmulatedVirtioGpuImpl::Map(uint32_t resourceId) { EmulatedResource* resource = GetResource(resourceId); if (resource == nullptr) { ALOGE("Failed to Map() resource %" PRIu32 ": not found.", resourceId); return nullptr; } uint8_t* mapped = nullptr; if (resource->type == EmulatedResourceType::kBlob) { if (!resource->mappedHostBytes.valid()) { ALOGE("Failed to Map() resource %" PRIu32 ": attempting to map blob " "without mappable flag?", resourceId); return nullptr; } mapped = resource->mappedHostBytes.get(); } else if (resource->type == EmulatedResourceType::kPipe) { mapped = resource->guestBytes.get(); } return mapped; } void EmulatedVirtioGpu::EmulatedVirtioGpuImpl::Unmap(uint32_t resourceId) { VirtioGpuTaskUnmap task = { .resourceId = resourceId, }; EnqueueVirtioGpuTask(0, std::move(task)); } int EmulatedVirtioGpu::EmulatedVirtioGpuImpl::Wait(uint32_t resourceId) { EmulatedResource* resource = GetResource(resourceId); if (resource == nullptr) { ALOGE("Failed to Wait() on resource %" PRIu32 ": not found.", resourceId); return -1; } std::vector> pendingWaitables; { std::lock_guard lock(resource->pendingWaitablesMutex); pendingWaitables = resource->pendingWaitables; resource->pendingWaitables.clear(); } for (auto& waitable : pendingWaitables) { waitable.wait(); } return 0; } int EmulatedVirtioGpu::EmulatedVirtioGpuImpl::TransferFromHost(uint32_t contextId, uint32_t resourceId, uint32_t transferOffset, uint32_t transferSize) { return TransferFromHost(contextId, resourceId, transferOffset, 0, transferSize, 1); } int EmulatedVirtioGpu::EmulatedVirtioGpuImpl::TransferFromHost(uint32_t contextId, uint32_t resourceId, uint32_t x, uint32_t y, uint32_t w, uint32_t h) { EmulatedResource* resource = GetResource(resourceId); if (resource == nullptr) { ALOGE("Failed to TransferFromHost() on resource %" PRIu32 ": not found.", resourceId); return -1; } VirtioGpuTaskTransferFromHost task = { .contextId = contextId, .resourceId = resourceId, .x = x, .y = y, .w = w, .h = h, }; auto waitable = EnqueueVirtioGpuTask(contextId, std::move(task)); { std::lock_guard lock(resource->pendingWaitablesMutex); resource->pendingWaitables.push_back(std::move(waitable)); } return 0; } int EmulatedVirtioGpu::EmulatedVirtioGpuImpl::TransferToHost(uint32_t contextId, uint32_t resourceId, uint32_t transferOffset, uint32_t transferSize) { return TransferToHost(contextId, resourceId, transferOffset, 0, transferSize, 1); } int EmulatedVirtioGpu::EmulatedVirtioGpuImpl::TransferToHost(uint32_t contextId, uint32_t resourceId, uint32_t x, uint32_t y, uint32_t w, uint32_t h) { EmulatedResource* resource = GetResource(resourceId); if (resource == nullptr) { ALOGE("Failed to TransferFromHost() on resource %" PRIu32 ": not found.", resourceId); return -1; } VirtioGpuTaskTransferToHost task = { .contextId = contextId, .resourceId = resourceId, .x = x, .y = y, .w = w, .h = h, }; auto waitable = EnqueueVirtioGpuTask(contextId, std::move(task)); { std::lock_guard lock(resource->pendingWaitablesMutex); resource->pendingWaitables.push_back(std::move(waitable)); } return 0; } std::optional EmulatedVirtioGpu::EmulatedVirtioGpuImpl::CreateBlob( uint32_t contextId, uint32_t blobMem, uint32_t blobFlags, uint64_t blobId, uint64_t blobSize) { const uint32_t resourceId = mNextVirtioGpuResourceId++; ALOGV("Enquing task to create blob resource-id:%d size:%" PRIu64, resourceId, blobSize); EmulatedResource* resource = CreateResource(resourceId, EmulatedResourceType::kBlob); VirtioGpuTaskCreateBlob createTask{ .contextId = contextId, .resourceId = resourceId, .params = { .blob_mem = blobMem, .blob_flags = blobFlags, .blob_id = blobId, .size = blobSize, }, }; auto createBlobCompletedWaitable = EnqueueVirtioGpuTask(contextId, std::move(createTask)); resource->pendingWaitables.push_back(std::move(createBlobCompletedWaitable)); if (blobFlags & 0x001 /*kBlobFlagMappable*/) { std::promise mappedBytesPromise; std::shared_future mappedBytesWaitable = mappedBytesPromise.get_future(); VirtioGpuTaskMap mapTask{ .resourceId = resourceId, .resourceMappedPromise = std::move(mappedBytesPromise), }; EnqueueVirtioGpuTask(contextId, std::move(mapTask)); resource->mappedHostBytes = std::move(mappedBytesWaitable); } VirtioGpuTaskContextAttachResource attachTask{ .contextId = contextId, .resourceId = resourceId, }; EnqueueVirtioGpuTask(contextId, std::move(attachTask)); return resourceId; } std::optional EmulatedVirtioGpu::EmulatedVirtioGpuImpl::CreateVirglBlob( uint32_t contextId, uint32_t width, uint32_t height, uint32_t virglFormat, uint32_t target, uint32_t bind, uint32_t size) { const uint32_t resourceId = mNextVirtioGpuResourceId++; EmulatedResource* resource = CreateResource(resourceId, EmulatedResourceType::kPipe); resource->guestBytes = std::make_unique(size); VirtioGpuTaskCreateResource task{ .contextId = contextId, .resourceId = resourceId, .resourceBytes = resource->guestBytes.get(), .params = { .target = target, .format = virglFormat, .bind = bind, .width = width, .height = height, .depth = 1, .array_size = 1, .last_level = 0, .nr_samples = 0, .flags = 0, }, }; auto taskCompletedWaitable = EnqueueVirtioGpuTask(contextId, std::move(task)); resource->pendingWaitables.push_back(std::move(taskCompletedWaitable)); VirtioGpuTaskContextAttachResource attachTask{ .contextId = contextId, .resourceId = resourceId, }; EnqueueVirtioGpuTask(contextId, std::move(attachTask)); return resourceId; } void EmulatedVirtioGpu::EmulatedVirtioGpuImpl::DestroyResource(uint32_t contextId, uint32_t resourceId) { DeleteResource(resourceId); VirtioGpuTaskUnrefResource unrefTask{ .resourceId = resourceId, }; EnqueueVirtioGpuTask(contextId, std::move(unrefTask)); VirtioGpuTaskContextDetachResource detachTask{ .contextId = contextId, .resourceId = resourceId, }; EnqueueVirtioGpuTask(contextId, std::move(detachTask)); } void EmulatedVirtioGpu::EmulatedVirtioGpuImpl::SnapshotSave(const std::string& directory) { uint32_t contextId = 0; VirtioGpuTaskSnapshotSave saveTask{ .directory = directory, }; EnqueueVirtioGpuTask(contextId, std::move(saveTask)).wait(); } void EmulatedVirtioGpu::EmulatedVirtioGpuImpl::SnapshotRestore(const std::string& directory) { uint32_t contextId = 0; VirtioGpuTaskSnapshotRestore restoreTask{ .directory = directory, }; EnqueueVirtioGpuTask(contextId, std::move(restoreTask)).wait(); } int EmulatedVirtioGpu::EmulatedVirtioGpuImpl::SubmitCmd(uint32_t contextId, uint32_t cmdSize, void* cmd, uint32_t ringIdx, VirtioGpuFenceFlags fenceFlags, uint32_t& fenceId, std::optional blobResourceId) { std::optional fence; if (fenceFlags & kFlagFence) { fence = VirtioGpuFence{.fenceId = CreateEmulatedFence(), .ringIdx = ringIdx}; } const VirtioGpuTaskSubmitCmd task = { .contextId = contextId, .commandBuffer = std::vector(reinterpret_cast(cmd), reinterpret_cast(cmd) + cmdSize), }; auto taskCompletedWaitable = EnqueueVirtioGpuTask(contextId, std::move(task), fence); if (blobResourceId) { EmulatedResource* resource = GetResource(*blobResourceId); if (resource == nullptr) { ALOGE("Failed to SubmitCmd() with resource %" PRIu32 ": not found.", *blobResourceId); return -1; } { std::lock_guard lock(resource->pendingWaitablesMutex); resource->pendingWaitables.push_back(std::move(taskCompletedWaitable)); } } if (fenceFlags & kFlagFence) { fenceId = (*fence).fenceId; } return 0; } int EmulatedVirtioGpu::EmulatedVirtioGpuImpl::WaitOnEmulatedFence(int fenceAsFileDescriptor, int timeoutMilliseconds) { uint32_t fenceId = static_cast(fenceAsFileDescriptor); ALOGV("Waiting on fence:%d", (int)fenceId); std::shared_future waitable; { std::lock_guard lock(mVirtioGpuFencesMutex); auto fenceIt = mVirtioGpuFences.find(fenceId); if (fenceIt == mVirtioGpuFences.end()) { ALOGE("Fence:%d already signaled", (int)fenceId); return 0; } auto& fence = fenceIt->second; waitable = fence.waitable; } auto status = waitable.wait_for(std::chrono::milliseconds(timeoutMilliseconds)); if (status == std::future_status::ready) { ALOGV("Finished waiting for fence:%d", (int)fenceId); return 0; } else { ALOGE("Timed out waiting for fence:%d", (int)fenceId); return -1; } } void EmulatedVirtioGpu::EmulatedVirtioGpuImpl::SignalEmulatedFence(uint32_t fenceId) { ALOGV("Signaling fence:%d", (int)fenceId); std::lock_guard lock(mVirtioGpuFencesMutex); auto fenceIt = mVirtioGpuFences.find(fenceId); if (fenceIt == mVirtioGpuFences.end()) { ALOGE("Failed to find fence %" PRIu32, fenceId); return; } auto& fenceInfo = fenceIt->second; fenceInfo.signaler.set_value(); } uint32_t EmulatedVirtioGpu::EmulatedVirtioGpuImpl::CreateEmulatedFence() { const uint32_t fenceId = mNextVirtioGpuFenceId++; ALOGV("Creating fence:%d", (int)fenceId); std::lock_guard lock(mVirtioGpuFencesMutex); auto [fenceIt, fenceCreated] = mVirtioGpuFences.emplace(fenceId, EmulatedFence{}); if (!fenceCreated) { ALOGE("Attempting to recreate fence %" PRIu32, fenceId); } auto& fenceInfo = fenceIt->second; fenceInfo.waitable = fenceInfo.signaler.get_future(); return fenceId; } std::shared_future EmulatedVirtioGpu::EmulatedVirtioGpuImpl::EnqueueVirtioGpuTask( uint32_t contextId, VirtioGpuTask task, std::optional fence) { std::promise taskCompletedSignaler; std::shared_future taskCompletedWaitable(taskCompletedSignaler.get_future()); std::lock_guard lock(mTasksMutex); mTasks.push(VirtioGpuTaskWithWaitable{ .contextId = contextId, .task = std::move(task), .taskCompletedSignaler = std::move(taskCompletedSignaler), .fence = fence, }); return taskCompletedWaitable; } void EmulatedVirtioGpu::EmulatedVirtioGpuImpl::DoTask(VirtioGpuTaskContextAttachResource task) { ALOGV("Performing task to attach resource-id:%d to context-id:%d", task.resourceId, task.contextId); rutabaga_context_attach_resource(mRutabaga, task.contextId, task.resourceId); ALOGV("Performing task to attach resource-id:%d to context-id:%d - done", task.resourceId, task.contextId); } void EmulatedVirtioGpu::EmulatedVirtioGpuImpl::DoTask(VirtioGpuTaskContextDetachResource task) { ALOGV("Performing task to detach resource-id:%d to context-id:%d", task.resourceId, task.contextId); rutabaga_context_detach_resource(mRutabaga, task.contextId, task.resourceId); ALOGV("Performing task to detach resource-id:%d to context-id:%d - done", task.resourceId, task.contextId); } void EmulatedVirtioGpu::EmulatedVirtioGpuImpl::DoTask(VirtioGpuTaskCreateBlob task) { ALOGV("Performing task to create blob resource-id:%d", task.resourceId); int ret = rutabaga_resource_create_blob(mRutabaga, task.contextId, task.resourceId, &task.params, /*iovecs=*/nullptr, /*handle=*/nullptr); if (ret) { ALOGE("Failed to create blob."); } ALOGV("Performing task to create blob resource-id:%d - done", task.resourceId); } void EmulatedVirtioGpu::EmulatedVirtioGpuImpl::DoTask(VirtioGpuTaskCreateContext task) { ALOGV("Performing task to create context-id:%" PRIu32 " context-init:%" PRIu32 " context-name:%s", task.contextId, task.contextInit, task.contextName.c_str()); int ret = rutabaga_context_create(mRutabaga, task.contextId, task.contextName.size(), task.contextName.data(), task.contextInit); if (ret) { ALOGE("Failed to create context-id:%" PRIu32 ".", task.contextId); return; } ALOGV("Performing task to create context-id:%" PRIu32 " context-init:%" PRIu32 " context-name:%s - done", task.contextId, task.contextInit, task.contextName.c_str()); } void EmulatedVirtioGpu::EmulatedVirtioGpuImpl::DoTask(VirtioGpuTaskCreateResource task) { ALOGV("Performing task to create resource resource:%d", task.resourceId); EmulatedResource* resource = GetResource(task.resourceId); int ret = rutabaga_resource_create_3d(mRutabaga, task.resourceId, &task.params); if (ret) { ALOGE("Failed to create resource:%d", task.resourceId); } resource->iovec.iov_base = task.resourceBytes; resource->iovec.iov_len = task.params.width * task.params.height * 4; struct rutabaga_iovecs vecs = {0}; vecs.iovecs = &resource->iovec; vecs.num_iovecs = 1; ret = rutabaga_resource_attach_backing(mRutabaga, task.resourceId, &vecs); if (ret) { ALOGE("Failed to attach iov to resource:%d", task.resourceId); } ALOGV("Performing task to create resource resource:%d - done", task.resourceId); rutabaga_context_attach_resource(mRutabaga, task.contextId, task.resourceId); } void EmulatedVirtioGpu::EmulatedVirtioGpuImpl::DoTask(VirtioGpuTaskDestroyContext task) { ALOGV("Performing task to destroy context-id:%" PRIu32, task.contextId); rutabaga_context_destroy(mRutabaga, task.contextId); ALOGV("Performing task to destroy context-id:%" PRIu32 " - done", task.contextId); } void EmulatedVirtioGpu::EmulatedVirtioGpuImpl::DoTask(VirtioGpuTaskMap task) { ALOGV("Performing task to map resource resource:%d", task.resourceId); struct rutabaga_mapping mapping; int ret = rutabaga_resource_map(mRutabaga, task.resourceId, &mapping); if (ret) { ALOGE("Failed to map resource:%d", task.resourceId); return; } task.resourceMappedPromise.set_value(reinterpret_cast(mapping.ptr)); ALOGV("Performing task to map resource resource:%d - done", task.resourceId); } void EmulatedVirtioGpu::EmulatedVirtioGpuImpl::DoTask(VirtioGpuTaskUnmap task) { ALOGV("Performing task to unmap resource resource:%d", task.resourceId); rutabaga_resource_unmap(mRutabaga, task.resourceId); ALOGV("Performing task to unmap resource:%d - done", task.resourceId); } void EmulatedVirtioGpu::EmulatedVirtioGpuImpl::DoTask(VirtioGpuTaskSubmitCmd task) { ALOGV("Performing task to execbuffer"); if (task.commandBuffer.size() % 4 != 0) { ALOGE("Unaligned command buffer?"); return; } struct rutabaga_command cmd = { .ctx_id = task.contextId, .cmd_size = static_cast(task.commandBuffer.size()), .cmd = reinterpret_cast(task.commandBuffer.data()), .num_in_fences = 0, .fence_ids = nullptr, }; int ret = rutabaga_submit_command(mRutabaga, &cmd); if (ret) { ALOGE("Failed to execbuffer."); } ALOGV("Performing task to execbuffer - done"); } void EmulatedVirtioGpu::EmulatedVirtioGpuImpl::DoTask(VirtioGpuTaskTransferFromHost task) { struct rutabaga_transfer transfer = {0}; transfer.x = task.x; transfer.y = task.y; transfer.z = 0; transfer.w = task.w; transfer.h = task.h; transfer.d = 1; int ret = rutabaga_resource_transfer_read(mRutabaga, task.contextId, task.resourceId, &transfer, nullptr); if (ret) { ALOGE("Failed to transferFromHost() for resource:%" PRIu32, task.resourceId); } } void EmulatedVirtioGpu::EmulatedVirtioGpuImpl::DoTask(VirtioGpuTaskTransferToHost task) { struct rutabaga_transfer transfer = {0}; transfer.x = task.x; transfer.y = task.y; transfer.z = 0; transfer.w = task.w; transfer.h = task.h; transfer.d = 1; int ret = rutabaga_resource_transfer_write(mRutabaga, task.contextId, task.resourceId, &transfer); if (ret) { ALOGE("Failed to transferToHost() for resource:%" PRIu32, task.resourceId); } } void EmulatedVirtioGpu::EmulatedVirtioGpuImpl::DoTask(VirtioGpuTaskUnrefResource task) { rutabaga_resource_unref(mRutabaga, task.resourceId); } void EmulatedVirtioGpu::EmulatedVirtioGpuImpl::DoTask(VirtioGpuTaskSnapshotSave task) { int ret = rutabaga_snapshot(mRutabaga, task.directory.c_str()); if (ret) { ALOGE("snapshotting failed"); } } void EmulatedVirtioGpu::EmulatedVirtioGpuImpl::DoTask(VirtioGpuTaskSnapshotRestore task) { int ret = rutabaga_restore(mRutabaga, task.directory.c_str()); if (ret) { ALOGE("snapshot restore failed"); } } void EmulatedVirtioGpu::EmulatedVirtioGpuImpl::DoTask(VirtioGpuTaskWithWaitable task) { std::visit( [this](auto&& work) { using T = std::decay_t; if constexpr (std::is_same_v) { DoTask(std::move(work)); } else if constexpr (std::is_same_v) { DoTask(std::move(work)); } else if constexpr (std::is_same_v) { DoTask(std::move(work)); } else if constexpr (std::is_same_v) { DoTask(std::move(work)); } else if constexpr (std::is_same_v) { DoTask(std::move(work)); } else if constexpr (std::is_same_v) { DoTask(std::move(work)); } else if constexpr (std::is_same_v) { DoTask(std::move(work)); } else if constexpr (std::is_same_v) { DoTask(std::move(work)); } else if constexpr (std::is_same_v) { DoTask(std::move(work)); } else if constexpr (std::is_same_v) { DoTask(std::move(work)); } else if constexpr (std::is_same_v) { DoTask(std::move(work)); } else if constexpr (std::is_same_v) { DoTask(std::move(work)); } else if constexpr (std::is_same_v) { DoTask(std::move(work)); } else if constexpr (std::is_same_v) { DoTask(std::move(work)); } }, task.task); if (task.fence) { const struct rutabaga_fence fenceInfo = { .flags = RUTABAGA_FLAG_INFO_RING_IDX, .fence_id = (*task.fence).fenceId, .ctx_id = task.contextId, .ring_idx = (*task.fence).ringIdx, }; int ret = rutabaga_create_fence(mRutabaga, &fenceInfo); if (ret) { ALOGE("Failed to create fence."); } } task.taskCompletedSignaler.set_value(); } void EmulatedVirtioGpu::EmulatedVirtioGpuImpl::RunVirtioGpuTaskProcessingLoop() { while (!mShuttingDown.load()) { std::optional task; { std::lock_guard lock(mTasksMutex); if (!mTasks.empty()) { task = std::move(mTasks.front()); mTasks.pop(); } } if (task) { DoTask(std::move(*task)); } } } EmulatedVirtioGpu::EmulatedVirtioGpu() : mImpl{std::make_unique()} {} namespace { static std::mutex sInstanceMutex; static std::weak_ptr sInstance; } // namespace /*static*/ std::shared_ptr EmulatedVirtioGpu::Get() { std::lock_guard lock(sInstanceMutex); std::shared_ptr instance = sInstance.lock(); if (instance != nullptr) { return instance; } instance = std::shared_ptr(new EmulatedVirtioGpu()); bool withGl = false; bool withVk = false; std::string features; struct BoolOption { std::string env; bool* val; }; const std::vector options = { {"GFXSTREAM_EMULATED_VIRTIO_GPU_WITH_GL", &withGl}, {"GFXSTREAM_EMULATED_VIRTIO_GPU_WITH_VK", &withVk}, }; for (const BoolOption& option : options) { const char* val = std::getenv(option.env.c_str()); if (val != nullptr && (val[0] == 'Y' || val[0] == 'y')) { *option.val = true; } } struct StringOption { std::string env; std::string* val; }; const std::vector stringOptions = { {"GFXSTREAM_EMULATED_VIRTIO_GPU_RENDERER_FEATURES", &features}, }; for (const StringOption& option : stringOptions) { const char* val = std::getenv(option.env.c_str()); if (val != nullptr) { *option.val = std::string(val); } } ALOGE("Initializing withGl:%d withVk:%d features:%s", withGl, withVk, features.c_str()); if (!instance->Init(withGl, withVk, features)) { ALOGE("Failed to initialize EmulatedVirtioGpu."); return nullptr; } ALOGE("Successfully initialized EmulatedVirtioGpu."); sInstance = instance; return instance; } /*static*/ uint32_t EmulatedVirtioGpu::GetNumActiveUsers() { std::lock_guard lock(sInstanceMutex); std::shared_ptr instance = sInstance.lock(); return instance.use_count(); } bool EmulatedVirtioGpu::Init(bool withGl, bool withVk, const std::string& features) { return mImpl->Init(withGl, withVk, features, this); } std::optional EmulatedVirtioGpu::CreateContext(uint32_t contextInit) { return mImpl->CreateContext(contextInit); } void EmulatedVirtioGpu::DestroyContext(uint32_t contextId) { mImpl->DestroyContext(contextId); } bool EmulatedVirtioGpu::GetCaps(uint32_t capsetId, uint32_t guestCapsSize, uint8_t* capset) { return mImpl->GetCaps(capsetId, guestCapsSize, capset); } uint8_t* EmulatedVirtioGpu::Map(uint32_t resourceId) { return mImpl->Map(resourceId); } void EmulatedVirtioGpu::Unmap(uint32_t resourceId) { mImpl->Unmap(resourceId); } int EmulatedVirtioGpu::SubmitCmd(uint32_t contextId, uint32_t cmdSize, void* cmd, uint32_t ringIdx, VirtioGpuFenceFlags fenceFlags, uint32_t& fenceId, std::optional blobResourceId) { return mImpl->SubmitCmd(contextId, cmdSize, cmd, ringIdx, fenceFlags, fenceId, blobResourceId); } int EmulatedVirtioGpu::Wait(uint32_t resourceId) { return mImpl->Wait(resourceId); } int EmulatedVirtioGpu::TransferFromHost(uint32_t contextId, uint32_t resourceId, uint32_t offset, uint32_t size) { return mImpl->TransferFromHost(contextId, resourceId, offset, size); } int EmulatedVirtioGpu::TransferFromHost(uint32_t contextId, uint32_t resourceId, uint32_t x, uint32_t y, uint32_t w, uint32_t h) { return mImpl->TransferFromHost(contextId, resourceId, x, y, w, h); } int EmulatedVirtioGpu::TransferToHost(uint32_t contextId, uint32_t resourceId, uint32_t offset, uint32_t size) { return mImpl->TransferToHost(contextId, resourceId, offset, size); } int EmulatedVirtioGpu::TransferToHost(uint32_t contextId, uint32_t resourceId, uint32_t x, uint32_t y, uint32_t w, uint32_t h) { return mImpl->TransferToHost(contextId, resourceId, x, y, w, h); } std::optional EmulatedVirtioGpu::CreateBlob(uint32_t contextId, uint32_t blobMem, uint32_t blobFlags, uint64_t blobId, uint64_t blobSize) { return mImpl->CreateBlob(contextId, blobMem, blobFlags, blobId, blobSize); } std::optional EmulatedVirtioGpu::CreateVirglBlob(uint32_t contextId, uint32_t width, uint32_t height, uint32_t virglFormat, uint32_t target, uint32_t bind, uint32_t size) { return mImpl->CreateVirglBlob(contextId, width, height, virglFormat, target, bind, size); } void EmulatedVirtioGpu::DestroyResource(uint32_t contextId, uint32_t resourceId) { mImpl->DestroyResource(contextId, resourceId); } void EmulatedVirtioGpu::SnapshotSave(const std::string& directory) { mImpl->SnapshotSave(directory); } void EmulatedVirtioGpu::SnapshotRestore(const std::string& directory) { mImpl->SnapshotRestore(directory); } int EmulatedVirtioGpu::WaitOnEmulatedFence(int fenceAsFileDescriptor, int timeoutMilliseconds) { return mImpl->WaitOnEmulatedFence(fenceAsFileDescriptor, timeoutMilliseconds); } void EmulatedVirtioGpu::SignalEmulatedFence(int fenceId) { mImpl->SignalEmulatedFence(fenceId); } bool GetNumActiveEmulatedVirtioGpuUsers() { return EmulatedVirtioGpu::GetNumActiveUsers(); } } // namespace gfxstream