Explorar el Código

OpenGL arbitrary vertex attributes. Change GLSL attribute indexing to zero-based to match HLSL, so the second texcoord (lightmaps, billboard sizes) is now called texCoord1, and instancing texcoords are texCoord4,5,6.

Lasse Öörni hace 9 años
padre
commit
8215c42e37

+ 4 - 2
Source/Urho3D/Graphics/GraphicsDefs.h

@@ -154,7 +154,8 @@ enum VertexElementType
     TYPE_VECTOR3,
     TYPE_VECTOR4,
     TYPE_UBYTE4,
-    TYPE_UBYTE4_NORM
+    TYPE_UBYTE4_NORM,
+    MAX_VERTEX_ELEMENT_TYPES
 };
 
 /// Arbitrary vertex declaration element semantics.
@@ -168,7 +169,8 @@ enum VertexElementSemantic
     SEM_COLOR,
     SEM_BLENDWEIGHTS,
     SEM_BLENDINDICES,
-    SEM_OBJECTINDEX
+    SEM_OBJECTINDEX,
+    MAX_VERTEX_ELEMENT_SEMANTICS
 };
 
 /// Vertex element description for arbitrary vertex declarations.

+ 142 - 107
Source/Urho3D/Graphics/OpenGL/OGLGraphics.cpp

@@ -163,11 +163,26 @@ static const unsigned glStencilOps[] =
 };
 #endif
 
-// Remap vertex attributes on OpenGL so that all usually needed attributes including skinning fit to the first 8.
-// This avoids a skinning bug on GLES2 devices which only support 8.
-static const unsigned glVertexAttrIndex[] =
+static const unsigned glElementTypes[] =
+{
+    GL_INT,
+    GL_FLOAT,
+    GL_FLOAT,
+    GL_FLOAT,
+    GL_FLOAT,
+    GL_UNSIGNED_BYTE,
+    GL_UNSIGNED_BYTE
+};
+
+static const unsigned glElementComponents[] =
 {
-    0, 1, 2, 3, 4, 8, 9, 5, 6, 7, 10, 11, 12, 13
+    1,
+    1,
+    2,
+    3,
+    4,
+    4,
+    4
 };
 
 #ifdef GL_ES_VERSION_2_0
@@ -845,114 +860,42 @@ void Graphics::SetVertexBuffer(VertexBuffer* buffer)
 {
     // Note: this is not multi-instance safe
     static PODVector<VertexBuffer*> vertexBuffers(1);
-    static PODVector<unsigned> elementMasks(1);
     vertexBuffers[0] = buffer;
-    elementMasks[0] = MASK_DEFAULT;
-    SetVertexBuffers(vertexBuffers, elementMasks);
+    SetVertexBuffers(vertexBuffers);
 }
 
-bool Graphics::SetVertexBuffers(const PODVector<VertexBuffer*>& buffers, const PODVector<unsigned>& elementMasks,
-    unsigned instanceOffset)
+bool Graphics::SetVertexBuffers(const PODVector<VertexBuffer*>& buffers, unsigned instanceOffset)
 {
     if (buffers.Size() > MAX_VERTEX_STREAMS)
     {
         URHO3D_LOGERROR("Too many vertex buffers");
         return false;
     }
-    if (buffers.Size() != elementMasks.Size())
+
+    if (instanceOffset != lastInstanceOffset_)
     {
-        URHO3D_LOGERROR("Amount of element masks and vertex buffers does not match");
-        return false;
+        lastInstanceOffset_ = instanceOffset;
+        impl_->vertexBuffersDirty_ = true;
     }
 
-    bool changed = false;
-    unsigned newAttributes = 0;
-
     for (unsigned i = 0; i < MAX_VERTEX_STREAMS; ++i)
     {
         VertexBuffer* buffer = 0;
-        unsigned elementMask = 0;
-
-        if (i < buffers.Size() && buffers[i])
-        {
+        if (i < buffers.Size())
             buffer = buffers[i];
-            if (elementMasks[i] == MASK_DEFAULT)
-                elementMask = buffer->GetElementMask();
-            else
-                elementMask = buffer->GetElementMask() & elementMasks[i];
-        }
-
-        // If buffer and element mask have stayed the same, skip to the next buffer
-        if (buffer == vertexBuffers_[i] && elementMask == elementMasks_[i] && instanceOffset == lastInstanceOffset_ && !changed)
+        if (buffer != vertexBuffers_[i])
         {
-            newAttributes |= elementMask;
-            continue;
-        }
-
-        vertexBuffers_[i] = buffer;
-        elementMasks_[i] = elementMask;
-        changed = true;
-
-        // Beware buffers with missing OpenGL objects, as binding a zero buffer object means accessing CPU memory for vertex data,
-        // in which case the pointer will be invalid and cause a crash
-        if (!buffer || !buffer->GetGPUObject())
-            continue;
-
-        SetVBO(buffer->GetGPUObject());
-        unsigned vertexSize = buffer->GetVertexSize();
-
-        for (unsigned j = 0; j < MAX_VERTEX_ELEMENTS; ++j)
-        {
-            unsigned attrIndex = glVertexAttrIndex[j];
-            unsigned elementBit = (unsigned)(1 << j);
-
-            if (elementMask & elementBit)
-            {
-                newAttributes |= elementBit;
-
-                // Enable attribute if not enabled yet
-                if ((impl_->enabledAttributes_ & elementBit) == 0)
-                {
-                    glEnableVertexAttribArray(attrIndex);
-                    impl_->enabledAttributes_ |= elementBit;
-                }
-
-                // Set the attribute pointer. Add instance offset for the instance matrix pointers
-                unsigned offset = (j >= ELEMENT_INSTANCEMATRIX1 && j < ELEMENT_OBJECTINDEX) ? instanceOffset * vertexSize : 0;
-                glVertexAttribPointer(attrIndex, VertexBuffer::elementComponents[j], VertexBuffer::elementType[j],
-                    (GLboolean)VertexBuffer::elementNormalize[j], vertexSize,
-                    reinterpret_cast<const GLvoid*>(buffer->GetElementOffset((VertexElement)j) + offset));
-            }
+            vertexBuffers_[i] = buffer;
+            impl_->vertexBuffersDirty_ = true;
         }
     }
 
-    if (!changed)
-        return true;
-
-    lastInstanceOffset_ = instanceOffset;
-
-    // Now check which vertex attributes should be disabled
-    unsigned disableAttributes = impl_->enabledAttributes_ & (~newAttributes);
-    unsigned disableIndex = 0;
-
-    while (disableAttributes)
-    {
-        if (disableAttributes & 1)
-        {
-            glDisableVertexAttribArray(glVertexAttrIndex[disableIndex]);
-            impl_->enabledAttributes_ &= ~(1 << disableIndex);
-        }
-        disableAttributes >>= 1;
-        ++disableIndex;
-    }
-
     return true;
 }
 
