|
@@ -28,11 +28,12 @@
|
|
|
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
|
|
/**************************************************************************/
|
|
|
|
|
|
-#include "rendering_shader_container_metal.h"
|
|
|
+#import "rendering_shader_container_metal.h"
|
|
|
|
|
|
-#include "servers/rendering/rendering_device.h"
|
|
|
+#import "metal_utils.h"
|
|
|
|
|
|
#import "core/io/marshalls.h"
|
|
|
+#import "servers/rendering/rendering_device.h"
|
|
|
|
|
|
#import <Metal/Metal.h>
|
|
|
#import <spirv.hpp>
|
|
@@ -85,6 +86,71 @@ const MetalDeviceProfile *MetalDeviceProfile::get_profile(MetalDeviceProfile::Pl
|
|
|
return &profiles.insert(key, res)->value;
|
|
|
}
|
|
|
|
|
|
+void RenderingShaderContainerMetal::_initialize_toolchain_properties() {
|
|
|
+ if (compiler_props.is_valid()) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ String sdk;
|
|
|
+ switch (device_profile->platform) {
|
|
|
+ case MetalDeviceProfile::Platform::macOS:
|
|
|
+ sdk = "macosx";
|
|
|
+ break;
|
|
|
+ case MetalDeviceProfile::Platform::iOS:
|
|
|
+ sdk = "iphoneos";
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ Vector<String> parts{ "echo", R"("")", "|", "/usr/bin/xcrun", "-sdk", sdk, "metal", "-E", "-dM", "-x", "metal", "-", "|", "grep", "-E", R"(\"__METAL_VERSION__|__ENVIRONMENT_OS\")" };
|
|
|
+
|
|
|
+ // Compile metal shaders for the minimum supported target instead of the host machine
|
|
|
+ if (min_os_version.is_valid()) {
|
|
|
+ switch (device_profile->platform) {
|
|
|
+ case MetalDeviceProfile::Platform::macOS: {
|
|
|
+ parts.push_back("-mmacosx-version-min=" + min_os_version.to_compiler_os_version());
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ case MetalDeviceProfile::Platform::iOS: {
|
|
|
+ parts.push_back("-mios-version-min=" + min_os_version.to_compiler_os_version());
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ String s = " ";
|
|
|
+ List<String> args = { "-c", String(" ").join(parts) };
|
|
|
+
|
|
|
+ String r_pipe;
|
|
|
+ int exit_code;
|
|
|
+ Error err = OS::get_singleton()->execute("sh", args, &r_pipe, &exit_code, true);
|
|
|
+ ERR_FAIL_COND_MSG(err != OK, "Failed to determine Metal toolchain properties");
|
|
|
+
|
|
|
+ // Parse the lines, which are in the form:
|
|
|
+ //
|
|
|
+ // #define VARNAME VALUE
|
|
|
+ Vector<String> lines = r_pipe.split("\n", false);
|
|
|
+ for (String &line : lines) {
|
|
|
+ Vector<String> name_val = line.trim_prefix("#define ").split(" ");
|
|
|
+ if (name_val.size() != 2) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ if (name_val[0] == "__ENVIRONMENT_OS_VERSION_MIN_REQUIRED__") {
|
|
|
+ compiler_props.os_version_min_required = MinOsVersion((uint32_t)name_val[1].to_int());
|
|
|
+ } else if (name_val[0] == "__METAL_VERSION__") {
|
|
|
+ uint32_t ver = (uint32_t)name_val[1].to_int();
|
|
|
+ uint32_t maj = ver / 100;
|
|
|
+ uint32_t min = (ver % 100) / 10;
|
|
|
+ compiler_props.metal_version = make_msl_version(maj, min);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (compiler_props.is_valid()) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return;
|
|
|
+}
|
|
|
+
|
|
|
Error RenderingShaderContainerMetal::compile_metal_source(const char *p_source, const StageData &p_stage_data, Vector<uint8_t> &r_binary_data) {
|
|
|
String name(shader_name.ptr());
|
|
|
if (name.contains_char(':')) {
|
|
@@ -115,9 +181,26 @@ Error RenderingShaderContainerMetal::compile_metal_source(const char *p_source,
|
|
|
break;
|
|
|
}
|
|
|
|
|
|
- // Build the metallib binary.
|
|
|
+ // Build the .metallib binary.
|
|
|
{
|
|
|
List<String> args{ "-sdk", sdk, "metal", "-O3" };
|
|
|
+
|
|
|
+ // Compile metal shaders for the minimum supported target instead of the host machine.
|
|
|
+ if (min_os_version.is_valid()) {
|
|
|
+ switch (device_profile->platform) {
|
|
|
+ case MetalDeviceProfile::Platform::macOS: {
|
|
|
+ args.push_back("-mmacosx-version-min=" + min_os_version.to_compiler_os_version());
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ case MetalDeviceProfile::Platform::iOS: {
|
|
|
+ args.push_back("-mios-version-min=" + min_os_version.to_compiler_os_version());
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ WARN_PRINT_ONCE(vformat("Minimum target OS version is not set, so baking shaders for Metal will target the default version of your toolchain: %s", compiler_props.os_version_min_required.to_compiler_os_version()));
|
|
|
+ }
|
|
|
+
|
|
|
if (p_stage_data.is_position_invariant) {
|
|
|
args.push_back("-fpreserve-invariance");
|
|
|
}
|
|
@@ -175,6 +258,10 @@ bool RenderingShaderContainerMetal::_set_code_from_spirv(const Vector<RenderingD
|
|
|
using spirv_cross::CompilerMSL;
|
|
|
using spirv_cross::Resource;
|
|
|
|
|
|
+ if (export_mode) {
|
|
|
+ _initialize_toolchain_properties();
|
|
|
+ }
|
|
|
+
|
|
|
// initialize Metal-specific reflection data
|
|
|
shaders.resize(p_spirv.size());
|
|
|
mtl_shaders.resize(p_spirv.size());
|
|
@@ -182,6 +269,7 @@ bool RenderingShaderContainerMetal::_set_code_from_spirv(const Vector<RenderingD
|
|
|
mtl_reflection_specialization_data.resize(reflection_specialization_data.size());
|
|
|
|
|
|
mtl_reflection_data.set_needs_view_mask_buffer(reflection_data.has_multiview);
|
|
|
+ mtl_reflection_data.profile = *device_profile;
|
|
|
|
|
|
// set_indexes will contain the starting offsets of each descriptor set in the binding set uniforms data
|
|
|
// including the last one, which is the size of reflection_binding_set_uniforms_count.
|
|
@@ -199,10 +287,25 @@ bool RenderingShaderContainerMetal::_set_code_from_spirv(const Vector<RenderingD
|
|
|
set_indexes[set_indexes_size - 1] = offset;
|
|
|
}
|
|
|
CompilerMSL::Options msl_options{};
|
|
|
- // MAJOR * 10000 + MINOR * 100
|
|
|
- uint32_t msl_version = CompilerMSL::Options::make_msl_version(device_profile->features.mslVersionMajor, device_profile->features.mslVersionMinor);
|
|
|
- msl_options.set_msl_version(device_profile->features.mslVersionMajor, device_profile->features.mslVersionMinor);
|
|
|
- mtl_reflection_data.msl_version = msl_options.msl_version;
|
|
|
+
|
|
|
+ // Determine Metal language version.
|
|
|
+ uint32_t msl_version = 0;
|
|
|
+ {
|
|
|
+ if (export_mode && compiler_props.is_valid()) {
|
|
|
+ // Use the properties determined by the toolchain and minimum OS version.
|
|
|
+ msl_version = compiler_props.metal_version;
|
|
|
+ mtl_reflection_data.os_min_version = compiler_props.os_version_min_required;
|
|
|
+ } else {
|
|
|
+ msl_version = make_msl_version(device_profile->features.mslVersionMajor, device_profile->features.mslVersionMinor);
|
|
|
+ mtl_reflection_data.os_min_version = MinOsVersion();
|
|
|
+ }
|
|
|
+ uint32_t msl_ver_maj = 0;
|
|
|
+ uint32_t msl_ver_min = 0;
|
|
|
+ parse_msl_version(msl_version, msl_ver_maj, msl_ver_min);
|
|
|
+ msl_options.set_msl_version(msl_ver_maj, msl_ver_min);
|
|
|
+ mtl_reflection_data.msl_version = msl_version;
|
|
|
+ }
|
|
|
+
|
|
|
msl_options.platform = device_profile->platform == MetalDeviceProfile::Platform::macOS ? CompilerMSL::Options::macOS : CompilerMSL::Options::iOS;
|
|
|
|
|
|
if (device_profile->platform == MetalDeviceProfile::Platform::iOS) {
|
|
@@ -238,7 +341,7 @@ bool RenderingShaderContainerMetal::_set_code_from_spirv(const Vector<RenderingD
|
|
|
msl_options.multiview_layered_rendering = true;
|
|
|
msl_options.view_mask_buffer_index = VIEW_MASK_BUFFER_INDEX;
|
|
|
}
|
|
|
- if (msl_version >= CompilerMSL::Options::make_msl_version(3, 2)) {
|
|
|
+ if (msl_version >= make_msl_version(3, 2)) {
|
|
|
// All 3.2+ versions support device coherence, so we can disable texture fences.
|
|
|
msl_options.readwrite_texture_fences = false;
|
|
|
}
|
|
@@ -571,13 +674,19 @@ bool RenderingShaderContainerMetal::_set_code_from_spirv(const Vector<RenderingD
|
|
|
memcpy(binary_data.ptrw(), source.c_str(), stage_data.source_size);
|
|
|
|
|
|
if (export_mode) {
|
|
|
- // Try to compile the Metal source code
|
|
|
- ::Vector<uint8_t> library_data;
|
|
|
- Error compile_err = compile_metal_source(source.c_str(), stage_data, library_data);
|
|
|
- if (compile_err == OK) {
|
|
|
- stage_data.library_size = library_data.size();
|
|
|
- binary_data.resize(stage_data.source_size + stage_data.library_size);
|
|
|
- memcpy(binary_data.ptrw() + stage_data.source_size, library_data.ptr(), stage_data.library_size);
|
|
|
+ if (compiler_props.is_valid()) {
|
|
|
+ // Try to compile the Metal source code.
|
|
|
+ ::Vector<uint8_t> library_data;
|
|
|
+ Error compile_err = compile_metal_source(source.c_str(), stage_data, library_data);
|
|
|
+ if (compile_err == OK) {
|
|
|
+ // If we successfully compiled to a `.metallib`, there are greater restrictions on target platforms,
|
|
|
+ // so we must update the properties.
|
|
|
+ stage_data.library_size = library_data.size();
|
|
|
+ binary_data.resize(stage_data.source_size + stage_data.library_size);
|
|
|
+ memcpy(binary_data.ptrw() + stage_data.source_size, library_data.ptr(), stage_data.library_size);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ WARN_PRINT_ONCE("Metal shader baking limited to SPIR-V: Unable to determine toolchain properties to compile .metallib");
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -693,6 +802,7 @@ Ref<RenderingShaderContainer> RenderingShaderContainerFormatMetal::create_contai
|
|
|
result.instantiate();
|
|
|
result->set_export_mode(export_mode);
|
|
|
result->set_device_profile(device_profile);
|
|
|
+ result->set_min_os_version(min_os_version);
|
|
|
return result;
|
|
|
}
|
|
|
|
|
@@ -704,6 +814,30 @@ RenderingDeviceCommons::ShaderSpirvVersion RenderingShaderContainerFormatMetal::
|
|
|
return SHADER_SPIRV_VERSION_1_6;
|
|
|
}
|
|
|
|
|
|
-RenderingShaderContainerFormatMetal::RenderingShaderContainerFormatMetal(const MetalDeviceProfile *p_device_profile, bool p_export) :
|
|
|
- export_mode(p_export), device_profile(p_device_profile) {
|
|
|
+RenderingShaderContainerFormatMetal::RenderingShaderContainerFormatMetal(const MetalDeviceProfile *p_device_profile, bool p_export, const MinOsVersion p_min_os_version) :
|
|
|
+ export_mode(p_export), min_os_version(p_min_os_version), device_profile(p_device_profile) {
|
|
|
+}
|
|
|
+
|
|
|
+String MinOsVersion::to_compiler_os_version() const {
|
|
|
+ if (version == UINT32_MAX) {
|
|
|
+ return "";
|
|
|
+ }
|
|
|
+
|
|
|
+ uint32_t major = version / 10000;
|
|
|
+ uint32_t minor = (version % 10000) / 100;
|
|
|
+ return vformat("%d.%d", major, minor);
|
|
|
+}
|
|
|
+
|
|
|
+MinOsVersion::MinOsVersion(const String &p_version) {
|
|
|
+ int pos = p_version.find_char('.');
|
|
|
+ if (pos > 0) {
|
|
|
+ version = (uint32_t)(p_version.substr(0, pos).to_int() * 10000 +
|
|
|
+ p_version.substr(pos + 1).to_int() * 100);
|
|
|
+ } else {
|
|
|
+ version = (uint32_t)(p_version.to_int() * 10000);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (version == 0) {
|
|
|
+ version = UINT32_MAX;
|
|
|
+ }
|
|
|
}
|