diff options
| author | gdkchan <gab.dark.100@gmail.com> | 2023-08-29 21:10:34 -0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-08-29 21:10:34 -0300 |
| commit | f09bba82b9366e5912b639a610ae89cbb1cf352c (patch) | |
| tree | 4811ffa52206eed7cf8aa200c64deb7410e5c56b /src/Ryujinx.Graphics.Gpu/Engine/Threed/ComputeDraw | |
| parent | 93d78f9ac4a37a50f0cc2e57addd330d072af742 (diff) | |
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
Diffstat (limited to 'src/Ryujinx.Graphics.Gpu/Engine/Threed/ComputeDraw')
4 files changed, 1420 insertions, 0 deletions
diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Threed/ComputeDraw/VertexInfoBufferUpdater.cs b/src/Ryujinx.Graphics.Gpu/Engine/Threed/ComputeDraw/VertexInfoBufferUpdater.cs new file mode 100644 index 00000000..65f556fc --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Engine/Threed/ComputeDraw/VertexInfoBufferUpdater.cs @@ -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))); + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Threed/ComputeDraw/VtgAsCompute.cs b/src/Ryujinx.Graphics.Gpu/Engine/Threed/ComputeDraw/VtgAsCompute.cs new file mode 100644 index 00000000..cbbfd251 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Engine/Threed/ComputeDraw/VtgAsCompute.cs @@ -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); + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Threed/ComputeDraw/VtgAsComputeContext.cs b/src/Ryujinx.Graphics.Gpu/Engine/Threed/ComputeDraw/VtgAsComputeContext.cs new file mode 100644 index 00000000..e9b754ff --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Engine/Threed/ComputeDraw/VtgAsComputeContext.cs @@ -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); + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Threed/ComputeDraw/VtgAsComputeState.cs b/src/Ryujinx.Graphics.Gpu/Engine/Threed/ComputeDraw/VtgAsComputeState.cs new file mode 100644 index 00000000..59a92508 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Engine/Threed/ComputeDraw/VtgAsComputeState.cs @@ -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; + } + } +} |
