Support backends with multiple shader languages and precompiled Metal libraries (#7769)

This commit is contained in:
Ben Doherty
2024-04-21 21:59:22 -04:00
committed by Benjamin Doherty
parent d9cba80bcf
commit 35fa79ec23
11 changed files with 159 additions and 79 deletions

View File

@@ -84,6 +84,9 @@ public:
// null terminating character.
Program& shader(ShaderStage shader, void const* data, size_t size);
// sets the language of the shader sources provided with shader() (defaults to ESSL3)
Program& shaderLanguage(ShaderLanguage shaderLanguage);
// Note: This is only needed for GLES3.0 backends, because the layout(binding=) syntax is
// not permitted in glsl. The backend needs a way to associate a uniform block
// to a binding point.
@@ -136,6 +139,8 @@ public:
utils::CString const& getName() const noexcept { return mName; }
utils::CString& getName() noexcept { return mName; }
auto const& getShaderLanguage() const { return mShaderLanguage; }
utils::FixedCapacityVector<SpecializationConstant> const& getSpecializationConstants() const noexcept {
return mSpecializationConstants;
}
@@ -155,6 +160,7 @@ private:
UniformBlockInfo mUniformBlocks = {};
SamplerGroupInfo mSamplerGroups = {};
ShaderSource mShadersSource;
ShaderLanguage mShaderLanguage = ShaderLanguage::ESSL3;
utils::CString mName;
uint64_t mCacheId{};
utils::Invocable<utils::io::ostream&(utils::io::ostream& out)> mLogger;

View File

@@ -21,7 +21,7 @@ namespace filament::backend {
using namespace utils;
// We want these in the .cpp file, so they're not inlined (not worth it)
Program::Program() noexcept { // NOLINT(modernize-use-equals-default)
Program::Program() noexcept { // NOLINT(modernize-use-equals-default)
}
Program::Program(Program&& rhs) noexcept = default;
@@ -47,6 +47,11 @@ Program& Program::shader(ShaderStage shader, void const* data, size_t size) {
return *this;
}
Program& Program::shaderLanguage(ShaderLanguage shaderLanguage) {
mShaderLanguage = shaderLanguage;
return *this;
}
Program& Program::uniformBlockBindings(
FixedCapacityVector<std::pair<utils::CString, uint8_t>> const& uniformBlockBindings) noexcept {
for (auto const& item : uniformBlockBindings) {

View File

@@ -106,26 +106,41 @@ bool MetalShaderCompiler::isParallelShaderCompileSupported() const noexcept {
continue;
}
assert_invariant(source[source.size() - 1] == '\0');
// the shader string is null terminated and the length includes the null character
NSString* objcSource = [[NSString alloc] initWithBytes:source.data()
length:source.size() - 1
encoding:NSUTF8StringEncoding];
// By default, Metal uses the most recent language version.
MTLCompileOptions* options = [MTLCompileOptions new];
// Disable Fast Math optimizations.
// This ensures that operations adhere to IEEE standards for floating-point arithmetic,
// which is crucial for half precision floats in scenarios where fast math optimizations
// lead to inaccuracies, such as in handling special values like NaN or Infinity.
options.fastMathEnabled = NO;
NSError* error = nil;
id<MTLLibrary> library = [device newLibraryWithSource:objcSource
options:options
error:&error];
id<MTLLibrary> library = nil;
switch (program.getShaderLanguage()) {
case ShaderLanguage::MSL: {
// By default, Metal uses the most recent language version.
MTLCompileOptions* options = [MTLCompileOptions new];
// Disable Fast Math optimizations.
// This ensures that operations adhere to IEEE standards for floating-point
// arithmetic, which is crucial for half precision floats in scenarios where fast
// math optimizations lead to inaccuracies, such as in handling special values like
// NaN or Infinity.
options.fastMathEnabled = NO;
assert_invariant(source[source.size() - 1] == '\0');
// the shader string is null terminated and the length includes the null character
NSString* objcSource = [[NSString alloc] initWithBytes:source.data()
length:source.size() - 1
encoding:NSUTF8StringEncoding];
library = [device newLibraryWithSource:objcSource options:options error:&error];
break;
}
case ShaderLanguage::METAL_LIBRARY: {
dispatch_data_t data = dispatch_data_create(source.data(), source.size(),
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
DISPATCH_DATA_DESTRUCTOR_DEFAULT);
library = [device newLibraryWithData:data error:&error];
break;
}
case ShaderLanguage::ESSL1:
case ShaderLanguage::ESSL3:
case ShaderLanguage::SPIRV:
break;
}
if (library == nil) {
NSString* errorMessage = @"unknown error";
if (error) {

View File

@@ -79,6 +79,20 @@ ShaderGenerator::ShaderGenerator(std::string vertex, std::string fragment,
mVertexBlob(transpileShader(ShaderStage::VERTEX, std::move(vertex), backend, isMobile, sib)),
mFragmentBlob(transpileShader(ShaderStage::FRAGMENT, std::move(fragment), backend,
isMobile, sib)) {
switch (backend) {
case Backend::OPENGL:
mShaderLanguage = filament::backend::ShaderLanguage::ESSL3;
break;
case Backend::VULKAN:
mShaderLanguage = filament::backend::ShaderLanguage::SPIRV;
break;
case Backend::METAL:
mShaderLanguage = filament::backend::ShaderLanguage::MSL;
break;
case Backend::NOOP:
mShaderLanguage = filament::backend::ShaderLanguage::ESSL3;
break;
}
}
ShaderGenerator::Blob ShaderGenerator::transpileShader(
@@ -160,6 +174,7 @@ ShaderGenerator::Blob ShaderGenerator::transpileShader(
Program ShaderGenerator::getProgram(filament::backend::DriverApi&) noexcept {
Program program;
program.shaderLanguage(mShaderLanguage);
program.shader(ShaderStage::VERTEX, mVertexBlob.data(), mVertexBlob.size());
program.shader(ShaderStage::FRAGMENT, mFragmentBlob.data(), mFragmentBlob.size());
return program;

View File

@@ -60,6 +60,7 @@ private:
Blob mFragmentBlob;
std::string mCompiledVertexShader;
std::string mCompiledFragmentShader;
filament::backend::ShaderLanguage mShaderLanguage;
};

View File

@@ -58,6 +58,7 @@ kernel void main0() {}
}
Program program;
program.shaderLanguage(ShaderLanguage::ESSL3);
program.shader(ShaderStage::COMPUTE, shader.data(), shader.size() + 1);
Handle<HwProgram> ph = driver.createProgram(std::move(program));
@@ -144,6 +145,7 @@ kernel void main0(device Output_data& output_data [[buffer(0)]],
driver.updateBufferObject(input_data, { data.data(), size }, 0);
Program program;
program.shaderLanguage(ShaderLanguage::ESSL3);
program.shader(ShaderStage::COMPUTE, shader.data(), shader.size() + 1);
Handle<HwProgram> ph = driver.createProgram(std::move(program));

View File

@@ -34,6 +34,7 @@
#include <utils/CString.h>
#include <stdlib.h>
#include <optional>
using namespace utils;
using namespace filament::backend;
@@ -42,36 +43,31 @@ using namespace filamat;
namespace filament {
// ------------------------------------------------------------------------------------------------
MaterialParser::MaterialParserDetails::MaterialParserDetails(ShaderLanguage language, const void* data, size_t size)
: mManagedBuffer(data, size),
mChunkContainer(mManagedBuffer.data(), mManagedBuffer.size()),
mMaterialChunk(mChunkContainer) {
constexpr std::pair<ChunkType, ChunkType> shaderLanguageToTags(ShaderLanguage language) {
switch (language) {
case ShaderLanguage::ESSL3:
mMaterialTag = ChunkType::MaterialGlsl;
mDictionaryTag = ChunkType::DictionaryText;
break;
return { ChunkType::MaterialGlsl, ChunkType::DictionaryText };
case ShaderLanguage::ESSL1:
mMaterialTag = ChunkType::MaterialEssl1;
mDictionaryTag = ChunkType::DictionaryText;
break;
return { ChunkType::MaterialEssl1, ChunkType::DictionaryText };
case ShaderLanguage::MSL:
mMaterialTag = ChunkType::MaterialMetal;
mDictionaryTag = ChunkType::DictionaryText;
break;
return { ChunkType::MaterialMetal, ChunkType::DictionaryText };
case ShaderLanguage::SPIRV:
mMaterialTag = ChunkType::MaterialSpirv;
mDictionaryTag = ChunkType::DictionarySpirv;
break;
return { ChunkType::MaterialSpirv, ChunkType::DictionarySpirv };
case ShaderLanguage::METAL_LIBRARY:
mMaterialTag = ChunkType::MaterialMetalLibrary;
mDictionaryTag = ChunkType::DictionaryMetalLibrary;
break;
return { ChunkType::MaterialMetalLibrary, ChunkType::DictionaryMetalLibrary };
}
}
// ------------------------------------------------------------------------------------------------
MaterialParser::MaterialParserDetails::MaterialParserDetails(
const utils::FixedCapacityVector<ShaderLanguage>& preferredLanguages, const void* data,
size_t size)
: mManagedBuffer(data, size),
mChunkContainer(mManagedBuffer.data(), mManagedBuffer.size()),
mPreferredLanguages(preferredLanguages),
mMaterialChunk(mChunkContainer) {}
template<typename T>
UTILS_NOINLINE
bool MaterialParser::MaterialParserDetails::getFromSimpleChunk(
@@ -87,9 +83,9 @@ bool MaterialParser::MaterialParserDetails::getFromSimpleChunk(
// ------------------------------------------------------------------------------------------------
MaterialParser::MaterialParser(ShaderLanguage language, const void* data, size_t size)
: mImpl(language, data, size) {
}
MaterialParser::MaterialParser(utils::FixedCapacityVector<ShaderLanguage> preferredLanguages,
const void* data, size_t size)
: mImpl(preferredLanguages, data, size) {}
ChunkContainer& MaterialParser::getChunkContainer() noexcept {
return mImpl.mChunkContainer;
@@ -104,20 +100,40 @@ MaterialParser::ParseResult MaterialParser::parse() noexcept {
if (UTILS_UNLIKELY(!cc.parse())) {
return ParseResult::ERROR_OTHER;
}
const ChunkType matTag = mImpl.mMaterialTag;
const ChunkType dictTag = mImpl.mDictionaryTag;
if (UTILS_UNLIKELY(!cc.hasChunk(matTag) || !cc.hasChunk(dictTag))) {
using MaybeShaderLanguageAndChunks =
std::optional<std::tuple<ShaderLanguage, ChunkType, ChunkType>>;
auto chooseLanguage = [this, &cc]() -> MaybeShaderLanguageAndChunks {
for (auto language : mImpl.mPreferredLanguages) {
const auto [matTag, dictTag] = shaderLanguageToTags(language);
if (cc.hasChunk(matTag) && cc.hasChunk(dictTag)) {
return std::make_tuple(language, matTag, dictTag);
}
}
return {};
};
const auto result = chooseLanguage();
if (!result.has_value()) {
return ParseResult::ERROR_MISSING_BACKEND;
}
const auto [chosenLanguage, matTag, dictTag] = result.value();
if (UTILS_UNLIKELY(!DictionaryReader::unflatten(cc, dictTag, mImpl.mBlobDictionary))) {
return ParseResult::ERROR_OTHER;
}
if (UTILS_UNLIKELY(!mImpl.mMaterialChunk.initialize(matTag))) {
return ParseResult::ERROR_OTHER;
}
mImpl.mChosenLanguage = chosenLanguage;
return ParseResult::SUCCESS;
}
backend::ShaderLanguage MaterialParser::getShaderLanguage() const noexcept {
return mImpl.mChosenLanguage;
}
// Accessors
bool MaterialParser::getMaterialVersion(uint32_t* value) const noexcept {
return mImpl.getFromSimpleChunk(ChunkType::MaterialVersion, value);

View File

@@ -29,10 +29,12 @@
#include <backend/DriverEnums.h>
#include <backend/Program.h>
#include <utils/compiler.h>
#include <utils/CString.h>
#include <utils/FixedCapacityVector.h>
#include <utils/compiler.h>
#include <inttypes.h>
#include <utility>
namespace filaflat {
class ChunkContainer;
@@ -48,7 +50,8 @@ struct MaterialConstant;
class MaterialParser {
public:
MaterialParser(backend::ShaderLanguage language, const void* data, size_t size);
MaterialParser(utils::FixedCapacityVector<backend::ShaderLanguage> preferredLanguages,
const void* data, size_t size);
MaterialParser(MaterialParser const& rhs) noexcept = delete;
MaterialParser& operator=(MaterialParser const& rhs) noexcept = delete;
@@ -60,6 +63,7 @@ public:
};
ParseResult parse() noexcept;
backend::ShaderLanguage getShaderLanguage() const noexcept;
// Accessors
bool getMaterialVersion(uint32_t* value) const noexcept;
@@ -130,7 +134,9 @@ public:
private:
struct MaterialParserDetails {
MaterialParserDetails(backend::ShaderLanguage language, const void* data, size_t size);
MaterialParserDetails(
const utils::FixedCapacityVector<backend::ShaderLanguage>& preferredLanguages,
const void* data, size_t size);
template<typename T>
bool getFromSimpleChunk(filamat::ChunkType type, T* value) const noexcept;
@@ -157,12 +163,12 @@ private:
ManagedBuffer mManagedBuffer;
filaflat::ChunkContainer mChunkContainer;
utils::FixedCapacityVector<backend::ShaderLanguage> mPreferredLanguages;
backend::ShaderLanguage mChosenLanguage;
// Keep MaterialChunk alive between calls to getShader to avoid reload the shader index.
filaflat::MaterialChunk mMaterialChunk;
filaflat::BlobDictionary mBlobDictionary;
filamat::ChunkType mMaterialTag = filamat::ChunkType::Unknown;
filamat::ChunkType mDictionaryTag = filamat::ChunkType::Unknown;
};
filaflat::ChunkContainer& getChunkContainer() noexcept;

View File

@@ -59,10 +59,11 @@
#include <filament/Texture.h>
#include <filament/VertexBuffer.h>
#include <utils/compiler.h>
#include <utils/Allocator.h>
#include <utils/JobSystem.h>
#include <utils/CountDownLatch.h>
#include <utils/FixedCapacityVector.h>
#include <utils/JobSystem.h>
#include <utils/compiler.h>
#include <chrono>
#include <memory>
@@ -231,19 +232,21 @@ public:
return mPlatform;
}
backend::ShaderLanguage getShaderLanguage() const noexcept {
// Return a vector of shader languages, in order of preference.
utils::FixedCapacityVector<backend::ShaderLanguage> getShaderLanguage() const noexcept {
switch (mBackend) {
case Backend::DEFAULT:
case Backend::NOOP:
default:
return backend::ShaderLanguage::ESSL3;
return { backend::ShaderLanguage::ESSL3 };
case Backend::OPENGL:
return getDriver().getFeatureLevel() == FeatureLevel::FEATURE_LEVEL_0
? backend::ShaderLanguage::ESSL1 : backend::ShaderLanguage::ESSL3;
return { getDriver().getFeatureLevel() == FeatureLevel::FEATURE_LEVEL_0
? backend::ShaderLanguage::ESSL1
: backend::ShaderLanguage::ESSL3 };
case Backend::VULKAN:
return backend::ShaderLanguage::SPIRV;
return { backend::ShaderLanguage::SPIRV };
case Backend::METAL:
return backend::ShaderLanguage::MSL;
return { backend::ShaderLanguage::METAL_LIBRARY, backend::ShaderLanguage::MSL };
}
}

View File

@@ -42,16 +42,16 @@
#include <backend/Program.h>
#include <utils/BitmaskEnum.h>
#include <utils/CString.h>
#include <utils/FixedCapacityVector.h>
#include <utils/Hash.h>
#include <utils/Invocable.h>
#include <utils/Log.h>
#include <utils/Panic.h>
#include <utils/bitset.h>
#include <utils/compiler.h>
#include <utils/debug.h>
#include <utils/CString.h>
#include <utils/FixedCapacityVector.h>
#include <utils/Invocable.h>
#include <utils/Log.h>
#include <utils/ostream.h>
#include <utils/Panic.h>
#include <utils/Hash.h>
#include <algorithm>
#include <array>
@@ -60,6 +60,7 @@
#include <mutex>
#include <new>
#include <optional>
#include <sstream>
#include <string_view>
#include <unordered_map>
#include <utility>
@@ -74,22 +75,32 @@ using namespace backend;
using namespace filaflat;
using namespace utils;
static std::unique_ptr<MaterialParser> createParser(
Backend backend, ShaderLanguage language, const void* data, size_t size) {
static std::unique_ptr<MaterialParser> createParser(Backend backend,
utils::FixedCapacityVector<ShaderLanguage> languages, const void* data, size_t size) {
// unique_ptr so we don't leak MaterialParser on failures below
auto materialParser = std::make_unique<MaterialParser>(language, data, size);
auto materialParser = std::make_unique<MaterialParser>(languages, data, size);
MaterialParser::ParseResult const materialResult = materialParser->parse();
if (UTILS_UNLIKELY(materialResult == MaterialParser::ParseResult::ERROR_MISSING_BACKEND)) {
std::stringstream languageNames;
for (auto it = languages.begin(); it != languages.end(); ++it) {
languageNames << shaderLanguageToString(*it);
if (std::next(it) != languages.end()) {
languageNames << ", ";
}
}
ASSERT_PRECONDITION(materialResult != MaterialParser::ParseResult::ERROR_MISSING_BACKEND,
"the material was not built for any of the %s backend's supported shader "
"languages (%s)\n",
backendToString(backend), languageNames.str().c_str());
}
if (backend == Backend::NOOP) {
return materialParser;
}
ASSERT_PRECONDITION(materialResult != MaterialParser::ParseResult::ERROR_MISSING_BACKEND,
"the material was not built for the %s backend and %s shader language\n",
backendToString(backend),
shaderLanguageToString(language));
ASSERT_PRECONDITION(materialResult == MaterialParser::ParseResult::SUCCESS,
"could not parse the material package");
@@ -217,7 +228,7 @@ FMaterial::FMaterial(FEngine& engine, const Material::Builder& builder,
success = parser->getUIB(&mUniformInterfaceBlock);
assert_invariant(success);
if (UTILS_UNLIKELY(engine.getShaderLanguage() == ShaderLanguage::ESSL1)) {
if (UTILS_UNLIKELY(parser->getShaderLanguage() == ShaderLanguage::ESSL1)) {
success = parser->getBindingUniformInfo(&mBindingUniformInfo);
assert_invariant(success);
@@ -530,6 +541,7 @@ Program FMaterial::getProgramWithVariants(
Program program;
program.shader(ShaderStage::VERTEX, vsBuilder.data(), vsBuilder.size())
.shader(ShaderStage::FRAGMENT, fsBuilder.data(), fsBuilder.size())
.shaderLanguage(mMaterialParser->getShaderLanguage())
.uniformBlockBindings(mUniformBlockBindings)
.diagnostics(mName,
[this, variant](io::ostream& out) -> io::ostream& {
@@ -551,8 +563,7 @@ Program FMaterial::getProgramWithVariants(
samplers.data(), info.count);
}
}
if (UTILS_UNLIKELY(mEngine.getShaderLanguage() == ShaderLanguage::ESSL1)) {
if (UTILS_UNLIKELY(mMaterialParser->getShaderLanguage() == ShaderLanguage::ESSL1)) {
assert_invariant(!mBindingUniformInfo.empty());
for (auto const& [index, uniforms] : mBindingUniformInfo) {
program.uniforms(uint32_t(index), uniforms);
@@ -861,7 +872,7 @@ void FMaterial::processSpecializationConstants(FEngine& engine, Material::Builde
mSpecializationConstants.push_back({
+ReservedSpecializationConstants::CONFIG_STEREO_EYE_COUNT,
(int)engine.getConfig().stereoscopicEyeCount });
if (UTILS_UNLIKELY(engine.getShaderLanguage() == ShaderLanguage::ESSL1)) {
if (UTILS_UNLIKELY(parser->getShaderLanguage() == ShaderLanguage::ESSL1)) {
// The actual value of this spec-constant is set in the OpenGLDriver backend.
mSpecializationConstants.push_back({
+ReservedSpecializationConstants::CONFIG_SRGB_SWAPCHAIN_EMULATION,

View File

@@ -31,7 +31,7 @@ using namespace filament;
// This will re-compile the test material with the current version of matc.
// To verify, rebuild and re-run test_material_parser (this test suite).
TEST(MaterialParser, Parse) {
MaterialParser parser(backend::ShaderLanguage::ESSL3,
MaterialParser parser({ backend::ShaderLanguage::ESSL3 },
FILAMENT_TEST_RESOURCES_TEST_MATERIAL_DATA, FILAMENT_TEST_RESOURCES_TEST_MATERIAL_SIZE);
MaterialParser::ParseResult materialOk = parser.parse();