end2end_tests: Forward Windows debug messages to stderr

When validation layers are enabled, D3D12 warnings and error
messages are logged to the shared DBWIN_BUFFER segment of memory.
This CL has makes the test environment watch for new events and
logs them to stderr so they show up in the test bot logs. This helps
debug problems in the D3D12 backend which previously just crashed
with a general Device Lost message.

Bug: none
Change-Id: I0eaddf9e16303bd65579e85fe6693bd8cdfbd8da
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/26640
Reviewed-by: Rafael Cintron <rafael.cintron@microsoft.com>
Reviewed-by: Bryan Bernhart <bryan.bernhart@intel.com>
Commit-Queue: Austin Eng <enga@chromium.org>
diff --git a/src/tests/DawnTest.cpp b/src/tests/DawnTest.cpp
index 80d2b99..33b3701 100644
--- a/src/tests/DawnTest.cpp
+++ b/src/tests/DawnTest.cpp
@@ -24,6 +24,7 @@
 #include "dawn_native/DawnNative.h"
 #include "dawn_wire/WireClient.h"
 #include "dawn_wire/WireServer.h"
+#include "utils/PlatformDebugLogger.h"
 #include "utils/SystemUtils.h"
 #include "utils/TerribleCommandBuffer.h"
 #include "utils/WGPUHelpers.h"
@@ -35,7 +36,7 @@
 #include <sstream>
 #include <unordered_map>
 
-#ifdef DAWN_ENABLE_BACKEND_OPENGL
+#if defined(DAWN_ENABLE_BACKEND_OPENGL)
 #    include "GLFW/glfw3.h"
 #    include "dawn_native/OpenGLBackend.h"
 #endif  // DAWN_ENABLE_BACKEND_OPENGL
@@ -81,7 +82,7 @@
 
     DawnTestEnvironment* gTestEnv = nullptr;
 
-}  // namespace
+}  // anonymous namespace
 
 const RGBA8 RGBA8::kZero = RGBA8(0, 0, 0, 0);
 const RGBA8 RGBA8::kBlack = RGBA8(0, 0, 0, 255);
@@ -186,6 +187,11 @@
 DawnTestEnvironment::DawnTestEnvironment(int argc, char** argv) {
     ParseArgs(argc, argv);
 
+    if (mEnableBackendValidation) {
+        mPlatformDebugLogger =
+            std::unique_ptr<utils::PlatformDebugLogger>(utils::CreatePlatformDebugLogger());
+    }
+
     // Create a temporary instance to select available and preferred adapters. This is done before
     // test instantiation so GetAvailableAdapterTestParamsForBackends can generate test
     // parameterizations all selected adapters. We drop the instance at the end of this function
@@ -199,6 +205,8 @@
     PrintTestConfigurationAndAdapterInfo();
 }
 
