Files
OpenEngine/open_engine/src/open_engine/opengl/opengl_shader.cpp

468 lines
16 KiB
C++

#include <pch.hpp>
#include <core.hpp>
#include <opengl/opengl_shader.hpp>
#include <instrumentor.hpp>
#include <core/time.hpp>
#include <logging.hpp>
#include <shaderc/shaderc.hpp>
#include <spirv_cross/spirv_cross.hpp>
#include <spirv_cross/spirv_glsl.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <glm/glm.hpp>
#include <filesystem>
#include <glad/glad.h>
//#include <alloca.h>
namespace OpenEngine {
namespace Utils {
static GLenum ShaderTypeFromString(const std::string& type)
{
if (type == "vertex")
return GL_VERTEX_SHADER;
if (type == "fragment" || type == "pixel")
return GL_FRAGMENT_SHADER;
OE_CORE_ASSERT(false, "Unknown shader type!");
return 0;
}
static shaderc_shader_kind GLShaderStageToShaderC(GLenum stage)
{
switch (stage)
{
case GL_VERTEX_SHADER: return shaderc_glsl_vertex_shader;
case GL_FRAGMENT_SHADER: return shaderc_glsl_fragment_shader;
}
OE_CORE_ASSERT(false, "No");
return (shaderc_shader_kind)0;
}
static const char* GLShaderStageToString(GLenum stage)
{
switch (stage)
{
case GL_VERTEX_SHADER: return "GL_VERTEX_SHADER";
case GL_FRAGMENT_SHADER: return "GL_FRAGMENT_SHADER";
}
OE_CORE_ASSERT(false, "No");
return nullptr;
}
static const char* GetCacheDirectory()
{
// TODO: make sure the assets directory is valid
return "assets/cache/shader/opengl";
}
static void CreateCacheDirectoryIfNeeded()
{
std::string cache_directory = GetCacheDirectory();
if (!std::filesystem::exists(cache_directory))
std::filesystem::create_directories(cache_directory);
}
static const char* GLShaderStageCachedOpenGLFileExtension(uint32_t stage)
{
switch (stage)
{
case GL_VERTEX_SHADER: return ".cached_opengl.vert";
case GL_FRAGMENT_SHADER: return ".cached_opengl.frag";
}
OE_CORE_ASSERT(false, "No");
return "";
}
static const char* GLShaderStageCachedVulkanFileExtension(uint32_t stage)
{
switch (stage)
{
case GL_VERTEX_SHADER: return ".cached_vulkan.vert";
case GL_FRAGMENT_SHADER: return ".cached_vulkan.frag";
}
OE_CORE_ASSERT(false, "No");
return "";
}
}
OpenGLShader::OpenGLShader(const std::string& filepath)
: file_path(filepath)
{
OE_PROFILE_FUNCTION();
Utils::CreateCacheDirectoryIfNeeded();
std::string source = ReadFile(filepath);
auto shader_sources = PreProcess(source);
{
CompileOrGetVulkanBinaries(shader_sources);
CompileOrGetOpenGLBinaries();
CreateProgram();
}
// Extract name from filepath
auto last_slash = filepath.find_last_of("/\\");
last_slash = last_slash == std::string::npos ? 0 : last_slash + 1;
auto lastDot = filepath.rfind('.');
auto count = lastDot == std::string::npos ? filepath.size() - last_slash : lastDot - last_slash;
name = filepath.substr(last_slash, count);
}
OpenGLShader::OpenGLShader(const std::string& name, const std::string& vertex_src, const std::string& fragment_src)
: name(name)
{
OE_PROFILE_FUNCTION();
std::unordered_map<GLenum, std::string> sources;
sources[GL_VERTEX_SHADER] = vertex_src;
sources[GL_FRAGMENT_SHADER] = fragment_src;
CompileOrGetVulkanBinaries(sources);
CompileOrGetOpenGLBinaries();
CreateProgram();
}
OpenGLShader::~OpenGLShader()
{
OE_PROFILE_FUNCTION();
glDeleteProgram(id);
}
std::string OpenGLShader::ReadFile(const std::string& filepath)
{
OE_PROFILE_FUNCTION();
std::string result;
std::ifstream in(filepath, std::ios::in | std::ios::binary); // ifstream closes itself due to RAII
if (in)
{
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);
}
else
{
OE_CORE_ERROR("Could not read from file '{0}'", filepath);
}
}
else
{
OE_CORE_ERROR("Could not open file '{0}'", filepath);
}
return result;
}
std::unordered_map<GLenum, std::string> OpenGLShader::PreProcess(const std::string& source)
{
OE_PROFILE_FUNCTION();
std::unordered_map<GLenum, 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); //Start of shader type declaration line
while (pos != std::string::npos)
{
size_t eol = source.find_first_of("\r\n", pos); //End of shader type declaration line
OE_CORE_ASSERT(eol != std::string::npos, "Syntax error");
size_t begin = pos + type_token_length + 1; //Start of shader type name (after "#type " keyword)
std::string type = source.substr(begin, eol - begin);
OE_CORE_ASSERT(Utils::ShaderTypeFromString(type), "Invalid shader type specified");
size_t next_line_pos = source.find_first_not_of("\r\n", eol); //Start of shader code after shader type declaration line
OE_CORE_ASSERT(next_line_pos != std::string::npos, "Syntax error");
pos = source.find(type_token, next_line_pos); //Start of next shader type declaration line
shader_sources[Utils::ShaderTypeFromString(type)] = (pos == std::string::npos) ? source.substr(next_line_pos) : source.substr(next_line_pos, pos - next_line_pos);
}
return shader_sources;
}
void OpenGLShader::CompileOrGetVulkanBinaries(const std::unordered_map<GLenum, std::string>& shaderSources)
{
GLuint program = glCreateProgram();
shaderc::Compiler compiler;
shaderc::CompileOptions options;
options.SetTargetEnvironment(shaderc_target_env_vulkan, shaderc_env_version_vulkan_1_2);
const bool optimize = true;
if (optimize)
options.SetOptimizationLevel(shaderc_optimization_level_performance);
std::filesystem::path cache_directory = Utils::GetCacheDirectory();
auto& shader_data = vulkan_spirv;
shader_data.clear();
for (auto&& [stage, source] : shaderSources)
{
std::filesystem::path shader_file_path = file_path;
std::filesystem::path cached_path = cache_directory / (shader_file_path.filename().string() + Utils::GLShaderStageCachedVulkanFileExtension(stage));
std::ifstream in(cached_path, std::ios::in | std::ios::binary);
if (in.is_open())
{
in.seekg(0, std::ios::end);
auto size = in.tellg();
in.seekg(0, std::ios::beg);
auto& data = shader_data[stage];
data.resize(size / sizeof(uint32_t));
in.read((char*)data.data(), size);
}
else
{
shaderc::SpvCompilationResult module = compiler.CompileGlslToSpv(source, Utils::GLShaderStageToShaderC(stage), file_path.c_str(), options);
if (module.GetCompilationStatus() != shaderc_compilation_status_success)
{
OE_CORE_ERROR(module.GetErrorMessage());
OE_CORE_ASSERT(false, "No");
}
shader_data[stage] = std::vector<uint32_t>(module.cbegin(), module.cend());
std::ofstream out(cached_path, std::ios::out | std::ios::binary);
if (out.is_open())
{
auto& data = shader_data[stage];
out.write((char*)data.data(), data.size() * sizeof(uint32_t));
out.flush();
out.close();
}
}
}
for (auto&& [stage, data]: shader_data)
Reflect(stage, data);
}
void OpenGLShader::CompileOrGetOpenGLBinaries()
{
auto& shader_data = opengl_spirv;
shaderc::Compiler compiler;
shaderc::CompileOptions options;
options.SetTargetEnvironment(shaderc_target_env_opengl, shaderc_env_version_opengl_4_5);
const bool optimize = false;
if (optimize)
options.SetOptimizationLevel(shaderc_optimization_level_performance);
std::filesystem::path cache_directory = Utils::GetCacheDirectory();
shader_data.clear();
m_OpenGLSourceCode.clear();
for (auto&& [stage, spirv] : vulkan_spirv)
{
std::filesystem::path shader_file_path = file_path;
std::filesystem::path cached_path = cache_directory / (shader_file_path.filename().string() + Utils::GLShaderStageCachedOpenGLFileExtension(stage));
std::ifstream in(cached_path, std::ios::in | std::ios::binary);
if (in.is_open())
{
in.seekg(0, std::ios::end);
auto size = in.tellg();
in.seekg(0, std::ios::beg);
auto& data = shader_data[stage];
data.resize(size / sizeof(uint32_t));
in.read((char*)data.data(), size);
}
else
{
spirv_cross::CompilerGLSL glsl_compiler(spirv);
m_OpenGLSourceCode[stage] = glsl_compiler.compile();
auto& source = m_OpenGLSourceCode[stage];
shaderc::SpvCompilationResult module = compiler.CompileGlslToSpv(source, Utils::GLShaderStageToShaderC(stage), file_path.c_str());
if (module.GetCompilationStatus() != shaderc_compilation_status_success)
{
OE_CORE_ERROR(module.GetErrorMessage());
OE_CORE_ASSERT(false, "No");
}
shader_data[stage] = std::vector<uint32_t>(module.cbegin(), module.cend());
std::ofstream out(cached_path, std::ios::out | std::ios::binary);
if (out.is_open())
{
auto& data = shader_data[stage];
out.write((char*)data.data(), data.size() * sizeof(uint32_t));
out.flush();
out.close();
}
}
}
}
void OpenGLShader::CreateProgram()
{
GLuint program = glCreateProgram();
std::vector<GLuint> shader_ids;
for (auto&& [stage, spirv] : opengl_spirv)
{
GLuint shaderID = shader_ids.emplace_back(glCreateShader(stage));
glShaderBinary(1, &shaderID, GL_SHADER_BINARY_FORMAT_SPIR_V, spirv.data(), spirv.size() * sizeof(uint32_t));
glSpecializeShader(shaderID, "main", 0, nullptr, nullptr);
glAttachShader(program, shaderID);
}
glLinkProgram(program);
GLint is_linked;
glGetProgramiv(program, GL_LINK_STATUS, &is_linked);
if (is_linked == GL_FALSE)
{
GLint max_length;
glGetProgramiv(program, GL_INFO_LOG_LENGTH, &max_length);
std::vector<GLchar> info_log(max_length);
glGetProgramInfoLog(program, max_length, &max_length, info_log.data());
OE_CORE_ERROR("Shader linking failed ({0}):\n{1}", file_path, info_log.data());
glDeleteProgram(program);
for (auto id : shader_ids)
glDeleteShader(id);
}
for (auto id : shader_ids)
{
glDetachShader(program, id);
glDeleteShader(id);
}
id = program;
}
void OpenGLShader::Reflect(GLenum stage, const std::vector<uint32_t>& shader_data)
{
spirv_cross::Compiler compiler(shader_data);
spirv_cross::ShaderResources resources = compiler.get_shader_resources();
OE_CORE_TRACE("OpenGLShader::Reflect - {0} {1}", Utils::GLShaderStageToString(stage), file_path);
OE_CORE_TRACE(" {0} uniform buffers", resources.uniform_buffers.size());
OE_CORE_TRACE(" {0} resources", resources.sampled_images.size());
OE_CORE_TRACE("Uniform buffers:");
for (const auto& resource : resources.uniform_buffers)
{
const auto& buffer_type = compiler.get_type(resource.base_type_id);
uint32_t buffer_size = compiler.get_declared_struct_size(buffer_type);
uint32_t binding = compiler.get_decoration(resource.id, spv::DecorationBinding);
int member_count = buffer_type.member_types.size();
OE_CORE_TRACE(" {0}", resource.name);
OE_CORE_TRACE(" Size = {0}", buffer_size);
OE_CORE_TRACE(" Binding = {0}", binding);
OE_CORE_TRACE(" Members = {0}", member_count);
}
}
void OpenGLShader::Bind() const
{
OE_PROFILE_FUNCTION();
glUseProgram(id);
}
void OpenGLShader::Unbind() const
{
OE_PROFILE_FUNCTION();
glUseProgram(0);
}
void OpenGLShader::SetBool(const std::string &name, bool value) const
{
OE_PROFILE_FUNCTION();
UploadBool(name, value);
}
void OpenGLShader::SetInt(const std::string &name, int value) const
{
OE_PROFILE_FUNCTION();
UploadInt(name, value);
}
void OpenGLShader::SetIntArray(const std::string &name, int* values, uint32_t count) const
{
OE_PROFILE_FUNCTION();
UploadIntArray(name, values, count);
}
void OpenGLShader::SetFloat(const std::string &name, float value) const
{
OE_PROFILE_FUNCTION();
UploadFloat(name, value);
}
void OpenGLShader::SetMat4(const std::string &name, const glm::mat4& value) const
{
OE_PROFILE_FUNCTION();
UploadMat4(name, value);
}
void OpenGLShader::SetVec2(const std::string &name, const glm::vec2& value) const
{
OE_PROFILE_FUNCTION();
UploadVec2(name, value);
}
void OpenGLShader::SetVec3(const std::string &name, const glm::vec3& value) const
{
OE_PROFILE_FUNCTION();
UploadVec3(name, value);
}
void OpenGLShader::SetVec4(const std::string &name, const glm::vec4& value) const
{
OE_PROFILE_FUNCTION();
UploadVec4(name, value);
}
void OpenGLShader::UploadBool(const std::string &name, bool value) const
{
glUniform1i(glGetUniformLocation(id, name.c_str()), (int)value);
}
void OpenGLShader::UploadInt(const std::string &name, int value) const
{
glUniform1i(glGetUniformLocation(id, name.c_str()), value);
}
void OpenGLShader::UploadIntArray(const std::string &name, int* values, uint32_t count) const
{
glUniform1iv(glGetUniformLocation(id, name.c_str()), count, values);
}
void OpenGLShader::UploadFloat(const std::string &name, float value) const
{
glUniform1f(glGetUniformLocation(id, name.c_str()), value);
}
void OpenGLShader::UploadMat4(const std::string &name, const glm::mat4& value) const
{
glUniformMatrix4fv(glGetUniformLocation(id, name.c_str()), 1, GL_FALSE, glm::value_ptr(value));
}
void OpenGLShader::UploadVec2(const std::string &name, const glm::vec2& value) const
{
glUniform2fv(glGetUniformLocation(id, name.c_str()), 1, glm::value_ptr(value));
}
void OpenGLShader::UploadVec3(const std::string &name, const glm::vec3& value) const
{
glUniform3fv(glGetUniformLocation(id, name.c_str()), 1, glm::value_ptr(value));
}
void OpenGLShader::UploadVec4(const std::string &name, const glm::vec4& value) const
{
glUniform4fv(glGetUniformLocation(id, name.c_str()), 1, glm::value_ptr(value));
}
}