Files
XCEngine/engine/src/RHI/OpenGL/OpenGLShader.cpp

498 lines
14 KiB
C++

#include "XCEngine/RHI/OpenGL/OpenGLShader.h"
#include <glad/glad.h>
#include <fstream>
#include <sstream>
#include <iostream>
#include <string>
namespace XCEngine {
namespace RHI {
namespace {
bool EndsWith(const std::string& value, const char* suffix) {
const size_t suffixLength = strlen(suffix);
return value.size() >= suffixLength && value.compare(value.size() - suffixLength, suffixLength, suffix) == 0;
}
std::string NarrowAscii(const std::wstring& value) {
std::string result;
result.reserve(value.size());
for (wchar_t ch : value) {
result.push_back(static_cast<char>(ch));
}
return result;
}
bool ResolveShaderType(const std::string& path, const char* target, ShaderType& type) {
if (target != nullptr) {
if (strstr(target, "vs_") != nullptr) {
type = ShaderType::Vertex;
return true;
}
if (strstr(target, "ps_") != nullptr || strstr(target, "fs_") != nullptr) {
type = ShaderType::Fragment;
return true;
}
if (strstr(target, "gs_") != nullptr) {
type = ShaderType::Geometry;
return true;
}
if (strstr(target, "cs_") != nullptr) {
type = ShaderType::Compute;
return true;
}
if (strstr(target, "hs_") != nullptr || strstr(target, "tcs") != nullptr) {
type = ShaderType::TessControl;
return true;
}
if (strstr(target, "ds_") != nullptr || strstr(target, "tes") != nullptr) {
type = ShaderType::TessEvaluation;
return true;
}
}
if (EndsWith(path, ".vert") || EndsWith(path, ".vs.glsl")) {
type = ShaderType::Vertex;
return true;
}
if (EndsWith(path, ".frag") || EndsWith(path, ".fs.glsl")) {
type = ShaderType::Fragment;
return true;
}
if (EndsWith(path, ".geom") || EndsWith(path, ".gs.glsl")) {
type = ShaderType::Geometry;
return true;
}
if (EndsWith(path, ".comp") || EndsWith(path, ".cs.glsl")) {
type = ShaderType::Compute;
return true;
}
if (EndsWith(path, ".tesc")) {
type = ShaderType::TessControl;
return true;
}
if (EndsWith(path, ".tese")) {
type = ShaderType::TessEvaluation;
return true;
}
return false;
}
bool ResolveShaderTypeFromSource(const std::string& source, ShaderType& type) {
if (source.find("layout(local_size_x") != std::string::npos ||
source.find("gl_GlobalInvocationID") != std::string::npos ||
source.find("gl_LocalInvocationID") != std::string::npos ||
source.find("gl_WorkGroupID") != std::string::npos) {
type = ShaderType::Compute;
return true;
}
if (source.find("EmitVertex") != std::string::npos ||
source.find("EndPrimitive") != std::string::npos ||
source.find("gl_in[") != std::string::npos) {
type = ShaderType::Geometry;
return true;
}
if (source.find("layout(vertices") != std::string::npos ||
source.find("gl_InvocationID") != std::string::npos) {
type = ShaderType::TessControl;
return true;
}
if (source.find("gl_TessCoord") != std::string::npos ||
source.find("gl_TessLevelOuter") != std::string::npos) {
type = ShaderType::TessEvaluation;
return true;
}
if (source.find("gl_Position") != std::string::npos) {
type = ShaderType::Vertex;
return true;
}
if (source.find("gl_FragCoord") != std::string::npos ||
source.find("gl_FragColor") != std::string::npos ||
source.find("gl_FragData") != std::string::npos) {
type = ShaderType::Fragment;
return true;
}
return false;
}
} // namespace
OpenGLShader::OpenGLShader()
: m_program(0), m_uniformsCached(false) {
}
OpenGLShader::~OpenGLShader() {
Shutdown();
}
bool OpenGLShader::CompileFromFile(const char* vertexPath, const char* fragmentPath) {
std::string vertexCode, fragmentCode;
std::ifstream vShaderFile(vertexPath), fShaderFile(fragmentPath);
if (!vShaderFile.is_open() || !fShaderFile.is_open()) {
return false;
}
std::stringstream vShaderStream, fShaderStream;
vShaderStream << vShaderFile.rdbuf();
fShaderStream << fShaderFile.rdbuf();
vShaderFile.close();
fShaderFile.close();
vertexCode = vShaderStream.str();
fragmentCode = fShaderStream.str();
return Compile(vertexCode.c_str(), fragmentCode.c_str());
}
bool OpenGLShader::CompileFromFile(const char* vertexPath, const char* fragmentPath, const char* geometryPath) {
std::string vertexCode, fragmentCode, geometryCode;
std::ifstream vShaderFile(vertexPath), fShaderFile(fragmentPath), gShaderFile(geometryPath);
if (!vShaderFile.is_open() || !fShaderFile.is_open() || !gShaderFile.is_open()) {
return false;
}
std::stringstream vShaderStream, fShaderStream, gShaderStream;
vShaderStream << vShaderFile.rdbuf();
fShaderStream << fShaderFile.rdbuf();
gShaderStream << gShaderFile.rdbuf();
vShaderFile.close();
fShaderFile.close();
gShaderFile.close();
vertexCode = vShaderStream.str();
fragmentCode = fShaderStream.str();
geometryCode = gShaderStream.str();
return Compile(vertexCode.c_str(), fragmentCode.c_str(), geometryCode.c_str());
}
bool OpenGLShader::Compile(const char* vertexSource, const char* fragmentSource) {
unsigned int vertex, fragment;
vertex = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertex, 1, &vertexSource, nullptr);
glCompileShader(vertex);
if (!CheckCompileErrors(vertex, "VERTEX")) {
return false;
}
fragment = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragment, 1, &fragmentSource, nullptr);
glCompileShader(fragment);
if (!CheckCompileErrors(fragment, "FRAGMENT")) {
return false;
}
m_program = glCreateProgram();
glAttachShader(m_program, vertex);
glAttachShader(m_program, fragment);
glLinkProgram(m_program);
if (!CheckLinkErrors(m_program)) {
return false;
}
glDeleteShader(vertex);
glDeleteShader(fragment);
m_uniformsCached = false;
return true;
}
bool OpenGLShader::Compile(const char* vertexSource, const char* fragmentSource, const char* geometrySource) {
unsigned int vertex, fragment, geometry;
vertex = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertex, 1, &vertexSource, nullptr);
glCompileShader(vertex);
if (!CheckCompileErrors(vertex, "VERTEX")) {
return false;
}
fragment = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragment, 1, &fragmentSource, nullptr);
glCompileShader(fragment);
if (!CheckCompileErrors(fragment, "FRAGMENT")) {
return false;
}
geometry = glCreateShader(GL_GEOMETRY_SHADER);
glShaderSource(geometry, 1, &geometrySource, nullptr);
glCompileShader(geometry);
if (!CheckCompileErrors(geometry, "GEOMETRY")) {
return false;
}
m_program = glCreateProgram();
glAttachShader(m_program, vertex);
glAttachShader(m_program, fragment);
glAttachShader(m_program, geometry);
glLinkProgram(m_program);
if (!CheckLinkErrors(m_program)) {
return false;
}
glDeleteShader(vertex);
glDeleteShader(fragment);
glDeleteShader(geometry);
m_uniformsCached = false;
return true;
}
bool OpenGLShader::CompileCompute(const char* computeSource) {
unsigned int compute = glCreateShader(GL_COMPUTE_SHADER);
glShaderSource(compute, 1, &computeSource, nullptr);
glCompileShader(compute);
if (!CheckCompileErrors(compute, "COMPUTE")) {
return false;
}
m_program = glCreateProgram();
glAttachShader(m_program, compute);
glLinkProgram(m_program);
if (!CheckLinkErrors(m_program)) {
return false;
}
glDeleteShader(compute);
m_type = ShaderType::Compute;
m_uniformsCached = false;
return true;
}
bool OpenGLShader::Compile(const char* source, ShaderType type) {
unsigned int shader = 0;
switch (type) {
case ShaderType::Vertex:
shader = glCreateShader(GL_VERTEX_SHADER);
break;
case ShaderType::Fragment:
shader = glCreateShader(GL_FRAGMENT_SHADER);
break;
case ShaderType::Geometry:
shader = glCreateShader(GL_GEOMETRY_SHADER);
break;
case ShaderType::Compute:
shader = glCreateShader(GL_COMPUTE_SHADER);
break;
case ShaderType::TessControl:
shader = glCreateShader(GL_TESS_CONTROL_SHADER);
break;
case ShaderType::TessEvaluation:
shader = glCreateShader(GL_TESS_EVALUATION_SHADER);
break;
default:
return false;
}
glShaderSource(shader, 1, &source, nullptr);
glCompileShader(shader);
const char* typeName[] = { "VERTEX", "FRAGMENT", "GEOMETRY", "COMPUTE", "TESS_CONTROL", "TESS_EVALUATION" };
if (!CheckCompileErrors(shader, typeName[(int)type])) {
return false;
}
if (m_program == 0) {
m_program = glCreateProgram();
}
glAttachShader(m_program, shader);
glLinkProgram(m_program);
if (!CheckLinkErrors(m_program)) {
return false;
}
glDeleteShader(shader);
m_type = type;
m_uniformsCached = false;
return true;
}
bool OpenGLShader::CompileFromFile(const wchar_t* filePath, const char* entryPoint, const char* target) {
(void)entryPoint;
if (filePath == nullptr) {
return false;
}
const std::wstring ws(filePath);
const std::string path = NarrowAscii(ws);
std::ifstream shaderFile(path);
if (!shaderFile.is_open()) {
return false;
}
std::stringstream shaderStream;
shaderStream << shaderFile.rdbuf();
ShaderType type = ShaderType::Vertex;
if (!ResolveShaderType(path, target, type)) {
return false;
}
const std::string source = shaderStream.str();
return Compile(source.c_str(), type);
}
bool OpenGLShader::Compile(const void* sourceData, size_t sourceSize, const char* entryPoint, const char* target) {
if (!sourceData || sourceSize == 0) {
return false;
}
(void)entryPoint;
const std::string source(static_cast<const char*>(sourceData), sourceSize);
ShaderType type = ShaderType::Vertex;
if (!ResolveShaderType(std::string(), target, type) &&
!ResolveShaderTypeFromSource(source, type)) {
return false;
}
return Compile(source.c_str(), type);
}
bool OpenGLShader::AdoptProgram(unsigned int program, ShaderType type) {
if (program == 0) {
return false;
}
if (m_program != 0 && m_program != program) {
glDeleteProgram(m_program);
}
m_program = program;
m_type = type;
m_uniformInfos.clear();
m_uniformsCached = false;
return true;
}
void OpenGLShader::Shutdown() {
if (m_program) {
glDeleteProgram(m_program);
m_program = 0;
}
m_uniformInfos.clear();
m_uniformsCached = false;
}
void OpenGLShader::Use() const {
glUseProgram(m_program);
}
void OpenGLShader::CacheUniformInfos() const {
if (m_uniformsCached || m_program == 0) {
return;
}
m_uniformInfos.clear();
GLint numUniforms = 0;
glGetProgramInterfaceiv(m_program, GL_UNIFORM, GL_ACTIVE_RESOURCES, &numUniforms);
for (GLint i = 0; i < numUniforms; ++i) {
GLenum props[] = { GL_NAME_LENGTH, GL_TYPE, GL_OFFSET, GL_ARRAY_SIZE };
GLint values[4] = { 0 };
glGetProgramResourceiv(m_program, GL_UNIFORM, i, 4, props, 4, nullptr, values);
std::vector<char> nameBuffer(values[0]);
glGetProgramResourceName(m_program, GL_UNIFORM, i, values[0], nullptr, nameBuffer.data());
UniformInfo info;
info.name = nameBuffer.data();
info.bindPoint = static_cast<uint32_t>(i);
info.type = static_cast<uint32_t>(values[1]);
info.offset = static_cast<uint32_t>(values[2]);
info.arraySize = static_cast<uint32_t>(values[3]);
GLint size = values[3];
switch (values[1]) {
case GL_FLOAT: info.size = sizeof(GLfloat) * size; break;
case GL_FLOAT_VEC2: info.size = sizeof(GLfloat) * 2 * size; break;
case GL_FLOAT_VEC3: info.size = sizeof(GLfloat) * 3 * size; break;
case GL_FLOAT_VEC4: info.size = sizeof(GLfloat) * 4 * size; break;
case GL_INT: info.size = sizeof(GLint) * size; break;
case GL_BOOL: info.size = sizeof(GLboolean) * size; break;
case GL_FLOAT_MAT4: info.size = sizeof(GLfloat) * 16 * size; break;
case GL_FLOAT_MAT3: info.size = sizeof(GLfloat) * 9 * size; break;
case GL_FLOAT_MAT2: info.size = sizeof(GLfloat) * 4 * size; break;
default: info.size = 0; break;
}
m_uniformInfos.push_back(info);
}
m_uniformsCached = true;
}
const std::vector<RHIShader::UniformInfo>& OpenGLShader::GetUniformInfos() const {
CacheUniformInfos();
return m_uniformInfos;
}
const RHIShader::UniformInfo* OpenGLShader::GetUniformInfo(const char* name) const {
CacheUniformInfos();
for (const auto& info : m_uniformInfos) {
if (info.name == name) {
return &info;
}
}
return nullptr;
}
int OpenGLShader::GetUniformLocation(const char* name) const {
return glGetUniformLocation(m_program, name);
}
bool OpenGLShader::CheckCompileErrors(unsigned int shader, const char* type) {
int success;
char infoLog[1024];
glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
if (!success) {
glGetShaderInfoLog(shader, 1024, nullptr, infoLog);
std::cout << "ERROR::SHADER_COMPILATION_ERROR: " << type << "\n" << infoLog << std::endl;
return false;
}
return true;
}
bool OpenGLShader::CheckLinkErrors(unsigned int program) {
int success;
char infoLog[1024];
glGetProgramiv(program, GL_LINK_STATUS, &success);
if (!success) {
glGetProgramInfoLog(program, 1024, nullptr, infoLog);
std::cout << "ERROR::PROGRAM_LINKING_ERROR\n" << infoLog << std::endl;
return false;
}
return true;
}
} // namespace RHI
} // namespace XCEngine