Fix mipmapped GL_ALPHA UBYTE textures on 9_3

Change-Id: I59020f8152d47091533d69d20fe5ff56e5f96bc1
Reviewed-on: https://chromium-review.googlesource.com/262551
Reviewed-by: Geoff Lang <geofflang@chromium.org>
Reviewed-by: Jamie Madill <jmadill@chromium.org>
Tested-by: Austin Kinross <aukinros@microsoft.com>
diff --git a/src/libANGLE/renderer/d3d/d3d11/formatutils11.cpp b/src/libANGLE/renderer/d3d/d3d11/formatutils11.cpp
index 0697a82..2f81d6d 100644
--- a/src/libANGLE/renderer/d3d/d3d11/formatutils11.cpp
+++ b/src/libANGLE/renderer/d3d/d3d11/formatutils11.cpp
@@ -549,7 +549,28 @@
     (*map)[internalFormat].push_back(TypeLoadFunctionPair(type, loadFunc));
 }
 
-D3D11LoadFunctionMap BuildD3D11LoadFunctionMap()
+D3D11LoadFunctionMap BuildD3D11_FL9_3_LoadFunctionMap()
+{
+    D3D11LoadFunctionMap map;
+
+    // From GL_EXT_texture_storage. Also used by GL_ALPHA8
+    // On feature level 9_3, A8_UNORM doesn't support mipmaps, so we must use RGBA8 instead
+    InsertLoadFunction(&map, GL_ALPHA8_EXT, GL_UNSIGNED_BYTE, LoadA8ToRGBA8);
+    
+    return map;
+}
+
+D3D11LoadFunctionMap BuildD3D11_FL10_0Plus_LoadFunctionMap()
+{
+    D3D11LoadFunctionMap map;
+
+    // From GL_EXT_texture_storage. Also used by GL_ALPHA8
+    InsertLoadFunction(&map, GL_ALPHA8_EXT, GL_UNSIGNED_BYTE, LoadToNative<GLubyte, 1>);
+
+    return map;
+}
+
+D3D11LoadFunctionMap BuildBaseD3D11LoadFunctionMap()
 {
     D3D11LoadFunctionMap map;
 
@@ -654,7 +675,7 @@
     InsertLoadFunction(&map, GL_ALPHA,              GL_HALF_FLOAT_OES,                 LoadA16FToRGBA16F                    );
 
     // From GL_EXT_texture_storage
-    InsertLoadFunction(&map, GL_ALPHA8_EXT,             GL_UNSIGNED_BYTE,              LoadToNative<GLubyte, 1>             );
+    // GL_ALPHA8_EXT GL_UNSIGNED_BYTE is in the feature-level-specific load function maps, due to differences between 9_3 and 10_0+
     InsertLoadFunction(&map, GL_LUMINANCE8_EXT,         GL_UNSIGNED_BYTE,              LoadL8ToRGBA8                        );
     InsertLoadFunction(&map, GL_LUMINANCE8_ALPHA8_EXT,  GL_UNSIGNED_BYTE,              LoadLA8ToRGBA8                       );
     InsertLoadFunction(&map, GL_ALPHA32F_EXT,           GL_FLOAT,                      LoadA32FToRGBA32F                    );
@@ -724,8 +745,8 @@
 {
 }
 
-static inline void InsertD3D11FormatInfo(D3D11ES3FormatMap *map, GLenum internalFormat, DXGI_FORMAT texFormat,
-                                         DXGI_FORMAT srvFormat, DXGI_FORMAT rtvFormat, DXGI_FORMAT dsvFormat)
+static inline void InsertD3D11FormatInfoBase(D3D11ES3FormatMap *formatMap, const D3D11LoadFunctionMap &flLoadFunctions, GLenum internalFormat, DXGI_FORMAT texFormat,
+                                             DXGI_FORMAT srvFormat, DXGI_FORMAT rtvFormat, DXGI_FORMAT dsvFormat)
 {
     TextureFormat info;
     info.texFormat = texFormat;
@@ -809,8 +830,8 @@
     InternalFormatInitializerMap::const_iterator initializerIter = initializerMap.find(internalFormat);
     info.dataInitializerFunction = (initializerIter != initializerMap.end()) ? initializerIter->second : NULL;
 
-    // Gather all the load functions for this internal format
-    static const D3D11LoadFunctionMap loadFunctions = BuildD3D11LoadFunctionMap();
+    // Gather all the load functions for this internal format from the base list
+    static const D3D11LoadFunctionMap loadFunctions = BuildBaseD3D11LoadFunctionMap();
     D3D11LoadFunctionMap::const_iterator loadFunctionIter = loadFunctions.find(internalFormat);
     if (loadFunctionIter != loadFunctions.end())
     {
@@ -823,25 +844,55 @@
         }
     }
 
-    map->insert(std::make_pair(internalFormat, info));
+    // Gather load functions for this internal format from the feature-level-specific list
+    D3D11LoadFunctionMap::const_iterator flLoadFunctionIter = flLoadFunctions.find(internalFormat);
+    if (flLoadFunctionIter != flLoadFunctions.end())
+    {
+        const std::vector<TypeLoadFunctionPair> &flLoadFunctionVector = flLoadFunctionIter->second;
+        for (size_t i = 0; i < flLoadFunctionVector.size(); i++)
+        {
+            GLenum type = flLoadFunctionVector[i].first;
+            LoadImageFunction function = flLoadFunctionVector[i].second;
+            info.loadFunctions.insert(std::make_pair(type, function));
+        }
+    }
+
+    formatMap->insert(std::make_pair(internalFormat, info));
+}
+
+static inline void InsertD3D11_FL9_3_FormatInfo(D3D11ES3FormatMap *map, GLenum internalFormat, DXGI_FORMAT texFormat,
+                                                DXGI_FORMAT srvFormat, DXGI_FORMAT rtvFormat, DXGI_FORMAT dsvFormat)
+{
+    static const D3D11LoadFunctionMap flLoadFunctions = BuildD3D11_FL9_3_LoadFunctionMap();
+    InsertD3D11FormatInfoBase(map, flLoadFunctions, internalFormat, texFormat, srvFormat, rtvFormat, dsvFormat);
+}
+
+static inline void InsertD3D11FormatInfo(D3D11ES3FormatMap *map, GLenum internalFormat, DXGI_FORMAT texFormat,
+                                         DXGI_FORMAT srvFormat, DXGI_FORMAT rtvFormat, DXGI_FORMAT dsvFormat)
+{
+    static const D3D11LoadFunctionMap flLoadFunctions = BuildD3D11_FL10_0Plus_LoadFunctionMap();
+    InsertD3D11FormatInfoBase(map, flLoadFunctions, internalFormat, texFormat, srvFormat, rtvFormat, dsvFormat);
 }
 
 static D3D11ES3FormatMap BuildD3D11_FL9_3FormatOverrideMap()
 {
     // D3D11 Feature Level 9_3 doesn't support as many texture formats as Feature Level 10_0+.
     // In particular, it doesn't support:
+    //      - mipmaps on DXGI_FORMAT_A8_NORM
     //      - *_TYPELESS formats
     //      - DXGI_FORMAT_D32_FLOAT_S8X24_UINT or DXGI_FORMAT_D32_FLOAT
 
     D3D11ES3FormatMap map;
 
-    //                         | GL internal format   | D3D11 texture format            | D3D11 SRV format     | D3D11 RTV format      | D3D11 DSV format
-    InsertD3D11FormatInfo(&map, GL_DEPTH_COMPONENT16,  DXGI_FORMAT_D16_UNORM,            DXGI_FORMAT_UNKNOWN,   DXGI_FORMAT_UNKNOWN,    DXGI_FORMAT_D16_UNORM);
-    InsertD3D11FormatInfo(&map, GL_DEPTH_COMPONENT24,  DXGI_FORMAT_D24_UNORM_S8_UINT,    DXGI_FORMAT_UNKNOWN,   DXGI_FORMAT_UNKNOWN,    DXGI_FORMAT_D24_UNORM_S8_UINT);
-    InsertD3D11FormatInfo(&map, GL_DEPTH_COMPONENT32F, DXGI_FORMAT_UNKNOWN,              DXGI_FORMAT_UNKNOWN,   DXGI_FORMAT_UNKNOWN,    DXGI_FORMAT_UNKNOWN);
-    InsertD3D11FormatInfo(&map, GL_DEPTH24_STENCIL8,   DXGI_FORMAT_D24_UNORM_S8_UINT,    DXGI_FORMAT_UNKNOWN,   DXGI_FORMAT_UNKNOWN,    DXGI_FORMAT_D24_UNORM_S8_UINT);
-    InsertD3D11FormatInfo(&map, GL_DEPTH32F_STENCIL8,  DXGI_FORMAT_UNKNOWN,              DXGI_FORMAT_UNKNOWN,   DXGI_FORMAT_UNKNOWN,    DXGI_FORMAT_UNKNOWN);
-    InsertD3D11FormatInfo(&map, GL_STENCIL_INDEX8,     DXGI_FORMAT_D24_UNORM_S8_UINT,    DXGI_FORMAT_UNKNOWN,   DXGI_FORMAT_UNKNOWN,    DXGI_FORMAT_D24_UNORM_S8_UINT);
+    //                                | GL internal format   | D3D11 texture format            | D3D11 SRV format           | D3D11 RTV format            | D3D11 DSV format
+    InsertD3D11_FL9_3_FormatInfo(&map, GL_ALPHA,              DXGI_FORMAT_R8G8B8A8_UNORM,       DXGI_FORMAT_R8G8B8A8_UNORM,  DXGI_FORMAT_R8G8B8A8_UNORM,   DXGI_FORMAT_UNKNOWN);
+    InsertD3D11_FL9_3_FormatInfo(&map, GL_ALPHA8_EXT,         DXGI_FORMAT_R8G8B8A8_UNORM,       DXGI_FORMAT_R8G8B8A8_UNORM,  DXGI_FORMAT_R8G8B8A8_UNORM,   DXGI_FORMAT_UNKNOWN);
+    InsertD3D11_FL9_3_FormatInfo(&map, GL_DEPTH_COMPONENT16,  DXGI_FORMAT_D16_UNORM,            DXGI_FORMAT_UNKNOWN,         DXGI_FORMAT_UNKNOWN,          DXGI_FORMAT_D16_UNORM);
+    InsertD3D11_FL9_3_FormatInfo(&map, GL_DEPTH_COMPONENT24,  DXGI_FORMAT_D24_UNORM_S8_UINT,    DXGI_FORMAT_UNKNOWN,         DXGI_FORMAT_UNKNOWN,          DXGI_FORMAT_D24_UNORM_S8_UINT);
+    InsertD3D11_FL9_3_FormatInfo(&map, GL_DEPTH_COMPONENT32F, DXGI_FORMAT_UNKNOWN,              DXGI_FORMAT_UNKNOWN,         DXGI_FORMAT_UNKNOWN,          DXGI_FORMAT_UNKNOWN);
+    InsertD3D11_FL9_3_FormatInfo(&map, GL_DEPTH24_STENCIL8,   DXGI_FORMAT_D24_UNORM_S8_UINT,    DXGI_FORMAT_UNKNOWN,         DXGI_FORMAT_UNKNOWN,          DXGI_FORMAT_D24_UNORM_S8_UINT);
+    InsertD3D11_FL9_3_FormatInfo(&map, GL_DEPTH32F_STENCIL8,  DXGI_FORMAT_UNKNOWN,              DXGI_FORMAT_UNKNOWN,         DXGI_FORMAT_UNKNOWN,          DXGI_FORMAT_UNKNOWN);
+    InsertD3D11_FL9_3_FormatInfo(&map, GL_STENCIL_INDEX8,     DXGI_FORMAT_D24_UNORM_S8_UINT,    DXGI_FORMAT_UNKNOWN,         DXGI_FORMAT_UNKNOWN,          DXGI_FORMAT_D24_UNORM_S8_UINT);
 
     return map;
 }
