using Ryujinx.Common; using Ryujinx.Graphics.GAL; using System.Runtime.InteropServices; namespace Ryujinx.Graphics.Gpu.Engine.Threed { /// /// Holds inline index buffer state. /// The inline index buffer data is sent to the GPU through the command buffer. /// struct IbStreamer { private const int BufferCapacity = 256; // Must be a power of 2. private BufferHandle _inlineIndexBuffer; private int _inlineIndexBufferSize; private int _inlineIndexCount; private uint[] _buffer; #pragma warning disable IDE0051 // Remove unused private member private readonly int _bufferOffset; #pragma warning restore IDE0051 /// /// Indicates if any index buffer data has been pushed. /// public readonly bool HasInlineIndexData => _inlineIndexCount != 0; /// /// Total numbers of indices that have been pushed. /// public readonly int InlineIndexCount => _inlineIndexCount; /// /// Gets the handle for the host buffer currently holding the inline index buffer data. /// /// Host buffer handle public readonly BufferHandle GetInlineIndexBuffer() { return _inlineIndexBuffer; } /// /// Gets the number of elements on the current inline index buffer, /// while also resetting it to zero for the next draw. /// /// Host renderer /// Inline index buffer count public int GetAndResetInlineIndexCount(IRenderer renderer) { UpdateRemaining(renderer); int temp = _inlineIndexCount; _inlineIndexCount = 0; return temp; } /// /// Pushes four 8-bit index buffer elements. /// /// Host renderer /// Method call argument public void VbElementU8(IRenderer renderer, int argument) { byte i0 = (byte)argument; byte i1 = (byte)(argument >> 8); byte i2 = (byte)(argument >> 16); byte i3 = (byte)(argument >> 24); int offset = _inlineIndexCount; PushData(renderer, offset, i0); PushData(renderer, offset + 1, i1); PushData(renderer, offset + 2, i2); PushData(renderer, offset + 3, i3); _inlineIndexCount += 4; } /// /// Pushes two 16-bit index buffer elements. /// /// Host renderer /// Method call argument public void VbElementU16(IRenderer renderer, int argument) { ushort i0 = (ushort)argument; ushort i1 = (ushort)(argument >> 16); int offset = _inlineIndexCount; PushData(renderer, offset, i0); PushData(renderer, offset + 1, i1); _inlineIndexCount += 2; } /// /// Pushes one 32-bit index buffer element. /// /// Host renderer /// Method call argument public void VbElementU32(IRenderer renderer, int argument) { uint i0 = (uint)argument; int offset = _inlineIndexCount++; PushData(renderer, offset, i0); } /// /// Pushes a 32-bit value to the index buffer. /// /// Host renderer /// Offset where the data should be written, in 32-bit words /// Index value to be written private void PushData(IRenderer renderer, int offset, uint value) { _buffer ??= new uint[BufferCapacity]; // We upload data in chunks. // If we are at the start of a chunk, then the buffer might be full, // in that case we need to submit any existing data before overwriting the buffer. int subOffset = offset & (BufferCapacity - 1); if (subOffset == 0 && offset != 0) { int baseOffset = (offset - BufferCapacity) * sizeof(uint); BufferHandle buffer = GetInlineIndexBuffer(renderer, baseOffset, BufferCapacity * sizeof(uint)); renderer.SetBufferData(buffer, baseOffset, MemoryMarshal.Cast(_buffer)); } _buffer[subOffset] = value; } /// /// Makes sure that any pending data is submitted to the GPU before the index buffer is used. /// /// Host renderer private void UpdateRemaining(IRenderer renderer) { int offset = _inlineIndexCount; if (offset == 0) { return; } int count = offset & (BufferCapacity - 1); if (count == 0) { count = BufferCapacity; } int baseOffset = (offset - count) * sizeof(uint); int length = count * sizeof(uint); BufferHandle buffer = GetInlineIndexBuffer(renderer, baseOffset, length); renderer.SetBufferData(buffer, baseOffset, MemoryMarshal.Cast(_buffer)[..length]); } /// /// Gets the handle of a buffer large enough to hold the data that will be written to . /// /// Host renderer /// Offset where the data will be written /// Number of bytes that will be written /// Buffer handle private BufferHandle GetInlineIndexBuffer(IRenderer renderer, int offset, int length) { // Calculate a reasonable size for the buffer that can fit all the data, // and that also won't require frequent resizes if we need to push more data. int size = BitUtils.AlignUp(offset + length + 0x10, 0x200); if (_inlineIndexBuffer == BufferHandle.Null) { _inlineIndexBuffer = renderer.CreateBuffer(size); _inlineIndexBufferSize = size; } else if (_inlineIndexBufferSize < size) { BufferHandle oldBuffer = _inlineIndexBuffer; int oldSize = _inlineIndexBufferSize; _inlineIndexBuffer = renderer.CreateBuffer(size); _inlineIndexBufferSize = size; renderer.Pipeline.CopyBuffer(oldBuffer, _inlineIndexBuffer, 0, 0, oldSize); renderer.DeleteBuffer(oldBuffer); } return _inlineIndexBuffer; } } }