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
{
///
/// GPU image presentation window.
///
public class Window
{
private readonly GpuContext _context;
///
/// Texture presented on the window.
///
private readonly struct PresentationTexture
{
///
/// Texture cache where the texture might be located.
///
public TextureCache Cache { get; }
///
/// Texture information.
///
public TextureInfo Info { get; }
///
/// Physical memory locations where the texture data is located.
///
public MultiRange Range { get; }
///
/// Texture crop region.
///
public ImageCrop Crop { get; }
///
/// Texture acquire callback.
///
public Action AcquireCallback { get; }
///
/// Texture release callback.
///
public Action ReleaseCallback { get; }
///
/// User defined object, passed to the various callbacks.
///
public object UserObj { get; }
///
/// Creates a new instance of the presentation texture.
///
/// Texture cache used to look for the texture to be presented
/// Information of the texture to be presented
/// Physical memory locations where the texture data is located
/// Texture crop region
/// Texture acquire callback
/// Texture release callback
/// User defined object passed to the release callback, can be used to identify the texture
public PresentationTexture(
TextureCache cache,
TextureInfo info,
MultiRange range,
ImageCrop crop,
Action acquireCallback,
Action releaseCallback,
object userObj)
{
Cache = cache;
Info = info;
Range = range;
Crop = crop;
AcquireCallback = acquireCallback;
ReleaseCallback = releaseCallback;
UserObj = userObj;
}
}
private readonly ConcurrentQueue _frameQueue;
private int _framesAvailable;
public bool IsFrameAvailable => _framesAvailable != 0;
///
/// Creates a new instance of the GPU presentation window.
///
/// GPU emulation context
public Window(GpuContext context)
{
_context = context;
_frameQueue = new ConcurrentQueue();
}
///
/// 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.
///
/// Process ID of the process that owns the texture pointed to by
/// CPU virtual address of the texture data
/// Texture width
/// Texture height
/// Texture stride for linear texture, should be zero otherwise
/// Indicates if the texture is linear, normally false
/// GOB blocks in the Y direction, for block linear textures
/// Texture format
/// Texture format bytes per pixel (must match the format)
/// Texture crop region
/// Texture acquire callback
/// Texture release callback
/// User defined object passed to the release callback
/// Thrown when is invalid
/// True if the frame was added to the queue, false otherwise
public bool EnqueueFrameThreadSafe(
ulong pid,
ulong address,
int width,
int height,
int stride,
bool isLinear,
int gobBlocksInY,
Format format,
byte bytesPerPixel,
ImageCrop crop,
Action acquireCallback,
Action releaseCallback,
object userObj)
{
if (!_context.PhysicalMemoryRegistry.TryGetValue(pid, out var physicalMemory))
{
return false;
}
FormatInfo formatInfo = new(format, 1, 1, bytesPerPixel, 4);
TextureInfo info = new(
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(address, (ulong)size);
_frameQueue.Enqueue(new PresentationTexture(
physicalMemory.TextureCache,
info,
range,
crop,
acquireCallback,
releaseCallback,
userObj));
return true;
}
///
/// Presents a texture on the queue.
/// If the queue is empty, then no texture is presented.
///
/// Callback method to call when a new texture should be presented on the screen
public void Present(Action swapBuffersCallback)
{
_context.AdvanceSequence();
if (_frameQueue.TryDequeue(out PresentationTexture pt))
{
pt.AcquireCallback(_context, pt.UserObj);
Image.Texture texture = pt.Cache.FindOrCreateTexture(null, TextureSearchFlags.WithUpscale, pt.Info, 0, range: pt.Range);
pt.Cache.Tick();
texture.SynchronizeMemory();
ImageCrop crop = new(
(int)(pt.Crop.Left * texture.ScaleFactor),
(int)MathF.Ceiling(pt.Crop.Right * texture.ScaleFactor),
(int)(pt.Crop.Top * texture.ScaleFactor),
(int)MathF.Ceiling(pt.Crop.Bottom * texture.ScaleFactor),
pt.Crop.FlipX,
pt.Crop.FlipY,
pt.Crop.IsStretched,
pt.Crop.AspectRatioX,
pt.Crop.AspectRatioY);
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);
}
}
///
/// Indicate that a frame on the queue is ready to be acquired.
///
public void SignalFrameReady()
{
Interlocked.Increment(ref _framesAvailable);
}
///
/// Determine if any frames are available, and decrement the available count if there are.
///
/// True if a frame is available, false otherwise
public bool ConsumeFrameAvailable()
{
if (Interlocked.CompareExchange(ref _framesAvailable, 0, 0) != 0)
{
Interlocked.Decrement(ref _framesAvailable);
return true;
}
return false;
}
}
}