/ src / Ryujinx.Graphics.OpenGL / VertexArray.cs
VertexArray.cs
  1  using OpenTK.Graphics.OpenGL;
  2  using Ryujinx.Graphics.GAL;
  3  using System;
  4  using System.Numerics;
  5  using System.Runtime.CompilerServices;
  6  
  7  namespace Ryujinx.Graphics.OpenGL
  8  {
  9      class VertexArray : IDisposable
 10      {
 11          public int Handle { get; private set; }
 12  
 13          private readonly VertexAttribDescriptor[] _vertexAttribs;
 14          private readonly VertexBufferDescriptor[] _vertexBuffers;
 15  
 16          private int _minVertexCount;
 17  
 18          private uint _vertexAttribsInUse;
 19          private uint _vertexBuffersInUse;
 20          private uint _vertexBuffersLimited;
 21  
 22          private BufferRange _indexBuffer;
 23          private readonly BufferHandle _tempIndexBuffer;
 24          private BufferHandle _tempVertexBuffer;
 25          private int _tempVertexBufferSize;
 26  
 27          public VertexArray()
 28          {
 29              Handle = GL.GenVertexArray();
 30  
 31              _vertexAttribs = new VertexAttribDescriptor[Constants.MaxVertexAttribs];
 32              _vertexBuffers = new VertexBufferDescriptor[Constants.MaxVertexBuffers];
 33  
 34              _tempIndexBuffer = Buffer.Create();
 35          }
 36  
 37          public void Bind()
 38          {
 39              GL.BindVertexArray(Handle);
 40          }
 41  
 42          public void SetVertexBuffers(ReadOnlySpan<VertexBufferDescriptor> vertexBuffers)
 43          {
 44              int minVertexCount = int.MaxValue;
 45  
 46              int bindingIndex;
 47              for (bindingIndex = 0; bindingIndex < vertexBuffers.Length; bindingIndex++)
 48              {
 49                  VertexBufferDescriptor vb = vertexBuffers[bindingIndex];
 50  
 51                  if (vb.Buffer.Handle != BufferHandle.Null)
 52                  {
 53                      int vertexCount = vb.Stride <= 0 ? 0 : vb.Buffer.Size / vb.Stride;
 54                      if (minVertexCount > vertexCount)
 55                      {
 56                          minVertexCount = vertexCount;
 57                      }
 58  
 59                      GL.BindVertexBuffer(bindingIndex, vb.Buffer.Handle.ToInt32(), (IntPtr)vb.Buffer.Offset, vb.Stride);
 60                      GL.VertexBindingDivisor(bindingIndex, vb.Divisor);
 61                      _vertexBuffersInUse |= 1u << bindingIndex;
 62                  }
 63                  else
 64                  {
 65                      if ((_vertexBuffersInUse & (1u << bindingIndex)) != 0)
 66                      {
 67                          GL.BindVertexBuffer(bindingIndex, 0, IntPtr.Zero, 0);
 68                          _vertexBuffersInUse &= ~(1u << bindingIndex);
 69                      }
 70                  }
 71  
 72                  _vertexBuffers[bindingIndex] = vb;
 73              }
 74  
 75              _minVertexCount = minVertexCount;
 76          }
 77  
 78          public void SetVertexAttributes(ReadOnlySpan<VertexAttribDescriptor> vertexAttribs)
 79          {
 80              int index = 0;
 81  
 82              for (; index < vertexAttribs.Length; index++)
 83              {
 84                  VertexAttribDescriptor attrib = vertexAttribs[index];
 85  
 86                  if (attrib.Equals(_vertexAttribs[index]))
 87                  {
 88                      continue;
 89                  }
 90  
 91                  FormatInfo fmtInfo = FormatTable.GetFormatInfo(attrib.Format);
 92  
 93                  if (attrib.IsZero)
 94                  {
 95                      // Disabling the attribute causes the shader to read a constant value.
 96                      // We currently set the constant to (0, 0, 0, 0).
 97                      DisableVertexAttrib(index);
 98                  }
 99                  else
100                  {
101                      EnableVertexAttrib(index);
102                  }
103  
104                  int offset = attrib.Offset;
105                  int size = fmtInfo.Components;
106  
107                  bool isFloat = fmtInfo.PixelType == PixelType.Float ||
108                                 fmtInfo.PixelType == PixelType.HalfFloat;
109  
110                  if (isFloat || fmtInfo.Normalized || fmtInfo.Scaled)
111                  {
112                      VertexAttribType type = (VertexAttribType)fmtInfo.PixelType;
113  
114                      GL.VertexAttribFormat(index, size, type, fmtInfo.Normalized, offset);
115                  }
116                  else
117                  {
118                      VertexAttribIntegerType type = (VertexAttribIntegerType)fmtInfo.PixelType;
119  
120                      GL.VertexAttribIFormat(index, size, type, offset);
121                  }
122  
123                  GL.VertexAttribBinding(index, attrib.BufferIndex);
124  
125                  _vertexAttribs[index] = attrib;
126              }
127  
128              for (; index < Constants.MaxVertexAttribs; index++)
129              {
130                  DisableVertexAttrib(index);
131              }
132          }
133  
134          public void SetIndexBuffer(BufferRange range)
135          {
136              _indexBuffer = range;
137              GL.BindBuffer(BufferTarget.ElementArrayBuffer, range.Handle.ToInt32());
138          }
139  
140          public void SetRangeOfIndexBuffer()
141          {
142              Buffer.Resize(_tempIndexBuffer, _indexBuffer.Size);
143              Buffer.Copy(_indexBuffer.Handle, _tempIndexBuffer, _indexBuffer.Offset, 0, _indexBuffer.Size);
144              GL.BindBuffer(BufferTarget.ElementArrayBuffer, _tempIndexBuffer.ToInt32());
145          }
146  
147          public void RestoreIndexBuffer()
148          {
149              GL.BindBuffer(BufferTarget.ElementArrayBuffer, _indexBuffer.Handle.ToInt32());
150          }
151  
152          public void PreDraw(int vertexCount)
153          {
154              LimitVertexBuffers(vertexCount);
155          }
156  
157          public void PreDrawVbUnbounded()
158          {
159              UnlimitVertexBuffers();
160          }
161  
162          public void LimitVertexBuffers(int vertexCount)
163          {
164              // Is it possible for the draw to fetch outside the bounds of any vertex buffer currently bound?
165  
166              if (vertexCount <= _minVertexCount)
167              {
168                  return;
169              }
170  
171              // If the draw can fetch out of bounds, let's ensure that it will only fetch zeros rather than memory garbage.
172  
173              int currentTempVbOffset = 0;
174              uint buffersInUse = _vertexBuffersInUse;
175  
176              while (buffersInUse != 0)
177              {
178                  int vbIndex = BitOperations.TrailingZeroCount(buffersInUse);
179  
180                  ref var vb = ref _vertexBuffers[vbIndex];
181  
182                  int requiredSize = vertexCount * vb.Stride;
183  
184                  if (vb.Buffer.Size < requiredSize)
185                  {
186                      BufferHandle tempVertexBuffer = EnsureTempVertexBufferSize(currentTempVbOffset + requiredSize);
187  
188                      Buffer.Copy(vb.Buffer.Handle, tempVertexBuffer, vb.Buffer.Offset, currentTempVbOffset, vb.Buffer.Size);
189                      Buffer.Clear(tempVertexBuffer, currentTempVbOffset + vb.Buffer.Size, requiredSize - vb.Buffer.Size, 0);
190  
191                      GL.BindVertexBuffer(vbIndex, tempVertexBuffer.ToInt32(), (IntPtr)currentTempVbOffset, vb.Stride);
192  
193                      currentTempVbOffset += requiredSize;
194                      _vertexBuffersLimited |= 1u << vbIndex;
195                  }
196  
197                  buffersInUse &= ~(1u << vbIndex);
198              }
199          }
200  
201          private BufferHandle EnsureTempVertexBufferSize(int size)
202          {
203              BufferHandle tempVertexBuffer = _tempVertexBuffer;
204  
205              if (_tempVertexBufferSize < size)
206              {
207                  _tempVertexBufferSize = size;
208  
209                  if (tempVertexBuffer == BufferHandle.Null)
210                  {
211                      tempVertexBuffer = Buffer.Create(size);
212                      _tempVertexBuffer = tempVertexBuffer;
213                      return tempVertexBuffer;
214                  }
215  
216                  Buffer.Resize(_tempVertexBuffer, size);
217              }
218  
219              return tempVertexBuffer;
220          }
221  
222          public void UnlimitVertexBuffers()
223          {
224              uint buffersLimited = _vertexBuffersLimited;
225  
226              if (buffersLimited == 0)
227              {
228                  return;
229              }
230  
231              while (buffersLimited != 0)
232              {
233                  int vbIndex = BitOperations.TrailingZeroCount(buffersLimited);
234  
235                  ref var vb = ref _vertexBuffers[vbIndex];
236  
237                  GL.BindVertexBuffer(vbIndex, vb.Buffer.Handle.ToInt32(), (IntPtr)vb.Buffer.Offset, vb.Stride);
238  
239                  buffersLimited &= ~(1u << vbIndex);
240              }
241  
242              _vertexBuffersLimited = 0;
243          }
244  
245          [MethodImpl(MethodImplOptions.AggressiveInlining)]
246          private void EnableVertexAttrib(int index)
247          {
248              uint mask = 1u << index;
249  
250              if ((_vertexAttribsInUse & mask) == 0)
251              {
252                  _vertexAttribsInUse |= mask;
253                  GL.EnableVertexAttribArray(index);
254              }
255          }
256  
257          [MethodImpl(MethodImplOptions.AggressiveInlining)]
258          private void DisableVertexAttrib(int index)
259          {
260              uint mask = 1u << index;
261  
262              if ((_vertexAttribsInUse & mask) != 0)
263              {
264                  _vertexAttribsInUse &= ~mask;
265                  GL.DisableVertexAttribArray(index);
266                  GL.VertexAttrib4(index, 0f, 0f, 0f, 1f);
267              }
268          }
269  
270          public void Dispose()
271          {
272              if (Handle != 0)
273              {
274                  GL.DeleteVertexArray(Handle);
275  
276                  Handle = 0;
277              }
278          }
279      }
280  }