From 10947894eac56d71da28d387bf823c7fd2aec7bd Mon Sep 17 00:00:00 2001 From: reallibretroadmin Date: Sun, 8 Jan 2023 01:37:54 +0100 Subject: [PATCH] Combine metal_common.m into metal.m --- Makefile.common | 1 - gfx/common/metal_common.m | 1547 ------------------------------------- gfx/drivers/metal.m | 1537 +++++++++++++++++++++++++++++++++++- griffin/griffin_objc.m | 1 - 4 files changed, 1525 insertions(+), 1561 deletions(-) delete mode 100644 gfx/common/metal_common.m 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"