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:
parent
c963b3c804
commit
470be03c2f
@ -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;
|
||||||
|
@ -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>
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -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,
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
Loading…
Reference in New Issue
Block a user