-bool Graphics::SetVertexBuffers(const Vector<SharedPtr<VertexBuffer> >& buffers, const PODVector<unsigned>& elementMasks,
-    unsigned instanceOffset)
+bool Graphics::SetVertexBuffers(const Vector<SharedPtr<VertexBuffer> >& buffers, unsigned instanceOffset)
 {
-    return SetVertexBuffers(reinterpret_cast<const PODVector<VertexBuffer*>&>(buffers), elementMasks, instanceOffset);
+    return SetVertexBuffers(reinterpret_cast<const PODVector<VertexBuffer*>&>(buffers), instanceOffset);
 }
 
 void Graphics::SetIndexBuffer(IndexBuffer* buffer)
@@ -1088,6 +1031,19 @@ void Graphics::SetShaders(ShaderVariation* vs, ShaderVariation* ps)
     // Store shader combination if shader dumping in progress
     if (shaderPrecache_)
         shaderPrecache_->StoreShaders(vertexShader_, pixelShader_);
+
+    if (shaderProgram_)
+    {
+        impl_->usedVertexAttributes_ = shaderProgram_->GetUsedVertexAttributes();
+        impl_->vertexAttributes_ = &shaderProgram_->GetVertexAttributes();
+    }
+    else
+    {
+        impl_->usedVertexAttributes_ = 0;
+        impl_->vertexAttributes_ = 0;
+    }
+
+    impl_->vertexBuffersDirty_ = true;
 }
 
 void Graphics::SetShaderParameter(StringHash param, const float* data, unsigned count)
@@ -2776,10 +2732,6 @@ void Graphics::CheckFeatureSupport()
         sRGBSupport_ = true;
         sRGBWriteSupport_ = true;
 
-        glVertexAttribDivisor(ELEMENT_INSTANCEMATRIX1, 1);
-        glVertexAttribDivisor(ELEMENT_INSTANCEMATRIX2, 1);
-        glVertexAttribDivisor(ELEMENT_INSTANCEMATRIX3, 1);
-
         glGetIntegerv(GL_MAX_COLOR_ATTACHMENTS, &numSupportedRTs);
     }
     else
@@ -2790,14 +2742,6 @@ void Graphics::CheckFeatureSupport()
         sRGBSupport_ = GLEW_EXT_texture_sRGB != 0;
         sRGBWriteSupport_ = GLEW_EXT_framebuffer_sRGB != 0;
 
-        // Set up instancing divisors if supported
-        if (instancingSupport_)
-        {
-            glVertexAttribDivisorARB(ELEMENT_INSTANCEMATRIX1, 1);
-            glVertexAttribDivisorARB(ELEMENT_INSTANCEMATRIX2, 1);
-            glVertexAttribDivisorARB(ELEMENT_INSTANCEMATRIX3, 1);
-        }
-
         glGetIntegerv(GL_MAX_COLOR_ATTACHMENTS_EXT, &numSupportedRTs);
     }
 
@@ -2824,12 +2768,6 @@ void Graphics::CheckFeatureSupport()
     // Instancing is in core in WebGL 2, so the extension may not be present anymore. In WebGL 1, find https://www.khronos.org/registry/webgl/extensions/ANGLE_instanced_arrays/
     // TODO: In the distant future, this may break if WebGL 3 is introduced, so either improve the GL_VERSION parsing here, or keep track of which WebGL version we attempted to initialize.
     instancingSupport_ = (strstr((const char *)glGetString(GL_VERSION), "WebGL 2.") != 0) || CheckExtension("ANGLE_instanced_arrays");
-    if (instancingSupport_)
-    {
-        glVertexAttribDivisorANGLE(ELEMENT_INSTANCEMATRIX1, 1);
-        glVertexAttribDivisorANGLE(ELEMENT_INSTANCEMATRIX2, 1);
-        glVertexAttribDivisorANGLE(ELEMENT_INSTANCEMATRIX3, 1);
-    }
 #else
     dxtTextureSupport_ = CheckExtension("EXT_texture_compression_dxt1");
     etcTextureSupport_ = CheckExtension("OES_compressed_ETC1_RGB8_texture");
@@ -3084,6 +3022,86 @@ void Graphics::PrepareDraw()
         }
 #endif
     }
