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 }