diff --git a/CMakeLists.txt b/CMakeLists.txt index 33c2bb2968..e1dd12d71b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -743,6 +743,8 @@ endif() if (FILAMENT_SUPPORTS_VULKAN) add_subdirectory(${LIBRARIES}/bluevk) add_subdirectory(${EXTERNAL}/vkmemalloc/tnt) + set(SPIRV_HEADERS_SKIP_EXAMPLES ON) + add_subdirectory(${EXTERNAL}/spirv-headers) endif() set(FILAMENT_SAMPLES_BINARY_DIR ${PROJECT_BINARY_DIR}/samples) diff --git a/filament/backend/CMakeLists.txt b/filament/backend/CMakeLists.txt index 0420162a41..4edf04af14 100644 --- a/filament/backend/CMakeLists.txt +++ b/filament/backend/CMakeLists.txt @@ -169,6 +169,8 @@ if (FILAMENT_SUPPORTS_VULKAN) src/vulkan/platform/VulkanPlatform.cpp src/vulkan/platform/VulkanPlatformSwapChainImpl.cpp src/vulkan/platform/VulkanPlatformSwapChainImpl.h + src/vulkan/spirv/VulkanSpirvUtils.cpp + src/vulkan/spirv/VulkanSpirvUtils.h src/vulkan/VulkanBlitter.cpp src/vulkan/VulkanBlitter.h src/vulkan/VulkanBuffer.cpp @@ -313,6 +315,7 @@ endif() if (FILAMENT_SUPPORTS_VULKAN) target_link_libraries(${TARGET} PUBLIC bluevk vkmemalloc vkshaders smol-v) + target_link_libraries(${TARGET} PRIVATE SPIRV-Headers) endif() if (FILAMENT_SUPPORTS_METAL) diff --git a/filament/backend/include/backend/DriverEnums.h b/filament/backend/include/backend/DriverEnums.h index a145d28d9a..919546d674 100644 --- a/filament/backend/include/backend/DriverEnums.h +++ b/filament/backend/include/backend/DriverEnums.h @@ -1194,8 +1194,8 @@ using FrameScheduledCallback = void(*)(PresentCallable callable, void* user); enum class Workaround : uint16_t { // The EASU pass must split because shader compiler flattens early-exit branch SPLIT_EASU, - // Backend allows feedback loop with ancillary buffers (depth/stencil) as long as they're read-only for - // the whole render pass. + // Backend allows feedback loop with ancillary buffers (depth/stencil) as long as they're + // read-only for the whole render pass. ALLOW_READ_ONLY_ANCILLARY_FEEDBACK_LOOP, // for some uniform arrays, it's needed to do an initialization to avoid crash on adreno gpu ADRENO_UNIFORM_ARRAY_CRASH, diff --git a/filament/backend/src/vulkan/VulkanDriver.cpp b/filament/backend/src/vulkan/VulkanDriver.cpp index 029c6a4d0e..1cea2ca315 100644 --- a/filament/backend/src/vulkan/VulkanDriver.cpp +++ b/filament/backend/src/vulkan/VulkanDriver.cpp @@ -54,7 +54,8 @@ VmaAllocator createAllocator(VkInstance instance, VkPhysicalDevice physicalDevic VmaAllocator allocator; VmaVulkanFunctions const funcs { #if VMA_DYNAMIC_VULKAN_FUNCTIONS - .vkGetInstanceProcAddr = vkGetInstanceProcAddr, .vkGetDeviceProcAddr = vkGetDeviceProcAddr, + .vkGetInstanceProcAddr = vkGetInstanceProcAddr, + .vkGetDeviceProcAddr = vkGetDeviceProcAddr, #else .vkGetPhysicalDeviceProperties = vkGetPhysicalDeviceProperties, .vkGetPhysicalDeviceMemoryProperties = vkGetPhysicalDeviceMemoryProperties, @@ -426,7 +427,8 @@ void VulkanDriver::destroyTexture(Handle th) { } void VulkanDriver::createProgramR(Handle ph, Program&& program) { - auto vkprogram = mResourceAllocator.construct(ph, mPlatform->getDevice(), program); + auto vkprogram + = mResourceAllocator.construct(ph, mPlatform->getDevice(), program); mResourceManager.acquire(vkprogram); } diff --git a/filament/backend/src/vulkan/VulkanHandles.cpp b/filament/backend/src/vulkan/VulkanHandles.cpp index ac6b4ccea1..5dfce4ddfb 100644 --- a/filament/backend/src/vulkan/VulkanHandles.cpp +++ b/filament/backend/src/vulkan/VulkanHandles.cpp @@ -18,6 +18,7 @@ #include "VulkanConstants.h" #include "VulkanMemory.h" +#include "spirv/VulkanSpirvUtils.h" #include @@ -46,61 +47,34 @@ static void clampToFramebuffer(VkRect2D* rect, uint32_t fbWidth, uint32_t fbHeig rect->extent.height = std::max(top - y, 0); } -VulkanProgram::VulkanProgram(VkDevice device, const Program& builder) noexcept +VulkanProgram::VulkanProgram(VkDevice device, Program& builder) noexcept : HwProgram(builder.getName()), VulkanResource(VulkanResourceType::PROGRAM), - mInfo(new PipelineInfo(builder.getSpecializationConstants().size())), + mInfo(new PipelineInfo()), mDevice(device) { - auto& blobs = builder.getShadersSource(); + Program::ShaderSource& blobs = builder.getShadersSource(); auto& modules = mInfo->shaders; + + auto const& specializationConstants = builder.getSpecializationConstants(); + for (size_t i = 0; i < MAX_SHADER_MODULES; i++) { - const auto& blob = blobs[i]; - uint32_t* data = (uint32_t*)blob.data(); + Program::ShaderBlob& blob = blobs[i]; + + workaroundSpecConstant(blob, specializationConstants); + + uint32_t* data = (uint32_t*) blob.data(); + size_t dataSize = blob.size(); + VkShaderModule& module = modules[i]; VkShaderModuleCreateInfo moduleInfo = { .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, - .codeSize = blob.size(), + .codeSize = dataSize, .pCode = data, }; VkResult result = vkCreateShaderModule(mDevice, &moduleInfo, VKALLOC, &module); ASSERT_POSTCONDITION(result == VK_SUCCESS, "Unable to create shader module."); } - // Note that bools are 4-bytes in Vulkan - // https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkBool32.html - constexpr uint32_t const CONSTANT_SIZE = 4; - - // populate the specialization constants requirements right now - auto const& specializationConstants = builder.getSpecializationConstants(); - uint32_t const specConstCount = static_cast(specializationConstants.size()); - char* specData = mInfo->specConstData.get(); - if (specConstCount > 0) { - mInfo->specializationInfo = { - .mapEntryCount = specConstCount, - .pMapEntries = mInfo->specConsts.data(), - .dataSize = specConstCount * CONSTANT_SIZE, - .pData = specData, - }; - } - for (uint32_t i = 0; i < specConstCount; ++i) { - uint32_t const offset = i * CONSTANT_SIZE; - mInfo->specConsts[i] = { - .constantID = specializationConstants[i].id, - .offset = offset, - .size = CONSTANT_SIZE, - }; - using SpecConstant = Program::SpecializationConstant::Type; - char const* addr = (char*)specData + offset; - SpecConstant const& arg = specializationConstants[i].value; - if (std::holds_alternative(arg)) { - *((VkBool32*)addr) = std::get(arg) ? VK_TRUE : VK_FALSE; - } else if (std::holds_alternative(arg)) { - *((float*)addr) = std::get(arg); - } else { - *((int32_t*)addr) = std::get(arg); - } - } - auto& groupInfo = builder.getSamplerGroupInfo(); auto& bindingToSamplerIndex = mInfo->bindingToSamplerIndex; auto& usage = mInfo->usage; @@ -123,7 +97,7 @@ VulkanProgram::VulkanProgram(VkDevice device, const Program& builder) noexcept VulkanProgram::VulkanProgram(VkDevice device, VkShaderModule vs, VkShaderModule fs, CustomSamplerInfoList const& samplerInfo) noexcept : VulkanResource(VulkanResourceType::PROGRAM), - mInfo(new PipelineInfo(0)), + mInfo(new PipelineInfo()), mDevice(device) { mInfo->shaders[0] = vs; mInfo->shaders[1] = fs; diff --git a/filament/backend/src/vulkan/VulkanHandles.h b/filament/backend/src/vulkan/VulkanHandles.h index f60d73ca55..043abc0506 100644 --- a/filament/backend/src/vulkan/VulkanHandles.h +++ b/filament/backend/src/vulkan/VulkanHandles.h @@ -25,7 +25,6 @@ #include "VulkanResources.h" #include "VulkanSwapChain.h" #include "VulkanTexture.h" -#include "VulkanUtility.h" #include "private/backend/SamplerGroup.h" @@ -38,7 +37,7 @@ class VulkanTimestamps; struct VulkanProgram : public HwProgram, VulkanResource { - VulkanProgram(VkDevice device, const Program& builder) noexcept; + VulkanProgram(VkDevice device, Program& builder) noexcept; struct CustomSamplerInfo { uint8_t groupIndex; @@ -65,21 +64,14 @@ struct VulkanProgram : public HwProgram, VulkanResource { return mInfo->bindingToSamplerIndex; } - inline VkSpecializationInfo const& getSpecConstInfo() const { - return mInfo->specializationInfo; - } - private: // TODO: handle compute shaders. // The expected order of shaders - from frontend to backend - is vertex, fragment, compute. - static constexpr uint8_t MAX_SHADER_MODULES = 2; + static constexpr uint8_t const MAX_SHADER_MODULES = 2; struct PipelineInfo { - PipelineInfo(size_t specConstsCount) : - bindingToSamplerIndex(MAX_SAMPLER_COUNT, 0xffff), - specConsts(specConstsCount, VkSpecializationMapEntry{}), - specConstData(new char[specConstsCount * 4]) - {} + PipelineInfo() + : bindingToSamplerIndex(MAX_SAMPLER_COUNT, 0xffff) {} // This bitset maps to each of the sampler in the sampler groups associated with this // program, and whether each sampler is used in which shader (i.e. vert, frag, compute). @@ -87,10 +79,7 @@ private: // We store the samplerGroupIndex as the top 8-bit and the index within each group as the lower 8-bit. utils::FixedCapacityVector bindingToSamplerIndex; - VkShaderModule shaders[MAX_SHADER_MODULES] = {VK_NULL_HANDLE}; - VkSpecializationInfo specializationInfo = {}; - utils::FixedCapacityVector specConsts; - std::unique_ptr specConstData; + VkShaderModule shaders[MAX_SHADER_MODULES] = { VK_NULL_HANDLE }; }; PipelineInfo* mInfo; diff --git a/filament/backend/src/vulkan/VulkanPipelineCache.cpp b/filament/backend/src/vulkan/VulkanPipelineCache.cpp index 000f8c755c..889888cd08 100644 --- a/filament/backend/src/vulkan/VulkanPipelineCache.cpp +++ b/filament/backend/src/vulkan/VulkanPipelineCache.cpp @@ -360,9 +360,7 @@ VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::createPipeline() n // If we reach this point, we need to create and stash a brand new pipeline object. shaderStages[0].module = mPipelineRequirements.shaders[0]; - shaderStages[0].pSpecializationInfo = mSpecializationRequirements; shaderStages[1].module = mPipelineRequirements.shaders[1]; - shaderStages[1].pSpecializationInfo = mSpecializationRequirements; // Expand our size-optimized structs into the proper Vk structs. uint32_t numVertexAttribs = 0; @@ -568,7 +566,6 @@ VulkanPipelineCache::PipelineLayoutCacheEntry* VulkanPipelineCache::getOrCreateP void VulkanPipelineCache::bindProgram(VulkanProgram* program) noexcept { mPipelineRequirements.shaders[0] = program->getVertexShader(); mPipelineRequirements.shaders[1] = program->getFragmentShader(); - mSpecializationRequirements = &program->getSpecConstInfo(); } void VulkanPipelineCache::bindRasterState(const RasterState& rasterState) noexcept { diff --git a/filament/backend/src/vulkan/VulkanPipelineCache.h b/filament/backend/src/vulkan/VulkanPipelineCache.h index 0e092e9555..018fd00efe 100644 --- a/filament/backend/src/vulkan/VulkanPipelineCache.h +++ b/filament/backend/src/vulkan/VulkanPipelineCache.h @@ -416,7 +416,6 @@ private: RasterState mCurrentRasterState; PipelineKey mPipelineRequirements = {}; DescriptorKey mDescriptorRequirements = {}; - VkSpecializationInfo const* mSpecializationRequirements = nullptr; // Current bindings for the pipeline and descriptor sets. PipelineKey mBoundPipeline = {}; diff --git a/filament/backend/src/vulkan/spirv/VulkanSpirvUtils.cpp b/filament/backend/src/vulkan/spirv/VulkanSpirvUtils.cpp new file mode 100644 index 0000000000..a72c0e8698 --- /dev/null +++ b/filament/backend/src/vulkan/spirv/VulkanSpirvUtils.cpp @@ -0,0 +1,125 @@ +/* + * 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 "VulkanSpirvUtils.h" + +#include + +#include +#include + +#include + +namespace filament::backend { + +using namespace bluevk; + +namespace { + +// This function transforms an OpSpecConstant instruction into just a OpConstant instruction. +// Additionally, it will adjust the value of the constant as given by the program. +void getTransformedConstantInst(SpecConstantValue const& value, uint32_t* inst) { + + // The first word is the size of the instruction and the instruction type. + // The second word is the type of the instruction. + // The third word is the "result id" (i.e. the variable to store into). + // The fourth word (only for floats and ints) is the literal representing the value. + // We only want to change the first and fourth words. + constexpr size_t const OP = 0; + constexpr size_t const VALUE = 3; + + if (std::holds_alternative(value)) { + inst[OP] = (3 << 16) + | (std::get(value) ? spv::Op::OpConstantTrue : spv::Op::OpConstantFalse); + } else if (std::holds_alternative(value)) { + float const fval = std::get(value); + inst[OP] = (4 << 16) | spv::Op::OpConstant; + inst[VALUE] = *reinterpret_cast(&fval); + } else { + int const ival = std::get(value); + inst[OP] = (4 << 16) | spv::Op::OpConstant; + inst[VALUE] = *reinterpret_cast(&ival); + } +} + +} // anonymous namespace + +void workaroundSpecConstant(Program::ShaderBlob& blob, + utils::FixedCapacityVector const& specConstants) { + using WordMap = std::unordered_map; + using SpecValueMap = std::unordered_map; + + WordMap varToIdMap; + SpecValueMap idToValue; + + for (auto spec: specConstants) { + idToValue[spec.id] = spec.value; + } + + // The follow loop will iterate through all the instructions and look for instructions of the + // form: + // + // OpDecorate %1 SpecId 0 + // %1 = OpSpecConstantFalse %bool + // + // We want to track the mapping between the variable id %1 and the specId 0. And when seeing a + // %1 as the result, we want to change that instruction to just an OpConsant where the constant + // value is adjusted by the spec values provided in the program. + // Additionally, we will turn the SpecId decorations to no-ops. + uint32_t* outputData = (uint32_t*) blob.data(); + + for (uint32_t cursor = 5, cursorEnd = blob.size() / 4; cursor < cursorEnd;) { + uint32_t const firstWord = outputData[cursor]; + uint32_t const wordCount = firstWord >> 16; + uint32_t const op = firstWord & 0x0000FFFF; + + switch(op) { + case spv::Op::OpDecorate: { + if (outputData[cursor + 2] == spv::Decoration::DecorationSpecId) { + uint32_t const targetVar = outputData[cursor + 1]; + uint32_t const specId = outputData[cursor + 3]; + varToIdMap[targetVar] = specId; + // Now we write noops over the decoration since its no longer needed. + for (size_t i = cursor, n = cursor + wordCount; i < n; ++i) { + outputData[i] = (1 << 16) | spv::Op::OpNop; + } + } + break; + } + case spv::Op::OpSpecConstant: + case spv::Op::OpSpecConstantTrue: + case spv::Op::OpSpecConstantFalse: { + uint32_t const targetVar = outputData[cursor + 2]; + + UTILS_UNUSED_IN_RELEASE WordMap::const_iterator idItr = varToIdMap.find(targetVar); + assert_invariant(idItr != varToIdMap.end() && + "Cannot find variable previously decorated with SpecId"); + + assert_invariant(idToValue.find(idItr->second) != idToValue.end() && + "Spec constant value not provided"); + + auto const& val = idToValue[varToIdMap[targetVar]]; + getTransformedConstantInst(val, &outputData[cursor]); + break; + } + default: + break; + } + cursor += wordCount; + } +} + +} // namespace filament::backend diff --git a/filament/backend/src/vulkan/spirv/VulkanSpirvUtils.h b/filament/backend/src/vulkan/spirv/VulkanSpirvUtils.h new file mode 100644 index 0000000000..8251ba3856 --- /dev/null +++ b/filament/backend/src/vulkan/spirv/VulkanSpirvUtils.h @@ -0,0 +1,43 @@ +/* + * 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. + */ + +#ifndef TNT_FILAMENT_BACKEND_VULKANSPIRVUTILS_H +#define TNT_FILAMENT_BACKEND_VULKANSPIRVUTILS_H + +#include "backend/Program.h" + +#include + +#include + +namespace filament::backend { + +using SpecConstantValue = Program::SpecializationConstant::Type; + +// For certain drivers, using spec constant can lead to compile errors [1] or undesirable behaviors +// [2]. In those instances, we simply change the spirv and set them to constants. This function will +// modify the blob (but it's safe to do so since blobs are "moved" to the backend). +// +// [1]: QC driver cannot use spec constant to size fields +// (https://github.com/google/filament/issues/6444). +// [2]: An internal driver does not DCE a block guarded by a spec-const boolean set to false +// (b/310603393). +void workaroundSpecConstant(Program::ShaderBlob& blob, + utils::FixedCapacityVector const& specConstants); + +} // namespace filament::backend + +#endif // TNT_FILAMENT_BACKEND_VULKANSPIRVUTILS_H