From 1623ab524f54901e154c8b644e2315985d5bd0a0 Mon Sep 17 00:00:00 2001 From: riperiperi Date: Mon, 8 Mar 2021 21:43:39 +0000 Subject: [PATCH] Improve Buffer Textures and flush Image Stores (#2088) * Improve Buffer Textures and flush Image Stores Fixes a number of issues with buffer textures: - Reworked Buffer Textures to create their buffers in the TextureManager, then bind them with the BufferManager later. - Fixes an issue where a buffer texture's buffer could be invalidated after it is bound, but before use. - Fixed width unpacking for large buffer textures. The width is now 32-bit rather than 16. - Force buffer textures to be rebound whenever any buffer is created, as using the handle id wasn't reliable, and the cost of binding isn't too high. Fixes vertex explosions and flickering animations in UE4 games. * Set ImageStore flag... for ImageStore. * Check the offset and size. --- Ryujinx.Graphics.Gpu/Engine/Compute.cs | 2 +- Ryujinx.Graphics.Gpu/Engine/Methods.cs | 2 +- .../Image/TextureBindingsManager.cs | 18 +++++- .../Image/TextureDescriptor.cs | 9 +++ Ryujinx.Graphics.Gpu/Image/TexturePool.cs | 5 +- Ryujinx.Graphics.Gpu/Memory/BufferManager.cs | 60 +++++++++++++------ .../Memory/BufferTextureBinding.cs | 60 +++++++++++++++++++ Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs | 2 +- .../Image/TextureBuffer.cs | 14 ++++- Ryujinx.Graphics.OpenGL/Renderer.cs | 6 +- .../Glsl/Instructions/InstGenMemory.cs | 3 + Ryujinx.Graphics.Shader/TextureUsageFlags.cs | 3 +- 12 files changed, 154 insertions(+), 30 deletions(-) create mode 100644 Ryujinx.Graphics.Gpu/Memory/BufferTextureBinding.cs diff --git a/Ryujinx.Graphics.Gpu/Engine/Compute.cs b/Ryujinx.Graphics.Gpu/Engine/Compute.cs index c7e059ba3..bcff59534 100644 --- a/Ryujinx.Graphics.Gpu/Engine/Compute.cs +++ b/Ryujinx.Graphics.Gpu/Engine/Compute.cs @@ -141,8 +141,8 @@ namespace Ryujinx.Graphics.Gpu.Engine TextureManager.SetComputeImages(imageBindings); - BufferManager.CommitComputeBindings(); TextureManager.CommitComputeBindings(); + BufferManager.CommitComputeBindings(); _context.Renderer.Pipeline.DispatchCompute( qmd.CtaRasterWidth, diff --git a/Ryujinx.Graphics.Gpu/Engine/Methods.cs b/Ryujinx.Graphics.Gpu/Engine/Methods.cs index 0731f1c2b..033311b2b 100644 --- a/Ryujinx.Graphics.Gpu/Engine/Methods.cs +++ b/Ryujinx.Graphics.Gpu/Engine/Methods.cs @@ -304,8 +304,8 @@ namespace Ryujinx.Graphics.Gpu.Engine { UpdateStorageBuffers(); - BufferManager.CommitGraphicsBindings(); TextureManager.CommitGraphicsBindings(); + BufferManager.CommitGraphicsBindings(); } /// diff --git a/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs b/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs index 173340b3b..7fea7ebe0 100644 --- a/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs +++ b/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs @@ -303,7 +303,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, _isCompute); + _context.Methods.BufferManager.SetBufferTextureStorage(hostTexture, texture.Range.GetSubRange(0).Address, texture.Size, bindingInfo, bindingInfo.Format, false); } Sampler sampler = _samplerPool.Get(samplerId); @@ -349,12 +349,26 @@ namespace Ryujinx.Graphics.Gpu.Image ITexture hostTexture = texture?.GetTargetTexture(bindingInfo.Target); + bool isStore = bindingInfo.Flags.HasFlag(TextureUsageFlags.ImageStore); + if (hostTexture != null && texture.Target == Target.TextureBuffer) { // 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, _isCompute); + + Format format = bindingInfo.Format; + + if (format == 0 && texture != null) + { + format = texture.Format; + } + + _context.Methods.BufferManager.SetBufferTextureStorage(hostTexture, texture.Range.GetSubRange(0).Address, texture.Size, bindingInfo, format, true); + } + else if (isStore) + { + texture?.SignalModified(); } if (_imageState[stageIndex][index].Texture != hostTexture || _rebind) diff --git a/Ryujinx.Graphics.Gpu/Image/TextureDescriptor.cs b/Ryujinx.Graphics.Gpu/Image/TextureDescriptor.cs index e85df136b..62862e741 100644 --- a/Ryujinx.Graphics.Gpu/Image/TextureDescriptor.cs +++ b/Ryujinx.Graphics.Gpu/Image/TextureDescriptor.cs @@ -153,6 +153,15 @@ namespace Ryujinx.Graphics.Gpu.Image return (int)(Word4 & 0xffff) + 1; } + /// + /// Unpack the width of a buffer texture. + /// + /// The texture width + public int UnpackBufferTextureWidth() + { + return (int)((Word4 & 0xffff) | (Word3 << 16)) + 1; + } + /// /// Unpacks the texture sRGB format flag. /// diff --git a/Ryujinx.Graphics.Gpu/Image/TexturePool.cs b/Ryujinx.Graphics.Gpu/Image/TexturePool.cs index eece2a79e..8f225f163 100644 --- a/Ryujinx.Graphics.Gpu/Image/TexturePool.cs +++ b/Ryujinx.Graphics.Gpu/Image/TexturePool.cs @@ -172,8 +172,6 @@ namespace Ryujinx.Graphics.Gpu.Image /// The texture information private TextureInfo GetInfo(TextureDescriptor descriptor, out int layerSize) { - int width = descriptor.UnpackWidth(); - int height = descriptor.UnpackHeight(); int depthOrLayers = descriptor.UnpackDepth(); int levels = descriptor.UnpackLevels(); @@ -190,6 +188,9 @@ namespace Ryujinx.Graphics.Gpu.Image Target target = descriptor.UnpackTextureTarget().Convert((samplesInX | samplesInY) != 1); + int width = target == Target.TextureBuffer ? descriptor.UnpackBufferTextureWidth() : descriptor.UnpackWidth(); + int height = descriptor.UnpackHeight(); + // We use 2D targets for 1D textures as that makes texture cache // management easier. We don't know the target for render target // and copies, so those would normally use 2D targets, which are diff --git a/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs b/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs index 08d52faa4..b2cd1ced7 100644 --- a/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs +++ b/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs @@ -1,9 +1,11 @@ using Ryujinx.Common; using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Gpu.Image; using Ryujinx.Graphics.Gpu.State; using Ryujinx.Graphics.Shader; using Ryujinx.Memory.Range; using System; +using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; @@ -31,6 +33,7 @@ namespace Ryujinx.Graphics.Gpu.Memory private IndexBuffer _indexBuffer; private VertexBuffer[] _vertexBuffers; private BufferBounds[] _transformFeedbackBuffers; + private List _bufferTextures; /// /// Holds shader stage buffer state and binding information. @@ -138,6 +141,8 @@ namespace Ryujinx.Graphics.Gpu.Memory _gpStorageBuffers[index] = new BuffersPerStage(Constants.TotalGpStorageBuffers); _gpUniformBuffers[index] = new BuffersPerStage(Constants.TotalGpUniformBuffers); } + + _bufferTextures = new List(); } /// @@ -620,10 +625,39 @@ namespace Ryujinx.Graphics.Gpu.Memory _context.Renderer.Pipeline.SetUniformBuffers(uRanges); + CommitBufferTextureBindings(); + // Force rebind after doing compute work. _rebind = true; } + /// + /// Commit any queued buffer texture bindings. + /// + private void CommitBufferTextureBindings() + { + if (_bufferTextures.Count > 0) + { + foreach (var binding in _bufferTextures) + { + binding.Texture.SetStorage(GetBufferRange(binding.Address, binding.Size, binding.BindingInfo.Flags.HasFlag(TextureUsageFlags.ImageStore))); + + // The texture must be rebound to use the new storage if it was updated. + + if (binding.IsImage) + { + _context.Renderer.Pipeline.SetImage(binding.BindingInfo.Binding, binding.Texture, binding.Format); + } + else + { + _context.Renderer.Pipeline.SetTexture(binding.BindingInfo.Binding, binding.Texture); + } + } + + _bufferTextures.Clear(); + } + } + /// /// Ensures that the graphics engine bindings are visible to the host GPU. /// Note: this actually performs the binding using the host graphics API. @@ -743,6 +777,8 @@ namespace Ryujinx.Graphics.Gpu.Memory UpdateBuffers(_gpUniformBuffers); } + CommitBufferTextureBindings(); + _rebind = false; } @@ -813,31 +849,19 @@ namespace Ryujinx.Graphics.Gpu.Memory } /// - /// Sets the buffer storage of a buffer texture. + /// Sets the buffer storage of a buffer texture. This will be bound when the buffer manager commits bindings. /// /// Buffer texture /// Address of the buffer in memory /// Size of the buffer in bytes - /// Indicates if the buffer texture belongs to the compute or graphics pipeline - public void SetBufferTextureStorage(ITexture texture, ulong address, ulong size, bool compute) + /// Binding info for the buffer texture + /// Format of the buffer texture + /// Whether the binding is for an image or a sampler + public void SetBufferTextureStorage(ITexture texture, ulong address, ulong size, TextureBindingInfo bindingInfo, Format format, bool isImage) { CreateBuffer(address, size); - if (_rebind) - { - // We probably had to modify existing buffers to create the texture buffer, - // so rebind everything to ensure we're using the new buffers for all bound resources. - if (compute) - { - CommitComputeBindings(); - } - else - { - CommitGraphicsBindings(); - } - } - - texture.SetStorage(GetBufferRange(address, size)); + _bufferTextures.Add(new BufferTextureBinding(texture, address, size, bindingInfo, format, isImage)); } /// diff --git a/Ryujinx.Graphics.Gpu/Memory/BufferTextureBinding.cs b/Ryujinx.Graphics.Gpu/Memory/BufferTextureBinding.cs new file mode 100644 index 000000000..cf0d225e8 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Memory/BufferTextureBinding.cs @@ -0,0 +1,60 @@ +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Gpu.Image; + +namespace Ryujinx.Graphics.Gpu.Memory +{ + /// + /// A buffer binding to apply to a buffer texture. + /// + struct BufferTextureBinding + { + /// + /// The buffer texture. + /// + public ITexture Texture { get; } + + /// + /// The base address of the buffer binding. + /// + public ulong Address { get; } + + /// + /// The size of the buffer binding in bytes. + /// + public ulong Size { get; } + + /// + /// The image or sampler binding info for the buffer texture. + /// + public TextureBindingInfo BindingInfo { get; } + + /// + /// The image format for the binding. + /// + public Format Format { get; } + + /// + /// Whether the binding is for an image or a sampler. + /// + public bool IsImage { get; } + + /// + /// Create a new buffer texture binding. + /// + /// Buffer texture + /// Base address + /// Size in bytes + /// Binding info + /// Binding format + /// Whether the binding is for an image or a sampler + public BufferTextureBinding(ITexture texture, ulong address, ulong size, TextureBindingInfo bindingInfo, Format format, bool isImage) + { + Texture = texture; + Address = address; + Size = size; + BindingInfo = bindingInfo; + Format = format; + IsImage = isImage; + } + } +} diff --git a/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs b/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs index bf89f29d6..768a58e7b 100644 --- a/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs +++ b/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs @@ -35,7 +35,7 @@ namespace Ryujinx.Graphics.Gpu.Shader /// /// Version of the codegen (to be changed when codegen or guest format change). /// - private const ulong ShaderCodeGenVersion = 1961; + private const ulong ShaderCodeGenVersion = 2088; // Progress reporting helpers private int _shaderCount; diff --git a/Ryujinx.Graphics.OpenGL/Image/TextureBuffer.cs b/Ryujinx.Graphics.OpenGL/Image/TextureBuffer.cs index 5607fb401..f49a06478 100644 --- a/Ryujinx.Graphics.OpenGL/Image/TextureBuffer.cs +++ b/Ryujinx.Graphics.OpenGL/Image/TextureBuffer.cs @@ -6,12 +6,17 @@ namespace Ryujinx.Graphics.OpenGL.Image { class TextureBuffer : TextureBase, ITexture { + private Renderer _renderer; private int _bufferOffset; private int _bufferSize; + private int _bufferCount; private BufferHandle _buffer; - public TextureBuffer(TextureCreateInfo info) : base(info) {} + public TextureBuffer(Renderer renderer, TextureCreateInfo info) : base(info) + { + _renderer = renderer; + } public void CopyTo(ITexture destination, int firstLayer, int firstLevel) { @@ -50,16 +55,19 @@ namespace Ryujinx.Graphics.OpenGL.Image public void SetStorage(BufferRange buffer) { - if (buffer.Handle == _buffer && + if (_buffer != BufferHandle.Null && buffer.Offset == _bufferOffset && - buffer.Size == _bufferSize) + buffer.Size == _bufferSize && + _renderer.BufferCount == _bufferCount) { + // Only rebind the buffer when more have been created. return; } _buffer = buffer.Handle; _bufferOffset = buffer.Offset; _bufferSize = buffer.Size; + _bufferCount = _renderer.BufferCount; Bind(0); diff --git a/Ryujinx.Graphics.OpenGL/Renderer.cs b/Ryujinx.Graphics.OpenGL/Renderer.cs index 4a3f51bfd..c3d083099 100644 --- a/Ryujinx.Graphics.OpenGL/Renderer.cs +++ b/Ryujinx.Graphics.OpenGL/Renderer.cs @@ -30,6 +30,8 @@ namespace Ryujinx.Graphics.OpenGL internal ResourcePool ResourcePool { get; } + internal int BufferCount { get; private set; } + public string GpuVendor { get; private set; } public string GpuRenderer { get; private set; } public string GpuVersion { get; private set; } @@ -52,6 +54,8 @@ namespace Ryujinx.Graphics.OpenGL public BufferHandle CreateBuffer(int size) { + BufferCount++; + return Buffer.Create(size); } @@ -69,7 +73,7 @@ namespace Ryujinx.Graphics.OpenGL { if (info.Target == Target.TextureBuffer) { - return new TextureBuffer(info); + return new TextureBuffer(this, info); } else { diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs index 3bfc06475..fc5b06b1a 100644 --- a/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs +++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs @@ -111,6 +111,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions if (texOp.Inst == Instruction.ImageStore) { + int texIndex = context.FindImageDescriptorIndex(texOp); + context.ImageDescriptors[texIndex] = context.ImageDescriptors[texIndex].SetFlag(TextureUsageFlags.ImageStore); + VariableType type = texOp.Format.GetComponentType(); string[] cElems = new string[4]; diff --git a/Ryujinx.Graphics.Shader/TextureUsageFlags.cs b/Ryujinx.Graphics.Shader/TextureUsageFlags.cs index 1f0243098..87be0d011 100644 --- a/Ryujinx.Graphics.Shader/TextureUsageFlags.cs +++ b/Ryujinx.Graphics.Shader/TextureUsageFlags.cs @@ -12,6 +12,7 @@ namespace Ryujinx.Graphics.Shader // Integer sampled textures must be noted for resolution scaling. ResScaleUnsupported = 1 << 0, - NeedsScaleValue = 1 << 1 + NeedsScaleValue = 1 << 1, + ImageStore = 1 << 2 } }