Re-resubmit "Compile the D3D11 VS and PS on separate threads at GL link time"

The latest version of this change disables multithreaded D3D shader
compilation with any C++ compiler other than VC (e.g. Clang).

Change-Id: If3ef5fd650055ae66397ea2c9121bfcf1792892a
Reviewed-on: https://chromium-review.googlesource.com/250328
Tested-by: Austin Kinross <aukinros@microsoft.com>
Reviewed-by: Geoff Lang <geofflang@chromium.org>
Reviewed-by: Cooper Partin <coopp@microsoft.com>
Reviewed-by: Jamie Madill <jmadill@chromium.org>
diff --git a/src/common/angleutils.cpp b/src/common/angleutils.cpp
index af5eb6c..b5cdd9d 100644
--- a/src/common/angleutils.cpp
+++ b/src/common/angleutils.cpp
@@ -29,7 +29,8 @@
 
 std::string FormatString(const char *fmt, va_list vararg)
 {
-    static std::vector<char> buffer(512);
+    // Note: this needs to be thread-safe, since the D3D11 renderer uses some multithreading
+    std::vector<char> buffer(512);
 
     size_t len = FormatStringIntoVector(fmt, vararg, buffer);
     return std::string(&buffer[0], len);
diff --git a/src/libANGLE/features.h b/src/libANGLE/features.h
index fbe013f..63b9102 100644
--- a/src/libANGLE/features.h
+++ b/src/libANGLE/features.h
@@ -37,4 +37,13 @@
 #define ANGLE_SHADER_DEBUG_INFO ANGLE_DISABLED
 #endif
 
+// Should we compile the D3D vertex and pixel shaders on separate threads at glLinkProgram() time?
+#if !defined(ANGLE_MULTITHREADED_D3D_SHADER_COMPILE)
+#if defined(_MSC_VER) // Other compilers (e.g. Clang) might not support exceptions
+#define ANGLE_MULTITHREADED_D3D_SHADER_COMPILE ANGLE_ENABLED
+#else
+#define ANGLE_MULTITHREADED_D3D_SHADER_COMPILE ANGLE_DISABLED
+#endif
+#endif
+
 #endif // LIBANGLE_FEATURES_H_
diff --git a/src/libANGLE/renderer/d3d/ProgramD3D.cpp b/src/libANGLE/renderer/d3d/ProgramD3D.cpp
index a368d29..0e6b653 100644
--- a/src/libANGLE/renderer/d3d/ProgramD3D.cpp
+++ b/src/libANGLE/renderer/d3d/ProgramD3D.cpp
@@ -6,13 +6,23 @@
 
 // ProgramD3D.cpp: Defines the rx::ProgramD3D class which implements rx::ProgramImpl.
 
+#include "libANGLE/features.h"
+
+#if ANGLE_MULTITHREADED_D3D_SHADER_COMPILE == ANGLE_ENABLED
+// <future> requires _HAS_EXCEPTIONS to be defined
+#ifdef _HAS_EXCEPTIONS
+#undef _HAS_EXCEPTIONS
+#endif // _HAS_EXCEPTIONS
+#define _HAS_EXCEPTIONS 1
+#include <future> // For std::async
+#endif // ANGLE_MULTITHREADED_D3D_SHADER_COMPILE == ANGLE_ENABLED
+
 #include "libANGLE/renderer/d3d/ProgramD3D.h"
 
 #include "common/utilities.h"
 #include "libANGLE/Framebuffer.h"
 #include "libANGLE/FramebufferAttachment.h"
 #include "libANGLE/Program.h"
-#include "libANGLE/features.h"
 #include "libANGLE/renderer/d3d/DynamicHLSL.h"
 #include "libANGLE/renderer/d3d/RendererD3D.h"
 #include "libANGLE/renderer/d3d/ShaderD3D.h"
@@ -943,6 +953,7 @@
     }
     else if (!infoLog)
     {
+        // This isn't thread-safe, so we should ensure that we always pass in an infoLog if using multiple threads.
         std::vector<char> tempCharBuffer(tempInfoLog.getLength() + 3);
         tempInfoLog.getLog(tempInfoLog.getLength(), NULL, &tempCharBuffer[0]);
         ERR("Error compiling dynamic vertex executable:\n%s\n", &tempCharBuffer[0]);
@@ -958,18 +969,49 @@
     ShaderD3D *vertexShaderD3D = ShaderD3D::makeShaderD3D(vertexShader->getImplementation());
     ShaderD3D *fragmentShaderD3D = ShaderD3D::makeShaderD3D(fragmentShader->getImplementation());
 
-    gl::VertexFormat defaultInputLayout[gl::MAX_VERTEX_ATTRIBS];
-    GetDefaultInputLayoutFromShader(vertexShader->getActiveAttributes(), defaultInputLayout);
-    ShaderExecutableD3D *defaultVertexExecutable = NULL;
-    gl::Error error = getVertexExecutableForInputLayout(defaultInputLayout, &defaultVertexExecutable, &infoLog);
-    if (error.isError())
-    {
-        return LinkResult(false, error);
-    }
+    gl::Error vertexShaderResult(GL_NO_ERROR);
+    gl::InfoLog tempVertexShaderInfoLog;
 
+#if ANGLE_MULTITHREADED_D3D_SHADER_COMPILE == ANGLE_ENABLED
+    // Use an async task to begin compiling the vertex shader asynchronously on its own task.
+    std::future<ShaderExecutableD3D*> vertexShaderTask = std::async([this, vertexShader, &tempVertexShaderInfoLog, &vertexShaderResult]()
+    {
+#endif // ANGLE_MULTITHREADED_D3D_SHADER_COMPILE
+
+        gl::VertexFormat defaultInputLayout[gl::MAX_VERTEX_ATTRIBS];
+        GetDefaultInputLayoutFromShader(vertexShader->getActiveAttributes(), defaultInputLayout);
+        ShaderExecutableD3D *defaultVertexExecutable = NULL;
+        vertexShaderResult = getVertexExecutableForInputLayout(defaultInputLayout, &defaultVertexExecutable, &tempVertexShaderInfoLog);
+
+#if ANGLE_MULTITHREADED_D3D_SHADER_COMPILE == ANGLE_ENABLED
+        return defaultVertexExecutable;
+    });
+#endif // ANGLE_MULTITHREADED_D3D_SHADER_COMPILE
+
+    // Continue to compile the pixel shader on the main thread
     std::vector<GLenum> defaultPixelOutput = GetDefaultOutputLayoutFromShader(getPixelShaderKey());
     ShaderExecutableD3D *defaultPixelExecutable = NULL;
-    error = getPixelExecutableForOutputLayout(defaultPixelOutput, &defaultPixelExecutable, &infoLog);
+    gl::Error error = getPixelExecutableForOutputLayout(defaultPixelOutput, &defaultPixelExecutable, &infoLog);
+
+#if ANGLE_MULTITHREADED_D3D_SHADER_COMPILE == ANGLE_ENABLED
+    // Call .get() on the vertex shader compilation. This waits until the task is complete before returning
+    ShaderExecutableD3D *defaultVertexExecutable = vertexShaderTask.get();
+#endif // ANGLE_MULTITHREADED_D3D_SHADER_COMPILE
+
+    // Combine the temporary infoLog with the real one
+    if (tempVertexShaderInfoLog.getLength() > 0)
+    {
+        std::vector<char> tempCharBuffer(tempVertexShaderInfoLog.getLength() + 3);
+        tempVertexShaderInfoLog.getLog(tempVertexShaderInfoLog.getLength(), NULL, &tempCharBuffer[0]);
+        infoLog.append(&tempCharBuffer[0]);
+    }
+
+    if (vertexShaderResult.isError())
+    {
+        return LinkResult(false, vertexShaderResult);
+    }
+
+    // If the pixel shader compilation failed, then return error
     if (error.isError())
     {
         return LinkResult(false, error);