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(); } } }