| // 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 "VirtioGpuResource.h" |
| |
| #include "FrameBuffer.h" |
| #include "VirtioGpuFormatUtils.h" |
| |
| namespace gfxstream { |
| namespace host { |
| |
| using android::base::DescriptorType; |
| #ifdef GFXSTREAM_BUILD_WITH_SNAPSHOT_FRONTEND_SUPPORT |
| using gfxstream::host::snapshot::VirtioGpuResourceCreateArgs; |
| using gfxstream::host::snapshot::VirtioGpuResourceCreateBlobArgs; |
| using gfxstream::host::snapshot::VirtioGpuResourceSnapshot; |
| #endif |
| |
| namespace { |
| |
| static constexpr int kPipeTryAgain = -2; |
| |
| enum pipe_texture_target { |
| PIPE_BUFFER, |
| PIPE_TEXTURE_1D, |
| PIPE_TEXTURE_2D, |
| PIPE_TEXTURE_3D, |
| PIPE_TEXTURE_CUBE, |
| PIPE_TEXTURE_RECT, |
| PIPE_TEXTURE_1D_ARRAY, |
| PIPE_TEXTURE_2D_ARRAY, |
| PIPE_TEXTURE_CUBE_ARRAY, |
| PIPE_MAX_TEXTURE_TYPES, |
| }; |
| |
| /** |
| * Resource binding flags -- state tracker must specify in advance all |
| * the ways a resource might be used. |
| */ |
| #define PIPE_BIND_DEPTH_STENCIL (1 << 0) /* create_surface */ |
| #define PIPE_BIND_RENDER_TARGET (1 << 1) /* create_surface */ |
| #define PIPE_BIND_BLENDABLE (1 << 2) /* create_surface */ |
| #define PIPE_BIND_SAMPLER_VIEW (1 << 3) /* create_sampler_view */ |
| #define PIPE_BIND_VERTEX_BUFFER (1 << 4) /* set_vertex_buffers */ |
| #define PIPE_BIND_INDEX_BUFFER (1 << 5) /* draw_elements */ |
| #define PIPE_BIND_CONSTANT_BUFFER (1 << 6) /* set_constant_buffer */ |
| #define PIPE_BIND_DISPLAY_TARGET (1 << 7) /* flush_front_buffer */ |
| #define PIPE_BIND_STREAM_OUTPUT (1 << 10) /* set_stream_output_buffers */ |
| #define PIPE_BIND_CURSOR (1 << 11) /* mouse cursor */ |
| #define PIPE_BIND_CUSTOM (1 << 12) /* state-tracker/winsys usages */ |
| #define PIPE_BIND_GLOBAL (1 << 13) /* set_global_binding */ |
| #define PIPE_BIND_SHADER_BUFFER (1 << 14) /* set_shader_buffers */ |
| #define PIPE_BIND_SHADER_IMAGE (1 << 15) /* set_shader_images */ |
| #define PIPE_BIND_COMPUTE_RESOURCE (1 << 16) /* set_compute_resources */ |
| #define PIPE_BIND_COMMAND_ARGS_BUFFER (1 << 17) /* pipe_draw_info.indirect */ |
| #define PIPE_BIND_QUERY_BUFFER (1 << 18) /* get_query_result_resource */ |
| |
| static inline uint32_t AlignUp(uint32_t n, uint32_t a) { return ((n + a - 1) / a) * a; } |
| |
| struct ResourceFormatInfo { |
| uint32_t drm_fourcc; |
| int bpp; |
| }; |
| |
| static std::unordered_map<int, struct ResourceFormatInfo> virglFormatInfoMap = { |
| {VIRGL_FORMAT_B8G8R8A8_UNORM, {DRM_FORMAT_ARGB8888, 4}}, |
| {VIRGL_FORMAT_B8G8R8X8_UNORM, {DRM_FORMAT_XRGB8888, 4}}, |
| {VIRGL_FORMAT_B5G6R5_UNORM, {DRM_FORMAT_RGB565, 2}}, |
| {VIRGL_FORMAT_R8G8B8A8_UNORM, {DRM_FORMAT_ABGR8888, 4}}, |
| {VIRGL_FORMAT_R8G8B8X8_UNORM, {DRM_FORMAT_XBGR8888, 4}}, |
| {VIRGL_FORMAT_R8_UNORM, {DRM_FORMAT_R8, 1}}, |
| }; |
| |
| static std::optional<int> DrmFourccToVirglFormat(uint32_t drm_fourcc) { |
| for (auto it : virglFormatInfoMap) { |
| if (it.second.drm_fourcc == drm_fourcc) { |
| return it.first; |
| } |
| } |
| return -1; |
| } |
| |
| static std::optional<struct ResourceFormatInfo> VirglFormatInfo(uint32_t virglFormat) { |
| auto it = virglFormatInfoMap.find(virglFormat); |
| if (virglFormatInfoMap.end() != it) { |
| return it->second; |
| } |
| return std::nullopt; |
| } |
| |
| VirtioGpuResourceType GetResourceType(const struct stream_renderer_resource_create_args& args) { |
| if (args.target == PIPE_BUFFER) { |
| return VirtioGpuResourceType::PIPE; |
| } |
| |
| if (args.format != VIRGL_FORMAT_R8_UNORM) { |
| return VirtioGpuResourceType::COLOR_BUFFER; |
| } |
| if (args.bind & VIRGL_BIND_SAMPLER_VIEW) { |
| return VirtioGpuResourceType::COLOR_BUFFER; |
| } |
| if (args.bind & VIRGL_BIND_RENDER_TARGET) { |
| return VirtioGpuResourceType::COLOR_BUFFER; |
| } |
| if (args.bind & VIRGL_BIND_SCANOUT) { |
| return VirtioGpuResourceType::COLOR_BUFFER; |
| } |
| if (args.bind & VIRGL_BIND_CURSOR) { |
| return VirtioGpuResourceType::COLOR_BUFFER; |
| } |
| if (!(args.bind & VIRGL_BIND_LINEAR)) { |
| return VirtioGpuResourceType::COLOR_BUFFER; |
| } |
| |
| return VirtioGpuResourceType::BUFFER; |
| } |
| |
| } // namespace |
| |
| /*static*/ |
| std::optional<VirtioGpuResource> VirtioGpuResource::Create( |
| const struct stream_renderer_resource_create_args* args, struct iovec* iov, uint32_t num_iovs) { |
| stream_renderer_debug("resource id: %u", args->handle); |
| |
| const auto resourceType = GetResourceType(*args); |
| if (resourceType == VirtioGpuResourceType::BLOB) { |
| stream_renderer_error("Failed to create resource: encountered blob."); |
| return std::nullopt; |
| } |
| |
| if (resourceType == VirtioGpuResourceType::PIPE) { |
| // Frontend only resource. |
| } else if (resourceType == VirtioGpuResourceType::BUFFER) { |
| FrameBuffer::getFB()->createBufferWithResourceHandle(args->width * args->height, |
| args->handle); |
| } else if (resourceType == VirtioGpuResourceType::COLOR_BUFFER) { |
| const uint32_t glformat = virgl_format_to_gl(args->format); |
| const auto fwkformat = (gfxstream::FrameworkFormat)virgl_format_to_fwk_format(args->format); |
| const bool linear = |
| #ifdef GFXSTREAM_ENABLE_GUEST_VIRTIO_RESOURCE_TILING_CONTROL |
| !!(args->bind & VIRGL_BIND_LINEAR); |
| #else |
| false; |
| #endif |
| FrameBuffer::getFB()->createColorBufferWithResourceHandle( |
| args->width, args->height, glformat, fwkformat, args->handle, linear); |
| FrameBuffer::getFB()->setGuestManagedColorBufferLifetime(true /* guest manages lifetime */); |
| FrameBuffer::getFB()->openColorBuffer(args->handle); |
| } else { |
| stream_renderer_error("Failed to create resource: unhandled type."); |
| return std::nullopt; |
| } |
| |
| VirtioGpuResource resource; |
| resource.mId = args->handle; |
| resource.mResourceType = resourceType; |
| resource.mCreateArgs = *args; |
| |
| resource.AttachIov(iov, num_iovs); |
| |
| return resource; |
| } |
| |
| /*static*/ |
| std::optional<VirtioGpuResource> VirtioGpuResource::Create( |
| uint32_t res_handle, const struct stream_renderer_handle* import_handle, |
| const struct stream_renderer_import_data* import_data) { |
| stream_renderer_debug("resource id: %u", res_handle); |
| |
| if (!import_handle || !import_data) { |
| stream_renderer_error("Failed to import resource: import_handle/import_data not provided."); |
| return std::nullopt; |
| } else if (!(import_data->flags & STREAM_RENDERER_IMPORT_FLAG_3D_INFO)) { |
| stream_renderer_error( |
| "Failed to import resource: stream_renderer_3d_info not provided in import data."); |
| return std::nullopt; |
| } |
| |
| struct stream_renderer_resource_create_args internal_create_args = {0}; |
| internal_create_args.handle = res_handle; |
| // TODO([email protected]): Determine VIRGL_BIND_LINEAR from info_3d? |
| internal_create_args.bind = VIRGL_BIND_SAMPLER_VIEW | VIRGL_BIND_SCANOUT | VIRGL_BIND_SHARED; |
| internal_create_args.target = PIPE_TEXTURE_2D; |
| // From info_3d |
| auto virglFormat = DrmFourccToVirglFormat(import_data->info_3d.drm_fourcc); |
| if (!virglFormat) { |
| stream_renderer_error("No virgl format available for drm_fourcc: %d", |
| import_data->info_3d.drm_fourcc); |
| return std::nullopt; |
| } |
| internal_create_args.format = *virglFormat; |
| internal_create_args.width = import_data->info_3d.width; |
| internal_create_args.height = import_data->info_3d.height; |
| // Default values |
| internal_create_args.depth = 1; |
| internal_create_args.array_size = 1; |
| internal_create_args.last_level = 0; |
| internal_create_args.nr_samples = 0; |
| internal_create_args.flags = 0; |
| |
| const auto resourceType = GetResourceType(internal_create_args); |
| if (resourceType != VirtioGpuResourceType::COLOR_BUFFER) { |
| stream_renderer_error( |
| "Failed to create resource with import_handle: arguments resulted in unhandled type. " |
| "Only ColorBuffer resources are supported for import."); |
| return std::nullopt; |
| } |
| |
| ExternalObjectManager::get()->addResourceExternalHandleInfo( |
| res_handle, ExternalHandleInfo{ |
| .handle = import_handle->os_handle, |
| .streamHandleType = import_handle->handle_type, |
| }); |
| const uint32_t glformat = virgl_format_to_gl(internal_create_args.format); |
| const auto fwkformat = |
| (gfxstream::FrameworkFormat)virgl_format_to_fwk_format(internal_create_args.format); |
| const bool linear = |
| #ifdef GFXSTREAM_ENABLE_GUEST_VIRTIO_RESOURCE_TILING_CONTROL |
| !!(internal_create_args.bind & VIRGL_BIND_LINEAR); |
| #else |
| false; |
| #endif |
| FrameBuffer::getFB()->createColorBufferWithResourceHandle( |
| internal_create_args.width, internal_create_args.height, glformat, fwkformat, |
| internal_create_args.handle, linear); |
| FrameBuffer::getFB()->setGuestManagedColorBufferLifetime(true /* guest manages lifetime */); |
| FrameBuffer::getFB()->openColorBuffer(internal_create_args.handle); |
| |
| VirtioGpuResource resource; |
| resource.mId = res_handle; |
| resource.mResourceType = resourceType; |
| resource.mCreateArgs = internal_create_args; |
| |
| return resource; |
| } |
| |
| /*static*/ std::optional<VirtioGpuResource> VirtioGpuResource::Create( |
| const gfxstream::host::FeatureSet& features, uint32_t pageSize, uint32_t contextId, |
| uint32_t resourceId, const struct stream_renderer_resource_create_args* createArgs, |
| const struct stream_renderer_create_blob* createBlobArgs, |
| const struct stream_renderer_handle* handle) { |
| VirtioGpuResource resource; |
| |
| std::optional<BlobDescriptorInfo> descriptorInfoOpt; |
| |
| if (createArgs != nullptr) { |
| auto resourceType = GetResourceType(*createArgs); |
| if (resourceType != VirtioGpuResourceType::BUFFER && |
| resourceType != VirtioGpuResourceType::COLOR_BUFFER) { |
| stream_renderer_error("failed to create blob resource: unhandled type."); |
| return std::nullopt; |
| } |
| |
| auto resourceOpt = Create(createArgs, nullptr, 0); |
| if (!resourceOpt) { |
| return std::nullopt; |
| } |
| |
| if (resourceType == VirtioGpuResourceType::BUFFER) { |
| descriptorInfoOpt = FrameBuffer::getFB()->exportBuffer(resourceId); |
| } else if (resourceType == VirtioGpuResourceType::COLOR_BUFFER) { |
| descriptorInfoOpt = FrameBuffer::getFB()->exportColorBuffer(resourceId); |
| } else { |
| stream_renderer_error("failed to create blob resource: unhandled type."); |
| return std::nullopt; |
| } |
| |
| resource = std::move(*resourceOpt); |
| } else { |
| resource.mResourceType = VirtioGpuResourceType::BLOB; |
| } |
| |
| resource.mId = resourceId; |
| resource.mCreateBlobArgs = *createBlobArgs; |
| |
| if (createBlobArgs->blob_id == 0) { |
| RingBlobMemory memory; |
| if (features.ExternalBlob.enabled) { |
| memory = RingBlob::CreateWithShmem(resourceId, createBlobArgs->size); |
| } else { |
| memory = RingBlob::CreateWithHostMemory(resourceId, createBlobArgs->size, pageSize); |
| } |
| if (!memory) { |
| stream_renderer_error("Failed to create blob: failed to create ring blob."); |
| return std::nullopt; |
| } |
| resource.mBlobMemory.emplace(std::move(memory)); |
| } else if (features.ExternalBlob.enabled) { |
| if (createBlobArgs->blob_mem == STREAM_BLOB_MEM_GUEST && |
| (createBlobArgs->blob_flags & STREAM_BLOB_FLAG_CREATE_GUEST_HANDLE)) { |
| #if defined(__linux__) || defined(__QNX__) |
| ManagedDescriptor managedHandle(handle->os_handle); |
| ExternalObjectManager::get()->addBlobDescriptorInfo( |
| contextId, createBlobArgs->blob_id, std::move(managedHandle), handle->handle_type, |
| 0, std::nullopt); |
| #else |
| stream_renderer_error("Failed to create blob: unimplemented external blob."); |
| return std::nullopt; |
| #endif |
| } else { |
| if (!descriptorInfoOpt) { |
| descriptorInfoOpt = ExternalObjectManager::get()->removeBlobDescriptorInfo( |
| contextId, createBlobArgs->blob_id); |
| } |
| if (!descriptorInfoOpt) { |
| stream_renderer_error("Failed to create blob: no external blob descriptor."); |
| return std::nullopt; |
| } |
| resource.mBlobMemory.emplace( |
| std::make_shared<BlobDescriptorInfo>(std::move(*descriptorInfoOpt))); |
| } |
| } else { |
| auto memoryMappingOpt = |
| ExternalObjectManager::get()->removeMapping(contextId, createBlobArgs->blob_id); |
| if (!memoryMappingOpt) { |
| stream_renderer_error("Failed to create blob: no external blob mapping."); |
| return std::nullopt; |
| } |
| resource.mBlobMemory.emplace(std::move(*memoryMappingOpt)); |
| } |
| |
| return resource; |
| } |
| |
| int VirtioGpuResource::Destroy() { |
| if (mResourceType == VirtioGpuResourceType::BUFFER) { |
| FrameBuffer::getFB()->closeBuffer(mId); |
| } else if (mResourceType == VirtioGpuResourceType::COLOR_BUFFER) { |
| FrameBuffer::getFB()->closeColorBuffer(mId); |
| } |
| return 0; |
| } |
| |
| int VirtioGpuResource::ImportHandle(const struct stream_renderer_handle* handle, |
| const struct stream_renderer_import_data* import_data) { |
| if (mResourceType != VirtioGpuResourceType::COLOR_BUFFER) { |
| stream_renderer_error( |
| "Failed to ImportResource: importing external handles to existing resources is only " |
| "supported for ColorBuffer resources."); |
| return -EINVAL; |
| } |
| |
| auto colorBufferPtr = FrameBuffer::getFB()->findColorBuffer(mId); |
| if (!colorBufferPtr) { |
| stream_renderer_error( |
| "Failed to ImportResource: could not find colorBuffer for res_handle: %d", mId); |
| return -EINVAL; |
| } |
| |
| const bool preserveContent = |
| (import_data->flags & STREAM_RENDERER_IMPORT_FLAG_PRESERVE_CONTENT); |
| bool importSuccess = false; |
| switch (handle->handle_type) { |
| #if GFXSTREAM_ENABLE_HOST_GLES |
| case STREAM_HANDLE_TYPE_PLATFORM_EGL_NATIVE_PIXMAP: |
| importSuccess = colorBufferPtr->glOpImportEglNativePixmap( |
| reinterpret_cast<void*>(handle->os_handle), preserveContent); |
| break; |
| #endif |
| default: |
| ERR("Unsupported handle_type: 0x%x, specified for importing to resource: %d", |
| handle->handle_type, mId); |
| return -EINVAL; |
| } |
| |
| return (importSuccess ? 0 : -EINVAL); |
| } |
| |
| void VirtioGpuResource::AttachIov(struct iovec* iov, uint32_t num_iovs) { |
| mIovs.clear(); |
| mLinear.clear(); |
| |
| size_t linearSize = 0; |
| if (num_iovs) { |
| mIovs.reserve(num_iovs); |
| for (int i = 0; i < num_iovs; ++i) { |
| mIovs.push_back(iov[i]); |
| linearSize += iov[i].iov_len; |
| } |
| } |
| |
| if (linearSize > 0) { |
| mLinear.resize(linearSize, 0); |
| } |
| } |
| |
| void VirtioGpuResource::AttachToContext(VirtioGpuContextId contextId) { |
| mAttachedToContexts.insert(contextId); |
| mLatestAttachedContext = contextId; |
| } |
| |
| void VirtioGpuResource::DetachFromContext(VirtioGpuContextId contextId) { |
| mAttachedToContexts.erase(contextId); |
| mLatestAttachedContext.reset(); |
| mHostPipe = nullptr; |
| } |
| |
| std::unordered_set<VirtioGpuContextId> VirtioGpuResource::GetAttachedContexts() const { |
| return mAttachedToContexts; |
| } |
| |
| void VirtioGpuResource::DetachIov() { |
| mIovs.clear(); |
| mLinear.clear(); |
| } |
| |
| int VirtioGpuResource::Map(void** outAddress, uint64_t* outSize) { |
| if (!mBlobMemory) { |
| stream_renderer_error("Failed to map resource %d: no blob memory to map.", mId); |
| return -EINVAL; |
| } |
| |
| void* hva = nullptr; |
| uint64_t hvaSize = 0; |
| |
| if (std::holds_alternative<RingBlobMemory>(*mBlobMemory)) { |
| auto& memory = std::get<RingBlobMemory>(*mBlobMemory); |
| hva = memory->map(); |
| hvaSize = memory->size(); |
| } else if (std::holds_alternative<ExternalMemoryMapping>(*mBlobMemory)) { |
| if (!mCreateBlobArgs) { |
| stream_renderer_error("failed to map resource %d: missing args.", mId); |
| return -EINVAL; |
| } |
| auto& memory = std::get<ExternalMemoryMapping>(*mBlobMemory); |
| hva = memory.addr; |
| hvaSize = mCreateBlobArgs->size; |
| } else { |
| stream_renderer_error("failed to map resource %d: no mappable memory.", mId); |
| return -EINVAL; |
| } |
| |
| if (outAddress) { |
| *outAddress = hva; |
| } |
| if (outSize) { |
| *outSize = hvaSize; |
| } |
| return 0; |
| } |
| |
| int VirtioGpuResource::GetInfo(struct stream_renderer_resource_info* outInfo) const { |
| if (!mCreateArgs) { |
| stream_renderer_error("Failed to get info: resource %d missing args.", mId); |
| return ENOENT; |
| } |
| |
| auto formatInfo = VirglFormatInfo(mCreateArgs->format); |
| if (!formatInfo) { |
| return EINVAL; |
| } |
| |
| outInfo->drm_fourcc = formatInfo->drm_fourcc; |
| outInfo->stride = AlignUp(mCreateArgs->width * formatInfo->bpp, 16U); |
| outInfo->virgl_format = mCreateArgs->format; |
| outInfo->handle = mCreateArgs->handle; |
| outInfo->height = mCreateArgs->height; |
| outInfo->width = mCreateArgs->width; |
| outInfo->depth = mCreateArgs->depth; |
| outInfo->flags = mCreateArgs->flags; |
| outInfo->tex_id = 0; |
| return 0; |
| } |
| |
| int VirtioGpuResource::GetVulkanInfo(struct stream_renderer_vulkan_info* outInfo) const { |
| if (!mBlobMemory) { |
| return -EINVAL; |
| } |
| if (!std::holds_alternative<ExternalMemoryInfo>(*mBlobMemory)) { |
| return -EINVAL; |
| } |
| auto& memory = std::get<ExternalMemoryInfo>(*mBlobMemory); |
| if (!memory->vulkanInfoOpt) { |
| return -EINVAL; |
| } |
| auto& memoryVulkanInfo = *memory->vulkanInfoOpt; |
| |
| outInfo->memory_index = memoryVulkanInfo.memoryIndex; |
| memcpy(outInfo->device_id.device_uuid, memoryVulkanInfo.deviceUUID, |
| sizeof(outInfo->device_id.device_uuid)); |
| memcpy(outInfo->device_id.driver_uuid, memoryVulkanInfo.driverUUID, |
| sizeof(outInfo->device_id.driver_uuid)); |
| return 0; |
| } |
| |
| int VirtioGpuResource::GetCaching(uint32_t* outCaching) const { |
| if (!mBlobMemory) { |
| stream_renderer_error("failed to get caching for resource %d: no blob memory", mId); |
| return -EINVAL; |
| } |
| |
| if (!std::holds_alternative<ExternalMemoryMapping>(*mBlobMemory) || |
| !std::holds_alternative<ExternalMemoryInfo>(*mBlobMemory)) { |
| *outCaching = STREAM_RENDERER_MAP_CACHE_CACHED; |
| return 0; |
| } else if (std::holds_alternative<ExternalMemoryMapping>(*mBlobMemory)) { |
| auto& memory = std::get<ExternalMemoryMapping>(*mBlobMemory); |
| *outCaching = memory.caching; |
| return 0; |
| } else if (std::holds_alternative<ExternalMemoryInfo>(*mBlobMemory)) { |
| auto& memory = std::get<ExternalMemoryInfo>(*mBlobMemory); |
| *outCaching = memory->caching; |
| return 0; |
| } |
| |
| stream_renderer_error("failed to get caching for resource %d: unhandled type?", mId); |
| return -EINVAL; |
| } |
| |
| int VirtioGpuResource::WaitSyncResource() { |
| if (mResourceType != VirtioGpuResourceType::COLOR_BUFFER) { |
| stream_renderer_error("waitSyncResource is undefined for non-ColorBuffer resource."); |
| return -EINVAL; |
| } |
| |
| return FrameBuffer::getFB()->waitSyncColorBuffer(mId); |
| } |
| |
| // Corresponds to Virtio GPU "TransferFromHost" commands and VMM requests to |
| // copy into display buffers. |
| int VirtioGpuResource::TransferRead(const GoldfishPipeServiceOps* ops, uint64_t offset, |
| stream_renderer_box* box, |
| std::optional<std::vector<struct iovec>> iovs) { |
| // First, copy from the underlying backend resource to this resource's linear buffer: |
| int ret = 0; |
| if (mResourceType == VirtioGpuResourceType::BLOB) { |
| stream_renderer_error("Failed to transfer: unexpected blob."); |
| return -EINVAL; |
| } else if (mResourceType == VirtioGpuResourceType::PIPE) { |
| ret = ReadFromPipeToLinear(ops, offset, box); |
| } else if (mResourceType == VirtioGpuResourceType::BUFFER) { |
| ret = ReadFromBufferToLinear(offset, box); |
| } else if (mResourceType == VirtioGpuResourceType::COLOR_BUFFER) { |
| ret = ReadFromColorBufferToLinear(offset, box); |
| } else { |
| stream_renderer_error("Failed to transfer: unhandled resource type."); |
| return -EINVAL; |
| } |
| if (ret != 0) { |
| stream_renderer_error("Failed to transfer: failed to sync with backend resource."); |
| return ret; |
| } |
| |
| // Second, copy from this resource's linear buffer to the desired iov: |
| if (iovs) { |
| ret = TransferToIov(offset, box, *iovs); |
| } else { |
| ret = TransferToIov(offset, box, mIovs); |
| } |
| if (ret != 0) { |
| stream_renderer_error("Failed to transfer: failed to copy to iov."); |
| } |
| return ret; |
| } |
| |
| // Corresponds to Virtio GPU "TransferToHost" commands. |
| VirtioGpuResource::TransferWriteResult VirtioGpuResource::TransferWrite( |
| const GoldfishPipeServiceOps* ops, uint64_t offset, stream_renderer_box* box, |
| std::optional<std::vector<struct iovec>> iovs) { |
| // First, copy from the desired iov to this resource's linear buffer: |
| int ret = 0; |
| if (iovs) { |
| ret = TransferFromIov(offset, box, *iovs); |
| } else { |
| ret = TransferFromIov(offset, box, mIovs); |
| } |
| if (ret != 0) { |
| stream_renderer_error("Failed to transfer: failed to copy from iov."); |
| return TransferWriteResult{ |
| .status = ret, |
| }; |
| } |
| |
| // Second, copy from this resource's linear buffer to the underlying backend resource: |
| if (mResourceType == VirtioGpuResourceType::BLOB) { |
| stream_renderer_error("Failed to transfer: unexpected blob."); |
| return TransferWriteResult{ |
| .status = -EINVAL, |
| }; |
| } else if (mResourceType == VirtioGpuResourceType::PIPE) { |
| return WriteToPipeFromLinear(ops, offset, box); |
| } else if (mResourceType == VirtioGpuResourceType::BUFFER) { |
| ret = WriteToBufferFromLinear(offset, box); |
| } else if (mResourceType == VirtioGpuResourceType::COLOR_BUFFER) { |
| ret = WriteToColorBufferFromLinear(offset, box); |
| } else { |
| stream_renderer_error("Failed to transfer: unhandled resource type."); |
| return TransferWriteResult{ |
| .status = -EINVAL, |
| }; |
| } |
| if (ret != 0) { |
| stream_renderer_error("Failed to transfer: failed to sync with backend resource."); |
| } |
| return TransferWriteResult{ |
| .status = ret, |
| }; |
| } |
| |
| int VirtioGpuResource::ReadFromPipeToLinear(const GoldfishPipeServiceOps* ops, uint64_t offset, |
| stream_renderer_box* box) { |
| if (mResourceType != VirtioGpuResourceType::PIPE) { |
| stream_renderer_error("Failed to transfer: resource %d is not PIPE.", mId); |
| return -EINVAL; |
| } |
| |
| // Do the pipe service op here, if there is an associated hostpipe. |
| auto hostPipe = mHostPipe; |
| if (!hostPipe) { |
| stream_renderer_error("Failed to transfer: resource %d missing PIPE.", mId); |
| return -EINVAL; |
| } |
| |
| size_t readBytes = 0; |
| size_t wantedBytes = readBytes + (size_t)box->w; |
| |
| while (readBytes < wantedBytes) { |
| GoldfishPipeBuffer buf = { |
| ((char*)mLinear.data()) + box->x + readBytes, |
| wantedBytes - readBytes, |
| }; |
| auto status = ops->guest_recv(hostPipe, &buf, 1); |
| |
| if (status > 0) { |
| readBytes += status; |
| } else if (status == kPipeTryAgain) { |
| ops->wait_guest_recv(hostPipe); |
| } else { |
| return EIO; |
| } |
| } |
| |
| return 0; |
| } |
| |
| VirtioGpuResource::TransferWriteResult VirtioGpuResource::WriteToPipeFromLinear( |
| const GoldfishPipeServiceOps* ops, uint64_t offset, stream_renderer_box* box) { |
| if (mResourceType != VirtioGpuResourceType::PIPE) { |
| stream_renderer_error("Failed to transfer: resource %d is not PIPE.", mId); |
| return TransferWriteResult{ |
| .status = -EINVAL, |
| }; |
| } |
| |
| if (!mCreateArgs) { |
| stream_renderer_error("Failed to transfer: resource %d missing args.", mId); |
| return TransferWriteResult{ |
| .status = -EINVAL, |
| }; |
| } |
| |
| // Do the pipe service op here, if there is an associated hostpipe. |
| auto hostPipe = mHostPipe; |
| if (!hostPipe) { |
| stream_renderer_error("No hostPipe"); |
| return TransferWriteResult{ |
| .status = -EINVAL, |
| }; |
| } |
| |
| stream_renderer_debug("resid: %d offset: 0x%llx hostpipe: %p", mCreateArgs->handle, |
| (unsigned long long)offset, hostPipe); |
| |
| size_t writtenBytes = 0; |
| size_t wantedBytes = (size_t)box->w; |
| |
| GoldfishHostPipe* updatedHostPipe = nullptr; |
| |
| while (writtenBytes < wantedBytes) { |
| GoldfishPipeBuffer buf = { |
| ((char*)mLinear.data()) + box->x + writtenBytes, |
| wantedBytes - writtenBytes, |
| }; |
| |
| // guest_send can now reallocate the pipe. |
| void* hostPipeBefore = hostPipe; |
| auto status = ops->guest_send(&hostPipe, &buf, 1); |
| |
| if (hostPipe != hostPipeBefore) { |
| updatedHostPipe = hostPipe; |
| } |
| |
| if (status > 0) { |
| writtenBytes += status; |
| } else if (status == kPipeTryAgain) { |
| ops->wait_guest_send(hostPipe); |
| } else { |
| return TransferWriteResult{ |
| .status = EIO, |
| }; |
| } |
| } |
| |
| TransferWriteResult result = { |
| .status = 0, |
| }; |
| if (updatedHostPipe != nullptr) { |
| result.contextId = mLatestAttachedContext.value_or(-1); |
| result.contextPipe = updatedHostPipe; |
| } |
| return result; |
| } |
| |
| int VirtioGpuResource::ReadFromBufferToLinear(uint64_t offset, stream_renderer_box* box) { |
| if (mResourceType != VirtioGpuResourceType::BUFFER) { |
| stream_renderer_error("Failed to transfer: resource %d is not BUFFER.", mId); |
| return -EINVAL; |
| } |
| |
| if (!mCreateArgs) { |
| stream_renderer_error("Failed to transfer: resource %d missing args.", mId); |
| return -EINVAL; |
| } |
| |
| FrameBuffer::getFB()->readBuffer(mCreateArgs->handle, 0, |
| mCreateArgs->width * mCreateArgs->height, mLinear.data()); |
| return 0; |
| } |
| |
| int VirtioGpuResource::WriteToBufferFromLinear(uint64_t offset, stream_renderer_box* box) { |
| if (mResourceType != VirtioGpuResourceType::BUFFER) { |
| stream_renderer_error("Failed to transfer: resource %d is not BUFFER.", mId); |
| return -EINVAL; |
| } |
| |
| if (!mCreateArgs) { |
| stream_renderer_error("Failed to transfer: resource %d missing args.", mId); |
| return -EINVAL; |
| } |
| |
| FrameBuffer::getFB()->updateBuffer(mCreateArgs->handle, 0, |
| mCreateArgs->width * mCreateArgs->height, mLinear.data()); |
| return 0; |
| } |
| |
| int VirtioGpuResource::ReadFromColorBufferToLinear(uint64_t offset, stream_renderer_box* box) { |
| if (mResourceType != VirtioGpuResourceType::COLOR_BUFFER) { |
| stream_renderer_error("Failed to transfer: resource %d is not COLOR_BUFFER.", mId); |
| return -EINVAL; |
| } |
| |
| if (!mCreateArgs) { |
| stream_renderer_error("Failed to transfer: resource %d missing args.", mId); |
| return -EINVAL; |
| } |
| |
| auto glformat = virgl_format_to_gl(mCreateArgs->format); |
| auto gltype = gl_format_to_natural_type(glformat); |
| |
| // We always xfer the whole thing again from GL |
| // since it's fiddly to calc / copy-out subregions |
| if (virgl_format_is_yuv(mCreateArgs->format)) { |
| FrameBuffer::getFB()->readColorBufferYUV(mCreateArgs->handle, 0, 0, mCreateArgs->width, |
| mCreateArgs->height, mLinear.data(), |
| mLinear.size()); |
| } else { |
| FrameBuffer::getFB()->readColorBuffer(mCreateArgs->handle, 0, 0, mCreateArgs->width, |
| mCreateArgs->height, glformat, gltype, |
| mLinear.data()); |
| } |
| |
| return 0; |
| } |
| |
| int VirtioGpuResource::WriteToColorBufferFromLinear(uint64_t offset, stream_renderer_box* box) { |
| if (mResourceType != VirtioGpuResourceType::COLOR_BUFFER) { |
| stream_renderer_error("Failed to transfer: resource %d is not COLOR_BUFFER.", mId); |
| return -EINVAL; |
| } |
| |
| if (!mCreateArgs) { |
| stream_renderer_error("Failed to transfer: resource %d missing args.", mId); |
| return -EINVAL; |
| } |
| |
| auto glformat = virgl_format_to_gl(mCreateArgs->format); |
| auto gltype = gl_format_to_natural_type(glformat); |
| |
| // We always xfer the whole thing again to GL |
| // since it's fiddly to calc / copy-out subregions |
| FrameBuffer::getFB()->updateColorBuffer(mCreateArgs->handle, 0, 0, mCreateArgs->width, |
| mCreateArgs->height, glformat, gltype, mLinear.data()); |
| return 0; |
| } |
| |
| int VirtioGpuResource::TransferToIov(uint64_t offset, const stream_renderer_box* box, |
| std::optional<std::vector<struct iovec>> iovs) { |
| if (iovs) { |
| return TransferWithIov(offset, box, *iovs, TransferDirection::LINEAR_TO_IOV); |
| } else { |
| return TransferWithIov(offset, box, mIovs, TransferDirection::LINEAR_TO_IOV); |
| } |
| } |
| |
| int VirtioGpuResource::TransferFromIov(uint64_t offset, const stream_renderer_box* box, |
| std::optional<std::vector<struct iovec>> iovs) { |
| if (iovs) { |
| return TransferWithIov(offset, box, *iovs, TransferDirection::IOV_TO_LINEAR); |
| } else { |
| return TransferWithIov(offset, box, mIovs, TransferDirection::IOV_TO_LINEAR); |
| } |
| } |
| |
| int VirtioGpuResource::TransferWithIov(uint64_t offset, const stream_renderer_box* box, |
| const std::vector<struct iovec>& iovs, |
| TransferDirection direction) { |
| if (!mCreateArgs) { |
| stream_renderer_error("failed to transfer: missing resource args."); |
| return -EINVAL; |
| } |
| if (box->x > mCreateArgs->width || box->y > mCreateArgs->height) { |
| stream_renderer_error("failed to transfer: box out of range of resource"); |
| return -EINVAL; |
| } |
| if (box->w == 0U || box->h == 0U) { |
| stream_renderer_error("failed to transfer: empty transfer"); |
| return -EINVAL; |
| } |
| if (box->x + box->w > mCreateArgs->width) { |
| stream_renderer_error("failed to transfer: box overflows resource width"); |
| return -EINVAL; |
| } |
| |
| size_t linearBase = |
| virgl_format_to_linear_base(mCreateArgs->format, mCreateArgs->width, mCreateArgs->height, |
| box->x, box->y, box->w, box->h); |
| size_t start = linearBase; |
| // height - 1 in order to treat the (w * bpp) row specially |
| // (i.e., the last row does not occupy the full stride) |
| size_t length = |
| virgl_format_to_total_xfer_len(mCreateArgs->format, mCreateArgs->width, mCreateArgs->height, |
| box->x, box->y, box->w, box->h); |
| size_t end = start + length; |
| |
| if (start == end) { |
| stream_renderer_error("failed to transfer: nothing to transfer"); |
| return -EINVAL; |
| } |
| |
| if (end > mLinear.size()) { |
| stream_renderer_error("failed to transfer: start + length overflows!"); |
| return -EINVAL; |
| } |
| |
| uint32_t iovIndex = 0; |
| size_t iovOffset = 0; |
| size_t written = 0; |
| char* linear = static_cast<char*>(mLinear.data()); |
| |
| while (written < length) { |
| if (iovIndex >= iovs.size()) { |
| stream_renderer_error("failed to transfer: write request overflowed iovs"); |
| return -EINVAL; |
| } |
| |
| const char* iovBase_const = static_cast<const char*>(iovs[iovIndex].iov_base); |
| char* iovBase = static_cast<char*>(iovs[iovIndex].iov_base); |
| size_t iovLen = iovs[iovIndex].iov_len; |
| size_t iovOffsetEnd = iovOffset + iovLen; |
| |
| auto lower_intersect = std::max(iovOffset, start); |
| auto upper_intersect = std::min(iovOffsetEnd, end); |
| if (lower_intersect < upper_intersect) { |
| size_t toWrite = upper_intersect - lower_intersect; |
| switch (direction) { |
| case TransferDirection::IOV_TO_LINEAR: |
| memcpy(linear + lower_intersect, iovBase_const + lower_intersect - iovOffset, |
| toWrite); |
| break; |
| case TransferDirection::LINEAR_TO_IOV: |
| memcpy(iovBase + lower_intersect - iovOffset, linear + lower_intersect, |
| toWrite); |
| break; |
| default: |
| stream_renderer_error("failed to transfer: invalid synchronization dir"); |
| return -EINVAL; |
| } |
| written += toWrite; |
| } |
| ++iovIndex; |
| iovOffset += iovLen; |
| } |
| |
| return 0; |
| } |
| |
| int VirtioGpuResource::ExportBlob(struct stream_renderer_handle* outHandle) { |
| if (!mBlobMemory) { |
| return -EINVAL; |
| } |
| |
| if (std::holds_alternative<RingBlobMemory>(*mBlobMemory)) { |
| auto& memory = std::get<RingBlobMemory>(*mBlobMemory); |
| if (!memory->isExportable()) { |
| return -EINVAL; |
| } |
| |
| // Handle ownership transferred to VMM, Gfxstream keeps the mapping. |
| #ifdef _WIN32 |
| outHandle->os_handle = |
| static_cast<int64_t>(reinterpret_cast<intptr_t>(memory->releaseHandle())); |
| #else |
| outHandle->os_handle = static_cast<int64_t>(memory->releaseHandle()); |
| #endif |
| outHandle->handle_type = STREAM_HANDLE_TYPE_MEM_SHM; |
| return 0; |
| } else if (std::holds_alternative<ExternalMemoryInfo>(*mBlobMemory)) { |
| auto& memory = std::get<ExternalMemoryInfo>(*mBlobMemory); |
| |
| auto rawDescriptorOpt = memory->descriptorInfo.descriptor.release(); |
| if (!rawDescriptorOpt) { |
| stream_renderer_error( |
| "failed to export blob for resource %u: failed to get raw handle.", mId); |
| return -EINVAL; |
| } |
| auto rawDescriptor = *rawDescriptorOpt; |
| |
| #ifdef _WIN32 |
| outHandle->os_handle = static_cast<int64_t>(reinterpret_cast<intptr_t>(rawDescriptor)); |
| #else |
| outHandle->os_handle = static_cast<int64_t>(rawDescriptor); |
| #endif |
| outHandle->handle_type = memory->descriptorInfo.streamHandleType; |
| return 0; |
| } |
| |
| return -EINVAL; |
| } |
| |
| std::shared_ptr<RingBlob> VirtioGpuResource::ShareRingBlob() { |
| if (!mBlobMemory) { |
| return nullptr; |
| } |
| if (!std::holds_alternative<RingBlobMemory>(*mBlobMemory)) { |
| return nullptr; |
| } |
| return std::get<RingBlobMemory>(*mBlobMemory); |
| } |
| |
| #ifdef GFXSTREAM_BUILD_WITH_SNAPSHOT_FRONTEND_SUPPORT |
| |
| std::optional<VirtioGpuResourceSnapshot> VirtioGpuResource::Snapshot() const { |
| VirtioGpuResourceSnapshot resourceSnapshot; |
| resourceSnapshot.set_id(mId); |
| resourceSnapshot.set_type(static_cast<::gfxstream::host::snapshot::VirtioGpuResourceType>(mResourceType)); |
| |
| if (mCreateArgs) { |
| VirtioGpuResourceCreateArgs* snapshotCreateArgs = resourceSnapshot.mutable_create_args(); |
| snapshotCreateArgs->set_id(mCreateArgs->handle); |
| snapshotCreateArgs->set_target(mCreateArgs->target); |
| snapshotCreateArgs->set_format(mCreateArgs->format); |
| snapshotCreateArgs->set_bind(mCreateArgs->bind); |
| snapshotCreateArgs->set_width(mCreateArgs->width); |
| snapshotCreateArgs->set_height(mCreateArgs->height); |
| snapshotCreateArgs->set_depth(mCreateArgs->depth); |
| snapshotCreateArgs->set_array_size(mCreateArgs->array_size); |
| snapshotCreateArgs->set_last_level(mCreateArgs->last_level); |
| snapshotCreateArgs->set_nr_samples(mCreateArgs->nr_samples); |
| snapshotCreateArgs->set_flags(mCreateArgs->flags); |
| } |
| |
| if (mCreateBlobArgs) { |
| auto* snapshotCreateArgs = resourceSnapshot.mutable_create_blob_args(); |
| snapshotCreateArgs->set_mem(mCreateBlobArgs->blob_mem); |
| snapshotCreateArgs->set_flags(mCreateBlobArgs->blob_flags); |
| snapshotCreateArgs->set_id(mCreateBlobArgs->blob_id); |
| snapshotCreateArgs->set_size(mCreateBlobArgs->size); |
| } |
| |
| if (mBlobMemory) { |
| if (std::holds_alternative<RingBlobMemory>(*mBlobMemory)) { |
| auto& memory = std::get<RingBlobMemory>(*mBlobMemory); |
| |
| auto snapshotRingBlobOpt = memory->Snapshot(); |
| if (!snapshotRingBlobOpt) { |
| stream_renderer_error("Failed to snapshot ring blob for resource %d.", mId); |
| return std::nullopt; |
| } |
| resourceSnapshot.mutable_ring_blob()->Swap(&*snapshotRingBlobOpt); |
| } else if (std::holds_alternative<ExternalMemoryInfo>(*mBlobMemory)) { |
| if (!mLatestAttachedContext) { |
| stream_renderer_error("Failed to snapshot resource %d: missing blob context?", mId); |
| return std::nullopt; |
| } |
| if (!mCreateBlobArgs) { |
| stream_renderer_error("Failed to snapshot resource %d: missing blob args?", mId); |
| return std::nullopt; |
| } |
| auto snapshotDescriptorInfo = resourceSnapshot.mutable_external_memory_descriptor(); |
| snapshotDescriptorInfo->set_context_id(*mLatestAttachedContext); |
| snapshotDescriptorInfo->set_blob_id(mCreateBlobArgs->blob_id); |
| } else if (std::holds_alternative<ExternalMemoryMapping>(*mBlobMemory)) { |
| if (!mLatestAttachedContext) { |
| stream_renderer_error("Failed to snapshot resource %d: missing blob context?", mId); |
| return std::nullopt; |
| } |
| if (!mCreateBlobArgs) { |
| stream_renderer_error("Failed to snapshot resource %d: missing blob args?", mId); |
| return std::nullopt; |
| } |
| auto snapshotDescriptorInfo = resourceSnapshot.mutable_external_memory_mapping(); |
| snapshotDescriptorInfo->set_context_id(*mLatestAttachedContext); |
| snapshotDescriptorInfo->set_blob_id(mCreateBlobArgs->blob_id); |
| } |
| } |
| |
| if (mLatestAttachedContext) { |
| resourceSnapshot.set_latest_attached_context(*mLatestAttachedContext); |
| } |
| |
| resourceSnapshot.mutable_attached_contexts()->Add(mAttachedToContexts.begin(), |
| mAttachedToContexts.end()); |
| |
| return resourceSnapshot; |
| } |
| |
| /*static*/ std::optional<VirtioGpuResource> VirtioGpuResource::Restore( |
| const VirtioGpuResourceSnapshot& resourceSnapshot) { |
| VirtioGpuResource resource = {}; |
| resource.mId = resourceSnapshot.id(); |
| resource.mResourceType = static_cast<VirtioGpuResourceType>(resourceSnapshot.type()); |
| |
| if (resourceSnapshot.has_create_args()) { |
| const auto& createArgsSnapshot = resourceSnapshot.create_args(); |
| resource.mCreateArgs = { |
| .handle = createArgsSnapshot.id(), |
| .target = createArgsSnapshot.target(), |
| .format = createArgsSnapshot.format(), |
| .bind = createArgsSnapshot.bind(), |
| .width = createArgsSnapshot.width(), |
| .height = createArgsSnapshot.height(), |
| .depth = createArgsSnapshot.depth(), |
| .array_size = createArgsSnapshot.array_size(), |
| .last_level = createArgsSnapshot.last_level(), |
| .nr_samples = createArgsSnapshot.nr_samples(), |
| .flags = createArgsSnapshot.flags(), |
| }; |
| } |
| |
| if (resourceSnapshot.has_create_blob_args()) { |
| const auto& createArgsSnapshot = resourceSnapshot.create_blob_args(); |
| resource.mCreateBlobArgs = { |
| .blob_mem = createArgsSnapshot.mem(), |
| .blob_flags = createArgsSnapshot.flags(), |
| .blob_id = createArgsSnapshot.id(), |
| .size = createArgsSnapshot.size(), |
| }; |
| } |
| |
| if (resourceSnapshot.has_ring_blob()) { |
| auto resourceRingBlobOpt = RingBlob::Restore(resourceSnapshot.ring_blob()); |
| if (!resourceRingBlobOpt) { |
| stream_renderer_error("Failed to restore ring blob for resource %d", resource.mId); |
| return std::nullopt; |
| } |
| resource.mBlobMemory.emplace(std::move(*resourceRingBlobOpt)); |
| } else if (resourceSnapshot.has_external_memory_descriptor()) { |
| const auto& snapshotDescriptorInfo = resourceSnapshot.external_memory_descriptor(); |
| |
| auto descriptorInfoOpt = ExternalObjectManager::get()->removeBlobDescriptorInfo( |
| snapshotDescriptorInfo.context_id(), snapshotDescriptorInfo.blob_id()); |
| if (!descriptorInfoOpt) { |
| stream_renderer_error( |
| "Failed to restore resource: failed to find blob descriptor info."); |
| return std::nullopt; |
| } |
| |
| resource.mBlobMemory.emplace( |
| std::make_shared<BlobDescriptorInfo>(std::move(*descriptorInfoOpt))); |
| } else if (resourceSnapshot.has_external_memory_mapping()) { |
| const auto& snapshotDescriptorInfo = resourceSnapshot.external_memory_mapping(); |
| |
| auto memoryMappingOpt = ExternalObjectManager::get()->removeMapping( |
| snapshotDescriptorInfo.context_id(), snapshotDescriptorInfo.blob_id()); |
| if (!memoryMappingOpt) { |
| stream_renderer_error("Failed to restore resource: failed to find mapping info."); |
| return std::nullopt; |
| } |
| resource.mBlobMemory.emplace(std::move(*memoryMappingOpt)); |
| } |
| |
| if (resourceSnapshot.has_latest_attached_context()) { |
| resource.mLatestAttachedContext = resourceSnapshot.latest_attached_context(); |
| } |
| |
| resource.mAttachedToContexts.insert(resourceSnapshot.attached_contexts().begin(), |
| resourceSnapshot.attached_contexts().end()); |
| |
| return resource; |
| } |
| |
| #endif // #ifdef GFXSTREAM_BUILD_WITH_SNAPSHOT_FRONTEND_SUPPORT |
| |
| } // namespace host |
| } // namespace gfxstream |