diff --git a/Makefile.common b/Makefile.common
index 1bd27ecaf0..817f0fb19b 100644
--- a/Makefile.common
+++ b/Makefile.common
@@ -1585,7 +1585,6 @@ ifeq ($(HAVE_METAL), 1)
DEF_FLAGS += -fobjc-arc
OBJ += \
gfx/common/metal/metal_renderer.o \
- gfx/common/metal_common.o \
gfx/drivers/metal.o \
gfx/drivers_font/metal_raster_font.o \
gfx/drivers_display/gfx_display_metal.o
diff --git a/gfx/common/metal_common.m b/gfx/common/metal_common.m
deleted file mode 100644
index cac9ed0714..0000000000
--- a/gfx/common/metal_common.m
+++ /dev/null
@@ -1,1547 +0,0 @@
-/* RetroArch - A frontend for libretro.
- * Copyright (C) 2018-2019 - Stuart Carnie
- * Copyright (C) 2011-2017 - Daniel De Matteis
- *
- * RetroArch is free software: you can redistribute it and/or modify it under the terms
- * of the GNU General Public License as published by the Free Software Found-
- * ation, either version 3 of the License, or (at your option) any later version.
- *
- * RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
- * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
- * PURPOSE. See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with RetroArch.
- * If not, see .
- */
-
-#import
-#import
-#import
-
-#import
-#import
-#include
-
-#import
-
-#import "metal_common.h"
-
-#include "../../ui/drivers/cocoa/apple_platform.h"
-#include "../../ui/drivers/cocoa/cocoa_common.h"
-
-#ifdef HAVE_REWIND
-#include "../../state_manager.h"
-#endif
-#ifdef HAVE_MENU
-#include "../../menu/menu_driver.h"
-#endif
-#ifdef HAVE_GFX_WIDGETS
-#include "../gfx_widgets.h"
-#endif
-
-#include "../../configuration.h"
-#include "../../verbosity.h"
-
-#define STRUCT_ASSIGN(x, y) \
-{ \
- NSObject * __y = y; \
- if (x != nil) { \
- NSObject * __foo = (__bridge_transfer NSObject *)(__bridge void *)(x); \
- __foo = nil; \
- x = (__bridge __typeof__(x))nil; \
- } \
- if (__y != nil) \
- x = (__bridge __typeof__(x))(__bridge_retained void *)((NSObject *)__y); \
- }
-
-@implementation MetalView
-
-#if !defined(HAVE_COCOATOUCH)
-- (void)keyDown:(NSEvent*)theEvent { }
-#endif
-
-/* Stop the annoying sound when pressing a key. */
-- (BOOL)acceptsFirstResponder { return YES; }
-- (BOOL)isFlipped { return YES; }
-@end
-
-#pragma mark - private categories
-
-@interface FrameView()
-
-@property (nonatomic, readwrite) video_viewport_t *viewport;
-
-- (instancetype)initWithDescriptor:(ViewDescriptor *)td context:(Context *)context;
-- (void)drawWithContext:(Context *)ctx;
-- (void)drawWithEncoder:(id)rce;
-
-@end
-
-@interface MetalMenu()
-@property (nonatomic, readonly) TexturedView *view;
-- (instancetype)initWithContext:(Context *)context;
-@end
-
-@interface Overlay()
-- (instancetype)initWithContext:(Context *)context;
-- (void)drawWithEncoder:(id)rce;
-@end
-
-@implementation MetalDriver
-{
- FrameView *_frameView;
- MetalMenu *_menu;
- Overlay *_overlay;
-
- video_info_t _video;
-
- id _device;
- id _library;
- Context *_context;
-
- CAMetalLayer *_layer;
-
- /* Render target layer state */
- id _t_pipelineState;
- id _t_pipelineStateNoAlpha;
-
- id _samplerStateLinear;
- id _samplerStateNearest;
-
- /* other state */
- Uniforms _viewportMVP;
-}
-
-- (instancetype)initWithVideo:(const video_info_t *)video
- input:(input_driver_t **)input
- inputData:(void **)inputData
-{
- if (self = [super init])
- {
- _device = MTLCreateSystemDefaultDevice();
- MetalView *view = (MetalView *)apple_platform.renderView;
- view.device = _device;
- view.delegate = self;
- _layer = (CAMetalLayer *)view.layer;
-
- if (![self _initMetal])
- return nil;
-
- _video = *video;
- _viewport = (video_viewport_t *)calloc(1, sizeof(video_viewport_t));
- _viewportMVP.projectionMatrix = matrix_proj_ortho(0, 1, 0, 1);
-
- _keepAspect = _video.force_aspect;
-
- gfx_ctx_mode_t mode = {
- .width = _video.width,
- .height = _video.height,
- .fullscreen = _video.fullscreen,
- };
-
- if (mode.width == 0 || mode.height == 0)
- {
- // 0 indicates full screen, so we'll use the view's dimensions, which should already be full screen
- // If this turns out to be the wrong assumption, we can use NSScreen to query the dimensions
- CGSize size = view.frame.size;
- mode.width = (unsigned int)size.width;
- mode.height = (unsigned int)size.height;
- }
-
- [apple_platform setVideoMode:mode];
-
- *input = NULL;
- *inputData = NULL;
- /* graphics display driver */
- _display = [[MenuDisplay alloc] initWithContext:_context];
- /* menu view */
- _menu = [[MetalMenu alloc] initWithContext:_context];
-
- /* Framebuffer view */
- {
- ViewDescriptor *vd = [ViewDescriptor new];
- vd.format = _video.rgb32 ? RPixelFormatBGRX8Unorm : RPixelFormatB5G6R5Unorm;
- vd.size = CGSizeMake(video->width, video->height);
- vd.filter = _video.smooth ? RTextureFilterLinear : RTextureFilterNearest;
- _frameView = [[FrameView alloc] initWithDescriptor:vd context:_context];
- _frameView.viewport = _viewport;
- [_frameView setFilteringIndex:0 smooth:video->smooth];
- }
-
- /* Overlay view */
- _overlay = [[Overlay alloc] initWithContext:_context];
-
- font_driver_init_osd((__bridge void *)self,
- video,
- false,
- video->is_threaded,
- FONT_DRIVER_RENDER_METAL_API);
- }
- return self;
-}
-
-- (void)dealloc
-{
- if (_viewport)
- {
- free(_viewport);
- _viewport = nil;
- }
- font_driver_free_osd();
-}
-
-- (bool)_initMetal
-{
- _library = [_device newDefaultLibrary];
- _context = [[Context alloc] initWithDevice:_device
- layer:_layer
- library:_library];
-
- {
- NSError *err;
- MTLRenderPipelineDescriptor *psd;
- MTLRenderPipelineColorAttachmentDescriptor *ca;
- MTLVertexDescriptor *vd = [MTLVertexDescriptor new];
- vd.attributes[0].offset = 0;
- vd.attributes[0].format = MTLVertexFormatFloat3;
- vd.attributes[1].offset = offsetof(Vertex, texCoord);
- vd.attributes[1].format = MTLVertexFormatFloat2;
- vd.layouts[0].stride = sizeof(Vertex);
-
- psd = [MTLRenderPipelineDescriptor new];
- psd.label = @"Pipeline+Alpha";
-
- ca = psd.colorAttachments[0];
- ca.pixelFormat = _layer.pixelFormat;
- ca.blendingEnabled = YES;
- ca.sourceAlphaBlendFactor = MTLBlendFactorSourceAlpha;
- ca.sourceRGBBlendFactor = MTLBlendFactorSourceAlpha;
- ca.destinationAlphaBlendFactor = MTLBlendFactorOneMinusSourceAlpha;
- ca.destinationRGBBlendFactor = MTLBlendFactorOneMinusSourceAlpha;
-
- psd.sampleCount = 1;
- psd.vertexDescriptor = vd;
- psd.vertexFunction = [_library newFunctionWithName:@"basic_vertex_proj_tex"];
- psd.fragmentFunction = [_library newFunctionWithName:@"basic_fragment_proj_tex"];
-
-
- _t_pipelineState = [_device newRenderPipelineStateWithDescriptor:psd error:&err];
- if (err != nil)
- {
- RARCH_ERR("[Metal]: error creating pipeline state %s\n", err.localizedDescription.UTF8String);
- return NO;
- }
-
- psd.label = @"Pipeline+No Alpha";
- ca.blendingEnabled = NO;
- _t_pipelineStateNoAlpha = [_device newRenderPipelineStateWithDescriptor:psd error:&err];
- if (err != nil)
- {
- RARCH_ERR("[Metal]: error creating pipeline state (no alpha) %s\n", err.localizedDescription.UTF8String);
- return NO;
- }
- }
-
- {
- MTLSamplerDescriptor *sd = [MTLSamplerDescriptor new];
- _samplerStateNearest = [_device newSamplerStateWithDescriptor:sd];
-
- sd.minFilter = MTLSamplerMinMagFilterLinear;
- sd.magFilter = MTLSamplerMinMagFilterLinear;
- _samplerStateLinear = [_device newSamplerStateWithDescriptor:sd];
- }
-
- return YES;
-}
-
-- (void)setViewportWidth:(unsigned)width height:(unsigned)height forceFull:(BOOL)forceFull allowRotate:(BOOL)allowRotate
-{
- _viewport->full_width = width;
- _viewport->full_height = height;
- video_driver_set_size(_viewport->full_width, _viewport->full_height);
- _layer.drawableSize = CGSizeMake(width, height);
- video_driver_update_viewport(_viewport, forceFull, _keepAspect);
- _context.viewport = _viewport; /* Update matrix */
- _viewportMVP.outputSize = simd_make_float2(_viewport->full_width, _viewport->full_height);
-}
-
-#pragma mark - video
-
-- (void)setVideo:(const video_info_t *)video { }
-
-- (bool)renderFrame:(const void *)frame
- data:(void*)data
- width:(unsigned)width
- height:(unsigned)height
- frameCount:(uint64_t)frameCount
- pitch:(unsigned)pitch
- msg:(const char *)msg
- info:(video_frame_info_t *)video_info
-{
- @autoreleasepool
- {
- bool statistics_show = video_info->statistics_show;
-
- [self _beginFrame];
-
- _frameView.frameCount = frameCount;
- if (frame && width && height)
- {
- _frameView.size = CGSizeMake(width, height);
- [_frameView updateFrame:frame pitch:pitch];
- }
-
- [self _drawCore];
- [self _drawMenu:video_info];
-
- id rce = _context.rce;
-
-#ifdef HAVE_OVERLAY
- if (_overlay.enabled)
- {
- [rce pushDebugGroup:@"overlay"];
- [_context resetRenderViewport:_overlay.fullscreen ? kFullscreenViewport : kVideoViewport];
- [rce setRenderPipelineState:[_context getStockShader:VIDEO_SHADER_STOCK_BLEND blend:YES]];
- [rce setVertexBytes:_context.uniforms length:sizeof(*_context.uniforms) atIndex:BufferIndexUniforms];
- [rce setFragmentSamplerState:_samplerStateLinear atIndex:SamplerIndexDraw];
- [_overlay drawWithEncoder:rce];
- [rce popDebugGroup];
- }
-#endif
-
- if (statistics_show)
- {
- struct font_params *osd_params = (struct font_params *)&video_info->osd_stat_params;
-
- if (osd_params)
- {
- [rce pushDebugGroup:@"video stats"];
- font_driver_render_msg(data, video_info->stat_text, osd_params, NULL);
- [rce popDebugGroup];
- }
- }
-
-#ifdef HAVE_GFX_WIDGETS
- [rce pushDebugGroup:@"display widgets"];
- if (video_info->widgets_active)
- gfx_widgets_frame(video_info);
- [rce popDebugGroup];
-#endif
-
- if (msg && *msg)
- {
- [rce pushDebugGroup:@"message"];
- [self _renderMessage:msg data:data];
- [rce popDebugGroup];
- }
-
- [self _endFrame];
- }
-
- return YES;
-}
-
-- (void)_renderMessage:(const char *)msg
- data:(void*)data
-{
- settings_t *settings = config_get_ptr();
- bool msg_bgcolor_enable = settings->bools.video_msg_bgcolor_enable;
-
- if (msg_bgcolor_enable)
- {
- float r, g, b, a;
- int msg_width =
- font_driver_get_message_width(NULL, msg, strlen(msg), 1.0f);
- float font_size = settings->floats.video_font_size;
- unsigned bgcolor_red
- = settings->uints.video_msg_bgcolor_red;
- unsigned bgcolor_green
- = settings->uints.video_msg_bgcolor_green;
- unsigned bgcolor_blue
- = settings->uints.video_msg_bgcolor_blue;
- float bgcolor_opacity = settings->floats.video_msg_bgcolor_opacity;
- float x = settings->floats.video_msg_pos_x;
- float y = 1.0f - settings->floats.video_msg_pos_y;
- float width = msg_width / (float)_viewport->full_width;
- float height = font_size / (float)_viewport->full_height;
-
- float x2 = 0.005f; /* extend background around text */
- float y2 = 0.005f;
-
- y -= height;
-
- x -= x2;
- y -= y2;
- width += x2;
- height += y2;
-
- r = bgcolor_red / 255.0f;
- g = bgcolor_green / 255.0f;
- b = bgcolor_blue / 255.0f;
- a = bgcolor_opacity;
-
- [_context resetRenderViewport:kFullscreenViewport];
- [_context drawQuadX:x y:y w:width h:height r:r g:g b:b a:a];
- }
-
- font_driver_render_msg(data, msg, NULL, NULL);
-}
-
-- (void)_beginFrame
-{
- video_viewport_t vp = *_viewport;
- video_driver_update_viewport(_viewport, NO, _keepAspect);
-
- if (memcmp(&vp, _viewport, sizeof(vp)) != 0)
- _context.viewport = _viewport;
-
- [_context begin];
-}
-
-- (void)_drawCore
-{
- id rce = _context.rce;
-
- /* draw back buffer */
- [rce pushDebugGroup:@"core frame"];
- [_frameView drawWithContext:_context];
-
- if ((_frameView.drawState & ViewDrawStateEncoder) != 0)
- {
- [rce setVertexBytes:_context.uniforms length:sizeof(*_context.uniforms) atIndex:BufferIndexUniforms];
- [rce setRenderPipelineState:_t_pipelineStateNoAlpha];
- if (_frameView.filter == RTextureFilterNearest)
- [rce setFragmentSamplerState:_samplerStateNearest atIndex:SamplerIndexDraw];
- else
- [rce setFragmentSamplerState:_samplerStateLinear atIndex:SamplerIndexDraw];
- [_frameView drawWithEncoder:rce];
- }
- [rce popDebugGroup];
-}
-
-- (void)_drawMenu:(video_frame_info_t *)video_info
-{
- bool menu_is_alive = video_info->menu_is_alive;
-
- if (!_menu.enabled)
- return;
-
- id rce = _context.rce;
-
- if (_menu.hasFrame)
- {
- [rce pushDebugGroup:@"menu frame"];
- [_menu.view drawWithContext:_context];
- [rce setVertexBytes:_context.uniforms length:sizeof(*_context.uniforms) atIndex:BufferIndexUniforms];
- [rce setRenderPipelineState:_t_pipelineState];
- if (_menu.view.filter == RTextureFilterNearest)
- [rce setFragmentSamplerState:_samplerStateNearest atIndex:SamplerIndexDraw];
- else
- [rce setFragmentSamplerState:_samplerStateLinear atIndex:SamplerIndexDraw];
- [_menu.view drawWithEncoder:rce];
- [rce popDebugGroup];
- }
-#if defined(HAVE_MENU)
- else
- {
- [rce pushDebugGroup:@"menu"];
- [_context resetRenderViewport:kFullscreenViewport];
- menu_driver_frame(menu_is_alive, video_info);
- [rce popDebugGroup];
- }
-#endif
-}
-
-- (void)_endFrame { [_context end]; }
-/* TODO/FIXME (sgc): resize*/
-- (void)setNeedsResize { }
-- (void)setRotation:(unsigned)rotation { [_context setRotation:rotation]; }
-- (Uniforms *)viewportMVP { return &_viewportMVP; }
-
-#pragma mark - MTKViewDelegate
-
-- (void)mtkView:(MTKView *)view drawableSizeWillChange:(CGSize)size
-{
-#ifdef HAVE_COCOATOUCH
- CGFloat scale = [[UIScreen mainScreen] scale];
- [self setViewportWidth:(unsigned int)view.bounds.size.width*scale height:(unsigned int)view.bounds.size.height*scale forceFull:NO allowRotate:YES];
-#else
- [self setViewportWidth:(unsigned int)size.width height:(unsigned int)size.height forceFull:NO allowRotate:YES];
-#endif
-}
-
-- (void)drawInMTKView:(MTKView *)view { }
-@end
-
-@implementation MetalMenu
-{
- Context *_context;
- TexturedView *_view;
- bool _enabled;
-}
-
-- (instancetype)initWithContext:(Context *)context
-{
- if (self = [super init])
- _context = context;
- return self;
-}
-
-- (bool)hasFrame { return _view != nil; }
-
-- (void)setEnabled:(bool)enabled
-{
- if (_enabled == enabled)
- return;
- _enabled = enabled;
- _view.visible = enabled;
-}
-
-- (bool)enabled { return _enabled; }
-
-- (void)updateWidth:(int)width
- height:(int)height
- format:(RPixelFormat)format
- filter:(RTextureFilter)filter
-{
- CGSize size = CGSizeMake(width, height);
-
- if (_view)
- {
- if (!(CGSizeEqualToSize(_view.size, size) &&
- _view.format == format &&
- _view.filter == filter))
- _view = nil;
- }
-
- if (!_view)
- {
- ViewDescriptor *vd = [ViewDescriptor new];
- vd.format = format;
- vd.filter = filter;
- vd.size = size;
- _view = [[TexturedView alloc] initWithDescriptor:vd context:_context];
- _view.visible = _enabled;
- }
-}
-
-- (void)updateFrame:(void const *)source
-{
- [_view updateFrame:source pitch:RPixelFormatToBPP(_view.format) * (NSUInteger)_view.size.width];
-}
-
-@end
-
-#pragma mark - FrameView
-
-#define MTLALIGN(x) __attribute__((aligned(x)))
-
-typedef struct
-{
- float x;
- float y;
- float z;
- float w;
-} float4_t;
-
-typedef struct texture
-{
- __unsafe_unretained id view;
- float4_t size_data;
-} texture_t;
-
-typedef struct MTLALIGN(16)
-{
- matrix_float4x4 mvp;
-
- struct
- {
- texture_t texture[GFX_MAX_FRAME_HISTORY + 1];
- MTLViewport viewport;
- float4_t output_size;
- } frame;
-
- struct
- {
- __unsafe_unretained id buffers[SLANG_CBUFFER_MAX];
- texture_t rt;
- texture_t feedback;
- uint32_t frame_count;
- int32_t frame_direction;
- pass_semantics_t semantics;
- MTLViewport viewport;
- __unsafe_unretained id _state;
- } pass[GFX_MAX_SHADERS];
-
- texture_t luts[GFX_MAX_TEXTURES];
-
-} engine_t;
-
-@implementation FrameView
-{
- Context *_context;
- id _texture; /* final render texture */
- Vertex _v[4];
- VertexSlang _vertex[4];
- CGSize _size; /* size of view in pixels */
- CGRect _frame;
- NSUInteger _bpp;
-
- id _src; /* source texture */
- bool _srcDirty;
-
- id _samplers[RARCH_FILTER_MAX][RARCH_WRAP_MAX];
- struct video_shader *_shader;
-
- engine_t _engine;
-
- bool resize_render_targets;
- bool init_history;
- video_viewport_t *_viewport;
-}
-
-- (instancetype)initWithDescriptor:(ViewDescriptor *)d context:(Context *)c
-{
- self = [super init];
- if (self)
- {
- _context = c;
- _format = d.format;
- _bpp = RPixelFormatToBPP(_format);
- _filter = d.filter;
- if (_format == RPixelFormatBGRA8Unorm || _format == RPixelFormatBGRX8Unorm)
- _drawState = ViewDrawStateEncoder;
- else
- _drawState = ViewDrawStateAll;
- _visible = YES;
- _engine.mvp = matrix_proj_ortho(0, 1, 0, 1);
- [self _initSamplers];
-
- self.size = d.size;
- self.frame = CGRectMake(0, 0, 1, 1);
- resize_render_targets = YES;
-
- /* Initialize slang vertex buffer */
- VertexSlang v[4] = {
- {simd_make_float4(0, 1, 0, 1), simd_make_float2(0, 1)},
- {simd_make_float4(1, 1, 0, 1), simd_make_float2(1, 1)},
- {simd_make_float4(0, 0, 0, 1), simd_make_float2(0, 0)},
- {simd_make_float4(1, 0, 0, 1), simd_make_float2(1, 0)},
- };
- memcpy(_vertex, v, sizeof(_vertex));
- }
- return self;
-}
-
-- (void)_initSamplers
-{
- int i;
- MTLSamplerDescriptor *sd = [MTLSamplerDescriptor new];
-
- /* Initialize samplers */
- for (i = 0; i < RARCH_WRAP_MAX; i++)
- {
- switch (i)
- {
- case RARCH_WRAP_BORDER:
-#if defined(HAVE_COCOATOUCH)
- sd.sAddressMode = MTLSamplerAddressModeClampToZero;
-#else
- sd.sAddressMode = MTLSamplerAddressModeClampToBorderColor;
-#endif
- break;
-
- case RARCH_WRAP_EDGE:
- sd.sAddressMode = MTLSamplerAddressModeClampToEdge;
- break;
-
- case RARCH_WRAP_REPEAT:
- sd.sAddressMode = MTLSamplerAddressModeRepeat;
- break;
-
- case RARCH_WRAP_MIRRORED_REPEAT:
- sd.sAddressMode = MTLSamplerAddressModeMirrorRepeat;
- break;
-
- default:
- continue;
- }
- sd.tAddressMode = sd.sAddressMode;
- sd.rAddressMode = sd.sAddressMode;
- sd.minFilter = MTLSamplerMinMagFilterLinear;
- sd.magFilter = MTLSamplerMinMagFilterLinear;
-
- id ss = [_context.device newSamplerStateWithDescriptor:sd];
- _samplers[RARCH_FILTER_LINEAR][i] = ss;
-
- sd.minFilter = MTLSamplerMinMagFilterNearest;
- sd.magFilter = MTLSamplerMinMagFilterNearest;
-
- ss = [_context.device newSamplerStateWithDescriptor:sd];
- _samplers[RARCH_FILTER_NEAREST][i] = ss;
- }
-}
-
-- (void)setFilteringIndex:(int)index smooth:(bool)smooth
-{
- int i;
- for (i = 0; i < RARCH_WRAP_MAX; i++)
- {
- if (smooth)
- _samplers[RARCH_FILTER_UNSPEC][i] = _samplers[RARCH_FILTER_LINEAR][i];
- else
- _samplers[RARCH_FILTER_UNSPEC][i] = _samplers[RARCH_FILTER_NEAREST][i];
- }
-}
-
-- (void)setSize:(CGSize)size
-{
- if (CGSizeEqualToSize(_size, size))
- return;
-
- _size = size;
- resize_render_targets = YES;
-
- if ( _format != RPixelFormatBGRA8Unorm
- && _format != RPixelFormatBGRX8Unorm)
- {
- MTLTextureDescriptor *td = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatR16Uint
- width:(NSUInteger)size.width
- height:(NSUInteger)size.height
- mipmapped:NO];
- _src = [_context.device newTextureWithDescriptor:td];
- }
-}
-
-- (CGSize)size { return _size; }
-
-- (void)setFrame:(CGRect)frame
-{
- if (CGRectEqualToRect(_frame, frame))
- return;
-
- /* update vertices */
- CGPoint o = frame.origin;
- CGSize s = frame.size;
-
- CGFloat l = o.x;
- CGFloat t = o.y;
- CGFloat r = o.x + s.width;
- CGFloat b = o.y + s.height;
-
- Vertex v[4] = {
- {simd_make_float3(l, b, 0), simd_make_float2(0, 1)},
- {simd_make_float3(r, b, 0), simd_make_float2(1, 1)},
- {simd_make_float3(l, t, 0), simd_make_float2(0, 0)},
- {simd_make_float3(r, t, 0), simd_make_float2(1, 0)},
- };
-
- _frame = frame;
- memcpy(_v, v, sizeof(_v));
-}
-
-- (CGRect)frame { return _frame; }
-
-- (void)_convertFormat
-{
- if ( _format == RPixelFormatBGRA8Unorm
- || _format == RPixelFormatBGRX8Unorm)
- return;
-
- if (!_srcDirty)
- return;
-
- [_context convertFormat:_format from:_src to:_texture];
- _srcDirty = NO;
-}
-
-- (void)_updateHistory
-{
- if (_shader)
- {
- if (_shader->history_size)
- {
- if (init_history)
- [self _initHistory];
- else
- {
- int k;
- /* todo: what about frame-duping ?
- * maybe clone d3d10_texture_t with AddRef */
- texture_t tmp = _engine.frame.texture[_shader->history_size];
- for (k = _shader->history_size; k > 0; k--)
- _engine.frame.texture[k] = _engine.frame.texture[k - 1];
- _engine.frame.texture[0] = tmp;
- }
- }
- }
-
- /* either no history, or we moved a texture of a different size in the front slot */
- if ( _engine.frame.texture[0].size_data.x != _size.width
- || _engine.frame.texture[0].size_data.y != _size.height)
- {
- MTLTextureDescriptor *td = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatBGRA8Unorm
- width:(NSUInteger)_size.width
- height:(NSUInteger)_size.height
- mipmapped:false];
- td.usage = MTLTextureUsageShaderRead | MTLTextureUsageShaderWrite;
- [self _initTexture:&_engine.frame.texture[0] withDescriptor:td];
- }
-}
-
-- (bool)readViewport:(uint8_t *)buffer isIdle:(bool)isIdle
-{
- bool res;
- bool enabled = _context.captureEnabled;
- if (!enabled)
- _context.captureEnabled = YES;
-
- video_driver_cached_frame();
-
- res = [_context readBackBuffer:buffer];
-
- if (!enabled)
- _context.captureEnabled = NO;
-
- return res;
-}
-
-- (void)updateFrame:(void const *)src pitch:(NSUInteger)pitch
-{
- if (_shader && (_engine.frame.output_size.x != _viewport->width ||
- _engine.frame.output_size.y != _viewport->height))
- resize_render_targets = YES;
-
- _engine.frame.viewport.originX = _viewport->x;
- _engine.frame.viewport.originY = _viewport->y;
- _engine.frame.viewport.width = _viewport->width;
- _engine.frame.viewport.height = _viewport->height;
- _engine.frame.viewport.znear = 0.0f;
- _engine.frame.viewport.zfar = 1.0f;
- _engine.frame.output_size.x = _viewport->width;
- _engine.frame.output_size.y = _viewport->height;
- _engine.frame.output_size.z = 1.0f / _viewport->width;
- _engine.frame.output_size.w = 1.0f / _viewport->height;
-
- if (resize_render_targets)
- [self _updateRenderTargets];
-
- [self _updateHistory];
-
- if ( _format == RPixelFormatBGRA8Unorm
- || _format == RPixelFormatBGRX8Unorm)
- {
- id tex = _engine.frame.texture[0].view;
- [tex replaceRegion:MTLRegionMake2D(0, 0, (NSUInteger)_size.width, (NSUInteger)_size.height)
- mipmapLevel:0 withBytes:src
- bytesPerRow:pitch];
- }
- else
- {
- [_src replaceRegion:MTLRegionMake2D(0, 0, (NSUInteger)_size.width, (NSUInteger)_size.height)
- mipmapLevel:0 withBytes:src
- bytesPerRow:(NSUInteger)(pitch)];
- _srcDirty = YES;
- }
-}
-
-- (void)_initTexture:(texture_t *)t withDescriptor:(MTLTextureDescriptor *)td
-{
- STRUCT_ASSIGN(t->view, [_context.device newTextureWithDescriptor:td]);
- t->size_data.x = td.width;
- t->size_data.y = td.height;
- t->size_data.z = 1.0f / td.width;
- t->size_data.w = 1.0f / td.height;
-}
-
-- (void)_initHistory
-{
- int i;
- MTLTextureDescriptor *td = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatBGRA8Unorm
- width:(NSUInteger)_size.width
- height:(NSUInteger)_size.height
- mipmapped:false];
- td.usage = MTLTextureUsageShaderRead
- | MTLTextureUsageShaderWrite
- | MTLTextureUsageRenderTarget;
-
- for (i = 0; i < _shader->history_size + 1; i++)
- [self _initTexture:&_engine.frame.texture[i] withDescriptor:td];
- init_history = NO;
-}
-
-- (void)drawWithEncoder:(id)rce
-{
- if (_texture)
- {
- [rce setViewport:_engine.frame.viewport];
- [rce setVertexBytes:&_v length:sizeof(_v) atIndex:BufferIndexPositions];
- [rce setFragmentTexture:_texture atIndex:TextureIndexColor];
- [rce drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4];
- }
-}
-
-- (void)drawWithContext:(Context *)ctx
-{
- int i;
- _texture = _engine.frame.texture[0].view;
- [self _convertFormat];
-
- if (!_shader || _shader->passes == 0)
- return;
-
- for (i = 0; i < _shader->passes; i++)
- {
- if (_shader->pass[i].feedback)
- {
- texture_t tmp = _engine.pass[i].feedback;
- _engine.pass[i].feedback = _engine.pass[i].rt;
- _engine.pass[i].rt = tmp;
- }
- }
-
- id cb = ctx.blitCommandBuffer;
- [cb pushDebugGroup:@"shaders"];
-
- MTLRenderPassDescriptor *rpd = [MTLRenderPassDescriptor new];
- rpd.colorAttachments[0].loadAction = MTLLoadActionDontCare;
- rpd.colorAttachments[0].storeAction = MTLStoreActionStore;
-
- for (i = 0; i < _shader->passes; i++)
- {
- int j;
- __unsafe_unretained id textures[SLANG_NUM_BINDINGS] = {NULL};
- id samplers[SLANG_NUM_BINDINGS] = {NULL};
- id rce = nil;
-
- BOOL backBuffer = (_engine.pass[i].rt.view == nil);
-
- if (backBuffer)
- rce = _context.rce;
- else
- {
- rpd.colorAttachments[0].texture = _engine.pass[i].rt.view;
- rce = [cb renderCommandEncoderWithDescriptor:rpd];
- }
-
- [rce setRenderPipelineState:_engine.pass[i]._state];
-
- NSURL *shaderPath = [NSURL fileURLWithPath:_engine.pass[i]._state.label];
- rce.label = shaderPath.lastPathComponent.stringByDeletingPathExtension;
-
- _engine.pass[i].frame_count = (uint32_t)_frameCount;
- if (_shader->pass[i].frame_count_mod)
- _engine.pass[i].frame_count %= _shader->pass[i].frame_count_mod;
-
-#ifdef HAVE_REWIND
- if (state_manager_frame_is_reversed())
- _engine.pass[i].frame_direction = -1;
- else
-#else
- _engine.pass[i].frame_direction = 1;
-#endif
-
- for (j = 0; j < SLANG_CBUFFER_MAX; j++)
- {
- id buffer = _engine.pass[i].buffers[j];
- cbuffer_sem_t *buffer_sem = &_engine.pass[i].semantics.cbuffers[j];
-
- if (buffer_sem->stage_mask && buffer_sem->uniforms)
- {
- void *data = buffer.contents;
- uniform_sem_t *uniform = buffer_sem->uniforms;
-
- while (uniform->size)
- {
- if (uniform->data)
- memcpy((uint8_t *)data + uniform->offset, uniform->data, uniform->size);
- uniform++;
- }
-
- if (buffer_sem->stage_mask & SLANG_STAGE_VERTEX_MASK)
- [rce setVertexBuffer:buffer offset:0 atIndex:buffer_sem->binding];
-
- if (buffer_sem->stage_mask & SLANG_STAGE_FRAGMENT_MASK)
- [rce setFragmentBuffer:buffer offset:0 atIndex:buffer_sem->binding];
-#if !defined(HAVE_COCOATOUCH)
- [buffer didModifyRange:NSMakeRange(0, buffer.length)];
-#endif
- }
- }
-
- texture_sem_t *texture_sem = _engine.pass[i].semantics.textures;
- while (texture_sem->stage_mask)
- {
- int binding = texture_sem->binding;
- id tex = (__bridge id)*(void **)texture_sem->texture_data;
- textures[binding] = tex;
- samplers[binding] = _samplers[texture_sem->filter][texture_sem->wrap];
- texture_sem++;
- }
-
- if (backBuffer)
- [rce setViewport:_engine.frame.viewport];
- else
- [rce setViewport:_engine.pass[i].viewport];
-
- [rce setFragmentTextures:textures withRange:NSMakeRange(0, SLANG_NUM_BINDINGS)];
- [rce setFragmentSamplerStates:samplers withRange:NSMakeRange(0, SLANG_NUM_BINDINGS)];
- [rce setVertexBytes:_vertex length:sizeof(_vertex) atIndex:4];
- [rce drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4];
-
- if (!backBuffer)
- [rce endEncoding];
-
- _texture = _engine.pass[i].rt.view;
- }
-
- if (_texture == nil)
- _drawState = ViewDrawStateContext;
- else
- _drawState = ViewDrawStateAll;
-
- [cb popDebugGroup];
-}
-
-- (void)_updateRenderTargets
-{
- int i;
- NSUInteger width, height;
- if (!_shader || !resize_render_targets) return;
-
- // release existing targets
- for (i = 0; i < _shader->passes; i++)
- {
- STRUCT_ASSIGN(_engine.pass[i].rt.view, nil);
- STRUCT_ASSIGN(_engine.pass[i].feedback.view, nil);
- memset(&_engine.pass[i].rt, 0, sizeof(_engine.pass[i].rt));
- memset(&_engine.pass[i].feedback, 0, sizeof(_engine.pass[i].feedback));
- }
-
- width = (NSUInteger)_size.width;
- height = (NSUInteger)_size.height;
-
- for (i = 0; i < _shader->passes; i++)
- {
- struct video_shader_pass *shader_pass = &_shader->pass[i];
-
- if (shader_pass->fbo.flags & FBO_SCALE_FLAG_VALID)
- {
- switch (shader_pass->fbo.type_x)
- {
- case RARCH_SCALE_INPUT:
- width *= shader_pass->fbo.scale_x;
- break;
-
- case RARCH_SCALE_VIEWPORT:
- width = (NSUInteger)(_viewport->width * shader_pass->fbo.scale_x);
- break;
-
- case RARCH_SCALE_ABSOLUTE:
- width = shader_pass->fbo.abs_x;
- break;
-
- default:
- break;
- }
-
- if (!width)
- width = _viewport->width;
-
- switch (shader_pass->fbo.type_y)
- {
- case RARCH_SCALE_INPUT:
- height *= shader_pass->fbo.scale_y;
- break;
-
- case RARCH_SCALE_VIEWPORT:
- height = (NSUInteger)(_viewport->height * shader_pass->fbo.scale_y);
- break;
-
- case RARCH_SCALE_ABSOLUTE:
- height = shader_pass->fbo.abs_y;
- break;
-
- default:
- break;
- }
-
- if (!height)
- height = _viewport->height;
- }
- else if (i == (_shader->passes - 1))
- {
- width = _viewport->width;
- height = _viewport->height;
- }
-
- /* Updating framebuffer size */
-
- MTLPixelFormat fmt = SelectOptimalPixelFormat(glslang_format_to_metal(_engine.pass[i].semantics.format));
- if ((i != (_shader->passes - 1)) ||
- (width != _viewport->width) || (height != _viewport->height) ||
- fmt != MTLPixelFormatBGRA8Unorm)
- {
- _engine.pass[i].viewport.width = width;
- _engine.pass[i].viewport.height = height;
- _engine.pass[i].viewport.znear = 0.0;
- _engine.pass[i].viewport.zfar = 1.0;
-
- MTLTextureDescriptor *td = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:fmt
- width:width height:height mipmapped:false];
- td.storageMode = MTLStorageModePrivate;
- td.usage = MTLTextureUsageShaderRead
- | MTLTextureUsageRenderTarget;
-
- [self _initTexture:&_engine.pass[i].rt withDescriptor:td];
-
- if (shader_pass->feedback)
- [self _initTexture:&_engine.pass[i].feedback withDescriptor:td];
- }
- else
- {
- _engine.pass[i].rt.size_data.x = width;
- _engine.pass[i].rt.size_data.y = height;
- _engine.pass[i].rt.size_data.z = 1.0f / width;
- _engine.pass[i].rt.size_data.w = 1.0f / height;
- }
- }
-
- resize_render_targets = NO;
-}
-
-- (void)_freeVideoShader:(struct video_shader *)shader
-{
- int i;
- if (!shader)
- return;
-
- for (i = 0; i < GFX_MAX_SHADERS; i++)
- {
- int j;
- STRUCT_ASSIGN(_engine.pass[i].rt.view, nil);
- STRUCT_ASSIGN(_engine.pass[i].feedback.view, nil);
- memset(&_engine.pass[i].rt, 0, sizeof(_engine.pass[i].rt));
- memset(&_engine.pass[i].feedback, 0, sizeof(_engine.pass[i].feedback));
-
- STRUCT_ASSIGN(_engine.pass[i]._state, nil);
-
- for (j = 0; j < SLANG_CBUFFER_MAX; j++)
- {
- STRUCT_ASSIGN(_engine.pass[i].buffers[j], nil);
- }
- }
-
- for (i = 0; i < GFX_MAX_TEXTURES; i++)
- {
- STRUCT_ASSIGN(_engine.luts[i].view, nil);
- }
-
- free(shader);
-}
-
-- (BOOL)setShaderFromPath:(NSString *)path
-{
- [self _freeVideoShader:_shader];
- _shader = nil;
-
- struct video_shader *shader = (struct video_shader *)calloc(1, sizeof(*shader));
- settings_t *settings = config_get_ptr();
- const char *dir_video_shader = settings->paths.directory_video_shader;
- NSString *shadersPath = [NSString stringWithFormat:@"%s/", dir_video_shader];
-
- @try
- {
- int i;
- texture_t *source = NULL;
- if (!video_shader_load_preset_into_shader(path.UTF8String, shader))
- return NO;
-
- source = &_engine.frame.texture[0];
-
- for (i = 0; i < shader->passes; source = &_engine.pass[i++].rt)
- {
- matrix_float4x4 *mvp = (i == shader->passes-1) ? &_context.uniforms->projectionMatrix : &_engine.mvp;
-
- /* clang-format off */
- semantics_map_t semantics_map = {
- {
- /* Original */
- {&_engine.frame.texture[0].view, 0,
- &_engine.frame.texture[0].size_data, 0},
-
- /* Source */
- {&source->view, 0,
- &source->size_data, 0},
-
- /* OriginalHistory */
- {&_engine.frame.texture[0].view, sizeof(*_engine.frame.texture),
- &_engine.frame.texture[0].size_data, sizeof(*_engine.frame.texture)},
-
- /* PassOutput */
- {&_engine.pass[0].rt.view, sizeof(*_engine.pass),
- &_engine.pass[0].rt.size_data, sizeof(*_engine.pass)},
-
- /* PassFeedback */
- {&_engine.pass[0].feedback.view, sizeof(*_engine.pass),
- &_engine.pass[0].feedback.size_data, sizeof(*_engine.pass)},
-
- /* User */
- {&_engine.luts[0].view, sizeof(*_engine.luts),
- &_engine.luts[0].size_data, sizeof(*_engine.luts)},
- },
- {
- mvp, /* MVP */
- &_engine.pass[i].rt.size_data, /* OutputSize */
- &_engine.frame.output_size, /* FinalViewportSize */
- &_engine.pass[i].frame_count, /* FrameCount */
- &_engine.pass[i].frame_direction, /* FrameDirection */
- }
- };
- /* clang-format on */
-
- if (!slang_process(shader, i, RARCH_SHADER_METAL, 20000, &semantics_map, &_engine.pass[i].semantics))
- return NO;
-
-#ifdef DEBUG
- bool save_msl = true;
-#else
- bool save_msl = false;
-#endif
- NSString *vs_src = [NSString stringWithUTF8String:shader->pass[i].source.string.vertex];
- NSString *fs_src = [NSString stringWithUTF8String:shader->pass[i].source.string.fragment];
-
- // vertex descriptor
- @try
- {
- NSError *err;
- MTLVertexDescriptor *vd = [MTLVertexDescriptor new];
- vd.attributes[0].offset = offsetof(VertexSlang, position);
- vd.attributes[0].format = MTLVertexFormatFloat4;
- vd.attributes[0].bufferIndex = 4;
- vd.attributes[1].offset = offsetof(VertexSlang, texCoord);
- vd.attributes[1].format = MTLVertexFormatFloat2;
- vd.attributes[1].bufferIndex = 4;
- vd.layouts[4].stride = sizeof(VertexSlang);
- vd.layouts[4].stepFunction = MTLVertexStepFunctionPerVertex;
-
- MTLRenderPipelineDescriptor *psd = [MTLRenderPipelineDescriptor new];
-
- psd.label = [[NSString stringWithUTF8String:shader->pass[i].source.path]
- stringByReplacingOccurrencesOfString:shadersPath withString:@""];
-
- MTLRenderPipelineColorAttachmentDescriptor *ca = psd.colorAttachments[0];
-
- ca.pixelFormat = SelectOptimalPixelFormat(glslang_format_to_metal(_engine.pass[i].semantics.format));
-
- /* TODO(sgc): confirm we never need blending for render passes */
- ca.blendingEnabled = NO;
- ca.sourceAlphaBlendFactor = MTLBlendFactorSourceAlpha;
- ca.sourceRGBBlendFactor = MTLBlendFactorSourceAlpha;
- ca.destinationAlphaBlendFactor = MTLBlendFactorOneMinusSourceAlpha;
- ca.destinationRGBBlendFactor = MTLBlendFactorOneMinusSourceAlpha;
-
- psd.sampleCount = 1;
- psd.vertexDescriptor = vd;
-
- id lib = [_context.device newLibraryWithSource:vs_src options:nil error:&err];
- if (err != nil)
- {
- if (lib == nil)
- {
- save_msl = true;
- RARCH_ERR("[Metal]: unable to compile vertex shader: %s\n", err.localizedDescription.UTF8String);
- return NO;
- }
-#if DEBUG
- RARCH_WARN("[Metal]: warnings compiling vertex shader: %s\n", err.localizedDescription.UTF8String);
-#endif
- }
-
- psd.vertexFunction = [lib newFunctionWithName:@"main0"];
-
- lib = [_context.device newLibraryWithSource:fs_src options:nil error:&err];
- if (err != nil)
- {
- if (lib == nil)
- {
- save_msl = true;
- RARCH_ERR("[Metal]: unable to compile fragment shader: %s\n", err.localizedDescription.UTF8String);
- return NO;
- }
-#if DEBUG
- RARCH_WARN("[Metal]: warnings compiling fragment shader: %s\n", err.localizedDescription.UTF8String);
-#endif
- }
- psd.fragmentFunction = [lib newFunctionWithName:@"main0"];
-
- STRUCT_ASSIGN(_engine.pass[i]._state,
- [_context.device newRenderPipelineStateWithDescriptor:psd error:&err]);
- if (err != nil)
- {
- save_msl = true;
- RARCH_ERR("[Metal]: error creating pipeline state for pass %d: %s\n", i,
- err.localizedDescription.UTF8String);
- return NO;
- }
-
- for (unsigned j = 0; j < SLANG_CBUFFER_MAX; j++)
- {
- unsigned int size = _engine.pass[i].semantics.cbuffers[j].size;
- if (size == 0)
- continue;
-
- id buf = [_context.device newBufferWithLength:size options:PLATFORM_METAL_RESOURCE_STORAGE_MODE];
- STRUCT_ASSIGN(_engine.pass[i].buffers[j], buf);
- }
- } @finally
- {
- if (save_msl)
- {
- NSError *err = nil;
- NSString *basePath = [[NSString stringWithUTF8String:shader->pass[i].source.path] stringByDeletingPathExtension];
-
- /* Saving Metal shader files... */
-
- [vs_src writeToFile:[basePath stringByAppendingPathExtension:@"vs.metal"]
- atomically:NO
- encoding:NSStringEncodingConversionAllowLossy
- error:&err];
- if (err != nil)
- {
- RARCH_ERR("[Metal]: unable to save vertex shader source: %s\n", err.localizedDescription.UTF8String);
- }
-
- err = nil;
- [fs_src writeToFile:[basePath stringByAppendingPathExtension:@"fs.metal"]
- atomically:NO
- encoding:NSStringEncodingConversionAllowLossy
- error:&err];
- if (err != nil)
- {
- RARCH_ERR("[Metal]: unable to save fragment shader source: %s\n",
- err.localizedDescription.UTF8String);
- }
- }
-
- free(shader->pass[i].source.string.vertex);
- free(shader->pass[i].source.string.fragment);
-
- shader->pass[i].source.string.vertex = NULL;
- shader->pass[i].source.string.fragment = NULL;
- }
- }
-
- for (i = 0; i < shader->luts; i++)
- {
- struct texture_image image;
- image.pixels = NULL;
- image.width = 0;
- image.height = 0;
- image.supports_rgba = true;
-
- if (!image_texture_load(&image, shader->lut[i].path))
- return NO;
-
- MTLTextureDescriptor *td = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatRGBA8Unorm
- width:image.width height:image.height
- mipmapped:shader->lut[i].mipmap];
- td.usage = MTLTextureUsageShaderRead;
- [self _initTexture:&_engine.luts[i] withDescriptor:td];
-
- [_engine.luts[i].view replaceRegion:MTLRegionMake2D(0, 0, image.width, image.height)
- mipmapLevel:0 withBytes:image.pixels
- bytesPerRow:4 * image.width];
-
- /* TODO(sgc): generate mip maps */
- image_texture_free(&image);
- }
- _shader = shader;
- shader = nil;
- }
- @finally
- {
- if (shader)
- [self _freeVideoShader:shader];
- }
-
- resize_render_targets = YES;
- init_history = YES;
-
- return YES;
-}
-
-@end
-
-@implementation Overlay
-{
- Context *_context;
- NSMutableArray> *_images;
- id _vert;
- bool _vertDirty;
-}
-
-- (instancetype)initWithContext:(Context *)context
-{
- if (self = [super init])
- _context = context;
- return self;
-}
-
-- (bool)loadImages:(const struct texture_image *)images count:(NSUInteger)count
-{
- int i;
- [self _freeImages];
-
- _images = [NSMutableArray arrayWithCapacity:count];
-
- NSUInteger needed = sizeof(SpriteVertex) * count * 4;
- if (!_vert || _vert.length < needed)
- _vert = [_context.device newBufferWithLength:needed options:PLATFORM_METAL_RESOURCE_STORAGE_MODE];
-
- for (i = 0; i < count; i++)
- {
- _images[i] = [_context newTexture:images[i] mipmapped:NO];
- [self updateVertexX:0 y:0 w:1 h:1 index:i];
- [self updateTextureCoordsX:0 y:0 w:1 h:1 index:i];
- [self _updateColorRed:1.0 green:1.0 blue:1.0 alpha:1.0 index:i];
- }
-
- _vertDirty = YES;
-
- return YES;
-}
-
-- (void)drawWithEncoder:(id)rce
-{
- int i;
- NSUInteger count;
-#if !defined(HAVE_COCOATOUCH)
- if (_vertDirty)
- {
- [_vert didModifyRange:NSMakeRange(0, _vert.length)];
- _vertDirty = NO;
- }
-#endif
-
- count = _images.count;
- for (i = 0; i < count; ++i)
- {
- NSUInteger offset = sizeof(SpriteVertex) * 4 * i;
- [rce setVertexBuffer:_vert offset:offset atIndex:BufferIndexPositions];
- [rce setFragmentTexture:_images[i] atIndex:TextureIndexColor];
- [rce drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4];
- }
-}
-
-- (SpriteVertex *)_getForIndex:(NSUInteger)index
-{
- SpriteVertex *pv = (SpriteVertex *)_vert.contents;
- return &pv[index * 4];
-}
-
-- (void)_updateColorRed:(float)r green:(float)g blue:(float)b alpha:(float)a index:(NSUInteger)index
-{
- simd_float4 color = simd_make_float4(r, g, b, a);
- SpriteVertex *pv = [self _getForIndex:index];
- pv[0].color = color;
- pv[1].color = color;
- pv[2].color = color;
- pv[3].color = color;
- _vertDirty = YES;
-}
-
-- (void)updateAlpha:(float)alpha index:(NSUInteger)index
-{
- [self _updateColorRed:1.0 green:1.0 blue:1.0 alpha:alpha index:index];
-}
-
-- (void)updateVertexX:(float)x y:(float)y w:(float)w h:(float)h index:(NSUInteger)index
-{
- SpriteVertex *pv = [self _getForIndex:index];
- pv[0].position = simd_make_float2(x, y);
- pv[1].position = simd_make_float2(x + w, y);
- pv[2].position = simd_make_float2(x, y + h);
- pv[3].position = simd_make_float2(x + w, y + h);
- _vertDirty = YES;
-}
-
-- (void)updateTextureCoordsX:(float)x y:(float)y w:(float)w h:(float)h index:(NSUInteger)index
-{
- SpriteVertex *pv = [self _getForIndex:index];
- pv[0].texCoord = simd_make_float2(x, y);
- pv[1].texCoord = simd_make_float2(x + w, y);
- pv[2].texCoord = simd_make_float2(x, y + h);
- pv[3].texCoord = simd_make_float2(x + w, y + h);
- _vertDirty = YES;
-}
-
-- (void)_freeImages { _images = nil; }
-
-@end
-
-MTLPixelFormat glslang_format_to_metal(glslang_format fmt)
-{
-#undef FMT2
-#define FMT2(x, y) case SLANG_FORMAT_##x: return MTLPixelFormat##y
-
- switch (fmt)
- {
- FMT2(R8_UNORM, R8Unorm);
- FMT2(R8_SINT, R8Sint);
- FMT2(R8_UINT, R8Uint);
- FMT2(R8G8_UNORM, RG8Unorm);
- FMT2(R8G8_SINT, RG8Sint);
- FMT2(R8G8_UINT, RG8Uint);
- FMT2(R8G8B8A8_UNORM, RGBA8Unorm);
- FMT2(R8G8B8A8_SINT, RGBA8Sint);
- FMT2(R8G8B8A8_UINT, RGBA8Uint);
- FMT2(R8G8B8A8_SRGB, RGBA8Unorm_sRGB);
-
- FMT2(A2B10G10R10_UNORM_PACK32, RGB10A2Unorm);
- FMT2(A2B10G10R10_UINT_PACK32, RGB10A2Uint);
-
- FMT2(R16_UINT, R16Uint);
- FMT2(R16_SINT, R16Sint);
- FMT2(R16_SFLOAT, R16Float);
- FMT2(R16G16_UINT, RG16Uint);
- FMT2(R16G16_SINT, RG16Sint);
- FMT2(R16G16_SFLOAT, RG16Float);
- FMT2(R16G16B16A16_UINT, RGBA16Uint);
- FMT2(R16G16B16A16_SINT, RGBA16Sint);
- FMT2(R16G16B16A16_SFLOAT, RGBA16Float);
-
- FMT2(R32_UINT, R32Uint);
- FMT2(R32_SINT, R32Sint);
- FMT2(R32_SFLOAT, R32Float);
- FMT2(R32G32_UINT, RG32Uint);
- FMT2(R32G32_SINT, RG32Sint);
- FMT2(R32G32_SFLOAT, RG32Float);
- FMT2(R32G32B32A32_UINT, RGBA32Uint);
- FMT2(R32G32B32A32_SINT, RGBA32Sint);
- FMT2(R32G32B32A32_SFLOAT, RGBA32Float);
-
- case SLANG_FORMAT_UNKNOWN:
- default:
- break;
- }
-#undef FMT2
- return MTLPixelFormatInvalid;
-}
-
-MTLPixelFormat SelectOptimalPixelFormat(MTLPixelFormat fmt)
-{
- switch (fmt)
- {
- case MTLPixelFormatRGBA8Unorm:
- return MTLPixelFormatBGRA8Unorm;
-
- case MTLPixelFormatRGBA8Unorm_sRGB:
- return MTLPixelFormatBGRA8Unorm_sRGB;
-
- default:
- break;
- }
-
- return fmt;
-}
diff --git a/gfx/drivers/metal.m b/gfx/drivers/metal.m
index 585e451ae8..863ebe8521 100644
--- a/gfx/drivers/metal.m
+++ b/gfx/drivers/metal.m
@@ -13,14 +13,19 @@
* If not, see .
*/
-#import
-#import
+#include
+#include
+#include
+#include
#include
#include
#include
+#include
#include
+#include
+
#include
#include
#include
@@ -32,29 +37,1537 @@
#include
#ifdef HAVE_CONFIG_H
-#import "../../config.h"
+#include "../../config.h"
#endif
#ifdef HAVE_MENU
-#import "../../menu/menu_driver.h"
+#include "../../menu/menu_driver.h"
#endif
#ifdef HAVE_GFX_WIDGETS
-#import "../gfx_widgets.h"
+#include "../gfx_widgets.h"
#endif
-#import "../font_driver.h"
+#include "../font_driver.h"
-#import "../common/metal_common.h"
+#include "../common/metal_common.h"
-#import "../../driver.h"
-#import "../../configuration.h"
+#include "../../driver.h"
+#include "../../configuration.h"
-#import "../../retroarch.h"
-#import "../../verbosity.h"
+#include "../../retroarch.h"
+#ifdef HAVE_REWIND
+#include "../../state_manager.h"
+#endif
+#include "../../verbosity.h"
-#import "../video_coord_array.h"
+#include "../video_coord_array.h"
#include "../../ui/drivers/cocoa/apple_platform.h"
+#include "../../ui/drivers/cocoa/cocoa_common.h"
+
+#define STRUCT_ASSIGN(x, y) \
+{ \
+ NSObject * __y = y; \
+ if (x != nil) { \
+ NSObject * __foo = (__bridge_transfer NSObject *)(__bridge void *)(x); \
+ __foo = nil; \
+ x = (__bridge __typeof__(x))nil; \
+ } \
+ if (__y != nil) \
+ x = (__bridge __typeof__(x))(__bridge_retained void *)((NSObject *)__y); \
+ }
+
+@implementation MetalView
+
+#if !defined(HAVE_COCOATOUCH)
+- (void)keyDown:(NSEvent*)theEvent { }
+#endif
+
+/* Stop the annoying sound when pressing a key. */
+- (BOOL)acceptsFirstResponder { return YES; }
+- (BOOL)isFlipped { return YES; }
+@end
+
+#pragma mark - private categories
+
+@interface FrameView()
+
+@property (nonatomic, readwrite) video_viewport_t *viewport;
+
+- (instancetype)initWithDescriptor:(ViewDescriptor *)td context:(Context *)context;
+- (void)drawWithContext:(Context *)ctx;
+- (void)drawWithEncoder:(id)rce;
+
+@end
+
+@interface MetalMenu()
+@property (nonatomic, readonly) TexturedView *view;
+- (instancetype)initWithContext:(Context *)context;
+@end
+
+@interface Overlay()
+- (instancetype)initWithContext:(Context *)context;
+- (void)drawWithEncoder:(id)rce;
+@end
+
+@implementation MetalDriver
+{
+ FrameView *_frameView;
+ MetalMenu *_menu;
+ Overlay *_overlay;
+
+ video_info_t _video;
+
+ id _device;
+ id _library;
+ Context *_context;
+
+ CAMetalLayer *_layer;
+
+ /* Render target layer state */
+ id _t_pipelineState;
+ id _t_pipelineStateNoAlpha;
+
+ id _samplerStateLinear;
+ id _samplerStateNearest;
+
+ /* other state */
+ Uniforms _viewportMVP;
+}
+
+- (instancetype)initWithVideo:(const video_info_t *)video
+ input:(input_driver_t **)input
+ inputData:(void **)inputData
+{
+ if (self = [super init])
+ {
+ _device = MTLCreateSystemDefaultDevice();
+ MetalView *view = (MetalView *)apple_platform.renderView;
+ view.device = _device;
+ view.delegate = self;
+ _layer = (CAMetalLayer *)view.layer;
+
+ if (![self _initMetal])
+ return nil;
+
+ _video = *video;
+ _viewport = (video_viewport_t *)calloc(1, sizeof(video_viewport_t));
+ _viewportMVP.projectionMatrix = matrix_proj_ortho(0, 1, 0, 1);
+
+ _keepAspect = _video.force_aspect;
+
+ gfx_ctx_mode_t mode = {
+ .width = _video.width,
+ .height = _video.height,
+ .fullscreen = _video.fullscreen,
+ };
+
+ if (mode.width == 0 || mode.height == 0)
+ {
+ // 0 indicates full screen, so we'll use the view's dimensions, which should already be full screen
+ // If this turns out to be the wrong assumption, we can use NSScreen to query the dimensions
+ CGSize size = view.frame.size;
+ mode.width = (unsigned int)size.width;
+ mode.height = (unsigned int)size.height;
+ }
+
+ [apple_platform setVideoMode:mode];
+
+ *input = NULL;
+ *inputData = NULL;
+ /* graphics display driver */
+ _display = [[MenuDisplay alloc] initWithContext:_context];
+ /* menu view */
+ _menu = [[MetalMenu alloc] initWithContext:_context];
+
+ /* Framebuffer view */
+ {
+ ViewDescriptor *vd = [ViewDescriptor new];
+ vd.format = _video.rgb32 ? RPixelFormatBGRX8Unorm : RPixelFormatB5G6R5Unorm;
+ vd.size = CGSizeMake(video->width, video->height);
+ vd.filter = _video.smooth ? RTextureFilterLinear : RTextureFilterNearest;
+ _frameView = [[FrameView alloc] initWithDescriptor:vd context:_context];
+ _frameView.viewport = _viewport;
+ [_frameView setFilteringIndex:0 smooth:video->smooth];
+ }
+
+ /* Overlay view */
+ _overlay = [[Overlay alloc] initWithContext:_context];
+
+ font_driver_init_osd((__bridge void *)self,
+ video,
+ false,
+ video->is_threaded,
+ FONT_DRIVER_RENDER_METAL_API);
+ }
+ return self;
+}
+
+- (void)dealloc
+{
+ if (_viewport)
+ {
+ free(_viewport);
+ _viewport = nil;
+ }
+ font_driver_free_osd();
+}
+
+- (bool)_initMetal
+{
+ _library = [_device newDefaultLibrary];
+ _context = [[Context alloc] initWithDevice:_device
+ layer:_layer
+ library:_library];
+
+ {
+ NSError *err;
+ MTLRenderPipelineDescriptor *psd;
+ MTLRenderPipelineColorAttachmentDescriptor *ca;
+ MTLVertexDescriptor *vd = [MTLVertexDescriptor new];
+ vd.attributes[0].offset = 0;
+ vd.attributes[0].format = MTLVertexFormatFloat3;
+ vd.attributes[1].offset = offsetof(Vertex, texCoord);
+ vd.attributes[1].format = MTLVertexFormatFloat2;
+ vd.layouts[0].stride = sizeof(Vertex);
+
+ psd = [MTLRenderPipelineDescriptor new];
+ psd.label = @"Pipeline+Alpha";
+
+ ca = psd.colorAttachments[0];
+ ca.pixelFormat = _layer.pixelFormat;
+ ca.blendingEnabled = YES;
+ ca.sourceAlphaBlendFactor = MTLBlendFactorSourceAlpha;
+ ca.sourceRGBBlendFactor = MTLBlendFactorSourceAlpha;
+ ca.destinationAlphaBlendFactor = MTLBlendFactorOneMinusSourceAlpha;
+ ca.destinationRGBBlendFactor = MTLBlendFactorOneMinusSourceAlpha;
+
+ psd.sampleCount = 1;
+ psd.vertexDescriptor = vd;
+ psd.vertexFunction = [_library newFunctionWithName:@"basic_vertex_proj_tex"];
+ psd.fragmentFunction = [_library newFunctionWithName:@"basic_fragment_proj_tex"];
+
+
+ _t_pipelineState = [_device newRenderPipelineStateWithDescriptor:psd error:&err];
+ if (err != nil)
+ {
+ RARCH_ERR("[Metal]: error creating pipeline state %s\n", err.localizedDescription.UTF8String);
+ return NO;
+ }
+
+ psd.label = @"Pipeline+No Alpha";
+ ca.blendingEnabled = NO;
+ _t_pipelineStateNoAlpha = [_device newRenderPipelineStateWithDescriptor:psd error:&err];
+ if (err != nil)
+ {
+ RARCH_ERR("[Metal]: error creating pipeline state (no alpha) %s\n", err.localizedDescription.UTF8String);
+ return NO;
+ }
+ }
+
+ {
+ MTLSamplerDescriptor *sd = [MTLSamplerDescriptor new];
+ _samplerStateNearest = [_device newSamplerStateWithDescriptor:sd];
+
+ sd.minFilter = MTLSamplerMinMagFilterLinear;
+ sd.magFilter = MTLSamplerMinMagFilterLinear;
+ _samplerStateLinear = [_device newSamplerStateWithDescriptor:sd];
+ }
+
+ return YES;
+}
+
+- (void)setViewportWidth:(unsigned)width height:(unsigned)height forceFull:(BOOL)forceFull allowRotate:(BOOL)allowRotate
+{
+ _viewport->full_width = width;
+ _viewport->full_height = height;
+ video_driver_set_size(_viewport->full_width, _viewport->full_height);
+ _layer.drawableSize = CGSizeMake(width, height);
+ video_driver_update_viewport(_viewport, forceFull, _keepAspect);
+ _context.viewport = _viewport; /* Update matrix */
+ _viewportMVP.outputSize = simd_make_float2(_viewport->full_width, _viewport->full_height);
+}
+
+#pragma mark - video
+
+- (void)setVideo:(const video_info_t *)video { }
+
+- (bool)renderFrame:(const void *)frame
+ data:(void*)data
+ width:(unsigned)width
+ height:(unsigned)height
+ frameCount:(uint64_t)frameCount
+ pitch:(unsigned)pitch
+ msg:(const char *)msg
+ info:(video_frame_info_t *)video_info
+{
+ @autoreleasepool
+ {
+ bool statistics_show = video_info->statistics_show;
+
+ [self _beginFrame];
+
+ _frameView.frameCount = frameCount;
+ if (frame && width && height)
+ {
+ _frameView.size = CGSizeMake(width, height);
+ [_frameView updateFrame:frame pitch:pitch];
+ }
+
+ [self _drawCore];
+ [self _drawMenu:video_info];
+
+ id rce = _context.rce;
+
+#ifdef HAVE_OVERLAY
+ if (_overlay.enabled)
+ {
+ [rce pushDebugGroup:@"overlay"];
+ [_context resetRenderViewport:_overlay.fullscreen ? kFullscreenViewport : kVideoViewport];
+ [rce setRenderPipelineState:[_context getStockShader:VIDEO_SHADER_STOCK_BLEND blend:YES]];
+ [rce setVertexBytes:_context.uniforms length:sizeof(*_context.uniforms) atIndex:BufferIndexUniforms];
+ [rce setFragmentSamplerState:_samplerStateLinear atIndex:SamplerIndexDraw];
+ [_overlay drawWithEncoder:rce];
+ [rce popDebugGroup];
+ }
+#endif
+
+ if (statistics_show)
+ {
+ struct font_params *osd_params = (struct font_params *)&video_info->osd_stat_params;
+
+ if (osd_params)
+ {
+ [rce pushDebugGroup:@"video stats"];
+ font_driver_render_msg(data, video_info->stat_text, osd_params, NULL);
+ [rce popDebugGroup];
+ }
+ }
+
+#ifdef HAVE_GFX_WIDGETS
+ [rce pushDebugGroup:@"display widgets"];
+ if (video_info->widgets_active)
+ gfx_widgets_frame(video_info);
+ [rce popDebugGroup];
+#endif
+
+ if (msg && *msg)
+ {
+ [rce pushDebugGroup:@"message"];
+ [self _renderMessage:msg data:data];
+ [rce popDebugGroup];
+ }
+
+ [self _endFrame];
+ }
+
+ return YES;
+}
+
+- (void)_renderMessage:(const char *)msg
+ data:(void*)data
+{
+ settings_t *settings = config_get_ptr();
+ bool msg_bgcolor_enable = settings->bools.video_msg_bgcolor_enable;
+
+ if (msg_bgcolor_enable)
+ {
+ float r, g, b, a;
+ int msg_width =
+ font_driver_get_message_width(NULL, msg, strlen(msg), 1.0f);
+ float font_size = settings->floats.video_font_size;
+ unsigned bgcolor_red
+ = settings->uints.video_msg_bgcolor_red;
+ unsigned bgcolor_green
+ = settings->uints.video_msg_bgcolor_green;
+ unsigned bgcolor_blue
+ = settings->uints.video_msg_bgcolor_blue;
+ float bgcolor_opacity = settings->floats.video_msg_bgcolor_opacity;
+ float x = settings->floats.video_msg_pos_x;
+ float y = 1.0f - settings->floats.video_msg_pos_y;
+ float width = msg_width / (float)_viewport->full_width;
+ float height = font_size / (float)_viewport->full_height;
+
+ float x2 = 0.005f; /* extend background around text */
+ float y2 = 0.005f;
+
+ y -= height;
+
+ x -= x2;
+ y -= y2;
+ width += x2;
+ height += y2;
+
+ r = bgcolor_red / 255.0f;
+ g = bgcolor_green / 255.0f;
+ b = bgcolor_blue / 255.0f;
+ a = bgcolor_opacity;
+
+ [_context resetRenderViewport:kFullscreenViewport];
+ [_context drawQuadX:x y:y w:width h:height r:r g:g b:b a:a];
+ }
+
+ font_driver_render_msg(data, msg, NULL, NULL);
+}
+
+- (void)_beginFrame
+{
+ video_viewport_t vp = *_viewport;
+ video_driver_update_viewport(_viewport, NO, _keepAspect);
+
+ if (memcmp(&vp, _viewport, sizeof(vp)) != 0)
+ _context.viewport = _viewport;
+
+ [_context begin];
+}
+
+- (void)_drawCore
+{
+ id rce = _context.rce;
+
+ /* draw back buffer */
+ [rce pushDebugGroup:@"core frame"];
+ [_frameView drawWithContext:_context];
+
+ if ((_frameView.drawState & ViewDrawStateEncoder) != 0)
+ {
+ [rce setVertexBytes:_context.uniforms length:sizeof(*_context.uniforms) atIndex:BufferIndexUniforms];
+ [rce setRenderPipelineState:_t_pipelineStateNoAlpha];
+ if (_frameView.filter == RTextureFilterNearest)
+ [rce setFragmentSamplerState:_samplerStateNearest atIndex:SamplerIndexDraw];
+ else
+ [rce setFragmentSamplerState:_samplerStateLinear atIndex:SamplerIndexDraw];
+ [_frameView drawWithEncoder:rce];
+ }
+ [rce popDebugGroup];
+}
+
+- (void)_drawMenu:(video_frame_info_t *)video_info
+{
+ bool menu_is_alive = video_info->menu_is_alive;
+
+ if (!_menu.enabled)
+ return;
+
+ id rce = _context.rce;
+
+ if (_menu.hasFrame)
+ {
+ [rce pushDebugGroup:@"menu frame"];
+ [_menu.view drawWithContext:_context];
+ [rce setVertexBytes:_context.uniforms length:sizeof(*_context.uniforms) atIndex:BufferIndexUniforms];
+ [rce setRenderPipelineState:_t_pipelineState];
+ if (_menu.view.filter == RTextureFilterNearest)
+ [rce setFragmentSamplerState:_samplerStateNearest atIndex:SamplerIndexDraw];
+ else
+ [rce setFragmentSamplerState:_samplerStateLinear atIndex:SamplerIndexDraw];
+ [_menu.view drawWithEncoder:rce];
+ [rce popDebugGroup];
+ }
+#if defined(HAVE_MENU)
+ else
+ {
+ [rce pushDebugGroup:@"menu"];
+ [_context resetRenderViewport:kFullscreenViewport];
+ menu_driver_frame(menu_is_alive, video_info);
+ [rce popDebugGroup];
+ }
+#endif
+}
+
+- (void)_endFrame { [_context end]; }
+/* TODO/FIXME (sgc): resize*/
+- (void)setNeedsResize { }
+- (void)setRotation:(unsigned)rotation { [_context setRotation:rotation]; }
+- (Uniforms *)viewportMVP { return &_viewportMVP; }
+
+#pragma mark - MTKViewDelegate
+
+- (void)mtkView:(MTKView *)view drawableSizeWillChange:(CGSize)size
+{
+#ifdef HAVE_COCOATOUCH
+ CGFloat scale = [[UIScreen mainScreen] scale];
+ [self setViewportWidth:(unsigned int)view.bounds.size.width*scale height:(unsigned int)view.bounds.size.height*scale forceFull:NO allowRotate:YES];
+#else
+ [self setViewportWidth:(unsigned int)size.width height:(unsigned int)size.height forceFull:NO allowRotate:YES];
+#endif
+}
+
+- (void)drawInMTKView:(MTKView *)view { }
+@end
+
+@implementation MetalMenu
+{
+ Context *_context;
+ TexturedView *_view;
+ bool _enabled;
+}
+
+- (instancetype)initWithContext:(Context *)context
+{
+ if (self = [super init])
+ _context = context;
+ return self;
+}
+
+- (bool)hasFrame { return _view != nil; }
+
+- (void)setEnabled:(bool)enabled
+{
+ if (_enabled == enabled)
+ return;
+ _enabled = enabled;
+ _view.visible = enabled;
+}
+
+- (bool)enabled { return _enabled; }
+
+- (void)updateWidth:(int)width
+ height:(int)height
+ format:(RPixelFormat)format
+ filter:(RTextureFilter)filter
+{
+ CGSize size = CGSizeMake(width, height);
+
+ if (_view)
+ {
+ if (!(CGSizeEqualToSize(_view.size, size) &&
+ _view.format == format &&
+ _view.filter == filter))
+ _view = nil;
+ }
+
+ if (!_view)
+ {
+ ViewDescriptor *vd = [ViewDescriptor new];
+ vd.format = format;
+ vd.filter = filter;
+ vd.size = size;
+ _view = [[TexturedView alloc] initWithDescriptor:vd context:_context];
+ _view.visible = _enabled;
+ }
+}
+
+- (void)updateFrame:(void const *)source
+{
+ [_view updateFrame:source pitch:RPixelFormatToBPP(_view.format) * (NSUInteger)_view.size.width];
+}
+
+@end
+
+#pragma mark - FrameView
+
+#define MTLALIGN(x) __attribute__((aligned(x)))
+
+typedef struct
+{
+ float x;
+ float y;
+ float z;
+ float w;
+} float4_t;
+
+typedef struct texture
+{
+ __unsafe_unretained id view;
+ float4_t size_data;
+} texture_t;
+
+typedef struct MTLALIGN(16)
+{
+ matrix_float4x4 mvp;
+
+ struct
+ {
+ texture_t texture[GFX_MAX_FRAME_HISTORY + 1];
+ MTLViewport viewport;
+ float4_t output_size;
+ } frame;
+
+ struct
+ {
+ __unsafe_unretained id buffers[SLANG_CBUFFER_MAX];
+ texture_t rt;
+ texture_t feedback;
+ uint32_t frame_count;
+ int32_t frame_direction;
+ pass_semantics_t semantics;
+ MTLViewport viewport;
+ __unsafe_unretained id _state;
+ } pass[GFX_MAX_SHADERS];
+
+ texture_t luts[GFX_MAX_TEXTURES];
+
+} engine_t;
+
+@implementation FrameView
+{
+ Context *_context;
+ id _texture; /* final render texture */
+ Vertex _v[4];
+ VertexSlang _vertex[4];
+ CGSize _size; /* size of view in pixels */
+ CGRect _frame;
+ NSUInteger _bpp;
+
+ id _src; /* source texture */
+ bool _srcDirty;
+
+ id _samplers[RARCH_FILTER_MAX][RARCH_WRAP_MAX];
+ struct video_shader *_shader;
+
+ engine_t _engine;
+
+ bool resize_render_targets;
+ bool init_history;
+ video_viewport_t *_viewport;
+}
+
+- (instancetype)initWithDescriptor:(ViewDescriptor *)d context:(Context *)c
+{
+ self = [super init];
+ if (self)
+ {
+ _context = c;
+ _format = d.format;
+ _bpp = RPixelFormatToBPP(_format);
+ _filter = d.filter;
+ if (_format == RPixelFormatBGRA8Unorm || _format == RPixelFormatBGRX8Unorm)
+ _drawState = ViewDrawStateEncoder;
+ else
+ _drawState = ViewDrawStateAll;
+ _visible = YES;
+ _engine.mvp = matrix_proj_ortho(0, 1, 0, 1);
+ [self _initSamplers];
+
+ self.size = d.size;
+ self.frame = CGRectMake(0, 0, 1, 1);
+ resize_render_targets = YES;
+
+ /* Initialize slang vertex buffer */
+ VertexSlang v[4] = {
+ {simd_make_float4(0, 1, 0, 1), simd_make_float2(0, 1)},
+ {simd_make_float4(1, 1, 0, 1), simd_make_float2(1, 1)},
+ {simd_make_float4(0, 0, 0, 1), simd_make_float2(0, 0)},
+ {simd_make_float4(1, 0, 0, 1), simd_make_float2(1, 0)},
+ };
+ memcpy(_vertex, v, sizeof(_vertex));
+ }
+ return self;
+}
+
+- (void)_initSamplers
+{
+ int i;
+ MTLSamplerDescriptor *sd = [MTLSamplerDescriptor new];
+
+ /* Initialize samplers */
+ for (i = 0; i < RARCH_WRAP_MAX; i++)
+ {
+ switch (i)
+ {
+ case RARCH_WRAP_BORDER:
+#if defined(HAVE_COCOATOUCH)
+ sd.sAddressMode = MTLSamplerAddressModeClampToZero;
+#else
+ sd.sAddressMode = MTLSamplerAddressModeClampToBorderColor;
+#endif
+ break;
+
+ case RARCH_WRAP_EDGE:
+ sd.sAddressMode = MTLSamplerAddressModeClampToEdge;
+ break;
+
+ case RARCH_WRAP_REPEAT:
+ sd.sAddressMode = MTLSamplerAddressModeRepeat;
+ break;
+
+ case RARCH_WRAP_MIRRORED_REPEAT:
+ sd.sAddressMode = MTLSamplerAddressModeMirrorRepeat;
+ break;
+
+ default:
+ continue;
+ }
+ sd.tAddressMode = sd.sAddressMode;
+ sd.rAddressMode = sd.sAddressMode;
+ sd.minFilter = MTLSamplerMinMagFilterLinear;
+ sd.magFilter = MTLSamplerMinMagFilterLinear;
+
+ id ss = [_context.device newSamplerStateWithDescriptor:sd];
+ _samplers[RARCH_FILTER_LINEAR][i] = ss;
+
+ sd.minFilter = MTLSamplerMinMagFilterNearest;
+ sd.magFilter = MTLSamplerMinMagFilterNearest;
+
+ ss = [_context.device newSamplerStateWithDescriptor:sd];
+ _samplers[RARCH_FILTER_NEAREST][i] = ss;
+ }
+}
+
+- (void)setFilteringIndex:(int)index smooth:(bool)smooth
+{
+ int i;
+ for (i = 0; i < RARCH_WRAP_MAX; i++)
+ {
+ if (smooth)
+ _samplers[RARCH_FILTER_UNSPEC][i] = _samplers[RARCH_FILTER_LINEAR][i];
+ else
+ _samplers[RARCH_FILTER_UNSPEC][i] = _samplers[RARCH_FILTER_NEAREST][i];
+ }
+}
+
+- (void)setSize:(CGSize)size
+{
+ if (CGSizeEqualToSize(_size, size))
+ return;
+
+ _size = size;
+ resize_render_targets = YES;
+
+ if ( _format != RPixelFormatBGRA8Unorm
+ && _format != RPixelFormatBGRX8Unorm)
+ {
+ MTLTextureDescriptor *td = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatR16Uint
+ width:(NSUInteger)size.width
+ height:(NSUInteger)size.height
+ mipmapped:NO];
+ _src = [_context.device newTextureWithDescriptor:td];
+ }
+}
+
+- (CGSize)size { return _size; }
+
+- (void)setFrame:(CGRect)frame
+{
+ if (CGRectEqualToRect(_frame, frame))
+ return;
+
+ /* update vertices */
+ CGPoint o = frame.origin;
+ CGSize s = frame.size;
+
+ CGFloat l = o.x;
+ CGFloat t = o.y;
+ CGFloat r = o.x + s.width;
+ CGFloat b = o.y + s.height;
+
+ Vertex v[4] = {
+ {simd_make_float3(l, b, 0), simd_make_float2(0, 1)},
+ {simd_make_float3(r, b, 0), simd_make_float2(1, 1)},
+ {simd_make_float3(l, t, 0), simd_make_float2(0, 0)},
+ {simd_make_float3(r, t, 0), simd_make_float2(1, 0)},
+ };
+
+ _frame = frame;
+ memcpy(_v, v, sizeof(_v));
+}
+
+- (CGRect)frame { return _frame; }
+
+- (void)_convertFormat
+{
+ if ( _format == RPixelFormatBGRA8Unorm
+ || _format == RPixelFormatBGRX8Unorm)
+ return;
+
+ if (!_srcDirty)
+ return;
+
+ [_context convertFormat:_format from:_src to:_texture];
+ _srcDirty = NO;
+}
+
+- (void)_updateHistory
+{
+ if (_shader)
+ {
+ if (_shader->history_size)
+ {
+ if (init_history)
+ [self _initHistory];
+ else
+ {
+ int k;
+ /* todo: what about frame-duping ?
+ * maybe clone d3d10_texture_t with AddRef */
+ texture_t tmp = _engine.frame.texture[_shader->history_size];
+ for (k = _shader->history_size; k > 0; k--)
+ _engine.frame.texture[k] = _engine.frame.texture[k - 1];
+ _engine.frame.texture[0] = tmp;
+ }
+ }
+ }
+
+ /* either no history, or we moved a texture of a different size in the front slot */
+ if ( _engine.frame.texture[0].size_data.x != _size.width
+ || _engine.frame.texture[0].size_data.y != _size.height)
+ {
+ MTLTextureDescriptor *td = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatBGRA8Unorm
+ width:(NSUInteger)_size.width
+ height:(NSUInteger)_size.height
+ mipmapped:false];
+ td.usage = MTLTextureUsageShaderRead | MTLTextureUsageShaderWrite;
+ [self _initTexture:&_engine.frame.texture[0] withDescriptor:td];
+ }
+}
+
+- (bool)readViewport:(uint8_t *)buffer isIdle:(bool)isIdle
+{
+ bool res;
+ bool enabled = _context.captureEnabled;
+ if (!enabled)
+ _context.captureEnabled = YES;
+
+ video_driver_cached_frame();
+
+ res = [_context readBackBuffer:buffer];
+
+ if (!enabled)
+ _context.captureEnabled = NO;
+
+ return res;
+}
+
+- (void)updateFrame:(void const *)src pitch:(NSUInteger)pitch
+{
+ if (_shader && (_engine.frame.output_size.x != _viewport->width ||
+ _engine.frame.output_size.y != _viewport->height))
+ resize_render_targets = YES;
+
+ _engine.frame.viewport.originX = _viewport->x;
+ _engine.frame.viewport.originY = _viewport->y;
+ _engine.frame.viewport.width = _viewport->width;
+ _engine.frame.viewport.height = _viewport->height;
+ _engine.frame.viewport.znear = 0.0f;
+ _engine.frame.viewport.zfar = 1.0f;
+ _engine.frame.output_size.x = _viewport->width;
+ _engine.frame.output_size.y = _viewport->height;
+ _engine.frame.output_size.z = 1.0f / _viewport->width;
+ _engine.frame.output_size.w = 1.0f / _viewport->height;
+
+ if (resize_render_targets)
+ [self _updateRenderTargets];
+
+ [self _updateHistory];
+
+ if ( _format == RPixelFormatBGRA8Unorm
+ || _format == RPixelFormatBGRX8Unorm)
+ {
+ id tex = _engine.frame.texture[0].view;
+ [tex replaceRegion:MTLRegionMake2D(0, 0, (NSUInteger)_size.width, (NSUInteger)_size.height)
+ mipmapLevel:0 withBytes:src
+ bytesPerRow:pitch];
+ }
+ else
+ {
+ [_src replaceRegion:MTLRegionMake2D(0, 0, (NSUInteger)_size.width, (NSUInteger)_size.height)
+ mipmapLevel:0 withBytes:src
+ bytesPerRow:(NSUInteger)(pitch)];
+ _srcDirty = YES;
+ }
+}
+
+- (void)_initTexture:(texture_t *)t withDescriptor:(MTLTextureDescriptor *)td
+{
+ STRUCT_ASSIGN(t->view, [_context.device newTextureWithDescriptor:td]);
+ t->size_data.x = td.width;
+ t->size_data.y = td.height;
+ t->size_data.z = 1.0f / td.width;
+ t->size_data.w = 1.0f / td.height;
+}
+
+- (void)_initHistory
+{
+ int i;
+ MTLTextureDescriptor *td = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatBGRA8Unorm
+ width:(NSUInteger)_size.width
+ height:(NSUInteger)_size.height
+ mipmapped:false];
+ td.usage = MTLTextureUsageShaderRead
+ | MTLTextureUsageShaderWrite
+ | MTLTextureUsageRenderTarget;
+
+ for (i = 0; i < _shader->history_size + 1; i++)
+ [self _initTexture:&_engine.frame.texture[i] withDescriptor:td];
+ init_history = NO;
+}
+
+- (void)drawWithEncoder:(id)rce
+{
+ if (_texture)
+ {
+ [rce setViewport:_engine.frame.viewport];
+ [rce setVertexBytes:&_v length:sizeof(_v) atIndex:BufferIndexPositions];
+ [rce setFragmentTexture:_texture atIndex:TextureIndexColor];
+ [rce drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4];
+ }
+}
+
+- (void)drawWithContext:(Context *)ctx
+{
+ int i;
+ _texture = _engine.frame.texture[0].view;
+ [self _convertFormat];
+
+ if (!_shader || _shader->passes == 0)
+ return;
+
+ for (i = 0; i < _shader->passes; i++)
+ {
+ if (_shader->pass[i].feedback)
+ {
+ texture_t tmp = _engine.pass[i].feedback;
+ _engine.pass[i].feedback = _engine.pass[i].rt;
+ _engine.pass[i].rt = tmp;
+ }
+ }
+
+ id cb = ctx.blitCommandBuffer;
+ [cb pushDebugGroup:@"shaders"];
+
+ MTLRenderPassDescriptor *rpd = [MTLRenderPassDescriptor new];
+ rpd.colorAttachments[0].loadAction = MTLLoadActionDontCare;
+ rpd.colorAttachments[0].storeAction = MTLStoreActionStore;
+
+ for (i = 0; i < _shader->passes; i++)
+ {
+ int j;
+ __unsafe_unretained id textures[SLANG_NUM_BINDINGS] = {NULL};
+ id samplers[SLANG_NUM_BINDINGS] = {NULL};
+ id rce = nil;
+
+ BOOL backBuffer = (_engine.pass[i].rt.view == nil);
+
+ if (backBuffer)
+ rce = _context.rce;
+ else
+ {
+ rpd.colorAttachments[0].texture = _engine.pass[i].rt.view;
+ rce = [cb renderCommandEncoderWithDescriptor:rpd];
+ }
+
+ [rce setRenderPipelineState:_engine.pass[i]._state];
+
+ NSURL *shaderPath = [NSURL fileURLWithPath:_engine.pass[i]._state.label];
+ rce.label = shaderPath.lastPathComponent.stringByDeletingPathExtension;
+
+ _engine.pass[i].frame_count = (uint32_t)_frameCount;
+ if (_shader->pass[i].frame_count_mod)
+ _engine.pass[i].frame_count %= _shader->pass[i].frame_count_mod;
+
+#ifdef HAVE_REWIND
+ if (state_manager_frame_is_reversed())
+ _engine.pass[i].frame_direction = -1;
+ else
+#else
+ _engine.pass[i].frame_direction = 1;
+#endif
+
+ for (j = 0; j < SLANG_CBUFFER_MAX; j++)
+ {
+ id buffer = _engine.pass[i].buffers[j];
+ cbuffer_sem_t *buffer_sem = &_engine.pass[i].semantics.cbuffers[j];
+
+ if (buffer_sem->stage_mask && buffer_sem->uniforms)
+ {
+ void *data = buffer.contents;
+ uniform_sem_t *uniform = buffer_sem->uniforms;
+
+ while (uniform->size)
+ {
+ if (uniform->data)
+ memcpy((uint8_t *)data + uniform->offset, uniform->data, uniform->size);
+ uniform++;
+ }
+
+ if (buffer_sem->stage_mask & SLANG_STAGE_VERTEX_MASK)
+ [rce setVertexBuffer:buffer offset:0 atIndex:buffer_sem->binding];
+
+ if (buffer_sem->stage_mask & SLANG_STAGE_FRAGMENT_MASK)
+ [rce setFragmentBuffer:buffer offset:0 atIndex:buffer_sem->binding];
+#if !defined(HAVE_COCOATOUCH)
+ [buffer didModifyRange:NSMakeRange(0, buffer.length)];
+#endif
+ }
+ }
+
+ texture_sem_t *texture_sem = _engine.pass[i].semantics.textures;
+ while (texture_sem->stage_mask)
+ {
+ int binding = texture_sem->binding;
+ id tex = (__bridge id)*(void **)texture_sem->texture_data;
+ textures[binding] = tex;
+ samplers[binding] = _samplers[texture_sem->filter][texture_sem->wrap];
+ texture_sem++;
+ }
+
+ if (backBuffer)
+ [rce setViewport:_engine.frame.viewport];
+ else
+ [rce setViewport:_engine.pass[i].viewport];
+
+ [rce setFragmentTextures:textures withRange:NSMakeRange(0, SLANG_NUM_BINDINGS)];
+ [rce setFragmentSamplerStates:samplers withRange:NSMakeRange(0, SLANG_NUM_BINDINGS)];
+ [rce setVertexBytes:_vertex length:sizeof(_vertex) atIndex:4];
+ [rce drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4];
+
+ if (!backBuffer)
+ [rce endEncoding];
+
+ _texture = _engine.pass[i].rt.view;
+ }
+
+ if (_texture == nil)
+ _drawState = ViewDrawStateContext;
+ else
+ _drawState = ViewDrawStateAll;
+
+ [cb popDebugGroup];
+}
+
+- (void)_updateRenderTargets
+{
+ int i;
+ NSUInteger width, height;
+ if (!_shader || !resize_render_targets) return;
+
+ // release existing targets
+ for (i = 0; i < _shader->passes; i++)
+ {
+ STRUCT_ASSIGN(_engine.pass[i].rt.view, nil);
+ STRUCT_ASSIGN(_engine.pass[i].feedback.view, nil);
+ memset(&_engine.pass[i].rt, 0, sizeof(_engine.pass[i].rt));
+ memset(&_engine.pass[i].feedback, 0, sizeof(_engine.pass[i].feedback));
+ }
+
+ width = (NSUInteger)_size.width;
+ height = (NSUInteger)_size.height;
+
+ for (i = 0; i < _shader->passes; i++)
+ {
+ struct video_shader_pass *shader_pass = &_shader->pass[i];
+
+ if (shader_pass->fbo.flags & FBO_SCALE_FLAG_VALID)
+ {
+ switch (shader_pass->fbo.type_x)
+ {
+ case RARCH_SCALE_INPUT:
+ width *= shader_pass->fbo.scale_x;
+ break;
+
+ case RARCH_SCALE_VIEWPORT:
+ width = (NSUInteger)(_viewport->width * shader_pass->fbo.scale_x);
+ break;
+
+ case RARCH_SCALE_ABSOLUTE:
+ width = shader_pass->fbo.abs_x;
+ break;
+
+ default:
+ break;
+ }
+
+ if (!width)
+ width = _viewport->width;
+
+ switch (shader_pass->fbo.type_y)
+ {
+ case RARCH_SCALE_INPUT:
+ height *= shader_pass->fbo.scale_y;
+ break;
+
+ case RARCH_SCALE_VIEWPORT:
+ height = (NSUInteger)(_viewport->height * shader_pass->fbo.scale_y);
+ break;
+
+ case RARCH_SCALE_ABSOLUTE:
+ height = shader_pass->fbo.abs_y;
+ break;
+
+ default:
+ break;
+ }
+
+ if (!height)
+ height = _viewport->height;
+ }
+ else if (i == (_shader->passes - 1))
+ {
+ width = _viewport->width;
+ height = _viewport->height;
+ }
+
+ /* Updating framebuffer size */
+
+ MTLPixelFormat fmt = SelectOptimalPixelFormat(glslang_format_to_metal(_engine.pass[i].semantics.format));
+ if ((i != (_shader->passes - 1)) ||
+ (width != _viewport->width) || (height != _viewport->height) ||
+ fmt != MTLPixelFormatBGRA8Unorm)
+ {
+ _engine.pass[i].viewport.width = width;
+ _engine.pass[i].viewport.height = height;
+ _engine.pass[i].viewport.znear = 0.0;
+ _engine.pass[i].viewport.zfar = 1.0;
+
+ MTLTextureDescriptor *td = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:fmt
+ width:width height:height mipmapped:false];
+ td.storageMode = MTLStorageModePrivate;
+ td.usage = MTLTextureUsageShaderRead
+ | MTLTextureUsageRenderTarget;
+
+ [self _initTexture:&_engine.pass[i].rt withDescriptor:td];
+
+ if (shader_pass->feedback)
+ [self _initTexture:&_engine.pass[i].feedback withDescriptor:td];
+ }
+ else
+ {
+ _engine.pass[i].rt.size_data.x = width;
+ _engine.pass[i].rt.size_data.y = height;
+ _engine.pass[i].rt.size_data.z = 1.0f / width;
+ _engine.pass[i].rt.size_data.w = 1.0f / height;
+ }
+ }
+
+ resize_render_targets = NO;
+}
+
+- (void)_freeVideoShader:(struct video_shader *)shader
+{
+ int i;
+ if (!shader)
+ return;
+
+ for (i = 0; i < GFX_MAX_SHADERS; i++)
+ {
+ int j;
+ STRUCT_ASSIGN(_engine.pass[i].rt.view, nil);
+ STRUCT_ASSIGN(_engine.pass[i].feedback.view, nil);
+ memset(&_engine.pass[i].rt, 0, sizeof(_engine.pass[i].rt));
+ memset(&_engine.pass[i].feedback, 0, sizeof(_engine.pass[i].feedback));
+
+ STRUCT_ASSIGN(_engine.pass[i]._state, nil);
+
+ for (j = 0; j < SLANG_CBUFFER_MAX; j++)
+ {
+ STRUCT_ASSIGN(_engine.pass[i].buffers[j], nil);
+ }
+ }
+
+ for (i = 0; i < GFX_MAX_TEXTURES; i++)
+ {
+ STRUCT_ASSIGN(_engine.luts[i].view, nil);
+ }
+
+ free(shader);
+}
+
+- (BOOL)setShaderFromPath:(NSString *)path
+{
+ [self _freeVideoShader:_shader];
+ _shader = nil;
+
+ struct video_shader *shader = (struct video_shader *)calloc(1, sizeof(*shader));
+ settings_t *settings = config_get_ptr();
+ const char *dir_video_shader = settings->paths.directory_video_shader;
+ NSString *shadersPath = [NSString stringWithFormat:@"%s/", dir_video_shader];
+
+ @try
+ {
+ int i;
+ texture_t *source = NULL;
+ if (!video_shader_load_preset_into_shader(path.UTF8String, shader))
+ return NO;
+
+ source = &_engine.frame.texture[0];
+
+ for (i = 0; i < shader->passes; source = &_engine.pass[i++].rt)
+ {
+ matrix_float4x4 *mvp = (i == shader->passes-1) ? &_context.uniforms->projectionMatrix : &_engine.mvp;
+
+ /* clang-format off */
+ semantics_map_t semantics_map = {
+ {
+ /* Original */
+ {&_engine.frame.texture[0].view, 0,
+ &_engine.frame.texture[0].size_data, 0},
+
+ /* Source */
+ {&source->view, 0,
+ &source->size_data, 0},
+
+ /* OriginalHistory */
+ {&_engine.frame.texture[0].view, sizeof(*_engine.frame.texture),
+ &_engine.frame.texture[0].size_data, sizeof(*_engine.frame.texture)},
+
+ /* PassOutput */
+ {&_engine.pass[0].rt.view, sizeof(*_engine.pass),
+ &_engine.pass[0].rt.size_data, sizeof(*_engine.pass)},
+
+ /* PassFeedback */
+ {&_engine.pass[0].feedback.view, sizeof(*_engine.pass),
+ &_engine.pass[0].feedback.size_data, sizeof(*_engine.pass)},
+
+ /* User */
+ {&_engine.luts[0].view, sizeof(*_engine.luts),
+ &_engine.luts[0].size_data, sizeof(*_engine.luts)},
+ },
+ {
+ mvp, /* MVP */
+ &_engine.pass[i].rt.size_data, /* OutputSize */
+ &_engine.frame.output_size, /* FinalViewportSize */
+ &_engine.pass[i].frame_count, /* FrameCount */
+ &_engine.pass[i].frame_direction, /* FrameDirection */
+ }
+ };
+ /* clang-format on */
+
+ if (!slang_process(shader, i, RARCH_SHADER_METAL, 20000, &semantics_map, &_engine.pass[i].semantics))
+ return NO;
+
+#ifdef DEBUG
+ bool save_msl = true;
+#else
+ bool save_msl = false;
+#endif
+ NSString *vs_src = [NSString stringWithUTF8String:shader->pass[i].source.string.vertex];
+ NSString *fs_src = [NSString stringWithUTF8String:shader->pass[i].source.string.fragment];
+
+ // vertex descriptor
+ @try
+ {
+ NSError *err;
+ MTLVertexDescriptor *vd = [MTLVertexDescriptor new];
+ vd.attributes[0].offset = offsetof(VertexSlang, position);
+ vd.attributes[0].format = MTLVertexFormatFloat4;
+ vd.attributes[0].bufferIndex = 4;
+ vd.attributes[1].offset = offsetof(VertexSlang, texCoord);
+ vd.attributes[1].format = MTLVertexFormatFloat2;
+ vd.attributes[1].bufferIndex = 4;
+ vd.layouts[4].stride = sizeof(VertexSlang);
+ vd.layouts[4].stepFunction = MTLVertexStepFunctionPerVertex;
+
+ MTLRenderPipelineDescriptor *psd = [MTLRenderPipelineDescriptor new];
+
+ psd.label = [[NSString stringWithUTF8String:shader->pass[i].source.path]
+ stringByReplacingOccurrencesOfString:shadersPath withString:@""];
+
+ MTLRenderPipelineColorAttachmentDescriptor *ca = psd.colorAttachments[0];
+
+ ca.pixelFormat = SelectOptimalPixelFormat(glslang_format_to_metal(_engine.pass[i].semantics.format));
+
+ /* TODO(sgc): confirm we never need blending for render passes */
+ ca.blendingEnabled = NO;
+ ca.sourceAlphaBlendFactor = MTLBlendFactorSourceAlpha;
+ ca.sourceRGBBlendFactor = MTLBlendFactorSourceAlpha;
+ ca.destinationAlphaBlendFactor = MTLBlendFactorOneMinusSourceAlpha;
+ ca.destinationRGBBlendFactor = MTLBlendFactorOneMinusSourceAlpha;
+
+ psd.sampleCount = 1;
+ psd.vertexDescriptor = vd;
+
+ id lib = [_context.device newLibraryWithSource:vs_src options:nil error:&err];
+ if (err != nil)
+ {
+ if (lib == nil)
+ {
+ save_msl = true;
+ RARCH_ERR("[Metal]: unable to compile vertex shader: %s\n", err.localizedDescription.UTF8String);
+ return NO;
+ }
+#if DEBUG
+ RARCH_WARN("[Metal]: warnings compiling vertex shader: %s\n", err.localizedDescription.UTF8String);
+#endif
+ }
+
+ psd.vertexFunction = [lib newFunctionWithName:@"main0"];
+
+ lib = [_context.device newLibraryWithSource:fs_src options:nil error:&err];
+ if (err != nil)
+ {
+ if (lib == nil)
+ {
+ save_msl = true;
+ RARCH_ERR("[Metal]: unable to compile fragment shader: %s\n", err.localizedDescription.UTF8String);
+ return NO;
+ }
+#if DEBUG
+ RARCH_WARN("[Metal]: warnings compiling fragment shader: %s\n", err.localizedDescription.UTF8String);
+#endif
+ }
+ psd.fragmentFunction = [lib newFunctionWithName:@"main0"];
+
+ STRUCT_ASSIGN(_engine.pass[i]._state,
+ [_context.device newRenderPipelineStateWithDescriptor:psd error:&err]);
+ if (err != nil)
+ {
+ save_msl = true;
+ RARCH_ERR("[Metal]: error creating pipeline state for pass %d: %s\n", i,
+ err.localizedDescription.UTF8String);
+ return NO;
+ }
+
+ for (unsigned j = 0; j < SLANG_CBUFFER_MAX; j++)
+ {
+ unsigned int size = _engine.pass[i].semantics.cbuffers[j].size;
+ if (size == 0)
+ continue;
+
+ id buf = [_context.device newBufferWithLength:size options:PLATFORM_METAL_RESOURCE_STORAGE_MODE];
+ STRUCT_ASSIGN(_engine.pass[i].buffers[j], buf);
+ }
+ } @finally
+ {
+ if (save_msl)
+ {
+ NSError *err = nil;
+ NSString *basePath = [[NSString stringWithUTF8String:shader->pass[i].source.path] stringByDeletingPathExtension];
+
+ /* Saving Metal shader files... */
+
+ [vs_src writeToFile:[basePath stringByAppendingPathExtension:@"vs.metal"]
+ atomically:NO
+ encoding:NSStringEncodingConversionAllowLossy
+ error:&err];
+ if (err != nil)
+ {
+ RARCH_ERR("[Metal]: unable to save vertex shader source: %s\n", err.localizedDescription.UTF8String);
+ }
+
+ err = nil;
+ [fs_src writeToFile:[basePath stringByAppendingPathExtension:@"fs.metal"]
+ atomically:NO
+ encoding:NSStringEncodingConversionAllowLossy
+ error:&err];
+ if (err != nil)
+ {
+ RARCH_ERR("[Metal]: unable to save fragment shader source: %s\n",
+ err.localizedDescription.UTF8String);
+ }
+ }
+
+ free(shader->pass[i].source.string.vertex);
+ free(shader->pass[i].source.string.fragment);
+
+ shader->pass[i].source.string.vertex = NULL;
+ shader->pass[i].source.string.fragment = NULL;
+ }
+ }
+
+ for (i = 0; i < shader->luts; i++)
+ {
+ struct texture_image image;
+ image.pixels = NULL;
+ image.width = 0;
+ image.height = 0;
+ image.supports_rgba = true;
+
+ if (!image_texture_load(&image, shader->lut[i].path))
+ return NO;
+
+ MTLTextureDescriptor *td = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatRGBA8Unorm
+ width:image.width height:image.height
+ mipmapped:shader->lut[i].mipmap];
+ td.usage = MTLTextureUsageShaderRead;
+ [self _initTexture:&_engine.luts[i] withDescriptor:td];
+
+ [_engine.luts[i].view replaceRegion:MTLRegionMake2D(0, 0, image.width, image.height)
+ mipmapLevel:0 withBytes:image.pixels
+ bytesPerRow:4 * image.width];
+
+ /* TODO(sgc): generate mip maps */
+ image_texture_free(&image);
+ }
+ _shader = shader;
+ shader = nil;
+ }
+ @finally
+ {
+ if (shader)
+ [self _freeVideoShader:shader];
+ }
+
+ resize_render_targets = YES;
+ init_history = YES;
+
+ return YES;
+}
+
+@end
+
+@implementation Overlay
+{
+ Context *_context;
+ NSMutableArray> *_images;
+ id _vert;
+ bool _vertDirty;
+}
+
+- (instancetype)initWithContext:(Context *)context
+{
+ if (self = [super init])
+ _context = context;
+ return self;
+}
+
+- (bool)loadImages:(const struct texture_image *)images count:(NSUInteger)count
+{
+ int i;
+ [self _freeImages];
+
+ _images = [NSMutableArray arrayWithCapacity:count];
+
+ NSUInteger needed = sizeof(SpriteVertex) * count * 4;
+ if (!_vert || _vert.length < needed)
+ _vert = [_context.device newBufferWithLength:needed options:PLATFORM_METAL_RESOURCE_STORAGE_MODE];
+
+ for (i = 0; i < count; i++)
+ {
+ _images[i] = [_context newTexture:images[i] mipmapped:NO];
+ [self updateVertexX:0 y:0 w:1 h:1 index:i];
+ [self updateTextureCoordsX:0 y:0 w:1 h:1 index:i];
+ [self _updateColorRed:1.0 green:1.0 blue:1.0 alpha:1.0 index:i];
+ }
+
+ _vertDirty = YES;
+
+ return YES;
+}
+
+- (void)drawWithEncoder:(id)rce
+{
+ int i;
+ NSUInteger count;
+#if !defined(HAVE_COCOATOUCH)
+ if (_vertDirty)
+ {
+ [_vert didModifyRange:NSMakeRange(0, _vert.length)];
+ _vertDirty = NO;
+ }
+#endif
+
+ count = _images.count;
+ for (i = 0; i < count; ++i)
+ {
+ NSUInteger offset = sizeof(SpriteVertex) * 4 * i;
+ [rce setVertexBuffer:_vert offset:offset atIndex:BufferIndexPositions];
+ [rce setFragmentTexture:_images[i] atIndex:TextureIndexColor];
+ [rce drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4];
+ }
+}
+
+- (SpriteVertex *)_getForIndex:(NSUInteger)index
+{
+ SpriteVertex *pv = (SpriteVertex *)_vert.contents;
+ return &pv[index * 4];
+}
+
+- (void)_updateColorRed:(float)r green:(float)g blue:(float)b alpha:(float)a index:(NSUInteger)index
+{
+ simd_float4 color = simd_make_float4(r, g, b, a);
+ SpriteVertex *pv = [self _getForIndex:index];
+ pv[0].color = color;
+ pv[1].color = color;
+ pv[2].color = color;
+ pv[3].color = color;
+ _vertDirty = YES;
+}
+
+- (void)updateAlpha:(float)alpha index:(NSUInteger)index
+{
+ [self _updateColorRed:1.0 green:1.0 blue:1.0 alpha:alpha index:index];
+}
+
+- (void)updateVertexX:(float)x y:(float)y w:(float)w h:(float)h index:(NSUInteger)index
+{
+ SpriteVertex *pv = [self _getForIndex:index];
+ pv[0].position = simd_make_float2(x, y);
+ pv[1].position = simd_make_float2(x + w, y);
+ pv[2].position = simd_make_float2(x, y + h);
+ pv[3].position = simd_make_float2(x + w, y + h);
+ _vertDirty = YES;
+}
+
+- (void)updateTextureCoordsX:(float)x y:(float)y w:(float)w h:(float)h index:(NSUInteger)index
+{
+ SpriteVertex *pv = [self _getForIndex:index];
+ pv[0].texCoord = simd_make_float2(x, y);
+ pv[1].texCoord = simd_make_float2(x + w, y);
+ pv[2].texCoord = simd_make_float2(x, y + h);
+ pv[3].texCoord = simd_make_float2(x + w, y + h);
+ _vertDirty = YES;
+}
+
+- (void)_freeImages { _images = nil; }
+
+@end
+
+MTLPixelFormat glslang_format_to_metal(glslang_format fmt)
+{
+#undef FMT2
+#define FMT2(x, y) case SLANG_FORMAT_##x: return MTLPixelFormat##y
+
+ switch (fmt)
+ {
+ FMT2(R8_UNORM, R8Unorm);
+ FMT2(R8_SINT, R8Sint);
+ FMT2(R8_UINT, R8Uint);
+ FMT2(R8G8_UNORM, RG8Unorm);
+ FMT2(R8G8_SINT, RG8Sint);
+ FMT2(R8G8_UINT, RG8Uint);
+ FMT2(R8G8B8A8_UNORM, RGBA8Unorm);
+ FMT2(R8G8B8A8_SINT, RGBA8Sint);
+ FMT2(R8G8B8A8_UINT, RGBA8Uint);
+ FMT2(R8G8B8A8_SRGB, RGBA8Unorm_sRGB);
+
+ FMT2(A2B10G10R10_UNORM_PACK32, RGB10A2Unorm);
+ FMT2(A2B10G10R10_UINT_PACK32, RGB10A2Uint);
+
+ FMT2(R16_UINT, R16Uint);
+ FMT2(R16_SINT, R16Sint);
+ FMT2(R16_SFLOAT, R16Float);
+ FMT2(R16G16_UINT, RG16Uint);
+ FMT2(R16G16_SINT, RG16Sint);
+ FMT2(R16G16_SFLOAT, RG16Float);
+ FMT2(R16G16B16A16_UINT, RGBA16Uint);
+ FMT2(R16G16B16A16_SINT, RGBA16Sint);
+ FMT2(R16G16B16A16_SFLOAT, RGBA16Float);
+
+ FMT2(R32_UINT, R32Uint);
+ FMT2(R32_SINT, R32Sint);
+ FMT2(R32_SFLOAT, R32Float);
+ FMT2(R32G32_UINT, RG32Uint);
+ FMT2(R32G32_SINT, RG32Sint);
+ FMT2(R32G32_SFLOAT, RG32Float);
+ FMT2(R32G32B32A32_UINT, RGBA32Uint);
+ FMT2(R32G32B32A32_SINT, RGBA32Sint);
+ FMT2(R32G32B32A32_SFLOAT, RGBA32Float);
+
+ case SLANG_FORMAT_UNKNOWN:
+ default:
+ break;
+ }
+#undef FMT2
+ return MTLPixelFormatInvalid;
+}
+
+MTLPixelFormat SelectOptimalPixelFormat(MTLPixelFormat fmt)
+{
+ switch (fmt)
+ {
+ case MTLPixelFormatRGBA8Unorm:
+ return MTLPixelFormatBGRA8Unorm;
+
+ case MTLPixelFormatRGBA8Unorm_sRGB:
+ return MTLPixelFormatBGRA8Unorm_sRGB;
+
+ default:
+ break;
+ }
+
+ return fmt;
+}
static uint32_t metal_get_flags(void *data);
diff --git a/griffin/griffin_objc.m b/griffin/griffin_objc.m
index db490b03fe..725204e24e 100644
--- a/griffin/griffin_objc.m
+++ b/griffin/griffin_objc.m
@@ -59,7 +59,6 @@
#ifdef HAVE_METAL
#import "../gfx/common/metal/metal_renderer.m"
-#import "../gfx/common/metal_common.m"
#import "../gfx/drivers/metal.m"
#import "../gfx/drivers_display/gfx_display_metal.m"
#import "../gfx/drivers_font/metal_raster_font.m"