val: Use the metal API to validate MSL shaders, if available Roughly 4x faster than validating with the MSL executable. Change-Id: I6566fa29622475c459eb3a988a842a9e19d4be6f Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/53680 Kokoro: Kokoro <noreply+kokoro@google.com> Reviewed-by: Antonio Maiorano <amaiorano@google.com> Commit-Queue: Ben Clayton <bclayton@google.com>
diff --git a/samples/main.cc b/samples/main.cc index 5d65afb..2d43d3f 100644 --- a/samples/main.cc +++ b/samples/main.cc
@@ -892,6 +892,16 @@ #endif // TINT_BUILD_HLSL_WRITER #if TINT_BUILD_MSL_WRITER case Format::kMsl: { + auto* w = static_cast<tint::writer::Text*>(writer.get()); + auto msl = w->result(); +#ifdef TINT_ENABLE_MSL_VALIDATION_USING_METAL_API + auto res = tint::val::MslUsingMetalAPI(msl); + if (res.failed) { + validation_failed = true; + validation_msgs << res.source << std::endl; + validation_msgs << res.output; + } +#else #ifdef _WIN32 const char* default_xcrun_exe = "metal.exe"; #else @@ -901,8 +911,6 @@ ? default_xcrun_exe : options.xcrun_path); if (xcrun.Found()) { - auto* w = static_cast<tint::writer::Text*>(writer.get()); - auto msl = w->result(); auto res = tint::val::Msl(xcrun.Path(), msl); if (res.failed) { validation_failed = true; @@ -913,8 +921,10 @@ validation_failed = true; validation_msgs << "xcrun executable not found. Cannot validate"; } +#endif // TINT_ENABLE_MSL_VALIDATION_USING_METAL_API break; } + #endif // TINT_BUILD_MSL_WRITER default: break;
diff --git a/src/BUILD.gn b/src/BUILD.gn index 57b816d..2696075 100644 --- a/src/BUILD.gn +++ b/src/BUILD.gn
@@ -239,7 +239,8 @@ ############################################################################### source_set("tint_val") { sources = [ - "val/val.cc", + "val/hlsl.cc", + "val/msl.cc", "val/val.h", ] public_deps = [ ":tint_utils_io" ]
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 16d5a90..1d5c6de 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt
@@ -462,9 +462,24 @@ ## Tint validation utilities. Used by tests and the tint executable. add_library(tint_val - val/val.cc + val/hlsl.cc + val/msl.cc val/val.h ) + +# If we're building on mac / ios and we have CoreGraphics, then we can use the +# metal API to validate our shaders. This is roughly 4x faster than invoking +# the metal shader compiler executable. +if(APPLE) + find_library(LIB_CORE_GRAPHICS CoreGraphics) + if(LIB_CORE_GRAPHICS) + target_sources(tint_val PRIVATE "val/msl_metal.mm") + target_compile_definitions(tint_val PUBLIC "-DTINT_ENABLE_MSL_VALIDATION_USING_METAL_API=1") + target_compile_options(tint_val PRIVATE "-fmodules" "-fcxx-modules") + target_link_options(tint_val PUBLIC "-framework" "CoreGraphics") + endif() +endif() + tint_default_compile_options(tint_val) target_link_libraries(tint_val tint_utils_io)
diff --git a/src/test_main.cc b/src/test_main.cc index 5cff409..cd1ea39 100644 --- a/src/test_main.cc +++ b/src/test_main.cc
@@ -102,6 +102,9 @@ #endif // TINT_BUILD_HLSL_WRITER #if TINT_BUILD_MSL_WRITER +#ifdef TINT_ENABLE_MSL_VALIDATION_USING_METAL_API + std::cout << "MSL validation with metal API enabled" << std::endl; +#else // This must be kept alive for the duration of RUN_ALL_TESTS() as the c_str() // is passed into tint::writer::msl::EnableMSLValidation(), which does not // make a copy. This is to work around Chromium's strict rules on globals @@ -124,6 +127,7 @@ } else { std::cout << "MSL validation with XCode SDK is not enabled" << std::endl; } +#endif // TINT_ENABLE_MSL_VALIDATION_USING_METAL_API #endif // TINT_BUILD_MSL_WRITER #if TINT_BUILD_SPV_READER
diff --git a/src/val/val.cc b/src/val/hlsl.cc similarity index 71% rename from src/val/val.cc rename to src/val/hlsl.cc index f755e15..23a67e6 100644 --- a/src/val/val.cc +++ b/src/val/hlsl.cc
@@ -89,45 +89,5 @@ return result; } -Result Msl(const std::string& xcrun_path, const std::string& source) { - Result result; - - auto xcrun = utils::Command(xcrun_path); - if (!xcrun.Found()) { - result.output = "xcrun not found at '" + std::string(xcrun_path) + "'"; - result.failed = true; - return result; - } - - result.source = source; - - utils::TmpFile file(".metal"); - file << result.source; - -#ifdef _WIN32 - // On Windows, we should actually be running metal.exe from the Metal - // Developer Tools for Windows - auto res = xcrun("-x", "metal", "-c", "-o", "NUL", file.Path()); -#else - auto res = - xcrun("-sdk", "macosx", "metal", "-o", "/dev/null", "-c", file.Path()); -#endif - if (!res.out.empty()) { - if (!result.output.empty()) { - result.output += "\n"; - } - result.output += res.out; - } - if (!res.err.empty()) { - if (!result.output.empty()) { - result.output += "\n"; - } - result.output += res.err; - } - result.failed = (res.error_code != 0); - - return result; -} - } // namespace val } // namespace tint
diff --git a/src/val/msl.cc b/src/val/msl.cc new file mode 100644 index 0000000..2b77a78 --- /dev/null +++ b/src/val/msl.cc
@@ -0,0 +1,66 @@ +// Copyright 2021 The Tint 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 "src/val/val.h" + +#include "src/ast/module.h" +#include "src/program.h" +#include "src/utils/io/command.h" +#include "src/utils/io/tmpfile.h" + +namespace tint { +namespace val { + +Result Msl(const std::string& xcrun_path, const std::string& source) { + Result result; + + auto xcrun = utils::Command(xcrun_path); + if (!xcrun.Found()) { + result.output = "xcrun not found at '" + std::string(xcrun_path) + "'"; + result.failed = true; + return result; + } + + result.source = source; + + utils::TmpFile file(".metal"); + file << result.source; + +#ifdef _WIN32 + // On Windows, we should actually be running metal.exe from the Metal + // Developer Tools for Windows + auto res = xcrun("-x", "metal", "-c", "-o", "NUL", file.Path()); +#else + auto res = + xcrun("-sdk", "macosx", "metal", "-o", "/dev/null", "-c", file.Path()); +#endif + if (!res.out.empty()) { + if (!result.output.empty()) { + result.output += "\n"; + } + result.output += res.out; + } + if (!res.err.empty()) { + if (!result.output.empty()) { + result.output += "\n"; + } + result.output += res.err; + } + result.failed = (res.error_code != 0); + + return result; +} + +} // namespace val +} // namespace tint
diff --git a/src/val/msl_metal.mm b/src/val/msl_metal.mm new file mode 100644 index 0000000..63b4961 --- /dev/null +++ b/src/val/msl_metal.mm
@@ -0,0 +1,58 @@ +// Copyright 2021 The Tint 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. + +#ifdef TINT_ENABLE_MSL_VALIDATION_USING_METAL_API + +@import Metal; + +// Disable: error: treating #include as an import of module 'std.string' +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wauto-import" +#include "src/val/val.h" +#pragma clang diagnostic pop + +namespace tint { +namespace val { + +Result MslUsingMetalAPI(const std::string& src) { + tint::val::Result result; + + NSError* error = nil; + + id<MTLDevice> device = MTLCreateSystemDefaultDevice(); + if (!device) { + result.output = "MTLCreateSystemDefaultDevice returned null"; + result.failed = true; + return result; + } + + NSString* source = [NSString stringWithCString:src.c_str() + encoding:NSUTF8StringEncoding]; + + id<MTLLibrary> library = [device newLibraryWithSource:source + options:nil + error:&error]; + if (!library) { + NSString* output = [error localizedDescription]; + result.output = [output UTF8String]; + result.failed = true; + } + + return result; +} + +} // namespace val +} // namespace tint + +#endif // TINT_ENABLE_MSL_VALIDATION_USING_METAL_API
diff --git a/src/val/val.h b/src/val/val.h index 489dcdc..309e44d 100644 --- a/src/val/val.h +++ b/src/val/val.h
@@ -52,6 +52,14 @@ /// @return the result of the compile Result Msl(const std::string& xcrun_path, const std::string& source); +#ifdef TINT_ENABLE_MSL_VALIDATION_USING_METAL_API +/// Msl attempts to compile the shader with the runtime Metal Shader Compiler +/// API, verifying that the shader compiles successfully. +/// @param source the generated MSL source +/// @return the result of the compile +Result MslUsingMetalAPI(const std::string& source); +#endif // TINT_ENABLE_MSL_VALIDATION_USING_METAL_API + } // namespace val } // namespace tint
diff --git a/src/writer/msl/test_helper.cc b/src/writer/msl/test_helper.cc index 6672507..b129c8a 100644 --- a/src/writer/msl/test_helper.cc +++ b/src/writer/msl/test_helper.cc
@@ -32,6 +32,13 @@ } val::Result Validate(Program* program) { +#ifdef TINT_ENABLE_MSL_VALIDATION_USING_METAL_API + auto gen = std::make_unique<GeneratorImpl>(program); + if (!gen->Generate()) { + return {true, gen->error(), ""}; + } + return tint::val::MslUsingMetalAPI(gen->result()); +#else // TINT_ENABLE_MSL_VALIDATION_USING_METAL_API if (!xcrun_path) { return val::Result{}; } @@ -41,6 +48,7 @@ return {true, gen->error(), ""}; } return val::Msl(xcrun_path, gen->result()); +#endif // TINT_ENABLE_MSL_VALIDATION_USING_METAL_API } } // namespace msl