diff options
Diffstat (limited to 'src/Ryujinx.Graphics.Gpu/Window.cs')
-rw-r--r-- | src/Ryujinx.Graphics.Gpu/Window.cs | 263 |
1 files changed, 263 insertions, 0 deletions
diff --git a/src/Ryujinx.Graphics.Gpu/Window.cs b/src/Ryujinx.Graphics.Gpu/Window.cs new file mode 100644 index 00000000..06d0fddf --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Window.cs @@ -0,0 +1,263 @@ +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Gpu.Image; +using Ryujinx.Graphics.Texture; +using Ryujinx.Memory.Range; +using System; +using System.Collections.Concurrent; +using System.Threading; + +namespace Ryujinx.Graphics.Gpu +{ + using Texture = Image.Texture; + + /// <summary> + /// GPU image presentation window. + /// </summary> + public class Window + { + private readonly GpuContext _context; + + /// <summary> + /// Texture presented on the window. + /// </summary> + private readonly struct PresentationTexture + { + /// <summary> + /// Texture cache where the texture might be located. + /// </summary> + public TextureCache Cache { get; } + + /// <summary> + /// Texture information. + /// </summary> + public TextureInfo Info { get; } + + /// <summary> + /// Physical memory locations where the texture data is located. + /// </summary> + public MultiRange Range { get; } + + /// <summary> + /// Texture crop region. + /// </summary> + public ImageCrop Crop { get; } + + /// <summary> + /// Texture acquire callback. + /// </summary> + public Action<GpuContext, object> AcquireCallback { get; } + + /// <summary> + /// Texture release callback. + /// </summary> + public Action<object> ReleaseCallback { get; } + + /// <summary> + /// User defined object, passed to the various callbacks. + /// </summary> + public object UserObj { get; } + + /// <summary> + /// Creates a new instance of the presentation texture. + /// </summary> + /// <param name="cache">Texture cache used to look for the texture to be presented</param> + /// <param name="info">Information of the texture to be presented</param> + /// <param name="range">Physical memory locations where the texture data is located</param> + /// <param name="crop">Texture crop region</param> + /// <param name="acquireCallback">Texture acquire callback</param> + /// <param name="releaseCallback">Texture release callback</param> + /// <param name="userObj">User defined object passed to the release callback, can be used to identify the texture</param> + public PresentationTexture( + TextureCache cache, + TextureInfo info, + MultiRange range, + ImageCrop crop, + Action<GpuContext, object> acquireCallback, + Action<object> releaseCallback, + object userObj) + { + Cache = cache; + Info = info; + Range = range; + Crop = crop; + AcquireCallback = acquireCallback; + ReleaseCallback = releaseCallback; + UserObj = userObj; + } + } + + private readonly ConcurrentQueue<PresentationTexture> _frameQueue; + + private int _framesAvailable; + + public bool IsFrameAvailable => _framesAvailable != 0; + + /// <summary> + /// Creates a new instance of the GPU presentation window. + /// </summary> + /// <param name="context">GPU emulation context</param> + public Window(GpuContext context) + { + _context = context; + + _frameQueue = new ConcurrentQueue<PresentationTexture>(); + } + + /// <summary> + /// Enqueues a frame for presentation. + /// This method is thread safe and can be called from any thread. + /// When the texture is presented and not needed anymore, the release callback is called. + /// It's an error to modify the texture after calling this method, before the release callback is called. + /// </summary> + /// <param name="pid">Process ID of the process that owns the texture pointed to by <paramref name="address"/></param> + /// <param name="address">CPU virtual address of the texture data</param> + /// <param name="width">Texture width</param> + /// <param name="height">Texture height</param> + /// <param name="stride">Texture stride for linear texture, should be zero otherwise</param> + /// <param name="isLinear">Indicates if the texture is linear, normally false</param> + /// <param name="gobBlocksInY">GOB blocks in the Y direction, for block linear textures</param> + /// <param name="format">Texture format</param> + /// <param name="bytesPerPixel">Texture format bytes per pixel (must match the format)</param> + /// <param name="crop">Texture crop region</param> + /// <param name="acquireCallback">Texture acquire callback</param> + /// <param name="releaseCallback">Texture release callback</param> + /// <param name="userObj">User defined object passed to the release callback</param> + /// <exception cref="ArgumentException">Thrown when <paramref name="pid"/> is invalid</exception> + /// <returns>True if the frame was added to the queue, false otherwise</returns> + public bool EnqueueFrameThreadSafe( + ulong pid, + ulong address, + int width, + int height, + int stride, + bool isLinear, + int gobBlocksInY, + Format format, + int bytesPerPixel, + ImageCrop crop, + Action<GpuContext, object> acquireCallback, + Action<object> releaseCallback, + object userObj) + { + if (!_context.PhysicalMemoryRegistry.TryGetValue(pid, out var physicalMemory)) + { + return false; + } + + FormatInfo formatInfo = new FormatInfo(format, 1, 1, bytesPerPixel, 4); + + TextureInfo info = new TextureInfo( + 0UL, + width, + height, + 1, + 1, + 1, + 1, + stride, + isLinear, + gobBlocksInY, + 1, + 1, + Target.Texture2D, + formatInfo); + + int size = SizeCalculator.GetBlockLinearTextureSize( + width, + height, + 1, + 1, + 1, + 1, + 1, + bytesPerPixel, + gobBlocksInY, + 1, + 1).TotalSize; + + MultiRange range = new MultiRange(address, (ulong)size); + + _frameQueue.Enqueue(new PresentationTexture( + physicalMemory.TextureCache, + info, + range, + crop, + acquireCallback, + releaseCallback, + userObj)); + + return true; + } + + /// <summary> + /// Presents a texture on the queue. + /// If the queue is empty, then no texture is presented. + /// </summary> + /// <param name="swapBuffersCallback">Callback method to call when a new texture should be presented on the screen</param> + public void Present(Action swapBuffersCallback) + { + _context.AdvanceSequence(); + + if (_frameQueue.TryDequeue(out PresentationTexture pt)) + { + pt.AcquireCallback(_context, pt.UserObj); + + Texture texture = pt.Cache.FindOrCreateTexture(null, TextureSearchFlags.WithUpscale, pt.Info, 0, pt.Range); + + pt.Cache.Tick(); + + texture.SynchronizeMemory(); + + ImageCrop crop = pt.Crop; + + if (texture.Info.Width > pt.Info.Width || texture.Info.Height > pt.Info.Height) + { + int top = crop.Top; + int bottom = crop.Bottom; + int left = crop.Left; + int right = crop.Right; + + if (top == 0 && bottom == 0) + { + bottom = Math.Min(texture.Info.Height, pt.Info.Height); + } + + if (left == 0 && right == 0) + { + right = Math.Min(texture.Info.Width, pt.Info.Width); + } + + crop = new ImageCrop(left, right, top, bottom, crop.FlipX, crop.FlipY, crop.IsStretched, crop.AspectRatioX, crop.AspectRatioY); + } + + _context.Renderer.Window.Present(texture.HostTexture, crop, swapBuffersCallback); + + pt.ReleaseCallback(pt.UserObj); + } + } + + /// <summary> + /// Indicate that a frame on the queue is ready to be acquired. + /// </summary> + public void SignalFrameReady() + { + Interlocked.Increment(ref _framesAvailable); + } + + /// <summary> + /// Determine if any frames are available, and decrement the available count if there are. + /// </summary> + /// <returns>True if a frame is available, false otherwise</returns> + public bool ConsumeFrameAvailable() + { + if (Interlocked.CompareExchange(ref _framesAvailable, 0, 0) != 0) + { + Interlocked.Decrement(ref _framesAvailable); + + return true; + } + + return false; + } + } +}
\ No newline at end of file |