bool ShaderProgram::Link() { Release(); if (!vertexShader_ || !pixelShader_ || !vertexShader_->GetGPUObjectName() || !pixelShader_->GetGPUObjectName()) return false; object_.name_ = glCreateProgram(); if (!object_.name_) { linkerOutput_ = "Could not create shader program"; return false; } glAttachShader(object_.name_, vertexShader_->GetGPUObjectName()); glAttachShader(object_.name_, pixelShader_->GetGPUObjectName()); glLinkProgram(object_.name_); int linked, length; glGetProgramiv(object_.name_, GL_LINK_STATUS, &linked); if (!linked) { glGetProgramiv(object_.name_, GL_INFO_LOG_LENGTH, &length); linkerOutput_.Resize((unsigned)length); int outLength; glGetProgramInfoLog(object_.name_, length, &outLength, &linkerOutput_[0]); glDeleteProgram(object_.name_); object_.name_ = 0; } else linkerOutput_.Clear(); if (!object_.name_) return false; const int MAX_NAME_LENGTH = 256; char nameBuffer[MAX_NAME_LENGTH]; int attributeCount, uniformCount, elementCount, nameLength; GLenum type; glUseProgram(object_.name_); // Check for vertex attributes glGetProgramiv(object_.name_, GL_ACTIVE_ATTRIBUTES, &attributeCount); for (int i = 0; i < attributeCount; ++i) { glGetActiveAttrib(object_.name_, i, (GLsizei)MAX_NAME_LENGTH, &nameLength, &elementCount, &type, nameBuffer); String name = String(nameBuffer, nameLength); VertexElementSemantic semantic = MAX_VERTEX_ELEMENT_SEMANTICS; unsigned char semanticIndex = 0; // Go in reverse order so that "binormal" is detected before "normal" for (unsigned j = MAX_VERTEX_ELEMENT_SEMANTICS - 1; j < MAX_VERTEX_ELEMENT_SEMANTICS; --j) { if (name.Contains(ShaderVariation::elementSemanticNames[j], false)) { semantic = (VertexElementSemantic)j; unsigned index = NumberPostfix(name); if (index != M_MAX_UNSIGNED) semanticIndex = (unsigned char)index; break; } } if (semantic == MAX_VERTEX_ELEMENT_SEMANTICS) { URHO3D_LOGWARNING("Found vertex attribute " + name + " with no known semantic in shader program " + vertexShader_->GetFullName() + " " + pixelShader_->GetFullName()); continue; } int location = glGetAttribLocation(object_.name_, name.CString()); vertexAttributes_[MakePair((unsigned char)semantic, semanticIndex)] = location; usedVertexAttributes_ |= (1u << location); } // Check for constant buffers #ifndef GL_ES_VERSION_2_0 HashMap<unsigned, unsigned> blockToBinding; if (Graphics::GetGL3Support()) { int numUniformBlocks = 0; glGetProgramiv(object_.name_, GL_ACTIVE_UNIFORM_BLOCKS, &numUniformBlocks); for (int i = 0; i < numUniformBlocks; ++i) { glGetActiveUniformBlockName(object_.name_, (GLuint)i, MAX_NAME_LENGTH, &nameLength, nameBuffer); String name(nameBuffer, (unsigned)nameLength); unsigned blockIndex = glGetUniformBlockIndex(object_.name_, name.CString()); unsigned group = M_MAX_UNSIGNED; // Try to recognize the use of the buffer from its name for (unsigned j = 0; j < MAX_SHADER_PARAMETER_GROUPS; ++j) { if (name.Contains(shaderParameterGroups[j], false)) { group = j; break; } } // If name is not recognized, search for a digit in the name and use that as the group index if (group == M_MAX_UNSIGNED) group = NumberPostfix(name); if (group >= MAX_SHADER_PARAMETER_GROUPS) { URHO3D_LOGWARNING("Skipping unrecognized uniform block " + name + " in shader program " + vertexShader_->GetFullName() + " " + pixelShader_->GetFullName()); continue; } // Find total constant buffer data size int dataSize; glGetActiveUniformBlockiv(object_.name_, blockIndex, GL_UNIFORM_BLOCK_DATA_SIZE, &dataSize); if (!dataSize) continue; unsigned bindingIndex = group; // Vertex shader constant buffer bindings occupy slots starting from zero to maximum supported, pixel shader bindings // from that point onward ShaderType shaderType = VS; if (name.Contains("PS", false)) { bindingIndex += MAX_SHADER_PARAMETER_GROUPS; shaderType = PS; } glUniformBlockBinding(object_.name_, blockIndex, bindingIndex); blockToBinding[blockIndex] = bindingIndex; constantBuffers_[bindingIndex] = graphics_->GetOrCreateConstantBuffer(shaderType, bindingIndex, (unsigned)dataSize); } } #endif // Check for shader parameters and texture units glGetProgramiv(object_.name_, GL_ACTIVE_UNIFORMS, &uniformCount); for (int i = 0; i < uniformCount; ++i) { glGetActiveUniform(object_.name_, (GLuint)i, MAX_NAME_LENGTH, nullptr, &elementCount, &type, nameBuffer); int location = glGetUniformLocation(object_.name_, nameBuffer); // Check for array index included in the name and strip it String name(nameBuffer); unsigned index = name.Find('['); if (index != String::NPOS) { // If not the first index, skip if (name.Find("[0]", index) == String::NPOS) continue; name = name.Substring(0, index); } if (name[0] == 'c') { // Store constant uniform String paramName = name.Substring(1); ShaderParameter parameter{paramName, type, location}; bool store = location >= 0; #ifndef GL_ES_VERSION_2_0 // If running OpenGL 3, the uniform may be inside a constant buffer if (parameter.location_ < 0 && Graphics::GetGL3Support()) { int blockIndex, blockOffset; glGetActiveUniformsiv(object_.name_, 1, (const GLuint*)&i, GL_UNIFORM_BLOCK_INDEX, &blockIndex); glGetActiveUniformsiv(object_.name_, 1, (const GLuint*)&i, GL_UNIFORM_OFFSET, &blockOffset); if (blockIndex >= 0) { parameter.offset_ = blockOffset; parameter.bufferPtr_ = constantBuffers_[blockToBinding[blockIndex]]; store = true; } } #endif if (store) shaderParameters_[StringHash(paramName)] = parameter; } else if (location >= 0 && name[0] == 's') { // Set the samplers here so that they do not have to be set later unsigned unit = graphics_->GetTextureUnit(name.Substring(1)); if (unit >= MAX_TEXTURE_UNITS) unit = NumberPostfix(name); if (unit < MAX_TEXTURE_UNITS) { useTextureUnits_[unit] = true; glUniform1iv(location, 1, reinterpret_cast<int*>(&unit)); } } } // Rehash the parameter & vertex attributes maps to ensure minimal load factor vertexAttributes_.Rehash(NextPowerOfTwo(vertexAttributes_.Size())); shaderParameters_.Rehash(NextPowerOfTwo(shaderParameters_.Size())); return true; }
bool ShaderProgram::Link() { PROFILE(LinkShaderProgram); Release(); if (!graphics || !graphics->IsInitialized()) { LOGERROR("Can not link shader program without initialized Graphics subsystem"); return false; } if (!vs || !ps) { LOGERROR("Shader(s) are null, can not link shader program"); return false; } if (!vs->GLShader() || !ps->GLShader()) { LOGERROR("Shaders have not been compiled, can not link shader program"); return false; } const String& vsSourceCode = vs->Parent() ? vs->Parent()->SourceCode() : String::EMPTY; const String& psSourceCode = ps->Parent() ? ps->Parent()->SourceCode() : String::EMPTY; program = glCreateProgram(); if (!program) { LOGERROR("Could not create shader program"); return false; } glAttachShader(program, vs->GLShader()); glAttachShader(program, ps->GLShader()); glLinkProgram(program); int linked; glGetProgramiv(program, GL_LINK_STATUS, &linked); if (!linked) { int length, outLength; String errorString; glGetProgramiv(program, GL_INFO_LOG_LENGTH, &length); errorString.Resize(length); glGetProgramInfoLog(program, length, &outLength, &errorString[0]); glDeleteProgram(program); program = 0; LOGERRORF("Could not link shaders %s: %s", FullName().CString(), errorString.CString()); return false; } LOGDEBUGF("Linked shaders %s", FullName().CString()); glUseProgram(program); char nameBuffer[MAX_NAME_LENGTH]; int numAttributes, numUniforms, numUniformBlocks, nameLength, numElements; GLenum type; attributes.Clear(); glGetProgramiv(program, GL_ACTIVE_ATTRIBUTES, &numAttributes); for (int i = 0; i < numAttributes; ++i) { glGetActiveAttrib(program, i, (GLsizei)MAX_NAME_LENGTH, &nameLength, &numElements, &type, nameBuffer); VertexAttribute newAttribute; newAttribute.name = String(nameBuffer, nameLength); newAttribute.semantic = SEM_POSITION; newAttribute.index = 0; for (size_t j = 0; elementSemanticNames[j]; ++j) { if (newAttribute.name.StartsWith(elementSemanticNames[j], false)) { int index = NumberPostfix(newAttribute.name); if (index >= 0) newAttribute.index = (unsigned char)index; break; } newAttribute.semantic = (ElementSemantic)(newAttribute.semantic + 1); } if (newAttribute.semantic == MAX_ELEMENT_SEMANTICS) { LOGWARNINGF("Found vertex attribute %s with no known semantic in shader program %s", newAttribute.name.CString(), FullName().CString()); continue; } newAttribute.location = glGetAttribLocation(program, newAttribute.name.CString()); attributes.Push(newAttribute); } glGetProgramiv(program, GL_ACTIVE_UNIFORMS, &numUniforms); int numTextures = 0; for (int i = 0; i < numUniforms; ++i) { glGetActiveUniform(program, i, MAX_NAME_LENGTH, &nameLength, &numElements, &type, nameBuffer); String name(nameBuffer, nameLength); if (type >= GL_SAMPLER_1D && type <= GL_SAMPLER_2D_SHADOW) { // Assign sampler uniforms to a texture unit according to the number appended to the sampler name int location = glGetUniformLocation(program, name.CString()); int unit = NumberPostfix(name); // If no unit number specified, assign in appearance order starting from unit 0 if (unit < 0) unit = numTextures; // Array samplers may have multiple elements, assign each sequentially if (numElements > 1) { Vector<int> units; for (int j = 0; j < numElements; ++j) units.Push(unit++); glUniform1iv(location, numElements, &units[0]); } else glUniform1iv(location, 1, &unit); numTextures += numElements; } } glGetProgramiv(program, GL_ACTIVE_UNIFORM_BLOCKS, &numUniformBlocks); for (int i = 0; i < numUniformBlocks; ++i) { glGetActiveUniformBlockName(program, i, (GLsizei)MAX_NAME_LENGTH, &nameLength, nameBuffer); // Determine whether uniform block belongs to vertex or pixel shader String name(nameBuffer, nameLength); bool foundVs = vsSourceCode.Contains(name); bool foundPs = psSourceCode.Contains(name); if (foundVs && foundPs) { LOGWARNINGF("Found uniform block %s in both vertex and pixel shader in shader program %s"); continue; } // Vertex shader constant buffer bindings occupy slots starting from zero to maximum supported, pixel shader bindings // from that point onward unsigned blockIndex = glGetUniformBlockIndex(program, name.CString()); int bindingIndex = NumberPostfix(name); // If no number postfix in the name, use the block index if (bindingIndex < 0) bindingIndex = blockIndex; if (foundPs) bindingIndex += (unsigned)graphics->NumVSConstantBuffers(); glUniformBlockBinding(program, blockIndex, bindingIndex); } return true; }