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