1 /*
2 * Copyright (C) 2023 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "GraphicsDetectorVkPrecisionQualifiersOnYuvSamplers.h"
18
19 #include <vector>
20
21 #include "Image.h"
22 #include "Vulkan.h"
23
24 namespace gfxstream {
25 namespace {
26
27 // kBlitTextureVert
28 #include "shaders/blit_texture.vert.inl"
29 // kBlitTextureFrag
30 #include "shaders/blit_texture.frag.inl"
31 // kBlitTextureLowpFrag
32 #include "shaders/blit_texture_lowp.frag.inl"
33 // kBlitTextureMediumpFrag
34 #include "shaders/blit_texture_mediump.frag.inl"
35 // kBlitTextureHighpFrag
36 #include "shaders/blit_texture_highp.frag.inl"
37
38 gfxstream::expected<bool, vkhpp::Result>
CanHandlePrecisionQualifierWithYuvSampler(const std::vector<uint8_t> & blitVertShaderSpirv,const std::vector<uint8_t> & blitFragShaderSpirv)39 CanHandlePrecisionQualifierWithYuvSampler(
40 const std::vector<uint8_t>& blitVertShaderSpirv,
41 const std::vector<uint8_t>& blitFragShaderSpirv) {
42 auto vk = VK_EXPECT(Vk::Load(
43 /*instance_extensions=*/{},
44 /*instance_layers=*/{},
45 /*device_extensions=*/
46 {
47 VK_KHR_SAMPLER_YCBCR_CONVERSION_EXTENSION_NAME,
48 }));
49
50 uint32_t textureWidth = 32;
51 uint32_t textureHeight = 32;
52 RGBAImage textureDataRgba = FillWithColor(textureWidth,
53 textureHeight,
54 /*red=*/0xFF,
55 /*green=*/0x00,
56 /*blue=*/0x00,
57 /*alpha=*/0xFF);
58
59 YUV420Image textureDataYuv = ConvertRGBA8888ToYUV420(textureDataRgba);
60 #if 0
61 // Debugging can be easier with a larger image with more details.
62 textureDataYuv = GFXSTREAM_EXPECT(LoadYUV420FromBitmapFile("custom.bmp"));
63 #endif
64
65 Vk::YuvImageWithMemory sampledImage = VK_EXPECT(vk.CreateYuvImage(
66 textureWidth,
67 textureHeight,
68 vkhpp::ImageUsageFlagBits::eSampled |
69 vkhpp::ImageUsageFlagBits::eTransferDst |
70 vkhpp::ImageUsageFlagBits::eTransferSrc,
71 vkhpp::MemoryPropertyFlagBits::eDeviceLocal,
72 vkhpp::ImageLayout::eTransferDstOptimal));
73
74 VK_EXPECT_RESULT(vk.LoadYuvImage(sampledImage.image,
75 textureWidth,
76 textureHeight,
77 textureDataYuv.y,
78 textureDataYuv.u,
79 textureDataYuv.v,
80 /*currentLayout=*/vkhpp::ImageLayout::eTransferDstOptimal,
81 /*returnedLayout=*/vkhpp::ImageLayout::eShaderReadOnlyOptimal));
82
83 Vk::FramebufferWithAttachments framebuffer =
84 VK_EXPECT(vk.CreateFramebuffer(textureWidth,
85 textureHeight,
86 /*colorAttachmentFormat=*/vkhpp::Format::eR8G8B8A8Unorm));
87
88 const vkhpp::Sampler descriptorSet0Binding0Sampler = *sampledImage.imageSampler;
89 const std::vector<vkhpp::DescriptorSetLayoutBinding> descriptorSet0Bindings =
90 {
91 vkhpp::DescriptorSetLayoutBinding{
92 .binding = 0,
93 .descriptorType = vkhpp::DescriptorType::eCombinedImageSampler,
94 .descriptorCount = 1,
95 .stageFlags = vkhpp::ShaderStageFlagBits::eFragment,
96 .pImmutableSamplers = &descriptorSet0Binding0Sampler,
97 },
98 };
99 const vkhpp::DescriptorSetLayoutCreateInfo descriptorSet0CreateInfo = {
100 .bindingCount = static_cast<uint32_t>(descriptorSet0Bindings.size()),
101 .pBindings = descriptorSet0Bindings.data(),
102 };
103 auto descriptorSet0Layout = VK_EXPECT_RV(vk.device().createDescriptorSetLayoutUnique(descriptorSet0CreateInfo));
104
105 const std::vector<vkhpp::DescriptorPoolSize> descriptorPoolSizes = {
106 vkhpp::DescriptorPoolSize{
107 .type = vkhpp::DescriptorType::eCombinedImageSampler,
108 .descriptorCount = 1,
109 },
110 };
111 const vkhpp::DescriptorPoolCreateInfo descriptorPoolCreateInfo = {
112 .flags = vkhpp::DescriptorPoolCreateFlagBits::eFreeDescriptorSet,
113 .maxSets = 1,
114 .poolSizeCount = static_cast<uint32_t>(descriptorPoolSizes.size()),
115 .pPoolSizes = descriptorPoolSizes.data(),
116 };
117 auto descriptorSet0Pool = VK_EXPECT_RV(vk.device().createDescriptorPoolUnique(descriptorPoolCreateInfo));
118
119 const vkhpp::DescriptorSetLayout descriptorSet0LayoutHandle = *descriptorSet0Layout;
120 const vkhpp::DescriptorSetAllocateInfo descriptorSet0AllocateInfo = {
121 .descriptorPool = *descriptorSet0Pool,
122 .descriptorSetCount = 1,
123 .pSetLayouts = &descriptorSet0LayoutHandle,
124 };
125 auto descriptorSets = VK_EXPECT_RV(vk.device().allocateDescriptorSetsUnique(descriptorSet0AllocateInfo));
126 auto descriptorSet0(std::move(descriptorSets[0]));
127
128 const vkhpp::DescriptorImageInfo descriptorSet0Binding0ImageInfo = {
129 .sampler = VK_NULL_HANDLE,
130 .imageView = *sampledImage.imageView,
131 .imageLayout = vkhpp::ImageLayout::eShaderReadOnlyOptimal,
132 };
133 const std::vector<vkhpp::WriteDescriptorSet> descriptorSet0Writes = {
134 vkhpp::WriteDescriptorSet{
135 .dstSet = *descriptorSet0,
136 .dstBinding = 0,
137 .dstArrayElement = 0,
138 .descriptorCount = 1,
139 .descriptorType = vkhpp::DescriptorType::eCombinedImageSampler,
140 .pImageInfo = &descriptorSet0Binding0ImageInfo,
141 .pBufferInfo = nullptr,
142 .pTexelBufferView = nullptr,
143 },
144 };
145 vk.device().updateDescriptorSets(descriptorSet0Writes, {});
146
147 const std::vector<vkhpp::DescriptorSetLayout> pipelineLayoutDescriptorSetLayouts = {
148 *descriptorSet0Layout,
149 };
150 const vkhpp::PipelineLayoutCreateInfo pipelineLayoutCreateInfo = {
151 .setLayoutCount = static_cast<uint32_t>(pipelineLayoutDescriptorSetLayouts.size()),
152 .pSetLayouts = pipelineLayoutDescriptorSetLayouts.data(),
153 };
154 auto pipelineLayout = VK_EXPECT_RV(vk.device().createPipelineLayoutUnique(pipelineLayoutCreateInfo));
155
156 const vkhpp::ShaderModuleCreateInfo vertShaderCreateInfo = {
157 .codeSize = static_cast<uint32_t>(blitVertShaderSpirv.size()),
158 .pCode = reinterpret_cast<const uint32_t*>(blitVertShaderSpirv.data()),
159 };
160 auto vertShaderModule = VK_EXPECT_RV(vk.device().createShaderModuleUnique(vertShaderCreateInfo));
161
162 const vkhpp::ShaderModuleCreateInfo fragShaderCreateInfo = {
163 .codeSize = static_cast<uint32_t>(blitFragShaderSpirv.size()),
164 .pCode = reinterpret_cast<const uint32_t*>(blitFragShaderSpirv.data()),
165 };
166 auto fragShaderModule = VK_EXPECT_RV(vk.device().createShaderModuleUnique(fragShaderCreateInfo));
167
168 const std::vector<vkhpp::PipelineShaderStageCreateInfo> pipelineStages = {
169 vkhpp::PipelineShaderStageCreateInfo{
170 .stage = vkhpp::ShaderStageFlagBits::eVertex,
171 .module = *vertShaderModule,
172 .pName = "main",
173 },
174 vkhpp::PipelineShaderStageCreateInfo{
175 .stage = vkhpp::ShaderStageFlagBits::eFragment,
176 .module = *fragShaderModule,
177 .pName = "main",
178 },
179 };
180 const vkhpp::PipelineVertexInputStateCreateInfo pipelineVertexInputStateCreateInfo = {};
181 const vkhpp::PipelineInputAssemblyStateCreateInfo pipelineInputAssemblyStateCreateInfo = {
182 .topology = vkhpp::PrimitiveTopology::eTriangleStrip,
183 };
184 const vkhpp::PipelineViewportStateCreateInfo pipelineViewportStateCreateInfo = {
185 .viewportCount = 1,
186 .pViewports = nullptr,
187 .scissorCount = 1,
188 .pScissors = nullptr,
189 };
190 const vkhpp::PipelineRasterizationStateCreateInfo pipelineRasterStateCreateInfo = {
191 .depthClampEnable = VK_FALSE,
192 .rasterizerDiscardEnable = VK_FALSE,
193 .polygonMode = vkhpp::PolygonMode::eFill,
194 .cullMode = {},
195 .frontFace = vkhpp::FrontFace::eCounterClockwise,
196 .depthBiasEnable = VK_FALSE,
197 .depthBiasConstantFactor = 0.0f,
198 .depthBiasClamp = 0.0f,
199 .depthBiasSlopeFactor = 0.0f,
200 .lineWidth = 1.0f,
201 };
202 const vkhpp::SampleMask pipelineSampleMask = 65535;
203 const vkhpp::PipelineMultisampleStateCreateInfo pipelineMultisampleStateCreateInfo = {
204 .rasterizationSamples = vkhpp::SampleCountFlagBits::e1,
205 .sampleShadingEnable = VK_FALSE,
206 .minSampleShading = 1.0f,
207 .pSampleMask = &pipelineSampleMask,
208 .alphaToCoverageEnable = VK_FALSE,
209 .alphaToOneEnable = VK_FALSE,
210 };
211 const vkhpp::PipelineDepthStencilStateCreateInfo pipelineDepthStencilStateCreateInfo = {
212 .depthTestEnable = VK_FALSE,
213 .depthWriteEnable = VK_FALSE,
214 .depthCompareOp = vkhpp::CompareOp::eLess,
215 .depthBoundsTestEnable = VK_FALSE,
216 .stencilTestEnable = VK_FALSE,
217 .front = {
218 .failOp = vkhpp::StencilOp::eKeep,
219 .passOp = vkhpp::StencilOp::eKeep,
220 .depthFailOp = vkhpp::StencilOp::eKeep,
221 .compareOp = vkhpp::CompareOp::eAlways,
222 .compareMask = 0,
223 .writeMask = 0,
224 .reference = 0,
225 },
226 .back = {
227 .failOp = vkhpp::StencilOp::eKeep,
228 .passOp = vkhpp::StencilOp::eKeep,
229 .depthFailOp = vkhpp::StencilOp::eKeep,
230 .compareOp = vkhpp::CompareOp::eAlways,
231 .compareMask = 0,
232 .writeMask = 0,
233 .reference = 0,
234 },
235 .minDepthBounds = 0.0f,
236 .maxDepthBounds = 0.0f,
237 };
238 const std::vector<vkhpp::PipelineColorBlendAttachmentState> pipelineColorBlendAttachments = {
239 vkhpp::PipelineColorBlendAttachmentState{
240 .blendEnable = VK_FALSE,
241 .srcColorBlendFactor = vkhpp::BlendFactor::eOne,
242 .dstColorBlendFactor = vkhpp::BlendFactor::eOneMinusSrcAlpha,
243 .colorBlendOp = vkhpp::BlendOp::eAdd,
244 .srcAlphaBlendFactor = vkhpp::BlendFactor::eOne,
245 .dstAlphaBlendFactor = vkhpp::BlendFactor::eOneMinusSrcAlpha,
246 .alphaBlendOp = vkhpp::BlendOp::eAdd,
247 .colorWriteMask = vkhpp::ColorComponentFlagBits::eR |
248 vkhpp::ColorComponentFlagBits::eG |
249 vkhpp::ColorComponentFlagBits::eB |
250 vkhpp::ColorComponentFlagBits::eA,
251 },
252 };
253 const vkhpp::PipelineColorBlendStateCreateInfo pipelineColorBlendStateCreateInfo = {
254 .logicOpEnable = VK_FALSE,
255 .logicOp = vkhpp::LogicOp::eCopy,
256 .attachmentCount = static_cast<uint32_t>(pipelineColorBlendAttachments.size()),
257 .pAttachments = pipelineColorBlendAttachments.data(),
258 .blendConstants = {{
259 0.0f,
260 0.0f,
261 0.0f,
262 0.0f,
263 }},
264 };
265 const std::vector<vkhpp::DynamicState> pipelineDynamicStates = {
266 vkhpp::DynamicState::eViewport,
267 vkhpp::DynamicState::eScissor,
268 };
269 const vkhpp::PipelineDynamicStateCreateInfo pipelineDynamicStateCreateInfo = {
270 .dynamicStateCount = static_cast<uint32_t>(pipelineDynamicStates.size()),
271 .pDynamicStates = pipelineDynamicStates.data(),
272 };
273 const vkhpp::GraphicsPipelineCreateInfo pipelineCreateInfo = {
274 .stageCount = static_cast<uint32_t>(pipelineStages.size()),
275 .pStages = pipelineStages.data(),
276 .pVertexInputState = &pipelineVertexInputStateCreateInfo,
277 .pInputAssemblyState = &pipelineInputAssemblyStateCreateInfo,
278 .pTessellationState = nullptr,
279 .pViewportState = &pipelineViewportStateCreateInfo,
280 .pRasterizationState = &pipelineRasterStateCreateInfo,
281 .pMultisampleState = &pipelineMultisampleStateCreateInfo,
282 .pDepthStencilState = &pipelineDepthStencilStateCreateInfo,
283 .pColorBlendState = &pipelineColorBlendStateCreateInfo,
284 .pDynamicState = &pipelineDynamicStateCreateInfo,
285 .layout = *pipelineLayout,
286 .renderPass = *framebuffer.renderpass,
287 .subpass = 0,
288 .basePipelineHandle = VK_NULL_HANDLE,
289 .basePipelineIndex = 0,
290 };
291 auto pipeline = VK_EXPECT_RV(vk.device().createGraphicsPipelineUnique({}, pipelineCreateInfo));
292
293 VK_EXPECT_RESULT(vk.DoCommandsImmediate(
294 [&](vkhpp::UniqueCommandBuffer& cmd) {
295 const std::vector<vkhpp::ClearValue> renderPassBeginClearValues = {
296 vkhpp::ClearValue{
297 .color = {
298 .float32 = {{
299 1.0f,
300 0.0f,
301 0.0f,
302 1.0f,
303 }},
304 },
305 },
306 };
307 const vkhpp::RenderPassBeginInfo renderPassBeginInfo = {
308 .renderPass = *framebuffer.renderpass,
309 .framebuffer = *framebuffer.framebuffer,
310 .renderArea = {
311 .offset = {
312 .x = 0,
313 .y = 0,
314 },
315 .extent = {
316 .width = textureWidth,
317 .height = textureHeight,
318 },
319 },
320 .clearValueCount = static_cast<uint32_t>(renderPassBeginClearValues.size()),
321 .pClearValues = renderPassBeginClearValues.data(),
322 };
323 cmd->beginRenderPass(renderPassBeginInfo, vkhpp::SubpassContents::eInline);
324
325 cmd->bindPipeline(vkhpp::PipelineBindPoint::eGraphics, *pipeline);
326
327 cmd->bindDescriptorSets(vkhpp::PipelineBindPoint::eGraphics,
328 *pipelineLayout,
329 /*firstSet=*/0, {*descriptorSet0},
330 /*dynamicOffsets=*/{});
331
332 const vkhpp::Viewport viewport = {
333 .x = 0.0f,
334 .y = 0.0f,
335 .width = static_cast<float>(textureWidth),
336 .height = static_cast<float>(textureHeight),
337 .minDepth = 0.0f,
338 .maxDepth = 1.0f,
339 };
340 cmd->setViewport(0, {viewport});
341
342 const vkhpp::Rect2D scissor = {
343 .offset = {
344 .x = 0,
345 .y = 0,
346 },
347 .extent = {
348 .width = textureWidth,
349 .height = textureHeight,
350 },
351 };
352 cmd->setScissor(0, {scissor});
353
354 cmd->draw(4, 1, 0, 0);
355
356 cmd->endRenderPass();
357 return vkhpp::Result::eSuccess;
358 }));
359
360 const std::vector<uint8_t> renderedPixels = VK_EXPECT(vk.DownloadImage(
361 textureWidth,
362 textureHeight,
363 framebuffer.colorAttachment->image,
364 vkhpp::ImageLayout::eColorAttachmentOptimal,
365 vkhpp::ImageLayout::eColorAttachmentOptimal));
366
367 #if 0
368 SaveRGBAToBitmapFile(textureWidth,
369 textureHeight,
370 renderedPixels.data(),
371 "rendered.bmp");
372 #endif
373
374 const RGBAImage actual = {
375 .width = textureWidth,
376 .height = textureHeight,
377 .pixels = std::move(renderedPixels),
378 };
379
380 auto result = CompareImages(textureDataRgba, actual);
381 return result.ok();
382 }
383
384 } // namespace
385
386 gfxstream::expected<Ok, std::string>
PopulateVulkanPrecisionQualifiersOnYuvSamplersQuirk(::gfxstream::proto::GraphicsAvailability * availability)387 PopulateVulkanPrecisionQualifiersOnYuvSamplersQuirk(
388 ::gfxstream::proto::GraphicsAvailability* availability) {
389 struct ShaderCombo {
390 std::string name;
391 const std::vector<uint8_t>& vert;
392 const std::vector<uint8_t>& frag;
393 };
394 const std::vector<ShaderCombo> combos = {
395 ShaderCombo{
396 .name = "sampler2D has no precision qualifier",
397 .vert = kBlitTextureVert,
398 .frag = kBlitTextureFrag,
399 },
400 ShaderCombo{
401 .name = "sampler2D has a 'lowp' precision qualifier",
402 .vert = kBlitTextureVert,
403 .frag = kBlitTextureLowpFrag,
404 },
405 ShaderCombo{
406 .name = "sampler2D has a 'mediump' precision qualifier",
407 .vert = kBlitTextureVert,
408 .frag = kBlitTextureMediumpFrag,
409 },
410 ShaderCombo{
411 .name = "sampler2D has a 'highp' precision qualifier",
412 .vert = kBlitTextureVert,
413 .frag = kBlitTextureHighpFrag,
414 },
415 };
416
417 bool anyTestFailed = false;
418 for (const auto& combo : combos) {
419 auto result = CanHandlePrecisionQualifierWithYuvSampler(combo.vert, combo.frag);
420 if (!result.ok()) {
421 // Failed to run to completion.
422 return gfxstream::unexpected(vkhpp::to_string(result.error()));
423 }
424 const bool passedTest = result.value();
425 if (!passedTest) {
426 // Ran to completion but had bad value.
427 anyTestFailed = true;
428 break;
429 }
430 }
431
432 // TODO: Run this test per device.
433 availability->mutable_vulkan()
434 ->mutable_physical_devices(0)
435 ->mutable_quirks()
436 ->set_has_issue_with_precision_qualifiers_on_yuv_samplers(anyTestFailed);
437 return Ok{};
438 }
439
440 } // namespace gfxstream