1/* RetroArch - A frontend for libretro. 2 * Copyright (C) 2018-2019 - Stuart Carnie 3 * Copyright (C) 2011-2017 - 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#import <Foundation/Foundation.h> 18#import <Metal/Metal.h> 19#import <QuartzCore/QuartzCore.h> 20 21#import <memory.h> 22#import <stddef.h> 23#include <simd/simd.h> 24 25#import <gfx/video_frame.h> 26 27#import "metal_common.h" 28 29#include "../../ui/drivers/cocoa/apple_platform.h" 30#include "../../ui/drivers/cocoa/cocoa_common.h" 31 32#ifdef HAVE_REWIND 33#include "../../state_manager.h" 34#endif 35#ifdef HAVE_MENU 36#include "../../menu/menu_driver.h" 37#endif 38#ifdef HAVE_GFX_WIDGETS 39#include "../gfx_widgets.h" 40#endif 41 42#include "../../configuration.h" 43#include "../../verbosity.h" 44 45#define STRUCT_ASSIGN(x, y) \ 46{ \ 47 NSObject * __y = y; \ 48 if (x != nil) { \ 49 NSObject * __foo = (__bridge_transfer NSObject *)(__bridge void *)(x); \ 50 __foo = nil; \ 51 x = (__bridge __typeof__(x))nil; \ 52 } \ 53 if (__y != nil) \ 54 x = (__bridge __typeof__(x))(__bridge_retained void *)((NSObject *)__y); \ 55 } 56 57@implementation MetalView 58 59#if !defined(HAVE_COCOATOUCH) 60- (void)keyDown:(NSEvent*)theEvent 61{ 62} 63#endif 64 65/* Stop the annoying sound when pressing a key. */ 66- (BOOL)acceptsFirstResponder 67{ 68 return YES; 69} 70 71- (BOOL)isFlipped 72{ 73 return YES; 74} 75@end 76 77#pragma mark - private categories 78 79@interface FrameView() 80 81@property (nonatomic, readwrite) video_viewport_t *viewport; 82 83- (instancetype)initWithDescriptor:(ViewDescriptor *)td context:(Context *)context; 84- (void)drawWithContext:(Context *)ctx; 85- (void)drawWithEncoder:(id<MTLRenderCommandEncoder>)rce; 86 87@end 88 89@interface MetalMenu() 90@property (nonatomic, readonly) TexturedView *view; 91- (instancetype)initWithContext:(Context *)context; 92@end 93 94@interface Overlay() 95- (instancetype)initWithContext:(Context *)context; 96- (void)drawWithEncoder:(id<MTLRenderCommandEncoder>)rce; 97@end 98 99@implementation MetalDriver 100{ 101 FrameView *_frameView; 102 MetalMenu *_menu; 103 Overlay *_overlay; 104 105 video_info_t _video; 106 107 id<MTLDevice> _device; 108 id<MTLLibrary> _library; 109 Context *_context; 110 111 CAMetalLayer *_layer; 112 113 // render target layer state 114 id<MTLRenderPipelineState> _t_pipelineState; 115 id<MTLRenderPipelineState> _t_pipelineStateNoAlpha; 116 117 id<MTLSamplerState> _samplerStateLinear; 118 id<MTLSamplerState> _samplerStateNearest; 119 120 // other state 121 Uniforms _viewportMVP; 122} 123 124- (instancetype)initWithVideo:(const video_info_t *)video 125 input:(input_driver_t **)input 126 inputData:(void **)inputData 127{ 128 if (self = [super init]) 129 { 130 _device = MTLCreateSystemDefaultDevice(); 131 MetalView *view = (MetalView *)apple_platform.renderView; 132 view.device = _device; 133 view.delegate = self; 134 _layer = (CAMetalLayer *)view.layer; 135 136 if (![self _initMetal]) 137 { 138 return nil; 139 } 140 141 _video = *video; 142 _viewport = (video_viewport_t *)calloc(1, sizeof(video_viewport_t)); 143 _viewportMVP.projectionMatrix = matrix_proj_ortho(0, 1, 0, 1); 144 145 _keepAspect = _video.force_aspect; 146 147 gfx_ctx_mode_t mode = { 148 .width = _video.width, 149 .height = _video.height, 150 .fullscreen = _video.fullscreen, 151 }; 152 153 if (mode.width == 0 || mode.height == 0) 154 { 155 // 0 indicates full screen, so we'll use the view's dimensions, which should already be full screen 156 // If this turns out to be the wrong assumption, we can use NSScreen to query the dimensions 157 CGSize size = view.frame.size; 158 mode.width = (unsigned int)size.width; 159 mode.height = (unsigned int)size.height; 160 } 161 162 [apple_platform setVideoMode:mode]; 163 164 *input = NULL; 165 *inputData = NULL; 166 167 // graphics display driver 168 _display = [[MenuDisplay alloc] initWithContext:_context]; 169 170 // menu view 171 _menu = [[MetalMenu alloc] initWithContext:_context]; 172 173 // frame buffer view 174 { 175 ViewDescriptor *vd = [ViewDescriptor new]; 176 vd.format = _video.rgb32 ? RPixelFormatBGRX8Unorm : RPixelFormatB5G6R5Unorm; 177 vd.size = CGSizeMake(video->width, video->height); 178 vd.filter = _video.smooth ? RTextureFilterLinear : RTextureFilterNearest; 179 _frameView = [[FrameView alloc] initWithDescriptor:vd context:_context]; 180 _frameView.viewport = _viewport; 181 [_frameView setFilteringIndex:0 smooth:video->smooth]; 182 } 183 184 // overlay view 185 _overlay = [[Overlay alloc] initWithContext:_context]; 186 187 font_driver_init_osd((__bridge void *)self, 188 video, 189 false, 190 video->is_threaded, 191 FONT_DRIVER_RENDER_METAL_API); 192 } 193 return self; 194} 195 196- (void)dealloc 197{ 198 if (_viewport) 199 { 200 free(_viewport); 201 _viewport = nil; 202 } 203 font_driver_free_osd(); 204} 205 206- (bool)_initMetal 207{ 208 _library = [_device newDefaultLibrary]; 209 _context = [[Context alloc] initWithDevice:_device 210 layer:_layer 211 library:_library]; 212 213 { 214 MTLVertexDescriptor *vd = [MTLVertexDescriptor new]; 215 vd.attributes[0].offset = 0; 216 vd.attributes[0].format = MTLVertexFormatFloat3; 217 vd.attributes[1].offset = offsetof(Vertex, texCoord); 218 vd.attributes[1].format = MTLVertexFormatFloat2; 219 vd.layouts[0].stride = sizeof(Vertex); 220 221 MTLRenderPipelineDescriptor *psd = [MTLRenderPipelineDescriptor new]; 222 psd.label = @"Pipeline+Alpha"; 223 224 MTLRenderPipelineColorAttachmentDescriptor *ca = psd.colorAttachments[0]; 225 ca.pixelFormat = _layer.pixelFormat; 226 ca.blendingEnabled = YES; 227 ca.sourceAlphaBlendFactor = MTLBlendFactorSourceAlpha; 228 ca.sourceRGBBlendFactor = MTLBlendFactorSourceAlpha; 229 ca.destinationAlphaBlendFactor = MTLBlendFactorOneMinusSourceAlpha; 230 ca.destinationRGBBlendFactor = MTLBlendFactorOneMinusSourceAlpha; 231 232 psd.sampleCount = 1; 233 psd.vertexDescriptor = vd; 234 psd.vertexFunction = [_library newFunctionWithName:@"basic_vertex_proj_tex"]; 235 psd.fragmentFunction = [_library newFunctionWithName:@"basic_fragment_proj_tex"]; 236 237 NSError *err; 238 _t_pipelineState = [_device newRenderPipelineStateWithDescriptor:psd error:&err]; 239 if (err != nil) 240 { 241 RARCH_ERR("[Metal]: error creating pipeline state %s\n", err.localizedDescription.UTF8String); 242 return NO; 243 } 244 245 psd.label = @"Pipeline+No Alpha"; 246 ca.blendingEnabled = NO; 247 _t_pipelineStateNoAlpha = [_device newRenderPipelineStateWithDescriptor:psd error:&err]; 248 if (err != nil) 249 { 250 RARCH_ERR("[Metal]: error creating pipeline state (no alpha) %s\n", err.localizedDescription.UTF8String); 251 return NO; 252 } 253 } 254 255 { 256 MTLSamplerDescriptor *sd = [MTLSamplerDescriptor new]; 257 _samplerStateNearest = [_device newSamplerStateWithDescriptor:sd]; 258 259 sd.minFilter = MTLSamplerMinMagFilterLinear; 260 sd.magFilter = MTLSamplerMinMagFilterLinear; 261 _samplerStateLinear = [_device newSamplerStateWithDescriptor:sd]; 262 } 263 264 return YES; 265} 266 267- (void)setViewportWidth:(unsigned)width height:(unsigned)height forceFull:(BOOL)forceFull allowRotate:(BOOL)allowRotate 268{ 269 _viewport->full_width = width; 270 _viewport->full_height = height; 271 video_driver_set_size(_viewport->full_width, _viewport->full_height); 272 _layer.drawableSize = CGSizeMake(width, height); 273 video_driver_update_viewport(_viewport, forceFull, _keepAspect); 274 275 // update matrix 276 _context.viewport = _viewport; 277 278 _viewportMVP.outputSize = simd_make_float2(_viewport->full_width, _viewport->full_height); 279} 280 281#pragma mark - video 282 283- (void)setVideo:(const video_info_t *)video 284{ 285 286} 287 288- (bool)renderFrame:(const void *)frame 289 data:(void*)data 290 width:(unsigned)width 291 height:(unsigned)height 292 frameCount:(uint64_t)frameCount 293 pitch:(unsigned)pitch 294 msg:(const char *)msg 295 info:(video_frame_info_t *)video_info 296{ 297 @autoreleasepool 298 { 299 bool statistics_show = video_info->statistics_show; 300 301 [self _beginFrame]; 302 303 _frameView.frameCount = frameCount; 304 if (frame && width && height) 305 { 306 _frameView.size = CGSizeMake(width, height); 307 [_frameView updateFrame:frame pitch:pitch]; 308 } 309 310 [self _drawCore]; 311 [self _drawMenu:video_info]; 312 313 id<MTLRenderCommandEncoder> rce = _context.rce; 314 315#ifdef HAVE_OVERLAY 316 if (_overlay.enabled) 317 { 318 [rce pushDebugGroup:@"overlay"]; 319 [_context resetRenderViewport:_overlay.fullscreen ? kFullscreenViewport : kVideoViewport]; 320 [rce setRenderPipelineState:[_context getStockShader:VIDEO_SHADER_STOCK_BLEND blend:YES]]; 321 [rce setVertexBytes:_context.uniforms length:sizeof(*_context.uniforms) atIndex:BufferIndexUniforms]; 322 [rce setFragmentSamplerState:_samplerStateLinear atIndex:SamplerIndexDraw]; 323 [_overlay drawWithEncoder:rce]; 324 [rce popDebugGroup]; 325 } 326#endif 327 328 if (statistics_show) 329 { 330 struct font_params *osd_params = (struct font_params *)&video_info->osd_stat_params; 331 332 if (osd_params) 333 { 334 [rce pushDebugGroup:@"video stats"]; 335 font_driver_render_msg(data, video_info->stat_text, osd_params, NULL); 336 [rce popDebugGroup]; 337 } 338 } 339 340#ifdef HAVE_GFX_WIDGETS 341 [rce pushDebugGroup:@"display widgets"]; 342 if (video_info->widgets_active) 343 gfx_widgets_frame(video_info); 344 [rce popDebugGroup]; 345#endif 346 347 if (msg && *msg) 348 { 349 [rce pushDebugGroup:@"message"]; 350 [self _renderMessage:msg data:data]; 351 [rce popDebugGroup]; 352 } 353 354 [self _endFrame]; 355 } 356 357 return YES; 358} 359 360- (void)_renderMessage:(const char *)msg 361 data:(void*)data 362{ 363 settings_t *settings = config_get_ptr(); 364 bool msg_bgcolor_enable = settings->bools.video_msg_bgcolor_enable; 365 366 if (msg_bgcolor_enable) 367 { 368 float r, g, b, a; 369 int msg_width = 370 font_driver_get_message_width(NULL, msg, (unsigned)strlen(msg), 1.0f); 371 float font_size = settings->floats.video_font_size; 372 unsigned bgcolor_red 373 = settings->uints.video_msg_bgcolor_red; 374 unsigned bgcolor_green 375 = settings->uints.video_msg_bgcolor_green; 376 unsigned bgcolor_blue 377 = settings->uints.video_msg_bgcolor_blue; 378 float bgcolor_opacity = settings->floats.video_msg_bgcolor_opacity; 379 float x = settings->floats.video_msg_pos_x; 380 float y = 1.0f - settings->floats.video_msg_pos_y; 381 float width = msg_width / (float)_viewport->full_width; 382 float height = font_size / (float)_viewport->full_height; 383 384 float x2 = 0.005f; /* extend background around text */ 385 float y2 = 0.005f; 386 387 y -= height; 388 389 x -= x2; 390 y -= y2; 391 width += x2; 392 height += y2; 393 394 r = bgcolor_red / 255.0f; 395 g = bgcolor_green / 255.0f; 396 b = bgcolor_blue / 255.0f; 397 a = bgcolor_opacity; 398 399 [_context resetRenderViewport:kFullscreenViewport]; 400 [_context drawQuadX:x y:y w:width h:height r:r g:g b:b a:a]; 401 } 402 403 font_driver_render_msg(data, msg, NULL, NULL); 404} 405 406- (void)_beginFrame 407{ 408 video_viewport_t vp = *_viewport; 409 video_driver_update_viewport(_viewport, NO, _keepAspect); 410 411 if (memcmp(&vp, _viewport, sizeof(vp)) != 0) 412 _context.viewport = _viewport; 413 414 [_context begin]; 415} 416 417- (void)_drawCore 418{ 419 id<MTLRenderCommandEncoder> rce = _context.rce; 420 421 /* draw back buffer */ 422 [rce pushDebugGroup:@"core frame"]; 423 [_frameView drawWithContext:_context]; 424 425 if ((_frameView.drawState & ViewDrawStateEncoder) != 0) 426 { 427 [rce setVertexBytes:_context.uniforms length:sizeof(*_context.uniforms) atIndex:BufferIndexUniforms]; 428 [rce setRenderPipelineState:_t_pipelineStateNoAlpha]; 429 if (_frameView.filter == RTextureFilterNearest) 430 { 431 [rce setFragmentSamplerState:_samplerStateNearest atIndex:SamplerIndexDraw]; 432 } 433 else 434 { 435 [rce setFragmentSamplerState:_samplerStateLinear atIndex:SamplerIndexDraw]; 436 } 437 [_frameView drawWithEncoder:rce]; 438 } 439 [rce popDebugGroup]; 440} 441 442- (void)_drawMenu:(video_frame_info_t *)video_info 443{ 444 bool menu_is_alive = video_info->menu_is_alive; 445 446 if (!_menu.enabled) 447 return; 448 449 id<MTLRenderCommandEncoder> rce = _context.rce; 450 451 if (_menu.hasFrame) 452 { 453 [rce pushDebugGroup:@"menu frame"]; 454 [_menu.view drawWithContext:_context]; 455 [rce setVertexBytes:_context.uniforms length:sizeof(*_context.uniforms) atIndex:BufferIndexUniforms]; 456 [rce setRenderPipelineState:_t_pipelineState]; 457 if (_menu.view.filter == RTextureFilterNearest) 458 { 459 [rce setFragmentSamplerState:_samplerStateNearest atIndex:SamplerIndexDraw]; 460 } 461 else 462 { 463 [rce setFragmentSamplerState:_samplerStateLinear atIndex:SamplerIndexDraw]; 464 } 465 [_menu.view drawWithEncoder:rce]; 466 [rce popDebugGroup]; 467 } 468#if defined(HAVE_MENU) 469 else 470 { 471 [rce pushDebugGroup:@"menu"]; 472 [_context resetRenderViewport:kFullscreenViewport]; 473 menu_driver_frame(menu_is_alive, video_info); 474 [rce popDebugGroup]; 475 } 476#endif 477} 478 479- (void)_endFrame 480{ 481 [_context end]; 482} 483 484- (void)setNeedsResize 485{ 486 // TODO(sgc): resize all drawables 487} 488 489- (void)setRotation:(unsigned)rotation 490{ 491 [_context setRotation:rotation]; 492} 493 494- (Uniforms *)viewportMVP 495{ 496 return &_viewportMVP; 497} 498 499#pragma mark - MTKViewDelegate 500 501- (void)mtkView:(MTKView *)view drawableSizeWillChange:(CGSize)size 502{ 503 NSLog(@"mtkView drawableSizeWillChange to: %f x %f",size.width,size.height); 504#ifdef HAVE_COCOATOUCH 505 CGFloat scale = [[UIScreen mainScreen] scale]; 506 [self setViewportWidth:(unsigned int)view.bounds.size.width*scale height:(unsigned int)view.bounds.size.height*scale forceFull:NO allowRotate:YES]; 507#else 508 [self setViewportWidth:(unsigned int)size.width height:(unsigned int)size.height forceFull:NO allowRotate:YES]; 509#endif 510} 511 512- (void)drawInMTKView:(MTKView *)view 513{ 514 515} 516 517@end 518 519@implementation MetalMenu 520{ 521 Context *_context; 522 TexturedView *_view; 523 bool _enabled; 524} 525 526- (instancetype)initWithContext:(Context *)context 527{ 528 if (self = [super init]) 529 { 530 _context = context; 531 } 532 return self; 533} 534 535- (bool)hasFrame 536{ 537 return _view != nil; 538} 539 540- (void)setEnabled:(bool)enabled 541{ 542 if (_enabled == enabled) return; 543 _enabled = enabled; 544 _view.visible = enabled; 545} 546 547- (bool)enabled 548{ 549 return _enabled; 550} 551 552- (void)updateWidth:(int)width 553 height:(int)height 554 format:(RPixelFormat)format 555 filter:(RTextureFilter)filter 556{ 557 CGSize size = CGSizeMake(width, height); 558 559 if (_view) 560 { 561 if (!(CGSizeEqualToSize(_view.size, size) && 562 _view.format == format && 563 _view.filter == filter)) 564 { 565 _view = nil; 566 } 567 } 568 569 if (!_view) 570 { 571 ViewDescriptor *vd = [ViewDescriptor new]; 572 vd.format = format; 573 vd.filter = filter; 574 vd.size = size; 575 _view = [[TexturedView alloc] initWithDescriptor:vd context:_context]; 576 _view.visible = _enabled; 577 } 578} 579 580- (void)updateFrame:(void const *)source 581{ 582 [_view updateFrame:source pitch:RPixelFormatToBPP(_view.format) * (NSUInteger)_view.size.width]; 583} 584 585@end 586 587#pragma mark - FrameView 588 589#define MTLALIGN(x) __attribute__((aligned(x))) 590 591typedef struct 592{ 593 float x; 594 float y; 595 float z; 596 float w; 597} float4_t; 598 599typedef struct texture 600{ 601 __unsafe_unretained id<MTLTexture> view; 602 float4_t size_data; 603} texture_t; 604 605typedef struct MTLALIGN(16) 606{ 607 matrix_float4x4 mvp; 608 609 struct 610 { 611 texture_t texture[GFX_MAX_FRAME_HISTORY + 1]; 612 MTLViewport viewport; 613 float4_t output_size; 614 } frame; 615 616 struct 617 { 618 __unsafe_unretained id<MTLBuffer> buffers[SLANG_CBUFFER_MAX]; 619 texture_t rt; 620 texture_t feedback; 621 uint32_t frame_count; 622 int32_t frame_direction; 623 pass_semantics_t semantics; 624 MTLViewport viewport; 625 __unsafe_unretained id<MTLRenderPipelineState> _state; 626 } pass[GFX_MAX_SHADERS]; 627 628 texture_t luts[GFX_MAX_TEXTURES]; 629 630} engine_t; 631 632@implementation FrameView 633{ 634 Context *_context; 635 id<MTLTexture> _texture; // final render texture 636 Vertex _v[4]; 637 VertexSlang _vertex[4]; 638 CGSize _size; // size of view in pixels 639 CGRect _frame; 640 NSUInteger _bpp; 641 642 id<MTLTexture> _src; // src texture 643 bool _srcDirty; 644 645 id<MTLSamplerState> _samplers[RARCH_FILTER_MAX][RARCH_WRAP_MAX]; 646 struct video_shader *_shader; 647 648 engine_t _engine; 649 650 bool resize_render_targets; 651 bool init_history; 652 video_viewport_t *_viewport; 653} 654 655- (instancetype)initWithDescriptor:(ViewDescriptor *)d context:(Context *)c 656{ 657 self = [super init]; 658 if (self) 659 { 660 _context = c; 661 _format = d.format; 662 _bpp = RPixelFormatToBPP(_format); 663 _filter = d.filter; 664 if (_format == RPixelFormatBGRA8Unorm || _format == RPixelFormatBGRX8Unorm) 665 { 666 _drawState = ViewDrawStateEncoder; 667 } 668 else 669 { 670 _drawState = ViewDrawStateAll; 671 } 672 _visible = YES; 673 _engine.mvp = matrix_proj_ortho(0, 1, 0, 1); 674 [self _initSamplers]; 675 676 self.size = d.size; 677 self.frame = CGRectMake(0, 0, 1, 1); 678 resize_render_targets = YES; 679 680 // init slang vertex buffer 681 VertexSlang v[4] = { 682 {simd_make_float4(0, 1, 0, 1), simd_make_float2(0, 1)}, 683 {simd_make_float4(1, 1, 0, 1), simd_make_float2(1, 1)}, 684 {simd_make_float4(0, 0, 0, 1), simd_make_float2(0, 0)}, 685 {simd_make_float4(1, 0, 0, 1), simd_make_float2(1, 0)}, 686 }; 687 memcpy(_vertex, v, sizeof(_vertex)); 688 } 689 return self; 690} 691 692- (void)_initSamplers 693{ 694 MTLSamplerDescriptor *sd = [MTLSamplerDescriptor new]; 695 696 /* Initialize samplers */ 697 for (unsigned i = 0; i < RARCH_WRAP_MAX; i++) 698 { 699 switch (i) 700 { 701 case RARCH_WRAP_BORDER: 702#if defined(HAVE_COCOATOUCH) 703 sd.sAddressMode = MTLSamplerAddressModeClampToZero; 704#else 705 sd.sAddressMode = MTLSamplerAddressModeClampToBorderColor; 706#endif 707 break; 708 709 case RARCH_WRAP_EDGE: 710 sd.sAddressMode = MTLSamplerAddressModeClampToEdge; 711 break; 712 713 case RARCH_WRAP_REPEAT: 714 sd.sAddressMode = MTLSamplerAddressModeRepeat; 715 break; 716 717 case RARCH_WRAP_MIRRORED_REPEAT: 718 sd.sAddressMode = MTLSamplerAddressModeMirrorRepeat; 719 break; 720 721 default: 722 continue; 723 } 724 sd.tAddressMode = sd.sAddressMode; 725 sd.rAddressMode = sd.sAddressMode; 726 sd.minFilter = MTLSamplerMinMagFilterLinear; 727 sd.magFilter = MTLSamplerMinMagFilterLinear; 728 729 id<MTLSamplerState> ss = [_context.device newSamplerStateWithDescriptor:sd]; 730 _samplers[RARCH_FILTER_LINEAR][i] = ss; 731 732 sd.minFilter = MTLSamplerMinMagFilterNearest; 733 sd.magFilter = MTLSamplerMinMagFilterNearest; 734 735 ss = [_context.device newSamplerStateWithDescriptor:sd]; 736 _samplers[RARCH_FILTER_NEAREST][i] = ss; 737 } 738} 739 740- (void)setFilteringIndex:(int)index smooth:(bool)smooth 741{ 742 for (int i = 0; i < RARCH_WRAP_MAX; i++) 743 { 744 if (smooth) 745 _samplers[RARCH_FILTER_UNSPEC][i] = _samplers[RARCH_FILTER_LINEAR][i]; 746 else 747 _samplers[RARCH_FILTER_UNSPEC][i] = _samplers[RARCH_FILTER_NEAREST][i]; 748 } 749} 750 751- (void)setSize:(CGSize)size 752{ 753 if (CGSizeEqualToSize(_size, size)) 754 return; 755 756 _size = size; 757 758 resize_render_targets = YES; 759 760 if (_format != RPixelFormatBGRA8Unorm && _format != RPixelFormatBGRX8Unorm) 761 { 762 MTLTextureDescriptor *td = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatR16Uint 763 width:(NSUInteger)size.width 764 height:(NSUInteger)size.height 765 mipmapped:NO]; 766 _src = [_context.device newTextureWithDescriptor:td]; 767 } 768} 769 770- (CGSize)size 771{ 772 return _size; 773} 774 775- (void)setFrame:(CGRect)frame 776{ 777 if (CGRectEqualToRect(_frame, frame)) 778 return; 779 780 _frame = frame; 781 782 // update vertices 783 CGPoint o = frame.origin; 784 CGSize s = frame.size; 785 786 CGFloat l = o.x; 787 CGFloat t = o.y; 788 CGFloat r = o.x + s.width; 789 CGFloat b = o.y + s.height; 790 791 Vertex v[4] = { 792 {simd_make_float3(l, b, 0), simd_make_float2(0, 1)}, 793 {simd_make_float3(r, b, 0), simd_make_float2(1, 1)}, 794 {simd_make_float3(l, t, 0), simd_make_float2(0, 0)}, 795 {simd_make_float3(r, t, 0), simd_make_float2(1, 0)}, 796 }; 797 memcpy(_v, v, sizeof(_v)); 798} 799 800- (CGRect)frame 801{ 802 return _frame; 803} 804 805- (void)_convertFormat 806{ 807 if ( _format == RPixelFormatBGRA8Unorm 808 || _format == RPixelFormatBGRX8Unorm) 809 return; 810 811 if (!_srcDirty) 812 return; 813 814 [_context convertFormat:_format from:_src to:_texture]; 815 _srcDirty = NO; 816} 817 818- (void)_updateHistory 819{ 820 if (_shader) 821 { 822 if (_shader->history_size) 823 { 824 if (init_history) 825 [self _initHistory]; 826 else 827 { 828 int k; 829 /* todo: what about frame-duping ? 830 * maybe clone d3d10_texture_t with AddRef */ 831 texture_t tmp = _engine.frame.texture[_shader->history_size]; 832 for (k = _shader->history_size; k > 0; k--) 833 _engine.frame.texture[k] = _engine.frame.texture[k - 1]; 834 _engine.frame.texture[0] = tmp; 835 } 836 } 837 } 838 839 /* either no history, or we moved a texture of a different size in the front slot */ 840 if (_engine.frame.texture[0].size_data.x != _size.width || 841 _engine.frame.texture[0].size_data.y != _size.height) 842 { 843 MTLTextureDescriptor *td = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatBGRA8Unorm 844 width:(NSUInteger)_size.width 845 height:(NSUInteger)_size.height 846 mipmapped:false]; 847 td.usage = MTLTextureUsageShaderRead | MTLTextureUsageShaderWrite; 848 [self _initTexture:&_engine.frame.texture[0] withDescriptor:td]; 849 } 850} 851 852- (bool)readViewport:(uint8_t *)buffer isIdle:(bool)isIdle 853{ 854 bool enabled = _context.captureEnabled; 855 if (!enabled) 856 _context.captureEnabled = YES; 857 858 video_driver_cached_frame(); 859 860 bool res = [_context readBackBuffer:buffer]; 861 862 if (!enabled) 863 _context.captureEnabled = NO; 864 865 return res; 866} 867 868- (void)updateFrame:(void const *)src pitch:(NSUInteger)pitch 869{ 870 if (_shader && (_engine.frame.output_size.x != _viewport->width || 871 _engine.frame.output_size.y != _viewport->height)) 872 resize_render_targets = YES; 873 874 _engine.frame.viewport.originX = _viewport->x; 875 _engine.frame.viewport.originY = _viewport->y; 876 _engine.frame.viewport.width = _viewport->width; 877 _engine.frame.viewport.height = _viewport->height; 878 _engine.frame.viewport.znear = 0.0f; 879 _engine.frame.viewport.zfar = 1.0f; 880 _engine.frame.output_size.x = _viewport->width; 881 _engine.frame.output_size.y = _viewport->height; 882 _engine.frame.output_size.z = 1.0f / _viewport->width; 883 _engine.frame.output_size.w = 1.0f / _viewport->height; 884 885 if (resize_render_targets) 886 [self _updateRenderTargets]; 887 888 [self _updateHistory]; 889 890 if (_format == RPixelFormatBGRA8Unorm || _format == RPixelFormatBGRX8Unorm) 891 { 892 id<MTLTexture> tex = _engine.frame.texture[0].view; 893 [tex replaceRegion:MTLRegionMake2D(0, 0, (NSUInteger)_size.width, (NSUInteger)_size.height) 894 mipmapLevel:0 withBytes:src 895 bytesPerRow:pitch]; 896 } 897 else 898 { 899 [_src replaceRegion:MTLRegionMake2D(0, 0, (NSUInteger)_size.width, (NSUInteger)_size.height) 900 mipmapLevel:0 withBytes:src 901 bytesPerRow:(NSUInteger)(pitch)]; 902 _srcDirty = YES; 903 } 904} 905 906- (void)_initTexture:(texture_t *)t withDescriptor:(MTLTextureDescriptor *)td 907{ 908 STRUCT_ASSIGN(t->view, [_context.device newTextureWithDescriptor:td]); 909 t->size_data.x = td.width; 910 t->size_data.y = td.height; 911 t->size_data.z = 1.0f / td.width; 912 t->size_data.w = 1.0f / td.height; 913} 914 915- (void)_initHistory 916{ 917 int i; 918 MTLTextureDescriptor *td = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatBGRA8Unorm 919 width:(NSUInteger)_size.width 920 height:(NSUInteger)_size.height 921 mipmapped:false]; 922 td.usage = MTLTextureUsageShaderRead | MTLTextureUsageShaderWrite | MTLTextureUsageRenderTarget; 923 924 for (i = 0; i < _shader->history_size + 1; i++) 925 [self _initTexture:&_engine.frame.texture[i] withDescriptor:td]; 926 init_history = NO; 927} 928 929- (void)drawWithEncoder:(id<MTLRenderCommandEncoder>)rce 930{ 931 if (_texture) 932 { 933 [rce setViewport:_engine.frame.viewport]; 934 [rce setVertexBytes:&_v length:sizeof(_v) atIndex:BufferIndexPositions]; 935 [rce setFragmentTexture:_texture atIndex:TextureIndexColor]; 936 [rce drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4]; 937 } 938} 939 940- (void)drawWithContext:(Context *)ctx 941{ 942 unsigned i; 943 _texture = _engine.frame.texture[0].view; 944 [self _convertFormat]; 945 946 if (!_shader || _shader->passes == 0) 947 return; 948 949 for (i = 0; i < _shader->passes; i++) 950 { 951 if (_shader->pass[i].feedback) 952 { 953 texture_t tmp = _engine.pass[i].feedback; 954 _engine.pass[i].feedback = _engine.pass[i].rt; 955 _engine.pass[i].rt = tmp; 956 } 957 } 958 959 id<MTLCommandBuffer> cb = ctx.blitCommandBuffer; 960 [cb pushDebugGroup:@"shaders"]; 961 962 MTLRenderPassDescriptor *rpd = [MTLRenderPassDescriptor new]; 963 rpd.colorAttachments[0].loadAction = MTLLoadActionDontCare; 964 rpd.colorAttachments[0].storeAction = MTLStoreActionStore; 965 966 for (unsigned i = 0; i < _shader->passes; i++) 967 { 968 id<MTLRenderCommandEncoder> rce = nil; 969 970 BOOL backBuffer = (_engine.pass[i].rt.view == nil); 971 972 if (backBuffer) 973 { 974 rce = _context.rce; 975 } 976 else 977 { 978 rpd.colorAttachments[0].texture = _engine.pass[i].rt.view; 979 rce = [cb renderCommandEncoderWithDescriptor:rpd]; 980 } 981 982 [rce setRenderPipelineState:_engine.pass[i]._state]; 983 984 NSURL *shaderPath = [NSURL fileURLWithPath:_engine.pass[i]._state.label]; 985 rce.label = shaderPath.lastPathComponent.stringByDeletingPathExtension; 986 987 _engine.pass[i].frame_count = (uint32_t)_frameCount; 988 if (_shader->pass[i].frame_count_mod) 989 _engine.pass[i].frame_count %= _shader->pass[i].frame_count_mod; 990 991#ifdef HAVE_REWIND 992 if (state_manager_frame_is_reversed()) 993 _engine.pass[i].frame_direction = -1; 994 else 995#else 996 _engine.pass[i].frame_direction = 1; 997#endif 998 999 for (unsigned j = 0; j < SLANG_CBUFFER_MAX; j++) 1000 { 1001 id<MTLBuffer> buffer = _engine.pass[i].buffers[j]; 1002 cbuffer_sem_t *buffer_sem = &_engine.pass[i].semantics.cbuffers[j]; 1003 1004 if (buffer_sem->stage_mask && buffer_sem->uniforms) 1005 { 1006 void *data = buffer.contents; 1007 uniform_sem_t *uniform = buffer_sem->uniforms; 1008 1009 while (uniform->size) 1010 { 1011 if (uniform->data) 1012 memcpy((uint8_t *)data + uniform->offset, uniform->data, uniform->size); 1013 uniform++; 1014 } 1015 1016 if (buffer_sem->stage_mask & SLANG_STAGE_VERTEX_MASK) 1017 [rce setVertexBuffer:buffer offset:0 atIndex:buffer_sem->binding]; 1018 1019 if (buffer_sem->stage_mask & SLANG_STAGE_FRAGMENT_MASK) 1020 [rce setFragmentBuffer:buffer offset:0 atIndex:buffer_sem->binding]; 1021#if !defined(HAVE_COCOATOUCH) 1022 [buffer didModifyRange:NSMakeRange(0, buffer.length)]; 1023#endif 1024 } 1025 } 1026 1027 __unsafe_unretained id<MTLTexture> textures[SLANG_NUM_BINDINGS] = {NULL}; 1028 id<MTLSamplerState> samplers[SLANG_NUM_BINDINGS] = {NULL}; 1029 1030 texture_sem_t *texture_sem = _engine.pass[i].semantics.textures; 1031 while (texture_sem->stage_mask) 1032 { 1033 int binding = texture_sem->binding; 1034 id<MTLTexture> tex = (__bridge id<MTLTexture>)*(void **)texture_sem->texture_data; 1035 textures[binding] = tex; 1036 samplers[binding] = _samplers[texture_sem->filter][texture_sem->wrap]; 1037 texture_sem++; 1038 } 1039 1040 if (backBuffer) 1041 { 1042 [rce setViewport:_engine.frame.viewport]; 1043 } 1044 else 1045 { 1046 [rce setViewport:_engine.pass[i].viewport]; 1047 } 1048 1049 [rce setFragmentTextures:textures withRange:NSMakeRange(0, SLANG_NUM_BINDINGS)]; 1050 [rce setFragmentSamplerStates:samplers withRange:NSMakeRange(0, SLANG_NUM_BINDINGS)]; 1051 [rce setVertexBytes:_vertex length:sizeof(_vertex) atIndex:4]; 1052 [rce drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4]; 1053 1054 if (!backBuffer) 1055 { 1056 [rce endEncoding]; 1057 } 1058 1059 _texture = _engine.pass[i].rt.view; 1060 } 1061 1062 if (_texture == nil) 1063 _drawState = ViewDrawStateContext; 1064 else 1065 _drawState = ViewDrawStateAll; 1066 1067 [cb popDebugGroup]; 1068} 1069 1070- (void)_updateRenderTargets 1071{ 1072 if (!_shader || !resize_render_targets) return; 1073 1074 // release existing targets 1075 for (int i = 0; i < _shader->passes; i++) 1076 { 1077 STRUCT_ASSIGN(_engine.pass[i].rt.view, nil); 1078 STRUCT_ASSIGN(_engine.pass[i].feedback.view, nil); 1079 memset(&_engine.pass[i].rt, 0, sizeof(_engine.pass[i].rt)); 1080 memset(&_engine.pass[i].feedback, 0, sizeof(_engine.pass[i].feedback)); 1081 } 1082 1083 NSUInteger width = (NSUInteger)_size.width, height = (NSUInteger)_size.height; 1084 1085 for (unsigned i = 0; i < _shader->passes; i++) 1086 { 1087 struct video_shader_pass *shader_pass = &_shader->pass[i]; 1088 1089 if (shader_pass->fbo.valid) 1090 { 1091 switch (shader_pass->fbo.type_x) 1092 { 1093 case RARCH_SCALE_INPUT: 1094 width *= shader_pass->fbo.scale_x; 1095 break; 1096 1097 case RARCH_SCALE_VIEWPORT: 1098 width = (NSUInteger)(_viewport->width * shader_pass->fbo.scale_x); 1099 break; 1100 1101 case RARCH_SCALE_ABSOLUTE: 1102 width = shader_pass->fbo.abs_x; 1103 break; 1104 1105 default: 1106 break; 1107 } 1108 1109 if (!width) 1110 width = _viewport->width; 1111 1112 switch (shader_pass->fbo.type_y) 1113 { 1114 case RARCH_SCALE_INPUT: 1115 height *= shader_pass->fbo.scale_y; 1116 break; 1117 1118 case RARCH_SCALE_VIEWPORT: 1119 height = (NSUInteger)(_viewport->height * shader_pass->fbo.scale_y); 1120 break; 1121 1122 case RARCH_SCALE_ABSOLUTE: 1123 height = shader_pass->fbo.abs_y; 1124 break; 1125 1126 default: 1127 break; 1128 } 1129 1130 if (!height) 1131 height = _viewport->height; 1132 } 1133 else if (i == (_shader->passes - 1)) 1134 { 1135 width = _viewport->width; 1136 height = _viewport->height; 1137 } 1138 1139 /* Updating framebuffer size */ 1140 1141 MTLPixelFormat fmt = SelectOptimalPixelFormat(glslang_format_to_metal(_engine.pass[i].semantics.format)); 1142 if ((i != (_shader->passes - 1)) || 1143 (width != _viewport->width) || (height != _viewport->height) || 1144 fmt != MTLPixelFormatBGRA8Unorm) 1145 { 1146 _engine.pass[i].viewport.width = width; 1147 _engine.pass[i].viewport.height = height; 1148 _engine.pass[i].viewport.znear = 0.0; 1149 _engine.pass[i].viewport.zfar = 1.0; 1150 1151 MTLTextureDescriptor *td = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:fmt 1152 width:width 1153 height:height 1154 mipmapped:false]; 1155 td.storageMode = MTLStorageModePrivate; 1156 td.usage = MTLTextureUsageShaderRead | MTLTextureUsageRenderTarget; 1157 [self _initTexture:&_engine.pass[i].rt withDescriptor:td]; 1158 1159 if (shader_pass->feedback) 1160 { 1161 [self _initTexture:&_engine.pass[i].feedback withDescriptor:td]; 1162 } 1163 } 1164 else 1165 { 1166 _engine.pass[i].rt.size_data.x = width; 1167 _engine.pass[i].rt.size_data.y = height; 1168 _engine.pass[i].rt.size_data.z = 1.0f / width; 1169 _engine.pass[i].rt.size_data.w = 1.0f / height; 1170 } 1171 } 1172 1173 resize_render_targets = NO; 1174} 1175 1176- (void)_freeVideoShader:(struct video_shader *)shader 1177{ 1178 if (!shader) 1179 return; 1180 1181 for (int i = 0; i < GFX_MAX_SHADERS; i++) 1182 { 1183 STRUCT_ASSIGN(_engine.pass[i].rt.view, nil); 1184 STRUCT_ASSIGN(_engine.pass[i].feedback.view, nil); 1185 memset(&_engine.pass[i].rt, 0, sizeof(_engine.pass[i].rt)); 1186 memset(&_engine.pass[i].feedback, 0, sizeof(_engine.pass[i].feedback)); 1187 1188 STRUCT_ASSIGN(_engine.pass[i]._state, nil); 1189 1190 for (unsigned j = 0; j < SLANG_CBUFFER_MAX; j++) 1191 { 1192 STRUCT_ASSIGN(_engine.pass[i].buffers[j], nil); 1193 } 1194 } 1195 1196 for (int i = 0; i < GFX_MAX_TEXTURES; i++) 1197 { 1198 STRUCT_ASSIGN(_engine.luts[i].view, nil); 1199 } 1200 1201 free(shader); 1202} 1203 1204- (BOOL)setShaderFromPath:(NSString *)path 1205{ 1206 [self _freeVideoShader:_shader]; 1207 _shader = nil; 1208 1209 struct video_shader *shader = (struct video_shader *)calloc(1, sizeof(*shader)); 1210 settings_t *settings = config_get_ptr(); 1211 const char *dir_video_shader = settings->paths.directory_video_shader; 1212 NSString *shadersPath = [NSString stringWithFormat:@"%s/", dir_video_shader]; 1213 1214 @try 1215 { 1216 unsigned i; 1217 texture_t *source = NULL; 1218 if (!video_shader_load_preset_into_shader(path.UTF8String, shader)) 1219 return NO; 1220 1221 source = &_engine.frame.texture[0]; 1222 1223 for (i = 0; i < shader->passes; source = &_engine.pass[i++].rt) 1224 { 1225 matrix_float4x4 *mvp = (i == shader->passes-1) ? &_context.uniforms->projectionMatrix : &_engine.mvp; 1226 1227 /* clang-format off */ 1228 semantics_map_t semantics_map = { 1229 { 1230 /* Original */ 1231 {&_engine.frame.texture[0].view, 0, 1232 &_engine.frame.texture[0].size_data, 0}, 1233 1234 /* Source */ 1235 {&source->view, 0, 1236 &source->size_data, 0}, 1237 1238 /* OriginalHistory */ 1239 {&_engine.frame.texture[0].view, sizeof(*_engine.frame.texture), 1240 &_engine.frame.texture[0].size_data, sizeof(*_engine.frame.texture)}, 1241 1242 /* PassOutput */ 1243 {&_engine.pass[0].rt.view, sizeof(*_engine.pass), 1244 &_engine.pass[0].rt.size_data, sizeof(*_engine.pass)}, 1245 1246 /* PassFeedback */ 1247 {&_engine.pass[0].feedback.view, sizeof(*_engine.pass), 1248 &_engine.pass[0].feedback.size_data, sizeof(*_engine.pass)}, 1249 1250 /* User */ 1251 {&_engine.luts[0].view, sizeof(*_engine.luts), 1252 &_engine.luts[0].size_data, sizeof(*_engine.luts)}, 1253 }, 1254 { 1255 mvp, /* MVP */ 1256 &_engine.pass[i].rt.size_data, /* OutputSize */ 1257 &_engine.frame.output_size, /* FinalViewportSize */ 1258 &_engine.pass[i].frame_count, /* FrameCount */ 1259 &_engine.pass[i].frame_direction, /* FrameDirection */ 1260 } 1261 }; 1262 /* clang-format on */ 1263 1264 if (!slang_process(shader, i, RARCH_SHADER_METAL, 20000, &semantics_map, &_engine.pass[i].semantics)) 1265 return NO; 1266 1267#ifdef DEBUG 1268 bool save_msl = true; 1269#else 1270 bool save_msl = false; 1271#endif 1272 NSString *vs_src = [NSString stringWithUTF8String:shader->pass[i].source.string.vertex]; 1273 NSString *fs_src = [NSString stringWithUTF8String:shader->pass[i].source.string.fragment]; 1274 1275 // vertex descriptor 1276 @try 1277 { 1278 NSError *err; 1279 MTLVertexDescriptor *vd = [MTLVertexDescriptor new]; 1280 vd.attributes[0].offset = offsetof(VertexSlang, position); 1281 vd.attributes[0].format = MTLVertexFormatFloat4; 1282 vd.attributes[0].bufferIndex = 4; 1283 vd.attributes[1].offset = offsetof(VertexSlang, texCoord); 1284 vd.attributes[1].format = MTLVertexFormatFloat2; 1285 vd.attributes[1].bufferIndex = 4; 1286 vd.layouts[4].stride = sizeof(VertexSlang); 1287 vd.layouts[4].stepFunction = MTLVertexStepFunctionPerVertex; 1288 1289 MTLRenderPipelineDescriptor *psd = [MTLRenderPipelineDescriptor new]; 1290 1291 psd.label = [[NSString stringWithUTF8String:shader->pass[i].source.path] 1292 stringByReplacingOccurrencesOfString:shadersPath withString:@""]; 1293 1294 MTLRenderPipelineColorAttachmentDescriptor *ca = psd.colorAttachments[0]; 1295 1296 ca.pixelFormat = SelectOptimalPixelFormat(glslang_format_to_metal(_engine.pass[i].semantics.format)); 1297 1298 /* TODO(sgc): confirm we never need blending for render passes */ 1299 ca.blendingEnabled = NO; 1300 ca.sourceAlphaBlendFactor = MTLBlendFactorSourceAlpha; 1301 ca.sourceRGBBlendFactor = MTLBlendFactorSourceAlpha; 1302 ca.destinationAlphaBlendFactor = MTLBlendFactorOneMinusSourceAlpha; 1303 ca.destinationRGBBlendFactor = MTLBlendFactorOneMinusSourceAlpha; 1304 1305 psd.sampleCount = 1; 1306 psd.vertexDescriptor = vd; 1307 1308 id<MTLLibrary> lib = [_context.device newLibraryWithSource:vs_src options:nil error:&err]; 1309 if (err != nil) 1310 { 1311 if (lib == nil) 1312 { 1313 save_msl = true; 1314 RARCH_ERR("[Metal]: unable to compile vertex shader: %s\n", err.localizedDescription.UTF8String); 1315 return NO; 1316 } 1317#if DEBUG 1318 RARCH_WARN("[Metal]: warnings compiling vertex shader: %s\n", err.localizedDescription.UTF8String); 1319#endif 1320 } 1321 1322 psd.vertexFunction = [lib newFunctionWithName:@"main0"]; 1323 1324 lib = [_context.device newLibraryWithSource:fs_src options:nil error:&err]; 1325 if (err != nil) 1326 { 1327 if (lib == nil) 1328 { 1329 save_msl = true; 1330 RARCH_ERR("[Metal]: unable to compile fragment shader: %s\n", err.localizedDescription.UTF8String); 1331 return NO; 1332 } 1333#if DEBUG 1334 RARCH_WARN("[Metal]: warnings compiling fragment shader: %s\n", err.localizedDescription.UTF8String); 1335#endif 1336 } 1337 psd.fragmentFunction = [lib newFunctionWithName:@"main0"]; 1338 1339 STRUCT_ASSIGN(_engine.pass[i]._state, 1340 [_context.device newRenderPipelineStateWithDescriptor:psd error:&err]); 1341 if (err != nil) 1342 { 1343 save_msl = true; 1344 RARCH_ERR("[Metal]: error creating pipeline state for pass %d: %s\n", i, 1345 err.localizedDescription.UTF8String); 1346 return NO; 1347 } 1348 1349 for (unsigned j = 0; j < SLANG_CBUFFER_MAX; j++) 1350 { 1351 unsigned int size = _engine.pass[i].semantics.cbuffers[j].size; 1352 if (size == 0) 1353 continue; 1354 1355 id<MTLBuffer> buf = [_context.device newBufferWithLength:size options:PLATFORM_METAL_RESOURCE_STORAGE_MODE]; 1356 STRUCT_ASSIGN(_engine.pass[i].buffers[j], buf); 1357 } 1358 } @finally 1359 { 1360 if (save_msl) 1361 { 1362 NSError *err = nil; 1363 NSString *basePath = [[NSString stringWithUTF8String:shader->pass[i].source.path] stringByDeletingPathExtension]; 1364 1365 /* Saving Metal shader files... */ 1366 1367 [vs_src writeToFile:[basePath stringByAppendingPathExtension:@"vs.metal"] 1368 atomically:NO 1369 encoding:NSStringEncodingConversionAllowLossy 1370 error:&err]; 1371 if (err != nil) 1372 { 1373 RARCH_ERR("[Metal]: unable to save vertex shader source: %s\n", err.localizedDescription.UTF8String); 1374 } 1375 1376 err = nil; 1377 [fs_src writeToFile:[basePath stringByAppendingPathExtension:@"fs.metal"] 1378 atomically:NO 1379 encoding:NSStringEncodingConversionAllowLossy 1380 error:&err]; 1381 if (err != nil) 1382 { 1383 RARCH_ERR("[Metal]: unable to save fragment shader source: %s\n", 1384 err.localizedDescription.UTF8String); 1385 } 1386 } 1387 1388 free(shader->pass[i].source.string.vertex); 1389 free(shader->pass[i].source.string.fragment); 1390 1391 shader->pass[i].source.string.vertex = NULL; 1392 shader->pass[i].source.string.fragment = NULL; 1393 } 1394 } 1395 1396 for (i = 0; i < shader->luts; i++) 1397 { 1398 struct texture_image image = {0}; 1399 image.supports_rgba = true; 1400 1401 if (!image_texture_load(&image, shader->lut[i].path)) 1402 return NO; 1403 1404 MTLTextureDescriptor *td = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatRGBA8Unorm 1405 width:image.width 1406 height:image.height 1407 mipmapped:shader->lut[i].mipmap]; 1408 td.usage = MTLTextureUsageShaderRead; 1409 [self _initTexture:&_engine.luts[i] withDescriptor:td]; 1410 1411 [_engine.luts[i].view replaceRegion:MTLRegionMake2D(0, 0, image.width, image.height) 1412 mipmapLevel:0 withBytes:image.pixels 1413 bytesPerRow:4 * image.width]; 1414 1415 /* TODO(sgc): generate mip maps */ 1416 image_texture_free(&image); 1417 } 1418 _shader = shader; 1419 shader = nil; 1420 } 1421 @finally 1422 { 1423 if (shader) 1424 { 1425 [self _freeVideoShader:shader]; 1426 } 1427 } 1428 1429 resize_render_targets = YES; 1430 init_history = YES; 1431 1432 return YES; 1433} 1434 1435@end 1436 1437@implementation Overlay 1438{ 1439 Context *_context; 1440 NSMutableArray<id<MTLTexture>> *_images; 1441 id<MTLBuffer> _vert; 1442 bool _vertDirty; 1443} 1444 1445- (instancetype)initWithContext:(Context *)context 1446{ 1447 if (self = [super init]) 1448 { 1449 _context = context; 1450 } 1451 return self; 1452} 1453 1454- (bool)loadImages:(const struct texture_image *)images count:(NSUInteger)count 1455{ 1456 [self _freeImages]; 1457 1458 _images = [NSMutableArray arrayWithCapacity:count]; 1459 1460 NSUInteger needed = sizeof(SpriteVertex) * count * 4; 1461 if (!_vert || _vert.length < needed) 1462 { 1463 _vert = [_context.device newBufferWithLength:needed options:PLATFORM_METAL_RESOURCE_STORAGE_MODE]; 1464 } 1465 1466 for (NSUInteger i = 0; i < count; i++) 1467 { 1468 _images[i] = [_context newTexture:images[i] mipmapped:NO]; 1469 [self updateVertexX:0 y:0 w:1 h:1 index:i]; 1470 [self updateTextureCoordsX:0 y:0 w:1 h:1 index:i]; 1471 [self _updateColorRed:1.0 green:1.0 blue:1.0 alpha:1.0 index:i]; 1472 } 1473 1474 _vertDirty = YES; 1475 1476 return YES; 1477} 1478 1479- (void)drawWithEncoder:(id<MTLRenderCommandEncoder>)rce 1480{ 1481#if !defined(HAVE_COCOATOUCH) 1482 if (_vertDirty) 1483 { 1484 [_vert didModifyRange:NSMakeRange(0, _vert.length)]; 1485 _vertDirty = NO; 1486 } 1487#endif 1488 1489 NSUInteger count = _images.count; 1490 for (NSUInteger i = 0; i < count; ++i) 1491 { 1492 NSUInteger offset = sizeof(SpriteVertex) * 4 * i; 1493 [rce setVertexBuffer:_vert offset:offset atIndex:BufferIndexPositions]; 1494 [rce setFragmentTexture:_images[i] atIndex:TextureIndexColor]; 1495 [rce drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4]; 1496 } 1497} 1498 1499- (SpriteVertex *)_getForIndex:(NSUInteger)index 1500{ 1501 SpriteVertex *pv = (SpriteVertex *)_vert.contents; 1502 return &pv[index * 4]; 1503} 1504 1505- (void)_updateColorRed:(float)r green:(float)g blue:(float)b alpha:(float)a index:(NSUInteger)index 1506{ 1507 simd_float4 color = simd_make_float4(r, g, b, a); 1508 SpriteVertex *pv = [self _getForIndex:index]; 1509 pv[0].color = color; 1510 pv[1].color = color; 1511 pv[2].color = color; 1512 pv[3].color = color; 1513 _vertDirty = YES; 1514} 1515 1516- (void)updateAlpha:(float)alpha index:(NSUInteger)index 1517{ 1518 [self _updateColorRed:1.0 green:1.0 blue:1.0 alpha:alpha index:index]; 1519} 1520 1521- (void)updateVertexX:(float)x y:(float)y w:(float)w h:(float)h index:(NSUInteger)index 1522{ 1523 SpriteVertex *pv = [self _getForIndex:index]; 1524 pv[0].position = simd_make_float2(x, y); 1525 pv[1].position = simd_make_float2(x + w, y); 1526 pv[2].position = simd_make_float2(x, y + h); 1527 pv[3].position = simd_make_float2(x + w, y + h); 1528 _vertDirty = YES; 1529} 1530 1531- (void)updateTextureCoordsX:(float)x y:(float)y w:(float)w h:(float)h index:(NSUInteger)index 1532{ 1533 SpriteVertex *pv = [self _getForIndex:index]; 1534 pv[0].texCoord = simd_make_float2(x, y); 1535 pv[1].texCoord = simd_make_float2(x + w, y); 1536 pv[2].texCoord = simd_make_float2(x, y + h); 1537 pv[3].texCoord = simd_make_float2(x + w, y + h); 1538 _vertDirty = YES; 1539} 1540 1541- (void)_freeImages 1542{ 1543 _images = nil; 1544} 1545 1546@end 1547 1548MTLPixelFormat glslang_format_to_metal(glslang_format fmt) 1549{ 1550#undef FMT2 1551#define FMT2(x, y) case SLANG_FORMAT_##x: return MTLPixelFormat##y 1552 1553 switch (fmt) 1554 { 1555 FMT2(R8_UNORM, R8Unorm); 1556 FMT2(R8_SINT, R8Sint); 1557 FMT2(R8_UINT, R8Uint); 1558 FMT2(R8G8_UNORM, RG8Unorm); 1559 FMT2(R8G8_SINT, RG8Sint); 1560 FMT2(R8G8_UINT, RG8Uint); 1561 FMT2(R8G8B8A8_UNORM, RGBA8Unorm); 1562 FMT2(R8G8B8A8_SINT, RGBA8Sint); 1563 FMT2(R8G8B8A8_UINT, RGBA8Uint); 1564 FMT2(R8G8B8A8_SRGB, RGBA8Unorm_sRGB); 1565 1566 FMT2(A2B10G10R10_UNORM_PACK32, RGB10A2Unorm); 1567 FMT2(A2B10G10R10_UINT_PACK32, RGB10A2Uint); 1568 1569 FMT2(R16_UINT, R16Uint); 1570 FMT2(R16_SINT, R16Sint); 1571 FMT2(R16_SFLOAT, R16Float); 1572 FMT2(R16G16_UINT, RG16Uint); 1573 FMT2(R16G16_SINT, RG16Sint); 1574 FMT2(R16G16_SFLOAT, RG16Float); 1575 FMT2(R16G16B16A16_UINT, RGBA16Uint); 1576 FMT2(R16G16B16A16_SINT, RGBA16Sint); 1577 FMT2(R16G16B16A16_SFLOAT, RGBA16Float); 1578 1579 FMT2(R32_UINT, R32Uint); 1580 FMT2(R32_SINT, R32Sint); 1581 FMT2(R32_SFLOAT, R32Float); 1582 FMT2(R32G32_UINT, RG32Uint); 1583 FMT2(R32G32_SINT, RG32Sint); 1584 FMT2(R32G32_SFLOAT, RG32Float); 1585 FMT2(R32G32B32A32_UINT, RGBA32Uint); 1586 FMT2(R32G32B32A32_SINT, RGBA32Sint); 1587 FMT2(R32G32B32A32_SFLOAT, RGBA32Float); 1588 1589 case SLANG_FORMAT_UNKNOWN: 1590 default: 1591 break; 1592 } 1593#undef FMT2 1594 return MTLPixelFormatInvalid; 1595} 1596 1597MTLPixelFormat SelectOptimalPixelFormat(MTLPixelFormat fmt) 1598{ 1599 switch (fmt) 1600 { 1601 case MTLPixelFormatRGBA8Unorm: 1602 return MTLPixelFormatBGRA8Unorm; 1603 1604 case MTLPixelFormatRGBA8Unorm_sRGB: 1605 return MTLPixelFormatBGRA8Unorm_sRGB; 1606 1607 default: 1608 return fmt; 1609 } 1610} 1611