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