/ src / Ryujinx.Graphics.Gpu / Memory / MultiRangeBuffer.cs
MultiRangeBuffer.cs
  1  using Ryujinx.Graphics.GAL;
  2  using Ryujinx.Memory.Range;
  3  using System;
  4  using System.Collections.Generic;
  5  
  6  namespace Ryujinx.Graphics.Gpu.Memory
  7  {
  8      /// <summary>
  9      /// Buffer, used to store vertex and index data, uniform and storage buffers, and others.
 10      /// </summary>
 11      class MultiRangeBuffer : IMultiRangeItem, IDisposable
 12      {
 13          private readonly GpuContext _context;
 14  
 15          /// <summary>
 16          /// Host buffer handle.
 17          /// </summary>
 18          public BufferHandle Handle { get; }
 19  
 20          /// <summary>
 21          /// Range of memory where the data is located.
 22          /// </summary>
 23          public MultiRange Range { get; }
 24  
 25          /// <summary>
 26          /// Ever increasing counter value indicating when the buffer was modified relative to other buffers.
 27          /// </summary>
 28          public int ModificationSequenceNumber { get; private set; }
 29  
 30          /// <summary>
 31          /// Physical buffer dependency entry.
 32          /// </summary>
 33          private readonly struct PhysicalDependency
 34          {
 35              /// <summary>
 36              /// Physical buffer.
 37              /// </summary>
 38              public readonly Buffer PhysicalBuffer;
 39  
 40              /// <summary>
 41              /// Offset of the range on the physical buffer.
 42              /// </summary>
 43              public readonly ulong PhysicalOffset;
 44  
 45              /// <summary>
 46              /// Offset of the range on the virtual buffer.
 47              /// </summary>
 48              public readonly ulong VirtualOffset;
 49  
 50              /// <summary>
 51              /// Size of the range.
 52              /// </summary>
 53              public readonly ulong Size;
 54  
 55              /// <summary>
 56              /// Creates a new physical dependency.
 57              /// </summary>
 58              /// <param name="physicalBuffer">Physical buffer</param>
 59              /// <param name="physicalOffset">Offset of the range on the physical buffer</param>
 60              /// <param name="virtualOffset">Offset of the range on the virtual buffer</param>
 61              /// <param name="size">Size of the range</param>
 62              public PhysicalDependency(Buffer physicalBuffer, ulong physicalOffset, ulong virtualOffset, ulong size)
 63              {
 64                  PhysicalBuffer = physicalBuffer;
 65                  PhysicalOffset = physicalOffset;
 66                  VirtualOffset = virtualOffset;
 67                  Size = size;
 68              }
 69          }
 70  
 71          private List<PhysicalDependency> _dependencies;
 72          private BufferModifiedRangeList _modifiedRanges = null;
 73  
 74          /// <summary>
 75          /// Creates a new instance of the buffer.
 76          /// </summary>
 77          /// <param name="context">GPU context that the buffer belongs to</param>
 78          /// <param name="range">Range of memory where the data is mapped</param>
 79          public MultiRangeBuffer(GpuContext context, MultiRange range)
 80          {
 81              _context = context;
 82              Range = range;
 83              Handle = context.Renderer.CreateBuffer((int)range.GetSize());
 84          }
 85  
 86          /// <summary>
 87          /// Creates a new instance of the buffer.
 88          /// </summary>
 89          /// <param name="context">GPU context that the buffer belongs to</param>
 90          /// <param name="range">Range of memory where the data is mapped</param>
 91          /// <param name="storages">Backing memory for the buffer</param>
 92          public MultiRangeBuffer(GpuContext context, MultiRange range, ReadOnlySpan<BufferRange> storages)
 93          {
 94              _context = context;
 95              Range = range;
 96              Handle = context.Renderer.CreateBufferSparse(storages);
 97          }
 98  
 99          /// <summary>
100          /// Gets a sub-range from the buffer.
101          /// </summary>
102          /// <remarks>
103          /// This can be used to bind and use sub-ranges of the buffer on the host API.
104          /// </remarks>
105          /// <param name="range">Range of memory where the data is mapped</param>
106          /// <returns>The buffer sub-range</returns>
107          public BufferRange GetRange(MultiRange range)
108          {
109              int offset = Range.FindOffset(range);
110  
111              return new BufferRange(Handle, offset, (int)range.GetSize());
112          }
113  
114          /// <summary>
115          /// Removes all physical buffer dependencies.
116          /// </summary>
117          public void ClearPhysicalDependencies()
118          {
119              _dependencies?.Clear();
120          }
121  
122          /// <summary>
123          /// Adds a physical buffer dependency.
124          /// </summary>
125          /// <param name="buffer">Physical buffer to be added</param>
126          /// <param name="rangeAddress">Address inside the physical buffer where the virtual buffer range is located</param>
127          /// <param name="dstOffset">Offset inside the virtual buffer where the physical range is located</param>
128          /// <param name="rangeSize">Size of the range in bytes</param>
129          public void AddPhysicalDependency(Buffer buffer, ulong rangeAddress, ulong dstOffset, ulong rangeSize)
130          {
131              (_dependencies ??= new()).Add(new(buffer, rangeAddress - buffer.Address, dstOffset, rangeSize));
132              buffer.AddVirtualDependency(this);
133          }
134  
135          /// <summary>
136          /// Tries to get the physical range corresponding to the given physical buffer.
137          /// </summary>
138          /// <param name="buffer">Physical buffer</param>
139          /// <param name="minimumVirtOffset">Minimum virtual offset that a range match can have</param>
140          /// <param name="physicalOffset">Physical offset of the match</param>
141          /// <param name="virtualOffset">Virtual offset of the match, always greater than or equal <paramref name="minimumVirtOffset"/></param>
142          /// <param name="size">Size of the range match</param>
143          /// <returns>True if a match was found for the given parameters, false otherwise</returns>
144          public bool TryGetPhysicalOffset(Buffer buffer, ulong minimumVirtOffset, out ulong physicalOffset, out ulong virtualOffset, out ulong size)
145          {
146              physicalOffset = 0;
147              virtualOffset = 0;
148              size = 0;
149  
150              if (_dependencies != null)
151              {
152                  foreach (var dependency in _dependencies)
153                  {
154                      if (dependency.PhysicalBuffer == buffer && dependency.VirtualOffset >= minimumVirtOffset)
155                      {
156                          physicalOffset = dependency.PhysicalOffset;
157                          virtualOffset = dependency.VirtualOffset;
158                          size = dependency.Size;
159  
160                          return true;
161                      }
162                  }
163              }
164  
165              return false;
166          }
167  
168          /// <summary>
169          /// Adds a modified virtual memory range.
170          /// </summary>
171          /// <remarks>
172          /// This is only required when the host does not support sparse buffers, otherwise only physical buffers need to track modification.
173          /// </remarks>
174          /// <param name="range">Modified range</param>
175          /// <param name="modifiedSequenceNumber">ModificationSequenceNumber</param>
176          public void AddModifiedRegion(MultiRange range, int modifiedSequenceNumber)
177          {
178              _modifiedRanges ??= new(_context, null, null);
179  
180              for (int i = 0; i < range.Count; i++)
181              {
182                  MemoryRange subRange = range.GetSubRange(i);
183  
184                  _modifiedRanges.SignalModified(subRange.Address, subRange.Size);
185              }
186  
187              ModificationSequenceNumber = modifiedSequenceNumber;
188          }
189  
190          /// <summary>
191          /// Calls the specified <paramref name="rangeAction"/> for all modified ranges that overlaps with <paramref name="buffer"/>.
192          /// </summary>
193          /// <param name="buffer">Buffer to have its range checked</param>
194          /// <param name="rangeAction">Action to perform for modified ranges</param>
195          public void ConsumeModifiedRegion(Buffer buffer, Action<ulong, ulong> rangeAction)
196          {
197              ConsumeModifiedRegion(buffer.Address, buffer.Size, rangeAction);
198          }
199  
200          /// <summary>
201          /// Calls the specified <paramref name="rangeAction"/> for all modified ranges that overlaps with <paramref name="address"/> and <paramref name="size"/>.
202          /// </summary>
203          /// <param name="address">Address of the region to consume</param>
204          /// <param name="size">Size of the region to consume</param>
205          /// <param name="rangeAction">Action to perform for modified ranges</param>
206          public void ConsumeModifiedRegion(ulong address, ulong size, Action<ulong, ulong> rangeAction)
207          {
208              if (_modifiedRanges != null)
209              {
210                  _modifiedRanges.GetRanges(address, size, rangeAction);
211                  _modifiedRanges.Clear(address, size);
212              }
213          }
214  
215          /// <summary>
216          /// Gets data from the specified region of the buffer, and places it on <paramref name="output"/>.
217          /// </summary>
218          /// <param name="output">Span to put the data into</param>
219          /// <param name="offset">Offset of the buffer to get the data from</param>
220          /// <param name="size">Size of the data in bytes</param>
221          public void GetData(Span<byte> output, int offset, int size)
222          {
223              using PinnedSpan<byte> data = _context.Renderer.GetBufferData(Handle, offset, size);
224              data.Get().CopyTo(output);
225          }
226  
227          /// <summary>
228          /// Disposes the host buffer.
229          /// </summary>
230          public void Dispose()
231          {
232              if (_dependencies != null)
233              {
234                  foreach (var dependency in _dependencies)
235                  {
236                      dependency.PhysicalBuffer.RemoveVirtualDependency(this);
237                  }
238  
239                  _dependencies = null;
240              }
241  
242              _context.Renderer.DeleteBuffer(Handle);
243          }
244      }
245  }