#include "SDL_internal.h"
#ifdef SDL_GPU_METAL
#include <Metal/Metal.h>
#include <QuartzCore/CoreAnimation.h>
#include "../SDL_sysgpu.h"
#define METAL_FIRST_VERTEX_BUFFER_SLOT 14
#define WINDOW_PROPERTY_DATA "SDL.internal.gpu.metal.data"
#define SDL_GPU_SHADERSTAGE_COMPUTE 2
#define TRACK_RESOURCE(resource, type, array, count, capacity) \
do { \
Uint32 i; \
\
for (i = 0; i < commandBuffer->count; i += 1) { \
if (commandBuffer->array[i] == (resource)) { \
return; \
} \
} \
\
if (commandBuffer->count == commandBuffer->capacity) { \
commandBuffer->capacity += 1; \
commandBuffer->array = SDL_realloc( \
commandBuffer->array, \
commandBuffer->capacity * sizeof(type)); \
} \
commandBuffer->array[commandBuffer->count] = (resource); \
commandBuffer->count += 1; \
SDL_AtomicIncRef(&(resource)->referenceCount); \
} while (0)
#define SET_ERROR_AND_RETURN(fmt, msg, ret) \
do { \
if (renderer->debugMode) { \
SDL_LogError(SDL_LOG_CATEGORY_GPU, fmt, msg); \
} \
SDL_SetError(fmt, msg); \
return ret; \
} while (0)
#define SET_STRING_ERROR_AND_RETURN(msg, ret) SET_ERROR_AND_RETURN("%s", msg, ret)
#include "Metal_Blit.h"
static bool METAL_Wait(SDL_GPURenderer *driverData);
static void METAL_ReleaseWindow(
SDL_GPURenderer *driverData,
SDL_Window *window);
static void METAL_INTERNAL_DestroyBlitResources(SDL_GPURenderer *driverData);
#define RETURN_FORMAT(availability, format) \
if (availability) { return format; } else { return MTLPixelFormatInvalid; }
static MTLPixelFormat SDLToMetal_TextureFormat(SDL_GPUTextureFormat format)
{
switch (format) {
case SDL_GPU_TEXTUREFORMAT_INVALID: return MTLPixelFormatInvalid;
case SDL_GPU_TEXTUREFORMAT_A8_UNORM: return MTLPixelFormatA8Unorm;
case SDL_GPU_TEXTUREFORMAT_R8_UNORM: return MTLPixelFormatR8Unorm;
case SDL_GPU_TEXTUREFORMAT_R8G8_UNORM: return MTLPixelFormatRG8Unorm;
case SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM: return MTLPixelFormatRGBA8Unorm;
case SDL_GPU_TEXTUREFORMAT_R16_UNORM: return MTLPixelFormatR16Unorm;
case SDL_GPU_TEXTUREFORMAT_R16G16_UNORM: return MTLPixelFormatRG16Unorm;
case SDL_GPU_TEXTUREFORMAT_R16G16B16A16_UNORM: return MTLPixelFormatRGBA16Unorm;
case SDL_GPU_TEXTUREFORMAT_R10G10B10A2_UNORM: return MTLPixelFormatRGB10A2Unorm;
case SDL_GPU_TEXTUREFORMAT_B5G6R5_UNORM: RETURN_FORMAT(@available(macOS 11.0, *), MTLPixelFormatB5G6R5Unorm);
case SDL_GPU_TEXTUREFORMAT_B5G5R5A1_UNORM: RETURN_FORMAT(@available(macOS 11.0, *), MTLPixelFormatBGR5A1Unorm);
case SDL_GPU_TEXTUREFORMAT_B4G4R4A4_UNORM: RETURN_FORMAT(@available(macOS 11.0, *), MTLPixelFormatABGR4Unorm);
case SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM: return MTLPixelFormatBGRA8Unorm;
case SDL_GPU_TEXTUREFORMAT_BC1_RGBA_UNORM: RETURN_FORMAT(@available(iOS 16.4, tvOS 16.4, *), MTLPixelFormatBC1_RGBA);
case SDL_GPU_TEXTUREFORMAT_BC2_RGBA_UNORM: RETURN_FORMAT(@available(iOS 16.4, tvOS 16.4, *), MTLPixelFormatBC2_RGBA);
case SDL_GPU_TEXTUREFORMAT_BC3_RGBA_UNORM: RETURN_FORMAT(@available(iOS 16.4, tvOS 16.4, *), MTLPixelFormatBC3_RGBA);
case SDL_GPU_TEXTUREFORMAT_BC4_R_UNORM: RETURN_FORMAT(@available(iOS 16.4, tvOS 16.4, *), MTLPixelFormatBC4_RUnorm);
case SDL_GPU_TEXTUREFORMAT_BC5_RG_UNORM: RETURN_FORMAT(@available(iOS 16.4, tvOS 16.4, *), MTLPixelFormatBC5_RGUnorm);
case SDL_GPU_TEXTUREFORMAT_BC7_RGBA_UNORM: RETURN_FORMAT(@available(iOS 16.4, tvOS 16.4, *), MTLPixelFormatBC7_RGBAUnorm);
case SDL_GPU_TEXTUREFORMAT_BC6H_RGB_FLOAT: RETURN_FORMAT(@available(iOS 16.4, tvOS 16.4, *), MTLPixelFormatBC6H_RGBFloat);
case SDL_GPU_TEXTUREFORMAT_BC6H_RGB_UFLOAT: RETURN_FORMAT(@available(iOS 16.4, tvOS 16.4, *), MTLPixelFormatBC6H_RGBUfloat);
case SDL_GPU_TEXTUREFORMAT_R8_SNORM: return MTLPixelFormatR8Snorm;
case SDL_GPU_TEXTUREFORMAT_R8G8_SNORM: return MTLPixelFormatRG8Snorm;
case SDL_GPU_TEXTUREFORMAT_R8G8B8A8_SNORM: return MTLPixelFormatRGBA8Snorm;
case SDL_GPU_TEXTUREFORMAT_R16_SNORM: return MTLPixelFormatR16Snorm;
case SDL_GPU_TEXTUREFORMAT_R16G16_SNORM: return MTLPixelFormatRG16Snorm;
case SDL_GPU_TEXTUREFORMAT_R16G16B16A16_SNORM: return MTLPixelFormatRGBA16Snorm;
case SDL_GPU_TEXTUREFORMAT_R16_FLOAT: return MTLPixelFormatR16Float;
case SDL_GPU_TEXTUREFORMAT_R16G16_FLOAT: return MTLPixelFormatRG16Float;
case SDL_GPU_TEXTUREFORMAT_R16G16B16A16_FLOAT: return MTLPixelFormatRGBA16Float;
case SDL_GPU_TEXTUREFORMAT_R32_FLOAT: return MTLPixelFormatR32Float;
case SDL_GPU_TEXTUREFORMAT_R32G32_FLOAT: return MTLPixelFormatRG32Float;
case SDL_GPU_TEXTUREFORMAT_R32G32B32A32_FLOAT: return MTLPixelFormatRGBA32Float;
case SDL_GPU_TEXTUREFORMAT_R11G11B10_UFLOAT: return MTLPixelFormatRG11B10Float;
case SDL_GPU_TEXTUREFORMAT_R8_UINT: return MTLPixelFormatR8Uint;
case SDL_GPU_TEXTUREFORMAT_R8G8_UINT: return MTLPixelFormatRG8Uint;
case SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UINT: return MTLPixelFormatRGBA8Uint;
case SDL_GPU_TEXTUREFORMAT_R16_UINT: return MTLPixelFormatR16Uint;
case SDL_GPU_TEXTUREFORMAT_R16G16_UINT: return MTLPixelFormatRG16Uint;
case SDL_GPU_TEXTUREFORMAT_R16G16B16A16_UINT: return MTLPixelFormatRGBA16Uint;
case SDL_GPU_TEXTUREFORMAT_R32_UINT: return MTLPixelFormatR32Uint;
case SDL_GPU_TEXTUREFORMAT_R32G32_UINT: return MTLPixelFormatRG32Uint;
case SDL_GPU_TEXTUREFORMAT_R32G32B32A32_UINT: return MTLPixelFormatRGBA32Uint;
case SDL_GPU_TEXTUREFORMAT_R8_INT: return MTLPixelFormatR8Sint;
case SDL_GPU_TEXTUREFORMAT_R8G8_INT: return MTLPixelFormatRG8Sint;
case SDL_GPU_TEXTUREFORMAT_R8G8B8A8_INT: return MTLPixelFormatRGBA8Sint;
case SDL_GPU_TEXTUREFORMAT_R16_INT: return MTLPixelFormatR16Sint;
case SDL_GPU_TEXTUREFORMAT_R16G16_INT: return MTLPixelFormatRG16Sint;
case SDL_GPU_TEXTUREFORMAT_R16G16B16A16_INT: return MTLPixelFormatRGBA16Sint;
case SDL_GPU_TEXTUREFORMAT_R32_INT: return MTLPixelFormatR32Sint;
case SDL_GPU_TEXTUREFORMAT_R32G32_INT: return MTLPixelFormatRG32Sint;
case SDL_GPU_TEXTUREFORMAT_R32G32B32A32_INT: return MTLPixelFormatRGBA32Sint;
case SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM_SRGB: return MTLPixelFormatRGBA8Unorm_sRGB;
case SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM_SRGB: return MTLPixelFormatBGRA8Unorm_sRGB;
case SDL_GPU_TEXTUREFORMAT_BC1_RGBA_UNORM_SRGB: RETURN_FORMAT(@available(iOS 16.4, tvOS 16.4, *), MTLPixelFormatBC1_RGBA_sRGB);
case SDL_GPU_TEXTUREFORMAT_BC2_RGBA_UNORM_SRGB: RETURN_FORMAT(@available(iOS 16.4, tvOS 16.4, *), MTLPixelFormatBC2_RGBA_sRGB);
case SDL_GPU_TEXTUREFORMAT_BC3_RGBA_UNORM_SRGB: RETURN_FORMAT(@available(iOS 16.4, tvOS 16.4, *), MTLPixelFormatBC3_RGBA_sRGB);
case SDL_GPU_TEXTUREFORMAT_BC7_RGBA_UNORM_SRGB: RETURN_FORMAT(@available(iOS 16.4, tvOS 16.4, *), MTLPixelFormatBC7_RGBAUnorm_sRGB);
case SDL_GPU_TEXTUREFORMAT_D16_UNORM: RETURN_FORMAT(@available(iOS 13.0, tvOS 13.0, *), MTLPixelFormatDepth16Unorm);
case SDL_GPU_TEXTUREFORMAT_D24_UNORM:
#ifdef SDL_PLATFORM_MACOS
return MTLPixelFormatDepth24Unorm_Stencil8;
#else
return MTLPixelFormatInvalid;
#endif
case SDL_GPU_TEXTUREFORMAT_D32_FLOAT: return MTLPixelFormatDepth32Float;
case SDL_GPU_TEXTUREFORMAT_D24_UNORM_S8_UINT:
#ifdef SDL_PLATFORM_MACOS
return MTLPixelFormatDepth24Unorm_Stencil8;
#else
return MTLPixelFormatInvalid;
#endif
case SDL_GPU_TEXTUREFORMAT_D32_FLOAT_S8_UINT: return MTLPixelFormatDepth32Float_Stencil8;
case SDL_GPU_TEXTUREFORMAT_ASTC_4x4_UNORM: RETURN_FORMAT(@available(macOS 11.0, *), MTLPixelFormatASTC_4x4_LDR);
case SDL_GPU_TEXTUREFORMAT_ASTC_5x4_UNORM: RETURN_FORMAT(@available(macOS 11.0, *), MTLPixelFormatASTC_5x4_LDR);
case SDL_GPU_TEXTUREFORMAT_ASTC_5x5_UNORM: RETURN_FORMAT(@available(macOS 11.0, *), MTLPixelFormatASTC_5x5_LDR);
case SDL_GPU_TEXTUREFORMAT_ASTC_6x5_UNORM: RETURN_FORMAT(@available(macOS 11.0, *), MTLPixelFormatASTC_6x5_LDR);
case SDL_GPU_TEXTUREFORMAT_ASTC_6x6_UNORM: RETURN_FORMAT(@available(macOS 11.0, *), MTLPixelFormatASTC_6x6_LDR);
case SDL_GPU_TEXTUREFORMAT_ASTC_8x5_UNORM: RETURN_FORMAT(@available(macOS 11.0, *), MTLPixelFormatASTC_8x5_LDR);
case SDL_GPU_TEXTUREFORMAT_ASTC_8x6_UNORM: RETURN_FORMAT(@available(macOS 11.0, *), MTLPixelFormatASTC_8x6_LDR);
case SDL_GPU_TEXTUREFORMAT_ASTC_8x8_UNORM: RETURN_FORMAT(@available(macOS 11.0, *), MTLPixelFormatASTC_8x8_LDR);
case SDL_GPU_TEXTUREFORMAT_ASTC_10x5_UNORM: RETURN_FORMAT(@available(macOS 11.0, *), MTLPixelFormatASTC_10x5_LDR);
case SDL_GPU_TEXTUREFORMAT_ASTC_10x6_UNORM: RETURN_FORMAT(@available(macOS 11.0, *), MTLPixelFormatASTC_10x6_LDR);
case SDL_GPU_TEXTUREFORMAT_ASTC_10x8_UNORM: RETURN_FORMAT(@available(macOS 11.0, *), MTLPixelFormatASTC_10x8_LDR);
case SDL_GPU_TEXTUREFORMAT_ASTC_10x10_UNORM: RETURN_FORMAT(@available(macOS 11.0, *), MTLPixelFormatASTC_10x10_LDR);
case SDL_GPU_TEXTUREFORMAT_ASTC_12x10_UNORM: RETURN_FORMAT(@available(macOS 11.0, *), MTLPixelFormatASTC_12x10_LDR);
case SDL_GPU_TEXTUREFORMAT_ASTC_12x12_UNORM: RETURN_FORMAT(@available(macOS 11.0, *), MTLPixelFormatASTC_12x12_LDR);
case SDL_GPU_TEXTUREFORMAT_ASTC_4x4_UNORM_SRGB: RETURN_FORMAT(@available(macOS 11.0, *), MTLPixelFormatASTC_4x4_sRGB);
case SDL_GPU_TEXTUREFORMAT_ASTC_5x4_UNORM_SRGB: RETURN_FORMAT(@available(macOS 11.0, *), MTLPixelFormatASTC_5x4_sRGB);
case SDL_GPU_TEXTUREFORMAT_ASTC_5x5_UNORM_SRGB: RETURN_FORMAT(@available(macOS 11.0, *), MTLPixelFormatASTC_5x5_sRGB);
case SDL_GPU_TEXTUREFORMAT_ASTC_6x5_UNORM_SRGB: RETURN_FORMAT(@available(macOS 11.0, *), MTLPixelFormatASTC_6x5_sRGB);
case SDL_GPU_TEXTUREFORMAT_ASTC_6x6_UNORM_SRGB: RETURN_FORMAT(@available(macOS 11.0, *), MTLPixelFormatASTC_6x6_sRGB);
case SDL_GPU_TEXTUREFORMAT_ASTC_8x5_UNORM_SRGB: RETURN_FORMAT(@available(macOS 11.0, *), MTLPixelFormatASTC_8x5_sRGB);
case SDL_GPU_TEXTUREFORMAT_ASTC_8x6_UNORM_SRGB: RETURN_FORMAT(@available(macOS 11.0, *), MTLPixelFormatASTC_8x6_sRGB);
case SDL_GPU_TEXTUREFORMAT_ASTC_8x8_UNORM_SRGB: RETURN_FORMAT(@available(macOS 11.0, *), MTLPixelFormatASTC_8x8_sRGB);
case SDL_GPU_TEXTUREFORMAT_ASTC_10x5_UNORM_SRGB: RETURN_FORMAT(@available(macOS 11.0, *), MTLPixelFormatASTC_10x5_sRGB);
case SDL_GPU_TEXTUREFORMAT_ASTC_10x6_UNORM_SRGB: RETURN_FORMAT(@available(macOS 11.0, *), MTLPixelFormatASTC_10x6_sRGB);
case SDL_GPU_TEXTUREFORMAT_ASTC_10x8_UNORM_SRGB: RETURN_FORMAT(@available(macOS 11.0, *), MTLPixelFormatASTC_10x8_sRGB);
case SDL_GPU_TEXTUREFORMAT_ASTC_10x10_UNORM_SRGB: RETURN_FORMAT(@available(macOS 11.0, *), MTLPixelFormatASTC_10x10_sRGB);
case SDL_GPU_TEXTUREFORMAT_ASTC_12x10_UNORM_SRGB: RETURN_FORMAT(@available(macOS 11.0, *), MTLPixelFormatASTC_12x10_sRGB);
case SDL_GPU_TEXTUREFORMAT_ASTC_12x12_UNORM_SRGB: RETURN_FORMAT(@available(macOS 11.0, *), MTLPixelFormatASTC_12x12_sRGB);
case SDL_GPU_TEXTUREFORMAT_ASTC_4x4_FLOAT: RETURN_FORMAT(@available(macOS 11.0, iOS 13.0, tvOS 16.0, *), MTLPixelFormatASTC_4x4_HDR);
case SDL_GPU_TEXTUREFORMAT_ASTC_5x4_FLOAT: RETURN_FORMAT(@available(macOS 11.0, iOS 13.0, tvOS 16.0, *), MTLPixelFormatASTC_5x4_HDR);
case SDL_GPU_TEXTUREFORMAT_ASTC_5x5_FLOAT: RETURN_FORMAT(@available(macOS 11.0, iOS 13.0, tvOS 16.0, *), MTLPixelFormatASTC_5x5_HDR);
case SDL_GPU_TEXTUREFORMAT_ASTC_6x5_FLOAT: RETURN_FORMAT(@available(macOS 11.0, iOS 13.0, tvOS 16.0, *), MTLPixelFormatASTC_6x5_HDR);
case SDL_GPU_TEXTUREFORMAT_ASTC_6x6_FLOAT: RETURN_FORMAT(@available(macOS 11.0, iOS 13.0, tvOS 16.0, *), MTLPixelFormatASTC_6x6_HDR);
case SDL_GPU_TEXTUREFORMAT_ASTC_8x5_FLOAT: RETURN_FORMAT(@available(macOS 11.0, iOS 13.0, tvOS 16.0, *), MTLPixelFormatASTC_8x5_HDR);
case SDL_GPU_TEXTUREFORMAT_ASTC_8x6_FLOAT: RETURN_FORMAT(@available(macOS 11.0, iOS 13.0, tvOS 16.0, *), MTLPixelFormatASTC_8x6_HDR);
case SDL_GPU_TEXTUREFORMAT_ASTC_8x8_FLOAT: RETURN_FORMAT(@available(macOS 11.0, iOS 13.0, tvOS 16.0, *), MTLPixelFormatASTC_8x8_HDR);
case SDL_GPU_TEXTUREFORMAT_ASTC_10x5_FLOAT: RETURN_FORMAT(@available(macOS 11.0, iOS 13.0, tvOS 16.0, *), MTLPixelFormatASTC_10x5_HDR);
case SDL_GPU_TEXTUREFORMAT_ASTC_10x6_FLOAT: RETURN_FORMAT(@available(macOS 11.0, iOS 13.0, tvOS 16.0, *), MTLPixelFormatASTC_10x6_HDR);
case SDL_GPU_TEXTUREFORMAT_ASTC_10x8_FLOAT: RETURN_FORMAT(@available(macOS 11.0, iOS 13.0, tvOS 16.0, *), MTLPixelFormatASTC_10x8_HDR);
case SDL_GPU_TEXTUREFORMAT_ASTC_10x10_FLOAT: RETURN_FORMAT(@available(macOS 11.0, iOS 13.0, tvOS 16.0, *), MTLPixelFormatASTC_10x10_HDR);
case SDL_GPU_TEXTUREFORMAT_ASTC_12x10_FLOAT: RETURN_FORMAT(@available(macOS 11.0, iOS 13.0, tvOS 16.0, *), MTLPixelFormatASTC_12x10_HDR);
case SDL_GPU_TEXTUREFORMAT_ASTC_12x12_FLOAT: RETURN_FORMAT(@available(macOS 11.0, iOS 13.0, tvOS 16.0, *), MTLPixelFormatASTC_12x12_HDR);
}
}
#undef RETURN_FORMAT
static MTLVertexFormat SDLToMetal_VertexFormat[] = {
MTLVertexFormatInvalid, MTLVertexFormatInt, MTLVertexFormatInt2, MTLVertexFormatInt3, MTLVertexFormatInt4, MTLVertexFormatUInt, MTLVertexFormatUInt2, MTLVertexFormatUInt3, MTLVertexFormatUInt4, MTLVertexFormatFloat, MTLVertexFormatFloat2, MTLVertexFormatFloat3, MTLVertexFormatFloat4, MTLVertexFormatChar2, MTLVertexFormatChar4, MTLVertexFormatUChar2, MTLVertexFormatUChar4, MTLVertexFormatChar2Normalized, MTLVertexFormatChar4Normalized, MTLVertexFormatUChar2Normalized, MTLVertexFormatUChar4Normalized, MTLVertexFormatShort2, MTLVertexFormatShort4, MTLVertexFormatUShort2, MTLVertexFormatUShort4, MTLVertexFormatShort2Normalized, MTLVertexFormatShort4Normalized, MTLVertexFormatUShort2Normalized, MTLVertexFormatUShort4Normalized, MTLVertexFormatHalf2, MTLVertexFormatHalf4 };
SDL_COMPILE_TIME_ASSERT(SDLToMetal_VertexFormat, SDL_arraysize(SDLToMetal_VertexFormat) == SDL_GPU_VERTEXELEMENTFORMAT_MAX_ENUM_VALUE);
static MTLIndexType SDLToMetal_IndexType[] = {
MTLIndexTypeUInt16, MTLIndexTypeUInt32, };
static MTLPrimitiveType SDLToMetal_PrimitiveType[] = {
MTLPrimitiveTypeTriangle, MTLPrimitiveTypeTriangleStrip, MTLPrimitiveTypeLine, MTLPrimitiveTypeLineStrip, MTLPrimitiveTypePoint };
static MTLTriangleFillMode SDLToMetal_PolygonMode[] = {
MTLTriangleFillModeFill, MTLTriangleFillModeLines, };
static MTLCullMode SDLToMetal_CullMode[] = {
MTLCullModeNone, MTLCullModeFront, MTLCullModeBack, };
static MTLWinding SDLToMetal_FrontFace[] = {
MTLWindingCounterClockwise, MTLWindingClockwise, };
static MTLBlendFactor SDLToMetal_BlendFactor[] = {
MTLBlendFactorZero, MTLBlendFactorZero, MTLBlendFactorOne, MTLBlendFactorSourceColor, MTLBlendFactorOneMinusSourceColor, MTLBlendFactorDestinationColor, MTLBlendFactorOneMinusDestinationColor, MTLBlendFactorSourceAlpha, MTLBlendFactorOneMinusSourceAlpha, MTLBlendFactorDestinationAlpha, MTLBlendFactorOneMinusDestinationAlpha, MTLBlendFactorBlendColor, MTLBlendFactorOneMinusBlendColor, MTLBlendFactorSourceAlphaSaturated, };
SDL_COMPILE_TIME_ASSERT(SDLToMetal_BlendFactor, SDL_arraysize(SDLToMetal_BlendFactor) == SDL_GPU_BLENDFACTOR_MAX_ENUM_VALUE);
static MTLBlendOperation SDLToMetal_BlendOp[] = {
MTLBlendOperationAdd, MTLBlendOperationAdd, MTLBlendOperationSubtract, MTLBlendOperationReverseSubtract, MTLBlendOperationMin, MTLBlendOperationMax, };
SDL_COMPILE_TIME_ASSERT(SDLToMetal_BlendOp, SDL_arraysize(SDLToMetal_BlendOp) == SDL_GPU_BLENDOP_MAX_ENUM_VALUE);
static MTLCompareFunction SDLToMetal_CompareOp[] = {
MTLCompareFunctionNever, MTLCompareFunctionNever, MTLCompareFunctionLess, MTLCompareFunctionEqual, MTLCompareFunctionLessEqual, MTLCompareFunctionGreater, MTLCompareFunctionNotEqual, MTLCompareFunctionGreaterEqual, MTLCompareFunctionAlways, };
SDL_COMPILE_TIME_ASSERT(SDLToMetal_CompareOp, SDL_arraysize(SDLToMetal_CompareOp) == SDL_GPU_COMPAREOP_MAX_ENUM_VALUE);
static MTLStencilOperation SDLToMetal_StencilOp[] = {
MTLStencilOperationKeep, MTLStencilOperationKeep, MTLStencilOperationZero, MTLStencilOperationReplace, MTLStencilOperationIncrementClamp, MTLStencilOperationDecrementClamp, MTLStencilOperationInvert, MTLStencilOperationIncrementWrap, MTLStencilOperationDecrementWrap, };
SDL_COMPILE_TIME_ASSERT(SDLToMetal_StencilOp, SDL_arraysize(SDLToMetal_StencilOp) == SDL_GPU_STENCILOP_MAX_ENUM_VALUE);
static MTLSamplerAddressMode SDLToMetal_SamplerAddressMode[] = {
MTLSamplerAddressModeRepeat, MTLSamplerAddressModeMirrorRepeat, MTLSamplerAddressModeClampToEdge };
static MTLSamplerMinMagFilter SDLToMetal_MinMagFilter[] = {
MTLSamplerMinMagFilterNearest, MTLSamplerMinMagFilterLinear, };
static MTLSamplerMipFilter SDLToMetal_MipFilter[] = {
MTLSamplerMipFilterNearest, MTLSamplerMipFilterLinear, };
static MTLLoadAction SDLToMetal_LoadOp[] = {
MTLLoadActionLoad, MTLLoadActionClear, MTLLoadActionDontCare, };
static MTLStoreAction SDLToMetal_StoreOp[] = {
MTLStoreActionStore,
MTLStoreActionDontCare,
MTLStoreActionMultisampleResolve,
MTLStoreActionStoreAndMultisampleResolve
};
static MTLVertexStepFunction SDLToMetal_StepFunction[] = {
MTLVertexStepFunctionPerVertex,
MTLVertexStepFunctionPerInstance,
};
static NSUInteger SDLToMetal_SampleCount[] = {
1, 2, 4, 8 };
static SDL_GPUTextureFormat SwapchainCompositionToFormat[] = {
SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM, SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM_SRGB, SDL_GPU_TEXTUREFORMAT_R16G16B16A16_FLOAT, SDL_GPU_TEXTUREFORMAT_R10G10B10A2_UNORM, };
static CFStringRef SwapchainCompositionToColorSpace[4];
static MTLTextureType SDLToMetal_TextureType(SDL_GPUTextureType textureType, bool isMSAA)
{
switch (textureType) {
case SDL_GPU_TEXTURETYPE_2D:
return isMSAA ? MTLTextureType2DMultisample : MTLTextureType2D;
case SDL_GPU_TEXTURETYPE_2D_ARRAY:
return MTLTextureType2DArray;
case SDL_GPU_TEXTURETYPE_3D:
return MTLTextureType3D;
case SDL_GPU_TEXTURETYPE_CUBE:
return MTLTextureTypeCube;
case SDL_GPU_TEXTURETYPE_CUBE_ARRAY:
return MTLTextureTypeCubeArray;
default:
return MTLTextureType2D;
}
}
static MTLColorWriteMask SDLToMetal_ColorWriteMask(
SDL_GPUColorComponentFlags mask)
{
MTLColorWriteMask result = 0;
if (mask & SDL_GPU_COLORCOMPONENT_R) {
result |= MTLColorWriteMaskRed;
}
if (mask & SDL_GPU_COLORCOMPONENT_G) {
result |= MTLColorWriteMaskGreen;
}
if (mask & SDL_GPU_COLORCOMPONENT_B) {
result |= MTLColorWriteMaskBlue;
}
if (mask & SDL_GPU_COLORCOMPONENT_A) {
result |= MTLColorWriteMaskAlpha;
}
return result;
}
static MTLDepthClipMode SDLToMetal_DepthClipMode(
bool enableDepthClip
) {
if (enableDepthClip) {
return MTLDepthClipModeClip;
} else {
return MTLDepthClipModeClamp;
}
}
typedef struct MetalRenderer MetalRenderer;
typedef struct MetalTexture
{
id<MTLTexture> handle;
SDL_AtomicInt referenceCount;
} MetalTexture;
typedef struct MetalTextureContainer
{
TextureCommonHeader header;
MetalTexture *activeTexture;
Uint8 canBeCycled;
Uint32 textureCapacity;
Uint32 textureCount;
MetalTexture **textures;
char *debugName;
} MetalTextureContainer;
typedef struct MetalFence
{
SDL_AtomicInt complete;
SDL_AtomicInt referenceCount;
} MetalFence;
typedef struct MetalWindowData
{
SDL_Window *window;
MetalRenderer *renderer;
int refcount;
SDL_MetalView view;
CAMetalLayer *layer;
SDL_GPUPresentMode presentMode;
id<CAMetalDrawable> drawable;
MetalTexture texture;
MetalTextureContainer textureContainer;
SDL_GPUFence *inFlightFences[MAX_FRAMES_IN_FLIGHT];
Uint32 frameCounter;
} MetalWindowData;
typedef struct MetalShader
{
id<MTLLibrary> library;
id<MTLFunction> function;
SDL_GPUShaderStage stage;
Uint32 numSamplers;
Uint32 numUniformBuffers;
Uint32 numStorageBuffers;
Uint32 numStorageTextures;
} MetalShader;
typedef struct MetalGraphicsPipeline
{
GraphicsPipelineCommonHeader header;
id<MTLRenderPipelineState> handle;
SDL_GPURasterizerState rasterizerState;
SDL_GPUPrimitiveType primitiveType;
id<MTLDepthStencilState> depth_stencil_state;
} MetalGraphicsPipeline;
typedef struct MetalComputePipeline
{
ComputePipelineCommonHeader header;
id<MTLComputePipelineState> handle;
Uint32 threadcountX;
Uint32 threadcountY;
Uint32 threadcountZ;
} MetalComputePipeline;
typedef struct MetalBuffer
{
id<MTLBuffer> handle;
SDL_AtomicInt referenceCount;
} MetalBuffer;
typedef struct MetalBufferContainer
{
MetalBuffer *activeBuffer;
Uint32 size;
Uint32 bufferCapacity;
Uint32 bufferCount;
MetalBuffer **buffers;
bool isPrivate;
bool isWriteOnly;
char *debugName;
} MetalBufferContainer;
typedef struct MetalUniformBuffer
{
id<MTLBuffer> handle;
Uint32 writeOffset;
Uint32 drawOffset;
} MetalUniformBuffer;
typedef struct MetalCommandBuffer
{
CommandBufferCommonHeader common;
MetalRenderer *renderer;
id<MTLCommandBuffer> handle;
MetalWindowData **windowDatas;
Uint32 windowDataCount;
Uint32 windowDataCapacity;
id<MTLRenderCommandEncoder> renderEncoder;
MetalGraphicsPipeline *graphics_pipeline;
MetalBuffer *indexBuffer;
Uint32 indexBufferOffset;
SDL_GPUIndexElementSize index_element_size;
id<MTLBlitCommandEncoder> blitEncoder;
id<MTLComputeCommandEncoder> computeEncoder;
MetalComputePipeline *compute_pipeline;
bool needVertexBufferBind;
bool needVertexSamplerBind;
bool needVertexStorageTextureBind;
bool needVertexStorageBufferBind;
bool needVertexUniformBufferBind[MAX_UNIFORM_BUFFERS_PER_STAGE];
bool needFragmentSamplerBind;
bool needFragmentStorageTextureBind;
bool needFragmentStorageBufferBind;
bool needFragmentUniformBufferBind[MAX_UNIFORM_BUFFERS_PER_STAGE];
bool needComputeSamplerBind;
bool needComputeReadOnlyStorageTextureBind;
bool needComputeReadOnlyStorageBufferBind;
bool needComputeUniformBufferBind[MAX_UNIFORM_BUFFERS_PER_STAGE];
id<MTLBuffer> vertexBuffers[MAX_VERTEX_BUFFERS];
Uint32 vertexBufferOffsets[MAX_VERTEX_BUFFERS];
Uint32 vertexBufferCount;
id<MTLSamplerState> vertexSamplers[MAX_TEXTURE_SAMPLERS_PER_STAGE];
id<MTLTexture> vertexTextures[MAX_TEXTURE_SAMPLERS_PER_STAGE];
id<MTLTexture> vertexStorageTextures[MAX_STORAGE_TEXTURES_PER_STAGE];
id<MTLBuffer> vertexStorageBuffers[MAX_STORAGE_BUFFERS_PER_STAGE];
MetalUniformBuffer *vertexUniformBuffers[MAX_UNIFORM_BUFFERS_PER_STAGE];
id<MTLSamplerState> fragmentSamplers[MAX_TEXTURE_SAMPLERS_PER_STAGE];
id<MTLTexture> fragmentTextures[MAX_TEXTURE_SAMPLERS_PER_STAGE];
id<MTLTexture> fragmentStorageTextures[MAX_STORAGE_TEXTURES_PER_STAGE];
id<MTLBuffer> fragmentStorageBuffers[MAX_STORAGE_BUFFERS_PER_STAGE];
MetalUniformBuffer *fragmentUniformBuffers[MAX_UNIFORM_BUFFERS_PER_STAGE];
id<MTLTexture> computeSamplerTextures[MAX_TEXTURE_SAMPLERS_PER_STAGE];
id<MTLSamplerState> computeSamplers[MAX_TEXTURE_SAMPLERS_PER_STAGE];
id<MTLTexture> computeReadOnlyTextures[MAX_STORAGE_TEXTURES_PER_STAGE];
id<MTLBuffer> computeReadOnlyBuffers[MAX_STORAGE_BUFFERS_PER_STAGE];
id<MTLTexture> computeReadWriteTextures[MAX_COMPUTE_WRITE_TEXTURES];
id<MTLBuffer> computeReadWriteBuffers[MAX_COMPUTE_WRITE_BUFFERS];
MetalUniformBuffer *computeUniformBuffers[MAX_UNIFORM_BUFFERS_PER_STAGE];
MetalUniformBuffer **usedUniformBuffers;
Uint32 usedUniformBufferCount;
Uint32 usedUniformBufferCapacity;
MetalFence *fence;
bool autoReleaseFence;
MetalBuffer **usedBuffers;
Uint32 usedBufferCount;
Uint32 usedBufferCapacity;
MetalTexture **usedTextures;
Uint32 usedTextureCount;
Uint32 usedTextureCapacity;
} MetalCommandBuffer;
typedef struct MetalSampler
{
id<MTLSamplerState> handle;
} MetalSampler;
typedef struct BlitPipeline
{
SDL_GPUGraphicsPipeline *pipeline;
SDL_GPUTextureFormat format;
} BlitPipeline;
struct MetalRenderer
{
SDL_GPUDevice *sdlGPUDevice;
id<MTLDevice> device;
id<MTLCommandQueue> queue;
bool debugMode;
SDL_PropertiesID props;
Uint32 allowedFramesInFlight;
MetalWindowData **claimedWindows;
Uint32 claimedWindowCount;
Uint32 claimedWindowCapacity;
MetalCommandBuffer **availableCommandBuffers;
Uint32 availableCommandBufferCount;
Uint32 availableCommandBufferCapacity;
MetalCommandBuffer **submittedCommandBuffers;
Uint32 submittedCommandBufferCount;
Uint32 submittedCommandBufferCapacity;
MetalFence **availableFences;
Uint32 availableFenceCount;
Uint32 availableFenceCapacity;
MetalUniformBuffer **uniformBufferPool;
Uint32 uniformBufferPoolCount;
Uint32 uniformBufferPoolCapacity;
MetalBufferContainer **bufferContainersToDestroy;
Uint32 bufferContainersToDestroyCount;
Uint32 bufferContainersToDestroyCapacity;
MetalTextureContainer **textureContainersToDestroy;
Uint32 textureContainersToDestroyCount;
Uint32 textureContainersToDestroyCapacity;
SDL_GPUShader *blitVertexShader;
SDL_GPUShader *blitFrom2DShader;
SDL_GPUShader *blitFrom2DArrayShader;
SDL_GPUShader *blitFrom3DShader;
SDL_GPUShader *blitFromCubeShader;
SDL_GPUShader *blitFromCubeArrayShader;
SDL_GPUSampler *blitNearestSampler;
SDL_GPUSampler *blitLinearSampler;
BlitPipelineCacheEntry *blitPipelines;
Uint32 blitPipelineCount;
Uint32 blitPipelineCapacity;
SDL_Mutex *submitLock;
SDL_Mutex *acquireCommandBufferLock;
SDL_Mutex *acquireUniformBufferLock;
SDL_Mutex *disposeLock;
SDL_Mutex *fenceLock;
SDL_Mutex *windowLock;
};
static inline Uint32 METAL_INTERNAL_NextHighestAlignment(
Uint32 n,
Uint32 align)
{
return align * ((n + align - 1) / align);
}
static void METAL_DestroyDevice(SDL_GPUDevice *device)
{
MetalRenderer *renderer = (MetalRenderer *)device->driverData;
METAL_Wait(device->driverData);
for (Sint32 i = renderer->claimedWindowCount - 1; i >= 0; i -= 1) {
METAL_ReleaseWindow(device->driverData, renderer->claimedWindows[i]->window);
}
SDL_free(renderer->claimedWindows);
METAL_INTERNAL_DestroyBlitResources(device->driverData);
for (Uint32 i = 0; i < renderer->uniformBufferPoolCount; i += 1) {
renderer->uniformBufferPool[i]->handle = nil;
SDL_free(renderer->uniformBufferPool[i]);
}
SDL_free(renderer->uniformBufferPool);
SDL_free(renderer->bufferContainersToDestroy);
SDL_free(renderer->textureContainersToDestroy);
for (Uint32 i = 0; i < renderer->availableCommandBufferCount; i += 1) {
MetalCommandBuffer *commandBuffer = renderer->availableCommandBuffers[i];
SDL_free(commandBuffer->usedBuffers);
SDL_free(commandBuffer->usedTextures);
SDL_free(commandBuffer->usedUniformBuffers);
SDL_free(commandBuffer->windowDatas);
SDL_free(commandBuffer);
}
SDL_free(renderer->availableCommandBuffers);
SDL_free(renderer->submittedCommandBuffers);
for (Uint32 i = 0; i < renderer->availableFenceCount; i += 1) {
SDL_free(renderer->availableFences[i]);
}
SDL_free(renderer->availableFences);
SDL_DestroyMutex(renderer->submitLock);
SDL_DestroyMutex(renderer->acquireCommandBufferLock);
SDL_DestroyMutex(renderer->acquireUniformBufferLock);
SDL_DestroyMutex(renderer->disposeLock);
SDL_DestroyMutex(renderer->fenceLock);
SDL_DestroyMutex(renderer->windowLock);
renderer->queue = nil;
SDL_DestroyProperties(renderer->props);
SDL_free(renderer);
SDL_free(device);
}
static SDL_PropertiesID METAL_GetDeviceProperties(SDL_GPUDevice *device)
{
MetalRenderer *renderer = (MetalRenderer *)device->driverData;
return renderer->props;
}
static void METAL_INTERNAL_TrackBuffer(
MetalCommandBuffer *commandBuffer,
MetalBuffer *buffer)
{
TRACK_RESOURCE(
buffer,
MetalBuffer *,
usedBuffers,
usedBufferCount,
usedBufferCapacity);
}
static void METAL_INTERNAL_TrackTexture(
MetalCommandBuffer *commandBuffer,
MetalTexture *texture)
{
TRACK_RESOURCE(
texture,
MetalTexture *,
usedTextures,
usedTextureCount,
usedTextureCapacity);
}
static void METAL_INTERNAL_TrackUniformBuffer(
MetalCommandBuffer *commandBuffer,
MetalUniformBuffer *uniformBuffer)
{
Uint32 i;
for (i = 0; i < commandBuffer->usedUniformBufferCount; i += 1) {
if (commandBuffer->usedUniformBuffers[i] == uniformBuffer) {
return;
}
}
if (commandBuffer->usedUniformBufferCount == commandBuffer->usedUniformBufferCapacity) {
commandBuffer->usedUniformBufferCapacity += 1;
commandBuffer->usedUniformBuffers = SDL_realloc(
commandBuffer->usedUniformBuffers,
commandBuffer->usedUniformBufferCapacity * sizeof(MetalUniformBuffer *));
}
commandBuffer->usedUniformBuffers[commandBuffer->usedUniformBufferCount] = uniformBuffer;
commandBuffer->usedUniformBufferCount += 1;
}
typedef struct MetalLibraryFunction
{
id<MTLLibrary> library;
id<MTLFunction> function;
} MetalLibraryFunction;
static bool METAL_INTERNAL_IsValidMetalLibrary(
const Uint8 *code,
size_t codeSize)
{
if (codeSize < 4 || code == NULL) {
return false;
}
return SDL_memcmp(code, "MTLB", 4) == 0;
}
static MetalLibraryFunction METAL_INTERNAL_CompileShader(
MetalRenderer *renderer,
SDL_GPUShaderFormat format,
const Uint8 *code,
size_t codeSize,
const char *entrypoint)
{
MetalLibraryFunction libraryFunction = { nil, nil };
id<MTLLibrary> library;
NSError *error;
dispatch_data_t data;
id<MTLFunction> function;
if (!entrypoint) {
entrypoint = "main0";
}
if (format == SDL_GPU_SHADERFORMAT_MSL) {
NSString *codeString = [[NSString alloc]
initWithBytes:code
length:codeSize
encoding:NSUTF8StringEncoding];
library = [renderer->device
newLibraryWithSource:codeString
options:nil
error:&error];
} else if (format == SDL_GPU_SHADERFORMAT_METALLIB) {
if (!METAL_INTERNAL_IsValidMetalLibrary(code, codeSize)) {
SET_STRING_ERROR_AND_RETURN(
"The provided shader code is not a valid Metal library!",
libraryFunction);
}
data = dispatch_data_create(
code,
codeSize,
dispatch_get_global_queue(0, 0),
DISPATCH_DATA_DESTRUCTOR_DEFAULT);
library = [renderer->device newLibraryWithData:data error:&error];
} else {
SDL_assert(!"SDL_gpu.c should have already validated this!");
return libraryFunction;
}
if (library == nil) {
SDL_LogError(
SDL_LOG_CATEGORY_GPU,
"Creating MTLLibrary failed: %s",
[[error description] cStringUsingEncoding:[NSString defaultCStringEncoding]]);
return libraryFunction;
} else if (error != nil) {
SDL_LogWarn(
SDL_LOG_CATEGORY_GPU,
"Creating MTLLibrary failed: %s",
[[error description] cStringUsingEncoding:[NSString defaultCStringEncoding]]);
}
function = [library newFunctionWithName:@(entrypoint)];
if (function == nil) {
SDL_LogError(
SDL_LOG_CATEGORY_GPU,
"Creating MTLFunction failed");
return libraryFunction;
}
libraryFunction.library = library;
libraryFunction.function = function;
return libraryFunction;
}
static void METAL_INTERNAL_DestroyTextureContainer(
MetalTextureContainer *container)
{
for (Uint32 i = 0; i < container->textureCount; i += 1) {
container->textures[i]->handle = nil;
SDL_free(container->textures[i]);
}
SDL_DestroyProperties(container->header.info.props);
if (container->debugName != NULL) {
SDL_free(container->debugName);
}
SDL_free(container->textures);
SDL_free(container);
}
static void METAL_ReleaseTexture(
SDL_GPURenderer *driverData,
SDL_GPUTexture *texture)
{
MetalRenderer *renderer = (MetalRenderer *)driverData;
MetalTextureContainer *container = (MetalTextureContainer *)texture;
SDL_LockMutex(renderer->disposeLock);
EXPAND_ARRAY_IF_NEEDED(
renderer->textureContainersToDestroy,
MetalTextureContainer *,
renderer->textureContainersToDestroyCount + 1,
renderer->textureContainersToDestroyCapacity,
renderer->textureContainersToDestroyCapacity + 1);
renderer->textureContainersToDestroy[renderer->textureContainersToDestroyCount] = container;
renderer->textureContainersToDestroyCount += 1;
SDL_UnlockMutex(renderer->disposeLock);
}
static void METAL_ReleaseSampler(
SDL_GPURenderer *driverData,
SDL_GPUSampler *sampler)
{
@autoreleasepool {
MetalSampler *metalSampler = (MetalSampler *)sampler;
metalSampler->handle = nil;
SDL_free(metalSampler);
}
}
static void METAL_INTERNAL_DestroyBufferContainer(
MetalBufferContainer *container)
{
for (Uint32 i = 0; i < container->bufferCount; i += 1) {
container->buffers[i]->handle = nil;
SDL_free(container->buffers[i]);
}
if (container->debugName != NULL) {
SDL_free(container->debugName);
}
SDL_free(container->buffers);
SDL_free(container);
}
static void METAL_ReleaseBuffer(
SDL_GPURenderer *driverData,
SDL_GPUBuffer *buffer)
{
MetalRenderer *renderer = (MetalRenderer *)driverData;
MetalBufferContainer *container = (MetalBufferContainer *)buffer;
SDL_LockMutex(renderer->disposeLock);
EXPAND_ARRAY_IF_NEEDED(
renderer->bufferContainersToDestroy,
MetalBufferContainer *,
renderer->bufferContainersToDestroyCount + 1,
renderer->bufferContainersToDestroyCapacity,
renderer->bufferContainersToDestroyCapacity + 1);
renderer->bufferContainersToDestroy[renderer->bufferContainersToDestroyCount] = container;
renderer->bufferContainersToDestroyCount += 1;
SDL_UnlockMutex(renderer->disposeLock);
}
static void METAL_ReleaseTransferBuffer(
SDL_GPURenderer *driverData,
SDL_GPUTransferBuffer *transferBuffer)
{
METAL_ReleaseBuffer(
driverData,
(SDL_GPUBuffer *)transferBuffer);
}
static void METAL_ReleaseShader(
SDL_GPURenderer *driverData,
SDL_GPUShader *shader)
{
@autoreleasepool {
MetalShader *metalShader = (MetalShader *)shader;
metalShader->function = nil;
metalShader->library = nil;
SDL_free(metalShader);
}
}
static void METAL_ReleaseComputePipeline(
SDL_GPURenderer *driverData,
SDL_GPUComputePipeline *computePipeline)
{
@autoreleasepool {
MetalComputePipeline *metalComputePipeline = (MetalComputePipeline *)computePipeline;
metalComputePipeline->handle = nil;
SDL_free(metalComputePipeline);
}
}
static void METAL_ReleaseGraphicsPipeline(
SDL_GPURenderer *driverData,
SDL_GPUGraphicsPipeline *graphicsPipeline)
{
@autoreleasepool {
MetalGraphicsPipeline *metalGraphicsPipeline = (MetalGraphicsPipeline *)graphicsPipeline;
metalGraphicsPipeline->handle = nil;
metalGraphicsPipeline->depth_stencil_state = nil;
SDL_free(metalGraphicsPipeline);
}
}
static SDL_GPUComputePipeline *METAL_CreateComputePipeline(
SDL_GPURenderer *driverData,
const SDL_GPUComputePipelineCreateInfo *createinfo)
{
@autoreleasepool {
MetalRenderer *renderer = (MetalRenderer *)driverData;
MetalLibraryFunction libraryFunction;
id<MTLComputePipelineState> handle;
MetalComputePipeline *pipeline;
NSError *error;
libraryFunction = METAL_INTERNAL_CompileShader(
renderer,
createinfo->format,
createinfo->code,
createinfo->code_size,
createinfo->entrypoint);
if (libraryFunction.library == nil || libraryFunction.function == nil) {
return NULL;
}
MTLComputePipelineDescriptor *descriptor = [MTLComputePipelineDescriptor new];
descriptor.computeFunction = libraryFunction.function;
if (renderer->debugMode && SDL_HasProperty(createinfo->props, SDL_PROP_GPU_COMPUTEPIPELINE_CREATE_NAME_STRING)) {
const char *name = SDL_GetStringProperty(createinfo->props, SDL_PROP_GPU_COMPUTEPIPELINE_CREATE_NAME_STRING, NULL);
descriptor.label = @(name);
}
handle = [renderer->device newComputePipelineStateWithDescriptor:descriptor options:MTLPipelineOptionNone reflection: nil error:&error];
if (error != NULL) {
SET_ERROR_AND_RETURN("Creating compute pipeline failed: %s", [[error description] UTF8String], NULL);
}
pipeline = SDL_calloc(1, sizeof(MetalComputePipeline));
pipeline->handle = handle;
pipeline->header.numSamplers = createinfo->num_samplers;
pipeline->header.numReadonlyStorageTextures = createinfo->num_readonly_storage_textures;
pipeline->header.numReadWriteStorageTextures = createinfo->num_readwrite_storage_textures;
pipeline->header.numReadonlyStorageBuffers = createinfo->num_readonly_storage_buffers;
pipeline->header.numReadWriteStorageBuffers = createinfo->num_readwrite_storage_buffers;
pipeline->header.numUniformBuffers = createinfo->num_uniform_buffers;
pipeline->threadcountX = createinfo->threadcount_x;
pipeline->threadcountY = createinfo->threadcount_y;
pipeline->threadcountZ = createinfo->threadcount_z;
return (SDL_GPUComputePipeline *)pipeline;
}
}
static SDL_GPUGraphicsPipeline *METAL_CreateGraphicsPipeline(
SDL_GPURenderer *driverData,
const SDL_GPUGraphicsPipelineCreateInfo *createinfo)
{
@autoreleasepool {
MetalRenderer *renderer = (MetalRenderer *)driverData;
MetalShader *vertexShader = (MetalShader *)createinfo->vertex_shader;
MetalShader *fragmentShader = (MetalShader *)createinfo->fragment_shader;
MTLRenderPipelineDescriptor *pipelineDescriptor;
const SDL_GPUColorTargetBlendState *blendState;
MTLVertexDescriptor *vertexDescriptor;
Uint32 binding;
MTLDepthStencilDescriptor *depthStencilDescriptor;
MTLStencilDescriptor *frontStencilDescriptor = NULL;
MTLStencilDescriptor *backStencilDescriptor = NULL;
id<MTLDepthStencilState> depthStencilState = nil;
id<MTLRenderPipelineState> pipelineState = nil;
NSError *error = NULL;
MetalGraphicsPipeline *result = NULL;
if (renderer->debugMode) {
if (vertexShader->stage != SDL_GPU_SHADERSTAGE_VERTEX) {
SDL_assert_release(!"CreateGraphicsPipeline was passed a fragment shader for the vertex stage");
}
if (fragmentShader->stage != SDL_GPU_SHADERSTAGE_FRAGMENT) {
SDL_assert_release(!"CreateGraphicsPipeline was passed a vertex shader for the fragment stage");
}
}
#ifdef SDL_PLATFORM_VISIONOS
if (!createinfo->rasterizer_state.enable_depth_clip) {
SDL_assert_release(!"Rasterizer state enable_depth_clip must be true on this platform");
}
#endif
pipelineDescriptor = [MTLRenderPipelineDescriptor new];
for (Uint32 i = 0; i < createinfo->target_info.num_color_targets; i += 1) {
blendState = &createinfo->target_info.color_target_descriptions[i].blend_state;
SDL_GPUColorComponentFlags colorWriteMask = blendState->enable_color_write_mask ?
blendState->color_write_mask :
0xF;
pipelineDescriptor.colorAttachments[i].pixelFormat = SDLToMetal_TextureFormat(createinfo->target_info.color_target_descriptions[i].format);
pipelineDescriptor.colorAttachments[i].writeMask = SDLToMetal_ColorWriteMask(colorWriteMask);
pipelineDescriptor.colorAttachments[i].blendingEnabled = blendState->enable_blend;
pipelineDescriptor.colorAttachments[i].rgbBlendOperation = SDLToMetal_BlendOp[blendState->color_blend_op];
pipelineDescriptor.colorAttachments[i].alphaBlendOperation = SDLToMetal_BlendOp[blendState->alpha_blend_op];
pipelineDescriptor.colorAttachments[i].sourceRGBBlendFactor = SDLToMetal_BlendFactor[blendState->src_color_blendfactor];
pipelineDescriptor.colorAttachments[i].sourceAlphaBlendFactor = SDLToMetal_BlendFactor[blendState->src_alpha_blendfactor];
pipelineDescriptor.colorAttachments[i].destinationRGBBlendFactor = SDLToMetal_BlendFactor[blendState->dst_color_blendfactor];
pipelineDescriptor.colorAttachments[i].destinationAlphaBlendFactor = SDLToMetal_BlendFactor[blendState->dst_alpha_blendfactor];
}
pipelineDescriptor.rasterSampleCount = SDLToMetal_SampleCount[createinfo->multisample_state.sample_count];
pipelineDescriptor.alphaToCoverageEnabled = createinfo->multisample_state.enable_alpha_to_coverage;
if (createinfo->target_info.has_depth_stencil_target) {
pipelineDescriptor.depthAttachmentPixelFormat = SDLToMetal_TextureFormat(createinfo->target_info.depth_stencil_format);
if (IsStencilFormat(createinfo->target_info.depth_stencil_format)) {
pipelineDescriptor.stencilAttachmentPixelFormat = SDLToMetal_TextureFormat(createinfo->target_info.depth_stencil_format);
}
if (createinfo->depth_stencil_state.enable_stencil_test) {
frontStencilDescriptor = [MTLStencilDescriptor new];
frontStencilDescriptor.stencilCompareFunction = SDLToMetal_CompareOp[createinfo->depth_stencil_state.front_stencil_state.compare_op];
frontStencilDescriptor.stencilFailureOperation = SDLToMetal_StencilOp[createinfo->depth_stencil_state.front_stencil_state.fail_op];
frontStencilDescriptor.depthStencilPassOperation = SDLToMetal_StencilOp[createinfo->depth_stencil_state.front_stencil_state.pass_op];
frontStencilDescriptor.depthFailureOperation = SDLToMetal_StencilOp[createinfo->depth_stencil_state.front_stencil_state.depth_fail_op];
frontStencilDescriptor.readMask = createinfo->depth_stencil_state.compare_mask;
frontStencilDescriptor.writeMask = createinfo->depth_stencil_state.write_mask;
backStencilDescriptor = [MTLStencilDescriptor new];
backStencilDescriptor.stencilCompareFunction = SDLToMetal_CompareOp[createinfo->depth_stencil_state.back_stencil_state.compare_op];
backStencilDescriptor.stencilFailureOperation = SDLToMetal_StencilOp[createinfo->depth_stencil_state.back_stencil_state.fail_op];
backStencilDescriptor.depthStencilPassOperation = SDLToMetal_StencilOp[createinfo->depth_stencil_state.back_stencil_state.pass_op];
backStencilDescriptor.depthFailureOperation = SDLToMetal_StencilOp[createinfo->depth_stencil_state.back_stencil_state.depth_fail_op];
backStencilDescriptor.readMask = createinfo->depth_stencil_state.compare_mask;
backStencilDescriptor.writeMask = createinfo->depth_stencil_state.write_mask;
}
depthStencilDescriptor = [MTLDepthStencilDescriptor new];
depthStencilDescriptor.depthCompareFunction = createinfo->depth_stencil_state.enable_depth_test ? SDLToMetal_CompareOp[createinfo->depth_stencil_state.compare_op] : MTLCompareFunctionAlways;
depthStencilDescriptor.depthWriteEnabled = createinfo->depth_stencil_state.enable_depth_write && createinfo->depth_stencil_state.enable_depth_test;
depthStencilDescriptor.frontFaceStencil = frontStencilDescriptor;
depthStencilDescriptor.backFaceStencil = backStencilDescriptor;
depthStencilState = [renderer->device newDepthStencilStateWithDescriptor:depthStencilDescriptor];
}
pipelineDescriptor.vertexFunction = vertexShader->function;
pipelineDescriptor.fragmentFunction = fragmentShader->function;
if (createinfo->vertex_input_state.num_vertex_buffers > 0) {
vertexDescriptor = [MTLVertexDescriptor vertexDescriptor];
for (Uint32 i = 0; i < createinfo->vertex_input_state.num_vertex_attributes; i += 1) {
Uint32 loc = createinfo->vertex_input_state.vertex_attributes[i].location;
vertexDescriptor.attributes[loc].format = SDLToMetal_VertexFormat[createinfo->vertex_input_state.vertex_attributes[i].format];
vertexDescriptor.attributes[loc].offset = createinfo->vertex_input_state.vertex_attributes[i].offset;
vertexDescriptor.attributes[loc].bufferIndex =
METAL_FIRST_VERTEX_BUFFER_SLOT + createinfo->vertex_input_state.vertex_attributes[i].buffer_slot;
}
for (Uint32 i = 0; i < createinfo->vertex_input_state.num_vertex_buffers; i += 1) {
binding = METAL_FIRST_VERTEX_BUFFER_SLOT + createinfo->vertex_input_state.vertex_buffer_descriptions[i].slot;
vertexDescriptor.layouts[binding].stepFunction = SDLToMetal_StepFunction[createinfo->vertex_input_state.vertex_buffer_descriptions[i].input_rate];
vertexDescriptor.layouts[binding].stepRate = 1;
vertexDescriptor.layouts[binding].stride = createinfo->vertex_input_state.vertex_buffer_descriptions[i].pitch;
}
pipelineDescriptor.vertexDescriptor = vertexDescriptor;
}
if (renderer->debugMode && SDL_HasProperty(createinfo->props, SDL_PROP_GPU_GRAPHICSPIPELINE_CREATE_NAME_STRING)) {
const char *name = SDL_GetStringProperty(createinfo->props, SDL_PROP_GPU_GRAPHICSPIPELINE_CREATE_NAME_STRING, NULL);
pipelineDescriptor.label = @(name);
}
pipelineState = [renderer->device newRenderPipelineStateWithDescriptor:pipelineDescriptor error:&error];
if (error != NULL) {
SET_ERROR_AND_RETURN("Creating render pipeline failed: %s", [[error description] UTF8String], NULL);
}
result = SDL_calloc(1, sizeof(MetalGraphicsPipeline));
result->handle = pipelineState;
result->depth_stencil_state = depthStencilState;
result->rasterizerState = createinfo->rasterizer_state;
result->primitiveType = createinfo->primitive_type;
result->header.num_vertex_samplers = vertexShader->numSamplers;
result->header.num_vertex_uniform_buffers = vertexShader->numUniformBuffers;
result->header.num_vertex_storage_buffers = vertexShader->numStorageBuffers;
result->header.num_vertex_storage_textures = vertexShader->numStorageTextures;
result->header.num_fragment_samplers = fragmentShader->numSamplers;
result->header.num_fragment_uniform_buffers = fragmentShader->numUniformBuffers;
result->header.num_fragment_storage_buffers = fragmentShader->numStorageBuffers;
result->header.num_fragment_storage_textures = fragmentShader->numStorageTextures;
return (SDL_GPUGraphicsPipeline *)result;
}
}
static void METAL_SetBufferName(
SDL_GPURenderer *driverData,
SDL_GPUBuffer *buffer,
const char *text)
{
@autoreleasepool {
MetalRenderer *renderer = (MetalRenderer *)driverData;
MetalBufferContainer *container = (MetalBufferContainer *)buffer;
if (renderer->debugMode && text != NULL) {
if (container->debugName != NULL) {
SDL_free(container->debugName);
}
container->debugName = SDL_strdup(text);
for (Uint32 i = 0; i < container->bufferCount; i += 1) {
container->buffers[i]->handle.label = @(text);
}
}
}
}
static void METAL_SetTextureName(
SDL_GPURenderer *driverData,
SDL_GPUTexture *texture,
const char *text)
{
@autoreleasepool {
MetalRenderer *renderer = (MetalRenderer *)driverData;
MetalTextureContainer *container = (MetalTextureContainer *)texture;
if (renderer->debugMode && text != NULL) {
if (container->debugName != NULL) {
SDL_free(container->debugName);
}
container->debugName = SDL_strdup(text);
for (Uint32 i = 0; i < container->textureCount; i += 1) {
container->textures[i]->handle.label = @(text);
}
}
}
}
static void METAL_InsertDebugLabel(
SDL_GPUCommandBuffer *commandBuffer,
const char *text)
{
@autoreleasepool {
MetalCommandBuffer *metalCommandBuffer = (MetalCommandBuffer *)commandBuffer;
NSString *label = @(text);
if (metalCommandBuffer->renderEncoder) {
[metalCommandBuffer->renderEncoder insertDebugSignpost:label];
} else if (metalCommandBuffer->blitEncoder) {
[metalCommandBuffer->blitEncoder insertDebugSignpost:label];
} else if (metalCommandBuffer->computeEncoder) {
[metalCommandBuffer->computeEncoder insertDebugSignpost:label];
} else {
[metalCommandBuffer->handle pushDebugGroup:label];
[metalCommandBuffer->handle popDebugGroup];
}
}
}
static void METAL_PushDebugGroup(
SDL_GPUCommandBuffer *commandBuffer,
const char *name)
{
@autoreleasepool {
MetalCommandBuffer *metalCommandBuffer = (MetalCommandBuffer *)commandBuffer;
NSString *label = @(name);
if (metalCommandBuffer->renderEncoder) {
[metalCommandBuffer->renderEncoder pushDebugGroup:label];
} else if (metalCommandBuffer->blitEncoder) {
[metalCommandBuffer->blitEncoder pushDebugGroup:label];
} else if (metalCommandBuffer->computeEncoder) {
[metalCommandBuffer->computeEncoder pushDebugGroup:label];
} else {
[metalCommandBuffer->handle pushDebugGroup:label];
}
}
}
static void METAL_PopDebugGroup(
SDL_GPUCommandBuffer *commandBuffer)
{
@autoreleasepool {
MetalCommandBuffer *metalCommandBuffer = (MetalCommandBuffer *)commandBuffer;
if (metalCommandBuffer->renderEncoder) {
[metalCommandBuffer->renderEncoder popDebugGroup];
} else if (metalCommandBuffer->blitEncoder) {
[metalCommandBuffer->blitEncoder popDebugGroup];
} else if (metalCommandBuffer->computeEncoder) {
[metalCommandBuffer->computeEncoder popDebugGroup];
} else {
[metalCommandBuffer->handle popDebugGroup];
}
}
}
static SDL_GPUSampler *METAL_CreateSampler(
SDL_GPURenderer *driverData,
const SDL_GPUSamplerCreateInfo *createinfo)
{
@autoreleasepool {
MetalRenderer *renderer = (MetalRenderer *)driverData;
MTLSamplerDescriptor *samplerDesc = [MTLSamplerDescriptor new];
id<MTLSamplerState> sampler;
MetalSampler *metalSampler;
samplerDesc.sAddressMode = SDLToMetal_SamplerAddressMode[createinfo->address_mode_u];
samplerDesc.tAddressMode = SDLToMetal_SamplerAddressMode[createinfo->address_mode_v];
samplerDesc.rAddressMode = SDLToMetal_SamplerAddressMode[createinfo->address_mode_w];
samplerDesc.minFilter = SDLToMetal_MinMagFilter[createinfo->min_filter];
samplerDesc.magFilter = SDLToMetal_MinMagFilter[createinfo->mag_filter];
samplerDesc.mipFilter = SDLToMetal_MipFilter[createinfo->mipmap_mode]; samplerDesc.lodMinClamp = createinfo->min_lod;
samplerDesc.lodMaxClamp = createinfo->max_lod;
samplerDesc.maxAnisotropy = (NSUInteger)((createinfo->enable_anisotropy) ? createinfo->max_anisotropy : 1);
samplerDesc.compareFunction = (createinfo->enable_compare) ? SDLToMetal_CompareOp[createinfo->compare_op] : MTLCompareFunctionAlways;
if (renderer->debugMode && SDL_HasProperty(createinfo->props, SDL_PROP_GPU_SAMPLER_CREATE_NAME_STRING)) {
const char *name = SDL_GetStringProperty(createinfo->props, SDL_PROP_GPU_SAMPLER_CREATE_NAME_STRING, NULL);
samplerDesc.label = @(name);
}
sampler = [renderer->device newSamplerStateWithDescriptor:samplerDesc];
if (sampler == NULL) {
SET_STRING_ERROR_AND_RETURN("Failed to create sampler", NULL);
}
metalSampler = (MetalSampler *)SDL_calloc(1, sizeof(MetalSampler));
metalSampler->handle = sampler;
return (SDL_GPUSampler *)metalSampler;
}
}
static SDL_GPUShader *METAL_CreateShader(
SDL_GPURenderer *driverData,
const SDL_GPUShaderCreateInfo *createinfo)
{
@autoreleasepool {
MetalLibraryFunction libraryFunction;
MetalShader *result;
libraryFunction = METAL_INTERNAL_CompileShader(
(MetalRenderer *)driverData,
createinfo->format,
createinfo->code,
createinfo->code_size,
createinfo->entrypoint);
if (libraryFunction.library == nil || libraryFunction.function == nil) {
return NULL;
}
result = SDL_calloc(1, sizeof(MetalShader));
result->library = libraryFunction.library;
result->function = libraryFunction.function;
result->stage = createinfo->stage;
result->numSamplers = createinfo->num_samplers;
result->numStorageBuffers = createinfo->num_storage_buffers;
result->numStorageTextures = createinfo->num_storage_textures;
result->numUniformBuffers = createinfo->num_uniform_buffers;
return (SDL_GPUShader *)result;
}
}
static MetalTexture *METAL_INTERNAL_CreateTexture(
MetalRenderer *renderer,
const SDL_GPUTextureCreateInfo *createinfo)
{
MTLTextureDescriptor *textureDescriptor = [MTLTextureDescriptor new];
id<MTLTexture> texture;
MetalTexture *metalTexture;
textureDescriptor.textureType = SDLToMetal_TextureType(createinfo->type, createinfo->sample_count > SDL_GPU_SAMPLECOUNT_1);
textureDescriptor.pixelFormat = SDLToMetal_TextureFormat(createinfo->format);
if (createinfo->format == SDL_GPU_TEXTUREFORMAT_B4G4R4A4_UNORM) {
if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) {
textureDescriptor.swizzle = MTLTextureSwizzleChannelsMake(MTLTextureSwizzleBlue,
MTLTextureSwizzleGreen,
MTLTextureSwizzleRed,
MTLTextureSwizzleAlpha);
} else {
SET_STRING_ERROR_AND_RETURN("SDL_GPU_TEXTUREFORMAT_B4G4R4A4_UNORM is not supported", NULL);
}
}
textureDescriptor.width = createinfo->width;
textureDescriptor.height = createinfo->height;
textureDescriptor.depth = (createinfo->type == SDL_GPU_TEXTURETYPE_3D) ? createinfo->layer_count_or_depth : 1;
textureDescriptor.mipmapLevelCount = createinfo->num_levels;
textureDescriptor.sampleCount = SDLToMetal_SampleCount[createinfo->sample_count];
textureDescriptor.arrayLength =
(createinfo->type == SDL_GPU_TEXTURETYPE_2D_ARRAY || createinfo->type == SDL_GPU_TEXTURETYPE_CUBE_ARRAY)
? createinfo->layer_count_or_depth
: 1;
textureDescriptor.storageMode = MTLStorageModePrivate;
textureDescriptor.usage = 0;
if (createinfo->usage & (SDL_GPU_TEXTUREUSAGE_COLOR_TARGET |
SDL_GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET)) {
textureDescriptor.usage |= MTLTextureUsageRenderTarget;
}
if (createinfo->usage & (SDL_GPU_TEXTUREUSAGE_SAMPLER |
SDL_GPU_TEXTUREUSAGE_GRAPHICS_STORAGE_READ |
SDL_GPU_TEXTUREUSAGE_COMPUTE_STORAGE_READ)) {
textureDescriptor.usage |= MTLTextureUsageShaderRead;
}
if (createinfo->usage & (SDL_GPU_TEXTUREUSAGE_COMPUTE_STORAGE_WRITE |
SDL_GPU_TEXTUREUSAGE_COMPUTE_STORAGE_SIMULTANEOUS_READ_WRITE)) {
textureDescriptor.usage |= MTLTextureUsageShaderWrite;
}
texture = [renderer->device newTextureWithDescriptor:textureDescriptor];
if (texture == NULL) {
SDL_LogError(SDL_LOG_CATEGORY_GPU, "Failed to create MTLTexture!");
return NULL;
}
metalTexture = (MetalTexture *)SDL_calloc(1, sizeof(MetalTexture));
metalTexture->handle = texture;
SDL_SetAtomicInt(&metalTexture->referenceCount, 0);
if (renderer->debugMode && SDL_HasProperty(createinfo->props, SDL_PROP_GPU_TEXTURE_CREATE_NAME_STRING)) {
metalTexture->handle.label = @(SDL_GetStringProperty(createinfo->props, SDL_PROP_GPU_TEXTURE_CREATE_NAME_STRING, NULL));
}
return metalTexture;
}
static bool METAL_SupportsSampleCount(
SDL_GPURenderer *driverData,
SDL_GPUTextureFormat format,
SDL_GPUSampleCount sampleCount)
{
@autoreleasepool {
MetalRenderer *renderer = (MetalRenderer *)driverData;
NSUInteger mtlSampleCount = SDLToMetal_SampleCount[sampleCount];
return [renderer->device supportsTextureSampleCount:mtlSampleCount];
}
}
static SDL_GPUTexture *METAL_CreateTexture(
SDL_GPURenderer *driverData,
const SDL_GPUTextureCreateInfo *createinfo)
{
@autoreleasepool {
MetalRenderer *renderer = (MetalRenderer *)driverData;
MetalTextureContainer *container;
MetalTexture *texture;
texture = METAL_INTERNAL_CreateTexture(
renderer,
createinfo);
if (texture == NULL) {
SET_STRING_ERROR_AND_RETURN("Failed to create texture", NULL);
}
container = SDL_calloc(1, sizeof(MetalTextureContainer));
container->canBeCycled = 1;
container->header.info = *createinfo;
container->header.info.props = SDL_CreateProperties();
if (createinfo->props) {
SDL_CopyProperties(createinfo->props, container->header.info.props);
}
container->activeTexture = texture;
container->textureCapacity = 1;
container->textureCount = 1;
container->textures = SDL_calloc(
container->textureCapacity, sizeof(MetalTexture *));
container->textures[0] = texture;
container->debugName = NULL;
if (SDL_HasProperty(createinfo->props, SDL_PROP_GPU_TEXTURE_CREATE_NAME_STRING)) {
container->debugName = SDL_strdup(SDL_GetStringProperty(createinfo->props, SDL_PROP_GPU_TEXTURE_CREATE_NAME_STRING, NULL));
}
return (SDL_GPUTexture *)container;
}
}
static MetalTexture *METAL_INTERNAL_PrepareTextureForWrite(
MetalRenderer *renderer,
MetalTextureContainer *container,
bool cycle)
{
Uint32 i;
if (cycle && container->canBeCycled) {
for (i = 0; i < container->textureCount; i += 1) {
if (SDL_GetAtomicInt(&container->textures[i]->referenceCount) == 0) {
container->activeTexture = container->textures[i];
return container->activeTexture;
}
}
EXPAND_ARRAY_IF_NEEDED(
container->textures,
MetalTexture *,
container->textureCount + 1,
container->textureCapacity,
container->textureCapacity + 1);
container->textures[container->textureCount] = METAL_INTERNAL_CreateTexture(
renderer,
&container->header.info);
container->textureCount += 1;
container->activeTexture = container->textures[container->textureCount - 1];
}
return container->activeTexture;
}
static MetalBuffer *METAL_INTERNAL_CreateBuffer(
MetalRenderer *renderer,
Uint32 size,
MTLResourceOptions resourceOptions,
const char *debugName)
{
id<MTLBuffer> bufferHandle;
MetalBuffer *metalBuffer;
size = METAL_INTERNAL_NextHighestAlignment(size, 4);
bufferHandle = [renderer->device newBufferWithLength:size options:resourceOptions];
if (bufferHandle == NULL) {
SDL_LogError(SDL_LOG_CATEGORY_GPU, "Could not create buffer");
return NULL;
}
metalBuffer = SDL_calloc(1, sizeof(MetalBuffer));
metalBuffer->handle = bufferHandle;
SDL_SetAtomicInt(&metalBuffer->referenceCount, 0);
if (debugName != NULL) {
metalBuffer->handle.label = @(debugName);
}
return metalBuffer;
}
static MetalBufferContainer *METAL_INTERNAL_CreateBufferContainer(
MetalRenderer *renderer,
Uint32 size,
bool isPrivate,
bool isWriteOnly,
const char *debugName)
{
MetalBufferContainer *container = SDL_calloc(1, sizeof(MetalBufferContainer));
MTLResourceOptions resourceOptions;
container->size = size;
container->bufferCapacity = 1;
container->bufferCount = 1;
container->buffers = SDL_calloc(
container->bufferCapacity, sizeof(MetalBuffer *));
container->isPrivate = isPrivate;
container->isWriteOnly = isWriteOnly;
container->debugName = NULL;
if (container->debugName != NULL) {
container->debugName = SDL_strdup(debugName);
}
if (isPrivate) {
resourceOptions = MTLResourceStorageModePrivate;
} else {
if (isWriteOnly) {
resourceOptions = MTLResourceCPUCacheModeWriteCombined;
} else {
resourceOptions = MTLResourceCPUCacheModeDefaultCache;
}
}
container->buffers[0] = METAL_INTERNAL_CreateBuffer(
renderer,
size,
resourceOptions,
debugName);
container->activeBuffer = container->buffers[0];
return container;
}
static SDL_GPUBuffer *METAL_CreateBuffer(
SDL_GPURenderer *driverData,
SDL_GPUBufferUsageFlags usage,
Uint32 size,
const char *debugName)
{
@autoreleasepool {
return (SDL_GPUBuffer *)METAL_INTERNAL_CreateBufferContainer(
(MetalRenderer *)driverData,
size,
true,
false,
debugName);
}
}
static SDL_GPUTransferBuffer *METAL_CreateTransferBuffer(
SDL_GPURenderer *driverData,
SDL_GPUTransferBufferUsage usage,
Uint32 size,
const char *debugName)
{
@autoreleasepool {
return (SDL_GPUTransferBuffer *)METAL_INTERNAL_CreateBufferContainer(
(MetalRenderer *)driverData,
size,
false,
usage == SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD,
debugName);
}
}
static MetalUniformBuffer *METAL_INTERNAL_CreateUniformBuffer(
MetalRenderer *renderer,
Uint32 size)
{
MetalUniformBuffer *uniformBuffer;
id<MTLBuffer> bufferHandle;
bufferHandle = [renderer->device newBufferWithLength:size options:MTLResourceCPUCacheModeWriteCombined];
if (bufferHandle == nil) {
SDL_LogError(SDL_LOG_CATEGORY_GPU, "Could not create uniform buffer");
return NULL;
}
uniformBuffer = SDL_calloc(1, sizeof(MetalUniformBuffer));
uniformBuffer->handle = bufferHandle;
uniformBuffer->writeOffset = 0;
uniformBuffer->drawOffset = 0;
return uniformBuffer;
}
static MetalBuffer *METAL_INTERNAL_PrepareBufferForWrite(
MetalRenderer *renderer,
MetalBufferContainer *container,
bool cycle)
{
MTLResourceOptions resourceOptions;
Uint32 i;
if (cycle && SDL_GetAtomicInt(&container->activeBuffer->referenceCount) > 0) {
for (i = 0; i < container->bufferCount; i += 1) {
if (SDL_GetAtomicInt(&container->buffers[i]->referenceCount) == 0) {
container->activeBuffer = container->buffers[i];
return container->activeBuffer;
}
}
EXPAND_ARRAY_IF_NEEDED(
container->buffers,
MetalBuffer *,
container->bufferCount + 1,
container->bufferCapacity,
container->bufferCapacity + 1);
if (container->isPrivate) {
resourceOptions = MTLResourceStorageModePrivate;
} else {
if (container->isWriteOnly) {
resourceOptions = MTLResourceCPUCacheModeWriteCombined;
} else {
resourceOptions = MTLResourceCPUCacheModeDefaultCache;
}
}
container->buffers[container->bufferCount] = METAL_INTERNAL_CreateBuffer(
renderer,
container->size,
resourceOptions,
container->debugName);
container->bufferCount += 1;
container->activeBuffer = container->buffers[container->bufferCount - 1];
}
return container->activeBuffer;
}
static void *METAL_MapTransferBuffer(
SDL_GPURenderer *driverData,
SDL_GPUTransferBuffer *transferBuffer,
bool cycle)
{
@autoreleasepool {
MetalRenderer *renderer = (MetalRenderer *)driverData;
MetalBufferContainer *container = (MetalBufferContainer *)transferBuffer;
MetalBuffer *buffer = METAL_INTERNAL_PrepareBufferForWrite(renderer, container, cycle);
return [buffer->handle contents];
}
}
static void METAL_UnmapTransferBuffer(
SDL_GPURenderer *driverData,
SDL_GPUTransferBuffer *transferBuffer)
{
#ifdef SDL_PLATFORM_MACOS
@autoreleasepool {
MetalBufferContainer *container = (MetalBufferContainer *)transferBuffer;
MetalBuffer *buffer = container->activeBuffer;
if (buffer->handle.storageMode == MTLStorageModeManaged) {
[buffer->handle didModifyRange:NSMakeRange(0, container->size)];
}
}
#endif
}
static void METAL_BeginCopyPass(
SDL_GPUCommandBuffer *commandBuffer)
{
@autoreleasepool {
MetalCommandBuffer *metalCommandBuffer = (MetalCommandBuffer *)commandBuffer;
metalCommandBuffer->blitEncoder = [metalCommandBuffer->handle blitCommandEncoder];
}
}
static void METAL_UploadToTexture(
SDL_GPUCommandBuffer *commandBuffer,
const SDL_GPUTextureTransferInfo *source,
const SDL_GPUTextureRegion *destination,
bool cycle)
{
@autoreleasepool {
MetalCommandBuffer *metalCommandBuffer = (MetalCommandBuffer *)commandBuffer;
MetalRenderer *renderer = metalCommandBuffer->renderer;
MetalBufferContainer *bufferContainer = (MetalBufferContainer *)source->transfer_buffer;
MetalTextureContainer *textureContainer = (MetalTextureContainer *)destination->texture;
MetalTexture *metalTexture = METAL_INTERNAL_PrepareTextureForWrite(renderer, textureContainer, cycle);
[metalCommandBuffer->blitEncoder
copyFromBuffer:bufferContainer->activeBuffer->handle
sourceOffset:source->offset
sourceBytesPerRow:BytesPerRow(destination->w, textureContainer->header.info.format)
sourceBytesPerImage:SDL_CalculateGPUTextureFormatSize(textureContainer->header.info.format, destination->w, destination->h, 1)
sourceSize:MTLSizeMake(destination->w, destination->h, destination->d)
toTexture:metalTexture->handle
destinationSlice:destination->layer
destinationLevel:destination->mip_level
destinationOrigin:MTLOriginMake(destination->x, destination->y, destination->z)];
METAL_INTERNAL_TrackTexture(metalCommandBuffer, metalTexture);
METAL_INTERNAL_TrackBuffer(metalCommandBuffer, bufferContainer->activeBuffer);
}
}
static void METAL_UploadToBuffer(
SDL_GPUCommandBuffer *commandBuffer,
const SDL_GPUTransferBufferLocation *source,
const SDL_GPUBufferRegion *destination,
bool cycle)
{
@autoreleasepool {
MetalCommandBuffer *metalCommandBuffer = (MetalCommandBuffer *)commandBuffer;
MetalRenderer *renderer = metalCommandBuffer->renderer;
MetalBufferContainer *transferContainer = (MetalBufferContainer *)source->transfer_buffer;
MetalBufferContainer *bufferContainer = (MetalBufferContainer *)destination->buffer;
MetalBuffer *metalBuffer = METAL_INTERNAL_PrepareBufferForWrite(
renderer,
bufferContainer,
cycle);
[metalCommandBuffer->blitEncoder
copyFromBuffer:transferContainer->activeBuffer->handle
sourceOffset:source->offset
toBuffer:metalBuffer->handle
destinationOffset:destination->offset
size:destination->size];
METAL_INTERNAL_TrackBuffer(metalCommandBuffer, metalBuffer);
METAL_INTERNAL_TrackBuffer(metalCommandBuffer, transferContainer->activeBuffer);
}
}
static void METAL_CopyTextureToTexture(
SDL_GPUCommandBuffer *commandBuffer,
const SDL_GPUTextureLocation *source,
const SDL_GPUTextureLocation *destination,
Uint32 w,
Uint32 h,
Uint32 d,
bool cycle)
{
@autoreleasepool {
MetalCommandBuffer *metalCommandBuffer = (MetalCommandBuffer *)commandBuffer;
MetalRenderer *renderer = metalCommandBuffer->renderer;
MetalTextureContainer *srcContainer = (MetalTextureContainer *)source->texture;
MetalTextureContainer *dstContainer = (MetalTextureContainer *)destination->texture;
MetalTexture *srcTexture = srcContainer->activeTexture;
MetalTexture *dstTexture = METAL_INTERNAL_PrepareTextureForWrite(
renderer,
dstContainer,
cycle);
[metalCommandBuffer->blitEncoder
copyFromTexture:srcTexture->handle
sourceSlice:source->layer
sourceLevel:source->mip_level
sourceOrigin:MTLOriginMake(source->x, source->y, source->z)
sourceSize:MTLSizeMake(w, h, d)
toTexture:dstTexture->handle
destinationSlice:destination->layer
destinationLevel:destination->mip_level
destinationOrigin:MTLOriginMake(destination->x, destination->y, destination->z)];
METAL_INTERNAL_TrackTexture(metalCommandBuffer, srcTexture);
METAL_INTERNAL_TrackTexture(metalCommandBuffer, dstTexture);
}
}
static void METAL_CopyBufferToBuffer(
SDL_GPUCommandBuffer *commandBuffer,
const SDL_GPUBufferLocation *source,
const SDL_GPUBufferLocation *destination,
Uint32 size,
bool cycle)
{
@autoreleasepool {
MetalCommandBuffer *metalCommandBuffer = (MetalCommandBuffer *)commandBuffer;
MetalRenderer *renderer = metalCommandBuffer->renderer;
MetalBufferContainer *srcContainer = (MetalBufferContainer *)source->buffer;
MetalBufferContainer *dstContainer = (MetalBufferContainer *)destination->buffer;
MetalBuffer *srcBuffer = srcContainer->activeBuffer;
MetalBuffer *dstBuffer = METAL_INTERNAL_PrepareBufferForWrite(
renderer,
dstContainer,
cycle);
[metalCommandBuffer->blitEncoder
copyFromBuffer:srcBuffer->handle
sourceOffset:source->offset
toBuffer:dstBuffer->handle
destinationOffset:destination->offset
size:size];
METAL_INTERNAL_TrackBuffer(metalCommandBuffer, srcBuffer);
METAL_INTERNAL_TrackBuffer(metalCommandBuffer, dstBuffer);
}
}
static void METAL_DownloadFromTexture(
SDL_GPUCommandBuffer *commandBuffer,
const SDL_GPUTextureRegion *source,
const SDL_GPUTextureTransferInfo *destination)
{
@autoreleasepool {
MetalCommandBuffer *metalCommandBuffer = (MetalCommandBuffer *)commandBuffer;
MetalRenderer *renderer = metalCommandBuffer->renderer;
MetalTextureContainer *textureContainer = (MetalTextureContainer *)source->texture;
MetalTexture *metalTexture = textureContainer->activeTexture;
MetalBufferContainer *bufferContainer = (MetalBufferContainer *)destination->transfer_buffer;
Uint32 bufferStride = destination->pixels_per_row;
Uint32 bufferImageHeight = destination->rows_per_layer;
Uint32 bytesPerRow, bytesPerDepthSlice;
MetalBuffer *dstBuffer = METAL_INTERNAL_PrepareBufferForWrite(
renderer,
bufferContainer,
false);
MTLOrigin regionOrigin = MTLOriginMake(
source->x,
source->y,
source->z);
MTLSize regionSize = MTLSizeMake(
source->w,
source->h,
source->d);
if (bufferStride == 0 || bufferImageHeight == 0) {
bufferStride = source->w;
bufferImageHeight = source->h;
}
bytesPerRow = BytesPerRow(bufferStride, textureContainer->header.info.format);
bytesPerDepthSlice = bytesPerRow * bufferImageHeight;
[metalCommandBuffer->blitEncoder
copyFromTexture:metalTexture->handle
sourceSlice:source->layer
sourceLevel:source->mip_level
sourceOrigin:regionOrigin
sourceSize:regionSize
toBuffer:dstBuffer->handle
destinationOffset:destination->offset
destinationBytesPerRow:bytesPerRow
destinationBytesPerImage:bytesPerDepthSlice];
METAL_INTERNAL_TrackTexture(metalCommandBuffer, metalTexture);
METAL_INTERNAL_TrackBuffer(metalCommandBuffer, dstBuffer);
}
}
static void METAL_DownloadFromBuffer(
SDL_GPUCommandBuffer *commandBuffer,
const SDL_GPUBufferRegion *source,
const SDL_GPUTransferBufferLocation *destination)
{
SDL_GPUBufferLocation sourceLocation;
sourceLocation.buffer = source->buffer;
sourceLocation.offset = source->offset;
METAL_CopyBufferToBuffer(
commandBuffer,
&sourceLocation,
(SDL_GPUBufferLocation *)destination,
source->size,
false);
}
static void METAL_EndCopyPass(
SDL_GPUCommandBuffer *commandBuffer)
{
@autoreleasepool {
MetalCommandBuffer *metalCommandBuffer = (MetalCommandBuffer *)commandBuffer;
[metalCommandBuffer->blitEncoder endEncoding];
metalCommandBuffer->blitEncoder = nil;
}
}
static void METAL_GenerateMipmaps(
SDL_GPUCommandBuffer *commandBuffer,
SDL_GPUTexture *texture)
{
@autoreleasepool {
MetalCommandBuffer *metalCommandBuffer = (MetalCommandBuffer *)commandBuffer;
MetalTextureContainer *container = (MetalTextureContainer *)texture;
MetalTexture *metalTexture = container->activeTexture;
METAL_BeginCopyPass(commandBuffer);
[metalCommandBuffer->blitEncoder
generateMipmapsForTexture:metalTexture->handle];
METAL_EndCopyPass(commandBuffer);
METAL_INTERNAL_TrackTexture(metalCommandBuffer, metalTexture);
}
}
static void METAL_INTERNAL_AllocateCommandBuffers(
MetalRenderer *renderer,
Uint32 allocateCount)
{
MetalCommandBuffer *commandBuffer;
renderer->availableCommandBufferCapacity += allocateCount;
renderer->availableCommandBuffers = SDL_realloc(
renderer->availableCommandBuffers,
sizeof(MetalCommandBuffer *) * renderer->availableCommandBufferCapacity);
for (Uint32 i = 0; i < allocateCount; i += 1) {
commandBuffer = SDL_calloc(1, sizeof(MetalCommandBuffer));
commandBuffer->renderer = renderer;
commandBuffer->windowDataCapacity = 1;
commandBuffer->windowDataCount = 0;
commandBuffer->windowDatas = SDL_calloc(
commandBuffer->windowDataCapacity, sizeof(MetalWindowData *));
commandBuffer->usedBufferCapacity = 4;
commandBuffer->usedBufferCount = 0;
commandBuffer->usedBuffers = SDL_calloc(
commandBuffer->usedBufferCapacity, sizeof(MetalBuffer *));
commandBuffer->usedTextureCapacity = 4;
commandBuffer->usedTextureCount = 0;
commandBuffer->usedTextures = SDL_calloc(
commandBuffer->usedTextureCapacity, sizeof(MetalTexture *));
renderer->availableCommandBuffers[renderer->availableCommandBufferCount] = commandBuffer;
renderer->availableCommandBufferCount += 1;
}
}
static MetalCommandBuffer *METAL_INTERNAL_GetInactiveCommandBufferFromPool(
MetalRenderer *renderer)
{
MetalCommandBuffer *commandBuffer;
if (renderer->availableCommandBufferCount == 0) {
METAL_INTERNAL_AllocateCommandBuffers(
renderer,
renderer->availableCommandBufferCapacity);
}
commandBuffer = renderer->availableCommandBuffers[renderer->availableCommandBufferCount - 1];
renderer->availableCommandBufferCount -= 1;
return commandBuffer;
}
static Uint8 METAL_INTERNAL_CreateFence(
MetalRenderer *renderer)
{
MetalFence *fence;
fence = SDL_calloc(1, sizeof(MetalFence));
SDL_SetAtomicInt(&fence->complete, 0);
SDL_SetAtomicInt(&fence->referenceCount, 0);
if (renderer->availableFenceCount >= renderer->availableFenceCapacity) {
renderer->availableFenceCapacity *= 2;
renderer->availableFences = SDL_realloc(
renderer->availableFences,
sizeof(MetalFence *) * renderer->availableFenceCapacity);
}
renderer->availableFences[renderer->availableFenceCount] = fence;
renderer->availableFenceCount += 1;
return 1;
}
static bool METAL_INTERNAL_AcquireFence(
MetalRenderer *renderer,
MetalCommandBuffer *commandBuffer)
{
MetalFence *fence;
SDL_LockMutex(renderer->fenceLock);
if (renderer->availableFenceCount == 0) {
if (!METAL_INTERNAL_CreateFence(renderer)) {
SDL_UnlockMutex(renderer->fenceLock);
SDL_LogError(SDL_LOG_CATEGORY_GPU, "Failed to create fence!");
return false;
}
}
fence = renderer->availableFences[renderer->availableFenceCount - 1];
renderer->availableFenceCount -= 1;
SDL_UnlockMutex(renderer->fenceLock);
commandBuffer->fence = fence;
SDL_SetAtomicInt(&fence->complete, 0); (void)SDL_AtomicIncRef(&commandBuffer->fence->referenceCount);
return true;
}
static SDL_GPUCommandBuffer *METAL_AcquireCommandBuffer(
SDL_GPURenderer *driverData)
{
@autoreleasepool {
MetalRenderer *renderer = (MetalRenderer *)driverData;
MetalCommandBuffer *commandBuffer;
SDL_LockMutex(renderer->acquireCommandBufferLock);
commandBuffer = METAL_INTERNAL_GetInactiveCommandBufferFromPool(renderer);
commandBuffer->handle = [renderer->queue commandBuffer];
commandBuffer->graphics_pipeline = NULL;
commandBuffer->compute_pipeline = NULL;
for (Uint32 i = 0; i < MAX_UNIFORM_BUFFERS_PER_STAGE; i += 1) {
commandBuffer->vertexUniformBuffers[i] = NULL;
commandBuffer->fragmentUniformBuffers[i] = NULL;
commandBuffer->computeUniformBuffers[i] = NULL;
}
commandBuffer->autoReleaseFence = true;
SDL_UnlockMutex(renderer->acquireCommandBufferLock);
return (SDL_GPUCommandBuffer *)commandBuffer;
}
}
static MetalUniformBuffer *METAL_INTERNAL_AcquireUniformBufferFromPool(
MetalCommandBuffer *commandBuffer)
{
MetalRenderer *renderer = commandBuffer->renderer;
MetalUniformBuffer *uniformBuffer;
SDL_LockMutex(renderer->acquireUniformBufferLock);
if (renderer->uniformBufferPoolCount > 0) {
uniformBuffer = renderer->uniformBufferPool[renderer->uniformBufferPoolCount - 1];
renderer->uniformBufferPoolCount -= 1;
} else {
uniformBuffer = METAL_INTERNAL_CreateUniformBuffer(
renderer,
UNIFORM_BUFFER_SIZE);
}
SDL_UnlockMutex(renderer->acquireUniformBufferLock);
METAL_INTERNAL_TrackUniformBuffer(commandBuffer, uniformBuffer);
return uniformBuffer;
}
static void METAL_INTERNAL_ReturnUniformBufferToPool(
MetalRenderer *renderer,
MetalUniformBuffer *uniformBuffer)
{
if (renderer->uniformBufferPoolCount >= renderer->uniformBufferPoolCapacity) {
renderer->uniformBufferPoolCapacity *= 2;
renderer->uniformBufferPool = SDL_realloc(
renderer->uniformBufferPool,
renderer->uniformBufferPoolCapacity * sizeof(MetalUniformBuffer *));
}
renderer->uniformBufferPool[renderer->uniformBufferPoolCount] = uniformBuffer;
renderer->uniformBufferPoolCount += 1;
uniformBuffer->writeOffset = 0;
uniformBuffer->drawOffset = 0;
}
static void METAL_SetViewport(
SDL_GPUCommandBuffer *commandBuffer,
const SDL_GPUViewport *viewport)
{
@autoreleasepool {
MetalCommandBuffer *metalCommandBuffer = (MetalCommandBuffer *)commandBuffer;
MTLViewport metalViewport;
metalViewport.originX = viewport->x;
metalViewport.originY = viewport->y;
metalViewport.width = viewport->w;
metalViewport.height = viewport->h;
metalViewport.znear = viewport->min_depth;
metalViewport.zfar = viewport->max_depth;
[metalCommandBuffer->renderEncoder setViewport:metalViewport];
}
}
static void METAL_SetScissor(
SDL_GPUCommandBuffer *commandBuffer,
const SDL_Rect *scissor)
{
@autoreleasepool {
MetalCommandBuffer *metalCommandBuffer = (MetalCommandBuffer *)commandBuffer;
MTLScissorRect metalScissor;
metalScissor.x = scissor->x;
metalScissor.y = scissor->y;
metalScissor.width = scissor->w;
metalScissor.height = scissor->h;
[metalCommandBuffer->renderEncoder setScissorRect:metalScissor];
}
}
static void METAL_SetBlendConstants(
SDL_GPUCommandBuffer *commandBuffer,
SDL_FColor blendConstants)
{
@autoreleasepool {
MetalCommandBuffer *metalCommandBuffer = (MetalCommandBuffer *)commandBuffer;
[metalCommandBuffer->renderEncoder setBlendColorRed:blendConstants.r
green:blendConstants.g
blue:blendConstants.b
alpha:blendConstants.a];
}
}
static void METAL_SetStencilReference(
SDL_GPUCommandBuffer *commandBuffer,
Uint8 reference)
{
@autoreleasepool {
MetalCommandBuffer *metalCommandBuffer = (MetalCommandBuffer *)commandBuffer;
[metalCommandBuffer->renderEncoder setStencilReferenceValue:reference];
}
}
static void METAL_BeginRenderPass(
SDL_GPUCommandBuffer *commandBuffer,
const SDL_GPUColorTargetInfo *colorTargetInfos,
Uint32 numColorTargets,
const SDL_GPUDepthStencilTargetInfo *depthStencilTargetInfo)
{
@autoreleasepool {
MetalCommandBuffer *metalCommandBuffer = (MetalCommandBuffer *)commandBuffer;
MetalRenderer *renderer = metalCommandBuffer->renderer;
MTLRenderPassDescriptor *passDescriptor = [MTLRenderPassDescriptor renderPassDescriptor];
Uint32 vpWidth = UINT_MAX;
Uint32 vpHeight = UINT_MAX;
SDL_GPUViewport viewport;
SDL_Rect scissorRect;
SDL_FColor blendConstants;
for (Uint32 i = 0; i < numColorTargets; i += 1) {
MetalTextureContainer *container = (MetalTextureContainer *)colorTargetInfos[i].texture;
MetalTexture *texture = METAL_INTERNAL_PrepareTextureForWrite(
renderer,
container,
colorTargetInfos[i].cycle);
passDescriptor.colorAttachments[i].texture = texture->handle;
passDescriptor.colorAttachments[i].level = colorTargetInfos[i].mip_level;
if (container->header.info.type == SDL_GPU_TEXTURETYPE_3D) {
passDescriptor.colorAttachments[i].depthPlane = colorTargetInfos[i].layer_or_depth_plane;
} else {
passDescriptor.colorAttachments[i].slice = colorTargetInfos[i].layer_or_depth_plane;
}
passDescriptor.colorAttachments[i].clearColor = MTLClearColorMake(
colorTargetInfos[i].clear_color.r,
colorTargetInfos[i].clear_color.g,
colorTargetInfos[i].clear_color.b,
colorTargetInfos[i].clear_color.a);
passDescriptor.colorAttachments[i].loadAction = SDLToMetal_LoadOp[colorTargetInfos[i].load_op];
passDescriptor.colorAttachments[i].storeAction = SDLToMetal_StoreOp[colorTargetInfos[i].store_op];
METAL_INTERNAL_TrackTexture(metalCommandBuffer, texture);
if (colorTargetInfos[i].store_op == SDL_GPU_STOREOP_RESOLVE || colorTargetInfos[i].store_op == SDL_GPU_STOREOP_RESOLVE_AND_STORE) {
MetalTextureContainer *resolveContainer = (MetalTextureContainer *)colorTargetInfos[i].resolve_texture;
MetalTexture *resolveTexture = METAL_INTERNAL_PrepareTextureForWrite(
renderer,
resolveContainer,
colorTargetInfos[i].cycle_resolve_texture);
passDescriptor.colorAttachments[i].resolveTexture = resolveTexture->handle;
passDescriptor.colorAttachments[i].resolveSlice = colorTargetInfos[i].resolve_layer;
passDescriptor.colorAttachments[i].resolveLevel = colorTargetInfos[i].resolve_mip_level;
METAL_INTERNAL_TrackTexture(metalCommandBuffer, resolveTexture);
}
}
if (depthStencilTargetInfo != NULL) {
MetalTextureContainer *container = (MetalTextureContainer *)depthStencilTargetInfo->texture;
MetalTexture *texture = METAL_INTERNAL_PrepareTextureForWrite(
renderer,
container,
depthStencilTargetInfo->cycle);
passDescriptor.depthAttachment.texture = texture->handle;
passDescriptor.depthAttachment.level = depthStencilTargetInfo->mip_level;
passDescriptor.depthAttachment.slice = depthStencilTargetInfo->layer;
passDescriptor.depthAttachment.loadAction = SDLToMetal_LoadOp[depthStencilTargetInfo->load_op];
passDescriptor.depthAttachment.storeAction = SDLToMetal_StoreOp[depthStencilTargetInfo->store_op];
passDescriptor.depthAttachment.clearDepth = depthStencilTargetInfo->clear_depth;
if (IsStencilFormat(container->header.info.format)) {
passDescriptor.stencilAttachment.texture = texture->handle;
passDescriptor.stencilAttachment.loadAction = SDLToMetal_LoadOp[depthStencilTargetInfo->stencil_load_op];
passDescriptor.stencilAttachment.storeAction = SDLToMetal_StoreOp[depthStencilTargetInfo->stencil_store_op];
passDescriptor.stencilAttachment.clearStencil = depthStencilTargetInfo->clear_stencil;
}
METAL_INTERNAL_TrackTexture(metalCommandBuffer, texture);
}
metalCommandBuffer->renderEncoder = [metalCommandBuffer->handle renderCommandEncoderWithDescriptor:passDescriptor];
for (Uint32 i = 0; i < numColorTargets; i += 1) {
MetalTextureContainer *container = (MetalTextureContainer *)colorTargetInfos[i].texture;
Uint32 w = container->header.info.width >> colorTargetInfos[i].mip_level;
Uint32 h = container->header.info.height >> colorTargetInfos[i].mip_level;
if (w < vpWidth) {
vpWidth = w;
}
if (h < vpHeight) {
vpHeight = h;
}
}
if (depthStencilTargetInfo != NULL) {
MetalTextureContainer *container = (MetalTextureContainer *)depthStencilTargetInfo->texture;
Uint32 w = container->header.info.width >> depthStencilTargetInfo->mip_level;
Uint32 h = container->header.info.height >> depthStencilTargetInfo->mip_level;
if (w < vpWidth) {
vpWidth = w;
}
if (h < vpHeight) {
vpHeight = h;
}
}
viewport.x = 0;
viewport.y = 0;
viewport.w = vpWidth;
viewport.h = vpHeight;
viewport.min_depth = 0;
viewport.max_depth = 1;
METAL_SetViewport(commandBuffer, &viewport);
scissorRect.x = 0;
scissorRect.y = 0;
scissorRect.w = vpWidth;
scissorRect.h = vpHeight;
METAL_SetScissor(commandBuffer, &scissorRect);
blendConstants.r = 1.0f;
blendConstants.g = 1.0f;
blendConstants.b = 1.0f;
blendConstants.a = 1.0f;
METAL_SetBlendConstants(
commandBuffer,
blendConstants);
METAL_SetStencilReference(
commandBuffer,
0);
}
}
static void METAL_BindGraphicsPipeline(
SDL_GPUCommandBuffer *commandBuffer,
SDL_GPUGraphicsPipeline *graphicsPipeline)
{
@autoreleasepool {
MetalCommandBuffer *metalCommandBuffer = (MetalCommandBuffer *)commandBuffer;
MetalGraphicsPipeline *previousPipeline = metalCommandBuffer->graphics_pipeline;
MetalGraphicsPipeline *pipeline = (MetalGraphicsPipeline *)graphicsPipeline;
SDL_GPURasterizerState *rast = &pipeline->rasterizerState;
Uint32 i;
metalCommandBuffer->graphics_pipeline = pipeline;
[metalCommandBuffer->renderEncoder setRenderPipelineState:pipeline->handle];
[metalCommandBuffer->renderEncoder setTriangleFillMode:SDLToMetal_PolygonMode[pipeline->rasterizerState.fill_mode]];
[metalCommandBuffer->renderEncoder setCullMode:SDLToMetal_CullMode[pipeline->rasterizerState.cull_mode]];
[metalCommandBuffer->renderEncoder setFrontFacingWinding:SDLToMetal_FrontFace[pipeline->rasterizerState.front_face]];
#ifndef SDL_PLATFORM_VISIONOS
[metalCommandBuffer->renderEncoder setDepthClipMode:SDLToMetal_DepthClipMode(pipeline->rasterizerState.enable_depth_clip)];
#endif
[metalCommandBuffer->renderEncoder
setDepthBias:((rast->enable_depth_bias) ? rast->depth_bias_constant_factor : 0)
slopeScale:((rast->enable_depth_bias) ? rast->depth_bias_slope_factor : 0)
clamp:((rast->enable_depth_bias) ? rast->depth_bias_clamp : 0)];
if (pipeline->depth_stencil_state != NULL) {
[metalCommandBuffer->renderEncoder
setDepthStencilState:pipeline->depth_stencil_state];
}
for (i = 0; i < MAX_UNIFORM_BUFFERS_PER_STAGE; i += 1) {
metalCommandBuffer->needVertexUniformBufferBind[i] = true;
metalCommandBuffer->needFragmentUniformBufferBind[i] = true;
}
for (i = 0; i < pipeline->header.num_vertex_uniform_buffers; i += 1) {
if (metalCommandBuffer->vertexUniformBuffers[i] == NULL) {
metalCommandBuffer->vertexUniformBuffers[i] = METAL_INTERNAL_AcquireUniformBufferFromPool(
metalCommandBuffer);
}
}
for (i = 0; i < pipeline->header.num_fragment_uniform_buffers; i += 1) {
if (metalCommandBuffer->fragmentUniformBuffers[i] == NULL) {
metalCommandBuffer->fragmentUniformBuffers[i] = METAL_INTERNAL_AcquireUniformBufferFromPool(
metalCommandBuffer);
}
}
if (previousPipeline && previousPipeline != pipeline) {
if (previousPipeline->header.num_vertex_uniform_buffers != pipeline->header.num_vertex_uniform_buffers) {
metalCommandBuffer->needVertexStorageBufferBind = true;
}
if (previousPipeline->header.num_fragment_uniform_buffers != pipeline->header.num_fragment_uniform_buffers) {
metalCommandBuffer->needFragmentStorageBufferBind = true;
}
}
}
}
static void METAL_BindVertexBuffers(
SDL_GPUCommandBuffer *commandBuffer,
Uint32 firstSlot,
const SDL_GPUBufferBinding *bindings,
Uint32 numBindings)
{
MetalCommandBuffer *metalCommandBuffer = (MetalCommandBuffer *)commandBuffer;
for (Uint32 i = 0; i < numBindings; i += 1) {
MetalBuffer *currentBuffer = ((MetalBufferContainer *)bindings[i].buffer)->activeBuffer;
if (metalCommandBuffer->vertexBuffers[firstSlot + i] != currentBuffer->handle || metalCommandBuffer->vertexBufferOffsets[firstSlot + i] != bindings[i].offset) {
metalCommandBuffer->vertexBuffers[firstSlot + i] = currentBuffer->handle;
metalCommandBuffer->vertexBufferOffsets[firstSlot + i] = bindings[i].offset;
metalCommandBuffer->needVertexBufferBind = true;
METAL_INTERNAL_TrackBuffer(metalCommandBuffer, currentBuffer);
}
}
metalCommandBuffer->vertexBufferCount =
SDL_max(metalCommandBuffer->vertexBufferCount, firstSlot + numBindings);
}
static void METAL_BindIndexBuffer(
SDL_GPUCommandBuffer *commandBuffer,
const SDL_GPUBufferBinding *binding,
SDL_GPUIndexElementSize indexElementSize)
{
MetalCommandBuffer *metalCommandBuffer = (MetalCommandBuffer *)commandBuffer;
metalCommandBuffer->indexBuffer = ((MetalBufferContainer *)binding->buffer)->activeBuffer;
metalCommandBuffer->indexBufferOffset = binding->offset;
metalCommandBuffer->index_element_size = indexElementSize;
METAL_INTERNAL_TrackBuffer(metalCommandBuffer, metalCommandBuffer->indexBuffer);
}
static void METAL_BindVertexSamplers(
SDL_GPUCommandBuffer *commandBuffer,
Uint32 firstSlot,
const SDL_GPUTextureSamplerBinding *textureSamplerBindings,
Uint32 numBindings)
{
MetalCommandBuffer *metalCommandBuffer = (MetalCommandBuffer *)commandBuffer;
MetalTextureContainer *textureContainer;
MetalSampler *sampler;
for (Uint32 i = 0; i < numBindings; i += 1) {
textureContainer = (MetalTextureContainer *)textureSamplerBindings[i].texture;
sampler = (MetalSampler *)textureSamplerBindings[i].sampler;
if (metalCommandBuffer->vertexSamplers[firstSlot + i] != sampler->handle) {
metalCommandBuffer->vertexSamplers[firstSlot + i] = sampler->handle;
metalCommandBuffer->needVertexSamplerBind = true;
}
if (metalCommandBuffer->vertexTextures[firstSlot + i] != textureContainer->activeTexture->handle) {
METAL_INTERNAL_TrackTexture(
metalCommandBuffer,
textureContainer->activeTexture);
metalCommandBuffer->vertexTextures[firstSlot + i] =
textureContainer->activeTexture->handle;
metalCommandBuffer->needVertexSamplerBind = true;
}
}
}
static void METAL_BindVertexStorageTextures(
SDL_GPUCommandBuffer *commandBuffer,
Uint32 firstSlot,
SDL_GPUTexture *const *storageTextures,
Uint32 numBindings)
{
MetalCommandBuffer *metalCommandBuffer = (MetalCommandBuffer *)commandBuffer;
MetalTextureContainer *textureContainer;
for (Uint32 i = 0; i < numBindings; i += 1) {
textureContainer = (MetalTextureContainer *)storageTextures[i];
if (metalCommandBuffer->vertexStorageTextures[firstSlot + i] != textureContainer->activeTexture->handle) {
METAL_INTERNAL_TrackTexture(
metalCommandBuffer,
textureContainer->activeTexture);
metalCommandBuffer->vertexStorageTextures[firstSlot + i] =
textureContainer->activeTexture->handle;
metalCommandBuffer->needVertexStorageTextureBind = true;
}
}
}
static void METAL_BindVertexStorageBuffers(
SDL_GPUCommandBuffer *commandBuffer,
Uint32 firstSlot,
SDL_GPUBuffer *const *storageBuffers,
Uint32 numBindings)
{
MetalCommandBuffer *metalCommandBuffer = (MetalCommandBuffer *)commandBuffer;
MetalBufferContainer *bufferContainer;
for (Uint32 i = 0; i < numBindings; i += 1) {
bufferContainer = (MetalBufferContainer *)storageBuffers[i];
if (metalCommandBuffer->vertexStorageBuffers[firstSlot + i] != bufferContainer->activeBuffer->handle) {
METAL_INTERNAL_TrackBuffer(
metalCommandBuffer,
bufferContainer->activeBuffer);
metalCommandBuffer->vertexStorageBuffers[firstSlot + i] =
bufferContainer->activeBuffer->handle;
metalCommandBuffer->needVertexStorageBufferBind = true;
}
}
}
static void METAL_BindFragmentSamplers(
SDL_GPUCommandBuffer *commandBuffer,
Uint32 firstSlot,
const SDL_GPUTextureSamplerBinding *textureSamplerBindings,
Uint32 numBindings)
{
MetalCommandBuffer *metalCommandBuffer = (MetalCommandBuffer *)commandBuffer;
MetalTextureContainer *textureContainer;
MetalSampler *sampler;
for (Uint32 i = 0; i < numBindings; i += 1) {
textureContainer = (MetalTextureContainer *)textureSamplerBindings[i].texture;
sampler = (MetalSampler *)textureSamplerBindings[i].sampler;
if (metalCommandBuffer->fragmentSamplers[firstSlot + i] != sampler->handle) {
metalCommandBuffer->fragmentSamplers[firstSlot + i] = sampler->handle;
metalCommandBuffer->needFragmentSamplerBind = true;
}
if (metalCommandBuffer->fragmentTextures[firstSlot + i] != textureContainer->activeTexture->handle) {
METAL_INTERNAL_TrackTexture(
metalCommandBuffer,
textureContainer->activeTexture);
metalCommandBuffer->fragmentTextures[firstSlot + i] =
textureContainer->activeTexture->handle;
metalCommandBuffer->needFragmentSamplerBind = true;
}
}
}
static void METAL_BindFragmentStorageTextures(
SDL_GPUCommandBuffer *commandBuffer,
Uint32 firstSlot,
SDL_GPUTexture *const *storageTextures,
Uint32 numBindings)
{
MetalCommandBuffer *metalCommandBuffer = (MetalCommandBuffer *)commandBuffer;
MetalTextureContainer *textureContainer;
for (Uint32 i = 0; i < numBindings; i += 1) {
textureContainer = (MetalTextureContainer *)storageTextures[i];
if (metalCommandBuffer->fragmentStorageTextures[firstSlot + i] != textureContainer->activeTexture->handle) {
METAL_INTERNAL_TrackTexture(
metalCommandBuffer,
textureContainer->activeTexture);
metalCommandBuffer->fragmentStorageTextures[firstSlot + i] =
textureContainer->activeTexture->handle;
metalCommandBuffer->needFragmentStorageTextureBind = true;
}
}
}
static void METAL_BindFragmentStorageBuffers(
SDL_GPUCommandBuffer *commandBuffer,
Uint32 firstSlot,
SDL_GPUBuffer *const *storageBuffers,
Uint32 numBindings)
{
MetalCommandBuffer *metalCommandBuffer = (MetalCommandBuffer *)commandBuffer;
MetalBufferContainer *bufferContainer;
for (Uint32 i = 0; i < numBindings; i += 1) {
bufferContainer = (MetalBufferContainer *)storageBuffers[i];
if (metalCommandBuffer->fragmentStorageBuffers[firstSlot + i] != bufferContainer->activeBuffer->handle) {
METAL_INTERNAL_TrackBuffer(
metalCommandBuffer,
bufferContainer->activeBuffer);
metalCommandBuffer->fragmentStorageBuffers[firstSlot + i] =
bufferContainer->activeBuffer->handle;
metalCommandBuffer->needFragmentStorageBufferBind = true;
}
}
}
static void METAL_INTERNAL_BindGraphicsResources(
MetalCommandBuffer *commandBuffer)
{
MetalGraphicsPipeline *graphicsPipeline = commandBuffer->graphics_pipeline;
NSUInteger offsets[MAX_STORAGE_BUFFERS_PER_STAGE] = { 0 };
if (commandBuffer->needVertexBufferBind) {
id<MTLBuffer> metalBuffers[MAX_VERTEX_BUFFERS];
NSUInteger bufferOffsets[MAX_VERTEX_BUFFERS];
NSRange range = NSMakeRange(METAL_FIRST_VERTEX_BUFFER_SLOT, commandBuffer->vertexBufferCount);
for (Uint32 i = 0; i < commandBuffer->vertexBufferCount; i += 1) {
metalBuffers[i] = commandBuffer->vertexBuffers[i];
bufferOffsets[i] = commandBuffer->vertexBufferOffsets[i];
}
[commandBuffer->renderEncoder setVertexBuffers:metalBuffers offsets:bufferOffsets withRange:range];
commandBuffer->needVertexBufferBind = false;
}
if (commandBuffer->needVertexSamplerBind) {
if (graphicsPipeline->header.num_vertex_samplers > 0) {
[commandBuffer->renderEncoder setVertexSamplerStates:commandBuffer->vertexSamplers
withRange:NSMakeRange(0, graphicsPipeline->header.num_vertex_samplers)];
[commandBuffer->renderEncoder setVertexTextures:commandBuffer->vertexTextures
withRange:NSMakeRange(0, graphicsPipeline->header.num_vertex_samplers)];
}
commandBuffer->needVertexSamplerBind = false;
}
if (commandBuffer->needVertexStorageTextureBind) {
if (graphicsPipeline->header.num_vertex_storage_textures > 0) {
[commandBuffer->renderEncoder setVertexTextures:commandBuffer->vertexStorageTextures
withRange:NSMakeRange(graphicsPipeline->header.num_vertex_samplers,
graphicsPipeline->header.num_vertex_storage_textures)];
}
commandBuffer->needVertexStorageTextureBind = false;
}
if (commandBuffer->needVertexStorageBufferBind) {
if (graphicsPipeline->header.num_vertex_storage_buffers > 0) {
[commandBuffer->renderEncoder setVertexBuffers:commandBuffer->vertexStorageBuffers
offsets:offsets
withRange:NSMakeRange(graphicsPipeline->header.num_vertex_uniform_buffers,
graphicsPipeline->header.num_vertex_storage_buffers)];
}
commandBuffer->needVertexStorageBufferBind = false;
}
for (Uint32 i = 0; i < graphicsPipeline->header.num_vertex_uniform_buffers; i += 1) {
if (commandBuffer->needVertexUniformBufferBind[i]) {
if (graphicsPipeline->header.num_vertex_uniform_buffers > i) {
[commandBuffer->renderEncoder
setVertexBuffer:commandBuffer->vertexUniformBuffers[i]->handle
offset:commandBuffer->vertexUniformBuffers[i]->drawOffset
atIndex:i];
}
commandBuffer->needVertexUniformBufferBind[i] = false;
}
}
if (commandBuffer->needFragmentSamplerBind) {
if (graphicsPipeline->header.num_fragment_samplers > 0) {
[commandBuffer->renderEncoder setFragmentSamplerStates:commandBuffer->fragmentSamplers
withRange:NSMakeRange(0, graphicsPipeline->header.num_fragment_samplers)];
[commandBuffer->renderEncoder setFragmentTextures:commandBuffer->fragmentTextures
withRange:NSMakeRange(0, graphicsPipeline->header.num_fragment_samplers)];
}
commandBuffer->needFragmentSamplerBind = false;
}
if (commandBuffer->needFragmentStorageTextureBind) {
if (graphicsPipeline->header.num_fragment_storage_textures > 0) {
[commandBuffer->renderEncoder setFragmentTextures:commandBuffer->fragmentStorageTextures
withRange:NSMakeRange(graphicsPipeline->header.num_fragment_samplers,
graphicsPipeline->header.num_fragment_storage_textures)];
}
commandBuffer->needFragmentStorageTextureBind = false;
}
if (commandBuffer->needFragmentStorageBufferBind) {
if (graphicsPipeline->header.num_fragment_storage_buffers > 0) {
[commandBuffer->renderEncoder setFragmentBuffers:commandBuffer->fragmentStorageBuffers
offsets:offsets
withRange:NSMakeRange(graphicsPipeline->header.num_fragment_uniform_buffers,
graphicsPipeline->header.num_fragment_storage_buffers)];
}
commandBuffer->needFragmentStorageBufferBind = false;
}
for (Uint32 i = 0; i < graphicsPipeline->header.num_fragment_uniform_buffers; i += 1) {
if (commandBuffer->needFragmentUniformBufferBind[i]) {
if (graphicsPipeline->header.num_fragment_uniform_buffers > i) {
[commandBuffer->renderEncoder
setFragmentBuffer:commandBuffer->fragmentUniformBuffers[i]->handle
offset:commandBuffer->fragmentUniformBuffers[i]->drawOffset
atIndex:i];
}
commandBuffer->needFragmentUniformBufferBind[i] = false;
}
}
}
static void METAL_INTERNAL_BindComputeResources(
MetalCommandBuffer *commandBuffer)
{
MetalComputePipeline *computePipeline = commandBuffer->compute_pipeline;
NSUInteger offsets[MAX_STORAGE_BUFFERS_PER_STAGE] = { 0 };
if (commandBuffer->needComputeSamplerBind) {
if (computePipeline->header.numSamplers > 0) {
[commandBuffer->computeEncoder setTextures:commandBuffer->computeSamplerTextures
withRange:NSMakeRange(0, computePipeline->header.numSamplers)];
[commandBuffer->computeEncoder setSamplerStates:commandBuffer->computeSamplers
withRange:NSMakeRange(0, computePipeline->header.numSamplers)];
}
commandBuffer->needComputeSamplerBind = false;
}
if (commandBuffer->needComputeReadOnlyStorageTextureBind) {
if (computePipeline->header.numReadonlyStorageTextures > 0) {
[commandBuffer->computeEncoder setTextures:commandBuffer->computeReadOnlyTextures
withRange:NSMakeRange(
computePipeline->header.numSamplers,
computePipeline->header.numReadonlyStorageTextures)];
}
commandBuffer->needComputeReadOnlyStorageTextureBind = false;
}
if (commandBuffer->needComputeReadOnlyStorageBufferBind) {
if (computePipeline->header.numReadonlyStorageBuffers > 0) {
[commandBuffer->computeEncoder setBuffers:commandBuffer->computeReadOnlyBuffers
offsets:offsets
withRange:NSMakeRange(computePipeline->header.numUniformBuffers,
computePipeline->header.numReadonlyStorageBuffers)];
}
commandBuffer->needComputeReadOnlyStorageBufferBind = false;
}
for (Uint32 i = 0; i < MAX_UNIFORM_BUFFERS_PER_STAGE; i += 1) {
if (commandBuffer->needComputeUniformBufferBind[i]) {
if (computePipeline->header.numUniformBuffers > i) {
[commandBuffer->computeEncoder
setBuffer:commandBuffer->computeUniformBuffers[i]->handle
offset:commandBuffer->computeUniformBuffers[i]->drawOffset
atIndex:i];
}
}
commandBuffer->needComputeUniformBufferBind[i] = false;
}
}
static void METAL_DrawIndexedPrimitives(
SDL_GPUCommandBuffer *commandBuffer,
Uint32 numIndices,
Uint32 numInstances,
Uint32 firstIndex,
Sint32 vertexOffset,
Uint32 firstInstance)
{
@autoreleasepool {
MetalCommandBuffer *metalCommandBuffer = (MetalCommandBuffer *)commandBuffer;
SDL_GPUPrimitiveType primitiveType = metalCommandBuffer->graphics_pipeline->primitiveType;
Uint32 indexSize = IndexSize(metalCommandBuffer->index_element_size);
METAL_INTERNAL_BindGraphicsResources(metalCommandBuffer);
[metalCommandBuffer->renderEncoder
drawIndexedPrimitives:SDLToMetal_PrimitiveType[primitiveType]
indexCount:numIndices
indexType:SDLToMetal_IndexType[metalCommandBuffer->index_element_size]
indexBuffer:metalCommandBuffer->indexBuffer->handle
indexBufferOffset:metalCommandBuffer->indexBufferOffset + (firstIndex * indexSize)
instanceCount:numInstances
baseVertex:vertexOffset
baseInstance:firstInstance];
}
}
static void METAL_DrawPrimitives(
SDL_GPUCommandBuffer *commandBuffer,
Uint32 numVertices,
Uint32 numInstances,
Uint32 firstVertex,
Uint32 firstInstance)
{
@autoreleasepool {
MetalCommandBuffer *metalCommandBuffer = (MetalCommandBuffer *)commandBuffer;
SDL_GPUPrimitiveType primitiveType = metalCommandBuffer->graphics_pipeline->primitiveType;
METAL_INTERNAL_BindGraphicsResources(metalCommandBuffer);
[metalCommandBuffer->renderEncoder
drawPrimitives:SDLToMetal_PrimitiveType[primitiveType]
vertexStart:firstVertex
vertexCount:numVertices
instanceCount:numInstances
baseInstance:firstInstance];
}
}
static void METAL_DrawPrimitivesIndirect(
SDL_GPUCommandBuffer *commandBuffer,
SDL_GPUBuffer *buffer,
Uint32 offset,
Uint32 drawCount)
{
@autoreleasepool {
MetalCommandBuffer *metalCommandBuffer = (MetalCommandBuffer *)commandBuffer;
MetalBuffer *metalBuffer = ((MetalBufferContainer *)buffer)->activeBuffer;
SDL_GPUPrimitiveType primitiveType = metalCommandBuffer->graphics_pipeline->primitiveType;
METAL_INTERNAL_BindGraphicsResources(metalCommandBuffer);
for (Uint32 i = 0; i < drawCount; i += 1) {
[metalCommandBuffer->renderEncoder
drawPrimitives:SDLToMetal_PrimitiveType[primitiveType]
indirectBuffer:metalBuffer->handle
indirectBufferOffset:offset + (sizeof(SDL_GPUIndirectDrawCommand) * i)];
}
METAL_INTERNAL_TrackBuffer(metalCommandBuffer, metalBuffer);
}
}
static void METAL_DrawIndexedPrimitivesIndirect(
SDL_GPUCommandBuffer *commandBuffer,
SDL_GPUBuffer *buffer,
Uint32 offset,
Uint32 drawCount)
{
@autoreleasepool {
MetalCommandBuffer *metalCommandBuffer = (MetalCommandBuffer *)commandBuffer;
MetalBuffer *metalBuffer = ((MetalBufferContainer *)buffer)->activeBuffer;
SDL_GPUPrimitiveType primitiveType = metalCommandBuffer->graphics_pipeline->primitiveType;
METAL_INTERNAL_BindGraphicsResources(metalCommandBuffer);
for (Uint32 i = 0; i < drawCount; i += 1) {
[metalCommandBuffer->renderEncoder
drawIndexedPrimitives:SDLToMetal_PrimitiveType[primitiveType]
indexType:SDLToMetal_IndexType[metalCommandBuffer->index_element_size]
indexBuffer:metalCommandBuffer->indexBuffer->handle
indexBufferOffset:metalCommandBuffer->indexBufferOffset
indirectBuffer:metalBuffer->handle
indirectBufferOffset:offset + (sizeof(SDL_GPUIndexedIndirectDrawCommand) * i)];
}
METAL_INTERNAL_TrackBuffer(metalCommandBuffer, metalBuffer);
}
}
static void METAL_EndRenderPass(
SDL_GPUCommandBuffer *commandBuffer)
{
@autoreleasepool {
MetalCommandBuffer *metalCommandBuffer = (MetalCommandBuffer *)commandBuffer;
[metalCommandBuffer->renderEncoder endEncoding];
metalCommandBuffer->renderEncoder = nil;
for (Uint32 i = 0; i < MAX_VERTEX_BUFFERS; i += 1) {
metalCommandBuffer->vertexBuffers[i] = nil;
metalCommandBuffer->vertexBufferOffsets[i] = 0;
metalCommandBuffer->vertexBufferCount = 0;
}
for (Uint32 i = 0; i < MAX_TEXTURE_SAMPLERS_PER_STAGE; i += 1) {
metalCommandBuffer->vertexSamplers[i] = nil;
metalCommandBuffer->vertexTextures[i] = nil;
metalCommandBuffer->fragmentSamplers[i] = nil;
metalCommandBuffer->fragmentTextures[i] = nil;
}
for (Uint32 i = 0; i < MAX_STORAGE_TEXTURES_PER_STAGE; i += 1) {
metalCommandBuffer->vertexStorageTextures[i] = nil;
metalCommandBuffer->fragmentStorageTextures[i] = nil;
}
for (Uint32 i = 0; i < MAX_STORAGE_BUFFERS_PER_STAGE; i += 1) {
metalCommandBuffer->vertexStorageBuffers[i] = nil;
metalCommandBuffer->fragmentStorageBuffers[i] = nil;
}
}
}
static void METAL_INTERNAL_PushUniformData(
MetalCommandBuffer *metalCommandBuffer,
SDL_GPUShaderStage shaderStage,
Uint32 slotIndex,
const void *data,
Uint32 length)
{
MetalUniformBuffer *metalUniformBuffer;
Uint32 alignedDataLength;
if (shaderStage == SDL_GPU_SHADERSTAGE_VERTEX) {
if (metalCommandBuffer->vertexUniformBuffers[slotIndex] == NULL) {
metalCommandBuffer->vertexUniformBuffers[slotIndex] = METAL_INTERNAL_AcquireUniformBufferFromPool(
metalCommandBuffer);
}
metalUniformBuffer = metalCommandBuffer->vertexUniformBuffers[slotIndex];
} else if (shaderStage == SDL_GPU_SHADERSTAGE_FRAGMENT) {
if (metalCommandBuffer->fragmentUniformBuffers[slotIndex] == NULL) {
metalCommandBuffer->fragmentUniformBuffers[slotIndex] = METAL_INTERNAL_AcquireUniformBufferFromPool(
metalCommandBuffer);
}
metalUniformBuffer = metalCommandBuffer->fragmentUniformBuffers[slotIndex];
} else if (shaderStage == SDL_GPU_SHADERSTAGE_COMPUTE) {
if (metalCommandBuffer->computeUniformBuffers[slotIndex] == NULL) {
metalCommandBuffer->computeUniformBuffers[slotIndex] = METAL_INTERNAL_AcquireUniformBufferFromPool(
metalCommandBuffer);
}
metalUniformBuffer = metalCommandBuffer->computeUniformBuffers[slotIndex];
} else {
SDL_LogError(SDL_LOG_CATEGORY_GPU, "Unrecognized shader stage!");
return;
}
alignedDataLength = METAL_INTERNAL_NextHighestAlignment(
length,
256);
if (metalUniformBuffer->writeOffset + alignedDataLength >= UNIFORM_BUFFER_SIZE) {
metalUniformBuffer = METAL_INTERNAL_AcquireUniformBufferFromPool(
metalCommandBuffer);
metalUniformBuffer->writeOffset = 0;
metalUniformBuffer->drawOffset = 0;
if (shaderStage == SDL_GPU_SHADERSTAGE_VERTEX) {
metalCommandBuffer->vertexUniformBuffers[slotIndex] = metalUniformBuffer;
} else if (shaderStage == SDL_GPU_SHADERSTAGE_FRAGMENT) {
metalCommandBuffer->fragmentUniformBuffers[slotIndex] = metalUniformBuffer;
} else if (shaderStage == SDL_GPU_SHADERSTAGE_COMPUTE) {
metalCommandBuffer->computeUniformBuffers[slotIndex] = metalUniformBuffer;
} else {
SDL_LogError(SDL_LOG_CATEGORY_GPU, "Unrecognized shader stage!");
return;
}
}
metalUniformBuffer->drawOffset = metalUniformBuffer->writeOffset;
SDL_memcpy(
(metalUniformBuffer->handle).contents + metalUniformBuffer->writeOffset,
data,
length);
metalUniformBuffer->writeOffset += alignedDataLength;
if (shaderStage == SDL_GPU_SHADERSTAGE_VERTEX) {
metalCommandBuffer->needVertexUniformBufferBind[slotIndex] = true;
} else if (shaderStage == SDL_GPU_SHADERSTAGE_FRAGMENT) {
metalCommandBuffer->needFragmentUniformBufferBind[slotIndex] = true;
} else if (shaderStage == SDL_GPU_SHADERSTAGE_COMPUTE) {
metalCommandBuffer->needComputeUniformBufferBind[slotIndex] = true;
} else {
SDL_LogError(SDL_LOG_CATEGORY_GPU, "Unrecognized shader stage!");
}
}
static void METAL_PushVertexUniformData(
SDL_GPUCommandBuffer *commandBuffer,
Uint32 slotIndex,
const void *data,
Uint32 length)
{
@autoreleasepool {
METAL_INTERNAL_PushUniformData(
(MetalCommandBuffer *)commandBuffer,
SDL_GPU_SHADERSTAGE_VERTEX,
slotIndex,
data,
length);
}
}
static void METAL_PushFragmentUniformData(
SDL_GPUCommandBuffer *commandBuffer,
Uint32 slotIndex,
const void *data,
Uint32 length)
{
@autoreleasepool {
METAL_INTERNAL_PushUniformData(
(MetalCommandBuffer *)commandBuffer,
SDL_GPU_SHADERSTAGE_FRAGMENT,
slotIndex,
data,
length);
}
}
static void METAL_Blit(
SDL_GPUCommandBuffer *commandBuffer,
const SDL_GPUBlitInfo *info)
{
MetalCommandBuffer *metalCommandBuffer = (MetalCommandBuffer *)commandBuffer;
MetalRenderer *renderer = (MetalRenderer *)metalCommandBuffer->renderer;
SDL_GPU_BlitCommon(
commandBuffer,
info,
renderer->blitLinearSampler,
renderer->blitNearestSampler,
renderer->blitVertexShader,
renderer->blitFrom2DShader,
renderer->blitFrom2DArrayShader,
renderer->blitFrom3DShader,
renderer->blitFromCubeShader,
renderer->blitFromCubeArrayShader,
&renderer->blitPipelines,
&renderer->blitPipelineCount,
&renderer->blitPipelineCapacity);
}
static void METAL_BeginComputePass(
SDL_GPUCommandBuffer *commandBuffer,
const SDL_GPUStorageTextureReadWriteBinding *storageTextureBindings,
Uint32 numStorageTextureBindings,
const SDL_GPUStorageBufferReadWriteBinding *storageBufferBindings,
Uint32 numStorageBufferBindings)
{
@autoreleasepool {
MetalCommandBuffer *metalCommandBuffer = (MetalCommandBuffer *)commandBuffer;
MetalTextureContainer *textureContainer;
MetalTexture *texture;
id<MTLTexture> textureView;
MetalBufferContainer *bufferContainer;
MetalBuffer *buffer;
metalCommandBuffer->computeEncoder = [metalCommandBuffer->handle computeCommandEncoder];
for (Uint32 i = 0; i < numStorageTextureBindings; i += 1) {
textureContainer = (MetalTextureContainer *)storageTextureBindings[i].texture;
texture = METAL_INTERNAL_PrepareTextureForWrite(
metalCommandBuffer->renderer,
textureContainer,
storageTextureBindings[i].cycle);
METAL_INTERNAL_TrackTexture(metalCommandBuffer, texture);
textureView = [texture->handle newTextureViewWithPixelFormat:SDLToMetal_TextureFormat(textureContainer->header.info.format)
textureType:SDLToMetal_TextureType(textureContainer->header.info.type, false)
levels:NSMakeRange(storageTextureBindings[i].mip_level, 1)
slices:NSMakeRange(storageTextureBindings[i].layer, 1)];
metalCommandBuffer->computeReadWriteTextures[i] = textureView;
}
for (Uint32 i = 0; i < numStorageBufferBindings; i += 1) {
bufferContainer = (MetalBufferContainer *)storageBufferBindings[i].buffer;
buffer = METAL_INTERNAL_PrepareBufferForWrite(
metalCommandBuffer->renderer,
bufferContainer,
storageBufferBindings[i].cycle);
METAL_INTERNAL_TrackBuffer(
metalCommandBuffer,
buffer);
metalCommandBuffer->computeReadWriteBuffers[i] = buffer->handle;
}
}
}
static void METAL_BindComputePipeline(
SDL_GPUCommandBuffer *commandBuffer,
SDL_GPUComputePipeline *computePipeline)
{
@autoreleasepool {
MetalCommandBuffer *metalCommandBuffer = (MetalCommandBuffer *)commandBuffer;
MetalComputePipeline *pipeline = (MetalComputePipeline *)computePipeline;
metalCommandBuffer->compute_pipeline = pipeline;
[metalCommandBuffer->computeEncoder setComputePipelineState:pipeline->handle];
for (Uint32 i = 0; i < MAX_UNIFORM_BUFFERS_PER_STAGE; i += 1) {
metalCommandBuffer->needComputeUniformBufferBind[i] = true;
}
for (Uint32 i = 0; i < pipeline->header.numUniformBuffers; i += 1) {
if (metalCommandBuffer->computeUniformBuffers[i] == NULL) {
metalCommandBuffer->computeUniformBuffers[i] = METAL_INTERNAL_AcquireUniformBufferFromPool(
metalCommandBuffer);
}
}
if (pipeline->header.numReadWriteStorageTextures > 0) {
[metalCommandBuffer->computeEncoder setTextures:metalCommandBuffer->computeReadWriteTextures
withRange:NSMakeRange(
pipeline->header.numSamplers +
pipeline->header.numReadonlyStorageTextures,
pipeline->header.numReadWriteStorageTextures)];
}
NSUInteger offsets[MAX_COMPUTE_WRITE_BUFFERS] = { 0 };
if (pipeline->header.numReadWriteStorageBuffers > 0) {
[metalCommandBuffer->computeEncoder setBuffers:metalCommandBuffer->computeReadWriteBuffers
offsets:offsets
withRange:NSMakeRange(
pipeline->header.numUniformBuffers +
pipeline->header.numReadonlyStorageBuffers,
pipeline->header.numReadWriteStorageBuffers)];
}
}
}
static void METAL_BindComputeSamplers(
SDL_GPUCommandBuffer *commandBuffer,
Uint32 firstSlot,
const SDL_GPUTextureSamplerBinding *textureSamplerBindings,
Uint32 numBindings)
{
MetalCommandBuffer *metalCommandBuffer = (MetalCommandBuffer *)commandBuffer;
MetalTextureContainer *textureContainer;
MetalSampler *sampler;
for (Uint32 i = 0; i < numBindings; i += 1) {
textureContainer = (MetalTextureContainer *)textureSamplerBindings[i].texture;
sampler = (MetalSampler *)textureSamplerBindings[i].sampler;
if (metalCommandBuffer->computeSamplers[firstSlot + i] != sampler->handle) {
metalCommandBuffer->computeSamplers[firstSlot + i] = sampler->handle;
metalCommandBuffer->needComputeSamplerBind = true;
}
if (metalCommandBuffer->computeSamplerTextures[firstSlot + i] != textureContainer->activeTexture->handle) {
METAL_INTERNAL_TrackTexture(
metalCommandBuffer,
textureContainer->activeTexture);
metalCommandBuffer->computeSamplerTextures[firstSlot + i] =
textureContainer->activeTexture->handle;
metalCommandBuffer->needComputeSamplerBind = true;
}
}
}
static void METAL_BindComputeStorageTextures(
SDL_GPUCommandBuffer *commandBuffer,
Uint32 firstSlot,
SDL_GPUTexture *const *storageTextures,
Uint32 numBindings)
{
MetalCommandBuffer *metalCommandBuffer = (MetalCommandBuffer *)commandBuffer;
MetalTextureContainer *textureContainer;
for (Uint32 i = 0; i < numBindings; i += 1) {
textureContainer = (MetalTextureContainer *)storageTextures[i];
if (metalCommandBuffer->computeReadOnlyTextures[firstSlot + i] != textureContainer->activeTexture->handle) {
METAL_INTERNAL_TrackTexture(
metalCommandBuffer,
textureContainer->activeTexture);
metalCommandBuffer->computeReadOnlyTextures[firstSlot + i] =
textureContainer->activeTexture->handle;
metalCommandBuffer->needComputeReadOnlyStorageTextureBind = true;
}
}
}
static void METAL_BindComputeStorageBuffers(
SDL_GPUCommandBuffer *commandBuffer,
Uint32 firstSlot,
SDL_GPUBuffer *const *storageBuffers,
Uint32 numBindings)
{
MetalCommandBuffer *metalCommandBuffer = (MetalCommandBuffer *)commandBuffer;
MetalBufferContainer *bufferContainer;
for (Uint32 i = 0; i < numBindings; i += 1) {
bufferContainer = (MetalBufferContainer *)storageBuffers[i];
if (metalCommandBuffer->computeReadOnlyBuffers[firstSlot + i] != bufferContainer->activeBuffer->handle) {
METAL_INTERNAL_TrackBuffer(
metalCommandBuffer,
bufferContainer->activeBuffer);
metalCommandBuffer->computeReadOnlyBuffers[firstSlot + i] =
bufferContainer->activeBuffer->handle;
metalCommandBuffer->needComputeReadOnlyStorageBufferBind = true;
}
}
}
static void METAL_PushComputeUniformData(
SDL_GPUCommandBuffer *commandBuffer,
Uint32 slotIndex,
const void *data,
Uint32 length)
{
@autoreleasepool {
METAL_INTERNAL_PushUniformData(
(MetalCommandBuffer *)commandBuffer,
SDL_GPU_SHADERSTAGE_COMPUTE,
slotIndex,
data,
length);
}
}
static void METAL_DispatchCompute(
SDL_GPUCommandBuffer *commandBuffer,
Uint32 groupcountX,
Uint32 groupcountY,
Uint32 groupcountZ)
{
@autoreleasepool {
MetalCommandBuffer *metalCommandBuffer = (MetalCommandBuffer *)commandBuffer;
MTLSize threadgroups = MTLSizeMake(groupcountX, groupcountY, groupcountZ);
MTLSize threadsPerThreadgroup = MTLSizeMake(
metalCommandBuffer->compute_pipeline->threadcountX,
metalCommandBuffer->compute_pipeline->threadcountY,
metalCommandBuffer->compute_pipeline->threadcountZ);
METAL_INTERNAL_BindComputeResources(metalCommandBuffer);
[metalCommandBuffer->computeEncoder
dispatchThreadgroups:threadgroups
threadsPerThreadgroup:threadsPerThreadgroup];
}
}
static void METAL_DispatchComputeIndirect(
SDL_GPUCommandBuffer *commandBuffer,
SDL_GPUBuffer *buffer,
Uint32 offset)
{
@autoreleasepool {
MetalCommandBuffer *metalCommandBuffer = (MetalCommandBuffer *)commandBuffer;
MetalBuffer *metalBuffer = ((MetalBufferContainer *)buffer)->activeBuffer;
MTLSize threadsPerThreadgroup = MTLSizeMake(
metalCommandBuffer->compute_pipeline->threadcountX,
metalCommandBuffer->compute_pipeline->threadcountY,
metalCommandBuffer->compute_pipeline->threadcountZ);
METAL_INTERNAL_BindComputeResources(metalCommandBuffer);
[metalCommandBuffer->computeEncoder
dispatchThreadgroupsWithIndirectBuffer:metalBuffer->handle
indirectBufferOffset:offset
threadsPerThreadgroup:threadsPerThreadgroup];
METAL_INTERNAL_TrackBuffer(metalCommandBuffer, metalBuffer);
}
}
static void METAL_EndComputePass(
SDL_GPUCommandBuffer *commandBuffer)
{
@autoreleasepool {
MetalCommandBuffer *metalCommandBuffer = (MetalCommandBuffer *)commandBuffer;
[metalCommandBuffer->computeEncoder endEncoding];
metalCommandBuffer->computeEncoder = nil;
for (Uint32 i = 0; i < MAX_TEXTURE_SAMPLERS_PER_STAGE; i += 1) {
metalCommandBuffer->computeSamplers[i] = nil;
metalCommandBuffer->computeSamplerTextures[i] = nil;
}
for (Uint32 i = 0; i < MAX_COMPUTE_WRITE_TEXTURES; i += 1) {
metalCommandBuffer->computeReadWriteTextures[i] = nil;
}
for (Uint32 i = 0; i < MAX_COMPUTE_WRITE_BUFFERS; i += 1) {
metalCommandBuffer->computeReadWriteBuffers[i] = nil;
}
for (Uint32 i = 0; i < MAX_STORAGE_TEXTURES_PER_STAGE; i += 1) {
metalCommandBuffer->computeReadOnlyTextures[i] = nil;
}
for (Uint32 i = 0; i < MAX_STORAGE_BUFFERS_PER_STAGE; i += 1) {
metalCommandBuffer->computeReadOnlyBuffers[i] = nil;
}
}
}
static void METAL_INTERNAL_ReleaseFenceToPool(
MetalRenderer *renderer,
MetalFence *fence)
{
SDL_LockMutex(renderer->fenceLock);
if (renderer->availableFenceCount == renderer->availableFenceCapacity) {
renderer->availableFenceCapacity *= 2;
renderer->availableFences = SDL_realloc(
renderer->availableFences,
renderer->availableFenceCapacity * sizeof(MetalFence *));
}
renderer->availableFences[renderer->availableFenceCount] = fence;
renderer->availableFenceCount += 1;
SDL_UnlockMutex(renderer->fenceLock);
}
static void METAL_ReleaseFence(
SDL_GPURenderer *driverData,
SDL_GPUFence *fence)
{
MetalFence *metalFence = (MetalFence *)fence;
if (SDL_AtomicDecRef(&metalFence->referenceCount)) {
METAL_INTERNAL_ReleaseFenceToPool(
(MetalRenderer *)driverData,
(MetalFence *)fence);
}
}
static void METAL_INTERNAL_CleanCommandBuffer(
MetalRenderer *renderer,
MetalCommandBuffer *commandBuffer,
bool cancel)
{
Uint32 i;
if (commandBuffer->renderEncoder) {
[commandBuffer->renderEncoder endEncoding];
commandBuffer->renderEncoder = nil;
}
if (commandBuffer->computeEncoder) {
[commandBuffer->computeEncoder endEncoding];
commandBuffer->computeEncoder = nil;
}
if (commandBuffer->blitEncoder) {
[commandBuffer->blitEncoder endEncoding];
commandBuffer->blitEncoder = nil;
}
SDL_LockMutex(renderer->acquireUniformBufferLock);
for (i = 0; i < commandBuffer->usedUniformBufferCount; i += 1) {
METAL_INTERNAL_ReturnUniformBufferToPool(
renderer,
commandBuffer->usedUniformBuffers[i]);
}
commandBuffer->usedUniformBufferCount = 0;
SDL_UnlockMutex(renderer->acquireUniformBufferLock);
for (i = 0; i < commandBuffer->usedBufferCount; i += 1) {
(void)SDL_AtomicDecRef(&commandBuffer->usedBuffers[i]->referenceCount);
}
commandBuffer->usedBufferCount = 0;
for (i = 0; i < commandBuffer->usedTextureCount; i += 1) {
(void)SDL_AtomicDecRef(&commandBuffer->usedTextures[i]->referenceCount);
}
commandBuffer->usedTextureCount = 0;
commandBuffer->windowDataCount = 0;
for (i = 0; i < MAX_VERTEX_BUFFERS; i += 1) {
commandBuffer->vertexBuffers[i] = nil;
commandBuffer->vertexBufferOffsets[i] = 0;
}
commandBuffer->vertexBufferCount = 0;
commandBuffer->indexBuffer = NULL;
for (i = 0; i < MAX_TEXTURE_SAMPLERS_PER_STAGE; i += 1) {
commandBuffer->vertexSamplers[i] = nil;
commandBuffer->vertexTextures[i] = nil;
commandBuffer->fragmentSamplers[i] = nil;
commandBuffer->fragmentTextures[i] = nil;
commandBuffer->computeSamplers[i] = nil;
commandBuffer->computeSamplerTextures[i] = nil;
}
for (i = 0; i < MAX_STORAGE_TEXTURES_PER_STAGE; i += 1) {
commandBuffer->vertexStorageTextures[i] = nil;
commandBuffer->fragmentStorageTextures[i] = nil;
commandBuffer->computeReadOnlyTextures[i] = nil;
}
for (i = 0; i < MAX_STORAGE_BUFFERS_PER_STAGE; i += 1) {
commandBuffer->vertexStorageBuffers[i] = nil;
commandBuffer->fragmentStorageBuffers[i] = nil;
commandBuffer->computeReadOnlyBuffers[i] = nil;
}
for (i = 0; i < MAX_COMPUTE_WRITE_TEXTURES; i += 1) {
commandBuffer->computeReadWriteTextures[i] = nil;
}
for (i = 0; i < MAX_COMPUTE_WRITE_BUFFERS; i += 1) {
commandBuffer->computeReadWriteBuffers[i] = nil;
}
commandBuffer->needVertexBufferBind = false;
commandBuffer->needVertexSamplerBind = false;
commandBuffer->needVertexStorageBufferBind = false;
commandBuffer->needVertexStorageTextureBind = false;
SDL_zeroa(commandBuffer->needVertexUniformBufferBind);
commandBuffer->needFragmentSamplerBind = false;
commandBuffer->needFragmentStorageBufferBind = false;
commandBuffer->needFragmentStorageTextureBind = false;
SDL_zeroa(commandBuffer->needFragmentUniformBufferBind);
commandBuffer->needComputeSamplerBind = false;
commandBuffer->needComputeReadOnlyStorageBufferBind = false;
commandBuffer->needComputeReadOnlyStorageTextureBind = false;
SDL_zeroa(commandBuffer->needComputeUniformBufferBind);
if (commandBuffer->autoReleaseFence) {
METAL_ReleaseFence(
(SDL_GPURenderer *)renderer,
(SDL_GPUFence *)commandBuffer->fence);
}
SDL_LockMutex(renderer->acquireCommandBufferLock);
if (renderer->availableCommandBufferCount == renderer->availableCommandBufferCapacity) {
renderer->availableCommandBufferCapacity += 1;
renderer->availableCommandBuffers = SDL_realloc(
renderer->availableCommandBuffers,
renderer->availableCommandBufferCapacity * sizeof(MetalCommandBuffer *));
}
renderer->availableCommandBuffers[renderer->availableCommandBufferCount] = commandBuffer;
renderer->availableCommandBufferCount += 1;
SDL_UnlockMutex(renderer->acquireCommandBufferLock);
if (!cancel) {
for (i = 0; i < renderer->submittedCommandBufferCount; i += 1) {
if (renderer->submittedCommandBuffers[i] == commandBuffer) {
renderer->submittedCommandBuffers[i] = renderer->submittedCommandBuffers[renderer->submittedCommandBufferCount - 1];
renderer->submittedCommandBufferCount -= 1;
}
}
}
}
static void METAL_INTERNAL_PerformPendingDestroys(
MetalRenderer *renderer)
{
Sint32 referenceCount = 0;
Sint32 i;
Uint32 j;
for (i = renderer->bufferContainersToDestroyCount - 1; i >= 0; i -= 1) {
referenceCount = 0;
for (j = 0; j < renderer->bufferContainersToDestroy[i]->bufferCount; j += 1) {
referenceCount += SDL_GetAtomicInt(&renderer->bufferContainersToDestroy[i]->buffers[j]->referenceCount);
}
if (referenceCount == 0) {
METAL_INTERNAL_DestroyBufferContainer(
renderer->bufferContainersToDestroy[i]);
renderer->bufferContainersToDestroy[i] = renderer->bufferContainersToDestroy[renderer->bufferContainersToDestroyCount - 1];
renderer->bufferContainersToDestroyCount -= 1;
}
}
for (i = renderer->textureContainersToDestroyCount - 1; i >= 0; i -= 1) {
referenceCount = 0;
for (j = 0; j < renderer->textureContainersToDestroy[i]->textureCount; j += 1) {
referenceCount += SDL_GetAtomicInt(&renderer->textureContainersToDestroy[i]->textures[j]->referenceCount);
}
if (referenceCount == 0) {
METAL_INTERNAL_DestroyTextureContainer(
renderer->textureContainersToDestroy[i]);
renderer->textureContainersToDestroy[i] = renderer->textureContainersToDestroy[renderer->textureContainersToDestroyCount - 1];
renderer->textureContainersToDestroyCount -= 1;
}
}
}
static bool METAL_WaitForFences(
SDL_GPURenderer *driverData,
bool waitAll,
SDL_GPUFence *const *fences,
Uint32 numFences)
{
@autoreleasepool {
MetalRenderer *renderer = (MetalRenderer *)driverData;
bool waiting;
if (waitAll) {
for (Uint32 i = 0; i < numFences; i += 1) {
while (!SDL_GetAtomicInt(&((MetalFence *)fences[i])->complete)) {
}
}
} else {
waiting = 1;
while (waiting) {
for (Uint32 i = 0; i < numFences; i += 1) {
if (SDL_GetAtomicInt(&((MetalFence *)fences[i])->complete) > 0) {
waiting = 0;
break;
}
}
}
}
METAL_INTERNAL_PerformPendingDestroys(renderer);
return true;
}
}
static bool METAL_QueryFence(
SDL_GPURenderer *driverData,
SDL_GPUFence *fence)
{
MetalFence *metalFence = (MetalFence *)fence;
return SDL_GetAtomicInt(&metalFence->complete) == 1;
}
static MetalWindowData *METAL_INTERNAL_FetchWindowData(SDL_Window *window)
{
SDL_PropertiesID properties = SDL_GetWindowProperties(window);
return (MetalWindowData *)SDL_GetPointerProperty(properties, WINDOW_PROPERTY_DATA, NULL);
}
static bool METAL_SupportsSwapchainComposition(
SDL_GPURenderer *driverData,
SDL_Window *window,
SDL_GPUSwapchainComposition swapchainComposition)
{
#ifndef SDL_PLATFORM_MACOS
if (swapchainComposition == SDL_GPU_SWAPCHAINCOMPOSITION_HDR10_ST2084) {
return false;
}
#endif
if (@available(macOS 11.0, *)) {
return true;
} else {
return swapchainComposition != SDL_GPU_SWAPCHAINCOMPOSITION_HDR10_ST2084;
}
}
static bool METAL_INTERNAL_CreateSwapchain(
MetalRenderer *renderer,
MetalWindowData *windowData,
SDL_GPUSwapchainComposition swapchainComposition,
SDL_GPUPresentMode presentMode)
{
CGColorSpaceRef colorspace;
CGSize drawableSize;
windowData->view = SDL_Metal_CreateView(windowData->window);
windowData->drawable = nil;
windowData->presentMode = SDL_GPU_PRESENTMODE_VSYNC;
windowData->frameCounter = 0;
for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; i += 1) {
windowData->inFlightFences[i] = NULL;
}
windowData->layer = (__bridge CAMetalLayer *)(SDL_Metal_GetLayer(windowData->view));
windowData->layer.device = renderer->device;
#ifdef SDL_PLATFORM_MACOS
if (@available(macOS 10.13, *)) {
windowData->layer.displaySyncEnabled = (presentMode != SDL_GPU_PRESENTMODE_IMMEDIATE);
windowData->presentMode = presentMode;
}
#endif
windowData->layer.pixelFormat = SDLToMetal_TextureFormat(SwapchainCompositionToFormat[swapchainComposition]);
#ifndef SDL_PLATFORM_TVOS
if (@available(iOS 16.0, *)) {
windowData->layer.wantsExtendedDynamicRangeContent = (swapchainComposition != SDL_GPU_SWAPCHAINCOMPOSITION_SDR);
}
#endif
colorspace = CGColorSpaceCreateWithName(SwapchainCompositionToColorSpace[swapchainComposition]);
windowData->layer.colorspace = colorspace;
CGColorSpaceRelease(colorspace);
windowData->texture.handle = nil;
for (Uint32 i = 0; i < 4; i += 1) {
SDL_GPU_FetchBlitPipeline(
renderer->sdlGPUDevice,
(SDL_GPUTextureType)i,
SwapchainCompositionToFormat[swapchainComposition],
renderer->blitVertexShader,
renderer->blitFrom2DShader,
renderer->blitFrom2DArrayShader,
renderer->blitFrom3DShader,
renderer->blitFromCubeShader,
renderer->blitFromCubeArrayShader,
&renderer->blitPipelines,
&renderer->blitPipelineCount,
&renderer->blitPipelineCapacity);
}
SDL_zero(windowData->textureContainer);
windowData->textureContainer.canBeCycled = 0;
windowData->textureContainer.activeTexture = &windowData->texture;
windowData->textureContainer.textureCapacity = 1;
windowData->textureContainer.textureCount = 1;
windowData->textureContainer.header.info.format = SwapchainCompositionToFormat[swapchainComposition];
windowData->textureContainer.header.info.num_levels = 1;
windowData->textureContainer.header.info.layer_count_or_depth = 1;
windowData->textureContainer.header.info.type = SDL_GPU_TEXTURETYPE_2D;
windowData->textureContainer.header.info.usage = SDL_GPU_TEXTUREUSAGE_COLOR_TARGET;
drawableSize = windowData->layer.drawableSize;
windowData->textureContainer.header.info.width = (Uint32)drawableSize.width;
windowData->textureContainer.header.info.height = (Uint32)drawableSize.height;
return true;
}
static bool METAL_SupportsPresentMode(
SDL_GPURenderer *driverData,
SDL_Window *window,
SDL_GPUPresentMode presentMode)
{
switch (presentMode) {
#ifdef SDL_PLATFORM_MACOS
case SDL_GPU_PRESENTMODE_IMMEDIATE:
#endif
case SDL_GPU_PRESENTMODE_VSYNC:
return true;
default:
return false;
}
}
static bool METAL_ClaimWindow(
SDL_GPURenderer *driverData,
SDL_Window *window)
{
@autoreleasepool {
MetalRenderer *renderer = (MetalRenderer *)driverData;
MetalWindowData *windowData = METAL_INTERNAL_FetchWindowData(window);
if (windowData == NULL) {
windowData = (MetalWindowData *)SDL_calloc(1, sizeof(MetalWindowData));
windowData->window = window;
windowData->renderer = renderer;
windowData->refcount = 1;
if (METAL_INTERNAL_CreateSwapchain(renderer, windowData, SDL_GPU_SWAPCHAINCOMPOSITION_SDR, SDL_GPU_PRESENTMODE_VSYNC)) {
SDL_SetPointerProperty(SDL_GetWindowProperties(window), WINDOW_PROPERTY_DATA, windowData);
SDL_LockMutex(renderer->windowLock);
if (renderer->claimedWindowCount >= renderer->claimedWindowCapacity) {
renderer->claimedWindowCapacity *= 2;
renderer->claimedWindows = SDL_realloc(
renderer->claimedWindows,
renderer->claimedWindowCapacity * sizeof(MetalWindowData *));
}
renderer->claimedWindows[renderer->claimedWindowCount] = windowData;
renderer->claimedWindowCount += 1;
SDL_UnlockMutex(renderer->windowLock);
return true;
} else {
SDL_free(windowData);
return false;
}
} else if (windowData->renderer == renderer) {
++windowData->refcount;
return true;
} else {
SET_STRING_ERROR_AND_RETURN("Window already claimed", false);
}
}
}
static void METAL_ReleaseWindow(
SDL_GPURenderer *driverData,
SDL_Window *window)
{
@autoreleasepool {
MetalRenderer *renderer = (MetalRenderer *)driverData;
MetalWindowData *windowData = METAL_INTERNAL_FetchWindowData(window);
if (windowData == NULL) {
return;
}
if (windowData->renderer != renderer) {
SDL_SetError("Window not claimed by this device");
return;
}
if (windowData->refcount > 1) {
--windowData->refcount;
return;
}
METAL_Wait(driverData);
SDL_Metal_DestroyView(windowData->view);
for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; i += 1) {
if (windowData->inFlightFences[i] != NULL) {
METAL_ReleaseFence(
(SDL_GPURenderer *)renderer,
windowData->inFlightFences[i]);
}
}
SDL_LockMutex(renderer->windowLock);
for (Uint32 i = 0; i < renderer->claimedWindowCount; i += 1) {
if (renderer->claimedWindows[i]->window == window) {
renderer->claimedWindows[i] = renderer->claimedWindows[renderer->claimedWindowCount - 1];
renderer->claimedWindowCount -= 1;
break;
}
}
SDL_UnlockMutex(renderer->windowLock);
SDL_free(windowData);
SDL_ClearProperty(SDL_GetWindowProperties(window), WINDOW_PROPERTY_DATA);
}
}
static bool METAL_WaitForSwapchain(
SDL_GPURenderer *driverData,
SDL_Window *window)
{
@autoreleasepool {
MetalRenderer *renderer = (MetalRenderer *)driverData;
MetalWindowData *windowData = METAL_INTERNAL_FetchWindowData(window);
if (windowData == NULL) {
SET_STRING_ERROR_AND_RETURN("Cannot wait for a swapchain from an unclaimed window!", false);
}
if (windowData->inFlightFences[windowData->frameCounter] != NULL) {
if (!METAL_WaitForFences(
driverData,
true,
&windowData->inFlightFences[windowData->frameCounter],
1)) {
return false;
}
}
return true;
}
}
static bool METAL_INTERNAL_AcquireSwapchainTexture(
bool block,
SDL_GPUCommandBuffer *commandBuffer,
SDL_Window *window,
SDL_GPUTexture **texture,
Uint32 *swapchainTextureWidth,
Uint32 *swapchainTextureHeight)
{
@autoreleasepool {
MetalCommandBuffer *metalCommandBuffer = (MetalCommandBuffer *)commandBuffer;
MetalRenderer *renderer = metalCommandBuffer->renderer;
MetalWindowData *windowData;
CGSize drawableSize;
*texture = NULL;
if (swapchainTextureWidth) {
*swapchainTextureWidth = 0;
}
if (swapchainTextureHeight) {
*swapchainTextureHeight = 0;
}
windowData = METAL_INTERNAL_FetchWindowData(window);
if (windowData == NULL) {
SET_STRING_ERROR_AND_RETURN("Window is not claimed by this SDL_GPUDevice", false);
}
drawableSize = windowData->layer.drawableSize;
windowData->textureContainer.header.info.width = (Uint32)drawableSize.width;
windowData->textureContainer.header.info.height = (Uint32)drawableSize.height;
if (swapchainTextureWidth) {
*swapchainTextureWidth = (Uint32)drawableSize.width;
}
if (swapchainTextureHeight) {
*swapchainTextureHeight = (Uint32)drawableSize.height;
}
if (windowData->inFlightFences[windowData->frameCounter] != NULL) {
if (block) {
if (!METAL_WaitForFences(
(SDL_GPURenderer *)renderer,
true,
&windowData->inFlightFences[windowData->frameCounter],
1)) {
return false;
}
} else {
if (!METAL_QueryFence(
(SDL_GPURenderer *)metalCommandBuffer->renderer,
windowData->inFlightFences[windowData->frameCounter])) {
return true;
}
}
METAL_ReleaseFence(
(SDL_GPURenderer *)metalCommandBuffer->renderer,
windowData->inFlightFences[windowData->frameCounter]);
windowData->inFlightFences[windowData->frameCounter] = NULL;
}
windowData->drawable = [windowData->layer nextDrawable];
windowData->texture.handle = [windowData->drawable texture];
if (metalCommandBuffer->windowDataCount == metalCommandBuffer->windowDataCapacity) {
metalCommandBuffer->windowDataCapacity += 1;
metalCommandBuffer->windowDatas = SDL_realloc(
metalCommandBuffer->windowDatas,
metalCommandBuffer->windowDataCapacity * sizeof(MetalWindowData *));
}
metalCommandBuffer->windowDatas[metalCommandBuffer->windowDataCount] = windowData;
metalCommandBuffer->windowDataCount += 1;
*texture = (SDL_GPUTexture *)&windowData->textureContainer;
return true;
}
}
static bool METAL_AcquireSwapchainTexture(
SDL_GPUCommandBuffer *command_buffer,
SDL_Window *window,
SDL_GPUTexture **swapchain_texture,
Uint32 *swapchain_texture_width,
Uint32 *swapchain_texture_height
) {
return METAL_INTERNAL_AcquireSwapchainTexture(
false,
command_buffer,
window,
swapchain_texture,
swapchain_texture_width,
swapchain_texture_height);
}
static bool METAL_WaitAndAcquireSwapchainTexture(
SDL_GPUCommandBuffer *command_buffer,
SDL_Window *window,
SDL_GPUTexture **swapchain_texture,
Uint32 *swapchain_texture_width,
Uint32 *swapchain_texture_height
) {
return METAL_INTERNAL_AcquireSwapchainTexture(
true,
command_buffer,
window,
swapchain_texture,
swapchain_texture_width,
swapchain_texture_height);
}
static SDL_GPUTextureFormat METAL_GetSwapchainTextureFormat(
SDL_GPURenderer *driverData,
SDL_Window *window)
{
MetalRenderer *renderer = (MetalRenderer *)driverData;
MetalWindowData *windowData = METAL_INTERNAL_FetchWindowData(window);
if (windowData == NULL) {
SET_STRING_ERROR_AND_RETURN("Cannot get swapchain format, window has not been claimed", SDL_GPU_TEXTUREFORMAT_INVALID);
}
return windowData->textureContainer.header.info.format;
}
static bool METAL_SetSwapchainParameters(
SDL_GPURenderer *driverData,
SDL_Window *window,
SDL_GPUSwapchainComposition swapchainComposition,
SDL_GPUPresentMode presentMode)
{
@autoreleasepool {
MetalRenderer *renderer = (MetalRenderer *)driverData;
MetalWindowData *windowData = METAL_INTERNAL_FetchWindowData(window);
CGColorSpaceRef colorspace;
if (windowData == NULL) {
SET_STRING_ERROR_AND_RETURN("Cannot set swapchain parameters, window has not been claimed!", false);
}
if (!METAL_SupportsSwapchainComposition(driverData, window, swapchainComposition)) {
SET_STRING_ERROR_AND_RETURN("Swapchain composition not supported", false);
}
if (!METAL_SupportsPresentMode(driverData, window, presentMode)) {
SET_STRING_ERROR_AND_RETURN("Present mode not supported", false);
}
METAL_Wait(driverData);
windowData->presentMode = SDL_GPU_PRESENTMODE_VSYNC;
#ifdef SDL_PLATFORM_MACOS
if (@available(macOS 10.13, *)) {
windowData->layer.displaySyncEnabled = (presentMode != SDL_GPU_PRESENTMODE_IMMEDIATE);
windowData->presentMode = presentMode;
}
#endif
windowData->layer.pixelFormat = SDLToMetal_TextureFormat(SwapchainCompositionToFormat[swapchainComposition]);
#ifndef SDL_PLATFORM_TVOS
if (@available(iOS 16.0, *)) {
windowData->layer.wantsExtendedDynamicRangeContent = (swapchainComposition != SDL_GPU_SWAPCHAINCOMPOSITION_SDR);
}
#endif
colorspace = CGColorSpaceCreateWithName(SwapchainCompositionToColorSpace[swapchainComposition]);
windowData->layer.colorspace = colorspace;
CGColorSpaceRelease(colorspace);
windowData->textureContainer.header.info.format = SwapchainCompositionToFormat[swapchainComposition];
return true;
}
}
static bool METAL_SetAllowedFramesInFlight(
SDL_GPURenderer *driverData,
Uint32 allowedFramesInFlight)
{
@autoreleasepool {
MetalRenderer *renderer = (MetalRenderer *)driverData;
if (!METAL_Wait(driverData)) {
return false;
}
renderer->allowedFramesInFlight = allowedFramesInFlight;
return true;
}
}
static bool METAL_Submit(
SDL_GPUCommandBuffer *commandBuffer)
{
@autoreleasepool {
MetalCommandBuffer *metalCommandBuffer = (MetalCommandBuffer *)commandBuffer;
MetalRenderer *renderer = metalCommandBuffer->renderer;
SDL_LockMutex(renderer->submitLock);
if (!METAL_INTERNAL_AcquireFence(renderer, metalCommandBuffer)) {
SDL_UnlockMutex(renderer->submitLock);
return false;
}
for (Uint32 i = 0; i < metalCommandBuffer->windowDataCount; i += 1) {
MetalWindowData *windowData = metalCommandBuffer->windowDatas[i];
[metalCommandBuffer->handle presentDrawable:windowData->drawable];
windowData->drawable = nil;
windowData->inFlightFences[windowData->frameCounter] = (SDL_GPUFence *)metalCommandBuffer->fence;
(void)SDL_AtomicIncRef(&metalCommandBuffer->fence->referenceCount);
windowData->frameCounter = (windowData->frameCounter + 1) % renderer->allowedFramesInFlight;
}
[metalCommandBuffer->handle addCompletedHandler:^(id<MTLCommandBuffer> buffer) {
SDL_AtomicIncRef(&metalCommandBuffer->fence->complete);
}];
[metalCommandBuffer->handle commit];
metalCommandBuffer->handle = nil;
if (renderer->submittedCommandBufferCount >= renderer->submittedCommandBufferCapacity) {
renderer->submittedCommandBufferCapacity = renderer->submittedCommandBufferCount + 1;
renderer->submittedCommandBuffers = SDL_realloc(
renderer->submittedCommandBuffers,
sizeof(MetalCommandBuffer *) * renderer->submittedCommandBufferCapacity);
}
renderer->submittedCommandBuffers[renderer->submittedCommandBufferCount] = metalCommandBuffer;
renderer->submittedCommandBufferCount += 1;
for (Sint32 i = renderer->submittedCommandBufferCount - 1; i >= 0; i -= 1) {
if (SDL_GetAtomicInt(&renderer->submittedCommandBuffers[i]->fence->complete)) {
METAL_INTERNAL_CleanCommandBuffer(
renderer,
renderer->submittedCommandBuffers[i],
false);
}
}
METAL_INTERNAL_PerformPendingDestroys(renderer);
SDL_UnlockMutex(renderer->submitLock);
return true;
}
}
static SDL_GPUFence *METAL_SubmitAndAcquireFence(
SDL_GPUCommandBuffer *commandBuffer)
{
MetalCommandBuffer *metalCommandBuffer = (MetalCommandBuffer *)commandBuffer;
metalCommandBuffer->autoReleaseFence = false;
if (!METAL_Submit(commandBuffer)) {
return NULL;
}
return (SDL_GPUFence *)metalCommandBuffer->fence;
}
static bool METAL_Cancel(
SDL_GPUCommandBuffer *commandBuffer)
{
MetalCommandBuffer *metalCommandBuffer = (MetalCommandBuffer *)commandBuffer;
MetalRenderer *renderer = metalCommandBuffer->renderer;
metalCommandBuffer->autoReleaseFence = false;
SDL_LockMutex(renderer->submitLock);
METAL_INTERNAL_CleanCommandBuffer(renderer, metalCommandBuffer, true);
SDL_UnlockMutex(renderer->submitLock);
return true;
}
static bool METAL_Wait(
SDL_GPURenderer *driverData)
{
@autoreleasepool {
MetalRenderer *renderer = (MetalRenderer *)driverData;
MetalCommandBuffer *commandBuffer;
for (Uint32 i = 0; i < renderer->submittedCommandBufferCount; i += 1) {
while (!SDL_GetAtomicInt(&renderer->submittedCommandBuffers[i]->fence->complete)) {
}
}
SDL_LockMutex(renderer->submitLock);
for (Sint32 i = renderer->submittedCommandBufferCount - 1; i >= 0; i -= 1) {
commandBuffer = renderer->submittedCommandBuffers[i];
METAL_INTERNAL_CleanCommandBuffer(renderer, commandBuffer, false);
}
METAL_INTERNAL_PerformPendingDestroys(renderer);
SDL_UnlockMutex(renderer->submitLock);
return true;
}
}
static bool METAL_SupportsTextureFormat(
SDL_GPURenderer *driverData,
SDL_GPUTextureFormat format,
SDL_GPUTextureType type,
SDL_GPUTextureUsageFlags usage)
{
@autoreleasepool {
MetalRenderer *renderer = (MetalRenderer *)driverData;
if ((usage & SDL_GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET)) {
if (!IsDepthFormat(format)) {
return false;
}
}
if (type == SDL_GPU_TEXTURETYPE_CUBE_ARRAY) {
#ifdef SDL_PLATFORM_MACOS
return true;
#else
if (@available(iOS 13.0, tvOS 13.0, *)) {
if (!([renderer->device supportsFamily:MTLGPUFamilyCommon2] ||
[renderer->device supportsFamily:MTLGPUFamilyApple4])) {
return false;
}
} else {
return false;
}
#endif
}
switch (format) {
case SDL_GPU_TEXTUREFORMAT_B5G6R5_UNORM:
case SDL_GPU_TEXTUREFORMAT_B5G5R5A1_UNORM:
case SDL_GPU_TEXTUREFORMAT_B4G4R4A4_UNORM:
if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) {
return [renderer->device supportsFamily:MTLGPUFamilyApple1];
} else {
return false;
}
case SDL_GPU_TEXTUREFORMAT_BC1_RGBA_UNORM:
case SDL_GPU_TEXTUREFORMAT_BC2_RGBA_UNORM:
case SDL_GPU_TEXTUREFORMAT_BC3_RGBA_UNORM:
case SDL_GPU_TEXTUREFORMAT_BC4_R_UNORM:
case SDL_GPU_TEXTUREFORMAT_BC5_RG_UNORM:
case SDL_GPU_TEXTUREFORMAT_BC7_RGBA_UNORM:
case SDL_GPU_TEXTUREFORMAT_BC6H_RGB_FLOAT:
case SDL_GPU_TEXTUREFORMAT_BC6H_RGB_UFLOAT:
case SDL_GPU_TEXTUREFORMAT_BC1_RGBA_UNORM_SRGB:
case SDL_GPU_TEXTUREFORMAT_BC2_RGBA_UNORM_SRGB:
case SDL_GPU_TEXTUREFORMAT_BC3_RGBA_UNORM_SRGB:
case SDL_GPU_TEXTUREFORMAT_BC7_RGBA_UNORM_SRGB:
if (@available(iOS 16.4, tvOS 16.4, *)) {
if (usage & SDL_GPU_TEXTUREUSAGE_COLOR_TARGET) {
return false;
}
if (@available(macOS 11.0, *)) {
return [renderer->device supportsBCTextureCompression];
} else {
return true;
}
} else {
return false;
}
case SDL_GPU_TEXTUREFORMAT_D24_UNORM:
case SDL_GPU_TEXTUREFORMAT_D24_UNORM_S8_UINT:
#ifdef SDL_PLATFORM_MACOS
return [renderer->device isDepth24Stencil8PixelFormatSupported];
#else
return false;
#endif
case SDL_GPU_TEXTUREFORMAT_D16_UNORM:
if (@available(macOS 10.12, iOS 13.0, tvOS 13.0, *)) {
return true;
} else {
return false;
}
case SDL_GPU_TEXTUREFORMAT_ASTC_4x4_UNORM:
case SDL_GPU_TEXTUREFORMAT_ASTC_5x4_UNORM:
case SDL_GPU_TEXTUREFORMAT_ASTC_5x5_UNORM:
case SDL_GPU_TEXTUREFORMAT_ASTC_6x5_UNORM:
case SDL_GPU_TEXTUREFORMAT_ASTC_6x6_UNORM:
case SDL_GPU_TEXTUREFORMAT_ASTC_8x5_UNORM:
case SDL_GPU_TEXTUREFORMAT_ASTC_8x6_UNORM:
case SDL_GPU_TEXTUREFORMAT_ASTC_8x8_UNORM:
case SDL_GPU_TEXTUREFORMAT_ASTC_10x5_UNORM:
case SDL_GPU_TEXTUREFORMAT_ASTC_10x6_UNORM:
case SDL_GPU_TEXTUREFORMAT_ASTC_10x8_UNORM:
case SDL_GPU_TEXTUREFORMAT_ASTC_10x10_UNORM:
case SDL_GPU_TEXTUREFORMAT_ASTC_12x10_UNORM:
case SDL_GPU_TEXTUREFORMAT_ASTC_12x12_UNORM:
case SDL_GPU_TEXTUREFORMAT_ASTC_4x4_UNORM_SRGB:
case SDL_GPU_TEXTUREFORMAT_ASTC_5x4_UNORM_SRGB:
case SDL_GPU_TEXTUREFORMAT_ASTC_5x5_UNORM_SRGB:
case SDL_GPU_TEXTUREFORMAT_ASTC_6x5_UNORM_SRGB:
case SDL_GPU_TEXTUREFORMAT_ASTC_6x6_UNORM_SRGB:
case SDL_GPU_TEXTUREFORMAT_ASTC_8x5_UNORM_SRGB:
case SDL_GPU_TEXTUREFORMAT_ASTC_8x6_UNORM_SRGB:
case SDL_GPU_TEXTUREFORMAT_ASTC_8x8_UNORM_SRGB:
case SDL_GPU_TEXTUREFORMAT_ASTC_10x5_UNORM_SRGB:
case SDL_GPU_TEXTUREFORMAT_ASTC_10x6_UNORM_SRGB:
case SDL_GPU_TEXTUREFORMAT_ASTC_10x8_UNORM_SRGB:
case SDL_GPU_TEXTUREFORMAT_ASTC_10x10_UNORM_SRGB:
case SDL_GPU_TEXTUREFORMAT_ASTC_12x10_UNORM_SRGB:
case SDL_GPU_TEXTUREFORMAT_ASTC_12x12_UNORM_SRGB:
#ifdef SDL_PLATFORM_MACOS
if (@available(macOS 11.0, *)) {
return [renderer->device supportsFamily:MTLGPUFamilyApple7];
} else {
return false;
}
#else
return true;
#endif
case SDL_GPU_TEXTUREFORMAT_ASTC_4x4_FLOAT:
case SDL_GPU_TEXTUREFORMAT_ASTC_5x4_FLOAT:
case SDL_GPU_TEXTUREFORMAT_ASTC_5x5_FLOAT:
case SDL_GPU_TEXTUREFORMAT_ASTC_6x5_FLOAT:
case SDL_GPU_TEXTUREFORMAT_ASTC_6x6_FLOAT:
case SDL_GPU_TEXTUREFORMAT_ASTC_8x5_FLOAT:
case SDL_GPU_TEXTUREFORMAT_ASTC_8x6_FLOAT:
case SDL_GPU_TEXTUREFORMAT_ASTC_8x8_FLOAT:
case SDL_GPU_TEXTUREFORMAT_ASTC_10x5_FLOAT:
case SDL_GPU_TEXTUREFORMAT_ASTC_10x6_FLOAT:
case SDL_GPU_TEXTUREFORMAT_ASTC_10x8_FLOAT:
case SDL_GPU_TEXTUREFORMAT_ASTC_10x10_FLOAT:
case SDL_GPU_TEXTUREFORMAT_ASTC_12x10_FLOAT:
case SDL_GPU_TEXTUREFORMAT_ASTC_12x12_FLOAT:
#ifdef SDL_PLATFORM_MACOS
if (@available(macOS 11.0, *)) {
return [renderer->device supportsFamily:MTLGPUFamilyApple7];
} else {
return false;
}
#else
if (@available(iOS 13.0, tvOS 13.0, *)) {
return [renderer->device supportsFamily:MTLGPUFamilyApple6];
} else {
return false;
}
#endif
default:
return true;
}
}
}
static bool METAL_PrepareDriver(SDL_VideoDevice *this, SDL_PropertiesID props)
{
if (!SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_SHADERS_MSL_BOOLEAN, false) &&
!SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_SHADERS_METALLIB_BOOLEAN, false)) {
return false;
}
if (@available(macOS 10.14, iOS 13.0, tvOS 13.0, *)) {
return (this->Metal_CreateView != NULL);
}
return false;
}
static void METAL_INTERNAL_InitBlitResources(
MetalRenderer *renderer)
{
SDL_GPUShaderCreateInfo shaderModuleCreateInfo;
SDL_GPUSamplerCreateInfo createinfo;
renderer->blitPipelineCapacity = 2;
renderer->blitPipelineCount = 0;
renderer->blitPipelines = SDL_calloc(
renderer->blitPipelineCapacity, sizeof(BlitPipelineCacheEntry));
SDL_zero(shaderModuleCreateInfo);
shaderModuleCreateInfo.code = FullscreenVert_metallib;
shaderModuleCreateInfo.code_size = FullscreenVert_metallib_len;
shaderModuleCreateInfo.stage = SDL_GPU_SHADERSTAGE_VERTEX;
shaderModuleCreateInfo.format = SDL_GPU_SHADERFORMAT_METALLIB;
shaderModuleCreateInfo.entrypoint = "FullscreenVert";
renderer->blitVertexShader = METAL_CreateShader(
(SDL_GPURenderer *)renderer,
&shaderModuleCreateInfo);
if (renderer->blitVertexShader == NULL) {
SDL_LogError(SDL_LOG_CATEGORY_GPU, "Failed to compile vertex shader for blit!");
}
shaderModuleCreateInfo.code = BlitFrom2D_metallib;
shaderModuleCreateInfo.code_size = BlitFrom2D_metallib_len;
shaderModuleCreateInfo.stage = SDL_GPU_SHADERSTAGE_FRAGMENT;
shaderModuleCreateInfo.entrypoint = "BlitFrom2D";
shaderModuleCreateInfo.num_samplers = 1;
shaderModuleCreateInfo.num_uniform_buffers = 1;
renderer->blitFrom2DShader = METAL_CreateShader(
(SDL_GPURenderer *)renderer,
&shaderModuleCreateInfo);
if (renderer->blitFrom2DShader == NULL) {
SDL_LogError(SDL_LOG_CATEGORY_GPU, "Failed to compile BlitFrom2D fragment shader!");
}
shaderModuleCreateInfo.code = BlitFrom2DArray_metallib;
shaderModuleCreateInfo.code_size = BlitFrom2DArray_metallib_len;
shaderModuleCreateInfo.entrypoint = "BlitFrom2DArray";
renderer->blitFrom2DArrayShader = METAL_CreateShader(
(SDL_GPURenderer *)renderer,
&shaderModuleCreateInfo);
if (renderer->blitFrom2DArrayShader == NULL) {
SDL_LogError(SDL_LOG_CATEGORY_GPU, "Failed to compile BlitFrom2DArray fragment shader!");
}
shaderModuleCreateInfo.code = BlitFrom3D_metallib;
shaderModuleCreateInfo.code_size = BlitFrom3D_metallib_len;
shaderModuleCreateInfo.entrypoint = "BlitFrom3D";
renderer->blitFrom3DShader = METAL_CreateShader(
(SDL_GPURenderer *)renderer,
&shaderModuleCreateInfo);
if (renderer->blitFrom3DShader == NULL) {
SDL_LogError(SDL_LOG_CATEGORY_GPU, "Failed to compile BlitFrom3D fragment shader!");
}
shaderModuleCreateInfo.code = BlitFromCube_metallib;
shaderModuleCreateInfo.code_size = BlitFromCube_metallib_len;
shaderModuleCreateInfo.entrypoint = "BlitFromCube";
renderer->blitFromCubeShader = METAL_CreateShader(
(SDL_GPURenderer *)renderer,
&shaderModuleCreateInfo);
if (renderer->blitFromCubeShader == NULL) {
SDL_LogError(SDL_LOG_CATEGORY_GPU, "Failed to compile BlitFromCube fragment shader!");
}
shaderModuleCreateInfo.code = BlitFromCubeArray_metallib;
shaderModuleCreateInfo.code_size = BlitFromCubeArray_metallib_len;
shaderModuleCreateInfo.entrypoint = "BlitFromCubeArray";
renderer->blitFromCubeArrayShader = METAL_CreateShader(
(SDL_GPURenderer *)renderer,
&shaderModuleCreateInfo);
if (renderer->blitFromCubeArrayShader == NULL) {
SDL_LogError(SDL_LOG_CATEGORY_GPU, "Failed to compile BlitFromCubeArray fragment shader!");
}
createinfo.address_mode_u = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE;
createinfo.address_mode_v = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE;
createinfo.address_mode_w = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE;
createinfo.enable_anisotropy = 0;
createinfo.enable_compare = 0;
createinfo.mag_filter = SDL_GPU_FILTER_NEAREST;
createinfo.min_filter = SDL_GPU_FILTER_NEAREST;
createinfo.mipmap_mode = SDL_GPU_SAMPLERMIPMAPMODE_NEAREST;
createinfo.mip_lod_bias = 0.0f;
createinfo.min_lod = 0;
createinfo.max_lod = 1000;
createinfo.max_anisotropy = 1.0f;
createinfo.compare_op = SDL_GPU_COMPAREOP_ALWAYS;
renderer->blitNearestSampler = METAL_CreateSampler(
(SDL_GPURenderer *)renderer,
&createinfo);
if (renderer->blitNearestSampler == NULL) {
SDL_LogError(SDL_LOG_CATEGORY_GPU, "Failed to create blit nearest sampler!");
}
createinfo.mag_filter = SDL_GPU_FILTER_LINEAR;
createinfo.min_filter = SDL_GPU_FILTER_LINEAR;
createinfo.mipmap_mode = SDL_GPU_SAMPLERMIPMAPMODE_LINEAR;
renderer->blitLinearSampler = METAL_CreateSampler(
(SDL_GPURenderer *)renderer,
&createinfo);
if (renderer->blitLinearSampler == NULL) {
SDL_LogError(SDL_LOG_CATEGORY_GPU, "Failed to create blit linear sampler!");
}
}
static void METAL_INTERNAL_DestroyBlitResources(
SDL_GPURenderer *driverData)
{
MetalRenderer *renderer = (MetalRenderer *)driverData;
METAL_ReleaseSampler(driverData, renderer->blitLinearSampler);
METAL_ReleaseSampler(driverData, renderer->blitNearestSampler);
METAL_ReleaseShader(driverData, renderer->blitVertexShader);
METAL_ReleaseShader(driverData, renderer->blitFrom2DShader);
METAL_ReleaseShader(driverData, renderer->blitFrom2DArrayShader);
METAL_ReleaseShader(driverData, renderer->blitFrom3DShader);
METAL_ReleaseShader(driverData, renderer->blitFromCubeShader);
METAL_ReleaseShader(driverData, renderer->blitFromCubeArrayShader);
for (Uint32 i = 0; i < renderer->blitPipelineCount; i += 1) {
METAL_ReleaseGraphicsPipeline(driverData, renderer->blitPipelines[i].pipeline);
}
SDL_free(renderer->blitPipelines);
}
static SDL_GPUDevice *METAL_CreateDevice(bool debugMode, bool preferLowPower, SDL_PropertiesID props)
{
@autoreleasepool {
MetalRenderer *renderer;
id<MTLDevice> device = NULL;
bool hasHardwareSupport = false;
bool verboseLogs = SDL_GetBooleanProperty(
props,
SDL_PROP_GPU_DEVICE_CREATE_VERBOSE_BOOLEAN,
true);
if (debugMode) {
SDL_setenv_unsafe("MTL_DEBUG_LAYER", "1", 0);
}
#ifdef SDL_PLATFORM_MACOS
if (preferLowPower) {
NSArray<id<MTLDevice>> *devices = MTLCopyAllDevices();
for (id<MTLDevice> candidate in devices) {
if (candidate.isLowPower) {
device = candidate;
break;
}
}
}
#endif
if (device == NULL) {
device = MTLCreateSystemDefaultDevice();
if (device == NULL) {
SDL_SetError("Failed to create Metal device");
return NULL;
}
}
#ifdef SDL_PLATFORM_MACOS
hasHardwareSupport = true;
bool allowMacFamily1 = SDL_GetBooleanProperty(
props,
SDL_PROP_GPU_DEVICE_CREATE_METAL_ALLOW_MACFAMILY1_BOOLEAN,
false);
if (@available(macOS 10.15, *)) {
hasHardwareSupport = allowMacFamily1 ?
[device supportsFamily:MTLGPUFamilyMac1] :
[device supportsFamily:MTLGPUFamilyMac2];
} else if (@available(macOS 10.14, *)) {
hasHardwareSupport = allowMacFamily1 ?
[device supportsFeatureSet:MTLFeatureSet_macOS_GPUFamily1_v4] :
[device supportsFeatureSet:MTLFeatureSet_macOS_GPUFamily2_v1];
}
#elif defined(SDL_PLATFORM_VISIONOS)
hasHardwareSupport = true;
#else
if (@available(iOS 13.0, tvOS 13.0, *)) {
hasHardwareSupport = [device supportsFamily:MTLGPUFamilyApple3];
}
#endif
if (!hasHardwareSupport) {
SDL_SetError("Device does not meet the hardware requirements for SDL_GPU Metal");
return NULL;
}
renderer = (MetalRenderer *)SDL_calloc(1, sizeof(MetalRenderer));
renderer->device = device;
renderer->queue = [device newCommandQueue];
renderer->props = SDL_CreateProperties();
if (verboseLogs) {
SDL_LogInfo(SDL_LOG_CATEGORY_GPU, "SDL_GPU Driver: Metal");
}
const char *deviceName = [device.name UTF8String];
SDL_SetStringProperty(
renderer->props,
SDL_PROP_GPU_DEVICE_NAME_STRING,
deviceName);
if (verboseLogs) {
SDL_LogInfo(SDL_LOG_CATEGORY_GPU, "Metal Device: %s", deviceName);
}
renderer->debugMode = debugMode;
renderer->allowedFramesInFlight = 2;
SwapchainCompositionToColorSpace[0] = kCGColorSpaceSRGB;
SwapchainCompositionToColorSpace[1] = kCGColorSpaceSRGB;
SwapchainCompositionToColorSpace[2] = kCGColorSpaceExtendedLinearSRGB;
if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) {
SwapchainCompositionToColorSpace[3] = kCGColorSpaceITUR_2100_PQ;
} else {
SwapchainCompositionToColorSpace[3] = NULL;
}
renderer->submitLock = SDL_CreateMutex();
renderer->acquireCommandBufferLock = SDL_CreateMutex();
renderer->acquireUniformBufferLock = SDL_CreateMutex();
renderer->disposeLock = SDL_CreateMutex();
renderer->fenceLock = SDL_CreateMutex();
renderer->windowLock = SDL_CreateMutex();
METAL_INTERNAL_AllocateCommandBuffers(renderer, 2);
renderer->availableFenceCapacity = 2;
renderer->availableFences = SDL_calloc(
renderer->availableFenceCapacity, sizeof(MetalFence *));
renderer->uniformBufferPoolCapacity = 32;
renderer->uniformBufferPoolCount = 32;
renderer->uniformBufferPool = SDL_calloc(
renderer->uniformBufferPoolCapacity, sizeof(MetalUniformBuffer *));
for (Uint32 i = 0; i < renderer->uniformBufferPoolCount; i += 1) {
renderer->uniformBufferPool[i] = METAL_INTERNAL_CreateUniformBuffer(
renderer,
UNIFORM_BUFFER_SIZE);
}
renderer->bufferContainersToDestroyCapacity = 2;
renderer->bufferContainersToDestroyCount = 0;
renderer->bufferContainersToDestroy = SDL_calloc(
renderer->bufferContainersToDestroyCapacity, sizeof(MetalBufferContainer *));
renderer->textureContainersToDestroyCapacity = 2;
renderer->textureContainersToDestroyCount = 0;
renderer->textureContainersToDestroy = SDL_calloc(
renderer->textureContainersToDestroyCapacity, sizeof(MetalTextureContainer *));
renderer->claimedWindowCapacity = 1;
renderer->claimedWindows = SDL_calloc(
renderer->claimedWindowCapacity, sizeof(MetalWindowData *));
METAL_INTERNAL_InitBlitResources(renderer);
SDL_GPUDevice *result = SDL_calloc(1, sizeof(SDL_GPUDevice));
ASSIGN_DRIVER(METAL)
result->driverData = (SDL_GPURenderer *)renderer;
result->shader_formats = SDL_GPU_SHADERFORMAT_MSL | SDL_GPU_SHADERFORMAT_METALLIB;
renderer->sdlGPUDevice = result;
return result;
}
}
SDL_GPUBootstrap MetalDriver = {
"metal",
METAL_PrepareDriver,
METAL_CreateDevice
};
#endif