1#import <CoreImage/CoreImage.h>
2#import "GBViewMetal.h"
3#pragma clang diagnostic ignored "-Wpartial-availability"
4
5
6static const vector_float2 rect[] =
7{
8    {-1, -1},
9    { 1, -1},
10    {-1,  1},
11    { 1,  1},
12};
13
14@implementation GBViewMetal
15{
16    id<MTLDevice> device;
17    id<MTLTexture> texture, previous_texture;
18    id<MTLBuffer> vertices;
19    id<MTLRenderPipelineState> pipeline_state;
20    id<MTLCommandQueue> command_queue;
21    id<MTLBuffer> frame_blending_mode_buffer;
22    id<MTLBuffer> output_resolution_buffer;
23    vector_float2 output_resolution;
24}
25
26+ (bool)isSupported
27{
28    if (MTLCopyAllDevices) {
29        return [MTLCopyAllDevices() count];
30    }
31    return false;
32}
33
34- (void) allocateTextures
35{
36    if (!device) return;
37
38    MTLTextureDescriptor *texture_descriptor = [[MTLTextureDescriptor alloc] init];
39
40    texture_descriptor.pixelFormat = MTLPixelFormatRGBA8Unorm;
41
42    texture_descriptor.width = GB_get_screen_width(self.gb);
43    texture_descriptor.height = GB_get_screen_height(self.gb);
44
45    texture = [device newTextureWithDescriptor:texture_descriptor];
46    previous_texture = [device newTextureWithDescriptor:texture_descriptor];
47
48}
49
50- (void)createInternalView
51{
52    MTKView *view = [[MTKView alloc] initWithFrame:self.frame device:(device = MTLCreateSystemDefaultDevice())];
53    view.delegate = self;
54    self.internalView = view;
55    view.paused = true;
56    view.enableSetNeedsDisplay = true;
57    view.framebufferOnly = false;
58
59    vertices = [device newBufferWithBytes:rect
60                                   length:sizeof(rect)
61                                  options:MTLResourceStorageModeShared];
62
63    static const GB_frame_blending_mode_t default_blending_mode = GB_FRAME_BLENDING_MODE_DISABLED;
64    frame_blending_mode_buffer = [device newBufferWithBytes:&default_blending_mode
65                                          length:sizeof(default_blending_mode)
66                                         options:MTLResourceStorageModeShared];
67
68    output_resolution_buffer = [device newBufferWithBytes:&output_resolution
69                                                   length:sizeof(output_resolution)
70                                                  options:MTLResourceStorageModeShared];
71
72    output_resolution = (simd_float2){view.drawableSize.width, view.drawableSize.height};
73    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(loadShader) name:@"GBFilterChanged" object:nil];
74    [self loadShader];
75}
76
77- (void) loadShader
78{
79    NSError *error = nil;
80    NSString *shader_source = [NSString stringWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"MasterShader"
81                                                                                                 ofType:@"metal"
82                                                                                            inDirectory:@"Shaders"]
83                                                        encoding:NSUTF8StringEncoding
84                                                           error:nil];
85
86    NSString *shader_name = [[NSUserDefaults standardUserDefaults] objectForKey:@"GBFilter"];
87    NSString *scaler_source = [NSString stringWithContentsOfFile:[[NSBundle mainBundle] pathForResource:shader_name
88                                                                                                 ofType:@"fsh"
89                                                                                            inDirectory:@"Shaders"]
90                                                        encoding:NSUTF8StringEncoding
91                                                           error:nil];
92
93    shader_source = [shader_source stringByReplacingOccurrencesOfString:@"{filter}"
94                                                             withString:scaler_source];
95
96    MTLCompileOptions *options = [[MTLCompileOptions alloc] init];
97    options.fastMathEnabled = true;
98    id<MTLLibrary> library = [device newLibraryWithSource:shader_source
99                                                   options:options
100                                                     error:&error];
101    if (error) {
102        NSLog(@"Error: %@", error);
103        if (!library) {
104            return;
105        }
106    }
107
108    id<MTLFunction> vertex_function = [library newFunctionWithName:@"vertex_shader"];
109    id<MTLFunction> fragment_function = [library newFunctionWithName:@"fragment_shader"];
110
111    // Set up a descriptor for creating a pipeline state object
112    MTLRenderPipelineDescriptor *pipeline_state_descriptor = [[MTLRenderPipelineDescriptor alloc] init];
113    pipeline_state_descriptor.vertexFunction = vertex_function;
114    pipeline_state_descriptor.fragmentFunction = fragment_function;
115    pipeline_state_descriptor.colorAttachments[0].pixelFormat = ((MTKView *)self.internalView).colorPixelFormat;
116
117    error = nil;
118    pipeline_state = [device newRenderPipelineStateWithDescriptor:pipeline_state_descriptor
119                                                             error:&error];
120    if (error)  {
121        NSLog(@"Failed to created pipeline state, error %@", error);
122        return;
123    }
124
125    command_queue = [device newCommandQueue];
126}
127
128- (void)mtkView:(MTKView *)view drawableSizeWillChange:(CGSize)size
129{
130    output_resolution = (vector_float2){size.width, size.height};
131    dispatch_async(dispatch_get_main_queue(), ^{
132        [(MTKView *)self.internalView draw];
133    });
134}
135
136- (void)drawInMTKView:(MTKView *)view
137{
138    if (!(view.window.occlusionState & NSWindowOcclusionStateVisible)) return;
139    if (!self.gb) return;
140    if (texture.width  != GB_get_screen_width(self.gb) ||
141        texture.height != GB_get_screen_height(self.gb)) {
142        [self allocateTextures];
143    }
144
145    MTLRegion region = {
146        {0, 0, 0},         // MTLOrigin
147        {texture.width, texture.height, 1} // MTLSize
148    };
149
150    [texture replaceRegion:region
151               mipmapLevel:0
152                 withBytes:[self currentBuffer]
153               bytesPerRow:texture.width * 4];
154    if ([self frameBlendingMode]) {
155        [previous_texture replaceRegion:region
156                            mipmapLevel:0
157                              withBytes:[self previousBuffer]
158                            bytesPerRow:texture.width * 4];
159    }
160
161    MTLRenderPassDescriptor *render_pass_descriptor = view.currentRenderPassDescriptor;
162    id<MTLCommandBuffer> command_buffer = [command_queue commandBuffer];
163
164    if (render_pass_descriptor != nil) {
165        *(GB_frame_blending_mode_t *)[frame_blending_mode_buffer contents] = [self frameBlendingMode];
166        *(vector_float2 *)[output_resolution_buffer contents] = output_resolution;
167
168        id<MTLRenderCommandEncoder> render_encoder =
169            [command_buffer renderCommandEncoderWithDescriptor:render_pass_descriptor];
170
171        [render_encoder setViewport:(MTLViewport){0.0, 0.0,
172            output_resolution.x,
173            output_resolution.y,
174            -1.0, 1.0}];
175
176        [render_encoder setRenderPipelineState:pipeline_state];
177
178        [render_encoder setVertexBuffer:vertices
179                                 offset:0
180                                atIndex:0];
181
182        [render_encoder setFragmentBuffer:frame_blending_mode_buffer
183                                   offset:0
184                                  atIndex:0];
185
186        [render_encoder setFragmentBuffer:output_resolution_buffer
187                                   offset:0
188                                  atIndex:1];
189
190        [render_encoder setFragmentTexture:texture
191                                  atIndex:0];
192
193        [render_encoder setFragmentTexture:previous_texture
194                                   atIndex:1];
195
196        [render_encoder drawPrimitives:MTLPrimitiveTypeTriangleStrip
197                          vertexStart:0
198                          vertexCount:4];
199
200        [render_encoder endEncoding];
201
202        [command_buffer presentDrawable:view.currentDrawable];
203    }
204
205
206    [command_buffer commit];
207}
208
209- (void)flip
210{
211    [super flip];
212    dispatch_async(dispatch_get_main_queue(), ^{
213        [(MTKView *)self.internalView setNeedsDisplay:true];
214    });
215}
216
217- (NSImage *)renderToImage
218{
219    CIImage *ciImage = [CIImage imageWithMTLTexture:[[(MTKView *)self.internalView currentDrawable] texture]
220                                            options:@{
221                                                kCIImageColorSpace: (__bridge_transfer id)CGColorSpaceCreateDeviceRGB()
222                                            }];
223    ciImage = [ciImage imageByApplyingTransform:CGAffineTransformTranslate(CGAffineTransformMakeScale(1, -1),
224                                                                           0, ciImage.extent.size.height)];
225    CIContext *context = [CIContext context];
226    CGImageRef cgImage = [context createCGImage:ciImage fromRect:ciImage.extent];
227    NSImage *ret = [[NSImage alloc] initWithCGImage:cgImage size:self.internalView.bounds.size];
228    CGImageRelease(cgImage);
229    return ret;
230}
231
232@end
233