+
+    if (impl_->vertexBuffersDirty_)
+    {
+        // Go through currently bound vertex buffers and set the attribute pointers that are available & required
+        // Use reverse order so that elements from higher index buffers will override lower index buffers
+        unsigned assignedLocations = 0;
+
+        for (unsigned i = MAX_VERTEX_STREAMS - 1; i < MAX_VERTEX_STREAMS; --i)
+        {
+            if (!vertexBuffers_[i] || !impl_->vertexAttributes_)
+                continue;
+
+            VertexBuffer* buffer = vertexBuffers_[i];
+            const PODVector<VertexElement>& elements = buffer->GetElements();
+
+            for (PODVector<VertexElement>::ConstIterator j = elements.Begin(); j != elements.End(); ++j)
+            {
+                const VertexElement& element = *j;
+                HashMap<Pair<unsigned char, unsigned char>, unsigned>::ConstIterator k = 
+                    impl_->vertexAttributes_->Find(MakePair((unsigned char)element.semantic_, element.index_));
+
+                if (k != impl_->vertexAttributes_->End())
+                {
+                    unsigned location = k->second_;
+                    unsigned locationMask = 1 << location;
+                    if (assignedLocations & locationMask)
+                        continue; // Already assigned by higher index vertex buffer
+                    assignedLocations |= locationMask;
+
+                    // Enable attribute if not enabled yet
+                    if (!(impl_->enabledVertexAttributes_ & locationMask))
+                    {
+                        glEnableVertexAttribArray(location);
+                        impl_->enabledVertexAttributes_ |= locationMask;
+                    }
+
+                    // Enable/disable instancing divisor as necessary
+                    unsigned dataStart = element.offset_;
+                    if (element.perInstance_)
+                    {
+                        dataStart += lastInstanceOffset_ * buffer->GetVertexSize();
+                        if (!(impl_->instancingVertexAttributes_ & locationMask))
+                        {
+                            SetVertexAttribDivisor(location, 1);
+                            impl_->instancingVertexAttributes_ |= locationMask;
+                        }
+                    }
+                    else
+                    {
+                        if (impl_->instancingVertexAttributes_ & locationMask)
+                        {
+                            SetVertexAttribDivisor(location, 0);
+                            impl_->instancingVertexAttributes_ &= ~locationMask;
+                        }
+                    }
+
+                    SetVBO(buffer->GetGPUObject());
+                    glVertexAttribPointer(location, glElementComponents[element.type_], glElementTypes[element.type_],
+                        element.type_ == TYPE_UBYTE4_NORM ? GL_TRUE : GL_FALSE, (unsigned)buffer->GetVertexSize(),
+                        (const void *)dataStart);
+                }
+            }
+        }
+
+        // Finally disable unnecessary vertex attributes
+        unsigned disableVertexAttributes = impl_->enabledVertexAttributes_ & (~impl_->usedVertexAttributes_);
+        unsigned location = 0;
+        while (disableVertexAttributes)
+        {
+            if (disableVertexAttributes & 1)
+            {
+                glDisableVertexAttribArray(location);
+                impl_->enabledVertexAttributes_ &= ~(1 << location);
+            }
+            ++location;
+            disableVertexAttributes >>= 1;
+        }
+
+        impl_->vertexBuffersDirty_ = false;
+    }
 }
 
 void Graphics::CleanupFramebuffers()
@@ -3149,7 +3167,9 @@ void Graphics::ResetCachedState()
     useClipPlane_ = false;
     lastInstanceOffset_ = 0;
     impl_->activeTexture_ = 0;
-    impl_->enabledAttributes_ = 0;
+    impl_->enabledVertexAttributes_ = 0;
+    impl_->usedVertexAttributes_ = 0;
+    impl_->instancingVertexAttributes_ = 0;
     impl_->boundFBO_ = impl_->systemFBO_;
     impl_->boundVBO_ = 0;
     impl_->boundUBO_ = 0;
@@ -3293,6 +3313,21 @@ bool Graphics::CheckFramebuffer()
         return glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE;
 }
 
+void Graphics::SetVertexAttribDivisor(unsigned location, unsigned divisor)
+{
+#ifndef GL_ES_VERSION_2_0
+    if (gl3Support)
+        glVertexAttribDivisor(location, divisor);
+    else if (instancingSupport_)
+        glVertexAttribDivisorARB(location, divisor);
+#else
+#ifdef __EMSCRIPTEN__
+    if (instancingSupport_)
+        glVertexAttribDivisorANGLE(location, divisor);
+#endif
+#endif
+}
+
 void RegisterGraphicsLibrary(Context* context)
 {
     Animation::RegisterObject(context);

+ 4 - 2
Source/Urho3D/Graphics/OpenGL/OGLGraphics.h

@@ -133,10 +133,10 @@ public:
     void SetVertexBuffer(VertexBuffer* buffer);
     /// Set multiple vertex buffers.
     bool SetVertexBuffers
-        (const PODVector<VertexBuffer*>& buffers, const PODVector<unsigned>& elementMasks, unsigned instanceOffset = 0);
+        (const PODVector<VertexBuffer*>& buffers, unsigned instanceOffset = 0);
     /// Set multiple vertex buffers.
     bool SetVertexBuffers
-        (const Vector<SharedPtr<VertexBuffer> >& buffers, const PODVector<unsigned>& elementMasks, unsigned instanceOffset = 0);
+        (const Vector<SharedPtr<VertexBuffer> >& buffers, unsigned instanceOffset = 0);
     /// Set index buffer.
     void SetIndexBuffer(IndexBuffer* buffer);
     /// Set shaders.
@@ -545,6 +545,8 @@ private:
     void BindStencilAttachment(unsigned object, bool isRenderBuffer);
     /// Check FBO completeness using either extension or core functionality.
     bool CheckFramebuffer();
+    /// Set vertex attrib divisor. No-op if unsupported.
+    void SetVertexAttribDivisor(unsigned location, unsigned divisor);
 
     /// Mutex for accessing the GPU objects vector from several threads.
     Mutex gpuObjectMutex_;

+ 6 - 2
Source/Urho3D/Graphics/OpenGL/OGLGraphicsImpl.cpp

@@ -35,12 +35,16 @@ GraphicsImpl::GraphicsImpl() :
     context_(0),
     systemFBO_(0),
     activeTexture_(0),
-    enabledAttributes_(0),
+    enabledVertexAttributes_(0),
+    usedVertexAttributes_(0),
+    instancingVertexAttributes_(0),
+    vertexAttributes_(0),
     boundFBO_(0),
     boundVBO_(0),
     boundUBO_(0),
     pixelFormat_(0),
-    fboDirty_(false)
+    fboDirty_(false),
+    vertexBuffersDirty_(false)
 {
 }
 

+ 10 - 2
Source/Urho3D/Graphics/OpenGL/OGLGraphicsImpl.h

@@ -116,8 +116,14 @@ private:
     unsigned systemFBO_;
     /// Active texture unit.
     unsigned activeTexture_;
-    /// Vertex attributes in use.
-    unsigned enabledAttributes_;
+    /// Enabled vertex attributes bitmask.
+    unsigned enabledVertexAttributes_;
+    /// Vertex attributes bitmask used by the current shader program.
+    unsigned usedVertexAttributes_;
+    /// Vertex attribute instancing bitmask for keeping track of divisors.
+    unsigned instancingVertexAttributes_;
+    /// Current mapping of vertex attribute locations by semantic. The map is owned by the shader program, so care must be taken to switch a null shader program when it's destroyed.
+    const HashMap<Pair<unsigned char, unsigned char>, unsigned>* vertexAttributes_;
     /// Currently bound frame buffer object.
     unsigned boundFBO_;
     /// Currently bound vertex buffer object.
@@ -130,6 +136,8 @@ private:
     HashMap<unsigned long long, FrameBufferObject> frameBuffers_;
     /// Need FBO commit flag.
     bool fboDirty_;
+    /// Need vertex attribute pointer update flag.
+    bool vertexBuffersDirty_;
     /// sRGB write mode flag.
     bool sRGBWrite_;
 };

