1 // Copyright (C) 2023 The Android Open Source Project
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #pragma once
16 
17 #include <gmock/gmock.h>
18 #include <gtest/gtest.h>
19 
20 #include <inttypes.h>
21 
22 #include <future>
23 #include <memory>
24 #include <optional>
25 #include <string>
26 #include <thread>
27 #include <unordered_set>
28 #include <variant>
29 
30 // clang-format off
31 #include <EGL/egl.h>
32 #include <EGL/eglext.h>
33 #include "OpenGLESDispatch/gldefs.h"
34 #include "OpenGLESDispatch/gles_functions.h"
35 #include "OpenGLESDispatch/RenderEGL_functions.h"
36 #include "OpenGLESDispatch/RenderEGL_extensions_functions.h"
37 
38 #define VULKAN_HPP_NAMESPACE vkhpp
39 #define VULKAN_HPP_DISPATCH_LOADER_DYNAMIC 1
40 #define VULKAN_HPP_ENABLE_DYNAMIC_LOADER_TOOL 1
41 #define VULKAN_HPP_NO_CONSTRUCTORS
42 #define VULKAN_HPP_NO_EXCEPTIONS
43 #include <vulkan/vulkan.hpp>
44 #include <vulkan/vk_android_native_buffer.h>
45 // clang-format on
46 
47 #include "KumquatInstance.h"
48 #include "Sync.h"
49 #include "drm_fourcc.h"
50 #include "gfxstream/Expected.h"
51 #include "gfxstream/guest/ANativeWindow.h"
52 #include "gfxstream/guest/GfxStreamGralloc.h"
53 #include "gfxstream/guest/RenderControlApi.h"
54 
55 namespace gfxstream {
56 namespace tests {
57 
58 constexpr const bool kSaveImagesIfComparisonFailed = false;
59 
60 MATCHER(IsOk, "an ok result") {
61   auto& result = arg;
62   if (!result.ok()) {
63     *result_listener << "which is an error with message: \""
64                      << result.error()
65                      << "\"";
66     return false;
67   }
68   return true;
69 }
70 
71 MATCHER(IsError, "an error result") {
72     auto& result = arg;
73     if (result.ok()) {
74         *result_listener << "which is an ok result";
75         return false;
76     }
77     return true;
78 }
79 
80 MATCHER(IsVkSuccess, "is VK_SUCCESS") {
81     auto& result = arg;
82     if (result != vkhpp::Result::eSuccess) {
83         *result_listener << "which is " << vkhpp::to_string(result);
84         return false;
85     }
86     return true;
87 }
88 
89 MATCHER(IsValidHandle, "a non-null handle") {
90     auto& result = arg;
91     if (!result) {
92         *result_listener << "which is a VK_NULL_HANDLE";
93         return false;
94     }
95     return true;
96 }
97 
98 struct Ok {};
99 
100 template <typename T>
101 using Result = gfxstream::expected<T, std::string>;
102 
103 #define GFXSTREAM_ASSERT(x)                                           \
104     ({                                                                \
105         auto gfxstream_expected = (x);                                \
106         if (!gfxstream_expected.ok()) {                               \
107             ASSERT_THAT(gfxstream_expected.ok(), ::testing::IsTrue()) \
108                 << "Assertion failed at line " << __LINE__            \
109                 << ": error was: " << gfxstream_expected.error();     \
110         }                                                             \
111         std::move(gfxstream_expected.value());                        \
112     })
113 
114 #define GFXSTREAM_ASSERT_VKHPP_RV(x)                                        \
115     ({                                                                      \
116         auto vkhpp_result_value = (x);                                      \
117         ASSERT_THAT(vkhpp_result_value.result, IsVkSuccess())               \
118             << "Assertion failed at line " << __LINE__ << ": VkResult was " \
119             << to_string(vkhpp_result_value.result);                        \
120         std::move(vkhpp_result_value.value);                                \
121     })
122 
123 #define GFXSTREAM_EXPECT_VKHPP_RESULT(x)                                                           \
124     ({                                                                                             \
125         auto vkhpp_result = (x);                                                                   \
126         if (vkhpp_result != vkhpp::Result::eSuccess) {                                             \
127             return gfxstream::unexpected("Found " + vkhpp::to_string(vkhpp_result) + " at line " + \
128                                          std::to_string(__LINE__));                                \
129         }                                                                                          \
130     })
131 
132 #define GFXSTREAM_EXPECT_VKHPP_RV(x)                                                              \
133     ({                                                                                            \
134         auto vkhpp_result_value = (x);                                                            \
135         if (vkhpp_result_value.result != vkhpp::Result::eSuccess) {                               \
136             return gfxstream::unexpected("Found " + vkhpp::to_string(vkhpp_result_value.result) + \
137                                          " at line " + std::to_string(__LINE__));                 \
138         }                                                                                         \
139         std::move(vkhpp_result_value.value);                                                      \
140     })
141 
142 struct GuestGlDispatchTable {
143 #define DECLARE_EGL_FUNCTION(return_type, function_name, signature) \
144     return_type(*function_name) signature = nullptr;
145 
146 #define DECLARE_GLES_FUNCTION(return_type, function_name, signature, args) \
147     return_type(*function_name) signature = nullptr;
148 
149     LIST_RENDER_EGL_FUNCTIONS(DECLARE_EGL_FUNCTION)
150     LIST_RENDER_EGL_EXTENSIONS_FUNCTIONS(DECLARE_EGL_FUNCTION)
151     LIST_GLES_FUNCTIONS(DECLARE_GLES_FUNCTION, DECLARE_GLES_FUNCTION)
152 };
153 
154 struct GuestRenderControlDispatchTable {
155     PFN_rcCreateDevice rcCreateDevice = nullptr;
156     PFN_rcDestroyDevice rcDestroyDevice = nullptr;
157     PFN_rcCompose rcCompose = nullptr;
158 };
159 
160 class ScopedRenderControlDevice {
161    public:
ScopedRenderControlDevice()162     ScopedRenderControlDevice() {}
163 
ScopedRenderControlDevice(GuestRenderControlDispatchTable & dispatch)164     ScopedRenderControlDevice(GuestRenderControlDispatchTable& dispatch) : mDispatch(&dispatch) {
165         mDevice = dispatch.rcCreateDevice();
166     }
167 
168     ScopedRenderControlDevice(const ScopedRenderControlDevice& rhs) = delete;
169     ScopedRenderControlDevice& operator=(const ScopedRenderControlDevice& rhs) = delete;
170 
ScopedRenderControlDevice(ScopedRenderControlDevice && rhs)171     ScopedRenderControlDevice(ScopedRenderControlDevice&& rhs)
172         : mDispatch(rhs.mDispatch), mDevice(rhs.mDevice) {
173         rhs.mDevice = nullptr;
174     }
175 
176     ScopedRenderControlDevice& operator=(ScopedRenderControlDevice&& rhs) {
177         mDispatch = rhs.mDispatch;
178         std::swap(mDevice, rhs.mDevice);
179         return *this;
180     }
181 
~ScopedRenderControlDevice()182     ~ScopedRenderControlDevice() {
183         if (mDevice != nullptr) {
184             mDispatch->rcDestroyDevice(mDevice);
185             mDevice = nullptr;
186         }
187     }
188 
189     operator RenderControlDevice*() { return mDevice; }
190     operator RenderControlDevice*() const { return mDevice; }
191 
192    private:
193     GuestRenderControlDispatchTable* mDispatch = nullptr;
194     RenderControlDevice* mDevice = nullptr;
195 };
196 
197 class ScopedGlType {
198    public:
199     using GlDispatch = GuestGlDispatchTable;
200     using GlDispatchGenFunc = void (*GuestGlDispatchTable::*)(GLsizei, GLuint*);
201     using GlDispatchDelFunc = void (*GuestGlDispatchTable::*)(GLsizei, const GLuint*);
202 
ScopedGlType()203     ScopedGlType() {}
204 
ScopedGlType(GlDispatch & glDispatch,GlDispatchGenFunc glGenFunc,GlDispatchDelFunc glDelFunc)205     ScopedGlType(GlDispatch& glDispatch, GlDispatchGenFunc glGenFunc, GlDispatchDelFunc glDelFunc)
206         : mGlDispatch(&glDispatch), mGlGenFunc(glGenFunc), mGlDelFunc(glDelFunc) {
207         (mGlDispatch->*mGlGenFunc)(1, &mHandle);
208     }
209 
210     ScopedGlType(const ScopedGlType& rhs) = delete;
211     ScopedGlType& operator=(const ScopedGlType& rhs) = delete;
212 
ScopedGlType(ScopedGlType && rhs)213     ScopedGlType(ScopedGlType&& rhs)
214         : mGlDispatch(rhs.mGlDispatch),
215           mGlGenFunc(rhs.mGlGenFunc),
216           mGlDelFunc(rhs.mGlDelFunc),
217           mHandle(rhs.mHandle) {
218         rhs.mHandle = 0;
219     }
220 
221     ScopedGlType& operator=(ScopedGlType&& rhs) {
222         mGlDispatch = rhs.mGlDispatch;
223         mGlGenFunc = rhs.mGlGenFunc;
224         mGlDelFunc = rhs.mGlDelFunc;
225         std::swap(mHandle, rhs.mHandle);
226         return *this;
227     }
228 
~ScopedGlType()229     ~ScopedGlType() { Reset(); }
230 
GLuint()231     operator GLuint() { return mHandle; }
GLuint()232     operator GLuint() const { return mHandle; }
233 
Reset()234     void Reset() {
235         if (mHandle != 0) {
236             (mGlDispatch->*mGlDelFunc)(1, &mHandle);
237             mHandle = 0;
238         }
239     }
240 
241    private:
242     GlDispatch* mGlDispatch = nullptr;
243     GlDispatchGenFunc mGlGenFunc = nullptr;
244     GlDispatchDelFunc mGlDelFunc = nullptr;
245     GLuint mHandle = 0;
246 };
247 
248 class ScopedGlBuffer : public ScopedGlType {
249    public:
ScopedGlBuffer(GlDispatch & dispatch)250     ScopedGlBuffer(GlDispatch& dispatch)
251         : ScopedGlType(dispatch, &GlDispatch::glGenBuffers, &GlDispatch::glDeleteBuffers) {}
252 };
253 
254 class ScopedGlTexture : public ScopedGlType {
255    public:
ScopedGlTexture(GlDispatch & dispatch)256     ScopedGlTexture(GlDispatch& dispatch)
257         : ScopedGlType(dispatch, &GlDispatch::glGenTextures, &GlDispatch::glDeleteTextures) {}
258 };
259 
260 class ScopedGlFramebuffer : public ScopedGlType {
261    public:
ScopedGlFramebuffer(GlDispatch & dispatch)262     ScopedGlFramebuffer(GlDispatch& dispatch)
263         : ScopedGlType(dispatch, &GlDispatch::glGenFramebuffers,
264                        &GlDispatch::glDeleteFramebuffers) {}
265 };
266 
267 class ScopedGlShader {
268    public:
269     using GlDispatch = GuestGlDispatchTable;
270 
271     ScopedGlShader() = default;
272 
273     ScopedGlShader(const ScopedGlShader& rhs) = delete;
274     ScopedGlShader& operator=(const ScopedGlShader& rhs) = delete;
275 
276     static Result<ScopedGlShader> MakeShader(GlDispatch& dispatch, GLenum type,
277                                              const std::string& source);
278 
ScopedGlShader(ScopedGlShader && rhs)279     ScopedGlShader(ScopedGlShader&& rhs) : mGlDispatch(rhs.mGlDispatch), mHandle(rhs.mHandle) {
280         rhs.mHandle = 0;
281     }
282 
283     ScopedGlShader& operator=(ScopedGlShader&& rhs) {
284         mGlDispatch = rhs.mGlDispatch;
285         std::swap(mHandle, rhs.mHandle);
286         return *this;
287     }
288 
~ScopedGlShader()289     ~ScopedGlShader() {
290         if (mHandle != 0) {
291             mGlDispatch->glDeleteShader(mHandle);
292             mHandle = 0;
293         }
294     }
295 
GLuint()296     operator GLuint() { return mHandle; }
GLuint()297     operator GLuint() const { return mHandle; }
298 
299    private:
ScopedGlShader(GlDispatch & dispatch,GLuint handle)300     ScopedGlShader(GlDispatch& dispatch, GLuint handle) : mGlDispatch(&dispatch), mHandle(handle) {}
301 
302     GlDispatch* mGlDispatch = nullptr;
303     GLuint mHandle = 0;
304 };
305 
306 class ScopedGlProgram {
307    public:
308     using GlDispatch = GuestGlDispatchTable;
309 
310     ScopedGlProgram() = default;
311 
312     ScopedGlProgram(const ScopedGlProgram& rhs) = delete;
313     ScopedGlProgram& operator=(const ScopedGlProgram& rhs) = delete;
314 
315     static Result<ScopedGlProgram> MakeProgram(GlDispatch& dispatch, const std::string& vertShader,
316                                                const std::string& fragShader);
317 
318     static Result<ScopedGlProgram> MakeProgram(GlDispatch& dispatch, GLenum programBinaryFormat,
319                                                const std::vector<uint8_t>& programBinaryData);
320 
ScopedGlProgram(ScopedGlProgram && rhs)321     ScopedGlProgram(ScopedGlProgram&& rhs) : mGlDispatch(rhs.mGlDispatch), mHandle(rhs.mHandle) {
322         rhs.mHandle = 0;
323     }
324 
325     ScopedGlProgram& operator=(ScopedGlProgram&& rhs) {
326         mGlDispatch = rhs.mGlDispatch;
327         std::swap(mHandle, rhs.mHandle);
328         return *this;
329     }
330 
~ScopedGlProgram()331     ~ScopedGlProgram() {
332         if (mHandle != 0) {
333             mGlDispatch->glDeleteProgram(mHandle);
334             mHandle = 0;
335         }
336     }
337 
GLuint()338     operator GLuint() { return mHandle; }
GLuint()339     operator GLuint() const { return mHandle; }
340 
341    private:
ScopedGlProgram(GlDispatch & dispatch,GLuint handle)342     ScopedGlProgram(GlDispatch& dispatch, GLuint handle)
343         : mGlDispatch(&dispatch), mHandle(handle) {}
344 
345     GlDispatch* mGlDispatch = nullptr;
346     GLuint mHandle = 0;
347 };
348 
349 class ScopedAHardwareBuffer {
350    public:
351     ScopedAHardwareBuffer() = default;
352 
353     static Result<ScopedAHardwareBuffer> Allocate(Gralloc& gralloc, uint32_t width, uint32_t height,
354                                                   uint32_t format);
355 
356     ScopedAHardwareBuffer(const ScopedAHardwareBuffer& rhs) = delete;
357     ScopedAHardwareBuffer& operator=(const ScopedAHardwareBuffer& rhs) = delete;
358 
ScopedAHardwareBuffer(ScopedAHardwareBuffer && rhs)359     ScopedAHardwareBuffer(ScopedAHardwareBuffer&& rhs)
360         : mGralloc(rhs.mGralloc), mHandle(rhs.mHandle) {
361         rhs.mHandle = nullptr;
362     }
363 
364     ScopedAHardwareBuffer& operator=(ScopedAHardwareBuffer&& rhs) {
365         std::swap(mGralloc, rhs.mGralloc);
366         std::swap(mHandle, rhs.mHandle);
367         return *this;
368     }
369 
~ScopedAHardwareBuffer()370     ~ScopedAHardwareBuffer() {
371         if (mHandle != nullptr) {
372             mGralloc->release(mHandle);
373             mHandle = 0;
374         }
375     }
376 
GetWidth()377     uint32_t GetWidth() const { return mGralloc->getWidth(mHandle); }
378 
GetHeight()379     uint32_t GetHeight() const { return mGralloc->getHeight(mHandle); }
380 
GetAHBFormat()381     uint32_t GetAHBFormat() const { return mGralloc->getFormat(mHandle); }
382 
GetDrmFormat()383     uint32_t GetDrmFormat() const { return mGralloc->getFormatDrmFourcc(mHandle); }
384 
Lock()385     Result<uint8_t*> Lock() {
386         uint8_t* mapped = nullptr;
387         int status = mGralloc->lock(mHandle, &mapped);
388         if (status != 0) {
389             return gfxstream::unexpected("Failed to lock AHB");
390         }
391         return mapped;
392     }
393 
LockPlanes()394     Result<std::vector<Gralloc::LockedPlane>> LockPlanes() {
395         std::vector<Gralloc::LockedPlane> planes;
396         int status = mGralloc->lockPlanes(mHandle, &planes);
397         if (status != 0) {
398             return gfxstream::unexpected("Failed to lock AHB");
399         }
400         return planes;
401     }
402 
Unlock()403     void Unlock() { mGralloc->unlock(mHandle); }
404 
405     operator AHardwareBuffer*() { return mHandle; }
406     operator AHardwareBuffer*() const { return mHandle; }
407 
408    private:
ScopedAHardwareBuffer(Gralloc & gralloc,AHardwareBuffer * handle)409     ScopedAHardwareBuffer(Gralloc& gralloc, AHardwareBuffer* handle)
410         : mGralloc(&gralloc), mHandle(handle) {}
411 
412     Gralloc* mGralloc = nullptr;
413     AHardwareBuffer* mHandle = nullptr;
414 };
415 
416 struct PixelR8G8B8A8 {
417     PixelR8G8B8A8() = default;
418 
PixelR8G8B8A8PixelR8G8B8A8419     PixelR8G8B8A8(uint8_t rr, uint8_t gg, uint8_t bb, uint8_t aa) : r(rr), g(gg), b(bb), a(aa) {}
420 
PixelR8G8B8A8PixelR8G8B8A8421     PixelR8G8B8A8(int xx, int yy, uint8_t rr, uint8_t gg, uint8_t bb, uint8_t aa)
422         : x(xx), y(yy), r(rr), g(gg), b(bb), a(aa) {}
423 
PixelR8G8B8A8PixelR8G8B8A8424     PixelR8G8B8A8(int xx, int yy, uint32_t rgba) : x(xx), y(yy) {
425         const uint8_t* parts = reinterpret_cast<const uint8_t*>(&rgba);
426         r = parts[0];
427         g = parts[1];
428         b = parts[2];
429         a = parts[3];
430     }
431 
432     std::optional<int> x;
433     std::optional<int> y;
434 
435     uint8_t r = 0;
436     uint8_t g = 0;
437     uint8_t b = 0;
438     uint8_t a = 0;
439 
ToStringPixelR8G8B8A8440     std::string ToString() const {
441         std::string ret = std::string("Pixel");
442         if (x) {
443             ret += std::string(" x:") + std::to_string(*x);
444         }
445         if (y) {
446             ret += std::string(" y:") + std::to_string(*y);
447         }
448         ret += std::string(" {");
449         ret += std::string(" r:") + std::to_string(static_cast<int>(r));
450         ret += std::string(" g:") + std::to_string(static_cast<int>(g));
451         ret += std::string(" b:") + std::to_string(static_cast<int>(b));
452         ret += std::string(" a:") + std::to_string(static_cast<int>(a));
453         ret += std::string(" }");
454         return ret;
455     }
456 
457     bool operator==(const PixelR8G8B8A8& rhs) const {
458         const auto& lhs = *this;
459         return std::tie(lhs.r, lhs.g, lhs.b, lhs.a) == std::tie(rhs.r, rhs.g, rhs.b, rhs.a);
460     }
461 
PrintToPixelR8G8B8A8462     friend void PrintTo(const PixelR8G8B8A8& pixel, std::ostream* os) { *os << pixel.ToString(); }
463 };
464 
465 void RGBToYUV(uint8_t r, uint8_t g, uint8_t b, uint8_t* outY, uint8_t* outU, uint8_t* outV);
466 
Fill(uint32_t w,uint32_t h,const PixelR8G8B8A8 & pixel)467 constexpr std::vector<uint8_t> Fill(uint32_t w, uint32_t h, const PixelR8G8B8A8& pixel) {
468     std::vector<uint8_t> ret;
469     ret.reserve(w * h * 4);
470     for (uint32_t y = 0; y < h; y++) {
471         for (uint32_t x = 0; x < w; x++) {
472             ret.push_back(pixel.r);
473             ret.push_back(pixel.g);
474             ret.push_back(pixel.b);
475             ret.push_back(pixel.a);
476         }
477     }
478     return ret;
479 }
480 
481 struct Image {
482     uint32_t width;
483     uint32_t height;
484     std::vector<uint32_t> pixels;
485 };
486 Image ImageFromColor(uint32_t w, uint32_t h, const PixelR8G8B8A8& pixel);
487 
488 enum class GfxstreamTransport {
489   kVirtioGpuAsg,
490   kVirtioGpuPipe,
491 };
492 
493 struct TestParams {
494     bool with_gl;
495     bool with_vk;
496     int samples = 1;
497     std::unordered_set<std::string> with_features;
498     GfxstreamTransport with_transport = GfxstreamTransport::kVirtioGpuAsg;
499 
500     std::string ToString() const;
501     friend std::ostream& operator<<(std::ostream& os, const TestParams& params);
502 };
503 
504 std::string GetTestName(const ::testing::TestParamInfo<TestParams>& info);
505 
506 // Generates the cartesian product of params with and without the given features.
507 std::vector<TestParams> WithAndWithoutFeatures(const std::vector<TestParams>& params,
508                                                const std::vector<std::string>& features);
509 
510 struct TypicalVkTestEnvironmentOptions {
511     uint32_t apiVersion{VK_API_VERSION_1_2};
512     std::optional<const void*> instanceCreateInfoPNext;
513     std::optional<std::vector<std::string>> deviceExtensions;
514     std::optional<const void*> deviceCreateInfoPNext;
515 };
516 
517 class GfxstreamEnd2EndTest : public ::testing::TestWithParam<TestParams> {
518    public:
519     std::unique_ptr<GuestGlDispatchTable> SetupGuestGl();
520     std::unique_ptr<GuestRenderControlDispatchTable> SetupGuestRc();
521     std::unique_ptr<vkhpp::DynamicLoader> SetupGuestVk();
522 
523     void SetUp() override;
524 
525     void TearDownGuest();
526     void TearDown() override;
527 
528     void SetUpEglContextAndSurface(uint32_t contextVersion,
529                                    uint32_t width,
530                                    uint32_t height,
531                                    EGLDisplay* outDisplay,
532                                    EGLContext* outContext,
533                                    EGLSurface* outSurface);
534 
535     void TearDownEglContextAndSurface(EGLDisplay display,
536                                       EGLContext context,
537                                       EGLSurface surface);
538 
539     Result<ScopedGlShader> SetUpShader(GLenum type, const std::string& source);
540 
541     Result<ScopedGlProgram> SetUpProgram(const std::string& vertSource,
542                                          const std::string& fragSource);
543 
544     Result<ScopedGlProgram> SetUpProgram(GLenum programBinaryFormat,
545                                          const std::vector<uint8_t>& programBinaryData);
546 
547     struct TypicalVkTestEnvironment {
548         vkhpp::UniqueInstance instance;
549         vkhpp::PhysicalDevice physicalDevice;
550         vkhpp::UniqueDevice device;
551         vkhpp::Queue queue;
552         uint32_t queueFamilyIndex;
553     };
554     Result<TypicalVkTestEnvironment> SetUpTypicalVkTestEnvironment(
555         const TypicalVkTestEnvironmentOptions& opts = {});
556 
557     void SnapshotSaveAndLoad();
558 
559     Result<Image> LoadImage(const std::string& basename);
560 
561     Result<Image> AsImage(ScopedAHardwareBuffer& ahb);
562 
563     Result<Ok> FillAhb(ScopedAHardwareBuffer& ahb, PixelR8G8B8A8 color);
564 
565     Result<ScopedAHardwareBuffer> CreateAHBFromImage(const std::string& basename);
566 
567     bool ArePixelsSimilar(uint32_t expectedPixel, uint32_t actualPixel);
568 
569     bool AreImagesSimilar(const Image& expected, const Image& actual);
570 
571     Result<Ok> CompareAHBWithGolden(ScopedAHardwareBuffer& ahb, const std::string& goldenBasename);
572 
573     std::unique_ptr<ANativeWindowHelper> mAnwHelper;
574     std::unique_ptr<Gralloc> mGralloc;
575     std::unique_ptr<SyncHelper> mSync;
576     std::unique_ptr<GuestGlDispatchTable> mGl;
577     std::unique_ptr<GuestRenderControlDispatchTable> mRc;
578     std::unique_ptr<vkhpp::DynamicLoader> mVk;
579 
580     std::unique_ptr<KumquatInstance> mKumquatInstance = nullptr;
581 };
582 
583 }  // namespace tests
584 }  // namespace gfxstream
585