Added PointSprites Support for renderers that do not support Geometry Shaders
Change-Id: Iae9ac5f8fbba68dba5e49ccda7bb7eebb05c8e9a
Reviewed-on: https://chromium-review.googlesource.com/240450
Reviewed-by: Geoff Lang <geofflang@chromium.org>
Reviewed-by: Jamie Madill <jmadill@chromium.org>
Tested-by: Geoff Lang <geofflang@chromium.org>
diff --git a/src/compiler/translator/OutputHLSL.cpp b/src/compiler/translator/OutputHLSL.cpp
index de1d4af..cb2c9f7 100644
--- a/src/compiler/translator/OutputHLSL.cpp
+++ b/src/compiler/translator/OutputHLSL.cpp
@@ -140,8 +140,8 @@
}
else
{
- // Reserve registers for dx_DepthRange and dx_ViewAdjust
- mUniformHLSL->reserveUniformRegisters(2);
+ // Reserve registers for dx_DepthRange, dx_ViewAdjust and dx_ViewCoords
+ mUniformHLSL->reserveUniformRegisters(3);
}
}
@@ -510,10 +510,11 @@
out << " float3 dx_DepthRange : packoffset(c0);\n";
}
- // dx_ViewAdjust will only be used in Feature Level 9 shaders.
+ // dx_ViewAdjust and dx_ViewCoords will only be used in Feature Level 9 shaders.
// However, we declare it for all shaders (including Feature Level 10+).
// The bytecode is the same whether we declare it or not, since D3DCompiler removes it if it's unused.
out << " float4 dx_ViewAdjust : packoffset(c1);\n";
+ out << " float2 dx_ViewCoords : packoffset(c2);\n";
out << "};\n"
"\n";
@@ -525,7 +526,8 @@
out << "uniform float3 dx_DepthRange : register(c0);\n";
}
- out << "uniform float4 dx_ViewAdjust : register(c1);\n"
+ out << "uniform float4 dx_ViewAdjust : register(c1);\n";
+ out << "uniform float2 dx_ViewCoords : register(c2);\n"
"\n";
}
diff --git a/src/libANGLE/renderer/Workarounds.h b/src/libANGLE/renderer/Workarounds.h
index c9fdbdd..bfb1ba1 100644
--- a/src/libANGLE/renderer/Workarounds.h
+++ b/src/libANGLE/renderer/Workarounds.h
@@ -28,7 +28,8 @@
Workarounds()
: mrtPerfWorkaround(false),
setDataFasterThanImageUpload(false),
- zeroMaxLodWorkaround(false)
+ zeroMaxLodWorkaround(false),
+ useInstancedPointSpriteEmulation(false)
{}
bool mrtPerfWorkaround;
@@ -40,6 +41,12 @@
// This causes problems when (for example) an application creates a mipmapped texture2D, but sets GL_TEXTURE_MIN_FILTER to GL_NEAREST (i.e disables mipmaps).
// To work around this, D3D11 FL9_3 has to create two copies of the texture. The textures' level zeros are identical, but only one texture has mips.
bool zeroMaxLodWorkaround;
+
+ // Some renderers do not support Geometry Shaders so the Geometry Shader-based
+ // PointSprite emulation will not work.
+ // To work around this, D3D11 FL9_3 has to use a different pointsprite
+ // emulation that is implemented using instanced quads.
+ bool useInstancedPointSpriteEmulation;
};
}
diff --git a/src/libANGLE/renderer/d3d/DynamicHLSL.cpp b/src/libANGLE/renderer/d3d/DynamicHLSL.cpp
index e7deac9..5a2a781 100644
--- a/src/libANGLE/renderer/d3d/DynamicHLSL.cpp
+++ b/src/libANGLE/renderer/d3d/DynamicHLSL.cpp
@@ -420,6 +420,22 @@
}
}
+ // If gl_PointSize is used in the shader then pointsprites rendering is expected.
+ // If the renderer does not support Geometry shaders then Instanced PointSprite emulation
+ // may must be used.
+ bool usesPointSize = sourceShader.find("GL_USES_POINT_SIZE") != std::string::npos;
+ bool useInstancedPointSpriteEmulation = usesPointSize && mRenderer->getWorkarounds().useInstancedPointSpriteEmulation;
+
+ // Instanced PointSprite emulation requires additional entries in the
+ // VS_INPUT structure to support the vertices that make up the quad vertices.
+ // These values must be in sync with the cooresponding values added during inputlayout creation
+ // in InputLayoutCache::applyVertexBuffers().
+ if (useInstancedPointSpriteEmulation)
+ {
+ structHLSL += " float3 spriteVertexPos : SPRITEPOSITION0;\n";
+ structHLSL += " float2 spriteTexCoord : SPRITETEXCOORD0;\n";
+ }
+
std::string replacementHLSL = "struct VS_INPUT\n"
"{\n" +
structHLSL +
@@ -694,6 +710,7 @@
bool usesFragCoord = fragmentShader->mUsesFragCoord;
bool usesPointCoord = fragmentShader->mUsesPointCoord;
bool usesPointSize = vertexShader->mUsesPointSize;
+ bool useInstancedPointSpriteEmulation = usesPointSize && mRenderer->getWorkarounds().useInstancedPointSpriteEmulation;
if (usesFragColor && usesFragData)
{
@@ -720,12 +737,27 @@
}
const std::string &varyingHLSL = generateVaryingHLSL(vertexShader);
+
+ // Instanced PointSprite emulation requires that gl_PointCoord is present in the vertex shader VS_OUTPUT
+ // structure to ensure compatibility with the generated PS_INPUT of the pixel shader.
+ // GeometryShader PointSprite emulation does not require this additional entry because the
+ // GS_OUTPUT of the Geometry shader contains the pointCoord value and already matches the PS_INPUT of the
+ // generated pixel shader.
const SemanticInfo &vertexSemantics = getSemanticInfo(registers, usesFragCoord,
- false, usesPointSize, false);
+ (useInstancedPointSpriteEmulation && usesPointCoord),
+ usesPointSize, false);
storeUserLinkedVaryings(vertexShader, linkedVaryings);
storeBuiltinLinkedVaryings(vertexSemantics, linkedVaryings);
+ // Instanced PointSprite emulation requires additional entries originally generated in the
+ // GeometryShader HLSL. These include pointsize clamp values.
+ if (useInstancedPointSpriteEmulation)
+ {
+ vertexHLSL += "static float minPointSize = " + Str((int)mRenderer->getRendererCaps().minAliasedPointSize) + ".0f;\n"
+ "static float maxPointSize = " + Str((int)mRenderer->getRendererCaps().maxAliasedPointSize) + ".0f;\n";
+ }
+
// Add stub string to be replaced when shader is dynamically defined by its layout
vertexHLSL += "\n" + VERTEX_ATTRIBUTE_STUB_STRING + "\n"
"struct VS_OUTPUT\n" + generateVaryingLinkHLSL(vertexSemantics, varyingHLSL) + "\n"
@@ -802,6 +834,22 @@
}
}
+ // Instanced PointSprite emulation requires additional entries to calculate
+ // the final output vertex positions of the quad that represents each sprite.
+ if (useInstancedPointSpriteEmulation)
+ {
+ vertexHLSL += "\n"
+ " gl_PointSize = clamp(gl_PointSize, minPointSize, maxPointSize);\n"
+ " output.dx_Position.xyz += float3(input.spriteVertexPos.x * gl_PointSize / (dx_ViewCoords.x*2), input.spriteVertexPos.y * gl_PointSize / (dx_ViewCoords.y*2), input.spriteVertexPos.z) * output.dx_Position.w;\n"
+ " output.gl_PointSize = gl_PointSize;\n";
+
+ if (usesPointCoord)
+ {
+ vertexHLSL += "\n"
+ " output.gl_PointCoord = input.spriteTexCoord;\n";
+ }
+ }
+
vertexHLSL += "\n"
" return output;\n"
"}\n";
diff --git a/src/libANGLE/renderer/d3d/ProgramD3D.cpp b/src/libANGLE/renderer/d3d/ProgramD3D.cpp
index cda41a2..3cd4e14 100644
--- a/src/libANGLE/renderer/d3d/ProgramD3D.cpp
+++ b/src/libANGLE/renderer/d3d/ProgramD3D.cpp
@@ -213,7 +213,12 @@
bool ProgramD3D::usesGeometryShader() const
{
- return usesPointSpriteEmulation();
+ return usesPointSpriteEmulation() && !usesInstancedPointSpriteEmulation();
+}
+
+bool ProgramD3D::usesInstancedPointSpriteEmulation() const
+{
+ return mRenderer->getWorkarounds().useInstancedPointSpriteEmulation;
}
GLint ProgramD3D::getSamplerMapping(gl::SamplerType type, unsigned int samplerIndex, const gl::Caps &caps) const
diff --git a/src/libANGLE/renderer/d3d/ProgramD3D.h b/src/libANGLE/renderer/d3d/ProgramD3D.h
index 31aa0db..13efdb2 100644
--- a/src/libANGLE/renderer/d3d/ProgramD3D.h
+++ b/src/libANGLE/renderer/d3d/ProgramD3D.h
@@ -58,6 +58,7 @@
bool usesPointSize() const { return mUsesPointSize; }
bool usesPointSpriteEmulation() const;
bool usesGeometryShader() const;
+ bool usesInstancedPointSpriteEmulation() const;
GLenum getBinaryFormat() { return GL_PROGRAM_BINARY_ANGLE; }
LinkResult load(gl::InfoLog &infoLog, gl::BinaryInputStream *stream);
diff --git a/src/libANGLE/renderer/d3d/RendererD3D.cpp b/src/libANGLE/renderer/d3d/RendererD3D.cpp
index b18f9bf..1904926 100644
--- a/src/libANGLE/renderer/d3d/RendererD3D.cpp
+++ b/src/libANGLE/renderer/d3d/RendererD3D.cpp
@@ -76,7 +76,7 @@
return error;
}
- if (!applyPrimitiveType(mode, count))
+ if (!applyPrimitiveType(mode, count, program->usesPointSize()))
{
return gl::Error(GL_NO_ERROR);
}
@@ -159,7 +159,7 @@
return error;
}
- if (!applyPrimitiveType(mode, count))
+ if (!applyPrimitiveType(mode, count, program->usesPointSize()))
{
return gl::Error(GL_NO_ERROR);
}
@@ -204,7 +204,7 @@
if (!skipDraw(data, mode))
{
- error = drawArrays(mode, count, instances, transformFeedbackActive);
+ error = drawArrays(mode, count, instances, transformFeedbackActive, program->usesPointSize());
if (error.isError())
{
return error;
diff --git a/src/libANGLE/renderer/d3d/RendererD3D.h b/src/libANGLE/renderer/d3d/RendererD3D.h
index 6ed6fe6..8f5e374 100644
--- a/src/libANGLE/renderer/d3d/RendererD3D.h
+++ b/src/libANGLE/renderer/d3d/RendererD3D.h
@@ -92,7 +92,7 @@
virtual gl::Error applyShaders(gl::Program *program, const gl::VertexFormat inputLayout[], const gl::Framebuffer *framebuffer,
bool rasterizerDiscard, bool transformFeedbackActive) = 0;
virtual gl::Error applyUniforms(const ProgramImpl &program, const std::vector<gl::LinkedUniform*> &uniformArray) = 0;
- virtual bool applyPrimitiveType(GLenum primitiveType, GLsizei elementCount) = 0;
+ virtual bool applyPrimitiveType(GLenum primitiveType, GLsizei elementCount, bool usesPointSize) = 0;
virtual gl::Error applyVertexBuffer(const gl::State &state, GLint first, GLsizei count, GLsizei instances) = 0;
virtual gl::Error applyIndexBuffer(const GLvoid *indices, gl::Buffer *elementArrayBuffer, GLsizei count, GLenum mode, GLenum type, TranslatedIndexData *indexInfo) = 0;
virtual void applyTransformFeedbackBuffers(const gl::State& state) = 0;
@@ -156,7 +156,7 @@
gl::Error getScratchMemoryBuffer(size_t requestedSize, MemoryBuffer **bufferOut);
protected:
- virtual gl::Error drawArrays(GLenum mode, GLsizei count, GLsizei instances, bool transformFeedbackActive) = 0;
+ virtual gl::Error drawArrays(GLenum mode, GLsizei count, GLsizei instances, bool transformFeedbackActive, bool usesPointSize) = 0;
virtual gl::Error drawElements(GLenum mode, GLsizei count, GLenum type, const GLvoid *indices,
gl::Buffer *elementArrayBuffer, const TranslatedIndexData &indexInfo, GLsizei instances) = 0;
@@ -201,6 +201,7 @@
{
float depthRange[4];
float viewAdjust[4];
+ float viewCoords[4];
};
struct dx_PixelConstants
diff --git a/src/libANGLE/renderer/d3d/d3d11/InputLayoutCache.cpp b/src/libANGLE/renderer/d3d/d3d11/InputLayoutCache.cpp
index 685f189..9417095 100644
--- a/src/libANGLE/renderer/d3d/d3d11/InputLayoutCache.cpp
+++ b/src/libANGLE/renderer/d3d/d3d11/InputLayoutCache.cpp
@@ -51,6 +51,8 @@
mCurrentVertexStrides[i] = -1;
mCurrentVertexOffsets[i] = -1;
}
+ mPointSpriteVertexBuffer = NULL;
+ mPointSpriteIndexBuffer = NULL;
}
InputLayoutCache::~InputLayoutCache()
@@ -73,6 +75,8 @@
SafeRelease(i->second.inputLayout);
}
mInputLayoutMap.clear();
+ SafeRelease(mPointSpriteVertexBuffer);
+ SafeRelease(mPointSpriteIndexBuffer);
markDirty();
}
@@ -94,6 +98,7 @@
int sortedSemanticIndices[gl::MAX_VERTEX_ATTRIBS];
programD3D->sortAttributesByLayout(attributes, sortedSemanticIndices);
+ bool usesInstancedPointSpriteEmulation = programD3D->usesPointSize() && programD3D->usesInstancedPointSpriteEmulation();
if (!mDevice || !mDeviceContext)
{
@@ -106,12 +111,15 @@
unsigned int firstIndexedElement = gl::MAX_VERTEX_ATTRIBS;
unsigned int firstInstancedElement = gl::MAX_VERTEX_ATTRIBS;
+ unsigned int nextAvailableInputSlot = 0;
for (unsigned int i = 0; i < gl::MAX_VERTEX_ATTRIBS; i++)
{
if (attributes[i].active)
{
D3D11_INPUT_CLASSIFICATION inputClass = attributes[i].divisor > 0 ? D3D11_INPUT_PER_INSTANCE_DATA : D3D11_INPUT_PER_VERTEX_DATA;
+ // If instanced pointsprite emulation is being used, the inputClass is required to be configured as per instance data
+ inputClass = usesInstancedPointSpriteEmulation ? D3D11_INPUT_PER_INSTANCE_DATA : inputClass;
gl::VertexFormat vertexFormat(*attributes[i].attribute, attributes[i].currentValueType);
const d3d11::VertexFormat &vertexFormatInfo = d3d11::GetVertexFormatInfo(vertexFormat, mFeatureLevel);
@@ -127,7 +135,7 @@
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 = attributes[i].divisor;
+ ilKey.elements[ilKey.elementCount].desc.InstanceDataStepRate = usesInstancedPointSpriteEmulation ? 1 : attributes[i].divisor;
if (inputClass == D3D11_INPUT_PER_VERTEX_DATA && firstIndexedElement == gl::MAX_VERTEX_ATTRIBS)
{
@@ -139,9 +147,43 @@
}
ilKey.elementCount++;
+ nextAvailableInputSlot = i + 1;
}
}
+ // Instanced PointSprite emulation requires additional entries in the
+ // inputlayout to support the vertices that make up the pointsprite quad.
+ if (usesInstancedPointSpriteEmulation)
+ {
+ 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;
+
+ // 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;
+ }
+
+ ilKey.elementCount++;
+
+ 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;
+
+ 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.
@@ -153,6 +195,13 @@
{
ilKey.elements[firstInstancedElement].desc.InputSlot = ilKey.elements[firstIndexedElement].desc.InputSlot;
ilKey.elements[firstIndexedElement].desc.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 (usesInstancedPointSpriteEmulation)
+ {
+ ilKey.elements[firstIndexedElement + 1].desc.InputSlot = 0;
+ }
}
ID3D11InputLayout *inputLayout = NULL;
@@ -223,6 +272,8 @@
bool dirtyBuffers = false;
size_t minDiff = gl::MAX_VERTEX_ATTRIBS;
size_t maxDiff = 0;
+ unsigned int nextAvailableIndex = 0;
+
for (unsigned int i = 0; i < gl::MAX_VERTEX_ATTRIBS; i++)
{
ID3D11Buffer *buffer = NULL;
@@ -249,9 +300,93 @@
mCurrentBuffers[i] = buffer;
mCurrentVertexStrides[i] = vertexStride;
mCurrentVertexOffsets[i] = vertexOffset;
+
+ // If a non null ID3D11Buffer is being assigned to mCurrentBuffers,
+ // then the next available index needs to be tracked to ensure
+ // that any instanced pointsprite emulation buffers will be properly packed.
+ if (buffer)
+ {
+ nextAvailableIndex = i + 1;
+ }
}
}
+ // Instanced PointSprite emulation requires two additional ID3D11Buffers.
+ // A vertex buffer needs to be created and added to the list of current buffers,
+ // strides and offsets collections. This buffer contains the vertices for a single
+ // PointSprite quad.
+ // An index buffer also needs to be created and applied because rendering instanced
+ // data on D3D11 FL9_3 requires DrawIndexedInstanced() to be used.
+ if (usesInstancedPointSpriteEmulation)
+ {
+ HRESULT result = S_OK;
+ const UINT pointSpriteVertexStride = sizeof(float) * 5;
+
+ if (!mPointSpriteVertexBuffer)
+ {
+ static const float pointSpriteVertices[] =
+ {
+ // Position // TexCoord
+ -1.0f, -1.0f, 0.0f, 0.0f, 1.0f,
+ -1.0f, 1.0f, 0.0f, 0.0f, 0.0f,
+ 1.0f, 1.0f, 0.0f, 1.0f, 0.0f,
+ 1.0f, -1.0f, 0.0f, 1.0f, 1.0f,
+ -1.0f, -1.0f, 0.0f, 0.0f, 1.0f,
+ 1.0f, 1.0f, 0.0f, 1.0f, 0.0f,
+ };
+
+ D3D11_SUBRESOURCE_DATA vertexBufferData = { pointSpriteVertices, 0, 0 };
+ D3D11_BUFFER_DESC vertexBufferDesc;
+ vertexBufferDesc.ByteWidth = sizeof(pointSpriteVertices);
+ vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
+ vertexBufferDesc.Usage = D3D11_USAGE_IMMUTABLE;
+ vertexBufferDesc.CPUAccessFlags = 0;
+ vertexBufferDesc.MiscFlags = 0;
+ vertexBufferDesc.StructureByteStride = 0;
+
+ result = mDevice->CreateBuffer(&vertexBufferDesc, &vertexBufferData, &mPointSpriteVertexBuffer);
+ if (FAILED(result))
+ {
+ return gl::Error(GL_OUT_OF_MEMORY, "Failed to create instanced pointsprite emulation vertex buffer, HRESULT: 0x%08x", result);
+ }
+ }
+
+ mCurrentBuffers[nextAvailableIndex] = mPointSpriteVertexBuffer;
+ mCurrentVertexStrides[nextAvailableIndex] = pointSpriteVertexStride;
+ mCurrentVertexOffsets[nextAvailableIndex] = 0;
+
+ if (!mPointSpriteIndexBuffer)
+ {
+ // Create an index buffer and set it for pointsprite rendering
+ static const unsigned short pointSpriteIndices[] =
+ {
+ 0, 1, 2, 3, 4, 5,
+ };
+
+ D3D11_SUBRESOURCE_DATA indexBufferData = { pointSpriteIndices, 0, 0 };
+ D3D11_BUFFER_DESC indexBufferDesc;
+ indexBufferDesc.ByteWidth = sizeof(pointSpriteIndices);
+ indexBufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER;
+ indexBufferDesc.Usage = D3D11_USAGE_IMMUTABLE;
+ indexBufferDesc.CPUAccessFlags = 0;
+ indexBufferDesc.MiscFlags = 0;
+ indexBufferDesc.StructureByteStride = 0;
+
+ result = mDevice->CreateBuffer(&indexBufferDesc, &indexBufferData, &mPointSpriteIndexBuffer);
+ if (FAILED(result))
+ {
+ SafeRelease(mPointSpriteVertexBuffer);
+ return gl::Error(GL_OUT_OF_MEMORY, "Failed to create instanced pointsprite emulation index buffer, HRESULT: 0x%08x", result);
+ }
+ }
+
+ // The index buffer is applied here because Instanced PointSprite emulation uses
+ // the a non-indexed rendering path in ANGLE (DrawArrays). This means that applyIndexBuffer()
+ // on the renderer will not be called and setting this buffer here ensures that the rendering
+ // path will contain the correct index buffers.
+ mDeviceContext->IASetIndexBuffer(mPointSpriteIndexBuffer, DXGI_FORMAT_R16_UINT, 0);
+ }
+
if (moveFirstIndexedIntoSlotZero)
{
// In this case, we swapped the slots of the first instanced element and the first indexed element, to ensure
diff --git a/src/libANGLE/renderer/d3d/d3d11/InputLayoutCache.h b/src/libANGLE/renderer/d3d/d3d11/InputLayoutCache.h
index 8e120bd..e1ebbba 100644
--- a/src/libANGLE/renderer/d3d/d3d11/InputLayoutCache.h
+++ b/src/libANGLE/renderer/d3d/d3d11/InputLayoutCache.h
@@ -77,6 +77,9 @@
UINT mCurrentVertexStrides[gl::MAX_VERTEX_ATTRIBS];
UINT mCurrentVertexOffsets[gl::MAX_VERTEX_ATTRIBS];
+ ID3D11Buffer *mPointSpriteVertexBuffer;
+ ID3D11Buffer *mPointSpriteIndexBuffer;
+
static std::size_t hashInputLayout(const InputLayoutKey &inputLayout);
static bool compareInputLayouts(const InputLayoutKey &a, const InputLayoutKey &b);
diff --git a/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp b/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp
index d184e25..4e1875e 100644
--- a/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp
+++ b/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp
@@ -928,6 +928,13 @@
mPixelConstants.viewCoords[2] = actualViewport.x + (actualViewport.width * 0.5f);
mPixelConstants.viewCoords[3] = actualViewport.y + (actualViewport.height * 0.5f);
+ // Instanced pointsprite emulation requires ViewCoords to be defined in the
+ // the vertex shader.
+ mVertexConstants.viewCoords[0] = mPixelConstants.viewCoords[0];
+ mVertexConstants.viewCoords[1] = mPixelConstants.viewCoords[1];
+ mVertexConstants.viewCoords[2] = mPixelConstants.viewCoords[2];
+ mVertexConstants.viewCoords[3] = mPixelConstants.viewCoords[3];
+
mPixelConstants.depthFront[0] = (actualZFar - actualZNear) * 0.5f;
mPixelConstants.depthFront[1] = (actualZNear + actualZFar) * 0.5f;
@@ -943,7 +950,7 @@
mForceSetViewport = false;
}
-bool Renderer11::applyPrimitiveType(GLenum mode, GLsizei count)
+bool Renderer11::applyPrimitiveType(GLenum mode, GLsizei count, bool usesPointSize)
{
D3D11_PRIMITIVE_TOPOLOGY primitiveTopology = D3D_PRIMITIVE_TOPOLOGY_UNDEFINED;
@@ -964,6 +971,14 @@
return false;
}
+ // If instanced pointsprite emulation is being used and If gl_PointSize is used in the shader,
+ // GL_POINTS mode is expected to render pointsprites.
+ // Instanced PointSprite emulation requires that the topology to be D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST.
+ if (mode == GL_POINTS && usesPointSize && getWorkarounds().useInstancedPointSpriteEmulation)
+ {
+ primitiveTopology = D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
+ }
+
if (primitiveTopology != mCurrentPrimitiveTopology)
{
mDeviceContext->IASetPrimitiveTopology(primitiveTopology);
@@ -1226,8 +1241,9 @@
}
}
-gl::Error Renderer11::drawArrays(GLenum mode, GLsizei count, GLsizei instances, bool transformFeedbackActive)
+gl::Error Renderer11::drawArrays(GLenum mode, GLsizei count, GLsizei instances, bool transformFeedbackActive, bool usesPointSize)
{
+ bool useInstancedPointSpriteEmulation = usesPointSize && getWorkarounds().useInstancedPointSpriteEmulation;
if (mode == GL_POINTS && transformFeedbackActive)
{
// Since point sprites are generated with a geometry shader, too many vertices will
@@ -1277,7 +1293,17 @@
}
else
{
- mDeviceContext->Draw(count, 0);
+ // If gl_PointSize is used and GL_POINTS is specified, then it is expected to render pointsprites.
+ // If instanced pointsprite emulation is being used the topology is expexted to be
+ // D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST and DrawIndexedInstanced must be used.
+ if (mode == GL_POINTS && useInstancedPointSpriteEmulation)
+ {
+ mDeviceContext->DrawIndexedInstanced(6, count, 0, 0, 0);
+ }
+ else
+ {
+ mDeviceContext->Draw(count, 0);
+ }
return gl::Error(GL_NO_ERROR);
}
}
diff --git a/src/libANGLE/renderer/d3d/d3d11/Renderer11.h b/src/libANGLE/renderer/d3d/d3d11/Renderer11.h
index a0132ba..440f84c 100644
--- a/src/libANGLE/renderer/d3d/d3d11/Renderer11.h
+++ b/src/libANGLE/renderer/d3d/d3d11/Renderer11.h
@@ -81,7 +81,7 @@
virtual void setViewport(const gl::Rectangle &viewport, float zNear, float zFar, GLenum drawMode, GLenum frontFace,
bool ignoreViewport);
- virtual bool applyPrimitiveType(GLenum mode, GLsizei count);
+ virtual bool applyPrimitiveType(GLenum mode, GLsizei count, bool usesPointSize);
gl::Error applyRenderTarget(const gl::Framebuffer *frameBuffer) override;
virtual gl::Error applyShaders(gl::Program *program, const gl::VertexFormat inputLayout[], const gl::Framebuffer *framebuffer,
bool rasterizerDiscard, bool transformFeedbackActive);
@@ -91,7 +91,7 @@
virtual gl::Error applyIndexBuffer(const GLvoid *indices, gl::Buffer *elementArrayBuffer, GLsizei count, GLenum mode, GLenum type, TranslatedIndexData *indexInfo);
virtual void applyTransformFeedbackBuffers(const gl::State &state);
- virtual gl::Error drawArrays(GLenum mode, GLsizei count, GLsizei instances, bool transformFeedbackActive);
+ virtual gl::Error drawArrays(GLenum mode, GLsizei count, GLsizei instances, bool transformFeedbackActive, bool usesPointSize);
virtual gl::Error drawElements(GLenum mode, GLsizei count, GLenum type, const GLvoid *indices,
gl::Buffer *elementArrayBuffer, const TranslatedIndexData &indexInfo, GLsizei instances);
diff --git a/src/libANGLE/renderer/d3d/d3d11/renderer11_utils.cpp b/src/libANGLE/renderer/d3d/d3d11/renderer11_utils.cpp
index 380cb31..d09d078 100644
--- a/src/libANGLE/renderer/d3d/d3d11/renderer11_utils.cpp
+++ b/src/libANGLE/renderer/d3d/d3d11/renderer11_utils.cpp
@@ -1142,6 +1142,7 @@
workarounds.mrtPerfWorkaround = true;
workarounds.setDataFasterThanImageUpload = true;
workarounds.zeroMaxLodWorkaround = (featureLevel <= D3D_FEATURE_LEVEL_9_3);
+ workarounds.useInstancedPointSpriteEmulation = (featureLevel <= D3D_FEATURE_LEVEL_9_3);
return workarounds;
}
diff --git a/src/libANGLE/renderer/d3d/d3d9/Renderer9.cpp b/src/libANGLE/renderer/d3d/d3d9/Renderer9.cpp
index ea637cb..d3fb4a2 100644
--- a/src/libANGLE/renderer/d3d/d3d9/Renderer9.cpp
+++ b/src/libANGLE/renderer/d3d/d3d9/Renderer9.cpp
@@ -1146,7 +1146,7 @@
mForceSetViewport = false;
}
-bool Renderer9::applyPrimitiveType(GLenum mode, GLsizei count)
+bool Renderer9::applyPrimitiveType(GLenum mode, GLsizei count, bool usesPointSize)
{
switch (mode)
{
@@ -1389,7 +1389,7 @@
UNREACHABLE();
}
-gl::Error Renderer9::drawArrays(GLenum mode, GLsizei count, GLsizei instances, bool transformFeedbackActive)
+gl::Error Renderer9::drawArrays(GLenum mode, GLsizei count, GLsizei instances, bool transformFeedbackActive, bool usesPointSize)
{
ASSERT(!transformFeedbackActive);
diff --git a/src/libANGLE/renderer/d3d/d3d9/Renderer9.h b/src/libANGLE/renderer/d3d/d3d9/Renderer9.h
index d174bb9..3e30d3f 100644
--- a/src/libANGLE/renderer/d3d/d3d9/Renderer9.h
+++ b/src/libANGLE/renderer/d3d/d3d9/Renderer9.h
@@ -87,13 +87,13 @@
virtual gl::Error applyShaders(gl::Program *program, const gl::VertexFormat inputLayout[], const gl::Framebuffer *framebuffer,
bool rasterizerDiscard, bool transformFeedbackActive);
virtual gl::Error applyUniforms(const ProgramImpl &program, const std::vector<gl::LinkedUniform*> &uniformArray);
- virtual bool applyPrimitiveType(GLenum primitiveType, GLsizei elementCount);
+ virtual bool applyPrimitiveType(GLenum primitiveType, GLsizei elementCount, bool usesPointSize);
virtual gl::Error applyVertexBuffer(const gl::State &state, GLint first, GLsizei count, GLsizei instances);
virtual gl::Error applyIndexBuffer(const GLvoid *indices, gl::Buffer *elementArrayBuffer, GLsizei count, GLenum mode, GLenum type, TranslatedIndexData *indexInfo);
virtual void applyTransformFeedbackBuffers(const gl::State& state);
- virtual gl::Error drawArrays(GLenum mode, GLsizei count, GLsizei instances, bool transformFeedbackActive);
+ virtual gl::Error drawArrays(GLenum mode, GLsizei count, GLsizei instances, bool transformFeedbackActive, bool usesPointSize);
virtual gl::Error drawElements(GLenum mode, GLsizei count, GLenum type, const GLvoid *indices,
gl::Buffer *elementArrayBuffer, const TranslatedIndexData &indexInfo, GLsizei instances);
diff --git a/src/libANGLE/renderer/d3d/d3d9/renderer9_utils.cpp b/src/libANGLE/renderer/d3d/d3d9/renderer9_utils.cpp
index 17c55fe..03e1c38 100644
--- a/src/libANGLE/renderer/d3d/d3d9/renderer9_utils.cpp
+++ b/src/libANGLE/renderer/d3d/d3d9/renderer9_utils.cpp
@@ -565,6 +565,7 @@
Workarounds workarounds;
workarounds.mrtPerfWorkaround = true;
workarounds.setDataFasterThanImageUpload = false;
+ workarounds.useInstancedPointSpriteEmulation = false;
return workarounds;
}
diff --git a/tests/angle_end2end_tests.gypi b/tests/angle_end2end_tests.gypi
index b9fad51..a62ab2e 100644
--- a/tests/angle_end2end_tests.gypi
+++ b/tests/angle_end2end_tests.gypi
@@ -35,6 +35,7 @@
'<(angle_path)/tests/angle_tests/media/pixel.inl',
'<(angle_path)/tests/angle_tests/OcclusionQueriesTest.cpp',
'<(angle_path)/tests/angle_tests/PBOExtensionTest.cpp',
+ '<(angle_path)/tests/angle_tests/PointSpritesTest.cpp',
'<(angle_path)/tests/angle_tests/ProgramBinaryTest.cpp',
'<(angle_path)/tests/angle_tests/ReadPixelsTest.cpp',
'<(angle_path)/tests/angle_tests/RendererTest.cpp',
diff --git a/tests/angle_tests/PointSpritesTest.cpp b/tests/angle_tests/PointSpritesTest.cpp
new file mode 100644
index 0000000..d109893
--- /dev/null
+++ b/tests/angle_tests/PointSpritesTest.cpp
@@ -0,0 +1,390 @@
+#include "ANGLETest.h"
+
+// Use this to select which configurations (e.g. which renderer, which GLES
+// major version) these tests should be run against.
+//
+// Some of the pointsprite tests below were ported from Khronos WebGL
+// conformance test suite.
+
+//
+// We test on D3D11 9_3 because the existing D3D11 PointSprite implementation
+// uses Geometry Shaders which are not supported for 9_3.
+// D3D9 and D3D11 are also tested to ensure no regressions.
+ANGLE_TYPED_TEST_CASE(PointSpritesTest, ES2_D3D9, ES2_D3D11, ES2_D3D11_FL9_3);
+
+template<typename T>
+class PointSpritesTest : public ANGLETest
+{
+ protected:
+ const int windowWidth = 256;
+ const int windowHeight = 256;
+ PointSpritesTest() : ANGLETest(T::GetGlesMajorVersion(), T::GetPlatform())
+ {
+ setWindowWidth(windowWidth);
+ setWindowHeight(windowHeight);
+ setConfigRedBits(8);
+ setConfigGreenBits(8);
+ setConfigBlueBits(8);
+ setConfigAlphaBits(8);
+ }
+
+ virtual void SetUp()
+ {
+ ANGLETest::SetUp();
+ }
+
+ float s2p(float s)
+ {
+ return (s + 1.0f) * 0.5f * (GLfloat)windowWidth;
+ }
+};
+
+// Checks gl_PointCoord and gl_PointSize
+// https://www.khronos.org/registry/webgl/sdk/tests/conformance/glsl/variables/gl-pointcoord.html
+TYPED_TEST(PointSpritesTest, PointCoordAndPointSizeCompliance)
+{
+ const std::string fs = SHADER_SOURCE
+ (
+ precision mediump float;
+ void main()
+ {
+ gl_FragColor = vec4(
+ gl_PointCoord.x,
+ gl_PointCoord.y,
+ 0,
+ 1);
+ }
+ );
+
+ const std::string vs = SHADER_SOURCE
+ (
+ attribute vec4 vPosition;
+ uniform float uPointSize;
+ void main()
+ {
+ gl_PointSize = uPointSize;
+ gl_Position = vPosition;
+ }
+ );
+
+ GLuint program = CompileProgram(vs, fs);
+ ASSERT_NE(program, 0u);
+ ASSERT_GL_NO_ERROR();
+
+ glUseProgram(program);
+
+ GLfloat pointSizeRange[2] = {};
+ glGetFloatv(GL_ALIASED_POINT_SIZE_RANGE, pointSizeRange);
+
+ GLfloat maxPointSize = pointSizeRange[1];
+
+ ASSERT_TRUE(maxPointSize >= 1);
+ maxPointSize = floorf(maxPointSize);
+ ASSERT_TRUE((int)maxPointSize % 1 == 0);
+
+ maxPointSize = std::min(maxPointSize, 64.0f);
+ GLfloat pointWidth = maxPointSize / windowWidth;
+ GLfloat step = floorf(maxPointSize / 4);
+ GLfloat pointStep = std::max<GLfloat>(1.0f, step);
+
+ GLint pointSizeLoc = glGetUniformLocation(program, "uPointSize");
+ ASSERT_GL_NO_ERROR();
+
+ glUniform1f(pointSizeLoc, maxPointSize);
+ ASSERT_GL_NO_ERROR();
+
+ GLfloat pixelOffset = ((int)maxPointSize % 2) ? (1.0f / (GLfloat)windowWidth) : 0;
+ GLuint vertexObject = 0;
+ glGenBuffers(1, &vertexObject);
+ ASSERT_NE(vertexObject, 0U);
+ ASSERT_GL_NO_ERROR();
+
+ glBindBuffer(GL_ARRAY_BUFFER, vertexObject);
+ ASSERT_GL_NO_ERROR();
+
+ GLfloat thePoints[] = { -0.5 + pixelOffset, -0.5 + pixelOffset,
+ 0.5 + pixelOffset, -0.5 + pixelOffset,
+ -0.5 + pixelOffset, 0.5 + pixelOffset,
+ 0.5 + pixelOffset, 0.5 + pixelOffset };
+
+ glBufferData(GL_ARRAY_BUFFER, sizeof(thePoints), thePoints, GL_STATIC_DRAW);
+ ASSERT_GL_NO_ERROR();
+
+ glEnableVertexAttribArray(0);
+ glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, 0);
+
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+ glDrawArrays(GL_POINTS, 0, 4);
+ ASSERT_GL_NO_ERROR();
+
+ glDeleteBuffers(1, &vertexObject);
+
+ std::string debugText;
+ for (float py = 0; py < 2; ++py) {
+ for (float px = 0; px < 2; ++px) {
+ float pointX = -0.5 + px + pixelOffset;
+ float pointY = -0.5 + py + pixelOffset;
+ for (int yy = 0; yy < maxPointSize; yy += pointStep) {
+ for (int xx = 0; xx < maxPointSize; xx += pointStep) {
+ // formula for s and t from OpenGL ES 2.0 spec section 3.3
+ float xw = s2p(pointX);
+ float yw = s2p(pointY);
+ float u = xx / maxPointSize * 2 - 1;
+ float v = yy / maxPointSize * 2 - 1;
+ float xf = floorf(s2p(pointX + u * pointWidth));
+ float yf = floorf(s2p(pointY + v * pointWidth));
+ float s = 0.5 + (xf + 0.5 - xw) / maxPointSize;
+ float t = 0.5 + (yf + 0.5 - yw) / maxPointSize;
+ GLubyte color[4] = { floorf(s * 255), floorf((1 - t) * 255), 0, 255 };
+ EXPECT_PIXEL_NEAR(xf, yf, color[0], color[1], color[2], color[3], 4);
+ }
+ }
+ }
+ }
+}
+
+// Verify that drawing a point without enabling any attributes succeeds
+// https://www.khronos.org/registry/webgl/sdk/tests/conformance/rendering/point-no-attributes.html
+TYPED_TEST(PointSpritesTest, PointWithoutAttributesCompliance)
+{
+ const std::string fs = SHADER_SOURCE
+ (
+ precision mediump float;
+ void main()
+ {
+ gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0);
+ }
+ );
+
+ const std::string vs = SHADER_SOURCE
+ (
+ void main()
+ {
+ gl_PointSize = 1.0;
+ gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
+ }
+ );
+
+ GLuint program = CompileProgram(vs, fs);
+ ASSERT_NE(program, 0u);
+ ASSERT_GL_NO_ERROR();
+
+ glUseProgram(program);
+
+ glDrawArrays(GL_POINTS, 0, 1);
+ ASSERT_GL_NO_ERROR();
+
+ // expect the center pixel to be green
+ EXPECT_PIXEL_EQ((windowWidth - 1) / 2, (windowHeight - 1) / 2, 0, 255, 0, 255);
+}
+
+// This is a regression test for a graphics driver bug affecting end caps on roads in MapsGL
+// https://www.khronos.org/registry/webgl/sdk/tests/conformance/rendering/point-with-gl-pointcoord-in-fragment-shader.html
+TYPED_TEST(PointSpritesTest, PointCoordRegressionTest)
+{
+ const std::string fs = SHADER_SOURCE
+ (
+ precision mediump float;
+ varying vec4 v_color;
+ void main()
+ {
+ // It seems as long as this mathematical expression references
+ // gl_PointCoord, the fragment's color is incorrect.
+ vec2 diff = gl_PointCoord - vec2(.5, .5);
+ if (length(diff) > 0.5)
+ discard;
+
+ // The point should be a solid color.
+ gl_FragColor = v_color;
+ }
+ );
+
+ const std::string vs = SHADER_SOURCE
+ (
+ varying vec4 v_color;
+ // The X and Y coordinates of the center of the point.
+ attribute vec2 a_vertex;
+ uniform float u_pointSize;
+ void main()
+ {
+ gl_PointSize = u_pointSize;
+ gl_Position = vec4(a_vertex, 0.0, 1.0);
+ // The color of the point.
+ v_color = vec4(0.0, 1.0, 0.0, 1.0);
+ }
+ );
+
+ GLuint program = CompileProgram(vs, fs);
+ ASSERT_NE(program, 0u);
+ ASSERT_GL_NO_ERROR();
+
+ glUseProgram(program);
+
+ GLfloat pointSizeRange[2] = {};
+ glGetFloatv(GL_ALIASED_POINT_SIZE_RANGE, pointSizeRange);
+
+ GLfloat maxPointSize = pointSizeRange[1];
+
+ ASSERT_TRUE(maxPointSize > 2);
+
+ glClearColor(0, 0, 0, 1);
+ glDisable(GL_DEPTH_TEST);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ GLint pointSizeLoc = glGetUniformLocation(program, "u_pointSize");
+ ASSERT_GL_NO_ERROR();
+
+ GLfloat pointSize = std::min<GLfloat>(20.0f, maxPointSize);
+ glUniform1f(pointSizeLoc, pointSize);
+ ASSERT_GL_NO_ERROR();
+
+ GLuint vertexObject = 0;
+ glGenBuffers(1, &vertexObject);
+ ASSERT_NE(vertexObject, 0U);
+ ASSERT_GL_NO_ERROR();
+
+ glBindBuffer(GL_ARRAY_BUFFER, vertexObject);
+ ASSERT_GL_NO_ERROR();
+
+ GLfloat thePoints[] = { 0.0f, 0.0f };
+
+ glBufferData(GL_ARRAY_BUFFER, sizeof(thePoints), thePoints, GL_STATIC_DRAW);
+ ASSERT_GL_NO_ERROR();
+
+ glEnableVertexAttribArray(0);
+ glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, 0);
+
+ glDrawArrays(GL_POINTS, 0, 1);
+ ASSERT_GL_NO_ERROR();
+
+ // expect the center pixel to be green
+ EXPECT_PIXEL_EQ((windowWidth - 1) / 2, (windowHeight - 1) / 2, 0, 255, 0, 255);
+
+ glDeleteBuffers(1, &vertexObject);
+}
+
+// Verify GL_VERTEX_PROGRAM_POINT_SIZE is enabled
+// https://www.khronos.org/registry/webgl/sdk/tests/conformance/rendering/point-size.html
+TYPED_TEST(PointSpritesTest, PointSizeEnabledCompliance)
+{
+ const std::string fs = SHADER_SOURCE
+ (
+ precision mediump float;
+ varying vec4 color;
+
+ void main()
+ {
+ gl_FragColor = color;
+ }
+ );
+
+ const std::string vs = SHADER_SOURCE
+ (
+ attribute vec3 pos;
+ attribute vec4 colorIn;
+ uniform float pointSize;
+ varying vec4 color;
+
+ void main()
+ {
+ gl_PointSize = pointSize;
+ color = colorIn;
+ gl_Position = vec4(pos, 1.0);
+ }
+ );
+
+ GLuint program = CompileProgram(vs, fs);
+ ASSERT_NE(program, 0u);
+ ASSERT_GL_NO_ERROR();
+
+ glUseProgram(program);
+
+ glDisable(GL_BLEND);
+
+ // The choice of (0.4, 0.4) ensures that the centers of the surrounding
+ // pixels are not contained within the point when it is of size 1, but
+ // that they definitely are when it is of size 2.
+ GLfloat vertices[] = { 0.4f, 0.4f, 0.0f };
+ GLubyte colors[] = { 255, 0, 0, 255 };
+
+ GLuint vertexObject = 0;
+ glGenBuffers(1, &vertexObject);
+ ASSERT_NE(vertexObject, 0U);
+ ASSERT_GL_NO_ERROR();
+
+ glBindBuffer(GL_ARRAY_BUFFER, vertexObject);
+ ASSERT_GL_NO_ERROR();
+
+ glBufferData(GL_ARRAY_BUFFER, sizeof(vertices) + sizeof(colors), NULL, GL_STATIC_DRAW);
+ ASSERT_GL_NO_ERROR();
+
+ glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices);
+ ASSERT_GL_NO_ERROR();
+
+ glBufferSubData(GL_ARRAY_BUFFER, sizeof(vertices), sizeof(colors), colors);
+ ASSERT_GL_NO_ERROR();
+
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+ glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
+ glEnableVertexAttribArray(0);
+
+ glVertexAttribPointer(1, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, (GLvoid*)sizeof(vertices));
+ glEnableVertexAttribArray(1);
+
+ GLint pointSizeLoc = glGetUniformLocation(program, "pointSize");
+ ASSERT_GL_NO_ERROR();
+
+ glUniform1f(pointSizeLoc, 1.0f);
+ ASSERT_GL_NO_ERROR();
+
+ glDrawArrays(GL_POINTS, 0, _countof(vertices) / 3);
+ ASSERT_GL_NO_ERROR();
+
+ // Test the pixels around the target Red pixel to ensure
+ // they are the expected color values
+ for (GLint y = 178; y < 180; ++y)
+ {
+ for (GLint x = 178; x < 180; ++x)
+ {
+ // 179x179 is expected to be a red pixel
+ // All others are black
+ GLubyte expectedColor[4] = { 0, 0, 0, 0 };
+ if (x == 179 && y == 179)
+ {
+ expectedColor[0] = 255;
+ expectedColor[3] = 255;
+ }
+ EXPECT_PIXEL_EQ(x, y, expectedColor[0], expectedColor[1], expectedColor[2], expectedColor[3]);
+ }
+ }
+
+ swapBuffers();
+
+ GLfloat pointSizeRange[2] = {};
+ glGetFloatv(GL_ALIASED_POINT_SIZE_RANGE, pointSizeRange);
+
+ if (pointSizeRange[1] >= 2.0)
+ {
+ // Draw a point of size 2 and verify it fills the appropriate region.
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+ glUniform1f(pointSizeLoc, 2.0f);
+ ASSERT_GL_NO_ERROR();
+
+ glDrawArrays(GL_POINTS, 0, _countof(vertices) / 3);
+ ASSERT_GL_NO_ERROR();
+
+ // Test the pixels to ensure the target is ALL Red pixels
+ for (GLint y = 178; y < 180; ++y)
+ {
+ for (GLint x = 178; x < 180; ++x)
+ {
+ EXPECT_PIXEL_EQ(x, y, 255, 0, 0, 255);
+ }
+ }
+ }
+
+ glDeleteBuffers(1, &vertexObject);
+}