From d18abffa659214ccc4a2016c46f25111cf41303d Mon Sep 17 00:00:00 2001 From: gdkchan Date: Sat, 29 Jul 2023 00:42:08 -0300 Subject: [PATCH] Implement buffer texture alignment --- src/Ryujinx.Graphics.GAL/Capabilities.cs | 6 ++ .../Image/TextureBindingsManager.cs | 71 +++++++++++++- .../Memory/SupportBufferUpdater.cs | 32 +++++++ .../Shader/GpuAccessorBase.cs | 4 + src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs | 2 + src/Ryujinx.Graphics.Shader/IGpuAccessor.cs | 18 ++++ src/Ryujinx.Graphics.Shader/SupportBuffer.cs | 12 ++- .../Translation/Transforms/TexturePass.cs | 96 ++++++++++++++++++- src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs | 2 + 9 files changed, 238 insertions(+), 5 deletions(-) diff --git a/src/Ryujinx.Graphics.GAL/Capabilities.cs b/src/Ryujinx.Graphics.GAL/Capabilities.cs index 4271c3d18..8ef416bf1 100644 --- a/src/Ryujinx.Graphics.GAL/Capabilities.cs +++ b/src/Ryujinx.Graphics.GAL/Capabilities.cs @@ -32,6 +32,7 @@ namespace Ryujinx.Graphics.GAL public readonly bool SupportsGeometryShader; public readonly bool SupportsGeometryShaderPassthrough; public readonly bool SupportsTransformFeedback; + public readonly bool SupportsImageBufferPixelAlignment; public readonly bool SupportsImageLoadFormatted; public readonly bool SupportsLayerVertexTessellation; public readonly bool SupportsMismatchingViewFormat; @@ -43,6 +44,7 @@ namespace Ryujinx.Graphics.GAL public readonly bool SupportsShaderBarrierDivergence; public readonly bool SupportsShaderFloat64; public readonly bool SupportsShaderNonUniformIndexing; + public readonly bool SupportsTextureBufferPixelAlignment; public readonly bool SupportsTextureGatherOffsets; public readonly bool SupportsTextureShadowLod; public readonly bool SupportsVertexStoreAndAtomics; @@ -101,6 +103,7 @@ namespace Ryujinx.Graphics.GAL bool supportsGeometryShader, bool supportsGeometryShaderPassthrough, bool supportsTransformFeedback, + bool supportsImageBufferPixelAlignment, bool supportsImageLoadFormatted, bool supportsLayerVertexTessellation, bool supportsMismatchingViewFormat, @@ -112,6 +115,7 @@ namespace Ryujinx.Graphics.GAL bool supportsShaderBarrierDivergence, bool supportsShaderFloat64, bool supportsShaderNonUniformIndexing, + bool supportsTextureBufferPixelAlignment, bool supportsTextureGatherOffsets, bool supportsTextureShadowLod, bool supportsVertexStoreAndAtomics, @@ -164,6 +168,7 @@ namespace Ryujinx.Graphics.GAL SupportsGeometryShader = supportsGeometryShader; SupportsGeometryShaderPassthrough = supportsGeometryShaderPassthrough; SupportsTransformFeedback = supportsTransformFeedback; + SupportsImageBufferPixelAlignment = supportsImageBufferPixelAlignment; SupportsImageLoadFormatted = supportsImageLoadFormatted; SupportsLayerVertexTessellation = supportsLayerVertexTessellation; SupportsMismatchingViewFormat = supportsMismatchingViewFormat; @@ -175,6 +180,7 @@ namespace Ryujinx.Graphics.GAL SupportsShaderBarrierDivergence = supportsShaderBarrierDivergence; SupportsShaderFloat64 = supportsShaderFloat64; SupportsShaderNonUniformIndexing = supportsShaderNonUniformIndexing; + SupportsTextureBufferPixelAlignment = supportsTextureBufferPixelAlignment; SupportsTextureGatherOffsets = supportsTextureGatherOffsets; SupportsTextureShadowLod = supportsTextureShadowLod; SupportsVertexStoreAndAtomics = supportsVertexStoreAndAtomics; diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs index 22a980a79..24af2f3eb 100644 --- a/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs +++ b/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs @@ -4,6 +4,7 @@ using Ryujinx.Graphics.Gpu.Engine.Types; using Ryujinx.Graphics.Gpu.Memory; using Ryujinx.Graphics.Gpu.Shader; using Ryujinx.Graphics.Shader; +using Ryujinx.Memory.Range; using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -67,6 +68,9 @@ namespace Ryujinx.Graphics.Gpu.Image private int _lastFragmentTotal; + private readonly int _bufferTextureAlignment; + private readonly int _bufferImageAlignment; + /// /// Constructs a new instance of the texture bindings manager. /// @@ -108,6 +112,10 @@ namespace Ryujinx.Graphics.Gpu.Image } _textureCounts = []; + + int alignment = context.Capabilities.TextureBufferOffsetAlignment; + _bufferTextureAlignment = context.Capabilities.SupportsTextureBufferPixelAlignment ? 0 : alignment; + _bufferImageAlignment = context.Capabilities.SupportsImageBufferPixelAlignment ? 0 : alignment; } /// @@ -521,10 +529,12 @@ namespace Ryujinx.Graphics.Gpu.Image if (hostTexture != null && texture.Target == Target.TextureBuffer) { + MultiRange range = GetAlignedBufferTextureRange(texture, index, stageIndex, false); + // Ensure that the buffer texture is using the correct buffer as storage. // Buffers are frequently re-created to accommodate larger data, so we need to re-bind // to ensure we're not using a old buffer that was already deleted. - _channel.BufferManager.SetBufferTextureStorage(stage, hostTexture, texture.Range, bindingInfo, false); + _channel.BufferManager.SetBufferTextureStorage(stage, hostTexture, range, bindingInfo, false); // Cache is not used for buffer texture, it must always rebind. state.CachedTexture = null; @@ -659,7 +669,9 @@ namespace Ryujinx.Graphics.Gpu.Image // Buffers are frequently re-created to accommodate larger data, so we need to re-bind // to ensure we're not using a old buffer that was already deleted. - _channel.BufferManager.SetBufferTextureStorage(stage, hostTexture, texture.Range, bindingInfo, true); + MultiRange range = GetAlignedBufferTextureRange(texture, index, stageIndex, true); + + _channel.BufferManager.SetBufferTextureStorage(stage, hostTexture, range, bindingInfo, true); // Cache is not used for buffer texture, it must always rebind. state.CachedTexture = null; @@ -692,6 +704,61 @@ namespace Ryujinx.Graphics.Gpu.Image return specStateMatches; } + /// + /// Gets the aligned address of the texture according to the host requirements. + /// + /// Texture to have its address aligned + /// Index of the texture in the shader + /// Index of the shader stage + /// True if the texture is bound as image, false for sampled textures + /// Aligned address and size of the buffer texture + private MultiRange GetAlignedBufferTextureRange(Texture texture, int index, int stageIndex, bool isImage) + { + MultiRange range = texture.Range; + int alignment = isImage ? _bufferImageAlignment : _bufferTextureAlignment; + + if (alignment != 0) + { + MemoryRange firstRange = range.GetSubRange(0); + ulong misalign = firstRange.Address & ((ulong)alignment - 1); + int offset = misalign != 0 ? (int)misalign / texture.Info.FormatInfo.BytesPerPixel : 0; + + if (misalign != 0) + { + firstRange = new MemoryRange(firstRange.Address - misalign, firstRange.Size + misalign); + + if (range.Count > 1) + { + MemoryRange[] ranges = new MemoryRange[range.Count]; + + ranges[0] = firstRange; + + for (int i = 1; i < range.Count; i++) + { + ranges[i] = range.GetSubRange(i); + } + + range = new MultiRange(ranges); + } + else + { + range = new MultiRange(firstRange.Address, firstRange.Size); + } + } + + if (isImage) + { + _context.SupportBufferUpdater.UpdateBufferImageOffset(stageIndex, index, offset); + } + else + { + _context.SupportBufferUpdater.UpdateBufferTextureOffset(stageIndex, index, offset); + } + } + + return range; + } + /// /// Gets the texture descriptor for a given texture handle. /// diff --git a/src/Ryujinx.Graphics.Gpu/Memory/SupportBufferUpdater.cs b/src/Ryujinx.Graphics.Gpu/Memory/SupportBufferUpdater.cs index 6eae99afb..6e35faccc 100644 --- a/src/Ryujinx.Graphics.Gpu/Memory/SupportBufferUpdater.cs +++ b/src/Ryujinx.Graphics.Gpu/Memory/SupportBufferUpdater.cs @@ -129,6 +129,38 @@ namespace Ryujinx.Graphics.Gpu.Memory } } + /// + /// Updates the offset used for accessing buffer textures that are not aligned to the host requirements. + /// + /// Index of the shader stage where the texture is used + /// Index of the texture binding in the shader + /// Offset for the misaligned part of the address + public void UpdateBufferTextureOffset(int stageIndex, int bindingIndex, int offset) + { + if (_data.BufferTextureOffset[stageIndex][bindingIndex].X != offset) + { + _data.BufferTextureOffset[stageIndex][bindingIndex].X = offset; + int index = stageIndex * SupportBuffer.TextureCount + bindingIndex; + MarkDirty(SupportBuffer.BufferTextureOffsetOffset + index * sizeof(int) * 4, sizeof(int)); + } + } + + /// + /// Updates the offset used for accessing buffer images that are not aligned to the host requirements. + /// + /// Index of the shader stage where the image is used + /// Index of the image binding in the shader + /// Offset for the misaligned part of the address + public void UpdateBufferImageOffset(int stageIndex, int bindingIndex, int offset) + { + if (_data.BufferTextureOffset[stageIndex][bindingIndex].Y != offset) + { + _data.BufferTextureOffset[stageIndex][bindingIndex].Y = offset; + int index = stageIndex * SupportBuffer.TextureCount + bindingIndex; + MarkDirty(SupportBuffer.BufferTextureOffsetOffset + index * sizeof(int) * 4 + sizeof(int), sizeof(int)); + } + } + /// /// Sets whether the format of a given render target is a BGRA format. /// diff --git a/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs b/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs index 2f8c329e5..e26cfc872 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs @@ -207,6 +207,10 @@ namespace Ryujinx.Graphics.Gpu.Shader public bool QueryHostSupportsBgraFormat() => _context.Capabilities.SupportsBgraFormat; + public bool QueryHostSupportsBufferImagePixelAlignment() => _context.Capabilities.SupportsImageBufferPixelAlignment; + + public bool QueryHostSupportsBufferTexturePixelAlignment() => _context.Capabilities.SupportsTextureBufferPixelAlignment; + public bool QueryHostSupportsFragmentShaderInterlock() => _context.Capabilities.SupportsFragmentShaderInterlock; public bool QueryHostSupportsFragmentShaderOrderingIntel() => _context.Capabilities.SupportsFragmentShaderOrderingIntel; diff --git a/src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs b/src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs index ad5847a29..955fab713 100644 --- a/src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs +++ b/src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs @@ -174,6 +174,7 @@ namespace Ryujinx.Graphics.OpenGL supportsGeometryShader: true, supportsGeometryShaderPassthrough: HwCapabilities.SupportsGeometryShaderPassthrough, supportsTransformFeedback: true, + supportsImageBufferPixelAlignment: false, supportsImageLoadFormatted: HwCapabilities.SupportsImageLoadFormatted, supportsLayerVertexTessellation: HwCapabilities.SupportsShaderViewportLayerArray, supportsMismatchingViewFormat: HwCapabilities.SupportsMismatchingViewFormat, @@ -185,6 +186,7 @@ namespace Ryujinx.Graphics.OpenGL supportsShaderBarrierDivergence: !(intelWindows || intelUnix), supportsShaderFloat64: true, supportsShaderNonUniformIndexing: false, + supportsTextureBufferPixelAlignment: false, supportsTextureGatherOffsets: true, supportsTextureShadowLod: HwCapabilities.SupportsTextureShadowLod, supportsVertexStoreAndAtomics: true, diff --git a/src/Ryujinx.Graphics.Shader/IGpuAccessor.cs b/src/Ryujinx.Graphics.Shader/IGpuAccessor.cs index 6635b82d3..366e42615 100644 --- a/src/Ryujinx.Graphics.Shader/IGpuAccessor.cs +++ b/src/Ryujinx.Graphics.Shader/IGpuAccessor.cs @@ -234,6 +234,24 @@ namespace Ryujinx.Graphics.Shader return true; } + /// + /// Queries host support for buffer image access with the buffer offset aligned to a single pixel rather than a fixed alignment. + /// + /// True if the host supports buffer image access with pixel alignment, false otherwise + bool QueryHostSupportsBufferImagePixelAlignment() + { + return true; + } + + /// + /// Queries host support for buffer texture access with the buffer offset aligned to a single pixel rather than a fixed alignment. + /// + /// True if the host supports buffer texture access with pixel alignment, false otherwise + bool QueryHostSupportsBufferTexturePixelAlignment() + { + return true; + } + /// /// Queries host support for fragment shader ordering critical sections on the shader code. /// diff --git a/src/Ryujinx.Graphics.Shader/SupportBuffer.cs b/src/Ryujinx.Graphics.Shader/SupportBuffer.cs index fb624d624..ea25d6f02 100644 --- a/src/Ryujinx.Graphics.Shader/SupportBuffer.cs +++ b/src/Ryujinx.Graphics.Shader/SupportBuffer.cs @@ -24,6 +24,7 @@ namespace Ryujinx.Graphics.Shader RenderScale, TfeOffset, TfeVertexCount, + BufferTextureOffset, } public struct SupportBuffer @@ -42,10 +43,13 @@ namespace Ryujinx.Graphics.Shader public static readonly int ComputeRenderScaleOffset; public static readonly int TfeOffsetOffset; public static readonly int TfeVertexCountOffset; + public static readonly int BufferTextureOffsetOffset; public const int FragmentIsBgraCount = 8; + public const int TextureCount = 64; + // One for the render target, 64 for the textures, and 8 for the images. - public const int RenderScaleMaxCount = 1 + 64 + 8; + public const int RenderScaleMaxCount = 1 + TextureCount + 8; private static int OffsetOf(ref SupportBuffer storage, ref T target) { @@ -68,6 +72,7 @@ namespace Ryujinx.Graphics.Shader ComputeRenderScaleOffset = GraphicsRenderScaleOffset + FieldSize; TfeOffsetOffset = OffsetOf(ref instance, ref instance.TfeOffset); TfeVertexCountOffset = OffsetOf(ref instance, ref instance.TfeVertexCount); + BufferTextureOffsetOffset = OffsetOf(ref instance, ref instance.BufferTextureOffset); } internal static StructureType GetStructureType() @@ -80,7 +85,8 @@ namespace Ryujinx.Graphics.Shader new StructureField(AggregateType.S32, "frag_scale_count"), new StructureField(AggregateType.Array | AggregateType.FP32, "render_scale", RenderScaleMaxCount), new StructureField(AggregateType.Vector4 | AggregateType.S32, "tfe_offset"), - new StructureField(AggregateType.S32, "tfe_vertex_count") + new StructureField(AggregateType.S32, "tfe_vertex_count"), + new StructureField(AggregateType.Array | AggregateType.Vector2 | AggregateType.S32, "buffer_texture_offset") ]); } @@ -95,5 +101,7 @@ namespace Ryujinx.Graphics.Shader public Vector4 TfeOffset; public Vector4 TfeVertexCount; + + public Array5>> BufferTextureOffset; } } diff --git a/src/Ryujinx.Graphics.Shader/Translation/Transforms/TexturePass.cs b/src/Ryujinx.Graphics.Shader/Translation/Transforms/TexturePass.cs index e0eefc7ea..235fd9a1a 100644 --- a/src/Ryujinx.Graphics.Shader/Translation/Transforms/TexturePass.cs +++ b/src/Ryujinx.Graphics.Shader/Translation/Transforms/TexturePass.cs @@ -1,4 +1,5 @@ using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.Translation.Optimizations; using System.Collections.Generic; using System.Linq; using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; @@ -27,7 +28,19 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms if (texOp.Type == SamplerType.TextureBuffer && !context.GpuAccessor.QueryHostSupportsSnormBufferTextureFormat()) { - node = InsertSnormNormalization(node, context.ResourceManager, context.GpuAccessor); + if (!context.GpuAccessor.QueryHostSupportsSnormBufferTextureFormat()) + { + node = InsertSnormNormalization(node, context.ResourceManager, context.GpuAccessor); + } + + if (!context.GpuAccessor.QueryHostSupportsBufferTexturePixelAlignment()) + { + node = InsertCoordOffset(node, context.ResourceManager, context.Stage, isImage: false); + } + } + else if (!context.GpuAccessor.QueryHostSupportsBufferTexturePixelAlignment() && texOp.Inst.IsImage()) + { + node = InsertCoordOffset(node, context.ResourceManager, context.Stage, isImage: true); } } } @@ -278,6 +291,87 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms return node; } + private static LinkedListNode InsertCoordOffset(LinkedListNode node, ResourceManager resourceManager, ShaderStage stage, bool isImage) + { + // Some GPUs have fixed alignment requirements for buffer textures. + // For those cases, we bind the aligned buffer offset, and apply the remaining offset on the shader. + + TextureOperation texOp = (TextureOperation)node.Value; + + if ((texOp.Type & SamplerType.Mask) != SamplerType.TextureBuffer) + { + return node; + } + + Operand[] sources = new Operand[texOp.SourcesCount]; + + for (int i = 0; i < texOp.SourcesCount; i++) + { + sources[i] = texOp.GetSource(i); + } + + bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; + bool isIndexed = resourceManager.IsArrayOfTexturesOrImages(texOp.Binding, isImage); + + int coordsIndex = isBindless || isIndexed ? 1 : 0; + + Operand[] dests = new Operand[texOp.DestsCount]; + + for (int i = 0; i < texOp.DestsCount; i++) + { + dests[i] = texOp.GetDest(i); + } + + LinkedListNode oldNode = node; + + Operand source = sources[coordsIndex]; + Operand offset = Local(); + Operand coordPlusOffset = Local(); + + int stageIndex = stage switch + { + ShaderStage.TessellationControl => 1, + ShaderStage.TessellationEvaluation => 2, + ShaderStage.Geometry => 3, + ShaderStage.Fragment => 4, + _ => 0, + }; + + int bindingIndex = isImage + ? resourceManager.FindImageDescriptorIndex(texOp.Binding) + : resourceManager.FindTextureDescriptorIndex(texOp.Binding); + + node.List.AddBefore(node, new Operation( + Instruction.Load, + StorageKind.ConstantBuffer, + offset, + Const(SupportBuffer.Binding), + Const((int)SupportBufferField.BufferTextureOffset), + Const(stageIndex * SupportBuffer.TextureCount + bindingIndex), + Const(isImage ? 1 : 0))); + + node.List.AddBefore(node, new Operation(Instruction.Add, coordPlusOffset, source, offset)); + + sources[coordsIndex] = coordPlusOffset; + + TextureOperation newTexOp = new( + texOp.Inst, + texOp.Type, + texOp.Format, + texOp.Flags, + texOp.Set, + texOp.Binding, + texOp.Index, + dests, + sources); + + node = node.List.AddBefore(node, newTexOp); + + Utils.DeleteNode(oldNode, texOp); + + return node; + } + private static LinkedListNode InsertConstOffsets(LinkedListNode node, ResourceManager resourceManager, IGpuAccessor gpuAccessor, ShaderStage stage) { // Non-constant texture offsets are not allowed (according to the spec), diff --git a/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs b/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs index f26c316e2..949bc8660 100644 --- a/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs +++ b/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs @@ -784,6 +784,7 @@ namespace Ryujinx.Graphics.Vulkan supportsGeometryShader: Capabilities.SupportsGeometryShader, supportsGeometryShaderPassthrough: Capabilities.SupportsGeometryShaderPassthrough, supportsTransformFeedback: Capabilities.SupportsTransformFeedback, + supportsImageBufferPixelAlignment: false, supportsImageLoadFormatted: features2.Features.ShaderStorageImageReadWithoutFormat, supportsLayerVertexTessellation: featuresVk12.ShaderOutputLayer, supportsMismatchingViewFormat: true, @@ -797,6 +798,7 @@ namespace Ryujinx.Graphics.Vulkan supportsShaderNonUniformIndexing: featuresVk12.ShaderSampledImageArrayNonUniformIndexing && featuresVk12.ShaderStorageImageArrayNonUniformIndexing, + supportsTextureBufferPixelAlignment: false, supportsTextureGatherOffsets: features2.Features.ShaderImageGatherExtended, supportsTextureShadowLod: false, supportsVertexStoreAndAtomics: features2.Features.VertexPipelineStoresAndAtomics,