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