sample: add a --fxc option to validate HLSL with FXC

Change-Id: I39d684a71d9f985a1f30b871f08f51bdf50f17a1
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/56064
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
Commit-Queue: Corentin Wallez <cwallez@chromium.org>
diff --git a/samples/main.cc b/samples/main.cc
index 2d43d3f..3ad5aa8 100644
--- a/samples/main.cc
+++ b/samples/main.cc
@@ -65,6 +65,7 @@
 
   std::vector<std::string> transforms;
 
+  bool use_fxc = false;
   std::string dxc_path;
   std::string xcrun_path;
 };
@@ -95,8 +96,9 @@
                                Affects AST dumping, and text-based output languages.
   --dump-inspector-bindings -- Dump reflection data about bindins to stdout.
   -h                        -- This help text
-  --validate                -- Validates generated SPIR-V with spirv-val.
-                               Has no effect on non-SPIRV outputs.
+  --validate                -- Validates the generated shader
+  --fxc                     -- Ask to validate HLSL output using FXC instead of DXC.
+                               When specified, automatically enables --validate
   --dxc                     -- Path to DXC executable, used to validate HLSL output.
                                When specified, automatically enables --validate
   --xcrun                   -- Path to xcrun executable, used to validate MSL output.
@@ -404,6 +406,9 @@
       opts->dump_inspector_bindings = true;
     } else if (arg == "--validate") {
       opts->validate = true;
+    } else if (arg == "--fxc") {
+      opts->validate = true;
+      opts->use_fxc = true;
     } else if (arg == "--dxc") {
       ++i;
       if (i >= args.size()) {
@@ -872,20 +877,31 @@
 #endif
 #if TINT_BUILD_HLSL_WRITER
       case Format::kHlsl: {
-        auto dxc = tint::utils::Command::LookPath(
-            options.dxc_path.empty() ? "dxc" : options.dxc_path);
-        if (dxc.Found()) {
-          auto* w = static_cast<tint::writer::Text*>(writer.get());
-          auto hlsl = w->result();
-          auto res = tint::val::Hlsl(dxc.Path(), hlsl, program.get());
-          if (res.failed) {
-            validation_failed = true;
-            validation_msgs << res.source << std::endl;
-            validation_msgs << res.output;
-          }
+        auto* w = static_cast<tint::writer::Text*>(writer.get());
+        auto hlsl = w->result();
+
+        tint::val::Result res;
+        if (options.use_fxc) {
+#ifdef _WIN32
+          res = tint::val::HlslUsingFXC(hlsl, program.get());
+#else
+          res.failed = true;
+          res.output = "FXC can only be used on Windows. Sorry :X";
+#endif  // _WIN32
         } else {
+          auto dxc = tint::utils::Command::LookPath(
+              options.dxc_path.empty() ? "dxc" : options.dxc_path);
+          if (dxc.Found()) {
+            res = tint::val::HlslUsingDXC(dxc.Path(), hlsl, program.get());
+          } else {
+            res.failed = true;
+            res.output = "DXC executable not found. Cannot validate";
+          }
+        }
+        if (res.failed) {
           validation_failed = true;
-          validation_msgs << "DXC executable not found. Cannot validate";
+          validation_msgs << res.source << std::endl;
+          validation_msgs << res.output;
         }
         break;
       }
diff --git a/src/val/hlsl.cc b/src/val/hlsl.cc
index 23a67e6..c1c3315 100644
--- a/src/val/hlsl.cc
+++ b/src/val/hlsl.cc
@@ -19,12 +19,21 @@
 #include "src/utils/io/command.h"
 #include "src/utils/io/tmpfile.h"
 
+#ifdef _WIN32
+#include <d3dcommon.h>
+#include <d3dcompiler.h>
+#include <windows.h>
+
+#include <wrl.h>
+using Microsoft::WRL::ComPtr;
+#endif  // _WIN32
+
 namespace tint {
 namespace val {
 
-Result Hlsl(const std::string& dxc_path,
-            const std::string& source,
-            Program* program) {
+Result HlslUsingDXC(const std::string& dxc_path,
+                    const std::string& source,
+                    Program* program) {
   Result result;
 
   auto dxc = utils::Command(dxc_path);
@@ -89,5 +98,77 @@
   return result;
 }
 
+#ifdef _WIN32
+Result HlslUsingFXC(const std::string& source, Program* program) {
+  Result result;
+
+  // This library leaks if an error happens in this function, but it is ok
+  // because it is loaded at most once, and the executables using HlslUsingFXC
+  // are short-lived.
+  HMODULE fxcLib = LoadLibraryA("d3dcompiler_47.dll");
+  if (fxcLib == nullptr) {
+    result.output = "Couldn't load FXC";
+    result.failed = true;
+    return result;
+  }
+
+  pD3DCompile d3dCompile =
+      reinterpret_cast<pD3DCompile>(GetProcAddress(fxcLib, "D3DCompile"));
+  if (d3dCompile == nullptr) {
+    result.output = "Couldn't load D3DCompile from FXC";
+    result.failed = true;
+    return result;
+  }
+
+  result.source = source;
+
+  bool found_an_entrypoint = false;
+  for (auto* func : program->AST().Functions()) {
+    if (func->IsEntryPoint()) {
+      found_an_entrypoint = true;
+
+      const char* profile = "";
+      switch (func->pipeline_stage()) {
+        case ast::PipelineStage::kNone:
+          result.output = "Invalid PipelineStage";
+          result.failed = true;
+          return result;
+        case ast::PipelineStage::kVertex:
+          profile = "vs_5_1";
+          break;
+        case ast::PipelineStage::kFragment:
+          profile = "ps_5_1";
+          break;
+        case ast::PipelineStage::kCompute:
+          profile = "cs_5_1";
+          break;
+      }
+
+      auto name = program->Symbols().NameFor(func->symbol());
+
+      ComPtr<ID3DBlob> compiledShader;
+      ComPtr<ID3DBlob> errors;
+      if (FAILED(d3dCompile(source.c_str(), source.length(), nullptr, nullptr,
+                            nullptr, name.c_str(), profile, 0, 0,
+                            &compiledShader, &errors))) {
+        result.output = static_cast<char*>(errors->GetBufferPointer());
+        result.failed = true;
+        return result;
+      }
+    }
+  }
+
+  FreeLibrary(fxcLib);
+
+  if (!found_an_entrypoint) {
+    result.output = "No entrypoint found";
+    result.failed = true;
+    return result;
+  }
+
+  return result;
+}
+#endif  // _WIN32
+
 }  // namespace val
 }  // namespace tint
diff --git a/src/val/val.h b/src/val/val.h
index 309e44d..fd3ae0a 100644
--- a/src/val/val.h
+++ b/src/val/val.h
@@ -41,9 +41,18 @@
 /// @param source the generated HLSL source
 /// @param program the HLSL program
 /// @return the result of the compile
-Result Hlsl(const std::string& dxc_path,
-            const std::string& source,
-            Program* program);
+Result HlslUsingDXC(const std::string& dxc_path,
+                    const std::string& source,
+                    Program* program);
+
+#ifdef _WIN32
+/// Hlsl attempts to compile the shader with FXC, verifying that the shader
+/// compiles successfully.
+/// @param source the generated HLSL source
+/// @param program the HLSL program
+/// @return the result of the compile
+Result HlslUsingFXC(const std::string& source, Program* program);
+#endif  // _WIN32
 
 /// Msl attempts to compile the shader with the Metal Shader Compiler,
 /// verifying that the shader compiles successfully.