/*
* 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 .
*/
#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
#include
/**
* Tokenizes a line of shader source code.
*/
static std::vector tokenize(const std::string& line)
{
std::vector 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 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(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(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 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 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 detect_shader_types(text_file* source)
{
std::list types;
// For each line in the source
for (std::size_t i = 0; i < source->size(); ++i)
{
// Tokenize line
std::vector 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& source)
{
std::ostringstream stream;
std::copy(source.begin(), source.end(), std::ostream_iterator(stream, "\n"));
return stream.str();
}
template <>
shader_program* resource_loader::load(resource_manager* resource_manager, PHYSFS_File* file)
{
// Load shader source
text_file* source = resource_loader::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_types = detect_shader_types(source);
// Load detected shaders
std::list 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;
}