GPU: Add fallback when 16-bit formats are not supported (#4108)

* Add conversion for 16 bit RGBA formats (not supported in Rosetta)

* Rebase fix

Rebase fix

* Forgot to remove this

* Fix RGBA16 format conversion

* Add RGBA4 -> RGBA8 conversion

* Handle host stride alignment

* Address Feedback Part 1

* Can't count

* Don't zero out rgb when alpha is 0

* Separate RGBA4 and 5-bit component formats

Not sure of a better way to name them...

* Add A1B5G5R5 conversion

* Put this in the right place.

* Make format naming consistent for capabilities

* Change method names
This commit is contained in:
riperiperi 2022-12-26 18:50:27 +00:00 committed by GitHub
parent c963b3c804
commit 470be03c2f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 291 additions and 20 deletions

View file

@ -18,7 +18,9 @@ namespace Ryujinx.Graphics.GAL
public readonly bool Supports3DTextureCompression; public readonly bool Supports3DTextureCompression;
public readonly bool SupportsBgraFormat; public readonly bool SupportsBgraFormat;
public readonly bool SupportsR4G4Format; public readonly bool SupportsR4G4Format;
public readonly bool SupportsR4G4B4A4Format;
public readonly bool SupportsSnormBufferTextureFormat; public readonly bool SupportsSnormBufferTextureFormat;
public readonly bool Supports5BitComponentFormat;
public readonly bool SupportsFragmentShaderInterlock; public readonly bool SupportsFragmentShaderInterlock;
public readonly bool SupportsFragmentShaderOrderingIntel; public readonly bool SupportsFragmentShaderOrderingIntel;
public readonly bool SupportsGeometryShaderPassthrough; public readonly bool SupportsGeometryShaderPassthrough;
@ -55,7 +57,9 @@ namespace Ryujinx.Graphics.GAL
bool supports3DTextureCompression, bool supports3DTextureCompression,
bool supportsBgraFormat, bool supportsBgraFormat,
bool supportsR4G4Format, bool supportsR4G4Format,
bool supportsR4G4B4A4Format,
bool supportsSnormBufferTextureFormat, bool supportsSnormBufferTextureFormat,
bool supports5BitComponentFormat,
bool supportsFragmentShaderInterlock, bool supportsFragmentShaderInterlock,
bool supportsFragmentShaderOrderingIntel, bool supportsFragmentShaderOrderingIntel,
bool supportsGeometryShaderPassthrough, bool supportsGeometryShaderPassthrough,
@ -89,7 +93,9 @@ namespace Ryujinx.Graphics.GAL
Supports3DTextureCompression = supports3DTextureCompression; Supports3DTextureCompression = supports3DTextureCompression;
SupportsBgraFormat = supportsBgraFormat; SupportsBgraFormat = supportsBgraFormat;
SupportsR4G4Format = supportsR4G4Format; SupportsR4G4Format = supportsR4G4Format;
SupportsR4G4B4A4Format = supportsR4G4B4A4Format;
SupportsSnormBufferTextureFormat = supportsSnormBufferTextureFormat; SupportsSnormBufferTextureFormat = supportsSnormBufferTextureFormat;
Supports5BitComponentFormat = supports5BitComponentFormat;
SupportsFragmentShaderInterlock = supportsFragmentShaderInterlock; SupportsFragmentShaderInterlock = supportsFragmentShaderInterlock;
SupportsFragmentShaderOrderingIntel = supportsFragmentShaderOrderingIntel; SupportsFragmentShaderOrderingIntel = supportsFragmentShaderOrderingIntel;
SupportsGeometryShaderPassthrough = supportsGeometryShaderPassthrough; SupportsGeometryShaderPassthrough = supportsGeometryShaderPassthrough;

View file

@ -448,6 +448,27 @@ namespace Ryujinx.Graphics.GAL
return false; return false;
} }
/// <summary>
/// Checks if the texture format is 16 bit packed.
/// </summary>
/// <param name="format">Texture format</param>
/// <returns>True if the texture format is 16 bit packed, false otherwise</returns>
public static bool Is16BitPacked(this Format format)
{
switch (format)
{
case Format.B5G6R5Unorm:
case Format.B5G5R5A1Unorm:
case Format.R5G5B5X1Unorm:
case Format.R5G5B5A1Unorm:
case Format.R5G6B5Unorm:
case Format.R4G4B4A4Unorm:
return true;
}
return false;
}
/// <summary> /// <summary>
/// Checks if the texture format is an ASTC format. /// Checks if the texture format is an ASTC format.
/// </summary> /// </summary>

