diff options
Diffstat (limited to 'src/Ryujinx.Graphics.OpenGL/VertexArray.cs')
-rw-r--r-- | src/Ryujinx.Graphics.OpenGL/VertexArray.cs | 280 |
1 files changed, 280 insertions, 0 deletions
diff --git a/src/Ryujinx.Graphics.OpenGL/VertexArray.cs b/src/Ryujinx.Graphics.OpenGL/VertexArray.cs new file mode 100644 index 00000000..7d22033e --- /dev/null +++ b/src/Ryujinx.Graphics.OpenGL/VertexArray.cs @@ -0,0 +1,280 @@ +using OpenTK.Graphics.OpenGL; +using Ryujinx.Graphics.GAL; +using System; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Graphics.OpenGL +{ + class VertexArray : IDisposable + { + public int Handle { get; private set; } + + private readonly VertexAttribDescriptor[] _vertexAttribs; + private readonly VertexBufferDescriptor[] _vertexBuffers; + + private int _minVertexCount; + + private uint _vertexAttribsInUse; + private uint _vertexBuffersInUse; + private uint _vertexBuffersLimited; + + private BufferRange _indexBuffer; + private BufferHandle _tempIndexBuffer; + private BufferHandle _tempVertexBuffer; + private int _tempVertexBufferSize; + + public VertexArray() + { + Handle = GL.GenVertexArray(); + + _vertexAttribs = new VertexAttribDescriptor[Constants.MaxVertexAttribs]; + _vertexBuffers = new VertexBufferDescriptor[Constants.MaxVertexBuffers]; + + _tempIndexBuffer = Buffer.Create(); + } + + public void Bind() + { + GL.BindVertexArray(Handle); + } + + public void SetVertexBuffers(ReadOnlySpan<VertexBufferDescriptor> vertexBuffers) + { + int minVertexCount = int.MaxValue; + + int bindingIndex; + for (bindingIndex = 0; bindingIndex < vertexBuffers.Length; bindingIndex++) + { + VertexBufferDescriptor vb = vertexBuffers[bindingIndex]; + + if (vb.Buffer.Handle != BufferHandle.Null) + { + int vertexCount = vb.Stride <= 0 ? 0 : vb.Buffer.Size / vb.Stride; + if (minVertexCount > vertexCount) + { + minVertexCount = vertexCount; + } + + GL.BindVertexBuffer(bindingIndex, vb.Buffer.Handle.ToInt32(), (IntPtr)vb.Buffer.Offset, vb.Stride); + GL.VertexBindingDivisor(bindingIndex, vb.Divisor); + _vertexBuffersInUse |= 1u << bindingIndex; + } + else + { + if ((_vertexBuffersInUse & (1u << bindingIndex)) != 0) + { + GL.BindVertexBuffer(bindingIndex, 0, IntPtr.Zero, 0); + _vertexBuffersInUse &= ~(1u << bindingIndex); + } + } + + _vertexBuffers[bindingIndex] = vb; + } + + _minVertexCount = minVertexCount; + } + + public void SetVertexAttributes(ReadOnlySpan<VertexAttribDescriptor> vertexAttribs) + { + int index = 0; + + for (; index < vertexAttribs.Length; index++) + { + VertexAttribDescriptor attrib = vertexAttribs[index]; + + if (attrib.Equals(_vertexAttribs[index])) + { + continue; + } + + FormatInfo fmtInfo = FormatTable.GetFormatInfo(attrib.Format); + + if (attrib.IsZero) + { + // Disabling the attribute causes the shader to read a constant value. + // We currently set the constant to (0, 0, 0, 0). + DisableVertexAttrib(index); + } + else + { + EnableVertexAttrib(index); + } + + int offset = attrib.Offset; + int size = fmtInfo.Components; + + bool isFloat = fmtInfo.PixelType == PixelType.Float || + fmtInfo.PixelType == PixelType.HalfFloat; + + if (isFloat || fmtInfo.Normalized || fmtInfo.Scaled) + { + VertexAttribType type = (VertexAttribType)fmtInfo.PixelType; + + GL.VertexAttribFormat(index, size, type, fmtInfo.Normalized, offset); + } + else + { + VertexAttribIntegerType type = (VertexAttribIntegerType)fmtInfo.PixelType; + + GL.VertexAttribIFormat(index, size, type, offset); + } + + GL.VertexAttribBinding(index, attrib.BufferIndex); + + _vertexAttribs[index] = attrib; + } + + for (; index < Constants.MaxVertexAttribs; index++) + { + DisableVertexAttrib(index); + } + } + + public void SetIndexBuffer(BufferRange range) + { + _indexBuffer = range; + GL.BindBuffer(BufferTarget.ElementArrayBuffer, range.Handle.ToInt32()); + } + + public void SetRangeOfIndexBuffer() + { + Buffer.Resize(_tempIndexBuffer, _indexBuffer.Size); + Buffer.Copy(_indexBuffer.Handle, _tempIndexBuffer, _indexBuffer.Offset, 0, _indexBuffer.Size); + GL.BindBuffer(BufferTarget.ElementArrayBuffer, _tempIndexBuffer.ToInt32()); + } + + public void RestoreIndexBuffer() + { + GL.BindBuffer(BufferTarget.ElementArrayBuffer, _indexBuffer.Handle.ToInt32()); + } + + public void PreDraw(int vertexCount) + { + LimitVertexBuffers(vertexCount); + } + + public void PreDrawVbUnbounded() + { + UnlimitVertexBuffers(); + } + + public void LimitVertexBuffers(int vertexCount) + { + // Is it possible for the draw to fetch outside the bounds of any vertex buffer currently bound? + + if (vertexCount <= _minVertexCount) + { + return; + } + + // If the draw can fetch out of bounds, let's ensure that it will only fetch zeros rather than memory garbage. + + int currentTempVbOffset = 0; + uint buffersInUse = _vertexBuffersInUse; + + while (buffersInUse != 0) + { + int vbIndex = BitOperations.TrailingZeroCount(buffersInUse); + + ref var vb = ref _vertexBuffers[vbIndex]; + + int requiredSize = vertexCount * vb.Stride; + + if (vb.Buffer.Size < requiredSize) + { + BufferHandle tempVertexBuffer = EnsureTempVertexBufferSize(currentTempVbOffset + requiredSize); + + Buffer.Copy(vb.Buffer.Handle, tempVertexBuffer, vb.Buffer.Offset, currentTempVbOffset, vb.Buffer.Size); + Buffer.Clear(tempVertexBuffer, currentTempVbOffset + vb.Buffer.Size, requiredSize - vb.Buffer.Size, 0); + + GL.BindVertexBuffer(vbIndex, tempVertexBuffer.ToInt32(), (IntPtr)currentTempVbOffset, vb.Stride); + + currentTempVbOffset += requiredSize; + _vertexBuffersLimited |= 1u << vbIndex; + } + + buffersInUse &= ~(1u << vbIndex); + } + } + + private BufferHandle EnsureTempVertexBufferSize(int size) + { + BufferHandle tempVertexBuffer = _tempVertexBuffer; + + if (_tempVertexBufferSize < size) + { + _tempVertexBufferSize = size; + + if (tempVertexBuffer == BufferHandle.Null) + { + tempVertexBuffer = Buffer.Create(size); + _tempVertexBuffer = tempVertexBuffer; + return tempVertexBuffer; + } + + Buffer.Resize(_tempVertexBuffer, size); + } + + return tempVertexBuffer; + } + + public void UnlimitVertexBuffers() + { + uint buffersLimited = _vertexBuffersLimited; + + if (buffersLimited == 0) + { + return; + } + + while (buffersLimited != 0) + { + int vbIndex = BitOperations.TrailingZeroCount(buffersLimited); + + ref var vb = ref _vertexBuffers[vbIndex]; + + GL.BindVertexBuffer(vbIndex, vb.Buffer.Handle.ToInt32(), (IntPtr)vb.Buffer.Offset, vb.Stride); + + buffersLimited &= ~(1u << vbIndex); + } + + _vertexBuffersLimited = 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void EnableVertexAttrib(int index) + { + uint mask = 1u << index; + + if ((_vertexAttribsInUse & mask) == 0) + { + _vertexAttribsInUse |= mask; + GL.EnableVertexAttribArray(index); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void DisableVertexAttrib(int index) + { + uint mask = 1u << index; + + if ((_vertexAttribsInUse & mask) != 0) + { + _vertexAttribsInUse &= ~mask; + GL.DisableVertexAttribArray(index); + GL.VertexAttrib4(index, 0f, 0f, 0f, 1f); + } + } + + public void Dispose() + { + if (Handle != 0) + { + GL.DeleteVertexArray(Handle); + + Handle = 0; + } + } + } +} |