1 /*
2 * Copyright 2019 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8 #include "include/core/SkTypes.h"
9
10 #if defined(SK_GANESH) && defined(SK_VULKAN)
11 #include "include/core/SkAlphaType.h"
12 #include "include/core/SkCanvas.h"
13 #include "include/core/SkColorSpace.h"
14 #include "include/core/SkColorType.h"
15 #include "include/core/SkImage.h"
16 #include "include/core/SkImageInfo.h"
17 #include "include/core/SkRefCnt.h"
18 #include "include/core/SkSurface.h"
19 #include "include/core/SkTypes.h"
20 #include "include/gpu/GpuTypes.h"
21 #include "include/gpu/GrBackendSurface.h"
22 #include "include/gpu/GrDirectContext.h"
23 #include "include/gpu/GrTypes.h"
24 #include "tests/CtsEnforcement.h"
25 #include "tests/Test.h"
26 #include "tools/gpu/vk/VkTestHelper.h"
27 #include "tools/gpu/vk/VkYcbcrSamplerHelper.h"
28
29 #include <vulkan/vulkan_core.h>
30
31 #include <cmath>
32 #include <cstddef>
33 #include <cstdint>
34 #include <vector>
35
36 struct GrContextOptions;
37 const size_t kImageWidth = 8;
38 const size_t kImageHeight = 8;
39
round_and_clamp(float x)40 static int round_and_clamp(float x) {
41 int r = static_cast<int>(round(x));
42 if (r > 255) return 255;
43 if (r < 0) return 0;
44 return r;
45 }
46
DEF_GANESH_TEST_FOR_VULKAN_CONTEXT(VkYCbcrSampler_DrawImageWithYcbcrSampler,reporter,context_info,CtsEnforcement::kApiLevel_T)47 DEF_GANESH_TEST_FOR_VULKAN_CONTEXT(VkYCbcrSampler_DrawImageWithYcbcrSampler,
48 reporter,
49 context_info,
50 CtsEnforcement::kApiLevel_T) {
51 VkTestHelper testHelper(false);
52 if (!testHelper.init()) {
53 ERRORF(reporter, "VkTestHelper initialization failed.");
54 return;
55 }
56
57 VkYcbcrSamplerHelper ycbcrHelper(testHelper.directContext());
58 if (!ycbcrHelper.isYCbCrSupported()) {
59 return;
60 }
61
62 if (!ycbcrHelper.createBackendTexture(kImageWidth, kImageHeight)) {
63 ERRORF(reporter, "Failed to create I420 backend texture");
64 return;
65 }
66
67 sk_sp<SkImage> srcImage = SkImage::MakeFromTexture(testHelper.directContext(),
68 ycbcrHelper.backendTexture(),
69 kTopLeft_GrSurfaceOrigin,
70 kRGB_888x_SkColorType,
71 kPremul_SkAlphaType,
72 nullptr);
73 if (!srcImage) {
74 ERRORF(reporter, "Failed to create I420 image");
75 return;
76 }
77
78 sk_sp<SkSurface> surface = SkSurface::MakeRenderTarget(
79 testHelper.directContext(),
80 skgpu::Budgeted::kNo,
81 SkImageInfo::Make(kImageWidth, kImageHeight, kN32_SkColorType, kPremul_SkAlphaType));
82 if (!surface) {
83 ERRORF(reporter, "Failed to create target SkSurface");
84 return;
85 }
86 surface->getCanvas()->drawImage(srcImage, 0, 0);
87 surface->flushAndSubmit();
88
89 std::vector<uint8_t> readbackData(kImageWidth * kImageHeight * 4);
90 if (!surface->readPixels(SkImageInfo::Make(kImageWidth, kImageHeight, kRGBA_8888_SkColorType,
91 kOpaque_SkAlphaType),
92 readbackData.data(), kImageWidth * 4, 0, 0)) {
93 ERRORF(reporter, "Readback failed");
94 return;
95 }
96
97 // Allow resulting color to be off by 1 in each channel as some Vulkan implementations do not
98 // round YCbCr sampler result properly.
99 const int kColorTolerance = 1;
100
101 // Verify results only for pixels with even coordinates, since others use
102 // interpolated U & V channels.
103 for (size_t y = 0; y < kImageHeight; y += 2) {
104 for (size_t x = 0; x < kImageWidth; x += 2) {
105 auto y2 = VkYcbcrSamplerHelper::GetExpectedY(x, y, kImageWidth, kImageHeight);
106 auto [u, v] = VkYcbcrSamplerHelper::GetExpectedUV(x, y, kImageWidth, kImageHeight);
107
108 // createI420Image() initializes the image with VK_SAMPLER_YCBCR_RANGE_ITU_NARROW.
109 float yChannel = (static_cast<float>(y2) - 16.0) / 219.0;
110 float uChannel = (static_cast<float>(u) - 128.0) / 224.0;
111 float vChannel = (static_cast<float>(v) - 128.0) / 224.0;
112
113 // BR.709 conversion as specified in
114 // https://www.khronos.org/registry/DataFormat/specs/1.2/dataformat.1.2.html#MODEL_YUV
115 int expectedR = round_and_clamp((yChannel + 1.5748f * vChannel) * 255.0);
116 int expectedG = round_and_clamp((yChannel - 0.13397432f / 0.7152f * uChannel -
117 0.33480248f / 0.7152f * vChannel) *
118 255.0);
119 int expectedB = round_and_clamp((yChannel + 1.8556f * uChannel) * 255.0);
120
121 int r = readbackData[(y * kImageWidth + x) * 4];
122 if (abs(r - expectedR) > kColorTolerance) {
123 ERRORF(reporter, "R should be %d, but is %d at (%zu, %zu)", expectedR, r, x, y);
124 }
125
126 int g = readbackData[(y * kImageWidth + x) * 4 + 1];
127 if (abs(g - expectedG) > kColorTolerance) {
128 ERRORF(reporter, "G should be %d, but is %d at (%zu, %zu)", expectedG, g, x, y);
129 }
130
131 int b = readbackData[(y * kImageWidth + x) * 4 + 2];
132 if (abs(b - expectedB) > kColorTolerance) {
133 ERRORF(reporter, "B should be %d, but is %d at (%zu, %zu)", expectedB, b, x, y);
134 }
135 }
136 }
137 }
138
139 // Verifies that it's not possible to allocate Ycbcr texture directly.
DEF_GANESH_TEST_FOR_VULKAN_CONTEXT(VkYCbcrSampler_NoYcbcrSurface,reporter,context_info,CtsEnforcement::kApiLevel_T)140 DEF_GANESH_TEST_FOR_VULKAN_CONTEXT(VkYCbcrSampler_NoYcbcrSurface,
141 reporter,
142 context_info,
143 CtsEnforcement::kApiLevel_T) {
144 VkTestHelper testHelper(false);
145 if (!testHelper.init()) {
146 ERRORF(reporter, "VkTestHelper initialization failed.");
147 return;
148 }
149
150 VkYcbcrSamplerHelper ycbcrHelper(testHelper.directContext());
151 if (!ycbcrHelper.isYCbCrSupported()) {
152 return;
153 }
154
155 GrBackendTexture texture = testHelper.directContext()->createBackendTexture(
156 kImageWidth, kImageHeight, GrBackendFormat::MakeVk(VK_FORMAT_G8_B8R8_2PLANE_420_UNORM),
157 GrMipmapped::kNo, GrRenderable::kNo, GrProtected::kNo);
158 if (texture.isValid()) {
159 ERRORF(reporter,
160 "GrDirectContext::createBackendTexture() didn't fail as expected for Ycbcr format.");
161 }
162 }
163
164 #endif // defined(SK_GANESH) && defined(SK_VULKAN)
165