aboutsummaryrefslogtreecommitdiff
path: root/Ryujinx.Graphics.Gpu/Memory
diff options
context:
space:
mode:
authorgdk <gab.dark.100@gmail.com>2019-10-13 03:02:07 -0300
committerThog <thog@protonmail.com>2020-01-09 02:13:00 +0100
commit1876b346fea647e8284a66bb6d62c38801035cff (patch)
tree6eeff094298cda84d1613dc5ec0691e51d7b35f1 /Ryujinx.Graphics.Gpu/Memory
parentf617fb542a0e3d36012d77a4b5acbde7b08902f2 (diff)
Initial work
Diffstat (limited to 'Ryujinx.Graphics.Gpu/Memory')
-rw-r--r--Ryujinx.Graphics.Gpu/Memory/Buffer.cs99
-rw-r--r--Ryujinx.Graphics.Gpu/Memory/BufferBounds.cs8
-rw-r--r--Ryujinx.Graphics.Gpu/Memory/BufferManager.cs530
-rw-r--r--Ryujinx.Graphics.Gpu/Memory/IPhysicalMemory.cs15
-rw-r--r--Ryujinx.Graphics.Gpu/Memory/IRange.cs10
-rw-r--r--Ryujinx.Graphics.Gpu/Memory/IndexBuffer.cs12
-rw-r--r--Ryujinx.Graphics.Gpu/Memory/MemoryAccessor.cs54
-rw-r--r--Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs265
-rw-r--r--Ryujinx.Graphics.Gpu/Memory/RangeList.cs208
-rw-r--r--Ryujinx.Graphics.Gpu/Memory/VertexBuffer.cs10
10 files changed, 1211 insertions, 0 deletions
diff --git a/Ryujinx.Graphics.Gpu/Memory/Buffer.cs b/Ryujinx.Graphics.Gpu/Memory/Buffer.cs
new file mode 100644
index 00000000..30bd1ac0
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Memory/Buffer.cs
@@ -0,0 +1,99 @@
+using Ryujinx.Graphics.GAL;
+using System;
+
+namespace Ryujinx.Graphics.Gpu.Memory
+{
+ class Buffer : IRange<Buffer>, IDisposable
+ {
+ private GpuContext _context;
+
+ private IBuffer _buffer;
+
+ public ulong Address { get; }
+ public ulong Size { get; }
+
+ public ulong EndAddress => Address + Size;
+
+ private int[] _sequenceNumbers;
+
+ public Buffer(GpuContext context, ulong address, ulong size)
+ {
+ _context = context;
+ Address = address;
+ Size = size;
+
+ _buffer = context.Renderer.CreateBuffer((int)size);
+
+ _sequenceNumbers = new int[size / MemoryManager.PageSize];
+
+ Invalidate();
+ }
+
+ public BufferRange GetRange(ulong address, ulong size)
+ {
+ int offset = (int)(address - Address);
+
+ return new BufferRange(_buffer, offset, (int)size);
+ }
+
+ public bool OverlapsWith(ulong address, ulong size)
+ {
+ return Address < address + size && address < EndAddress;
+ }
+
+ public void SynchronizeMemory(ulong address, ulong size)
+ {
+ int currentSequenceNumber = _context.SequenceNumber;
+
+ bool needsSync = false;
+
+ ulong buffOffset = address - Address;
+
+ ulong buffEndOffset = (buffOffset + size + MemoryManager.PageMask) & ~MemoryManager.PageMask;
+
+ int startIndex = (int)(buffOffset / MemoryManager.PageSize);
+ int endIndex = (int)(buffEndOffset / MemoryManager.PageSize);
+
+ for (int index = startIndex; index < endIndex; index++)
+ {
+ if (_sequenceNumbers[index] != currentSequenceNumber)
+ {
+ _sequenceNumbers[index] = currentSequenceNumber;
+
+ needsSync = true;
+ }
+ }
+
+ if (!needsSync)
+ {
+ return;
+ }
+
+ (ulong, ulong)[] modifiedRanges = _context.PhysicalMemory.GetModifiedRanges(address, size);
+
+ for (int index = 0; index < modifiedRanges.Length; index++)
+ {
+ (ulong mAddress, ulong mSize) = modifiedRanges[index];
+
+ int offset = (int)(mAddress - Address);
+
+ _buffer.SetData(offset, _context.PhysicalMemory.Read(mAddress, mSize));
+ }
+ }
+
+ public void CopyTo(Buffer destination, int dstOffset)
+ {
+ _buffer.CopyTo(destination._buffer, 0, dstOffset, (int)Size);
+ }
+
+ public void Invalidate()
+ {
+ _buffer.SetData(0, _context.PhysicalMemory.Read(Address, Size));
+ }
+
+ public void Dispose()
+ {
+ _buffer.Dispose();
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Memory/BufferBounds.cs b/Ryujinx.Graphics.Gpu/Memory/BufferBounds.cs
new file mode 100644
index 00000000..2a074bd3
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Memory/BufferBounds.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.Graphics.Gpu.Memory
+{
+ struct BufferBounds
+ {
+ public ulong Address;
+ public ulong Size;
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs b/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs
new file mode 100644
index 00000000..eb2e0ca9
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs
@@ -0,0 +1,530 @@
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.GAL.InputAssembler;
+using Ryujinx.Graphics.Gpu.State;
+using Ryujinx.Graphics.Shader;
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Graphics.Gpu.Memory
+{
+ class BufferManager
+ {
+ private const ulong BufferAlignmentSize = 0x1000;
+ private const ulong BufferAlignmentMask = BufferAlignmentSize - 1;
+
+ private GpuContext _context;
+
+ private RangeList<Buffer> _buffers;
+
+ private IndexBuffer _indexBuffer;
+
+ private VertexBuffer[] _vertexBuffers;
+
+ private class BuffersPerStage
+ {
+ public uint EnableMask { get; set; }
+
+ public BufferBounds[] Buffers { get; }
+
+ public BuffersPerStage(int count)
+ {
+ Buffers = new BufferBounds[count];
+ }
+
+ public void Bind(int index, ulong address, ulong size)
+ {
+ Buffers[index].Address = address;
+ Buffers[index].Size = size;
+ }
+ }
+
+ private BuffersPerStage _cpStorageBuffers;
+ private BuffersPerStage _cpUniformBuffers;
+ private BuffersPerStage[] _gpStorageBuffers;
+ private BuffersPerStage[] _gpUniformBuffers;
+
+ private bool _gpStorageBuffersDirty;
+ private bool _gpUniformBuffersDirty;
+
+ private bool _indexBufferDirty;
+ private bool _vertexBuffersDirty;
+
+ private bool _rebind;
+
+ public BufferManager(GpuContext context)
+ {
+ _context = context;
+
+ _buffers = new RangeList<Buffer>();
+
+ _vertexBuffers = new VertexBuffer[Constants.TotalVertexBuffers];
+
+ _cpStorageBuffers = new BuffersPerStage(Constants.TotalCpStorageBuffers);
+ _cpUniformBuffers = new BuffersPerStage(Constants.TotalCpUniformBuffers);
+
+ _gpStorageBuffers = new BuffersPerStage[Constants.TotalShaderStages];
+ _gpUniformBuffers = new BuffersPerStage[Constants.TotalShaderStages];
+
+ for (int index = 0; index < Constants.TotalShaderStages; index++)
+ {
+ _gpStorageBuffers[index] = new BuffersPerStage(Constants.TotalGpStorageBuffers);
+ _gpUniformBuffers[index] = new BuffersPerStage(Constants.TotalGpUniformBuffers);
+ }
+ }
+
+ public void SetIndexBuffer(ulong gpuVa, ulong size, IndexType type)
+ {
+ ulong address = TranslateAndCreateBuffer(gpuVa, size);
+
+ _indexBuffer.Address = address;
+ _indexBuffer.Size = size;
+ _indexBuffer.Type = type;
+
+ _indexBufferDirty = true;
+ }
+
+ public void SetVertexBuffer(int index, ulong gpuVa, ulong size, int stride, int divisor)
+ {
+ ulong address = TranslateAndCreateBuffer(gpuVa, size);
+
+ _vertexBuffers[index].Address = address;
+ _vertexBuffers[index].Size = size;
+ _vertexBuffers[index].Stride = stride;
+ _vertexBuffers[index].Divisor = divisor;
+
+ _vertexBuffersDirty = true;
+ }
+
+ public void SetComputeStorageBuffer(int index, ulong gpuVa, ulong size)
+ {
+ // TODO: Improve
+ size += gpuVa & 0x3fUL;
+
+ gpuVa &= ~0x3fUL;
+
+ ulong address = TranslateAndCreateBuffer(gpuVa, size);
+
+ _cpStorageBuffers.Bind(index, address, size);
+ }
+
+ public void SetGraphicsStorageBuffer(int stage, int index, ulong gpuVa, ulong size)
+ {
+ // TODO: Improve
+ size += gpuVa & 0x3fUL;
+
+ gpuVa &= ~0x3fUL;
+
+ ulong address = TranslateAndCreateBuffer(gpuVa, size);
+
+ _gpStorageBuffers[stage].Bind(index, address, size);
+
+ _gpStorageBuffersDirty = true;
+ }
+
+ public void SetComputeUniformBuffer(int index, ulong gpuVa, ulong size)
+ {
+ ulong address = TranslateAndCreateBuffer(gpuVa, size);
+
+ _cpUniformBuffers.Bind(index, address, size);
+ }
+
+ public void SetGraphicsUniformBuffer(int stage, int index, ulong gpuVa, ulong size)
+ {
+ ulong address = TranslateAndCreateBuffer(gpuVa, size);
+
+ _gpUniformBuffers[stage].Bind(index, address, size);
+
+ _gpUniformBuffersDirty = true;
+ }
+
+ public void SetComputeStorageBufferEnableMask(uint mask)
+ {
+ _cpStorageBuffers.EnableMask = mask;
+ }
+
+ public void SetGraphicsStorageBufferEnableMask(int stage, uint mask)
+ {
+ _gpStorageBuffers[stage].EnableMask = mask;
+
+ _gpStorageBuffersDirty = true;
+ }
+
+ public void SetComputeUniformBufferEnableMask(uint mask)
+ {
+ _cpUniformBuffers.EnableMask = mask;
+ }
+
+ public void SetGraphicsUniformBufferEnableMask(int stage, uint mask)
+ {
+ _gpUniformBuffers[stage].EnableMask = mask;
+
+ _gpUniformBuffersDirty = true;
+ }
+
+ private ulong TranslateAndCreateBuffer(ulong gpuVa, ulong size)
+ {
+ if (gpuVa == 0)
+ {
+ return 0;
+ }
+
+ ulong address = _context.MemoryManager.Translate(gpuVa);
+
+ if (address == MemoryManager.BadAddress)
+ {
+ return 0;
+ }
+
+ ulong endAddress = address + size;
+
+ ulong alignedAddress = address & ~BufferAlignmentMask;
+
+ ulong alignedEndAddress = (endAddress + BufferAlignmentMask) & ~BufferAlignmentMask;
+
+ // The buffer must have the size of at least one page.
+ if (alignedEndAddress == alignedAddress)
+ {
+ alignedEndAddress += BufferAlignmentSize;
+ }
+
+ CreateBuffer(alignedAddress, alignedEndAddress - alignedAddress);
+
+ return address;
+ }
+
+ private void CreateBuffer(ulong address, ulong size)
+ {
+ Buffer[] overlaps = _buffers.FindOverlaps(address, size);
+
+ if (overlaps.Length != 0)
+ {
+ // The buffer already exists. We can just return the existing buffer
+ // if the buffer we need is fully contained inside the overlapping buffer.
+ // Otherwise, we must delete the overlapping buffers and create a bigger buffer
+ // that fits all the data we need. We also need to copy the contents from the
+ // old buffer(s) to the new buffer.
+ ulong endAddress = address + size;
+
+ if (overlaps[0].Address > address || overlaps[0].EndAddress < endAddress)
+ {
+ foreach (Buffer buffer in overlaps)
+ {
+ address = Math.Min(address, buffer.Address);
+ endAddress = Math.Max(endAddress, buffer.EndAddress);
+
+ buffer.SynchronizeMemory(buffer.Address, buffer.Size);
+
+ _buffers.Remove(buffer);
+ }
+
+ Buffer newBuffer = new Buffer(_context, address, endAddress - address);
+
+ _buffers.Add(newBuffer);
+
+ foreach (Buffer buffer in overlaps)
+ {
+ int dstOffset = (int)(buffer.Address - newBuffer.Address);
+
+ buffer.CopyTo(newBuffer, dstOffset);
+
+ buffer.Dispose();
+ }
+
+ _rebind = true;
+ }
+ }
+ else
+ {
+ // No overlap, just create a new buffer.
+ Buffer buffer = new Buffer(_context, address, size);
+
+ _buffers.Add(buffer);
+ }
+ }
+
+ public ulong GetComputeUniformBufferAddress(int index)
+ {
+ return _cpUniformBuffers.Buffers[index].Address;
+ }
+
+ public ulong GetGraphicsUniformBufferAddress(int stage, int index)
+ {
+ return _gpUniformBuffers[stage].Buffers[index].Address;
+ }
+
+ public void CommitComputeBindings()
+ {
+ uint enableMask = _cpStorageBuffers.EnableMask;
+
+ for (int index = 0; (enableMask >> index) != 0; index++)
+ {
+ if ((enableMask & (1u << index)) == 0)
+ {
+ continue;
+ }
+
+ BufferBounds bounds = _cpStorageBuffers.Buffers[index];
+
+ if (bounds.Address == 0)
+ {
+ continue;
+ }
+
+ BufferRange buffer = GetBufferRange(bounds.Address, bounds.Size);
+
+ _context.Renderer.ComputePipeline.SetStorageBuffer(index, buffer);
+ }
+
+ enableMask = _cpUniformBuffers.EnableMask;
+
+ for (int index = 0; (enableMask >> index) != 0; index++)
+ {
+ if ((enableMask & (1u << index)) == 0)
+ {
+ continue;
+ }
+
+ BufferBounds bounds = _cpUniformBuffers.Buffers[index];
+
+ if (bounds.Address == 0)
+ {
+ continue;
+ }
+
+ BufferRange buffer = GetBufferRange(bounds.Address, bounds.Size);
+
+ _context.Renderer.ComputePipeline.SetUniformBuffer(index, buffer);
+
+ if (index == 0)
+ {
+ // TODO: Improve
+ Span<byte> data = _context.PhysicalMemory.Read(bounds.Address + 0x310, 0x100);
+
+ Span<int> words = MemoryMarshal.Cast<byte, int>(data);
+
+ for (int offset = 0; offset < 0x40; offset += 4)
+ {
+ words[offset] &= 0x3f;
+ }
+
+ buffer = GetBufferRange(bounds.Address + 0x310, 0x100);
+
+ buffer.Buffer.SetData(buffer.Offset, data);
+ }
+ }
+ }
+
+ public void CommitBindings()
+ {
+ if (_indexBufferDirty || _rebind)
+ {
+ _indexBufferDirty = false;
+
+ if (_indexBuffer.Address != 0)
+ {
+ BufferRange buffer = GetBufferRange(_indexBuffer.Address, _indexBuffer.Size);
+
+ _context.Renderer.GraphicsPipeline.BindIndexBuffer(buffer, _indexBuffer.Type);
+ }
+ }
+ else if (_indexBuffer.Address != 0)
+ {
+ SynchronizeBufferRange(_indexBuffer.Address, _indexBuffer.Size);
+ }
+
+ if (_vertexBuffersDirty || _rebind)
+ {
+ _vertexBuffersDirty = false;
+
+ VertexBufferDescriptor[] vertexBuffers = new VertexBufferDescriptor[Constants.TotalVertexBuffers];
+
+ for (int index = 0; index < Constants.TotalVertexBuffers; index++)
+ {
+ VertexBuffer vb = _vertexBuffers[index];
+
+ if (vb.Address == 0)
+ {
+ continue;
+ }
+
+ BufferRange buffer = GetBufferRange(vb.Address, vb.Size);
+
+ vertexBuffers[index] = new VertexBufferDescriptor(buffer, vb.Stride, vb.Divisor);
+ }
+
+ _context.Renderer.GraphicsPipeline.BindVertexBuffers(vertexBuffers);
+ }
+ else
+ {
+ for (int index = 0; index < Constants.TotalVertexBuffers; index++)
+ {
+ VertexBuffer vb = _vertexBuffers[index];
+
+ if (vb.Address == 0)
+ {
+ continue;
+ }
+
+ SynchronizeBufferRange(vb.Address, vb.Size);
+ }
+ }
+
+ if (_gpStorageBuffersDirty || _rebind)
+ {
+ _gpStorageBuffersDirty = false;
+
+ BindBuffers(_gpStorageBuffers, isStorage: true);
+ }
+ else
+ {
+ UpdateBuffers(_gpStorageBuffers);
+ }
+
+ if (_gpUniformBuffersDirty || _rebind)
+ {
+ _gpUniformBuffersDirty = false;
+
+ BindBuffers(_gpUniformBuffers, isStorage: false);
+ }
+ else
+ {
+ UpdateBuffers(_gpUniformBuffers);
+ }
+
+ _rebind = false;
+ }
+
+ private void BindBuffers(BuffersPerStage[] bindings, bool isStorage)
+ {
+ BindOrUpdateBuffers(bindings, bind: true, isStorage);
+ }
+
+ private void UpdateBuffers(BuffersPerStage[] bindings)
+ {
+ BindOrUpdateBuffers(bindings, bind: false);
+ }
+
+ private void BindOrUpdateBuffers(BuffersPerStage[] bindings, bool bind, bool isStorage = false)
+ {
+ for (ShaderStage stage = ShaderStage.Vertex; stage <= ShaderStage.Fragment; stage++)
+ {
+ uint enableMask = bindings[(int)stage - 1].EnableMask;
+
+ if (enableMask == 0)
+ {
+ continue;
+ }
+
+ for (int index = 0; (enableMask >> index) != 0; index++)
+ {
+ if ((enableMask & (1u << index)) == 0)
+ {
+ continue;
+ }
+
+ BufferBounds bounds = bindings[(int)stage - 1].Buffers[index];
+
+ if (bounds.Address == 0)
+ {
+ continue;
+ }
+
+ if (bind)
+ {
+ BindBuffer(index, stage, bounds, isStorage);
+ }
+ else
+ {
+ SynchronizeBufferRange(bounds.Address, bounds.Size);
+ }
+ }
+ }
+ }
+
+ private void BindBuffer(int index, ShaderStage stage, BufferBounds bounds, bool isStorage)
+ {
+ BufferRange buffer = GetBufferRange(bounds.Address, bounds.Size);
+
+ BufferRange[] buffers = new BufferRange[] { buffer };
+
+ if (isStorage)
+ {
+ _context.Renderer.GraphicsPipeline.BindStorageBuffers(index, stage, buffers);
+ }
+ else
+ {
+ _context.Renderer.GraphicsPipeline.BindUniformBuffers(index, stage, buffers);
+ }
+
+ if (!isStorage && index == 0)
+ {
+ // TODO: Improve
+ Span<byte> data = _context.PhysicalMemory.Read(bounds.Address + 0x110, 0x100);
+
+ Span<int> words = MemoryMarshal.Cast<byte, int>(data);
+
+ for (int offset = 0; offset < 0x40; offset += 4)
+ {
+ words[offset] &= 0x3f;
+ }
+
+ buffer = GetBufferRange(bounds.Address + 0x110, 0x100);
+
+ buffer.Buffer.SetData(buffer.Offset, data);
+ }
+ }
+
+ public void CopyBuffer(GpuVa srcVa, GpuVa dstVa, ulong size)
+ {
+ ulong srcAddress = TranslateAndCreateBuffer(srcVa.Pack(), size);
+ ulong dstAddress = TranslateAndCreateBuffer(dstVa.Pack(), size);
+
+ BufferRange srcBuffer = GetBufferRange(srcAddress, size);
+ BufferRange dstBuffer = GetBufferRange(dstAddress, size);
+
+ srcBuffer.Buffer.CopyTo(
+ dstBuffer.Buffer,
+ srcBuffer.Offset,
+ dstBuffer.Offset,
+ (int)size);
+ }
+
+ private BufferRange GetBufferRange(ulong address, ulong size)
+ {
+ Buffer buffer;
+
+ if (size != 0)
+ {
+ buffer = _buffers.FindFirstOverlap(address, size);
+
+ buffer.SynchronizeMemory(address, size);
+ }
+ else
+ {
+ buffer = _buffers.FindFirstOverlap(address, 1);
+ }
+
+ return buffer.GetRange(address, size);
+ }
+
+ private void SynchronizeBufferRange(ulong address, ulong size)
+ {
+ if (size != 0)
+ {
+ Buffer buffer = _buffers.FindFirstOverlap(address, size);
+
+ buffer.SynchronizeMemory(address, size);
+ }
+ }
+
+ public void InvalidateRange(ulong address, ulong size)
+ {
+ Buffer[] overlappingBuffers = _buffers.FindOverlaps(address, size);
+
+ foreach (Buffer buffer in overlappingBuffers)
+ {
+ buffer.Invalidate();
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Memory/IPhysicalMemory.cs b/Ryujinx.Graphics.Gpu/Memory/IPhysicalMemory.cs
new file mode 100644
index 00000000..5f21704d
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Memory/IPhysicalMemory.cs
@@ -0,0 +1,15 @@
+using System;
+
+namespace Ryujinx.Graphics.Gpu.Memory
+{
+ public interface IPhysicalMemory
+ {
+ int GetPageSize();
+
+ Span<byte> Read(ulong address, ulong size);
+
+ void Write(ulong address, Span<byte> data);
+
+ (ulong, ulong)[] GetModifiedRanges(ulong address, ulong size);
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Memory/IRange.cs b/Ryujinx.Graphics.Gpu/Memory/IRange.cs
new file mode 100644
index 00000000..ee3d5c0b
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Memory/IRange.cs
@@ -0,0 +1,10 @@
+namespace Ryujinx.Graphics.Gpu.Memory
+{
+ interface IRange<T>
+ {
+ ulong Address { get; }
+ ulong Size { get; }
+
+ bool OverlapsWith(ulong address, ulong size);
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Memory/IndexBuffer.cs b/Ryujinx.Graphics.Gpu/Memory/IndexBuffer.cs
new file mode 100644
index 00000000..ce2a2c74
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Memory/IndexBuffer.cs
@@ -0,0 +1,12 @@
+using Ryujinx.Graphics.GAL;
+
+namespace Ryujinx.Graphics.Gpu.Memory
+{
+ struct IndexBuffer
+ {
+ public ulong Address;
+ public ulong Size;
+
+ public IndexType Type;
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Memory/MemoryAccessor.cs b/Ryujinx.Graphics.Gpu/Memory/MemoryAccessor.cs
new file mode 100644
index 00000000..500c36e5
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Memory/MemoryAccessor.cs
@@ -0,0 +1,54 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Graphics.Gpu.Memory
+{
+ class MemoryAccessor
+ {
+ private GpuContext _context;
+
+ public MemoryAccessor(GpuContext context)
+ {
+ _context = context;
+ }
+
+ public Span<byte> Read(ulong gpuVa, ulong maxSize)
+ {
+ ulong processVa = _context.MemoryManager.Translate(gpuVa);
+
+ ulong size = Math.Min(_context.MemoryManager.GetSubSize(gpuVa), maxSize);
+
+ return _context.PhysicalMemory.Read(processVa, size);
+ }
+
+ public T Read<T>(ulong gpuVa) where T : struct
+ {
+ ulong processVa = _context.MemoryManager.Translate(gpuVa);
+
+ ulong size = (uint)Marshal.SizeOf<T>();
+
+ return MemoryMarshal.Cast<byte, T>(_context.PhysicalMemory.Read(processVa, size))[0];
+ }
+
+ public int ReadInt32(ulong gpuVa)
+ {
+ ulong processVa = _context.MemoryManager.Translate(gpuVa);
+
+ return BitConverter.ToInt32(_context.PhysicalMemory.Read(processVa, 4));
+ }
+
+ public void Write(ulong gpuVa, int value)
+ {
+ ulong processVa = _context.MemoryManager.Translate(gpuVa);
+
+ _context.PhysicalMemory.Write(processVa, BitConverter.GetBytes(value));
+ }
+
+ public void Write(ulong gpuVa, Span<byte> data)
+ {
+ ulong processVa = _context.MemoryManager.Translate(gpuVa);
+
+ _context.PhysicalMemory.Write(processVa, data);
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs b/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs
new file mode 100644
index 00000000..d1a3e69c
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs
@@ -0,0 +1,265 @@
+namespace Ryujinx.Graphics.Gpu.Memory
+{
+ public class MemoryManager
+ {
+ private const ulong AddressSpaceSize = 1UL << 40;
+
+ public const ulong BadAddress = ulong.MaxValue;
+
+ private const int PtLvl0Bits = 14;
+ private const int PtLvl1Bits = 14;
+ private const int PtPageBits = 12;
+
+ private const ulong PtLvl0Size = 1UL << PtLvl0Bits;
+ private const ulong PtLvl1Size = 1UL << PtLvl1Bits;
+ public const ulong PageSize = 1UL << PtPageBits;
+
+ private const ulong PtLvl0Mask = PtLvl0Size - 1;
+ private const ulong PtLvl1Mask = PtLvl1Size - 1;
+ public const ulong PageMask = PageSize - 1;
+
+ private const int PtLvl0Bit = PtPageBits + PtLvl1Bits;
+ private const int PtLvl1Bit = PtPageBits;
+
+ private const ulong PteUnmapped = 0xffffffff_ffffffff;
+ private const ulong PteReserved = 0xffffffff_fffffffe;
+
+ private ulong[][] _pageTable;
+
+ public MemoryManager()
+ {
+ _pageTable = new ulong[PtLvl0Size][];
+ }
+
+ public ulong Map(ulong pa, ulong va, ulong size)
+ {
+ lock (_pageTable)
+ {
+ for (ulong offset = 0; offset < size; offset += PageSize)
+ {
+ SetPte(va + offset, pa + offset);
+ }
+ }
+
+ return va;
+ }
+
+ public ulong Map(ulong pa, ulong size)
+ {
+ lock (_pageTable)
+ {
+ ulong va = GetFreePosition(size);
+
+ if (va != PteUnmapped)
+ {
+ for (ulong offset = 0; offset < size; offset += PageSize)
+ {
+ SetPte(va + offset, pa + offset);
+ }
+ }
+
+ return va;
+ }
+ }
+
+ public ulong MapLow(ulong pa, ulong size)
+ {
+ lock (_pageTable)
+ {
+ ulong va = GetFreePosition(size, 1, PageSize);
+
+ if (va != PteUnmapped && va <= uint.MaxValue && (va + size) <= uint.MaxValue)
+ {
+ for (ulong offset = 0; offset < size; offset += PageSize)
+ {
+ SetPte(va + offset, pa + offset);
+ }
+ }
+ else
+ {
+ va = PteUnmapped;
+ }
+
+ return va;
+ }
+ }
+
+ public ulong ReserveFixed(ulong va, ulong size)
+ {
+ lock (_pageTable)
+ {
+ for (ulong offset = 0; offset < size; offset += PageSize)
+ {
+ if (IsPageInUse(va + offset))
+ {
+ return PteUnmapped;
+ }
+ }
+
+ for (ulong offset = 0; offset < size; offset += PageSize)
+ {
+ SetPte(va + offset, PteReserved);
+ }
+ }
+
+ return va;
+ }
+
+ public ulong Reserve(ulong size, ulong alignment)
+ {
+ lock (_pageTable)
+ {
+ ulong address = GetFreePosition(size, alignment);
+
+ if (address != PteUnmapped)
+ {
+ for (ulong offset = 0; offset < size; offset += PageSize)
+ {
+ SetPte(address + offset, PteReserved);
+ }
+ }
+
+ return address;
+ }
+ }
+
+ public void Free(ulong va, ulong size)
+ {
+ lock (_pageTable)
+ {
+ for (ulong offset = 0; offset < size; offset += PageSize)
+ {
+ SetPte(va + offset, PteUnmapped);
+ }
+ }
+ }
+
+ private ulong GetFreePosition(ulong size, ulong alignment = 1, ulong start = 1UL << 32)
+ {
+ // Note: Address 0 is not considered valid by the driver,
+ // when 0 is returned it's considered a mapping error.
+ ulong address = start;
+ ulong freeSize = 0;
+
+ if (alignment == 0)
+ {
+ alignment = 1;
+ }
+
+ alignment = (alignment + PageMask) & ~PageMask;
+
+ while (address + freeSize < AddressSpaceSize)
+ {
+ if (!IsPageInUse(address + freeSize))
+ {
+ freeSize += PageSize;
+
+ if (freeSize >= size)
+ {
+ return address;
+ }
+ }
+ else
+ {
+ address += freeSize + PageSize;
+ freeSize = 0;
+
+ ulong remainder = address % alignment;
+
+ if (remainder != 0)
+ {
+ address = (address - remainder) + alignment;
+ }
+ }
+ }
+
+ return PteUnmapped;
+ }
+
+ internal ulong GetSubSize(ulong gpuVa)
+ {
+ ulong size = 0;
+
+ while (GetPte(gpuVa + size) != PteUnmapped)
+ {
+ size += PageSize;
+ }
+
+ return size;
+ }
+
+ internal ulong Translate(ulong gpuVa)
+ {
+ ulong baseAddress = GetPte(gpuVa);
+
+ if (baseAddress == PteUnmapped || baseAddress == PteReserved)
+ {
+ return PteUnmapped;
+ }
+
+ return baseAddress + (gpuVa & PageMask);
+ }
+
+ public bool IsRegionFree(ulong va, ulong size)
+ {
+ for (ulong offset = 0; offset < size; offset += PageSize)
+ {
+ if (IsPageInUse(va + offset))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private bool IsPageInUse(ulong va)
+ {
+ if (va >> PtLvl0Bits + PtLvl1Bits + PtPageBits != 0)
+ {
+ return false;
+ }
+
+ ulong l0 = (va >> PtLvl0Bit) & PtLvl0Mask;
+ ulong l1 = (va >> PtLvl1Bit) & PtLvl1Mask;
+
+ if (_pageTable[l0] == null)
+ {
+ return false;
+ }
+
+ return _pageTable[l0][l1] != PteUnmapped;
+ }
+
+ private ulong GetPte(ulong address)
+ {
+ ulong l0 = (address >> PtLvl0Bit) & PtLvl0Mask;
+ ulong l1 = (address >> PtLvl1Bit) & PtLvl1Mask;
+
+ if (_pageTable[l0] == null)
+ {
+ return PteUnmapped;
+ }
+
+ return _pageTable[l0][l1];
+ }
+
+ private void SetPte(ulong address, ulong tgtAddr)
+ {
+ ulong l0 = (address >> PtLvl0Bit) & PtLvl0Mask;
+ ulong l1 = (address >> PtLvl1Bit) & PtLvl1Mask;
+
+ if (_pageTable[l0] == null)
+ {
+ _pageTable[l0] = new ulong[PtLvl1Size];
+
+ for (ulong index = 0; index < PtLvl1Size; index++)
+ {
+ _pageTable[l0][index] = PteUnmapped;
+ }
+ }
+
+ _pageTable[l0][l1] = tgtAddr;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Memory/RangeList.cs b/Ryujinx.Graphics.Gpu/Memory/RangeList.cs
new file mode 100644
index 00000000..6114f15d
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Memory/RangeList.cs
@@ -0,0 +1,208 @@
+using System.Collections.Generic;
+
+namespace Ryujinx.Graphics.Gpu.Memory
+{
+ class RangeList<T> where T : IRange<T>
+ {
+ private List<T> _items;
+
+ public RangeList()
+ {
+ _items = new List<T>();
+ }
+
+ public void Add(T item)
+ {
+ lock (_items)
+ {
+ int index = BinarySearch(item.Address);
+
+ if (index < 0)
+ {
+ index = ~index;
+ }
+
+ _items.Insert(index, item);
+ }
+ }
+
+ public bool Remove(T item)
+ {
+ lock (_items)
+ {
+ int index = BinarySearch(item.Address);
+
+ if (index >= 0)
+ {
+ while (index > 0 && _items[index - 1].Address == item.Address)
+ {
+ index--;
+ }
+
+ while (index < _items.Count)
+ {
+ if (_items[index].Equals(item))
+ {
+ _items.RemoveAt(index);
+
+ return true;
+ }
+
+ if (_items[index].Address > item.Address)
+ {
+ break;
+ }
+
+ index++;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ public T FindFirstOverlap(T item)
+ {
+ return FindFirstOverlap(item.Address, item.Size);
+ }
+
+ public T FindFirstOverlap(ulong address, ulong size)
+ {
+ lock (_items)
+ {
+ int index = BinarySearch(address, size);
+
+ if (index < 0)
+ {
+ return default(T);
+ }
+
+ return _items[index];
+ }
+ }
+
+ public T[] FindOverlaps(T item)
+ {
+ return FindOverlaps(item.Address, item.Size);
+ }
+
+ public T[] FindOverlaps(ulong address, ulong size)
+ {
+ List<T> overlapsList = new List<T>();
+
+ ulong endAddress = address + size;
+
+ lock (_items)
+ {
+ foreach (T item in _items)
+ {
+ if (item.Address >= endAddress)
+ {
+ break;
+ }
+
+ if (item.OverlapsWith(address, size))
+ {
+ overlapsList.Add(item);
+ }
+ }
+ }
+
+ return overlapsList.ToArray();
+ }
+
+ public T[] FindOverlaps(ulong address)
+ {
+ List<T> overlapsList = new List<T>();
+
+ lock (_items)
+ {
+ int index = BinarySearch(address);
+
+ if (index >= 0)
+ {
+ while (index > 0 && _items[index - 1].Address == address)
+ {
+ index--;
+ }
+
+ while (index < _items.Count)
+ {
+ T overlap = _items[index++];
+
+ if (overlap.Address != address)
+ {
+ break;
+ }
+
+ overlapsList.Add(overlap);
+ }
+ }
+ }
+
+ return overlapsList.ToArray();
+ }
+
+ private int BinarySearch(ulong address)
+ {
+ int left = 0;
+ int right = _items.Count - 1;
+
+ while (left <= right)
+ {
+ int range = right - left;
+
+ int middle = left + (range >> 1);
+
+ T item = _items[middle];
+
+ if (item.Address == address)
+ {
+ return middle;
+ }
+
+ if (address < item.Address)
+ {
+ right = middle - 1;
+ }
+ else
+ {
+ left = middle + 1;
+ }
+ }
+
+ return ~left;
+ }
+
+ private int BinarySearch(ulong address, ulong size)
+ {
+ int left = 0;
+ int right = _items.Count - 1;
+
+ while (left <= right)
+ {
+ int range = right - left;
+
+ int middle = left + (range >> 1);
+
+ T item = _items[middle];
+
+ if (item.OverlapsWith(address, size))
+ {
+ return middle;
+ }
+
+ if (address < item.Address)
+ {
+ right = middle - 1;
+ }
+ else
+ {
+ left = middle + 1;
+ }
+ }
+
+ return ~left;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Memory/VertexBuffer.cs b/Ryujinx.Graphics.Gpu/Memory/VertexBuffer.cs
new file mode 100644
index 00000000..1cb854d6
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Memory/VertexBuffer.cs
@@ -0,0 +1,10 @@
+namespace Ryujinx.Graphics.Gpu.Memory
+{
+ struct VertexBuffer
+ {
+ public ulong Address;
+ public ulong Size;
+ public int Stride;
+ public int Divisor;
+ }
+} \ No newline at end of file