diff --git a/.github/actions/godot-build/action.yml b/.github/actions/godot-build/action.yml
index bf29b7e430a..61613d26289 100644
--- a/.github/actions/godot-build/action.yml
+++ b/.github/actions/godot-build/action.yml
@@ -7,11 +7,14 @@ inputs:
tests:
description: Unit tests.
default: false
+ required: false
platform:
description: Target platform.
required: false
sconsflags:
+ description: Additional SCons flags.
default: ""
+ required: false
scons-cache:
description: The SCons cache path.
default: "${{ github.workspace }}/.scons-cache/"
diff --git a/COPYRIGHT.txt b/COPYRIGHT.txt
index 1e32c87c016..a9d6cd7d320 100644
--- a/COPYRIGHT.txt
+++ b/COPYRIGHT.txt
@@ -475,6 +475,11 @@ Comment: RVO2
Copyright: 2016, University of North Carolina at Chapel Hill
License: Apache-2.0
+Files: ./thirdparty/spirv-cross/
+Comment: SPIRV-Cross
+Copyright: 2015-2021, Arm Limited
+License: Apache-2.0 or Expat
+
Files: ./thirdparty/spirv-reflect/
Comment: SPIRV-Reflect
Copyright: 2017-2022, Google Inc.
diff --git a/SConstruct b/SConstruct
index 599db4b8deb..ca160c1762b 100644
--- a/SConstruct
+++ b/SConstruct
@@ -222,6 +222,7 @@ opts.Add(BoolVariable("xaudio2", "Enable the XAudio2 audio driver", False))
opts.Add(BoolVariable("vulkan", "Enable the vulkan rendering driver", True))
opts.Add(BoolVariable("opengl3", "Enable the OpenGL/GLES3 rendering driver", True))
opts.Add(BoolVariable("d3d12", "Enable the Direct3D 12 rendering driver", False))
+opts.Add(BoolVariable("metal", "Enable the Metal rendering driver (Apple arm64 only)", False))
opts.Add(BoolVariable("openxr", "Enable the OpenXR driver", True))
opts.Add(BoolVariable("use_volk", "Use the volk library to load the Vulkan loader dynamically", True))
opts.Add(BoolVariable("disable_exceptions", "Force disabling exception handling code", True))
diff --git a/core/core_bind.cpp b/core/core_bind.cpp
index a15085bcde5..36f662b92b9 100644
--- a/core/core_bind.cpp
+++ b/core/core_bind.cpp
@@ -692,6 +692,7 @@ void OS::_bind_methods() {
BIND_ENUM_CONSTANT(RENDERING_DRIVER_VULKAN);
BIND_ENUM_CONSTANT(RENDERING_DRIVER_OPENGL3);
BIND_ENUM_CONSTANT(RENDERING_DRIVER_D3D12);
+ BIND_ENUM_CONSTANT(RENDERING_DRIVER_METAL);
BIND_ENUM_CONSTANT(SYSTEM_DIR_DESKTOP);
BIND_ENUM_CONSTANT(SYSTEM_DIR_DCIM);
diff --git a/core/core_bind.h b/core/core_bind.h
index 69a359e4ed5..d744da25511 100644
--- a/core/core_bind.h
+++ b/core/core_bind.h
@@ -132,6 +132,7 @@ public:
RENDERING_DRIVER_VULKAN,
RENDERING_DRIVER_OPENGL3,
RENDERING_DRIVER_D3D12,
+ RENDERING_DRIVER_METAL,
};
PackedByteArray get_entropy(int p_bytes);
diff --git a/doc/classes/OS.xml b/doc/classes/OS.xml
index bc20ff4e454..9675f5af507 100644
--- a/doc/classes/OS.xml
+++ b/doc/classes/OS.xml
@@ -802,6 +802,9 @@
The Direct3D 12 rendering driver.
+
+ The Metal rendering driver.
+
Refers to the Desktop directory path.
diff --git a/drivers/SCsub b/drivers/SCsub
index e77b96cc87d..521d607740a 100644
--- a/drivers/SCsub
+++ b/drivers/SCsub
@@ -33,6 +33,8 @@ if env["opengl3"]:
SConscript("gl_context/SCsub")
SConscript("gles3/SCsub")
SConscript("egl/SCsub")
+if env["metal"]:
+ SConscript("metal/SCsub")
# Core dependencies
SConscript("png/SCsub")
diff --git a/drivers/metal/README.md b/drivers/metal/README.md
new file mode 100644
index 00000000000..30cfa523609
--- /dev/null
+++ b/drivers/metal/README.md
@@ -0,0 +1,39 @@
+# Metal Rendering Device
+
+This document aims to describe the Metal rendering device implementation in Godot.
+
+## Future work / ideas
+
+* Use placement heaps
+* Explicit hazard tracking
+* [MetalFX] upscaling support?
+
+## Acknowledgments
+
+The Metal rendering owes a lot to the work of the [MoltenVK] project, which is a Vulkan implementation on top of Metal.
+In accordance with the Apache 2.0 license, the following copyright notices have been included where applicable:
+
+```
+/**************************************************************************/
+/* */
+/* Portions of this code were derived from MoltenVK. */
+/* */
+/* Copyright (c) 2015-2023 The Brenwill Workshop Ltd. */
+/* (http://www.brenwill.com) */
+/* */
+/* 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. */
+/**************************************************************************/
+```
+
+[MoltenVK]: https://github.com/KhronosGroup/MoltenVK
+[MetalFX]: https://developer.apple.com/documentation/metalfx?language=objc
diff --git a/drivers/metal/SCsub b/drivers/metal/SCsub
new file mode 100644
index 00000000000..30129b78061
--- /dev/null
+++ b/drivers/metal/SCsub
@@ -0,0 +1,49 @@
+#!/usr/bin/env python
+
+Import("env")
+
+env_metal = env.Clone()
+
+# Thirdparty source files
+
+thirdparty_obj = []
+
+thirdparty_dir = "#thirdparty/spirv-cross/"
+thirdparty_sources = [
+ "spirv_cfg.cpp",
+ "spirv_cross_util.cpp",
+ "spirv_cross.cpp",
+ "spirv_parser.cpp",
+ "spirv_msl.cpp",
+ "spirv_reflect.cpp",
+ "spirv_glsl.cpp",
+ "spirv_cross_parsed_ir.cpp",
+]
+thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources]
+
+env_metal.Prepend(CPPPATH=[thirdparty_dir, thirdparty_dir + "/include"])
+
+# Must enable exceptions for SPIRV-Cross; otherwise, it will abort the process on errors.
+if "-fno-exceptions" in env_metal["CXXFLAGS"]:
+ env_metal["CXXFLAGS"].remove("-fno-exceptions")
+env_metal.Append(CXXFLAGS=["-fexceptions"])
+
+env_thirdparty = env_metal.Clone()
+env_thirdparty.disable_warnings()
+env_thirdparty.add_source_files(thirdparty_obj, thirdparty_sources)
+env_metal.drivers_sources += thirdparty_obj
+
+# Enable C++20 for the Objective-C++ Metal code, which uses C++20 concepts.
+if "-std=gnu++17" in env_metal["CXXFLAGS"]:
+ env_metal["CXXFLAGS"].remove("-std=gnu++17")
+env_metal.Append(CXXFLAGS=["-std=c++20"])
+
+# Driver source files
+
+driver_obj = []
+
+env_metal.add_source_files(driver_obj, "*.mm")
+env.drivers_sources += driver_obj
+
+# Needed to force rebuilding the driver files when the thirdparty library is updated.
+env.Depends(driver_obj, thirdparty_obj)
diff --git a/drivers/metal/metal_device_properties.h b/drivers/metal/metal_device_properties.h
new file mode 100644
index 00000000000..7467e8ceb49
--- /dev/null
+++ b/drivers/metal/metal_device_properties.h
@@ -0,0 +1,141 @@
+/**************************************************************************/
+/* metal_device_properties.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+/**************************************************************************/
+/* */
+/* Portions of this code were derived from MoltenVK. */
+/* */
+/* Copyright (c) 2015-2023 The Brenwill Workshop Ltd. */
+/* (http://www.brenwill.com) */
+/* */
+/* 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 METAL_DEVICE_PROPERTIES_H
+#define METAL_DEVICE_PROPERTIES_H
+
+#import "servers/rendering/rendering_device.h"
+
+#import
+#import
+
+/** The buffer index to use for vertex content. */
+const static uint32_t VERT_CONTENT_BUFFER_INDEX = 0;
+const static uint32_t MAX_COLOR_ATTACHMENT_COUNT = 8;
+
+typedef NS_OPTIONS(NSUInteger, SampleCount) {
+ SampleCount1 = (1UL << 0),
+ SampleCount2 = (1UL << 1),
+ SampleCount4 = (1UL << 2),
+ SampleCount8 = (1UL << 3),
+ SampleCount16 = (1UL << 4),
+ SampleCount32 = (1UL << 5),
+ SampleCount64 = (1UL << 6),
+};
+
+struct API_AVAILABLE(macos(11.0), ios(14.0)) MetalFeatures {
+ uint32_t mslVersion;
+ MTLGPUFamily highestFamily;
+ MTLLanguageVersion mslVersionEnum;
+ SampleCount supportedSampleCounts;
+ long hostMemoryPageSize;
+ bool layeredRendering;
+ bool multisampleLayeredRendering;
+ bool quadPermute; /**< If true, quadgroup permutation functions (vote, ballot, shuffle) are supported in shaders. */
+ bool simdPermute; /**< If true, SIMD-group permutation functions (vote, ballot, shuffle) are supported in shaders. */
+ bool simdReduction; /**< If true, SIMD-group reduction functions (arithmetic) are supported in shaders. */
+ bool tessellationShader; /**< If true, tessellation shaders are supported. */
+ bool imageCubeArray; /**< If true, image cube arrays are supported. */
+};
+
+struct MetalLimits {
+ uint64_t maxImageArrayLayers;
+ uint64_t maxFramebufferHeight;
+ uint64_t maxFramebufferWidth;
+ uint64_t maxImageDimension1D;
+ uint64_t maxImageDimension2D;
+ uint64_t maxImageDimension3D;
+ uint64_t maxImageDimensionCube;
+ uint64_t maxViewportDimensionX;
+ uint64_t maxViewportDimensionY;
+ MTLSize maxThreadsPerThreadGroup;
+ MTLSize maxComputeWorkGroupCount;
+ uint64_t maxBoundDescriptorSets;
+ uint64_t maxColorAttachments;
+ uint64_t maxTexturesPerArgumentBuffer;
+ uint64_t maxSamplersPerArgumentBuffer;
+ uint64_t maxBuffersPerArgumentBuffer;
+ uint64_t maxBufferLength;
+ uint64_t minUniformBufferOffsetAlignment;
+ uint64_t maxVertexDescriptorLayoutStride;
+ uint16_t maxViewports;
+ uint32_t maxPerStageBufferCount; /**< The total number of per-stage Metal buffers available for shader uniform content and attributes. */
+ uint32_t maxPerStageTextureCount; /**< The total number of per-stage Metal textures available for shader uniform content. */
+ uint32_t maxPerStageSamplerCount; /**< The total number of per-stage Metal samplers available for shader uniform content. */
+ uint32_t maxVertexInputAttributes;
+ uint32_t maxVertexInputBindings;
+ uint32_t maxVertexInputBindingStride;
+ uint32_t maxDrawIndexedIndexValue;
+
+ uint32_t minSubgroupSize; /**< The minimum number of threads in a SIMD-group. */
+ uint32_t maxSubgroupSize; /**< The maximum number of threads in a SIMD-group. */
+ BitField subgroupSupportedShaderStages;
+ BitField subgroupSupportedOperations; /**< The subgroup operations supported by the device. */
+};
+
+class API_AVAILABLE(macos(11.0), ios(14.0)) MetalDeviceProperties {
+private:
+ void init_features(id p_device);
+ void init_limits(id p_device);
+
+public:
+ MetalFeatures features;
+ MetalLimits limits;
+
+ SampleCount find_nearest_supported_sample_count(RenderingDevice::TextureSamples p_samples) const;
+
+ MetalDeviceProperties(id p_device);
+ ~MetalDeviceProperties();
+
+private:
+ static const SampleCount sample_count[RenderingDevice::TextureSamples::TEXTURE_SAMPLES_MAX];
+};
+
+#endif // METAL_DEVICE_PROPERTIES_H
diff --git a/drivers/metal/metal_device_properties.mm b/drivers/metal/metal_device_properties.mm
new file mode 100644
index 00000000000..857fa8c66ee
--- /dev/null
+++ b/drivers/metal/metal_device_properties.mm
@@ -0,0 +1,327 @@
+/**************************************************************************/
+/* metal_device_properties.mm */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+/**************************************************************************/
+/* */
+/* Portions of this code were derived from MoltenVK. */
+/* */
+/* Copyright (c) 2015-2023 The Brenwill Workshop Ltd. */
+/* (http://www.brenwill.com) */
+/* */
+/* 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. */
+/**************************************************************************/
+
+#import "metal_device_properties.h"
+
+#import
+#import
+#import
+
+// Common scaling multipliers.
+#define KIBI (1024)
+#define MEBI (KIBI * KIBI)
+
+#if (TARGET_OS_OSX && __MAC_OS_X_VERSION_MAX_ALLOWED < 140000) || (TARGET_OS_IOS && __IPHONE_OS_VERSION_MAX_ALLOWED < 170000)
+#define MTLGPUFamilyApple9 (MTLGPUFamily)1009
+#endif
+
+API_AVAILABLE(macos(11.0), ios(14.0))
+MTLGPUFamily &operator--(MTLGPUFamily &p_family) {
+ p_family = static_cast(static_cast(p_family) - 1);
+ if (p_family < MTLGPUFamilyApple1) {
+ p_family = MTLGPUFamilyApple9;
+ }
+
+ return p_family;
+}
+
+void MetalDeviceProperties::init_features(id p_device) {
+ features = {};
+
+ features.highestFamily = MTLGPUFamilyApple1;
+ for (MTLGPUFamily family = MTLGPUFamilyApple9; family >= MTLGPUFamilyApple1; --family) {
+ if ([p_device supportsFamily:family]) {
+ features.highestFamily = family;
+ break;
+ }
+ }
+
+ features.hostMemoryPageSize = sysconf(_SC_PAGESIZE);
+
+ for (SampleCount sc = SampleCount1; sc <= SampleCount64; sc <<= 1) {
+ if ([p_device supportsTextureSampleCount:sc]) {
+ features.supportedSampleCounts |= sc;
+ }
+ }
+
+ features.layeredRendering = [p_device supportsFamily:MTLGPUFamilyApple5];
+ features.multisampleLayeredRendering = [p_device supportsFamily:MTLGPUFamilyApple7];
+ features.tessellationShader = [p_device supportsFamily:MTLGPUFamilyApple3];
+ features.imageCubeArray = [p_device supportsFamily:MTLGPUFamilyApple3];
+ features.quadPermute = [p_device supportsFamily:MTLGPUFamilyApple4];
+ features.simdPermute = [p_device supportsFamily:MTLGPUFamilyApple6];
+ features.simdReduction = [p_device supportsFamily:MTLGPUFamilyApple7];
+
+ MTLCompileOptions *opts = [MTLCompileOptions new];
+ features.mslVersionEnum = opts.languageVersion; // By default, Metal uses the most recent language version.
+
+#define setMSLVersion(m_maj, m_min) \
+ features.mslVersion = SPIRV_CROSS_NAMESPACE::CompilerMSL::Options::make_msl_version(m_maj, m_min)
+
+ switch (features.mslVersionEnum) {
+#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 150000 || __IPHONE_OS_VERSION_MAX_ALLOWED >= 180000
+ case MTLLanguageVersion3_2:
+ setMSLVersion(3, 2);
+ break;
+#endif
+#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 140000 || __IPHONE_OS_VERSION_MAX_ALLOWED >= 170000
+ case MTLLanguageVersion3_1:
+ setMSLVersion(3, 1);
+ break;
+#endif
+ case MTLLanguageVersion3_0:
+ setMSLVersion(3, 0);
+ break;
+ case MTLLanguageVersion2_4:
+ setMSLVersion(2, 4);
+ break;
+ case MTLLanguageVersion2_3:
+ setMSLVersion(2, 3);
+ break;
+ case MTLLanguageVersion2_2:
+ setMSLVersion(2, 2);
+ break;
+ case MTLLanguageVersion2_1:
+ setMSLVersion(2, 1);
+ break;
+ case MTLLanguageVersion2_0:
+ setMSLVersion(2, 0);
+ break;
+ case MTLLanguageVersion1_2:
+ setMSLVersion(1, 2);
+ break;
+ case MTLLanguageVersion1_1:
+ setMSLVersion(1, 1);
+ break;
+#if TARGET_OS_IPHONE && !TARGET_OS_MACCATALYST
+ case MTLLanguageVersion1_0:
+ setMSLVersion(1, 0);
+ break;
+#endif
+ }
+}
+
+void MetalDeviceProperties::init_limits(id p_device) {
+ using std::max;
+ using std::min;
+
+ // FST: https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf
+
+ // FST: Maximum number of layers per 1D texture array, 2D texture array, or 3D texture.
+ limits.maxImageArrayLayers = 2048;
+ if ([p_device supportsFamily:MTLGPUFamilyApple3]) {
+ // FST: Maximum 2D texture width and height.
+ limits.maxFramebufferWidth = 16384;
+ limits.maxFramebufferHeight = 16384;
+ limits.maxViewportDimensionX = 16384;
+ limits.maxViewportDimensionY = 16384;
+ // FST: Maximum 1D texture width.
+ limits.maxImageDimension1D = 16384;
+ // FST: Maximum 2D texture width and height.
+ limits.maxImageDimension2D = 16384;
+ // FST: Maximum cube map texture width and height.
+ limits.maxImageDimensionCube = 16384;
+ } else {
+ // FST: Maximum 2D texture width and height.
+ limits.maxFramebufferWidth = 8192;
+ limits.maxFramebufferHeight = 8192;
+ limits.maxViewportDimensionX = 8192;
+ limits.maxViewportDimensionY = 8192;
+ // FST: Maximum 1D texture width.
+ limits.maxImageDimension1D = 8192;
+ // FST: Maximum 2D texture width and height.
+ limits.maxImageDimension2D = 8192;
+ // FST: Maximum cube map texture width and height.
+ limits.maxImageDimensionCube = 8192;
+ }
+ // FST: Maximum 3D texture width, height, and depth.
+ limits.maxImageDimension3D = 2048;
+
+ limits.maxThreadsPerThreadGroup = p_device.maxThreadsPerThreadgroup;
+ // No effective limits.
+ limits.maxComputeWorkGroupCount = { std::numeric_limits::max(), std::numeric_limits::max(), std::numeric_limits::max() };
+ // https://github.com/KhronosGroup/MoltenVK/blob/568cc3acc0e2299931fdaecaaa1fc3ec5b4af281/MoltenVK/MoltenVK/GPUObjects/MVKDevice.h#L85
+ limits.maxBoundDescriptorSets = SPIRV_CROSS_NAMESPACE::kMaxArgumentBuffers;
+ // FST: Maximum number of color render targets per render pass descriptor.
+ limits.maxColorAttachments = 8;
+
+ // Maximum number of textures the device can access, per stage, from an argument buffer.
+ if ([p_device supportsFamily:MTLGPUFamilyApple6]) {
+ limits.maxTexturesPerArgumentBuffer = 1'000'000;
+ } else if ([p_device supportsFamily:MTLGPUFamilyApple4]) {
+ limits.maxTexturesPerArgumentBuffer = 96;
+ } else {
+ limits.maxTexturesPerArgumentBuffer = 31;
+ }
+
+ // Maximum number of samplers the device can access, per stage, from an argument buffer.
+ if ([p_device supportsFamily:MTLGPUFamilyApple6]) {
+ limits.maxSamplersPerArgumentBuffer = 1024;
+ } else {
+ limits.maxSamplersPerArgumentBuffer = 16;
+ }
+
+ // Maximum number of buffers the device can access, per stage, from an argument buffer.
+ if ([p_device supportsFamily:MTLGPUFamilyApple6]) {
+ limits.maxBuffersPerArgumentBuffer = std::numeric_limits::max();
+ } else if ([p_device supportsFamily:MTLGPUFamilyApple4]) {
+ limits.maxBuffersPerArgumentBuffer = 96;
+ } else {
+ limits.maxBuffersPerArgumentBuffer = 31;
+ }
+
+ limits.minSubgroupSize = limits.maxSubgroupSize = 1;
+ // These values were taken from MoltenVK.
+ if (features.simdPermute) {
+ limits.minSubgroupSize = 4;
+ limits.maxSubgroupSize = 32;
+ } else if (features.quadPermute) {
+ limits.minSubgroupSize = limits.maxSubgroupSize = 4;
+ }
+
+ limits.subgroupSupportedShaderStages.set_flag(RDD::ShaderStage::SHADER_STAGE_COMPUTE_BIT);
+ if (features.tessellationShader) {
+ limits.subgroupSupportedShaderStages.set_flag(RDD::ShaderStage::SHADER_STAGE_TESSELATION_CONTROL_BIT);
+ }
+ limits.subgroupSupportedShaderStages.set_flag(RDD::ShaderStage::SHADER_STAGE_FRAGMENT_BIT);
+
+ limits.subgroupSupportedOperations.set_flag(RD::SubgroupOperations::SUBGROUP_BASIC_BIT);
+ if (features.simdPermute || features.quadPermute) {
+ limits.subgroupSupportedOperations.set_flag(RD::SubgroupOperations::SUBGROUP_VOTE_BIT);
+ limits.subgroupSupportedOperations.set_flag(RD::SubgroupOperations::SUBGROUP_BALLOT_BIT);
+ limits.subgroupSupportedOperations.set_flag(RD::SubgroupOperations::SUBGROUP_SHUFFLE_BIT);
+ limits.subgroupSupportedOperations.set_flag(RD::SubgroupOperations::SUBGROUP_SHUFFLE_RELATIVE_BIT);
+ }
+
+ if (features.simdReduction) {
+ limits.subgroupSupportedOperations.set_flag(RD::SubgroupOperations::SUBGROUP_ARITHMETIC_BIT);
+ }
+
+ if (features.quadPermute) {
+ limits.subgroupSupportedOperations.set_flag(RD::SubgroupOperations::SUBGROUP_QUAD_BIT);
+ }
+
+ limits.maxBufferLength = p_device.maxBufferLength;
+
+ // FST: Maximum size of vertex descriptor layout stride.
+ limits.maxVertexDescriptorLayoutStride = std::numeric_limits::max();
+
+ // Maximum number of viewports.
+ if ([p_device supportsFamily:MTLGPUFamilyApple5]) {
+ limits.maxViewports = 16;
+ } else {
+ limits.maxViewports = 1;
+ }
+
+ limits.maxPerStageBufferCount = 31;
+ limits.maxPerStageSamplerCount = 16;
+ if ([p_device supportsFamily:MTLGPUFamilyApple6]) {
+ limits.maxPerStageTextureCount = 128;
+ } else if ([p_device supportsFamily:MTLGPUFamilyApple4]) {
+ limits.maxPerStageTextureCount = 96;
+ } else {
+ limits.maxPerStageTextureCount = 31;
+ }
+
+ limits.maxVertexInputAttributes = 31;
+ limits.maxVertexInputBindings = 31;
+ limits.maxVertexInputBindingStride = (2 * KIBI);
+
+#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST
+ limits.minUniformBufferOffsetAlignment = 64;
+#endif
+
+#if TARGET_OS_OSX
+ // This is Apple Silicon specific.
+ limits.minUniformBufferOffsetAlignment = 16;
+#endif
+
+ limits.maxDrawIndexedIndexValue = std::numeric_limits::max() - 1;
+}
+
+MetalDeviceProperties::MetalDeviceProperties(id p_device) {
+ init_features(p_device);
+ init_limits(p_device);
+}
+
+MetalDeviceProperties::~MetalDeviceProperties() {
+}
+
+SampleCount MetalDeviceProperties::find_nearest_supported_sample_count(RenderingDevice::TextureSamples p_samples) const {
+ SampleCount supported = features.supportedSampleCounts;
+ if (supported & sample_count[p_samples]) {
+ return sample_count[p_samples];
+ }
+
+ SampleCount requested_sample_count = sample_count[p_samples];
+ // Find the nearest supported sample count.
+ while (requested_sample_count > SampleCount1) {
+ if (supported & requested_sample_count) {
+ return requested_sample_count;
+ }
+ requested_sample_count = (SampleCount)(requested_sample_count >> 1);
+ }
+
+ return SampleCount1;
+}
+
+// region static members
+
+const SampleCount MetalDeviceProperties::sample_count[RenderingDevice::TextureSamples::TEXTURE_SAMPLES_MAX] = {
+ SampleCount1,
+ SampleCount2,
+ SampleCount4,
+ SampleCount8,
+ SampleCount16,
+ SampleCount32,
+ SampleCount64,
+};
+
+// endregion
diff --git a/drivers/metal/metal_objects.h b/drivers/metal/metal_objects.h
new file mode 100644
index 00000000000..70f86f2facd
--- /dev/null
+++ b/drivers/metal/metal_objects.h
@@ -0,0 +1,838 @@
+/**************************************************************************/
+/* metal_objects.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+/**************************************************************************/
+/* */
+/* Portions of this code were derived from MoltenVK. */
+/* */
+/* Copyright (c) 2015-2023 The Brenwill Workshop Ltd. */
+/* (http://www.brenwill.com) */
+/* */
+/* 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 METAL_OBJECTS_H
+#define METAL_OBJECTS_H
+
+#import "metal_device_properties.h"
+#import "metal_utils.h"
+#import "pixel_formats.h"
+
+#import "servers/rendering/rendering_device_driver.h"
+
+#import
+#import
+#import
+#import
+#import
+#import
+#import
+
+// These types can be used in Vector and other containers that use
+// pointer operations not supported by ARC.
+namespace MTL {
+#define MTL_CLASS(name) \
+ class name { \
+ public: \
+ name(id obj = nil) : m_obj(obj) {} \
+ operator id() const { return m_obj; } \
+ id m_obj; \
+ };
+
+MTL_CLASS(Texture)
+
+} //namespace MTL
+
+enum ShaderStageUsage : uint32_t {
+ None = 0,
+ Vertex = RDD::SHADER_STAGE_VERTEX_BIT,
+ Fragment = RDD::SHADER_STAGE_FRAGMENT_BIT,
+ TesselationControl = RDD::SHADER_STAGE_TESSELATION_CONTROL_BIT,
+ TesselationEvaluation = RDD::SHADER_STAGE_TESSELATION_EVALUATION_BIT,
+ Compute = RDD::SHADER_STAGE_COMPUTE_BIT,
+};
+
+_FORCE_INLINE_ ShaderStageUsage &operator|=(ShaderStageUsage &p_a, int p_b) {
+ p_a = ShaderStageUsage(uint32_t(p_a) | uint32_t(p_b));
+ return p_a;
+}
+
+enum class MDCommandBufferStateType {
+ None,
+ Render,
+ Compute,
+ Blit,
+};
+
+enum class MDPipelineType {
+ None,
+ Render,
+ Compute,
+};
+
+class MDRenderPass;
+class MDPipeline;
+class MDRenderPipeline;
+class MDComputePipeline;
+class MDFrameBuffer;
+class RenderingDeviceDriverMetal;
+class MDUniformSet;
+class MDShader;
+
+#pragma mark - Resource Factory
+
+struct ClearAttKey {
+ const static uint32_t COLOR_COUNT = MAX_COLOR_ATTACHMENT_COUNT;
+ const static uint32_t DEPTH_INDEX = COLOR_COUNT;
+ const static uint32_t STENCIL_INDEX = DEPTH_INDEX + 1;
+ const static uint32_t ATTACHMENT_COUNT = STENCIL_INDEX + 1;
+
+ uint16_t sample_count = 0;
+ uint16_t pixel_formats[ATTACHMENT_COUNT] = { 0 };
+
+ _FORCE_INLINE_ void set_color_format(uint32_t p_idx, MTLPixelFormat p_fmt) { pixel_formats[p_idx] = p_fmt; }
+ _FORCE_INLINE_ void set_depth_format(MTLPixelFormat p_fmt) { pixel_formats[DEPTH_INDEX] = p_fmt; }
+ _FORCE_INLINE_ void set_stencil_format(MTLPixelFormat p_fmt) { pixel_formats[STENCIL_INDEX] = p_fmt; }
+ _FORCE_INLINE_ MTLPixelFormat depth_format() const { return (MTLPixelFormat)pixel_formats[DEPTH_INDEX]; }
+ _FORCE_INLINE_ MTLPixelFormat stencil_format() const { return (MTLPixelFormat)pixel_formats[STENCIL_INDEX]; }
+
+ _FORCE_INLINE_ bool is_enabled(uint32_t p_idx) const { return pixel_formats[p_idx] != 0; }
+ _FORCE_INLINE_ bool is_depth_enabled() const { return pixel_formats[DEPTH_INDEX] != 0; }
+ _FORCE_INLINE_ bool is_stencil_enabled() const { return pixel_formats[STENCIL_INDEX] != 0; }
+
+ _FORCE_INLINE_ bool operator==(const ClearAttKey &p_rhs) const {
+ return memcmp(this, &p_rhs, sizeof(ClearAttKey)) == 0;
+ }
+
+ uint32_t hash() const {
+ uint32_t h = hash_murmur3_one_32(sample_count);
+ h = hash_murmur3_buffer(pixel_formats, ATTACHMENT_COUNT * sizeof(pixel_formats[0]), h);
+ return h;
+ }
+};
+
+class API_AVAILABLE(macos(11.0), ios(14.0)) MDResourceFactory {
+private:
+ RenderingDeviceDriverMetal *device_driver;
+
+ id new_func(NSString *p_source, NSString *p_name, NSError **p_error);
+ id new_clear_vert_func(ClearAttKey &p_key);
+ id new_clear_frag_func(ClearAttKey &p_key);
+ NSString *get_format_type_string(MTLPixelFormat p_fmt);
+
+public:
+ id new_clear_pipeline_state(ClearAttKey &p_key, NSError **p_error);
+ id new_depth_stencil_state(bool p_use_depth, bool p_use_stencil);
+
+ MDResourceFactory(RenderingDeviceDriverMetal *p_device_driver) :
+ device_driver(p_device_driver) {}
+ ~MDResourceFactory() = default;
+};
+
+class API_AVAILABLE(macos(11.0), ios(14.0)) MDResourceCache {
+private:
+ typedef HashMap, HashableHasher> HashMap;
+ std::unique_ptr resource_factory;
+ HashMap clear_states;
+
+ struct {
+ id all;
+ id depth_only;
+ id stencil_only;
+ id none;
+ } clear_depth_stencil_state;
+
+public:
+ id get_clear_render_pipeline_state(ClearAttKey &p_key, NSError **p_error);
+ id get_depth_stencil_state(bool p_use_depth, bool p_use_stencil);
+
+ explicit MDResourceCache(RenderingDeviceDriverMetal *p_device_driver) :
+ resource_factory(new MDResourceFactory(p_device_driver)) {}
+ ~MDResourceCache() = default;
+};
+
+class API_AVAILABLE(macos(11.0), ios(14.0)) MDCommandBuffer {
+private:
+ RenderingDeviceDriverMetal *device_driver = nullptr;
+ id queue = nil;
+ id commandBuffer = nil;
+
+ void _end_compute_dispatch();
+ void _end_blit();
+
+#pragma mark - Render
+
+ void _render_set_dirty_state();
+ void _render_bind_uniform_sets();
+
+ static void _populate_vertices(simd::float4 *p_vertices, Size2i p_fb_size, VectorView p_rects);
+ static uint32_t _populate_vertices(simd::float4 *p_vertices, uint32_t p_index, Rect2i const &p_rect, Size2i p_fb_size);
+ void _end_render_pass();
+ void _render_clear_render_area();
+
+public:
+ MDCommandBufferStateType type = MDCommandBufferStateType::None;
+
+ struct RenderState {
+ MDRenderPass *pass = nullptr;
+ MDFrameBuffer *frameBuffer = nullptr;
+ MDRenderPipeline *pipeline = nullptr;
+ LocalVector clear_values;
+ LocalVector viewports;
+ LocalVector scissors;
+ std::optional blend_constants;
+ uint32_t current_subpass = UINT32_MAX;
+ Rect2i render_area = {};
+ bool is_rendering_entire_area = false;
+ MTLRenderPassDescriptor *desc = nil;
+ id encoder = nil;
+ id __unsafe_unretained index_buffer = nil; // Buffer is owned by RDD.
+ MTLIndexType index_type = MTLIndexTypeUInt16;
+ LocalVector __unsafe_unretained> vertex_buffers;
+ LocalVector vertex_offsets;
+ // clang-format off
+ enum DirtyFlag: uint8_t {
+ DIRTY_NONE = 0b0000'0000,
+ DIRTY_PIPELINE = 0b0000'0001, //! pipeline state
+ DIRTY_UNIFORMS = 0b0000'0010, //! uniform sets
+ DIRTY_DEPTH = 0b0000'0100, //! depth / stenci state
+ DIRTY_VERTEX = 0b0000'1000, //! vertex buffers
+ DIRTY_VIEWPORT = 0b0001'0000, //! viewport rectangles
+ DIRTY_SCISSOR = 0b0010'0000, //! scissor rectangles
+ DIRTY_BLEND = 0b0100'0000, //! blend state
+ DIRTY_RASTER = 0b1000'0000, //! encoder state like cull mode
+
+ DIRTY_ALL = 0xff,
+ };
+ // clang-format on
+ BitField dirty = DIRTY_NONE;
+
+ LocalVector uniform_sets;
+ // Bit mask of the uniform sets that are dirty, to prevent redundant binding.
+ uint64_t uniform_set_mask = 0;
+
+ _FORCE_INLINE_ void reset() {
+ pass = nil;
+ frameBuffer = nil;
+ pipeline = nil;
+ current_subpass = UINT32_MAX;
+ render_area = {};
+ is_rendering_entire_area = false;
+ desc = nil;
+ encoder = nil;
+ index_buffer = nil;
+ index_type = MTLIndexTypeUInt16;
+ dirty = DIRTY_NONE;
+ uniform_sets.clear();
+ uniform_set_mask = 0;
+ clear_values.clear();
+ viewports.clear();
+ scissors.clear();
+ blend_constants.reset();
+ vertex_buffers.clear();
+ vertex_offsets.clear();
+ }
+
+ _FORCE_INLINE_ void mark_viewport_dirty() {
+ if (viewports.is_empty()) {
+ return;
+ }
+ dirty.set_flag(DirtyFlag::DIRTY_VIEWPORT);
+ }
+
+ _FORCE_INLINE_ void mark_scissors_dirty() {
+ if (scissors.is_empty()) {
+ return;
+ }
+ dirty.set_flag(DirtyFlag::DIRTY_SCISSOR);
+ }
+
+ _FORCE_INLINE_ void mark_vertex_dirty() {
+ if (vertex_buffers.is_empty()) {
+ return;
+ }
+ dirty.set_flag(DirtyFlag::DIRTY_VERTEX);
+ }
+
+ _FORCE_INLINE_ void mark_uniforms_dirty(std::initializer_list l) {
+ if (uniform_sets.is_empty()) {
+ return;
+ }
+ for (uint32_t i : l) {
+ if (i < uniform_sets.size() && uniform_sets[i] != nullptr) {
+ uniform_set_mask |= 1 << i;
+ }
+ }
+ dirty.set_flag(DirtyFlag::DIRTY_UNIFORMS);
+ }
+
+ _FORCE_INLINE_ void mark_uniforms_dirty(void) {
+ if (uniform_sets.is_empty()) {
+ return;
+ }
+ for (uint32_t i = 0; i < uniform_sets.size(); i++) {
+ if (uniform_sets[i] != nullptr) {
+ uniform_set_mask |= 1 << i;
+ }
+ }
+ dirty.set_flag(DirtyFlag::DIRTY_UNIFORMS);
+ }
+
+ MTLScissorRect clip_to_render_area(MTLScissorRect p_rect) const {
+ uint32_t raLeft = render_area.position.x;
+ uint32_t raRight = raLeft + render_area.size.width;
+ uint32_t raBottom = render_area.position.y;
+ uint32_t raTop = raBottom + render_area.size.height;
+
+ p_rect.x = CLAMP(p_rect.x, raLeft, MAX(raRight - 1, raLeft));
+ p_rect.y = CLAMP(p_rect.y, raBottom, MAX(raTop - 1, raBottom));
+ p_rect.width = MIN(p_rect.width, raRight - p_rect.x);
+ p_rect.height = MIN(p_rect.height, raTop - p_rect.y);
+
+ return p_rect;
+ }
+
+ Rect2i clip_to_render_area(Rect2i p_rect) const {
+ int32_t raLeft = render_area.position.x;
+ int32_t raRight = raLeft + render_area.size.width;
+ int32_t raBottom = render_area.position.y;
+ int32_t raTop = raBottom + render_area.size.height;
+
+ p_rect.position.x = CLAMP(p_rect.position.x, raLeft, MAX(raRight - 1, raLeft));
+ p_rect.position.y = CLAMP(p_rect.position.y, raBottom, MAX(raTop - 1, raBottom));
+ p_rect.size.width = MIN(p_rect.size.width, raRight - p_rect.position.x);
+ p_rect.size.height = MIN(p_rect.size.height, raTop - p_rect.position.y);
+
+ return p_rect;
+ }
+
+ } render;
+
+ // State specific for a compute pass.
+ struct {
+ MDComputePipeline *pipeline = nullptr;
+ id encoder = nil;
+ _FORCE_INLINE_ void reset() {
+ pipeline = nil;
+ encoder = nil;
+ }
+ } compute;
+
+ // State specific to a blit pass.
+ struct {
+ id encoder = nil;
+ _FORCE_INLINE_ void reset() {
+ encoder = nil;
+ }
+ } blit;
+
+ _FORCE_INLINE_ id get_command_buffer() const {
+ return commandBuffer;
+ }
+
+ void begin();
+ void commit();
+ void end();
+
+ id blit_command_encoder();
+ void encodeRenderCommandEncoderWithDescriptor(MTLRenderPassDescriptor *p_desc, NSString *p_label);
+
+ void bind_pipeline(RDD::PipelineID p_pipeline);
+
+#pragma mark - Render Commands
+
+ void render_bind_uniform_set(RDD::UniformSetID p_uniform_set, RDD::ShaderID p_shader, uint32_t p_set_index);
+ void render_clear_attachments(VectorView p_attachment_clears, VectorView p_rects);
+ void render_set_viewport(VectorView p_viewports);
+ void render_set_scissor(VectorView p_scissors);
+ void render_set_blend_constants(const Color &p_constants);
+ void render_begin_pass(RDD::RenderPassID p_render_pass,
+ RDD::FramebufferID p_frameBuffer,
+ RDD::CommandBufferType p_cmd_buffer_type,
+ const Rect2i &p_rect,
+ VectorView p_clear_values);
+ void render_next_subpass();
+ void render_draw(uint32_t p_vertex_count,
+ uint32_t p_instance_count,
+ uint32_t p_base_vertex,
+ uint32_t p_first_instance);
+ void render_bind_vertex_buffers(uint32_t p_binding_count, const RDD::BufferID *p_buffers, const uint64_t *p_offsets);
+ void render_bind_index_buffer(RDD::BufferID p_buffer, RDD::IndexBufferFormat p_format, uint64_t p_offset);
+
+ void render_draw_indexed(uint32_t p_index_count,
+ uint32_t p_instance_count,
+ uint32_t p_first_index,
+ int32_t p_vertex_offset,
+ uint32_t p_first_instance);
+
+ void render_draw_indexed_indirect(RDD::BufferID p_indirect_buffer, uint64_t p_offset, uint32_t p_draw_count, uint32_t p_stride);
+ void render_draw_indexed_indirect_count(RDD::BufferID p_indirect_buffer, uint64_t p_offset, RDD::BufferID p_count_buffer, uint64_t p_count_buffer_offset, uint32_t p_max_draw_count, uint32_t p_stride);
+ void render_draw_indirect(RDD::BufferID p_indirect_buffer, uint64_t p_offset, uint32_t p_draw_count, uint32_t p_stride);
+ void render_draw_indirect_count(RDD::BufferID p_indirect_buffer, uint64_t p_offset, RDD::BufferID p_count_buffer, uint64_t p_count_buffer_offset, uint32_t p_max_draw_count, uint32_t p_stride);
+
+ void render_end_pass();
+
+#pragma mark - Compute Commands
+
+ void compute_bind_uniform_set(RDD::UniformSetID p_uniform_set, RDD::ShaderID p_shader, uint32_t p_set_index);
+ void compute_dispatch(uint32_t p_x_groups, uint32_t p_y_groups, uint32_t p_z_groups);
+ void compute_dispatch_indirect(RDD::BufferID p_indirect_buffer, uint64_t p_offset);
+
+ MDCommandBuffer(id p_queue, RenderingDeviceDriverMetal *p_device_driver) :
+ device_driver(p_device_driver), queue(p_queue) {
+ type = MDCommandBufferStateType::None;
+ }
+
+ MDCommandBuffer() = default;
+};
+
+#if (TARGET_OS_OSX && __MAC_OS_X_VERSION_MAX_ALLOWED < 140000) || (TARGET_OS_IOS && __IPHONE_OS_VERSION_MAX_ALLOWED < 170000)
+#define MTLBindingAccess MTLArgumentAccess
+#define MTLBindingAccessReadOnly MTLArgumentAccessReadOnly
+#define MTLBindingAccessReadWrite MTLArgumentAccessReadWrite
+#define MTLBindingAccessWriteOnly MTLArgumentAccessWriteOnly
+#endif
+
+struct API_AVAILABLE(macos(11.0), ios(14.0)) BindingInfo {
+ MTLDataType dataType = MTLDataTypeNone;
+ uint32_t index = 0;
+ MTLBindingAccess access = MTLBindingAccessReadOnly;
+ MTLResourceUsage usage = 0;
+ MTLTextureType textureType = MTLTextureType2D;
+ spv::ImageFormat imageFormat = spv::ImageFormatUnknown;
+ uint32_t arrayLength = 0;
+ bool isMultisampled = false;
+
+ inline MTLArgumentDescriptor *new_argument_descriptor() const {
+ MTLArgumentDescriptor *desc = MTLArgumentDescriptor.argumentDescriptor;
+ desc.dataType = dataType;
+ desc.index = index;
+ desc.access = access;
+ desc.textureType = textureType;
+ desc.arrayLength = arrayLength;
+ return desc;
+ }
+
+ size_t serialize_size() const {
+ return sizeof(uint32_t) * 8 /* 8 uint32_t fields */;
+ }
+
+ template
+ void serialize(W &p_writer) const {
+ p_writer.write((uint32_t)dataType);
+ p_writer.write(index);
+ p_writer.write((uint32_t)access);
+ p_writer.write((uint32_t)usage);
+ p_writer.write((uint32_t)textureType);
+ p_writer.write(imageFormat);
+ p_writer.write(arrayLength);
+ p_writer.write(isMultisampled);
+ }
+
+ template
+ void deserialize(R &p_reader) {
+ p_reader.read((uint32_t &)dataType);
+ p_reader.read(index);
+ p_reader.read((uint32_t &)access);
+ p_reader.read((uint32_t &)usage);
+ p_reader.read((uint32_t &)textureType);
+ p_reader.read((uint32_t &)imageFormat);
+ p_reader.read(arrayLength);
+ p_reader.read(isMultisampled);
+ }
+};
+
+using RDC = RenderingDeviceCommons;
+
+typedef API_AVAILABLE(macos(11.0), ios(14.0)) HashMap BindingInfoMap;
+
+struct API_AVAILABLE(macos(11.0), ios(14.0)) UniformInfo {
+ uint32_t binding;
+ ShaderStageUsage active_stages = None;
+ BindingInfoMap bindings;
+ BindingInfoMap bindings_secondary;
+};
+
+struct API_AVAILABLE(macos(11.0), ios(14.0)) UniformSet {
+ LocalVector uniforms;
+ uint32_t buffer_size = 0;
+ HashMap offsets;
+ HashMap> encoders;
+};
+
+class API_AVAILABLE(macos(11.0), ios(14.0)) MDShader {
+public:
+ CharString name;
+ Vector sets;
+
+ virtual void encode_push_constant_data(VectorView p_data, MDCommandBuffer *p_cb) = 0;
+
+ MDShader(CharString p_name, Vector p_sets) :
+ name(p_name), sets(p_sets) {}
+ virtual ~MDShader() = default;
+};
+
+class API_AVAILABLE(macos(11.0), ios(14.0)) MDComputeShader final : public MDShader {
+public:
+ struct {
+ uint32_t binding = -1;
+ uint32_t size = 0;
+ } push_constants;
+ MTLSize local = {};
+
+ id kernel;
+#if DEV_ENABLED
+ CharString kernel_source;
+#endif
+
+ void encode_push_constant_data(VectorView p_data, MDCommandBuffer *p_cb) final;
+
+ MDComputeShader(CharString p_name, Vector p_sets, id p_kernel);
+ ~MDComputeShader() override = default;
+};
+
+class API_AVAILABLE(macos(11.0), ios(14.0)) MDRenderShader final : public MDShader {
+public:
+ struct {
+ struct {
+ int32_t binding = -1;
+ uint32_t size = 0;
+ } vert;
+ struct {
+ int32_t binding = -1;
+ uint32_t size = 0;
+ } frag;
+ } push_constants;
+
+ id vert;
+ id frag;
+#if DEV_ENABLED
+ CharString vert_source;
+ CharString frag_source;
+#endif
+
+ void encode_push_constant_data(VectorView p_data, MDCommandBuffer *p_cb) final;
+
+ MDRenderShader(CharString p_name, Vector p_sets, id p_vert, id p_frag);
+ ~MDRenderShader() override = default;
+};
+
+enum StageResourceUsage : uint32_t {
+ VertexRead = (MTLResourceUsageRead << RDD::SHADER_STAGE_VERTEX * 2),
+ VertexWrite = (MTLResourceUsageWrite << RDD::SHADER_STAGE_VERTEX * 2),
+ FragmentRead = (MTLResourceUsageRead << RDD::SHADER_STAGE_FRAGMENT * 2),
+ FragmentWrite = (MTLResourceUsageWrite << RDD::SHADER_STAGE_FRAGMENT * 2),
+ TesselationControlRead = (MTLResourceUsageRead << RDD::SHADER_STAGE_TESSELATION_CONTROL * 2),
+ TesselationControlWrite = (MTLResourceUsageWrite << RDD::SHADER_STAGE_TESSELATION_CONTROL * 2),
+ TesselationEvaluationRead = (MTLResourceUsageRead << RDD::SHADER_STAGE_TESSELATION_EVALUATION * 2),
+ TesselationEvaluationWrite = (MTLResourceUsageWrite << RDD::SHADER_STAGE_TESSELATION_EVALUATION * 2),
+ ComputeRead = (MTLResourceUsageRead << RDD::SHADER_STAGE_COMPUTE * 2),
+ ComputeWrite = (MTLResourceUsageWrite << RDD::SHADER_STAGE_COMPUTE * 2),
+};
+
+_FORCE_INLINE_ StageResourceUsage &operator|=(StageResourceUsage &p_a, uint32_t p_b) {
+ p_a = StageResourceUsage(uint32_t(p_a) | p_b);
+ return p_a;
+}
+
+_FORCE_INLINE_ StageResourceUsage stage_resource_usage(RDC::ShaderStage p_stage, MTLResourceUsage p_usage) {
+ return StageResourceUsage(p_usage << (p_stage * 2));
+}
+
+_FORCE_INLINE_ MTLResourceUsage resource_usage_for_stage(StageResourceUsage p_usage, RDC::ShaderStage p_stage) {
+ return MTLResourceUsage((p_usage >> (p_stage * 2)) & 0b11);
+}
+
+template <>
+struct HashMapComparatorDefault {
+ static bool compare(const RDD::ShaderID &p_lhs, const RDD::ShaderID &p_rhs) {
+ return p_lhs.id == p_rhs.id;
+ }
+};
+
+struct BoundUniformSet {
+ id buffer;
+ HashMap, StageResourceUsage> bound_resources;
+};
+
+class API_AVAILABLE(macos(11.0), ios(14.0)) MDUniformSet {
+public:
+ uint32_t index;
+ LocalVector uniforms;
+ HashMap bound_uniforms;
+
+ BoundUniformSet &boundUniformSetForShader(MDShader *p_shader, id p_device);
+};
+
+enum class MDAttachmentType : uint8_t {
+ None = 0,
+ Color = 1 << 0,
+ Depth = 1 << 1,
+ Stencil = 1 << 2,
+};
+
+_FORCE_INLINE_ MDAttachmentType &operator|=(MDAttachmentType &p_a, MDAttachmentType p_b) {
+ flags::set(p_a, p_b);
+ return p_a;
+}
+
+_FORCE_INLINE_ bool operator&(MDAttachmentType p_a, MDAttachmentType p_b) {
+ return uint8_t(p_a) & uint8_t(p_b);
+}
+
+struct MDSubpass {
+ uint32_t subpass_index = 0;
+ LocalVector input_references;
+ LocalVector color_references;
+ RDD::AttachmentReference depth_stencil_reference;
+ LocalVector resolve_references;
+
+ MTLFmtCaps getRequiredFmtCapsForAttachmentAt(uint32_t p_index) const;
+};
+
+struct API_AVAILABLE(macos(11.0), ios(14.0)) MDAttachment {
+private:
+ uint32_t index = 0;
+ uint32_t firstUseSubpassIndex = 0;
+ uint32_t lastUseSubpassIndex = 0;
+
+public:
+ MTLPixelFormat format = MTLPixelFormatInvalid;
+ MDAttachmentType type = MDAttachmentType::None;
+ MTLLoadAction loadAction = MTLLoadActionDontCare;
+ MTLStoreAction storeAction = MTLStoreActionDontCare;
+ MTLLoadAction stencilLoadAction = MTLLoadActionDontCare;
+ MTLStoreAction stencilStoreAction = MTLStoreActionDontCare;
+ uint32_t samples = 1;
+
+ /*!
+ * @brief Returns true if this attachment is first used in the given subpass.
+ * @param p_subpass
+ * @return
+ */
+ _FORCE_INLINE_ bool isFirstUseOf(MDSubpass const &p_subpass) const {
+ return p_subpass.subpass_index == firstUseSubpassIndex;
+ }
+
+ /*!
+ * @brief Returns true if this attachment is last used in the given subpass.
+ * @param p_subpass
+ * @return
+ */
+ _FORCE_INLINE_ bool isLastUseOf(MDSubpass const &p_subpass) const {
+ return p_subpass.subpass_index == lastUseSubpassIndex;
+ }
+
+ void linkToSubpass(MDRenderPass const &p_pass);
+
+ MTLStoreAction getMTLStoreAction(MDSubpass const &p_subpass,
+ bool p_is_rendering_entire_area,
+ bool p_has_resolve,
+ bool p_can_resolve,
+ bool p_is_stencil) const;
+ bool configureDescriptor(MTLRenderPassAttachmentDescriptor *p_desc,
+ PixelFormats &p_pf,
+ MDSubpass const &p_subpass,
+ id p_attachment,
+ bool p_is_rendering_entire_area,
+ bool p_has_resolve,
+ bool p_can_resolve,
+ bool p_is_stencil) const;
+ /** Returns whether this attachment should be cleared in the subpass. */
+ bool shouldClear(MDSubpass const &p_subpass, bool p_is_stencil) const;
+};
+
+class API_AVAILABLE(macos(11.0), ios(14.0)) MDRenderPass {
+public:
+ Vector attachments;
+ Vector subpasses;
+
+ uint32_t get_sample_count() const {
+ return attachments.is_empty() ? 1 : attachments[0].samples;
+ }
+
+ MDRenderPass(Vector &p_attachments, Vector &p_subpasses);
+};
+
+class API_AVAILABLE(macos(11.0), ios(14.0)) MDPipeline {
+public:
+ MDPipelineType type;
+
+ explicit MDPipeline(MDPipelineType p_type) :
+ type(p_type) {}
+ virtual ~MDPipeline() = default;
+};
+
+class API_AVAILABLE(macos(11.0), ios(14.0)) MDRenderPipeline final : public MDPipeline {
+public:
+ id state = nil;
+ id depth_stencil = nil;
+ uint32_t push_constant_size = 0;
+ uint32_t push_constant_stages_mask = 0;
+ SampleCount sample_count = SampleCount1;
+
+ struct {
+ MTLCullMode cull_mode = MTLCullModeNone;
+ MTLTriangleFillMode fill_mode = MTLTriangleFillModeFill;
+ MTLDepthClipMode clip_mode = MTLDepthClipModeClip;
+ MTLWinding winding = MTLWindingClockwise;
+ MTLPrimitiveType render_primitive = MTLPrimitiveTypePoint;
+
+ struct {
+ bool enabled = false;
+ } depth_test;
+
+ struct {
+ bool enabled = false;
+ float depth_bias = 0.0;
+ float slope_scale = 0.0;
+ float clamp = 0.0;
+ _FORCE_INLINE_ void apply(id __unsafe_unretained p_enc) const {
+ if (!enabled) {
+ return;
+ }
+ [p_enc setDepthBias:depth_bias slopeScale:slope_scale clamp:clamp];
+ }
+ } depth_bias;
+
+ struct {
+ bool enabled = false;
+ uint32_t front_reference = 0;
+ uint32_t back_reference = 0;
+ _FORCE_INLINE_ void apply(id __unsafe_unretained p_enc) const {
+ if (!enabled)
+ return;
+ [p_enc setStencilFrontReferenceValue:front_reference backReferenceValue:back_reference];
+ };
+ } stencil;
+
+ struct {
+ bool enabled = false;
+ float r = 0.0;
+ float g = 0.0;
+ float b = 0.0;
+ float a = 0.0;
+
+ _FORCE_INLINE_ void apply(id __unsafe_unretained p_enc) const {
+ //if (!enabled)
+ // return;
+ [p_enc setBlendColorRed:r green:g blue:b alpha:a];
+ };
+ } blend;
+
+ _FORCE_INLINE_ void apply(id __unsafe_unretained p_enc) const {
+ [p_enc setCullMode:cull_mode];
+ [p_enc setTriangleFillMode:fill_mode];
+ [p_enc setDepthClipMode:clip_mode];
+ [p_enc setFrontFacingWinding:winding];
+ depth_bias.apply(p_enc);
+ stencil.apply(p_enc);
+ blend.apply(p_enc);
+ }
+
+ } raster_state;
+
+ MDRenderShader *shader = nil;
+
+ MDRenderPipeline() :
+ MDPipeline(MDPipelineType::Render) {}
+ ~MDRenderPipeline() final = default;
+};
+
+class API_AVAILABLE(macos(11.0), ios(14.0)) MDComputePipeline final : public MDPipeline {
+public:
+ id state = nil;
+ struct {
+ MTLSize local = {};
+ } compute_state;
+
+ MDComputeShader *shader = nil;
+
+ explicit MDComputePipeline(id p_state) :
+ MDPipeline(MDPipelineType::Compute), state(p_state) {}
+ ~MDComputePipeline() final = default;
+};
+
+class API_AVAILABLE(macos(11.0), ios(14.0)) MDFrameBuffer {
+public:
+ Vector textures;
+ Size2i size;
+ MDFrameBuffer(Vector p_textures, Size2i p_size) :
+ textures(p_textures), size(p_size) {}
+ MDFrameBuffer() {}
+
+ virtual ~MDFrameBuffer() = default;
+};
+
+// These functions are used to convert between Objective-C objects and
+// the RIDs used by Godot, respecting automatic reference counting.
+namespace rid {
+
+// Converts an Objective-C object to a pointer, and incrementing the
+// reference count.
+_FORCE_INLINE_
+void *owned(id p_id) {
+ return (__bridge_retained void *)p_id;
+}
+
+#define MAKE_ID(FROM, TO) \
+ _FORCE_INLINE_ TO make(FROM p_obj) { return TO(owned(p_obj)); }
+
+MAKE_ID(id, RDD::TextureID)
+MAKE_ID(id, RDD::BufferID)
+MAKE_ID(id, RDD::SamplerID)
+MAKE_ID(MTLVertexDescriptor *, RDD::VertexFormatID)
+MAKE_ID(id, RDD::CommandPoolID)
+
+// Converts a pointer to an Objective-C object without changing the reference count.
+_FORCE_INLINE_
+auto get(RDD::ID p_id) {
+ return (p_id.id) ? (__bridge ::id)(void *)p_id.id : nil;
+}
+
+// Converts a pointer to an Objective-C object, and decrements the reference count.
+_FORCE_INLINE_
+auto release(RDD::ID p_id) {
+ return (__bridge_transfer ::id)(void *)p_id.id;
+}
+
+} // namespace rid
+
+#endif // METAL_OBJECTS_H
diff --git a/drivers/metal/metal_objects.mm b/drivers/metal/metal_objects.mm
new file mode 100644
index 00000000000..3ce00f74a30
--- /dev/null
+++ b/drivers/metal/metal_objects.mm
@@ -0,0 +1,1380 @@
+/**************************************************************************/
+/* metal_objects.mm */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+/**************************************************************************/
+/* */
+/* Portions of this code were derived from MoltenVK. */
+/* */
+/* Copyright (c) 2015-2023 The Brenwill Workshop Ltd. */
+/* (http://www.brenwill.com) */
+/* */
+/* 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. */
+/**************************************************************************/
+
+#import "metal_objects.h"
+
+#import "pixel_formats.h"
+#import "rendering_device_driver_metal.h"
+
+void MDCommandBuffer::begin() {
+ DEV_ASSERT(commandBuffer == nil);
+ commandBuffer = queue.commandBuffer;
+}
+
+void MDCommandBuffer::end() {
+ switch (type) {
+ case MDCommandBufferStateType::None:
+ return;
+ case MDCommandBufferStateType::Render:
+ return render_end_pass();
+ case MDCommandBufferStateType::Compute:
+ return _end_compute_dispatch();
+ case MDCommandBufferStateType::Blit:
+ return _end_blit();
+ }
+}
+
+void MDCommandBuffer::commit() {
+ end();
+ [commandBuffer commit];
+ commandBuffer = nil;
+}
+
+void MDCommandBuffer::bind_pipeline(RDD::PipelineID p_pipeline) {
+ MDPipeline *p = (MDPipeline *)(p_pipeline.id);
+
+ // End current encoder if it is a compute encoder or blit encoder,
+ // as they do not have a defined end boundary in the RDD like render.
+ if (type == MDCommandBufferStateType::Compute) {
+ _end_compute_dispatch();
+ } else if (type == MDCommandBufferStateType::Blit) {
+ _end_blit();
+ }
+
+ if (p->type == MDPipelineType::Render) {
+ DEV_ASSERT(type == MDCommandBufferStateType::Render);
+ MDRenderPipeline *rp = (MDRenderPipeline *)p;
+
+ if (render.encoder == nil) {
+ // This condition occurs when there are no attachments when calling render_next_subpass()
+ // and is due to the SUPPORTS_FRAGMENT_SHADER_WITH_ONLY_SIDE_EFFECTS flag.
+ render.desc.defaultRasterSampleCount = static_cast(rp->sample_count);
+
+// NOTE(sgc): This is to test rdar://FB13605547 and will be deleted once fix is confirmed.
+#if 0
+ if (render.pipeline->sample_count == 4) {
+ static id tex = nil;
+ static id res_tex = nil;
+ static dispatch_once_t onceToken;
+ dispatch_once(&onceToken, ^{
+ Size2i sz = render.frameBuffer->size;
+ MTLTextureDescriptor *td = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatRGBA8Unorm width:sz.width height:sz.height mipmapped:NO];
+ td.textureType = MTLTextureType2DMultisample;
+ td.storageMode = MTLStorageModeMemoryless;
+ td.usage = MTLTextureUsageRenderTarget;
+ td.sampleCount = render.pipeline->sample_count;
+ tex = [device_driver->get_device() newTextureWithDescriptor:td];
+
+ td.textureType = MTLTextureType2D;
+ td.storageMode = MTLStorageModePrivate;
+ td.usage = MTLTextureUsageShaderWrite;
+ td.sampleCount = 1;
+ res_tex = [device_driver->get_device() newTextureWithDescriptor:td];
+ });
+ render.desc.colorAttachments[0].texture = tex;
+ render.desc.colorAttachments[0].loadAction = MTLLoadActionClear;
+ render.desc.colorAttachments[0].storeAction = MTLStoreActionMultisampleResolve;
+
+ render.desc.colorAttachments[0].resolveTexture = res_tex;
+ }
+#endif
+ render.encoder = [commandBuffer renderCommandEncoderWithDescriptor:render.desc];
+ }
+
+ if (render.pipeline != rp) {
+ render.dirty.set_flag((RenderState::DirtyFlag)(RenderState::DIRTY_PIPELINE | RenderState::DIRTY_RASTER));
+ // Mark all uniforms as dirty, as variants of a shader pipeline may have a different entry point ABI,
+ // due to setting force_active_argument_buffer_resources = true for spirv_cross::CompilerMSL::Options.
+ // As a result, uniform sets with the same layout will generate redundant binding warnings when
+ // capturing a Metal frame in Xcode.
+ //
+ // If we don't mark as dirty, then some bindings will generate a validation error.
+ render.mark_uniforms_dirty();
+ if (render.pipeline != nullptr && render.pipeline->depth_stencil != rp->depth_stencil) {
+ render.dirty.set_flag(RenderState::DIRTY_DEPTH);
+ }
+ render.pipeline = rp;
+ }
+ } else if (p->type == MDPipelineType::Compute) {
+ DEV_ASSERT(type == MDCommandBufferStateType::None);
+ type = MDCommandBufferStateType::Compute;
+
+ compute.pipeline = (MDComputePipeline *)p;
+ compute.encoder = commandBuffer.computeCommandEncoder;
+ [compute.encoder setComputePipelineState:compute.pipeline->state];
+ }
+}
+
+id MDCommandBuffer::blit_command_encoder() {
+ switch (type) {
+ case MDCommandBufferStateType::None:
+ break;
+ case MDCommandBufferStateType::Render:
+ render_end_pass();
+ break;
+ case MDCommandBufferStateType::Compute:
+ _end_compute_dispatch();
+ break;
+ case MDCommandBufferStateType::Blit:
+ return blit.encoder;
+ }
+
+ type = MDCommandBufferStateType::Blit;
+ blit.encoder = commandBuffer.blitCommandEncoder;
+ return blit.encoder;
+}
+
+void MDCommandBuffer::encodeRenderCommandEncoderWithDescriptor(MTLRenderPassDescriptor *p_desc, NSString *p_label) {
+ switch (type) {
+ case MDCommandBufferStateType::None:
+ break;
+ case MDCommandBufferStateType::Render:
+ render_end_pass();
+ break;
+ case MDCommandBufferStateType::Compute:
+ _end_compute_dispatch();
+ break;
+ case MDCommandBufferStateType::Blit:
+ _end_blit();
+ break;
+ }
+
+ id enc = [commandBuffer renderCommandEncoderWithDescriptor:p_desc];
+ if (p_label != nil) {
+ [enc pushDebugGroup:p_label];
+ [enc popDebugGroup];
+ }
+ [enc endEncoding];
+}
+
+#pragma mark - Render Commands
+
+void MDCommandBuffer::render_bind_uniform_set(RDD::UniformSetID p_uniform_set, RDD::ShaderID p_shader, uint32_t p_set_index) {
+ DEV_ASSERT(type == MDCommandBufferStateType::Render);
+
+ MDUniformSet *set = (MDUniformSet *)(p_uniform_set.id);
+ if (render.uniform_sets.size() <= set->index) {
+ uint32_t s = render.uniform_sets.size();
+ render.uniform_sets.resize(set->index + 1);
+ // Set intermediate values to null.
+ std::fill(&render.uniform_sets[s], &render.uniform_sets[set->index] + 1, nullptr);
+ }
+
+ if (render.uniform_sets[set->index] != set) {
+ render.dirty.set_flag(RenderState::DIRTY_UNIFORMS);
+ render.uniform_set_mask |= 1ULL << set->index;
+ render.uniform_sets[set->index] = set;
+ }
+}
+
+void MDCommandBuffer::render_clear_attachments(VectorView p_attachment_clears, VectorView p_rects) {
+ DEV_ASSERT(type == MDCommandBufferStateType::Render);
+
+ uint32_t vertex_count = p_rects.size() * 6;
+
+ simd::float4 vertices[vertex_count];
+ simd::float4 clear_colors[ClearAttKey::ATTACHMENT_COUNT];
+
+ Size2i size = render.frameBuffer->size;
+ Rect2i render_area = render.clip_to_render_area({ { 0, 0 }, size });
+ size = Size2i(render_area.position.x + render_area.size.width, render_area.position.y + render_area.size.height);
+ _populate_vertices(vertices, size, p_rects);
+
+ ClearAttKey key;
+ key.sample_count = render.pass->get_sample_count();
+
+ float depth_value = 0;
+ uint32_t stencil_value = 0;
+
+ for (uint32_t i = 0; i < p_attachment_clears.size(); i++) {
+ RDD::AttachmentClear const &attClear = p_attachment_clears[i];
+ uint32_t attachment_index;
+ if (attClear.aspect.has_flag(RDD::TEXTURE_ASPECT_COLOR_BIT)) {
+ attachment_index = attClear.color_attachment;
+ } else {
+ attachment_index = render.pass->subpasses[render.current_subpass].depth_stencil_reference.attachment;
+ }
+
+ MDAttachment const &mda = render.pass->attachments[attachment_index];
+ if (attClear.aspect.has_flag(RDD::TEXTURE_ASPECT_COLOR_BIT)) {
+ key.set_color_format(attachment_index, mda.format);
+ clear_colors[attachment_index] = {
+ attClear.value.color.r,
+ attClear.value.color.g,
+ attClear.value.color.b,
+ attClear.value.color.a
+ };
+ }
+
+ if (attClear.aspect.has_flag(RDD::TEXTURE_ASPECT_DEPTH_BIT)) {
+ key.set_depth_format(mda.format);
+ depth_value = attClear.value.depth;
+ }
+
+ if (attClear.aspect.has_flag(RDD::TEXTURE_ASPECT_STENCIL_BIT)) {
+ key.set_stencil_format(mda.format);
+ stencil_value = attClear.value.stencil;
+ }
+ }
+ clear_colors[ClearAttKey::DEPTH_INDEX] = {
+ depth_value,
+ depth_value,
+ depth_value,
+ depth_value
+ };
+
+ id enc = render.encoder;
+
+ MDResourceCache &cache = device_driver->get_resource_cache();
+
+ [enc pushDebugGroup:@"ClearAttachments"];
+ [enc setRenderPipelineState:cache.get_clear_render_pipeline_state(key, nil)];
+ [enc setDepthStencilState:cache.get_depth_stencil_state(
+ key.is_depth_enabled(),
+ key.is_stencil_enabled())];
+ [enc setStencilReferenceValue:stencil_value];
+ [enc setCullMode:MTLCullModeNone];
+ [enc setTriangleFillMode:MTLTriangleFillModeFill];
+ [enc setDepthBias:0 slopeScale:0 clamp:0];
+ [enc setViewport:{ 0, 0, (double)size.width, (double)size.height, 0.0, 1.0 }];
+ [enc setScissorRect:{ 0, 0, (NSUInteger)size.width, (NSUInteger)size.height }];
+
+ [enc setVertexBytes:clear_colors length:sizeof(clear_colors) atIndex:0];
+ [enc setFragmentBytes:clear_colors length:sizeof(clear_colors) atIndex:0];
+ [enc setVertexBytes:vertices length:vertex_count * sizeof(vertices[0]) atIndex:device_driver->get_metal_buffer_index_for_vertex_attribute_binding(VERT_CONTENT_BUFFER_INDEX)];
+
+ [enc drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:vertex_count];
+ [enc popDebugGroup];
+
+ render.dirty.set_flag((RenderState::DirtyFlag)(RenderState::DIRTY_PIPELINE | RenderState::DIRTY_DEPTH | RenderState::DIRTY_RASTER));
+ render.mark_uniforms_dirty({ 0 }); // Mark index 0 dirty, if there is already a binding for index 0.
+ render.mark_viewport_dirty();
+ render.mark_scissors_dirty();
+ render.mark_vertex_dirty();
+}
+
+void MDCommandBuffer::_render_set_dirty_state() {
+ _render_bind_uniform_sets();
+
+ if (render.dirty.has_flag(RenderState::DIRTY_PIPELINE)) {
+ [render.encoder setRenderPipelineState:render.pipeline->state];
+ }
+
+ if (render.dirty.has_flag(RenderState::DIRTY_VIEWPORT)) {
+ [render.encoder setViewports:render.viewports.ptr() count:render.viewports.size()];
+ }
+
+ if (render.dirty.has_flag(RenderState::DIRTY_DEPTH)) {
+ [render.encoder setDepthStencilState:render.pipeline->depth_stencil];
+ }
+
+ if (render.dirty.has_flag(RenderState::DIRTY_RASTER)) {
+ render.pipeline->raster_state.apply(render.encoder);
+ }
+
+ if (render.dirty.has_flag(RenderState::DIRTY_SCISSOR) && !render.scissors.is_empty()) {
+ size_t len = render.scissors.size();
+ MTLScissorRect rects[len];
+ for (size_t i = 0; i < len; i++) {
+ rects[i] = render.clip_to_render_area(render.scissors[i]);
+ }
+ [render.encoder setScissorRects:rects count:len];
+ }
+
+ if (render.dirty.has_flag(RenderState::DIRTY_BLEND) && render.blend_constants.has_value()) {
+ [render.encoder setBlendColorRed:render.blend_constants->r green:render.blend_constants->g blue:render.blend_constants->b alpha:render.blend_constants->a];
+ }
+
+ if (render.dirty.has_flag(RenderState::DIRTY_VERTEX)) {
+ uint32_t p_binding_count = render.vertex_buffers.size();
+ uint32_t first = device_driver->get_metal_buffer_index_for_vertex_attribute_binding(p_binding_count - 1);
+ [render.encoder setVertexBuffers:render.vertex_buffers.ptr()
+ offsets:render.vertex_offsets.ptr()
+ withRange:NSMakeRange(first, p_binding_count)];
+ }
+
+ render.dirty.clear();
+}
+
+void MDCommandBuffer::render_set_viewport(VectorView p_viewports) {
+ render.viewports.resize(p_viewports.size());
+ for (uint32_t i = 0; i < p_viewports.size(); i += 1) {
+ Rect2i const &vp = p_viewports[i];
+ render.viewports[i] = {
+ .originX = static_cast(vp.position.x),
+ .originY = static_cast(vp.position.y),
+ .width = static_cast(vp.size.width),
+ .height = static_cast(vp.size.height),
+ .znear = 0.0,
+ .zfar = 1.0,
+ };
+ }
+
+ render.dirty.set_flag(RenderState::DIRTY_VIEWPORT);
+}
+
+void MDCommandBuffer::render_set_scissor(VectorView p_scissors) {
+ render.scissors.resize(p_scissors.size());
+ for (uint32_t i = 0; i < p_scissors.size(); i += 1) {
+ Rect2i const &vp = p_scissors[i];
+ render.scissors[i] = {
+ .x = static_cast(vp.position.x),
+ .y = static_cast(vp.position.y),
+ .width = static_cast(vp.size.width),
+ .height = static_cast(vp.size.height),
+ };
+ }
+
+ render.dirty.set_flag(RenderState::DIRTY_SCISSOR);
+}
+
+void MDCommandBuffer::render_set_blend_constants(const Color &p_constants) {
+ DEV_ASSERT(type == MDCommandBufferStateType::Render);
+ if (render.blend_constants != p_constants) {
+ render.blend_constants = p_constants;
+ render.dirty.set_flag(RenderState::DIRTY_BLEND);
+ }
+}
+
+void MDCommandBuffer::_render_bind_uniform_sets() {
+ DEV_ASSERT(type == MDCommandBufferStateType::Render);
+ if (!render.dirty.has_flag(RenderState::DIRTY_UNIFORMS)) {
+ return;
+ }
+
+ render.dirty.clear_flag(RenderState::DIRTY_UNIFORMS);
+ uint64_t set_uniforms = render.uniform_set_mask;
+ render.uniform_set_mask = 0;
+
+ id enc = render.encoder;
+ MDRenderShader *shader = render.pipeline->shader;
+ id device = enc.device;
+
+ while (set_uniforms != 0) {
+ // Find the index of the next set bit.
+ int index = __builtin_ctzll(set_uniforms);
+ // Clear the set bit.
+ set_uniforms &= ~(1ULL << index);
+ MDUniformSet *set = render.uniform_sets[index];
+ if (set == nullptr || set->index >= (uint32_t)shader->sets.size()) {
+ continue;
+ }
+ UniformSet const &set_info = shader->sets[set->index];
+
+ BoundUniformSet &bus = set->boundUniformSetForShader(shader, device);
+
+ for (KeyValue, StageResourceUsage> const &keyval : bus.bound_resources) {
+ MTLResourceUsage usage = resource_usage_for_stage(keyval.value, RDD::ShaderStage::SHADER_STAGE_VERTEX);
+ if (usage != 0) {
+ [enc useResource:keyval.key usage:usage stages:MTLRenderStageVertex];
+ }
+ usage = resource_usage_for_stage(keyval.value, RDD::ShaderStage::SHADER_STAGE_FRAGMENT);
+ if (usage != 0) {
+ [enc useResource:keyval.key usage:usage stages:MTLRenderStageFragment];
+ }
+ }
+
+ // Set the buffer for the vertex stage.
+ {
+ uint32_t const *offset = set_info.offsets.getptr(RDD::SHADER_STAGE_VERTEX);
+ if (offset) {
+ [enc setVertexBuffer:bus.buffer offset:*offset atIndex:set->index];
+ }
+ }
+ // Set the buffer for the fragment stage.
+ {
+ uint32_t const *offset = set_info.offsets.getptr(RDD::SHADER_STAGE_FRAGMENT);
+ if (offset) {
+ [enc setFragmentBuffer:bus.buffer offset:*offset atIndex:set->index];
+ }
+ }
+ }
+}
+
+void MDCommandBuffer::_populate_vertices(simd::float4 *p_vertices, Size2i p_fb_size, VectorView p_rects) {
+ uint32_t idx = 0;
+ for (uint32_t i = 0; i < p_rects.size(); i++) {
+ Rect2i const &rect = p_rects[i];
+ idx = _populate_vertices(p_vertices, idx, rect, p_fb_size);
+ }
+}
+
+uint32_t MDCommandBuffer::_populate_vertices(simd::float4 *p_vertices, uint32_t p_index, Rect2i const &p_rect, Size2i p_fb_size) {
+ // Determine the positions of the four edges of the
+ // clear rectangle as a fraction of the attachment size.
+ float leftPos = (float)(p_rect.position.x) / (float)p_fb_size.width;
+ float rightPos = (float)(p_rect.size.width) / (float)p_fb_size.width + leftPos;
+ float bottomPos = (float)(p_rect.position.y) / (float)p_fb_size.height;
+ float topPos = (float)(p_rect.size.height) / (float)p_fb_size.height + bottomPos;
+
+ // Transform to clip-space coordinates, which are bounded by (-1.0 < p < 1.0) in clip-space.
+ leftPos = (leftPos * 2.0f) - 1.0f;
+ rightPos = (rightPos * 2.0f) - 1.0f;
+ bottomPos = (bottomPos * 2.0f) - 1.0f;
+ topPos = (topPos * 2.0f) - 1.0f;
+
+ simd::float4 vtx;
+
+ uint32_t idx = p_index;
+ vtx.z = 0.0;
+ vtx.w = (float)1;
+
+ // Top left vertex - First triangle.
+ vtx.y = topPos;
+ vtx.x = leftPos;
+ p_vertices[idx++] = vtx;
+
+ // Bottom left vertex.
+ vtx.y = bottomPos;
+ vtx.x = leftPos;
+ p_vertices[idx++] = vtx;
+
+ // Bottom right vertex.
+ vtx.y = bottomPos;
+ vtx.x = rightPos;
+ p_vertices[idx++] = vtx;
+
+ // Bottom right vertex - Second triangle.
+ p_vertices[idx++] = vtx;
+
+ // Top right vertex.
+ vtx.y = topPos;
+ vtx.x = rightPos;
+ p_vertices[idx++] = vtx;
+
+ // Top left vertex.
+ vtx.y = topPos;
+ vtx.x = leftPos;
+ p_vertices[idx++] = vtx;
+
+ return idx;
+}
+
+void MDCommandBuffer::render_begin_pass(RDD::RenderPassID p_render_pass, RDD::FramebufferID p_frameBuffer, RDD::CommandBufferType p_cmd_buffer_type, const Rect2i &p_rect, VectorView p_clear_values) {
+ DEV_ASSERT(commandBuffer != nil);
+ end();
+
+ MDRenderPass *pass = (MDRenderPass *)(p_render_pass.id);
+ MDFrameBuffer *fb = (MDFrameBuffer *)(p_frameBuffer.id);
+
+ type = MDCommandBufferStateType::Render;
+ render.pass = pass;
+ render.current_subpass = UINT32_MAX;
+ render.render_area = p_rect;
+ render.clear_values.resize(p_clear_values.size());
+ for (uint32_t i = 0; i < p_clear_values.size(); i++) {
+ render.clear_values[i] = p_clear_values[i];
+ }
+ render.is_rendering_entire_area = (p_rect.position == Point2i(0, 0)) && p_rect.size == fb->size;
+ render.frameBuffer = fb;
+ render_next_subpass();
+}
+
+void MDCommandBuffer::_end_render_pass() {
+ MDFrameBuffer const &fb_info = *render.frameBuffer;
+ MDRenderPass const &pass_info = *render.pass;
+ MDSubpass const &subpass = pass_info.subpasses[render.current_subpass];
+
+ PixelFormats &pf = device_driver->get_pixel_formats();
+
+ for (uint32_t i = 0; i < subpass.resolve_references.size(); i++) {
+ uint32_t color_index = subpass.color_references[i].attachment;
+ uint32_t resolve_index = subpass.resolve_references[i].attachment;
+ DEV_ASSERT((color_index == RDD::AttachmentReference::UNUSED) == (resolve_index == RDD::AttachmentReference::UNUSED));
+ if (color_index == RDD::AttachmentReference::UNUSED || !fb_info.textures[color_index]) {
+ continue;
+ }
+
+ id resolve_tex = fb_info.textures[resolve_index];
+
+ CRASH_COND_MSG(!flags::all(pf.getCapabilities(resolve_tex.pixelFormat), kMTLFmtCapsResolve), "not implemented: unresolvable texture types");
+ // see: https://github.com/KhronosGroup/MoltenVK/blob/d20d13fe2735adb845636a81522df1b9d89c0fba/MoltenVK/MoltenVK/GPUObjects/MVKRenderPass.mm#L407
+ }
+
+ [render.encoder endEncoding];
+ render.encoder = nil;
+}
+
+void MDCommandBuffer::_render_clear_render_area() {
+ MDRenderPass const &pass = *render.pass;
+ MDSubpass const &subpass = pass.subpasses[render.current_subpass];
+
+ // First determine attachments that should be cleared.
+ LocalVector clears;
+ clears.reserve(subpass.color_references.size() + /* possible depth stencil clear */ 1);
+
+ for (uint32_t i = 0; i < subpass.color_references.size(); i++) {
+ uint32_t idx = subpass.color_references[i].attachment;
+ if (idx != RDD::AttachmentReference::UNUSED && pass.attachments[idx].shouldClear(subpass, false)) {
+ clears.push_back({ .aspect = RDD::TEXTURE_ASPECT_COLOR_BIT, .color_attachment = idx, .value = render.clear_values[idx] });
+ }
+ }
+ uint32_t ds_index = subpass.depth_stencil_reference.attachment;
+ MDAttachment const &attachment = pass.attachments[ds_index];
+ bool shouldClearDepth = (ds_index != RDD::AttachmentReference::UNUSED && attachment.shouldClear(subpass, false));
+ bool shouldClearStencil = (ds_index != RDD::AttachmentReference::UNUSED && attachment.shouldClear(subpass, true));
+ if (shouldClearDepth || shouldClearStencil) {
+ BitField bits;
+ if (shouldClearDepth && attachment.type & MDAttachmentType::Depth) {
+ bits.set_flag(RDD::TEXTURE_ASPECT_DEPTH_BIT);
+ }
+ if (shouldClearStencil && attachment.type & MDAttachmentType::Stencil) {
+ bits.set_flag(RDD::TEXTURE_ASPECT_STENCIL_BIT);
+ }
+
+ clears.push_back({ .aspect = bits, .color_attachment = ds_index, .value = render.clear_values[ds_index] });
+ }
+
+ if (clears.is_empty()) {
+ return;
+ }
+
+ render_clear_attachments(clears, { render.render_area });
+}
+
+void MDCommandBuffer::render_next_subpass() {
+ DEV_ASSERT(commandBuffer != nil);
+
+ if (render.current_subpass == UINT32_MAX) {
+ render.current_subpass = 0;
+ } else {
+ _end_render_pass();
+ render.current_subpass++;
+ }
+
+ MDFrameBuffer const &fb = *render.frameBuffer;
+ MDRenderPass const &pass = *render.pass;
+ MDSubpass const &subpass = pass.subpasses[render.current_subpass];
+
+ MTLRenderPassDescriptor *desc = MTLRenderPassDescriptor.renderPassDescriptor;
+ PixelFormats &pf = device_driver->get_pixel_formats();
+
+ uint32_t attachmentCount = 0;
+ for (uint32_t i = 0; i < subpass.color_references.size(); i++) {
+ uint32_t idx = subpass.color_references[i].attachment;
+ if (idx == RDD::AttachmentReference::UNUSED) {
+ continue;
+ }
+
+ attachmentCount += 1;
+ MTLRenderPassColorAttachmentDescriptor *ca = desc.colorAttachments[i];
+
+ uint32_t resolveIdx = subpass.resolve_references.is_empty() ? RDD::AttachmentReference::UNUSED : subpass.resolve_references[i].attachment;
+ bool has_resolve = resolveIdx != RDD::AttachmentReference::UNUSED;
+ bool can_resolve = true;
+ if (resolveIdx != RDD::AttachmentReference::UNUSED) {
+ id resolve_tex = fb.textures[resolveIdx];
+ can_resolve = flags::all(pf.getCapabilities(resolve_tex.pixelFormat), kMTLFmtCapsResolve);
+ if (can_resolve) {
+ ca.resolveTexture = resolve_tex;
+ } else {
+ CRASH_NOW_MSG("unimplemented: using a texture format that is not supported for resolve");
+ }
+ }
+
+ MDAttachment const &attachment = pass.attachments[idx];
+
+ id tex = fb.textures[idx];
+ if ((attachment.type & MDAttachmentType::Color)) {
+ if (attachment.configureDescriptor(ca, pf, subpass, tex, render.is_rendering_entire_area, has_resolve, can_resolve, false)) {
+ Color clearColor = render.clear_values[idx].color;
+ ca.clearColor = MTLClearColorMake(clearColor.r, clearColor.g, clearColor.b, clearColor.a);
+ }
+ }
+ }
+
+ if (subpass.depth_stencil_reference.attachment != RDD::AttachmentReference::UNUSED) {
+ attachmentCount += 1;
+ uint32_t idx = subpass.depth_stencil_reference.attachment;
+ MDAttachment const &attachment = pass.attachments[idx];
+ id tex = fb.textures[idx];
+ if (attachment.type & MDAttachmentType::Depth) {
+ MTLRenderPassDepthAttachmentDescriptor *da = desc.depthAttachment;
+ if (attachment.configureDescriptor(da, pf, subpass, tex, render.is_rendering_entire_area, false, false, false)) {
+ da.clearDepth = render.clear_values[idx].depth;
+ }
+ }
+
+ if (attachment.type & MDAttachmentType::Stencil) {
+ MTLRenderPassStencilAttachmentDescriptor *sa = desc.stencilAttachment;
+ if (attachment.configureDescriptor(sa, pf, subpass, tex, render.is_rendering_entire_area, false, false, true)) {
+ sa.clearStencil = render.clear_values[idx].stencil;
+ }
+ }
+ }
+
+ desc.renderTargetWidth = MAX((NSUInteger)MIN(render.render_area.position.x + render.render_area.size.width, fb.size.width), 1u);
+ desc.renderTargetHeight = MAX((NSUInteger)MIN(render.render_area.position.y + render.render_area.size.height, fb.size.height), 1u);
+
+ if (attachmentCount == 0) {
+ // If there are no attachments, delay the creation of the encoder,
+ // so we can use a matching sample count for the pipeline, by setting
+ // the defaultRasterSampleCount from the pipeline's sample count.
+ render.desc = desc;
+ } else {
+ render.encoder = [commandBuffer renderCommandEncoderWithDescriptor:desc];
+
+ if (!render.is_rendering_entire_area) {
+ _render_clear_render_area();
+ }
+ // With a new encoder, all state is dirty.
+ render.dirty.set_flag(RenderState::DIRTY_ALL);
+ }
+}
+
+void MDCommandBuffer::render_draw(uint32_t p_vertex_count,
+ uint32_t p_instance_count,
+ uint32_t p_base_vertex,
+ uint32_t p_first_instance) {
+ DEV_ASSERT(type == MDCommandBufferStateType::Render);
+ _render_set_dirty_state();
+
+ DEV_ASSERT(render.dirty == 0);
+
+ id enc = render.encoder;
+
+ [enc drawPrimitives:render.pipeline->raster_state.render_primitive
+ vertexStart:p_base_vertex
+ vertexCount:p_vertex_count
+ instanceCount:p_instance_count
+ baseInstance:p_first_instance];
+}
+
+void MDCommandBuffer::render_bind_vertex_buffers(uint32_t p_binding_count, const RDD::BufferID *p_buffers, const uint64_t *p_offsets) {
+ DEV_ASSERT(type == MDCommandBufferStateType::Render);
+
+ render.vertex_buffers.resize(p_binding_count);
+ render.vertex_offsets.resize(p_binding_count);
+
+ // Reverse the buffers, as their bindings are assigned in descending order.
+ for (uint32_t i = 0; i < p_binding_count; i += 1) {
+ render.vertex_buffers[i] = rid::get(p_buffers[p_binding_count - i - 1]);
+ render.vertex_offsets[i] = p_offsets[p_binding_count - i - 1];
+ }
+
+ if (render.encoder) {
+ uint32_t first = device_driver->get_metal_buffer_index_for_vertex_attribute_binding(p_binding_count - 1);
+ [render.encoder setVertexBuffers:render.vertex_buffers.ptr()
+ offsets:render.vertex_offsets.ptr()
+ withRange:NSMakeRange(first, p_binding_count)];
+ } else {
+ render.dirty.set_flag(RenderState::DIRTY_VERTEX);
+ }
+}
+
+void MDCommandBuffer::render_bind_index_buffer(RDD::BufferID p_buffer, RDD::IndexBufferFormat p_format, uint64_t p_offset) {
+ DEV_ASSERT(type == MDCommandBufferStateType::Render);
+
+ render.index_buffer = rid::get(p_buffer);
+ render.index_type = p_format == RDD::IndexBufferFormat::INDEX_BUFFER_FORMAT_UINT16 ? MTLIndexTypeUInt16 : MTLIndexTypeUInt32;
+}
+
+void MDCommandBuffer::render_draw_indexed(uint32_t p_index_count,
+ uint32_t p_instance_count,
+ uint32_t p_first_index,
+ int32_t p_vertex_offset,
+ uint32_t p_first_instance) {
+ DEV_ASSERT(type == MDCommandBufferStateType::Render);
+ _render_set_dirty_state();
+
+ id enc = render.encoder;
+
+ [enc drawIndexedPrimitives:render.pipeline->raster_state.render_primitive
+ indexCount:p_index_count
+ indexType:render.index_type
+ indexBuffer:render.index_buffer
+ indexBufferOffset:p_vertex_offset
+ instanceCount:p_instance_count
+ baseVertex:p_first_index
+ baseInstance:p_first_instance];
+}
+
+void MDCommandBuffer::render_draw_indexed_indirect(RDD::BufferID p_indirect_buffer, uint64_t p_offset, uint32_t p_draw_count, uint32_t p_stride) {
+ DEV_ASSERT(type == MDCommandBufferStateType::Render);
+ _render_set_dirty_state();
+
+ id enc = render.encoder;
+
+ id indirect_buffer = rid::get(p_indirect_buffer);
+ NSUInteger indirect_offset = p_offset;
+
+ for (uint32_t i = 0; i < p_draw_count; i++) {
+ [enc drawIndexedPrimitives:render.pipeline->raster_state.render_primitive
+ indexType:render.index_type
+ indexBuffer:render.index_buffer
+ indexBufferOffset:0
+ indirectBuffer:indirect_buffer
+ indirectBufferOffset:indirect_offset];
+ indirect_offset += p_stride;
+ }
+}
+
+void MDCommandBuffer::render_draw_indexed_indirect_count(RDD::BufferID p_indirect_buffer, uint64_t p_offset, RDD::BufferID p_count_buffer, uint64_t p_count_buffer_offset, uint32_t p_max_draw_count, uint32_t p_stride) {
+ ERR_FAIL_MSG("not implemented");
+}
+
+void MDCommandBuffer::render_draw_indirect(RDD::BufferID p_indirect_buffer, uint64_t p_offset, uint32_t p_draw_count, uint32_t p_stride) {
+ DEV_ASSERT(type == MDCommandBufferStateType::Render);
+ _render_set_dirty_state();
+
+ id enc = render.encoder;
+
+ id indirect_buffer = rid::get(p_indirect_buffer);
+ NSUInteger indirect_offset = p_offset;
+
+ for (uint32_t i = 0; i < p_draw_count; i++) {
+ [enc drawPrimitives:render.pipeline->raster_state.render_primitive
+ indirectBuffer:indirect_buffer
+ indirectBufferOffset:indirect_offset];
+ indirect_offset += p_stride;
+ }
+}
+
+void MDCommandBuffer::render_draw_indirect_count(RDD::BufferID p_indirect_buffer, uint64_t p_offset, RDD::BufferID p_count_buffer, uint64_t p_count_buffer_offset, uint32_t p_max_draw_count, uint32_t p_stride) {
+ ERR_FAIL_MSG("not implemented");
+}
+
+void MDCommandBuffer::render_end_pass() {
+ DEV_ASSERT(type == MDCommandBufferStateType::Render);
+
+ [render.encoder endEncoding];
+ render.reset();
+ type = MDCommandBufferStateType::None;
+}
+
+#pragma mark - Compute
+
+void MDCommandBuffer::compute_bind_uniform_set(RDD::UniformSetID p_uniform_set, RDD::ShaderID p_shader, uint32_t p_set_index) {
+ DEV_ASSERT(type == MDCommandBufferStateType::Compute);
+
+ id enc = compute.encoder;
+ id device = enc.device;
+
+ MDShader *shader = (MDShader *)(p_shader.id);
+ UniformSet const &set_info = shader->sets[p_set_index];
+
+ MDUniformSet *set = (MDUniformSet *)(p_uniform_set.id);
+ BoundUniformSet &bus = set->boundUniformSetForShader(shader, device);
+
+ for (KeyValue, StageResourceUsage> &keyval : bus.bound_resources) {
+ MTLResourceUsage usage = resource_usage_for_stage(keyval.value, RDD::ShaderStage::SHADER_STAGE_COMPUTE);
+ if (usage != 0) {
+ [enc useResource:keyval.key usage:usage];
+ }
+ }
+
+ uint32_t const *offset = set_info.offsets.getptr(RDD::SHADER_STAGE_COMPUTE);
+ if (offset) {
+ [enc setBuffer:bus.buffer offset:*offset atIndex:p_set_index];
+ }
+}
+
+void MDCommandBuffer::compute_dispatch(uint32_t p_x_groups, uint32_t p_y_groups, uint32_t p_z_groups) {
+ DEV_ASSERT(type == MDCommandBufferStateType::Compute);
+
+ MTLRegion region = MTLRegionMake3D(0, 0, 0, p_x_groups, p_y_groups, p_z_groups);
+
+ id enc = compute.encoder;
+ [enc dispatchThreadgroups:region.size threadsPerThreadgroup:compute.pipeline->compute_state.local];
+}
+
+void MDCommandBuffer::compute_dispatch_indirect(RDD::BufferID p_indirect_buffer, uint64_t p_offset) {
+ DEV_ASSERT(type == MDCommandBufferStateType::Compute);
+
+ id indirectBuffer = rid::get(p_indirect_buffer);
+
+ id enc = compute.encoder;
+ [enc dispatchThreadgroupsWithIndirectBuffer:indirectBuffer indirectBufferOffset:p_offset threadsPerThreadgroup:compute.pipeline->compute_state.local];
+}
+
+void MDCommandBuffer::_end_compute_dispatch() {
+ DEV_ASSERT(type == MDCommandBufferStateType::Compute);
+
+ [compute.encoder endEncoding];
+ compute.reset();
+ type = MDCommandBufferStateType::None;
+}
+
+void MDCommandBuffer::_end_blit() {
+ DEV_ASSERT(type == MDCommandBufferStateType::Blit);
+
+ [blit.encoder endEncoding];
+ blit.reset();
+ type = MDCommandBufferStateType::None;
+}
+
+MDComputeShader::MDComputeShader(CharString p_name, Vector p_sets, id p_kernel) :
+ MDShader(p_name, p_sets), kernel(p_kernel) {
+}
+
+void MDComputeShader::encode_push_constant_data(VectorView p_data, MDCommandBuffer *p_cb) {
+ DEV_ASSERT(p_cb->type == MDCommandBufferStateType::Compute);
+ if (push_constants.binding == (uint32_t)-1) {
+ return;
+ }
+
+ id enc = p_cb->compute.encoder;
+
+ void const *ptr = p_data.ptr();
+ size_t length = p_data.size() * sizeof(uint32_t);
+
+ [enc setBytes:ptr length:length atIndex:push_constants.binding];
+}
+
+MDRenderShader::MDRenderShader(CharString p_name, Vector p_sets, id _Nonnull p_vert, id _Nonnull p_frag) :
+ MDShader(p_name, p_sets), vert(p_vert), frag(p_frag) {
+}
+
+void MDRenderShader::encode_push_constant_data(VectorView p_data, MDCommandBuffer *p_cb) {
+ DEV_ASSERT(p_cb->type == MDCommandBufferStateType::Render);
+ id enc = p_cb->render.encoder;
+
+ void const *ptr = p_data.ptr();
+ size_t length = p_data.size() * sizeof(uint32_t);
+
+ if (push_constants.vert.binding > -1) {
+ [enc setVertexBytes:ptr length:length atIndex:push_constants.vert.binding];
+ }
+
+ if (push_constants.frag.binding > -1) {
+ [enc setFragmentBytes:ptr length:length atIndex:push_constants.frag.binding];
+ }
+}
+
+BoundUniformSet &MDUniformSet::boundUniformSetForShader(MDShader *p_shader, id p_device) {
+ BoundUniformSet *sus = bound_uniforms.getptr(p_shader);
+ if (sus != nullptr) {
+ return *sus;
+ }
+
+ UniformSet const &set = p_shader->sets[index];
+
+ HashMap, StageResourceUsage> bound_resources;
+ auto add_usage = [&bound_resources](id __unsafe_unretained res, RDD::ShaderStage stage, MTLResourceUsage usage) {
+ StageResourceUsage *sru = bound_resources.getptr(res);
+ if (sru == nullptr) {
+ bound_resources.insert(res, stage_resource_usage(stage, usage));
+ } else {
+ *sru |= stage_resource_usage(stage, usage);
+ }
+ };
+ id enc_buffer = nil;
+ if (set.buffer_size > 0) {
+ MTLResourceOptions options = MTLResourceStorageModeShared | MTLResourceHazardTrackingModeTracked;
+ enc_buffer = [p_device newBufferWithLength:set.buffer_size options:options];
+ for (KeyValue> const &kv : set.encoders) {
+ RDD::ShaderStage const stage = kv.key;
+ ShaderStageUsage const stage_usage = ShaderStageUsage(1 << stage);
+ id const enc = kv.value;
+
+ [enc setArgumentBuffer:enc_buffer offset:set.offsets[stage]];
+
+ for (uint32_t i = 0; i < uniforms.size(); i++) {
+ RDD::BoundUniform const &uniform = uniforms[i];
+ UniformInfo ui = set.uniforms[i];
+
+ BindingInfo *bi = ui.bindings.getptr(stage);
+ if (bi == nullptr) {
+ // No binding for this stage.
+ continue;
+ }
+
+ if ((ui.active_stages & stage_usage) == 0) {
+ // Not active for this state, so don't bind anything.
+ continue;
+ }
+
+ switch (uniform.type) {
+ case RDD::UNIFORM_TYPE_SAMPLER: {
+ size_t count = uniform.ids.size();
+ id __unsafe_unretained *objects = ALLOCA_ARRAY(id __unsafe_unretained, count);
+ for (size_t j = 0; j < count; j += 1) {
+ objects[j] = rid::get(uniform.ids[j].id);
+ }
+ [enc setSamplerStates:objects withRange:NSMakeRange(bi->index, count)];
+ } break;
+ case RDD::UNIFORM_TYPE_SAMPLER_WITH_TEXTURE: {
+ size_t count = uniform.ids.size() / 2;
+ id __unsafe_unretained *textures = ALLOCA_ARRAY(id __unsafe_unretained, count);
+ id __unsafe_unretained *samplers = ALLOCA_ARRAY(id __unsafe_unretained, count);
+ for (uint32_t j = 0; j < count; j += 1) {
+ id sampler = rid::get(uniform.ids[j * 2 + 0]);
+ id texture = rid::get(uniform.ids[j * 2 + 1]);
+ samplers[j] = sampler;
+ textures[j] = texture;
+ add_usage(texture, stage, bi->usage);
+ }
+ BindingInfo *sbi = ui.bindings_secondary.getptr(stage);
+ if (sbi) {
+ [enc setSamplerStates:samplers withRange:NSMakeRange(sbi->index, count)];
+ }
+ [enc setTextures:textures
+ withRange:NSMakeRange(bi->index, count)];
+ } break;
+ case RDD::UNIFORM_TYPE_TEXTURE: {
+ size_t count = uniform.ids.size();
+ if (count == 1) {
+ id obj = rid::get(uniform.ids[0]);
+ [enc setTexture:obj atIndex:bi->index];
+ add_usage(obj, stage, bi->usage);
+ } else {
+ id __unsafe_unretained *objects = ALLOCA_ARRAY(id __unsafe_unretained, count);
+ for (size_t j = 0; j < count; j += 1) {
+ id obj = rid::get(uniform.ids[j]);
+ objects[j] = obj;
+ add_usage(obj, stage, bi->usage);
+ }
+ [enc setTextures:objects withRange:NSMakeRange(bi->index, count)];
+ }
+ } break;
+ case RDD::UNIFORM_TYPE_IMAGE: {
+ size_t count = uniform.ids.size();
+ if (count == 1) {
+ id obj = rid::get(uniform.ids[0]);
+ [enc setTexture:obj atIndex:bi->index];
+ add_usage(obj, stage, bi->usage);
+ BindingInfo *sbi = ui.bindings_secondary.getptr(stage);
+ if (sbi) {
+ id tex = obj.parentTexture ? obj.parentTexture : obj;
+ id buf = tex.buffer;
+ if (buf) {
+ [enc setBuffer:buf offset:tex.bufferOffset atIndex:sbi->index];
+ }
+ }
+ } else {
+ id __unsafe_unretained *objects = ALLOCA_ARRAY(id __unsafe_unretained, count);
+ for (size_t j = 0; j < count; j += 1) {
+ id obj = rid::get(uniform.ids[j]);
+ objects[j] = obj;
+ add_usage(obj, stage, bi->usage);
+ }
+ [enc setTextures:objects withRange:NSMakeRange(bi->index, count)];
+ }
+ } break;
+ case RDD::UNIFORM_TYPE_TEXTURE_BUFFER: {
+ ERR_PRINT("not implemented: UNIFORM_TYPE_TEXTURE_BUFFER");
+ } break;
+ case RDD::UNIFORM_TYPE_SAMPLER_WITH_TEXTURE_BUFFER: {
+ ERR_PRINT("not implemented: UNIFORM_TYPE_SAMPLER_WITH_TEXTURE_BUFFER");
+ } break;
+ case RDD::UNIFORM_TYPE_IMAGE_BUFFER: {
+ CRASH_NOW_MSG("not implemented: UNIFORM_TYPE_IMAGE_BUFFER");
+ } break;
+ case RDD::UNIFORM_TYPE_UNIFORM_BUFFER: {
+ id buffer = rid::get(uniform.ids[0]);
+ [enc setBuffer:buffer offset:0 atIndex:bi->index];
+ add_usage(buffer, stage, bi->usage);
+ } break;
+ case RDD::UNIFORM_TYPE_STORAGE_BUFFER: {
+ id buffer = rid::get(uniform.ids[0]);
+ [enc setBuffer:buffer offset:0 atIndex:bi->index];
+ add_usage(buffer, stage, bi->usage);
+ } break;
+ case RDD::UNIFORM_TYPE_INPUT_ATTACHMENT: {
+ size_t count = uniform.ids.size();
+ if (count == 1) {
+ id obj = rid::get(uniform.ids[0]);
+ [enc setTexture:obj atIndex:bi->index];
+ add_usage(obj, stage, bi->usage);
+ } else {
+ id __unsafe_unretained *objects = ALLOCA_ARRAY(id __unsafe_unretained, count);
+ for (size_t j = 0; j < count; j += 1) {
+ id obj = rid::get(uniform.ids[j]);
+ objects[j] = obj;
+ add_usage(obj, stage, bi->usage);
+ }
+ [enc setTextures:objects withRange:NSMakeRange(bi->index, count)];
+ }
+ } break;
+ default: {
+ DEV_ASSERT(false);
+ }
+ }
+ }
+ }
+ }
+
+ BoundUniformSet bs = { .buffer = enc_buffer, .bound_resources = bound_resources };
+ bound_uniforms.insert(p_shader, bs);
+ return bound_uniforms.get(p_shader);
+}
+
+MTLFmtCaps MDSubpass::getRequiredFmtCapsForAttachmentAt(uint32_t p_index) const {
+ MTLFmtCaps caps = kMTLFmtCapsNone;
+
+ for (RDD::AttachmentReference const &ar : input_references) {
+ if (ar.attachment == p_index) {
+ flags::set(caps, kMTLFmtCapsRead);
+ break;
+ }
+ }
+
+ for (RDD::AttachmentReference const &ar : color_references) {
+ if (ar.attachment == p_index) {
+ flags::set(caps, kMTLFmtCapsColorAtt);
+ break;
+ }
+ }
+
+ for (RDD::AttachmentReference const &ar : resolve_references) {
+ if (ar.attachment == p_index) {
+ flags::set(caps, kMTLFmtCapsResolve);
+ break;
+ }
+ }
+
+ if (depth_stencil_reference.attachment == p_index) {
+ flags::set(caps, kMTLFmtCapsDSAtt);
+ }
+
+ return caps;
+}
+
+void MDAttachment::linkToSubpass(const MDRenderPass &p_pass) {
+ firstUseSubpassIndex = UINT32_MAX;
+ lastUseSubpassIndex = 0;
+
+ for (MDSubpass const &subpass : p_pass.subpasses) {
+ MTLFmtCaps reqCaps = subpass.getRequiredFmtCapsForAttachmentAt(index);
+ if (reqCaps) {
+ firstUseSubpassIndex = MIN(subpass.subpass_index, firstUseSubpassIndex);
+ lastUseSubpassIndex = MAX(subpass.subpass_index, lastUseSubpassIndex);
+ }
+ }
+}
+
+MTLStoreAction MDAttachment::getMTLStoreAction(MDSubpass const &p_subpass,
+ bool p_is_rendering_entire_area,
+ bool p_has_resolve,
+ bool p_can_resolve,
+ bool p_is_stencil) const {
+ if (!p_is_rendering_entire_area || !isLastUseOf(p_subpass)) {
+ return p_has_resolve && p_can_resolve ? MTLStoreActionStoreAndMultisampleResolve : MTLStoreActionStore;
+ }
+
+ switch (p_is_stencil ? stencilStoreAction : storeAction) {
+ case MTLStoreActionStore:
+ return p_has_resolve && p_can_resolve ? MTLStoreActionStoreAndMultisampleResolve : MTLStoreActionStore;
+ case MTLStoreActionDontCare:
+ return p_has_resolve ? (p_can_resolve ? MTLStoreActionMultisampleResolve : MTLStoreActionStore) : MTLStoreActionDontCare;
+
+ default:
+ return MTLStoreActionStore;
+ }
+}
+
+bool MDAttachment::configureDescriptor(MTLRenderPassAttachmentDescriptor *p_desc,
+ PixelFormats &p_pf,
+ MDSubpass const &p_subpass,
+ id p_attachment,
+ bool p_is_rendering_entire_area,
+ bool p_has_resolve,
+ bool p_can_resolve,
+ bool p_is_stencil) const {
+ p_desc.texture = p_attachment;
+
+ MTLLoadAction load;
+ if (!p_is_rendering_entire_area || !isFirstUseOf(p_subpass)) {
+ load = MTLLoadActionLoad;
+ } else {
+ load = p_is_stencil ? stencilLoadAction : loadAction;
+ }
+
+ p_desc.loadAction = load;
+
+ MTLPixelFormat mtlFmt = p_attachment.pixelFormat;
+ bool isDepthFormat = p_pf.isDepthFormat(mtlFmt);
+ bool isStencilFormat = p_pf.isStencilFormat(mtlFmt);
+ if (isStencilFormat && !p_is_stencil && !isDepthFormat) {
+ p_desc.storeAction = MTLStoreActionDontCare;
+ } else {
+ p_desc.storeAction = getMTLStoreAction(p_subpass, p_is_rendering_entire_area, p_has_resolve, p_can_resolve, p_is_stencil);
+ }
+
+ return load == MTLLoadActionClear;
+}
+
+bool MDAttachment::shouldClear(const MDSubpass &p_subpass, bool p_is_stencil) const {
+ // If the subpass is not the first subpass to use this attachment, don't clear this attachment.
+ if (p_subpass.subpass_index != firstUseSubpassIndex) {
+ return false;
+ }
+ return (p_is_stencil ? stencilLoadAction : loadAction) == MTLLoadActionClear;
+}
+
+MDRenderPass::MDRenderPass(Vector &p_attachments, Vector &p_subpasses) :
+ attachments(p_attachments), subpasses(p_subpasses) {
+ for (MDAttachment &att : attachments) {
+ att.linkToSubpass(*this);
+ }
+}
+
+#pragma mark - Resource Factory
+
+id MDResourceFactory::new_func(NSString *p_source, NSString *p_name, NSError **p_error) {
+ @autoreleasepool {
+ NSError *err = nil;
+ MTLCompileOptions *options = [MTLCompileOptions new];
+ id device = device_driver->get_device();
+ id mtlLib = [device newLibraryWithSource:p_source
+ options:options
+ error:&err];
+ if (err) {
+ if (p_error != nil) {
+ *p_error = err;
+ }
+ }
+ return [mtlLib newFunctionWithName:p_name];
+ }
+}
+
+id MDResourceFactory::new_clear_vert_func(ClearAttKey &p_key) {
+ @autoreleasepool {
+ NSString *msl = [NSString stringWithFormat:@R"(
+#include
+using namespace metal;
+
+typedef struct {
+ float4 a_position [[attribute(0)]];
+} AttributesPos;
+
+typedef struct {
+ float4 colors[9];
+} ClearColorsIn;
+
+typedef struct {
+ float4 v_position [[position]];
+ uint layer;
+} VaryingsPos;
+
+vertex VaryingsPos vertClear(AttributesPos attributes [[stage_in]], constant ClearColorsIn& ccIn [[buffer(0)]]) {
+ VaryingsPos varyings;
+ varyings.v_position = float4(attributes.a_position.x, -attributes.a_position.y, ccIn.colors[%d].r, 1.0);
+ varyings.layer = uint(attributes.a_position.w);
+ return varyings;
+}
+)",
+ ClearAttKey::DEPTH_INDEX];
+
+ return new_func(msl, @"vertClear", nil);
+ }
+}
+
+id MDResourceFactory::new_clear_frag_func(ClearAttKey &p_key) {
+ @autoreleasepool {
+ NSMutableString *msl = [NSMutableString stringWithCapacity:2048];
+
+ [msl appendFormat:@R"(
+#include
+using namespace metal;
+
+typedef struct {
+ float4 v_position [[position]];
+} VaryingsPos;
+
+typedef struct {
+ float4 colors[9];
+} ClearColorsIn;
+
+typedef struct {
+)"];
+
+ for (uint32_t caIdx = 0; caIdx < ClearAttKey::COLOR_COUNT; caIdx++) {
+ if (p_key.is_enabled(caIdx)) {
+ NSString *typeStr = get_format_type_string((MTLPixelFormat)p_key.pixel_formats[caIdx]);
+ [msl appendFormat:@" %@4 color%u [[color(%u)]];\n", typeStr, caIdx, caIdx];
+ }
+ }
+ [msl appendFormat:@R"(} ClearColorsOut;
+
+fragment ClearColorsOut fragClear(VaryingsPos varyings [[stage_in]], constant ClearColorsIn& ccIn [[buffer(0)]]) {
+
+ ClearColorsOut ccOut;
+)"];
+ for (uint32_t caIdx = 0; caIdx < ClearAttKey::COLOR_COUNT; caIdx++) {
+ if (p_key.is_enabled(caIdx)) {
+ NSString *typeStr = get_format_type_string((MTLPixelFormat)p_key.pixel_formats[caIdx]);
+ [msl appendFormat:@" ccOut.color%u = %@4(ccIn.colors[%u]);\n", caIdx, typeStr, caIdx];
+ }
+ }
+ [msl appendString:@R"( return ccOut;
+})"];
+
+ return new_func(msl, @"fragClear", nil);
+ }
+}
+
+NSString *MDResourceFactory::get_format_type_string(MTLPixelFormat p_fmt) {
+ switch (device_driver->get_pixel_formats().getFormatType(p_fmt)) {
+ case MTLFormatType::ColorInt8:
+ case MTLFormatType::ColorInt16:
+ return @"short";
+ case MTLFormatType::ColorUInt8:
+ case MTLFormatType::ColorUInt16:
+ return @"ushort";
+ case MTLFormatType::ColorInt32:
+ return @"int";
+ case MTLFormatType::ColorUInt32:
+ return @"uint";
+ case MTLFormatType::ColorHalf:
+ return @"half";
+ case MTLFormatType::ColorFloat:
+ case MTLFormatType::DepthStencil:
+ case MTLFormatType::Compressed:
+ return @"float";
+ case MTLFormatType::None:
+ return @"unexpected_MTLPixelFormatInvalid";
+ }
+}
+
+id MDResourceFactory::new_depth_stencil_state(bool p_use_depth, bool p_use_stencil) {
+ MTLDepthStencilDescriptor *dsDesc = [MTLDepthStencilDescriptor new];
+ dsDesc.depthCompareFunction = MTLCompareFunctionAlways;
+ dsDesc.depthWriteEnabled = p_use_depth;
+
+ if (p_use_stencil) {
+ MTLStencilDescriptor *sDesc = [MTLStencilDescriptor new];
+ sDesc.stencilCompareFunction = MTLCompareFunctionAlways;
+ sDesc.stencilFailureOperation = MTLStencilOperationReplace;
+ sDesc.depthFailureOperation = MTLStencilOperationReplace;
+ sDesc.depthStencilPassOperation = MTLStencilOperationReplace;
+
+ dsDesc.frontFaceStencil = sDesc;
+ dsDesc.backFaceStencil = sDesc;
+ } else {
+ dsDesc.frontFaceStencil = nil;
+ dsDesc.backFaceStencil = nil;
+ }
+
+ return [device_driver->get_device() newDepthStencilStateWithDescriptor:dsDesc];
+}
+
+id MDResourceFactory::new_clear_pipeline_state(ClearAttKey &p_key, NSError **p_error) {
+ PixelFormats &pixFmts = device_driver->get_pixel_formats();
+
+ id vtxFunc = new_clear_vert_func(p_key);
+ id fragFunc = new_clear_frag_func(p_key);
+ MTLRenderPipelineDescriptor *plDesc = [MTLRenderPipelineDescriptor new];
+ plDesc.label = @"ClearRenderAttachments";
+ plDesc.vertexFunction = vtxFunc;
+ plDesc.fragmentFunction = fragFunc;
+ plDesc.rasterSampleCount = p_key.sample_count;
+ plDesc.inputPrimitiveTopology = MTLPrimitiveTopologyClassTriangle;
+
+ for (uint32_t caIdx = 0; caIdx < ClearAttKey::COLOR_COUNT; caIdx++) {
+ MTLRenderPipelineColorAttachmentDescriptor *colorDesc = plDesc.colorAttachments[caIdx];
+ colorDesc.pixelFormat = (MTLPixelFormat)p_key.pixel_formats[caIdx];
+ colorDesc.writeMask = p_key.is_enabled(caIdx) ? MTLColorWriteMaskAll : MTLColorWriteMaskNone;
+ }
+
+ MTLPixelFormat mtlDepthFormat = p_key.depth_format();
+ if (pixFmts.isDepthFormat(mtlDepthFormat)) {
+ plDesc.depthAttachmentPixelFormat = mtlDepthFormat;
+ }
+
+ MTLPixelFormat mtlStencilFormat = p_key.stencil_format();
+ if (pixFmts.isStencilFormat(mtlStencilFormat)) {
+ plDesc.stencilAttachmentPixelFormat = mtlStencilFormat;
+ }
+
+ MTLVertexDescriptor *vtxDesc = plDesc.vertexDescriptor;
+
+ // Vertex attribute descriptors.
+ MTLVertexAttributeDescriptorArray *vaDescArray = vtxDesc.attributes;
+ MTLVertexAttributeDescriptor *vaDesc;
+ NSUInteger vtxBuffIdx = device_driver->get_metal_buffer_index_for_vertex_attribute_binding(VERT_CONTENT_BUFFER_INDEX);
+ NSUInteger vtxStride = 0;
+
+ // Vertex location.
+ vaDesc = vaDescArray[0];
+ vaDesc.format = MTLVertexFormatFloat4;
+ vaDesc.bufferIndex = vtxBuffIdx;
+ vaDesc.offset = vtxStride;
+ vtxStride += sizeof(simd::float4);
+
+ // Vertex attribute buffer.
+ MTLVertexBufferLayoutDescriptorArray *vbDescArray = vtxDesc.layouts;
+ MTLVertexBufferLayoutDescriptor *vbDesc = vbDescArray[vtxBuffIdx];
+ vbDesc.stepFunction = MTLVertexStepFunctionPerVertex;
+ vbDesc.stepRate = 1;
+ vbDesc.stride = vtxStride;
+
+ return [device_driver->get_device() newRenderPipelineStateWithDescriptor:plDesc error:p_error];
+}
+
+id MDResourceCache::get_clear_render_pipeline_state(ClearAttKey &p_key, NSError **p_error) {
+ HashMap::ConstIterator it = clear_states.find(p_key);
+ if (it != clear_states.end()) {
+ return it->value;
+ }
+
+ id state = resource_factory->new_clear_pipeline_state(p_key, p_error);
+ clear_states[p_key] = state;
+ return state;
+}
+
+id MDResourceCache::get_depth_stencil_state(bool p_use_depth, bool p_use_stencil) {
+ id __strong *val;
+ if (p_use_depth && p_use_stencil) {
+ val = &clear_depth_stencil_state.all;
+ } else if (p_use_depth) {
+ val = &clear_depth_stencil_state.depth_only;
+ } else if (p_use_stencil) {
+ val = &clear_depth_stencil_state.stencil_only;
+ } else {
+ val = &clear_depth_stencil_state.none;
+ }
+ DEV_ASSERT(val != nullptr);
+
+ if (*val == nil) {
+ *val = resource_factory->new_depth_stencil_state(p_use_depth, p_use_stencil);
+ }
+ return *val;
+}
diff --git a/drivers/metal/metal_utils.h b/drivers/metal/metal_utils.h
new file mode 100644
index 00000000000..eed1aad89bc
--- /dev/null
+++ b/drivers/metal/metal_utils.h
@@ -0,0 +1,81 @@
+/**************************************************************************/
+/* metal_utils.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef METAL_UTILS_H
+#define METAL_UTILS_H
+
+#pragma mark - Boolean flags
+
+namespace flags {
+
+/*! Sets the flags within the value parameter specified by the mask parameter. */
+template
+void set(Tv &p_value, Tm p_mask) {
+ using T = std::underlying_type_t;
+ p_value = static_cast(static_cast(p_value) | static_cast(p_mask));
+}
+
+/*! Clears the flags within the value parameter specified by the mask parameter. */
+template
+void clear(Tv &p_value, Tm p_mask) {
+ using T = std::underlying_type_t;
+ p_value = static_cast(static_cast(p_value) & ~static_cast(p_mask));
+}
+
+/*! Returns whether the specified value has any of the bits specified in mask set to 1. */
+template
+static constexpr bool any(Tv p_value, const Tm p_mask) { return ((p_value & p_mask) != 0); }
+
+/*! Returns whether the specified value has all of the bits specified in mask set to 1. */
+template
+static constexpr bool all(Tv p_value, const Tm p_mask) { return ((p_value & p_mask) == p_mask); }
+
+} //namespace flags
+
+#pragma mark - Alignment and Offsets
+
+static constexpr bool is_power_of_two(uint64_t p_value) {
+ return p_value && ((p_value & (p_value - 1)) == 0);
+}
+
+static constexpr uint64_t round_up_to_alignment(uint64_t p_value, uint64_t p_alignment) {
+ DEV_ASSERT(is_power_of_two(p_alignment));
+
+ if (p_alignment == 0) {
+ return p_value;
+ }
+
+ uint64_t mask = p_alignment - 1;
+ uint64_t aligned_value = (p_value + mask) & ~mask;
+
+ return aligned_value;
+}
+
+#endif // METAL_UTILS_H
diff --git a/drivers/metal/pixel_formats.h b/drivers/metal/pixel_formats.h
new file mode 100644
index 00000000000..167c3d56009
--- /dev/null
+++ b/drivers/metal/pixel_formats.h
@@ -0,0 +1,416 @@
+/**************************************************************************/
+/* pixel_formats.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+/**************************************************************************/
+/* */
+/* Portions of this code were derived from MoltenVK. */
+/* */
+/* Copyright (c) 2015-2023 The Brenwill Workshop Ltd. */
+/* (http://www.brenwill.com) */
+/* */
+/* 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 PIXEL_FORMATS_H
+#define PIXEL_FORMATS_H
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+
+#import "servers/rendering/rendering_device.h"
+
+#import
+
+static const uint32_t _mtlPixelFormatCount = 256;
+static const uint32_t _mtlPixelFormatCoreCount = MTLPixelFormatX32_Stencil8 + 2; // The actual last enum value is not available on iOS.
+static const uint32_t _mtlVertexFormatCount = MTLVertexFormatHalf + 1;
+
+#pragma mark -
+#pragma mark Metal format capabilities
+
+typedef enum : uint16_t {
+
+ kMTLFmtCapsNone = 0,
+ /*! The format can be used in a shader read operation. */
+ kMTLFmtCapsRead = (1 << 0),
+ /*! The format can be used in a shader filter operation during sampling. */
+ kMTLFmtCapsFilter = (1 << 1),
+ /*! The format can be used in a shader write operation. */
+ kMTLFmtCapsWrite = (1 << 2),
+ /*! The format can be used with atomic operations. */
+ kMTLFmtCapsAtomic = (1 << 3),
+ /*! The format can be used as a color attachment. */
+ kMTLFmtCapsColorAtt = (1 << 4),
+ /*! The format can be used as a depth-stencil attachment. */
+ kMTLFmtCapsDSAtt = (1 << 5),
+ /*! The format can be used with blend operations. */
+ kMTLFmtCapsBlend = (1 << 6),
+ /*! The format can be used as a destination for multisample antialias (MSAA) data. */
+ kMTLFmtCapsMSAA = (1 << 7),
+ /*! The format can be used as a resolve attachment. */
+ kMTLFmtCapsResolve = (1 << 8),
+ kMTLFmtCapsVertex = (1 << 9),
+
+ kMTLFmtCapsRF = (kMTLFmtCapsRead | kMTLFmtCapsFilter),
+ kMTLFmtCapsRC = (kMTLFmtCapsRead | kMTLFmtCapsColorAtt),
+ kMTLFmtCapsRCB = (kMTLFmtCapsRC | kMTLFmtCapsBlend),
+ kMTLFmtCapsRCM = (kMTLFmtCapsRC | kMTLFmtCapsMSAA),
+ kMTLFmtCapsRCMB = (kMTLFmtCapsRCM | kMTLFmtCapsBlend),
+ kMTLFmtCapsRWC = (kMTLFmtCapsRC | kMTLFmtCapsWrite),
+ kMTLFmtCapsRWCB = (kMTLFmtCapsRWC | kMTLFmtCapsBlend),
+ kMTLFmtCapsRWCM = (kMTLFmtCapsRWC | kMTLFmtCapsMSAA),
+ kMTLFmtCapsRWCMB = (kMTLFmtCapsRWCM | kMTLFmtCapsBlend),
+ kMTLFmtCapsRFCMRB = (kMTLFmtCapsRCMB | kMTLFmtCapsFilter | kMTLFmtCapsResolve),
+ kMTLFmtCapsRFWCMB = (kMTLFmtCapsRWCMB | kMTLFmtCapsFilter),
+ kMTLFmtCapsAll = (kMTLFmtCapsRFWCMB | kMTLFmtCapsResolve),
+
+ kMTLFmtCapsDRM = (kMTLFmtCapsDSAtt | kMTLFmtCapsRead | kMTLFmtCapsMSAA),
+ kMTLFmtCapsDRFM = (kMTLFmtCapsDRM | kMTLFmtCapsFilter),
+ kMTLFmtCapsDRMR = (kMTLFmtCapsDRM | kMTLFmtCapsResolve),
+ kMTLFmtCapsDRFMR = (kMTLFmtCapsDRFM | kMTLFmtCapsResolve),
+
+ kMTLFmtCapsChromaSubsampling = kMTLFmtCapsRF,
+ kMTLFmtCapsMultiPlanar = kMTLFmtCapsChromaSubsampling,
+} MTLFmtCaps;
+
+inline MTLFmtCaps operator|(MTLFmtCaps p_left, MTLFmtCaps p_right) {
+ return static_cast(static_cast(p_left) | p_right);
+}
+
+inline MTLFmtCaps &operator|=(MTLFmtCaps &p_left, MTLFmtCaps p_right) {
+ return (p_left = p_left | p_right);
+}
+
+#pragma mark -
+#pragma mark Metal view classes
+
+enum class MTLViewClass : uint8_t {
+ None,
+ Color8,
+ Color16,
+ Color32,
+ Color64,
+ Color128,
+ PVRTC_RGB_2BPP,
+ PVRTC_RGB_4BPP,
+ PVRTC_RGBA_2BPP,
+ PVRTC_RGBA_4BPP,
+ EAC_R11,
+ EAC_RG11,
+ EAC_RGBA8,
+ ETC2_RGB8,
+ ETC2_RGB8A1,
+ ASTC_4x4,
+ ASTC_5x4,
+ ASTC_5x5,
+ ASTC_6x5,
+ ASTC_6x6,
+ ASTC_8x5,
+ ASTC_8x6,
+ ASTC_8x8,
+ ASTC_10x5,
+ ASTC_10x6,
+ ASTC_10x8,
+ ASTC_10x10,
+ ASTC_12x10,
+ ASTC_12x12,
+ BC1_RGBA,
+ BC2_RGBA,
+ BC3_RGBA,
+ BC4_R,
+ BC5_RG,
+ BC6H_RGB,
+ BC7_RGBA,
+ Depth24_Stencil8,
+ Depth32_Stencil8,
+ BGRA10_XR,
+ BGR10_XR
+};
+
+#pragma mark -
+#pragma mark Format descriptors
+
+/** Enumerates the data type of a format. */
+enum class MTLFormatType {
+ None, /**< Format type is unknown. */
+ ColorHalf, /**< A 16-bit floating point color. */
+ ColorFloat, /**< A 32-bit floating point color. */
+ ColorInt8, /**< A signed 8-bit integer color. */
+ ColorUInt8, /**< An unsigned 8-bit integer color. */
+ ColorInt16, /**< A signed 16-bit integer color. */
+ ColorUInt16, /**< An unsigned 16-bit integer color. */
+ ColorInt32, /**< A signed 32-bit integer color. */
+ ColorUInt32, /**< An unsigned 32-bit integer color. */
+ DepthStencil, /**< A depth and stencil value. */
+ Compressed, /**< A block-compressed color. */
+};
+
+typedef struct Extent2D {
+ uint32_t width;
+ uint32_t height;
+} Extent2D;
+
+/** Describes the properties of a DataFormat, including the corresponding Metal pixel and vertex format. */
+typedef struct DataFormatDesc {
+ RD::DataFormat dataFormat;
+ MTLPixelFormat mtlPixelFormat;
+ MTLPixelFormat mtlPixelFormatSubstitute;
+ MTLVertexFormat mtlVertexFormat;
+ MTLVertexFormat mtlVertexFormatSubstitute;
+ uint8_t chromaSubsamplingPlaneCount;
+ uint8_t chromaSubsamplingComponentBits;
+ Extent2D blockTexelSize;
+ uint32_t bytesPerBlock;
+ MTLFormatType formatType;
+ const char *name;
+ bool hasReportedSubstitution;
+
+ inline double bytesPerTexel() const { return (double)bytesPerBlock / (double)(blockTexelSize.width * blockTexelSize.height); }
+
+ inline bool isSupported() const { return (mtlPixelFormat != MTLPixelFormatInvalid || chromaSubsamplingPlaneCount > 1); }
+ inline bool isSupportedOrSubstitutable() const { return isSupported() || (mtlPixelFormatSubstitute != MTLPixelFormatInvalid); }
+
+ inline bool vertexIsSupported() const { return (mtlVertexFormat != MTLVertexFormatInvalid); }
+ inline bool vertexIsSupportedOrSubstitutable() const { return vertexIsSupported() || (mtlVertexFormatSubstitute != MTLVertexFormatInvalid); }
+} DataFormatDesc;
+
+/** Describes the properties of a MTLPixelFormat or MTLVertexFormat. */
+typedef struct MTLFormatDesc {
+ union {
+ MTLPixelFormat mtlPixelFormat;
+ MTLVertexFormat mtlVertexFormat;
+ };
+ RD::DataFormat dataFormat;
+ MTLFmtCaps mtlFmtCaps;
+ MTLViewClass mtlViewClass;
+ MTLPixelFormat mtlPixelFormatLinear;
+ const char *name = nullptr;
+
+ inline bool isSupported() const { return (mtlPixelFormat != MTLPixelFormatInvalid) && (mtlFmtCaps != kMTLFmtCapsNone); }
+} MTLFormatDesc;
+
+class API_AVAILABLE(macos(11.0), ios(14.0)) PixelFormats {
+ using DataFormat = RD::DataFormat;
+
+public:
+ /** Returns whether the DataFormat is supported by the GPU bound to this instance. */
+ bool isSupported(DataFormat p_format);
+
+ /** Returns whether the DataFormat is supported by this implementation, or can be substituted by one that is. */
+ bool isSupportedOrSubstitutable(DataFormat p_format);
+
+ /** Returns whether the specified Metal MTLPixelFormat can be used as a depth format. */
+ _FORCE_INLINE_ bool isDepthFormat(MTLPixelFormat p_format) {
+ switch (p_format) {
+ case MTLPixelFormatDepth32Float:
+ case MTLPixelFormatDepth16Unorm:
+ case MTLPixelFormatDepth32Float_Stencil8:
+#if TARGET_OS_OSX
+ case MTLPixelFormatDepth24Unorm_Stencil8:
+#endif
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /** Returns whether the specified Metal MTLPixelFormat can be used as a stencil format. */
+ _FORCE_INLINE_ bool isStencilFormat(MTLPixelFormat p_format) {
+ switch (p_format) {
+ case MTLPixelFormatStencil8:
+#if TARGET_OS_OSX
+ case MTLPixelFormatDepth24Unorm_Stencil8:
+ case MTLPixelFormatX24_Stencil8:
+#endif
+ case MTLPixelFormatDepth32Float_Stencil8:
+ case MTLPixelFormatX32_Stencil8:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /** Returns whether the specified Metal MTLPixelFormat is a PVRTC format. */
+ bool isPVRTCFormat(MTLPixelFormat p_format);
+
+ /** Returns the format type corresponding to the specified Godot pixel format, */
+ MTLFormatType getFormatType(DataFormat p_format);
+
+ /** Returns the format type corresponding to the specified Metal MTLPixelFormat, */
+ MTLFormatType getFormatType(MTLPixelFormat p_formt);
+
+ /**
+ * Returns the Metal MTLPixelFormat corresponding to the specified Godot pixel
+ * or returns MTLPixelFormatInvalid if no corresponding MTLPixelFormat exists.
+ */
+ MTLPixelFormat getMTLPixelFormat(DataFormat p_format);
+
+ /**
+ * Returns the DataFormat corresponding to the specified Metal MTLPixelFormat,
+ * or returns DATA_FORMAT_MAX if no corresponding DataFormat exists.
+ */
+ DataFormat getDataFormat(MTLPixelFormat p_format);
+
+ /**
+ * Returns the size, in bytes, of a texel block of the specified Godot pixel.
+ * For uncompressed formats, the returned value corresponds to the size in bytes of a single texel.
+ */
+ uint32_t getBytesPerBlock(DataFormat p_format);
+
+ /**
+ * Returns the size, in bytes, of a texel block of the specified Metal format.
+ * For uncompressed formats, the returned value corresponds to the size in bytes of a single texel.
+ */
+ uint32_t getBytesPerBlock(MTLPixelFormat p_format);
+
+ /** Returns the number of planes of the specified chroma-subsampling (YCbCr) DataFormat */
+ uint8_t getChromaSubsamplingPlaneCount(DataFormat p_format);
+
+ /** Returns the number of bits per channel of the specified chroma-subsampling (YCbCr) DataFormat */
+ uint8_t getChromaSubsamplingComponentBits(DataFormat p_format);
+
+ /**
+ * Returns the size, in bytes, of a texel of the specified Godot format.
+ * The returned value may be fractional for certain compressed formats.
+ */
+ float getBytesPerTexel(DataFormat p_format);
+
+ /**
+ * Returns the size, in bytes, of a texel of the specified Metal format.
+ * The returned value may be fractional for certain compressed formats.
+ */
+ float getBytesPerTexel(MTLPixelFormat p_format);
+
+ /**
+ * Returns the size, in bytes, of a row of texels of the specified Godot pixel format.
+ *
+ * For compressed formats, this takes into consideration the compression block size,
+ * and p_texels_per_row should specify the width in texels, not blocks. The result is rounded
+ * up if p_texels_per_row is not an integer multiple of the compression block width.
+ */
+ size_t getBytesPerRow(DataFormat p_format, uint32_t p_texels_per_row);
+
+ /**
+ * Returns the size, in bytes, of a row of texels of the specified Metal format.
+ *
+ * For compressed formats, this takes into consideration the compression block size,
+ * and texelsPerRow should specify the width in texels, not blocks. The result is rounded
+ * up if texelsPerRow is not an integer multiple of the compression block width.
+ */
+ size_t getBytesPerRow(MTLPixelFormat p_format, uint32_t p_texels_per_row);
+
+ /**
+ * Returns the size, in bytes, of a texture layer of the specified Godot pixel format.
+ *
+ * For compressed formats, this takes into consideration the compression block size,
+ * and p_texel_rows_per_layer should specify the height in texels, not blocks. The result is
+ * rounded up if p_texel_rows_per_layer is not an integer multiple of the compression block height.
+ */
+ size_t getBytesPerLayer(DataFormat p_format, size_t p_bytes_per_row, uint32_t p_texel_rows_per_layer);
+
+ /**
+ * Returns the size, in bytes, of a texture layer of the specified Metal format.
+ * For compressed formats, this takes into consideration the compression block size,
+ * and p_texel_rows_per_layer should specify the height in texels, not blocks. The result is
+ * rounded up if p_texel_rows_per_layer is not an integer multiple of the compression block height.
+ */
+ size_t getBytesPerLayer(MTLPixelFormat p_format, size_t p_bytes_per_row, uint32_t p_texel_rows_per_layer);
+
+ /** Returns the Metal format capabilities supported by the specified Godot format, without substitution. */
+ MTLFmtCaps getCapabilities(DataFormat p_format, bool p_extended = false);
+
+ /** Returns the Metal format capabilities supported by the specified Metal format. */
+ MTLFmtCaps getCapabilities(MTLPixelFormat p_format, bool p_extended = false);
+
+ /**
+ * Returns the Metal MTLVertexFormat corresponding to the specified
+ * DataFormat as used as a vertex attribute format.
+ */
+ MTLVertexFormat getMTLVertexFormat(DataFormat p_format);
+
+#pragma mark Construction
+
+ explicit PixelFormats(id p_device);
+
+protected:
+ id device;
+
+ DataFormatDesc &getDataFormatDesc(DataFormat p_format);
+ DataFormatDesc &getDataFormatDesc(MTLPixelFormat p_format);
+ MTLFormatDesc &getMTLPixelFormatDesc(MTLPixelFormat p_format);
+ MTLFormatDesc &getMTLVertexFormatDesc(MTLVertexFormat p_format);
+ void initDataFormatCapabilities();
+ void initMTLPixelFormatCapabilities();
+ void initMTLVertexFormatCapabilities();
+ void buildMTLFormatMaps();
+ void buildDFFormatMaps();
+ void modifyMTLFormatCapabilities();
+ void modifyMTLFormatCapabilities(id p_device);
+ void addMTLPixelFormatCapabilities(id p_device,
+ MTLFeatureSet p_feature_set,
+ MTLPixelFormat p_format,
+ MTLFmtCaps p_caps);
+ void addMTLPixelFormatCapabilities(id p_device,
+ MTLGPUFamily p_family,
+ MTLPixelFormat p_format,
+ MTLFmtCaps p_caps);
+ void disableMTLPixelFormatCapabilities(MTLPixelFormat p_format,
+ MTLFmtCaps p_caps);
+ void disableAllMTLPixelFormatCapabilities(MTLPixelFormat p_format);
+ void addMTLVertexFormatCapabilities(id p_device,
+ MTLFeatureSet p_feature_set,
+ MTLVertexFormat p_format,
+ MTLFmtCaps p_caps);
+
+ DataFormatDesc _dataFormatDescriptions[RD::DATA_FORMAT_MAX];
+ MTLFormatDesc _mtlPixelFormatDescriptions[_mtlPixelFormatCount];
+ MTLFormatDesc _mtlVertexFormatDescriptions[_mtlVertexFormatCount];
+
+ // Most Metal formats have small values and are mapped by simple lookup array.
+ // Outliers are mapped by a map.
+ uint16_t _mtlFormatDescIndicesByMTLPixelFormatsCore[_mtlPixelFormatCoreCount];
+ HashMap _mtlFormatDescIndicesByMTLPixelFormatsExt;
+
+ uint16_t _mtlFormatDescIndicesByMTLVertexFormats[_mtlVertexFormatCount];
+};
+
+#pragma clang diagnostic pop
+
+#endif // PIXEL_FORMATS_H
diff --git a/drivers/metal/pixel_formats.mm b/drivers/metal/pixel_formats.mm
new file mode 100644
index 00000000000..ac737b3f0a0
--- /dev/null
+++ b/drivers/metal/pixel_formats.mm
@@ -0,0 +1,1298 @@
+/**************************************************************************/
+/* pixel_formats.mm */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+/**************************************************************************/
+/* */
+/* Portions of this code were derived from MoltenVK. */
+/* */
+/* Copyright (c) 2015-2023 The Brenwill Workshop Ltd. */
+/* (http://www.brenwill.com) */
+/* */
+/* 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. */
+/**************************************************************************/
+
+#import "pixel_formats.h"
+
+#import "metal_utils.h"
+
+#if TARGET_OS_IPHONE || TARGET_OS_TV
+#if !(__IPHONE_OS_VERSION_MAX_ALLOWED >= 160400) // iOS/tvOS 16.4
+#define MTLPixelFormatBC1_RGBA MTLPixelFormatInvalid
+#define MTLPixelFormatBC1_RGBA_sRGB MTLPixelFormatInvalid
+#define MTLPixelFormatBC2_RGBA MTLPixelFormatInvalid
+#define MTLPixelFormatBC2_RGBA_sRGB MTLPixelFormatInvalid
+#define MTLPixelFormatBC3_RGBA MTLPixelFormatInvalid
+#define MTLPixelFormatBC3_RGBA_sRGB MTLPixelFormatInvalid
+#define MTLPixelFormatBC4_RUnorm MTLPixelFormatInvalid
+#define MTLPixelFormatBC4_RSnorm MTLPixelFormatInvalid
+#define MTLPixelFormatBC5_RGUnorm MTLPixelFormatInvalid
+#define MTLPixelFormatBC5_RGSnorm MTLPixelFormatInvalid
+#define MTLPixelFormatBC6H_RGBUfloat MTLPixelFormatInvalid
+#define MTLPixelFormatBC6H_RGBFloat MTLPixelFormatInvalid
+#define MTLPixelFormatBC7_RGBAUnorm MTLPixelFormatInvalid
+#define MTLPixelFormatBC7_RGBAUnorm_sRGB MTLPixelFormatInvalid
+#endif
+
+#define MTLPixelFormatDepth16Unorm_Stencil8 MTLPixelFormatDepth32Float_Stencil8
+#define MTLPixelFormatDepth24Unorm_Stencil8 MTLPixelFormatInvalid
+#define MTLPixelFormatX24_Stencil8 MTLPixelFormatInvalid
+#endif
+
+#if TARGET_OS_TV
+#define MTLPixelFormatASTC_4x4_HDR MTLPixelFormatInvalid
+#define MTLPixelFormatASTC_5x4_HDR MTLPixelFormatInvalid
+#define MTLPixelFormatASTC_5x5_HDR MTLPixelFormatInvalid
+#define MTLPixelFormatASTC_6x5_HDR MTLPixelFormatInvalid
+#define MTLPixelFormatASTC_6x6_HDR MTLPixelFormatInvalid
+#define MTLPixelFormatASTC_8x5_HDR MTLPixelFormatInvalid
+#define MTLPixelFormatASTC_8x6_HDR MTLPixelFormatInvalid
+#define MTLPixelFormatASTC_8x8_HDR MTLPixelFormatInvalid
+#define MTLPixelFormatASTC_10x5_HDR MTLPixelFormatInvalid
+#define MTLPixelFormatASTC_10x6_HDR MTLPixelFormatInvalid
+#define MTLPixelFormatASTC_10x8_HDR MTLPixelFormatInvalid
+#define MTLPixelFormatASTC_10x10_HDR MTLPixelFormatInvalid
+#define MTLPixelFormatASTC_12x10_HDR MTLPixelFormatInvalid
+#define MTLPixelFormatASTC_12x12_HDR MTLPixelFormatInvalid
+#endif
+
+#if !((__MAC_OS_X_VERSION_MAX_ALLOWED >= 140000) || (__IPHONE_OS_VERSION_MAX_ALLOWED >= 170000)) // Xcode 15
+#define MTLVertexFormatFloatRG11B10 MTLVertexFormatInvalid
+#define MTLVertexFormatFloatRGB9E5 MTLVertexFormatInvalid
+#endif
+
+/** Selects and returns one of the values, based on the platform OS. */
+_FORCE_INLINE_ constexpr MTLFmtCaps select_platform_caps(MTLFmtCaps p_macOS_val, MTLFmtCaps p_iOS_val) {
+#if (TARGET_OS_IOS || TARGET_OS_TV) && !TARGET_OS_MACCATALYST
+ return p_iOS_val;
+#elif TARGET_OS_OSX
+ return p_macOS_val;
+#else
+#error "unsupported platform"
+#endif
+}
+
+template
+void clear(T *p_val, size_t p_count = 1) {
+ memset(p_val, 0, sizeof(T) * p_count);
+}
+
+#pragma mark -
+#pragma mark PixelFormats
+
+bool PixelFormats::isSupported(DataFormat p_format) {
+ return getDataFormatDesc(p_format).isSupported();
+}
+
+bool PixelFormats::isSupportedOrSubstitutable(DataFormat p_format) {
+ return getDataFormatDesc(p_format).isSupportedOrSubstitutable();
+}
+
+bool PixelFormats::isPVRTCFormat(MTLPixelFormat p_format) {
+ switch (p_format) {
+ case MTLPixelFormatPVRTC_RGBA_2BPP:
+ case MTLPixelFormatPVRTC_RGBA_2BPP_sRGB:
+ case MTLPixelFormatPVRTC_RGBA_4BPP:
+ case MTLPixelFormatPVRTC_RGBA_4BPP_sRGB:
+ case MTLPixelFormatPVRTC_RGB_2BPP:
+ case MTLPixelFormatPVRTC_RGB_2BPP_sRGB:
+ case MTLPixelFormatPVRTC_RGB_4BPP:
+ case MTLPixelFormatPVRTC_RGB_4BPP_sRGB:
+ return true;
+ default:
+ return false;
+ }
+}
+
+MTLFormatType PixelFormats::getFormatType(DataFormat p_format) {
+ return getDataFormatDesc(p_format).formatType;
+}
+
+MTLFormatType PixelFormats::getFormatType(MTLPixelFormat p_formt) {
+ return getDataFormatDesc(p_formt).formatType;
+}
+
+MTLPixelFormat PixelFormats::getMTLPixelFormat(DataFormat p_format) {
+ DataFormatDesc &dfDesc = getDataFormatDesc(p_format);
+ MTLPixelFormat mtlPixFmt = dfDesc.mtlPixelFormat;
+
+ // If the MTLPixelFormat is not supported but DataFormat is valid,
+ // attempt to substitute a different format.
+ if (mtlPixFmt == MTLPixelFormatInvalid && p_format != RD::DATA_FORMAT_MAX && dfDesc.chromaSubsamplingPlaneCount <= 1) {
+ mtlPixFmt = dfDesc.mtlPixelFormatSubstitute;
+ }
+
+ return mtlPixFmt;
+}
+
+RD::DataFormat PixelFormats::getDataFormat(MTLPixelFormat p_format) {
+ return getMTLPixelFormatDesc(p_format).dataFormat;
+}
+
+uint32_t PixelFormats::getBytesPerBlock(DataFormat p_format) {
+ return getDataFormatDesc(p_format).bytesPerBlock;
+}
+
+uint32_t PixelFormats::getBytesPerBlock(MTLPixelFormat p_format) {
+ return getDataFormatDesc(p_format).bytesPerBlock;
+}
+
+uint8_t PixelFormats::getChromaSubsamplingPlaneCount(DataFormat p_format) {
+ return getDataFormatDesc(p_format).chromaSubsamplingPlaneCount;
+}
+
+uint8_t PixelFormats::getChromaSubsamplingComponentBits(DataFormat p_format) {
+ return getDataFormatDesc(p_format).chromaSubsamplingComponentBits;
+}
+
+float PixelFormats::getBytesPerTexel(DataFormat p_format) {
+ return getDataFormatDesc(p_format).bytesPerTexel();
+}
+
+float PixelFormats::getBytesPerTexel(MTLPixelFormat p_format) {
+ return getDataFormatDesc(p_format).bytesPerTexel();
+}
+
+size_t PixelFormats::getBytesPerRow(DataFormat p_format, uint32_t p_texels_per_row) {
+ DataFormatDesc &dfDesc = getDataFormatDesc(p_format);
+ return Math::division_round_up(p_texels_per_row, dfDesc.blockTexelSize.width) * dfDesc.bytesPerBlock;
+}
+
+size_t PixelFormats::getBytesPerRow(MTLPixelFormat p_format, uint32_t p_texels_per_row) {
+ DataFormatDesc &dfDesc = getDataFormatDesc(p_format);
+ return Math::division_round_up(p_texels_per_row, dfDesc.blockTexelSize.width) * dfDesc.bytesPerBlock;
+}
+
+size_t PixelFormats::getBytesPerLayer(DataFormat p_format, size_t p_bytes_per_row, uint32_t p_texel_rows_per_layer) {
+ return Math::division_round_up(p_texel_rows_per_layer, getDataFormatDesc(p_format).blockTexelSize.height) * p_bytes_per_row;
+}
+
+size_t PixelFormats::getBytesPerLayer(MTLPixelFormat p_format, size_t p_bytes_per_row, uint32_t p_texel_rows_per_layer) {
+ return Math::division_round_up(p_texel_rows_per_layer, getDataFormatDesc(p_format).blockTexelSize.height) * p_bytes_per_row;
+}
+
+MTLFmtCaps PixelFormats::getCapabilities(DataFormat p_format, bool p_extended) {
+ return getCapabilities(getDataFormatDesc(p_format).mtlPixelFormat, p_extended);
+}
+
+MTLFmtCaps PixelFormats::getCapabilities(MTLPixelFormat p_format, bool p_extended) {
+ MTLFormatDesc &mtlDesc = getMTLPixelFormatDesc(p_format);
+ MTLFmtCaps caps = mtlDesc.mtlFmtCaps;
+ if (!p_extended || mtlDesc.mtlViewClass == MTLViewClass::None) {
+ return caps;
+ }
+ // Now get caps of all formats in the view class.
+ for (MTLFormatDesc &otherDesc : _mtlPixelFormatDescriptions) {
+ if (otherDesc.mtlViewClass == mtlDesc.mtlViewClass) {
+ caps |= otherDesc.mtlFmtCaps;
+ }
+ }
+ return caps;
+}
+
+MTLVertexFormat PixelFormats::getMTLVertexFormat(DataFormat p_format) {
+ DataFormatDesc &dfDesc = getDataFormatDesc(p_format);
+ MTLVertexFormat format = dfDesc.mtlVertexFormat;
+
+ if (format == MTLVertexFormatInvalid) {
+ String errMsg;
+ errMsg += "DataFormat ";
+ errMsg += dfDesc.name;
+ errMsg += " is not supported for vertex buffers on this device.";
+
+ if (dfDesc.vertexIsSupportedOrSubstitutable()) {
+ format = dfDesc.mtlVertexFormatSubstitute;
+
+ DataFormatDesc &dfDescSubs = getDataFormatDesc(getMTLVertexFormatDesc(format).dataFormat);
+ errMsg += " Using DataFormat ";
+ errMsg += dfDescSubs.name;
+ errMsg += " instead.";
+ }
+ WARN_PRINT(errMsg);
+ }
+
+ return format;
+}
+
+DataFormatDesc &PixelFormats::getDataFormatDesc(DataFormat p_format) {
+ CRASH_BAD_INDEX_MSG(p_format, RD::DATA_FORMAT_MAX, "Attempting to describe an invalid DataFormat");
+ return _dataFormatDescriptions[p_format];
+}
+
+DataFormatDesc &PixelFormats::getDataFormatDesc(MTLPixelFormat p_format) {
+ return getDataFormatDesc(getMTLPixelFormatDesc(p_format).dataFormat);
+}
+
+// Return a reference to the Metal format descriptor corresponding to the MTLPixelFormat.
+MTLFormatDesc &PixelFormats::getMTLPixelFormatDesc(MTLPixelFormat p_format) {
+ uint16_t fmtIdx = ((p_format < _mtlPixelFormatCoreCount)
+ ? _mtlFormatDescIndicesByMTLPixelFormatsCore[p_format]
+ : _mtlFormatDescIndicesByMTLPixelFormatsExt[p_format]);
+ return _mtlPixelFormatDescriptions[fmtIdx];
+}
+
+// Return a reference to the Metal format descriptor corresponding to the MTLVertexFormat.
+MTLFormatDesc &PixelFormats::getMTLVertexFormatDesc(MTLVertexFormat p_format) {
+ uint16_t fmtIdx = (p_format < _mtlVertexFormatCount) ? _mtlFormatDescIndicesByMTLVertexFormats[p_format] : 0;
+ return _mtlVertexFormatDescriptions[fmtIdx];
+}
+
+PixelFormats::PixelFormats(id p_device) :
+ device(p_device) {
+ initMTLPixelFormatCapabilities();
+ initMTLVertexFormatCapabilities();
+ buildMTLFormatMaps();
+ modifyMTLFormatCapabilities();
+
+ initDataFormatCapabilities();
+ buildDFFormatMaps();
+}
+
+#define addDfFormatDescFull(DATA_FMT, MTL_FMT, MTL_FMT_ALT, MTL_VTX_FMT, MTL_VTX_FMT_ALT, CSPC, CSCB, BLK_W, BLK_H, BLK_BYTE_CNT, MVK_FMT_TYPE) \
+ CRASH_BAD_INDEX_MSG(RD::DATA_FORMAT_##DATA_FMT, RD::DATA_FORMAT_MAX, "Attempting to describe too many DataFormats"); \
+ _dataFormatDescriptions[RD::DATA_FORMAT_##DATA_FMT] = { RD::DATA_FORMAT_##DATA_FMT, MTLPixelFormat##MTL_FMT, MTLPixelFormat##MTL_FMT_ALT, MTLVertexFormat##MTL_VTX_FMT, MTLVertexFormat##MTL_VTX_FMT_ALT, \
+ CSPC, CSCB, { BLK_W, BLK_H }, BLK_BYTE_CNT, MTLFormatType::MVK_FMT_TYPE, "DATA_FORMAT_" #DATA_FMT, false }
+
+#define addDataFormatDesc(DATA_FMT, MTL_FMT, MTL_FMT_ALT, MTL_VTX_FMT, MTL_VTX_FMT_ALT, BLK_W, BLK_H, BLK_BYTE_CNT, MVK_FMT_TYPE) \
+ addDfFormatDescFull(DATA_FMT, MTL_FMT, MTL_FMT_ALT, MTL_VTX_FMT, MTL_VTX_FMT_ALT, 0, 0, BLK_W, BLK_H, BLK_BYTE_CNT, MVK_FMT_TYPE)
+
+#define addDfFormatDescChromaSubsampling(DATA_FMT, MTL_FMT, CSPC, CSCB, BLK_W, BLK_H, BLK_BYTE_CNT) \
+ addDfFormatDescFull(DATA_FMT, MTL_FMT, Invalid, Invalid, Invalid, CSPC, CSCB, BLK_W, BLK_H, BLK_BYTE_CNT, ColorFloat)
+
+void PixelFormats::initDataFormatCapabilities() {
+ clear(_dataFormatDescriptions, RD::DATA_FORMAT_MAX);
+
+ addDataFormatDesc(R4G4_UNORM_PACK8, Invalid, Invalid, Invalid, Invalid, 1, 1, 1, ColorFloat);
+ addDataFormatDesc(R4G4B4A4_UNORM_PACK16, ABGR4Unorm, Invalid, Invalid, Invalid, 1, 1, 2, ColorFloat);
+ addDataFormatDesc(B4G4R4A4_UNORM_PACK16, Invalid, Invalid, Invalid, Invalid, 1, 1, 2, ColorFloat);
+
+ addDataFormatDesc(R5G6B5_UNORM_PACK16, B5G6R5Unorm, Invalid, Invalid, Invalid, 1, 1, 2, ColorFloat);
+ addDataFormatDesc(B5G6R5_UNORM_PACK16, Invalid, Invalid, Invalid, Invalid, 1, 1, 2, ColorFloat);
+ addDataFormatDesc(R5G5B5A1_UNORM_PACK16, A1BGR5Unorm, Invalid, Invalid, Invalid, 1, 1, 2, ColorFloat);
+ addDataFormatDesc(B5G5R5A1_UNORM_PACK16, Invalid, Invalid, Invalid, Invalid, 1, 1, 2, ColorFloat);
+ addDataFormatDesc(A1R5G5B5_UNORM_PACK16, BGR5A1Unorm, Invalid, Invalid, Invalid, 1, 1, 2, ColorFloat);
+
+ addDataFormatDesc(R8_UNORM, R8Unorm, Invalid, UCharNormalized, UChar2Normalized, 1, 1, 1, ColorFloat);
+ addDataFormatDesc(R8_SNORM, R8Snorm, Invalid, CharNormalized, Char2Normalized, 1, 1, 1, ColorFloat);
+ addDataFormatDesc(R8_USCALED, Invalid, Invalid, UChar, UChar2, 1, 1, 1, ColorFloat);
+ addDataFormatDesc(R8_SSCALED, Invalid, Invalid, Char, Char2, 1, 1, 1, ColorFloat);
+ addDataFormatDesc(R8_UINT, R8Uint, Invalid, UChar, UChar2, 1, 1, 1, ColorUInt8);
+ addDataFormatDesc(R8_SINT, R8Sint, Invalid, Char, Char2, 1, 1, 1, ColorInt8);
+ addDataFormatDesc(R8_SRGB, R8Unorm_sRGB, Invalid, UCharNormalized, UChar2Normalized, 1, 1, 1, ColorFloat);
+
+ addDataFormatDesc(R8G8_UNORM, RG8Unorm, Invalid, UChar2Normalized, Invalid, 1, 1, 2, ColorFloat);
+ addDataFormatDesc(R8G8_SNORM, RG8Snorm, Invalid, Char2Normalized, Invalid, 1, 1, 2, ColorFloat);
+ addDataFormatDesc(R8G8_USCALED, Invalid, Invalid, UChar2, Invalid, 1, 1, 2, ColorFloat);
+ addDataFormatDesc(R8G8_SSCALED, Invalid, Invalid, Char2, Invalid, 1, 1, 2, ColorFloat);
+ addDataFormatDesc(R8G8_UINT, RG8Uint, Invalid, UChar2, Invalid, 1, 1, 2, ColorUInt8);
+ addDataFormatDesc(R8G8_SINT, RG8Sint, Invalid, Char2, Invalid, 1, 1, 2, ColorInt8);
+ addDataFormatDesc(R8G8_SRGB, RG8Unorm_sRGB, Invalid, UChar2Normalized, Invalid, 1, 1, 2, ColorFloat);
+
+ addDataFormatDesc(R8G8B8_UNORM, Invalid, Invalid, UChar3Normalized, Invalid, 1, 1, 3, ColorFloat);
+ addDataFormatDesc(R8G8B8_SNORM, Invalid, Invalid, Char3Normalized, Invalid, 1, 1, 3, ColorFloat);
+ addDataFormatDesc(R8G8B8_USCALED, Invalid, Invalid, UChar3, Invalid, 1, 1, 3, ColorFloat);
+ addDataFormatDesc(R8G8B8_SSCALED, Invalid, Invalid, Char3, Invalid, 1, 1, 3, ColorFloat);
+ addDataFormatDesc(R8G8B8_UINT, Invalid, Invalid, UChar3, Invalid, 1, 1, 3, ColorUInt8);
+ addDataFormatDesc(R8G8B8_SINT, Invalid, Invalid, Char3, Invalid, 1, 1, 3, ColorInt8);
+ addDataFormatDesc(R8G8B8_SRGB, Invalid, Invalid, UChar3Normalized, Invalid, 1, 1, 3, ColorFloat);
+
+ addDataFormatDesc(B8G8R8_UNORM, Invalid, Invalid, Invalid, Invalid, 1, 1, 3, ColorFloat);
+ addDataFormatDesc(B8G8R8_SNORM, Invalid, Invalid, Invalid, Invalid, 1, 1, 3, ColorFloat);
+ addDataFormatDesc(B8G8R8_USCALED, Invalid, Invalid, Invalid, Invalid, 1, 1, 3, ColorFloat);
+ addDataFormatDesc(B8G8R8_SSCALED, Invalid, Invalid, Invalid, Invalid, 1, 1, 3, ColorFloat);
+ addDataFormatDesc(B8G8R8_UINT, Invalid, Invalid, Invalid, Invalid, 1, 1, 3, ColorUInt8);
+ addDataFormatDesc(B8G8R8_SINT, Invalid, Invalid, Invalid, Invalid, 1, 1, 3, ColorInt8);
+ addDataFormatDesc(B8G8R8_SRGB, Invalid, Invalid, Invalid, Invalid, 1, 1, 3, ColorFloat);
+
+ addDataFormatDesc(R8G8B8A8_UNORM, RGBA8Unorm, Invalid, UChar4Normalized, Invalid, 1, 1, 4, ColorFloat);
+ addDataFormatDesc(R8G8B8A8_SNORM, RGBA8Snorm, Invalid, Char4Normalized, Invalid, 1, 1, 4, ColorFloat);
+ addDataFormatDesc(R8G8B8A8_USCALED, Invalid, Invalid, UChar4, Invalid, 1, 1, 4, ColorFloat);
+ addDataFormatDesc(R8G8B8A8_SSCALED, Invalid, Invalid, Char4, Invalid, 1, 1, 4, ColorFloat);
+ addDataFormatDesc(R8G8B8A8_UINT, RGBA8Uint, Invalid, UChar4, Invalid, 1, 1, 4, ColorUInt8);
+ addDataFormatDesc(R8G8B8A8_SINT, RGBA8Sint, Invalid, Char4, Invalid, 1, 1, 4, ColorInt8);
+ addDataFormatDesc(R8G8B8A8_SRGB, RGBA8Unorm_sRGB, Invalid, UChar4Normalized, Invalid, 1, 1, 4, ColorFloat);
+
+ addDataFormatDesc(B8G8R8A8_UNORM, BGRA8Unorm, Invalid, UChar4Normalized_BGRA, Invalid, 1, 1, 4, ColorFloat);
+ addDataFormatDesc(B8G8R8A8_SNORM, Invalid, Invalid, Invalid, Invalid, 1, 1, 4, ColorFloat);
+ addDataFormatDesc(B8G8R8A8_USCALED, Invalid, Invalid, Invalid, Invalid, 1, 1, 4, ColorFloat);
+ addDataFormatDesc(B8G8R8A8_SSCALED, Invalid, Invalid, Invalid, Invalid, 1, 1, 4, ColorFloat);
+ addDataFormatDesc(B8G8R8A8_UINT, Invalid, Invalid, Invalid, Invalid, 1, 1, 4, ColorUInt8);
+ addDataFormatDesc(B8G8R8A8_SINT, Invalid, Invalid, Invalid, Invalid, 1, 1, 4, ColorInt8);
+ addDataFormatDesc(B8G8R8A8_SRGB, BGRA8Unorm_sRGB, Invalid, Invalid, Invalid, 1, 1, 4, ColorFloat);
+
+ addDataFormatDesc(A8B8G8R8_UNORM_PACK32, RGBA8Unorm, Invalid, UChar4Normalized, Invalid, 1, 1, 4, ColorFloat);
+ addDataFormatDesc(A8B8G8R8_SNORM_PACK32, RGBA8Snorm, Invalid, Char4Normalized, Invalid, 1, 1, 4, ColorFloat);
+ addDataFormatDesc(A8B8G8R8_USCALED_PACK32, Invalid, Invalid, UChar4, Invalid, 1, 1, 4, ColorFloat);
+ addDataFormatDesc(A8B8G8R8_SSCALED_PACK32, Invalid, Invalid, Char4, Invalid, 1, 1, 4, ColorFloat);
+ addDataFormatDesc(A8B8G8R8_UINT_PACK32, RGBA8Uint, Invalid, UChar4, Invalid, 1, 1, 4, ColorUInt8);
+ addDataFormatDesc(A8B8G8R8_SINT_PACK32, RGBA8Sint, Invalid, Char4, Invalid, 1, 1, 4, ColorInt8);
+ addDataFormatDesc(A8B8G8R8_SRGB_PACK32, RGBA8Unorm_sRGB, Invalid, UChar4Normalized, Invalid, 1, 1, 4, ColorFloat);
+
+ addDataFormatDesc(A2R10G10B10_UNORM_PACK32, BGR10A2Unorm, Invalid, Invalid, Invalid, 1, 1, 4, ColorFloat);
+ addDataFormatDesc(A2R10G10B10_SNORM_PACK32, Invalid, Invalid, Invalid, Invalid, 1, 1, 4, ColorFloat);
+ addDataFormatDesc(A2R10G10B10_USCALED_PACK32, Invalid, Invalid, Invalid, Invalid, 1, 1, 4, ColorFloat);
+ addDataFormatDesc(A2R10G10B10_SSCALED_PACK32, Invalid, Invalid, Invalid, Invalid, 1, 1, 4, ColorFloat);
+ addDataFormatDesc(A2R10G10B10_UINT_PACK32, Invalid, Invalid, Invalid, Invalid, 1, 1, 4, ColorUInt16);
+ addDataFormatDesc(A2R10G10B10_SINT_PACK32, Invalid, Invalid, Invalid, Invalid, 1, 1, 4, ColorInt16);
+
+ addDataFormatDesc(A2B10G10R10_UNORM_PACK32, RGB10A2Unorm, Invalid, UInt1010102Normalized, Invalid, 1, 1, 4, ColorFloat);
+ addDataFormatDesc(A2B10G10R10_SNORM_PACK32, Invalid, Invalid, Int1010102Normalized, Invalid, 1, 1, 4, ColorFloat);
+ addDataFormatDesc(A2B10G10R10_USCALED_PACK32, Invalid, Invalid, Invalid, Invalid, 1, 1, 4, ColorFloat);
+ addDataFormatDesc(A2B10G10R10_SSCALED_PACK32, Invalid, Invalid, Invalid, Invalid, 1, 1, 4, ColorFloat);
+ addDataFormatDesc(A2B10G10R10_UINT_PACK32, RGB10A2Uint, Invalid, Invalid, Invalid, 1, 1, 4, ColorUInt16);
+ addDataFormatDesc(A2B10G10R10_SINT_PACK32, Invalid, Invalid, Invalid, Invalid, 1, 1, 4, ColorInt16);
+
+ addDataFormatDesc(R16_UNORM, R16Unorm, Invalid, UShortNormalized, UShort2Normalized, 1, 1, 2, ColorFloat);
+ addDataFormatDesc(R16_SNORM, R16Snorm, Invalid, ShortNormalized, Short2Normalized, 1, 1, 2, ColorFloat);
+ addDataFormatDesc(R16_USCALED, Invalid, Invalid, UShort, UShort2, 1, 1, 2, ColorFloat);
+ addDataFormatDesc(R16_SSCALED, Invalid, Invalid, Short, Short2, 1, 1, 2, ColorFloat);
+ addDataFormatDesc(R16_UINT, R16Uint, Invalid, UShort, UShort2, 1, 1, 2, ColorUInt16);
+ addDataFormatDesc(R16_SINT, R16Sint, Invalid, Short, Short2, 1, 1, 2, ColorInt16);
+ addDataFormatDesc(R16_SFLOAT, R16Float, Invalid, Half, Half2, 1, 1, 2, ColorFloat);
+
+ addDataFormatDesc(R16G16_UNORM, RG16Unorm, Invalid, UShort2Normalized, Invalid, 1, 1, 4, ColorFloat);
+ addDataFormatDesc(R16G16_SNORM, RG16Snorm, Invalid, Short2Normalized, Invalid, 1, 1, 4, ColorFloat);
+ addDataFormatDesc(R16G16_USCALED, Invalid, Invalid, UShort2, Invalid, 1, 1, 4, ColorFloat);
+ addDataFormatDesc(R16G16_SSCALED, Invalid, Invalid, Short2, Invalid, 1, 1, 4, ColorFloat);
+ addDataFormatDesc(R16G16_UINT, RG16Uint, Invalid, UShort2, Invalid, 1, 1, 4, ColorUInt16);
+ addDataFormatDesc(R16G16_SINT, RG16Sint, Invalid, Short2, Invalid, 1, 1, 4, ColorInt16);
+ addDataFormatDesc(R16G16_SFLOAT, RG16Float, Invalid, Half2, Invalid, 1, 1, 4, ColorFloat);
+
+ addDataFormatDesc(R16G16B16_UNORM, Invalid, Invalid, UShort3Normalized, Invalid, 1, 1, 6, ColorFloat);
+ addDataFormatDesc(R16G16B16_SNORM, Invalid, Invalid, Short3Normalized, Invalid, 1, 1, 6, ColorFloat);
+ addDataFormatDesc(R16G16B16_USCALED, Invalid, Invalid, UShort3, Invalid, 1, 1, 6, ColorFloat);
+ addDataFormatDesc(R16G16B16_SSCALED, Invalid, Invalid, Short3, Invalid, 1, 1, 6, ColorFloat);
+ addDataFormatDesc(R16G16B16_UINT, Invalid, Invalid, UShort3, Invalid, 1, 1, 6, ColorUInt16);
+ addDataFormatDesc(R16G16B16_SINT, Invalid, Invalid, Short3, Invalid, 1, 1, 6, ColorInt16);
+ addDataFormatDesc(R16G16B16_SFLOAT, Invalid, Invalid, Half3, Invalid, 1, 1, 6, ColorFloat);
+
+ addDataFormatDesc(R16G16B16A16_UNORM, RGBA16Unorm, Invalid, UShort4Normalized, Invalid, 1, 1, 8, ColorFloat);
+ addDataFormatDesc(R16G16B16A16_SNORM, RGBA16Snorm, Invalid, Short4Normalized, Invalid, 1, 1, 8, ColorFloat);
+ addDataFormatDesc(R16G16B16A16_USCALED, Invalid, Invalid, UShort4, Invalid, 1, 1, 8, ColorFloat);
+ addDataFormatDesc(R16G16B16A16_SSCALED, Invalid, Invalid, Short4, Invalid, 1, 1, 8, ColorFloat);
+ addDataFormatDesc(R16G16B16A16_UINT, RGBA16Uint, Invalid, UShort4, Invalid, 1, 1, 8, ColorUInt16);
+ addDataFormatDesc(R16G16B16A16_SINT, RGBA16Sint, Invalid, Short4, Invalid, 1, 1, 8, ColorInt16);
+ addDataFormatDesc(R16G16B16A16_SFLOAT, RGBA16Float, Invalid, Half4, Invalid, 1, 1, 8, ColorFloat);
+
+ addDataFormatDesc(R32_UINT, R32Uint, Invalid, UInt, Invalid, 1, 1, 4, ColorUInt32);
+ addDataFormatDesc(R32_SINT, R32Sint, Invalid, Int, Invalid, 1, 1, 4, ColorInt32);
+ addDataFormatDesc(R32_SFLOAT, R32Float, Invalid, Float, Invalid, 1, 1, 4, ColorFloat);
+
+ addDataFormatDesc(R32G32_UINT, RG32Uint, Invalid, UInt2, Invalid, 1, 1, 8, ColorUInt32);
+ addDataFormatDesc(R32G32_SINT, RG32Sint, Invalid, Int2, Invalid, 1, 1, 8, ColorInt32);
+ addDataFormatDesc(R32G32_SFLOAT, RG32Float, Invalid, Float2, Invalid, 1, 1, 8, ColorFloat);
+
+ addDataFormatDesc(R32G32B32_UINT, Invalid, Invalid, UInt3, Invalid, 1, 1, 12, ColorUInt32);
+ addDataFormatDesc(R32G32B32_SINT, Invalid, Invalid, Int3, Invalid, 1, 1, 12, ColorInt32);
+ addDataFormatDesc(R32G32B32_SFLOAT, Invalid, Invalid, Float3, Invalid, 1, 1, 12, ColorFloat);
+
+ addDataFormatDesc(R32G32B32A32_UINT, RGBA32Uint, Invalid, UInt4, Invalid, 1, 1, 16, ColorUInt32);
+ addDataFormatDesc(R32G32B32A32_SINT, RGBA32Sint, Invalid, Int4, Invalid, 1, 1, 16, ColorInt32);
+ addDataFormatDesc(R32G32B32A32_SFLOAT, RGBA32Float, Invalid, Float4, Invalid, 1, 1, 16, ColorFloat);
+
+ addDataFormatDesc(R64_UINT, Invalid, Invalid, Invalid, Invalid, 1, 1, 8, ColorFloat);
+ addDataFormatDesc(R64_SINT, Invalid, Invalid, Invalid, Invalid, 1, 1, 8, ColorFloat);
+ addDataFormatDesc(R64_SFLOAT, Invalid, Invalid, Invalid, Invalid, 1, 1, 8, ColorFloat);
+
+ addDataFormatDesc(R64G64_UINT, Invalid, Invalid, Invalid, Invalid, 1, 1, 16, ColorFloat);
+ addDataFormatDesc(R64G64_SINT, Invalid, Invalid, Invalid, Invalid, 1, 1, 16, ColorFloat);
+ addDataFormatDesc(R64G64_SFLOAT, Invalid, Invalid, Invalid, Invalid, 1, 1, 16, ColorFloat);
+
+ addDataFormatDesc(R64G64B64_UINT, Invalid, Invalid, Invalid, Invalid, 1, 1, 24, ColorFloat);
+ addDataFormatDesc(R64G64B64_SINT, Invalid, Invalid, Invalid, Invalid, 1, 1, 24, ColorFloat);
+ addDataFormatDesc(R64G64B64_SFLOAT, Invalid, Invalid, Invalid, Invalid, 1, 1, 24, ColorFloat);
+
+ addDataFormatDesc(R64G64B64A64_UINT, Invalid, Invalid, Invalid, Invalid, 1, 1, 32, ColorFloat);
+ addDataFormatDesc(R64G64B64A64_SINT, Invalid, Invalid, Invalid, Invalid, 1, 1, 32, ColorFloat);
+ addDataFormatDesc(R64G64B64A64_SFLOAT, Invalid, Invalid, Invalid, Invalid, 1, 1, 32, ColorFloat);
+
+ addDataFormatDesc(B10G11R11_UFLOAT_PACK32, RG11B10Float, Invalid, Invalid, Invalid, 1, 1, 4, ColorFloat);
+ addDataFormatDesc(E5B9G9R9_UFLOAT_PACK32, RGB9E5Float, Invalid, Invalid, Invalid, 1, 1, 4, ColorFloat);
+
+ addDataFormatDesc(D32_SFLOAT, Depth32Float, Invalid, Invalid, Invalid, 1, 1, 4, DepthStencil);
+ addDataFormatDesc(D32_SFLOAT_S8_UINT, Depth32Float_Stencil8, Invalid, Invalid, Invalid, 1, 1, 5, DepthStencil);
+
+ addDataFormatDesc(S8_UINT, Stencil8, Invalid, Invalid, Invalid, 1, 1, 1, DepthStencil);
+
+ addDataFormatDesc(D16_UNORM, Depth16Unorm, Depth32Float, Invalid, Invalid, 1, 1, 2, DepthStencil);
+ addDataFormatDesc(D16_UNORM_S8_UINT, Invalid, Invalid, Invalid, Invalid, 1, 1, 3, DepthStencil);
+ addDataFormatDesc(D24_UNORM_S8_UINT, Depth24Unorm_Stencil8, Depth32Float_Stencil8, Invalid, Invalid, 1, 1, 4, DepthStencil);
+
+ addDataFormatDesc(X8_D24_UNORM_PACK32, Invalid, Depth24Unorm_Stencil8, Invalid, Invalid, 1, 1, 4, DepthStencil);
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunguarded-availability"
+
+ addDataFormatDesc(BC1_RGB_UNORM_BLOCK, BC1_RGBA, Invalid, Invalid, Invalid, 4, 4, 8, Compressed);
+ addDataFormatDesc(BC1_RGB_SRGB_BLOCK, BC1_RGBA_sRGB, Invalid, Invalid, Invalid, 4, 4, 8, Compressed);
+ addDataFormatDesc(BC1_RGBA_UNORM_BLOCK, BC1_RGBA, Invalid, Invalid, Invalid, 4, 4, 8, Compressed);
+ addDataFormatDesc(BC1_RGBA_SRGB_BLOCK, BC1_RGBA_sRGB, Invalid, Invalid, Invalid, 4, 4, 8, Compressed);
+
+ addDataFormatDesc(BC2_UNORM_BLOCK, BC2_RGBA, Invalid, Invalid, Invalid, 4, 4, 16, Compressed);
+ addDataFormatDesc(BC2_SRGB_BLOCK, BC2_RGBA_sRGB, Invalid, Invalid, Invalid, 4, 4, 16, Compressed);
+
+ addDataFormatDesc(BC3_UNORM_BLOCK, BC3_RGBA, Invalid, Invalid, Invalid, 4, 4, 16, Compressed);
+ addDataFormatDesc(BC3_SRGB_BLOCK, BC3_RGBA_sRGB, Invalid, Invalid, Invalid, 4, 4, 16, Compressed);
+
+ addDataFormatDesc(BC4_UNORM_BLOCK, BC4_RUnorm, Invalid, Invalid, Invalid, 4, 4, 8, Compressed);
+ addDataFormatDesc(BC4_SNORM_BLOCK, BC4_RSnorm, Invalid, Invalid, Invalid, 4, 4, 8, Compressed);
+
+ addDataFormatDesc(BC5_UNORM_BLOCK, BC5_RGUnorm, Invalid, Invalid, Invalid, 4, 4, 16, Compressed);
+ addDataFormatDesc(BC5_SNORM_BLOCK, BC5_RGSnorm, Invalid, Invalid, Invalid, 4, 4, 16, Compressed);
+
+ addDataFormatDesc(BC6H_UFLOAT_BLOCK, BC6H_RGBUfloat, Invalid, Invalid, Invalid, 4, 4, 16, Compressed);
+ addDataFormatDesc(BC6H_SFLOAT_BLOCK, BC6H_RGBFloat, Invalid, Invalid, Invalid, 4, 4, 16, Compressed);
+
+ addDataFormatDesc(BC7_UNORM_BLOCK, BC7_RGBAUnorm, Invalid, Invalid, Invalid, 4, 4, 16, Compressed);
+ addDataFormatDesc(BC7_SRGB_BLOCK, BC7_RGBAUnorm_sRGB, Invalid, Invalid, Invalid, 4, 4, 16, Compressed);
+
+#pragma clang diagnostic pop
+
+ addDataFormatDesc(ETC2_R8G8B8_UNORM_BLOCK, ETC2_RGB8, Invalid, Invalid, Invalid, 4, 4, 8, Compressed);
+ addDataFormatDesc(ETC2_R8G8B8_SRGB_BLOCK, ETC2_RGB8_sRGB, Invalid, Invalid, Invalid, 4, 4, 8, Compressed);
+ addDataFormatDesc(ETC2_R8G8B8A1_UNORM_BLOCK, ETC2_RGB8A1, Invalid, Invalid, Invalid, 4, 4, 8, Compressed);
+ addDataFormatDesc(ETC2_R8G8B8A1_SRGB_BLOCK, ETC2_RGB8A1_sRGB, Invalid, Invalid, Invalid, 4, 4, 8, Compressed);
+
+ addDataFormatDesc(ETC2_R8G8B8A8_UNORM_BLOCK, EAC_RGBA8, Invalid, Invalid, Invalid, 4, 4, 16, Compressed);
+ addDataFormatDesc(ETC2_R8G8B8A8_SRGB_BLOCK, EAC_RGBA8_sRGB, Invalid, Invalid, Invalid, 4, 4, 16, Compressed);
+
+ addDataFormatDesc(EAC_R11_UNORM_BLOCK, EAC_R11Unorm, Invalid, Invalid, Invalid, 4, 4, 8, Compressed);
+ addDataFormatDesc(EAC_R11_SNORM_BLOCK, EAC_R11Snorm, Invalid, Invalid, Invalid, 4, 4, 8, Compressed);
+
+ addDataFormatDesc(EAC_R11G11_UNORM_BLOCK, EAC_RG11Unorm, Invalid, Invalid, Invalid, 4, 4, 16, Compressed);
+ addDataFormatDesc(EAC_R11G11_SNORM_BLOCK, EAC_RG11Snorm, Invalid, Invalid, Invalid, 4, 4, 16, Compressed);
+
+ addDataFormatDesc(ASTC_4x4_UNORM_BLOCK, ASTC_4x4_LDR, Invalid, Invalid, Invalid, 4, 4, 16, Compressed);
+ addDataFormatDesc(ASTC_4x4_SRGB_BLOCK, ASTC_4x4_sRGB, Invalid, Invalid, Invalid, 4, 4, 16, Compressed);
+ addDataFormatDesc(ASTC_5x4_UNORM_BLOCK, ASTC_5x4_LDR, Invalid, Invalid, Invalid, 5, 4, 16, Compressed);
+ addDataFormatDesc(ASTC_5x4_SRGB_BLOCK, ASTC_5x4_sRGB, Invalid, Invalid, Invalid, 5, 4, 16, Compressed);
+ addDataFormatDesc(ASTC_5x5_UNORM_BLOCK, ASTC_5x5_LDR, Invalid, Invalid, Invalid, 5, 5, 16, Compressed);
+ addDataFormatDesc(ASTC_5x5_SRGB_BLOCK, ASTC_5x5_sRGB, Invalid, Invalid, Invalid, 5, 5, 16, Compressed);
+ addDataFormatDesc(ASTC_6x5_UNORM_BLOCK, ASTC_6x5_LDR, Invalid, Invalid, Invalid, 6, 5, 16, Compressed);
+ addDataFormatDesc(ASTC_6x5_SRGB_BLOCK, ASTC_6x5_sRGB, Invalid, Invalid, Invalid, 6, 5, 16, Compressed);
+ addDataFormatDesc(ASTC_6x6_UNORM_BLOCK, ASTC_6x6_LDR, Invalid, Invalid, Invalid, 6, 6, 16, Compressed);
+ addDataFormatDesc(ASTC_6x6_SRGB_BLOCK, ASTC_6x6_sRGB, Invalid, Invalid, Invalid, 6, 6, 16, Compressed);
+ addDataFormatDesc(ASTC_8x5_UNORM_BLOCK, ASTC_8x5_LDR, Invalid, Invalid, Invalid, 8, 5, 16, Compressed);
+ addDataFormatDesc(ASTC_8x5_SRGB_BLOCK, ASTC_8x5_sRGB, Invalid, Invalid, Invalid, 8, 5, 16, Compressed);
+ addDataFormatDesc(ASTC_8x6_UNORM_BLOCK, ASTC_8x6_LDR, Invalid, Invalid, Invalid, 8, 6, 16, Compressed);
+ addDataFormatDesc(ASTC_8x6_SRGB_BLOCK, ASTC_8x6_sRGB, Invalid, Invalid, Invalid, 8, 6, 16, Compressed);
+ addDataFormatDesc(ASTC_8x8_UNORM_BLOCK, ASTC_8x8_LDR, Invalid, Invalid, Invalid, 8, 8, 16, Compressed);
+ addDataFormatDesc(ASTC_8x8_SRGB_BLOCK, ASTC_8x8_sRGB, Invalid, Invalid, Invalid, 8, 8, 16, Compressed);
+ addDataFormatDesc(ASTC_10x5_UNORM_BLOCK, ASTC_10x5_LDR, Invalid, Invalid, Invalid, 10, 5, 16, Compressed);
+ addDataFormatDesc(ASTC_10x5_SRGB_BLOCK, ASTC_10x5_sRGB, Invalid, Invalid, Invalid, 10, 5, 16, Compressed);
+ addDataFormatDesc(ASTC_10x6_UNORM_BLOCK, ASTC_10x6_LDR, Invalid, Invalid, Invalid, 10, 6, 16, Compressed);
+ addDataFormatDesc(ASTC_10x6_SRGB_BLOCK, ASTC_10x6_sRGB, Invalid, Invalid, Invalid, 10, 6, 16, Compressed);
+ addDataFormatDesc(ASTC_10x8_UNORM_BLOCK, ASTC_10x8_LDR, Invalid, Invalid, Invalid, 10, 8, 16, Compressed);
+ addDataFormatDesc(ASTC_10x8_SRGB_BLOCK, ASTC_10x8_sRGB, Invalid, Invalid, Invalid, 10, 8, 16, Compressed);
+ addDataFormatDesc(ASTC_10x10_UNORM_BLOCK, ASTC_10x10_LDR, Invalid, Invalid, Invalid, 10, 10, 16, Compressed);
+ addDataFormatDesc(ASTC_10x10_SRGB_BLOCK, ASTC_10x10_sRGB, Invalid, Invalid, Invalid, 10, 10, 16, Compressed);
+ addDataFormatDesc(ASTC_12x10_UNORM_BLOCK, ASTC_12x10_LDR, Invalid, Invalid, Invalid, 12, 10, 16, Compressed);
+ addDataFormatDesc(ASTC_12x10_SRGB_BLOCK, ASTC_12x10_sRGB, Invalid, Invalid, Invalid, 12, 10, 16, Compressed);
+ addDataFormatDesc(ASTC_12x12_UNORM_BLOCK, ASTC_12x12_LDR, Invalid, Invalid, Invalid, 12, 12, 16, Compressed);
+ addDataFormatDesc(ASTC_12x12_SRGB_BLOCK, ASTC_12x12_sRGB, Invalid, Invalid, Invalid, 12, 12, 16, Compressed);
+
+ addDfFormatDescChromaSubsampling(G8B8G8R8_422_UNORM, GBGR422, 1, 8, 2, 1, 4);
+ addDfFormatDescChromaSubsampling(B8G8R8G8_422_UNORM, BGRG422, 1, 8, 2, 1, 4);
+ addDfFormatDescChromaSubsampling(G8_B8_R8_3PLANE_420_UNORM, Invalid, 3, 8, 2, 2, 6);
+ addDfFormatDescChromaSubsampling(G8_B8R8_2PLANE_420_UNORM, Invalid, 2, 8, 2, 2, 6);
+ addDfFormatDescChromaSubsampling(G8_B8_R8_3PLANE_422_UNORM, Invalid, 3, 8, 2, 1, 4);
+ addDfFormatDescChromaSubsampling(G8_B8R8_2PLANE_422_UNORM, Invalid, 2, 8, 2, 1, 4);
+ addDfFormatDescChromaSubsampling(G8_B8_R8_3PLANE_444_UNORM, Invalid, 3, 8, 1, 1, 3);
+ addDfFormatDescChromaSubsampling(R10X6_UNORM_PACK16, R16Unorm, 0, 10, 1, 1, 2);
+ addDfFormatDescChromaSubsampling(R10X6G10X6_UNORM_2PACK16, RG16Unorm, 0, 10, 1, 1, 4);
+ addDfFormatDescChromaSubsampling(R10X6G10X6B10X6A10X6_UNORM_4PACK16, RGBA16Unorm, 0, 10, 1, 1, 8);
+ addDfFormatDescChromaSubsampling(G10X6B10X6G10X6R10X6_422_UNORM_4PACK16, Invalid, 1, 10, 2, 1, 8);
+ addDfFormatDescChromaSubsampling(B10X6G10X6R10X6G10X6_422_UNORM_4PACK16, Invalid, 1, 10, 2, 1, 8);
+ addDfFormatDescChromaSubsampling(G10X6_B10X6_R10X6_3PLANE_420_UNORM_3PACK16, Invalid, 3, 10, 2, 2, 12);
+ addDfFormatDescChromaSubsampling(G10X6_B10X6R10X6_2PLANE_420_UNORM_3PACK16, Invalid, 2, 10, 2, 2, 12);
+ addDfFormatDescChromaSubsampling(G10X6_B10X6_R10X6_3PLANE_422_UNORM_3PACK16, Invalid, 3, 10, 2, 1, 8);
+ addDfFormatDescChromaSubsampling(G10X6_B10X6R10X6_2PLANE_422_UNORM_3PACK16, Invalid, 2, 10, 2, 1, 8);
+ addDfFormatDescChromaSubsampling(G10X6_B10X6_R10X6_3PLANE_444_UNORM_3PACK16, Invalid, 3, 10, 1, 1, 6);
+ addDfFormatDescChromaSubsampling(R12X4_UNORM_PACK16, R16Unorm, 0, 12, 1, 1, 2);
+ addDfFormatDescChromaSubsampling(R12X4G12X4_UNORM_2PACK16, RG16Unorm, 0, 12, 1, 1, 4);
+ addDfFormatDescChromaSubsampling(R12X4G12X4B12X4A12X4_UNORM_4PACK16, RGBA16Unorm, 0, 12, 1, 1, 8);
+ addDfFormatDescChromaSubsampling(G12X4B12X4G12X4R12X4_422_UNORM_4PACK16, Invalid, 1, 12, 2, 1, 8);
+ addDfFormatDescChromaSubsampling(B12X4G12X4R12X4G12X4_422_UNORM_4PACK16, Invalid, 1, 12, 2, 1, 8);
+ addDfFormatDescChromaSubsampling(G12X4_B12X4_R12X4_3PLANE_420_UNORM_3PACK16, Invalid, 3, 12, 2, 2, 12);
+ addDfFormatDescChromaSubsampling(G12X4_B12X4R12X4_2PLANE_420_UNORM_3PACK16, Invalid, 2, 12, 2, 2, 12);
+ addDfFormatDescChromaSubsampling(G12X4_B12X4_R12X4_3PLANE_422_UNORM_3PACK16, Invalid, 3, 12, 2, 1, 8);
+ addDfFormatDescChromaSubsampling(G12X4_B12X4R12X4_2PLANE_422_UNORM_3PACK16, Invalid, 2, 12, 2, 1, 8);
+ addDfFormatDescChromaSubsampling(G12X4_B12X4_R12X4_3PLANE_444_UNORM_3PACK16, Invalid, 3, 12, 1, 1, 6);
+ addDfFormatDescChromaSubsampling(G16B16G16R16_422_UNORM, Invalid, 1, 16, 2, 1, 8);
+ addDfFormatDescChromaSubsampling(B16G16R16G16_422_UNORM, Invalid, 1, 16, 2, 1, 8);
+ addDfFormatDescChromaSubsampling(G16_B16_R16_3PLANE_420_UNORM, Invalid, 3, 16, 2, 2, 12);
+ addDfFormatDescChromaSubsampling(G16_B16R16_2PLANE_420_UNORM, Invalid, 2, 16, 2, 2, 12);
+ addDfFormatDescChromaSubsampling(G16_B16_R16_3PLANE_422_UNORM, Invalid, 3, 16, 2, 1, 8);
+ addDfFormatDescChromaSubsampling(G16_B16R16_2PLANE_422_UNORM, Invalid, 2, 16, 2, 1, 8);
+ addDfFormatDescChromaSubsampling(G16_B16_R16_3PLANE_444_UNORM, Invalid, 3, 16, 1, 1, 6);
+}
+
+#define addMTLPixelFormatDescFull(MTL_FMT, VIEW_CLASS, IOS_CAPS, MACOS_CAPS, MTL_FMT_LINEAR) \
+ CRASH_BAD_INDEX_MSG(fmtIdx, _mtlPixelFormatCount, "Adding too many pixel formats"); \
+ _mtlPixelFormatDescriptions[fmtIdx++] = { .mtlPixelFormat = MTLPixelFormat##MTL_FMT, RD::DATA_FORMAT_MAX, select_platform_caps(kMTLFmtCaps##MACOS_CAPS, kMTLFmtCaps##IOS_CAPS), MTLViewClass::VIEW_CLASS, MTLPixelFormat##MTL_FMT_LINEAR, "MTLPixelFormat" #MTL_FMT }
+
+#define addMTLPixelFormatDesc(MTL_FMT, VIEW_CLASS, IOS_CAPS, MACOS_CAPS) \
+ addMTLPixelFormatDescFull(MTL_FMT, VIEW_CLASS, IOS_CAPS, MACOS_CAPS, MTL_FMT)
+
+#define addMTLPixelFormatDescSRGB(MTL_FMT, VIEW_CLASS, IOS_CAPS, MACOS_CAPS, MTL_FMT_LINEAR) \
+ addMTLPixelFormatDescFull(MTL_FMT, VIEW_CLASS, IOS_CAPS, MACOS_CAPS, MTL_FMT_LINEAR)
+
+void PixelFormats::initMTLPixelFormatCapabilities() {
+ clear(_mtlPixelFormatDescriptions, _mtlPixelFormatCount);
+
+ uint32_t fmtIdx = 0;
+
+ // When adding to this list, be sure to ensure _mtlPixelFormatCount is large enough for the format count.
+
+ // MTLPixelFormatInvalid must come first.
+ addMTLPixelFormatDesc(Invalid, None, None, None);
+
+ // Ordinary 8-bit pixel formats.
+ addMTLPixelFormatDesc(A8Unorm, Color8, RF, RF);
+ addMTLPixelFormatDesc(R8Unorm, Color8, All, All);
+ addMTLPixelFormatDescSRGB(R8Unorm_sRGB, Color8, RFCMRB, None, R8Unorm);
+ addMTLPixelFormatDesc(R8Snorm, Color8, RFWCMB, All);
+ addMTLPixelFormatDesc(R8Uint, Color8, RWCM, RWCM);
+ addMTLPixelFormatDesc(R8Sint, Color8, RWCM, RWCM);
+
+ // Ordinary 16-bit pixel formats.
+ addMTLPixelFormatDesc(R16Unorm, Color16, RFWCMB, All);
+ addMTLPixelFormatDesc(R16Snorm, Color16, RFWCMB, All);
+ addMTLPixelFormatDesc(R16Uint, Color16, RWCM, RWCM);
+ addMTLPixelFormatDesc(R16Sint, Color16, RWCM, RWCM);
+ addMTLPixelFormatDesc(R16Float, Color16, All, All);
+
+ addMTLPixelFormatDesc(RG8Unorm, Color16, All, All);
+ addMTLPixelFormatDescSRGB(RG8Unorm_sRGB, Color16, RFCMRB, None, RG8Unorm);
+ addMTLPixelFormatDesc(RG8Snorm, Color16, RFWCMB, All);
+ addMTLPixelFormatDesc(RG8Uint, Color16, RWCM, RWCM);
+ addMTLPixelFormatDesc(RG8Sint, Color16, RWCM, RWCM);
+
+ // Packed 16-bit pixel formats.
+ addMTLPixelFormatDesc(B5G6R5Unorm, Color16, RFCMRB, None);
+ addMTLPixelFormatDesc(A1BGR5Unorm, Color16, RFCMRB, None);
+ addMTLPixelFormatDesc(ABGR4Unorm, Color16, RFCMRB, None);
+ addMTLPixelFormatDesc(BGR5A1Unorm, Color16, RFCMRB, None);
+
+ // Ordinary 32-bit pixel formats.
+ addMTLPixelFormatDesc(R32Uint, Color32, RC, RWCM);
+ addMTLPixelFormatDesc(R32Sint, Color32, RC, RWCM);
+ addMTLPixelFormatDesc(R32Float, Color32, RCMB, All);
+
+ addMTLPixelFormatDesc(RG16Unorm, Color32, RFWCMB, All);
+ addMTLPixelFormatDesc(RG16Snorm, Color32, RFWCMB, All);
+ addMTLPixelFormatDesc(RG16Uint, Color32, RWCM, RWCM);
+ addMTLPixelFormatDesc(RG16Sint, Color32, RWCM, RWCM);
+ addMTLPixelFormatDesc(RG16Float, Color32, All, All);
+
+ addMTLPixelFormatDesc(RGBA8Unorm, Color32, All, All);
+ addMTLPixelFormatDescSRGB(RGBA8Unorm_sRGB, Color32, RFCMRB, RFCMRB, RGBA8Unorm);
+ addMTLPixelFormatDesc(RGBA8Snorm, Color32, RFWCMB, All);
+ addMTLPixelFormatDesc(RGBA8Uint, Color32, RWCM, RWCM);
+ addMTLPixelFormatDesc(RGBA8Sint, Color32, RWCM, RWCM);
+
+ addMTLPixelFormatDesc(BGRA8Unorm, Color32, All, All);
+ addMTLPixelFormatDescSRGB(BGRA8Unorm_sRGB, Color32, RFCMRB, RFCMRB, BGRA8Unorm);
+
+ // Packed 32-bit pixel formats.
+ addMTLPixelFormatDesc(RGB10A2Unorm, Color32, RFCMRB, All);
+ addMTLPixelFormatDesc(RGB10A2Uint, Color32, RCM, RWCM);
+ addMTLPixelFormatDesc(RG11B10Float, Color32, RFCMRB, All);
+ addMTLPixelFormatDesc(RGB9E5Float, Color32, RFCMRB, RF);
+
+ // Ordinary 64-bit pixel formats.
+ addMTLPixelFormatDesc(RG32Uint, Color64, RC, RWCM);
+ addMTLPixelFormatDesc(RG32Sint, Color64, RC, RWCM);
+ addMTLPixelFormatDesc(RG32Float, Color64, RCB, All);
+
+ addMTLPixelFormatDesc(RGBA16Unorm, Color64, RFWCMB, All);
+ addMTLPixelFormatDesc(RGBA16Snorm, Color64, RFWCMB, All);
+ addMTLPixelFormatDesc(RGBA16Uint, Color64, RWCM, RWCM);
+ addMTLPixelFormatDesc(RGBA16Sint, Color64, RWCM, RWCM);
+ addMTLPixelFormatDesc(RGBA16Float, Color64, All, All);
+
+ // Ordinary 128-bit pixel formats.
+ addMTLPixelFormatDesc(RGBA32Uint, Color128, RC, RWCM);
+ addMTLPixelFormatDesc(RGBA32Sint, Color128, RC, RWCM);
+ addMTLPixelFormatDesc(RGBA32Float, Color128, RC, All);
+
+ // Compressed pixel formats.
+ addMTLPixelFormatDesc(PVRTC_RGBA_2BPP, PVRTC_RGBA_2BPP, RF, None);
+ addMTLPixelFormatDescSRGB(PVRTC_RGBA_2BPP_sRGB, PVRTC_RGBA_2BPP, RF, None, PVRTC_RGBA_2BPP);
+ addMTLPixelFormatDesc(PVRTC_RGBA_4BPP, PVRTC_RGBA_4BPP, RF, None);
+ addMTLPixelFormatDescSRGB(PVRTC_RGBA_4BPP_sRGB, PVRTC_RGBA_4BPP, RF, None, PVRTC_RGBA_4BPP);
+
+ addMTLPixelFormatDesc(ETC2_RGB8, ETC2_RGB8, RF, None);
+ addMTLPixelFormatDescSRGB(ETC2_RGB8_sRGB, ETC2_RGB8, RF, None, ETC2_RGB8);
+ addMTLPixelFormatDesc(ETC2_RGB8A1, ETC2_RGB8A1, RF, None);
+ addMTLPixelFormatDescSRGB(ETC2_RGB8A1_sRGB, ETC2_RGB8A1, RF, None, ETC2_RGB8A1);
+ addMTLPixelFormatDesc(EAC_RGBA8, EAC_RGBA8, RF, None);
+ addMTLPixelFormatDescSRGB(EAC_RGBA8_sRGB, EAC_RGBA8, RF, None, EAC_RGBA8);
+ addMTLPixelFormatDesc(EAC_R11Unorm, EAC_R11, RF, None);
+ addMTLPixelFormatDesc(EAC_R11Snorm, EAC_R11, RF, None);
+ addMTLPixelFormatDesc(EAC_RG11Unorm, EAC_RG11, RF, None);
+ addMTLPixelFormatDesc(EAC_RG11Snorm, EAC_RG11, RF, None);
+
+ addMTLPixelFormatDesc(ASTC_4x4_LDR, ASTC_4x4, None, None);
+ addMTLPixelFormatDescSRGB(ASTC_4x4_sRGB, ASTC_4x4, None, None, ASTC_4x4_LDR);
+ addMTLPixelFormatDesc(ASTC_4x4_HDR, ASTC_4x4, None, None);
+ addMTLPixelFormatDesc(ASTC_5x4_LDR, ASTC_5x4, None, None);
+ addMTLPixelFormatDescSRGB(ASTC_5x4_sRGB, ASTC_5x4, None, None, ASTC_5x4_LDR);
+ addMTLPixelFormatDesc(ASTC_5x4_HDR, ASTC_5x4, None, None);
+ addMTLPixelFormatDesc(ASTC_5x5_LDR, ASTC_5x5, None, None);
+ addMTLPixelFormatDescSRGB(ASTC_5x5_sRGB, ASTC_5x5, None, None, ASTC_5x5_LDR);
+ addMTLPixelFormatDesc(ASTC_5x5_HDR, ASTC_5x5, None, None);
+ addMTLPixelFormatDesc(ASTC_6x5_LDR, ASTC_6x5, None, None);
+ addMTLPixelFormatDescSRGB(ASTC_6x5_sRGB, ASTC_6x5, None, None, ASTC_6x5_LDR);
+ addMTLPixelFormatDesc(ASTC_6x5_HDR, ASTC_6x5, None, None);
+ addMTLPixelFormatDesc(ASTC_6x6_LDR, ASTC_6x6, None, None);
+ addMTLPixelFormatDescSRGB(ASTC_6x6_sRGB, ASTC_6x6, None, None, ASTC_6x6_LDR);
+ addMTLPixelFormatDesc(ASTC_6x6_HDR, ASTC_6x6, None, None);
+ addMTLPixelFormatDesc(ASTC_8x5_LDR, ASTC_8x5, None, None);
+ addMTLPixelFormatDescSRGB(ASTC_8x5_sRGB, ASTC_8x5, None, None, ASTC_8x5_LDR);
+ addMTLPixelFormatDesc(ASTC_8x5_HDR, ASTC_8x5, None, None);
+ addMTLPixelFormatDesc(ASTC_8x6_LDR, ASTC_8x6, None, None);
+ addMTLPixelFormatDescSRGB(ASTC_8x6_sRGB, ASTC_8x6, None, None, ASTC_8x6_LDR);
+ addMTLPixelFormatDesc(ASTC_8x6_HDR, ASTC_8x6, None, None);
+ addMTLPixelFormatDesc(ASTC_8x8_LDR, ASTC_8x8, None, None);
+ addMTLPixelFormatDescSRGB(ASTC_8x8_sRGB, ASTC_8x8, None, None, ASTC_8x8_LDR);
+ addMTLPixelFormatDesc(ASTC_8x8_HDR, ASTC_8x8, None, None);
+ addMTLPixelFormatDesc(ASTC_10x5_LDR, ASTC_10x5, None, None);
+ addMTLPixelFormatDescSRGB(ASTC_10x5_sRGB, ASTC_10x5, None, None, ASTC_10x5_LDR);
+ addMTLPixelFormatDesc(ASTC_10x5_HDR, ASTC_10x5, None, None);
+ addMTLPixelFormatDesc(ASTC_10x6_LDR, ASTC_10x6, None, None);
+ addMTLPixelFormatDescSRGB(ASTC_10x6_sRGB, ASTC_10x6, None, None, ASTC_10x6_LDR);
+ addMTLPixelFormatDesc(ASTC_10x6_HDR, ASTC_10x6, None, None);
+ addMTLPixelFormatDesc(ASTC_10x8_LDR, ASTC_10x8, None, None);
+ addMTLPixelFormatDescSRGB(ASTC_10x8_sRGB, ASTC_10x8, None, None, ASTC_10x8_LDR);
+ addMTLPixelFormatDesc(ASTC_10x8_HDR, ASTC_10x8, None, None);
+ addMTLPixelFormatDesc(ASTC_10x10_LDR, ASTC_10x10, None, None);
+ addMTLPixelFormatDescSRGB(ASTC_10x10_sRGB, ASTC_10x10, None, None, ASTC_10x10_LDR);
+ addMTLPixelFormatDesc(ASTC_10x10_HDR, ASTC_10x10, None, None);
+ addMTLPixelFormatDesc(ASTC_12x10_LDR, ASTC_12x10, None, None);
+ addMTLPixelFormatDescSRGB(ASTC_12x10_sRGB, ASTC_12x10, None, None, ASTC_12x10_LDR);
+ addMTLPixelFormatDesc(ASTC_12x10_HDR, ASTC_12x10, None, None);
+ addMTLPixelFormatDesc(ASTC_12x12_LDR, ASTC_12x12, None, None);
+ addMTLPixelFormatDescSRGB(ASTC_12x12_sRGB, ASTC_12x12, None, None, ASTC_12x12_LDR);
+ addMTLPixelFormatDesc(ASTC_12x12_HDR, ASTC_12x12, None, None);
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunguarded-availability"
+
+ addMTLPixelFormatDesc(BC1_RGBA, BC1_RGBA, RF, RF);
+ addMTLPixelFormatDescSRGB(BC1_RGBA_sRGB, BC1_RGBA, RF, RF, BC1_RGBA);
+ addMTLPixelFormatDesc(BC2_RGBA, BC2_RGBA, RF, RF);
+ addMTLPixelFormatDescSRGB(BC2_RGBA_sRGB, BC2_RGBA, RF, RF, BC2_RGBA);
+ addMTLPixelFormatDesc(BC3_RGBA, BC3_RGBA, RF, RF);
+ addMTLPixelFormatDescSRGB(BC3_RGBA_sRGB, BC3_RGBA, RF, RF, BC3_RGBA);
+ addMTLPixelFormatDesc(BC4_RUnorm, BC4_R, RF, RF);
+ addMTLPixelFormatDesc(BC4_RSnorm, BC4_R, RF, RF);
+ addMTLPixelFormatDesc(BC5_RGUnorm, BC5_RG, RF, RF);
+ addMTLPixelFormatDesc(BC5_RGSnorm, BC5_RG, RF, RF);
+ addMTLPixelFormatDesc(BC6H_RGBUfloat, BC6H_RGB, RF, RF);
+ addMTLPixelFormatDesc(BC6H_RGBFloat, BC6H_RGB, RF, RF);
+ addMTLPixelFormatDesc(BC7_RGBAUnorm, BC7_RGBA, RF, RF);
+ addMTLPixelFormatDescSRGB(BC7_RGBAUnorm_sRGB, BC7_RGBA, RF, RF, BC7_RGBAUnorm);
+
+#pragma clang diagnostic pop
+
+ // YUV pixel formats.
+ addMTLPixelFormatDesc(GBGR422, None, RF, RF);
+ addMTLPixelFormatDesc(BGRG422, None, RF, RF);
+
+ // Extended range and wide color pixel formats.
+ addMTLPixelFormatDesc(BGRA10_XR, BGRA10_XR, None, None);
+ addMTLPixelFormatDescSRGB(BGRA10_XR_sRGB, BGRA10_XR, None, None, BGRA10_XR);
+ addMTLPixelFormatDesc(BGR10_XR, BGR10_XR, None, None);
+ addMTLPixelFormatDescSRGB(BGR10_XR_sRGB, BGR10_XR, None, None, BGR10_XR);
+ addMTLPixelFormatDesc(BGR10A2Unorm, Color32, None, None);
+
+ // Depth and stencil pixel formats.
+ addMTLPixelFormatDesc(Depth16Unorm, None, None, None);
+ addMTLPixelFormatDesc(Depth32Float, None, DRM, DRFMR);
+ addMTLPixelFormatDesc(Stencil8, None, DRM, DRMR);
+ addMTLPixelFormatDesc(Depth24Unorm_Stencil8, Depth24_Stencil8, None, None);
+ addMTLPixelFormatDesc(Depth32Float_Stencil8, Depth32_Stencil8, DRM, DRFMR);
+ addMTLPixelFormatDesc(X24_Stencil8, Depth24_Stencil8, None, DRMR);
+ addMTLPixelFormatDesc(X32_Stencil8, Depth32_Stencil8, DRM, DRMR);
+
+ // When adding to this list, be sure to ensure _mtlPixelFormatCount is large enough for the format count.
+}
+
+#define addMTLVertexFormatDesc(MTL_VTX_FMT, IOS_CAPS, MACOS_CAPS) \
+ CRASH_BAD_INDEX_MSG(fmtIdx, _mtlVertexFormatCount, "Attempting to describe too many MTLVertexFormats"); \
+ _mtlVertexFormatDescriptions[fmtIdx++] = { .mtlVertexFormat = MTLVertexFormat##MTL_VTX_FMT, RD::DATA_FORMAT_MAX, select_platform_caps(kMTLFmtCaps##MACOS_CAPS, kMTLFmtCaps##IOS_CAPS), MTLViewClass::None, MTLPixelFormatInvalid, "MTLVertexFormat" #MTL_VTX_FMT }
+
+void PixelFormats::initMTLVertexFormatCapabilities() {
+ clear(_mtlVertexFormatDescriptions, _mtlVertexFormatCount);
+
+ uint32_t fmtIdx = 0;
+
+ // When adding to this list, be sure to ensure _mtlVertexFormatCount is large enough for the format count.
+
+ // MTLVertexFormatInvalid must come first.
+ addMTLVertexFormatDesc(Invalid, None, None);
+
+ addMTLVertexFormatDesc(UChar2Normalized, Vertex, Vertex);
+ addMTLVertexFormatDesc(Char2Normalized, Vertex, Vertex);
+ addMTLVertexFormatDesc(UChar2, Vertex, Vertex);
+ addMTLVertexFormatDesc(Char2, Vertex, Vertex);
+
+ addMTLVertexFormatDesc(UChar3Normalized, Vertex, Vertex);
+ addMTLVertexFormatDesc(Char3Normalized, Vertex, Vertex);
+ addMTLVertexFormatDesc(UChar3, Vertex, Vertex);
+ addMTLVertexFormatDesc(Char3, Vertex, Vertex);
+
+ addMTLVertexFormatDesc(UChar4Normalized, Vertex, Vertex);
+ addMTLVertexFormatDesc(Char4Normalized, Vertex, Vertex);
+ addMTLVertexFormatDesc(UChar4, Vertex, Vertex);
+ addMTLVertexFormatDesc(Char4, Vertex, Vertex);
+
+ addMTLVertexFormatDesc(UInt1010102Normalized, Vertex, Vertex);
+ addMTLVertexFormatDesc(Int1010102Normalized, Vertex, Vertex);
+
+ addMTLVertexFormatDesc(UShort2Normalized, Vertex, Vertex);
+ addMTLVertexFormatDesc(Short2Normalized, Vertex, Vertex);
+ addMTLVertexFormatDesc(UShort2, Vertex, Vertex);
+ addMTLVertexFormatDesc(Short2, Vertex, Vertex);
+ addMTLVertexFormatDesc(Half2, Vertex, Vertex);
+
+ addMTLVertexFormatDesc(UShort3Normalized, Vertex, Vertex);
+ addMTLVertexFormatDesc(Short3Normalized, Vertex, Vertex);
+ addMTLVertexFormatDesc(UShort3, Vertex, Vertex);
+ addMTLVertexFormatDesc(Short3, Vertex, Vertex);
+ addMTLVertexFormatDesc(Half3, Vertex, Vertex);
+
+ addMTLVertexFormatDesc(UShort4Normalized, Vertex, Vertex);
+ addMTLVertexFormatDesc(Short4Normalized, Vertex, Vertex);
+ addMTLVertexFormatDesc(UShort4, Vertex, Vertex);
+ addMTLVertexFormatDesc(Short4, Vertex, Vertex);
+ addMTLVertexFormatDesc(Half4, Vertex, Vertex);
+
+ addMTLVertexFormatDesc(UInt, Vertex, Vertex);
+ addMTLVertexFormatDesc(Int, Vertex, Vertex);
+ addMTLVertexFormatDesc(Float, Vertex, Vertex);
+
+ addMTLVertexFormatDesc(UInt2, Vertex, Vertex);
+ addMTLVertexFormatDesc(Int2, Vertex, Vertex);
+ addMTLVertexFormatDesc(Float2, Vertex, Vertex);
+
+ addMTLVertexFormatDesc(UInt3, Vertex, Vertex);
+ addMTLVertexFormatDesc(Int3, Vertex, Vertex);
+ addMTLVertexFormatDesc(Float3, Vertex, Vertex);
+
+ addMTLVertexFormatDesc(UInt4, Vertex, Vertex);
+ addMTLVertexFormatDesc(Int4, Vertex, Vertex);
+ addMTLVertexFormatDesc(Float4, Vertex, Vertex);
+
+ addMTLVertexFormatDesc(UCharNormalized, None, None);
+ addMTLVertexFormatDesc(CharNormalized, None, None);
+ addMTLVertexFormatDesc(UChar, None, None);
+ addMTLVertexFormatDesc(Char, None, None);
+
+ addMTLVertexFormatDesc(UShortNormalized, None, None);
+ addMTLVertexFormatDesc(ShortNormalized, None, None);
+ addMTLVertexFormatDesc(UShort, None, None);
+ addMTLVertexFormatDesc(Short, None, None);
+ addMTLVertexFormatDesc(Half, None, None);
+
+ addMTLVertexFormatDesc(UChar4Normalized_BGRA, None, None);
+
+ // When adding to this list, be sure to ensure _mtlVertexFormatCount is large enough for the format count.
+}
+
+void PixelFormats::buildMTLFormatMaps() {
+ // Set all MTLPixelFormats and MTLVertexFormats to undefined/invalid.
+ clear(_mtlFormatDescIndicesByMTLPixelFormatsCore, _mtlPixelFormatCoreCount);
+ clear(_mtlFormatDescIndicesByMTLVertexFormats, _mtlVertexFormatCount);
+
+ // Build lookup table for MTLPixelFormat specs.
+ // For most Metal format values, which are small and consecutive, use a simple lookup array.
+ // For outlier format values, which can be large, use a map.
+ for (uint32_t fmtIdx = 0; fmtIdx < _mtlPixelFormatCount; fmtIdx++) {
+ MTLPixelFormat fmt = _mtlPixelFormatDescriptions[fmtIdx].mtlPixelFormat;
+ if (fmt) {
+ if (fmt < _mtlPixelFormatCoreCount) {
+ _mtlFormatDescIndicesByMTLPixelFormatsCore[fmt] = fmtIdx;
+ } else {
+ _mtlFormatDescIndicesByMTLPixelFormatsExt[fmt] = fmtIdx;
+ }
+ }
+ }
+
+ // Build lookup table for MTLVertexFormat specs.
+ for (uint32_t fmtIdx = 0; fmtIdx < _mtlVertexFormatCount; fmtIdx++) {
+ MTLVertexFormat fmt = _mtlVertexFormatDescriptions[fmtIdx].mtlVertexFormat;
+ if (fmt) {
+ _mtlFormatDescIndicesByMTLVertexFormats[fmt] = fmtIdx;
+ }
+ }
+}
+
+// If the device supports the feature set, add additional capabilities to a MTLPixelFormat.
+void PixelFormats::addMTLPixelFormatCapabilities(id p_device,
+ MTLFeatureSet p_feature_set,
+ MTLPixelFormat p_format,
+ MTLFmtCaps p_caps) {
+ if ([p_device supportsFeatureSet:p_feature_set]) {
+ flags::set(getMTLPixelFormatDesc(p_format).mtlFmtCaps, p_caps);
+ }
+}
+
+// If the device supports the GPU family, add additional capabilities to a MTLPixelFormat.
+void PixelFormats::addMTLPixelFormatCapabilities(id p_device,
+ MTLGPUFamily p_family,
+ MTLPixelFormat p_format,
+ MTLFmtCaps p_caps) {
+ if ([p_device supportsFamily:p_family]) {
+ flags::set(getMTLPixelFormatDesc(p_format).mtlFmtCaps, p_caps);
+ }
+}
+
+// Disable capability flags in the Metal pixel format.
+void PixelFormats::disableMTLPixelFormatCapabilities(MTLPixelFormat p_format,
+ MTLFmtCaps p_caps) {
+ flags::clear(getMTLPixelFormatDesc(p_format).mtlFmtCaps, p_caps);
+}
+
+void PixelFormats::disableAllMTLPixelFormatCapabilities(MTLPixelFormat p_format) {
+ getMTLPixelFormatDesc(p_format).mtlFmtCaps = kMTLFmtCapsNone;
+}
+
+// If the device supports the feature set, add additional capabilities to a MTLVertexFormat.
+void PixelFormats::addMTLVertexFormatCapabilities(id p_device,
+ MTLFeatureSet p_feature_set,
+ MTLVertexFormat p_format,
+ MTLFmtCaps p_caps) {
+ if ([p_device supportsFeatureSet:p_feature_set]) {
+ flags::set(getMTLVertexFormatDesc(p_format).mtlFmtCaps, p_caps);
+ }
+}
+
+void PixelFormats::modifyMTLFormatCapabilities() {
+ modifyMTLFormatCapabilities(device);
+}
+
+// If the supportsBCTextureCompression query is available, use it.
+bool supports_bc_texture_compression(id p_device) {
+#if (TARGET_OS_OSX || TARGET_OS_IOS && __IPHONE_OS_VERSION_MAX_ALLOWED >= 160400)
+ if (@available(macOS 11.0, iOS 16.4, *)) {
+ return p_device.supportsBCTextureCompression;
+ }
+#endif
+ return false;
+}
+
+#define addFeatSetMTLPixFmtCaps(FEAT_SET, MTL_FMT, CAPS) \
+ addMTLPixelFormatCapabilities(p_device, MTLFeatureSet_##FEAT_SET, MTLPixelFormat##MTL_FMT, kMTLFmtCaps##CAPS)
+
+#define addFeatSetMTLVtxFmtCaps(FEAT_SET, MTL_FMT, CAPS) \
+ addMTLVertexFormatCapabilities(p_device, MTLFeatureSet_##FEAT_SET, MTLVertexFormat##MTL_FMT, kMTLFmtCaps##CAPS)
+
+#define addGPUMTLPixFmtCaps(GPU_FAM, MTL_FMT, CAPS) \
+ addMTLPixelFormatCapabilities(p_device, MTLGPUFamily##GPU_FAM, MTLPixelFormat##MTL_FMT, kMTLFmtCaps##CAPS)
+
+#define disableAllMTLPixFmtCaps(MTL_FMT) \
+ disableAllMTLPixelFormatCapabilities(MTLPixelFormat##MTL_FMT)
+
+#define disableMTLPixFmtCaps(MTL_FMT, CAPS) \
+ disableMTLPixelFormatCapabilities(MTLPixelFormat##MTL_FMT, kMTLFmtCaps##CAPS)
+
+void PixelFormats::modifyMTLFormatCapabilities(id p_device) {
+ if (!supports_bc_texture_compression(p_device)) {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunguarded-availability"
+
+ disableAllMTLPixFmtCaps(BC1_RGBA);
+ disableAllMTLPixFmtCaps(BC1_RGBA_sRGB);
+ disableAllMTLPixFmtCaps(BC2_RGBA);
+ disableAllMTLPixFmtCaps(BC2_RGBA_sRGB);
+ disableAllMTLPixFmtCaps(BC3_RGBA);
+ disableAllMTLPixFmtCaps(BC3_RGBA_sRGB);
+ disableAllMTLPixFmtCaps(BC4_RUnorm);
+ disableAllMTLPixFmtCaps(BC4_RSnorm);
+ disableAllMTLPixFmtCaps(BC5_RGUnorm);
+ disableAllMTLPixFmtCaps(BC5_RGSnorm);
+ disableAllMTLPixFmtCaps(BC6H_RGBUfloat);
+ disableAllMTLPixFmtCaps(BC6H_RGBFloat);
+ disableAllMTLPixFmtCaps(BC7_RGBAUnorm);
+ disableAllMTLPixFmtCaps(BC7_RGBAUnorm_sRGB);
+
+#pragma clang diagnostic pop
+ }
+
+ if (!p_device.supports32BitMSAA) {
+ disableMTLPixFmtCaps(R32Uint, MSAA);
+ disableMTLPixFmtCaps(R32Uint, Resolve);
+ disableMTLPixFmtCaps(R32Sint, MSAA);
+ disableMTLPixFmtCaps(R32Sint, Resolve);
+ disableMTLPixFmtCaps(R32Float, MSAA);
+ disableMTLPixFmtCaps(R32Float, Resolve);
+ disableMTLPixFmtCaps(RG32Uint, MSAA);
+ disableMTLPixFmtCaps(RG32Uint, Resolve);
+ disableMTLPixFmtCaps(RG32Sint, MSAA);
+ disableMTLPixFmtCaps(RG32Sint, Resolve);
+ disableMTLPixFmtCaps(RG32Float, MSAA);
+ disableMTLPixFmtCaps(RG32Float, Resolve);
+ disableMTLPixFmtCaps(RGBA32Uint, MSAA);
+ disableMTLPixFmtCaps(RGBA32Uint, Resolve);
+ disableMTLPixFmtCaps(RGBA32Sint, MSAA);
+ disableMTLPixFmtCaps(RGBA32Sint, Resolve);
+ disableMTLPixFmtCaps(RGBA32Float, MSAA);
+ disableMTLPixFmtCaps(RGBA32Float, Resolve);
+ }
+
+ if (!p_device.supports32BitFloatFiltering) {
+ disableMTLPixFmtCaps(R32Float, Filter);
+ disableMTLPixFmtCaps(RG32Float, Filter);
+ disableMTLPixFmtCaps(RGBA32Float, Filter);
+ }
+
+#if TARGET_OS_OSX
+ addGPUMTLPixFmtCaps(Apple1, R32Uint, Atomic);
+ addGPUMTLPixFmtCaps(Apple1, R32Sint, Atomic);
+
+ if (p_device.isDepth24Stencil8PixelFormatSupported) {
+ addGPUMTLPixFmtCaps(Apple1, Depth24Unorm_Stencil8, DRFMR);
+ }
+
+ addFeatSetMTLPixFmtCaps(macOS_GPUFamily1_v2, Depth16Unorm, DRFMR);
+
+ addFeatSetMTLPixFmtCaps(macOS_GPUFamily1_v3, BGR10A2Unorm, RFCMRB);
+
+ addGPUMTLPixFmtCaps(Apple5, R8Unorm_sRGB, All);
+
+ addGPUMTLPixFmtCaps(Apple5, RG8Unorm_sRGB, All);
+
+ addGPUMTLPixFmtCaps(Apple5, B5G6R5Unorm, RFCMRB);
+ addGPUMTLPixFmtCaps(Apple5, A1BGR5Unorm, RFCMRB);
+ addGPUMTLPixFmtCaps(Apple5, ABGR4Unorm, RFCMRB);
+ addGPUMTLPixFmtCaps(Apple5, BGR5A1Unorm, RFCMRB);
+
+ addGPUMTLPixFmtCaps(Apple5, RGBA8Unorm_sRGB, All);
+ addGPUMTLPixFmtCaps(Apple5, BGRA8Unorm_sRGB, All);
+
+ // Blending is actually supported for this format, but format channels cannot be individually write-enabled during blending.
+ // Disabling blending is the least-intrusive way to handle this in a Godot-friendly way.
+ addGPUMTLPixFmtCaps(Apple5, RGB9E5Float, All);
+ disableMTLPixFmtCaps(RGB9E5Float, Blend);
+
+ addGPUMTLPixFmtCaps(Apple5, PVRTC_RGBA_2BPP, RF);
+ addGPUMTLPixFmtCaps(Apple5, PVRTC_RGBA_2BPP_sRGB, RF);
+ addGPUMTLPixFmtCaps(Apple5, PVRTC_RGBA_4BPP, RF);
+ addGPUMTLPixFmtCaps(Apple5, PVRTC_RGBA_4BPP_sRGB, RF);
+
+ addGPUMTLPixFmtCaps(Apple5, ETC2_RGB8, RF);
+ addGPUMTLPixFmtCaps(Apple5, ETC2_RGB8_sRGB, RF);
+ addGPUMTLPixFmtCaps(Apple5, ETC2_RGB8A1, RF);
+ addGPUMTLPixFmtCaps(Apple5, ETC2_RGB8A1_sRGB, RF);
+ addGPUMTLPixFmtCaps(Apple5, EAC_RGBA8, RF);
+ addGPUMTLPixFmtCaps(Apple5, EAC_RGBA8_sRGB, RF);
+ addGPUMTLPixFmtCaps(Apple5, EAC_R11Unorm, RF);
+ addGPUMTLPixFmtCaps(Apple5, EAC_R11Snorm, RF);
+ addGPUMTLPixFmtCaps(Apple5, EAC_RG11Unorm, RF);
+ addGPUMTLPixFmtCaps(Apple5, EAC_RG11Snorm, RF);
+
+ addGPUMTLPixFmtCaps(Apple5, ASTC_4x4_LDR, RF);
+ addGPUMTLPixFmtCaps(Apple5, ASTC_4x4_sRGB, RF);
+ addGPUMTLPixFmtCaps(Apple6, ASTC_4x4_HDR, RF);
+ addGPUMTLPixFmtCaps(Apple5, ASTC_5x4_LDR, RF);
+ addGPUMTLPixFmtCaps(Apple5, ASTC_5x4_sRGB, RF);
+ addGPUMTLPixFmtCaps(Apple6, ASTC_5x4_HDR, RF);
+ addGPUMTLPixFmtCaps(Apple5, ASTC_5x5_LDR, RF);
+ addGPUMTLPixFmtCaps(Apple5, ASTC_5x5_sRGB, RF);
+ addGPUMTLPixFmtCaps(Apple6, ASTC_5x5_HDR, RF);
+ addGPUMTLPixFmtCaps(Apple5, ASTC_6x5_LDR, RF);
+ addGPUMTLPixFmtCaps(Apple5, ASTC_6x5_sRGB, RF);
+ addGPUMTLPixFmtCaps(Apple6, ASTC_6x5_HDR, RF);
+ addGPUMTLPixFmtCaps(Apple5, ASTC_6x6_LDR, RF);
+ addGPUMTLPixFmtCaps(Apple5, ASTC_6x6_sRGB, RF);
+ addGPUMTLPixFmtCaps(Apple6, ASTC_6x6_HDR, RF);
+ addGPUMTLPixFmtCaps(Apple5, ASTC_8x5_LDR, RF);
+ addGPUMTLPixFmtCaps(Apple5, ASTC_8x5_sRGB, RF);
+ addGPUMTLPixFmtCaps(Apple6, ASTC_8x5_HDR, RF);
+ addGPUMTLPixFmtCaps(Apple5, ASTC_8x6_LDR, RF);
+ addGPUMTLPixFmtCaps(Apple5, ASTC_8x6_sRGB, RF);
+ addGPUMTLPixFmtCaps(Apple6, ASTC_8x6_HDR, RF);
+ addGPUMTLPixFmtCaps(Apple5, ASTC_8x8_LDR, RF);
+ addGPUMTLPixFmtCaps(Apple5, ASTC_8x8_sRGB, RF);
+ addGPUMTLPixFmtCaps(Apple6, ASTC_8x8_HDR, RF);
+ addGPUMTLPixFmtCaps(Apple5, ASTC_10x5_LDR, RF);
+ addGPUMTLPixFmtCaps(Apple5, ASTC_10x5_sRGB, RF);
+ addGPUMTLPixFmtCaps(Apple6, ASTC_10x5_HDR, RF);
+ addGPUMTLPixFmtCaps(Apple5, ASTC_10x6_LDR, RF);
+ addGPUMTLPixFmtCaps(Apple5, ASTC_10x6_sRGB, RF);
+ addGPUMTLPixFmtCaps(Apple6, ASTC_10x6_HDR, RF);
+ addGPUMTLPixFmtCaps(Apple5, ASTC_10x8_LDR, RF);
+ addGPUMTLPixFmtCaps(Apple5, ASTC_10x8_sRGB, RF);
+ addGPUMTLPixFmtCaps(Apple6, ASTC_10x8_HDR, RF);
+ addGPUMTLPixFmtCaps(Apple5, ASTC_10x10_LDR, RF);
+ addGPUMTLPixFmtCaps(Apple5, ASTC_10x10_sRGB, RF);
+ addGPUMTLPixFmtCaps(Apple6, ASTC_10x10_HDR, RF);
+ addGPUMTLPixFmtCaps(Apple5, ASTC_12x10_LDR, RF);
+ addGPUMTLPixFmtCaps(Apple5, ASTC_12x10_sRGB, RF);
+ addGPUMTLPixFmtCaps(Apple6, ASTC_12x10_HDR, RF);
+ addGPUMTLPixFmtCaps(Apple5, ASTC_12x12_LDR, RF);
+ addGPUMTLPixFmtCaps(Apple5, ASTC_12x12_sRGB, RF);
+ addGPUMTLPixFmtCaps(Apple6, ASTC_12x12_HDR, RF);
+
+ addGPUMTLPixFmtCaps(Apple5, BGRA10_XR, All);
+ addGPUMTLPixFmtCaps(Apple5, BGRA10_XR_sRGB, All);
+ addGPUMTLPixFmtCaps(Apple5, BGR10_XR, All);
+ addGPUMTLPixFmtCaps(Apple5, BGR10_XR_sRGB, All);
+
+ addFeatSetMTLVtxFmtCaps(macOS_GPUFamily1_v3, UCharNormalized, Vertex);
+ addFeatSetMTLVtxFmtCaps(macOS_GPUFamily1_v3, CharNormalized, Vertex);
+ addFeatSetMTLVtxFmtCaps(macOS_GPUFamily1_v3, UChar, Vertex);
+ addFeatSetMTLVtxFmtCaps(macOS_GPUFamily1_v3, Char, Vertex);
+ addFeatSetMTLVtxFmtCaps(macOS_GPUFamily1_v3, UShortNormalized, Vertex);
+ addFeatSetMTLVtxFmtCaps(macOS_GPUFamily1_v3, ShortNormalized, Vertex);
+ addFeatSetMTLVtxFmtCaps(macOS_GPUFamily1_v3, UShort, Vertex);
+ addFeatSetMTLVtxFmtCaps(macOS_GPUFamily1_v3, Short, Vertex);
+ addFeatSetMTLVtxFmtCaps(macOS_GPUFamily1_v3, Half, Vertex);
+ addFeatSetMTLVtxFmtCaps(macOS_GPUFamily1_v3, UChar4Normalized_BGRA, Vertex);
+#endif
+
+#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST
+ addFeatSetMTLPixFmtCaps(iOS_GPUFamily2_v3, R8Unorm_sRGB, All);
+ addFeatSetMTLPixFmtCaps(iOS_GPUFamily3_v1, R8Unorm_sRGB, All);
+
+ addFeatSetMTLPixFmtCaps(iOS_GPUFamily2_v1, R8Snorm, All);
+
+ addFeatSetMTLPixFmtCaps(iOS_GPUFamily2_v3, RG8Unorm_sRGB, All);
+ addFeatSetMTLPixFmtCaps(iOS_GPUFamily3_v1, RG8Unorm_sRGB, All);
+
+ addFeatSetMTLPixFmtCaps(iOS_GPUFamily2_v1, RG8Snorm, All);
+
+ addFeatSetMTLPixFmtCaps(iOS_GPUFamily1_v2, R32Uint, RWC);
+ addFeatSetMTLPixFmtCaps(iOS_GPUFamily1_v2, R32Uint, Atomic);
+ addFeatSetMTLPixFmtCaps(iOS_GPUFamily1_v2, R32Sint, RWC);
+ addFeatSetMTLPixFmtCaps(iOS_GPUFamily1_v2, R32Sint, Atomic);
+
+ addFeatSetMTLPixFmtCaps(iOS_GPUFamily1_v2, R32Float, RWCMB);
+
+ addFeatSetMTLPixFmtCaps(iOS_GPUFamily2_v3, RGBA8Unorm_sRGB, All);
+ addFeatSetMTLPixFmtCaps(iOS_GPUFamily3_v1, RGBA8Unorm_sRGB, All);
+
+ addFeatSetMTLPixFmtCaps(iOS_GPUFamily2_v1, RGBA8Snorm, All);
+
+ addFeatSetMTLPixFmtCaps(iOS_GPUFamily2_v3, BGRA8Unorm_sRGB, All);
+ addFeatSetMTLPixFmtCaps(iOS_GPUFamily3_v1, BGRA8Unorm_sRGB, All);
+
+ addFeatSetMTLPixFmtCaps(iOS_GPUFamily3_v1, RGB10A2Unorm, All);
+ addFeatSetMTLPixFmtCaps(iOS_GPUFamily3_v1, RGB10A2Uint, RWCM);
+ addFeatSetMTLPixFmtCaps(iOS_GPUFamily3_v1, RG11B10Float, All);
+ addFeatSetMTLPixFmtCaps(iOS_GPUFamily3_v1, RGB9E5Float, All);
+
+ addFeatSetMTLPixFmtCaps(iOS_GPUFamily1_v2, RG32Uint, RWC);
+ addFeatSetMTLPixFmtCaps(iOS_GPUFamily1_v2, RG32Sint, RWC);
+ addFeatSetMTLPixFmtCaps(iOS_GPUFamily1_v2, RG32Float, RWCB);
+
+ addFeatSetMTLPixFmtCaps(iOS_GPUFamily1_v2, RGBA32Uint, RWC);
+ addFeatSetMTLPixFmtCaps(iOS_GPUFamily1_v2, RGBA32Sint, RWC);
+ addFeatSetMTLPixFmtCaps(iOS_GPUFamily1_v2, RGBA32Float, RWC);
+
+ addFeatSetMTLPixFmtCaps(iOS_GPUFamily2_v1, ASTC_4x4_LDR, RF);
+ addFeatSetMTLPixFmtCaps(iOS_GPUFamily2_v1, ASTC_4x4_sRGB, RF);
+ addFeatSetMTLPixFmtCaps(iOS_GPUFamily2_v1, ASTC_5x4_LDR, RF);
+ addFeatSetMTLPixFmtCaps(iOS_GPUFamily2_v1, ASTC_5x4_sRGB, RF);
+ addFeatSetMTLPixFmtCaps(iOS_GPUFamily2_v1, ASTC_5x5_LDR, RF);
+ addFeatSetMTLPixFmtCaps(iOS_GPUFamily2_v1, ASTC_5x5_sRGB, RF);
+ addFeatSetMTLPixFmtCaps(iOS_GPUFamily2_v1, ASTC_6x5_LDR, RF);
+ addFeatSetMTLPixFmtCaps(iOS_GPUFamily2_v1, ASTC_6x5_sRGB, RF);
+ addFeatSetMTLPixFmtCaps(iOS_GPUFamily2_v1, ASTC_6x6_LDR, RF);
+ addFeatSetMTLPixFmtCaps(iOS_GPUFamily2_v1, ASTC_6x6_sRGB, RF);
+ addFeatSetMTLPixFmtCaps(iOS_GPUFamily2_v1, ASTC_8x5_LDR, RF);
+ addFeatSetMTLPixFmtCaps(iOS_GPUFamily2_v1, ASTC_8x5_sRGB, RF);
+ addFeatSetMTLPixFmtCaps(iOS_GPUFamily2_v1, ASTC_8x6_LDR, RF);
+ addFeatSetMTLPixFmtCaps(iOS_GPUFamily2_v1, ASTC_8x6_sRGB, RF);
+ addFeatSetMTLPixFmtCaps(iOS_GPUFamily2_v1, ASTC_8x8_LDR, RF);
+ addFeatSetMTLPixFmtCaps(iOS_GPUFamily2_v1, ASTC_8x8_sRGB, RF);
+ addFeatSetMTLPixFmtCaps(iOS_GPUFamily2_v1, ASTC_10x5_LDR, RF);
+ addFeatSetMTLPixFmtCaps(iOS_GPUFamily2_v1, ASTC_10x5_sRGB, RF);
+ addFeatSetMTLPixFmtCaps(iOS_GPUFamily2_v1, ASTC_10x6_LDR, RF);
+ addFeatSetMTLPixFmtCaps(iOS_GPUFamily2_v1, ASTC_10x6_sRGB, RF);
+ addFeatSetMTLPixFmtCaps(iOS_GPUFamily2_v1, ASTC_10x8_LDR, RF);
+ addFeatSetMTLPixFmtCaps(iOS_GPUFamily2_v1, ASTC_10x8_sRGB, RF);
+ addFeatSetMTLPixFmtCaps(iOS_GPUFamily2_v1, ASTC_10x10_LDR, RF);
+ addFeatSetMTLPixFmtCaps(iOS_GPUFamily2_v1, ASTC_10x10_sRGB, RF);
+ addFeatSetMTLPixFmtCaps(iOS_GPUFamily2_v1, ASTC_12x10_LDR, RF);
+ addFeatSetMTLPixFmtCaps(iOS_GPUFamily2_v1, ASTC_12x10_sRGB, RF);
+ addFeatSetMTLPixFmtCaps(iOS_GPUFamily2_v1, ASTC_12x12_LDR, RF);
+ addFeatSetMTLPixFmtCaps(iOS_GPUFamily2_v1, ASTC_12x12_sRGB, RF);
+
+ addFeatSetMTLPixFmtCaps(iOS_GPUFamily3_v1, Depth32Float, DRMR);
+ addFeatSetMTLPixFmtCaps(iOS_GPUFamily3_v1, Depth32Float_Stencil8, DRMR);
+ addFeatSetMTLPixFmtCaps(iOS_GPUFamily3_v1, Stencil8, DRMR);
+
+ addFeatSetMTLPixFmtCaps(iOS_GPUFamily3_v2, BGRA10_XR, All);
+ addFeatSetMTLPixFmtCaps(iOS_GPUFamily3_v2, BGRA10_XR_sRGB, All);
+ addFeatSetMTLPixFmtCaps(iOS_GPUFamily3_v2, BGR10_XR, All);
+ addFeatSetMTLPixFmtCaps(iOS_GPUFamily3_v2, BGR10_XR_sRGB, All);
+
+ addFeatSetMTLPixFmtCaps(iOS_GPUFamily1_v4, BGR10A2Unorm, All);
+
+ addGPUMTLPixFmtCaps(Apple6, ASTC_4x4_HDR, RF);
+ addGPUMTLPixFmtCaps(Apple6, ASTC_5x4_HDR, RF);
+ addGPUMTLPixFmtCaps(Apple6, ASTC_5x5_HDR, RF);
+ addGPUMTLPixFmtCaps(Apple6, ASTC_6x5_HDR, RF);
+ addGPUMTLPixFmtCaps(Apple6, ASTC_6x6_HDR, RF);
+ addGPUMTLPixFmtCaps(Apple6, ASTC_8x5_HDR, RF);
+ addGPUMTLPixFmtCaps(Apple6, ASTC_8x6_HDR, RF);
+ addGPUMTLPixFmtCaps(Apple6, ASTC_8x8_HDR, RF);
+ addGPUMTLPixFmtCaps(Apple6, ASTC_10x5_HDR, RF);
+ addGPUMTLPixFmtCaps(Apple6, ASTC_10x6_HDR, RF);
+ addGPUMTLPixFmtCaps(Apple6, ASTC_10x8_HDR, RF);
+ addGPUMTLPixFmtCaps(Apple6, ASTC_10x10_HDR, RF);
+ addGPUMTLPixFmtCaps(Apple6, ASTC_12x10_HDR, RF);
+ addGPUMTLPixFmtCaps(Apple6, ASTC_12x12_HDR, RF);
+
+ addGPUMTLPixFmtCaps(Apple1, Depth16Unorm, DRFM);
+ addGPUMTLPixFmtCaps(Apple3, Depth16Unorm, DRFMR);
+
+ // Vertex formats.
+ addFeatSetMTLVtxFmtCaps(iOS_GPUFamily1_v4, UCharNormalized, Vertex);
+ addFeatSetMTLVtxFmtCaps(iOS_GPUFamily1_v4, CharNormalized, Vertex);
+ addFeatSetMTLVtxFmtCaps(iOS_GPUFamily1_v4, UChar, Vertex);
+ addFeatSetMTLVtxFmtCaps(iOS_GPUFamily1_v4, Char, Vertex);
+ addFeatSetMTLVtxFmtCaps(iOS_GPUFamily1_v4, UShortNormalized, Vertex);
+ addFeatSetMTLVtxFmtCaps(iOS_GPUFamily1_v4, ShortNormalized, Vertex);
+ addFeatSetMTLVtxFmtCaps(iOS_GPUFamily1_v4, UShort, Vertex);
+ addFeatSetMTLVtxFmtCaps(iOS_GPUFamily1_v4, Short, Vertex);
+ addFeatSetMTLVtxFmtCaps(iOS_GPUFamily1_v4, Half, Vertex);
+ addFeatSetMTLVtxFmtCaps(iOS_GPUFamily1_v4, UChar4Normalized_BGRA, Vertex);
+
+// Disable for iOS simulator last.
+#if TARGET_OS_SIMULATOR
+ if (![mtlDevice supportsFamily:MTLGPUFamilyApple5]) {
+ disableAllMTLPixFmtCaps(R8Unorm_sRGB);
+ disableAllMTLPixFmtCaps(RG8Unorm_sRGB);
+ disableAllMTLPixFmtCaps(B5G6R5Unorm);
+ disableAllMTLPixFmtCaps(A1BGR5Unorm);
+ disableAllMTLPixFmtCaps(ABGR4Unorm);
+ disableAllMTLPixFmtCaps(BGR5A1Unorm);
+
+ disableAllMTLPixFmtCaps(BGRA10_XR);
+ disableAllMTLPixFmtCaps(BGRA10_XR_sRGB);
+ disableAllMTLPixFmtCaps(BGR10_XR);
+ disableAllMTLPixFmtCaps(BGR10_XR_sRGB);
+
+ disableAllMTLPixFmtCaps(GBGR422);
+ disableAllMTLPixFmtCaps(BGRG422);
+
+ disableMTLPixFmtCaps(RGB9E5Float, ColorAtt);
+
+ disableMTLPixFmtCaps(R8Unorm_sRGB, Write);
+ disableMTLPixFmtCaps(RG8Unorm_sRGB, Write);
+ disableMTLPixFmtCaps(RGBA8Unorm_sRGB, Write);
+ disableMTLPixFmtCaps(BGRA8Unorm_sRGB, Write);
+ disableMTLPixFmtCaps(PVRTC_RGBA_2BPP_sRGB, Write);
+ disableMTLPixFmtCaps(PVRTC_RGBA_4BPP_sRGB, Write);
+ disableMTLPixFmtCaps(ETC2_RGB8_sRGB, Write);
+ disableMTLPixFmtCaps(ETC2_RGB8A1_sRGB, Write);
+ disableMTLPixFmtCaps(EAC_RGBA8_sRGB, Write);
+ disableMTLPixFmtCaps(ASTC_4x4_sRGB, Write);
+ disableMTLPixFmtCaps(ASTC_5x4_sRGB, Write);
+ disableMTLPixFmtCaps(ASTC_5x5_sRGB, Write);
+ disableMTLPixFmtCaps(ASTC_6x5_sRGB, Write);
+ disableMTLPixFmtCaps(ASTC_6x6_sRGB, Write);
+ disableMTLPixFmtCaps(ASTC_8x5_sRGB, Write);
+ disableMTLPixFmtCaps(ASTC_8x6_sRGB, Write);
+ disableMTLPixFmtCaps(ASTC_8x8_sRGB, Write);
+ disableMTLPixFmtCaps(ASTC_10x5_sRGB, Write);
+ disableMTLPixFmtCaps(ASTC_10x6_sRGB, Write);
+ disableMTLPixFmtCaps(ASTC_10x8_sRGB, Write);
+ disableMTLPixFmtCaps(ASTC_10x10_sRGB, Write);
+ disableMTLPixFmtCaps(ASTC_12x10_sRGB, Write);
+ disableMTLPixFmtCaps(ASTC_12x12_sRGB, Write);
+ }
+#endif
+#endif
+}
+
+#undef addFeatSetMTLPixFmtCaps
+#undef addGPUOSMTLPixFmtCaps
+#undef disableMTLPixFmtCaps
+#undef disableAllMTLPixFmtCaps
+#undef addFeatSetMTLVtxFmtCaps
+
+// Populates the DataFormat lookup maps and connects Godot and Metal pixel formats to one-another.
+void PixelFormats::buildDFFormatMaps() {
+ // Iterate through the DataFormat descriptions, populate the lookup maps and back pointers,
+ // and validate the Metal formats for the platform and OS.
+ for (uint32_t fmtIdx = 0; fmtIdx < RD::DATA_FORMAT_MAX; fmtIdx++) {
+ DataFormatDesc &dfDesc = _dataFormatDescriptions[fmtIdx];
+ DataFormat dfFmt = dfDesc.dataFormat;
+ if (dfFmt != RD::DATA_FORMAT_MAX) {
+ // Populate the back reference from the Metal formats to the Godot format.
+ // Validate the corresponding Metal formats for the platform, and clear them
+ // in the Godot format if not supported.
+ if (dfDesc.mtlPixelFormat) {
+ MTLFormatDesc &mtlDesc = getMTLPixelFormatDesc(dfDesc.mtlPixelFormat);
+ if (mtlDesc.dataFormat == RD::DATA_FORMAT_MAX) {
+ mtlDesc.dataFormat = dfFmt;
+ }
+ if (!mtlDesc.isSupported()) {
+ dfDesc.mtlPixelFormat = MTLPixelFormatInvalid;
+ }
+ }
+ if (dfDesc.mtlPixelFormatSubstitute) {
+ MTLFormatDesc &mtlDesc = getMTLPixelFormatDesc(dfDesc.mtlPixelFormatSubstitute);
+ if (!mtlDesc.isSupported()) {
+ dfDesc.mtlPixelFormatSubstitute = MTLPixelFormatInvalid;
+ }
+ }
+ if (dfDesc.mtlVertexFormat) {
+ MTLFormatDesc &mtlDesc = getMTLVertexFormatDesc(dfDesc.mtlVertexFormat);
+ if (mtlDesc.dataFormat == RD::DATA_FORMAT_MAX) {
+ mtlDesc.dataFormat = dfFmt;
+ }
+ if (!mtlDesc.isSupported()) {
+ dfDesc.mtlVertexFormat = MTLVertexFormatInvalid;
+ }
+ }
+ if (dfDesc.mtlVertexFormatSubstitute) {
+ MTLFormatDesc &mtlDesc = getMTLVertexFormatDesc(dfDesc.mtlVertexFormatSubstitute);
+ if (!mtlDesc.isSupported()) {
+ dfDesc.mtlVertexFormatSubstitute = MTLVertexFormatInvalid;
+ }
+ }
+ }
+ }
+}
diff --git a/drivers/metal/rendering_context_driver_metal.h b/drivers/metal/rendering_context_driver_metal.h
new file mode 100644
index 00000000000..0363ab111a2
--- /dev/null
+++ b/drivers/metal/rendering_context_driver_metal.h
@@ -0,0 +1,206 @@
+/**************************************************************************/
+/* rendering_context_driver_metal.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef RENDERING_CONTEXT_DRIVER_METAL_H
+#define RENDERING_CONTEXT_DRIVER_METAL_H
+
+#ifdef METAL_ENABLED
+
+#import "rendering_device_driver_metal.h"
+
+#import "servers/rendering/rendering_context_driver.h"
+
+#import
+#import
+#import
+
+@class CAMetalLayer;
+@protocol CAMetalDrawable;
+class PixelFormats;
+class MDResourceCache;
+
+class API_AVAILABLE(macos(11.0), ios(14.0)) RenderingContextDriverMetal : public RenderingContextDriver {
+protected:
+ id metal_device = nil;
+ Device device; // There is only one device on Apple Silicon.
+
+public:
+ Error initialize() final override;
+ const Device &device_get(uint32_t p_device_index) const final override;
+ uint32_t device_get_count() const final override;
+ bool device_supports_present(uint32_t p_device_index, SurfaceID p_surface) const final override { return true; }
+ RenderingDeviceDriver *driver_create() final override;
+ void driver_free(RenderingDeviceDriver *p_driver) final override;
+ SurfaceID surface_create(const void *p_platform_data) final override;
+ void surface_set_size(SurfaceID p_surface, uint32_t p_width, uint32_t p_height) final override;
+ void surface_set_vsync_mode(SurfaceID p_surface, DisplayServer::VSyncMode p_vsync_mode) final override;
+ DisplayServer::VSyncMode surface_get_vsync_mode(SurfaceID p_surface) const final override;
+ uint32_t surface_get_width(SurfaceID p_surface) const final override;
+ uint32_t surface_get_height(SurfaceID p_surface) const final override;
+ void surface_set_needs_resize(SurfaceID p_surface, bool p_needs_resize) final override;
+ bool surface_get_needs_resize(SurfaceID p_surface) const final override;
+ void surface_destroy(SurfaceID p_surface) final override;
+ bool is_debug_utils_enabled() const final override { return true; }
+
+#pragma mark - Metal-specific methods
+
+ // Platform-specific data for the Windows embedded in this driver.
+ struct WindowPlatformData {
+ CAMetalLayer *__unsafe_unretained layer;
+ };
+
+ class Surface {
+ protected:
+ id device;
+
+ public:
+ uint32_t width = 0;
+ uint32_t height = 0;
+ DisplayServer::VSyncMode vsync_mode = DisplayServer::VSYNC_ENABLED;
+ bool needs_resize = false;
+
+ Surface(id p_device) :
+ device(p_device) {}
+ virtual ~Surface() = default;
+
+ MTLPixelFormat get_pixel_format() const { return MTLPixelFormatBGRA8Unorm; }
+ virtual Error resize(uint32_t p_desired_framebuffer_count) = 0;
+ virtual RDD::FramebufferID acquire_next_frame_buffer() = 0;
+ virtual void present(MDCommandBuffer *p_cmd_buffer) = 0;
+ };
+
+ class SurfaceLayer : public Surface {
+ CAMetalLayer *__unsafe_unretained layer = nil;
+ LocalVector frame_buffers;
+ LocalVector> drawables;
+ uint32_t rear = -1;
+ uint32_t front = 0;
+ uint32_t count = 0;
+
+ public:
+ SurfaceLayer(CAMetalLayer *p_layer, id p_device) :
+ Surface(p_device), layer(p_layer) {
+ layer.allowsNextDrawableTimeout = YES;
+ layer.framebufferOnly = YES;
+ layer.opaque = OS::get_singleton()->is_layered_allowed() ? NO : YES;
+ layer.pixelFormat = get_pixel_format();
+ layer.device = p_device;
+ }
+
+ ~SurfaceLayer() override {
+ layer = nil;
+ }
+
+ Error resize(uint32_t p_desired_framebuffer_count) override final {
+ if (width == 0 || height == 0) {
+ // Very likely the window is minimized, don't create a swap chain.
+ return ERR_SKIP;
+ }
+
+ CGSize drawableSize = CGSizeMake(width, height);
+ CGSize current = layer.drawableSize;
+ if (!CGSizeEqualToSize(current, drawableSize)) {
+ layer.drawableSize = drawableSize;
+ }
+
+ // Metal supports a maximum of 3 drawables.
+ p_desired_framebuffer_count = MIN(3U, p_desired_framebuffer_count);
+ layer.maximumDrawableCount = p_desired_framebuffer_count;
+
+#if TARGET_OS_OSX
+ // Display sync is only supported on macOS.
+ switch (vsync_mode) {
+ case DisplayServer::VSYNC_MAILBOX:
+ case DisplayServer::VSYNC_ADAPTIVE:
+ case DisplayServer::VSYNC_ENABLED:
+ layer.displaySyncEnabled = YES;
+ break;
+ case DisplayServer::VSYNC_DISABLED:
+ layer.displaySyncEnabled = NO;
+ break;
+ }
+#endif
+ drawables.resize(p_desired_framebuffer_count);
+ frame_buffers.resize(p_desired_framebuffer_count);
+ for (uint32_t i = 0; i < p_desired_framebuffer_count; i++) {
+ // Reserve space for the drawable texture.
+ frame_buffers[i].textures.resize(1);
+ }
+
+ return OK;
+ }
+
+ RDD::FramebufferID acquire_next_frame_buffer() override final {
+ if (count == frame_buffers.size()) {
+ return RDD::FramebufferID();
+ }
+
+ rear = (rear + 1) % frame_buffers.size();
+ count++;
+
+ MDFrameBuffer &frame_buffer = frame_buffers[rear];
+ frame_buffer.size = Size2i(width, height);
+
+ id drawable = layer.nextDrawable;
+ ERR_FAIL_NULL_V_MSG(drawable, RDD::FramebufferID(), "no drawable available");
+ drawables[rear] = drawable;
+ frame_buffer.textures.write[0] = drawable.texture;
+
+ return RDD::FramebufferID(&frame_buffer);
+ }
+
+ void present(MDCommandBuffer *p_cmd_buffer) override final {
+ if (count == 0) {
+ return;
+ }
+
+ // Release texture and drawable.
+ frame_buffers[front].textures.write[0] = nil;
+ id drawable = drawables[front];
+ drawables[front] = nil;
+
+ count--;
+ front = (front + 1) % frame_buffers.size();
+
+ [p_cmd_buffer->get_command_buffer() presentDrawable:drawable];
+ }
+ };
+
+ id