From 7650c0a29d142c385501bd9c768d311c63025621 Mon Sep 17 00:00:00 2001 From: Oleksiy Yakovenko Date: Sat, 16 Nov 2024 13:27:38 +0100 Subject: [PATCH] cocoaui: use setVertexBuffer and a ring buffer of buffers instead of setVertexBytes and re-creating buffers on every frame --- osx/deadbeef.xcodeproj/project.pbxproj | 12 ++- .../Scope/ScopeVisualizationViewController.m | 21 +++-- .../Visualization/Shared/MetalBufferLoop.h | 39 ++++++++ .../Visualization/Shared/MetalBufferLoop.m | 89 +++++++++++++++++++ .../Visualization/Shared/ShaderRenderer.h | 2 +- .../Visualization/Shared/ShaderRenderer.m | 54 +++++++---- ...ctrumAnalyzerVisualizationViewController.m | 49 ++++++---- 7 files changed, 220 insertions(+), 46 deletions(-) create mode 100644 plugins/cocoaui/Visualization/Shared/MetalBufferLoop.h create mode 100644 plugins/cocoaui/Visualization/Shared/MetalBufferLoop.m diff --git a/osx/deadbeef.xcodeproj/project.pbxproj b/osx/deadbeef.xcodeproj/project.pbxproj index 23632b24f8..263ae30522 100644 --- a/osx/deadbeef.xcodeproj/project.pbxproj +++ b/osx/deadbeef.xcodeproj/project.pbxproj @@ -742,6 +742,8 @@ 2D1E1DCD27B90051004DEF1D /* libavutil.56.dylib in Copy Plugins */ = {isa = PBXBuildFile; fileRef = 2DD376FD2739B854007AD315 /* libavutil.56.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; 2D1E215724F9826C00E2895D /* DdbPlayItemPasteboardSerializer.h in Headers */ = {isa = PBXBuildFile; fileRef = 2D1E215524F9826C00E2895D /* DdbPlayItemPasteboardSerializer.h */; }; 2D1E215824F9826C00E2895D /* DdbPlayItemPasteboardSerializer.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D1E215624F9826C00E2895D /* DdbPlayItemPasteboardSerializer.m */; }; + 2D208EAF2CE8D5FA002E4892 /* MetalBufferLoop.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D208EAE2CE8D5FA002E4892 /* MetalBufferLoop.m */; }; + 2D208EB02CE8D5FA002E4892 /* MetalBufferLoop.h in Headers */ = {isa = PBXBuildFile; fileRef = 2D208EAD2CE8D5FA002E4892 /* MetalBufferLoop.h */; }; 2D2092502A9A82E600CDBB2B /* libjansson.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 2DA84F7B24F58894003507A2 /* libjansson.dylib */; }; 2D20D12423F099F4008ACBE6 /* NSImage+Additions.h in Headers */ = {isa = PBXBuildFile; fileRef = 2D20D12223F099F4008ACBE6 /* NSImage+Additions.h */; }; 2D20D12523F099F4008ACBE6 /* NSImage+Additions.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D20D12323F099F4008ACBE6 /* NSImage+Additions.m */; }; @@ -5448,6 +5450,8 @@ 2D1E1D8527AD9B25004DEF1D /* buffered_file_writer.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = buffered_file_writer.c; sourceTree = ""; }; 2D1E215524F9826C00E2895D /* DdbPlayItemPasteboardSerializer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DdbPlayItemPasteboardSerializer.h; sourceTree = ""; }; 2D1E215624F9826C00E2895D /* DdbPlayItemPasteboardSerializer.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DdbPlayItemPasteboardSerializer.m; sourceTree = ""; }; + 2D208EAD2CE8D5FA002E4892 /* MetalBufferLoop.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MetalBufferLoop.h; sourceTree = ""; }; + 2D208EAE2CE8D5FA002E4892 /* MetalBufferLoop.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MetalBufferLoop.m; sourceTree = ""; }; 2D20924C2A9A077800CDBB2B /* scriptable_tfquery.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = scriptable_tfquery.h; sourceTree = ""; }; 2D20924D2A9A077800CDBB2B /* scriptable_tfquery.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = scriptable_tfquery.c; sourceTree = ""; }; 2D20D12223F099F4008ACBE6 /* NSImage+Additions.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSImage+Additions.h"; sourceTree = ""; }; @@ -8436,6 +8440,10 @@ 0D568539271DC6550026F700 /* Shared */ = { isa = PBXGroup; children = ( + 2D208EAD2CE8D5FA002E4892 /* MetalBufferLoop.h */, + 2D208EAE2CE8D5FA002E4892 /* MetalBufferLoop.m */, + 2D5F86AD2C480776000457B3 /* MetalView.h */, + 2D5F86AE2C480776000457B3 /* MetalView.m */, 2D98B66E272B272300E655AE /* ScopeShaderTypes.h */, 2D7C1FAB2946001B00163538 /* ShaderRenderer.h */, 2D7C1FAC2946001B00163538 /* ShaderRenderer.m */, @@ -8446,8 +8454,6 @@ 2D98B67E272B272300E655AE /* VisualizationShaders.metal */, 0D56853A271DC6670026F700 /* VisualizationViewController.h */, 0D56853B271DC6670026F700 /* VisualizationViewController.m */, - 2D5F86AD2C480776000457B3 /* MetalView.h */, - 2D5F86AE2C480776000457B3 /* MetalView.m */, ); path = Shared; sourceTree = ""; @@ -14838,6 +14844,7 @@ 2DD8865A2B71056E000434FB /* KeyboardShortcutConverter.h in Headers */, 2D72F6FF277BA9D800868136 /* DdbVolumeBar.h in Headers */, 2DD6D0A519AE609F009308A5 /* DdbSeekBar.h in Headers */, + 2D208EB02CE8D5FA002E4892 /* MetalBufferLoop.h in Headers */, 2DB1641A24FAB13400034E11 /* MediaLibraryManager.h in Headers */, 2D30D0B425E2A5DD0023A299 /* DesignModeState.h in Headers */, 2DA2A0EE1BE7FE4700601670 /* u8_uc_map.h in Headers */, @@ -18594,6 +18601,7 @@ 2D6D81F01CCF9E0B00028788 /* DdbTableViewRightClickActivate.m in Sources */, 2D29ADFA2B72473B001648B0 /* keyboard_shortcut_serializer.c in Sources */, 2DC6583F274C361C00583E14 /* HolderWidget.m in Sources */, + 2D208EAF2CE8D5FA002E4892 /* MetalBufferLoop.m in Sources */, 2D71C26B1DC88E5C00247CEF /* ScriptableTableDataSource.m in Sources */, 2DD3776127414BF6007AD315 /* ScopePreferencesViewController.m in Sources */, 2D01D7EE1AB222BF00BCD3C4 /* plugins.c in Sources */, diff --git a/plugins/cocoaui/Visualization/Scope/ScopeVisualizationViewController.m b/plugins/cocoaui/Visualization/Scope/ScopeVisualizationViewController.m index c638c328cf..6ec2fbc0fa 100644 --- a/plugins/cocoaui/Visualization/Scope/ScopeVisualizationViewController.m +++ b/plugins/cocoaui/Visualization/Scope/ScopeVisualizationViewController.m @@ -9,6 +9,7 @@ #include #import #import "MetalView.h" +#import "MetalBufferLoop.h" #import "ScopePreferencesViewController.h" #import "ScopePreferencesWindowController.h" #import "ScopeShaderTypes.h" @@ -34,6 +35,8 @@ @interface ScopeVisualizationViewController() )device encoder:(id)encoder viewParams:(ShaderRendererParams)viewParams { +- (BOOL)applyFragParamsWithViewport:(vector_uint2)viewport device:(id)device commandBuffer:(id)commandBuffer encoder:(id)encoder viewParams:(ShaderRendererParams)viewParams { float scale = (float)(viewParams.backingScaleFactor / [self scaleFactorForBackingScaleFactor:viewParams.backingScaleFactor]); struct ScopeFragParams params; @@ -413,17 +417,20 @@ - (BOOL)applyFragParamsWithViewport:(vector_uint2)viewport device:(id [encoder setFragmentBytes:¶ms length:sizeof (params) atIndex:0]; if (_draw_data.points == NULL) { - char bytes[12] = {0}; - [encoder setFragmentBytes:bytes length:12 atIndex:1]; + id buffer = [self.bufferLoop nextBufferForSize:12]; + [encoder setFragmentBuffer:buffer offset:0 atIndex:1]; } else { - // Metal documentation states that MTLBuffer should be used for buffers larger than 4K in size. - // Alternative is to use setFragmentBytes, which also works, but could have compatibility issues on older hardware. - id buffer = [device newBufferWithBytes:_draw_data.points length:_draw_data.point_count * sizeof (ddb_scope_point_t) * params.channels options:0]; - + NSUInteger size = _draw_data.point_count * sizeof (ddb_scope_point_t) * params.channels; + id buffer = [self.bufferLoop nextBufferForSize:size]; + memcpy (buffer.contents, _draw_data.points, size); [encoder setFragmentBuffer:buffer offset:0 atIndex:1]; } + [commandBuffer addCompletedHandler:^(id _Nonnull completedCommandBuffer) { + [self.bufferLoop signalCompletion]; + }]; + return YES; } diff --git a/plugins/cocoaui/Visualization/Shared/MetalBufferLoop.h b/plugins/cocoaui/Visualization/Shared/MetalBufferLoop.h new file mode 100644 index 0000000000..51e298ae36 --- /dev/null +++ b/plugins/cocoaui/Visualization/Shared/MetalBufferLoop.h @@ -0,0 +1,39 @@ +/* + DeaDBeeF -- the music player + Copyright (C) 2009-2024 Oleksiy Yakovenko and other contributors + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source distribution. +*/ + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface MetalBufferLoop : NSObject + ++ (instancetype)new NS_UNAVAILABLE; +- (instancetype)init NS_UNAVAILABLE; +- (instancetype)initWithMetalDevice:(id)device bufferCount:(NSUInteger)bufferCount NS_DESIGNATED_INITIALIZER; +- (id)nextBufferForSize:(NSUInteger)size; +- (void)signalCompletion; + +@end + +NS_ASSUME_NONNULL_END diff --git a/plugins/cocoaui/Visualization/Shared/MetalBufferLoop.m b/plugins/cocoaui/Visualization/Shared/MetalBufferLoop.m new file mode 100644 index 0000000000..969f581f6b --- /dev/null +++ b/plugins/cocoaui/Visualization/Shared/MetalBufferLoop.m @@ -0,0 +1,89 @@ +/* + DeaDBeeF -- the music player + Copyright (C) 2009-2024 Oleksiy Yakovenko and other contributors + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source distribution. +*/ + +#import "MetalBufferLoop.h" + +@interface MetalBufferLoop() + +// Parameters +@property (nonatomic) id device; +@property (nonatomic) NSUInteger bufferCount; +@property (nonatomic) dispatch_semaphore_t semaphore; + +// State +@property (nonatomic) NSArray> *buffers; +@property (nonatomic) NSUInteger currentBufferSize; +@property (nonatomic) NSUInteger currentBuffer; + +@end + + +@implementation MetalBufferLoop + +- (instancetype)initWithMetalDevice:(id)device bufferCount:(NSUInteger)bufferCount { + self = [super init]; + if (self == nil) { + return nil; + } + + _device = device; + _bufferCount = bufferCount; + _semaphore = dispatch_semaphore_create(bufferCount); + + return self; +} + +- (void)ensureBufferSize:(NSUInteger)size { + if (size == self.currentBufferSize) { + return; + } + + self.currentBufferSize = size; + NSMutableArray> *array = [[NSMutableArray alloc] initWithCapacity:self.bufferCount]; + for (NSUInteger i = 0; i < self.bufferCount; i++) { + [array addObject:[self.device newBufferWithLength:size options:MTLResourceStorageModeShared]]; + } + + self.buffers = array.copy; +} + +- (id)nextBufferForSize:(NSUInteger)size { + dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER); + + [self ensureBufferSize:size]; + + id buffer = self.buffers[self.currentBuffer]; + self.currentBuffer += 1; + if (self.currentBuffer >= self.bufferCount) { + self.currentBuffer = 0; + } + + return buffer; +} + +- (void)signalCompletion { + dispatch_semaphore_signal(self.semaphore); +} + + +@end diff --git a/plugins/cocoaui/Visualization/Shared/ShaderRenderer.h b/plugins/cocoaui/Visualization/Shared/ShaderRenderer.h index d43c2e7bd9..f0696e07e5 100644 --- a/plugins/cocoaui/Visualization/Shared/ShaderRenderer.h +++ b/plugins/cocoaui/Visualization/Shared/ShaderRenderer.h @@ -32,7 +32,7 @@ typedef struct { } ShaderRendererParams; @protocol ShaderRendererDelegate -- (BOOL)applyFragParamsWithViewport:(vector_uint2)viewport device:(nonnull id )device encoder:(nonnull id )encoder viewParams:(ShaderRendererParams)params; +- (BOOL)applyFragParamsWithViewport:(vector_uint2)viewport device:(nonnull id )device commandBuffer:(nonnull id)commandBuffer encoder:(nonnull id )encoder viewParams:(ShaderRendererParams)params; @end @interface ShaderRenderer : NSObject diff --git a/plugins/cocoaui/Visualization/Shared/ShaderRenderer.m b/plugins/cocoaui/Visualization/Shared/ShaderRenderer.m index 3333f7ec09..ef0d4b7b16 100644 --- a/plugins/cocoaui/Visualization/Shared/ShaderRenderer.m +++ b/plugins/cocoaui/Visualization/Shared/ShaderRenderer.m @@ -23,24 +23,32 @@ #import #import +#import "MetalBufferLoop.h" #import "ShaderRenderer.h" #import "ShaderRendererTypes.h" -@implementation ShaderRenderer { - // renderer global ivars - id _device; - id _commandQueue; - id _pipelineState; +@interface ShaderRenderer() { + // renderer global ivars + id _device; + id _commandQueue; + id _pipelineState; - // Render pass descriptor which creates a render command encoder to draw to the drawable - // textures - MTLRenderPassDescriptor *_drawableRenderDescriptor; + // Render pass descriptor which creates a render command encoder to draw to the drawable + // textures + MTLRenderPassDescriptor *_drawableRenderDescriptor; - CGSize _viewportSize; + CGSize _viewportSize; - NSUInteger _frameNum; + NSUInteger _frameNum; } +@property (nonatomic) MetalBufferLoop *bufferLoop; +@property (nonatomic) MetalBufferLoop *vpBufferLoop; + +@end + +@implementation ShaderRenderer + - (nonnull instancetype)initWithMetalDevice:(nonnull id)device drawablePixelFormat:(MTLPixelFormat)drawablePixelFormat fragmentShaderName:(NSString *)fragmentShaderName @@ -52,6 +60,9 @@ - (nonnull instancetype)initWithMetalDevice:(nonnull id)device _device = device; + _bufferLoop = [[MetalBufferLoop alloc] initWithMetalDevice:device bufferCount:3]; + _vpBufferLoop = [[MetalBufferLoop alloc] initWithMetalDevice:device bufferCount:3]; + _commandQueue = [_device newCommandQueue]; _drawableRenderDescriptor = [MTLRenderPassDescriptor new]; @@ -157,15 +168,20 @@ - (void)renderToMetalLayer:(nonnull CAMetalLayer*)metalLayer viewParams:(ShaderR [renderEncoder setRenderPipelineState:_pipelineState]; vector_uint2 vp = { (uint)_viewportSize.width, (uint)_viewportSize.height }; - if ([self.delegate applyFragParamsWithViewport:vp device:_device encoder:renderEncoder viewParams:viewParams]) { - // Pass in the parameter data. - [renderEncoder setVertexBytes:quadVertices - length:sizeof(quadVertices) - atIndex:ShaderRendererVertexInputIndexVertices]; - - [renderEncoder setVertexBytes:&vp - length:sizeof(vp) - atIndex:ShaderRendererVertexInputIndexViewportSize]; + if ([self.delegate applyFragParamsWithViewport:vp device:_device commandBuffer:commandBuffer encoder:renderEncoder viewParams:viewParams]) { + id buffer = [self.bufferLoop nextBufferForSize:sizeof(quadVertices)]; + memcpy (buffer.contents, quadVertices, sizeof(quadVertices)); + [renderEncoder setVertexBuffer:buffer offset:0 atIndex:ShaderRendererVertexInputIndexVertices]; + + id vpBuffer = [self.vpBufferLoop nextBufferForSize:sizeof(vp)]; + memcpy (vpBuffer.contents, &vp, sizeof(vp)); + + [renderEncoder setVertexBuffer:vpBuffer offset:0 atIndex:ShaderRendererVertexInputIndexViewportSize]; + + [commandBuffer addCompletedHandler:^(id _Nonnull completedCommandBuffer) { + [self.bufferLoop signalCompletion]; + [self.vpBufferLoop signalCompletion]; + }]; // Draw the triangle. [renderEncoder drawPrimitives:MTLPrimitiveTypeTriangleStrip diff --git a/plugins/cocoaui/Visualization/SpectrumAnalyzer/SpectrumAnalyzerVisualizationViewController.m b/plugins/cocoaui/Visualization/SpectrumAnalyzer/SpectrumAnalyzerVisualizationViewController.m index ce526fab7d..090d9abfa3 100644 --- a/plugins/cocoaui/Visualization/SpectrumAnalyzer/SpectrumAnalyzerVisualizationViewController.m +++ b/plugins/cocoaui/Visualization/SpectrumAnalyzer/SpectrumAnalyzerVisualizationViewController.m @@ -2,6 +2,7 @@ #include #import #import "MetalView.h" +#import "MetalBufferLoop.h" #import "ShaderRenderer.h" #import "SpectrumAnalyzerPreferencesWindowController.h" #import "SpectrumAnalyzerPreferencesViewController.h" @@ -36,6 +37,8 @@ @interface SpectrumAnalyzerVisualizationViewController() )device encoder:(id)encoder viewParams:(ShaderRendererParams)viewParams { +- (BOOL)applyFragParamsWithViewport:(vector_uint2)viewport device:(id)device commandBuffer:(id)commandBuffer encoder:(id)encoder viewParams:(ShaderRendererParams)viewParams { struct SpectrumFragParams params; @@ -459,32 +465,41 @@ - (BOOL)applyFragParamsWithViewport:(vector_uint2)viewport device:(id // bar data if (_draw_data.bars == NULL) { - char bytes[12] = {0}; - [encoder setFragmentBytes:bytes length:12 atIndex:1]; - - // This buffer is unused in this scenario, but necessary to shut up API validator - [encoder setFragmentBytes:bytes length:4 atIndex:2]; + // unused / empty + NSUInteger size = 12; + id buffer = [self.bufferLoop nextBufferForSize:size]; + [encoder setFragmentBuffer:buffer offset:0 atIndex:1]; + id lookupBuffer = [self.lookupBufferLoop nextBufferForSize:4]; + [encoder setFragmentBuffer:lookupBuffer offset:0 atIndex:2]; } else if (_draw_data.mode == DDB_ANALYZER_MODE_FREQUENCIES) { - // In this scenario, the buffer is too large, need to use MTLBuffer. - id buffer = [device newBufferWithBytes:_draw_data.bars length:_draw_data.bar_count * sizeof (struct SpectrumFragBar) options:0]; - + NSUInteger size = _draw_data.bar_count * sizeof (struct SpectrumFragBar); + id buffer = [self.bufferLoop nextBufferForSize:size]; + memcpy (buffer.contents, _draw_data.bars, size); [encoder setFragmentBuffer:buffer offset:0 atIndex:1]; + NSUInteger lookupSize = _draw_data.bar_index_for_x_coordinate_table_size * sizeof (int); + id lookupBuffer = [self.lookupBufferLoop nextBufferForSize:lookupSize]; if (_draw_data.bar_index_for_x_coordinate_table != NULL) { - id lookupBuffer = [device newBufferWithBytes:_draw_data.bar_index_for_x_coordinate_table length:_draw_data.bar_index_for_x_coordinate_table_size * sizeof (int) options:0]; - [encoder setFragmentBuffer:lookupBuffer offset:0 atIndex:2]; + memcpy (lookupBuffer.contents, _draw_data.bar_index_for_x_coordinate_table, lookupSize); } + [encoder setFragmentBuffer:lookupBuffer offset:0 atIndex:2]; } else { - // The buffer is not bigger than ~2.5KB (211 bars * 12 bytes), - // therefore it should be safe to use setFragmentBytes. - [encoder setFragmentBytes:_draw_data.bars length:_draw_data.bar_count * sizeof (struct SpectrumFragBar) atIndex:1]; - - // This buffer is unused in this scenario, but necessary to shut up API validator - [encoder setFragmentBytes:_draw_data.bars length:4 atIndex:2]; + NSUInteger size = _draw_data.bar_count * sizeof (struct SpectrumFragBar); + id buffer = [self.bufferLoop nextBufferForSize:size]; + memcpy (buffer.contents, _draw_data.bars, size); + [encoder setFragmentBuffer:buffer offset:0 atIndex:1]; + // unused / empty + id lookupBuffer = [self.lookupBufferLoop nextBufferForSize:4]; + [encoder setFragmentBuffer:lookupBuffer offset:0 atIndex:2]; } + [commandBuffer addCompletedHandler:^(id _Nonnull completedCommandBuffer) { + [self.bufferLoop signalCompletion]; + [self.lookupBufferLoop signalCompletion]; + }]; + return YES; } @end