diff options
Diffstat (limited to 'src/Ryujinx.Graphics.Gpu/Engine/Threed/ComputeDraw/VtgAsComputeContext.cs')
-rw-r--r-- | src/Ryujinx.Graphics.Gpu/Engine/Threed/ComputeDraw/VtgAsComputeContext.cs | 648 |
1 files changed, 648 insertions, 0 deletions
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); + } + } +} |