vk: spec const workaround through runtime spirv modification (#7399)
To avoid driver edge cases with spec const, we manually change the spec const into a regular constant in the spirv. FIXES=310603393
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<HwTexture> th) {
|
||||
}
|
||||
|
||||
void VulkanDriver::createProgramR(Handle<HwProgram> ph, Program&& program) {
|
||||
auto vkprogram = mResourceAllocator.construct<VulkanProgram>(ph, mPlatform->getDevice(), program);
|
||||
auto vkprogram
|
||||
= mResourceAllocator.construct<VulkanProgram>(ph, mPlatform->getDevice(), program);
|
||||
mResourceManager.acquire(vkprogram);
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
#include "VulkanConstants.h"
|
||||
#include "VulkanMemory.h"
|
||||
#include "spirv/VulkanSpirvUtils.h"
|
||||
|
||||
#include <backend/platforms/VulkanPlatform.h>
|
||||
|
||||
@@ -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<uint32_t>(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<bool>(arg)) {
|
||||
*((VkBool32*)addr) = std::get<bool>(arg) ? VK_TRUE : VK_FALSE;
|
||||
} else if (std::holds_alternative<float>(arg)) {
|
||||
*((float*)addr) = std::get<float>(arg);
|
||||
} else {
|
||||
*((int32_t*)addr) = std::get<int32_t>(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;
|
||||
|
||||
@@ -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<uint16_t> bindingToSamplerIndex;
|
||||
VkShaderModule shaders[MAX_SHADER_MODULES] = {VK_NULL_HANDLE};
|
||||
VkSpecializationInfo specializationInfo = {};
|
||||
utils::FixedCapacityVector<VkSpecializationMapEntry> specConsts;
|
||||
std::unique_ptr<char[]> specConstData;
|
||||
VkShaderModule shaders[MAX_SHADER_MODULES] = { VK_NULL_HANDLE };
|
||||
};
|
||||
|
||||
PipelineInfo* mInfo;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 = {};
|
||||
|
||||
@@ -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 <utils/Log.h>
|
||||
|
||||
#include <bluevk/BlueVK.h>
|
||||
#include <spirv/unified1/spirv.hpp>
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
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<bool>(value)) {
|
||||
inst[OP] = (3 << 16)
|
||||
| (std::get<bool>(value) ? spv::Op::OpConstantTrue : spv::Op::OpConstantFalse);
|
||||
} else if (std::holds_alternative<float>(value)) {
|
||||
float const fval = std::get<float>(value);
|
||||
inst[OP] = (4 << 16) | spv::Op::OpConstant;
|
||||
inst[VALUE] = *reinterpret_cast<uint32_t const*>(&fval);
|
||||
} else {
|
||||
int const ival = std::get<int>(value);
|
||||
inst[OP] = (4 << 16) | spv::Op::OpConstant;
|
||||
inst[VALUE] = *reinterpret_cast<uint32_t const*>(&ival);
|
||||
}
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
void workaroundSpecConstant(Program::ShaderBlob& blob,
|
||||
utils::FixedCapacityVector<Program::SpecializationConstant> const& specConstants) {
|
||||
using WordMap = std::unordered_map<uint32_t, uint32_t>;
|
||||
using SpecValueMap = std::unordered_map<uint32_t, SpecConstantValue>;
|
||||
|
||||
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
|
||||
@@ -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 <utils/FixedCapacityVector.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
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<Program::SpecializationConstant> const& specConstants);
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
#endif // TNT_FILAMENT_BACKEND_VULKANSPIRVUTILS_H
|
||||
Reference in New Issue
Block a user