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 #include <gmock/gmock.h>
16 #include <gtest/gtest.h>
17 
18 #include "GfxstreamEnd2EndTests.h"
19 
20 #include <dlfcn.h>
21 #include <log/log.h>
22 
23 #include <filesystem>
24 
25 #include "aemu/base/Path.h"
26 #include "gfxstream/ImageUtils.h"
27 #include "gfxstream/Strings.h"
28 
29 VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE
30 
31 namespace gfxstream {
32 namespace tests {
33 namespace {
34 
35 using testing::AnyOf;
36 using testing::Eq;
37 using testing::Gt;
38 using testing::IsFalse;
39 using testing::IsTrue;
40 using testing::Not;
41 using testing::NotNull;
42 
GetTestDataPath(const std::string & basename)43 std::string GetTestDataPath(const std::string& basename) {
44     const std::filesystem::path testBinaryDirectory = gfxstream::guest::getProgramDirectory();
45     return (testBinaryDirectory / "testdata" / basename).string();
46 }
47 
48 }  // namespace
49 
ImageFromColor(uint32_t w,uint32_t h,const PixelR8G8B8A8 & pixel)50 Image ImageFromColor(uint32_t w, uint32_t h, const PixelR8G8B8A8& pixel) {
51     uint32_t rgba = 0;
52     uint8_t* rgbaParts = reinterpret_cast<uint8_t*>(&rgba);
53     rgbaParts[0] = pixel.r;
54     rgbaParts[1] = pixel.g;
55     rgbaParts[2] = pixel.b;
56     rgbaParts[3] = pixel.a;
57 
58     Image ret;
59     ret.width = w;
60     ret.height = h;
61     ret.pixels.resize(w * h, rgba);
62     return ret;
63 }
64 
GfxstreamTransportToEnvVar(GfxstreamTransport transport)65 std::string GfxstreamTransportToEnvVar(GfxstreamTransport transport) {
66     switch (transport) {
67         case GfxstreamTransport::kVirtioGpuAsg: {
68             return "virtio-gpu-asg";
69         }
70         case GfxstreamTransport::kVirtioGpuPipe: {
71             return "virtio-gpu-pipe";
72         }
73     }
74 }
75 
GfxstreamTransportToString(GfxstreamTransport transport)76 std::string GfxstreamTransportToString(GfxstreamTransport transport) {
77     switch (transport) {
78         case GfxstreamTransport::kVirtioGpuAsg: {
79             return "VirtioGpuAsg";
80         }
81         case GfxstreamTransport::kVirtioGpuPipe: {
82             return "VirtioGpuPipe";
83         }
84     }
85 }
86 
ToString() const87 std::string TestParams::ToString() const {
88     std::string ret;
89     ret += (with_gl ? "With" : "Without");
90     ret += "Gl";
91     ret += (with_vk ? "With" : "Without");
92     ret += "Vk";
93     ret += "SampleCount" + std::to_string(samples);
94     if (!with_features.empty()) {
95         ret += "WithFeatures_";
96         ret += Join(with_features, "_");
97         ret += "_";
98     }
99     ret += "Over";
100     ret += GfxstreamTransportToString(with_transport);
101     return ret;
102 }
103 
operator <<(std::ostream & os,const TestParams & params)104 std::ostream& operator<<(std::ostream& os, const TestParams& params) {
105     return os << params.ToString();
106 }
107 
GetTestName(const::testing::TestParamInfo<TestParams> & info)108 std::string GetTestName(const ::testing::TestParamInfo<TestParams>& info) {
109     return info.param.ToString();
110 }
111 
WithAndWithoutFeatures(const std::vector<TestParams> & params,const std::vector<std::string> & features)112 std::vector<TestParams> WithAndWithoutFeatures(const std::vector<TestParams>& params,
113                                                const std::vector<std::string>& features) {
114     std::vector<TestParams> output;
115     output.reserve(params.size() * 2);
116 
117     // Copy of all of the existing test params:
118     output.insert(output.end(), params.begin(), params.end());
119 
120     // Copy of all of the existing test params with the new features:
121     for (TestParams copy : params) {
122         copy.with_features.insert(features.begin(), features.end());
123         output.push_back(copy);
124     }
125 
126     return output;
127 }
128 
SetupGuestGl()129 std::unique_ptr<GuestGlDispatchTable> GfxstreamEnd2EndTest::SetupGuestGl() {
130     const std::filesystem::path testDirectory = gfxstream::guest::getProgramDirectory();
131     const std::string eglLibPath = (testDirectory / "libEGL_emulation.so").string();
132     const std::string gles2LibPath = (testDirectory / "libGLESv2_emulation.so").string();
133 
134     void* eglLib = dlopen(eglLibPath.c_str(), RTLD_NOW | RTLD_LOCAL);
135     if (!eglLib) {
136         ALOGE("Failed to load Gfxstream EGL library from %s.", eglLibPath.c_str());
137         return nullptr;
138     }
139 
140     void* gles2Lib = dlopen(gles2LibPath.c_str(), RTLD_NOW | RTLD_LOCAL);
141     if (!gles2Lib) {
142         ALOGE("Failed to load Gfxstream GLES2 library from %s.", gles2LibPath.c_str());
143         return nullptr;
144     }
145 
146     using GenericFnType = void*(void);
147     using GetProcAddrType = GenericFnType*(const char*);
148 
149     auto eglGetAddr = reinterpret_cast<GetProcAddrType*>(dlsym(eglLib, "eglGetProcAddress"));
150     if (!eglGetAddr) {
151         ALOGE("Failed to load Gfxstream EGL library from %s.", eglLibPath.c_str());
152         return nullptr;
153     }
154 
155     auto gl = std::make_unique<GuestGlDispatchTable>();
156 
157     #define LOAD_EGL_FUNCTION(return_type, function_name, signature) \
158         gl-> function_name = reinterpret_cast< return_type (*) signature >(eglGetAddr( #function_name ));
159 
160     LIST_RENDER_EGL_FUNCTIONS(LOAD_EGL_FUNCTION)
161     LIST_RENDER_EGL_EXTENSIONS_FUNCTIONS(LOAD_EGL_FUNCTION)
162 
163 #define LOAD_GLES2_FUNCTION(return_type, function_name, signature, callargs)         \
164     gl->function_name =                                                              \
165         reinterpret_cast<return_type(*) signature>(dlsym(gles2Lib, #function_name)); \
166     if (!gl->function_name) {                                                        \
167         gl->function_name =                                                          \
168             reinterpret_cast<return_type(*) signature>(eglGetAddr(#function_name));  \
169     }
170 
171     LIST_GLES_FUNCTIONS(LOAD_GLES2_FUNCTION, LOAD_GLES2_FUNCTION)
172 
173     return gl;
174 }
175 
SetupGuestRc()176 std::unique_ptr<GuestRenderControlDispatchTable> GfxstreamEnd2EndTest::SetupGuestRc() {
177     const std::filesystem::path testDirectory = gfxstream::guest::getProgramDirectory();
178     const std::string rcLibPath = (testDirectory / "libgfxstream_guest_rendercontrol.so").string();
179 
180     void* rcLib = dlopen(rcLibPath.c_str(), RTLD_NOW | RTLD_LOCAL);
181     if (!rcLib) {
182         ALOGE("Failed to load Gfxstream RenderControl library from %s.", rcLibPath.c_str());
183         return nullptr;
184     }
185 
186     auto rc = std::make_unique<GuestRenderControlDispatchTable>();
187 
188 #define LOAD_RENDERCONTROL_FUNCTION(name)                         \
189     rc->name = reinterpret_cast<PFN_##name>(dlsym(rcLib, #name)); \
190     if (rc->name == nullptr) {                                    \
191         ALOGE("Failed to load RenderControl function %s", #name); \
192         return nullptr;                                           \
193     }
194 
195     LOAD_RENDERCONTROL_FUNCTION(rcCreateDevice);
196     LOAD_RENDERCONTROL_FUNCTION(rcDestroyDevice);
197     LOAD_RENDERCONTROL_FUNCTION(rcCompose);
198 
199     return rc;
200 }
201 
SetupGuestVk()202 std::unique_ptr<vkhpp::DynamicLoader> GfxstreamEnd2EndTest::SetupGuestVk() {
203     const std::filesystem::path testDirectory = gfxstream::guest::getProgramDirectory();
204     const std::string vkLibPath = (testDirectory / "vulkan.ranchu.so").string();
205 
206     auto dl = std::make_unique<vkhpp::DynamicLoader>(vkLibPath);
207     if (!dl->success()) {
208         ALOGE("Failed to load Vulkan from: %s", vkLibPath.c_str());
209         return nullptr;
210     }
211 
212     auto getInstanceProcAddr = dl->getProcAddress<PFN_vkGetInstanceProcAddr>("vk_icdGetInstanceProcAddr");
213     if (!getInstanceProcAddr) {
214         ALOGE("Failed to load Vulkan vkGetInstanceProcAddr. %s", dlerror());
215         return nullptr;
216     }
217 
218     VULKAN_HPP_DEFAULT_DISPATCHER.init(getInstanceProcAddr);
219 
220     return dl;
221 }
222 
SetUp()223 void GfxstreamEnd2EndTest::SetUp() {
224     const TestParams params = GetParam();
225     const std::string transportValue = GfxstreamTransportToEnvVar(params.with_transport);
226     std::vector<std::string> featureEnables;
227     for (const std::string& feature : params.with_features) {
228         featureEnables.push_back(feature + ":enabled");
229     }
230 
231     ASSERT_THAT(setenv("GFXSTREAM_TRANSPORT", transportValue.c_str(), /*overwrite=*/1), Eq(0));
232 
233     ASSERT_THAT(setenv("VIRTGPU_KUMQUAT", "1", /*overwrite=*/1), Eq(0));
234     const std::string features = Join(featureEnables, ",");
235 
236     // We probably don't need to create a Kumquat Server instance for every test.  GTest provides
237     // SetUpTestSuite + TearDownTestSuite for common resources that can be shared across a test
238     // suite.
239     mKumquatInstance = std::make_unique<KumquatInstance>();
240     mKumquatInstance->SetUp(params.with_gl, params.with_vk, features);
241 
242     if (params.with_gl) {
243         mGl = SetupGuestGl();
244         ASSERT_THAT(mGl, NotNull());
245     }
246     if (params.with_vk) {
247         mVk = SetupGuestVk();
248         ASSERT_THAT(mVk, NotNull());
249     }
250 
251     mRc = SetupGuestRc();
252     ASSERT_THAT(mRc, NotNull());
253 
254     mAnwHelper.reset(createPlatformANativeWindowHelper());
255     mGralloc.reset(createPlatformGralloc());
256     mSync.reset(createPlatformSyncHelper());
257 }
258 
TearDownGuest()259 void GfxstreamEnd2EndTest::TearDownGuest() {
260     if (mGl) {
261         EGLDisplay display = mGl->eglGetCurrentDisplay();
262         if (display != EGL_NO_DISPLAY) {
263             mGl->eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
264             mGl->eglTerminate(display);
265         }
266         mGl->eglReleaseThread();
267         mGl.reset();
268     }
269     mVk.reset();
270     mRc.reset();
271 
272     mAnwHelper.reset();
273     mGralloc.reset();
274     mSync.reset();
275 }
276 
TearDown()277 void GfxstreamEnd2EndTest::TearDown() {
278     TearDownGuest();
279     mKumquatInstance.reset();
280 }
281 
SetUpEglContextAndSurface(uint32_t contextVersion,uint32_t width,uint32_t height,EGLDisplay * outDisplay,EGLContext * outContext,EGLSurface * outSurface)282 void GfxstreamEnd2EndTest::SetUpEglContextAndSurface(
283         uint32_t contextVersion,
284         uint32_t width,
285         uint32_t height,
286         EGLDisplay* outDisplay,
287         EGLContext* outContext,
288         EGLSurface* outSurface) {
289     ASSERT_THAT(contextVersion, AnyOf(Eq(2), Eq(3)))
290         << "Invalid context version requested.";
291 
292     EGLDisplay display = mGl->eglGetDisplay(EGL_DEFAULT_DISPLAY);
293     ASSERT_THAT(display, Not(Eq(EGL_NO_DISPLAY)));
294 
295     int versionMajor = 0;
296     int versionMinor = 0;
297     ASSERT_THAT(mGl->eglInitialize(display, &versionMajor, &versionMinor), IsTrue());
298 
299     ASSERT_THAT(mGl->eglBindAPI(EGL_OPENGL_ES_API), IsTrue());
300 
301     // clang-format off
302     static const EGLint configAttributes[] = {
303         EGL_SURFACE_TYPE,    EGL_PBUFFER_BIT,
304         EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
305         EGL_NONE,
306     };
307     // clang-format on
308 
309     int numConfigs = 0;
310     ASSERT_THAT(mGl->eglChooseConfig(display, configAttributes, nullptr, 1, &numConfigs), IsTrue());
311     ASSERT_THAT(numConfigs, Gt(0));
312 
313     EGLConfig config = nullptr;
314     ASSERT_THAT(mGl->eglChooseConfig(display, configAttributes, &config, 1, &numConfigs), IsTrue());
315     ASSERT_THAT(config, Not(Eq(nullptr)));
316 
317     // clang-format off
318     static const EGLint surfaceAttributes[] = {
319         EGL_WIDTH,  static_cast<EGLint>(width),
320         EGL_HEIGHT, static_cast<EGLint>(height),
321         EGL_NONE,
322     };
323     // clang-format on
324 
325     EGLSurface surface = mGl->eglCreatePbufferSurface(display, config, surfaceAttributes);
326     ASSERT_THAT(surface, Not(Eq(EGL_NO_SURFACE)));
327 
328     // clang-format off
329     static const EGLint contextAttribs[] = {
330         EGL_CONTEXT_CLIENT_VERSION, static_cast<EGLint>(contextVersion),
331         EGL_NONE,
332     };
333     // clang-format on
334 
335     EGLContext context = mGl->eglCreateContext(display, config, EGL_NO_CONTEXT, contextAttribs);
336     ASSERT_THAT(context, Not(Eq(EGL_NO_CONTEXT)));
337 
338     ASSERT_THAT(mGl->eglMakeCurrent(display, surface, surface, context), IsTrue());
339 
340     *outDisplay = display;
341     *outContext = context;
342     *outSurface = surface;
343 }
344 
TearDownEglContextAndSurface(EGLDisplay display,EGLContext context,EGLSurface surface)345 void GfxstreamEnd2EndTest::TearDownEglContextAndSurface(
346         EGLDisplay display,
347         EGLContext context,
348         EGLSurface surface) {
349     ASSERT_THAT(mGl->eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT), IsTrue());
350     ASSERT_THAT(mGl->eglDestroyContext(display, context), IsTrue());
351     ASSERT_THAT(mGl->eglDestroySurface(display, surface), IsTrue());
352 }
353 
MakeShader(GlDispatch & dispatch,GLenum type,const std::string & source)354 Result<ScopedGlShader> ScopedGlShader::MakeShader(GlDispatch& dispatch, GLenum type,
355                                                   const std::string& source) {
356     GLuint shader = dispatch.glCreateShader(type);
357     if (!shader) {
358         return gfxstream::unexpected("Failed to create shader.");
359     }
360 
361     const GLchar* sourceTyped = (const GLchar*)source.c_str();
362     const GLint sourceLength = source.size();
363     dispatch.glShaderSource(shader, 1, &sourceTyped, &sourceLength);
364     dispatch.glCompileShader(shader);
365 
366     GLint compileStatus;
367     dispatch.glGetShaderiv(shader, GL_COMPILE_STATUS, &compileStatus);
368 
369     if (compileStatus != GL_TRUE) {
370         GLint errorLogLength = 0;
371         dispatch.glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &errorLogLength);
372         if (!errorLogLength) {
373             errorLogLength = 512;
374         }
375 
376         std::vector<GLchar> errorLog(errorLogLength);
377         dispatch.glGetShaderInfoLog(shader, errorLogLength, &errorLogLength, errorLog.data());
378 
379         const std::string errorString = errorLogLength == 0 ? "" : errorLog.data();
380         ALOGE("Shader compilation failed with: \"%s\"", errorString.c_str());
381 
382         dispatch.glDeleteShader(shader);
383         return gfxstream::unexpected(errorString);
384     }
385 
386     return ScopedGlShader(dispatch, shader);
387 }
388 
MakeProgram(GlDispatch & dispatch,const std::string & vertSource,const std::string & fragSource)389 Result<ScopedGlProgram> ScopedGlProgram::MakeProgram(GlDispatch& dispatch,
390                                                      const std::string& vertSource,
391                                                      const std::string& fragSource) {
392     auto vertShader =
393         GFXSTREAM_EXPECT(ScopedGlShader::MakeShader(dispatch, GL_VERTEX_SHADER, vertSource));
394     auto fragShader =
395         GFXSTREAM_EXPECT(ScopedGlShader::MakeShader(dispatch, GL_FRAGMENT_SHADER, fragSource));
396 
397     GLuint program = dispatch.glCreateProgram();
398     dispatch.glAttachShader(program, vertShader);
399     dispatch.glAttachShader(program, fragShader);
400     dispatch.glLinkProgram(program);
401 
402     GLint linkStatus;
403     dispatch.glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);
404     if (linkStatus != GL_TRUE) {
405         GLint errorLogLength = 0;
406         dispatch.glGetProgramiv(program, GL_INFO_LOG_LENGTH, &errorLogLength);
407         if (!errorLogLength) {
408             errorLogLength = 512;
409         }
410 
411         std::vector<char> errorLog(errorLogLength, 0);
412         dispatch.glGetProgramInfoLog(program, errorLogLength, nullptr, errorLog.data());
413 
414         const std::string errorString = errorLogLength == 0 ? "" : errorLog.data();
415         ALOGE("Program link failed with: \"%s\"", errorString.c_str());
416 
417         dispatch.glDeleteProgram(program);
418         return gfxstream::unexpected(errorString);
419     }
420 
421     return ScopedGlProgram(dispatch, program);
422 }
423 
MakeProgram(GlDispatch & dispatch,GLenum programBinaryFormat,const std::vector<uint8_t> & programBinaryData)424 Result<ScopedGlProgram> ScopedGlProgram::MakeProgram(
425     GlDispatch& dispatch, GLenum programBinaryFormat,
426     const std::vector<uint8_t>& programBinaryData) {
427     GLuint program = dispatch.glCreateProgram();
428     dispatch.glProgramBinary(program, programBinaryFormat, programBinaryData.data(),
429                              programBinaryData.size());
430 
431     GLint linkStatus;
432     dispatch.glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);
433     if (linkStatus != GL_TRUE) {
434         GLint errorLogLength = 0;
435         dispatch.glGetProgramiv(program, GL_INFO_LOG_LENGTH, &errorLogLength);
436         if (!errorLogLength) {
437             errorLogLength = 512;
438         }
439 
440         std::vector<char> errorLog(errorLogLength, 0);
441         dispatch.glGetProgramInfoLog(program, errorLogLength, nullptr, errorLog.data());
442 
443         const std::string errorString = errorLogLength == 0 ? "" : errorLog.data();
444         ALOGE("Program link failed with: \"%s\"", errorString.c_str());
445 
446         dispatch.glDeleteProgram(program);
447         return gfxstream::unexpected(errorString);
448     }
449 
450     return ScopedGlProgram(dispatch, program);
451 }
452 
Allocate(Gralloc & gralloc,uint32_t width,uint32_t height,uint32_t format)453 Result<ScopedAHardwareBuffer> ScopedAHardwareBuffer::Allocate(Gralloc& gralloc, uint32_t width,
454                                                               uint32_t height, uint32_t format) {
455     AHardwareBuffer* ahb = nullptr;
456     int status = gralloc.allocate(width, height, format, -1, &ahb);
457     if (status != 0) {
458         return gfxstream::unexpected(std::string("Failed to allocate AHB with width:") +
459                                          std::to_string(width) + std::string(" height:") +
460                                          std::to_string(height) + std::string(" format:") +
461                                          std::to_string(format));
462     }
463 
464     return ScopedAHardwareBuffer(gralloc, ahb);
465 }
466 
SetUpShader(GLenum type,const std::string & source)467 Result<ScopedGlShader> GfxstreamEnd2EndTest::SetUpShader(GLenum type, const std::string& source) {
468     if (!mGl) {
469         return gfxstream::unexpected("Gl not enabled for this test.");
470     }
471 
472     return ScopedGlShader::MakeShader(*mGl, type, source);
473 }
474 
SetUpProgram(const std::string & vertSource,const std::string & fragSource)475 Result<ScopedGlProgram> GfxstreamEnd2EndTest::SetUpProgram(const std::string& vertSource,
476                                                            const std::string& fragSource) {
477     if (!mGl) {
478         return gfxstream::unexpected("Gl not enabled for this test.");
479     }
480 
481     return ScopedGlProgram::MakeProgram(*mGl, vertSource, fragSource);
482 }
483 
SetUpProgram(GLenum programBinaryFormat,const std::vector<uint8_t> & programBinaryData)484 Result<ScopedGlProgram> GfxstreamEnd2EndTest::SetUpProgram(
485     GLenum programBinaryFormat, const std::vector<uint8_t>& programBinaryData) {
486     if (!mGl) {
487         return gfxstream::unexpected("Gl not enabled for this test.");
488     }
489 
490     return ScopedGlProgram::MakeProgram(*mGl, programBinaryFormat, programBinaryData);
491 }
492 
493 Result<GfxstreamEnd2EndTest::TypicalVkTestEnvironment>
SetUpTypicalVkTestEnvironment(const TypicalVkTestEnvironmentOptions & opts)494 GfxstreamEnd2EndTest::SetUpTypicalVkTestEnvironment(const TypicalVkTestEnvironmentOptions& opts) {
495     const auto availableInstanceLayers = vkhpp::enumerateInstanceLayerProperties().value;
496     ALOGV("Available instance layers:");
497     for (const vkhpp::LayerProperties& layer : availableInstanceLayers) {
498         ALOGV(" - %s", layer.layerName.data());
499     }
500 
501     constexpr const bool kEnableValidationLayers = true;
502 
503     std::vector<const char*> requestedInstanceExtensions;
504     std::vector<const char*> requestedInstanceLayers;
505     if (kEnableValidationLayers) {
506         requestedInstanceExtensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
507     }
508 
509     const vkhpp::ApplicationInfo applicationInfo{
510         .pApplicationName = ::testing::UnitTest::GetInstance()->current_test_info()->name(),
511         .applicationVersion = 1,
512         .pEngineName = "Gfxstream Testing Engine",
513         .engineVersion = 1,
514         .apiVersion = opts.apiVersion,
515     };
516     const vkhpp::InstanceCreateInfo instanceCreateInfo{
517         .pNext = opts.instanceCreateInfoPNext ? *opts.instanceCreateInfoPNext : nullptr,
518         .pApplicationInfo = &applicationInfo,
519         .enabledLayerCount = static_cast<uint32_t>(requestedInstanceLayers.size()),
520         .ppEnabledLayerNames = requestedInstanceLayers.data(),
521         .enabledExtensionCount = static_cast<uint32_t>(requestedInstanceExtensions.size()),
522         .ppEnabledExtensionNames = requestedInstanceExtensions.data(),
523     };
524 
525     auto instance = GFXSTREAM_EXPECT_VKHPP_RV(vkhpp::createInstanceUnique(instanceCreateInfo));
526 
527     VULKAN_HPP_DEFAULT_DISPATCHER.init(*instance);
528 
529     auto physicalDevices = GFXSTREAM_EXPECT_VKHPP_RV(instance->enumeratePhysicalDevices());
530     ALOGV("Available physical devices:");
531     for (const auto& physicalDevice : physicalDevices) {
532         const auto physicalDeviceProps = physicalDevice.getProperties();
533         ALOGV(" - %s", physicalDeviceProps.deviceName.data());
534     }
535 
536     if (physicalDevices.empty()) {
537         return gfxstream::unexpected(
538             "Failed to set up typical VK env: no physical devices available.");
539     }
540 
541     auto physicalDevice = std::move(physicalDevices[0]);
542     {
543         const auto physicalDeviceProps = physicalDevice.getProperties();
544         ALOGV("Selected physical device: %s", physicalDeviceProps.deviceName.data());
545     }
546     {
547         const auto exts =
548             GFXSTREAM_EXPECT_VKHPP_RV(physicalDevice.enumerateDeviceExtensionProperties());
549         ALOGV("Available physical device extensions:");
550         for (const auto& ext : exts) {
551             ALOGV(" - %s", ext.extensionName.data());
552         }
553     }
554 
555     uint32_t graphicsQueueFamilyIndex = -1;
556     {
557         const auto props = physicalDevice.getQueueFamilyProperties();
558         for (uint32_t i = 0; i < props.size(); i++) {
559             const auto& prop = props[i];
560             if (prop.queueFlags & vkhpp::QueueFlagBits::eGraphics) {
561                 graphicsQueueFamilyIndex = i;
562                 break;
563             }
564         }
565     }
566     if (graphicsQueueFamilyIndex == -1) {
567         return gfxstream::unexpected("Failed to set up typical VK env: no graphics queue.");
568     }
569 
570     const float queuePriority = 1.0f;
571     const vkhpp::DeviceQueueCreateInfo deviceQueueCreateInfo = {
572         .queueFamilyIndex = graphicsQueueFamilyIndex,
573         .queueCount = 1,
574         .pQueuePriorities = &queuePriority,
575     };
576     std::vector<const char*> deviceExtensions = {
577         VK_ANDROID_NATIVE_BUFFER_EXTENSION_NAME,
578         VK_ANDROID_EXTERNAL_MEMORY_ANDROID_HARDWARE_BUFFER_EXTENSION_NAME,
579     };
580     if (opts.deviceExtensions) {
581         for (const std::string& ext : *opts.deviceExtensions) {
582             deviceExtensions.push_back(ext.c_str());
583         }
584     }
585     const vkhpp::DeviceCreateInfo deviceCreateInfo = {
586         .pNext = opts.deviceCreateInfoPNext ? *opts.deviceCreateInfoPNext : nullptr,
587         .pQueueCreateInfos = &deviceQueueCreateInfo,
588         .queueCreateInfoCount = 1,
589         .enabledLayerCount = 0,
590         .ppEnabledLayerNames = nullptr,
591         .enabledExtensionCount = static_cast<uint32_t>(deviceExtensions.size()),
592         .ppEnabledExtensionNames = deviceExtensions.data(),
593     };
594     auto device = GFXSTREAM_EXPECT_VKHPP_RV(physicalDevice.createDeviceUnique(deviceCreateInfo));
595 
596     auto queue = device->getQueue(graphicsQueueFamilyIndex, 0);
597 
598     return TypicalVkTestEnvironment{
599         .instance = std::move(instance),
600         .physicalDevice = std::move(physicalDevice),
601         .device = std::move(device),
602         .queue = std::move(queue),
603         .queueFamilyIndex = graphicsQueueFamilyIndex,
604     };
605 }
606 
SnapshotSaveAndLoad()607 void GfxstreamEnd2EndTest::SnapshotSaveAndLoad() {
608     mKumquatInstance->Snapshot();
609     mKumquatInstance->Restore();
610 }
611 
LoadImage(const std::string & basename)612 Result<Image> GfxstreamEnd2EndTest::LoadImage(const std::string& basename) {
613     const std::string filepath = GetTestDataPath(basename);
614     if (!std::filesystem::exists(filepath)) {
615         return gfxstream::unexpected("File " + filepath + " does not exist.");
616     }
617     if (!std::filesystem::is_regular_file(filepath)) {
618         return gfxstream::unexpected("File " + filepath + " is not a regular file.");
619     }
620 
621     Image image;
622 
623     uint32_t sourceWidth = 0;
624     uint32_t sourceHeight = 0;
625     std::vector<uint32_t> sourcePixels;
626     if (!LoadRGBAFromPng(filepath, &image.width, &image.height, &image.pixels)) {
627         return gfxstream::unexpected("Failed to load " + filepath + " as RGBA PNG.");
628     }
629 
630     return image;
631 }
632 
AsImage(ScopedAHardwareBuffer & ahb)633 Result<Image> GfxstreamEnd2EndTest::AsImage(ScopedAHardwareBuffer& ahb) {
634     Image actual;
635     actual.width = ahb.GetWidth();
636     if (actual.width == 0) {
637         return gfxstream::unexpected("Failed to query AHB width.");
638     }
639     actual.height = ahb.GetHeight();
640     if (actual.height == 0) {
641         return gfxstream::unexpected("Failed to query AHB height.");
642     }
643     actual.pixels.resize(actual.width * actual.height);
644 
645     const uint32_t ahbFormat = ahb.GetAHBFormat();
646     if (ahbFormat != GFXSTREAM_AHB_FORMAT_R8G8B8A8_UNORM &&
647         ahbFormat != GFXSTREAM_AHB_FORMAT_B8G8R8A8_UNORM) {
648         return gfxstream::unexpected("Unhandled AHB format " + std::to_string(ahbFormat));
649     }
650 
651     {
652         uint8_t* ahbPixels = GFXSTREAM_EXPECT(ahb.Lock());
653         std::memcpy(actual.pixels.data(), ahbPixels, actual.pixels.size() * sizeof(uint32_t));
654         ahb.Unlock();
655     }
656 
657     if (ahbFormat == GFXSTREAM_AHB_FORMAT_B8G8R8A8_UNORM) {
658         for (uint32_t& pixel : actual.pixels) {
659             uint8_t* pixelComponents = reinterpret_cast<uint8_t*>(&pixel);
660             std::swap(pixelComponents[0], pixelComponents[2]);
661         }
662     }
663 
664     return actual;
665 }
666 
FillAhb(ScopedAHardwareBuffer & ahb,PixelR8G8B8A8 color)667 Result<Ok> GfxstreamEnd2EndTest::FillAhb(ScopedAHardwareBuffer& ahb, PixelR8G8B8A8 color) {
668     const uint32_t drmFormat = ahb.GetDrmFormat();
669 
670     const uint32_t ahbWidth = ahb.GetWidth();
671     const uint32_t ahbHeight = ahb.GetHeight();
672 
673     std::vector<Gralloc::LockedPlane> planes = GFXSTREAM_EXPECT(ahb.LockPlanes());
674     if (drmFormat == DRM_FORMAT_ABGR8888) {
675         const Gralloc::LockedPlane& plane = planes[0];
676 
677         std::vector<uint8_t> srcRow;
678         for (uint32_t x = 0; x < ahbWidth; x++) {
679             srcRow.push_back(color.r);
680             srcRow.push_back(color.g);
681             srcRow.push_back(color.b);
682             srcRow.push_back(color.a);
683         }
684 
685         for (uint32_t y = 0; y < ahbHeight; y++) {
686             uint8_t* dstRow = plane.data + (y * plane.rowStrideBytes);
687             std::memcpy(dstRow, srcRow.data(), srcRow.size());
688         }
689     } else if (drmFormat == DRM_FORMAT_NV12 || drmFormat == DRM_FORMAT_YVU420) {
690         uint8_t colorY;
691         uint8_t colorU;
692         uint8_t colorV;
693         RGBToYUV(color.r, color.g, color.b, &colorY, &colorU, &colorV);
694 
695         const Gralloc::LockedPlane& yPlane = planes[0];
696         const Gralloc::LockedPlane& uPlane = planes[1];
697         const Gralloc::LockedPlane& vPlane = planes[2];
698 
699         colorY = 178;
700         colorU = 171;
701         colorV = 0;
702 
703         for (uint32_t y = 0; y < ahbHeight; y++) {
704             for (uint32_t x = 0; x < ahbWidth; x++) {
705                 uint8_t* dstY =
706                     yPlane.data + (y * yPlane.rowStrideBytes) + (x * yPlane.pixelStrideBytes);
707                 uint8_t* dstU = uPlane.data + ((y / 2) * uPlane.rowStrideBytes) +
708                                 ((x / 2) * uPlane.pixelStrideBytes);
709                 uint8_t* dstV = vPlane.data + ((y / 2) * vPlane.rowStrideBytes) +
710                                 ((x / 2) * vPlane.pixelStrideBytes);
711                 *dstY = colorY;
712                 *dstU = colorU;
713                 *dstV = colorV;
714             }
715         }
716     } else {
717         return gfxstream::unexpected("Unhandled DRM format: " + std::to_string(drmFormat));
718     }
719 
720     ahb.Unlock();
721 
722     return Ok{};
723 }
724 
CreateAHBFromImage(const std::string & basename)725 Result<ScopedAHardwareBuffer> GfxstreamEnd2EndTest::CreateAHBFromImage(
726     const std::string& basename) {
727     auto image = GFXSTREAM_EXPECT(LoadImage(basename));
728 
729     auto ahb = GFXSTREAM_EXPECT(ScopedAHardwareBuffer::Allocate(
730         *mGralloc, image.width, image.height, GFXSTREAM_AHB_FORMAT_R8G8B8A8_UNORM));
731 
732     {
733         uint8_t* ahbPixels = GFXSTREAM_EXPECT(ahb.Lock());
734         std::memcpy(ahbPixels, image.pixels.data(), image.pixels.size() * sizeof(uint32_t));
735         ahb.Unlock();
736     }
737 
738     return std::move(ahb);
739 }
740 
ArePixelsSimilar(uint32_t expectedPixel,uint32_t actualPixel)741 bool GfxstreamEnd2EndTest::ArePixelsSimilar(uint32_t expectedPixel, uint32_t actualPixel) {
742     const uint8_t* actualRGBA = reinterpret_cast<const uint8_t*>(&actualPixel);
743     const uint8_t* expectedRGBA = reinterpret_cast<const uint8_t*>(&expectedPixel);
744 
745     constexpr const uint32_t kRGBA8888Tolerance = 2;
746     for (uint32_t channel = 0; channel < 4; channel++) {
747         const uint8_t actualChannel = actualRGBA[channel];
748         const uint8_t expectedChannel = expectedRGBA[channel];
749 
750         if ((std::max(actualChannel, expectedChannel) - std::min(actualChannel, expectedChannel)) >
751             kRGBA8888Tolerance) {
752             return false;
753         }
754     }
755     return true;
756 }
757 
AreImagesSimilar(const Image & expected,const Image & actual)758 bool GfxstreamEnd2EndTest::AreImagesSimilar(const Image& expected, const Image& actual) {
759     if (actual.width != expected.width) {
760         ADD_FAILURE() << "Image comparison failed: " << "expected.width " << expected.width << "vs"
761                       << "actual.width " << actual.width;
762         return false;
763     }
764     if (actual.height != expected.height) {
765         ADD_FAILURE() << "Image comparison failed: " << "expected.height " << expected.height
766                       << "vs" << "actual.height " << actual.height;
767         return false;
768     }
769     const uint32_t width = actual.width;
770     const uint32_t height = actual.height;
771     const uint32_t* actualPixels = actual.pixels.data();
772     const uint32_t* expectedPixels = expected.pixels.data();
773 
774     bool imagesSimilar = true;
775 
776     uint32_t reportedIncorrectPixels = 0;
777     constexpr const uint32_t kMaxReportedIncorrectPixels = 5;
778 
779     for (uint32_t y = 0; y < height; y++) {
780         for (uint32_t x = 0; x < width; x++) {
781             const uint32_t actualPixel = actualPixels[y * height + x];
782             const uint32_t expectedPixel = expectedPixels[y * width + x];
783             if (!ArePixelsSimilar(expectedPixel, actualPixel)) {
784                 imagesSimilar = false;
785                 if (reportedIncorrectPixels < kMaxReportedIncorrectPixels) {
786                     reportedIncorrectPixels++;
787                     const uint8_t* actualRGBA = reinterpret_cast<const uint8_t*>(&actualPixel);
788                     const uint8_t* expectedRGBA = reinterpret_cast<const uint8_t*>(&expectedPixel);
789                     // clang-format off
790                     ADD_FAILURE()
791                         << "Pixel comparison failed at (" << x << ", " << y << ") "
792                         << " with actual "
793                         << " r:" << static_cast<int>(actualRGBA[0])
794                         << " g:" << static_cast<int>(actualRGBA[1])
795                         << " b:" << static_cast<int>(actualRGBA[2])
796                         << " a:" << static_cast<int>(actualRGBA[3])
797                         << " but expected "
798                         << " r:" << static_cast<int>(expectedRGBA[0])
799                         << " g:" << static_cast<int>(expectedRGBA[1])
800                         << " b:" << static_cast<int>(expectedRGBA[2])
801                         << " a:" << static_cast<int>(expectedRGBA[3]);
802                     // clang-format on
803                 }
804             }
805         }
806     }
807     return imagesSimilar;
808 }
809 
CompareAHBWithGolden(ScopedAHardwareBuffer & ahb,const std::string & goldenBasename)810 Result<Ok> GfxstreamEnd2EndTest::CompareAHBWithGolden(ScopedAHardwareBuffer& ahb,
811                                                       const std::string& goldenBasename) {
812     Image actual = GFXSTREAM_EXPECT(AsImage(ahb));
813     Result<Image> expected = LoadImage(goldenBasename);
814 
815     bool imagesAreSimilar = false;
816     if (expected.ok()) {
817         imagesAreSimilar = AreImagesSimilar(*expected, actual);
818     } else {
819         imagesAreSimilar = false;
820     }
821 
822     if (!imagesAreSimilar && kSaveImagesIfComparisonFailed) {
823         static uint32_t sImageNumber{1};
824         const std::string outputBasename = std::to_string(sImageNumber++) + "_" + goldenBasename;
825         const std::string output =
826             (std::filesystem::temp_directory_path() / outputBasename).string();
827         SaveRGBAToPng(actual.width, actual.height, actual.pixels.data(), output);
828         ADD_FAILURE() << "Saved image comparison actual image to " << output;
829     }
830 
831     if (!imagesAreSimilar) {
832         return gfxstream::unexpected(
833             "Image comparison failed (consider setting kSaveImagesIfComparisonFailed to true to "
834             "see the actual image generated).");
835     }
836 
837     return {};
838 }
839 
840 namespace {
841 
ClampToU8(int x)842 static constexpr uint8_t ClampToU8(int x) {
843     if (x < 0) return 0;
844     if (x > 255) return 255;
845     return static_cast<uint8_t>(x);
846 }
847 
SaturateToInt(float x)848 static constexpr int SaturateToInt(float x) {
849     constexpr int kMaxS32FitsInFloat = 2147483520;
850     constexpr int kMinS32FitsInFloat = -kMaxS32FitsInFloat;
851     x = x < kMaxS32FitsInFloat ? x : kMaxS32FitsInFloat;
852     x = x > kMinS32FitsInFloat ? x : kMinS32FitsInFloat;
853     return (int)x;
854 }
855 
Round(float x)856 static constexpr float Round(float x) { return (float)((double)x); }
857 
858 }  // namespace
859 
RGBToYUV(uint8_t r,uint8_t g,uint8_t b,uint8_t * outY,uint8_t * outU,uint8_t * outV)860 void RGBToYUV(uint8_t r, uint8_t g, uint8_t b, uint8_t* outY, uint8_t* outU, uint8_t* outV) {
861     static const float kRGBToYUVBT601FullRange[] = {
862         // clang-format off
863          0.299000f,  0.587000f,  0.114000f,  0.000000f,  0.000000f,
864         -0.168736f, -0.331264f,  0.500000f,  0.000000f,  0.501961f,
865          0.500000f, -0.418688f, -0.081312f,  0.000000f,  0.501961f,
866          0.000000f,  0.000000f,  0.000000f,  1.000000f,  0.000000f,
867         // clang-format on
868     };
869 
870     *outY = ClampToU8(SaturateToInt(
871         Round((kRGBToYUVBT601FullRange[0] * r) + (kRGBToYUVBT601FullRange[1] * g) +
872               (kRGBToYUVBT601FullRange[2] * b) + (kRGBToYUVBT601FullRange[4] * 255))));
873     *outU = ClampToU8(SaturateToInt(
874         Round((kRGBToYUVBT601FullRange[5] * r) + (kRGBToYUVBT601FullRange[6] * g) +
875               (kRGBToYUVBT601FullRange[7] * b) + (kRGBToYUVBT601FullRange[9] * 255))));
876     *outV = ClampToU8(SaturateToInt(
877         Round((kRGBToYUVBT601FullRange[10] * r) + (kRGBToYUVBT601FullRange[11] * g) +
878               (kRGBToYUVBT601FullRange[12] * b) + (kRGBToYUVBT601FullRange[14] * 255))));
879 }
880 
881 }  // namespace tests
882 }  // namespace gfxstream
883