snes9x/vulkan/slang_shader.cpp

246 lines
6.7 KiB
C++

#include "slang_shader.hpp"
#include "slang_helpers.hpp"
#include <ostream>
#include <string>
#include <string_view>
#include <sstream>
#include <vector>
#include <fstream>
#include "../external/glslang/glslang/Public/ShaderLang.h"
#include "../external/glslang/SPIRV/GlslangToSpv.h"
#include "../external/glslang/glslang/Public/ResourceLimits.h"
using std::string;
using std::vector;
SlangShader::SlangShader()
{
ubo_size = 0;
}
SlangShader::~SlangShader()
{
}
/*
Recursively load shader file and included files into memory, applying
#include and #pragma directives. Will strip all directives except
#pragma stage.
*/
bool SlangShader::preprocess_shader_file(string filename, vector<string> &lines)
{
std::ifstream stream(filename.c_str(), std::ios::binary);
if (stream.fail())
return false;
string line;
while (std::getline(stream, line, '\n'))
{
std::string_view sv(line);
trim(sv);
if (sv.empty())
continue;
else if (sv.compare(0, 8, "#include") == 0)
{
sv.remove_prefix(8);
trim(sv);
if (sv.length() && sv[0] == '\"' && sv[sv.length() - 1] == '\"')
{
sv.remove_prefix(1);
sv.remove_suffix(1);
string include_file(sv);
canonicalize(include_file, filename);
preprocess_shader_file(include_file, lines);
}
else
{
printf("Syntax error: #include.\n");
return false;
}
}
else if (sv.compare(0, 17, "#pragma parameter") == 0)
{
Parameter p{};
sv.remove_prefix(17);
auto tokens = split_string_quotes(string{ sv });
if (tokens.size() < 5)
{
printf("Syntax error: #pragma parameter\n");
printf("%s\n", string{ sv }.c_str());
return false;
}
p.id = tokens[0];
p.name = tokens[1];
p.val = std::stof(tokens[2]);
p.min = std::stof(tokens[3]);
p.max = std::stof(tokens[4]);
if (tokens.size() >= 6)
p.step = std::stof(tokens[5]);
p.significant_digits = 0;
for (size_t i = 2; i < tokens.size(); i++)
{
int significant_digits = get_significant_digits(tokens[i]);
if (significant_digits > p.significant_digits)
p.significant_digits = significant_digits;
}
parameters.push_back(p);
continue;
}
else if (sv.compare(0, 12, "#pragma name") == 0)
{
alias = sv.substr(13);
}
else if (sv.compare(0, 14, "#pragma format") == 0)
{
format = sv.substr(15);
}
else
{
if (sv.compare(0, 13, "#pragma stage") == 0)
pragma_stage_lines.push_back(lines.size());
lines.push_back(line);
}
}
return true;
}
/*
Use the #pragma stage lines to divide the file into separate vertex and
fragment shaders. Must have called preprocess beforehand.
*/
void SlangShader::divide_into_stages(const std::vector<std::string> &lines)
{
enum
{
vertex,
fragment,
both
} stage;
stage = both;
std::ostringstream vertex_shader_stream;
std::ostringstream fragment_shader_stream;
auto p = pragma_stage_lines.begin();
for (size_t i = 0; i < lines.size(); i++)
{
if (p != pragma_stage_lines.end() && i == *p)
{
if (lines[i].find("vertex") != string::npos)
stage = vertex;
else if (lines[i].find("fragment") != string::npos)
stage = fragment;
p++;
}
else
{
if (stage == vertex || stage == both)
vertex_shader_stream << lines[i] << '\n';
if (stage == fragment || stage == both)
fragment_shader_stream << lines[i] << '\n';
}
}
vertex_shader_string = vertex_shader_stream.str();
fragment_shader_string = fragment_shader_stream.str();
}
/*
Load a shader file into memory, preprocess, divide and compile it to
SPIRV bytecode. Returns true on success.
*/
bool SlangShader::load_file(string new_filename)
{
if (!new_filename.empty())
filename = new_filename;
pragma_stage_lines.clear();
vector<string> lines;
if (!preprocess_shader_file(filename, lines))
{
printf("Failed to load shader file: %s\n", filename.c_str());
return false;
}
divide_into_stages(lines);
if (!generate_spirv())
return false;
return true;
}
void SlangShader::initialize_glslang()
{
static bool ProcessInitialized = false;
if (!ProcessInitialized)
{
glslang::InitializeProcess();
ProcessInitialized = true;
}
}
std::vector<uint32_t> SlangShader::generate_spirv(std::string shader_string, std::string stage)
{
initialize_glslang();
const EShMessages messages = (EShMessages)(EShMsgDefault | EShMsgVulkanRules | EShMsgSpvRules);
string debug;
auto forbid_includer = glslang::TShader::ForbidIncluder();
auto language = stage == "vertex" ? EShLangVertex : stage == "fragment" ? EShLangFragment : EShLangCompute;
glslang::TShader shaderTShader(language);
auto compile = [&](glslang::TShader &shader, string &shader_string, std::vector<uint32_t> &spirv) -> bool {
const char *source = shader_string.c_str();
shader.setStrings(&source, 1);
if (!shader.preprocess(GetDefaultResources(), 450, ENoProfile, false, false, messages, &debug, forbid_includer))
return false;
if (!shader.parse(GetDefaultResources(), 450, false, messages))
return false;
glslang::TProgram program;
program.addShader(&shader);
if (!program.link(messages))
return false;
glslang::GlslangToSpv(*program.getIntermediate(shader.getStage()), spirv);
return true;
};
std::vector<uint32_t> spirv;
if (!compile(shaderTShader, shader_string, spirv))
{
printf("%s\n%s\n%s\n", debug.c_str(), shaderTShader.getInfoLog(), shaderTShader.getInfoDebugLog());
}
return spirv;
}
/*
Generate SPIRV from separate preprocessed fragment and vertex shaders.
Must have called divide_into_stages beforehand. Returns true on success.
*/
bool SlangShader::generate_spirv()
{
vertex_shader_spirv = generate_spirv(vertex_shader_string, "vertex");
if (vertex_shader_spirv.empty())
return false;
fragment_shader_spirv = generate_spirv(fragment_shader_string, "fragment");
if (fragment_shader_spirv.empty())
return false;
return true;
}