1/* RetroArch - A frontend for libretro. 2 * Copyright (C) 2018 - Stuart Carnie 3 * copyright (c) 2011-2021 - Daniel De Matteis 4 * 5 * RetroArch is free software: you can redistribute it and/or modify it under the terms 6 * of the GNU General Public License as published by the Free Software Found- 7 * ation, either version 3 of the License, or (at your option) any later version. 8 * 9 * RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 10 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 11 * PURPOSE. See the GNU General Public License for more details. 12 * 13 * You should have received a copy of the GNU General Public License along with RetroArch. 14 * If not, see <http://www.gnu.org/licenses/>. 15 */ 16 17#include <retro_assert.h> 18 19#import <Metal/Metal.h> 20#import <QuartzCore/QuartzCore.h> 21 22#import "metal_common.h" 23#import "metal_shader_types.h" 24 25#ifdef HAVE_MENU 26#include "../../../menu/menu_driver.h" 27#endif 28 29#import "../metal_common.h" 30#include "../../verbosity.h" 31 32/* 33 * COMMON 34 */ 35 36static NSString *RPixelStrings[RPixelFormatCount]; 37 38NSUInteger RPixelFormatToBPP(RPixelFormat format) 39{ 40 switch (format) 41 { 42 case RPixelFormatBGRA8Unorm: 43 case RPixelFormatBGRX8Unorm: 44 return 4; 45 46 case RPixelFormatB5G6R5Unorm: 47 case RPixelFormatBGRA4Unorm: 48 return 2; 49 50 default: 51 RARCH_ERR("[Metal]: unknown RPixel format: %d\n", format); 52 return 4; 53 } 54} 55 56static NSString *NSStringFromRPixelFormat(RPixelFormat format) 57{ 58 static dispatch_once_t onceToken; 59 dispatch_once(&onceToken, ^{ 60 61#define STRING(literal) RPixelStrings[literal] = @#literal 62 STRING(RPixelFormatInvalid); 63 STRING(RPixelFormatB5G6R5Unorm); 64 STRING(RPixelFormatBGRA4Unorm); 65 STRING(RPixelFormatBGRA8Unorm); 66 STRING(RPixelFormatBGRX8Unorm); 67#undef STRING 68 69 }); 70 71 if (format >= RPixelFormatCount) 72 { 73 format = RPixelFormatInvalid; 74 } 75 76 return RPixelStrings[format]; 77} 78 79matrix_float4x4 make_matrix_float4x4(const float *v) 80{ 81 simd_float4 P = simd_make_float4(v[0], v[1], v[2], v[3]); 82 v += 4; 83 simd_float4 Q = simd_make_float4(v[0], v[1], v[2], v[3]); 84 v += 4; 85 simd_float4 R = simd_make_float4(v[0], v[1], v[2], v[3]); 86 v += 4; 87 simd_float4 S = simd_make_float4(v[0], v[1], v[2], v[3]); 88 89 matrix_float4x4 mat = {P, Q, R, S}; 90 return mat; 91} 92 93matrix_float4x4 matrix_proj_ortho(float left, float right, float top, float bottom) 94{ 95 float near = 0; 96 float far = 1; 97 98 float sx = 2 / (right - left); 99 float sy = 2 / (top - bottom); 100 float sz = 1 / (far - near); 101 float tx = (right + left) / (left - right); 102 float ty = (top + bottom) / (bottom - top); 103 float tz = near / (far - near); 104 105 simd_float4 P = simd_make_float4(sx, 0, 0, 0); 106 simd_float4 Q = simd_make_float4(0, sy, 0, 0); 107 simd_float4 R = simd_make_float4(0, 0, sz, 0); 108 simd_float4 S = simd_make_float4(tx, ty, tz, 1); 109 110 matrix_float4x4 mat = {P, Q, R, S}; 111 return mat; 112} 113 114matrix_float4x4 matrix_rotate_z(float rot) 115{ 116 float cz, sz; 117 __sincosf(rot, &sz, &cz); 118 119 simd_float4 P = simd_make_float4(cz, -sz, 0, 0); 120 simd_float4 Q = simd_make_float4(sz, cz, 0, 0); 121 simd_float4 R = simd_make_float4( 0, 0, 1, 0); 122 simd_float4 S = simd_make_float4( 0, 0, 0, 1); 123 124 matrix_float4x4 mat = {P, Q, R, S}; 125 return mat; 126} 127 128/* 129 * CONTEXT 130 */ 131 132@interface BufferNode : NSObject 133@property (nonatomic, readonly) id<MTLBuffer> src; 134@property (nonatomic, readwrite) NSUInteger allocated; 135@property (nonatomic, readwrite) BufferNode *next; 136@end 137 138@interface BufferChain : NSObject 139- (instancetype)initWithDevice:(id<MTLDevice>)device blockLen:(NSUInteger)blockLen; 140- (bool)allocRange:(BufferRange *)range length:(NSUInteger)length; 141- (void)commitRanges; 142- (void)discard; 143@end 144 145@interface Texture() 146@property (nonatomic, readwrite) id<MTLTexture> texture; 147@property (nonatomic, readwrite) id<MTLSamplerState> sampler; 148@end 149 150@interface Context() 151- (bool)_initConversionFilters; 152@end 153 154@implementation Context 155{ 156 dispatch_semaphore_t _inflightSemaphore; 157 id<MTLCommandQueue> _commandQueue; 158 CAMetalLayer *_layer; 159 id<CAMetalDrawable> _drawable; 160 video_viewport_t _viewport; 161 id<MTLSamplerState> _samplers[TEXTURE_FILTER_MIPMAP_NEAREST + 1]; 162 Filter *_filters[RPixelFormatCount]; // convert to bgra8888 163 164 // main render pass state 165 id<MTLRenderCommandEncoder> _rce; 166 167 id<MTLCommandBuffer> _blitCommandBuffer; 168 169 NSUInteger _currentChain; 170 BufferChain *_chain[CHAIN_LENGTH]; 171 MTLClearColor _clearColor; 172 173 id<MTLRenderPipelineState> _states[GFX_MAX_SHADERS][2]; 174 id<MTLRenderPipelineState> _clearState; 175 176 bool _captureEnabled; 177 id<MTLTexture> _backBuffer; 178 179 unsigned _rotation; 180 matrix_float4x4 _mvp_no_rot; 181 matrix_float4x4 _mvp; 182 183 Uniforms _uniforms; 184 Uniforms _uniformsNoRotate; 185} 186 187- (instancetype)initWithDevice:(id<MTLDevice>)d 188 layer:(CAMetalLayer *)layer 189 library:(id<MTLLibrary>)l 190{ 191 if (self = [super init]) 192 { 193 _inflightSemaphore = dispatch_semaphore_create(MAX_INFLIGHT); 194 _device = d; 195 _layer = layer; 196#ifdef OSX 197 _layer.framebufferOnly = NO; 198 _layer.displaySyncEnabled = YES; 199#endif 200 _library = l; 201 _commandQueue = [_device newCommandQueue]; 202 _clearColor = MTLClearColorMake(0, 0, 0, 1); 203 _uniforms.projectionMatrix = matrix_proj_ortho(0, 1, 0, 1); 204 205 _rotation = 0; 206 [self setRotation:0]; 207 _mvp_no_rot = matrix_proj_ortho(0, 1, 0, 1); 208 _mvp = matrix_proj_ortho(0, 1, 0, 1); 209 210 { 211 MTLSamplerDescriptor *sd = [MTLSamplerDescriptor new]; 212 213 sd.label = @"NEAREST"; 214 _samplers[TEXTURE_FILTER_NEAREST] = [d newSamplerStateWithDescriptor:sd]; 215 216 sd.mipFilter = MTLSamplerMipFilterNearest; 217 sd.label = @"MIPMAP_NEAREST"; 218 _samplers[TEXTURE_FILTER_MIPMAP_NEAREST] = [d newSamplerStateWithDescriptor:sd]; 219 220 sd.mipFilter = MTLSamplerMipFilterNotMipmapped; 221 sd.minFilter = MTLSamplerMinMagFilterLinear; 222 sd.magFilter = MTLSamplerMinMagFilterLinear; 223 sd.label = @"LINEAR"; 224 _samplers[TEXTURE_FILTER_LINEAR] = [d newSamplerStateWithDescriptor:sd]; 225 226 sd.mipFilter = MTLSamplerMipFilterLinear; 227 sd.label = @"MIPMAP_LINEAR"; 228 _samplers[TEXTURE_FILTER_MIPMAP_LINEAR] = [d newSamplerStateWithDescriptor:sd]; 229 } 230 231 if (![self _initConversionFilters]) 232 return nil; 233 234 if (![self _initClearState]) 235 return nil; 236 237 if (![self _initMenuStates]) 238 return nil; 239 240 for (int i = 0; i < CHAIN_LENGTH; i++) 241 { 242 _chain[i] = [[BufferChain alloc] initWithDevice:_device blockLen:65536]; 243 } 244 } 245 return self; 246} 247 248- (video_viewport_t *)viewport 249{ 250 return &_viewport; 251} 252 253- (void)setViewport:(video_viewport_t *)viewport 254{ 255 _viewport = *viewport; 256 _uniforms.outputSize = simd_make_float2(_viewport.full_width, _viewport.full_height); 257} 258 259- (Uniforms *)uniforms 260{ 261 return &_uniforms; 262} 263 264- (void)setRotation:(unsigned)rotation 265{ 266 _rotation = 270 * rotation; 267 268 /* Calculate projection. */ 269 _mvp_no_rot = matrix_proj_ortho(0, 1, 0, 1); 270 271 bool allow_rotate = true; 272 if (!allow_rotate) 273 { 274 _mvp = _mvp_no_rot; 275 return; 276 } 277 278 matrix_float4x4 rot = matrix_rotate_z((float)(M_PI * _rotation / 180.0f)); 279 _mvp = simd_mul(rot, _mvp_no_rot); 280 281 _uniforms.projectionMatrix = _mvp; 282 _uniformsNoRotate.projectionMatrix = _mvp_no_rot; 283} 284 285- (void)setDisplaySyncEnabled:(bool)displaySyncEnabled 286{ 287#ifdef OSX 288 _layer.displaySyncEnabled = displaySyncEnabled; 289#endif 290} 291 292- (bool)displaySyncEnabled 293{ 294#ifdef OSX 295 return _layer.displaySyncEnabled; 296#else 297 return NO; 298#endif 299} 300 301#pragma mark - shaders 302 303- (id<MTLRenderPipelineState>)getStockShader:(int)index blend:(bool)blend 304{ 305 assert(index > 0 && index < GFX_MAX_SHADERS); 306 307 switch (index) 308 { 309 case VIDEO_SHADER_STOCK_BLEND: 310 case VIDEO_SHADER_MENU: 311 case VIDEO_SHADER_MENU_2: 312 case VIDEO_SHADER_MENU_3: 313 case VIDEO_SHADER_MENU_4: 314 case VIDEO_SHADER_MENU_5: 315 case VIDEO_SHADER_MENU_6: 316 break; 317 default: 318 index = VIDEO_SHADER_STOCK_BLEND; 319 break; 320 } 321 322 return _states[index][blend ? 1 : 0]; 323} 324 325- (MTLVertexDescriptor *)_spriteVertexDescriptor 326{ 327 MTLVertexDescriptor *vd = [MTLVertexDescriptor new]; 328 vd.attributes[0].offset = 0; 329 vd.attributes[0].format = MTLVertexFormatFloat2; 330 vd.attributes[1].offset = offsetof(SpriteVertex, texCoord); 331 vd.attributes[1].format = MTLVertexFormatFloat2; 332 vd.attributes[2].offset = offsetof(SpriteVertex, color); 333 vd.attributes[2].format = MTLVertexFormatFloat4; 334 vd.layouts[0].stride = sizeof(SpriteVertex); 335 return vd; 336} 337 338- (bool)_initClearState 339{ 340 MTLVertexDescriptor *vd = [self _spriteVertexDescriptor]; 341 MTLRenderPipelineDescriptor *psd = [MTLRenderPipelineDescriptor new]; 342 psd.label = @"clear_state"; 343 344 MTLRenderPipelineColorAttachmentDescriptor *ca = psd.colorAttachments[0]; 345 ca.pixelFormat = _layer.pixelFormat; 346 347 psd.vertexDescriptor = vd; 348 psd.vertexFunction = [_library newFunctionWithName:@"stock_vertex"]; 349 psd.fragmentFunction = [_library newFunctionWithName:@"stock_fragment_color"]; 350 351 NSError *err; 352 _clearState = [_device newRenderPipelineStateWithDescriptor:psd error:&err]; 353 if (err != nil) 354 { 355 RARCH_ERR("[Metal]: error creating clear pipeline state %s\n", err.localizedDescription.UTF8String); 356 return NO; 357 } 358 359 return YES; 360} 361 362- (bool)_initMenuStates 363{ 364 MTLVertexDescriptor *vd = [self _spriteVertexDescriptor]; 365 MTLRenderPipelineDescriptor *psd = [MTLRenderPipelineDescriptor new]; 366 psd.label = @"stock"; 367 368 MTLRenderPipelineColorAttachmentDescriptor *ca = psd.colorAttachments[0]; 369 ca.pixelFormat = _layer.pixelFormat; 370 ca.blendingEnabled = NO; 371 ca.sourceRGBBlendFactor = MTLBlendFactorSourceAlpha; 372 ca.destinationRGBBlendFactor = MTLBlendFactorOneMinusSourceAlpha; 373 ca.sourceAlphaBlendFactor = MTLBlendFactorSourceAlpha; 374 ca.destinationAlphaBlendFactor = MTLBlendFactorOneMinusSourceAlpha; 375 376 psd.sampleCount = 1; 377 psd.vertexDescriptor = vd; 378 psd.vertexFunction = [_library newFunctionWithName:@"stock_vertex"]; 379 psd.fragmentFunction = [_library newFunctionWithName:@"stock_fragment"]; 380 381 NSError *err; 382 _states[VIDEO_SHADER_STOCK_BLEND][0] = [_device newRenderPipelineStateWithDescriptor:psd error:&err]; 383 if (err != nil) 384 { 385 RARCH_ERR("[Metal]: error creating pipeline state %s\n", err.localizedDescription.UTF8String); 386 return NO; 387 } 388 389 psd.label = @"stock_blend"; 390 ca.blendingEnabled = YES; 391 _states[VIDEO_SHADER_STOCK_BLEND][1] = [_device newRenderPipelineStateWithDescriptor:psd error:&err]; 392 if (err != nil) 393 { 394 RARCH_ERR("[Metal]: error creating pipeline state %s\n", err.localizedDescription.UTF8String); 395 return NO; 396 } 397 398 MTLFunctionConstantValues *vals; 399 400 psd.label = @"snow_simple"; 401 ca.blendingEnabled = YES; 402 { 403 vals = [MTLFunctionConstantValues new]; 404 float values[3] = { 405 1.25f, // baseScale 406 0.50f, // density 407 0.15f, // speed 408 }; 409 [vals setConstantValue:&values[0] type:MTLDataTypeFloat withName:@"snowBaseScale"]; 410 [vals setConstantValue:&values[1] type:MTLDataTypeFloat withName:@"snowDensity"]; 411 [vals setConstantValue:&values[2] type:MTLDataTypeFloat withName:@"snowSpeed"]; 412 } 413 psd.fragmentFunction = [_library newFunctionWithName:@"snow_fragment" constantValues:vals error:&err]; 414 _states[VIDEO_SHADER_MENU_3][1] = [_device newRenderPipelineStateWithDescriptor:psd error:&err]; 415 if (err != nil) 416 { 417 RARCH_ERR("[Metal]: error creating pipeline state %s\n", err.localizedDescription.UTF8String); 418 return NO; 419 } 420 421 psd.label = @"snow"; 422 ca.blendingEnabled = YES; 423 { 424 vals = [MTLFunctionConstantValues new]; 425 float values[3] = { 426 3.50f, // baseScale 427 0.70f, // density 428 0.25f, // speed 429 }; 430 [vals setConstantValue:&values[0] type:MTLDataTypeFloat withName:@"snowBaseScale"]; 431 [vals setConstantValue:&values[1] type:MTLDataTypeFloat withName:@"snowDensity"]; 432 [vals setConstantValue:&values[2] type:MTLDataTypeFloat withName:@"snowSpeed"]; 433 } 434 psd.fragmentFunction = [_library newFunctionWithName:@"snow_fragment" constantValues:vals error:&err]; 435 _states[VIDEO_SHADER_MENU_4][1] = [_device newRenderPipelineStateWithDescriptor:psd error:&err]; 436 if (err != nil) 437 { 438 RARCH_ERR("[Metal]: error creating pipeline state %s\n", err.localizedDescription.UTF8String); 439 return NO; 440 } 441 442 psd.label = @"bokeh"; 443 ca.blendingEnabled = YES; 444 psd.fragmentFunction = [_library newFunctionWithName:@"bokeh_fragment"]; 445 _states[VIDEO_SHADER_MENU_5][1] = [_device newRenderPipelineStateWithDescriptor:psd error:&err]; 446 if (err != nil) 447 { 448 RARCH_ERR("[Metal]: error creating pipeline state %s\n", err.localizedDescription.UTF8String); 449 return NO; 450 } 451 452 psd.label = @"snowflake"; 453 ca.blendingEnabled = YES; 454 psd.fragmentFunction = [_library newFunctionWithName:@"snowflake_fragment"]; 455 _states[VIDEO_SHADER_MENU_6][1] = [_device newRenderPipelineStateWithDescriptor:psd error:&err]; 456 if (err != nil) 457 { 458 RARCH_ERR("[Metal]: error creating pipeline state %s\n", err.localizedDescription.UTF8String); 459 return NO; 460 } 461 462 psd.label = @"ribbon"; 463 ca.blendingEnabled = NO; 464 psd.vertexFunction = [_library newFunctionWithName:@"ribbon_vertex"]; 465 psd.fragmentFunction = [_library newFunctionWithName:@"ribbon_fragment"]; 466 _states[VIDEO_SHADER_MENU][0] = [_device newRenderPipelineStateWithDescriptor:psd error:&err]; 467 if (err != nil) 468 { 469 RARCH_ERR("[Metal]: error creating pipeline state %s\n", err.localizedDescription.UTF8String); 470 return NO; 471 } 472 473 psd.label = @"ribbon_blend"; 474 ca.blendingEnabled = YES; 475 ca.sourceRGBBlendFactor = MTLBlendFactorOne; 476 ca.destinationRGBBlendFactor = MTLBlendFactorOne; 477 _states[VIDEO_SHADER_MENU][1] = [_device newRenderPipelineStateWithDescriptor:psd error:&err]; 478 if (err != nil) 479 { 480 RARCH_ERR("[Metal]: error creating pipeline state %s\n", err.localizedDescription.UTF8String); 481 return NO; 482 } 483 484 psd.label = @"ribbon_simple"; 485 ca.blendingEnabled = NO; 486 psd.vertexFunction = [_library newFunctionWithName:@"ribbon_simple_vertex"]; 487 psd.fragmentFunction = [_library newFunctionWithName:@"ribbon_simple_fragment"]; 488 _states[VIDEO_SHADER_MENU_2][0] = [_device newRenderPipelineStateWithDescriptor:psd error:&err]; 489 if (err != nil) 490 { 491 RARCH_ERR("[Metal]: error creating pipeline state %s\n", err.localizedDescription.UTF8String); 492 return NO; 493 } 494 495 psd.label = @"ribbon_simple_blend"; 496 ca.blendingEnabled = YES; 497 ca.sourceRGBBlendFactor = MTLBlendFactorOne; 498 ca.destinationRGBBlendFactor = MTLBlendFactorOne; 499 _states[VIDEO_SHADER_MENU_2][1] = [_device newRenderPipelineStateWithDescriptor:psd error:&err]; 500 if (err != nil) 501 { 502 RARCH_ERR("[Metal]: error creating pipeline state %s\n", err.localizedDescription.UTF8String); 503 return NO; 504 } 505 506 return YES; 507} 508 509- (bool)_initConversionFilters 510{ 511 NSError *err = nil; 512 _filters[RPixelFormatBGRA4Unorm] = [Filter newFilterWithFunctionName:@"convert_bgra4444_to_bgra8888" 513 device:_device 514 library:_library 515 error:&err]; 516 if (err) 517 { 518 RARCH_LOG("[Metal]: unable to create 'convert_bgra4444_to_bgra8888' conversion filter: %s\n", 519 err.localizedDescription.UTF8String); 520 return NO; 521 } 522 523 _filters[RPixelFormatB5G6R5Unorm] = [Filter newFilterWithFunctionName:@"convert_rgb565_to_bgra8888" 524 device:_device 525 library:_library 526 error:&err]; 527 if (err) 528 { 529 RARCH_LOG("[Metal]: unable to create 'convert_rgb565_to_bgra8888' conversion filter: %s\n", 530 err.localizedDescription.UTF8String); 531 return NO; 532 } 533 534 return YES; 535} 536 537- (Texture *)newTexture:(struct texture_image)image filter:(enum texture_filter_type)filter 538{ 539 assert(filter >= TEXTURE_FILTER_LINEAR && filter <= TEXTURE_FILTER_MIPMAP_NEAREST); 540 541 if (!image.pixels || !image.width || !image.height) 542 { 543 /* Create a dummy texture instead. */ 544#define T0 0xff000000u 545#define T1 0xffffffffu 546 static const uint32_t checkerboard[] = { 547 T0, T1, T0, T1, T0, T1, T0, T1, 548 T1, T0, T1, T0, T1, T0, T1, T0, 549 T0, T1, T0, T1, T0, T1, T0, T1, 550 T1, T0, T1, T0, T1, T0, T1, T0, 551 T0, T1, T0, T1, T0, T1, T0, T1, 552 T1, T0, T1, T0, T1, T0, T1, T0, 553 T0, T1, T0, T1, T0, T1, T0, T1, 554 T1, T0, T1, T0, T1, T0, T1, T0, 555 }; 556#undef T0 557#undef T1 558 559 image.pixels = (uint32_t *)checkerboard; 560 image.width = 8; 561 image.height = 8; 562 filter = TEXTURE_FILTER_MIPMAP_NEAREST; 563 } 564 565 BOOL mipmapped = filter == TEXTURE_FILTER_MIPMAP_LINEAR || filter == TEXTURE_FILTER_MIPMAP_NEAREST; 566 567 Texture *tex = [Texture new]; 568 tex.texture = [self newTexture:image mipmapped:mipmapped]; 569 tex.sampler = _samplers[filter]; 570 571 return tex; 572} 573 574- (id<MTLTexture>)newTexture:(struct texture_image)image mipmapped:(bool)mipmapped 575{ 576 MTLTextureDescriptor *td = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatBGRA8Unorm 577 width:image.width 578 height:image.height 579 mipmapped:mipmapped]; 580 581 id<MTLTexture> t = [_device newTextureWithDescriptor:td]; 582 [t replaceRegion:MTLRegionMake2D(0, 0, image.width, image.height) 583 mipmapLevel:0 584 withBytes:image.pixels 585 bytesPerRow:4 * image.width]; 586 587 if (mipmapped) 588 { 589 id<MTLCommandBuffer> cb = self.blitCommandBuffer; 590 id<MTLBlitCommandEncoder> bce = [cb blitCommandEncoder]; 591 [bce generateMipmapsForTexture:t]; 592 [bce endEncoding]; 593 } 594 595 return t; 596} 597 598- (id<CAMetalDrawable>)nextDrawable 599{ 600 if (_drawable == nil) 601 { 602 _drawable = _layer.nextDrawable; 603 } 604 return _drawable; 605} 606 607- (void)convertFormat:(RPixelFormat)fmt from:(id<MTLTexture>)src to:(id<MTLTexture>)dst 608{ 609 assert(src.width == dst.width && src.height == dst.height); 610 assert(fmt >= 0 && fmt < RPixelFormatCount); 611 Filter *conv = _filters[fmt]; 612 assert(conv != nil); 613 [conv apply:self.blitCommandBuffer in:src out:dst]; 614} 615 616- (id<MTLCommandBuffer>)blitCommandBuffer 617{ 618 if (!_blitCommandBuffer) { 619 _blitCommandBuffer = [_commandQueue commandBuffer]; 620 _blitCommandBuffer.label = @"Blit command buffer"; 621 } 622 return _blitCommandBuffer; 623} 624 625- (void)_nextChain 626{ 627 _currentChain = (_currentChain + 1) % CHAIN_LENGTH; 628 [_chain[_currentChain] discard]; 629} 630 631- (void)setCaptureEnabled:(bool)captureEnabled 632{ 633 if (_captureEnabled == captureEnabled) 634 return; 635 636 _captureEnabled = captureEnabled; 637 //_layer.framebufferOnly = !captureEnabled; 638} 639 640- (bool)captureEnabled 641{ 642 return _captureEnabled; 643} 644 645- (bool)readBackBuffer:(uint8_t *)buffer 646{ 647 if (!_captureEnabled || _backBuffer == nil) 648 return NO; 649 650 if (_backBuffer.pixelFormat != MTLPixelFormatBGRA8Unorm) 651 { 652 RARCH_WARN("[Metal]: unexpected pixel format %d\n", _backBuffer.pixelFormat); 653 return NO; 654 } 655 656 uint8_t *tmp = malloc(_backBuffer.width * _backBuffer.height * 4); 657 658 [_backBuffer getBytes:tmp 659 bytesPerRow:4 * _backBuffer.width 660 fromRegion:MTLRegionMake2D(0, 0, _backBuffer.width, _backBuffer.height) 661 mipmapLevel:0]; 662 663 NSUInteger srcStride = _backBuffer.width * 4; 664 uint8_t const *src = tmp + (_viewport.y * srcStride); 665 666 NSUInteger dstStride = _viewport.width * 3; 667 uint8_t *dst = buffer + (_viewport.height - 1) * dstStride; 668 669 for (int y = 0; y < _viewport.height; y++, src += srcStride, dst -= dstStride) 670 { 671 for (int x = 0; x < _viewport.width; x++) 672 { 673 dst[3 * x + 0] = src[4 * (_viewport.x + x) + 0]; 674 dst[3 * x + 1] = src[4 * (_viewport.x + x) + 1]; 675 dst[3 * x + 2] = src[4 * (_viewport.x + x) + 2]; 676 } 677 } 678 679 free(tmp); 680 681 return YES; 682} 683 684- (void)begin 685{ 686 assert(_commandBuffer == nil); 687 dispatch_semaphore_wait(_inflightSemaphore, DISPATCH_TIME_FOREVER); 688 _commandBuffer = [_commandQueue commandBuffer]; 689 _commandBuffer.label = @"Frame command buffer"; 690 _backBuffer = nil; 691} 692 693- (id<MTLRenderCommandEncoder>)rce 694{ 695 assert(_commandBuffer != nil); 696 if (_rce == nil) 697 { 698 MTLRenderPassDescriptor *rpd = [MTLRenderPassDescriptor new]; 699 rpd.colorAttachments[0].clearColor = _clearColor; 700 rpd.colorAttachments[0].loadAction = MTLLoadActionClear; 701 rpd.colorAttachments[0].texture = self.nextDrawable.texture; 702 if (_captureEnabled) 703 { 704 _backBuffer = self.nextDrawable.texture; 705 } 706 _rce = [_commandBuffer renderCommandEncoderWithDescriptor:rpd]; 707 _rce.label = @"Frame command encoder"; 708 } 709 return _rce; 710} 711 712- (void)resetRenderViewport:(ViewportResetMode)mode 713{ 714 bool fullscreen = mode == kFullscreenViewport; 715 MTLViewport vp = { 716 .originX = fullscreen ? 0 : _viewport.x, 717 .originY = fullscreen ? 0 : _viewport.y, 718 .width = fullscreen ? _viewport.full_width : _viewport.width, 719 .height = fullscreen ? _viewport.full_height : _viewport.height, 720 .znear = 0, 721 .zfar = 1, 722 }; 723 [self.rce setViewport:vp]; 724} 725 726- (void)resetScissorRect 727{ 728 MTLScissorRect sr = { 729 .x = 0, 730 .y = 0, 731 .width = _viewport.full_width, 732 .height = _viewport.full_height, 733 }; 734 [self.rce setScissorRect:sr]; 735} 736 737- (void)drawQuadX:(float)x y:(float)y w:(float)w h:(float)h 738 r:(float)r g:(float)g b:(float)b a:(float)a 739{ 740 SpriteVertex v[4]; 741 v[0].position = simd_make_float2(x, y); 742 v[1].position = simd_make_float2(x + w, y); 743 v[2].position = simd_make_float2(x, y + h); 744 v[3].position = simd_make_float2(x + w, y + h); 745 746 simd_float4 color = simd_make_float4(r, g, b, a); 747 v[0].color = color; 748 v[1].color = color; 749 v[2].color = color; 750 v[3].color = color; 751 752 id<MTLRenderCommandEncoder> rce = self.rce; 753 [rce setRenderPipelineState:_clearState]; 754 [rce setVertexBytes:&v length:sizeof(v) atIndex:BufferIndexPositions]; 755 [rce setVertexBytes:&_uniforms length:sizeof(_uniforms) atIndex:BufferIndexUniforms]; 756 [rce drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4]; 757} 758 759- (void)end 760{ 761 assert(_commandBuffer != nil); 762 763 [_chain[_currentChain] commitRanges]; 764 765 if (_blitCommandBuffer) 766 { 767#ifdef OSX 768 if (_captureEnabled) 769 { 770 id<MTLBlitCommandEncoder> bce = [_blitCommandBuffer blitCommandEncoder]; 771 [bce synchronizeResource:_backBuffer]; 772 [bce endEncoding]; 773 } 774#endif 775 // pending blits for mipmaps or render passes for slang shaders 776 [_blitCommandBuffer commit]; 777 [_blitCommandBuffer waitUntilCompleted]; 778 _blitCommandBuffer = nil; 779 } 780 781 if (_rce) 782 { 783 [_rce endEncoding]; 784 _rce = nil; 785 } 786 787 __block dispatch_semaphore_t inflight = _inflightSemaphore; 788 [_commandBuffer addCompletedHandler:^(id<MTLCommandBuffer> _) { 789 dispatch_semaphore_signal(inflight); 790 }]; 791 792 if (self.nextDrawable) 793 { 794 [_commandBuffer presentDrawable:self.nextDrawable]; 795 } 796 797 [_commandBuffer commit]; 798 799 _commandBuffer = nil; 800 _drawable = nil; 801 [self _nextChain]; 802} 803 804- (bool)allocRange:(BufferRange *)range length:(NSUInteger)length 805{ 806 return [_chain[_currentChain] allocRange:range length:length]; 807} 808 809@end 810 811@implementation Texture 812@end 813 814@implementation BufferNode 815 816- (instancetype)initWithBuffer:(id<MTLBuffer>)src 817{ 818 if (self = [super init]) 819 { 820 _src = src; 821 } 822 return self; 823} 824 825@end 826 827@implementation BufferChain 828{ 829 id<MTLDevice> _device; 830 NSUInteger _blockLen; 831 BufferNode *_head; 832 NSUInteger _offset; // offset into _current 833 BufferNode *_current; 834 NSUInteger _length; 835 NSUInteger _allocated; 836} 837 838/* macOS requires constants in a buffer to have a 256 byte alignment. */ 839#ifdef TARGET_OS_MAC 840static const NSUInteger kConstantAlignment = 256; 841#else 842static const NSUInteger kConstantAlignment = 4; 843#endif 844 845- (instancetype)initWithDevice:(id<MTLDevice>)device blockLen:(NSUInteger)blockLen 846{ 847 if (self = [super init]) 848 { 849 _device = device; 850 _blockLen = blockLen; 851 } 852 return self; 853} 854 855- (NSString *)debugDescription 856{ 857 return [NSString stringWithFormat:@"length=%ld, allocated=%ld", _length, _allocated]; 858} 859 860- (void)commitRanges 861{ 862#ifdef OSX 863 BufferNode *n; 864 for (n = _head; n != nil; n = n.next) 865 { 866 if (n.allocated > 0) 867 [n.src didModifyRange:NSMakeRange(0, n.allocated)]; 868 } 869#endif 870} 871 872- (void)discard 873{ 874 _current = _head; 875 _offset = 0; 876 _allocated = 0; 877} 878 879- (bool)allocRange:(BufferRange *)range length:(NSUInteger)length 880{ 881 MTLResourceOptions opts; 882 opts = PLATFORM_METAL_RESOURCE_STORAGE_MODE; 883 memset(range, 0, sizeof(*range)); 884 885 if (!_head) 886 { 887 _head = [[BufferNode alloc] initWithBuffer:[_device newBufferWithLength:_blockLen options:opts]]; 888 _length += _blockLen; 889 _current = _head; 890 _offset = 0; 891 } 892 893 if ([self _subAllocRange:range length:length]) 894 return YES; 895 896 while (_current.next) 897 { 898 [self _nextNode]; 899 if ([self _subAllocRange:range length:length]) 900 return YES; 901 } 902 903 NSUInteger blockLen = _blockLen; 904 if (length > blockLen) 905 blockLen = length; 906 907 _current.next = [[BufferNode alloc] initWithBuffer:[_device newBufferWithLength:blockLen options:opts]]; 908 if (!_current.next) 909 return NO; 910 911 _length += blockLen; 912 913 [self _nextNode]; 914 retro_assert([self _subAllocRange:range length:length]); 915 return YES; 916} 917 918- (void)_nextNode 919{ 920 _current = _current.next; 921 _offset = 0; 922} 923 924- (BOOL)_subAllocRange:(BufferRange *)range length:(NSUInteger)length 925{ 926 NSUInteger nextOffset = _offset + length; 927 if (nextOffset <= _current.src.length) 928 { 929 _current.allocated = nextOffset; 930 _allocated += length; 931 range->data = _current.src.contents + _offset; 932 range->buffer = _current.src; 933 range->offset = _offset; 934 _offset = MTL_ALIGN_BUFFER(nextOffset); 935 return YES; 936 } 937 return NO; 938} 939 940@end 941 942/* 943 * FILTER 944 */ 945 946@interface Filter() 947- (instancetype)initWithKernel:(id<MTLComputePipelineState>)kernel sampler:(id<MTLSamplerState>)sampler; 948@end 949 950@implementation Filter 951{ 952 id<MTLComputePipelineState> _kernel; 953} 954 955+ (instancetype)newFilterWithFunctionName:(NSString *)name device:(id<MTLDevice>)device library:(id<MTLLibrary>)library error:(NSError **)error 956{ 957 id<MTLFunction> function = [library newFunctionWithName:name]; 958 id<MTLComputePipelineState> kernel = [device newComputePipelineStateWithFunction:function error:error]; 959 if (*error != nil) 960 { 961 return nil; 962 } 963 964 MTLSamplerDescriptor *sd = [MTLSamplerDescriptor new]; 965 sd.minFilter = MTLSamplerMinMagFilterNearest; 966 sd.magFilter = MTLSamplerMinMagFilterNearest; 967 sd.sAddressMode = MTLSamplerAddressModeClampToEdge; 968 sd.tAddressMode = MTLSamplerAddressModeClampToEdge; 969 sd.mipFilter = MTLSamplerMipFilterNotMipmapped; 970 id<MTLSamplerState> sampler = [device newSamplerStateWithDescriptor:sd]; 971 972 return [[Filter alloc] initWithKernel:kernel sampler:sampler]; 973} 974 975- (instancetype)initWithKernel:(id<MTLComputePipelineState>)kernel sampler:(id<MTLSamplerState>)sampler 976{ 977 if (self = [super init]) 978 { 979 _kernel = kernel; 980 _sampler = sampler; 981 } 982 return self; 983} 984 985- (void)apply:(id<MTLCommandBuffer>)cb in:(id<MTLTexture>)tin out:(id<MTLTexture>)tout 986{ 987 id<MTLComputeCommandEncoder> ce = [cb computeCommandEncoder]; 988 ce.label = @"filter kernel"; 989 990 [ce setComputePipelineState:_kernel]; 991 992 [ce setTexture:tin atIndex:0]; 993 [ce setTexture:tout atIndex:1]; 994 995 [self.delegate configure:ce]; 996 997 MTLSize size = MTLSizeMake(16, 16, 1); 998 MTLSize count = MTLSizeMake((tin.width + size.width + 1) / size.width, (tin.height + size.height + 1) / size.height, 999 1); 1000 1001 [ce dispatchThreadgroups:count threadsPerThreadgroup:size]; 1002 1003 [ce endEncoding]; 1004} 1005 1006- (void)apply:(id<MTLCommandBuffer>)cb inBuf:(id<MTLBuffer>)tin outTex:(id<MTLTexture>)tout 1007{ 1008 id<MTLComputeCommandEncoder> ce = [cb computeCommandEncoder]; 1009 ce.label = @"filter kernel"; 1010 1011 [ce setComputePipelineState:_kernel]; 1012 1013 [ce setBuffer:tin offset:0 atIndex:0]; 1014 [ce setTexture:tout atIndex:0]; 1015 1016 [self.delegate configure:ce]; 1017 1018 MTLSize size = MTLSizeMake(32, 1, 1); 1019 MTLSize count = MTLSizeMake((tin.length + 00) / 32, 1, 1); 1020 1021 [ce dispatchThreadgroups:count threadsPerThreadgroup:size]; 1022 1023 [ce endEncoding]; 1024} 1025 1026@end 1027 1028#ifdef HAVE_MENU 1029@implementation MenuDisplay 1030{ 1031 Context *_context; 1032 MTLClearColor _clearColor; 1033 MTLScissorRect _scissorRect; 1034 BOOL _useScissorRect; 1035 Uniforms _uniforms; 1036 bool _clearNextRender; 1037} 1038 1039- (instancetype)initWithContext:(Context *)context 1040{ 1041 if (self = [super init]) 1042 { 1043 _context = context; 1044 _clearColor = MTLClearColorMake(0.0, 0.0, 0.0, 1.0); 1045 _uniforms.projectionMatrix = matrix_proj_ortho(0, 1, 0, 1); 1046 _useScissorRect = NO; 1047 } 1048 return self; 1049} 1050 1051+ (const float *)defaultVertices 1052{ 1053 static float dummy[8] = { 1054 0.0f, 0.0f, 1055 1.0f, 0.0f, 1056 0.0f, 1.0f, 1057 1.0f, 1.0f, 1058 }; 1059 return &dummy[0]; 1060} 1061 1062+ (const float *)defaultTexCoords 1063{ 1064 static float dummy[8] = { 1065 0.0f, 1.0f, 1066 1.0f, 1.0f, 1067 0.0f, 0.0f, 1068 1.0f, 0.0f, 1069 }; 1070 return &dummy[0]; 1071} 1072 1073+ (const float *)defaultColor 1074{ 1075 static float dummy[16] = { 1076 1.0f, 0.0f, 1.0f, 1.0f, 1077 1.0f, 0.0f, 1.0f, 1.0f, 1078 1.0f, 0.0f, 1.0f, 1.0f, 1079 1.0f, 0.0f, 1.0f, 1.0f, 1080 }; 1081 return &dummy[0]; 1082} 1083 1084- (void)setClearColor:(MTLClearColor)clearColor 1085{ 1086 _clearColor = clearColor; 1087 _clearNextRender = YES; 1088} 1089 1090- (MTLClearColor)clearColor 1091{ 1092 return _clearColor; 1093} 1094 1095- (void)setScissorRect:(MTLScissorRect)rect 1096{ 1097 _scissorRect = rect; 1098 _useScissorRect = YES; 1099} 1100 1101- (void)clearScissorRect 1102{ 1103 _useScissorRect = NO; 1104 [_context resetScissorRect]; 1105} 1106 1107- (MTLPrimitiveType)_toPrimitiveType:(enum gfx_display_prim_type)prim 1108{ 1109 switch (prim) 1110 { 1111 case GFX_DISPLAY_PRIM_TRIANGLESTRIP: 1112 return MTLPrimitiveTypeTriangleStrip; 1113 case GFX_DISPLAY_PRIM_TRIANGLES: 1114 default: 1115 /* Unexpected primitive type, defaulting to triangle */ 1116 break; 1117 } 1118 1119 return MTLPrimitiveTypeTriangle; 1120} 1121 1122- (void)drawPipeline:(gfx_display_ctx_draw_t *)draw 1123{ 1124 static struct video_coords blank_coords; 1125 1126 draw->x = 0; 1127 draw->y = 0; 1128 draw->matrix_data = NULL; 1129 1130 _uniforms.outputSize = simd_make_float2(_context.viewport->full_width, _context.viewport->full_height); 1131 1132 draw->backend_data = &_uniforms; 1133 draw->backend_data_size = sizeof(_uniforms); 1134 1135 switch (draw->pipeline_id) 1136 { 1137 /* ribbon */ 1138 default: 1139 case VIDEO_SHADER_MENU: 1140 case VIDEO_SHADER_MENU_2: 1141 { 1142 gfx_display_t *p_disp = disp_get_ptr(); 1143 video_coord_array_t *ca = &p_disp->dispca; 1144 draw->coords = (struct video_coords *)&ca->coords; 1145 break; 1146 } 1147 1148 case VIDEO_SHADER_MENU_3: 1149 case VIDEO_SHADER_MENU_4: 1150 case VIDEO_SHADER_MENU_5: 1151 case VIDEO_SHADER_MENU_6: 1152 { 1153 draw->coords = &blank_coords; 1154 blank_coords.vertices = 4; 1155 draw->prim_type = GFX_DISPLAY_PRIM_TRIANGLESTRIP; 1156 break; 1157 } 1158 } 1159 1160 _uniforms.time += 0.01; 1161} 1162 1163- (void)draw:(gfx_display_ctx_draw_t *)draw 1164{ 1165 unsigned i; 1166 BufferRange range; 1167 NSUInteger vertex_count; 1168 SpriteVertex *pv; 1169 const float *vertex = draw->coords->vertex ?: MenuDisplay.defaultVertices; 1170 const float *tex_coord = draw->coords->tex_coord ?: MenuDisplay.defaultTexCoords; 1171 const float *color = draw->coords->color ?: MenuDisplay.defaultColor; 1172 NSUInteger needed = draw->coords->vertices * sizeof(SpriteVertex); 1173 if (![_context allocRange:&range length:needed]) 1174 return; 1175 1176 vertex_count = draw->coords->vertices; 1177 pv = (SpriteVertex *)range.data; 1178 1179 for (i = 0; i < draw->coords->vertices; i++, pv++) 1180 { 1181 pv->position = simd_make_float2(vertex[0], 1.0f - vertex[1]); 1182 vertex += 2; 1183 1184 pv->texCoord = simd_make_float2(tex_coord[0], tex_coord[1]); 1185 tex_coord += 2; 1186 1187 pv->color = simd_make_float4(color[0], color[1], color[2], color[3]); 1188 color += 4; 1189 } 1190 1191 id<MTLRenderCommandEncoder> rce = _context.rce; 1192 if (_clearNextRender) 1193 { 1194 [_context resetRenderViewport:kFullscreenViewport]; 1195 [_context drawQuadX:0 1196 y:0 1197 w:1 1198 h:1 1199 r:(float)_clearColor.red 1200 g:(float)_clearColor.green 1201 b:(float)_clearColor.blue 1202 a:(float)_clearColor.alpha 1203 ]; 1204 _clearNextRender = NO; 1205 } 1206 1207 MTLViewport vp = { 1208 .originX = draw->x, 1209 .originY = _context.viewport->full_height - draw->y - draw->height, 1210 .width = draw->width, 1211 .height = draw->height, 1212 .znear = 0, 1213 .zfar = 1, 1214 }; 1215 [rce setViewport:vp]; 1216 1217 if (_useScissorRect) 1218 [rce setScissorRect:_scissorRect]; 1219 1220 switch (draw->pipeline_id) 1221 { 1222#if HAVE_SHADERPIPELINE 1223 case VIDEO_SHADER_MENU: 1224 case VIDEO_SHADER_MENU_2: 1225 case VIDEO_SHADER_MENU_3: 1226 case VIDEO_SHADER_MENU_4: 1227 case VIDEO_SHADER_MENU_5: 1228 case VIDEO_SHADER_MENU_6: 1229 [rce setRenderPipelineState:[_context getStockShader:draw->pipeline_id blend:_blend]]; 1230 [rce setVertexBytes:draw->backend_data length:draw->backend_data_size atIndex:BufferIndexUniforms]; 1231 [rce setVertexBuffer:range.buffer offset:range.offset atIndex:BufferIndexPositions]; 1232 [rce setFragmentBytes:draw->backend_data length:draw->backend_data_size atIndex:BufferIndexUniforms]; 1233 [rce drawPrimitives:[self _toPrimitiveType:draw->prim_type] vertexStart:0 vertexCount:vertex_count]; 1234 return; 1235#endif 1236 default: 1237 break; 1238 } 1239 1240 Texture *tex = (__bridge Texture *)(void *)draw->texture; 1241 if (tex == nil) 1242 return; 1243 1244 [rce setRenderPipelineState:[_context getStockShader:VIDEO_SHADER_STOCK_BLEND blend:_blend]]; 1245 1246 Uniforms uniforms = { 1247 .projectionMatrix = draw->matrix_data ? make_matrix_float4x4((const float *)draw->matrix_data) 1248 : _uniforms.projectionMatrix 1249 }; 1250 [rce setVertexBytes:&uniforms length:sizeof(uniforms) atIndex:BufferIndexUniforms]; 1251 [rce setVertexBuffer:range.buffer offset:range.offset atIndex:BufferIndexPositions]; 1252 [rce setFragmentTexture:tex.texture atIndex:TextureIndexColor]; 1253 [rce setFragmentSamplerState:tex.sampler atIndex:SamplerIndexDraw]; 1254 [rce drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:vertex_count]; 1255} 1256@end 1257#endif 1258 1259@implementation ViewDescriptor 1260 1261- (instancetype)init 1262{ 1263 self = [super init]; 1264 if (self) 1265 { 1266 _format = RPixelFormatBGRA8Unorm; 1267 } 1268 return self; 1269} 1270 1271- (NSString *)debugDescription 1272{ 1273#if defined(HAVE_COCOATOUCH) 1274 NSString *sizeDesc = [NSString stringWithFormat:@"width: %f, height: %f",_size.width,_size.height]; 1275#else 1276 NSString *sizeDesc = NSStringFromSize(_size); 1277#endif 1278 return [NSString stringWithFormat:@"( format = %@, frame = %@ )", 1279 NSStringFromRPixelFormat(_format), 1280 sizeDesc]; 1281} 1282 1283@end 1284 1285@implementation TexturedView 1286{ 1287 Context *_context; 1288 id<MTLTexture> _texture; // optimal render texture 1289 Vertex _v[4]; 1290 CGSize _size; // size of view in pixels 1291 CGRect _frame; 1292 NSUInteger _bpp; 1293 1294 id<MTLTexture> _src; // source texture 1295 bool _srcDirty; 1296} 1297 1298- (instancetype)initWithDescriptor:(ViewDescriptor *)d context:(Context *)c 1299{ 1300 self = [super init]; 1301 if (self) 1302 { 1303 _format = d.format; 1304 _bpp = RPixelFormatToBPP(_format); 1305 _filter = d.filter; 1306 _context = c; 1307 _visible = YES; 1308 if (_format == RPixelFormatBGRA8Unorm || _format == RPixelFormatBGRX8Unorm) 1309 { 1310 _drawState = ViewDrawStateEncoder; 1311 } 1312 else 1313 { 1314 _drawState = ViewDrawStateAll; 1315 } 1316 self.size = d.size; 1317 self.frame = CGRectMake(0, 0, 1, 1); 1318 } 1319 return self; 1320} 1321 1322- (void)setSize:(CGSize)size 1323{ 1324 if (CGSizeEqualToSize(_size, size)) 1325 { 1326 return; 1327 } 1328 1329 _size = size; 1330 1331 { 1332 MTLTextureDescriptor *td = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatBGRA8Unorm 1333 width:(NSUInteger)size.width 1334 height:(NSUInteger)size.height 1335 mipmapped:NO]; 1336 td.usage = MTLTextureUsageShaderRead | MTLTextureUsageShaderWrite; 1337 _texture = [_context.device newTextureWithDescriptor:td]; 1338 } 1339 1340 if (_format != RPixelFormatBGRA8Unorm && _format != RPixelFormatBGRX8Unorm) 1341 { 1342 MTLTextureDescriptor *td = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatR16Uint 1343 width:(NSUInteger)size.width 1344 height:(NSUInteger)size.height 1345 mipmapped:NO]; 1346 _src = [_context.device newTextureWithDescriptor:td]; 1347 } 1348} 1349 1350- (CGSize)size 1351{ 1352 return _size; 1353} 1354 1355- (void)setFrame:(CGRect)frame 1356{ 1357 if (CGRectEqualToRect(_frame, frame)) 1358 { 1359 return; 1360 } 1361 1362 _frame = frame; 1363 1364 float l = (float)CGRectGetMinX(frame); 1365 float t = (float)CGRectGetMinY(frame); 1366 float r = (float)CGRectGetMaxX(frame); 1367 float b = (float)CGRectGetMaxY(frame); 1368 1369 Vertex v[4] = { 1370 {simd_make_float3(l, b, 0), simd_make_float2(0, 1)}, 1371 {simd_make_float3(r, b, 0), simd_make_float2(1, 1)}, 1372 {simd_make_float3(l, t, 0), simd_make_float2(0, 0)}, 1373 {simd_make_float3(r, t, 0), simd_make_float2(1, 0)}, 1374 }; 1375 memcpy(_v, v, sizeof(_v)); 1376} 1377 1378- (CGRect)frame 1379{ 1380 return _frame; 1381} 1382 1383- (void)_convertFormat 1384{ 1385 if (_format == RPixelFormatBGRA8Unorm || _format == RPixelFormatBGRX8Unorm) 1386 return; 1387 1388 if (!_srcDirty) 1389 return; 1390 1391 [_context convertFormat:_format from:_src to:_texture]; 1392 _srcDirty = NO; 1393} 1394 1395- (void)drawWithContext:(Context *)ctx 1396{ 1397 [self _convertFormat]; 1398} 1399 1400- (void)drawWithEncoder:(id<MTLRenderCommandEncoder>)rce 1401{ 1402 [rce setVertexBytes:&_v length:sizeof(_v) atIndex:BufferIndexPositions]; 1403 [rce setFragmentTexture:_texture atIndex:TextureIndexColor]; 1404 [rce drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4]; 1405} 1406 1407- (void)updateFrame:(void const *)src pitch:(NSUInteger)pitch 1408{ 1409 if (_format == RPixelFormatBGRA8Unorm || _format == RPixelFormatBGRX8Unorm) 1410 { 1411 [_texture replaceRegion:MTLRegionMake2D(0, 0, (NSUInteger)_size.width, (NSUInteger)_size.height) 1412 mipmapLevel:0 withBytes:src 1413 bytesPerRow:(NSUInteger)(4 * pitch)]; 1414 } 1415 else 1416 { 1417 [_src replaceRegion:MTLRegionMake2D(0, 0, (NSUInteger)_size.width, (NSUInteger)_size.height) 1418 mipmapLevel:0 withBytes:src 1419 bytesPerRow:(NSUInteger)(pitch)]; 1420 _srcDirty = YES; 1421 } 1422} 1423 1424@end 1425