aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorlizzie <lizzie@eden-emu.dev>2026-05-15 22:08:09 +0200
committercrueter <crueter@eden-emu.dev>2026-05-15 22:08:09 +0200
commit4d49341918981a782395fe379a8e6846e1fd11b2 (patch)
tree6d09831d70ef4898dd5da9db4e1bf72200c504da
parent2f0f8a979c88b1ded8b5cce9f9bee1a59bb2508c (diff)
[vk, opengl] recognize and use ETC2 (if available) textures natively (#3237)
this makes it so VK and OGL backends map the NVIDIA's ETC2 into VK_FORMAT_ETC-whatever and GL_ETC-whatever remaps, instead of using the default fallback for AR8G8B8. in short, just make the ETC2 textures be submitted as ETC2 instead of being submit as A8R8G8B8. Signed-off-by: lizzie lizzie@eden-emu.dev Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3237 Reviewed-by: Ghost <> Reviewed-by: crueter <crueter@eden-emu.dev>
-rw-r--r--src/video_core/renderer_vulkan/maxwell_to_vk.cpp11
-rw-r--r--src/video_core/surface.cpp19
-rw-r--r--src/video_core/surface.h9
-rw-r--r--src/video_core/texture_cache/format_lookup_table.cpp14
-rw-r--r--src/video_core/texture_cache/formatter.h210
-rw-r--r--src/video_core/vulkan_common/vulkan_device.h5
6 files changed, 60 insertions, 208 deletions
diff --git a/src/video_core/renderer_vulkan/maxwell_to_vk.cpp b/src/video_core/renderer_vulkan/maxwell_to_vk.cpp
index 930f06daf6..77ef8bf7bc 100644
--- a/src/video_core/renderer_vulkan/maxwell_to_vk.cpp
+++ b/src/video_core/renderer_vulkan/maxwell_to_vk.cpp
@@ -217,6 +217,12 @@ FormatInfo SurfaceFormat(const Device& device, FormatType format_type, bool with
SURFACE_FORMAT_ELEM(VK_FORMAT_ASTC_6x5_UNORM_BLOCK, 0, ASTC_2D_6X5_UNORM) \
SURFACE_FORMAT_ELEM(VK_FORMAT_ASTC_6x5_SRGB_BLOCK, 0, ASTC_2D_6X5_SRGB) \
SURFACE_FORMAT_ELEM(VK_FORMAT_E5B9G9R9_UFLOAT_PACK32, 0, E5B9G9R9_FLOAT) \
+ SURFACE_FORMAT_ELEM(VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK, 0, ETC2_RGB_UNORM) \
+ SURFACE_FORMAT_ELEM(VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK, 0, ETC2_RGBA_UNORM) \
+ SURFACE_FORMAT_ELEM(VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK, 0, ETC2_RGB_PTA_UNORM) \
+ SURFACE_FORMAT_ELEM(VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK, 0, ETC2_RGB_SRGB) \
+ SURFACE_FORMAT_ELEM(VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK, 0, ETC2_RGBA_SRGB) \
+ SURFACE_FORMAT_ELEM(VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK, 0, ETC2_RGB_PTA_SRGB) \
/* Depth formats */ \
SURFACE_FORMAT_ELEM(VK_FORMAT_D32_SFLOAT, usage_attachable, D32_FLOAT) \
SURFACE_FORMAT_ELEM(VK_FORMAT_D16_UNORM, usage_attachable, D16_UNORM) \
@@ -253,8 +259,8 @@ FormatInfo SurfaceFormat(const Device& device, FormatType format_type, bool with
break;
}
}
- // Transcode on hardware that doesn't support BCn natively
if (!device.IsOptimalBcnSupported() && VideoCore::Surface::IsPixelFormatBCn(pixel_format)) {
+ // Transcode on hardware that doesn't support BCn natively
if (pixel_format == PixelFormat::BC4_SNORM) {
tuple.format = VK_FORMAT_R8_SNORM;
} else if (pixel_format == PixelFormat::BC4_UNORM) {
@@ -270,6 +276,9 @@ FormatInfo SurfaceFormat(const Device& device, FormatType format_type, bool with
} else {
tuple.format = VK_FORMAT_A8B8G8R8_UNORM_PACK32;
}
+ } else if (!device.IsOptimalEtc2Supported() && VideoCore::Surface::IsPixelFormatETC2(pixel_format)) {
+ // Transcode on hardware that doesn't support ETC2 natively
+ tuple.format = is_srgb ? VK_FORMAT_A8B8G8R8_SRGB_PACK32 : VK_FORMAT_A8B8G8R8_UNORM_PACK32;
}
bool const attachable = (tuple.usage & usage_attachable) != 0;
bool const storage = (tuple.usage & usage_storage) != 0;
diff --git a/src/video_core/surface.cpp b/src/video_core/surface.cpp
index 1998849e84..69c1720943 100644
--- a/src/video_core/surface.cpp
+++ b/src/video_core/surface.cpp
@@ -1,4 +1,4 @@
-// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
@@ -336,6 +336,20 @@ bool IsPixelFormatBCn(PixelFormat format) {
}
}
+bool IsPixelFormatETC2(PixelFormat format) {
+ switch (format) {
+ case PixelFormat::ETC2_RGB_UNORM:
+ case PixelFormat::ETC2_RGBA_UNORM:
+ case PixelFormat::ETC2_RGB_PTA_UNORM:
+ case PixelFormat::ETC2_RGB_SRGB:
+ case PixelFormat::ETC2_RGBA_SRGB:
+ case PixelFormat::ETC2_RGB_PTA_SRGB:
+ return true;
+ default:
+ return false;
+ }
+}
+
bool IsPixelFormatSRGB(PixelFormat format) {
switch (format) {
case PixelFormat::A8B8G8R8_SRGB:
@@ -344,6 +358,9 @@ bool IsPixelFormatSRGB(PixelFormat format) {
case PixelFormat::BC2_SRGB:
case PixelFormat::BC3_SRGB:
case PixelFormat::BC7_SRGB:
+ case PixelFormat::ETC2_RGB_SRGB:
+ case PixelFormat::ETC2_RGBA_SRGB:
+ case PixelFormat::ETC2_RGB_PTA_SRGB:
case PixelFormat::ASTC_2D_4X4_SRGB:
case PixelFormat::ASTC_2D_8X8_SRGB:
case PixelFormat::ASTC_2D_8X5_SRGB:
diff --git a/src/video_core/surface.h b/src/video_core/surface.h
index a97bcb39b5..c659a977c1 100644
--- a/src/video_core/surface.h
+++ b/src/video_core/surface.h
@@ -111,6 +111,12 @@ namespace VideoCore::Surface {
PIXEL_FORMAT_ELEM(ASTC_2D_6X5_UNORM, 6, 5, 128) \
PIXEL_FORMAT_ELEM(ASTC_2D_6X5_SRGB, 6, 5, 128) \
PIXEL_FORMAT_ELEM(E5B9G9R9_FLOAT, 1, 1, 32) \
+ PIXEL_FORMAT_ELEM(ETC2_RGB_UNORM, 4, 4, 64) \
+ PIXEL_FORMAT_ELEM(ETC2_RGBA_UNORM, 4, 4, 128) \
+ PIXEL_FORMAT_ELEM(ETC2_RGB_PTA_UNORM, 4, 4, 64) \
+ PIXEL_FORMAT_ELEM(ETC2_RGB_SRGB, 4, 4, 64) \
+ PIXEL_FORMAT_ELEM(ETC2_RGBA_SRGB, 4, 4, 128) \
+ PIXEL_FORMAT_ELEM(ETC2_RGB_PTA_SRGB, 4, 4, 64) \
/* Depth formats */ \
PIXEL_FORMAT_ELEM(D32_FLOAT, 1, 1, 32) \
PIXEL_FORMAT_ELEM(D16_UNORM, 1, 1, 16) \
@@ -181,8 +187,6 @@ constexpr u32 BitsPerBlock(PixelFormat format) noexcept {
}
}
-#undef PIXEL_FORMAT_LIST
-
/// Returns the sizer in bytes of the specified pixel format
constexpr u32 BytesPerBlock(PixelFormat pixel_format) {
return BitsPerBlock(pixel_format) / CHAR_BIT;
@@ -198,6 +202,7 @@ SurfaceType GetFormatType(PixelFormat pixel_format);
bool HasAlpha(PixelFormat pixel_format);
bool IsPixelFormatASTC(PixelFormat format);
bool IsPixelFormatBCn(PixelFormat format);
+bool IsPixelFormatETC2(PixelFormat format);
bool IsPixelFormatSRGB(PixelFormat format);
bool IsPixelFormatInteger(PixelFormat format);
bool IsPixelFormatSignedInteger(PixelFormat format);
diff --git a/src/video_core/texture_cache/format_lookup_table.cpp b/src/video_core/texture_cache/format_lookup_table.cpp
index a9e03b375d..ea68a1c063 100644
--- a/src/video_core/texture_cache/format_lookup_table.cpp
+++ b/src/video_core/texture_cache/format_lookup_table.cpp
@@ -191,6 +191,20 @@ PixelFormat PixelFormatFromTextureInfo(TextureFormat format, ComponentType red,
return PixelFormat::BC6H_SFLOAT;
case Hash(TextureFormat::BC6H_U16, FLOAT):
return PixelFormat::BC6H_UFLOAT;
+ /* ETC2 */
+ case Hash(TextureFormat::ETC2_RGB, UNORM, LINEAR):
+ return PixelFormat::ETC2_RGB_UNORM;
+ case Hash(TextureFormat::ETC2_RGB_PTA, UNORM, LINEAR):
+ return PixelFormat::ETC2_RGB_PTA_UNORM;
+ case Hash(TextureFormat::ETC2_RGBA, UNORM, LINEAR):
+ return PixelFormat::ETC2_RGBA_UNORM;
+ case Hash(TextureFormat::ETC2_RGB, UNORM, SRGB):
+ return PixelFormat::ETC2_RGB_SRGB;
+ case Hash(TextureFormat::ETC2_RGB_PTA, UNORM, SRGB):
+ return PixelFormat::ETC2_RGB_PTA_SRGB;
+ case Hash(TextureFormat::ETC2_RGBA, UNORM, SRGB):
+ return PixelFormat::ETC2_RGBA_SRGB;
+ /* ASTC */
case Hash(TextureFormat::ASTC_2D_4X4, UNORM, LINEAR):
return PixelFormat::ASTC_2D_4X4_UNORM;
case Hash(TextureFormat::ASTC_2D_4X4, UNORM, SRGB):
diff --git a/src/video_core/texture_cache/formatter.h b/src/video_core/texture_cache/formatter.h
index 33c32645a2..3e2e749274 100644
--- a/src/video_core/texture_cache/formatter.h
+++ b/src/video_core/texture_cache/formatter.h
@@ -1,3 +1,6 @@
+// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -17,210 +20,9 @@ struct fmt::formatter<VideoCore::Surface::PixelFormat> : fmt::formatter<fmt::str
using VideoCore::Surface::PixelFormat;
const string_view name = [format] {
switch (format) {
- case PixelFormat::A8B8G8R8_UNORM:
- return "A8B8G8R8_UNORM";
- case PixelFormat::A8B8G8R8_SNORM:
- return "A8B8G8R8_SNORM";
- case PixelFormat::A8B8G8R8_SINT:
- return "A8B8G8R8_SINT";
- case PixelFormat::A8B8G8R8_UINT:
- return "A8B8G8R8_UINT";
- case PixelFormat::R5G6B5_UNORM:
- return "R5G6B5_UNORM";
- case PixelFormat::B5G6R5_UNORM:
- return "B5G6R5_UNORM";
- case PixelFormat::A1R5G5B5_UNORM:
- return "A1R5G5B5_UNORM";
- case PixelFormat::A2B10G10R10_UNORM:
- return "A2B10G10R10_UNORM";
- case PixelFormat::A2B10G10R10_UINT:
- return "A2B10G10R10_UINT";
- case PixelFormat::A2R10G10B10_UNORM:
- return "A2R10G10B10_UNORM";
- case PixelFormat::A1B5G5R5_UNORM:
- return "A1B5G5R5_UNORM";
- case PixelFormat::A5B5G5R1_UNORM:
- return "A5B5G5R1_UNORM";
- case PixelFormat::R8_UNORM:
- return "R8_UNORM";
- case PixelFormat::R8_SNORM:
- return "R8_SNORM";
- case PixelFormat::R8_SINT:
- return "R8_SINT";
- case PixelFormat::R8_UINT:
- return "R8_UINT";
- case PixelFormat::R16G16B16A16_FLOAT:
- return "R16G16B16A16_FLOAT";
- case PixelFormat::R16G16B16A16_UNORM:
- return "R16G16B16A16_UNORM";
- case PixelFormat::R16G16B16A16_SNORM:
- return "R16G16B16A16_SNORM";
- case PixelFormat::R16G16B16A16_SINT:
- return "R16G16B16A16_SINT";
- case PixelFormat::R16G16B16A16_UINT:
- return "R16G16B16A16_UINT";
- case PixelFormat::B10G11R11_FLOAT:
- return "B10G11R11_FLOAT";
- case PixelFormat::R32G32B32A32_UINT:
- return "R32G32B32A32_UINT";
- case PixelFormat::BC1_RGBA_UNORM:
- return "BC1_RGBA_UNORM";
- case PixelFormat::BC2_UNORM:
- return "BC2_UNORM";
- case PixelFormat::BC3_UNORM:
- return "BC3_UNORM";
- case PixelFormat::BC4_UNORM:
- return "BC4_UNORM";
- case PixelFormat::BC4_SNORM:
- return "BC4_SNORM";
- case PixelFormat::BC5_UNORM:
- return "BC5_UNORM";
- case PixelFormat::BC5_SNORM:
- return "BC5_SNORM";
- case PixelFormat::BC7_UNORM:
- return "BC7_UNORM";
- case PixelFormat::BC6H_UFLOAT:
- return "BC6H_UFLOAT";
- case PixelFormat::BC6H_SFLOAT:
- return "BC6H_SFLOAT";
- case PixelFormat::ASTC_2D_4X4_UNORM:
- return "ASTC_2D_4X4_UNORM";
- case PixelFormat::B8G8R8A8_UNORM:
- return "B8G8R8A8_UNORM";
- case PixelFormat::R32G32B32A32_FLOAT:
- return "R32G32B32A32_FLOAT";
- case PixelFormat::R32G32B32A32_SINT:
- return "R32G32B32A32_SINT";
- case PixelFormat::R32G32_FLOAT:
- return "R32G32_FLOAT";
- case PixelFormat::R32G32_SINT:
- return "R32G32_SINT";
- case PixelFormat::R32_FLOAT:
- return "R32_FLOAT";
- case PixelFormat::R16_FLOAT:
- return "R16_FLOAT";
- case PixelFormat::R16_UNORM:
- return "R16_UNORM";
- case PixelFormat::R16_SNORM:
- return "R16_SNORM";
- case PixelFormat::R16_UINT:
- return "R16_UINT";
- case PixelFormat::R16_SINT:
- return "R16_SINT";
- case PixelFormat::R16G16_UNORM:
- return "R16G16_UNORM";
- case PixelFormat::R16G16_FLOAT:
- return "R16G16_FLOAT";
- case PixelFormat::R16G16_UINT:
- return "R16G16_UINT";
- case PixelFormat::R16G16_SINT:
- return "R16G16_SINT";
- case PixelFormat::R16G16_SNORM:
- return "R16G16_SNORM";
- case PixelFormat::R32G32B32_FLOAT:
- return "R32G32B32_FLOAT";
- case PixelFormat::A8B8G8R8_SRGB:
- return "A8B8G8R8_SRGB";
- case PixelFormat::R8G8_UNORM:
- return "R8G8_UNORM";
- case PixelFormat::R8G8_SNORM:
- return "R8G8_SNORM";
- case PixelFormat::R8G8_SINT:
- return "R8G8_SINT";
- case PixelFormat::R8G8_UINT:
- return "R8G8_UINT";
- case PixelFormat::R32G32_UINT:
- return "R32G32_UINT";
- case PixelFormat::R16G16B16X16_FLOAT:
- return "R16G16B16X16_FLOAT";
- case PixelFormat::R32_UINT:
- return "R32_UINT";
- case PixelFormat::R32_SINT:
- return "R32_SINT";
- case PixelFormat::ASTC_2D_8X8_UNORM:
- return "ASTC_2D_8X8_UNORM";
- case PixelFormat::ASTC_2D_8X5_UNORM:
- return "ASTC_2D_8X5_UNORM";
- case PixelFormat::ASTC_2D_5X4_UNORM:
- return "ASTC_2D_5X4_UNORM";
- case PixelFormat::B8G8R8A8_SRGB:
- return "B8G8R8A8_SRGB";
- case PixelFormat::BC1_RGBA_SRGB:
- return "BC1_RGBA_SRGB";
- case PixelFormat::BC2_SRGB:
- return "BC2_SRGB";
- case PixelFormat::BC3_SRGB:
- return "BC3_SRGB";
- case PixelFormat::BC7_SRGB:
- return "BC7_SRGB";
- case PixelFormat::A4B4G4R4_UNORM:
- return "A4B4G4R4_UNORM";
- case PixelFormat::G4R4_UNORM:
- return "G4R4_UNORM";
- case PixelFormat::ASTC_2D_4X4_SRGB:
- return "ASTC_2D_4X4_SRGB";
- case PixelFormat::ASTC_2D_8X8_SRGB:
- return "ASTC_2D_8X8_SRGB";
- case PixelFormat::ASTC_2D_8X5_SRGB:
- return "ASTC_2D_8X5_SRGB";
- case PixelFormat::ASTC_2D_5X4_SRGB:
- return "ASTC_2D_5X4_SRGB";
- case PixelFormat::ASTC_2D_5X5_UNORM:
- return "ASTC_2D_5X5_UNORM";
- case PixelFormat::ASTC_2D_5X5_SRGB:
- return "ASTC_2D_5X5_SRGB";
- case PixelFormat::ASTC_2D_10X8_UNORM:
- return "ASTC_2D_10X8_UNORM";
- case PixelFormat::ASTC_2D_10X8_SRGB:
- return "ASTC_2D_10X8_SRGB";
- case PixelFormat::ASTC_2D_6X6_UNORM:
- return "ASTC_2D_6X6_UNORM";
- case PixelFormat::ASTC_2D_6X6_SRGB:
- return "ASTC_2D_6X6_SRGB";
- case PixelFormat::ASTC_2D_10X6_UNORM:
- return "ASTC_2D_10X6_UNORM";
- case PixelFormat::ASTC_2D_10X6_SRGB:
- return "ASTC_2D_10X6_SRGB";
- case PixelFormat::ASTC_2D_10X5_UNORM:
- return "ASTC_2D_10X5_UNORM";
- case PixelFormat::ASTC_2D_10X5_SRGB:
- return "ASTC_2D_10X5_SRGB";
- case PixelFormat::ASTC_2D_10X10_UNORM:
- return "ASTC_2D_10X10_UNORM";
- case PixelFormat::ASTC_2D_10X10_SRGB:
- return "ASTC_2D_10X10_SRGB";
- case PixelFormat::ASTC_2D_12X10_UNORM:
- return "ASTC_2D_12X10_UNORM";
- case PixelFormat::ASTC_2D_12X10_SRGB:
- return "ASTC_2D_12X10_SRGB";
- case PixelFormat::ASTC_2D_12X12_UNORM:
- return "ASTC_2D_12X12_UNORM";
- case PixelFormat::ASTC_2D_12X12_SRGB:
- return "ASTC_2D_12X12_SRGB";
- case PixelFormat::ASTC_2D_8X6_UNORM:
- return "ASTC_2D_8X6_UNORM";
- case PixelFormat::ASTC_2D_8X6_SRGB:
- return "ASTC_2D_8X6_SRGB";
- case PixelFormat::ASTC_2D_6X5_UNORM:
- return "ASTC_2D_6X5_UNORM";
- case PixelFormat::ASTC_2D_6X5_SRGB:
- return "ASTC_2D_6X5_SRGB";
- case PixelFormat::E5B9G9R9_FLOAT:
- return "E5B9G9R9_FLOAT";
- case PixelFormat::D32_FLOAT:
- return "D32_FLOAT";
- case PixelFormat::D16_UNORM:
- return "D16_UNORM";
- case PixelFormat::X8_D24_UNORM:
- return "X8_D24_UNORM";
- case PixelFormat::S8_UINT:
- return "S8_UINT";
- case PixelFormat::D24_UNORM_S8_UINT:
- return "D24_UNORM_S8_UINT";
- case PixelFormat::S8_UINT_D24_UNORM:
- return "S8_UINT_D24_UNORM";
- case PixelFormat::D32_FLOAT_S8_UINT:
- return "D32_FLOAT_S8_UINT";
+#define PIXEL_FORMAT_ELEM(NAME, ...) case PixelFormat::NAME: return #NAME;
+ PIXEL_FORMAT_LIST
+#undef PIXEL_FORMAT_ELEM
case PixelFormat::MaxDepthStencilFormat:
case PixelFormat::Invalid:
return "Invalid";
diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h
index 13fe1b371f..7f2c29519f 100644
--- a/src/video_core/vulkan_common/vulkan_device.h
+++ b/src/video_core/vulkan_common/vulkan_device.h
@@ -363,6 +363,11 @@ public:
return features.features.textureCompressionBC;
}
+ /// Returns true if ETC2 is natively supported.
+ bool IsOptimalEtc2Supported() const {
+ return features.features.textureCompressionETC2;
+ }
+
/// Returns true if descriptor aliasing is natively supported.
bool IsDescriptorAliasingSupported() const {
return GetDriverID() != VK_DRIVER_ID_QUALCOMM_PROPRIETARY;