aboutsummaryrefslogtreecommitdiff
path: root/src/Ryujinx.Graphics.Gpu/Window.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/Ryujinx.Graphics.Gpu/Window.cs')
-rw-r--r--src/Ryujinx.Graphics.Gpu/Window.cs263
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