From ec3e848d7998038ce22c41acdbf81032bf47991f Mon Sep 17 00:00:00 2001 From: riperiperi Date: Thu, 26 Aug 2021 23:31:29 +0100 Subject: [PATCH] Add a Multithreading layer for the GAL, multi-thread shader compilation at runtime (#2501) * Initial Implementation About as fast as nvidia GL multithreading, can be improved with faster command queuing. * Struct based command list Speeds up a bit. Still a lot of time lost to resource copy. * Do shader init while the render thread is active. * Introduce circular span pool V1 Ideally should be able to use structs instead of references for storing these spans on commands. Will try that next. * Refactor SpanRef some more Use a struct to represent SpanRef, rather than a reference. * Flush buffers on background thread * Use a span for UpdateRenderScale. Much faster than copying the array. * Calculate command size using reflection * WIP parallel shaders * Some minor optimisation * Only 2 max refs per command now. The command with 3 refs is gone. :relieved: * Don't cast on the GPU side * Remove redundant casts, force sync on window present * Fix Shader Cache * Fix host shader save. * Fixup to work with new renderer stuff * Make command Run static, use array of delegates as lookup Profile says this takes less time than the previous way. * Bring up to date * Add settings toggle. Fix Muiltithreading Off mode. * Fix warning. * Release tracking lock for flushes * Fix Conditional Render fast path with threaded gal * Make handle iteration safe when releasing the lock This is mostly temporary. * Attempt to set backend threading on driver Only really works on nvidia before launching a game. * Fix race condition with BufferModifiedRangeList, exceptions in tracking actions * Update buffer set commands * Some cleanup * Only use stutter workaround when using opengl renderer non-threaded * Add host-conditional reservation of counter events There has always been the possibility that conditional rendering could use a query object just as it is disposed by the counter queue. This change makes it so that when the host decides to use host conditional rendering, the query object is reserved so that it cannot be deleted. Counter events can optionally start reserved, as the threaded implementation can reserve them before the backend creates them, and there would otherwise be a short amount of time where the counter queue could dispose the event before a call to reserve it could be made. * Address Feedback * Make counter flush tracked again. Hopefully does not cause any issues this time. * Wait for FlushTo on the main queue thread. Currently assumes only one thread will want to FlushTo (in this case, the GPU thread) * Add SDL2 headless integration * Add HLE macro commands. Co-authored-by: Mary --- .../Configuration/BackendThreading.cs | 9 + Ryujinx.Common/Pools/ThreadStaticArray.cs | 20 + Ryujinx.Graphics.GAL/ICounterEvent.cs | 2 + Ryujinx.Graphics.GAL/IPipeline.cs | 2 +- Ryujinx.Graphics.GAL/IRenderer.cs | 11 +- Ryujinx.Graphics.GAL/IWindow.cs | 4 +- .../Multithreading/BufferMap.cs | 170 +++++++ .../Multithreading/CommandHelper.cs | 234 ++++++++++ .../Multithreading/CommandType.cs | 97 ++++ .../Multithreading/Commands/BarrierCommand.cs | 12 + .../Commands/BeginTransformFeedbackCommand.cs | 18 + .../Commands/Buffer/BufferDisposeCommand.cs | 19 + .../Commands/Buffer/BufferGetDataCommand.cs | 29 ++ .../Commands/Buffer/BufferSetDataCommand.cs | 27 ++ .../Commands/ClearBufferCommand.cs | 24 + .../Commands/ClearRenderTargetColorCommand.cs | 22 + .../ClearRenderTargetDepthStencilCommand.cs | 24 + .../Commands/CommandBufferBarrierCommand.cs | 12 + .../Commands/CopyBufferCommand.cs | 26 ++ .../CounterEventDisposeCommand.cs | 21 + .../CounterEvent/CounterEventFlushCommand.cs | 21 + .../Commands/DispatchComputeCommand.cs | 22 + .../Multithreading/Commands/DrawCommand.cs | 26 ++ .../Commands/DrawIndexedCommand.cs | 24 + .../EndHostConditionalRenderingCommand.cs | 12 + .../Commands/EndTransformFeedbackCommand.cs | 12 + .../Multithreading/Commands/IGALCommand.cs | 7 + .../MultiDrawIndexedIndirectCountCommand.cs | 29 ++ .../Commands/MultiDrawIndirectCountCommand.cs | 29 ++ .../Program/ProgramCheckLinkCommand.cs | 27 ++ .../Commands/Program/ProgramDisposeCommand.cs | 21 + .../Program/ProgramGetBinaryCommand.cs | 25 + .../Commands/Renderer/ActionCommand.cs | 21 + .../Commands/Renderer/CompileShaderCommand.cs | 22 + .../Commands/Renderer/CreateBufferCommand.cs | 23 + .../Commands/Renderer/CreateProgramCommand.cs | 28 ++ .../Commands/Renderer/CreateSamplerCommand.cs | 23 + .../Commands/Renderer/CreateSyncCommand.cs | 20 + .../Commands/Renderer/CreateTextureCommand.cs | 25 + .../Renderer/GetCapabilitiesCommand.cs | 20 + .../Commands/Renderer/PreFrameCommand.cs | 14 + .../Commands/Renderer/ReportCounterCommand.cs | 30 ++ .../Commands/Renderer/ResetCounterCommand.cs | 18 + .../Renderer/UpdateCountersCommand.cs | 12 + .../Commands/Sampler/SamplerDisposeCommand.cs | 21 + .../Commands/SetAlphaTestCommand.cs | 22 + .../Commands/SetBlendStateCommand.cs | 20 + .../Commands/SetDepthBiasCommand.cs | 24 + .../Commands/SetDepthClampCommand.cs | 18 + .../Commands/SetDepthModeCommand.cs | 18 + .../Commands/SetDepthTestCommand.cs | 18 + .../Commands/SetFaceCullingCommand.cs | 20 + .../Commands/SetFrontFaceCommand.cs | 18 + .../Commands/SetImageCommand.cs | 25 + .../Commands/SetIndexBufferCommand.cs | 21 + .../Commands/SetLineParametersCommand.cs | 20 + .../Commands/SetLogicOpStateCommand.cs | 20 + .../Commands/SetPointParametersCommand.cs | 24 + .../Commands/SetPrimitiveRestartCommand.cs | 20 + .../Commands/SetPrimitiveTopologyCommand.cs | 18 + .../Commands/SetProgramCommand.cs | 25 + .../Commands/SetRasterizerDiscardCommand.cs | 18 + .../SetRenderTargetColorMasksCommand.cs | 23 + .../Commands/SetRenderTargetScaleCommand.cs | 18 + .../Commands/SetRenderTargetsCommand.cs | 24 + .../Commands/SetSamplerCommand.cs | 23 + .../Commands/SetScissorCommand.cs | 28 ++ .../Commands/SetStencilTestCommand.cs | 18 + .../Commands/SetStorageBuffersCommand.cs | 25 + .../Commands/SetTextureCommand.cs | 23 + .../SetTransformFeedbackBuffersCommand.cs | 23 + .../Commands/SetUniformBuffersCommand.cs | 25 + .../Commands/SetUserClipDistanceCommand.cs | 20 + .../Commands/SetVertexAttribsCommand.cs | 23 + .../Commands/SetVertexBuffersCommand.cs | 24 + .../Commands/SetViewportsCommand.cs | 26 ++ .../Commands/Shader/ShaderDisposeCommand.cs | 21 + .../Commands/Texture/TextureCopyToCommand.cs | 28 ++ .../Texture/TextureCopyToScaledCommand.cs | 30 ++ .../Texture/TextureCopyToSliceCommand.cs | 32 ++ .../Texture/TextureCreateViewCommand.cs | 30 ++ .../Commands/Texture/TextureGetDataCommand.cs | 26 ++ .../Commands/Texture/TextureReleaseCommand.cs | 21 + .../Commands/Texture/TextureSetDataCommand.cs | 25 + .../Texture/TextureSetDataSliceCommand.cs | 29 ++ .../Texture/TextureSetStorageCommand.cs | 23 + .../Commands/TextureBarrierCommand.cs | 12 + .../Commands/TextureBarrierTiledCommand.cs | 12 + .../TryHostConditionalRenderingCommand.cs | 25 + ...TryHostConditionalRenderingFlushCommand.cs | 25 + .../Commands/UpdateRenderScaleCommand.cs | 28 ++ .../Commands/Window/WindowPresentCommand.cs | 27 ++ .../Multithreading/Model/CircularSpanPool.cs | 89 ++++ .../Multithreading/Model/PinnedSpan.cs | 23 + .../Multithreading/Model/ResultBox.cs | 7 + .../Multithreading/Model/SpanRef.cs | 39 ++ .../Multithreading/Model/TableRef.cs | 22 + .../Multithreading/Resources/ProgramQueue.cs | 107 +++++ .../Programs/BinaryProgramRequest.cs | 21 + .../Resources/Programs/IProgramRequest.cs | 8 + .../Programs/SourceProgramRequest.cs | 32 ++ .../Resources/ThreadedCounterEvent.cs | 80 ++++ .../Resources/ThreadedProgram.cs | 48 ++ .../Resources/ThreadedSampler.cs | 22 + .../Resources/ThreadedShader.cs | 38 ++ .../Resources/ThreadedTexture.cs | 116 +++++ .../Multithreading/SyncMap.cs | 62 +++ .../Multithreading/ThreadedHelpers.cs | 28 ++ .../Multithreading/ThreadedPipeline.cs | 344 ++++++++++++++ .../Multithreading/ThreadedRenderer.cs | 441 ++++++++++++++++++ .../Multithreading/ThreadedWindow.cs | 34 ++ .../Ryujinx.Graphics.GAL.csproj | 10 +- .../Engine/Threed/DrawManager.cs | 7 +- .../Engine/Threed/SemaphoreUpdater.cs | 7 +- Ryujinx.Graphics.Gpu/GpuContext.cs | 11 + Ryujinx.Graphics.Gpu/Memory/Buffer.cs | 2 +- .../Memory/BufferModifiedRangeList.cs | 35 +- Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs | 47 +- Ryujinx.Graphics.Gpu/Window.cs | 4 +- Ryujinx.Graphics.OpenGL/Pipeline.cs | 24 +- .../Queries/BufferedQuery.cs | 1 + .../Queries/CounterQueue.cs | 49 +- .../Queries/CounterQueueEvent.cs | 59 ++- Ryujinx.Graphics.OpenGL/Queries/Counters.cs | 4 +- Ryujinx.Graphics.OpenGL/Renderer.cs | 10 +- Ryujinx.Graphics.OpenGL/Window.cs | 4 +- Ryujinx.HLE/Switch.cs | 1 + Ryujinx.Headless.SDL2/Options.cs | 3 + Ryujinx.Headless.SDL2/Program.cs | 15 +- Ryujinx.Headless.SDL2/WindowBase.cs | 106 +++-- Ryujinx.Memory/Tracking/MemoryTracking.cs | 28 +- Ryujinx.Memory/Tracking/RegionHandle.cs | 24 +- Ryujinx.Memory/Tracking/VirtualRegion.cs | 6 +- Ryujinx/Config.json | 1 + .../Configuration/ConfigurationFileFormat.cs | 7 +- Ryujinx/Configuration/ConfigurationState.cs | 21 +- Ryujinx/Program.cs | 3 +- Ryujinx/Ui/GLRenderer.cs | 16 +- Ryujinx/Ui/MainWindow.cs | 12 + Ryujinx/Ui/RendererWidgetBase.cs | 106 +++-- Ryujinx/Ui/Windows/SettingsWindow.cs | 18 + Ryujinx/Ui/Windows/SettingsWindow.glade | 94 ++++ Ryujinx/_schema.json | 14 +- 143 files changed, 4491 insertions(+), 200 deletions(-) create mode 100644 Ryujinx.Common/Configuration/BackendThreading.cs create mode 100644 Ryujinx.Common/Pools/ThreadStaticArray.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/BufferMap.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/CommandHelper.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/CommandType.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Commands/BarrierCommand.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Commands/BeginTransformFeedbackCommand.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Commands/Buffer/BufferDisposeCommand.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Commands/Buffer/BufferGetDataCommand.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Commands/Buffer/BufferSetDataCommand.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Commands/ClearBufferCommand.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Commands/ClearRenderTargetColorCommand.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Commands/ClearRenderTargetDepthStencilCommand.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Commands/CommandBufferBarrierCommand.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Commands/CopyBufferCommand.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Commands/CounterEvent/CounterEventDisposeCommand.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Commands/CounterEvent/CounterEventFlushCommand.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Commands/DispatchComputeCommand.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Commands/DrawCommand.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Commands/DrawIndexedCommand.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Commands/EndHostConditionalRenderingCommand.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Commands/EndTransformFeedbackCommand.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Commands/IGALCommand.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Commands/MultiDrawIndexedIndirectCountCommand.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Commands/MultiDrawIndirectCountCommand.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Commands/Program/ProgramCheckLinkCommand.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Commands/Program/ProgramDisposeCommand.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Commands/Program/ProgramGetBinaryCommand.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/ActionCommand.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CompileShaderCommand.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateBufferCommand.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateProgramCommand.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateSamplerCommand.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateSyncCommand.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateTextureCommand.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/GetCapabilitiesCommand.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/PreFrameCommand.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/ReportCounterCommand.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/ResetCounterCommand.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/UpdateCountersCommand.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Commands/Sampler/SamplerDisposeCommand.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Commands/SetAlphaTestCommand.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Commands/SetBlendStateCommand.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Commands/SetDepthBiasCommand.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Commands/SetDepthClampCommand.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Commands/SetDepthModeCommand.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Commands/SetDepthTestCommand.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Commands/SetFaceCullingCommand.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Commands/SetFrontFaceCommand.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Commands/SetImageCommand.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Commands/SetIndexBufferCommand.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Commands/SetLineParametersCommand.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Commands/SetLogicOpStateCommand.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Commands/SetPointParametersCommand.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Commands/SetPrimitiveRestartCommand.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Commands/SetPrimitiveTopologyCommand.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Commands/SetProgramCommand.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Commands/SetRasterizerDiscardCommand.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Commands/SetRenderTargetColorMasksCommand.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Commands/SetRenderTargetScaleCommand.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Commands/SetRenderTargetsCommand.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Commands/SetSamplerCommand.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Commands/SetScissorCommand.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Commands/SetStencilTestCommand.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Commands/SetStorageBuffersCommand.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Commands/SetTextureCommand.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Commands/SetTransformFeedbackBuffersCommand.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Commands/SetUniformBuffersCommand.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Commands/SetUserClipDistanceCommand.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Commands/SetVertexAttribsCommand.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Commands/SetVertexBuffersCommand.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Commands/SetViewportsCommand.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Commands/Shader/ShaderDisposeCommand.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureCopyToCommand.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureCopyToScaledCommand.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureCopyToSliceCommand.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureCreateViewCommand.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureGetDataCommand.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureReleaseCommand.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureSetDataCommand.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureSetDataSliceCommand.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureSetStorageCommand.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Commands/TextureBarrierCommand.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Commands/TextureBarrierTiledCommand.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Commands/TryHostConditionalRenderingCommand.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Commands/TryHostConditionalRenderingFlushCommand.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Commands/UpdateRenderScaleCommand.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Commands/Window/WindowPresentCommand.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Model/CircularSpanPool.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Model/PinnedSpan.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Model/ResultBox.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Model/SpanRef.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Model/TableRef.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Resources/ProgramQueue.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Resources/Programs/BinaryProgramRequest.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Resources/Programs/IProgramRequest.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Resources/Programs/SourceProgramRequest.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedCounterEvent.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedProgram.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedSampler.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedShader.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedTexture.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/SyncMap.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/ThreadedHelpers.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/ThreadedRenderer.cs create mode 100644 Ryujinx.Graphics.GAL/Multithreading/ThreadedWindow.cs diff --git a/Ryujinx.Common/Configuration/BackendThreading.cs b/Ryujinx.Common/Configuration/BackendThreading.cs new file mode 100644 index 00000000..cfc08914 --- /dev/null +++ b/Ryujinx.Common/Configuration/BackendThreading.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Common.Configuration +{ + public enum BackendThreading + { + Auto, + Off, + On + } +} diff --git a/Ryujinx.Common/Pools/ThreadStaticArray.cs b/Ryujinx.Common/Pools/ThreadStaticArray.cs new file mode 100644 index 00000000..21434a02 --- /dev/null +++ b/Ryujinx.Common/Pools/ThreadStaticArray.cs @@ -0,0 +1,20 @@ +using System; + +namespace Ryujinx.Common.Pools +{ + public static class ThreadStaticArray + { + [ThreadStatic] + private static T[] _array; + + public static ref T[] Get() + { + if (_array == null) + { + _array = new T[1]; + } + + return ref _array; + } + } +} diff --git a/Ryujinx.Graphics.GAL/ICounterEvent.cs b/Ryujinx.Graphics.GAL/ICounterEvent.cs index dfabec61..13b15ae4 100644 --- a/Ryujinx.Graphics.GAL/ICounterEvent.cs +++ b/Ryujinx.Graphics.GAL/ICounterEvent.cs @@ -6,6 +6,8 @@ namespace Ryujinx.Graphics.GAL { bool Invalid { get; set; } + bool ReserveForHostAccess(); + void Flush(); } } diff --git a/Ryujinx.Graphics.GAL/IPipeline.cs b/Ryujinx.Graphics.GAL/IPipeline.cs index a5af6391..7a7d83cc 100644 --- a/Ryujinx.Graphics.GAL/IPipeline.cs +++ b/Ryujinx.Graphics.GAL/IPipeline.cs @@ -99,6 +99,6 @@ namespace Ryujinx.Graphics.GAL bool TryHostConditionalRendering(ICounterEvent value, ICounterEvent compare, bool isEqual); void EndHostConditionalRendering(); - void UpdateRenderScale(ShaderStage stage, float[] scales, int textureCount, int imageCount); + void UpdateRenderScale(ShaderStage stage, ReadOnlySpan scales, int textureCount, int imageCount); } } diff --git a/Ryujinx.Graphics.GAL/IRenderer.cs b/Ryujinx.Graphics.GAL/IRenderer.cs index 56a40172..7c0cb394 100644 --- a/Ryujinx.Graphics.GAL/IRenderer.cs +++ b/Ryujinx.Graphics.GAL/IRenderer.cs @@ -8,11 +8,13 @@ namespace Ryujinx.Graphics.GAL { event EventHandler ScreenCaptured; + bool PreferThreading { get; } + IPipeline Pipeline { get; } IWindow Window { get; } - void BackgroundContextAction(Action action); + void BackgroundContextAction(Action action, bool alwaysBackground = false); IShader CompileShader(ShaderStage stage, string code); @@ -39,10 +41,15 @@ namespace Ryujinx.Graphics.GAL void PreFrame(); - ICounterEvent ReportCounter(CounterType type, EventHandler resultHandler); + ICounterEvent ReportCounter(CounterType type, EventHandler resultHandler, bool hostReserved); void ResetCounter(CounterType type); + void RunLoop(Action gpuLoop) + { + gpuLoop(); + } + void WaitSync(ulong id); void Initialize(GraphicsDebugLevel logLevel); diff --git a/Ryujinx.Graphics.GAL/IWindow.cs b/Ryujinx.Graphics.GAL/IWindow.cs index 369f7b9a..d716d46e 100644 --- a/Ryujinx.Graphics.GAL/IWindow.cs +++ b/Ryujinx.Graphics.GAL/IWindow.cs @@ -1,8 +1,10 @@ +using System; + namespace Ryujinx.Graphics.GAL { public interface IWindow { - void Present(ITexture texture, ImageCrop crop); + void Present(ITexture texture, ImageCrop crop, Action swapBuffersCallback); void SetSize(int width, int height); } diff --git a/Ryujinx.Graphics.GAL/Multithreading/BufferMap.cs b/Ryujinx.Graphics.GAL/Multithreading/BufferMap.cs new file mode 100644 index 00000000..fcf09f9f --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/BufferMap.cs @@ -0,0 +1,170 @@ +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Threading; + +namespace Ryujinx.Graphics.GAL.Multithreading +{ + /// + /// Buffer handles given to the client are not the same as those provided by the backend, + /// as their handle is created at a later point on the queue. + /// The handle returned is a unique identifier that will map to the real buffer when it is available. + /// Note that any uses within the queue should be safe, but outside you must use MapBufferBlocking. + /// + class BufferMap + { + private ulong _bufferHandle = 0; + + private Dictionary _bufferMap = new Dictionary(); + private HashSet _inFlight = new HashSet(); + private AutoResetEvent _inFlightChanged = new AutoResetEvent(false); + + internal BufferHandle CreateBufferHandle() + { + ulong handle64 = Interlocked.Increment(ref _bufferHandle); + + BufferHandle threadedHandle = Unsafe.As(ref handle64); + + lock (_inFlight) + { + _inFlight.Add(threadedHandle); + } + + return threadedHandle; + } + + internal void AssignBuffer(BufferHandle threadedHandle, BufferHandle realHandle) + { + lock (_bufferMap) + { + _bufferMap[threadedHandle] = realHandle; + } + + lock (_inFlight) + { + _inFlight.Remove(threadedHandle); + } + + _inFlightChanged.Set(); + } + + internal void UnassignBuffer(BufferHandle threadedHandle) + { + lock (_bufferMap) + { + _bufferMap.Remove(threadedHandle); + } + } + + internal BufferHandle MapBuffer(BufferHandle handle) + { + // Maps a threaded buffer to a backend one. + // Threaded buffers are returned on creation as the buffer + // isn't actually created until the queue runs the command. + + BufferHandle result; + + lock (_bufferMap) + { + if (!_bufferMap.TryGetValue(handle, out result)) + { + result = BufferHandle.Null; + } + + return result; + } + } + + internal BufferHandle MapBufferBlocking(BufferHandle handle) + { + // Blocks until the handle is available. + + BufferHandle result; + + lock (_bufferMap) + { + if (_bufferMap.TryGetValue(handle, out result)) + { + return result; + } + } + + bool signal = false; + + while (true) + { + lock (_inFlight) + { + if (!_inFlight.Contains(handle)) + { + break; + } + } + + _inFlightChanged.WaitOne(); + signal = true; + } + + if (signal) + { + // Signal other threads which might still be waiting. + _inFlightChanged.Set(); + } + + return MapBuffer(handle); + } + + internal BufferRange MapBufferRange(BufferRange range) + { + return new BufferRange(MapBuffer(range.Handle), range.Offset, range.Size); + } + + internal Span MapBufferRanges(Span ranges) + { + // Rewrite the buffer ranges to point to the mapped handles. + + lock (_bufferMap) + { + for (int i = 0; i < ranges.Length; i++) + { + ref BufferRange range = ref ranges[i]; + BufferHandle result; + + if (!_bufferMap.TryGetValue(range.Handle, out result)) + { + result = BufferHandle.Null; + } + + range = new BufferRange(result, range.Offset, range.Size); + } + } + + return ranges; + } + + internal Span MapBufferRanges(Span ranges) + { + // Rewrite the buffer ranges to point to the mapped handles. + + lock (_bufferMap) + { + for (int i = 0; i < ranges.Length; i++) + { + BufferRange range = ranges[i].Buffer; + BufferHandle result; + + if (!_bufferMap.TryGetValue(range.Handle, out result)) + { + result = BufferHandle.Null; + } + + range = new BufferRange(result, range.Offset, range.Size); + + ranges[i] = new VertexBufferDescriptor(range, ranges[i].Stride, ranges[i].Divisor); + } + } + + return ranges; + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/CommandHelper.cs b/Ryujinx.Graphics.GAL/Multithreading/CommandHelper.cs new file mode 100644 index 00000000..82a75ea7 --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/CommandHelper.cs @@ -0,0 +1,234 @@ +using Ryujinx.Graphics.GAL.Multithreading.Commands; +using Ryujinx.Graphics.GAL.Multithreading.Commands.Buffer; +using Ryujinx.Graphics.GAL.Multithreading.Commands.CounterEvent; +using Ryujinx.Graphics.GAL.Multithreading.Commands.Program; +using Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer; +using Ryujinx.Graphics.GAL.Multithreading.Commands.Sampler; +using Ryujinx.Graphics.GAL.Multithreading.Commands.Shader; +using Ryujinx.Graphics.GAL.Multithreading.Commands.Texture; +using Ryujinx.Graphics.GAL.Multithreading.Commands.Window; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.GAL.Multithreading +{ + static class CommandHelper + { + private delegate void CommandDelegate(Span memory, ThreadedRenderer threaded, IRenderer renderer); + + private static int _totalCommands = (int)Enum.GetValues().Max() + 1; + private static CommandDelegate[] _lookup = new CommandDelegate[_totalCommands]; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static ref T GetCommand(Span memory) + { + return ref Unsafe.As(ref MemoryMarshal.GetReference(memory)); + } + + public static int GetMaxCommandSize() + { + Assembly assembly = typeof(CommandHelper).Assembly; + + IEnumerable commands = assembly.GetTypes().Where(type => typeof(IGALCommand).IsAssignableFrom(type) && type.IsValueType); + + int maxSize = commands.Max(command => + { + MethodInfo method = typeof(Unsafe).GetMethod(nameof(Unsafe.SizeOf)); + MethodInfo generic = method.MakeGenericMethod(command); + int size = (int)generic.Invoke(null, null); + + return size; + }); + + InitLookup(); + + return maxSize + 1; // 1 byte reserved for command size. + } + + private static void InitLookup() + { + _lookup[(int)CommandType.Action] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => + ActionCommand.Run(ref GetCommand(memory), threaded, renderer); + _lookup[(int)CommandType.CompileShader] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => + CompileShaderCommand.Run(ref GetCommand(memory), threaded, renderer); + _lookup[(int)CommandType.CreateBuffer] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => + CreateBufferCommand.Run(ref GetCommand(memory), threaded, renderer); + _lookup[(int)CommandType.CreateProgram] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => + CreateProgramCommand.Run(ref GetCommand(memory), threaded, renderer); + _lookup[(int)CommandType.CreateSampler] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => + CreateSamplerCommand.Run(ref GetCommand(memory), threaded, renderer); + _lookup[(int)CommandType.CreateSync] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => + CreateSyncCommand.Run(ref GetCommand(memory), threaded, renderer); + _lookup[(int)CommandType.CreateTexture] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => + CreateTextureCommand.Run(ref GetCommand(memory), threaded, renderer); + _lookup[(int)CommandType.GetCapabilities] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => + GetCapabilitiesCommand.Run(ref GetCommand(memory), threaded, renderer); + _lookup[(int)CommandType.PreFrame] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => + PreFrameCommand.Run(ref GetCommand(memory), threaded, renderer); + _lookup[(int)CommandType.ReportCounter] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => + ReportCounterCommand.Run(ref GetCommand(memory), threaded, renderer); + _lookup[(int)CommandType.ResetCounter] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => + ResetCounterCommand.Run(ref GetCommand(memory), threaded, renderer); + _lookup[(int)CommandType.UpdateCounters] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => + UpdateCountersCommand.Run(ref GetCommand(memory), threaded, renderer); + + _lookup[(int)CommandType.BufferDispose] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => + BufferDisposeCommand.Run(ref GetCommand(memory), threaded, renderer); + _lookup[(int)CommandType.BufferGetData] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => + BufferGetDataCommand.Run(ref GetCommand(memory), threaded, renderer); + _lookup[(int)CommandType.BufferSetData] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => + BufferSetDataCommand.Run(ref GetCommand(memory), threaded, renderer); + + _lookup[(int)CommandType.CounterEventDispose] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => + CounterEventDisposeCommand.Run(ref GetCommand(memory), threaded, renderer); + _lookup[(int)CommandType.CounterEventFlush] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => + CounterEventFlushCommand.Run(ref GetCommand(memory), threaded, renderer); + + _lookup[(int)CommandType.ProgramDispose] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => + ProgramDisposeCommand.Run(ref GetCommand(memory), threaded, renderer); + _lookup[(int)CommandType.ProgramGetBinary] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => + ProgramGetBinaryCommand.Run(ref GetCommand(memory), threaded, renderer); + _lookup[(int)CommandType.ProgramCheckLink] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => + ProgramCheckLinkCommand.Run(ref GetCommand(memory), threaded, renderer); + + _lookup[(int)CommandType.SamplerDispose] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => + SamplerDisposeCommand.Run(ref GetCommand(memory), threaded, renderer); + + _lookup[(int)CommandType.ShaderDispose] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => + ShaderDisposeCommand.Run(ref GetCommand(memory), threaded, renderer); + + _lookup[(int)CommandType.TextureCopyTo] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => + TextureCopyToCommand.Run(ref GetCommand(memory), threaded, renderer); + _lookup[(int)CommandType.TextureCopyToScaled] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => + TextureCopyToScaledCommand.Run(ref GetCommand(memory), threaded, renderer); + _lookup[(int)CommandType.TextureCopyToSlice] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => + TextureCopyToSliceCommand.Run(ref GetCommand(memory), threaded, renderer); + _lookup[(int)CommandType.TextureCreateView] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => + TextureCreateViewCommand.Run(ref GetCommand(memory), threaded, renderer); + _lookup[(int)CommandType.TextureGetData] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => + TextureGetDataCommand.Run(ref GetCommand(memory), threaded, renderer); + _lookup[(int)CommandType.TextureRelease] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => + TextureReleaseCommand.Run(ref GetCommand(memory), threaded, renderer); + _lookup[(int)CommandType.TextureSetData] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => + TextureSetDataCommand.Run(ref GetCommand(memory), threaded, renderer); + _lookup[(int)CommandType.TextureSetDataSlice] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => + TextureSetDataSliceCommand.Run(ref GetCommand(memory), threaded, renderer); + _lookup[(int)CommandType.TextureSetStorage] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => + TextureSetStorageCommand.Run(ref GetCommand(memory), threaded, renderer); + + _lookup[(int)CommandType.WindowPresent] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => + WindowPresentCommand.Run(ref GetCommand(memory), threaded, renderer); + + _lookup[(int)CommandType.Barrier] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => + BarrierCommand.Run(ref GetCommand(memory), threaded, renderer); + _lookup[(int)CommandType.BeginTransformFeedback] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => + BeginTransformFeedbackCommand.Run(ref GetCommand(memory), threaded, renderer); + _lookup[(int)CommandType.ClearBuffer] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => + ClearBufferCommand.Run(ref GetCommand(memory), threaded, renderer); + _lookup[(int)CommandType.ClearRenderTargetColor] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => + ClearRenderTargetColorCommand.Run(ref GetCommand(memory), threaded, renderer); + _lookup[(int)CommandType.ClearRenderTargetDepthStencil] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => + ClearRenderTargetDepthStencilCommand.Run(ref GetCommand(memory), threaded, renderer); + _lookup[(int)CommandType.CommandBufferBarrier] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => + CommandBufferBarrierCommand.Run(ref GetCommand(memory), threaded, renderer); + _lookup[(int)CommandType.CopyBuffer] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => + CopyBufferCommand.Run(ref GetCommand(memory), threaded, renderer); + _lookup[(int)CommandType.DispatchCompute] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => + DispatchComputeCommand.Run(ref GetCommand(memory), threaded, renderer); + _lookup[(int)CommandType.Draw] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => + DrawCommand.Run(ref GetCommand(memory), threaded, renderer); + _lookup[(int)CommandType.DrawIndexed] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => + DrawIndexedCommand.Run(ref GetCommand(memory), threaded, renderer); + _lookup[(int)CommandType.EndHostConditionalRendering] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => + EndHostConditionalRenderingCommand.Run(renderer); + _lookup[(int)CommandType.EndTransformFeedback] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => + EndTransformFeedbackCommand.Run(ref GetCommand(memory), threaded, renderer); + _lookup[(int)CommandType.MultiDrawIndirectCount] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => + MultiDrawIndirectCountCommand.Run(ref GetCommand(memory), threaded, renderer); + _lookup[(int)CommandType.MultiDrawIndexedIndirectCount] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => + MultiDrawIndexedIndirectCountCommand.Run(ref GetCommand(memory), threaded, renderer); + _lookup[(int)CommandType.SetAlphaTest] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => + SetAlphaTestCommand.Run(ref GetCommand(memory), threaded, renderer); + _lookup[(int)CommandType.SetBlendState] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => + SetBlendStateCommand.Run(ref GetCommand(memory), threaded, renderer); + _lookup[(int)CommandType.SetDepthBias] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => + SetDepthBiasCommand.Run(ref GetCommand(memory), threaded, renderer); + _lookup[(int)CommandType.SetDepthClamp] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => + SetDepthClampCommand.Run(ref GetCommand(memory), threaded, renderer); + _lookup[(int)CommandType.SetDepthMode] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => + SetDepthModeCommand.Run(ref GetCommand(memory), threaded, renderer); + _lookup[(int)CommandType.SetDepthTest] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => + SetDepthTestCommand.Run(ref GetCommand(memory), threaded, renderer); + _lookup[(int)CommandType.SetFaceCulling] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => + SetFaceCullingCommand.Run(ref GetCommand(memory), threaded, renderer); + _lookup[(int)CommandType.SetFrontFace] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => + SetFrontFaceCommand.Run(ref GetCommand(memory), threaded, renderer); + _lookup[(int)CommandType.SetStorageBuffers] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => + SetStorageBuffersCommand.Run(ref GetCommand(memory), threaded, renderer); + _lookup[(int)CommandType.SetTransformFeedbackBuffers] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => + SetTransformFeedbackBuffersCommand.Run(ref GetCommand(memory), threaded, renderer); + _lookup[(int)CommandType.SetUniformBuffers] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => + SetUniformBuffersCommand.Run(ref GetCommand(memory), threaded, renderer); + _lookup[(int)CommandType.SetImage] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => + SetImageCommand.Run(ref GetCommand(memory), threaded, renderer); + _lookup[(int)CommandType.SetIndexBuffer] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => + SetIndexBufferCommand.Run(ref GetCommand(memory), threaded, renderer); + _lookup[(int)CommandType.SetLineParameters] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => + SetLineParametersCommand.Run(ref GetCommand(memory), threaded, renderer); + _lookup[(int)CommandType.SetLogicOpState] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => + SetLogicOpStateCommand.Run(ref GetCommand(memory), threaded, renderer); + _lookup[(int)CommandType.SetPointParameters] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => + SetPointParametersCommand.Run(ref GetCommand(memory), threaded, renderer); + _lookup[(int)CommandType.SetPrimitiveRestart] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => + SetPrimitiveRestartCommand.Run(ref GetCommand(memory), threaded, renderer); + _lookup[(int)CommandType.SetPrimitiveTopology] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => + SetPrimitiveTopologyCommand.Run(ref GetCommand(memory), threaded, renderer); + _lookup[(int)CommandType.SetProgram] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => + SetProgramCommand.Run(ref GetCommand(memory), threaded, renderer); + _lookup[(int)CommandType.SetRasterizerDiscard] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => + SetRasterizerDiscardCommand.Run(ref GetCommand(memory), threaded, renderer); + _lookup[(int)CommandType.SetRenderTargetColorMasks] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => + SetRenderTargetColorMasksCommand.Run(ref GetCommand(memory), threaded, renderer); + _lookup[(int)CommandType.SetRenderTargetScale] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => + SetRenderTargetScaleCommand.Run(ref GetCommand(memory), threaded, renderer); + _lookup[(int)CommandType.SetRenderTargets] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => + SetRenderTargetsCommand.Run(ref GetCommand(memory), threaded, renderer); + _lookup[(int)CommandType.SetSampler] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => + SetSamplerCommand.Run(ref GetCommand(memory), threaded, renderer); + _lookup[(int)CommandType.SetScissor] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => + SetScissorCommand.Run(ref GetCommand(memory), threaded, renderer); + _lookup[(int)CommandType.SetStencilTest] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => + SetStencilTestCommand.Run(ref GetCommand(memory), threaded, renderer); + _lookup[(int)CommandType.SetTexture] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => + SetTextureCommand.Run(ref GetCommand(memory), threaded, renderer); + _lookup[(int)CommandType.SetUserClipDistance] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => + SetUserClipDistanceCommand.Run(ref GetCommand(memory), threaded, renderer); + _lookup[(int)CommandType.SetVertexAttribs] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => + SetVertexAttribsCommand.Run(ref GetCommand(memory), threaded, renderer); + _lookup[(int)CommandType.SetVertexBuffers] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => + SetVertexBuffersCommand.Run(ref GetCommand(memory), threaded, renderer); + _lookup[(int)CommandType.SetViewports] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => + SetViewportsCommand.Run(ref GetCommand(memory), threaded, renderer); + _lookup[(int)CommandType.TextureBarrier] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => + TextureBarrierCommand.Run(ref GetCommand(memory), threaded, renderer); + _lookup[(int)CommandType.TextureBarrierTiled] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => + TextureBarrierTiledCommand.Run(ref GetCommand(memory), threaded, renderer); + _lookup[(int)CommandType.TryHostConditionalRendering] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => + TryHostConditionalRenderingCommand.Run(ref GetCommand(memory), threaded, renderer); + _lookup[(int)CommandType.TryHostConditionalRenderingFlush] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => + TryHostConditionalRenderingFlushCommand.Run(ref GetCommand(memory), threaded, renderer); + _lookup[(int)CommandType.UpdateRenderScale] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => + UpdateRenderScaleCommand.Run(ref GetCommand(memory), threaded, renderer); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void RunCommand(Span memory, ThreadedRenderer threaded, IRenderer renderer) + { + _lookup[memory[memory.Length - 1]](memory, threaded, renderer); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/CommandType.cs b/Ryujinx.Graphics.GAL/Multithreading/CommandType.cs new file mode 100644 index 00000000..0761a7f0 --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/CommandType.cs @@ -0,0 +1,97 @@ +namespace Ryujinx.Graphics.GAL.Multithreading +{ + enum CommandType : byte + { + Action, + CompileShader, + CreateBuffer, + CreateProgram, + CreateSampler, + CreateSync, + CreateTexture, + GetCapabilities, + Unused, + PreFrame, + ReportCounter, + ResetCounter, + UpdateCounters, + + BufferDispose, + BufferGetData, + BufferSetData, + + CounterEventDispose, + CounterEventFlush, + + ProgramDispose, + ProgramGetBinary, + ProgramCheckLink, + + SamplerDispose, + + ShaderDispose, + + TextureCopyTo, + TextureCopyToScaled, + TextureCopyToSlice, + TextureCreateView, + TextureGetData, + TextureRelease, + TextureSetData, + TextureSetDataSlice, + TextureSetStorage, + + WindowPresent, + + Barrier, + BeginTransformFeedback, + ClearBuffer, + ClearRenderTargetColor, + ClearRenderTargetDepthStencil, + CommandBufferBarrier, + CopyBuffer, + DispatchCompute, + Draw, + DrawIndexed, + EndHostConditionalRendering, + EndTransformFeedback, + MultiDrawIndirectCount, + MultiDrawIndexedIndirectCount, + SetAlphaTest, + SetBlendState, + SetDepthBias, + SetDepthClamp, + SetDepthMode, + SetDepthTest, + SetFaceCulling, + SetFrontFace, + SetStorageBuffers, + SetTransformFeedbackBuffers, + SetUniformBuffers, + SetImage, + SetIndexBuffer, + SetLineParameters, + SetLogicOpState, + SetPointParameters, + SetPrimitiveRestart, + SetPrimitiveTopology, + SetProgram, + SetRasterizerDiscard, + SetRenderTargetColorMasks, + SetRenderTargetScale, + SetRenderTargets, + SetSampler, + SetScissor, + SetStencilTest, + SetTexture, + SetUserClipDistance, + SetVertexAttribs, + SetVertexBuffers, + SetViewports, + TextureBarrier, + TextureBarrierTiled, + TryHostConditionalRendering, + TryHostConditionalRenderingFlush, + UpdateRenderScale + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/BarrierCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/BarrierCommand.cs new file mode 100644 index 00000000..f187c3c2 --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/BarrierCommand.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct BarrierCommand : IGALCommand + { + public CommandType CommandType => CommandType.Barrier; + + public static void Run(ref BarrierCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.Barrier(); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/BeginTransformFeedbackCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/BeginTransformFeedbackCommand.cs new file mode 100644 index 00000000..ea547d8b --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/BeginTransformFeedbackCommand.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct BeginTransformFeedbackCommand : IGALCommand + { + public CommandType CommandType => CommandType.BeginTransformFeedback; + private PrimitiveTopology _topology; + + public void Set(PrimitiveTopology topology) + { + _topology = topology; + } + + public static void Run(ref BeginTransformFeedbackCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.BeginTransformFeedback(command._topology); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/Buffer/BufferDisposeCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/Buffer/BufferDisposeCommand.cs new file mode 100644 index 00000000..68167be0 --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/Buffer/BufferDisposeCommand.cs @@ -0,0 +1,19 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Buffer +{ + struct BufferDisposeCommand : IGALCommand + { + public CommandType CommandType => CommandType.BufferDispose; + private BufferHandle _buffer; + + public void Set(BufferHandle buffer) + { + _buffer = buffer; + } + + public static void Run(ref BufferDisposeCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.DeleteBuffer(threaded.Buffers.MapBuffer(command._buffer)); + threaded.Buffers.UnassignBuffer(command._buffer); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/Buffer/BufferGetDataCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/Buffer/BufferGetDataCommand.cs new file mode 100644 index 00000000..786ed87c --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/Buffer/BufferGetDataCommand.cs @@ -0,0 +1,29 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using System; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Buffer +{ + struct BufferGetDataCommand : IGALCommand + { + public CommandType CommandType => CommandType.BufferGetData; + private BufferHandle _buffer; + private int _offset; + private int _size; + private TableRef>> _result; + + public void Set(BufferHandle buffer, int offset, int size, TableRef>> result) + { + _buffer = buffer; + _offset = offset; + _size = size; + _result = result; + } + + public static void Run(ref BufferGetDataCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + ReadOnlySpan result = renderer.GetBufferData(threaded.Buffers.MapBuffer(command._buffer), command._offset, command._size); + + command._result.Get(threaded).Result = new PinnedSpan(result); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/Buffer/BufferSetDataCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/Buffer/BufferSetDataCommand.cs new file mode 100644 index 00000000..6f39898e --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/Buffer/BufferSetDataCommand.cs @@ -0,0 +1,27 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using System; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Buffer +{ + struct BufferSetDataCommand : IGALCommand + { + public CommandType CommandType => CommandType.BufferSetData; + private BufferHandle _buffer; + private int _offset; + private SpanRef _data; + + public void Set(BufferHandle buffer, int offset, SpanRef data) + { + _buffer = buffer; + _offset = offset; + _data = data; + } + + public static void Run(ref BufferSetDataCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + ReadOnlySpan data = command._data.Get(threaded); + renderer.SetBufferData(threaded.Buffers.MapBuffer(command._buffer), command._offset, data); + command._data.Dispose(threaded); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/ClearBufferCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/ClearBufferCommand.cs new file mode 100644 index 00000000..2b194b46 --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/ClearBufferCommand.cs @@ -0,0 +1,24 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct ClearBufferCommand : IGALCommand + { + public CommandType CommandType => CommandType.ClearBuffer; + private BufferHandle _destination; + private int _offset; + private int _size; + private uint _value; + + public void Set(BufferHandle destination, int offset, int size, uint value) + { + _destination = destination; + _offset = offset; + _size = size; + _value = value; + } + + public static void Run(ref ClearBufferCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.ClearBuffer(threaded.Buffers.MapBuffer(command._destination), command._offset, command._size, command._value); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/ClearRenderTargetColorCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/ClearRenderTargetColorCommand.cs new file mode 100644 index 00000000..57509f1c --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/ClearRenderTargetColorCommand.cs @@ -0,0 +1,22 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct ClearRenderTargetColorCommand : IGALCommand + { + public CommandType CommandType => CommandType.ClearRenderTargetColor; + private int _index; + private uint _componentMask; + private ColorF _color; + + public void Set(int index, uint componentMask, ColorF color) + { + _index = index; + _componentMask = componentMask; + _color = color; + } + + public static void Run(ref ClearRenderTargetColorCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.ClearRenderTargetColor(command._index, command._componentMask, command._color); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/ClearRenderTargetDepthStencilCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/ClearRenderTargetDepthStencilCommand.cs new file mode 100644 index 00000000..3692cd37 --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/ClearRenderTargetDepthStencilCommand.cs @@ -0,0 +1,24 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct ClearRenderTargetDepthStencilCommand : IGALCommand + { + public CommandType CommandType => CommandType.ClearRenderTargetDepthStencil; + private float _depthValue; + private bool _depthMask; + private int _stencilValue; + private int _stencilMask; + + public void Set(float depthValue, bool depthMask, int stencilValue, int stencilMask) + { + _depthValue = depthValue; + _depthMask = depthMask; + _stencilValue = stencilValue; + _stencilMask = stencilMask; + } + + public static void Run(ref ClearRenderTargetDepthStencilCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.ClearRenderTargetDepthStencil(command._depthValue, command._depthMask, command._stencilValue, command._stencilMask); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/CommandBufferBarrierCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/CommandBufferBarrierCommand.cs new file mode 100644 index 00000000..8c828648 --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/CommandBufferBarrierCommand.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct CommandBufferBarrierCommand : IGALCommand + { + public CommandType CommandType => CommandType.CommandBufferBarrier; + + public static void Run(ref CommandBufferBarrierCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.CommandBufferBarrier(); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/CopyBufferCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/CopyBufferCommand.cs new file mode 100644 index 00000000..e8f80d98 --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/CopyBufferCommand.cs @@ -0,0 +1,26 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct CopyBufferCommand : IGALCommand + { + public CommandType CommandType => CommandType.CopyBuffer; + private BufferHandle _source; + private BufferHandle _destination; + private int _srcOffset; + private int _dstOffset; + private int _size; + + public void Set(BufferHandle source, BufferHandle destination, int srcOffset, int dstOffset, int size) + { + _source = source; + _destination = destination; + _srcOffset = srcOffset; + _dstOffset = dstOffset; + _size = size; + } + + public static void Run(ref CopyBufferCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.CopyBuffer(threaded.Buffers.MapBuffer(command._source), threaded.Buffers.MapBuffer(command._destination), command._srcOffset, command._dstOffset, command._size); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/CounterEvent/CounterEventDisposeCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/CounterEvent/CounterEventDisposeCommand.cs new file mode 100644 index 00000000..ae634e6a --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/CounterEvent/CounterEventDisposeCommand.cs @@ -0,0 +1,21 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.CounterEvent +{ + struct CounterEventDisposeCommand : IGALCommand + { + public CommandType CommandType => CommandType.CounterEventDispose; + private TableRef _event; + + public void Set(TableRef evt) + { + _event = evt; + } + + public static void Run(ref CounterEventDisposeCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + command._event.Get(threaded).Base.Dispose(); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/CounterEvent/CounterEventFlushCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/CounterEvent/CounterEventFlushCommand.cs new file mode 100644 index 00000000..e4ff4c18 --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/CounterEvent/CounterEventFlushCommand.cs @@ -0,0 +1,21 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.CounterEvent +{ + struct CounterEventFlushCommand : IGALCommand + { + public CommandType CommandType => CommandType.CounterEventFlush; + private TableRef _event; + + public void Set(TableRef evt) + { + _event = evt; + } + + public static void Run(ref CounterEventFlushCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + command._event.Get(threaded).Base.Flush(); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/DispatchComputeCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/DispatchComputeCommand.cs new file mode 100644 index 00000000..26c88062 --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/DispatchComputeCommand.cs @@ -0,0 +1,22 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct DispatchComputeCommand : IGALCommand + { + public CommandType CommandType => CommandType.DispatchCompute; + private int _groupsX; + private int _groupsY; + private int _groupsZ; + + public void Set(int groupsX, int groupsY, int groupsZ) + { + _groupsX = groupsX; + _groupsY = groupsY; + _groupsZ = groupsZ; + } + + public static void Run(ref DispatchComputeCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.DispatchCompute(command._groupsX, command._groupsY, command._groupsZ); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/DrawCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/DrawCommand.cs new file mode 100644 index 00000000..ff27303a --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/DrawCommand.cs @@ -0,0 +1,26 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct DrawIndexedCommand : IGALCommand + { + public CommandType CommandType => CommandType.DrawIndexed; + private int _indexCount; + private int _instanceCount; + private int _firstIndex; + private int _firstVertex; + private int _firstInstance; + + public void Set(int indexCount, int instanceCount, int firstIndex, int firstVertex, int firstInstance) + { + _indexCount = indexCount; + _instanceCount = instanceCount; + _firstIndex = firstIndex; + _firstVertex = firstVertex; + _firstInstance = firstInstance; + } + + public static void Run(ref DrawIndexedCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.DrawIndexed(command._indexCount, command._instanceCount, command._firstIndex, command._firstVertex, command._firstInstance); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/DrawIndexedCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/DrawIndexedCommand.cs new file mode 100644 index 00000000..fc84819a --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/DrawIndexedCommand.cs @@ -0,0 +1,24 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct DrawCommand : IGALCommand + { + public CommandType CommandType => CommandType.Draw; + private int _vertexCount; + private int _instanceCount; + private int _firstVertex; + private int _firstInstance; + + public void Set(int vertexCount, int instanceCount, int firstVertex, int firstInstance) + { + _vertexCount = vertexCount; + _instanceCount = instanceCount; + _firstVertex = firstVertex; + _firstInstance = firstInstance; + } + + public static void Run(ref DrawCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.Draw(command._vertexCount, command._instanceCount, command._firstVertex, command._firstInstance); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/EndHostConditionalRenderingCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/EndHostConditionalRenderingCommand.cs new file mode 100644 index 00000000..e0edd9ab --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/EndHostConditionalRenderingCommand.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct EndHostConditionalRenderingCommand : IGALCommand + { + public CommandType CommandType => CommandType.EndHostConditionalRendering; + + public static void Run(IRenderer renderer) + { + renderer.Pipeline.EndHostConditionalRendering(); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/EndTransformFeedbackCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/EndTransformFeedbackCommand.cs new file mode 100644 index 00000000..561996e3 --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/EndTransformFeedbackCommand.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct EndTransformFeedbackCommand : IGALCommand + { + public CommandType CommandType => CommandType.EndTransformFeedback; + + public static void Run(ref EndTransformFeedbackCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.EndTransformFeedback(); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/IGALCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/IGALCommand.cs new file mode 100644 index 00000000..5fb04c80 --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/IGALCommand.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + interface IGALCommand + { + CommandType CommandType { get; } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/MultiDrawIndexedIndirectCountCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/MultiDrawIndexedIndirectCountCommand.cs new file mode 100644 index 00000000..6798f8cc --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/MultiDrawIndexedIndirectCountCommand.cs @@ -0,0 +1,29 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct MultiDrawIndexedIndirectCountCommand : IGALCommand + { + public CommandType CommandType => CommandType.MultiDrawIndexedIndirectCount; + private BufferRange _indirectBuffer; + private BufferRange _parameterBuffer; + private int _maxDrawCount; + private int _stride; + + public void Set(BufferRange indirectBuffer, BufferRange parameterBuffer, int maxDrawCount, int stride) + { + _indirectBuffer = indirectBuffer; + _parameterBuffer = parameterBuffer; + _maxDrawCount = maxDrawCount; + _stride = stride; + } + + public static void Run(ref MultiDrawIndexedIndirectCountCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.MultiDrawIndexedIndirectCount( + threaded.Buffers.MapBufferRange(command._indirectBuffer), + threaded.Buffers.MapBufferRange(command._parameterBuffer), + command._maxDrawCount, + command._stride + ); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/MultiDrawIndirectCountCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/MultiDrawIndirectCountCommand.cs new file mode 100644 index 00000000..7a9d07f3 --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/MultiDrawIndirectCountCommand.cs @@ -0,0 +1,29 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct MultiDrawIndirectCountCommand : IGALCommand + { + public CommandType CommandType => CommandType.MultiDrawIndirectCount; + private BufferRange _indirectBuffer; + private BufferRange _parameterBuffer; + private int _maxDrawCount; + private int _stride; + + public void Set(BufferRange indirectBuffer, BufferRange parameterBuffer, int maxDrawCount, int stride) + { + _indirectBuffer = indirectBuffer; + _parameterBuffer = parameterBuffer; + _maxDrawCount = maxDrawCount; + _stride = stride; + } + + public static void Run(ref MultiDrawIndirectCountCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.MultiDrawIndirectCount( + threaded.Buffers.MapBufferRange(command._indirectBuffer), + threaded.Buffers.MapBufferRange(command._parameterBuffer), + command._maxDrawCount, + command._stride + ); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/Program/ProgramCheckLinkCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/Program/ProgramCheckLinkCommand.cs new file mode 100644 index 00000000..7ae887f4 --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/Program/ProgramCheckLinkCommand.cs @@ -0,0 +1,27 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Program +{ + struct ProgramCheckLinkCommand : IGALCommand + { + public CommandType CommandType => CommandType.ProgramCheckLink; + private TableRef _program; + private bool _blocking; + private TableRef> _result; + + public void Set(TableRef program, bool blocking, TableRef> result) + { + _program = program; + _blocking = blocking; + _result = result; + } + + public static void Run(ref ProgramCheckLinkCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + ProgramLinkStatus result = command._program.Get(threaded).Base.CheckProgramLink(command._blocking); + + command._result.Get(threaded).Result = result; + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/Program/ProgramDisposeCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/Program/ProgramDisposeCommand.cs new file mode 100644 index 00000000..e614c392 --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/Program/ProgramDisposeCommand.cs @@ -0,0 +1,21 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Program +{ + struct ProgramDisposeCommand : IGALCommand + { + public CommandType CommandType => CommandType.ProgramDispose; + private TableRef _program; + + public void Set(TableRef program) + { + _program = program; + } + + public static void Run(ref ProgramDisposeCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + command._program.Get(threaded).Base.Dispose(); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/Program/ProgramGetBinaryCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/Program/ProgramGetBinaryCommand.cs new file mode 100644 index 00000000..92c0a6d6 --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/Program/ProgramGetBinaryCommand.cs @@ -0,0 +1,25 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Program +{ + struct ProgramGetBinaryCommand : IGALCommand + { + public CommandType CommandType => CommandType.ProgramGetBinary; + private TableRef _program; + private TableRef> _result; + + public void Set(TableRef program, TableRef> result) + { + _program = program; + _result = result; + } + + public static void Run(ref ProgramGetBinaryCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + byte[] result = command._program.Get(threaded).Base.GetBinary(); + + command._result.Get(threaded).Result = result; + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/ActionCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/ActionCommand.cs new file mode 100644 index 00000000..07e55c96 --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/ActionCommand.cs @@ -0,0 +1,21 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using System; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer +{ + struct ActionCommand : IGALCommand + { + public CommandType CommandType => CommandType.Action; + private TableRef _action; + + public void Set(TableRef action) + { + _action = action; + } + + public static void Run(ref ActionCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + command._action.Get(threaded)(); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CompileShaderCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CompileShaderCommand.cs new file mode 100644 index 00000000..2bd9725d --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CompileShaderCommand.cs @@ -0,0 +1,22 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer +{ + struct CompileShaderCommand : IGALCommand + { + public CommandType CommandType => CommandType.CompileShader; + private TableRef _shader; + + public void Set(TableRef shader) + { + _shader = shader; + } + + public static void Run(ref CompileShaderCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + ThreadedShader shader = command._shader.Get(threaded); + shader.EnsureCreated(); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateBufferCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateBufferCommand.cs new file mode 100644 index 00000000..4d1cbb28 --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateBufferCommand.cs @@ -0,0 +1,23 @@ +using Ryujinx.Graphics.GAL.Multithreading.Resources; +using Ryujinx.Graphics.Shader; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer +{ + struct CreateBufferCommand : IGALCommand + { + public CommandType CommandType => CommandType.CreateBuffer; + private BufferHandle _threadedHandle; + private int _size; + + public void Set(BufferHandle threadedHandle, int size) + { + _threadedHandle = threadedHandle; + _size = size; + } + + public static void Run(ref CreateBufferCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + threaded.Buffers.AssignBuffer(command._threadedHandle, renderer.CreateBuffer(command._size)); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateProgramCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateProgramCommand.cs new file mode 100644 index 00000000..e24505e5 --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateProgramCommand.cs @@ -0,0 +1,28 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources.Programs; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer +{ + struct CreateProgramCommand : IGALCommand + { + public CommandType CommandType => CommandType.CreateProgram; + private TableRef _request; + + public void Set(TableRef request) + { + _request = request; + } + + public static void Run(ref CreateProgramCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + IProgramRequest request = command._request.Get(threaded); + + if (request.Threaded.Base == null) + { + request.Threaded.Base = request.Create(renderer); + } + + threaded.Programs.ProcessQueue(); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateSamplerCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateSamplerCommand.cs new file mode 100644 index 00000000..bca98cfb --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateSamplerCommand.cs @@ -0,0 +1,23 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer +{ + struct CreateSamplerCommand : IGALCommand + { + public CommandType CommandType => CommandType.CreateSampler; + private TableRef _sampler; + private SamplerCreateInfo _info; + + public void Set(TableRef sampler, SamplerCreateInfo info) + { + _sampler = sampler; + _info = info; + } + + public static void Run(ref CreateSamplerCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + command._sampler.Get(threaded).Base = renderer.CreateSampler(command._info); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateSyncCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateSyncCommand.cs new file mode 100644 index 00000000..2e23760e --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateSyncCommand.cs @@ -0,0 +1,20 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer +{ + struct CreateSyncCommand : IGALCommand + { + public CommandType CommandType => CommandType.CreateSync; + private ulong _id; + + public void Set(ulong id) + { + _id = id; + } + + public static void Run(ref CreateSyncCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.CreateSync(command._id); + + threaded.Sync.AssignSync(command._id); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateTextureCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateTextureCommand.cs new file mode 100644 index 00000000..f9240125 --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateTextureCommand.cs @@ -0,0 +1,25 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer +{ + struct CreateTextureCommand : IGALCommand + { + public CommandType CommandType => CommandType.CreateTexture; + private TableRef _texture; + private TextureCreateInfo _info; + private float _scale; + + public void Set(TableRef texture, TextureCreateInfo info, float scale) + { + _texture = texture; + _info = info; + _scale = scale; + } + + public static void Run(ref CreateTextureCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + command._texture.Get(threaded).Base = renderer.CreateTexture(command._info, command._scale); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/GetCapabilitiesCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/GetCapabilitiesCommand.cs new file mode 100644 index 00000000..102ed9da --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/GetCapabilitiesCommand.cs @@ -0,0 +1,20 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer +{ + struct GetCapabilitiesCommand : IGALCommand + { + public CommandType CommandType => CommandType.GetCapabilities; + private TableRef> _result; + + public void Set(TableRef> result) + { + _result = result; + } + + public static void Run(ref GetCapabilitiesCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + command._result.Get(threaded).Result = renderer.GetCapabilities(); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/PreFrameCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/PreFrameCommand.cs new file mode 100644 index 00000000..67cafd18 --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/PreFrameCommand.cs @@ -0,0 +1,14 @@ +using System; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer +{ + struct PreFrameCommand : IGALCommand + { + public CommandType CommandType => CommandType.PreFrame; + + public static void Run(ref PreFrameCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.PreFrame(); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/ReportCounterCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/ReportCounterCommand.cs new file mode 100644 index 00000000..d477f235 --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/ReportCounterCommand.cs @@ -0,0 +1,30 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; +using System; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer +{ + struct ReportCounterCommand : IGALCommand + { + public CommandType CommandType => CommandType.ReportCounter; + private TableRef _event; + private CounterType _type; + private TableRef> _resultHandler; + private bool _hostReserved; + + public void Set(TableRef evt, CounterType type, TableRef> resultHandler, bool hostReserved) + { + _event = evt; + _type = type; + _resultHandler = resultHandler; + _hostReserved = hostReserved; + } + + public static void Run(ref ReportCounterCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + ThreadedCounterEvent evt = command._event.Get(threaded); + + evt.Create(renderer, command._type, command._resultHandler.Get(threaded), command._hostReserved); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/ResetCounterCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/ResetCounterCommand.cs new file mode 100644 index 00000000..2835bf31 --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/ResetCounterCommand.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer +{ + struct ResetCounterCommand : IGALCommand + { + public CommandType CommandType => CommandType.ResetCounter; + private CounterType _type; + + public void Set(CounterType type) + { + _type = type; + } + + public static void Run(ref ResetCounterCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.ResetCounter(command._type); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/UpdateCountersCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/UpdateCountersCommand.cs new file mode 100644 index 00000000..f28bf080 --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/UpdateCountersCommand.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer +{ + struct UpdateCountersCommand : IGALCommand + { + public CommandType CommandType => CommandType.UpdateCounters; + + public static void Run(ref UpdateCountersCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.UpdateCounters(); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/Sampler/SamplerDisposeCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/Sampler/SamplerDisposeCommand.cs new file mode 100644 index 00000000..8f4dfb7e --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/Sampler/SamplerDisposeCommand.cs @@ -0,0 +1,21 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Sampler +{ + struct SamplerDisposeCommand : IGALCommand + { + public CommandType CommandType => CommandType.SamplerDispose; + private TableRef _sampler; + + public void Set(TableRef sampler) + { + _sampler = sampler; + } + + public static void Run(ref SamplerDisposeCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + command._sampler.Get(threaded).Base.Dispose(); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/SetAlphaTestCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/SetAlphaTestCommand.cs new file mode 100644 index 00000000..89379387 --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/SetAlphaTestCommand.cs @@ -0,0 +1,22 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct SetAlphaTestCommand : IGALCommand + { + public CommandType CommandType => CommandType.SetAlphaTest; + private bool _enable; + private float _reference; + private CompareOp _op; + + public void Set(bool enable, float reference, CompareOp op) + { + _enable = enable; + _reference = reference; + _op = op; + } + + public static void Run(ref SetAlphaTestCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.SetAlphaTest(command._enable, command._reference, command._op); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/SetBlendStateCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/SetBlendStateCommand.cs new file mode 100644 index 00000000..6cc4894e --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/SetBlendStateCommand.cs @@ -0,0 +1,20 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct SetBlendStateCommand : IGALCommand + { + public CommandType CommandType => CommandType.SetBlendState; + private int _index; + private BlendDescriptor _blend; + + public void Set(int index, BlendDescriptor blend) + { + _index = index; + _blend = blend; + } + + public static void Run(ref SetBlendStateCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.SetBlendState(command._index, command._blend); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/SetDepthBiasCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/SetDepthBiasCommand.cs new file mode 100644 index 00000000..352242a3 --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/SetDepthBiasCommand.cs @@ -0,0 +1,24 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct SetDepthBiasCommand : IGALCommand + { + public CommandType CommandType => CommandType.SetDepthBias; + private PolygonModeMask _enables; + private float _factor; + private float _units; + private float _clamp; + + public void Set(PolygonModeMask enables, float factor, float units, float clamp) + { + _enables = enables; + _factor = factor; + _units = units; + _clamp = clamp; + } + + public static void Run(ref SetDepthBiasCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.SetDepthBias(command._enables, command._factor, command._units, command._clamp); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/SetDepthClampCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/SetDepthClampCommand.cs new file mode 100644 index 00000000..21c8f3e6 --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/SetDepthClampCommand.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct SetDepthClampCommand : IGALCommand + { + public CommandType CommandType => CommandType.SetDepthClamp; + private bool _clamp; + + public void Set(bool clamp) + { + _clamp = clamp; + } + + public static void Run(ref SetDepthClampCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.SetDepthClamp(command._clamp); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/SetDepthModeCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/SetDepthModeCommand.cs new file mode 100644 index 00000000..28c36be8 --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/SetDepthModeCommand.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct SetDepthModeCommand : IGALCommand + { + public CommandType CommandType => CommandType.SetDepthMode; + private DepthMode _mode; + + public void Set(DepthMode mode) + { + _mode = mode; + } + + public static void Run(ref SetDepthModeCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.SetDepthMode(command._mode); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/SetDepthTestCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/SetDepthTestCommand.cs new file mode 100644 index 00000000..585d3e8b --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/SetDepthTestCommand.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct SetDepthTestCommand : IGALCommand + { + public CommandType CommandType => CommandType.SetDepthTest; + private DepthTestDescriptor _depthTest; + + public void Set(DepthTestDescriptor depthTest) + { + _depthTest = depthTest; + } + + public static void Run(ref SetDepthTestCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.SetDepthTest(command._depthTest); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/SetFaceCullingCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/SetFaceCullingCommand.cs new file mode 100644 index 00000000..2a2b41ca --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/SetFaceCullingCommand.cs @@ -0,0 +1,20 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct SetFaceCullingCommand : IGALCommand + { + public CommandType CommandType => CommandType.SetFaceCulling; + private bool _enable; + private Face _face; + + public void Set(bool enable, Face face) + { + _enable = enable; + _face = face; + } + + public static void Run(ref SetFaceCullingCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.SetFaceCulling(command._enable, command._face); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/SetFrontFaceCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/SetFrontFaceCommand.cs new file mode 100644 index 00000000..a415237f --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/SetFrontFaceCommand.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct SetFrontFaceCommand : IGALCommand + { + public CommandType CommandType => CommandType.SetFrontFace; + private FrontFace _frontFace; + + public void Set(FrontFace frontFace) + { + _frontFace = frontFace; + } + + public static void Run(ref SetFrontFaceCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.SetFrontFace(command._frontFace); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/SetImageCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/SetImageCommand.cs new file mode 100644 index 00000000..4223a621 --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/SetImageCommand.cs @@ -0,0 +1,25 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct SetImageCommand : IGALCommand + { + public CommandType CommandType => CommandType.SetImage; + private int _binding; + private TableRef _texture; + private Format _imageFormat; + + public void Set(int binding, TableRef texture, Format imageFormat) + { + _binding = binding; + _texture = texture; + _imageFormat = imageFormat; + } + + public static void Run(ref SetImageCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.SetImage(command._binding, command._texture.GetAs(threaded)?.Base, command._imageFormat); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/SetIndexBufferCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/SetIndexBufferCommand.cs new file mode 100644 index 00000000..753e21f9 --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/SetIndexBufferCommand.cs @@ -0,0 +1,21 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct SetIndexBufferCommand : IGALCommand + { + public CommandType CommandType => CommandType.SetIndexBuffer; + private BufferRange _buffer; + private IndexType _type; + + public void Set(BufferRange buffer, IndexType type) + { + _buffer = buffer; + _type = type; + } + + public static void Run(ref SetIndexBufferCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + BufferRange range = threaded.Buffers.MapBufferRange(command._buffer); + renderer.Pipeline.SetIndexBuffer(range, command._type); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/SetLineParametersCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/SetLineParametersCommand.cs new file mode 100644 index 00000000..7fd2e5b1 --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/SetLineParametersCommand.cs @@ -0,0 +1,20 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct SetLineParametersCommand : IGALCommand + { + public CommandType CommandType => CommandType.SetLineParameters; + private float _width; + private bool _smooth; + + public void Set(float width, bool smooth) + { + _width = width; + _smooth = smooth; + } + + public static void Run(ref SetLineParametersCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.SetLineParameters(command._width, command._smooth); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/SetLogicOpStateCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/SetLogicOpStateCommand.cs new file mode 100644 index 00000000..253ef138 --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/SetLogicOpStateCommand.cs @@ -0,0 +1,20 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct SetLogicOpStateCommand : IGALCommand + { + public CommandType CommandType => CommandType.SetLogicOpState; + private bool _enable; + private LogicalOp _op; + + public void Set(bool enable, LogicalOp op) + { + _enable = enable; + _op = op; + } + + public static void Run(ref SetLogicOpStateCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.SetLogicOpState(command._enable, command._op); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/SetPointParametersCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/SetPointParametersCommand.cs new file mode 100644 index 00000000..37833a0e --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/SetPointParametersCommand.cs @@ -0,0 +1,24 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct SetPointParametersCommand : IGALCommand + { + public CommandType CommandType => CommandType.SetPointParameters; + private float _size; + private bool _isProgramPointSize; + private bool _enablePointSprite; + private Origin _origin; + + public void Set(float size, bool isProgramPointSize, bool enablePointSprite, Origin origin) + { + _size = size; + _isProgramPointSize = isProgramPointSize; + _enablePointSprite = enablePointSprite; + _origin = origin; + } + + public static void Run(ref SetPointParametersCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.SetPointParameters(command._size, command._isProgramPointSize, command._enablePointSprite, command._origin); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/SetPrimitiveRestartCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/SetPrimitiveRestartCommand.cs new file mode 100644 index 00000000..e5f6ecf3 --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/SetPrimitiveRestartCommand.cs @@ -0,0 +1,20 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct SetPrimitiveRestartCommand : IGALCommand + { + public CommandType CommandType => CommandType.SetPrimitiveRestart; + private bool _enable; + private int _index; + + public void Set(bool enable, int index) + { + _enable = enable; + _index = index; + } + + public static void Run(ref SetPrimitiveRestartCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.SetPrimitiveRestart(command._enable, command._index); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/SetPrimitiveTopologyCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/SetPrimitiveTopologyCommand.cs new file mode 100644 index 00000000..0bf29260 --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/SetPrimitiveTopologyCommand.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct SetPrimitiveTopologyCommand : IGALCommand + { + public CommandType CommandType => CommandType.SetPrimitiveTopology; + private PrimitiveTopology _topology; + + public void Set(PrimitiveTopology topology) + { + _topology = topology; + } + + public static void Run(ref SetPrimitiveTopologyCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.SetPrimitiveTopology(command._topology); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/SetProgramCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/SetProgramCommand.cs new file mode 100644 index 00000000..c35d9c1f --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/SetProgramCommand.cs @@ -0,0 +1,25 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct SetProgramCommand : IGALCommand + { + public CommandType CommandType => CommandType.SetProgram; + private TableRef _program; + + public void Set(TableRef program) + { + _program = program; + } + + public static void Run(ref SetProgramCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + ThreadedProgram program = command._program.GetAs(threaded); + + threaded.Programs.WaitForProgram(program); + + renderer.Pipeline.SetProgram(program.Base); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/SetRasterizerDiscardCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/SetRasterizerDiscardCommand.cs new file mode 100644 index 00000000..4f92ce99 --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/SetRasterizerDiscardCommand.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct SetRasterizerDiscardCommand : IGALCommand + { + public CommandType CommandType => CommandType.SetRasterizerDiscard; + private bool _discard; + + public void Set(bool discard) + { + _discard = discard; + } + + public static void Run(ref SetRasterizerDiscardCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.SetRasterizerDiscard(command._discard); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/SetRenderTargetColorMasksCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/SetRenderTargetColorMasksCommand.cs new file mode 100644 index 00000000..1e75ddb8 --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/SetRenderTargetColorMasksCommand.cs @@ -0,0 +1,23 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using System; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct SetRenderTargetColorMasksCommand : IGALCommand + { + public CommandType CommandType => CommandType.SetRenderTargetColorMasks; + private SpanRef _componentMask; + + public void Set(SpanRef componentMask) + { + _componentMask = componentMask; + } + + public static void Run(ref SetRenderTargetColorMasksCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + ReadOnlySpan componentMask = command._componentMask.Get(threaded); + renderer.Pipeline.SetRenderTargetColorMasks(componentMask); + command._componentMask.Dispose(threaded); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/SetRenderTargetScaleCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/SetRenderTargetScaleCommand.cs new file mode 100644 index 00000000..a97a63db --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/SetRenderTargetScaleCommand.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct SetRenderTargetScaleCommand : IGALCommand + { + public CommandType CommandType => CommandType.SetRenderTargetScale; + private float _scale; + + public void Set(float scale) + { + _scale = scale; + } + + public static void Run(ref SetRenderTargetScaleCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.SetRenderTargetScale(command._scale); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/SetRenderTargetsCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/SetRenderTargetsCommand.cs new file mode 100644 index 00000000..30f798dd --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/SetRenderTargetsCommand.cs @@ -0,0 +1,24 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; +using System.Linq; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct SetRenderTargetsCommand : IGALCommand + { + public CommandType CommandType => CommandType.SetRenderTargets; + private TableRef _colors; + private TableRef _depthStencil; + + public void Set(TableRef colors, TableRef depthStencil) + { + _colors = colors; + _depthStencil = depthStencil; + } + + public static void Run(ref SetRenderTargetsCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.SetRenderTargets(command._colors.Get(threaded).Select(color => ((ThreadedTexture)color)?.Base).ToArray(), command._depthStencil.GetAs(threaded)?.Base); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/SetSamplerCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/SetSamplerCommand.cs new file mode 100644 index 00000000..f3be24db --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/SetSamplerCommand.cs @@ -0,0 +1,23 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct SetSamplerCommand : IGALCommand + { + public CommandType CommandType => CommandType.SetSampler; + private int _index; + private TableRef _sampler; + + public void Set(int index, TableRef sampler) + { + _index = index; + _sampler = sampler; + } + + public static void Run(ref SetSamplerCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.SetSampler(command._index, command._sampler.GetAs(threaded)?.Base); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/SetScissorCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/SetScissorCommand.cs new file mode 100644 index 00000000..6c95d096 --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/SetScissorCommand.cs @@ -0,0 +1,28 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct SetScissorCommand : IGALCommand + { + public CommandType CommandType => CommandType.SetScissor; + private int _index; + private bool _enable; + private int _x; + private int _y; + private int _width; + private int _height; + + public void Set(int index, bool enable, int x, int y, int width, int height) + { + _index = index; + _enable = enable; + _x = x; + _y = y; + _width = width; + _height = height; + } + + public static void Run(ref SetScissorCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.SetScissor(command._index, command._enable, command._x, command._y, command._width, command._height); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/SetStencilTestCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/SetStencilTestCommand.cs new file mode 100644 index 00000000..cc5db4df --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/SetStencilTestCommand.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct SetStencilTestCommand : IGALCommand + { + public CommandType CommandType => CommandType.SetStencilTest; + private StencilTestDescriptor _stencilTest; + + public void Set(StencilTestDescriptor stencilTest) + { + _stencilTest = stencilTest; + } + + public static void Run(ref SetStencilTestCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.SetStencilTest(command._stencilTest); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/SetStorageBuffersCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/SetStorageBuffersCommand.cs new file mode 100644 index 00000000..c2963373 --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/SetStorageBuffersCommand.cs @@ -0,0 +1,25 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using System; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct SetStorageBuffersCommand : IGALCommand + { + public CommandType CommandType => CommandType.SetStorageBuffers; + private int _first; + private SpanRef _buffers; + + public void Set(int first, SpanRef buffers) + { + _first = first; + _buffers = buffers; + } + + public static void Run(ref SetStorageBuffersCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + Span buffers = command._buffers.Get(threaded); + renderer.Pipeline.SetStorageBuffers(command._first, threaded.Buffers.MapBufferRanges(buffers)); + command._buffers.Dispose(threaded); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/SetTextureCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/SetTextureCommand.cs new file mode 100644 index 00000000..e86f512b --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/SetTextureCommand.cs @@ -0,0 +1,23 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct SetTextureCommand : IGALCommand + { + public CommandType CommandType => CommandType.SetTexture; + private int _binding; + private TableRef _texture; + + public void Set(int binding, TableRef texture) + { + _binding = binding; + _texture = texture; + } + + public static void Run(ref SetTextureCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.SetTexture(command._binding, command._texture.GetAs(threaded)?.Base); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/SetTransformFeedbackBuffersCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/SetTransformFeedbackBuffersCommand.cs new file mode 100644 index 00000000..5125447c --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/SetTransformFeedbackBuffersCommand.cs @@ -0,0 +1,23 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using System; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct SetTransformFeedbackBuffersCommand : IGALCommand + { + public CommandType CommandType => CommandType.SetTransformFeedbackBuffers; + private SpanRef _buffers; + + public void Set(SpanRef buffers) + { + _buffers = buffers; + } + + public static void Run(ref SetTransformFeedbackBuffersCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + Span buffers = command._buffers.Get(threaded); + renderer.Pipeline.SetTransformFeedbackBuffers(threaded.Buffers.MapBufferRanges(buffers)); + command._buffers.Dispose(threaded); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/SetUniformBuffersCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/SetUniformBuffersCommand.cs new file mode 100644 index 00000000..750d8dac --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/SetUniformBuffersCommand.cs @@ -0,0 +1,25 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using System; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct SetUniformBuffersCommand : IGALCommand + { + public CommandType CommandType => CommandType.SetUniformBuffers; + private int _first; + private SpanRef _buffers; + + public void Set(int first, SpanRef buffers) + { + _first = first; + _buffers = buffers; + } + + public static void Run(ref SetUniformBuffersCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + Span buffers = command._buffers.Get(threaded); + renderer.Pipeline.SetUniformBuffers(command._first, threaded.Buffers.MapBufferRanges(buffers)); + command._buffers.Dispose(threaded); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/SetUserClipDistanceCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/SetUserClipDistanceCommand.cs new file mode 100644 index 00000000..f0f05779 --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/SetUserClipDistanceCommand.cs @@ -0,0 +1,20 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct SetUserClipDistanceCommand : IGALCommand + { + public CommandType CommandType => CommandType.SetUserClipDistance; + private int _index; + private bool _enableClip; + + public void Set(int index, bool enableClip) + { + _index = index; + _enableClip = enableClip; + } + + public static void Run(ref SetUserClipDistanceCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.SetUserClipDistance(command._index, command._enableClip); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/SetVertexAttribsCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/SetVertexAttribsCommand.cs new file mode 100644 index 00000000..cbc313e9 --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/SetVertexAttribsCommand.cs @@ -0,0 +1,23 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using System; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct SetVertexAttribsCommand : IGALCommand + { + public CommandType CommandType => CommandType.SetVertexAttribs; + private SpanRef _vertexAttribs; + + public void Set(SpanRef vertexAttribs) + { + _vertexAttribs = vertexAttribs; + } + + public static void Run(ref SetVertexAttribsCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + ReadOnlySpan vertexAttribs = command._vertexAttribs.Get(threaded); + renderer.Pipeline.SetVertexAttribs(vertexAttribs); + command._vertexAttribs.Dispose(threaded); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/SetVertexBuffersCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/SetVertexBuffersCommand.cs new file mode 100644 index 00000000..bdaba2c9 --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/SetVertexBuffersCommand.cs @@ -0,0 +1,24 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using System; +using System.Buffers; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct SetVertexBuffersCommand : IGALCommand + { + public CommandType CommandType => CommandType.SetVertexBuffers; + private SpanRef _vertexBuffers; + + public void Set(SpanRef vertexBuffers) + { + _vertexBuffers = vertexBuffers; + } + + public static void Run(ref SetVertexBuffersCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + Span vertexBuffers = command._vertexBuffers.Get(threaded); + renderer.Pipeline.SetVertexBuffers(threaded.Buffers.MapBufferRanges(vertexBuffers)); + command._vertexBuffers.Dispose(threaded); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/SetViewportsCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/SetViewportsCommand.cs new file mode 100644 index 00000000..e11b00e8 --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/SetViewportsCommand.cs @@ -0,0 +1,26 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using System; +using System.Buffers; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct SetViewportsCommand : IGALCommand + { + public CommandType CommandType => CommandType.SetViewports; + private int _first; + private SpanRef _viewports; + + public void Set(int first, SpanRef viewports) + { + _first = first; + _viewports = viewports; + } + + public static void Run(ref SetViewportsCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + ReadOnlySpan viewports = command._viewports.Get(threaded); + renderer.Pipeline.SetViewports(command._first, viewports); + command._viewports.Dispose(threaded); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/Shader/ShaderDisposeCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/Shader/ShaderDisposeCommand.cs new file mode 100644 index 00000000..ebb2c927 --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/Shader/ShaderDisposeCommand.cs @@ -0,0 +1,21 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Shader +{ + struct ShaderDisposeCommand : IGALCommand + { + public CommandType CommandType => CommandType.ShaderDispose; + private TableRef _shader; + + public void Set(TableRef shader) + { + _shader = shader; + } + + public static void Run(ref ShaderDisposeCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + command._shader.Get(threaded).Base.Dispose(); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureCopyToCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureCopyToCommand.cs new file mode 100644 index 00000000..112c1fd1 --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureCopyToCommand.cs @@ -0,0 +1,28 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture +{ + struct TextureCopyToCommand : IGALCommand + { + public CommandType CommandType => CommandType.TextureCopyTo; + private TableRef _texture; + private TableRef _destination; + private int _firstLayer; + private int _firstLevel; + + public void Set(TableRef texture, TableRef destination, int firstLayer, int firstLevel) + { + _texture = texture; + _destination = destination; + _firstLayer = firstLayer; + _firstLevel = firstLevel; + } + + public static void Run(ref TextureCopyToCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + ThreadedTexture source = command._texture.Get(threaded); + source.Base.CopyTo(command._destination.Get(threaded).Base, command._firstLayer, command._firstLevel); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureCopyToScaledCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureCopyToScaledCommand.cs new file mode 100644 index 00000000..11843361 --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureCopyToScaledCommand.cs @@ -0,0 +1,30 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture +{ + struct TextureCopyToScaledCommand : IGALCommand + { + public CommandType CommandType => CommandType.TextureCopyToScaled; + private TableRef _texture; + private TableRef _destination; + private Extents2D _srcRegion; + private Extents2D _dstRegion; + private bool _linearFilter; + + public void Set(TableRef texture, TableRef destination, Extents2D srcRegion, Extents2D dstRegion, bool linearFilter) + { + _texture = texture; + _destination = destination; + _srcRegion = srcRegion; + _dstRegion = dstRegion; + _linearFilter = linearFilter; + } + + public static void Run(ref TextureCopyToScaledCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + ThreadedTexture source = command._texture.Get(threaded); + source.Base.CopyTo(command._destination.Get(threaded).Base, command._srcRegion, command._dstRegion, command._linearFilter); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureCopyToSliceCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureCopyToSliceCommand.cs new file mode 100644 index 00000000..363edb00 --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureCopyToSliceCommand.cs @@ -0,0 +1,32 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture +{ + struct TextureCopyToSliceCommand : IGALCommand + { + public CommandType CommandType => CommandType.TextureCopyToSlice; + private TableRef _texture; + private TableRef _destination; + private int _srcLayer; + private int _dstLayer; + private int _srcLevel; + private int _dstLevel; + + public void Set(TableRef texture, TableRef destination, int srcLayer, int dstLayer, int srcLevel, int dstLevel) + { + _texture = texture; + _destination = destination; + _srcLayer = srcLayer; + _dstLayer = dstLayer; + _srcLevel = srcLevel; + _dstLevel = dstLevel; + } + + public static void Run(ref TextureCopyToSliceCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + ThreadedTexture source = command._texture.Get(threaded); + source.Base.CopyTo(command._destination.Get(threaded).Base, command._srcLayer, command._dstLayer, command._srcLevel, command._dstLevel); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureCreateViewCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureCreateViewCommand.cs new file mode 100644 index 00000000..7c385407 --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureCreateViewCommand.cs @@ -0,0 +1,30 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture +{ + struct TextureCreateViewCommand : IGALCommand + { + public CommandType CommandType => CommandType.TextureCreateView; + private TableRef _texture; + private TableRef _destination; + private TextureCreateInfo _info; + private int _firstLayer; + private int _firstLevel; + + public void Set(TableRef texture, TableRef destination, TextureCreateInfo info, int firstLayer, int firstLevel) + { + _texture = texture; + _destination = destination; + _info = info; + _firstLayer = firstLayer; + _firstLevel = firstLevel; + } + + public static void Run(ref TextureCreateViewCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + ThreadedTexture source = command._texture.Get(threaded); + command._destination.Get(threaded).Base = source.Base.CreateView(command._info, command._firstLayer, command._firstLevel); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureGetDataCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureGetDataCommand.cs new file mode 100644 index 00000000..9e7d0c64 --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureGetDataCommand.cs @@ -0,0 +1,26 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; +using System; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture +{ + struct TextureGetDataCommand : IGALCommand + { + public CommandType CommandType => CommandType.TextureGetData; + private TableRef _texture; + private TableRef>> _result; + + public void Set(TableRef texture, TableRef>> result) + { + _texture = texture; + _result = result; + } + + public static void Run(ref TextureGetDataCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + ReadOnlySpan result = command._texture.Get(threaded).Base.GetData(); + + command._result.Get(threaded).Result = new PinnedSpan(result); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureReleaseCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureReleaseCommand.cs new file mode 100644 index 00000000..591b2214 --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureReleaseCommand.cs @@ -0,0 +1,21 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture +{ + struct TextureReleaseCommand : IGALCommand + { + public CommandType CommandType => CommandType.TextureRelease; + private TableRef _texture; + + public void Set(TableRef texture) + { + _texture = texture; + } + + public static void Run(ref TextureReleaseCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + command._texture.Get(threaded).Base.Release(); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureSetDataCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureSetDataCommand.cs new file mode 100644 index 00000000..a8a6d274 --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureSetDataCommand.cs @@ -0,0 +1,25 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; +using System; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture +{ + struct TextureSetDataCommand : IGALCommand + { + public CommandType CommandType => CommandType.TextureSetData; + private TableRef _texture; + private TableRef _data; + + public void Set(TableRef texture, TableRef data) + { + _texture = texture; + _data = data; + } + + public static void Run(ref TextureSetDataCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + ThreadedTexture texture = command._texture.Get(threaded); + texture.Base.SetData(new ReadOnlySpan(command._data.Get(threaded))); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureSetDataSliceCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureSetDataSliceCommand.cs new file mode 100644 index 00000000..0179ff11 --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureSetDataSliceCommand.cs @@ -0,0 +1,29 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; +using System; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture +{ + struct TextureSetDataSliceCommand : IGALCommand + { + public CommandType CommandType => CommandType.TextureSetDataSlice; + private TableRef _texture; + private TableRef _data; + private int _layer; + private int _level; + + public void Set(TableRef texture, TableRef data, int layer, int level) + { + _texture = texture; + _data = data; + _layer = layer; + _level = level; + } + + public static void Run(ref TextureSetDataSliceCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + ThreadedTexture texture = command._texture.Get(threaded); + texture.Base.SetData(new ReadOnlySpan(command._data.Get(threaded)), command._layer, command._level); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureSetStorageCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureSetStorageCommand.cs new file mode 100644 index 00000000..f86a9c44 --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureSetStorageCommand.cs @@ -0,0 +1,23 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture +{ + struct TextureSetStorageCommand : IGALCommand + { + public CommandType CommandType => CommandType.TextureSetStorage; + private TableRef _texture; + private BufferRange _storage; + + public void Set(TableRef texture, BufferRange storage) + { + _texture = texture; + _storage = storage; + } + + public static void Run(ref TextureSetStorageCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + command._texture.Get(threaded).Base.SetStorage(threaded.Buffers.MapBufferRange(command._storage)); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/TextureBarrierCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/TextureBarrierCommand.cs new file mode 100644 index 00000000..b0b46021 --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/TextureBarrierCommand.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct TextureBarrierCommand : IGALCommand + { + public CommandType CommandType => CommandType.TextureBarrier; + + public static void Run(ref TextureBarrierCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.TextureBarrier(); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/TextureBarrierTiledCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/TextureBarrierTiledCommand.cs new file mode 100644 index 00000000..f92abe5b --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/TextureBarrierTiledCommand.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct TextureBarrierTiledCommand : IGALCommand + { + public CommandType CommandType => CommandType.TextureBarrierTiled; + + public static void Run(ref TextureBarrierTiledCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.TextureBarrierTiled(); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/TryHostConditionalRenderingCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/TryHostConditionalRenderingCommand.cs new file mode 100644 index 00000000..65e1748d --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/TryHostConditionalRenderingCommand.cs @@ -0,0 +1,25 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct TryHostConditionalRenderingCommand : IGALCommand + { + public CommandType CommandType => CommandType.TryHostConditionalRendering; + private TableRef _value; + private ulong _compare; + private bool _isEqual; + + public void Set(TableRef value, ulong compare, bool isEqual) + { + _value = value; + _compare = compare; + _isEqual = isEqual; + } + + public static void Run(ref TryHostConditionalRenderingCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.TryHostConditionalRendering(command._value.Get(threaded)?.Base, command._compare, command._isEqual); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/TryHostConditionalRenderingFlushCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/TryHostConditionalRenderingFlushCommand.cs new file mode 100644 index 00000000..29eb8dd4 --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/TryHostConditionalRenderingFlushCommand.cs @@ -0,0 +1,25 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct TryHostConditionalRenderingFlushCommand : IGALCommand + { + public CommandType CommandType => CommandType.TryHostConditionalRenderingFlush; + private TableRef _value; + private TableRef _compare; + private bool _isEqual; + + public void Set(TableRef value, TableRef compare, bool isEqual) + { + _value = value; + _compare = compare; + _isEqual = isEqual; + } + + public static void Run(ref TryHostConditionalRenderingFlushCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.TryHostConditionalRendering(command._value.Get(threaded)?.Base, command._compare.Get(threaded)?.Base, command._isEqual); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/UpdateRenderScaleCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/UpdateRenderScaleCommand.cs new file mode 100644 index 00000000..fafb52a8 --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/UpdateRenderScaleCommand.cs @@ -0,0 +1,28 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.Shader; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct UpdateRenderScaleCommand : IGALCommand + { + public CommandType CommandType => CommandType.UpdateRenderScale; + private ShaderStage _stage; + private SpanRef _scales; + private int _textureCount; + private int _imageCount; + + public void Set(ShaderStage stage, SpanRef scales, int textureCount, int imageCount) + { + _stage = stage; + _scales = scales; + _textureCount = textureCount; + _imageCount = imageCount; + } + + public static void Run(ref UpdateRenderScaleCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.UpdateRenderScale(command._stage, command._scales.Get(threaded), command._textureCount, command._imageCount); + command._scales.Dispose(threaded); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/Window/WindowPresentCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/Window/WindowPresentCommand.cs new file mode 100644 index 00000000..c4f3b553 --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/Window/WindowPresentCommand.cs @@ -0,0 +1,27 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; +using System; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Window +{ + struct WindowPresentCommand : IGALCommand + { + public CommandType CommandType => CommandType.WindowPresent; + private TableRef _texture; + private ImageCrop _crop; + private TableRef _swapBuffersCallback; + + public void Set(TableRef texture, ImageCrop crop, TableRef swapBuffersCallback) + { + _texture = texture; + _crop = crop; + _swapBuffersCallback = swapBuffersCallback; + } + + public static void Run(ref WindowPresentCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + threaded.SignalFrame(); + renderer.Window.Present(command._texture.Get(threaded)?.Base, command._crop, command._swapBuffersCallback.Get(threaded)); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Model/CircularSpanPool.cs b/Ryujinx.Graphics.GAL/Multithreading/Model/CircularSpanPool.cs new file mode 100644 index 00000000..4ea1a2c7 --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Model/CircularSpanPool.cs @@ -0,0 +1,89 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Threading; + +namespace Ryujinx.Graphics.GAL.Multithreading.Model +{ + /// + /// A memory pool for passing through Span resources with one producer and consumer. + /// Data is copied on creation to part of the pool, then that region is reserved until it is disposed by the consumer. + /// Similar to the command queue, this pool assumes that data is created and disposed in the same order. + /// + class CircularSpanPool + { + private ThreadedRenderer _renderer; + private byte[] _pool; + private int _size; + + private int _producerPtr; + private int _producerSkipPosition = -1; + private int _consumerPtr; + + public CircularSpanPool(ThreadedRenderer renderer, int size) + { + _renderer = renderer; + _size = size; + _pool = new byte[size]; + } + + public SpanRef Insert(ReadOnlySpan data) where T : unmanaged + { + int size = data.Length * Unsafe.SizeOf(); + + // Wrapping aware circular queue. + // If there's no space at the end of the pool for this span, we can't fragment it. + // So just loop back around to the start. Remember the last skipped position. + + bool wraparound = _producerPtr + size >= _size; + int index = wraparound ? 0 : _producerPtr; + + // _consumerPtr is from another thread, and we're taking it without a lock, so treat this as a snapshot in the past. + // We know that it will always be before or equal to the producer pointer, and it cannot pass it. + // This is enough to reason about if there is space in the queue for the data, even if we're checking against an outdated value. + + int consumer = _consumerPtr; + bool beforeConsumer = _producerPtr < consumer; + + if (size > _size - 1 || (wraparound && beforeConsumer) || ((index < consumer || wraparound) && index + size >= consumer)) + { + // Just get an array in the following situations: + // - The data is too large to fit in the pool. + // - A wraparound would happen but the consumer would be covered by it. + // - The producer would catch up to the consumer as a result. + + return new SpanRef(_renderer, data.ToArray()); + } + + data.CopyTo(MemoryMarshal.Cast(new Span(_pool).Slice(index, size))); + + if (wraparound) + { + _producerSkipPosition = _producerPtr; + } + + _producerPtr = index + size; + + return new SpanRef(data.Length); + } + + public Span Get(int length) where T : unmanaged + { + int size = length * Unsafe.SizeOf(); + + if (_consumerPtr == Interlocked.CompareExchange(ref _producerSkipPosition, -1, _consumerPtr)) + { + _consumerPtr = 0; + } + + return MemoryMarshal.Cast(new Span(_pool).Slice(_consumerPtr, size)); + } + + public void Dispose(int length) where T : unmanaged + { + int size = length * Unsafe.SizeOf(); + + _consumerPtr = _consumerPtr + size; + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Model/PinnedSpan.cs b/Ryujinx.Graphics.GAL/Multithreading/Model/PinnedSpan.cs new file mode 100644 index 00000000..16e148c2 --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Model/PinnedSpan.cs @@ -0,0 +1,23 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.GAL.Multithreading.Model +{ + unsafe struct PinnedSpan where T : unmanaged + { + private void* _ptr; + private int _size; + + public PinnedSpan(ReadOnlySpan span) + { + _ptr = Unsafe.AsPointer(ref MemoryMarshal.GetReference(span)); + _size = span.Length; + } + + public ReadOnlySpan Get() + { + return new ReadOnlySpan(_ptr, _size * Unsafe.SizeOf()); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Model/ResultBox.cs b/Ryujinx.Graphics.GAL/Multithreading/Model/ResultBox.cs new file mode 100644 index 00000000..7a0be785 --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Model/ResultBox.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Model +{ + public class ResultBox + { + public T Result; + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Model/SpanRef.cs b/Ryujinx.Graphics.GAL/Multithreading/Model/SpanRef.cs new file mode 100644 index 00000000..7dbebc76 --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Model/SpanRef.cs @@ -0,0 +1,39 @@ +using System; + +namespace Ryujinx.Graphics.GAL.Multithreading.Model +{ + struct SpanRef where T : unmanaged + { + private int _packedLengthId; + + public SpanRef(ThreadedRenderer renderer, T[] data) + { + _packedLengthId = -(renderer.AddTableRef(data) + 1); + } + + public SpanRef(int length) + { + _packedLengthId = length; + } + + public Span Get(ThreadedRenderer renderer) + { + if (_packedLengthId >= 0) + { + return renderer.SpanPool.Get(_packedLengthId); + } + else + { + return new Span((T[])renderer.RemoveTableRef(-(_packedLengthId + 1))); + } + } + + public void Dispose(ThreadedRenderer renderer) + { + if (_packedLengthId > 0) + { + renderer.SpanPool.Dispose(_packedLengthId); + } + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Model/TableRef.cs b/Ryujinx.Graphics.GAL/Multithreading/Model/TableRef.cs new file mode 100644 index 00000000..166aa71a --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Model/TableRef.cs @@ -0,0 +1,22 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Model +{ + struct TableRef + { + private int _index; + + public TableRef(ThreadedRenderer renderer, T reference) + { + _index = renderer.AddTableRef(reference); + } + + public T Get(ThreadedRenderer renderer) + { + return (T)renderer.RemoveTableRef(_index); + } + + public T2 GetAs(ThreadedRenderer renderer) where T2 : T + { + return (T2)renderer.RemoveTableRef(_index); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Resources/ProgramQueue.cs b/Ryujinx.Graphics.GAL/Multithreading/Resources/ProgramQueue.cs new file mode 100644 index 00000000..3f982d31 --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Resources/ProgramQueue.cs @@ -0,0 +1,107 @@ +using Ryujinx.Graphics.GAL.Multithreading.Resources.Programs; +using System; +using System.Collections.Generic; +using System.Threading; + +namespace Ryujinx.Graphics.GAL.Multithreading.Resources +{ + /// + /// A structure handling multithreaded compilation for programs. + /// + class ProgramQueue + { + private const int MaxConcurrentCompilations = 8; + + private IRenderer _renderer; + + private Queue _toCompile; + private List _inProgress; + + public ProgramQueue(IRenderer renderer) + { + _renderer = renderer; + + _toCompile = new Queue(); + _inProgress = new List(); + } + + public void Add(IProgramRequest request) + { + lock (_toCompile) + { + _toCompile.Enqueue(request); + } + } + + public void ProcessQueue() + { + for (int i = 0; i < _inProgress.Count; i++) + { + ThreadedProgram program = _inProgress[i]; + + ProgramLinkStatus status = program.Base.CheckProgramLink(false); + + if (status != ProgramLinkStatus.Incomplete) + { + program.Compiled = true; + _inProgress.RemoveAt(i--); + } + } + + int freeSpace = MaxConcurrentCompilations - _inProgress.Count; + + for (int i = 0; i < freeSpace; i++) + { + // Begin compilation of some programs in the compile queue. + IProgramRequest program; + + lock (_toCompile) + { + if (!_toCompile.TryDequeue(out program)) + { + break; + } + } + + if (program.Threaded.Base != null) + { + ProgramLinkStatus status = program.Threaded.Base.CheckProgramLink(false); + + if (status != ProgramLinkStatus.Incomplete) + { + // This program is already compiled. Keep going through the queue. + program.Threaded.Compiled = true; + i--; + continue; + } + } + else + { + program.Threaded.Base = program.Create(_renderer); + } + + _inProgress.Add(program.Threaded); + } + } + + /// + /// Process the queue until the given program has finished compiling. + /// This will begin compilation of other programs on the queue as well. + /// + /// The program to wait for + public void WaitForProgram(ThreadedProgram program) + { + Span spinWait = stackalloc SpinWait[1]; + + while (!program.Compiled) + { + ProcessQueue(); + + if (!program.Compiled) + { + spinWait[0].SpinOnce(-1); + } + } + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Resources/Programs/BinaryProgramRequest.cs b/Ryujinx.Graphics.GAL/Multithreading/Resources/Programs/BinaryProgramRequest.cs new file mode 100644 index 00000000..96bfedf8 --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Resources/Programs/BinaryProgramRequest.cs @@ -0,0 +1,21 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Resources.Programs +{ + class BinaryProgramRequest : IProgramRequest + { + public ThreadedProgram Threaded { get; set; } + + private byte[] _data; + + public BinaryProgramRequest(ThreadedProgram program, byte[] data) + { + Threaded = program; + + _data = data; + } + + public IProgram Create(IRenderer renderer) + { + return renderer.LoadProgramBinary(_data); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Resources/Programs/IProgramRequest.cs b/Ryujinx.Graphics.GAL/Multithreading/Resources/Programs/IProgramRequest.cs new file mode 100644 index 00000000..cdbfe03c --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Resources/Programs/IProgramRequest.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Resources.Programs +{ + interface IProgramRequest + { + ThreadedProgram Threaded { get; set; } + IProgram Create(IRenderer renderer); + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Resources/Programs/SourceProgramRequest.cs b/Ryujinx.Graphics.GAL/Multithreading/Resources/Programs/SourceProgramRequest.cs new file mode 100644 index 00000000..d40ce6a4 --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Resources/Programs/SourceProgramRequest.cs @@ -0,0 +1,32 @@ +using System.Linq; + +namespace Ryujinx.Graphics.GAL.Multithreading.Resources.Programs +{ + class SourceProgramRequest : IProgramRequest + { + public ThreadedProgram Threaded { get; set; } + + private IShader[] _shaders; + private TransformFeedbackDescriptor[] _transformFeedbackDescriptors; + + public SourceProgramRequest(ThreadedProgram program, IShader[] shaders, TransformFeedbackDescriptor[] transformFeedbackDescriptors) + { + Threaded = program; + + _shaders = shaders; + _transformFeedbackDescriptors = transformFeedbackDescriptors; + } + + public IProgram Create(IRenderer renderer) + { + IShader[] shaders = _shaders.Select(shader => + { + var threaded = (ThreadedShader)shader; + threaded?.EnsureCreated(); + return threaded?.Base; + }).ToArray(); + + return renderer.CreateProgram(shaders, _transformFeedbackDescriptors); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedCounterEvent.cs b/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedCounterEvent.cs new file mode 100644 index 00000000..4b7471d6 --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedCounterEvent.cs @@ -0,0 +1,80 @@ +using Ryujinx.Graphics.GAL.Multithreading.Commands.CounterEvent; +using Ryujinx.Graphics.GAL.Multithreading.Model; +using System.Threading; + +namespace Ryujinx.Graphics.GAL.Multithreading.Resources +{ + class ThreadedCounterEvent : ICounterEvent + { + private ThreadedRenderer _renderer; + public ICounterEvent Base; + + public bool Invalid { get; set; } + + public CounterType Type { get; } + public bool ClearCounter { get; } + + private bool _reserved; + private int _createLock; + + public ThreadedCounterEvent(ThreadedRenderer renderer, CounterType type, bool clearCounter) + { + _renderer = renderer; + Type = type; + ClearCounter = clearCounter; + } + + private TableRef Ref(T reference) + { + return new TableRef(_renderer, reference); + } + + public void Dispose() + { + _renderer.New().Set(Ref(this)); + _renderer.QueueCommand(); + } + + public void Flush() + { + ThreadedHelpers.SpinUntilNonNull(ref Base); + + Base.Flush(); + } + + public bool ReserveForHostAccess() + { + if (Base != null) + { + return Base.ReserveForHostAccess(); + } + else + { + bool result = true; + + // A very light lock, as this case is uncommon. + ThreadedHelpers.SpinUntilExchange(ref _createLock, 1, 0); + + if (Base != null) + { + result = Base.ReserveForHostAccess(); + } + else + { + _reserved = true; + } + + Volatile.Write(ref _createLock, 0); + + return result; + } + } + + public void Create(IRenderer renderer, CounterType type, System.EventHandler eventHandler, bool hostReserved) + { + ThreadedHelpers.SpinUntilExchange(ref _createLock, 1, 0); + Base = renderer.ReportCounter(type, eventHandler, hostReserved || _reserved); + Volatile.Write(ref _createLock, 0); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedProgram.cs b/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedProgram.cs new file mode 100644 index 00000000..068d058e --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedProgram.cs @@ -0,0 +1,48 @@ +using Ryujinx.Graphics.GAL.Multithreading.Commands.Program; +using Ryujinx.Graphics.GAL.Multithreading.Model; + +namespace Ryujinx.Graphics.GAL.Multithreading.Resources +{ + class ThreadedProgram : IProgram + { + private ThreadedRenderer _renderer; + + public IProgram Base; + + internal bool Compiled; + + public ThreadedProgram(ThreadedRenderer renderer) + { + _renderer = renderer; + } + + private TableRef Ref(T reference) + { + return new TableRef(_renderer, reference); + } + + public void Dispose() + { + _renderer.New().Set(Ref(this)); + _renderer.QueueCommand(); + } + + public byte[] GetBinary() + { + ResultBox box = new ResultBox(); + _renderer.New().Set(Ref(this), Ref(box)); + _renderer.InvokeCommand(); + + return box.Result; + } + + public ProgramLinkStatus CheckProgramLink(bool blocking) + { + ResultBox box = new ResultBox(); + _renderer.New().Set(Ref(this), blocking, Ref(box)); + _renderer.InvokeCommand(); + + return box.Result; + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedSampler.cs b/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedSampler.cs new file mode 100644 index 00000000..d8de9a70 --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedSampler.cs @@ -0,0 +1,22 @@ +using Ryujinx.Graphics.GAL.Multithreading.Commands.Sampler; +using Ryujinx.Graphics.GAL.Multithreading.Model; + +namespace Ryujinx.Graphics.GAL.Multithreading.Resources +{ + class ThreadedSampler : ISampler + { + private ThreadedRenderer _renderer; + public ISampler Base; + + public ThreadedSampler(ThreadedRenderer renderer) + { + _renderer = renderer; + } + + public void Dispose() + { + _renderer.New().Set(new TableRef(_renderer, this)); + _renderer.QueueCommand(); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedShader.cs b/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedShader.cs new file mode 100644 index 00000000..dcbecf38 --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedShader.cs @@ -0,0 +1,38 @@ +using Ryujinx.Graphics.GAL.Multithreading.Commands.Shader; +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.Shader; + +namespace Ryujinx.Graphics.GAL.Multithreading.Resources +{ + class ThreadedShader : IShader + { + private ThreadedRenderer _renderer; + private ShaderStage _stage; + private string _code; + + public IShader Base; + + public ThreadedShader(ThreadedRenderer renderer, ShaderStage stage, string code) + { + _renderer = renderer; + + _stage = stage; + _code = code; + } + + internal void EnsureCreated() + { + if (_code != null && Base == null) + { + Base = _renderer.BaseRenderer.CompileShader(_stage, _code); + _code = null; + } + } + + public void Dispose() + { + _renderer.New().Set(new TableRef(_renderer, this)); + _renderer.QueueCommand(); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedTexture.cs b/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedTexture.cs new file mode 100644 index 00000000..634d32fa --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedTexture.cs @@ -0,0 +1,116 @@ +using Ryujinx.Graphics.GAL.Multithreading.Commands.Texture; +using Ryujinx.Graphics.GAL.Multithreading.Model; +using System; + +namespace Ryujinx.Graphics.GAL.Multithreading.Resources +{ + /// + /// Threaded representation of a texture. + /// + class ThreadedTexture : ITexture + { + private ThreadedRenderer _renderer; + private TextureCreateInfo _info; + public ITexture Base; + + public int Width => _info.Width; + + public int Height => _info.Height; + + public float ScaleFactor { get; } + + public ThreadedTexture(ThreadedRenderer renderer, TextureCreateInfo info, float scale) + { + _renderer = renderer; + _info = info; + ScaleFactor = scale; + } + + private TableRef Ref(T reference) + { + return new TableRef(_renderer, reference); + } + + public void CopyTo(ITexture destination, int firstLayer, int firstLevel) + { + _renderer.New().Set(Ref(this), Ref((ThreadedTexture)destination), firstLayer, firstLevel); + _renderer.QueueCommand(); + } + + public void CopyTo(ITexture destination, int srcLayer, int dstLayer, int srcLevel, int dstLevel) + { + _renderer.New().Set(Ref(this), Ref((ThreadedTexture)destination), srcLayer, dstLayer, srcLevel, dstLevel); + _renderer.QueueCommand(); + } + + public void CopyTo(ITexture destination, Extents2D srcRegion, Extents2D dstRegion, bool linearFilter) + { + ThreadedTexture dest = (ThreadedTexture)destination; + + if (_renderer.IsGpuThread()) + { + _renderer.New().Set(Ref(this), Ref(dest), srcRegion, dstRegion, linearFilter); + _renderer.QueueCommand(); + } + else + { + // Scaled copy can happen on another thread for a res scale flush. + ThreadedHelpers.SpinUntilNonNull(ref Base); + ThreadedHelpers.SpinUntilNonNull(ref dest.Base); + + Base.CopyTo(dest.Base, srcRegion, dstRegion, linearFilter); + } + } + + public ITexture CreateView(TextureCreateInfo info, int firstLayer, int firstLevel) + { + ThreadedTexture newTex = new ThreadedTexture(_renderer, info, ScaleFactor); + _renderer.New().Set(Ref(this), Ref(newTex), info, firstLayer, firstLevel); + _renderer.QueueCommand(); + + return newTex; + } + + public ReadOnlySpan GetData() + { + if (_renderer.IsGpuThread()) + { + ResultBox> box = new ResultBox>(); + _renderer.New().Set(Ref(this), Ref(box)); + _renderer.InvokeCommand(); + + return box.Result.Get(); + } + else + { + ThreadedHelpers.SpinUntilNonNull(ref Base); + + return Base.GetData(); + } + } + + public void SetData(ReadOnlySpan data) + { + _renderer.New().Set(Ref(this), Ref(data.ToArray())); + _renderer.QueueCommand(); + } + + public void SetData(ReadOnlySpan data, int layer, int level) + { + _renderer.New().Set(Ref(this), Ref(data.ToArray()), layer, level); + _renderer.QueueCommand(); + } + + public void SetStorage(BufferRange buffer) + { + _renderer.New().Set(Ref(this), buffer); + _renderer.QueueCommand(); + } + + public void Release() + { + _renderer.New().Set(Ref(this)); + _renderer.QueueCommand(); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/SyncMap.cs b/Ryujinx.Graphics.GAL/Multithreading/SyncMap.cs new file mode 100644 index 00000000..ae09e852 --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/SyncMap.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +using System.Threading; + +namespace Ryujinx.Graphics.GAL.Multithreading +{ + class SyncMap : IDisposable + { + private HashSet _inFlight = new HashSet(); + private AutoResetEvent _inFlightChanged = new AutoResetEvent(false); + + internal void CreateSyncHandle(ulong id) + { + lock (_inFlight) + { + _inFlight.Add(id); + } + } + + internal void AssignSync(ulong id) + { + lock (_inFlight) + { + _inFlight.Remove(id); + } + + _inFlightChanged.Set(); + } + + internal void WaitSyncAvailability(ulong id) + { + // Blocks until the handle is available. + + bool signal = false; + + while (true) + { + lock (_inFlight) + { + if (!_inFlight.Contains(id)) + { + break; + } + } + + _inFlightChanged.WaitOne(); + signal = true; + } + + if (signal) + { + // Signal other threads which might still be waiting. + _inFlightChanged.Set(); + } + } + + public void Dispose() + { + _inFlightChanged.Dispose(); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/ThreadedHelpers.cs b/Ryujinx.Graphics.GAL/Multithreading/ThreadedHelpers.cs new file mode 100644 index 00000000..7ddb19e6 --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/ThreadedHelpers.cs @@ -0,0 +1,28 @@ +using System; +using System.Threading; + +namespace Ryujinx.Graphics.GAL.Multithreading +{ + static class ThreadedHelpers + { + public static void SpinUntilNonNull(ref T obj) where T : class + { + Span spinWait = stackalloc SpinWait[1]; + + while (obj == null) + { + spinWait[0].SpinOnce(-1); + } + } + + public static void SpinUntilExchange(ref int target, int value, int comparand) + { + Span spinWait = stackalloc SpinWait[1]; + + while (Interlocked.CompareExchange(ref target, value, comparand) != comparand) + { + spinWait[0].SpinOnce(-1); + } + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs b/Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs new file mode 100644 index 00000000..0f523481 --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs @@ -0,0 +1,344 @@ +using Ryujinx.Graphics.GAL.Multithreading.Commands; +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; +using Ryujinx.Graphics.Shader; +using System; +using System.Linq; + +namespace Ryujinx.Graphics.GAL.Multithreading +{ + public class ThreadedPipeline : IPipeline + { + private ThreadedRenderer _renderer; + private IPipeline _impl; + + public ThreadedPipeline(ThreadedRenderer renderer, IPipeline impl) + { + _renderer = renderer; + _impl = impl; + } + + private TableRef Ref(T reference) + { + return new TableRef(_renderer, reference); + } + + public void Barrier() + { + _renderer.New(); + _renderer.QueueCommand(); + } + + public void BeginTransformFeedback(PrimitiveTopology topology) + { + _renderer.New().Set(topology); + _renderer.QueueCommand(); + } + + public void ClearBuffer(BufferHandle destination, int offset, int size, uint value) + { + _renderer.New().Set(destination, offset, size, value); + _renderer.QueueCommand(); + } + + public void ClearRenderTargetColor(int index, uint componentMask, ColorF color) + { + _renderer.New().Set(index, componentMask, color); + _renderer.QueueCommand(); + } + + public void ClearRenderTargetDepthStencil(float depthValue, bool depthMask, int stencilValue, int stencilMask) + { + _renderer.New().Set(depthValue, depthMask, stencilValue, stencilMask); + _renderer.QueueCommand(); + } + + public void CommandBufferBarrier() + { + _renderer.New(); + _renderer.QueueCommand(); + } + + public void CopyBuffer(BufferHandle source, BufferHandle destination, int srcOffset, int dstOffset, int size) + { + _renderer.New().Set(source, destination, srcOffset, dstOffset, size); + _renderer.QueueCommand(); + } + + public void DispatchCompute(int groupsX, int groupsY, int groupsZ) + { + _renderer.New().Set(groupsX, groupsY, groupsZ); + _renderer.QueueCommand(); + } + + public void Draw(int vertexCount, int instanceCount, int firstVertex, int firstInstance) + { + _renderer.New().Set(vertexCount, instanceCount, firstVertex, firstInstance); + _renderer.QueueCommand(); + } + + public void DrawIndexed(int indexCount, int instanceCount, int firstIndex, int firstVertex, int firstInstance) + { + _renderer.New().Set(indexCount, instanceCount, firstIndex, firstVertex, firstInstance); + _renderer.QueueCommand(); + } + + public void EndHostConditionalRendering() + { + _renderer.New(); + _renderer.QueueCommand(); + } + + public void EndTransformFeedback() + { + _renderer.New(); + _renderer.QueueCommand(); + } + + public void MultiDrawIndirectCount(BufferRange indirectBuffer, BufferRange parameterBuffer, int maxDrawCount, int stride) + { + _renderer.New().Set(indirectBuffer, parameterBuffer, maxDrawCount, stride); + _renderer.QueueCommand(); + } + + public void MultiDrawIndexedIndirectCount(BufferRange indirectBuffer, BufferRange parameterBuffer, int maxDrawCount, int stride) + { + _renderer.New().Set(indirectBuffer, parameterBuffer, maxDrawCount, stride); + _renderer.QueueCommand(); + } + + public void SetAlphaTest(bool enable, float reference, CompareOp op) + { + _renderer.New().Set(enable, reference, op); + _renderer.QueueCommand(); + } + + public void SetBlendState(int index, BlendDescriptor blend) + { + _renderer.New().Set(index, blend); + _renderer.QueueCommand(); + } + + public void SetDepthBias(PolygonModeMask enables, float factor, float units, float clamp) + { + _renderer.New().Set(enables, factor, units, clamp); + _renderer.QueueCommand(); + } + + public void SetDepthClamp(bool clamp) + { + _renderer.New().Set(clamp); + _renderer.QueueCommand(); + } + + public void SetDepthMode(DepthMode mode) + { + _renderer.New().Set(mode); + _renderer.QueueCommand(); + } + + public void SetDepthTest(DepthTestDescriptor depthTest) + { + _renderer.New().Set(depthTest); + _renderer.QueueCommand(); + } + + public void SetFaceCulling(bool enable, Face face) + { + _renderer.New().Set(enable, face); + _renderer.QueueCommand(); + } + + public void SetFrontFace(FrontFace frontFace) + { + _renderer.New().Set(frontFace); + _renderer.QueueCommand(); + } + + public void SetImage(int binding, ITexture texture, Format imageFormat) + { + _renderer.New().Set(binding, Ref(texture), imageFormat); + _renderer.QueueCommand(); + } + + public void SetIndexBuffer(BufferRange buffer, IndexType type) + { + _renderer.New().Set(buffer, type); + _renderer.QueueCommand(); + } + + public void SetLineParameters(float width, bool smooth) + { + _renderer.New().Set(width, smooth); + _renderer.QueueCommand(); + } + + public void SetLogicOpState(bool enable, LogicalOp op) + { + _renderer.New().Set(enable, op); + _renderer.QueueCommand(); + } + + public void SetPointParameters(float size, bool isProgramPointSize, bool enablePointSprite, Origin origin) + { + _renderer.New().Set(size, isProgramPointSize, enablePointSprite, origin); + _renderer.QueueCommand(); + } + + public void SetPrimitiveRestart(bool enable, int index) + { + _renderer.New().Set(enable, index); + _renderer.QueueCommand(); + } + + public void SetPrimitiveTopology(PrimitiveTopology topology) + { + _renderer.New().Set(topology); + _renderer.QueueCommand(); + } + + public void SetProgram(IProgram program) + { + _renderer.New().Set(Ref(program)); + _renderer.QueueCommand(); + } + + public void SetRasterizerDiscard(bool discard) + { + _renderer.New().Set(discard); + _renderer.QueueCommand(); + } + + public void SetRenderTargetColorMasks(ReadOnlySpan componentMask) + { + _renderer.New().Set(_renderer.CopySpan(componentMask)); + _renderer.QueueCommand(); + } + + public void SetRenderTargets(ITexture[] colors, ITexture depthStencil) + { + _renderer.New().Set(Ref(colors.ToArray()), Ref(depthStencil)); + _renderer.QueueCommand(); + } + + public void SetRenderTargetScale(float scale) + { + _renderer.New().Set(scale); + _renderer.QueueCommand(); + } + + public void SetSampler(int binding, ISampler sampler) + { + _renderer.New().Set(binding, Ref(sampler)); + _renderer.QueueCommand(); + } + + public void SetScissor(int index, bool enable, int x, int y, int width, int height) + { + _renderer.New().Set(index, enable, x, y, width, height); + _renderer.QueueCommand(); + } + + public void SetStencilTest(StencilTestDescriptor stencilTest) + { + _renderer.New().Set(stencilTest); + _renderer.QueueCommand(); + } + + public void SetStorageBuffers(int first, ReadOnlySpan buffers) + { + _renderer.New().Set(first, _renderer.CopySpan(buffers)); + _renderer.QueueCommand(); + } + + public void SetTexture(int binding, ITexture texture) + { + _renderer.New().Set(binding, Ref(texture)); + _renderer.QueueCommand(); + } + + public void SetTransformFeedbackBuffers(ReadOnlySpan buffers) + { + _renderer.New().Set(_renderer.CopySpan(buffers)); + _renderer.QueueCommand(); + } + + public void SetUniformBuffers(int first, ReadOnlySpan buffers) + { + _renderer.New().Set(first, _renderer.CopySpan(buffers)); + _renderer.QueueCommand(); + } + + public void SetUserClipDistance(int index, bool enableClip) + { + _renderer.New().Set(index, enableClip); + _renderer.QueueCommand(); + } + + public void SetVertexAttribs(ReadOnlySpan vertexAttribs) + { + _renderer.New().Set(_renderer.CopySpan(vertexAttribs)); + _renderer.QueueCommand(); + } + + public void SetVertexBuffers(ReadOnlySpan vertexBuffers) + { + _renderer.New().Set(_renderer.CopySpan(vertexBuffers)); + _renderer.QueueCommand(); + } + + public void SetViewports(int first, ReadOnlySpan viewports) + { + _renderer.New().Set(first, _renderer.CopySpan(viewports)); + _renderer.QueueCommand(); + } + + public void TextureBarrier() + { + _renderer.New(); + _renderer.QueueCommand(); + } + + public void TextureBarrierTiled() + { + _renderer.New(); + _renderer.QueueCommand(); + } + + public bool TryHostConditionalRendering(ICounterEvent value, ulong compare, bool isEqual) + { + var evt = value as ThreadedCounterEvent; + if (evt != null) + { + if (compare == 0 && evt.Type == CounterType.SamplesPassed && evt.ClearCounter) + { + if (!evt.ReserveForHostAccess()) + { + return false; + } + + _renderer.New().Set(Ref(evt), compare, isEqual); + _renderer.QueueCommand(); + return true; + } + } + + _renderer.New().Set(Ref(evt), Ref(null), isEqual); + _renderer.QueueCommand(); + return false; + } + + public bool TryHostConditionalRendering(ICounterEvent value, ICounterEvent compare, bool isEqual) + { + _renderer.New().Set(Ref(value as ThreadedCounterEvent), Ref(compare as ThreadedCounterEvent), isEqual); + _renderer.QueueCommand(); + return false; + } + + public void UpdateRenderScale(ShaderStage stage, ReadOnlySpan scales, int textureCount, int imageCount) + { + _renderer.New().Set(stage, _renderer.CopySpan(scales.Slice(0, textureCount + imageCount)), textureCount, imageCount); + _renderer.QueueCommand(); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/ThreadedRenderer.cs b/Ryujinx.Graphics.GAL/Multithreading/ThreadedRenderer.cs new file mode 100644 index 00000000..52197688 --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/ThreadedRenderer.cs @@ -0,0 +1,441 @@ +using Ryujinx.Common; +using Ryujinx.Common.Configuration; +using Ryujinx.Graphics.GAL.Multithreading.Commands; +using Ryujinx.Graphics.GAL.Multithreading.Commands.Buffer; +using Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer; +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; +using Ryujinx.Graphics.GAL.Multithreading.Resources.Programs; +using Ryujinx.Graphics.Shader; +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Threading; + +namespace Ryujinx.Graphics.GAL.Multithreading +{ + /// + /// The ThreadedRenderer is a layer that can be put in front of any Renderer backend to make + /// its processing happen on a separate thread, rather than intertwined with the GPU emulation. + /// A new thread is created to handle the GPU command processing, separate from the renderer thread. + /// Calls to the renderer, pipeline and resources are queued to happen on the renderer thread. + /// + public class ThreadedRenderer : IRenderer + { + private const int SpanPoolBytes = 4 * 1024 * 1024; + private const int MaxRefsPerCommand = 2; + private const int QueueCount = 10000; + + private int _elementSize; + private IRenderer _baseRenderer; + private Thread _gpuThread; + private bool _disposed; + private bool _running; + + private AutoResetEvent _frameComplete = new AutoResetEvent(true); + + private ManualResetEventSlim _galWorkAvailable; + private CircularSpanPool _spanPool; + + private ManualResetEventSlim _invokeRun; + + private bool _lastSampleCounterClear = true; + + private byte[] _commandQueue; + private object[] _refQueue; + + private int _consumerPtr; + private int _commandCount; + + private int _producerPtr; + private int _lastProducedPtr; + private int _invokePtr; + + private int _refProducerPtr; + private int _refConsumerPtr; + + public event EventHandler ScreenCaptured; + + internal BufferMap Buffers { get; } + internal SyncMap Sync { get; } + internal CircularSpanPool SpanPool { get; } + internal ProgramQueue Programs { get; } + + public IPipeline Pipeline { get; } + public IWindow Window { get; } + + public IRenderer BaseRenderer => _baseRenderer; + + public bool PreferThreading => _baseRenderer.PreferThreading; + + public ThreadedRenderer(IRenderer renderer) + { + _baseRenderer = renderer; + + renderer.ScreenCaptured += (object sender, ScreenCaptureImageInfo info) => ScreenCaptured?.Invoke(this, info); + + Pipeline = new ThreadedPipeline(this, renderer.Pipeline); + Window = new ThreadedWindow(this, renderer.Window); + Buffers = new BufferMap(); + Sync = new SyncMap(); + Programs = new ProgramQueue(renderer); + + _galWorkAvailable = new ManualResetEventSlim(false); + _invokeRun = new ManualResetEventSlim(); + _spanPool = new CircularSpanPool(this, SpanPoolBytes); + SpanPool = _spanPool; + + _elementSize = BitUtils.AlignUp(CommandHelper.GetMaxCommandSize(), 4); + + _commandQueue = new byte[_elementSize * QueueCount]; + _refQueue = new object[MaxRefsPerCommand * QueueCount]; + } + + public void RunLoop(Action gpuLoop) + { + _running = true; + + _gpuThread = new Thread(() => { + gpuLoop(); + _running = false; + _galWorkAvailable.Set(); + }); + + _gpuThread.Name = "GPU.MainThread"; + _gpuThread.Start(); + + RenderLoop(); + } + + public void RenderLoop() + { + // Power through the render queue until the Gpu thread work is done. + + while (_running && !_disposed) + { + _galWorkAvailable.Wait(); + _galWorkAvailable.Reset(); + + // The other thread can only increase the command count. + // We can assume that if it is above 0, it will stay there or get higher. + + while (_commandCount > 0) + { + int commandPtr = _consumerPtr; + + Span command = new Span(_commandQueue, commandPtr * _elementSize, _elementSize); + + // Run the command. + + CommandHelper.RunCommand(command, this, _baseRenderer); + + if (Interlocked.CompareExchange(ref _invokePtr, -1, commandPtr) == commandPtr) + { + _invokeRun.Set(); + } + + _consumerPtr = (_consumerPtr + 1) % QueueCount; + + Interlocked.Decrement(ref _commandCount); + } + } + } + + internal SpanRef CopySpan(ReadOnlySpan data) where T : unmanaged + { + return _spanPool.Insert(data); + } + + private TableRef Ref(T reference) + { + return new TableRef(this, reference); + } + + internal ref T New() where T : struct + { + while (_producerPtr == (_consumerPtr + QueueCount - 1) % QueueCount) + { + // If incrementing the producer pointer would overflow, we need to wait. + // _consumerPtr can only move forward, so there's no race to worry about here. + + Thread.Sleep(1); + } + + int taken = _producerPtr; + _lastProducedPtr = taken; + + _producerPtr = (_producerPtr + 1) % QueueCount; + + Span memory = new Span(_commandQueue, taken * _elementSize, _elementSize); + ref T result = ref Unsafe.As(ref MemoryMarshal.GetReference(memory)); + + memory[memory.Length - 1] = (byte)((IGALCommand)result).CommandType; + + return ref result; + } + + internal int AddTableRef(object obj) + { + // The reference table is sized so that it will never overflow, so long as the references are taken after the command is allocated. + + int index = _refProducerPtr; + + _refQueue[index] = obj; + + _refProducerPtr = (_refProducerPtr + 1) % _refQueue.Length; + + return index; + } + + internal object RemoveTableRef(int index) + { + Debug.Assert(index == _refConsumerPtr); + + object result = _refQueue[_refConsumerPtr]; + _refQueue[_refConsumerPtr] = null; + + _refConsumerPtr = (_refConsumerPtr + 1) % _refQueue.Length; + + return result; + } + + internal void QueueCommand() + { + int result = Interlocked.Increment(ref _commandCount); + + if (result == 1) + { + _galWorkAvailable.Set(); + } + } + + internal void InvokeCommand() + { + _invokeRun.Reset(); + _invokePtr = _lastProducedPtr; + + QueueCommand(); + + // Wait for the command to complete. + _invokeRun.Wait(); + } + + internal void WaitForFrame() + { + _frameComplete.WaitOne(); + } + + internal void SignalFrame() + { + _frameComplete.Set(); + } + + internal bool IsGpuThread() + { + return Thread.CurrentThread == _gpuThread; + } + + public void BackgroundContextAction(Action action, bool alwaysBackground = false) + { + if (IsGpuThread() && !alwaysBackground) + { + // The action must be performed on the render thread. + New().Set(Ref(action)); + InvokeCommand(); + } + else + { + _baseRenderer.BackgroundContextAction(action, true); + } + } + + public IShader CompileShader(ShaderStage stage, string code) + { + var shader = new ThreadedShader(this, stage, code); + New().Set(Ref(shader)); + QueueCommand(); + + return shader; + } + + public BufferHandle CreateBuffer(int size) + { + BufferHandle handle = Buffers.CreateBufferHandle(); + New().Set(handle, size); + QueueCommand(); + + return handle; + } + + public IProgram CreateProgram(IShader[] shaders, TransformFeedbackDescriptor[] transformFeedbackDescriptors) + { + var program = new ThreadedProgram(this); + SourceProgramRequest request = new SourceProgramRequest(program, shaders, transformFeedbackDescriptors); + Programs.Add(request); + + New().Set(Ref((IProgramRequest)request)); + QueueCommand(); + + return program; + } + + public ISampler CreateSampler(SamplerCreateInfo info) + { + var sampler = new ThreadedSampler(this); + New().Set(Ref(sampler), info); + QueueCommand(); + + return sampler; + } + + public void CreateSync(ulong id) + { + Sync.CreateSyncHandle(id); + New().Set(id); + QueueCommand(); + } + + public ITexture CreateTexture(TextureCreateInfo info, float scale) + { + if (IsGpuThread()) + { + var texture = new ThreadedTexture(this, info, scale); + New().Set(Ref(texture), info, scale); + QueueCommand(); + + return texture; + } + else + { + var texture = new ThreadedTexture(this, info, scale); + texture.Base = _baseRenderer.CreateTexture(info, scale); + + return texture; + } + } + + public void DeleteBuffer(BufferHandle buffer) + { + New().Set(buffer); + QueueCommand(); + } + + public ReadOnlySpan GetBufferData(BufferHandle buffer, int offset, int size) + { + if (IsGpuThread()) + { + ResultBox> box = new ResultBox>(); + New().Set(buffer, offset, size, Ref(box)); + InvokeCommand(); + + return box.Result.Get(); + } + else + { + return _baseRenderer.GetBufferData(Buffers.MapBufferBlocking(buffer), offset, size); + } + } + + public Capabilities GetCapabilities() + { + ResultBox box = new ResultBox(); + New().Set(Ref(box)); + InvokeCommand(); + + return box.Result; + } + + /// + /// Initialize the base renderer. Must be called on the render thread. + /// + /// Log level to use + public void Initialize(GraphicsDebugLevel logLevel) + { + _baseRenderer.Initialize(logLevel); + } + + public IProgram LoadProgramBinary(byte[] programBinary) + { + var program = new ThreadedProgram(this); + + BinaryProgramRequest request = new BinaryProgramRequest(program, programBinary); + Programs.Add(request); + + New().Set(Ref((IProgramRequest)request)); + QueueCommand(); + + return program; + } + + public void PreFrame() + { + New(); + QueueCommand(); + } + + public ICounterEvent ReportCounter(CounterType type, EventHandler resultHandler, bool hostReserved) + { + ThreadedCounterEvent evt = new ThreadedCounterEvent(this, type, _lastSampleCounterClear); + New().Set(Ref(evt), type, Ref(resultHandler), hostReserved); + QueueCommand(); + + if (type == CounterType.SamplesPassed) + { + _lastSampleCounterClear = false; + } + + return evt; + } + + public void ResetCounter(CounterType type) + { + New().Set(type); + QueueCommand(); + _lastSampleCounterClear = true; + } + + public void Screenshot() + { + _baseRenderer.Screenshot(); + } + + public void SetBufferData(BufferHandle buffer, int offset, ReadOnlySpan data) + { + New().Set(buffer, offset, CopySpan(data)); + QueueCommand(); + } + + public void UpdateCounters() + { + New(); + QueueCommand(); + } + + public void WaitSync(ulong id) + { + Sync.WaitSyncAvailability(id); + + _baseRenderer.WaitSync(id); + } + + public void Dispose() + { + // Dispose must happen from the render thread, after all commands have completed. + + // Stop the GPU thread. + _disposed = true; + _gpuThread.Join(); + + // Dispose the renderer. + _baseRenderer.Dispose(); + + // Dispose events. + _frameComplete.Dispose(); + _galWorkAvailable.Dispose(); + _invokeRun.Dispose(); + + Sync.Dispose(); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/ThreadedWindow.cs b/Ryujinx.Graphics.GAL/Multithreading/ThreadedWindow.cs new file mode 100644 index 00000000..4db3cb19 --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/ThreadedWindow.cs @@ -0,0 +1,34 @@ +using Ryujinx.Graphics.GAL.Multithreading.Commands.Window; +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; +using System; + +namespace Ryujinx.Graphics.GAL.Multithreading +{ + public class ThreadedWindow : IWindow + { + private ThreadedRenderer _renderer; + private IWindow _impl; + + public ThreadedWindow(ThreadedRenderer renderer, IWindow window) + { + _renderer = renderer; + _impl = window; + } + + public void Present(ITexture texture, ImageCrop crop, Action swapBuffersCallback) + { + // If there's already a frame in the pipeline, wait for it to be presented first. + // This is a multithread rate limit - we can't be more than one frame behind the command queue. + + _renderer.WaitForFrame(); + _renderer.New().Set(new TableRef(_renderer, texture as ThreadedTexture), crop, new TableRef(_renderer, swapBuffersCallback)); + _renderer.QueueCommand(); + } + + public void SetSize(int width, int height) + { + _impl.SetSize(width, height); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Ryujinx.Graphics.GAL.csproj b/Ryujinx.Graphics.GAL/Ryujinx.Graphics.GAL.csproj index 6b359738..e8b3f52d 100644 --- a/Ryujinx.Graphics.GAL/Ryujinx.Graphics.GAL.csproj +++ b/Ryujinx.Graphics.GAL/Ryujinx.Graphics.GAL.csproj @@ -1,9 +1,17 @@ - + net5.0 + + true + + + + true + + diff --git a/Ryujinx.Graphics.Gpu/Engine/Threed/DrawManager.cs b/Ryujinx.Graphics.Gpu/Engine/Threed/DrawManager.cs index 2443917c..e01938bd 100644 --- a/Ryujinx.Graphics.Gpu/Engine/Threed/DrawManager.cs +++ b/Ryujinx.Graphics.Gpu/Engine/Threed/DrawManager.cs @@ -224,9 +224,12 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed _instanceIndex = 0; } - _context.Renderer.Pipeline.SetPrimitiveTopology(topology); + if (_drawState.Topology != topology) + { + _context.Renderer.Pipeline.SetPrimitiveTopology(topology); - _drawState.Topology = topology; + _drawState.Topology = topology; + } } /// diff --git a/Ryujinx.Graphics.Gpu/Engine/Threed/SemaphoreUpdater.cs b/Ryujinx.Graphics.Gpu/Engine/Threed/SemaphoreUpdater.cs index 37fa51c4..cb0d593d 100644 --- a/Ryujinx.Graphics.Gpu/Engine/Threed/SemaphoreUpdater.cs +++ b/Ryujinx.Graphics.Gpu/Engine/Threed/SemaphoreUpdater.cs @@ -1,5 +1,6 @@ using Ryujinx.Common; using Ryujinx.Graphics.GAL; +using System.Runtime.InteropServices; namespace Ryujinx.Graphics.Gpu.Engine.Threed { @@ -184,13 +185,13 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed resultHandler(null, 0); break; case ReportCounterType.SamplesPassed: - counter = _context.Renderer.ReportCounter(CounterType.SamplesPassed, resultHandler); + counter = _context.Renderer.ReportCounter(CounterType.SamplesPassed, resultHandler, false); break; case ReportCounterType.PrimitivesGenerated: - counter = _context.Renderer.ReportCounter(CounterType.PrimitivesGenerated, resultHandler); + counter = _context.Renderer.ReportCounter(CounterType.PrimitivesGenerated, resultHandler, false); break; case ReportCounterType.TransformFeedbackPrimitivesWritten: - counter = _context.Renderer.ReportCounter(CounterType.TransformFeedbackPrimitivesWritten, resultHandler); + counter = _context.Renderer.ReportCounter(CounterType.TransformFeedbackPrimitivesWritten, resultHandler, false); break; } diff --git a/Ryujinx.Graphics.Gpu/GpuContext.cs b/Ryujinx.Graphics.Gpu/GpuContext.cs index 734fc492..034c8fcb 100644 --- a/Ryujinx.Graphics.Gpu/GpuContext.cs +++ b/Ryujinx.Graphics.Gpu/GpuContext.cs @@ -184,6 +184,17 @@ namespace Ryujinx.Graphics.Gpu } } + /// + /// Processes the queue of shaders that must save their binaries to the disk cache. + /// + public void ProcessShaderCacheQueue() + { + foreach (var physicalMemory in PhysicalMemoryRegistry.Values) + { + physicalMemory.ShaderCache.ProcessShaderCacheQueue(); + } + } + /// /// Advances internal sequence number. /// This forces the update of any modified GPU resource. diff --git a/Ryujinx.Graphics.Gpu/Memory/Buffer.cs b/Ryujinx.Graphics.Gpu/Memory/Buffer.cs index 3f869a50..af69e693 100644 --- a/Ryujinx.Graphics.Gpu/Memory/Buffer.cs +++ b/Ryujinx.Graphics.Gpu/Memory/Buffer.cs @@ -449,7 +449,7 @@ namespace Ryujinx.Graphics.Gpu.Memory (address, size) = PageAlign(address, size); ranges.WaitForAndGetRanges(address, size, Flush); } - }); + }, true); } /// diff --git a/Ryujinx.Graphics.Gpu/Memory/BufferModifiedRangeList.cs b/Ryujinx.Graphics.Gpu/Memory/BufferModifiedRangeList.cs index 594dd066..9e5c5e84 100644 --- a/Ryujinx.Graphics.Gpu/Memory/BufferModifiedRangeList.cs +++ b/Ryujinx.Graphics.Gpu/Memory/BufferModifiedRangeList.cs @@ -1,4 +1,5 @@ -using Ryujinx.Memory.Range; +using Ryujinx.Common.Pools; +using Ryujinx.Memory.Range; using System; using System.Linq; @@ -63,10 +64,6 @@ namespace Ryujinx.Graphics.Gpu.Memory private object _lock = new object(); - // The list can be accessed from both the GPU thread, and a background thread. - private BufferModifiedRange[] _foregroundOverlaps = new BufferModifiedRange[1]; - private BufferModifiedRange[] _backgroundOverlaps = new BufferModifiedRange[1]; - /// /// Creates a new instance of a modified range list. /// @@ -87,11 +84,13 @@ namespace Ryujinx.Graphics.Gpu.Memory lock (_lock) { // Slices a given region using the modified regions in the list. Calls the action for the new slices. - int count = FindOverlapsNonOverlapping(address, size, ref _foregroundOverlaps); + ref var overlaps = ref ThreadStaticArray.Get(); + + int count = FindOverlapsNonOverlapping(address, size, ref overlaps); for (int i = 0; i < count; i++) { - BufferModifiedRange overlap = _foregroundOverlaps[i]; + BufferModifiedRange overlap = overlaps[i]; if (overlap.Address > address) { @@ -124,7 +123,9 @@ namespace Ryujinx.Graphics.Gpu.Memory lock (_lock) { // We may overlap with some existing modified regions. They must be cut into by the new entry. - int count = FindOverlapsNonOverlapping(address, size, ref _foregroundOverlaps); + ref var overlaps = ref ThreadStaticArray.Get(); + + int count = FindOverlapsNonOverlapping(address, size, ref overlaps); ulong endAddress = address + size; ulong syncNumber = _context.SyncNumber; @@ -133,7 +134,7 @@ namespace Ryujinx.Graphics.Gpu.Memory { // The overlaps must be removed or split. - BufferModifiedRange overlap = _foregroundOverlaps[i]; + BufferModifiedRange overlap = overlaps[i]; if (overlap.Address == address && overlap.Size == size) { @@ -174,15 +175,17 @@ namespace Ryujinx.Graphics.Gpu.Memory { int count = 0; + ref var overlaps = ref ThreadStaticArray.Get(); + // Range list must be consistent for this operation. lock (_lock) { - count = FindOverlapsNonOverlapping(address, size, ref _foregroundOverlaps); + count = FindOverlapsNonOverlapping(address, size, ref overlaps); } for (int i = 0; i < count; i++) { - BufferModifiedRange overlap = _foregroundOverlaps[i]; + BufferModifiedRange overlap = overlaps[i]; rangeAction(overlap.Address, overlap.Size); } } @@ -198,7 +201,7 @@ namespace Ryujinx.Graphics.Gpu.Memory // Range list must be consistent for this operation. lock (_lock) { - return FindOverlapsNonOverlapping(address, size, ref _foregroundOverlaps) > 0; + return FindOverlapsNonOverlapping(address, size, ref ThreadStaticArray.Get()) > 0; } } @@ -221,10 +224,12 @@ namespace Ryujinx.Graphics.Gpu.Memory int rangeCount = 0; + ref var overlaps = ref ThreadStaticArray.Get(); + // Range list must be consistent for this operation lock (_lock) { - rangeCount = FindOverlapsNonOverlapping(address, size, ref _backgroundOverlaps); + rangeCount = FindOverlapsNonOverlapping(address, size, ref overlaps); } if (rangeCount == 0) @@ -239,7 +244,7 @@ namespace Ryujinx.Graphics.Gpu.Memory for (int i = 0; i < rangeCount; i++) { - BufferModifiedRange overlap = _backgroundOverlaps[i]; + BufferModifiedRange overlap = overlaps[i]; long diff = (long)(overlap.SyncNumber - currentSync); @@ -262,7 +267,7 @@ namespace Ryujinx.Graphics.Gpu.Memory { for (int i = 0; i < rangeCount; i++) { - BufferModifiedRange overlap = _backgroundOverlaps[i]; + BufferModifiedRange overlap = overlaps[i]; long diff = (long)(overlap.SyncNumber - currentSync); diff --git a/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs b/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs index 91edbf2a..e5289bc0 100644 --- a/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs +++ b/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs @@ -35,6 +35,8 @@ namespace Ryujinx.Graphics.Gpu.Shader private Dictionary _gpProgramsDiskCache; private Dictionary _cpProgramsDiskCache; + private Queue<(IProgram, Action)> _programsToSaveQueue; + /// /// Version of the codegen (to be changed when codegen or guest format change). /// @@ -59,6 +61,31 @@ namespace Ryujinx.Graphics.Gpu.Shader _gpPrograms = new Dictionary>(); _gpProgramsDiskCache = new Dictionary(); _cpProgramsDiskCache = new Dictionary(); + + _programsToSaveQueue = new Queue<(IProgram, Action)>(); + } + + /// + /// Processes the queue of shaders that must save their binaries to the disk cache. + /// + public void ProcessShaderCacheQueue() + { + // Check to see if the binaries for previously compiled shaders are ready, and save them out. + + while (_programsToSaveQueue.Count > 0) + { + (IProgram program, Action dataAction) = _programsToSaveQueue.Peek(); + + if (program.CheckProgramLink(false) != ProgramLinkStatus.Incomplete) + { + dataAction(program.GetBinary()); + _programsToSaveQueue.Dequeue(); + } + else + { + break; + } + } } /// @@ -597,10 +624,6 @@ namespace Ryujinx.Graphics.Gpu.Shader IProgram hostProgram = _context.Renderer.CreateProgram(new IShader[] { shader.HostShader }, null); - hostProgram.CheckProgramLink(true); - - byte[] hostProgramBinary = HostShaderCacheEntry.Create(hostProgram.GetBinary(), new ShaderCodeHolder[] { shader }); - cpShader = new ShaderBundle(hostProgram, shader); if (isShaderCacheEnabled) @@ -609,7 +632,11 @@ namespace Ryujinx.Graphics.Gpu.Shader if (!isShaderCacheReadOnly) { - _cacheManager.SaveProgram(ref programCodeHash, CacheHelper.CreateGuestProgramDump(shaderCacheEntries), hostProgramBinary); + byte[] guestProgramDump = CacheHelper.CreateGuestProgramDump(shaderCacheEntries); + _programsToSaveQueue.Enqueue((hostProgram, (byte[] hostProgramBinary) => + { + _cacheManager.SaveProgram(ref programCodeHash, guestProgramDump, HostShaderCacheEntry.Create(hostProgramBinary, new ShaderCodeHolder[] { shader })); + })); } } } @@ -740,10 +767,6 @@ namespace Ryujinx.Graphics.Gpu.Shader IProgram hostProgram = _context.Renderer.CreateProgram(hostShaders.ToArray(), tfd); - hostProgram.CheckProgramLink(true); - - byte[] hostProgramBinary = HostShaderCacheEntry.Create(hostProgram.GetBinary(), shaders); - gpShaders = new ShaderBundle(hostProgram, shaders); if (isShaderCacheEnabled) @@ -752,7 +775,11 @@ namespace Ryujinx.Graphics.Gpu.Shader if (!isShaderCacheReadOnly) { - _cacheManager.SaveProgram(ref programCodeHash, CacheHelper.CreateGuestProgramDump(shaderCacheEntries, tfd), hostProgramBinary); + byte[] guestProgramDump = CacheHelper.CreateGuestProgramDump(shaderCacheEntries, tfd); + _programsToSaveQueue.Enqueue((hostProgram, (byte[] hostProgramBinary) => + { + _cacheManager.SaveProgram(ref programCodeHash, guestProgramDump, HostShaderCacheEntry.Create(hostProgramBinary, shaders)); + })); } } } diff --git a/Ryujinx.Graphics.Gpu/Window.cs b/Ryujinx.Graphics.Gpu/Window.cs index 541a80cc..1432ef3b 100644 --- a/Ryujinx.Graphics.Gpu/Window.cs +++ b/Ryujinx.Graphics.Gpu/Window.cs @@ -201,9 +201,7 @@ namespace Ryujinx.Graphics.Gpu texture.SynchronizeMemory(); - _context.Renderer.Window.Present(texture.HostTexture, pt.Crop); - - swapBuffersCallback(); + _context.Renderer.Window.Present(texture.HostTexture, pt.Crop, swapBuffersCallback); pt.ReleaseCallback(pt.UserObj); } diff --git a/Ryujinx.Graphics.OpenGL/Pipeline.cs b/Ryujinx.Graphics.OpenGL/Pipeline.cs index 24dd97f9..dd07afcf 100644 --- a/Ryujinx.Graphics.OpenGL/Pipeline.cs +++ b/Ryujinx.Graphics.OpenGL/Pipeline.cs @@ -33,6 +33,8 @@ namespace Ryujinx.Graphics.OpenGL private int _boundDrawFramebuffer; private int _boundReadFramebuffer; + private CounterQueueEvent _activeConditionalRender; + private struct Vector4 { public T X; @@ -1246,7 +1248,7 @@ namespace Ryujinx.Graphics.OpenGL return (_boundDrawFramebuffer, _boundReadFramebuffer); } - public void UpdateRenderScale(ShaderStage stage, float[] scales, int textureCount, int imageCount) + public void UpdateRenderScale(ShaderStage stage, ReadOnlySpan scales, int textureCount, int imageCount) { if (stage != ShaderStage.Compute && stage != ShaderStage.Fragment) { @@ -1352,16 +1354,18 @@ namespace Ryujinx.Graphics.OpenGL // - Comparing against 0. // - Event has not already been flushed. - if (evt.Disposed) - { - // If the event has been flushed, then just use the values on the CPU. - // The query object may already be repurposed for another draw (eg. begin + end). - return false; - } - if (compare == 0 && evt.Type == QueryTarget.SamplesPassed && evt.ClearCounter) { + if (!value.ReserveForHostAccess()) + { + // If the event has been flushed, then just use the values on the CPU. + // The query object may already be repurposed for another draw (eg. begin + end). + return false; + } + GL.BeginConditionalRender(evt.Query, isEqual ? ConditionalRenderType.QueryNoWaitInverted : ConditionalRenderType.QueryNoWait); + _activeConditionalRender = evt; + return true; } } @@ -1381,6 +1385,9 @@ namespace Ryujinx.Graphics.OpenGL public void EndHostConditionalRendering() { GL.EndConditionalRender(); + + _activeConditionalRender?.ReleaseHostAccess(); + _activeConditionalRender = null; } public void Dispose() @@ -1400,6 +1407,7 @@ namespace Ryujinx.Graphics.OpenGL } } + _activeConditionalRender?.ReleaseHostAccess(); _framebuffer?.Dispose(); _vertexArray?.Dispose(); } diff --git a/Ryujinx.Graphics.OpenGL/Queries/BufferedQuery.cs b/Ryujinx.Graphics.OpenGL/Queries/BufferedQuery.cs index a87655be..7323abfe 100644 --- a/Ryujinx.Graphics.OpenGL/Queries/BufferedQuery.cs +++ b/Ryujinx.Graphics.OpenGL/Queries/BufferedQuery.cs @@ -54,6 +54,7 @@ namespace Ryujinx.Graphics.OpenGL.Queries Marshal.WriteInt64(_bufferMap, -1L); GL.GetQueryObject(Query, GetQueryObjectParam.QueryResult, (long*)0); + GL.MemoryBarrier(MemoryBarrierFlags.QueryBufferBarrierBit | MemoryBarrierFlags.ClientMappedBufferBarrierBit); } } diff --git a/Ryujinx.Graphics.OpenGL/Queries/CounterQueue.cs b/Ryujinx.Graphics.OpenGL/Queries/CounterQueue.cs index 5984a7ca..f4ab02fb 100644 --- a/Ryujinx.Graphics.OpenGL/Queries/CounterQueue.cs +++ b/Ryujinx.Graphics.OpenGL/Queries/CounterQueue.cs @@ -17,12 +17,14 @@ namespace Ryujinx.Graphics.OpenGL.Queries private CounterQueueEvent _current; private ulong _accumulatedCounter; + private int _waiterCount; private object _lock = new object(); private Queue _queryPool; private AutoResetEvent _queuedEvent = new AutoResetEvent(false); private AutoResetEvent _wakeSignal = new AutoResetEvent(false); + private AutoResetEvent _eventConsumed = new AutoResetEvent(false); private Thread _consumerThread; @@ -63,7 +65,13 @@ namespace Ryujinx.Graphics.OpenGL.Queries } else { - evt.TryConsume(ref _accumulatedCounter, true, _wakeSignal); + // Spin-wait rather than sleeping if there are any waiters, by passing null instead of the wake signal. + evt.TryConsume(ref _accumulatedCounter, true, _waiterCount == 0 ? _wakeSignal : null); + } + + if (_waiterCount > 0) + { + _eventConsumed.Set(); } } } @@ -95,7 +103,7 @@ namespace Ryujinx.Graphics.OpenGL.Queries } } - public CounterQueueEvent QueueReport(EventHandler resultHandler, ulong lastDrawIndex) + public CounterQueueEvent QueueReport(EventHandler resultHandler, ulong lastDrawIndex, bool hostReserved) { CounterQueueEvent result; ulong draws = lastDrawIndex - _current.DrawIndex; @@ -105,6 +113,12 @@ namespace Ryujinx.Graphics.OpenGL.Queries // A query's result only matters if more than one draw was performed during it. // Otherwise, dummy it out and return 0 immediately. + if (hostReserved) + { + // This counter event is guaranteed to be available for host conditional rendering. + _current.ReserveForHostAccess(); + } + if (draws > 0) { _current.Complete(true); @@ -175,25 +189,18 @@ namespace Ryujinx.Graphics.OpenGL.Queries public void FlushTo(CounterQueueEvent evt) { - lock (_lock) + // Flush the counter queue on the main thread. + + Interlocked.Increment(ref _waiterCount); + + _wakeSignal.Set(); + + while (!evt.Disposed) { - if (evt.Disposed) - { - return; - } - - // Tell the queue to process all events up to this one. - while (_events.Count > 0) - { - CounterQueueEvent flush = _events.Dequeue(); - flush.TryConsume(ref _accumulatedCounter, true); - - if (flush == evt) - { - return; - } - } + _eventConsumed.WaitOne(1); } + + Interlocked.Decrement(ref _waiterCount); } public void Dispose() @@ -218,6 +225,10 @@ namespace Ryujinx.Graphics.OpenGL.Queries { query.Dispose(); } + + _queuedEvent.Dispose(); + _wakeSignal.Dispose(); + _eventConsumed.Dispose(); } } } diff --git a/Ryujinx.Graphics.OpenGL/Queries/CounterQueueEvent.cs b/Ryujinx.Graphics.OpenGL/Queries/CounterQueueEvent.cs index 0e1025af..8b0ae30e 100644 --- a/Ryujinx.Graphics.OpenGL/Queries/CounterQueueEvent.cs +++ b/Ryujinx.Graphics.OpenGL/Queries/CounterQueueEvent.cs @@ -1,4 +1,5 @@ using OpenTK.Graphics.OpenGL; +using Ryujinx.Common.Logging; using Ryujinx.Graphics.GAL; using System; using System.Threading; @@ -21,7 +22,11 @@ namespace Ryujinx.Graphics.OpenGL.Queries private CounterQueue _queue; private BufferedQuery _counter; + private bool _hostAccessReserved = false; + private int _refCount = 1; // Starts with a reference from the counter queue. + private object _lock = new object(); + private ulong _result = ulong.MaxValue; public CounterQueueEvent(CounterQueue queue, QueryTarget type, ulong drawIndex) { @@ -76,6 +81,8 @@ namespace Ryujinx.Graphics.OpenGL.Queries result += (ulong)queryResult; + _result = result; + OnResult?.Invoke(this, result); Dispose(); // Return the our resources to the pool. @@ -95,10 +102,60 @@ namespace Ryujinx.Graphics.OpenGL.Queries _queue.FlushTo(this); } + public void DecrementRefCount() + { + if (Interlocked.Decrement(ref _refCount) == 0) + { + DisposeInternal(); + } + } + + public bool ReserveForHostAccess() + { + if (_hostAccessReserved) + { + return true; + } + + if (IsValueAvailable()) + { + return false; + } + + if (Interlocked.Increment(ref _refCount) == 1) + { + Interlocked.Decrement(ref _refCount); + + return false; + } + + _hostAccessReserved = true; + + return true; + } + + public void ReleaseHostAccess() + { + _hostAccessReserved = false; + + DecrementRefCount(); + } + + private void DisposeInternal() + { + _queue.ReturnQueryObject(_counter); + } + + private bool IsValueAvailable() + { + return _result != ulong.MaxValue || _counter.TryGetResult(out _); + } + public void Dispose() { Disposed = true; - _queue.ReturnQueryObject(_counter); + + DecrementRefCount(); } } } diff --git a/Ryujinx.Graphics.OpenGL/Queries/Counters.cs b/Ryujinx.Graphics.OpenGL/Queries/Counters.cs index ac441d5f..0c0a915d 100644 --- a/Ryujinx.Graphics.OpenGL/Queries/Counters.cs +++ b/Ryujinx.Graphics.OpenGL/Queries/Counters.cs @@ -23,9 +23,9 @@ namespace Ryujinx.Graphics.OpenGL.Queries } } - public CounterQueueEvent QueueReport(CounterType type, EventHandler resultHandler, ulong lastDrawIndex) + public CounterQueueEvent QueueReport(CounterType type, EventHandler resultHandler, ulong lastDrawIndex, bool hostReserved) { - return _counterQueues[(int)type].QueueReport(resultHandler, lastDrawIndex); + return _counterQueues[(int)type].QueueReport(resultHandler, lastDrawIndex, hostReserved); } public void QueueReset(CounterType type) diff --git a/Ryujinx.Graphics.OpenGL/Renderer.cs b/Ryujinx.Graphics.OpenGL/Renderer.cs index 6b620bb8..92f65083 100644 --- a/Ryujinx.Graphics.OpenGL/Renderer.cs +++ b/Ryujinx.Graphics.OpenGL/Renderer.cs @@ -40,6 +40,8 @@ namespace Ryujinx.Graphics.OpenGL public string GpuRenderer { get; private set; } public string GpuVersion { get; private set; } + public bool PreferThreading => true; + public Renderer() { _pipeline = new Pipeline(); @@ -129,9 +131,9 @@ namespace Ryujinx.Graphics.OpenGL ResourcePool.Tick(); } - public ICounterEvent ReportCounter(CounterType type, EventHandler resultHandler) + public ICounterEvent ReportCounter(CounterType type, EventHandler resultHandler, bool hostReserved) { - return _counters.QueueReport(type, resultHandler, _pipeline.DrawCount); + return _counters.QueueReport(type, resultHandler, _pipeline.DrawCount, hostReserved); } public void Initialize(GraphicsDebugLevel glLogLevel) @@ -163,8 +165,10 @@ namespace Ryujinx.Graphics.OpenGL _counters.QueueReset(type); } - public void BackgroundContextAction(Action action) + public void BackgroundContextAction(Action action, bool alwaysBackground = false) { + // alwaysBackground is ignored, since we cannot switch from the current context. + if (IOpenGLContext.HasContext()) { action(); // We have a context already - use that (assuming it is the main one). diff --git a/Ryujinx.Graphics.OpenGL/Window.cs b/Ryujinx.Graphics.OpenGL/Window.cs index b80348cd..da214553 100644 --- a/Ryujinx.Graphics.OpenGL/Window.cs +++ b/Ryujinx.Graphics.OpenGL/Window.cs @@ -23,13 +23,15 @@ namespace Ryujinx.Graphics.OpenGL _renderer = renderer; } - public void Present(ITexture texture, ImageCrop crop) + public void Present(ITexture texture, ImageCrop crop, Action swapBuffersCallback) { GL.Disable(EnableCap.FramebufferSrgb); CopyTextureToFrameBufferRGB(0, GetCopyFramebufferHandleLazy(), (TextureView)texture, crop); GL.Enable(EnableCap.FramebufferSrgb); + + swapBuffersCallback(); } public void SetSize(int width, int height) diff --git a/Ryujinx.HLE/Switch.cs b/Ryujinx.HLE/Switch.cs index 77596243..096963e3 100644 --- a/Ryujinx.HLE/Switch.cs +++ b/Ryujinx.HLE/Switch.cs @@ -129,6 +129,7 @@ namespace Ryujinx.HLE public void ProcessFrame() { + Gpu.ProcessShaderCacheQueue(); Gpu.Renderer.PreFrame(); Gpu.GPFifo.DispatchCalls(); diff --git a/Ryujinx.Headless.SDL2/Options.cs b/Ryujinx.Headless.SDL2/Options.cs index a396ff40..179246c9 100644 --- a/Ryujinx.Headless.SDL2/Options.cs +++ b/Ryujinx.Headless.SDL2/Options.cs @@ -149,6 +149,9 @@ namespace Ryujinx.Headless.SDL2 [Option("aspect-ratio", Required = false, Default = AspectRatio.Fixed16x9, HelpText = "Aspect Ratio applied to the renderer window.")] public AspectRatio AspectRatio { get; set; } + [Option("backend-threading", Required = false, Default = BackendThreading.Auto, HelpText = "Whether or not backend threading is enabled. The \"Auto\" setting will determine whether threading should be enabled at runtime.")] + public BackendThreading BackendThreading { get; set; } + [Option("graphics-shaders-dump-path", Required = false, HelpText = "Dumps shaders in this local directory. (Developer only)")] public string GraphicsShadersDumpPath { get; set; } diff --git a/Ryujinx.Headless.SDL2/Program.cs b/Ryujinx.Headless.SDL2/Program.cs index c5e914eb..508daae5 100644 --- a/Ryujinx.Headless.SDL2/Program.cs +++ b/Ryujinx.Headless.SDL2/Program.cs @@ -10,6 +10,8 @@ using Ryujinx.Common.Configuration.Hid.Keyboard; using Ryujinx.Common.Logging; using Ryujinx.Common.System; using Ryujinx.Common.Utilities; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.GAL.Multithreading; using Ryujinx.Graphics.Gpu; using Ryujinx.Graphics.Gpu.Shader; using Ryujinx.Graphics.OpenGL; @@ -433,12 +435,23 @@ namespace Ryujinx.Headless.SDL2 private static Switch InitializeEmulationContext(WindowBase window, Options options) { + IRenderer renderer = new Renderer(); + + BackendThreading threadingMode = options.BackendThreading; + + bool threadedGAL = threadingMode == BackendThreading.On || (threadingMode == BackendThreading.Auto && renderer.PreferThreading); + + if (threadedGAL) + { + renderer = new ThreadedRenderer(renderer); + } + HLEConfiguration configuration = new HLEConfiguration(_virtualFileSystem, _libHacHorizonManager, _contentManager, _accountManager, _userChannelPersistence, - new Renderer(), + renderer, new SDL2HardwareDeviceDriver(), (bool)options.ExpandRam ? MemoryConfiguration.MemoryConfiguration6GB : MemoryConfiguration.MemoryConfiguration4GB, window, diff --git a/Ryujinx.Headless.SDL2/WindowBase.cs b/Ryujinx.Headless.SDL2/WindowBase.cs index 7f574e97..6297e64e 100644 --- a/Ryujinx.Headless.SDL2/WindowBase.cs +++ b/Ryujinx.Headless.SDL2/WindowBase.cs @@ -3,6 +3,7 @@ using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration.Hid; using Ryujinx.Common.Logging; using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.GAL.Multithreading; using Ryujinx.HLE; using Ryujinx.HLE.HOS.Applets; using Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy.Types; @@ -73,7 +74,15 @@ namespace Ryujinx.Headless.SDL2 public void Initialize(Switch device, List inputConfigs, bool enableKeyboard, bool enableMouse) { Device = device; - Renderer = Device.Gpu.Renderer; + + IRenderer renderer = Device.Gpu.Renderer; + + if (renderer is ThreadedRenderer tr) + { + renderer = tr.BaseRenderer; + } + + Renderer = renderer; NpadManager.Initialize(device, inputConfigs, enableKeyboard, enableMouse); TouchScreenManager.Initialize(device); @@ -148,52 +157,55 @@ namespace Ryujinx.Headless.SDL2 _gpuVendorName = GetGpuVendorName(); - Device.Gpu.InitializeShaderCache(); - Translator.IsReadyForTranslation.Set(); - - while (_isActive) + Device.Gpu.Renderer.RunLoop(() => { - if (_isStopped) + Device.Gpu.InitializeShaderCache(); + Translator.IsReadyForTranslation.Set(); + + while (_isActive) { - return; - } - - _ticks += _chrono.ElapsedTicks; - - _chrono.Restart(); - - if (Device.WaitFifo()) - { - Device.Statistics.RecordFifoStart(); - Device.ProcessFrame(); - Device.Statistics.RecordFifoEnd(); - } - - while (Device.ConsumeFrameAvailable()) - { - Device.PresentFrame(SwapBuffers); - } - - if (_ticks >= _ticksPerFrame) - { - string dockedMode = Device.System.State.DockedMode ? "Docked" : "Handheld"; - float scale = Graphics.Gpu.GraphicsConfig.ResScale; - if (scale != 1) + if (_isStopped) { - dockedMode += $" ({scale}x)"; + return; } - StatusUpdatedEvent?.Invoke(this, new StatusUpdatedEventArgs( - Device.EnableDeviceVsync, - dockedMode, - Device.Configuration.AspectRatio.ToText(), - $"Game: {Device.Statistics.GetGameFrameRate():00.00} FPS", - $"FIFO: {Device.Statistics.GetFifoPercent():0.00} %", - $"GPU: {_gpuVendorName}")); + _ticks += _chrono.ElapsedTicks; - _ticks = Math.Min(_ticks - _ticksPerFrame, _ticksPerFrame); + _chrono.Restart(); + + if (Device.WaitFifo()) + { + Device.Statistics.RecordFifoStart(); + Device.ProcessFrame(); + Device.Statistics.RecordFifoEnd(); + } + + while (Device.ConsumeFrameAvailable()) + { + Device.PresentFrame(SwapBuffers); + } + + if (_ticks >= _ticksPerFrame) + { + string dockedMode = Device.System.State.DockedMode ? "Docked" : "Handheld"; + float scale = Graphics.Gpu.GraphicsConfig.ResScale; + if (scale != 1) + { + dockedMode += $" ({scale}x)"; + } + + StatusUpdatedEvent?.Invoke(this, new StatusUpdatedEventArgs( + Device.EnableDeviceVsync, + dockedMode, + Device.Configuration.AspectRatio.ToText(), + $"Game: {Device.Statistics.GetGameFrameRate():00.00} FPS", + $"FIFO: {Device.Statistics.GetFifoPercent():0.00} %", + $"GPU: {_gpuVendorName}")); + + _ticks = Math.Min(_ticks - _ticksPerFrame, _ticksPerFrame); + } } - } + }); FinalizeRenderer(); } @@ -295,16 +307,20 @@ namespace Ryujinx.Headless.SDL2 }; renderLoopThread.Start(); - Thread nvStutterWorkaround = new Thread(NVStutterWorkaround) + Thread nvStutterWorkaround = null; + if (Renderer is Graphics.OpenGL.Renderer) { - Name = "GUI.NVStutterWorkaround" - }; - nvStutterWorkaround.Start(); + nvStutterWorkaround = new Thread(NVStutterWorkaround) + { + Name = "GUI.NVStutterWorkaround" + }; + nvStutterWorkaround.Start(); + } MainLoop(); renderLoopThread.Join(); - nvStutterWorkaround.Join(); + nvStutterWorkaround?.Join(); Exit(); } diff --git a/Ryujinx.Memory/Tracking/MemoryTracking.cs b/Ryujinx.Memory/Tracking/MemoryTracking.cs index aafb418d..ed3d7e38 100644 --- a/Ryujinx.Memory/Tracking/MemoryTracking.cs +++ b/Ryujinx.Memory/Tracking/MemoryTracking.cs @@ -1,4 +1,6 @@ -using Ryujinx.Memory.Range; +using Ryujinx.Common.Pools; +using Ryujinx.Memory.Range; +using System; using System.Collections.Generic; namespace Ryujinx.Memory.Tracking @@ -14,9 +16,6 @@ namespace Ryujinx.Memory.Tracking // Only use these from within the lock. private readonly NonOverlappingRangeList _virtualRegions; - // Only use these from within the lock. - private readonly VirtualRegion[] _virtualResults = new VirtualRegion[10]; - private readonly int _pageSize; /// @@ -62,12 +61,13 @@ namespace Ryujinx.Memory.Tracking lock (TrackingLock) { - var results = _virtualResults; - int count = _virtualRegions.FindOverlapsNonOverlapping(va, size, ref results); + ref var overlaps = ref ThreadStaticArray.Get(); + + int count = _virtualRegions.FindOverlapsNonOverlapping(va, size, ref overlaps); for (int i = 0; i < count; i++) { - VirtualRegion region = results[i]; + VirtualRegion region = overlaps[i]; // If the region has been fully remapped, signal that it has been mapped again. bool remapped = _memoryManager.IsRangeMapped(region.Address, region.Size); @@ -94,12 +94,13 @@ namespace Ryujinx.Memory.Tracking lock (TrackingLock) { - var results = _virtualResults; - int count = _virtualRegions.FindOverlapsNonOverlapping(va, size, ref results); + ref var overlaps = ref ThreadStaticArray.Get(); + + int count = _virtualRegions.FindOverlapsNonOverlapping(va, size, ref overlaps); for (int i = 0; i < count; i++) { - VirtualRegion region = results[i]; + VirtualRegion region = overlaps[i]; region.SignalMappingChanged(false); } @@ -201,8 +202,9 @@ namespace Ryujinx.Memory.Tracking lock (TrackingLock) { - var results = _virtualResults; - int count = _virtualRegions.FindOverlapsNonOverlapping(address, size, ref results); + ref var overlaps = ref ThreadStaticArray.Get(); + + int count = _virtualRegions.FindOverlapsNonOverlapping(address, size, ref overlaps); if (count == 0) { @@ -221,7 +223,7 @@ namespace Ryujinx.Memory.Tracking for (int i = 0; i < count; i++) { - VirtualRegion region = results[i]; + VirtualRegion region = overlaps[i]; region.Signal(address, size, write); } } diff --git a/Ryujinx.Memory/Tracking/RegionHandle.cs b/Ryujinx.Memory/Tracking/RegionHandle.cs index 2e45ef80..2df02f1e 100644 --- a/Ryujinx.Memory/Tracking/RegionHandle.cs +++ b/Ryujinx.Memory/Tracking/RegionHandle.cs @@ -1,6 +1,7 @@ using Ryujinx.Memory.Range; using System; using System.Collections.Generic; +using System.Linq; using System.Threading; namespace Ryujinx.Memory.Tracking @@ -112,7 +113,7 @@ namespace Ryujinx.Memory.Tracking /// Signal that a memory action occurred within this handle's virtual regions. /// /// Whether the region was written to or read - internal void Signal(ulong address, ulong size, bool write) + internal void Signal(ulong address, ulong size, bool write, ref IList handleIterable) { RegionSignal action = Interlocked.Exchange(ref _preAction, null); @@ -124,7 +125,26 @@ namespace Ryujinx.Memory.Tracking return; } - action?.Invoke(address, size); + if (action != null) + { + // Copy the handles list in case it changes when we're out of the lock. + if (handleIterable is List) + { + handleIterable = handleIterable.ToArray(); + } + + // Temporarily release the tracking lock while we're running the action. + Monitor.Exit(_tracking.TrackingLock); + + try + { + action.Invoke(address, size); + } + finally + { + Monitor.Enter(_tracking.TrackingLock); + } + } if (write) { diff --git a/Ryujinx.Memory/Tracking/VirtualRegion.cs b/Ryujinx.Memory/Tracking/VirtualRegion.cs index e758f38e..40f56351 100644 --- a/Ryujinx.Memory/Tracking/VirtualRegion.cs +++ b/Ryujinx.Memory/Tracking/VirtualRegion.cs @@ -21,9 +21,11 @@ namespace Ryujinx.Memory.Tracking public override void Signal(ulong address, ulong size, bool write) { - foreach (var handle in Handles) + IList handles = Handles; + + for (int i = 0; i < handles.Count; i++) { - handle.Signal(address, size, write); + handles[i].Signal(address, size, write, ref handles); } UpdateProtection(); diff --git a/Ryujinx/Config.json b/Ryujinx/Config.json index 504a713f..d98a8e0c 100644 --- a/Ryujinx/Config.json +++ b/Ryujinx/Config.json @@ -1,6 +1,7 @@ { "version": 29, "enable_file_log": true, + "backend_threading": "Auto", "res_scale": 1, "res_scale_custom": 1, "max_anisotropy": -1, diff --git a/Ryujinx/Configuration/ConfigurationFileFormat.cs b/Ryujinx/Configuration/ConfigurationFileFormat.cs index ae43d587..fbfa9c60 100644 --- a/Ryujinx/Configuration/ConfigurationFileFormat.cs +++ b/Ryujinx/Configuration/ConfigurationFileFormat.cs @@ -14,7 +14,7 @@ namespace Ryujinx.Configuration /// /// The current version of the file format /// - public const int CurrentVersion = 30; + public const int CurrentVersion = 31; public int Version { get; set; } @@ -23,6 +23,11 @@ namespace Ryujinx.Configuration /// public bool EnableFileLog { get; set; } + /// + /// Whether or not backend threading is enabled. The "Auto" setting will determine whether threading should be enabled at runtime. + /// + public BackendThreading BackendThreading { get; set; } + /// /// Resolution Scale. An integer scale applied to applicable render targets. Values 1-4, or -1 to use a custom floating point scale instead. /// diff --git a/Ryujinx/Configuration/ConfigurationState.cs b/Ryujinx/Configuration/ConfigurationState.cs index 57e449c3..1476f623 100644 --- a/Ryujinx/Configuration/ConfigurationState.cs +++ b/Ryujinx/Configuration/ConfigurationState.cs @@ -301,6 +301,11 @@ namespace Ryujinx.Configuration /// public class GraphicsSection { + /// + /// Whether or not backend threading is enabled. The "Auto" setting will determine whether threading should be enabled at runtime. + /// + public ReactiveObject BackendThreading { get; private set; } + /// /// Max Anisotropy. Values range from 0 - 16. Set to -1 to let the game decide. /// @@ -338,6 +343,8 @@ namespace Ryujinx.Configuration public GraphicsSection() { + BackendThreading = new ReactiveObject(); + BackendThreading.Event += static (sender, e) => LogValueChange(sender, e, nameof(BackendThreading)); ResScale = new ReactiveObject(); ResScale.Event += static (sender, e) => LogValueChange(sender, e, nameof(ResScale)); ResScaleCustom = new ReactiveObject(); @@ -423,6 +430,7 @@ namespace Ryujinx.Configuration { Version = ConfigurationFileFormat.CurrentVersion, EnableFileLog = Logger.EnableFileLog, + BackendThreading = Graphics.BackendThreading, ResScale = Graphics.ResScale, ResScaleCustom = Graphics.ResScaleCustom, MaxAnisotropy = Graphics.MaxAnisotropy, @@ -491,6 +499,7 @@ namespace Ryujinx.Configuration public void LoadDefault() { Logger.EnableFileLog.Value = true; + Graphics.BackendThreading.Value = BackendThreading.Auto; Graphics.ResScale.Value = 1; Graphics.ResScaleCustom.Value = 1.0f; Graphics.MaxAnisotropy.Value = -1.0f; @@ -879,7 +888,7 @@ namespace Ryujinx.Configuration { Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 30."); - foreach(InputConfig config in configurationFileFormat.InputConfig) + foreach (InputConfig config in configurationFileFormat.InputConfig) { if (config is StandardControllerInputConfig controllerConfig) { @@ -895,7 +904,17 @@ namespace Ryujinx.Configuration configurationFileUpdated = true; } + if (configurationFileFormat.Version < 31) + { + Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 31."); + + configurationFileFormat.BackendThreading = BackendThreading.Auto; + + configurationFileUpdated = true; + } + Logger.EnableFileLog.Value = configurationFileFormat.EnableFileLog; + Graphics.BackendThreading.Value = configurationFileFormat.BackendThreading; Graphics.ResScale.Value = configurationFileFormat.ResScale; Graphics.ResScaleCustom.Value = configurationFileFormat.ResScaleCustom; Graphics.MaxAnisotropy.Value = configurationFileFormat.MaxAnisotropy; diff --git a/Ryujinx/Program.cs b/Ryujinx/Program.cs index 846275cd..1e0fdd3a 100644 --- a/Ryujinx/Program.cs +++ b/Ryujinx/Program.cs @@ -140,7 +140,8 @@ namespace Ryujinx PrintSystemInfo(); // Enable OGL multithreading on the driver, when available. - DriverUtilities.ToggleOGLThreading(true); + BackendThreading threadingMode = ConfigurationState.Instance.Graphics.BackendThreading; + DriverUtilities.ToggleOGLThreading(threadingMode == BackendThreading.Off); // Initialize Gtk. Application.Init(); diff --git a/Ryujinx/Ui/GLRenderer.cs b/Ryujinx/Ui/GLRenderer.cs index 4c3f8ce4..e10c03a3 100644 --- a/Ryujinx/Ui/GLRenderer.cs +++ b/Ryujinx/Ui/GLRenderer.cs @@ -1,16 +1,9 @@ -using ARMeilleure.Translation; -using ARMeilleure.Translation.PTC; -using Gdk; +using Gdk; using Gtk; using OpenTK.Graphics.OpenGL; -using Ryujinx.Common; using Ryujinx.Common.Configuration; -using Ryujinx.Configuration; using Ryujinx.Graphics.OpenGL; -using Ryujinx.HLE.HOS.Services.Hid; -using Ryujinx.Input; using Ryujinx.Input.HLE; -using Ryujinx.Ui.Widgets; using SPB.Graphics; using SPB.Graphics.OpenGL; using SPB.Platform; @@ -18,17 +11,10 @@ using SPB.Platform.GLX; using SPB.Platform.WGL; using SPB.Windowing; using System; -using System.Diagnostics; -using System.Linq; using System.Runtime.InteropServices; -using System.Threading; - -using Key = Ryujinx.Input.Key; namespace Ryujinx.Ui { - using Switch = HLE.Switch; - public class GlRenderer : RendererWidgetBase { private GraphicsDebugLevel _glLogLevel; diff --git a/Ryujinx/Ui/MainWindow.cs b/Ryujinx/Ui/MainWindow.cs index e13d70f9..fa46de61 100644 --- a/Ryujinx/Ui/MainWindow.cs +++ b/Ryujinx/Ui/MainWindow.cs @@ -27,6 +27,7 @@ using Ryujinx.Common.Logging; using Ryujinx.Common.System; using Ryujinx.Configuration; using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.GAL.Multithreading; using Ryujinx.Graphics.OpenGL; using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.FileSystem.Content; @@ -401,6 +402,17 @@ namespace Ryujinx.Ui renderer = new Renderer(); } + BackendThreading threadingMode = ConfigurationState.Instance.Graphics.BackendThreading; + + bool threadedGAL = threadingMode == BackendThreading.On || (threadingMode == BackendThreading.Auto && renderer.PreferThreading); + + if (threadedGAL) + { + renderer = new ThreadedRenderer(renderer); + } + + Logger.Info?.PrintMsg(LogClass.Gpu, $"Backend Threading ({threadingMode}): {threadedGAL}"); + IHardwareDeviceDriver deviceDriver = new DummyHardwareDeviceDriver(); if (ConfigurationState.Instance.System.AudioBackend.Value == AudioBackend.SDL2) diff --git a/Ryujinx/Ui/RendererWidgetBase.cs b/Ryujinx/Ui/RendererWidgetBase.cs index d099d509..da10ba47 100644 --- a/Ryujinx/Ui/RendererWidgetBase.cs +++ b/Ryujinx/Ui/RendererWidgetBase.cs @@ -7,6 +7,7 @@ using Ryujinx.Common.Configuration; using Ryujinx.Common.Logging; using Ryujinx.Configuration; using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.GAL.Multithreading; using Ryujinx.Input; using Ryujinx.Input.GTK3; using Ryujinx.Input.HLE; @@ -293,7 +294,15 @@ namespace Ryujinx.Ui public void Initialize(Switch device) { Device = device; - Renderer = Device.Gpu.Renderer; + + IRenderer renderer = Device.Gpu.Renderer; + + if (renderer is ThreadedRenderer tr) + { + renderer = tr.BaseRenderer; + } + + Renderer = renderer; Renderer?.Window.SetSize(_windowWidth, _windowHeight); if (Renderer != null) @@ -375,52 +384,55 @@ namespace Ryujinx.Ui _gpuVendorName = GetGpuVendorName(); - Device.Gpu.InitializeShaderCache(); - Translator.IsReadyForTranslation.Set(); - - while (_isActive) + Device.Gpu.Renderer.RunLoop(() => { - if (_isStopped) + Device.Gpu.InitializeShaderCache(); + Translator.IsReadyForTranslation.Set(); + + while (_isActive) { - return; - } - - _ticks += _chrono.ElapsedTicks; - - _chrono.Restart(); - - if (Device.WaitFifo()) - { - Device.Statistics.RecordFifoStart(); - Device.ProcessFrame(); - Device.Statistics.RecordFifoEnd(); - } - - while (Device.ConsumeFrameAvailable()) - { - Device.PresentFrame(SwapBuffers); - } - - if (_ticks >= _ticksPerFrame) - { - string dockedMode = ConfigurationState.Instance.System.EnableDockedMode ? "Docked" : "Handheld"; - float scale = Graphics.Gpu.GraphicsConfig.ResScale; - if (scale != 1) + if (_isStopped) { - dockedMode += $" ({scale}x)"; + return; } - StatusUpdatedEvent?.Invoke(this, new StatusUpdatedEventArgs( - Device.EnableDeviceVsync, - dockedMode, - ConfigurationState.Instance.Graphics.AspectRatio.Value.ToText(), - $"Game: {Device.Statistics.GetGameFrameRate():00.00} FPS", - $"FIFO: {Device.Statistics.GetFifoPercent():0.00} %", - $"GPU: {_gpuVendorName}")); + _ticks += _chrono.ElapsedTicks; - _ticks = Math.Min(_ticks - _ticksPerFrame, _ticksPerFrame); + _chrono.Restart(); + + if (Device.WaitFifo()) + { + Device.Statistics.RecordFifoStart(); + Device.ProcessFrame(); + Device.Statistics.RecordFifoEnd(); + } + + while (Device.ConsumeFrameAvailable()) + { + Device.PresentFrame(SwapBuffers); + } + + if (_ticks >= _ticksPerFrame) + { + string dockedMode = ConfigurationState.Instance.System.EnableDockedMode ? "Docked" : "Handheld"; + float scale = Graphics.Gpu.GraphicsConfig.ResScale; + if (scale != 1) + { + dockedMode += $" ({scale}x)"; + } + + StatusUpdatedEvent?.Invoke(this, new StatusUpdatedEventArgs( + Device.EnableDeviceVsync, + dockedMode, + ConfigurationState.Instance.Graphics.AspectRatio.Value.ToText(), + $"Game: {Device.Statistics.GetGameFrameRate():00.00} FPS", + $"FIFO: {Device.Statistics.GetFifoPercent():0.00} %", + $"GPU: {_gpuVendorName}")); + + _ticks = Math.Min(_ticks - _ticksPerFrame, _ticksPerFrame); + } } - } + }); } public void Start() @@ -455,16 +467,20 @@ namespace Ryujinx.Ui }; renderLoopThread.Start(); - Thread nvStutterWorkaround = new Thread(NVStutterWorkaround) + Thread nvStutterWorkaround = null; + if (Renderer is Graphics.OpenGL.Renderer) { - Name = "GUI.NVStutterWorkaround" - }; - nvStutterWorkaround.Start(); + nvStutterWorkaround = new Thread(NVStutterWorkaround) + { + Name = "GUI.NVStutterWorkaround" + }; + nvStutterWorkaround.Start(); + } MainLoop(); renderLoopThread.Join(); - nvStutterWorkaround.Join(); + nvStutterWorkaround?.Join(); Exit(); } diff --git a/Ryujinx/Ui/Windows/SettingsWindow.cs b/Ryujinx/Ui/Windows/SettingsWindow.cs index e7e89640..a4a006b4 100644 --- a/Ryujinx/Ui/Windows/SettingsWindow.cs +++ b/Ryujinx/Ui/Windows/SettingsWindow.cs @@ -4,11 +4,13 @@ using Ryujinx.Audio.Backends.SDL2; using Ryujinx.Audio.Backends.SoundIo; using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration.Hid; +using Ryujinx.Common.GraphicsDriver; using Ryujinx.Configuration; using Ryujinx.Configuration.System; using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.HOS.Services.Time.TimeZone; using Ryujinx.Ui.Helper; +using Ryujinx.Ui.Widgets; using System; using System.Collections.Generic; using System.Globalization; @@ -79,6 +81,7 @@ namespace Ryujinx.Ui.Windows [GUI] Label _custThemePathLabel; [GUI] TreeView _gameDirsBox; [GUI] Entry _addGameDirBox; + [GUI] ComboBoxText _galThreading; [GUI] Entry _graphicsShadersDumpPath; [GUI] ComboBoxText _anisotropy; [GUI] ComboBoxText _aspectRatio; @@ -124,6 +127,13 @@ namespace Ryujinx.Ui.Windows _systemTimeZoneEntry.FocusOutEvent += TimeZoneEntry_FocusOut; _resScaleCombo.Changed += (sender, args) => _resScaleText.Visible = _resScaleCombo.ActiveId == "-1"; + _galThreading.Changed += (sender, args) => + { + if (_galThreading.ActiveId != ConfigurationState.Instance.Graphics.BackendThreading.Value.ToString()) + { + GtkDialog.CreateInfoDialog("Warning - Backend Threading", "Ryujinx must be restarted after changing this option for it to apply fully. Depending on your platform, you may need to manually disable your driver's own multithreading when using Ryujinx's."); + } + }; // Setup Currents. if (ConfigurationState.Instance.Logger.EnableFileLog) @@ -291,6 +301,7 @@ namespace Ryujinx.Ui.Windows _systemLanguageSelect.SetActiveId(ConfigurationState.Instance.System.Language.Value.ToString()); _systemRegionSelect.SetActiveId(ConfigurationState.Instance.System.Region.Value.ToString()); + _galThreading.SetActiveId(ConfigurationState.Instance.Graphics.BackendThreading.Value.ToString()); _resScaleCombo.SetActiveId(ConfigurationState.Instance.Graphics.ResScale.Value.ToString()); _anisotropy.SetActiveId(ConfigurationState.Instance.Graphics.MaxAnisotropy.Value.ToString()); _aspectRatio.SetActiveId(((int)ConfigurationState.Instance.Graphics.AspectRatio.Value).ToString()); @@ -445,6 +456,12 @@ namespace Ryujinx.Ui.Windows memoryMode = MemoryManagerMode.HostMappedUnsafe; } + BackendThreading backendThreading = Enum.Parse(_galThreading.ActiveId); + if (ConfigurationState.Instance.Graphics.BackendThreading != backendThreading) + { + DriverUtilities.ToggleOGLThreading(backendThreading == BackendThreading.Off); + } + ConfigurationState.Instance.Logger.EnableError.Value = _errorLogToggle.Active; ConfigurationState.Instance.Logger.EnableWarn.Value = _warningLogToggle.Active; ConfigurationState.Instance.Logger.EnableInfo.Value = _infoLogToggle.Active; @@ -478,6 +495,7 @@ namespace Ryujinx.Ui.Windows ConfigurationState.Instance.System.FsGlobalAccessLogMode.Value = (int)_fsLogSpinAdjustment.Value; ConfigurationState.Instance.Graphics.MaxAnisotropy.Value = float.Parse(_anisotropy.ActiveId, CultureInfo.InvariantCulture); ConfigurationState.Instance.Graphics.AspectRatio.Value = Enum.Parse(_aspectRatio.ActiveId); + ConfigurationState.Instance.Graphics.BackendThreading.Value = backendThreading; ConfigurationState.Instance.Graphics.ResScale.Value = int.Parse(_resScaleCombo.ActiveId); ConfigurationState.Instance.Graphics.ResScaleCustom.Value = resScaleCustom; diff --git a/Ryujinx/Ui/Windows/SettingsWindow.glade b/Ryujinx/Ui/Windows/SettingsWindow.glade index e96ad64a..0d3fdf51 100644 --- a/Ryujinx/Ui/Windows/SettingsWindow.glade +++ b/Ryujinx/Ui/Windows/SettingsWindow.glade @@ -1802,6 +1802,100 @@ False 5 vertical + + + True + False + 5 + 5 + vertical + + + True + False + start + 5 + 5 + 5 + Features + + + + + + False + True + 0 + + + + + True + False + 10 + 10 + vertical + + + True + False + 5 + 5 + + + True + False + Enable Graphics Backend Multithreading + Graphics Backend Multithreading: + + + False + True + 5 + 0 + + + + + True + False + Executes graphics backend commands on a second thread. Allows runtime multithreading of shader compilation, reduces stuttering, and improves performance on drivers without multithreading support of their own. Slightly varying peak performance on drivers with multithreading. Ryujinx may need to be restarted to correctly disable driver built-in multithreading, or you may need to do it manually to get the best performance. + -1 + + Auto + Off + On + + + + False + True + 1 + + + + + False + True + 5 + 1 + + + + + False + True + 2 + + + + + False + True + 5 + 0 + + True diff --git a/Ryujinx/_schema.json b/Ryujinx/_schema.json index 47da28b1..356916a7 100644 --- a/Ryujinx/_schema.json +++ b/Ryujinx/_schema.json @@ -908,6 +908,18 @@ } }, "properties": { + "backend_threading": { + "$id": "#/properties/backend_threading", + "type": "string", + "title": "Backend Threading", + "description": "Whether backend threading is enabled or not. 'Auto' selects the most appropriate option for the current OS, vendor and backend.", + "default": "Auto", + "examples": [ + "Auto", + "Off", + "On" + ] + }, "res_scale": { "$id": "#/properties/res_scale", "type": "integer", @@ -1456,7 +1468,7 @@ "title": "Hotkey Controls", "required": [ "toggle_vsync", - "screenshot" + "screenshot" ], "properties": { "toggle_vsync": {