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