aboutsummaryrefslogtreecommitdiff
path: root/src/Ryujinx.Graphics.GAL
diff options
context:
space:
mode:
Diffstat (limited to 'src/Ryujinx.Graphics.GAL')
-rw-r--r--src/Ryujinx.Graphics.GAL/AddressMode.cs14
-rw-r--r--src/Ryujinx.Graphics.GAL/AdvancedBlendDescriptor.cs16
-rw-r--r--src/Ryujinx.Graphics.GAL/AdvancedBlendOp.cs52
-rw-r--r--src/Ryujinx.Graphics.GAL/AdvancedBlendOverlap.cs9
-rw-r--r--src/Ryujinx.Graphics.GAL/AntiAliasing.cs12
-rw-r--r--src/Ryujinx.Graphics.GAL/BlendDescriptor.cs35
-rw-r--r--src/Ryujinx.Graphics.GAL/BlendFactor.cs62
-rw-r--r--src/Ryujinx.Graphics.GAL/BlendOp.cs17
-rw-r--r--src/Ryujinx.Graphics.GAL/BufferAssignment.cs14
-rw-r--r--src/Ryujinx.Graphics.GAL/BufferHandle.cs14
-rw-r--r--src/Ryujinx.Graphics.GAL/BufferRange.cs21
-rw-r--r--src/Ryujinx.Graphics.GAL/Capabilities.cs140
-rw-r--r--src/Ryujinx.Graphics.GAL/ColorF.cs4
-rw-r--r--src/Ryujinx.Graphics.GAL/CompareMode.cs8
-rw-r--r--src/Ryujinx.Graphics.GAL/CompareOp.cs23
-rw-r--r--src/Ryujinx.Graphics.GAL/CounterType.cs9
-rw-r--r--src/Ryujinx.Graphics.GAL/DepthMode.cs8
-rw-r--r--src/Ryujinx.Graphics.GAL/DepthStencilMode.cs8
-rw-r--r--src/Ryujinx.Graphics.GAL/DepthTestDescriptor.cs20
-rw-r--r--src/Ryujinx.Graphics.GAL/DeviceInfo.cs18
-rw-r--r--src/Ryujinx.Graphics.GAL/Extents2D.cs31
-rw-r--r--src/Ryujinx.Graphics.GAL/Extents2DF.cs18
-rw-r--r--src/Ryujinx.Graphics.GAL/Face.cs9
-rw-r--r--src/Ryujinx.Graphics.GAL/Format.cs667
-rw-r--r--src/Ryujinx.Graphics.GAL/FrontFace.cs8
-rw-r--r--src/Ryujinx.Graphics.GAL/HardwareInfo.cs14
-rw-r--r--src/Ryujinx.Graphics.GAL/ICounterEvent.cs13
-rw-r--r--src/Ryujinx.Graphics.GAL/IPipeline.cs113
-rw-r--r--src/Ryujinx.Graphics.GAL/IProgram.cs11
-rw-r--r--src/Ryujinx.Graphics.GAL/IRenderer.cs65
-rw-r--r--src/Ryujinx.Graphics.GAL/ISampler.cs6
-rw-r--r--src/Ryujinx.Graphics.GAL/ITexture.cs27
-rw-r--r--src/Ryujinx.Graphics.GAL/IWindow.cs17
-rw-r--r--src/Ryujinx.Graphics.GAL/ImageCrop.cs37
-rw-r--r--src/Ryujinx.Graphics.GAL/IndexType.cs9
-rw-r--r--src/Ryujinx.Graphics.GAL/LogicalOp.cs22
-rw-r--r--src/Ryujinx.Graphics.GAL/MagFilter.cs8
-rw-r--r--src/Ryujinx.Graphics.GAL/MinFilter.cs12
-rw-r--r--src/Ryujinx.Graphics.GAL/MultisampleDescriptor.cs19
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/BufferMap.cs194
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/CommandHelper.cs149
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/CommandType.cs102
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/BarrierCommand.cs12
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/BeginTransformFeedbackCommand.cs18
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/Buffer/BufferDisposeCommand.cs19
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/Buffer/BufferGetDataCommand.cs29
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/Buffer/BufferSetDataCommand.cs27
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/ClearBufferCommand.cs24
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/ClearRenderTargetColorCommand.cs26
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/ClearRenderTargetDepthStencilCommand.cs28
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/CommandBufferBarrierCommand.cs12
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/CopyBufferCommand.cs26
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/CounterEvent/CounterEventDisposeCommand.cs21
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/CounterEvent/CounterEventFlushCommand.cs21
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/DispatchComputeCommand.cs22
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/DrawCommand.cs26
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/DrawIndexedCommand.cs24
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/DrawIndexedIndirectCommand.cs18
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/DrawIndexedIndirectCountCommand.cs29
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/DrawIndirectCommand.cs18
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/DrawIndirectCountCommand.cs29
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/DrawTextureCommand.cs31
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/EndHostConditionalRenderingCommand.cs12
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/EndTransformFeedbackCommand.cs12
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/IGALCommand.cs12
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/Program/ProgramCheckLinkCommand.cs27
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/Program/ProgramDisposeCommand.cs21
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/Program/ProgramGetBinaryCommand.cs25
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/ActionCommand.cs21
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateBufferCommand.cs29
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateProgramCommand.cs28
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateSamplerCommand.cs23
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateSyncCommand.cs22
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateTextureCommand.cs25
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/GetCapabilitiesCommand.cs20
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/PreFrameCommand.cs12
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/ReportCounterCommand.cs30
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/ResetCounterCommand.cs18
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/UpdateCountersCommand.cs12
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/Sampler/SamplerDisposeCommand.cs21
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetAlphaTestCommand.cs22
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetBlendStateAdvancedCommand.cs18
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetBlendStateCommand.cs20
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetDepthBiasCommand.cs24
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetDepthClampCommand.cs18
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetDepthModeCommand.cs18
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetDepthTestCommand.cs18
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetFaceCullingCommand.cs20
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetFrontFaceCommand.cs18
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetImageCommand.cs25
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetIndexBufferCommand.cs21
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetLineParametersCommand.cs20
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetLogicOpStateCommand.cs20
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetMultisampleStateCommand.cs18
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetPatchParametersCommand.cs25
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetPointParametersCommand.cs24
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetPolygonModeCommand.cs20
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetPrimitiveRestartCommand.cs20
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetPrimitiveTopologyCommand.cs18
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetProgramCommand.cs25
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetRasterizerDiscardCommand.cs18
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetRenderTargetColorMasksCommand.cs23
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetRenderTargetScaleCommand.cs18
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetRenderTargetsCommand.cs24
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetScissorsCommand.cs22
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetStencilTestCommand.cs18
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetStorageBuffersCommand.cs23
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetTextureAndSamplerCommand.cs28
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetTransformFeedbackBuffersCommand.cs23
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetUniformBuffersCommand.cs23
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetUserClipDistanceCommand.cs20
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetVertexAttribsCommand.cs23
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetVertexBuffersCommand.cs23
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetViewportsCommand.cs25
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureCopyToCommand.cs28
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureCopyToScaledCommand.cs30
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureCopyToSliceCommand.cs32
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureCreateViewCommand.cs30
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureGetDataCommand.cs26
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureGetDataSliceCommand.cs30
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureReleaseCommand.cs21
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureSetDataCommand.cs25
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureSetDataSliceCommand.cs29
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureSetDataSliceRegionCommand.cs31
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureSetStorageCommand.cs23
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/TextureBarrierCommand.cs12
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/TextureBarrierTiledCommand.cs12
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/TryHostConditionalRenderingCommand.cs25
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/TryHostConditionalRenderingFlushCommand.cs25
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/UpdateRenderScaleCommand.cs25
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Commands/Window/WindowPresentCommand.cs27
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Model/CircularSpanPool.cs89
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Model/ResultBox.cs7
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Model/SpanRef.cs39
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Model/TableRef.cs22
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Resources/ProgramQueue.cs107
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Resources/Programs/BinaryProgramRequest.cs25
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Resources/Programs/IProgramRequest.cs8
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Resources/Programs/SourceProgramRequest.cs23
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedCounterEvent.cs80
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedProgram.cs48
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedSampler.cs22
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedTexture.cs141
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/SyncMap.cs62
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/ThreadedHelpers.cs28
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs380
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/ThreadedRenderer.cs488
-rw-r--r--src/Ryujinx.Graphics.GAL/Multithreading/ThreadedWindow.cs42
-rw-r--r--src/Ryujinx.Graphics.GAL/Origin.cs8
-rw-r--r--src/Ryujinx.Graphics.GAL/PinnedSpan.cs53
-rw-r--r--src/Ryujinx.Graphics.GAL/PolygonMode.cs9
-rw-r--r--src/Ryujinx.Graphics.GAL/PolygonModeMask.cs12
-rw-r--r--src/Ryujinx.Graphics.GAL/PrimitiveTopology.cs21
-rw-r--r--src/Ryujinx.Graphics.GAL/ProgramLinkStatus.cs9
-rw-r--r--src/Ryujinx.Graphics.GAL/ProgramPipelineState.cs78
-rw-r--r--src/Ryujinx.Graphics.GAL/Rectangle.cs18
-rw-r--r--src/Ryujinx.Graphics.GAL/Ryujinx.Graphics.GAL.csproj20
-rw-r--r--src/Ryujinx.Graphics.GAL/SamplerCreateInfo.cs72
-rw-r--r--src/Ryujinx.Graphics.GAL/ScreenCaptureImageInfo.cs22
-rw-r--r--src/Ryujinx.Graphics.GAL/ShaderBindings.cs24
-rw-r--r--src/Ryujinx.Graphics.GAL/ShaderInfo.cs23
-rw-r--r--src/Ryujinx.Graphics.GAL/ShaderSource.cs31
-rw-r--r--src/Ryujinx.Graphics.GAL/StencilOp.cs23
-rw-r--r--src/Ryujinx.Graphics.GAL/StencilTestDescriptor.cs56
-rw-r--r--src/Ryujinx.Graphics.GAL/SupportBufferUpdater.cs101
-rw-r--r--src/Ryujinx.Graphics.GAL/SwizzleComponent.cs12
-rw-r--r--src/Ryujinx.Graphics.GAL/Target.cs34
-rw-r--r--src/Ryujinx.Graphics.GAL/TextureCreateInfo.cs164
-rw-r--r--src/Ryujinx.Graphics.GAL/TextureReleaseCallback.cs4
-rw-r--r--src/Ryujinx.Graphics.GAL/UpscaleType.cs9
-rw-r--r--src/Ryujinx.Graphics.GAL/VertexAttribDescriptor.cs4
-rw-r--r--src/Ryujinx.Graphics.GAL/VertexBufferDescriptor.cs17
-rw-r--r--src/Ryujinx.Graphics.GAL/Viewport.cs33
-rw-r--r--src/Ryujinx.Graphics.GAL/ViewportSwizzle.cs16
174 files changed, 6523 insertions, 0 deletions
diff --git a/src/Ryujinx.Graphics.GAL/AddressMode.cs b/src/Ryujinx.Graphics.GAL/AddressMode.cs
new file mode 100644
index 00000000..153925b1
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/AddressMode.cs
@@ -0,0 +1,14 @@
+namespace Ryujinx.Graphics.GAL
+{
+ public enum AddressMode
+ {
+ Repeat,
+ MirroredRepeat,
+ ClampToEdge,
+ ClampToBorder,
+ Clamp,
+ MirrorClampToEdge,
+ MirrorClampToBorder,
+ MirrorClamp
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.Graphics.GAL/AdvancedBlendDescriptor.cs b/src/Ryujinx.Graphics.GAL/AdvancedBlendDescriptor.cs
new file mode 100644
index 00000000..1f1f7c3f
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/AdvancedBlendDescriptor.cs
@@ -0,0 +1,16 @@
+namespace Ryujinx.Graphics.GAL
+{
+ public struct AdvancedBlendDescriptor
+ {
+ public AdvancedBlendOp Op { get; }
+ public AdvancedBlendOverlap Overlap { get; }
+ public bool SrcPreMultiplied { get; }
+
+ public AdvancedBlendDescriptor(AdvancedBlendOp op, AdvancedBlendOverlap overlap, bool srcPreMultiplied)
+ {
+ Op = op;
+ Overlap = overlap;
+ SrcPreMultiplied = srcPreMultiplied;
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/AdvancedBlendOp.cs b/src/Ryujinx.Graphics.GAL/AdvancedBlendOp.cs
new file mode 100644
index 00000000..4140bf49
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/AdvancedBlendOp.cs
@@ -0,0 +1,52 @@
+namespace Ryujinx.Graphics.GAL
+{
+ public enum AdvancedBlendOp
+ {
+ Zero,
+ Src,
+ Dst,
+ SrcOver,
+ DstOver,
+ SrcIn,
+ DstIn,
+ SrcOut,
+ DstOut,
+ SrcAtop,
+ DstAtop,
+ Xor,
+ Plus,
+ PlusClamped,
+ PlusClampedAlpha,
+ PlusDarker,
+ Multiply,
+ Screen,
+ Overlay,
+ Darken,
+ Lighten,
+ ColorDodge,
+ ColorBurn,
+ HardLight,
+ SoftLight,
+ Difference,
+ Minus,
+ MinusClamped,
+ Exclusion,
+ Contrast,
+ Invert,
+ InvertRGB,
+ InvertOvg,
+ LinearDodge,
+ LinearBurn,
+ VividLight,
+ LinearLight,
+ PinLight,
+ HardMix,
+ Red,
+ Green,
+ Blue,
+ HslHue,
+ HslSaturation,
+ HslColor,
+ HslLuminosity
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.Graphics.GAL/AdvancedBlendOverlap.cs b/src/Ryujinx.Graphics.GAL/AdvancedBlendOverlap.cs
new file mode 100644
index 00000000..d4feb2b3
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/AdvancedBlendOverlap.cs
@@ -0,0 +1,9 @@
+namespace Ryujinx.Graphics.GAL
+{
+ public enum AdvancedBlendOverlap
+ {
+ Uncorrelated,
+ Disjoint,
+ Conjoint
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/AntiAliasing.cs b/src/Ryujinx.Graphics.GAL/AntiAliasing.cs
new file mode 100644
index 00000000..d4e5754d
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/AntiAliasing.cs
@@ -0,0 +1,12 @@
+namespace Ryujinx.Graphics.GAL
+{
+ public enum AntiAliasing
+ {
+ None,
+ Fxaa,
+ SmaaLow,
+ SmaaMedium,
+ SmaaHigh,
+ SmaaUltra
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/BlendDescriptor.cs b/src/Ryujinx.Graphics.GAL/BlendDescriptor.cs
new file mode 100644
index 00000000..83b8afe2
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/BlendDescriptor.cs
@@ -0,0 +1,35 @@
+namespace Ryujinx.Graphics.GAL
+{
+ public readonly struct BlendDescriptor
+ {
+ public bool Enable { get; }
+
+ public ColorF BlendConstant { get; }
+ public BlendOp ColorOp { get; }
+ public BlendFactor ColorSrcFactor { get; }
+ public BlendFactor ColorDstFactor { get; }
+ public BlendOp AlphaOp { get; }
+ public BlendFactor AlphaSrcFactor { get; }
+ public BlendFactor AlphaDstFactor { get; }
+
+ public BlendDescriptor(
+ bool enable,
+ ColorF blendConstant,
+ BlendOp colorOp,
+ BlendFactor colorSrcFactor,
+ BlendFactor colorDstFactor,
+ BlendOp alphaOp,
+ BlendFactor alphaSrcFactor,
+ BlendFactor alphaDstFactor)
+ {
+ Enable = enable;
+ BlendConstant = blendConstant;
+ ColorOp = colorOp;
+ ColorSrcFactor = colorSrcFactor;
+ ColorDstFactor = colorDstFactor;
+ AlphaOp = alphaOp;
+ AlphaSrcFactor = alphaSrcFactor;
+ AlphaDstFactor = alphaDstFactor;
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/BlendFactor.cs b/src/Ryujinx.Graphics.GAL/BlendFactor.cs
new file mode 100644
index 00000000..4149ad51
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/BlendFactor.cs
@@ -0,0 +1,62 @@
+namespace Ryujinx.Graphics.GAL
+{
+ public enum BlendFactor
+ {
+ Zero = 1,
+ One,
+ SrcColor,
+ OneMinusSrcColor,
+ SrcAlpha,
+ OneMinusSrcAlpha,
+ DstAlpha,
+ OneMinusDstAlpha,
+ DstColor,
+ OneMinusDstColor,
+ SrcAlphaSaturate,
+ Src1Color = 0x10,
+ OneMinusSrc1Color,
+ Src1Alpha,
+ OneMinusSrc1Alpha,
+ ConstantColor = 0xc001,
+ OneMinusConstantColor,
+ ConstantAlpha,
+ OneMinusConstantAlpha,
+
+ ZeroGl = 0x4000,
+ OneGl = 0x4001,
+ SrcColorGl = 0x4300,
+ OneMinusSrcColorGl = 0x4301,
+ SrcAlphaGl = 0x4302,
+ OneMinusSrcAlphaGl = 0x4303,
+ DstAlphaGl = 0x4304,
+ OneMinusDstAlphaGl = 0x4305,
+ DstColorGl = 0x4306,
+ OneMinusDstColorGl = 0x4307,
+ SrcAlphaSaturateGl = 0x4308,
+ Src1ColorGl = 0xc900,
+ OneMinusSrc1ColorGl = 0xc901,
+ Src1AlphaGl = 0xc902,
+ OneMinusSrc1AlphaGl = 0xc903
+ }
+
+ public static class BlendFactorExtensions
+ {
+ public static bool IsDualSource(this BlendFactor factor)
+ {
+ switch (factor)
+ {
+ case BlendFactor.Src1Color:
+ case BlendFactor.Src1ColorGl:
+ case BlendFactor.Src1Alpha:
+ case BlendFactor.Src1AlphaGl:
+ case BlendFactor.OneMinusSrc1Color:
+ case BlendFactor.OneMinusSrc1ColorGl:
+ case BlendFactor.OneMinusSrc1Alpha:
+ case BlendFactor.OneMinusSrc1AlphaGl:
+ return true;
+ }
+
+ return false;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.Graphics.GAL/BlendOp.cs b/src/Ryujinx.Graphics.GAL/BlendOp.cs
new file mode 100644
index 00000000..b4a5a930
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/BlendOp.cs
@@ -0,0 +1,17 @@
+namespace Ryujinx.Graphics.GAL
+{
+ public enum BlendOp
+ {
+ Add = 1,
+ Subtract,
+ ReverseSubtract,
+ Minimum,
+ Maximum,
+
+ AddGl = 0x8006,
+ MinimumGl = 0x8007,
+ MaximumGl = 0x8008,
+ SubtractGl = 0x800a,
+ ReverseSubtractGl = 0x800b
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/BufferAssignment.cs b/src/Ryujinx.Graphics.GAL/BufferAssignment.cs
new file mode 100644
index 00000000..d803d90b
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/BufferAssignment.cs
@@ -0,0 +1,14 @@
+namespace Ryujinx.Graphics.GAL
+{
+ public readonly struct BufferAssignment
+ {
+ public readonly int Binding;
+ public readonly BufferRange Range;
+
+ public BufferAssignment(int binding, BufferRange range)
+ {
+ Binding = binding;
+ Range = range;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.Graphics.GAL/BufferHandle.cs b/src/Ryujinx.Graphics.GAL/BufferHandle.cs
new file mode 100644
index 00000000..5ba50d19
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/BufferHandle.cs
@@ -0,0 +1,14 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Graphics.GAL
+{
+ [StructLayout(LayoutKind.Sequential, Size = 8)]
+ public readonly record struct BufferHandle
+ {
+ private readonly ulong _value;
+
+ public static BufferHandle Null => new BufferHandle(0);
+
+ private BufferHandle(ulong value) => _value = value;
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/BufferRange.cs b/src/Ryujinx.Graphics.GAL/BufferRange.cs
new file mode 100644
index 00000000..ad9ebee4
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/BufferRange.cs
@@ -0,0 +1,21 @@
+namespace Ryujinx.Graphics.GAL
+{
+ public readonly struct BufferRange
+ {
+ private static readonly BufferRange _empty = new BufferRange(BufferHandle.Null, 0, 0);
+
+ public static BufferRange Empty => _empty;
+
+ public BufferHandle Handle { get; }
+
+ public int Offset { get; }
+ public int Size { get; }
+
+ public BufferRange(BufferHandle handle, int offset, int size)
+ {
+ Handle = handle;
+ Offset = offset;
+ Size = size;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.Graphics.GAL/Capabilities.cs b/src/Ryujinx.Graphics.GAL/Capabilities.cs
new file mode 100644
index 00000000..a93d3846
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Capabilities.cs
@@ -0,0 +1,140 @@
+using Ryujinx.Graphics.Shader.Translation;
+
+namespace Ryujinx.Graphics.GAL
+{
+ public readonly struct Capabilities
+ {
+ public readonly TargetApi Api;
+ public readonly string VendorName;
+
+ public readonly bool HasFrontFacingBug;
+ public readonly bool HasVectorIndexingBug;
+ public readonly bool NeedsFragmentOutputSpecialization;
+ public readonly bool ReduceShaderPrecision;
+
+ public readonly bool SupportsAstcCompression;
+ public readonly bool SupportsBc123Compression;
+ public readonly bool SupportsBc45Compression;
+ public readonly bool SupportsBc67Compression;
+ public readonly bool SupportsEtc2Compression;
+ public readonly bool Supports3DTextureCompression;
+ public readonly bool SupportsBgraFormat;
+ public readonly bool SupportsR4G4Format;
+ public readonly bool SupportsR4G4B4A4Format;
+ public readonly bool SupportsSnormBufferTextureFormat;
+ public readonly bool Supports5BitComponentFormat;
+ public readonly bool SupportsBlendEquationAdvanced;
+ public readonly bool SupportsFragmentShaderInterlock;
+ public readonly bool SupportsFragmentShaderOrderingIntel;
+ public readonly bool SupportsGeometryShader;
+ public readonly bool SupportsGeometryShaderPassthrough;
+ public readonly bool SupportsImageLoadFormatted;
+ public readonly bool SupportsLayerVertexTessellation;
+ public readonly bool SupportsMismatchingViewFormat;
+ public readonly bool SupportsCubemapView;
+ public readonly bool SupportsNonConstantTextureOffset;
+ public readonly bool SupportsShaderBallot;
+ public readonly bool SupportsTextureShadowLod;
+ public readonly bool SupportsViewportIndexVertexTessellation;
+ public readonly bool SupportsViewportMask;
+ public readonly bool SupportsViewportSwizzle;
+ public readonly bool SupportsIndirectParameters;
+
+ public readonly uint MaximumUniformBuffersPerStage;
+ public readonly uint MaximumStorageBuffersPerStage;
+ public readonly uint MaximumTexturesPerStage;
+ public readonly uint MaximumImagesPerStage;
+
+ public readonly int MaximumComputeSharedMemorySize;
+ public readonly float MaximumSupportedAnisotropy;
+ public readonly int StorageBufferOffsetAlignment;
+
+ public readonly int GatherBiasPrecision;
+
+ public Capabilities(
+ TargetApi api,
+ string vendorName,
+ bool hasFrontFacingBug,
+ bool hasVectorIndexingBug,
+ bool needsFragmentOutputSpecialization,
+ bool reduceShaderPrecision,
+ bool supportsAstcCompression,
+ bool supportsBc123Compression,
+ bool supportsBc45Compression,
+ bool supportsBc67Compression,
+ bool supportsEtc2Compression,
+ bool supports3DTextureCompression,
+ bool supportsBgraFormat,
+ bool supportsR4G4Format,
+ bool supportsR4G4B4A4Format,
+ bool supportsSnormBufferTextureFormat,
+ bool supports5BitComponentFormat,
+ bool supportsBlendEquationAdvanced,
+ bool supportsFragmentShaderInterlock,
+ bool supportsFragmentShaderOrderingIntel,
+ bool supportsGeometryShader,
+ bool supportsGeometryShaderPassthrough,
+ bool supportsImageLoadFormatted,
+ bool supportsLayerVertexTessellation,
+ bool supportsMismatchingViewFormat,
+ bool supportsCubemapView,
+ bool supportsNonConstantTextureOffset,
+ bool supportsShaderBallot,
+ bool supportsTextureShadowLod,
+ bool supportsViewportIndexVertexTessellation,
+ bool supportsViewportMask,
+ bool supportsViewportSwizzle,
+ bool supportsIndirectParameters,
+ uint maximumUniformBuffersPerStage,
+ uint maximumStorageBuffersPerStage,
+ uint maximumTexturesPerStage,
+ uint maximumImagesPerStage,
+ int maximumComputeSharedMemorySize,
+ float maximumSupportedAnisotropy,
+ int storageBufferOffsetAlignment,
+ int gatherBiasPrecision)
+ {
+ Api = api;
+ VendorName = vendorName;
+ HasFrontFacingBug = hasFrontFacingBug;
+ HasVectorIndexingBug = hasVectorIndexingBug;
+ NeedsFragmentOutputSpecialization = needsFragmentOutputSpecialization;
+ ReduceShaderPrecision = reduceShaderPrecision;
+ SupportsAstcCompression = supportsAstcCompression;
+ SupportsBc123Compression = supportsBc123Compression;
+ SupportsBc45Compression = supportsBc45Compression;
+ SupportsBc67Compression = supportsBc67Compression;
+ SupportsEtc2Compression = supportsEtc2Compression;
+ Supports3DTextureCompression = supports3DTextureCompression;
+ SupportsBgraFormat = supportsBgraFormat;
+ SupportsR4G4Format = supportsR4G4Format;
+ SupportsR4G4B4A4Format = supportsR4G4B4A4Format;
+ SupportsSnormBufferTextureFormat = supportsSnormBufferTextureFormat;
+ Supports5BitComponentFormat = supports5BitComponentFormat;
+ SupportsBlendEquationAdvanced = supportsBlendEquationAdvanced;
+ SupportsFragmentShaderInterlock = supportsFragmentShaderInterlock;
+ SupportsFragmentShaderOrderingIntel = supportsFragmentShaderOrderingIntel;
+ SupportsGeometryShader = supportsGeometryShader;
+ SupportsGeometryShaderPassthrough = supportsGeometryShaderPassthrough;
+ SupportsImageLoadFormatted = supportsImageLoadFormatted;
+ SupportsLayerVertexTessellation = supportsLayerVertexTessellation;
+ SupportsMismatchingViewFormat = supportsMismatchingViewFormat;
+ SupportsCubemapView = supportsCubemapView;
+ SupportsNonConstantTextureOffset = supportsNonConstantTextureOffset;
+ SupportsShaderBallot = supportsShaderBallot;
+ SupportsTextureShadowLod = supportsTextureShadowLod;
+ SupportsViewportIndexVertexTessellation = supportsViewportIndexVertexTessellation;
+ SupportsViewportMask = supportsViewportMask;
+ SupportsViewportSwizzle = supportsViewportSwizzle;
+ SupportsIndirectParameters = supportsIndirectParameters;
+ MaximumUniformBuffersPerStage = maximumUniformBuffersPerStage;
+ MaximumStorageBuffersPerStage = maximumStorageBuffersPerStage;
+ MaximumTexturesPerStage = maximumTexturesPerStage;
+ MaximumImagesPerStage = maximumImagesPerStage;
+ MaximumComputeSharedMemorySize = maximumComputeSharedMemorySize;
+ MaximumSupportedAnisotropy = maximumSupportedAnisotropy;
+ StorageBufferOffsetAlignment = storageBufferOffsetAlignment;
+ GatherBiasPrecision = gatherBiasPrecision;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.Graphics.GAL/ColorF.cs b/src/Ryujinx.Graphics.GAL/ColorF.cs
new file mode 100644
index 00000000..235f4229
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/ColorF.cs
@@ -0,0 +1,4 @@
+namespace Ryujinx.Graphics.GAL
+{
+ public readonly record struct ColorF(float Red, float Green, float Blue, float Alpha);
+}
diff --git a/src/Ryujinx.Graphics.GAL/CompareMode.cs b/src/Ryujinx.Graphics.GAL/CompareMode.cs
new file mode 100644
index 00000000..7a64d9bb
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/CompareMode.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.Graphics.GAL
+{
+ public enum CompareMode
+ {
+ None,
+ CompareRToTexture
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.Graphics.GAL/CompareOp.cs b/src/Ryujinx.Graphics.GAL/CompareOp.cs
new file mode 100644
index 00000000..358ed2b4
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/CompareOp.cs
@@ -0,0 +1,23 @@
+namespace Ryujinx.Graphics.GAL
+{
+ public enum CompareOp
+ {
+ Never = 1,
+ Less,
+ Equal,
+ LessOrEqual,
+ Greater,
+ NotEqual,
+ GreaterOrEqual,
+ Always,
+
+ NeverGl = 0x200,
+ LessGl = 0x201,
+ EqualGl = 0x202,
+ LessOrEqualGl = 0x203,
+ GreaterGl = 0x204,
+ NotEqualGl = 0x205,
+ GreaterOrEqualGl = 0x206,
+ AlwaysGl = 0x207,
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.Graphics.GAL/CounterType.cs b/src/Ryujinx.Graphics.GAL/CounterType.cs
new file mode 100644
index 00000000..9d7b3b98
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/CounterType.cs
@@ -0,0 +1,9 @@
+namespace Ryujinx.Graphics.GAL
+{
+ public enum CounterType
+ {
+ SamplesPassed,
+ PrimitivesGenerated,
+ TransformFeedbackPrimitivesWritten
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.Graphics.GAL/DepthMode.cs b/src/Ryujinx.Graphics.GAL/DepthMode.cs
new file mode 100644
index 00000000..aafbb65a
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/DepthMode.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.Graphics.GAL
+{
+ public enum DepthMode
+ {
+ MinusOneToOne,
+ ZeroToOne
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.Graphics.GAL/DepthStencilMode.cs b/src/Ryujinx.Graphics.GAL/DepthStencilMode.cs
new file mode 100644
index 00000000..e80d0d4b
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/DepthStencilMode.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.Graphics.GAL
+{
+ public enum DepthStencilMode
+ {
+ Depth,
+ Stencil
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.Graphics.GAL/DepthTestDescriptor.cs b/src/Ryujinx.Graphics.GAL/DepthTestDescriptor.cs
new file mode 100644
index 00000000..4c593392
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/DepthTestDescriptor.cs
@@ -0,0 +1,20 @@
+namespace Ryujinx.Graphics.GAL
+{
+ public readonly struct DepthTestDescriptor
+ {
+ public bool TestEnable { get; }
+ public bool WriteEnable { get; }
+
+ public CompareOp Func { get; }
+
+ public DepthTestDescriptor(
+ bool testEnable,
+ bool writeEnable,
+ CompareOp func)
+ {
+ TestEnable = testEnable;
+ WriteEnable = writeEnable;
+ Func = func;
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/DeviceInfo.cs b/src/Ryujinx.Graphics.GAL/DeviceInfo.cs
new file mode 100644
index 00000000..eb4a016f
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/DeviceInfo.cs
@@ -0,0 +1,18 @@
+namespace Ryujinx.Graphics.GAL
+{
+ public readonly struct DeviceInfo
+ {
+ public readonly string Id;
+ public readonly string Vendor;
+ public readonly string Name;
+ public readonly bool IsDiscrete;
+
+ public DeviceInfo(string id, string vendor, string name, bool isDiscrete)
+ {
+ Id = id;
+ Vendor = vendor;
+ Name = name;
+ IsDiscrete = isDiscrete;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.Graphics.GAL/Extents2D.cs b/src/Ryujinx.Graphics.GAL/Extents2D.cs
new file mode 100644
index 00000000..bac44f83
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Extents2D.cs
@@ -0,0 +1,31 @@
+using Ryujinx.Common;
+
+namespace Ryujinx.Graphics.GAL
+{
+ public readonly struct Extents2D
+ {
+ public int X1 { get; }
+ public int Y1 { get; }
+ public int X2 { get; }
+ public int Y2 { get; }
+
+ public Extents2D(int x1, int y1, int x2, int y2)
+ {
+ X1 = x1;
+ Y1 = y1;
+ X2 = x2;
+ Y2 = y2;
+ }
+
+ public Extents2D Reduce(int level)
+ {
+ int div = 1 << level;
+
+ return new Extents2D(
+ X1 >> level,
+ Y1 >> level,
+ BitUtils.DivRoundUp(X2, div),
+ BitUtils.DivRoundUp(Y2, div));
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.Graphics.GAL/Extents2DF.cs b/src/Ryujinx.Graphics.GAL/Extents2DF.cs
new file mode 100644
index 00000000..43f0e25e
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Extents2DF.cs
@@ -0,0 +1,18 @@
+namespace Ryujinx.Graphics.GAL
+{
+ public readonly struct Extents2DF
+ {
+ public float X1 { get; }
+ public float Y1 { get; }
+ public float X2 { get; }
+ public float Y2 { get; }
+
+ public Extents2DF(float x1, float y1, float x2, float y2)
+ {
+ X1 = x1;
+ Y1 = y1;
+ X2 = x2;
+ Y2 = y2;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.Graphics.GAL/Face.cs b/src/Ryujinx.Graphics.GAL/Face.cs
new file mode 100644
index 00000000..0587641f
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Face.cs
@@ -0,0 +1,9 @@
+namespace Ryujinx.Graphics.GAL
+{
+ public enum Face
+ {
+ Front = 0x404,
+ Back = 0x405,
+ FrontAndBack = 0x408
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.Graphics.GAL/Format.cs b/src/Ryujinx.Graphics.GAL/Format.cs
new file mode 100644
index 00000000..5e0274e5
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Format.cs
@@ -0,0 +1,667 @@
+namespace Ryujinx.Graphics.GAL
+{
+ public enum Format
+ {
+ R8Unorm,
+ R8Snorm,
+ R8Uint,
+ R8Sint,
+ R16Float,
+ R16Unorm,
+ R16Snorm,
+ R16Uint,
+ R16Sint,
+ R32Float,
+ R32Uint,
+ R32Sint,
+ R8G8Unorm,
+ R8G8Snorm,
+ R8G8Uint,
+ R8G8Sint,
+ R16G16Float,
+ R16G16Unorm,
+ R16G16Snorm,
+ R16G16Uint,
+ R16G16Sint,
+ R32G32Float,
+ R32G32Uint,
+ R32G32Sint,
+ R8G8B8Unorm,
+ R8G8B8Snorm,
+ R8G8B8Uint,
+ R8G8B8Sint,
+ R16G16B16Float,
+ R16G16B16Unorm,
+ R16G16B16Snorm,
+ R16G16B16Uint,
+ R16G16B16Sint,
+ R32G32B32Float,
+ R32G32B32Uint,
+ R32G32B32Sint,
+ R8G8B8A8Unorm,
+ R8G8B8A8Snorm,
+ R8G8B8A8Uint,
+ R8G8B8A8Sint,
+ R16G16B16A16Float,
+ R16G16B16A16Unorm,
+ R16G16B16A16Snorm,
+ R16G16B16A16Uint,
+ R16G16B16A16Sint,
+ R32G32B32A32Float,
+ R32G32B32A32Uint,
+ R32G32B32A32Sint,
+ S8Uint,
+ D16Unorm,
+ S8UintD24Unorm,
+ D32Float,
+ D24UnormS8Uint,
+ D32FloatS8Uint,
+ R8G8B8A8Srgb,
+ R4G4Unorm,
+ R4G4B4A4Unorm,
+ R5G5B5X1Unorm,
+ R5G5B5A1Unorm,
+ R5G6B5Unorm,
+ R10G10B10A2Unorm,
+ R10G10B10A2Uint,
+ R11G11B10Float,
+ R9G9B9E5Float,
+ Bc1RgbaUnorm,
+ Bc2Unorm,
+ Bc3Unorm,
+ Bc1RgbaSrgb,
+ Bc2Srgb,
+ Bc3Srgb,
+ Bc4Unorm,
+ Bc4Snorm,
+ Bc5Unorm,
+ Bc5Snorm,
+ Bc7Unorm,
+ Bc7Srgb,
+ Bc6HSfloat,
+ Bc6HUfloat,
+ Etc2RgbUnorm,
+ Etc2RgbaUnorm,
+ Etc2RgbPtaUnorm,
+ Etc2RgbSrgb,
+ Etc2RgbaSrgb,
+ Etc2RgbPtaSrgb,
+ R8Uscaled,
+ R8Sscaled,
+ R16Uscaled,
+ R16Sscaled,
+ R32Uscaled,
+ R32Sscaled,
+ R8G8Uscaled,
+ R8G8Sscaled,
+ R16G16Uscaled,
+ R16G16Sscaled,
+ R32G32Uscaled,
+ R32G32Sscaled,
+ R8G8B8Uscaled,
+ R8G8B8Sscaled,
+ R16G16B16Uscaled,
+ R16G16B16Sscaled,
+ R32G32B32Uscaled,
+ R32G32B32Sscaled,
+ R8G8B8A8Uscaled,
+ R8G8B8A8Sscaled,
+ R16G16B16A16Uscaled,
+ R16G16B16A16Sscaled,
+ R32G32B32A32Uscaled,
+ R32G32B32A32Sscaled,
+ R10G10B10A2Snorm,
+ R10G10B10A2Sint,
+ R10G10B10A2Uscaled,
+ R10G10B10A2Sscaled,
+ Astc4x4Unorm,
+ Astc5x4Unorm,
+ Astc5x5Unorm,
+ Astc6x5Unorm,
+ Astc6x6Unorm,
+ Astc8x5Unorm,
+ Astc8x6Unorm,
+ Astc8x8Unorm,
+ Astc10x5Unorm,
+ Astc10x6Unorm,
+ Astc10x8Unorm,
+ Astc10x10Unorm,
+ Astc12x10Unorm,
+ Astc12x12Unorm,
+ Astc4x4Srgb,
+ Astc5x4Srgb,
+ Astc5x5Srgb,
+ Astc6x5Srgb,
+ Astc6x6Srgb,
+ Astc8x5Srgb,
+ Astc8x6Srgb,
+ Astc8x8Srgb,
+ Astc10x5Srgb,
+ Astc10x6Srgb,
+ Astc10x8Srgb,
+ Astc10x10Srgb,
+ Astc12x10Srgb,
+ Astc12x12Srgb,
+ B5G6R5Unorm,
+ B5G5R5A1Unorm,
+ A1B5G5R5Unorm,
+ B8G8R8A8Unorm,
+ B8G8R8A8Srgb
+ }
+
+ public static class FormatExtensions
+ {
+ /// <summary>
+ /// The largest scalar size for a buffer format.
+ /// </summary>
+ public const int MaxBufferFormatScalarSize = 4;
+
+ /// <summary>
+ /// Gets the byte size for a single component of this format, or its packed size.
+ /// </summary>
+ /// <param name="format">Texture format</param>
+ /// <returns>Byte size for a single component, or packed size</returns>
+ public static int GetScalarSize(this Format format)
+ {
+ switch (format)
+ {
+ case Format.R8Unorm:
+ case Format.R8Snorm:
+ case Format.R8Uint:
+ case Format.R8Sint:
+ case Format.R8G8Unorm:
+ case Format.R8G8Snorm:
+ case Format.R8G8Uint:
+ case Format.R8G8Sint:
+ case Format.R8G8B8Unorm:
+ case Format.R8G8B8Snorm:
+ case Format.R8G8B8Uint:
+ case Format.R8G8B8Sint:
+ case Format.R8G8B8A8Unorm:
+ case Format.R8G8B8A8Snorm:
+ case Format.R8G8B8A8Uint:
+ case Format.R8G8B8A8Sint:
+ case Format.R8G8B8A8Srgb:
+ case Format.R4G4Unorm:
+ case Format.R8Uscaled:
+ case Format.R8Sscaled:
+ case Format.R8G8Uscaled:
+ case Format.R8G8Sscaled:
+ case Format.R8G8B8Uscaled:
+ case Format.R8G8B8Sscaled:
+ case Format.R8G8B8A8Uscaled:
+ case Format.R8G8B8A8Sscaled:
+ case Format.B8G8R8A8Unorm:
+ case Format.B8G8R8A8Srgb:
+ return 1;
+
+ case Format.R16Float:
+ case Format.R16Unorm:
+ case Format.R16Snorm:
+ case Format.R16Uint:
+ case Format.R16Sint:
+ case Format.R16G16Float:
+ case Format.R16G16Unorm:
+ case Format.R16G16Snorm:
+ case Format.R16G16Uint:
+ case Format.R16G16Sint:
+ case Format.R16G16B16Float:
+ case Format.R16G16B16Unorm:
+ case Format.R16G16B16Snorm:
+ case Format.R16G16B16Uint:
+ case Format.R16G16B16Sint:
+ case Format.R16G16B16A16Float:
+ case Format.R16G16B16A16Unorm:
+ case Format.R16G16B16A16Snorm:
+ case Format.R16G16B16A16Uint:
+ case Format.R16G16B16A16Sint:
+ case Format.R4G4B4A4Unorm:
+ case Format.R5G5B5X1Unorm:
+ case Format.R5G5B5A1Unorm:
+ case Format.R5G6B5Unorm:
+ case Format.R16Uscaled:
+ case Format.R16Sscaled:
+ case Format.R16G16Uscaled:
+ case Format.R16G16Sscaled:
+ case Format.R16G16B16Uscaled:
+ case Format.R16G16B16Sscaled:
+ case Format.R16G16B16A16Uscaled:
+ case Format.R16G16B16A16Sscaled:
+ case Format.B5G6R5Unorm:
+ case Format.B5G5R5A1Unorm:
+ case Format.A1B5G5R5Unorm:
+ return 2;
+
+ case Format.R32Float:
+ case Format.R32Uint:
+ case Format.R32Sint:
+ case Format.R32G32Float:
+ case Format.R32G32Uint:
+ case Format.R32G32Sint:
+ case Format.R32G32B32Float:
+ case Format.R32G32B32Uint:
+ case Format.R32G32B32Sint:
+ case Format.R32G32B32A32Float:
+ case Format.R32G32B32A32Uint:
+ case Format.R32G32B32A32Sint:
+ case Format.R10G10B10A2Unorm:
+ case Format.R10G10B10A2Uint:
+ case Format.R11G11B10Float:
+ case Format.R9G9B9E5Float:
+ case Format.R32Uscaled:
+ case Format.R32Sscaled:
+ case Format.R32G32Uscaled:
+ case Format.R32G32Sscaled:
+ case Format.R32G32B32Uscaled:
+ case Format.R32G32B32Sscaled:
+ case Format.R32G32B32A32Uscaled:
+ case Format.R32G32B32A32Sscaled:
+ case Format.R10G10B10A2Snorm:
+ case Format.R10G10B10A2Sint:
+ case Format.R10G10B10A2Uscaled:
+ case Format.R10G10B10A2Sscaled:
+ return 4;
+
+ case Format.S8Uint:
+ return 1;
+ case Format.D16Unorm:
+ return 2;
+ case Format.S8UintD24Unorm:
+ case Format.D32Float:
+ case Format.D24UnormS8Uint:
+ return 4;
+ case Format.D32FloatS8Uint:
+ return 8;
+
+ case Format.Bc1RgbaUnorm:
+ case Format.Bc1RgbaSrgb:
+ return 8;
+
+ case Format.Bc2Unorm:
+ case Format.Bc3Unorm:
+ case Format.Bc2Srgb:
+ case Format.Bc3Srgb:
+ case Format.Bc4Unorm:
+ case Format.Bc4Snorm:
+ case Format.Bc5Unorm:
+ case Format.Bc5Snorm:
+ case Format.Bc7Unorm:
+ case Format.Bc7Srgb:
+ case Format.Bc6HSfloat:
+ case Format.Bc6HUfloat:
+ return 16;
+
+ case Format.Etc2RgbUnorm:
+ case Format.Etc2RgbPtaUnorm:
+ case Format.Etc2RgbSrgb:
+ case Format.Etc2RgbPtaSrgb:
+ return 8;
+
+ case Format.Etc2RgbaUnorm:
+ case Format.Etc2RgbaSrgb:
+ return 16;
+
+ case Format.Astc4x4Unorm:
+ case Format.Astc5x4Unorm:
+ case Format.Astc5x5Unorm:
+ case Format.Astc6x5Unorm:
+ case Format.Astc6x6Unorm:
+ case Format.Astc8x5Unorm:
+ case Format.Astc8x6Unorm:
+ case Format.Astc8x8Unorm:
+ case Format.Astc10x5Unorm:
+ case Format.Astc10x6Unorm:
+ case Format.Astc10x8Unorm:
+ case Format.Astc10x10Unorm:
+ case Format.Astc12x10Unorm:
+ case Format.Astc12x12Unorm:
+ case Format.Astc4x4Srgb:
+ case Format.Astc5x4Srgb:
+ case Format.Astc5x5Srgb:
+ case Format.Astc6x5Srgb:
+ case Format.Astc6x6Srgb:
+ case Format.Astc8x5Srgb:
+ case Format.Astc8x6Srgb:
+ case Format.Astc8x8Srgb:
+ case Format.Astc10x5Srgb:
+ case Format.Astc10x6Srgb:
+ case Format.Astc10x8Srgb:
+ case Format.Astc10x10Srgb:
+ case Format.Astc12x10Srgb:
+ case Format.Astc12x12Srgb:
+ return 16;
+ }
+
+ return 1;
+ }
+
+ /// <summary>
+ /// Checks if the texture format is valid to use as image format.
+ /// </summary>
+ /// <param name="format">Texture format</param>
+ /// <returns>True if the texture can be used as image, false otherwise</returns>
+ public static bool IsImageCompatible(this Format format)
+ {
+ switch (format)
+ {
+ case Format.R8Unorm:
+ case Format.R8Snorm:
+ case Format.R8Uint:
+ case Format.R8Sint:
+ case Format.R16Float:
+ case Format.R16Unorm:
+ case Format.R16Snorm:
+ case Format.R16Uint:
+ case Format.R16Sint:
+ case Format.R32Float:
+ case Format.R32Uint:
+ case Format.R32Sint:
+ case Format.R8G8Unorm:
+ case Format.R8G8Snorm:
+ case Format.R8G8Uint:
+ case Format.R8G8Sint:
+ case Format.R16G16Float:
+ case Format.R16G16Unorm:
+ case Format.R16G16Snorm:
+ case Format.R16G16Uint:
+ case Format.R16G16Sint:
+ case Format.R32G32Float:
+ case Format.R32G32Uint:
+ case Format.R32G32Sint:
+ case Format.R8G8B8A8Unorm:
+ case Format.R8G8B8A8Snorm:
+ case Format.R8G8B8A8Uint:
+ case Format.R8G8B8A8Sint:
+ case Format.R16G16B16A16Float:
+ case Format.R16G16B16A16Unorm:
+ case Format.R16G16B16A16Snorm:
+ case Format.R16G16B16A16Uint:
+ case Format.R16G16B16A16Sint:
+ case Format.R32G32B32A32Float:
+ case Format.R32G32B32A32Uint:
+ case Format.R32G32B32A32Sint:
+ case Format.R10G10B10A2Unorm:
+ case Format.R10G10B10A2Uint:
+ case Format.R11G11B10Float:
+ return true;
+ }
+
+ return false;
+ }
+
+ /// <summary>
+ /// Checks if the texture format is valid to use as render target color format.
+ /// </summary>
+ /// <param name="format">Texture format</param>
+ /// <returns>True if the texture can be used as render target, false otherwise</returns>
+ public static bool IsRtColorCompatible(this Format format)
+ {
+ switch (format)
+ {
+ case Format.R32G32B32A32Float:
+ case Format.R32G32B32A32Sint:
+ case Format.R32G32B32A32Uint:
+ case Format.R16G16B16A16Unorm:
+ case Format.R16G16B16A16Snorm:
+ case Format.R16G16B16A16Sint:
+ case Format.R16G16B16A16Uint:
+ case Format.R16G16B16A16Float:
+ case Format.R32G32Float:
+ case Format.R32G32Sint:
+ case Format.R32G32Uint:
+ case Format.B8G8R8A8Unorm:
+ case Format.B8G8R8A8Srgb:
+ case Format.R10G10B10A2Unorm:
+ case Format.R10G10B10A2Uint:
+ case Format.R8G8B8A8Unorm:
+ case Format.R8G8B8A8Srgb:
+ case Format.R8G8B8A8Snorm:
+ case Format.R8G8B8A8Sint:
+ case Format.R8G8B8A8Uint:
+ case Format.R16G16Unorm:
+ case Format.R16G16Snorm:
+ case Format.R16G16Sint:
+ case Format.R16G16Uint:
+ case Format.R16G16Float:
+ case Format.R11G11B10Float:
+ case Format.R32Sint:
+ case Format.R32Uint:
+ case Format.R32Float:
+ case Format.B5G6R5Unorm:
+ case Format.B5G5R5A1Unorm:
+ case Format.R8G8Unorm:
+ case Format.R8G8Snorm:
+ case Format.R8G8Sint:
+ case Format.R8G8Uint:
+ case Format.R16Unorm:
+ case Format.R16Snorm:
+ case Format.R16Sint:
+ case Format.R16Uint:
+ case Format.R16Float:
+ case Format.R8Unorm:
+ case Format.R8Snorm:
+ case Format.R8Sint:
+ case Format.R8Uint:
+ return true;
+ }
+
+ return false;
+ }
+
+ /// <summary>
+ /// Checks if the texture format is 16 bit packed.
+ /// </summary>
+ /// <param name="format">Texture format</param>
+ /// <returns>True if the texture format is 16 bit packed, false otherwise</returns>
+ public static bool Is16BitPacked(this Format format)
+ {
+ switch (format)
+ {
+ case Format.B5G6R5Unorm:
+ case Format.B5G5R5A1Unorm:
+ case Format.R5G5B5X1Unorm:
+ case Format.R5G5B5A1Unorm:
+ case Format.R5G6B5Unorm:
+ case Format.R4G4B4A4Unorm:
+ return true;
+ }
+
+ return false;
+ }
+
+ /// <summary>
+ /// Checks if the texture format is an ASTC format.
+ /// </summary>
+ /// <param name="format">Texture format</param>
+ /// <returns>True if the texture format is an ASTC format, false otherwise</returns>
+ public static bool IsAstc(this Format format)
+ {
+ return format.IsAstcUnorm() || format.IsAstcSrgb();
+ }
+
+ /// <summary>
+ /// Checks if the texture format is an ASTC Unorm format.
+ /// </summary>
+ /// <param name="format">Texture format</param>
+ /// <returns>True if the texture format is an ASTC Unorm format, false otherwise</returns>
+ public static bool IsAstcUnorm(this Format format)
+ {
+ switch (format)
+ {
+ case Format.Astc4x4Unorm:
+ case Format.Astc5x4Unorm:
+ case Format.Astc5x5Unorm:
+ case Format.Astc6x5Unorm:
+ case Format.Astc6x6Unorm:
+ case Format.Astc8x5Unorm:
+ case Format.Astc8x6Unorm:
+ case Format.Astc8x8Unorm:
+ case Format.Astc10x5Unorm:
+ case Format.Astc10x6Unorm:
+ case Format.Astc10x8Unorm:
+ case Format.Astc10x10Unorm:
+ case Format.Astc12x10Unorm:
+ case Format.Astc12x12Unorm:
+ return true;
+ }
+
+ return false;
+ }
+
+ /// <summary>
+ /// Checks if the texture format is an ASTC SRGB format.
+ /// </summary>
+ /// <param name="format">Texture format</param>
+ /// <returns>True if the texture format is an ASTC SRGB format, false otherwise</returns>
+ public static bool IsAstcSrgb(this Format format)
+ {
+ switch (format)
+ {
+ case Format.Astc4x4Srgb:
+ case Format.Astc5x4Srgb:
+ case Format.Astc5x5Srgb:
+ case Format.Astc6x5Srgb:
+ case Format.Astc6x6Srgb:
+ case Format.Astc8x5Srgb:
+ case Format.Astc8x6Srgb:
+ case Format.Astc8x8Srgb:
+ case Format.Astc10x5Srgb:
+ case Format.Astc10x6Srgb:
+ case Format.Astc10x8Srgb:
+ case Format.Astc10x10Srgb:
+ case Format.Astc12x10Srgb:
+ case Format.Astc12x12Srgb:
+ return true;
+ }
+
+ return false;
+ }
+
+ /// <summary>
+ /// Checks if the texture format is an ETC2 format.
+ /// </summary>
+ /// <param name="format">Texture format</param>
+ /// <returns>True if the texture format is an ETC2 format, false otherwise</returns>
+ public static bool IsEtc2(this Format format)
+ {
+ switch (format)
+ {
+ case Format.Etc2RgbaSrgb:
+ case Format.Etc2RgbaUnorm:
+ case Format.Etc2RgbPtaSrgb:
+ case Format.Etc2RgbPtaUnorm:
+ case Format.Etc2RgbSrgb:
+ case Format.Etc2RgbUnorm:
+ return true;
+ }
+
+ return false;
+ }
+
+ /// <summary>
+ /// Checks if the texture format is a BGR format.
+ /// </summary>
+ /// <param name="format">Texture format</param>
+ /// <returns>True if the texture format is a BGR format, false otherwise</returns>
+ public static bool IsBgr(this Format format)
+ {
+ switch (format)
+ {
+ case Format.B5G6R5Unorm:
+ case Format.B5G5R5A1Unorm:
+ case Format.B8G8R8A8Unorm:
+ case Format.B8G8R8A8Srgb:
+ return true;
+ }
+
+ return false;
+ }
+
+ /// <summary>
+ /// Checks if the texture format is a depth, stencil or depth-stencil format.
+ /// </summary>
+ /// <param name="format">Texture format</param>
+ /// <returns>True if the format is a depth, stencil or depth-stencil format, false otherwise</returns>
+ public static bool IsDepthOrStencil(this Format format)
+ {
+ switch (format)
+ {
+ case Format.D16Unorm:
+ case Format.D24UnormS8Uint:
+ case Format.S8UintD24Unorm:
+ case Format.D32Float:
+ case Format.D32FloatS8Uint:
+ case Format.S8Uint:
+ return true;
+ }
+
+ return false;
+ }
+
+ /// <summary>
+ /// Checks if the texture format is an unsigned integer color format.
+ /// </summary>
+ /// <param name="format">Texture format</param>
+ /// <returns>True if the texture format is an unsigned integer color format, false otherwise</returns>
+ public static bool IsUint(this Format format)
+ {
+ switch (format)
+ {
+ case Format.R8Uint:
+ case Format.R16Uint:
+ case Format.R32Uint:
+ case Format.R8G8Uint:
+ case Format.R16G16Uint:
+ case Format.R32G32Uint:
+ case Format.R8G8B8Uint:
+ case Format.R16G16B16Uint:
+ case Format.R32G32B32Uint:
+ case Format.R8G8B8A8Uint:
+ case Format.R16G16B16A16Uint:
+ case Format.R32G32B32A32Uint:
+ case Format.R10G10B10A2Uint:
+ return true;
+ }
+
+ return false;
+ }
+
+ /// <summary>
+ /// Checks if the texture format is a signed integer color format.
+ /// </summary>
+ /// <param name="format">Texture format</param>
+ /// <returns>True if the texture format is a signed integer color format, false otherwise</returns>
+ public static bool IsSint(this Format format)
+ {
+ switch (format)
+ {
+ case Format.R8Sint:
+ case Format.R16Sint:
+ case Format.R32Sint:
+ case Format.R8G8Sint:
+ case Format.R16G16Sint:
+ case Format.R32G32Sint:
+ case Format.R8G8B8Sint:
+ case Format.R16G16B16Sint:
+ case Format.R32G32B32Sint:
+ case Format.R8G8B8A8Sint:
+ case Format.R16G16B16A16Sint:
+ case Format.R32G32B32A32Sint:
+ case Format.R10G10B10A2Sint:
+ return true;
+ }
+
+ return false;
+ }
+
+ /// <summary>
+ /// Checks if the texture format is an integer color format.
+ /// </summary>
+ /// <param name="format">Texture format</param>
+ /// <returns>True if the texture format is an integer color format, false otherwise</returns>
+ public static bool IsInteger(this Format format)
+ {
+ return format.IsUint() || format.IsSint();
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.Graphics.GAL/FrontFace.cs b/src/Ryujinx.Graphics.GAL/FrontFace.cs
new file mode 100644
index 00000000..aa6bfdc5
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/FrontFace.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.Graphics.GAL
+{
+ public enum FrontFace
+ {
+ Clockwise = 0x900,
+ CounterClockwise = 0x901
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.Graphics.GAL/HardwareInfo.cs b/src/Ryujinx.Graphics.GAL/HardwareInfo.cs
new file mode 100644
index 00000000..4dd6849b
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/HardwareInfo.cs
@@ -0,0 +1,14 @@
+namespace Ryujinx.Graphics.GAL
+{
+ public readonly struct HardwareInfo
+ {
+ public string GpuVendor { get; }
+ public string GpuModel { get; }
+
+ public HardwareInfo(string gpuVendor, string gpuModel)
+ {
+ GpuVendor = gpuVendor;
+ GpuModel = gpuModel;
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/ICounterEvent.cs b/src/Ryujinx.Graphics.GAL/ICounterEvent.cs
new file mode 100644
index 00000000..13b15ae4
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/ICounterEvent.cs
@@ -0,0 +1,13 @@
+using System;
+
+namespace Ryujinx.Graphics.GAL
+{
+ public interface ICounterEvent : IDisposable
+ {
+ bool Invalid { get; set; }
+
+ bool ReserveForHostAccess();
+
+ void Flush();
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/IPipeline.cs b/src/Ryujinx.Graphics.GAL/IPipeline.cs
new file mode 100644
index 00000000..0a362081
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/IPipeline.cs
@@ -0,0 +1,113 @@
+using Ryujinx.Graphics.Shader;
+using System;
+
+namespace Ryujinx.Graphics.GAL
+{
+ public interface IPipeline
+ {
+ void Barrier();
+
+ void BeginTransformFeedback(PrimitiveTopology topology);
+
+ void ClearBuffer(BufferHandle destination, int offset, int size, uint value);
+
+ void ClearRenderTargetColor(int index, int layer, int layerCount, uint componentMask, ColorF color);
+
+ void ClearRenderTargetDepthStencil(
+ int layer,
+ int layerCount,
+ float depthValue,
+ bool depthMask,
+ int stencilValue,
+ int stencilMask);
+
+ void CommandBufferBarrier();
+
+ void CopyBuffer(BufferHandle source, BufferHandle destination, int srcOffset, int dstOffset, int size);
+
+ void DispatchCompute(int groupsX, int groupsY, int groupsZ);
+
+ void Draw(int vertexCount, int instanceCount, int firstVertex, int firstInstance);
+ void DrawIndexed(
+ int indexCount,
+ int instanceCount,
+ int firstIndex,
+ int firstVertex,
+ int firstInstance);
+ void DrawIndexedIndirect(BufferRange indirectBuffer);
+ void DrawIndexedIndirectCount(BufferRange indirectBuffer, BufferRange parameterBuffer, int maxDrawCount, int stride);
+ void DrawIndirect(BufferRange indirectBuffer);
+ void DrawIndirectCount(BufferRange indirectBuffer, BufferRange parameterBuffer, int maxDrawCount, int stride);
+ void DrawTexture(ITexture texture, ISampler sampler, Extents2DF srcRegion, Extents2DF dstRegion);
+
+ void EndTransformFeedback();
+
+ void SetAlphaTest(bool enable, float reference, CompareOp op);
+
+ void SetBlendState(AdvancedBlendDescriptor blend);
+ void SetBlendState(int index, BlendDescriptor blend);
+
+ void SetDepthBias(PolygonModeMask enables, float factor, float units, float clamp);
+ void SetDepthClamp(bool clamp);
+ void SetDepthMode(DepthMode mode);
+ void SetDepthTest(DepthTestDescriptor depthTest);
+
+ void SetFaceCulling(bool enable, Face face);
+
+ void SetFrontFace(FrontFace frontFace);
+
+ void SetIndexBuffer(BufferRange buffer, IndexType type);
+
+ void SetImage(int binding, ITexture texture, Format imageFormat);
+
+ void SetLineParameters(float width, bool smooth);
+
+ void SetLogicOpState(bool enable, LogicalOp op);
+
+ void SetMultisampleState(MultisampleDescriptor multisample);
+
+ void SetPatchParameters(int vertices, ReadOnlySpan<float> defaultOuterLevel, ReadOnlySpan<float> defaultInnerLevel);
+ void SetPointParameters(float size, bool isProgramPointSize, bool enablePointSprite, Origin origin);
+
+ void SetPolygonMode(PolygonMode frontMode, PolygonMode backMode);
+
+ void SetPrimitiveRestart(bool enable, int index);
+
+ void SetPrimitiveTopology(PrimitiveTopology topology);
+
+ void SetProgram(IProgram program);
+
+ void SetRasterizerDiscard(bool discard);
+
+ void SetRenderTargetScale(float scale);
+ void SetRenderTargetColorMasks(ReadOnlySpan<uint> componentMask);
+ void SetRenderTargets(ITexture[] colors, ITexture depthStencil);
+
+ void SetScissors(ReadOnlySpan<Rectangle<int>> regions);
+
+ void SetStencilTest(StencilTestDescriptor stencilTest);
+
+ void SetStorageBuffers(ReadOnlySpan<BufferAssignment> buffers);
+
+ void SetTextureAndSampler(ShaderStage stage, int binding, ITexture texture, ISampler sampler);
+
+ void SetTransformFeedbackBuffers(ReadOnlySpan<BufferRange> buffers);
+ void SetUniformBuffers(ReadOnlySpan<BufferAssignment> buffers);
+
+ void SetUserClipDistance(int index, bool enableClip);
+
+ void SetVertexAttribs(ReadOnlySpan<VertexAttribDescriptor> vertexAttribs);
+ void SetVertexBuffers(ReadOnlySpan<VertexBufferDescriptor> vertexBuffers);
+
+ void SetViewports(ReadOnlySpan<Viewport> viewports, bool disableTransform);
+
+ void TextureBarrier();
+ void TextureBarrierTiled();
+
+ bool TryHostConditionalRendering(ICounterEvent value, ulong compare, bool isEqual);
+ bool TryHostConditionalRendering(ICounterEvent value, ICounterEvent compare, bool isEqual);
+ void EndHostConditionalRendering();
+
+ void UpdateRenderScale(ReadOnlySpan<float> scales, int totalCount, int fragmentCount);
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/IProgram.cs b/src/Ryujinx.Graphics.GAL/IProgram.cs
new file mode 100644
index 00000000..272a2f7d
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/IProgram.cs
@@ -0,0 +1,11 @@
+using System;
+
+namespace Ryujinx.Graphics.GAL
+{
+ public interface IProgram : IDisposable
+ {
+ ProgramLinkStatus CheckProgramLink(bool blocking);
+
+ byte[] GetBinary();
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/IRenderer.cs b/src/Ryujinx.Graphics.GAL/IRenderer.cs
new file mode 100644
index 00000000..2af7b5db
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/IRenderer.cs
@@ -0,0 +1,65 @@
+using Ryujinx.Common.Configuration;
+using System;
+
+namespace Ryujinx.Graphics.GAL
+{
+ public interface IRenderer : IDisposable
+ {
+ event EventHandler<ScreenCaptureImageInfo> ScreenCaptured;
+
+ bool PreferThreading { get; }
+
+ IPipeline Pipeline { get; }
+
+ IWindow Window { get; }
+
+ void BackgroundContextAction(Action action, bool alwaysBackground = false);
+
+ BufferHandle CreateBuffer(int size, BufferHandle storageHint);
+
+ BufferHandle CreateBuffer(int size)
+ {
+ return CreateBuffer(size, BufferHandle.Null);
+ }
+
+ IProgram CreateProgram(ShaderSource[] shaders, ShaderInfo info);
+
+ ISampler CreateSampler(SamplerCreateInfo info);
+ ITexture CreateTexture(TextureCreateInfo info, float scale);
+
+ void CreateSync(ulong id, bool strict);
+
+ void DeleteBuffer(BufferHandle buffer);
+
+ PinnedSpan<byte> GetBufferData(BufferHandle buffer, int offset, int size);
+
+ Capabilities GetCapabilities();
+ ulong GetCurrentSync();
+ HardwareInfo GetHardwareInfo();
+
+ IProgram LoadProgramBinary(byte[] programBinary, bool hasFragmentShader, ShaderInfo info);
+
+ void SetBufferData(BufferHandle buffer, int offset, ReadOnlySpan<byte> data);
+
+ void UpdateCounters();
+
+ void PreFrame();
+
+ ICounterEvent ReportCounter(CounterType type, EventHandler<ulong> resultHandler, bool hostReserved);
+
+ void ResetCounter(CounterType type);
+
+ void RunLoop(Action gpuLoop)
+ {
+ gpuLoop();
+ }
+
+ void WaitSync(ulong id);
+
+ void Initialize(GraphicsDebugLevel logLevel);
+
+ void SetInterruptAction(Action<Action> interruptAction);
+
+ void Screenshot();
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/ISampler.cs b/src/Ryujinx.Graphics.GAL/ISampler.cs
new file mode 100644
index 00000000..3aefc6a7
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/ISampler.cs
@@ -0,0 +1,6 @@
+using System;
+
+namespace Ryujinx.Graphics.GAL
+{
+ public interface ISampler : IDisposable { }
+} \ No newline at end of file
diff --git a/src/Ryujinx.Graphics.GAL/ITexture.cs b/src/Ryujinx.Graphics.GAL/ITexture.cs
new file mode 100644
index 00000000..792c863c
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/ITexture.cs
@@ -0,0 +1,27 @@
+using Ryujinx.Common.Memory;
+using System;
+
+namespace Ryujinx.Graphics.GAL
+{
+ public interface ITexture
+ {
+ int Width { get; }
+ int Height { get; }
+ float ScaleFactor { get; }
+
+ void CopyTo(ITexture destination, int firstLayer, int firstLevel);
+ void CopyTo(ITexture destination, int srcLayer, int dstLayer, int srcLevel, int dstLevel);
+ void CopyTo(ITexture destination, Extents2D srcRegion, Extents2D dstRegion, bool linearFilter);
+
+ ITexture CreateView(TextureCreateInfo info, int firstLayer, int firstLevel);
+
+ PinnedSpan<byte> GetData();
+ PinnedSpan<byte> GetData(int layer, int level);
+
+ void SetData(SpanOrArray<byte> data);
+ void SetData(SpanOrArray<byte> data, int layer, int level);
+ void SetData(SpanOrArray<byte> data, int layer, int level, Rectangle<int> region);
+ void SetStorage(BufferRange buffer);
+ void Release();
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.Graphics.GAL/IWindow.cs b/src/Ryujinx.Graphics.GAL/IWindow.cs
new file mode 100644
index 00000000..1221d685
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/IWindow.cs
@@ -0,0 +1,17 @@
+using System;
+
+namespace Ryujinx.Graphics.GAL
+{
+ public interface IWindow
+ {
+ void Present(ITexture texture, ImageCrop crop, Action swapBuffersCallback);
+
+ void SetSize(int width, int height);
+
+ void ChangeVSyncMode(bool vsyncEnabled);
+
+ void SetAntiAliasing(AntiAliasing antialiasing);
+ void SetScalingFilter(ScalingFilter type);
+ void SetScalingFilterLevel(float level);
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/ImageCrop.cs b/src/Ryujinx.Graphics.GAL/ImageCrop.cs
new file mode 100644
index 00000000..e8220974
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/ImageCrop.cs
@@ -0,0 +1,37 @@
+namespace Ryujinx.Graphics.GAL
+{
+ public readonly struct ImageCrop
+ {
+ public int Left { get; }
+ public int Right { get; }
+ public int Top { get; }
+ public int Bottom { get; }
+ public bool FlipX { get; }
+ public bool FlipY { get; }
+ public bool IsStretched { get; }
+ public float AspectRatioX { get; }
+ public float AspectRatioY { get; }
+
+ public ImageCrop(
+ int left,
+ int right,
+ int top,
+ int bottom,
+ bool flipX,
+ bool flipY,
+ bool isStretched,
+ float aspectRatioX,
+ float aspectRatioY)
+ {
+ Left = left;
+ Right = right;
+ Top = top;
+ Bottom = bottom;
+ FlipX = flipX;
+ FlipY = flipY;
+ IsStretched = isStretched;
+ AspectRatioX = aspectRatioX;
+ AspectRatioY = aspectRatioY;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.Graphics.GAL/IndexType.cs b/src/Ryujinx.Graphics.GAL/IndexType.cs
new file mode 100644
index 00000000..4abf28d9
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/IndexType.cs
@@ -0,0 +1,9 @@
+namespace Ryujinx.Graphics.GAL
+{
+ public enum IndexType
+ {
+ UByte,
+ UShort,
+ UInt
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.Graphics.GAL/LogicalOp.cs b/src/Ryujinx.Graphics.GAL/LogicalOp.cs
new file mode 100644
index 00000000..848215d0
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/LogicalOp.cs
@@ -0,0 +1,22 @@
+namespace Ryujinx.Graphics.GAL
+{
+ public enum LogicalOp
+ {
+ Clear = 0x1500,
+ And = 0x1501,
+ AndReverse = 0x1502,
+ Copy = 0x1503,
+ AndInverted = 0x1504,
+ Noop = 0x1505,
+ Xor = 0x1506,
+ Or = 0x1507,
+ Nor = 0x1508,
+ Equiv = 0x1509,
+ Invert = 0x150A,
+ OrReverse = 0x150B,
+ CopyInverted = 0x150C,
+ OrInverted = 0x150D,
+ Nand = 0x150E,
+ Set = 0x150F
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/MagFilter.cs b/src/Ryujinx.Graphics.GAL/MagFilter.cs
new file mode 100644
index 00000000..f20d095e
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/MagFilter.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.Graphics.GAL
+{
+ public enum MagFilter
+ {
+ Nearest = 1,
+ Linear
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.Graphics.GAL/MinFilter.cs b/src/Ryujinx.Graphics.GAL/MinFilter.cs
new file mode 100644
index 00000000..b7a0740b
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/MinFilter.cs
@@ -0,0 +1,12 @@
+namespace Ryujinx.Graphics.GAL
+{
+ public enum MinFilter
+ {
+ Nearest = 1,
+ Linear,
+ NearestMipmapNearest,
+ LinearMipmapNearest,
+ NearestMipmapLinear,
+ LinearMipmapLinear
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.Graphics.GAL/MultisampleDescriptor.cs b/src/Ryujinx.Graphics.GAL/MultisampleDescriptor.cs
new file mode 100644
index 00000000..a6fb65aa
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/MultisampleDescriptor.cs
@@ -0,0 +1,19 @@
+namespace Ryujinx.Graphics.GAL
+{
+ public readonly struct MultisampleDescriptor
+ {
+ public bool AlphaToCoverageEnable { get; }
+ public bool AlphaToCoverageDitherEnable { get; }
+ public bool AlphaToOneEnable { get; }
+
+ public MultisampleDescriptor(
+ bool alphaToCoverageEnable,
+ bool alphaToCoverageDitherEnable,
+ bool alphaToOneEnable)
+ {
+ AlphaToCoverageEnable = alphaToCoverageEnable;
+ AlphaToCoverageDitherEnable = alphaToCoverageDitherEnable;
+ AlphaToOneEnable = alphaToOneEnable;
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/BufferMap.cs b/src/Ryujinx.Graphics.GAL/Multithreading/BufferMap.cs
new file mode 100644
index 00000000..24b0af2d
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/BufferMap.cs
@@ -0,0 +1,194 @@
+using System;
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+using System.Threading;
+
+namespace Ryujinx.Graphics.GAL.Multithreading
+{
+ /// <summary>
+ /// 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.
+ /// </summary>
+ class BufferMap
+ {
+ private ulong _bufferHandle = 0;
+
+ private Dictionary<BufferHandle, BufferHandle> _bufferMap = new Dictionary<BufferHandle, BufferHandle>();
+ private HashSet<BufferHandle> _inFlight = new HashSet<BufferHandle>();
+ private AutoResetEvent _inFlightChanged = new AutoResetEvent(false);
+
+ internal BufferHandle CreateBufferHandle()
+ {
+ ulong handle64 = Interlocked.Increment(ref _bufferHandle);
+
+ BufferHandle threadedHandle = Unsafe.As<ulong, BufferHandle>(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<BufferRange> MapBufferRanges(Span<BufferRange> 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<BufferAssignment> MapBufferRanges(Span<BufferAssignment> ranges)
+ {
+ // Rewrite the buffer ranges to point to the mapped handles.
+
+ lock (_bufferMap)
+ {
+ for (int i = 0; i < ranges.Length; i++)
+ {
+ ref BufferAssignment assignment = ref ranges[i];
+ BufferRange range = assignment.Range;
+ BufferHandle result;
+
+ if (!_bufferMap.TryGetValue(range.Handle, out result))
+ {
+ result = BufferHandle.Null;
+ }
+
+ assignment = new BufferAssignment(ranges[i].Binding, new BufferRange(result, range.Offset, range.Size));
+ }
+ }
+
+ return ranges;
+ }
+
+ internal Span<VertexBufferDescriptor> MapBufferRanges(Span<VertexBufferDescriptor> 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/src/Ryujinx.Graphics.GAL/Multithreading/CommandHelper.cs b/src/Ryujinx.Graphics.GAL/Multithreading/CommandHelper.cs
new file mode 100644
index 00000000..063b7edf
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/CommandHelper.cs
@@ -0,0 +1,149 @@
+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.Texture;
+using Ryujinx.Graphics.GAL.Multithreading.Commands.Window;
+using System;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Graphics.GAL.Multithreading
+{
+ static class CommandHelper
+ {
+ private delegate void CommandDelegate(Span<byte> memory, ThreadedRenderer threaded, IRenderer renderer);
+
+ private static int _totalCommands = (int)Enum.GetValues<CommandType>().Max() + 1;
+ private static CommandDelegate[] _lookup = new CommandDelegate[_totalCommands];
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static ref T GetCommand<T>(Span<byte> memory)
+ {
+ return ref Unsafe.As<byte, T>(ref MemoryMarshal.GetReference(memory));
+ }
+
+ public static int GetMaxCommandSize()
+ {
+ return InitLookup() + 1; // 1 byte reserved for command size.
+ }
+
+ private static int InitLookup()
+ {
+ int maxCommandSize = 0;
+
+ void Register<T>(CommandType commandType) where T : unmanaged, IGALCommand, IGALCommand<T>
+ {
+ maxCommandSize = Math.Max(maxCommandSize, Unsafe.SizeOf<T>());
+ _lookup[(int)commandType] = (memory, threaded, renderer) => T.Run(ref GetCommand<T>(memory), threaded, renderer);
+ }
+
+ Register<ActionCommand>(CommandType.Action);
+ Register<CreateBufferCommand>(CommandType.CreateBuffer);
+ Register<CreateProgramCommand>(CommandType.CreateProgram);
+ Register<CreateSamplerCommand>(CommandType.CreateSampler);
+ Register<CreateSyncCommand>(CommandType.CreateSync);
+ Register<CreateTextureCommand>(CommandType.CreateTexture);
+ Register<GetCapabilitiesCommand>(CommandType.GetCapabilities);
+ Register<PreFrameCommand>(CommandType.PreFrame);
+ Register<ReportCounterCommand>(CommandType.ReportCounter);
+ Register<ResetCounterCommand>(CommandType.ResetCounter);
+ Register<UpdateCountersCommand>(CommandType.UpdateCounters);
+
+ Register<BufferDisposeCommand>(CommandType.BufferDispose);
+ Register<BufferGetDataCommand>(CommandType.BufferGetData);
+ Register<BufferSetDataCommand>(CommandType.BufferSetData);
+
+ Register<CounterEventDisposeCommand>(CommandType.CounterEventDispose);
+ Register<CounterEventFlushCommand>(CommandType.CounterEventFlush);
+
+ Register<ProgramDisposeCommand>(CommandType.ProgramDispose);
+ Register<ProgramGetBinaryCommand>(CommandType.ProgramGetBinary);
+ Register<ProgramCheckLinkCommand>(CommandType.ProgramCheckLink);
+
+ Register<SamplerDisposeCommand>(CommandType.SamplerDispose);
+
+ Register<TextureCopyToCommand>(CommandType.TextureCopyTo);
+ Register<TextureCopyToScaledCommand>(CommandType.TextureCopyToScaled);
+ Register<TextureCopyToSliceCommand>(CommandType.TextureCopyToSlice);
+ Register<TextureCreateViewCommand>(CommandType.TextureCreateView);
+ Register<TextureGetDataCommand>(CommandType.TextureGetData);
+ Register<TextureGetDataSliceCommand>(CommandType.TextureGetDataSlice);
+ Register<TextureReleaseCommand>(CommandType.TextureRelease);
+ Register<TextureSetDataCommand>(CommandType.TextureSetData);
+ Register<TextureSetDataSliceCommand>(CommandType.TextureSetDataSlice);
+ Register<TextureSetDataSliceRegionCommand>(CommandType.TextureSetDataSliceRegion);
+ Register<TextureSetStorageCommand>(CommandType.TextureSetStorage);
+
+ Register<WindowPresentCommand>(CommandType.WindowPresent);
+
+ Register<BarrierCommand>(CommandType.Barrier);
+ Register<BeginTransformFeedbackCommand>(CommandType.BeginTransformFeedback);
+ Register<ClearBufferCommand>(CommandType.ClearBuffer);
+ Register<ClearRenderTargetColorCommand>(CommandType.ClearRenderTargetColor);
+ Register<ClearRenderTargetDepthStencilCommand>(CommandType.ClearRenderTargetDepthStencil);
+ Register<CommandBufferBarrierCommand>(CommandType.CommandBufferBarrier);
+ Register<CopyBufferCommand>(CommandType.CopyBuffer);
+ Register<DispatchComputeCommand>(CommandType.DispatchCompute);
+ Register<DrawCommand>(CommandType.Draw);
+ Register<DrawIndexedCommand>(CommandType.DrawIndexed);
+ Register<DrawIndexedIndirectCommand>(CommandType.DrawIndexedIndirect);
+ Register<DrawIndexedIndirectCountCommand>(CommandType.DrawIndexedIndirectCount);
+ Register<DrawIndirectCommand>(CommandType.DrawIndirect);
+ Register<DrawIndirectCountCommand>(CommandType.DrawIndirectCount);
+ Register<DrawTextureCommand>(CommandType.DrawTexture);
+ Register<EndHostConditionalRenderingCommand>(CommandType.EndHostConditionalRendering);
+ Register<EndTransformFeedbackCommand>(CommandType.EndTransformFeedback);
+ Register<SetAlphaTestCommand>(CommandType.SetAlphaTest);
+ Register<SetBlendStateAdvancedCommand>(CommandType.SetBlendStateAdvanced);
+ Register<SetBlendStateCommand>(CommandType.SetBlendState);
+ Register<SetDepthBiasCommand>(CommandType.SetDepthBias);
+ Register<SetDepthClampCommand>(CommandType.SetDepthClamp);
+ Register<SetDepthModeCommand>(CommandType.SetDepthMode);
+ Register<SetDepthTestCommand>(CommandType.SetDepthTest);
+ Register<SetFaceCullingCommand>(CommandType.SetFaceCulling);
+ Register<SetFrontFaceCommand>(CommandType.SetFrontFace);
+ Register<SetStorageBuffersCommand>(CommandType.SetStorageBuffers);
+ Register<SetTransformFeedbackBuffersCommand>(CommandType.SetTransformFeedbackBuffers);
+ Register<SetUniformBuffersCommand>(CommandType.SetUniformBuffers);
+ Register<SetImageCommand>(CommandType.SetImage);
+ Register<SetIndexBufferCommand>(CommandType.SetIndexBuffer);
+ Register<SetLineParametersCommand>(CommandType.SetLineParameters);
+ Register<SetLogicOpStateCommand>(CommandType.SetLogicOpState);
+ Register<SetMultisampleStateCommand>(CommandType.SetMultisampleState);
+ Register<SetPatchParametersCommand>(CommandType.SetPatchParameters);
+ Register<SetPointParametersCommand>(CommandType.SetPointParameters);
+ Register<SetPolygonModeCommand>(CommandType.SetPolygonMode);
+ Register<SetPrimitiveRestartCommand>(CommandType.SetPrimitiveRestart);
+ Register<SetPrimitiveTopologyCommand>(CommandType.SetPrimitiveTopology);
+ Register<SetProgramCommand>(CommandType.SetProgram);
+ Register<SetRasterizerDiscardCommand>(CommandType.SetRasterizerDiscard);
+ Register<SetRenderTargetColorMasksCommand>(CommandType.SetRenderTargetColorMasks);
+ Register<SetRenderTargetScaleCommand>(CommandType.SetRenderTargetScale);
+ Register<SetRenderTargetsCommand>(CommandType.SetRenderTargets);
+ Register<SetScissorsCommand>(CommandType.SetScissor);
+ Register<SetStencilTestCommand>(CommandType.SetStencilTest);
+ Register<SetTextureAndSamplerCommand>(CommandType.SetTextureAndSampler);
+ Register<SetUserClipDistanceCommand>(CommandType.SetUserClipDistance);
+ Register<SetVertexAttribsCommand>(CommandType.SetVertexAttribs);
+ Register<SetVertexBuffersCommand>(CommandType.SetVertexBuffers);
+ Register<SetViewportsCommand>(CommandType.SetViewports);
+ Register<TextureBarrierCommand>(CommandType.TextureBarrier);
+ Register<TextureBarrierTiledCommand>(CommandType.TextureBarrierTiled);
+ Register<TryHostConditionalRenderingCommand>(CommandType.TryHostConditionalRendering);
+ Register<TryHostConditionalRenderingFlushCommand>(CommandType.TryHostConditionalRenderingFlush);
+ Register<UpdateRenderScaleCommand>(CommandType.UpdateRenderScale);
+
+ return maxCommandSize;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void RunCommand(Span<byte> memory, ThreadedRenderer threaded, IRenderer renderer)
+ {
+ _lookup[memory[memory.Length - 1]](memory, threaded, renderer);
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/CommandType.cs b/src/Ryujinx.Graphics.GAL/Multithreading/CommandType.cs
new file mode 100644
index 00000000..61e729b4
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/CommandType.cs
@@ -0,0 +1,102 @@
+namespace Ryujinx.Graphics.GAL.Multithreading
+{
+ enum CommandType : byte
+ {
+ Action,
+ CreateBuffer,
+ CreateProgram,
+ CreateSampler,
+ CreateSync,
+ CreateTexture,
+ GetCapabilities,
+ Unused,
+ PreFrame,
+ ReportCounter,
+ ResetCounter,
+ UpdateCounters,
+
+ BufferDispose,
+ BufferGetData,
+ BufferSetData,
+
+ CounterEventDispose,
+ CounterEventFlush,
+
+ ProgramDispose,
+ ProgramGetBinary,
+ ProgramCheckLink,
+
+ SamplerDispose,
+
+ TextureCopyTo,
+ TextureCopyToScaled,
+ TextureCopyToSlice,
+ TextureCreateView,
+ TextureGetData,
+ TextureGetDataSlice,
+ TextureRelease,
+ TextureSetData,
+ TextureSetDataSlice,
+ TextureSetDataSliceRegion,
+ TextureSetStorage,
+
+ WindowPresent,
+
+ Barrier,
+ BeginTransformFeedback,
+ ClearBuffer,
+ ClearRenderTargetColor,
+ ClearRenderTargetDepthStencil,
+ CommandBufferBarrier,
+ CopyBuffer,
+ DispatchCompute,
+ Draw,
+ DrawIndexed,
+ DrawIndexedIndirect,
+ DrawIndexedIndirectCount,
+ DrawIndirect,
+ DrawIndirectCount,
+ DrawTexture,
+ EndHostConditionalRendering,
+ EndTransformFeedback,
+ SetAlphaTest,
+ SetBlendStateAdvanced,
+ SetBlendState,
+ SetDepthBias,
+ SetDepthClamp,
+ SetDepthMode,
+ SetDepthTest,
+ SetFaceCulling,
+ SetFrontFace,
+ SetStorageBuffers,
+ SetTransformFeedbackBuffers,
+ SetUniformBuffers,
+ SetImage,
+ SetIndexBuffer,
+ SetLineParameters,
+ SetLogicOpState,
+ SetMultisampleState,
+ SetPatchParameters,
+ SetPointParameters,
+ SetPolygonMode,
+ SetPrimitiveRestart,
+ SetPrimitiveTopology,
+ SetProgram,
+ SetRasterizerDiscard,
+ SetRenderTargetColorMasks,
+ SetRenderTargetScale,
+ SetRenderTargets,
+ SetScissor,
+ SetStencilTest,
+ SetTextureAndSampler,
+ SetUserClipDistance,
+ SetVertexAttribs,
+ SetVertexBuffers,
+ SetViewports,
+ TextureBarrier,
+ TextureBarrierTiled,
+ TryHostConditionalRendering,
+ TryHostConditionalRenderingFlush,
+ UpdateRenderScale
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/BarrierCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/BarrierCommand.cs
new file mode 100644
index 00000000..4f8e1b08
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/BarrierCommand.cs
@@ -0,0 +1,12 @@
+namespace Ryujinx.Graphics.GAL.Multithreading.Commands
+{
+ struct BarrierCommand : IGALCommand, IGALCommand<BarrierCommand>
+ {
+ public CommandType CommandType => CommandType.Barrier;
+
+ public static void Run(ref BarrierCommand command, ThreadedRenderer threaded, IRenderer renderer)
+ {
+ renderer.Pipeline.Barrier();
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/BeginTransformFeedbackCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/BeginTransformFeedbackCommand.cs
new file mode 100644
index 00000000..50032635
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/BeginTransformFeedbackCommand.cs
@@ -0,0 +1,18 @@
+namespace Ryujinx.Graphics.GAL.Multithreading.Commands
+{
+ struct BeginTransformFeedbackCommand : IGALCommand, IGALCommand<BeginTransformFeedbackCommand>
+ {
+ 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/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Buffer/BufferDisposeCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Buffer/BufferDisposeCommand.cs
new file mode 100644
index 00000000..5be42fff
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Buffer/BufferDisposeCommand.cs
@@ -0,0 +1,19 @@
+namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Buffer
+{
+ struct BufferDisposeCommand : IGALCommand, IGALCommand<BufferDisposeCommand>
+ {
+ 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/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Buffer/BufferGetDataCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Buffer/BufferGetDataCommand.cs
new file mode 100644
index 00000000..031c6153
--- /dev/null
+++ b/src/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, IGALCommand<BufferGetDataCommand>
+ {
+ public CommandType CommandType => CommandType.BufferGetData;
+ private BufferHandle _buffer;
+ private int _offset;
+ private int _size;
+ private TableRef<ResultBox<PinnedSpan<byte>>> _result;
+
+ public void Set(BufferHandle buffer, int offset, int size, TableRef<ResultBox<PinnedSpan<byte>>> result)
+ {
+ _buffer = buffer;
+ _offset = offset;
+ _size = size;
+ _result = result;
+ }
+
+ public static void Run(ref BufferGetDataCommand command, ThreadedRenderer threaded, IRenderer renderer)
+ {
+ PinnedSpan<byte> result = renderer.GetBufferData(threaded.Buffers.MapBuffer(command._buffer), command._offset, command._size);
+
+ command._result.Get(threaded).Result = result;
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Buffer/BufferSetDataCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Buffer/BufferSetDataCommand.cs
new file mode 100644
index 00000000..dcb8c2f2
--- /dev/null
+++ b/src/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, IGALCommand<BufferSetDataCommand>
+ {
+ public CommandType CommandType => CommandType.BufferSetData;
+ private BufferHandle _buffer;
+ private int _offset;
+ private SpanRef<byte> _data;
+
+ public void Set(BufferHandle buffer, int offset, SpanRef<byte> data)
+ {
+ _buffer = buffer;
+ _offset = offset;
+ _data = data;
+ }
+
+ public static void Run(ref BufferSetDataCommand command, ThreadedRenderer threaded, IRenderer renderer)
+ {
+ ReadOnlySpan<byte> data = command._data.Get(threaded);
+ renderer.SetBufferData(threaded.Buffers.MapBuffer(command._buffer), command._offset, data);
+ command._data.Dispose(threaded);
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/ClearBufferCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/ClearBufferCommand.cs
new file mode 100644
index 00000000..1d70460a
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/ClearBufferCommand.cs
@@ -0,0 +1,24 @@
+namespace Ryujinx.Graphics.GAL.Multithreading.Commands
+{
+ struct ClearBufferCommand : IGALCommand, IGALCommand<ClearBufferCommand>
+ {
+ 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/src/Ryujinx.Graphics.GAL/Multithreading/Commands/ClearRenderTargetColorCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/ClearRenderTargetColorCommand.cs
new file mode 100644
index 00000000..f8c2bdfe
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/ClearRenderTargetColorCommand.cs
@@ -0,0 +1,26 @@
+namespace Ryujinx.Graphics.GAL.Multithreading.Commands
+{
+ struct ClearRenderTargetColorCommand : IGALCommand, IGALCommand<ClearRenderTargetColorCommand>
+ {
+ public CommandType CommandType => CommandType.ClearRenderTargetColor;
+ private int _index;
+ private int _layer;
+ private int _layerCount;
+ private uint _componentMask;
+ private ColorF _color;
+
+ public void Set(int index, int layer, int layerCount, uint componentMask, ColorF color)
+ {
+ _index = index;
+ _layer = layer;
+ _layerCount = layerCount;
+ _componentMask = componentMask;
+ _color = color;
+ }
+
+ public static void Run(ref ClearRenderTargetColorCommand command, ThreadedRenderer threaded, IRenderer renderer)
+ {
+ renderer.Pipeline.ClearRenderTargetColor(command._index, command._layer, command._layerCount, command._componentMask, command._color);
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/ClearRenderTargetDepthStencilCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/ClearRenderTargetDepthStencilCommand.cs
new file mode 100644
index 00000000..ca86673e
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/ClearRenderTargetDepthStencilCommand.cs
@@ -0,0 +1,28 @@
+namespace Ryujinx.Graphics.GAL.Multithreading.Commands
+{
+ struct ClearRenderTargetDepthStencilCommand : IGALCommand, IGALCommand<ClearRenderTargetDepthStencilCommand>
+ {
+ public CommandType CommandType => CommandType.ClearRenderTargetDepthStencil;
+ private int _layer;
+ private int _layerCount;
+ private float _depthValue;
+ private bool _depthMask;
+ private int _stencilValue;
+ private int _stencilMask;
+
+ public void Set(int layer, int layerCount, float depthValue, bool depthMask, int stencilValue, int stencilMask)
+ {
+ _layer = layer;
+ _layerCount = layerCount;
+ _depthValue = depthValue;
+ _depthMask = depthMask;
+ _stencilValue = stencilValue;
+ _stencilMask = stencilMask;
+ }
+
+ public static void Run(ref ClearRenderTargetDepthStencilCommand command, ThreadedRenderer threaded, IRenderer renderer)
+ {
+ renderer.Pipeline.ClearRenderTargetDepthStencil(command._layer, command._layerCount, command._depthValue, command._depthMask, command._stencilValue, command._stencilMask);
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/CommandBufferBarrierCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/CommandBufferBarrierCommand.cs
new file mode 100644
index 00000000..ad3ab0f8
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/CommandBufferBarrierCommand.cs
@@ -0,0 +1,12 @@
+namespace Ryujinx.Graphics.GAL.Multithreading.Commands
+{
+ struct CommandBufferBarrierCommand : IGALCommand, IGALCommand<CommandBufferBarrierCommand>
+ {
+ public CommandType CommandType => CommandType.CommandBufferBarrier;
+
+ public static void Run(ref CommandBufferBarrierCommand command, ThreadedRenderer threaded, IRenderer renderer)
+ {
+ renderer.Pipeline.CommandBufferBarrier();
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/CopyBufferCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/CopyBufferCommand.cs
new file mode 100644
index 00000000..43111bce
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/CopyBufferCommand.cs
@@ -0,0 +1,26 @@
+namespace Ryujinx.Graphics.GAL.Multithreading.Commands
+{
+ struct CopyBufferCommand : IGALCommand, IGALCommand<CopyBufferCommand>
+ {
+ 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/src/Ryujinx.Graphics.GAL/Multithreading/Commands/CounterEvent/CounterEventDisposeCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/CounterEvent/CounterEventDisposeCommand.cs
new file mode 100644
index 00000000..e5250212
--- /dev/null
+++ b/src/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, IGALCommand<CounterEventDisposeCommand>
+ {
+ public CommandType CommandType => CommandType.CounterEventDispose;
+ private TableRef<ThreadedCounterEvent> _event;
+
+ public void Set(TableRef<ThreadedCounterEvent> evt)
+ {
+ _event = evt;
+ }
+
+ public static void Run(ref CounterEventDisposeCommand command, ThreadedRenderer threaded, IRenderer renderer)
+ {
+ command._event.Get(threaded).Base.Dispose();
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/CounterEvent/CounterEventFlushCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/CounterEvent/CounterEventFlushCommand.cs
new file mode 100644
index 00000000..608cf8f9
--- /dev/null
+++ b/src/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, IGALCommand<CounterEventFlushCommand>
+ {
+ public CommandType CommandType => CommandType.CounterEventFlush;
+ private TableRef<ThreadedCounterEvent> _event;
+
+ public void Set(TableRef<ThreadedCounterEvent> evt)
+ {
+ _event = evt;
+ }
+
+ public static void Run(ref CounterEventFlushCommand command, ThreadedRenderer threaded, IRenderer renderer)
+ {
+ command._event.Get(threaded).Base.Flush();
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/DispatchComputeCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/DispatchComputeCommand.cs
new file mode 100644
index 00000000..29568837
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/DispatchComputeCommand.cs
@@ -0,0 +1,22 @@
+namespace Ryujinx.Graphics.GAL.Multithreading.Commands
+{
+ struct DispatchComputeCommand : IGALCommand, IGALCommand<DispatchComputeCommand>
+ {
+ 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/src/Ryujinx.Graphics.GAL/Multithreading/Commands/DrawCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/DrawCommand.cs
new file mode 100644
index 00000000..804eaa49
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/DrawCommand.cs
@@ -0,0 +1,26 @@
+namespace Ryujinx.Graphics.GAL.Multithreading.Commands
+{
+ struct DrawIndexedCommand : IGALCommand, IGALCommand<DrawIndexedCommand>
+ {
+ 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/src/Ryujinx.Graphics.GAL/Multithreading/Commands/DrawIndexedCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/DrawIndexedCommand.cs
new file mode 100644
index 00000000..1b28afcd
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/DrawIndexedCommand.cs
@@ -0,0 +1,24 @@
+namespace Ryujinx.Graphics.GAL.Multithreading.Commands
+{
+ struct DrawCommand : IGALCommand, IGALCommand<DrawCommand>
+ {
+ 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/src/Ryujinx.Graphics.GAL/Multithreading/Commands/DrawIndexedIndirectCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/DrawIndexedIndirectCommand.cs
new file mode 100644
index 00000000..521b2f0c
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/DrawIndexedIndirectCommand.cs
@@ -0,0 +1,18 @@
+namespace Ryujinx.Graphics.GAL.Multithreading.Commands
+{
+ struct DrawIndexedIndirectCommand : IGALCommand, IGALCommand<DrawIndexedIndirectCommand>
+ {
+ public CommandType CommandType => CommandType.DrawIndexedIndirect;
+ private BufferRange _indirectBuffer;
+
+ public void Set(BufferRange indirectBuffer)
+ {
+ _indirectBuffer = indirectBuffer;
+ }
+
+ public static void Run(ref DrawIndexedIndirectCommand command, ThreadedRenderer threaded, IRenderer renderer)
+ {
+ renderer.Pipeline.DrawIndexedIndirect(threaded.Buffers.MapBufferRange(command._indirectBuffer));
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/DrawIndexedIndirectCountCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/DrawIndexedIndirectCountCommand.cs
new file mode 100644
index 00000000..6bdf376d
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/DrawIndexedIndirectCountCommand.cs
@@ -0,0 +1,29 @@
+namespace Ryujinx.Graphics.GAL.Multithreading.Commands
+{
+ struct DrawIndexedIndirectCountCommand : IGALCommand, IGALCommand<DrawIndexedIndirectCountCommand>
+ {
+ public CommandType CommandType => CommandType.DrawIndexedIndirectCount;
+ 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 DrawIndexedIndirectCountCommand command, ThreadedRenderer threaded, IRenderer renderer)
+ {
+ renderer.Pipeline.DrawIndexedIndirectCount(
+ threaded.Buffers.MapBufferRange(command._indirectBuffer),
+ threaded.Buffers.MapBufferRange(command._parameterBuffer),
+ command._maxDrawCount,
+ command._stride
+ );
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/DrawIndirectCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/DrawIndirectCommand.cs
new file mode 100644
index 00000000..e1947084
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/DrawIndirectCommand.cs
@@ -0,0 +1,18 @@
+namespace Ryujinx.Graphics.GAL.Multithreading.Commands
+{
+ struct DrawIndirectCommand : IGALCommand, IGALCommand<DrawIndirectCommand>
+ {
+ public CommandType CommandType => CommandType.DrawIndirect;
+ private BufferRange _indirectBuffer;
+
+ public void Set(BufferRange indirectBuffer)
+ {
+ _indirectBuffer = indirectBuffer;
+ }
+
+ public static void Run(ref DrawIndirectCommand command, ThreadedRenderer threaded, IRenderer renderer)
+ {
+ renderer.Pipeline.DrawIndirect(threaded.Buffers.MapBufferRange(command._indirectBuffer));
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/DrawIndirectCountCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/DrawIndirectCountCommand.cs
new file mode 100644
index 00000000..ef56ffb2
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/DrawIndirectCountCommand.cs
@@ -0,0 +1,29 @@
+namespace Ryujinx.Graphics.GAL.Multithreading.Commands
+{
+ struct DrawIndirectCountCommand : IGALCommand, IGALCommand<DrawIndirectCountCommand>
+ {
+ public CommandType CommandType => CommandType.DrawIndirectCount;
+ 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 DrawIndirectCountCommand command, ThreadedRenderer threaded, IRenderer renderer)
+ {
+ renderer.Pipeline.DrawIndirectCount(
+ threaded.Buffers.MapBufferRange(command._indirectBuffer),
+ threaded.Buffers.MapBufferRange(command._parameterBuffer),
+ command._maxDrawCount,
+ command._stride
+ );
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/DrawTextureCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/DrawTextureCommand.cs
new file mode 100644
index 00000000..b3e9c4b5
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/DrawTextureCommand.cs
@@ -0,0 +1,31 @@
+using Ryujinx.Graphics.GAL.Multithreading.Model;
+using Ryujinx.Graphics.GAL.Multithreading.Resources;
+
+namespace Ryujinx.Graphics.GAL.Multithreading.Commands
+{
+ struct DrawTextureCommand : IGALCommand, IGALCommand<DrawTextureCommand>
+ {
+ public CommandType CommandType => CommandType.DrawTexture;
+ private TableRef<ITexture> _texture;
+ private TableRef<ISampler> _sampler;
+ private Extents2DF _srcRegion;
+ private Extents2DF _dstRegion;
+
+ public void Set(TableRef<ITexture> texture, TableRef<ISampler> sampler, Extents2DF srcRegion, Extents2DF dstRegion)
+ {
+ _texture = texture;
+ _sampler = sampler;
+ _srcRegion = srcRegion;
+ _dstRegion = dstRegion;
+ }
+
+ public static void Run(ref DrawTextureCommand command, ThreadedRenderer threaded, IRenderer renderer)
+ {
+ renderer.Pipeline.DrawTexture(
+ command._texture.GetAs<ThreadedTexture>(threaded)?.Base,
+ command._sampler.GetAs<ThreadedSampler>(threaded)?.Base,
+ command._srcRegion,
+ command._dstRegion);
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/EndHostConditionalRenderingCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/EndHostConditionalRenderingCommand.cs
new file mode 100644
index 00000000..877af23b
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/EndHostConditionalRenderingCommand.cs
@@ -0,0 +1,12 @@
+namespace Ryujinx.Graphics.GAL.Multithreading.Commands
+{
+ struct EndHostConditionalRenderingCommand : IGALCommand, IGALCommand<EndHostConditionalRenderingCommand>
+ {
+ public CommandType CommandType => CommandType.EndHostConditionalRendering;
+
+ public static void Run(ref EndHostConditionalRenderingCommand command, ThreadedRenderer threaded, IRenderer renderer)
+ {
+ renderer.Pipeline.EndHostConditionalRendering();
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/EndTransformFeedbackCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/EndTransformFeedbackCommand.cs
new file mode 100644
index 00000000..33df325f
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/EndTransformFeedbackCommand.cs
@@ -0,0 +1,12 @@
+namespace Ryujinx.Graphics.GAL.Multithreading.Commands
+{
+ struct EndTransformFeedbackCommand : IGALCommand, IGALCommand<EndTransformFeedbackCommand>
+ {
+ public CommandType CommandType => CommandType.EndTransformFeedback;
+
+ public static void Run(ref EndTransformFeedbackCommand command, ThreadedRenderer threaded, IRenderer renderer)
+ {
+ renderer.Pipeline.EndTransformFeedback();
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/IGALCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/IGALCommand.cs
new file mode 100644
index 00000000..ea831c8d
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/IGALCommand.cs
@@ -0,0 +1,12 @@
+namespace Ryujinx.Graphics.GAL.Multithreading.Commands
+{
+ interface IGALCommand
+ {
+ CommandType CommandType { get; }
+ }
+
+ interface IGALCommand<T> where T : IGALCommand
+ {
+ abstract static void Run(ref T command, ThreadedRenderer threaded, IRenderer renderer);
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Program/ProgramCheckLinkCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Program/ProgramCheckLinkCommand.cs
new file mode 100644
index 00000000..f3662424
--- /dev/null
+++ b/src/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, IGALCommand<ProgramCheckLinkCommand>
+ {
+ public CommandType CommandType => CommandType.ProgramCheckLink;
+ private TableRef<ThreadedProgram> _program;
+ private bool _blocking;
+ private TableRef<ResultBox<ProgramLinkStatus>> _result;
+
+ public void Set(TableRef<ThreadedProgram> program, bool blocking, TableRef<ResultBox<ProgramLinkStatus>> 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/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Program/ProgramDisposeCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Program/ProgramDisposeCommand.cs
new file mode 100644
index 00000000..d1ec4298
--- /dev/null
+++ b/src/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, IGALCommand<ProgramDisposeCommand>
+ {
+ public CommandType CommandType => CommandType.ProgramDispose;
+ private TableRef<ThreadedProgram> _program;
+
+ public void Set(TableRef<ThreadedProgram> program)
+ {
+ _program = program;
+ }
+
+ public static void Run(ref ProgramDisposeCommand command, ThreadedRenderer threaded, IRenderer renderer)
+ {
+ command._program.Get(threaded).Base.Dispose();
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Program/ProgramGetBinaryCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Program/ProgramGetBinaryCommand.cs
new file mode 100644
index 00000000..16963245
--- /dev/null
+++ b/src/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, IGALCommand<ProgramGetBinaryCommand>
+ {
+ public CommandType CommandType => CommandType.ProgramGetBinary;
+ private TableRef<ThreadedProgram> _program;
+ private TableRef<ResultBox<byte[]>> _result;
+
+ public void Set(TableRef<ThreadedProgram> program, TableRef<ResultBox<byte[]>> 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/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/ActionCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/ActionCommand.cs
new file mode 100644
index 00000000..41987da1
--- /dev/null
+++ b/src/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, IGALCommand<ActionCommand>
+ {
+ public CommandType CommandType => CommandType.Action;
+ private TableRef<Action> _action;
+
+ public void Set(TableRef<Action> action)
+ {
+ _action = action;
+ }
+
+ public static void Run(ref ActionCommand command, ThreadedRenderer threaded, IRenderer renderer)
+ {
+ command._action.Get(threaded)();
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateBufferCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateBufferCommand.cs
new file mode 100644
index 00000000..b36d8bbe
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateBufferCommand.cs
@@ -0,0 +1,29 @@
+namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer
+{
+ struct CreateBufferCommand : IGALCommand, IGALCommand<CreateBufferCommand>
+ {
+ public CommandType CommandType => CommandType.CreateBuffer;
+ private BufferHandle _threadedHandle;
+ private int _size;
+ private BufferHandle _storageHint;
+
+ public void Set(BufferHandle threadedHandle, int size, BufferHandle storageHint)
+ {
+ _threadedHandle = threadedHandle;
+ _size = size;
+ _storageHint = storageHint;
+ }
+
+ public static void Run(ref CreateBufferCommand command, ThreadedRenderer threaded, IRenderer renderer)
+ {
+ BufferHandle hint = BufferHandle.Null;
+
+ if (command._storageHint != BufferHandle.Null)
+ {
+ hint = threaded.Buffers.MapBuffer(command._storageHint);
+ }
+
+ threaded.Buffers.AssignBuffer(command._threadedHandle, renderer.CreateBuffer(command._size, hint));
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateProgramCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateProgramCommand.cs
new file mode 100644
index 00000000..19563e12
--- /dev/null
+++ b/src/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, IGALCommand<CreateProgramCommand>
+ {
+ public CommandType CommandType => CommandType.CreateProgram;
+ private TableRef<IProgramRequest> _request;
+
+ public void Set(TableRef<IProgramRequest> 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/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateSamplerCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateSamplerCommand.cs
new file mode 100644
index 00000000..6ab862d4
--- /dev/null
+++ b/src/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, IGALCommand<CreateSamplerCommand>
+ {
+ public CommandType CommandType => CommandType.CreateSampler;
+ private TableRef<ThreadedSampler> _sampler;
+ private SamplerCreateInfo _info;
+
+ public void Set(TableRef<ThreadedSampler> 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/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateSyncCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateSyncCommand.cs
new file mode 100644
index 00000000..32afb051
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateSyncCommand.cs
@@ -0,0 +1,22 @@
+namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer
+{
+ struct CreateSyncCommand : IGALCommand, IGALCommand<CreateSyncCommand>
+ {
+ public CommandType CommandType => CommandType.CreateSync;
+ private ulong _id;
+ private bool _strict;
+
+ public void Set(ulong id, bool strict)
+ {
+ _id = id;
+ _strict = strict;
+ }
+
+ public static void Run(ref CreateSyncCommand command, ThreadedRenderer threaded, IRenderer renderer)
+ {
+ renderer.CreateSync(command._id, command._strict);
+
+ threaded.Sync.AssignSync(command._id);
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateTextureCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateTextureCommand.cs
new file mode 100644
index 00000000..0347ded4
--- /dev/null
+++ b/src/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, IGALCommand<CreateTextureCommand>
+ {
+ public CommandType CommandType => CommandType.CreateTexture;
+ private TableRef<ThreadedTexture> _texture;
+ private TextureCreateInfo _info;
+ private float _scale;
+
+ public void Set(TableRef<ThreadedTexture> 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/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/GetCapabilitiesCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/GetCapabilitiesCommand.cs
new file mode 100644
index 00000000..4111dcfd
--- /dev/null
+++ b/src/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, IGALCommand<GetCapabilitiesCommand>
+ {
+ public CommandType CommandType => CommandType.GetCapabilities;
+ private TableRef<ResultBox<Capabilities>> _result;
+
+ public void Set(TableRef<ResultBox<Capabilities>> result)
+ {
+ _result = result;
+ }
+
+ public static void Run(ref GetCapabilitiesCommand command, ThreadedRenderer threaded, IRenderer renderer)
+ {
+ command._result.Get(threaded).Result = renderer.GetCapabilities();
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/PreFrameCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/PreFrameCommand.cs
new file mode 100644
index 00000000..820908f3
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/PreFrameCommand.cs
@@ -0,0 +1,12 @@
+namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer
+{
+ struct PreFrameCommand : IGALCommand, IGALCommand<PreFrameCommand>
+ {
+ public CommandType CommandType => CommandType.PreFrame;
+
+ public static void Run(ref PreFrameCommand command, ThreadedRenderer threaded, IRenderer renderer)
+ {
+ renderer.PreFrame();
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/ReportCounterCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/ReportCounterCommand.cs
new file mode 100644
index 00000000..4b0210cb
--- /dev/null
+++ b/src/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, IGALCommand<ReportCounterCommand>
+ {
+ public CommandType CommandType => CommandType.ReportCounter;
+ private TableRef<ThreadedCounterEvent> _event;
+ private CounterType _type;
+ private TableRef<EventHandler<ulong>> _resultHandler;
+ private bool _hostReserved;
+
+ public void Set(TableRef<ThreadedCounterEvent> evt, CounterType type, TableRef<EventHandler<ulong>> 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/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/ResetCounterCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/ResetCounterCommand.cs
new file mode 100644
index 00000000..3d796041
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/ResetCounterCommand.cs
@@ -0,0 +1,18 @@
+namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer
+{
+ struct ResetCounterCommand : IGALCommand, IGALCommand<ResetCounterCommand>
+ {
+ 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/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/UpdateCountersCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/UpdateCountersCommand.cs
new file mode 100644
index 00000000..c7076c0e
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/UpdateCountersCommand.cs
@@ -0,0 +1,12 @@
+namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer
+{
+ struct UpdateCountersCommand : IGALCommand, IGALCommand<UpdateCountersCommand>
+ {
+ public CommandType CommandType => CommandType.UpdateCounters;
+
+ public static void Run(ref UpdateCountersCommand command, ThreadedRenderer threaded, IRenderer renderer)
+ {
+ renderer.UpdateCounters();
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Sampler/SamplerDisposeCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Sampler/SamplerDisposeCommand.cs
new file mode 100644
index 00000000..9485e9a1
--- /dev/null
+++ b/src/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, IGALCommand<SamplerDisposeCommand>
+ {
+ public CommandType CommandType => CommandType.SamplerDispose;
+ private TableRef<ThreadedSampler> _sampler;
+
+ public void Set(TableRef<ThreadedSampler> sampler)
+ {
+ _sampler = sampler;
+ }
+
+ public static void Run(ref SamplerDisposeCommand command, ThreadedRenderer threaded, IRenderer renderer)
+ {
+ command._sampler.Get(threaded).Base.Dispose();
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetAlphaTestCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetAlphaTestCommand.cs
new file mode 100644
index 00000000..a96879ff
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetAlphaTestCommand.cs
@@ -0,0 +1,22 @@
+namespace Ryujinx.Graphics.GAL.Multithreading.Commands
+{
+ struct SetAlphaTestCommand : IGALCommand, IGALCommand<SetAlphaTestCommand>
+ {
+ 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/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetBlendStateAdvancedCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetBlendStateAdvancedCommand.cs
new file mode 100644
index 00000000..2ec10a50
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetBlendStateAdvancedCommand.cs
@@ -0,0 +1,18 @@
+namespace Ryujinx.Graphics.GAL.Multithreading.Commands
+{
+ struct SetBlendStateAdvancedCommand : IGALCommand, IGALCommand<SetBlendStateAdvancedCommand>
+ {
+ public CommandType CommandType => CommandType.SetBlendStateAdvanced;
+ private AdvancedBlendDescriptor _blend;
+
+ public void Set(AdvancedBlendDescriptor blend)
+ {
+ _blend = blend;
+ }
+
+ public static void Run(ref SetBlendStateAdvancedCommand command, ThreadedRenderer threaded, IRenderer renderer)
+ {
+ renderer.Pipeline.SetBlendState(command._blend);
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetBlendStateCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetBlendStateCommand.cs
new file mode 100644
index 00000000..68e48da5
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetBlendStateCommand.cs
@@ -0,0 +1,20 @@
+namespace Ryujinx.Graphics.GAL.Multithreading.Commands
+{
+ struct SetBlendStateCommand : IGALCommand, IGALCommand<SetBlendStateCommand>
+ {
+ 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/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetDepthBiasCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetDepthBiasCommand.cs
new file mode 100644
index 00000000..eb8d4a72
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetDepthBiasCommand.cs
@@ -0,0 +1,24 @@
+namespace Ryujinx.Graphics.GAL.Multithreading.Commands
+{
+ struct SetDepthBiasCommand : IGALCommand, IGALCommand<SetDepthBiasCommand>
+ {
+ 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/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetDepthClampCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetDepthClampCommand.cs
new file mode 100644
index 00000000..15159cb4
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetDepthClampCommand.cs
@@ -0,0 +1,18 @@
+namespace Ryujinx.Graphics.GAL.Multithreading.Commands
+{
+ struct SetDepthClampCommand : IGALCommand, IGALCommand<SetDepthClampCommand>
+ {
+ 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/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetDepthModeCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetDepthModeCommand.cs
new file mode 100644
index 00000000..3e169164
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetDepthModeCommand.cs
@@ -0,0 +1,18 @@
+namespace Ryujinx.Graphics.GAL.Multithreading.Commands
+{
+ struct SetDepthModeCommand : IGALCommand, IGALCommand<SetDepthModeCommand>
+ {
+ 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/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetDepthTestCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetDepthTestCommand.cs
new file mode 100644
index 00000000..2abaeb78
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetDepthTestCommand.cs
@@ -0,0 +1,18 @@
+namespace Ryujinx.Graphics.GAL.Multithreading.Commands
+{
+ struct SetDepthTestCommand : IGALCommand, IGALCommand<SetDepthTestCommand>
+ {
+ 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/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetFaceCullingCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetFaceCullingCommand.cs
new file mode 100644
index 00000000..54311e95
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetFaceCullingCommand.cs
@@ -0,0 +1,20 @@
+namespace Ryujinx.Graphics.GAL.Multithreading.Commands
+{
+ struct SetFaceCullingCommand : IGALCommand, IGALCommand<SetFaceCullingCommand>
+ {
+ 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/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetFrontFaceCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetFrontFaceCommand.cs
new file mode 100644
index 00000000..e4d7b814
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetFrontFaceCommand.cs
@@ -0,0 +1,18 @@
+namespace Ryujinx.Graphics.GAL.Multithreading.Commands
+{
+ struct SetFrontFaceCommand : IGALCommand, IGALCommand<SetFrontFaceCommand>
+ {
+ 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/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetImageCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetImageCommand.cs
new file mode 100644
index 00000000..7836acd7
--- /dev/null
+++ b/src/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, IGALCommand<SetImageCommand>
+ {
+ public CommandType CommandType => CommandType.SetImage;
+ private int _binding;
+ private TableRef<ITexture> _texture;
+ private Format _imageFormat;
+
+ public void Set(int binding, TableRef<ITexture> 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<ThreadedTexture>(threaded)?.Base, command._imageFormat);
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetIndexBufferCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetIndexBufferCommand.cs
new file mode 100644
index 00000000..ded44c55
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetIndexBufferCommand.cs
@@ -0,0 +1,21 @@
+namespace Ryujinx.Graphics.GAL.Multithreading.Commands
+{
+ struct SetIndexBufferCommand : IGALCommand, IGALCommand<SetIndexBufferCommand>
+ {
+ 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/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetLineParametersCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetLineParametersCommand.cs
new file mode 100644
index 00000000..68331932
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetLineParametersCommand.cs
@@ -0,0 +1,20 @@
+namespace Ryujinx.Graphics.GAL.Multithreading.Commands
+{
+ struct SetLineParametersCommand : IGALCommand, IGALCommand<SetLineParametersCommand>
+ {
+ 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/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetLogicOpStateCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetLogicOpStateCommand.cs
new file mode 100644
index 00000000..2d7fc169
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetLogicOpStateCommand.cs
@@ -0,0 +1,20 @@
+namespace Ryujinx.Graphics.GAL.Multithreading.Commands
+{
+ struct SetLogicOpStateCommand : IGALCommand, IGALCommand<SetLogicOpStateCommand>
+ {
+ 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/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetMultisampleStateCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetMultisampleStateCommand.cs
new file mode 100644
index 00000000..f7b4969a
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetMultisampleStateCommand.cs
@@ -0,0 +1,18 @@
+namespace Ryujinx.Graphics.GAL.Multithreading.Commands
+{
+ struct SetMultisampleStateCommand : IGALCommand, IGALCommand<SetMultisampleStateCommand>
+ {
+ public CommandType CommandType => CommandType.SetMultisampleState;
+ private MultisampleDescriptor _multisample;
+
+ public void Set(MultisampleDescriptor multisample)
+ {
+ _multisample = multisample;
+ }
+
+ public static void Run(ref SetMultisampleStateCommand command, ThreadedRenderer threaded, IRenderer renderer)
+ {
+ renderer.Pipeline.SetMultisampleState(command._multisample);
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetPatchParametersCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetPatchParametersCommand.cs
new file mode 100644
index 00000000..815bc3c2
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetPatchParametersCommand.cs
@@ -0,0 +1,25 @@
+using Ryujinx.Common.Memory;
+using System;
+
+namespace Ryujinx.Graphics.GAL.Multithreading.Commands
+{
+ struct SetPatchParametersCommand : IGALCommand, IGALCommand<SetPatchParametersCommand>
+ {
+ public CommandType CommandType => CommandType.SetPatchParameters;
+ private int _vertices;
+ private Array4<float> _defaultOuterLevel;
+ private Array2<float> _defaultInnerLevel;
+
+ public void Set(int vertices, ReadOnlySpan<float> defaultOuterLevel, ReadOnlySpan<float> defaultInnerLevel)
+ {
+ _vertices = vertices;
+ defaultOuterLevel.CopyTo(_defaultOuterLevel.AsSpan());
+ defaultInnerLevel.CopyTo(_defaultInnerLevel.AsSpan());
+ }
+
+ public static void Run(ref SetPatchParametersCommand command, ThreadedRenderer threaded, IRenderer renderer)
+ {
+ renderer.Pipeline.SetPatchParameters(command._vertices, command._defaultOuterLevel.AsSpan(), command._defaultInnerLevel.AsSpan());
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetPointParametersCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetPointParametersCommand.cs
new file mode 100644
index 00000000..e3fad0f8
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetPointParametersCommand.cs
@@ -0,0 +1,24 @@
+namespace Ryujinx.Graphics.GAL.Multithreading.Commands
+{
+ struct SetPointParametersCommand : IGALCommand, IGALCommand<SetPointParametersCommand>
+ {
+ 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/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetPolygonModeCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetPolygonModeCommand.cs
new file mode 100644
index 00000000..ea2f838b
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetPolygonModeCommand.cs
@@ -0,0 +1,20 @@
+namespace Ryujinx.Graphics.GAL.Multithreading.Commands
+{
+ struct SetPolygonModeCommand : IGALCommand, IGALCommand<SetPolygonModeCommand>
+ {
+ public CommandType CommandType => CommandType.SetPolygonMode;
+ private PolygonMode _frontMode;
+ private PolygonMode _backMode;
+
+ public void Set(PolygonMode frontMode, PolygonMode backMode)
+ {
+ _frontMode = frontMode;
+ _backMode = backMode;
+ }
+
+ public static void Run(ref SetPolygonModeCommand command, ThreadedRenderer threaded, IRenderer renderer)
+ {
+ renderer.Pipeline.SetPolygonMode(command._frontMode, command._backMode);
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetPrimitiveRestartCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetPrimitiveRestartCommand.cs
new file mode 100644
index 00000000..26b88b01
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetPrimitiveRestartCommand.cs
@@ -0,0 +1,20 @@
+namespace Ryujinx.Graphics.GAL.Multithreading.Commands
+{
+ struct SetPrimitiveRestartCommand : IGALCommand, IGALCommand<SetPrimitiveRestartCommand>
+ {
+ 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/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetPrimitiveTopologyCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetPrimitiveTopologyCommand.cs
new file mode 100644
index 00000000..062c4e57
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetPrimitiveTopologyCommand.cs
@@ -0,0 +1,18 @@
+namespace Ryujinx.Graphics.GAL.Multithreading.Commands
+{
+ struct SetPrimitiveTopologyCommand : IGALCommand, IGALCommand<SetPrimitiveTopologyCommand>
+ {
+ 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/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetProgramCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetProgramCommand.cs
new file mode 100644
index 00000000..fa2e9a8a
--- /dev/null
+++ b/src/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, IGALCommand<SetProgramCommand>
+ {
+ public CommandType CommandType => CommandType.SetProgram;
+ private TableRef<IProgram> _program;
+
+ public void Set(TableRef<IProgram> program)
+ {
+ _program = program;
+ }
+
+ public static void Run(ref SetProgramCommand command, ThreadedRenderer threaded, IRenderer renderer)
+ {
+ ThreadedProgram program = command._program.GetAs<ThreadedProgram>(threaded);
+
+ threaded.Programs.WaitForProgram(program);
+
+ renderer.Pipeline.SetProgram(program.Base);
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetRasterizerDiscardCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetRasterizerDiscardCommand.cs
new file mode 100644
index 00000000..d2095a4f
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetRasterizerDiscardCommand.cs
@@ -0,0 +1,18 @@
+namespace Ryujinx.Graphics.GAL.Multithreading.Commands
+{
+ struct SetRasterizerDiscardCommand : IGALCommand, IGALCommand<SetRasterizerDiscardCommand>
+ {
+ 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/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetRenderTargetColorMasksCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetRenderTargetColorMasksCommand.cs
new file mode 100644
index 00000000..c247ff3a
--- /dev/null
+++ b/src/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, IGALCommand<SetRenderTargetColorMasksCommand>
+ {
+ public CommandType CommandType => CommandType.SetRenderTargetColorMasks;
+ private SpanRef<uint> _componentMask;
+
+ public void Set(SpanRef<uint> componentMask)
+ {
+ _componentMask = componentMask;
+ }
+
+ public static void Run(ref SetRenderTargetColorMasksCommand command, ThreadedRenderer threaded, IRenderer renderer)
+ {
+ ReadOnlySpan<uint> componentMask = command._componentMask.Get(threaded);
+ renderer.Pipeline.SetRenderTargetColorMasks(componentMask);
+ command._componentMask.Dispose(threaded);
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetRenderTargetScaleCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetRenderTargetScaleCommand.cs
new file mode 100644
index 00000000..7cb5ec11
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetRenderTargetScaleCommand.cs
@@ -0,0 +1,18 @@
+namespace Ryujinx.Graphics.GAL.Multithreading.Commands
+{
+ struct SetRenderTargetScaleCommand : IGALCommand, IGALCommand<SetRenderTargetScaleCommand>
+ {
+ 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/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetRenderTargetsCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetRenderTargetsCommand.cs
new file mode 100644
index 00000000..0b175a72
--- /dev/null
+++ b/src/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, IGALCommand<SetRenderTargetsCommand>
+ {
+ public CommandType CommandType => CommandType.SetRenderTargets;
+ private TableRef<ITexture[]> _colors;
+ private TableRef<ITexture> _depthStencil;
+
+ public void Set(TableRef<ITexture[]> colors, TableRef<ITexture> 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<ThreadedTexture>(threaded)?.Base);
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetScissorsCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetScissorsCommand.cs
new file mode 100644
index 00000000..985d775e
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetScissorsCommand.cs
@@ -0,0 +1,22 @@
+using Ryujinx.Graphics.GAL.Multithreading.Model;
+
+namespace Ryujinx.Graphics.GAL.Multithreading.Commands
+{
+ struct SetScissorsCommand : IGALCommand, IGALCommand<SetScissorsCommand>
+ {
+ public CommandType CommandType => CommandType.SetScissor;
+ private SpanRef<Rectangle<int>> _scissors;
+
+ public void Set(SpanRef<Rectangle<int>> scissors)
+ {
+ _scissors = scissors;
+ }
+
+ public static void Run(ref SetScissorsCommand command, ThreadedRenderer threaded, IRenderer renderer)
+ {
+ renderer.Pipeline.SetScissors(command._scissors.Get(threaded));
+
+ command._scissors.Dispose(threaded);
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetStencilTestCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetStencilTestCommand.cs
new file mode 100644
index 00000000..41bff97e
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetStencilTestCommand.cs
@@ -0,0 +1,18 @@
+namespace Ryujinx.Graphics.GAL.Multithreading.Commands
+{
+ struct SetStencilTestCommand : IGALCommand, IGALCommand<SetStencilTestCommand>
+ {
+ 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/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetStorageBuffersCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetStorageBuffersCommand.cs
new file mode 100644
index 00000000..6ecb0989
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetStorageBuffersCommand.cs
@@ -0,0 +1,23 @@
+using Ryujinx.Graphics.GAL.Multithreading.Model;
+using System;
+
+namespace Ryujinx.Graphics.GAL.Multithreading.Commands
+{
+ struct SetStorageBuffersCommand : IGALCommand, IGALCommand<SetStorageBuffersCommand>
+ {
+ public CommandType CommandType => CommandType.SetStorageBuffers;
+ private SpanRef<BufferAssignment> _buffers;
+
+ public void Set(SpanRef<BufferAssignment> buffers)
+ {
+ _buffers = buffers;
+ }
+
+ public static void Run(ref SetStorageBuffersCommand command, ThreadedRenderer threaded, IRenderer renderer)
+ {
+ Span<BufferAssignment> buffers = command._buffers.Get(threaded);
+ renderer.Pipeline.SetStorageBuffers(threaded.Buffers.MapBufferRanges(buffers));
+ command._buffers.Dispose(threaded);
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetTextureAndSamplerCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetTextureAndSamplerCommand.cs
new file mode 100644
index 00000000..5e8e0854
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetTextureAndSamplerCommand.cs
@@ -0,0 +1,28 @@
+using Ryujinx.Graphics.GAL.Multithreading.Model;
+using Ryujinx.Graphics.GAL.Multithreading.Resources;
+using Ryujinx.Graphics.Shader;
+
+namespace Ryujinx.Graphics.GAL.Multithreading.Commands
+{
+ struct SetTextureAndSamplerCommand : IGALCommand, IGALCommand<SetTextureAndSamplerCommand>
+ {
+ public CommandType CommandType => CommandType.SetTextureAndSampler;
+ private ShaderStage _stage;
+ private int _binding;
+ private TableRef<ITexture> _texture;
+ private TableRef<ISampler> _sampler;
+
+ public void Set(ShaderStage stage, int binding, TableRef<ITexture> texture, TableRef<ISampler> sampler)
+ {
+ _stage = stage;
+ _binding = binding;
+ _texture = texture;
+ _sampler = sampler;
+ }
+
+ public static void Run(ref SetTextureAndSamplerCommand command, ThreadedRenderer threaded, IRenderer renderer)
+ {
+ renderer.Pipeline.SetTextureAndSampler(command._stage, command._binding, command._texture.GetAs<ThreadedTexture>(threaded)?.Base, command._sampler.GetAs<ThreadedSampler>(threaded)?.Base);
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetTransformFeedbackBuffersCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetTransformFeedbackBuffersCommand.cs
new file mode 100644
index 00000000..e0d4ef2d
--- /dev/null
+++ b/src/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, IGALCommand<SetTransformFeedbackBuffersCommand>
+ {
+ public CommandType CommandType => CommandType.SetTransformFeedbackBuffers;
+ private SpanRef<BufferRange> _buffers;
+
+ public void Set(SpanRef<BufferRange> buffers)
+ {
+ _buffers = buffers;
+ }
+
+ public static void Run(ref SetTransformFeedbackBuffersCommand command, ThreadedRenderer threaded, IRenderer renderer)
+ {
+ Span<BufferRange> buffers = command._buffers.Get(threaded);
+ renderer.Pipeline.SetTransformFeedbackBuffers(threaded.Buffers.MapBufferRanges(buffers));
+ command._buffers.Dispose(threaded);
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetUniformBuffersCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetUniformBuffersCommand.cs
new file mode 100644
index 00000000..9e93db9e
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetUniformBuffersCommand.cs
@@ -0,0 +1,23 @@
+using Ryujinx.Graphics.GAL.Multithreading.Model;
+using System;
+
+namespace Ryujinx.Graphics.GAL.Multithreading.Commands
+{
+ struct SetUniformBuffersCommand : IGALCommand, IGALCommand<SetUniformBuffersCommand>
+ {
+ public CommandType CommandType => CommandType.SetUniformBuffers;
+ private SpanRef<BufferAssignment> _buffers;
+
+ public void Set(SpanRef<BufferAssignment> buffers)
+ {
+ _buffers = buffers;
+ }
+
+ public static void Run(ref SetUniformBuffersCommand command, ThreadedRenderer threaded, IRenderer renderer)
+ {
+ Span<BufferAssignment> buffers = command._buffers.Get(threaded);
+ renderer.Pipeline.SetUniformBuffers(threaded.Buffers.MapBufferRanges(buffers));
+ command._buffers.Dispose(threaded);
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetUserClipDistanceCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetUserClipDistanceCommand.cs
new file mode 100644
index 00000000..4336ce49
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetUserClipDistanceCommand.cs
@@ -0,0 +1,20 @@
+namespace Ryujinx.Graphics.GAL.Multithreading.Commands
+{
+ struct SetUserClipDistanceCommand : IGALCommand, IGALCommand<SetUserClipDistanceCommand>
+ {
+ 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/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetVertexAttribsCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetVertexAttribsCommand.cs
new file mode 100644
index 00000000..e442c72d
--- /dev/null
+++ b/src/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, IGALCommand<SetVertexAttribsCommand>
+ {
+ public CommandType CommandType => CommandType.SetVertexAttribs;
+ private SpanRef<VertexAttribDescriptor> _vertexAttribs;
+
+ public void Set(SpanRef<VertexAttribDescriptor> vertexAttribs)
+ {
+ _vertexAttribs = vertexAttribs;
+ }
+
+ public static void Run(ref SetVertexAttribsCommand command, ThreadedRenderer threaded, IRenderer renderer)
+ {
+ ReadOnlySpan<VertexAttribDescriptor> vertexAttribs = command._vertexAttribs.Get(threaded);
+ renderer.Pipeline.SetVertexAttribs(vertexAttribs);
+ command._vertexAttribs.Dispose(threaded);
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetVertexBuffersCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetVertexBuffersCommand.cs
new file mode 100644
index 00000000..585da2a4
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetVertexBuffersCommand.cs
@@ -0,0 +1,23 @@
+using Ryujinx.Graphics.GAL.Multithreading.Model;
+using System;
+
+namespace Ryujinx.Graphics.GAL.Multithreading.Commands
+{
+ struct SetVertexBuffersCommand : IGALCommand, IGALCommand<SetVertexBuffersCommand>
+ {
+ public CommandType CommandType => CommandType.SetVertexBuffers;
+ private SpanRef<VertexBufferDescriptor> _vertexBuffers;
+
+ public void Set(SpanRef<VertexBufferDescriptor> vertexBuffers)
+ {
+ _vertexBuffers = vertexBuffers;
+ }
+
+ public static void Run(ref SetVertexBuffersCommand command, ThreadedRenderer threaded, IRenderer renderer)
+ {
+ Span<VertexBufferDescriptor> vertexBuffers = command._vertexBuffers.Get(threaded);
+ renderer.Pipeline.SetVertexBuffers(threaded.Buffers.MapBufferRanges(vertexBuffers));
+ command._vertexBuffers.Dispose(threaded);
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetViewportsCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetViewportsCommand.cs
new file mode 100644
index 00000000..c18bd811
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetViewportsCommand.cs
@@ -0,0 +1,25 @@
+using Ryujinx.Graphics.GAL.Multithreading.Model;
+using System;
+
+namespace Ryujinx.Graphics.GAL.Multithreading.Commands
+{
+ struct SetViewportsCommand : IGALCommand, IGALCommand<SetViewportsCommand>
+ {
+ public CommandType CommandType => CommandType.SetViewports;
+ private SpanRef<Viewport> _viewports;
+ private bool _disableTransform;
+
+ public void Set(SpanRef<Viewport> viewports, bool disableTransform)
+ {
+ _viewports = viewports;
+ _disableTransform = disableTransform;
+ }
+
+ public static void Run(ref SetViewportsCommand command, ThreadedRenderer threaded, IRenderer renderer)
+ {
+ ReadOnlySpan<Viewport> viewports = command._viewports.Get(threaded);
+ renderer.Pipeline.SetViewports(viewports, command._disableTransform);
+ command._viewports.Dispose(threaded);
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureCopyToCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureCopyToCommand.cs
new file mode 100644
index 00000000..02d0b639
--- /dev/null
+++ b/src/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, IGALCommand<TextureCopyToCommand>
+ {
+ public CommandType CommandType => CommandType.TextureCopyTo;
+ private TableRef<ThreadedTexture> _texture;
+ private TableRef<ThreadedTexture> _destination;
+ private int _firstLayer;
+ private int _firstLevel;
+
+ public void Set(TableRef<ThreadedTexture> texture, TableRef<ThreadedTexture> 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/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureCopyToScaledCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureCopyToScaledCommand.cs
new file mode 100644
index 00000000..6b83d3f8
--- /dev/null
+++ b/src/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, IGALCommand<TextureCopyToScaledCommand>
+ {
+ public CommandType CommandType => CommandType.TextureCopyToScaled;
+ private TableRef<ThreadedTexture> _texture;
+ private TableRef<ThreadedTexture> _destination;
+ private Extents2D _srcRegion;
+ private Extents2D _dstRegion;
+ private bool _linearFilter;
+
+ public void Set(TableRef<ThreadedTexture> texture, TableRef<ThreadedTexture> 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/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureCopyToSliceCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureCopyToSliceCommand.cs
new file mode 100644
index 00000000..2a340a70
--- /dev/null
+++ b/src/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, IGALCommand<TextureCopyToSliceCommand>
+ {
+ public CommandType CommandType => CommandType.TextureCopyToSlice;
+ private TableRef<ThreadedTexture> _texture;
+ private TableRef<ThreadedTexture> _destination;
+ private int _srcLayer;
+ private int _dstLayer;
+ private int _srcLevel;
+ private int _dstLevel;
+
+ public void Set(TableRef<ThreadedTexture> texture, TableRef<ThreadedTexture> 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/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureCreateViewCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureCreateViewCommand.cs
new file mode 100644
index 00000000..09e9ca2f
--- /dev/null
+++ b/src/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, IGALCommand<TextureCreateViewCommand>
+ {
+ public CommandType CommandType => CommandType.TextureCreateView;
+ private TableRef<ThreadedTexture> _texture;
+ private TableRef<ThreadedTexture> _destination;
+ private TextureCreateInfo _info;
+ private int _firstLayer;
+ private int _firstLevel;
+
+ public void Set(TableRef<ThreadedTexture> texture, TableRef<ThreadedTexture> 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/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureGetDataCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureGetDataCommand.cs
new file mode 100644
index 00000000..91320d45
--- /dev/null
+++ b/src/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, IGALCommand<TextureGetDataCommand>
+ {
+ public CommandType CommandType => CommandType.TextureGetData;
+ private TableRef<ThreadedTexture> _texture;
+ private TableRef<ResultBox<PinnedSpan<byte>>> _result;
+
+ public void Set(TableRef<ThreadedTexture> texture, TableRef<ResultBox<PinnedSpan<byte>>> result)
+ {
+ _texture = texture;
+ _result = result;
+ }
+
+ public static void Run(ref TextureGetDataCommand command, ThreadedRenderer threaded, IRenderer renderer)
+ {
+ PinnedSpan<byte> result = command._texture.Get(threaded).Base.GetData();
+
+ command._result.Get(threaded).Result = result;
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureGetDataSliceCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureGetDataSliceCommand.cs
new file mode 100644
index 00000000..ec06cc4d
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureGetDataSliceCommand.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.Texture
+{
+ struct TextureGetDataSliceCommand : IGALCommand, IGALCommand<TextureGetDataSliceCommand>
+ {
+ public CommandType CommandType => CommandType.TextureGetDataSlice;
+ private TableRef<ThreadedTexture> _texture;
+ private TableRef<ResultBox<PinnedSpan<byte>>> _result;
+ private int _layer;
+ private int _level;
+
+ public void Set(TableRef<ThreadedTexture> texture, TableRef<ResultBox<PinnedSpan<byte>>> result, int layer, int level)
+ {
+ _texture = texture;
+ _result = result;
+ _layer = layer;
+ _level = level;
+ }
+
+ public static void Run(ref TextureGetDataSliceCommand command, ThreadedRenderer threaded, IRenderer renderer)
+ {
+ PinnedSpan<byte> result = command._texture.Get(threaded).Base.GetData(command._layer, command._level);
+
+ command._result.Get(threaded).Result = result;
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureReleaseCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureReleaseCommand.cs
new file mode 100644
index 00000000..61486e09
--- /dev/null
+++ b/src/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, IGALCommand<TextureReleaseCommand>
+ {
+ public CommandType CommandType => CommandType.TextureRelease;
+ private TableRef<ThreadedTexture> _texture;
+
+ public void Set(TableRef<ThreadedTexture> texture)
+ {
+ _texture = texture;
+ }
+
+ public static void Run(ref TextureReleaseCommand command, ThreadedRenderer threaded, IRenderer renderer)
+ {
+ command._texture.Get(threaded).Base.Release();
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureSetDataCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureSetDataCommand.cs
new file mode 100644
index 00000000..cfbaffd3
--- /dev/null
+++ b/src/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, IGALCommand<TextureSetDataCommand>
+ {
+ public CommandType CommandType => CommandType.TextureSetData;
+ private TableRef<ThreadedTexture> _texture;
+ private TableRef<byte[]> _data;
+
+ public void Set(TableRef<ThreadedTexture> texture, TableRef<byte[]> 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<byte>(command._data.Get(threaded)));
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureSetDataSliceCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureSetDataSliceCommand.cs
new file mode 100644
index 00000000..a7126f61
--- /dev/null
+++ b/src/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, IGALCommand<TextureSetDataSliceCommand>
+ {
+ public CommandType CommandType => CommandType.TextureSetDataSlice;
+ private TableRef<ThreadedTexture> _texture;
+ private TableRef<byte[]> _data;
+ private int _layer;
+ private int _level;
+
+ public void Set(TableRef<ThreadedTexture> texture, TableRef<byte[]> 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<byte>(command._data.Get(threaded)), command._layer, command._level);
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureSetDataSliceRegionCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureSetDataSliceRegionCommand.cs
new file mode 100644
index 00000000..4df83e08
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureSetDataSliceRegionCommand.cs
@@ -0,0 +1,31 @@
+using Ryujinx.Graphics.GAL.Multithreading.Model;
+using Ryujinx.Graphics.GAL.Multithreading.Resources;
+using System;
+
+namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture
+{
+ struct TextureSetDataSliceRegionCommand : IGALCommand, IGALCommand<TextureSetDataSliceRegionCommand>
+ {
+ public CommandType CommandType => CommandType.TextureSetDataSliceRegion;
+ private TableRef<ThreadedTexture> _texture;
+ private TableRef<byte[]> _data;
+ private int _layer;
+ private int _level;
+ private Rectangle<int> _region;
+
+ public void Set(TableRef<ThreadedTexture> texture, TableRef<byte[]> data, int layer, int level, Rectangle<int> region)
+ {
+ _texture = texture;
+ _data = data;
+ _layer = layer;
+ _level = level;
+ _region = region;
+ }
+
+ public static void Run(ref TextureSetDataSliceRegionCommand command, ThreadedRenderer threaded, IRenderer renderer)
+ {
+ ThreadedTexture texture = command._texture.Get(threaded);
+ texture.Base.SetData(new ReadOnlySpan<byte>(command._data.Get(threaded)), command._layer, command._level, command._region);
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureSetStorageCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureSetStorageCommand.cs
new file mode 100644
index 00000000..2a1943a9
--- /dev/null
+++ b/src/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, IGALCommand<TextureSetStorageCommand>
+ {
+ public CommandType CommandType => CommandType.TextureSetStorage;
+ private TableRef<ThreadedTexture> _texture;
+ private BufferRange _storage;
+
+ public void Set(TableRef<ThreadedTexture> 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/src/Ryujinx.Graphics.GAL/Multithreading/Commands/TextureBarrierCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/TextureBarrierCommand.cs
new file mode 100644
index 00000000..ce1a83a7
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/TextureBarrierCommand.cs
@@ -0,0 +1,12 @@
+namespace Ryujinx.Graphics.GAL.Multithreading.Commands
+{
+ struct TextureBarrierCommand : IGALCommand, IGALCommand<TextureBarrierCommand>
+ {
+ public CommandType CommandType => CommandType.TextureBarrier;
+
+ public static void Run(ref TextureBarrierCommand command, ThreadedRenderer threaded, IRenderer renderer)
+ {
+ renderer.Pipeline.TextureBarrier();
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/TextureBarrierTiledCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/TextureBarrierTiledCommand.cs
new file mode 100644
index 00000000..c65ffe2e
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/TextureBarrierTiledCommand.cs
@@ -0,0 +1,12 @@
+namespace Ryujinx.Graphics.GAL.Multithreading.Commands
+{
+ struct TextureBarrierTiledCommand : IGALCommand, IGALCommand<TextureBarrierTiledCommand>
+ {
+ public CommandType CommandType => CommandType.TextureBarrierTiled;
+
+ public static void Run(ref TextureBarrierTiledCommand command, ThreadedRenderer threaded, IRenderer renderer)
+ {
+ renderer.Pipeline.TextureBarrierTiled();
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/TryHostConditionalRenderingCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/TryHostConditionalRenderingCommand.cs
new file mode 100644
index 00000000..9124ca1f
--- /dev/null
+++ b/src/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, IGALCommand<TryHostConditionalRenderingCommand>
+ {
+ public CommandType CommandType => CommandType.TryHostConditionalRendering;
+ private TableRef<ThreadedCounterEvent> _value;
+ private ulong _compare;
+ private bool _isEqual;
+
+ public void Set(TableRef<ThreadedCounterEvent> 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/src/Ryujinx.Graphics.GAL/Multithreading/Commands/TryHostConditionalRenderingFlushCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/TryHostConditionalRenderingFlushCommand.cs
new file mode 100644
index 00000000..a5d07640
--- /dev/null
+++ b/src/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, IGALCommand<TryHostConditionalRenderingFlushCommand>
+ {
+ public CommandType CommandType => CommandType.TryHostConditionalRenderingFlush;
+ private TableRef<ThreadedCounterEvent> _value;
+ private TableRef<ThreadedCounterEvent> _compare;
+ private bool _isEqual;
+
+ public void Set(TableRef<ThreadedCounterEvent> value, TableRef<ThreadedCounterEvent> 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/src/Ryujinx.Graphics.GAL/Multithreading/Commands/UpdateRenderScaleCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/UpdateRenderScaleCommand.cs
new file mode 100644
index 00000000..ebe14150
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/UpdateRenderScaleCommand.cs
@@ -0,0 +1,25 @@
+using Ryujinx.Graphics.GAL.Multithreading.Model;
+
+namespace Ryujinx.Graphics.GAL.Multithreading.Commands
+{
+ struct UpdateRenderScaleCommand : IGALCommand, IGALCommand<UpdateRenderScaleCommand>
+ {
+ public CommandType CommandType => CommandType.UpdateRenderScale;
+ private SpanRef<float> _scales;
+ private int _totalCount;
+ private int _fragmentCount;
+
+ public void Set(SpanRef<float> scales, int totalCount, int fragmentCount)
+ {
+ _scales = scales;
+ _totalCount = totalCount;
+ _fragmentCount = fragmentCount;
+ }
+
+ public static void Run(ref UpdateRenderScaleCommand command, ThreadedRenderer threaded, IRenderer renderer)
+ {
+ renderer.Pipeline.UpdateRenderScale(command._scales.Get(threaded), command._totalCount, command._fragmentCount);
+ command._scales.Dispose(threaded);
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Window/WindowPresentCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Window/WindowPresentCommand.cs
new file mode 100644
index 00000000..6a24cd35
--- /dev/null
+++ b/src/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, IGALCommand<WindowPresentCommand>
+ {
+ public CommandType CommandType => CommandType.WindowPresent;
+ private TableRef<ThreadedTexture> _texture;
+ private ImageCrop _crop;
+ private TableRef<Action> _swapBuffersCallback;
+
+ public void Set(TableRef<ThreadedTexture> texture, ImageCrop crop, TableRef<Action> 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/src/Ryujinx.Graphics.GAL/Multithreading/Model/CircularSpanPool.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Model/CircularSpanPool.cs
new file mode 100644
index 00000000..4ea1a2c7
--- /dev/null
+++ b/src/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
+{
+ /// <summary>
+ /// A memory pool for passing through Span<T> 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.
+ /// </summary>
+ 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<T> Insert<T>(ReadOnlySpan<T> data) where T : unmanaged
+ {
+ int size = data.Length * Unsafe.SizeOf<T>();
+
+ // 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<T>(_renderer, data.ToArray());
+ }
+
+ data.CopyTo(MemoryMarshal.Cast<byte, T>(new Span<byte>(_pool).Slice(index, size)));
+
+ if (wraparound)
+ {
+ _producerSkipPosition = _producerPtr;
+ }
+
+ _producerPtr = index + size;
+
+ return new SpanRef<T>(data.Length);
+ }
+
+ public Span<T> Get<T>(int length) where T : unmanaged
+ {
+ int size = length * Unsafe.SizeOf<T>();
+
+ if (_consumerPtr == Interlocked.CompareExchange(ref _producerSkipPosition, -1, _consumerPtr))
+ {
+ _consumerPtr = 0;
+ }
+
+ return MemoryMarshal.Cast<byte, T>(new Span<byte>(_pool).Slice(_consumerPtr, size));
+ }
+
+ public void Dispose<T>(int length) where T : unmanaged
+ {
+ int size = length * Unsafe.SizeOf<T>();
+
+ _consumerPtr = _consumerPtr + size;
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Model/ResultBox.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Model/ResultBox.cs
new file mode 100644
index 00000000..7a0be785
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/Model/ResultBox.cs
@@ -0,0 +1,7 @@
+namespace Ryujinx.Graphics.GAL.Multithreading.Model
+{
+ public class ResultBox<T>
+ {
+ public T Result;
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Model/SpanRef.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Model/SpanRef.cs
new file mode 100644
index 00000000..7dbebc76
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/Model/SpanRef.cs
@@ -0,0 +1,39 @@
+using System;
+
+namespace Ryujinx.Graphics.GAL.Multithreading.Model
+{
+ struct SpanRef<T> 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<T> Get(ThreadedRenderer renderer)
+ {
+ if (_packedLengthId >= 0)
+ {
+ return renderer.SpanPool.Get<T>(_packedLengthId);
+ }
+ else
+ {
+ return new Span<T>((T[])renderer.RemoveTableRef(-(_packedLengthId + 1)));
+ }
+ }
+
+ public void Dispose(ThreadedRenderer renderer)
+ {
+ if (_packedLengthId > 0)
+ {
+ renderer.SpanPool.Dispose<T>(_packedLengthId);
+ }
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Model/TableRef.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Model/TableRef.cs
new file mode 100644
index 00000000..166aa71a
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/Model/TableRef.cs
@@ -0,0 +1,22 @@
+namespace Ryujinx.Graphics.GAL.Multithreading.Model
+{
+ struct TableRef<T>
+ {
+ 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<T2>(ThreadedRenderer renderer) where T2 : T
+ {
+ return (T2)renderer.RemoveTableRef(_index);
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Resources/ProgramQueue.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Resources/ProgramQueue.cs
new file mode 100644
index 00000000..3f982d31
--- /dev/null
+++ b/src/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
+{
+ /// <summary>
+ /// A structure handling multithreaded compilation for programs.
+ /// </summary>
+ class ProgramQueue
+ {
+ private const int MaxConcurrentCompilations = 8;
+
+ private IRenderer _renderer;
+
+ private Queue<IProgramRequest> _toCompile;
+ private List<ThreadedProgram> _inProgress;
+
+ public ProgramQueue(IRenderer renderer)
+ {
+ _renderer = renderer;
+
+ _toCompile = new Queue<IProgramRequest>();
+ _inProgress = new List<ThreadedProgram>();
+ }
+
+ 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);
+ }
+ }
+
+ /// <summary>
+ /// Process the queue until the given program has finished compiling.
+ /// This will begin compilation of other programs on the queue as well.
+ /// </summary>
+ /// <param name="program">The program to wait for</param>
+ public void WaitForProgram(ThreadedProgram program)
+ {
+ Span<SpinWait> spinWait = stackalloc SpinWait[1];
+
+ while (!program.Compiled)
+ {
+ ProcessQueue();
+
+ if (!program.Compiled)
+ {
+ spinWait[0].SpinOnce(-1);
+ }
+ }
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Resources/Programs/BinaryProgramRequest.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Resources/Programs/BinaryProgramRequest.cs
new file mode 100644
index 00000000..b4c6853f
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/Resources/Programs/BinaryProgramRequest.cs
@@ -0,0 +1,25 @@
+namespace Ryujinx.Graphics.GAL.Multithreading.Resources.Programs
+{
+ class BinaryProgramRequest : IProgramRequest
+ {
+ public ThreadedProgram Threaded { get; set; }
+
+ private byte[] _data;
+ private bool _hasFragmentShader;
+ private ShaderInfo _info;
+
+ public BinaryProgramRequest(ThreadedProgram program, byte[] data, bool hasFragmentShader, ShaderInfo info)
+ {
+ Threaded = program;
+
+ _data = data;
+ _hasFragmentShader = hasFragmentShader;
+ _info = info;
+ }
+
+ public IProgram Create(IRenderer renderer)
+ {
+ return renderer.LoadProgramBinary(_data, _hasFragmentShader, _info);
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Resources/Programs/IProgramRequest.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Resources/Programs/IProgramRequest.cs
new file mode 100644
index 00000000..cdbfe03c
--- /dev/null
+++ b/src/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/src/Ryujinx.Graphics.GAL/Multithreading/Resources/Programs/SourceProgramRequest.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Resources/Programs/SourceProgramRequest.cs
new file mode 100644
index 00000000..ff06abb1
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/Resources/Programs/SourceProgramRequest.cs
@@ -0,0 +1,23 @@
+namespace Ryujinx.Graphics.GAL.Multithreading.Resources.Programs
+{
+ class SourceProgramRequest : IProgramRequest
+ {
+ public ThreadedProgram Threaded { get; set; }
+
+ private ShaderSource[] _shaders;
+ private ShaderInfo _info;
+
+ public SourceProgramRequest(ThreadedProgram program, ShaderSource[] shaders, ShaderInfo info)
+ {
+ Threaded = program;
+
+ _shaders = shaders;
+ _info = info;
+ }
+
+ public IProgram Create(IRenderer renderer)
+ {
+ return renderer.CreateProgram(_shaders, _info);
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedCounterEvent.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedCounterEvent.cs
new file mode 100644
index 00000000..4b7471d6
--- /dev/null
+++ b/src/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<T> Ref<T>(T reference)
+ {
+ return new TableRef<T>(_renderer, reference);
+ }
+
+ public void Dispose()
+ {
+ _renderer.New<CounterEventDisposeCommand>().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<ulong> eventHandler, bool hostReserved)
+ {
+ ThreadedHelpers.SpinUntilExchange(ref _createLock, 1, 0);
+ Base = renderer.ReportCounter(type, eventHandler, hostReserved || _reserved);
+ Volatile.Write(ref _createLock, 0);
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedProgram.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedProgram.cs
new file mode 100644
index 00000000..068d058e
--- /dev/null
+++ b/src/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<T> Ref<T>(T reference)
+ {
+ return new TableRef<T>(_renderer, reference);
+ }
+
+ public void Dispose()
+ {
+ _renderer.New<ProgramDisposeCommand>().Set(Ref(this));
+ _renderer.QueueCommand();
+ }
+
+ public byte[] GetBinary()
+ {
+ ResultBox<byte[]> box = new ResultBox<byte[]>();
+ _renderer.New<ProgramGetBinaryCommand>().Set(Ref(this), Ref(box));
+ _renderer.InvokeCommand();
+
+ return box.Result;
+ }
+
+ public ProgramLinkStatus CheckProgramLink(bool blocking)
+ {
+ ResultBox<ProgramLinkStatus> box = new ResultBox<ProgramLinkStatus>();
+ _renderer.New<ProgramCheckLinkCommand>().Set(Ref(this), blocking, Ref(box));
+ _renderer.InvokeCommand();
+
+ return box.Result;
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedSampler.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedSampler.cs
new file mode 100644
index 00000000..d8de9a70
--- /dev/null
+++ b/src/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<SamplerDisposeCommand>().Set(new TableRef<ThreadedSampler>(_renderer, this));
+ _renderer.QueueCommand();
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedTexture.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedTexture.cs
new file mode 100644
index 00000000..ee1cfa29
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedTexture.cs
@@ -0,0 +1,141 @@
+using Ryujinx.Common.Memory;
+using Ryujinx.Graphics.GAL.Multithreading.Commands.Texture;
+using Ryujinx.Graphics.GAL.Multithreading.Model;
+using System;
+
+namespace Ryujinx.Graphics.GAL.Multithreading.Resources
+{
+ /// <summary>
+ /// Threaded representation of a texture.
+ /// </summary>
+ 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<T> Ref<T>(T reference)
+ {
+ return new TableRef<T>(_renderer, reference);
+ }
+
+ public void CopyTo(ITexture destination, int firstLayer, int firstLevel)
+ {
+ _renderer.New<TextureCopyToCommand>().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<TextureCopyToSliceCommand>().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<TextureCopyToScaledCommand>().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<TextureCreateViewCommand>().Set(Ref(this), Ref(newTex), info, firstLayer, firstLevel);
+ _renderer.QueueCommand();
+
+ return newTex;
+ }
+
+ public PinnedSpan<byte> GetData()
+ {
+ if (_renderer.IsGpuThread())
+ {
+ ResultBox<PinnedSpan<byte>> box = new ResultBox<PinnedSpan<byte>>();
+ _renderer.New<TextureGetDataCommand>().Set(Ref(this), Ref(box));
+ _renderer.InvokeCommand();
+
+ return box.Result;
+ }
+ else
+ {
+ ThreadedHelpers.SpinUntilNonNull(ref Base);
+
+ return Base.GetData();
+ }
+ }
+
+ public PinnedSpan<byte> GetData(int layer, int level)
+ {
+ if (_renderer.IsGpuThread())
+ {
+ ResultBox<PinnedSpan<byte>> box = new ResultBox<PinnedSpan<byte>>();
+ _renderer.New<TextureGetDataSliceCommand>().Set(Ref(this), Ref(box), layer, level);
+ _renderer.InvokeCommand();
+
+ return box.Result;
+ }
+ else
+ {
+ ThreadedHelpers.SpinUntilNonNull(ref Base);
+
+ return Base.GetData(layer, level);
+ }
+ }
+
+ public void SetData(SpanOrArray<byte> data)
+ {
+ _renderer.New<TextureSetDataCommand>().Set(Ref(this), Ref(data.ToArray()));
+ _renderer.QueueCommand();
+ }
+
+ public void SetData(SpanOrArray<byte> data, int layer, int level)
+ {
+ _renderer.New<TextureSetDataSliceCommand>().Set(Ref(this), Ref(data.ToArray()), layer, level);
+ _renderer.QueueCommand();
+ }
+
+ public void SetData(SpanOrArray<byte> data, int layer, int level, Rectangle<int> region)
+ {
+ _renderer.New<TextureSetDataSliceRegionCommand>().Set(Ref(this), Ref(data.ToArray()), layer, level, region);
+ _renderer.QueueCommand();
+ }
+
+ public void SetStorage(BufferRange buffer)
+ {
+ _renderer.New<TextureSetStorageCommand>().Set(Ref(this), buffer);
+ _renderer.QueueCommand();
+ }
+
+ public void Release()
+ {
+ _renderer.New<TextureReleaseCommand>().Set(Ref(this));
+ _renderer.QueueCommand();
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/SyncMap.cs b/src/Ryujinx.Graphics.GAL/Multithreading/SyncMap.cs
new file mode 100644
index 00000000..ae09e852
--- /dev/null
+++ b/src/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<ulong> _inFlight = new HashSet<ulong>();
+ 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/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedHelpers.cs b/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedHelpers.cs
new file mode 100644
index 00000000..7ddb19e6
--- /dev/null
+++ b/src/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<T>(ref T obj) where T : class
+ {
+ Span<SpinWait> spinWait = stackalloc SpinWait[1];
+
+ while (obj == null)
+ {
+ spinWait[0].SpinOnce(-1);
+ }
+ }
+
+ public static void SpinUntilExchange(ref int target, int value, int comparand)
+ {
+ Span<SpinWait> spinWait = stackalloc SpinWait[1];
+
+ while (Interlocked.CompareExchange(ref target, value, comparand) != comparand)
+ {
+ spinWait[0].SpinOnce(-1);
+ }
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs b/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs
new file mode 100644
index 00000000..1bdc9cf4
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs
@@ -0,0 +1,380 @@
+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<T> Ref<T>(T reference)
+ {
+ return new TableRef<T>(_renderer, reference);
+ }
+
+ public void Barrier()
+ {
+ _renderer.New<BarrierCommand>();
+ _renderer.QueueCommand();
+ }
+
+ public void BeginTransformFeedback(PrimitiveTopology topology)
+ {
+ _renderer.New<BeginTransformFeedbackCommand>().Set(topology);
+ _renderer.QueueCommand();
+ }
+
+ public void ClearBuffer(BufferHandle destination, int offset, int size, uint value)
+ {
+ _renderer.New<ClearBufferCommand>().Set(destination, offset, size, value);
+ _renderer.QueueCommand();
+ }
+
+ public void ClearRenderTargetColor(int index, int layer, int layerCount, uint componentMask, ColorF color)
+ {
+ _renderer.New<ClearRenderTargetColorCommand>().Set(index, layer, layerCount, componentMask, color);
+ _renderer.QueueCommand();
+ }
+
+ public void ClearRenderTargetDepthStencil(int layer, int layerCount, float depthValue, bool depthMask, int stencilValue, int stencilMask)
+ {
+ _renderer.New<ClearRenderTargetDepthStencilCommand>().Set(layer, layerCount, depthValue, depthMask, stencilValue, stencilMask);
+ _renderer.QueueCommand();
+ }
+
+ public void CommandBufferBarrier()
+ {
+ _renderer.New<CommandBufferBarrierCommand>();
+ _renderer.QueueCommand();
+ }
+
+ public void CopyBuffer(BufferHandle source, BufferHandle destination, int srcOffset, int dstOffset, int size)
+ {
+ _renderer.New<CopyBufferCommand>().Set(source, destination, srcOffset, dstOffset, size);
+ _renderer.QueueCommand();
+ }
+
+ public void DispatchCompute(int groupsX, int groupsY, int groupsZ)
+ {
+ _renderer.New<DispatchComputeCommand>().Set(groupsX, groupsY, groupsZ);
+ _renderer.QueueCommand();
+ }
+
+ public void Draw(int vertexCount, int instanceCount, int firstVertex, int firstInstance)
+ {
+ _renderer.New<DrawCommand>().Set(vertexCount, instanceCount, firstVertex, firstInstance);
+ _renderer.QueueCommand();
+ }
+
+ public void DrawIndexed(int indexCount, int instanceCount, int firstIndex, int firstVertex, int firstInstance)
+ {
+ _renderer.New<DrawIndexedCommand>().Set(indexCount, instanceCount, firstIndex, firstVertex, firstInstance);
+ _renderer.QueueCommand();
+ }
+
+ public void DrawIndexedIndirect(BufferRange indirectBuffer)
+ {
+ _renderer.New<DrawIndexedIndirectCommand>().Set(indirectBuffer);
+ _renderer.QueueCommand();
+ }
+
+ public void DrawIndexedIndirectCount(BufferRange indirectBuffer, BufferRange parameterBuffer, int maxDrawCount, int stride)
+ {
+ _renderer.New<DrawIndexedIndirectCountCommand>().Set(indirectBuffer, parameterBuffer, maxDrawCount, stride);
+ _renderer.QueueCommand();
+ }
+
+ public void DrawIndirect(BufferRange indirectBuffer)
+ {
+ _renderer.New<DrawIndirectCommand>().Set(indirectBuffer);
+ _renderer.QueueCommand();
+ }
+
+ public void DrawIndirectCount(BufferRange indirectBuffer, BufferRange parameterBuffer, int maxDrawCount, int stride)
+ {
+ _renderer.New<DrawIndirectCountCommand>().Set(indirectBuffer, parameterBuffer, maxDrawCount, stride);
+ _renderer.QueueCommand();
+ }
+
+ public void DrawTexture(ITexture texture, ISampler sampler, Extents2DF srcRegion, Extents2DF dstRegion)
+ {
+ _renderer.New<DrawTextureCommand>().Set(Ref(texture), Ref(sampler), srcRegion, dstRegion);
+ _renderer.QueueCommand();
+ }
+
+ public void EndHostConditionalRendering()
+ {
+ _renderer.New<EndHostConditionalRenderingCommand>();
+ _renderer.QueueCommand();
+ }
+
+ public void EndTransformFeedback()
+ {
+ _renderer.New<EndTransformFeedbackCommand>();
+ _renderer.QueueCommand();
+ }
+
+ public void SetAlphaTest(bool enable, float reference, CompareOp op)
+ {
+ _renderer.New<SetAlphaTestCommand>().Set(enable, reference, op);
+ _renderer.QueueCommand();
+ }
+
+ public void SetBlendState(AdvancedBlendDescriptor blend)
+ {
+ _renderer.New<SetBlendStateAdvancedCommand>().Set(blend);
+ _renderer.QueueCommand();
+ }
+
+ public void SetBlendState(int index, BlendDescriptor blend)
+ {
+ _renderer.New<SetBlendStateCommand>().Set(index, blend);
+ _renderer.QueueCommand();
+ }
+
+ public void SetDepthBias(PolygonModeMask enables, float factor, float units, float clamp)
+ {
+ _renderer.New<SetDepthBiasCommand>().Set(enables, factor, units, clamp);
+ _renderer.QueueCommand();
+ }
+
+ public void SetDepthClamp(bool clamp)
+ {
+ _renderer.New<SetDepthClampCommand>().Set(clamp);
+ _renderer.QueueCommand();
+ }
+
+ public void SetDepthMode(DepthMode mode)
+ {
+ _renderer.New<SetDepthModeCommand>().Set(mode);
+ _renderer.QueueCommand();
+ }
+
+ public void SetDepthTest(DepthTestDescriptor depthTest)
+ {
+ _renderer.New<SetDepthTestCommand>().Set(depthTest);
+ _renderer.QueueCommand();
+ }
+
+ public void SetFaceCulling(bool enable, Face face)
+ {
+ _renderer.New<SetFaceCullingCommand>().Set(enable, face);
+ _renderer.QueueCommand();
+ }
+
+ public void SetFrontFace(FrontFace frontFace)
+ {
+ _renderer.New<SetFrontFaceCommand>().Set(frontFace);
+ _renderer.QueueCommand();
+ }
+
+ public void SetImage(int binding, ITexture texture, Format imageFormat)
+ {
+ _renderer.New<SetImageCommand>().Set(binding, Ref(texture), imageFormat);
+ _renderer.QueueCommand();
+ }
+
+ public void SetIndexBuffer(BufferRange buffer, IndexType type)
+ {
+ _renderer.New<SetIndexBufferCommand>().Set(buffer, type);
+ _renderer.QueueCommand();
+ }
+
+ public void SetLineParameters(float width, bool smooth)
+ {
+ _renderer.New<SetLineParametersCommand>().Set(width, smooth);
+ _renderer.QueueCommand();
+ }
+
+ public void SetLogicOpState(bool enable, LogicalOp op)
+ {
+ _renderer.New<SetLogicOpStateCommand>().Set(enable, op);
+ _renderer.QueueCommand();
+ }
+
+ public void SetMultisampleState(MultisampleDescriptor multisample)
+ {
+ _renderer.New<SetMultisampleStateCommand>().Set(multisample);
+ _renderer.QueueCommand();
+ }
+
+ public void SetPatchParameters(int vertices, ReadOnlySpan<float> defaultOuterLevel, ReadOnlySpan<float> defaultInnerLevel)
+ {
+ _renderer.New<SetPatchParametersCommand>().Set(vertices, defaultOuterLevel, defaultInnerLevel);
+ _renderer.QueueCommand();
+ }
+
+ public void SetPointParameters(float size, bool isProgramPointSize, bool enablePointSprite, Origin origin)
+ {
+ _renderer.New<SetPointParametersCommand>().Set(size, isProgramPointSize, enablePointSprite, origin);
+ _renderer.QueueCommand();
+ }
+
+ public void SetPolygonMode(PolygonMode frontMode, PolygonMode backMode)
+ {
+ _renderer.New<SetPolygonModeCommand>().Set(frontMode, backMode);
+ _renderer.QueueCommand();
+ }
+
+ public void SetPrimitiveRestart(bool enable, int index)
+ {
+ _renderer.New<SetPrimitiveRestartCommand>().Set(enable, index);
+ _renderer.QueueCommand();
+ }
+
+ public void SetPrimitiveTopology(PrimitiveTopology topology)
+ {
+ _renderer.New<SetPrimitiveTopologyCommand>().Set(topology);
+ _renderer.QueueCommand();
+ }
+
+ public void SetProgram(IProgram program)
+ {
+ _renderer.New<SetProgramCommand>().Set(Ref(program));
+ _renderer.QueueCommand();
+ }
+
+ public void SetRasterizerDiscard(bool discard)
+ {
+ _renderer.New<SetRasterizerDiscardCommand>().Set(discard);
+ _renderer.QueueCommand();
+ }
+
+ public void SetRenderTargetColorMasks(ReadOnlySpan<uint> componentMask)
+ {
+ _renderer.New<SetRenderTargetColorMasksCommand>().Set(_renderer.CopySpan(componentMask));
+ _renderer.QueueCommand();
+ }
+
+ public void SetRenderTargets(ITexture[] colors, ITexture depthStencil)
+ {
+ _renderer.New<SetRenderTargetsCommand>().Set(Ref(colors.ToArray()), Ref(depthStencil));
+ _renderer.QueueCommand();
+ }
+
+ public void SetRenderTargetScale(float scale)
+ {
+ _renderer.New<SetRenderTargetScaleCommand>().Set(scale);
+ _renderer.QueueCommand();
+ }
+
+ public void SetScissors(ReadOnlySpan<Rectangle<int>> scissors)
+ {
+ _renderer.New<SetScissorsCommand>().Set(_renderer.CopySpan(scissors));
+ _renderer.QueueCommand();
+ }
+
+ public void SetStencilTest(StencilTestDescriptor stencilTest)
+ {
+ _renderer.New<SetStencilTestCommand>().Set(stencilTest);
+ _renderer.QueueCommand();
+ }
+
+ public void SetStorageBuffers(ReadOnlySpan<BufferAssignment> buffers)
+ {
+ _renderer.New<SetStorageBuffersCommand>().Set(_renderer.CopySpan(buffers));
+ _renderer.QueueCommand();
+ }
+
+ public void SetTextureAndSampler(ShaderStage stage, int binding, ITexture texture, ISampler sampler)
+ {
+ _renderer.New<SetTextureAndSamplerCommand>().Set(stage, binding, Ref(texture), Ref(sampler));
+ _renderer.QueueCommand();
+ }
+
+ public void SetTransformFeedbackBuffers(ReadOnlySpan<BufferRange> buffers)
+ {
+ _renderer.New<SetTransformFeedbackBuffersCommand>().Set(_renderer.CopySpan(buffers));
+ _renderer.QueueCommand();
+ }
+
+ public void SetUniformBuffers(ReadOnlySpan<BufferAssignment> buffers)
+ {
+ _renderer.New<SetUniformBuffersCommand>().Set(_renderer.CopySpan(buffers));
+ _renderer.QueueCommand();
+ }
+
+ public void SetUserClipDistance(int index, bool enableClip)
+ {
+ _renderer.New<SetUserClipDistanceCommand>().Set(index, enableClip);
+ _renderer.QueueCommand();
+ }
+
+ public void SetVertexAttribs(ReadOnlySpan<VertexAttribDescriptor> vertexAttribs)
+ {
+ _renderer.New<SetVertexAttribsCommand>().Set(_renderer.CopySpan(vertexAttribs));
+ _renderer.QueueCommand();
+ }
+
+ public void SetVertexBuffers(ReadOnlySpan<VertexBufferDescriptor> vertexBuffers)
+ {
+ _renderer.New<SetVertexBuffersCommand>().Set(_renderer.CopySpan(vertexBuffers));
+ _renderer.QueueCommand();
+ }
+
+ public void SetViewports(ReadOnlySpan<Viewport> viewports, bool disableTransform)
+ {
+ _renderer.New<SetViewportsCommand>().Set(_renderer.CopySpan(viewports), disableTransform);
+ _renderer.QueueCommand();
+ }
+
+ public void TextureBarrier()
+ {
+ _renderer.New<TextureBarrierCommand>();
+ _renderer.QueueCommand();
+ }
+
+ public void TextureBarrierTiled()
+ {
+ _renderer.New<TextureBarrierTiledCommand>();
+ _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<TryHostConditionalRenderingCommand>().Set(Ref(evt), compare, isEqual);
+ _renderer.QueueCommand();
+ return true;
+ }
+ }
+
+ _renderer.New<TryHostConditionalRenderingFlushCommand>().Set(Ref(evt), Ref<ThreadedCounterEvent>(null), isEqual);
+ _renderer.QueueCommand();
+ return false;
+ }
+
+ public bool TryHostConditionalRendering(ICounterEvent value, ICounterEvent compare, bool isEqual)
+ {
+ _renderer.New<TryHostConditionalRenderingFlushCommand>().Set(Ref(value as ThreadedCounterEvent), Ref(compare as ThreadedCounterEvent), isEqual);
+ _renderer.QueueCommand();
+ return false;
+ }
+
+ public void UpdateRenderScale(ReadOnlySpan<float> scales, int totalCount, int fragmentCount)
+ {
+ _renderer.New<UpdateRenderScaleCommand>().Set(_renderer.CopySpan(scales.Slice(0, totalCount)), totalCount, fragmentCount);
+ _renderer.QueueCommand();
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedRenderer.cs b/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedRenderer.cs
new file mode 100644
index 00000000..2148f43f
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedRenderer.cs
@@ -0,0 +1,488 @@
+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 System;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Threading;
+
+namespace Ryujinx.Graphics.GAL.Multithreading
+{
+ /// <summary>
+ /// 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.
+ /// </summary>
+ 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 Thread _backendThread;
+ private bool _disposed;
+ private bool _running;
+
+ private AutoResetEvent _frameComplete = new AutoResetEvent(true);
+
+ private ManualResetEventSlim _galWorkAvailable;
+ private CircularSpanPool _spanPool;
+
+ private ManualResetEventSlim _invokeRun;
+ private AutoResetEvent _interruptRun;
+
+ 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;
+
+ private Action _interruptAction;
+
+ public event EventHandler<ScreenCaptureImageInfo> 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 += (sender, info) => ScreenCaptured?.Invoke(this, info);
+ renderer.SetInterruptAction(Interrupt);
+
+ Pipeline = new ThreadedPipeline(this, renderer.Pipeline);
+ Window = new ThreadedWindow(this, renderer);
+ Buffers = new BufferMap();
+ Sync = new SyncMap();
+ Programs = new ProgramQueue(renderer);
+
+ _galWorkAvailable = new ManualResetEventSlim(false);
+ _invokeRun = new ManualResetEventSlim();
+ _interruptRun = new AutoResetEvent(false);
+ _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;
+
+ _backendThread = Thread.CurrentThread;
+
+ _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();
+
+ if (Volatile.Read(ref _interruptAction) != null)
+ {
+ _interruptAction();
+ _interruptRun.Set();
+
+ Interlocked.Exchange(ref _interruptAction, null);
+ }
+
+ // 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 (Volatile.Read(ref _commandCount) > 0 && Volatile.Read(ref _interruptAction) == null)
+ {
+ int commandPtr = _consumerPtr;
+
+ Span<byte> command = new Span<byte>(_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<T> CopySpan<T>(ReadOnlySpan<T> data) where T : unmanaged
+ {
+ return _spanPool.Insert(data);
+ }
+
+ private TableRef<T> Ref<T>(T reference)
+ {
+ return new TableRef<T>(this, reference);
+ }
+
+ internal ref T New<T>() where T : struct
+ {
+ while (_producerPtr == (Volatile.Read(ref _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<byte> memory = new Span<byte>(_commandQueue, taken * _elementSize, _elementSize);
+ ref T result = ref Unsafe.As<byte, T>(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<ActionCommand>().Set(Ref(action));
+ InvokeCommand();
+ }
+ else
+ {
+ _baseRenderer.BackgroundContextAction(action, true);
+ }
+ }
+
+ public BufferHandle CreateBuffer(int size, BufferHandle storageHint)
+ {
+ BufferHandle handle = Buffers.CreateBufferHandle();
+ New<CreateBufferCommand>().Set(handle, size, storageHint);
+ QueueCommand();
+
+ return handle;
+ }
+
+ public IProgram CreateProgram(ShaderSource[] shaders, ShaderInfo info)
+ {
+ var program = new ThreadedProgram(this);
+
+ SourceProgramRequest request = new SourceProgramRequest(program, shaders, info);
+
+ Programs.Add(request);
+
+ New<CreateProgramCommand>().Set(Ref((IProgramRequest)request));
+ QueueCommand();
+
+ return program;
+ }
+
+ public ISampler CreateSampler(SamplerCreateInfo info)
+ {
+ var sampler = new ThreadedSampler(this);
+ New<CreateSamplerCommand>().Set(Ref(sampler), info);
+ QueueCommand();
+
+ return sampler;
+ }
+
+ public void CreateSync(ulong id, bool strict)
+ {
+ Sync.CreateSyncHandle(id);
+ New<CreateSyncCommand>().Set(id, strict);
+ QueueCommand();
+ }
+
+ public ITexture CreateTexture(TextureCreateInfo info, float scale)
+ {
+ if (IsGpuThread())
+ {
+ var texture = new ThreadedTexture(this, info, scale);
+ New<CreateTextureCommand>().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<BufferDisposeCommand>().Set(buffer);
+ QueueCommand();
+ }
+
+ public PinnedSpan<byte> GetBufferData(BufferHandle buffer, int offset, int size)
+ {
+ if (IsGpuThread())
+ {
+ ResultBox<PinnedSpan<byte>> box = new ResultBox<PinnedSpan<byte>>();
+ New<BufferGetDataCommand>().Set(buffer, offset, size, Ref(box));
+ InvokeCommand();
+
+ return box.Result;
+ }
+ else
+ {
+ return _baseRenderer.GetBufferData(Buffers.MapBufferBlocking(buffer), offset, size);
+ }
+ }
+
+ public Capabilities GetCapabilities()
+ {
+ ResultBox<Capabilities> box = new ResultBox<Capabilities>();
+ New<GetCapabilitiesCommand>().Set(Ref(box));
+ InvokeCommand();
+
+ return box.Result;
+ }
+
+ public ulong GetCurrentSync()
+ {
+ return _baseRenderer.GetCurrentSync();
+ }
+
+ public HardwareInfo GetHardwareInfo()
+ {
+ return _baseRenderer.GetHardwareInfo();
+ }
+
+ /// <summary>
+ /// Initialize the base renderer. Must be called on the render thread.
+ /// </summary>
+ /// <param name="logLevel">Log level to use</param>
+ public void Initialize(GraphicsDebugLevel logLevel)
+ {
+ _baseRenderer.Initialize(logLevel);
+ }
+
+ public IProgram LoadProgramBinary(byte[] programBinary, bool hasFragmentShader, ShaderInfo info)
+ {
+ var program = new ThreadedProgram(this);
+
+ BinaryProgramRequest request = new BinaryProgramRequest(program, programBinary, hasFragmentShader, info);
+ Programs.Add(request);
+
+ New<CreateProgramCommand>().Set(Ref((IProgramRequest)request));
+ QueueCommand();
+
+ return program;
+ }
+
+ public void PreFrame()
+ {
+ New<PreFrameCommand>();
+ QueueCommand();
+ }
+
+ public ICounterEvent ReportCounter(CounterType type, EventHandler<ulong> resultHandler, bool hostReserved)
+ {
+ ThreadedCounterEvent evt = new ThreadedCounterEvent(this, type, _lastSampleCounterClear);
+ New<ReportCounterCommand>().Set(Ref(evt), type, Ref(resultHandler), hostReserved);
+ QueueCommand();
+
+ if (type == CounterType.SamplesPassed)
+ {
+ _lastSampleCounterClear = false;
+ }
+
+ return evt;
+ }
+
+ public void ResetCounter(CounterType type)
+ {
+ New<ResetCounterCommand>().Set(type);
+ QueueCommand();
+ _lastSampleCounterClear = true;
+ }
+
+ public void Screenshot()
+ {
+ _baseRenderer.Screenshot();
+ }
+
+ public void SetBufferData(BufferHandle buffer, int offset, ReadOnlySpan<byte> data)
+ {
+ New<BufferSetDataCommand>().Set(buffer, offset, CopySpan(data));
+ QueueCommand();
+ }
+
+ public void UpdateCounters()
+ {
+ New<UpdateCountersCommand>();
+ QueueCommand();
+ }
+
+ public void WaitSync(ulong id)
+ {
+ Sync.WaitSyncAvailability(id);
+
+ _baseRenderer.WaitSync(id);
+ }
+
+ private void Interrupt(Action action)
+ {
+ // Interrupt the backend thread from any external thread and invoke the given action.
+
+ if (Thread.CurrentThread == _backendThread)
+ {
+ // If this is called from the backend thread, the action can run immediately.
+ action();
+ }
+ else
+ {
+ while (Interlocked.CompareExchange(ref _interruptAction, action, null) != null) { }
+
+ _galWorkAvailable.Set();
+
+ _interruptRun.WaitOne();
+ }
+ }
+
+ public void SetInterruptAction(Action<Action> interruptAction)
+ {
+ // Threaded renderer ignores given interrupt action, as it provides its own to the child renderer.
+ }
+
+ public void Dispose()
+ {
+ // Dispose must happen from the render thread, after all commands have completed.
+
+ // Stop the GPU thread.
+ _disposed = true;
+
+ if (_gpuThread != null && _gpuThread.IsAlive)
+ {
+ _gpuThread.Join();
+ }
+
+ // Dispose the renderer.
+ _baseRenderer.Dispose();
+
+ // Dispose events.
+ _frameComplete.Dispose();
+ _galWorkAvailable.Dispose();
+ _invokeRun.Dispose();
+ _interruptRun.Dispose();
+
+ Sync.Dispose();
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedWindow.cs b/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedWindow.cs
new file mode 100644
index 00000000..a647d37e
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedWindow.cs
@@ -0,0 +1,42 @@
+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 IRenderer _impl;
+
+ public ThreadedWindow(ThreadedRenderer renderer, IRenderer impl)
+ {
+ _renderer = renderer;
+ _impl = impl;
+ }
+
+ 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<WindowPresentCommand>().Set(new TableRef<ThreadedTexture>(_renderer, texture as ThreadedTexture), crop, new TableRef<Action>(_renderer, swapBuffersCallback));
+ _renderer.QueueCommand();
+ }
+
+ public void SetSize(int width, int height)
+ {
+ _impl.Window.SetSize(width, height);
+ }
+
+ public void ChangeVSyncMode(bool vsyncEnabled) { }
+
+ public void SetAntiAliasing(AntiAliasing effect) { }
+
+ public void SetScalingFilter(ScalingFilter type) { }
+
+ public void SetScalingFilterLevel(float level) { }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/Origin.cs b/src/Ryujinx.Graphics.GAL/Origin.cs
new file mode 100644
index 00000000..d1b69cfd
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Origin.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.Graphics.GAL
+{
+ public enum Origin
+ {
+ UpperLeft,
+ LowerLeft
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/PinnedSpan.cs b/src/Ryujinx.Graphics.GAL/PinnedSpan.cs
new file mode 100644
index 00000000..275b3b86
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/PinnedSpan.cs
@@ -0,0 +1,53 @@
+using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Graphics.GAL
+{
+ public unsafe struct PinnedSpan<T> : IDisposable where T : unmanaged
+ {
+ private void* _ptr;
+ private int _size;
+ private Action _disposeAction;
+
+ /// <summary>
+ /// Creates a new PinnedSpan from an existing ReadOnlySpan. The span *must* be pinned in memory.
+ /// The data must be guaranteed to live until disposeAction is called.
+ /// </summary>
+ /// <param name="span">Existing span</param>
+ /// <param name="disposeAction">Action to call on dispose</param>
+ /// <remarks>
+ /// If a dispose action is not provided, it is safe to assume the resource will be available until the next call.
+ /// </remarks>
+ public static PinnedSpan<T> UnsafeFromSpan(ReadOnlySpan<T> span, Action disposeAction = null)
+ {
+ return new PinnedSpan<T>(Unsafe.AsPointer(ref MemoryMarshal.GetReference(span)), span.Length, disposeAction);
+ }
+
+ /// <summary>
+ /// Creates a new PinnedSpan from an existing unsafe region. The data must be guaranteed to live until disposeAction is called.
+ /// </summary>
+ /// <param name="ptr">Pointer to the region</param>
+ /// <param name="size">The total items of T the region contains</param>
+ /// <param name="disposeAction">Action to call on dispose</param>
+ /// <remarks>
+ /// If a dispose action is not provided, it is safe to assume the resource will be available until the next call.
+ /// </remarks>
+ public PinnedSpan(void* ptr, int size, Action disposeAction = null)
+ {
+ _ptr = ptr;
+ _size = size;
+ _disposeAction = disposeAction;
+ }
+
+ public ReadOnlySpan<T> Get()
+ {
+ return new ReadOnlySpan<T>(_ptr, _size * Unsafe.SizeOf<T>());
+ }
+
+ public void Dispose()
+ {
+ _disposeAction?.Invoke();
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/PolygonMode.cs b/src/Ryujinx.Graphics.GAL/PolygonMode.cs
new file mode 100644
index 00000000..d6110c1b
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/PolygonMode.cs
@@ -0,0 +1,9 @@
+namespace Ryujinx.Graphics.GAL
+{
+ public enum PolygonMode
+ {
+ Point = 0x1b00,
+ Line = 0x1b01,
+ Fill = 0x1b02
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/PolygonModeMask.cs b/src/Ryujinx.Graphics.GAL/PolygonModeMask.cs
new file mode 100644
index 00000000..514b4231
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/PolygonModeMask.cs
@@ -0,0 +1,12 @@
+using System;
+
+namespace Ryujinx.Graphics.GAL
+{
+ [Flags]
+ public enum PolygonModeMask
+ {
+ Point = 1 << 0,
+ Line = 1 << 1,
+ Fill = 1 << 2
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/PrimitiveTopology.cs b/src/Ryujinx.Graphics.GAL/PrimitiveTopology.cs
new file mode 100644
index 00000000..ed567a68
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/PrimitiveTopology.cs
@@ -0,0 +1,21 @@
+namespace Ryujinx.Graphics.GAL
+{
+ public enum PrimitiveTopology
+ {
+ Points,
+ Lines,
+ LineLoop,
+ LineStrip,
+ Triangles,
+ TriangleStrip,
+ TriangleFan,
+ Quads,
+ QuadStrip,
+ Polygon,
+ LinesAdjacency,
+ LineStripAdjacency,
+ TrianglesAdjacency,
+ TriangleStripAdjacency,
+ Patches
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/ProgramLinkStatus.cs b/src/Ryujinx.Graphics.GAL/ProgramLinkStatus.cs
new file mode 100644
index 00000000..5ca1be8c
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/ProgramLinkStatus.cs
@@ -0,0 +1,9 @@
+namespace Ryujinx.Graphics.GAL
+{
+ public enum ProgramLinkStatus
+ {
+ Incomplete,
+ Success,
+ Failure
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/ProgramPipelineState.cs b/src/Ryujinx.Graphics.GAL/ProgramPipelineState.cs
new file mode 100644
index 00000000..41afb34b
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/ProgramPipelineState.cs
@@ -0,0 +1,78 @@
+using Ryujinx.Common.Memory;
+using System;
+
+namespace Ryujinx.Graphics.GAL
+{
+ /// <summary>
+ /// Descriptor for a pipeline buffer binding.
+ /// </summary>
+ public readonly struct BufferPipelineDescriptor
+ {
+ public bool Enable { get; }
+ public int Stride { get; }
+ public int Divisor { get; }
+
+ public BufferPipelineDescriptor(bool enable, int stride, int divisor)
+ {
+ Enable = enable;
+ Stride = stride;
+ Divisor = divisor;
+ }
+ }
+
+ /// <summary>
+ /// State required for a program to compile shaders.
+ /// </summary>
+ public struct ProgramPipelineState
+ {
+ // Some state is considered always dynamic and should not be included:
+ // - Viewports/Scissors
+ // - Bias values (not enable)
+
+ public int SamplesCount;
+ public Array8<bool> AttachmentEnable;
+ public Array8<Format> AttachmentFormats;
+ public bool DepthStencilEnable;
+ public Format DepthStencilFormat;
+
+ public bool LogicOpEnable;
+ public LogicalOp LogicOp;
+ public Array8<BlendDescriptor> BlendDescriptors;
+ public Array8<uint> ColorWriteMask;
+
+ public int VertexAttribCount;
+ public Array32<VertexAttribDescriptor> VertexAttribs;
+
+ public int VertexBufferCount;
+ public Array32<BufferPipelineDescriptor> VertexBuffers;
+
+ // TODO: Min/max depth bounds.
+ public DepthTestDescriptor DepthTest;
+ public StencilTestDescriptor StencilTest;
+ public FrontFace FrontFace;
+ public Face CullMode;
+ public bool CullEnable;
+
+ public PolygonModeMask BiasEnable;
+
+ public float LineWidth;
+ // TODO: Polygon mode.
+ public bool DepthClampEnable;
+ public bool RasterizerDiscard;
+ public PrimitiveTopology Topology;
+ public bool PrimitiveRestartEnable;
+ public uint PatchControlPoints;
+
+ public void SetVertexAttribs(ReadOnlySpan<VertexAttribDescriptor> vertexAttribs)
+ {
+ VertexAttribCount = vertexAttribs.Length;
+ vertexAttribs.CopyTo(VertexAttribs.AsSpan());
+ }
+
+ public void SetLogicOpState(bool enable, LogicalOp op)
+ {
+ LogicOp = op;
+ LogicOpEnable = enable;
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/Rectangle.cs b/src/Ryujinx.Graphics.GAL/Rectangle.cs
new file mode 100644
index 00000000..c8fa93d9
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Rectangle.cs
@@ -0,0 +1,18 @@
+namespace Ryujinx.Graphics.GAL
+{
+ public readonly struct Rectangle<T> where T : unmanaged
+ {
+ public T X { get; }
+ public T Y { get; }
+ public T Width { get; }
+ public T Height { get; }
+
+ public Rectangle(T x, T y, T width, T height)
+ {
+ X = x;
+ Y = y;
+ Width = width;
+ Height = height;
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/Ryujinx.Graphics.GAL.csproj b/src/Ryujinx.Graphics.GAL/Ryujinx.Graphics.GAL.csproj
new file mode 100644
index 00000000..189108a3
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Ryujinx.Graphics.GAL.csproj
@@ -0,0 +1,20 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <TargetFramework>net7.0</TargetFramework>
+ </PropertyGroup>
+
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
+ <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+ </PropertyGroup>
+
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
+ <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\Ryujinx.Graphics.Shader\Ryujinx.Graphics.Shader.csproj" />
+ <ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
+ </ItemGroup>
+
+</Project>
diff --git a/src/Ryujinx.Graphics.GAL/SamplerCreateInfo.cs b/src/Ryujinx.Graphics.GAL/SamplerCreateInfo.cs
new file mode 100644
index 00000000..990c302e
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/SamplerCreateInfo.cs
@@ -0,0 +1,72 @@
+namespace Ryujinx.Graphics.GAL
+{
+ public readonly struct SamplerCreateInfo
+ {
+ public MinFilter MinFilter { get; }
+ public MagFilter MagFilter { get; }
+
+ public bool SeamlessCubemap { get; }
+
+ public AddressMode AddressU { get; }
+ public AddressMode AddressV { get; }
+ public AddressMode AddressP { get; }
+
+ public CompareMode CompareMode { get; }
+ public CompareOp CompareOp { get; }
+
+ public ColorF BorderColor { get; }
+
+ public float MinLod { get; }
+ public float MaxLod { get; }
+ public float MipLodBias { get; }
+ public float MaxAnisotropy { get; }
+
+ public SamplerCreateInfo(
+ MinFilter minFilter,
+ MagFilter magFilter,
+ bool seamlessCubemap,
+ AddressMode addressU,
+ AddressMode addressV,
+ AddressMode addressP,
+ CompareMode compareMode,
+ CompareOp compareOp,
+ ColorF borderColor,
+ float minLod,
+ float maxLod,
+ float mipLodBias,
+ float maxAnisotropy)
+ {
+ MinFilter = minFilter;
+ MagFilter = magFilter;
+ SeamlessCubemap = seamlessCubemap;
+ AddressU = addressU;
+ AddressV = addressV;
+ AddressP = addressP;
+ CompareMode = compareMode;
+ CompareOp = compareOp;
+ BorderColor = borderColor;
+ MinLod = minLod;
+ MaxLod = maxLod;
+ MipLodBias = mipLodBias;
+ MaxAnisotropy = maxAnisotropy;
+ }
+
+ public static SamplerCreateInfo Create(MinFilter minFilter, MagFilter magFilter)
+ {
+ return new SamplerCreateInfo(
+ minFilter,
+ magFilter,
+ false,
+ AddressMode.ClampToEdge,
+ AddressMode.ClampToEdge,
+ AddressMode.ClampToEdge,
+ CompareMode.None,
+ CompareOp.Always,
+ new ColorF(0f, 0f, 0f, 0f),
+ 0f,
+ 0f,
+ 0f,
+ 1f);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.Graphics.GAL/ScreenCaptureImageInfo.cs b/src/Ryujinx.Graphics.GAL/ScreenCaptureImageInfo.cs
new file mode 100644
index 00000000..129913ec
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/ScreenCaptureImageInfo.cs
@@ -0,0 +1,22 @@
+namespace Ryujinx.Graphics.GAL
+{
+ public readonly struct ScreenCaptureImageInfo
+ {
+ public ScreenCaptureImageInfo(int width, int height, bool isBgra, byte[] data, bool flipX, bool flipY)
+ {
+ Width = width;
+ Height = height;
+ IsBgra = isBgra;
+ Data = data;
+ FlipX = flipX;
+ FlipY = flipY;
+ }
+
+ public int Width { get; }
+ public int Height { get; }
+ public byte[] Data { get; }
+ public bool IsBgra { get; }
+ public bool FlipX { get; }
+ public bool FlipY { get; }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.Graphics.GAL/ShaderBindings.cs b/src/Ryujinx.Graphics.GAL/ShaderBindings.cs
new file mode 100644
index 00000000..6ab29382
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/ShaderBindings.cs
@@ -0,0 +1,24 @@
+using System.Collections.Generic;
+
+namespace Ryujinx.Graphics.GAL
+{
+ public readonly struct ShaderBindings
+ {
+ public IReadOnlyCollection<int> UniformBufferBindings { get; }
+ public IReadOnlyCollection<int> StorageBufferBindings { get; }
+ public IReadOnlyCollection<int> TextureBindings { get; }
+ public IReadOnlyCollection<int> ImageBindings { get; }
+
+ public ShaderBindings(
+ IReadOnlyCollection<int> uniformBufferBindings,
+ IReadOnlyCollection<int> storageBufferBindings,
+ IReadOnlyCollection<int> textureBindings,
+ IReadOnlyCollection<int> imageBindings)
+ {
+ UniformBufferBindings = uniformBufferBindings;
+ StorageBufferBindings = storageBufferBindings;
+ TextureBindings = textureBindings;
+ ImageBindings = imageBindings;
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/ShaderInfo.cs b/src/Ryujinx.Graphics.GAL/ShaderInfo.cs
new file mode 100644
index 00000000..b4c87117
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/ShaderInfo.cs
@@ -0,0 +1,23 @@
+namespace Ryujinx.Graphics.GAL
+{
+ public struct ShaderInfo
+ {
+ public int FragmentOutputMap { get; }
+ public ProgramPipelineState? State { get; }
+ public bool FromCache { get; set; }
+
+ public ShaderInfo(int fragmentOutputMap, ProgramPipelineState state, bool fromCache = false)
+ {
+ FragmentOutputMap = fragmentOutputMap;
+ State = state;
+ FromCache = fromCache;
+ }
+
+ public ShaderInfo(int fragmentOutputMap, bool fromCache = false)
+ {
+ FragmentOutputMap = fragmentOutputMap;
+ State = null;
+ FromCache = fromCache;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.Graphics.GAL/ShaderSource.cs b/src/Ryujinx.Graphics.GAL/ShaderSource.cs
new file mode 100644
index 00000000..91d3a632
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/ShaderSource.cs
@@ -0,0 +1,31 @@
+using Ryujinx.Graphics.Shader;
+using Ryujinx.Graphics.Shader.Translation;
+
+namespace Ryujinx.Graphics.GAL
+{
+ public readonly struct ShaderSource
+ {
+ public string Code { get; }
+ public byte[] BinaryCode { get; }
+ public ShaderBindings Bindings { get; }
+ public ShaderStage Stage { get; }
+ public TargetLanguage Language { get; }
+
+ public ShaderSource(string code, byte[] binaryCode, ShaderBindings bindings, ShaderStage stage, TargetLanguage language)
+ {
+ Code = code;
+ BinaryCode = binaryCode;
+ Bindings = bindings;
+ Stage = stage;
+ Language = language;
+ }
+
+ public ShaderSource(string code, ShaderBindings bindings, ShaderStage stage, TargetLanguage language) : this(code, null, bindings, stage, language)
+ {
+ }
+
+ public ShaderSource(byte[] binaryCode, ShaderBindings bindings, ShaderStage stage, TargetLanguage language) : this(null, binaryCode, bindings, stage, language)
+ {
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.Graphics.GAL/StencilOp.cs b/src/Ryujinx.Graphics.GAL/StencilOp.cs
new file mode 100644
index 00000000..fe999b0f
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/StencilOp.cs
@@ -0,0 +1,23 @@
+namespace Ryujinx.Graphics.GAL
+{
+ public enum StencilOp
+ {
+ Keep = 1,
+ Zero,
+ Replace,
+ IncrementAndClamp,
+ DecrementAndClamp,
+ Invert,
+ IncrementAndWrap,
+ DecrementAndWrap,
+
+ ZeroGl = 0x0,
+ InvertGl = 0x150a,
+ KeepGl = 0x1e00,
+ ReplaceGl = 0x1e01,
+ IncrementAndClampGl = 0x1e02,
+ DecrementAndClampGl = 0x1e03,
+ IncrementAndWrapGl = 0x8507,
+ DecrementAndWrapGl = 0x8508
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.Graphics.GAL/StencilTestDescriptor.cs b/src/Ryujinx.Graphics.GAL/StencilTestDescriptor.cs
new file mode 100644
index 00000000..db46c957
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/StencilTestDescriptor.cs
@@ -0,0 +1,56 @@
+namespace Ryujinx.Graphics.GAL
+{
+ public readonly struct StencilTestDescriptor
+ {
+ public bool TestEnable { get; }
+
+ public CompareOp FrontFunc { get; }
+ public StencilOp FrontSFail { get; }
+ public StencilOp FrontDpPass { get; }
+ public StencilOp FrontDpFail { get; }
+ public int FrontFuncRef { get; }
+ public int FrontFuncMask { get; }
+ public int FrontMask { get; }
+ public CompareOp BackFunc { get; }
+ public StencilOp BackSFail { get; }
+ public StencilOp BackDpPass { get; }
+ public StencilOp BackDpFail { get; }
+ public int BackFuncRef { get; }
+ public int BackFuncMask { get; }
+ public int BackMask { get; }
+
+ public StencilTestDescriptor(
+ bool testEnable,
+ CompareOp frontFunc,
+ StencilOp frontSFail,
+ StencilOp frontDpPass,
+ StencilOp frontDpFail,
+ int frontFuncRef,
+ int frontFuncMask,
+ int frontMask,
+ CompareOp backFunc,
+ StencilOp backSFail,
+ StencilOp backDpPass,
+ StencilOp backDpFail,
+ int backFuncRef,
+ int backFuncMask,
+ int backMask)
+ {
+ TestEnable = testEnable;
+ FrontFunc = frontFunc;
+ FrontSFail = frontSFail;
+ FrontDpPass = frontDpPass;
+ FrontDpFail = frontDpFail;
+ FrontFuncRef = frontFuncRef;
+ FrontFuncMask = frontFuncMask;
+ FrontMask = frontMask;
+ BackFunc = backFunc;
+ BackSFail = backSFail;
+ BackDpPass = backDpPass;
+ BackDpFail = backDpFail;
+ BackFuncRef = backFuncRef;
+ BackFuncMask = backFuncMask;
+ BackMask = backMask;
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/SupportBufferUpdater.cs b/src/Ryujinx.Graphics.GAL/SupportBufferUpdater.cs
new file mode 100644
index 00000000..6eeddb6c
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/SupportBufferUpdater.cs
@@ -0,0 +1,101 @@
+using Ryujinx.Graphics.Shader;
+using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Graphics.GAL
+{
+ public class SupportBufferUpdater : IDisposable
+ {
+ public SupportBuffer Data;
+ public BufferHandle Handle;
+
+ private IRenderer _renderer;
+ private int _startOffset = -1;
+ private int _endOffset = -1;
+
+ public SupportBufferUpdater(IRenderer renderer)
+ {
+ _renderer = renderer;
+ Handle = renderer.CreateBuffer(SupportBuffer.RequiredSize);
+ renderer.Pipeline.ClearBuffer(Handle, 0, SupportBuffer.RequiredSize, 0);
+ }
+
+ private void MarkDirty(int startOffset, int byteSize)
+ {
+ int endOffset = startOffset + byteSize;
+
+ if (_startOffset == -1)
+ {
+ _startOffset = startOffset;
+ _endOffset = endOffset;
+ }
+ else
+ {
+ if (startOffset < _startOffset)
+ {
+ _startOffset = startOffset;
+ }
+
+ if (endOffset > _endOffset)
+ {
+ _endOffset = endOffset;
+ }
+ }
+ }
+
+ public void UpdateFragmentRenderScaleCount(int count)
+ {
+ if (Data.FragmentRenderScaleCount.X != count)
+ {
+ Data.FragmentRenderScaleCount.X = count;
+
+ MarkDirty(SupportBuffer.FragmentRenderScaleCountOffset, sizeof(int));
+ }
+ }
+
+ private void UpdateGenericField<T>(int baseOffset, ReadOnlySpan<T> data, Span<T> target, int offset, int count) where T : unmanaged
+ {
+ data.Slice(0, count).CopyTo(target.Slice(offset));
+
+ int elemSize = Unsafe.SizeOf<T>();
+
+ MarkDirty(baseOffset + offset * elemSize, count * elemSize);
+ }
+
+ public void UpdateRenderScale(ReadOnlySpan<Vector4<float>> data, int offset, int count)
+ {
+ UpdateGenericField(SupportBuffer.GraphicsRenderScaleOffset, data, Data.RenderScale.AsSpan(), offset, count);
+ }
+
+ public void UpdateFragmentIsBgra(ReadOnlySpan<Vector4<int>> data, int offset, int count)
+ {
+ UpdateGenericField(SupportBuffer.FragmentIsBgraOffset, data, Data.FragmentIsBgra.AsSpan(), offset, count);
+ }
+
+ public void UpdateViewportInverse(Vector4<float> data)
+ {
+ Data.ViewportInverse = data;
+
+ MarkDirty(SupportBuffer.ViewportInverseOffset, SupportBuffer.FieldSize);
+ }
+
+ public void Commit()
+ {
+ if (_startOffset != -1)
+ {
+ ReadOnlySpan<byte> data = MemoryMarshal.Cast<SupportBuffer, byte>(MemoryMarshal.CreateSpan(ref Data, 1));
+
+ _renderer.SetBufferData(Handle, _startOffset, data.Slice(_startOffset, _endOffset - _startOffset));
+
+ _startOffset = -1;
+ _endOffset = -1;
+ }
+ }
+
+ public void Dispose()
+ {
+ _renderer.DeleteBuffer(Handle);
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/SwizzleComponent.cs b/src/Ryujinx.Graphics.GAL/SwizzleComponent.cs
new file mode 100644
index 00000000..a405bd13
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/SwizzleComponent.cs
@@ -0,0 +1,12 @@
+namespace Ryujinx.Graphics.GAL
+{
+ public enum SwizzleComponent
+ {
+ Zero,
+ One,
+ Red,
+ Green,
+ Blue,
+ Alpha
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.Graphics.GAL/Target.cs b/src/Ryujinx.Graphics.GAL/Target.cs
new file mode 100644
index 00000000..711eea24
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Target.cs
@@ -0,0 +1,34 @@
+namespace Ryujinx.Graphics.GAL
+{
+ public enum Target
+ {
+ Texture1D,
+ Texture2D,
+ Texture3D,
+ Texture1DArray,
+ Texture2DArray,
+ Texture2DMultisample,
+ Texture2DMultisampleArray,
+ Cubemap,
+ CubemapArray,
+ TextureBuffer
+ }
+
+ public static class TargetExtensions
+ {
+ public static bool IsMultisample(this Target target)
+ {
+ return target == Target.Texture2DMultisample || target == Target.Texture2DMultisampleArray;
+ }
+
+ public static bool HasDepthOrLayers(this Target target)
+ {
+ return target == Target.Texture3D ||
+ target == Target.Texture1DArray ||
+ target == Target.Texture2DArray ||
+ target == Target.Texture2DMultisampleArray ||
+ target == Target.Cubemap ||
+ target == Target.CubemapArray;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.Graphics.GAL/TextureCreateInfo.cs b/src/Ryujinx.Graphics.GAL/TextureCreateInfo.cs
new file mode 100644
index 00000000..52b3b11f
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/TextureCreateInfo.cs
@@ -0,0 +1,164 @@
+using Ryujinx.Common;
+using System;
+using System.Numerics;
+
+namespace Ryujinx.Graphics.GAL
+{
+ public readonly struct TextureCreateInfo : IEquatable<TextureCreateInfo>
+ {
+ public int Width { get; }
+ public int Height { get; }
+ public int Depth { get; }
+ public int Levels { get; }
+ public int Samples { get; }
+ public int BlockWidth { get; }
+ public int BlockHeight { get; }
+ public int BytesPerPixel { get; }
+
+ public bool IsCompressed => (BlockWidth | BlockHeight) != 1;
+
+ public Format Format { get; }
+
+ public DepthStencilMode DepthStencilMode { get; }
+
+ public Target Target { get; }
+
+ public SwizzleComponent SwizzleR { get; }
+ public SwizzleComponent SwizzleG { get; }
+ public SwizzleComponent SwizzleB { get; }
+ public SwizzleComponent SwizzleA { get; }
+
+ public TextureCreateInfo(
+ int width,
+ int height,
+ int depth,
+ int levels,
+ int samples,
+ int blockWidth,
+ int blockHeight,
+ int bytesPerPixel,
+ Format format,
+ DepthStencilMode depthStencilMode,
+ Target target,
+ SwizzleComponent swizzleR,
+ SwizzleComponent swizzleG,
+ SwizzleComponent swizzleB,
+ SwizzleComponent swizzleA)
+ {
+ Width = width;
+ Height = height;
+ Depth = depth;
+ Levels = levels;
+ Samples = samples;
+ BlockWidth = blockWidth;
+ BlockHeight = blockHeight;
+ BytesPerPixel = bytesPerPixel;
+ Format = format;
+ DepthStencilMode = depthStencilMode;
+ Target = target;
+ SwizzleR = swizzleR;
+ SwizzleG = swizzleG;
+ SwizzleB = swizzleB;
+ SwizzleA = swizzleA;
+ }
+
+ public int GetMipSize(int level)
+ {
+ return GetMipStride(level) * GetLevelHeight(level) * GetLevelDepth(level);
+ }
+
+ public int GetMipSize2D(int level)
+ {
+ return GetMipStride(level) * GetLevelHeight(level);
+ }
+
+ public int GetMipStride(int level)
+ {
+ return BitUtils.AlignUp(GetLevelWidth(level) * BytesPerPixel, 4);
+ }
+
+ private int GetLevelWidth(int level)
+ {
+ return BitUtils.DivRoundUp(GetLevelSize(Width, level), BlockWidth);
+ }
+
+ private int GetLevelHeight(int level)
+ {
+ return BitUtils.DivRoundUp(GetLevelSize(Height, level), BlockHeight);
+ }
+
+ private int GetLevelDepth(int level)
+ {
+ return Target == Target.Texture3D ? GetLevelSize(Depth, level) : GetLayers();
+ }
+
+ public int GetDepthOrLayers()
+ {
+ return Target == Target.Texture3D ? Depth : GetLayers();
+ }
+
+ public int GetLayers()
+ {
+ if (Target == Target.Texture2DArray ||
+ Target == Target.Texture2DMultisampleArray ||
+ Target == Target.CubemapArray)
+ {
+ return Depth;
+ }
+ else if (Target == Target.Cubemap)
+ {
+ return 6;
+ }
+
+ return 1;
+ }
+
+ public int GetLevelsClamped()
+ {
+ int maxSize = Width;
+
+ if (Target != Target.Texture1D &&
+ Target != Target.Texture1DArray)
+ {
+ maxSize = Math.Max(maxSize, Height);
+ }
+
+ if (Target == Target.Texture3D)
+ {
+ maxSize = Math.Max(maxSize, Depth);
+ }
+
+ int maxLevels = BitOperations.Log2((uint)maxSize) + 1;
+ return Math.Min(Levels, maxLevels);
+ }
+
+ private static int GetLevelSize(int size, int level)
+ {
+ return Math.Max(1, size >> level);
+ }
+
+ public override int GetHashCode()
+ {
+ return HashCode.Combine(Width, Height);
+ }
+
+ bool IEquatable<TextureCreateInfo>.Equals(TextureCreateInfo other)
+ {
+ return Width == other.Width &&
+ Height == other.Height &&
+ Depth == other.Depth &&
+ Levels == other.Levels &&
+ Samples == other.Samples &&
+ BlockWidth == other.BlockWidth &&
+ BlockHeight == other.BlockHeight &&
+ BytesPerPixel == other.BytesPerPixel &&
+ Format == other.Format &&
+ DepthStencilMode == other.DepthStencilMode &&
+ Target == other.Target &&
+ SwizzleR == other.SwizzleR &&
+ SwizzleG == other.SwizzleG &&
+ SwizzleB == other.SwizzleB &&
+ SwizzleA == other.SwizzleA;
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/TextureReleaseCallback.cs b/src/Ryujinx.Graphics.GAL/TextureReleaseCallback.cs
new file mode 100644
index 00000000..c058df2b
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/TextureReleaseCallback.cs
@@ -0,0 +1,4 @@
+namespace Ryujinx.Graphics.GAL
+{
+ public delegate void TextureReleaseCallback(object context);
+}
diff --git a/src/Ryujinx.Graphics.GAL/UpscaleType.cs b/src/Ryujinx.Graphics.GAL/UpscaleType.cs
new file mode 100644
index 00000000..442b65f2
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/UpscaleType.cs
@@ -0,0 +1,9 @@
+namespace Ryujinx.Graphics.GAL
+{
+ public enum ScalingFilter
+ {
+ Bilinear,
+ Nearest,
+ Fsr
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.Graphics.GAL/VertexAttribDescriptor.cs b/src/Ryujinx.Graphics.GAL/VertexAttribDescriptor.cs
new file mode 100644
index 00000000..4f5ea6a6
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/VertexAttribDescriptor.cs
@@ -0,0 +1,4 @@
+namespace Ryujinx.Graphics.GAL
+{
+ public readonly record struct VertexAttribDescriptor(int BufferIndex, int Offset, bool IsZero, Format Format);
+}
diff --git a/src/Ryujinx.Graphics.GAL/VertexBufferDescriptor.cs b/src/Ryujinx.Graphics.GAL/VertexBufferDescriptor.cs
new file mode 100644
index 00000000..15f0dff8
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/VertexBufferDescriptor.cs
@@ -0,0 +1,17 @@
+namespace Ryujinx.Graphics.GAL
+{
+ public readonly struct VertexBufferDescriptor
+ {
+ public BufferRange Buffer { get; }
+
+ public int Stride { get; }
+ public int Divisor { get; }
+
+ public VertexBufferDescriptor(BufferRange buffer, int stride, int divisor)
+ {
+ Buffer = buffer;
+ Stride = stride;
+ Divisor = divisor;
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/Viewport.cs b/src/Ryujinx.Graphics.GAL/Viewport.cs
new file mode 100644
index 00000000..94012c00
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Viewport.cs
@@ -0,0 +1,33 @@
+namespace Ryujinx.Graphics.GAL
+{
+ public readonly struct Viewport
+ {
+ public Rectangle<float> Region { get; }
+
+ public ViewportSwizzle SwizzleX { get; }
+ public ViewportSwizzle SwizzleY { get; }
+ public ViewportSwizzle SwizzleZ { get; }
+ public ViewportSwizzle SwizzleW { get; }
+
+ public float DepthNear { get; }
+ public float DepthFar { get; }
+
+ public Viewport(
+ Rectangle<float> region,
+ ViewportSwizzle swizzleX,
+ ViewportSwizzle swizzleY,
+ ViewportSwizzle swizzleZ,
+ ViewportSwizzle swizzleW,
+ float depthNear,
+ float depthFar)
+ {
+ Region = region;
+ SwizzleX = swizzleX;
+ SwizzleY = swizzleY;
+ SwizzleZ = swizzleZ;
+ SwizzleW = swizzleW;
+ DepthNear = depthNear;
+ DepthFar = depthFar;
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/ViewportSwizzle.cs b/src/Ryujinx.Graphics.GAL/ViewportSwizzle.cs
new file mode 100644
index 00000000..c24a2246
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/ViewportSwizzle.cs
@@ -0,0 +1,16 @@
+namespace Ryujinx.Graphics.GAL
+{
+ public enum ViewportSwizzle
+ {
+ PositiveX = 0,
+ NegativeX = 1,
+ PositiveY = 2,
+ NegativeY = 3,
+ PositiveZ = 4,
+ NegativeZ = 5,
+ PositiveW = 6,
+ NegativeW = 7,
+
+ NegativeFlag = 1
+ }
+}