blob: e57c3b4fe15ac71e82f707aff2e2c736b98c7e36 [file] [log] [blame] [edit]
// Copyright (C) 2024 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "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