using System; using System.Runtime.InteropServices; namespace Ryujinx.Graphics.Gpu.Engine.Threed { /// /// Constant buffer updater. /// class ConstantBufferUpdater { private const int UniformDataCacheSize = 512; private readonly GpuChannel _channel; private readonly DeviceStateWithShadow _state; // State associated with direct uniform buffer updates. // This state is used to attempt to batch together consecutive updates. private ulong _ubBeginCpuAddress = 0; private ulong _ubFollowUpAddress = 0; private ulong _ubByteCount = 0; private int _ubIndex = 0; private int[] _ubData = new int[UniformDataCacheSize]; /// /// Creates a new instance of the constant buffer updater. /// /// GPU channel /// Channel state public ConstantBufferUpdater(GpuChannel channel, DeviceStateWithShadow state) { _channel = channel; _state = state; } /// /// Binds a uniform buffer for the vertex shader stage. /// /// Method call argument public void BindVertex(int argument) { Bind(argument, ShaderType.Vertex); } /// /// Binds a uniform buffer for the tessellation control shader stage. /// /// Method call argument public void BindTessControl(int argument) { Bind(argument, ShaderType.TessellationControl); } /// /// Binds a uniform buffer for the tessellation evaluation shader stage. /// /// Method call argument public void BindTessEvaluation(int argument) { Bind(argument, ShaderType.TessellationEvaluation); } /// /// Binds a uniform buffer for the geometry shader stage. /// /// Method call argument public void BindGeometry(int argument) { Bind(argument, ShaderType.Geometry); } /// /// Binds a uniform buffer for the fragment shader stage. /// /// Method call argument public void BindFragment(int argument) { Bind(argument, ShaderType.Fragment); } /// /// Binds a uniform buffer for the specified shader stage. /// /// Method call argument /// Shader stage that will access the uniform buffer private void Bind(int argument, ShaderType type) { bool enable = (argument & 1) != 0; int index = (argument >> 4) & 0x1f; FlushUboDirty(); if (enable) { var uniformBuffer = _state.State.UniformBufferState; ulong address = uniformBuffer.Address.Pack(); _channel.BufferManager.SetGraphicsUniformBuffer((int)type, index, address, (uint)uniformBuffer.Size); } else { _channel.BufferManager.SetGraphicsUniformBuffer((int)type, index, 0, 0); } } /// /// Flushes any queued UBO updates. /// public void FlushUboDirty() { if (_ubFollowUpAddress != 0) { var memoryManager = _channel.MemoryManager; Span data = MemoryMarshal.Cast(_ubData.AsSpan(0, (int)(_ubByteCount / 4))); if (memoryManager.Physical.WriteWithRedundancyCheck(_ubBeginCpuAddress, data)) { memoryManager.Physical.BufferCache.ForceDirty(memoryManager, _ubFollowUpAddress - _ubByteCount, _ubByteCount); } _ubFollowUpAddress = 0; _ubIndex = 0; } } /// /// Updates the uniform buffer data with inline data. /// /// New uniform buffer data word public void Update(int argument) { var uniformBuffer = _state.State.UniformBufferState; ulong address = uniformBuffer.Address.Pack() + (uint)uniformBuffer.Offset; if (_ubFollowUpAddress != address || _ubIndex == _ubData.Length) { FlushUboDirty(); _ubByteCount = 0; _ubBeginCpuAddress = _channel.MemoryManager.Translate(address); } _ubData[_ubIndex++] = argument; _ubFollowUpAddress = address + 4; _ubByteCount += 4; _state.State.UniformBufferState.Offset += 4; } /// /// Updates the uniform buffer data with inline data. /// /// Data to be written to the uniform buffer public void Update(ReadOnlySpan data) { var uniformBuffer = _state.State.UniformBufferState; ulong address = uniformBuffer.Address.Pack() + (uint)uniformBuffer.Offset; ulong size = (ulong)data.Length * 4; if (_ubFollowUpAddress != address || _ubIndex + data.Length > _ubData.Length) { FlushUboDirty(); _ubByteCount = 0; _ubBeginCpuAddress = _channel.MemoryManager.Translate(address); } data.CopyTo(_ubData.AsSpan(_ubIndex)); _ubIndex += data.Length; _ubFollowUpAddress = address + size; _ubByteCount += size; _state.State.UniformBufferState.Offset += data.Length * 4; } } }