#include "ShaderGL.h" #include "GeometryGL.h" #include "DeviceGL.h" #include "HeadersGL.h" #include #include LOGGROUP(Graphics) namespace RBX { namespace Graphics { static int getShaderParameter(GLuint shader, GLenum pname) { GLint result; glGetShaderiv(shader, pname, &result); return result; } static int getProgramParameter(GLuint program, GLenum pname) { GLint result; glGetProgramiv(program, pname, &result); return result; } template static std::string getInfoLog(unsigned int id, GetParameter getParameter, GetLog getLog) { int infoLength = getParameter(id, GL_INFO_LOG_LENGTH); if (infoLength > 0) { std::vector buffer(infoLength); getLog(id, infoLength, NULL, &buffer[0]); return std::string(&buffer[0]); } else { return ""; } } template static void dumpInfoLog(unsigned int id, GetParameter getParameter, GetLog getLog) { std::string log = getInfoLog(id, getParameter, getLog); // Intel and AMD drivers like to say "No errors" for every shader. if (strstr(log.c_str(), "warn") || strstr(log.c_str(), "Warn") || strstr(log.c_str(), "WARN")) { FASTLOG2(FLog::Graphics, "Shader %d has a non-empty infolog (length %d)", id, log.length()); ShaderProgram::dumpToFLog(log, FLog::Graphics); } } static unsigned int compileShader(const std::string& source, GLenum type) { unsigned int id = glCreateShader(type); RBXASSERT(id); int length = source.length(); const char* data = source.c_str(); glShaderSource(id, 1, &data, &length); glCompileShader(id); if (getShaderParameter(id, GL_COMPILE_STATUS) != 1) { std::string infoLog = getInfoLog(id, getShaderParameter, glGetShaderInfoLog); glDeleteShader(id); throw std::runtime_error(infoLog); } dumpInfoLog(id, getShaderParameter, glGetShaderInfoLog); return id; } static unsigned int parseAttribs(const std::string& source, const char* prefix) { std::set attribs; size_t offset = 0; while (offset < source.length()) { std::string::size_type start = source.find(prefix, offset); if (start == std::string::npos) break; std::string::size_type eol = source.find('\n', start); if (eol == std::string::npos) break; offset = eol; std::string::size_type semi = source.find(';', start); std::string::size_type space = source.find_last_of(' ', eol); if ((start == 0 || source[start - 1] == '\n') && semi != std::string::npos && space != std::string::npos && space < semi) attribs.insert(source.substr(space + 1, semi - space - 1)); } unsigned int result = 0; for (unsigned int attr = 0; attr < 16; ++attr) { const char* name = VertexLayoutGL::getShaderAttributeName(attr); if (name && attribs.count(name)) result |= 1 << attr; } return result; } static void applyAttribs(unsigned int id, unsigned int attribMask) { for (unsigned int attr = 0; attr < 16; ++attr) if (attribMask & (1 << attr)) { const char* name = VertexLayoutGL::getShaderAttributeName(attr); RBXASSERT(name); glBindAttribLocation(id, attr, name); } } static std::vector parseSamplers(const std::string& source) { std::vector result; size_t offset = 0; while (offset < source.length()) { std::string::size_type start = source.find("//$$", offset); if (start == std::string::npos) return result; std::string::size_type equals = source.find('=', start); if (equals == std::string::npos) return result; std::string::size_type end = source.find('\n', equals); if (end == std::string::npos) return result; std::string name(source.begin() + start + 4, source.begin() + equals); std::string value(source.begin() + equals + 1, source.begin() + end); if (!value.empty() && value[0] == 's') { FragmentShaderGL::Sampler entry = {name, atoi(value.c_str() + 1)}; result.push_back(entry); } offset = end; } return result; } static int getSamplerLocation(unsigned int id, const std::string& name) { int location; if ((location = glGetUniformLocation(id, name.c_str())) >= 0) return location; if ((location = glGetUniformLocation(id, ("xlu_" + name).c_str())) >= 0) return location; return -1; } static unsigned int applySamplers(unsigned int id, const std::vector& samplers) { unsigned int mask = 0; glUseProgram(id); for (size_t i = 0; i < samplers.size(); ++i) { int location = getSamplerLocation(id, samplers[i].name); if (location >= 0) { glUniform1i(location, samplers[i].slot); mask |= 1 << samplers[i].slot; } } glUseProgram(0); return mask; } static ShaderProgramGL::Uniform::Type getUniformType(GLenum type) { switch (type) { case GL_FLOAT: return ShaderProgramGL::Uniform::Type_Float; case GL_FLOAT_VEC2: return ShaderProgramGL::Uniform::Type_Float2; case GL_FLOAT_VEC3: return ShaderProgramGL::Uniform::Type_Float3; case GL_FLOAT_VEC4: return ShaderProgramGL::Uniform::Type_Float4; case GL_FLOAT_MAT4: return ShaderProgramGL::Uniform::Type_Float4x4; default: return ShaderProgramGL::Uniform::Type_Unknown; } } typedef std::map UniformTable; static UniformTable extractUniforms(unsigned int id) { int uniformCount = getProgramParameter(id, GL_ACTIVE_UNIFORMS); int uniformMaxLength = getProgramParameter(id, GL_ACTIVE_UNIFORM_MAX_LENGTH); std::vector uniformName(uniformMaxLength + 1); UniformTable result; for (int i = 0; i < uniformCount; ++i) { GLint size; GLenum type; glGetActiveUniform(id, i, uniformMaxLength, NULL, &size, &type, &uniformName[0]); ShaderProgramGL::Uniform::Type uniformType = getUniformType(type); int location = glGetUniformLocation(id, &uniformName[0]); if (location >= 0 && uniformType != ShaderProgramGL::Uniform::Type_Unknown) { ShaderProgramGL::Uniform uniform = {location, uniformType, static_cast(size), static_cast(0)}; std::string name(&uniformName[0]); // Remove xlu_ prefix for entrypoint-local uniforms if (name.compare(0, 4, "xlu_") == 0) name.erase(name.begin(), name.begin() + 4); // Remove [0] from arrays std::string::size_type index = name.find('['); if (index != std::string::npos) name.erase(name.begin() + index, name.end()); result[name] = uniform; } } return result; } static const ShaderProgramGL::Uniform* findUniform(const UniformTable& ctab, const std::string& name) { UniformTable::const_iterator it = ctab.find(name); return (it == ctab.end()) ? NULL : &it->second; } static inline void transposeMatrix(float* dest, const float* source, const float* lastColumn) { dest[0] = source[0]; dest[1] = source[4]; dest[2] = source[8]; dest[3] = lastColumn[0]; dest[4] = source[1]; dest[5] = source[5]; dest[6] = source[9]; dest[7] = lastColumn[1]; dest[8] = source[2]; dest[9] = source[6]; dest[10] = source[10]; dest[11] = lastColumn[2]; dest[12] = source[3]; dest[13] = source[7]; dest[14] = source[11]; dest[15] = lastColumn[3]; } VertexShaderGL::VertexShaderGL(Device* device, const std::string& source) : VertexShader(device) , id(0) , attribMask(0) { id = compileShader(source, GL_VERTEX_SHADER); attribMask = parseAttribs(source, static_cast(device)->getCapsGL().ext3 ? "in " : "attribute "); } VertexShaderGL::~VertexShaderGL() { glDeleteShader(id); } void VertexShaderGL::reloadBytecode(const std::vector& bytecode) { throw std::runtime_error("Bytecode reloading is not supported"); } FragmentShaderGL::FragmentShaderGL(Device* device, const std::string& source) : FragmentShader(device) , id(0) { id = compileShader(source, GL_FRAGMENT_SHADER); samplers = parseSamplers(source); } FragmentShaderGL::~FragmentShaderGL() { glDeleteShader(id); } void FragmentShaderGL::reloadBytecode(const std::vector& bytecode) { throw std::runtime_error("Bytecode reloading is not supported"); } ShaderProgramGL::ShaderProgramGL(Device* device, const shared_ptr& vertexShader, const shared_ptr& fragmentShader) : ShaderProgram(device, vertexShader, fragmentShader) , id(0) , uniformWorldMatrix(-1) , uniformWorldMatrixArray(-1) , cachedGlobalVersion(0) , maxWorldTransforms(0) , samplerMask(0) { id = glCreateProgram(); RBXASSERT(id); glAttachShader(id, static_cast(vertexShader.get())->getId()); glAttachShader(id, static_cast(fragmentShader.get())->getId()); // Setup attribute locations applyAttribs(id, static_cast(vertexShader.get())->getAttribMask()); glLinkProgram(id); if (getProgramParameter(id, GL_LINK_STATUS) != 1) { std::string infoLog = getInfoLog(id, getProgramParameter, glGetProgramInfoLog); glDeleteProgram(id); throw std::runtime_error(infoLog); } dumpInfoLog(id, getProgramParameter, glGetProgramInfoLog); // Make sure the program uses correct sampler slot assignments // Note: this resets current program to 0! static_cast(device)->getImmediateContextGL()->invalidateCachedProgram(); samplerMask = applySamplers(id, static_cast(fragmentShader.get())->getSamplers()); // Populate constant tables const std::vector& globalConstants = static_cast(device)->getGlobalConstants(); UniformTable ctab = extractUniforms(id); for (size_t i = 0; i < globalConstants.size(); ++i) if (const Uniform* uniform = findUniform(ctab, globalConstants[i].name)) { Uniform u = *uniform; u.offset = globalConstants[i].offset; globalUniforms.push_back(u); ctab.erase(globalConstants[i].name); } // Populate world matrix information if (const Uniform* uniform = findUniform(ctab, "WorldMatrix")) { RBXASSERT(uniform->type == Uniform::Type_Float4x4); uniformWorldMatrix = uniform->location; maxWorldTransforms = 1; ctab.erase("WorldMatrix"); } if (const Uniform* uniform = findUniform(ctab, "WorldMatrixArray")) { RBXASSERT(uniform->type == Uniform::Type_Float4); RBXASSERT(uniform->size % 3 == 0); uniformWorldMatrixArray = uniform->location; maxWorldTransforms = uniform->size / 3; ctab.erase("WorldMatrixArray"); } // Populate uniform information for (UniformTable::iterator it = ctab.begin(); it != ctab.end(); ++it) { uniforms.push_back(std::make_pair(it->first, it->second)); } } ShaderProgramGL::~ShaderProgramGL() { glDeleteProgram(id); // Shader program destruction invalidates currently bound program id static_cast(device)->getImmediateContextGL()->invalidateCachedProgram(); } int ShaderProgramGL::getConstantHandle(const char* name) const { for (size_t i = 0; i < uniforms.size(); ++i) if (uniforms[i].first == name) return i; return -1; } unsigned int ShaderProgramGL::getMaxWorldTransforms() const { return maxWorldTransforms; } unsigned int ShaderProgramGL::getSamplerMask() const { return samplerMask; } void ShaderProgramGL::bind(const void* globalData, unsigned int globalVersion, ShaderProgramGL** cache) { if (*cache != this) { *cache = this; glUseProgram(id); } if (cachedGlobalVersion != globalVersion) { cachedGlobalVersion = globalVersion; for (size_t i = 0; i < globalUniforms.size(); ++i) { const Uniform& u = globalUniforms[i]; const float* data = reinterpret_cast(static_cast(globalData) + u.offset); switch (u.type) { case Uniform::Type_Float: glUniform1fv(u.location, 1, data); break; case Uniform::Type_Float2: glUniform2fv(u.location, 1, data); break; case Uniform::Type_Float3: glUniform3fv(u.location, 1, data); break; case Uniform::Type_Float4: glUniform4fv(u.location, 1, data); break; case Uniform::Type_Float4x4: { float matrix[16]; transposeMatrix(matrix, data, data + 12); glUniformMatrix4fv(u.location, 1, false, matrix); break; } default: break; } } } } void ShaderProgramGL::setWorldTransforms4x3(const float* data, size_t matrixCount) { if (matrixCount == 0) return; if (uniformWorldMatrix >= 0) { static const float lastColumn[] = {0, 0, 0, 1}; float matrix[16]; transposeMatrix(matrix, data, lastColumn); glUniformMatrix4fv(uniformWorldMatrix, 1, false, matrix); } if (uniformWorldMatrixArray >= 0) { RBXASSERT(matrixCount <= maxWorldTransforms); glUniform4fv(uniformWorldMatrixArray, matrixCount * 3, data); } } void ShaderProgramGL::setConstant(int handle, const float* data, size_t vectorCount) { if (handle < 0) return; RBXASSERT(static_cast(handle) < uniforms.size()); const Uniform& u = uniforms[handle].second; switch (u.type) { case Uniform::Type_Float: RBXASSERT(vectorCount == 1); glUniform1fv(u.location, 1, data); break; case Uniform::Type_Float2: RBXASSERT(vectorCount == 1); glUniform2fv(u.location, 1, data); break; case Uniform::Type_Float3: RBXASSERT(vectorCount == 1); glUniform3fv(u.location, 1, data); break; case Uniform::Type_Float4: RBXASSERT(vectorCount > 0 && vectorCount <= u.size); glUniform4fv(u.location, vectorCount, data); break; default: RBXASSERT(false); } } } }