/* * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "GraphicsDetectorVkPrecisionQualifiersOnYuvSamplers.h" #include #include "Image.h" #include "Vulkan.h" namespace gfxstream { namespace { // kBlitTextureVert #include "shaders/blit_texture.vert.inl" // kBlitTextureFrag #include "shaders/blit_texture.frag.inl" // kBlitTextureLowpFrag #include "shaders/blit_texture_lowp.frag.inl" // kBlitTextureMediumpFrag #include "shaders/blit_texture_mediump.frag.inl" // kBlitTextureHighpFrag #include "shaders/blit_texture_highp.frag.inl" gfxstream::expected CanHandlePrecisionQualifierWithYuvSampler( const std::vector& blitVertShaderSpirv, const std::vector& blitFragShaderSpirv) { auto vk = VK_EXPECT(Vk::Load( /*instance_extensions=*/{}, /*instance_layers=*/{}, /*device_extensions=*/ { VK_KHR_SAMPLER_YCBCR_CONVERSION_EXTENSION_NAME, })); uint32_t textureWidth = 32; uint32_t textureHeight = 32; RGBAImage textureDataRgba = FillWithColor(textureWidth, textureHeight, /*red=*/0xFF, /*green=*/0x00, /*blue=*/0x00, /*alpha=*/0xFF); YUV420Image textureDataYuv = ConvertRGBA8888ToYUV420(textureDataRgba); #if 0 // Debugging can be easier with a larger image with more details. textureDataYuv = GFXSTREAM_EXPECT(LoadYUV420FromBitmapFile("custom.bmp")); #endif Vk::YuvImageWithMemory sampledImage = VK_EXPECT(vk.CreateYuvImage( textureWidth, textureHeight, vkhpp::ImageUsageFlagBits::eSampled | vkhpp::ImageUsageFlagBits::eTransferDst | vkhpp::ImageUsageFlagBits::eTransferSrc, vkhpp::MemoryPropertyFlagBits::eDeviceLocal, vkhpp::ImageLayout::eTransferDstOptimal)); VK_EXPECT_RESULT(vk.LoadYuvImage(sampledImage.image, textureWidth, textureHeight, textureDataYuv.y, textureDataYuv.u, textureDataYuv.v, /*currentLayout=*/vkhpp::ImageLayout::eTransferDstOptimal, /*returnedLayout=*/vkhpp::ImageLayout::eShaderReadOnlyOptimal)); Vk::FramebufferWithAttachments framebuffer = VK_EXPECT(vk.CreateFramebuffer(textureWidth, textureHeight, /*colorAttachmentFormat=*/vkhpp::Format::eR8G8B8A8Unorm)); const vkhpp::Sampler descriptorSet0Binding0Sampler = *sampledImage.imageSampler; const std::vector descriptorSet0Bindings = { vkhpp::DescriptorSetLayoutBinding{ .binding = 0, .descriptorType = vkhpp::DescriptorType::eCombinedImageSampler, .descriptorCount = 1, .stageFlags = vkhpp::ShaderStageFlagBits::eFragment, .pImmutableSamplers = &descriptorSet0Binding0Sampler, }, }; const vkhpp::DescriptorSetLayoutCreateInfo descriptorSet0CreateInfo = { .bindingCount = static_cast(descriptorSet0Bindings.size()), .pBindings = descriptorSet0Bindings.data(), }; auto descriptorSet0Layout = VK_EXPECT_RV(vk.device().createDescriptorSetLayoutUnique(descriptorSet0CreateInfo)); const std::vector descriptorPoolSizes = { vkhpp::DescriptorPoolSize{ .type = vkhpp::DescriptorType::eCombinedImageSampler, .descriptorCount = 1, }, }; const vkhpp::DescriptorPoolCreateInfo descriptorPoolCreateInfo = { .flags = vkhpp::DescriptorPoolCreateFlagBits::eFreeDescriptorSet, .maxSets = 1, .poolSizeCount = static_cast(descriptorPoolSizes.size()), .pPoolSizes = descriptorPoolSizes.data(), }; auto descriptorSet0Pool = VK_EXPECT_RV(vk.device().createDescriptorPoolUnique(descriptorPoolCreateInfo)); const vkhpp::DescriptorSetLayout descriptorSet0LayoutHandle = *descriptorSet0Layout; const vkhpp::DescriptorSetAllocateInfo descriptorSet0AllocateInfo = { .descriptorPool = *descriptorSet0Pool, .descriptorSetCount = 1, .pSetLayouts = &descriptorSet0LayoutHandle, }; auto descriptorSets = VK_EXPECT_RV(vk.device().allocateDescriptorSetsUnique(descriptorSet0AllocateInfo)); auto descriptorSet0(std::move(descriptorSets[0])); const vkhpp::DescriptorImageInfo descriptorSet0Binding0ImageInfo = { .sampler = VK_NULL_HANDLE, .imageView = *sampledImage.imageView, .imageLayout = vkhpp::ImageLayout::eShaderReadOnlyOptimal, }; const std::vector descriptorSet0Writes = { vkhpp::WriteDescriptorSet{ .dstSet = *descriptorSet0, .dstBinding = 0, .dstArrayElement = 0, .descriptorCount = 1, .descriptorType = vkhpp::DescriptorType::eCombinedImageSampler, .pImageInfo = &descriptorSet0Binding0ImageInfo, .pBufferInfo = nullptr, .pTexelBufferView = nullptr, }, }; vk.device().updateDescriptorSets(descriptorSet0Writes, {}); const std::vector pipelineLayoutDescriptorSetLayouts = { *descriptorSet0Layout, }; const vkhpp::PipelineLayoutCreateInfo pipelineLayoutCreateInfo = { .setLayoutCount = static_cast(pipelineLayoutDescriptorSetLayouts.size()), .pSetLayouts = pipelineLayoutDescriptorSetLayouts.data(), }; auto pipelineLayout = VK_EXPECT_RV(vk.device().createPipelineLayoutUnique(pipelineLayoutCreateInfo)); const vkhpp::ShaderModuleCreateInfo vertShaderCreateInfo = { .codeSize = static_cast(blitVertShaderSpirv.size()), .pCode = reinterpret_cast(blitVertShaderSpirv.data()), }; auto vertShaderModule = VK_EXPECT_RV(vk.device().createShaderModuleUnique(vertShaderCreateInfo)); const vkhpp::ShaderModuleCreateInfo fragShaderCreateInfo = { .codeSize = static_cast(blitFragShaderSpirv.size()), .pCode = reinterpret_cast(blitFragShaderSpirv.data()), }; auto fragShaderModule = VK_EXPECT_RV(vk.device().createShaderModuleUnique(fragShaderCreateInfo)); const std::vector pipelineStages = { vkhpp::PipelineShaderStageCreateInfo{ .stage = vkhpp::ShaderStageFlagBits::eVertex, .module = *vertShaderModule, .pName = "main", }, vkhpp::PipelineShaderStageCreateInfo{ .stage = vkhpp::ShaderStageFlagBits::eFragment, .module = *fragShaderModule, .pName = "main", }, }; const vkhpp::PipelineVertexInputStateCreateInfo pipelineVertexInputStateCreateInfo = {}; const vkhpp::PipelineInputAssemblyStateCreateInfo pipelineInputAssemblyStateCreateInfo = { .topology = vkhpp::PrimitiveTopology::eTriangleStrip, }; const vkhpp::PipelineViewportStateCreateInfo pipelineViewportStateCreateInfo = { .viewportCount = 1, .pViewports = nullptr, .scissorCount = 1, .pScissors = nullptr, }; const vkhpp::PipelineRasterizationStateCreateInfo pipelineRasterStateCreateInfo = { .depthClampEnable = VK_FALSE, .rasterizerDiscardEnable = VK_FALSE, .polygonMode = vkhpp::PolygonMode::eFill, .cullMode = {}, .frontFace = vkhpp::FrontFace::eCounterClockwise, .depthBiasEnable = VK_FALSE, .depthBiasConstantFactor = 0.0f, .depthBiasClamp = 0.0f, .depthBiasSlopeFactor = 0.0f, .lineWidth = 1.0f, }; const vkhpp::SampleMask pipelineSampleMask = 65535; const vkhpp::PipelineMultisampleStateCreateInfo pipelineMultisampleStateCreateInfo = { .rasterizationSamples = vkhpp::SampleCountFlagBits::e1, .sampleShadingEnable = VK_FALSE, .minSampleShading = 1.0f, .pSampleMask = &pipelineSampleMask, .alphaToCoverageEnable = VK_FALSE, .alphaToOneEnable = VK_FALSE, }; const vkhpp::PipelineDepthStencilStateCreateInfo pipelineDepthStencilStateCreateInfo = { .depthTestEnable = VK_FALSE, .depthWriteEnable = VK_FALSE, .depthCompareOp = vkhpp::CompareOp::eLess, .depthBoundsTestEnable = VK_FALSE, .stencilTestEnable = VK_FALSE, .front = { .failOp = vkhpp::StencilOp::eKeep, .passOp = vkhpp::StencilOp::eKeep, .depthFailOp = vkhpp::StencilOp::eKeep, .compareOp = vkhpp::CompareOp::eAlways, .compareMask = 0, .writeMask = 0, .reference = 0, }, .back = { .failOp = vkhpp::StencilOp::eKeep, .passOp = vkhpp::StencilOp::eKeep, .depthFailOp = vkhpp::StencilOp::eKeep, .compareOp = vkhpp::CompareOp::eAlways, .compareMask = 0, .writeMask = 0, .reference = 0, }, .minDepthBounds = 0.0f, .maxDepthBounds = 0.0f, }; const std::vector pipelineColorBlendAttachments = { vkhpp::PipelineColorBlendAttachmentState{ .blendEnable = VK_FALSE, .srcColorBlendFactor = vkhpp::BlendFactor::eOne, .dstColorBlendFactor = vkhpp::BlendFactor::eOneMinusSrcAlpha, .colorBlendOp = vkhpp::BlendOp::eAdd, .srcAlphaBlendFactor = vkhpp::BlendFactor::eOne, .dstAlphaBlendFactor = vkhpp::BlendFactor::eOneMinusSrcAlpha, .alphaBlendOp = vkhpp::BlendOp::eAdd, .colorWriteMask = vkhpp::ColorComponentFlagBits::eR | vkhpp::ColorComponentFlagBits::eG | vkhpp::ColorComponentFlagBits::eB | vkhpp::ColorComponentFlagBits::eA, }, }; const vkhpp::PipelineColorBlendStateCreateInfo pipelineColorBlendStateCreateInfo = { .logicOpEnable = VK_FALSE, .logicOp = vkhpp::LogicOp::eCopy, .attachmentCount = static_cast(pipelineColorBlendAttachments.size()), .pAttachments = pipelineColorBlendAttachments.data(), .blendConstants = {{ 0.0f, 0.0f, 0.0f, 0.0f, }}, }; const std::vector pipelineDynamicStates = { vkhpp::DynamicState::eViewport, vkhpp::DynamicState::eScissor, }; const vkhpp::PipelineDynamicStateCreateInfo pipelineDynamicStateCreateInfo = { .dynamicStateCount = static_cast(pipelineDynamicStates.size()), .pDynamicStates = pipelineDynamicStates.data(), }; const vkhpp::GraphicsPipelineCreateInfo pipelineCreateInfo = { .stageCount = static_cast(pipelineStages.size()), .pStages = pipelineStages.data(), .pVertexInputState = &pipelineVertexInputStateCreateInfo, .pInputAssemblyState = &pipelineInputAssemblyStateCreateInfo, .pTessellationState = nullptr, .pViewportState = &pipelineViewportStateCreateInfo, .pRasterizationState = &pipelineRasterStateCreateInfo, .pMultisampleState = &pipelineMultisampleStateCreateInfo, .pDepthStencilState = &pipelineDepthStencilStateCreateInfo, .pColorBlendState = &pipelineColorBlendStateCreateInfo, .pDynamicState = &pipelineDynamicStateCreateInfo, .layout = *pipelineLayout, .renderPass = *framebuffer.renderpass, .subpass = 0, .basePipelineHandle = VK_NULL_HANDLE, .basePipelineIndex = 0, }; auto pipeline = VK_EXPECT_RV(vk.device().createGraphicsPipelineUnique({}, pipelineCreateInfo)); VK_EXPECT_RESULT(vk.DoCommandsImmediate( [&](vkhpp::UniqueCommandBuffer& cmd) { const std::vector renderPassBeginClearValues = { vkhpp::ClearValue{ .color = { .float32 = {{ 1.0f, 0.0f, 0.0f, 1.0f, }}, }, }, }; const vkhpp::RenderPassBeginInfo renderPassBeginInfo = { .renderPass = *framebuffer.renderpass, .framebuffer = *framebuffer.framebuffer, .renderArea = { .offset = { .x = 0, .y = 0, }, .extent = { .width = textureWidth, .height = textureHeight, }, }, .clearValueCount = static_cast(renderPassBeginClearValues.size()), .pClearValues = renderPassBeginClearValues.data(), }; cmd->beginRenderPass(renderPassBeginInfo, vkhpp::SubpassContents::eInline); cmd->bindPipeline(vkhpp::PipelineBindPoint::eGraphics, *pipeline); cmd->bindDescriptorSets(vkhpp::PipelineBindPoint::eGraphics, *pipelineLayout, /*firstSet=*/0, {*descriptorSet0}, /*dynamicOffsets=*/{}); const vkhpp::Viewport viewport = { .x = 0.0f, .y = 0.0f, .width = static_cast(textureWidth), .height = static_cast(textureHeight), .minDepth = 0.0f, .maxDepth = 1.0f, }; cmd->setViewport(0, {viewport}); const vkhpp::Rect2D scissor = { .offset = { .x = 0, .y = 0, }, .extent = { .width = textureWidth, .height = textureHeight, }, }; cmd->setScissor(0, {scissor}); cmd->draw(4, 1, 0, 0); cmd->endRenderPass(); return vkhpp::Result::eSuccess; })); const std::vector renderedPixels = VK_EXPECT(vk.DownloadImage( textureWidth, textureHeight, framebuffer.colorAttachment->image, vkhpp::ImageLayout::eColorAttachmentOptimal, vkhpp::ImageLayout::eColorAttachmentOptimal)); #if 0 SaveRGBAToBitmapFile(textureWidth, textureHeight, renderedPixels.data(), "rendered.bmp"); #endif const RGBAImage actual = { .width = textureWidth, .height = textureHeight, .pixels = std::move(renderedPixels), }; auto result = CompareImages(textureDataRgba, actual); return result.ok(); } } // namespace gfxstream::expected PopulateVulkanPrecisionQualifiersOnYuvSamplersQuirk( ::gfxstream::proto::GraphicsAvailability* availability) { struct ShaderCombo { std::string name; const std::vector& vert; const std::vector& frag; }; const std::vector combos = { ShaderCombo{ .name = "sampler2D has no precision qualifier", .vert = kBlitTextureVert, .frag = kBlitTextureFrag, }, ShaderCombo{ .name = "sampler2D has a 'lowp' precision qualifier", .vert = kBlitTextureVert, .frag = kBlitTextureLowpFrag, }, ShaderCombo{ .name = "sampler2D has a 'mediump' precision qualifier", .vert = kBlitTextureVert, .frag = kBlitTextureMediumpFrag, }, ShaderCombo{ .name = "sampler2D has a 'highp' precision qualifier", .vert = kBlitTextureVert, .frag = kBlitTextureHighpFrag, }, }; bool anyTestFailed = false; for (const auto& combo : combos) { auto result = CanHandlePrecisionQualifierWithYuvSampler(combo.vert, combo.frag); if (!result.ok()) { // Failed to run to completion. return gfxstream::unexpected(vkhpp::to_string(result.error())); } const bool passedTest = result.value(); if (!passedTest) { // Ran to completion but had bad value. anyTestFailed = true; break; } } // TODO: Run this test per device. availability->mutable_vulkan() ->mutable_physical_devices(0) ->mutable_quirks() ->set_has_issue_with_precision_qualifiers_on_yuv_samplers(anyTestFailed); return Ok{}; } } // namespace gfxstream