src/ngfx/graphics/ShaderTools.cpp

Functions

Name
string getEnv(const string & name)
json * getEntry(const json & data, const string & key)
shaderc_shader_kind toShaderKind(const string & ext)

Attributes

Name
auto readFile
auto writeFile
auto toLower

Defines

Name
V(func)
PATCH

Functions Documentation

function getEnv

static string getEnv(
    const string & name
)

function getEntry

static json * getEntry(
    const json & data,
    const string & key
)

function toShaderKind

static shaderc_shader_kind toShaderKind(
    const string & ext
)

Attributes Documentation

variable readFile

auto readFile = FileUtil::readFile;

variable writeFile

auto writeFile = FileUtil::writeFile;

variable toLower

auto toLower = StringUtil::toLower;

Macro Documentation

define V

#define V(
    func
)
  {                                                                            \
    ret = func;                                                                \
    if (ret != 0)                                                              \
      return ret;                                                              \
  }

define PATCH

#define PATCH string("patch")

Source code

/*
 * Copyright 2020 GoPro Inc.
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 "ShaderTools.h"
#include "ngfx/core/DebugUtil.h"
#include "ngfx/core/FileUtil.h"
#include "ngfx/core/StringUtil.h"
#include <cctype>
#include <filesystem>
#include <fstream>
#include <regex>
#include <set>
#include <spirv_cross/spirv_glsl.hpp>
#include <spirv_cross/spirv_hlsl.hpp>
#include <spirv_cross/spirv_msl.hpp>
#include <spirv_cross/spirv_reflect.hpp>
#include <sstream>
using namespace std;
using namespace ngfx;
auto readFile = FileUtil::readFile;
auto writeFile = FileUtil::writeFile;
auto toLower = StringUtil::toLower;
namespace fs = std::filesystem;
#define V(func)                                                                \
  {                                                                            \
    ret = func;                                                                \
    if (ret != 0)                                                              \
      return ret;                                                              \
  }

#ifdef _WIN32
#define PATCH string("patch.exe")
#else
#define PATCH string("patch")
#endif

static string getEnv(const string &name) {
  char *value = getenv(name.c_str());
  return (value ? value : "");
}
static json *getEntry(const json &data, const string &key) {
  auto it = data.find(key);
  if (it == data.end())
    return nullptr;
  return (json *)&it.value();
}
ShaderTools::ShaderTools(bool verbose) : verbose(verbose) {
  defaultIncludePaths = {"ngfx/data/shaders", "nodegl/data/shaders"};
}

int ShaderTools::cmd(string str) {
  if (verbose) {
    NGFX_LOG(">> %s", str.c_str());
  } else
    str += " >> /dev/null 2>&1";
  return system(str.c_str());
}

bool ShaderTools::findIncludeFile(const string &includeFilename,
                                  const vector<string> &includePaths,
                                  string &includeFile) {
  for (const string &includePath : includePaths) {
    fs::path filename = includePath / fs::path(includeFilename);
    if (fs::exists(filename)) {
      includeFile = filename.string();
      return true;
    }
  }
  return false;
}

int ShaderTools::preprocess(const string &src, const string &dataPath,
                            string &dst) {
  dst = "";
  vector<string> includePaths = defaultIncludePaths;
  includePaths.push_back(dataPath);
  istringstream sstream(src);
  string line;
  while (std::getline(sstream, line)) {
    smatch matchIncludeGroups;
    bool matchInclude =
        regex_search(line, matchIncludeGroups, regex("#include \"([^\"]*)"));
    if (matchInclude) {
      string includeFilename = matchIncludeGroups[1];
      string includeFilePath;
      findIncludeFile(includeFilename, includePaths, includeFilePath);
      dst += readFile(includeFilePath);
    } else {
      dst += line + "\n";
    }
  }
  return 0;
}

int ShaderTools::compileShaderGLSL(
    const string &src, shaderc_shader_kind shaderKind,
    const MacroDefinitions &defines, string &spv, bool verbose,
    shaderc_optimization_level optimizationLevel) {
  shaderc::Compiler compiler;
  shaderc::CompileOptions compileOptions;
  for (const MacroDefinition &define : defines) {
    compileOptions.AddMacroDefinition(define.name, define.value);
  }
  compileOptions.SetOptimizationLevel(optimizationLevel);
  compileOptions.SetGenerateDebugInfo();

  auto result = compiler.CompileGlslToSpv(src, shaderKind, "", compileOptions);
  if (result.GetCompilationStatus() != shaderc_compilation_status_success) {
    NGFX_ERR("cannot compile file: %s", result.GetErrorMessage().c_str());
    return 1;
  }
  spv = string((const char *)result.cbegin(),
               sizeof(uint32_t) * (result.cend() - result.cbegin()));
  return 0;
}

int ShaderTools::removeUnusedVariablesGLSL(const std::string &src,
                                           shaderc_shader_kind shaderKind,
                                           const MacroDefinitions &defines,
                                           std::string &dst) {
  int ret = 0;
  string spv;
  V(compileShaderGLSL(src, shaderKind, defines, spv, false));
  auto compilerGLSL = make_unique<spirv_cross::CompilerGLSL>(
      (const uint32_t *)spv.data(), spv.size() / sizeof(uint32_t));
  auto activeVariables = compilerGLSL->get_active_interface_variables();
  compilerGLSL->get_shader_resources(activeVariables);
  compilerGLSL->set_enabled_interface_variables(move(activeVariables));
  spirv_cross::CompilerGLSL::Options opts;
  opts.vulkan_semantics = true;
  compilerGLSL->set_common_options(opts);
  dst = compilerGLSL->compile();
  return 0;
}

int ShaderTools::patchShaderLayoutsGLSL(const string &src, string &dst) {
  dst = "";
  istringstream sstream(src);
  string line;
  while (std::getline(sstream, line)) {
    // Patch GLSL shader layouts
    smatch g;

    bool matchLayout = regex_search(line, g,
                                    regex("^(.*)"
                                          "layout\\s*\\("
                                          "([^)]*)"
                                          "binding[\\s]*=[\\s]*"
                                          "([\\d]+)"
                                          "([^)]*)"
                                          "\\)"
                                          "(.*)\r*$"));
    if (matchLayout) {
      dst += g[1].str() + "layout(" + g[2].str() + "set = " + g[3].str() +
             ", binding = 0" + g[4].str() + ")" + g[5].str() + "\n";
    } else {
      dst += line + "\n";
    }
  }
  return 0;
}

static shaderc_shader_kind toShaderKind(const string &ext) {
  static const map<string, shaderc_shader_kind> shaderKindMap = {
      {".vert", shaderc_vertex_shader},
      {".frag", shaderc_fragment_shader},
      {".comp", shaderc_compute_shader}};
  return shaderKindMap.at(ext);
}
int ShaderTools::compileShaderGLSL(string filename,
                                   const MacroDefinitions &defines,
                                   const string &outDir,
                                   vector<string> &outFiles, int flags) {
  string parentPath = fs::path(filename).parent_path().string();
  filename = fs::path(filename).filename().string();
  string inFileName =
      fs::path(parentPath + "/" + filename).make_preferred().string();
  string outFileName =
      fs::path(outDir + "/" + filename + ".spv").make_preferred().string();
  if (!FileUtil::srcFileNewerThanOutFile(inFileName, outFileName)) {
    outFiles.push_back(outFileName);
    return 0;
  }
  string src, dst;
  int ret = 0;

  src = FileUtil::readFile(inFileName);
  string ext = FileUtil::splitExt(inFileName)[1];
  shaderc_shader_kind shaderKind = toShaderKind(ext);
  if (flags & REMOVE_UNUSED_VARIABLES) {
    V(removeUnusedVariablesGLSL(src, shaderKind, defines, dst));
    src = move(dst);
  }
  if (flags & PATCH_SHADER_LAYOUTS_GLSL) {
    V(patchShaderLayoutsGLSL(src, dst));
    src = move(dst);
  }
  V(compileShaderGLSL(src, shaderKind, defines, dst));
  writeFile(outFileName, dst);
  outFiles.push_back(outFileName);
  return 0;
}

int ShaderTools::compileShaderMSL(const string &file,
                                  const MacroDefinitions &defines,
                                  string outDir, vector<string> &outFiles) {
  string strippedFilename =
      FileUtil::splitExt(fs::path(file).filename().string())[0];
  string inFileName = fs::path(outDir + "/" + strippedFilename + ".metal")
                          .make_preferred()
                          .string();
  string outFileName = fs::path(outDir + "/" + strippedFilename + ".metallib")
                           .make_preferred()
                           .string();
  if (!FileUtil::srcFileNewerThanOutFile(inFileName, outFileName)) {
    outFiles.push_back(outFileName);
    return 0;
  }

  string debugFlags = "-gline-tables-only -MO";
  int result = cmd("xcrun -sdk macosx metal " + debugFlags + " -c " +
                   inFileName + " -o " + outDir + "/" + strippedFilename +
                   ".air && "
                   "xcrun -sdk macosx metallib " +
                   outDir + "/" + strippedFilename + ".air -o " + outFileName);
  if (result == 0)
    NGFX_LOG("compiled file: %s", file.c_str());
  else
    NGFX_ERR("cannot compile file: %s", file.c_str());
  outFiles.push_back(outFileName);
  return result;
}

int ShaderTools::compileShaderHLSL(const string &file,
                                   const MacroDefinitions &defines,
                                   string outDir, vector<string> &outFiles) {
  string strippedFilename =
      FileUtil::splitExt(fs::path(file).filename().string())[0];
  string inFileName = fs::path(outDir + "/" + strippedFilename + ".hlsl")
                          .make_preferred()
                          .string();
  string outFileName = fs::path(outDir + "/" + strippedFilename + ".dxc")
                           .make_preferred()
                           .string();
  if (!FileUtil::srcFileNewerThanOutFile(inFileName, outFileName)) {
    outFiles.push_back(outFileName);
    return 0;
  }

  string shaderModel = "";
  if (strstr(inFileName.c_str(), ".vert"))
    shaderModel = "vs_5_0";
  else if (strstr(inFileName.c_str(), ".frag"))
    shaderModel = "ps_5_0";
  else if (strstr(inFileName.c_str(), ".comp"))
    shaderModel = "cs_5_0";
  int result = cmd("dxc.exe /T " + shaderModel + " /Fo " + outFileName + " " +
                   inFileName);
  if (result == 0)
    NGFX_LOG("compiled file: %s", file.c_str());
  else
    NGFX_ERR("cannot compile file: %s", file.c_str());
  outFiles.push_back(outFileName);
  return result;
}

int ShaderTools::convertSPVToMSL(const string &spv,
                                 shaderc_shader_kind shaderKind, string &msl) {
  auto compilerMSL = make_unique<spirv_cross::CompilerMSL>(
      (const uint32_t *)spv.data(), spv.size() / sizeof(uint32_t));
  msl = compilerMSL->compile();
  return 0;
}

int ShaderTools::convertSPVToHLSL(const string &spv,
                                  shaderc_shader_kind shaderKind, string &hlsl,
                                  uint32_t shaderModel) {
  auto compilerHLSL = make_unique<spirv_cross::CompilerHLSL>(
      (const uint32_t *)spv.data(), spv.size() / sizeof(uint32_t));
  auto options = compilerHLSL->get_hlsl_options();
  options.shader_model = shaderModel;
  compilerHLSL->set_hlsl_options(options);
  hlsl = compilerHLSL->compile();
  return 0;
}

int ShaderTools::convertShader(const string &file, const string &extraArgs,
                               string outDir, Format fmt,
                               vector<string> &outFiles) {
  auto splitFilename = FileUtil::splitExt(fs::path(file).filename().string());
  string strippedFilename = splitFilename[0];
  string ext = FileUtil::splitExt(strippedFilename)[1];
  string inFileName = fs::path(outDir + "/" + strippedFilename + ".spv")
                          .make_preferred()
                          .string();
  string outFileName = fs::path(outDir + "/" + strippedFilename +
                                (fmt == FORMAT_MSL ? ".metal" : ".hlsl"))
                           .make_preferred()
                           .string();
  if (!FileUtil::srcFileNewerThanOutFile(inFileName, outFileName)) {
    outFiles.push_back(outFileName);
    return 0;
  }
  string spv = FileUtil::readFile(inFileName), dst;
  int result;
  if (fmt == FORMAT_MSL) {
    result = convertSPVToMSL(spv, toShaderKind(ext), dst);
  } else {
    result = convertSPVToHLSL(spv, toShaderKind(ext), dst);
  }
  FileUtil::writeFile(outFileName, dst);
  string args =
      (fmt == FORMAT_MSL ? "--msl" : "--hlsl --shader-model 60") + extraArgs;
  if (result == 0)
    NGFX_LOG("converted file: %s to %s", inFileName.c_str(),
             outFileName.c_str());
  else
    NGFX_ERR("cannot convert file: %s", file.c_str());
  outFiles.push_back(outFileName);
  return result;
}

bool ShaderTools::findMetalReflectData(
    const vector<RegexUtil::Match> &metalReflectData, const string &name,
    RegexUtil::Match &match) {
  for (const RegexUtil::Match &data : metalReflectData) {
    if (data.s[2] == name) {
      match = data;
      return true;
    } else if (strstr(data.s[1].c_str(), name.c_str())) {
      match = data;
      return true;
    }
  }
  return false;
}

int ShaderTools::patchShaderReflectionDataMSL(const std::string &glslReflect,
                                              const std::string &ext,
                                              const std::string &msl,
                                              std::string &mslReflect) {
  auto glslReflectJson = json::parse(glslReflect);
  MetalReflectData metalReflectData;
  if (ext == ".vert") {
    metalReflectData.attributes =
        RegexUtil::findAll(regex("([^\\s]*)[\\s]*([^\\s]*)[\\s]*\\[\\["
                                 "attribute\\(([0-9]+)\\)\\]\\]"),
                           msl);
  }
  metalReflectData.buffers = RegexUtil::findAll(
      regex("([^\\s]*)[\\s]*([^\\s]*)[\\s]*\\[\\[buffer\\(([0-9]+)\\)\\]\\]"),
      msl);
  metalReflectData.textures = RegexUtil::findAll(
      regex("([^\\s]*)[\\s]*([^\\s]*)[\\s]*\\[\\[texture\\(([0-9]+)\\)\\]\\]"),
      msl);

  json *textures = getEntry(glslReflectJson, "textures"),
       *ubos = getEntry(glslReflectJson, "ubos"),
       *ssbos = getEntry(glslReflectJson, "ssbos"),
       *images = getEntry(glslReflectJson, "images");
  uint32_t numDescriptors =
      (textures ? textures->size() : 0) + (images ? images->size() : 0) +
      (ubos ? ubos->size() : 0) + (ssbos ? ssbos->size() : 0);

  // update input bindings
  if (ext == ".vert") {
    json *inputs = getEntry(glslReflectJson, "inputs");
    for (json &input : *inputs) {
      RegexUtil::Match metalInputReflectData;
      bool foundMatch = findMetalReflectData(
          metalReflectData.attributes, input["name"], metalInputReflectData);
      if (!foundMatch) {
        return 1;
      }
      input["location"] = stoi(metalInputReflectData.s[3]) + numDescriptors;
    }
  }

  // update descriptor bindings
  if (textures)
    for (json &descriptor : *textures) {
      RegexUtil::Match metalTextureReflectData;
      bool foundMatch =
          findMetalReflectData(metalReflectData.textures, descriptor["name"],
                               metalTextureReflectData);
      assert(foundMatch);
      descriptor["set"] = stoi(metalTextureReflectData.s[3]);
    }
  if (ubos)
    for (json &descriptor : *ubos) {
      RegexUtil::Match metalBufferReflectData;
      bool foundMatch = findMetalReflectData(
          metalReflectData.buffers, descriptor["name"], metalBufferReflectData);
      assert(foundMatch);
      descriptor["set"] = stoi(metalBufferReflectData.s[3]);
    }
  if (ssbos)
    for (json &descriptor : *ssbos) {
      RegexUtil::Match metalBufferReflectData;
      bool foundMatch = findMetalReflectData(
          metalReflectData.buffers, descriptor["name"], metalBufferReflectData);
      assert(foundMatch);
      descriptor["set"] = stoi(metalBufferReflectData.s[3]);
    }
  if (images)
    for (json &descriptor : *images) {
      RegexUtil::Match metalTextureReflectData;
      bool foundMatch =
          findMetalReflectData(metalReflectData.textures, descriptor["name"],
                               metalTextureReflectData);
      assert(foundMatch);
      descriptor["set"] = stoi(metalTextureReflectData.s[3]);
    }

  mslReflect = glslReflectJson.dump(4);
  return 0;
}

int ShaderTools::patchShaderReflectionDataHLSL(const std::string &glslReflect,
                                               const std::string &ext,
                                               const std::string &hlsl,
                                               std::string &hlslReflect) {
  auto glslReflectJson = json::parse(glslReflect);
  HLSLReflectData hlslReflectData;

  // parse semantics
  if (ext == ".vert") {
    json *inputs = getEntry(glslReflectJson, "inputs");
    for (json &input : *inputs) {
      regex p(input["name"].get<string>() + "\\s*:\\s*([^;]*);");
      vector<RegexUtil::Match> hlslReflectData = RegexUtil::findAll(p, hlsl);
      input["semantic"] = hlslReflectData[0].s[1];
    }
  }

  // get descriptors
  json *textures = getEntry(glslReflectJson, "textures"),
       *ubos = getEntry(glslReflectJson, "ubos"),
       *ssbos = getEntry(glslReflectJson, "ssbos"),
       *images = getEntry(glslReflectJson, "images");
  map<int, json *> descriptors;
  if (textures)
    for (auto &desc : *textures)
      descriptors[desc["set"].get<int>()] = &desc;
  if (ubos)
    for (auto &desc : *ubos)
      descriptors[desc["set"].get<int>()] = &desc;
  if (ssbos)
    for (auto &desc : *ssbos)
      descriptors[desc["set"].get<int>()] = &desc;
  if (images)
    for (auto &desc : *images)
      descriptors[desc["set"].get<int>()] = &desc;

  // patch descriptor bindings
  set<int> sets;
  set<string> samplerTypes = {"sampler2D", "sampler3D", "samplerCube"};
  for (const auto &kv : descriptors) {
    uint32_t set = kv.first;
    json &desc = *kv.second;
    while (sets.find(set) != sets.end())
      set += 1;
    desc["set"] = set;
    sets.insert(set);
    if (samplerTypes.find(desc["type"]) != samplerTypes.end())
      sets.insert(set + 1);
  }
  hlslReflect = glslReflectJson.dump(4);
  return 0;
}

int ShaderTools::genShaderReflectionGLSL(const string &, const string &ext,
                                         const string &spv, string &glslMap) {
  spirv_cross::CompilerReflection compilerReflection(
      (const uint32_t *)spv.data(), spv.size() / sizeof(uint32_t));
  auto reflectOutput = compilerReflection.compile();
  glslMap = json::parse(reflectOutput).dump(4);
  return 0;
}

int ShaderTools::genShaderReflectionMSL(const string &msl, const string &ext,
                                        const string &spv, string &mslMap) {
  string glslReflect;
  genShaderReflectionGLSL("", ext, spv, glslReflect);
  return patchShaderReflectionDataMSL(glslReflect, ext, msl, mslMap);
}

int ShaderTools::genShaderReflectionHLSL(const string &hlsl, const string &ext,
                                         const string &spv, string &hlslMap) {
  string glslReflect;
  genShaderReflectionGLSL("", ext, spv, glslReflect);
  return patchShaderReflectionDataHLSL(glslReflect, ext, hlsl, hlslMap);
}

string ShaderTools::parseReflectionData(const json &reflectData, string ext) {
  string contents = "";
  if (ext == ".vert") {
    json *inputs = getEntry(reflectData, "inputs");
    contents += "INPUT_ATTRIBUTES " + to_string(inputs->size()) + "\n";
    for (const json &input : *inputs) {
      string inputName = input["name"];
      string inputSemantic = "";
      string inputNameLower = toLower(inputName);
      inputSemantic = "UNDEFINED";
      if (input.find("semantic") != input.end())
        inputSemantic = input["semantic"];
      map<string, string> inputTypeMap = {
          {"float", "VERTEXFORMAT_FLOAT"}, {"vec2", "VERTEXFORMAT_FLOAT2"},
          {"vec3", "VERTEXFORMAT_FLOAT3"}, {"vec4", "VERTEXFORMAT_FLOAT4"},
          {"ivec2", "VERTEXFORMAT_INT2"},  {"ivec3", "VERTEXFORMAT_INT3"},
          {"ivec4", "VERTEXFORMAT_INT4"},  {"mat2", "VERTEXFORMAT_MAT2"},
          {"mat3", "VERTEXFORMAT_MAT3"},   {"mat4", "VERTEXFORMAT_MAT4"}};
      string inputType = inputTypeMap[input["type"]];
      contents += "\t" + inputName + " " + inputSemantic + " " +
                  to_string(input["location"].get<int>()) + " " + inputType +
                  "\n";
    }
  }
  json *textures = getEntry(reflectData, "textures"),
       *ubos = getEntry(reflectData, "ubos"),
       *ssbos = getEntry(reflectData, "ssbos"),
       *images = getEntry(reflectData, "images"),
       *types = getEntry(reflectData, "types");
  json uniformBufferInfos;
  json shaderStorageBufferInfos;

  std::function<void(const json &, json &, uint32_t, string)> parseMembers =
      [&](const json &membersData, json &members, uint32_t baseOffset = 0,
          string baseName = "") {
        for (const json &memberData : membersData) {
          const map<string, int> typeSizeMap = {
              {"int", 4},    {"uint", 4},  {"float", 4},  {"vec2", 8},
              {"vec3", 12},  {"vec4", 16}, {"ivec2", 8},  {"ivec3", 12},
              {"ivec4", 16}, {"uvec2", 8}, {"uvec3", 12}, {"uvec4", 16},
              {"mat2", 16},  {"mat3", 36}, {"mat4", 64}};
          string memberType = memberData["type"];
          if (typeSizeMap.find(memberType) != typeSizeMap.end()) {
            json member = memberData;
            member["name"] = baseName + member["name"].get<string>();
            member["size"] = typeSizeMap.at(memberType);
            member["offset"] = member["offset"].get<int>() + baseOffset;
            member["array_count"] = (member.find("array") != member.end())
                                        ? member["array"][0].get<int>()
                                        : 0;
            member["array_stride"] =
                (member.find("array_stride") != member.end())
                    ? member["array_stride"].get<int>()
                    : 0;
            members.push_back(member);
          } else if (types->find(memberType) != types->end()) {
            const json &type = (*types)[memberType];
            parseMembers(type["members"], members,
                         baseOffset + memberData["offset"].get<int>(),
                         baseName + memberData["name"].get<string>() + ".");
          } else
            NGFX_ERR("unrecognized type: {memberType}");
        }
      };

  auto parseBuffers = [&](const json &buffers, json &bufferInfos) {
    for (const json &buffer : buffers) {
      const json &bufferType = (*types)[buffer["type"].get<string>()];
      json bufferMembers = {};
      parseMembers(bufferType["members"], bufferMembers, 0, "");
      json bufferInfo = {{"name", buffer["name"].get<string>()},
                         {"set", buffer["set"].get<int>()},
                         {"binding", buffer["binding"].get<int>()},
                         {"members", bufferMembers}};
      bufferInfos.push_back(bufferInfo);
    }
  };
  if (ubos)
    parseBuffers(*ubos, uniformBufferInfos);
  if (ssbos)
    parseBuffers(*ssbos, shaderStorageBufferInfos);

  json textureDescriptors = {};
  json bufferDescriptors = {};
  if (textures)
    for (const json &texture : *textures) {
      textureDescriptors[to_string(texture["set"].get<int>())] = {
          {"type", texture["type"]},
          {"name", texture["name"]},
          {"set", texture["set"]},
          {"binding", texture["binding"]}};
    }
  if (images)
    for (const json &image : *images) {
      textureDescriptors[to_string(image["set"].get<int>())] = {
          {"type", image["type"]},
          {"name", image["name"]},
          {"set", image["set"]},
          {"binding", image["binding"]}};
    }
  if (ubos)
    for (const json &ubo : *ubos) {
      bufferDescriptors[to_string(ubo["set"].get<int>())] = {
          {"type", "uniformBuffer"},
          {"name", ubo["name"]},
          {"set", ubo["set"]},
          {"binding", ubo["binding"]}};
    }
  if (ssbos)
    for (const json &ssbo : *ssbos) {
      bufferDescriptors[to_string(ssbo["set"].get<int>())] = {
          {"type", "shaderStorageBuffer"},
          {"name", ssbo["name"]},
          {"set", ssbo["set"]},
          {"binding", ssbo["binding"]}};
    }
  contents += "DESCRIPTORS " +
              to_string(textureDescriptors.size() + bufferDescriptors.size()) +
              "\n";
  map<string, string> descriptorTypeMap = {
      {"sampler2D", "DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER"},
      {"sampler3D", "DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER"},
      {"samplerCube", "DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER"},
      {"image2D", "DESCRIPTOR_TYPE_STORAGE_IMAGE"},
      {"uniformBuffer", "DESCRIPTOR_TYPE_UNIFORM_BUFFER"},
      {"shaderStorageBuffer", "DESCRIPTOR_TYPE_STORAGE_BUFFER"}};
  for (auto &[key, val] : textureDescriptors.items()) {
    string descriptorType = descriptorTypeMap[val["type"]];
    contents += "\t" + val["name"].get<string>() + " " + descriptorType + " " +
                to_string(val["set"].get<int>()) + "\n";
  }
  for (auto &[key, val] : bufferDescriptors.items()) {
    string descriptorType = descriptorTypeMap[val["type"]];
    contents += "\t" + val["name"].get<string>() + " " + descriptorType + " " +
                to_string(val["set"].get<int>()) + "\n";
  }
  auto processBufferInfos = [&](const json &bufferInfo) -> string {
    string contents = "";
    const json &memberInfos = bufferInfo["members"];
    contents += bufferInfo["name"].get<string>() + " " +
                to_string(bufferInfo["set"].get<int>()) + " " +
                to_string(memberInfos.size()) + "\n";
    for (const json &m : memberInfos) {
      contents += m["name"].get<string>() + " " +
                  to_string(m["offset"].get<int>()) + " " +
                  to_string(m["size"].get<int>()) + " " +
                  to_string(m["array_count"].get<int>()) + " " +
                  to_string(m["array_stride"].get<int>()) + "\n";
    }
    return contents;
  };
  contents +=
      "UNIFORM_BUFFER_INFOS " + to_string(uniformBufferInfos.size()) + "\n";
  for (const json &bufferInfo : uniformBufferInfos) {
    contents += processBufferInfos(bufferInfo);
  }

  contents += "SHADER_STORAGE_BUFFER_INFOS " +
              to_string(shaderStorageBufferInfos.size()) + "\n";
  for (const json &bufferInfo : shaderStorageBufferInfos) {
    contents += processBufferInfos(bufferInfo);
  }
  return contents;
}

int ShaderTools::generateShaderMapGLSL(const string &file, string outDir,
                                       vector<string> &outFiles) {
  string filename = fs::path(file).filename().string();
  string ext = FileUtil::splitExt(filename)[1];

  string glslFileName =
      fs::path(outDir + "/" + filename).make_preferred().string();
  string spvFileName =
      fs::path(outDir + "/" + filename + ".spv").make_preferred().string();
  string glslMapFileName =
      fs::path(outDir + "/" + filename + ".map").make_preferred().string();
  if (!FileUtil::srcFileNewerThanOutFile(glslFileName, glslMapFileName)) {
    outFiles.push_back(glslMapFileName);
    return 0;
  }

  string glsl = "", spv = readFile(spvFileName), glslReflect;
  genShaderReflectionGLSL(glsl, ext, spv, glslReflect);
  auto glslReflectJson = json::parse(glslReflect);
  string glslMap = parseReflectionData(glslReflectJson, ext);

  writeFile(glslMapFileName, glslMap);
  outFiles.push_back(glslMapFileName);
  return 0;
}

int ShaderTools::generateShaderMapMSL(const string &file, string outDir,
                                      vector<string> &outFiles) {
  auto splitFilename = FileUtil::splitExt(fs::path(file).filename().string());
  string glslFilename = splitFilename[0];
  string ext = FileUtil::splitExt(splitFilename[0])[1];

  string mslFileName = fs::path(outDir + "/" + glslFilename + ".metal")
                           .make_preferred()
                           .string();
  string spvFileName =
      fs::path(outDir + "/" + glslFilename + ".spv").make_preferred().string();
  string mslMapFileName = fs::path(outDir + "/" + glslFilename + ".metal.map")
                              .make_preferred()
                              .string();
  if (!FileUtil::srcFileNewerThanOutFile(mslFileName, mslMapFileName)) {
    outFiles.push_back(mslMapFileName);
    return 0;
  }

  string msl = readFile(mslFileName), spv = readFile(spvFileName), mslReflect;
  genShaderReflectionMSL(msl, ext, spv, mslReflect);
  auto mslReflectJson = json::parse(mslReflect);
  string mslMap = parseReflectionData(mslReflectJson, ext);

  writeFile(mslMapFileName, mslMap);
  outFiles.push_back(mslMapFileName);
  return 0;
}

int ShaderTools::generateShaderMapHLSL(const string &file, string outDir,
                                       vector<string> &outFiles) {
  auto splitFilename = FileUtil::splitExt(fs::path(file).filename().string());
  string glslFilename = splitFilename[0];
  string ext = FileUtil::splitExt(splitFilename[0])[1];

  string hlslFileName =
      fs::path(outDir + "/" + glslFilename + ".hlsl").make_preferred().string();
  string spvFileName =
      fs::path(outDir + "/" + glslFilename + ".spv").make_preferred().string();
  string hlslMapFileName = fs::path(outDir + "/" + glslFilename + ".hlsl.map")
                               .make_preferred()
                               .string();
  if (!FileUtil::srcFileNewerThanOutFile(hlslFileName, hlslMapFileName)) {
    outFiles.push_back(hlslMapFileName);
    return 0;
  }

  string hlsl = readFile(hlslFileName), spv = readFile(spvFileName),
         hlslReflect;
  genShaderReflectionHLSL(hlsl, ext, spv, hlslReflect);
  auto hlslReflectJson = json::parse(hlslReflect);
  string hlslMap = parseReflectionData(hlslReflectJson, ext);

  writeFile(hlslMapFileName, hlslMap);
  outFiles.push_back(hlslMapFileName);
  return 0;
}

vector<string> ShaderTools::convertShaders(const vector<string> &files,
                                           string outDir, Format fmt) {
  vector<string> outFiles;
  for (const string &file : files)
    convertShader(file, "", outDir, fmt, outFiles);
  return outFiles;
}

vector<string> ShaderTools::compileShaders(const vector<string> &files,
                                           string outDir, Format fmt,
                                           const MacroDefinitions &defines,
                                           int flags) {
#ifdef GRAPHICS_BACKEND_VULKAN
  defines += " -DGRAPHICS_BACKEND_VULKAN=1";
#endif
  vector<string> outFiles;
  for (const string &file : files) {
    if (fmt == FORMAT_GLSL)
      compileShaderGLSL(file, defines, outDir, outFiles, flags);
    else if (fmt == FORMAT_MSL)
      compileShaderMSL(file, defines, outDir, outFiles);
    else if (fmt == FORMAT_HLSL)
      compileShaderHLSL(file, defines, outDir, outFiles);
  }
  return outFiles;
}

void ShaderTools::applyPatches(const vector<string> &patchFiles,
                               string outDir) {
  for (const string &patchFile : patchFiles) {
    string filename = FileUtil::splitExt(fs::path(patchFile).string())[0];
    NGFX_LOG("filename: %s", filename.c_str());
    string outFile =
        fs::path(outDir + "/" + filename).make_preferred().string();
    if (fs::exists(outFile)) {
      NGFX_LOG("applying patch: {patchFile}");
      string cmdStr = PATCH + " -N -u " + outFile + " -i " + patchFile;
      cmd(cmdStr);
    }
  }
}

vector<string> ShaderTools::generateShaderMaps(const vector<string> &files,
                                               string outDir, Format fmt) {
  vector<string> outFiles;
  for (const string &file : files) {
    if (fmt == FORMAT_GLSL)
      generateShaderMapGLSL(file, outDir, outFiles);
    else if (fmt == FORMAT_MSL)
      generateShaderMapMSL(file, outDir, outFiles);
    else if (fmt == FORMAT_HLSL)
      generateShaderMapHLSL(file, outDir, outFiles);
  }
  return outFiles;
}

Updated on 3 April 2021 at 20:21:51 PDT