Merge pull request #110264 from stuartcarnie/109846/metal_version

Metal: Ensure baked Metal binaries can be loaded on the minimum target OS
This commit is contained in:
Clay John 2025-09-08 08:23:15 -07:00 committed by GitHub
commit 0e8df80231
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 250 additions and 44 deletions

View file

@ -143,11 +143,15 @@ class API_AVAILABLE(macos(11.0), ios(14.0), tvos(14.0)) MetalDeviceProperties {
private:
void init_features(id<MTLDevice> p_device);
void init_limits(id<MTLDevice> p_device);
void init_os_props();
public:
MetalFeatures features;
MetalLimits limits;
// maj * 10000 + min * 100 + patch
uint32_t os_version;
SampleCount find_nearest_supported_sample_count(RenderingDevice::TextureSamples p_samples) const;
MetalDeviceProperties(id<MTLDevice> p_device);

View file

@ -311,9 +311,15 @@ void MetalDeviceProperties::init_limits(id<MTLDevice> p_device) {
#endif
}
void MetalDeviceProperties::init_os_props() {
NSOperatingSystemVersion ver = NSProcessInfo.processInfo.operatingSystemVersion;
os_version = (uint32_t)ver.majorVersion * 10000 + (uint32_t)ver.minorVersion * 100 + (uint32_t)ver.patchVersion;
}
MetalDeviceProperties::MetalDeviceProperties(id<MTLDevice> p_device) {
init_features(p_device);
init_limits(p_device);
init_os_props();
}
MetalDeviceProperties::~MetalDeviceProperties() {

View file

@ -103,6 +103,11 @@ extern os_log_t LOG_DRIVER;
// Used for dynamic tracing.
extern os_log_t LOG_INTERVALS;
_FORCE_INLINE_ static uint32_t make_msl_version(uint32_t major, uint32_t minor = 0, uint32_t patch = 0) {
return (major * 10000) + (minor * 100) + patch;
_FORCE_INLINE_ static uint32_t make_msl_version(uint32_t p_major, uint32_t p_minor = 0, uint32_t p_patch = 0) {
return (p_major * 10000) + (p_minor * 100) + p_patch;
}
_FORCE_INLINE_ static void parse_msl_version(uint32_t p_version, uint32_t &r_major, uint32_t &r_minor) {
r_major = p_version / 10000;
r_minor = (p_version % 10000) / 100;
}

View file

@ -1135,12 +1135,17 @@ RDD::ShaderID RenderingDeviceDriverMetal::shader_create_from_container(const Ref
// We need to regenerate the shader if the cache is moved to an incompatible device.
ERR_FAIL_COND_V_MSG(device_properties->features.argument_buffers_tier < MTLArgumentBuffersTier2 && mtl_reflection_data.uses_argument_buffers(),
RDD::ShaderID(),
"Shader was generated with argument buffers, but device has limited support");
"Shader was compiled with argument buffers enabled, but this device does not support them");
uint32_t msl_version = make_msl_version(device_properties->features.mslVersionMajor, device_properties->features.mslVersionMinor);
ERR_FAIL_COND_V_MSG(msl_version < mtl_reflection_data.msl_version,
RDD::ShaderID(),
"Shader was compiled with a newer version of Metal than is available on the device.");
"Shader was compiled for a newer version of Metal");
MTLGPUFamily compiled_gpu_family = static_cast<MTLGPUFamily>(mtl_reflection_data.profile.gpu);
ERR_FAIL_COND_V_MSG(device_properties->features.highestFamily < compiled_gpu_family,
RDD::ShaderID(),
"Shader was generated for a newer Apple GPU");
MTLCompileOptions *options = [MTLCompileOptions new];
uint32_t major = mtl_reflection_data.msl_version / 10000;
@ -1181,6 +1186,9 @@ RDD::ShaderID RenderingDeviceDriverMetal::shader_create_from_container(const Ref
MDLibrary *library = nil;
if (shader_data.library_size > 0) {
ERR_FAIL_COND_V_MSG(mtl_reflection_data.os_min_version > device_properties->os_version,
RDD::ShaderID(),
"Metal shader binary was generated for a newer target OS");
dispatch_data_t binary = dispatch_data_create(decompressed_code.ptr() + shader_data.source_size, shader_data.library_size, dispatch_get_main_queue(), DISPATCH_DATA_DESTRUCTOR_DEFAULT);
library = [MDLibrary newLibraryWithCacheEntry:cd
device:device

View file

@ -41,6 +41,25 @@ const uint32_t VIEW_MASK_BUFFER_INDEX = 24;
class RenderingShaderContainerFormatMetal;
class MinOsVersion {
uint32_t version;
public:
String to_compiler_os_version() const;
bool is_null() const { return version == UINT32_MAX; }
bool is_valid() const { return version != UINT32_MAX; }
MinOsVersion(const String &p_version);
explicit MinOsVersion(uint32_t p_version) :
version(p_version) {}
MinOsVersion() :
version(UINT32_MAX) {}
bool operator>(uint32_t p_other) {
return version > p_other;
}
};
/// @brief A minimal structure that defines a device profile for Metal.
///
/// This structure is used by the `RenderingShaderContainerMetal` class to
@ -53,17 +72,20 @@ struct MetalDeviceProfile {
iOS = 1,
};
/// @brief The GPU family.
/*! @brief The GPU family.
*
* NOTE: These values match Apple's MTLGPUFamily
*/
enum class GPU : uint32_t {
Apple1,
Apple2,
Apple3,
Apple4,
Apple5,
Apple6,
Apple7,
Apple8,
Apple9,
Apple1 = 1001,
Apple2 = 1002,
Apple3 = 1003,
Apple4 = 1004,
Apple5 = 1005,
Apple6 = 1006,
Apple7 = 1007,
Apple8 = 1008,
Apple9 = 1009,
};
enum class ArgumentBuffersTier : uint32_t {
@ -108,6 +130,13 @@ public:
/// The Metal language version specified when compiling SPIR-V to MSL.
/// Format is major * 10000 + minor * 100 + patch.
uint32_t msl_version = UINT32_MAX;
/*! @brief The minimum supported OS version for shaders baked to a `.metallib`.
*
* NOTE: This property is only valid when shaders are baked to a .metalllib
*
* Format is major * 10000 + minor * 100 + patch.
*/
MinOsVersion os_min_version;
uint32_t flags = NONE;
/// @brief Returns `true` if the shader is compiled with multi-view support.
@ -210,9 +239,23 @@ public:
HeaderData mtl_reflection_data; // compliment to reflection_data
Vector<StageData> mtl_shaders; // compliment to shaders
private:
struct ToolchainProperties {
MinOsVersion os_version_min_required;
uint32_t metal_version = UINT32_MAX;
_FORCE_INLINE_ bool is_null() const { return os_version_min_required.is_null() || metal_version == UINT32_MAX; }
_FORCE_INLINE_ bool is_valid() const { return !is_null(); }
};
ToolchainProperties compiler_props;
void _initialize_toolchain_properties();
private:
const MetalDeviceProfile *device_profile = nullptr;
bool export_mode = false;
MinOsVersion min_os_version;
Vector<UniformData> mtl_reflection_binding_set_uniforms_data; // compliment to reflection_binding_set_uniforms_data
Vector<SpecializationData> mtl_reflection_specialization_data; // compliment to reflection_specialization_data
@ -224,6 +267,7 @@ public:
void set_export_mode(bool p_export_mode) { export_mode = p_export_mode; }
void set_device_profile(const MetalDeviceProfile *p_device_profile) { device_profile = p_device_profile; }
void set_min_os_version(const MinOsVersion p_min_os_version) { min_os_version = p_min_os_version; }
struct MetalShaderReflection {
Vector<Vector<UniformData>> uniform_sets;
@ -253,6 +297,7 @@ protected:
class RenderingShaderContainerFormatMetal : public RenderingShaderContainerFormat {
bool export_mode = false;
MinOsVersion min_os_version;
const MetalDeviceProfile *device_profile = nullptr;
@ -260,6 +305,6 @@ public:
virtual Ref<RenderingShaderContainer> create_container() const override;
virtual ShaderLanguageVersion get_shader_language_version() const override;
virtual ShaderSpirvVersion get_shader_spirv_version() const override;
RenderingShaderContainerFormatMetal(const MetalDeviceProfile *p_device_profile, bool p_export = false);
RenderingShaderContainerFormatMetal(const MetalDeviceProfile *p_device_profile, bool p_export = false, const MinOsVersion p_min_os_version = MinOsVersion());
virtual ~RenderingShaderContainerFormatMetal() = default;
};

View file

@ -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,14 +674,20 @@ 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
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");
}
}
uint32_t binary_data_size = binary_data.size();
@ -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;
}
}

View file

@ -54,7 +54,7 @@ bool ShaderBakerExportPlugin::_is_active(const Vector<String> &p_features) const
return RendererSceneRenderRD::get_singleton() != nullptr && RendererRD::MaterialStorage::get_singleton() != nullptr && p_features.has("shader_baker");
}
bool ShaderBakerExportPlugin::_initialize_container_format(const Ref<EditorExportPlatform> &p_platform, const Vector<String> &p_features) {
bool ShaderBakerExportPlugin::_initialize_container_format(const Ref<EditorExportPlatform> &p_platform, const Vector<String> &p_features, const Ref<EditorExportPreset> &p_preset) {
Variant driver_variant = GLOBAL_GET("rendering/rendering_device/driver." + p_platform->get_os_name().to_lower());
if (!driver_variant.is_string()) {
driver_variant = GLOBAL_GET("rendering/rendering_device/driver");
@ -67,7 +67,7 @@ bool ShaderBakerExportPlugin::_initialize_container_format(const Ref<EditorExpor
for (Ref<ShaderBakerExportPluginPlatform> platform : platforms) {
if (platform->matches_driver(shader_container_driver)) {
shader_container_format = platform->create_shader_container_format(p_platform);
shader_container_format = platform->create_shader_container_format(p_platform, get_export_preset());
ERR_FAIL_NULL_V_MSG(shader_container_format, false, "Unable to create shader container format for the export platform.");
return true;
}
@ -99,7 +99,7 @@ bool ShaderBakerExportPlugin::_begin_customize_resources(const Ref<EditorExportP
return false;
}
if (!_initialize_container_format(p_platform, p_features)) {
if (!_initialize_container_format(p_platform, p_features, get_export_preset())) {
return false;
}

View file

@ -38,7 +38,7 @@ class ShaderBakerExportPluginPlatform : public RefCounted {
GDCLASS(ShaderBakerExportPluginPlatform, RefCounted);
public:
virtual RenderingShaderContainerFormat *create_shader_container_format(const Ref<EditorExportPlatform> &p_platform) = 0;
virtual RenderingShaderContainerFormat *create_shader_container_format(const Ref<EditorExportPlatform> &p_platform, const Ref<EditorExportPreset> &p_preset) = 0;
virtual bool matches_driver(const String &p_driver) = 0;
virtual ~ShaderBakerExportPluginPlatform() {}
};
@ -82,7 +82,7 @@ protected:
virtual String get_name() const override;
virtual bool _is_active(const Vector<String> &p_features) const;
virtual bool _initialize_container_format(const Ref<EditorExportPlatform> &p_platform, const Vector<String> &p_features);
virtual bool _initialize_container_format(const Ref<EditorExportPlatform> &p_platform, const Vector<String> &p_features, const Ref<EditorExportPreset> &p_preset);
virtual void _cleanup_container_format();
virtual bool _initialize_cache_directory();
virtual bool _begin_customize_resources(const Ref<EditorExportPlatform> &p_platform, const Vector<String> &p_features) override;

View file

@ -34,7 +34,7 @@
#include <windows.h>
RenderingShaderContainerFormat *ShaderBakerExportPluginPlatformD3D12::create_shader_container_format(const Ref<EditorExportPlatform> &p_platform) {
RenderingShaderContainerFormat *ShaderBakerExportPluginPlatformD3D12::create_shader_container_format(const Ref<EditorExportPlatform> &p_platform, const Ref<EditorExportPreset> &p_preset) {
if (lib_d3d12 == nullptr) {
lib_d3d12 = LoadLibraryW(L"D3D12.dll");
ERR_FAIL_NULL_V_MSG(lib_d3d12, nullptr, "Unable to load D3D12.dll.");

View file

@ -39,7 +39,7 @@ private:
void *lib_d3d12 = nullptr;
public:
virtual RenderingShaderContainerFormat *create_shader_container_format(const Ref<EditorExportPlatform> &p_platform) override;
virtual RenderingShaderContainerFormat *create_shader_container_format(const Ref<EditorExportPlatform> &p_platform, const Ref<EditorExportPreset> &p_preset) override;
virtual bool matches_driver(const String &p_driver) override;
virtual ~ShaderBakerExportPluginPlatformD3D12() override;
};

View file

@ -32,18 +32,22 @@
#include "drivers/metal/rendering_shader_container_metal.h"
RenderingShaderContainerFormat *ShaderBakerExportPluginPlatformMetal::create_shader_container_format(const Ref<EditorExportPlatform> &p_platform) {
RenderingShaderContainerFormat *ShaderBakerExportPluginPlatformMetal::create_shader_container_format(const Ref<EditorExportPlatform> &p_platform, const Ref<EditorExportPreset> &p_preset) {
const String &os_name = p_platform->get_os_name();
const MetalDeviceProfile *profile;
String min_os_version;
if (os_name == U"macOS") {
profile = MetalDeviceProfile::get_profile(MetalDeviceProfile::Platform::macOS, MetalDeviceProfile::GPU::Apple7);
// Godot metal doesn't support x86_64 mac so no need to worry about that version
min_os_version = p_preset->get("application/min_macos_version_arm64");
} else if (os_name == U"iOS") {
profile = MetalDeviceProfile::get_profile(MetalDeviceProfile::Platform::iOS, MetalDeviceProfile::GPU::Apple7);
min_os_version = p_preset->get("application/min_ios_version");
} else {
ERR_FAIL_V_MSG(nullptr, vformat("Unsupported platform: %s", os_name));
}
return memnew(RenderingShaderContainerFormatMetal(profile, true));
return memnew(RenderingShaderContainerFormatMetal(profile, true, min_os_version));
}
bool ShaderBakerExportPluginPlatformMetal::matches_driver(const String &p_driver) {

View file

@ -34,6 +34,6 @@
class ShaderBakerExportPluginPlatformMetal : public ShaderBakerExportPluginPlatform {
public:
virtual RenderingShaderContainerFormat *create_shader_container_format(const Ref<EditorExportPlatform> &p_platform) override;
virtual RenderingShaderContainerFormat *create_shader_container_format(const Ref<EditorExportPlatform> &p_platform, const Ref<EditorExportPreset> &p_preset) override;
virtual bool matches_driver(const String &p_driver) override;
};

View file

@ -32,7 +32,7 @@
#include "drivers/vulkan/rendering_shader_container_vulkan.h"
RenderingShaderContainerFormat *ShaderBakerExportPluginPlatformVulkan::create_shader_container_format(const Ref<EditorExportPlatform> &p_platform) {
RenderingShaderContainerFormat *ShaderBakerExportPluginPlatformVulkan::create_shader_container_format(const Ref<EditorExportPlatform> &p_platform, const Ref<EditorExportPreset> &p_preset) {
return memnew(RenderingShaderContainerFormatVulkan);
}

View file

@ -36,6 +36,6 @@ class ShaderBakerExportPluginPlatformVulkan : public ShaderBakerExportPluginPlat
GDCLASS(ShaderBakerExportPluginPlatformVulkan, ShaderBakerExportPluginPlatform);
public:
virtual RenderingShaderContainerFormat *create_shader_container_format(const Ref<EditorExportPlatform> &p_platform) override;
virtual RenderingShaderContainerFormat *create_shader_container_format(const Ref<EditorExportPlatform> &p_platform, const Ref<EditorExportPreset> &p_preset) override;
virtual bool matches_driver(const String &p_driver) override;
};