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:
gdkchan
2023-08-29 21:10:34 -03:00
committed by GitHub
parent 93d78f9ac4
commit f09bba82b9
65 changed files with 3912 additions and 593 deletions

View File

@@ -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)));
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}
}