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);
+}