| // 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" |
| |
| #ifdef GFXSTREAM_BUILD_WITH_SNAPSHOT_FRONTEND_SUPPORT |
| #include <filesystem> |
| #include <fcntl.h> |
| // X11 defines status as a preprocessor define which messes up |
| // anyone with a `Status` type. |
| #include <google/protobuf/io/zero_copy_stream_impl.h> |
| #include <google/protobuf/text_format.h> |
| #endif // ifdef GFXSTREAM_BUILD_WITH_SNAPSHOT_FRONTEND_SUPPORT |
| |
| #include <vulkan/vulkan.h> |
| |
| #include "FrameBuffer.h" |
| #include "FrameworkFormats.h" |
| #include "VkCommonOperations.h" |
| #include "aemu/base/files/StdioStream.h" |
| #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.hpp" |
| #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 waitForPendingCleanups() { |
| std::promise<void> pendingCleanupsCompletedSignal; |
| std::future<void> pendingCleanupsCompltedWaitable = pendingCleanupsCompletedSignal.get_future(); |
| enqueueCleanup([&]() { pendingCleanupsCompletedSignal.set_value(); }); |
| pendingCleanupsCompltedWaitable.wait(); |
| } |
| |
| 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(getFenceCompletionCallback()); |
| |
| #if !defined(_WIN32) |
| mPageSize = getpagesize(); |
| #endif |
| |
| mCleanupThread.reset(new CleanupThread()); |
| |
| return 0; |
| } |
| |
| void VirtioGpuFrontend::teardown() { |
| destroyVirtioGpuObjects(); |
| |
| 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.SetHostPipe(hostPipe); |
| |
| // Also update any resources associated with it |
| for (auto resourceId : context.GetAttachedResources()) { |
| 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 contextId, uint32_t nlen, const char* name, |
| uint32_t contextInit) { |
| std::string contextName(name, nlen); |
| |
| stream_renderer_debug("ctxid: %u len: %u name: %s", contextId, nlen, contextName.c_str()); |
| auto ops = ensureAndGetServiceOps(); |
| |
| auto contextOpt = VirtioGpuContext::Create(ops, contextId, contextName, contextInit); |
| if (!contextOpt) { |
| stream_renderer_error("Failed to create context %u.", contextId); |
| return -EINVAL; |
| } |
| mContexts[contextId] = std::move(*contextOpt); |
| return 0; |
| } |
| |
| VirtioGpuTimelines::FenceCompletionCallback VirtioGpuFrontend::getFenceCompletionCallback() { |
| // Forwards fence completions from VirtioGpuTimelines to the client (VMM). |
| return [this](const VirtioGpuTimelines::Ring& ring, VirtioGpuTimelines::FenceId fenceId) { |
| struct stream_renderer_fence fence = {0}; |
| fence.fence_id = fenceId; |
| fence.flags = STREAM_RENDERER_FLAG_FENCE; |
| if (const auto* contextSpecificRing = std::get_if<VirtioGpuRingContextSpecific>(&ring)) { |
| fence.flags |= STREAM_RENDERER_FLAG_FENCE_RING_IDX; |
| fence.ctx_id = contextSpecificRing->mCtxId; |
| fence.ring_idx = contextSpecificRing->mRingIdx; |
| } |
| mFenceCallback(mCookie, &fence); |
| }; |
| } |
| |
| int VirtioGpuFrontend::destroyContext(VirtioGpuCtxId contextId) { |
| stream_renderer_debug("ctxid: %u", contextId); |
| |
| auto contextIt = mContexts.find(contextId); |
| if (contextIt == mContexts.end()) { |
| stream_renderer_error("failed to destroy context %d: context not found", contextId); |
| return -EINVAL; |
| } |
| auto& context = contextIt->second; |
| |
| context.Destroy(ensureAndGetServiceOps(), mAddressSpaceDeviceControlOps); |
| |
| mContexts.erase(contextIt); |
| return 0; |
| } |
| |
| #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) |
| |
| auto contextIt = mContexts.find(ctxId); |
| if (contextIt == mContexts.end()) { |
| stream_renderer_error("ctx id %u not found", ctxId); |
| return -EINVAL; |
| } |
| auto& context = contextIt->second; |
| |
| 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; |
| |
| return context.CreateAddressSpaceGraphicsInstance(mAddressSpaceDeviceControlOps, |
| resource); |
| } |
| case GFXSTREAM_CONTEXT_PING: { |
| DECODE(contextPing, gfxstream::gfxstreamContextPing, dwords) |
| |
| return context.PingAddressSpaceGraphicsInstance(mAddressSpaceDeviceControlOps, |
| contextPing.resourceId); |
| } |
| 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"); |
| |
| // Make sure the context-specific ring is used |
| ring = VirtioGpuRingContextSpecific{ |
| .mCtxId = cmd->ctx_id, |
| .mRingIdx = 0, |
| }; |
| |
| 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 contextIt = mContexts.find(cmd->ctx_id); |
| if (contextIt == mContexts.end()) { |
| stream_renderer_error("ctx id %u is not found", cmd->ctx_id); |
| return -EINVAL; |
| } |
| auto& context = contextIt->second; |
| |
| return context.AddPendingBlob(create3d.blobId, rc3d); |
| } |
| case GFXSTREAM_ACQUIRE_SYNC: { |
| GFXSTREAM_TRACE_EVENT(GFXSTREAM_TRACE_STREAM_RENDERER_CATEGORY, |
| "GFXSTREAM_ACQUIRE_SYNC"); |
| |
| DECODE(acquireSync, gfxstream::gfxstreamAcquireSync, buffer); |
| |
| auto contextIt = mContexts.find(cmd->ctx_id); |
| if (contextIt == mContexts.end()) { |
| stream_renderer_error("ctx id %u is not found", cmd->ctx_id); |
| return -EINVAL; |
| } |
| auto& context = contextIt->second; |
| return context.AcquireSync(acquireSync.syncId); |
| } |
| 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()); |
| |
| mVirtioGpuTimelines->enqueueFence(ring, fence_id); |
| |
| return 0; |
| } |
| |
| int VirtioGpuFrontend::acquireContextFence(uint32_t contextId, uint64_t fenceId) { |
| auto contextIt = mContexts.find(contextId); |
| if (contextIt == mContexts.end()) { |
| stream_renderer_error("failed to acquire context %u fence: context not found", contextId); |
| return -EINVAL; |
| } |
| auto& context = contextIt->second; |
| |
| auto syncInfoOpt = context.TakeSync(); |
| if (!syncInfoOpt) { |
| stream_renderer_error("failed to acquire context %u fence: no sync acquired", contextId); |
| return -EINVAL; |
| } |
| |
| mSyncMap[fenceId] = std::make_shared<gfxstream::SyncDescriptorInfo>(std::move(*syncInfoOpt)); |
| |
| 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; |
| } |
| |
| int VirtioGpuFrontend::importResource(uint32_t res_handle, |
| const struct stream_renderer_handle* import_handle, |
| const struct stream_renderer_import_data* import_data) { |
| if (!import_handle) { |
| stream_renderer_error( |
| "import_handle was not provided in call to importResource for handle: %d", res_handle); |
| return -EINVAL; |
| } else if (import_data && (import_data->flags & STREAM_RENDERER_IMPORT_FLAG_RESOURCE_EXISTS)) { |
| auto resourceIt = mResources.find(res_handle); |
| if (resourceIt == mResources.end()) { |
| stream_renderer_error( |
| "import_data::flags specified STREAM_RENDERER_IMPORT_FLAG_RESOURCE_EXISTS, but " |
| "internal resource does not already exist", |
| res_handle); |
| return -EINVAL; |
| } |
| return resourceIt->second.ImportHandle(import_handle, import_data); |
| } else { |
| auto resourceOpt = VirtioGpuResource::Create(res_handle, import_handle, import_data); |
| if (!resourceOpt) { |
| stream_renderer_error("Failed to create resource %u, with import_handle/import_data", |
| res_handle); |
| return -EINVAL; |
| } |
| mResources[res_handle] = std::move(*resourceOpt); |
| return 0; |
| } |
| } |
| |
| void VirtioGpuFrontend::unrefResource(uint32_t resourceId) { |
| stream_renderer_debug("resource: %u", resourceId); |
| |
| auto resourceIt = mResources.find(resourceId); |
| if (resourceIt == mResources.end()) return; |
| auto& resource = resourceIt->second; |
| |
| auto attachedContextIds = resource.GetAttachedContexts(); |
| for (auto contextId : attachedContextIds) { |
| detachResource(contextId, resourceId); |
| } |
| |
| resource.Destroy(); |
| |
| mResources.erase(resourceIt); |
| } |
| |
| 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 contextId, uint32_t resourceId) { |
| stream_renderer_debug("ctxid: %u resid: %u", contextId, resourceId); |
| |
| auto contextIt = mContexts.find(contextId); |
| if (contextIt == mContexts.end()) { |
| stream_renderer_error("failed to attach resource %u to context %u: context not found.", |
| resourceId, contextId); |
| return; |
| } |
| auto& context = contextIt->second; |
| |
| auto resourceIt = mResources.find(resourceId); |
| if (resourceIt == mResources.end()) { |
| stream_renderer_error("failed to attach resource %u to context %u: resource not found.", |
| resourceId, contextId); |
| return; |
| } |
| auto& resource = resourceIt->second; |
| |
| context.AttachResource(resource); |
| } |
| |
| void VirtioGpuFrontend::detachResource(uint32_t contextId, uint32_t resourceId) { |
| stream_renderer_debug("ctxid: %u resid: %u", contextId, resourceId); |
| |
| auto contextIt = mContexts.find(contextId); |
| if (contextIt == mContexts.end()) { |
| stream_renderer_error("failed to detach resource %u to context %u: context not found.", |
| resourceId, contextId); |
| return; |
| } |
| auto& context = contextIt->second; |
| |
| auto resourceIt = mResources.find(resourceId); |
| if (resourceIt == mResources.end()) { |
| stream_renderer_error("failed to attach resource %u to context %u: resource not found.", |
| resourceId, contextId); |
| return; |
| } |
| auto& resource = resourceIt->second; |
| |
| auto resourceAsgOpt = context.TakeAddressSpaceGraphicsHandle(resourceId); |
| if (resourceAsgOpt) { |
| mCleanupThread->enqueueCleanup( |
| [this, asgBlob = resource.ShareRingBlob(), asgHandle = *resourceAsgOpt]() { |
| mAddressSpaceDeviceControlOps->destroy_handle(asgHandle); |
| }); |
| } |
| |
| context.DetachResource(resource); |
| } |
| |
| 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; |
| |
| auto createArgs = context.TakePendingBlob(createBlobArgs->blob_id); |
| |
| 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 %d.", 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 %d.", resourceId); |
| return -EINVAL; |
| } |
| |
| // TODO(lfy): Good place to run any registered cleanup callbacks. |
| // No-op for now. |
| return 0; |
| } |
| |
| 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->streamHandleType; |
| |
| #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); |
| } |
| |
| int VirtioGpuFrontend::destroyVirtioGpuObjects() { |
| { |
| std::vector<VirtioGpuResourceId> resourceIds; |
| resourceIds.reserve(mResources.size()); |
| for (auto& [resourceId, resource] : mResources) { |
| const auto contextIds = resource.GetAttachedContexts(); |
| for (const VirtioGpuContextId contextId : contextIds) { |
| detachResource(contextId, resourceId); |
| } |
| resourceIds.push_back(resourceId); |
| } |
| for (const VirtioGpuResourceId resourceId : resourceIds) { |
| unrefResource(resourceId); |
| } |
| mResources.clear(); |
| } |
| { |
| std::vector<VirtioGpuContextId> contextIds; |
| contextIds.reserve(mContexts.size()); |
| for (const auto& [contextId, _] : mContexts) { |
| contextIds.push_back(contextId); |
| } |
| for (const VirtioGpuContextId contextId : contextIds) { |
| destroyContext(contextId); |
| } |
| mContexts.clear(); |
| } |
| |
| if (mCleanupThread) { |
| mCleanupThread->waitForPendingCleanups(); |
| } |
| |
| return 0; |
| } |
| |
| #ifdef CONFIG_AEMU |
| void VirtioGpuFrontend::setServiceOps(const GoldfishPipeServiceOps* ops) { mServiceOps = ops; } |
| #endif // CONFIG_AEMU |
| |
| inline const GoldfishPipeServiceOps* VirtioGpuFrontend::ensureAndGetServiceOps() { |
| if (mServiceOps) return mServiceOps; |
| mServiceOps = goldfish_pipe_get_service_ops(); |
| return mServiceOps; |
| } |
| |
| #ifdef GFXSTREAM_BUILD_WITH_SNAPSHOT_FRONTEND_SUPPORT |
| |
| static constexpr const char kSnapshotBasenameAsg[] = "gfxstream_asg.bin"; |
| static constexpr const char kSnapshotBasenameFrontend[] = "gfxstream_frontend.txtproto"; |
| static constexpr const char kSnapshotBasenameRenderer[] = "gfxstream_renderer.bin"; |
| |
| int VirtioGpuFrontend::snapshotRenderer(const char* directory) { |
| const std::filesystem::path snapshotDirectory = std::string(directory); |
| const std::filesystem::path snapshotPath = snapshotDirectory / kSnapshotBasenameRenderer; |
| |
| android::base::StdioStream stream(fopen(snapshotPath.c_str(), "wb"), |
| android::base::StdioStream::kOwner); |
| android::snapshot::SnapshotSaveStream saveStream{ |
| .stream = &stream, |
| }; |
| |
| android_getOpenglesRenderer()->save(saveStream.stream, saveStream.textureSaver); |
| return 0; |
| } |
| |
| int VirtioGpuFrontend::snapshotFrontend(const char* directory) { |
| gfxstream::host::snapshot::VirtioGpuFrontendSnapshot snapshot; |
| |
| for (const auto& [contextId, context] : mContexts) { |
| auto contextSnapshotOpt = context.Snapshot(); |
| if (!contextSnapshotOpt) { |
| stream_renderer_error("Failed to snapshot context %d", contextId); |
| return -1; |
| } |
| (*snapshot.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; |
| } |
| (*snapshot.mutable_resources())[resourceId] = std::move(*resourceSnapshotOpt); |
| } |
| |
| if (mVirtioGpuTimelines) { |
| auto timelinesSnapshotOpt = mVirtioGpuTimelines->Snapshot(); |
| if (!timelinesSnapshotOpt) { |
| stream_renderer_error("Failed to snapshot timelines."); |
| return -1; |
| } |
| snapshot.mutable_timelines()->Swap(&*timelinesSnapshotOpt); |
| } |
| |
| const std::filesystem::path snapshotDirectory = std::string(directory); |
| const std::filesystem::path snapshotPath = snapshotDirectory / kSnapshotBasenameFrontend; |
| int snapshotFd = open(snapshotPath.c_str(), O_CREAT | O_WRONLY | O_TRUNC, 0660); |
| if (snapshotFd < 0) { |
| stream_renderer_error("Failed to save snapshot: failed to open %s", snapshotPath.c_str()); |
| return -1; |
| } |
| google::protobuf::io::FileOutputStream snapshotOutputStream(snapshotFd); |
| snapshotOutputStream.SetCloseOnDelete(true); |
| if (!google::protobuf::TextFormat::Print(snapshot, &snapshotOutputStream)) { |
| stream_renderer_error("Failed to save snapshot: failed to serialize to stream."); |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| int VirtioGpuFrontend::snapshotAsg(const char* directory) { |
| const std::filesystem::path snapshotDirectory = std::string(directory); |
| const std::filesystem::path snapshotPath = snapshotDirectory / kSnapshotBasenameAsg; |
| |
| android::base::StdioStream stream(fopen(snapshotPath.c_str(), "wb"), |
| android::base::StdioStream::kOwner); |
| android::snapshot::SnapshotLoadStream saveStream{ |
| .stream = &stream, |
| }; |
| |
| int ret = android::emulation::goldfish_address_space_memory_state_save(saveStream.stream); |
| if (ret) { |
| stream_renderer_error("Failed to save snapshot: failed to save ASG state."); |
| return ret; |
| } |
| return 0; |
| } |
| |
| int VirtioGpuFrontend::snapshot(const char* directory) { |
| stream_renderer_debug("directory:%s", directory); |
| |
| android_getOpenglesRenderer()->pauseAllPreSave(); |
| |
| int ret = snapshotRenderer(directory); |
| if (ret) { |
| stream_renderer_error("Failed to save snapshot: failed to snapshot renderer."); |
| return ret; |
| } |
| |
| ret = snapshotFrontend(directory); |
| if (ret) { |
| stream_renderer_error("Failed to save snapshot: failed to snapshot frontend."); |
| return ret; |
| } |
| |
| ret = snapshotAsg(directory); |
| if (ret) { |
| stream_renderer_error("Failed to save snapshot: failed to snapshot ASG device."); |
| return ret; |
| } |
| |
| stream_renderer_debug("directory:%s - done!", directory); |
| return 0; |
| } |
| |
| int VirtioGpuFrontend::restoreRenderer(const char* directory) { |
| const std::filesystem::path snapshotDirectory = std::string(directory); |
| const std::filesystem::path snapshotPath = snapshotDirectory / kSnapshotBasenameRenderer; |
| |
| android::base::StdioStream stream(fopen(snapshotPath.c_str(), "rb"), |
| android::base::StdioStream::kOwner); |
| android::snapshot::SnapshotLoadStream loadStream{ |
| .stream = &stream, |
| }; |
| |
| android_getOpenglesRenderer()->load(loadStream.stream, loadStream.textureLoader); |
| return 0; |
| } |
| |
| int VirtioGpuFrontend::restoreFrontend(const char* directory) { |
| const std::filesystem::path snapshotDirectory = std::string(directory); |
| const std::filesystem::path snapshotPath = snapshotDirectory / kSnapshotBasenameFrontend; |
| |
| gfxstream::host::snapshot::VirtioGpuFrontendSnapshot snapshot; |
| { |
| int snapshotFd = open(snapshotPath.c_str(), O_RDONLY); |
| if (snapshotFd < 0) { |
| stream_renderer_error("Failed to restore snapshot: failed to open %s", |
| snapshotPath.c_str()); |
| return -1; |
| } |
| google::protobuf::io::FileInputStream snapshotInputStream(snapshotFd); |
| snapshotInputStream.SetCloseOnDelete(true); |
| if (!google::protobuf::TextFormat::Parse(&snapshotInputStream, &snapshot)) { |
| stream_renderer_error("Failed to restore snapshot: failed to parse from file."); |
| return -1; |
| } |
| } |
| |
| mContexts.clear(); |
| mResources.clear(); |
| |
| for (const auto& [contextId, contextSnapshot] : snapshot.contexts()) { |
| auto contextOpt = VirtioGpuContext::Restore(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)); |
| } |
| |
| mVirtioGpuTimelines = |
| VirtioGpuTimelines::Restore(getFenceCompletionCallback(), snapshot.timelines()); |
| if (!mVirtioGpuTimelines) { |
| stream_renderer_error("Failed to restore timelines."); |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| int VirtioGpuFrontend::restoreAsg(const char* directory) { |
| const std::filesystem::path snapshotDirectory = std::string(directory); |
| const std::filesystem::path snapshotPath = snapshotDirectory / kSnapshotBasenameAsg; |
| |
| android::base::StdioStream stream(fopen(snapshotPath.c_str(), "rb"), |
| android::base::StdioStream::kOwner); |
| android::snapshot::SnapshotLoadStream loadStream{ |
| .stream = &stream, |
| }; |
| |
| // Gather external memory info that the ASG device needs to reload. |
| android::emulation::AddressSpaceDeviceLoadResources asgLoadResources; |
| for (const auto& [contextId, context] : mContexts) { |
| for (const auto [resourceId, asgId] : context.AsgInstances()) { |
| auto resourceIt = mResources.find(resourceId); |
| if (resourceIt == mResources.end()) { |
| stream_renderer_error("Failed to restore ASG device: context %" PRIu32 |
| " claims resource %" PRIu32 " is used for ASG %" PRIu32 |
| " but resource not found.", |
| contextId, resourceId, asgId); |
| return -1; |
| } |
| auto& resource = resourceIt->second; |
| |
| void* mappedAddr = nullptr; |
| uint64_t mappedSize = 0; |
| |
| int ret = resource.Map(&mappedAddr, &mappedSize); |
| if (ret) { |
| stream_renderer_error("Failed to restore ASG device: failed to map resource %" PRIu32, resourceId); |
| return -1; |
| } |
| |
| asgLoadResources.contextExternalMemoryMap[asgId] = { |
| .externalAddress = mappedAddr, |
| .externalAddressSize = mappedSize, |
| }; |
| } |
| } |
| |
| int ret = android::emulation::goldfish_address_space_memory_state_set_load_resources(asgLoadResources); |
| if (ret) { |
| stream_renderer_error("Failed to restore ASG device: failed to set ASG load resources."); |
| return ret; |
| } |
| |
| ret = android::emulation::goldfish_address_space_memory_state_load(loadStream.stream); |
| if (ret) { |
| stream_renderer_error("Failed to restore ASG device: failed to restore ASG state."); |
| return ret; |
| } |
| return 0; |
| } |
| |
| int VirtioGpuFrontend::restore(const char* directory) { |
| stream_renderer_debug("directory:%s", directory); |
| |
| destroyVirtioGpuObjects(); |
| |
| int ret = restoreRenderer(directory); |
| if (ret) { |
| stream_renderer_error("Failed to load snapshot: failed to load renderer."); |
| return ret; |
| } |
| |
| ret = restoreFrontend(directory); |
| if (ret) { |
| stream_renderer_error("Failed to load snapshot: failed to load frontend."); |
| return ret; |
| } |
| |
| ret = restoreAsg(directory); |
| if (ret) { |
| stream_renderer_error("Failed to load snapshot: failed to load ASG device."); |
| return ret; |
| } |
| |
| // In end2end tests, we don't really do snapshot save for render threads. |
| // We will need to resume all render threads without waiting for snapshot. |
| android_getOpenglesRenderer()->resumeAll(false); |
| |
| stream_renderer_debug("directory:%s - done!", directory); |
| return 0; |
| } |
| |
| #endif // ifdef GFXSTREAM_BUILD_WITH_SNAPSHOT_FRONTEND_SUPPORT |
| |
| } // namespace host |
| } // namespace gfxstream |