View file

@ -911,7 +911,40 @@ namespace Ryujinx.Graphics.Gpu.Image
} }
else if (!_context.Capabilities.SupportsR4G4Format && Format == Format.R4G4Unorm) else if (!_context.Capabilities.SupportsR4G4Format && Format == Format.R4G4Unorm)
{ {
result = PixelConverter.ConvertR4G4ToR4G4B4A4(result); result = PixelConverter.ConvertR4G4ToR4G4B4A4(result, width);
if (!_context.Capabilities.SupportsR4G4B4A4Format)
{
result = PixelConverter.ConvertR4G4B4A4ToR8G8B8A8(result, width);
}
}
else if (Format == Format.R4G4B4A4Unorm)
{
if (!_context.Capabilities.SupportsR4G4B4A4Format)
{
result = PixelConverter.ConvertR4G4B4A4ToR8G8B8A8(result, width);
}
}
else if (!_context.Capabilities.Supports5BitComponentFormat && Format.Is16BitPacked())
{
switch (Format)
{
case Format.B5G6R5Unorm:
case Format.R5G6B5Unorm:
result = PixelConverter.ConvertR5G6B5ToR8G8B8A8(result, width);
break;
case Format.B5G5R5A1Unorm:
case Format.R5G5B5X1Unorm:
case Format.R5G5B5A1Unorm:
result = PixelConverter.ConvertR5G5B5ToR8G8B8A8(result, width, Format == Format.R5G5B5X1Unorm);
break;
case Format.A1B5G5R5Unorm:
result = PixelConverter.ConvertA1B5G5R5ToR8G8B8A8(result, width);
break;
case Format.R4G4B4A4Unorm:
result = PixelConverter.ConvertR4G4B4A4ToR8G8B8A8(result, width);
break;
}
} }
return result; return result;

View file

@ -131,9 +131,28 @@ namespace Ryujinx.Graphics.Gpu.Image
} }
if (!caps.SupportsR4G4Format && info.FormatInfo.Format == Format.R4G4Unorm) if (!caps.SupportsR4G4Format && info.FormatInfo.Format == Format.R4G4Unorm)
{
if (caps.SupportsR4G4B4A4Format)
{ {
return new FormatInfo(Format.R4G4B4A4Unorm, 1, 1, 2, 4); return new FormatInfo(Format.R4G4B4A4Unorm, 1, 1, 2, 4);
} }
else
{
return new FormatInfo(Format.R8G8B8A8Unorm, 1, 1, 4, 4);
}
}
if (info.FormatInfo.Format == Format.R4G4B4A4Unorm)
{
if (!caps.SupportsR4G4B4A4Format)
{
return new FormatInfo(Format.R8G8B8A8Unorm, 1, 1, 4, 4);
}
}
else if (!caps.Supports5BitComponentFormat && info.FormatInfo.Format.Is16BitPacked())
{
return new FormatInfo(info.FormatInfo.Format.IsBgr() ? Format.B8G8R8A8Unorm : Format.R8G8B8A8Unorm, 1, 1, 4, 4);
}
return info.FormatInfo; return info.FormatInfo;
} }

View file

@ -114,7 +114,9 @@ namespace Ryujinx.Graphics.OpenGL
supports3DTextureCompression: false, supports3DTextureCompression: false,
supportsBgraFormat: false, supportsBgraFormat: false,
supportsR4G4Format: false, supportsR4G4Format: false,
supportsR4G4B4A4Format: true,
supportsSnormBufferTextureFormat: false, supportsSnormBufferTextureFormat: false,
supports5BitComponentFormat: true,
supportsFragmentShaderInterlock: HwCapabilities.SupportsFragmentShaderInterlock, supportsFragmentShaderInterlock: HwCapabilities.SupportsFragmentShaderInterlock,
supportsFragmentShaderOrderingIntel: HwCapabilities.SupportsFragmentShaderOrdering, supportsFragmentShaderOrderingIntel: HwCapabilities.SupportsFragmentShaderOrdering,
supportsGeometryShaderPassthrough: HwCapabilities.SupportsGeometryShaderPassthrough, supportsGeometryShaderPassthrough: HwCapabilities.SupportsGeometryShaderPassthrough,

View file

