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