using Ryujinx.Cpu.Tracking;
using Ryujinx.Graphics.GAL;
using Ryujinx.Memory.Range;
using System;
namespace Ryujinx.Graphics.Gpu.Memory
{
///
/// Buffer, used to store vertex and index data, uniform and storage buffers, and others.
///
class Buffer : IRange, IDisposable
{
private static ulong GranularBufferThreshold = 4096;
private readonly GpuContext _context;
///
/// Host buffer handle.
///
public BufferHandle Handle { get; }
///
/// Start address of the buffer in guest memory.
///
public ulong Address { get; }
///
/// Size of the buffer in bytes.
///
public ulong Size { get; }
///
/// End address of the buffer in guest memory.
///
public ulong EndAddress => Address + Size;
private CpuMultiRegionHandle _memoryTrackingGranular;
private CpuRegionHandle _memoryTracking;
private readonly Action _modifiedDelegate;
private int _sequenceNumber;
private bool _useGranular;
///
/// Creates a new instance of the buffer.
///
/// GPU context that the buffer belongs to
/// Start address of the buffer
/// Size of the buffer in bytes
public Buffer(GpuContext context, ulong address, ulong size)
{
_context = context;
Address = address;
Size = size;
Handle = context.Renderer.CreateBuffer((int)size);
_useGranular = size > GranularBufferThreshold;
if (_useGranular)
{
_memoryTrackingGranular = context.PhysicalMemory.BeginGranularTracking(address, size);
}
else
{
_memoryTracking = context.PhysicalMemory.BeginTracking(address, size);
}
_modifiedDelegate = new Action(RegionModified);
}
///
/// Gets a sub-range from the buffer.
///
///
/// This can be used to bind and use sub-ranges of the buffer on the host API.
///
/// Start address of the sub-range, must be greater than or equal to the buffer address
/// Size in bytes of the sub-range, must be less than or equal to the buffer size
/// The buffer sub-range
public BufferRange GetRange(ulong address, ulong size)
{
int offset = (int)(address - Address);
return new BufferRange(Handle, offset, (int)size);
}
///
/// Checks if a given range overlaps with the buffer.
///
/// Start address of the range
/// Size in bytes of the range
/// True if the range overlaps, false otherwise
public bool OverlapsWith(ulong address, ulong size)
{
return Address < address + size && address < EndAddress;
}
///
/// Performs guest to host memory synchronization of the buffer data.
///
///
/// This causes the buffer data to be overwritten if a write was detected from the CPU,
/// since the last call to this method.
///
/// Start address of the range to synchronize
/// Size in bytes of the range to synchronize
public void SynchronizeMemory(ulong address, ulong size)
{
if (_useGranular)
{
_memoryTrackingGranular.QueryModified(address, size, _modifiedDelegate, _context.SequenceNumber);
}
else
{
if (_memoryTracking.Dirty && _context.SequenceNumber != _sequenceNumber)
{
_memoryTracking.Reprotect();
_context.Renderer.SetBufferData(Handle, 0, _context.PhysicalMemory.GetSpan(Address, (int)Size));
_sequenceNumber = _context.SequenceNumber;
}
}
}
///
/// Indicate that a region of the buffer was modified, and must be loaded from memory.
///
/// Start address of the modified region
/// Size of the modified region
private void RegionModified(ulong mAddress, ulong mSize)
{
if (mAddress < Address)
{
mAddress = Address;
}
ulong maxSize = Address + Size - mAddress;
if (mSize > maxSize)
{
mSize = maxSize;
}
int offset = (int)(mAddress - Address);
_context.Renderer.SetBufferData(Handle, offset, _context.PhysicalMemory.GetSpan(mAddress, (int)mSize));
}
///
/// Performs copy of all the buffer data from one buffer to another.
///
/// The destination buffer to copy the data into
/// The offset of the destination buffer to copy into
public void CopyTo(Buffer destination, int dstOffset)
{
_context.Renderer.Pipeline.CopyBuffer(Handle, destination.Handle, 0, dstOffset, (int)Size);
}
///
/// Flushes a range of the buffer.
/// This writes the range data back into guest memory.
///
/// Start address of the range
/// Size in bytes of the range
public void Flush(ulong address, ulong size)
{
int offset = (int)(address - Address);
byte[] data = _context.Renderer.GetBufferData(Handle, offset, (int)size);
// TODO: When write tracking shaders, they will need to be aware of changes in overlapping buffers.
_context.PhysicalMemory.WriteUntracked(address, data);
}
///
/// Disposes the host buffer.
///
public void Dispose()
{
_context.Renderer.DeleteBuffer(Handle);
_memoryTrackingGranular?.Dispose();
_memoryTracking?.Dispose();
}
}
}