| // Copyright 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 "GpuDecompressionPipeline.h" |
| |
| #include "host-common/logging.h" |
| #include "vulkan/VkFormatUtils.h" |
| #include "vulkan/emulated_textures/shaders/DecompressionShaders.h" |
| #include "vulkan/vk_enum_string_helper.h" |
| |
| namespace gfxstream { |
| namespace vk { |
| |
| namespace { |
| |
| // Which GPU decoder we use for ASTC textures. |
| // Note: we currently only enable ASTC decompression for native vulkan apps, and we try CPU |
| // decompression first, before falling back to GPU decompression. |
| static AstcDecoder activeAstcDecoder = AstcDecoder::NewRgb; |
| |
| struct ShaderGroup { |
| ShaderData shader1D; |
| ShaderData shader2D; |
| ShaderData shader3D; |
| }; |
| |
| // Helper macro to declare the shader goups |
| #define DECLARE_SHADER_GROUP(Format) \ |
| constexpr ShaderGroup kShader##Format { \ |
| .shader1D = {.code = decompression_shaders::Format##_1D, \ |
| .size = sizeof(decompression_shaders::Format##_1D)}, \ |
| .shader2D = {.code = decompression_shaders::Format##_2D, \ |
| .size = sizeof(decompression_shaders::Format##_2D)}, \ |
| .shader3D = {.code = decompression_shaders::Format##_3D, \ |
| .size = sizeof(decompression_shaders::Format##_3D)}, \ |
| } |
| |
| DECLARE_SHADER_GROUP(Astc); |
| DECLARE_SHADER_GROUP(AstcToRgb); |
| DECLARE_SHADER_GROUP(AstcToBc3); |
| DECLARE_SHADER_GROUP(EacR11Snorm); |
| DECLARE_SHADER_GROUP(EacR11Unorm); |
| DECLARE_SHADER_GROUP(EacRG11Snorm); |
| DECLARE_SHADER_GROUP(EacRG11Unorm); |
| DECLARE_SHADER_GROUP(Etc2RGB8); |
| DECLARE_SHADER_GROUP(Etc2RGBA8); |
| |
| #undef DECLARE_SHADER_GROUP |
| |
| // Returns the group of shaders that can decompress a given format, or null if none is found. |
| const ShaderGroup* getShaderGroup(VkFormat format) { |
| switch (format) { |
| case VK_FORMAT_ASTC_4x4_UNORM_BLOCK: |
| case VK_FORMAT_ASTC_5x4_UNORM_BLOCK: |
| case VK_FORMAT_ASTC_5x5_UNORM_BLOCK: |
| case VK_FORMAT_ASTC_6x5_UNORM_BLOCK: |
| case VK_FORMAT_ASTC_6x6_UNORM_BLOCK: |
| case VK_FORMAT_ASTC_8x5_UNORM_BLOCK: |
| case VK_FORMAT_ASTC_8x6_UNORM_BLOCK: |
| case VK_FORMAT_ASTC_8x8_UNORM_BLOCK: |
| case VK_FORMAT_ASTC_10x5_UNORM_BLOCK: |
| case VK_FORMAT_ASTC_10x6_UNORM_BLOCK: |
| case VK_FORMAT_ASTC_10x8_UNORM_BLOCK: |
| case VK_FORMAT_ASTC_10x10_UNORM_BLOCK: |
| case VK_FORMAT_ASTC_12x10_UNORM_BLOCK: |
| case VK_FORMAT_ASTC_12x12_UNORM_BLOCK: |
| case VK_FORMAT_ASTC_4x4_SRGB_BLOCK: |
| case VK_FORMAT_ASTC_5x4_SRGB_BLOCK: |
| case VK_FORMAT_ASTC_5x5_SRGB_BLOCK: |
| case VK_FORMAT_ASTC_6x5_SRGB_BLOCK: |
| case VK_FORMAT_ASTC_6x6_SRGB_BLOCK: |
| case VK_FORMAT_ASTC_8x5_SRGB_BLOCK: |
| case VK_FORMAT_ASTC_8x6_SRGB_BLOCK: |
| case VK_FORMAT_ASTC_8x8_SRGB_BLOCK: |
| case VK_FORMAT_ASTC_10x5_SRGB_BLOCK: |
| case VK_FORMAT_ASTC_10x6_SRGB_BLOCK: |
| case VK_FORMAT_ASTC_10x8_SRGB_BLOCK: |
| case VK_FORMAT_ASTC_10x10_SRGB_BLOCK: |
| case VK_FORMAT_ASTC_12x10_SRGB_BLOCK: |
| case VK_FORMAT_ASTC_12x12_SRGB_BLOCK: |
| switch (activeAstcDecoder) { |
| case AstcDecoder::Old: |
| return &kShaderAstc; |
| case AstcDecoder::NewRgb: |
| return &kShaderAstcToRgb; |
| case AstcDecoder::NewBc3: |
| return &kShaderAstcToBc3; |
| } |
| case VK_FORMAT_EAC_R11_SNORM_BLOCK: |
| return &kShaderEacR11Snorm; |
| |
| case VK_FORMAT_EAC_R11_UNORM_BLOCK: |
| return &kShaderEacR11Unorm; |
| |
| case VK_FORMAT_EAC_R11G11_SNORM_BLOCK: |
| return &kShaderEacRG11Snorm; |
| |
| case VK_FORMAT_EAC_R11G11_UNORM_BLOCK: |
| return &kShaderEacRG11Unorm; |
| |
| case VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK: |
| case VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK: |
| case VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK: |
| case VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK: |
| return &kShaderEtc2RGB8; |
| |
| case VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK: |
| case VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK: |
| return &kShaderEtc2RGBA8; |
| |
| default: |
| return nullptr; |
| } |
| } |
| |
| // Returns the shader that can decompress a given image format and type |
| const ShaderData* getDecompressionShader(VkFormat format, VkImageType imageType) { |
| const ShaderGroup* group = getShaderGroup(format); |
| if (!group) return nullptr; |
| |
| switch (imageType) { |
| case VK_IMAGE_TYPE_1D: |
| return &group->shader1D; |
| case VK_IMAGE_TYPE_2D: |
| return &group->shader2D; |
| case VK_IMAGE_TYPE_3D: |
| return &group->shader3D; |
| default: |
| return nullptr; |
| } |
| } |
| |
| const char* string_AstcDecoder(AstcDecoder decoder) { |
| switch (decoder) { |
| case AstcDecoder::Old: |
| return "Old"; |
| case AstcDecoder::NewRgb: |
| return "NewRgb"; |
| case AstcDecoder::NewBc3: |
| return "NewBc3"; |
| } |
| return "Unknown"; |
| } |
| |
| } // namespace |
| |
| // static |
| std::unique_ptr<GpuDecompressionPipeline> GpuDecompressionPipeline::create( |
| VulkanDispatch* vk, VkDevice device, VkFormat compressedFormat, |
| VkImageType imageType, VkDescriptorSetLayout descriptorSetLayout, |
| VkPipelineLayout pipelineLayout) { |
| auto pipeline = std::unique_ptr<GpuDecompressionPipeline>(new GpuDecompressionPipeline( |
| vk, device, compressedFormat, imageType, descriptorSetLayout, pipelineLayout)); |
| if (!pipeline->initialize()) { |
| return nullptr; |
| } |
| return pipeline; |
| } |
| |
| GpuDecompressionPipeline::GpuDecompressionPipeline(VulkanDispatch* vk, VkDevice device, |
| VkFormat compressedFormat, VkImageType imageType, |
| VkDescriptorSetLayout descriptorSetLayout, |
| VkPipelineLayout pipelineLayout) |
| : mVk(vk), |
| mDevice(device), |
| mCompressedFormat(compressedFormat), |
| mImageType(imageType), |
| mDescriptorSetLayout(descriptorSetLayout), |
| mPipelineLayout(pipelineLayout) { |
| INFO("Created GPU decompression pipeline for format %s %s. ASTC decoder: %s", |
| string_VkFormat(mCompressedFormat), string_VkImageType(imageType), |
| string_AstcDecoder(activeAstcDecoder)); |
| } |
| |
| bool GpuDecompressionPipeline::initialize() { |
| const ShaderData* shaderData = getDecompressionShader(mCompressedFormat, mImageType); |
| if (!shaderData) { |
| WARN("No decompression shader found for format %s and img type %s", |
| string_VkFormat(mCompressedFormat), string_VkImageType(mImageType)); |
| return false; |
| } |
| |
| VkShaderModuleCreateInfo shaderInfo = { |
| .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, |
| .codeSize = shaderData->size, |
| .pCode = shaderData->code, |
| }; |
| VkShaderModule shader; |
| VkResult result = mVk->vkCreateShaderModule(mDevice, &shaderInfo, nullptr, &shader); |
| if (result != VK_SUCCESS) { |
| WARN("GPU decompression: error calling vkCreateShaderModule: %d", result); |
| return false; |
| } |
| |
| VkComputePipelineCreateInfo computePipelineInfo = { |
| .sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO, |
| .stage = {.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, |
| .stage = VK_SHADER_STAGE_COMPUTE_BIT, |
| .module = shader, |
| .pName = "main"}, |
| .layout = mPipelineLayout, |
| }; |
| result = mVk->vkCreateComputePipelines(mDevice, nullptr, 1, &computePipelineInfo, nullptr, |
| &mPipeline); |
| mVk->vkDestroyShaderModule(mDevice, shader, nullptr); |
| if (result != VK_SUCCESS) { |
| WARN("GPU decompression: error calling vkCreateComputePipelines: %d", result); |
| return false; |
| } |
| return true; |
| } |
| |
| GpuDecompressionPipeline::~GpuDecompressionPipeline() { |
| if (!mVk || !mDevice) return; |
| mVk->vkDestroyPipeline(mDevice, mPipeline, nullptr); |
| } |
| |
| // static |
| void GpuDecompressionPipelineManager::setAstcDecoder(AstcDecoder value) { |
| activeAstcDecoder = value; |
| } |
| AstcDecoder GpuDecompressionPipelineManager::astcDecoder() { return activeAstcDecoder; } |
| |
| GpuDecompressionPipelineManager::GpuDecompressionPipelineManager(VulkanDispatch* vk, |
| VkDevice device) |
| : mVk(vk), mDevice(device) {} |
| |
| GpuDecompressionPipeline* GpuDecompressionPipelineManager::get(VkFormat compressedFormat, |
| VkImageType imageType) { |
| auto& pipeline = mPipelines[getDecompressionShader(compressedFormat, imageType)]; |
| if (!pipeline) { |
| VkDescriptorSetLayout descriptorSetLayout = getDescriptorSetLayout(); |
| if (descriptorSetLayout == VK_NULL_HANDLE) return nullptr; |
| |
| VkPipelineLayout pipelineLayout = getPipelineLayout(compressedFormat); |
| if (pipelineLayout == VK_NULL_HANDLE) return nullptr; |
| |
| pipeline = GpuDecompressionPipeline::create(mVk, mDevice, compressedFormat, imageType, |
| descriptorSetLayout, pipelineLayout); |
| } |
| return pipeline.get(); |
| } |
| |
| VkDescriptorSetLayout GpuDecompressionPipelineManager::getDescriptorSetLayout() { |
| VkDescriptorSetLayoutBinding dsLayoutBindings[] = { |
| { |
| .binding = 0, |
| .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, |
| .descriptorCount = 1, |
| .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, |
| }, |
| { |
| .binding = 1, |
| .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, |
| .descriptorCount = 1, |
| .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, |
| }, |
| }; |
| VkDescriptorSetLayoutCreateInfo dsLayoutInfo = { |
| .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, |
| .bindingCount = std::size(dsLayoutBindings), |
| .pBindings = dsLayoutBindings, |
| }; |
| VkResult result = |
| mVk->vkCreateDescriptorSetLayout(mDevice, &dsLayoutInfo, nullptr, &mDescriptorSetLayout); |
| if (result != VK_SUCCESS) { |
| WARN("GPU decompression: error calling vkCreateDescriptorSetLayout: %d", result); |
| return VK_NULL_HANDLE; |
| } |
| return mDescriptorSetLayout; |
| } |
| |
| VkPipelineLayout GpuDecompressionPipelineManager::getPipelineLayout(VkFormat format) { |
| VkPipelineLayout* pipelineLayout; |
| VkPushConstantRange pushConstant = {.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT}; |
| |
| if (isAstc(format)) { |
| pipelineLayout = &mAstcPipelineLayout; |
| pushConstant.size = sizeof(AstcPushConstant); |
| } else { |
| pipelineLayout = &mEtc2PipelineLayout; |
| pushConstant.size = sizeof(Etc2PushConstant); |
| } |
| |
| VkPipelineLayoutCreateInfo pipelineLayoutInfo = { |
| .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, |
| .setLayoutCount = 1, |
| .pSetLayouts = &mDescriptorSetLayout, |
| .pushConstantRangeCount = 1, |
| .pPushConstantRanges = &pushConstant, |
| }; |
| VkResult result = |
| mVk->vkCreatePipelineLayout(mDevice, &pipelineLayoutInfo, nullptr, pipelineLayout); |
| if (result != VK_SUCCESS) { |
| WARN("GPU decompression: error calling vkCreatePipelineLayout for format %s: %d", |
| string_VkFormat(format), result); |
| return VK_NULL_HANDLE; |
| } |
| return *pipelineLayout; |
| } |
| |
| void GpuDecompressionPipelineManager::clear() { |
| mPipelines.clear(); |
| if (mVk && mDevice) { |
| mVk->vkDestroyDescriptorSetLayout(mDevice, mDescriptorSetLayout, nullptr); |
| mVk->vkDestroyPipelineLayout(mDevice, mAstcPipelineLayout, nullptr); |
| mVk->vkDestroyPipelineLayout(mDevice, mEtc2PipelineLayout, nullptr); |
| |
| mDescriptorSetLayout = VK_NULL_HANDLE; |
| mAstcPipelineLayout = VK_NULL_HANDLE; |
| mEtc2PipelineLayout = VK_NULL_HANDLE; |
| } |
| } |
| |
| GpuDecompressionPipelineManager::~GpuDecompressionPipelineManager() { |
| if (!mPipelines.empty() || mDescriptorSetLayout != VK_NULL_HANDLE || |
| mAstcPipelineLayout != VK_NULL_HANDLE || mEtc2PipelineLayout != VK_NULL_HANDLE) { |
| WARN( |
| "Resource leak: GpuDecompressionPipelineManager is being destroyed but clear() wasn't" |
| " called first"); |
| } |
| } |
| |
| } // namespace vk |
| } // namespace gfxstream |