SameBoy | Accurate GB/GBC emulator |
| download: https://git.y1.nz/archives/sameboy.tar.gz | |
| README | Files | Log | Refs | LICENSE |
commit 9f53fcc30bcbb069cfd172c1bd54cdd8d03e5258 parent 4cf3b3c9481f83a0cfc08fb0bd2d531b5f51ca44 Author: Lior Halphon <LIJI32@gmail.com> Date: Sat, 13 Jan 2024 18:05:27 +0200 Proper fix to Metal flickering Diffstat:
| M | AppleCommon/GBViewMetal.m | 148 | ++++++++++++++++++++++++++++++++++++------------------------------------------- |
1 file changed, 68 insertions(+), 80 deletions(-)
diff --git a/AppleCommon/GBViewMetal.m b/AppleCommon/GBViewMetal.m @@ -15,14 +15,16 @@ static const vector_float2 rect[] = @implementation GBViewMetal { - id<MTLDevice> device; - id<MTLTexture> texture, previous_texture; - id<MTLBuffer> vertices; - id<MTLRenderPipelineState> pipeline_state; - id<MTLCommandQueue> command_queue; - id<MTLBuffer> frame_blending_mode_buffer; - id<MTLBuffer> output_resolution_buffer; - vector_float2 output_resolution; + id<MTLDevice> _device; + id<MTLTexture> _texture, _previousTexture; + id<MTLBuffer> _vertices; + id<MTLRenderPipelineState> _pipelineState; + id<MTLCommandQueue> _commandQueue; + id<MTLBuffer> _frameBlendingModeBuffer; + id<MTLBuffer> _outputResolutionBuffer; + vector_float2 _outputResolution; + id<MTLCommandBuffer> _commandBuffer; + bool _waitedForFrame; } + (bool)isSupported @@ -36,10 +38,9 @@ static const vector_float2 rect[] = return false; #endif } - - (void) allocateTextures { - if (!device) return; + if (!_device) return; MTLTextureDescriptor *texture_descriptor = [[MTLTextureDescriptor alloc] init]; @@ -48,34 +49,34 @@ static const vector_float2 rect[] = texture_descriptor.width = GB_get_screen_width(self.gb); texture_descriptor.height = GB_get_screen_height(self.gb); - texture = [device newTextureWithDescriptor:texture_descriptor]; - previous_texture = [device newTextureWithDescriptor:texture_descriptor]; + _texture = [_device newTextureWithDescriptor:texture_descriptor]; + _previousTexture = [_device newTextureWithDescriptor:texture_descriptor]; } - (void)createInternalView { - MTKView *view = [[MTKView alloc] initWithFrame:self.frame device:(device = MTLCreateSystemDefaultDevice())]; + MTKView *view = [[MTKView alloc] initWithFrame:self.frame device:(_device = MTLCreateSystemDefaultDevice())]; view.delegate = self; self.internalView = view; view.paused = true; view.enableSetNeedsDisplay = true; view.framebufferOnly = false; - vertices = [device newBufferWithBytes:rect + _vertices = [_device newBufferWithBytes:rect length:sizeof(rect) options:MTLResourceStorageModeShared]; static const GB_frame_blending_mode_t default_blending_mode = GB_FRAME_BLENDING_MODE_DISABLED; - frame_blending_mode_buffer = [device newBufferWithBytes:&default_blending_mode + _frameBlendingModeBuffer = [_device newBufferWithBytes:&default_blending_mode length:sizeof(default_blending_mode) options:MTLResourceStorageModeShared]; - output_resolution_buffer = [device newBufferWithBytes:&output_resolution - length:sizeof(output_resolution) + _outputResolutionBuffer = [_device newBufferWithBytes:&_outputResolution + length:sizeof(_outputResolution) options:MTLResourceStorageModeShared]; - output_resolution = (simd_float2){view.drawableSize.width, view.drawableSize.height}; + _outputResolution = (simd_float2){view.drawableSize.width, view.drawableSize.height}; /* TODO: NSObject+DefaultsObserver can replace the less flexible `addDefaultObserver` in iOS */ #if TARGET_OS_IPHONE [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(loadShader) name:@"GBFilterChanged" object:nil]; @@ -106,7 +107,7 @@ static const vector_float2 rect[] = MTLCompileOptions *options = [[MTLCompileOptions alloc] init]; options.fastMathEnabled = true; - id<MTLLibrary> library = [device newLibraryWithSource:shader_source + id<MTLLibrary> library = [_device newLibraryWithSource:shader_source options:options error:&error]; if (error) { @@ -126,19 +127,19 @@ static const vector_float2 rect[] = pipeline_state_descriptor.colorAttachments[0].pixelFormat = ((MTKView *)self.internalView).colorPixelFormat; error = nil; - pipeline_state = [device newRenderPipelineStateWithDescriptor:pipeline_state_descriptor + _pipelineState = [_device newRenderPipelineStateWithDescriptor:pipeline_state_descriptor error:&error]; if (error) { NSLog(@"Failed to created pipeline state, error %@", error); return; } - command_queue = [device newCommandQueue]; + _commandQueue = [_device newCommandQueue]; } - (void)mtkView:(MTKView *)view drawableSizeWillChange:(CGSize)size { - output_resolution = (vector_float2){size.width, size.height}; + _outputResolution = (vector_float2){size.width, size.height}; dispatch_async(dispatch_get_main_queue(), ^{ [(MTKView *)self.internalView draw]; }); @@ -150,106 +151,93 @@ static const vector_float2 rect[] = if (!(view.window.occlusionState & NSWindowOcclusionStateVisible)) return; #endif if (!self.gb) return; - if (texture.width != GB_get_screen_width(self.gb) || - texture.height != GB_get_screen_height(self.gb)) { + if (_texture.width != GB_get_screen_width(self.gb) || + _texture.height != GB_get_screen_height(self.gb)) { [self allocateTextures]; } MTLRegion region = { {0, 0, 0}, // MTLOrigin - {texture.width, texture.height, 1} // MTLSize + {_texture.width, _texture.height, 1} // MTLSize }; + + /* Don't start rendering if the previous frame hasn't finished yet. Either wait, or skip the frame */ + if (_commandBuffer && _commandBuffer.status != MTLCommandBufferStatusCompleted) { + if (_waitedForFrame) return; + [_commandBuffer waitUntilCompleted]; + _waitedForFrame = true; + } + else { + _waitedForFrame = false; + } GB_frame_blending_mode_t mode = [self frameBlendingMode]; - switch (mode) { - case GB_FRAME_BLENDING_MODE_SIMPLE: - case GB_FRAME_BLENDING_MODE_ACCURATE_EVEN: - [previous_texture replaceRegion:region - mipmapLevel:0 - withBytes:[self previousBuffer] - bytesPerRow:texture.width * 4]; - case GB_FRAME_BLENDING_MODE_DISABLED: - [texture replaceRegion:region - mipmapLevel:0 - withBytes:[self currentBuffer] - bytesPerRow:texture.width * 4]; - break; - case GB_FRAME_BLENDING_MODE_ACCURATE_ODD: - /* - I don't understand GPUs. When rapidly changing `mode`, the shader might sometimes incorrectly - use the old value. I couldn't figure out how to fix it, so I just work around the issue by - avoiding rapid changes in `mode`. - */ - mode = GB_FRAME_BLENDING_MODE_ACCURATE_EVEN; - [previous_texture replaceRegion:region - mipmapLevel:0 - withBytes:[self currentBuffer] - bytesPerRow:texture.width * 4]; - [texture replaceRegion:region - mipmapLevel:0 - withBytes:[self previousBuffer] - bytesPerRow:texture.width * 4]; - break; + [_texture replaceRegion:region + mipmapLevel:0 + withBytes:[self currentBuffer] + bytesPerRow:_texture.width * 4]; + + if (mode) { + [_previousTexture replaceRegion:region + mipmapLevel:0 + withBytes:[self previousBuffer] + bytesPerRow:_texture.width * 4]; } - - MTLRenderPassDescriptor *render_pass_descriptor = view.currentRenderPassDescriptor; - id<MTLCommandBuffer> command_buffer = [command_queue commandBuffer]; + + MTLRenderPassDescriptor *renderPassDescriptor = view.currentRenderPassDescriptor; + _commandBuffer = [_commandQueue commandBuffer]; - if (render_pass_descriptor) { - *(GB_frame_blending_mode_t *)[frame_blending_mode_buffer contents] = mode; - *(vector_float2 *)[output_resolution_buffer contents] = output_resolution; + if (renderPassDescriptor) { + *(GB_frame_blending_mode_t *)[_frameBlendingModeBuffer contents] = mode; + *(vector_float2 *)[_outputResolutionBuffer contents] = _outputResolution; - id<MTLRenderCommandEncoder> render_encoder = - [command_buffer renderCommandEncoderWithDescriptor:render_pass_descriptor]; + id<MTLRenderCommandEncoder> renderEncoder = + [_commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor]; - [render_encoder setViewport:(MTLViewport){0.0, 0.0, - output_resolution.x, - output_resolution.y, + [renderEncoder setViewport:(MTLViewport){0.0, 0.0, + _outputResolution.x, + _outputResolution.y, -1.0, 1.0}]; - [render_encoder setRenderPipelineState:pipeline_state]; + [renderEncoder setRenderPipelineState:_pipelineState]; - [render_encoder setVertexBuffer:vertices + [renderEncoder setVertexBuffer:_vertices offset:0 atIndex:0]; - [render_encoder setFragmentBuffer:frame_blending_mode_buffer + [renderEncoder setFragmentBuffer:_frameBlendingModeBuffer offset:0 atIndex:0]; - [render_encoder setFragmentBuffer:output_resolution_buffer + [renderEncoder setFragmentBuffer:_outputResolutionBuffer offset:0 atIndex:1]; - [render_encoder setFragmentTexture:texture + [renderEncoder setFragmentTexture:_texture atIndex:0]; - [render_encoder setFragmentTexture:previous_texture + [renderEncoder setFragmentTexture:_previousTexture atIndex:1]; - [render_encoder drawPrimitives:MTLPrimitiveTypeTriangleStrip + [renderEncoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4]; - [render_encoder endEncoding]; + [renderEncoder endEncoding]; - [command_buffer presentDrawable:view.currentDrawable]; + [_commandBuffer presentDrawable:view.currentDrawable]; } - [command_buffer commit]; + [_commandBuffer commit]; } - (void)flip { [super flip]; dispatch_async(dispatch_get_main_queue(), ^{ -#if TARGET_OS_IPHONE - [(MTKView *)self.internalView setNeedsDisplay]; -#else - [(MTKView *)self.internalView setNeedsDisplay:true]; -#endif + [(MTKView *)self.internalView draw]; }); }
This webpage is intended to be an accessible preview of this repository. To get a fuller picture, clone it and use the git CLI.