@ -7,7 +7,7 @@ namespace Ryujinx.Graphics.Texture
{ {
public static class LayoutConverter public static class LayoutConverter
{ {
private const int HostStrideAlignment = 4; public const int HostStrideAlignment = 4;
public static void ConvertBlockLinearToLinear( public static void ConvertBlockLinearToLinear(
Span<byte> dst, Span<byte> dst,

View file

@ -1,3 +1,4 @@
using Ryujinx.Common;
using System; using System;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Runtime.Intrinsics; using System.Runtime.Intrinsics;
@ -7,9 +8,27 @@ namespace Ryujinx.Graphics.Texture
{ {
public static class PixelConverter public static class PixelConverter
{ {
public unsafe static byte[] ConvertR4G4ToR4G4B4A4(ReadOnlySpan<byte> data) private static (int remainder, int outRemainder, int height) GetLineRemainders(int length, int width, int bpp, int outBpp)
{
int stride = BitUtils.AlignUp(width * bpp, LayoutConverter.HostStrideAlignment);
int remainder = stride / bpp - width;
int outStride = BitUtils.AlignUp(width * outBpp, LayoutConverter.HostStrideAlignment);
int outRemainder = outStride / outBpp - width;
return (remainder, outRemainder, length / stride);
}
public unsafe static byte[] ConvertR4G4ToR4G4B4A4(ReadOnlySpan<byte> data, int width)
{ {
byte[] output = new byte[data.Length * 2]; byte[] output = new byte[data.Length * 2];
(int remainder, int outRemainder, int height) = GetLineRemainders(data.Length, width, 1, 2);
Span<ushort> outputSpan = MemoryMarshal.Cast<byte, ushort>(output);
if (remainder == 0)
{
int start = 0; int start = 0;
if (Sse41.IsSupported) if (Sse41.IsSupported)
@ -26,12 +45,170 @@ namespace Ryujinx.Graphics.Texture
} }
} }
Span<ushort> outputSpan = MemoryMarshal.Cast<byte, ushort>(output);
for (int i = start; i < data.Length; i++) for (int i = start; i < data.Length; i++)
{ {
outputSpan[i] = (ushort)data[i]; outputSpan[i] = (ushort)data[i];
} }
}
else
{
int offset = 0;
int outOffset = 0;
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
outputSpan[outOffset++] = data[offset++];
}
offset += remainder;
outOffset += outRemainder;
}
}
return output;
}
public unsafe static byte[] ConvertR5G6B5ToR8G8B8A8(ReadOnlySpan<byte> data, int width)
{
byte[] output = new byte[data.Length * 2];
int offset = 0;
int outOffset = 0;
(int remainder, int outRemainder, int height) = GetLineRemainders(data.Length, width, 2, 4);
ReadOnlySpan<ushort> inputSpan = MemoryMarshal.Cast<byte, ushort>(data);
Span<uint> outputSpan = MemoryMarshal.Cast<byte, uint>(output);
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
uint packed = inputSpan[offset++];
uint outputPacked = 0xff000000;
outputPacked |= (packed << 3) & 0x000000f8;
outputPacked |= (packed << 8) & 0x00f80000;
// Replicate 5 bit components.
outputPacked |= (outputPacked >> 5) & 0x00070007;
// Include and replicate 6 bit component.
outputPacked |= ((packed << 5) & 0x0000fc00) | ((packed >> 1) & 0x00000300);
outputSpan[outOffset++] = outputPacked;
}
offset += remainder;
outOffset += outRemainder;
}
return output;
}
public unsafe static byte[] ConvertR5G5B5ToR8G8B8A8(ReadOnlySpan<byte> data, int width, bool forceAlpha)
{
byte[] output = new byte[data.Length * 2];
int offset = 0;
int outOffset = 0;
(int remainder, int outRemainder, int height) = GetLineRemainders(data.Length, width, 2, 4);
ReadOnlySpan<ushort> inputSpan = MemoryMarshal.Cast<byte, ushort>(data);
Span<uint> outputSpan = MemoryMarshal.Cast<byte, uint>(output);
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
uint packed = inputSpan[offset++];
uint a = forceAlpha ? 1 : (packed >> 15);
uint outputPacked = a * 0xff000000;
outputPacked |= (packed << 3) & 0x000000f8;
outputPacked |= (packed << 6) & 0x0000f800;
outputPacked |= (packed << 9) & 0x00f80000;
// Replicate 5 bit components.
outputPacked |= (outputPacked >> 5) & 0x00070707;
outputSpan[outOffset++] = outputPacked;
}
offset += remainder;
outOffset += outRemainder;
}
return output;
}
public unsafe static byte[] ConvertA1B5G5R5ToR8G8B8A8(ReadOnlySpan<byte> data, int width)
{
byte[] output = new byte[data.Length * 2];
int offset = 0;
int outOffset = 0;
(int remainder, int outRemainder, int height) = GetLineRemainders(data.Length, width, 2, 4);
ReadOnlySpan<ushort> inputSpan = MemoryMarshal.Cast<byte, ushort>(data);
Span<uint> outputSpan = MemoryMarshal.Cast<byte, uint>(output);
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
uint packed = inputSpan[offset++];
uint a = packed >> 15;
uint outputPacked = a * 0xff000000;
outputPacked |= (packed >> 8) & 0x000000f8;
outputPacked |= (packed << 5) & 0x0000f800;
outputPacked |= (packed << 18) & 0x00f80000;
// Replicate 5 bit components.
outputPacked |= (outputPacked >> 5) & 0x00070707;
outputSpan[outOffset++] = outputPacked;
}
offset += remainder;
outOffset += outRemainder;
}
return output;
}
public unsafe static byte[] ConvertR4G4B4A4ToR8G8B8A8(ReadOnlySpan<byte> data, int width)
{
byte[] output = new byte[data.Length * 2];
int offset = 0;
int outOffset = 0;
(int remainder, int outRemainder, int height) = GetLineRemainders(data.Length, width, 2, 4);
ReadOnlySpan<ushort> inputSpan = MemoryMarshal.Cast<byte, ushort>(data);
Span<uint> outputSpan = MemoryMarshal.Cast<byte, uint>(output);
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
uint packed = inputSpan[offset++];
uint outputPacked = packed & 0x0000000f;
outputPacked |= (packed << 4) & 0x00000f00;
outputPacked |= (packed << 8) & 0x000f0000;
outputPacked |= (packed << 12) & 0x0f000000;
outputSpan[outOffset++] = outputPacked * 0x11;
}
offset += remainder;
outOffset += outRemainder;
}
return output; return output;
} }

