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;
///
/// GPU image presentation window.
///
public class Window
{
private readonly GpuContext _context;
///
/// Texture presented on the window.
///
private struct PresentationTexture
{
///
/// 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.
///
/// 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(
TextureInfo info,
MultiRange range,
ImageCrop crop,
Action acquireCallback,
Action releaseCallback,
object userObj)
{
Info = info;
Range = range;
Crop = crop;
AcquireCallback = acquireCallback;
ReleaseCallback = releaseCallback;
UserObj = userObj;
}
}
private readonly ConcurrentQueue _frameQueue;
private int _framesAvailable;
///
/// 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.
///
/// 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
public void EnqueueFrameThreadSafe(
ulong address,
int width,
int height,
int stride,
bool isLinear,
int gobBlocksInY,
Format format,
int bytesPerPixel,
ImageCrop crop,
Action acquireCallback,
Action releaseCallback,
object userObj)
{
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(info, range, crop, acquireCallback, releaseCallback, userObj));
}
///
/// 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);
Texture texture = _context.Methods.TextureCache.FindOrCreateTexture(TextureSearchFlags.WithUpscale, pt.Info, 0, null, pt.Range);
texture.SynchronizeMemory();
_context.Renderer.Window.Present(texture.HostTexture, pt.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;
}
}
}