diff --git a/shaders/modal.frag b/assets/shaders/modal.frag similarity index 100% rename from shaders/modal.frag rename to assets/shaders/modal.frag diff --git a/assets/shaders/ui_error.frag b/assets/shaders/ui_error.frag new file mode 100644 index 0000000..29ccc8c --- /dev/null +++ b/assets/shaders/ui_error.frag @@ -0,0 +1,18 @@ +uniform vec2 u_resolution; +uniform vec2 u_mouse; +uniform float u_duration; +uniform float u_time; +uniform float u_time_end; +uniform sampler2D texture; +uniform bool is_shape; + +void main() { + if(is_shape) { + vec4 color = vec4(1.0, 0.0, 0.0, 1.0); + gl_FragColor = gl_Color * color; + } else { + vec4 pixel = texture2D(texture, gl_TexCoord[0].xy); + vec4 color = vec4(1.0, 0.0, 0.0, 1.0); + gl_FragColor = gl_Color * color * pixel; + } +} diff --git a/shaders/ui_shader.frag b/assets/shaders/ui_shader.frag similarity index 100% rename from shaders/ui_shader.frag rename to assets/shaders/ui_shader.frag diff --git a/shaders/ui_shape_shader.frag b/assets/shaders/ui_shape_shader.frag similarity index 100% rename from shaders/ui_shape_shader.frag rename to assets/shaders/ui_shape_shader.frag diff --git a/constants.hpp b/constants.hpp index 1a7d6ad..30d62a6 100644 --- a/constants.hpp +++ b/constants.hpp @@ -73,7 +73,6 @@ constexpr wchar_t BG_TILE = L'█'; constexpr wchar_t UI_BASE_CHAR = L'█'; constexpr int BG_BOX_OFFSET=5; constexpr const char *FONT_FILE_NAME="assets/text.otf"; -constexpr const char *DEFAULT_UI_SHADER = "shaders/ui_shader.frag"; constexpr std::array COMPASS{ // L"E", L"SE", L"S", L"SW", L"W", L"NW", L"N", L"NE" diff --git a/guecs.cpp b/guecs.cpp index 24eff7c..1c5df73 100644 --- a/guecs.cpp +++ b/guecs.cpp @@ -1,4 +1,5 @@ #include "guecs.hpp" +#include "shaders.hpp" namespace guecs { @@ -66,31 +67,31 @@ namespace guecs { } void Shader::init(lel::Cell &cell) { - ptr = std::make_shared(); - bool good = ptr->loadFromFile(name, sf::Shader::Type::Fragment); - dbc::check(good, fmt::format("failed to load shader {}", name)); - ptr->setUniform("u_resolution", sf::Vector2f({float(cell.w), float(cell.h)})); - + auto shader = shaders::get(name); + shader->setUniform("u_resolution", sf::Vector2f({float(cell.w), float(cell.h)})); clock = std::make_shared(); } void Shader::step() { + auto shader = shaders::get(name); sf::Time u_time = clock->getElapsedTime(); float current_time = u_time.asSeconds(); if(current_time < u_time_end) { - ptr->setUniform("u_time", current_time); + shader->setUniform("u_time", current_time); } else { active = false; } } void Shader::run() { + auto shader = shaders::get(name); + active = true; sf::Time u_time = clock->getElapsedTime(); u_time_end = u_time.asSeconds() + duration; - ptr->setUniform("u_duration", duration); - ptr->setUniform("u_time_end", u_time_end); + shader->setUniform("u_duration", duration); + shader->setUniform("u_time_end", u_time_end); } UI::UI() { diff --git a/guecs.hpp b/guecs.hpp index ebce9d1..4c1790d 100644 --- a/guecs.hpp +++ b/guecs.hpp @@ -11,6 +11,7 @@ #include "constants.hpp" #include "components.hpp" #include +#include "shaders.hpp" namespace guecs { using std::shared_ptr, std::make_shared, std::wstring, std::string; @@ -83,10 +84,9 @@ namespace guecs { struct Shader { float duration = 0.1f; - std::string name{DEFAULT_UI_SHADER}; + std::string name{"ui_shader"}; float u_time_end = 0.0; bool active = false; - std::shared_ptr ptr = nullptr; std::shared_ptr clock = nullptr; void init(lel::Cell &cell); @@ -199,7 +199,7 @@ namespace guecs { auto& shader = $world.get(ent); if(shader.active) { - shader_ptr = shader.ptr.get(); + shader_ptr = shaders::get(shader.name); shader_ptr->setUniform("is_shape", is_shape); } } diff --git a/gui_fsm.cpp b/gui_fsm.cpp index f95cf07..ff5d316 100644 --- a/gui_fsm.cpp +++ b/gui_fsm.cpp @@ -286,6 +286,7 @@ namespace gui { case KEY::P: sound::mute(false); $debug_ui.debug(); + shaders::reload(); break; case KEY::O: autowalking = true; diff --git a/main.cpp b/main.cpp index 02913ce..d6d88e4 100644 --- a/main.cpp +++ b/main.cpp @@ -5,6 +5,7 @@ #include "ai.hpp" #include "animation.hpp" #include +#include "shaders.hpp" int main(int argc, char* argv[]) { try { @@ -12,6 +13,7 @@ int main(int argc, char* argv[]) { sound::init(); ai::init("assets/ai.json"); animation::init(); + shaders::init(); sound::mute(true); gui::FSM main; diff --git a/meson.build b/meson.build index d31d23c..38b26e5 100644 --- a/meson.build +++ b/meson.build @@ -114,6 +114,7 @@ sources = [ 'ritual_ui.cpp', 'rituals.cpp', 'save.cpp', + 'shaders.cpp', 'shiterator.hpp', 'sound.cpp', 'spatialmap.cpp', @@ -145,6 +146,7 @@ executable('runtests', sources + [ 'tests/matrix.cpp', 'tests/pathing.cpp', 'tests/rituals.cpp', + 'tests/shaders.cpp', 'tests/sound.cpp', 'tests/spatialmap.cpp', 'tests/stats.cpp', diff --git a/raycaster.cpp b/raycaster.cpp index ab7e838..1a51795 100644 --- a/raycaster.cpp +++ b/raycaster.cpp @@ -394,7 +394,7 @@ void Raycaster::update_level(GameLevel level) { void Raycaster::init_shaders() { // dbc::check(sf::Shader::isAvailable(), "no shaders?!"); - bool good = $brightness.loadFromFile("shaders/modal.frag", sf::Shader::Type::Fragment); + bool good = $brightness.loadFromFile("assets/shaders/modal.frag", sf::Shader::Type::Fragment); dbc::check(good, "shader could not be loaded"); $brightness.setUniform("source", sf::Shader::CurrentTexture); } diff --git a/shaders.cpp b/shaders.cpp new file mode 100644 index 0000000..c464d68 --- /dev/null +++ b/shaders.cpp @@ -0,0 +1,60 @@ +#include "shaders.hpp" +#include +#include "dbc.hpp" +#include +#include "config.hpp" +#include "constants.hpp" +#include + +namespace shaders { + using std::shared_ptr, std::make_shared; + + static ShaderManager SMGR; + static bool initialized = false; + + + bool load_shader(std::string name, nlohmann::json& settings) { + std::string file_name = settings["file_name"]; + auto ptr = std::make_shared(); + bool good = ptr->loadFromFile(file_name, sf::Shader::Type::Fragment); + if(good) SMGR.shaders.try_emplace(name, name, file_name, ptr); + return good; + } + + void init() { + if(!initialized) { + initialized = true; + Config config("assets/shaders.json"); + bool good = load_shader("ERROR", config["ERROR"]); + dbc::check(good, "Failed to load ERROR shader. Look in assets/shaders.json"); + + for(auto& [name, settings] : config.json().items()) { + if(name == "ERROR") continue; + + dbc::check(!SMGR.shaders.contains(name), + fmt::format("shader name '{}' duplicated in assets/shaders.json", name)); + good = load_shader(name, settings); + + if(!good) { + dbc::log(fmt::format("failed to load shader {}", name)); + SMGR.shaders.insert_or_assign(name, SMGR.shaders.at("ERROR")); + } + } + } + } + + sf::Shader* get(std::string name) { + dbc::check(initialized, "you forgot to shaders::init()"); + dbc::check(SMGR.shaders.contains(name), + fmt::format("shader name '{}' not in assets/shaders.json", name)); + + auto& rec = SMGR.shaders.at(name); + return rec.ptr.get(); + } + + void reload() { + initialized = false; + SMGR.shaders.clear(); + init(); + } +}; diff --git a/shaders.hpp b/shaders.hpp new file mode 100644 index 0000000..25db13b --- /dev/null +++ b/shaders.hpp @@ -0,0 +1,26 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include "matrix.hpp" +#include + +namespace shaders { + struct Record { + std::string name; + std::string file_name; + std::shared_ptr ptr = nullptr; + }; + + struct ShaderManager { + std::unordered_map shaders; + }; + + void init(); + bool load_shader(std::string& name, nlohmann::json& settings); + sf::Shader* get(std::string name); + void reload(); +} diff --git a/tests/shaders.cpp b/tests/shaders.cpp new file mode 100644 index 0000000..52adb5e --- /dev/null +++ b/tests/shaders.cpp @@ -0,0 +1,22 @@ +#include +#include +#include +#include "shaders.hpp" + +using namespace fmt; + +TEST_CASE("shader loading/init works", "[shaders]") { + shaders::init(); + + sf::Shader* ui_shader = shaders::get("ui_shader"); + auto other_test = shaders::get("ui_shader"); + + REQUIRE(ui_shader != nullptr); + REQUIRE(ui_shader == other_test); + + shaders::reload(); + + // auto after_reload = shaders::get("ui_shader"); + // REQUIRE(ui_shader != after_reload); + // REQUIRE(other_test != after_reload); +}