View file

@ -396,6 +396,17 @@ namespace Ryujinx.Graphics.Vulkan
GAL.Format.Etc2RgbSrgb, GAL.Format.Etc2RgbSrgb,
GAL.Format.Etc2RgbUnorm); GAL.Format.Etc2RgbUnorm);
bool supports5BitComponentFormat = FormatCapabilities.OptimalFormatsSupport(compressedFormatFeatureFlags,
GAL.Format.R5G6B5Unorm,
GAL.Format.R5G5B5A1Unorm,
GAL.Format.R5G5B5X1Unorm,
GAL.Format.B5G6R5Unorm,
GAL.Format.B5G5R5A1Unorm,
GAL.Format.A1B5G5R5Unorm);
bool supportsR4G4B4A4Format = FormatCapabilities.OptimalFormatsSupport(compressedFormatFeatureFlags,
GAL.Format.R4G4B4A4Unorm);
PhysicalDeviceVulkan12Features featuresVk12 = new PhysicalDeviceVulkan12Features() PhysicalDeviceVulkan12Features featuresVk12 = new PhysicalDeviceVulkan12Features()
{ {
SType = StructureType.PhysicalDeviceVulkan12Features SType = StructureType.PhysicalDeviceVulkan12Features
@ -425,7 +436,9 @@ namespace Ryujinx.Graphics.Vulkan
supports3DTextureCompression: true, supports3DTextureCompression: true,
supportsBgraFormat: true, supportsBgraFormat: true,
supportsR4G4Format: false, supportsR4G4Format: false,
supportsR4G4B4A4Format: supportsR4G4B4A4Format,
supportsSnormBufferTextureFormat: true, supportsSnormBufferTextureFormat: true,
supports5BitComponentFormat: supports5BitComponentFormat,
supportsFragmentShaderInterlock: Capabilities.SupportsFragmentShaderInterlock, supportsFragmentShaderInterlock: Capabilities.SupportsFragmentShaderInterlock,
supportsFragmentShaderOrderingIntel: false, supportsFragmentShaderOrderingIntel: false,
supportsGeometryShaderPassthrough: Capabilities.SupportsGeometryShaderPassthrough, supportsGeometryShaderPassthrough: Capabilities.SupportsGeometryShaderPassthrough,