mirror of
https://github.com/bsnes-emu/bsnes.git
synced 2025-09-03 14:12:49 +02:00
Proper fix to Metal flickering
This commit is contained in:
@@ -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];
|
||||
|
||||
if (render_pass_descriptor) {
|
||||
*(GB_frame_blending_mode_t *)[frame_blending_mode_buffer contents] = mode;
|
||||
*(vector_float2 *)[output_resolution_buffer contents] = output_resolution;
|
||||
|
||||
id<MTLRenderCommandEncoder> render_encoder =
|
||||
[command_buffer renderCommandEncoderWithDescriptor:render_pass_descriptor];
|
||||
|
||||
[render_encoder setViewport:(MTLViewport){0.0, 0.0,
|
||||
output_resolution.x,
|
||||
output_resolution.y,
|
||||
MTLRenderPassDescriptor *renderPassDescriptor = view.currentRenderPassDescriptor;
|
||||
_commandBuffer = [_commandQueue commandBuffer];
|
||||
|
||||
if (renderPassDescriptor) {
|
||||
*(GB_frame_blending_mode_t *)[_frameBlendingModeBuffer contents] = mode;
|
||||
*(vector_float2 *)[_outputResolutionBuffer contents] = _outputResolution;
|
||||
|
||||
id<MTLRenderCommandEncoder> renderEncoder =
|
||||
[_commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor];
|
||||
|
||||
[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];
|
||||
});
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user