1 /*
2 * Copyright 2023 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "RutabagaLayer.h"
18
19 #include <inttypes.h>
20 #include <log/log.h>
21
22 #include <cstdlib>
23 #include <future>
24 #include <memory>
25 #include <optional>
26 #include <queue>
27 #include <sstream>
28 #include <string>
29 #include <thread>
30 #include <unordered_map>
31 #include <variant>
32
33 #include "gfxstream/virtio-gpu-gfxstream-renderer.h"
34 #include "gfxstream/virtio-gpu-gfxstream-renderer-unstable.h"
35
36 namespace gfxstream {
37 namespace {
38
39 constexpr const uint32_t kInvalidContextId = 0;
40
Split(const std::string & s,const std::string & delimiters)41 std::vector<std::string> Split(const std::string& s, const std::string& delimiters) {
42 if (delimiters.empty()) {
43 return {};
44 }
45
46 std::vector<std::string> result;
47
48 size_t base = 0;
49 size_t found;
50 while (true) {
51 found = s.find_first_of(delimiters, base);
52 result.push_back(s.substr(base, found - base));
53 if (found == s.npos) break;
54 base = found + 1;
55 }
56
57 return result;
58 }
59
Join(const std::vector<std::string> & things,const std::string & separator)60 std::string Join(const std::vector<std::string>& things, const std::string& separator) {
61 if (things.empty()) {
62 return "";
63 }
64
65 std::ostringstream result;
66 result << *things.begin();
67 for (auto it = std::next(things.begin()); it != things.end(); ++it) {
68 result << separator << *it;
69 }
70 return result.str();
71 }
72
73 } // namespace
74
75 class EmulatedVirtioGpu::EmulatedVirtioGpuImpl {
76 public:
77 EmulatedVirtioGpuImpl();
78 ~EmulatedVirtioGpuImpl();
79
80 bool Init(bool withGl, bool withVk, bool withVkSnapshots, EmulatedVirtioGpu* parent);
81
82 VirtGpuCaps GetCaps(VirtGpuCapset capset);
83
84 std::optional<uint32_t> CreateContext();
85 void DestroyContext(uint32_t contextId);
86
87 uint8_t* Map(uint32_t resourceId);
88 void Unmap(uint32_t resourceId);
89
90 int ExecBuffer(uint32_t contextId, struct VirtGpuExecBuffer& execbuffer,
91 std::optional<uint32_t> blobResourceId);
92
93 int Wait(uint32_t resourceId);
94
95 int TransferFromHost(uint32_t contextId, uint32_t resourceId, uint32_t transferOffset,
96 uint32_t transferSize);
97 int TransferToHost(uint32_t contextId, uint32_t resourceId, uint32_t transferOffset,
98 uint32_t transferSize);
99
100 std::optional<uint32_t> CreateBlob(uint32_t contextId, const struct VirtGpuCreateBlob& params);
101 std::optional<uint32_t> CreateVirglBlob(uint32_t contextId, uint32_t width, uint32_t height,
102 uint32_t virglFormat);
103
104 void DestroyResource(uint32_t contextId, uint32_t resourceId);
105
106 uint32_t CreateEmulatedFence();
107
108 void SignalEmulatedFence(uint32_t fenceId);
109
110 int WaitOnEmulatedFence(int fenceAsFileDescriptor, int timeoutMilliseconds);
111
112 private:
113 struct VirtioGpuTaskContextAttachResource {
114 uint32_t contextId;
115 uint32_t resourceId;
116 };
117 struct VirtioGpuTaskContextDetachResource {
118 uint32_t contextId;
119 uint32_t resourceId;
120 };
121 struct VirtioGpuTaskCreateContext {
122 uint32_t contextId;
123 uint32_t contextInit;
124 std::string contextName;
125 };
126 struct VirtioGpuTaskCreateBlob {
127 uint32_t contextId;
128 uint32_t resourceId;
129 struct stream_renderer_create_blob params;
130 };
131 struct VirtioGpuTaskCreateResource {
132 uint32_t contextId;
133 uint32_t resourceId;
134 uint8_t* resourceBytes;
135 struct stream_renderer_resource_create_args params;
136 };
137 struct VirtioGpuTaskDestroyContext {
138 uint32_t contextId;
139 };
140 struct VirtioGpuTaskMap {
141 uint32_t resourceId;
142 std::promise<uint8_t*> resourceMappedPromise;
143 };
144 struct VirtioGpuTaskExecBuffer {
145 uint32_t contextId;
146 std::vector<std::byte> commandBuffer;
147 };
148 struct VirtioGpuTaskTransferToHost {
149 uint32_t contextId;
150 uint32_t resourceId;
151 uint32_t transferOffset;
152 uint32_t transferSize;
153 };
154 struct VirtioGpuTaskTransferFromHost {
155 uint32_t contextId;
156 uint32_t resourceId;
157 uint32_t transferOffset;
158 uint32_t transferSize;
159 };
160 struct VirtioGpuTaskUnrefResource {
161 uint32_t resourceId;
162 };
163 using VirtioGpuTask =
164 std::variant<VirtioGpuTaskContextAttachResource, VirtioGpuTaskContextDetachResource,
165 VirtioGpuTaskCreateBlob, VirtioGpuTaskCreateContext,
166 VirtioGpuTaskCreateResource, VirtioGpuTaskDestroyContext, VirtioGpuTaskMap,
167 VirtioGpuTaskExecBuffer, VirtioGpuTaskTransferFromHost,
168 VirtioGpuTaskTransferToHost, VirtioGpuTaskUnrefResource>;
169 struct VirtioGpuTaskWithWaitable {
170 uint32_t contextId;
171 VirtioGpuTask task;
172 std::promise<void> taskCompletedSignaler;
173 std::optional<uint32_t> fence;
174 };
175
176 std::shared_future<void> EnqueueVirtioGpuTask(uint32_t contextId, VirtioGpuTask task,
177 std::optional<uint32_t> fence = std::nullopt);
178 void DoTask(VirtioGpuTaskContextAttachResource task);
179 void DoTask(VirtioGpuTaskContextDetachResource task);
180 void DoTask(VirtioGpuTaskCreateContext task);
181 void DoTask(VirtioGpuTaskCreateBlob task);
182 void DoTask(VirtioGpuTaskCreateResource task);
183 void DoTask(VirtioGpuTaskDestroyContext task);
184 void DoTask(VirtioGpuTaskMap task);
185 void DoTask(VirtioGpuTaskExecBuffer task);
186 void DoTask(VirtioGpuTaskTransferFromHost task);
187 void DoTask(VirtioGpuTaskTransferToHost task);
188 void DoTask(VirtioGpuTaskWithWaitable task);
189 void DoTask(VirtioGpuTaskUnrefResource task);
190
191 void RunVirtioGpuTaskProcessingLoop();
192
193 std::atomic<uint32_t> mNextContextId{1};
194 std::atomic<uint32_t> mNextVirtioGpuResourceId{1};
195 std::atomic<uint32_t> mNextVirtioGpuFenceId{1};
196
197 std::atomic_bool mShuttingDown{false};
198
199 std::mutex mTasksMutex;
200 std::queue<VirtioGpuTaskWithWaitable> mTasks;
201
202 enum class EmulatedResourceType {
203 kBlob,
204 kPipe,
205 };
206 struct EmulatedResource {
207 EmulatedResourceType type;
208
209 std::mutex pendingWaitablesMutex;
210 std::vector<std::shared_future<void>> pendingWaitables;
211
212 // For non-blob resources, the guest shadow memory.
213 std::unique_ptr<uint8_t[]> guestBytes;
214
215 // For mappable blob resources, the host memory once it is mapped.
216 std::shared_future<uint8_t*> mappedHostBytes;
217 };
218 std::mutex mResourcesMutex;
219 std::unordered_map<uint32_t, EmulatedResource> mResources;
220
CreateResource(uint32_t resourceId,EmulatedResourceType resourceType)221 EmulatedResource* CreateResource(uint32_t resourceId, EmulatedResourceType resourceType) {
222 std::lock_guard<std::mutex> lock(mResourcesMutex);
223
224 auto [it, created] = mResources.emplace(
225 std::piecewise_construct, std::forward_as_tuple(resourceId), std::forward_as_tuple());
226 if (!created) {
227 ALOGE("Created resource %" PRIu32 " twice?", resourceId);
228 }
229
230 EmulatedResource* resource = &it->second;
231 resource->type = resourceType;
232 return resource;
233 }
234
GetResource(uint32_t resourceId)235 EmulatedResource* GetResource(uint32_t resourceId) {
236 std::lock_guard<std::mutex> lock(mResourcesMutex);
237
238 auto it = mResources.find(resourceId);
239 if (it == mResources.end()) {
240 return nullptr;
241 }
242
243 return &it->second;
244 }
245
DeleteResource(uint32_t resourceId)246 void DeleteResource(uint32_t resourceId) {
247 std::lock_guard<std::mutex> lock(mResourcesMutex);
248 mResources.erase(resourceId);
249 }
250
251 struct EmulatedFence {
252 std::promise<void> signaler;
253 std::shared_future<void> waitable;
254 };
255 std::mutex mVirtioGpuFencesMutex;
256 std::unordered_map<uint32_t, EmulatedFence> mVirtioGpuFences;
257
258 std::thread mWorkerThread;
259 };
260
EmulatedVirtioGpuImpl()261 EmulatedVirtioGpu::EmulatedVirtioGpuImpl::EmulatedVirtioGpuImpl()
262 : mWorkerThread([this]() { RunVirtioGpuTaskProcessingLoop(); }) {}
263
~EmulatedVirtioGpuImpl()264 EmulatedVirtioGpu::EmulatedVirtioGpuImpl::~EmulatedVirtioGpuImpl() {
265 mShuttingDown = true;
266 mWorkerThread.join();
267
268 stream_renderer_teardown();
269 }
270
271 namespace {
272
WriteFenceTrampoline(void * cookie,struct stream_renderer_fence * fence)273 void WriteFenceTrampoline(void* cookie, struct stream_renderer_fence* fence) {
274 auto* gpu = reinterpret_cast<EmulatedVirtioGpu*>(cookie);
275 gpu->SignalEmulatedFence(fence->fence_id);
276 }
277
278 } // namespace
279
Init(bool withGl,bool withVk,bool withVkSnapshots,EmulatedVirtioGpu * parent)280 bool EmulatedVirtioGpu::EmulatedVirtioGpuImpl::Init(bool withGl, bool withVk, bool withVkSnapshots,
281 EmulatedVirtioGpu* parent) {
282 std::vector<stream_renderer_param> renderer_params{
283 stream_renderer_param{
284 .key = STREAM_RENDERER_PARAM_USER_DATA,
285 .value = static_cast<uint64_t>(reinterpret_cast<uintptr_t>(parent)),
286 },
287 stream_renderer_param{
288 .key = STREAM_RENDERER_PARAM_FENCE_CALLBACK,
289 .value = static_cast<uint64_t>(reinterpret_cast<uintptr_t>(&WriteFenceTrampoline)),
290 },
291 stream_renderer_param{
292 .key = STREAM_RENDERER_PARAM_RENDERER_FLAGS,
293 .value =
294 static_cast<uint64_t>(STREAM_RENDERER_FLAGS_USE_SURFACELESS_BIT) |
295 (withGl ? static_cast<uint64_t>(STREAM_RENDERER_FLAGS_USE_EGL_BIT |
296 STREAM_RENDERER_FLAGS_USE_GLES_BIT)
297 : 0) |
298 (withVk ? static_cast<uint64_t>(STREAM_RENDERER_FLAGS_USE_VK_BIT) : 0) |
299 (withVkSnapshots ? static_cast<uint64_t>(STREAM_RENDERER_FLAGS_VULKAN_SNAPSHOTS)
300 : 0),
301 },
302 stream_renderer_param{
303 .key = STREAM_RENDERER_PARAM_WIN0_WIDTH,
304 .value = 32,
305 },
306 stream_renderer_param{
307 .key = STREAM_RENDERER_PARAM_WIN0_HEIGHT,
308 .value = 32,
309 },
310 };
311 return stream_renderer_init(renderer_params.data(), renderer_params.size()) == 0;
312 }
313
GetCaps(VirtGpuCapset capset)314 VirtGpuCaps EmulatedVirtioGpu::EmulatedVirtioGpuImpl::GetCaps(VirtGpuCapset capset) {
315 VirtGpuCaps caps = {
316 .params =
317 {
318 [kParam3D] = 1,
319 [kParamCapsetFix] = 1,
320 [kParamResourceBlob] = 1,
321 [kParamHostVisible] = 1,
322 [kParamCrossDevice] = 0,
323 [kParamContextInit] = 1,
324 [kParamSupportedCapsetIds] = 0,
325 [kParamCreateGuestHandle] = 0,
326 },
327 };
328
329 stream_renderer_fill_caps(static_cast<uint32_t>(capset), 0, &caps.vulkanCapset);
330
331 return caps;
332 }
333
CreateContext()334 std::optional<uint32_t> EmulatedVirtioGpu::EmulatedVirtioGpuImpl::CreateContext() {
335 const uint32_t contextId = mNextContextId++;
336
337 VirtioGpuTaskCreateContext task = {
338 .contextId = contextId,
339 .contextInit = 0,
340 .contextName = "EmulatedVirtioGpu Context " + std::to_string(contextId),
341 };
342 EnqueueVirtioGpuTask(contextId, std::move(task));
343 return contextId;
344 }
345
DestroyContext(uint32_t contextId)346 void EmulatedVirtioGpu::EmulatedVirtioGpuImpl::DestroyContext(uint32_t contextId) {
347 VirtioGpuTaskDestroyContext task = {
348 .contextId = contextId,
349 };
350 EnqueueVirtioGpuTask(contextId, std::move(task));
351 }
352
Map(uint32_t resourceId)353 uint8_t* EmulatedVirtioGpu::EmulatedVirtioGpuImpl::Map(uint32_t resourceId) {
354 EmulatedResource* resource = GetResource(resourceId);
355 if (resource == nullptr) {
356 ALOGE("Failed to Map() resource %" PRIu32 ": not found.", resourceId);
357 return nullptr;
358 }
359
360 uint8_t* mapped = nullptr;
361 if (resource->type == EmulatedResourceType::kBlob) {
362 if (!resource->mappedHostBytes.valid()) {
363 ALOGE("Failed to Map() resource %" PRIu32
364 ": attempting to map blob "
365 "without mappable flag?",
366 resourceId);
367 return nullptr;
368 }
369 mapped = resource->mappedHostBytes.get();
370 } else if (resource->type == EmulatedResourceType::kPipe) {
371 mapped = resource->guestBytes.get();
372 }
373 return mapped;
374 }
375
Unmap(uint32_t resourceId)376 void EmulatedVirtioGpu::EmulatedVirtioGpuImpl::Unmap(uint32_t resourceId) {
377 stream_renderer_resource_unmap(resourceId);
378 }
379
Wait(uint32_t resourceId)380 int EmulatedVirtioGpu::EmulatedVirtioGpuImpl::Wait(uint32_t resourceId) {
381 EmulatedResource* resource = GetResource(resourceId);
382 if (resource == nullptr) {
383 ALOGE("Failed to Wait() on resource %" PRIu32 ": not found.", resourceId);
384 return -1;
385 }
386
387 std::vector<std::shared_future<void>> pendingWaitables;
388 {
389 std::lock_guard<std::mutex> lock(resource->pendingWaitablesMutex);
390 pendingWaitables = resource->pendingWaitables;
391 resource->pendingWaitables.clear();
392 }
393 for (auto& waitable : pendingWaitables) {
394 waitable.wait();
395 }
396
397 return 0;
398 }
399
TransferFromHost(uint32_t contextId,uint32_t resourceId,uint32_t transferOffset,uint32_t transferSize)400 int EmulatedVirtioGpu::EmulatedVirtioGpuImpl::TransferFromHost(uint32_t contextId,
401 uint32_t resourceId,
402 uint32_t transferOffset,
403 uint32_t transferSize) {
404 EmulatedResource* resource = GetResource(resourceId);
405 if (resource == nullptr) {
406 ALOGE("Failed to TransferFromHost() on resource %" PRIu32 ": not found.", resourceId);
407 return -1;
408 }
409
410 VirtioGpuTaskTransferFromHost task = {
411 .contextId = contextId,
412 .resourceId = resourceId,
413 .transferOffset = transferOffset,
414 .transferSize = transferSize,
415 };
416 auto waitable = EnqueueVirtioGpuTask(contextId, std::move(task));
417
418 {
419 std::lock_guard<std::mutex> lock(resource->pendingWaitablesMutex);
420 resource->pendingWaitables.push_back(std::move(waitable));
421 }
422
423 return 0;
424 }
425
TransferToHost(uint32_t contextId,uint32_t resourceId,uint32_t transferOffset,uint32_t transferSize)426 int EmulatedVirtioGpu::EmulatedVirtioGpuImpl::TransferToHost(uint32_t contextId,
427 uint32_t resourceId,
428 uint32_t transferOffset,
429 uint32_t transferSize) {
430 EmulatedResource* resource = GetResource(resourceId);
431 if (resource == nullptr) {
432 ALOGE("Failed to TransferFromHost() on resource %" PRIu32 ": not found.", resourceId);
433 return -1;
434 }
435
436 VirtioGpuTaskTransferToHost task = {
437 .contextId = contextId,
438 .resourceId = resourceId,
439 .transferOffset = transferOffset,
440 .transferSize = transferSize,
441 };
442 auto waitable = EnqueueVirtioGpuTask(contextId, std::move(task));
443
444 {
445 std::lock_guard<std::mutex> lock(resource->pendingWaitablesMutex);
446 resource->pendingWaitables.push_back(std::move(waitable));
447 }
448
449 return 0;
450 }
451
CreateBlob(uint32_t contextId,const struct VirtGpuCreateBlob & blobCreate)452 std::optional<uint32_t> EmulatedVirtioGpu::EmulatedVirtioGpuImpl::CreateBlob(
453 uint32_t contextId, const struct VirtGpuCreateBlob& blobCreate) {
454
455 const uint32_t resourceId = mNextVirtioGpuResourceId++;
456
457 ALOGV("Enquing task to create blob resource-id:%d size:%" PRIu64, resourceId, blobCreate.size);
458
459 EmulatedResource* resource = CreateResource(resourceId, EmulatedResourceType::kBlob);
460
461 VirtioGpuTaskCreateBlob createTask{
462 .contextId = contextId,
463 .resourceId = resourceId,
464 .params =
465 {
466 .blob_mem = static_cast<uint32_t>(blobCreate.blobMem),
467 .blob_flags = static_cast<uint32_t>(blobCreate.flags),
468 .blob_id = blobCreate.blobId,
469 .size = blobCreate.size,
470 },
471 };
472 auto createBlobCompletedWaitable = EnqueueVirtioGpuTask(contextId, std::move(createTask));
473 resource->pendingWaitables.push_back(std::move(createBlobCompletedWaitable));
474
475 if (blobCreate.flags & kBlobFlagMappable) {
476 std::promise<uint8_t*> mappedBytesPromise;
477 std::shared_future<uint8_t*> mappedBytesWaitable = mappedBytesPromise.get_future();
478
479 VirtioGpuTaskMap mapTask{
480 .resourceId = resourceId,
481 .resourceMappedPromise = std::move(mappedBytesPromise),
482 };
483 EnqueueVirtioGpuTask(contextId, std::move(mapTask));
484 resource->mappedHostBytes = std::move(mappedBytesWaitable);
485 }
486
487 VirtioGpuTaskContextAttachResource attachTask{
488 .contextId = contextId,
489 .resourceId = resourceId,
490 };
491 EnqueueVirtioGpuTask(contextId, std::move(attachTask));
492
493 return resourceId;
494 }
495
CreateVirglBlob(uint32_t contextId,uint32_t width,uint32_t height,uint32_t virglFormat)496 std::optional<uint32_t> EmulatedVirtioGpu::EmulatedVirtioGpuImpl::CreateVirglBlob(
497 uint32_t contextId, uint32_t width, uint32_t height, uint32_t virglFormat) {
498
499 const uint32_t resourceId = mNextVirtioGpuResourceId++;
500
501 EmulatedResource* resource = CreateResource(resourceId, EmulatedResourceType::kPipe);
502
503 uint32_t target = 0;
504 uint32_t bind = 0;
505 uint32_t bpp = 0;
506
507 switch (virglFormat) {
508 case VIRGL_FORMAT_R8G8B8A8_UNORM:
509 case VIRGL_FORMAT_B8G8R8A8_UNORM:
510 target = PIPE_TEXTURE_2D;
511 bind = VIRGL_BIND_RENDER_TARGET;
512 bpp = 4;
513 break;
514 case VIRGL_FORMAT_B5G6R5_UNORM:
515 target = PIPE_TEXTURE_2D;
516 bind = VIRGL_BIND_RENDER_TARGET;
517 bpp = 2;
518 break;
519 case VIRGL_FORMAT_R8G8B8_UNORM:
520 target = PIPE_TEXTURE_2D;
521 bind = VIRGL_BIND_RENDER_TARGET;
522 bpp = 3;
523 break;
524 case VIRGL_FORMAT_R8_UNORM:
525 target = PIPE_BUFFER;
526 bind = VIRGL_BIND_CUSTOM;
527 bpp = 1;
528 break;
529 default:
530 ALOGE("Unknown virgl format %u", virglFormat);
531 return {};
532 }
533
534 resource->guestBytes = std::make_unique<uint8_t[]>(width * height * bpp);
535
536 VirtioGpuTaskCreateResource task{
537 .contextId = contextId,
538 .resourceId = resourceId,
539 .resourceBytes = resource->guestBytes.get(),
540 .params =
541 {
542 .handle = resourceId,
543 .target = target,
544 .format = virglFormat,
545 .bind = bind,
546 .width = width,
547 .height = height,
548 .depth = 1,
549 .array_size = 1,
550 .last_level = 0,
551 .nr_samples = 0,
552 .flags = 0,
553 },
554 };
555 auto taskCompletedWaitable = EnqueueVirtioGpuTask(contextId, std::move(task));
556 resource->pendingWaitables.push_back(std::move(taskCompletedWaitable));
557
558 VirtioGpuTaskContextAttachResource attachTask{
559 .contextId = contextId,
560 .resourceId = resourceId,
561 };
562 EnqueueVirtioGpuTask(contextId, std::move(attachTask));
563
564 return resourceId;
565 }
566
DestroyResource(uint32_t contextId,uint32_t resourceId)567 void EmulatedVirtioGpu::EmulatedVirtioGpuImpl::DestroyResource(uint32_t contextId,
568 uint32_t resourceId) {
569 DeleteResource(resourceId);
570
571 VirtioGpuTaskUnrefResource unrefTask{
572 .resourceId = resourceId,
573 };
574 EnqueueVirtioGpuTask(contextId, std::move(unrefTask));
575
576 VirtioGpuTaskContextDetachResource detachTask{
577 .contextId = contextId,
578 .resourceId = resourceId,
579 };
580 EnqueueVirtioGpuTask(contextId, std::move(detachTask));
581 }
582
ExecBuffer(uint32_t contextId,struct VirtGpuExecBuffer & execbuffer,std::optional<uint32_t> blobResourceId)583 int EmulatedVirtioGpu::EmulatedVirtioGpuImpl::ExecBuffer(uint32_t contextId,
584 struct VirtGpuExecBuffer& execbuffer,
585 std::optional<uint32_t> blobResourceId) {
586 std::optional<uint32_t> fence;
587
588 if (execbuffer.flags & kFenceOut) {
589 fence = CreateEmulatedFence();
590 }
591
592 const VirtioGpuTaskExecBuffer task = {
593 .contextId = contextId,
594 .commandBuffer = std::vector<std::byte>(
595 reinterpret_cast<std::byte*>(execbuffer.command),
596 reinterpret_cast<std::byte*>(execbuffer.command) + execbuffer.command_size),
597 };
598 auto taskCompletedWaitable = EnqueueVirtioGpuTask(contextId, std::move(task), fence);
599
600 if (blobResourceId) {
601 EmulatedResource* resource = GetResource(*blobResourceId);
602 if (resource == nullptr) {
603 ALOGE("Failed to ExecBuffer() with resource %" PRIu32 ": not found.", *blobResourceId);
604 return -1;
605 }
606
607 {
608 std::lock_guard<std::mutex> lock(resource->pendingWaitablesMutex);
609 resource->pendingWaitables.push_back(std::move(taskCompletedWaitable));
610 }
611 }
612
613 if (execbuffer.flags & kFenceOut) {
614 execbuffer.handle.osHandle = *fence;
615 execbuffer.handle.type = kFenceHandleSyncFd;
616 }
617
618 return 0;
619 }
620
WaitOnEmulatedFence(int fenceAsFileDescriptor,int timeoutMilliseconds)621 int EmulatedVirtioGpu::EmulatedVirtioGpuImpl::WaitOnEmulatedFence(int fenceAsFileDescriptor,
622 int timeoutMilliseconds) {
623 uint32_t fenceId = static_cast<uint32_t>(fenceAsFileDescriptor);
624 ALOGV("Waiting on fence:%d", (int)fenceId);
625
626 std::shared_future<void> waitable;
627
628 {
629 std::lock_guard<std::mutex> lock(mVirtioGpuFencesMutex);
630
631 auto fenceIt = mVirtioGpuFences.find(fenceId);
632 if (fenceIt == mVirtioGpuFences.end()) {
633 ALOGE("Fence:%d already signaled", (int)fenceId);
634 return 0;
635 }
636 auto& fence = fenceIt->second;
637
638 waitable = fence.waitable;
639 }
640
641 auto status = waitable.wait_for(std::chrono::milliseconds(timeoutMilliseconds));
642 if (status == std::future_status::ready) {
643 ALOGV("Finished waiting for fence:%d", (int)fenceId);
644 return 0;
645 } else {
646 ALOGE("Timed out waiting for fence:%d", (int)fenceId);
647 return -1;
648 }
649 }
650
SignalEmulatedFence(uint32_t fenceId)651 void EmulatedVirtioGpu::EmulatedVirtioGpuImpl::SignalEmulatedFence(uint32_t fenceId) {
652 ALOGV("Signaling fence:%d", (int)fenceId);
653
654 std::lock_guard<std::mutex> lock(mVirtioGpuFencesMutex);
655
656 auto fenceIt = mVirtioGpuFences.find(fenceId);
657 if (fenceIt == mVirtioGpuFences.end()) {
658 ALOGE("Failed to find fence %" PRIu32, fenceId);
659 return;
660 }
661 auto& fenceInfo = fenceIt->second;
662 fenceInfo.signaler.set_value();
663 }
664
CreateEmulatedFence()665 uint32_t EmulatedVirtioGpu::EmulatedVirtioGpuImpl::CreateEmulatedFence() {
666 const uint32_t fenceId = mNextVirtioGpuFenceId++;
667
668 ALOGV("Creating fence:%d", (int)fenceId);
669
670 std::lock_guard<std::mutex> lock(mVirtioGpuFencesMutex);
671
672 auto [fenceIt, fenceCreated] = mVirtioGpuFences.emplace(fenceId, EmulatedFence{});
673 if (!fenceCreated) {
674 ALOGE("Attempting to recreate fence %" PRIu32, fenceId);
675 }
676
677 auto& fenceInfo = fenceIt->second;
678 fenceInfo.waitable = fenceInfo.signaler.get_future();
679
680 return fenceId;
681 }
682
EnqueueVirtioGpuTask(uint32_t contextId,VirtioGpuTask task,std::optional<uint32_t> fence)683 std::shared_future<void> EmulatedVirtioGpu::EmulatedVirtioGpuImpl::EnqueueVirtioGpuTask(
684 uint32_t contextId, VirtioGpuTask task, std::optional<uint32_t> fence) {
685 std::promise<void> taskCompletedSignaler;
686 std::shared_future<void> taskCompletedWaitable(taskCompletedSignaler.get_future());
687
688 std::lock_guard<std::mutex> lock(mTasksMutex);
689 mTasks.push(VirtioGpuTaskWithWaitable{
690 .contextId = contextId,
691 .task = std::move(task),
692 .taskCompletedSignaler = std::move(taskCompletedSignaler),
693 .fence = fence,
694 });
695
696 return taskCompletedWaitable;
697 }
698
DoTask(VirtioGpuTaskContextAttachResource task)699 void EmulatedVirtioGpu::EmulatedVirtioGpuImpl::DoTask(VirtioGpuTaskContextAttachResource task) {
700 ALOGV("Performing task to attach resource-id:%d to context-id:%d", task.resourceId,
701 task.contextId);
702
703 stream_renderer_ctx_attach_resource(task.contextId, task.resourceId);
704
705 ALOGV("Performing task to attach resource-id:%d to context-id:%d - done", task.resourceId,
706 task.contextId);
707 }
708
DoTask(VirtioGpuTaskContextDetachResource task)709 void EmulatedVirtioGpu::EmulatedVirtioGpuImpl::DoTask(VirtioGpuTaskContextDetachResource task) {
710 ALOGV("Performing task to detach resource-id:%d to context-id:%d", task.resourceId,
711 task.contextId);
712
713 stream_renderer_ctx_detach_resource(task.contextId, task.resourceId);
714
715 ALOGV("Performing task to detach resource-id:%d to context-id:%d - done", task.resourceId,
716 task.contextId);
717 }
718
DoTask(VirtioGpuTaskCreateBlob task)719 void EmulatedVirtioGpu::EmulatedVirtioGpuImpl::DoTask(VirtioGpuTaskCreateBlob task) {
720 ALOGV("Performing task to create blob resource-id:%d", task.resourceId);
721
722 int ret = stream_renderer_create_blob(task.contextId, task.resourceId, &task.params,
723 /*iovecs=*/nullptr,
724 /*num_iovs=*/0,
725 /*handle=*/nullptr);
726 if (ret) {
727 ALOGE("Failed to create blob.");
728 }
729
730 ALOGV("Performing task to create blob resource-id:%d - done", task.resourceId);
731 }
732
DoTask(VirtioGpuTaskCreateContext task)733 void EmulatedVirtioGpu::EmulatedVirtioGpuImpl::DoTask(VirtioGpuTaskCreateContext task) {
734 ALOGV("Performing task to create context-id:%" PRIu32 " context-init:%" PRIu32
735 " context-name:%s",
736 task.contextId, task.contextInit, task.contextName.c_str());
737
738 int ret = stream_renderer_context_create(task.contextId, task.contextName.size(),
739 task.contextName.data(), task.contextInit);
740 if (ret) {
741 ALOGE("Failed to create context-id:%" PRIu32 ".", task.contextId);
742 return;
743 }
744
745 ALOGV("Performing task to create context-id:%" PRIu32 " context-init:%" PRIu32
746 " context-name:%s - done",
747 task.contextId, task.contextInit, task.contextName.c_str());
748 }
749
DoTask(VirtioGpuTaskCreateResource task)750 void EmulatedVirtioGpu::EmulatedVirtioGpuImpl::DoTask(VirtioGpuTaskCreateResource task) {
751 ALOGV("Performing task to create resource resource:%d", task.resourceId);
752
753 int ret = stream_renderer_resource_create(&task.params, nullptr, 0);
754 if (ret) {
755 ALOGE("Failed to create resource:%d", task.resourceId);
756 }
757
758 struct iovec iov = {
759 .iov_base = task.resourceBytes,
760 .iov_len = task.params.width,
761 };
762 ret = stream_renderer_resource_attach_iov(task.resourceId, &iov, 1);
763 if (ret) {
764 ALOGE("Failed to attach iov to resource:%d", task.resourceId);
765 }
766
767 ALOGV("Performing task to create resource resource:%d - done", task.resourceId);
768
769 stream_renderer_ctx_attach_resource(task.contextId, task.resourceId);
770 }
771
DoTask(VirtioGpuTaskDestroyContext task)772 void EmulatedVirtioGpu::EmulatedVirtioGpuImpl::DoTask(VirtioGpuTaskDestroyContext task) {
773 ALOGV("Performing task to destroy context-id:%" PRIu32, task.contextId);
774
775 stream_renderer_context_destroy(task.contextId);
776
777 ALOGV("Performing task to destroy context-id:%" PRIu32 " - done", task.contextId);
778 }
779
DoTask(VirtioGpuTaskMap task)780 void EmulatedVirtioGpu::EmulatedVirtioGpuImpl::DoTask(VirtioGpuTaskMap task) {
781 ALOGV("Performing task to map resource resource:%d", task.resourceId);
782
783 void* mapped = nullptr;
784
785 int ret = stream_renderer_resource_map(task.resourceId, &mapped, nullptr);
786 if (ret) {
787 ALOGE("Failed to map resource:%d", task.resourceId);
788 return;
789 }
790
791 task.resourceMappedPromise.set_value(reinterpret_cast<uint8_t*>(mapped));
792 ALOGV("Performing task to map resource resource:%d - done", task.resourceId);
793 }
794
DoTask(VirtioGpuTaskExecBuffer task)795 void EmulatedVirtioGpu::EmulatedVirtioGpuImpl::DoTask(VirtioGpuTaskExecBuffer task) {
796 ALOGV("Performing task to execbuffer");
797
798 if (task.commandBuffer.size() % 4 != 0) {
799 ALOGE("Unaligned command buffer?");
800 return;
801 }
802
803 stream_renderer_command cmd = {
804 .ctx_id = task.contextId,
805 .cmd_size = static_cast<uint32_t>(task.commandBuffer.size()),
806 .cmd = reinterpret_cast<uint8_t*>(task.commandBuffer.data()),
807 .num_in_fences = 0,
808 .fences = nullptr,
809 };
810
811 int ret = stream_renderer_submit_cmd(&cmd);
812 if (ret) {
813 ALOGE("Failed to execbuffer.");
814 }
815
816 ALOGV("Performing task to execbuffer - done");
817 }
818
DoTask(VirtioGpuTaskTransferFromHost task)819 void EmulatedVirtioGpu::EmulatedVirtioGpuImpl::DoTask(VirtioGpuTaskTransferFromHost task) {
820 struct stream_renderer_box transferBox = {
821 .x = task.transferOffset,
822 .y = 0,
823 .z = 0,
824 .w = task.transferSize,
825 .h = 1,
826 .d = 1,
827 };
828
829 int ret = stream_renderer_transfer_read_iov(task.resourceId, task.contextId,
830 /*level=*/0,
831 /*stride=*/0,
832 /*layer_stride=*/0, &transferBox,
833 /*offset=*/0,
834 /*iov=*/nullptr,
835 /*iovec_cnt=*/0);
836 if (ret) {
837 ALOGE("Failed to transferFromHost() for resource:%" PRIu32, task.resourceId);
838 }
839 }
840
DoTask(VirtioGpuTaskTransferToHost task)841 void EmulatedVirtioGpu::EmulatedVirtioGpuImpl::DoTask(VirtioGpuTaskTransferToHost task) {
842 struct stream_renderer_box transferBox = {
843 .x = task.transferOffset,
844 .y = 0,
845 .z = 0,
846 .w = task.transferSize,
847 .h = 1,
848 .d = 1,
849 };
850
851 int ret = stream_renderer_transfer_write_iov(task.resourceId, task.contextId,
852 /*level=*/0,
853 /*stride=*/0,
854 /*layer_stride=*/0, &transferBox,
855 /*offset=*/0,
856 /*iov=*/nullptr,
857 /*iovec_cnt=*/0);
858 if (ret) {
859 ALOGE("Failed to transferToHost() for resource:%" PRIu32, task.resourceId);
860 }
861 }
862
DoTask(VirtioGpuTaskUnrefResource task)863 void EmulatedVirtioGpu::EmulatedVirtioGpuImpl::DoTask(VirtioGpuTaskUnrefResource task) {
864 stream_renderer_resource_unref(task.resourceId);
865 }
866
DoTask(VirtioGpuTaskWithWaitable task)867 void EmulatedVirtioGpu::EmulatedVirtioGpuImpl::DoTask(VirtioGpuTaskWithWaitable task) {
868 std::visit(
869 [this](auto&& work) {
870 using T = std::decay_t<decltype(work)>;
871 if constexpr (std::is_same_v<T, VirtioGpuTaskContextAttachResource>) {
872 DoTask(std::move(work));
873 } else if constexpr (std::is_same_v<T, VirtioGpuTaskContextDetachResource>) {
874 DoTask(std::move(work));
875 } else if constexpr (std::is_same_v<T, VirtioGpuTaskCreateBlob>) {
876 DoTask(std::move(work));
877 } else if constexpr (std::is_same_v<T, VirtioGpuTaskCreateContext>) {
878 DoTask(std::move(work));
879 } else if constexpr (std::is_same_v<T, VirtioGpuTaskCreateResource>) {
880 DoTask(std::move(work));
881 } else if constexpr (std::is_same_v<T, VirtioGpuTaskDestroyContext>) {
882 DoTask(std::move(work));
883 } else if constexpr (std::is_same_v<T, VirtioGpuTaskMap>) {
884 DoTask(std::move(work));
885 } else if constexpr (std::is_same_v<T, VirtioGpuTaskExecBuffer>) {
886 DoTask(std::move(work));
887 } else if constexpr (std::is_same_v<T, VirtioGpuTaskTransferFromHost>) {
888 DoTask(std::move(work));
889 } else if constexpr (std::is_same_v<T, VirtioGpuTaskTransferToHost>) {
890 DoTask(std::move(work));
891 } else if constexpr (std::is_same_v<T, VirtioGpuTaskUnrefResource>) {
892 DoTask(std::move(work));
893 }
894 },
895 task.task);
896
897 if (task.fence) {
898 const stream_renderer_fence fenceInfo = {
899 .flags = STREAM_RENDERER_FLAG_FENCE_RING_IDX,
900 .fence_id = *task.fence,
901 .ctx_id = task.contextId,
902 .ring_idx = 0,
903 };
904 int ret = stream_renderer_create_fence(&fenceInfo);
905 if (ret) {
906 ALOGE("Failed to create fence.");
907 }
908 }
909
910 task.taskCompletedSignaler.set_value();
911 }
912
RunVirtioGpuTaskProcessingLoop()913 void EmulatedVirtioGpu::EmulatedVirtioGpuImpl::RunVirtioGpuTaskProcessingLoop() {
914 while (!mShuttingDown.load()) {
915 std::optional<VirtioGpuTaskWithWaitable> task;
916
917 {
918 std::lock_guard<std::mutex> lock(mTasksMutex);
919 if (!mTasks.empty()) {
920 task = std::move(mTasks.front());
921 mTasks.pop();
922 }
923 }
924
925 if (task) {
926 DoTask(std::move(*task));
927 }
928 }
929 }
930
931 namespace {
932
933 EmulatedVirtioGpu* sInstance = nullptr;
934
935 } // namespace
936
EmulatedVirtioGpu()937 EmulatedVirtioGpu::EmulatedVirtioGpu()
938 : mImpl{std::make_unique<EmulatedVirtioGpu::EmulatedVirtioGpuImpl>()} {}
939
940 /*static*/
Get()941 EmulatedVirtioGpu& EmulatedVirtioGpu::Get() {
942 if (sInstance == nullptr) {
943 sInstance = new EmulatedVirtioGpu();
944
945 bool withGl = false;
946 bool withVk = true;
947 bool withVkSnapshots = false;
948
949 struct Option {
950 std::string env;
951 bool* val;
952 };
953 const std::vector<Option> options = {
954 {"GFXSTREAM_EMULATED_VIRTIO_GPU_WITH_GL", &withGl},
955 {"GFXSTREAM_EMULATED_VIRTIO_GPU_WITH_VK", &withVk},
956 {"GFXSTREAM_EMULATED_VIRTIO_GPU_WITH_VK_SNAPSHOTS", &withVkSnapshots},
957 };
958 for (const Option option : options) {
959 const char* val = std::getenv(option.env.c_str());
960 if (val != nullptr && (val[0] == 'Y' || val[0] == 'y')) {
961 *option.val = true;
962 }
963 }
964
965 ALOGE("Initializing withGl:%d withVk:%d withVkSnapshots:%d", withGl, withVk,
966 withVkSnapshots);
967 if (!sInstance->Init(withGl, withVk, withVkSnapshots)) {
968 ALOGE("Failed to initialize EmulatedVirtioGpu.");
969 }
970 }
971 return *sInstance;
972 }
973
974 /*static*/
Reset()975 void EmulatedVirtioGpu::Reset() {
976 if (sInstance != nullptr) {
977 delete sInstance;
978 sInstance = nullptr;
979 }
980 }
981
Init(bool withGl,bool withVk,bool withVkSnapshots)982 bool EmulatedVirtioGpu::Init(bool withGl, bool withVk, bool withVkSnapshots) {
983 return mImpl->Init(withGl, withVk, withVkSnapshots, this);
984 }
985
CreateContext()986 std::optional<uint32_t> EmulatedVirtioGpu::CreateContext() { return mImpl->CreateContext(); }
987
DestroyContext(uint32_t contextId)988 void EmulatedVirtioGpu::DestroyContext(uint32_t contextId) { mImpl->DestroyContext(contextId); }
989
GetCaps(VirtGpuCapset capset)990 VirtGpuCaps EmulatedVirtioGpu::GetCaps(VirtGpuCapset capset) { return mImpl->GetCaps(capset); }
991
Map(uint32_t resourceId)992 uint8_t* EmulatedVirtioGpu::Map(uint32_t resourceId) { return mImpl->Map(resourceId); }
993
Unmap(uint32_t resourceId)994 void EmulatedVirtioGpu::Unmap(uint32_t resourceId) { mImpl->Unmap(resourceId); }
995
ExecBuffer(uint32_t contextId,struct VirtGpuExecBuffer & execbuffer,std::optional<uint32_t> blobResourceId)996 int EmulatedVirtioGpu::ExecBuffer(uint32_t contextId, struct VirtGpuExecBuffer& execbuffer,
997 std::optional<uint32_t> blobResourceId) {
998 return mImpl->ExecBuffer(contextId, execbuffer, blobResourceId);
999 }
1000
Wait(uint32_t resourceId)1001 int EmulatedVirtioGpu::Wait(uint32_t resourceId) { return mImpl->Wait(resourceId); }
1002
TransferFromHost(uint32_t contextId,uint32_t resourceId,uint32_t offset,uint32_t size)1003 int EmulatedVirtioGpu::TransferFromHost(uint32_t contextId, uint32_t resourceId, uint32_t offset,
1004 uint32_t size) {
1005 return mImpl->TransferFromHost(contextId, resourceId, offset, size);
1006 }
1007
TransferToHost(uint32_t contextId,uint32_t resourceId,uint32_t offset,uint32_t size)1008 int EmulatedVirtioGpu::TransferToHost(uint32_t contextId, uint32_t resourceId, uint32_t offset,
1009 uint32_t size) {
1010 return mImpl->TransferToHost(contextId, resourceId, offset, size);
1011 }
1012
CreateBlob(uint32_t contextId,const struct VirtGpuCreateBlob & params)1013 std::optional<uint32_t> EmulatedVirtioGpu::CreateBlob(uint32_t contextId,
1014 const struct VirtGpuCreateBlob& params) {
1015 return mImpl->CreateBlob(contextId, params);
1016 }
1017
CreateVirglBlob(uint32_t contextId,uint32_t width,uint32_t height,uint32_t virglFormat)1018 std::optional<uint32_t> EmulatedVirtioGpu::CreateVirglBlob(uint32_t contextId, uint32_t width,
1019 uint32_t height, uint32_t virglFormat) {
1020 return mImpl->CreateVirglBlob(contextId, width, height, virglFormat);
1021 }
1022
DestroyResource(uint32_t contextId,uint32_t resourceId)1023 void EmulatedVirtioGpu::DestroyResource(uint32_t contextId, uint32_t resourceId) {
1024 mImpl->DestroyResource(contextId, resourceId);
1025 }
1026
WaitOnEmulatedFence(int fenceAsFileDescriptor,int timeoutMilliseconds)1027 int EmulatedVirtioGpu::WaitOnEmulatedFence(int fenceAsFileDescriptor, int timeoutMilliseconds) {
1028 return mImpl->WaitOnEmulatedFence(fenceAsFileDescriptor, timeoutMilliseconds);
1029 }
1030
SignalEmulatedFence(int fenceId)1031 void EmulatedVirtioGpu::SignalEmulatedFence(int fenceId) { mImpl->SignalEmulatedFence(fenceId); }
1032
ResetEmulatedVirtioGpu()1033 void ResetEmulatedVirtioGpu() { EmulatedVirtioGpu::Reset(); }
1034
1035 } // namespace gfxstream
1036