Add Metal support for macOS (arm64) and iOS

This commit is contained in:
Stuart Carnie
2024-02-20 05:52:00 +11:00
committed by Rémi Verschelde
parent 826de7976a
commit 2d0165574d
72 changed files with 67923 additions and 28 deletions

39
drivers/metal/README.md Normal file
View File

@ -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

49
drivers/metal/SCsub Normal file
View File

@ -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)

View File

@ -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 <Foundation/Foundation.h>
#import <Metal/Metal.h>
/** 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<RDD::ShaderStage> subgroupSupportedShaderStages;
BitField<RD::SubgroupOperations> subgroupSupportedOperations; /**< The subgroup operations supported by the device. */
};
class API_AVAILABLE(macos(11.0), ios(14.0)) MetalDeviceProperties {
private:
void init_features(id<MTLDevice> p_device);
void init_limits(id<MTLDevice> p_device);
public:
MetalFeatures features;
MetalLimits limits;
SampleCount find_nearest_supported_sample_count(RenderingDevice::TextureSamples p_samples) const;
MetalDeviceProperties(id<MTLDevice> p_device);
~MetalDeviceProperties();
private:
static const SampleCount sample_count[RenderingDevice::TextureSamples::TEXTURE_SAMPLES_MAX];
};
#endif // METAL_DEVICE_PROPERTIES_H

View File