+ 79 - 58
Source/Urho3D/Graphics/OpenGL/OGLShaderProgram.cpp

@@ -34,7 +34,7 @@
 namespace Urho3D
 {
 
-const char* shaderParameterGroups[] = {
+static const char* shaderParameterGroups[] = {
     "frame",
     "camera",
     "zone",
@@ -44,6 +44,30 @@ const char* shaderParameterGroups[] = {
     "custom"
 };
 
+static const char* elementSemanticNames[] =
+{
+    "POS",
+    "NORMAL",
+    "BINORMAL",
+    "TANGENT",
+    "TEXCOORD",
+    "COLOR",
+    "BLENDWEIGHT",
+    "BLENDINDICES",
+    "OBJECTINDEX"
+};
+
+static unsigned NumberPostfix(const String& str)
+{
+    for (unsigned i = 0; i < str.Length(); ++i)
+    {
+        if (IsDigit(str[i]))
+            return ToUInt(str.CString() + i);
+    }
+
+    return M_MAX_UNSIGNED;
+}
+
 unsigned ShaderProgram::globalFrameNumber = 0;
 const void* ShaderProgram::globalParameterSources[MAX_SHADER_PARAMETER_GROUPS];
 
@@ -51,6 +75,7 @@ ShaderProgram::ShaderProgram(Graphics* graphics, ShaderVariation* vertexShader,
     GPUObject(graphics),
     vertexShader_(vertexShader),
     pixelShader_(pixelShader),
+    usedVertexAttributes_(0),
     frameNumber_(0)
 {
     for (unsigned i = 0; i < MAX_TEXTURE_UNITS; ++i)
@@ -92,6 +117,8 @@ void ShaderProgram::Release()
         object_ = 0;
         linkerOutput_.Clear();
         shaderParameters_.Clear();
+        vertexAttributes_.Clear();
+        usedVertexAttributes_ = 0;
 
         for (unsigned i = 0; i < MAX_TEXTURE_UNITS; ++i)
             useTextureUnit_[i] = false;
@@ -114,26 +141,6 @@ bool ShaderProgram::Link()
         return false;
     }
 
-    // Bind vertex attribute locations to ensure they are the same in all shaders
-    // Note: this is not the same order as in VertexBuffer, instead a remapping is used to ensure everything except cube texture
-    // coordinates fit to the first 8 for better GLES2 device compatibility
-    glBindAttribLocation(object_, 0, "iPos");
-    glBindAttribLocation(object_, 1, "iNormal");
-    glBindAttribLocation(object_, 2, "iColor");
-    glBindAttribLocation(object_, 3, "iTexCoord");
-    glBindAttribLocation(object_, 4, "iTexCoord2");
-    glBindAttribLocation(object_, 5, "iTangent");
-    glBindAttribLocation(object_, 6, "iBlendWeights");
-    glBindAttribLocation(object_, 7, "iBlendIndices");
-    glBindAttribLocation(object_, 8, "iCubeTexCoord");
-    glBindAttribLocation(object_, 9, "iCubeTexCoord2");
-#if !defined(GL_ES_VERSION_2_0) || defined(__EMSCRIPTEN__)
-    glBindAttribLocation(object_, 10, "iInstanceMatrix1");
-    glBindAttribLocation(object_, 11, "iInstanceMatrix2");
-    glBindAttribLocation(object_, 12, "iInstanceMatrix3");
-#endif
-    glBindAttribLocation(object_, 13, "iObjectIndex");
-
     glAttachShader(object_, vertexShader_->GetGPUObject());
     glAttachShader(object_, pixelShader_->GetGPUObject());
     glLinkProgram(object_);
@@ -155,12 +162,47 @@ bool ShaderProgram::Link()
     if (!object_)
         return false;
 
-    const int MAX_PARAMETER_NAME_LENGTH = 256;
-    char uniformName[MAX_PARAMETER_NAME_LENGTH];
-    int uniformCount;
+    const int MAX_NAME_LENGTH = 256;
+    char nameBuffer[MAX_NAME_LENGTH];
+    int attributeCount, uniformCount, elementCount, nameLength;
+    GLenum type;
 
     glUseProgram(object_);
-    glGetProgramiv(object_, GL_ACTIVE_UNIFORMS, &uniformCount);
+
+    // Check for vertex attributes
+    glGetProgramiv(object_, GL_ACTIVE_ATTRIBUTES, &attributeCount);
+    for (int i = 0; i < attributeCount; ++i)
+    {
+        glGetActiveAttrib(object_, 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(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.CString());
+        vertexAttributes_[MakePair((unsigned char)semantic, semanticIndex)] = location;
+        usedVertexAttributes_ |= (1 << location);
+    }
 
     // Check for constant buffers
 #ifndef GL_ES_VERSION_2_0
@@ -173,10 +215,9 @@ bool ShaderProgram::Link()
         glGetProgramiv(object_, GL_ACTIVE_UNIFORM_BLOCKS, &numUniformBlocks);
         for (int i = 0; i < numUniformBlocks; ++i)
         {
-            int nameLength;
-            glGetActiveUniformBlockName(object_, (GLuint)i, MAX_PARAMETER_NAME_LENGTH, &nameLength, uniformName);
+            glGetActiveUniformBlockName(object_, (GLuint)i, MAX_NAME_LENGTH, &nameLength, nameBuffer);
 
-            String name(uniformName, (unsigned)nameLength);
+            String name(nameBuffer, (unsigned)nameLength);
 
             unsigned blockIndex = glGetUniformBlockIndex(object_, name.CString());
             unsigned group = M_MAX_UNSIGNED;
@@ -193,16 +234,7 @@ bool ShaderProgram::Link()
 
             // If name is not recognized, search for a digit in the name and use that as the group index
             if (group == M_MAX_UNSIGNED)
-            {
-                for (unsigned j = 1; j < name.Length(); ++j)
-                {
-                    if (name[j] >= '0' && name[j] <= '5')
-                    {
-                        group = name[j] - '0';
-                        break;
-                    }
-                }
-            }
+                group = NumberPostfix(name);
 
             if (group >= MAX_SHADER_PARAMETER_GROUPS)
             {
@@ -232,16 +264,14 @@ bool ShaderProgram::Link()
 #endif
 
     // Check for shader parameters and texture units
+    glGetProgramiv(object_, GL_ACTIVE_UNIFORMS, &uniformCount);
     for (int i = 0; i < uniformCount; ++i)
     {
-        unsigned type;
-        int count;
-
-        glGetActiveUniform(object_, (GLuint)i, MAX_PARAMETER_NAME_LENGTH, 0, &count, &type, uniformName);
-        int location = glGetUniformLocation(object_, uniformName);
+        glGetActiveUniform(object_, (GLuint)i, MAX_NAME_LENGTH, 0, &elementCount, &type, nameBuffer);
+        int location = glGetUniformLocation(object_, nameBuffer);
 
         // Check for array index included in the name and strip it
-        String name(uniformName);
+        String name(nameBuffer);
         unsigned index = name.Find('[');
         if (index != String::NPOS)
         {
@@ -281,29 +311,20 @@ bool ShaderProgram::Link()
         else if (location >= 0 && name[0] == 's')
         {
             // Set the samplers here so that they do not have to be set later
-            int unit = graphics_->GetTextureUnit(name.Substring(1));
+            unsigned unit = graphics_->GetTextureUnit(name.Substring(1));
             if (unit >= MAX_TEXTURE_UNITS)
-            {
-                // If texture unit name is not recognized, search for a digit in the name and use that as the unit index
-                for (unsigned j = 1; j < name.Length(); ++j)
-                {
-                    if (name[j] >= '0' && name[j] <= '9')
-                    {
-                        unit = name[j] - '0';
-                        break;
-                    }
-                }
-            }
+                unit = NumberPostfix(name);
 
             if (unit < MAX_TEXTURE_UNITS)
             {
                 useTextureUnit_[unit] = true;
-                glUniform1iv(location, 1, &unit);
+                glUniform1iv(location, 1, reinterpret_cast<int*>(&unit));
             }
         }
     }
 
-    // Rehash the parameter map to ensure minimal load factor
+    // Rehash the parameter & vertex attributes maps to ensure minimal load factor
+    vertexAttributes_.Rehash(NextPowerOfTwo(vertexAttributes_.Size()));
     shaderParameters_.Rehash(NextPowerOfTwo(shaderParameters_.Size()));
 
     return true;

+ 10 - 0
Source/Urho3D/Graphics/OpenGL/OGLShaderProgram.h

@@ -84,6 +84,12 @@ public:
     /// Return linker output.
     const String& GetLinkerOutput() const { return linkerOutput_; }
 
+    /// Return semantic to vertex attributes location mappings used by the shader.
+    const HashMap<Pair<unsigned char, unsigned char>, unsigned>& GetVertexAttributes() const { return vertexAttributes_; }
+
+    /// Return attribute location use bitmask.
+    unsigned GetUsedVertexAttributes() const { return usedVertexAttributes_; }
+
     /// Return all constant buffers.
     const SharedPtr<ConstantBuffer>* GetConstantBuffers() const { return &constantBuffers_[0]; }
 
@@ -106,6 +112,10 @@ private:
     HashMap<StringHash, ShaderParameter> shaderParameters_;
     /// Texture unit use.
     bool useTextureUnit_[MAX_TEXTURE_UNITS];
+    /// Vertex attributes.
+    HashMap<Pair<unsigned char, unsigned char>, unsigned> vertexAttributes_;
+    /// Used vertex attribute location bitmask.
+    unsigned usedVertexAttributes_;
     /// Constant buffers by binding index.
     SharedPtr<ConstantBuffer> constantBuffers_[MAX_SHADER_PARAMETER_GROUPS * 2];
     /// Remembered shader parameter sources for individual uniform mode.

+ 85 - 90
Source/Urho3D/Graphics/OpenGL/OGLVertexBuffer.cpp

@@ -32,78 +32,6 @@
 namespace Urho3D
 {
 
-const unsigned VertexBuffer::elementSize[] =
-{
-    3 * sizeof(float), // Position
-    3 * sizeof(float), // Normal
-    4 * sizeof(unsigned char), // Color
-    2 * sizeof(float), // Texcoord1
-    2 * sizeof(float), // Texcoord2
-    3 * sizeof(float), // Cubetexcoord1
-    3 * sizeof(float), // Cubetexcoord2
-    4 * sizeof(float), // Tangent
-    4 * sizeof(float), // Blendweights
-    4 * sizeof(unsigned char), // Blendindices
-    4 * sizeof(float), // Instancematrix1
-    4 * sizeof(float), // Instancematrix2
-    4 * sizeof(float), // Instancematrix3
-    sizeof(int) // Object index
-};
-
-const unsigned VertexBuffer::elementType[] =
-{
-    GL_FLOAT, // Position
-    GL_FLOAT, // Normal
-    GL_UNSIGNED_BYTE, // Color
-    GL_FLOAT, // Texcoord1
-    GL_FLOAT, // Texcoord2
-    GL_FLOAT, // Cubetexcoord1
-    GL_FLOAT, // Cubetexcoord2
-    GL_FLOAT, // Tangent
-    GL_FLOAT, // Blendweights
-    GL_UNSIGNED_BYTE, // Blendindices
-    GL_FLOAT, // Instancematrix1
-    GL_FLOAT, // Instancematrix2
-    GL_FLOAT, // Instancematrix3
-    GL_INT // Object index
-};
-
-const unsigned VertexBuffer::elementComponents[] =
-{
-    3, // Position
-    3, // Normal
-    4, // Color
-    2, // Texcoord1
-    2, // Texcoord2
-    3, // Cubetexcoord1
-    3, // Cubetexcoord2
-    4, // Tangent
-    4, // Blendweights
-    4, // Blendindices
-    4, // Instancematrix1
-    4, // Instancematrix2
-    4, // Instancematrix3
-    1 // Object index
-};
-
-const unsigned VertexBuffer::elementNormalize[] =
-{
-    GL_FALSE, // Position
-    GL_FALSE, // Normal
-    GL_TRUE, // Color
-    GL_FALSE, // Texcoord1
-    GL_FALSE, // Texcoord2
-    GL_FALSE, // Cubetexcoord1
-    GL_FALSE, // Cubetexcoord2
-    GL_FALSE, // Tangent
-    GL_FALSE, // Blendweights
-    GL_FALSE, // Blendindices
-    GL_FALSE, // Instancematrix1
-    GL_FALSE, // Instancematrix2
-    GL_FALSE, // Instancematrix3
-    GL_FALSE // Object index
-};
-
 VertexBuffer::VertexBuffer(Context* context, bool forceHeadless) :
     Object(context),
     GPUObject(forceHeadless ? (Graphics*)0 : GetSubsystem<Graphics>()),
@@ -184,12 +112,17 @@ void VertexBuffer::SetShadowed(bool enable)
 }
 
 bool VertexBuffer::SetSize(unsigned vertexCount, unsigned elementMask, bool dynamic)
+{
+    return SetSize(vertexCount, GetElements(elementMask), dynamic);
+}
+
+bool VertexBuffer::SetSize(unsigned vertexCount, const PODVector<VertexElement>& elements, bool dynamic)
 {
     Unlock();
 
-    dynamic_ = dynamic;
     vertexCount_ = vertexCount;
-    elementMask_ = elementMask;
+    elements_ = elements;
+    dynamic_ = dynamic;
 
     UpdateOffsets();
 
@@ -351,43 +284,105 @@ void VertexBuffer::Unlock()
 void VertexBuffer::UpdateOffsets()
 {
     unsigned elementOffset = 0;
-    for (unsigned i = 0; i < MAX_VERTEX_ELEMENTS; ++i)
+    elementHash_ = 0;
+    elementMask_ = 0;
+
+    for (PODVector<VertexElement>::Iterator i = elements_.Begin(); i != elements_.End(); ++i)
     {
-        if (elementMask_ & (1 << i))
+        i->offset_ = elementOffset;
+        elementOffset += ELEMENT_TYPESIZES[i->type_];
+        elementHash_ <<= 6;
+        elementHash_ += (((int)i->type_ + 1) * ((int)i->semantic_ + 1) + i->index_);
+
+        for (unsigned j = 0; j < MAX_LEGACY_VERTEX_ELEMENTS; ++j)
         {
-            elementOffset_[i] = elementOffset;
-            elementOffset += elementSize[i];
+            const VertexElement& legacy = LEGACY_VERTEXELEMENTS[j];
+            if (i->type_ == legacy.type_ && i->semantic_ == legacy.semantic_ && i->index_ == legacy.index_)
+                elementMask_ |= (1 << j);
         }
-        else
-            elementOffset_[i] = NO_ELEMENT;
     }
+
     vertexSize_ = elementOffset;
 }
 
-unsigned VertexBuffer::GetVertexSize(unsigned elementMask)
+const VertexElement* VertexBuffer::GetElement(VertexElementSemantic semantic, unsigned char index) const
 {
-    unsigned vertexSize = 0;
+    for (PODVector<VertexElement>::ConstIterator i = elements_.Begin(); i != elements_.End(); ++i)
+    {
+        if (i->semantic_ == semantic && i->index_ == index)
+            return &(*i);
+    }
 
-    for (unsigned i = 0; i < MAX_VERTEX_ELEMENTS; ++i)
+    return 0;
+}
+
+const VertexElement* VertexBuffer::GetElement(VertexElementType type, VertexElementSemantic semantic, unsigned char index) const
+{
+    for (PODVector<VertexElement>::ConstIterator i = elements_.Begin(); i != elements_.End(); ++i)
+    {
+        if (i->type_ == type && i->semantic_ == semantic && i->index_ == index)
+            return &(*i);
+    }
+
+    return 0;
+}
+
+const VertexElement* VertexBuffer::GetElement(const PODVector<VertexElement>& elements, VertexElementType type, VertexElementSemantic semantic, unsigned char index)
+{
+    for (PODVector<VertexElement>::ConstIterator i = elements.Begin(); i != elements.End(); ++i)
+    {
+        if (i->type_ == type && i->semantic_ == semantic && i->index_ == index)
+            return &(*i);
+    }
+
+    return 0;
+}
+
+bool VertexBuffer::HasElement(const PODVector<VertexElement>& elements, VertexElementType type, VertexElementSemantic semantic, unsigned char index)
+{
+    return GetElement(elements, type, semantic, index) != 0;
+}
+
+unsigned VertexBuffer::GetElementOffset(const PODVector<VertexElement>& elements, VertexElementType type, VertexElementSemantic semantic, unsigned char index)
+{
+    const VertexElement* element = GetElement(elements, type, semantic, index);
+    return element ? element->offset_ : M_MAX_UNSIGNED;
+}
+
+PODVector<VertexElement> VertexBuffer::GetElements(unsigned elementMask)
+{
+    PODVector<VertexElement> ret;
+
+    for (unsigned i = 0; i < MAX_LEGACY_VERTEX_ELEMENTS; ++i)
     {
         if (elementMask & (1 << i))
-            vertexSize += elementSize[i];
+            ret.Push(LEGACY_VERTEXELEMENTS[i]);
     }
 
-    return vertexSize;
+    return ret;
 }
 
-unsigned VertexBuffer::GetElementOffset(unsigned elementMask, VertexElement element)
+unsigned VertexBuffer::GetVertexSize(const PODVector<VertexElement>& elements)
+{
+    unsigned size = 0;
+
+    for (unsigned i = 0; i < elements.Size(); ++i)
+        size += ELEMENT_TYPESIZES[elements[i].type_];
+
+    return size;
+}
+
+unsigned VertexBuffer::GetVertexSize(unsigned elementMask)
 {
-    unsigned offset = 0;
+    unsigned size = 0;
 
-    for (unsigned i = 0; i != element; ++i)
+    for (unsigned i = 0; i < MAX_LEGACY_VERTEX_ELEMENTS; ++i)
     {
         if (elementMask & (1 << i))
-            offset += elementSize[i];
+            size += ELEMENT_TYPESIZES[LEGACY_VERTEXELEMENTS[i].type_];
     }
 
-    return offset;
+    return size;
 }
 
 bool VertexBuffer::Create()

+ 49 - 20
Source/Urho3D/Graphics/OpenGL/OGLVertexBuffer.h

@@ -47,7 +47,9 @@ public:
 
     /// Enable shadowing in CPU memory. Shadowing is forced on if the graphics subsystem does not exist.
     void SetShadowed(bool enable);
-    /// Set size and vertex elements and dynamic mode. Previous data will be lost.
+    /// Set size, vertex elements and dynamic mode. Previous data will be lost.
+    bool SetSize(unsigned vertexCount, const PODVector<VertexElement>& elements, bool dynamic = false);
+    /// Set size and vertex elements and dynamic mode using legacy element bitmask. Previous data will be lost.
     bool SetSize(unsigned vertexCount, unsigned elementMask, bool dynamic = false);
     /// Set all data in the buffer.
     bool SetData(const void* data);
@@ -73,11 +75,32 @@ public:
     /// Return vertex size.
     unsigned GetVertexSize() const { return vertexSize_; }
 
-    /// Return bitmask of vertex elements.
-    unsigned GetElementMask() const { return elementMask_; }
+    /// Return vertex elements.
+    const PODVector<VertexElement>& GetElements() const { return elements_; }
+
+    /// Return vertex element, or null if does not exist.
+    const VertexElement* GetElement(VertexElementSemantic semantic, unsigned char index = 0) const;
+
+    /// Return vertex element with specific type, or null if does not exist.
+    const VertexElement* GetElement(VertexElementType type, VertexElementSemantic semantic, unsigned char index = 0) const;
+
+    /// Return whether has a specified element semantic.
+    bool HasElement(VertexElementSemantic semantic, unsigned char index = 0) const { return GetElement(semantic, index) != 0; }
+
+    /// Return whether has an element semantic with specific type.
+    bool HasElement(VertexElementType type, VertexElementSemantic semantic, unsigned char index = 0) const { return GetElement(type, semantic, index) != 0; }
+
+    /// Return offset of a element within vertex, or M_MAX_UNSIGNED if does not exist.
+    unsigned GetElementOffset(VertexElementSemantic semantic, unsigned char index = 0) const { const VertexElement* element = GetElement(semantic, index); return element ? element->offset_ : M_MAX_UNSIGNED; }
+
+    /// Return offset of a element with specific type within vertex, or M_MAX_UNSIGNED if element does not exist.
+    unsigned GetElementOffset(VertexElementType type, VertexElementSemantic semantic, unsigned char index = 0) const { const VertexElement* element = GetElement(type, semantic, index); return element ? element->offset_ : M_MAX_UNSIGNED; }
 
-    /// Return offset of a specified element within a vertex.
-    unsigned GetElementOffset(VertexElement element) const { return elementOffset_[element]; }
+    /// Return buffer hash for building vertex declarations.
+    unsigned long long GetBufferHash(unsigned streamIndex) { return elementHash_ << (streamIndex * 16); }
+
+    /// Return legacy vertex element mask.
+    unsigned GetElementMask() const { return elementMask_; }
 
     /// Return CPU memory shadow data.
     unsigned char* GetShadowData() const { return shadowData_.Get(); }
@@ -85,19 +108,23 @@ public:
     /// Return shared array pointer to the CPU memory shadow data.
     SharedArrayPtr<unsigned char> GetShadowDataShared() const { return shadowData_; }
 
-    /// Return vertex size corresponding to a vertex element mask.
+    /// Return element with specified type and semantic from a vertex element list, or null if does not exist.
+    static const VertexElement* GetElement(const PODVector<VertexElement>& elements, VertexElementType type, VertexElementSemantic semantic, unsigned char index = 0);
+
+    /// Return whether element list has a specified element type and semantic.
+    static bool HasElement(const PODVector<VertexElement>& elements, VertexElementType type, VertexElementSemantic semantic, unsigned char index = 0);
+
+    /// Return element offset for specified type and semantic from a vertex element list, or M_MAX_UNSIGNED if does not exist.
+    static unsigned GetElementOffset(const PODVector<VertexElement>& elements, VertexElementType type, VertexElementSemantic semantic, unsigned char index = 0);
+
+    /// Return a vertex element list from a legacy element bitmask.
+    static PODVector<VertexElement> GetElements(unsigned elementMask);
+
+    /// Return vertex size from an element list.
+    static unsigned GetVertexSize(const PODVector<VertexElement>& elements);
+
+    /// Return vertex size for a legacy vertex element bitmask.
     static unsigned GetVertexSize(unsigned elementMask);
-    /// Return element offset from an element mask.
-    static unsigned GetElementOffset(unsigned elementMask, VertexElement element);
-
-    /// Vertex element sizes in bytes.
-    static const unsigned elementSize[];
-    /// Vertex element OpenGL types.
-    static const unsigned elementType[];
-    /// Vertex element OpenGL component counts.
-    static const unsigned elementComponents[];
-    /// Vertex element OpenGL normalization.
-    static const unsigned elementNormalize[];
 
 private:
     /// Update offsets of vertex elements.
@@ -113,10 +140,12 @@ private:
     unsigned vertexCount_;
     /// Vertex size.
     unsigned vertexSize_;
-    /// Vertex element bitmask.
+    /// Vertex elements.
+    PODVector<VertexElement> elements_;
+    /// Vertex element hash.
+    unsigned long long elementHash_;
+    /// Vertex element legacy bitmask.
     unsigned elementMask_;
-    /// Vertex element offsets.
-    unsigned elementOffset_[MAX_VERTEX_ELEMENTS];
     /// Buffer locking state.
     LockState lockState_;
     /// Lock start vertex.

+ 1 - 1
bin/CoreData/Shaders/GLSL/LitSolid.glsl

@@ -82,7 +82,7 @@ void VS()
             // If using lightmap, disregard zone ambient light
             // If using AO, calculate ambient in the PS
             vVertexLight = vec3(0.0, 0.0, 0.0);
-            vTexCoord2 = iTexCoord2;
+            vTexCoord2 = iTexCoord1;
         #else
             vVertexLight = GetAmbient(GetZonePos(worldPos));
         #endif

+ 1 - 1
bin/CoreData/Shaders/GLSL/TerrainBlend.glsl

@@ -81,7 +81,7 @@ void VS()
             // If using lightmap, disregard zone ambient light
             // If using AO, calculate ambient in the PS
             vVertexLight = vec3(0.0, 0.0, 0.0);
-            vTexCoord2 = iTexCoord2;
+            vTexCoord2 = iTexCoord1;
         #else
             vVertexLight = GetAmbient(GetZonePos(worldPos));
         #endif

+ 9 - 9
bin/CoreData/Shaders/GLSL/Transform.glsl

@@ -10,16 +10,16 @@ attribute vec4 iPos;
 attribute vec3 iNormal;
 attribute vec4 iColor;
 attribute vec2 iTexCoord;
-attribute vec2 iTexCoord2;
+attribute vec2 iTexCoord1;
 attribute vec4 iTangent;
 attribute vec4 iBlendWeights;
 attribute vec4 iBlendIndices;
 attribute vec3 iCubeTexCoord;
-attribute vec4 iCubeTexCoord2;
+attribute vec4 iCubeTexCoord1;
 #ifdef INSTANCED
-    attribute vec4 iInstanceMatrix1;
-    attribute vec4 iInstanceMatrix2;
-    attribute vec4 iInstanceMatrix3;
+    attribute vec4 iTexCoord4;
+    attribute vec4 iTexCoord5;
+    attribute vec4 iTexCoord6;
 #endif
 attribute float iObjectIndex;
 
@@ -39,7 +39,7 @@ mat4 GetSkinMatrix(vec4 blendWeights, vec4 blendIndices)
 mat4 GetInstanceMatrix()
 {
     const vec4 lastColumn = vec4(0.0, 0.0, 0.0, 1.0);
-    return mat4(iInstanceMatrix1, iInstanceMatrix2, iInstanceMatrix3, lastColumn);
+    return mat4(iTexCoord4, iTexCoord5, iTexCoord6, lastColumn);
 }
 #endif
 
@@ -104,8 +104,8 @@ mat3 GetFaceCameraRotation(vec3 cameraPos, vec3 position, vec3 direction)
 
 vec3 GetBillboardPos(vec4 iPos, vec3 iDirection, vec3 iCameraPos, mat4 modelMatrix)
 {
-    return (iPos * modelMatrix).xyz + 
-        vec3(iTexCoord2.x, 0.0, iTexCoord2.y) * GetFaceCameraRotation(iCameraPos, iPos.xyz, iDirection);
+    return (iPos * modelMatrix).xyz +
+        vec3(iTexCoord1.x, 0.0, iTexCoord1.y) * GetFaceCameraRotation(iCameraPos, iPos.xyz, iDirection);
 }
 
 vec3 GetBillboardNormal(vec4 iPos, vec3 iDirection, vec3 iCameraPos)
@@ -125,7 +125,7 @@ vec3 GetBillboardNormal(vec4 iPos, vec3 iDirection, vec3 iCameraPos)
 vec3 GetWorldPos(mat4 modelMatrix)
 {
     #if defined(BILLBOARD)
-        return GetBillboardPos(iPos, iTexCoord2, modelMatrix);
+        return GetBillboardPos(iPos, iTexCoord1, modelMatrix);
     #elif defined(DIRBILLBOARD)
         return GetBillboardPos(iPos, iNormal, iTangent.xyz, modelMatrix);
     #else

+ 1 - 1
bin/CoreData/Shaders/GLSL/Vegetation.glsl

@@ -92,7 +92,7 @@ void VS()
             // If using lightmap, disregard zone ambient light
             // If using AO, calculate ambient in the PS
             vVertexLight = vec3(0.0, 0.0, 0.0);
-            vTexCoord2 = iTexCoord2;
+            vTexCoord2 = iTexCoord1;
         #else
             vVertexLight = GetAmbient(GetZonePos(worldPos));
         #endif