Commit 324bcc46 by Austin Kinross Committed by Geoff Lang

Fix instancing on D3D11 9_3, by ensuring slot zero contains non-instanced data

D3D11 Feature Level 9_3 supports instancing, but slot 0 in the input layout must not be instanced. D3D9 has a similar restriction, where stream 0 must not be instanced. This restriction can be worked around by remapping any non-instanced slot to slot 0. This works because HLSL uses shader semantics to match the vertex inputs to the elements in the input layout, rather than the slots. BUG=angle:858 Change-Id: I67b2be9095afc206a4b9f107ed61356820551afe Reviewed-on: https://chromium-review.googlesource.com/237270Tested-by: 's avatarAustin Kinross <aukinros@microsoft.com> Reviewed-by: 's avatarGeoff Lang <geofflang@chromium.org>
parent ce8bb2fa
...@@ -104,6 +104,9 @@ gl::Error InputLayoutCache::applyVertexBuffers(TranslatedAttribute attributes[gl ...@@ -104,6 +104,9 @@ gl::Error InputLayoutCache::applyVertexBuffers(TranslatedAttribute attributes[gl
static const char* semanticName = "TEXCOORD"; static const char* semanticName = "TEXCOORD";
unsigned int firstIndexedElement = gl::MAX_VERTEX_ATTRIBS;
unsigned int firstInstancedElement = gl::MAX_VERTEX_ATTRIBS;
for (unsigned int i = 0; i < gl::MAX_VERTEX_ATTRIBS; i++) for (unsigned int i = 0; i < gl::MAX_VERTEX_ATTRIBS; i++)
{ {
if (attributes[i].active) if (attributes[i].active)
...@@ -125,10 +128,33 @@ gl::Error InputLayoutCache::applyVertexBuffers(TranslatedAttribute attributes[gl ...@@ -125,10 +128,33 @@ gl::Error InputLayoutCache::applyVertexBuffers(TranslatedAttribute attributes[gl
ilKey.elements[ilKey.elementCount].desc.AlignedByteOffset = 0; ilKey.elements[ilKey.elementCount].desc.AlignedByteOffset = 0;
ilKey.elements[ilKey.elementCount].desc.InputSlotClass = inputClass; ilKey.elements[ilKey.elementCount].desc.InputSlotClass = inputClass;
ilKey.elements[ilKey.elementCount].desc.InstanceDataStepRate = attributes[i].divisor; ilKey.elements[ilKey.elementCount].desc.InstanceDataStepRate = attributes[i].divisor;
if (inputClass == D3D11_INPUT_PER_VERTEX_DATA && firstIndexedElement == gl::MAX_VERTEX_ATTRIBS)
{
firstIndexedElement = ilKey.elementCount;
}
else if (inputClass == D3D11_INPUT_PER_INSTANCE_DATA && firstInstancedElement == gl::MAX_VERTEX_ATTRIBS)
{
firstInstancedElement = ilKey.elementCount;
}
ilKey.elementCount++; ilKey.elementCount++;
} }
} }
// On 9_3, we must ensure that slot 0 contains non-instanced data.
// If slot 0 currently contains instanced data then we swap it with a non-instanced element.
// Note that instancing is only available on 9_3 via ANGLE_instanced_arrays, since 9_3 doesn't support OpenGL ES 3.0.
// As per the spec for ANGLE_instanced_arrays, not all attributes can be instanced simultaneously, so a non-instanced element must exist.
ASSERT(!(mFeatureLevel <= D3D_FEATURE_LEVEL_9_3 && firstInstancedElement == gl::MAX_VERTEX_ATTRIBS));
bool moveFirstIndexedIntoSlotZero = mFeatureLevel <= D3D_FEATURE_LEVEL_9_3 && firstInstancedElement == 0 && firstIndexedElement != gl::MAX_VERTEX_ATTRIBS;
if (moveFirstIndexedIntoSlotZero)
{
ilKey.elements[firstInstancedElement].desc.InputSlot = ilKey.elements[firstIndexedElement].desc.InputSlot;
ilKey.elements[firstIndexedElement].desc.InputSlot = 0;
}
ID3D11InputLayout *inputLayout = NULL; ID3D11InputLayout *inputLayout = NULL;
InputLayoutMap::iterator keyIter = mInputLayoutMap.find(ilKey); InputLayoutMap::iterator keyIter = mInputLayoutMap.find(ilKey);
...@@ -226,6 +252,16 @@ gl::Error InputLayoutCache::applyVertexBuffers(TranslatedAttribute attributes[gl ...@@ -226,6 +252,16 @@ gl::Error InputLayoutCache::applyVertexBuffers(TranslatedAttribute attributes[gl
} }
} }
if (moveFirstIndexedIntoSlotZero)
{
// In this case, we swapped the slots of the first instanced element and the first indexed element, to ensure
// that the first slot contains non-instanced data (required by Feature Level 9_3).
// We must also swap the corresponding buffers sent to IASetVertexBuffers so that the correct data is sent to each slot.
std::swap(mCurrentBuffers[firstIndexedElement], mCurrentBuffers[firstInstancedElement]);
std::swap(mCurrentVertexStrides[firstIndexedElement], mCurrentVertexStrides[firstInstancedElement]);
std::swap(mCurrentVertexOffsets[firstIndexedElement], mCurrentVertexOffsets[firstInstancedElement]);
}
if (dirtyBuffers) if (dirtyBuffers)
{ {
ASSERT(minDiff <= maxDiff && maxDiff < gl::MAX_VERTEX_ATTRIBS); ASSERT(minDiff <= maxDiff && maxDiff < gl::MAX_VERTEX_ATTRIBS);
......
...@@ -356,7 +356,13 @@ static bool GetInstancingSupport(D3D_FEATURE_LEVEL featureLevel) ...@@ -356,7 +356,13 @@ static bool GetInstancingSupport(D3D_FEATURE_LEVEL featureLevel)
case D3D_FEATURE_LEVEL_11_1: case D3D_FEATURE_LEVEL_11_1:
case D3D_FEATURE_LEVEL_11_0: case D3D_FEATURE_LEVEL_11_0:
case D3D_FEATURE_LEVEL_10_1: case D3D_FEATURE_LEVEL_10_1:
case D3D_FEATURE_LEVEL_10_0: case D3D_FEATURE_LEVEL_10_0: return true;
// Feature Level 9_3 supports instancing, but slot 0 in the input layout must not be instanced.
// D3D9 has a similar restriction, where stream 0 must not be instanced.
// This restriction can be worked around by remapping any non-instanced slot to slot 0.
// This works because HLSL uses shader semantics to match the vertex inputs to the elements in the input layout, rather than the slots.
// Note that we only support instancing via ANGLE_instanced_array on 9_3, since 9_3 doesn't support OpenGL ES 3.0
case D3D_FEATURE_LEVEL_9_3: return true; case D3D_FEATURE_LEVEL_9_3: return true;
case D3D_FEATURE_LEVEL_9_2: case D3D_FEATURE_LEVEL_9_2:
......
#include "ANGLETest.h"
// Use this to select which configurations (e.g. which renderer, which GLES major version) these tests should be run against.
// We test on D3D9 and D3D11 9_3 because they use special codepaths when attribute zero is instanced, unlike D3D11.
ANGLE_TYPED_TEST_CASE(InstancingTest, ES2_D3D9, ES2_D3D11, ES2_D3D11_FL9_3);
template<typename T>
class InstancingTest : public ANGLETest
{
protected:
InstancingTest() : ANGLETest(T::GetGlesMajorVersion(), T::GetPlatform())
{
setWindowWidth(256);
setWindowHeight(256);
setConfigRedBits(8);
setConfigGreenBits(8);
setConfigBlueBits(8);
setConfigAlphaBits(8);
}
virtual void SetUp()
{
ANGLETest::SetUp();
mVertexAttribDivisorANGLE = NULL;
mDrawArraysInstancedANGLE = NULL;
mDrawElementsInstancedANGLE = NULL;
char *extensionString = (char*)glGetString(GL_EXTENSIONS);
if (strstr(extensionString, "GL_ANGLE_instanced_arrays"))
{
mVertexAttribDivisorANGLE = (PFNGLVERTEXATTRIBDIVISORANGLEPROC)eglGetProcAddress("glVertexAttribDivisorANGLE");
mDrawArraysInstancedANGLE = (PFNGLDRAWARRAYSINSTANCEDANGLEPROC)eglGetProcAddress("glDrawArraysInstancedANGLE");
mDrawElementsInstancedANGLE = (PFNGLDRAWELEMENTSINSTANCEDANGLEPROC)eglGetProcAddress("glDrawElementsInstancedANGLE");
}
ASSERT_TRUE(mVertexAttribDivisorANGLE != NULL);
ASSERT_TRUE(mDrawArraysInstancedANGLE != NULL);
ASSERT_TRUE(mDrawElementsInstancedANGLE != NULL);
// Initialize the vertex and index vectors
GLfloat vertex1[3] = {-quadRadius, quadRadius, 0.0f};
GLfloat vertex2[3] = {-quadRadius, -quadRadius, 0.0f};
GLfloat vertex3[3] = { quadRadius, -quadRadius, 0.0f};
GLfloat vertex4[3] = { quadRadius, quadRadius, 0.0f};
mVertices.insert(mVertices.end(), vertex1, vertex1 + 3);
mVertices.insert(mVertices.end(), vertex2, vertex2 + 3);
mVertices.insert(mVertices.end(), vertex3, vertex3 + 3);
mVertices.insert(mVertices.end(), vertex4, vertex4 + 3);
GLfloat coord1[2] = {0.0f, 0.0f};
GLfloat coord2[2] = {0.0f, 1.0f};
GLfloat coord3[2] = {1.0f, 1.0f};
GLfloat coord4[2] = {1.0f, 0.0f};
mTexcoords.insert(mTexcoords.end(), coord1, coord1 + 2);
mTexcoords.insert(mTexcoords.end(), coord2, coord2 + 2);
mTexcoords.insert(mTexcoords.end(), coord3, coord3 + 2);
mTexcoords.insert(mTexcoords.end(), coord4, coord4 + 2);
mIndices.push_back(0);
mIndices.push_back(1);
mIndices.push_back(2);
mIndices.push_back(0);
mIndices.push_back(2);
mIndices.push_back(3);
// Tile a 3x3 grid of the tiles
for (float y = -1.0f + quadRadius; y < 1.0f - quadRadius; y += quadRadius * 3)
{
for (float x = -1.0f + quadRadius; x < 1.0f - quadRadius; x += quadRadius * 3)
{
GLfloat instance[3] = {x + quadRadius, y + quadRadius, 0.0f};
mInstances.insert(mInstances.end(), instance, instance + 3);
}
}
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
ASSERT_GL_NO_ERROR();
}
virtual void runTest(std::string vs, bool shouldAttribZeroBeInstanced)
{
const std::string fs = SHADER_SOURCE
(
precision mediump float;
void main()
{
gl_FragColor = vec4(1.0, 0, 0, 1.0);
}
);
GLuint program = CompileProgram(vs, fs);
ASSERT_NE(program, 0u);
// Get the attribute locations
GLint positionLoc = glGetAttribLocation(program, "a_position");
GLint instancePosLoc = glGetAttribLocation(program, "a_instancePos");
// If this ASSERT fails then the vertex shader code should be refactored
ASSERT_EQ(shouldAttribZeroBeInstanced, (instancePosLoc == 0));
// Set the viewport
glViewport(0, 0, getWindowWidth(), getWindowHeight());
// Clear the color buffer
glClear(GL_COLOR_BUFFER_BIT);
// Use the program object
glUseProgram(program);
// Load the vertex position
glVertexAttribPointer(positionLoc, 3, GL_FLOAT, GL_FALSE, 0, mVertices.data());
glEnableVertexAttribArray(positionLoc);
// Load the instance position
glVertexAttribPointer(instancePosLoc, 3, GL_FLOAT, GL_FALSE, 0, mInstances.data());
glEnableVertexAttribArray(instancePosLoc);
// Enable instancing
mVertexAttribDivisorANGLE(instancePosLoc, 1);
// Do the instanced draw
mDrawElementsInstancedANGLE(GL_TRIANGLES, mIndices.size(), GL_UNSIGNED_SHORT, mIndices.data(), mInstances.size());
ASSERT_GL_NO_ERROR();
// Check that various pixels are the expected color.
EXPECT_PIXEL_EQ(quadRadius * getWindowWidth(), quadRadius * getWindowHeight(), 255, 0, 0, 255);
EXPECT_PIXEL_EQ((1 - quadRadius) * getWindowWidth(), (1 - quadRadius) * getWindowHeight(), 255, 0, 0, 255);
EXPECT_PIXEL_EQ((quadRadius / 2) * getWindowWidth(), (quadRadius / 2) * getWindowHeight(), 0, 0, 0, 255);
EXPECT_PIXEL_EQ((1 - quadRadius / 2) * getWindowWidth(), (1 - quadRadius / 2) * getWindowHeight(), 0, 0, 0, 255);
}
// Loaded entry points
PFNGLVERTEXATTRIBDIVISORANGLEPROC mVertexAttribDivisorANGLE;
PFNGLDRAWARRAYSINSTANCEDANGLEPROC mDrawArraysInstancedANGLE;
PFNGLDRAWELEMENTSINSTANCEDANGLEPROC mDrawElementsInstancedANGLE;
// Vertex data
std::vector<GLfloat> mVertices;
std::vector<GLfloat> mTexcoords;
std::vector<GLfloat> mInstances;
std::vector<GLushort> mIndices;
const GLfloat quadRadius = 0.2f;
};
// This test uses a vertex shader with the first attribute (attribute zero) instanced.
// On D3D9 and D3D11 FL9_3, this triggers a special codepath that rearranges the input layout sent to D3D,
// to ensure that slot/stream zero of the input layout doesn't contain per-instance data.
TYPED_TEST(InstancingTest, AttributeZeroInstanced)
{
const std::string vs = SHADER_SOURCE
(
attribute vec3 a_instancePos;
attribute vec3 a_position;
void main()
{
gl_Position = vec4(a_position.xyz + a_instancePos.xyz, 1.0);
}
);
runTest(vs, true);
}
// Same as AttributeZeroInstanced, but attribute zero is not instanced.
// This ensures the general instancing codepath (i.e. without rearranging the input layout) works as expected.
TYPED_TEST(InstancingTest, AttributeZeroNotInstanced)
{
const std::string vs = SHADER_SOURCE
(
attribute vec3 a_position;
attribute vec3 a_instancePos;
void main()
{
gl_Position = vec4(a_position.xyz + a_instancePos.xyz, 1.0);
}
);
runTest(vs, false);
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment