/ src / Ryujinx.Graphics.Gpu / Engine / Twod / TwodClass.cs
TwodClass.cs
  1  using Ryujinx.Common;
  2  using Ryujinx.Graphics.Device;
  3  using Ryujinx.Graphics.GAL;
  4  using Ryujinx.Graphics.Gpu.Engine.Types;
  5  using Ryujinx.Graphics.Gpu.Image;
  6  using Ryujinx.Graphics.Texture;
  7  using Ryujinx.Memory;
  8  using System;
  9  using System.Collections.Generic;
 10  using System.Runtime.CompilerServices;
 11  using System.Runtime.InteropServices;
 12  using System.Runtime.Intrinsics;
 13  
 14  namespace Ryujinx.Graphics.Gpu.Engine.Twod
 15  {
 16      /// <summary>
 17      /// Represents a 2D engine class.
 18      /// </summary>
 19      class TwodClass : IDeviceState
 20      {
 21          private readonly GpuChannel _channel;
 22          private readonly DeviceState<TwodClassState> _state;
 23  
 24          /// <summary>
 25          /// Creates a new instance of the 2D engine class.
 26          /// </summary>
 27          /// <param name="channel">The channel that will make use of the engine</param>
 28          public TwodClass(GpuChannel channel)
 29          {
 30              _channel = channel;
 31              _state = new DeviceState<TwodClassState>(new Dictionary<string, RwCallback>
 32              {
 33                  { nameof(TwodClassState.PixelsFromMemorySrcY0Int), new RwCallback(PixelsFromMemorySrcY0Int, null) },
 34              });
 35          }
 36  
 37          /// <summary>
 38          /// Reads data from the class registers.
 39          /// </summary>
 40          /// <param name="offset">Register byte offset</param>
 41          /// <returns>Data at the specified offset</returns>
 42          public int Read(int offset) => _state.Read(offset);
 43  
 44          /// <summary>
 45          /// Writes data to the class registers.
 46          /// </summary>
 47          /// <param name="offset">Register byte offset</param>
 48          /// <param name="data">Data to be written</param>
 49          public void Write(int offset, int data) => _state.Write(offset, data);
 50  
 51          /// <summary>
 52          /// Determines if data is compatible between the source and destination texture.
 53          /// The two textures must have the same size, layout, and bytes per pixel.
 54          /// </summary>
 55          /// <param name="lhs">Info for the first texture</param>
 56          /// <param name="rhs">Info for the second texture</param>
 57          /// <param name="lhsFormat">Format of the first texture</param>
 58          /// <param name="rhsFormat">Format of the second texture</param>
 59          /// <returns>True if the data is compatible, false otherwise</returns>
 60          private static bool IsDataCompatible(TwodTexture lhs, TwodTexture rhs, FormatInfo lhsFormat, FormatInfo rhsFormat)
 61          {
 62              if (lhsFormat.BytesPerPixel != rhsFormat.BytesPerPixel ||
 63                  lhs.Height != rhs.Height ||
 64                  lhs.Depth != rhs.Depth ||
 65                  lhs.LinearLayout != rhs.LinearLayout ||
 66                  lhs.MemoryLayout.Packed != rhs.MemoryLayout.Packed)
 67              {
 68                  return false;
 69              }
 70  
 71              if (lhs.LinearLayout)
 72              {
 73                  return lhs.Stride == rhs.Stride;
 74              }
 75              else
 76              {
 77                  return lhs.Width == rhs.Width;
 78              }
 79          }
 80  
 81          /// <summary>
 82          /// Determine if the given region covers the full texture, also considering width alignment.
 83          /// </summary>
 84          /// <param name="texture">The texture to check</param>
 85          /// <param name="formatInfo"></param>
 86          /// <param name="x1">Region start x</param>
 87          /// <param name="y1">Region start y</param>
 88          /// <param name="x2">Region end x</param>
 89          /// <param name="y2">Region end y</param>
 90          /// <returns>True if the region covers the full texture, false otherwise</returns>
 91          private static bool IsCopyRegionComplete(TwodTexture texture, FormatInfo formatInfo, int x1, int y1, int x2, int y2)
 92          {
 93              if (x1 != 0 || y1 != 0 || y2 != texture.Height)
 94              {
 95                  return false;
 96              }
 97  
 98              int width;
 99              int widthAlignment;
100  
101              if (texture.LinearLayout)
102              {
103                  widthAlignment = 1;
104                  width = texture.Stride / formatInfo.BytesPerPixel;
105              }
106              else
107              {
108                  widthAlignment = Constants.GobAlignment / formatInfo.BytesPerPixel;
109                  width = texture.Width;
110              }
111  
112              return width == BitUtils.AlignUp(x2, widthAlignment);
113          }
114  
115          /// <summary>
116          /// Performs a full data copy between two textures, reading and writing guest memory directly.
117          /// The textures must have a matching layout, size, and bytes per pixel.
118          /// </summary>
119          /// <param name="src">The source texture</param>
120          /// <param name="dst">The destination texture</param>
121          /// <param name="w">Copy width</param>
122          /// <param name="h">Copy height</param>
123          /// <param name="bpp">Bytes per pixel</param>
124          private void UnscaledFullCopy(TwodTexture src, TwodTexture dst, int w, int h, int bpp)
125          {
126              var srcCalculator = new OffsetCalculator(
127                  w,
128                  h,
129                  src.Stride,
130                  src.LinearLayout,
131                  src.MemoryLayout.UnpackGobBlocksInY(),
132                  src.MemoryLayout.UnpackGobBlocksInZ(),
133                  bpp);
134  
135              (int _, int srcSize) = srcCalculator.GetRectangleRange(0, 0, w, h);
136  
137              var memoryManager = _channel.MemoryManager;
138  
139              ulong srcGpuVa = src.Address.Pack();
140              ulong dstGpuVa = dst.Address.Pack();
141  
142              ReadOnlySpan<byte> srcSpan = memoryManager.GetSpan(srcGpuVa, srcSize, true);
143  
144              int width;
145              int height = src.Height;
146              if (src.LinearLayout)
147              {
148                  width = src.Stride / bpp;
149              }
150              else
151              {
152                  width = src.Width;
153              }
154  
155              // If the copy is not equal to the width and height of the texture, we will need to copy partially.
156              // It's worth noting that it has already been established that the src and dst are the same size.
157  
158              if (w == width && h == height)
159              {
160                  memoryManager.Write(dstGpuVa, srcSpan);
161              }
162              else
163              {
164                  using WritableRegion dstRegion = memoryManager.GetWritableRegion(dstGpuVa, srcSize, true);
165                  Span<byte> dstSpan = dstRegion.Memory.Span;
166  
167                  if (src.LinearLayout)
168                  {
169                      int stride = src.Stride;
170                      int offset = 0;
171                      int lineSize = width * bpp;
172  
173                      for (int y = 0; y < height; y++)
174                      {
175                          srcSpan.Slice(offset, lineSize).CopyTo(dstSpan[offset..]);
176  
177                          offset += stride;
178                      }
179                  }
180                  else
181                  {
182                      // Copy with the block linear layout in mind.
183                      // Recreate the offset calculate with bpp 1 for copy.
184  
185                      int stride = w * bpp;
186  
187                      srcCalculator = new OffsetCalculator(
188                          stride,
189                          h,
190                          0,
191                          false,
192                          src.MemoryLayout.UnpackGobBlocksInY(),
193                          src.MemoryLayout.UnpackGobBlocksInZ(),
194                          1);
195  
196                      int strideTrunc = BitUtils.AlignDown(stride, 16);
197  
198                      ReadOnlySpan<Vector128<byte>> srcVec = MemoryMarshal.Cast<byte, Vector128<byte>>(srcSpan);
199                      Span<Vector128<byte>> dstVec = MemoryMarshal.Cast<byte, Vector128<byte>>(dstSpan);
200  
201                      for (int y = 0; y < h; y++)
202                      {
203                          int x = 0;
204  
205                          srcCalculator.SetY(y);
206  
207                          for (; x < strideTrunc; x += 16)
208                          {
209                              int offset = srcCalculator.GetOffset(x) >> 4;
210  
211                              dstVec[offset] = srcVec[offset];
212                          }
213  
214                          for (; x < stride; x++)
215                          {
216                              int offset = srcCalculator.GetOffset(x);
217  
218                              dstSpan[offset] = srcSpan[offset];
219                          }
220                      }
221                  }
222              }
223          }
224  
225          /// <summary>
226          /// Performs the blit operation, triggered by the register write.
227          /// </summary>
228          /// <param name="argument">Method call argument</param>
229          private void PixelsFromMemorySrcY0Int(int argument)
230          {
231              var memoryManager = _channel.MemoryManager;
232  
233              var dstCopyTexture = Unsafe.As<uint, TwodTexture>(ref _state.State.SetDstFormat);
234              var srcCopyTexture = Unsafe.As<uint, TwodTexture>(ref _state.State.SetSrcFormat);
235  
236              long srcX = ((long)_state.State.SetPixelsFromMemorySrcX0Int << 32) | (long)(ulong)_state.State.SetPixelsFromMemorySrcX0Frac;
237              long srcY = ((long)_state.State.PixelsFromMemorySrcY0Int << 32) | (long)(ulong)_state.State.SetPixelsFromMemorySrcY0Frac;
238  
239              long duDx = ((long)_state.State.SetPixelsFromMemoryDuDxInt << 32) | (long)(ulong)_state.State.SetPixelsFromMemoryDuDxFrac;
240              long dvDy = ((long)_state.State.SetPixelsFromMemoryDvDyInt << 32) | (long)(ulong)_state.State.SetPixelsFromMemoryDvDyFrac;
241  
242              bool originCorner = _state.State.SetPixelsFromMemorySampleModeOrigin == SetPixelsFromMemorySampleModeOrigin.Corner;
243  
244              if (originCorner)
245              {
246                  // If the origin is corner, it is assumed that the guest API
247                  // is manually centering the origin by adding a offset to the
248                  // source region X/Y coordinates.
249                  // Here we attempt to remove such offset to ensure we have the correct region.
250                  // The offset is calculated as FactorXY / 2.0, where FactorXY = SrcXY / DstXY,
251                  // so we do the same here by dividing the fixed point value by 2, while
252                  // throwing away the fractional part to avoid rounding errors.
253                  srcX -= (duDx >> 33) << 32;
254                  srcY -= (dvDy >> 33) << 32;
255              }
256  
257              int srcX1 = (int)(srcX >> 32);
258              int srcY1 = (int)(srcY >> 32);
259  
260              int srcX2 = srcX1 + (int)((duDx * _state.State.SetPixelsFromMemoryDstWidth + uint.MaxValue) >> 32);
261              int srcY2 = srcY1 + (int)((dvDy * _state.State.SetPixelsFromMemoryDstHeight + uint.MaxValue) >> 32);
262  
263              int dstX1 = (int)_state.State.SetPixelsFromMemoryDstX0;
264              int dstY1 = (int)_state.State.SetPixelsFromMemoryDstY0;
265  
266              int dstX2 = dstX1 + (int)_state.State.SetPixelsFromMemoryDstWidth;
267              int dstY2 = dstY1 + (int)_state.State.SetPixelsFromMemoryDstHeight;
268  
269              // The source and destination textures should at least be as big as the region being requested.
270              // The hints will only resize within alignment constraints, so out of bound copies won't resize in most cases.
271              var srcHint = new Size(srcX2, srcY2, 1);
272              var dstHint = new Size(dstX2, dstY2, 1);
273  
274              var srcCopyTextureFormat = srcCopyTexture.Format.Convert();
275  
276              int srcWidthAligned = srcCopyTexture.Stride / srcCopyTextureFormat.BytesPerPixel;
277  
278              ulong offset = 0;
279  
280              // For an out of bounds copy, we must ensure that the copy wraps to the next line,
281              // so for a copy from a 64x64 texture, in the region [32, 96[, there are 32 pixels that are
282              // outside the bounds of the texture. We fill the destination with the first 32 pixels
283              // of the next line on the source texture.
284              // This can be done by simply adding an offset to the texture address, so that the initial
285              // gap is skipped and the copy is inside bounds again.
286              // This is required by the proprietary guest OpenGL driver.
287              if (srcCopyTexture.LinearLayout && srcCopyTexture.Width == srcX2 && srcX2 > srcWidthAligned && srcX1 > 0)
288              {
289                  offset = (ulong)(srcX1 * srcCopyTextureFormat.BytesPerPixel);
290                  srcCopyTexture.Width -= srcX1;
291                  srcX2 -= srcX1;
292                  srcX1 = 0;
293              }
294  
295              FormatInfo dstCopyTextureFormat = dstCopyTexture.Format.Convert();
296  
297              bool canDirectCopy = GraphicsConfig.Fast2DCopy &&
298                  srcX2 == dstX2 && srcY2 == dstY2 &&
299                  IsDataCompatible(srcCopyTexture, dstCopyTexture, srcCopyTextureFormat, dstCopyTextureFormat) &&
300                  IsCopyRegionComplete(srcCopyTexture, srcCopyTextureFormat, srcX1, srcY1, srcX2, srcY2) &&
301                  IsCopyRegionComplete(dstCopyTexture, dstCopyTextureFormat, dstX1, dstY1, dstX2, dstY2);
302  
303              // We can only allow aliasing of color formats as depth if the source and destination textures
304              // are the same, as we can't blit between different depth formats.
305              bool srcDepthAlias = srcCopyTexture.Format == dstCopyTexture.Format;
306  
307              var srcTexture = memoryManager.Physical.TextureCache.FindOrCreateTexture(
308                  memoryManager,
309                  srcCopyTexture,
310                  offset,
311                  srcCopyTextureFormat,
312                  srcDepthAlias,
313                  !canDirectCopy,
314                  false,
315                  srcHint);
316  
317              if (srcTexture == null)
318              {
319                  if (canDirectCopy)
320                  {
321                      // Directly copy the data on CPU.
322                      UnscaledFullCopy(srcCopyTexture, dstCopyTexture, srcX2, srcY2, srcCopyTextureFormat.BytesPerPixel);
323                  }
324  
325                  return;
326              }
327  
328              memoryManager.Physical.TextureCache.Lift(srcTexture);
329  
330              // When the source texture that was found has a depth format,
331              // we must enforce the target texture also has a depth format,
332              // as copies between depth and color formats are not allowed.
333              // For depth blit, the destination texture format should always match exactly.
334  
335              if (srcTexture.Format.IsDepthOrStencil())
336              {
337                  dstCopyTextureFormat = srcTexture.Info.FormatInfo;
338              }
339              else
340              {
341                  dstCopyTextureFormat = dstCopyTexture.Format.Convert();
342              }
343  
344              var dstTexture = memoryManager.Physical.TextureCache.FindOrCreateTexture(
345                  memoryManager,
346                  dstCopyTexture,
347                  0,
348                  dstCopyTextureFormat,
349                  depthAlias: false,
350                  shouldCreate: true,
351                  srcTexture.ScaleMode == TextureScaleMode.Scaled,
352                  dstHint);
353  
354              if (dstTexture == null)
355              {
356                  return;
357              }
358  
359              if (srcTexture.Info.Samples > 1 || dstTexture.Info.Samples > 1)
360              {
361                  srcTexture.PropagateScale(dstTexture);
362              }
363  
364              float scale = srcTexture.ScaleFactor;
365              float dstScale = dstTexture.ScaleFactor;
366  
367              Extents2D srcRegion = new(
368                  (int)Math.Ceiling(scale * (srcX1 / srcTexture.Info.SamplesInX)),
369                  (int)Math.Ceiling(scale * (srcY1 / srcTexture.Info.SamplesInY)),
370                  (int)Math.Ceiling(scale * (srcX2 / srcTexture.Info.SamplesInX)),
371                  (int)Math.Ceiling(scale * (srcY2 / srcTexture.Info.SamplesInY)));
372  
373              Extents2D dstRegion = new(
374                  (int)Math.Ceiling(dstScale * (dstX1 / dstTexture.Info.SamplesInX)),
375                  (int)Math.Ceiling(dstScale * (dstY1 / dstTexture.Info.SamplesInY)),
376                  (int)Math.Ceiling(dstScale * (dstX2 / dstTexture.Info.SamplesInX)),
377                  (int)Math.Ceiling(dstScale * (dstY2 / dstTexture.Info.SamplesInY)));
378  
379              bool linearFilter = _state.State.SetPixelsFromMemorySampleModeFilter == SetPixelsFromMemorySampleModeFilter.Bilinear;
380  
381              srcTexture.HostTexture.CopyTo(dstTexture.HostTexture, srcRegion, dstRegion, linearFilter);
382  
383              dstTexture.SignalModified();
384          }
385      }
386  }