Files
filament/tools/matinfo/src/main.cpp
Mathias Agopian 8d484dd762 break dependency of filaflat on filabridge
filaflat only had on header dependency on filabridge (DriverEnums.h)
and only needed two small enum types.
In fact, I don't think it was right for filaflat to assume any 
particular value for these fields -- this is the responsibility of the
callers.
2019-03-01 08:43:01 -08:00

1045 lines
34 KiB
C++

/*
* Copyright (C) 2018 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 <getopt/getopt.h>
#include <filaflat/ChunkContainer.h>
#include <filaflat/MaterialChunk.h>
#include <filaflat/ShaderBuilder.h>
#include <filaflat/SpirvDictionaryReader.h>
#include <filaflat/TextDictionaryReader.h>
#include <filaflat/Unflattener.h>
#include <private/filament/SamplerInterfaceBlock.h>
#include <private/filament/UniformInterfaceBlock.h>
#include <filament/MaterialChunkType.h>
#include <filament/EngineEnums.h>
#include <filament/MaterialEnums.h>
#include <filament/driver/DriverEnums.h>
#include <utils/Path.h>
#include <spirv_glsl.hpp>
#include <spirv-tools/libspirv.h>
#include <fstream>
#include <iomanip>
#include <iostream>
using namespace filaflat;
using namespace filamat;
using namespace filament;
using namespace filament::driver;
using namespace utils;
static const int alignment = 24;
class MaterialParser {
public:
MaterialParser(filament::driver::Backend backend, const void* data, size_t size)
: mBackend(backend), mChunkContainer(data, size) {
switch (mBackend) {
case Backend::OPENGL:
MATERIAL = ChunkType::MaterialGlsl;
DICTIONARY = ChunkType::DictionaryGlsl;
break;
case Backend::METAL:
MATERIAL = ChunkType::MaterialMetal;
DICTIONARY = ChunkType::DictionaryMetal;
break;
case Backend::VULKAN:
MATERIAL = ChunkType::MaterialSpirv;
DICTIONARY = ChunkType::DictionarySpirv;
break;
default:
break;
}
}
bool parse() noexcept {
return mChunkContainer.parse();
}
bool isShadingMaterial() const noexcept {
ChunkContainer const& cc = mChunkContainer;
return cc.hasChunk(MaterialName) && cc.hasChunk(MaterialVersion) &&
cc.hasChunk(MaterialUib) && cc.hasChunk(MaterialSib) &&
cc.hasChunk(MaterialShaderModels) && cc.hasChunk(MATERIAL);
}
bool isPostProcessMaterial() const noexcept {
ChunkContainer const& cc = mChunkContainer;
return cc.hasChunk(PostProcessVersion)
&& cc.hasChunk(MATERIAL) && cc.hasChunk(DICTIONARY);
}
bool getShader(ShaderModel shaderModel,
uint8_t variant, ShaderType st, ShaderBuilder& shader) noexcept {
ChunkContainer const& cc = mChunkContainer;
if (!cc.hasChunk(MATERIAL) || !cc.hasChunk(DICTIONARY)) {
return false;
}
BlobDictionary blobDictionary;
if (mBackend == Backend::OPENGL) {
if (!TextDictionaryReader::unflatten(cc, blobDictionary, DICTIONARY)) {
return false;
}
} else {
if (!SpirvDictionaryReader::unflatten(cc, blobDictionary, DICTIONARY)) {
return false;
}
}
Unflattener unflattener(cc.getChunkStart(MATERIAL), cc.getChunkEnd(MATERIAL));
MaterialChunk materialChunk;
switch (mBackend) {
case Backend::OPENGL:
return materialChunk.getTextShader(unflattener, blobDictionary,
shader, (uint8_t)shaderModel, variant, st);
case Backend::METAL:
return materialChunk.getTextShader(unflattener, blobDictionary,
shader, (uint8_t)shaderModel, variant, st);
case Backend::VULKAN:
return materialChunk.getSpirvShader(unflattener, blobDictionary,
shader, (uint8_t)shaderModel, variant, st);
default:
return false;
}
}
private:
ChunkContainer mChunkContainer;
filament::driver::Backend mBackend;
ChunkType MATERIAL = ChunkType::Unknown;
ChunkType DICTIONARY = ChunkType::Unknown;
};
struct Config {
bool printGLSL = false;
bool printSPIRV = false;
bool printMetal = false;
bool transpile = false;
bool binary = false;
uint64_t shaderIndex;
};
struct ShaderInfo {
filament::driver::ShaderModel shaderModel;
uint8_t variant;
filament::driver::ShaderType pipelineStage;
uint32_t offset;
};
static void printUsage(const char* name) {
std::string execName(utils::Path(name).getName());
std::string usage(
"MATINFO prints information about material files compiled with matc\n"
"Usage:\n"
" MATINFO [options] <material file>\n"
"\n"
"Options:\n"
" --help, -h\n"
" Print this message\n\n"
" --print-glsl=[index], -g\n"
" Print GLSL for the nth shader (0 is the first OpenGL shader)\n\n"
" --print-spirv=[index], -s\n"
" Print disasm for the nth shader (0 is the first Vulkan shader)\n\n"
" --print-metal=[index], -m\n"
" Print Metal Shading Language for the nth shader (0 is the first Metal shader)\n\n"
" --print-vkglsl=[index], -v\n"
" Print the nth Vulkan shader transpiled into GLSL\n\n"
" --dump-binary=[index], -b\n"
" Dump binary SPIRV for the nth Vulkan shader to 'out.spv'\n\n"
" --license\n"
" Print copyright and license information\n\n"
);
const std::string from("MATINFO");
for (size_t pos = usage.find(from); pos != std::string::npos; pos = usage.find(from, pos)) {
usage.replace(pos, from.length(), execName);
}
printf("%s", usage.c_str());
}
static void license() {
std::cout <<
#include "licenses/licenses.inc"
;
}
static int handleArguments(int argc, char* argv[], Config* config) {
static constexpr const char* OPTSTR = "hlg:s:v:b:";
static const struct option OPTIONS[] = {
{ "help", no_argument, 0, 'h' },
{ "license", no_argument, 0, 'l' },
{ "print-glsl", required_argument, 0, 'g' },
{ "print-spirv", required_argument, 0, 's' },
{ "print-vkglsl", required_argument, 0, 'v' },
{ "print-metal", required_argument, 0, 'm' },
{ "dump-binary", required_argument, 0, 'b' },
{ 0, 0, 0, 0 } // termination of the option list
};
int opt;
int optionIndex = 0;
while ((opt = getopt_long(argc, argv, OPTSTR, OPTIONS, &optionIndex)) >= 0) {
std::string arg(optarg ? optarg : "");
switch (opt) {
default:
case 'h':
printUsage(argv[0]);
exit(0);
case 'l':
license();
exit(0);
case 'g':
config->printGLSL = true;
config->shaderIndex = static_cast<uint64_t>(std::stoi(arg));
break;
case 's':
config->printSPIRV = true;
config->shaderIndex = static_cast<uint64_t>(std::stoi(arg));
break;
case 'v':
config->printSPIRV = true;
config->shaderIndex = static_cast<uint64_t>(std::stoi(arg));
config->transpile = true;
break;
case 'b':
config->printSPIRV = true;
config->shaderIndex = static_cast<uint64_t>(std::stoi(arg));
config->binary = true;
break;
case 'm':
config->printMetal = true;
config->shaderIndex = static_cast<uint64_t>(std::stoi(arg));
}
}
return optind;
}
static std::ifstream::pos_type getFileSize(const char* filename) {
std::ifstream in(filename, std::ifstream::ate | std::ifstream::binary);
return in.tellg();
}
template<typename T>
static bool read(const ChunkContainer& container, filamat::ChunkType type, T* value) noexcept {
if (!container.hasChunk(type)) {
return false;
}
Unflattener unflattener(container.getChunkStart(type), container.getChunkEnd(type));
return unflattener.read(value);
}
template<typename T>
static const char* toString(T value);
template<>
const char* toString(filament::Shading shadingModel) {
switch (shadingModel) {
case filament::Shading::UNLIT: return "unlit";
case filament::Shading::LIT: return "lit";
case filament::Shading::SUBSURFACE: return "subsurface";
case filament::Shading::CLOTH: return "cloth";
}
}
template<>
const char* toString(filament::BlendingMode blendingMode) {
switch (blendingMode) {
case filament::BlendingMode::OPAQUE: return "opaque";
case filament::BlendingMode::TRANSPARENT: return "transparent";
case filament::BlendingMode::FADE: return "fade";
case filament::BlendingMode::ADD: return "add";
case filament::BlendingMode::MASKED: return "masked";
}
}
template<>
const char* toString(filament::Interpolation interpolation) {
switch (interpolation) {
case filament::Interpolation::SMOOTH: return "smooth";
case filament::Interpolation::FLAT: return "flat";
}
}
template<>
const char* toString(filament::VertexDomain domain) {
switch (domain) {
case filament::VertexDomain::OBJECT: return "object";
case filament::VertexDomain::WORLD: return "world";
case filament::VertexDomain::VIEW: return "view";
case filament::VertexDomain::DEVICE: return "device";
}
}
template<>
const char* toString(filament::driver::CullingMode cullingMode) {
switch (cullingMode) {
case filament::driver::CullingMode::NONE: return "none";
case filament::driver::CullingMode::FRONT: return "front";
case filament::driver::CullingMode::BACK: return "back";
case filament::driver::CullingMode::FRONT_AND_BACK: return "front & back";
}
}
template<>
const char* toString(filament::TransparencyMode transparencyMode) {
switch (transparencyMode) {
case filament::TransparencyMode::DEFAULT: return "default";
case filament::TransparencyMode::TWO_PASSES_ONE_SIDE: return "two passes, one side";
case filament::TransparencyMode::TWO_PASSES_TWO_SIDES: return "two passes, two sides";
}
}
template<>
const char* toString(filament::VertexAttribute attribute) {
switch (attribute) {
case filament::POSITION: return "position";
case filament::TANGENTS: return "tangents";
case filament::COLOR: return "color";
case filament::UV0: return "uv0";
case filament::UV1: return "uv1";
case filament::BONE_INDICES: return "bone indices";
case filament::BONE_WEIGHTS: return "bone weights";
}
return "--";
}
template<>
const char* toString(bool value) {
return value ? "true" : "false";
}
template<>
const char* toString(filament::driver::ShaderType stage) {
switch (stage) {
case filament::driver::ShaderType::VERTEX: return "vs";
case filament::driver::ShaderType::FRAGMENT: return "fs";
default: break;
}
return "--";
}
template<>
const char* toString(filament::driver::ShaderModel model) {
switch (model) {
case filament::driver::ShaderModel::UNKNOWN: return "--";
case filament::driver::ShaderModel::GL_ES_30: return "gles30";
case filament::driver::ShaderModel::GL_CORE_41: return "gl41";
}
}
template<>
const char* toString(filament::UniformInterfaceBlock::Type type) {
switch (type) {
case filament::driver::UniformType::BOOL: return "bool";
case filament::driver::UniformType::BOOL2: return "bool2";
case filament::driver::UniformType::BOOL3: return "bool3";
case filament::driver::UniformType::BOOL4: return "bool4";
case filament::driver::UniformType::FLOAT: return "float";
case filament::driver::UniformType::FLOAT2: return "float2";
case filament::driver::UniformType::FLOAT3: return "float3";
case filament::driver::UniformType::FLOAT4: return "float4";
case filament::driver::UniformType::INT: return "int";
case filament::driver::UniformType::INT2: return "int2";
case filament::driver::UniformType::INT3: return "int3";
case filament::driver::UniformType::INT4: return "int4";
case filament::driver::UniformType::UINT: return "uint";
case filament::driver::UniformType::UINT2: return "uint2";
case filament::driver::UniformType::UINT3: return "uint3";
case filament::driver::UniformType::UINT4: return "uint4";
case filament::driver::UniformType::MAT3: return "float3x3";
case filament::driver::UniformType::MAT4: return "float4x4";
}
}
template<>
const char* toString(filament::SamplerInterfaceBlock::Type type) {
switch (type) {
case filament::driver::SamplerType::SAMPLER_2D: return "sampler2D";
case filament::driver::SamplerType::SAMPLER_CUBEMAP: return "samplerCubemap";
case filament::driver::SamplerType::SAMPLER_EXTERNAL: return "samplerExternal";
}
}
template<>
const char* toString(filament::SamplerInterfaceBlock::Precision precision) {
switch (precision) {
case filament::driver::Precision::LOW: return "lowp";
case filament::driver::Precision::MEDIUM: return "mediump";
case filament::driver::Precision::HIGH: return "highp";
case filament::driver::Precision::DEFAULT: return "default";
}
}
template<>
const char* toString(filament::SamplerInterfaceBlock::Format format) {
switch (format) {
case filament::driver::SamplerFormat::INT: return "int";
case filament::driver::SamplerFormat::UINT: return "uint";
case filament::driver::SamplerFormat::FLOAT: return "float";
case filament::driver::SamplerFormat::SHADOW: return "shadow";
}
}
static std::string arraySizeToString(uint64_t size) {
if (size > 1) {
std::string s = "[";
s += size;
s += "]";
return s;
}
return "";
}
template<typename T, typename V>
static void printChunk(const ChunkContainer& container, filamat::ChunkType type, const char* title) {
T value;
if (read(container, type, reinterpret_cast<V*>(&value))) {
std::cout << " " << std::setw(alignment) << std::left << title;
std::cout << toString(value) << std::endl;
}
}
static void printFloatChunk(const ChunkContainer& container, filamat::ChunkType type,
const char* title) {
float value;
if (read(container, type, &value)) {
std::cout << " " << std::setw(alignment) << std::left << title;
std::cout << std::setprecision(2) << value << std::endl;
}
}
static void printUint32Chunk(const ChunkContainer& container, filamat::ChunkType type,
const char* title) {
uint32_t value;
if (read(container, type, &value)) {
std::cout << " " << std::setw(alignment) << std::left << title;
std::cout << value << std::endl;
}
}
static bool printMaterial(const ChunkContainer& container) {
std::cout << "Material:" << std::endl;
uint32_t version;
if (read(container, filamat::MaterialVersion, &version)) {
std::cout << " " << std::setw(alignment) << std::left << "Version: ";
std::cout << version << std::endl;
}
printUint32Chunk(container, filamat::PostProcessVersion, "Post process version: ");
CString name;
if (read(container, filamat::MaterialName, &name)) {
std::cout << " " << std::setw(alignment) << std::left << "Name: ";
std::cout << name.c_str() << std::endl;
}
std::cout << std::endl;
std::cout << "Shading:" << std::endl;
printChunk<filament::Shading, uint8_t>(container, filamat::MaterialShading, "Model: ");
printChunk<filament::VertexDomain, uint8_t>(container, filamat::MaterialVertexDomain,
"Vertex domain: ");
printChunk<filament::Interpolation, uint8_t>(container, filamat::MaterialInterpolation,
"Interpolation: ");
printChunk<bool, bool>(container, filamat::MaterialShadowMultiplier, "Shadow multiply: ");
printChunk<bool, bool>(container, filamat::MaterialCurvatureToRoughness, "Curvature to roughness: ");
printChunk<bool, bool>(container, filamat::MaterialLimitOverInterpolation, "Limit interpolation: ");
printChunk<bool, bool>(container, filamat::MaterialClearCoatIorChange, "Clear coat IOR change: ");
std::cout << std::endl;
std::cout << "Raster state:" << std::endl;
printChunk<filament::BlendingMode, uint8_t>(container, filamat::MaterialBlendingMode, "Blending: ");
printFloatChunk(container, filamat::MaterialMaskThreshold, "Mask threshold: ");
printChunk<bool, bool>(container, filamat::MaterialColorWrite, "Color write: ");
printChunk<bool, bool>(container, filamat::MaterialDepthWrite, "Depth write: ");
printChunk<bool, bool>(container, filamat::MaterialDepthTest, "Depth test: ");
printChunk<bool, bool>(container, filamat::MaterialDoubleSided, "Double sided: ");
printChunk<filament::driver::CullingMode, uint8_t>(container, filamat::MaterialCullingMode,
"Culling: ");
printChunk<filament::TransparencyMode, uint8_t>(container, filamat::MaterialTransparencyMode,
"Transparency: ");
std::cout << std::endl;
uint32_t requiredAttributes;
if (read(container, filamat::MaterialRequiredAttributes, &requiredAttributes)) {
filament::AttributeBitset bitset;
bitset.setValue(requiredAttributes);
if (bitset.count() > 0) {
std::cout << "Required attributes:" << std::endl;
for (size_t i = 0; i < bitset.size(); i++) {
if (bitset.test(i)) {
std::cout << " " <<
toString(static_cast<filament::VertexAttribute>(i)) << std::endl;
}
}
std::cout << std::endl;
}
}
return true;
}
static bool printParametersInfo(ChunkContainer container) {
if (!container.hasChunk(filamat::ChunkType::MaterialUib)) {
return true;
}
Unflattener uib(
container.getChunkStart(filamat::ChunkType::MaterialUib),
container.getChunkEnd(filamat::ChunkType::MaterialUib));
CString name;
if (!uib.read(&name)) {
return false;
}
uint64_t uibCount;
if (!uib.read(&uibCount)) {
return false;
}
Unflattener sib(
container.getChunkStart(filamat::ChunkType::MaterialSib),
container.getChunkEnd(filamat::ChunkType::MaterialSib));
if (!sib.read(&name)) {
return false;
}
uint64_t sibCount;
if (!sib.read(&sibCount)) {
return false;
}
if (uibCount == 0 && sibCount == 0) {
return true;
}
std::cout << "Parameters:" << std::endl;
for (uint64_t i = 0; i < uibCount; i++) {
CString fieldName;
uint64_t fieldSize;
uint8_t fieldType;
uint8_t fieldPrecision;
if (!uib.read(&fieldName)) {
return false;
}
if (!uib.read(&fieldSize)) {
return false;
}
if (!uib.read(&fieldType)) {
return false;
}
if (!uib.read(&fieldPrecision)) {
return false;
}
std::cout << " "
<< std::setw(alignment) << fieldName.c_str()
<< std::setw(alignment) << toString(filament::UniformInterfaceBlock::Type(fieldType))
<< arraySizeToString(fieldSize)
<< std::setw(10) << toString(filament::UniformInterfaceBlock::Precision(fieldPrecision))
<< std::endl;
}
for (uint64_t i = 0; i < sibCount; i++) {
CString fieldName;
uint8_t fieldType;
uint8_t fieldFormat;
uint8_t fieldPrecision;
bool fieldMultisample;
if (!sib.read(&fieldName)) {
return false;
}
if (!sib.read(&fieldType)) {
return false;
}
if (!sib.read(&fieldFormat))
return false;
if (!sib.read(&fieldPrecision)) {
return false;
}
if (!sib.read(&fieldMultisample)) {
return false;
}
std::cout << " "
<< std::setw(alignment) << fieldName.c_str()
<< std::setw(alignment) << toString(filament::SamplerInterfaceBlock::Type(fieldType))
<< std::setw(10) << toString(filament::SamplerInterfaceBlock::Precision(fieldPrecision))
<< toString(filament::SamplerInterfaceBlock::Format(fieldFormat))
<< std::endl;
}
std::cout << std::endl;
return true;
}
static void printChunks(const ChunkContainer& container) {
std::cout << "Chunks:" << std::endl;
std::cout << " " << std::setw(9) << std::left << "Name ";
std::cout << std::setw(7) << std::right << "Size" << std::endl;
size_t count = container.getChunkCount();
for (size_t i = 0; i < count; i++) {
auto chunk = container.getChunk(i);
std::cout << " " << typeToString(chunk.type).c_str() << " ";
std::cout << std::setw(7) << std::right << chunk.desc.size << std::endl;
}
}
static bool getMetalShaderInfo(ChunkContainer container, std::vector<ShaderInfo>* info) {
if (!container.hasChunk(filamat::ChunkType::MaterialMetal)) {
return true; // that's not an error, a material can have no metal stuff
}
Unflattener unflattener(
container.getChunkStart(filamat::ChunkType::MaterialMetal),
container.getChunkEnd(filamat::ChunkType::MaterialMetal));
uint64_t shaderCount = 0;
if (!unflattener.read(&shaderCount) || shaderCount == 0) {
return false;
}
info->clear();
info->reserve(shaderCount);
for (uint64_t i = 0; i < shaderCount; i++) {
uint8_t shaderModelValue;
uint8_t variantValue;
uint8_t pipelineStageValue;
uint32_t offsetValue;
if (!unflattener.read(&shaderModelValue)) {
return false;
}
if (!unflattener.read(&variantValue)) {
return false;
}
if (!unflattener.read(&pipelineStageValue)) {
return false;
}
if (!unflattener.read(&offsetValue)) {
return false;
}
info->push_back({
.shaderModel = filament::driver::ShaderModel(shaderModelValue),
.variant = variantValue,
.pipelineStage = filament::driver::ShaderType(pipelineStageValue),
.offset = offsetValue
});
}
return true;
}
static bool getGlShaderInfo(ChunkContainer container, std::vector<ShaderInfo>* info) {
if (!container.hasChunk(filamat::ChunkType::MaterialGlsl)) {
return true; // that's not an error, a material can have no glsl stuff
}
Unflattener unflattener(
container.getChunkStart(filamat::ChunkType::MaterialGlsl),
container.getChunkEnd(filamat::ChunkType::MaterialGlsl));
uint64_t shaderCount;
if (!unflattener.read(&shaderCount) || shaderCount == 0) {
return false;
}
info->clear();
info->reserve(shaderCount);
for (uint64_t i = 0; i < shaderCount; i++) {
uint8_t shaderModelValue;
uint8_t variantValue;
uint8_t pipelineStageValue;
uint32_t offsetValue;
if (!unflattener.read(&shaderModelValue)) {
return false;
}
if (!unflattener.read(&variantValue)) {
return false;
}
if (!unflattener.read(&pipelineStageValue)) {
return false;
}
if (!unflattener.read(&offsetValue)) {
return false;
}
info->push_back({
.shaderModel = filament::driver::ShaderModel(shaderModelValue),
.variant = variantValue,
.pipelineStage = filament::driver::ShaderType(pipelineStageValue),
.offset = offsetValue
});
}
return true;
}
static bool getVkShaderInfo(ChunkContainer container, std::vector<ShaderInfo>* info) {
if (!container.hasChunk(filamat::ChunkType::MaterialSpirv)) {
return true; // that's not an error, a material can have no spirv stuff
}
Unflattener unflattener(
container.getChunkStart(filamat::ChunkType::MaterialSpirv),
container.getChunkEnd(filamat::ChunkType::MaterialSpirv));
uint64_t shaderCount;
if (!unflattener.read(&shaderCount) || shaderCount == 0) {
return false;
}
info->clear();
info->reserve(shaderCount);
for (uint64_t i = 0; i < shaderCount; i++) {
uint8_t shaderModelValue;
uint8_t variantValue;
uint8_t pipelineStageValue;
uint32_t dictionaryIndex;
if (!unflattener.read(&shaderModelValue)) {
return false;
}
if (!unflattener.read(&variantValue)) {
return false;
}
if (!unflattener.read(&pipelineStageValue)) {
return false;
}
if (!unflattener.read(&dictionaryIndex)) {
return false;
}
info->push_back({
.shaderModel = filament::driver::ShaderModel(shaderModelValue),
.variant = variantValue,
.pipelineStage = filament::driver::ShaderType(pipelineStageValue),
.offset = dictionaryIndex
});
}
return true;
}
static void printShaderInfo(const std::vector<ShaderInfo>& info) {
for (uint64_t i = 0; i < info.size(); ++i) {
const auto& item = info[i];
std::cout << " #";
std::cout << std::setw(4) << std::left << i;
std::cout << std::setw(6) << std::left << toString(item.shaderModel);
std::cout << " ";
std::cout << std::setw(2) << std::left << toString(item.pipelineStage);
std::cout << " ";
std::cout << "0x" << std::hex << std::setfill('0') << std::setw(2)
<< std::right << (int) item.variant;
std::cout << std::setfill(' ') << std::dec << std::endl;
}
std::cout << std::endl;
}
static bool printGlslInfo(ChunkContainer container) {
std::vector<ShaderInfo> info;
if (!getGlShaderInfo(container, &info)) {
return false;
}
std::cout << "GLSL shaders:" << std::endl;
printShaderInfo(info);
return true;
}
static bool printVkInfo(ChunkContainer container) {
std::vector<ShaderInfo> info;
if (!getVkShaderInfo(container, &info)) {
return false;
}
std::cout << "Vulkan shaders:" << std::endl;
printShaderInfo(info);
return true;
}
static bool printMetalInfo(ChunkContainer container) {
std::vector<ShaderInfo> info;
if (!getMetalShaderInfo(container, &info)) {
return false;
}
std::cout << "Metal shaders:" << std::endl;
printShaderInfo(info);
return true;
}
static bool printMaterialInfo(const ChunkContainer& container) {
if (!printMaterial(container)) {
return false;
}
if (!printParametersInfo(container)) {
return false;
}
if (!printGlslInfo(container)) {
return false;
}
if (!printVkInfo(container)) {
return false;
}
if (!printMetalInfo(container)) {
return false;
}
printChunks(container);
std::cout << std::endl;
return true;
}
static void transpileSpirv(const std::vector<uint32_t>& spirv) {
using namespace spirv_cross;
// We assume that users of the tool are interested in reading GLSL-ES, since our primary
// target platform is Android.
CompilerGLSL::Options emitOptions;
emitOptions.es = true;
emitOptions.vulkan_semantics = true;
CompilerGLSL glslCompiler(move(spirv));
glslCompiler.set_common_options(emitOptions);
std::cout << glslCompiler.compile();
}
static void disassembleSpirv(const std::vector<uint32_t>& spirv) {
// If desired feel free to locally replace this with the glslang disassembler (spv::Disassemble)
// but please do not submit. We prefer to use the syntax that the standalone "spirv-dis" tool
// uses, which lets us easily generate test cases for the spirv-cross project.
auto context = spvContextCreate(SPV_ENV_UNIVERSAL_1_1);
spv_text text = nullptr;
const uint32_t options = SPV_BINARY_TO_TEXT_OPTION_INDENT;
spvBinaryToText(context, spirv.data(), spirv.size(), options, &text, nullptr);
std::cout << text->str << std::endl;
spvTextDestroy(text);
spvContextDestroy(context);
}
static void dumpSpirvBinary(const std::vector<uint32_t>& spirv, std::string filename) {
std::ofstream out(filename, std::ofstream::binary);
out.write((const char*) spirv.data(), spirv.size() * 4);
std::cout << "Binary SPIR-V dumped to " << filename << std::endl;
}
static bool parseChunks(Config config, void* data, size_t size) {
ChunkContainer container(data, size);
if (!container.parse()) {
return false;
}
if (config.printGLSL || config.printSPIRV || config.printMetal) {
filaflat::ShaderBuilder builder;
std::vector<ShaderInfo> info;
if (config.printGLSL) {
MaterialParser parser(filament::driver::Backend::OPENGL, data, size);
if (!parser.parse() ||
(!parser.isShadingMaterial() && !parser.isPostProcessMaterial())) {
return false;
}
if (!getGlShaderInfo(container, &info)) {
std::cerr << "Failed to parse GLSL chunk." << std::endl;
return false;
}
if (config.shaderIndex >= info.size()) {
std::cerr << "Shader index out of range." << std::endl;
return false;
}
const auto& item = info[config.shaderIndex];
parser.getShader(item.shaderModel, item.variant, item.pipelineStage, builder);
std::cout << builder.c_str();
return true;
}
if (config.printSPIRV) {
MaterialParser parser(filament::driver::Backend::VULKAN, data, size);
if (!parser.parse() ||
(!parser.isShadingMaterial() && !parser.isPostProcessMaterial())) {
return false;
}
if (!getVkShaderInfo(container, &info)) {
std::cerr << "Failed to parse SPIRV chunk." << std::endl;
return false;
}
if (config.shaderIndex >= info.size()) {
std::cerr << "Shader index out of range." << std::endl;
return false;
}
const auto& item = info[config.shaderIndex];
parser.getShader(item.shaderModel, item.variant, item.pipelineStage, builder);
// Build std::vector<uint32_t> since that's what the Khronos libraries consume.
uint32_t const* words = reinterpret_cast<uint32_t const*>(builder.c_str());
assert(0 == (builder.size() % 4));
const std::vector<uint32_t> spirv(words, words + builder.size() / 4);
if (config.transpile) {
transpileSpirv(spirv);
} else if (config.binary) {
dumpSpirvBinary(spirv, "out.spv");
} else {
disassembleSpirv(spirv);
}
return true;
}
if (config.printMetal) {
MaterialParser parser(filament::driver::Backend::METAL, data, size);
if (!parser.parse() ||
(!parser.isShadingMaterial() && !parser.isPostProcessMaterial())) {
return false;
}
if (!getMetalShaderInfo(container, &info)) {
std::cerr << "Failed to parse Metal chunk." << std::endl;
return false;
}
if (config.shaderIndex >= info.size()) {
std::cerr << "Shader index out of range." << std::endl;
return false;
}
const auto& item = info[config.shaderIndex];
parser.getShader(item.shaderModel, item.variant, item.pipelineStage, builder);
std::cout << builder.getShader().c_str();
return true;
}
}
if (!printMaterialInfo(container)) {
std::cerr << "The source material is invalid." << std::endl;
return false;
}
return true;
}
// Parse the contents of .inc files, which look like: "0xba, 0xdf, 0xf0" etc. Happily, istream
// skips over whitespace and commas, and stoul takes care of leading "0x" when parsing hex.
static bool parseTextBlob(Config config, std::istream& in) {
std::vector<char> buffer;
std::string hexcode;
while (in >> hexcode) {
buffer.push_back(static_cast<char>(std::stoul(hexcode, nullptr, 16)));
}
return parseChunks(config, buffer.data(), buffer.size());
}
static bool parseBinary(Config config, std::istream& in, long fileSize) {
std::vector<char> buffer(static_cast<unsigned long>(fileSize));
if (in.read(buffer.data(), fileSize)) {
return parseChunks(config, buffer.data(), buffer.size());
}
std::cerr << "Could not read the source material." << std::endl;
return false;
}
int main(int argc, char* argv[]) {
Config config;
int optionIndex = handleArguments(argc, argv, &config);
int numArgs = argc - optionIndex;
if (numArgs < 1) {
printUsage(argv[0]);
return 1;
}
Path src(argv[optionIndex]);
if (!src.exists()) {
std::cerr << "The source material " << src << " does not exist." << std::endl;
return 1;
}
long fileSize = static_cast<long>(getFileSize(src.c_str()));
if (fileSize <= 0) {
std::cerr << "The source material " << src << " is invalid." << std::endl;
return 1;
}
std::ifstream in(src.c_str(), std::ifstream::in);
if (in.is_open()) {
if (src.getExtension() == "inc") {
return parseTextBlob(config, in) ? 0 : 1;
} else {
return parseBinary(config, in, fileSize) ? 0 : 1;
}
} else {
std::cerr << "Could not open the source material " << src << std::endl;
return 1;
};
return 0;
}