/*
|
|
* Copyright (C) 2021 Christopher J. Howard
|
|
*
|
|
* This file is part of Antkeeper source code.
|
|
*
|
|
* Antkeeper source code is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* Antkeeper source code is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "resources/resource-loader.hpp"
|
|
#include "resources/resource-manager.hpp"
|
|
#include "resources/text-file.hpp"
|
|
#include "rasterizer/shader-type.hpp"
|
|
#include "rasterizer/shader.hpp"
|
|
#include "rasterizer/shader-program.hpp"
|
|
#include <sstream>
|
|
#include <iterator>
|
|
|
|
/**
|
|
* Tokenizes a line of shader source code.
|
|
*/
|
|
static std::vector<std::string> tokenize(const std::string& line)
|
|
{
|
|
std::vector<std::string> tokens;
|
|
std::string token;
|
|
std::istringstream linestream(line);
|
|
while (linestream >> token)
|
|
tokens.push_back(token);
|
|
|
|
return tokens;
|
|
}
|
|
|
|
/**
|
|
* Handles `#pragma include` directives by loading the specified text files and inserting them in place.
|
|
*/
|
|
static void handle_includes(text_file* source, resource_manager* resource_manager)
|
|
{
|
|
// For each line in the source
|
|
for (std::size_t i = 0; i < source->size(); ++i)
|
|
{
|
|
// Tokenize line
|
|
std::vector<std::string> tokens = tokenize((*source)[i]);
|
|
|
|
// Look for `#pragma include` directives
|
|
if (tokens.size() == 3 && tokens[0] == "#pragma" && tokens[1] == "include")
|
|
{
|
|
// Get path to include file
|
|
std::string path = tokens[2].substr(1, tokens[2].length() - 2);
|
|
|
|
// Load include file
|
|
if (!resource_manager->load<text_file>(path))
|
|
{
|
|
std::string message = std::string("Failed to load shader include file \"") + path + std::string("\"");
|
|
throw std::runtime_error(message.c_str());
|
|
}
|
|
text_file include_file = *(resource_manager->load<text_file>(path));
|
|
|
|
// Handle `#pragma include` directives inside include file
|
|
handle_includes(&include_file, resource_manager);
|
|
|
|
// Replace #pragma include directive with include file contents
|
|
source->erase(source->begin() + i);
|
|
source->insert(source->begin() + i, include_file.begin(), include_file.end());
|
|
i += include_file.size() - 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Injects the `DEBUG` or `NDEBUG` macros after the `#version` directive.
|
|
*/
|
|
static void inject_debug_macro(text_file* source)
|
|
{
|
|
// For each line in the source
|
|
for (std::size_t i = 0; i < source->size(); ++i)
|
|
{
|
|
// Tokenize line
|
|
std::vector<std::string> tokens = tokenize((*source)[i]);
|
|
|
|
// Inject DEBUG and NDEBUG macros
|
|
if (!tokens.empty() && tokens[0] == "#version")
|
|
{
|
|
#if defined(NDEBUG)
|
|
source->insert(source->begin() + i + 1, "#define NDEBUG");
|
|
#else
|
|
source->insert(source->begin() + i + 1, "#define DEBUG");
|
|
#endif
|
|
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Injects a shader type macro definition after the `#version` directive.
|
|
*/
|
|
static void inject_shader_type_macro(text_file* source, shader_type type)
|
|
{
|
|
const char* vertex_macro = "#define _VERTEX";
|
|
const char* fragment_macro = "#define _FRAGMENT";
|
|
const char* geometry_macro = "#define _GEOMETRY";
|
|
|
|
const char* macro = nullptr;
|
|
if (type == shader_type::vertex)
|
|
macro = vertex_macro;
|
|
else if (type == shader_type::fragment)
|
|
macro = fragment_macro;
|
|
else if (type == shader_type::geometry)
|
|
macro = geometry_macro;
|
|
|
|
// For each line in the source
|
|
for (std::size_t i = 0; i < source->size(); ++i)
|
|
{
|
|
// Tokenize line
|
|
std::vector<std::string> tokens = tokenize((*source)[i]);
|
|
|
|
// Inject shader type macro
|
|
if (!tokens.empty() && tokens[0] == "#version")
|
|
{
|
|
source->insert(source->begin() + i + 1, macro);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
static std::list<shader_type> detect_shader_types(text_file* source)
|
|
{
|
|
std::list<shader_type> types;
|
|
|
|
// For each line in the source
|
|
for (std::size_t i = 0; i < source->size(); ++i)
|
|
{
|
|
// Tokenize line
|
|
std::vector<std::string> tokens = tokenize((*source)[i]);
|
|
|
|
// Look for `#pragma include` directives
|
|
if (tokens.size() == 2 && tokens[0] == "#pragma")
|
|
{
|
|
if (tokens[1] == "vertex")
|
|
{
|
|
types.push_back(shader_type::vertex);
|
|
}
|
|
else if (tokens[1] == "fragment")
|
|
{
|
|
types.push_back(shader_type::fragment);
|
|
}
|
|
else if (tokens[1] == "geometry")
|
|
{
|
|
types.push_back(shader_type::geometry);
|
|
}
|
|
}
|
|
}
|
|
|
|
return types;
|
|
}
|
|
|
|
static std::string generate_source_buffer(const std::vector<std::string>& source)
|
|
{
|
|
std::ostringstream stream;
|
|
std::copy(source.begin(), source.end(), std::ostream_iterator<std::string>(stream, "\n"));
|
|
return stream.str();
|
|
}
|
|
|
|
template <>
|
|
shader_program* resource_loader<shader_program>::load(resource_manager* resource_manager, PHYSFS_File* file)
|
|
{
|
|
// Load shader source
|
|
text_file* source = resource_loader<text_file>::load(resource_manager, file);
|
|
|
|
// Handle `#pragma include` directives
|
|
handle_includes(source, resource_manager);
|
|
|
|
// Inject `DEBUG` or `NDEBUG` macro definitions
|
|
inject_debug_macro(source);
|
|
|
|
// Detect declared shader types via the `#pragma vertex`, `#pragma fragment` and `#pragma geometry` directives
|
|
std::list<shader_type> shader_types = detect_shader_types(source);
|
|
|
|
// Load detected shaders
|
|
std::list<shader*> shaders;
|
|
for (shader_type type: shader_types)
|
|
{
|
|
text_file type_source = *source;
|
|
inject_shader_type_macro(&type_source, type);
|
|
std::string source_buffer = generate_source_buffer(type_source);
|
|
shaders.push_back(new shader(type, source_buffer));
|
|
}
|
|
|
|
// Create shader program
|
|
shader_program* program = new shader_program(shaders);
|
|
|
|
// Delete shaders
|
|
for (shader* shader: shaders)
|
|
{
|
|
delete shader;
|
|
}
|
|
|
|
return program;
|
|
}
|
|
|