The Glslang SPIR-V remapper was removed, and replaced by the CanonicalizeIds pass in spirv-opt. See https://github.com/KhronosGroup/SPIRV-Tools/pull/6174 Filament's GLSL post-optimization algorithm already runs CanonicalizeIds. Bug: b:450891564
1156 lines
47 KiB
C++
1156 lines
47 KiB
C++
/*
|
|
* Copyright (C) 2017 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 "GLSLPostProcessor.h"
|
|
|
|
#include <GlslangToSpv.h>
|
|
#include <spirv-tools/libspirv.hpp>
|
|
|
|
#include <spirv_glsl.hpp>
|
|
#include <spirv_msl.hpp>
|
|
|
|
#include "private/filament/DescriptorSets.h"
|
|
#include "sca/builtinResource.h"
|
|
#include "sca/GLSLTools.h"
|
|
|
|
#include "shaders/CodeGenerator.h"
|
|
#include "shaders/MaterialInfo.h"
|
|
#include "shaders/SibGenerator.h"
|
|
|
|
#include "MetalArgumentBuffer.h"
|
|
#include "SpirvFixup.h"
|
|
|
|
#include <filament/MaterialEnums.h>
|
|
|
|
#include <utils/compiler.h>
|
|
#include <utils/debug.h>
|
|
#include <utils/Log.h>
|
|
#include <utils/ostream.h>
|
|
|
|
#include <algorithm>
|
|
#include <optional>
|
|
#include <sstream>
|
|
#include <string>
|
|
#include <type_traits>
|
|
#include <unordered_map>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
|
|
#ifdef FILAMENT_SUPPORTS_WEBGPU
|
|
#include <tint/tint.h>
|
|
#endif
|
|
|
|
using namespace glslang;
|
|
using namespace spirv_cross;
|
|
using namespace spvtools;
|
|
using namespace filament;
|
|
using namespace filament::backend;
|
|
|
|
namespace filamat {
|
|
|
|
using namespace utils;
|
|
|
|
namespace msl { // this is only used for MSL
|
|
|
|
using BindingIndexMap = std::unordered_map<std::string, uint16_t>;
|
|
|
|
#ifndef DEBUG_LOG_DESCRIPTOR_SETS
|
|
#define DEBUG_LOG_DESCRIPTOR_SETS 0
|
|
#endif
|
|
|
|
static const char* toString(ShaderStageFlags const flags) {
|
|
std::vector<const char*> stages;
|
|
if (any(flags & ShaderStageFlags::VERTEX)) {
|
|
stages.push_back("VERTEX");
|
|
}
|
|
if (any(flags & ShaderStageFlags::FRAGMENT)) {
|
|
stages.push_back("FRAGMENT");
|
|
}
|
|
if (any(flags & ShaderStageFlags::COMPUTE)) {
|
|
stages.push_back("COMPUTE");
|
|
}
|
|
if (stages.empty()) {
|
|
return "NONE";
|
|
}
|
|
static char buffer[64];
|
|
buffer[0] = '\0';
|
|
for (size_t i = 0; i < stages.size(); i++) {
|
|
if (i > 0) {
|
|
strcat(buffer, " | ");
|
|
}
|
|
strcat(buffer, stages[i]);
|
|
}
|
|
return buffer;
|
|
}
|
|
|
|
static const char* prettyDescriptorFlags(DescriptorFlags const flags) {
|
|
if (flags == DescriptorFlags::DYNAMIC_OFFSET) {
|
|
return "DYNAMIC_OFFSET";
|
|
}
|
|
return "NONE";
|
|
}
|
|
|
|
static const char* prettyPrintSamplerType(SamplerType const type) {
|
|
switch (type) {
|
|
case SamplerType::SAMPLER_2D:
|
|
return "SAMPLER_2D";
|
|
case SamplerType::SAMPLER_2D_ARRAY:
|
|
return "SAMPLER_2D_ARRAY";
|
|
case SamplerType::SAMPLER_CUBEMAP:
|
|
return "SAMPLER_CUBEMAP";
|
|
case SamplerType::SAMPLER_EXTERNAL:
|
|
return "SAMPLER_EXTERNAL";
|
|
case SamplerType::SAMPLER_3D:
|
|
return "SAMPLER_3D";
|
|
case SamplerType::SAMPLER_CUBEMAP_ARRAY:
|
|
return "SAMPLER_CUBEMAP_ARRAY";
|
|
}
|
|
}
|
|
|
|
DescriptorSetLayout getPerMaterialDescriptorSet(SamplerInterfaceBlock const& sib) noexcept {
|
|
auto const& samplers = sib.getSamplerInfoList();
|
|
|
|
DescriptorSetLayout layout;
|
|
layout.bindings.reserve(1 + samplers.size());
|
|
|
|
layout.bindings.push_back(DescriptorSetLayoutBinding { DescriptorType::UNIFORM_BUFFER,
|
|
ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT,
|
|
+PerMaterialBindingPoints::MATERIAL_PARAMS, DescriptorFlags::NONE, 0 });
|
|
|
|
for (auto const& sampler: samplers) {
|
|
DescriptorSetLayoutBinding layoutBinding{
|
|
DescriptorType::SAMPLER_EXTERNAL,
|
|
sampler.stages, sampler.binding,
|
|
DescriptorFlags::NONE,
|
|
0
|
|
};
|
|
if (sampler.type != SamplerInterfaceBlock::Type::SAMPLER_EXTERNAL) {
|
|
layoutBinding.type = descriptor_sets::getDescriptorType(sampler.type, sampler.format);
|
|
}
|
|
layout.bindings.push_back(layoutBinding);
|
|
}
|
|
|
|
return layout;
|
|
}
|
|
|
|
static void collectDescriptorsForSet(DescriptorSetBindingPoints set,
|
|
const GLSLPostProcessor::Config& config, DescriptorSetInfo& descriptors) {
|
|
const MaterialInfo& material = *config.materialInfo;
|
|
|
|
// get the descriptor set layout for the given pinding point
|
|
DescriptorSetLayout const descriptorSetLayout = [&] {
|
|
switch (set) {
|
|
case DescriptorSetBindingPoints::PER_VIEW: {
|
|
bool const isLit = material.isLit || material.hasShadowMultiplier;
|
|
bool const isSSR = material.reflectionMode == ReflectionMode::SCREEN_SPACE ||
|
|
material.refractionMode == RefractionMode::SCREEN_SPACE;
|
|
bool const hasFog = !(config.variantFilter & UserVariantFilterMask(UserVariantFilterBit::FOG));
|
|
return descriptor_sets::getPerViewDescriptorSetLayoutWithVariant(
|
|
config.variant, config.domain, isLit, isSSR, hasFog);
|
|
}
|
|
case DescriptorSetBindingPoints::PER_RENDERABLE:
|
|
return descriptor_sets::getPerRenderableLayout();
|
|
case DescriptorSetBindingPoints::PER_MATERIAL:
|
|
return getPerMaterialDescriptorSet(config.materialInfo->sib);
|
|
default:
|
|
return DescriptorSetLayout {};
|
|
}
|
|
}();
|
|
|
|
// get the sampler list for this binding point
|
|
auto samplerList = [&] {
|
|
switch (set) {
|
|
case DescriptorSetBindingPoints::PER_VIEW:
|
|
return SibGenerator::getPerViewSib(config.variant).getSamplerInfoList();
|
|
case DescriptorSetBindingPoints::PER_RENDERABLE:
|
|
return SibGenerator::getPerRenderableSib(config.variant).getSamplerInfoList();
|
|
case DescriptorSetBindingPoints::PER_MATERIAL:
|
|
return config.materialInfo->sib.getSamplerInfoList();
|
|
default:
|
|
return SamplerInterfaceBlock::SamplerInfoList {};
|
|
}
|
|
}();
|
|
|
|
// filter the list with the descriptor set layout
|
|
auto const descriptorSetSamplerList =
|
|
SamplerInterfaceBlock::filterSamplerList(std::move(samplerList), descriptorSetLayout);
|
|
|
|
// helper to get the name of a descriptor for this set, given a binding.
|
|
auto getDescriptorName = [set, &descriptorSetSamplerList](descriptor_binding_t binding) {
|
|
if (set == DescriptorSetBindingPoints::PER_MATERIAL) {
|
|
auto pos = std::find_if(descriptorSetSamplerList.begin(), descriptorSetSamplerList.end(),
|
|
[&](const auto& entry) { return entry.binding == binding; });
|
|
if (pos == descriptorSetSamplerList.end()) {
|
|
return descriptor_sets::getDescriptorName(set, binding);
|
|
}
|
|
return pos->uniformName;
|
|
}
|
|
return descriptor_sets::getDescriptorName(set, binding);
|
|
};
|
|
|
|
for (auto const& layoutBinding : descriptorSetLayout.bindings) {
|
|
descriptor_binding_t binding = layoutBinding.binding;
|
|
auto name = getDescriptorName(binding);
|
|
if (DescriptorSetLayoutBinding::isSampler(layoutBinding.type)) {
|
|
auto const pos = std::find_if(descriptorSetSamplerList.begin(), descriptorSetSamplerList.end(),
|
|
[&](const auto& entry) { return entry.binding == binding; });
|
|
assert_invariant(pos != descriptorSetSamplerList.end());
|
|
descriptors.emplace_back(name, layoutBinding, *pos);
|
|
} else {
|
|
descriptors.emplace_back(name, layoutBinding, std::nullopt);
|
|
}
|
|
}
|
|
|
|
std::sort(descriptors.begin(), descriptors.end(), [](const auto& a, const auto& b) {
|
|
return std::get<1>(a).binding < std::get<1>(b).binding;
|
|
});
|
|
}
|
|
|
|
static void prettyPrintDescriptorSetInfoVector(DescriptorSets const& sets) noexcept {
|
|
auto getName = [](uint8_t const set) {
|
|
switch (set) {
|
|
case +DescriptorSetBindingPoints::PER_VIEW:
|
|
return "perViewDescriptorSetLayout";
|
|
case +DescriptorSetBindingPoints::PER_RENDERABLE:
|
|
return "perRenderableDescriptorSetLayout";
|
|
case +DescriptorSetBindingPoints::PER_MATERIAL:
|
|
return "perMaterialDescriptorSetLayout";
|
|
default:
|
|
return "unknown";
|
|
}
|
|
};
|
|
for (size_t setIndex = 0; setIndex < MAX_DESCRIPTOR_SET_COUNT; setIndex++) {
|
|
auto const& descriptors = sets[setIndex];
|
|
printf("[DS] info (%s) = [\n", getName(setIndex));
|
|
for (auto const& descriptor : descriptors) {
|
|
auto const& [name, info, sampler] = descriptor;
|
|
if (DescriptorSetLayoutBinding::isSampler(info.type)) {
|
|
assert_invariant(sampler.has_value());
|
|
printf(" {name = %s, binding = %d, type = %.*s, count = %d, stage = %s, flags = "
|
|
"%s, samplerType = %s}",
|
|
name.c_str_safe(), info.binding,
|
|
int(to_string(info.type).size()),
|
|
to_string(info.type).data(),
|
|
info.count,
|
|
toString(info.stageFlags), prettyDescriptorFlags(info.flags),
|
|
prettyPrintSamplerType(sampler->type));
|
|
} else {
|
|
printf(" {name = %s, binding = %d, type = %.*s, count = %d, stage = %s, flags = "
|
|
"%s}",
|
|
name.c_str_safe(), info.binding,
|
|
int(to_string(info.type).size()),
|
|
to_string(info.type).data(),
|
|
info.count,
|
|
toString(info.stageFlags), prettyDescriptorFlags(info.flags));
|
|
}
|
|
printf(",\n");
|
|
}
|
|
printf("]\n");
|
|
}
|
|
}
|
|
|
|
static void collectDescriptorSets(const GLSLPostProcessor::Config& config, DescriptorSets& sets) {
|
|
auto perViewDescriptors = DescriptorSetInfo::with_capacity(MAX_DESCRIPTOR_COUNT);
|
|
collectDescriptorsForSet(DescriptorSetBindingPoints::PER_VIEW, config, perViewDescriptors);
|
|
sets[+DescriptorSetBindingPoints::PER_VIEW] = std::move(perViewDescriptors);
|
|
|
|
auto perRenderableDescriptors = DescriptorSetInfo::with_capacity(MAX_DESCRIPTOR_COUNT);
|
|
collectDescriptorsForSet(
|
|
DescriptorSetBindingPoints::PER_RENDERABLE, config, perRenderableDescriptors);
|
|
sets[+DescriptorSetBindingPoints::PER_RENDERABLE] = std::move(perRenderableDescriptors);
|
|
|
|
auto perMaterialDescriptors = DescriptorSetInfo::with_capacity(MAX_DESCRIPTOR_COUNT);
|
|
collectDescriptorsForSet(
|
|
DescriptorSetBindingPoints::PER_MATERIAL, config, perMaterialDescriptors);
|
|
sets[+DescriptorSetBindingPoints::PER_MATERIAL] = std::move(perMaterialDescriptors);
|
|
}
|
|
|
|
} // namespace msl
|
|
|
|
GLSLPostProcessor::GLSLPostProcessor(
|
|
MaterialBuilder::Optimization optimization,
|
|
MaterialBuilder::Workarounds workarounds,
|
|
uint32_t flags)
|
|
: mOptimization(optimization), mWorkarounds(workarounds),
|
|
mPrintShaders(flags & PRINT_SHADERS),
|
|
mGenerateDebugInfo(flags & GENERATE_DEBUG_INFO) {
|
|
}
|
|
|
|
GLSLPostProcessor::~GLSLPostProcessor() = default;
|
|
|
|
static bool filterSpvOptimizerMessage(spv_message_level_t level) {
|
|
#ifdef NDEBUG
|
|
// In release builds, only log errors.
|
|
if (level == SPV_MSG_WARNING ||
|
|
level == SPV_MSG_INFO ||
|
|
level == SPV_MSG_DEBUG) {
|
|
return false;
|
|
}
|
|
#endif
|
|
return true;
|
|
}
|
|
|
|
static std::string stringifySpvOptimizerMessage(spv_message_level_t level, const char* source,
|
|
const spv_position_t& position, const char* message) {
|
|
const char* levelString = nullptr;
|
|
switch (level) {
|
|
case SPV_MSG_FATAL:
|
|
levelString = "FATAL";
|
|
break;
|
|
case SPV_MSG_INTERNAL_ERROR:
|
|
levelString = "INTERNAL ERROR";
|
|
break;
|
|
case SPV_MSG_ERROR:
|
|
levelString = "ERROR";
|
|
break;
|
|
case SPV_MSG_WARNING:
|
|
levelString = "WARNING";
|
|
break;
|
|
case SPV_MSG_INFO:
|
|
levelString = "INFO";
|
|
break;
|
|
case SPV_MSG_DEBUG:
|
|
levelString = "DEBUG";
|
|
break;
|
|
}
|
|
|
|
std::ostringstream oss;
|
|
oss << levelString << ": ";
|
|
if (source) oss << source << ":";
|
|
oss << position.line << ":" << position.column << ":";
|
|
oss << position.index << ": ";
|
|
if (message) oss << message;
|
|
|
|
return oss.str();
|
|
}
|
|
|
|
void GLSLPostProcessor::spirvToMsl(const SpirvBlob* spirv, std::string* outMsl,
|
|
ShaderStage stage, ShaderModel shaderModel,
|
|
bool useFramebufferFetch, const DescriptorSets& descriptorSets,
|
|
const ShaderMinifier* minifier) {
|
|
using namespace msl;
|
|
|
|
CompilerMSL mslCompiler(*spirv);
|
|
CompilerGLSL::Options const options;
|
|
mslCompiler.set_common_options(options);
|
|
|
|
const CompilerMSL::Options::Platform platform =
|
|
shaderModel == ShaderModel::MOBILE ?
|
|
CompilerMSL::Options::Platform::iOS : CompilerMSL::Options::Platform::macOS;
|
|
|
|
CompilerMSL::Options mslOptions = {};
|
|
mslOptions.platform = platform,
|
|
mslOptions.msl_version = shaderModel == ShaderModel::MOBILE ?
|
|
CompilerMSL::Options::make_msl_version(2, 0) : CompilerMSL::Options::make_msl_version(2, 2);
|
|
|
|
if (useFramebufferFetch) {
|
|
mslOptions.use_framebuffer_fetch_subpasses = true;
|
|
// On macOS, framebuffer fetch is only available starting with MSL 2.3. Filament will only
|
|
// use framebuffer fetch materials on devices that support it.
|
|
if (shaderModel == ShaderModel::DESKTOP) {
|
|
mslOptions.msl_version = CompilerMSL::Options::make_msl_version(2, 3);
|
|
}
|
|
}
|
|
|
|
mslOptions.argument_buffers = true;
|
|
mslOptions.ios_support_base_vertex_instance = true;
|
|
mslOptions.dynamic_offsets_buffer_index = 25;
|
|
|
|
mslCompiler.set_msl_options(mslOptions);
|
|
|
|
|
|
|
|
auto executionModel = mslCompiler.get_execution_model();
|
|
|
|
// Map each descriptor set (argument buffer) to a [[buffer(n)]] binding.
|
|
// For example, mapDescriptorSet(0, 21) says "map descriptor set 0 to [[buffer(21)]]"
|
|
auto mapDescriptorSet = [&mslCompiler](uint32_t set, uint32_t buffer) {
|
|
MSLResourceBinding argBufferBinding;
|
|
argBufferBinding.basetype = SPIRType::BaseType::Float;
|
|
argBufferBinding.stage = mslCompiler.get_execution_model();
|
|
argBufferBinding.desc_set = set;
|
|
argBufferBinding.binding = kArgumentBufferBinding;
|
|
argBufferBinding.count = 1;
|
|
argBufferBinding.msl_buffer = buffer;
|
|
mslCompiler.add_msl_resource_binding(argBufferBinding);
|
|
};
|
|
for (int i = 0; i < MAX_DESCRIPTOR_SET_COUNT; i++) {
|
|
mapDescriptorSet(i, CodeGenerator::METAL_DESCRIPTOR_SET_BINDING_START + i);
|
|
}
|
|
|
|
auto resources = mslCompiler.get_shader_resources();
|
|
|
|
// We're using argument buffers for descriptor sets, however, we cannot rely on spirv-cross to
|
|
// generate the argument buffer definitions.
|
|
//
|
|
// Consider a shader with 3 textures:
|
|
// layout (set = 0, binding = 0) uniform sampler2D texture1;
|
|
// layout (set = 0, binding = 1) uniform sampler2D texture2;
|
|
// layout (set = 0, binding = 2) uniform sampler2D texture3;
|
|
//
|
|
// If only texture1 and texture2 are used in the material, then texture3 will be optimized away.
|
|
// This results in an argument buffer like the following:
|
|
// struct spvDescriptorSetBuffer0 {
|
|
// texture2d<float> texture1 [[id(0)]];
|
|
// sampler texture1Smplr [[id(1)]];
|
|
// texture2d<float> texture2 [[id(2)]];
|
|
// sampler texture2Smplr [[id(3)]];
|
|
// };
|
|
// Note that this happens even if "pad_argument_buffer_resources" and
|
|
// "force_active_argument_buffer_resources" are true.
|
|
//
|
|
// This would be fine, except older Apple devices don't like it when the argument buffer in the
|
|
// shader doesn't precisely match the one generated at runtime.
|
|
//
|
|
// So, we use the MetalArgumentBuffer class to replace spirv-cross' argument buffer definitions
|
|
// with our own that contain all the descriptors, even those optimized away.
|
|
std::vector<MetalArgumentBuffer*> argumentBuffers;
|
|
size_t dynamicOffsetsBufferIndex = 0;
|
|
for (size_t setIndex = 0; setIndex < MAX_DESCRIPTOR_SET_COUNT; setIndex++) {
|
|
auto const& descriptors = descriptorSets[setIndex];
|
|
auto argBufferBuilder = MetalArgumentBuffer::Builder().name(
|
|
"spvDescriptorSetBuffer" + std::to_string(int(setIndex)));
|
|
for (auto const& descriptor : descriptors) {
|
|
auto const& [name, info, sampler] = descriptor;
|
|
if (!hasShaderType(info.stageFlags, stage)) {
|
|
if (any(info.flags & DescriptorFlags::DYNAMIC_OFFSET)) {
|
|
// We still need to increment the dynamic offset index
|
|
dynamicOffsetsBufferIndex++;
|
|
}
|
|
continue;
|
|
}
|
|
switch (info.type) {
|
|
case DescriptorType::INPUT_ATTACHMENT:
|
|
// TODO: Handle INPUT_ATTACHMENT case
|
|
break;
|
|
case DescriptorType::UNIFORM_BUFFER:
|
|
case DescriptorType::SHADER_STORAGE_BUFFER: {
|
|
std::string lowercasedName = name.c_str();
|
|
assert_invariant(!lowercasedName.empty());
|
|
lowercasedName[0] = std::tolower(lowercasedName[0]);
|
|
argBufferBuilder
|
|
.buffer(info.binding * 2 + 0, name.c_str(), lowercasedName);
|
|
if (any(info.flags & DescriptorFlags::DYNAMIC_OFFSET)) {
|
|
// Note: this requires that the sets and descriptors are sorted (at least
|
|
// the uniforms).
|
|
mslCompiler.add_dynamic_buffer(
|
|
setIndex, info.binding * 2 + 0, dynamicOffsetsBufferIndex++);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case DescriptorType::SAMPLER_2D_FLOAT:
|
|
case DescriptorType::SAMPLER_2D_INT:
|
|
case DescriptorType::SAMPLER_2D_UINT:
|
|
case DescriptorType::SAMPLER_2D_DEPTH:
|
|
case DescriptorType::SAMPLER_2D_ARRAY_FLOAT:
|
|
case DescriptorType::SAMPLER_2D_ARRAY_INT:
|
|
case DescriptorType::SAMPLER_2D_ARRAY_UINT:
|
|
case DescriptorType::SAMPLER_2D_ARRAY_DEPTH:
|
|
case DescriptorType::SAMPLER_CUBE_FLOAT:
|
|
case DescriptorType::SAMPLER_CUBE_INT:
|
|
case DescriptorType::SAMPLER_CUBE_UINT:
|
|
case DescriptorType::SAMPLER_CUBE_DEPTH:
|
|
case DescriptorType::SAMPLER_CUBE_ARRAY_FLOAT:
|
|
case DescriptorType::SAMPLER_CUBE_ARRAY_INT:
|
|
case DescriptorType::SAMPLER_CUBE_ARRAY_UINT:
|
|
case DescriptorType::SAMPLER_CUBE_ARRAY_DEPTH:
|
|
case DescriptorType::SAMPLER_3D_FLOAT:
|
|
case DescriptorType::SAMPLER_3D_INT:
|
|
case DescriptorType::SAMPLER_3D_UINT:
|
|
case DescriptorType::SAMPLER_2D_MS_FLOAT:
|
|
case DescriptorType::SAMPLER_2D_MS_INT:
|
|
case DescriptorType::SAMPLER_2D_MS_UINT:
|
|
case DescriptorType::SAMPLER_2D_MS_ARRAY_FLOAT:
|
|
case DescriptorType::SAMPLER_2D_MS_ARRAY_INT:
|
|
case DescriptorType::SAMPLER_2D_MS_ARRAY_UINT:
|
|
case DescriptorType::SAMPLER_EXTERNAL: {
|
|
assert_invariant(sampler.has_value());
|
|
const std::string samplerName = std::string(name.c_str()) + "Smplr";
|
|
argBufferBuilder
|
|
.texture(info.binding * 2 + 0, name.c_str(), sampler->type,
|
|
sampler->format, sampler->multisample)
|
|
.sampler(info.binding * 2 + 1, samplerName);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
argumentBuffers.push_back(argBufferBuilder.build());
|
|
}
|
|
|
|
// Bind push constants to [buffer(26)]
|
|
MSLResourceBinding pushConstantBinding;
|
|
// the baseType doesn't matter, but can't be UNKNOWN
|
|
pushConstantBinding.basetype = SPIRType::BaseType::Struct;
|
|
pushConstantBinding.stage = executionModel;
|
|
pushConstantBinding.desc_set = kPushConstDescSet;
|
|
pushConstantBinding.binding = kPushConstBinding;
|
|
pushConstantBinding.count = 1;
|
|
pushConstantBinding.msl_buffer = CodeGenerator::METAL_PUSH_CONSTANT_BUFFER_INDEX;
|
|
mslCompiler.add_msl_resource_binding(pushConstantBinding);
|
|
|
|
*outMsl = mslCompiler.compile();
|
|
if (minifier) {
|
|
*outMsl = minifier->removeWhitespace(*outMsl);
|
|
}
|
|
|
|
// Replace spirv-cross' generated argument buffers with our own.
|
|
for (auto* argBuffer : argumentBuffers) {
|
|
auto argBufferMsl = argBuffer->getMsl();
|
|
MetalArgumentBuffer::replaceInShader(*outMsl, argBuffer->getName(), argBufferMsl);
|
|
MetalArgumentBuffer::destroy(&argBuffer);
|
|
}
|
|
}
|
|
|
|
void GLSLPostProcessor::rebindImageSamplerForWGSL(std::vector<uint32_t> &spirv) {
|
|
constexpr size_t HEADER_SIZE = 5;
|
|
size_t const dataSize = spirv.size();
|
|
uint32_t *data = spirv.data();
|
|
|
|
std::set<uint32_t> samplerTargetIDs;
|
|
|
|
auto pass = [&](uint32_t targetOp, std::function<void(uint32_t)> f) {
|
|
for (uint32_t cursor = HEADER_SIZE, cursorEnd = dataSize; cursor < cursorEnd;) {
|
|
uint32_t const firstWord = data[cursor];
|
|
uint32_t const wordCount = firstWord >> 16;
|
|
uint32_t const op = firstWord & 0x0000FFFF;
|
|
if (targetOp == op) {
|
|
f(cursor + 1);
|
|
}
|
|
cursor += wordCount;
|
|
}
|
|
};
|
|
|
|
//Parse through debug name info to determine which bindings are samplers and which are not.
|
|
// This is possible because the sampler splitting pass outputs sampler and texture pairs of the form:
|
|
// `uniform sampler2D var_x` => `uniform sampler var_sampler` and `uniform texture2D var_texture`;
|
|
// TODO: This works, but may limit what optimizations can be done and has the potential to collide with user
|
|
// variable names. Ideally, trace usage to determine binding type.
|
|
pass(spv::Op::OpName, [&](uint32_t pos) {
|
|
auto target = data[pos];
|
|
char *name = (char *) &data[pos + 1];
|
|
std::string_view view(name);
|
|
if (view.find("_sampler") != std::string_view::npos) {
|
|
samplerTargetIDs.insert(target);
|
|
}
|
|
});
|
|
|
|
// Write out the offset bindings
|
|
pass(spv::Op::OpDecorate, [&](uint32_t pos) {
|
|
uint32_t const type = data[pos + 1];
|
|
if (type == spv::Decoration::DecorationBinding) {
|
|
uint32_t const targetVar = data[pos];
|
|
if (samplerTargetIDs.find(targetVar) != samplerTargetIDs.end()) {
|
|
data[pos + 2] = data[pos + 2] * 2 + 1;
|
|
} else {
|
|
data[pos + 2] = data[pos + 2] * 2;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
bool GLSLPostProcessor::spirvToWgsl(SpirvBlob *spirv, std::string *outWsl) {
|
|
#if FILAMENT_SUPPORTS_WEBGPU
|
|
//We need to run some opt-passes at all times to transpile to WGSL
|
|
auto optimizer = createEmptyOptimizer();
|
|
optimizer->RegisterPass(CreateSplitCombinedImageSamplerPass());
|
|
optimizeSpirv(optimizer, *spirv);
|
|
|
|
//After splitting the image samplers, we need to remap the bindings to separate them.
|
|
rebindImageSamplerForWGSL(*spirv);
|
|
|
|
//Allow non-uniform derivatives due to our nested shaders. See https://github.com/gpuweb/gpuweb/issues/3479
|
|
const tint::spirv::reader::Options readerOpts{true};
|
|
|
|
tint::Program tintRead = tint::spirv::reader::Read(*spirv, readerOpts);
|
|
|
|
if (tintRead.Diagnostics().ContainsErrors()) {
|
|
//We know errors can potentially crop up, and want the ability to ignore them if needed for sample bringup
|
|
#ifndef FILAMENT_WEBGPU_IGNORE_TNT_READ_ERRORS
|
|
slog.e << "Tint Reader Error: " << tintRead.Diagnostics().Str() << io::endl;
|
|
spv_context context = spvContextCreate(SPV_ENV_VULKAN_1_1_SPIRV_1_4);
|
|
spv_text text = nullptr;
|
|
spv_diagnostic diagnostic = nullptr;
|
|
spv_result_t result = spvBinaryToText(
|
|
context,
|
|
spirv->data(),
|
|
spirv->size(),
|
|
SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES | SPV_BINARY_TO_TEXT_OPTION_COLOR,
|
|
&text,
|
|
&diagnostic);
|
|
slog.e << "Beginning SpirV-output dump with ret " << result << "\n\n" << text->str << "\n\nEndSPIRV\n" <<
|
|
io::endl;
|
|
spvTextDestroy(text);
|
|
slog.e << "Tint Reader Error: " << tintRead.Diagnostics().Str() << io::endl;
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
tint::Result<tint::wgsl::writer::Output> wgslOut = tint::wgsl::writer::Generate(tintRead);
|
|
/// An instance of SuccessType that can be used to check a tint Result.
|
|
tint::SuccessType tintSuccess;
|
|
|
|
if (wgslOut != tintSuccess) {
|
|
slog.e << "Tint writer error: " << wgslOut.Failure().reason << io::endl;
|
|
return false;
|
|
}
|
|
// Tint adds annotations that Dawn complains about when consuming. remove for now
|
|
// https://dawn.googlesource.com/dawn/+/efb17b02543fb52c0b2e21d6082c0c9fbc2168a9%5E%21/
|
|
char const* annotationStr = "@stride(16) @internal(disable_validation__ignore_stride)";
|
|
size_t pos = wgslOut->wgsl.find(annotationStr);
|
|
while (pos != std::string::npos) {
|
|
wgslOut->wgsl.erase(pos, strlen(annotationStr));
|
|
pos = wgslOut->wgsl.find(annotationStr);
|
|
}
|
|
*outWsl = wgslOut->wgsl;
|
|
return true;
|
|
#else
|
|
slog.i << "Trying to emit WGSL without including WebGPU dependencies,"
|
|
" please set CMake arg FILAMENT_SUPPORTS_WEBGPU and FILAMENT_SUPPORTS_WEBGPU"
|
|
<< io::endl;
|
|
return false;
|
|
#endif
|
|
|
|
}
|
|
|
|
bool GLSLPostProcessor::process(const std::string& inputShader, Config const& config,
|
|
std::string* outputGlsl, SpirvBlob* outputSpirv, std::string* outputMsl, std::string* outputWgsl) {
|
|
using TargetLanguage = MaterialBuilder::TargetLanguage;
|
|
|
|
if (config.targetLanguage == TargetLanguage::GLSL &&
|
|
mOptimization == MaterialBuilder::Optimization::NONE) {
|
|
*outputGlsl = inputShader;
|
|
if (mPrintShaders) {
|
|
slog.i << *outputGlsl << io::endl;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
InternalConfig internalConfig{
|
|
.glslOutput = outputGlsl,
|
|
.spirvOutput = outputSpirv,
|
|
.mslOutput = outputMsl,
|
|
.wgslOutput = outputWgsl,
|
|
};
|
|
|
|
switch (config.shaderType) {
|
|
case ShaderStage::VERTEX:
|
|
internalConfig.shLang = EShLangVertex;
|
|
break;
|
|
case ShaderStage::FRAGMENT:
|
|
internalConfig.shLang = EShLangFragment;
|
|
break;
|
|
case ShaderStage::COMPUTE:
|
|
internalConfig.shLang = EShLangCompute;
|
|
break;
|
|
}
|
|
|
|
TProgram program;
|
|
TShader tShader(internalConfig.shLang);
|
|
|
|
// The cleaner must be declared after the TShader to prevent ASAN failures.
|
|
GLSLangCleaner const cleaner;
|
|
|
|
const char* shaderCString = inputShader.c_str();
|
|
tShader.setStrings(&shaderCString, 1);
|
|
|
|
// This allows shaders to query if they will be run through glslang.
|
|
// OpenGL shaders without optimization, for example, won't have this define.
|
|
tShader.setPreamble("#define FILAMENT_GLSLANG\n");
|
|
|
|
internalConfig.langVersion = GLSLTools::getGlslDefaultVersion(config.shaderModel);
|
|
GLSLTools::prepareShaderParser(config.targetApi, config.targetLanguage, tShader,
|
|
internalConfig.shLang, internalConfig.langVersion);
|
|
|
|
EShMessages msg = GLSLTools::glslangFlagsFromTargetApi(config.targetApi, config.targetLanguage);
|
|
if (config.hasFramebufferFetch) {
|
|
// FIXME: subpasses require EShMsgVulkanRules, which I think is a mistake.
|
|
// SpvRules should be enough.
|
|
// I think this could cause the compilation to fail on gl_VertexID.
|
|
using Type = std::underlying_type_t<EShMessages>;
|
|
msg = EShMessages(Type(msg) | Type(EShMsgVulkanRules));
|
|
}
|
|
|
|
bool const ok = tShader.parse(&DefaultTBuiltInResource, internalConfig.langVersion, false, msg);
|
|
if (!ok) {
|
|
slog.e << tShader.getInfoLog() << io::endl;
|
|
return false;
|
|
}
|
|
|
|
// add texture lod bias
|
|
if (config.shaderType == ShaderStage::FRAGMENT &&
|
|
config.domain == MaterialDomain::SURFACE) {
|
|
GLSLTools::textureLodBias(tShader);
|
|
}
|
|
|
|
program.addShader(&tShader);
|
|
// Even though we only have a single shader stage, linking is still necessary to finalize
|
|
// SPIR-V types
|
|
bool const linkOk = program.link(msg);
|
|
if (!linkOk) {
|
|
slog.e << tShader.getInfoLog() << io::endl;
|
|
return false;
|
|
}
|
|
|
|
switch (mOptimization) {
|
|
case MaterialBuilder::Optimization::NONE:
|
|
if (internalConfig.spirvOutput) {
|
|
SpvOptions options;
|
|
options.generateDebugInfo = mGenerateDebugInfo;
|
|
GlslangToSpv(*program.getIntermediate(internalConfig.shLang),
|
|
*internalConfig.spirvOutput, &options);
|
|
fixupClipDistance(*internalConfig.spirvOutput, config);
|
|
if (internalConfig.mslOutput) {
|
|
auto sibs = SibVector::with_capacity(CONFIG_SAMPLER_BINDING_COUNT);
|
|
DescriptorSets descriptors {};
|
|
msl::collectDescriptorSets(config, descriptors);
|
|
#if DEBUG_LOG_DESCRIPTOR_SETS == 1
|
|
msl::prettyPrintDescriptorSetInfoVector(descriptors);
|
|
#endif
|
|
spirvToMsl(internalConfig.spirvOutput, internalConfig.mslOutput,
|
|
config.shaderType, config.shaderModel, config.hasFramebufferFetch, descriptors,
|
|
mGenerateDebugInfo ? &internalConfig.minifier : nullptr);
|
|
}
|
|
if (internalConfig.wgslOutput) {
|
|
if (!spirvToWgsl(internalConfig.spirvOutput, internalConfig.wgslOutput)) {
|
|
return false;
|
|
}
|
|
}
|
|
} else {
|
|
slog.e << "GLSL post-processor invoked with optimization level NONE"
|
|
<< io::endl;
|
|
}
|
|
break;
|
|
case MaterialBuilder::Optimization::PREPROCESSOR:
|
|
if (!preprocessOptimization(tShader, config, internalConfig)) {
|
|
return false;
|
|
}
|
|
break;
|
|
case MaterialBuilder::Optimization::SIZE:
|
|
case MaterialBuilder::Optimization::PERFORMANCE:
|
|
if (!fullOptimization(tShader, config, internalConfig)) {
|
|
return false;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (internalConfig.glslOutput) {
|
|
if (!mGenerateDebugInfo) {
|
|
*internalConfig.glslOutput =
|
|
internalConfig.minifier.removeWhitespace(
|
|
*internalConfig.glslOutput,
|
|
mOptimization == MaterialBuilder::Optimization::SIZE);
|
|
|
|
// In theory this should only be enabled for SIZE, but in practice we often use PERFORMANCE.
|
|
if (mOptimization != MaterialBuilder::Optimization::NONE) {
|
|
*internalConfig.glslOutput =
|
|
internalConfig.minifier.renameStructFields(*internalConfig.glslOutput);
|
|
}
|
|
}
|
|
if (mPrintShaders) {
|
|
slog.i << *internalConfig.glslOutput << io::endl;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool GLSLPostProcessor::preprocessOptimization(TShader& tShader,
|
|
Config const& config, InternalConfig& internalConfig) const {
|
|
using TargetApi = MaterialBuilder::TargetApi;
|
|
assert_invariant(bool(internalConfig.spirvOutput) == (config.targetApi != TargetApi::OPENGL));
|
|
|
|
std::string glsl;
|
|
TShader::ForbidIncluder forbidIncluder;
|
|
|
|
const int version = GLSLTools::getGlslDefaultVersion(config.shaderModel);
|
|
EShMessages const msg =
|
|
GLSLTools::glslangFlagsFromTargetApi(config.targetApi, config.targetLanguage);
|
|
bool ok = tShader.preprocess(&DefaultTBuiltInResource, version, ENoProfile, false, false,
|
|
msg, &glsl, forbidIncluder);
|
|
|
|
if (!ok) {
|
|
slog.e << tShader.getInfoLog() << io::endl;
|
|
return false;
|
|
}
|
|
|
|
if (internalConfig.spirvOutput) {
|
|
TProgram program;
|
|
TShader spirvShader(internalConfig.shLang);
|
|
|
|
// The cleaner must be declared after the TShader/TProgram which are setting the current
|
|
// pool in the tls
|
|
GLSLangCleaner const cleaner;
|
|
|
|
const char* shaderCString = glsl.c_str();
|
|
spirvShader.setStrings(&shaderCString, 1);
|
|
GLSLTools::prepareShaderParser(config.targetApi, config.targetLanguage, spirvShader,
|
|
internalConfig.shLang, internalConfig.langVersion);
|
|
ok = spirvShader.parse(&DefaultTBuiltInResource, internalConfig.langVersion, false, msg);
|
|
program.addShader(&spirvShader);
|
|
// Even though we only have a single shader stage, linking is still necessary to finalize
|
|
// SPIR-V types
|
|
bool const linkOk = program.link(msg);
|
|
if (!ok || !linkOk) {
|
|
slog.e << spirvShader.getInfoLog() << io::endl;
|
|
return false;
|
|
} else {
|
|
SpvOptions options;
|
|
options.generateDebugInfo = mGenerateDebugInfo;
|
|
GlslangToSpv(*program.getIntermediate(internalConfig.shLang),
|
|
*internalConfig.spirvOutput, &options);
|
|
fixupClipDistance(*internalConfig.spirvOutput, config);
|
|
}
|
|
}
|
|
|
|
if (internalConfig.mslOutput) {
|
|
DescriptorSets descriptors {};
|
|
msl::collectDescriptorSets(config, descriptors);
|
|
#if DEBUG_LOG_DESCRIPTOR_SETS == 1
|
|
msl::prettyPrintDescriptorSetInfoVector(descriptors);
|
|
#endif
|
|
spirvToMsl(internalConfig.spirvOutput, internalConfig.mslOutput, config.shaderType,
|
|
config.shaderModel, config.hasFramebufferFetch, descriptors,
|
|
mGenerateDebugInfo ? &internalConfig.minifier : nullptr);
|
|
}
|
|
if (internalConfig.wgslOutput) {
|
|
if (!spirvToWgsl(internalConfig.spirvOutput, internalConfig.wgslOutput)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
if (internalConfig.glslOutput) {
|
|
*internalConfig.glslOutput = glsl;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool GLSLPostProcessor::fullOptimization(const TShader& tShader,
|
|
Config const& config, InternalConfig& internalConfig) const {
|
|
SpirvBlob spirv;
|
|
|
|
bool const optimizeForSize = mOptimization == MaterialBuilderBase::Optimization::SIZE;
|
|
|
|
// Compile GLSL to to SPIR-V
|
|
SpvOptions options;
|
|
options.generateDebugInfo = mGenerateDebugInfo;
|
|
GlslangToSpv(*tShader.getIntermediate(), spirv, &options);
|
|
|
|
if (internalConfig.spirvOutput) {
|
|
// Run the SPIR-V optimizer
|
|
OptimizerPtr const optimizer = createOptimizer(mOptimization, config);
|
|
optimizeSpirv(optimizer, spirv);
|
|
} else {
|
|
if (!optimizeForSize) {
|
|
OptimizerPtr const optimizer = createOptimizer(mOptimization, config);
|
|
optimizeSpirv(optimizer, spirv);
|
|
}
|
|
}
|
|
|
|
fixupClipDistance(spirv, config);
|
|
|
|
if (internalConfig.spirvOutput) {
|
|
*internalConfig.spirvOutput = spirv;
|
|
}
|
|
|
|
if (internalConfig.mslOutput) {
|
|
DescriptorSets descriptors {};
|
|
msl::collectDescriptorSets(config, descriptors);
|
|
#if DEBUG_LOG_DESCRIPTOR_SETS == 1
|
|
msl::prettyPrintDescriptorSetInfoVector(descriptors);
|
|
#endif
|
|
spirvToMsl(&spirv, internalConfig.mslOutput, config.shaderType, config.shaderModel,
|
|
config.hasFramebufferFetch, descriptors,
|
|
mGenerateDebugInfo ? &internalConfig.minifier : nullptr);
|
|
}
|
|
if (internalConfig.wgslOutput) {
|
|
if (!spirvToWgsl(&spirv, internalConfig.wgslOutput)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Transpile back to GLSL
|
|
if (internalConfig.glslOutput) {
|
|
CompilerGLSL::Options glslOptions;
|
|
auto version = GLSLTools::getShadingLanguageVersion(
|
|
config.shaderModel, config.featureLevel);
|
|
glslOptions.es = version.second;
|
|
glslOptions.version = version.first;
|
|
glslOptions.enable_420pack_extension = glslOptions.version >= 420;
|
|
glslOptions.fragment.default_float_precision = glslOptions.es ?
|
|
CompilerGLSL::Options::Precision::Mediump : CompilerGLSL::Options::Precision::Highp;
|
|
glslOptions.fragment.default_int_precision = glslOptions.es ?
|
|
CompilerGLSL::Options::Precision::Mediump : CompilerGLSL::Options::Precision::Highp;
|
|
|
|
// TODO: this should be done only on the "feature level 0" variant
|
|
if (config.featureLevel == 0) {
|
|
// convert UBOs to plain uniforms if we're at feature level 0
|
|
glslOptions.emit_uniform_buffer_as_plain_uniforms = true;
|
|
}
|
|
|
|
if (config.variant.hasStereo() && config.shaderType == ShaderStage::VERTEX) {
|
|
switch (config.materialInfo->stereoscopicType) {
|
|
case StereoscopicType::MULTIVIEW:
|
|
// For stereo variants using multiview feature, this generates the shader code below.
|
|
// #extension GL_OVR_multiview2 : require
|
|
// layout(num_views = 2) in;
|
|
glslOptions.ovr_multiview_view_count = config.materialInfo->stereoscopicEyeCount;
|
|
break;
|
|
case StereoscopicType::INSTANCED:
|
|
case StereoscopicType::NONE:
|
|
// Nothing to generate
|
|
break;
|
|
}
|
|
}
|
|
|
|
CompilerGLSL glslCompiler(std::move(spirv));
|
|
glslCompiler.set_common_options(glslOptions);
|
|
|
|
if (!glslOptions.es) {
|
|
// enable GL_ARB_shading_language_packing if available
|
|
glslCompiler.add_header_line("#extension GL_ARB_shading_language_packing : enable");
|
|
}
|
|
|
|
if (tShader.getStage() == EShLangFragment && glslOptions.es) {
|
|
for (auto i : config.glsl.subpassInputToColorLocation) {
|
|
glslCompiler.remap_ext_framebuffer_fetch(i.first, i.second, true);
|
|
}
|
|
}
|
|
|
|
#ifdef SPIRV_CROSS_EXCEPTIONS_TO_ASSERTIONS
|
|
*internalConfig.glslOutput = glslCompiler.compile();
|
|
#else
|
|
try {
|
|
*internalConfig.glslOutput = glslCompiler.compile();
|
|
} catch (CompilerError e) {
|
|
slog.e << "ERROR: " << e.what() << io::endl;
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
// spirv-cross automatically redeclares gl_ClipDistance if it's used. Some drivers don't
|
|
// like this, so we simply remove it.
|
|
// According to EXT_clip_cull_distance, gl_ClipDistance can be
|
|
// "implicitly sized by indexing it only with integral constant expressions".
|
|
std::string& str = *internalConfig.glslOutput;
|
|
const std::string clipDistanceDefinition = "out float gl_ClipDistance[2];";
|
|
size_t const found = str.find(clipDistanceDefinition);
|
|
if (found != std::string::npos) {
|
|
str.replace(found, clipDistanceDefinition.length(), "");
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
std::shared_ptr<Optimizer> GLSLPostProcessor::createEmptyOptimizer() {
|
|
auto optimizer = std::make_shared<Optimizer>(SPV_ENV_UNIVERSAL_1_3);
|
|
optimizer->SetMessageConsumer([](spv_message_level_t level,
|
|
const char* source, const spv_position_t& position, const char* message) {
|
|
if (!filterSpvOptimizerMessage(level)) {
|
|
return;
|
|
}
|
|
slog.e << stringifySpvOptimizerMessage(level, source, position, message)
|
|
<< io::endl;
|
|
});
|
|
return optimizer;
|
|
}
|
|
|
|
std::shared_ptr<Optimizer> GLSLPostProcessor::createOptimizer(
|
|
MaterialBuilder::Optimization optimization, Config const& config) {
|
|
auto optimizer = createEmptyOptimizer();
|
|
|
|
if (optimization == MaterialBuilder::Optimization::SIZE) {
|
|
// When optimizing for size, we don't run the SPIR-V through any size optimization passes
|
|
// when targeting MSL. This results in better line dictionary compression. We do, however,
|
|
// still register the passes necessary (below) to support half precision floating point
|
|
// math.
|
|
if (config.targetApi != MaterialBuilder::TargetApi::METAL) {
|
|
registerSizePasses(*optimizer, config);
|
|
}
|
|
} else if (optimization == MaterialBuilder::Optimization::PERFORMANCE) {
|
|
registerPerformancePasses(*optimizer, config);
|
|
}
|
|
|
|
// Metal doesn't support relaxed precision, but does have support for float16 math operations.
|
|
if (config.targetApi == MaterialBuilder::TargetApi::METAL) {
|
|
optimizer->RegisterPass(CreateConvertRelaxedToHalfPass());
|
|
optimizer->RegisterPass(CreateSimplificationPass());
|
|
optimizer->RegisterPass(CreateRedundancyEliminationPass());
|
|
optimizer->RegisterPass(CreateAggressiveDCEPass());
|
|
}
|
|
|
|
return optimizer;
|
|
}
|
|
|
|
void GLSLPostProcessor::optimizeSpirv(OptimizerPtr optimizer, SpirvBlob& spirv) {
|
|
|
|
// Always add the CanonicalizeIds Pass.
|
|
// The CanonicalIds pass replaces the old SPIR-V remapper in Glslang.
|
|
optimizer->RegisterPass(CreateCanonicalizeIdsPass());
|
|
|
|
// run optimizer
|
|
if (!optimizer->Run(spirv.data(), spirv.size(), &spirv)) {
|
|
slog.e << "SPIR-V optimizer pass failed" << io::endl;
|
|
return;
|
|
}
|
|
}
|
|
|
|
void GLSLPostProcessor::fixupClipDistance(
|
|
SpirvBlob& spirv, Config const& config) const {
|
|
if (!config.usesClipDistance) {
|
|
return;
|
|
}
|
|
// This should match the version of SPIR-V used in GLSLTools::prepareShaderParser.
|
|
SpirvTools const tools(SPV_ENV_UNIVERSAL_1_3);
|
|
std::string disassembly;
|
|
const bool result = tools.Disassemble(spirv, &disassembly);
|
|
assert_invariant(result);
|
|
if (filamat::fixupClipDistance(disassembly)) {
|
|
spirv.clear();
|
|
tools.Assemble(disassembly, &spirv);
|
|
assert_invariant(tools.Validate(spirv));
|
|
}
|
|
}
|
|
|
|
// CreateMergeReturnPass() causes these issues:
|
|
// - triggers a segfault with AMD OpenGL drivers on macOS
|
|
// - triggers a crash on some Adreno drivers (b/291140208, b/289401984, b/289393290)
|
|
// However Metal requires this pass in order to correctly generate half-precision MSL
|
|
// CreateMergeReturnPass() also creates issues with Tint conversion related to the
|
|
// bitwise "<<" Operator used in shaders/src/surface_light_directional.fs against
|
|
// a signed integer.
|
|
//
|
|
// CreateSimplificationPass() creates a lot of problems:
|
|
// - Adreno GPU show artifacts after running simplification passes (Vulkan)
|
|
// - spirv-cross fails generating working glsl
|
|
// (https://github.com/KhronosGroup/SPIRV-Cross/issues/2162)
|
|
// - generally it makes the code more complicated, e.g.: replacing for loops with
|
|
// while-if-break, unclear if it helps for anything.
|
|
// However, the simplification passes below are necessary when targeting Metal, otherwise the
|
|
// result is mismatched half / float assignments in MSL.
|
|
|
|
void GLSLPostProcessor::registerPerformancePasses(Optimizer& optimizer, Config const& config) {
|
|
auto RegisterPass = [&](Optimizer::PassToken&& pass,
|
|
MaterialBuilder::TargetApi apiFilter = MaterialBuilder::TargetApi::ALL) {
|
|
// Workaround management is currently very simple, only two values are possible
|
|
// ALL and NONE. If the value is anything but NONE, we apply all workarounds.
|
|
if (config.workarounds != MaterialBuilderBase::Workarounds::NONE) {
|
|
if (!(config.targetApi & apiFilter)) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
// FIXME: Workaround within a workaround!!! We IGNORE config.workarounds for WEBGPU
|
|
// because Tint doesn't even compile with MergeReturn/Simplification pass
|
|
// active:
|
|
// Tint Reader Error: warning: code is unreachable
|
|
// error: no matching overload for 'operator << (i32, i32)'
|
|
// 2 candidate operators:
|
|
// • 'operator << (T ✓ , u32 ✗ ) -> T' where:
|
|
// ✓ 'T' is 'abstract-int', 'i32' or 'u32'
|
|
// • 'operator << (vecN<T> ✗ , vecN<u32> ✗ ) -> vecN<T>' where:
|
|
// ✗ 'T' is 'abstract-int', 'i32' or 'u32'
|
|
if (any(config.targetApi & MaterialBuilder::TargetApi::WEBGPU)) {
|
|
if (!(config.targetApi & apiFilter)) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
optimizer.RegisterPass(std::move(pass));
|
|
};
|
|
|
|
RegisterPass(CreateWrapOpKillPass());
|
|
RegisterPass(CreateDeadBranchElimPass());
|
|
RegisterPass(CreateMergeReturnPass(), MaterialBuilder::TargetApi::METAL);
|
|
RegisterPass(CreateInlineExhaustivePass());
|
|
RegisterPass(CreateAggressiveDCEPass());
|
|
RegisterPass(CreatePrivateToLocalPass());
|
|
RegisterPass(CreateLocalSingleBlockLoadStoreElimPass());
|
|
RegisterPass(CreateLocalSingleStoreElimPass());
|
|
RegisterPass(CreateAggressiveDCEPass());
|
|
RegisterPass(CreateScalarReplacementPass());
|
|
RegisterPass(CreateLocalAccessChainConvertPass());
|
|
RegisterPass(CreateLocalSingleBlockLoadStoreElimPass());
|
|
RegisterPass(CreateLocalSingleStoreElimPass());
|
|
RegisterPass(CreateAggressiveDCEPass());
|
|
RegisterPass(CreateLocalMultiStoreElimPass());
|
|
RegisterPass(CreateAggressiveDCEPass());
|
|
RegisterPass(CreateCCPPass());
|
|
RegisterPass(CreateAggressiveDCEPass());
|
|
RegisterPass(CreateRedundancyEliminationPass());
|
|
RegisterPass(CreateCombineAccessChainsPass());
|
|
RegisterPass(CreateSimplificationPass(), MaterialBuilder::TargetApi::METAL);
|
|
RegisterPass(CreateVectorDCEPass());
|
|
RegisterPass(CreateDeadInsertElimPass());
|
|
RegisterPass(CreateDeadBranchElimPass());
|
|
RegisterPass(CreateSimplificationPass(), MaterialBuilder::TargetApi::METAL);
|
|
RegisterPass(CreateIfConversionPass());
|
|
RegisterPass(CreateCopyPropagateArraysPass());
|
|
RegisterPass(CreateReduceLoadSizePass());
|
|
RegisterPass(CreateAggressiveDCEPass());
|
|
RegisterPass(CreateBlockMergePass());
|
|
RegisterPass(CreateRedundancyEliminationPass());
|
|
RegisterPass(CreateDeadBranchElimPass());
|
|
RegisterPass(CreateBlockMergePass());
|
|
RegisterPass(CreateSimplificationPass(), MaterialBuilder::TargetApi::METAL);
|
|
}
|
|
|
|
void GLSLPostProcessor::registerSizePasses(Optimizer& optimizer, Config const& config) {
|
|
auto RegisterPass = [&](Optimizer::PassToken&& pass,
|
|
MaterialBuilder::TargetApi apiFilter = MaterialBuilder::TargetApi::ALL) {
|
|
if (!(config.targetApi & apiFilter)) {
|
|
return;
|
|
}
|
|
optimizer.RegisterPass(std::move(pass));
|
|
};
|
|
|
|
RegisterPass(CreateWrapOpKillPass());
|
|
RegisterPass(CreateDeadBranchElimPass());
|
|
RegisterPass(CreateInlineExhaustivePass());
|
|
RegisterPass(CreateEliminateDeadFunctionsPass());
|
|
RegisterPass(CreatePrivateToLocalPass());
|
|
RegisterPass(CreateScalarReplacementPass(0));
|
|
RegisterPass(CreateLocalMultiStoreElimPass());
|
|
RegisterPass(CreateCCPPass());
|
|
RegisterPass(CreateLoopUnrollPass(true));
|
|
RegisterPass(CreateDeadBranchElimPass());
|
|
RegisterPass(CreateScalarReplacementPass(0));
|
|
RegisterPass(CreateLocalSingleStoreElimPass());
|
|
RegisterPass(CreateIfConversionPass());
|
|
RegisterPass(CreateAggressiveDCEPass());
|
|
RegisterPass(CreateDeadBranchElimPass());
|
|
RegisterPass(CreateBlockMergePass());
|
|
RegisterPass(CreateLocalAccessChainConvertPass());
|
|
RegisterPass(CreateLocalSingleBlockLoadStoreElimPass());
|
|
RegisterPass(CreateAggressiveDCEPass());
|
|
RegisterPass(CreateCopyPropagateArraysPass());
|
|
RegisterPass(CreateVectorDCEPass());
|
|
RegisterPass(CreateDeadInsertElimPass());
|
|
// this breaks UBO layout
|
|
//RegisterPass(CreateEliminateDeadMembersPass());
|
|
RegisterPass(CreateLocalSingleStoreElimPass());
|
|
RegisterPass(CreateBlockMergePass());
|
|
RegisterPass(CreateLocalMultiStoreElimPass());
|
|
RegisterPass(CreateRedundancyEliminationPass());
|
|
RegisterPass(CreateAggressiveDCEPass());
|
|
RegisterPass(CreateCFGCleanupPass());
|
|
}
|
|
|
|
} // namespace filamat
|