mirror of
https://github.com/Ryujinx/Ryujinx.git
synced 2024-08-19 21:52:22 +00:00
Geometry shader emulation for macOS (#5551)
* Implement vertex and geometry shader conversion to compute * Call InitializeReservedCounts for compute too * PR feedback * Set clip distance mask for geometry and tessellation shaders too * Transform feedback emulation only for vertex
This commit is contained in:
parent
93d78f9ac4
commit
f09bba82b9
|
@ -39,6 +39,7 @@ namespace Ryujinx.Graphics.GAL
|
||||||
public readonly bool SupportsShaderBarrierDivergence;
|
public readonly bool SupportsShaderBarrierDivergence;
|
||||||
public readonly bool SupportsShaderFloat64;
|
public readonly bool SupportsShaderFloat64;
|
||||||
public readonly bool SupportsTextureShadowLod;
|
public readonly bool SupportsTextureShadowLod;
|
||||||
|
public readonly bool SupportsVertexStoreAndAtomics;
|
||||||
public readonly bool SupportsViewportIndexVertexTessellation;
|
public readonly bool SupportsViewportIndexVertexTessellation;
|
||||||
public readonly bool SupportsViewportMask;
|
public readonly bool SupportsViewportMask;
|
||||||
public readonly bool SupportsViewportSwizzle;
|
public readonly bool SupportsViewportSwizzle;
|
||||||
|
@ -54,6 +55,7 @@ namespace Ryujinx.Graphics.GAL
|
||||||
public readonly float MaximumSupportedAnisotropy;
|
public readonly float MaximumSupportedAnisotropy;
|
||||||
public readonly int ShaderSubgroupSize;
|
public readonly int ShaderSubgroupSize;
|
||||||
public readonly int StorageBufferOffsetAlignment;
|
public readonly int StorageBufferOffsetAlignment;
|
||||||
|
public readonly int TextureBufferOffsetAlignment;
|
||||||
|
|
||||||
public readonly int GatherBiasPrecision;
|
public readonly int GatherBiasPrecision;
|
||||||
|
|
||||||
|
@ -91,6 +93,7 @@ namespace Ryujinx.Graphics.GAL
|
||||||
bool supportsShaderBarrierDivergence,
|
bool supportsShaderBarrierDivergence,
|
||||||
bool supportsShaderFloat64,
|
bool supportsShaderFloat64,
|
||||||
bool supportsTextureShadowLod,
|
bool supportsTextureShadowLod,
|
||||||
|
bool supportsVertexStoreAndAtomics,
|
||||||
bool supportsViewportIndexVertexTessellation,
|
bool supportsViewportIndexVertexTessellation,
|
||||||
bool supportsViewportMask,
|
bool supportsViewportMask,
|
||||||
bool supportsViewportSwizzle,
|
bool supportsViewportSwizzle,
|
||||||
|
@ -104,6 +107,7 @@ namespace Ryujinx.Graphics.GAL
|
||||||
float maximumSupportedAnisotropy,
|
float maximumSupportedAnisotropy,
|
||||||
int shaderSubgroupSize,
|
int shaderSubgroupSize,
|
||||||
int storageBufferOffsetAlignment,
|
int storageBufferOffsetAlignment,
|
||||||
|
int textureBufferOffsetAlignment,
|
||||||
int gatherBiasPrecision)
|
int gatherBiasPrecision)
|
||||||
{
|
{
|
||||||
Api = api;
|
Api = api;
|
||||||
|
@ -139,6 +143,7 @@ namespace Ryujinx.Graphics.GAL
|
||||||
SupportsShaderBarrierDivergence = supportsShaderBarrierDivergence;
|
SupportsShaderBarrierDivergence = supportsShaderBarrierDivergence;
|
||||||
SupportsShaderFloat64 = supportsShaderFloat64;
|
SupportsShaderFloat64 = supportsShaderFloat64;
|
||||||
SupportsTextureShadowLod = supportsTextureShadowLod;
|
SupportsTextureShadowLod = supportsTextureShadowLod;
|
||||||
|
SupportsVertexStoreAndAtomics = supportsVertexStoreAndAtomics;
|
||||||
SupportsViewportIndexVertexTessellation = supportsViewportIndexVertexTessellation;
|
SupportsViewportIndexVertexTessellation = supportsViewportIndexVertexTessellation;
|
||||||
SupportsViewportMask = supportsViewportMask;
|
SupportsViewportMask = supportsViewportMask;
|
||||||
SupportsViewportSwizzle = supportsViewportSwizzle;
|
SupportsViewportSwizzle = supportsViewportSwizzle;
|
||||||
|
@ -152,6 +157,7 @@ namespace Ryujinx.Graphics.GAL
|
||||||
MaximumSupportedAnisotropy = maximumSupportedAnisotropy;
|
MaximumSupportedAnisotropy = maximumSupportedAnisotropy;
|
||||||
ShaderSubgroupSize = shaderSubgroupSize;
|
ShaderSubgroupSize = shaderSubgroupSize;
|
||||||
StorageBufferOffsetAlignment = storageBufferOffsetAlignment;
|
StorageBufferOffsetAlignment = storageBufferOffsetAlignment;
|
||||||
|
TextureBufferOffsetAlignment = textureBufferOffsetAlignment;
|
||||||
GatherBiasPrecision = gatherBiasPrecision;
|
GatherBiasPrecision = gatherBiasPrecision;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.GPFifo
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a GPU General Purpose FIFO command processor.
|
/// Represents a GPU General Purpose FIFO command processor.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
class GPFifoProcessor
|
class GPFifoProcessor : IDisposable
|
||||||
{
|
{
|
||||||
private const int MacrosCount = 0x80;
|
private const int MacrosCount = 0x80;
|
||||||
private const int MacroIndexMask = MacrosCount - 1;
|
private const int MacroIndexMask = MacrosCount - 1;
|
||||||
|
@ -327,5 +327,19 @@ namespace Ryujinx.Graphics.Gpu.Engine.GPFifo
|
||||||
{
|
{
|
||||||
_3dClass.PerformDeferredDraws();
|
_3dClass.PerformDeferredDraws();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected virtual void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
_3dClass.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,141 @@
|
||||||
|
using Ryujinx.Graphics.GAL;
|
||||||
|
using Ryujinx.Graphics.Gpu.Memory;
|
||||||
|
using Ryujinx.Graphics.Shader;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Vertex info buffer data updater.
|
||||||
|
/// </summary>
|
||||||
|
class VertexInfoBufferUpdater : BufferUpdater
|
||||||
|
{
|
||||||
|
private VertexInfoBuffer _data;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new instance of the vertex info buffer updater.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="renderer">Renderer that the vertex info buffer will be used with</param>
|
||||||
|
public VertexInfoBufferUpdater(IRenderer renderer) : base(renderer)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets vertex data related counts.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="vertexCount">Number of vertices used on the draw</param>
|
||||||
|
/// <param name="instanceCount">Number of draw instances</param>
|
||||||
|
/// <param name="firstVertex">Index of the first vertex on the vertex buffer</param>
|
||||||
|
/// <param name="firstInstance">Index of the first instanced vertex on the vertex buffer</param>
|
||||||
|
public void SetVertexCounts(int vertexCount, int instanceCount, int firstVertex, int firstInstance)
|
||||||
|
{
|
||||||
|
if (_data.VertexCounts.X != vertexCount)
|
||||||
|
{
|
||||||
|
_data.VertexCounts.X = vertexCount;
|
||||||
|
MarkDirty(VertexInfoBuffer.VertexCountsOffset, sizeof(int));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_data.VertexCounts.Y != instanceCount)
|
||||||
|
{
|
||||||
|
_data.VertexCounts.Y = instanceCount;
|
||||||
|
MarkDirty(VertexInfoBuffer.VertexCountsOffset + sizeof(int), sizeof(int));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_data.VertexCounts.Z != firstVertex)
|
||||||
|
{
|
||||||
|
_data.VertexCounts.Z = firstVertex;
|
||||||
|
MarkDirty(VertexInfoBuffer.VertexCountsOffset + sizeof(int) * 2, sizeof(int));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_data.VertexCounts.W != firstInstance)
|
||||||
|
{
|
||||||
|
_data.VertexCounts.W = firstInstance;
|
||||||
|
MarkDirty(VertexInfoBuffer.VertexCountsOffset + sizeof(int) * 3, sizeof(int));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets vertex data related counts.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="primitivesCount">Number of primitives consumed by the geometry shader</param>
|
||||||
|
public void SetGeometryCounts(int primitivesCount)
|
||||||
|
{
|
||||||
|
if (_data.GeometryCounts.X != primitivesCount)
|
||||||
|
{
|
||||||
|
_data.GeometryCounts.X = primitivesCount;
|
||||||
|
MarkDirty(VertexInfoBuffer.GeometryCountsOffset, sizeof(int));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets a vertex stride and related data.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="index">Index of the vertex stride to be updated</param>
|
||||||
|
/// <param name="stride">Stride divided by the component or format size</param>
|
||||||
|
/// <param name="componentCount">Number of components that the format has</param>
|
||||||
|
public void SetVertexStride(int index, int stride, int componentCount)
|
||||||
|
{
|
||||||
|
if (_data.VertexStrides[index].X != stride)
|
||||||
|
{
|
||||||
|
_data.VertexStrides[index].X = stride;
|
||||||
|
MarkDirty(VertexInfoBuffer.VertexStridesOffset + index * Unsafe.SizeOf<Vector4<int>>(), sizeof(int));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int c = 1; c < 4; c++)
|
||||||
|
{
|
||||||
|
int value = c < componentCount ? 1 : 0;
|
||||||
|
|
||||||
|
ref int currentValue = ref GetElementRef(ref _data.VertexStrides[index], c);
|
||||||
|
|
||||||
|
if (currentValue != value)
|
||||||
|
{
|
||||||
|
currentValue = value;
|
||||||
|
MarkDirty(VertexInfoBuffer.VertexStridesOffset + index * Unsafe.SizeOf<Vector4<int>>() + c * sizeof(int), sizeof(int));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets a vertex offset and related data.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="index">Index of the vertex offset to be updated</param>
|
||||||
|
/// <param name="offset">Offset divided by the component or format size</param>
|
||||||
|
/// <param name="divisor">If the draw is instanced, should have the vertex divisor value, otherwise should be zero</param>
|
||||||
|
public void SetVertexOffset(int index, int offset, int divisor)
|
||||||
|
{
|
||||||
|
if (_data.VertexOffsets[index].X != offset)
|
||||||
|
{
|
||||||
|
_data.VertexOffsets[index].X = offset;
|
||||||
|
MarkDirty(VertexInfoBuffer.VertexOffsetsOffset + index * Unsafe.SizeOf<Vector4<int>>(), sizeof(int));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_data.VertexOffsets[index].Y != divisor)
|
||||||
|
{
|
||||||
|
_data.VertexOffsets[index].Y = divisor;
|
||||||
|
MarkDirty(VertexInfoBuffer.VertexOffsetsOffset + index * Unsafe.SizeOf<Vector4<int>>() + sizeof(int), sizeof(int));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the offset of the index buffer.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="offset">Offset divided by the component size</param>
|
||||||
|
public void SetIndexBufferOffset(int offset)
|
||||||
|
{
|
||||||
|
if (_data.GeometryCounts.W != offset)
|
||||||
|
{
|
||||||
|
_data.GeometryCounts.W = offset;
|
||||||
|
MarkDirty(VertexInfoBuffer.GeometryCountsOffset + sizeof(int) * 3, sizeof(int));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Submits all pending buffer updates to the GPU.
|
||||||
|
/// </summary>
|
||||||
|
public void Commit()
|
||||||
|
{
|
||||||
|
Commit(MemoryMarshal.Cast<VertexInfoBuffer, byte>(MemoryMarshal.CreateSpan(ref _data, 1)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,96 @@
|
||||||
|
using Ryujinx.Graphics.GAL;
|
||||||
|
using Ryujinx.Graphics.Gpu.Shader;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Vertex, tessellation and geometry as compute shader draw manager.
|
||||||
|
/// </summary>
|
||||||
|
class VtgAsCompute : IDisposable
|
||||||
|
{
|
||||||
|
private readonly GpuContext _context;
|
||||||
|
private readonly GpuChannel _channel;
|
||||||
|
private readonly DeviceStateWithShadow<ThreedClassState> _state;
|
||||||
|
private readonly VtgAsComputeContext _vacContext;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new instance of the vertex, tessellation and geometry as compute shader draw manager.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">GPU context</param>
|
||||||
|
/// <param name="channel">GPU channel</param>
|
||||||
|
/// <param name="state">3D engine state</param>
|
||||||
|
public VtgAsCompute(GpuContext context, GpuChannel channel, DeviceStateWithShadow<ThreedClassState> state)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
_channel = channel;
|
||||||
|
_state = state;
|
||||||
|
_vacContext = new(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Emulates the pre-rasterization stages of a draw operation using a compute shader.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="engine">3D engine</param>
|
||||||
|
/// <param name="vertexAsCompute">Vertex shader converted to compute</param>
|
||||||
|
/// <param name="geometryAsCompute">Optional geometry shader converted to compute</param>
|
||||||
|
/// <param name="vertexPassthroughProgram">Fragment shader with a vertex passthrough shader to feed the compute output into the fragment stage</param>
|
||||||
|
/// <param name="topology">Primitive topology of the draw</param>
|
||||||
|
/// <param name="count">Index or vertex count of the draw</param>
|
||||||
|
/// <param name="instanceCount">Instance count</param>
|
||||||
|
/// <param name="firstIndex">First index on the index buffer, for indexed draws</param>
|
||||||
|
/// <param name="firstVertex">First vertex on the vertex buffer</param>
|
||||||
|
/// <param name="firstInstance">First instance</param>
|
||||||
|
/// <param name="indexed">Whether the draw is indexed</param>
|
||||||
|
public void DrawAsCompute(
|
||||||
|
ThreedClass engine,
|
||||||
|
ShaderAsCompute vertexAsCompute,
|
||||||
|
ShaderAsCompute geometryAsCompute,
|
||||||
|
IProgram vertexPassthroughProgram,
|
||||||
|
PrimitiveTopology topology,
|
||||||
|
int count,
|
||||||
|
int instanceCount,
|
||||||
|
int firstIndex,
|
||||||
|
int firstVertex,
|
||||||
|
int firstInstance,
|
||||||
|
bool indexed)
|
||||||
|
{
|
||||||
|
VtgAsComputeState state = new(
|
||||||
|
_context,
|
||||||
|
_channel,
|
||||||
|
_state,
|
||||||
|
_vacContext,
|
||||||
|
engine,
|
||||||
|
vertexAsCompute,
|
||||||
|
geometryAsCompute,
|
||||||
|
vertexPassthroughProgram,
|
||||||
|
topology,
|
||||||
|
count,
|
||||||
|
instanceCount,
|
||||||
|
firstIndex,
|
||||||
|
firstVertex,
|
||||||
|
firstInstance,
|
||||||
|
indexed);
|
||||||
|
|
||||||
|
state.RunVertex();
|
||||||
|
state.RunGeometry();
|
||||||
|
state.RunFragment();
|
||||||
|
|
||||||
|
_vacContext.FreeBuffers();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
_vacContext.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,648 @@
|
||||||
|
using Ryujinx.Common;
|
||||||
|
using Ryujinx.Graphics.GAL;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Vertex, tessellation and geometry as compute shader context.
|
||||||
|
/// </summary>
|
||||||
|
class VtgAsComputeContext : IDisposable
|
||||||
|
{
|
||||||
|
private const int DummyBufferSize = 16;
|
||||||
|
|
||||||
|
private readonly GpuContext _context;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Cache of buffer textures used for vertex and index buffers.
|
||||||
|
/// </summary>
|
||||||
|
private class BufferTextureCache : IDisposable
|
||||||
|
{
|
||||||
|
private readonly Dictionary<Format, ITexture> _cache;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new instance of the buffer texture cache.
|
||||||
|
/// </summary>
|
||||||
|
public BufferTextureCache()
|
||||||
|
{
|
||||||
|
_cache = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a cached or creates and caches a buffer texture with the specified format.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="renderer">Renderer where the texture will be used</param>
|
||||||
|
/// <param name="format">Format of the buffer texture</param>
|
||||||
|
/// <returns>Buffer texture</returns>
|
||||||
|
public ITexture Get(IRenderer renderer, Format format)
|
||||||
|
{
|
||||||
|
if (!_cache.TryGetValue(format, out ITexture bufferTexture))
|
||||||
|
{
|
||||||
|
bufferTexture = renderer.CreateTexture(new TextureCreateInfo(
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
format,
|
||||||
|
DepthStencilMode.Depth,
|
||||||
|
Target.TextureBuffer,
|
||||||
|
SwizzleComponent.Red,
|
||||||
|
SwizzleComponent.Green,
|
||||||
|
SwizzleComponent.Blue,
|
||||||
|
SwizzleComponent.Alpha));
|
||||||
|
|
||||||
|
_cache.Add(format, bufferTexture);
|
||||||
|
}
|
||||||
|
|
||||||
|
return bufferTexture;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
foreach (var texture in _cache.Values)
|
||||||
|
{
|
||||||
|
texture.Release();
|
||||||
|
}
|
||||||
|
|
||||||
|
_cache.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Buffer state.
|
||||||
|
/// </summary>
|
||||||
|
private struct Buffer
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Buffer handle.
|
||||||
|
/// </summary>
|
||||||
|
public BufferHandle Handle;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Current free buffer offset.
|
||||||
|
/// </summary>
|
||||||
|
public int Offset;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Total buffer size in bytes.
|
||||||
|
/// </summary>
|
||||||
|
public int Size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Index buffer state.
|
||||||
|
/// </summary>
|
||||||
|
private readonly struct IndexBuffer
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Buffer handle.
|
||||||
|
/// </summary>
|
||||||
|
public BufferHandle Handle { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Index count.
|
||||||
|
/// </summary>
|
||||||
|
public int Count { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Size in bytes.
|
||||||
|
/// </summary>
|
||||||
|
public int Size { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new index buffer state.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="handle">Buffer handle</param>
|
||||||
|
/// <param name="count">Index count</param>
|
||||||
|
/// <param name="size">Size in bytes</param>
|
||||||
|
public IndexBuffer(BufferHandle handle, int count, int size)
|
||||||
|
{
|
||||||
|
Handle = handle;
|
||||||
|
Count = count;
|
||||||
|
Size = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a full range starting from the beggining of the buffer.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Range</returns>
|
||||||
|
public readonly BufferRange ToRange()
|
||||||
|
{
|
||||||
|
return new BufferRange(Handle, 0, Size);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a range starting from the beggining of the buffer, with the specified size.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="size">Size in bytes of the range</param>
|
||||||
|
/// <returns>Range</returns>
|
||||||
|
public readonly BufferRange ToRange(int size)
|
||||||
|
{
|
||||||
|
return new BufferRange(Handle, 0, size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly BufferTextureCache[] _bufferTextures;
|
||||||
|
private BufferHandle _dummyBuffer;
|
||||||
|
private Buffer _vertexDataBuffer;
|
||||||
|
private Buffer _geometryVertexDataBuffer;
|
||||||
|
private Buffer _geometryIndexDataBuffer;
|
||||||
|
private BufferHandle _sequentialIndexBuffer;
|
||||||
|
private int _sequentialIndexBufferCount;
|
||||||
|
|
||||||
|
private readonly Dictionary<PrimitiveTopology, IndexBuffer> _topologyRemapBuffers;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Vertex information buffer updater.
|
||||||
|
/// </summary>
|
||||||
|
public VertexInfoBufferUpdater VertexInfoBufferUpdater { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new instance of the vertex, tessellation and geometry as compute shader context.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context"></param>
|
||||||
|
public VtgAsComputeContext(GpuContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
_bufferTextures = new BufferTextureCache[Constants.TotalVertexBuffers + 2];
|
||||||
|
_topologyRemapBuffers = new();
|
||||||
|
VertexInfoBufferUpdater = new(context.Renderer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the number of complete primitives that can be formed with a given vertex count, for a given topology.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="primitiveType">Topology</param>
|
||||||
|
/// <param name="count">Vertex count</param>
|
||||||
|
/// <returns>Total of complete primitives</returns>
|
||||||
|
public static int GetPrimitivesCount(PrimitiveTopology primitiveType, int count)
|
||||||
|
{
|
||||||
|
return primitiveType switch
|
||||||
|
{
|
||||||
|
PrimitiveTopology.Lines => count / 2,
|
||||||
|
PrimitiveTopology.LinesAdjacency => count / 4,
|
||||||
|
PrimitiveTopology.LineLoop => count > 1 ? count : 0,
|
||||||
|
PrimitiveTopology.LineStrip => Math.Max(count - 1, 0),
|
||||||
|
PrimitiveTopology.LineStripAdjacency => Math.Max(count - 3, 0),
|
||||||
|
PrimitiveTopology.Triangles => count / 3,
|
||||||
|
PrimitiveTopology.TrianglesAdjacency => count / 6,
|
||||||
|
PrimitiveTopology.TriangleStrip or
|
||||||
|
PrimitiveTopology.TriangleFan or
|
||||||
|
PrimitiveTopology.Polygon => Math.Max(count - 2, 0),
|
||||||
|
PrimitiveTopology.TriangleStripAdjacency => Math.Max(count - 2, 0) / 2,
|
||||||
|
PrimitiveTopology.Quads => (count / 4) * 2, // In triangles.
|
||||||
|
PrimitiveTopology.QuadStrip => Math.Max((count - 2) / 2, 0) * 2, // In triangles.
|
||||||
|
_ => count,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the total of vertices that a single primitive has, for the specified topology.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="primitiveType">Topology</param>
|
||||||
|
/// <returns>Vertex count</returns>
|
||||||
|
private static int GetVerticesPerPrimitive(PrimitiveTopology primitiveType)
|
||||||
|
{
|
||||||
|
return primitiveType switch
|
||||||
|
{
|
||||||
|
PrimitiveTopology.Lines or
|
||||||
|
PrimitiveTopology.LineLoop or
|
||||||
|
PrimitiveTopology.LineStrip => 2,
|
||||||
|
PrimitiveTopology.LinesAdjacency or
|
||||||
|
PrimitiveTopology.LineStripAdjacency => 4,
|
||||||
|
PrimitiveTopology.Triangles or
|
||||||
|
PrimitiveTopology.TriangleStrip or
|
||||||
|
PrimitiveTopology.TriangleFan or
|
||||||
|
PrimitiveTopology.Polygon => 3,
|
||||||
|
PrimitiveTopology.TrianglesAdjacency or
|
||||||
|
PrimitiveTopology.TriangleStripAdjacency => 6,
|
||||||
|
PrimitiveTopology.Quads or
|
||||||
|
PrimitiveTopology.QuadStrip => 3, // 2 triangles.
|
||||||
|
_ => 1,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a cached or creates a new buffer that can be used to map linear indices to ones
|
||||||
|
/// of a specified topology, and build complete primitives.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="topology">Topology</param>
|
||||||
|
/// <param name="count">Number of input vertices that needs to be mapped using that buffer</param>
|
||||||
|
/// <returns>Remap buffer range</returns>
|
||||||
|
public BufferRange GetOrCreateTopologyRemapBuffer(PrimitiveTopology topology, int count)
|
||||||
|
{
|
||||||
|
if (!_topologyRemapBuffers.TryGetValue(topology, out IndexBuffer buffer) || buffer.Count < count)
|
||||||
|
{
|
||||||
|
if (buffer.Handle != BufferHandle.Null)
|
||||||
|
{
|
||||||
|
_context.Renderer.DeleteBuffer(buffer.Handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer = CreateTopologyRemapBuffer(topology, count);
|
||||||
|
_topologyRemapBuffers[topology] = buffer;
|
||||||
|
|
||||||
|
return buffer.ToRange();
|
||||||
|
}
|
||||||
|
|
||||||
|
return buffer.ToRange(Math.Max(GetPrimitivesCount(topology, count) * GetVerticesPerPrimitive(topology), 1) * sizeof(uint));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new topology remap buffer.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="topology">Topology</param>
|
||||||
|
/// <param name="count">Maximum of vertices that will be accessed</param>
|
||||||
|
/// <returns>Remap buffer range</returns>
|
||||||
|
private IndexBuffer CreateTopologyRemapBuffer(PrimitiveTopology topology, int count)
|
||||||
|
{
|
||||||
|
// Size can't be zero as creating zero sized buffers is invalid.
|
||||||
|
Span<int> data = new int[Math.Max(GetPrimitivesCount(topology, count) * GetVerticesPerPrimitive(topology), 1)];
|
||||||
|
|
||||||
|
switch (topology)
|
||||||
|
{
|
||||||
|
case PrimitiveTopology.Points:
|
||||||
|
case PrimitiveTopology.Lines:
|
||||||
|
case PrimitiveTopology.LinesAdjacency:
|
||||||
|
case PrimitiveTopology.Triangles:
|
||||||
|
case PrimitiveTopology.TrianglesAdjacency:
|
||||||
|
case PrimitiveTopology.Patches:
|
||||||
|
for (int index = 0; index < data.Length; index++)
|
||||||
|
{
|
||||||
|
data[index] = index;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case PrimitiveTopology.LineLoop:
|
||||||
|
data[^1] = 0;
|
||||||
|
|
||||||
|
for (int index = 0; index < ((data.Length - 1) & ~1); index += 2)
|
||||||
|
{
|
||||||
|
data[index] = index >> 1;
|
||||||
|
data[index + 1] = (index >> 1) + 1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case PrimitiveTopology.LineStrip:
|
||||||
|
for (int index = 0; index < ((data.Length - 1) & ~1); index += 2)
|
||||||
|
{
|
||||||
|
data[index] = index >> 1;
|
||||||
|
data[index + 1] = (index >> 1) + 1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case PrimitiveTopology.TriangleStrip:
|
||||||
|
int tsTrianglesCount = data.Length / 3;
|
||||||
|
int tsOutIndex = 3;
|
||||||
|
|
||||||
|
if (tsTrianglesCount > 0)
|
||||||
|
{
|
||||||
|
data[0] = 0;
|
||||||
|
data[1] = 1;
|
||||||
|
data[2] = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int tri = 1; tri < tsTrianglesCount; tri++)
|
||||||
|
{
|
||||||
|
int baseIndex = tri * 3;
|
||||||
|
|
||||||
|
if ((tri & 1) != 0)
|
||||||
|
{
|
||||||
|
data[baseIndex] = tsOutIndex - 1;
|
||||||
|
data[baseIndex + 1] = tsOutIndex - 2;
|
||||||
|
data[baseIndex + 2] = tsOutIndex++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
data[baseIndex] = tsOutIndex - 2;
|
||||||
|
data[baseIndex + 1] = tsOutIndex - 1;
|
||||||
|
data[baseIndex + 2] = tsOutIndex++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case PrimitiveTopology.TriangleFan:
|
||||||
|
case PrimitiveTopology.Polygon:
|
||||||
|
int tfTrianglesCount = data.Length / 3;
|
||||||
|
int tfOutIndex = 1;
|
||||||
|
|
||||||
|
for (int index = 0; index < tfTrianglesCount * 3; index += 3)
|
||||||
|
{
|
||||||
|
data[index] = 0;
|
||||||
|
data[index + 1] = tfOutIndex;
|
||||||
|
data[index + 2] = ++tfOutIndex;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case PrimitiveTopology.Quads:
|
||||||
|
int qQuadsCount = data.Length / 6;
|
||||||
|
|
||||||
|
for (int quad = 0; quad < qQuadsCount; quad++)
|
||||||
|
{
|
||||||
|
int index = quad * 6;
|
||||||
|
int qIndex = quad * 4;
|
||||||
|
|
||||||
|
data[index] = qIndex;
|
||||||
|
data[index + 1] = qIndex + 1;
|
||||||
|
data[index + 2] = qIndex + 2;
|
||||||
|
data[index + 3] = qIndex;
|
||||||
|
data[index + 4] = qIndex + 2;
|
||||||
|
data[index + 5] = qIndex + 3;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case PrimitiveTopology.QuadStrip:
|
||||||
|
int qsQuadsCount = data.Length / 6;
|
||||||
|
|
||||||
|
if (qsQuadsCount > 0)
|
||||||
|
{
|
||||||
|
data[0] = 0;
|
||||||
|
data[1] = 1;
|
||||||
|
data[2] = 2;
|
||||||
|
data[3] = 0;
|
||||||
|
data[4] = 2;
|
||||||
|
data[5] = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int quad = 1; quad < qsQuadsCount; quad++)
|
||||||
|
{
|
||||||
|
int index = quad * 6;
|
||||||
|
int qIndex = quad * 2;
|
||||||
|
|
||||||
|
data[index] = qIndex + 1;
|
||||||
|
data[index + 1] = qIndex;
|
||||||
|
data[index + 2] = qIndex + 2;
|
||||||
|
data[index + 3] = qIndex + 1;
|
||||||
|
data[index + 4] = qIndex + 2;
|
||||||
|
data[index + 5] = qIndex + 3;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case PrimitiveTopology.LineStripAdjacency:
|
||||||
|
for (int index = 0; index < ((data.Length - 3) & ~3); index += 4)
|
||||||
|
{
|
||||||
|
int lIndex = index >> 2;
|
||||||
|
|
||||||
|
data[index] = lIndex;
|
||||||
|
data[index + 1] = lIndex + 1;
|
||||||
|
data[index + 2] = lIndex + 2;
|
||||||
|
data[index + 3] = lIndex + 3;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case PrimitiveTopology.TriangleStripAdjacency:
|
||||||
|
int tsaTrianglesCount = data.Length / 6;
|
||||||
|
int tsaOutIndex = 6;
|
||||||
|
|
||||||
|
if (tsaTrianglesCount > 0)
|
||||||
|
{
|
||||||
|
data[0] = 0;
|
||||||
|
data[1] = 1;
|
||||||
|
data[2] = 2;
|
||||||
|
data[3] = 3;
|
||||||
|
data[4] = 4;
|
||||||
|
data[5] = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int tri = 1; tri < tsaTrianglesCount; tri++)
|
||||||
|
{
|
||||||
|
int baseIndex = tri * 6;
|
||||||
|
|
||||||
|
if ((tri & 1) != 0)
|
||||||
|
{
|
||||||
|
data[baseIndex] = tsaOutIndex - 2;
|
||||||
|
data[baseIndex + 1] = tsaOutIndex - 1;
|
||||||
|
data[baseIndex + 2] = tsaOutIndex - 4;
|
||||||
|
data[baseIndex + 3] = tsaOutIndex - 3;
|
||||||
|
data[baseIndex + 4] = tsaOutIndex++;
|
||||||
|
data[baseIndex + 5] = tsaOutIndex++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
data[baseIndex] = tsaOutIndex - 4;
|
||||||
|
data[baseIndex + 1] = tsaOutIndex - 3;
|
||||||
|
data[baseIndex + 2] = tsaOutIndex - 2;
|
||||||
|
data[baseIndex + 3] = tsaOutIndex - 1;
|
||||||
|
data[baseIndex + 4] = tsaOutIndex++;
|
||||||
|
data[baseIndex + 5] = tsaOutIndex++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
ReadOnlySpan<byte> dataBytes = MemoryMarshal.Cast<int, byte>(data);
|
||||||
|
|
||||||
|
BufferHandle buffer = _context.Renderer.CreateBuffer(dataBytes.Length);
|
||||||
|
_context.Renderer.SetBufferData(buffer, 0, dataBytes);
|
||||||
|
|
||||||
|
return new IndexBuffer(buffer, count, dataBytes.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a buffer texture with a given format, for the given index.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="index">Index of the buffer texture</param>
|
||||||
|
/// <param name="format">Format of the buffer texture</param>
|
||||||
|
/// <returns>Buffer texture</returns>
|
||||||
|
public ITexture EnsureBufferTexture(int index, Format format)
|
||||||
|
{
|
||||||
|
return (_bufferTextures[index] ??= new()).Get(_context.Renderer, format);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the offset and size of usable storage on the output vertex buffer.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="size">Size in bytes that will be used</param>
|
||||||
|
/// <returns>Usable offset and size on the buffer</returns>
|
||||||
|
public (int, int) GetVertexDataBuffer(int size)
|
||||||
|
{
|
||||||
|
return EnsureBuffer(ref _vertexDataBuffer, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the offset and size of usable storage on the output geometry shader vertex buffer.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="size">Size in bytes that will be used</param>
|
||||||
|
/// <returns>Usable offset and size on the buffer</returns>
|
||||||
|
public (int, int) GetGeometryVertexDataBuffer(int size)
|
||||||
|
{
|
||||||
|
return EnsureBuffer(ref _geometryVertexDataBuffer, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the offset and size of usable storage on the output geometry shader index buffer.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="size">Size in bytes that will be used</param>
|
||||||
|
/// <returns>Usable offset and size on the buffer</returns>
|
||||||
|
public (int, int) GetGeometryIndexDataBuffer(int size)
|
||||||
|
{
|
||||||
|
return EnsureBuffer(ref _geometryIndexDataBuffer, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a range of the output vertex buffer for binding.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="offset">Offset of the range</param>
|
||||||
|
/// <param name="size">Size of the range in bytes</param>
|
||||||
|
/// <returns>Range</returns>
|
||||||
|
public BufferRange GetVertexDataBufferRange(int offset, int size)
|
||||||
|
{
|
||||||
|
return new BufferRange(_vertexDataBuffer.Handle, offset, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a range of the output geometry shader vertex buffer for binding.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="offset">Offset of the range</param>
|
||||||
|
/// <param name="size">Size of the range in bytes</param>
|
||||||
|
/// <returns>Range</returns>
|
||||||
|
public BufferRange GetGeometryVertexDataBufferRange(int offset, int size)
|
||||||
|
{
|
||||||
|
return new BufferRange(_geometryVertexDataBuffer.Handle, offset, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a range of the output geometry shader index buffer for binding.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="offset">Offset of the range</param>
|
||||||
|
/// <param name="size">Size of the range in bytes</param>
|
||||||
|
/// <returns>Range</returns>
|
||||||
|
public BufferRange GetGeometryIndexDataBufferRange(int offset, int size)
|
||||||
|
{
|
||||||
|
return new BufferRange(_geometryIndexDataBuffer.Handle, offset, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the range for a dummy 16 bytes buffer, filled with zeros.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Dummy buffer range</returns>
|
||||||
|
public BufferRange GetDummyBufferRange()
|
||||||
|
{
|
||||||
|
if (_dummyBuffer == BufferHandle.Null)
|
||||||
|
{
|
||||||
|
_dummyBuffer = _context.Renderer.CreateBuffer(DummyBufferSize);
|
||||||
|
_context.Renderer.Pipeline.ClearBuffer(_dummyBuffer, 0, DummyBufferSize, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new BufferRange(_dummyBuffer, 0, DummyBufferSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the range for a sequential index buffer, with ever incrementing index values.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="count">Minimum number of indices that the buffer should have</param>
|
||||||
|
/// <returns>Buffer handle</returns>
|
||||||
|
public BufferHandle GetSequentialIndexBuffer(int count)
|
||||||
|
{
|
||||||
|
if (_sequentialIndexBufferCount < count)
|
||||||
|
{
|
||||||
|
if (_sequentialIndexBuffer != BufferHandle.Null)
|
||||||
|
{
|
||||||
|
_context.Renderer.DeleteBuffer(_sequentialIndexBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
_sequentialIndexBuffer = _context.Renderer.CreateBuffer(count * sizeof(uint));
|
||||||
|
_sequentialIndexBufferCount = count;
|
||||||
|
|
||||||
|
Span<int> data = new int[count];
|
||||||
|
|
||||||
|
for (int index = 0; index < count; index++)
|
||||||
|
{
|
||||||
|
data[index] = index;
|
||||||
|
}
|
||||||
|
|
||||||
|
_context.Renderer.SetBufferData(_sequentialIndexBuffer, 0, MemoryMarshal.Cast<int, byte>(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
return _sequentialIndexBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ensure that a buffer exists, is large enough, and allocates a sub-region of the specified size inside the buffer.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="buffer">Buffer state</param>
|
||||||
|
/// <param name="size">Required size in bytes</param>
|
||||||
|
/// <returns>Allocated offset and size</returns>
|
||||||
|
private (int, int) EnsureBuffer(ref Buffer buffer, int size)
|
||||||
|
{
|
||||||
|
int newSize = buffer.Offset + size;
|
||||||
|
|
||||||
|
if (buffer.Size < newSize)
|
||||||
|
{
|
||||||
|
if (buffer.Handle != BufferHandle.Null)
|
||||||
|
{
|
||||||
|
_context.Renderer.DeleteBuffer(buffer.Handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer.Handle = _context.Renderer.CreateBuffer(newSize);
|
||||||
|
buffer.Size = newSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
int offset = buffer.Offset;
|
||||||
|
|
||||||
|
buffer.Offset = BitUtils.AlignUp(newSize, _context.Capabilities.StorageBufferOffsetAlignment);
|
||||||
|
|
||||||
|
return (offset, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Frees all buffer sub-regions that were previously allocated.
|
||||||
|
/// </summary>
|
||||||
|
public void FreeBuffers()
|
||||||
|
{
|
||||||
|
_vertexDataBuffer.Offset = 0;
|
||||||
|
_geometryVertexDataBuffer.Offset = 0;
|
||||||
|
_geometryIndexDataBuffer.Offset = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
for (int index = 0; index < _bufferTextures.Length; index++)
|
||||||
|
{
|
||||||
|
_bufferTextures[index]?.Dispose();
|
||||||
|
_bufferTextures[index] = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
DestroyIfNotNull(ref _dummyBuffer);
|
||||||
|
DestroyIfNotNull(ref _vertexDataBuffer.Handle);
|
||||||
|
DestroyIfNotNull(ref _geometryVertexDataBuffer.Handle);
|
||||||
|
DestroyIfNotNull(ref _geometryIndexDataBuffer.Handle);
|
||||||
|
DestroyIfNotNull(ref _sequentialIndexBuffer);
|
||||||
|
|
||||||
|
foreach (var indexBuffer in _topologyRemapBuffers.Values)
|
||||||
|
{
|
||||||
|
_context.Renderer.DeleteBuffer(indexBuffer.Handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
_topologyRemapBuffers.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Deletes a buffer if the handle is valid (not null), then sets the handle to null.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="handle">Buffer handle</param>
|
||||||
|
private void DestroyIfNotNull(ref BufferHandle handle)
|
||||||
|
{
|
||||||
|
if (handle != BufferHandle.Null)
|
||||||
|
{
|
||||||
|
_context.Renderer.DeleteBuffer(handle);
|
||||||
|
handle = BufferHandle.Null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,535 @@
|
||||||
|
using Ryujinx.Common;
|
||||||
|
using Ryujinx.Common.Logging;
|
||||||
|
using Ryujinx.Graphics.GAL;
|
||||||
|
using Ryujinx.Graphics.Gpu.Engine.Types;
|
||||||
|
using Ryujinx.Graphics.Gpu.Image;
|
||||||
|
using Ryujinx.Graphics.Gpu.Shader;
|
||||||
|
using Ryujinx.Graphics.Shader;
|
||||||
|
using Ryujinx.Graphics.Shader.Translation;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Vertex, tessellation and geometry as compute shader state.
|
||||||
|
/// </summary>
|
||||||
|
struct VtgAsComputeState
|
||||||
|
{
|
||||||
|
private const int ComputeLocalSize = 32;
|
||||||
|
|
||||||
|
private readonly GpuContext _context;
|
||||||
|
private readonly GpuChannel _channel;
|
||||||
|
private readonly DeviceStateWithShadow<ThreedClassState> _state;
|
||||||
|
private readonly VtgAsComputeContext _vacContext;
|
||||||
|
private readonly ThreedClass _engine;
|
||||||
|
private readonly ShaderAsCompute _vertexAsCompute;
|
||||||
|
private readonly ShaderAsCompute _geometryAsCompute;
|
||||||
|
private readonly IProgram _vertexPassthroughProgram;
|
||||||
|
private readonly PrimitiveTopology _topology;
|
||||||
|
private readonly int _count;
|
||||||
|
private readonly int _instanceCount;
|
||||||
|
private readonly int _firstIndex;
|
||||||
|
private readonly int _firstVertex;
|
||||||
|
private readonly int _firstInstance;
|
||||||
|
private readonly bool _indexed;
|
||||||
|
|
||||||
|
private readonly int _vertexDataOffset;
|
||||||
|
private readonly int _vertexDataSize;
|
||||||
|
private readonly int _geometryVertexDataOffset;
|
||||||
|
private readonly int _geometryVertexDataSize;
|
||||||
|
private readonly int _geometryIndexDataOffset;
|
||||||
|
private readonly int _geometryIndexDataSize;
|
||||||
|
private readonly int _geometryIndexDataCount;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new vertex, tessellation and geometry as compute shader state.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">GPU context</param>
|
||||||
|
/// <param name="channel">GPU channel</param>
|
||||||
|
/// <param name="state">3D engine state</param>
|
||||||
|
/// <param name="vacContext">Vertex as compute context</param>
|
||||||
|
/// <param name="engine">3D engine</param>
|
||||||
|
/// <param name="vertexAsCompute">Vertex shader converted to compute</param>
|
||||||
|
/// <param name="geometryAsCompute">Optional geometry shader converted to compute</param>
|
||||||
|
/// <param name="vertexPassthroughProgram">Fragment shader with a vertex passthrough shader to feed the compute output into the fragment stage</param>
|
||||||
|
/// <param name="topology">Primitive topology of the draw</param>
|
||||||
|
/// <param name="count">Index or vertex count of the draw</param>
|
||||||
|
/// <param name="instanceCount">Instance count</param>
|
||||||
|
/// <param name="firstIndex">First index on the index buffer, for indexed draws</param>
|
||||||
|
/// <param name="firstVertex">First vertex on the vertex buffer</param>
|
||||||
|
/// <param name="firstInstance">First instance</param>
|
||||||
|
/// <param name="indexed">Whether the draw is indexed</param>
|
||||||
|
public VtgAsComputeState(
|
||||||
|
GpuContext context,
|
||||||
|
GpuChannel channel,
|
||||||
|
DeviceStateWithShadow<ThreedClassState> state,
|
||||||
|
VtgAsComputeContext vacContext,
|
||||||
|
ThreedClass engine,
|
||||||
|
ShaderAsCompute vertexAsCompute,
|
||||||
|
ShaderAsCompute geometryAsCompute,
|
||||||
|
IProgram vertexPassthroughProgram,
|
||||||
|
PrimitiveTopology topology,
|
||||||
|
int count,
|
||||||
|
int instanceCount,
|
||||||
|
int firstIndex,
|
||||||
|
int firstVertex,
|
||||||
|
int firstInstance,
|
||||||
|
bool indexed)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
_channel = channel;
|
||||||
|
_state = state;
|
||||||
|
_vacContext = vacContext;
|
||||||
|
_engine = engine;
|
||||||
|
_vertexAsCompute = vertexAsCompute;
|
||||||
|
_geometryAsCompute = geometryAsCompute;
|
||||||
|
_vertexPassthroughProgram = vertexPassthroughProgram;
|
||||||
|
_topology = topology;
|
||||||
|
_count = count;
|
||||||
|
_instanceCount = instanceCount;
|
||||||
|
_firstIndex = firstIndex;
|
||||||
|
_firstVertex = firstVertex;
|
||||||
|
_firstInstance = firstInstance;
|
||||||
|
_indexed = indexed;
|
||||||
|
|
||||||
|
int vertexDataSize = vertexAsCompute.Reservations.OutputSizeInBytesPerInvocation * count * instanceCount;
|
||||||
|
|
||||||
|
(_vertexDataOffset, _vertexDataSize) = _vacContext.GetVertexDataBuffer(vertexDataSize);
|
||||||
|
|
||||||
|
if (geometryAsCompute != null)
|
||||||
|
{
|
||||||
|
int totalPrimitivesCount = VtgAsComputeContext.GetPrimitivesCount(topology, count * instanceCount);
|
||||||
|
int maxCompleteStrips = GetMaxCompleteStrips(geometryAsCompute.Info.GeometryVerticesPerPrimitive, geometryAsCompute.Info.GeometryMaxOutputVertices);
|
||||||
|
int totalVerticesCount = totalPrimitivesCount * geometryAsCompute.Info.GeometryMaxOutputVertices * geometryAsCompute.Info.ThreadsPerInputPrimitive;
|
||||||
|
int geometryVbDataSize = totalVerticesCount * geometryAsCompute.Reservations.OutputSizeInBytesPerInvocation;
|
||||||
|
int geometryIbDataCount = totalVerticesCount + totalPrimitivesCount * maxCompleteStrips;
|
||||||
|
int geometryIbDataSize = geometryIbDataCount * sizeof(uint);
|
||||||
|
|
||||||
|
(_geometryVertexDataOffset, _geometryVertexDataSize) = vacContext.GetGeometryVertexDataBuffer(geometryVbDataSize);
|
||||||
|
(_geometryIndexDataOffset, _geometryIndexDataSize) = vacContext.GetGeometryIndexDataBuffer(geometryIbDataSize);
|
||||||
|
|
||||||
|
_geometryIndexDataCount = geometryIbDataCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Emulates the vertex stage using compute.
|
||||||
|
/// </summary>
|
||||||
|
public readonly void RunVertex()
|
||||||
|
{
|
||||||
|
_context.Renderer.Pipeline.SetProgram(_vertexAsCompute.HostProgram);
|
||||||
|
|
||||||
|
int primitivesCount = VtgAsComputeContext.GetPrimitivesCount(_topology, _count);
|
||||||
|
|
||||||
|
_vacContext.VertexInfoBufferUpdater.SetVertexCounts(_count, _instanceCount, _firstVertex, _firstInstance);
|
||||||
|
_vacContext.VertexInfoBufferUpdater.SetGeometryCounts(primitivesCount);
|
||||||
|
|
||||||
|
for (int index = 0; index < Constants.TotalVertexAttribs; index++)
|
||||||
|
{
|
||||||
|
var vertexAttrib = _state.State.VertexAttribState[index];
|
||||||
|
|
||||||
|
if (!FormatTable.TryGetSingleComponentAttribFormat(vertexAttrib.UnpackFormat(), out Format format, out int componentsCount))
|
||||||
|
{
|
||||||
|
Logger.Debug?.Print(LogClass.Gpu, $"Invalid attribute format 0x{vertexAttrib.UnpackFormat():X}.");
|
||||||
|
|
||||||
|
format = vertexAttrib.UnpackType() switch
|
||||||
|
{
|
||||||
|
VertexAttribType.Sint => Format.R32Sint,
|
||||||
|
VertexAttribType.Uint => Format.R32Uint,
|
||||||
|
_ => Format.R32Float
|
||||||
|
};
|
||||||
|
|
||||||
|
componentsCount = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vertexAttrib.UnpackIsConstant())
|
||||||
|
{
|
||||||
|
_vacContext.VertexInfoBufferUpdater.SetVertexStride(index, 0, componentsCount);
|
||||||
|
_vacContext.VertexInfoBufferUpdater.SetVertexOffset(index, 0, 0);
|
||||||
|
SetDummyBufferTexture(_vertexAsCompute.Reservations, index, format);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int bufferIndex = vertexAttrib.UnpackBufferIndex();
|
||||||
|
|
||||||
|
GpuVa endAddress = _state.State.VertexBufferEndAddress[bufferIndex];
|
||||||
|
var vertexBuffer = _state.State.VertexBufferState[bufferIndex];
|
||||||
|
bool instanced = _state.State.VertexBufferInstanced[bufferIndex];
|
||||||
|
|
||||||
|
ulong address = vertexBuffer.Address.Pack();
|
||||||
|
|
||||||
|
if (!vertexBuffer.UnpackEnable() || !_channel.MemoryManager.IsMapped(address))
|
||||||
|
{
|
||||||
|
_vacContext.VertexInfoBufferUpdater.SetVertexStride(index, 0, componentsCount);
|
||||||
|
_vacContext.VertexInfoBufferUpdater.SetVertexOffset(index, 0, 0);
|
||||||
|
SetDummyBufferTexture(_vertexAsCompute.Reservations, index, format);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int vbStride = vertexBuffer.UnpackStride();
|
||||||
|
ulong vbSize = GetVertexBufferSize(address, endAddress.Pack(), vbStride, _indexed, instanced, _firstVertex, _count);
|
||||||
|
|
||||||
|
ulong oldVbSize = vbSize;
|
||||||
|
|
||||||
|
ulong attributeOffset = (ulong)vertexAttrib.UnpackOffset();
|
||||||
|
int componentSize = format.GetScalarSize();
|
||||||
|
|
||||||
|
address += attributeOffset;
|
||||||
|
|
||||||
|
ulong misalign = address & ((ulong)_context.Capabilities.TextureBufferOffsetAlignment - 1);
|
||||||
|
|
||||||
|
vbSize = Align(vbSize - attributeOffset + misalign, componentSize);
|
||||||
|
|
||||||
|
SetBufferTexture(_vertexAsCompute.Reservations, index, format, address - misalign, vbSize);
|
||||||
|
|
||||||
|
_vacContext.VertexInfoBufferUpdater.SetVertexStride(index, vbStride / componentSize, componentsCount);
|
||||||
|
_vacContext.VertexInfoBufferUpdater.SetVertexOffset(index, (int)misalign / componentSize, instanced ? vertexBuffer.Divisor : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_indexed)
|
||||||
|
{
|
||||||
|
SetIndexBufferTexture(_vertexAsCompute.Reservations, _firstIndex, _count, out int ibOffset);
|
||||||
|
_vacContext.VertexInfoBufferUpdater.SetIndexBufferOffset(ibOffset);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SetSequentialIndexBufferTexture(_vertexAsCompute.Reservations, _count);
|
||||||
|
_vacContext.VertexInfoBufferUpdater.SetIndexBufferOffset(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int vertexInfoBinding = _vertexAsCompute.Reservations.VertexInfoConstantBufferBinding;
|
||||||
|
BufferRange vertexInfoRange = new(_vacContext.VertexInfoBufferUpdater.Handle, 0, VertexInfoBuffer.RequiredSize);
|
||||||
|
_context.Renderer.Pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(vertexInfoBinding, vertexInfoRange) });
|
||||||
|
|
||||||
|
int vertexDataBinding = _vertexAsCompute.Reservations.VertexOutputStorageBufferBinding;
|
||||||
|
BufferRange vertexDataRange = _vacContext.GetVertexDataBufferRange(_vertexDataOffset, _vertexDataSize);
|
||||||
|
_context.Renderer.Pipeline.SetStorageBuffers(stackalloc[] { new BufferAssignment(vertexDataBinding, vertexDataRange) });
|
||||||
|
|
||||||
|
_vacContext.VertexInfoBufferUpdater.Commit();
|
||||||
|
|
||||||
|
_context.Renderer.Pipeline.DispatchCompute(
|
||||||
|
BitUtils.DivRoundUp(_count, ComputeLocalSize),
|
||||||
|
BitUtils.DivRoundUp(_instanceCount, ComputeLocalSize),
|
||||||
|
1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Emulates the geometry stage using compute, if it exists, otherwise does nothing.
|
||||||
|
/// </summary>
|
||||||
|
public readonly void RunGeometry()
|
||||||
|
{
|
||||||
|
if (_geometryAsCompute == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int primitivesCount = VtgAsComputeContext.GetPrimitivesCount(_topology, _count);
|
||||||
|
|
||||||
|
_vacContext.VertexInfoBufferUpdater.SetVertexCounts(_count, _instanceCount, _firstVertex, _firstInstance);
|
||||||
|
_vacContext.VertexInfoBufferUpdater.SetGeometryCounts(primitivesCount);
|
||||||
|
_vacContext.VertexInfoBufferUpdater.Commit();
|
||||||
|
|
||||||
|
int vertexInfoBinding = _vertexAsCompute.Reservations.VertexInfoConstantBufferBinding;
|
||||||
|
BufferRange vertexInfoRange = new(_vacContext.VertexInfoBufferUpdater.Handle, 0, VertexInfoBuffer.RequiredSize);
|
||||||
|
_context.Renderer.Pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(vertexInfoBinding, vertexInfoRange) });
|
||||||
|
|
||||||
|
int vertexDataBinding = _vertexAsCompute.Reservations.VertexOutputStorageBufferBinding;
|
||||||
|
|
||||||
|
// Wait until compute is done.
|
||||||
|
// TODO: Batch compute and draw operations to avoid pipeline stalls.
|
||||||
|
_context.Renderer.Pipeline.Barrier();
|
||||||
|
_context.Renderer.Pipeline.SetProgram(_geometryAsCompute.HostProgram);
|
||||||
|
|
||||||
|
SetTopologyRemapBufferTexture(_geometryAsCompute.Reservations, _topology, _count);
|
||||||
|
|
||||||
|
int geometryVbBinding = _geometryAsCompute.Reservations.GeometryVertexOutputStorageBufferBinding;
|
||||||
|
int geometryIbBinding = _geometryAsCompute.Reservations.GeometryIndexOutputStorageBufferBinding;
|
||||||
|
|
||||||
|
BufferRange vertexDataRange = _vacContext.GetVertexDataBufferRange(_vertexDataOffset, _vertexDataSize);
|
||||||
|
BufferRange vertexBuffer = _vacContext.GetGeometryVertexDataBufferRange(_geometryVertexDataOffset, _geometryVertexDataSize);
|
||||||
|
BufferRange indexBuffer = _vacContext.GetGeometryIndexDataBufferRange(_geometryIndexDataOffset, _geometryIndexDataSize);
|
||||||
|
|
||||||
|
_context.Renderer.Pipeline.SetStorageBuffers(stackalloc[]
|
||||||
|
{
|
||||||
|
new BufferAssignment(vertexDataBinding, vertexDataRange),
|
||||||
|
new BufferAssignment(geometryVbBinding, vertexBuffer),
|
||||||
|
new BufferAssignment(geometryIbBinding, indexBuffer),
|
||||||
|
});
|
||||||
|
|
||||||
|
_context.Renderer.Pipeline.DispatchCompute(
|
||||||
|
BitUtils.DivRoundUp(primitivesCount, ComputeLocalSize),
|
||||||
|
BitUtils.DivRoundUp(_instanceCount, ComputeLocalSize),
|
||||||
|
_geometryAsCompute.Info.ThreadsPerInputPrimitive);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Performs a draw using the data produced on the vertex, tessellation and geometry stages,
|
||||||
|
/// if rasterizer discard is disabled.
|
||||||
|
/// </summary>
|
||||||
|
public readonly void RunFragment()
|
||||||
|
{
|
||||||
|
bool tfEnabled = _state.State.TfEnable;
|
||||||
|
|
||||||
|
if (!_state.State.RasterizeEnable && (!tfEnabled || !_context.Capabilities.SupportsTransformFeedback))
|
||||||
|
{
|
||||||
|
// No need to run fragment if rasterizer discard is enabled,
|
||||||
|
// and we are emulating transform feedback or transform feedback is disabled.
|
||||||
|
|
||||||
|
// Note: We might skip geometry shader here, but right now, this is fine,
|
||||||
|
// because the only cases that triggers VTG to compute are geometry shader
|
||||||
|
// being not supported, or the vertex pipeline doing store operations.
|
||||||
|
// If the geometry shader does not do any store and rasterizer discard is enabled, the geometry shader can be skipped.
|
||||||
|
// If the geometry shader does have stores, it would have been converted to compute too if stores are not supported.
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int vertexDataBinding = _vertexAsCompute.Reservations.VertexOutputStorageBufferBinding;
|
||||||
|
|
||||||
|
_context.Renderer.Pipeline.Barrier();
|
||||||
|
|
||||||
|
_vacContext.VertexInfoBufferUpdater.SetVertexCounts(_count, _instanceCount, _firstVertex, _firstInstance);
|
||||||
|
_vacContext.VertexInfoBufferUpdater.Commit();
|
||||||
|
|
||||||
|
if (_geometryAsCompute != null)
|
||||||
|
{
|
||||||
|
BufferRange vertexBuffer = _vacContext.GetGeometryVertexDataBufferRange(_geometryVertexDataOffset, _geometryVertexDataSize);
|
||||||
|
BufferRange indexBuffer = _vacContext.GetGeometryIndexDataBufferRange(_geometryIndexDataOffset, _geometryIndexDataSize);
|
||||||
|
|
||||||
|
_context.Renderer.Pipeline.SetProgram(_vertexPassthroughProgram);
|
||||||
|
_context.Renderer.Pipeline.SetIndexBuffer(indexBuffer, IndexType.UInt);
|
||||||
|
_context.Renderer.Pipeline.SetStorageBuffers(stackalloc[] { new BufferAssignment(vertexDataBinding, vertexBuffer) });
|
||||||
|
|
||||||
|
_context.Renderer.Pipeline.SetPrimitiveRestart(true, -1);
|
||||||
|
_context.Renderer.Pipeline.SetPrimitiveTopology(GetGeometryOutputTopology(_geometryAsCompute.Info.GeometryVerticesPerPrimitive));
|
||||||
|
|
||||||
|
_context.Renderer.Pipeline.DrawIndexed(_geometryIndexDataCount, 1, 0, 0, 0);
|
||||||
|
|
||||||
|
_engine.ForceStateDirtyByIndex(StateUpdater.IndexBufferStateIndex);
|
||||||
|
_engine.ForceStateDirtyByIndex(StateUpdater.PrimitiveRestartStateIndex);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BufferRange vertexDataRange = _vacContext.GetVertexDataBufferRange(_vertexDataOffset, _vertexDataSize);
|
||||||
|
|
||||||
|
_context.Renderer.Pipeline.SetProgram(_vertexPassthroughProgram);
|
||||||
|
_context.Renderer.Pipeline.SetStorageBuffers(stackalloc[] { new BufferAssignment(vertexDataBinding, vertexDataRange) });
|
||||||
|
_context.Renderer.Pipeline.Draw(_count, _instanceCount, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a strip primitive topology from the vertices per primitive count.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="verticesPerPrimitive">Vertices per primitive count</param>
|
||||||
|
/// <returns>Primitive topology</returns>
|
||||||
|
private static PrimitiveTopology GetGeometryOutputTopology(int verticesPerPrimitive)
|
||||||
|
{
|
||||||
|
return verticesPerPrimitive switch
|
||||||
|
{
|
||||||
|
3 => PrimitiveTopology.TriangleStrip,
|
||||||
|
2 => PrimitiveTopology.LineStrip,
|
||||||
|
_ => PrimitiveTopology.Points,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the maximum number of complete primitive strips for a vertex count.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="verticesPerPrimitive">Vertices per primitive count</param>
|
||||||
|
/// <param name="maxOutputVertices">Maximum geometry shader output vertices count</param>
|
||||||
|
/// <returns>Maximum number of complete primitive strips</returns>
|
||||||
|
private static int GetMaxCompleteStrips(int verticesPerPrimitive, int maxOutputVertices)
|
||||||
|
{
|
||||||
|
return maxOutputVertices / verticesPerPrimitive;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Binds a dummy buffer as vertex buffer into a buffer texture.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="reservations">Shader resource binding reservations</param>
|
||||||
|
/// <param name="index">Buffer texture index</param>
|
||||||
|
/// <param name="format">Buffer texture format</param>
|
||||||
|
private readonly void SetDummyBufferTexture(ResourceReservations reservations, int index, Format format)
|
||||||
|
{
|
||||||
|
ITexture bufferTexture = _vacContext.EnsureBufferTexture(index + 2, format);
|
||||||
|
bufferTexture.SetStorage(_vacContext.GetDummyBufferRange());
|
||||||
|
|
||||||
|
_context.Renderer.Pipeline.SetTextureAndSampler(ShaderStage.Compute, reservations.GetVertexBufferTextureBinding(index), bufferTexture, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Binds a vertex buffer into a buffer texture.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="reservations">Shader resource binding reservations</param>
|
||||||
|
/// <param name="index">Buffer texture index</param>
|
||||||
|
/// <param name="format">Buffer texture format</param>
|
||||||
|
/// <param name="address">Address of the vertex buffer</param>
|
||||||
|
/// <param name="size">Size of the buffer in bytes</param>
|
||||||
|
private readonly void SetBufferTexture(ResourceReservations reservations, int index, Format format, ulong address, ulong size)
|
||||||
|
{
|
||||||
|
var memoryManager = _channel.MemoryManager;
|
||||||
|
|
||||||
|
address = memoryManager.Translate(address);
|
||||||
|
BufferRange range = memoryManager.Physical.BufferCache.GetBufferRange(address, size);
|
||||||
|
|
||||||
|
ITexture bufferTexture = _vacContext.EnsureBufferTexture(index + 2, format);
|
||||||
|
bufferTexture.SetStorage(range);
|
||||||
|
|
||||||
|
_context.Renderer.Pipeline.SetTextureAndSampler(ShaderStage.Compute, reservations.GetVertexBufferTextureBinding(index), bufferTexture, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Binds the index buffer into a buffer texture.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="reservations">Shader resource binding reservations</param>
|
||||||
|
/// <param name="firstIndex">First index of the index buffer</param>
|
||||||
|
/// <param name="count">Index count</param>
|
||||||
|
/// <param name="misalignedOffset">Offset that should be added when accessing the buffer texture on the shader</param>
|
||||||
|
private readonly void SetIndexBufferTexture(ResourceReservations reservations, int firstIndex, int count, out int misalignedOffset)
|
||||||
|
{
|
||||||
|
ulong address = _state.State.IndexBufferState.Address.Pack();
|
||||||
|
ulong indexOffset = (ulong)firstIndex;
|
||||||
|
ulong size = (ulong)count;
|
||||||
|
|
||||||
|
int shift = 0;
|
||||||
|
Format format = Format.R8Uint;
|
||||||
|
|
||||||
|
switch (_state.State.IndexBufferState.Type)
|
||||||
|
{
|
||||||
|
case IndexType.UShort:
|
||||||
|
shift = 1;
|
||||||
|
format = Format.R16Uint;
|
||||||
|
break;
|
||||||
|
case IndexType.UInt:
|
||||||
|
shift = 2;
|
||||||
|
format = Format.R32Uint;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
indexOffset <<= shift;
|
||||||
|
size <<= shift;
|
||||||
|
|
||||||
|
var memoryManager = _channel.MemoryManager;
|
||||||
|
|
||||||
|
address = memoryManager.Translate(address + indexOffset);
|
||||||
|
ulong misalign = address & ((ulong)_context.Capabilities.TextureBufferOffsetAlignment - 1);
|
||||||
|
BufferRange range = memoryManager.Physical.BufferCache.GetBufferRange(address - misalign, size + misalign);
|
||||||
|
misalignedOffset = (int)misalign >> shift;
|
||||||
|
|
||||||
|
SetIndexBufferTexture(reservations, range, format);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the host buffer texture for the index buffer.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="reservations">Shader resource binding reservations</param>
|
||||||
|
/// <param name="range">Index buffer range</param>
|
||||||
|
/// <param name="format">Index buffer format</param>
|
||||||
|
private readonly void SetIndexBufferTexture(ResourceReservations reservations, BufferRange range, Format format)
|
||||||
|
{
|
||||||
|
ITexture bufferTexture = _vacContext.EnsureBufferTexture(0, format);
|
||||||
|
bufferTexture.SetStorage(range);
|
||||||
|
|
||||||
|
_context.Renderer.Pipeline.SetTextureAndSampler(ShaderStage.Compute, reservations.IndexBufferTextureBinding, bufferTexture, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the host buffer texture for the topology remap buffer.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="reservations">Shader resource binding reservations</param>
|
||||||
|
/// <param name="topology">Input topology</param>
|
||||||
|
/// <param name="count">Input vertex count</param>
|
||||||
|
private readonly void SetTopologyRemapBufferTexture(ResourceReservations reservations, PrimitiveTopology topology, int count)
|
||||||
|
{
|
||||||
|
ITexture bufferTexture = _vacContext.EnsureBufferTexture(1, Format.R32Uint);
|
||||||
|
bufferTexture.SetStorage(_vacContext.GetOrCreateTopologyRemapBuffer(topology, count));
|
||||||
|
|
||||||
|
_context.Renderer.Pipeline.SetTextureAndSampler(ShaderStage.Compute, reservations.TopologyRemapBufferTextureBinding, bufferTexture, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the host buffer texture to a generated sequential index buffer.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="reservations">Shader resource binding reservations</param>
|
||||||
|
/// <param name="count">Vertex count</param>
|
||||||
|
private readonly void SetSequentialIndexBufferTexture(ResourceReservations reservations, int count)
|
||||||
|
{
|
||||||
|
BufferHandle sequentialIndexBuffer = _vacContext.GetSequentialIndexBuffer(count);
|
||||||
|
|
||||||
|
ITexture bufferTexture = _vacContext.EnsureBufferTexture(0, Format.R32Uint);
|
||||||
|
bufferTexture.SetStorage(new BufferRange(sequentialIndexBuffer, 0, count * sizeof(uint)));
|
||||||
|
|
||||||
|
_context.Renderer.Pipeline.SetTextureAndSampler(ShaderStage.Compute, reservations.IndexBufferTextureBinding, bufferTexture, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the size of a vertex buffer based on the current 3D engine state.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="vbAddress">Vertex buffer address</param>
|
||||||
|
/// <param name="vbEndAddress">Vertex buffer end address (exclusive)</param>
|
||||||
|
/// <param name="vbStride">Vertex buffer stride</param>
|
||||||
|
/// <param name="indexed">Whether the draw is indexed</param>
|
||||||
|
/// <param name="instanced">Whether the draw is instanced</param>
|
||||||
|
/// <param name="firstVertex">First vertex index</param>
|
||||||
|
/// <param name="vertexCount">Vertex count</param>
|
||||||
|
/// <returns>Size of the vertex buffer, in bytes</returns>
|
||||||
|
private readonly ulong GetVertexBufferSize(ulong vbAddress, ulong vbEndAddress, int vbStride, bool indexed, bool instanced, int firstVertex, int vertexCount)
|
||||||
|
{
|
||||||
|
IndexType indexType = _state.State.IndexBufferState.Type;
|
||||||
|
bool indexTypeSmall = indexType == IndexType.UByte || indexType == IndexType.UShort;
|
||||||
|
ulong vbSize = vbEndAddress - vbAddress + 1;
|
||||||
|
ulong size;
|
||||||
|
|
||||||
|
if (indexed || vbStride == 0 || instanced)
|
||||||
|
{
|
||||||
|
// This size may be (much) larger than the real vertex buffer size.
|
||||||
|
// Avoid calculating it this way, unless we don't have any other option.
|
||||||
|
|
||||||
|
size = vbSize;
|
||||||
|
|
||||||
|
if (vbStride > 0 && indexTypeSmall && indexed && !instanced)
|
||||||
|
{
|
||||||
|
// If the index type is a small integer type, then we might be still able
|
||||||
|
// to reduce the vertex buffer size based on the maximum possible index value.
|
||||||
|
|
||||||
|
ulong maxVertexBufferSize = indexType == IndexType.UByte ? 0x100UL : 0x10000UL;
|
||||||
|
|
||||||
|
maxVertexBufferSize += _state.State.FirstVertex;
|
||||||
|
maxVertexBufferSize *= (uint)vbStride;
|
||||||
|
|
||||||
|
size = Math.Min(size, maxVertexBufferSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// For non-indexed draws, we can guess the size from the vertex count
|
||||||
|
// and stride.
|
||||||
|
|
||||||
|
int firstInstance = (int)_state.State.FirstInstance;
|
||||||
|
|
||||||
|
size = Math.Min(vbSize, (ulong)((firstInstance + firstVertex + vertexCount) * vbStride));
|
||||||
|
}
|
||||||
|
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Aligns a size to a given alignment value.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="size">Size</param>
|
||||||
|
/// <param name="alignment">Alignment</param>
|
||||||
|
/// <returns>Aligned size</returns>
|
||||||
|
private static ulong Align(ulong size, int alignment)
|
||||||
|
{
|
||||||
|
ulong align = (ulong)alignment;
|
||||||
|
|
||||||
|
size += align - 1;
|
||||||
|
|
||||||
|
size /= align;
|
||||||
|
size *= align;
|
||||||
|
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
using Ryujinx.Graphics.GAL;
|
using Ryujinx.Graphics.GAL;
|
||||||
|
using Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw;
|
||||||
using Ryujinx.Graphics.Gpu.Engine.Types;
|
using Ryujinx.Graphics.Gpu.Engine.Types;
|
||||||
using Ryujinx.Graphics.Gpu.Memory;
|
using Ryujinx.Graphics.Gpu.Memory;
|
||||||
using System;
|
using System;
|
||||||
|
@ -8,7 +9,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Draw manager.
|
/// Draw manager.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
class DrawManager
|
class DrawManager : IDisposable
|
||||||
{
|
{
|
||||||
// Since we don't know the index buffer size for indirect draws,
|
// Since we don't know the index buffer size for indirect draws,
|
||||||
// we must assume a minimum and maximum size and use that for buffer data update purposes.
|
// we must assume a minimum and maximum size and use that for buffer data update purposes.
|
||||||
|
@ -20,6 +21,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||||
private readonly DeviceStateWithShadow<ThreedClassState> _state;
|
private readonly DeviceStateWithShadow<ThreedClassState> _state;
|
||||||
private readonly DrawState _drawState;
|
private readonly DrawState _drawState;
|
||||||
private readonly SpecializationStateUpdater _currentSpecState;
|
private readonly SpecializationStateUpdater _currentSpecState;
|
||||||
|
private readonly VtgAsCompute _vtgAsCompute;
|
||||||
private bool _topologySet;
|
private bool _topologySet;
|
||||||
|
|
||||||
private bool _instancedDrawPending;
|
private bool _instancedDrawPending;
|
||||||
|
@ -53,6 +55,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||||
_state = state;
|
_state = state;
|
||||||
_drawState = drawState;
|
_drawState = drawState;
|
||||||
_currentSpecState = spec;
|
_currentSpecState = spec;
|
||||||
|
_vtgAsCompute = new(context, channel, state);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -127,7 +130,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||||
{
|
{
|
||||||
if (renderEnable == ConditionalRenderEnabled.False)
|
if (renderEnable == ConditionalRenderEnabled.False)
|
||||||
{
|
{
|
||||||
PerformDeferredDraws();
|
PerformDeferredDraws(engine);
|
||||||
}
|
}
|
||||||
|
|
||||||
_drawState.DrawIndexed = false;
|
_drawState.DrawIndexed = false;
|
||||||
|
@ -190,13 +193,13 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||||
|
|
||||||
_channel.BufferManager.SetIndexBuffer(br, IndexType.UInt);
|
_channel.BufferManager.SetIndexBuffer(br, IndexType.UInt);
|
||||||
|
|
||||||
_context.Renderer.Pipeline.DrawIndexed(inlineIndexCount, 1, firstIndex, firstVertex, firstInstance);
|
DrawImpl(engine, inlineIndexCount, 1, firstIndex, firstVertex, firstInstance, indexed: true);
|
||||||
}
|
}
|
||||||
else if (_drawState.DrawIndexed)
|
else if (_drawState.DrawIndexed)
|
||||||
{
|
{
|
||||||
int firstVertex = (int)_state.State.FirstVertex;
|
int firstVertex = (int)_state.State.FirstVertex;
|
||||||
|
|
||||||
_context.Renderer.Pipeline.DrawIndexed(indexCount, 1, firstIndex, firstVertex, firstInstance);
|
DrawImpl(engine, indexCount, 1, firstIndex, firstVertex, firstInstance, indexed: true);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -204,7 +207,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||||
var drawState = _state.State.VertexBufferDrawState;
|
var drawState = _state.State.VertexBufferDrawState;
|
||||||
#pragma warning restore IDE0059
|
#pragma warning restore IDE0059
|
||||||
|
|
||||||
_context.Renderer.Pipeline.Draw(drawVertexCount, 1, drawFirstVertex, firstInstance);
|
DrawImpl(engine, drawVertexCount, 1, 0, drawFirstVertex, firstInstance, indexed: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
_drawState.DrawIndexed = false;
|
_drawState.DrawIndexed = false;
|
||||||
|
@ -219,24 +222,26 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||||
/// Starts draw.
|
/// Starts draw.
|
||||||
/// This sets primitive type and instanced draw parameters.
|
/// This sets primitive type and instanced draw parameters.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="engine">3D engine where this method is being called</param>
|
||||||
/// <param name="argument">Method call argument</param>
|
/// <param name="argument">Method call argument</param>
|
||||||
public void DrawBegin(int argument)
|
public void DrawBegin(ThreedClass engine, int argument)
|
||||||
{
|
{
|
||||||
bool incrementInstance = (argument & (1 << 26)) != 0;
|
bool incrementInstance = (argument & (1 << 26)) != 0;
|
||||||
bool resetInstance = (argument & (1 << 27)) == 0;
|
bool resetInstance = (argument & (1 << 27)) == 0;
|
||||||
|
|
||||||
PrimitiveType type = (PrimitiveType)(argument & 0xffff);
|
PrimitiveType type = (PrimitiveType)(argument & 0xffff);
|
||||||
DrawBegin(incrementInstance, resetInstance, type);
|
DrawBegin(engine, incrementInstance, resetInstance, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Starts draw.
|
/// Starts draw.
|
||||||
/// This sets primitive type and instanced draw parameters.
|
/// This sets primitive type and instanced draw parameters.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="engine">3D engine where this method is being called</param>
|
||||||
/// <param name="incrementInstance">Indicates if the current instance should be incremented</param>
|
/// <param name="incrementInstance">Indicates if the current instance should be incremented</param>
|
||||||
/// <param name="resetInstance">Indicates if the current instance should be set to zero</param>
|
/// <param name="resetInstance">Indicates if the current instance should be set to zero</param>
|
||||||
/// <param name="primitiveType">Primitive type</param>
|
/// <param name="primitiveType">Primitive type</param>
|
||||||
private void DrawBegin(bool incrementInstance, bool resetInstance, PrimitiveType primitiveType)
|
private void DrawBegin(ThreedClass engine, bool incrementInstance, bool resetInstance, PrimitiveType primitiveType)
|
||||||
{
|
{
|
||||||
if (incrementInstance)
|
if (incrementInstance)
|
||||||
{
|
{
|
||||||
|
@ -244,7 +249,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||||
}
|
}
|
||||||
else if (resetInstance)
|
else if (resetInstance)
|
||||||
{
|
{
|
||||||
PerformDeferredDraws();
|
PerformDeferredDraws(engine);
|
||||||
|
|
||||||
_instanceIndex = 0;
|
_instanceIndex = 0;
|
||||||
}
|
}
|
||||||
|
@ -364,7 +369,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||||
/// <param name="instanced">True to increment the current instance value, false otherwise</param>
|
/// <param name="instanced">True to increment the current instance value, false otherwise</param>
|
||||||
private void DrawIndexBufferBeginEndInstance(ThreedClass engine, int argument, bool instanced)
|
private void DrawIndexBufferBeginEndInstance(ThreedClass engine, int argument, bool instanced)
|
||||||
{
|
{
|
||||||
DrawBegin(instanced, !instanced, (PrimitiveType)((argument >> 28) & 0xf));
|
DrawBegin(engine, instanced, !instanced, (PrimitiveType)((argument >> 28) & 0xf));
|
||||||
|
|
||||||
int firstIndex = argument & 0xffff;
|
int firstIndex = argument & 0xffff;
|
||||||
int indexCount = (argument >> 16) & 0xfff;
|
int indexCount = (argument >> 16) & 0xfff;
|
||||||
|
@ -409,7 +414,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||||
/// <param name="instanced">True to increment the current instance value, false otherwise</param>
|
/// <param name="instanced">True to increment the current instance value, false otherwise</param>
|
||||||
private void DrawVertexArrayBeginEndInstance(ThreedClass engine, int argument, bool instanced)
|
private void DrawVertexArrayBeginEndInstance(ThreedClass engine, int argument, bool instanced)
|
||||||
{
|
{
|
||||||
DrawBegin(instanced, !instanced, (PrimitiveType)((argument >> 28) & 0xf));
|
DrawBegin(engine, instanced, !instanced, (PrimitiveType)((argument >> 28) & 0xf));
|
||||||
|
|
||||||
int firstVertex = argument & 0xffff;
|
int firstVertex = argument & 0xffff;
|
||||||
int vertexCount = (argument >> 16) & 0xfff;
|
int vertexCount = (argument >> 16) & 0xfff;
|
||||||
|
@ -541,23 +546,12 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||||
|
|
||||||
engine.UpdateState();
|
engine.UpdateState();
|
||||||
|
|
||||||
if (instanceCount > 1)
|
DrawImpl(engine, count, instanceCount, firstIndex, firstVertex, firstInstance, indexed);
|
||||||
{
|
|
||||||
// Must be called after UpdateState as it assumes the shader state
|
|
||||||
// has already been set, and that bindings have been updated already.
|
|
||||||
|
|
||||||
_channel.BufferManager.SetInstancedDrawVertexCount(count);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (indexed)
|
if (indexed)
|
||||||
{
|
{
|
||||||
_context.Renderer.Pipeline.DrawIndexed(count, instanceCount, firstIndex, firstVertex, firstInstance);
|
|
||||||
_state.State.FirstVertex = 0;
|
_state.State.FirstVertex = 0;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
_context.Renderer.Pipeline.Draw(count, instanceCount, firstVertex, firstInstance);
|
|
||||||
}
|
|
||||||
|
|
||||||
_state.State.FirstInstance = 0;
|
_state.State.FirstInstance = 0;
|
||||||
|
|
||||||
|
@ -569,6 +563,67 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Performs a indexed or non-indexed draw.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="engine">3D engine where this method is being called</param>
|
||||||
|
/// <param name="count">Index count for indexed draws, vertex count for non-indexed draws</param>
|
||||||
|
/// <param name="instanceCount">Instance count</param>
|
||||||
|
/// <param name="firstIndex">First index on the index buffer for indexed draws, ignored for non-indexed draws</param>
|
||||||
|
/// <param name="firstVertex">First vertex on the vertex buffer</param>
|
||||||
|
/// <param name="firstInstance">First instance</param>
|
||||||
|
/// <param name="indexed">True if the draw is indexed, false otherwise</param>
|
||||||
|
private void DrawImpl(
|
||||||
|
ThreedClass engine,
|
||||||
|
int count,
|
||||||
|
int instanceCount,
|
||||||
|
int firstIndex,
|
||||||
|
int firstVertex,
|
||||||
|
int firstInstance,
|
||||||
|
bool indexed)
|
||||||
|
{
|
||||||
|
if (instanceCount > 1)
|
||||||
|
{
|
||||||
|
_channel.BufferManager.SetInstancedDrawVertexCount(count);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_drawState.VertexAsCompute != null)
|
||||||
|
{
|
||||||
|
_vtgAsCompute.DrawAsCompute(
|
||||||
|
engine,
|
||||||
|
_drawState.VertexAsCompute,
|
||||||
|
_drawState.GeometryAsCompute,
|
||||||
|
_drawState.VertexPassthrough,
|
||||||
|
_drawState.Topology,
|
||||||
|
count,
|
||||||
|
instanceCount,
|
||||||
|
firstIndex,
|
||||||
|
firstVertex,
|
||||||
|
firstInstance,
|
||||||
|
indexed);
|
||||||
|
|
||||||
|
if (_drawState.GeometryAsCompute != null)
|
||||||
|
{
|
||||||
|
// Geometry draws need to change the topology, so we need to set it here again
|
||||||
|
// if we are going to do a regular draw.
|
||||||
|
// Would have been better to do that on the callee, but doing it here
|
||||||
|
// avoids having to pass the draw manager instance.
|
||||||
|
ForceStateDirty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (indexed)
|
||||||
|
{
|
||||||
|
_context.Renderer.Pipeline.DrawIndexed(count, instanceCount, firstIndex, firstVertex, firstInstance);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_context.Renderer.Pipeline.Draw(count, instanceCount, firstVertex, firstInstance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Performs a indirect draw, with parameters from a GPU buffer.
|
/// Performs a indirect draw, with parameters from a GPU buffer.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -667,43 +722,42 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||||
/// Once we detect the last instanced draw, then we perform the host instanced draw,
|
/// Once we detect the last instanced draw, then we perform the host instanced draw,
|
||||||
/// with the accumulated instance count.
|
/// with the accumulated instance count.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void PerformDeferredDraws()
|
/// <param name="engine">3D engine where this method is being called</param>
|
||||||
|
public void PerformDeferredDraws(ThreedClass engine)
|
||||||
{
|
{
|
||||||
// Perform any pending instanced draw.
|
// Perform any pending instanced draw.
|
||||||
if (_instancedDrawPending)
|
if (_instancedDrawPending)
|
||||||
{
|
{
|
||||||
_instancedDrawPending = false;
|
_instancedDrawPending = false;
|
||||||
|
|
||||||
|
int instanceCount = _instanceIndex + 1;
|
||||||
|
int firstInstance = _instancedFirstInstance;
|
||||||
bool indexedInline = _instancedIndexedInline;
|
bool indexedInline = _instancedIndexedInline;
|
||||||
|
|
||||||
if (_instancedIndexed || indexedInline)
|
if (_instancedIndexed || indexedInline)
|
||||||
{
|
{
|
||||||
|
int indexCount = _instancedIndexCount;
|
||||||
|
|
||||||
if (indexedInline)
|
if (indexedInline)
|
||||||
{
|
{
|
||||||
int inlineIndexCount = _drawState.IbStreamer.GetAndResetInlineIndexCount(_context.Renderer);
|
int inlineIndexCount = _drawState.IbStreamer.GetAndResetInlineIndexCount(_context.Renderer);
|
||||||
BufferRange br = new(_drawState.IbStreamer.GetInlineIndexBuffer(), 0, inlineIndexCount * 4);
|
BufferRange br = new(_drawState.IbStreamer.GetInlineIndexBuffer(), 0, inlineIndexCount * 4);
|
||||||
|
|
||||||
_channel.BufferManager.SetIndexBuffer(br, IndexType.UInt);
|
_channel.BufferManager.SetIndexBuffer(br, IndexType.UInt);
|
||||||
|
indexCount = inlineIndexCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
_channel.BufferManager.SetInstancedDrawVertexCount(_instancedIndexCount);
|
int firstIndex = _instancedFirstIndex;
|
||||||
|
int firstVertex = _instancedFirstVertex;
|
||||||
|
|
||||||
_context.Renderer.Pipeline.DrawIndexed(
|
DrawImpl(engine, indexCount, instanceCount, firstIndex, firstVertex, firstInstance, indexed: true);
|
||||||
_instancedIndexCount,
|
|
||||||
_instanceIndex + 1,
|
|
||||||
_instancedFirstIndex,
|
|
||||||
_instancedFirstVertex,
|
|
||||||
_instancedFirstInstance);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_channel.BufferManager.SetInstancedDrawVertexCount(_instancedDrawStateCount);
|
int vertexCount = _instancedDrawStateCount;
|
||||||
|
int firstVertex = _instancedDrawStateFirst;
|
||||||
|
|
||||||
_context.Renderer.Pipeline.Draw(
|
DrawImpl(engine, vertexCount, instanceCount, 0, firstVertex, firstInstance, indexed: false);
|
||||||
_instancedDrawStateCount,
|
|
||||||
_instanceIndex + 1,
|
|
||||||
_instancedDrawStateFirst,
|
|
||||||
_instancedFirstInstance);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -866,5 +920,19 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||||
_context.Renderer.Pipeline.EndHostConditionalRendering();
|
_context.Renderer.Pipeline.EndHostConditionalRendering();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected virtual void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
_vtgAsCompute.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using Ryujinx.Graphics.GAL;
|
using Ryujinx.Graphics.GAL;
|
||||||
|
using Ryujinx.Graphics.Gpu.Shader;
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||||
{
|
{
|
||||||
|
@ -61,5 +62,20 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||||
/// Index buffer data streamer for inline index buffer updates, such as those used in legacy OpenGL.
|
/// Index buffer data streamer for inline index buffer updates, such as those used in legacy OpenGL.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IbStreamer IbStreamer = new();
|
public IbStreamer IbStreamer = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If the vertex shader is emulated on compute, this should be set to the compute program, otherwise it should be null.
|
||||||
|
/// </summary>
|
||||||
|
public ShaderAsCompute VertexAsCompute;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If a geometry shader exists and is emulated on compute, this should be set to the compute program, otherwise it should be null.
|
||||||
|
/// </summary>
|
||||||
|
public ShaderAsCompute GeometryAsCompute;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If the vertex shader is emulated on compute, this should be set to the passthrough vertex program, otherwise it should be null.
|
||||||
|
/// </summary>
|
||||||
|
public IProgram VertexPassthrough;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -218,11 +218,13 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||||
{
|
{
|
||||||
bool changed = false;
|
bool changed = false;
|
||||||
ref Array32<AttributeType> attributeTypes = ref _graphics.AttributeTypes;
|
ref Array32<AttributeType> attributeTypes = ref _graphics.AttributeTypes;
|
||||||
bool supportsScaledFormats = _context.Capabilities.SupportsScaledVertexFormats;
|
bool mayConvertVtgToCompute = ShaderCache.MayConvertVtgToCompute(ref _context.Capabilities);
|
||||||
|
bool supportsScaledFormats = _context.Capabilities.SupportsScaledVertexFormats && !mayConvertVtgToCompute;
|
||||||
|
|
||||||
for (int location = 0; location < state.Length; location++)
|
for (int location = 0; location < state.Length; location++)
|
||||||
{
|
{
|
||||||
VertexAttribType type = state[location].UnpackType();
|
VertexAttribType type = state[location].UnpackType();
|
||||||
|
VertexAttribSize size = state[location].UnpackSize();
|
||||||
|
|
||||||
AttributeType value;
|
AttributeType value;
|
||||||
|
|
||||||
|
@ -247,6 +249,18 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mayConvertVtgToCompute && (size == VertexAttribSize.Rgb10A2 || size == VertexAttribSize.Rg11B10))
|
||||||
|
{
|
||||||
|
value |= AttributeType.Packed;
|
||||||
|
|
||||||
|
if (type == VertexAttribType.Snorm ||
|
||||||
|
type == VertexAttribType.Sint ||
|
||||||
|
type == VertexAttribType.Sscaled)
|
||||||
|
{
|
||||||
|
value |= AttributeType.PackedRgb10A2Signed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (attributeTypes[location] != value)
|
if (attributeTypes[location] != value)
|
||||||
{
|
{
|
||||||
attributeTypes[location] = value;
|
attributeTypes[location] = value;
|
||||||
|
|
|
@ -20,6 +20,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||||
public const int RasterizerStateIndex = 15;
|
public const int RasterizerStateIndex = 15;
|
||||||
public const int ScissorStateIndex = 16;
|
public const int ScissorStateIndex = 16;
|
||||||
public const int VertexBufferStateIndex = 0;
|
public const int VertexBufferStateIndex = 0;
|
||||||
|
public const int IndexBufferStateIndex = 23;
|
||||||
public const int PrimitiveRestartStateIndex = 12;
|
public const int PrimitiveRestartStateIndex = 12;
|
||||||
public const int RenderTargetStateIndex = 27;
|
public const int RenderTargetStateIndex = 27;
|
||||||
|
|
||||||
|
@ -290,7 +291,13 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||||
// of the shader for the new state.
|
// of the shader for the new state.
|
||||||
if (_shaderSpecState != null && _currentSpecState.HasChanged())
|
if (_shaderSpecState != null && _currentSpecState.HasChanged())
|
||||||
{
|
{
|
||||||
if (!_shaderSpecState.MatchesGraphics(_channel, ref _currentSpecState.GetPoolState(), ref _currentSpecState.GetGraphicsState(), _vsUsesDrawParameters, false))
|
if (!_shaderSpecState.MatchesGraphics(
|
||||||
|
_channel,
|
||||||
|
ref _currentSpecState.GetPoolState(),
|
||||||
|
ref _currentSpecState.GetGraphicsState(),
|
||||||
|
_drawState.VertexAsCompute != null,
|
||||||
|
_vsUsesDrawParameters,
|
||||||
|
checkTextures: false))
|
||||||
{
|
{
|
||||||
// Shader must be reloaded. _vtgWritesRtLayer should not change.
|
// Shader must be reloaded. _vtgWritesRtLayer should not change.
|
||||||
UpdateShaderState();
|
UpdateShaderState();
|
||||||
|
@ -1453,6 +1460,19 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||||
_fsReadsFragCoord = false;
|
_fsReadsFragCoord = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (gs.VertexAsCompute != null)
|
||||||
|
{
|
||||||
|
_drawState.VertexAsCompute = gs.VertexAsCompute;
|
||||||
|
_drawState.GeometryAsCompute = gs.GeometryAsCompute;
|
||||||
|
_drawState.VertexPassthrough = gs.HostProgram;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_drawState.VertexAsCompute = null;
|
||||||
|
_drawState.GeometryAsCompute = null;
|
||||||
|
_drawState.VertexPassthrough = null;
|
||||||
|
}
|
||||||
|
|
||||||
_context.Renderer.Pipeline.SetProgram(gs.HostProgram);
|
_context.Renderer.Pipeline.SetProgram(gs.HostProgram);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1540,5 +1560,14 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||||
{
|
{
|
||||||
_updateTracker.ForceDirty(ShaderStateIndex);
|
_updateTracker.ForceDirty(ShaderStateIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Forces a register group as dirty, by index.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="groupIndex">Index of the group to be dirtied</param>
|
||||||
|
public void ForceDirty(int groupIndex)
|
||||||
|
{
|
||||||
|
_updateTracker.ForceDirty(groupIndex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a 3D engine class.
|
/// Represents a 3D engine class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
class ThreedClass : IDeviceState
|
class ThreedClass : IDeviceState, IDisposable
|
||||||
{
|
{
|
||||||
private readonly GpuContext _context;
|
private readonly GpuContext _context;
|
||||||
private readonly GPFifoClass _fifoClass;
|
private readonly GPFifoClass _fifoClass;
|
||||||
|
@ -178,6 +178,15 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||||
_stateUpdater.SetDirty(offset);
|
_stateUpdater.SetDirty(offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Marks the specified register range for a group index as dirty, forcing the associated state to update on the next draw.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="groupIndex">Index of the group to dirty</param>
|
||||||
|
public void ForceStateDirtyByIndex(int groupIndex)
|
||||||
|
{
|
||||||
|
_stateUpdater.ForceDirty(groupIndex);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Forces the shaders to be rebound on the next draw.
|
/// Forces the shaders to be rebound on the next draw.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -207,7 +216,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void PerformDeferredDraws()
|
public void PerformDeferredDraws()
|
||||||
{
|
{
|
||||||
_drawManager.PerformDeferredDraws();
|
_drawManager.PerformDeferredDraws(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -402,7 +411,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||||
/// <param name="argument">Method call argument</param>
|
/// <param name="argument">Method call argument</param>
|
||||||
private void DrawBegin(int argument)
|
private void DrawBegin(int argument)
|
||||||
{
|
{
|
||||||
_drawManager.DrawBegin(argument);
|
_drawManager.DrawBegin(this, argument);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -617,5 +626,19 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||||
{
|
{
|
||||||
_drawManager.Clear(this, argument, layerCount);
|
_drawManager.Clear(this, argument, layerCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected virtual void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
_drawManager.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -135,6 +135,7 @@ namespace Ryujinx.Graphics.Gpu
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void Destroy()
|
private void Destroy()
|
||||||
{
|
{
|
||||||
|
_processor.Dispose();
|
||||||
TextureManager.Dispose();
|
TextureManager.Dispose();
|
||||||
|
|
||||||
var oldMemoryManager = Interlocked.Exchange(ref _memoryManager, null);
|
var oldMemoryManager = Interlocked.Exchange(ref _memoryManager, null);
|
||||||
|
|
|
@ -557,6 +557,91 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
};
|
};
|
||||||
#pragma warning restore IDE0055
|
#pragma warning restore IDE0055
|
||||||
|
|
||||||
|
// Note: Some of those formats have been changed and requires conversion on the shader,
|
||||||
|
// as GPUs don't support them when used as buffer texture format.
|
||||||
|
private static readonly Dictionary<VertexAttributeFormat, (Format, int)> _singleComponentAttribFormats = new()
|
||||||
|
{
|
||||||
|
{ VertexAttributeFormat.R8Unorm, (Format.R8Unorm, 1) },
|
||||||
|
{ VertexAttributeFormat.R8Snorm, (Format.R8Snorm, 1) },
|
||||||
|
{ VertexAttributeFormat.R8Uint, (Format.R8Uint, 1) },
|
||||||
|
{ VertexAttributeFormat.R8Sint, (Format.R8Sint, 1) },
|
||||||
|
{ VertexAttributeFormat.R16Float, (Format.R16Float, 1) },
|
||||||
|
{ VertexAttributeFormat.R16Unorm, (Format.R16Unorm, 1) },
|
||||||
|
{ VertexAttributeFormat.R16Snorm, (Format.R16Snorm, 1) },
|
||||||
|
{ VertexAttributeFormat.R16Uint, (Format.R16Uint, 1) },
|
||||||
|
{ VertexAttributeFormat.R16Sint, (Format.R16Sint, 1) },
|
||||||
|
{ VertexAttributeFormat.R32Float, (Format.R32Float, 1) },
|
||||||
|
{ VertexAttributeFormat.R32Uint, (Format.R32Uint, 1) },
|
||||||
|
{ VertexAttributeFormat.R32Sint, (Format.R32Sint, 1) },
|
||||||
|
{ VertexAttributeFormat.R8G8Unorm, (Format.R8Unorm, 2) },
|
||||||
|
{ VertexAttributeFormat.R8G8Snorm, (Format.R8Snorm, 2) },
|
||||||
|
{ VertexAttributeFormat.R8G8Uint, (Format.R8Uint, 2) },
|
||||||
|
{ VertexAttributeFormat.R8G8Sint, (Format.R8Sint, 2) },
|
||||||
|
{ VertexAttributeFormat.R16G16Float, (Format.R16Float, 2) },
|
||||||
|
{ VertexAttributeFormat.R16G16Unorm, (Format.R16Unorm, 2) },
|
||||||
|
{ VertexAttributeFormat.R16G16Snorm, (Format.R16Snorm, 2) },
|
||||||
|
{ VertexAttributeFormat.R16G16Uint, (Format.R16Uint, 2) },
|
||||||
|
{ VertexAttributeFormat.R16G16Sint, (Format.R16Sint, 2) },
|
||||||
|
{ VertexAttributeFormat.R32G32Float, (Format.R32Float, 2) },
|
||||||
|
{ VertexAttributeFormat.R32G32Uint, (Format.R32Uint, 2) },
|
||||||
|
{ VertexAttributeFormat.R32G32Sint, (Format.R32Sint, 2) },
|
||||||
|
{ VertexAttributeFormat.R8G8B8Unorm, (Format.R8Unorm, 3) },
|
||||||
|
{ VertexAttributeFormat.R8G8B8Snorm, (Format.R8Snorm, 3) },
|
||||||
|
{ VertexAttributeFormat.R8G8B8Uint, (Format.R8Uint, 3) },
|
||||||
|
{ VertexAttributeFormat.R8G8B8Sint, (Format.R8Sint, 3) },
|
||||||
|
{ VertexAttributeFormat.R16G16B16Float, (Format.R16Float, 3) },
|
||||||
|
{ VertexAttributeFormat.R16G16B16Unorm, (Format.R16Unorm, 3) },
|
||||||
|
{ VertexAttributeFormat.R16G16B16Snorm, (Format.R16Snorm, 3) },
|
||||||
|
{ VertexAttributeFormat.R16G16B16Uint, (Format.R16Uint, 3) },
|
||||||
|
{ VertexAttributeFormat.R16G16B16Sint, (Format.R16Sint, 3) },
|
||||||
|
{ VertexAttributeFormat.R32G32B32Float, (Format.R32Float, 3) },
|
||||||
|
{ VertexAttributeFormat.R32G32B32Uint, (Format.R32Uint, 3) },
|
||||||
|
{ VertexAttributeFormat.R32G32B32Sint, (Format.R32Sint, 3) },
|
||||||
|
{ VertexAttributeFormat.R8G8B8A8Unorm, (Format.R8Unorm, 4) },
|
||||||
|
{ VertexAttributeFormat.R8G8B8A8Snorm, (Format.R8Snorm, 4) },
|
||||||
|
{ VertexAttributeFormat.R8G8B8A8Uint, (Format.R8Uint, 4) },
|
||||||
|
{ VertexAttributeFormat.R8G8B8A8Sint, (Format.R8Sint, 4) },
|
||||||
|
{ VertexAttributeFormat.R16G16B16A16Float, (Format.R16Float, 4) },
|
||||||
|
{ VertexAttributeFormat.R16G16B16A16Unorm, (Format.R16Unorm, 4) },
|
||||||
|
{ VertexAttributeFormat.R16G16B16A16Snorm, (Format.R16Snorm, 4) },
|
||||||
|
{ VertexAttributeFormat.R16G16B16A16Uint, (Format.R16Uint, 4) },
|
||||||
|
{ VertexAttributeFormat.R16G16B16A16Sint, (Format.R16Sint, 4) },
|
||||||
|
{ VertexAttributeFormat.R32G32B32A32Float, (Format.R32Float, 4) },
|
||||||
|
{ VertexAttributeFormat.R32G32B32A32Uint, (Format.R32Uint, 4) },
|
||||||
|
{ VertexAttributeFormat.R32G32B32A32Sint, (Format.R32Sint, 4) },
|
||||||
|
{ VertexAttributeFormat.A2B10G10R10Unorm, (Format.R10G10B10A2Unorm, 4) },
|
||||||
|
{ VertexAttributeFormat.A2B10G10R10Uint, (Format.R10G10B10A2Uint, 4) },
|
||||||
|
{ VertexAttributeFormat.B10G11R11Float, (Format.R11G11B10Float, 3) },
|
||||||
|
{ VertexAttributeFormat.R8Uscaled, (Format.R8Uint, 1) }, // Uscaled -> Uint
|
||||||
|
{ VertexAttributeFormat.R8Sscaled, (Format.R8Sint, 1) }, // Sscaled -> Sint
|
||||||
|
{ VertexAttributeFormat.R16Uscaled, (Format.R16Uint, 1) }, // Uscaled -> Uint
|
||||||
|
{ VertexAttributeFormat.R16Sscaled, (Format.R16Sint, 1) }, // Sscaled -> Sint
|
||||||
|
{ VertexAttributeFormat.R32Uscaled, (Format.R32Uint, 1) }, // Uscaled -> Uint
|
||||||
|
{ VertexAttributeFormat.R32Sscaled, (Format.R32Sint, 1) }, // Sscaled -> Sint
|
||||||
|
{ VertexAttributeFormat.R8G8Uscaled, (Format.R8Uint, 2) }, // Uscaled -> Uint
|
||||||
|
{ VertexAttributeFormat.R8G8Sscaled, (Format.R8Sint, 2) }, // Sscaled -> Sint
|
||||||
|
{ VertexAttributeFormat.R16G16Uscaled, (Format.R16Uint, 2) }, // Uscaled -> Uint
|
||||||
|
{ VertexAttributeFormat.R16G16Sscaled, (Format.R16Sint, 2) }, // Sscaled -> Sint
|
||||||
|
{ VertexAttributeFormat.R32G32Uscaled, (Format.R32Uint, 2) }, // Uscaled -> Uint
|
||||||
|
{ VertexAttributeFormat.R32G32Sscaled, (Format.R32Sint, 2) }, // Sscaled -> Sint
|
||||||
|
{ VertexAttributeFormat.R8G8B8Uscaled, (Format.R8Uint, 3) }, // Uscaled -> Uint
|
||||||
|
{ VertexAttributeFormat.R8G8B8Sscaled, (Format.R8Sint, 3) }, // Sscaled -> Sint
|
||||||
|
{ VertexAttributeFormat.R16G16B16Uscaled, (Format.R16Uint, 3) }, // Uscaled -> Uint
|
||||||
|
{ VertexAttributeFormat.R16G16B16Sscaled, (Format.R16Sint, 3) }, // Sscaled -> Sint
|
||||||
|
{ VertexAttributeFormat.R32G32B32Uscaled, (Format.R32Uint, 3) }, // Uscaled -> Uint
|
||||||
|
{ VertexAttributeFormat.R32G32B32Sscaled, (Format.R32Sint , 3) }, // Sscaled -> Sint
|
||||||
|
{ VertexAttributeFormat.R8G8B8A8Uscaled, (Format.R8Uint, 4) }, // Uscaled -> Uint
|
||||||
|
{ VertexAttributeFormat.R8G8B8A8Sscaled, (Format.R8Sint, 4) }, // Sscaled -> Sint
|
||||||
|
{ VertexAttributeFormat.R16G16B16A16Uscaled, (Format.R16Uint, 4) }, // Uscaled -> Uint
|
||||||
|
{ VertexAttributeFormat.R16G16B16A16Sscaled, (Format.R16Sint, 4) }, // Sscaled -> Sint
|
||||||
|
{ VertexAttributeFormat.R32G32B32A32Uscaled, (Format.R32Uint, 4) }, // Uscaled -> Uint
|
||||||
|
{ VertexAttributeFormat.R32G32B32A32Sscaled, (Format.R32Sint, 4) }, // Sscaled -> Sint
|
||||||
|
{ VertexAttributeFormat.A2B10G10R10Snorm, (Format.R10G10B10A2Uint, 4) }, // Snorm -> Uint
|
||||||
|
{ VertexAttributeFormat.A2B10G10R10Sint, (Format.R10G10B10A2Uint, 4) }, // Sint -> Uint
|
||||||
|
{ VertexAttributeFormat.A2B10G10R10Uscaled, (Format.R10G10B10A2Uint, 4) }, // Uscaled -> Uint
|
||||||
|
{ VertexAttributeFormat.A2B10G10R10Sscaled, (Format.R10G10B10A2Sint, 4) } // Sscaled -> Sint
|
||||||
|
};
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Try getting the texture format from an encoded format integer from the Maxwell texture descriptor.
|
/// Try getting the texture format from an encoded format integer from the Maxwell texture descriptor.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -581,5 +666,22 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
{
|
{
|
||||||
return _attribFormats.TryGetValue((VertexAttributeFormat)encoded, out format);
|
return _attribFormats.TryGetValue((VertexAttributeFormat)encoded, out format);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Try getting a single component vertex attribute format from an encoded format integer from Maxwell attribute registers.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="encoded">The encoded format integer from the attribute registers</param>
|
||||||
|
/// <param name="format">The output single component vertex attribute format</param>
|
||||||
|
/// <param name="componentsCount">Number of components that the format has</param>
|
||||||
|
/// <returns>True if the format is valid, false otherwise</returns>
|
||||||
|
public static bool TryGetSingleComponentAttribFormat(uint encoded, out Format format, out int componentsCount)
|
||||||
|
{
|
||||||
|
bool result = _singleComponentAttribFormats.TryGetValue((VertexAttributeFormat)encoded, out var tuple);
|
||||||
|
|
||||||
|
format = tuple.Item1;
|
||||||
|
componentsCount = tuple.Item2;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,6 @@ using Ryujinx.Graphics.Shader;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.Gpu.Memory
|
namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
{
|
{
|
||||||
|
@ -15,9 +14,6 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
/// </summary>
|
/// </summary>
|
||||||
class BufferManager
|
class BufferManager
|
||||||
{
|
{
|
||||||
private const int TfInfoVertexCountOffset = Constants.TotalTransformFeedbackBuffers * sizeof(int);
|
|
||||||
private const int TfInfoBufferSize = TfInfoVertexCountOffset + sizeof(int);
|
|
||||||
|
|
||||||
private readonly GpuContext _context;
|
private readonly GpuContext _context;
|
||||||
private readonly GpuChannel _channel;
|
private readonly GpuChannel _channel;
|
||||||
|
|
||||||
|
@ -104,9 +100,6 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
private readonly BuffersPerStage[] _gpStorageBuffers;
|
private readonly BuffersPerStage[] _gpStorageBuffers;
|
||||||
private readonly BuffersPerStage[] _gpUniformBuffers;
|
private readonly BuffersPerStage[] _gpUniformBuffers;
|
||||||
|
|
||||||
private BufferHandle _tfInfoBuffer;
|
|
||||||
private readonly int[] _tfInfoData;
|
|
||||||
|
|
||||||
private bool _gpStorageBuffersDirty;
|
private bool _gpStorageBuffersDirty;
|
||||||
private bool _gpUniformBuffersDirty;
|
private bool _gpUniformBuffersDirty;
|
||||||
|
|
||||||
|
@ -146,11 +139,6 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
_bufferTextures = new List<BufferTextureBinding>();
|
_bufferTextures = new List<BufferTextureBinding>();
|
||||||
|
|
||||||
_ranges = new BufferAssignment[Constants.TotalGpUniformBuffers * Constants.ShaderStages];
|
_ranges = new BufferAssignment[Constants.TotalGpUniformBuffers * Constants.ShaderStages];
|
||||||
|
|
||||||
if (!context.Capabilities.SupportsTransformFeedback)
|
|
||||||
{
|
|
||||||
_tfInfoData = new int[Constants.TotalTransformFeedbackBuffers];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -339,13 +327,10 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
/// <param name="vertexCount">Vertex count per instance</param>
|
/// <param name="vertexCount">Vertex count per instance</param>
|
||||||
public void SetInstancedDrawVertexCount(int vertexCount)
|
public void SetInstancedDrawVertexCount(int vertexCount)
|
||||||
{
|
{
|
||||||
if (!_context.Capabilities.SupportsTransformFeedback &&
|
if (!_context.Capabilities.SupportsTransformFeedback && HasTransformFeedbackOutputs)
|
||||||
HasTransformFeedbackOutputs &&
|
|
||||||
_tfInfoBuffer != BufferHandle.Null)
|
|
||||||
{
|
{
|
||||||
Span<byte> data = stackalloc byte[sizeof(int)];
|
_context.SupportBufferUpdater.SetTfeVertexCount(vertexCount);
|
||||||
MemoryMarshal.Cast<byte, int>(data)[0] = vertexCount;
|
_context.SupportBufferUpdater.Commit();
|
||||||
_context.Renderer.SetBufferData(_tfInfoBuffer, TfInfoVertexCountOffset, data);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -607,17 +592,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
}
|
}
|
||||||
else if (HasTransformFeedbackOutputs)
|
else if (HasTransformFeedbackOutputs)
|
||||||
{
|
{
|
||||||
Span<int> info = _tfInfoData.AsSpan();
|
Span<BufferAssignment> buffers = stackalloc BufferAssignment[Constants.TotalTransformFeedbackBuffers];
|
||||||
Span<BufferAssignment> buffers = stackalloc BufferAssignment[Constants.TotalTransformFeedbackBuffers + 1];
|
|
||||||
|
|
||||||
bool needsDataUpdate = false;
|
|
||||||
|
|
||||||
if (_tfInfoBuffer == BufferHandle.Null)
|
|
||||||
{
|
|
||||||
_tfInfoBuffer = _context.Renderer.CreateBuffer(TfInfoBufferSize, BufferAccess.Stream);
|
|
||||||
}
|
|
||||||
|
|
||||||
buffers[0] = new BufferAssignment(0, new BufferRange(_tfInfoBuffer, 0, TfInfoBufferSize));
|
|
||||||
|
|
||||||
int alignment = _context.Capabilities.StorageBufferOffsetAlignment;
|
int alignment = _context.Capabilities.StorageBufferOffsetAlignment;
|
||||||
|
|
||||||
|
@ -627,7 +602,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
|
|
||||||
if (tfb.Address == 0)
|
if (tfb.Address == 0)
|
||||||
{
|
{
|
||||||
buffers[1 + index] = new BufferAssignment(1 + index, BufferRange.Empty);
|
buffers[index] = new BufferAssignment(index, BufferRange.Empty);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -637,22 +612,12 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
|
|
||||||
int tfeOffset = ((int)tfb.Address & (alignment - 1)) / 4;
|
int tfeOffset = ((int)tfb.Address & (alignment - 1)) / 4;
|
||||||
|
|
||||||
if (info[index] != tfeOffset)
|
_context.SupportBufferUpdater.SetTfeOffset(index, tfeOffset);
|
||||||
{
|
|
||||||
info[index] = tfeOffset;
|
|
||||||
needsDataUpdate = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
buffers[1 + index] = new BufferAssignment(1 + index, bufferCache.GetBufferRange(address, size, write: true));
|
buffers[index] = new BufferAssignment(index, bufferCache.GetBufferRange(address, size, write: true));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (needsDataUpdate)
|
|
||||||
{
|
|
||||||
Span<byte> infoData = MemoryMarshal.Cast<int, byte>(info);
|
|
||||||
_context.Renderer.SetBufferData(_tfInfoBuffer, 0, infoData);
|
|
||||||
}
|
|
||||||
|
|
||||||
_context.Renderer.Pipeline.SetStorageBuffers(buffers);
|
_context.Renderer.Pipeline.SetStorageBuffers(buffers);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
123
src/Ryujinx.Graphics.Gpu/Memory/BufferUpdater.cs
Normal file
123
src/Ryujinx.Graphics.Gpu/Memory/BufferUpdater.cs
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
using Ryujinx.Graphics.GAL;
|
||||||
|
using Ryujinx.Graphics.Shader;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Buffer data updater.
|
||||||
|
/// </summary>
|
||||||
|
class BufferUpdater : IDisposable
|
||||||
|
{
|
||||||
|
private BufferHandle _handle;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handle of the buffer.
|
||||||
|
/// </summary>
|
||||||
|
public BufferHandle Handle => _handle;
|
||||||
|
|
||||||
|
private readonly IRenderer _renderer;
|
||||||
|
private int _startOffset = -1;
|
||||||
|
private int _endOffset = -1;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new instance of the buffer updater.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="renderer">Renderer that the buffer will be used with</param>
|
||||||
|
public BufferUpdater(IRenderer renderer)
|
||||||
|
{
|
||||||
|
_renderer = renderer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mark a region of the buffer as modified and needing to be sent to the GPU.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="startOffset">Start offset of the region in bytes</param>
|
||||||
|
/// <param name="byteSize">Size of the region in bytes</param>
|
||||||
|
protected 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Submits all pending buffer updates to the GPU.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="data">All data that should be sent to the GPU. Only the modified regions will be updated</param>
|
||||||
|
/// <param name="binding">Optional binding to bind the buffer if a new buffer was created</param>
|
||||||
|
protected void Commit(ReadOnlySpan<byte> data, int binding = -1)
|
||||||
|
{
|
||||||
|
if (_startOffset != -1)
|
||||||
|
{
|
||||||
|
if (_handle == BufferHandle.Null)
|
||||||
|
{
|
||||||
|
_handle = _renderer.CreateBuffer(data.Length, BufferAccess.Stream);
|
||||||
|
_renderer.Pipeline.ClearBuffer(_handle, 0, data.Length, 0);
|
||||||
|
|
||||||
|
if (binding >= 0)
|
||||||
|
{
|
||||||
|
var range = new BufferRange(_handle, 0, data.Length);
|
||||||
|
_renderer.Pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(0, range) });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
_renderer.SetBufferData(_handle, _startOffset, data[_startOffset.._endOffset]);
|
||||||
|
|
||||||
|
_startOffset = -1;
|
||||||
|
_endOffset = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a reference to a given element of a vector.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="vector">Vector to get the element reference from</param>
|
||||||
|
/// <param name="elementIndex">Element index</param>
|
||||||
|
/// <returns>Reference to the specified element</returns>
|
||||||
|
protected static ref T GetElementRef<T>(ref Vector4<T> vector, int elementIndex)
|
||||||
|
{
|
||||||
|
switch (elementIndex)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
return ref vector.X;
|
||||||
|
case 1:
|
||||||
|
return ref vector.Y;
|
||||||
|
case 2:
|
||||||
|
return ref vector.Z;
|
||||||
|
case 3:
|
||||||
|
return ref vector.W;
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(elementIndex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Destroys the buffer.
|
||||||
|
/// </summary>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (_handle != BufferHandle.Null)
|
||||||
|
{
|
||||||
|
_renderer.DeleteBuffer(_handle);
|
||||||
|
_handle = BufferHandle.Null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,56 +9,21 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Support buffer data updater.
|
/// Support buffer data updater.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
class SupportBufferUpdater : IDisposable
|
class SupportBufferUpdater : BufferUpdater
|
||||||
{
|
{
|
||||||
private SupportBuffer _data;
|
private SupportBuffer _data;
|
||||||
private BufferHandle _handle;
|
|
||||||
|
|
||||||
private readonly IRenderer _renderer;
|
|
||||||
private int _startOffset = -1;
|
|
||||||
private int _endOffset = -1;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new instance of the support buffer updater.
|
/// Creates a new instance of the support buffer updater.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="renderer">Renderer that the support buffer will be used with</param>
|
/// <param name="renderer">Renderer that the support buffer will be used with</param>
|
||||||
public SupportBufferUpdater(IRenderer renderer)
|
public SupportBufferUpdater(IRenderer renderer) : base(renderer)
|
||||||
{
|
{
|
||||||
_renderer = renderer;
|
|
||||||
|
|
||||||
var defaultScale = new Vector4<float> { X = 1f, Y = 0f, Z = 0f, W = 0f };
|
var defaultScale = new Vector4<float> { X = 1f, Y = 0f, Z = 0f, W = 0f };
|
||||||
_data.RenderScale.AsSpan().Fill(defaultScale);
|
_data.RenderScale.AsSpan().Fill(defaultScale);
|
||||||
DirtyRenderScale(0, SupportBuffer.RenderScaleMaxCount);
|
DirtyRenderScale(0, SupportBuffer.RenderScaleMaxCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Mark a region of the support buffer as modified and needing to be sent to the GPU.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="startOffset">Start offset of the region in bytes</param>
|
|
||||||
/// <param name="byteSize">Size of the region in bytes</param>
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Marks the fragment render scale count as being modified.
|
/// Marks the fragment render scale count as being modified.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -220,40 +185,40 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Submits all pending buffer updates to the GPU.
|
/// Sets offset for the misaligned portion of a transform feedback buffer, and the buffer size, for transform feedback emulation.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Commit()
|
/// <param name="bufferIndex">Index of the transform feedback buffer</param>
|
||||||
|
/// <param name="offset">Misaligned offset of the buffer</param>
|
||||||
|
public void SetTfeOffset(int bufferIndex, int offset)
|
||||||
{
|
{
|
||||||
if (_startOffset != -1)
|
ref int currentOffset = ref GetElementRef(ref _data.TfeOffset, bufferIndex);
|
||||||
|
|
||||||
|
if (currentOffset != offset)
|
||||||
{
|
{
|
||||||
if (_handle == BufferHandle.Null)
|
currentOffset = offset;
|
||||||
{
|
MarkDirty(SupportBuffer.TfeOffsetOffset + bufferIndex * sizeof(int), sizeof(int));
|
||||||
_handle = _renderer.CreateBuffer(SupportBuffer.RequiredSize, BufferAccess.Stream);
|
|
||||||
_renderer.Pipeline.ClearBuffer(_handle, 0, SupportBuffer.RequiredSize, 0);
|
|
||||||
|
|
||||||
var range = new BufferRange(_handle, 0, SupportBuffer.RequiredSize);
|
|
||||||
_renderer.Pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(0, range) });
|
|
||||||
}
|
|
||||||
|
|
||||||
ReadOnlySpan<byte> data = MemoryMarshal.Cast<SupportBuffer, byte>(MemoryMarshal.CreateSpan(ref _data, 1));
|
|
||||||
|
|
||||||
_renderer.SetBufferData(_handle, _startOffset, data[_startOffset.._endOffset]);
|
|
||||||
|
|
||||||
_startOffset = -1;
|
|
||||||
_endOffset = -1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Destroys the support buffer.
|
/// Sets the vertex count used for transform feedback emulation with instanced draws.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Dispose()
|
/// <param name="vertexCount">Vertex count of the instanced draw</param>
|
||||||
|
public void SetTfeVertexCount(int vertexCount)
|
||||||
{
|
{
|
||||||
if (_handle != BufferHandle.Null)
|
if (_data.TfeVertexCount.X != vertexCount)
|
||||||
{
|
{
|
||||||
_renderer.DeleteBuffer(_handle);
|
_data.TfeVertexCount.X = vertexCount;
|
||||||
_handle = BufferHandle.Null;
|
MarkDirty(SupportBuffer.TfeVertexCountOffset, sizeof(int));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Submits all pending buffer updates to the GPU.
|
||||||
|
/// </summary>
|
||||||
|
public void Commit()
|
||||||
|
{
|
||||||
|
Commit(MemoryMarshal.Cast<SupportBuffer, byte>(MemoryMarshal.CreateSpan(ref _data, 1)), SupportBuffer.Binding);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,16 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IProgram HostProgram { get; }
|
public IProgram HostProgram { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Optional vertex shader converted to compute.
|
||||||
|
/// </summary>
|
||||||
|
public ShaderAsCompute VertexAsCompute { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Optional geometry shader converted to compute.
|
||||||
|
/// </summary>
|
||||||
|
public ShaderAsCompute GeometryAsCompute { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// GPU state used to create this version of the shader.
|
/// GPU state used to create this version of the shader.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -45,12 +55,25 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
Bindings = new CachedShaderBindings(shaders.Length == 1, shaders);
|
Bindings = new CachedShaderBindings(shaders.Length == 1, shaders);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public CachedShaderProgram(
|
||||||
|
IProgram hostProgram,
|
||||||
|
ShaderAsCompute vertexAsCompute,
|
||||||
|
ShaderAsCompute geometryAsCompute,
|
||||||
|
ShaderSpecializationState specializationState,
|
||||||
|
CachedShaderStage[] shaders) : this(hostProgram, specializationState, shaders)
|
||||||
|
{
|
||||||
|
VertexAsCompute = vertexAsCompute;
|
||||||
|
GeometryAsCompute = geometryAsCompute;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Dispose of the host shader resources.
|
/// Dispose of the host shader resources.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
HostProgram.Dispose();
|
HostProgram.Dispose();
|
||||||
|
VertexAsCompute?.HostProgram.Dispose();
|
||||||
|
GeometryAsCompute?.HostProgram.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,7 +35,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||||
ShaderSpecializationState oldSpecState,
|
ShaderSpecializationState oldSpecState,
|
||||||
ShaderSpecializationState newSpecState,
|
ShaderSpecializationState newSpecState,
|
||||||
ResourceCounts counts,
|
ResourceCounts counts,
|
||||||
int stageIndex) : base(context, counts, stageIndex, oldSpecState.TransformFeedbackDescriptors != null)
|
int stageIndex) : base(context, counts, stageIndex)
|
||||||
{
|
{
|
||||||
_data = data;
|
_data = data;
|
||||||
_cb1Data = cb1Data;
|
_cb1Data = cb1Data;
|
||||||
|
|
|
@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||||
private const ushort FileFormatVersionMajor = 1;
|
private const ushort FileFormatVersionMajor = 1;
|
||||||
private const ushort FileFormatVersionMinor = 2;
|
private const ushort FileFormatVersionMinor = 2;
|
||||||
private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor;
|
private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor;
|
||||||
private const uint CodeGenVersion = 5609;
|
private const uint CodeGenVersion = 5551;
|
||||||
|
|
||||||
private const string SharedTocFileName = "shared.toc";
|
private const string SharedTocFileName = "shared.toc";
|
||||||
private const string SharedDataFileName = "shared.data";
|
private const string SharedDataFileName = "shared.data";
|
||||||
|
@ -140,6 +140,21 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ShaderStage Stage;
|
public ShaderStage Stage;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Number of vertices that each output primitive has on a geometry shader.
|
||||||
|
/// </summary>
|
||||||
|
public byte GeometryVerticesPerPrimitive;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Maximum number of vertices that a geometry shader may generate.
|
||||||
|
/// </summary>
|
||||||
|
public ushort GeometryMaxOutputVertices;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Number of invocations per primitive on tessellation or geometry shaders.
|
||||||
|
/// </summary>
|
||||||
|
public ushort ThreadsPerInputPrimitive;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Indicates if the fragment shader accesses the fragment coordinate built-in variable.
|
/// Indicates if the fragment shader accesses the fragment coordinate built-in variable.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -783,9 +798,10 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||||
sBuffers,
|
sBuffers,
|
||||||
textures,
|
textures,
|
||||||
images,
|
images,
|
||||||
ShaderIdentification.None,
|
|
||||||
0,
|
|
||||||
dataInfo.Stage,
|
dataInfo.Stage,
|
||||||
|
dataInfo.GeometryVerticesPerPrimitive,
|
||||||
|
dataInfo.GeometryMaxOutputVertices,
|
||||||
|
dataInfo.ThreadsPerInputPrimitive,
|
||||||
dataInfo.UsesFragCoord,
|
dataInfo.UsesFragCoord,
|
||||||
dataInfo.UsesInstanceId,
|
dataInfo.UsesInstanceId,
|
||||||
dataInfo.UsesDrawParameters,
|
dataInfo.UsesDrawParameters,
|
||||||
|
@ -813,6 +829,9 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||||
TexturesCount = (ushort)info.Textures.Count,
|
TexturesCount = (ushort)info.Textures.Count,
|
||||||
ImagesCount = (ushort)info.Images.Count,
|
ImagesCount = (ushort)info.Images.Count,
|
||||||
Stage = info.Stage,
|
Stage = info.Stage,
|
||||||
|
GeometryVerticesPerPrimitive = (byte)info.GeometryVerticesPerPrimitive,
|
||||||
|
GeometryMaxOutputVertices = (ushort)info.GeometryMaxOutputVertices,
|
||||||
|
ThreadsPerInputPrimitive = (ushort)info.ThreadsPerInputPrimitive,
|
||||||
UsesFragCoord = info.UsesFragCoord,
|
UsesFragCoord = info.UsesFragCoord,
|
||||||
UsesInstanceId = info.UsesInstanceId,
|
UsesInstanceId = info.UsesInstanceId,
|
||||||
UsesDrawParameters = info.UsesDrawParameters,
|
UsesDrawParameters = info.UsesDrawParameters,
|
||||||
|
|
|
@ -595,6 +595,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||||
|
|
||||||
ResourceCounts counts = new();
|
ResourceCounts counts = new();
|
||||||
|
|
||||||
|
DiskCacheGpuAccessor[] gpuAccessors = new DiskCacheGpuAccessor[Constants.ShaderStages];
|
||||||
TranslatorContext[] translatorContexts = new TranslatorContext[Constants.ShaderStages + 1];
|
TranslatorContext[] translatorContexts = new TranslatorContext[Constants.ShaderStages + 1];
|
||||||
TranslatorContext nextStage = null;
|
TranslatorContext nextStage = null;
|
||||||
|
|
||||||
|
@ -626,14 +627,22 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||||
translatorContexts[0] = DecodeGraphicsShader(gpuAccessorA, api, DefaultFlags | TranslationFlags.VertexA, 0);
|
translatorContexts[0] = DecodeGraphicsShader(gpuAccessorA, api, DefaultFlags | TranslationFlags.VertexA, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gpuAccessors[stageIndex] = gpuAccessor;
|
||||||
translatorContexts[stageIndex + 1] = currentStage;
|
translatorContexts[stageIndex + 1] = currentStage;
|
||||||
nextStage = currentStage;
|
nextStage = currentStage;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_context.Capabilities.SupportsGeometryShader)
|
bool hasGeometryShader = translatorContexts[4] != null;
|
||||||
|
bool vertexHasStore = translatorContexts[1] != null && translatorContexts[1].HasStore;
|
||||||
|
bool geometryHasStore = hasGeometryShader && translatorContexts[4].HasStore;
|
||||||
|
bool vertexToCompute = ShouldConvertVertexToCompute(_context, vertexHasStore, geometryHasStore, hasGeometryShader);
|
||||||
|
|
||||||
|
// We don't support caching shader stages that have been converted to compute currently,
|
||||||
|
// so just eliminate them if they exist in the cache.
|
||||||
|
if (vertexToCompute)
|
||||||
{
|
{
|
||||||
ShaderCache.TryRemoveGeometryStage(translatorContexts);
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
CachedShaderStage[] shaders = new CachedShaderStage[guestShaders.Length];
|
CachedShaderStage[] shaders = new CachedShaderStage[guestShaders.Length];
|
||||||
|
@ -647,6 +656,8 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||||
|
|
||||||
if (currentStage != null)
|
if (currentStage != null)
|
||||||
{
|
{
|
||||||
|
gpuAccessors[stageIndex].InitializeReservedCounts(specState.TransformFeedbackDescriptors != null, vertexToCompute);
|
||||||
|
|
||||||
ShaderProgram program;
|
ShaderProgram program;
|
||||||
|
|
||||||
byte[] guestCode = guestShaders[stageIndex + 1].Value.Code;
|
byte[] guestCode = guestShaders[stageIndex + 1].Value.Code;
|
||||||
|
@ -701,6 +712,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||||
ResourceCounts counts = new();
|
ResourceCounts counts = new();
|
||||||
ShaderSpecializationState newSpecState = new(ref specState.ComputeState);
|
ShaderSpecializationState newSpecState = new(ref specState.ComputeState);
|
||||||
DiskCacheGpuAccessor gpuAccessor = new(_context, shader.Code, shader.Cb1Data, specState, newSpecState, counts, 0);
|
DiskCacheGpuAccessor gpuAccessor = new(_context, shader.Code, shader.Cb1Data, specState, newSpecState, counts, 0);
|
||||||
|
gpuAccessor.InitializeReservedCounts(tfEnabled: false, vertexAsCompute: false);
|
||||||
|
|
||||||
TranslatorContext translatorContext = DecodeComputeShader(gpuAccessor, _context.Capabilities.Api, 0);
|
TranslatorContext translatorContext = DecodeComputeShader(gpuAccessor, _context.Capabilities.Api, 0);
|
||||||
|
|
||||||
|
|
|
@ -25,11 +25,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
/// <param name="channel">GPU channel</param>
|
/// <param name="channel">GPU channel</param>
|
||||||
/// <param name="state">Current GPU state</param>
|
/// <param name="state">Current GPU state</param>
|
||||||
/// <param name="stageIndex">Graphics shader stage index (0 = Vertex, 4 = Fragment)</param>
|
/// <param name="stageIndex">Graphics shader stage index (0 = Vertex, 4 = Fragment)</param>
|
||||||
public GpuAccessor(
|
public GpuAccessor(GpuContext context, GpuChannel channel, GpuAccessorState state, int stageIndex) : base(context, state.ResourceCounts, stageIndex)
|
||||||
GpuContext context,
|
|
||||||
GpuChannel channel,
|
|
||||||
GpuAccessorState state,
|
|
||||||
int stageIndex) : base(context, state.ResourceCounts, stageIndex, state.TransformFeedbackDescriptors != null)
|
|
||||||
{
|
{
|
||||||
_isVulkan = context.Capabilities.Api == TargetApi.Vulkan;
|
_isVulkan = context.Capabilities.Api == TargetApi.Vulkan;
|
||||||
_channel = channel;
|
_channel = channel;
|
||||||
|
@ -49,7 +45,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
/// <param name="context">GPU context</param>
|
/// <param name="context">GPU context</param>
|
||||||
/// <param name="channel">GPU channel</param>
|
/// <param name="channel">GPU channel</param>
|
||||||
/// <param name="state">Current GPU state</param>
|
/// <param name="state">Current GPU state</param>
|
||||||
public GpuAccessor(GpuContext context, GpuChannel channel, GpuAccessorState state) : base(context, state.ResourceCounts, 0, false)
|
public GpuAccessor(GpuContext context, GpuChannel channel, GpuAccessorState state) : base(context, state.ResourceCounts, 0)
|
||||||
{
|
{
|
||||||
_channel = channel;
|
_channel = channel;
|
||||||
_state = state;
|
_state = state;
|
||||||
|
|
|
@ -15,8 +15,10 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
private readonly ResourceCounts _resourceCounts;
|
private readonly ResourceCounts _resourceCounts;
|
||||||
private readonly int _stageIndex;
|
private readonly int _stageIndex;
|
||||||
|
|
||||||
private readonly int _reservedConstantBuffers;
|
private int _reservedConstantBuffers;
|
||||||
private readonly int _reservedStorageBuffers;
|
private int _reservedStorageBuffers;
|
||||||
|
private int _reservedTextures;
|
||||||
|
private int _reservedImages;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new GPU accessor.
|
/// Creates a new GPU accessor.
|
||||||
|
@ -24,15 +26,26 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
/// <param name="context">GPU context</param>
|
/// <param name="context">GPU context</param>
|
||||||
/// <param name="resourceCounts">Counter of GPU resources used by the shader</param>
|
/// <param name="resourceCounts">Counter of GPU resources used by the shader</param>
|
||||||
/// <param name="stageIndex">Index of the shader stage, 0 for compute</param>
|
/// <param name="stageIndex">Index of the shader stage, 0 for compute</param>
|
||||||
/// <param name="tfEnabled">Indicates if the current graphics shader is used with transform feedback enabled</param>
|
public GpuAccessorBase(GpuContext context, ResourceCounts resourceCounts, int stageIndex)
|
||||||
public GpuAccessorBase(GpuContext context, ResourceCounts resourceCounts, int stageIndex, bool tfEnabled)
|
|
||||||
{
|
{
|
||||||
_context = context;
|
_context = context;
|
||||||
_resourceCounts = resourceCounts;
|
_resourceCounts = resourceCounts;
|
||||||
_stageIndex = stageIndex;
|
_stageIndex = stageIndex;
|
||||||
|
}
|
||||||
|
|
||||||
_reservedConstantBuffers = 1; // For the support buffer.
|
/// <summary>
|
||||||
_reservedStorageBuffers = !context.Capabilities.SupportsTransformFeedback && tfEnabled ? 5 : 0;
|
/// Initializes counts for bindings that will be reserved for emulator use.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tfEnabled">Indicates if the current graphics shader is used with transform feedback enabled</param>
|
||||||
|
/// <param name="vertexAsCompute">Indicates that the vertex shader will be emulated on a compute shader</param>
|
||||||
|
public void InitializeReservedCounts(bool tfEnabled, bool vertexAsCompute)
|
||||||
|
{
|
||||||
|
ResourceReservationCounts rrc = new(!_context.Capabilities.SupportsTransformFeedback && tfEnabled, vertexAsCompute);
|
||||||
|
|
||||||
|
_reservedConstantBuffers = rrc.ReservedConstantBuffers;
|
||||||
|
_reservedStorageBuffers = rrc.ReservedStorageBuffers;
|
||||||
|
_reservedTextures = rrc.ReservedTextures;
|
||||||
|
_reservedImages = rrc.ReservedImages;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int QueryBindingConstantBuffer(int index)
|
public int QueryBindingConstantBuffer(int index)
|
||||||
|
@ -69,6 +82,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
|
|
||||||
public int QueryBindingTexture(int index, bool isBuffer)
|
public int QueryBindingTexture(int index, bool isBuffer)
|
||||||
{
|
{
|
||||||
|
int binding;
|
||||||
|
|
||||||
if (_context.Capabilities.Api == TargetApi.Vulkan)
|
if (_context.Capabilities.Api == TargetApi.Vulkan)
|
||||||
{
|
{
|
||||||
if (isBuffer)
|
if (isBuffer)
|
||||||
|
@ -76,16 +91,20 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
index += (int)_context.Capabilities.MaximumTexturesPerStage;
|
index += (int)_context.Capabilities.MaximumTexturesPerStage;
|
||||||
}
|
}
|
||||||
|
|
||||||
return GetBindingFromIndex(index, _context.Capabilities.MaximumTexturesPerStage * 2, "Texture");
|
binding = GetBindingFromIndex(index, _context.Capabilities.MaximumTexturesPerStage * 2, "Texture");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return _resourceCounts.TexturesCount++;
|
binding = _resourceCounts.TexturesCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return binding + _reservedTextures;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int QueryBindingImage(int index, bool isBuffer)
|
public int QueryBindingImage(int index, bool isBuffer)
|
||||||
{
|
{
|
||||||
|
int binding;
|
||||||
|
|
||||||
if (_context.Capabilities.Api == TargetApi.Vulkan)
|
if (_context.Capabilities.Api == TargetApi.Vulkan)
|
||||||
{
|
{
|
||||||
if (isBuffer)
|
if (isBuffer)
|
||||||
|
@ -93,12 +112,14 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
index += (int)_context.Capabilities.MaximumImagesPerStage;
|
index += (int)_context.Capabilities.MaximumImagesPerStage;
|
||||||
}
|
}
|
||||||
|
|
||||||
return GetBindingFromIndex(index, _context.Capabilities.MaximumImagesPerStage * 2, "Image");
|
binding = GetBindingFromIndex(index, _context.Capabilities.MaximumImagesPerStage * 2, "Image");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return _resourceCounts.ImagesCount++;
|
binding = _resourceCounts.ImagesCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return binding + _reservedImages;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int GetBindingFromIndex(int index, uint maxPerStage, string resourceName)
|
private int GetBindingFromIndex(int index, uint maxPerStage, string resourceName)
|
||||||
|
|
20
src/Ryujinx.Graphics.Gpu/Shader/ShaderAsCompute.cs
Normal file
20
src/Ryujinx.Graphics.Gpu/Shader/ShaderAsCompute.cs
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
using Ryujinx.Graphics.GAL;
|
||||||
|
using Ryujinx.Graphics.Shader;
|
||||||
|
using Ryujinx.Graphics.Shader.Translation;
|
||||||
|
|
||||||
|
namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
|
{
|
||||||
|
class ShaderAsCompute
|
||||||
|
{
|
||||||
|
public IProgram HostProgram { get; }
|
||||||
|
public ShaderProgramInfo Info { get; }
|
||||||
|
public ResourceReservations Reservations { get; }
|
||||||
|
|
||||||
|
public ShaderAsCompute(IProgram hostProgram, ShaderProgramInfo info, ResourceReservations reservations)
|
||||||
|
{
|
||||||
|
HostProgram = hostProgram;
|
||||||
|
Info = info;
|
||||||
|
Reservations = reservations;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -215,9 +215,10 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
ShaderSpecializationState specState = new(ref computeState);
|
ShaderSpecializationState specState = new(ref computeState);
|
||||||
GpuAccessorState gpuAccessorState = new(poolState, computeState, default, specState);
|
GpuAccessorState gpuAccessorState = new(poolState, computeState, default, specState);
|
||||||
GpuAccessor gpuAccessor = new(_context, channel, gpuAccessorState);
|
GpuAccessor gpuAccessor = new(_context, channel, gpuAccessorState);
|
||||||
|
gpuAccessor.InitializeReservedCounts(tfEnabled: false, vertexAsCompute: false);
|
||||||
|
|
||||||
TranslatorContext translatorContext = DecodeComputeShader(gpuAccessor, _context.Capabilities.Api, gpuVa);
|
TranslatorContext translatorContext = DecodeComputeShader(gpuAccessor, _context.Capabilities.Api, gpuVa);
|
||||||
TranslatedShader translatedShader = TranslateShader(_dumper, channel, translatorContext, cachedGuestCode);
|
TranslatedShader translatedShader = TranslateShader(_dumper, channel, translatorContext, cachedGuestCode, asCompute: false);
|
||||||
|
|
||||||
ShaderSource[] shaderSourcesArray = new ShaderSource[] { CreateShaderSource(translatedShader.Program) };
|
ShaderSource[] shaderSourcesArray = new ShaderSource[] { CreateShaderSource(translatedShader.Program) };
|
||||||
ShaderInfo info = ShaderInfoBuilder.BuildForCompute(_context, translatedShader.Program.Info);
|
ShaderInfo info = ShaderInfoBuilder.BuildForCompute(_context, translatedShader.Program.Info);
|
||||||
|
@ -321,6 +322,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
|
|
||||||
ReadOnlySpan<ulong> addressesSpan = addresses.AsSpan();
|
ReadOnlySpan<ulong> addressesSpan = addresses.AsSpan();
|
||||||
|
|
||||||
|
GpuAccessor[] gpuAccessors = new GpuAccessor[Constants.ShaderStages];
|
||||||
TranslatorContext[] translatorContexts = new TranslatorContext[Constants.ShaderStages + 1];
|
TranslatorContext[] translatorContexts = new TranslatorContext[Constants.ShaderStages + 1];
|
||||||
TranslatorContext nextStage = null;
|
TranslatorContext nextStage = null;
|
||||||
|
|
||||||
|
@ -345,22 +347,31 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
translatorContexts[0] = DecodeGraphicsShader(gpuAccessor, api, DefaultFlags | TranslationFlags.VertexA, addresses.VertexA);
|
translatorContexts[0] = DecodeGraphicsShader(gpuAccessor, api, DefaultFlags | TranslationFlags.VertexA, addresses.VertexA);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gpuAccessors[stageIndex] = gpuAccessor;
|
||||||
translatorContexts[stageIndex + 1] = currentStage;
|
translatorContexts[stageIndex + 1] = currentStage;
|
||||||
nextStage = currentStage;
|
nextStage = currentStage;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_context.Capabilities.SupportsGeometryShader)
|
bool hasGeometryShader = translatorContexts[4] != null;
|
||||||
{
|
bool vertexHasStore = translatorContexts[1] != null && translatorContexts[1].HasStore;
|
||||||
TryRemoveGeometryStage(translatorContexts);
|
bool geometryHasStore = hasGeometryShader && translatorContexts[4].HasStore;
|
||||||
}
|
bool vertexToCompute = ShouldConvertVertexToCompute(_context, vertexHasStore, geometryHasStore, hasGeometryShader);
|
||||||
|
bool geometryToCompute = ShouldConvertGeometryToCompute(_context, geometryHasStore);
|
||||||
|
|
||||||
CachedShaderStage[] shaders = new CachedShaderStage[Constants.ShaderStages + 1];
|
CachedShaderStage[] shaders = new CachedShaderStage[Constants.ShaderStages + 1];
|
||||||
List<ShaderSource> shaderSources = new();
|
List<ShaderSource> shaderSources = new();
|
||||||
|
|
||||||
TranslatorContext previousStage = null;
|
TranslatorContext previousStage = null;
|
||||||
|
ShaderInfoBuilder infoBuilder = new(_context, transformFeedbackDescriptors != null, vertexToCompute);
|
||||||
|
|
||||||
ShaderInfoBuilder infoBuilder = new(_context, transformFeedbackDescriptors != null);
|
if (geometryToCompute && translatorContexts[4] != null)
|
||||||
|
{
|
||||||
|
translatorContexts[4].SetVertexOutputMapForGeometryAsCompute(translatorContexts[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
ShaderAsCompute vertexAsCompute = null;
|
||||||
|
ShaderAsCompute geometryAsCompute = null;
|
||||||
|
|
||||||
for (int stageIndex = 0; stageIndex < Constants.ShaderStages; stageIndex++)
|
for (int stageIndex = 0; stageIndex < Constants.ShaderStages; stageIndex++)
|
||||||
{
|
{
|
||||||
|
@ -368,8 +379,12 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
|
|
||||||
if (currentStage != null)
|
if (currentStage != null)
|
||||||
{
|
{
|
||||||
|
gpuAccessors[stageIndex].InitializeReservedCounts(transformFeedbackDescriptors != null, vertexToCompute);
|
||||||
|
|
||||||
ShaderProgram program;
|
ShaderProgram program;
|
||||||
|
|
||||||
|
bool asCompute = (stageIndex == 0 && vertexToCompute) || (stageIndex == 3 && geometryToCompute);
|
||||||
|
|
||||||
if (stageIndex == 0 && translatorContexts[0] != null)
|
if (stageIndex == 0 && translatorContexts[0] != null)
|
||||||
{
|
{
|
||||||
TranslatedShaderVertexPair translatedShader = TranslateShader(
|
TranslatedShaderVertexPair translatedShader = TranslateShader(
|
||||||
|
@ -378,7 +393,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
currentStage,
|
currentStage,
|
||||||
translatorContexts[0],
|
translatorContexts[0],
|
||||||
cachedGuestCode.VertexACode,
|
cachedGuestCode.VertexACode,
|
||||||
cachedGuestCode.VertexBCode);
|
cachedGuestCode.VertexBCode,
|
||||||
|
asCompute);
|
||||||
|
|
||||||
shaders[0] = translatedShader.VertexA;
|
shaders[0] = translatedShader.VertexA;
|
||||||
shaders[1] = translatedShader.VertexB;
|
shaders[1] = translatedShader.VertexB;
|
||||||
|
@ -388,12 +404,31 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
{
|
{
|
||||||
byte[] code = cachedGuestCode.GetByIndex(stageIndex);
|
byte[] code = cachedGuestCode.GetByIndex(stageIndex);
|
||||||
|
|
||||||
TranslatedShader translatedShader = TranslateShader(_dumper, channel, currentStage, code);
|
TranslatedShader translatedShader = TranslateShader(_dumper, channel, currentStage, code, asCompute);
|
||||||
|
|
||||||
shaders[stageIndex + 1] = translatedShader.Shader;
|
shaders[stageIndex + 1] = translatedShader.Shader;
|
||||||
program = translatedShader.Program;
|
program = translatedShader.Program;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (asCompute)
|
||||||
|
{
|
||||||
|
bool tfEnabled = transformFeedbackDescriptors != null;
|
||||||
|
|
||||||
|
if (stageIndex == 0)
|
||||||
|
{
|
||||||
|
vertexAsCompute = CreateHostVertexAsComputeProgram(program, currentStage, tfEnabled);
|
||||||
|
|
||||||
|
TranslatorContext lastInVertexPipeline = geometryToCompute ? translatorContexts[4] ?? currentStage : currentStage;
|
||||||
|
|
||||||
|
program = lastInVertexPipeline.GenerateVertexPassthroughForCompute();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
geometryAsCompute = CreateHostVertexAsComputeProgram(program, currentStage, tfEnabled);
|
||||||
|
program = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (program != null)
|
if (program != null)
|
||||||
{
|
{
|
||||||
shaderSources.Add(CreateShaderSource(program));
|
shaderSources.Add(CreateShaderSource(program));
|
||||||
|
@ -418,46 +453,81 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
|
|
||||||
IProgram hostProgram = _context.Renderer.CreateProgram(shaderSourcesArray, info);
|
IProgram hostProgram = _context.Renderer.CreateProgram(shaderSourcesArray, info);
|
||||||
|
|
||||||
gpShaders = new CachedShaderProgram(hostProgram, specState, shaders);
|
gpShaders = new(hostProgram, vertexAsCompute, geometryAsCompute, specState, shaders);
|
||||||
|
|
||||||
_graphicsShaderCache.Add(gpShaders);
|
_graphicsShaderCache.Add(gpShaders);
|
||||||
|
|
||||||
|
// We don't currently support caching shaders that have been converted to compute.
|
||||||
|
if (vertexAsCompute == null)
|
||||||
|
{
|
||||||
EnqueueProgramToSave(gpShaders, hostProgram, shaderSourcesArray);
|
EnqueueProgramToSave(gpShaders, hostProgram, shaderSourcesArray);
|
||||||
|
}
|
||||||
|
|
||||||
_gpPrograms[addresses] = gpShaders;
|
_gpPrograms[addresses] = gpShaders;
|
||||||
|
|
||||||
return gpShaders;
|
return gpShaders;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Tries to eliminate the geometry stage from the array of translator contexts.
|
/// Checks if a vertex shader should be converted to a compute shader due to it making use of
|
||||||
|
/// features that are not supported on the host.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="translatorContexts">Array of translator contexts</param>
|
/// <param name="context">GPU context of the shader</param>
|
||||||
public static void TryRemoveGeometryStage(TranslatorContext[] translatorContexts)
|
/// <param name="vertexHasStore">Whether the vertex shader has image or storage buffer store operations</param>
|
||||||
|
/// <param name="geometryHasStore">Whether the geometry shader has image or storage buffer store operations, if one exists</param>
|
||||||
|
/// <param name="hasGeometryShader">Whether a geometry shader exists</param>
|
||||||
|
/// <returns>True if the vertex shader should be converted to compute, false otherwise</returns>
|
||||||
|
public static bool ShouldConvertVertexToCompute(GpuContext context, bool vertexHasStore, bool geometryHasStore, bool hasGeometryShader)
|
||||||
{
|
{
|
||||||
if (translatorContexts[4] != null)
|
// If the host does not support store operations on vertex,
|
||||||
|
// we need to emulate it on a compute shader.
|
||||||
|
if (!context.Capabilities.SupportsVertexStoreAndAtomics && vertexHasStore)
|
||||||
{
|
{
|
||||||
// We have a geometry shader, but geometry shaders are not supported.
|
return true;
|
||||||
// Try to eliminate the geometry shader.
|
|
||||||
|
|
||||||
ShaderProgramInfo info = translatorContexts[4].Translate().Info;
|
|
||||||
|
|
||||||
if (info.Identification == ShaderIdentification.GeometryLayerPassthrough)
|
|
||||||
{
|
|
||||||
// We managed to identify that this geometry shader is only used to set the output Layer value,
|
|
||||||
// we can set the Layer on the previous stage instead (usually the vertex stage) and eliminate it.
|
|
||||||
|
|
||||||
for (int i = 3; i >= 1; i--)
|
|
||||||
{
|
|
||||||
if (translatorContexts[i] != null)
|
|
||||||
{
|
|
||||||
translatorContexts[i].SetGeometryShaderLayerInputAttribute(info.GpLayerInputAttribute);
|
|
||||||
translatorContexts[i].SetLastInVertexPipeline();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
translatorContexts[4] = null;
|
// If any stage after the vertex stage is converted to compute,
|
||||||
|
// we need to convert vertex to compute too.
|
||||||
|
return hasGeometryShader && ShouldConvertGeometryToCompute(context, geometryHasStore);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if a geometry shader should be converted to a compute shader due to it making use of
|
||||||
|
/// features that are not supported on the host.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">GPU context of the shader</param>
|
||||||
|
/// <param name="geometryHasStore">Whether the geometry shader has image or storage buffer store operations, if one exists</param>
|
||||||
|
/// <returns>True if the geometry shader should be converted to compute, false otherwise</returns>
|
||||||
|
public static bool ShouldConvertGeometryToCompute(GpuContext context, bool geometryHasStore)
|
||||||
|
{
|
||||||
|
return (!context.Capabilities.SupportsVertexStoreAndAtomics && geometryHasStore) ||
|
||||||
|
!context.Capabilities.SupportsGeometryShader;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if it might be necessary for any vertex, tessellation or geometry shader to be converted to compute,
|
||||||
|
/// based on the supported host features.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="capabilities">Host capabilities</param>
|
||||||
|
/// <returns>True if the possibility of a shader being converted to compute exists, false otherwise</returns>
|
||||||
|
public static bool MayConvertVtgToCompute(ref Capabilities capabilities)
|
||||||
|
{
|
||||||
|
return !capabilities.SupportsVertexStoreAndAtomics || !capabilities.SupportsGeometryShader;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a compute shader from a vertex, tessellation or geometry shader that has been converted to compute.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="program">Shader program</param>
|
||||||
|
/// <param name="context">Translation context of the shader</param>
|
||||||
|
/// <param name="tfEnabled">Whether transform feedback is enabled</param>
|
||||||
|
/// <returns>Compute shader</returns>
|
||||||
|
private ShaderAsCompute CreateHostVertexAsComputeProgram(ShaderProgram program, TranslatorContext context, bool tfEnabled)
|
||||||
|
{
|
||||||
|
ShaderSource source = new(program.Code, program.BinaryCode, ShaderStage.Compute, program.Language);
|
||||||
|
ShaderInfo info = ShaderInfoBuilder.BuildForVertexAsCompute(_context, program.Info, tfEnabled);
|
||||||
|
|
||||||
|
return new(_context.Renderer.CreateProgram(new[] { source }, info), program.Info, context.GetResourceReservations());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -573,9 +643,16 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool vertexAsCompute = gpShaders.VertexAsCompute != null;
|
||||||
bool usesDrawParameters = gpShaders.Shaders[1]?.Info.UsesDrawParameters ?? false;
|
bool usesDrawParameters = gpShaders.Shaders[1]?.Info.UsesDrawParameters ?? false;
|
||||||
|
|
||||||
return gpShaders.SpecializationState.MatchesGraphics(channel, ref poolState, ref graphicsState, usesDrawParameters, true);
|
return gpShaders.SpecializationState.MatchesGraphics(
|
||||||
|
channel,
|
||||||
|
ref poolState,
|
||||||
|
ref graphicsState,
|
||||||
|
vertexAsCompute,
|
||||||
|
usesDrawParameters,
|
||||||
|
checkTextures: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -636,6 +713,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
/// <param name="vertexA">Optional translator context of the shader that should be combined</param>
|
/// <param name="vertexA">Optional translator context of the shader that should be combined</param>
|
||||||
/// <param name="codeA">Optional Maxwell binary code of the Vertex A shader, if present</param>
|
/// <param name="codeA">Optional Maxwell binary code of the Vertex A shader, if present</param>
|
||||||
/// <param name="codeB">Optional Maxwell binary code of the Vertex B or current stage shader, if present on cache</param>
|
/// <param name="codeB">Optional Maxwell binary code of the Vertex B or current stage shader, if present on cache</param>
|
||||||
|
/// <param name="asCompute">Indicates that the vertex shader should be converted to a compute shader</param>
|
||||||
/// <returns>Compiled graphics shader code</returns>
|
/// <returns>Compiled graphics shader code</returns>
|
||||||
private static TranslatedShaderVertexPair TranslateShader(
|
private static TranslatedShaderVertexPair TranslateShader(
|
||||||
ShaderDumper dumper,
|
ShaderDumper dumper,
|
||||||
|
@ -643,7 +721,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
TranslatorContext currentStage,
|
TranslatorContext currentStage,
|
||||||
TranslatorContext vertexA,
|
TranslatorContext vertexA,
|
||||||
byte[] codeA,
|
byte[] codeA,
|
||||||
byte[] codeB)
|
byte[] codeB,
|
||||||
|
bool asCompute)
|
||||||
{
|
{
|
||||||
ulong cb1DataAddress = channel.BufferManager.GetGraphicsUniformBufferAddress(0, 1);
|
ulong cb1DataAddress = channel.BufferManager.GetGraphicsUniformBufferAddress(0, 1);
|
||||||
|
|
||||||
|
@ -663,7 +742,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
pathsB = dumper.Dump(codeB, compute: false);
|
pathsB = dumper.Dump(codeB, compute: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
ShaderProgram program = currentStage.Translate(vertexA);
|
ShaderProgram program = currentStage.Translate(vertexA, asCompute);
|
||||||
|
|
||||||
pathsB.Prepend(program);
|
pathsB.Prepend(program);
|
||||||
pathsA.Prepend(program);
|
pathsA.Prepend(program);
|
||||||
|
@ -681,8 +760,9 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
/// <param name="channel">GPU channel using the shader</param>
|
/// <param name="channel">GPU channel using the shader</param>
|
||||||
/// <param name="context">Translator context of the stage to be translated</param>
|
/// <param name="context">Translator context of the stage to be translated</param>
|
||||||
/// <param name="code">Optional Maxwell binary code of the current stage shader, if present on cache</param>
|
/// <param name="code">Optional Maxwell binary code of the current stage shader, if present on cache</param>
|
||||||
|
/// <param name="asCompute">Indicates that the vertex shader should be converted to a compute shader</param>
|
||||||
/// <returns>Compiled graphics shader code</returns>
|
/// <returns>Compiled graphics shader code</returns>
|
||||||
private static TranslatedShader TranslateShader(ShaderDumper dumper, GpuChannel channel, TranslatorContext context, byte[] code)
|
private static TranslatedShader TranslateShader(ShaderDumper dumper, GpuChannel channel, TranslatorContext context, byte[] code, bool asCompute)
|
||||||
{
|
{
|
||||||
var memoryManager = channel.MemoryManager;
|
var memoryManager = channel.MemoryManager;
|
||||||
|
|
||||||
|
@ -694,7 +774,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
code ??= memoryManager.GetSpan(context.Address, context.Size).ToArray();
|
code ??= memoryManager.GetSpan(context.Address, context.Size).ToArray();
|
||||||
|
|
||||||
ShaderDumpPaths paths = dumper?.Dump(code, context.Stage == ShaderStage.Compute) ?? default;
|
ShaderDumpPaths paths = dumper?.Dump(code, context.Stage == ShaderStage.Compute) ?? default;
|
||||||
ShaderProgram program = context.Translate();
|
ShaderProgram program = context.Translate(asCompute);
|
||||||
|
|
||||||
paths.Prepend(program);
|
paths.Prepend(program);
|
||||||
|
|
||||||
|
|
|
@ -33,6 +33,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
|
|
||||||
private readonly int _reservedConstantBuffers;
|
private readonly int _reservedConstantBuffers;
|
||||||
private readonly int _reservedStorageBuffers;
|
private readonly int _reservedStorageBuffers;
|
||||||
|
private readonly int _reservedTextures;
|
||||||
|
private readonly int _reservedImages;
|
||||||
|
|
||||||
private readonly List<ResourceDescriptor>[] _resourceDescriptors;
|
private readonly List<ResourceDescriptor>[] _resourceDescriptors;
|
||||||
private readonly List<ResourceUsage>[] _resourceUsages;
|
private readonly List<ResourceUsage>[] _resourceUsages;
|
||||||
|
@ -42,7 +44,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="context">GPU context that owns the shaders that will be added to the builder</param>
|
/// <param name="context">GPU context that owns the shaders that will be added to the builder</param>
|
||||||
/// <param name="tfEnabled">Indicates if the graphics shader is used with transform feedback enabled</param>
|
/// <param name="tfEnabled">Indicates if the graphics shader is used with transform feedback enabled</param>
|
||||||
public ShaderInfoBuilder(GpuContext context, bool tfEnabled)
|
/// <param name="vertexAsCompute">Indicates that the vertex shader will be emulated on a compute shader</param>
|
||||||
|
public ShaderInfoBuilder(GpuContext context, bool tfEnabled, bool vertexAsCompute = false)
|
||||||
{
|
{
|
||||||
_context = context;
|
_context = context;
|
||||||
|
|
||||||
|
@ -60,27 +63,34 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
AddDescriptor(SupportBufferStages, ResourceType.UniformBuffer, UniformSetIndex, 0, 1);
|
AddDescriptor(SupportBufferStages, ResourceType.UniformBuffer, UniformSetIndex, 0, 1);
|
||||||
AddUsage(SupportBufferStages, ResourceType.UniformBuffer, ResourceAccess.Read, UniformSetIndex, 0, 1);
|
AddUsage(SupportBufferStages, ResourceType.UniformBuffer, ResourceAccess.Read, UniformSetIndex, 0, 1);
|
||||||
|
|
||||||
_reservedConstantBuffers = 1; // For the support buffer.
|
ResourceReservationCounts rrc = new(!context.Capabilities.SupportsTransformFeedback && tfEnabled, vertexAsCompute);
|
||||||
|
|
||||||
if (!context.Capabilities.SupportsTransformFeedback && tfEnabled)
|
_reservedConstantBuffers = rrc.ReservedConstantBuffers;
|
||||||
{
|
_reservedStorageBuffers = rrc.ReservedStorageBuffers;
|
||||||
_reservedStorageBuffers = 5;
|
_reservedTextures = rrc.ReservedTextures;
|
||||||
|
_reservedImages = rrc.ReservedImages;
|
||||||
|
|
||||||
AddDescriptor(VtgStages, ResourceType.StorageBuffer, StorageSetIndex, 0, 5);
|
// TODO: Handle that better? Maybe we should only set the binding that are really needed on each shader.
|
||||||
AddUsage(VtgStages, ResourceType.StorageBuffer, ResourceAccess.Read, StorageSetIndex, 0, 1);
|
ResourceStages stages = vertexAsCompute ? ResourceStages.Compute | ResourceStages.Vertex : VtgStages;
|
||||||
AddUsage(VtgStages, ResourceType.StorageBuffer, ResourceAccess.Write, StorageSetIndex, 1, 4);
|
|
||||||
|
PopulateDescriptorAndUsages(stages, ResourceType.UniformBuffer, ResourceAccess.Read, UniformSetIndex, 1, rrc.ReservedConstantBuffers - 1);
|
||||||
|
PopulateDescriptorAndUsages(stages, ResourceType.StorageBuffer, ResourceAccess.ReadWrite, StorageSetIndex, 0, rrc.ReservedStorageBuffers);
|
||||||
|
PopulateDescriptorAndUsages(stages, ResourceType.BufferTexture, ResourceAccess.Read, TextureSetIndex, 0, rrc.ReservedTextures);
|
||||||
|
PopulateDescriptorAndUsages(stages, ResourceType.BufferImage, ResourceAccess.ReadWrite, ImageSetIndex, 0, rrc.ReservedImages);
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
private void PopulateDescriptorAndUsages(ResourceStages stages, ResourceType type, ResourceAccess access, int setIndex, int start, int count)
|
||||||
{
|
{
|
||||||
_reservedStorageBuffers = 0;
|
AddDescriptor(stages, type, setIndex, start, count);
|
||||||
}
|
AddUsage(stages, type, access, setIndex, start, count);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds information from a given shader stage.
|
/// Adds information from a given shader stage.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="info">Shader stage information</param>
|
/// <param name="info">Shader stage information</param>
|
||||||
public void AddStageInfo(ShaderProgramInfo info)
|
/// <param name="vertexAsCompute">True if the shader stage has been converted into a compute shader</param>
|
||||||
|
public void AddStageInfo(ShaderProgramInfo info, bool vertexAsCompute = false)
|
||||||
{
|
{
|
||||||
if (info.Stage == ShaderStage.Fragment)
|
if (info.Stage == ShaderStage.Fragment)
|
||||||
{
|
{
|
||||||
|
@ -96,7 +106,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
_ => 0,
|
_ => 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
ResourceStages stages = info.Stage switch
|
ResourceStages stages = vertexAsCompute ? ResourceStages.Compute : info.Stage switch
|
||||||
{
|
{
|
||||||
ShaderStage.Compute => ResourceStages.Compute,
|
ShaderStage.Compute => ResourceStages.Compute,
|
||||||
ShaderStage.Vertex => ResourceStages.Vertex,
|
ShaderStage.Vertex => ResourceStages.Vertex,
|
||||||
|
@ -114,8 +124,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
|
|
||||||
int uniformBinding = _reservedConstantBuffers + stageIndex * uniformsPerStage;
|
int uniformBinding = _reservedConstantBuffers + stageIndex * uniformsPerStage;
|
||||||
int storageBinding = _reservedStorageBuffers + stageIndex * storagesPerStage;
|
int storageBinding = _reservedStorageBuffers + stageIndex * storagesPerStage;
|
||||||
int textureBinding = stageIndex * texturesPerStage * 2;
|
int textureBinding = _reservedTextures + stageIndex * texturesPerStage * 2;
|
||||||
int imageBinding = stageIndex * imagesPerStage * 2;
|
int imageBinding = _reservedImages + stageIndex * imagesPerStage * 2;
|
||||||
|
|
||||||
AddDescriptor(stages, ResourceType.UniformBuffer, UniformSetIndex, uniformBinding, uniformsPerStage);
|
AddDescriptor(stages, ResourceType.UniformBuffer, UniformSetIndex, uniformBinding, uniformsPerStage);
|
||||||
AddDescriptor(stages, ResourceType.StorageBuffer, StorageSetIndex, storageBinding, storagesPerStage);
|
AddDescriptor(stages, ResourceType.StorageBuffer, StorageSetIndex, storageBinding, storagesPerStage);
|
||||||
|
@ -285,11 +295,28 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
/// <returns>Shader information</returns>
|
/// <returns>Shader information</returns>
|
||||||
public static ShaderInfo BuildForCompute(GpuContext context, ShaderProgramInfo info, bool fromCache = false)
|
public static ShaderInfo BuildForCompute(GpuContext context, ShaderProgramInfo info, bool fromCache = false)
|
||||||
{
|
{
|
||||||
ShaderInfoBuilder builder = new(context, tfEnabled: false);
|
ShaderInfoBuilder builder = new(context, tfEnabled: false, vertexAsCompute: false);
|
||||||
|
|
||||||
builder.AddStageInfo(info);
|
builder.AddStageInfo(info);
|
||||||
|
|
||||||
return builder.Build(null, fromCache);
|
return builder.Build(null, fromCache);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Builds shader information for a vertex or geometry shader thas was converted to compute shader.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">GPU context that owns the shader</param>
|
||||||
|
/// <param name="info">Compute shader information</param>
|
||||||
|
/// <param name="tfEnabled">Indicates if the graphics shader is used with transform feedback enabled</param>
|
||||||
|
/// <param name="fromCache">True if the compute shader comes from a disk cache, false otherwise</param>
|
||||||
|
/// <returns>Shader information</returns>
|
||||||
|
public static ShaderInfo BuildForVertexAsCompute(GpuContext context, ShaderProgramInfo info, bool tfEnabled, bool fromCache = false)
|
||||||
|
{
|
||||||
|
ShaderInfoBuilder builder = new(context, tfEnabled, vertexAsCompute: true);
|
||||||
|
|
||||||
|
builder.AddStageInfo(info, vertexAsCompute: true);
|
||||||
|
|
||||||
|
return builder.Build(null, fromCache);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,9 +35,16 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
{
|
{
|
||||||
foreach (var entry in _entries)
|
foreach (var entry in _entries)
|
||||||
{
|
{
|
||||||
|
bool vertexAsCompute = entry.VertexAsCompute != null;
|
||||||
bool usesDrawParameters = entry.Shaders[1]?.Info.UsesDrawParameters ?? false;
|
bool usesDrawParameters = entry.Shaders[1]?.Info.UsesDrawParameters ?? false;
|
||||||
|
|
||||||
if (entry.SpecializationState.MatchesGraphics(channel, ref poolState, ref graphicsState, usesDrawParameters, true))
|
if (entry.SpecializationState.MatchesGraphics(
|
||||||
|
channel,
|
||||||
|
ref poolState,
|
||||||
|
ref graphicsState,
|
||||||
|
vertexAsCompute,
|
||||||
|
usesDrawParameters,
|
||||||
|
checkTextures: true))
|
||||||
{
|
{
|
||||||
program = entry;
|
program = entry;
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -457,6 +457,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
/// <param name="channel">GPU channel</param>
|
/// <param name="channel">GPU channel</param>
|
||||||
/// <param name="poolState">Texture pool state</param>
|
/// <param name="poolState">Texture pool state</param>
|
||||||
/// <param name="graphicsState">Graphics state</param>
|
/// <param name="graphicsState">Graphics state</param>
|
||||||
|
/// <param name="vertexAsCompute">Indicates that the vertex shader has been converted into a compute shader</param>
|
||||||
/// <param name="usesDrawParameters">Indicates whether the vertex shader accesses draw parameters</param>
|
/// <param name="usesDrawParameters">Indicates whether the vertex shader accesses draw parameters</param>
|
||||||
/// <param name="checkTextures">Indicates whether texture descriptors should be checked</param>
|
/// <param name="checkTextures">Indicates whether texture descriptors should be checked</param>
|
||||||
/// <returns>True if the state matches, false otherwise</returns>
|
/// <returns>True if the state matches, false otherwise</returns>
|
||||||
|
@ -464,6 +465,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
GpuChannel channel,
|
GpuChannel channel,
|
||||||
ref GpuChannelPoolState poolState,
|
ref GpuChannelPoolState poolState,
|
||||||
ref GpuChannelGraphicsState graphicsState,
|
ref GpuChannelGraphicsState graphicsState,
|
||||||
|
bool vertexAsCompute,
|
||||||
bool usesDrawParameters,
|
bool usesDrawParameters,
|
||||||
bool checkTextures)
|
bool checkTextures)
|
||||||
{
|
{
|
||||||
|
@ -497,10 +499,26 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ShaderCache.MayConvertVtgToCompute(ref channel.Capabilities) && !vertexAsCompute)
|
||||||
|
{
|
||||||
|
for (int index = 0; index < graphicsState.AttributeTypes.Length; index++)
|
||||||
|
{
|
||||||
|
AttributeType lType = FilterAttributeType(channel, graphicsState.AttributeTypes[index]);
|
||||||
|
AttributeType rType = FilterAttributeType(channel, GraphicsState.AttributeTypes[index]);
|
||||||
|
|
||||||
|
if (lType != rType)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
if (!graphicsState.AttributeTypes.AsSpan().SequenceEqual(GraphicsState.AttributeTypes.AsSpan()))
|
if (!graphicsState.AttributeTypes.AsSpan().SequenceEqual(GraphicsState.AttributeTypes.AsSpan()))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (usesDrawParameters && graphicsState.HasConstantBufferDrawParameters != GraphicsState.HasConstantBufferDrawParameters)
|
if (usesDrawParameters && graphicsState.HasConstantBufferDrawParameters != GraphicsState.HasConstantBufferDrawParameters)
|
||||||
{
|
{
|
||||||
|
@ -530,6 +548,19 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
return Matches(channel, ref poolState, checkTextures, isCompute: false);
|
return Matches(channel, ref poolState, checkTextures, isCompute: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static AttributeType FilterAttributeType(GpuChannel channel, AttributeType type)
|
||||||
|
{
|
||||||
|
type &= ~(AttributeType.Packed | AttributeType.PackedRgb10A2Signed);
|
||||||
|
|
||||||
|
if (channel.Capabilities.SupportsScaledVertexFormats &&
|
||||||
|
(type == AttributeType.Sscaled || type == AttributeType.Uscaled))
|
||||||
|
{
|
||||||
|
type = AttributeType.Float;
|
||||||
|
}
|
||||||
|
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Checks if the recorded state matches the current GPU compute engine state.
|
/// Checks if the recorded state matches the current GPU compute engine state.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -29,6 +29,7 @@ namespace Ryujinx.Graphics.OpenGL
|
||||||
|
|
||||||
private static readonly Lazy<int> _maximumComputeSharedMemorySize = new(() => GetLimit(All.MaxComputeSharedMemorySize));
|
private static readonly Lazy<int> _maximumComputeSharedMemorySize = new(() => GetLimit(All.MaxComputeSharedMemorySize));
|
||||||
private static readonly Lazy<int> _storageBufferOffsetAlignment = new(() => GetLimit(All.ShaderStorageBufferOffsetAlignment));
|
private static readonly Lazy<int> _storageBufferOffsetAlignment = new(() => GetLimit(All.ShaderStorageBufferOffsetAlignment));
|
||||||
|
private static readonly Lazy<int> _textureBufferOffsetAlignment = new(() => GetLimit(All.TextureBufferOffsetAlignment));
|
||||||
|
|
||||||
public enum GpuVendor
|
public enum GpuVendor
|
||||||
{
|
{
|
||||||
|
@ -78,6 +79,7 @@ namespace Ryujinx.Graphics.OpenGL
|
||||||
|
|
||||||
public static int MaximumComputeSharedMemorySize => _maximumComputeSharedMemorySize.Value;
|
public static int MaximumComputeSharedMemorySize => _maximumComputeSharedMemorySize.Value;
|
||||||
public static int StorageBufferOffsetAlignment => _storageBufferOffsetAlignment.Value;
|
public static int StorageBufferOffsetAlignment => _storageBufferOffsetAlignment.Value;
|
||||||
|
public static int TextureBufferOffsetAlignment => _textureBufferOffsetAlignment.Value;
|
||||||
|
|
||||||
public static float MaximumSupportedAnisotropy => _maxSupportedAnisotropy.Value;
|
public static float MaximumSupportedAnisotropy => _maxSupportedAnisotropy.Value;
|
||||||
|
|
||||||
|
|
|
@ -164,6 +164,7 @@ namespace Ryujinx.Graphics.OpenGL
|
||||||
supportsShaderBarrierDivergence: !(intelWindows || intelUnix),
|
supportsShaderBarrierDivergence: !(intelWindows || intelUnix),
|
||||||
supportsShaderFloat64: true,
|
supportsShaderFloat64: true,
|
||||||
supportsTextureShadowLod: HwCapabilities.SupportsTextureShadowLod,
|
supportsTextureShadowLod: HwCapabilities.SupportsTextureShadowLod,
|
||||||
|
supportsVertexStoreAndAtomics: true,
|
||||||
supportsViewportIndexVertexTessellation: HwCapabilities.SupportsShaderViewportLayerArray,
|
supportsViewportIndexVertexTessellation: HwCapabilities.SupportsShaderViewportLayerArray,
|
||||||
supportsViewportMask: HwCapabilities.SupportsViewportArray2,
|
supportsViewportMask: HwCapabilities.SupportsViewportArray2,
|
||||||
supportsViewportSwizzle: HwCapabilities.SupportsViewportSwizzle,
|
supportsViewportSwizzle: HwCapabilities.SupportsViewportSwizzle,
|
||||||
|
@ -177,6 +178,7 @@ namespace Ryujinx.Graphics.OpenGL
|
||||||
maximumSupportedAnisotropy: HwCapabilities.MaximumSupportedAnisotropy,
|
maximumSupportedAnisotropy: HwCapabilities.MaximumSupportedAnisotropy,
|
||||||
shaderSubgroupSize: Constants.MaxSubgroupSize,
|
shaderSubgroupSize: Constants.MaxSubgroupSize,
|
||||||
storageBufferOffsetAlignment: HwCapabilities.StorageBufferOffsetAlignment,
|
storageBufferOffsetAlignment: HwCapabilities.StorageBufferOffsetAlignment,
|
||||||
|
textureBufferOffsetAlignment: HwCapabilities.TextureBufferOffsetAlignment,
|
||||||
gatherBiasPrecision: intelWindows || amdWindows ? 8 : 0); // Precision is 8 for these vendors on Vulkan.
|
gatherBiasPrecision: intelWindows || amdWindows ? 8 : 0); // Precision is 8 for these vendors on Vulkan.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,13 +11,17 @@ namespace Ryujinx.Graphics.Shader
|
||||||
Uint,
|
Uint,
|
||||||
Sscaled,
|
Sscaled,
|
||||||
Uscaled,
|
Uscaled,
|
||||||
|
|
||||||
|
Packed = 1 << 6,
|
||||||
|
PackedRgb10A2Signed = 1 << 7,
|
||||||
|
AnyPacked = Packed | PackedRgb10A2Signed,
|
||||||
}
|
}
|
||||||
|
|
||||||
static class AttributeTypeExtensions
|
static class AttributeTypeExtensions
|
||||||
{
|
{
|
||||||
public static AggregateType ToAggregateType(this AttributeType type)
|
public static AggregateType ToAggregateType(this AttributeType type)
|
||||||
{
|
{
|
||||||
return type switch
|
return (type & ~AttributeType.AnyPacked) switch
|
||||||
{
|
{
|
||||||
AttributeType.Float => AggregateType.FP32,
|
AttributeType.Float => AggregateType.FP32,
|
||||||
AttributeType.Sint => AggregateType.S32,
|
AttributeType.Sint => AggregateType.S32,
|
||||||
|
@ -28,7 +32,7 @@ namespace Ryujinx.Graphics.Shader
|
||||||
|
|
||||||
public static AggregateType ToAggregateType(this AttributeType type, bool supportsScaledFormats)
|
public static AggregateType ToAggregateType(this AttributeType type, bool supportsScaledFormats)
|
||||||
{
|
{
|
||||||
return type switch
|
return (type & ~AttributeType.AnyPacked) switch
|
||||||
{
|
{
|
||||||
AttributeType.Float => AggregateType.FP32,
|
AttributeType.Float => AggregateType.FP32,
|
||||||
AttributeType.Sint => AggregateType.S32,
|
AttributeType.Sint => AggregateType.S32,
|
||||||
|
|
|
@ -100,10 +100,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
string outPrimitive = context.Definitions.OutputTopology.ToGlslString();
|
string outPrimitive = context.Definitions.OutputTopology.ToGlslString();
|
||||||
|
int maxOutputVertices = context.Definitions.MaxOutputVertices;
|
||||||
int maxOutputVertices = context.Definitions.GpPassthrough
|
|
||||||
? context.Definitions.InputTopology.ToInputVertices()
|
|
||||||
: context.Definitions.MaxOutputVertices;
|
|
||||||
|
|
||||||
context.AppendLine($"layout ({outPrimitive}, max_vertices = {maxOutputVertices}) out;");
|
context.AppendLine($"layout ({outPrimitive}, max_vertices = {maxOutputVertices}) out;");
|
||||||
}
|
}
|
||||||
|
@ -320,6 +317,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
||||||
{
|
{
|
||||||
string typeName = GetVarTypeName(context, memory.Type & ~AggregateType.Array);
|
string typeName = GetVarTypeName(context, memory.Type & ~AggregateType.Array);
|
||||||
|
|
||||||
|
if (memory.Type.HasFlag(AggregateType.Array))
|
||||||
|
{
|
||||||
if (memory.ArrayLength > 0)
|
if (memory.ArrayLength > 0)
|
||||||
{
|
{
|
||||||
string arraySize = memory.ArrayLength.ToString(CultureInfo.InvariantCulture);
|
string arraySize = memory.ArrayLength.ToString(CultureInfo.InvariantCulture);
|
||||||
|
@ -331,6 +330,11 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
||||||
context.AppendLine($"{prefix}{typeName} {memory.Name}[];");
|
context.AppendLine($"{prefix}{typeName} {memory.Name}[];");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
context.AppendLine($"{prefix}{typeName} {memory.Name};");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void DeclareSamplers(CodeGenContext context, IEnumerable<TextureDefinition> definitions)
|
private static void DeclareSamplers(CodeGenContext context, IEnumerable<TextureDefinition> definitions)
|
||||||
|
|
|
@ -31,6 +31,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
||||||
IoVariable.FrontColorDiffuse => ("gl_FrontColor", AggregateType.Vector4 | AggregateType.FP32), // Deprecated.
|
IoVariable.FrontColorDiffuse => ("gl_FrontColor", AggregateType.Vector4 | AggregateType.FP32), // Deprecated.
|
||||||
IoVariable.FrontColorSpecular => ("gl_FrontSecondaryColor", AggregateType.Vector4 | AggregateType.FP32), // Deprecated.
|
IoVariable.FrontColorSpecular => ("gl_FrontSecondaryColor", AggregateType.Vector4 | AggregateType.FP32), // Deprecated.
|
||||||
IoVariable.FrontFacing => ("gl_FrontFacing", AggregateType.Bool),
|
IoVariable.FrontFacing => ("gl_FrontFacing", AggregateType.Bool),
|
||||||
|
IoVariable.GlobalId => ("gl_GlobalInvocationID", AggregateType.Vector3 | AggregateType.U32),
|
||||||
IoVariable.InstanceId => ("gl_InstanceID", AggregateType.S32),
|
IoVariable.InstanceId => ("gl_InstanceID", AggregateType.S32),
|
||||||
IoVariable.InstanceIndex => ("gl_InstanceIndex", AggregateType.S32),
|
IoVariable.InstanceIndex => ("gl_InstanceIndex", AggregateType.S32),
|
||||||
IoVariable.InvocationId => ("gl_InvocationID", AggregateType.S32),
|
IoVariable.InvocationId => ("gl_InvocationID", AggregateType.S32),
|
||||||
|
|
|
@ -27,8 +27,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||||
public ILogger Logger { get; }
|
public ILogger Logger { get; }
|
||||||
public TargetApi TargetApi { get; }
|
public TargetApi TargetApi { get; }
|
||||||
|
|
||||||
public int InputVertices { get; }
|
|
||||||
|
|
||||||
public Dictionary<int, Instruction> ConstantBuffers { get; } = new();
|
public Dictionary<int, Instruction> ConstantBuffers { get; } = new();
|
||||||
public Dictionary<int, Instruction> StorageBuffers { get; } = new();
|
public Dictionary<int, Instruction> StorageBuffers { get; } = new();
|
||||||
|
|
||||||
|
@ -101,19 +99,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||||
Logger = parameters.Logger;
|
Logger = parameters.Logger;
|
||||||
TargetApi = parameters.TargetApi;
|
TargetApi = parameters.TargetApi;
|
||||||
|
|
||||||
if (parameters.Definitions.Stage == ShaderStage.Geometry)
|
|
||||||
{
|
|
||||||
InputVertices = parameters.Definitions.InputTopology switch
|
|
||||||
{
|
|
||||||
InputTopology.Points => 1,
|
|
||||||
InputTopology.Lines => 2,
|
|
||||||
InputTopology.LinesAdjacency => 2,
|
|
||||||
InputTopology.Triangles => 3,
|
|
||||||
InputTopology.TrianglesAdjacency => 3,
|
|
||||||
_ => throw new InvalidOperationException($"Invalid input topology \"{parameters.Definitions.InputTopology}\"."),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
AddCapability(Capability.Shader);
|
AddCapability(Capability.Shader);
|
||||||
AddCapability(Capability.Float64);
|
AddCapability(Capability.Float64);
|
||||||
|
|
||||||
|
|
|
@ -369,7 +369,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||||
if (context.Definitions.Stage != ShaderStage.Vertex)
|
if (context.Definitions.Stage != ShaderStage.Vertex)
|
||||||
{
|
{
|
||||||
var perVertexInputStructType = CreatePerVertexStructType(context);
|
var perVertexInputStructType = CreatePerVertexStructType(context);
|
||||||
int arraySize = context.Definitions.Stage == ShaderStage.Geometry ? context.InputVertices : 32;
|
int arraySize = context.Definitions.Stage == ShaderStage.Geometry ? context.Definitions.InputTopology.ToInputVertices() : 32;
|
||||||
var perVertexInputArrayType = context.TypeArray(perVertexInputStructType, context.Constant(context.TypeU32(), arraySize));
|
var perVertexInputArrayType = context.TypeArray(perVertexInputStructType, context.Constant(context.TypeU32(), arraySize));
|
||||||
var perVertexInputPointerType = context.TypePointer(StorageClass.Input, perVertexInputArrayType);
|
var perVertexInputPointerType = context.TypePointer(StorageClass.Input, perVertexInputArrayType);
|
||||||
var perVertexInputVariable = context.Variable(perVertexInputPointerType, StorageClass.Input);
|
var perVertexInputVariable = context.Variable(perVertexInputPointerType, StorageClass.Input);
|
||||||
|
@ -506,7 +506,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||||
|
|
||||||
if (!isPerPatch && IoMap.IsPerVertex(ioVariable, context.Definitions.Stage, isOutput))
|
if (!isPerPatch && IoMap.IsPerVertex(ioVariable, context.Definitions.Stage, isOutput))
|
||||||
{
|
{
|
||||||
int arraySize = context.Definitions.Stage == ShaderStage.Geometry ? context.InputVertices : 32;
|
int arraySize = context.Definitions.Stage == ShaderStage.Geometry ? context.Definitions.InputTopology.ToInputVertices() : 32;
|
||||||
spvType = context.TypeArray(spvType, context.Constant(context.TypeU32(), arraySize));
|
spvType = context.TypeArray(spvType, context.Constant(context.TypeU32(), arraySize));
|
||||||
|
|
||||||
if (context.Definitions.GpPassthrough && context.HostCapabilities.SupportsGeometryShaderPassthrough)
|
if (context.Definitions.GpPassthrough && context.HostCapabilities.SupportsGeometryShaderPassthrough)
|
||||||
|
|
|
@ -22,6 +22,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||||
IoVariable.FragmentCoord => (BuiltIn.FragCoord, AggregateType.Vector4 | AggregateType.FP32),
|
IoVariable.FragmentCoord => (BuiltIn.FragCoord, AggregateType.Vector4 | AggregateType.FP32),
|
||||||
IoVariable.FragmentOutputDepth => (BuiltIn.FragDepth, AggregateType.FP32),
|
IoVariable.FragmentOutputDepth => (BuiltIn.FragDepth, AggregateType.FP32),
|
||||||
IoVariable.FrontFacing => (BuiltIn.FrontFacing, AggregateType.Bool),
|
IoVariable.FrontFacing => (BuiltIn.FrontFacing, AggregateType.Bool),
|
||||||
|
IoVariable.GlobalId => (BuiltIn.GlobalInvocationId, AggregateType.Vector3 | AggregateType.U32),
|
||||||
IoVariable.InstanceId => (BuiltIn.InstanceId, AggregateType.S32),
|
IoVariable.InstanceId => (BuiltIn.InstanceId, AggregateType.S32),
|
||||||
IoVariable.InstanceIndex => (BuiltIn.InstanceIndex, AggregateType.S32),
|
IoVariable.InstanceIndex => (BuiltIn.InstanceIndex, AggregateType.S32),
|
||||||
IoVariable.InvocationId => (BuiltIn.InvocationId, AggregateType.S32),
|
IoVariable.InvocationId => (BuiltIn.InvocationId, AggregateType.S32),
|
||||||
|
|
|
@ -239,9 +239,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||||
_ => throw new InvalidOperationException($"Invalid output topology \"{context.Definitions.OutputTopology}\"."),
|
_ => throw new InvalidOperationException($"Invalid output topology \"{context.Definitions.OutputTopology}\"."),
|
||||||
});
|
});
|
||||||
|
|
||||||
int maxOutputVertices = context.Definitions.GpPassthrough ? context.InputVertices : context.Definitions.MaxOutputVertices;
|
context.AddExecutionMode(spvFunc, ExecutionMode.OutputVertices, (SpvLiteralInteger)context.Definitions.MaxOutputVertices);
|
||||||
|
|
||||||
context.AddExecutionMode(spvFunc, ExecutionMode.OutputVertices, (SpvLiteralInteger)maxOutputVertices);
|
|
||||||
}
|
}
|
||||||
else if (context.Definitions.Stage == ShaderStage.Fragment)
|
else if (context.Definitions.Stage == ShaderStage.Fragment)
|
||||||
{
|
{
|
||||||
|
@ -279,6 +277,14 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||||
localSizeZ);
|
localSizeZ);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (context.Definitions.Stage != ShaderStage.Fragment &&
|
||||||
|
context.Definitions.Stage != ShaderStage.Geometry &&
|
||||||
|
context.Definitions.Stage != ShaderStage.Compute &&
|
||||||
|
context.Info.IoDefinitions.Contains(new IoDefinition(StorageKind.Output, IoVariable.Layer)))
|
||||||
|
{
|
||||||
|
context.AddCapability(Capability.ShaderLayer);
|
||||||
|
}
|
||||||
|
|
||||||
if (context.Definitions.TransformFeedbackEnabled && context.Definitions.LastInVertexPipeline)
|
if (context.Definitions.TransformFeedbackEnabled && context.Definitions.LastInVertexPipeline)
|
||||||
{
|
{
|
||||||
context.AddExecutionMode(spvFunc, ExecutionMode.Xfb);
|
context.AddExecutionMode(spvFunc, ExecutionMode.Xfb);
|
||||||
|
|
|
@ -10,11 +10,5 @@ namespace Ryujinx.Graphics.Shader
|
||||||
public const int NvnBaseVertexByteOffset = 0x640;
|
public const int NvnBaseVertexByteOffset = 0x640;
|
||||||
public const int NvnBaseInstanceByteOffset = 0x644;
|
public const int NvnBaseInstanceByteOffset = 0x644;
|
||||||
public const int NvnDrawIndexByteOffset = 0x648;
|
public const int NvnDrawIndexByteOffset = 0x648;
|
||||||
|
|
||||||
// Transform Feedback emulation.
|
|
||||||
|
|
||||||
public const int TfeInfoBinding = 0;
|
|
||||||
public const int TfeBufferBaseBinding = 1;
|
|
||||||
public const int TfeBuffersCount = 4;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,6 +60,11 @@ namespace Ryujinx.Graphics.Shader.Decoders
|
||||||
_functionsWithId.Add(function);
|
_functionsWithId.Add(function);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IoUsage GetIoUsage()
|
||||||
|
{
|
||||||
|
return new IoUsage(UsedFeatures, ClipDistancesWritten, AttributeUsage.UsedOutputAttributes);
|
||||||
|
}
|
||||||
|
|
||||||
public IEnumerator<DecodedFunction> GetEnumerator()
|
public IEnumerator<DecodedFunction> GetEnumerator()
|
||||||
{
|
{
|
||||||
return _functions.Values.GetEnumerator();
|
return _functions.Values.GetEnumerator();
|
||||||
|
|
|
@ -297,6 +297,9 @@ namespace Ryujinx.Graphics.Shader.Decoders
|
||||||
case InstName.Ssy:
|
case InstName.Ssy:
|
||||||
block.AddPushOp(op);
|
block.AddPushOp(op);
|
||||||
break;
|
break;
|
||||||
|
case InstName.Shfl:
|
||||||
|
context.SetUsedFeature(FeatureFlags.Shuffle);
|
||||||
|
break;
|
||||||
case InstName.Ldl:
|
case InstName.Ldl:
|
||||||
case InstName.Stl:
|
case InstName.Stl:
|
||||||
context.SetUsedFeature(FeatureFlags.LocalMemory);
|
context.SetUsedFeature(FeatureFlags.LocalMemory);
|
||||||
|
@ -307,8 +310,22 @@ namespace Ryujinx.Graphics.Shader.Decoders
|
||||||
case InstName.Sts:
|
case InstName.Sts:
|
||||||
context.SetUsedFeature(FeatureFlags.SharedMemory);
|
context.SetUsedFeature(FeatureFlags.SharedMemory);
|
||||||
break;
|
break;
|
||||||
case InstName.Shfl:
|
case InstName.Atom:
|
||||||
context.SetUsedFeature(FeatureFlags.Shuffle);
|
case InstName.AtomCas:
|
||||||
|
case InstName.Red:
|
||||||
|
case InstName.Stg:
|
||||||
|
case InstName.Suatom:
|
||||||
|
case InstName.SuatomB:
|
||||||
|
case InstName.SuatomB2:
|
||||||
|
case InstName.SuatomCas:
|
||||||
|
case InstName.SuatomCasB:
|
||||||
|
case InstName.Sured:
|
||||||
|
case InstName.SuredB:
|
||||||
|
case InstName.Sust:
|
||||||
|
case InstName.SustB:
|
||||||
|
case InstName.SustD:
|
||||||
|
case InstName.SustDB:
|
||||||
|
context.SetUsedFeature(FeatureFlags.Store);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -424,6 +441,12 @@ namespace Ryujinx.Graphics.Shader.Decoders
|
||||||
context.SetUsedFeature(FeatureFlags.RtLayer);
|
context.SetUsedFeature(FeatureFlags.RtLayer);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case AttributeConsts.ViewportIndex:
|
||||||
|
if (definitions.Stage != ShaderStage.Fragment)
|
||||||
|
{
|
||||||
|
context.SetUsedFeature(FeatureFlags.ViewportIndex);
|
||||||
|
}
|
||||||
|
break;
|
||||||
case AttributeConsts.ClipDistance0:
|
case AttributeConsts.ClipDistance0:
|
||||||
case AttributeConsts.ClipDistance1:
|
case AttributeConsts.ClipDistance1:
|
||||||
case AttributeConsts.ClipDistance2:
|
case AttributeConsts.ClipDistance2:
|
||||||
|
@ -432,11 +455,17 @@ namespace Ryujinx.Graphics.Shader.Decoders
|
||||||
case AttributeConsts.ClipDistance5:
|
case AttributeConsts.ClipDistance5:
|
||||||
case AttributeConsts.ClipDistance6:
|
case AttributeConsts.ClipDistance6:
|
||||||
case AttributeConsts.ClipDistance7:
|
case AttributeConsts.ClipDistance7:
|
||||||
if (definitions.Stage == ShaderStage.Vertex)
|
if (definitions.Stage.IsVtg())
|
||||||
{
|
{
|
||||||
context.SetClipDistanceWritten((attr - AttributeConsts.ClipDistance0) / 4);
|
context.SetClipDistanceWritten((attr - AttributeConsts.ClipDistance0) / 4);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case AttributeConsts.ViewportMask:
|
||||||
|
if (definitions.Stage != ShaderStage.Fragment)
|
||||||
|
{
|
||||||
|
context.SetUsedFeature(FeatureFlags.ViewportMask);
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
|
@ -25,6 +25,19 @@ namespace Ryujinx.Graphics.Shader
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int ToInputVertices(this InputTopology topology)
|
public static int ToInputVertices(this InputTopology topology)
|
||||||
|
{
|
||||||
|
return topology switch
|
||||||
|
{
|
||||||
|
InputTopology.Points => 1,
|
||||||
|
InputTopology.Lines => 2,
|
||||||
|
InputTopology.LinesAdjacency => 4,
|
||||||
|
InputTopology.Triangles => 3,
|
||||||
|
InputTopology.TrianglesAdjacency => 6,
|
||||||
|
_ => 1,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int ToInputVerticesNoAdjacency(this InputTopology topology)
|
||||||
{
|
{
|
||||||
return topology switch
|
return topology switch
|
||||||
{
|
{
|
||||||
|
|
|
@ -63,7 +63,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
||||||
{
|
{
|
||||||
value = AttributeMap.GenerateAttributeLoad(context, primVertex, offset, isOutput, op.P);
|
value = AttributeMap.GenerateAttributeLoad(context, primVertex, offset, isOutput, op.P);
|
||||||
|
|
||||||
if (!context.TranslatorContext.Definitions.SupportsScaledVertexFormats &&
|
if ((!context.TranslatorContext.Definitions.SupportsScaledVertexFormats || context.VertexAsCompute) &&
|
||||||
context.TranslatorContext.Stage == ShaderStage.Vertex &&
|
context.TranslatorContext.Stage == ShaderStage.Vertex &&
|
||||||
!op.O &&
|
!op.O &&
|
||||||
offset >= 0x80 &&
|
offset >= 0x80 &&
|
||||||
|
|
|
@ -18,6 +18,7 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
|
||||||
FrontColorDiffuse,
|
FrontColorDiffuse,
|
||||||
FrontColorSpecular,
|
FrontColorSpecular,
|
||||||
FrontFacing,
|
FrontFacing,
|
||||||
|
GlobalId,
|
||||||
InstanceId,
|
InstanceId,
|
||||||
InstanceIndex,
|
InstanceIndex,
|
||||||
InvocationId,
|
InvocationId,
|
||||||
|
|
22
src/Ryujinx.Graphics.Shader/ResourceReservationCounts.cs
Normal file
22
src/Ryujinx.Graphics.Shader/ResourceReservationCounts.cs
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
using Ryujinx.Graphics.Shader.Translation;
|
||||||
|
|
||||||
|
namespace Ryujinx.Graphics.Shader
|
||||||
|
{
|
||||||
|
public readonly struct ResourceReservationCounts
|
||||||
|
{
|
||||||
|
public readonly int ReservedConstantBuffers { get; }
|
||||||
|
public readonly int ReservedStorageBuffers { get; }
|
||||||
|
public readonly int ReservedTextures { get; }
|
||||||
|
public readonly int ReservedImages { get; }
|
||||||
|
|
||||||
|
public ResourceReservationCounts(bool isTransformFeedbackEmulated, bool vertexAsCompute)
|
||||||
|
{
|
||||||
|
ResourceReservations reservations = new(isTransformFeedbackEmulated, vertexAsCompute);
|
||||||
|
|
||||||
|
ReservedConstantBuffers = reservations.ReservedConstantBuffers;
|
||||||
|
ReservedStorageBuffers = reservations.ReservedStorageBuffers;
|
||||||
|
ReservedTextures = reservations.ReservedTextures;
|
||||||
|
ReservedImages = reservations.ReservedImages;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,8 +0,0 @@
|
||||||
namespace Ryujinx.Graphics.Shader
|
|
||||||
{
|
|
||||||
public enum ShaderIdentification
|
|
||||||
{
|
|
||||||
None,
|
|
||||||
GeometryLayerPassthrough,
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -10,9 +10,10 @@ namespace Ryujinx.Graphics.Shader
|
||||||
public ReadOnlyCollection<TextureDescriptor> Textures { get; }
|
public ReadOnlyCollection<TextureDescriptor> Textures { get; }
|
||||||
public ReadOnlyCollection<TextureDescriptor> Images { get; }
|
public ReadOnlyCollection<TextureDescriptor> Images { get; }
|
||||||
|
|
||||||
public ShaderIdentification Identification { get; }
|
|
||||||
public int GpLayerInputAttribute { get; }
|
|
||||||
public ShaderStage Stage { get; }
|
public ShaderStage Stage { get; }
|
||||||
|
public int GeometryVerticesPerPrimitive { get; }
|
||||||
|
public int GeometryMaxOutputVertices { get; }
|
||||||
|
public int ThreadsPerInputPrimitive { get; }
|
||||||
public bool UsesFragCoord { get; }
|
public bool UsesFragCoord { get; }
|
||||||
public bool UsesInstanceId { get; }
|
public bool UsesInstanceId { get; }
|
||||||
public bool UsesDrawParameters { get; }
|
public bool UsesDrawParameters { get; }
|
||||||
|
@ -25,9 +26,10 @@ namespace Ryujinx.Graphics.Shader
|
||||||
BufferDescriptor[] sBuffers,
|
BufferDescriptor[] sBuffers,
|
||||||
TextureDescriptor[] textures,
|
TextureDescriptor[] textures,
|
||||||
TextureDescriptor[] images,
|
TextureDescriptor[] images,
|
||||||
ShaderIdentification identification,
|
|
||||||
int gpLayerInputAttribute,
|
|
||||||
ShaderStage stage,
|
ShaderStage stage,
|
||||||
|
int geometryVerticesPerPrimitive,
|
||||||
|
int geometryMaxOutputVertices,
|
||||||
|
int threadsPerInputPrimitive,
|
||||||
bool usesFragCoord,
|
bool usesFragCoord,
|
||||||
bool usesInstanceId,
|
bool usesInstanceId,
|
||||||
bool usesDrawParameters,
|
bool usesDrawParameters,
|
||||||
|
@ -40,9 +42,10 @@ namespace Ryujinx.Graphics.Shader
|
||||||
Textures = Array.AsReadOnly(textures);
|
Textures = Array.AsReadOnly(textures);
|
||||||
Images = Array.AsReadOnly(images);
|
Images = Array.AsReadOnly(images);
|
||||||
|
|
||||||
Identification = identification;
|
|
||||||
GpLayerInputAttribute = gpLayerInputAttribute;
|
|
||||||
Stage = stage;
|
Stage = stage;
|
||||||
|
GeometryVerticesPerPrimitive = geometryVerticesPerPrimitive;
|
||||||
|
GeometryMaxOutputVertices = geometryMaxOutputVertices;
|
||||||
|
ThreadsPerInputPrimitive = threadsPerInputPrimitive;
|
||||||
UsesFragCoord = usesFragCoord;
|
UsesFragCoord = usesFragCoord;
|
||||||
UsesInstanceId = usesInstanceId;
|
UsesInstanceId = usesInstanceId;
|
||||||
UsesDrawParameters = usesDrawParameters;
|
UsesDrawParameters = usesDrawParameters;
|
||||||
|
|
|
@ -22,11 +22,13 @@ namespace Ryujinx.Graphics.Shader
|
||||||
ViewportSize,
|
ViewportSize,
|
||||||
FragmentRenderScaleCount,
|
FragmentRenderScaleCount,
|
||||||
RenderScale,
|
RenderScale,
|
||||||
|
TfeOffset,
|
||||||
|
TfeVertexCount,
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct SupportBuffer
|
public struct SupportBuffer
|
||||||
{
|
{
|
||||||
internal const int Binding = 0;
|
public const int Binding = 0;
|
||||||
|
|
||||||
public static readonly int FieldSize;
|
public static readonly int FieldSize;
|
||||||
public static readonly int RequiredSize;
|
public static readonly int RequiredSize;
|
||||||
|
@ -38,6 +40,8 @@ namespace Ryujinx.Graphics.Shader
|
||||||
public static readonly int FragmentRenderScaleCountOffset;
|
public static readonly int FragmentRenderScaleCountOffset;
|
||||||
public static readonly int GraphicsRenderScaleOffset;
|
public static readonly int GraphicsRenderScaleOffset;
|
||||||
public static readonly int ComputeRenderScaleOffset;
|
public static readonly int ComputeRenderScaleOffset;
|
||||||
|
public static readonly int TfeOffsetOffset;
|
||||||
|
public static readonly int TfeVertexCountOffset;
|
||||||
|
|
||||||
public const int FragmentIsBgraCount = 8;
|
public const int FragmentIsBgraCount = 8;
|
||||||
// One for the render target, 64 for the textures, and 8 for the images.
|
// One for the render target, 64 for the textures, and 8 for the images.
|
||||||
|
@ -62,18 +66,22 @@ namespace Ryujinx.Graphics.Shader
|
||||||
FragmentRenderScaleCountOffset = OffsetOf(ref instance, ref instance.FragmentRenderScaleCount);
|
FragmentRenderScaleCountOffset = OffsetOf(ref instance, ref instance.FragmentRenderScaleCount);
|
||||||
GraphicsRenderScaleOffset = OffsetOf(ref instance, ref instance.RenderScale);
|
GraphicsRenderScaleOffset = OffsetOf(ref instance, ref instance.RenderScale);
|
||||||
ComputeRenderScaleOffset = GraphicsRenderScaleOffset + FieldSize;
|
ComputeRenderScaleOffset = GraphicsRenderScaleOffset + FieldSize;
|
||||||
|
TfeOffsetOffset = OffsetOf(ref instance, ref instance.TfeOffset);
|
||||||
|
TfeVertexCountOffset = OffsetOf(ref instance, ref instance.TfeVertexCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static StructureType GetStructureType()
|
internal static StructureType GetStructureType()
|
||||||
{
|
{
|
||||||
return new StructureType(new[]
|
return new StructureType(new[]
|
||||||
{
|
{
|
||||||
new StructureField(AggregateType.U32, "s_alpha_test"),
|
new StructureField(AggregateType.U32, "alpha_test"),
|
||||||
new StructureField(AggregateType.Array | AggregateType.U32, "s_is_bgra", FragmentIsBgraCount),
|
new StructureField(AggregateType.Array | AggregateType.U32, "is_bgra", FragmentIsBgraCount),
|
||||||
new StructureField(AggregateType.Vector4 | AggregateType.FP32, "s_viewport_inverse"),
|
new StructureField(AggregateType.Vector4 | AggregateType.FP32, "viewport_inverse"),
|
||||||
new StructureField(AggregateType.Vector4 | AggregateType.FP32, "s_viewport_size"),
|
new StructureField(AggregateType.Vector4 | AggregateType.FP32, "viewport_size"),
|
||||||
new StructureField(AggregateType.S32, "s_frag_scale_count"),
|
new StructureField(AggregateType.S32, "frag_scale_count"),
|
||||||
new StructureField(AggregateType.Array | AggregateType.FP32, "s_render_scale", RenderScaleMaxCount),
|
new StructureField(AggregateType.Array | AggregateType.FP32, "render_scale", RenderScaleMaxCount),
|
||||||
|
new StructureField(AggregateType.Vector4 | AggregateType.S32, "tfe_offset"),
|
||||||
|
new StructureField(AggregateType.S32, "tfe_vertex_count"),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,5 +93,8 @@ namespace Ryujinx.Graphics.Shader
|
||||||
|
|
||||||
// Render scale max count: 1 + 64 + 8. First scale is fragment output scale, others are textures/image inputs.
|
// Render scale max count: 1 + 64 + 8. First scale is fragment output scale, others are textures/image inputs.
|
||||||
public Array73<Vector4<float>> RenderScale;
|
public Array73<Vector4<float>> RenderScale;
|
||||||
|
|
||||||
|
public Vector4<int> TfeOffset;
|
||||||
|
public Vector4<int> TfeVertexCount;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||||
{
|
{
|
||||||
public const int PrimitiveId = 0x060;
|
public const int PrimitiveId = 0x060;
|
||||||
public const int Layer = 0x064;
|
public const int Layer = 0x064;
|
||||||
|
public const int ViewportIndex = 0x068;
|
||||||
public const int PositionX = 0x070;
|
public const int PositionX = 0x070;
|
||||||
public const int PositionY = 0x074;
|
public const int PositionY = 0x074;
|
||||||
public const int FrontColorDiffuseR = 0x280;
|
public const int FrontColorDiffuseR = 0x280;
|
||||||
|
@ -24,6 +25,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||||
public const int TexCoordCount = 10;
|
public const int TexCoordCount = 10;
|
||||||
public const int TexCoordBase = 0x300;
|
public const int TexCoordBase = 0x300;
|
||||||
public const int TexCoordEnd = TexCoordBase + TexCoordCount * 16;
|
public const int TexCoordEnd = TexCoordBase + TexCoordCount * 16;
|
||||||
|
public const int ViewportMask = 0x3a0;
|
||||||
public const int FrontFacing = 0x3fc;
|
public const int FrontFacing = 0x3fc;
|
||||||
|
|
||||||
public const int UserAttributesCount = 32;
|
public const int UserAttributesCount = 32;
|
||||||
|
|
|
@ -14,6 +14,8 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||||
public TranslatorContext TranslatorContext { get; }
|
public TranslatorContext TranslatorContext { get; }
|
||||||
public ResourceManager ResourceManager { get; }
|
public ResourceManager ResourceManager { get; }
|
||||||
|
|
||||||
|
public bool VertexAsCompute { get; }
|
||||||
|
|
||||||
public bool IsNonMain { get; }
|
public bool IsNonMain { get; }
|
||||||
|
|
||||||
public Block CurrBlock { get; set; }
|
public Block CurrBlock { get; set; }
|
||||||
|
@ -59,11 +61,13 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||||
TranslatorContext translatorContext,
|
TranslatorContext translatorContext,
|
||||||
ResourceManager resourceManager,
|
ResourceManager resourceManager,
|
||||||
DecodedProgram program,
|
DecodedProgram program,
|
||||||
|
bool vertexAsCompute,
|
||||||
bool isNonMain) : this()
|
bool isNonMain) : this()
|
||||||
{
|
{
|
||||||
TranslatorContext = translatorContext;
|
TranslatorContext = translatorContext;
|
||||||
ResourceManager = resourceManager;
|
ResourceManager = resourceManager;
|
||||||
Program = program;
|
Program = program;
|
||||||
|
VertexAsCompute = vertexAsCompute;
|
||||||
IsNonMain = isNonMain;
|
IsNonMain = isNonMain;
|
||||||
|
|
||||||
EmitStart();
|
EmitStart();
|
||||||
|
@ -71,13 +75,87 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||||
|
|
||||||
private void EmitStart()
|
private void EmitStart()
|
||||||
{
|
{
|
||||||
if (TranslatorContext.Definitions.Stage == ShaderStage.Vertex &&
|
if (TranslatorContext.Options.Flags.HasFlag(TranslationFlags.VertexA))
|
||||||
TranslatorContext.Options.TargetApi == TargetApi.Vulkan &&
|
{
|
||||||
(TranslatorContext.Options.Flags & TranslationFlags.VertexA) == 0)
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TranslatorContext.Definitions.Stage == ShaderStage.Vertex && TranslatorContext.Options.TargetApi == TargetApi.Vulkan)
|
||||||
{
|
{
|
||||||
// Vulkan requires the point size to be always written on the shader if the primitive topology is points.
|
// Vulkan requires the point size to be always written on the shader if the primitive topology is points.
|
||||||
this.Store(StorageKind.Output, IoVariable.PointSize, null, ConstF(TranslatorContext.Definitions.PointSize));
|
this.Store(StorageKind.Output, IoVariable.PointSize, null, ConstF(TranslatorContext.Definitions.PointSize));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (VertexAsCompute)
|
||||||
|
{
|
||||||
|
int vertexInfoCbBinding = ResourceManager.Reservations.VertexInfoConstantBufferBinding;
|
||||||
|
int countFieldIndex = TranslatorContext.Stage == ShaderStage.Vertex
|
||||||
|
? (int)VertexInfoBufferField.VertexCounts
|
||||||
|
: (int)VertexInfoBufferField.GeometryCounts;
|
||||||
|
|
||||||
|
Operand outputVertexOffset = this.Load(StorageKind.Input, IoVariable.GlobalId, Const(0));
|
||||||
|
Operand vertexCount = this.Load(StorageKind.ConstantBuffer, vertexInfoCbBinding, Const(countFieldIndex), Const(0));
|
||||||
|
Operand isVertexOob = this.ICompareGreaterOrEqualUnsigned(outputVertexOffset, vertexCount);
|
||||||
|
|
||||||
|
Operand lblVertexInBounds = Label();
|
||||||
|
|
||||||
|
this.BranchIfFalse(lblVertexInBounds, isVertexOob);
|
||||||
|
this.Return();
|
||||||
|
this.MarkLabel(lblVertexInBounds);
|
||||||
|
|
||||||
|
Operand outputInstanceOffset = this.Load(StorageKind.Input, IoVariable.GlobalId, Const(1));
|
||||||
|
Operand instanceCount = this.Load(StorageKind.ConstantBuffer, vertexInfoCbBinding, Const((int)VertexInfoBufferField.VertexCounts), Const(1));
|
||||||
|
Operand firstVertex = this.Load(StorageKind.ConstantBuffer, vertexInfoCbBinding, Const((int)VertexInfoBufferField.VertexCounts), Const(2));
|
||||||
|
Operand firstInstance = this.Load(StorageKind.ConstantBuffer, vertexInfoCbBinding, Const((int)VertexInfoBufferField.VertexCounts), Const(3));
|
||||||
|
Operand ibBaseOffset = this.Load(StorageKind.ConstantBuffer, vertexInfoCbBinding, Const((int)VertexInfoBufferField.GeometryCounts), Const(3));
|
||||||
|
Operand isInstanceOob = this.ICompareGreaterOrEqualUnsigned(outputInstanceOffset, instanceCount);
|
||||||
|
|
||||||
|
Operand lblInstanceInBounds = Label();
|
||||||
|
|
||||||
|
this.BranchIfFalse(lblInstanceInBounds, isInstanceOob);
|
||||||
|
this.Return();
|
||||||
|
this.MarkLabel(lblInstanceInBounds);
|
||||||
|
|
||||||
|
if (TranslatorContext.Stage == ShaderStage.Vertex)
|
||||||
|
{
|
||||||
|
Operand vertexIndexVr = Local();
|
||||||
|
|
||||||
|
this.TextureSample(
|
||||||
|
SamplerType.TextureBuffer,
|
||||||
|
TextureFlags.IntCoords,
|
||||||
|
ResourceManager.Reservations.IndexBufferTextureBinding,
|
||||||
|
1,
|
||||||
|
new[] { vertexIndexVr },
|
||||||
|
new[] { this.IAdd(ibBaseOffset, outputVertexOffset) });
|
||||||
|
|
||||||
|
this.Store(StorageKind.LocalMemory, ResourceManager.LocalVertexIndexVertexRateMemoryId, this.IAdd(firstVertex, vertexIndexVr));
|
||||||
|
this.Store(StorageKind.LocalMemory, ResourceManager.LocalVertexIndexInstanceRateMemoryId, this.IAdd(firstInstance, outputInstanceOffset));
|
||||||
|
}
|
||||||
|
else if (TranslatorContext.Stage == ShaderStage.Geometry)
|
||||||
|
{
|
||||||
|
int inputVertices = TranslatorContext.Definitions.InputTopology.ToInputVertices();
|
||||||
|
|
||||||
|
Operand baseVertex = this.IMultiply(outputVertexOffset, Const(inputVertices));
|
||||||
|
|
||||||
|
for (int index = 0; index < inputVertices; index++)
|
||||||
|
{
|
||||||
|
Operand vertexIndex = Local();
|
||||||
|
|
||||||
|
this.TextureSample(
|
||||||
|
SamplerType.TextureBuffer,
|
||||||
|
TextureFlags.IntCoords,
|
||||||
|
ResourceManager.Reservations.TopologyRemapBufferTextureBinding,
|
||||||
|
1,
|
||||||
|
new[] { vertexIndex },
|
||||||
|
new[] { this.IAdd(baseVertex, Const(index)) });
|
||||||
|
|
||||||
|
this.Store(StorageKind.LocalMemory, ResourceManager.LocalTopologyRemapMemoryId, Const(index), vertexIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.Store(StorageKind.LocalMemory, ResourceManager.LocalGeometryOutputVertexCountMemoryId, Const(0));
|
||||||
|
this.Store(StorageKind.LocalMemory, ResourceManager.LocalGeometryOutputIndexCountMemoryId, Const(0));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public T GetOp<T>() where T : unmanaged
|
public T GetOp<T>() where T : unmanaged
|
||||||
|
@ -166,16 +244,21 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||||
|
|
||||||
public void PrepareForVertexReturn()
|
public void PrepareForVertexReturn()
|
||||||
{
|
{
|
||||||
if (!TranslatorContext.GpuAccessor.QueryHostSupportsTransformFeedback() && TranslatorContext.GpuAccessor.QueryTransformFeedbackEnabled())
|
// TODO: Support transform feedback emulation on stages other than vertex.
|
||||||
{
|
// Those stages might produce more primitives, so it needs a way to "compact" the output after it is written.
|
||||||
Operand vertexCount = this.Load(StorageKind.StorageBuffer, Constants.TfeInfoBinding, Const(1));
|
|
||||||
|
|
||||||
for (int tfbIndex = 0; tfbIndex < Constants.TfeBuffersCount; tfbIndex++)
|
if (!TranslatorContext.GpuAccessor.QueryHostSupportsTransformFeedback() &&
|
||||||
|
TranslatorContext.GpuAccessor.QueryTransformFeedbackEnabled() &&
|
||||||
|
TranslatorContext.Stage == ShaderStage.Vertex)
|
||||||
|
{
|
||||||
|
Operand vertexCount = this.Load(StorageKind.ConstantBuffer, SupportBuffer.Binding, Const((int)SupportBufferField.TfeVertexCount));
|
||||||
|
|
||||||
|
for (int tfbIndex = 0; tfbIndex < ResourceReservations.TfeBuffersCount; tfbIndex++)
|
||||||
{
|
{
|
||||||
var locations = TranslatorContext.GpuAccessor.QueryTransformFeedbackVaryingLocations(tfbIndex);
|
var locations = TranslatorContext.GpuAccessor.QueryTransformFeedbackVaryingLocations(tfbIndex);
|
||||||
var stride = TranslatorContext.GpuAccessor.QueryTransformFeedbackStride(tfbIndex);
|
var stride = TranslatorContext.GpuAccessor.QueryTransformFeedbackStride(tfbIndex);
|
||||||
|
|
||||||
Operand baseOffset = this.Load(StorageKind.StorageBuffer, Constants.TfeInfoBinding, Const(0), Const(tfbIndex));
|
Operand baseOffset = this.Load(StorageKind.ConstantBuffer, SupportBuffer.Binding, Const((int)SupportBufferField.TfeOffset), Const(tfbIndex));
|
||||||
Operand baseVertex = this.Load(StorageKind.Input, IoVariable.BaseVertex);
|
Operand baseVertex = this.Load(StorageKind.Input, IoVariable.BaseVertex);
|
||||||
Operand baseInstance = this.Load(StorageKind.Input, IoVariable.BaseInstance);
|
Operand baseInstance = this.Load(StorageKind.Input, IoVariable.BaseInstance);
|
||||||
Operand vertexIndex = this.Load(StorageKind.Input, IoVariable.VertexIndex);
|
Operand vertexIndex = this.Load(StorageKind.Input, IoVariable.VertexIndex);
|
||||||
|
@ -200,7 +283,9 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||||
Operand offset = this.IAdd(baseOffset, Const(j));
|
Operand offset = this.IAdd(baseOffset, Const(j));
|
||||||
Operand value = Instructions.AttributeMap.GenerateAttributeLoad(this, null, location * 4, isOutput: true, isPerPatch: false);
|
Operand value = Instructions.AttributeMap.GenerateAttributeLoad(this, null, location * 4, isOutput: true, isPerPatch: false);
|
||||||
|
|
||||||
this.Store(StorageKind.StorageBuffer, Constants.TfeBufferBaseBinding + tfbIndex, Const(0), offset, value);
|
int binding = ResourceManager.Reservations.GetTfeBufferStorageBufferBinding(tfbIndex);
|
||||||
|
|
||||||
|
this.Store(StorageKind.StorageBuffer, binding, Const(0), offset, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -225,16 +310,6 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||||
|
|
||||||
this.Store(StorageKind.Output, IoVariable.Position, null, Const(2), this.FPFusedMultiplyAdd(z, ConstF(0.5f), halfW));
|
this.Store(StorageKind.Output, IoVariable.Position, null, Const(2), this.FPFusedMultiplyAdd(z, ConstF(0.5f), halfW));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (TranslatorContext.Definitions.Stage != ShaderStage.Geometry && TranslatorContext.HasLayerInputAttribute)
|
|
||||||
{
|
|
||||||
int attrVecIndex = TranslatorContext.GpLayerInputAttribute >> 2;
|
|
||||||
int attrComponentIndex = TranslatorContext.GpLayerInputAttribute & 3;
|
|
||||||
|
|
||||||
Operand layer = this.Load(StorageKind.Output, IoVariable.UserDefined, null, Const(attrVecIndex), Const(attrComponentIndex));
|
|
||||||
|
|
||||||
this.Store(StorageKind.Output, IoVariable.Layer, null, layer);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void PrepareForVertexReturn(out Operand oldXLocal, out Operand oldYLocal, out Operand oldZLocal)
|
public void PrepareForVertexReturn(out Operand oldXLocal, out Operand oldYLocal, out Operand oldZLocal)
|
||||||
|
@ -308,9 +383,30 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||||
|
|
||||||
if (TranslatorContext.Definitions.GpPassthrough && !TranslatorContext.GpuAccessor.QueryHostSupportsGeometryShaderPassthrough())
|
if (TranslatorContext.Definitions.GpPassthrough && !TranslatorContext.GpuAccessor.QueryHostSupportsGeometryShaderPassthrough())
|
||||||
{
|
{
|
||||||
int inputVertices = TranslatorContext.Definitions.InputTopology.ToInputVertices();
|
int inputStart, inputEnd, inputStep;
|
||||||
|
|
||||||
for (int primIndex = 0; primIndex < inputVertices; primIndex++)
|
InputTopology topology = TranslatorContext.Definitions.InputTopology;
|
||||||
|
|
||||||
|
if (topology == InputTopology.LinesAdjacency)
|
||||||
|
{
|
||||||
|
inputStart = 1;
|
||||||
|
inputEnd = 3;
|
||||||
|
inputStep = 1;
|
||||||
|
}
|
||||||
|
else if (topology == InputTopology.TrianglesAdjacency)
|
||||||
|
{
|
||||||
|
inputStart = 0;
|
||||||
|
inputEnd = 6;
|
||||||
|
inputStep = 2;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
inputStart = 0;
|
||||||
|
inputEnd = topology.ToInputVerticesNoAdjacency();
|
||||||
|
inputStep = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int primIndex = inputStart; primIndex < inputEnd; primIndex += inputStep)
|
||||||
{
|
{
|
||||||
WritePositionOutput(primIndex);
|
WritePositionOutput(primIndex);
|
||||||
|
|
||||||
|
@ -428,6 +524,65 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (VertexAsCompute)
|
||||||
|
{
|
||||||
|
if (TranslatorContext.Stage == ShaderStage.Vertex)
|
||||||
|
{
|
||||||
|
int vertexInfoCbBinding = ResourceManager.Reservations.VertexInfoConstantBufferBinding;
|
||||||
|
int vertexOutputSbBinding = ResourceManager.Reservations.VertexOutputStorageBufferBinding;
|
||||||
|
int stride = ResourceManager.Reservations.OutputSizePerInvocation;
|
||||||
|
|
||||||
|
Operand vertexCount = this.Load(StorageKind.ConstantBuffer, vertexInfoCbBinding, Const((int)VertexInfoBufferField.VertexCounts), Const(0));
|
||||||
|
|
||||||
|
Operand outputVertexOffset = this.Load(StorageKind.Input, IoVariable.GlobalId, Const(0));
|
||||||
|
Operand outputInstanceOffset = this.Load(StorageKind.Input, IoVariable.GlobalId, Const(1));
|
||||||
|
|
||||||
|
Operand outputBaseVertex = this.IMultiply(outputInstanceOffset, vertexCount);
|
||||||
|
|
||||||
|
Operand baseOffset = this.IMultiply(this.IAdd(outputBaseVertex, outputVertexOffset), Const(stride));
|
||||||
|
|
||||||
|
for (int offset = 0; offset < stride; offset++)
|
||||||
|
{
|
||||||
|
Operand vertexOffset = this.IAdd(baseOffset, Const(offset));
|
||||||
|
Operand value = this.Load(StorageKind.LocalMemory, ResourceManager.LocalVertexDataMemoryId, Const(offset));
|
||||||
|
|
||||||
|
this.Store(StorageKind.StorageBuffer, vertexOutputSbBinding, Const(0), vertexOffset, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (TranslatorContext.Stage == ShaderStage.Geometry)
|
||||||
|
{
|
||||||
|
Operand lblLoopHead = Label();
|
||||||
|
Operand lblExit = Label();
|
||||||
|
|
||||||
|
this.MarkLabel(lblLoopHead);
|
||||||
|
|
||||||
|
Operand writtenIndices = this.Load(StorageKind.LocalMemory, ResourceManager.LocalGeometryOutputIndexCountMemoryId);
|
||||||
|
|
||||||
|
int maxIndicesPerPrimitiveInvocation = TranslatorContext.Definitions.GetGeometryOutputIndexBufferStridePerInstance();
|
||||||
|
int maxIndicesPerPrimitive = maxIndicesPerPrimitiveInvocation * TranslatorContext.Definitions.ThreadsPerInputPrimitive;
|
||||||
|
|
||||||
|
this.BranchIfTrue(lblExit, this.ICompareGreaterOrEqualUnsigned(writtenIndices, Const(maxIndicesPerPrimitiveInvocation)));
|
||||||
|
|
||||||
|
int vertexInfoCbBinding = ResourceManager.Reservations.VertexInfoConstantBufferBinding;
|
||||||
|
|
||||||
|
Operand primitiveIndex = this.Load(StorageKind.Input, IoVariable.GlobalId, Const(0));
|
||||||
|
Operand instanceIndex = this.Load(StorageKind.Input, IoVariable.GlobalId, Const(1));
|
||||||
|
Operand invocationId = this.Load(StorageKind.Input, IoVariable.GlobalId, Const(2));
|
||||||
|
Operand vertexCount = this.Load(StorageKind.ConstantBuffer, vertexInfoCbBinding, Const((int)VertexInfoBufferField.VertexCounts), Const(0));
|
||||||
|
Operand primitiveId = this.IAdd(this.IMultiply(instanceIndex, vertexCount), primitiveIndex);
|
||||||
|
Operand ibOffset = this.IMultiply(primitiveId, Const(maxIndicesPerPrimitive));
|
||||||
|
ibOffset = this.IAdd(ibOffset, this.IMultiply(invocationId, Const(maxIndicesPerPrimitiveInvocation)));
|
||||||
|
ibOffset = this.IAdd(ibOffset, writtenIndices);
|
||||||
|
|
||||||
|
this.Store(StorageKind.StorageBuffer, ResourceManager.Reservations.GeometryIndexOutputStorageBufferBinding, Const(0), ibOffset, Const(-1));
|
||||||
|
this.Store(StorageKind.LocalMemory, ResourceManager.LocalGeometryOutputIndexCountMemoryId, this.IAdd(writtenIndices, Const(1)));
|
||||||
|
|
||||||
|
this.Branch(lblLoopHead);
|
||||||
|
|
||||||
|
this.MarkLabel(lblExit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -831,6 +831,11 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||||
return context.Add(Instruction.Store, storageKind, null, e0, e1, value);
|
return context.Add(Instruction.Store, storageKind, null, e0, e1, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Operand Store(this EmitterContext context, StorageKind storageKind, int binding, Operand value)
|
||||||
|
{
|
||||||
|
return context.Add(Instruction.Store, storageKind, null, Const(binding), value);
|
||||||
|
}
|
||||||
|
|
||||||
public static Operand Store(this EmitterContext context, StorageKind storageKind, int binding, Operand e0, Operand value)
|
public static Operand Store(this EmitterContext context, StorageKind storageKind, int binding, Operand e0, Operand value)
|
||||||
{
|
{
|
||||||
return context.Add(Instruction.Store, storageKind, null, Const(binding), e0, value);
|
return context.Add(Instruction.Store, storageKind, null, Const(binding), e0, value);
|
||||||
|
|
|
@ -19,8 +19,12 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||||
DrawParameters = 1 << 4,
|
DrawParameters = 1 << 4,
|
||||||
RtLayer = 1 << 5,
|
RtLayer = 1 << 5,
|
||||||
Shuffle = 1 << 6,
|
Shuffle = 1 << 6,
|
||||||
|
ViewportIndex = 1 << 7,
|
||||||
|
ViewportMask = 1 << 8,
|
||||||
FixedFuncAttr = 1 << 9,
|
FixedFuncAttr = 1 << 9,
|
||||||
LocalMemory = 1 << 10,
|
LocalMemory = 1 << 10,
|
||||||
SharedMemory = 1 << 11,
|
SharedMemory = 1 << 11,
|
||||||
|
Store = 1 << 12,
|
||||||
|
VtgAsCompute = 1 << 13,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
28
src/Ryujinx.Graphics.Shader/Translation/IoUsage.cs
Normal file
28
src/Ryujinx.Graphics.Shader/Translation/IoUsage.cs
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
namespace Ryujinx.Graphics.Shader.Translation
|
||||||
|
{
|
||||||
|
readonly struct IoUsage
|
||||||
|
{
|
||||||
|
private readonly FeatureFlags _usedFeatures;
|
||||||
|
|
||||||
|
public readonly bool UsesRtLayer => _usedFeatures.HasFlag(FeatureFlags.RtLayer);
|
||||||
|
public readonly bool UsesViewportIndex => _usedFeatures.HasFlag(FeatureFlags.ViewportIndex);
|
||||||
|
public readonly bool UsesViewportMask => _usedFeatures.HasFlag(FeatureFlags.ViewportMask);
|
||||||
|
public readonly byte ClipDistancesWritten { get; }
|
||||||
|
public readonly int UserDefinedMap { get; }
|
||||||
|
|
||||||
|
public IoUsage(FeatureFlags usedFeatures, byte clipDistancesWritten, int userDefinedMap)
|
||||||
|
{
|
||||||
|
_usedFeatures = usedFeatures;
|
||||||
|
ClipDistancesWritten = clipDistancesWritten;
|
||||||
|
UserDefinedMap = userDefinedMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly IoUsage Combine(IoUsage other)
|
||||||
|
{
|
||||||
|
return new IoUsage(
|
||||||
|
_usedFeatures | other._usedFeatures,
|
||||||
|
(byte)(ClipDistancesWritten | other.ClipDistancesWritten),
|
||||||
|
UserDefinedMap | other.UserDefinedMap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -48,12 +48,22 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||||
public int LocalMemoryId { get; private set; }
|
public int LocalMemoryId { get; private set; }
|
||||||
public int SharedMemoryId { get; private set; }
|
public int SharedMemoryId { get; private set; }
|
||||||
|
|
||||||
|
public int LocalVertexDataMemoryId { get; private set; }
|
||||||
|
public int LocalTopologyRemapMemoryId { get; private set; }
|
||||||
|
public int LocalVertexIndexVertexRateMemoryId { get; private set; }
|
||||||
|
public int LocalVertexIndexInstanceRateMemoryId { get; private set; }
|
||||||
|
public int LocalGeometryOutputVertexCountMemoryId { get; private set; }
|
||||||
|
public int LocalGeometryOutputIndexCountMemoryId { get; private set; }
|
||||||
|
|
||||||
public ShaderProperties Properties { get; }
|
public ShaderProperties Properties { get; }
|
||||||
|
|
||||||
public ResourceManager(ShaderStage stage, IGpuAccessor gpuAccessor)
|
public ResourceReservations Reservations { get; }
|
||||||
|
|
||||||
|
public ResourceManager(ShaderStage stage, IGpuAccessor gpuAccessor, ResourceReservations reservations = null)
|
||||||
{
|
{
|
||||||
_gpuAccessor = gpuAccessor;
|
_gpuAccessor = gpuAccessor;
|
||||||
Properties = new();
|
Properties = new();
|
||||||
|
Reservations = reservations;
|
||||||
_stage = stage;
|
_stage = stage;
|
||||||
_stagePrefix = GetShaderStagePrefix(stage);
|
_stagePrefix = GetShaderStagePrefix(stage);
|
||||||
|
|
||||||
|
@ -114,6 +124,29 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetVertexAsComputeLocalMemories(ShaderStage stage, InputTopology inputTopology)
|
||||||
|
{
|
||||||
|
LocalVertexDataMemoryId = AddMemoryDefinition("local_vertex_data", AggregateType.Array | AggregateType.FP32, Reservations.OutputSizePerInvocation);
|
||||||
|
|
||||||
|
if (stage == ShaderStage.Vertex)
|
||||||
|
{
|
||||||
|
LocalVertexIndexVertexRateMemoryId = AddMemoryDefinition("local_vertex_index_vr", AggregateType.U32);
|
||||||
|
LocalVertexIndexInstanceRateMemoryId = AddMemoryDefinition("local_vertex_index_ir", AggregateType.U32);
|
||||||
|
}
|
||||||
|
else if (stage == ShaderStage.Geometry)
|
||||||
|
{
|
||||||
|
LocalTopologyRemapMemoryId = AddMemoryDefinition("local_topology_remap", AggregateType.Array | AggregateType.U32, inputTopology.ToInputVertices());
|
||||||
|
|
||||||
|
LocalGeometryOutputVertexCountMemoryId = AddMemoryDefinition("local_geometry_output_vertex", AggregateType.U32);
|
||||||
|
LocalGeometryOutputIndexCountMemoryId = AddMemoryDefinition("local_geometry_output_index", AggregateType.U32);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int AddMemoryDefinition(string name, AggregateType type, int arrayLength = 1)
|
||||||
|
{
|
||||||
|
return Properties.AddLocalMemory(new MemoryDefinition(name, type, arrayLength));
|
||||||
|
}
|
||||||
|
|
||||||
public int GetConstantBufferBinding(int slot)
|
public int GetConstantBufferBinding(int slot)
|
||||||
{
|
{
|
||||||
int binding = _cbSlotToBindingMap[slot];
|
int binding = _cbSlotToBindingMap[slot];
|
||||||
|
@ -465,17 +498,22 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||||
return descriptors;
|
return descriptors;
|
||||||
}
|
}
|
||||||
|
|
||||||
public (int, int) GetCbufSlotAndHandleForTexture(int binding)
|
public bool TryGetCbufSlotAndHandleForTexture(int binding, out int cbufSlot, out int handle)
|
||||||
{
|
{
|
||||||
foreach ((TextureInfo info, TextureMeta meta) in _usedTextures)
|
foreach ((TextureInfo info, TextureMeta meta) in _usedTextures)
|
||||||
{
|
{
|
||||||
if (meta.Binding == binding)
|
if (meta.Binding == binding)
|
||||||
{
|
{
|
||||||
return (info.CbufSlot, info.Handle);
|
cbufSlot = info.CbufSlot;
|
||||||
|
handle = info.Handle;
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new ArgumentException($"Binding {binding} is invalid.");
|
cbufSlot = 0;
|
||||||
|
handle = 0;
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int FindDescriptorIndex(TextureDescriptor[] array, int binding)
|
private static int FindDescriptorIndex(TextureDescriptor[] array, int binding)
|
||||||
|
|
186
src/Ryujinx.Graphics.Shader/Translation/ResourceReservations.cs
Normal file
186
src/Ryujinx.Graphics.Shader/Translation/ResourceReservations.cs
Normal file
|
@ -0,0 +1,186 @@
|
||||||
|
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
|
||||||
|
using Ryujinx.Graphics.Shader.StructuredIr;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace Ryujinx.Graphics.Shader.Translation
|
||||||
|
{
|
||||||
|
public class ResourceReservations
|
||||||
|
{
|
||||||
|
public const int TfeBuffersCount = 4;
|
||||||
|
|
||||||
|
public const int MaxVertexBufferTextures = 32;
|
||||||
|
|
||||||
|
public int VertexInfoConstantBufferBinding { get; }
|
||||||
|
public int VertexOutputStorageBufferBinding { get; }
|
||||||
|
public int GeometryVertexOutputStorageBufferBinding { get; }
|
||||||
|
public int GeometryIndexOutputStorageBufferBinding { get; }
|
||||||
|
public int IndexBufferTextureBinding { get; }
|
||||||
|
public int TopologyRemapBufferTextureBinding { get; }
|
||||||
|
|
||||||
|
public int ReservedConstantBuffers { get; }
|
||||||
|
public int ReservedStorageBuffers { get; }
|
||||||
|
public int ReservedTextures { get; }
|
||||||
|
public int ReservedImages { get; }
|
||||||
|
public int InputSizePerInvocation { get; }
|
||||||
|
public int OutputSizePerInvocation { get; }
|
||||||
|
public int OutputSizeInBytesPerInvocation => OutputSizePerInvocation * sizeof(uint);
|
||||||
|
|
||||||
|
private readonly int _tfeBufferSbBaseBinding;
|
||||||
|
private readonly int _vertexBufferTextureBaseBinding;
|
||||||
|
|
||||||
|
private readonly Dictionary<IoDefinition, int> _offsets;
|
||||||
|
internal IReadOnlyDictionary<IoDefinition, int> Offsets => _offsets;
|
||||||
|
|
||||||
|
internal ResourceReservations(bool isTransformFeedbackEmulated, bool vertexAsCompute)
|
||||||
|
{
|
||||||
|
// All stages reserves the first constant buffer binding for the support buffer.
|
||||||
|
ReservedConstantBuffers = 1;
|
||||||
|
ReservedStorageBuffers = 0;
|
||||||
|
ReservedTextures = 0;
|
||||||
|
ReservedImages = 0;
|
||||||
|
|
||||||
|
if (isTransformFeedbackEmulated)
|
||||||
|
{
|
||||||
|
// Transform feedback emulation currently always uses 4 storage buffers.
|
||||||
|
_tfeBufferSbBaseBinding = ReservedStorageBuffers;
|
||||||
|
ReservedStorageBuffers = TfeBuffersCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vertexAsCompute)
|
||||||
|
{
|
||||||
|
// One constant buffer reserved for vertex related state.
|
||||||
|
VertexInfoConstantBufferBinding = ReservedConstantBuffers++;
|
||||||
|
|
||||||
|
// One storage buffer for the output vertex data.
|
||||||
|
VertexOutputStorageBufferBinding = ReservedStorageBuffers++;
|
||||||
|
|
||||||
|
// One storage buffer for the output geometry vertex data.
|
||||||
|
GeometryVertexOutputStorageBufferBinding = ReservedStorageBuffers++;
|
||||||
|
|
||||||
|
// One storage buffer for the output geometry index data.
|
||||||
|
GeometryIndexOutputStorageBufferBinding = ReservedStorageBuffers++;
|
||||||
|
|
||||||
|
// Enough textures reserved for all vertex attributes, plus the index buffer.
|
||||||
|
IndexBufferTextureBinding = ReservedTextures;
|
||||||
|
TopologyRemapBufferTextureBinding = ReservedTextures + 1;
|
||||||
|
_vertexBufferTextureBaseBinding = ReservedTextures + 2;
|
||||||
|
ReservedTextures += 2 + MaxVertexBufferTextures;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal ResourceReservations(
|
||||||
|
IGpuAccessor gpuAccessor,
|
||||||
|
bool isTransformFeedbackEmulated,
|
||||||
|
bool vertexAsCompute,
|
||||||
|
IoUsage? vacInput,
|
||||||
|
IoUsage vacOutput) : this(isTransformFeedbackEmulated, vertexAsCompute)
|
||||||
|
{
|
||||||
|
if (vertexAsCompute)
|
||||||
|
{
|
||||||
|
_offsets = new();
|
||||||
|
|
||||||
|
if (vacInput.HasValue)
|
||||||
|
{
|
||||||
|
InputSizePerInvocation = FillIoOffsetMap(gpuAccessor, StorageKind.Input, vacInput.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
OutputSizePerInvocation = FillIoOffsetMap(gpuAccessor, StorageKind.Output, vacOutput);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int FillIoOffsetMap(IGpuAccessor gpuAccessor, StorageKind storageKind, IoUsage vacUsage)
|
||||||
|
{
|
||||||
|
int offset = 0;
|
||||||
|
|
||||||
|
for (int c = 0; c < 4; c++)
|
||||||
|
{
|
||||||
|
_offsets.Add(new IoDefinition(storageKind, IoVariable.Position, 0, c), offset++);
|
||||||
|
}
|
||||||
|
|
||||||
|
_offsets.Add(new IoDefinition(storageKind, IoVariable.PointSize), offset++);
|
||||||
|
|
||||||
|
int clipDistancesWrittenMap = vacUsage.ClipDistancesWritten;
|
||||||
|
|
||||||
|
while (clipDistancesWrittenMap != 0)
|
||||||
|
{
|
||||||
|
int index = BitOperations.TrailingZeroCount(clipDistancesWrittenMap);
|
||||||
|
|
||||||
|
_offsets.Add(new IoDefinition(storageKind, IoVariable.ClipDistance, 0, index), offset++);
|
||||||
|
|
||||||
|
clipDistancesWrittenMap &= ~(1 << index);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vacUsage.UsesRtLayer)
|
||||||
|
{
|
||||||
|
_offsets.Add(new IoDefinition(storageKind, IoVariable.Layer), offset++);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vacUsage.UsesViewportIndex && gpuAccessor.QueryHostSupportsViewportIndexVertexTessellation())
|
||||||
|
{
|
||||||
|
_offsets.Add(new IoDefinition(storageKind, IoVariable.VertexIndex), offset++);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vacUsage.UsesViewportMask && gpuAccessor.QueryHostSupportsViewportMask())
|
||||||
|
{
|
||||||
|
_offsets.Add(new IoDefinition(storageKind, IoVariable.ViewportMask), offset++);
|
||||||
|
}
|
||||||
|
|
||||||
|
int usedDefinedMap = vacUsage.UserDefinedMap;
|
||||||
|
|
||||||
|
while (usedDefinedMap != 0)
|
||||||
|
{
|
||||||
|
int location = BitOperations.TrailingZeroCount(usedDefinedMap);
|
||||||
|
|
||||||
|
for (int c = 0; c < 4; c++)
|
||||||
|
{
|
||||||
|
_offsets.Add(new IoDefinition(storageKind, IoVariable.UserDefined, location, c), offset++);
|
||||||
|
}
|
||||||
|
|
||||||
|
usedDefinedMap &= ~(1 << location);
|
||||||
|
}
|
||||||
|
|
||||||
|
return offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static bool IsVectorOrArrayVariable(IoVariable variable)
|
||||||
|
{
|
||||||
|
return variable switch
|
||||||
|
{
|
||||||
|
IoVariable.ClipDistance or
|
||||||
|
IoVariable.Position => true,
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetTfeBufferStorageBufferBinding(int bufferIndex)
|
||||||
|
{
|
||||||
|
return _tfeBufferSbBaseBinding + bufferIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetVertexBufferTextureBinding(int vaLocation)
|
||||||
|
{
|
||||||
|
return _vertexBufferTextureBaseBinding + vaLocation;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal bool TryGetOffset(StorageKind storageKind, int location, int component, out int offset)
|
||||||
|
{
|
||||||
|
return _offsets.TryGetValue(new IoDefinition(storageKind, IoVariable.UserDefined, location, component), out offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal bool TryGetOffset(StorageKind storageKind, IoVariable ioVariable, int location, int component, out int offset)
|
||||||
|
{
|
||||||
|
return _offsets.TryGetValue(new IoDefinition(storageKind, ioVariable, location, component), out offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal bool TryGetOffset(StorageKind storageKind, IoVariable ioVariable, int component, out int offset)
|
||||||
|
{
|
||||||
|
return _offsets.TryGetValue(new IoDefinition(storageKind, ioVariable, 0, component), out offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal bool TryGetOffset(StorageKind storageKind, IoVariable ioVariable, out int offset)
|
||||||
|
{
|
||||||
|
return _offsets.TryGetValue(new IoDefinition(storageKind, ioVariable, 0, 0), out offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -32,7 +32,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||||
public bool GpPassthrough { get; }
|
public bool GpPassthrough { get; }
|
||||||
public bool LastInVertexPipeline { get; set; }
|
public bool LastInVertexPipeline { get; set; }
|
||||||
|
|
||||||
public int ThreadsPerInputPrimitive { get; }
|
public int ThreadsPerInputPrimitive { get; private set; }
|
||||||
|
|
||||||
public InputTopology InputTopology => _graphicsState.Topology;
|
public InputTopology InputTopology => _graphicsState.Topology;
|
||||||
public OutputTopology OutputTopology { get; }
|
public OutputTopology OutputTopology { get; }
|
||||||
|
@ -97,9 +97,14 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||||
|
|
||||||
private readonly Dictionary<TransformFeedbackVariable, TransformFeedbackOutput> _transformFeedbackDefinitions;
|
private readonly Dictionary<TransformFeedbackVariable, TransformFeedbackOutput> _transformFeedbackDefinitions;
|
||||||
|
|
||||||
public ShaderDefinitions(ShaderStage stage)
|
public ShaderDefinitions(ShaderStage stage, ulong transformFeedbackVecMap, TransformFeedbackOutput[] transformFeedbackOutputs)
|
||||||
{
|
{
|
||||||
Stage = stage;
|
Stage = stage;
|
||||||
|
TransformFeedbackEnabled = transformFeedbackOutputs != null;
|
||||||
|
_transformFeedbackOutputs = transformFeedbackOutputs;
|
||||||
|
_transformFeedbackDefinitions = new();
|
||||||
|
|
||||||
|
PopulateTransformFeedbackDefinitions(transformFeedbackVecMap, transformFeedbackOutputs);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ShaderDefinitions(
|
public ShaderDefinitions(
|
||||||
|
@ -142,7 +147,6 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||||
bool omapSampleMask,
|
bool omapSampleMask,
|
||||||
bool omapDepth,
|
bool omapDepth,
|
||||||
bool supportsScaledVertexFormats,
|
bool supportsScaledVertexFormats,
|
||||||
bool transformFeedbackEnabled,
|
|
||||||
ulong transformFeedbackVecMap,
|
ulong transformFeedbackVecMap,
|
||||||
TransformFeedbackOutput[] transformFeedbackOutputs)
|
TransformFeedbackOutput[] transformFeedbackOutputs)
|
||||||
{
|
{
|
||||||
|
@ -151,17 +155,22 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||||
GpPassthrough = gpPassthrough;
|
GpPassthrough = gpPassthrough;
|
||||||
ThreadsPerInputPrimitive = threadsPerInputPrimitive;
|
ThreadsPerInputPrimitive = threadsPerInputPrimitive;
|
||||||
OutputTopology = outputTopology;
|
OutputTopology = outputTopology;
|
||||||
MaxOutputVertices = maxOutputVertices;
|
MaxOutputVertices = gpPassthrough ? graphicsState.Topology.ToInputVerticesNoAdjacency() : maxOutputVertices;
|
||||||
ImapTypes = imapTypes;
|
ImapTypes = imapTypes;
|
||||||
OmapTargets = omapTargets;
|
OmapTargets = omapTargets;
|
||||||
OmapSampleMask = omapSampleMask;
|
OmapSampleMask = omapSampleMask;
|
||||||
OmapDepth = omapDepth;
|
OmapDepth = omapDepth;
|
||||||
LastInVertexPipeline = stage < ShaderStage.Fragment;
|
LastInVertexPipeline = stage < ShaderStage.Fragment;
|
||||||
SupportsScaledVertexFormats = supportsScaledVertexFormats;
|
SupportsScaledVertexFormats = supportsScaledVertexFormats;
|
||||||
TransformFeedbackEnabled = transformFeedbackEnabled;
|
TransformFeedbackEnabled = transformFeedbackOutputs != null;
|
||||||
_transformFeedbackOutputs = transformFeedbackOutputs;
|
_transformFeedbackOutputs = transformFeedbackOutputs;
|
||||||
_transformFeedbackDefinitions = new();
|
_transformFeedbackDefinitions = new();
|
||||||
|
|
||||||
|
PopulateTransformFeedbackDefinitions(transformFeedbackVecMap, transformFeedbackOutputs);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PopulateTransformFeedbackDefinitions(ulong transformFeedbackVecMap, TransformFeedbackOutput[] transformFeedbackOutputs)
|
||||||
|
{
|
||||||
while (transformFeedbackVecMap != 0)
|
while (transformFeedbackVecMap != 0)
|
||||||
{
|
{
|
||||||
int vecIndex = BitOperations.TrailingZeroCount(transformFeedbackVecMap);
|
int vecIndex = BitOperations.TrailingZeroCount(transformFeedbackVecMap);
|
||||||
|
@ -200,16 +209,6 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||||
OaIndexing = true;
|
OaIndexing = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public TransformFeedbackOutput[] GetTransformFeedbackOutputs()
|
|
||||||
{
|
|
||||||
if (!HasTransformFeedbackOutputs())
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return _transformFeedbackOutputs;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool TryGetTransformFeedbackOutput(IoVariable ioVariable, int location, int component, out TransformFeedbackOutput transformFeedbackOutput)
|
public bool TryGetTransformFeedbackOutput(IoVariable ioVariable, int location, int component, out TransformFeedbackOutput transformFeedbackOutput)
|
||||||
{
|
{
|
||||||
if (!HasTransformFeedbackOutputs())
|
if (!HasTransformFeedbackOutputs())
|
||||||
|
@ -320,5 +319,35 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||||
{
|
{
|
||||||
return _graphicsState.AttributeTypes[location];
|
return _graphicsState.AttributeTypes[location];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool IsAttributeSint(int location)
|
||||||
|
{
|
||||||
|
return (_graphicsState.AttributeTypes[location] & ~AttributeType.AnyPacked) == AttributeType.Sint;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsAttributePacked(int location)
|
||||||
|
{
|
||||||
|
return _graphicsState.AttributeTypes[location].HasFlag(AttributeType.Packed);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsAttributePackedRgb10A2Signed(int location)
|
||||||
|
{
|
||||||
|
return _graphicsState.AttributeTypes[location].HasFlag(AttributeType.PackedRgb10A2Signed);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetGeometryOutputIndexBufferStridePerInstance()
|
||||||
|
{
|
||||||
|
return MaxOutputVertices + OutputTopology switch
|
||||||
|
{
|
||||||
|
OutputTopology.LineStrip => MaxOutputVertices / 2,
|
||||||
|
OutputTopology.TriangleStrip => MaxOutputVertices / 3,
|
||||||
|
_ => MaxOutputVertices,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetGeometryOutputIndexBufferStride()
|
||||||
|
{
|
||||||
|
return GetGeometryOutputIndexBufferStridePerInstance() * ThreadsPerInputPrimitive;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,187 +0,0 @@
|
||||||
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.Shader.Translation
|
|
||||||
{
|
|
||||||
static class ShaderIdentifier
|
|
||||||
{
|
|
||||||
public static ShaderIdentification Identify(
|
|
||||||
IReadOnlyList<Function> functions,
|
|
||||||
IGpuAccessor gpuAccessor,
|
|
||||||
ShaderStage stage,
|
|
||||||
InputTopology inputTopology,
|
|
||||||
out int layerInputAttr)
|
|
||||||
{
|
|
||||||
if (stage == ShaderStage.Geometry &&
|
|
||||||
inputTopology == InputTopology.Triangles &&
|
|
||||||
!gpuAccessor.QueryHostSupportsGeometryShader() &&
|
|
||||||
IsLayerPassthroughGeometryShader(functions, out layerInputAttr))
|
|
||||||
{
|
|
||||||
return ShaderIdentification.GeometryLayerPassthrough;
|
|
||||||
}
|
|
||||||
|
|
||||||
layerInputAttr = 0;
|
|
||||||
return ShaderIdentification.None;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool IsLayerPassthroughGeometryShader(IReadOnlyList<Function> functions, out int layerInputAttr)
|
|
||||||
{
|
|
||||||
bool writesLayer = false;
|
|
||||||
layerInputAttr = 0;
|
|
||||||
|
|
||||||
if (functions.Count != 1)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
int verticesCount = 0;
|
|
||||||
int totalVerticesCount = 0;
|
|
||||||
|
|
||||||
foreach (BasicBlock block in functions[0].Blocks)
|
|
||||||
{
|
|
||||||
// We are not expecting loops or any complex control flow here, so fail in those cases.
|
|
||||||
if (block.Branch != null && block.Branch.Index <= block.Index)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (INode node in block.Operations)
|
|
||||||
{
|
|
||||||
if (node is not Operation operation)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (IsResourceWrite(operation.Inst, operation.StorageKind))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (operation.Inst == Instruction.Store && operation.StorageKind == StorageKind.Output)
|
|
||||||
{
|
|
||||||
Operand src = operation.GetSource(operation.SourcesCount - 1);
|
|
||||||
Operation srcAttributeAsgOp = null;
|
|
||||||
|
|
||||||
if (src.Type == OperandType.LocalVariable &&
|
|
||||||
src.AsgOp is Operation asgOp &&
|
|
||||||
asgOp.Inst == Instruction.Load &&
|
|
||||||
asgOp.StorageKind.IsInputOrOutput())
|
|
||||||
{
|
|
||||||
if (asgOp.StorageKind != StorageKind.Input)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
srcAttributeAsgOp = asgOp;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (srcAttributeAsgOp != null)
|
|
||||||
{
|
|
||||||
IoVariable dstAttribute = (IoVariable)operation.GetSource(0).Value;
|
|
||||||
IoVariable srcAttribute = (IoVariable)srcAttributeAsgOp.GetSource(0).Value;
|
|
||||||
|
|
||||||
if (dstAttribute == IoVariable.Layer && srcAttribute == IoVariable.UserDefined)
|
|
||||||
{
|
|
||||||
if (srcAttributeAsgOp.SourcesCount != 4)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
writesLayer = true;
|
|
||||||
layerInputAttr = srcAttributeAsgOp.GetSource(1).Value * 4 + srcAttributeAsgOp.GetSource(3).Value;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (dstAttribute != srcAttribute)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
int inputsCount = operation.SourcesCount - 2;
|
|
||||||
|
|
||||||
if (dstAttribute == IoVariable.UserDefined)
|
|
||||||
{
|
|
||||||
if (operation.GetSource(1).Value != srcAttributeAsgOp.GetSource(1).Value)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
inputsCount--;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < inputsCount; i++)
|
|
||||||
{
|
|
||||||
int dstIndex = operation.SourcesCount - 2 - i;
|
|
||||||
int srcIndex = srcAttributeAsgOp.SourcesCount - 1 - i;
|
|
||||||
|
|
||||||
if ((dstIndex | srcIndex) < 0)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (operation.GetSource(dstIndex).Type != OperandType.Constant ||
|
|
||||||
srcAttributeAsgOp.GetSource(srcIndex).Type != OperandType.Constant ||
|
|
||||||
operation.GetSource(dstIndex).Value != srcAttributeAsgOp.GetSource(srcIndex).Value)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (src.Type == OperandType.Constant)
|
|
||||||
{
|
|
||||||
int dstComponent = operation.GetSource(operation.SourcesCount - 2).Value;
|
|
||||||
float expectedValue = dstComponent == 3 ? 1f : 0f;
|
|
||||||
|
|
||||||
if (src.AsFloat() != expectedValue)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (operation.Inst == Instruction.EmitVertex)
|
|
||||||
{
|
|
||||||
verticesCount++;
|
|
||||||
}
|
|
||||||
else if (operation.Inst == Instruction.EndPrimitive)
|
|
||||||
{
|
|
||||||
totalVerticesCount += verticesCount;
|
|
||||||
verticesCount = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return totalVerticesCount + verticesCount == 3 && writesLayer;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool IsResourceWrite(Instruction inst, StorageKind storageKind)
|
|
||||||
{
|
|
||||||
switch (inst)
|
|
||||||
{
|
|
||||||
case Instruction.AtomicAdd:
|
|
||||||
case Instruction.AtomicAnd:
|
|
||||||
case Instruction.AtomicCompareAndSwap:
|
|
||||||
case Instruction.AtomicMaxS32:
|
|
||||||
case Instruction.AtomicMaxU32:
|
|
||||||
case Instruction.AtomicMinS32:
|
|
||||||
case Instruction.AtomicMinU32:
|
|
||||||
case Instruction.AtomicOr:
|
|
||||||
case Instruction.AtomicSwap:
|
|
||||||
case Instruction.AtomicXor:
|
|
||||||
case Instruction.ImageAtomic:
|
|
||||||
case Instruction.ImageStore:
|
|
||||||
return true;
|
|
||||||
case Instruction.Store:
|
|
||||||
return storageKind == StorageKind.StorageBuffer ||
|
|
||||||
storageKind == StorageKind.SharedMemory ||
|
|
||||||
storageKind == StorageKind.LocalMemory;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -6,6 +6,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||||
{
|
{
|
||||||
public readonly HelperFunctionManager Hfm;
|
public readonly HelperFunctionManager Hfm;
|
||||||
public readonly BasicBlock[] Blocks;
|
public readonly BasicBlock[] Blocks;
|
||||||
|
public readonly ShaderDefinitions Definitions;
|
||||||
public readonly ResourceManager ResourceManager;
|
public readonly ResourceManager ResourceManager;
|
||||||
public readonly IGpuAccessor GpuAccessor;
|
public readonly IGpuAccessor GpuAccessor;
|
||||||
public readonly TargetLanguage TargetLanguage;
|
public readonly TargetLanguage TargetLanguage;
|
||||||
|
@ -15,6 +16,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||||
public TransformContext(
|
public TransformContext(
|
||||||
HelperFunctionManager hfm,
|
HelperFunctionManager hfm,
|
||||||
BasicBlock[] blocks,
|
BasicBlock[] blocks,
|
||||||
|
ShaderDefinitions definitions,
|
||||||
ResourceManager resourceManager,
|
ResourceManager resourceManager,
|
||||||
IGpuAccessor gpuAccessor,
|
IGpuAccessor gpuAccessor,
|
||||||
TargetLanguage targetLanguage,
|
TargetLanguage targetLanguage,
|
||||||
|
@ -23,6 +25,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||||
{
|
{
|
||||||
Hfm = hfm;
|
Hfm = hfm;
|
||||||
Blocks = blocks;
|
Blocks = blocks;
|
||||||
|
Definitions = definitions;
|
||||||
ResourceManager = resourceManager;
|
ResourceManager = resourceManager;
|
||||||
GpuAccessor = gpuAccessor;
|
GpuAccessor = gpuAccessor;
|
||||||
TargetLanguage = targetLanguage;
|
TargetLanguage = targetLanguage;
|
||||||
|
|
|
@ -0,0 +1,378 @@
|
||||||
|
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
|
||||||
|
using Ryujinx.Graphics.Shader.Translation.Optimizations;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
|
||||||
|
|
||||||
|
namespace Ryujinx.Graphics.Shader.Translation.Transforms
|
||||||
|
{
|
||||||
|
class GeometryToCompute : ITransformPass
|
||||||
|
{
|
||||||
|
public static bool IsEnabled(IGpuAccessor gpuAccessor, ShaderStage stage, TargetLanguage targetLanguage, FeatureFlags usedFeatures)
|
||||||
|
{
|
||||||
|
return usedFeatures.HasFlag(FeatureFlags.VtgAsCompute);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LinkedListNode<INode> RunPass(TransformContext context, LinkedListNode<INode> node)
|
||||||
|
{
|
||||||
|
if (context.Definitions.Stage != ShaderStage.Geometry)
|
||||||
|
{
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
Operation operation = (Operation)node.Value;
|
||||||
|
|
||||||
|
LinkedListNode<INode> newNode = node;
|
||||||
|
|
||||||
|
switch (operation.Inst)
|
||||||
|
{
|
||||||
|
case Instruction.EmitVertex:
|
||||||
|
newNode = GenerateEmitVertex(context.Definitions, context.ResourceManager, node);
|
||||||
|
break;
|
||||||
|
case Instruction.EndPrimitive:
|
||||||
|
newNode = GenerateEndPrimitive(context.Definitions, context.ResourceManager, node);
|
||||||
|
break;
|
||||||
|
case Instruction.Load:
|
||||||
|
if (operation.StorageKind == StorageKind.Input)
|
||||||
|
{
|
||||||
|
IoVariable ioVariable = (IoVariable)operation.GetSource(0).Value;
|
||||||
|
|
||||||
|
if (TryGetOffset(context.ResourceManager, operation, StorageKind.Input, out int inputOffset))
|
||||||
|
{
|
||||||
|
Operand primVertex = ioVariable == IoVariable.UserDefined
|
||||||
|
? operation.GetSource(2)
|
||||||
|
: operation.GetSource(1);
|
||||||
|
|
||||||
|
Operand vertexElemOffset = GenerateVertexOffset(context.ResourceManager, node, inputOffset, primVertex);
|
||||||
|
|
||||||
|
newNode = node.List.AddBefore(node, new Operation(
|
||||||
|
Instruction.Load,
|
||||||
|
StorageKind.StorageBuffer,
|
||||||
|
operation.Dest,
|
||||||
|
new[] { Const(context.ResourceManager.Reservations.VertexOutputStorageBufferBinding), Const(0), vertexElemOffset }));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
switch (ioVariable)
|
||||||
|
{
|
||||||
|
case IoVariable.InvocationId:
|
||||||
|
newNode = GenerateInvocationId(node, operation.Dest);
|
||||||
|
break;
|
||||||
|
case IoVariable.PrimitiveId:
|
||||||
|
newNode = GeneratePrimitiveId(context.ResourceManager, node, operation.Dest);
|
||||||
|
break;
|
||||||
|
case IoVariable.GlobalId:
|
||||||
|
case IoVariable.SubgroupEqMask:
|
||||||
|
case IoVariable.SubgroupGeMask:
|
||||||
|
case IoVariable.SubgroupGtMask:
|
||||||
|
case IoVariable.SubgroupLaneId:
|
||||||
|
case IoVariable.SubgroupLeMask:
|
||||||
|
case IoVariable.SubgroupLtMask:
|
||||||
|
// Those are valid or expected for geometry shaders.
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
context.GpuAccessor.Log($"Invalid input \"{ioVariable}\".");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (operation.StorageKind == StorageKind.Output)
|
||||||
|
{
|
||||||
|
if (TryGetOffset(context.ResourceManager, operation, StorageKind.Output, out int outputOffset))
|
||||||
|
{
|
||||||
|
newNode = node.List.AddBefore(node, new Operation(
|
||||||
|
Instruction.Load,
|
||||||
|
StorageKind.LocalMemory,
|
||||||
|
operation.Dest,
|
||||||
|
new[] { Const(context.ResourceManager.LocalVertexDataMemoryId), Const(outputOffset) }));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
context.GpuAccessor.Log($"Invalid output \"{(IoVariable)operation.GetSource(0).Value}\".");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Instruction.Store:
|
||||||
|
if (operation.StorageKind == StorageKind.Output)
|
||||||
|
{
|
||||||
|
if (TryGetOffset(context.ResourceManager, operation, StorageKind.Output, out int outputOffset))
|
||||||
|
{
|
||||||
|
Operand value = operation.GetSource(operation.SourcesCount - 1);
|
||||||
|
|
||||||
|
newNode = node.List.AddBefore(node, new Operation(
|
||||||
|
Instruction.Store,
|
||||||
|
StorageKind.LocalMemory,
|
||||||
|
(Operand)null,
|
||||||
|
new[] { Const(context.ResourceManager.LocalVertexDataMemoryId), Const(outputOffset), value }));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
context.GpuAccessor.Log($"Invalid output \"{(IoVariable)operation.GetSource(0).Value}\".");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newNode != node)
|
||||||
|
{
|
||||||
|
Utils.DeleteNode(node, operation);
|
||||||
|
}
|
||||||
|
|
||||||
|
return newNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static LinkedListNode<INode> GenerateEmitVertex(ShaderDefinitions definitions, ResourceManager resourceManager, LinkedListNode<INode> node)
|
||||||
|
{
|
||||||
|
int vbOutputBinding = resourceManager.Reservations.GeometryVertexOutputStorageBufferBinding;
|
||||||
|
int ibOutputBinding = resourceManager.Reservations.GeometryIndexOutputStorageBufferBinding;
|
||||||
|
int stride = resourceManager.Reservations.OutputSizePerInvocation;
|
||||||
|
|
||||||
|
Operand outputPrimVertex = IncrementLocalMemory(node, resourceManager.LocalGeometryOutputVertexCountMemoryId);
|
||||||
|
Operand baseVertexOffset = GenerateBaseOffset(
|
||||||
|
resourceManager,
|
||||||
|
node,
|
||||||
|
definitions.MaxOutputVertices * definitions.ThreadsPerInputPrimitive,
|
||||||
|
definitions.ThreadsPerInputPrimitive);
|
||||||
|
Operand outputBaseVertex = Local();
|
||||||
|
node.List.AddBefore(node, new Operation(Instruction.Add, outputBaseVertex, new[] { baseVertexOffset, outputPrimVertex }));
|
||||||
|
|
||||||
|
Operand outputPrimIndex = IncrementLocalMemory(node, resourceManager.LocalGeometryOutputIndexCountMemoryId);
|
||||||
|
Operand baseIndexOffset = GenerateBaseOffset(
|
||||||
|
resourceManager,
|
||||||
|
node,
|
||||||
|
definitions.GetGeometryOutputIndexBufferStride(),
|
||||||
|
definitions.ThreadsPerInputPrimitive);
|
||||||
|
Operand outputBaseIndex = Local();
|
||||||
|
node.List.AddBefore(node, new Operation(Instruction.Add, outputBaseIndex, new[] { baseIndexOffset, outputPrimIndex }));
|
||||||
|
|
||||||
|
node.List.AddBefore(node, new Operation(
|
||||||
|
Instruction.Store,
|
||||||
|
StorageKind.StorageBuffer,
|
||||||
|
null,
|
||||||
|
new[] { Const(ibOutputBinding), Const(0), outputBaseIndex, outputBaseVertex }));
|
||||||
|
|
||||||
|
Operand baseOffset = Local();
|
||||||
|
node.List.AddBefore(node, new Operation(Instruction.Multiply, baseOffset, new[] { outputBaseVertex, Const(stride) }));
|
||||||
|
|
||||||
|
LinkedListNode<INode> newNode = node;
|
||||||
|
|
||||||
|
for (int offset = 0; offset < stride; offset++)
|
||||||
|
{
|
||||||
|
Operand vertexOffset;
|
||||||
|
|
||||||
|
if (offset > 0)
|
||||||
|
{
|
||||||
|
vertexOffset = Local();
|
||||||
|
node.List.AddBefore(node, new Operation(Instruction.Add, vertexOffset, new[] { baseOffset, Const(offset) }));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
vertexOffset = baseOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
Operand value = Local();
|
||||||
|
node.List.AddBefore(node, new Operation(
|
||||||
|
Instruction.Load,
|
||||||
|
StorageKind.LocalMemory,
|
||||||
|
value,
|
||||||
|
new[] { Const(resourceManager.LocalVertexDataMemoryId), Const(offset) }));
|
||||||
|
|
||||||
|
newNode = node.List.AddBefore(node, new Operation(
|
||||||
|
Instruction.Store,
|
||||||
|
StorageKind.StorageBuffer,
|
||||||
|
null,
|
||||||
|
new[] { Const(vbOutputBinding), Const(0), vertexOffset, value }));
|
||||||
|
}
|
||||||
|
|
||||||
|
return newNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static LinkedListNode<INode> GenerateEndPrimitive(ShaderDefinitions definitions, ResourceManager resourceManager, LinkedListNode<INode> node)
|
||||||
|
{
|
||||||
|
int ibOutputBinding = resourceManager.Reservations.GeometryIndexOutputStorageBufferBinding;
|
||||||
|
|
||||||
|
Operand outputPrimIndex = IncrementLocalMemory(node, resourceManager.LocalGeometryOutputIndexCountMemoryId);
|
||||||
|
Operand baseIndexOffset = GenerateBaseOffset(
|
||||||
|
resourceManager,
|
||||||
|
node,
|
||||||
|
definitions.GetGeometryOutputIndexBufferStride(),
|
||||||
|
definitions.ThreadsPerInputPrimitive);
|
||||||
|
Operand outputBaseIndex = Local();
|
||||||
|
node.List.AddBefore(node, new Operation(Instruction.Add, outputBaseIndex, new[] { baseIndexOffset, outputPrimIndex }));
|
||||||
|
|
||||||
|
return node.List.AddBefore(node, new Operation(
|
||||||
|
Instruction.Store,
|
||||||
|
StorageKind.StorageBuffer,
|
||||||
|
null,
|
||||||
|
new[] { Const(ibOutputBinding), Const(0), outputBaseIndex, Const(-1) }));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Operand GenerateBaseOffset(ResourceManager resourceManager, LinkedListNode<INode> node, int stride, int threadsPerInputPrimitive)
|
||||||
|
{
|
||||||
|
Operand primitiveId = Local();
|
||||||
|
GeneratePrimitiveId(resourceManager, node, primitiveId);
|
||||||
|
|
||||||
|
Operand baseOffset = Local();
|
||||||
|
node.List.AddBefore(node, new Operation(Instruction.Multiply, baseOffset, new[] { primitiveId, Const(stride) }));
|
||||||
|
|
||||||
|
Operand invocationId = Local();
|
||||||
|
GenerateInvocationId(node, invocationId);
|
||||||
|
|
||||||
|
Operand invocationOffset = Local();
|
||||||
|
node.List.AddBefore(node, new Operation(Instruction.Multiply, invocationOffset, new[] { invocationId, Const(stride / threadsPerInputPrimitive) }));
|
||||||
|
|
||||||
|
Operand combinedOffset = Local();
|
||||||
|
node.List.AddBefore(node, new Operation(Instruction.Add, combinedOffset, new[] { baseOffset, invocationOffset }));
|
||||||
|
|
||||||
|
return combinedOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Operand IncrementLocalMemory(LinkedListNode<INode> node, int memoryId)
|
||||||
|
{
|
||||||
|
Operand oldValue = Local();
|
||||||
|
node.List.AddBefore(node, new Operation(
|
||||||
|
Instruction.Load,
|
||||||
|
StorageKind.LocalMemory,
|
||||||
|
oldValue,
|
||||||
|
new[] { Const(memoryId) }));
|
||||||
|
|
||||||
|
Operand newValue = Local();
|
||||||
|
node.List.AddBefore(node, new Operation(Instruction.Add, newValue, new[] { oldValue, Const(1) }));
|
||||||
|
|
||||||
|
node.List.AddBefore(node, new Operation(Instruction.Store, StorageKind.LocalMemory, null, new[] { Const(memoryId), newValue }));
|
||||||
|
|
||||||
|
return oldValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Operand GenerateVertexOffset(
|
||||||
|
ResourceManager resourceManager,
|
||||||
|
LinkedListNode<INode> node,
|
||||||
|
int elementOffset,
|
||||||
|
Operand primVertex)
|
||||||
|
{
|
||||||
|
int vertexInfoCbBinding = resourceManager.Reservations.VertexInfoConstantBufferBinding;
|
||||||
|
|
||||||
|
Operand vertexCount = Local();
|
||||||
|
node.List.AddBefore(node, new Operation(
|
||||||
|
Instruction.Load,
|
||||||
|
StorageKind.ConstantBuffer,
|
||||||
|
vertexCount,
|
||||||
|
new[] { Const(vertexInfoCbBinding), Const((int)VertexInfoBufferField.VertexCounts), Const(0) }));
|
||||||
|
|
||||||
|
Operand primInputVertex = Local();
|
||||||
|
node.List.AddBefore(node, new Operation(
|
||||||
|
Instruction.Load,
|
||||||
|
StorageKind.LocalMemory,
|
||||||
|
primInputVertex,
|
||||||
|
new[] { Const(resourceManager.LocalTopologyRemapMemoryId), primVertex }));
|
||||||
|
|
||||||
|
Operand instanceIndex = Local();
|
||||||
|
node.List.AddBefore(node, new Operation(
|
||||||
|
Instruction.Load,
|
||||||
|
StorageKind.Input,
|
||||||
|
instanceIndex,
|
||||||
|
new[] { Const((int)IoVariable.GlobalId), Const(1) }));
|
||||||
|
|
||||||
|
Operand baseVertex = Local();
|
||||||
|
node.List.AddBefore(node, new Operation(Instruction.Multiply, baseVertex, new[] { instanceIndex, vertexCount }));
|
||||||
|
|
||||||
|
Operand vertexIndex = Local();
|
||||||
|
node.List.AddBefore(node, new Operation(Instruction.Add, vertexIndex, new[] { baseVertex, primInputVertex }));
|
||||||
|
|
||||||
|
Operand vertexBaseOffset = Local();
|
||||||
|
node.List.AddBefore(node, new Operation(
|
||||||
|
Instruction.Multiply,
|
||||||
|
vertexBaseOffset,
|
||||||
|
new[] { vertexIndex, Const(resourceManager.Reservations.InputSizePerInvocation) }));
|
||||||
|
|
||||||
|
Operand vertexElemOffset;
|
||||||
|
|
||||||
|
if (elementOffset != 0)
|
||||||
|
{
|
||||||
|
vertexElemOffset = Local();
|
||||||
|
|
||||||
|
node.List.AddBefore(node, new Operation(Instruction.Add, vertexElemOffset, new[] { vertexBaseOffset, Const(elementOffset) }));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
vertexElemOffset = vertexBaseOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
return vertexElemOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static LinkedListNode<INode> GeneratePrimitiveId(ResourceManager resourceManager, LinkedListNode<INode> node, Operand dest)
|
||||||
|
{
|
||||||
|
int vertexInfoCbBinding = resourceManager.Reservations.VertexInfoConstantBufferBinding;
|
||||||
|
|
||||||
|
Operand vertexCount = Local();
|
||||||
|
node.List.AddBefore(node, new Operation(
|
||||||
|
Instruction.Load,
|
||||||
|
StorageKind.ConstantBuffer,
|
||||||
|
vertexCount,
|
||||||
|
new[] { Const(vertexInfoCbBinding), Const((int)VertexInfoBufferField.VertexCounts), Const(0) }));
|
||||||
|
|
||||||
|
Operand vertexIndex = Local();
|
||||||
|
node.List.AddBefore(node, new Operation(
|
||||||
|
Instruction.Load,
|
||||||
|
StorageKind.Input,
|
||||||
|
vertexIndex,
|
||||||
|
new[] { Const((int)IoVariable.GlobalId), Const(0) }));
|
||||||
|
|
||||||
|
Operand instanceIndex = Local();
|
||||||
|
node.List.AddBefore(node, new Operation(
|
||||||
|
Instruction.Load,
|
||||||
|
StorageKind.Input,
|
||||||
|
instanceIndex,
|
||||||
|
new[] { Const((int)IoVariable.GlobalId), Const(1) }));
|
||||||
|
|
||||||
|
Operand baseVertex = Local();
|
||||||
|
node.List.AddBefore(node, new Operation(Instruction.Multiply, baseVertex, new[] { instanceIndex, vertexCount }));
|
||||||
|
|
||||||
|
return node.List.AddBefore(node, new Operation(Instruction.Add, dest, new[] { baseVertex, vertexIndex }));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static LinkedListNode<INode> GenerateInvocationId(LinkedListNode<INode> node, Operand dest)
|
||||||
|
{
|
||||||
|
return node.List.AddBefore(node, new Operation(
|
||||||
|
Instruction.Load,
|
||||||
|
StorageKind.Input,
|
||||||
|
dest,
|
||||||
|
new[] { Const((int)IoVariable.GlobalId), Const(2) }));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryGetOffset(ResourceManager resourceManager, Operation operation, StorageKind storageKind, out int outputOffset)
|
||||||
|
{
|
||||||
|
bool isStore = operation.Inst == Instruction.Store;
|
||||||
|
|
||||||
|
IoVariable ioVariable = (IoVariable)operation.GetSource(0).Value;
|
||||||
|
|
||||||
|
bool isValidOutput;
|
||||||
|
|
||||||
|
if (ioVariable == IoVariable.UserDefined)
|
||||||
|
{
|
||||||
|
int lastIndex = operation.SourcesCount - (isStore ? 2 : 1);
|
||||||
|
|
||||||
|
int location = operation.GetSource(1).Value;
|
||||||
|
int component = operation.GetSource(lastIndex).Value;
|
||||||
|
|
||||||
|
isValidOutput = resourceManager.Reservations.TryGetOffset(storageKind, location, component, out outputOffset);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (ResourceReservations.IsVectorOrArrayVariable(ioVariable))
|
||||||
|
{
|
||||||
|
int component = operation.GetSource(operation.SourcesCount - (isStore ? 2 : 1)).Value;
|
||||||
|
|
||||||
|
isValidOutput = resourceManager.Reservations.TryGetOffset(storageKind, ioVariable, component, out outputOffset);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
isValidOutput = resourceManager.Reservations.TryGetOffset(storageKind, ioVariable, out outputOffset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return isValidOutput;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -153,15 +153,13 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
|
||||||
|
|
||||||
bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
|
bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
|
||||||
|
|
||||||
if (isBindless)
|
if (isBindless || !resourceManager.TryGetCbufSlotAndHandleForTexture(texOp.Binding, out int cbufSlot, out int handle))
|
||||||
{
|
{
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool intCoords = (texOp.Flags & TextureFlags.IntCoords) != 0;
|
bool intCoords = (texOp.Flags & TextureFlags.IntCoords) != 0;
|
||||||
|
|
||||||
(int cbufSlot, int handle) = resourceManager.GetCbufSlotAndHandleForTexture(texOp.Binding);
|
|
||||||
|
|
||||||
bool isCoordNormalized = gpuAccessor.QueryTextureCoordNormalized(handle, cbufSlot);
|
bool isCoordNormalized = gpuAccessor.QueryTextureCoordNormalized(handle, cbufSlot);
|
||||||
|
|
||||||
if (isCoordNormalized || intCoords)
|
if (isCoordNormalized || intCoords)
|
||||||
|
@ -607,13 +605,11 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
|
||||||
|
|
||||||
// We can't query the format of a bindless texture,
|
// We can't query the format of a bindless texture,
|
||||||
// because the handle is unknown, it can have any format.
|
// because the handle is unknown, it can have any format.
|
||||||
if (texOp.Flags.HasFlag(TextureFlags.Bindless))
|
if (texOp.Flags.HasFlag(TextureFlags.Bindless) || !resourceManager.TryGetCbufSlotAndHandleForTexture(texOp.Binding, out int cbufSlot, out int handle))
|
||||||
{
|
{
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
(int cbufSlot, int handle) = resourceManager.GetCbufSlotAndHandleForTexture(texOp.Binding);
|
|
||||||
|
|
||||||
TextureFormat format = gpuAccessor.QueryTextureFormat(handle, cbufSlot);
|
TextureFormat format = gpuAccessor.QueryTextureFormat(handle, cbufSlot);
|
||||||
|
|
||||||
int maxPositive = format switch
|
int maxPositive = format switch
|
||||||
|
|
|
@ -14,6 +14,8 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
|
||||||
RunPass<SharedStoreSmallIntCas>(context);
|
RunPass<SharedStoreSmallIntCas>(context);
|
||||||
RunPass<SharedAtomicSignedCas>(context);
|
RunPass<SharedAtomicSignedCas>(context);
|
||||||
RunPass<ShufflePass>(context);
|
RunPass<ShufflePass>(context);
|
||||||
|
RunPass<VertexToCompute>(context);
|
||||||
|
RunPass<GeometryToCompute>(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void RunPass<T>(TransformContext context) where T : ITransformPass
|
private static void RunPass<T>(TransformContext context) where T : ITransformPass
|
||||||
|
|
|
@ -0,0 +1,364 @@
|
||||||
|
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
|
||||||
|
using Ryujinx.Graphics.Shader.Translation.Optimizations;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
|
||||||
|
|
||||||
|
namespace Ryujinx.Graphics.Shader.Translation.Transforms
|
||||||
|
{
|
||||||
|
class VertexToCompute : ITransformPass
|
||||||
|
{
|
||||||
|
public static bool IsEnabled(IGpuAccessor gpuAccessor, ShaderStage stage, TargetLanguage targetLanguage, FeatureFlags usedFeatures)
|
||||||
|
{
|
||||||
|
return usedFeatures.HasFlag(FeatureFlags.VtgAsCompute);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LinkedListNode<INode> RunPass(TransformContext context, LinkedListNode<INode> node)
|
||||||
|
{
|
||||||
|
if (context.Definitions.Stage != ShaderStage.Vertex)
|
||||||
|
{
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
Operation operation = (Operation)node.Value;
|
||||||
|
|
||||||
|
LinkedListNode<INode> newNode = node;
|
||||||
|
|
||||||
|
if (operation.Inst == Instruction.Load && operation.StorageKind == StorageKind.Input)
|
||||||
|
{
|
||||||
|
Operand dest = operation.Dest;
|
||||||
|
|
||||||
|
switch ((IoVariable)operation.GetSource(0).Value)
|
||||||
|
{
|
||||||
|
case IoVariable.BaseInstance:
|
||||||
|
newNode = GenerateBaseInstanceLoad(context.ResourceManager, node, dest);
|
||||||
|
break;
|
||||||
|
case IoVariable.BaseVertex:
|
||||||
|
newNode = GenerateBaseVertexLoad(context.ResourceManager, node, dest);
|
||||||
|
break;
|
||||||
|
case IoVariable.InstanceId:
|
||||||
|
newNode = GenerateInstanceIdLoad(node, dest);
|
||||||
|
break;
|
||||||
|
case IoVariable.InstanceIndex:
|
||||||
|
newNode = GenerateInstanceIndexLoad(context.ResourceManager, node, dest);
|
||||||
|
break;
|
||||||
|
case IoVariable.VertexId:
|
||||||
|
case IoVariable.VertexIndex:
|
||||||
|
newNode = GenerateVertexIndexLoad(context.ResourceManager, node, dest);
|
||||||
|
break;
|
||||||
|
case IoVariable.UserDefined:
|
||||||
|
int location = operation.GetSource(1).Value;
|
||||||
|
int component = operation.GetSource(2).Value;
|
||||||
|
|
||||||
|
if (context.Definitions.IsAttributePacked(location))
|
||||||
|
{
|
||||||
|
bool needsSextNorm = context.Definitions.IsAttributePackedRgb10A2Signed(location);
|
||||||
|
|
||||||
|
Operand temp = needsSextNorm ? Local() : dest;
|
||||||
|
Operand vertexElemOffset = GenerateVertexOffset(context.ResourceManager, node, location, 0);
|
||||||
|
|
||||||
|
newNode = node.List.AddBefore(node, new TextureOperation(
|
||||||
|
Instruction.TextureSample,
|
||||||
|
SamplerType.TextureBuffer,
|
||||||
|
TextureFormat.Unknown,
|
||||||
|
TextureFlags.IntCoords,
|
||||||
|
context.ResourceManager.Reservations.GetVertexBufferTextureBinding(location),
|
||||||
|
1 << component,
|
||||||
|
new[] { temp },
|
||||||
|
new[] { vertexElemOffset }));
|
||||||
|
|
||||||
|
if (needsSextNorm)
|
||||||
|
{
|
||||||
|
bool sint = context.Definitions.IsAttributeSint(location);
|
||||||
|
CopySignExtendedNormalized(node, component == 3 ? 2 : 10, !sint, dest, temp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Operand temp = component > 0 ? Local() : dest;
|
||||||
|
Operand vertexElemOffset = GenerateVertexOffset(context.ResourceManager, node, location, component);
|
||||||
|
|
||||||
|
newNode = node.List.AddBefore(node, new TextureOperation(
|
||||||
|
Instruction.TextureSample,
|
||||||
|
SamplerType.TextureBuffer,
|
||||||
|
TextureFormat.Unknown,
|
||||||
|
TextureFlags.IntCoords,
|
||||||
|
context.ResourceManager.Reservations.GetVertexBufferTextureBinding(location),
|
||||||
|
1,
|
||||||
|
new[] { temp },
|
||||||
|
new[] { vertexElemOffset }));
|
||||||
|
|
||||||
|
if (component > 0)
|
||||||
|
{
|
||||||
|
newNode = CopyMasked(context.ResourceManager, newNode, location, component, dest, temp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case IoVariable.GlobalId:
|
||||||
|
case IoVariable.SubgroupEqMask:
|
||||||
|
case IoVariable.SubgroupGeMask:
|
||||||
|
case IoVariable.SubgroupGtMask:
|
||||||
|
case IoVariable.SubgroupLaneId:
|
||||||
|
case IoVariable.SubgroupLeMask:
|
||||||
|
case IoVariable.SubgroupLtMask:
|
||||||
|
// Those are valid or expected for vertex shaders.
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
context.GpuAccessor.Log($"Invalid input \"{(IoVariable)operation.GetSource(0).Value}\".");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (operation.Inst == Instruction.Load && operation.StorageKind == StorageKind.Output)
|
||||||
|
{
|
||||||
|
if (TryGetOutputOffset(context.ResourceManager, operation, out int outputOffset))
|
||||||
|
{
|
||||||
|
newNode = node.List.AddBefore(node, new Operation(
|
||||||
|
Instruction.Load,
|
||||||
|
StorageKind.LocalMemory,
|
||||||
|
operation.Dest,
|
||||||
|
new[] { Const(context.ResourceManager.LocalVertexDataMemoryId), Const(outputOffset) }));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
context.GpuAccessor.Log($"Invalid output \"{(IoVariable)operation.GetSource(0).Value}\".");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (operation.Inst == Instruction.Store && operation.StorageKind == StorageKind.Output)
|
||||||
|
{
|
||||||
|
if (TryGetOutputOffset(context.ResourceManager, operation, out int outputOffset))
|
||||||
|
{
|
||||||
|
Operand value = operation.GetSource(operation.SourcesCount - 1);
|
||||||
|
|
||||||
|
newNode = node.List.AddBefore(node, new Operation(
|
||||||
|
Instruction.Store,
|
||||||
|
StorageKind.LocalMemory,
|
||||||
|
(Operand)null,
|
||||||
|
new[] { Const(context.ResourceManager.LocalVertexDataMemoryId), Const(outputOffset), value }));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
context.GpuAccessor.Log($"Invalid output \"{(IoVariable)operation.GetSource(0).Value}\".");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newNode != node)
|
||||||
|
{
|
||||||
|
Utils.DeleteNode(node, operation);
|
||||||
|
}
|
||||||
|
|
||||||
|
return newNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Operand GenerateVertexOffset(ResourceManager resourceManager, LinkedListNode<INode> node, int location, int component)
|
||||||
|
{
|
||||||
|
int vertexInfoCbBinding = resourceManager.Reservations.VertexInfoConstantBufferBinding;
|
||||||
|
|
||||||
|
Operand vertexIdVr = Local();
|
||||||
|
GenerateVertexIdVertexRateLoad(resourceManager, node, vertexIdVr);
|
||||||
|
|
||||||
|
Operand vertexIdIr = Local();
|
||||||
|
GenerateVertexIdInstanceRateLoad(resourceManager, node, vertexIdIr);
|
||||||
|
|
||||||
|
Operand attributeOffset = Local();
|
||||||
|
node.List.AddBefore(node, new Operation(
|
||||||
|
Instruction.Load,
|
||||||
|
StorageKind.ConstantBuffer,
|
||||||
|
attributeOffset,
|
||||||
|
new[] { Const(vertexInfoCbBinding), Const((int)VertexInfoBufferField.VertexOffsets), Const(location), Const(0) }));
|
||||||
|
|
||||||
|
Operand isInstanceRate = Local();
|
||||||
|
node.List.AddBefore(node, new Operation(
|
||||||
|
Instruction.Load,
|
||||||
|
StorageKind.ConstantBuffer,
|
||||||
|
isInstanceRate,
|
||||||
|
new[] { Const(vertexInfoCbBinding), Const((int)VertexInfoBufferField.VertexOffsets), Const(location), Const(1) }));
|
||||||
|
|
||||||
|
Operand vertexId = Local();
|
||||||
|
node.List.AddBefore(node, new Operation(
|
||||||
|
Instruction.ConditionalSelect,
|
||||||
|
vertexId,
|
||||||
|
new[] { isInstanceRate, vertexIdIr, vertexIdVr }));
|
||||||
|
|
||||||
|
Operand vertexStride = Local();
|
||||||
|
node.List.AddBefore(node, new Operation(
|
||||||
|
Instruction.Load,
|
||||||
|
StorageKind.ConstantBuffer,
|
||||||
|
vertexStride,
|
||||||
|
new[] { Const(vertexInfoCbBinding), Const((int)VertexInfoBufferField.VertexStrides), Const(location), Const(0) }));
|
||||||
|
|
||||||
|
Operand vertexBaseOffset = Local();
|
||||||
|
node.List.AddBefore(node, new Operation(Instruction.Multiply, vertexBaseOffset, new[] { vertexId, vertexStride }));
|
||||||
|
|
||||||
|
Operand vertexOffset = Local();
|
||||||
|
node.List.AddBefore(node, new Operation(Instruction.Add, vertexOffset, new[] { attributeOffset, vertexBaseOffset }));
|
||||||
|
|
||||||
|
Operand vertexElemOffset;
|
||||||
|
|
||||||
|
if (component != 0)
|
||||||
|
{
|
||||||
|
vertexElemOffset = Local();
|
||||||
|
|
||||||
|
node.List.AddBefore(node, new Operation(Instruction.Add, vertexElemOffset, new[] { vertexOffset, Const(component) }));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
vertexElemOffset = vertexOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
return vertexElemOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static LinkedListNode<INode> CopySignExtendedNormalized(LinkedListNode<INode> node, int bits, bool normalize, Operand dest, Operand src)
|
||||||
|
{
|
||||||
|
Operand leftShifted = Local();
|
||||||
|
node = node.List.AddAfter(node, new Operation(
|
||||||
|
Instruction.ShiftLeft,
|
||||||
|
leftShifted,
|
||||||
|
new[] { src, Const(32 - bits) }));
|
||||||
|
|
||||||
|
Operand rightShifted = normalize ? Local() : dest;
|
||||||
|
node = node.List.AddAfter(node, new Operation(
|
||||||
|
Instruction.ShiftRightS32,
|
||||||
|
rightShifted,
|
||||||
|
new[] { leftShifted, Const(32 - bits) }));
|
||||||
|
|
||||||
|
if (normalize)
|
||||||
|
{
|
||||||
|
Operand asFloat = Local();
|
||||||
|
node = node.List.AddAfter(node, new Operation(Instruction.ConvertS32ToFP32, asFloat, new[] { rightShifted }));
|
||||||
|
node = node.List.AddAfter(node, new Operation(
|
||||||
|
Instruction.FP32 | Instruction.Multiply,
|
||||||
|
dest,
|
||||||
|
new[] { asFloat, ConstF(1f / (1 << (bits - 1))) }));
|
||||||
|
}
|
||||||
|
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static LinkedListNode<INode> CopyMasked(
|
||||||
|
ResourceManager resourceManager,
|
||||||
|
LinkedListNode<INode> node,
|
||||||
|
int location,
|
||||||
|
int component,
|
||||||
|
Operand dest,
|
||||||
|
Operand src)
|
||||||
|
{
|
||||||
|
Operand componentExists = Local();
|
||||||
|
int vertexInfoCbBinding = resourceManager.Reservations.VertexInfoConstantBufferBinding;
|
||||||
|
node = node.List.AddAfter(node, new Operation(
|
||||||
|
Instruction.Load,
|
||||||
|
StorageKind.ConstantBuffer,
|
||||||
|
componentExists,
|
||||||
|
new[] { Const(vertexInfoCbBinding), Const((int)VertexInfoBufferField.VertexStrides), Const(location), Const(component) }));
|
||||||
|
|
||||||
|
return node.List.AddAfter(node, new Operation(
|
||||||
|
Instruction.ConditionalSelect,
|
||||||
|
dest,
|
||||||
|
new[] { componentExists, src, ConstF(component == 3 ? 1f : 0f) }));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static LinkedListNode<INode> GenerateBaseVertexLoad(ResourceManager resourceManager, LinkedListNode<INode> node, Operand dest)
|
||||||
|
{
|
||||||
|
int vertexInfoCbBinding = resourceManager.Reservations.VertexInfoConstantBufferBinding;
|
||||||
|
|
||||||
|
return node.List.AddBefore(node, new Operation(
|
||||||
|
Instruction.Load,
|
||||||
|
StorageKind.ConstantBuffer,
|
||||||
|
dest,
|
||||||
|
new[] { Const(vertexInfoCbBinding), Const((int)VertexInfoBufferField.VertexCounts), Const(2) }));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static LinkedListNode<INode> GenerateBaseInstanceLoad(ResourceManager resourceManager, LinkedListNode<INode> node, Operand dest)
|
||||||
|
{
|
||||||
|
int vertexInfoCbBinding = resourceManager.Reservations.VertexInfoConstantBufferBinding;
|
||||||
|
|
||||||
|
return node.List.AddBefore(node, new Operation(
|
||||||
|
Instruction.Load,
|
||||||
|
StorageKind.ConstantBuffer,
|
||||||
|
dest,
|
||||||
|
new[] { Const(vertexInfoCbBinding), Const((int)VertexInfoBufferField.VertexCounts), Const(3) }));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static LinkedListNode<INode> GenerateVertexIndexLoad(ResourceManager resourceManager, LinkedListNode<INode> node, Operand dest)
|
||||||
|
{
|
||||||
|
Operand baseVertex = Local();
|
||||||
|
Operand vertexId = Local();
|
||||||
|
|
||||||
|
GenerateBaseVertexLoad(resourceManager, node, baseVertex);
|
||||||
|
GenerateVertexIdVertexRateLoad(resourceManager, node, vertexId);
|
||||||
|
|
||||||
|
return node.List.AddBefore(node, new Operation(Instruction.Add, dest, new[] { baseVertex, vertexId }));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static LinkedListNode<INode> GenerateInstanceIndexLoad(ResourceManager resourceManager, LinkedListNode<INode> node, Operand dest)
|
||||||
|
{
|
||||||
|
Operand baseInstance = Local();
|
||||||
|
Operand instanceId = Local();
|
||||||
|
|
||||||
|
GenerateBaseInstanceLoad(resourceManager, node, baseInstance);
|
||||||
|
|
||||||
|
node.List.AddBefore(node, new Operation(
|
||||||
|
Instruction.Load,
|
||||||
|
StorageKind.Input,
|
||||||
|
instanceId,
|
||||||
|
new[] { Const((int)IoVariable.GlobalId), Const(1) }));
|
||||||
|
|
||||||
|
return node.List.AddBefore(node, new Operation(Instruction.Add, dest, new[] { baseInstance, instanceId }));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static LinkedListNode<INode> GenerateVertexIdVertexRateLoad(ResourceManager resourceManager, LinkedListNode<INode> node, Operand dest)
|
||||||
|
{
|
||||||
|
Operand[] sources = new Operand[] { Const(resourceManager.LocalVertexIndexVertexRateMemoryId) };
|
||||||
|
|
||||||
|
return node.List.AddBefore(node, new Operation(Instruction.Load, StorageKind.LocalMemory, dest, sources));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static LinkedListNode<INode> GenerateVertexIdInstanceRateLoad(ResourceManager resourceManager, LinkedListNode<INode> node, Operand dest)
|
||||||
|
{
|
||||||
|
Operand[] sources = new Operand[] { Const(resourceManager.LocalVertexIndexInstanceRateMemoryId) };
|
||||||
|
|
||||||
|
return node.List.AddBefore(node, new Operation(Instruction.Load, StorageKind.LocalMemory, dest, sources));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static LinkedListNode<INode> GenerateInstanceIdLoad(LinkedListNode<INode> node, Operand dest)
|
||||||
|
{
|
||||||
|
Operand[] sources = new Operand[] { Const((int)IoVariable.GlobalId), Const(1) };
|
||||||
|
|
||||||
|
return node.List.AddBefore(node, new Operation(Instruction.Load, StorageKind.Input, dest, sources));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryGetOutputOffset(ResourceManager resourceManager, Operation operation, out int outputOffset)
|
||||||
|
{
|
||||||
|
bool isStore = operation.Inst == Instruction.Store;
|
||||||
|
|
||||||
|
IoVariable ioVariable = (IoVariable)operation.GetSource(0).Value;
|
||||||
|
|
||||||
|
bool isValidOutput;
|
||||||
|
|
||||||
|
if (ioVariable == IoVariable.UserDefined)
|
||||||
|
{
|
||||||
|
int lastIndex = operation.SourcesCount - (isStore ? 2 : 1);
|
||||||
|
|
||||||
|
int location = operation.GetSource(1).Value;
|
||||||
|
int component = operation.GetSource(lastIndex).Value;
|
||||||
|
|
||||||
|
isValidOutput = resourceManager.Reservations.TryGetOffset(StorageKind.Output, location, component, out outputOffset);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (ResourceReservations.IsVectorOrArrayVariable(ioVariable))
|
||||||
|
{
|
||||||
|
int component = operation.GetSource(operation.SourcesCount - (isStore ? 2 : 1)).Value;
|
||||||
|
|
||||||
|
isValidOutput = resourceManager.Reservations.TryGetOffset(StorageKind.Output, ioVariable, component, out outputOffset);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
isValidOutput = resourceManager.Reservations.TryGetOffset(StorageKind.Output, ioVariable, out outputOffset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return isValidOutput;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -77,12 +77,32 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ShaderDefinitions CreateGraphicsDefinitions(IGpuAccessor gpuAccessor, ShaderHeader header)
|
private static ShaderDefinitions CreateGraphicsDefinitions(IGpuAccessor gpuAccessor, ShaderHeader header)
|
||||||
|
{
|
||||||
|
TransformFeedbackOutput[] transformFeedbackOutputs = GetTransformFeedbackOutputs(gpuAccessor, out ulong transformFeedbackVecMap);
|
||||||
|
|
||||||
|
return new ShaderDefinitions(
|
||||||
|
header.Stage,
|
||||||
|
gpuAccessor.QueryGraphicsState(),
|
||||||
|
header.Stage == ShaderStage.Geometry && header.GpPassthrough,
|
||||||
|
header.ThreadsPerInputPrimitive,
|
||||||
|
header.OutputTopology,
|
||||||
|
header.MaxOutputVertexCount,
|
||||||
|
header.ImapTypes,
|
||||||
|
header.OmapTargets,
|
||||||
|
header.OmapSampleMask,
|
||||||
|
header.OmapDepth,
|
||||||
|
gpuAccessor.QueryHostSupportsScaledVertexFormats(),
|
||||||
|
transformFeedbackVecMap,
|
||||||
|
transformFeedbackOutputs);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static TransformFeedbackOutput[] GetTransformFeedbackOutputs(IGpuAccessor gpuAccessor, out ulong transformFeedbackVecMap)
|
||||||
{
|
{
|
||||||
bool transformFeedbackEnabled =
|
bool transformFeedbackEnabled =
|
||||||
gpuAccessor.QueryTransformFeedbackEnabled() &&
|
gpuAccessor.QueryTransformFeedbackEnabled() &&
|
||||||
gpuAccessor.QueryHostSupportsTransformFeedback();
|
gpuAccessor.QueryHostSupportsTransformFeedback();
|
||||||
TransformFeedbackOutput[] transformFeedbackOutputs = null;
|
TransformFeedbackOutput[] transformFeedbackOutputs = null;
|
||||||
ulong transformFeedbackVecMap = 0UL;
|
transformFeedbackVecMap = 0UL;
|
||||||
|
|
||||||
if (transformFeedbackEnabled)
|
if (transformFeedbackEnabled)
|
||||||
{
|
{
|
||||||
|
@ -105,21 +125,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ShaderDefinitions(
|
return transformFeedbackOutputs;
|
||||||
header.Stage,
|
|
||||||
gpuAccessor.QueryGraphicsState(),
|
|
||||||
header.Stage == ShaderStage.Geometry && header.GpPassthrough,
|
|
||||||
header.ThreadsPerInputPrimitive,
|
|
||||||
header.OutputTopology,
|
|
||||||
header.MaxOutputVertexCount,
|
|
||||||
header.ImapTypes,
|
|
||||||
header.OmapTargets,
|
|
||||||
header.OmapSampleMask,
|
|
||||||
header.OmapDepth,
|
|
||||||
gpuAccessor.QueryHostSupportsScaledVertexFormats(),
|
|
||||||
transformFeedbackEnabled,
|
|
||||||
transformFeedbackVecMap,
|
|
||||||
transformFeedbackOutputs);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int GetLocalMemorySize(ShaderHeader header)
|
private static int GetLocalMemorySize(ShaderHeader header)
|
||||||
|
@ -131,6 +137,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||||
TranslatorContext translatorContext,
|
TranslatorContext translatorContext,
|
||||||
ResourceManager resourceManager,
|
ResourceManager resourceManager,
|
||||||
DecodedProgram program,
|
DecodedProgram program,
|
||||||
|
bool vertexAsCompute,
|
||||||
bool initializeOutputs,
|
bool initializeOutputs,
|
||||||
out int initializationOperations)
|
out int initializationOperations)
|
||||||
{
|
{
|
||||||
|
@ -147,7 +154,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||||
|
|
||||||
for (int index = 0; index < functions.Length; index++)
|
for (int index = 0; index < functions.Length; index++)
|
||||||
{
|
{
|
||||||
EmitterContext context = new(translatorContext, resourceManager, program, index != 0);
|
EmitterContext context = new(translatorContext, resourceManager, program, vertexAsCompute, index != 0);
|
||||||
|
|
||||||
if (initializeOutputs && index == 0)
|
if (initializeOutputs && index == 0)
|
||||||
{
|
{
|
||||||
|
|
|
@ -8,7 +8,6 @@ using Ryujinx.Graphics.Shader.Translation.Optimizations;
|
||||||
using Ryujinx.Graphics.Shader.Translation.Transforms;
|
using Ryujinx.Graphics.Shader.Translation.Transforms;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
|
using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
|
||||||
using static Ryujinx.Graphics.Shader.Translation.Translator;
|
using static Ryujinx.Graphics.Shader.Translation.Translator;
|
||||||
|
@ -19,14 +18,12 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||||
{
|
{
|
||||||
private readonly DecodedProgram _program;
|
private readonly DecodedProgram _program;
|
||||||
private readonly int _localMemorySize;
|
private readonly int _localMemorySize;
|
||||||
|
private IoUsage _vertexOutput;
|
||||||
|
|
||||||
public ulong Address { get; }
|
public ulong Address { get; }
|
||||||
public int Size { get; }
|
public int Size { get; }
|
||||||
public int Cb1DataSize => _program.Cb1DataSize;
|
public int Cb1DataSize => _program.Cb1DataSize;
|
||||||
|
|
||||||
internal bool HasLayerInputAttribute { get; private set; }
|
|
||||||
internal int GpLayerInputAttribute { get; private set; }
|
|
||||||
|
|
||||||
internal AttributeUsage AttributeUsage => _program.AttributeUsage;
|
internal AttributeUsage AttributeUsage => _program.AttributeUsage;
|
||||||
|
|
||||||
internal ShaderDefinitions Definitions { get; }
|
internal ShaderDefinitions Definitions { get; }
|
||||||
|
@ -37,7 +34,8 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||||
|
|
||||||
internal TranslationOptions Options { get; }
|
internal TranslationOptions Options { get; }
|
||||||
|
|
||||||
internal FeatureFlags UsedFeatures { get; private set; }
|
private bool IsTransformFeedbackEmulated => !GpuAccessor.QueryHostSupportsTransformFeedback() && GpuAccessor.QueryTransformFeedbackEnabled();
|
||||||
|
public bool HasStore => _program.UsedFeatures.HasFlag(FeatureFlags.Store) || (IsTransformFeedbackEmulated && Definitions.LastInVertexPipeline);
|
||||||
|
|
||||||
public bool LayerOutputWritten { get; private set; }
|
public bool LayerOutputWritten { get; private set; }
|
||||||
public int LayerOutputAttribute { get; private set; }
|
public int LayerOutputAttribute { get; private set; }
|
||||||
|
@ -55,10 +53,10 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||||
Size = size;
|
Size = size;
|
||||||
_program = program;
|
_program = program;
|
||||||
_localMemorySize = localMemorySize;
|
_localMemorySize = localMemorySize;
|
||||||
|
_vertexOutput = new IoUsage(FeatureFlags.None, 0, -1);
|
||||||
Definitions = definitions;
|
Definitions = definitions;
|
||||||
GpuAccessor = gpuAccessor;
|
GpuAccessor = gpuAccessor;
|
||||||
Options = options;
|
Options = options;
|
||||||
UsedFeatures = program.UsedFeatures;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsLoadUserDefined(Operation operation)
|
private static bool IsLoadUserDefined(Operation operation)
|
||||||
|
@ -171,13 +169,6 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||||
LayerOutputAttribute = attr;
|
LayerOutputAttribute = attr;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetGeometryShaderLayerInputAttribute(int attr)
|
|
||||||
{
|
|
||||||
UsedFeatures |= FeatureFlags.RtLayer;
|
|
||||||
HasLayerInputAttribute = true;
|
|
||||||
GpLayerInputAttribute = attr;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetLastInVertexPipeline()
|
public void SetLastInVertexPipeline()
|
||||||
{
|
{
|
||||||
Definitions.LastInVertexPipeline = true;
|
Definitions.LastInVertexPipeline = true;
|
||||||
|
@ -187,7 +178,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||||
{
|
{
|
||||||
AttributeUsage.MergeFromtNextStage(
|
AttributeUsage.MergeFromtNextStage(
|
||||||
Definitions.GpPassthrough,
|
Definitions.GpPassthrough,
|
||||||
nextStage.UsedFeatures.HasFlag(FeatureFlags.FixedFuncAttr),
|
nextStage._program.UsedFeatures.HasFlag(FeatureFlags.FixedFuncAttr),
|
||||||
nextStage.AttributeUsage);
|
nextStage.AttributeUsage);
|
||||||
|
|
||||||
// We don't consider geometry shaders using the geometry shader passthrough feature
|
// We don't consider geometry shaders using the geometry shader passthrough feature
|
||||||
|
@ -200,9 +191,9 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ShaderProgram Translate()
|
public ShaderProgram Translate(bool asCompute = false)
|
||||||
{
|
{
|
||||||
ResourceManager resourceManager = CreateResourceManager();
|
ResourceManager resourceManager = CreateResourceManager(asCompute);
|
||||||
|
|
||||||
bool usesLocalMemory = _program.UsedFeatures.HasFlag(FeatureFlags.LocalMemory);
|
bool usesLocalMemory = _program.UsedFeatures.HasFlag(FeatureFlags.LocalMemory);
|
||||||
|
|
||||||
|
@ -215,36 +206,42 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||||
resourceManager.SetCurrentSharedMemory(GpuAccessor.QueryComputeSharedMemorySize(), usesSharedMemory);
|
resourceManager.SetCurrentSharedMemory(GpuAccessor.QueryComputeSharedMemorySize(), usesSharedMemory);
|
||||||
}
|
}
|
||||||
|
|
||||||
FunctionCode[] code = EmitShader(this, resourceManager, _program, initializeOutputs: true, out _);
|
FunctionCode[] code = EmitShader(this, resourceManager, _program, asCompute, initializeOutputs: true, out _);
|
||||||
|
|
||||||
return Translate(code, resourceManager, UsedFeatures, _program.ClipDistancesWritten);
|
return Translate(code, resourceManager, _program.UsedFeatures, _program.ClipDistancesWritten, asCompute);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ShaderProgram Translate(TranslatorContext other)
|
public ShaderProgram Translate(TranslatorContext other, bool asCompute = false)
|
||||||
{
|
{
|
||||||
ResourceManager resourceManager = CreateResourceManager();
|
ResourceManager resourceManager = CreateResourceManager(asCompute);
|
||||||
|
|
||||||
bool usesLocalMemory = _program.UsedFeatures.HasFlag(FeatureFlags.LocalMemory);
|
bool usesLocalMemory = _program.UsedFeatures.HasFlag(FeatureFlags.LocalMemory);
|
||||||
resourceManager.SetCurrentLocalMemory(_localMemorySize, usesLocalMemory);
|
resourceManager.SetCurrentLocalMemory(_localMemorySize, usesLocalMemory);
|
||||||
|
|
||||||
FunctionCode[] code = EmitShader(this, resourceManager, _program, initializeOutputs: false, out _);
|
FunctionCode[] code = EmitShader(this, resourceManager, _program, asCompute, initializeOutputs: false, out _);
|
||||||
|
|
||||||
bool otherUsesLocalMemory = other._program.UsedFeatures.HasFlag(FeatureFlags.LocalMemory);
|
bool otherUsesLocalMemory = other._program.UsedFeatures.HasFlag(FeatureFlags.LocalMemory);
|
||||||
resourceManager.SetCurrentLocalMemory(other._localMemorySize, otherUsesLocalMemory);
|
resourceManager.SetCurrentLocalMemory(other._localMemorySize, otherUsesLocalMemory);
|
||||||
|
|
||||||
FunctionCode[] otherCode = EmitShader(other, resourceManager, other._program, initializeOutputs: true, out int aStart);
|
FunctionCode[] otherCode = EmitShader(other, resourceManager, other._program, asCompute, initializeOutputs: true, out int aStart);
|
||||||
|
|
||||||
code = Combine(otherCode, code, aStart);
|
code = Combine(otherCode, code, aStart);
|
||||||
|
|
||||||
return Translate(
|
return Translate(
|
||||||
code,
|
code,
|
||||||
resourceManager,
|
resourceManager,
|
||||||
UsedFeatures | other.UsedFeatures,
|
_program.UsedFeatures | other._program.UsedFeatures,
|
||||||
(byte)(_program.ClipDistancesWritten | other._program.ClipDistancesWritten));
|
(byte)(_program.ClipDistancesWritten | other._program.ClipDistancesWritten),
|
||||||
|
asCompute);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ShaderProgram Translate(FunctionCode[] functions, ResourceManager resourceManager, FeatureFlags usedFeatures, byte clipDistancesWritten)
|
private ShaderProgram Translate(FunctionCode[] functions, ResourceManager resourceManager, FeatureFlags usedFeatures, byte clipDistancesWritten, bool asCompute)
|
||||||
{
|
{
|
||||||
|
if (asCompute)
|
||||||
|
{
|
||||||
|
usedFeatures |= FeatureFlags.VtgAsCompute;
|
||||||
|
}
|
||||||
|
|
||||||
var cfgs = new ControlFlowGraph[functions.Length];
|
var cfgs = new ControlFlowGraph[functions.Length];
|
||||||
var frus = new RegisterUsage.FunctionRegisterUsage[functions.Length];
|
var frus = new RegisterUsage.FunctionRegisterUsage[functions.Length];
|
||||||
|
|
||||||
|
@ -294,6 +291,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||||
TransformContext context = new(
|
TransformContext context = new(
|
||||||
hfm,
|
hfm,
|
||||||
cfg.Blocks,
|
cfg.Blocks,
|
||||||
|
Definitions,
|
||||||
resourceManager,
|
resourceManager,
|
||||||
GpuAccessor,
|
GpuAccessor,
|
||||||
Options.TargetLanguage,
|
Options.TargetLanguage,
|
||||||
|
@ -307,28 +305,24 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||||
funcs[i] = new Function(cfg.Blocks, $"fun{i}", false, inArgumentsCount, outArgumentsCount);
|
funcs[i] = new Function(cfg.Blocks, $"fun{i}", false, inArgumentsCount, outArgumentsCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
var identification = ShaderIdentifier.Identify(funcs, GpuAccessor, Definitions.Stage, Definitions.InputTopology, out int layerInputAttr);
|
|
||||||
|
|
||||||
return Generate(
|
return Generate(
|
||||||
funcs,
|
funcs,
|
||||||
AttributeUsage,
|
AttributeUsage,
|
||||||
|
GetDefinitions(asCompute),
|
||||||
Definitions,
|
Definitions,
|
||||||
resourceManager,
|
resourceManager,
|
||||||
usedFeatures,
|
usedFeatures,
|
||||||
clipDistancesWritten,
|
clipDistancesWritten);
|
||||||
identification,
|
|
||||||
layerInputAttr);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private ShaderProgram Generate(
|
private ShaderProgram Generate(
|
||||||
IReadOnlyList<Function> funcs,
|
IReadOnlyList<Function> funcs,
|
||||||
AttributeUsage attributeUsage,
|
AttributeUsage attributeUsage,
|
||||||
ShaderDefinitions definitions,
|
ShaderDefinitions definitions,
|
||||||
|
ShaderDefinitions originalDefinitions,
|
||||||
ResourceManager resourceManager,
|
ResourceManager resourceManager,
|
||||||
FeatureFlags usedFeatures,
|
FeatureFlags usedFeatures,
|
||||||
byte clipDistancesWritten,
|
byte clipDistancesWritten)
|
||||||
ShaderIdentification identification = ShaderIdentification.None,
|
|
||||||
int layerInputAttr = 0)
|
|
||||||
{
|
{
|
||||||
var sInfo = StructuredProgram.MakeStructuredProgram(
|
var sInfo = StructuredProgram.MakeStructuredProgram(
|
||||||
funcs,
|
funcs,
|
||||||
|
@ -337,20 +331,28 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||||
resourceManager,
|
resourceManager,
|
||||||
Options.Flags.HasFlag(TranslationFlags.DebugMode));
|
Options.Flags.HasFlag(TranslationFlags.DebugMode));
|
||||||
|
|
||||||
|
int geometryVerticesPerPrimitive = Definitions.OutputTopology switch
|
||||||
|
{
|
||||||
|
OutputTopology.LineStrip => 2,
|
||||||
|
OutputTopology.TriangleStrip => 3,
|
||||||
|
_ => 1
|
||||||
|
};
|
||||||
|
|
||||||
var info = new ShaderProgramInfo(
|
var info = new ShaderProgramInfo(
|
||||||
resourceManager.GetConstantBufferDescriptors(),
|
resourceManager.GetConstantBufferDescriptors(),
|
||||||
resourceManager.GetStorageBufferDescriptors(),
|
resourceManager.GetStorageBufferDescriptors(),
|
||||||
resourceManager.GetTextureDescriptors(),
|
resourceManager.GetTextureDescriptors(),
|
||||||
resourceManager.GetImageDescriptors(),
|
resourceManager.GetImageDescriptors(),
|
||||||
identification,
|
originalDefinitions.Stage,
|
||||||
layerInputAttr,
|
geometryVerticesPerPrimitive,
|
||||||
definitions.Stage,
|
originalDefinitions.MaxOutputVertices,
|
||||||
|
originalDefinitions.ThreadsPerInputPrimitive,
|
||||||
usedFeatures.HasFlag(FeatureFlags.FragCoordXY),
|
usedFeatures.HasFlag(FeatureFlags.FragCoordXY),
|
||||||
usedFeatures.HasFlag(FeatureFlags.InstanceId),
|
usedFeatures.HasFlag(FeatureFlags.InstanceId),
|
||||||
usedFeatures.HasFlag(FeatureFlags.DrawParameters),
|
usedFeatures.HasFlag(FeatureFlags.DrawParameters),
|
||||||
usedFeatures.HasFlag(FeatureFlags.RtLayer),
|
usedFeatures.HasFlag(FeatureFlags.RtLayer),
|
||||||
clipDistancesWritten,
|
clipDistancesWritten,
|
||||||
definitions.OmapTargets);
|
originalDefinitions.OmapTargets);
|
||||||
|
|
||||||
var hostCapabilities = new HostCapabilities(
|
var hostCapabilities = new HostCapabilities(
|
||||||
GpuAccessor.QueryHostReducedPrecision(),
|
GpuAccessor.QueryHostReducedPrecision(),
|
||||||
|
@ -372,37 +374,203 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private ResourceManager CreateResourceManager()
|
private ResourceManager CreateResourceManager(bool vertexAsCompute)
|
||||||
{
|
{
|
||||||
ResourceManager resourceManager = new(Definitions.Stage, GpuAccessor);
|
ResourceManager resourceManager = new(Definitions.Stage, GpuAccessor, GetResourceReservations());
|
||||||
|
|
||||||
if (!GpuAccessor.QueryHostSupportsTransformFeedback() && GpuAccessor.QueryTransformFeedbackEnabled())
|
if (IsTransformFeedbackEmulated)
|
||||||
{
|
{
|
||||||
StructureType tfeInfoStruct = new(new StructureField[]
|
|
||||||
{
|
|
||||||
new StructureField(AggregateType.Array | AggregateType.U32, "base_offset", 4),
|
|
||||||
new StructureField(AggregateType.U32, "vertex_count")
|
|
||||||
});
|
|
||||||
|
|
||||||
BufferDefinition tfeInfoBuffer = new(BufferLayout.Std430, 1, Constants.TfeInfoBinding, "tfe_info", tfeInfoStruct);
|
|
||||||
resourceManager.Properties.AddOrUpdateStorageBuffer(tfeInfoBuffer);
|
|
||||||
|
|
||||||
StructureType tfeDataStruct = new(new StructureField[]
|
StructureType tfeDataStruct = new(new StructureField[]
|
||||||
{
|
{
|
||||||
new StructureField(AggregateType.Array | AggregateType.U32, "data", 0)
|
new StructureField(AggregateType.Array | AggregateType.U32, "data", 0)
|
||||||
});
|
});
|
||||||
|
|
||||||
for (int i = 0; i < Constants.TfeBuffersCount; i++)
|
for (int i = 0; i < ResourceReservations.TfeBuffersCount; i++)
|
||||||
{
|
{
|
||||||
int binding = Constants.TfeBufferBaseBinding + i;
|
int binding = resourceManager.Reservations.GetTfeBufferStorageBufferBinding(i);
|
||||||
BufferDefinition tfeDataBuffer = new(BufferLayout.Std430, 1, binding, $"tfe_data{i}", tfeDataStruct);
|
BufferDefinition tfeDataBuffer = new(BufferLayout.Std430, 1, binding, $"tfe_data{i}", tfeDataStruct);
|
||||||
resourceManager.Properties.AddOrUpdateStorageBuffer(tfeDataBuffer);
|
resourceManager.Properties.AddOrUpdateStorageBuffer(tfeDataBuffer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (vertexAsCompute)
|
||||||
|
{
|
||||||
|
int vertexInfoCbBinding = resourceManager.Reservations.VertexInfoConstantBufferBinding;
|
||||||
|
BufferDefinition vertexInfoBuffer = new(BufferLayout.Std140, 0, vertexInfoCbBinding, "vb_info", VertexInfoBuffer.GetStructureType());
|
||||||
|
resourceManager.Properties.AddOrUpdateConstantBuffer(vertexInfoBuffer);
|
||||||
|
|
||||||
|
StructureType vertexOutputStruct = new(new StructureField[]
|
||||||
|
{
|
||||||
|
new StructureField(AggregateType.Array | AggregateType.FP32, "data", 0)
|
||||||
|
});
|
||||||
|
|
||||||
|
int vertexOutputSbBinding = resourceManager.Reservations.VertexOutputStorageBufferBinding;
|
||||||
|
BufferDefinition vertexOutputBuffer = new(BufferLayout.Std430, 1, vertexOutputSbBinding, "vertex_output", vertexOutputStruct);
|
||||||
|
resourceManager.Properties.AddOrUpdateStorageBuffer(vertexOutputBuffer);
|
||||||
|
|
||||||
|
if (Stage == ShaderStage.Vertex)
|
||||||
|
{
|
||||||
|
int ibBinding = resourceManager.Reservations.IndexBufferTextureBinding;
|
||||||
|
TextureDefinition indexBuffer = new(2, ibBinding, "ib_data", SamplerType.TextureBuffer, TextureFormat.Unknown, TextureUsageFlags.None);
|
||||||
|
resourceManager.Properties.AddOrUpdateTexture(indexBuffer);
|
||||||
|
|
||||||
|
int inputMap = _program.AttributeUsage.UsedInputAttributes;
|
||||||
|
|
||||||
|
while (inputMap != 0)
|
||||||
|
{
|
||||||
|
int location = BitOperations.TrailingZeroCount(inputMap);
|
||||||
|
int binding = resourceManager.Reservations.GetVertexBufferTextureBinding(location);
|
||||||
|
TextureDefinition vaBuffer = new(2, binding, $"vb_data{location}", SamplerType.TextureBuffer, TextureFormat.Unknown, TextureUsageFlags.None);
|
||||||
|
resourceManager.Properties.AddOrUpdateTexture(vaBuffer);
|
||||||
|
|
||||||
|
inputMap &= ~(1 << location);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (Stage == ShaderStage.Geometry)
|
||||||
|
{
|
||||||
|
int trbBinding = resourceManager.Reservations.TopologyRemapBufferTextureBinding;
|
||||||
|
TextureDefinition remapBuffer = new(2, trbBinding, "trb_data", SamplerType.TextureBuffer, TextureFormat.Unknown, TextureUsageFlags.None);
|
||||||
|
resourceManager.Properties.AddOrUpdateTexture(remapBuffer);
|
||||||
|
|
||||||
|
int geometryVbOutputSbBinding = resourceManager.Reservations.GeometryVertexOutputStorageBufferBinding;
|
||||||
|
BufferDefinition geometryVbOutputBuffer = new(BufferLayout.Std430, 1, geometryVbOutputSbBinding, "geometry_vb_output", vertexOutputStruct);
|
||||||
|
resourceManager.Properties.AddOrUpdateStorageBuffer(geometryVbOutputBuffer);
|
||||||
|
|
||||||
|
StructureType geometryIbOutputStruct = new(new StructureField[]
|
||||||
|
{
|
||||||
|
new StructureField(AggregateType.Array | AggregateType.U32, "data", 0)
|
||||||
|
});
|
||||||
|
|
||||||
|
int geometryIbOutputSbBinding = resourceManager.Reservations.GeometryIndexOutputStorageBufferBinding;
|
||||||
|
BufferDefinition geometryIbOutputBuffer = new(BufferLayout.Std430, 1, geometryIbOutputSbBinding, "geometry_ib_output", geometryIbOutputStruct);
|
||||||
|
resourceManager.Properties.AddOrUpdateStorageBuffer(geometryIbOutputBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
resourceManager.SetVertexAsComputeLocalMemories(Definitions.Stage, Definitions.InputTopology);
|
||||||
|
}
|
||||||
|
|
||||||
return resourceManager;
|
return resourceManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ShaderDefinitions GetDefinitions(bool vertexAsCompute)
|
||||||
|
{
|
||||||
|
if (vertexAsCompute)
|
||||||
|
{
|
||||||
|
return new ShaderDefinitions(ShaderStage.Compute, 32, 32, 1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return Definitions;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ResourceReservations GetResourceReservations()
|
||||||
|
{
|
||||||
|
IoUsage ioUsage = _program.GetIoUsage();
|
||||||
|
|
||||||
|
if (Definitions.GpPassthrough)
|
||||||
|
{
|
||||||
|
ioUsage = ioUsage.Combine(_vertexOutput);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ResourceReservations(GpuAccessor, IsTransformFeedbackEmulated, vertexAsCompute: true, _vertexOutput, ioUsage);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetVertexOutputMapForGeometryAsCompute(TranslatorContext vertexContext)
|
||||||
|
{
|
||||||
|
_vertexOutput = vertexContext._program.GetIoUsage();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ShaderProgram GenerateVertexPassthroughForCompute()
|
||||||
|
{
|
||||||
|
var attributeUsage = new AttributeUsage(GpuAccessor);
|
||||||
|
var resourceManager = new ResourceManager(ShaderStage.Vertex, GpuAccessor);
|
||||||
|
|
||||||
|
var reservations = GetResourceReservations();
|
||||||
|
|
||||||
|
int vertexInfoCbBinding = reservations.VertexInfoConstantBufferBinding;
|
||||||
|
|
||||||
|
if (Stage == ShaderStage.Vertex)
|
||||||
|
{
|
||||||
|
BufferDefinition vertexInfoBuffer = new(BufferLayout.Std140, 0, vertexInfoCbBinding, "vb_info", VertexInfoBuffer.GetStructureType());
|
||||||
|
resourceManager.Properties.AddOrUpdateConstantBuffer(vertexInfoBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
StructureType vertexInputStruct = new(new StructureField[]
|
||||||
|
{
|
||||||
|
new StructureField(AggregateType.Array | AggregateType.FP32, "data", 0)
|
||||||
|
});
|
||||||
|
|
||||||
|
int vertexDataSbBinding = reservations.VertexOutputStorageBufferBinding;
|
||||||
|
BufferDefinition vertexOutputBuffer = new(BufferLayout.Std430, 1, vertexDataSbBinding, "vb_input", vertexInputStruct);
|
||||||
|
resourceManager.Properties.AddOrUpdateStorageBuffer(vertexOutputBuffer);
|
||||||
|
|
||||||
|
var context = new EmitterContext();
|
||||||
|
|
||||||
|
Operand vertexIndex = Options.TargetApi == TargetApi.OpenGL
|
||||||
|
? context.Load(StorageKind.Input, IoVariable.VertexId)
|
||||||
|
: context.Load(StorageKind.Input, IoVariable.VertexIndex);
|
||||||
|
|
||||||
|
if (Stage == ShaderStage.Vertex)
|
||||||
|
{
|
||||||
|
Operand vertexCount = context.Load(StorageKind.ConstantBuffer, vertexInfoCbBinding, Const((int)VertexInfoBufferField.VertexCounts), Const(0));
|
||||||
|
|
||||||
|
// Base instance will be always zero when this shader is used, so which one we use here doesn't really matter.
|
||||||
|
Operand instanceId = Options.TargetApi == TargetApi.OpenGL
|
||||||
|
? context.Load(StorageKind.Input, IoVariable.InstanceId)
|
||||||
|
: context.Load(StorageKind.Input, IoVariable.InstanceIndex);
|
||||||
|
|
||||||
|
vertexIndex = context.IAdd(context.IMultiply(instanceId, vertexCount), vertexIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
Operand baseOffset = context.IMultiply(vertexIndex, Const(reservations.OutputSizePerInvocation));
|
||||||
|
|
||||||
|
foreach ((IoDefinition ioDefinition, int inputOffset) in reservations.Offsets)
|
||||||
|
{
|
||||||
|
if (ioDefinition.StorageKind != StorageKind.Output)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Operand vertexOffset = inputOffset != 0 ? context.IAdd(baseOffset, Const(inputOffset)) : baseOffset;
|
||||||
|
Operand value = context.Load(StorageKind.StorageBuffer, vertexDataSbBinding, Const(0), vertexOffset);
|
||||||
|
|
||||||
|
if (ioDefinition.IoVariable == IoVariable.UserDefined)
|
||||||
|
{
|
||||||
|
context.Store(StorageKind.Output, ioDefinition.IoVariable, null, Const(ioDefinition.Location), Const(ioDefinition.Component), value);
|
||||||
|
attributeUsage.SetOutputUserAttribute(ioDefinition.Location);
|
||||||
|
}
|
||||||
|
else if (ResourceReservations.IsVectorOrArrayVariable(ioDefinition.IoVariable))
|
||||||
|
{
|
||||||
|
context.Store(StorageKind.Output, ioDefinition.IoVariable, null, Const(ioDefinition.Component), value);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
context.Store(StorageKind.Output, ioDefinition.IoVariable, null, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var operations = context.GetOperations();
|
||||||
|
var cfg = ControlFlowGraph.Create(operations);
|
||||||
|
var function = new Function(cfg.Blocks, "main", false, 0, 0);
|
||||||
|
|
||||||
|
var transformFeedbackOutputs = GetTransformFeedbackOutputs(GpuAccessor, out ulong transformFeedbackVecMap);
|
||||||
|
|
||||||
|
var definitions = new ShaderDefinitions(ShaderStage.Vertex, transformFeedbackVecMap, transformFeedbackOutputs)
|
||||||
|
{
|
||||||
|
LastInVertexPipeline = true
|
||||||
|
};
|
||||||
|
|
||||||
|
return Generate(
|
||||||
|
new[] { function },
|
||||||
|
attributeUsage,
|
||||||
|
definitions,
|
||||||
|
definitions,
|
||||||
|
resourceManager,
|
||||||
|
FeatureFlags.None,
|
||||||
|
0);
|
||||||
|
}
|
||||||
|
|
||||||
public ShaderProgram GenerateGeometryPassthrough()
|
public ShaderProgram GenerateGeometryPassthrough()
|
||||||
{
|
{
|
||||||
int outputAttributesMask = AttributeUsage.UsedOutputAttributes;
|
int outputAttributesMask = AttributeUsage.UsedOutputAttributes;
|
||||||
|
@ -484,7 +652,14 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||||
outputTopology,
|
outputTopology,
|
||||||
maxOutputVertices);
|
maxOutputVertices);
|
||||||
|
|
||||||
return Generate(new[] { function }, attributeUsage, definitions, resourceManager, FeatureFlags.RtLayer, 0);
|
return Generate(
|
||||||
|
new[] { function },
|
||||||
|
attributeUsage,
|
||||||
|
definitions,
|
||||||
|
definitions,
|
||||||
|
resourceManager,
|
||||||
|
FeatureFlags.RtLayer,
|
||||||
|
0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
59
src/Ryujinx.Graphics.Shader/VertexInfoBuffer.cs
Normal file
59
src/Ryujinx.Graphics.Shader/VertexInfoBuffer.cs
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
using Ryujinx.Common.Memory;
|
||||||
|
using Ryujinx.Graphics.Shader.StructuredIr;
|
||||||
|
using Ryujinx.Graphics.Shader.Translation;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace Ryujinx.Graphics.Shader
|
||||||
|
{
|
||||||
|
enum VertexInfoBufferField
|
||||||
|
{
|
||||||
|
// Must match the order of the fields on the struct.
|
||||||
|
VertexCounts,
|
||||||
|
GeometryCounts,
|
||||||
|
VertexStrides,
|
||||||
|
VertexOffsets,
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct VertexInfoBuffer
|
||||||
|
{
|
||||||
|
public static readonly int RequiredSize;
|
||||||
|
|
||||||
|
public static readonly int VertexCountsOffset;
|
||||||
|
public static readonly int GeometryCountsOffset;
|
||||||
|
public static readonly int VertexStridesOffset;
|
||||||
|
public static readonly int VertexOffsetsOffset;
|
||||||
|
|
||||||
|
private static int OffsetOf<T>(ref VertexInfoBuffer storage, ref T target)
|
||||||
|
{
|
||||||
|
return (int)Unsafe.ByteOffset(ref Unsafe.As<VertexInfoBuffer, T>(ref storage), ref target);
|
||||||
|
}
|
||||||
|
|
||||||
|
static VertexInfoBuffer()
|
||||||
|
{
|
||||||
|
RequiredSize = Unsafe.SizeOf<VertexInfoBuffer>();
|
||||||
|
|
||||||
|
VertexInfoBuffer instance = new();
|
||||||
|
|
||||||
|
VertexCountsOffset = OffsetOf(ref instance, ref instance.VertexCounts);
|
||||||
|
GeometryCountsOffset = OffsetOf(ref instance, ref instance.GeometryCounts);
|
||||||
|
VertexStridesOffset = OffsetOf(ref instance, ref instance.VertexStrides);
|
||||||
|
VertexOffsetsOffset = OffsetOf(ref instance, ref instance.VertexOffsets);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static StructureType GetStructureType()
|
||||||
|
{
|
||||||
|
return new StructureType(new[]
|
||||||
|
{
|
||||||
|
new StructureField(AggregateType.Vector4 | AggregateType.U32, "vertex_counts"),
|
||||||
|
new StructureField(AggregateType.Vector4 | AggregateType.U32, "geometry_counts"),
|
||||||
|
new StructureField(AggregateType.Array | AggregateType.Vector4 | AggregateType.U32, "vertex_strides", ResourceReservations.MaxVertexBufferTextures),
|
||||||
|
new StructureField(AggregateType.Array | AggregateType.Vector4 | AggregateType.U32, "vertex_offsets", ResourceReservations.MaxVertexBufferTextures),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector4<int> VertexCounts;
|
||||||
|
public Vector4<int> GeometryCounts;
|
||||||
|
public Array32<Vector4<int>> VertexStrides;
|
||||||
|
public Array32<Vector4<int>> VertexOffsets;
|
||||||
|
}
|
||||||
|
}
|
|
@ -605,6 +605,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
supportsShaderBarrierDivergence: Vendor != Vendor.Intel,
|
supportsShaderBarrierDivergence: Vendor != Vendor.Intel,
|
||||||
supportsShaderFloat64: Capabilities.SupportsShaderFloat64,
|
supportsShaderFloat64: Capabilities.SupportsShaderFloat64,
|
||||||
supportsTextureShadowLod: false,
|
supportsTextureShadowLod: false,
|
||||||
|
supportsVertexStoreAndAtomics: features2.Features.VertexPipelineStoresAndAtomics,
|
||||||
supportsViewportIndexVertexTessellation: featuresVk12.ShaderOutputViewportIndex,
|
supportsViewportIndexVertexTessellation: featuresVk12.ShaderOutputViewportIndex,
|
||||||
supportsViewportMask: Capabilities.SupportsViewportArray2,
|
supportsViewportMask: Capabilities.SupportsViewportArray2,
|
||||||
supportsViewportSwizzle: false,
|
supportsViewportSwizzle: false,
|
||||||
|
@ -618,6 +619,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
maximumSupportedAnisotropy: (int)limits.MaxSamplerAnisotropy,
|
maximumSupportedAnisotropy: (int)limits.MaxSamplerAnisotropy,
|
||||||
shaderSubgroupSize: (int)Capabilities.SubgroupSize,
|
shaderSubgroupSize: (int)Capabilities.SubgroupSize,
|
||||||
storageBufferOffsetAlignment: (int)limits.MinStorageBufferOffsetAlignment,
|
storageBufferOffsetAlignment: (int)limits.MinStorageBufferOffsetAlignment,
|
||||||
|
textureBufferOffsetAlignment: (int)limits.MinTexelBufferOffsetAlignment,
|
||||||
gatherBiasPrecision: IsIntelWindows || IsAmdWindows ? (int)Capabilities.SubTexelPrecisionBits : 0);
|
gatherBiasPrecision: IsIntelWindows || IsAmdWindows ? (int)Capabilities.SubTexelPrecisionBits : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,12 @@ namespace Ryujinx.ShaderTools
|
||||||
[Option("compute", Required = false, Default = false, HelpText = "Indicate that the shader is a compute shader.")]
|
[Option("compute", Required = false, Default = false, HelpText = "Indicate that the shader is a compute shader.")]
|
||||||
public bool Compute { get; set; }
|
public bool Compute { get; set; }
|
||||||
|
|
||||||
|
[Option("vertex-as-compute", Required = false, Default = false, HelpText = "Indicate that the shader is a vertex shader and should be converted to compute.")]
|
||||||
|
public bool VertexAsCompute { get; set; }
|
||||||
|
|
||||||
|
[Option("vertex-passthrough", Required = false, Default = false, HelpText = "Indicate that the shader is a vertex passthrough shader for compute output.")]
|
||||||
|
public bool VertexPassthrough { get; set; }
|
||||||
|
|
||||||
[Option("target-language", Required = false, Default = TargetLanguage.Glsl, HelpText = "Indicate the target shader language to use.")]
|
[Option("target-language", Required = false, Default = TargetLanguage.Glsl, HelpText = "Indicate the target shader language to use.")]
|
||||||
public TargetLanguage TargetLanguage { get; set; }
|
public TargetLanguage TargetLanguage { get; set; }
|
||||||
|
|
||||||
|
@ -54,8 +60,18 @@ namespace Ryujinx.ShaderTools
|
||||||
byte[] data = File.ReadAllBytes(options.InputPath);
|
byte[] data = File.ReadAllBytes(options.InputPath);
|
||||||
|
|
||||||
TranslationOptions translationOptions = new(options.TargetLanguage, options.TargetApi, flags);
|
TranslationOptions translationOptions = new(options.TargetLanguage, options.TargetApi, flags);
|
||||||
|
TranslatorContext translatorContext = Translator.CreateContext(0, new GpuAccessor(data), translationOptions);
|
||||||
|
|
||||||
ShaderProgram program = Translator.CreateContext(0, new GpuAccessor(data), translationOptions).Translate();
|
ShaderProgram program;
|
||||||
|
|
||||||
|
if (options.VertexPassthrough)
|
||||||
|
{
|
||||||
|
program = translatorContext.GenerateVertexPassthroughForCompute();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
program = translatorContext.Translate(options.VertexAsCompute);
|
||||||
|
}
|
||||||
|
|
||||||
if (options.OutputPath == null)
|
if (options.OutputPath == null)
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in a new issue