From a0900ec433b0bf688850284a69685e750eb16f90 Mon Sep 17 00:00:00 2001 From: Stuart Carnie Date: Tue, 3 Jul 2018 22:28:33 -0700 Subject: [PATCH] feat(Metal): full xmb pipeline support Menu TODOs: * understand why ribbon does not look the same as GL * add clear support to `MenuDisplay` for glui --- gfx/common/metal/Context.h | 26 +- gfx/common/metal/Context.m | 208 +++++++++++-- gfx/common/metal/Filter.h | 4 +- gfx/common/metal/MenuDisplay.h | 5 +- gfx/common/metal/MenuDisplay.m | 223 ++++++-------- gfx/common/metal/RendererCommon.h | 10 + gfx/common/metal/RendererCommon.m | 8 +- gfx/common/metal/TexturedView.h | 12 +- gfx/common/metal/View.h | 6 +- gfx/common/metal/menu_pipeline.metal | 273 ++++++++++++++++++ gfx/common/metal_common.h | 38 +-- gfx/common/metal_common.m | 226 +++++++++++---- gfx/drivers_font/metal_raster_font.m | 55 ++-- menu/drivers_display/menu_display_metal.m | 2 +- pkg/apple/BaseConfig.xcconfig | 2 +- .../RetroArch_Metal.xcodeproj/project.pbxproj | 56 ++-- ui/drivers/cocoa/cocoa_common.m | 5 + ui/drivers/ui_cocoa.m | 4 +- 18 files changed, 858 insertions(+), 305 deletions(-) create mode 100644 gfx/common/metal/menu_pipeline.metal diff --git a/gfx/common/metal/Context.h b/gfx/common/metal/Context.h index 79577dfd22..d335e80510 100644 --- a/gfx/common/metal/Context.h +++ b/gfx/common/metal/Context.h @@ -11,27 +11,35 @@ #import "RendererCommon.h" @interface Texture : NSObject -@property (readonly) id texture; -@property (readonly) id sampler; +@property (nonatomic, readonly) id texture; +@property (nonatomic, readonly) id sampler; @end +typedef struct +{ + void *data; + NSUInteger offset; + __unsafe_unretained id buffer; +} BufferRange; + /*! @brief Context contains the render state used by various components */ @interface Context : NSObject -@property (readonly) id device; -@property (readonly) id library; +@property (nonatomic, readonly) id device; +@property (nonatomic, readonly) id library; +@property (nonatomic, readwrite) MTLClearColor clearColor; /*! @brief Returns the command buffer used for pre-render work, * such as mip maps for applying filters * */ -@property (readonly) id blitCommandBuffer; +@property (nonatomic, readonly) id blitCommandBuffer; /*! @brief Returns the command buffer for the current frame */ -@property (readonly) id commandBuffer; -@property (readonly) id nextDrawable; +@property (nonatomic, readonly) id commandBuffer; +@property (nonatomic, readonly) id nextDrawable; /*! @brief Main render encoder to back buffer */ -@property (readonly) id rce; +@property (nonatomic, readonly) id rce; - (instancetype)initWithDevice:(id)d layer:(CAMetalLayer *)layer @@ -40,6 +48,8 @@ - (Texture *)newTexture:(struct texture_image)image filter:(enum texture_filter_type)filter; - (void)convertFormat:(RPixelFormat)fmt from:(id)src to:(id)dst; +- (bool)allocRange:(BufferRange *)range length:(NSUInteger)length; + /*! @brief begin marks the beginning of a frame */ - (void)begin; diff --git a/gfx/common/metal/Context.m b/gfx/common/metal/Context.m index cc77a484b2..16e9d8142d 100644 --- a/gfx/common/metal/Context.m +++ b/gfx/common/metal/Context.m @@ -10,9 +10,22 @@ #import "Filter.h" #import +@interface BufferNode : NSObject +@property (nonatomic, readonly) id src; +@property (nonatomic, readwrite) NSUInteger allocated; +@property (nonatomic, readwrite) BufferNode *next; +@end + +@interface BufferChain : NSObject +- (instancetype)initWithDevice:(id)device blockLen:(NSUInteger)blockLen; +- (bool)allocRange:(BufferRange *)range length:(NSUInteger)length; +- (void)commitRanges; +- (void)discard; +@end + @interface Texture() -@property (readwrite) id texture; -@property (readwrite) id sampler; +@property (nonatomic, readwrite) id texture; +@property (nonatomic, readwrite) id sampler; @end @interface Context() @@ -32,6 +45,10 @@ id _rce; id _blitCommandBuffer; + + NSUInteger _currentChain; + BufferChain *_chain[CHAIN_LENGTH]; + MTLClearColor _clearColor; } - (instancetype)initWithDevice:(id)d @@ -45,31 +62,39 @@ _layer = layer; _library = l; _commandQueue = [_device newCommandQueue]; + _clearColor = MTLClearColorMake(0, 0, 0, 1); + + { + MTLSamplerDescriptor *sd = [MTLSamplerDescriptor new]; - MTLSamplerDescriptor *sd = [MTLSamplerDescriptor new]; + sd.label = @"NEAREST"; + _samplers[TEXTURE_FILTER_NEAREST] = [d newSamplerStateWithDescriptor:sd]; - sd.label = @"NEAREST"; - _samplers[TEXTURE_FILTER_NEAREST] = [d newSamplerStateWithDescriptor:sd]; + sd.mipFilter = MTLSamplerMipFilterNearest; + sd.label = @"MIPMAP_NEAREST"; + _samplers[TEXTURE_FILTER_MIPMAP_NEAREST] = [d newSamplerStateWithDescriptor:sd]; - sd.mipFilter = MTLSamplerMipFilterNearest; - sd.label = @"MIPMAP_NEAREST"; - _samplers[TEXTURE_FILTER_MIPMAP_NEAREST] = [d newSamplerStateWithDescriptor:sd]; - - sd.mipFilter = MTLSamplerMipFilterNotMipmapped; - sd.minFilter = MTLSamplerMinMagFilterLinear; - sd.magFilter = MTLSamplerMinMagFilterLinear; - sd.label = @"LINEAR"; - _samplers[TEXTURE_FILTER_LINEAR] = [d newSamplerStateWithDescriptor:sd]; - - sd.mipFilter = MTLSamplerMipFilterLinear; - sd.label = @"MIPMAP_LINEAR"; - _samplers[TEXTURE_FILTER_MIPMAP_LINEAR] = [d newSamplerStateWithDescriptor:sd]; + sd.mipFilter = MTLSamplerMipFilterNotMipmapped; + sd.minFilter = MTLSamplerMinMagFilterLinear; + sd.magFilter = MTLSamplerMinMagFilterLinear; + sd.label = @"LINEAR"; + _samplers[TEXTURE_FILTER_LINEAR] = [d newSamplerStateWithDescriptor:sd]; + sd.mipFilter = MTLSamplerMipFilterLinear; + sd.label = @"MIPMAP_LINEAR"; + _samplers[TEXTURE_FILTER_MIPMAP_LINEAR] = [d newSamplerStateWithDescriptor:sd]; + } + if (![self _initConversionFilters]) return nil; if (![self _initMainState]) return nil; + + for (int i = 0; i < CHAIN_LENGTH; i++) + { + _chain[i] = [[BufferChain alloc] initWithDevice:_device blockLen:65536]; + } } return self; } @@ -186,6 +211,12 @@ return _blitCommandBuffer; } +- (void)_nextChain +{ + _currentChain = (_currentChain + 1) % CHAIN_LENGTH; + [_chain[_currentChain] discard]; +} + - (void)begin { assert(_commandBuffer == nil); @@ -199,6 +230,8 @@ if (_rce == nil) { MTLRenderPassDescriptor *rpd = [MTLRenderPassDescriptor new]; + rpd.colorAttachments[0].clearColor = _clearColor; + rpd.colorAttachments[0].loadAction = MTLLoadActionClear; rpd.colorAttachments[0].texture = self.nextDrawable.texture; _rce = [_commandBuffer renderCommandEncoderWithDescriptor:rpd]; } @@ -207,7 +240,9 @@ - (void)end { - assert(self->_commandBuffer != nil); + assert(_commandBuffer != nil); + + [_chain[_currentChain] commitRanges]; if (_blitCommandBuffer) { @@ -233,9 +268,144 @@ _commandBuffer = nil; _drawable = nil; + [self _nextChain]; +} + +- (bool)allocRange:(BufferRange *)range length:(NSUInteger)length +{ + return [_chain[_currentChain] allocRange:range length:length]; } @end @implementation Texture @end + +@implementation BufferNode + +- (instancetype)initWithBuffer:(id)src +{ + if (self = [super init]) + { + _src = src; + } + return self; +} + +@end + +@implementation BufferChain +{ + id _device; + NSUInteger _blockLen; + BufferNode *_head; + NSUInteger _offset; // offset into _current + BufferNode *_current; + NSUInteger _length; + NSUInteger _allocated; +} + +/* macOS requires constants in a buffer to have a 256 byte alignment. */ +#ifdef TARGET_OS_MAC +static const NSUInteger kConstantAlignment = 256; +#else +static const NSUInteger kConstantAlignment = 4; +#endif + + +- (instancetype)initWithDevice:(id)device blockLen:(NSUInteger)blockLen +{ + if (self = [super init]) + { + _device = device; + _blockLen = blockLen; + } + return self; +} + +- (NSString *)debugDescription +{ + return [NSString stringWithFormat:@"length=%ld, allocated=%ld", _length, _allocated]; +} + +- (void)commitRanges +{ + for (BufferNode *n = _head; n != nil; n = n.next) + { + if (n.allocated > 0) + { + [n.src didModifyRange:NSMakeRange(0, n.allocated)]; + } + } +} + +- (void)discard +{ + _current = _head; + _offset = 0; + _allocated = 0; +} + +- (bool)allocRange:(BufferRange *)range length:(NSUInteger)length +{ + bzero(range, sizeof(*range)); + + if (!_head) + { + _head = [[BufferNode alloc] initWithBuffer:[_device newBufferWithLength:_blockLen options:MTLResourceStorageModeManaged]]; + _length += _blockLen; + _current = _head; + _offset = 0; + } + + if ([self _subAllocRange:range length:length]) + return YES; + + while (_current.next) + { + [self _nextNode]; + if ([self _subAllocRange:range length:length]) + return YES; + } + + NSUInteger blockLen = _blockLen; + if (length > blockLen) + { + blockLen = length; + } + + _current.next = [[BufferNode alloc] initWithBuffer:[_device newBufferWithLength:blockLen options:MTLResourceStorageModeManaged]]; + if (!_current.next) + return NO; + + _length += blockLen; + + [self _nextNode]; + retro_assert([self _subAllocRange:range length:length]); + return YES; +} + +- (void)_nextNode +{ + _current = _current.next; + _offset = 0; +} + +- (BOOL)_subAllocRange:(BufferRange *)range length:(NSUInteger)length +{ + NSUInteger nextOffset = _offset + length; + if (nextOffset <= _current.src.length) + { + _current.allocated = nextOffset; + _allocated += length; + range->data = _current.src.contents + _offset; + range->buffer = _current.src; + range->offset = _offset; + _offset = MTL_ALIGN_BUFFER(nextOffset); + return YES; + } + return NO; +} + + +@end diff --git a/gfx/common/metal/Filter.h b/gfx/common/metal/Filter.h index cf185beba0..d40f0b8798 100644 --- a/gfx/common/metal/Filter.h +++ b/gfx/common/metal/Filter.h @@ -15,8 +15,8 @@ @interface Filter : NSObject -@property (readwrite) id delegate; -@property (readonly) id sampler; +@property (nonatomic, readwrite) id delegate; +@property (nonatomic, readonly) id sampler; -(void)apply:(id)cb in:(id)tin out:(id)tout; -(void)apply:(id)cb inBuf:(id)tin outTex:(id)tout; diff --git a/gfx/common/metal/MenuDisplay.h b/gfx/common/metal/MenuDisplay.h index ff5347e38f..f467029d6e 100644 --- a/gfx/common/metal/MenuDisplay.h +++ b/gfx/common/metal/MenuDisplay.h @@ -10,8 +10,8 @@ @interface MenuDisplay : NSObject -@property (readwrite) BOOL blend; -@property (readwrite) MTLClearColor clearColor; +@property (nonatomic, readwrite) BOOL blend; +@property (nonatomic, readwrite) MTLClearColor clearColor; - (instancetype)initWithDriver:(MetalDriver *)driver; - (void)drawPipeline:(menu_display_ctx_draw_t *)draw video:(video_frame_info_t *)video; @@ -22,7 +22,6 @@ + (const float *)defaultVertices; + (const float *)defaultTexCoords; + (const float *)defaultColor; -+ (const float *)defaultMatrix; @end diff --git a/gfx/common/metal/MenuDisplay.m b/gfx/common/metal/MenuDisplay.m index 7bc93f2a1e..08b858364a 100644 --- a/gfx/common/metal/MenuDisplay.m +++ b/gfx/common/metal/MenuDisplay.m @@ -45,10 +45,10 @@ + (const float *)defaultTexCoords { static float dummy[] = { - 0.0f, 1.0f, - 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 1.0f, + 1.0f, 1.0f, }; return &dummy[0]; } @@ -64,17 +64,6 @@ return &dummy[0]; } -+ (const float *)defaultMatrix -{ - static matrix_float4x4 dummy; - - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - dummy = matrix_proj_ortho(0, 1, 0, 1); - }); - return &dummy; -} - - (void)setClearColor:(MTLClearColor)clearColor { _clearColor = clearColor; @@ -100,133 +89,103 @@ } } -static INLINE void write_quad4(SpriteVertex *pv, - float x, float y, float width, float height, float scale, - float tex_x, float tex_y, float tex_width, float tex_height, - const float *color) -{ - unsigned i; - static const float strip[2 * 4] = { - 0.0f, 1.0f, - 1.0f, 1.0f, - 0.0f, 0.0f, - 1.0f, 0.0f, - }; - - float swidth = width * scale; - float sheight = height * scale; - - x += (width - swidth) / 2; - y += (height - sheight) / 2; - - for (i = 0; i < 4; i++) - { - pv[i].position = simd_make_float2(x + strip[2 * i + 0] * swidth, - y + strip[2 * i + 1] * sheight); - pv[i].texCoord = simd_make_float2(tex_x + strip[2 * i + 0] * tex_width, - tex_y + strip[2 * i + 1] * tex_height); - pv[i].color = simd_make_float4(color[0], color[1], color[2], color[3]); - color += 4; - } -} - -static INLINE void write_quad4a(SpriteVertex *pv, - float x, float y, float width, float height, float scale, - float tex_x, float tex_y, float tex_width, float tex_height, - const float *color) -{ - unsigned i; - static const float vert[2 * 4] = { - 0.0f, 1.0f, - 1.0f, 1.0f, - 0.0f, 0.0f, - 1.0f, 0.0f, - }; - static const float tex[2 * 4] = { - 0.0f, 0.0f, - 1.0f, 0.0f, - 0.0f, 1.0f, - 1.0f, 1.0f, - }; - - float swidth = width * scale; - float sheight = height * scale; - - x += (width - swidth) / 2; - y += (height - sheight) / 2; - - for (i = 0; i < 4; i++) - { - pv[i].position = simd_make_float2(x + vert[2 * i + 0] * swidth, - y + vert[2 * i + 1] * sheight); - pv[i].texCoord = simd_make_float2(tex_x + tex[2 * i + 0] * tex_width, - tex_y + tex[2 * i + 1] * tex_height); - pv[i].color = simd_make_float4(color[0], color[1], color[2], color[3]); - color += 4; - } -} - - (void)drawPipeline:(menu_display_ctx_draw_t *)draw video:(video_frame_info_t *)video { - + static struct video_coords blank_coords; + + draw->x = 0; + draw->y = 0; + draw->matrix_data = NULL; + + _uniforms.outputSize = simd_make_float2(_driver.viewport->full_width, _driver.viewport->full_height); + + draw->pipeline.backend_data = &_uniforms; + draw->pipeline.backend_data_size = sizeof(_uniforms); + + switch (draw->pipeline.id) + { + // ribbon + default: + case VIDEO_SHADER_MENU: + case VIDEO_SHADER_MENU_2: + { + video_coord_array_t *ca = menu_display_get_coords_array(); + draw->coords = (struct video_coords *)&ca->coords; + break; + } + + case VIDEO_SHADER_MENU_3: + case VIDEO_SHADER_MENU_4: + case VIDEO_SHADER_MENU_5: + case VIDEO_SHADER_MENU_6: + { + draw->coords = &blank_coords; + blank_coords.vertices = 4; + draw->prim_type = MENU_DISPLAY_PRIM_TRIANGLESTRIP; + break; + } + } + + _uniforms.time += 0.01; } - (void)draw:(menu_display_ctx_draw_t *)draw video:(video_frame_info_t *)video { Texture *tex = (__bridge Texture *)(void *)draw->texture; - const float *vertex = draw->coords->vertex; - const float *tex_coord = draw->coords->tex_coord; - const float *color = draw->coords->color; + const float *vertex = draw->coords->vertex ?: MenuDisplay.defaultVertices; + const float *tex_coord = draw->coords->tex_coord ?: MenuDisplay.defaultTexCoords; + const float *color = draw->coords->color ?: MenuDisplay.defaultColor; - if (!vertex) - vertex = MenuDisplay.defaultVertices; - if (!tex_coord) - tex_coord = MenuDisplay.defaultTexCoords; - if (!draw->coords->lut_tex_coord) - draw->coords->lut_tex_coord = MenuDisplay.defaultTexCoords; - - // TODO(sgc): is this necessary? - // if (!texture) - // texture = &vk->display.blank_texture; - if (!color) - color = MenuDisplay.defaultColor; - - assert(draw->coords->vertices <= 4); - SpriteVertex buf[4]; - SpriteVertex *pv = buf; - Uniforms *uniforms; - if (draw->coords->vertex == NULL) + NSUInteger needed = draw->coords->vertices * sizeof(SpriteVertex); + BufferRange range; + if (![_context allocRange:&range length:needed]) { - write_quad4a(pv, - draw->x, - draw->y, - draw->width, - draw->height, - draw->scale_factor, - 0.0, 0.0, 1.0, 1.0, color); + RARCH_ERR("[Metal]: MenuDisplay unable to allocate buffer of %d bytes", needed); + return; + } + + NSUInteger vertexCount = draw->coords->vertices; + SpriteVertex *pv = (SpriteVertex *)range.data; + for (unsigned i = 0; i < draw->coords->vertices; i++, pv++) + { + pv->position = simd_make_float2(vertex[0], 1.0 - vertex[1]); + vertex += 2; - uniforms = _driver.viewportMVP; - } - else - { - for (unsigned i = 0; i < draw->coords->vertices; i++, pv++) - { - /* Y-flip. We're using top-left coordinates */ - pv->position = simd_make_float2(vertex[0], vertex[1]); - vertex += 2; - - pv->texCoord = simd_make_float2(tex_coord[0], tex_coord[1]); - tex_coord += 2; - - pv->color = simd_make_float4(color[0], color[1], color[2], color[3]); - color += 4; - } - uniforms = &_uniforms; + pv->texCoord = simd_make_float2(tex_coord[0], tex_coord[1]); + tex_coord += 2; + + pv->color = simd_make_float4(color[0], color[1], color[2], color[3]); + color += 4; } + id rce = _context.rce; + MTLViewport vp = { + .originX = draw->x, + .originY = _driver.viewport->full_height - draw->y - draw->height, + .width = draw->width, + .height = draw->height, + .znear = 0, + .zfar = 1, + }; + [rce setViewport:vp]; + switch (draw->pipeline.id) { -#ifdef HAVE_SHADERPIPELINE +#if HAVE_SHADERPIPELINE + case VIDEO_SHADER_MENU: + case VIDEO_SHADER_MENU_2: + case VIDEO_SHADER_MENU_3: + case VIDEO_SHADER_MENU_4: + case VIDEO_SHADER_MENU_5: + case VIDEO_SHADER_MENU_6: + { + [rce setRenderPipelineState:[_driver getStockShader:draw->pipeline.id blend:_blend]]; + [rce setVertexBytes:draw->pipeline.backend_data length:draw->pipeline.backend_data_size atIndex:BufferIndexUniforms]; + [rce setVertexBuffer:range.buffer offset:range.offset atIndex:BufferIndexPositions]; + [rce setFragmentBytes:draw->pipeline.backend_data length:draw->pipeline.backend_data_size atIndex:BufferIndexUniforms]; + [rce drawPrimitives:[self _toPrimitiveType:draw->prim_type] vertexStart:0 vertexCount:vertexCount]; + break; + } #endif default: { @@ -235,15 +194,17 @@ static INLINE void write_quad4a(SpriteVertex *pv, // TODO(sgc): draw quad to clear _clearNextRender = NO; } - - id rce = _context.rce; [rce setRenderPipelineState:[_driver getStockShader:VIDEO_SHADER_STOCK_BLEND blend:_blend]]; - [rce setVertexBytes:uniforms length:sizeof(*uniforms) atIndex:BufferIndexUniforms]; - [rce setVertexBytes:buf length:sizeof(buf) atIndex:BufferIndexPositions]; + Uniforms uniforms = { + .projectionMatrix = draw->matrix_data ? *(matrix_float4x4 *)draw->matrix_data + : _uniforms.projectionMatrix + }; + [rce setVertexBytes:&uniforms length:sizeof(uniforms) atIndex:BufferIndexUniforms]; + [rce setVertexBuffer:range.buffer offset:range.offset atIndex:BufferIndexPositions]; [rce setFragmentTexture:tex.texture atIndex:TextureIndexColor]; [rce setFragmentSamplerState:tex.sampler atIndex:SamplerIndexDraw]; - [rce drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4]; + [rce drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:vertexCount]; break; } diff --git a/gfx/common/metal/RendererCommon.h b/gfx/common/metal/RendererCommon.h index 8a443a2161..2661ace833 100644 --- a/gfx/common/metal/RendererCommon.h +++ b/gfx/common/metal/RendererCommon.h @@ -14,6 +14,16 @@ // TODO(sgc): implement triple buffering /*! @brief maximum inflight frames */ #define MAX_INFLIGHT 1 +#define CHAIN_LENGTH 3 + +/* macOS requires constants in a buffer to have a 256 byte alignment. */ +#ifdef TARGET_OS_MAC +#define kMetalBufferAlignment 256 +#else +#define kMetalBufferAlignment 4 +#endif + +#define MTL_ALIGN_BUFFER(size) ((size + kMetalBufferAlignment - 1) & (~(kMetalBufferAlignment - 1))) #pragma mark - Pixel Formats diff --git a/gfx/common/metal/RendererCommon.m b/gfx/common/metal/RendererCommon.m index b914c108d3..3751d20e2d 100644 --- a/gfx/common/metal/RendererCommon.m +++ b/gfx/common/metal/RendererCommon.m @@ -62,10 +62,10 @@ matrix_float4x4 matrix_proj_ortho(float left, float right, float top, float bott float ty = (top + bottom) / (bottom - top); float tz = near / (far - near); - simd_float4 P = {sx, 0, 0, 0}; - simd_float4 Q = {0, sy, 0, 0}; - simd_float4 R = {0, 0, sz, 0}; - simd_float4 S = {tx, ty, tz, 1}; + simd_float4 P = simd_make_float4(sx, 0, 0, 0); + simd_float4 Q = simd_make_float4(0, sy, 0, 0); + simd_float4 R = simd_make_float4(0, 0, sz, 0); + simd_float4 S = simd_make_float4(tx, ty, tz, 1); matrix_float4x4 mat = {P, Q, R, S}; return mat; diff --git a/gfx/common/metal/TexturedView.h b/gfx/common/metal/TexturedView.h index 5ed51728b3..750dd6d7e5 100644 --- a/gfx/common/metal/TexturedView.h +++ b/gfx/common/metal/TexturedView.h @@ -6,12 +6,12 @@ @interface TexturedView : NSObject -@property (readonly) RPixelFormat format; -@property (readonly) RTextureFilter filter; -@property (readwrite) BOOL visible; -@property (readwrite) CGRect frame; -@property (readwrite) CGSize size; -@property (readonly) ViewDrawState drawState; +@property (nonatomic, readonly) RPixelFormat format; +@property (nonatomic, readonly) RTextureFilter filter; +@property (nonatomic, readwrite) BOOL visible; +@property (nonatomic, readwrite) CGRect frame; +@property (nonatomic, readwrite) CGSize size; +@property (nonatomic, readonly) ViewDrawState drawState; - (instancetype)initWithDescriptor:(ViewDescriptor *)td context:(Context *)c; diff --git a/gfx/common/metal/View.h b/gfx/common/metal/View.h index 7345a6bd7b..b8f62439a1 100644 --- a/gfx/common/metal/View.h +++ b/gfx/common/metal/View.h @@ -20,9 +20,9 @@ typedef NS_ENUM(NSInteger, ViewDrawState) }; @interface ViewDescriptor : NSObject -@property (readwrite) RPixelFormat format; -@property (readwrite) RTextureFilter filter; -@property (readwrite) CGSize size; +@property (nonatomic, readwrite) RPixelFormat format; +@property (nonatomic, readwrite) RTextureFilter filter; +@property (nonatomic, readwrite) CGSize size; - (instancetype)init; @end diff --git a/gfx/common/metal/menu_pipeline.metal b/gfx/common/metal/menu_pipeline.metal new file mode 100644 index 0000000000..df1e587496 --- /dev/null +++ b/gfx/common/metal/menu_pipeline.metal @@ -0,0 +1,273 @@ +// +// pipeline_ribbon.metal +// RetroArch +// +// Created by Stuart Carnie on 6/30/18. +// + +#include + +#import "ShaderTypes.h" + +using namespace metal; + +#pragma mark - ribbon simple + +namespace ribbon { + +float iqhash(float n) +{ + return fract(sin(n) * 43758.5453); +} + +float noise(float3 x) +{ + float3 p = floor(x); + float3 f = fract(x); + f = f * f * (3.0 - 2.0 * f); + float n = p.x + p.y * 57.0 + 113.0 * p.z; + return mix(mix(mix(iqhash(n), iqhash(n + 1.0), f.x), + mix(iqhash(n + 57.0), iqhash(n + 58.0), f.x), f.y), + mix(mix(iqhash(n + 113.0), iqhash(n + 114.0), f.x), + mix(iqhash(n + 170.0), iqhash(n + 171.0), f.x), f.y), f.z); +} + +float xmb_noise2(float3 x, const device Uniforms &constants) +{ + return cos(x.z * 4.0) * cos(x.z + constants.time / 10.0 + x.x); +} + +} + +#pragma mark - ribbon simple + +vertex FontFragmentIn ribbon_simple_vertex(const SpriteVertex in [[ stage_in ]], const device Uniforms &constants [[ buffer(BufferIndexUniforms) ]]) +{ + float4 t = (constants.projectionMatrix * float4(in.position, 0, 1)); + + float3 v = float3(t.x, 0.0, 1.0-t.y); + float3 v2 = v; + + v2.x = v2.x + constants.time / 2.0; + v2.z = v.z * 3.0; + v.y = cos((v.x + v.z / 3.0 + constants.time) * 2.0) / 10.0 + ribbon::noise(v2.xyz) / 4.0; + v.y = -v.y; + + FontFragmentIn out; + out.position = float4(v, 1.0); + return out; +} + +fragment float4 ribbon_simple_fragment() +{ + return float4(0.05, 0.05, 0.05, 1.0); +} + +#pragma mark - ribbon + +typedef struct +{ + vector_float4 position [[position]]; + vector_float3 vEC; +} RibbonOutIn; + + +vertex RibbonOutIn ribbon_vertex(const SpriteVertex in [[ stage_in ]], const device Uniforms &constants [[ buffer(BufferIndexUniforms) ]]) +{ + float4 t = (constants.projectionMatrix * float4(in.position, 0, 1)); + + float3 v = float3(t.x, 0.0, 1.0-t.y); + float3 v2 = v; + float3 v3 = v; + + v.y = ribbon::xmb_noise2(v2, constants) / 8.0; + + v3.x -= constants.time / 5.0; + v3.x /= 4.0; + + v3.z -= constants.time / 10.0; + v3.y -= constants.time / 100.0; + + v.z -= ribbon::noise(v3 * 7.0) / 15.0; + v.y -= ribbon::noise(v3 * 7.0) / 15.0 + cos(v.x * 2.0 - constants.time / 2.0) / 5.0 - 0.3; + v.y = -v.y; + + RibbonOutIn out; + out.vEC = v; + out.position = float4(v, 1.0); + return out; +} + +fragment float4 ribbon_fragment(RibbonOutIn in [[ stage_in ]]) +{ + const float3 up = float3(0.0, 0.0, 1.0); + float3 x = dfdx(in.vEC); + float3 y = dfdy(in.vEC); + float3 normal = normalize(cross(x, y)); + float c = 1.0 - dot(normal, up); + c = (1.0 - cos(c * c)) / 13.0; + return float4(c, c, c, 1.0); +} + +#pragma mark - snow constants + +constant float snowBaseScale [[ function_constant(0) ]]; // [1.0 .. 10.0] +constant float snowDensity [[ function_constant(1) ]]; // [0.01 .. 1.0] +constant float snowSpeed [[ function_constant(2) ]]; // [0.1 .. 1.0] + +#pragma mark - snow simple + +namespace snow +{ + +float rand(float2 co) +{ + return fract(sin(dot(co.xy, float2(12.9898, 78.233))) * 43758.5453); +} + +float dist_func(float2 distv) +{ + float dist = sqrt((distv.x * distv.x) + (distv.y * distv.y)) * (40.0 / snowBaseScale); + dist = clamp(dist, 0.0, 1.0); + return cos(dist * (3.14159265358 * 0.5)) * 0.5; +} + +float random_dots(float2 co) +{ + float part = 1.0 / 20.0; + float2 cd = floor(co / part); + float p = rand(cd); + + if (p > 0.005 * (snowDensity * 40.0)) + return 0.0; + + float2 dpos = (float2(fract(p * 2.0) , p) + float2(2.0, 2.0)) * 0.25; + + float2 cellpos = fract(co / part); + float2 distv = (cellpos - dpos); + + return dist_func(distv); +} + +float snow(float2 pos, float time, float scale) +{ + // add wobble + pos.x += cos(pos.y * 1.2 + time * 3.14159 * 2.0 + 1.0 / scale) / (8.0 / scale) * 4.0; + // add gravity + pos += time * scale * float2(-0.5, 1.0) * 4.0; + return random_dots(pos / scale) * (scale * 0.5 + 0.5); +} + +} + +fragment float4 snow_fragment(FontFragmentIn in [[ stage_in ]], + const device Uniforms &constants [[ buffer(BufferIndexUniforms) ]]) +{ + float tim = constants.time * 0.4 * snowSpeed; + float2 pos = in.position.xy / constants.outputSize.xx; + pos.y = 1.0 - pos.y; // Flip Y + float a = 0.0; + // Each of these is a layer of snow + // Remove some for better performance + // Changing the scale (3rd value) will mess with the looping + a += snow::snow(pos, tim, 1.0); + a += snow::snow(pos, tim, 0.7); + a += snow::snow(pos, tim, 0.6); + a += snow::snow(pos, tim, 0.5); + a += snow::snow(pos, tim, 0.4); + a += snow::snow(pos, tim, 0.3); + a += snow::snow(pos, tim, 0.25); + a += snow::snow(pos, tim, 0.125); + a = a * min(pos.y * 4.0, 1.0); + return float4(1.0, 1.0, 1.0, a); +} + +fragment float4 bokeh_fragment(FontFragmentIn in [[ stage_in ]], + const device Uniforms &constants [[ buffer(BufferIndexUniforms) ]]) +{ + float speed = constants.time * 4.0; + float2 uv = -1.0 + 2.0 * in.position.xy / constants.outputSize; + uv.x *= constants.outputSize.x / constants.outputSize.y; + float3 color = float3(0.0); + + for( int i=0; i < 8; i++ ) + { + float pha = sin(float(i) * 546.13 + 1.0) * 0.5 + 0.5; + float siz = pow(sin(float(i) * 651.74 + 5.0) * 0.5 + 0.5, 4.0); + float pox = sin(float(i) * 321.55 + 4.1) * constants.outputSize.x / constants.outputSize.y; + float rad = 0.1 + 0.5 * siz + sin(pha + siz) / 4.0; + float2 pos = float2(pox + sin(speed / 15. + pha + siz), - 1.0 - rad + (2.0 + 2.0 * rad) * fract(pha + 0.3 * (speed / 7.) * (0.2 + 0.8 * siz))); + float dis = length(uv - pos); + if(dis < rad) + { + float3 col = mix(float3(0.194 * sin(speed / 6.0) + 0.3, 0.2, 0.3 * pha), float3(1.1 * sin(speed / 9.0) + 0.3, 0.2 * pha, 0.4), 0.5 + 0.5 * sin(float(i))); + color += col.zyx * (1.0 - smoothstep(rad * 0.15, rad, dis)); + } + } + color *= sqrt(1.5 - 0.5 * length(uv)); + return float4(color.r, color.g, color.b , 0.5); +} + +namespace snowflake { + +float rand_float(float x) +{ + return snow::rand(float2(x, 1.0)); +} + +float snow(float3 pos, float2 uv, float o, float atime) +{ + float2 d = (pos.xy - uv); + float a = atan(d.y / d.x) + sin(atime*1.0 + o) * 10.0; + + float dist = d.x*d.x + d.y*d.y; + + if(dist < pos.z/400.0) + { + float col = 0.0; + if(sin(a * 8.0) < 0.0) + { + col=1.0; + } + if(dist < pos.z/800.0) + { + col+=1.0; + } + return col * pos.z; + } + + return 0.0; +} + +float col(float2 c, const device Uniforms &constants) +{ + float color = 0.0; + float atime = (constants.time + 1.0) / 4.0; + + for (int i = 1; i < 15; i++) + { + float o = rand_float(float(i) / 3.0) * 15.0; + float z = rand_float(float(i) + 13.0); + float x = 1.8 - (3.6) * (rand_float(floor((constants.time*((z + 1.0) / 2.0) +o) / 2.0)) + sin(constants.time * o /1000.0) / 10.0); + float y = 1.0 - fmod((constants.time * ((z + 1.0)/2.0)) + o, 2.0); + + color += snow(float3(x,y,z), c, o, atime); + } + + return color; +} + +} + +fragment float4 snowflake_fragment(FontFragmentIn in [[ stage_in ]], + const device Uniforms &constants [[ buffer(BufferIndexUniforms) ]]) +{ + float2 uv = in.position.xy / constants.outputSize.xy; + uv = uv * 2.0 - 1.0; + float2 p = uv; + p.x *= constants.outputSize.x / constants.outputSize.y; + //p.y = -p.y; + + float c = snowflake::col(p, constants); + return float4(c,c,c,c); +} diff --git a/gfx/common/metal_common.h b/gfx/common/metal_common.h index 723ecab8da..7b4dacda0d 100644 --- a/gfx/common/metal_common.h +++ b/gfx/common/metal_common.h @@ -28,14 +28,14 @@ extern MTLPixelFormat SelectOptimalPixelFormat(MTLPixelFormat fmt); @interface FrameView : NSObject -@property (readonly) RPixelFormat format; -@property (readonly) RTextureFilter filter; -@property (readwrite) BOOL visible; -@property (readwrite) CGRect frame; -@property (readwrite) CGSize size; -@property (readonly) ViewDrawState drawState; -@property (readonly) struct video_shader* shader; -@property (readwrite) uint64_t frameCount; +@property (nonatomic, readonly) RPixelFormat format; +@property (nonatomic, readonly) RTextureFilter filter; +@property (nonatomic, readwrite) BOOL visible; +@property (nonatomic, readwrite) CGRect frame; +@property (nonatomic, readwrite) CGSize size; +@property (nonatomic, readonly) ViewDrawState drawState; +@property (nonatomic, readonly) struct video_shader* shader; +@property (nonatomic, readwrite) uint64_t frameCount; - (void)setFilteringIndex:(int)index smooth:(bool)smooth; - (BOOL)setShaderFromPath:(NSString *)path; @@ -46,9 +46,9 @@ extern MTLPixelFormat SelectOptimalPixelFormat(MTLPixelFormat fmt); @interface MetalMenu : NSObject -@property (readonly) bool hasFrame; -@property (readwrite) bool enabled; -@property (readwrite) float alpha; +@property (nonatomic, readonly) bool hasFrame; +@property (nonatomic, readwrite) bool enabled; +@property (nonatomic, readwrite) float alpha; - (void)updateFrame:(void const *)source; @@ -60,20 +60,20 @@ extern MTLPixelFormat SelectOptimalPixelFormat(MTLPixelFormat fmt); @interface MetalDriver : NSObject -@property (readonly) video_viewport_t* viewport; -@property (readwrite) bool keepAspect; -@property (readonly) MetalMenu* menu; -@property (readonly) FrameView* frameView; -@property (readonly) MenuDisplay* display; -@property (readonly) Context* context; -@property (readonly) Uniforms* viewportMVP; +@property (nonatomic, readonly) video_viewport_t* viewport; +@property (nonatomic, readwrite) bool keepAspect; +@property (nonatomic, readonly) MetalMenu* menu; +@property (nonatomic, readonly) FrameView* frameView; +@property (nonatomic, readonly) MenuDisplay* display; +@property (nonatomic, readonly) Context* context; +@property (nonatomic, readonly) Uniforms* viewportMVP; +@property (nonatomic, readonly) Uniforms* viewportMVPNormalized; - (instancetype)initWithVideo:(const video_info_t *)video input:(const input_driver_t **)input inputData:(void **)inputData; - (void)setVideo:(const video_info_t *)video; -- (void)setShaderIndex:(NSUInteger)index; - (bool)renderFrame:(const void *)data width:(unsigned)width height:(unsigned)height diff --git a/gfx/common/metal_common.m b/gfx/common/metal_common.m index bde736be08..01fb722b8d 100644 --- a/gfx/common/metal_common.m +++ b/gfx/common/metal_common.m @@ -32,7 +32,7 @@ @interface FrameView() -@property (readwrite) video_viewport_t *viewport; +@property (nonatomic, readwrite) video_viewport_t *viewport; - (instancetype)initWithDescriptor:(ViewDescriptor *)td context:(Context *)context; - (void)drawWithContext:(Context *)ctx; @@ -41,7 +41,7 @@ @end @interface MetalMenu() -@property (readonly) TexturedView *view; +@property (nonatomic, readonly) TexturedView *view; - (instancetype)initWithContext:(Context *)context; @end @@ -71,7 +71,7 @@ // other state Uniforms _uniforms; Uniforms _viewportMVP; - BOOL _begin, _end; + Uniforms _viewportMVPNormalized; } - (instancetype)initWithVideo:(const video_info_t *)video @@ -96,9 +96,6 @@ return nil; } - _begin = NO; - _end = NO; - _video = *video; _viewport = (video_viewport_t *)calloc(1, sizeof(video_viewport_t)); @@ -221,15 +218,15 @@ { MTLRenderPipelineDescriptor *psd = [MTLRenderPipelineDescriptor new]; - psd.label = @"stock_no_blend"; + psd.label = @"stock"; MTLRenderPipelineColorAttachmentDescriptor *ca = psd.colorAttachments[0]; ca.pixelFormat = _layer.pixelFormat; ca.blendingEnabled = NO; - ca.sourceAlphaBlendFactor = MTLBlendFactorSourceAlpha; ca.sourceRGBBlendFactor = MTLBlendFactorSourceAlpha; - ca.destinationAlphaBlendFactor = MTLBlendFactorOneMinusSourceAlpha; ca.destinationRGBBlendFactor = MTLBlendFactorOneMinusSourceAlpha; + ca.sourceAlphaBlendFactor = MTLBlendFactorSourceAlpha; + ca.destinationAlphaBlendFactor = MTLBlendFactorOneMinusSourceAlpha; psd.sampleCount = 1; psd.vertexDescriptor = vd; @@ -243,7 +240,7 @@ RARCH_ERR("[Metal]: error creating pipeline state %s\n", err.localizedDescription.UTF8String); return NO; } - + psd.label = @"stock_blend"; ca.blendingEnabled = YES; _states[VIDEO_SHADER_STOCK_BLEND][1] = [_device newRenderPipelineStateWithDescriptor:psd error:&err]; @@ -252,6 +249,114 @@ RARCH_ERR("[Metal]: error creating pipeline state %s\n", err.localizedDescription.UTF8String); return NO; } + + MTLFunctionConstantValues *vals; + + psd.label = @"snow_simple"; + ca.blendingEnabled = YES; + { + vals = [MTLFunctionConstantValues new]; + float values[3] = { + 1.25f, // baseScale + 0.50f, // density + 0.15f, // speed + }; + [vals setConstantValue:&values[0] type:MTLDataTypeFloat withName:@"snowBaseScale"]; + [vals setConstantValue:&values[1] type:MTLDataTypeFloat withName:@"snowDensity"]; + [vals setConstantValue:&values[2] type:MTLDataTypeFloat withName:@"snowSpeed"]; + } + psd.fragmentFunction = [_library newFunctionWithName:@"snow_fragment" constantValues:vals error:&err]; + _states[VIDEO_SHADER_MENU_3][1] = [_device newRenderPipelineStateWithDescriptor:psd error:&err]; + if (err != nil) + { + RARCH_ERR("[Metal]: error creating pipeline state %s\n", err.localizedDescription.UTF8String); + return NO; + } + + psd.label = @"snow"; + ca.blendingEnabled = YES; + { + vals = [MTLFunctionConstantValues new]; + float values[3] = { + 3.50f, // baseScale + 0.70f, // density + 0.25f, // speed + }; + [vals setConstantValue:&values[0] type:MTLDataTypeFloat withName:@"snowBaseScale"]; + [vals setConstantValue:&values[1] type:MTLDataTypeFloat withName:@"snowDensity"]; + [vals setConstantValue:&values[2] type:MTLDataTypeFloat withName:@"snowSpeed"]; + } + psd.fragmentFunction = [_library newFunctionWithName:@"snow_fragment" constantValues:vals error:&err]; + _states[VIDEO_SHADER_MENU_4][1] = [_device newRenderPipelineStateWithDescriptor:psd error:&err]; + if (err != nil) + { + RARCH_ERR("[Metal]: error creating pipeline state %s\n", err.localizedDescription.UTF8String); + return NO; + } + + psd.label = @"bokeh"; + ca.blendingEnabled = YES; + psd.fragmentFunction = [_library newFunctionWithName:@"bokeh_fragment"]; + _states[VIDEO_SHADER_MENU_5][1] = [_device newRenderPipelineStateWithDescriptor:psd error:&err]; + if (err != nil) + { + RARCH_ERR("[Metal]: error creating pipeline state %s\n", err.localizedDescription.UTF8String); + return NO; + } + + psd.label = @"snowflake"; + ca.blendingEnabled = YES; + psd.fragmentFunction = [_library newFunctionWithName:@"snowflake_fragment"]; + _states[VIDEO_SHADER_MENU_6][1] = [_device newRenderPipelineStateWithDescriptor:psd error:&err]; + if (err != nil) + { + RARCH_ERR("[Metal]: error creating pipeline state %s\n", err.localizedDescription.UTF8String); + return NO; + } + + psd.label = @"ribbon"; + ca.blendingEnabled = NO; + psd.vertexFunction = [_library newFunctionWithName:@"ribbon_vertex"]; + psd.fragmentFunction = [_library newFunctionWithName:@"ribbon_fragment"]; + _states[VIDEO_SHADER_MENU][0] = [_device newRenderPipelineStateWithDescriptor:psd error:&err]; + if (err != nil) + { + RARCH_ERR("[Metal]: error creating pipeline state %s\n", err.localizedDescription.UTF8String); + return NO; + } + + psd.label = @"ribbon_blend"; + ca.blendingEnabled = YES; + ca.sourceRGBBlendFactor = MTLBlendFactorOne; + ca.destinationRGBBlendFactor = MTLBlendFactorOne; + _states[VIDEO_SHADER_MENU][1] = [_device newRenderPipelineStateWithDescriptor:psd error:&err]; + if (err != nil) + { + RARCH_ERR("[Metal]: error creating pipeline state %s\n", err.localizedDescription.UTF8String); + return NO; + } + + psd.label = @"ribbon_simple"; + ca.blendingEnabled = NO; + psd.vertexFunction = [_library newFunctionWithName:@"ribbon_simple_vertex"]; + psd.fragmentFunction = [_library newFunctionWithName:@"ribbon_simple_fragment"]; + _states[VIDEO_SHADER_MENU_2][0] = [_device newRenderPipelineStateWithDescriptor:psd error:&err]; + if (err != nil) + { + RARCH_ERR("[Metal]: error creating pipeline state %s\n", err.localizedDescription.UTF8String); + return NO; + } + + psd.label = @"ribbon_simple_blend"; + ca.blendingEnabled = YES; + ca.sourceRGBBlendFactor = MTLBlendFactorOne; + ca.destinationRGBBlendFactor = MTLBlendFactorOne; + _states[VIDEO_SHADER_MENU_2][1] = [_device newRenderPipelineStateWithDescriptor:psd error:&err]; + if (err != nil) + { + RARCH_ERR("[Metal]: error creating pipeline state %s\n", err.localizedDescription.UTF8String); + return NO; + } } return YES; } @@ -261,18 +366,44 @@ _uniforms.projectionMatrix = matrix_proj_ortho(0, 1, 0, 1); } +- (void)_updateViewport:(CGSize)size +{ + _viewport->full_width = (unsigned int)size.width; + _viewport->full_height = (unsigned int)size.height; + video_driver_set_size(&_viewport->full_width, &_viewport->full_height); + _layer.drawableSize = size; + video_driver_update_viewport(_viewport, NO, _keepAspect); + + _viewportMVP.outputSize = simd_make_float2(_viewport->full_width, _viewport->full_height); + _viewportMVP.projectionMatrix = matrix_proj_ortho(0, _viewport->full_width, _viewport->full_height, 0); + _viewportMVP.projectionMatrix = matrix_proj_ortho(0, _viewport->full_width, 0, _viewport->full_height); + + _viewportMVPNormalized.outputSize = simd_make_float2(_viewport->full_width, _viewport->full_height); + _viewportMVPNormalized.projectionMatrix = matrix_proj_ortho(0, 1, 0, 1); +} + #pragma mark - shaders - (id)getStockShader:(int)index blend:(bool)blend { assert(index > 0 && index < GFX_MAX_SHADERS); - return _states[index][blend ? 1 : 0]; -} - -- (void)setShaderIndex:(NSUInteger)index -{ + switch (index) + { + case VIDEO_SHADER_STOCK_BLEND: + case VIDEO_SHADER_MENU: + case VIDEO_SHADER_MENU_2: + case VIDEO_SHADER_MENU_3: + case VIDEO_SHADER_MENU_4: + case VIDEO_SHADER_MENU_5: + case VIDEO_SHADER_MENU_6: + break; + default: + index = VIDEO_SHADER_STOCK_BLEND; + break; + } + return _states[index][blend ? 1 : 0]; } #pragma mark - video @@ -324,19 +455,12 @@ - (void)_beginFrame { video_driver_update_viewport(_viewport, NO, _keepAspect); - - assert(!_begin && !_end); - _begin = YES; [_context begin]; [self _updateUniforms]; } - (void)_drawViews:(video_frame_info_t *)video_info { - assert(_begin && !_end); - _begin = NO; - _end = YES; - id rce = _context.rce; // draw back buffer @@ -358,28 +482,37 @@ [_frameView drawWithEncoder:rce]; } [rce popDebugGroup]; + + if (_menu.enabled && _menu.hasFrame) + { + [_menu.view drawWithContext:_context]; + [rce setVertexBytes:&_uniforms length:sizeof(_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]; + } #if defined(HAVE_MENU) if (_menu.enabled) { + MTLViewport viewport = { + .originX = 0.0f, + .originY = 0.0f, + .width = _viewport->full_width, + .height = _viewport->full_height, + .znear = 0.0f, + .zfar = 1.0, + }; + [rce setViewport:viewport]; [rce pushDebugGroup:@"menu"]; menu_driver_frame(video_info); - - if (_menu.hasFrame) - { - [_menu.view drawWithContext:_context]; - [rce setVertexBytes:&_uniforms length:sizeof(_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]; } #endif @@ -387,8 +520,6 @@ - (void)_endFrame { - assert(!_begin && _end); - _end = NO; [_context end]; } @@ -402,18 +533,16 @@ return &_viewportMVP; } +- (Uniforms *)viewportMVPNormalized +{ + return &_viewportMVPNormalized; +} + #pragma mark - MTKViewDelegate - (void)mtkView:(MTKView *)view drawableSizeWillChange:(CGSize)size { - _viewport->full_width = (unsigned int)size.width; - _viewport->full_height = (unsigned int)size.height; - video_driver_set_size(&_viewport->full_width, &_viewport->full_height); - _layer.drawableSize = size; - video_driver_update_viewport(_viewport, NO, _keepAspect); - - _viewportMVP.outputSize = simd_make_float2(_viewport->full_width, _viewport->full_height); - _viewportMVP.projectionMatrix = matrix_proj_ortho(0, _viewport->full_width, _viewport->full_height, 0); + [self _updateViewport:size]; } - (void)drawInMTKView:(MTKView *)view @@ -870,7 +999,6 @@ static vertex_t vertex_bytes[] = { id cb = ctx.blitCommandBuffer; MTLRenderPassDescriptor *rpd = [MTLRenderPassDescriptor new]; - // rpd.colorAttachments[0].clearColor = MTLClearColorMake(0, 0, 0, 1.0); rpd.colorAttachments[0].loadAction = MTLLoadActionDontCare; rpd.colorAttachments[0].storeAction = MTLStoreActionStore; diff --git a/gfx/drivers_font/metal_raster_font.m b/gfx/drivers_font/metal_raster_font.m index dcf5e98e3e..848e5970cb 100644 --- a/gfx/drivers_font/metal_raster_font.m +++ b/gfx/drivers_font/metal_raster_font.m @@ -24,6 +24,7 @@ @interface MetalRaster : NSObject { + __weak MetalDriver *_driver; const font_renderer_driver_t *_font_driver; void *_font_data; struct font_atlas *_atlas; @@ -44,36 +45,25 @@ unsigned _vertices; } -@property (weak, readwrite) MetalDriver *metal; @property (readonly) struct font_atlas *atlas; -@property (readwrite) bool needsUpdate; -- (instancetype)initWithDriver:(MetalDriver *)metal fontPath:(const char *)font_path fontSize:(unsigned)font_size; +- (instancetype)initWithDriver:(MetalDriver *)driver fontPath:(const char *)font_path fontSize:(unsigned)font_size; -- (int)getWidthForMessage:(const char *)msg length:(unsigned int)length scale:(float)scale; +- (int)getWidthForMessage:(const char *)msg length:(NSUInteger)length scale:(float)scale; - (const struct font_glyph *)getGlyph:(uint32_t)code; @end @implementation MetalRaster -/* macOS requires constants in a buffer to have a 256 byte alignment. */ -#ifdef TARGET_OS_MAC -static const NSUInteger kConstantAlignment = 256; -#else -static const NSUInteger kConstantAlignment = 4; -#endif - -#define ALIGN_CONSTANTS(size) ((size + kConstantAlignment - 1) & (~(kConstantAlignment - 1))) - -- (instancetype)initWithDriver:(MetalDriver *)metal fontPath:(const char *)font_path fontSize:(unsigned)font_size +- (instancetype)initWithDriver:(MetalDriver *)driver fontPath:(const char *)font_path fontSize:(unsigned)font_size { if (self = [super init]) { - if (metal == nil) + if (driver == nil) return nil; - _metal = metal; - _context = metal.context; + _driver = driver; + _context = driver.context; if (!font_renderer_create_default((const void **)&_font_driver, &_font_data, font_path, font_size)) { @@ -83,7 +73,7 @@ static const NSUInteger kConstantAlignment = 4; _uniforms.projectionMatrix = matrix_proj_ortho(0, 1, 0, 1); _atlas = _font_driver->get_atlas(_font_data); - _stride = ALIGN_CONSTANTS(_atlas->width); + _stride = MTL_ALIGN_BUFFER(_atlas->width); if (_stride == _atlas->width) { _buffer = [_context.device newBufferWithBytes:_atlas->buffer @@ -115,7 +105,6 @@ static const NSUInteger kConstantAlignment = 4; _capacity = 12000; _vert = [_context.device newBufferWithLength:sizeof(SpriteVertex) * _capacity options:MTLResourceStorageModeManaged]; - _needsUpdate = true; if (![self _initializeState]) { return nil; @@ -188,15 +177,14 @@ static const NSUInteger kConstantAlignment = 4; [_buffer didModifyRange:NSMakeRange(offset, len)]; _atlas->dirty = false; - _needsUpdate = true; } } -- (int)getWidthForMessage:(const char *)msg length:(unsigned int)length scale:(float)scale +- (int)getWidthForMessage:(const char *)msg length:(NSUInteger)length scale:(float)scale { int delta_x = 0; - for (unsigned i = 0; i < length; i++) + for (NSUInteger i = 0; i < length; i++) { const struct font_glyph *glyph = _font_driver->get_glyph(_font_data, (uint8_t)msg[i]); if (!glyph) /* Do something smarter here ... */ @@ -262,14 +250,14 @@ static INLINE void write_quad6(SpriteVertex *pv, aligned:(unsigned)aligned { const char *msg_end = msg + length; - int x = roundf(posX * _metal.viewport->width); - int y = roundf((1.0f - posY) * _metal.viewport->height); + int x = (int)roundf(posX * _driver.viewport->full_width); + int y = (int)roundf((1.0f - posY) * _driver.viewport->full_height); int delta_x = 0; int delta_y = 0; float inv_tex_size_x = 1.0f / _texture.width; float inv_tex_size_y = 1.0f / _texture.height; - float inv_win_width = 1.0f / _metal.viewport->width; - float inv_win_height = 1.0f / _metal.viewport->height; + float inv_win_width = 1.0f / _driver.viewport->full_width; + float inv_win_height = 1.0f / _driver.viewport->full_height; switch (aligned) { @@ -335,8 +323,19 @@ static INLINE void write_quad6(SpriteVertex *pv, id rce = _context.rce; [rce pushDebugGroup:@"render fonts"]; + + MTLViewport vp = { + .originX = 0, + .originY = 0, + .width = _driver.viewport->full_width, + .height = _driver.viewport->full_height, + .znear = 0, + .zfar = 1, + }; + [rce setViewport:vp]; + [rce setRenderPipelineState:_state]; - [rce setVertexBytes:&_uniforms length:sizeof(_uniforms) atIndex:BufferIndexUniforms]; + [rce setVertexBytes:&_uniforms length:sizeof(Uniforms) atIndex:BufferIndexUniforms]; [rce setVertexBuffer:_vert offset:start atIndex:BufferIndexPositions]; [rce setFragmentTexture:_texture atIndex:TextureIndexColor]; [rce setFragmentSamplerState:_sampler atIndex:SamplerIndexDraw]; @@ -386,7 +385,7 @@ static INLINE void write_quad6(SpriteVertex *pv, } else { - unsigned msg_len = strlen(msg); + NSUInteger msg_len = strlen(msg); [self _renderLine:msg video:video length:msg_len diff --git a/menu/drivers_display/menu_display_metal.m b/menu/drivers_display/menu_display_metal.m index b80702a72b..beb57553ee 100644 --- a/menu/drivers_display/menu_display_metal.m +++ b/menu/drivers_display/menu_display_metal.m @@ -35,7 +35,7 @@ static void *menu_display_metal_get_default_mvp(video_frame_info_t *video_info) if (!md) return NULL; - return (void *)md.viewportMVP; + return (void *)&md.viewportMVPNormalized->projectionMatrix; } static void menu_display_metal_blend_begin(video_frame_info_t *video_info) diff --git a/pkg/apple/BaseConfig.xcconfig b/pkg/apple/BaseConfig.xcconfig index 16ade1b7e1..1969463c8a 100644 --- a/pkg/apple/BaseConfig.xcconfig +++ b/pkg/apple/BaseConfig.xcconfig @@ -16,7 +16,7 @@ LIBRARY_SEARCH_PATHS[sdk=macosx*] = $(inherited) $(VULKAN_FRAMEWORK_PATH) // OTHER_LDFLAGS = $(inherited) -lMoltenVK -framework MoltenVK -OTHER_CFLAGS = $(inherited) -DHAVE_RUNAHEAD -DHAVE_GRIFFIN -DHAVE_FLAC -DHAVE_DR_FLAC -DHAVE_DR_MP3 -DHAVE_LROUND -DFLAC__HAS_OGG=0 -DHAVE_CHD -DHAVE_STB_VORBIS -DHAVE_MINIUPNPC -DHAVE_BUILTINMINIUPNPC -DHAVE_UPDATE_ASSETS -DHAVE_LANGEXTRA -DHAVE_CHEEVOS -DHAVE_IMAGEVIEWER -DHAVE_IOHIDMANAGER -DHAVE_CORETEXT -DHAVE_RGUI -DHAVE_MENU -DOSX -DHAVE_OPENGL -DHAVE_CC_RESAMPLER -DHAVE_GLSL -DINLINE=inline -D__LIBRETRO__ -DHAVE_COREAUDIO -DHAVE_DYNAMIC -DHAVE_OVERLAY -DHAVE_ZLIB -DHAVE_RPNG -DHAVE_RJPEG -DHAVE_RBMP -DHAVE_RTGA -DHAVE_COCOA -DHAVE_MAIN -DHAVE_NETWORKGAMEPAD -DHAVE_NETWORKING -DRARCH_INTERNAL -DHAVE_THREADS -DHAVE_DYLIB -DHAVE_7ZIP -DHAVE_MATERIALUI -DHAVE_HID -DHAVE_XMB -DHAVE_SEGA -DHAVE_SHADERPIPELINE -DHAVE_MMAP -DHAVE_LIBRETRODB -DHAVE_GETOPT_LONG -DHAVE_METAL -DHAVE_SLANG -DHAVE_GLSLANG -DHAVE_SPIRV_CROSS -DWANT_GLSLANG -DENABLE_HLSL -DGLSLANG_OSINCLUDE_UNIX +OTHER_CFLAGS = $(inherited) -DHAVE_RUNAHEAD -DHAVE_GRIFFIN -DHAVE_FLAC -DHAVE_DR_FLAC -DHAVE_DR_MP3 -DHAVE_LROUND -DFLAC__HAS_OGG=0 -DHAVE_CHD -DHAVE_STB_VORBIS -DHAVE_MINIUPNPC -DHAVE_BUILTINMINIUPNPC -DHAVE_UPDATE_ASSETS -DHAVE_LANGEXTRA -DHAVE_CHEEVOS -DHAVE_IMAGEVIEWER -DHAVE_IOHIDMANAGER -DHAVE_CORETEXT -DHAVE_RGUI -DHAVE_MENU -DOSX -DHAVE_OPENGL -DHAVE_CC_RESAMPLER -DHAVE_GLSL -DINLINE=inline -D__LIBRETRO__ -DHAVE_COREAUDIO -DHAVE_DYNAMIC -DHAVE_OVERLAY -DHAVE_ZLIB -DHAVE_RPNG -DHAVE_RJPEG -DHAVE_RBMP -DHAVE_RTGA -DHAVE_COCOA -DHAVE_MAIN -DHAVE_NETWORKGAMEPAD -DHAVE_NETWORKING -DRARCH_INTERNAL -DHAVE_THREADS -DHAVE_DYLIB -DHAVE_7ZIP -DHAVE_MATERIALUI -DHAVE_HID -DHAVE_XMB -DHAVE_SEGA -DHAVE_SHADERPIPELINE -DHAVE_MMAP -DHAVE_LIBRETRODB -DHAVE_GETOPT_LONG -DHAVE_METAL -DHAVE_SLANG -DHAVE_GLSLANG -DHAVE_SPIRV_CROSS -DWANT_GLSLANG -DENABLE_HLSL -DGLSLANG_OSINCLUDE_UNIX -DMETAL_DEBUG SRCBASE = $(SRCROOT)/../.. DEPS_DIR = $(SRCBASE)/deps diff --git a/pkg/apple/RetroArch_Metal.xcodeproj/project.pbxproj b/pkg/apple/RetroArch_Metal.xcodeproj/project.pbxproj index 8fd0f4dee9..e8bf2b08b3 100644 --- a/pkg/apple/RetroArch_Metal.xcodeproj/project.pbxproj +++ b/pkg/apple/RetroArch_Metal.xcodeproj/project.pbxproj @@ -8,6 +8,7 @@ /* Begin PBXBuildFile section */ 05269A6220ABF20500C29F1E /* MetalKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 05269A6120ABF20500C29F1E /* MetalKit.framework */; }; + 05770B9920E805160013DABC /* menu_pipeline.metal in Sources */ = {isa = PBXBuildFile; fileRef = 05770B9820E805160013DABC /* menu_pipeline.metal */; }; 05A8C7B420DB75A500FF7857 /* Shaders.metal in Sources */ = {isa = PBXBuildFile; fileRef = 05A8C74E20DB72F100FF7857 /* Shaders.metal */; }; 05A8E23820A63CB40084ABDA /* Metal.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 05A8E23720A63CB40084ABDA /* Metal.framework */; }; 05A8E23A20A63CED0084ABDA /* IOSurface.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 05A8E23920A63CED0084ABDA /* IOSurface.framework */; }; @@ -70,6 +71,7 @@ 0566C78E20E49E6800BC768F /* scaler_int.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = scaler_int.h; sourceTree = ""; }; 0566C78F20E49E6800BC768F /* filter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = filter.h; sourceTree = ""; }; 0566C79020E49E6800BC768F /* gl_capabilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = gl_capabilities.h; sourceTree = ""; }; + 05770B9820E805160013DABC /* menu_pipeline.metal */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.metal; path = menu_pipeline.metal; sourceTree = ""; }; 05A8C51B20DB72F000FF7857 /* menu_shader.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = menu_shader.h; sourceTree = ""; }; 05A8C51D20DB72F000FF7857 /* menu_cbs_get_value.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = menu_cbs_get_value.c; sourceTree = ""; }; 05A8C51E20DB72F000FF7857 /* menu_cbs_sublabel.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = menu_cbs_sublabel.c; sourceTree = ""; }; @@ -826,7 +828,6 @@ children = ( 05A8C64120DB72F000FF7857 /* d3d_shaders */, 05A8C60020DB72F000FF7857 /* gl_shaders */, - 05A8C7B320DB756F00FF7857 /* metal_shaders */, 05A8C62220DB72F000FF7857 /* vulkan_shaders */, 05A8C63E20DB72F000FF7857 /* d3d10.c */, 05A8C5F020DB72F000FF7857 /* gl.c */, @@ -876,33 +877,33 @@ 05A8C62220DB72F000FF7857 /* vulkan_shaders */ = { isa = PBXGroup; children = ( - 05A8C62320DB72F000FF7857 /* pipeline_snow_simple.vert */, - 05A8C62420DB72F000FF7857 /* opaque.vert */, - 05A8C62520DB72F000FF7857 /* alpha_blend.frag */, - 05A8C62620DB72F000FF7857 /* pipeline_snow.frag */, - 05A8C62720DB72F000FF7857 /* pipeline_ribbon.vert */, - 05A8C62820DB72F000FF7857 /* opaque.vert.inc */, - 05A8C62920DB72F000FF7857 /* alpha_blend.frag.inc */, - 05A8C62A20DB72F000FF7857 /* pipeline_ribbon.frag.inc */, 05A8C62B20DB72F000FF7857 /* Makefile */, - 05A8C62C20DB72F000FF7857 /* pipeline_bokeh.frag.inc */, - 05A8C62D20DB72F000FF7857 /* font.frag.inc */, - 05A8C62E20DB72F000FF7857 /* pipeline_snow.frag.inc */, - 05A8C62F20DB72F000FF7857 /* pipeline_ribbon_simple.vert */, - 05A8C63020DB72F000FF7857 /* pipeline_snow_simple.frag.inc */, - 05A8C63120DB72F000FF7857 /* pipeline_ribbon_simple.frag.inc */, - 05A8C63220DB72F000FF7857 /* pipeline_ribbon_simple.vert.inc */, - 05A8C63320DB72F000FF7857 /* pipeline_ribbon.frag */, - 05A8C63420DB72F000FF7857 /* pipeline_snow_simple.vert.inc */, + 05A8C62520DB72F000FF7857 /* alpha_blend.frag */, 05A8C63520DB72F000FF7857 /* font.frag */, - 05A8C63620DB72F000FF7857 /* alpha_blend.vert */, - 05A8C63720DB72F000FF7857 /* pipeline_snow_simple.frag */, 05A8C63820DB72F000FF7857 /* opaque.frag */, - 05A8C63920DB72F000FF7857 /* pipeline_ribbon.vert.inc */, - 05A8C63A20DB72F000FF7857 /* alpha_blend.vert.inc */, - 05A8C63B20DB72F000FF7857 /* opaque.frag.inc */, 05A8C63C20DB72F000FF7857 /* pipeline_bokeh.frag */, 05A8C63D20DB72F000FF7857 /* pipeline_ribbon_simple.frag */, + 05A8C63320DB72F000FF7857 /* pipeline_ribbon.frag */, + 05A8C63720DB72F000FF7857 /* pipeline_snow_simple.frag */, + 05A8C62620DB72F000FF7857 /* pipeline_snow.frag */, + 05A8C62920DB72F000FF7857 /* alpha_blend.frag.inc */, + 05A8C63A20DB72F000FF7857 /* alpha_blend.vert.inc */, + 05A8C62D20DB72F000FF7857 /* font.frag.inc */, + 05A8C63B20DB72F000FF7857 /* opaque.frag.inc */, + 05A8C62820DB72F000FF7857 /* opaque.vert.inc */, + 05A8C62C20DB72F000FF7857 /* pipeline_bokeh.frag.inc */, + 05A8C63120DB72F000FF7857 /* pipeline_ribbon_simple.frag.inc */, + 05A8C63220DB72F000FF7857 /* pipeline_ribbon_simple.vert.inc */, + 05A8C62A20DB72F000FF7857 /* pipeline_ribbon.frag.inc */, + 05A8C63920DB72F000FF7857 /* pipeline_ribbon.vert.inc */, + 05A8C63020DB72F000FF7857 /* pipeline_snow_simple.frag.inc */, + 05A8C63420DB72F000FF7857 /* pipeline_snow_simple.vert.inc */, + 05A8C62E20DB72F000FF7857 /* pipeline_snow.frag.inc */, + 05A8C63620DB72F000FF7857 /* alpha_blend.vert */, + 05A8C62420DB72F000FF7857 /* opaque.vert */, + 05A8C62F20DB72F000FF7857 /* pipeline_ribbon_simple.vert */, + 05A8C62720DB72F000FF7857 /* pipeline_ribbon.vert */, + 05A8C62320DB72F000FF7857 /* pipeline_snow_simple.vert */, ); path = vulkan_shaders; sourceTree = ""; @@ -960,6 +961,7 @@ 05A8C74C20DB72F100FF7857 /* RendererCommon.h */, 05A8C75420DB72F100FF7857 /* RendererCommon.m */, 05A8C74E20DB72F100FF7857 /* Shaders.metal */, + 05770B9820E805160013DABC /* menu_pipeline.metal */, 05A8C75120DB72F100FF7857 /* ShaderTypes.h */, 05A8C74920DB72F100FF7857 /* TexturedView.h */, 05A8C75620DB72F100FF7857 /* TexturedView.m */, @@ -1029,13 +1031,6 @@ path = drivers_font_renderer; sourceTree = ""; }; - 05A8C7B320DB756F00FF7857 /* metal_shaders */ = { - isa = PBXGroup; - children = ( - ); - path = metal_shaders; - sourceTree = ""; - }; 05C5D53220E3DD0900654EE4 /* input */ = { isa = PBXGroup; children = ( @@ -1318,6 +1313,7 @@ buildActionMask = 2147483647; files = ( 05D7753720A567A700646447 /* griffin_glslang.cpp in Sources */, + 05770B9920E805160013DABC /* menu_pipeline.metal in Sources */, 05D7753520A567A400646447 /* griffin_cpp.cpp in Sources */, 509F0C9D1AA23AFC00619ECC /* griffin_objc.m in Sources */, 840222FC1A889EE2009AB261 /* griffin.c in Sources */, diff --git a/ui/drivers/cocoa/cocoa_common.m b/ui/drivers/cocoa/cocoa_common.m index 451b8a412e..8ce30fe99a 100644 --- a/ui/drivers/cocoa/cocoa_common.m +++ b/ui/drivers/cocoa/cocoa_common.m @@ -55,6 +55,11 @@ { return YES; } + +- (BOOL)isFlipped +{ + return YES; +} @end #endif diff --git a/ui/drivers/ui_cocoa.m b/ui/drivers/ui_cocoa.m index b3173fe66d..b1d19e8000 100644 --- a/ui/drivers/ui_cocoa.m +++ b/ui/drivers/ui_cocoa.m @@ -297,7 +297,9 @@ static char** waiting_argv; } - (void)setVideoMode:(gfx_ctx_mode_t)mode { - // TODO(sgc): handle full screen + // TODO(sgc): handle full screen? + // cheap hack to ensure MTKView posts triggers a drawable resize event + [self.window setContentSize:NSMakeSize(mode.width-1, mode.height)]; [self.window setContentSize:NSMakeSize(mode.width, mode.height)]; }