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