Atlas - SDL_render_metal.m
Home / ext / SDL / src / render / metal Lines: 1 | Size: 94847 bytes [Download] [Show on GitHub] [Search similar files] [Raw] [Raw (proxy)][FILE BEGIN]1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2025 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; 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_INDEX8: 719 case SDL_PIXELFORMAT_IYUV: 720 case SDL_PIXELFORMAT_YV12: 721 case SDL_PIXELFORMAT_NV12: 722 case SDL_PIXELFORMAT_NV21: 723 pixfmt = MTLPixelFormatR8Unorm; 724 break; 725 case SDL_PIXELFORMAT_P010: 726 pixfmt = MTLPixelFormatR16Unorm; 727 break; 728 case SDL_PIXELFORMAT_RGBA64_FLOAT: 729 pixfmt = MTLPixelFormatRGBA16Float; 730 break; 731 case SDL_PIXELFORMAT_RGBA128_FLOAT: 732 pixfmt = MTLPixelFormatRGBA32Float; 733 break; 734 default: 735 return SDL_SetError("Texture format %s not supported by Metal", SDL_GetPixelFormatName(texture->format)); 736 } 737 738 mtltexdesc = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:pixfmt 739 width:(NSUInteger)texture->w 740 height:(NSUInteger)texture->h 741 mipmapped:NO]; 742 743 if (texture->access == SDL_TEXTUREACCESS_TARGET) { 744 mtltexdesc.usage = MTLTextureUsageShaderRead | MTLTextureUsageRenderTarget; 745 } else { 746 mtltexdesc.usage = MTLTextureUsageShaderRead; 747 } 748 749 if (surface) { 750 mtltexture = [data.mtldevice newTextureWithDescriptor:mtltexdesc iosurface:surface plane:0]; 751 } else { 752 mtltexture = [data.mtldevice newTextureWithDescriptor:mtltexdesc]; 753 } 754 if (mtltexture == nil) { 755 return SDL_SetError("Texture allocation failed"); 756 } 757 758 mtltextureUv = nil; 759#ifdef SDL_HAVE_YUV 760 BOOL yuv = (texture->format == SDL_PIXELFORMAT_IYUV || texture->format == SDL_PIXELFORMAT_YV12); 761 BOOL nv12 = (texture->format == SDL_PIXELFORMAT_NV12 || texture->format == SDL_PIXELFORMAT_NV21 || texture->format == SDL_PIXELFORMAT_P010); 762 763 if (yuv) { 764 mtltexdesc.pixelFormat = MTLPixelFormatR8Unorm; 765 mtltexdesc.width = (texture->w + 1) / 2; 766 mtltexdesc.height = (texture->h + 1) / 2; 767 mtltexdesc.textureType = MTLTextureType2DArray; 768 mtltexdesc.arrayLength = 2; 769 } else if (texture->format == SDL_PIXELFORMAT_P010) { 770 mtltexdesc.pixelFormat = MTLPixelFormatRG16Unorm; 771 mtltexdesc.width = (texture->w + 1) / 2; 772 mtltexdesc.height = (texture->h + 1) / 2; 773 } else if (nv12) { 774 mtltexdesc.pixelFormat = MTLPixelFormatRG8Unorm; 775 mtltexdesc.width = (texture->w + 1) / 2; 776 mtltexdesc.height = (texture->h + 1) / 2; 777 } 778 779 if (yuv || nv12) { 780 if (surface) { 781 mtltextureUv = [data.mtldevice newTextureWithDescriptor:mtltexdesc iosurface:surface plane:1]; 782 } else { 783 mtltextureUv = [data.mtldevice newTextureWithDescriptor:mtltexdesc]; 784 } 785 if (mtltextureUv == nil) { 786 return SDL_SetError("Texture allocation failed"); 787 } 788 } 789#endif // SDL_HAVE_YUV 790 texturedata = [[SDL3METAL_TextureData alloc] init]; 791 if (texture->format == SDL_PIXELFORMAT_INDEX8) { 792 texturedata.fragmentFunction = SDL_METAL_FRAGMENT_PALETTE; 793#ifdef SDL_HAVE_YUV 794 } else if (yuv) { 795 texturedata.fragmentFunction = SDL_METAL_FRAGMENT_YUV; 796 } else if (nv12) { 797 texturedata.fragmentFunction = SDL_METAL_FRAGMENT_NV12; 798#endif 799 } else { 800 texturedata.fragmentFunction = SDL_METAL_FRAGMENT_COPY; 801 } 802 texturedata.mtltexture = mtltexture; 803 texturedata.mtltextureUv = mtltextureUv; 804#ifdef SDL_HAVE_YUV 805 texturedata.yuv = yuv; 806 texturedata.nv12 = nv12; 807 if (yuv || nv12) { 808 size_t offset = GetYCbCRtoRGBConversionMatrix(texture->colorspace, texture->w, texture->h, 8); 809 if (offset == 0) { 810 return SDL_SetError("Unsupported YUV colorspace"); 811 } 812 texturedata.conversionBufferOffset = offset; 813 } 814#endif 815 texture->internal = (void *)CFBridgingRetain(texturedata); 816 817 return true; 818 } 819} 820 821static void METAL_UploadTextureData(id<MTLTexture> texture, SDL_Rect rect, int slice, 822 const void *pixels, int pitch) 823{ 824 [texture replaceRegion:MTLRegionMake2D(rect.x, rect.y, rect.w, rect.h) 825 mipmapLevel:0 826 slice:slice 827 withBytes:pixels 828 bytesPerRow:pitch 829 bytesPerImage:0]; 830} 831 832static MTLStorageMode METAL_GetStorageMode(id<MTLResource> resource) 833{ 834 return resource.storageMode; 835} 836 837static bool METAL_UpdateTextureInternal(SDL_Renderer *renderer, BOOL hasdata, 838 id<MTLTexture> texture, SDL_Rect rect, int slice, 839 const void *pixels, int pitch) 840{ 841 SDL3METAL_RenderData *data = (__bridge SDL3METAL_RenderData *)renderer->internal; 842 SDL_Rect stagingrect = { 0, 0, rect.w, rect.h }; 843 MTLTextureDescriptor *desc; 844 id<MTLTexture> stagingtex; 845 id<MTLBlitCommandEncoder> blitcmd; 846 847 /* If the texture is managed or shared and this is the first upload, we can 848 * use replaceRegion to upload to it directly. Otherwise we upload the data 849 * to a staging texture and copy that over. */ 850 if (!hasdata && METAL_GetStorageMode(texture) != MTLStorageModePrivate) { 851 METAL_UploadTextureData(texture, rect, slice, pixels, pitch); 852 return true; 853 } 854 855 desc = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:texture.pixelFormat 856 width:rect.w 857 height:rect.h 858 mipmapped:NO]; 859 860 if (desc == nil) { 861 return SDL_OutOfMemory(); 862 } 863 864 /* TODO: We could have a pool of textures or a MTLHeap we allocate from, 865 * and release a staging texture back to the pool in the command buffer's 866 * completion handler. */ 867 stagingtex = [data.mtldevice newTextureWithDescriptor:desc]; 868 if (stagingtex == nil) { 869 return SDL_OutOfMemory(); 870 } 871 872 METAL_UploadTextureData(stagingtex, stagingrect, 0, pixels, pitch); 873 874 if (data.mtlcmdencoder != nil) { 875 [data.mtlcmdencoder endEncoding]; 876 data.mtlcmdencoder = nil; 877 } 878 879 if (data.mtlcmdbuffer == nil) { 880 data.mtlcmdbuffer = [data.mtlcmdqueue commandBuffer]; 881 } 882 883 blitcmd = [data.mtlcmdbuffer blitCommandEncoder]; 884 885 [blitcmd copyFromTexture:stagingtex 886 sourceSlice:0 887 sourceLevel:0 888 sourceOrigin:MTLOriginMake(0, 0, 0) 889 sourceSize:MTLSizeMake(rect.w, rect.h, 1) 890 toTexture:texture 891 destinationSlice:slice 892 destinationLevel:0 893 destinationOrigin:MTLOriginMake(rect.x, rect.y, 0)]; 894 895 [blitcmd endEncoding]; 896 897 /* TODO: This isn't very efficient for the YUV formats, which call 898 * UpdateTextureInternal multiple times in a row. */ 899 [data.mtlcmdbuffer commit]; 900 data.mtlcmdbuffer = nil; 901 902 return true; 903} 904 905static bool METAL_UpdateTexture(SDL_Renderer *renderer, SDL_Texture *texture, 906 const SDL_Rect *rect, const void *pixels, int pitch) 907{ 908 @autoreleasepool { 909 SDL3METAL_TextureData *texturedata = (__bridge SDL3METAL_TextureData *)texture->internal; 910 911 if (!METAL_UpdateTextureInternal(renderer, texturedata.hasdata, texturedata.mtltexture, *rect, 0, pixels, pitch)) { 912 return false; 913 } 914#ifdef SDL_HAVE_YUV 915 if (texturedata.yuv) { 916 int Uslice = texture->format == SDL_PIXELFORMAT_YV12 ? 1 : 0; 917 int Vslice = texture->format == SDL_PIXELFORMAT_YV12 ? 0 : 1; 918 int UVpitch = (pitch + 1) / 2; 919 SDL_Rect UVrect = { rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2 }; 920 921 // Skip to the correct offset into the next texture 922 pixels = (const void *)((const Uint8 *)pixels + rect->h * pitch); 923 if (!METAL_UpdateTextureInternal(renderer, texturedata.hasdata, texturedata.mtltextureUv, UVrect, Uslice, pixels, UVpitch)) { 924 return false; 925 } 926 927 // Skip to the correct offset into the next texture 928 pixels = (const void *)((const Uint8 *)pixels + UVrect.h * UVpitch); 929 if (!METAL_UpdateTextureInternal(renderer, texturedata.hasdata, texturedata.mtltextureUv, UVrect, Vslice, pixels, UVpitch)) { 930 return false; 931 } 932 } 933 934 if (texturedata.nv12) { 935 SDL_Rect UVrect = { rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2 }; 936 int UVpitch = 2 * ((pitch + 1) / 2); 937 938 // Skip to the correct offset into the next texture 939 pixels = (const void *)((const Uint8 *)pixels + rect->h * pitch); 940 if (!METAL_UpdateTextureInternal(renderer, texturedata.hasdata, texturedata.mtltextureUv, UVrect, 0, pixels, UVpitch)) { 941 return false; 942 } 943 } 944#endif 945 texturedata.hasdata = YES; 946 947 return true; 948 } 949} 950 951#ifdef SDL_HAVE_YUV 952static bool METAL_UpdateTextureYUV(SDL_Renderer *renderer, SDL_Texture *texture, 953 const SDL_Rect *rect, 954 const Uint8 *Yplane, int Ypitch, 955 const Uint8 *Uplane, int Upitch, 956 const Uint8 *Vplane, int Vpitch) 957{ 958 @autoreleasepool { 959 SDL3METAL_TextureData *texturedata = (__bridge SDL3METAL_TextureData *)texture->internal; 960 const int Uslice = 0; 961 const int Vslice = 1; 962 SDL_Rect UVrect = { rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2 }; 963 964 // Bail out if we're supposed to update an empty rectangle 965 if (rect->w <= 0 || rect->h <= 0) { 966 return true; 967 } 968 969 if (!METAL_UpdateTextureInternal(renderer, texturedata.hasdata, texturedata.mtltexture, *rect, 0, Yplane, Ypitch)) { 970 return false; 971 } 972 if (!METAL_UpdateTextureInternal(renderer, texturedata.hasdata, texturedata.mtltextureUv, UVrect, Uslice, Uplane, Upitch)) { 973 return false; 974 } 975 if (!METAL_UpdateTextureInternal(renderer, texturedata.hasdata, texturedata.mtltextureUv, UVrect, Vslice, Vplane, Vpitch)) { 976 return false; 977 } 978 979 texturedata.hasdata = YES; 980 981 return true; 982 } 983} 984 985static bool METAL_UpdateTextureNV(SDL_Renderer *renderer, SDL_Texture *texture, 986 const SDL_Rect *rect, 987 const Uint8 *Yplane, int Ypitch, 988 const Uint8 *UVplane, int UVpitch) 989{ 990 @autoreleasepool { 991 SDL3METAL_TextureData *texturedata = (__bridge SDL3METAL_TextureData *)texture->internal; 992 SDL_Rect UVrect = { rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2 }; 993 994 // Bail out if we're supposed to update an empty rectangle 995 if (rect->w <= 0 || rect->h <= 0) { 996 return true; 997 } 998 999 if (!METAL_UpdateTextureInternal(renderer, texturedata.hasdata, texturedata.mtltexture, *rect, 0, Yplane, Ypitch)) { 1000 return false; 1001 } 1002 1003 if (!METAL_UpdateTextureInternal(renderer, texturedata.hasdata, texturedata.mtltextureUv, UVrect, 0, UVplane, UVpitch)) { 1004 return false; 1005 } 1006 1007 texturedata.hasdata = YES; 1008 1009 return true; 1010 } 1011} 1012#endif 1013 1014static bool METAL_LockTexture(SDL_Renderer *renderer, SDL_Texture *texture, 1015 const SDL_Rect *rect, void **pixels, int *pitch) 1016{ 1017 @autoreleasepool { 1018 SDL3METAL_RenderData *data = (__bridge SDL3METAL_RenderData *)renderer->internal; 1019 SDL3METAL_TextureData *texturedata = (__bridge SDL3METAL_TextureData *)texture->internal; 1020 int buffersize = 0; 1021 id<MTLBuffer> lockedbuffer = nil; 1022 1023 if (rect->w <= 0 || rect->h <= 0) { 1024 return SDL_SetError("Invalid rectangle dimensions for LockTexture."); 1025 } 1026 1027 *pitch = SDL_BYTESPERPIXEL(texture->format) * rect->w; 1028#ifdef SDL_HAVE_YUV 1029 if (texturedata.yuv || texturedata.nv12) { 1030 buffersize = ((*pitch) * rect->h) + (2 * (*pitch + 1) / 2) * ((rect->h + 1) / 2); 1031 } else 1032#endif 1033 { 1034 buffersize = (*pitch) * rect->h; 1035 } 1036 1037 lockedbuffer = [data.mtldevice newBufferWithLength:buffersize options:MTLResourceStorageModeShared]; 1038 if (lockedbuffer == nil) { 1039 return SDL_OutOfMemory(); 1040 } 1041 1042 texturedata.lockedrect = *rect; 1043 texturedata.lockedbuffer = lockedbuffer; 1044 *pixels = [lockedbuffer contents]; 1045 1046 return true; 1047 } 1048} 1049 1050static void METAL_UnlockTexture(SDL_Renderer *renderer, SDL_Texture *texture) 1051{ 1052 @autoreleasepool { 1053 SDL3METAL_RenderData *data = (__bridge SDL3METAL_RenderData *)renderer->internal; 1054 SDL3METAL_TextureData *texturedata = (__bridge SDL3METAL_TextureData *)texture->internal; 1055 id<MTLBlitCommandEncoder> blitcmd; 1056 SDL_Rect rect = texturedata.lockedrect; 1057 int pitch = SDL_BYTESPERPIXEL(texture->format) * rect.w; 1058#ifdef SDL_HAVE_YUV 1059 SDL_Rect UVrect = { rect.x / 2, rect.y / 2, (rect.w + 1) / 2, (rect.h + 1) / 2 }; 1060#endif 1061 1062 if (texturedata.lockedbuffer == nil) { 1063 return; 1064 } 1065 1066 if (data.mtlcmdencoder != nil) { 1067 [data.mtlcmdencoder endEncoding]; 1068 data.mtlcmdencoder = nil; 1069 } 1070 1071 if (data.mtlcmdbuffer == nil) { 1072 data.mtlcmdbuffer = [data.mtlcmdqueue commandBuffer]; 1073 } 1074 1075 blitcmd = [data.mtlcmdbuffer blitCommandEncoder]; 1076 1077 [blitcmd copyFromBuffer:texturedata.lockedbuffer 1078 sourceOffset:0 1079 sourceBytesPerRow:pitch 1080 sourceBytesPerImage:0 1081 sourceSize:MTLSizeMake(rect.w, rect.h, 1) 1082 toTexture:texturedata.mtltexture 1083 destinationSlice:0 1084 destinationLevel:0 1085 destinationOrigin:MTLOriginMake(rect.x, rect.y, 0)]; 1086#ifdef SDL_HAVE_YUV 1087 if (texturedata.yuv) { 1088 int Uslice = texture->format == SDL_PIXELFORMAT_YV12 ? 1 : 0; 1089 int Vslice = texture->format == SDL_PIXELFORMAT_YV12 ? 0 : 1; 1090 int UVpitch = (pitch + 1) / 2; 1091 1092 [blitcmd copyFromBuffer:texturedata.lockedbuffer 1093 sourceOffset:rect.h * pitch 1094 sourceBytesPerRow:UVpitch 1095 sourceBytesPerImage:UVpitch * UVrect.h 1096 sourceSize:MTLSizeMake(UVrect.w, UVrect.h, 1) 1097 toTexture:texturedata.mtltextureUv 1098 destinationSlice:Uslice 1099 destinationLevel:0 1100 destinationOrigin:MTLOriginMake(UVrect.x, UVrect.y, 0)]; 1101 1102 [blitcmd copyFromBuffer:texturedata.lockedbuffer 1103 sourceOffset:(rect.h * pitch) + UVrect.h * UVpitch 1104 sourceBytesPerRow:UVpitch 1105 sourceBytesPerImage:UVpitch * UVrect.h 1106 sourceSize:MTLSizeMake(UVrect.w, UVrect.h, 1) 1107 toTexture:texturedata.mtltextureUv 1108 destinationSlice:Vslice 1109 destinationLevel:0 1110 destinationOrigin:MTLOriginMake(UVrect.x, UVrect.y, 0)]; 1111 } 1112 1113 if (texturedata.nv12) { 1114 int UVpitch = 2 * ((pitch + 1) / 2); 1115 1116 [blitcmd copyFromBuffer:texturedata.lockedbuffer 1117 sourceOffset:rect.h * pitch 1118 sourceBytesPerRow:UVpitch 1119 sourceBytesPerImage:0 1120 sourceSize:MTLSizeMake(UVrect.w, UVrect.h, 1) 1121 toTexture:texturedata.mtltextureUv 1122 destinationSlice:0 1123 destinationLevel:0 1124 destinationOrigin:MTLOriginMake(UVrect.x, UVrect.y, 0)]; 1125 } 1126#endif 1127 [blitcmd endEncoding]; 1128 1129 [data.mtlcmdbuffer commit]; 1130 data.mtlcmdbuffer = nil; 1131 1132 texturedata.lockedbuffer = nil; // Retained property, so it calls release. 1133 texturedata.hasdata = YES; 1134 } 1135} 1136 1137static bool METAL_SetRenderTarget(SDL_Renderer *renderer, SDL_Texture *texture) 1138{ 1139 @autoreleasepool { 1140 SDL3METAL_RenderData *data = (__bridge SDL3METAL_RenderData *)renderer->internal; 1141 1142 if (data.mtlcmdencoder) { 1143 /* End encoding for the previous render target so we can set up a new 1144 * render pass for this one. */ 1145 [data.mtlcmdencoder endEncoding]; 1146 [data.mtlcmdbuffer commit]; 1147 1148 data.mtlcmdencoder = nil; 1149 data.mtlcmdbuffer = nil; 1150 } 1151 1152 /* We don't begin a new render pass right away - we delay it until an actual 1153 * draw or clear happens. That way we can use hardware clears when possible, 1154 * which are only available when beginning a new render pass. */ 1155 return true; 1156 } 1157} 1158 1159static bool METAL_QueueSetViewport(SDL_Renderer *renderer, SDL_RenderCommand *cmd) 1160{ 1161 float projection[4][4]; // Prepare an orthographic projection 1162 const int w = cmd->data.viewport.rect.w; 1163 const int h = cmd->data.viewport.rect.h; 1164 const size_t matrixlen = sizeof(projection); 1165 float *matrix = (float *)SDL_AllocateRenderVertices(renderer, matrixlen, CONSTANT_ALIGN(16), &cmd->data.viewport.first); 1166 if (!matrix) { 1167 return false; 1168 } 1169 1170 SDL_memset(projection, '\0', matrixlen); 1171 if (w && h) { 1172 projection[0][0] = 2.0f / w; 1173 projection[1][1] = -2.0f / h; 1174 projection[3][0] = -1.0f; 1175 projection[3][1] = 1.0f; 1176 projection[3][3] = 1.0f; 1177 } 1178 SDL_memcpy(matrix, projection, matrixlen); 1179 1180 return true; 1181} 1182 1183static bool METAL_QueueNoOp(SDL_Renderer *renderer, SDL_RenderCommand *cmd) 1184{ 1185 return true; // nothing to do in this backend. 1186} 1187 1188static bool METAL_QueueDrawPoints(SDL_Renderer *renderer, SDL_RenderCommand *cmd, const SDL_FPoint *points, int count) 1189{ 1190 SDL_FColor color = cmd->data.draw.color; 1191 bool convert_color = SDL_RenderingLinearSpace(renderer); 1192 1193 const size_t vertlen = (2 * sizeof(float) + 4 * sizeof(float)) * count; 1194 float *verts = (float *)SDL_AllocateRenderVertices(renderer, vertlen, DEVICE_ALIGN(8), &cmd->data.draw.first); 1195 if (!verts) { 1196 return false; 1197 } 1198 cmd->data.draw.count = count; 1199 1200 if (convert_color) { 1201 SDL_ConvertToLinear(&color); 1202 } 1203 1204 for (int i = 0; i < count; i++, points++) { 1205 *(verts++) = points->x; 1206 *(verts++) = points->y; 1207 *(verts++) = color.r; 1208 *(verts++) = color.g; 1209 *(verts++) = color.b; 1210 *(verts++) = color.a; 1211 } 1212 return true; 1213} 1214 1215static bool METAL_QueueDrawLines(SDL_Renderer *renderer, SDL_RenderCommand *cmd, const SDL_FPoint *points, int count) 1216{ 1217 SDL_FColor color = cmd->data.draw.color; 1218 bool convert_color = SDL_RenderingLinearSpace(renderer); 1219 size_t vertlen; 1220 float *verts; 1221 1222 SDL_assert(count >= 2); // should have been checked at the higher level. 1223 1224 vertlen = (2 * sizeof(float) + 4 * sizeof(float)) * count; 1225 verts = (float *)SDL_AllocateRenderVertices(renderer, vertlen, DEVICE_ALIGN(8), &cmd->data.draw.first); 1226 if (!verts) { 1227 return false; 1228 } 1229 cmd->data.draw.count = count; 1230 1231 if (convert_color) { 1232 SDL_ConvertToLinear(&color); 1233 } 1234 1235 for (int i = 0; i < count; i++, points++) { 1236 *(verts++) = points->x; 1237 *(verts++) = points->y; 1238 *(verts++) = color.r; 1239 *(verts++) = color.g; 1240 *(verts++) = color.b; 1241 *(verts++) = color.a; 1242 } 1243 1244 /* If the line segment is completely horizontal or vertical, 1245 make it one pixel longer, to satisfy the diamond-exit rule. 1246 We should probably do this for diagonal lines too, but we'd have to 1247 do some trigonometry to figure out the correct pixel and generally 1248 when we have problems with pixel perfection, it's for straight lines 1249 that are missing a pixel that frames something and not arbitrary 1250 angles. Maybe !!! FIXME for later, though. */ 1251 1252 points -= 2; // update the last line. 1253 verts -= 6; 1254 1255 { 1256 const float xstart = points[0].x; 1257 const float ystart = points[0].y; 1258 const float xend = points[1].x; 1259 const float yend = points[1].y; 1260 1261 if (ystart == yend) { // horizontal line 1262 verts[0] += (xend > xstart) ? 1.0f : -1.0f; 1263 } else if (xstart == xend) { // vertical line 1264 verts[1] += (yend > ystart) ? 1.0f : -1.0f; 1265 } 1266 } 1267 1268 return true; 1269} 1270 1271static bool METAL_QueueGeometry(SDL_Renderer *renderer, SDL_RenderCommand *cmd, SDL_Texture *texture, 1272 const float *xy, int xy_stride, const SDL_FColor *color, int color_stride, const float *uv, int uv_stride, 1273 int num_vertices, const void *indices, int num_indices, int size_indices, 1274 float scale_x, float scale_y) 1275{ 1276 bool convert_color = SDL_RenderingLinearSpace(renderer); 1277 int count = indices ? num_indices : num_vertices; 1278 const size_t vertlen = (2 * sizeof(float) + 4 * sizeof(float) + (texture ? 2 : 0) * sizeof(float)) * count; 1279 float *verts = (float *)SDL_AllocateRenderVertices(renderer, vertlen, DEVICE_ALIGN(8), &cmd->data.draw.first); 1280 if (!verts) { 1281 return false; 1282 } 1283 1284 cmd->data.draw.count = count; 1285 size_indices = indices ? size_indices : 0; 1286 1287 for (int i = 0; i < count; i++) { 1288 int j; 1289 float *xy_; 1290 SDL_FColor col_; 1291 if (size_indices == 4) { 1292 j = ((const Uint32 *)indices)[i]; 1293 } else if (size_indices == 2) { 1294 j = ((const Uint16 *)indices)[i]; 1295 } else if (size_indices == 1) { 1296 j = ((const Uint8 *)indices)[i]; 1297 } else { 1298 j = i; 1299 } 1300 1301 xy_ = (float *)((char *)xy + j * xy_stride); 1302 1303 *(verts++) = xy_[0] * scale_x; 1304 *(verts++) = xy_[1] * scale_y; 1305 1306 col_ = *(SDL_FColor *)((char *)color + j * color_stride); 1307 1308 if (convert_color) { 1309 SDL_ConvertToLinear(&col_); 1310 } 1311 1312 *(verts++) = col_.r; 1313 *(verts++) = col_.g; 1314 *(verts++) = col_.b; 1315 *(verts++) = col_.a; 1316 1317 if (texture) { 1318 float *uv_ = (float *)((char *)uv + j * uv_stride); 1319 *(verts++) = uv_[0]; 1320 *(verts++) = uv_[1]; 1321 } 1322 } 1323 1324 return true; 1325} 1326 1327// These should mirror the definitions in SDL_shaders_metal.metal 1328//static const float TONEMAP_NONE = 0; 1329//static const float TONEMAP_LINEAR = 1; 1330static const float TONEMAP_CHROME = 2; 1331 1332//static const float TEXTURETYPE_NONE = 0; 1333static const float TEXTURETYPE_RGB = 1; 1334static const float TEXTURETYPE_RGB_PIXELART = 2; 1335static const float TEXTURETYPE_PALETTE_NEAREST = 3; 1336static const float TEXTURETYPE_PALETTE_LINEAR = 4; 1337static const float TEXTURETYPE_PALETTE_PIXELART = 5; 1338static const float TEXTURETYPE_NV12 = 6; 1339static const float TEXTURETYPE_NV21 = 7; 1340static const float TEXTURETYPE_YUV = 8; 1341 1342//static const float INPUTTYPE_UNSPECIFIED = 0; 1343static const float INPUTTYPE_SRGB = 1; 1344static const float INPUTTYPE_SCRGB = 2; 1345static const float INPUTTYPE_HDR10 = 3; 1346 1347typedef struct 1348{ 1349 float scRGB_output; 1350 float texture_type; 1351 float input_type; 1352 float color_scale; 1353 1354 float texel_width; 1355 float texel_height; 1356 float texture_width; 1357 float texture_height; 1358 1359 float tonemap_method; 1360 float tonemap_factor1; 1361 float tonemap_factor2; 1362 float sdr_white_point; 1363} PixelShaderConstants; 1364 1365typedef struct 1366{ 1367 __unsafe_unretained id<MTLRenderPipelineState> pipeline; 1368 __unsafe_unretained id<MTLBuffer> vertex_buffer; 1369 size_t constants_offset; 1370 SDL_Texture *texture; 1371 bool texture_palette; 1372 SDL_ScaleMode texture_scale_mode; 1373 SDL_TextureAddressMode texture_address_mode_u; 1374 SDL_TextureAddressMode texture_address_mode_v; 1375 bool cliprect_dirty; 1376 bool cliprect_enabled; 1377 SDL_Rect cliprect; 1378 bool viewport_dirty; 1379 SDL_Rect viewport; 1380 size_t projection_offset; 1381 bool shader_constants_dirty; 1382 PixelShaderConstants shader_constants; 1383} METAL_DrawStateCache; 1384 1385static void SetupShaderConstants(SDL_Renderer *renderer, const SDL_RenderCommand *cmd, const SDL_Texture *texture, PixelShaderConstants *constants) 1386{ 1387 float output_headroom; 1388 1389 SDL_zerop(constants); 1390 1391 constants->scRGB_output = (float)SDL_RenderingLinearSpace(renderer); 1392 constants->color_scale = cmd->data.draw.color_scale; 1393 1394 if (texture) { 1395 switch (texture->format) { 1396 case SDL_PIXELFORMAT_INDEX8: 1397 switch (cmd->data.draw.texture_scale_mode) { 1398 case SDL_SCALEMODE_NEAREST: 1399 constants->texture_type = TEXTURETYPE_PALETTE_NEAREST; 1400 break; 1401 case SDL_SCALEMODE_LINEAR: 1402 constants->texture_type = TEXTURETYPE_PALETTE_LINEAR; 1403 break; 1404 case SDL_SCALEMODE_PIXELART: 1405 constants->texture_type = TEXTURETYPE_PALETTE_PIXELART; 1406 break; 1407 default: 1408 SDL_assert(!"Unknown scale mode"); 1409 break; 1410 } 1411 break; 1412 case SDL_PIXELFORMAT_YV12: 1413 case SDL_PIXELFORMAT_IYUV: 1414 constants->texture_type = TEXTURETYPE_YUV; 1415 break; 1416 case SDL_PIXELFORMAT_NV12: 1417 constants->texture_type = TEXTURETYPE_NV12; 1418 break; 1419 case SDL_PIXELFORMAT_NV21: 1420 constants->texture_type = TEXTURETYPE_NV21; 1421 break; 1422 case SDL_PIXELFORMAT_P010: 1423 constants->texture_type = TEXTURETYPE_NV12; 1424 break; 1425 default: 1426 if (cmd->data.draw.texture_scale_mode == SDL_SCALEMODE_PIXELART) { 1427 constants->texture_type = TEXTURETYPE_RGB_PIXELART; 1428 } else { 1429 constants->texture_type = TEXTURETYPE_RGB; 1430 } 1431 } 1432 1433 switch (SDL_COLORSPACETRANSFER(texture->colorspace)) { 1434 case SDL_TRANSFER_CHARACTERISTICS_LINEAR: 1435 constants->input_type = INPUTTYPE_SCRGB; 1436 break; 1437 case SDL_TRANSFER_CHARACTERISTICS_PQ: 1438 constants->input_type = INPUTTYPE_HDR10; 1439 break; 1440 default: 1441 constants->input_type = INPUTTYPE_SRGB; 1442 break; 1443 } 1444 1445 if (constants->texture_type == TEXTURETYPE_PALETTE_LINEAR || 1446 constants->texture_type == TEXTURETYPE_PALETTE_PIXELART || 1447 constants->texture_type == TEXTURETYPE_RGB_PIXELART) { 1448 constants->texture_width = texture->w; 1449 constants->texture_height = texture->h; 1450 constants->texel_width = 1.0f / constants->texture_width; 1451 constants->texel_height = 1.0f / constants->texture_height; 1452 } 1453 1454 constants->sdr_white_point = texture->SDR_white_point; 1455 1456 if (renderer->target) { 1457 output_headroom = renderer->target->HDR_headroom; 1458 } else { 1459 output_headroom = renderer->HDR_headroom; 1460 } 1461 1462 if (texture->HDR_headroom > output_headroom && output_headroom > 0.0f) { 1463 constants->tonemap_method = TONEMAP_CHROME; 1464 constants->tonemap_factor1 = (output_headroom / (texture->HDR_headroom * texture->HDR_headroom)); 1465 constants->tonemap_factor2 = (1.0f / output_headroom); 1466 } 1467 } 1468} 1469 1470static 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) 1471{ 1472 SDL3METAL_RenderData *data = (__bridge SDL3METAL_RenderData *)renderer->internal; 1473 const SDL_BlendMode blend = cmd->data.draw.blend; 1474 size_t first = cmd->data.draw.first; 1475 id<MTLRenderPipelineState> newpipeline; 1476 PixelShaderConstants solid_constants; 1477 1478 if (!METAL_ActivateRenderCommandEncoder(renderer, MTLLoadActionLoad, NULL, statecache->vertex_buffer)) { 1479 return false; 1480 } 1481 1482 if (statecache->viewport_dirty) { 1483 MTLViewport viewport; 1484 viewport.originX = statecache->viewport.x; 1485 viewport.originY = statecache->viewport.y; 1486 viewport.width = statecache->viewport.w; 1487 viewport.height = statecache->viewport.h; 1488 viewport.znear = 0.0; 1489 viewport.zfar = 1.0; 1490 [data.mtlcmdencoder setViewport:viewport]; 1491 [data.mtlcmdencoder setVertexBuffer:mtlbufvertex offset:statecache->projection_offset atIndex:2]; // projection 1492 statecache->viewport_dirty = false; 1493 } 1494 1495 if (statecache->cliprect_dirty) { 1496 SDL_Rect output; 1497 SDL_Rect clip; 1498 if (statecache->cliprect_enabled) { 1499 clip = statecache->cliprect; 1500 clip.x += statecache->viewport.x; 1501 clip.y += statecache->viewport.y; 1502 } else { 1503 clip = statecache->viewport; 1504 } 1505 1506 // Set Scissor Rect Validation: w/h must be <= render pass 1507 SDL_zero(output); 1508 1509 if (renderer->target) { 1510 output.w = renderer->target->w; 1511 output.h = renderer->target->h; 1512 } else { 1513 METAL_GetOutputSize(renderer, &output.w, &output.h); 1514 } 1515 1516 if (SDL_GetRectIntersection(&output, &clip, &clip)) { 1517 MTLScissorRect mtlrect; 1518 mtlrect.x = clip.x; 1519 mtlrect.y = clip.y; 1520 mtlrect.width = clip.w; 1521 mtlrect.height = clip.h; 1522 [data.mtlcmdencoder setScissorRect:mtlrect]; 1523 } 1524 1525 statecache->cliprect_dirty = false; 1526 } 1527 1528 newpipeline = ChoosePipelineState(data, data.activepipelines, shader, blend); 1529 if (newpipeline != statecache->pipeline) { 1530 [data.mtlcmdencoder setRenderPipelineState:newpipeline]; 1531 statecache->pipeline = newpipeline; 1532 } 1533 1534 if (!shader_constants) { 1535 SetupShaderConstants(renderer, cmd, NULL, &solid_constants); 1536 shader_constants = &solid_constants; 1537 } 1538 1539 if (statecache->shader_constants_dirty || 1540 SDL_memcmp(shader_constants, &statecache->shader_constants, sizeof(*shader_constants)) != 0) { 1541 id<MTLBuffer> mtlbufconstants = [data.mtldevice newBufferWithLength:sizeof(*shader_constants) options:MTLResourceStorageModeShared]; 1542 mtlbufconstants.label = @"SDL shader constants data"; 1543 SDL_memcpy([mtlbufconstants contents], shader_constants, sizeof(*shader_constants)); 1544 [data.mtlcmdencoder setFragmentBuffer:mtlbufconstants offset:0 atIndex:0]; 1545 1546 SDL_memcpy(&statecache->shader_constants, shader_constants, sizeof(*shader_constants)); 1547 statecache->shader_constants_dirty = false; 1548 } 1549 1550 if (constants_offset != statecache->constants_offset) { 1551 if (constants_offset != CONSTANTS_OFFSET_INVALID) { 1552 [data.mtlcmdencoder setVertexBuffer:data.mtlbufconstants offset:constants_offset atIndex:3]; 1553 } 1554 statecache->constants_offset = constants_offset; 1555 } 1556 1557 [data.mtlcmdencoder setVertexBufferOffset:first atIndex:0]; // position/texcoords 1558 return true; 1559} 1560 1561static id<MTLSamplerState> GetSampler(SDL3METAL_RenderData *data, SDL_PixelFormat format, SDL_ScaleMode scale_mode, SDL_TextureAddressMode address_u, SDL_TextureAddressMode address_v) 1562{ 1563 NSNumber *key = [NSNumber numberWithInteger:RENDER_SAMPLER_HASHKEY(scale_mode, address_u, address_v)]; 1564 id<MTLSamplerState> mtlsampler = data.mtlsamplers[key]; 1565 if (mtlsampler == nil) { 1566 MTLSamplerDescriptor *samplerdesc; 1567 samplerdesc = [[MTLSamplerDescriptor alloc] init]; 1568 switch (scale_mode) { 1569 case SDL_SCALEMODE_NEAREST: 1570 samplerdesc.minFilter = MTLSamplerMinMagFilterNearest; 1571 samplerdesc.magFilter = MTLSamplerMinMagFilterNearest; 1572 break; 1573 case SDL_SCALEMODE_PIXELART: // Uses linear sampling 1574 case SDL_SCALEMODE_LINEAR: 1575 if (format == SDL_PIXELFORMAT_INDEX8) { 1576 // We'll do linear sampling in the shader 1577 samplerdesc.minFilter = MTLSamplerMinMagFilterNearest; 1578 samplerdesc.magFilter = MTLSamplerMinMagFilterNearest; 1579 } else { 1580 samplerdesc.minFilter = MTLSamplerMinMagFilterLinear; 1581 samplerdesc.magFilter = MTLSamplerMinMagFilterLinear; 1582 } 1583 break; 1584 default: 1585 SDL_SetError("Unknown scale mode: %d", scale_mode); 1586 return nil; 1587 } 1588 switch (address_u) { 1589 case SDL_TEXTURE_ADDRESS_CLAMP: 1590 samplerdesc.sAddressMode = MTLSamplerAddressModeClampToEdge; 1591 break; 1592 case SDL_TEXTURE_ADDRESS_WRAP: 1593 samplerdesc.sAddressMode = MTLSamplerAddressModeRepeat; 1594 break; 1595 default: 1596 SDL_SetError("Unknown texture address mode: %d", address_u); 1597 return nil; 1598 } 1599 switch (address_v) { 1600 case SDL_TEXTURE_ADDRESS_CLAMP: 1601 samplerdesc.tAddressMode = MTLSamplerAddressModeClampToEdge; 1602 break; 1603 case SDL_TEXTURE_ADDRESS_WRAP: 1604 samplerdesc.tAddressMode = MTLSamplerAddressModeRepeat; 1605 break; 1606 default: 1607 SDL_SetError("Unknown texture address mode: %d", address_v); 1608 return nil; 1609 } 1610 mtlsampler = [data.mtldevice newSamplerStateWithDescriptor:samplerdesc]; 1611 if (mtlsampler == nil) { 1612 SDL_SetError("Couldn't create sampler"); 1613 return nil; 1614 } 1615 data.mtlsamplers[key] = mtlsampler; 1616 } 1617 return mtlsampler; 1618} 1619 1620static bool SetCopyState(SDL_Renderer *renderer, const SDL_RenderCommand *cmd, const size_t constants_offset, 1621 id<MTLBuffer> mtlbufvertex, METAL_DrawStateCache *statecache) 1622{ 1623 SDL3METAL_RenderData *data = (__bridge SDL3METAL_RenderData *)renderer->internal; 1624 SDL_Texture *texture = cmd->data.draw.texture; 1625 SDL3METAL_TextureData *texturedata = (__bridge SDL3METAL_TextureData *)texture->internal; 1626 PixelShaderConstants constants; 1627 1628 SetupShaderConstants(renderer, cmd, texture, &constants); 1629 1630 if (!SetDrawState(renderer, cmd, texturedata.fragmentFunction, &constants, constants_offset, mtlbufvertex, statecache)) { 1631 return false; 1632 } 1633 1634 if (texture != statecache->texture) { 1635 [data.mtlcmdencoder setFragmentTexture:texturedata.mtltexture atIndex:0]; 1636 if (texture->palette) { 1637 SDL3METAL_PaletteData *palette = (__bridge SDL3METAL_PaletteData *)texture->palette->internal; 1638 [data.mtlcmdencoder setFragmentTexture:palette.mtltexture atIndex:1]; 1639 } 1640#ifdef SDL_HAVE_YUV 1641 if (texturedata.yuv || texturedata.nv12) { 1642 [data.mtlcmdencoder setFragmentTexture:texturedata.mtltextureUv atIndex:1]; 1643 [data.mtlcmdencoder setFragmentBuffer:data.mtlbufconstants offset:texturedata.conversionBufferOffset atIndex:1]; 1644 } 1645#endif 1646 statecache->texture = texture; 1647 } 1648 1649 if (cmd->data.draw.texture_scale_mode != statecache->texture_scale_mode || 1650 cmd->data.draw.texture_address_mode_u != statecache->texture_address_mode_u || 1651 cmd->data.draw.texture_address_mode_v != statecache->texture_address_mode_v) { 1652 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); 1653 if (mtlsampler == nil) { 1654 return false; 1655 } 1656 [data.mtlcmdencoder setFragmentSamplerState:mtlsampler atIndex:0]; 1657 1658 statecache->texture_scale_mode = cmd->data.draw.texture_scale_mode; 1659 statecache->texture_address_mode_u = cmd->data.draw.texture_address_mode_u; 1660 statecache->texture_address_mode_v = cmd->data.draw.texture_address_mode_v; 1661 } 1662 if (texture->palette) { 1663 if (!statecache->texture_palette) { 1664 id<MTLSamplerState> mtlsampler = GetSampler(data, SDL_PIXELFORMAT_UNKNOWN, SDL_SCALEMODE_NEAREST, SDL_TEXTURE_ADDRESS_CLAMP, SDL_TEXTURE_ADDRESS_CLAMP); 1665 if (mtlsampler == nil) { 1666 return false; 1667 } 1668 [data.mtlcmdencoder setFragmentSamplerState:mtlsampler atIndex:1]; 1669 statecache->texture_palette = true; 1670 } 1671 } else { 1672 statecache->texture_palette = false; 1673 } 1674 return true; 1675} 1676 1677static void METAL_InvalidateCachedState(SDL_Renderer *renderer) 1678{ 1679 // METAL_DrawStateCache only exists during a run of METAL_RunCommandQueue, so there's nothing to invalidate! 1680} 1681 1682static bool METAL_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd, void *vertices, size_t vertsize) 1683{ 1684 @autoreleasepool { 1685 SDL3METAL_RenderData *data = (__bridge SDL3METAL_RenderData *)renderer->internal; 1686 id<MTLBuffer> mtlbufvertex = nil; 1687 METAL_DrawStateCache statecache; 1688 SDL_zero(statecache); 1689 1690 statecache.pipeline = nil; 1691 statecache.vertex_buffer = nil; 1692 statecache.constants_offset = CONSTANTS_OFFSET_INVALID; 1693 statecache.texture = NULL; 1694 statecache.texture_scale_mode = SDL_SCALEMODE_INVALID; 1695 statecache.texture_address_mode_u = SDL_TEXTURE_ADDRESS_INVALID; 1696 statecache.texture_address_mode_v = SDL_TEXTURE_ADDRESS_INVALID; 1697 statecache.shader_constants_dirty = true; 1698 statecache.cliprect_dirty = true; 1699 statecache.viewport_dirty = true; 1700 statecache.projection_offset = 0; 1701 1702 // !!! FIXME: have a ring of pre-made MTLBuffers we cycle through? How expensive is creation? 1703 if (vertsize > 0) { 1704 /* We can memcpy to a shared buffer from the CPU and read it from the GPU 1705 * without any extra copying. It's a bit slower on macOS to read shared 1706 * data from the GPU than to read managed/private data, but we avoid the 1707 * cost of copying the data and the code's simpler. Apple's best 1708 * practices guide recommends this approach for streamed vertex data. 1709 */ 1710 mtlbufvertex = [data.mtldevice newBufferWithLength:vertsize options:MTLResourceStorageModeShared]; 1711 mtlbufvertex.label = @"SDL vertex data"; 1712 SDL_memcpy([mtlbufvertex contents], vertices, vertsize); 1713 1714 statecache.vertex_buffer = mtlbufvertex; 1715 } 1716 1717 // If there's a command buffer here unexpectedly (app requested one?). Commit it so we can start fresh. 1718 [data.mtlcmdencoder endEncoding]; 1719 [data.mtlcmdbuffer commit]; 1720 data.mtlcmdencoder = nil; 1721 data.mtlcmdbuffer = nil; 1722 1723 while (cmd) { 1724 switch (cmd->command) { 1725 case SDL_RENDERCMD_SETVIEWPORT: 1726 { 1727 SDL_memcpy(&statecache.viewport, &cmd->data.viewport.rect, sizeof(statecache.viewport)); 1728 statecache.projection_offset = cmd->data.viewport.first; 1729 statecache.viewport_dirty = true; 1730 statecache.cliprect_dirty = true; 1731 break; 1732 } 1733 1734 case SDL_RENDERCMD_SETCLIPRECT: 1735 { 1736 SDL_memcpy(&statecache.cliprect, &cmd->data.cliprect.rect, sizeof(statecache.cliprect)); 1737 statecache.cliprect_enabled = cmd->data.cliprect.enabled; 1738 statecache.cliprect_dirty = true; 1739 break; 1740 } 1741 1742 case SDL_RENDERCMD_SETDRAWCOLOR: 1743 { 1744 break; 1745 } 1746 1747 case SDL_RENDERCMD_CLEAR: 1748 { 1749 /* If we're already encoding a command buffer, dump it without committing it. We'd just 1750 clear all its work anyhow, and starting a new encoder will let us use a hardware clear 1751 operation via MTLLoadActionClear. */ 1752 if (data.mtlcmdencoder != nil) { 1753 [data.mtlcmdencoder endEncoding]; 1754 1755 // !!! FIXME: have to commit, or an uncommitted but enqueued buffer will prevent the frame from finishing. 1756 [data.mtlcmdbuffer commit]; 1757 data.mtlcmdencoder = nil; 1758 data.mtlcmdbuffer = nil; 1759 } 1760 1761 // force all this state to be reconfigured on next command buffer. 1762 statecache.pipeline = nil; 1763 statecache.constants_offset = CONSTANTS_OFFSET_INVALID; 1764 statecache.texture = NULL; 1765 statecache.shader_constants_dirty = true; 1766 statecache.cliprect_dirty = true; 1767 statecache.viewport_dirty = true; 1768 1769 { 1770 bool convert_color = SDL_RenderingLinearSpace(renderer); 1771 SDL_FColor color = cmd->data.color.color; 1772 if (convert_color) { 1773 SDL_ConvertToLinear(&color); 1774 } 1775 color.r *= cmd->data.color.color_scale; 1776 color.g *= cmd->data.color.color_scale; 1777 color.b *= cmd->data.color.color_scale; 1778 MTLClearColor mtlcolor = MTLClearColorMake(color.r, color.g, color.b, color.a); 1779 1780 // get new command encoder, set up with an initial clear operation. 1781 // (this might fail, and future draw operations will notice.) 1782 METAL_ActivateRenderCommandEncoder(renderer, MTLLoadActionClear, &mtlcolor, mtlbufvertex); 1783 } 1784 break; 1785 } 1786 1787 case SDL_RENDERCMD_DRAW_LINES: 1788 { 1789 if (SetDrawState(renderer, cmd, SDL_METAL_FRAGMENT_SOLID, NULL, CONSTANTS_OFFSET_HALF_PIXEL_TRANSFORM, mtlbufvertex, &statecache)) { 1790 size_t count = cmd->data.draw.count; 1791 if (count > 2) { 1792 // joined lines cannot be grouped 1793 [data.mtlcmdencoder drawPrimitives:MTLPrimitiveTypeLineStrip vertexStart:0 vertexCount:count]; 1794 } else { 1795 // let's group non joined lines 1796 SDL_RenderCommand *finalcmd = cmd; 1797 SDL_RenderCommand *nextcmd; 1798 float thiscolorscale = cmd->data.draw.color_scale; 1799 SDL_BlendMode thisblend = cmd->data.draw.blend; 1800 1801 for (nextcmd = cmd->next; nextcmd; nextcmd = nextcmd->next) { 1802 const SDL_RenderCommandType nextcmdtype = nextcmd->command; 1803 if (nextcmdtype != SDL_RENDERCMD_DRAW_LINES) { 1804 if (nextcmdtype == SDL_RENDERCMD_SETDRAWCOLOR) { 1805 // The vertex data has the draw color built in, ignore this 1806 continue; 1807 } 1808 break; // can't go any further on this draw call, different render command up next. 1809 } else if (nextcmd->data.draw.count != 2) { 1810 break; // can't go any further on this draw call, those are joined lines 1811 } else if (nextcmd->data.draw.blend != thisblend || 1812 nextcmd->data.draw.color_scale != thiscolorscale) { 1813 break; // can't go any further on this draw call, different blendmode copy up next. 1814 } else { 1815 finalcmd = nextcmd; // we can combine copy operations here. Mark this one as the furthest okay command. 1816 count += nextcmd->data.draw.count; 1817 } 1818 } 1819 1820 [data.mtlcmdencoder drawPrimitives:MTLPrimitiveTypeLine vertexStart:0 vertexCount:count]; 1821 cmd = finalcmd; // skip any copy commands we just combined in here. 1822 } 1823 } 1824 break; 1825 } 1826 1827 case SDL_RENDERCMD_FILL_RECTS: // unused 1828 break; 1829 1830 case SDL_RENDERCMD_COPY: // unused 1831 break; 1832 1833 case SDL_RENDERCMD_COPY_EX: // unused 1834 break; 1835 1836 case SDL_RENDERCMD_DRAW_POINTS: 1837 case SDL_RENDERCMD_GEOMETRY: 1838 { 1839 float thiscolorscale = cmd->data.draw.color_scale; 1840 SDL_Texture *thistexture = cmd->data.draw.texture; 1841 SDL_BlendMode thisblend = cmd->data.draw.blend; 1842 SDL_ScaleMode thisscalemode = cmd->data.draw.texture_scale_mode; 1843 SDL_TextureAddressMode thisaddressmode_u = cmd->data.draw.texture_address_mode_u; 1844 SDL_TextureAddressMode thisaddressmode_v = cmd->data.draw.texture_address_mode_v; 1845 const SDL_RenderCommandType thiscmdtype = cmd->command; 1846 SDL_RenderCommand *finalcmd = cmd; 1847 SDL_RenderCommand *nextcmd; 1848 size_t count = cmd->data.draw.count; 1849 for (nextcmd = cmd->next; nextcmd; nextcmd = nextcmd->next) { 1850 const SDL_RenderCommandType nextcmdtype = nextcmd->command; 1851 if (nextcmdtype != thiscmdtype) { 1852 if (nextcmdtype == SDL_RENDERCMD_SETDRAWCOLOR) { 1853 // The vertex data has the draw color built in, ignore this 1854 continue; 1855 } 1856 break; // can't go any further on this draw call, different render command up next. 1857 } else if (nextcmd->data.draw.texture != thistexture || 1858 nextcmd->data.draw.texture_scale_mode != thisscalemode || 1859 nextcmd->data.draw.texture_address_mode_u != thisaddressmode_u || 1860 nextcmd->data.draw.texture_address_mode_v != thisaddressmode_v || 1861 nextcmd->data.draw.blend != thisblend || 1862 nextcmd->data.draw.color_scale != thiscolorscale) { 1863 break; // can't go any further on this draw call, different texture/blendmode copy up next. 1864 } else { 1865 finalcmd = nextcmd; // we can combine copy operations here. Mark this one as the furthest okay command. 1866 count += nextcmd->data.draw.count; 1867 } 1868 } 1869 1870 if (thiscmdtype == SDL_RENDERCMD_GEOMETRY) { 1871 if (thistexture) { 1872 if (SetCopyState(renderer, cmd, CONSTANTS_OFFSET_IDENTITY, mtlbufvertex, &statecache)) { 1873 [data.mtlcmdencoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:count]; 1874 } 1875 } else { 1876 if (SetDrawState(renderer, cmd, SDL_METAL_FRAGMENT_SOLID, NULL, CONSTANTS_OFFSET_IDENTITY, mtlbufvertex, &statecache)) { 1877 [data.mtlcmdencoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:count]; 1878 } 1879 } 1880 } else { 1881 if (SetDrawState(renderer, cmd, SDL_METAL_FRAGMENT_SOLID, NULL, CONSTANTS_OFFSET_HALF_PIXEL_TRANSFORM, mtlbufvertex, &statecache)) { 1882 [data.mtlcmdencoder drawPrimitives:MTLPrimitiveTypePoint vertexStart:0 vertexCount:count]; 1883 } 1884 } 1885 cmd = finalcmd; // skip any copy commands we just combined in here. 1886 break; 1887 } 1888 1889 case SDL_RENDERCMD_NO_OP: 1890 break; 1891 } 1892 cmd = cmd->next; 1893 } 1894 1895 return true; 1896 } 1897} 1898 1899static SDL_Surface *METAL_RenderReadPixels(SDL_Renderer *renderer, const SDL_Rect *rect) 1900{ 1901 @autoreleasepool { 1902 SDL3METAL_RenderData *data = (__bridge SDL3METAL_RenderData *)renderer->internal; 1903 id<MTLTexture> mtltexture; 1904 MTLRegion mtlregion; 1905 Uint32 format; 1906 SDL_Surface *surface; 1907 1908 if (!METAL_ActivateRenderCommandEncoder(renderer, MTLLoadActionLoad, NULL, nil)) { 1909 SDL_SetError("Failed to activate render command encoder (is your window in the background?"); 1910 return NULL; 1911 } 1912 1913 [data.mtlcmdencoder endEncoding]; 1914 mtltexture = data.mtlpassdesc.colorAttachments[0].texture; 1915 1916#ifdef SDL_PLATFORM_MACOS 1917 /* on macOS with managed-storage textures, we need to tell the driver to 1918 * update the CPU-side copy of the texture data. 1919 * NOTE: Currently all of our textures are managed on macOS. We'll need some 1920 * extra copying for any private textures. */ 1921 if (METAL_GetStorageMode(mtltexture) == MTLStorageModeManaged) { 1922 id<MTLBlitCommandEncoder> blit = [data.mtlcmdbuffer blitCommandEncoder]; 1923 [blit synchronizeResource:mtltexture]; 1924 [blit endEncoding]; 1925 } 1926#endif 1927 1928 /* Commit the current command buffer and wait until it's completed, to make 1929 * sure the GPU has finished rendering to it by the time we read it. */ 1930 [data.mtlcmdbuffer commit]; 1931 [data.mtlcmdbuffer waitUntilCompleted]; 1932 data.mtlcmdencoder = nil; 1933 data.mtlcmdbuffer = nil; 1934 1935 mtlregion = MTLRegionMake2D(rect->x, rect->y, rect->w, rect->h); 1936 1937 switch (mtltexture.pixelFormat) { 1938 case MTLPixelFormatBGRA8Unorm: 1939 case MTLPixelFormatBGRA8Unorm_sRGB: 1940 format = SDL_PIXELFORMAT_ARGB8888; 1941 break; 1942 case MTLPixelFormatRGBA8Unorm: 1943 case MTLPixelFormatRGBA8Unorm_sRGB: 1944 format = SDL_PIXELFORMAT_ABGR8888; 1945 break; 1946 case MTLPixelFormatRGB10A2Unorm: 1947 format = SDL_PIXELFORMAT_ABGR2101010; 1948 break; 1949 case MTLPixelFormatRGBA16Float: 1950 format = SDL_PIXELFORMAT_RGBA64_FLOAT; 1951 break; 1952 default: 1953 SDL_SetError("Unknown framebuffer pixel format"); 1954 return NULL; 1955 } 1956 surface = SDL_CreateSurface(rect->w, rect->h, format); 1957 if (surface) { 1958 [mtltexture getBytes:surface->pixels bytesPerRow:surface->pitch fromRegion:mtlregion mipmapLevel:0]; 1959 } 1960 return surface; 1961 } 1962} 1963 1964static bool METAL_RenderPresent(SDL_Renderer *renderer) 1965{ 1966 @autoreleasepool { 1967 SDL3METAL_RenderData *data = (__bridge SDL3METAL_RenderData *)renderer->internal; 1968 bool ready = true; 1969 1970 // If we don't have a command buffer, we can't present, so activate to get one. 1971 if (data.mtlcmdencoder == nil) { 1972 // We haven't even gotten a backbuffer yet? Load and clear it. Otherwise, load the existing data. 1973 if (data.mtlbackbuffer == nil) { 1974 float alpha = (SDL_GetWindowFlags(renderer->window) & SDL_WINDOW_TRANSPARENT) ? 0.0f : 1.0f; 1975 MTLClearColor color = MTLClearColorMake(0.0f, 0.0f, 0.0f, alpha); 1976 ready = METAL_ActivateRenderCommandEncoder(renderer, MTLLoadActionClear, &color, nil); 1977 } else { 1978 ready = METAL_ActivateRenderCommandEncoder(renderer, MTLLoadActionLoad, NULL, nil); 1979 } 1980 } 1981 1982 [data.mtlcmdencoder endEncoding]; 1983 1984 // If we don't have a drawable to present, don't try to present it. 1985 // But we'll still try to commit the command buffer in case it was already enqueued. 1986 if (ready) { 1987 SDL_assert(data.mtlbackbuffer != nil); 1988 [data.mtlcmdbuffer presentDrawable:data.mtlbackbuffer]; 1989 } 1990 1991 [data.mtlcmdbuffer commit]; 1992 1993 data.mtlcmdencoder = nil; 1994 data.mtlcmdbuffer = nil; 1995 data.mtlbackbuffer = nil; 1996 1997 if (renderer->hidden || !ready) { 1998 return false; 1999 } 2000 return true; 2001 } 2002} 2003 2004static void METAL_DestroyTexture(SDL_Renderer *renderer, SDL_Texture *texture) 2005{ 2006 @autoreleasepool { 2007 CFBridgingRelease(texture->internal); 2008 texture->internal = NULL; 2009 } 2010} 2011 2012static void METAL_DestroyRenderer(SDL_Renderer *renderer) 2013{ 2014 @autoreleasepool { 2015 if (renderer->internal) { 2016 SDL3METAL_RenderData *data = CFBridgingRelease(renderer->internal); 2017 2018 if (data.mtlcmdencoder != nil) { 2019 [data.mtlcmdencoder endEncoding]; 2020 } 2021 2022 DestroyAllPipelines(data.allpipelines, data.pipelinescount); 2023 2024 /* Release the metal view instead of destroying it, 2025 in case we want to use it later (recreating the renderer) 2026 */ 2027 // SDL_Metal_DestroyView(data.mtlview); 2028 CFBridgingRelease(data.mtlview); 2029 } 2030 } 2031} 2032 2033static void *METAL_GetMetalLayer(SDL_Renderer *renderer) 2034{ 2035 @autoreleasepool { 2036 SDL3METAL_RenderData *data = (__bridge SDL3METAL_RenderData *)renderer->internal; 2037 return (__bridge void *)data.mtllayer; 2038 } 2039} 2040 2041static void *METAL_GetMetalCommandEncoder(SDL_Renderer *renderer) 2042{ 2043 @autoreleasepool { 2044 // note that data.mtlcmdencoder can be nil if METAL_ActivateRenderCommandEncoder fails. 2045 // Before SDL 2.0.18, it might have returned a non-nil encoding that might not have been 2046 // usable for presentation. Check your return values! 2047 SDL3METAL_RenderData *data; 2048 METAL_ActivateRenderCommandEncoder(renderer, MTLLoadActionLoad, NULL, nil); 2049 data = (__bridge SDL3METAL_RenderData *)renderer->internal; 2050 return (__bridge void *)data.mtlcmdencoder; 2051 } 2052} 2053 2054static bool METAL_SetVSync(SDL_Renderer *renderer, const int vsync) 2055{ 2056#if defined(SDL_PLATFORM_MACOS) || TARGET_OS_MACCATALYST 2057 SDL3METAL_RenderData *data = (__bridge SDL3METAL_RenderData *)renderer->internal; 2058 switch (vsync) { 2059 case 0: 2060 data.mtllayer.displaySyncEnabled = NO; 2061 break; 2062 case 1: 2063 data.mtllayer.displaySyncEnabled = YES; 2064 break; 2065 default: 2066 return SDL_Unsupported(); 2067 } 2068 return true; 2069#else 2070 switch (vsync) { 2071 case 1: 2072 return true; 2073 default: 2074 return SDL_Unsupported(); 2075 } 2076#endif 2077} 2078 2079static SDL_MetalView GetWindowView(SDL_Window *window) 2080{ 2081#ifdef SDL_VIDEO_DRIVER_COCOA 2082 NSWindow *nswindow = (__bridge NSWindow *)SDL_GetPointerProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_COCOA_WINDOW_POINTER, NULL); 2083 NSInteger tag = (NSInteger)SDL_GetNumberProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_COCOA_METAL_VIEW_TAG_NUMBER, 0); 2084 if (nswindow && tag) { 2085 NSView *view = nswindow.contentView; 2086 if (view.subviews.count > 0) { 2087 view = view.subviews[0]; 2088 if (view.tag == tag) { 2089 return (SDL_MetalView)CFBridgingRetain(view); 2090 } 2091 } 2092 } 2093#endif 2094 2095#ifdef SDL_VIDEO_DRIVER_UIKIT 2096 UIWindow *uiwindow = (__bridge UIWindow *)SDL_GetPointerProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_UIKIT_WINDOW_POINTER, NULL); 2097 NSInteger tag = (NSInteger)SDL_GetNumberProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_UIKIT_METAL_VIEW_TAG_NUMBER, 0); 2098 if (uiwindow && tag) { 2099 UIView *view = uiwindow.rootViewController.view; 2100 if (view.tag == tag) { 2101 return (SDL_MetalView)CFBridgingRetain(view); 2102 } 2103 } 2104#endif 2105 2106 return nil; 2107} 2108 2109static bool METAL_CreateRenderer(SDL_Renderer *renderer, SDL_Window *window, SDL_PropertiesID create_props) 2110{ 2111 @autoreleasepool { 2112 SDL3METAL_RenderData *data = NULL; 2113 id<MTLDevice> mtldevice = nil; 2114 SDL_MetalView view = NULL; 2115 CAMetalLayer *layer = nil; 2116 NSError *err = nil; 2117 dispatch_data_t mtllibdata; 2118 char *constantdata; 2119 int maxtexsize, quadcount = UINT16_MAX / 4; 2120 UInt16 *indexdata; 2121 size_t indicessize = sizeof(UInt16) * quadcount * 6; 2122 id<MTLCommandQueue> mtlcmdqueue; 2123 id<MTLLibrary> mtllibrary; 2124 id<MTLBuffer> mtlbufconstantstaging, mtlbufquadindicesstaging, mtlbufconstants, mtlbufquadindices; 2125 id<MTLCommandBuffer> cmdbuffer; 2126 id<MTLBlitCommandEncoder> blitcmd; 2127 bool scRGB_supported = false; 2128 2129 // Note: matrices are column major. 2130 float identitytransform[16] = { 2131 1.0f, 2132 0.0f, 2133 0.0f, 2134 0.0f, 2135 0.0f, 2136 1.0f, 2137 0.0f, 2138 0.0f, 2139 0.0f, 2140 0.0f, 2141 1.0f, 2142 0.0f, 2143 0.0f, 2144 0.0f, 2145 0.0f, 2146 1.0f, 2147 }; 2148 2149 float halfpixeltransform[16] = { 2150 1.0f, 2151 0.0f, 2152 0.0f, 2153 0.0f, 2154 0.0f, 2155 1.0f, 2156 0.0f, 2157 0.0f, 2158 0.0f, 2159 0.0f, 2160 1.0f, 2161 0.0f, 2162 0.5f, 2163 0.5f, 2164 0.0f, 2165 1.0f, 2166 }; 2167 2168 const size_t YCbCr_shader_matrix_size = 4 * 4 * sizeof(float); 2169 2170 SDL_SetupRendererColorspace(renderer, create_props); 2171 2172#ifndef SDL_PLATFORM_TVOS 2173 if (@available(macos 10.11, iOS 16.0, *)) { 2174 scRGB_supported = true; 2175 } 2176#endif 2177 if (renderer->output_colorspace != SDL_COLORSPACE_SRGB) { 2178 if (renderer->output_colorspace == SDL_COLORSPACE_SRGB_LINEAR && scRGB_supported) { 2179 // This colorspace is supported 2180 } else { 2181 return SDL_SetError("Unsupported output colorspace"); 2182 } 2183 } 2184 2185#ifdef SDL_PLATFORM_MACOS 2186 if (SDL_GetHintBoolean(SDL_HINT_RENDER_METAL_PREFER_LOW_POWER_DEVICE, true)) { 2187 NSArray<id<MTLDevice>> *devices = MTLCopyAllDevices(); 2188 2189 for (id<MTLDevice> device in devices) { 2190 if (device.isLowPower) { 2191 mtldevice = device; 2192 break; 2193 } 2194 } 2195 } 2196#endif 2197 2198 if (mtldevice == nil) { 2199 mtldevice = MTLCreateSystemDefaultDevice(); 2200 } 2201 2202 if (mtldevice == nil) { 2203 return SDL_SetError("Failed to obtain Metal device"); 2204 } 2205 2206 view = GetWindowView(window); 2207 if (view == nil) { 2208 view = SDL_Metal_CreateView(window); 2209 } 2210 2211 if (view == NULL) { 2212 return false; 2213 } 2214 2215 // !!! FIXME: error checking on all of this. 2216 data = [[SDL3METAL_RenderData alloc] init]; 2217 2218 if (data == nil) { 2219 /* Release the metal view instead of destroying it, 2220 in case we want to use it later (recreating the renderer) 2221 */ 2222 // SDL_Metal_DestroyView(view); 2223 CFBridgingRelease(view); 2224 return SDL_SetError("SDL3METAL_RenderData alloc/init failed"); 2225 } 2226 2227 renderer->internal = (void *)CFBridgingRetain(data); 2228 METAL_InvalidateCachedState(renderer); 2229 renderer->window = window; 2230 2231 data.mtlview = view; 2232 2233#ifdef SDL_PLATFORM_MACOS 2234 layer = (CAMetalLayer *)[(__bridge NSView *)view layer]; 2235#else 2236 layer = (CAMetalLayer *)[(__bridge UIView *)view layer]; 2237#endif 2238 2239#ifndef SDL_PLATFORM_TVOS 2240 if (renderer->output_colorspace == SDL_COLORSPACE_SRGB_LINEAR) { 2241 if (@available(macos 10.11, iOS 16.0, *)) { 2242 layer.wantsExtendedDynamicRangeContent = YES; 2243 } else { 2244 SDL_assert(!"Logic error, scRGB is not actually supported"); 2245 } 2246 layer.pixelFormat = MTLPixelFormatRGBA16Float; 2247 2248 const CFStringRef name = kCGColorSpaceExtendedLinearSRGB; 2249 CGColorSpaceRef colorspace = CGColorSpaceCreateWithName(name); 2250 layer.colorspace = colorspace; 2251 CGColorSpaceRelease(colorspace); 2252 } 2253#endif // !SDL_PLATFORM_TVOS 2254 2255 layer.device = mtldevice; 2256 2257 // Necessary for RenderReadPixels. 2258 layer.framebufferOnly = NO; 2259 2260 data.mtldevice = layer.device; 2261 data.mtllayer = layer; 2262 mtlcmdqueue = [data.mtldevice newCommandQueue]; 2263 data.mtlcmdqueue = mtlcmdqueue; 2264 data.mtlcmdqueue.label = @"SDL Metal Renderer"; 2265 data.mtlpassdesc = [MTLRenderPassDescriptor renderPassDescriptor]; 2266 2267 // The compiled .metallib is embedded in a static array in a header file 2268 // but the original shader source code is in SDL_shaders_metal.metal. 2269 mtllibdata = dispatch_data_create(sdl_metallib, sdl_metallib_len, dispatch_get_global_queue(0, 0), ^{ 2270 }); 2271 mtllibrary = [data.mtldevice newLibraryWithData:mtllibdata error:&err]; 2272 data.mtllibrary = mtllibrary; 2273 SDL_assert(err == nil); 2274 data.mtllibrary.label = @"SDL Metal renderer shader library"; 2275 2276 // Do some shader pipeline state loading up-front rather than on demand. 2277 data.pipelinescount = 0; 2278 data.allpipelines = NULL; 2279 ChooseShaderPipelines(data, MTLPixelFormatBGRA8Unorm); 2280 2281 data.mtlsamplers = [[NSMutableDictionary<NSNumber *, id<MTLSamplerState>> alloc] init]; 2282 2283 mtlbufconstantstaging = [data.mtldevice newBufferWithLength:CONSTANTS_LENGTH options:MTLResourceStorageModeShared]; 2284 2285 constantdata = [mtlbufconstantstaging contents]; 2286 SDL_memcpy(constantdata + CONSTANTS_OFFSET_IDENTITY, identitytransform, sizeof(identitytransform)); 2287 SDL_memcpy(constantdata + CONSTANTS_OFFSET_HALF_PIXEL_TRANSFORM, halfpixeltransform, sizeof(halfpixeltransform)); 2288 SDL_memcpy(constantdata + CONSTANTS_OFFSET_DECODE_BT601_LIMITED, SDL_GetYCbCRtoRGBConversionMatrix(SDL_COLORSPACE_BT601_LIMITED, 0, 0, 8), YCbCr_shader_matrix_size); 2289 SDL_memcpy(constantdata + CONSTANTS_OFFSET_DECODE_BT601_FULL, SDL_GetYCbCRtoRGBConversionMatrix(SDL_COLORSPACE_BT601_FULL, 0, 0, 8), YCbCr_shader_matrix_size); 2290 SDL_memcpy(constantdata + CONSTANTS_OFFSET_DECODE_BT709_LIMITED, SDL_GetYCbCRtoRGBConversionMatrix(SDL_COLORSPACE_BT709_LIMITED, 0, 0, 8), YCbCr_shader_matrix_size); 2291 SDL_memcpy(constantdata + CONSTANTS_OFFSET_DECODE_BT709_FULL, SDL_GetYCbCRtoRGBConversionMatrix(SDL_COLORSPACE_BT709_FULL, 0, 0, 8), YCbCr_shader_matrix_size); 2292 SDL_memcpy(constantdata + CONSTANTS_OFFSET_DECODE_BT2020_LIMITED, SDL_GetYCbCRtoRGBConversionMatrix(SDL_COLORSPACE_BT2020_LIMITED, 0, 0, 10), YCbCr_shader_matrix_size); 2293 SDL_memcpy(constantdata + CONSTANTS_OFFSET_DECODE_BT2020_FULL, SDL_GetYCbCRtoRGBConversionMatrix(SDL_COLORSPACE_BT2020_FULL, 0, 0, 10), YCbCr_shader_matrix_size); 2294 2295 mtlbufquadindicesstaging = [data.mtldevice newBufferWithLength:indicessize options:MTLResourceStorageModeShared]; 2296 2297 /* Quads in the following vertex order (matches the FillRects vertices): 2298 * 1---3 2299 * | \ | 2300 * 0---2 2301 */ 2302 indexdata = [mtlbufquadindicesstaging contents]; 2303 for (int i = 0; i < quadcount; i++) { 2304 indexdata[i * 6 + 0] = i * 4 + 0; 2305 indexdata[i * 6 + 1] = i * 4 + 1; 2306 indexdata[i * 6 + 2] = i * 4 + 2; 2307 2308 indexdata[i * 6 + 3] = i * 4 + 2; 2309 indexdata[i * 6 + 4] = i * 4 + 1; 2310 indexdata[i * 6 + 5] = i * 4 + 3; 2311 } 2312 2313 mtlbufconstants = [data.mtldevice newBufferWithLength:CONSTANTS_LENGTH options:MTLResourceStorageModePrivate]; 2314 data.mtlbufconstants = mtlbufconstants; 2315 data.mtlbufconstants.label = @"SDL constant data"; 2316 2317 mtlbufquadindices = [data.mtldevice newBufferWithLength:indicessize options:MTLResourceStorageModePrivate]; 2318 data.mtlbufquadindices = mtlbufquadindices; 2319 data.mtlbufquadindices.label = @"SDL quad index buffer"; 2320 2321 cmdbuffer = [data.mtlcmdqueue commandBuffer]; 2322 blitcmd = [cmdbuffer blitCommandEncoder]; 2323 2324 [blitcmd copyFromBuffer:mtlbufconstantstaging sourceOffset:0 toBuffer:mtlbufconstants destinationOffset:0 size:CONSTANTS_LENGTH]; 2325 [blitcmd copyFromBuffer:mtlbufquadindicesstaging sourceOffset:0 toBuffer:mtlbufquadindices destinationOffset:0 size:indicessize]; 2326 2327 [blitcmd endEncoding]; 2328 [cmdbuffer commit]; 2329 2330 // !!! FIXME: force more clears here so all the drawables are sane to start, and our static buffers are definitely flushed. 2331 2332 renderer->WindowEvent = METAL_WindowEvent; 2333 renderer->GetOutputSize = METAL_GetOutputSize; 2334 renderer->SupportsBlendMode = METAL_SupportsBlendMode; 2335 renderer->CreatePalette = METAL_CreatePalette; 2336 renderer->UpdatePalette = METAL_UpdatePalette; 2337 renderer->DestroyPalette = METAL_DestroyPalette; 2338 renderer->CreateTexture = METAL_CreateTexture; 2339 renderer->UpdateTexture = METAL_UpdateTexture; 2340#ifdef SDL_HAVE_YUV 2341 renderer->UpdateTextureYUV = METAL_UpdateTextureYUV; 2342 renderer->UpdateTextureNV = METAL_UpdateTextureNV; 2343#endif 2344 renderer->LockTexture = METAL_LockTexture; 2345 renderer->UnlockTexture = METAL_UnlockTexture; 2346 renderer->SetRenderTarget = METAL_SetRenderTarget; 2347 renderer->QueueSetViewport = METAL_QueueSetViewport; 2348 renderer->QueueSetDrawColor = METAL_QueueNoOp; 2349 renderer->QueueDrawPoints = METAL_QueueDrawPoints; 2350 renderer->QueueDrawLines = METAL_QueueDrawLines; 2351 renderer->QueueGeometry = METAL_QueueGeometry; 2352 renderer->InvalidateCachedState = METAL_InvalidateCachedState; 2353 renderer->RunCommandQueue = METAL_RunCommandQueue; 2354 renderer->RenderReadPixels = METAL_RenderReadPixels; 2355 renderer->RenderPresent = METAL_RenderPresent; 2356 renderer->DestroyTexture = METAL_DestroyTexture; 2357 renderer->DestroyRenderer = METAL_DestroyRenderer; 2358 renderer->SetVSync = METAL_SetVSync; 2359 renderer->GetMetalLayer = METAL_GetMetalLayer; 2360 renderer->GetMetalCommandEncoder = METAL_GetMetalCommandEncoder; 2361 2362 renderer->name = METAL_RenderDriver.name; 2363 SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_ARGB8888); 2364 SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_ABGR8888); 2365 SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_ABGR2101010); 2366 SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_RGBA64_FLOAT); 2367 SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_RGBA128_FLOAT); 2368 SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_INDEX8); 2369 SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_YV12); 2370 SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_IYUV); 2371 SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_NV12); 2372 SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_NV21); 2373 SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_P010); 2374 2375#if defined(SDL_PLATFORM_MACOS) || TARGET_OS_MACCATALYST 2376 data.mtllayer.displaySyncEnabled = NO; 2377#endif 2378 2379 // https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf 2380 maxtexsize = 4096; 2381#if defined(SDL_PLATFORM_MACOS) || TARGET_OS_MACCATALYST 2382 maxtexsize = 16384; 2383#elif defined(SDL_PLATFORM_TVOS) 2384 maxtexsize = 8192; 2385 if ([mtldevice supportsFeatureSet:MTLFeatureSet_tvOS_GPUFamily2_v1]) { 2386 maxtexsize = 16384; 2387 } 2388#else 2389 if ([mtldevice supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily4_v1]) { 2390 maxtexsize = 16384; 2391 } else if ([mtldevice supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily3_v1]) { 2392 maxtexsize = 16384; 2393 } else if ([mtldevice supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily2_v2] || [mtldevice supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily1_v2]) { 2394 maxtexsize = 8192; 2395 } else { 2396 maxtexsize = 4096; 2397 } 2398#endif 2399 2400 SDL_SetNumberProperty(SDL_GetRendererProperties(renderer), SDL_PROP_RENDERER_MAX_TEXTURE_SIZE_NUMBER, maxtexsize); 2401 2402 return true; 2403 } 2404} 2405 2406SDL_RenderDriver METAL_RenderDriver = { 2407 METAL_CreateRenderer, "metal" 2408}; 2409 2410#endif // SDL_VIDEO_RENDER_METAL 2411[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.