+DawnTestEnvironment::~DawnTestEnvironment() = default;
+
 void DawnTestEnvironment::ParseArgs(int argc, char** argv) {
     size_t argLen = 0;  // Set when parsing --arg=X arguments
     for (int i = 1; i < argc; ++i) {
diff --git a/src/tests/DawnTest.h b/src/tests/DawnTest.h
index 903ab60..1116b56 100644
--- a/src/tests/DawnTest.h
+++ b/src/tests/DawnTest.h
@@ -155,6 +155,7 @@
                                 std::initializer_list<const char*> forceDisabledWorkarounds = {});
 
 namespace utils {
+    class PlatformDebugLogger;
     class TerribleCommandBuffer;
 }  // namespace utils
 
@@ -176,7 +177,7 @@
 class DawnTestEnvironment : public testing::Environment {
   public:
     DawnTestEnvironment(int argc, char** argv);
-    ~DawnTestEnvironment() override = default;
+    ~DawnTestEnvironment() override;
 
     static void SetEnvironment(DawnTestEnvironment* env);
 
@@ -219,6 +220,8 @@
     std::string mWireTraceDir;
     std::vector<dawn_native::DeviceType> mDevicePreferences;
     std::vector<TestAdapterProperties> mAdapterProperties;
+
+    std::unique_ptr<utils::PlatformDebugLogger> mPlatformDebugLogger;
 };
 
 class DawnTestBase {
diff --git a/src/utils/BUILD.gn b/src/utils/BUILD.gn
index a2c57c3..eff4183 100644
--- a/src/utils/BUILD.gn
+++ b/src/utils/BUILD.gn
@@ -70,6 +70,7 @@
     "ComboRenderBundleEncoderDescriptor.h",
     "ComboRenderPipelineDescriptor.cpp",
     "ComboRenderPipelineDescriptor.h",
+    "PlatformDebugLogger.h",
     "SystemUtils.cpp",
     "SystemUtils.h",
     "TerribleCommandBuffer.cpp",
@@ -90,6 +91,12 @@
   frameworks = []
 
   if (is_win) {
+    sources += [ "WindowsDebugLogger.cpp" ]
+  } else {
+    sources += [ "EmptyDebugLogger.cpp" ]
+  }
+
+  if (is_win) {
     sources += [ "WindowsTimer.cpp" ]
   } else if (is_mac) {
     sources += [
diff --git a/src/utils/CMakeLists.txt b/src/utils/CMakeLists.txt
index 3a95940..080c2d5 100644
--- a/src/utils/CMakeLists.txt
+++ b/src/utils/CMakeLists.txt
@@ -22,6 +22,7 @@
     "ComboRenderPipelineDescriptor.h"
     "GLFWUtils.cpp"
     "GLFWUtils.h"
+    "PlatformDebugLogger.h"
     "SystemUtils.cpp"
     "SystemUtils.h"
     "TerribleCommandBuffer.cpp"
@@ -43,6 +44,12 @@
 )
 
 if(WIN32)
+    target_sources(dawn_utils PRIVATE "WindowsDebugLogger.cpp")
+else()
+    target_sources(dawn_utils PRIVATE "EmptyDebugLogger.cpp")
+endif()
+
+if(WIN32)
     target_sources(dawn_utils PRIVATE "WindowsTimer.cpp")
 elseif(APPLE)
     target_sources(dawn_utils PRIVATE
diff --git a/src/utils/EmptyDebugLogger.cpp b/src/utils/EmptyDebugLogger.cpp
new file mode 100644
index 0000000..ed0ad7f
--- /dev/null
+++ b/src/utils/EmptyDebugLogger.cpp
@@ -0,0 +1,29 @@
+// Copyright 2020 The Dawn Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "utils/PlatformDebugLogger.h"
+
+namespace utils {
+
+    class EmptyDebugLogger : public PlatformDebugLogger {
+      public:
+        EmptyDebugLogger() = default;
+        ~EmptyDebugLogger() override = default;
+    };
+
+    PlatformDebugLogger* CreatePlatformDebugLogger() {
+        return new EmptyDebugLogger();
+    }
+
+}  // namespace utils
diff --git a/src/utils/PlatformDebugLogger.h b/src/utils/PlatformDebugLogger.h
new file mode 100644
index 0000000..33c46de
--- /dev/null
+++ b/src/utils/PlatformDebugLogger.h
@@ -0,0 +1,29 @@
+// Copyright 2020 The Dawn Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef UTILS_PLATFORMDEBUGLOGGER_H_
+#define UTILS_PLATFORMDEBUGLOGGER_H_
+
+namespace utils {
+
+    class PlatformDebugLogger {
+      public:
+        virtual ~PlatformDebugLogger() = default;
+    };
+
+    PlatformDebugLogger* CreatePlatformDebugLogger();
+
+}  // namespace utils
+
+#endif  // UTILS_PLATFORMDEBUGLOGGER_H_
diff --git a/src/utils/WindowsDebugLogger.cpp b/src/utils/WindowsDebugLogger.cpp
new file mode 100644
index 0000000..0383e0a
--- /dev/null
+++ b/src/utils/WindowsDebugLogger.cpp
@@ -0,0 +1,104 @@
+// Copyright 2020 The Dawn Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "utils/PlatformDebugLogger.h"
+
+#include "common/Assert.h"
+#include "common/windows_with_undefs.h"
+
+#include <array>
+#include <thread>
+
+namespace utils {
+
+    class WindowsDebugLogger : public PlatformDebugLogger {
+      public:
+        WindowsDebugLogger() : PlatformDebugLogger() {
+            if (IsDebuggerPresent()) {
+                // This condition is true when running inside Visual Studio or some other debugger.
+                // Messages are already printed there so we don't need to do anything.
+                return;
+            }
+
+            mShouldExitHandle = CreateEventA(nullptr, TRUE, FALSE, nullptr);
+            ASSERT(mShouldExitHandle != nullptr);
+
+            mThread = std::thread(
+                [](HANDLE shouldExit) {
+                    // https://blogs.msdn.microsoft.com/reiley/2011/07/29/a-debugging-approach-to-outputdebugstring/
+                    // for the layout of this struct.
+                    struct {
+                        DWORD process_id;
+                        char data[4096 - sizeof(DWORD)];
+                    }* dbWinBuffer = nullptr;
+
+                    HANDLE file = CreateFileMappingA(INVALID_HANDLE_VALUE, nullptr, PAGE_READWRITE,
+                                                     0, sizeof(*dbWinBuffer), "DBWIN_BUFFER");
+                    ASSERT(file != nullptr);
+                    ASSERT(file != INVALID_HANDLE_VALUE);
+
+                    dbWinBuffer = static_cast<decltype(dbWinBuffer)>(
+                        MapViewOfFile(file, SECTION_MAP_READ, 0, 0, 0));
+                    ASSERT(dbWinBuffer != nullptr);
+
+                    HANDLE dbWinBufferReady =
+                        CreateEventA(nullptr, FALSE, FALSE, "DBWIN_BUFFER_READY");
+                    ASSERT(dbWinBufferReady != nullptr);
+
+                    HANDLE dbWinDataReady = CreateEventA(nullptr, FALSE, FALSE, "DBWIN_DATA_READY");
+                    ASSERT(dbWinDataReady != nullptr);
+
+                    std::array<HANDLE, 2> waitHandles = {shouldExit, dbWinDataReady};
+                    while (true) {
+                        SetEvent(dbWinBufferReady);
+                        DWORD wait = WaitForMultipleObjects(waitHandles.size(), waitHandles.data(),
+                                                            FALSE, INFINITE);
+                        if (wait == WAIT_OBJECT_0) {
+                            break;
+                        }
+                        ASSERT(wait == WAIT_OBJECT_0 + 1);
+                        fprintf(stderr, "%.*s\n", static_cast<int>(sizeof(dbWinBuffer->data)),
+                                dbWinBuffer->data);
+                        fflush(stderr);
+                    }
+
+                    CloseHandle(dbWinDataReady);
+                    CloseHandle(dbWinBufferReady);
+                    UnmapViewOfFile(dbWinBuffer);
+                    CloseHandle(file);
+                },
+                mShouldExitHandle);
+        }
+
+        ~WindowsDebugLogger() override {
+            if (mShouldExitHandle != nullptr) {
+                ASSERT(SetEvent(mShouldExitHandle));
+                CloseHandle(mShouldExitHandle);
+            }
+
+            if (mThread.joinable()) {
+                mThread.join();
+            }
+        }
+
+      private:
+        std::thread mThread;
+        HANDLE mShouldExitHandle = INVALID_HANDLE_VALUE;
+    };
+
+    PlatformDebugLogger* CreatePlatformDebugLogger() {
+        return new WindowsDebugLogger();
+    }
+
+}  // namespace utils