@ -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 <Metal/Metal.h>
#import <spirv_cross.hpp>
#import <spirv_msl.hpp>
// 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<MTLGPUFamily>(static_cast<int>(p_family) - 1);
if (p_family < MTLGPUFamilyApple1) {
p_family = MTLGPUFamilyApple9;
}
return p_family;
}
void MetalDeviceProperties::init_features(id<MTLDevice> 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<MTLDevice> 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<uint32_t>::max(), std::numeric_limits<uint32_t>::max(), std::numeric_limits<uint32_t>::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<uint64_t>::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<uint64_t>::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<uint32_t>::max() - 1;
}
MetalDeviceProperties::MetalDeviceProperties(id<MTLDevice> 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

View File

@ -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 <Foundation/Foundation.h>
#import <Metal/Metal.h>
#import <QuartzCore/CAMetalLayer.h>
#import <simd/simd.h>
#import <initializer_list>
#import <optional>
#import <spirv.hpp>
// 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<MTL##name> obj = nil) : m_obj(obj) {} \
operator id<MTL##name>() const { return m_obj; } \
id<MTL##name> 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<MTLFunction> new_func(NSString *p_source, NSString *p_name, NSError **p_error);
id<MTLFunction> new_clear_vert_func(ClearAttKey &p_key);
id<MTLFunction> new_clear_frag_func(ClearAttKey &p_key);
NSString *get_format_type_string(MTLPixelFormat p_fmt);
public:
id<MTLRenderPipelineState> new_clear_pipeline_state(ClearAttKey &p_key, NSError **p_error);
id<MTLDepthStencilState> 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<ClearAttKey, id<MTLRenderPipelineState>, HashableHasher<ClearAttKey>> HashMap;
std::unique_ptr<MDResourceFactory> resource_factory;
HashMap clear_states;
struct {
id<MTLDepthStencilState> all;
id<MTLDepthStencilState> depth_only;
id<MTLDepthStencilState> stencil_only;
id<MTLDepthStencilState> none;
} clear_depth_stencil_state;
public:
id<MTLRenderPipelineState> get_clear_render_pipeline_state(ClearAttKey &p_key, NSError **p_error);
id<MTLDepthStencilState> 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<MTLCommandQueue> queue = nil;
id<MTLCommandBuffer> 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<Rect2i> 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<RDD::RenderPassClearValue> clear_values;
LocalVector<MTLViewport> viewports;
LocalVector<MTLScissorRect> scissors;
std::optional<Color> blend_constants;
uint32_t current_subpass = UINT32_MAX;
Rect2i render_area = {};
bool is_rendering_entire_area = false;
MTLRenderPassDescriptor *desc = nil;
id<MTLRenderCommandEncoder> encoder = nil;
id<MTLBuffer> __unsafe_unretained index_buffer = nil; // Buffer is owned by RDD.
MTLIndexType index_type = MTLIndexTypeUInt16;
LocalVector<id<MTLBuffer> __unsafe_unretained> vertex_buffers;
LocalVector<NSUInteger> 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<DirtyFlag> dirty = DIRTY_NONE;
LocalVector<MDUniformSet *> 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<uint32_t> 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<MTLComputeCommandEncoder> encoder = nil;
_FORCE_INLINE_ void reset() {
pipeline = nil;
encoder = nil;
}
} compute;
// State specific to a blit pass.
struct {
id<MTLBlitCommandEncoder> encoder = nil;
_FORCE_INLINE_ void reset() {
encoder = nil;
}
} blit;
_FORCE_INLINE_ id<MTLCommandBuffer> get_command_buffer() const {
return commandBuffer;
}
void begin();
void commit();
void end();
id<MTLBlitCommandEncoder> 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<RDD::AttachmentClear> p_attachment_clears, VectorView<Rect2i> p_rects);
void render_set_viewport(VectorView<Rect2i> p_viewports);
void render_set_scissor(VectorView<Rect2i> 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<RDD::RenderPassClearValue> 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<MTLCommandQueue> 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 <typename W>
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 <typename R>
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<RDC::ShaderStage, BindingInfo> 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<UniformInfo> uniforms;
uint32_t buffer_size = 0;
HashMap<RDC::ShaderStage, uint32_t> offsets;
HashMap<RDC::ShaderStage, id<MTLArgumentEncoder>> encoders;
};
class API_AVAILABLE(macos(11.0), ios(14.0)) MDShader {
public:
CharString name;
Vector<UniformSet> sets;
virtual void encode_push_constant_data(VectorView<uint32_t> p_data, MDCommandBuffer *p_cb) = 0;
MDShader(CharString p_name, Vector<UniformSet> 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<MTLLibrary> kernel;
#if DEV_ENABLED
CharString kernel_source;
#endif
void encode_push_constant_data(VectorView<uint32_t> p_data, MDCommandBuffer *p_cb) final;
MDComputeShader(CharString p_name, Vector<UniformSet> p_sets, id<MTLLibrary> 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<MTLLibrary> vert;
id<MTLLibrary> frag;
#if DEV_ENABLED
CharString vert_source;
CharString frag_source;
#endif
void encode_push_constant_data(VectorView<uint32_t> p_data, MDCommandBuffer *p_cb) final;
MDRenderShader(CharString p_name, Vector<UniformSet> p_sets, id<MTLLibrary> p_vert, id<MTLLibrary> 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<RDD::ShaderID> {
static bool compare(const RDD::ShaderID &p_lhs, const RDD::ShaderID &p_rhs) {
return p_lhs.id == p_rhs.id;
}
};
struct BoundUniformSet {
id<MTLBuffer> buffer;
HashMap<id<MTLResource>, StageResourceUsage> bound_resources;
};
class API_AVAILABLE(macos(11.0), ios(14.0)) MDUniformSet {
public:
uint32_t index;
LocalVector<RDD::BoundUniform> uniforms;
HashMap<MDShader *, BoundUniformSet> bound_uniforms;
BoundUniformSet &boundUniformSetForShader(MDShader *p_shader, id<MTLDevice> 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<RDD::AttachmentReference> input_references;
LocalVector<RDD::AttachmentReference> color_references;
RDD::AttachmentReference depth_stencil_reference;
LocalVector<RDD::AttachmentReference> 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<MTLTexture> 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<MDAttachment> attachments;
Vector<MDSubpass> subpasses;
uint32_t get_sample_count() const {
return attachments.is_empty() ? 1 : attachments[0].samples;
}
MDRenderPass(Vector<MDAttachment> &p_attachments, Vector<MDSubpass> &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<MTLRenderPipelineState> state = nil;
id<MTLDepthStencilState> 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<MTLRenderCommandEncoder> __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<MTLRenderCommandEncoder> __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<MTLRenderCommandEncoder> __unsafe_unretained p_enc) const {
//if (!enabled)
// return;
[p_enc setBlendColorRed:r green:g blue:b alpha:a];
};
} blend;
_FORCE_INLINE_ void apply(id<MTLRenderCommandEncoder> __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<MTLComputePipelineState> state = nil;
struct {
MTLSize local = {};
} compute_state;
MDComputeShader *shader = nil;
explicit MDComputePipeline(id<MTLComputePipelineState> p_state) :
MDPipeline(MDPipelineType::Compute), state(p_state) {}
~MDComputePipeline() final = default;
};
class API_AVAILABLE(macos(11.0), ios(14.0)) MDFrameBuffer {
public:
Vector<MTL::Texture> textures;
Size2i size;
MDFrameBuffer(Vector<MTL::Texture> 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<MTLTexture>, RDD::TextureID)
MAKE_ID(id<MTLBuffer>, RDD::BufferID)
MAKE_ID(id<MTLSamplerState>, RDD::SamplerID)
MAKE_ID(MTLVertexDescriptor *, RDD::VertexFormatID)
MAKE_ID(id<MTLCommandQueue>, 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

File diff suppressed because it is too large Load Diff

View File

@ -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 <typename Tv, typename Tm>
void set(Tv &p_value, Tm p_mask) {
using T = std::underlying_type_t<Tv>;
p_value = static_cast<Tv>(static_cast<T>(p_value) | static_cast<T>(p_mask));
}
/*! Clears the flags within the value parameter specified by the mask parameter. */
template <typename Tv, typename Tm>
void clear(Tv &p_value, Tm p_mask) {
using T = std::underlying_type_t<Tv>;
p_value = static_cast<Tv>(static_cast<T>(p_value) & ~static_cast<T>(p_mask));
}
/*! Returns whether the specified value has any of the bits specified in mask set to 1. */
template <typename Tv, typename Tm>
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 <typename Tv, typename Tm>
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

View File

@ -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 <Metal/Metal.h>
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<MTLFmtCaps>(static_cast<uint32_t>(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<MTLDevice> p_device);
protected:
id<MTLDevice> 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<MTLDevice> p_device);
void addMTLPixelFormatCapabilities(id<MTLDevice> p_device,
MTLFeatureSet p_feature_set,
MTLPixelFormat p_format,
MTLFmtCaps p_caps);
void addMTLPixelFormatCapabilities(id<MTLDevice> 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<MTLDevice> 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<uint32_t, uint32_t> _mtlFormatDescIndicesByMTLPixelFormatsExt;
uint16_t _mtlFormatDescIndicesByMTLVertexFormats[_mtlVertexFormatCount];
};
#pragma clang diagnostic pop
#endif // PIXEL_FORMATS_H

File diff suppressed because it is too large Load Diff

View File

@ -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 <CoreGraphics/CGGeometry.h>
#import <Metal/Metal.h>
#import <QuartzCore/CALayer.h>
@class CAMetalLayer;
@protocol CAMetalDrawable;
class PixelFormats;
class MDResourceCache;
class API_AVAILABLE(macos(11.0), ios(14.0)) RenderingContextDriverMetal : public RenderingContextDriver {
protected:
id<MTLDevice> 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<MTLDevice> device;
public:
uint32_t width = 0;
uint32_t height = 0;
DisplayServer::VSyncMode vsync_mode = DisplayServer::VSYNC_ENABLED;
bool needs_resize = false;
Surface(id<MTLDevice> 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<MDFrameBuffer> frame_buffers;
LocalVector<id<MTLDrawable>> drawables;
uint32_t rear = -1;
uint32_t front = 0;
uint32_t count = 0;
public:
SurfaceLayer(CAMetalLayer *p_layer, id<MTLDevice> 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<CAMetalDrawable> 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<MTLDrawable> drawable = drawables[front];
drawables[front] = nil;
count--;
front = (front + 1) % frame_buffers.size();
[p_cmd_buffer->get_command_buffer() presentDrawable:drawable];
}
};
id<MTLDevice> get_metal_device() const { return metal_device; }
#pragma mark - Initialization
RenderingContextDriverMetal();
~RenderingContextDriverMetal() override;
};
#endif // METAL_ENABLED
#endif // RENDERING_CONTEXT_DRIVER_METAL_H

View File

@ -0,0 +1,134 @@
/**************************************************************************/
/* rendering_context_driver_metal.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. */
/**************************************************************************/
#import "rendering_context_driver_metal.h"
@protocol MTLDeviceEx <MTLDevice>
#if TARGET_OS_OSX && __MAC_OS_X_VERSION_MAX_ALLOWED < 130300
- (void)setShouldMaximizeConcurrentCompilation:(BOOL)v;
#endif
@end
RenderingContextDriverMetal::RenderingContextDriverMetal() {
}
RenderingContextDriverMetal::~RenderingContextDriverMetal() {
}
Error RenderingContextDriverMetal::initialize() {
metal_device = MTLCreateSystemDefaultDevice();
#if TARGET_OS_OSX
if (@available(macOS 13.3, *)) {
[id<MTLDeviceEx>(metal_device) setShouldMaximizeConcurrentCompilation:YES];
}
#endif
device.type = DEVICE_TYPE_INTEGRATED_GPU;
device.vendor = VENDOR_APPLE;
device.workarounds = Workarounds();
MetalDeviceProperties props(metal_device);
int version = (int)props.features.highestFamily - (int)MTLGPUFamilyApple1 + 1;
device.name = vformat("%s (Apple%d)", metal_device.name.UTF8String, version);
return OK;
}
const RenderingContextDriver::Device &RenderingContextDriverMetal::device_get(uint32_t p_device_index) const {
DEV_ASSERT(p_device_index < 1);
return device;
}
uint32_t RenderingContextDriverMetal::device_get_count() const {
return 1;
}
RenderingDeviceDriver *RenderingContextDriverMetal::driver_create() {
return memnew(RenderingDeviceDriverMetal(this));
}
void RenderingContextDriverMetal::driver_free(RenderingDeviceDriver *p_driver) {
memdelete(p_driver);
}
RenderingContextDriver::SurfaceID RenderingContextDriverMetal::surface_create(const void *p_platform_data) {
const WindowPlatformData *wpd = (const WindowPlatformData *)(p_platform_data);
Surface *surface = memnew(SurfaceLayer(wpd->layer, metal_device));
return SurfaceID(surface);
}
void RenderingContextDriverMetal::surface_set_size(SurfaceID p_surface, uint32_t p_width, uint32_t p_height) {
Surface *surface = (Surface *)(p_surface);
if (surface->width == p_width && surface->height == p_height) {
return;
}
surface->width = p_width;
surface->height = p_height;
surface->needs_resize = true;
}
void RenderingContextDriverMetal::surface_set_vsync_mode(SurfaceID p_surface, DisplayServer::VSyncMode p_vsync_mode) {
Surface *surface = (Surface *)(p_surface);
if (surface->vsync_mode == p_vsync_mode) {
return;
}
surface->vsync_mode = p_vsync_mode;
surface->needs_resize = true;
}
DisplayServer::VSyncMode RenderingContextDriverMetal::surface_get_vsync_mode(SurfaceID p_surface) const {
Surface *surface = (Surface *)(p_surface);
return surface->vsync_mode;
}
uint32_t RenderingContextDriverMetal::surface_get_width(SurfaceID p_surface) const {
Surface *surface = (Surface *)(p_surface);
return surface->width;
}
uint32_t RenderingContextDriverMetal::surface_get_height(SurfaceID p_surface) const {
Surface *surface = (Surface *)(p_surface);
return surface->height;
}
void RenderingContextDriverMetal::surface_set_needs_resize(SurfaceID p_surface, bool p_needs_resize) {
Surface *surface = (Surface *)(p_surface);
surface->needs_resize = p_needs_resize;
}
bool RenderingContextDriverMetal::surface_get_needs_resize(SurfaceID p_surface) const {
Surface *surface = (Surface *)(p_surface);
return surface->needs_resize;
}
void RenderingContextDriverMetal::surface_destroy(SurfaceID p_surface) {
Surface *surface = (Surface *)(p_surface);
memdelete(surface);
}

View File

@ -0,0 +1,417 @@
/**************************************************************************/
/* rendering_device_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_DEVICE_DRIVER_METAL_H
#define RENDERING_DEVICE_DRIVER_METAL_H
#import "metal_objects.h"
#import "servers/rendering/rendering_device_driver.h"
#import <Metal/Metal.h>
#import <spirv.hpp>
#import <variant>
#ifdef DEBUG_ENABLED
#ifndef _DEBUG
#define _DEBUG
#endif
#endif
class RenderingContextDriverMetal;
class API_AVAILABLE(macos(11.0), ios(14.0)) RenderingDeviceDriverMetal : public RenderingDeviceDriver {
template <typename T>
using Result = std::variant<T, Error>;
#pragma mark - Generic
RenderingContextDriverMetal *context_driver = nullptr;
RenderingContextDriver::Device context_device;
id<MTLDevice> device = nil;
uint32_t version_major = 2;
uint32_t version_minor = 0;
MetalDeviceProperties *metal_device_properties = nullptr;
PixelFormats *pixel_formats = nullptr;
std::unique_ptr<MDResourceCache> resource_cache;
RDD::Capabilities capabilities;
RDD::MultiviewCapabilities multiview_capabilities;
id<MTLBinaryArchive> archive = nil;
uint32_t archive_count = 0;
id<MTLCommandQueue> device_queue = nil;
id<MTLCaptureScope> device_scope = nil;
String pipeline_cache_id;
Error _create_device();
Error _check_capabilities();
public:
Error initialize(uint32_t p_device_index, uint32_t p_frame_count) override final;
#pragma mark - Memory
#pragma mark - Buffers
public:
virtual BufferID buffer_create(uint64_t p_size, BitField<BufferUsageBits> p_usage, MemoryAllocationType p_allocation_type) override final;
virtual bool buffer_set_texel_format(BufferID p_buffer, DataFormat p_format) override final;
virtual void buffer_free(BufferID p_buffer) override final;
virtual uint64_t buffer_get_allocation_size(BufferID p_buffer) override final;
virtual uint8_t *buffer_map(BufferID p_buffer) override final;
virtual void buffer_unmap(BufferID p_buffer) override final;
#pragma mark - Texture
private:
// Returns true if the texture is a valid linear format.
Result<bool> is_valid_linear(TextureFormat const &p_format) const;
void _get_sub_resource(TextureID p_texture, const TextureSubresource &p_subresource, TextureCopyableLayout *r_layout) const;
public:
virtual TextureID texture_create(const TextureFormat &p_format, const TextureView &p_view) override final;
virtual TextureID texture_create_from_extension(uint64_t p_native_texture, TextureType p_type, DataFormat p_format, uint32_t p_array_layers, bool p_depth_stencil) override final;
virtual TextureID texture_create_shared(TextureID p_original_texture, const TextureView &p_view) override final;
virtual TextureID texture_create_shared_from_slice(TextureID p_original_texture, const TextureView &p_view, TextureSliceType p_slice_type, uint32_t p_layer, uint32_t p_layers, uint32_t p_mipmap, uint32_t p_mipmaps) override final;
virtual void texture_free(TextureID p_texture) override final;
virtual uint64_t texture_get_allocation_size(TextureID p_texture) override final;
virtual void texture_get_copyable_layout(TextureID p_texture, const TextureSubresource &p_subresource, TextureCopyableLayout *r_layout) override final;
virtual uint8_t *texture_map(TextureID p_texture, const TextureSubresource &p_subresource) override final;
virtual void texture_unmap(TextureID p_texture) override final;
virtual BitField<TextureUsageBits> texture_get_usages_supported_by_format(DataFormat p_format, bool p_cpu_readable) override final;
virtual bool texture_can_make_shared_with_format(TextureID p_texture, DataFormat p_format, bool &r_raw_reinterpretation) override final;
#pragma mark - Sampler
public:
virtual SamplerID sampler_create(const SamplerState &p_state) final override;
virtual void sampler_free(SamplerID p_sampler) final override;
virtual bool sampler_is_format_supported_for_filter(DataFormat p_format, SamplerFilter p_filter) override final;
#pragma mark - Vertex Array
private:
public:
virtual VertexFormatID vertex_format_create(VectorView<VertexAttribute> p_vertex_attribs) override final;
virtual void vertex_format_free(VertexFormatID p_vertex_format) override final;
#pragma mark - Barriers
virtual void command_pipeline_barrier(
CommandBufferID p_cmd_buffer,
BitField<PipelineStageBits> p_src_stages,
BitField<PipelineStageBits> p_dst_stages,
VectorView<MemoryBarrier> p_memory_barriers,
VectorView<BufferBarrier> p_buffer_barriers,
VectorView<TextureBarrier> p_texture_barriers) override final;
#pragma mark - Fences
private:
struct Fence {
dispatch_semaphore_t semaphore;
Fence() :
semaphore(dispatch_semaphore_create(0)) {}
};
public:
virtual FenceID fence_create() override final;
virtual Error fence_wait(FenceID p_fence) override final;
virtual void fence_free(FenceID p_fence) override final;
#pragma mark - Semaphores
public:
virtual SemaphoreID semaphore_create() override final;
virtual void semaphore_free(SemaphoreID p_semaphore) override final;
#pragma mark - Commands
// ----- QUEUE FAMILY -----
virtual CommandQueueFamilyID command_queue_family_get(BitField<CommandQueueFamilyBits> p_cmd_queue_family_bits, RenderingContextDriver::SurfaceID p_surface = 0) override final;
// ----- QUEUE -----
public:
virtual CommandQueueID command_queue_create(CommandQueueFamilyID p_cmd_queue_family, bool p_identify_as_main_queue = false) override final;
virtual Error command_queue_execute_and_present(CommandQueueID p_cmd_queue, VectorView<SemaphoreID> p_wait_semaphores, VectorView<CommandBufferID> p_cmd_buffers, VectorView<SemaphoreID> p_cmd_semaphores, FenceID p_cmd_fence, VectorView<SwapChainID> p_swap_chains) override final;
virtual void command_queue_free(CommandQueueID p_cmd_queue) override final;
// ----- POOL -----
virtual CommandPoolID command_pool_create(CommandQueueFamilyID p_cmd_queue_family, CommandBufferType p_cmd_buffer_type) override final;
virtual void command_pool_free(CommandPoolID p_cmd_pool) override final;
// ----- BUFFER -----
private:
// Used to maintain references.
Vector<MDCommandBuffer *> command_buffers;
public:
virtual CommandBufferID command_buffer_create(CommandPoolID p_cmd_pool) override final;
virtual bool command_buffer_begin(CommandBufferID p_cmd_buffer) override final;
virtual bool command_buffer_begin_secondary(CommandBufferID p_cmd_buffer, RenderPassID p_render_pass, uint32_t p_subpass, FramebufferID p_framebuffer) override final;
virtual void command_buffer_end(CommandBufferID p_cmd_buffer) override final;
virtual void command_buffer_execute_secondary(CommandBufferID p_cmd_buffer, VectorView<CommandBufferID> p_secondary_cmd_buffers) override final;
#pragma mark - Swapchain
private:
struct SwapChain {
RenderingContextDriver::SurfaceID surface = RenderingContextDriver::SurfaceID();
RenderPassID render_pass;
RDD::DataFormat data_format = DATA_FORMAT_MAX;
SwapChain() :
render_pass(nullptr) {}
};
void _swap_chain_release(SwapChain *p_swap_chain);
void _swap_chain_release_buffers(SwapChain *p_swap_chain);
public:
virtual SwapChainID swap_chain_create(RenderingContextDriver::SurfaceID p_surface) override final;
virtual Error swap_chain_resize(CommandQueueID p_cmd_queue, SwapChainID p_swap_chain, uint32_t p_desired_framebuffer_count) override final;
virtual FramebufferID swap_chain_acquire_framebuffer(CommandQueueID p_cmd_queue, SwapChainID p_swap_chain, bool &r_resize_required) override final;
virtual RenderPassID swap_chain_get_render_pass(SwapChainID p_swap_chain) override final;
virtual DataFormat swap_chain_get_format(SwapChainID p_swap_chain) override final;
virtual void swap_chain_free(SwapChainID p_swap_chain) override final;
#pragma mark - Frame Buffer
virtual FramebufferID framebuffer_create(RenderPassID p_render_pass, VectorView<TextureID> p_attachments, uint32_t p_width, uint32_t p_height) override final;
virtual void framebuffer_free(FramebufferID p_framebuffer) override final;
#pragma mark - Shader
private:
// Serialization types need access to private state.
friend struct ShaderStageData;
friend struct SpecializationConstantData;
friend struct UniformData;
friend struct ShaderBinaryData;
friend struct PushConstantData;
private:
Error _reflect_spirv16(VectorView<ShaderStageSPIRVData> p_spirv, ShaderReflection &r_reflection);
public:
virtual String shader_get_binary_cache_key() override final;
virtual Vector<uint8_t> shader_compile_binary_from_spirv(VectorView<ShaderStageSPIRVData> p_spirv, const String &p_shader_name) override final;
virtual ShaderID shader_create_from_bytecode(const Vector<uint8_t> &p_shader_binary, ShaderDescription &r_shader_desc, String &r_name) override final;
virtual void shader_free(ShaderID p_shader) override final;
#pragma mark - Uniform Set
public:
virtual UniformSetID uniform_set_create(VectorView<BoundUniform> p_uniforms, ShaderID p_shader, uint32_t p_set_index) override final;
virtual void uniform_set_free(UniformSetID p_uniform_set) override final;
#pragma mark - Commands
virtual void command_uniform_set_prepare_for_use(CommandBufferID p_cmd_buffer, UniformSetID p_uniform_set, ShaderID p_shader, uint32_t p_set_index) override final;
#pragma mark Transfer
private:
enum class CopySource {
Buffer,
Texture,
};
void _copy_texture_buffer(CommandBufferID p_cmd_buffer,
CopySource p_source,
TextureID p_texture,
BufferID p_buffer,
VectorView<BufferTextureCopyRegion> p_regions);
public:
virtual void command_clear_buffer(CommandBufferID p_cmd_buffer, BufferID p_buffer, uint64_t p_offset, uint64_t p_size) override final;
virtual void command_copy_buffer(CommandBufferID p_cmd_buffer, BufferID p_src_buffer, BufferID p_dst_buffer, VectorView<BufferCopyRegion> p_regions) override final;
virtual void command_copy_texture(CommandBufferID p_cmd_buffer, TextureID p_src_texture, TextureLayout p_src_texture_layout, TextureID p_dst_texture, TextureLayout p_dst_texture_layout, VectorView<TextureCopyRegion> p_regions) override final;
virtual void command_resolve_texture(CommandBufferID p_cmd_buffer, TextureID p_src_texture, TextureLayout p_src_texture_layout, uint32_t p_src_layer, uint32_t p_src_mipmap, TextureID p_dst_texture, TextureLayout p_dst_texture_layout, uint32_t p_dst_layer, uint32_t p_dst_mipmap) override final;
virtual void command_clear_color_texture(CommandBufferID p_cmd_buffer, TextureID p_texture, TextureLayout p_texture_layout, const Color &p_color, const TextureSubresourceRange &p_subresources) override final;
virtual void command_copy_buffer_to_texture(CommandBufferID p_cmd_buffer, BufferID p_src_buffer, TextureID p_dst_texture, TextureLayout p_dst_texture_layout, VectorView<BufferTextureCopyRegion> p_regions) override final;
virtual void command_copy_texture_to_buffer(CommandBufferID p_cmd_buffer, TextureID p_src_texture, TextureLayout p_src_texture_layout, BufferID p_dst_buffer, VectorView<BufferTextureCopyRegion> p_regions) override final;
#pragma mark Pipeline
private:
Result<id<MTLFunction>> _create_function(id<MTLLibrary> p_library, NSString *p_name, VectorView<PipelineSpecializationConstant> &p_specialization_constants);
public:
virtual void pipeline_free(PipelineID p_pipeline_id) override final;
// ----- BINDING -----
virtual void command_bind_push_constants(CommandBufferID p_cmd_buffer, ShaderID p_shader, uint32_t p_first_index, VectorView<uint32_t> p_data) override final;
// ----- CACHE -----
private:
String _pipeline_get_cache_path() const;
public:
virtual bool pipeline_cache_create(const Vector<uint8_t> &p_data) override final;
virtual void pipeline_cache_free() override final;
virtual size_t pipeline_cache_query_size() override final;
virtual Vector<uint8_t> pipeline_cache_serialize() override final;
#pragma mark Rendering
// ----- SUBPASS -----
virtual RenderPassID render_pass_create(VectorView<Attachment> p_attachments, VectorView<Subpass> p_subpasses, VectorView<SubpassDependency> p_subpass_dependencies, uint32_t p_view_count) override final;
virtual void render_pass_free(RenderPassID p_render_pass) override final;
// ----- COMMANDS -----
public:
virtual void command_begin_render_pass(CommandBufferID p_cmd_buffer, RenderPassID p_render_pass, FramebufferID p_framebuffer, CommandBufferType p_cmd_buffer_type, const Rect2i &p_rect, VectorView<RenderPassClearValue> p_clear_values) override final;
virtual void command_end_render_pass(CommandBufferID p_cmd_buffer) override final;
virtual void command_next_render_subpass(CommandBufferID p_cmd_buffer, CommandBufferType p_cmd_buffer_type) override final;
virtual void command_render_set_viewport(CommandBufferID p_cmd_buffer, VectorView<Rect2i> p_viewports) override final;
virtual void command_render_set_scissor(CommandBufferID p_cmd_buffer, VectorView<Rect2i> p_scissors) override final;
virtual void command_render_clear_attachments(CommandBufferID p_cmd_buffer, VectorView<AttachmentClear> p_attachment_clears, VectorView<Rect2i> p_rects) override final;
// Binding.
virtual void command_bind_render_pipeline(CommandBufferID p_cmd_buffer, PipelineID p_pipeline) override final;
virtual void command_bind_render_uniform_set(CommandBufferID p_cmd_buffer, UniformSetID p_uniform_set, ShaderID p_shader, uint32_t p_set_index) override final;
// Drawing.
virtual void command_render_draw(CommandBufferID p_cmd_buffer, uint32_t p_vertex_count, uint32_t p_instance_count, uint32_t p_base_vertex, uint32_t p_first_instance) override final;
virtual void command_render_draw_indexed(CommandBufferID p_cmd_buffer, 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) override final;
virtual void command_render_draw_indexed_indirect(CommandBufferID p_cmd_buffer, BufferID p_indirect_buffer, uint64_t p_offset, uint32_t p_draw_count, uint32_t p_stride) override final;
virtual void command_render_draw_indexed_indirect_count(CommandBufferID p_cmd_buffer, BufferID p_indirect_buffer, uint64_t p_offset, BufferID p_count_buffer, uint64_t p_count_buffer_offset, uint32_t p_max_draw_count, uint32_t p_stride) override final;
virtual void command_render_draw_indirect(CommandBufferID p_cmd_buffer, BufferID p_indirect_buffer, uint64_t p_offset, uint32_t p_draw_count, uint32_t p_stride) override final;
virtual void command_render_draw_indirect_count(CommandBufferID p_cmd_buffer, BufferID p_indirect_buffer, uint64_t p_offset, BufferID p_count_buffer, uint64_t p_count_buffer_offset, uint32_t p_max_draw_count, uint32_t p_stride) override final;
// Buffer binding.
virtual void command_render_bind_vertex_buffers(CommandBufferID p_cmd_buffer, uint32_t p_binding_count, const BufferID *p_buffers, const uint64_t *p_offsets) override final;
virtual void command_render_bind_index_buffer(CommandBufferID p_cmd_buffer, BufferID p_buffer, IndexBufferFormat p_format, uint64_t p_offset) override final;
// Dynamic state.
virtual void command_render_set_blend_constants(CommandBufferID p_cmd_buffer, const Color &p_constants) override final;
virtual void command_render_set_line_width(CommandBufferID p_cmd_buffer, float p_width) override final;
// ----- PIPELINE -----
virtual PipelineID render_pipeline_create(
ShaderID p_shader,
VertexFormatID p_vertex_format,
RenderPrimitive p_render_primitive,
PipelineRasterizationState p_rasterization_state,
PipelineMultisampleState p_multisample_state,
PipelineDepthStencilState p_depth_stencil_state,
PipelineColorBlendState p_blend_state,
VectorView<int32_t> p_color_attachments,
BitField<PipelineDynamicStateFlags> p_dynamic_state,
RenderPassID p_render_pass,
uint32_t p_render_subpass,
VectorView<PipelineSpecializationConstant> p_specialization_constants) override final;
#pragma mark - Compute
// ----- COMMANDS -----
// Binding.
virtual void command_bind_compute_pipeline(CommandBufferID p_cmd_buffer, PipelineID p_pipeline) override final;
virtual void command_bind_compute_uniform_set(CommandBufferID p_cmd_buffer, UniformSetID p_uniform_set, ShaderID p_shader, uint32_t p_set_index) override final;
// Dispatching.
virtual void command_compute_dispatch(CommandBufferID p_cmd_buffer, uint32_t p_x_groups, uint32_t p_y_groups, uint32_t p_z_groups) override final;
virtual void command_compute_dispatch_indirect(CommandBufferID p_cmd_buffer, BufferID p_indirect_buffer, uint64_t p_offset) override final;
// ----- PIPELINE -----
virtual PipelineID compute_pipeline_create(ShaderID p_shader, VectorView<PipelineSpecializationConstant> p_specialization_constants) override final;
#pragma mark - Queries
// ----- TIMESTAMP -----
// Basic.
virtual QueryPoolID timestamp_query_pool_create(uint32_t p_query_count) override final;
virtual void timestamp_query_pool_free(QueryPoolID p_pool_id) override final;
virtual void timestamp_query_pool_get_results(QueryPoolID p_pool_id, uint32_t p_query_count, uint64_t *r_results) override final;
virtual uint64_t timestamp_query_result_to_time(uint64_t p_result) override final;
// Commands.
virtual void command_timestamp_query_pool_reset(CommandBufferID p_cmd_buffer, QueryPoolID p_pool_id, uint32_t p_query_count) override final;
virtual void command_timestamp_write(CommandBufferID p_cmd_buffer, QueryPoolID p_pool_id, uint32_t p_index) override final;
#pragma mark - Labels
virtual void command_begin_label(CommandBufferID p_cmd_buffer, const char *p_label_name, const Color &p_color) override final;
virtual void command_end_label(CommandBufferID p_cmd_buffer) override final;
#pragma mark - Submission
virtual void begin_segment(uint32_t p_frame_index, uint32_t p_frames_drawn) override final;
virtual void end_segment() override final;
#pragma mark - Miscellaneous
virtual void set_object_name(ObjectType p_type, ID p_driver_id, const String &p_name) override final;
virtual uint64_t get_resource_native_handle(DriverResource p_type, ID p_driver_id) override final;
virtual uint64_t get_total_memory_used() override final;
virtual uint64_t limit_get(Limit p_limit) override final;
virtual uint64_t api_trait_get(ApiTrait p_trait) override final;
virtual bool has_feature(Features p_feature) override final;
virtual const MultiviewCapabilities &get_multiview_capabilities() override final;
virtual String get_api_name() const override final { return "Metal"; };
virtual String get_api_version() const override final;
virtual String get_pipeline_cache_uuid() const override final;
virtual const Capabilities &get_capabilities() const override final;
virtual bool is_composite_alpha_supported(CommandQueueID p_queue) const override final;
// Metal-specific.
id<MTLDevice> get_device() const { return device; }
PixelFormats &get_pixel_formats() const { return *pixel_formats; }
MDResourceCache &get_resource_cache() const { return *resource_cache; }
MetalDeviceProperties const &get_device_properties() const { return *metal_device_properties; }
_FORCE_INLINE_ uint32_t get_metal_buffer_index_for_vertex_attribute_binding(uint32_t p_binding) {
return (metal_device_properties->limits.maxPerStageBufferCount - 1) - p_binding;
}
size_t get_texel_buffer_alignment_for_format(RDD::DataFormat p_format) const;
size_t get_texel_buffer_alignment_for_format(MTLPixelFormat p_format) const;
/******************/
RenderingDeviceDriverMetal(RenderingContextDriverMetal *p_context_driver);
~RenderingDeviceDriverMetal();
};
#endif // RENDERING_DEVICE_DRIVER_METAL_H

File diff suppressed because it is too large Load Diff