aboutsummaryrefslogtreecommitdiff
path: root/src/rendering/shader.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/rendering/shader.cpp')
-rw-r--r--src/rendering/shader.cpp324
1 files changed, 297 insertions, 27 deletions
diff --git a/src/rendering/shader.cpp b/src/rendering/shader.cpp
index 167b855..80015e6 100644
--- a/src/rendering/shader.cpp
+++ b/src/rendering/shader.cpp
@@ -1,48 +1,318 @@
#include "shader.h"
-#include "renderer.h"
-#include "platform/opengl/opengl_shader.h"
-#include "platform/vulkan/vulkan_shader.h"
+#include <glad/glad.h>
+#include <glm/gtc/type_ptr.hpp>
+
+#include <fstream>
+#include <iostream>
namespace Donut
{
- auto Shader::create(const std::string& filepath) -> Shader*
+ static uint32_t ShaderTypeFromString(const std::string& type)
+ {
+ if (type == "vertex")
+ return GL_VERTEX_SHADER;
+ if (type == "fragment" || type == "pixel")
+ return GL_FRAGMENT_SHADER;
+ if (type == "compute")
+ return GL_COMPUTE_SHADER;
+ return 0;
+ }
+
+ // Shaders are authored in Slang and compiled to assets/shaders/generated/
+ // <name>.glsl by tools/compile-shaders.sh. Given a legacy ".../<name>.glsl"
+ // path, prefer that generated file when present; otherwise fall back to the
+ // hand-written GLSL (e.g. shaders not yet ported to Slang).
+ static std::string ResolveShaderPath(const std::string& filepath)
+ {
+ size_t slash = filepath.find_last_of("/\\");
+ std::string dir = (slash == std::string::npos) ? std::string() : filepath.substr(0, slash + 1);
+ std::string file = (slash == std::string::npos) ? filepath : filepath.substr(slash + 1);
+ size_t dot = file.rfind('.');
+ std::string base = (dot == std::string::npos) ? file : file.substr(0, dot);
+
+ std::string generated = dir + "generated/" + base + ".glsl";
+ std::ifstream test(generated);
+ if (test.good())
+ return generated;
+ return filepath;
+ }
+
+ Shader::Shader(const std::string& filepath)
+ {
+ std::string resolved = ResolveShaderPath(filepath);
+ m_is_slang = (resolved != filepath);
+ std::string source = read_file(resolved);
+ auto shader_sources = pre_process(source);
+ compile(shader_sources);
+
+ auto last_slash = filepath.find_last_of("/\\");
+ last_slash = last_slash == std::string::npos ? 0 : last_slash + 1;
+ auto last_dot = filepath.rfind('.');
+ auto count = last_dot == std::string::npos ? filepath.size() - last_slash : last_dot - last_slash;
+ m_name = filepath.substr(last_slash, count);
+ }
+
+ Shader::Shader(const std::string& name, const std::string& vertex_src, const std::string& fragment_src)
+ : m_name(name)
+ {
+ std::unordered_map<uint32_t, std::string> sources;
+ sources[GL_VERTEX_SHADER] = vertex_src;
+ sources[GL_FRAGMENT_SHADER] = fragment_src;
+ compile(sources);
+ }
+
+ Shader::Shader(const std::string& name, const std::string& compute_src)
+ : m_name(name)
+ {
+ std::unordered_map<uint32_t, std::string> sources;
+ sources[GL_COMPUTE_SHADER] = compute_src;
+ compile(sources);
+ }
+
+ Shader::~Shader()
+ {
+ glDeleteProgram(m_renderer_id);
+ }
+
+ auto Shader::read_file(const std::string& filepath) -> std::string
{
- switch (Renderer::get_api())
+ std::string result;
+ std::ifstream in(filepath, std::ios::in |
+ std::ios::binary);
+
+ if (in)
{
- case RendererAPI::API::OpenGL:
- return new OpenGLShader(filepath);
- case RendererAPI::API::Vulkan:
- return new VulkanShader(filepath);
- default:
- return nullptr;
+ in.seekg(0, std::ios::end);
+ size_t size = in.tellg();
+ if (size != -1)
+ {
+ result.resize(size);
+ in.seekg(0, std::ios::beg);
+ in.read(&result[0], size);
+ }
}
+ return result;
}
- auto Shader::create(const std::string& name, const std::string& vertex_src, const std::string& fragment_src) -> Shader*
+ auto Shader::pre_process(const std::string& source) -> std::unordered_map<uint32_t, std::string>
{
- switch (Renderer::get_api())
+ std::unordered_map<uint32_t, std::string> shader_sources;
+
+ const char* type_token = "#type";
+ size_t type_token_length = strlen(type_token);
+ size_t pos = source.find(type_token, 0);
+
+ while (pos != std::string::npos)
{
- case RendererAPI::API::OpenGL:
- return new OpenGLShader(name, vertex_src, fragment_src);
- case RendererAPI::API::Vulkan:
- return new VulkanShader(name, vertex_src, fragment_src);
- default:
- return nullptr;
+ size_t eol = source.find_first_of("\r\n", pos);
+ size_t begin = pos + type_token_length + 1;
+ std::string type = source.substr(begin, eol - begin);
+
+ size_t next_line_pos = source.find_first_not_of("\r\n", eol);
+ pos = source.find(type_token, next_line_pos);
+ shader_sources[ShaderTypeFromString(type)] = (pos == std::string::npos) ? source.substr(next_line_pos) :
+ source.substr(next_line_pos, pos - next_line_pos);
}
+
+ return shader_sources;
}
- auto Shader::create_compute(const std::string& name, const std::string& compute_src) -> Shader*
+ auto Shader::compile(const std::unordered_map<uint32_t, std::string>& shader_sources) -> void
{
- switch (Renderer::get_api())
+ uint32_t program = glCreateProgram();
+ std::vector<uint32_t> glShaderIDs(shader_sources.size());
+ for (auto& kv : shader_sources)
+ {
+ uint32_t type = kv.first;
+ const std::string& source = kv.second;
+
+ uint32_t shader = glCreateShader(type);
+ const char* source_c_str = source.c_str();
+ glShaderSource(shader, 1, &source_c_str, 0);
+ glCompileShader(shader);
+
+ int is_compiled = 0;
+ glGetShaderiv(shader, GL_COMPILE_STATUS, &is_compiled);
+ if (is_compiled == GL_FALSE)
+ {
+ int max_length = 0;
+ glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &max_length);
+ std::vector<char> info_log(max_length);
+ glGetShaderInfoLog(shader, max_length, &max_length, &info_log[0]);
+ glDeleteShader(shader);
+ for (auto id : glShaderIDs)
+ glDeleteShader(id);
+ glDeleteProgram(program);
+ m_renderer_id = 0;
+ // info_log.data() is null when the driver returns an empty log
+ // (e.g. macOS rejecting a compute shader); streaming a null
+ // char* into std::cout calls strlen(NULL) and crashes.
+ const char* log = info_log.empty() ? "" : info_log.data();
+ std::cout << "Shader compilation failure!" << std::endl << log << std::endl;
+ return;
+ }
+ glAttachShader(program, shader);
+ glShaderIDs.push_back(shader);
+ }
+
+ m_renderer_id = program;
+ glLinkProgram(m_renderer_id);
+
+ int is_linked = 0;
+ glGetProgramiv(m_renderer_id, GL_LINK_STATUS, (int*)&is_linked);
+ if (is_linked == GL_FALSE)
{
- case RendererAPI::API::OpenGL:
- return new OpenGLShader(name, compute_src);
- case RendererAPI::API::Vulkan:
- return new VulkanShader(name, compute_src);
- default:
- return nullptr;
+ int max_length = 0;
+ glGetProgramiv(m_renderer_id, GL_INFO_LOG_LENGTH, &max_length);
+ std::vector<char> info_log(max_length);
+ glGetProgramInfoLog(m_renderer_id, max_length, &max_length, &info_log[0]);
+ glDeleteProgram(m_renderer_id);
+ for (auto id : glShaderIDs)
+ glDeleteShader(id);
+ m_renderer_id = 0;
+ const char* log = info_log.empty() ? "" : info_log.data();
+ std::cout << "Shader link failure!" << std::endl << log << std::endl;
+ return;
}
+
+ for (auto id : glShaderIDs)
+ {
+ glDetachShader(m_renderer_id, id);
+ glDeleteShader(id);
+ }
+ }
+
+ auto Shader::bind() const -> void
+ {
+ glUseProgram(m_renderer_id);
+ }
+
+ auto Shader::unbind() const -> void
+ {
+ glUseProgram(0);
+ }
+
+ auto Shader::set_int(const std::string& name, int value) -> void
+ {
+ upload_uniform_int(name, value);
+ }
+
+ auto Shader::set_int_array(const std::string& name, int* values, uint32_t count) -> void
+ {
+ upload_uniform_int_array(name, values, count);
+ }
+
+ auto Shader::set_float(const std::string& name, float value) -> void
+ {
+ upload_uniform_float(name, value);
+ }
+
+ auto Shader::set_float2(const std::string& name, const glm::vec2& value) -> void
+ {
+ upload_uniform_float2(name, value);
+ }
+
+ auto Shader::set_float3(const std::string& name, const glm::vec3& value) -> void
+ {
+ upload_uniform_float3(name, value);
+ }
+
+ auto Shader::set_float4(const std::string& name, const glm::vec4& value) -> void
+ {
+ upload_uniform_float4(name, value);
+ }
+
+ auto Shader::set_mat4(const std::string& name, const glm::mat4& value) -> void
+ {
+ upload_uniform_mat4(name, value);
+ }
+
+ auto Shader::upload_uniform_int(const std::string& name, int value) -> void
+ {
+ int location = glGetUniformLocation(m_renderer_id, name.c_str());
+ glUniform1i(location, value);
+ }
+
+ auto Shader::upload_uniform_int_array(const std::string& name, int* values, uint32_t count) -> void
+ {
+ int location = glGetUniformLocation(m_renderer_id, name.c_str());
+ glUniform1iv(location, count, values);
+ }
+
+ auto Shader::upload_uniform_float(const std::string& name, float value) -> void
+ {
+ int location = glGetUniformLocation(m_renderer_id, name.c_str());
+ glUniform1f(location, value);
+ }
+
+ auto Shader::upload_uniform_float2(const std::string& name, const glm::vec2& value) -> void
+ {
+ int location = glGetUniformLocation(m_renderer_id, name.c_str());
+ glUniform2f(location, value.x, value.y);
+ }
+
+ auto Shader::upload_uniform_float3(const std::string& name, const glm::vec3& value) -> void
+ {
+ int location = glGetUniformLocation(m_renderer_id, name.c_str());
+ glUniform3f(location, value.x, value.y, value.z);
+ }
+
+ auto Shader::upload_uniform_float4(const std::string& name, const glm::vec4& value) -> void
+ {
+ int location = glGetUniformLocation(m_renderer_id, name.c_str());
+ glUniform4f(location, value.x, value.y, value.z, value.w);
+ }
+
+ auto Shader::upload_uniform_mat3(const std::string& name, const glm::mat3& matrix) -> void
+ {
+ int location = glGetUniformLocation(m_renderer_id, name.c_str());
+ glUniformMatrix3fv(location, 1, m_is_slang ? GL_TRUE : GL_FALSE, glm::value_ptr(matrix));
+ }
+
+ auto Shader::upload_uniform_mat4(const std::string& name, const glm::mat4& matrix) -> void
+ {
+ int location = glGetUniformLocation(m_renderer_id, name.c_str());
+ glUniformMatrix4fv(location, 1, m_is_slang ? GL_TRUE : GL_FALSE, glm::value_ptr(matrix));
+ }
+
+ auto Shader::dispatch(uint32_t x, uint32_t y, uint32_t z) -> void
+ {
+ // Compute shaders require OpenGL 4.3+. On drivers that cap out earlier
+ // (e.g. macOS, which is frozen at 4.1) glDispatchCompute is never
+ // loaded and the pointer is null. Guard so we no-op instead of crash.
+ if (m_renderer_id == 0 || glDispatchCompute == nullptr)
+ return;
+ glDispatchCompute(x, y, z);
+ }
+
+ auto Shader::dispatch_indirect(uint32_t offset) -> void
+ {
+ if (m_renderer_id == 0 || glDispatchComputeIndirect == nullptr)
+ return;
+ glDispatchComputeIndirect(offset);
+ }
+
+ auto Shader::memory_barrier(uint32_t barriers) -> void
+ {
+ if (glMemoryBarrier == nullptr)
+ return;
+ glMemoryBarrier(barriers);
+ }
+
+ auto Shader::create(const std::string& filepath) -> Shader*
+ {
+ return new Shader(filepath);
+ }
+
+ auto Shader::create(const std::string& name, const std::string& vertex_src, const std::string& fragment_src) -> Shader*
+ {
+ return new Shader(name, vertex_src, fragment_src);
+ }
+
+ auto Shader::create_compute(const std::string& name, const std::string& compute_src) -> Shader*
+ {
+ return new Shader(name, compute_src);
}
auto ShaderLibrary::add(const Ref<Shader>& shader) -> void