Created
August 7, 2020 15:32
-
-
Save yeaFern/ecd60a9bb93cb75ef9c4f775cb1d9f3f to your computer and use it in GitHub Desktop.
OpenGL Shader Program Caching
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "graphics/Shader.h" | |
#include "graphics/ShaderCache.h" | |
namespace Kigu | |
{ | |
Shader::Shader(const std::string name, const File& vertex, const File& fragment) | |
: m_Name(name), m_Program(glCreateProgram()) | |
{ | |
if (!ShaderCache::IsCachingSupported()) | |
{ | |
LogFatal("Shader caching is not available."); | |
} | |
// Check if the shader needs to be recompiled. | |
bool isCacheValid = true; | |
if (ShaderCache::NeedsCompilation(m_Name, vertex )) { isCacheValid = false; } | |
if (ShaderCache::NeedsCompilation(m_Name, fragment)) { isCacheValid = false; } | |
if (isCacheValid) | |
{ | |
// If the cached binary is valid, load it. | |
bool success = LoadFromBinary(); | |
// If the cached binary failed to link, recompile the shader. | |
if (!success) | |
{ | |
LogWarn("Shader cache failed to link, recompiling."); | |
CompileAndSave(vertex, fragment); | |
} | |
} | |
else | |
{ | |
// If the shader cache is invalid, recompile the shader. | |
LogInfo("Shader cache invalid, compiling."); | |
CompileAndSave(vertex, fragment); | |
} | |
} | |
GLint Shader::CompileShader(const File& source, ShaderType type) | |
{ | |
if (!source.Exists()) | |
{ | |
LogFatal(source.GetPath() << " cannot be found."); | |
} | |
// Read the source file into a buffer. | |
GLint size = static_cast<GLint>(source.GetSize()); | |
char* buffer = new char[size]; | |
source.Read(buffer, size); | |
// Create the shader and pass it the source. | |
GLuint id = glCreateShader(static_cast<GLenum>(type)); | |
glShaderSource(id, 1, &buffer, &size); | |
// Compile and check for errors. | |
glCompileShader(id); | |
GLint success; | |
glGetShaderiv(id, GL_COMPILE_STATUS, &success); | |
if (!success) | |
{ | |
GLchar infoLog[1024]; | |
glGetShaderInfoLog(id, 1024, nullptr, infoLog); | |
LogError("====================================================="); | |
LogError(infoLog); | |
LogError("== Caused by: " << source.GetFileName()); | |
LogError("== Failed to compile Shader ========================="); | |
} | |
// Attach the shader and clean up. | |
glAttachShader(m_Program, id); | |
delete[] buffer; | |
return id; | |
} | |
void Shader::CompileAndSave(const File& vertex, const File& fragment) | |
{ | |
// Compile both shaders. | |
GLuint v = CompileShader(vertex, ShaderType::Vertex); | |
GLuint f = CompileShader(fragment, ShaderType::Fragment); | |
// Link and check for errors. | |
glLinkProgram(m_Program); | |
GLint success = 0; | |
GLchar errorLog[1024] = { 0 }; | |
glGetProgramiv(m_Program, GL_LINK_STATUS, &success); | |
if (success == 0) | |
{ | |
glGetProgramInfoLog(m_Program, sizeof(errorLog), NULL, errorLog); | |
LogError("====================================================="); | |
LogError(errorLog); | |
LogError("== Failed to link Shader ============================"); | |
} | |
// Clean up the shaders. | |
glDeleteShader(v); | |
glDeleteShader(f); | |
// Get the size of the binary. | |
GLint binaryLength = 0; | |
glGetProgramiv(m_Program, GL_PROGRAM_BINARY_LENGTH, &binaryLength); | |
// Allocate space for the binary, as well as the binary format. | |
size_t bufferSize = sizeof(GLenum) + binaryLength; | |
char* binary = new char[sizeof(GLenum) + binaryLength]; | |
// Get the binary from the driver, saving the format. | |
GLenum binaryFormat; | |
glGetProgramBinary(m_Program, binaryLength, nullptr, &binaryFormat, binary + sizeof(GLenum)); | |
// Prefix the binary with the format. | |
*((GLenum*)binary) = binaryFormat; | |
// Write the binary to the cache. | |
ShaderCache::Write(m_Name, binary, bufferSize); | |
// Clean up. | |
delete[] binary; | |
} | |
bool Shader::LoadFromBinary() | |
{ | |
// Get the binary file from the cache. | |
File binary = ShaderCache::Get(m_Name); | |
// Read it into a buffer. | |
char* buffer = new char[binary.GetSize()]; | |
binary.Read(buffer, binary.GetSize()); | |
// Grab the format from the front of the buffer; | |
GLenum format = *((GLenum*)buffer); | |
// Calculate offset to start of the shader binary. | |
void* binaryStart = buffer + sizeof(GLenum); | |
size_t binaryLength = binary.GetSize() - sizeof(GLenum); | |
// Upload the binary. | |
glProgramBinary(m_Program, format, binaryStart, binaryLength); | |
// Clean up. | |
delete[] buffer; | |
// Check for success. | |
GLint success = 0; | |
glGetProgramiv(m_Program, GL_LINK_STATUS, &success); | |
return success; | |
} | |
ShaderPtr Shader::Create(const std::string& name, const File& vertex, const File& fragment) | |
{ | |
return std::make_unique<Shader>(name, vertex, fragment); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#pragma once | |
#include <memory> | |
#include <glad/glad.h> | |
#include "util/File.h" | |
#include "util/Log.h" | |
namespace Kigu | |
{ | |
enum class ShaderType : GLenum | |
{ | |
Vertex = GL_VERTEX_SHADER, | |
Fragment = GL_FRAGMENT_SHADER | |
}; | |
class Shader | |
{ | |
private: | |
std::string m_Name; | |
GLuint m_Program; | |
public: | |
Shader( | |
const std::string name, | |
const File& vertex, | |
const File& fragment | |
); | |
inline GLuint GetProgram() const { return m_Program; } | |
private: | |
GLint CompileShader(const File& source, ShaderType type); | |
void CompileAndSave(const File& vertex, const File& fragment); | |
bool LoadFromBinary(); | |
public: | |
static std::unique_ptr<Shader> Create(const std::string& name, const File& vertex, const File& fragment); | |
}; | |
using ShaderPtr = std::unique_ptr<Shader>; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "graphics/ShaderCache.h" | |
#include <iostream> | |
namespace Kigu | |
{ | |
bool ShaderCache::IsCachingSupported() | |
{ | |
// Shader caching is only available on GPUs which support at least one binary format. | |
GLint formats; | |
glGetIntegerv(GL_NUM_PROGRAM_BINARY_FORMATS, &formats); | |
return formats > 0; | |
} | |
bool ShaderCache::NeedsCompilation(const std::string& shaderName, const File& source) | |
{ | |
// A shader needs compilation if either; | |
// - It does not exist in the shader cache. | |
// - The cached binary is older than the source file. | |
std::filesystem::path cache(ShaderCachePath); | |
if (!std::filesystem::exists(cache) || !std::filesystem::is_directory(cache)) | |
{ | |
// If the shader cache doesn't exist at all, create it. | |
std::filesystem::create_directories(cache); | |
// We can also guarantee that the cache is empty, and thus, the shader should be compiled. | |
return true; | |
} | |
File binary(ShaderCachePath + shaderName + ".bin"); | |
if (!binary.Exists()) | |
{ | |
// If the binary does not exist, the shader needs to be compiled. | |
return true; | |
} | |
if (binary.GetLastWriteTime() < source.GetLastWriteTime()) | |
{ | |
// If the binary is out of date, the shader needs to be compiled. | |
return true; | |
} | |
return false; | |
} | |
File ShaderCache::Get(const std::string& shaderName) | |
{ | |
std::filesystem::path cache(ShaderCachePath); | |
if (!std::filesystem::exists(cache) || !std::filesystem::is_directory(cache)) | |
{ | |
// If the shader cache doesn't exist at all, create it. | |
std::filesystem::create_directories(cache); | |
// Throw an error as the binary does not exist. | |
LogError("Shader binary does not exist for shader '" << shaderName << "'."); | |
} | |
File binary(ShaderCachePath + shaderName + ".bin"); | |
if (!binary.Exists()) | |
{ | |
// Throw an error as the binary does not exist. | |
LogError("Shader binary does not exist for shader '" << shaderName << "'."); | |
} | |
return binary; | |
} | |
void ShaderCache::Write(const std::string& shaderName, char* buffer, size_t length) | |
{ | |
std::filesystem::path cache(ShaderCachePath); | |
if (!std::filesystem::exists(cache) || !std::filesystem::is_directory(cache)) | |
{ | |
// If the shader cache doesn't exist at all, create it. | |
std::filesystem::create_directories(cache); | |
} | |
// Write the binary. | |
File binary(ShaderCachePath + shaderName + ".bin"); | |
binary.Write(buffer, length); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#pragma once | |
#include <glad/glad.h> | |
#include "util/File.h" | |
#include "util/Log.h" | |
namespace Kigu | |
{ | |
class ShaderCache | |
{ | |
public: | |
static constexpr auto ShaderCachePath = "Data/ShaderCache/"; | |
static bool IsCachingSupported(); | |
static bool NeedsCompilation(const std::string& shaderName, const File& source); | |
static File Get(const std::string& shaderName); | |
static void Write(const std::string& shaderName, char* buffer, size_t length); | |
}; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment