Atlas - SDL_render_metal.m
Home / ext / SDL2 / src / render / metal Lines: 1 | Size: 55845 bytes [Download] [Show on GitHub] [Search similar files] [Raw] [Raw (proxy)][FILE BEGIN]1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2018 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#if SDL_VIDEO_RENDER_METAL && !SDL_RENDER_DISABLED 24 25#include "SDL_hints.h" 26#include "SDL_log.h" 27#include "SDL_assert.h" 28#include "SDL_syswm.h" 29#include "../SDL_sysrender.h" 30 31#ifdef __MACOSX__ 32#include "../../video/cocoa/SDL_cocoametalview.h" 33#else 34#include "../../video/uikit/SDL_uikitmetalview.h" 35#endif 36#include <Availability.h> 37#import <Metal/Metal.h> 38#import <QuartzCore/CAMetalLayer.h> 39 40/* Regenerate these with build-metal-shaders.sh */ 41#ifdef __MACOSX__ 42#include "SDL_shaders_metal_osx.h" 43#else 44#include "SDL_shaders_metal_ios.h" 45#endif 46 47/* Apple Metal renderer implementation */ 48 49static SDL_Renderer *METAL_CreateRenderer(SDL_Window * window, Uint32 flags); 50static void METAL_WindowEvent(SDL_Renderer * renderer, 51 const SDL_WindowEvent *event); 52static int METAL_GetOutputSize(SDL_Renderer * renderer, int *w, int *h); 53static SDL_bool METAL_SupportsBlendMode(SDL_Renderer * renderer, SDL_BlendMode blendMode); 54static int METAL_CreateTexture(SDL_Renderer * renderer, SDL_Texture * texture); 55static int METAL_UpdateTexture(SDL_Renderer * renderer, SDL_Texture * texture, 56 const SDL_Rect * rect, const void *pixels, 57 int pitch); 58static int METAL_UpdateTextureYUV(SDL_Renderer * renderer, SDL_Texture * texture, 59 const SDL_Rect * rect, 60 const Uint8 *Yplane, int Ypitch, 61 const Uint8 *Uplane, int Upitch, 62 const Uint8 *Vplane, int Vpitch); 63static int METAL_LockTexture(SDL_Renderer * renderer, SDL_Texture * texture, 64 const SDL_Rect * rect, void **pixels, int *pitch); 65static void METAL_UnlockTexture(SDL_Renderer * renderer, SDL_Texture * texture); 66static int METAL_SetRenderTarget(SDL_Renderer * renderer, SDL_Texture * texture); 67static int METAL_UpdateViewport(SDL_Renderer * renderer); 68static int METAL_UpdateClipRect(SDL_Renderer * renderer); 69static int METAL_RenderClear(SDL_Renderer * renderer); 70static int METAL_RenderDrawPoints(SDL_Renderer * renderer, 71 const SDL_FPoint * points, int count); 72static int METAL_RenderDrawLines(SDL_Renderer * renderer, 73 const SDL_FPoint * points, int count); 74static int METAL_RenderFillRects(SDL_Renderer * renderer, 75 const SDL_FRect * rects, int count); 76static int METAL_RenderCopy(SDL_Renderer * renderer, SDL_Texture * texture, 77 const SDL_Rect * srcrect, const SDL_FRect * dstrect); 78static int METAL_RenderCopyEx(SDL_Renderer * renderer, SDL_Texture * texture, 79 const SDL_Rect * srcrect, const SDL_FRect * dstrect, 80 const double angle, const SDL_FPoint *center, const SDL_RendererFlip flip); 81static int METAL_RenderReadPixels(SDL_Renderer * renderer, const SDL_Rect * rect, 82 Uint32 pixel_format, void * pixels, int pitch); 83static void METAL_RenderPresent(SDL_Renderer * renderer); 84static void METAL_DestroyTexture(SDL_Renderer * renderer, SDL_Texture * texture); 85static void METAL_DestroyRenderer(SDL_Renderer * renderer); 86static void *METAL_GetMetalLayer(SDL_Renderer * renderer); 87static void *METAL_GetMetalCommandEncoder(SDL_Renderer * renderer); 88 89SDL_RenderDriver METAL_RenderDriver = { 90 METAL_CreateRenderer, 91 { 92 "metal", 93 (SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC | SDL_RENDERER_TARGETTEXTURE), 94 6, 95 { 96 SDL_PIXELFORMAT_ARGB8888, 97 SDL_PIXELFORMAT_ABGR8888, 98 SDL_PIXELFORMAT_YV12, 99 SDL_PIXELFORMAT_IYUV, 100 SDL_PIXELFORMAT_NV12, 101 SDL_PIXELFORMAT_NV21 102 }, 103 0, 0, 104 } 105}; 106 107/* macOS requires constants in a buffer to have a 256 byte alignment. */ 108#ifdef __MACOSX__ 109#define CONSTANT_ALIGN 256 110#else 111#define CONSTANT_ALIGN 4 112#endif 113 114#define ALIGN_CONSTANTS(size) ((size + CONSTANT_ALIGN - 1) & (~(CONSTANT_ALIGN - 1))) 115 116static const size_t CONSTANTS_OFFSET_IDENTITY = 0; 117static const size_t CONSTANTS_OFFSET_HALF_PIXEL_TRANSFORM = ALIGN_CONSTANTS(CONSTANTS_OFFSET_IDENTITY + sizeof(float) * 16); 118static const size_t CONSTANTS_OFFSET_DECODE_JPEG = ALIGN_CONSTANTS(CONSTANTS_OFFSET_HALF_PIXEL_TRANSFORM + sizeof(float) * 16); 119static const size_t CONSTANTS_OFFSET_DECODE_BT601 = ALIGN_CONSTANTS(CONSTANTS_OFFSET_DECODE_JPEG + sizeof(float) * 4 * 4); 120static const size_t CONSTANTS_OFFSET_DECODE_BT709 = ALIGN_CONSTANTS(CONSTANTS_OFFSET_DECODE_BT601 + sizeof(float) * 4 * 4); 121static const size_t CONSTANTS_OFFSET_CLEAR_VERTS = ALIGN_CONSTANTS(CONSTANTS_OFFSET_DECODE_BT709 + sizeof(float) * 4 * 4); 122static const size_t CONSTANTS_LENGTH = CONSTANTS_OFFSET_CLEAR_VERTS + sizeof(float) * 6; 123 124typedef enum SDL_MetalVertexFunction 125{ 126 SDL_METAL_VERTEX_SOLID, 127 SDL_METAL_VERTEX_COPY, 128} SDL_MetalVertexFunction; 129 130typedef enum SDL_MetalFragmentFunction 131{ 132 SDL_METAL_FRAGMENT_SOLID = 0, 133 SDL_METAL_FRAGMENT_COPY, 134 SDL_METAL_FRAGMENT_YUV, 135 SDL_METAL_FRAGMENT_NV12, 136 SDL_METAL_FRAGMENT_NV21, 137 SDL_METAL_FRAGMENT_COUNT, 138} SDL_MetalFragmentFunction; 139 140typedef struct METAL_PipelineState 141{ 142 SDL_BlendMode blendMode; 143 void *pipe; 144} METAL_PipelineState; 145 146typedef struct METAL_PipelineCache 147{ 148 METAL_PipelineState *states; 149 int count; 150 SDL_MetalVertexFunction vertexFunction; 151 SDL_MetalFragmentFunction fragmentFunction; 152 MTLPixelFormat renderTargetFormat; 153 const char *label; 154} METAL_PipelineCache; 155 156/* Each shader combination used by drawing functions has a separate pipeline 157 * cache, and we have a separate list of caches for each render target pixel 158 * format. This is more efficient than iterating over a global cache to find 159 * the pipeline based on the specified shader combination and RT pixel format, 160 * since we know what the RT pixel format is when we set the render target, and 161 * we know what the shader combination is inside each drawing function's code. */ 162typedef struct METAL_ShaderPipelines 163{ 164 MTLPixelFormat renderTargetFormat; 165 METAL_PipelineCache caches[SDL_METAL_FRAGMENT_COUNT]; 166} METAL_ShaderPipelines; 167 168@interface METAL_RenderData : NSObject 169 @property (nonatomic, retain) id<MTLDevice> mtldevice; 170 @property (nonatomic, retain) id<MTLCommandQueue> mtlcmdqueue; 171 @property (nonatomic, retain) id<MTLCommandBuffer> mtlcmdbuffer; 172 @property (nonatomic, retain) id<MTLRenderCommandEncoder> mtlcmdencoder; 173 @property (nonatomic, retain) id<MTLLibrary> mtllibrary; 174 @property (nonatomic, retain) id<CAMetalDrawable> mtlbackbuffer; 175 @property (nonatomic, retain) id<MTLSamplerState> mtlsamplernearest; 176 @property (nonatomic, retain) id<MTLSamplerState> mtlsamplerlinear; 177 @property (nonatomic, retain) id<MTLBuffer> mtlbufconstants; 178 @property (nonatomic, retain) CAMetalLayer *mtllayer; 179 @property (nonatomic, retain) MTLRenderPassDescriptor *mtlpassdesc; 180 @property (nonatomic, assign) METAL_ShaderPipelines *activepipelines; 181 @property (nonatomic, assign) METAL_ShaderPipelines *allpipelines; 182 @property (nonatomic, assign) int pipelinescount; 183@end 184 185@implementation METAL_RenderData 186#if !__has_feature(objc_arc) 187- (void)dealloc 188{ 189 [_mtldevice release]; 190 [_mtlcmdqueue release]; 191 [_mtlcmdbuffer release]; 192 [_mtlcmdencoder release]; 193 [_mtllibrary release]; 194 [_mtlbackbuffer release]; 195 [_mtlsamplernearest release]; 196 [_mtlsamplerlinear release]; 197 [_mtlbufconstants release]; 198 [_mtllayer release]; 199 [_mtlpassdesc release]; 200 [super dealloc]; 201} 202#endif 203@end 204 205@interface METAL_TextureData : NSObject 206 @property (nonatomic, retain) id<MTLTexture> mtltexture; 207 @property (nonatomic, retain) id<MTLTexture> mtltexture_uv; 208 @property (nonatomic, retain) id<MTLSamplerState> mtlsampler; 209 @property (nonatomic, assign) SDL_MetalFragmentFunction fragmentFunction; 210 @property (nonatomic, assign) BOOL yuv; 211 @property (nonatomic, assign) BOOL nv12; 212 @property (nonatomic, assign) size_t conversionBufferOffset; 213@end 214 215@implementation METAL_TextureData 216#if !__has_feature(objc_arc) 217- (void)dealloc 218{ 219 [_mtltexture release]; 220 [_mtltexture_uv release]; 221 [_mtlsampler release]; 222 [super dealloc]; 223} 224#endif 225@end 226 227static int 228IsMetalAvailable(const SDL_SysWMinfo *syswm) 229{ 230 if (syswm->subsystem != SDL_SYSWM_COCOA && syswm->subsystem != SDL_SYSWM_UIKIT) { 231 return SDL_SetError("Metal render target only supports Cocoa and UIKit video targets at the moment."); 232 } 233 234 // this checks a weak symbol. 235#if (defined(__MACOSX__) && (MAC_OS_X_VERSION_MIN_REQUIRED < 101100)) 236 if (MTLCreateSystemDefaultDevice == NULL) { // probably on 10.10 or lower. 237 return SDL_SetError("Metal framework not available on this system"); 238 } 239#endif 240 241 return 0; 242} 243 244static const MTLBlendOperation invalidBlendOperation = (MTLBlendOperation)0xFFFFFFFF; 245static const MTLBlendFactor invalidBlendFactor = (MTLBlendFactor)0xFFFFFFFF; 246 247static MTLBlendOperation 248GetBlendOperation(SDL_BlendOperation operation) 249{ 250 switch (operation) { 251 case SDL_BLENDOPERATION_ADD: return MTLBlendOperationAdd; 252 case SDL_BLENDOPERATION_SUBTRACT: return MTLBlendOperationSubtract; 253 case SDL_BLENDOPERATION_REV_SUBTRACT: return MTLBlendOperationReverseSubtract; 254 case SDL_BLENDOPERATION_MINIMUM: return MTLBlendOperationMin; 255 case SDL_BLENDOPERATION_MAXIMUM: return MTLBlendOperationMax; 256 default: return invalidBlendOperation; 257 } 258} 259 260static MTLBlendFactor 261GetBlendFactor(SDL_BlendFactor factor) 262{ 263 switch (factor) { 264 case SDL_BLENDFACTOR_ZERO: return MTLBlendFactorZero; 265 case SDL_BLENDFACTOR_ONE: return MTLBlendFactorOne; 266 case SDL_BLENDFACTOR_SRC_COLOR: return MTLBlendFactorSourceColor; 267 case SDL_BLENDFACTOR_ONE_MINUS_SRC_COLOR: return MTLBlendFactorOneMinusSourceColor; 268 case SDL_BLENDFACTOR_SRC_ALPHA: return MTLBlendFactorSourceAlpha; 269 case SDL_BLENDFACTOR_ONE_MINUS_SRC_ALPHA: return MTLBlendFactorOneMinusSourceAlpha; 270 case SDL_BLENDFACTOR_DST_COLOR: return MTLBlendFactorDestinationColor; 271 case SDL_BLENDFACTOR_ONE_MINUS_DST_COLOR: return MTLBlendFactorOneMinusDestinationColor; 272 case SDL_BLENDFACTOR_DST_ALPHA: return MTLBlendFactorDestinationAlpha; 273 case SDL_BLENDFACTOR_ONE_MINUS_DST_ALPHA: return MTLBlendFactorOneMinusDestinationAlpha; 274 default: return invalidBlendFactor; 275 } 276} 277 278static NSString * 279GetVertexFunctionName(SDL_MetalVertexFunction function) 280{ 281 switch (function) { 282 case SDL_METAL_VERTEX_SOLID: return @"SDL_Solid_vertex"; 283 case SDL_METAL_VERTEX_COPY: return @"SDL_Copy_vertex"; 284 default: return nil; 285 } 286} 287 288static NSString * 289GetFragmentFunctionName(SDL_MetalFragmentFunction function) 290{ 291 switch (function) { 292 case SDL_METAL_FRAGMENT_SOLID: return @"SDL_Solid_fragment"; 293 case SDL_METAL_FRAGMENT_COPY: return @"SDL_Copy_fragment"; 294 case SDL_METAL_FRAGMENT_YUV: return @"SDL_YUV_fragment"; 295 case SDL_METAL_FRAGMENT_NV12: return @"SDL_NV12_fragment"; 296 case SDL_METAL_FRAGMENT_NV21: return @"SDL_NV21_fragment"; 297 default: return nil; 298 } 299} 300 301static id<MTLRenderPipelineState> 302MakePipelineState(METAL_RenderData *data, METAL_PipelineCache *cache, 303 NSString *blendlabel, SDL_BlendMode blendmode) 304{ 305 id<MTLFunction> mtlvertfn = [data.mtllibrary newFunctionWithName:GetVertexFunctionName(cache->vertexFunction)]; 306 id<MTLFunction> mtlfragfn = [data.mtllibrary newFunctionWithName:GetFragmentFunctionName(cache->fragmentFunction)]; 307 SDL_assert(mtlvertfn != nil); 308 SDL_assert(mtlfragfn != nil); 309 310 MTLRenderPipelineDescriptor *mtlpipedesc = [[MTLRenderPipelineDescriptor alloc] init]; 311 mtlpipedesc.vertexFunction = mtlvertfn; 312 mtlpipedesc.fragmentFunction = mtlfragfn; 313 314 MTLRenderPipelineColorAttachmentDescriptor *rtdesc = mtlpipedesc.colorAttachments[0]; 315 316 rtdesc.pixelFormat = cache->renderTargetFormat; 317 318 if (blendmode != SDL_BLENDMODE_NONE) { 319 rtdesc.blendingEnabled = YES; 320 rtdesc.sourceRGBBlendFactor = GetBlendFactor(SDL_GetBlendModeSrcColorFactor(blendmode)); 321 rtdesc.destinationRGBBlendFactor = GetBlendFactor(SDL_GetBlendModeDstColorFactor(blendmode)); 322 rtdesc.rgbBlendOperation = GetBlendOperation(SDL_GetBlendModeColorOperation(blendmode)); 323 rtdesc.sourceAlphaBlendFactor = GetBlendFactor(SDL_GetBlendModeSrcAlphaFactor(blendmode)); 324 rtdesc.destinationAlphaBlendFactor = GetBlendFactor(SDL_GetBlendModeDstAlphaFactor(blendmode)); 325 rtdesc.alphaBlendOperation = GetBlendOperation(SDL_GetBlendModeAlphaOperation(blendmode)); 326 } else { 327 rtdesc.blendingEnabled = NO; 328 } 329 330 mtlpipedesc.label = [@(cache->label) stringByAppendingString:blendlabel]; 331 332 NSError *err = nil; 333 id<MTLRenderPipelineState> state = [data.mtldevice newRenderPipelineStateWithDescriptor:mtlpipedesc error:&err]; 334 SDL_assert(err == nil); 335 336 METAL_PipelineState pipeline; 337 pipeline.blendMode = blendmode; 338 pipeline.pipe = (void *)CFBridgingRetain(state); 339 340 METAL_PipelineState *states = SDL_realloc(cache->states, (cache->count + 1) * sizeof(pipeline)); 341 342#if !__has_feature(objc_arc) 343 [mtlpipedesc release]; // !!! FIXME: can these be reused for each creation, or does the pipeline obtain it? 344 [mtlvertfn release]; 345 [mtlfragfn release]; 346 [state release]; 347#endif 348 349 if (states) { 350 states[cache->count++] = pipeline; 351 cache->states = states; 352 return (__bridge id<MTLRenderPipelineState>)pipeline.pipe; 353 } else { 354 CFBridgingRelease(pipeline.pipe); 355 SDL_OutOfMemory(); 356 return NULL; 357 } 358} 359 360static void 361MakePipelineCache(METAL_RenderData *data, METAL_PipelineCache *cache, const char *label, 362 MTLPixelFormat rtformat, SDL_MetalVertexFunction vertfn, SDL_MetalFragmentFunction fragfn) 363{ 364 SDL_zerop(cache); 365 366 cache->vertexFunction = vertfn; 367 cache->fragmentFunction = fragfn; 368 cache->renderTargetFormat = rtformat; 369 cache->label = label; 370 371 /* Create pipeline states for the default blend modes. Custom blend modes 372 * will be added to the cache on-demand. */ 373 MakePipelineState(data, cache, @" (blend=none)", SDL_BLENDMODE_NONE); 374 MakePipelineState(data, cache, @" (blend=blend)", SDL_BLENDMODE_BLEND); 375 MakePipelineState(data, cache, @" (blend=add)", SDL_BLENDMODE_ADD); 376 MakePipelineState(data, cache, @" (blend=mod)", SDL_BLENDMODE_MOD); 377} 378 379static void 380DestroyPipelineCache(METAL_PipelineCache *cache) 381{ 382 if (cache != NULL) { 383 for (int i = 0; i < cache->count; i++) { 384 CFBridgingRelease(cache->states[i].pipe); 385 } 386 387 SDL_free(cache->states); 388 } 389} 390 391void 392MakeShaderPipelines(METAL_RenderData *data, METAL_ShaderPipelines *pipelines, MTLPixelFormat rtformat) 393{ 394 SDL_zerop(pipelines); 395 396 pipelines->renderTargetFormat = rtformat; 397 398 MakePipelineCache(data, &pipelines->caches[SDL_METAL_FRAGMENT_SOLID], "SDL primitives pipeline", rtformat, SDL_METAL_VERTEX_SOLID, SDL_METAL_FRAGMENT_SOLID); 399 MakePipelineCache(data, &pipelines->caches[SDL_METAL_FRAGMENT_COPY], "SDL copy pipeline", rtformat, SDL_METAL_VERTEX_COPY, SDL_METAL_FRAGMENT_COPY); 400 MakePipelineCache(data, &pipelines->caches[SDL_METAL_FRAGMENT_YUV], "SDL YUV pipeline", rtformat, SDL_METAL_VERTEX_COPY, SDL_METAL_FRAGMENT_YUV); 401 MakePipelineCache(data, &pipelines->caches[SDL_METAL_FRAGMENT_NV12], "SDL NV12 pipeline", rtformat, SDL_METAL_VERTEX_COPY, SDL_METAL_FRAGMENT_NV12); 402 MakePipelineCache(data, &pipelines->caches[SDL_METAL_FRAGMENT_NV21], "SDL NV21 pipeline", rtformat, SDL_METAL_VERTEX_COPY, SDL_METAL_FRAGMENT_NV21); 403} 404 405static METAL_ShaderPipelines * 406ChooseShaderPipelines(METAL_RenderData *data, MTLPixelFormat rtformat) 407{ 408 METAL_ShaderPipelines *allpipelines = data.allpipelines; 409 int count = data.pipelinescount; 410 411 for (int i = 0; i < count; i++) { 412 if (allpipelines[i].renderTargetFormat == rtformat) { 413 return &allpipelines[i]; 414 } 415 } 416 417 allpipelines = SDL_realloc(allpipelines, (count + 1) * sizeof(METAL_ShaderPipelines)); 418 419 if (allpipelines == NULL) { 420 SDL_OutOfMemory(); 421 return NULL; 422 } 423 424 MakeShaderPipelines(data, &allpipelines[count], rtformat); 425 426 data.allpipelines = allpipelines; 427 data.pipelinescount = count + 1; 428 429 return &data.allpipelines[count]; 430} 431 432static void 433DestroyAllPipelines(METAL_ShaderPipelines *allpipelines, int count) 434{ 435 if (allpipelines != NULL) { 436 for (int i = 0; i < count; i++) { 437 for (int cache = 0; cache < SDL_METAL_FRAGMENT_COUNT; cache++) { 438 DestroyPipelineCache(&allpipelines[i].caches[cache]); 439 } 440 } 441 442 SDL_free(allpipelines); 443 } 444} 445 446static inline id<MTLRenderPipelineState> 447ChoosePipelineState(METAL_RenderData *data, METAL_ShaderPipelines *pipelines, SDL_MetalFragmentFunction fragfn, SDL_BlendMode blendmode) 448{ 449 METAL_PipelineCache *cache = &pipelines->caches[fragfn]; 450 451 for (int i = 0; i < cache->count; i++) { 452 if (cache->states[i].blendMode == blendmode) { 453 return (__bridge id<MTLRenderPipelineState>)cache->states[i].pipe; 454 } 455 } 456 457 return MakePipelineState(data, cache, [NSString stringWithFormat:@" (blend=custom 0x%x)", blendmode], blendmode); 458} 459 460static SDL_Renderer * 461METAL_CreateRenderer(SDL_Window * window, Uint32 flags) 462{ @autoreleasepool { 463 SDL_Renderer *renderer = NULL; 464 METAL_RenderData *data = NULL; 465 id<MTLDevice> mtldevice = nil; 466 SDL_SysWMinfo syswm; 467 468 SDL_VERSION(&syswm.version); 469 if (!SDL_GetWindowWMInfo(window, &syswm)) { 470 return NULL; 471 } 472 473 if (IsMetalAvailable(&syswm) == -1) { 474 return NULL; 475 } 476 477 renderer = (SDL_Renderer *) SDL_calloc(1, sizeof(*renderer)); 478 if (!renderer) { 479 SDL_OutOfMemory(); 480 return NULL; 481 } 482 483 // !!! FIXME: MTLCopyAllDevices() can find other GPUs on macOS... 484 mtldevice = MTLCreateSystemDefaultDevice(); 485 486 if (mtldevice == nil) { 487 SDL_free(renderer); 488 SDL_SetError("Failed to obtain Metal device"); 489 return NULL; 490 } 491 492 // !!! FIXME: error checking on all of this. 493 data = [[METAL_RenderData alloc] init]; 494 495 renderer->driverdata = (void*)CFBridgingRetain(data); 496 renderer->window = window; 497 498#ifdef __MACOSX__ 499 NSView *view = Cocoa_Mtl_AddMetalView(window); 500 CAMetalLayer *layer = (CAMetalLayer *)[view layer]; 501 502 layer.device = mtldevice; 503 504 //layer.colorspace = nil; 505 506#else 507 UIView *view = UIKit_Mtl_AddMetalView(window); 508 CAMetalLayer *layer = (CAMetalLayer *)[view layer]; 509#endif 510 511 // Necessary for RenderReadPixels. 512 layer.framebufferOnly = NO; 513 514 data.mtldevice = layer.device; 515 data.mtllayer = layer; 516 id<MTLCommandQueue> mtlcmdqueue = [data.mtldevice newCommandQueue]; 517 data.mtlcmdqueue = mtlcmdqueue; 518 data.mtlcmdqueue.label = @"SDL Metal Renderer"; 519 data.mtlpassdesc = [MTLRenderPassDescriptor renderPassDescriptor]; 520 521 NSError *err = nil; 522 523 // The compiled .metallib is embedded in a static array in a header file 524 // but the original shader source code is in SDL_shaders_metal.metal. 525 dispatch_data_t mtllibdata = dispatch_data_create(sdl_metallib, sdl_metallib_len, dispatch_get_global_queue(0, 0), ^{}); 526 id<MTLLibrary> mtllibrary = [data.mtldevice newLibraryWithData:mtllibdata error:&err]; 527 data.mtllibrary = mtllibrary; 528 SDL_assert(err == nil); 529#if !__has_feature(objc_arc) 530 dispatch_release(mtllibdata); 531#endif 532 data.mtllibrary.label = @"SDL Metal renderer shader library"; 533 534 /* Do some shader pipeline state loading up-front rather than on demand. */ 535 data.pipelinescount = 0; 536 data.allpipelines = NULL; 537 ChooseShaderPipelines(data, MTLPixelFormatBGRA8Unorm); 538 539 MTLSamplerDescriptor *samplerdesc = [[MTLSamplerDescriptor alloc] init]; 540 541 samplerdesc.minFilter = MTLSamplerMinMagFilterNearest; 542 samplerdesc.magFilter = MTLSamplerMinMagFilterNearest; 543 id<MTLSamplerState> mtlsamplernearest = [data.mtldevice newSamplerStateWithDescriptor:samplerdesc]; 544 data.mtlsamplernearest = mtlsamplernearest; 545 546 samplerdesc.minFilter = MTLSamplerMinMagFilterLinear; 547 samplerdesc.magFilter = MTLSamplerMinMagFilterLinear; 548 id<MTLSamplerState> mtlsamplerlinear = [data.mtldevice newSamplerStateWithDescriptor:samplerdesc]; 549 data.mtlsamplerlinear = mtlsamplerlinear; 550 551 /* Note: matrices are column major. */ 552 float identitytransform[16] = { 553 1.0f, 0.0f, 0.0f, 0.0f, 554 0.0f, 1.0f, 0.0f, 0.0f, 555 0.0f, 0.0f, 1.0f, 0.0f, 556 0.0f, 0.0f, 0.0f, 1.0f, 557 }; 558 559 float halfpixeltransform[16] = { 560 1.0f, 0.0f, 0.0f, 0.0f, 561 0.0f, 1.0f, 0.0f, 0.0f, 562 0.0f, 0.0f, 1.0f, 0.0f, 563 0.5f, 0.5f, 0.0f, 1.0f, 564 }; 565 566 /* Metal pads float3s to 16 bytes. */ 567 float decodetransformJPEG[4*4] = { 568 0.0, -0.501960814, -0.501960814, 0.0, /* offset */ 569 1.0000, 0.0000, 1.4020, 0.0, /* Rcoeff */ 570 1.0000, -0.3441, -0.7141, 0.0, /* Gcoeff */ 571 1.0000, 1.7720, 0.0000, 0.0, /* Bcoeff */ 572 }; 573 574 float decodetransformBT601[4*4] = { 575 -0.0627451017, -0.501960814, -0.501960814, 0.0, /* offset */ 576 1.1644, 0.0000, 1.5960, 0.0, /* Rcoeff */ 577 1.1644, -0.3918, -0.8130, 0.0, /* Gcoeff */ 578 1.1644, 2.0172, 0.0000, 0.0, /* Bcoeff */ 579 }; 580 581 float decodetransformBT709[4*4] = { 582 0.0, -0.501960814, -0.501960814, 0.0, /* offset */ 583 1.0000, 0.0000, 1.4020, 0.0, /* Rcoeff */ 584 1.0000, -0.3441, -0.7141, 0.0, /* Gcoeff */ 585 1.0000, 1.7720, 0.0000, 0.0, /* Bcoeff */ 586 }; 587 588 float clearverts[6] = {0.0f, 0.0f, 0.0f, 2.0f, 2.0f, 0.0f}; 589 590 id<MTLBuffer> mtlbufconstantstaging = [data.mtldevice newBufferWithLength:CONSTANTS_LENGTH options:MTLResourceStorageModeShared]; 591 mtlbufconstantstaging.label = @"SDL constant staging data"; 592 593 id<MTLBuffer> mtlbufconstants = [data.mtldevice newBufferWithLength:CONSTANTS_LENGTH options:MTLResourceStorageModePrivate]; 594 data.mtlbufconstants = mtlbufconstants; 595 data.mtlbufconstants.label = @"SDL constant data"; 596 597 char *constantdata = [mtlbufconstantstaging contents]; 598 SDL_memcpy(constantdata + CONSTANTS_OFFSET_IDENTITY, identitytransform, sizeof(identitytransform)); 599 SDL_memcpy(constantdata + CONSTANTS_OFFSET_HALF_PIXEL_TRANSFORM, halfpixeltransform, sizeof(halfpixeltransform)); 600 SDL_memcpy(constantdata + CONSTANTS_OFFSET_DECODE_JPEG, decodetransformJPEG, sizeof(decodetransformJPEG)); 601 SDL_memcpy(constantdata + CONSTANTS_OFFSET_DECODE_BT601, decodetransformBT601, sizeof(decodetransformBT601)); 602 SDL_memcpy(constantdata + CONSTANTS_OFFSET_DECODE_BT709, decodetransformBT709, sizeof(decodetransformBT709)); 603 SDL_memcpy(constantdata + CONSTANTS_OFFSET_CLEAR_VERTS, clearverts, sizeof(clearverts)); 604 605 id<MTLCommandBuffer> cmdbuffer = [data.mtlcmdqueue commandBuffer]; 606 id<MTLBlitCommandEncoder> blitcmd = [cmdbuffer blitCommandEncoder]; 607 608 [blitcmd copyFromBuffer:mtlbufconstantstaging sourceOffset:0 toBuffer:data.mtlbufconstants destinationOffset:0 size:CONSTANTS_LENGTH]; 609 610 [blitcmd endEncoding]; 611 [cmdbuffer commit]; 612 613 // !!! FIXME: force more clears here so all the drawables are sane to start, and our static buffers are definitely flushed. 614 615 renderer->WindowEvent = METAL_WindowEvent; 616 renderer->GetOutputSize = METAL_GetOutputSize; 617 renderer->SupportsBlendMode = METAL_SupportsBlendMode; 618 renderer->CreateTexture = METAL_CreateTexture; 619 renderer->UpdateTexture = METAL_UpdateTexture; 620 renderer->UpdateTextureYUV = METAL_UpdateTextureYUV; 621 renderer->LockTexture = METAL_LockTexture; 622 renderer->UnlockTexture = METAL_UnlockTexture; 623 renderer->SetRenderTarget = METAL_SetRenderTarget; 624 renderer->UpdateViewport = METAL_UpdateViewport; 625 renderer->UpdateClipRect = METAL_UpdateClipRect; 626 renderer->RenderClear = METAL_RenderClear; 627 renderer->RenderDrawPoints = METAL_RenderDrawPoints; 628 renderer->RenderDrawLines = METAL_RenderDrawLines; 629 renderer->RenderFillRects = METAL_RenderFillRects; 630 renderer->RenderCopy = METAL_RenderCopy; 631 renderer->RenderCopyEx = METAL_RenderCopyEx; 632 renderer->RenderReadPixels = METAL_RenderReadPixels; 633 renderer->RenderPresent = METAL_RenderPresent; 634 renderer->DestroyTexture = METAL_DestroyTexture; 635 renderer->DestroyRenderer = METAL_DestroyRenderer; 636 renderer->GetMetalLayer = METAL_GetMetalLayer; 637 renderer->GetMetalCommandEncoder = METAL_GetMetalCommandEncoder; 638 639 renderer->info = METAL_RenderDriver.info; 640 renderer->info.flags = (SDL_RENDERER_ACCELERATED | SDL_RENDERER_TARGETTEXTURE); 641 642#if defined(__MACOSX__) && defined(MAC_OS_X_VERSION_10_13) 643 if (@available(macOS 10.13, *)) { 644 data.mtllayer.displaySyncEnabled = (flags & SDL_RENDERER_PRESENTVSYNC) != 0; 645 } else 646#endif 647 { 648 renderer->info.flags |= SDL_RENDERER_PRESENTVSYNC; 649 } 650 651 /* https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf */ 652 int maxtexsize = 4096; 653#if defined(__MACOSX__) 654 maxtexsize = 16384; 655#elif defined(__TVOS__) 656 maxtexsize = 8192; 657#ifdef __TVOS_11_0 658 if (@available(tvOS 11.0, *)) { 659 if ([mtldevice supportsFeatureSet:MTLFeatureSet_tvOS_GPUFamily2_v1]) { 660 maxtexsize = 16384; 661 } 662 } 663#endif 664#else 665#ifdef __IPHONE_11_0 666 if ([mtldevice supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily4_v1]) { 667 maxtexsize = 16384; 668 } else 669#endif 670#ifdef __IPHONE_10_0 671 if ([mtldevice supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily3_v1]) { 672 maxtexsize = 16384; 673 } else 674#endif 675 if ([mtldevice supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily2_v2] || [mtldevice supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily1_v2]) { 676 maxtexsize = 8192; 677 } else { 678 maxtexsize = 4096; 679 } 680#endif 681 682 renderer->info.max_texture_width = maxtexsize; 683 renderer->info.max_texture_height = maxtexsize; 684 685#if !__has_feature(objc_arc) 686 [mtlcmdqueue release]; 687 [mtllibrary release]; 688 [samplerdesc release]; 689 [mtlsamplernearest release]; 690 [mtlsamplerlinear release]; 691 [mtlbufconstants release]; 692 [view release]; 693 [data release]; 694 [mtldevice release]; 695#endif 696 697 return renderer; 698}} 699 700static void 701METAL_ActivateRenderCommandEncoder(SDL_Renderer * renderer, MTLLoadAction load) 702{ 703 METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata; 704 705 /* Our SetRenderTarget just signals that the next render operation should 706 * set up a new render pass. This is where that work happens. */ 707 if (data.mtlcmdencoder == nil) { 708 id<MTLTexture> mtltexture = nil; 709 710 if (renderer->target != NULL) { 711 METAL_TextureData *texdata = (__bridge METAL_TextureData *)renderer->target->driverdata; 712 mtltexture = texdata.mtltexture; 713 } else { 714 if (data.mtlbackbuffer == nil) { 715 /* The backbuffer's contents aren't guaranteed to persist after 716 * presenting, so we can leave it undefined when loading it. */ 717 data.mtlbackbuffer = [data.mtllayer nextDrawable]; 718 if (load == MTLLoadActionLoad) { 719 load = MTLLoadActionDontCare; 720 } 721 } 722 mtltexture = data.mtlbackbuffer.texture; 723 } 724 725 SDL_assert(mtltexture); 726 727 if (load == MTLLoadActionClear) { 728 MTLClearColor color = MTLClearColorMake(renderer->r/255.0, renderer->g/255.0, renderer->b/255.0, renderer->a/255.0); 729 data.mtlpassdesc.colorAttachments[0].clearColor = color; 730 } 731 732 data.mtlpassdesc.colorAttachments[0].loadAction = load; 733 data.mtlpassdesc.colorAttachments[0].texture = mtltexture; 734 735 data.mtlcmdbuffer = [data.mtlcmdqueue commandBuffer]; 736 data.mtlcmdencoder = [data.mtlcmdbuffer renderCommandEncoderWithDescriptor:data.mtlpassdesc]; 737 738 if (data.mtlbackbuffer != nil && mtltexture == data.mtlbackbuffer.texture) { 739 data.mtlcmdencoder.label = @"SDL metal renderer backbuffer"; 740 } else { 741 data.mtlcmdencoder.label = @"SDL metal renderer render target"; 742 } 743 744 data.activepipelines = ChooseShaderPipelines(data, mtltexture.pixelFormat); 745 746 /* Make sure the viewport and clip rect are set on the new render pass. */ 747 METAL_UpdateViewport(renderer); 748 METAL_UpdateClipRect(renderer); 749 } 750} 751 752static void 753METAL_WindowEvent(SDL_Renderer * renderer, const SDL_WindowEvent *event) 754{ 755 if (event->event == SDL_WINDOWEVENT_SIZE_CHANGED || 756 event->event == SDL_WINDOWEVENT_SHOWN || 757 event->event == SDL_WINDOWEVENT_HIDDEN) { 758 // !!! FIXME: write me 759 } 760} 761 762static int 763METAL_GetOutputSize(SDL_Renderer * renderer, int *w, int *h) 764{ @autoreleasepool { 765 METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata; 766 if (w) { 767 *w = (int)data.mtllayer.drawableSize.width; 768 } 769 if (h) { 770 *h = (int)data.mtllayer.drawableSize.height; 771 } 772 return 0; 773}} 774 775static SDL_bool 776METAL_SupportsBlendMode(SDL_Renderer * renderer, SDL_BlendMode blendMode) 777{ 778 SDL_BlendFactor srcColorFactor = SDL_GetBlendModeSrcColorFactor(blendMode); 779 SDL_BlendFactor srcAlphaFactor = SDL_GetBlendModeSrcAlphaFactor(blendMode); 780 SDL_BlendOperation colorOperation = SDL_GetBlendModeColorOperation(blendMode); 781 SDL_BlendFactor dstColorFactor = SDL_GetBlendModeDstColorFactor(blendMode); 782 SDL_BlendFactor dstAlphaFactor = SDL_GetBlendModeDstAlphaFactor(blendMode); 783 SDL_BlendOperation alphaOperation = SDL_GetBlendModeAlphaOperation(blendMode); 784 785 if (GetBlendFactor(srcColorFactor) == invalidBlendFactor || 786 GetBlendFactor(srcAlphaFactor) == invalidBlendFactor || 787 GetBlendOperation(colorOperation) == invalidBlendOperation || 788 GetBlendFactor(dstColorFactor) == invalidBlendFactor || 789 GetBlendFactor(dstAlphaFactor) == invalidBlendFactor || 790 GetBlendOperation(alphaOperation) == invalidBlendOperation) { 791 return SDL_FALSE; 792 } 793 return SDL_TRUE; 794} 795 796static int 797METAL_CreateTexture(SDL_Renderer * renderer, SDL_Texture * texture) 798{ @autoreleasepool { 799 METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata; 800 MTLPixelFormat pixfmt; 801 802 switch (texture->format) { 803 case SDL_PIXELFORMAT_ABGR8888: 804 pixfmt = MTLPixelFormatRGBA8Unorm; 805 break; 806 case SDL_PIXELFORMAT_ARGB8888: 807 pixfmt = MTLPixelFormatBGRA8Unorm; 808 break; 809 case SDL_PIXELFORMAT_IYUV: 810 case SDL_PIXELFORMAT_YV12: 811 case SDL_PIXELFORMAT_NV12: 812 case SDL_PIXELFORMAT_NV21: 813 pixfmt = MTLPixelFormatR8Unorm; 814 break; 815 default: 816 return SDL_SetError("Texture format %s not supported by Metal", SDL_GetPixelFormatName(texture->format)); 817 } 818 819 MTLTextureDescriptor *mtltexdesc = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:pixfmt 820 width:(NSUInteger)texture->w height:(NSUInteger)texture->h mipmapped:NO]; 821 822 /* Not available in iOS 8. */ 823 if ([mtltexdesc respondsToSelector:@selector(usage)]) { 824 if (texture->access == SDL_TEXTUREACCESS_TARGET) { 825 mtltexdesc.usage = MTLTextureUsageShaderRead | MTLTextureUsageRenderTarget; 826 } else { 827 mtltexdesc.usage = MTLTextureUsageShaderRead; 828 } 829 } 830 831 id<MTLTexture> mtltexture = [data.mtldevice newTextureWithDescriptor:mtltexdesc]; 832 if (mtltexture == nil) { 833 return SDL_SetError("Texture allocation failed"); 834 } 835 836 id<MTLTexture> mtltexture_uv = nil; 837 838 BOOL yuv = (texture->format == SDL_PIXELFORMAT_IYUV) || (texture->format == SDL_PIXELFORMAT_YV12); 839 BOOL nv12 = (texture->format == SDL_PIXELFORMAT_NV12) || (texture->format == SDL_PIXELFORMAT_NV21); 840 841 if (yuv) { 842 mtltexdesc.pixelFormat = MTLPixelFormatR8Unorm; 843 mtltexdesc.width = (texture->w + 1) / 2; 844 mtltexdesc.height = (texture->h + 1) / 2; 845 mtltexdesc.textureType = MTLTextureType2DArray; 846 mtltexdesc.arrayLength = 2; 847 mtltexture_uv = [data.mtldevice newTextureWithDescriptor:mtltexdesc]; 848 } else if (nv12) { 849 mtltexdesc.pixelFormat = MTLPixelFormatRG8Unorm; 850 mtltexdesc.width = (texture->w + 1) / 2; 851 mtltexdesc.height = (texture->h + 1) / 2; 852 mtltexture_uv = [data.mtldevice newTextureWithDescriptor:mtltexdesc]; 853 } 854 855 METAL_TextureData *texturedata = [[METAL_TextureData alloc] init]; 856 if (texture->scaleMode == SDL_ScaleModeNearest) { 857 texturedata.mtlsampler = data.mtlsamplernearest; 858 } else { 859 texturedata.mtlsampler = data.mtlsamplerlinear; 860 } 861 texturedata.mtltexture = mtltexture; 862 texturedata.mtltexture_uv = mtltexture_uv; 863 864 texturedata.yuv = yuv; 865 texturedata.nv12 = nv12; 866 867 if (yuv) { 868 texturedata.fragmentFunction = SDL_METAL_FRAGMENT_YUV; 869 } else if (texture->format == SDL_PIXELFORMAT_NV12) { 870 texturedata.fragmentFunction = SDL_METAL_FRAGMENT_NV12; 871 } else if (texture->format == SDL_PIXELFORMAT_NV21) { 872 texturedata.fragmentFunction = SDL_METAL_FRAGMENT_NV21; 873 } else { 874 texturedata.fragmentFunction = SDL_METAL_FRAGMENT_COPY; 875 } 876 877 if (yuv || nv12) { 878 size_t offset = 0; 879 SDL_YUV_CONVERSION_MODE mode = SDL_GetYUVConversionModeForResolution(texture->w, texture->h); 880 switch (mode) { 881 case SDL_YUV_CONVERSION_JPEG: offset = CONSTANTS_OFFSET_DECODE_JPEG; break; 882 case SDL_YUV_CONVERSION_BT601: offset = CONSTANTS_OFFSET_DECODE_BT601; break; 883 case SDL_YUV_CONVERSION_BT709: offset = CONSTANTS_OFFSET_DECODE_BT709; break; 884 default: offset = 0; break; 885 } 886 texturedata.conversionBufferOffset = offset; 887 } 888 889 texture->driverdata = (void*)CFBridgingRetain(texturedata); 890 891#if !__has_feature(objc_arc) 892 [texturedata release]; 893 [mtltexture release]; 894 [mtltexture_uv release]; 895#endif 896 897 return 0; 898}} 899 900static int 901METAL_UpdateTexture(SDL_Renderer * renderer, SDL_Texture * texture, 902 const SDL_Rect * rect, const void *pixels, int pitch) 903{ @autoreleasepool { 904 METAL_TextureData *texturedata = (__bridge METAL_TextureData *)texture->driverdata; 905 906 /* !!! FIXME: replaceRegion does not do any synchronization, so it might 907 * !!! FIXME: stomp on a previous frame's data that's currently being read 908 * !!! FIXME: by the GPU. */ 909 [texturedata.mtltexture replaceRegion:MTLRegionMake2D(rect->x, rect->y, rect->w, rect->h) 910 mipmapLevel:0 911 withBytes:pixels 912 bytesPerRow:pitch]; 913 914 if (texturedata.yuv) { 915 int Uslice = texture->format == SDL_PIXELFORMAT_YV12 ? 1 : 0; 916 int Vslice = texture->format == SDL_PIXELFORMAT_YV12 ? 0 : 1; 917 918 /* Skip to the correct offset into the next texture */ 919 pixels = (const void*)((const Uint8*)pixels + rect->h * pitch); 920 [texturedata.mtltexture_uv replaceRegion:MTLRegionMake2D(rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2) 921 mipmapLevel:0 922 slice:Uslice 923 withBytes:pixels 924 bytesPerRow:(pitch + 1) / 2 925 bytesPerImage:0]; 926 927 /* Skip to the correct offset into the next texture */ 928 pixels = (const void*)((const Uint8*)pixels + ((rect->h + 1) / 2) * ((pitch + 1)/2)); 929 [texturedata.mtltexture_uv replaceRegion:MTLRegionMake2D(rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2) 930 mipmapLevel:0 931 slice:Vslice 932 withBytes:pixels 933 bytesPerRow:(pitch + 1) / 2 934 bytesPerImage:0]; 935 } 936 937 if (texturedata.nv12) { 938 /* Skip to the correct offset into the next texture */ 939 pixels = (const void*)((const Uint8*)pixels + rect->h * pitch); 940 [texturedata.mtltexture_uv replaceRegion:MTLRegionMake2D(rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2) 941 mipmapLevel:0 942 slice:0 943 withBytes:pixels 944 bytesPerRow:2 * ((pitch + 1) / 2) 945 bytesPerImage:0]; 946 } 947 948 return 0; 949}} 950 951static int 952METAL_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{ @autoreleasepool { 958 METAL_TextureData *texturedata = (__bridge METAL_TextureData *)texture->driverdata; 959 int Uslice = texture->format == SDL_PIXELFORMAT_YV12 ? 1 : 0; 960 int Vslice = texture->format == SDL_PIXELFORMAT_YV12 ? 0 : 1; 961 962 /* Bail out if we're supposed to update an empty rectangle */ 963 if (rect->w <= 0 || rect->h <= 0) { 964 return 0; 965 } 966 967 [texturedata.mtltexture replaceRegion:MTLRegionMake2D(rect->x, rect->y, rect->w, rect->h) 968 mipmapLevel:0 969 withBytes:Yplane 970 bytesPerRow:Ypitch]; 971 972 [texturedata.mtltexture_uv replaceRegion:MTLRegionMake2D(rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2) 973 mipmapLevel:0 974 slice:Uslice 975 withBytes:Uplane 976 bytesPerRow:Upitch 977 bytesPerImage:0]; 978 979 [texturedata.mtltexture_uv replaceRegion:MTLRegionMake2D(rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2) 980 mipmapLevel:0 981 slice:Vslice 982 withBytes:Vplane 983 bytesPerRow:Vpitch 984 bytesPerImage:0]; 985 986 return 0; 987}} 988 989static int 990METAL_LockTexture(SDL_Renderer * renderer, SDL_Texture * texture, 991 const SDL_Rect * rect, void **pixels, int *pitch) 992{ 993 return SDL_Unsupported(); // !!! FIXME: write me 994} 995 996static void 997METAL_UnlockTexture(SDL_Renderer * renderer, SDL_Texture * texture) 998{ 999 // !!! FIXME: write me 1000} 1001 1002static int 1003METAL_SetRenderTarget(SDL_Renderer * renderer, SDL_Texture * texture) 1004{ @autoreleasepool { 1005 METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata; 1006 1007 if (data.mtlcmdencoder) { 1008 /* End encoding for the previous render target so we can set up a new 1009 * render pass for this one. */ 1010 [data.mtlcmdencoder endEncoding]; 1011 [data.mtlcmdbuffer commit]; 1012 1013 data.mtlcmdencoder = nil; 1014 data.mtlcmdbuffer = nil; 1015 } 1016 1017 /* We don't begin a new render pass right away - we delay it until an actual 1018 * draw or clear happens. That way we can use hardware clears when possible, 1019 * which are only available when beginning a new render pass. */ 1020 return 0; 1021}} 1022 1023static int 1024METAL_SetOrthographicProjection(SDL_Renderer *renderer, int w, int h) 1025{ @autoreleasepool { 1026 METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata; 1027 float projection[4][4]; 1028 1029 if (!w || !h) { 1030 return 0; 1031 } 1032 1033 /* Prepare an orthographic projection */ 1034 projection[0][0] = 2.0f / w; 1035 projection[0][1] = 0.0f; 1036 projection[0][2] = 0.0f; 1037 projection[0][3] = 0.0f; 1038 projection[1][0] = 0.0f; 1039 projection[1][1] = -2.0f / h; 1040 projection[1][2] = 0.0f; 1041 projection[1][3] = 0.0f; 1042 projection[2][0] = 0.0f; 1043 projection[2][1] = 0.0f; 1044 projection[2][2] = 0.0f; 1045 projection[2][3] = 0.0f; 1046 projection[3][0] = -1.0f; 1047 projection[3][1] = 1.0f; 1048 projection[3][2] = 0.0f; 1049 projection[3][3] = 1.0f; 1050 1051 // !!! FIXME: This should be in a buffer... 1052 [data.mtlcmdencoder setVertexBytes:projection length:sizeof(float)*16 atIndex:2]; 1053 return 0; 1054}} 1055 1056static int 1057METAL_UpdateViewport(SDL_Renderer * renderer) 1058{ @autoreleasepool { 1059 METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata; 1060 if (data.mtlcmdencoder) { 1061 MTLViewport viewport; 1062 viewport.originX = renderer->viewport.x; 1063 viewport.originY = renderer->viewport.y; 1064 viewport.width = renderer->viewport.w; 1065 viewport.height = renderer->viewport.h; 1066 viewport.znear = 0.0; 1067 viewport.zfar = 1.0; 1068 [data.mtlcmdencoder setViewport:viewport]; 1069 METAL_SetOrthographicProjection(renderer, renderer->viewport.w, renderer->viewport.h); 1070 } 1071 return 0; 1072}} 1073 1074static int 1075METAL_UpdateClipRect(SDL_Renderer * renderer) 1076{ @autoreleasepool { 1077 METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata; 1078 if (data.mtlcmdencoder) { 1079 MTLScissorRect mtlrect; 1080 // !!! FIXME: should this care about the viewport? 1081 if (renderer->clipping_enabled) { 1082 const SDL_Rect *rect = &renderer->clip_rect; 1083 mtlrect.x = renderer->viewport.x + rect->x; 1084 mtlrect.y = renderer->viewport.x + rect->y; 1085 mtlrect.width = rect->w; 1086 mtlrect.height = rect->h; 1087 } else { 1088 mtlrect.x = renderer->viewport.x; 1089 mtlrect.y = renderer->viewport.y; 1090 mtlrect.width = renderer->viewport.w; 1091 mtlrect.height = renderer->viewport.h; 1092 } 1093 if (mtlrect.width > 0 && mtlrect.height > 0) { 1094 [data.mtlcmdencoder setScissorRect:mtlrect]; 1095 } 1096 } 1097 return 0; 1098}} 1099 1100static int 1101METAL_RenderClear(SDL_Renderer * renderer) 1102{ @autoreleasepool { 1103 METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata; 1104 1105 /* Since we set up the render command encoder lazily when a draw is 1106 * requested, we can do the fast path hardware clear if no draws have 1107 * happened since the last SetRenderTarget. */ 1108 if (data.mtlcmdencoder == nil) { 1109 METAL_ActivateRenderCommandEncoder(renderer, MTLLoadActionClear); 1110 } else { 1111 // !!! FIXME: render color should live in a dedicated uniform buffer. 1112 const float color[4] = { ((float)renderer->r) / 255.0f, ((float)renderer->g) / 255.0f, ((float)renderer->b) / 255.0f, ((float)renderer->a) / 255.0f }; 1113 1114 MTLViewport viewport; // RenderClear ignores the viewport state, though, so reset that. 1115 viewport.originX = viewport.originY = 0.0; 1116 viewport.width = data.mtlpassdesc.colorAttachments[0].texture.width; 1117 viewport.height = data.mtlpassdesc.colorAttachments[0].texture.height; 1118 viewport.znear = 0.0; 1119 viewport.zfar = 1.0; 1120 1121 // Slow path for clearing: draw a filled fullscreen triangle. 1122 METAL_SetOrthographicProjection(renderer, 1, 1); 1123 [data.mtlcmdencoder setViewport:viewport]; 1124 [data.mtlcmdencoder setRenderPipelineState:ChoosePipelineState(data, data.activepipelines, SDL_METAL_FRAGMENT_SOLID, SDL_BLENDMODE_NONE)]; 1125 [data.mtlcmdencoder setVertexBuffer:data.mtlbufconstants offset:CONSTANTS_OFFSET_CLEAR_VERTS atIndex:0]; 1126 [data.mtlcmdencoder setVertexBuffer:data.mtlbufconstants offset:CONSTANTS_OFFSET_IDENTITY atIndex:3]; 1127 [data.mtlcmdencoder setFragmentBytes:color length:sizeof(color) atIndex:0]; 1128 [data.mtlcmdencoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:3]; 1129 1130 // reset the viewport for the rest of our usual drawing work... 1131 viewport.originX = renderer->viewport.x; 1132 viewport.originY = renderer->viewport.y; 1133 viewport.width = renderer->viewport.w; 1134 viewport.height = renderer->viewport.h; 1135 viewport.znear = 0.0; 1136 viewport.zfar = 1.0; 1137 [data.mtlcmdencoder setViewport:viewport]; 1138 METAL_SetOrthographicProjection(renderer, renderer->viewport.w, renderer->viewport.h); 1139 } 1140 1141 return 0; 1142}} 1143 1144// normalize a value from 0.0f to len into 0.0f to 1.0f. 1145static inline float 1146normtex(const float _val, const float len) 1147{ 1148 return _val / len; 1149} 1150 1151static int 1152DrawVerts(SDL_Renderer * renderer, const SDL_FPoint * points, int count, 1153 const MTLPrimitiveType primtype) 1154{ @autoreleasepool { 1155 METAL_ActivateRenderCommandEncoder(renderer, MTLLoadActionLoad); 1156 1157 const size_t vertlen = (sizeof (float) * 2) * count; 1158 METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata; 1159 1160 // !!! FIXME: render color should live in a dedicated uniform buffer. 1161 const float color[4] = { ((float)renderer->r) / 255.0f, ((float)renderer->g) / 255.0f, ((float)renderer->b) / 255.0f, ((float)renderer->a) / 255.0f }; 1162 1163 [data.mtlcmdencoder setRenderPipelineState:ChoosePipelineState(data, data.activepipelines, SDL_METAL_FRAGMENT_SOLID, renderer->blendMode)]; 1164 [data.mtlcmdencoder setFragmentBytes:color length:sizeof(color) atIndex:0]; 1165 1166 [data.mtlcmdencoder setVertexBytes:points length:vertlen atIndex:0]; 1167 [data.mtlcmdencoder setVertexBuffer:data.mtlbufconstants offset:CONSTANTS_OFFSET_HALF_PIXEL_TRANSFORM atIndex:3]; 1168 [data.mtlcmdencoder drawPrimitives:primtype vertexStart:0 vertexCount:count]; 1169 1170 return 0; 1171}} 1172 1173static int 1174METAL_RenderDrawPoints(SDL_Renderer * renderer, const SDL_FPoint * points, int count) 1175{ 1176 return DrawVerts(renderer, points, count, MTLPrimitiveTypePoint); 1177} 1178 1179static int 1180METAL_RenderDrawLines(SDL_Renderer * renderer, const SDL_FPoint * points, int count) 1181{ 1182 return DrawVerts(renderer, points, count, MTLPrimitiveTypeLineStrip); 1183} 1184 1185static int 1186METAL_RenderFillRects(SDL_Renderer * renderer, const SDL_FRect * rects, int count) 1187{ @autoreleasepool { 1188 METAL_ActivateRenderCommandEncoder(renderer, MTLLoadActionLoad); 1189 METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata; 1190 1191 // !!! FIXME: render color should live in a dedicated uniform buffer. 1192 const float color[4] = { ((float)renderer->r) / 255.0f, ((float)renderer->g) / 255.0f, ((float)renderer->b) / 255.0f, ((float)renderer->a) / 255.0f }; 1193 1194 [data.mtlcmdencoder setRenderPipelineState:ChoosePipelineState(data, data.activepipelines, SDL_METAL_FRAGMENT_SOLID, renderer->blendMode)]; 1195 [data.mtlcmdencoder setFragmentBytes:color length:sizeof(color) atIndex:0]; 1196 [data.mtlcmdencoder setVertexBuffer:data.mtlbufconstants offset:CONSTANTS_OFFSET_IDENTITY atIndex:3]; 1197 1198 for (int i = 0; i < count; i++, rects++) { 1199 if ((rects->w <= 0.0f) || (rects->h <= 0.0f)) continue; 1200 1201 const float verts[] = { 1202 rects->x, rects->y + rects->h, 1203 rects->x, rects->y, 1204 rects->x + rects->w, rects->y + rects->h, 1205 rects->x + rects->w, rects->y 1206 }; 1207 1208 [data.mtlcmdencoder setVertexBytes:verts length:sizeof(verts) atIndex:0]; 1209 [data.mtlcmdencoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4]; 1210 } 1211 1212 return 0; 1213}} 1214 1215static void 1216METAL_SetupRenderCopy(METAL_RenderData *data, SDL_Texture *texture, METAL_TextureData *texturedata) 1217{ 1218 float color[4] = { 1.0f, 1.0f, 1.0f, 1.0f }; 1219 if (texture->modMode) { 1220 color[0] = ((float)texture->r) / 255.0f; 1221 color[1] = ((float)texture->g) / 255.0f; 1222 color[2] = ((float)texture->b) / 255.0f; 1223 color[3] = ((float)texture->a) / 255.0f; 1224 } 1225 1226 [data.mtlcmdencoder setRenderPipelineState:ChoosePipelineState(data, data.activepipelines, texturedata.fragmentFunction, texture->blendMode)]; 1227 [data.mtlcmdencoder setFragmentBytes:color length:sizeof(color) atIndex:0]; 1228 [data.mtlcmdencoder setFragmentSamplerState:texturedata.mtlsampler atIndex:0]; 1229 1230 [data.mtlcmdencoder setFragmentTexture:texturedata.mtltexture atIndex:0]; 1231 1232 if (texturedata.yuv || texturedata.nv12) { 1233 [data.mtlcmdencoder setFragmentTexture:texturedata.mtltexture_uv atIndex:1]; 1234 [data.mtlcmdencoder setFragmentBuffer:data.mtlbufconstants offset:texturedata.conversionBufferOffset atIndex:1]; 1235 } 1236} 1237 1238static int 1239METAL_RenderCopy(SDL_Renderer * renderer, SDL_Texture * texture, 1240 const SDL_Rect * srcrect, const SDL_FRect * dstrect) 1241{ @autoreleasepool { 1242 METAL_ActivateRenderCommandEncoder(renderer, MTLLoadActionLoad); 1243 METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata; 1244 METAL_TextureData *texturedata = (__bridge METAL_TextureData *)texture->driverdata; 1245 const float texw = (float) texturedata.mtltexture.width; 1246 const float texh = (float) texturedata.mtltexture.height; 1247 1248 METAL_SetupRenderCopy(data, texture, texturedata); 1249 1250 const float xy[] = { 1251 dstrect->x, dstrect->y + dstrect->h, 1252 dstrect->x, dstrect->y, 1253 dstrect->x + dstrect->w, dstrect->y + dstrect->h, 1254 dstrect->x + dstrect->w, dstrect->y 1255 }; 1256 1257 const float uv[] = { 1258 normtex(srcrect->x, texw), normtex(srcrect->y + srcrect->h, texh), 1259 normtex(srcrect->x, texw), normtex(srcrect->y, texh), 1260 normtex(srcrect->x + srcrect->w, texw), normtex(srcrect->y + srcrect->h, texh), 1261 normtex(srcrect->x + srcrect->w, texw), normtex(srcrect->y, texh) 1262 }; 1263 1264 [data.mtlcmdencoder setVertexBytes:xy length:sizeof(xy) atIndex:0]; 1265 [data.mtlcmdencoder setVertexBytes:uv length:sizeof(uv) atIndex:1]; 1266 [data.mtlcmdencoder setVertexBuffer:data.mtlbufconstants offset:CONSTANTS_OFFSET_IDENTITY atIndex:3]; 1267 [data.mtlcmdencoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4]; 1268 1269 return 0; 1270}} 1271 1272static int 1273METAL_RenderCopyEx(SDL_Renderer * renderer, SDL_Texture * texture, 1274 const SDL_Rect * srcrect, const SDL_FRect * dstrect, 1275 const double angle, const SDL_FPoint *center, const SDL_RendererFlip flip) 1276{ @autoreleasepool { 1277 METAL_ActivateRenderCommandEncoder(renderer, MTLLoadActionLoad); 1278 METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata; 1279 METAL_TextureData *texturedata = (__bridge METAL_TextureData *)texture->driverdata; 1280 const float texw = (float) texturedata.mtltexture.width; 1281 const float texh = (float) texturedata.mtltexture.height; 1282 float transform[16]; 1283 float minu, maxu, minv, maxv; 1284 1285 METAL_SetupRenderCopy(data, texture, texturedata); 1286 1287 minu = normtex(srcrect->x, texw); 1288 maxu = normtex(srcrect->x + srcrect->w, texw); 1289 minv = normtex(srcrect->y, texh); 1290 maxv = normtex(srcrect->y + srcrect->h, texh); 1291 1292 if (flip & SDL_FLIP_HORIZONTAL) { 1293 float tmp = maxu; 1294 maxu = minu; 1295 minu = tmp; 1296 } 1297 if (flip & SDL_FLIP_VERTICAL) { 1298 float tmp = maxv; 1299 maxv = minv; 1300 minv = tmp; 1301 } 1302 1303 const float uv[] = { 1304 minu, maxv, 1305 minu, minv, 1306 maxu, maxv, 1307 maxu, minv 1308 }; 1309 1310 const float xy[] = { 1311 -center->x, dstrect->h - center->y, 1312 -center->x, -center->y, 1313 dstrect->w - center->x, dstrect->h - center->y, 1314 dstrect->w - center->x, -center->y 1315 }; 1316 1317 { 1318 float rads = (float)(M_PI * (float) angle / 180.0f); 1319 float c = cosf(rads), s = sinf(rads); 1320 SDL_memset(transform, 0, sizeof(transform)); 1321 1322 transform[10] = transform[15] = 1.0f; 1323 1324 /* Rotation */ 1325 transform[0] = c; 1326 transform[1] = s; 1327 transform[4] = -s; 1328 transform[5] = c; 1329 1330 /* Translation */ 1331 transform[12] = dstrect->x + center->x; 1332 transform[13] = dstrect->y + center->y; 1333 } 1334 1335 [data.mtlcmdencoder setVertexBytes:xy length:sizeof(xy) atIndex:0]; 1336 [data.mtlcmdencoder setVertexBytes:uv length:sizeof(uv) atIndex:1]; 1337 [data.mtlcmdencoder setVertexBytes:transform length:sizeof(transform) atIndex:3]; 1338 [data.mtlcmdencoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4]; 1339 1340 return 0; 1341}} 1342 1343static int 1344METAL_RenderReadPixels(SDL_Renderer * renderer, const SDL_Rect * rect, 1345 Uint32 pixel_format, void * pixels, int pitch) 1346{ @autoreleasepool { 1347 METAL_ActivateRenderCommandEncoder(renderer, MTLLoadActionLoad); 1348 1349 // !!! FIXME: this probably needs to commit the current command buffer, and probably waitUntilCompleted 1350 METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata; 1351 id<MTLTexture> mtltexture = data.mtlpassdesc.colorAttachments[0].texture; 1352 MTLRegion mtlregion = MTLRegionMake2D(rect->x, rect->y, rect->w, rect->h); 1353 1354 // we only do BGRA8 or RGBA8 at the moment, so 4 will do. 1355 const int temp_pitch = rect->w * 4; 1356 void *temp_pixels = SDL_malloc(temp_pitch * rect->h); 1357 if (!temp_pixels) { 1358 return SDL_OutOfMemory(); 1359 } 1360 1361 [mtltexture getBytes:temp_pixels bytesPerRow:temp_pitch fromRegion:mtlregion mipmapLevel:0]; 1362 1363 const Uint32 temp_format = (mtltexture.pixelFormat == MTLPixelFormatBGRA8Unorm) ? SDL_PIXELFORMAT_ARGB8888 : SDL_PIXELFORMAT_ABGR8888; 1364 const int status = SDL_ConvertPixels(rect->w, rect->h, temp_format, temp_pixels, temp_pitch, pixel_format, pixels, pitch); 1365 SDL_free(temp_pixels); 1366 return status; 1367}} 1368 1369static void 1370METAL_RenderPresent(SDL_Renderer * renderer) 1371{ @autoreleasepool { 1372 METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata; 1373 1374 if (data.mtlcmdencoder != nil) { 1375 [data.mtlcmdencoder endEncoding]; 1376 } 1377 if (data.mtlbackbuffer != nil) { 1378 [data.mtlcmdbuffer presentDrawable:data.mtlbackbuffer]; 1379 } 1380 if (data.mtlcmdbuffer != nil) { 1381 [data.mtlcmdbuffer commit]; 1382 } 1383 data.mtlcmdencoder = nil; 1384 data.mtlcmdbuffer = nil; 1385 data.mtlbackbuffer = nil; 1386}} 1387 1388static void 1389METAL_DestroyTexture(SDL_Renderer * renderer, SDL_Texture * texture) 1390{ @autoreleasepool { 1391 CFBridgingRelease(texture->driverdata); 1392 texture->driverdata = NULL; 1393}} 1394 1395static void 1396METAL_DestroyRenderer(SDL_Renderer * renderer) 1397{ @autoreleasepool { 1398 if (renderer->driverdata) { 1399 METAL_RenderData *data = CFBridgingRelease(renderer->driverdata); 1400 1401 if (data.mtlcmdencoder != nil) { 1402 [data.mtlcmdencoder endEncoding]; 1403 } 1404 1405 DestroyAllPipelines(data.allpipelines, data.pipelinescount); 1406 } 1407 1408 SDL_free(renderer); 1409}} 1410 1411static void * 1412METAL_GetMetalLayer(SDL_Renderer * renderer) 1413{ @autoreleasepool { 1414 METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata; 1415 return (__bridge void*)data.mtllayer; 1416}} 1417 1418static void * 1419METAL_GetMetalCommandEncoder(SDL_Renderer * renderer) 1420{ @autoreleasepool { 1421 METAL_ActivateRenderCommandEncoder(renderer, MTLLoadActionLoad); 1422 METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata; 1423 return (__bridge void*)data.mtlcmdencoder; 1424}} 1425 1426#endif /* SDL_VIDEO_RENDER_METAL && !SDL_RENDER_DISABLED */ 1427 1428/* vi: set ts=4 sw=4 expandtab: */ 1429[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.