git.y1.nz

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.