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