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,