From 1203b30cfdcd5db4bf983993c00cf56eb0d58075 Mon Sep 17 00:00:00 2001 From: Joseph Walton-Rivers <joseph@walton-rivers.uk> Date: Wed, 22 Jun 2022 08:33:17 +0100 Subject: [PATCH] add 2D fallback shader --- fggl/gfx/ogl/renderer.cpp | 15 ++-- fggl/gfx/ogl/shader.cpp | 134 +++++++++++++++++++++++------ include/fggl/gfx/ogl/renderer.hpp | 1 + include/fggl/gfx/ogl/shader.hpp | 25 +++++- include/fggl/gfx/ogl4/fallback.hpp | 63 ++++++++++++++ 5 files changed, 205 insertions(+), 33 deletions(-) create mode 100644 include/fggl/gfx/ogl4/fallback.hpp diff --git a/fggl/gfx/ogl/renderer.cpp b/fggl/gfx/ogl/renderer.cpp index c65e7b5..09782d1 100644 --- a/fggl/gfx/ogl/renderer.cpp +++ b/fggl/gfx/ogl/renderer.cpp @@ -22,6 +22,7 @@ #include <fggl/gfx/ogl/renderer.hpp> #include <fggl/gfx/paint.hpp> #include "fggl/data/model.hpp" +#include "fggl/gfx/ogl4/fallback.hpp" #include <glm/ext/matrix_clip_space.hpp> #include <glm/ext/matrix_transform.hpp> @@ -137,7 +138,7 @@ namespace fggl::gfx { using data::Mesh2D; using data::Vertex2D; - OpenGL4Backend::OpenGL4Backend(const Window &owner) : fggl::gfx::Graphics() { + OpenGL4Backend::OpenGL4Backend(const Window &owner) : fggl::gfx::Graphics(), m_canvasPipeline(INVALID_SHADER_ID) { // initialise GLEW, or fail // FIXME this binds the graphics stack to GLFW :'( int version = gladLoadGLLoader( (GLADloadproc)glfwGetProcAddress ); @@ -166,8 +167,13 @@ namespace fggl::gfx { m_cache = std::make_unique<ShaderCache>(storage); // setup 2D rendering system - ShaderConfig shader2DConfig = ShaderFromName("shader2D"); - m_cache->load(shader2DConfig); + ShaderConfig shader2DConfig = ShaderFromName("canvas"); + m_canvasPipeline = m_cache->load(shader2DConfig); + if ( m_canvasPipeline == INVALID_SHADER_ID) { + debug::error("failed to load shader2D - using fallback"); + m_canvasPipeline = m_cache->get(ogl4::FALLBACK_CANVAS_PIPELINE); + } + ShaderConfig shader3DConfig = ShaderFromName("phong"); m_cache->load(shader3DConfig); @@ -191,8 +197,7 @@ namespace fggl::gfx { return; } - auto shader2D = m_cache->get("shader2D"); - m_canvasRenderer->render(shader2D, paint); + m_canvasRenderer->render(m_canvasPipeline, paint); } void OpenGL4Backend::drawScene(ecs3::World &world) { diff --git a/fggl/gfx/ogl/shader.cpp b/fggl/gfx/ogl/shader.cpp index e43481c..5f9e251 100644 --- a/fggl/gfx/ogl/shader.cpp +++ b/fggl/gfx/ogl/shader.cpp @@ -14,6 +14,7 @@ #include "fggl/gfx/ogl/types.hpp" #include <fggl/gfx/ogl/shader.hpp> +#include "fggl/gfx/ogl4/fallback.hpp" #include <iostream> #include <vector> @@ -21,17 +22,10 @@ using namespace fggl::gfx; -bool ShaderCache::compileShader(const std::string& fname, GLuint sid) { - std::string source; - bool result = m_storage->load(fggl::data::Data, fname, &source); - if ( !result ) { - spdlog::warn("could not load shader source from disk: {}", fname.c_str()); - return false; - } - +bool ShaderCache::compileShaderFromSource(const std::string& source, GLuint sid) { // upload and compile shader - const auto *src_cstr = (const GLchar *)source.c_str(); - glShaderSource(sid, 1, &src_cstr, 0); + const char* src = source.c_str(); + glShaderSource(sid, 1, &src, nullptr); glCompileShader(sid); // check it worked @@ -44,15 +38,25 @@ bool ShaderCache::compileShader(const std::string& fname, GLuint sid) { char* infoLog = new char[maxLength]; glGetShaderInfoLog(sid, maxLength, &maxLength, infoLog); - spdlog::warn("could not compile shader, '{}': {}", fname, infoLog); + spdlog::warn("could not compile shader source: {}", infoLog); delete[] infoLog; - return false; } return true; } +bool ShaderCache::readAndCompileShader(const std::string &filename, GLuint shader) { + std::string source; + bool result = m_storage->load(fggl::data::Data, filename, &source); + if ( !result ) { + spdlog::warn("could not load shader source from disk: {}", filename.c_str()); + return false; + } + + return compileShaderFromSource(source, shader); +} + ShaderCache::ShaderCache(std::shared_ptr<fggl::data::Storage> storage) : m_storage(storage), m_shaders(), m_binary(true) { if ( !GLAD_GL_ARB_get_program_binary ) { @@ -63,6 +67,7 @@ ShaderCache::ShaderCache(std::shared_ptr<fggl::data::Storage> storage) : m_stora if ( GLAD_GL_ARB_shading_language_include ) { setupIncludes(); } + initFallbackPipelines(); } void ShaderCache::setupIncludes() { @@ -80,14 +85,14 @@ void ShaderCache::setupIncludes() { } -bool ShaderCache::loadFromDisk(GLuint pid, const ShaderConfig& config) { +bool ShaderCache::loadFromDisk(GLuint pid, const std::string& pipelineName) { BinaryCache cache; - auto fname = "shader_" + config.name + ".bin"; + auto fname = "shader_" + pipelineName + ".bin"; bool status = m_storage->load( fggl::data::Cache, fname, &cache ); if ( !status ) { - spdlog::info("cached shader '{}' could not be loaded from disk", config.name); + spdlog::info("cached shader '{}' could not be loaded from disk", pipelineName); return false; } @@ -96,12 +101,11 @@ bool ShaderCache::loadFromDisk(GLuint pid, const ShaderConfig& config) { return result; } -void ShaderCache::saveToDisk(GLuint pid, const ShaderConfig& config) { - +void ShaderCache::saveToDisk(GLuint pid, const std::string& pipelineName) { BinaryCache cache; cacheSave(pid, &cache); - auto fname = "shader_" + config.name + ".bin"; + auto fname = "shader_" + pipelineName + ".bin"; m_storage->save( fggl::data::Cache, fname, &cache); } @@ -125,7 +129,7 @@ GLuint ShaderCache::load(const ShaderConfig& config) { if ( m_binary ) { // if we have support for shader cache, give that a go - bool worked = loadFromDisk(pid, config); + bool worked = loadFromDisk(pid, config.name); if ( worked ) { m_shaders[config.name] = pid; return pid; @@ -136,17 +140,17 @@ GLuint ShaderCache::load(const ShaderConfig& config) { // TODO actual shader loading GLuint vertShader = glCreateShader(GL_VERTEX_SHADER); - compileShader(config.vertex, vertShader); + readAndCompileShader(config.vertex, vertShader); glAttachShader(pid, vertShader); GLuint fragShader = glCreateShader(GL_FRAGMENT_SHADER); - compileShader(config.fragment, fragShader); + readAndCompileShader(config.fragment, fragShader); glAttachShader(pid, fragShader); - GLuint geomShader; + GLuint geomShader = 0; if ( config.hasGeom ) { geomShader = glCreateShader(GL_GEOMETRY_SHADER); - compileShader( config.geometry, geomShader ); + readAndCompileShader( config.geometry, geomShader ); glAttachShader(pid, geomShader); } @@ -175,11 +179,11 @@ GLuint ShaderCache::load(const ShaderConfig& config) { glDeleteShader( geomShader ); } - return false; + return INVALID_SHADER_ID; } if ( m_binary ) { - saveToDisk(pid, config); + saveToDisk(pid, config.name); } // update the cache and return @@ -187,6 +191,76 @@ GLuint ShaderCache::load(const ShaderConfig& config) { return pid; } +GLuint ShaderCache::load(const ShaderSources& sources, bool allowBinaryCache) { + + spdlog::debug("starting shader program generation for {}", sources.name); + + GLuint pid = glCreateProgram(); + + if ( m_binary && allowBinaryCache ) { + // if we have support for shader cache, give that a go + bool worked = loadFromDisk(pid, sources.name); + if ( worked ) { + m_shaders[sources.name] = pid; + return pid; + } + + spdlog::debug("could not use cached shader for '{}', doing full compile.", sources.name); + } + + // TODO actual shader loading + GLuint vertShader = glCreateShader(GL_VERTEX_SHADER); + compileShaderFromSource(sources.vertexSource, vertShader); + glAttachShader(pid, vertShader); + + GLuint fragShader = glCreateShader(GL_FRAGMENT_SHADER); + compileShaderFromSource(sources.fragmentSource, fragShader); + glAttachShader(pid, fragShader); + + GLuint geomShader = INVALID_SHADER_ID; + if ( !sources.geometrySource.empty() ) { + geomShader = glCreateShader(GL_GEOMETRY_SHADER); + compileShaderFromSource(sources.geometrySource, geomShader); + glAttachShader(pid, geomShader); + } + + glLinkProgram(pid); + glDetachShader(pid, vertShader); + glDetachShader(pid, fragShader); + + if ( geomShader != INVALID_SHADER_ID ) { + glDetachShader(pid, geomShader); + } + + GLint linked = GL_FALSE; + glGetProgramiv(pid, GL_LINK_STATUS, &linked); + if ( linked == GL_FALSE ) { + + // get the error + std::array<char, 512> infoLog; + glGetProgramInfoLog(pid, infoLog.size(), nullptr, infoLog.data()); + spdlog::warn("linking shader program '{}' failed: {}", sources.name, infoLog.data()); + + // cleanup + glDeleteProgram( pid ); + glDeleteShader( vertShader ); + glDeleteShader( fragShader ); + if ( geomShader != INVALID_SHADER_ID ) { + glDeleteShader( geomShader ); + } + + return INVALID_SHADER_ID; + } + + if ( m_binary && allowBinaryCache ) { + saveToDisk(pid, sources.name); + } + + // update the cache and return + m_shaders[ sources.name ] = pid; + return pid; +} + void ShaderCache::cacheSave(GLuint pid, BinaryCache* cache) { GLsizei length; glGetProgramiv(pid, GL_PROGRAM_BINARY_LENGTH, &length); @@ -209,6 +283,16 @@ bool ShaderCache::cacheLoad(GLuint pid, const BinaryCache* cache) { return status == GL_TRUE; } +void ShaderCache::initFallbackPipelines() { + // canvas fallback pipeline + load({ + .name = ogl4::FALLBACK_CANVAS_PIPELINE, + .vertexSource = ogl4::FALLBACK_CANVAS_VERTEX_SHADER, + .fragmentSource = ogl4::FALLBACK_CANVAS_FRAGMENT_SHADER, + .geometrySource = "" + }, false); +} + template<> bool fggl::data::fggl_deserialize(std::filesystem::path &data, fggl::gfx::BinaryCache *out) { auto f = diff --git a/include/fggl/gfx/ogl/renderer.hpp b/include/fggl/gfx/ogl/renderer.hpp index 93200ab..8abc93a 100644 --- a/include/fggl/gfx/ogl/renderer.hpp +++ b/include/fggl/gfx/ogl/renderer.hpp @@ -119,6 +119,7 @@ namespace fggl::gfx { std::unique_ptr<ogl4::CanvasRenderer> m_canvasRenderer; std::unique_ptr<ogl4::DebugRenderer> m_debugRenderer; std::unique_ptr<ShaderCache> m_cache; + GLuint m_canvasPipeline; }; using OpenGL4 = OpenGL4Backend; diff --git a/include/fggl/gfx/ogl/shader.hpp b/include/fggl/gfx/ogl/shader.hpp index 3c04b27..ba3bd18 100644 --- a/include/fggl/gfx/ogl/shader.hpp +++ b/include/fggl/gfx/ogl/shader.hpp @@ -25,6 +25,8 @@ namespace fggl::gfx { + constexpr GLuint INVALID_SHADER_ID = 0; + struct ShaderConfig { // required std::string name; @@ -36,6 +38,13 @@ namespace fggl::gfx { bool hasGeom = false; }; + struct ShaderSources { + std::string name; + std::string vertexSource; + std::string fragmentSource; + std::string geometrySource; + }; + inline ShaderConfig ShaderFromName(const std::string &name) { return { name, @@ -56,10 +65,19 @@ namespace fggl::gfx { ~ShaderCache() = default; GLuint load(const ShaderConfig &config); + GLuint load(const ShaderSources &sources, bool allowBinaryCache); + GLuint getOrLoad(const ShaderConfig &config); GLuint get(const std::string &name); + /** + * Fallback pipelines. + * + * All fallback pipelines are prefixed with "fallback_". They should be used as 'last resort' if there is + * no matching shader on disk for this game. + */ + void initFallbackPipelines(); private: std::shared_ptr<fggl::data::Storage> m_storage; @@ -69,11 +87,12 @@ namespace fggl::gfx { void setupIncludes(); // opengl operations - bool compileShader(const std::string &, GLuint); + bool readAndCompileShader(const std::string& filename, GLuint shader); + bool compileShaderFromSource(const std::string& source, GLuint); // file io operations - bool loadFromDisk(GLuint pid, const ShaderConfig &config); - void saveToDisk(GLuint pid, const ShaderConfig &config); + bool loadFromDisk(GLuint pid, const std::string &pipelineName); + void saveToDisk(GLuint pid, const std::string &pipelineName); bool m_binary; void cacheSave(GLuint pid, BinaryCache *cache); diff --git a/include/fggl/gfx/ogl4/fallback.hpp b/include/fggl/gfx/ogl4/fallback.hpp new file mode 100644 index 0000000..b2fe7e4 --- /dev/null +++ b/include/fggl/gfx/ogl4/fallback.hpp @@ -0,0 +1,63 @@ +/* + * This file is part of FGGL. + * + * FGGL is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any + * later version. + * + * FGGL 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with FGGL. + * If not, see <https://www.gnu.org/licenses/>. + */ + +// +// Created by webpigeon on 22/06/22. +// + +#ifndef FGGL_GFX_OGL4_FALLBACK_HPP +#define FGGL_GFX_OGL4_FALLBACK_HPP + +/** + * Fallback shaders. + * + * Embedded in the library, so that the user can use the library without having to include the shaders. + */ +namespace fggl::gfx::ogl4 { + + constexpr const char* FALLBACK_CANVAS_PIPELINE = "fallback_canvas"; + + constexpr const char* FALLBACK_CANVAS_VERTEX_SHADER = R"glsl( + #version 330 core + layout (location = 0) in vec2 aPos; + layout (location = 1) in vec3 aColour; + layout (location = 2) in vec2 aTexPos; + + uniform mat4 projection; + + out vec3 colour; + out vec2 texPos; + + void main() { + gl_Position = projection * vec4(aPos, 0.0, 1.0); + colour = aColour; + texPos = aTexPos; + })glsl"; + + constexpr const char* FALLBACK_CANVAS_FRAGMENT_SHADER = R"glsl( + #version 330 core + uniform sampler2D tex; + + in vec3 colour; + in vec2 texPos; + + out vec4 fragColour; + + void main() { + fragColour = vec4(colour.xyz, texture(tex, texPos).r); + })glsl"; + +} // namespace fggl::gfx::ogl4 + +#endif //FGGL_GFX_OGL4_FALLBACK_HPP -- GitLab