using Ryujinx.Common; using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.Gpu.Image; using Ryujinx.Graphics.Gpu.State; using Ryujinx.Graphics.Shader; using Ryujinx.Memory.Range; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; namespace Ryujinx.Graphics.Gpu.Memory { /// /// Buffer manager. /// class BufferManager { private const int StackToHeapThreshold = 16; private const int OverlapsBufferInitialCapacity = 10; private const int OverlapsBufferMaxCapacity = 10000; private const ulong BufferAlignmentSize = 0x1000; private const ulong BufferAlignmentMask = BufferAlignmentSize - 1; private GpuContext _context; private RangeList _buffers; private Buffer[] _bufferOverlaps; private IndexBuffer _indexBuffer; private VertexBuffer[] _vertexBuffers; private BufferBounds[] _transformFeedbackBuffers; private List _bufferTextures; /// /// Holds shader stage buffer state and binding information. /// private class BuffersPerStage { /// /// Shader buffer binding information. /// public BufferDescriptor[] Bindings { get; } /// /// Buffer regions. /// public BufferBounds[] Buffers { get; } /// /// Total amount of buffers used on the shader. /// public int Count { get; private set; } /// /// Creates a new instance of the shader stage buffer information. /// /// Maximum amount of buffers that the shader stage can use public BuffersPerStage(int count) { Bindings = new BufferDescriptor[count]; Buffers = new BufferBounds[count]; } /// /// Sets the region of a buffer at a given slot. /// /// Buffer slot /// Region virtual address /// Region size in bytes /// Buffer usage flags public void SetBounds(int index, ulong address, ulong size, BufferUsageFlags flags = BufferUsageFlags.None) { Buffers[index] = new BufferBounds(address, size, flags); } /// /// Sets shader buffer binding information. /// /// Buffer binding information public void SetBindings(ReadOnlyCollection descriptors) { if (descriptors == null) { Count = 0; return; } descriptors.CopyTo(Bindings, 0); Count = descriptors.Count; } } private BuffersPerStage _cpStorageBuffers; private BuffersPerStage _cpUniformBuffers; private BuffersPerStage[] _gpStorageBuffers; private BuffersPerStage[] _gpUniformBuffers; private int _cpStorageBufferBindings; private int _cpUniformBufferBindings; private int _gpStorageBufferBindings; private int _gpUniformBufferBindings; private bool _gpStorageBuffersDirty; private bool _gpUniformBuffersDirty; private bool _indexBufferDirty; private bool _vertexBuffersDirty; private uint _vertexBuffersEnableMask; private bool _transformFeedbackBuffersDirty; private bool _rebind; private Dictionary _dirtyCache; /// /// Creates a new instance of the buffer manager. /// /// The GPU context that the buffer manager belongs to public BufferManager(GpuContext context) { _context = context; _buffers = new RangeList(); _bufferOverlaps = new Buffer[OverlapsBufferInitialCapacity]; _vertexBuffers = new VertexBuffer[Constants.TotalVertexBuffers]; _transformFeedbackBuffers = new BufferBounds[Constants.TotalTransformFeedbackBuffers]; _cpStorageBuffers = new BuffersPerStage(Constants.TotalCpStorageBuffers); _cpUniformBuffers = new BuffersPerStage(Constants.TotalCpUniformBuffers); _gpStorageBuffers = new BuffersPerStage[Constants.ShaderStages]; _gpUniformBuffers = new BuffersPerStage[Constants.ShaderStages]; for (int index = 0; index < Constants.ShaderStages; index++) { _gpStorageBuffers[index] = new BuffersPerStage(Constants.TotalGpStorageBuffers); _gpUniformBuffers[index] = new BuffersPerStage(Constants.TotalGpUniformBuffers); } _bufferTextures = new List(); _dirtyCache = new Dictionary(); } /// /// Sets the memory range with the index buffer data, to be used for subsequent draw calls. /// /// Start GPU virtual address of the index buffer /// Size, in bytes, of the index buffer /// Type of each index buffer element 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; } /// /// Sets a new index buffer that overrides the one set on the call to . /// /// Buffer to be used as index buffer /// Type of each index buffer element public void SetIndexBuffer(BufferRange buffer, IndexType type) { _context.Renderer.Pipeline.SetIndexBuffer(buffer, type); _indexBufferDirty = true; } /// /// Sets the memory range with vertex buffer data, to be used for subsequent draw calls. /// /// Index of the vertex buffer (up to 16) /// GPU virtual address of the buffer /// Size in bytes of the buffer /// Stride of the buffer, defined as the number of bytes of each vertex /// Vertex divisor of the buffer, for instanced draws 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; if (address != 0) { _vertexBuffersEnableMask |= 1u << index; } else { _vertexBuffersEnableMask &= ~(1u << index); } } /// /// Sets a transform feedback buffer on the graphics pipeline. /// The output from the vertex transformation stages are written into the feedback buffer. /// /// Index of the transform feedback buffer /// Start GPU virtual address of the buffer /// Size in bytes of the transform feedback buffer public void SetTransformFeedbackBuffer(int index, ulong gpuVa, ulong size) { ulong address = TranslateAndCreateBuffer(gpuVa, size); _transformFeedbackBuffers[index] = new BufferBounds(address, size); _transformFeedbackBuffersDirty = true; } /// /// Sets a storage buffer on the compute pipeline. /// Storage buffers can be read and written to on shaders. /// /// Index of the storage buffer /// Start GPU virtual address of the buffer /// Size in bytes of the storage buffer /// Buffer usage flags public void SetComputeStorageBuffer(int index, ulong gpuVa, ulong size, BufferUsageFlags flags) { size += gpuVa & ((ulong)_context.Capabilities.StorageBufferOffsetAlignment - 1); gpuVa = BitUtils.AlignDown(gpuVa, _context.Capabilities.StorageBufferOffsetAlignment); ulong address = TranslateAndCreateBuffer(gpuVa, size); _cpStorageBuffers.SetBounds(index, address, size, flags); } /// /// Sets a storage buffer on the graphics pipeline. /// Storage buffers can be read and written to on shaders. /// /// Index of the shader stage /// Index of the storage buffer /// Start GPU virtual address of the buffer /// Size in bytes of the storage buffer /// Buffer usage flags public void SetGraphicsStorageBuffer(int stage, int index, ulong gpuVa, ulong size, BufferUsageFlags flags) { size += gpuVa & ((ulong)_context.Capabilities.StorageBufferOffsetAlignment - 1); gpuVa = BitUtils.AlignDown(gpuVa, _context.Capabilities.StorageBufferOffsetAlignment); ulong address = TranslateAndCreateBuffer(gpuVa, size); if (_gpStorageBuffers[stage].Buffers[index].Address != address || _gpStorageBuffers[stage].Buffers[index].Size != size) { _gpStorageBuffersDirty = true; } _gpStorageBuffers[stage].SetBounds(index, address, size, flags); } /// /// Sets a uniform buffer on the compute pipeline. /// Uniform buffers are read-only from shaders, and have a small capacity. /// /// Index of the uniform buffer /// Start GPU virtual address of the buffer /// Size in bytes of the storage buffer public void SetComputeUniformBuffer(int index, ulong gpuVa, ulong size) { ulong address = TranslateAndCreateBuffer(gpuVa, size); _cpUniformBuffers.SetBounds(index, address, size); } /// /// Sets a uniform buffer on the graphics pipeline. /// Uniform buffers are read-only from shaders, and have a small capacity. /// /// Index of the shader stage /// Index of the uniform buffer /// Start GPU virtual address of the buffer /// Size in bytes of the storage buffer public void SetGraphicsUniformBuffer(int stage, int index, ulong gpuVa, ulong size) { ulong address = TranslateAndCreateBuffer(gpuVa, size); _gpUniformBuffers[stage].SetBounds(index, address, size); _gpUniformBuffersDirty = true; } /// /// Sets the binding points for the storage buffers bound on the compute pipeline. /// /// Buffer descriptors with the binding point values public void SetComputeStorageBufferBindings(ReadOnlyCollection descriptors) { _cpStorageBuffers.SetBindings(descriptors); _cpStorageBufferBindings = descriptors.Count != 0 ? descriptors.Max(x => x.Binding) + 1 : 0; } /// /// Sets the binding points for the storage buffers bound on the graphics pipeline. /// /// Index of the shader stage /// Buffer descriptors with the binding point values public void SetGraphicsStorageBufferBindings(int stage, ReadOnlyCollection descriptors) { _gpStorageBuffers[stage].SetBindings(descriptors); _gpStorageBuffersDirty = true; } /// /// Sets the total number of storage buffer bindings used. /// /// Number of storage buffer bindings used public void SetGraphicsStorageBufferBindingsCount(int count) { _gpStorageBufferBindings = count; } /// /// Sets the binding points for the uniform buffers bound on the compute pipeline. /// /// Buffer descriptors with the binding point values public void SetComputeUniformBufferBindings(ReadOnlyCollection descriptors) { _cpUniformBuffers.SetBindings(descriptors); _cpUniformBufferBindings = descriptors.Count != 0 ? descriptors.Max(x => x.Binding) + 1 : 0; } /// /// Sets the enabled uniform buffers mask on the graphics pipeline. /// Each bit set on the mask indicates that the respective buffer index is enabled. /// /// Index of the shader stage /// Buffer descriptors with the binding point values public void SetGraphicsUniformBufferBindings(int stage, ReadOnlyCollection descriptors) { _gpUniformBuffers[stage].SetBindings(descriptors); _gpUniformBuffersDirty = true; } /// /// Sets the total number of uniform buffer bindings used. /// /// Number of uniform buffer bindings used public void SetGraphicsUniformBufferBindingsCount(int count) { _gpUniformBufferBindings = count; } /// /// Gets a bit mask indicating which compute uniform buffers are currently bound. /// /// Mask where each bit set indicates a bound constant buffer public uint GetComputeUniformBufferUseMask() { uint mask = 0; for (int i = 0; i < _cpUniformBuffers.Buffers.Length; i++) { if (_cpUniformBuffers.Buffers[i].Address != 0) { mask |= 1u << i; } } return mask; } /// /// Gets a bit mask indicating which graphics uniform buffers are currently bound. /// /// Index of the shader stage /// Mask where each bit set indicates a bound constant buffer public uint GetGraphicsUniformBufferUseMask(int stage) { uint mask = 0; for (int i = 0; i < _gpUniformBuffers[stage].Buffers.Length; i++) { if (_gpUniformBuffers[stage].Buffers[i].Address != 0) { mask |= 1u << i; } } return mask; } /// /// Handles removal of buffers written to a memory region being unmapped. /// /// Sender object /// Event arguments public void MemoryUnmappedHandler(object sender, UnmapEventArgs e) { Buffer[] overlaps = new Buffer[10]; int overlapCount; ulong address = _context.MemoryManager.Translate(e.Address); ulong size = e.Size; lock (_buffers) { overlapCount = _buffers.FindOverlaps(address, size, ref overlaps); } for (int i = 0; i < overlapCount; i++) { overlaps[i].Unmapped(address, size); } } /// /// Performs address translation of the GPU virtual address, and creates a /// new buffer, if needed, for the specified range. /// /// Start GPU virtual address of the buffer /// Size in bytes of the buffer /// CPU virtual address of the buffer, after address translation private ulong TranslateAndCreateBuffer(ulong gpuVa, ulong size) { if (gpuVa == 0) { return 0; } ulong address = _context.MemoryManager.Translate(gpuVa); if (address == MemoryManager.PteUnmapped) { return 0; } CreateBuffer(address, size); return address; } /// /// Creates a new buffer for the specified range, if it does not yet exist. /// This can be used to ensure the existance of a buffer. /// /// Address of the buffer in memory /// Size of the buffer in bytes public void CreateBuffer(ulong address, ulong size) { 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; } CreateBufferAligned(alignedAddress, alignedEndAddress - alignedAddress); } /// /// Performs address translation of the GPU virtual address, and attempts to force /// the buffer in the region as dirty. /// The buffer lookup for this function is cached in a dictionary for quick access, which /// accelerates common UBO updates. /// /// Start GPU virtual address of the buffer /// Size in bytes of the buffer public void ForceDirty(ulong gpuVa, ulong size) { BufferCacheEntry result; if (!_dirtyCache.TryGetValue(gpuVa, out result) || result.EndGpuAddress < gpuVa + size || result.UnmappedSequence != result.Buffer.UnmappedSequence) { ulong address = TranslateAndCreateBuffer(gpuVa, size); result = new BufferCacheEntry(address, gpuVa, GetBuffer(address, size)); _dirtyCache[gpuVa] = result; } result.Buffer.ForceDirty(result.Address, size); } /// /// Creates a new buffer for the specified range, if needed. /// If a buffer where this range can be fully contained already exists, /// then the creation of a new buffer is not necessary. /// /// Address of the buffer in guest memory /// Size in bytes of the buffer private void CreateBufferAligned(ulong address, ulong size) { int overlapsCount; lock (_buffers) { overlapsCount = _buffers.FindOverlapsNonOverlapping(address, size, ref _bufferOverlaps); } if (overlapsCount != 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 (_bufferOverlaps[0].Address > address || _bufferOverlaps[0].EndAddress < endAddress) { for (int index = 0; index < overlapsCount; index++) { Buffer buffer = _bufferOverlaps[index]; address = Math.Min(address, buffer.Address); endAddress = Math.Max(endAddress, buffer.EndAddress); lock (_buffers) { _buffers.Remove(buffer); } } Buffer newBuffer = new Buffer(_context, address, endAddress - address); newBuffer.SynchronizeMemory(address, endAddress - address); lock (_buffers) { _buffers.Add(newBuffer); } for (int index = 0; index < overlapsCount; index++) { Buffer buffer = _bufferOverlaps[index]; int dstOffset = (int)(buffer.Address - newBuffer.Address); buffer.ForceSynchronizeMemory(buffer.Address, buffer.Size); buffer.CopyTo(newBuffer, dstOffset); newBuffer.InheritModifiedRanges(buffer); buffer.Dispose(); } // Existing buffers were modified, we need to rebind everything. _rebind = true; } } else { // No overlap, just create a new buffer. Buffer buffer = new Buffer(_context, address, size); lock (_buffers) { _buffers.Add(buffer); } } ShrinkOverlapsBufferIfNeeded(); } /// /// Resizes the temporary buffer used for range list intersection results, if it has grown too much. /// private void ShrinkOverlapsBufferIfNeeded() { if (_bufferOverlaps.Length > OverlapsBufferMaxCapacity) { Array.Resize(ref _bufferOverlaps, OverlapsBufferMaxCapacity); } } /// /// Gets the address of the compute uniform buffer currently bound at the given index. /// /// Index of the uniform buffer binding /// The uniform buffer address, or an undefined value if the buffer is not currently bound public ulong GetComputeUniformBufferAddress(int index) { return _cpUniformBuffers.Buffers[index].Address; } /// /// Gets the address of the graphics uniform buffer currently bound at the given index. /// /// Index of the shader stage /// Index of the uniform buffer binding /// The uniform buffer address, or an undefined value if the buffer is not currently bound public ulong GetGraphicsUniformBufferAddress(int stage, int index) { return _gpUniformBuffers[stage].Buffers[index].Address; } /// /// Ensures that the compute engine bindings are visible to the host GPU. /// Note: this actually performs the binding using the host graphics API. /// public void CommitComputeBindings() { int sCount = _cpStorageBufferBindings; Span sRanges = sCount < StackToHeapThreshold ? stackalloc BufferRange[sCount] : new BufferRange[sCount]; for (int index = 0; index < _cpStorageBuffers.Count; index++) { ref var bindingInfo = ref _cpStorageBuffers.Bindings[index]; BufferBounds bounds = _cpStorageBuffers.Buffers[bindingInfo.Slot]; if (bounds.Address != 0) { // The storage buffer size is not reliable (it might be lower than the actual size), // so we bind the entire buffer to allow otherwise out of range accesses to work. sRanges[bindingInfo.Binding] = GetBufferRangeTillEnd( bounds.Address, bounds.Size, bounds.Flags.HasFlag(BufferUsageFlags.Write)); } } _context.Renderer.Pipeline.SetStorageBuffers(sRanges); int uCount = _cpUniformBufferBindings; Span uRanges = uCount < StackToHeapThreshold ? stackalloc BufferRange[uCount] : new BufferRange[uCount]; for (int index = 0; index < _cpUniformBuffers.Count; index++) { ref var bindingInfo = ref _cpUniformBuffers.Bindings[index]; BufferBounds bounds = _cpUniformBuffers.Buffers[bindingInfo.Slot]; if (bounds.Address != 0) { uRanges[bindingInfo.Binding] = GetBufferRange(bounds.Address, bounds.Size); } } _context.Renderer.Pipeline.SetUniformBuffers(uRanges); CommitBufferTextureBindings(); // Force rebind after doing compute work. _rebind = true; } /// /// Commit any queued buffer texture bindings. /// private void CommitBufferTextureBindings() { if (_bufferTextures.Count > 0) { foreach (var binding in _bufferTextures) { binding.Texture.SetStorage(GetBufferRange(binding.Address, binding.Size, binding.BindingInfo.Flags.HasFlag(TextureUsageFlags.ImageStore))); // The texture must be rebound to use the new storage if it was updated. if (binding.IsImage) { _context.Renderer.Pipeline.SetImage(binding.BindingInfo.Binding, binding.Texture, binding.Format); } else { _context.Renderer.Pipeline.SetTexture(binding.BindingInfo.Binding, binding.Texture); } } _bufferTextures.Clear(); } } /// /// Ensures that the graphics engine bindings are visible to the host GPU. /// Note: this actually performs the binding using the host graphics API. /// public void CommitGraphicsBindings() { if (_indexBufferDirty || _rebind) { _indexBufferDirty = false; if (_indexBuffer.Address != 0) { BufferRange buffer = GetBufferRange(_indexBuffer.Address, _indexBuffer.Size); _context.Renderer.Pipeline.SetIndexBuffer(buffer, _indexBuffer.Type); } } else if (_indexBuffer.Address != 0) { SynchronizeBufferRange(_indexBuffer.Address, _indexBuffer.Size); } uint vbEnableMask = _vertexBuffersEnableMask; if (_vertexBuffersDirty || _rebind) { _vertexBuffersDirty = false; Span vertexBuffers = stackalloc VertexBufferDescriptor[Constants.TotalVertexBuffers]; for (int index = 0; (vbEnableMask >> index) != 0; 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.Pipeline.SetVertexBuffers(vertexBuffers); } else { for (int index = 0; (vbEnableMask >> index) != 0; index++) { VertexBuffer vb = _vertexBuffers[index]; if (vb.Address == 0) { continue; } SynchronizeBufferRange(vb.Address, vb.Size); } } if (_transformFeedbackBuffersDirty || _rebind) { _transformFeedbackBuffersDirty = false; Span tfbs = stackalloc BufferRange[Constants.TotalTransformFeedbackBuffers]; for (int index = 0; index < Constants.TotalTransformFeedbackBuffers; index++) { BufferBounds tfb = _transformFeedbackBuffers[index]; if (tfb.Address == 0) { tfbs[index] = BufferRange.Empty; continue; } tfbs[index] = GetBufferRange(tfb.Address, tfb.Size); } _context.Renderer.Pipeline.SetTransformFeedbackBuffers(tfbs); } else { for (int index = 0; index < Constants.TotalTransformFeedbackBuffers; index++) { BufferBounds tfb = _transformFeedbackBuffers[index]; if (tfb.Address == 0) { continue; } SynchronizeBufferRange(tfb.Address, tfb.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); } CommitBufferTextureBindings(); _rebind = false; } /// /// Bind respective buffer bindings on the host API. /// /// Bindings to bind /// True to bind as storage buffer, false to bind as uniform buffers private void BindBuffers(BuffersPerStage[] bindings, bool isStorage) { int count = isStorage ? _gpStorageBufferBindings : _gpUniformBufferBindings; Span ranges = count < StackToHeapThreshold ? stackalloc BufferRange[count] : new BufferRange[count]; for (ShaderStage stage = ShaderStage.Vertex; stage <= ShaderStage.Fragment; stage++) { ref var buffers = ref bindings[(int)stage - 1]; for (int index = 0; index < buffers.Count; index++) { ref var bindingInfo = ref buffers.Bindings[index]; BufferBounds bounds = buffers.Buffers[bindingInfo.Slot]; if (bounds.Address != 0) { ranges[bindingInfo.Binding] = isStorage ? GetBufferRangeTillEnd(bounds.Address, bounds.Size, bounds.Flags.HasFlag(BufferUsageFlags.Write)) : GetBufferRange(bounds.Address, bounds.Size, bounds.Flags.HasFlag(BufferUsageFlags.Write)); } } } if (isStorage) { _context.Renderer.Pipeline.SetStorageBuffers(ranges); } else { _context.Renderer.Pipeline.SetUniformBuffers(ranges); } } /// /// Updates data for the already bound buffer bindings. /// /// Bindings to update private void UpdateBuffers(BuffersPerStage[] bindings) { for (ShaderStage stage = ShaderStage.Vertex; stage <= ShaderStage.Fragment; stage++) { ref var buffers = ref bindings[(int)stage - 1]; for (int index = 0; index < buffers.Count; index++) { ref var binding = ref buffers.Bindings[index]; BufferBounds bounds = buffers.Buffers[binding.Slot]; if (bounds.Address == 0) { continue; } SynchronizeBufferRange(bounds.Address, bounds.Size); } } } /// /// Sets the buffer storage of a buffer texture. This will be bound when the buffer manager commits bindings. /// /// Buffer texture /// Address of the buffer in memory /// Size of the buffer in bytes /// Binding info for the buffer texture /// Format of the buffer texture /// Whether the binding is for an image or a sampler public void SetBufferTextureStorage(ITexture texture, ulong address, ulong size, TextureBindingInfo bindingInfo, Format format, bool isImage) { CreateBuffer(address, size); _bufferTextures.Add(new BufferTextureBinding(texture, address, size, bindingInfo, format, isImage)); } /// /// Copy a buffer data from a given address to another. /// /// /// This does a GPU side copy. /// /// GPU virtual address of the copy source /// GPU virtual address of the copy destination /// Size in bytes of the copy public void CopyBuffer(GpuVa srcVa, GpuVa dstVa, ulong size) { ulong srcAddress = TranslateAndCreateBuffer(srcVa.Pack(), size); ulong dstAddress = TranslateAndCreateBuffer(dstVa.Pack(), size); Buffer srcBuffer = GetBuffer(srcAddress, size); Buffer dstBuffer = GetBuffer(dstAddress, size); int srcOffset = (int)(srcAddress - srcBuffer.Address); int dstOffset = (int)(dstAddress - dstBuffer.Address); _context.Renderer.Pipeline.CopyBuffer( srcBuffer.Handle, dstBuffer.Handle, srcOffset, dstOffset, (int)size); if (srcBuffer.IsModified(srcAddress, size)) { dstBuffer.SignalModified(dstAddress, size); } else { // Optimization: If the data being copied is already in memory, then copy it directly instead of flushing from GPU. dstBuffer.ClearModified(dstAddress, size); _context.PhysicalMemory.WriteUntracked(dstAddress, _context.PhysicalMemory.GetSpan(srcAddress, (int)size)); } } /// /// Clears a buffer at a given address with the specified value. /// /// /// Both the address and size must be aligned to 4 bytes. /// /// GPU virtual address of the region to clear /// Number of bytes to clear /// Value to be written into the buffer public void ClearBuffer(GpuVa gpuVa, ulong size, uint value) { ulong address = TranslateAndCreateBuffer(gpuVa.Pack(), size); Buffer buffer = GetBuffer(address, size); int offset = (int)(address - buffer.Address); _context.Renderer.Pipeline.ClearBuffer(buffer.Handle, offset, (int)size, value); buffer.SignalModified(address, size); } /// /// Gets a buffer sub-range starting at a given memory address. /// /// Start address of the memory range /// Size in bytes of the memory range /// Whether the buffer will be written to by this use /// The buffer sub-range starting at the given memory address private BufferRange GetBufferRangeTillEnd(ulong address, ulong size, bool write = false) { return GetBuffer(address, size, write).GetRange(address); } /// /// Gets a buffer sub-range for a given memory range. /// /// Start address of the memory range /// Size in bytes of the memory range /// Whether the buffer will be written to by this use /// The buffer sub-range for the given range private BufferRange GetBufferRange(ulong address, ulong size, bool write = false) { return GetBuffer(address, size, write).GetRange(address, size); } /// /// Gets a buffer for a given memory range. /// A buffer overlapping with the specified range is assumed to already exist on the cache. /// /// Start address of the memory range /// Size in bytes of the memory range /// Whether the buffer will be written to by this use /// The buffer where the range is fully contained private Buffer GetBuffer(ulong address, ulong size, bool write = false) { Buffer buffer; if (size != 0) { lock (_buffers) { buffer = _buffers.FindFirstOverlap(address, size); } buffer.SynchronizeMemory(address, size); if (write) { buffer.SignalModified(address, size); } } else { lock (_buffers) { buffer = _buffers.FindFirstOverlap(address, 1); } } return buffer; } /// /// Performs guest to host memory synchronization of a given memory range. /// /// Start address of the memory range /// Size in bytes of the memory range private void SynchronizeBufferRange(ulong address, ulong size) { if (size != 0) { Buffer buffer; lock (_buffers) { buffer = _buffers.FindFirstOverlap(address, size); } buffer.SynchronizeMemory(address, size); } } /// /// Disposes all buffers in the cache. /// It's an error to use the buffer manager after disposal. /// public void Dispose() { lock (_buffers) { foreach (Buffer buffer in _buffers) { buffer.Dispose(); } } } } }