#include "pch.h" #include "log.h" #include "shader.h" #include "asset.h" #include "app.h" #include "renderer_gl/opengl_capabilities.h" #include "renderer_gl/shader_bindings.h" #include namespace { [[nodiscard]] GLenum vertex_shader_stage() noexcept { return static_cast(pp::renderer::gl::vertex_shader_stage()); } [[nodiscard]] GLenum fragment_shader_stage() noexcept { return static_cast(pp::renderer::gl::fragment_shader_stage()); } [[nodiscard]] GLenum shader_compile_status_query() noexcept { return static_cast(pp::renderer::gl::shader_compile_status_query()); } [[nodiscard]] GLenum program_link_status_query() noexcept { return static_cast(pp::renderer::gl::program_link_status_query()); } [[nodiscard]] GLenum active_uniform_count_query() noexcept { return static_cast(pp::renderer::gl::active_uniform_count_query()); } void use_opengl_program(std::uint32_t program) noexcept { glUseProgram(static_cast(program)); } void delete_opengl_program(std::uint32_t program) noexcept { glDeleteProgram(static_cast(program)); } void set_opengl_uniform_4fv(std::int32_t location, std::int32_t count, const float* values) noexcept { glUniform4fv(static_cast(location), static_cast(count), values); } void set_opengl_uniform_3fv(std::int32_t location, std::int32_t count, const float* values) noexcept { glUniform3fv(static_cast(location), static_cast(count), values); } void set_opengl_uniform_2fv(std::int32_t location, std::int32_t count, const float* values) noexcept { glUniform2fv(static_cast(location), static_cast(count), values); } void set_opengl_uniform_matrix_4fv( std::int32_t location, std::int32_t count, std::uint8_t transpose, const float* values) noexcept { glUniformMatrix4fv( static_cast(location), static_cast(count), static_cast(transpose), values); } void set_opengl_uniform_1i(std::int32_t location, std::int32_t value) noexcept { glUniform1i(static_cast(location), static_cast(value)); } void set_opengl_uniform_1f(std::int32_t location, float value) noexcept { glUniform1f(static_cast(location), value); } std::int32_t get_opengl_attrib_location(std::uint32_t program, const char* name) noexcept { return static_cast(glGetAttribLocation(static_cast(program), name)); } std::uint32_t create_opengl_shader(std::uint32_t stage) noexcept { return static_cast(glCreateShader(static_cast(stage))); } void set_opengl_shader_source( std::uint32_t shader, std::int32_t count, const char* const* sources) noexcept { glShaderSource( static_cast(shader), static_cast(count), reinterpret_cast(sources), nullptr); } void compile_opengl_shader(std::uint32_t shader) noexcept { glCompileShader(static_cast(shader)); } void query_opengl_shader_integer( std::uint32_t shader, std::uint32_t query, std::int32_t* value) noexcept { glGetShaderiv( static_cast(shader), static_cast(query), reinterpret_cast(value)); } void get_opengl_shader_info_log( std::uint32_t shader, std::int32_t capacity, std::int32_t* length, char* info_log) noexcept { glGetShaderInfoLog( static_cast(shader), static_cast(capacity), reinterpret_cast(length), reinterpret_cast(info_log)); } void delete_opengl_shader(std::uint32_t shader) noexcept { glDeleteShader(static_cast(shader)); } std::uint32_t create_opengl_program() noexcept { return static_cast(glCreateProgram()); } void attach_opengl_shader(std::uint32_t program, std::uint32_t shader) noexcept { glAttachShader(static_cast(program), static_cast(shader)); } void link_opengl_program(std::uint32_t program) noexcept { glLinkProgram(static_cast(program)); } void bind_opengl_attrib_location(std::uint32_t program, std::uint32_t location, const char* name) noexcept { glBindAttribLocation(static_cast(program), static_cast(location), name); } void query_opengl_program_integer( std::uint32_t program, std::uint32_t query, std::int32_t* value) noexcept { glGetProgramiv( static_cast(program), static_cast(query), reinterpret_cast(value)); } void get_opengl_program_info_log( std::uint32_t program, std::int32_t capacity, std::int32_t* length, char* info_log) noexcept { glGetProgramInfoLog( static_cast(program), static_cast(capacity), reinterpret_cast(length), reinterpret_cast(info_log)); } void get_opengl_active_uniform( std::uint32_t program, std::uint32_t index, std::int32_t capacity, std::int32_t* length, std::int32_t* size, std::uint32_t* type, char* name) noexcept { glGetActiveUniform( static_cast(program), static_cast(index), static_cast(capacity), reinterpret_cast(length), reinterpret_cast(size), reinterpret_cast(type), reinterpret_cast(name)); } std::int32_t get_opengl_uniform_location(std::uint32_t program, const char* name) noexcept { return static_cast(glGetUniformLocation(static_cast(program), name)); } } std::map ShaderManager::m_shaders; Shader* ShaderManager::m_current; pp::renderer::RenderDeviceFeatures ShaderManager::m_render_device_features {}; bool ShaderManager::ext_framebuffer_fetch = false; bool ShaderManager::ext_float32 = false; bool ShaderManager::ext_float32_linear = false; bool ShaderManager::ext_float16 = false; bool ShaderManager::ext_map_aligned = false; std::string Shader::read(const std::string& path) { Asset a; std::string ret; if (a.open(path.c_str())) { struct stat tmp_info; std::string abs_path = Asset::absolute(path); if (stat(abs_path.c_str(), &tmp_info) == 0) m_deps[path] = tmp_info; std::regex reg_include(R"!(#include "([^"]+)")!"); std::string data((char*)a.read_all(), a.m_len); // split path std::string base; std::regex reg_path(R"((.*)[\\/]([^\\/]+)\.(\w+)$)"); std::smatch m; if (std::regex_search(path, m, reg_path)) { base = m[1].str(); } for (const auto& l : split(data, '\n')) { std::smatch include_match; if (std::regex_search(l, include_match, reg_include)) { std::string inc = base + "/" + include_match[1].str(); if (Asset::exist(inc.c_str())) { std::string subdata = read(inc); ret.append(subdata + "\n"); } } else { ret.append(l + "\n"); } } } return ret; } void Shader::parse_error(const std::string& msg, const std::string& code) { auto code_lines = split(code, '\n'); auto error_lines = split(msg, '\n'); std::smatch m; std::regex r(R"(\((\d+)\))"); for (const auto& line : error_lines) { LOG("%s", line.c_str()); if (std::regex_search(line, m, r)) { int ln = std::stoi(m[1].str()); if (ln < code_lines.size()) { int n = 2; int s = std::max(ln - n, 1); int e = std::min((int)code_lines.size() - 1, ln + n); for (int i = s; i < e; i++) { LOG("- line %02d: %s", i, code_lines[i - 1].c_str()); } } } } } bool Shader::load(const std::string& path) { std::string data = read(path); if (data.empty()) return false; std::map> sections; std::shared_ptr current_section = nullptr; std::regex reg_section(R"!(\[\[(.*)\]\])!"); for (const auto& l : split(data, '\n')) { std::smatch m; if (std::regex_search(l, m, reg_section)) { std::string section_name = m[1].str(); if (!sections[section_name]) sections[section_name] = std::make_shared(); current_section = sections[section_name]; } else { // create an un-named section if (!current_section) current_section = sections[""] = std::make_shared(); current_section->append(l + "\n"); } } if (sections.find("vertex") != sections.end() && sections.find("fragment") != sections.end()) { *sections["vertex"] = SHADER_VERSION + *sections["vertex"]; *sections["fragment"] = SHADER_VERSION + *sections["fragment"]; if (create(*sections["vertex"], *sections["fragment"])) { m_path = path; return true; } } else { LOG("could not find [[vertex]] and [[fragment]] sections on %s", path.c_str()); } return false; } bool Shader::reload() { if (m_path.empty()) return false; bool dirty = false; struct stat tmp_info; for (auto& d : m_deps) { std::string abs_path = Asset::absolute(d.first); if (stat(abs_path.c_str(), &tmp_info) != 0) continue; if (tmp_info.st_mtime > d.second.st_mtime) { d.second = tmp_info; dirty = true; } } if (dirty) { LOG("reload shader %s", m_path.c_str()); destroy(); return load(m_path); } return false; } bool Shader::create(const std::string& vertex, const std::string& fragment) { bool ret = true; App::I->render_task([this, &ret, vertex, fragment] { static char infolog[4096]; const auto vertex_shader = pp::renderer::gl::compile_opengl_shader_source( pp::renderer::gl::vertex_shader_stage(), vertex.c_str(), infolog, static_cast(sizeof(infolog)), pp::renderer::gl::OpenGlShaderCompileDispatch { .create_shader = create_opengl_shader, .shader_source = set_opengl_shader_source, .compile_shader = compile_opengl_shader, .get_shader_integer = query_opengl_shader_integer, .get_shader_info_log = get_opengl_shader_info_log, }); if (!vertex_shader.ok()) { ret = false; LOG("Shader::create() vertex compile failed because: %s", vertex_shader.status().message); return; } if (vertex_shader.value().info_log_length > 0) { LOG("\nVERTEX SHADER: %s", m_path.c_str()); parse_error(infolog, vertex); } if (vertex_shader.value().compile_status == 0) { const auto status = pp::renderer::gl::delete_opengl_shader( vertex_shader.value().shader_id, pp::renderer::gl::OpenGlShaderDeleteDispatch { .delete_shader = delete_opengl_shader, }); if (!status.ok()) LOG("Shader::create() vertex shader cleanup failed because: %s", status.message); ret = false; return; } const auto fragment_shader = pp::renderer::gl::compile_opengl_shader_source( pp::renderer::gl::fragment_shader_stage(), fragment.c_str(), infolog, static_cast(sizeof(infolog)), pp::renderer::gl::OpenGlShaderCompileDispatch { .create_shader = create_opengl_shader, .shader_source = set_opengl_shader_source, .compile_shader = compile_opengl_shader, .get_shader_integer = query_opengl_shader_integer, .get_shader_info_log = get_opengl_shader_info_log, }); if (!fragment_shader.ok()) { const auto status = pp::renderer::gl::delete_opengl_shader( vertex_shader.value().shader_id, pp::renderer::gl::OpenGlShaderDeleteDispatch { .delete_shader = delete_opengl_shader, }); if (!status.ok()) LOG("Shader::create() vertex shader cleanup failed because: %s", status.message); ret = false; LOG("Shader::create() fragment compile failed because: %s", fragment_shader.status().message); return; } if (fragment_shader.value().info_log_length > 0) { LOG("\nFRAGMENT SHADER: %s", m_path.c_str()); parse_error(infolog, fragment); } if (fragment_shader.value().compile_status == 0) { const auto vertex_cleanup = pp::renderer::gl::delete_opengl_shader( vertex_shader.value().shader_id, pp::renderer::gl::OpenGlShaderDeleteDispatch { .delete_shader = delete_opengl_shader, }); const auto fragment_cleanup = pp::renderer::gl::delete_opengl_shader( fragment_shader.value().shader_id, pp::renderer::gl::OpenGlShaderDeleteDispatch { .delete_shader = delete_opengl_shader, }); if (!vertex_cleanup.ok()) LOG("Shader::create() vertex shader cleanup failed because: %s", vertex_cleanup.message); if (!fragment_cleanup.ok()) LOG("Shader::create() fragment shader cleanup failed because: %s", fragment_cleanup.message); ret = false; return; } const auto program = pp::renderer::gl::link_opengl_shader_program( vertex_shader.value().shader_id, fragment_shader.value().shader_id, pp::renderer::gl::panopainter_shader_attribute_bindings(), infolog, static_cast(sizeof(infolog)), pp::renderer::gl::OpenGlProgramLinkDispatch { .create_program = create_opengl_program, .attach_shader = attach_opengl_shader, .delete_shader = delete_opengl_shader, .link_program = link_opengl_program, .get_attrib_location = get_opengl_attrib_location, .bind_attrib_location = bind_opengl_attrib_location, .get_program_integer = query_opengl_program_integer, .get_program_info_log = get_opengl_program_info_log, }); if (!program.ok()) { const auto vertex_cleanup = pp::renderer::gl::delete_opengl_shader( vertex_shader.value().shader_id, pp::renderer::gl::OpenGlShaderDeleteDispatch { .delete_shader = delete_opengl_shader, }); const auto fragment_cleanup = pp::renderer::gl::delete_opengl_shader( fragment_shader.value().shader_id, pp::renderer::gl::OpenGlShaderDeleteDispatch { .delete_shader = delete_opengl_shader, }); if (!vertex_cleanup.ok()) LOG("Shader::create() vertex shader cleanup failed because: %s", vertex_cleanup.message); if (!fragment_cleanup.ok()) LOG("Shader::create() fragment shader cleanup failed because: %s", fragment_cleanup.message); ret = false; LOG("Shader::create() program link failed because: %s", program.status().message); return; } if (program.value().info_log_length > 0) LOG("LINK SHADER: %s\n%s", m_path.c_str(), infolog); if (program.value().link_status == 0) { const auto status = pp::renderer::gl::delete_opengl_program( program.value().program_id, pp::renderer::gl::OpenGlProgramDeleteDispatch { .use_program = use_opengl_program, .delete_program = delete_opengl_program, }); if (!status.ok()) LOG("Shader::create() program cleanup failed because: %s", status.message); ret = false; return; } const auto cleanup_program = [](std::uint32_t program_id) noexcept { const auto status = pp::renderer::gl::delete_opengl_program( program_id, pp::renderer::gl::OpenGlProgramDeleteDispatch { .use_program = use_opengl_program, .delete_program = delete_opengl_program, }); if (!status.ok()) LOG("Shader::create() program cleanup failed because: %s", status.message); }; // Parse shader uniforms { const auto uniform_count = pp::renderer::gl::query_opengl_program_integer( program.value().program_id, pp::renderer::gl::active_uniform_count_query(), pp::renderer::gl::OpenGlProgramIntegerDispatch { .get_program_integer = query_opengl_program_integer, }); if (!uniform_count.ok()) { LOG("Shader::create() uniform discovery failed because: %s", uniform_count.status().message); cleanup_program(program.value().program_id); ret = false; return; } constexpr std::int32_t bufSize = 64; // maximum name length char uniform_name[bufSize]; // variable name in GLSL const auto count = uniform_count.value(); for (int i = 0; i < count; i++) { const auto uniform = pp::renderer::gl::get_opengl_active_uniform( program.value().program_id, static_cast(i), uniform_name, bufSize, pp::renderer::gl::OpenGlActiveUniformDispatch { .get_active_uniform = get_opengl_active_uniform, }); if (!uniform.ok()) { LOG("Shader::create() active uniform discovery failed because: %s", uniform.status().message); cleanup_program(program.value().program_id); ret = false; return; } kShaderUniform id = static_cast(pp::renderer::gl::shader_uniform_id(uniform_name)); if (m_umap.find(id) != m_umap.end()) LOG("UNIFORM ALREADY DEFINED: %s", uniform_name); const auto location = pp::renderer::gl::get_opengl_uniform_location( program.value().program_id, uniform_name, pp::renderer::gl::OpenGlUniformLocationDispatch { .get_uniform_location = get_opengl_uniform_location, }); if (!location.ok()) { LOG("Shader::create() uniform location failed because: %s", location.status().message); cleanup_program(program.value().program_id); ret = false; return; } m_umap[id] = location.value(); } } prog = program.value().program_id; }); return ret; } void Shader::destroy() { if (prog) { App::I->render_task_async([prog=prog] { const auto status = pp::renderer::gl::delete_opengl_program( static_cast(prog), pp::renderer::gl::OpenGlProgramDeleteDispatch { .use_program = use_opengl_program, .delete_program = delete_opengl_program, }); if (!status.ok()) LOG("Shader::destroy() failed because: %s", status.message); }); prog = 0; } m_umap.clear(); } void Shader::use() { const auto status = pp::renderer::gl::use_opengl_program( static_cast(prog), pp::renderer::gl::OpenGlProgramUseDispatch { .use_program = use_opengl_program, }); if (!status.ok()) LOG("Shader::use() failed because: %s", status.message); } void Shader::u_vec4(kShaderUniform id, const glm::vec4& v) { if (m_umap.count(id) == 0) LOG("UNIFORM vec4 %d NOT FOUND in shader %d", (int)id, (int)name) else { const auto status = pp::renderer::gl::set_opengl_uniform_vec4( static_cast(m_umap[id]), glm::value_ptr(v), pp::renderer::gl::OpenGlUniformVec4Dispatch { .uniform_4fv = set_opengl_uniform_4fv, }); if (!status.ok()) LOG("Shader::u_vec4() failed because: %s", status.message); } } void Shader::u_vec3(kShaderUniform id, const glm::vec3& v) { if (m_umap.count(id) == 0) LOG("UNIFORM vec3 %d NOT FOUND in shader %d", (int)id, (int)name) else { const auto status = pp::renderer::gl::set_opengl_uniform_vec3( static_cast(m_umap[id]), glm::value_ptr(v), pp::renderer::gl::OpenGlUniformVec3Dispatch { .uniform_3fv = set_opengl_uniform_3fv, }); if (!status.ok()) LOG("Shader::u_vec3() failed because: %s", status.message); } } void Shader::u_vec2(kShaderUniform id, const glm::vec2& v) { if (m_umap.count(id) == 0) LOG("UNIFORM vec2 %d NOT FOUND in shader %d", (int)id, (int)name) else { const auto status = pp::renderer::gl::set_opengl_uniform_vec2( static_cast(m_umap[id]), glm::value_ptr(v), pp::renderer::gl::OpenGlUniformVec2Dispatch { .uniform_2fv = set_opengl_uniform_2fv, }); if (!status.ok()) LOG("Shader::u_vec2() failed because: %s", status.message); } } void Shader::u_mat4(kShaderUniform id, const glm::mat4& m) { if (m_umap.count(id) == 0) LOG("UNIFORM mat4 %d NOT FOUND in shader %d", (int)id, (int)name) else { const auto status = pp::renderer::gl::set_opengl_uniform_mat4( static_cast(m_umap[id]), glm::value_ptr(m), pp::renderer::gl::OpenGlUniformMat4Dispatch { .uniform_matrix_4fv = set_opengl_uniform_matrix_4fv, }); if (!status.ok()) LOG("Shader::u_mat4() failed because: %s", status.message); } } void Shader::u_int(kShaderUniform id, int i) { if (m_umap.count(id) == 0) LOG("UNIFORM int %d NOT FOUND in shader %d", (int)id, (int)name) else { const auto status = pp::renderer::gl::set_opengl_uniform_int( static_cast(m_umap[id]), static_cast(i), pp::renderer::gl::OpenGlUniformIntDispatch { .uniform_1i = set_opengl_uniform_1i, }); if (!status.ok()) LOG("Shader::u_int() failed because: %s", status.message); } } void Shader::u_int(const char* uniform_name, int i) { const auto location = pp::renderer::gl::get_opengl_attribute_location( static_cast(prog), uniform_name, pp::renderer::gl::OpenGlAttributeLocationDispatch { .get_attrib_location = get_opengl_attrib_location, }); if (!location.ok()) { LOG("Shader::u_int(name) lookup failed because: %s", location.status().message); return; } const auto status = pp::renderer::gl::set_opengl_uniform_int( location.value(), static_cast(i), pp::renderer::gl::OpenGlUniformIntDispatch { .uniform_1i = set_opengl_uniform_1i, }); if (!status.ok()) LOG("Shader::u_int(name) failed because: %s", status.message); } void Shader::u_float(kShaderUniform id, float f) { if (m_umap.count(id) == 0) LOG("UNIFORM float %d NOT FOUND in shader %d", (int)id, (int)name) else { const auto status = pp::renderer::gl::set_opengl_uniform_float( static_cast(m_umap[id]), f, pp::renderer::gl::OpenGlUniformFloatDispatch { .uniform_1f = set_opengl_uniform_1f, }); if (!status.ok()) LOG("Shader::u_float() failed because: %s", status.message); } } GLint Shader::GetAttribLocation(const char* attribute_name) { const auto location = pp::renderer::gl::get_opengl_attribute_location( static_cast(prog), attribute_name, pp::renderer::gl::OpenGlAttributeLocationDispatch { .get_attrib_location = get_opengl_attrib_location, }); if (!location.ok()) { LOG("Shader::GetAttribLocation() failed because: %s", location.status().message); return -1; } return static_cast(location.value()); } bool ShaderManager::load(kShader id, const std::string& path) { m_shaders[id].name = id; return m_shaders[id].load(path); } bool ShaderManager::reload() { bool success = false; for (auto& s : m_shaders) success |= s.second.reload(); return success; } bool ShaderManager::create(kShader id, const std::string& vertex, const std::string& fragment) { m_shaders[id].name = id; return m_shaders[id].create(vertex, fragment); } void ShaderManager::use(kShader id) { m_current = &m_shaders[id]; m_current->use(); } void ShaderManager::use(const char* name) { m_current = &m_shaders[(kShader)const_hash(name)]; m_current->use(); } void ShaderManager::u_vec4(kShaderUniform id, const glm::vec4& v) { m_current->u_vec4(id, v); } void ShaderManager::u_vec3(kShaderUniform id, const glm::vec3& v) { m_current->u_vec3(id, v); } void ShaderManager::u_vec2(kShaderUniform id, const glm::vec2& v) { m_current->u_vec2(id, v); } void ShaderManager::u_mat4(kShaderUniform id, const glm::mat4& m) { m_current->u_mat4(id, m); } void ShaderManager::u_int(kShaderUniform id, int i) { m_current->u_int(id, i); } void ShaderManager::u_int(const char* name, int i) { m_current->u_int(name, i); } Shader* ShaderManager::get(kShader id) { auto it = m_shaders.find(id); return (it == m_shaders.end()) ? nullptr : &it->second; } void ShaderManager::u_float(kShaderUniform id, float f) { m_current->u_float(id, f); } void ShaderManager::set_render_device_features(pp::renderer::RenderDeviceFeatures features) noexcept { m_render_device_features = features; } pp::renderer::RenderDeviceFeatures ShaderManager::render_device_features() noexcept { return m_render_device_features; } void ShaderManager::invalidate() { m_shaders.clear(); } bool check_uniform_uniqueness() { return pp::renderer::gl::validate_shader_uniform_names( pp::renderer::gl::panopainter_shader_uniform_names()) .ok(); }