diff --git a/src/tests/end2end_tests/TextureTest.cpp b/src/tests/end2end_tests/TextureTest.cpp
index 34ebb79..ef8610d 100644
--- a/src/tests/end2end_tests/TextureTest.cpp
+++ b/src/tests/end2end_tests/TextureTest.cpp
@@ -1,7 +1,7 @@
 #include "ANGLETest.h"
 
 // Use this to select which configurations (e.g. which renderer, which GLES major version) these tests should be run against.
-ANGLE_TYPED_TEST_CASE(TextureTest, ES2_D3D9, ES2_D3D11);
+ANGLE_TYPED_TEST_CASE(TextureTest, ES2_D3D9, ES2_D3D11, ES2_D3D11_FL9_3);
 
 template<typename T>
 class TextureTest : public ANGLETest
@@ -521,3 +521,86 @@
 
     testFloatCopySubImage(4, 4);
 }
+
+// Port of https://www.khronos.org/registry/webgl/conformance-suites/1.0.3/conformance/textures/texture-npot.html
+// Run against GL_ALPHA/UNSIGNED_BYTE format, to ensure that D3D11 Feature Level 9_3 correctly handles GL_ALPHA
+TYPED_TEST(TextureTest, TextureNPOT_GL_ALPHA_UBYTE)
+{
+    const int npotTexSize = 5;
+    const int potTexSize = 4; // Should be less than npotTexSize
+    GLuint tex2D;
+
+    if (extensionEnabled("GL_OES_texture_npot"))
+    {
+        // This test isn't applicable if texture_npot is enabled
+        return;
+    }
+
+    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
+
+    glActiveTexture(GL_TEXTURE0);
+    glGenTextures(1, &tex2D);
+    glBindTexture(GL_TEXTURE_2D, tex2D);
+
+    std::vector<GLubyte> pixels(1 * npotTexSize * npotTexSize);
+    for (size_t pixelId = 0; pixelId < npotTexSize * npotTexSize; ++pixelId)
+    {
+        pixels[pixelId] = 64;
+    }
+
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+
+    // Check that an NPOT texture not on level 0 generates INVALID_VALUE
+    glTexImage2D(GL_TEXTURE_2D, 1, GL_ALPHA, npotTexSize, npotTexSize, 0, GL_ALPHA, GL_UNSIGNED_BYTE, pixels.data());
+    EXPECT_GL_ERROR(GL_INVALID_VALUE);
+
+    // Check that an NPOT texture on level 0 succeeds
+    glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, npotTexSize, npotTexSize, 0, GL_ALPHA, GL_UNSIGNED_BYTE, pixels.data());
+    EXPECT_GL_NO_ERROR();
+
+    // Check that generateMipmap fails on NPOT
+    glGenerateMipmap(GL_TEXTURE_2D);
+    EXPECT_GL_ERROR(GL_INVALID_OPERATION);
+
+    // Check that nothing is drawn if filtering is not correct for NPOT
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+    glClear(GL_COLOR_BUFFER_BIT);
+    drawQuad(m2DProgram, "position", 1.0f);
+    EXPECT_PIXEL_EQ(getWindowWidth() / 2, getWindowHeight() / 2, 0, 0, 0, 255);
+
+    // NPOT texture with TEXTURE_MIN_FILTER not NEAREST or LINEAR should draw with 0,0,0,255
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR);
+    glClear(GL_COLOR_BUFFER_BIT);
+    drawQuad(m2DProgram, "position", 1.0f);
+    EXPECT_PIXEL_EQ(getWindowWidth() / 2, getWindowHeight() / 2, 0, 0, 0, 255);
+
+    // NPOT texture with TEXTURE_MIN_FILTER set to LINEAR should draw
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+    glClear(GL_COLOR_BUFFER_BIT);
+    drawQuad(m2DProgram, "position", 1.0f);
+    EXPECT_PIXEL_EQ(getWindowWidth() / 2, getWindowHeight() / 2, 0, 0, 0, 64);
+
+    // Check that glTexImage2D for POT texture succeeds
+    glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, potTexSize, potTexSize, 0, GL_ALPHA, GL_UNSIGNED_BYTE, pixels.data());
+    EXPECT_GL_NO_ERROR();
+
+    // Check that generateMipmap for an POT texture succeeds
+    glGenerateMipmap(GL_TEXTURE_2D);
+    EXPECT_GL_NO_ERROR();
+
+    // POT texture with TEXTURE_MIN_FILTER set to LINEAR_MIPMAP_LINEAR should draw
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+    glClear(GL_COLOR_BUFFER_BIT);
+    drawQuad(m2DProgram, "position", 1.0f);
+    EXPECT_PIXEL_EQ(getWindowWidth() / 2, getWindowHeight() / 2, 0, 0, 0, 64);
+    EXPECT_GL_NO_ERROR();
+}