mirror of https://github.com/snes9xgit/snes9x.git
246 lines
6.7 KiB
C++
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;
|
|
} |