diff --git a/Ryujinx.Graphics.Gpu/Engine/Compute.cs b/Ryujinx.Graphics.Gpu/Engine/Compute.cs index be317a7f4..a7f6ec063 100644 --- a/Ryujinx.Graphics.Gpu/Engine/Compute.cs +++ b/Ryujinx.Graphics.Gpu/Engine/Compute.cs @@ -40,7 +40,7 @@ namespace Ryujinx.Graphics.Gpu.Engine ulong gpuVa = (uint)qmd.ConstantBufferAddrLower(index) | (ulong)qmd.ConstantBufferAddrUpper(index) << 32; ulong size = (ulong)qmd.ConstantBufferSize(index); - BufferManager.SetComputeUniformBuffer(index, gpuVa, size); + state.Channel.BufferManager.SetComputeUniformBuffer(index, gpuVa, size); } ShaderBundle cs = ShaderCache.GetComputeShader( @@ -57,9 +57,9 @@ namespace Ryujinx.Graphics.Gpu.Engine var samplerPool = state.Get(MethodOffset.SamplerPoolState); var texturePool = state.Get(MethodOffset.TexturePoolState); - TextureManager.SetComputeSamplerPool(samplerPool.Address.Pack(), samplerPool.MaximumId, qmd.SamplerIndex); - TextureManager.SetComputeTexturePool(texturePool.Address.Pack(), texturePool.MaximumId); - TextureManager.SetComputeTextureBufferIndex(state.Get(MethodOffset.TextureBufferIndex)); + state.Channel.TextureManager.SetComputeSamplerPool(samplerPool.Address.Pack(), samplerPool.MaximumId, qmd.SamplerIndex); + state.Channel.TextureManager.SetComputeTexturePool(texturePool.Address.Pack(), texturePool.MaximumId); + state.Channel.TextureManager.SetComputeTextureBufferIndex(state.Get(MethodOffset.TextureBufferIndex)); ShaderProgramInfo info = cs.Shaders[0].Info; @@ -76,7 +76,7 @@ namespace Ryujinx.Graphics.Gpu.Engine continue; } - ulong cbDescAddress = BufferManager.GetComputeUniformBufferAddress(0); + ulong cbDescAddress = state.Channel.BufferManager.GetComputeUniformBufferAddress(0); int cbDescOffset = 0x260 + (cb.Slot - 8) * 0x10; @@ -84,14 +84,14 @@ namespace Ryujinx.Graphics.Gpu.Engine SbDescriptor cbDescriptor = _context.PhysicalMemory.Read(cbDescAddress); - BufferManager.SetComputeUniformBuffer(cb.Slot, cbDescriptor.PackAddress(), (uint)cbDescriptor.Size); + state.Channel.BufferManager.SetComputeUniformBuffer(cb.Slot, cbDescriptor.PackAddress(), (uint)cbDescriptor.Size); } for (int index = 0; index < info.SBuffers.Count; index++) { BufferDescriptor sb = info.SBuffers[index]; - ulong sbDescAddress = BufferManager.GetComputeUniformBufferAddress(0); + ulong sbDescAddress = state.Channel.BufferManager.GetComputeUniformBufferAddress(0); int sbDescOffset = 0x310 + sb.Slot * 0x10; @@ -99,11 +99,11 @@ namespace Ryujinx.Graphics.Gpu.Engine SbDescriptor sbDescriptor = _context.PhysicalMemory.Read(sbDescAddress); - BufferManager.SetComputeStorageBuffer(sb.Slot, sbDescriptor.PackAddress(), (uint)sbDescriptor.Size, sb.Flags); + state.Channel.BufferManager.SetComputeStorageBuffer(sb.Slot, sbDescriptor.PackAddress(), (uint)sbDescriptor.Size, sb.Flags); } - BufferManager.SetComputeStorageBufferBindings(info.SBuffers); - BufferManager.SetComputeUniformBufferBindings(info.CBuffers); + state.Channel.BufferManager.SetComputeStorageBufferBindings(info.SBuffers); + state.Channel.BufferManager.SetComputeUniformBufferBindings(info.CBuffers); var textureBindings = new TextureBindingInfo[info.Textures.Count]; @@ -121,7 +121,7 @@ namespace Ryujinx.Graphics.Gpu.Engine descriptor.Flags); } - TextureManager.SetComputeTextures(textureBindings); + state.Channel.TextureManager.SetComputeTextures(textureBindings); var imageBindings = new TextureBindingInfo[info.Images.Count]; @@ -141,10 +141,10 @@ namespace Ryujinx.Graphics.Gpu.Engine descriptor.Flags); } - TextureManager.SetComputeImages(imageBindings); + state.Channel.TextureManager.SetComputeImages(imageBindings); - TextureManager.CommitComputeBindings(); - BufferManager.CommitComputeBindings(); + state.Channel.TextureManager.CommitComputeBindings(); + state.Channel.BufferManager.CommitComputeBindings(); _context.Renderer.Pipeline.DispatchCompute( qmd.CtaRasterWidth, diff --git a/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoDevice.cs b/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoDevice.cs index d0fcf1421..0e284ac56 100644 --- a/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoDevice.cs +++ b/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoDevice.cs @@ -25,6 +25,11 @@ namespace Ryujinx.Graphics.Gpu.Engine.GPFifo /// private struct CommandBuffer { + /// + /// Processor used to process the command buffer. Contains channel state. + /// + public GPFifoProcessor Processor; + /// /// The type of the command buffer. /// @@ -60,11 +65,11 @@ namespace Ryujinx.Graphics.Gpu.Engine.GPFifo private readonly ConcurrentQueue _commandBufferQueue; private CommandBuffer _currentCommandBuffer; + private GPFifoProcessor _prevChannelProcessor; private readonly bool _ibEnable; private readonly GpuContext _context; private readonly AutoResetEvent _event; - private readonly GPFifoProcessor _processor; private bool _interrupt; @@ -78,8 +83,6 @@ namespace Ryujinx.Graphics.Gpu.Engine.GPFifo _ibEnable = true; _context = context; _event = new AutoResetEvent(false); - - _processor = new GPFifoProcessor(context); } /// @@ -94,11 +97,13 @@ namespace Ryujinx.Graphics.Gpu.Engine.GPFifo /// Push a GPFIFO entry in the form of a prefetched command buffer. /// It is intended to be used by nvservices to handle special cases. /// + /// Processor used to process /// The command buffer containing the prefetched commands - public void PushHostCommandBuffer(int[] commandBuffer) + internal void PushHostCommandBuffer(GPFifoProcessor processor, int[] commandBuffer) { _commandBufferQueue.Enqueue(new CommandBuffer { + Processor = processor, Type = CommandBufferType.Prefetch, Words = commandBuffer, EntryAddress = ulong.MaxValue, @@ -109,9 +114,10 @@ namespace Ryujinx.Graphics.Gpu.Engine.GPFifo /// /// Create a CommandBuffer from a GPFIFO entry. /// + /// Processor used to process the command buffer pointed to by /// The GPFIFO entry /// A new CommandBuffer based on the GPFIFO entry - private CommandBuffer CreateCommandBuffer(GPEntry entry) + private static CommandBuffer CreateCommandBuffer(GPFifoProcessor processor, GPEntry entry) { CommandBufferType type = CommandBufferType.Prefetch; @@ -124,6 +130,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.GPFifo return new CommandBuffer { + Processor = processor, Type = type, Words = null, EntryAddress = startAddress, @@ -134,8 +141,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.GPFifo /// /// Pushes GPFIFO entries. /// + /// Processor used to process the command buffers pointed to by /// GPFIFO entries - public void PushEntries(ReadOnlySpan entries) + internal void PushEntries(GPFifoProcessor processor, ReadOnlySpan entries) { bool beforeBarrier = true; @@ -143,7 +151,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.GPFifo { ulong entry = entries[index]; - CommandBuffer commandBuffer = CreateCommandBuffer(Unsafe.As(ref entry)); + CommandBuffer commandBuffer = CreateCommandBuffer(processor, Unsafe.As(ref entry)); if (beforeBarrier && commandBuffer.Type == CommandBufferType.Prefetch) { @@ -173,12 +181,24 @@ namespace Ryujinx.Graphics.Gpu.Engine.GPFifo /// public void DispatchCalls() { + // Use this opportunity to also dispose any pending channels that were closed. + _context.DisposePendingChannels(); + + // Process command buffers. while (_ibEnable && !_interrupt && _commandBufferQueue.TryDequeue(out CommandBuffer entry)) { _currentCommandBuffer = entry; _currentCommandBuffer.Fetch(_context); - _processor.Process(_currentCommandBuffer.Words); + // If we are changing the current channel, + // we need to force all the host state to be updated. + if (_prevChannelProcessor != entry.Processor) + { + _prevChannelProcessor = entry.Processor; + entry.Processor.ForceAllDirty(); + } + + entry.Processor.Process(_currentCommandBuffer.Words); } _interrupt = false; diff --git a/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoProcessor.cs b/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoProcessor.cs index 78912bcc5..dc8a1c75d 100644 --- a/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoProcessor.cs +++ b/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoProcessor.cs @@ -35,7 +35,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.GPFifo /// Creates a new instance of the GPU General Purpose FIFO command processor. /// /// GPU context - public GPFifoProcessor(GpuContext context) + /// Channel that the GPFIFO processor belongs to + public GPFifoProcessor(GpuContext context, GpuChannel channel) { _context = context; @@ -44,7 +45,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.GPFifo for (int index = 0; index < _subChannels.Length; index++) { - _subChannels[index] = new GpuState(); + _subChannels[index] = new GpuState(channel); _context.Methods.RegisterCallbacks(_subChannels[index]); } @@ -186,5 +187,17 @@ namespace Ryujinx.Graphics.Gpu.Engine.GPFifo _subChannels[i].ShadowRamControl = control; } } + + /// + /// Forces a full host state update by marking all state as modified, + /// and also requests all GPU resources in use to be rebound. + /// + public void ForceAllDirty() + { + for (int index = 0; index < _subChannels.Length; index++) + { + _subChannels[index].ForceAllDirty(); + } + } } } diff --git a/Ryujinx.Graphics.Gpu/Engine/MethodClear.cs b/Ryujinx.Graphics.Gpu/Engine/MethodClear.cs index ea33304a0..5f6316dcb 100644 --- a/Ryujinx.Graphics.Gpu/Engine/MethodClear.cs +++ b/Ryujinx.Graphics.Gpu/Engine/MethodClear.cs @@ -35,7 +35,7 @@ namespace Ryujinx.Graphics.Gpu.Engine UpdateRenderTargetState(state, useControl: false, singleUse: index); - TextureManager.UpdateRenderTargets(); + state.Channel.TextureManager.UpdateRenderTargets(); bool clearDepth = (argument & 1) != 0; bool clearStencil = (argument & 2) != 0; diff --git a/Ryujinx.Graphics.Gpu/Engine/MethodCopyBuffer.cs b/Ryujinx.Graphics.Gpu/Engine/MethodCopyBuffer.cs index a1cf86ec6..c4d8a83d7 100644 --- a/Ryujinx.Graphics.Gpu/Engine/MethodCopyBuffer.cs +++ b/Ryujinx.Graphics.Gpu/Engine/MethodCopyBuffer.cs @@ -112,7 +112,7 @@ namespace Ryujinx.Graphics.Gpu.Engine if (completeSource && completeDest) { - Image.Texture target = TextureManager.FindTexture(dst, cbp, swizzle, dstLinear); + Image.Texture target = TextureCache.FindTexture(dst, cbp, swizzle, dstLinear); if (target != null) { ReadOnlySpan data; @@ -209,13 +209,13 @@ namespace Ryujinx.Graphics.Gpu.Engine swizzle.UnpackComponentSize() == 4) { // Fast path for clears when remap is enabled. - BufferManager.ClearBuffer(cbp.DstAddress, (uint)size * 4, state.Get(MethodOffset.CopyBufferConstA)); + BufferCache.ClearBuffer(cbp.DstAddress, (uint)size * 4, state.Get(MethodOffset.CopyBufferConstA)); } else { // TODO: Implement remap functionality. // Buffer to buffer copy. - BufferManager.CopyBuffer(cbp.SrcAddress, cbp.DstAddress, (uint)size); + BufferCache.CopyBuffer(cbp.SrcAddress, cbp.DstAddress, (uint)size); } } } diff --git a/Ryujinx.Graphics.Gpu/Engine/MethodCopyTexture.cs b/Ryujinx.Graphics.Gpu/Engine/MethodCopyTexture.cs index da08f31a1..d05702626 100644 --- a/Ryujinx.Graphics.Gpu/Engine/MethodCopyTexture.cs +++ b/Ryujinx.Graphics.Gpu/Engine/MethodCopyTexture.cs @@ -80,7 +80,7 @@ namespace Ryujinx.Graphics.Gpu.Engine srcX1 = 0; } - Texture srcTexture = TextureManager.FindOrCreateTexture(srcCopyTexture, offset, srcCopyTextureFormat, true, srcHint); + Texture srcTexture = TextureCache.FindOrCreateTexture(srcCopyTexture, offset, srcCopyTextureFormat, true, srcHint); if (srcTexture == null) { @@ -101,7 +101,7 @@ namespace Ryujinx.Graphics.Gpu.Engine dstCopyTextureFormat = dstCopyTexture.Format.Convert(); } - Texture dstTexture = TextureManager.FindOrCreateTexture(dstCopyTexture, 0, dstCopyTextureFormat, srcTexture.ScaleMode == TextureScaleMode.Scaled, dstHint); + Texture dstTexture = TextureCache.FindOrCreateTexture(dstCopyTexture, 0, dstCopyTextureFormat, srcTexture.ScaleMode == TextureScaleMode.Scaled, dstHint); if (dstTexture == null) { diff --git a/Ryujinx.Graphics.Gpu/Engine/MethodDraw.cs b/Ryujinx.Graphics.Gpu/Engine/MethodDraw.cs index 88f2e8fe9..fec1cc465 100644 --- a/Ryujinx.Graphics.Gpu/Engine/MethodDraw.cs +++ b/Ryujinx.Graphics.Gpu/Engine/MethodDraw.cs @@ -109,7 +109,7 @@ namespace Ryujinx.Graphics.Gpu.Engine BufferRange br = new BufferRange(_ibStreamer.GetInlineIndexBuffer(), 0, inlineIndexCount * 4); - _context.Methods.BufferManager.SetIndexBuffer(br, IndexType.UInt); + state.Channel.BufferManager.SetIndexBuffer(br, IndexType.UInt); _context.Renderer.Pipeline.DrawIndexed( inlineIndexCount, diff --git a/Ryujinx.Graphics.Gpu/Engine/MethodUniformBufferBind.cs b/Ryujinx.Graphics.Gpu/Engine/MethodUniformBufferBind.cs index 16fb31d69..33533e8ba 100644 --- a/Ryujinx.Graphics.Gpu/Engine/MethodUniformBufferBind.cs +++ b/Ryujinx.Graphics.Gpu/Engine/MethodUniformBufferBind.cs @@ -74,11 +74,11 @@ namespace Ryujinx.Graphics.Gpu.Engine ulong address = uniformBuffer.Address.Pack(); - BufferManager.SetGraphicsUniformBuffer((int)type, index, address, (uint)uniformBuffer.Size); + state.Channel.BufferManager.SetGraphicsUniformBuffer((int)type, index, address, (uint)uniformBuffer.Size); } else { - BufferManager.SetGraphicsUniformBuffer((int)type, index, 0, 0); + state.Channel.BufferManager.SetGraphicsUniformBuffer((int)type, index, 0, 0); } } } diff --git a/Ryujinx.Graphics.Gpu/Engine/MethodUniformBufferUpdate.cs b/Ryujinx.Graphics.Gpu/Engine/MethodUniformBufferUpdate.cs index 3e1dd1518..981d2e943 100644 --- a/Ryujinx.Graphics.Gpu/Engine/MethodUniformBufferUpdate.cs +++ b/Ryujinx.Graphics.Gpu/Engine/MethodUniformBufferUpdate.cs @@ -20,7 +20,7 @@ namespace Ryujinx.Graphics.Gpu.Engine { if (_ubFollowUpAddress != 0) { - BufferManager.ForceDirty(_ubFollowUpAddress - _ubByteCount, _ubByteCount); + BufferCache.ForceDirty(_ubFollowUpAddress - _ubByteCount, _ubByteCount); _ubFollowUpAddress = 0; } diff --git a/Ryujinx.Graphics.Gpu/Engine/Methods.cs b/Ryujinx.Graphics.Gpu/Engine/Methods.cs index 431ea4496..39b3d13c4 100644 --- a/Ryujinx.Graphics.Gpu/Engine/Methods.cs +++ b/Ryujinx.Graphics.Gpu/Engine/Methods.cs @@ -30,12 +30,12 @@ namespace Ryujinx.Graphics.Gpu.Engine /// /// GPU buffer manager. /// - public BufferManager BufferManager { get; } + public BufferCache BufferCache { get; } /// /// GPU texture manager. /// - public TextureManager TextureManager { get; } + public TextureCache TextureCache { get; } private bool _isAnyVbInstanced; private bool _vsUsesInstanceId; @@ -57,12 +57,12 @@ namespace Ryujinx.Graphics.Gpu.Engine _currentProgramInfo = new ShaderProgramInfo[Constants.ShaderStages]; - BufferManager = new BufferManager(context); - TextureManager = new TextureManager(context); + BufferCache = new BufferCache(context); + TextureCache = new TextureCache(context); context.MemoryManager.MemoryUnmapped += _counterCache.MemoryUnmappedHandler; - context.MemoryManager.MemoryUnmapped += TextureManager.MemoryUnmappedHandler; - context.MemoryManager.MemoryUnmapped += BufferManager.MemoryUnmappedHandler; + context.MemoryManager.MemoryUnmapped += TextureCache.MemoryUnmappedHandler; + context.MemoryManager.MemoryUnmapped += BufferCache.MemoryUnmappedHandler; } /// @@ -280,7 +280,7 @@ namespace Ryujinx.Graphics.Gpu.Engine UpdateLogicOpState(state); } - CommitBindings(); + CommitBindings(state); if (tfEnable && !_prevTfEnable) { @@ -303,18 +303,20 @@ namespace Ryujinx.Graphics.Gpu.Engine /// Ensures that the bindings are visible to the host GPU. /// Note: this actually performs the binding using the host graphics API. /// - private void CommitBindings() + /// Current GPU state + private void CommitBindings(GpuState state) { - UpdateStorageBuffers(); + UpdateStorageBuffers(state); - TextureManager.CommitGraphicsBindings(); - BufferManager.CommitGraphicsBindings(); + state.Channel.TextureManager.CommitGraphicsBindings(); + state.Channel.BufferManager.CommitGraphicsBindings(); } /// /// Updates storage buffer bindings. /// - private void UpdateStorageBuffers() + /// Current GPU state + private void UpdateStorageBuffers(GpuState state) { for (int stage = 0; stage < _currentProgramInfo.Length; stage++) { @@ -329,7 +331,7 @@ namespace Ryujinx.Graphics.Gpu.Engine { BufferDescriptor sb = info.SBuffers[index]; - ulong sbDescAddress = BufferManager.GetGraphicsUniformBufferAddress(stage, 0); + ulong sbDescAddress = state.Channel.BufferManager.GetGraphicsUniformBufferAddress(stage, 0); int sbDescOffset = 0x110 + stage * 0x100 + sb.Slot * 0x10; @@ -337,7 +339,7 @@ namespace Ryujinx.Graphics.Gpu.Engine SbDescriptor sbDescriptor = _context.PhysicalMemory.Read(sbDescAddress); - BufferManager.SetGraphicsStorageBuffer(stage, sb.Slot, sbDescriptor.PackAddress(), (uint)sbDescriptor.Size, sb.Flags); + state.Channel.BufferManager.SetGraphicsStorageBuffer(stage, sb.Slot, sbDescriptor.PackAddress(), (uint)sbDescriptor.Size, sb.Flags); } } } @@ -372,14 +374,14 @@ namespace Ryujinx.Graphics.Gpu.Engine if (index >= count || !IsRtEnabled(colorState)) { - changedScale |= TextureManager.SetRenderTargetColor(index, null); + changedScale |= state.Channel.TextureManager.SetRenderTargetColor(index, null); continue; } - Texture color = TextureManager.FindOrCreateTexture(colorState, samplesInX, samplesInY, sizeHint); + Texture color = TextureCache.FindOrCreateTexture(colorState, samplesInX, samplesInY, sizeHint); - changedScale |= TextureManager.SetRenderTargetColor(index, color); + changedScale |= state.Channel.TextureManager.SetRenderTargetColor(index, color); } bool dsEnable = state.Get(MethodOffset.RtDepthStencilEnable); @@ -391,15 +393,15 @@ namespace Ryujinx.Graphics.Gpu.Engine var dsState = state.Get(MethodOffset.RtDepthStencilState); var dsSize = state.Get(MethodOffset.RtDepthStencilSize); - depthStencil = TextureManager.FindOrCreateTexture(dsState, dsSize, samplesInX, samplesInY, sizeHint); + depthStencil = TextureCache.FindOrCreateTexture(dsState, dsSize, samplesInX, samplesInY, sizeHint); } - changedScale |= TextureManager.SetRenderTargetDepthStencil(depthStencil); + changedScale |= state.Channel.TextureManager.SetRenderTargetDepthStencil(depthStencil); if (changedScale) { - TextureManager.UpdateRenderTargetScale(singleUse); - _context.Renderer.Pipeline.SetRenderTargetScale(TextureManager.RenderTargetScale); + state.Channel.TextureManager.UpdateRenderTargetScale(singleUse); + _context.Renderer.Pipeline.SetRenderTargetScale(state.Channel.TextureManager.RenderTargetScale); UpdateViewportTransform(state); UpdateScissorState(state); @@ -436,7 +438,7 @@ namespace Ryujinx.Graphics.Gpu.Engine int width = scissor.X2 - x; int height = scissor.Y2 - y; - float scale = TextureManager.RenderTargetScale; + float scale = state.Channel.TextureManager.RenderTargetScale; if (scale != 1f) { x = (int)(x * scale); @@ -545,7 +547,7 @@ namespace Ryujinx.Graphics.Gpu.Engine float width = scaleX * 2; float height = scaleY * 2; - float scale = TextureManager.RenderTargetScale; + float scale = state.Channel.TextureManager.RenderTargetScale; if (scale != 1f) { x *= scale; @@ -670,7 +672,7 @@ namespace Ryujinx.Graphics.Gpu.Engine ? texturePool.MaximumId : samplerPool.MaximumId; - TextureManager.SetGraphicsSamplerPool(samplerPool.Address.Pack(), maximumId, samplerIndex); + state.Channel.TextureManager.SetGraphicsSamplerPool(samplerPool.Address.Pack(), maximumId, samplerIndex); } /// @@ -681,9 +683,8 @@ namespace Ryujinx.Graphics.Gpu.Engine { var texturePool = state.Get(MethodOffset.TexturePoolState); - TextureManager.SetGraphicsTexturePool(texturePool.Address.Pack(), texturePool.MaximumId); - - TextureManager.SetGraphicsTextureBufferIndex(state.Get(MethodOffset.TextureBufferIndex)); + state.Channel.TextureManager.SetGraphicsTexturePool(texturePool.Address.Pack(), texturePool.MaximumId); + state.Channel.TextureManager.SetGraphicsTextureBufferIndex(state.Get(MethodOffset.TextureBufferIndex)); } /// @@ -771,7 +772,7 @@ namespace Ryujinx.Graphics.Gpu.Engine case IndexType.UInt: size *= 4; break; } - BufferManager.SetIndexBuffer(gpuVa, size, indexBuffer.Type); + state.Channel.BufferManager.SetIndexBuffer(gpuVa, size, indexBuffer.Type); // The index buffer affects the vertex buffer size calculation, we // need to ensure that they are updated. @@ -792,7 +793,7 @@ namespace Ryujinx.Graphics.Gpu.Engine if (!vertexBuffer.UnpackEnable()) { - BufferManager.SetVertexBuffer(index, 0, 0, 0, 0); + state.Channel.BufferManager.SetVertexBuffer(index, 0, 0, 0, 0); continue; } @@ -828,7 +829,7 @@ namespace Ryujinx.Graphics.Gpu.Engine size = (ulong)((firstInstance + drawState.First + drawState.Count) * stride); } - BufferManager.SetVertexBuffer(index, address, size, stride, divisor); + state.Channel.BufferManager.SetVertexBuffer(index, address, size, stride, divisor); } } @@ -1017,10 +1018,10 @@ namespace Ryujinx.Graphics.Gpu.Engine if (info == null) { - TextureManager.SetGraphicsTextures(stage, Array.Empty()); - TextureManager.SetGraphicsImages(stage, Array.Empty()); - BufferManager.SetGraphicsStorageBufferBindings(stage, null); - BufferManager.SetGraphicsUniformBufferBindings(stage, null); + state.Channel.TextureManager.SetGraphicsTextures(stage, Array.Empty()); + state.Channel.TextureManager.SetGraphicsImages(stage, Array.Empty()); + state.Channel.BufferManager.SetGraphicsStorageBufferBindings(stage, null); + state.Channel.BufferManager.SetGraphicsUniformBufferBindings(stage, null); continue; } @@ -1040,7 +1041,7 @@ namespace Ryujinx.Graphics.Gpu.Engine descriptor.Flags); } - TextureManager.SetGraphicsTextures(stage, textureBindings); + state.Channel.TextureManager.SetGraphicsTextures(stage, textureBindings); var imageBindings = new TextureBindingInfo[info.Images.Count]; @@ -1060,10 +1061,10 @@ namespace Ryujinx.Graphics.Gpu.Engine descriptor.Flags); } - TextureManager.SetGraphicsImages(stage, imageBindings); + state.Channel.TextureManager.SetGraphicsImages(stage, imageBindings); - BufferManager.SetGraphicsStorageBufferBindings(stage, info.SBuffers); - BufferManager.SetGraphicsUniformBufferBindings(stage, info.CBuffers); + state.Channel.BufferManager.SetGraphicsStorageBufferBindings(stage, info.SBuffers); + state.Channel.BufferManager.SetGraphicsUniformBufferBindings(stage, info.CBuffers); if (info.SBuffers.Count != 0) { @@ -1076,8 +1077,8 @@ namespace Ryujinx.Graphics.Gpu.Engine } } - BufferManager.SetGraphicsStorageBufferBindingsCount(storageBufferBindingsCount); - BufferManager.SetGraphicsUniformBufferBindingsCount(uniformBufferBindingsCount); + state.Channel.BufferManager.SetGraphicsStorageBufferBindingsCount(storageBufferBindingsCount); + state.Channel.BufferManager.SetGraphicsUniformBufferBindingsCount(uniformBufferBindingsCount); _context.Renderer.Pipeline.SetProgram(gs.HostProgram); } @@ -1094,12 +1095,12 @@ namespace Ryujinx.Graphics.Gpu.Engine if (!tfb.Enable) { - BufferManager.SetTransformFeedbackBuffer(index, 0, 0); + state.Channel.BufferManager.SetTransformFeedbackBuffer(index, 0, 0); continue; } - BufferManager.SetTransformFeedbackBuffer(index, tfb.Address.Pack(), (uint)tfb.Size); + state.Channel.BufferManager.SetTransformFeedbackBuffer(index, tfb.Address.Pack(), (uint)tfb.Size); } } diff --git a/Ryujinx.Graphics.Gpu/GpuChannel.cs b/Ryujinx.Graphics.Gpu/GpuChannel.cs new file mode 100644 index 000000000..791434490 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/GpuChannel.cs @@ -0,0 +1,78 @@ +using Ryujinx.Graphics.Gpu.Engine.GPFifo; +using Ryujinx.Graphics.Gpu.Image; +using Ryujinx.Graphics.Gpu.Memory; +using System; + +namespace Ryujinx.Graphics.Gpu +{ + /// + /// Represents a GPU channel. + /// + public class GpuChannel : IDisposable + { + private readonly GpuContext _context; + private readonly GPFifoDevice _device; + private readonly GPFifoProcessor _processor; + + /// + /// Channel buffer bindings manager. + /// + internal BufferManager BufferManager { get; } + + /// + /// Channel texture bindings manager. + /// + internal TextureManager TextureManager { get; } + + /// + /// Creates a new instance of a GPU channel. + /// + /// GPU context that the channel belongs to + internal GpuChannel(GpuContext context) + { + _context = context; + _device = context.GPFifo; + _processor = new GPFifoProcessor(context, this); + BufferManager = new BufferManager(context); + TextureManager = new TextureManager(context, this); + } + + /// + /// Push a GPFIFO entry in the form of a prefetched command buffer. + /// It is intended to be used by nvservices to handle special cases. + /// + /// The command buffer containing the prefetched commands + public void PushHostCommandBuffer(int[] commandBuffer) + { + _device.PushHostCommandBuffer(_processor, commandBuffer); + } + + /// + /// Pushes GPFIFO entries. + /// + /// GPFIFO entries + public void PushEntries(ReadOnlySpan entries) + { + _device.PushEntries(_processor, entries); + } + + /// + /// Disposes the GPU channel. + /// It's an error to use the GPU channel after disposal. + /// + public void Dispose() + { + _context.DisposedChannels.Enqueue(this); + } + + /// + /// Performs disposal of the host GPU resources used by this channel, that are not shared. + /// This must only be called from the render thread. + /// + internal void Destroy() + { + BufferManager.Dispose(); + TextureManager.Dispose(); + } + } +} diff --git a/Ryujinx.Graphics.Gpu/GpuContext.cs b/Ryujinx.Graphics.Gpu/GpuContext.cs index a9386ce5c..2ba832bb9 100644 --- a/Ryujinx.Graphics.Gpu/GpuContext.cs +++ b/Ryujinx.Graphics.Gpu/GpuContext.cs @@ -72,6 +72,11 @@ namespace Ryujinx.Graphics.Gpu /// internal List SyncActions { get; } + /// + /// Queue with closed channels for deferred disposal from the render thread. + /// + internal Queue DisposedChannels { get; } + private readonly Lazy _caps; /// @@ -111,6 +116,13 @@ namespace Ryujinx.Graphics.Gpu HostInitalized = new ManualResetEvent(false); SyncActions = new List(); + + DisposedChannels = new Queue(); + } + + public GpuChannel CreateChannel() + { + return new GpuChannel(this); } /// @@ -173,6 +185,18 @@ namespace Ryujinx.Graphics.Gpu } } + /// + /// Performs deferred disposal of closed channels. + /// This must only be called from the render thread. + /// + internal void DisposePendingChannels() + { + while (DisposedChannels.TryDequeue(out GpuChannel channel)) + { + channel.Destroy(); + } + } + /// /// Disposes all GPU resources currently cached. /// It's an error to push any GPU commands after disposal. @@ -181,9 +205,10 @@ namespace Ryujinx.Graphics.Gpu /// public void Dispose() { + DisposePendingChannels(); Methods.ShaderCache.Dispose(); - Methods.BufferManager.Dispose(); - Methods.TextureManager.Dispose(); + Methods.BufferCache.Dispose(); + Methods.TextureCache.Dispose(); Renderer.Dispose(); GPFifo.Dispose(); HostInitalized.Dispose(); diff --git a/Ryujinx.Graphics.Gpu/Image/Texture.cs b/Ryujinx.Graphics.Gpu/Image/Texture.cs index 6f720f4c1..0948c494b 100644 --- a/Ryujinx.Graphics.Gpu/Image/Texture.cs +++ b/Ryujinx.Graphics.Gpu/Image/Texture.cs @@ -218,7 +218,7 @@ namespace Ryujinx.Graphics.Gpu.Image { Debug.Assert(!isView); - TextureCreateInfo createInfo = TextureManager.GetCreateInfo(Info, _context.Capabilities, ScaleFactor); + TextureCreateInfo createInfo = TextureCache.GetCreateInfo(Info, _context.Capabilities, ScaleFactor); HostTexture = _context.Renderer.CreateTexture(createInfo, ScaleFactor); SynchronizeMemory(); // Load the data. @@ -242,7 +242,7 @@ namespace Ryujinx.Graphics.Gpu.Image ScaleFactor = GraphicsConfig.ResScale; } - TextureCreateInfo createInfo = TextureManager.GetCreateInfo(Info, _context.Capabilities, ScaleFactor); + TextureCreateInfo createInfo = TextureCache.GetCreateInfo(Info, _context.Capabilities, ScaleFactor); HostTexture = _context.Renderer.CreateTexture(createInfo, ScaleFactor); } } @@ -284,7 +284,7 @@ namespace Ryujinx.Graphics.Gpu.Image ScaleFactor, ScaleMode); - TextureCreateInfo createInfo = TextureManager.GetCreateInfo(info, _context.Capabilities, ScaleFactor); + TextureCreateInfo createInfo = TextureCache.GetCreateInfo(info, _context.Capabilities, ScaleFactor); texture.HostTexture = HostTexture.CreateView(createInfo, firstLayer, firstLevel); _viewStorage.AddView(texture); @@ -453,7 +453,7 @@ namespace Ryujinx.Graphics.Gpu.Image Info.SwizzleB, Info.SwizzleA)); - TextureCreateInfo createInfo = TextureManager.GetCreateInfo(Info, _context.Capabilities, ScaleFactor); + TextureCreateInfo createInfo = TextureCache.GetCreateInfo(Info, _context.Capabilities, ScaleFactor); if (_viewStorage != this) { @@ -511,7 +511,7 @@ namespace Ryujinx.Graphics.Gpu.Image { if (storage == null) { - TextureCreateInfo createInfo = TextureManager.GetCreateInfo(Info, _context.Capabilities, scale); + TextureCreateInfo createInfo = TextureCache.GetCreateInfo(Info, _context.Capabilities, scale); storage = _context.Renderer.CreateTexture(createInfo, scale); } @@ -558,7 +558,7 @@ namespace Ryujinx.Graphics.Gpu.Image Logger.Debug?.Print(LogClass.Gpu, $" Recreating view {Info.Width}x{Info.Height} {Info.FormatInfo.Format.ToString()}."); view.ScaleFactor = scale; - TextureCreateInfo viewCreateInfo = TextureManager.GetCreateInfo(view.Info, _context.Capabilities, scale); + TextureCreateInfo viewCreateInfo = TextureCache.GetCreateInfo(view.Info, _context.Capabilities, scale); ITexture newView = HostTexture.CreateView(viewCreateInfo, view.FirstLayer - FirstLayer, view.FirstLevel - FirstLevel); view.ReplaceStorage(newView); @@ -1134,7 +1134,7 @@ namespace Ryujinx.Graphics.Gpu.Image foreach (Texture view in viewCopy) { - TextureCreateInfo createInfo = TextureManager.GetCreateInfo(view.Info, _context.Capabilities, ScaleFactor); + TextureCreateInfo createInfo = TextureCache.GetCreateInfo(view.Info, _context.Capabilities, ScaleFactor); ITexture newView = parent.HostTexture.CreateView(createInfo, view.FirstLayer + firstLayer, view.FirstLevel + firstLevel); @@ -1280,7 +1280,7 @@ namespace Ryujinx.Graphics.Gpu.Image _viewStorage.RemoveView(this); } - _context.Methods.TextureManager.RemoveTextureFromCache(this); + _context.Methods.TextureCache.RemoveTextureFromCache(this); } Debug.Assert(newRefCount >= 0); diff --git a/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs b/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs index d96d2b2a6..3689975d4 100644 --- a/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs +++ b/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs @@ -16,9 +16,9 @@ namespace Ryujinx.Graphics.Gpu.Image private const int SlotHigh = 16; private const int SlotMask = (1 << SlotHigh) - 1; - private GpuContext _context; + private readonly GpuContext _context; - private bool _isCompute; + private readonly bool _isCompute; private SamplerPool _samplerPool; @@ -27,10 +27,11 @@ namespace Ryujinx.Graphics.Gpu.Image private ulong _texturePoolAddress; private int _texturePoolMaximumId; - private TexturePoolCache _texturePoolCache; + private readonly GpuChannel _channel; + private readonly TexturePoolCache _texturePoolCache; - private TextureBindingInfo[][] _textureBindings; - private TextureBindingInfo[][] _imageBindings; + private readonly TextureBindingInfo[][] _textureBindings; + private readonly TextureBindingInfo[][] _imageBindings; private struct TextureStatePerStage { @@ -38,26 +39,28 @@ namespace Ryujinx.Graphics.Gpu.Image public ISampler Sampler; } - private TextureStatePerStage[][] _textureState; - private TextureStatePerStage[][] _imageState; + private readonly TextureStatePerStage[][] _textureState; + private readonly TextureStatePerStage[][] _imageState; private int _textureBufferIndex; private bool _rebind; - private float[] _scales; + private readonly float[] _scales; private bool _scaleChanged; /// /// Constructs a new instance of the texture bindings manager. /// /// The GPU context that the texture bindings manager belongs to - /// Texture pools cache used to get texture pools from + /// The GPU channel that the texture bindings manager belongs to + /// Texture pools cache used to get texture pools from /// True if the bindings manager is used for the compute engine - public TextureBindingsManager(GpuContext context, TexturePoolCache texturePoolCache, bool isCompute) + public TextureBindingsManager(GpuContext context, GpuChannel channel, TexturePoolCache poolCache, bool isCompute) { _context = context; - _texturePoolCache = texturePoolCache; + _channel = channel; + _texturePoolCache = poolCache; _isCompute = isCompute; int stages = isCompute ? 1 : Constants.ShaderStages; @@ -174,11 +177,9 @@ namespace Ryujinx.Graphics.Gpu.Image float scale = texture.ScaleFactor; - TextureManager manager = _context.Methods.TextureManager; - if (scale != 1) { - Texture activeTarget = manager.GetAnyRenderTarget(); + Texture activeTarget = _channel.TextureManager.GetAnyRenderTarget(); if (activeTarget != null && activeTarget.Info.Width / (float)texture.Info.Width == activeTarget.Info.Height / (float)texture.Info.Height) { @@ -319,7 +320,7 @@ namespace Ryujinx.Graphics.Gpu.Image // Ensure that the buffer texture is using the correct buffer as storage. // Buffers are frequently re-created to accomodate larger data, so we need to re-bind // to ensure we're not using a old buffer that was already deleted. - _context.Methods.BufferManager.SetBufferTextureStorage(hostTexture, texture.Range.GetSubRange(0).Address, texture.Size, bindingInfo, bindingInfo.Format, false); + _channel.BufferManager.SetBufferTextureStorage(hostTexture, texture.Range.GetSubRange(0).Address, texture.Size, bindingInfo, bindingInfo.Format, false); } Sampler sampler = _samplerPool.Get(samplerId); @@ -392,7 +393,7 @@ namespace Ryujinx.Graphics.Gpu.Image format = texture.Format; } - _context.Methods.BufferManager.SetBufferTextureStorage(hostTexture, texture.Range.GetSubRange(0).Address, texture.Size, bindingInfo, format, true); + _channel.BufferManager.SetBufferTextureStorage(hostTexture, texture.Range.GetSubRange(0).Address, texture.Size, bindingInfo, format, true); } else if (isStore) { @@ -454,10 +455,10 @@ namespace Ryujinx.Graphics.Gpu.Image /// The packed texture and sampler ID (the real texture handle) private int ReadPackedId(int stageIndex, int wordOffset, int textureBufferIndex, int samplerBufferIndex) { - var bufferManager = _context.Methods.BufferManager; + var bufferManager = _context.Methods.BufferCache; ulong textureBufferAddress = _isCompute - ? bufferManager.GetComputeUniformBufferAddress(textureBufferIndex) - : bufferManager.GetGraphicsUniformBufferAddress(stageIndex, textureBufferIndex); + ? _channel.BufferManager.GetComputeUniformBufferAddress(textureBufferIndex) + : _channel.BufferManager.GetGraphicsUniformBufferAddress(stageIndex, textureBufferIndex); int handle = _context.PhysicalMemory.Read(textureBufferAddress + (ulong)(wordOffset & HandleMask) * 4); @@ -470,8 +471,8 @@ namespace Ryujinx.Graphics.Gpu.Image if (wordOffset >> HandleHigh != 0) { ulong samplerBufferAddress = _isCompute - ? bufferManager.GetComputeUniformBufferAddress(samplerBufferIndex) - : bufferManager.GetGraphicsUniformBufferAddress(stageIndex, samplerBufferIndex); + ? _channel.BufferManager.GetComputeUniformBufferAddress(samplerBufferIndex) + : _channel.BufferManager.GetGraphicsUniformBufferAddress(stageIndex, samplerBufferIndex); handle |= _context.PhysicalMemory.Read(samplerBufferAddress + (ulong)((wordOffset >> HandleHigh) - 1) * 4); } @@ -513,6 +514,7 @@ namespace Ryujinx.Graphics.Gpu.Image public void Dispose() { _samplerPool?.Dispose(); + _texturePoolCache.Dispose(); } } } \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Image/TextureCache.cs b/Ryujinx.Graphics.Gpu/Image/TextureCache.cs new file mode 100644 index 000000000..24fa723a7 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Image/TextureCache.cs @@ -0,0 +1,967 @@ +using Ryujinx.Common; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Gpu.Image; +using Ryujinx.Graphics.Gpu.Memory; +using Ryujinx.Graphics.Gpu.State; +using Ryujinx.Graphics.Texture; +using Ryujinx.Memory.Range; +using System; + +namespace Ryujinx.Graphics.Gpu.Image +{ + /// + /// Texture cache. + /// + class TextureCache : IDisposable + { + private struct OverlapInfo + { + public TextureViewCompatibility Compatibility { get; } + public int FirstLayer { get; } + public int FirstLevel { get; } + + public OverlapInfo(TextureViewCompatibility compatibility, int firstLayer, int firstLevel) + { + Compatibility = compatibility; + FirstLayer = firstLayer; + FirstLevel = firstLevel; + } + } + + private const int OverlapsBufferInitialCapacity = 10; + private const int OverlapsBufferMaxCapacity = 10000; + + private readonly GpuContext _context; + + private readonly MultiRangeList _textures; + + private Texture[] _textureOverlaps; + private OverlapInfo[] _overlapInfo; + + private readonly AutoDeleteCache _cache; + + /// + /// Constructs a new instance of the texture manager. + /// + /// The GPU context that the texture manager belongs to + public TextureCache(GpuContext context) + { + _context = context; + + _textures = new MultiRangeList(); + + _textureOverlaps = new Texture[OverlapsBufferInitialCapacity]; + _overlapInfo = new OverlapInfo[OverlapsBufferInitialCapacity]; + + _cache = new AutoDeleteCache(); + } + + /// + /// Handles removal of textures written to a memory region being unmapped. + /// + /// Sender object + /// Event arguments + public void MemoryUnmappedHandler(object sender, UnmapEventArgs e) + { + Texture[] overlaps = new Texture[10]; + int overlapCount; + + lock (_textures) + { + overlapCount = _textures.FindOverlaps(_context.MemoryManager.Translate(e.Address), e.Size, ref overlaps); + } + + for (int i = 0; i < overlapCount; i++) + { + overlaps[i].Unmapped(); + } + } + + /// + /// Determines if a given texture is eligible for upscaling from its info. + /// + /// The texture info to check + /// True if eligible + private static bool IsUpscaleCompatible(TextureInfo info) + { + return (info.Target == Target.Texture2D || info.Target == Target.Texture2DArray) && !info.FormatInfo.IsCompressed && UpscaleSafeMode(info); + } + + /// + /// Determines if a given texture is "safe" for upscaling from its info. + /// Note that this is different from being compatible - this elilinates targets that would have detrimental effects when scaled. + /// + /// The texture info to check + /// True if safe + private static bool UpscaleSafeMode(TextureInfo info) + { + // While upscaling works for all targets defined by IsUpscaleCompatible, we additionally blacklist targets here that + // may have undesirable results (upscaling blur textures) or simply waste GPU resources (upscaling texture atlas). + + if (info.Levels > 3) + { + // Textures with more than 3 levels are likely to be game textures, rather than render textures. + // Small textures with full mips are likely to be removed by the next check. + return false; + } + + if (info.Width < 8 || info.Height < 8) + { + // Discount textures with small dimensions. + return false; + } + + if (!(info.FormatInfo.Format.IsDepthOrStencil() || info.FormatInfo.Components == 1)) + { + // Discount square textures that aren't depth-stencil like. (excludes game textures, cubemap faces, most 3D texture LUT, texture atlas) + // Detect if the texture is possibly square. Widths may be aligned, so to remove the uncertainty we align both the width and height. + + int widthAlignment = (info.IsLinear ? Constants.StrideAlignment : Constants.GobAlignment) / info.FormatInfo.BytesPerPixel; + + bool possiblySquare = BitUtils.AlignUp(info.Width, widthAlignment) == BitUtils.AlignUp(info.Height, widthAlignment); + + if (possiblySquare) + { + return false; + } + } + + int aspect = (int)Math.Round((info.Width / (float)info.Height) * 9); + if (aspect == 16 && info.Height < 360) + { + // Targets that are roughly 16:9 can only be rescaled if they're equal to or above 360p. (excludes blur and bloom textures) + return false; + } + + return true; + } + + /// + /// Tries to find an existing texture, or create a new one if not found. + /// + /// Copy texture to find or create + /// Offset to be added to the physical texture address + /// Format information of the copy texture + /// Indicates if the texture should be scaled from the start + /// A hint indicating the minimum used size for the texture + /// The texture + public Texture FindOrCreateTexture(CopyTexture copyTexture, ulong offset, FormatInfo formatInfo, bool preferScaling = true, Size? sizeHint = null) + { + int gobBlocksInY = copyTexture.MemoryLayout.UnpackGobBlocksInY(); + int gobBlocksInZ = copyTexture.MemoryLayout.UnpackGobBlocksInZ(); + + int width; + + if (copyTexture.LinearLayout) + { + width = copyTexture.Stride / formatInfo.BytesPerPixel; + } + else + { + width = copyTexture.Width; + } + + TextureInfo info = new TextureInfo( + copyTexture.Address.Pack() + offset, + width, + copyTexture.Height, + copyTexture.Depth, + 1, + 1, + 1, + copyTexture.Stride, + copyTexture.LinearLayout, + gobBlocksInY, + gobBlocksInZ, + 1, + Target.Texture2D, + formatInfo); + + TextureSearchFlags flags = TextureSearchFlags.ForCopy; + + if (preferScaling) + { + flags |= TextureSearchFlags.WithUpscale; + } + + Texture texture = FindOrCreateTexture(flags, info, 0, sizeHint); + + texture?.SynchronizeMemory(); + + return texture; + } + + /// + /// Tries to find an existing texture, or create a new one if not found. + /// + /// Color buffer texture to find or create + /// Number of samples in the X direction, for MSAA + /// Number of samples in the Y direction, for MSAA + /// A hint indicating the minimum used size for the texture + /// The texture + public Texture FindOrCreateTexture(RtColorState colorState, int samplesInX, int samplesInY, Size sizeHint) + { + bool isLinear = colorState.MemoryLayout.UnpackIsLinear(); + + int gobBlocksInY = colorState.MemoryLayout.UnpackGobBlocksInY(); + int gobBlocksInZ = colorState.MemoryLayout.UnpackGobBlocksInZ(); + + Target target; + + if (colorState.MemoryLayout.UnpackIsTarget3D()) + { + target = Target.Texture3D; + } + else if ((samplesInX | samplesInY) != 1) + { + target = colorState.Depth > 1 + ? Target.Texture2DMultisampleArray + : Target.Texture2DMultisample; + } + else + { + target = colorState.Depth > 1 + ? Target.Texture2DArray + : Target.Texture2D; + } + + FormatInfo formatInfo = colorState.Format.Convert(); + + int width, stride; + + // For linear textures, the width value is actually the stride. + // We can easily get the width by dividing the stride by the bpp, + // since the stride is the total number of bytes occupied by a + // line. The stride should also meet alignment constraints however, + // so the width we get here is the aligned width. + if (isLinear) + { + width = colorState.WidthOrStride / formatInfo.BytesPerPixel; + stride = colorState.WidthOrStride; + } + else + { + width = colorState.WidthOrStride; + stride = 0; + } + + TextureInfo info = new TextureInfo( + colorState.Address.Pack(), + width, + colorState.Height, + colorState.Depth, + 1, + samplesInX, + samplesInY, + stride, + isLinear, + gobBlocksInY, + gobBlocksInZ, + 1, + target, + formatInfo); + + int layerSize = !isLinear ? colorState.LayerSize * 4 : 0; + + Texture texture = FindOrCreateTexture(TextureSearchFlags.WithUpscale, info, layerSize, sizeHint); + + texture?.SynchronizeMemory(); + + return texture; + } + + /// + /// Tries to find an existing texture, or create a new one if not found. + /// + /// Depth-stencil buffer texture to find or create + /// Size of the depth-stencil texture + /// Number of samples in the X direction, for MSAA + /// Number of samples in the Y direction, for MSAA + /// A hint indicating the minimum used size for the texture + /// The texture + public Texture FindOrCreateTexture(RtDepthStencilState dsState, Size3D size, int samplesInX, int samplesInY, Size sizeHint) + { + int gobBlocksInY = dsState.MemoryLayout.UnpackGobBlocksInY(); + int gobBlocksInZ = dsState.MemoryLayout.UnpackGobBlocksInZ(); + + Target target = (samplesInX | samplesInY) != 1 + ? Target.Texture2DMultisample + : Target.Texture2D; + + FormatInfo formatInfo = dsState.Format.Convert(); + + TextureInfo info = new TextureInfo( + dsState.Address.Pack(), + size.Width, + size.Height, + size.Depth, + 1, + samplesInX, + samplesInY, + 0, + false, + gobBlocksInY, + gobBlocksInZ, + 1, + target, + formatInfo); + + Texture texture = FindOrCreateTexture(TextureSearchFlags.WithUpscale, info, dsState.LayerSize * 4, sizeHint); + + texture?.SynchronizeMemory(); + + return texture; + } + + /// + /// Tries to find an existing texture, or create a new one if not found. + /// + /// The texture search flags, defines texture comparison rules + /// Texture information of the texture to be found or created + /// Size in bytes of a single texture layer + /// A hint indicating the minimum used size for the texture + /// Optional ranges of physical memory where the texture data is located + /// The texture + public Texture FindOrCreateTexture(TextureSearchFlags flags, TextureInfo info, int layerSize = 0, Size? sizeHint = null, MultiRange? range = null) + { + bool isSamplerTexture = (flags & TextureSearchFlags.ForSampler) != 0; + + bool isScalable = IsUpscaleCompatible(info); + + TextureScaleMode scaleMode = TextureScaleMode.Blacklisted; + if (isScalable) + { + scaleMode = (flags & TextureSearchFlags.WithUpscale) != 0 ? TextureScaleMode.Scaled : TextureScaleMode.Eligible; + } + + ulong address; + + if (range != null) + { + address = range.Value.GetSubRange(0).Address; + } + else + { + address = _context.MemoryManager.Translate(info.GpuAddress); + + if (address == MemoryManager.PteUnmapped) + { + return null; + } + } + + int sameAddressOverlapsCount; + + lock (_textures) + { + // Try to find a perfect texture match, with the same address and parameters. + sameAddressOverlapsCount = _textures.FindOverlaps(address, ref _textureOverlaps); + } + + Texture texture = null; + + TextureMatchQuality bestQuality = TextureMatchQuality.NoMatch; + + for (int index = 0; index < sameAddressOverlapsCount; index++) + { + Texture overlap = _textureOverlaps[index]; + + TextureMatchQuality matchQuality = overlap.IsExactMatch(info, flags); + + if (matchQuality != TextureMatchQuality.NoMatch) + { + // If the parameters match, we need to make sure the texture is mapped to the same memory regions. + + // If a range of memory was supplied, just check if the ranges match. + if (range != null && !overlap.Range.Equals(range.Value)) + { + continue; + } + + // If no range was supplied, we can check if the GPU virtual address match. If they do, + // we know the textures are located at the same memory region. + // If they don't, it may still be mapped to the same physical region, so we + // do a more expensive check to tell if they are mapped into the same physical regions. + // If the GPU VA for the texture has ever been unmapped, then the range must be checked regardless. + if ((overlap.Info.GpuAddress != info.GpuAddress || overlap.ChangedMapping) && + !_context.MemoryManager.CompareRange(overlap.Range, info.GpuAddress)) + { + continue; + } + } + + if (matchQuality == TextureMatchQuality.Perfect) + { + texture = overlap; + break; + } + else if (matchQuality > bestQuality) + { + texture = overlap; + bestQuality = matchQuality; + } + } + + if (texture != null) + { + if (!isSamplerTexture) + { + // If not a sampler texture, it is managed by the auto delete + // cache, ensure that it is on the "top" of the list to avoid + // deletion. + _cache.Lift(texture); + } + + ChangeSizeIfNeeded(info, texture, isSamplerTexture, sizeHint); + + texture.SynchronizeMemory(); + + return texture; + } + + // Calculate texture sizes, used to find all overlapping textures. + SizeInfo sizeInfo = info.CalculateSizeInfo(layerSize); + + ulong size = (ulong)sizeInfo.TotalSize; + + if (range == null) + { + range = _context.MemoryManager.GetPhysicalRegions(info.GpuAddress, size); + } + + // Find view compatible matches. + int overlapsCount; + + lock (_textures) + { + overlapsCount = _textures.FindOverlaps(range.Value, ref _textureOverlaps); + } + + if (_overlapInfo.Length != _textureOverlaps.Length) + { + Array.Resize(ref _overlapInfo, _textureOverlaps.Length); + } + + // =============== Find Texture View of Existing Texture =============== + + int fullyCompatible = 0; + + // Evaluate compatibility of overlaps + + for (int index = 0; index < overlapsCount; index++) + { + Texture overlap = _textureOverlaps[index]; + TextureViewCompatibility overlapCompatibility = overlap.IsViewCompatible(info, range.Value, sizeInfo.LayerSize, out int firstLayer, out int firstLevel); + + if (overlapCompatibility == TextureViewCompatibility.Full) + { + if (overlap.IsView) + { + overlapCompatibility = TextureViewCompatibility.CopyOnly; + } + else + { + fullyCompatible++; + } + } + + _overlapInfo[index] = new OverlapInfo(overlapCompatibility, firstLayer, firstLevel); + } + + // Search through the overlaps to find a compatible view and establish any copy dependencies. + + for (int index = 0; index < overlapsCount; index++) + { + Texture overlap = _textureOverlaps[index]; + OverlapInfo oInfo = _overlapInfo[index]; + + if (oInfo.Compatibility == TextureViewCompatibility.Full) + { + TextureInfo adjInfo = AdjustSizes(overlap, info, oInfo.FirstLevel); + + if (!isSamplerTexture) + { + info = adjInfo; + } + + texture = overlap.CreateView(adjInfo, sizeInfo, range.Value, oInfo.FirstLayer, oInfo.FirstLevel); + + ChangeSizeIfNeeded(info, texture, isSamplerTexture, sizeHint); + + texture.SynchronizeMemory(); + break; + } + else if (oInfo.Compatibility == TextureViewCompatibility.CopyOnly && fullyCompatible == 0) + { + // Only copy compatible. If there's another choice for a FULLY compatible texture, choose that instead. + + texture = new Texture(_context, info, sizeInfo, range.Value, scaleMode); + texture.InitializeGroup(true, true); + texture.InitializeData(false, false); + + overlap.SynchronizeMemory(); + overlap.CreateCopyDependency(texture, oInfo.FirstLayer, oInfo.FirstLevel, true); + break; + } + } + + if (texture != null) + { + // This texture could be a view of multiple parent textures with different storages, even if it is a view. + // When a texture is created, make sure all possible dependencies to other textures are created as copies. + // (even if it could be fulfilled without a copy) + + for (int index = 0; index < overlapsCount; index++) + { + Texture overlap = _textureOverlaps[index]; + OverlapInfo oInfo = _overlapInfo[index]; + + if (oInfo.Compatibility != TextureViewCompatibility.Incompatible && overlap.Group != texture.Group) + { + overlap.SynchronizeMemory(); + overlap.CreateCopyDependency(texture, oInfo.FirstLayer, oInfo.FirstLevel, true); + } + } + + texture.SynchronizeMemory(); + } + + // =============== Create a New Texture =============== + + // No match, create a new texture. + if (texture == null) + { + texture = new Texture(_context, info, sizeInfo, range.Value, scaleMode); + + // Step 1: Find textures that are view compatible with the new texture. + // Any textures that are incompatible will contain garbage data, so they should be removed where possible. + + int viewCompatible = 0; + fullyCompatible = 0; + bool setData = isSamplerTexture || overlapsCount == 0 || flags.HasFlag(TextureSearchFlags.ForCopy); + + bool hasLayerViews = false; + bool hasMipViews = false; + + for (int index = 0; index < overlapsCount; index++) + { + Texture overlap = _textureOverlaps[index]; + bool overlapInCache = overlap.CacheNode != null; + + TextureViewCompatibility compatibility = texture.IsViewCompatible(overlap.Info, overlap.Range, overlap.LayerSize, out int firstLayer, out int firstLevel); + + if (overlap.IsView && compatibility == TextureViewCompatibility.Full) + { + compatibility = TextureViewCompatibility.CopyOnly; + } + + if (compatibility != TextureViewCompatibility.Incompatible) + { + if (compatibility == TextureViewCompatibility.Full) + { + if (viewCompatible == fullyCompatible) + { + _overlapInfo[viewCompatible] = new OverlapInfo(compatibility, firstLayer, firstLevel); + _textureOverlaps[viewCompatible++] = overlap; + } + else + { + // Swap overlaps so that the fully compatible views have priority. + + _overlapInfo[viewCompatible] = _overlapInfo[fullyCompatible]; + _textureOverlaps[viewCompatible++] = _textureOverlaps[fullyCompatible]; + + _overlapInfo[fullyCompatible] = new OverlapInfo(compatibility, firstLayer, firstLevel); + _textureOverlaps[fullyCompatible] = overlap; + } + fullyCompatible++; + } + else + { + _overlapInfo[viewCompatible] = new OverlapInfo(compatibility, firstLayer, firstLevel); + _textureOverlaps[viewCompatible++] = overlap; + } + + hasLayerViews |= overlap.Info.GetSlices() < texture.Info.GetSlices(); + hasMipViews |= overlap.Info.Levels < texture.Info.Levels; + } + else if (overlapInCache || !setData) + { + if (info.GobBlocksInZ > 1 && info.GobBlocksInZ == overlap.Info.GobBlocksInZ) + { + // Allow overlapping slices of 3D textures. Could be improved in future by making sure the textures don't overlap. + continue; + } + + // The overlap texture is going to contain garbage data after we draw, or is generally incompatible. + // If the texture cannot be entirely contained in the new address space, and one of its view children is compatible with us, + // it must be flushed before removal, so that the data is not lost. + + // If the texture was modified since its last use, then that data is probably meant to go into this texture. + // If the data has been modified by the CPU, then it also shouldn't be flushed. + bool modified = overlap.ConsumeModified(); + + bool flush = overlapInCache && !modified && !texture.Range.Contains(overlap.Range) && overlap.HasViewCompatibleChild(texture); + + setData |= modified || flush; + + if (overlapInCache) + { + _cache.Remove(overlap, flush); + } + } + } + + texture.InitializeGroup(hasLayerViews, hasMipViews); + + // We need to synchronize before copying the old view data to the texture, + // otherwise the copied data would be overwritten by a future synchronization. + texture.InitializeData(false, setData); + + for (int index = 0; index < viewCompatible; index++) + { + Texture overlap = _textureOverlaps[index]; + + OverlapInfo oInfo = _overlapInfo[index]; + + if (overlap.Group == texture.Group) + { + // If the texture group is equal, then this texture (or its parent) is already a view. + continue; + } + + TextureInfo overlapInfo = AdjustSizes(texture, overlap.Info, oInfo.FirstLevel); + + if (texture.ScaleFactor != overlap.ScaleFactor) + { + // A bit tricky, our new texture may need to contain an existing texture that is upscaled, but isn't itself. + // In that case, we prefer the higher scale only if our format is render-target-like, otherwise we scale the view down before copy. + + texture.PropagateScale(overlap); + } + + if (oInfo.Compatibility != TextureViewCompatibility.Full) + { + // Copy only compatibility, or target texture is already a view. + + overlap.SynchronizeMemory(); + texture.CreateCopyDependency(overlap, oInfo.FirstLayer, oInfo.FirstLevel, false); + } + else + { + TextureCreateInfo createInfo = GetCreateInfo(overlapInfo, _context.Capabilities, overlap.ScaleFactor); + + ITexture newView = texture.HostTexture.CreateView(createInfo, oInfo.FirstLayer, oInfo.FirstLevel); + + overlap.SynchronizeMemory(); + + overlap.HostTexture.CopyTo(newView, 0, 0); + + overlap.ReplaceView(texture, overlapInfo, newView, oInfo.FirstLayer, oInfo.FirstLevel); + } + } + + texture.SynchronizeMemory(); + } + + // Sampler textures are managed by the texture pool, all other textures + // are managed by the auto delete cache. + if (!isSamplerTexture) + { + _cache.Add(texture); + } + + lock (_textures) + { + _textures.Add(texture); + } + + ShrinkOverlapsBufferIfNeeded(); + + return texture; + } + + /// + /// Changes a texture's size to match the desired size for samplers, + /// or increases a texture's size to fit the region indicated by a size hint. + /// + /// The desired texture info + /// The texture to resize + /// True if the texture will be used for a sampler, false otherwise + /// A hint indicating the minimum used size for the texture + private void ChangeSizeIfNeeded(TextureInfo info, Texture texture, bool isSamplerTexture, Size? sizeHint) + { + if (isSamplerTexture) + { + // If this is used for sampling, the size must match, + // otherwise the shader would sample garbage data. + // To fix that, we create a new texture with the correct + // size, and copy the data from the old one to the new one. + + if (!TextureCompatibility.SizeMatches(texture.Info, info)) + { + texture.ChangeSize(info.Width, info.Height, info.DepthOrLayers); + } + } + else if (sizeHint != null) + { + // A size hint indicates that data will be used within that range, at least. + // If the texture is smaller than the size hint, it must be enlarged to meet it. + // The maximum size is provided by the requested info, which generally has an aligned size. + + int width = Math.Max(texture.Info.Width, Math.Min(sizeHint.Value.Width, info.Width)); + int height = Math.Max(texture.Info.Height, Math.Min(sizeHint.Value.Height, info.Height)); + + if (texture.Info.Width != width || texture.Info.Height != height) + { + texture.ChangeSize(width, height, info.DepthOrLayers); + } + } + } + + /// + /// Tries to find an existing texture matching the given buffer copy destination. If none is found, returns null. + /// + /// The texture information + /// The copy buffer parameters + /// The copy buffer swizzle + /// True if the texture has a linear layout, false otherwise + /// A matching texture, or null if there is no match + public Texture FindTexture(CopyBufferTexture tex, CopyBufferParams cbp, CopyBufferSwizzle swizzle, bool linear) + { + ulong address = _context.MemoryManager.Translate(cbp.DstAddress.Pack()); + + if (address == MemoryManager.PteUnmapped) + { + return null; + } + + int bpp = swizzle.UnpackDstComponentsCount() * swizzle.UnpackComponentSize(); + + int addressMatches = _textures.FindOverlaps(address, ref _textureOverlaps); + + for (int i = 0; i < addressMatches; i++) + { + Texture texture = _textureOverlaps[i]; + FormatInfo format = texture.Info.FormatInfo; + + if (texture.Info.DepthOrLayers > 1) + { + continue; + } + + bool match; + + if (linear) + { + // Size is not available for linear textures. Use the stride and end of the copy region instead. + + match = texture.Info.IsLinear && texture.Info.Stride == cbp.DstStride && tex.RegionY + cbp.YCount <= texture.Info.Height; + } + else + { + // Bpp may be a mismatch between the target texture and the param. + // Due to the way linear strided and block layouts work, widths can be multiplied by Bpp for comparison. + // Note: tex.Width is the aligned texture size. Prefer param.XCount, as the destination should be a texture with that exact size. + + bool sizeMatch = cbp.XCount * bpp == texture.Info.Width * format.BytesPerPixel && tex.Height == texture.Info.Height; + bool formatMatch = !texture.Info.IsLinear && + texture.Info.GobBlocksInY == tex.MemoryLayout.UnpackGobBlocksInY() && + texture.Info.GobBlocksInZ == tex.MemoryLayout.UnpackGobBlocksInZ(); + + match = sizeMatch && formatMatch; + } + + if (match) + { + return texture; + } + } + + return null; + } + + /// + /// Resizes the temporary buffer used for range list intersection results, if it has grown too much. + /// + private void ShrinkOverlapsBufferIfNeeded() + { + if (_textureOverlaps.Length > OverlapsBufferMaxCapacity) + { + Array.Resize(ref _textureOverlaps, OverlapsBufferMaxCapacity); + } + } + + /// + /// Adjusts the size of the texture information for a given mipmap level, + /// based on the size of a parent texture. + /// + /// The parent texture + /// The texture information to be adjusted + /// The first level of the texture view + /// The adjusted texture information with the new size + private static TextureInfo AdjustSizes(Texture parent, TextureInfo info, int firstLevel) + { + // When the texture is used as view of another texture, we must + // ensure that the sizes are valid, otherwise data uploads would fail + // (and the size wouldn't match the real size used on the host API). + // Given a parent texture from where the view is created, we have the + // following rules: + // - The view size must be equal to the parent size, divided by (2 ^ l), + // where l is the first mipmap level of the view. The division result must + // be rounded down, and the result must be clamped to 1. + // - If the parent format is compressed, and the view format isn't, the + // view size is calculated as above, but the width and height of the + // view must be also divided by the compressed format block width and height. + // - If the parent format is not compressed, and the view is, the view + // size is calculated as described on the first point, but the width and height + // of the view must be also multiplied by the block width and height. + int width = Math.Max(1, parent.Info.Width >> firstLevel); + int height = Math.Max(1, parent.Info.Height >> firstLevel); + + if (parent.Info.FormatInfo.IsCompressed && !info.FormatInfo.IsCompressed) + { + width = BitUtils.DivRoundUp(width, parent.Info.FormatInfo.BlockWidth); + height = BitUtils.DivRoundUp(height, parent.Info.FormatInfo.BlockHeight); + } + else if (!parent.Info.FormatInfo.IsCompressed && info.FormatInfo.IsCompressed) + { + width *= info.FormatInfo.BlockWidth; + height *= info.FormatInfo.BlockHeight; + } + + int depthOrLayers; + + if (info.Target == Target.Texture3D) + { + depthOrLayers = Math.Max(1, parent.Info.DepthOrLayers >> firstLevel); + } + else + { + depthOrLayers = info.DepthOrLayers; + } + + return new TextureInfo( + info.GpuAddress, + width, + height, + depthOrLayers, + info.Levels, + info.SamplesInX, + info.SamplesInY, + info.Stride, + info.IsLinear, + info.GobBlocksInY, + info.GobBlocksInZ, + info.GobBlocksInTileX, + info.Target, + info.FormatInfo, + info.DepthStencilMode, + info.SwizzleR, + info.SwizzleG, + info.SwizzleB, + info.SwizzleA); + } + + + /// + /// Gets a texture creation information from texture information. + /// This can be used to create new host textures. + /// + /// Texture information + /// GPU capabilities + /// Texture scale factor, to be applied to the texture size + /// The texture creation information + public static TextureCreateInfo GetCreateInfo(TextureInfo info, Capabilities caps, float scale) + { + FormatInfo formatInfo = TextureCompatibility.ToHostCompatibleFormat(info, caps); + + if (info.Target == Target.TextureBuffer) + { + // We assume that the host does not support signed normalized format + // (as is the case with OpenGL), so we just use a unsigned format. + // The shader will need the appropriate conversion code to compensate. + switch (formatInfo.Format) + { + case Format.R8Snorm: + formatInfo = new FormatInfo(Format.R8Sint, 1, 1, 1, 1); + break; + case Format.R16Snorm: + formatInfo = new FormatInfo(Format.R16Sint, 1, 1, 2, 1); + break; + case Format.R8G8Snorm: + formatInfo = new FormatInfo(Format.R8G8Sint, 1, 1, 2, 2); + break; + case Format.R16G16Snorm: + formatInfo = new FormatInfo(Format.R16G16Sint, 1, 1, 4, 2); + break; + case Format.R8G8B8A8Snorm: + formatInfo = new FormatInfo(Format.R8G8B8A8Sint, 1, 1, 4, 4); + break; + case Format.R16G16B16A16Snorm: + formatInfo = new FormatInfo(Format.R16G16B16A16Sint, 1, 1, 8, 4); + break; + } + } + + int width = info.Width / info.SamplesInX; + int height = info.Height / info.SamplesInY; + + int depth = info.GetDepth() * info.GetLayers(); + + if (scale != 1f) + { + width = (int)MathF.Ceiling(width * scale); + height = (int)MathF.Ceiling(height * scale); + } + + return new TextureCreateInfo( + width, + height, + depth, + info.Levels, + info.Samples, + formatInfo.BlockWidth, + formatInfo.BlockHeight, + formatInfo.BytesPerPixel, + formatInfo.Format, + info.DepthStencilMode, + info.Target, + info.SwizzleR, + info.SwizzleG, + info.SwizzleB, + info.SwizzleA); + } + + /// + /// Removes a texture from the cache. + /// + /// + /// This only removes the texture from the internal list, not from the auto-deletion cache. + /// It may still have live references after the removal. + /// + /// The texture to be removed + public void RemoveTextureFromCache(Texture texture) + { + lock (_textures) + { + _textures.Remove(texture); + } + } + + /// + /// Disposes all textures and samplers in the cache. + /// It's an error to use the texture cache after disposal. + /// + public void Dispose() + { + lock (_textures) + { + foreach (Texture texture in _textures) + { + texture.Dispose(); + } + } + } + } +} diff --git a/Ryujinx.Graphics.Gpu/Image/TextureManager.cs b/Ryujinx.Graphics.Gpu/Image/TextureManager.cs index 989dca7ad..74c9766a5 100644 --- a/Ryujinx.Graphics.Gpu/Image/TextureManager.cs +++ b/Ryujinx.Graphics.Gpu/Image/TextureManager.cs @@ -1,10 +1,5 @@ -using Ryujinx.Common; -using Ryujinx.Graphics.GAL; -using Ryujinx.Graphics.Gpu.Image; -using Ryujinx.Graphics.Gpu.Memory; +using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.Gpu.State; -using Ryujinx.Graphics.Texture; -using Ryujinx.Memory.Range; using System; namespace Ryujinx.Graphics.Gpu.Image @@ -14,23 +9,6 @@ namespace Ryujinx.Graphics.Gpu.Image /// class TextureManager : IDisposable { - private struct OverlapInfo - { - public TextureViewCompatibility Compatibility { get; } - public int FirstLayer { get; } - public int FirstLevel { get; } - - public OverlapInfo(TextureViewCompatibility compatibility, int firstLayer, int firstLevel) - { - Compatibility = compatibility; - FirstLayer = firstLayer; - FirstLevel = firstLevel; - } - } - - private const int OverlapsBufferInitialCapacity = 10; - private const int OverlapsBufferMaxCapacity = 10000; - private readonly GpuContext _context; private readonly TextureBindingsManager _cpBindingsManager; @@ -41,40 +19,27 @@ namespace Ryujinx.Graphics.Gpu.Image private Texture _rtDepthStencil; private ITexture _rtHostDs; - private readonly MultiRangeList _textures; - - private Texture[] _textureOverlaps; - private OverlapInfo[] _overlapInfo; - - private readonly AutoDeleteCache _cache; - /// /// The scaling factor applied to all currently bound render targets. /// public float RenderTargetScale { get; private set; } = 1f; /// - /// Constructs a new instance of the texture manager. + /// Creates a new instance of the texture manager. /// - /// The GPU context that the texture manager belongs to - public TextureManager(GpuContext context) + /// GPU context that the texture manager belongs to + /// GPU channel that the texture manager belongs to + public TextureManager(GpuContext context, GpuChannel channel) { _context = context; TexturePoolCache texturePoolCache = new TexturePoolCache(context); - _cpBindingsManager = new TextureBindingsManager(context, texturePoolCache, isCompute: true); - _gpBindingsManager = new TextureBindingsManager(context, texturePoolCache, isCompute: false); + _cpBindingsManager = new TextureBindingsManager(context, channel, texturePoolCache, isCompute: true); + _gpBindingsManager = new TextureBindingsManager(context, channel, texturePoolCache, isCompute: false); _rtColors = new Texture[Constants.TotalRenderTargets]; _rtHostColors = new ITexture[Constants.TotalRenderTargets]; - - _textures = new MultiRangeList(); - - _textureOverlaps = new Texture[OverlapsBufferInitialCapacity]; - _overlapInfo = new OverlapInfo[OverlapsBufferInitialCapacity]; - - _cache = new AutoDeleteCache(); } /// @@ -393,914 +358,38 @@ namespace Ryujinx.Graphics.Gpu.Image } /// - /// Determines if a given texture is eligible for upscaling from its info. + /// Forces all textures, samplers, images and render targets to be rebound the next time + /// CommitGraphicsBindings is called. /// - /// The texture info to check - /// True if eligible - public bool IsUpscaleCompatible(TextureInfo info) + public void Rebind() { - return (info.Target == Target.Texture2D || info.Target == Target.Texture2DArray) && !info.FormatInfo.IsCompressed && UpscaleSafeMode(info); + _gpBindingsManager.Rebind(); + + for (int index = 0; index < _rtHostColors.Length; index++) + { + _rtHostColors[index] = null; + } + + _rtHostDs = null; } /// - /// Determines if a given texture is "safe" for upscaling from its info. - /// Note that this is different from being compatible - this elilinates targets that would have detrimental effects when scaled. - /// - /// The texture info to check - /// True if safe - public bool UpscaleSafeMode(TextureInfo info) - { - // While upscaling works for all targets defined by IsUpscaleCompatible, we additionally blacklist targets here that - // may have undesirable results (upscaling blur textures) or simply waste GPU resources (upscaling texture atlas). - - if (info.Levels > 3) - { - // Textures with more than 3 levels are likely to be game textures, rather than render textures. - // Small textures with full mips are likely to be removed by the next check. - return false; - } - - if (info.Width < 8 || info.Height < 8) - { - // Discount textures with small dimensions. - return false; - } - - if (!(info.FormatInfo.Format.IsDepthOrStencil() || info.FormatInfo.Components == 1)) - { - // Discount square textures that aren't depth-stencil like. (excludes game textures, cubemap faces, most 3D texture LUT, texture atlas) - // Detect if the texture is possibly square. Widths may be aligned, so to remove the uncertainty we align both the width and height. - - int widthAlignment = (info.IsLinear ? Constants.StrideAlignment : Constants.GobAlignment) / info.FormatInfo.BytesPerPixel; - - bool possiblySquare = BitUtils.AlignUp(info.Width, widthAlignment) == BitUtils.AlignUp(info.Height, widthAlignment); - - if (possiblySquare) - { - return false; - } - } - - int aspect = (int)Math.Round((info.Width / (float)info.Height) * 9); - if (aspect == 16 && info.Height < 360) - { - // Targets that are roughly 16:9 can only be rescaled if they're equal to or above 360p. (excludes blur and bloom textures) - return false; - } - - return true; - } - - /// - /// Handles removal of textures written to a memory region being unmapped. - /// - /// Sender object - /// Event arguments - public void MemoryUnmappedHandler(object sender, UnmapEventArgs e) - { - Texture[] overlaps = new Texture[10]; - int overlapCount; - - lock (_textures) - { - overlapCount = _textures.FindOverlaps(_context.MemoryManager.Translate(e.Address), e.Size, ref overlaps); - } - - for (int i = 0; i < overlapCount; i++) - { - overlaps[i].Unmapped(); - } - } - - /// - /// Tries to find an existing texture, or create a new one if not found. - /// - /// Copy texture to find or create - /// Offset to be added to the physical texture address - /// Format information of the copy texture - /// Indicates if the texture should be scaled from the start - /// A hint indicating the minimum used size for the texture - /// The texture - public Texture FindOrCreateTexture(CopyTexture copyTexture, ulong offset, FormatInfo formatInfo, bool preferScaling = true, Size? sizeHint = null) - { - int gobBlocksInY = copyTexture.MemoryLayout.UnpackGobBlocksInY(); - int gobBlocksInZ = copyTexture.MemoryLayout.UnpackGobBlocksInZ(); - - int width; - - if (copyTexture.LinearLayout) - { - width = copyTexture.Stride / formatInfo.BytesPerPixel; - } - else - { - width = copyTexture.Width; - } - - TextureInfo info = new TextureInfo( - copyTexture.Address.Pack() + offset, - width, - copyTexture.Height, - copyTexture.Depth, - 1, - 1, - 1, - copyTexture.Stride, - copyTexture.LinearLayout, - gobBlocksInY, - gobBlocksInZ, - 1, - Target.Texture2D, - formatInfo); - - TextureSearchFlags flags = TextureSearchFlags.ForCopy; - - if (preferScaling) - { - flags |= TextureSearchFlags.WithUpscale; - } - - Texture texture = FindOrCreateTexture(flags, info, 0, sizeHint); - - texture?.SynchronizeMemory(); - - return texture; - } - - /// - /// Tries to find an existing texture, or create a new one if not found. - /// - /// Color buffer texture to find or create - /// Number of samples in the X direction, for MSAA - /// Number of samples in the Y direction, for MSAA - /// A hint indicating the minimum used size for the texture - /// The texture - public Texture FindOrCreateTexture(RtColorState colorState, int samplesInX, int samplesInY, Size sizeHint) - { - bool isLinear = colorState.MemoryLayout.UnpackIsLinear(); - - int gobBlocksInY = colorState.MemoryLayout.UnpackGobBlocksInY(); - int gobBlocksInZ = colorState.MemoryLayout.UnpackGobBlocksInZ(); - - Target target; - - if (colorState.MemoryLayout.UnpackIsTarget3D()) - { - target = Target.Texture3D; - } - else if ((samplesInX | samplesInY) != 1) - { - target = colorState.Depth > 1 - ? Target.Texture2DMultisampleArray - : Target.Texture2DMultisample; - } - else - { - target = colorState.Depth > 1 - ? Target.Texture2DArray - : Target.Texture2D; - } - - FormatInfo formatInfo = colorState.Format.Convert(); - - int width, stride; - - // For linear textures, the width value is actually the stride. - // We can easily get the width by dividing the stride by the bpp, - // since the stride is the total number of bytes occupied by a - // line. The stride should also meet alignment constraints however, - // so the width we get here is the aligned width. - if (isLinear) - { - width = colorState.WidthOrStride / formatInfo.BytesPerPixel; - stride = colorState.WidthOrStride; - } - else - { - width = colorState.WidthOrStride; - stride = 0; - } - - TextureInfo info = new TextureInfo( - colorState.Address.Pack(), - width, - colorState.Height, - colorState.Depth, - 1, - samplesInX, - samplesInY, - stride, - isLinear, - gobBlocksInY, - gobBlocksInZ, - 1, - target, - formatInfo); - - int layerSize = !isLinear ? colorState.LayerSize * 4 : 0; - - Texture texture = FindOrCreateTexture(TextureSearchFlags.WithUpscale, info, layerSize, sizeHint); - - texture?.SynchronizeMemory(); - - return texture; - } - - /// - /// Tries to find an existing texture, or create a new one if not found. - /// - /// Depth-stencil buffer texture to find or create - /// Size of the depth-stencil texture - /// Number of samples in the X direction, for MSAA - /// Number of samples in the Y direction, for MSAA - /// A hint indicating the minimum used size for the texture - /// The texture - public Texture FindOrCreateTexture(RtDepthStencilState dsState, Size3D size, int samplesInX, int samplesInY, Size sizeHint) - { - int gobBlocksInY = dsState.MemoryLayout.UnpackGobBlocksInY(); - int gobBlocksInZ = dsState.MemoryLayout.UnpackGobBlocksInZ(); - - Target target = (samplesInX | samplesInY) != 1 - ? Target.Texture2DMultisample - : Target.Texture2D; - - FormatInfo formatInfo = dsState.Format.Convert(); - - TextureInfo info = new TextureInfo( - dsState.Address.Pack(), - size.Width, - size.Height, - size.Depth, - 1, - samplesInX, - samplesInY, - 0, - false, - gobBlocksInY, - gobBlocksInZ, - 1, - target, - formatInfo); - - Texture texture = FindOrCreateTexture(TextureSearchFlags.WithUpscale, info, dsState.LayerSize * 4, sizeHint); - - texture?.SynchronizeMemory(); - - return texture; - } - - /// - /// Tries to find an existing texture, or create a new one if not found. - /// - /// The texture search flags, defines texture comparison rules - /// Texture information of the texture to be found or created - /// Size in bytes of a single texture layer - /// A hint indicating the minimum used size for the texture - /// Optional ranges of physical memory where the texture data is located - /// The texture - public Texture FindOrCreateTexture(TextureSearchFlags flags, TextureInfo info, int layerSize = 0, Size? sizeHint = null, MultiRange? range = null) - { - bool isSamplerTexture = (flags & TextureSearchFlags.ForSampler) != 0; - - bool isScalable = IsUpscaleCompatible(info); - - TextureScaleMode scaleMode = TextureScaleMode.Blacklisted; - if (isScalable) - { - scaleMode = (flags & TextureSearchFlags.WithUpscale) != 0 ? TextureScaleMode.Scaled : TextureScaleMode.Eligible; - } - - ulong address; - - if (range != null) - { - address = range.Value.GetSubRange(0).Address; - } - else - { - address = _context.MemoryManager.Translate(info.GpuAddress); - - if (address == MemoryManager.PteUnmapped) - { - return null; - } - } - - int sameAddressOverlapsCount; - - lock (_textures) - { - // Try to find a perfect texture match, with the same address and parameters. - sameAddressOverlapsCount = _textures.FindOverlaps(address, ref _textureOverlaps); - } - - Texture texture = null; - - TextureMatchQuality bestQuality = TextureMatchQuality.NoMatch; - - for (int index = 0; index < sameAddressOverlapsCount; index++) - { - Texture overlap = _textureOverlaps[index]; - - TextureMatchQuality matchQuality = overlap.IsExactMatch(info, flags); - - if (matchQuality != TextureMatchQuality.NoMatch) - { - // If the parameters match, we need to make sure the texture is mapped to the same memory regions. - - // If a range of memory was supplied, just check if the ranges match. - if (range != null && !overlap.Range.Equals(range.Value)) - { - continue; - } - - // If no range was supplied, we can check if the GPU virtual address match. If they do, - // we know the textures are located at the same memory region. - // If they don't, it may still be mapped to the same physical region, so we - // do a more expensive check to tell if they are mapped into the same physical regions. - // If the GPU VA for the texture has ever been unmapped, then the range must be checked regardless. - if ((overlap.Info.GpuAddress != info.GpuAddress || overlap.ChangedMapping) && - !_context.MemoryManager.CompareRange(overlap.Range, info.GpuAddress)) - { - continue; - } - } - - if (matchQuality == TextureMatchQuality.Perfect) - { - texture = overlap; - break; - } - else if (matchQuality > bestQuality) - { - texture = overlap; - bestQuality = matchQuality; - } - } - - if (texture != null) - { - if (!isSamplerTexture) - { - // If not a sampler texture, it is managed by the auto delete - // cache, ensure that it is on the "top" of the list to avoid - // deletion. - _cache.Lift(texture); - } - - ChangeSizeIfNeeded(info, texture, isSamplerTexture, sizeHint); - - texture.SynchronizeMemory(); - - return texture; - } - - // Calculate texture sizes, used to find all overlapping textures. - SizeInfo sizeInfo = info.CalculateSizeInfo(layerSize); - - ulong size = (ulong)sizeInfo.TotalSize; - - if (range == null) - { - range = _context.MemoryManager.GetPhysicalRegions(info.GpuAddress, size); - } - - // Find view compatible matches. - int overlapsCount; - - lock (_textures) - { - overlapsCount = _textures.FindOverlaps(range.Value, ref _textureOverlaps); - } - - if (_overlapInfo.Length != _textureOverlaps.Length) - { - Array.Resize(ref _overlapInfo, _textureOverlaps.Length); - } - - // =============== Find Texture View of Existing Texture =============== - - int fullyCompatible = 0; - - // Evaluate compatibility of overlaps - - for (int index = 0; index < overlapsCount; index++) - { - Texture overlap = _textureOverlaps[index]; - TextureViewCompatibility overlapCompatibility = overlap.IsViewCompatible(info, range.Value, sizeInfo.LayerSize, out int firstLayer, out int firstLevel); - - if (overlapCompatibility == TextureViewCompatibility.Full) - { - if (overlap.IsView) - { - overlapCompatibility = TextureViewCompatibility.CopyOnly; - } - else - { - fullyCompatible++; - } - } - - _overlapInfo[index] = new OverlapInfo(overlapCompatibility, firstLayer, firstLevel); - } - - // Search through the overlaps to find a compatible view and establish any copy dependencies. - - for (int index = 0; index < overlapsCount; index++) - { - Texture overlap = _textureOverlaps[index]; - OverlapInfo oInfo = _overlapInfo[index]; - - if (oInfo.Compatibility == TextureViewCompatibility.Full) - { - TextureInfo adjInfo = AdjustSizes(overlap, info, oInfo.FirstLevel); - - if (!isSamplerTexture) - { - info = adjInfo; - } - - texture = overlap.CreateView(adjInfo, sizeInfo, range.Value, oInfo.FirstLayer, oInfo.FirstLevel); - - ChangeSizeIfNeeded(info, texture, isSamplerTexture, sizeHint); - - texture.SynchronizeMemory(); - break; - } - else if (oInfo.Compatibility == TextureViewCompatibility.CopyOnly && fullyCompatible == 0) - { - // Only copy compatible. If there's another choice for a FULLY compatible texture, choose that instead. - - texture = new Texture(_context, info, sizeInfo, range.Value, scaleMode); - texture.InitializeGroup(true, true); - texture.InitializeData(false, false); - - overlap.SynchronizeMemory(); - overlap.CreateCopyDependency(texture, oInfo.FirstLayer, oInfo.FirstLevel, true); - break; - } - } - - if (texture != null) - { - // This texture could be a view of multiple parent textures with different storages, even if it is a view. - // When a texture is created, make sure all possible dependencies to other textures are created as copies. - // (even if it could be fulfilled without a copy) - - for (int index = 0; index < overlapsCount; index++) - { - Texture overlap = _textureOverlaps[index]; - OverlapInfo oInfo = _overlapInfo[index]; - - if (oInfo.Compatibility != TextureViewCompatibility.Incompatible && overlap.Group != texture.Group) - { - overlap.SynchronizeMemory(); - overlap.CreateCopyDependency(texture, oInfo.FirstLayer, oInfo.FirstLevel, true); - } - } - - texture.SynchronizeMemory(); - } - - // =============== Create a New Texture =============== - - // No match, create a new texture. - if (texture == null) - { - texture = new Texture(_context, info, sizeInfo, range.Value, scaleMode); - - // Step 1: Find textures that are view compatible with the new texture. - // Any textures that are incompatible will contain garbage data, so they should be removed where possible. - - int viewCompatible = 0; - fullyCompatible = 0; - bool setData = isSamplerTexture || overlapsCount == 0 || flags.HasFlag(TextureSearchFlags.ForCopy); - - bool hasLayerViews = false; - bool hasMipViews = false; - - for (int index = 0; index < overlapsCount; index++) - { - Texture overlap = _textureOverlaps[index]; - bool overlapInCache = overlap.CacheNode != null; - - TextureViewCompatibility compatibility = texture.IsViewCompatible(overlap.Info, overlap.Range, overlap.LayerSize, out int firstLayer, out int firstLevel); - - if (overlap.IsView && compatibility == TextureViewCompatibility.Full) - { - compatibility = TextureViewCompatibility.CopyOnly; - } - - if (compatibility != TextureViewCompatibility.Incompatible) - { - if (compatibility == TextureViewCompatibility.Full) - { - if (viewCompatible == fullyCompatible) - { - _overlapInfo[viewCompatible] = new OverlapInfo(compatibility, firstLayer, firstLevel); - _textureOverlaps[viewCompatible++] = overlap; - } - else - { - // Swap overlaps so that the fully compatible views have priority. - - _overlapInfo[viewCompatible] = _overlapInfo[fullyCompatible]; - _textureOverlaps[viewCompatible++] = _textureOverlaps[fullyCompatible]; - - _overlapInfo[fullyCompatible] = new OverlapInfo(compatibility, firstLayer, firstLevel); - _textureOverlaps[fullyCompatible] = overlap; - } - fullyCompatible++; - } - else - { - _overlapInfo[viewCompatible] = new OverlapInfo(compatibility, firstLayer, firstLevel); - _textureOverlaps[viewCompatible++] = overlap; - } - - hasLayerViews |= overlap.Info.GetSlices() < texture.Info.GetSlices(); - hasMipViews |= overlap.Info.Levels < texture.Info.Levels; - } - else if (overlapInCache || !setData) - { - if (info.GobBlocksInZ > 1 && info.GobBlocksInZ == overlap.Info.GobBlocksInZ) - { - // Allow overlapping slices of 3D textures. Could be improved in future by making sure the textures don't overlap. - continue; - } - - // The overlap texture is going to contain garbage data after we draw, or is generally incompatible. - // If the texture cannot be entirely contained in the new address space, and one of its view children is compatible with us, - // it must be flushed before removal, so that the data is not lost. - - // If the texture was modified since its last use, then that data is probably meant to go into this texture. - // If the data has been modified by the CPU, then it also shouldn't be flushed. - bool modified = overlap.ConsumeModified(); - - bool flush = overlapInCache && !modified && !texture.Range.Contains(overlap.Range) && overlap.HasViewCompatibleChild(texture); - - setData |= modified || flush; - - if (overlapInCache) - { - _cache.Remove(overlap, flush); - } - } - } - - texture.InitializeGroup(hasLayerViews, hasMipViews); - - // We need to synchronize before copying the old view data to the texture, - // otherwise the copied data would be overwritten by a future synchronization. - texture.InitializeData(false, setData); - - for (int index = 0; index < viewCompatible; index++) - { - Texture overlap = _textureOverlaps[index]; - - OverlapInfo oInfo = _overlapInfo[index]; - - if (overlap.Group == texture.Group) - { - // If the texture group is equal, then this texture (or its parent) is already a view. - continue; - } - - TextureInfo overlapInfo = AdjustSizes(texture, overlap.Info, oInfo.FirstLevel); - - if (texture.ScaleFactor != overlap.ScaleFactor) - { - // A bit tricky, our new texture may need to contain an existing texture that is upscaled, but isn't itself. - // In that case, we prefer the higher scale only if our format is render-target-like, otherwise we scale the view down before copy. - - texture.PropagateScale(overlap); - } - - if (oInfo.Compatibility != TextureViewCompatibility.Full) - { - // Copy only compatibility, or target texture is already a view. - - overlap.SynchronizeMemory(); - texture.CreateCopyDependency(overlap, oInfo.FirstLayer, oInfo.FirstLevel, false); - } - else - { - TextureCreateInfo createInfo = GetCreateInfo(overlapInfo, _context.Capabilities, overlap.ScaleFactor); - - ITexture newView = texture.HostTexture.CreateView(createInfo, oInfo.FirstLayer, oInfo.FirstLevel); - - overlap.SynchronizeMemory(); - - overlap.HostTexture.CopyTo(newView, 0, 0); - - overlap.ReplaceView(texture, overlapInfo, newView, oInfo.FirstLayer, oInfo.FirstLevel); - } - } - - texture.SynchronizeMemory(); - } - - // Sampler textures are managed by the texture pool, all other textures - // are managed by the auto delete cache. - if (!isSamplerTexture) - { - _cache.Add(texture); - } - - lock (_textures) - { - _textures.Add(texture); - } - - ShrinkOverlapsBufferIfNeeded(); - - return texture; - } - - /// - /// Changes a texture's size to match the desired size for samplers, - /// or increases a texture's size to fit the region indicated by a size hint. - /// - /// The desired texture info - /// The texture to resize - /// True if the texture will be used for a sampler, false otherwise - /// A hint indicating the minimum used size for the texture - private void ChangeSizeIfNeeded(TextureInfo info, Texture texture, bool isSamplerTexture, Size? sizeHint) - { - if (isSamplerTexture) - { - // If this is used for sampling, the size must match, - // otherwise the shader would sample garbage data. - // To fix that, we create a new texture with the correct - // size, and copy the data from the old one to the new one. - - if (!TextureCompatibility.SizeMatches(texture.Info, info)) - { - texture.ChangeSize(info.Width, info.Height, info.DepthOrLayers); - } - } - else if (sizeHint != null) - { - // A size hint indicates that data will be used within that range, at least. - // If the texture is smaller than the size hint, it must be enlarged to meet it. - // The maximum size is provided by the requested info, which generally has an aligned size. - - int width = Math.Max(texture.Info.Width, Math.Min(sizeHint.Value.Width, info.Width)); - int height = Math.Max(texture.Info.Height, Math.Min(sizeHint.Value.Height, info.Height)); - - if (texture.Info.Width != width || texture.Info.Height != height) - { - texture.ChangeSize(width, height, info.DepthOrLayers); - } - } - } - - /// - /// Tries to find an existing texture matching the given buffer copy destination. If none is found, returns null. - /// - /// The texture information - /// The copy buffer parameters - /// The copy buffer swizzle - /// True if the texture has a linear layout, false otherwise - /// A matching texture, or null if there is no match - public Texture FindTexture(CopyBufferTexture tex, CopyBufferParams cbp, CopyBufferSwizzle swizzle, bool linear) - { - ulong address = _context.MemoryManager.Translate(cbp.DstAddress.Pack()); - - if (address == MemoryManager.PteUnmapped) - { - return null; - } - - int bpp = swizzle.UnpackDstComponentsCount() * swizzle.UnpackComponentSize(); - - int addressMatches = _textures.FindOverlaps(address, ref _textureOverlaps); - - for (int i = 0; i < addressMatches; i++) - { - Texture texture = _textureOverlaps[i]; - FormatInfo format = texture.Info.FormatInfo; - - if (texture.Info.DepthOrLayers > 1) - { - continue; - } - - bool match; - - if (linear) - { - // Size is not available for linear textures. Use the stride and end of the copy region instead. - - match = texture.Info.IsLinear && texture.Info.Stride == cbp.DstStride && tex.RegionY + cbp.YCount <= texture.Info.Height; - } - else - { - // Bpp may be a mismatch between the target texture and the param. - // Due to the way linear strided and block layouts work, widths can be multiplied by Bpp for comparison. - // Note: tex.Width is the aligned texture size. Prefer param.XCount, as the destination should be a texture with that exact size. - - bool sizeMatch = cbp.XCount * bpp == texture.Info.Width * format.BytesPerPixel && tex.Height == texture.Info.Height; - bool formatMatch = !texture.Info.IsLinear && - texture.Info.GobBlocksInY == tex.MemoryLayout.UnpackGobBlocksInY() && - texture.Info.GobBlocksInZ == tex.MemoryLayout.UnpackGobBlocksInZ(); - - match = sizeMatch && formatMatch; - } - - if (match) - { - return texture; - } - } - - return null; - } - - /// - /// Resizes the temporary buffer used for range list intersection results, if it has grown too much. - /// - private void ShrinkOverlapsBufferIfNeeded() - { - if (_textureOverlaps.Length > OverlapsBufferMaxCapacity) - { - Array.Resize(ref _textureOverlaps, OverlapsBufferMaxCapacity); - } - } - - /// - /// Adjusts the size of the texture information for a given mipmap level, - /// based on the size of a parent texture. - /// - /// The parent texture - /// The texture information to be adjusted - /// The first level of the texture view - /// The adjusted texture information with the new size - private static TextureInfo AdjustSizes(Texture parent, TextureInfo info, int firstLevel) - { - // When the texture is used as view of another texture, we must - // ensure that the sizes are valid, otherwise data uploads would fail - // (and the size wouldn't match the real size used on the host API). - // Given a parent texture from where the view is created, we have the - // following rules: - // - The view size must be equal to the parent size, divided by (2 ^ l), - // where l is the first mipmap level of the view. The division result must - // be rounded down, and the result must be clamped to 1. - // - If the parent format is compressed, and the view format isn't, the - // view size is calculated as above, but the width and height of the - // view must be also divided by the compressed format block width and height. - // - If the parent format is not compressed, and the view is, the view - // size is calculated as described on the first point, but the width and height - // of the view must be also multiplied by the block width and height. - int width = Math.Max(1, parent.Info.Width >> firstLevel); - int height = Math.Max(1, parent.Info.Height >> firstLevel); - - if (parent.Info.FormatInfo.IsCompressed && !info.FormatInfo.IsCompressed) - { - width = BitUtils.DivRoundUp(width, parent.Info.FormatInfo.BlockWidth); - height = BitUtils.DivRoundUp(height, parent.Info.FormatInfo.BlockHeight); - } - else if (!parent.Info.FormatInfo.IsCompressed && info.FormatInfo.IsCompressed) - { - width *= info.FormatInfo.BlockWidth; - height *= info.FormatInfo.BlockHeight; - } - - int depthOrLayers; - - if (info.Target == Target.Texture3D) - { - depthOrLayers = Math.Max(1, parent.Info.DepthOrLayers >> firstLevel); - } - else - { - depthOrLayers = info.DepthOrLayers; - } - - return new TextureInfo( - info.GpuAddress, - width, - height, - depthOrLayers, - info.Levels, - info.SamplesInX, - info.SamplesInY, - info.Stride, - info.IsLinear, - info.GobBlocksInY, - info.GobBlocksInZ, - info.GobBlocksInTileX, - info.Target, - info.FormatInfo, - info.DepthStencilMode, - info.SwizzleR, - info.SwizzleG, - info.SwizzleB, - info.SwizzleA); - } - - - /// - /// Gets a texture creation information from texture information. - /// This can be used to create new host textures. - /// - /// Texture information - /// GPU capabilities - /// Texture scale factor, to be applied to the texture size - /// The texture creation information - public static TextureCreateInfo GetCreateInfo(TextureInfo info, Capabilities caps, float scale) - { - FormatInfo formatInfo = TextureCompatibility.ToHostCompatibleFormat(info, caps); - - if (info.Target == Target.TextureBuffer) - { - // We assume that the host does not support signed normalized format - // (as is the case with OpenGL), so we just use a unsigned format. - // The shader will need the appropriate conversion code to compensate. - switch (formatInfo.Format) - { - case Format.R8Snorm: - formatInfo = new FormatInfo(Format.R8Sint, 1, 1, 1, 1); - break; - case Format.R16Snorm: - formatInfo = new FormatInfo(Format.R16Sint, 1, 1, 2, 1); - break; - case Format.R8G8Snorm: - formatInfo = new FormatInfo(Format.R8G8Sint, 1, 1, 2, 2); - break; - case Format.R16G16Snorm: - formatInfo = new FormatInfo(Format.R16G16Sint, 1, 1, 4, 2); - break; - case Format.R8G8B8A8Snorm: - formatInfo = new FormatInfo(Format.R8G8B8A8Sint, 1, 1, 4, 4); - break; - case Format.R16G16B16A16Snorm: - formatInfo = new FormatInfo(Format.R16G16B16A16Sint, 1, 1, 8, 4); - break; - } - } - - int width = info.Width / info.SamplesInX; - int height = info.Height / info.SamplesInY; - - int depth = info.GetDepth() * info.GetLayers(); - - if (scale != 1f) - { - width = (int)MathF.Ceiling(width * scale); - height = (int)MathF.Ceiling(height * scale); - } - - return new TextureCreateInfo( - width, - height, - depth, - info.Levels, - info.Samples, - formatInfo.BlockWidth, - formatInfo.BlockHeight, - formatInfo.BytesPerPixel, - formatInfo.Format, - info.DepthStencilMode, - info.Target, - info.SwizzleR, - info.SwizzleG, - info.SwizzleB, - info.SwizzleA); - } - - /// - /// Removes a texture from the cache. - /// - /// - /// This only removes the texture from the internal list, not from the auto-deletion cache. - /// It may still have live references after the removal. - /// - /// The texture to be removed - public void RemoveTextureFromCache(Texture texture) - { - lock (_textures) - { - _textures.Remove(texture); - } - } - - /// - /// Disposes all textures and samplers in the cache. + /// Disposes the texture manager. /// It's an error to use the texture manager after disposal. /// public void Dispose() { - lock (_textures) - { - foreach (Texture texture in _textures) - { - texture.Dispose(); - } + _cpBindingsManager.Dispose(); + _gpBindingsManager.Dispose(); - _cpBindingsManager.Dispose(); - _gpBindingsManager.Dispose(); + for (int i = 0; i < _rtColors.Length; i++) + { + _rtColors[i]?.DecrementReferenceCount(); + _rtColors[i] = null; } + + _rtDepthStencil?.DecrementReferenceCount(); + _rtDepthStencil = null; } } } diff --git a/Ryujinx.Graphics.Gpu/Image/TexturePool.cs b/Ryujinx.Graphics.Gpu/Image/TexturePool.cs index 8f225f163..128dd89e5 100644 --- a/Ryujinx.Graphics.Gpu/Image/TexturePool.cs +++ b/Ryujinx.Graphics.Gpu/Image/TexturePool.cs @@ -57,7 +57,7 @@ namespace Ryujinx.Graphics.Gpu.Image ProcessDereferenceQueue(); - texture = Context.Methods.TextureManager.FindOrCreateTexture(TextureSearchFlags.ForSampler, info, layerSize); + texture = Context.Methods.TextureCache.FindOrCreateTexture(TextureSearchFlags.ForSampler, info, layerSize); // If this happens, then the texture address is invalid, we can't add it to the cache. if (texture == null) diff --git a/Ryujinx.Graphics.Gpu/Image/TexturePoolCache.cs b/Ryujinx.Graphics.Gpu/Image/TexturePoolCache.cs index 268cec389..c9eebf8bb 100644 --- a/Ryujinx.Graphics.Gpu/Image/TexturePoolCache.cs +++ b/Ryujinx.Graphics.Gpu/Image/TexturePoolCache.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; namespace Ryujinx.Graphics.Gpu.Image @@ -7,7 +8,7 @@ namespace Ryujinx.Graphics.Gpu.Image /// This can keep multiple texture pools, and return the current one as needed. /// It is useful for applications that uses multiple texture pools. /// - class TexturePoolCache + class TexturePoolCache : IDisposable { private const int MaxCapacity = 4; @@ -72,5 +73,19 @@ namespace Ryujinx.Graphics.Gpu.Image return pool; } + + /// + /// Disposes the texture pool cache. + /// It's an error to use the texture pool cache after disposal. + /// + public void Dispose() + { + foreach (TexturePool pool in _pools) + { + pool.Dispose(); + } + + _pools.Clear(); + } } } \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Memory/BufferCache.cs b/Ryujinx.Graphics.Gpu/Memory/BufferCache.cs new file mode 100644 index 000000000..eb2d08ae3 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Memory/BufferCache.cs @@ -0,0 +1,390 @@ +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Gpu.State; +using Ryujinx.Memory.Range; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Ryujinx.Graphics.Gpu.Memory +{ + /// + /// Buffer cache. + /// + class BufferCache : IDisposable + { + 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 readonly RangeList _buffers; + + private Buffer[] _bufferOverlaps; + + private readonly Dictionary _dirtyCache; + + public event Action NotifyBuffersModified; + + /// + /// Creates a new instance of the buffer manager. + /// + /// The GPU context that the buffer manager belongs to + public BufferCache(GpuContext context) + { + _context = context; + + _buffers = new RangeList(); + + _bufferOverlaps = new Buffer[OverlapsBufferInitialCapacity]; + + _dirtyCache = new Dictionary(); + } + + /// + /// 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 + public 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, _bufferOverlaps.Take(overlapsCount)); + + lock (_buffers) + { + _buffers.Add(newBuffer); + } + + for (int index = 0; index < overlapsCount; index++) + { + Buffer buffer = _bufferOverlaps[index]; + + int dstOffset = (int)(buffer.Address - newBuffer.Address); + + buffer.CopyTo(newBuffer, dstOffset); + newBuffer.InheritModifiedRanges(buffer); + + buffer.DisposeData(); + } + + newBuffer.SynchronizeMemory(address, endAddress - address); + + // Existing buffers were modified, we need to rebind everything. + NotifyBuffersModified?.Invoke(); + } + } + 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); + } + } + + /// + /// 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 + public 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 + public 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 + public 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(); + } + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs b/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs index 20fa1f3a9..e43cb3b3c 100644 --- a/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs +++ b/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs @@ -1,9 +1,7 @@ -using Ryujinx.Common; +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; @@ -14,26 +12,16 @@ namespace Ryujinx.Graphics.Gpu.Memory /// /// Buffer manager. /// - class BufferManager + class BufferManager : IDisposable { 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 readonly GpuContext _context; private IndexBuffer _indexBuffer; - private VertexBuffer[] _vertexBuffers; - private BufferBounds[] _transformFeedbackBuffers; - private List _bufferTextures; + private readonly VertexBuffer[] _vertexBuffers; + private readonly BufferBounds[] _transformFeedbackBuffers; + private readonly List _bufferTextures; /// /// Holds shader stage buffer state and binding information. @@ -94,10 +82,10 @@ namespace Ryujinx.Graphics.Gpu.Memory } } - private BuffersPerStage _cpStorageBuffers; - private BuffersPerStage _cpUniformBuffers; - private BuffersPerStage[] _gpStorageBuffers; - private BuffersPerStage[] _gpUniformBuffers; + private readonly BuffersPerStage _cpStorageBuffers; + private readonly BuffersPerStage _cpUniformBuffers; + private readonly BuffersPerStage[] _gpStorageBuffers; + private readonly BuffersPerStage[] _gpUniformBuffers; private int _cpStorageBufferBindings; private int _cpUniformBufferBindings; @@ -114,20 +102,14 @@ namespace Ryujinx.Graphics.Gpu.Memory private bool _rebind; - private Dictionary _dirtyCache; - /// /// Creates a new instance of the buffer manager. /// - /// The GPU context that the buffer manager belongs to + /// 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]; @@ -146,9 +128,10 @@ namespace Ryujinx.Graphics.Gpu.Memory _bufferTextures = new List(); - _dirtyCache = new Dictionary(); + context.Methods.BufferCache.NotifyBuffersModified += Rebind; } + /// /// Sets the memory range with the index buffer data, to be used for subsequent draw calls. /// @@ -157,11 +140,11 @@ namespace Ryujinx.Graphics.Gpu.Memory /// Type of each index buffer element public void SetIndexBuffer(ulong gpuVa, ulong size, IndexType type) { - ulong address = TranslateAndCreateBuffer(gpuVa, size); + ulong address = _context.Methods.BufferCache.TranslateAndCreateBuffer(gpuVa, size); _indexBuffer.Address = address; - _indexBuffer.Size = size; - _indexBuffer.Type = type; + _indexBuffer.Size = size; + _indexBuffer.Type = type; _indexBufferDirty = true; } @@ -188,11 +171,11 @@ namespace Ryujinx.Graphics.Gpu.Memory /// 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); + ulong address = _context.Methods.BufferCache.TranslateAndCreateBuffer(gpuVa, size); _vertexBuffers[index].Address = address; - _vertexBuffers[index].Size = size; - _vertexBuffers[index].Stride = stride; + _vertexBuffers[index].Size = size; + _vertexBuffers[index].Stride = stride; _vertexBuffers[index].Divisor = divisor; _vertexBuffersDirty = true; @@ -216,7 +199,7 @@ namespace Ryujinx.Graphics.Gpu.Memory /// Size in bytes of the transform feedback buffer public void SetTransformFeedbackBuffer(int index, ulong gpuVa, ulong size) { - ulong address = TranslateAndCreateBuffer(gpuVa, size); + ulong address = _context.Methods.BufferCache.TranslateAndCreateBuffer(gpuVa, size); _transformFeedbackBuffers[index] = new BufferBounds(address, size); _transformFeedbackBuffersDirty = true; @@ -236,7 +219,7 @@ namespace Ryujinx.Graphics.Gpu.Memory gpuVa = BitUtils.AlignDown(gpuVa, _context.Capabilities.StorageBufferOffsetAlignment); - ulong address = TranslateAndCreateBuffer(gpuVa, size); + ulong address = _context.Methods.BufferCache.TranslateAndCreateBuffer(gpuVa, size); _cpStorageBuffers.SetBounds(index, address, size, flags); } @@ -256,10 +239,10 @@ namespace Ryujinx.Graphics.Gpu.Memory gpuVa = BitUtils.AlignDown(gpuVa, _context.Capabilities.StorageBufferOffsetAlignment); - ulong address = TranslateAndCreateBuffer(gpuVa, size); + ulong address = _context.Methods.BufferCache.TranslateAndCreateBuffer(gpuVa, size); if (_gpStorageBuffers[stage].Buffers[index].Address != address || - _gpStorageBuffers[stage].Buffers[index].Size != size) + _gpStorageBuffers[stage].Buffers[index].Size != size) { _gpStorageBuffersDirty = true; } @@ -276,7 +259,7 @@ namespace Ryujinx.Graphics.Gpu.Memory /// Size in bytes of the storage buffer public void SetComputeUniformBuffer(int index, ulong gpuVa, ulong size) { - ulong address = TranslateAndCreateBuffer(gpuVa, size); + ulong address = _context.Methods.BufferCache.TranslateAndCreateBuffer(gpuVa, size); _cpUniformBuffers.SetBounds(index, address, size); } @@ -291,7 +274,7 @@ namespace Ryujinx.Graphics.Gpu.Memory /// Size in bytes of the storage buffer public void SetGraphicsUniformBuffer(int stage, int index, ulong gpuVa, ulong size) { - ulong address = TranslateAndCreateBuffer(gpuVa, size); + ulong address = _context.Methods.BufferCache.TranslateAndCreateBuffer(gpuVa, size); _gpUniformBuffers[stage].SetBounds(index, address, size); _gpUniformBuffersDirty = true; @@ -397,191 +380,6 @@ namespace Ryujinx.Graphics.Gpu.Memory 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, _bufferOverlaps.Take(overlapsCount)); - - lock (_buffers) - { - _buffers.Add(newBuffer); - } - - for (int index = 0; index < overlapsCount; index++) - { - Buffer buffer = _bufferOverlaps[index]; - - int dstOffset = (int)(buffer.Address - newBuffer.Address); - - buffer.CopyTo(newBuffer, dstOffset); - newBuffer.InheritModifiedRanges(buffer); - - buffer.DisposeData(); - } - - newBuffer.SynchronizeMemory(address, endAddress - address); - - // 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. @@ -624,7 +422,7 @@ namespace Ryujinx.Graphics.Gpu.Memory { // 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( + sRanges[bindingInfo.Binding] = _context.Methods.BufferCache.GetBufferRangeTillEnd( bounds.Address, bounds.Size, bounds.Flags.HasFlag(BufferUsageFlags.Write)); @@ -645,7 +443,7 @@ namespace Ryujinx.Graphics.Gpu.Memory if (bounds.Address != 0) { - uRanges[bindingInfo.Binding] = GetBufferRange(bounds.Address, bounds.Size); + uRanges[bindingInfo.Binding] = _context.Methods.BufferCache.GetBufferRange(bounds.Address, bounds.Size); } } @@ -654,7 +452,7 @@ namespace Ryujinx.Graphics.Gpu.Memory CommitBufferTextureBindings(); // Force rebind after doing compute work. - _rebind = true; + Rebind(); } /// @@ -666,7 +464,9 @@ namespace Ryujinx.Graphics.Gpu.Memory { foreach (var binding in _bufferTextures) { - binding.Texture.SetStorage(GetBufferRange(binding.Address, binding.Size, binding.BindingInfo.Flags.HasFlag(TextureUsageFlags.ImageStore))); + var isStore = binding.BindingInfo.Flags.HasFlag(TextureUsageFlags.ImageStore); + var range = _context.Methods.BufferCache.GetBufferRange(binding.Address, binding.Size, isStore); + binding.Texture.SetStorage(range); // The texture must be rebound to use the new storage if it was updated. @@ -696,14 +496,14 @@ namespace Ryujinx.Graphics.Gpu.Memory if (_indexBuffer.Address != 0) { - BufferRange buffer = GetBufferRange(_indexBuffer.Address, _indexBuffer.Size); + BufferRange buffer = _context.Methods.BufferCache.GetBufferRange(_indexBuffer.Address, _indexBuffer.Size); _context.Renderer.Pipeline.SetIndexBuffer(buffer, _indexBuffer.Type); } } else if (_indexBuffer.Address != 0) { - SynchronizeBufferRange(_indexBuffer.Address, _indexBuffer.Size); + _context.Methods.BufferCache.SynchronizeBufferRange(_indexBuffer.Address, _indexBuffer.Size); } uint vbEnableMask = _vertexBuffersEnableMask; @@ -723,7 +523,7 @@ namespace Ryujinx.Graphics.Gpu.Memory continue; } - BufferRange buffer = GetBufferRange(vb.Address, vb.Size); + BufferRange buffer = _context.Methods.BufferCache.GetBufferRange(vb.Address, vb.Size); vertexBuffers[index] = new VertexBufferDescriptor(buffer, vb.Stride, vb.Divisor); } @@ -741,7 +541,7 @@ namespace Ryujinx.Graphics.Gpu.Memory continue; } - SynchronizeBufferRange(vb.Address, vb.Size); + _context.Methods.BufferCache.SynchronizeBufferRange(vb.Address, vb.Size); } } @@ -761,7 +561,7 @@ namespace Ryujinx.Graphics.Gpu.Memory continue; } - tfbs[index] = GetBufferRange(tfb.Address, tfb.Size); + tfbs[index] = _context.Methods.BufferCache.GetBufferRange(tfb.Address, tfb.Size); } _context.Renderer.Pipeline.SetTransformFeedbackBuffers(tfbs); @@ -777,7 +577,7 @@ namespace Ryujinx.Graphics.Gpu.Memory continue; } - SynchronizeBufferRange(tfb.Address, tfb.Size); + _context.Methods.BufferCache.SynchronizeBufferRange(tfb.Address, tfb.Size); } } @@ -831,9 +631,10 @@ namespace Ryujinx.Graphics.Gpu.Memory if (bounds.Address != 0) { + var isWrite = bounds.Flags.HasFlag(BufferUsageFlags.Write); ranges[bindingInfo.Binding] = isStorage - ? GetBufferRangeTillEnd(bounds.Address, bounds.Size, bounds.Flags.HasFlag(BufferUsageFlags.Write)) - : GetBufferRange(bounds.Address, bounds.Size, bounds.Flags.HasFlag(BufferUsageFlags.Write)); + ? _context.Methods.BufferCache.GetBufferRangeTillEnd(bounds.Address, bounds.Size, isWrite) + : _context.Methods.BufferCache.GetBufferRange(bounds.Address, bounds.Size, isWrite); } } } @@ -869,7 +670,7 @@ namespace Ryujinx.Graphics.Gpu.Memory continue; } - SynchronizeBufferRange(bounds.Address, bounds.Size); + _context.Methods.BufferCache.SynchronizeBufferRange(bounds.Address, bounds.Size); } } } @@ -885,167 +686,26 @@ namespace Ryujinx.Graphics.Gpu.Memory /// 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); + _context.Methods.BufferCache.CreateBuffer(address, size); _bufferTextures.Add(new BufferTextureBinding(texture, address, size, bindingInfo, format, isImage)); } /// - /// Copy a buffer data from a given address to another. + /// Force all bound textures and images to be rebound the next time CommitBindings is called. /// - /// - /// 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) + public void Rebind() { - 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)); - } + _rebind = true; } /// - /// 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. + /// Disposes the buffer manager. + /// It is an error to use the buffer manager after disposal. /// public void Dispose() { - lock (_buffers) - { - foreach (Buffer buffer in _buffers) - { - buffer.Dispose(); - } - } + _context.Methods.BufferCache.NotifyBuffersModified -= Rebind; } } -} \ No newline at end of file +} diff --git a/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs b/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs index f5373bd6f..75ff037e1 100644 --- a/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs +++ b/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs @@ -129,8 +129,8 @@ namespace Ryujinx.Graphics.Gpu.Shader public uint QueryConstantBufferUse() { return _compute - ? _context.Methods.BufferManager.GetComputeUniformBufferUseMask() - : _context.Methods.BufferManager.GetGraphicsUniformBufferUseMask(_stageIndex); + ? _state.Channel.BufferManager.GetComputeUniformBufferUseMask() + : _state.Channel.BufferManager.GetGraphicsUniformBufferUseMask(_stageIndex); } /// @@ -190,11 +190,11 @@ namespace Ryujinx.Graphics.Gpu.Shader { if (_compute) { - return _context.Methods.TextureManager.GetComputeTextureDescriptor(_state, handle, cbufSlot); + return _state.Channel.TextureManager.GetComputeTextureDescriptor(_state, handle, cbufSlot); } else { - return _context.Methods.TextureManager.GetGraphicsTextureDescriptor(_state, _stageIndex, handle, cbufSlot); + return _state.Channel.TextureManager.GetGraphicsTextureDescriptor(_state, _stageIndex, handle, cbufSlot); } } diff --git a/Ryujinx.Graphics.Gpu/State/GpuState.cs b/Ryujinx.Graphics.Gpu/State/GpuState.cs index 16dad5c10..ff4d78296 100644 --- a/Ryujinx.Graphics.Gpu/State/GpuState.cs +++ b/Ryujinx.Graphics.Gpu/State/GpuState.cs @@ -1,4 +1,5 @@ -using System; +using Ryujinx.Graphics.Gpu.Image; +using System; using System.Runtime.InteropServices; namespace Ryujinx.Graphics.Gpu.State @@ -37,11 +38,19 @@ namespace Ryujinx.Graphics.Gpu.State /// public ShadowRamControl ShadowRamControl { get; set; } + /// + /// GPU channel for the sub-channel state. + /// + public GpuChannel Channel { get; } + /// /// Creates a new instance of the GPU state. /// - public GpuState() + /// Channel that the sub-channel state belongs to + public GpuState(GpuChannel channel) { + Channel = channel; + _memory = new int[RegistersCount]; _shadow = new int[RegistersCount]; @@ -221,6 +230,21 @@ namespace Ryujinx.Graphics.Gpu.State } } + /// + /// Forces a full host state update by marking all state as modified, + /// and also requests all GPU resources in use to be rebound. + /// + public void ForceAllDirty() + { + for (int index = 0; index < _registers.Length; index++) + { + _registers[index].Modified = true; + } + + Channel.BufferManager.Rebind(); + Channel.TextureManager.Rebind(); + } + /// /// Checks if a given register has been modified since the last call to this method. /// diff --git a/Ryujinx.Graphics.Gpu/Window.cs b/Ryujinx.Graphics.Gpu/Window.cs index ad70adfd0..28cc17ed5 100644 --- a/Ryujinx.Graphics.Gpu/Window.cs +++ b/Ryujinx.Graphics.Gpu/Window.cs @@ -174,7 +174,7 @@ namespace Ryujinx.Graphics.Gpu { pt.AcquireCallback(_context, pt.UserObj); - Texture texture = _context.Methods.TextureManager.FindOrCreateTexture(TextureSearchFlags.WithUpscale, pt.Info, 0, null, pt.Range); + Texture texture = _context.Methods.TextureCache.FindOrCreateTexture(TextureSearchFlags.WithUpscale, pt.Info, 0, null, pt.Range); texture.SynchronizeMemory(); diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/NvHostChannelDeviceFile.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/NvHostChannelDeviceFile.cs index 807e0c927..644fc8355 100644 --- a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/NvHostChannelDeviceFile.cs +++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/NvHostChannelDeviceFile.cs @@ -1,5 +1,5 @@ -using Ryujinx.Common.Collections; -using Ryujinx.Common.Logging; +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.Gpu; using Ryujinx.Graphics.Gpu.Memory; using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu; using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel.Types; @@ -24,7 +24,8 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel private readonly Switch _device; private readonly IVirtualMemoryManager _memory; - private NvMemoryAllocator _memoryAllocator; + private readonly NvMemoryAllocator _memoryAllocator; + private readonly GpuChannel _channel; public enum ResourcePolicy { @@ -42,12 +43,13 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel public NvHostChannelDeviceFile(ServiceCtx context, IVirtualMemoryManager memory, long owner) : base(context, owner) { - _device = context.Device; - _memory = memory; - _timeout = 3000; - _submitTimeout = 0; - _timeslice = 0; + _device = context.Device; + _memory = memory; + _timeout = 3000; + _submitTimeout = 0; + _timeslice = 0; _memoryAllocator = _device.MemoryAllocator; + _channel = _device.Gpu.CreateChannel(); ChannelSyncpoints = new uint[MaxModuleSyncpoint]; @@ -429,10 +431,10 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel if (header.Flags.HasFlag(SubmitGpfifoFlags.FenceWait) && !_device.System.HostSyncpoint.IsSyncpointExpired(header.Fence.Id, header.Fence.Value)) { - _device.Gpu.GPFifo.PushHostCommandBuffer(CreateWaitCommandBuffer(header.Fence)); + _channel.PushHostCommandBuffer(CreateWaitCommandBuffer(header.Fence)); } - _device.Gpu.GPFifo.PushEntries(entries); + _channel.PushEntries(entries); header.Fence.Id = _channelSyncpoint.Id; @@ -454,7 +456,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel if (header.Flags.HasFlag(SubmitGpfifoFlags.FenceIncrement)) { - _device.Gpu.GPFifo.PushHostCommandBuffer(CreateIncrementCommandBuffer(ref header.Fence, header.Flags)); + _channel.PushHostCommandBuffer(CreateIncrementCommandBuffer(ref header.Fence, header.Flags)); } header.Flags = SubmitGpfifoFlags.None; @@ -541,6 +543,9 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel return commandBuffer; } - public override void Close() { } + public override void Close() + { + _channel.Dispose(); + } } }