Window.cs
1 using Ryujinx.Graphics.GAL; 2 using Ryujinx.Graphics.Gpu.Image; 3 using Ryujinx.Graphics.Texture; 4 using Ryujinx.Memory.Range; 5 using System; 6 using System.Collections.Concurrent; 7 using System.Threading; 8 9 namespace Ryujinx.Graphics.Gpu 10 { 11 /// <summary> 12 /// GPU image presentation window. 13 /// </summary> 14 public class Window 15 { 16 private readonly GpuContext _context; 17 18 /// <summary> 19 /// Texture presented on the window. 20 /// </summary> 21 private readonly struct PresentationTexture 22 { 23 /// <summary> 24 /// Texture cache where the texture might be located. 25 /// </summary> 26 public TextureCache Cache { get; } 27 28 /// <summary> 29 /// Texture information. 30 /// </summary> 31 public TextureInfo Info { get; } 32 33 /// <summary> 34 /// Physical memory locations where the texture data is located. 35 /// </summary> 36 public MultiRange Range { get; } 37 38 /// <summary> 39 /// Texture crop region. 40 /// </summary> 41 public ImageCrop Crop { get; } 42 43 /// <summary> 44 /// Texture acquire callback. 45 /// </summary> 46 public Action<GpuContext, object> AcquireCallback { get; } 47 48 /// <summary> 49 /// Texture release callback. 50 /// </summary> 51 public Action<object> ReleaseCallback { get; } 52 53 /// <summary> 54 /// User defined object, passed to the various callbacks. 55 /// </summary> 56 public object UserObj { get; } 57 58 /// <summary> 59 /// Creates a new instance of the presentation texture. 60 /// </summary> 61 /// <param name="cache">Texture cache used to look for the texture to be presented</param> 62 /// <param name="info">Information of the texture to be presented</param> 63 /// <param name="range">Physical memory locations where the texture data is located</param> 64 /// <param name="crop">Texture crop region</param> 65 /// <param name="acquireCallback">Texture acquire callback</param> 66 /// <param name="releaseCallback">Texture release callback</param> 67 /// <param name="userObj">User defined object passed to the release callback, can be used to identify the texture</param> 68 public PresentationTexture( 69 TextureCache cache, 70 TextureInfo info, 71 MultiRange range, 72 ImageCrop crop, 73 Action<GpuContext, object> acquireCallback, 74 Action<object> releaseCallback, 75 object userObj) 76 { 77 Cache = cache; 78 Info = info; 79 Range = range; 80 Crop = crop; 81 AcquireCallback = acquireCallback; 82 ReleaseCallback = releaseCallback; 83 UserObj = userObj; 84 } 85 } 86 87 private readonly ConcurrentQueue<PresentationTexture> _frameQueue; 88 89 private int _framesAvailable; 90 91 public bool IsFrameAvailable => _framesAvailable != 0; 92 93 /// <summary> 94 /// Creates a new instance of the GPU presentation window. 95 /// </summary> 96 /// <param name="context">GPU emulation context</param> 97 public Window(GpuContext context) 98 { 99 _context = context; 100 101 _frameQueue = new ConcurrentQueue<PresentationTexture>(); 102 } 103 104 /// <summary> 105 /// Enqueues a frame for presentation. 106 /// This method is thread safe and can be called from any thread. 107 /// When the texture is presented and not needed anymore, the release callback is called. 108 /// It's an error to modify the texture after calling this method, before the release callback is called. 109 /// </summary> 110 /// <param name="pid">Process ID of the process that owns the texture pointed to by <paramref name="address"/></param> 111 /// <param name="address">CPU virtual address of the texture data</param> 112 /// <param name="width">Texture width</param> 113 /// <param name="height">Texture height</param> 114 /// <param name="stride">Texture stride for linear texture, should be zero otherwise</param> 115 /// <param name="isLinear">Indicates if the texture is linear, normally false</param> 116 /// <param name="gobBlocksInY">GOB blocks in the Y direction, for block linear textures</param> 117 /// <param name="format">Texture format</param> 118 /// <param name="bytesPerPixel">Texture format bytes per pixel (must match the format)</param> 119 /// <param name="crop">Texture crop region</param> 120 /// <param name="acquireCallback">Texture acquire callback</param> 121 /// <param name="releaseCallback">Texture release callback</param> 122 /// <param name="userObj">User defined object passed to the release callback</param> 123 /// <exception cref="ArgumentException">Thrown when <paramref name="pid"/> is invalid</exception> 124 /// <returns>True if the frame was added to the queue, false otherwise</returns> 125 public bool EnqueueFrameThreadSafe( 126 ulong pid, 127 ulong address, 128 int width, 129 int height, 130 int stride, 131 bool isLinear, 132 int gobBlocksInY, 133 Format format, 134 byte bytesPerPixel, 135 ImageCrop crop, 136 Action<GpuContext, object> acquireCallback, 137 Action<object> releaseCallback, 138 object userObj) 139 { 140 if (!_context.PhysicalMemoryRegistry.TryGetValue(pid, out var physicalMemory)) 141 { 142 return false; 143 } 144 145 FormatInfo formatInfo = new(format, 1, 1, bytesPerPixel, 4); 146 147 TextureInfo info = new( 148 0UL, 149 width, 150 height, 151 1, 152 1, 153 1, 154 1, 155 stride, 156 isLinear, 157 gobBlocksInY, 158 1, 159 1, 160 Target.Texture2D, 161 formatInfo); 162 163 int size = SizeCalculator.GetBlockLinearTextureSize( 164 width, 165 height, 166 1, 167 1, 168 1, 169 1, 170 1, 171 bytesPerPixel, 172 gobBlocksInY, 173 1, 174 1).TotalSize; 175 176 MultiRange range = new(address, (ulong)size); 177 178 _frameQueue.Enqueue(new PresentationTexture( 179 physicalMemory.TextureCache, 180 info, 181 range, 182 crop, 183 acquireCallback, 184 releaseCallback, 185 userObj)); 186 187 return true; 188 } 189 190 /// <summary> 191 /// Presents a texture on the queue. 192 /// If the queue is empty, then no texture is presented. 193 /// </summary> 194 /// <param name="swapBuffersCallback">Callback method to call when a new texture should be presented on the screen</param> 195 public void Present(Action swapBuffersCallback) 196 { 197 _context.AdvanceSequence(); 198 199 if (_frameQueue.TryDequeue(out PresentationTexture pt)) 200 { 201 pt.AcquireCallback(_context, pt.UserObj); 202 203 Image.Texture texture = pt.Cache.FindOrCreateTexture(null, TextureSearchFlags.WithUpscale, pt.Info, 0, range: pt.Range); 204 205 pt.Cache.Tick(); 206 207 texture.SynchronizeMemory(); 208 209 ImageCrop crop = new( 210 (int)(pt.Crop.Left * texture.ScaleFactor), 211 (int)MathF.Ceiling(pt.Crop.Right * texture.ScaleFactor), 212 (int)(pt.Crop.Top * texture.ScaleFactor), 213 (int)MathF.Ceiling(pt.Crop.Bottom * texture.ScaleFactor), 214 pt.Crop.FlipX, 215 pt.Crop.FlipY, 216 pt.Crop.IsStretched, 217 pt.Crop.AspectRatioX, 218 pt.Crop.AspectRatioY); 219 220 if (texture.Info.Width > pt.Info.Width || texture.Info.Height > pt.Info.Height) 221 { 222 int top = crop.Top; 223 int bottom = crop.Bottom; 224 int left = crop.Left; 225 int right = crop.Right; 226 227 if (top == 0 && bottom == 0) 228 { 229 bottom = Math.Min(texture.Info.Height, pt.Info.Height); 230 } 231 232 if (left == 0 && right == 0) 233 { 234 right = Math.Min(texture.Info.Width, pt.Info.Width); 235 } 236 237 crop = new ImageCrop(left, right, top, bottom, crop.FlipX, crop.FlipY, crop.IsStretched, crop.AspectRatioX, crop.AspectRatioY); 238 } 239 240 _context.Renderer.Window.Present(texture.HostTexture, crop, swapBuffersCallback); 241 242 pt.ReleaseCallback(pt.UserObj); 243 } 244 } 245 246 /// <summary> 247 /// Indicate that a frame on the queue is ready to be acquired. 248 /// </summary> 249 public void SignalFrameReady() 250 { 251 Interlocked.Increment(ref _framesAvailable); 252 } 253 254 /// <summary> 255 /// Determine if any frames are available, and decrement the available count if there are. 256 /// </summary> 257 /// <returns>True if a frame is available, false otherwise</returns> 258 public bool ConsumeFrameAvailable() 259 { 260 if (Interlocked.CompareExchange(ref _framesAvailable, 0, 0) != 0) 261 { 262 Interlocked.Decrement(ref _framesAvailable); 263 264 return true; 265 } 266 267 return false; 268 } 269 } 270 }