D3D11: Futher optimize input layout cache.
*re-land with fix for matrix attributes*
*re-re-land with fix for attributes with BindAttribLocation*
Using the new vertex format type enum, we can shrink the size
of the input layout tables and reduce draw call overhead
further.
BUG=angleproject:959
Change-Id: I181acd3d7d519f5587cbe180fb1bca8530b7cfc2
Reviewed-on: https://chromium-review.googlesource.com/285348
Reviewed-by: Geoff Lang <geofflang@chromium.org>
Tested-by: Jamie Madill <jmadill@chromium.org>
diff --git a/src/libANGLE/Program.h b/src/libANGLE/Program.h
index 17baa0d..949b61f 100644
--- a/src/libANGLE/Program.h
+++ b/src/libANGLE/Program.h
@@ -195,6 +195,7 @@
void getActiveAttribute(GLuint index, GLsizei bufsize, GLsizei *length, GLint *size, GLenum *type, GLchar *name);
GLint getActiveAttributeCount();
GLint getActiveAttributeMaxLength();
+ const sh::Attribute *getLinkedAttributes() const { return mLinkedAttribute; }
GLint getSamplerMapping(SamplerType type, unsigned int samplerIndex, const Caps &caps);
GLenum getSamplerTextureType(SamplerType type, unsigned int samplerIndex);
diff --git a/src/libANGLE/formatutils.cpp b/src/libANGLE/formatutils.cpp
index 506431e..b731dad 100644
--- a/src/libANGLE/formatutils.cpp
+++ b/src/libANGLE/formatutils.cpp
@@ -647,6 +647,58 @@
return formatSet;
}
+AttributeType GetAttributeType(GLenum enumValue)
+{
+ switch (enumValue)
+ {
+ case GL_FLOAT:
+ return ATTRIBUTE_FLOAT;
+ case GL_FLOAT_VEC2:
+ return ATTRIBUTE_VEC2;
+ case GL_FLOAT_VEC3:
+ return ATTRIBUTE_VEC3;
+ case GL_FLOAT_VEC4:
+ return ATTRIBUTE_VEC4;
+ case GL_INT:
+ return ATTRIBUTE_INT;
+ case GL_INT_VEC2:
+ return ATTRIBUTE_IVEC2;
+ case GL_INT_VEC3:
+ return ATTRIBUTE_IVEC3;
+ case GL_INT_VEC4:
+ return ATTRIBUTE_IVEC4;
+ case GL_UNSIGNED_INT:
+ return ATTRIBUTE_UINT;
+ case GL_UNSIGNED_INT_VEC2:
+ return ATTRIBUTE_UVEC2;
+ case GL_UNSIGNED_INT_VEC3:
+ return ATTRIBUTE_UVEC3;
+ case GL_UNSIGNED_INT_VEC4:
+ return ATTRIBUTE_UVEC4;
+ case GL_FLOAT_MAT2:
+ return ATTRIBUTE_MAT2;
+ case GL_FLOAT_MAT3:
+ return ATTRIBUTE_MAT3;
+ case GL_FLOAT_MAT4:
+ return ATTRIBUTE_MAT4;
+ case GL_FLOAT_MAT2x3:
+ return ATTRIBUTE_MAT2x3;
+ case GL_FLOAT_MAT2x4:
+ return ATTRIBUTE_MAT2x4;
+ case GL_FLOAT_MAT3x2:
+ return ATTRIBUTE_MAT3x2;
+ case GL_FLOAT_MAT3x4:
+ return ATTRIBUTE_MAT3x4;
+ case GL_FLOAT_MAT4x2:
+ return ATTRIBUTE_MAT4x2;
+ case GL_FLOAT_MAT4x3:
+ return ATTRIBUTE_MAT4x3;
+ default:
+ UNREACHABLE();
+ return ATTRIBUTE_FLOAT;
+ }
+}
+
VertexFormatType GetVertexFormatType(GLenum type, GLboolean normalized, GLuint components, bool pureInteger)
{
switch (type)
diff --git a/src/libANGLE/formatutils.h b/src/libANGLE/formatutils.h
index a3165af..92a27b1 100644
--- a/src/libANGLE/formatutils.h
+++ b/src/libANGLE/formatutils.h
@@ -76,6 +76,37 @@
typedef std::set<GLenum> FormatSet;
const FormatSet &GetAllSizedInternalFormats();
+// From the ESSL 3.00.4 spec:
+// Vertex shader inputs can only be float, floating-point vectors, matrices, signed and unsigned
+// integers and integer vectors. Vertex shader inputs cannot be arrays or structures.
+
+enum AttributeType
+{
+ ATTRIBUTE_FLOAT,
+ ATTRIBUTE_VEC2,
+ ATTRIBUTE_VEC3,
+ ATTRIBUTE_VEC4,
+ ATTRIBUTE_INT,
+ ATTRIBUTE_IVEC2,
+ ATTRIBUTE_IVEC3,
+ ATTRIBUTE_IVEC4,
+ ATTRIBUTE_UINT,
+ ATTRIBUTE_UVEC2,
+ ATTRIBUTE_UVEC3,
+ ATTRIBUTE_UVEC4,
+ ATTRIBUTE_MAT2,
+ ATTRIBUTE_MAT3,
+ ATTRIBUTE_MAT4,
+ ATTRIBUTE_MAT2x3,
+ ATTRIBUTE_MAT2x4,
+ ATTRIBUTE_MAT3x2,
+ ATTRIBUTE_MAT3x4,
+ ATTRIBUTE_MAT4x2,
+ ATTRIBUTE_MAT4x3,
+};
+
+AttributeType GetAttributeType(GLenum enumValue);
+
enum VertexFormatType
{
VERTEX_FORMAT_INVALID,
diff --git a/src/libANGLE/renderer/d3d/d3d11/InputLayoutCache.cpp b/src/libANGLE/renderer/d3d/d3d11/InputLayoutCache.cpp
index 75b7bab..e54e5f8 100644
--- a/src/libANGLE/renderer/d3d/d3d11/InputLayoutCache.cpp
+++ b/src/libANGLE/renderer/d3d/d3d11/InputLayoutCache.cpp
@@ -8,16 +8,17 @@
// D3D11 input layouts.
#include "libANGLE/renderer/d3d/d3d11/InputLayoutCache.h"
-#include "libANGLE/renderer/d3d/d3d11/VertexBuffer11.h"
-#include "libANGLE/renderer/d3d/d3d11/Buffer11.h"
-#include "libANGLE/renderer/d3d/d3d11/ShaderExecutable11.h"
-#include "libANGLE/renderer/d3d/d3d11/formatutils11.h"
-#include "libANGLE/renderer/d3d/ProgramD3D.h"
-#include "libANGLE/renderer/d3d/VertexDataManager.h"
-#include "libANGLE/renderer/d3d/IndexDataManager.h"
+
+#include "common/utilities.h"
#include "libANGLE/Program.h"
#include "libANGLE/VertexAttribute.h"
-
+#include "libANGLE/renderer/d3d/IndexDataManager.h"
+#include "libANGLE/renderer/d3d/ProgramD3D.h"
+#include "libANGLE/renderer/d3d/VertexDataManager.h"
+#include "libANGLE/renderer/d3d/d3d11/Buffer11.h"
+#include "libANGLE/renderer/d3d/d3d11/ShaderExecutable11.h"
+#include "libANGLE/renderer/d3d/d3d11/VertexBuffer11.h"
+#include "libANGLE/renderer/d3d/d3d11/formatutils11.h"
#include "third_party/murmurhash/MurmurHash3.h"
namespace rx
@@ -48,40 +49,80 @@
}
}
+GLenum GetNextGLSLAttributeType(const sh::Attribute *linkedAttributes, int index)
+{
+ // Count matrices differently
+ int subIndex = 0;
+ for (int attribIndex = 0; attribIndex < gl::MAX_VERTEX_ATTRIBS; ++attribIndex)
+ {
+ GLenum attribType = linkedAttributes[attribIndex].type;
+
+ if (attribType == GL_NONE)
+ {
+ continue;
+ }
+
+ GLenum transposedType = gl::TransposeMatrixType(attribType);
+ subIndex += gl::VariableRowCount(transposedType);
+ if (subIndex > index)
+ {
+ return transposedType;
+ }
+ }
+
+ UNREACHABLE();
+ return GL_NONE;
+}
+
const unsigned int kDefaultCacheSize = 1024;
+struct PackedAttribute
+{
+ uint8_t attribType;
+ uint8_t semanticIndex;
+ uint8_t vertexFormatType;
+ uint8_t divisor;
+};
+
} // anonymous namespace
-bool InputLayoutCache::PackedAttributeComparator::operator()(const PackedAttributeLayout &a,
- const PackedAttributeLayout &b) const
+void InputLayoutCache::PackedAttributeLayout::addAttributeData(
+ GLenum glType,
+ UINT semanticIndex,
+ gl::VertexFormatType vertexFormatType,
+ unsigned int divisor)
{
- if (a.numAttributes != b.numAttributes)
+ gl::AttributeType attribType = gl::GetAttributeType(glType);
+
+ PackedAttribute packedAttrib;
+ packedAttrib.attribType = static_cast<uint8_t>(attribType);
+ packedAttrib.semanticIndex = static_cast<uint8_t>(semanticIndex);
+ packedAttrib.vertexFormatType = static_cast<uint8_t>(vertexFormatType);
+ packedAttrib.divisor = static_cast<uint8_t>(divisor);
+
+ ASSERT(static_cast<gl::AttributeType>(packedAttrib.attribType) == attribType);
+ ASSERT(static_cast<UINT>(packedAttrib.semanticIndex) == semanticIndex);
+ ASSERT(static_cast<gl::VertexFormatType>(packedAttrib.vertexFormatType) == vertexFormatType);
+ ASSERT(static_cast<unsigned int>(packedAttrib.divisor) == divisor);
+
+ static_assert(sizeof(uint32_t) == sizeof(PackedAttribute), "PackedAttributes must be 32-bits exactly.");
+
+ attributeData[numAttributes++] = gl::bitCast<uint32_t>(packedAttrib);
+}
+
+bool InputLayoutCache::PackedAttributeLayout::operator<(const PackedAttributeLayout &other) const
+{
+ if (numAttributes != other.numAttributes)
{
- return a.numAttributes < b.numAttributes;
+ return numAttributes < other.numAttributes;
}
- if (a.flags != b.flags)
+ if (flags != other.flags)
{
- return a.flags < b.flags;
+ return flags < other.flags;
}
- for (size_t attribIndex = 0; attribIndex < a.numAttributes; attribIndex++)
- {
- const auto &attribA = a.attributeData[attribIndex];
- const auto &attribB = b.attributeData[attribIndex];
-
- if (attribA.glType != attribB.glType)
- return attribA.glType < attribB.glType;
- if (attribA.semanticIndex != attribB.semanticIndex)
- return attribA.semanticIndex < attribB.semanticIndex;
- if (attribA.dxgiFormat != attribB.dxgiFormat)
- return attribA.dxgiFormat < attribB.dxgiFormat;
- if (attribA.divisor != attribB.divisor)
- return attribA.divisor < attribB.divisor;
- }
-
- // Equal
- return false;
+ return memcmp(attributeData, other.attributeData, sizeof(uint32_t) * numAttributes) < 0;
}
InputLayoutCache::InputLayoutCache()
@@ -154,9 +195,8 @@
return gl::Error(GL_OUT_OF_MEMORY, "Internal input layout cache is not initialized.");
}
- InputLayoutKey ilKey;
- ilKey.elementCount = 0;
-
+ unsigned int inputElementCount = 0;
+ D3D11_INPUT_ELEMENT_DESC inputElements[gl::MAX_VERTEX_ATTRIBS];
PackedAttributeLayout layout;
static const char* semanticName = "TEXCOORD";
@@ -165,6 +205,8 @@
unsigned int firstInstancedElement = gl::MAX_VERTEX_ATTRIBS;
unsigned int nextAvailableInputSlot = 0;
+ const sh::Attribute *linkedAttributes = program->getLinkedAttributes();
+
for (unsigned int i = 0; i < unsortedAttributes.size(); i++)
{
if (sortedAttributes[i]->active)
@@ -176,35 +218,34 @@
gl::VertexFormatType vertexFormatType = gl::GetVertexFormatType(*sortedAttributes[i]->attribute, sortedAttributes[i]->currentValueType);
const d3d11::VertexFormat &vertexFormatInfo = d3d11::GetVertexFormatInfo(vertexFormatType, mFeatureLevel);
- // Record the type of the associated vertex shader vector in our key
- // This will prevent mismatched vertex shaders from using the same input layout
- GLint attributeSize;
- program->getActiveAttribute(ilKey.elementCount, 0, NULL, &attributeSize, &ilKey.elements[ilKey.elementCount].glslElementType, NULL);
-
- ilKey.elements[ilKey.elementCount].desc.SemanticName = semanticName;
- ilKey.elements[ilKey.elementCount].desc.SemanticIndex = sortedSemanticIndices[i];
- ilKey.elements[ilKey.elementCount].desc.Format = vertexFormatInfo.nativeFormat;
- ilKey.elements[ilKey.elementCount].desc.InputSlot = i;
- ilKey.elements[ilKey.elementCount].desc.AlignedByteOffset = 0;
- ilKey.elements[ilKey.elementCount].desc.InputSlotClass = inputClass;
- ilKey.elements[ilKey.elementCount].desc.InstanceDataStepRate = instancedPointSpritesActive ? 1 : sortedAttributes[i]->divisor;
+ inputElements[inputElementCount].SemanticName = semanticName;
+ inputElements[inputElementCount].SemanticIndex = sortedSemanticIndices[i];
+ inputElements[inputElementCount].Format = vertexFormatInfo.nativeFormat;
+ inputElements[inputElementCount].InputSlot = i;
+ inputElements[inputElementCount].AlignedByteOffset = 0;
+ inputElements[inputElementCount].InputSlotClass = inputClass;
+ inputElements[inputElementCount].InstanceDataStepRate = instancedPointSpritesActive ? 1 : sortedAttributes[i]->divisor;
if (inputClass == D3D11_INPUT_PER_VERTEX_DATA && firstIndexedElement == gl::MAX_VERTEX_ATTRIBS)
{
- firstIndexedElement = ilKey.elementCount;
+ firstIndexedElement = inputElementCount;
}
else if (inputClass == D3D11_INPUT_PER_INSTANCE_DATA && firstInstancedElement == gl::MAX_VERTEX_ATTRIBS)
{
- firstInstancedElement = ilKey.elementCount;
+ firstInstancedElement = inputElementCount;
}
- ilKey.elementCount++;
- nextAvailableInputSlot = i + 1;
+ // Record the type of the associated vertex shader vector in our key
+ // This will prevent mismatched vertex shaders from using the same input layout
+ GLenum glslElementType = GetNextGLSLAttributeType(linkedAttributes, inputElementCount);
- layout.addAttributeData(ilKey.elements[ilKey.elementCount].glslElementType,
+ layout.addAttributeData(glslElementType,
sortedSemanticIndices[i],
- vertexFormatInfo.nativeFormat,
+ vertexFormatType,
sortedAttributes[i]->divisor);
+
+ inputElementCount++;
+ nextAvailableInputSlot = i + 1;
}
}
@@ -213,33 +254,33 @@
// We do this even if mode != GL_POINTS, since the shader signature has these inputs, and the input layout must match the shader
if (programUsesInstancedPointSprites)
{
- ilKey.elements[ilKey.elementCount].desc.SemanticName = "SPRITEPOSITION";
- ilKey.elements[ilKey.elementCount].desc.SemanticIndex = 0;
- ilKey.elements[ilKey.elementCount].desc.Format = DXGI_FORMAT_R32G32B32_FLOAT;
- ilKey.elements[ilKey.elementCount].desc.InputSlot = nextAvailableInputSlot;
- ilKey.elements[ilKey.elementCount].desc.AlignedByteOffset = 0;
- ilKey.elements[ilKey.elementCount].desc.InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
- ilKey.elements[ilKey.elementCount].desc.InstanceDataStepRate = 0;
+ inputElements[inputElementCount].SemanticName = "SPRITEPOSITION";
+ inputElements[inputElementCount].SemanticIndex = 0;
+ inputElements[inputElementCount].Format = DXGI_FORMAT_R32G32B32_FLOAT;
+ inputElements[inputElementCount].InputSlot = nextAvailableInputSlot;
+ inputElements[inputElementCount].AlignedByteOffset = 0;
+ inputElements[inputElementCount].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
+ inputElements[inputElementCount].InstanceDataStepRate = 0;
// The new elements are D3D11_INPUT_PER_VERTEX_DATA data so the indexed element
// tracking must be applied. This ensures that the instancing specific
// buffer swapping logic continues to work.
if (firstIndexedElement == gl::MAX_VERTEX_ATTRIBS)
{
- firstIndexedElement = ilKey.elementCount;
+ firstIndexedElement = inputElementCount;
}
- ilKey.elementCount++;
+ inputElementCount++;
- ilKey.elements[ilKey.elementCount].desc.SemanticName = "SPRITETEXCOORD";
- ilKey.elements[ilKey.elementCount].desc.SemanticIndex = 0;
- ilKey.elements[ilKey.elementCount].desc.Format = DXGI_FORMAT_R32G32_FLOAT;
- ilKey.elements[ilKey.elementCount].desc.InputSlot = nextAvailableInputSlot;
- ilKey.elements[ilKey.elementCount].desc.AlignedByteOffset = sizeof(float) * 3;
- ilKey.elements[ilKey.elementCount].desc.InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
- ilKey.elements[ilKey.elementCount].desc.InstanceDataStepRate = 0;
+ inputElements[inputElementCount].SemanticName = "SPRITETEXCOORD";
+ inputElements[inputElementCount].SemanticIndex = 0;
+ inputElements[inputElementCount].Format = DXGI_FORMAT_R32G32_FLOAT;
+ inputElements[inputElementCount].InputSlot = nextAvailableInputSlot;
+ inputElements[inputElementCount].AlignedByteOffset = sizeof(float) * 3;
+ inputElements[inputElementCount].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
+ inputElements[inputElementCount].InstanceDataStepRate = 0;
- ilKey.elementCount++;
+ inputElementCount++;
}
// On 9_3, we must ensure that slot 0 contains non-instanced data.
@@ -251,14 +292,14 @@
if (moveFirstIndexedIntoSlotZero)
{
- ilKey.elements[firstInstancedElement].desc.InputSlot = ilKey.elements[firstIndexedElement].desc.InputSlot;
- ilKey.elements[firstIndexedElement].desc.InputSlot = 0;
+ inputElements[firstInstancedElement].InputSlot = inputElements[firstIndexedElement].InputSlot;
+ inputElements[firstIndexedElement].InputSlot = 0;
// Instanced PointSprite emulation uses multiple layout entries across a single vertex buffer.
// If an index swap is performed, we need to ensure that all elements get the proper InputSlot.
if (programUsesInstancedPointSprites)
{
- ilKey.elements[firstIndexedElement + 1].desc.InputSlot = 0;
+ inputElements[firstIndexedElement + 1].InputSlot = 0;
}
}
@@ -299,12 +340,12 @@
ShaderExecutableD3D *shader11 = GetAs<ShaderExecutable11>(shader);
D3D11_INPUT_ELEMENT_DESC descs[gl::MAX_VERTEX_ATTRIBS];
- for (unsigned int j = 0; j < ilKey.elementCount; ++j)
+ for (unsigned int j = 0; j < inputElementCount; ++j)
{
- descs[j] = ilKey.elements[j].desc;
+ descs[j] = inputElements[j];
}
- HRESULT result = mDevice->CreateInputLayout(descs, ilKey.elementCount, shader11->getFunction(), shader11->getLength(), &inputLayout);
+ HRESULT result = mDevice->CreateInputLayout(descs, inputElementCount, shader11->getFunction(), shader11->getLength(), &inputLayout);
if (FAILED(result))
{
return gl::Error(GL_OUT_OF_MEMORY, "Failed to create internal input layout, HRESULT: 0x%08x", result);
diff --git a/src/libANGLE/renderer/d3d/d3d11/InputLayoutCache.h b/src/libANGLE/renderer/d3d/d3d11/InputLayoutCache.h
index 26def6f..4cc0ec9 100644
--- a/src/libANGLE/renderer/d3d/d3d11/InputLayoutCache.h
+++ b/src/libANGLE/renderer/d3d/d3d11/InputLayoutCache.h
@@ -10,16 +10,17 @@
#ifndef LIBANGLE_RENDERER_D3D_D3D11_INPUTLAYOUTCACHE_H_
#define LIBANGLE_RENDERER_D3D_D3D11_INPUTLAYOUTCACHE_H_
-#include "libANGLE/Constants.h"
-#include "libANGLE/Error.h"
-#include "common/angleutils.h"
-
#include <GLES2/gl2.h>
#include <cstddef>
#include <map>
#include <unordered_map>
+#include "common/angleutils.h"
+#include "libANGLE/Constants.h"
+#include "libANGLE/Error.h"
+#include "libANGLE/formatutils.h"
+
namespace gl
{
class Program;
@@ -48,28 +49,6 @@
void setCacheSize(unsigned int cacheSize) { mCacheSize = cacheSize; }
private:
- struct InputLayoutElement
- {
- D3D11_INPUT_ELEMENT_DESC desc;
- GLenum glslElementType;
- };
-
- struct InputLayoutKey
- {
- unsigned int elementCount;
- InputLayoutElement elements[gl::MAX_VERTEX_ATTRIBS];
-
- const char *begin() const
- {
- return reinterpret_cast<const char*>(&elementCount);
- }
-
- const char *end() const
- {
- return reinterpret_cast<const char*>(&elements[elementCount]);
- }
- };
-
struct PackedAttributeLayout
{
PackedAttributeLayout()
@@ -80,23 +59,10 @@
void addAttributeData(GLenum glType,
UINT semanticIndex,
- DXGI_FORMAT dxgiFormat,
- unsigned int divisor)
- {
- attributeData[numAttributes].glType = glType;
- attributeData[numAttributes].semanticIndex = semanticIndex;
- attributeData[numAttributes].dxgiFormat = dxgiFormat;
- attributeData[numAttributes].divisor = divisor;
- ++numAttributes;
- }
+ gl::VertexFormatType vertexFormatType,
+ unsigned int divisor);
- struct PackedAttribute
- {
- GLenum glType;
- UINT semanticIndex;
- DXGI_FORMAT dxgiFormat;
- unsigned int divisor;
- };
+ bool operator<(const PackedAttributeLayout &other) const;
enum Flags
{
@@ -107,15 +73,10 @@
size_t numAttributes;
unsigned int flags;
- PackedAttribute attributeData[gl::MAX_VERTEX_ATTRIBS];
+ uint32_t attributeData[gl::MAX_VERTEX_ATTRIBS];
};
- struct PackedAttributeComparator
- {
- bool operator()(const PackedAttributeLayout &a, const PackedAttributeLayout &b) const;
- };
-
- std::map<PackedAttributeLayout, ID3D11InputLayout *, PackedAttributeComparator> mLayoutMap;
+ std::map<PackedAttributeLayout, ID3D11InputLayout *> mLayoutMap;
ID3D11InputLayout *mCurrentIL;
ID3D11Buffer *mCurrentBuffers[gl::MAX_VERTEX_ATTRIBS];
diff --git a/src/tests/gl_tests/VertexAttributeTest.cpp b/src/tests/gl_tests/VertexAttributeTest.cpp
index cc96d2b..48597ae 100644
--- a/src/tests/gl_tests/VertexAttributeTest.cpp
+++ b/src/tests/gl_tests/VertexAttributeTest.cpp
@@ -333,6 +333,22 @@
ASSERT_EQ(0u, program);
}
+// Simple test for when we use glBindAttribLocation
+TEST_P(VertexAttributeTest, SimpleBindAttribLocation)
+{
+ // Re-use the multi-attrib program, binding attribute 0
+ GLuint program = compileMultiAttribProgram(1);
+ glBindAttribLocation(program, 2, "position");
+ glBindAttribLocation(program, 3, "a0");
+ glLinkProgram(program);
+
+ // Setup and draw the quad
+ setupMultiAttribs(program, 1, 0.5f);
+ drawQuad(program, "position", 0.5f);
+ EXPECT_GL_NO_ERROR();
+ EXPECT_PIXEL_NEAR(0, 0, 128, 0, 0, 255, 1);
+}
+
// Use this to select which configurations (e.g. which renderer, which GLES major version) these tests should be run against.
// D3D11 Feature Level 9_3 uses different D3D formats for vertex attribs compared to Feature Levels 10_0+, so we should test them separately.
ANGLE_INSTANTIATE_TEST(VertexAttributeTest, ES2_D3D9(), ES2_D3D11(), ES2_D3D11_FL9_3(), ES2_OPENGL(), ES3_OPENGL());