diff --git a/Ryujinx.Graphics.GAL/Capabilities.cs b/Ryujinx.Graphics.GAL/Capabilities.cs index 20bd87c6c..54a9ae3bf 100644 --- a/Ryujinx.Graphics.GAL/Capabilities.cs +++ b/Ryujinx.Graphics.GAL/Capabilities.cs @@ -6,6 +6,7 @@ namespace Ryujinx.Graphics.GAL public bool HasVectorIndexingBug { get; } public bool SupportsAstcCompression { get; } + public bool SupportsR4G4Format { get; } public bool SupportsFragmentShaderInterlock { get; } public bool SupportsFragmentShaderOrderingIntel { get; } public bool SupportsImageLoadFormatted { get; } @@ -24,6 +25,7 @@ namespace Ryujinx.Graphics.GAL bool hasFrontFacingBug, bool hasVectorIndexingBug, bool supportsAstcCompression, + bool supportsR4G4Format, bool supportsFragmentShaderInterlock, bool supportsFragmentShaderOrderingIntel, bool supportsImageLoadFormatted, @@ -40,6 +42,7 @@ namespace Ryujinx.Graphics.GAL HasFrontFacingBug = hasFrontFacingBug; HasVectorIndexingBug = hasVectorIndexingBug; SupportsAstcCompression = supportsAstcCompression; + SupportsR4G4Format = supportsR4G4Format; SupportsFragmentShaderInterlock = supportsFragmentShaderInterlock; SupportsFragmentShaderOrderingIntel = supportsFragmentShaderOrderingIntel; SupportsImageLoadFormatted = supportsImageLoadFormatted; diff --git a/Ryujinx.Graphics.GAL/Format.cs b/Ryujinx.Graphics.GAL/Format.cs index 98b6f5068..d5e183baf 100644 --- a/Ryujinx.Graphics.GAL/Format.cs +++ b/Ryujinx.Graphics.GAL/Format.cs @@ -58,6 +58,7 @@ namespace Ryujinx.Graphics.GAL D32FloatS8Uint, R8G8B8X8Srgb, R8G8B8A8Srgb, + R4G4Unorm, R4G4B4A4Unorm, R5G5B5X1Unorm, R5G5B5A1Unorm, diff --git a/Ryujinx.Graphics.Gpu/Image/FormatTable.cs b/Ryujinx.Graphics.Gpu/Image/FormatTable.cs index 03687adee..3c97e2e27 100644 --- a/Ryujinx.Graphics.Gpu/Image/FormatTable.cs +++ b/Ryujinx.Graphics.Gpu/Image/FormatTable.cs @@ -58,6 +58,7 @@ namespace Ryujinx.Graphics.Gpu.Image { 0x25385, new FormatInfo(Format.D32FloatS8Uint, 1, 1, 8, 2) }, { 0x253b0, new FormatInfo(Format.D32FloatS8Uint, 1, 1, 8, 2) }, { 0xa4908, new FormatInfo(Format.R8G8B8A8Srgb, 1, 1, 4, 4) }, + { 0x2491e, new FormatInfo(Format.R4G4Unorm, 1, 1, 1, 2) }, { 0x24912, new FormatInfo(Format.R4G4B4A4Unorm, 1, 1, 2, 4) }, { 0x24914, new FormatInfo(Format.R5G5B5A1Unorm, 1, 1, 2, 4) }, { 0x24915, new FormatInfo(Format.R5G6B5Unorm, 1, 1, 2, 3) }, diff --git a/Ryujinx.Graphics.Gpu/Image/Texture.cs b/Ryujinx.Graphics.Gpu/Image/Texture.cs index 590356e34..6d981479a 100644 --- a/Ryujinx.Graphics.Gpu/Image/Texture.cs +++ b/Ryujinx.Graphics.Gpu/Image/Texture.cs @@ -785,7 +785,7 @@ namespace Ryujinx.Graphics.Gpu.Image // Handle compressed cases not supported by the host: // - ASTC is usually not supported on desktop cards. // - BC4/BC5 is not supported on 3D textures. - if (!_context.Capabilities.SupportsAstcCompression && Info.FormatInfo.Format.IsAstc()) + if (!_context.Capabilities.SupportsAstcCompression && Format.IsAstc()) { if (!AstcDecoder.TryDecodeToRgba8P( data.ToArray(), @@ -805,11 +805,15 @@ namespace Ryujinx.Graphics.Gpu.Image data = decoded; } - else if (Target == Target.Texture3D && Info.FormatInfo.Format.IsBc4()) + else if (!_context.Capabilities.SupportsR4G4Format && Format == Format.R4G4Unorm) + { + data = PixelConverter.ConvertR4G4ToR4G4B4A4(data); + } + else if (Target == Target.Texture3D && Format.IsBc4()) { data = BCnDecoder.DecodeBC4(data, width, height, depth, levels, layers, Info.FormatInfo.Format == Format.Bc4Snorm); } - else if (Target == Target.Texture3D && Info.FormatInfo.Format.IsBc5()) + else if (Target == Target.Texture3D && Format.IsBc5()) { data = BCnDecoder.DecodeBC5(data, width, height, depth, levels, layers, Info.FormatInfo.Format == Format.Bc5Snorm); } diff --git a/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs b/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs index c70b29719..ce9fd75c7 100644 --- a/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs +++ b/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs @@ -66,6 +66,11 @@ namespace Ryujinx.Graphics.Gpu.Image } } + if (!caps.SupportsR4G4Format && info.FormatInfo.Format == Format.R4G4Unorm) + { + return new FormatInfo(Format.R4G4B4A4Unorm, 1, 1, 2, 4); + } + if (info.Target == Target.Texture3D) { // The host API does not support 3D BC4/BC5 compressed formats. diff --git a/Ryujinx.Graphics.OpenGL/Renderer.cs b/Ryujinx.Graphics.OpenGL/Renderer.cs index 25b762b2a..0c16ec5a1 100644 --- a/Ryujinx.Graphics.OpenGL/Renderer.cs +++ b/Ryujinx.Graphics.OpenGL/Renderer.cs @@ -101,21 +101,22 @@ namespace Ryujinx.Graphics.OpenGL public Capabilities GetCapabilities() { return new Capabilities( - HwCapabilities.Vendor == HwCapabilities.GpuVendor.IntelWindows, - HwCapabilities.Vendor == HwCapabilities.GpuVendor.AmdWindows, - HwCapabilities.SupportsAstcCompression, - HwCapabilities.SupportsFragmentShaderInterlock, - HwCapabilities.SupportsFragmentShaderOrdering, - HwCapabilities.SupportsImageLoadFormatted, - HwCapabilities.SupportsMismatchingViewFormat, - HwCapabilities.SupportsNonConstantTextureOffset, - HwCapabilities.SupportsShaderBallot, - HwCapabilities.SupportsTextureShadowLod, - HwCapabilities.SupportsViewportSwizzle, - HwCapabilities.SupportsIndirectParameters, - HwCapabilities.MaximumComputeSharedMemorySize, - HwCapabilities.MaximumSupportedAnisotropy, - HwCapabilities.StorageBufferOffsetAlignment); + hasFrontFacingBug: HwCapabilities.Vendor == HwCapabilities.GpuVendor.IntelWindows, + hasVectorIndexingBug: HwCapabilities.Vendor == HwCapabilities.GpuVendor.AmdWindows, + supportsAstcCompression: HwCapabilities.SupportsAstcCompression, + supportsR4G4Format: false, + supportsFragmentShaderInterlock: HwCapabilities.SupportsFragmentShaderInterlock, + supportsFragmentShaderOrderingIntel: HwCapabilities.SupportsFragmentShaderOrdering, + supportsImageLoadFormatted: HwCapabilities.SupportsImageLoadFormatted, + supportsMismatchingViewFormat: HwCapabilities.SupportsMismatchingViewFormat, + supportsNonConstantTextureOffset: HwCapabilities.SupportsNonConstantTextureOffset, + supportsShaderBallot: HwCapabilities.SupportsShaderBallot, + supportsTextureShadowLod: HwCapabilities.SupportsTextureShadowLod, + supportsViewportSwizzle: HwCapabilities.SupportsViewportSwizzle, + supportsIndirectParameters: HwCapabilities.SupportsIndirectParameters, + maximumComputeSharedMemorySize: HwCapabilities.MaximumComputeSharedMemorySize, + maximumSupportedAnisotropy: HwCapabilities.MaximumSupportedAnisotropy, + storageBufferOffsetAlignment: HwCapabilities.StorageBufferOffsetAlignment); } public void SetBufferData(BufferHandle buffer, int offset, ReadOnlySpan data) diff --git a/Ryujinx.Graphics.Texture/PixelConverter.cs b/Ryujinx.Graphics.Texture/PixelConverter.cs new file mode 100644 index 000000000..d7e45a693 --- /dev/null +++ b/Ryujinx.Graphics.Texture/PixelConverter.cs @@ -0,0 +1,39 @@ +using System; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; + +namespace Ryujinx.Graphics.Texture +{ + public static class PixelConverter + { + public unsafe static byte[] ConvertR4G4ToR4G4B4A4(ReadOnlySpan data) + { + byte[] output = new byte[data.Length * 2]; + int start = 0; + + if (Sse41.IsSupported) + { + int sizeTrunc = data.Length & ~7; + start = sizeTrunc; + + fixed (byte* inputPtr = data, outputPtr = output) + { + for (ulong offset = 0; offset < (ulong)sizeTrunc; offset += 8) + { + Sse2.Store(outputPtr + offset * 2, Sse41.ConvertToVector128Int16(inputPtr + offset).AsByte()); + } + } + } + + Span outputSpan = MemoryMarshal.Cast(output); + + for (int i = start; i < data.Length; i++) + { + outputSpan[i] = (ushort)data[i]; + } + + return output; + } + } +}