SameBoy | Accurate GB/GBC emulator |
| download: https://git.y1.nz/archives/sameboy.tar.gz | |
| README | Files | Log | Refs | LICENSE |
AppleCommon/GBViewMetal.m
1 #import <CoreImage/CoreImage.h>
2 #import "GBViewMetal.h"
3 #pragma clang diagnostic ignored "-Wpartial-availability"
4 #if !TARGET_OS_IPHONE
5 #import "../Cocoa/NSObject+DefaultsObserver.h"
6 #endif
7
8 static const vector_float2 rect[] =
9 {
10 {-1, -1},
11 { 1, -1},
12 {-1, 1},
13 { 1, 1},
14 };
15
16 @implementation GBViewMetal
17 {
18 id<MTLDevice> _device;
19 id<MTLTexture> _texture, _previousTexture;
20 id<MTLBuffer> _vertices;
21 id<MTLRenderPipelineState> _pipelineState;
22 id<MTLCommandQueue> _commandQueue;
23 id<MTLBuffer> _frameBlendingModeBuffer;
24 id<MTLBuffer> _outputResolutionBuffer;
25 vector_float2 _outputResolution;
26 id<MTLCommandBuffer> _commandBuffer;
27 bool _waitedForFrame;
28 _Atomic unsigned _pendingFrames;
29 }
30
31 + (bool)isSupported
32 {
33 #if TARGET_OS_IPHONE
34 return true;
35 #else
36 if (MTLCopyAllDevices) {
37 return [MTLCopyAllDevices() count];
38 }
39 return false;
40 #endif
41 }
42 - (void) allocateTextures
43 {
44 if (!_device) return;
45
46 MTLTextureDescriptor *texture_descriptor = [[MTLTextureDescriptor alloc] init];
47
48 texture_descriptor.pixelFormat = MTLPixelFormatRGBA8Unorm;
49
50 texture_descriptor.width = GB_get_screen_width(self.gb);
51 texture_descriptor.height = GB_get_screen_height(self.gb);
52
53 _texture = [_device newTextureWithDescriptor:texture_descriptor];
54 _previousTexture = [_device newTextureWithDescriptor:texture_descriptor];
55
56 }
57
58 - (void)createInternalView
59 {
60 MTKView *view = [[MTKView alloc] initWithFrame:self.frame device:(_device = MTLCreateSystemDefaultDevice())];
61 view.delegate = self;
62 self.internalView = view;
63 view.paused = true;
64 view.enableSetNeedsDisplay = true;
65 view.framebufferOnly = false;
66
67 _vertices = [_device newBufferWithBytes:rect
68 length:sizeof(rect)
69 options:MTLResourceStorageModeShared];
70
71 static const GB_frame_blending_mode_t default_blending_mode = GB_FRAME_BLENDING_MODE_DISABLED;
72 _frameBlendingModeBuffer = [_device newBufferWithBytes:&default_blending_mode
73 length:sizeof(default_blending_mode)
74 options:MTLResourceStorageModeShared];
75
76 _outputResolutionBuffer = [_device newBufferWithBytes:&_outputResolution
77 length:sizeof(_outputResolution)
78 options:MTLResourceStorageModeShared];
79
80 _outputResolution = (simd_float2){view.drawableSize.width, view.drawableSize.height};
81 /* TODO: NSObject+DefaultsObserver can replace the less flexible `addDefaultObserver` in iOS */
82 #if TARGET_OS_IPHONE
83 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(loadShader) name:@"GBFilterChanged" object:nil];
84 [self loadShader];
85 #else
86 [self observeStandardDefaultsKey:@"GBFilter" selector:@selector(loadShader)];
87 #endif
88 }
89
90 - (void)loadShader
91 {
92 NSError *error = nil;
93 NSString *shader_source = [NSString stringWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"MasterShader"
94 ofType:@"metal"
95 inDirectory:@"Shaders"]
96 encoding:NSUTF8StringEncoding
97 error:nil];
98
99 NSString *shader_name = [[NSUserDefaults standardUserDefaults] objectForKey:@"GBFilter"];
100 NSString *scaler_source = [NSString stringWithContentsOfFile:[[NSBundle mainBundle] pathForResource:shader_name
101 ofType:@"fsh"
102 inDirectory:@"Shaders"]
103 encoding:NSUTF8StringEncoding
104 error:nil];
105
106 shader_source = [shader_source stringByReplacingOccurrencesOfString:@"{filter}"
107 withString:scaler_source];
108
109 MTLCompileOptions *options = [[MTLCompileOptions alloc] init];
110 options.fastMathEnabled = true;
111 id<MTLLibrary> library = [_device newLibraryWithSource:shader_source
112 options:options
113 error:&error];
114 if (error) {
115 NSLog(@"Error: %@", error);
116 if (!library) {
117 return;
118 }
119 }
120
121 id<MTLFunction> vertex_function = [library newFunctionWithName:@"vertex_shader"];
122 id<MTLFunction> fragment_function = [library newFunctionWithName:@"fragment_shader"];
123
124 // Set up a descriptor for creating a pipeline state object
125 MTLRenderPipelineDescriptor *pipeline_state_descriptor = [[MTLRenderPipelineDescriptor alloc] init];
126 pipeline_state_descriptor.vertexFunction = vertex_function;
127 pipeline_state_descriptor.fragmentFunction = fragment_function;
128 pipeline_state_descriptor.colorAttachments[0].pixelFormat = ((MTKView *)self.internalView).colorPixelFormat;
129
130 error = nil;
131 _pipelineState = [_device newRenderPipelineStateWithDescriptor:pipeline_state_descriptor
132 error:&error];
133 if (error) {
134 NSLog(@"Failed to created pipeline state, error %@", error);
135 return;
136 }
137
138 _commandQueue = [_device newCommandQueue];
139 }
140
141 - (void)mtkView:(MTKView *)view drawableSizeWillChange:(CGSize)size
142 {
143 _outputResolution = (vector_float2){size.width, size.height};
144 dispatch_async(dispatch_get_main_queue(), ^{
145 [(MTKView *)self.internalView draw];
146 });
147 }
148
149 - (void)drawInMTKView:(MTKView *)view
150 {
151 #if !TARGET_OS_IPHONE
152 if (!(view.window.occlusionState & NSWindowOcclusionStateVisible)) return;
153 #endif
154 if (!self.gb) return;
155 if (_texture.width != GB_get_screen_width(self.gb) ||
156 _texture.height != GB_get_screen_height(self.gb)) {
157 [self allocateTextures];
158 }
159
160 MTLRegion region = {
161 {0, 0, 0}, // MTLOrigin
162 {_texture.width, _texture.height, 1} // MTLSize
163 };
164
165 /* Don't start rendering if the previous frame hasn't finished yet. Either wait, or skip the frame */
166 if (_commandBuffer && _commandBuffer.status != MTLCommandBufferStatusCompleted) {
167 if (_waitedForFrame) return;
168 [_commandBuffer waitUntilCompleted];
169 _waitedForFrame = true;
170 }
171 else {
172 _waitedForFrame = false;
173 }
174
175 GB_frame_blending_mode_t mode = [self frameBlendingMode];
176
177 [_texture replaceRegion:region
178 mipmapLevel:0
179 withBytes:[self currentBuffer]
180 bytesPerRow:_texture.width * 4];
181
182 if (mode) {
183 [_previousTexture replaceRegion:region
184 mipmapLevel:0
185 withBytes:[self previousBuffer]
186 bytesPerRow:_texture.width * 4];
187 }
188
189 MTLRenderPassDescriptor *renderPassDescriptor = view.currentRenderPassDescriptor;
190 _commandBuffer = [_commandQueue commandBuffer];
191
192 if (renderPassDescriptor) {
193 *(GB_frame_blending_mode_t *)[_frameBlendingModeBuffer contents] = mode;
194 *(vector_float2 *)[_outputResolutionBuffer contents] = _outputResolution;
195
196 id<MTLRenderCommandEncoder> renderEncoder =
197 [_commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor];
198
199 [renderEncoder setViewport:(MTLViewport){0.0, 0.0,
200 _outputResolution.x,
201 _outputResolution.y,
202 -1.0, 1.0}];
203
204 [renderEncoder setRenderPipelineState:_pipelineState];
205
206 [renderEncoder setVertexBuffer:_vertices
207 offset:0
208 atIndex:0];
209
210 [renderEncoder setFragmentBuffer:_frameBlendingModeBuffer
211 offset:0
212 atIndex:0];
213
214 [renderEncoder setFragmentBuffer:_outputResolutionBuffer
215 offset:0
216 atIndex:1];
217
218 [renderEncoder setFragmentTexture:_texture
219 atIndex:0];
220
221 [renderEncoder setFragmentTexture:_previousTexture
222 atIndex:1];
223
224 [renderEncoder drawPrimitives:MTLPrimitiveTypeTriangleStrip
225 vertexStart:0
226 vertexCount:4];
227
228 [renderEncoder endEncoding];
229
230 [_commandBuffer presentDrawable:view.currentDrawable];
231 }
232
233
234 [_commandBuffer commit];
235 }
236
237 - (void)flip
238 {
239 [super flip];
240 if (_pendingFrames == 2) return;
241 _pendingFrames++;
242 dispatch_async(dispatch_get_main_queue(), ^{
243 [(MTKView *)self.internalView draw];
244 _pendingFrames--;
245 });
246 }
247
248 #if !TARGET_OS_IPHONE
249 - (NSImage *)renderToImage
250 {
251 CIImage *ciImage = [CIImage imageWithMTLTexture:[[(MTKView *)self.internalView currentDrawable] texture]
252 options:@{
253 kCIImageColorSpace: (__bridge_transfer id)CGColorSpaceCreateDeviceRGB(),
254 kCIImageProperties: [NSNull null]
255 }];
256 ciImage = [ciImage imageByApplyingTransform:CGAffineTransformTranslate(CGAffineTransformMakeScale(1, -1),
257 0, ciImage.extent.size.height)];
258 CIContext *context = [CIContext context];
259 CGImageRef cgImage = [context createCGImage:ciImage fromRect:ciImage.extent];
260 NSImage *ret = [[NSImage alloc] initWithCGImage:cgImage size:self.internalView.bounds.size];
261 CGImageRelease(cgImage);
262 return ret;
263 }
264 #endif
265
266 @end
This webpage is intended to be an accessible preview of this repository. To get a fuller picture, clone it and use the git CLI.