Atlas - SDL_render_metal.m
Home / ext / SDL / src / render / metal Lines: 1 | Size: 96530 bytes [Download] [Show on GitHub] [Search similar files] [Raw] [Raw (proxy)][FILE BEGIN]1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2026 Sam Lantinga <[email protected]> 4 5 This software is provided 'as-is', without any express or implied 6 warranty. In no event will the authors be held liable for any damages 7 arising from the use of this software. 8 9 Permission is granted to anyone to use this software for any purpose, 10 including commercial applications, and to alter it and redistribute it 11 freely, subject to the following restrictions: 12 13 1. The origin of this software must not be misrepresented; you must not 14 claim that you wrote the original software. If you use this software 15 in a product, an acknowledgment in the product documentation would be 16 appreciated but is not required. 17 2. Altered source versions must be plainly marked as such, and must not be 18 misrepresented as being the original software. 19 3. This notice may not be removed or altered from any source distribution. 20*/ 21#include "SDL_internal.h" 22 23#ifdef SDL_VIDEO_RENDER_METAL 24 25#include "../SDL_sysrender.h" 26#include "../../video/SDL_pixels_c.h" 27 28#import <CoreVideo/CoreVideo.h> 29#import <Metal/Metal.h> 30#import <QuartzCore/CAMetalLayer.h> 31 32#ifdef SDL_VIDEO_DRIVER_COCOA 33#import <AppKit/NSWindow.h> 34#import <AppKit/NSView.h> 35#endif 36#ifdef SDL_VIDEO_DRIVER_UIKIT 37#import <UIKit/UIKit.h> 38#endif 39 40// Regenerate these with build-metal-shaders.sh 41#ifdef SDL_PLATFORM_MACOS 42#include "SDL_shaders_metal_macos.h" 43#elif defined(SDL_PLATFORM_TVOS) 44#if TARGET_OS_SIMULATOR 45#include "SDL_shaders_metal_tvsimulator.h" 46#else 47#include "SDL_shaders_metal_tvos.h" 48#endif 49#else 50#if TARGET_OS_SIMULATOR 51#include "SDL_shaders_metal_iphonesimulator.h" 52#else 53#include "SDL_shaders_metal_ios.h" 54#endif 55#endif 56 57// Apple Metal renderer implementation 58 59// macOS requires constants in a buffer to have a 256 byte alignment. 60// Use native type alignments from https://developer.apple.com/metal/Metal-Shading-Language-Specification.pdf 61#if defined(SDL_PLATFORM_MACOS) || TARGET_OS_SIMULATOR || TARGET_OS_MACCATALYST 62#define CONSTANT_ALIGN(x) (256) 63#else 64#define CONSTANT_ALIGN(x) (x < 4 ? 4 : x) 65#endif 66 67#define DEVICE_ALIGN(x) (x < 4 ? 4 : x) 68 69#define ALIGN_CONSTANTS(align, size) ((size + CONSTANT_ALIGN(align) - 1) & (~(CONSTANT_ALIGN(align) - 1))) 70 71static const size_t CONSTANTS_OFFSET_INVALID = 0xFFFFFFFF; 72static const size_t CONSTANTS_OFFSET_IDENTITY = 0; 73static const size_t CONSTANTS_OFFSET_HALF_PIXEL_TRANSFORM = ALIGN_CONSTANTS(16, CONSTANTS_OFFSET_IDENTITY + sizeof(float) * 16); 74static const size_t CONSTANTS_OFFSET_DECODE_BT601_LIMITED = ALIGN_CONSTANTS(16, CONSTANTS_OFFSET_HALF_PIXEL_TRANSFORM + sizeof(float) * 16); 75static const size_t CONSTANTS_OFFSET_DECODE_BT601_FULL = ALIGN_CONSTANTS(16, CONSTANTS_OFFSET_DECODE_BT601_LIMITED + sizeof(float) * 4 * 4); 76static const size_t CONSTANTS_OFFSET_DECODE_BT709_LIMITED = ALIGN_CONSTANTS(16, CONSTANTS_OFFSET_DECODE_BT601_FULL + sizeof(float) * 4 * 4); 77static const size_t CONSTANTS_OFFSET_DECODE_BT709_FULL = ALIGN_CONSTANTS(16, CONSTANTS_OFFSET_DECODE_BT709_LIMITED + sizeof(float) * 4 * 4); 78static const size_t CONSTANTS_OFFSET_DECODE_BT2020_LIMITED = ALIGN_CONSTANTS(16, CONSTANTS_OFFSET_DECODE_BT709_FULL + sizeof(float) * 4 * 4); 79static const size_t CONSTANTS_OFFSET_DECODE_BT2020_FULL = ALIGN_CONSTANTS(16, CONSTANTS_OFFSET_DECODE_BT2020_LIMITED + sizeof(float) * 4 * 4); 80static const size_t CONSTANTS_LENGTH = CONSTANTS_OFFSET_DECODE_BT2020_FULL + sizeof(float) * 4 * 4; 81 82typedef enum SDL_MetalVertexFunction 83{ 84 SDL_METAL_VERTEX_SOLID, 85 SDL_METAL_VERTEX_COPY, 86} SDL_MetalVertexFunction; 87 88typedef enum SDL_MetalFragmentFunction 89{ 90 SDL_METAL_FRAGMENT_SOLID = 0, 91 SDL_METAL_FRAGMENT_PALETTE, 92 SDL_METAL_FRAGMENT_COPY, 93 SDL_METAL_FRAGMENT_YUV, 94 SDL_METAL_FRAGMENT_NV12, 95 SDL_METAL_FRAGMENT_COUNT, 96} SDL_MetalFragmentFunction; 97 98typedef struct METAL_PipelineState 99{ 100 SDL_BlendMode blendMode; 101 void *pipe; 102} METAL_PipelineState; 103 104typedef struct METAL_PipelineCache 105{ 106 METAL_PipelineState *states; 107 int count; 108 SDL_MetalVertexFunction vertexFunction; 109 SDL_MetalFragmentFunction fragmentFunction; 110 MTLPixelFormat renderTargetFormat; 111 const char *label; 112} METAL_PipelineCache; 113 114/* Each shader combination used by drawing functions has a separate pipeline 115 * cache, and we have a separate list of caches for each render target pixel 116 * format. This is more efficient than iterating over a global cache to find 117 * the pipeline based on the specified shader combination and RT pixel format, 118 * since we know what the RT pixel format is when we set the render target, and 119 * we know what the shader combination is inside each drawing function's code. */ 120typedef struct METAL_ShaderPipelines 121{ 122 MTLPixelFormat renderTargetFormat; 123 METAL_PipelineCache caches[SDL_METAL_FRAGMENT_COUNT]; 124} METAL_ShaderPipelines; 125 126@interface SDL3METAL_RenderData : NSObject 127@property(nonatomic, retain) id<MTLDevice> mtldevice; 128@property(nonatomic, retain) id<MTLCommandQueue> mtlcmdqueue; 129@property(nonatomic, retain) id<MTLCommandBuffer> mtlcmdbuffer; 130@property(nonatomic, retain) id<MTLRenderCommandEncoder> mtlcmdencoder; 131@property(nonatomic, retain) id<MTLLibrary> mtllibrary; 132@property(nonatomic, retain) id<CAMetalDrawable> mtlbackbuffer; 133@property(nonatomic, retain) NSMutableDictionary<NSNumber *, id<MTLSamplerState>> *mtlsamplers; 134@property(nonatomic, retain) id<MTLBuffer> mtlbufconstants; 135@property(nonatomic, retain) id<MTLBuffer> mtlbufquadindices; 136@property(nonatomic, assign) SDL_MetalView mtlview; 137@property(nonatomic, retain) CAMetalLayer *mtllayer; 138@property(nonatomic, retain) MTLRenderPassDescriptor *mtlpassdesc; 139@property(nonatomic, assign) METAL_ShaderPipelines *activepipelines; 140@property(nonatomic, assign) METAL_ShaderPipelines *allpipelines; 141@property(nonatomic, assign) int pipelinescount; 142@end 143 144@implementation SDL3METAL_RenderData 145@end 146 147@interface SDL3METAL_PaletteData : NSObject 148@property(nonatomic, retain) id<MTLTexture> mtltexture; 149@property(nonatomic, assign) BOOL hasdata; 150@end 151 152@implementation SDL3METAL_PaletteData 153@end 154 155@interface SDL3METAL_TextureData : NSObject 156@property(nonatomic, retain) id<MTLTexture> mtltexture; 157@property(nonatomic, retain) id<MTLTexture> mtlpalette; 158@property(nonatomic, retain) id<MTLTexture> mtltextureUv; 159@property(nonatomic, assign) SDL_MetalFragmentFunction fragmentFunction; 160#ifdef SDL_HAVE_YUV 161@property(nonatomic, assign) BOOL yuv; 162@property(nonatomic, assign) BOOL nv12; 163@property(nonatomic, assign) size_t conversionBufferOffset; 164#endif 165@property(nonatomic, assign) BOOL hasdata; 166@property(nonatomic, retain) id<MTLBuffer> lockedbuffer; 167@property(nonatomic, assign) SDL_Rect lockedrect; 168@end 169 170@implementation SDL3METAL_TextureData 171@end 172 173static bool METAL_UpdateTextureInternal(SDL_Renderer *renderer, BOOL hasdata, 174 id<MTLTexture> texture, SDL_Rect rect, int slice, 175 const void *pixels, int pitch); 176 177static const MTLBlendOperation invalidBlendOperation = (MTLBlendOperation)0xFFFFFFFF; 178static const MTLBlendFactor invalidBlendFactor = (MTLBlendFactor)0xFFFFFFFF; 179 180static MTLBlendOperation GetBlendOperation(SDL_BlendOperation operation) 181{ 182 switch (operation) { 183 case SDL_BLENDOPERATION_ADD: 184 return MTLBlendOperationAdd; 185 case SDL_BLENDOPERATION_SUBTRACT: 186 return MTLBlendOperationSubtract; 187 case SDL_BLENDOPERATION_REV_SUBTRACT: 188 return MTLBlendOperationReverseSubtract; 189 case SDL_BLENDOPERATION_MINIMUM: 190 return MTLBlendOperationMin; 191 case SDL_BLENDOPERATION_MAXIMUM: 192 return MTLBlendOperationMax; 193 default: 194 return invalidBlendOperation; 195 } 196} 197 198static MTLBlendFactor GetBlendFactor(SDL_BlendFactor factor) 199{ 200 switch (factor) { 201 case SDL_BLENDFACTOR_ZERO: 202 return MTLBlendFactorZero; 203 case SDL_BLENDFACTOR_ONE: 204 return MTLBlendFactorOne; 205 case SDL_BLENDFACTOR_SRC_COLOR: 206 return MTLBlendFactorSourceColor; 207 case SDL_BLENDFACTOR_ONE_MINUS_SRC_COLOR: 208 return MTLBlendFactorOneMinusSourceColor; 209 case SDL_BLENDFACTOR_SRC_ALPHA: 210 return MTLBlendFactorSourceAlpha; 211 case SDL_BLENDFACTOR_ONE_MINUS_SRC_ALPHA: 212 return MTLBlendFactorOneMinusSourceAlpha; 213 case SDL_BLENDFACTOR_DST_COLOR: 214 return MTLBlendFactorDestinationColor; 215 case SDL_BLENDFACTOR_ONE_MINUS_DST_COLOR: 216 return MTLBlendFactorOneMinusDestinationColor; 217 case SDL_BLENDFACTOR_DST_ALPHA: 218 return MTLBlendFactorDestinationAlpha; 219 case SDL_BLENDFACTOR_ONE_MINUS_DST_ALPHA: 220 return MTLBlendFactorOneMinusDestinationAlpha; 221 default: 222 return invalidBlendFactor; 223 } 224} 225 226static NSString *GetVertexFunctionName(SDL_MetalVertexFunction function) 227{ 228 switch (function) { 229 case SDL_METAL_VERTEX_SOLID: 230 return @"SDL_Solid_vertex"; 231 case SDL_METAL_VERTEX_COPY: 232 return @"SDL_Copy_vertex"; 233 default: 234 return nil; 235 } 236} 237 238static NSString *GetFragmentFunctionName(SDL_MetalFragmentFunction function) 239{ 240 switch (function) { 241 case SDL_METAL_FRAGMENT_SOLID: 242 return @"SDL_Solid_fragment"; 243 case SDL_METAL_FRAGMENT_PALETTE: 244 return @"SDL_Palette_fragment"; 245 case SDL_METAL_FRAGMENT_COPY: 246 return @"SDL_Copy_fragment"; 247 case SDL_METAL_FRAGMENT_YUV: 248 return @"SDL_YUV_fragment"; 249 case SDL_METAL_FRAGMENT_NV12: 250 return @"SDL_NV12_fragment"; 251 default: 252 return nil; 253 } 254} 255 256static id<MTLRenderPipelineState> MakePipelineState(SDL3METAL_RenderData *data, METAL_PipelineCache *cache, 257 NSString *blendlabel, SDL_BlendMode blendmode) 258{ 259 MTLRenderPipelineDescriptor *mtlpipedesc; 260 MTLVertexDescriptor *vertdesc; 261 MTLRenderPipelineColorAttachmentDescriptor *rtdesc; 262 NSError *err = nil; 263 id<MTLRenderPipelineState> state; 264 METAL_PipelineState pipeline; 265 METAL_PipelineState *states; 266 267 id<MTLFunction> mtlvertfn = [data.mtllibrary newFunctionWithName:GetVertexFunctionName(cache->vertexFunction)]; 268 id<MTLFunction> mtlfragfn = [data.mtllibrary newFunctionWithName:GetFragmentFunctionName(cache->fragmentFunction)]; 269 SDL_assert(mtlvertfn != nil); 270 SDL_assert(mtlfragfn != nil); 271 272 mtlpipedesc = [[MTLRenderPipelineDescriptor alloc] init]; 273 mtlpipedesc.vertexFunction = mtlvertfn; 274 mtlpipedesc.fragmentFunction = mtlfragfn; 275 276 vertdesc = [MTLVertexDescriptor vertexDescriptor]; 277 278 switch (cache->vertexFunction) { 279 case SDL_METAL_VERTEX_SOLID: 280 // position (float2), color (float4) 281 vertdesc.layouts[0].stride = sizeof(float) * 2 + sizeof(float) * 4; 282 vertdesc.layouts[0].stepFunction = MTLVertexStepFunctionPerVertex; 283 284 vertdesc.attributes[0].format = MTLVertexFormatFloat2; 285 vertdesc.attributes[0].offset = 0; 286 vertdesc.attributes[0].bufferIndex = 0; 287 288 vertdesc.attributes[1].format = MTLVertexFormatFloat4; 289 vertdesc.attributes[1].offset = sizeof(float) * 2; 290 vertdesc.attributes[1].bufferIndex = 0; 291 292 break; 293 case SDL_METAL_VERTEX_COPY: 294 // position (float2), color (float4), texcoord (float2) 295 vertdesc.layouts[0].stride = sizeof(float) * 2 + sizeof(float) * 4 + sizeof(float) * 2; 296 vertdesc.layouts[0].stepFunction = MTLVertexStepFunctionPerVertex; 297 298 vertdesc.attributes[0].format = MTLVertexFormatFloat2; 299 vertdesc.attributes[0].offset = 0; 300 vertdesc.attributes[0].bufferIndex = 0; 301 302 vertdesc.attributes[1].format = MTLVertexFormatFloat4; 303 vertdesc.attributes[1].offset = sizeof(float) * 2; 304 vertdesc.attributes[1].bufferIndex = 0; 305 306 vertdesc.attributes[2].format = MTLVertexFormatFloat2; 307 vertdesc.attributes[2].offset = sizeof(float) * 2 + sizeof(float) * 4; 308 vertdesc.attributes[2].bufferIndex = 0; 309 break; 310 } 311 312 mtlpipedesc.vertexDescriptor = vertdesc; 313 314 rtdesc = mtlpipedesc.colorAttachments[0]; 315 rtdesc.pixelFormat = cache->renderTargetFormat; 316 317 if (blendmode != SDL_BLENDMODE_NONE) { 318 rtdesc.blendingEnabled = YES; 319 rtdesc.sourceRGBBlendFactor = GetBlendFactor(SDL_GetBlendModeSrcColorFactor(blendmode)); 320 rtdesc.destinationRGBBlendFactor = GetBlendFactor(SDL_GetBlendModeDstColorFactor(blendmode)); 321 rtdesc.rgbBlendOperation = GetBlendOperation(SDL_GetBlendModeColorOperation(blendmode)); 322 rtdesc.sourceAlphaBlendFactor = GetBlendFactor(SDL_GetBlendModeSrcAlphaFactor(blendmode)); 323 rtdesc.destinationAlphaBlendFactor = GetBlendFactor(SDL_GetBlendModeDstAlphaFactor(blendmode)); 324 rtdesc.alphaBlendOperation = GetBlendOperation(SDL_GetBlendModeAlphaOperation(blendmode)); 325 } else { 326 rtdesc.blendingEnabled = NO; 327 } 328 329 mtlpipedesc.label = [@(cache->label) stringByAppendingString:blendlabel]; 330 331 state = [data.mtldevice newRenderPipelineStateWithDescriptor:mtlpipedesc error:&err]; 332 SDL_assert(err == nil); 333 334 pipeline.blendMode = blendmode; 335 pipeline.pipe = (void *)CFBridgingRetain(state); 336 337 states = SDL_realloc(cache->states, (cache->count + 1) * sizeof(pipeline)); 338 339 if (states) { 340 states[cache->count++] = pipeline; 341 cache->states = states; 342 return (__bridge id<MTLRenderPipelineState>)pipeline.pipe; 343 } else { 344 CFBridgingRelease(pipeline.pipe); 345 return NULL; 346 } 347} 348 349static void MakePipelineCache(SDL3METAL_RenderData *data, METAL_PipelineCache *cache, const char *label, 350 MTLPixelFormat rtformat, SDL_MetalVertexFunction vertfn, SDL_MetalFragmentFunction fragfn) 351{ 352 SDL_zerop(cache); 353 354 cache->vertexFunction = vertfn; 355 cache->fragmentFunction = fragfn; 356 cache->renderTargetFormat = rtformat; 357 cache->label = label; 358 359 /* Create pipeline states for the default blend modes. Custom blend modes 360 * will be added to the cache on-demand. */ 361 MakePipelineState(data, cache, @" (blend=none)", SDL_BLENDMODE_NONE); 362 MakePipelineState(data, cache, @" (blend=blend)", SDL_BLENDMODE_BLEND); 363 MakePipelineState(data, cache, @" (blend=add)", SDL_BLENDMODE_ADD); 364 MakePipelineState(data, cache, @" (blend=mod)", SDL_BLENDMODE_MOD); 365 MakePipelineState(data, cache, @" (blend=mul)", SDL_BLENDMODE_MUL); 366} 367 368static void DestroyPipelineCache(METAL_PipelineCache *cache) 369{ 370 if (cache != NULL) { 371 for (int i = 0; i < cache->count; i++) { 372 CFBridgingRelease(cache->states[i].pipe); 373 } 374 375 SDL_free(cache->states); 376 } 377} 378 379void MakeShaderPipelines(SDL3METAL_RenderData *data, METAL_ShaderPipelines *pipelines, MTLPixelFormat rtformat) 380{ 381 SDL_zerop(pipelines); 382 383 pipelines->renderTargetFormat = rtformat; 384 385 MakePipelineCache(data, &pipelines->caches[SDL_METAL_FRAGMENT_SOLID], "SDL primitives pipeline", rtformat, SDL_METAL_VERTEX_SOLID, SDL_METAL_FRAGMENT_SOLID); 386 MakePipelineCache(data, &pipelines->caches[SDL_METAL_FRAGMENT_PALETTE], "SDL palette pipeline", rtformat, SDL_METAL_VERTEX_COPY, SDL_METAL_FRAGMENT_PALETTE); 387 MakePipelineCache(data, &pipelines->caches[SDL_METAL_FRAGMENT_COPY], "SDL copy pipeline", rtformat, SDL_METAL_VERTEX_COPY, SDL_METAL_FRAGMENT_COPY); 388 MakePipelineCache(data, &pipelines->caches[SDL_METAL_FRAGMENT_YUV], "SDL YUV pipeline", rtformat, SDL_METAL_VERTEX_COPY, SDL_METAL_FRAGMENT_YUV); 389 MakePipelineCache(data, &pipelines->caches[SDL_METAL_FRAGMENT_NV12], "SDL NV12 pipeline", rtformat, SDL_METAL_VERTEX_COPY, SDL_METAL_FRAGMENT_NV12); 390} 391 392static METAL_ShaderPipelines *ChooseShaderPipelines(SDL3METAL_RenderData *data, MTLPixelFormat rtformat) 393{ 394 METAL_ShaderPipelines *allpipelines = data.allpipelines; 395 int count = data.pipelinescount; 396 397 for (int i = 0; i < count; i++) { 398 if (allpipelines[i].renderTargetFormat == rtformat) { 399 return &allpipelines[i]; 400 } 401 } 402 403 allpipelines = SDL_realloc(allpipelines, (count + 1) * sizeof(METAL_ShaderPipelines)); 404 405 if (allpipelines == NULL) { 406 return NULL; 407 } 408 409 MakeShaderPipelines(data, &allpipelines[count], rtformat); 410 411 data.allpipelines = allpipelines; 412 data.pipelinescount = count + 1; 413 414 return &data.allpipelines[count]; 415} 416 417static void DestroyAllPipelines(METAL_ShaderPipelines *allpipelines, int count) 418{ 419 if (allpipelines != NULL) { 420 for (int i = 0; i < count; i++) { 421 for (int cache = 0; cache < SDL_METAL_FRAGMENT_COUNT; cache++) { 422 DestroyPipelineCache(&allpipelines[i].caches[cache]); 423 } 424 } 425 426 SDL_free(allpipelines); 427 } 428} 429 430static inline id<MTLRenderPipelineState> ChoosePipelineState(SDL3METAL_RenderData *data, METAL_ShaderPipelines *pipelines, SDL_MetalFragmentFunction fragfn, SDL_BlendMode blendmode) 431{ 432 METAL_PipelineCache *cache = &pipelines->caches[fragfn]; 433 434 for (int i = 0; i < cache->count; i++) { 435 if (cache->states[i].blendMode == blendmode) { 436 return (__bridge id<MTLRenderPipelineState>)cache->states[i].pipe; 437 } 438 } 439 440 return MakePipelineState(data, cache, [NSString stringWithFormat:@" (blend=custom 0x%x)", blendmode], blendmode); 441} 442 443static bool METAL_ActivateRenderCommandEncoder(SDL_Renderer *renderer, MTLLoadAction load, MTLClearColor *clear_color, id<MTLBuffer> vertex_buffer) 444{ 445 SDL3METAL_RenderData *data = (__bridge SDL3METAL_RenderData *)renderer->internal; 446 447 /* Our SetRenderTarget just signals that the next render operation should 448 * set up a new render pass. This is where that work happens. */ 449 if (data.mtlcmdencoder == nil) { 450 id<MTLTexture> mtltexture = nil; 451 452 if (renderer->target != NULL) { 453 SDL3METAL_TextureData *texdata = (__bridge SDL3METAL_TextureData *)renderer->target->internal; 454 mtltexture = texdata.mtltexture; 455 } else { 456 if (data.mtlbackbuffer == nil) { 457 /* The backbuffer's contents aren't guaranteed to persist after 458 * presenting, so we can leave it undefined when loading it. */ 459 data.mtlbackbuffer = [data.mtllayer nextDrawable]; 460 if (load == MTLLoadActionLoad) { 461 load = MTLLoadActionDontCare; 462 } 463 } 464 if (data.mtlbackbuffer != nil) { 465 mtltexture = data.mtlbackbuffer.texture; 466 } 467 } 468 469 /* mtltexture can be nil here if macOS refused to give us a drawable, 470 which apparently can happen for minimized windows, etc. */ 471 if (mtltexture == nil) { 472 return false; 473 } 474 475 if (load == MTLLoadActionClear) { 476 SDL_assert(clear_color != NULL); 477 data.mtlpassdesc.colorAttachments[0].clearColor = *clear_color; 478 } 479 480 data.mtlpassdesc.colorAttachments[0].loadAction = load; 481 data.mtlpassdesc.colorAttachments[0].texture = mtltexture; 482 483 data.mtlcmdbuffer = [data.mtlcmdqueue commandBuffer]; 484 data.mtlcmdencoder = [data.mtlcmdbuffer renderCommandEncoderWithDescriptor:data.mtlpassdesc]; 485 486 if (data.mtlbackbuffer != nil && mtltexture == data.mtlbackbuffer.texture) { 487 data.mtlcmdencoder.label = @"SDL metal renderer backbuffer"; 488 } else { 489 data.mtlcmdencoder.label = @"SDL metal renderer render target"; 490 } 491 492 /* Set up buffer bindings for positions, texcoords, and color once here, 493 * the offsets are adjusted in the code that uses them. */ 494 if (vertex_buffer != nil) { 495 [data.mtlcmdencoder setVertexBuffer:vertex_buffer offset:0 atIndex:0]; 496 [data.mtlcmdencoder setFragmentBuffer:vertex_buffer offset:0 atIndex:0]; 497 } 498 499 data.activepipelines = ChooseShaderPipelines(data, mtltexture.pixelFormat); 500 501 // make sure this has a definite place in the queue. This way it will 502 // execute reliably whether the app tries to make its own command buffers 503 // or whatever. This means we can _always_ batch rendering commands! 504 [data.mtlcmdbuffer enqueue]; 505 } 506 507 return true; 508} 509 510static void METAL_WindowEvent(SDL_Renderer *renderer, const SDL_WindowEvent *event) 511{ 512} 513 514static bool METAL_GetOutputSize(SDL_Renderer *renderer, int *w, int *h) 515{ 516 @autoreleasepool { 517 SDL3METAL_RenderData *data = (__bridge SDL3METAL_RenderData *)renderer->internal; 518 if (w) { 519 *w = (int)data.mtllayer.drawableSize.width; 520 } 521 if (h) { 522 *h = (int)data.mtllayer.drawableSize.height; 523 } 524 return true; 525 } 526} 527 528static bool METAL_SupportsBlendMode(SDL_Renderer *renderer, SDL_BlendMode blendMode) 529{ 530 SDL_BlendFactor srcColorFactor = SDL_GetBlendModeSrcColorFactor(blendMode); 531 SDL_BlendFactor srcAlphaFactor = SDL_GetBlendModeSrcAlphaFactor(blendMode); 532 SDL_BlendOperation colorOperation = SDL_GetBlendModeColorOperation(blendMode); 533 SDL_BlendFactor dstColorFactor = SDL_GetBlendModeDstColorFactor(blendMode); 534 SDL_BlendFactor dstAlphaFactor = SDL_GetBlendModeDstAlphaFactor(blendMode); 535 SDL_BlendOperation alphaOperation = SDL_GetBlendModeAlphaOperation(blendMode); 536 537 if (GetBlendFactor(srcColorFactor) == invalidBlendFactor || 538 GetBlendFactor(srcAlphaFactor) == invalidBlendFactor || 539 GetBlendOperation(colorOperation) == invalidBlendOperation || 540 GetBlendFactor(dstColorFactor) == invalidBlendFactor || 541 GetBlendFactor(dstAlphaFactor) == invalidBlendFactor || 542 GetBlendOperation(alphaOperation) == invalidBlendOperation) { 543 return false; 544 } 545 return true; 546} 547 548size_t GetBT601ConversionMatrix(SDL_Colorspace colorspace) 549{ 550 switch (SDL_COLORSPACERANGE(colorspace)) { 551 case SDL_COLOR_RANGE_LIMITED: 552 case SDL_COLOR_RANGE_UNKNOWN: 553 return CONSTANTS_OFFSET_DECODE_BT601_LIMITED; 554 case SDL_COLOR_RANGE_FULL: 555 return CONSTANTS_OFFSET_DECODE_BT601_FULL; 556 default: 557 break; 558 } 559 return 0; 560} 561 562size_t GetBT709ConversionMatrix(SDL_Colorspace colorspace) 563{ 564 switch (SDL_COLORSPACERANGE(colorspace)) { 565 case SDL_COLOR_RANGE_LIMITED: 566 case SDL_COLOR_RANGE_UNKNOWN: 567 return CONSTANTS_OFFSET_DECODE_BT709_LIMITED; 568 case SDL_COLOR_RANGE_FULL: 569 return CONSTANTS_OFFSET_DECODE_BT709_FULL; 570 default: 571 break; 572 } 573 return 0; 574} 575 576size_t GetBT2020ConversionMatrix(SDL_Colorspace colorspace) 577{ 578 switch (SDL_COLORSPACERANGE(colorspace)) { 579 case SDL_COLOR_RANGE_LIMITED: 580 case SDL_COLOR_RANGE_UNKNOWN: 581 return CONSTANTS_OFFSET_DECODE_BT2020_LIMITED; 582 case SDL_COLOR_RANGE_FULL: 583 return CONSTANTS_OFFSET_DECODE_BT2020_FULL; 584 default: 585 break; 586 } 587 return 0; 588} 589 590size_t GetYCbCRtoRGBConversionMatrix(SDL_Colorspace colorspace, int w, int h, int bits_per_pixel) 591{ 592 const int YUV_SD_THRESHOLD = 576; 593 594 switch (SDL_COLORSPACEMATRIX(colorspace)) { 595 case SDL_MATRIX_COEFFICIENTS_BT470BG: 596 case SDL_MATRIX_COEFFICIENTS_BT601: 597 return GetBT601ConversionMatrix(colorspace); 598 599 case SDL_MATRIX_COEFFICIENTS_BT709: 600 return GetBT709ConversionMatrix(colorspace); 601 602 case SDL_MATRIX_COEFFICIENTS_BT2020_NCL: 603 return GetBT2020ConversionMatrix(colorspace); 604 605 case SDL_MATRIX_COEFFICIENTS_UNSPECIFIED: 606 switch (bits_per_pixel) { 607 case 8: 608 if (h <= YUV_SD_THRESHOLD) { 609 return GetBT601ConversionMatrix(colorspace); 610 } else { 611 return GetBT709ConversionMatrix(colorspace); 612 } 613 case 10: 614 case 16: 615 return GetBT2020ConversionMatrix(colorspace); 616 default: 617 break; 618 } 619 break; 620 default: 621 break; 622 } 623 return 0; 624} 625 626static bool METAL_CreatePalette(SDL_Renderer *renderer, SDL_TexturePalette *palette) 627{ 628 @autoreleasepool { 629 SDL3METAL_RenderData *data = (__bridge SDL3METAL_RenderData *)renderer->internal; 630 MTLPixelFormat pixfmt; 631 MTLTextureDescriptor *mtltexdesc; 632 id<MTLTexture> mtltexture = nil; 633 SDL3METAL_PaletteData *palettedata; 634 635 if (renderer->output_colorspace == SDL_COLORSPACE_SRGB_LINEAR) { 636 pixfmt = MTLPixelFormatRGBA8Unorm_sRGB; 637 } else { 638 pixfmt = MTLPixelFormatRGBA8Unorm; 639 } 640 mtltexdesc = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:pixfmt 641 width:256 642 height:1 643 mipmapped:NO]; 644 mtltexdesc.usage = MTLTextureUsageShaderRead; 645 646 mtltexture = [data.mtldevice newTextureWithDescriptor:mtltexdesc]; 647 if (mtltexture == nil) { 648 return SDL_SetError("Palette allocation failed"); 649 } 650 651 palettedata = [[SDL3METAL_PaletteData alloc] init]; 652 palettedata.mtltexture = mtltexture; 653 palette->internal = (void *)CFBridgingRetain(palettedata); 654 655 return true; 656 } 657} 658 659static bool METAL_UpdatePalette(SDL_Renderer *renderer, SDL_TexturePalette *palette, int ncolors, SDL_Color *colors) 660{ 661 @autoreleasepool { 662 SDL3METAL_PaletteData *palettedata = (__bridge SDL3METAL_PaletteData *)palette->internal; 663 SDL_Rect rect = { 0, 0, ncolors, 1 }; 664 665 if (!METAL_UpdateTextureInternal(renderer, palettedata.hasdata, palettedata.mtltexture, rect, 0, colors, ncolors * sizeof(*colors))) { 666 return false; 667 } 668 palettedata.hasdata = true; 669 return true; 670 } 671} 672 673static void METAL_DestroyPalette(SDL_Renderer *renderer, SDL_TexturePalette *palette) 674{ 675 @autoreleasepool { 676 CFBridgingRelease(palette->internal); 677 palette->internal = NULL; 678 } 679} 680 681static bool METAL_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SDL_PropertiesID create_props) 682{ 683 @autoreleasepool { 684 SDL3METAL_RenderData *data = (__bridge SDL3METAL_RenderData *)renderer->internal; 685 MTLPixelFormat pixfmt = MTLPixelFormatInvalid; 686 MTLTextureDescriptor *mtltexdesc; 687 id<MTLTexture> mtltexture = nil, mtltextureUv = nil; 688 SDL3METAL_TextureData *texturedata; 689 CVPixelBufferRef pixelbuffer = nil; 690 IOSurfaceRef surface = nil; 691 692 pixelbuffer = SDL_GetPointerProperty(create_props, SDL_PROP_TEXTURE_CREATE_METAL_PIXELBUFFER_POINTER, nil); 693 if (pixelbuffer) { 694 surface = CVPixelBufferGetIOSurface(pixelbuffer); 695 if (!surface) { 696 return SDL_SetError("CVPixelBufferGetIOSurface() failed"); 697 } 698 } 699 700 switch (texture->format) { 701 case SDL_PIXELFORMAT_ABGR8888: 702 if (renderer->output_colorspace == SDL_COLORSPACE_SRGB_LINEAR) { 703 pixfmt = MTLPixelFormatRGBA8Unorm_sRGB; 704 } else { 705 pixfmt = MTLPixelFormatRGBA8Unorm; 706 } 707 break; 708 case SDL_PIXELFORMAT_ARGB8888: 709 if (renderer->output_colorspace == SDL_COLORSPACE_SRGB_LINEAR) { 710 pixfmt = MTLPixelFormatBGRA8Unorm_sRGB; 711 } else { 712 pixfmt = MTLPixelFormatBGRA8Unorm; 713 } 714 break; 715 case SDL_PIXELFORMAT_ABGR2101010: 716 pixfmt = MTLPixelFormatRGB10A2Unorm; 717 break; 718 case SDL_PIXELFORMAT_RGB565: 719 if (@available(macOS 11.0, *)) { 720 pixfmt = MTLPixelFormatB5G6R5Unorm; 721 } 722 break; 723 case SDL_PIXELFORMAT_RGBA5551: 724 if (@available(macOS 11.0, *)) { 725 pixfmt = MTLPixelFormatA1BGR5Unorm; 726 } 727 break; 728 case SDL_PIXELFORMAT_ARGB1555: 729 if (@available(macOS 11.0, *)) { 730 pixfmt = MTLPixelFormatBGR5A1Unorm; 731 } 732 break; 733 case SDL_PIXELFORMAT_RGBA4444: 734 if (@available(macOS 11.0, *)) { 735 pixfmt = MTLPixelFormatABGR4Unorm; 736 } 737 break; 738 case SDL_PIXELFORMAT_INDEX8: 739 case SDL_PIXELFORMAT_IYUV: 740 case SDL_PIXELFORMAT_YV12: 741 case SDL_PIXELFORMAT_NV12: 742 case SDL_PIXELFORMAT_NV21: 743 pixfmt = MTLPixelFormatR8Unorm; 744 break; 745 case SDL_PIXELFORMAT_P010: 746 pixfmt = MTLPixelFormatR16Unorm; 747 break; 748 case SDL_PIXELFORMAT_RGBA64_FLOAT: 749 pixfmt = MTLPixelFormatRGBA16Float; 750 break; 751 case SDL_PIXELFORMAT_RGBA128_FLOAT: 752 pixfmt = MTLPixelFormatRGBA32Float; 753 break; 754 default: 755 break; 756 } 757 758 if (pixfmt == MTLPixelFormatInvalid) { 759 return SDL_SetError("Texture format %s not supported by Metal", SDL_GetPixelFormatName(texture->format)); 760 } 761 762 mtltexdesc = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:pixfmt 763 width:(NSUInteger)texture->w 764 height:(NSUInteger)texture->h 765 mipmapped:NO]; 766 767 if (texture->access == SDL_TEXTUREACCESS_TARGET) { 768 mtltexdesc.usage = MTLTextureUsageShaderRead | MTLTextureUsageRenderTarget; 769 } else { 770 mtltexdesc.usage = MTLTextureUsageShaderRead; 771 } 772 773 if (surface) { 774 mtltexture = [data.mtldevice newTextureWithDescriptor:mtltexdesc iosurface:surface plane:0]; 775 } else { 776 mtltexture = [data.mtldevice newTextureWithDescriptor:mtltexdesc]; 777 } 778 if (mtltexture == nil) { 779 return SDL_SetError("Texture allocation failed"); 780 } 781 782 mtltextureUv = nil; 783#ifdef SDL_HAVE_YUV 784 BOOL yuv = (texture->format == SDL_PIXELFORMAT_IYUV || texture->format == SDL_PIXELFORMAT_YV12); 785 BOOL nv12 = (texture->format == SDL_PIXELFORMAT_NV12 || texture->format == SDL_PIXELFORMAT_NV21 || texture->format == SDL_PIXELFORMAT_P010); 786 787 if (yuv) { 788 mtltexdesc.pixelFormat = MTLPixelFormatR8Unorm; 789 mtltexdesc.width = (texture->w + 1) / 2; 790 mtltexdesc.height = (texture->h + 1) / 2; 791 mtltexdesc.textureType = MTLTextureType2DArray; 792 mtltexdesc.arrayLength = 2; 793 } else if (texture->format == SDL_PIXELFORMAT_P010) { 794 mtltexdesc.pixelFormat = MTLPixelFormatRG16Unorm; 795 mtltexdesc.width = (texture->w + 1) / 2; 796 mtltexdesc.height = (texture->h + 1) / 2; 797 } else if (nv12) { 798 mtltexdesc.pixelFormat = MTLPixelFormatRG8Unorm; 799 mtltexdesc.width = (texture->w + 1) / 2; 800 mtltexdesc.height = (texture->h + 1) / 2; 801 } 802 803 if (yuv || nv12) { 804 if (surface) { 805 mtltextureUv = [data.mtldevice newTextureWithDescriptor:mtltexdesc iosurface:surface plane:1]; 806 } else { 807 mtltextureUv = [data.mtldevice newTextureWithDescriptor:mtltexdesc]; 808 } 809 if (mtltextureUv == nil) { 810 return SDL_SetError("Texture allocation failed"); 811 } 812 } 813#endif // SDL_HAVE_YUV 814 texturedata = [[SDL3METAL_TextureData alloc] init]; 815 if (texture->format == SDL_PIXELFORMAT_INDEX8) { 816 texturedata.fragmentFunction = SDL_METAL_FRAGMENT_PALETTE; 817#ifdef SDL_HAVE_YUV 818 } else if (yuv) { 819 texturedata.fragmentFunction = SDL_METAL_FRAGMENT_YUV; 820 } else if (nv12) { 821 texturedata.fragmentFunction = SDL_METAL_FRAGMENT_NV12; 822#endif 823 } else { 824 texturedata.fragmentFunction = SDL_METAL_FRAGMENT_COPY; 825 } 826 texturedata.mtltexture = mtltexture; 827 texturedata.mtltextureUv = mtltextureUv; 828#ifdef SDL_HAVE_YUV 829 texturedata.yuv = yuv; 830 texturedata.nv12 = nv12; 831 if (yuv || nv12) { 832 size_t offset = GetYCbCRtoRGBConversionMatrix(texture->colorspace, texture->w, texture->h, 8); 833 if (offset == 0) { 834 return SDL_SetError("Unsupported YUV colorspace"); 835 } 836 texturedata.conversionBufferOffset = offset; 837 } 838#endif 839 texture->internal = (void *)CFBridgingRetain(texturedata); 840 841 return true; 842 } 843} 844 845static void METAL_UploadTextureData(id<MTLTexture> texture, SDL_Rect rect, int slice, 846 const void *pixels, int pitch) 847{ 848 [texture replaceRegion:MTLRegionMake2D(rect.x, rect.y, rect.w, rect.h) 849 mipmapLevel:0 850 slice:slice 851 withBytes:pixels 852 bytesPerRow:pitch 853 bytesPerImage:0]; 854} 855 856static MTLStorageMode METAL_GetStorageMode(id<MTLResource> resource) 857{ 858 return resource.storageMode; 859} 860 861static bool METAL_UpdateTextureInternal(SDL_Renderer *renderer, BOOL hasdata, 862 id<MTLTexture> texture, SDL_Rect rect, int slice, 863 const void *pixels, int pitch) 864{ 865 SDL3METAL_RenderData *data = (__bridge SDL3METAL_RenderData *)renderer->internal; 866 SDL_Rect stagingrect = { 0, 0, rect.w, rect.h }; 867 MTLTextureDescriptor *desc; 868 id<MTLTexture> stagingtex; 869 id<MTLBlitCommandEncoder> blitcmd; 870 871 /* If the texture is managed or shared and this is the first upload, we can 872 * use replaceRegion to upload to it directly. Otherwise we upload the data 873 * to a staging texture and copy that over. */ 874 if (!hasdata && METAL_GetStorageMode(texture) != MTLStorageModePrivate) { 875 METAL_UploadTextureData(texture, rect, slice, pixels, pitch); 876 return true; 877 } 878 879 desc = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:texture.pixelFormat 880 width:rect.w 881 height:rect.h 882 mipmapped:NO]; 883 884 if (desc == nil) { 885 return SDL_OutOfMemory(); 886 } 887 888 /* TODO: We could have a pool of textures or a MTLHeap we allocate from, 889 * and release a staging texture back to the pool in the command buffer's 890 * completion handler. */ 891 stagingtex = [data.mtldevice newTextureWithDescriptor:desc]; 892 if (stagingtex == nil) { 893 return SDL_OutOfMemory(); 894 } 895 896 METAL_UploadTextureData(stagingtex, stagingrect, 0, pixels, pitch); 897 898 if (data.mtlcmdencoder != nil) { 899 [data.mtlcmdencoder endEncoding]; 900 data.mtlcmdencoder = nil; 901 } 902 903 if (data.mtlcmdbuffer == nil) { 904 data.mtlcmdbuffer = [data.mtlcmdqueue commandBuffer]; 905 } 906 907 blitcmd = [data.mtlcmdbuffer blitCommandEncoder]; 908 909 [blitcmd copyFromTexture:stagingtex 910 sourceSlice:0 911 sourceLevel:0 912 sourceOrigin:MTLOriginMake(0, 0, 0) 913 sourceSize:MTLSizeMake(rect.w, rect.h, 1) 914 toTexture:texture 915 destinationSlice:slice 916 destinationLevel:0 917 destinationOrigin:MTLOriginMake(rect.x, rect.y, 0)]; 918 919 [blitcmd endEncoding]; 920 921 /* TODO: This isn't very efficient for the YUV formats, which call 922 * UpdateTextureInternal multiple times in a row. */ 923 [data.mtlcmdbuffer commit]; 924 data.mtlcmdbuffer = nil; 925 926 return true; 927} 928 929static bool METAL_UpdateTexture(SDL_Renderer *renderer, SDL_Texture *texture, 930 const SDL_Rect *rect, const void *pixels, int pitch) 931{ 932 @autoreleasepool { 933 SDL3METAL_TextureData *texturedata = (__bridge SDL3METAL_TextureData *)texture->internal; 934 935 if (!METAL_UpdateTextureInternal(renderer, texturedata.hasdata, texturedata.mtltexture, *rect, 0, pixels, pitch)) { 936 return false; 937 } 938#ifdef SDL_HAVE_YUV 939 if (texturedata.yuv) { 940 int Uslice = texture->format == SDL_PIXELFORMAT_YV12 ? 1 : 0; 941 int Vslice = texture->format == SDL_PIXELFORMAT_YV12 ? 0 : 1; 942 int UVpitch = (pitch + 1) / 2; 943 SDL_Rect UVrect = { rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2 }; 944 945 // Skip to the correct offset into the next texture 946 pixels = (const void *)((const Uint8 *)pixels + rect->h * pitch); 947 if (!METAL_UpdateTextureInternal(renderer, texturedata.hasdata, texturedata.mtltextureUv, UVrect, Uslice, pixels, UVpitch)) { 948 return false; 949 } 950 951 // Skip to the correct offset into the next texture 952 pixels = (const void *)((const Uint8 *)pixels + UVrect.h * UVpitch); 953 if (!METAL_UpdateTextureInternal(renderer, texturedata.hasdata, texturedata.mtltextureUv, UVrect, Vslice, pixels, UVpitch)) { 954 return false; 955 } 956 } 957 958 if (texturedata.nv12) { 959 SDL_Rect UVrect = { rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2 }; 960 int UVpitch = 2 * ((pitch + 1) / 2); 961 962 // Skip to the correct offset into the next texture 963 pixels = (const void *)((const Uint8 *)pixels + rect->h * pitch); 964 if (!METAL_UpdateTextureInternal(renderer, texturedata.hasdata, texturedata.mtltextureUv, UVrect, 0, pixels, UVpitch)) { 965 return false; 966 } 967 } 968#endif 969 texturedata.hasdata = YES; 970 971 return true; 972 } 973} 974 975#ifdef SDL_HAVE_YUV 976static bool METAL_UpdateTextureYUV(SDL_Renderer *renderer, SDL_Texture *texture, 977 const SDL_Rect *rect, 978 const Uint8 *Yplane, int Ypitch, 979 const Uint8 *Uplane, int Upitch, 980 const Uint8 *Vplane, int Vpitch) 981{ 982 @autoreleasepool { 983 SDL3METAL_TextureData *texturedata = (__bridge SDL3METAL_TextureData *)texture->internal; 984 const int Uslice = 0; 985 const int Vslice = 1; 986 SDL_Rect UVrect = { rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2 }; 987 988 // Bail out if we're supposed to update an empty rectangle 989 if (rect->w <= 0 || rect->h <= 0) { 990 return true; 991 } 992 993 if (!METAL_UpdateTextureInternal(renderer, texturedata.hasdata, texturedata.mtltexture, *rect, 0, Yplane, Ypitch)) { 994 return false; 995 } 996 if (!METAL_UpdateTextureInternal(renderer, texturedata.hasdata, texturedata.mtltextureUv, UVrect, Uslice, Uplane, Upitch)) { 997 return false; 998 } 999 if (!METAL_UpdateTextureInternal(renderer, texturedata.hasdata, texturedata.mtltextureUv, UVrect, Vslice, Vplane, Vpitch)) { 1000 return false; 1001 } 1002 1003 texturedata.hasdata = YES; 1004 1005 return true; 1006 } 1007} 1008 1009static bool METAL_UpdateTextureNV(SDL_Renderer *renderer, SDL_Texture *texture, 1010 const SDL_Rect *rect, 1011 const Uint8 *Yplane, int Ypitch, 1012 const Uint8 *UVplane, int UVpitch) 1013{ 1014 @autoreleasepool { 1015 SDL3METAL_TextureData *texturedata = (__bridge SDL3METAL_TextureData *)texture->internal; 1016 SDL_Rect UVrect = { rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2 }; 1017 1018 // Bail out if we're supposed to update an empty rectangle 1019 if (rect->w <= 0 || rect->h <= 0) { 1020 return true; 1021 } 1022 1023 if (!METAL_UpdateTextureInternal(renderer, texturedata.hasdata, texturedata.mtltexture, *rect, 0, Yplane, Ypitch)) { 1024 return false; 1025 } 1026 1027 if (!METAL_UpdateTextureInternal(renderer, texturedata.hasdata, texturedata.mtltextureUv, UVrect, 0, UVplane, UVpitch)) { 1028 return false; 1029 } 1030 1031 texturedata.hasdata = YES; 1032 1033 return true; 1034 } 1035} 1036#endif 1037 1038static bool METAL_LockTexture(SDL_Renderer *renderer, SDL_Texture *texture, 1039 const SDL_Rect *rect, void **pixels, int *pitch) 1040{ 1041 @autoreleasepool { 1042 SDL3METAL_RenderData *data = (__bridge SDL3METAL_RenderData *)renderer->internal; 1043 SDL3METAL_TextureData *texturedata = (__bridge SDL3METAL_TextureData *)texture->internal; 1044 int buffersize = 0; 1045 id<MTLBuffer> lockedbuffer = nil; 1046 1047 if (rect->w <= 0 || rect->h <= 0) { 1048 return SDL_SetError("Invalid rectangle dimensions for LockTexture."); 1049 } 1050 1051 *pitch = SDL_BYTESPERPIXEL(texture->format) * rect->w; 1052#ifdef SDL_HAVE_YUV 1053 if (texturedata.yuv || texturedata.nv12) { 1054 buffersize = ((*pitch) * rect->h) + (2 * (*pitch + 1) / 2) * ((rect->h + 1) / 2); 1055 } else 1056#endif 1057 { 1058 buffersize = (*pitch) * rect->h; 1059 } 1060 1061 lockedbuffer = [data.mtldevice newBufferWithLength:buffersize options:MTLResourceStorageModeShared]; 1062 if (lockedbuffer == nil) { 1063 return SDL_OutOfMemory(); 1064 } 1065 1066 texturedata.lockedrect = *rect; 1067 texturedata.lockedbuffer = lockedbuffer; 1068 *pixels = [lockedbuffer contents]; 1069 1070 return true; 1071 } 1072} 1073 1074static void METAL_UnlockTexture(SDL_Renderer *renderer, SDL_Texture *texture) 1075{ 1076 @autoreleasepool { 1077 SDL3METAL_RenderData *data = (__bridge SDL3METAL_RenderData *)renderer->internal; 1078 SDL3METAL_TextureData *texturedata = (__bridge SDL3METAL_TextureData *)texture->internal; 1079 id<MTLBlitCommandEncoder> blitcmd; 1080 SDL_Rect rect = texturedata.lockedrect; 1081 int pitch = SDL_BYTESPERPIXEL(texture->format) * rect.w; 1082#ifdef SDL_HAVE_YUV 1083 SDL_Rect UVrect = { rect.x / 2, rect.y / 2, (rect.w + 1) / 2, (rect.h + 1) / 2 }; 1084#endif 1085 1086 if (texturedata.lockedbuffer == nil) { 1087 return; 1088 } 1089 1090 if (data.mtlcmdencoder != nil) { 1091 [data.mtlcmdencoder endEncoding]; 1092 data.mtlcmdencoder = nil; 1093 } 1094 1095 if (data.mtlcmdbuffer == nil) { 1096 data.mtlcmdbuffer = [data.mtlcmdqueue commandBuffer]; 1097 } 1098 1099 blitcmd = [data.mtlcmdbuffer blitCommandEncoder]; 1100 1101 [blitcmd copyFromBuffer:texturedata.lockedbuffer 1102 sourceOffset:0 1103 sourceBytesPerRow:pitch 1104 sourceBytesPerImage:0 1105 sourceSize:MTLSizeMake(rect.w, rect.h, 1) 1106 toTexture:texturedata.mtltexture 1107 destinationSlice:0 1108 destinationLevel:0 1109 destinationOrigin:MTLOriginMake(rect.x, rect.y, 0)]; 1110#ifdef SDL_HAVE_YUV 1111 if (texturedata.yuv) { 1112 int Uslice = texture->format == SDL_PIXELFORMAT_YV12 ? 1 : 0; 1113 int Vslice = texture->format == SDL_PIXELFORMAT_YV12 ? 0 : 1; 1114 int UVpitch = (pitch + 1) / 2; 1115 1116 [blitcmd copyFromBuffer:texturedata.lockedbuffer 1117 sourceOffset:rect.h * pitch 1118 sourceBytesPerRow:UVpitch 1119 sourceBytesPerImage:UVpitch * UVrect.h 1120 sourceSize:MTLSizeMake(UVrect.w, UVrect.h, 1) 1121 toTexture:texturedata.mtltextureUv 1122 destinationSlice:Uslice 1123 destinationLevel:0 1124 destinationOrigin:MTLOriginMake(UVrect.x, UVrect.y, 0)]; 1125 1126 [blitcmd copyFromBuffer:texturedata.lockedbuffer 1127 sourceOffset:(rect.h * pitch) + UVrect.h * UVpitch 1128 sourceBytesPerRow:UVpitch 1129 sourceBytesPerImage:UVpitch * UVrect.h 1130 sourceSize:MTLSizeMake(UVrect.w, UVrect.h, 1) 1131 toTexture:texturedata.mtltextureUv 1132 destinationSlice:Vslice 1133 destinationLevel:0 1134 destinationOrigin:MTLOriginMake(UVrect.x, UVrect.y, 0)]; 1135 } 1136 1137 if (texturedata.nv12) { 1138 int UVpitch = 2 * ((pitch + 1) / 2); 1139 1140 [blitcmd copyFromBuffer:texturedata.lockedbuffer 1141 sourceOffset:rect.h * pitch 1142 sourceBytesPerRow:UVpitch 1143 sourceBytesPerImage:0 1144 sourceSize:MTLSizeMake(UVrect.w, UVrect.h, 1) 1145 toTexture:texturedata.mtltextureUv 1146 destinationSlice:0 1147 destinationLevel:0 1148 destinationOrigin:MTLOriginMake(UVrect.x, UVrect.y, 0)]; 1149 } 1150#endif 1151 [blitcmd endEncoding]; 1152 1153 [data.mtlcmdbuffer commit]; 1154 data.mtlcmdbuffer = nil; 1155 1156 texturedata.lockedbuffer = nil; // Retained property, so it calls release. 1157 texturedata.hasdata = YES; 1158 } 1159} 1160 1161static bool METAL_SetRenderTarget(SDL_Renderer *renderer, SDL_Texture *texture) 1162{ 1163 @autoreleasepool { 1164 SDL3METAL_RenderData *data = (__bridge SDL3METAL_RenderData *)renderer->internal; 1165 1166 if (data.mtlcmdencoder) { 1167 /* End encoding for the previous render target so we can set up a new 1168 * render pass for this one. */ 1169 [data.mtlcmdencoder endEncoding]; 1170 [data.mtlcmdbuffer commit]; 1171 1172 data.mtlcmdencoder = nil; 1173 data.mtlcmdbuffer = nil; 1174 } 1175 1176 /* We don't begin a new render pass right away - we delay it until an actual 1177 * draw or clear happens. That way we can use hardware clears when possible, 1178 * which are only available when beginning a new render pass. */ 1179 return true; 1180 } 1181} 1182 1183static bool METAL_QueueSetViewport(SDL_Renderer *renderer, SDL_RenderCommand *cmd) 1184{ 1185 float projection[4][4]; // Prepare an orthographic projection 1186 const int w = cmd->data.viewport.rect.w; 1187 const int h = cmd->data.viewport.rect.h; 1188 const size_t matrixlen = sizeof(projection); 1189 float *matrix = (float *)SDL_AllocateRenderVertices(renderer, matrixlen, CONSTANT_ALIGN(16), &cmd->data.viewport.first); 1190 if (!matrix) { 1191 return false; 1192 } 1193 1194 SDL_memset(projection, '\0', matrixlen); 1195 if (w && h) { 1196 projection[0][0] = 2.0f / w; 1197 projection[1][1] = -2.0f / h; 1198 projection[3][0] = -1.0f; 1199 projection[3][1] = 1.0f; 1200 projection[3][3] = 1.0f; 1201 } 1202 SDL_memcpy(matrix, projection, matrixlen); 1203 1204 return true; 1205} 1206 1207static bool METAL_QueueNoOp(SDL_Renderer *renderer, SDL_RenderCommand *cmd) 1208{ 1209 return true; // nothing to do in this backend. 1210} 1211 1212static bool METAL_QueueDrawPoints(SDL_Renderer *renderer, SDL_RenderCommand *cmd, const SDL_FPoint *points, int count) 1213{ 1214 SDL_FColor color = cmd->data.draw.color; 1215 bool convert_color = SDL_RenderingLinearSpace(renderer); 1216 1217 const size_t vertlen = (2 * sizeof(float) + 4 * sizeof(float)) * count; 1218 float *verts = (float *)SDL_AllocateRenderVertices(renderer, vertlen, DEVICE_ALIGN(8), &cmd->data.draw.first); 1219 if (!verts) { 1220 return false; 1221 } 1222 cmd->data.draw.count = count; 1223 1224 if (convert_color) { 1225 SDL_ConvertToLinear(&color); 1226 } 1227 1228 for (int i = 0; i < count; i++, points++) { 1229 *(verts++) = points->x; 1230 *(verts++) = points->y; 1231 *(verts++) = color.r; 1232 *(verts++) = color.g; 1233 *(verts++) = color.b; 1234 *(verts++) = color.a; 1235 } 1236 return true; 1237} 1238 1239static bool METAL_QueueDrawLines(SDL_Renderer *renderer, SDL_RenderCommand *cmd, const SDL_FPoint *points, int count) 1240{ 1241 SDL_FColor color = cmd->data.draw.color; 1242 bool convert_color = SDL_RenderingLinearSpace(renderer); 1243 size_t vertlen; 1244 float *verts; 1245 1246 SDL_assert(count >= 2); // should have been checked at the higher level. 1247 1248 vertlen = (2 * sizeof(float) + 4 * sizeof(float)) * count; 1249 verts = (float *)SDL_AllocateRenderVertices(renderer, vertlen, DEVICE_ALIGN(8), &cmd->data.draw.first); 1250 if (!verts) { 1251 return false; 1252 } 1253 cmd->data.draw.count = count; 1254 1255 if (convert_color) { 1256 SDL_ConvertToLinear(&color); 1257 } 1258 1259 for (int i = 0; i < count; i++, points++) { 1260 *(verts++) = points->x; 1261 *(verts++) = points->y; 1262 *(verts++) = color.r; 1263 *(verts++) = color.g; 1264 *(verts++) = color.b; 1265 *(verts++) = color.a; 1266 } 1267 1268 /* If the line segment is completely horizontal or vertical, 1269 make it one pixel longer, to satisfy the diamond-exit rule. 1270 We should probably do this for diagonal lines too, but we'd have to 1271 do some trigonometry to figure out the correct pixel and generally 1272 when we have problems with pixel perfection, it's for straight lines 1273 that are missing a pixel that frames something and not arbitrary 1274 angles. Maybe !!! FIXME for later, though. */ 1275 1276 points -= 2; // update the last line. 1277 verts -= 6; 1278 1279 { 1280 const float xstart = points[0].x; 1281 const float ystart = points[0].y; 1282 const float xend = points[1].x; 1283 const float yend = points[1].y; 1284 1285 if (ystart == yend) { // horizontal line 1286 verts[0] += (xend > xstart) ? 1.0f : -1.0f; 1287 } else if (xstart == xend) { // vertical line 1288 verts[1] += (yend > ystart) ? 1.0f : -1.0f; 1289 } 1290 } 1291 1292 return true; 1293} 1294 1295static bool METAL_QueueGeometry(SDL_Renderer *renderer, SDL_RenderCommand *cmd, SDL_Texture *texture, 1296 const float *xy, int xy_stride, const SDL_FColor *color, int color_stride, const float *uv, int uv_stride, 1297 int num_vertices, const void *indices, int num_indices, int size_indices, 1298 float scale_x, float scale_y) 1299{ 1300 bool convert_color = SDL_RenderingLinearSpace(renderer); 1301 int count = indices ? num_indices : num_vertices; 1302 const size_t vertlen = (2 * sizeof(float) + 4 * sizeof(float) + (texture ? 2 : 0) * sizeof(float)) * count; 1303 float *verts = (float *)SDL_AllocateRenderVertices(renderer, vertlen, DEVICE_ALIGN(8), &cmd->data.draw.first); 1304 if (!verts) { 1305 return false; 1306 } 1307 1308 cmd->data.draw.count = count; 1309 size_indices = indices ? size_indices : 0; 1310 1311 for (int i = 0; i < count; i++) { 1312 int j; 1313 float *xy_; 1314 SDL_FColor col_; 1315 if (size_indices == 4) { 1316 j = ((const Uint32 *)indices)[i]; 1317 } else if (size_indices == 2) { 1318 j = ((const Uint16 *)indices)[i]; 1319 } else if (size_indices == 1) { 1320 j = ((const Uint8 *)indices)[i]; 1321 } else { 1322 j = i; 1323 } 1324 1325 xy_ = (float *)((char *)xy + j * xy_stride); 1326 1327 *(verts++) = xy_[0] * scale_x; 1328 *(verts++) = xy_[1] * scale_y; 1329 1330 col_ = *(SDL_FColor *)((char *)color + j * color_stride); 1331 1332 if (convert_color) { 1333 SDL_ConvertToLinear(&col_); 1334 } 1335 1336 *(verts++) = col_.r; 1337 *(verts++) = col_.g; 1338 *(verts++) = col_.b; 1339 *(verts++) = col_.a; 1340 1341 if (texture) { 1342 float *uv_ = (float *)((char *)uv + j * uv_stride); 1343 *(verts++) = uv_[0]; 1344 *(verts++) = uv_[1]; 1345 } 1346 } 1347 1348 return true; 1349} 1350 1351// These should mirror the definitions in SDL_shaders_metal.metal 1352//static const float TONEMAP_NONE = 0; 1353//static const float TONEMAP_LINEAR = 1; 1354static const float TONEMAP_CHROME = 2; 1355 1356//static const float TEXTURETYPE_NONE = 0; 1357static const float TEXTURETYPE_RGB = 1; 1358static const float TEXTURETYPE_RGB_PIXELART = 2; 1359static const float TEXTURETYPE_PALETTE_NEAREST = 3; 1360static const float TEXTURETYPE_PALETTE_LINEAR = 4; 1361static const float TEXTURETYPE_PALETTE_PIXELART = 5; 1362static const float TEXTURETYPE_NV12 = 6; 1363static const float TEXTURETYPE_NV21 = 7; 1364static const float TEXTURETYPE_YUV = 8; 1365 1366//static const float INPUTTYPE_UNSPECIFIED = 0; 1367static const float INPUTTYPE_SRGB = 1; 1368static const float INPUTTYPE_SCRGB = 2; 1369static const float INPUTTYPE_HDR10 = 3; 1370 1371typedef struct 1372{ 1373 float scRGB_output; 1374 float texture_type; 1375 float input_type; 1376 float color_scale; 1377 1378 float texel_width; 1379 float texel_height; 1380 float texture_width; 1381 float texture_height; 1382 1383 float tonemap_method; 1384 float tonemap_factor1; 1385 float tonemap_factor2; 1386 float sdr_white_point; 1387} PixelShaderConstants; 1388 1389typedef struct 1390{ 1391 __unsafe_unretained id<MTLRenderPipelineState> pipeline; 1392 __unsafe_unretained id<MTLBuffer> vertex_buffer; 1393 size_t constants_offset; 1394 SDL_Texture *texture; 1395 bool texture_palette; 1396 SDL_PixelFormat texture_format; 1397 SDL_ScaleMode texture_scale_mode; 1398 SDL_TextureAddressMode texture_address_mode_u; 1399 SDL_TextureAddressMode texture_address_mode_v; 1400 bool cliprect_dirty; 1401 bool cliprect_enabled; 1402 SDL_Rect cliprect; 1403 bool viewport_dirty; 1404 SDL_Rect viewport; 1405 size_t projection_offset; 1406 bool shader_constants_dirty; 1407 PixelShaderConstants shader_constants; 1408} METAL_DrawStateCache; 1409 1410static void SetupShaderConstants(SDL_Renderer *renderer, const SDL_RenderCommand *cmd, const SDL_Texture *texture, PixelShaderConstants *constants) 1411{ 1412 float output_headroom; 1413 1414 SDL_zerop(constants); 1415 1416 constants->scRGB_output = (float)SDL_RenderingLinearSpace(renderer); 1417 constants->color_scale = cmd->data.draw.color_scale; 1418 1419 if (texture) { 1420 switch (texture->format) { 1421 case SDL_PIXELFORMAT_INDEX8: 1422 switch (cmd->data.draw.texture_scale_mode) { 1423 case SDL_SCALEMODE_NEAREST: 1424 constants->texture_type = TEXTURETYPE_PALETTE_NEAREST; 1425 break; 1426 case SDL_SCALEMODE_LINEAR: 1427 constants->texture_type = TEXTURETYPE_PALETTE_LINEAR; 1428 break; 1429 case SDL_SCALEMODE_PIXELART: 1430 constants->texture_type = TEXTURETYPE_PALETTE_PIXELART; 1431 break; 1432 default: 1433 SDL_assert(!"Unknown scale mode"); 1434 break; 1435 } 1436 break; 1437 case SDL_PIXELFORMAT_YV12: 1438 case SDL_PIXELFORMAT_IYUV: 1439 constants->texture_type = TEXTURETYPE_YUV; 1440 break; 1441 case SDL_PIXELFORMAT_NV12: 1442 constants->texture_type = TEXTURETYPE_NV12; 1443 break; 1444 case SDL_PIXELFORMAT_NV21: 1445 constants->texture_type = TEXTURETYPE_NV21; 1446 break; 1447 case SDL_PIXELFORMAT_P010: 1448 constants->texture_type = TEXTURETYPE_NV12; 1449 break; 1450 default: 1451 if (cmd->data.draw.texture_scale_mode == SDL_SCALEMODE_PIXELART) { 1452 constants->texture_type = TEXTURETYPE_RGB_PIXELART; 1453 } else { 1454 constants->texture_type = TEXTURETYPE_RGB; 1455 } 1456 } 1457 1458 switch (SDL_COLORSPACETRANSFER(texture->colorspace)) { 1459 case SDL_TRANSFER_CHARACTERISTICS_LINEAR: 1460 constants->input_type = INPUTTYPE_SCRGB; 1461 break; 1462 case SDL_TRANSFER_CHARACTERISTICS_PQ: 1463 constants->input_type = INPUTTYPE_HDR10; 1464 break; 1465 default: 1466 constants->input_type = INPUTTYPE_SRGB; 1467 break; 1468 } 1469 1470 if (constants->texture_type == TEXTURETYPE_PALETTE_LINEAR || 1471 constants->texture_type == TEXTURETYPE_PALETTE_PIXELART || 1472 constants->texture_type == TEXTURETYPE_RGB_PIXELART) { 1473 constants->texture_width = texture->w; 1474 constants->texture_height = texture->h; 1475 constants->texel_width = 1.0f / constants->texture_width; 1476 constants->texel_height = 1.0f / constants->texture_height; 1477 } 1478 1479 constants->sdr_white_point = texture->SDR_white_point; 1480 1481 if (renderer->target) { 1482 output_headroom = renderer->target->HDR_headroom; 1483 } else { 1484 output_headroom = renderer->HDR_headroom; 1485 } 1486 1487 if (texture->HDR_headroom > output_headroom && output_headroom > 0.0f) { 1488 constants->tonemap_method = TONEMAP_CHROME; 1489 constants->tonemap_factor1 = (output_headroom / (texture->HDR_headroom * texture->HDR_headroom)); 1490 constants->tonemap_factor2 = (1.0f / output_headroom); 1491 } 1492 } 1493} 1494 1495static bool SetDrawState(SDL_Renderer *renderer, const SDL_RenderCommand *cmd, const SDL_MetalFragmentFunction shader, PixelShaderConstants *shader_constants, const size_t constants_offset, id<MTLBuffer> mtlbufvertex, METAL_DrawStateCache *statecache) 1496{ 1497 SDL3METAL_RenderData *data = (__bridge SDL3METAL_RenderData *)renderer->internal; 1498 const SDL_BlendMode blend = cmd->data.draw.blend; 1499 size_t first = cmd->data.draw.first; 1500 id<MTLRenderPipelineState> newpipeline; 1501 PixelShaderConstants solid_constants; 1502 1503 if (!METAL_ActivateRenderCommandEncoder(renderer, MTLLoadActionLoad, NULL, statecache->vertex_buffer)) { 1504 return false; 1505 } 1506 1507 if (statecache->viewport_dirty) { 1508 MTLViewport viewport; 1509 viewport.originX = statecache->viewport.x; 1510 viewport.originY = statecache->viewport.y; 1511 viewport.width = statecache->viewport.w; 1512 viewport.height = statecache->viewport.h; 1513 viewport.znear = 0.0; 1514 viewport.zfar = 1.0; 1515 [data.mtlcmdencoder setViewport:viewport]; 1516 [data.mtlcmdencoder setVertexBuffer:mtlbufvertex offset:statecache->projection_offset atIndex:2]; // projection 1517 statecache->viewport_dirty = false; 1518 } 1519 1520 if (statecache->cliprect_dirty) { 1521 SDL_Rect output; 1522 SDL_Rect clip; 1523 if (statecache->cliprect_enabled) { 1524 clip = statecache->cliprect; 1525 clip.x += statecache->viewport.x; 1526 clip.y += statecache->viewport.y; 1527 } else { 1528 clip = statecache->viewport; 1529 } 1530 1531 // Set Scissor Rect Validation: w/h must be <= render pass 1532 SDL_zero(output); 1533 1534 if (renderer->target) { 1535 output.w = renderer->target->w; 1536 output.h = renderer->target->h; 1537 } else { 1538 METAL_GetOutputSize(renderer, &output.w, &output.h); 1539 } 1540 1541 if (SDL_GetRectIntersection(&output, &clip, &clip)) { 1542 MTLScissorRect mtlrect; 1543 mtlrect.x = clip.x; 1544 mtlrect.y = clip.y; 1545 mtlrect.width = clip.w; 1546 mtlrect.height = clip.h; 1547 [data.mtlcmdencoder setScissorRect:mtlrect]; 1548 } 1549 1550 statecache->cliprect_dirty = false; 1551 } 1552 1553 newpipeline = ChoosePipelineState(data, data.activepipelines, shader, blend); 1554 if (newpipeline != statecache->pipeline) { 1555 [data.mtlcmdencoder setRenderPipelineState:newpipeline]; 1556 statecache->pipeline = newpipeline; 1557 } 1558 1559 if (!shader_constants) { 1560 SetupShaderConstants(renderer, cmd, NULL, &solid_constants); 1561 shader_constants = &solid_constants; 1562 } 1563 1564 if (statecache->shader_constants_dirty || 1565 SDL_memcmp(shader_constants, &statecache->shader_constants, sizeof(*shader_constants)) != 0) { 1566 id<MTLBuffer> mtlbufconstants = [data.mtldevice newBufferWithLength:sizeof(*shader_constants) options:MTLResourceStorageModeShared]; 1567 mtlbufconstants.label = @"SDL shader constants data"; 1568 SDL_memcpy([mtlbufconstants contents], shader_constants, sizeof(*shader_constants)); 1569 [data.mtlcmdencoder setFragmentBuffer:mtlbufconstants offset:0 atIndex:0]; 1570 1571 SDL_memcpy(&statecache->shader_constants, shader_constants, sizeof(*shader_constants)); 1572 statecache->shader_constants_dirty = false; 1573 } 1574 1575 if (constants_offset != statecache->constants_offset) { 1576 if (constants_offset != CONSTANTS_OFFSET_INVALID) { 1577 [data.mtlcmdencoder setVertexBuffer:data.mtlbufconstants offset:constants_offset atIndex:3]; 1578 } 1579 statecache->constants_offset = constants_offset; 1580 } 1581 1582 [data.mtlcmdencoder setVertexBufferOffset:first atIndex:0]; // position/texcoords 1583 return true; 1584} 1585 1586static id<MTLSamplerState> GetSampler(SDL3METAL_RenderData *data, SDL_PixelFormat format, SDL_ScaleMode scale_mode, SDL_TextureAddressMode address_u, SDL_TextureAddressMode address_v) 1587{ 1588 if (format == SDL_PIXELFORMAT_INDEX8) { 1589 // We'll do linear sampling in the shader if needed 1590 scale_mode = SDL_SCALEMODE_NEAREST; 1591 } 1592 1593 NSNumber *key = [NSNumber numberWithInteger:RENDER_SAMPLER_HASHKEY(scale_mode, address_u, address_v)]; 1594 id<MTLSamplerState> mtlsampler = data.mtlsamplers[key]; 1595 if (mtlsampler == nil) { 1596 MTLSamplerDescriptor *samplerdesc; 1597 samplerdesc = [[MTLSamplerDescriptor alloc] init]; 1598 switch (scale_mode) { 1599 case SDL_SCALEMODE_NEAREST: 1600 samplerdesc.minFilter = MTLSamplerMinMagFilterNearest; 1601 samplerdesc.magFilter = MTLSamplerMinMagFilterNearest; 1602 break; 1603 case SDL_SCALEMODE_PIXELART: // Uses linear sampling 1604 case SDL_SCALEMODE_LINEAR: 1605 samplerdesc.minFilter = MTLSamplerMinMagFilterLinear; 1606 samplerdesc.magFilter = MTLSamplerMinMagFilterLinear; 1607 break; 1608 default: 1609 SDL_SetError("Unknown scale mode: %d", scale_mode); 1610 return nil; 1611 } 1612 switch (address_u) { 1613 case SDL_TEXTURE_ADDRESS_CLAMP: 1614 samplerdesc.sAddressMode = MTLSamplerAddressModeClampToEdge; 1615 break; 1616 case SDL_TEXTURE_ADDRESS_WRAP: 1617 samplerdesc.sAddressMode = MTLSamplerAddressModeRepeat; 1618 break; 1619 default: 1620 SDL_SetError("Unknown texture address mode: %d", address_u); 1621 return nil; 1622 } 1623 switch (address_v) { 1624 case SDL_TEXTURE_ADDRESS_CLAMP: 1625 samplerdesc.tAddressMode = MTLSamplerAddressModeClampToEdge; 1626 break; 1627 case SDL_TEXTURE_ADDRESS_WRAP: 1628 samplerdesc.tAddressMode = MTLSamplerAddressModeRepeat; 1629 break; 1630 default: 1631 SDL_SetError("Unknown texture address mode: %d", address_v); 1632 return nil; 1633 } 1634 mtlsampler = [data.mtldevice newSamplerStateWithDescriptor:samplerdesc]; 1635 if (mtlsampler == nil) { 1636 SDL_SetError("Couldn't create sampler"); 1637 return nil; 1638 } 1639 data.mtlsamplers[key] = mtlsampler; 1640 } 1641 return mtlsampler; 1642} 1643 1644static bool SetCopyState(SDL_Renderer *renderer, const SDL_RenderCommand *cmd, const size_t constants_offset, 1645 id<MTLBuffer> mtlbufvertex, METAL_DrawStateCache *statecache) 1646{ 1647 SDL3METAL_RenderData *data = (__bridge SDL3METAL_RenderData *)renderer->internal; 1648 SDL_Texture *texture = cmd->data.draw.texture; 1649 SDL3METAL_TextureData *texturedata = (__bridge SDL3METAL_TextureData *)texture->internal; 1650 PixelShaderConstants constants; 1651 1652 SetupShaderConstants(renderer, cmd, texture, &constants); 1653 1654 if (!SetDrawState(renderer, cmd, texturedata.fragmentFunction, &constants, constants_offset, mtlbufvertex, statecache)) { 1655 return false; 1656 } 1657 1658 if (texture != statecache->texture) { 1659 [data.mtlcmdencoder setFragmentTexture:texturedata.mtltexture atIndex:0]; 1660 if (texture->palette) { 1661 SDL3METAL_PaletteData *palette = (__bridge SDL3METAL_PaletteData *)texture->palette->internal; 1662 [data.mtlcmdencoder setFragmentTexture:palette.mtltexture atIndex:1]; 1663 } 1664#ifdef SDL_HAVE_YUV 1665 if (texturedata.yuv || texturedata.nv12) { 1666 [data.mtlcmdencoder setFragmentTexture:texturedata.mtltextureUv atIndex:1]; 1667 [data.mtlcmdencoder setFragmentBuffer:data.mtlbufconstants offset:texturedata.conversionBufferOffset atIndex:1]; 1668 } 1669#endif 1670 statecache->texture = texture; 1671 } 1672 1673 if (texture->format != statecache->texture_format || 1674 cmd->data.draw.texture_scale_mode != statecache->texture_scale_mode || 1675 cmd->data.draw.texture_address_mode_u != statecache->texture_address_mode_u || 1676 cmd->data.draw.texture_address_mode_v != statecache->texture_address_mode_v) { 1677 id<MTLSamplerState> mtlsampler = GetSampler(data, texture->format, cmd->data.draw.texture_scale_mode, cmd->data.draw.texture_address_mode_u, cmd->data.draw.texture_address_mode_v); 1678 if (mtlsampler == nil) { 1679 return false; 1680 } 1681 [data.mtlcmdencoder setFragmentSamplerState:mtlsampler atIndex:0]; 1682 1683 statecache->texture_format = texture->format; 1684 statecache->texture_scale_mode = cmd->data.draw.texture_scale_mode; 1685 statecache->texture_address_mode_u = cmd->data.draw.texture_address_mode_u; 1686 statecache->texture_address_mode_v = cmd->data.draw.texture_address_mode_v; 1687 } 1688 if (texture->palette) { 1689 if (!statecache->texture_palette) { 1690 id<MTLSamplerState> mtlsampler = GetSampler(data, SDL_PIXELFORMAT_UNKNOWN, SDL_SCALEMODE_NEAREST, SDL_TEXTURE_ADDRESS_CLAMP, SDL_TEXTURE_ADDRESS_CLAMP); 1691 if (mtlsampler == nil) { 1692 return false; 1693 } 1694 [data.mtlcmdencoder setFragmentSamplerState:mtlsampler atIndex:1]; 1695 statecache->texture_palette = true; 1696 } 1697 } else { 1698 statecache->texture_palette = false; 1699 } 1700 return true; 1701} 1702 1703static void METAL_InvalidateCachedState(SDL_Renderer *renderer) 1704{ 1705 // METAL_DrawStateCache only exists during a run of METAL_RunCommandQueue, so there's nothing to invalidate! 1706} 1707 1708static bool METAL_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd, void *vertices, size_t vertsize) 1709{ 1710 @autoreleasepool { 1711 SDL3METAL_RenderData *data = (__bridge SDL3METAL_RenderData *)renderer->internal; 1712 id<MTLBuffer> mtlbufvertex = nil; 1713 METAL_DrawStateCache statecache; 1714 SDL_zero(statecache); 1715 1716 statecache.pipeline = nil; 1717 statecache.vertex_buffer = nil; 1718 statecache.constants_offset = CONSTANTS_OFFSET_INVALID; 1719 statecache.texture = NULL; 1720 statecache.texture_scale_mode = SDL_SCALEMODE_INVALID; 1721 statecache.texture_address_mode_u = SDL_TEXTURE_ADDRESS_INVALID; 1722 statecache.texture_address_mode_v = SDL_TEXTURE_ADDRESS_INVALID; 1723 statecache.shader_constants_dirty = true; 1724 statecache.cliprect_dirty = true; 1725 statecache.viewport_dirty = true; 1726 statecache.projection_offset = 0; 1727 1728 // !!! FIXME: have a ring of pre-made MTLBuffers we cycle through? How expensive is creation? 1729 if (vertsize > 0) { 1730 /* We can memcpy to a shared buffer from the CPU and read it from the GPU 1731 * without any extra copying. It's a bit slower on macOS to read shared 1732 * data from the GPU than to read managed/private data, but we avoid the 1733 * cost of copying the data and the code's simpler. Apple's best 1734 * practices guide recommends this approach for streamed vertex data. 1735 */ 1736 mtlbufvertex = [data.mtldevice newBufferWithLength:vertsize options:MTLResourceStorageModeShared]; 1737 mtlbufvertex.label = @"SDL vertex data"; 1738 SDL_memcpy([mtlbufvertex contents], vertices, vertsize); 1739 1740 statecache.vertex_buffer = mtlbufvertex; 1741 } 1742 1743 // If there's a command buffer here unexpectedly (app requested one?). Commit it so we can start fresh. 1744 [data.mtlcmdencoder endEncoding]; 1745 [data.mtlcmdbuffer commit]; 1746 data.mtlcmdencoder = nil; 1747 data.mtlcmdbuffer = nil; 1748 1749 while (cmd) { 1750 switch (cmd->command) { 1751 case SDL_RENDERCMD_SETVIEWPORT: 1752 { 1753 SDL_memcpy(&statecache.viewport, &cmd->data.viewport.rect, sizeof(statecache.viewport)); 1754 statecache.projection_offset = cmd->data.viewport.first; 1755 statecache.viewport_dirty = true; 1756 statecache.cliprect_dirty = true; 1757 break; 1758 } 1759 1760 case SDL_RENDERCMD_SETCLIPRECT: 1761 { 1762 SDL_memcpy(&statecache.cliprect, &cmd->data.cliprect.rect, sizeof(statecache.cliprect)); 1763 statecache.cliprect_enabled = cmd->data.cliprect.enabled; 1764 statecache.cliprect_dirty = true; 1765 break; 1766 } 1767 1768 case SDL_RENDERCMD_SETDRAWCOLOR: 1769 { 1770 break; 1771 } 1772 1773 case SDL_RENDERCMD_CLEAR: 1774 { 1775 /* If we're already encoding a command buffer, dump it without committing it. We'd just 1776 clear all its work anyhow, and starting a new encoder will let us use a hardware clear 1777 operation via MTLLoadActionClear. */ 1778 if (data.mtlcmdencoder != nil) { 1779 [data.mtlcmdencoder endEncoding]; 1780 1781 // !!! FIXME: have to commit, or an uncommitted but enqueued buffer will prevent the frame from finishing. 1782 [data.mtlcmdbuffer commit]; 1783 data.mtlcmdencoder = nil; 1784 data.mtlcmdbuffer = nil; 1785 } 1786 1787 // force all this state to be reconfigured on next command buffer. 1788 statecache.pipeline = nil; 1789 statecache.constants_offset = CONSTANTS_OFFSET_INVALID; 1790 statecache.texture = NULL; 1791 statecache.shader_constants_dirty = true; 1792 statecache.cliprect_dirty = true; 1793 statecache.viewport_dirty = true; 1794 1795 { 1796 bool convert_color = SDL_RenderingLinearSpace(renderer); 1797 SDL_FColor color = cmd->data.color.color; 1798 if (convert_color) { 1799 SDL_ConvertToLinear(&color); 1800 } 1801 color.r *= cmd->data.color.color_scale; 1802 color.g *= cmd->data.color.color_scale; 1803 color.b *= cmd->data.color.color_scale; 1804 MTLClearColor mtlcolor = MTLClearColorMake(color.r, color.g, color.b, color.a); 1805 1806 // get new command encoder, set up with an initial clear operation. 1807 // (this might fail, and future draw operations will notice.) 1808 METAL_ActivateRenderCommandEncoder(renderer, MTLLoadActionClear, &mtlcolor, mtlbufvertex); 1809 } 1810 break; 1811 } 1812 1813 case SDL_RENDERCMD_DRAW_LINES: 1814 { 1815 if (SetDrawState(renderer, cmd, SDL_METAL_FRAGMENT_SOLID, NULL, CONSTANTS_OFFSET_HALF_PIXEL_TRANSFORM, mtlbufvertex, &statecache)) { 1816 size_t count = cmd->data.draw.count; 1817 if (count > 2) { 1818 // joined lines cannot be grouped 1819 [data.mtlcmdencoder drawPrimitives:MTLPrimitiveTypeLineStrip vertexStart:0 vertexCount:count]; 1820 } else { 1821 // let's group non joined lines 1822 SDL_RenderCommand *finalcmd = cmd; 1823 SDL_RenderCommand *nextcmd; 1824 float thiscolorscale = cmd->data.draw.color_scale; 1825 SDL_BlendMode thisblend = cmd->data.draw.blend; 1826 1827 for (nextcmd = cmd->next; nextcmd; nextcmd = nextcmd->next) { 1828 const SDL_RenderCommandType nextcmdtype = nextcmd->command; 1829 if (nextcmdtype != SDL_RENDERCMD_DRAW_LINES) { 1830 if (nextcmdtype == SDL_RENDERCMD_SETDRAWCOLOR) { 1831 // The vertex data has the draw color built in, ignore this 1832 continue; 1833 } 1834 break; // can't go any further on this draw call, different render command up next. 1835 } else if (nextcmd->data.draw.count != 2) { 1836 break; // can't go any further on this draw call, those are joined lines 1837 } else if (nextcmd->data.draw.blend != thisblend || 1838 nextcmd->data.draw.color_scale != thiscolorscale) { 1839 break; // can't go any further on this draw call, different blendmode copy up next. 1840 } else { 1841 finalcmd = nextcmd; // we can combine copy operations here. Mark this one as the furthest okay command. 1842 count += nextcmd->data.draw.count; 1843 } 1844 } 1845 1846 [data.mtlcmdencoder drawPrimitives:MTLPrimitiveTypeLine vertexStart:0 vertexCount:count]; 1847 cmd = finalcmd; // skip any copy commands we just combined in here. 1848 } 1849 } 1850 break; 1851 } 1852 1853 case SDL_RENDERCMD_FILL_RECTS: // unused 1854 break; 1855 1856 case SDL_RENDERCMD_COPY: // unused 1857 break; 1858 1859 case SDL_RENDERCMD_COPY_EX: // unused 1860 break; 1861 1862 case SDL_RENDERCMD_DRAW_POINTS: 1863 case SDL_RENDERCMD_GEOMETRY: 1864 { 1865 float thiscolorscale = cmd->data.draw.color_scale; 1866 SDL_Texture *thistexture = cmd->data.draw.texture; 1867 SDL_BlendMode thisblend = cmd->data.draw.blend; 1868 SDL_ScaleMode thisscalemode = cmd->data.draw.texture_scale_mode; 1869 SDL_TextureAddressMode thisaddressmode_u = cmd->data.draw.texture_address_mode_u; 1870 SDL_TextureAddressMode thisaddressmode_v = cmd->data.draw.texture_address_mode_v; 1871 const SDL_RenderCommandType thiscmdtype = cmd->command; 1872 SDL_RenderCommand *finalcmd = cmd; 1873 SDL_RenderCommand *nextcmd; 1874 size_t count = cmd->data.draw.count; 1875 for (nextcmd = cmd->next; nextcmd; nextcmd = nextcmd->next) { 1876 const SDL_RenderCommandType nextcmdtype = nextcmd->command; 1877 if (nextcmdtype != thiscmdtype) { 1878 if (nextcmdtype == SDL_RENDERCMD_SETDRAWCOLOR) { 1879 // The vertex data has the draw color built in, ignore this 1880 continue; 1881 } 1882 break; // can't go any further on this draw call, different render command up next. 1883 } else if (nextcmd->data.draw.texture != thistexture || 1884 nextcmd->data.draw.texture_scale_mode != thisscalemode || 1885 nextcmd->data.draw.texture_address_mode_u != thisaddressmode_u || 1886 nextcmd->data.draw.texture_address_mode_v != thisaddressmode_v || 1887 nextcmd->data.draw.blend != thisblend || 1888 nextcmd->data.draw.color_scale != thiscolorscale) { 1889 break; // can't go any further on this draw call, different texture/blendmode copy up next. 1890 } else { 1891 finalcmd = nextcmd; // we can combine copy operations here. Mark this one as the furthest okay command. 1892 count += nextcmd->data.draw.count; 1893 } 1894 } 1895 1896 if (thiscmdtype == SDL_RENDERCMD_GEOMETRY) { 1897 if (thistexture) { 1898 if (SetCopyState(renderer, cmd, CONSTANTS_OFFSET_IDENTITY, mtlbufvertex, &statecache)) { 1899 [data.mtlcmdencoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:count]; 1900 } 1901 } else { 1902 if (SetDrawState(renderer, cmd, SDL_METAL_FRAGMENT_SOLID, NULL, CONSTANTS_OFFSET_IDENTITY, mtlbufvertex, &statecache)) { 1903 [data.mtlcmdencoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:count]; 1904 } 1905 } 1906 } else { 1907 if (SetDrawState(renderer, cmd, SDL_METAL_FRAGMENT_SOLID, NULL, CONSTANTS_OFFSET_HALF_PIXEL_TRANSFORM, mtlbufvertex, &statecache)) { 1908 [data.mtlcmdencoder drawPrimitives:MTLPrimitiveTypePoint vertexStart:0 vertexCount:count]; 1909 } 1910 } 1911 cmd = finalcmd; // skip any copy commands we just combined in here. 1912 break; 1913 } 1914 1915 case SDL_RENDERCMD_NO_OP: 1916 break; 1917 } 1918 cmd = cmd->next; 1919 } 1920 1921 return true; 1922 } 1923} 1924 1925static SDL_Surface *METAL_RenderReadPixels(SDL_Renderer *renderer, const SDL_Rect *rect) 1926{ 1927 @autoreleasepool { 1928 SDL3METAL_RenderData *data = (__bridge SDL3METAL_RenderData *)renderer->internal; 1929 id<MTLTexture> mtltexture; 1930 MTLRegion mtlregion; 1931 Uint32 format; 1932 SDL_Surface *surface; 1933 1934 if (!METAL_ActivateRenderCommandEncoder(renderer, MTLLoadActionLoad, NULL, nil)) { 1935 SDL_SetError("Failed to activate render command encoder (is your window in the background?"); 1936 return NULL; 1937 } 1938 1939 [data.mtlcmdencoder endEncoding]; 1940 mtltexture = data.mtlpassdesc.colorAttachments[0].texture; 1941 1942#ifdef SDL_PLATFORM_MACOS 1943 /* on macOS with managed-storage textures, we need to tell the driver to 1944 * update the CPU-side copy of the texture data. 1945 * NOTE: Currently all of our textures are managed on macOS. We'll need some 1946 * extra copying for any private textures. */ 1947 if (METAL_GetStorageMode(mtltexture) == MTLStorageModeManaged) { 1948 id<MTLBlitCommandEncoder> blit = [data.mtlcmdbuffer blitCommandEncoder]; 1949 [blit synchronizeResource:mtltexture]; 1950 [blit endEncoding]; 1951 } 1952#endif 1953 1954 /* Commit the current command buffer and wait until it's completed, to make 1955 * sure the GPU has finished rendering to it by the time we read it. */ 1956 [data.mtlcmdbuffer commit]; 1957 [data.mtlcmdbuffer waitUntilCompleted]; 1958 data.mtlcmdencoder = nil; 1959 data.mtlcmdbuffer = nil; 1960 1961 mtlregion = MTLRegionMake2D(rect->x, rect->y, rect->w, rect->h); 1962 1963 switch (mtltexture.pixelFormat) { 1964 case MTLPixelFormatBGRA8Unorm: 1965 case MTLPixelFormatBGRA8Unorm_sRGB: 1966 format = SDL_PIXELFORMAT_ARGB8888; 1967 break; 1968 case MTLPixelFormatRGBA8Unorm: 1969 case MTLPixelFormatRGBA8Unorm_sRGB: 1970 format = SDL_PIXELFORMAT_ABGR8888; 1971 break; 1972 case MTLPixelFormatRGB10A2Unorm: 1973 format = SDL_PIXELFORMAT_ABGR2101010; 1974 break; 1975 case MTLPixelFormatRGBA16Float: 1976 format = SDL_PIXELFORMAT_RGBA64_FLOAT; 1977 break; 1978 case MTLPixelFormatB5G6R5Unorm: 1979 format = SDL_PIXELFORMAT_RGB565; 1980 break; 1981 case MTLPixelFormatA1BGR5Unorm: 1982 format = SDL_PIXELFORMAT_RGBA5551; 1983 break; 1984 case MTLPixelFormatBGR5A1Unorm: 1985 format = SDL_PIXELFORMAT_ARGB1555; 1986 break; 1987 case MTLPixelFormatABGR4Unorm: 1988 format = SDL_PIXELFORMAT_RGBA4444; 1989 break; 1990 default: 1991 SDL_SetError("Unknown framebuffer pixel format"); 1992 return NULL; 1993 } 1994 surface = SDL_CreateSurface(rect->w, rect->h, format); 1995 if (surface) { 1996 [mtltexture getBytes:surface->pixels bytesPerRow:surface->pitch fromRegion:mtlregion mipmapLevel:0]; 1997 } 1998 return surface; 1999 } 2000} 2001 2002static bool METAL_RenderPresent(SDL_Renderer *renderer) 2003{ 2004 @autoreleasepool { 2005 SDL3METAL_RenderData *data = (__bridge SDL3METAL_RenderData *)renderer->internal; 2006 bool ready = true; 2007 2008 // If we don't have a command buffer, we can't present, so activate to get one. 2009 if (data.mtlcmdencoder == nil) { 2010 // We haven't even gotten a backbuffer yet? Load and clear it. Otherwise, load the existing data. 2011 if (data.mtlbackbuffer == nil) { 2012 float alpha = (SDL_GetWindowFlags(renderer->window) & SDL_WINDOW_TRANSPARENT) ? 0.0f : 1.0f; 2013 MTLClearColor color = MTLClearColorMake(0.0f, 0.0f, 0.0f, alpha); 2014 ready = METAL_ActivateRenderCommandEncoder(renderer, MTLLoadActionClear, &color, nil); 2015 } else { 2016 ready = METAL_ActivateRenderCommandEncoder(renderer, MTLLoadActionLoad, NULL, nil); 2017 } 2018 } 2019 2020 [data.mtlcmdencoder endEncoding]; 2021 2022 // If we don't have a drawable to present, don't try to present it. 2023 // But we'll still try to commit the command buffer in case it was already enqueued. 2024 if (ready) { 2025 SDL_assert(data.mtlbackbuffer != nil); 2026 [data.mtlcmdbuffer presentDrawable:data.mtlbackbuffer]; 2027 } 2028 2029 [data.mtlcmdbuffer commit]; 2030 2031 data.mtlcmdencoder = nil; 2032 data.mtlcmdbuffer = nil; 2033 data.mtlbackbuffer = nil; 2034 2035 if (renderer->hidden || !ready) { 2036 return false; 2037 } 2038 return true; 2039 } 2040} 2041 2042static void METAL_DestroyTexture(SDL_Renderer *renderer, SDL_Texture *texture) 2043{ 2044 @autoreleasepool { 2045 CFBridgingRelease(texture->internal); 2046 texture->internal = NULL; 2047 } 2048} 2049 2050static void METAL_DestroyRenderer(SDL_Renderer *renderer) 2051{ 2052 @autoreleasepool { 2053 if (renderer->internal) { 2054 SDL3METAL_RenderData *data = CFBridgingRelease(renderer->internal); 2055 2056 if (data.mtlcmdencoder != nil) { 2057 [data.mtlcmdencoder endEncoding]; 2058 } 2059 2060 DestroyAllPipelines(data.allpipelines, data.pipelinescount); 2061 2062 /* Release the metal view instead of destroying it, 2063 in case we want to use it later (recreating the renderer) 2064 */ 2065 // SDL_Metal_DestroyView(data.mtlview); 2066 CFBridgingRelease(data.mtlview); 2067 } 2068 } 2069} 2070 2071static void *METAL_GetMetalLayer(SDL_Renderer *renderer) 2072{ 2073 @autoreleasepool { 2074 SDL3METAL_RenderData *data = (__bridge SDL3METAL_RenderData *)renderer->internal; 2075 return (__bridge void *)data.mtllayer; 2076 } 2077} 2078 2079static void *METAL_GetMetalCommandEncoder(SDL_Renderer *renderer) 2080{ 2081 @autoreleasepool { 2082 // note that data.mtlcmdencoder can be nil if METAL_ActivateRenderCommandEncoder fails. 2083 // Before SDL 2.0.18, it might have returned a non-nil encoding that might not have been 2084 // usable for presentation. Check your return values! 2085 SDL3METAL_RenderData *data; 2086 METAL_ActivateRenderCommandEncoder(renderer, MTLLoadActionLoad, NULL, nil); 2087 data = (__bridge SDL3METAL_RenderData *)renderer->internal; 2088 return (__bridge void *)data.mtlcmdencoder; 2089 } 2090} 2091 2092static bool METAL_SetVSync(SDL_Renderer *renderer, const int vsync) 2093{ 2094#if defined(SDL_PLATFORM_MACOS) || TARGET_OS_MACCATALYST 2095 SDL3METAL_RenderData *data = (__bridge SDL3METAL_RenderData *)renderer->internal; 2096 switch (vsync) { 2097 case 0: 2098 data.mtllayer.displaySyncEnabled = NO; 2099 break; 2100 case 1: 2101 data.mtllayer.displaySyncEnabled = YES; 2102 break; 2103 default: 2104 return SDL_Unsupported(); 2105 } 2106 return true; 2107#else 2108 switch (vsync) { 2109 case 1: 2110 return true; 2111 default: 2112 return SDL_Unsupported(); 2113 } 2114#endif 2115} 2116 2117static SDL_MetalView GetWindowView(SDL_Window *window) 2118{ 2119#ifdef SDL_VIDEO_DRIVER_COCOA 2120 NSWindow *nswindow = (__bridge NSWindow *)SDL_GetPointerProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_COCOA_WINDOW_POINTER, NULL); 2121 NSInteger tag = (NSInteger)SDL_GetNumberProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_COCOA_METAL_VIEW_TAG_NUMBER, 0); 2122 if (nswindow && tag) { 2123 NSView *view = nswindow.contentView; 2124 if (view.subviews.count > 0) { 2125 view = view.subviews[0]; 2126 if (view.tag == tag) { 2127 return (SDL_MetalView)CFBridgingRetain(view); 2128 } 2129 } 2130 } 2131#endif 2132 2133#ifdef SDL_VIDEO_DRIVER_UIKIT 2134 UIWindow *uiwindow = (__bridge UIWindow *)SDL_GetPointerProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_UIKIT_WINDOW_POINTER, NULL); 2135 NSInteger tag = (NSInteger)SDL_GetNumberProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_UIKIT_METAL_VIEW_TAG_NUMBER, 0); 2136 if (uiwindow && tag) { 2137 UIView *view = uiwindow.rootViewController.view; 2138 if (view.tag == tag) { 2139 return (SDL_MetalView)CFBridgingRetain(view); 2140 } 2141 } 2142#endif 2143 2144 return nil; 2145} 2146 2147static bool METAL_CreateRenderer(SDL_Renderer *renderer, SDL_Window *window, SDL_PropertiesID create_props) 2148{ 2149 @autoreleasepool { 2150 SDL3METAL_RenderData *data = NULL; 2151 id<MTLDevice> mtldevice = nil; 2152 SDL_MetalView view = NULL; 2153 CAMetalLayer *layer = nil; 2154 NSError *err = nil; 2155 dispatch_data_t mtllibdata; 2156 char *constantdata; 2157 int maxtexsize, quadcount = UINT16_MAX / 4; 2158 UInt16 *indexdata; 2159 size_t indicessize = sizeof(UInt16) * quadcount * 6; 2160 id<MTLCommandQueue> mtlcmdqueue; 2161 id<MTLLibrary> mtllibrary; 2162 id<MTLBuffer> mtlbufconstantstaging, mtlbufquadindicesstaging, mtlbufconstants, mtlbufquadindices; 2163 id<MTLCommandBuffer> cmdbuffer; 2164 id<MTLBlitCommandEncoder> blitcmd; 2165 bool scRGB_supported = false; 2166 2167 // Note: matrices are column major. 2168 float identitytransform[16] = { 2169 1.0f, 2170 0.0f, 2171 0.0f, 2172 0.0f, 2173 0.0f, 2174 1.0f, 2175 0.0f, 2176 0.0f, 2177 0.0f, 2178 0.0f, 2179 1.0f, 2180 0.0f, 2181 0.0f, 2182 0.0f, 2183 0.0f, 2184 1.0f, 2185 }; 2186 2187 float halfpixeltransform[16] = { 2188 1.0f, 2189 0.0f, 2190 0.0f, 2191 0.0f, 2192 0.0f, 2193 1.0f, 2194 0.0f, 2195 0.0f, 2196 0.0f, 2197 0.0f, 2198 1.0f, 2199 0.0f, 2200 0.5f, 2201 0.5f, 2202 0.0f, 2203 1.0f, 2204 }; 2205 2206 const size_t YCbCr_shader_matrix_size = 4 * 4 * sizeof(float); 2207 2208 SDL_SetupRendererColorspace(renderer, create_props); 2209 2210#ifndef SDL_PLATFORM_TVOS 2211 if (@available(macos 10.11, iOS 16.0, *)) { 2212 scRGB_supported = true; 2213 } 2214#endif 2215 if (renderer->output_colorspace != SDL_COLORSPACE_SRGB) { 2216 if (renderer->output_colorspace == SDL_COLORSPACE_SRGB_LINEAR && scRGB_supported) { 2217 // This colorspace is supported 2218 } else { 2219 return SDL_SetError("Unsupported output colorspace"); 2220 } 2221 } 2222 2223#ifdef SDL_PLATFORM_MACOS 2224 if (SDL_GetHintBoolean(SDL_HINT_RENDER_METAL_PREFER_LOW_POWER_DEVICE, true)) { 2225 NSArray<id<MTLDevice>> *devices = MTLCopyAllDevices(); 2226 2227 for (id<MTLDevice> device in devices) { 2228 if (device.isLowPower) { 2229 mtldevice = device; 2230 break; 2231 } 2232 } 2233 } 2234#endif 2235 2236 if (mtldevice == nil) { 2237 mtldevice = MTLCreateSystemDefaultDevice(); 2238 } 2239 2240 if (mtldevice == nil) { 2241 return SDL_SetError("Failed to obtain Metal device"); 2242 } 2243 2244 view = GetWindowView(window); 2245 if (view == nil) { 2246 view = SDL_Metal_CreateView(window); 2247 } 2248 2249 if (view == NULL) { 2250 return false; 2251 } 2252 2253 // !!! FIXME: error checking on all of this. 2254 data = [[SDL3METAL_RenderData alloc] init]; 2255 2256 if (data == nil) { 2257 /* Release the metal view instead of destroying it, 2258 in case we want to use it later (recreating the renderer) 2259 */ 2260 // SDL_Metal_DestroyView(view); 2261 CFBridgingRelease(view); 2262 return SDL_SetError("SDL3METAL_RenderData alloc/init failed"); 2263 } 2264 2265 renderer->internal = (void *)CFBridgingRetain(data); 2266 METAL_InvalidateCachedState(renderer); 2267 renderer->window = window; 2268 2269 data.mtlview = view; 2270 2271#ifdef SDL_PLATFORM_MACOS 2272 layer = (CAMetalLayer *)[(__bridge NSView *)view layer]; 2273#else 2274 layer = (CAMetalLayer *)[(__bridge UIView *)view layer]; 2275#endif 2276 2277#ifndef SDL_PLATFORM_TVOS 2278 if (renderer->output_colorspace == SDL_COLORSPACE_SRGB_LINEAR) { 2279 if (@available(macos 10.11, iOS 16.0, *)) { 2280 layer.wantsExtendedDynamicRangeContent = YES; 2281 } else { 2282 SDL_assert(!"Logic error, scRGB is not actually supported"); 2283 } 2284 layer.pixelFormat = MTLPixelFormatRGBA16Float; 2285 2286 const CFStringRef name = kCGColorSpaceExtendedLinearSRGB; 2287 CGColorSpaceRef colorspace = CGColorSpaceCreateWithName(name); 2288 layer.colorspace = colorspace; 2289 CGColorSpaceRelease(colorspace); 2290 } 2291#endif // !SDL_PLATFORM_TVOS 2292 2293 layer.device = mtldevice; 2294 2295 // Necessary for RenderReadPixels. 2296 layer.framebufferOnly = NO; 2297 2298 data.mtldevice = layer.device; 2299 data.mtllayer = layer; 2300 mtlcmdqueue = [data.mtldevice newCommandQueue]; 2301 data.mtlcmdqueue = mtlcmdqueue; 2302 data.mtlcmdqueue.label = @"SDL Metal Renderer"; 2303 data.mtlpassdesc = [MTLRenderPassDescriptor renderPassDescriptor]; 2304 2305 // The compiled .metallib is embedded in a static array in a header file 2306 // but the original shader source code is in SDL_shaders_metal.metal. 2307 mtllibdata = dispatch_data_create(sdl_metallib, sdl_metallib_len, dispatch_get_global_queue(0, 0), ^{ 2308 }); 2309 mtllibrary = [data.mtldevice newLibraryWithData:mtllibdata error:&err]; 2310 data.mtllibrary = mtllibrary; 2311 SDL_assert(err == nil); 2312 data.mtllibrary.label = @"SDL Metal renderer shader library"; 2313 2314 // Do some shader pipeline state loading up-front rather than on demand. 2315 data.pipelinescount = 0; 2316 data.allpipelines = NULL; 2317 ChooseShaderPipelines(data, MTLPixelFormatBGRA8Unorm); 2318 2319 data.mtlsamplers = [[NSMutableDictionary<NSNumber *, id<MTLSamplerState>> alloc] init]; 2320 2321 mtlbufconstantstaging = [data.mtldevice newBufferWithLength:CONSTANTS_LENGTH options:MTLResourceStorageModeShared]; 2322 2323 constantdata = [mtlbufconstantstaging contents]; 2324 SDL_memcpy(constantdata + CONSTANTS_OFFSET_IDENTITY, identitytransform, sizeof(identitytransform)); 2325 SDL_memcpy(constantdata + CONSTANTS_OFFSET_HALF_PIXEL_TRANSFORM, halfpixeltransform, sizeof(halfpixeltransform)); 2326 SDL_memcpy(constantdata + CONSTANTS_OFFSET_DECODE_BT601_LIMITED, SDL_GetYCbCRtoRGBConversionMatrix(SDL_COLORSPACE_BT601_LIMITED, 0, 0, 8), YCbCr_shader_matrix_size); 2327 SDL_memcpy(constantdata + CONSTANTS_OFFSET_DECODE_BT601_FULL, SDL_GetYCbCRtoRGBConversionMatrix(SDL_COLORSPACE_BT601_FULL, 0, 0, 8), YCbCr_shader_matrix_size); 2328 SDL_memcpy(constantdata + CONSTANTS_OFFSET_DECODE_BT709_LIMITED, SDL_GetYCbCRtoRGBConversionMatrix(SDL_COLORSPACE_BT709_LIMITED, 0, 0, 8), YCbCr_shader_matrix_size); 2329 SDL_memcpy(constantdata + CONSTANTS_OFFSET_DECODE_BT709_FULL, SDL_GetYCbCRtoRGBConversionMatrix(SDL_COLORSPACE_BT709_FULL, 0, 0, 8), YCbCr_shader_matrix_size); 2330 SDL_memcpy(constantdata + CONSTANTS_OFFSET_DECODE_BT2020_LIMITED, SDL_GetYCbCRtoRGBConversionMatrix(SDL_COLORSPACE_BT2020_LIMITED, 0, 0, 10), YCbCr_shader_matrix_size); 2331 SDL_memcpy(constantdata + CONSTANTS_OFFSET_DECODE_BT2020_FULL, SDL_GetYCbCRtoRGBConversionMatrix(SDL_COLORSPACE_BT2020_FULL, 0, 0, 10), YCbCr_shader_matrix_size); 2332 2333 mtlbufquadindicesstaging = [data.mtldevice newBufferWithLength:indicessize options:MTLResourceStorageModeShared]; 2334 2335 /* Quads in the following vertex order (matches the FillRects vertices): 2336 * 1---3 2337 * | \ | 2338 * 0---2 2339 */ 2340 indexdata = [mtlbufquadindicesstaging contents]; 2341 for (int i = 0; i < quadcount; i++) { 2342 indexdata[i * 6 + 0] = i * 4 + 0; 2343 indexdata[i * 6 + 1] = i * 4 + 1; 2344 indexdata[i * 6 + 2] = i * 4 + 2; 2345 2346 indexdata[i * 6 + 3] = i * 4 + 2; 2347 indexdata[i * 6 + 4] = i * 4 + 1; 2348 indexdata[i * 6 + 5] = i * 4 + 3; 2349 } 2350 2351 mtlbufconstants = [data.mtldevice newBufferWithLength:CONSTANTS_LENGTH options:MTLResourceStorageModePrivate]; 2352 data.mtlbufconstants = mtlbufconstants; 2353 data.mtlbufconstants.label = @"SDL constant data"; 2354 2355 mtlbufquadindices = [data.mtldevice newBufferWithLength:indicessize options:MTLResourceStorageModePrivate]; 2356 data.mtlbufquadindices = mtlbufquadindices; 2357 data.mtlbufquadindices.label = @"SDL quad index buffer"; 2358 2359 cmdbuffer = [data.mtlcmdqueue commandBuffer]; 2360 blitcmd = [cmdbuffer blitCommandEncoder]; 2361 2362 [blitcmd copyFromBuffer:mtlbufconstantstaging sourceOffset:0 toBuffer:mtlbufconstants destinationOffset:0 size:CONSTANTS_LENGTH]; 2363 [blitcmd copyFromBuffer:mtlbufquadindicesstaging sourceOffset:0 toBuffer:mtlbufquadindices destinationOffset:0 size:indicessize]; 2364 2365 [blitcmd endEncoding]; 2366 [cmdbuffer commit]; 2367 2368 // !!! FIXME: force more clears here so all the drawables are sane to start, and our static buffers are definitely flushed. 2369 2370 renderer->WindowEvent = METAL_WindowEvent; 2371 renderer->GetOutputSize = METAL_GetOutputSize; 2372 renderer->SupportsBlendMode = METAL_SupportsBlendMode; 2373 renderer->CreatePalette = METAL_CreatePalette; 2374 renderer->UpdatePalette = METAL_UpdatePalette; 2375 renderer->DestroyPalette = METAL_DestroyPalette; 2376 renderer->CreateTexture = METAL_CreateTexture; 2377 renderer->UpdateTexture = METAL_UpdateTexture; 2378#ifdef SDL_HAVE_YUV 2379 renderer->UpdateTextureYUV = METAL_UpdateTextureYUV; 2380 renderer->UpdateTextureNV = METAL_UpdateTextureNV; 2381#endif 2382 renderer->LockTexture = METAL_LockTexture; 2383 renderer->UnlockTexture = METAL_UnlockTexture; 2384 renderer->SetRenderTarget = METAL_SetRenderTarget; 2385 renderer->QueueSetViewport = METAL_QueueSetViewport; 2386 renderer->QueueSetDrawColor = METAL_QueueNoOp; 2387 renderer->QueueDrawPoints = METAL_QueueDrawPoints; 2388 renderer->QueueDrawLines = METAL_QueueDrawLines; 2389 renderer->QueueGeometry = METAL_QueueGeometry; 2390 renderer->InvalidateCachedState = METAL_InvalidateCachedState; 2391 renderer->RunCommandQueue = METAL_RunCommandQueue; 2392 renderer->RenderReadPixels = METAL_RenderReadPixels; 2393 renderer->RenderPresent = METAL_RenderPresent; 2394 renderer->DestroyTexture = METAL_DestroyTexture; 2395 renderer->DestroyRenderer = METAL_DestroyRenderer; 2396 renderer->SetVSync = METAL_SetVSync; 2397 renderer->GetMetalLayer = METAL_GetMetalLayer; 2398 renderer->GetMetalCommandEncoder = METAL_GetMetalCommandEncoder; 2399 2400 renderer->name = METAL_RenderDriver.name; 2401 SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_ARGB8888); 2402 SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_ABGR8888); 2403 SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_ABGR2101010); 2404 SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_RGBA64_FLOAT); 2405 SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_RGBA128_FLOAT); 2406 if (@available(macOS 11.0, iOS 13.0, tvOS 13.0, *)) { 2407 if ([mtldevice supportsFamily:MTLGPUFamilyApple1]) { 2408 SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_RGB565); 2409 SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_RGBA5551); 2410 SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_ARGB1555); 2411 SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_RGBA4444); 2412 } 2413 } 2414 SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_INDEX8); 2415 SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_YV12); 2416 SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_IYUV); 2417 SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_NV12); 2418 SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_NV21); 2419 SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_P010); 2420 2421#if defined(SDL_PLATFORM_MACOS) || TARGET_OS_MACCATALYST 2422 data.mtllayer.displaySyncEnabled = NO; 2423#endif 2424 2425 // https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf 2426 maxtexsize = 4096; 2427#if defined(SDL_PLATFORM_MACOS) || TARGET_OS_MACCATALYST 2428 maxtexsize = 16384; 2429#elif defined(SDL_PLATFORM_TVOS) 2430 maxtexsize = 8192; 2431 if ([mtldevice supportsFeatureSet:MTLFeatureSet_tvOS_GPUFamily2_v1]) { 2432 maxtexsize = 16384; 2433 } 2434#else 2435 if ([mtldevice supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily4_v1]) { 2436 maxtexsize = 16384; 2437 } else if ([mtldevice supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily3_v1]) { 2438 maxtexsize = 16384; 2439 } else if ([mtldevice supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily2_v2] || [mtldevice supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily1_v2]) { 2440 maxtexsize = 8192; 2441 } else { 2442 maxtexsize = 4096; 2443 } 2444#endif 2445 2446 SDL_SetNumberProperty(SDL_GetRendererProperties(renderer), SDL_PROP_RENDERER_MAX_TEXTURE_SIZE_NUMBER, maxtexsize); 2447 2448 return true; 2449 } 2450} 2451 2452SDL_RenderDriver METAL_RenderDriver = { 2453 METAL_CreateRenderer, "metal" 2454}; 2455 2456#endif // SDL_VIDEO_RENDER_METAL 2457[FILE END](C) 2025 0x4248 (C) 2025 4248 Media and 4248 Systems, All part of 0x4248 See LICENCE files for more information. Not all files are by 0x4248 always check Licencing.