Skip to content

Commit 7c0a7bf

Browse files
committed
Add .astc texture replacement support
Previously, we only supported ASTC .dds files, which are non-standard.
1 parent 9087a40 commit 7c0a7bf

4 files changed

Lines changed: 152 additions & 6 deletions

File tree

cmake/aurora_gx.cmake

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ add_library(aurora_gx STATIC
44
lib/gfx/depth_peek.cpp
55
lib/gfx/pipeline_cache.cpp
66
lib/gfx/render_worker.cpp
7+
lib/gfx/astc_io.cpp
78
lib/gfx/dds_io.cpp
89
lib/gfx/tex_copy_conv.cpp
910
lib/gfx/tex_palette_conv.cpp

lib/gfx/astc_io.cpp

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
#include "astc_io.hpp"
2+
3+
#include <algorithm>
4+
#include <array>
5+
#include <cstring>
6+
#include <filesystem>
7+
#include <utility>
8+
9+
#include "dds_io.hpp"
10+
#include "texture.hpp"
11+
12+
namespace aurora::gfx::astc {
13+
namespace {
14+
struct ASTCHeader {
15+
uint8_t magic[4];
16+
uint8_t blockWidth;
17+
uint8_t blockHeight;
18+
uint8_t blockDepth;
19+
uint8_t xSize[3];
20+
uint8_t ySize[3];
21+
uint8_t zSize[3];
22+
};
23+
static_assert(sizeof(ASTCHeader) == 16);
24+
25+
constexpr std::array<uint8_t, 4> kASTCMagic{0x13, 0xab, 0xa1, 0x5c};
26+
27+
uint32_t read_u24_le(const uint8_t bytes[3]) noexcept {
28+
return static_cast<uint32_t>(bytes[0]) | (static_cast<uint32_t>(bytes[1]) << 8) |
29+
(static_cast<uint32_t>(bytes[2]) << 16);
30+
}
31+
32+
std::optional<wgpu::TextureFormat> resolve_astc_format(uint8_t blockWidth, uint8_t blockHeight,
33+
uint8_t blockDepth) noexcept {
34+
if (blockDepth != 1) {
35+
return std::nullopt;
36+
}
37+
38+
switch ((static_cast<uint32_t>(blockWidth) << 8) | blockHeight) {
39+
case 0x0404:
40+
return wgpu::TextureFormat::ASTC4x4Unorm;
41+
case 0x0504:
42+
return wgpu::TextureFormat::ASTC5x4Unorm;
43+
case 0x0505:
44+
return wgpu::TextureFormat::ASTC5x5Unorm;
45+
case 0x0605:
46+
return wgpu::TextureFormat::ASTC6x5Unorm;
47+
case 0x0606:
48+
return wgpu::TextureFormat::ASTC6x6Unorm;
49+
case 0x0805:
50+
return wgpu::TextureFormat::ASTC8x5Unorm;
51+
case 0x0806:
52+
return wgpu::TextureFormat::ASTC8x6Unorm;
53+
case 0x0808:
54+
return wgpu::TextureFormat::ASTC8x8Unorm;
55+
case 0x0a05:
56+
return wgpu::TextureFormat::ASTC10x5Unorm;
57+
case 0x0a06:
58+
return wgpu::TextureFormat::ASTC10x6Unorm;
59+
case 0x0a08:
60+
return wgpu::TextureFormat::ASTC10x8Unorm;
61+
case 0x0a0a:
62+
return wgpu::TextureFormat::ASTC10x10Unorm;
63+
case 0x0c0a:
64+
return wgpu::TextureFormat::ASTC12x10Unorm;
65+
case 0x0c0c:
66+
return wgpu::TextureFormat::ASTC12x12Unorm;
67+
default:
68+
return std::nullopt;
69+
}
70+
}
71+
} // namespace
72+
73+
std::optional<ConvertedTexture> parse_astc_bytes(ArrayRef<uint8_t> bytes) noexcept {
74+
if (bytes.size() < sizeof(ASTCHeader)) {
75+
return std::nullopt;
76+
}
77+
78+
const auto* header = reinterpret_cast<const ASTCHeader*>(bytes.data());
79+
if (!std::equal(kASTCMagic.begin(), kASTCMagic.end(), header->magic)) {
80+
return std::nullopt;
81+
}
82+
83+
const uint32_t width = read_u24_le(header->xSize);
84+
const uint32_t height = read_u24_le(header->ySize);
85+
const uint32_t depth = read_u24_le(header->zSize);
86+
if (width == 0 || height == 0 || depth != 1) {
87+
return std::nullopt;
88+
}
89+
90+
const auto format = resolve_astc_format(header->blockWidth, header->blockHeight, header->blockDepth);
91+
if (!format.has_value()) {
92+
return std::nullopt;
93+
}
94+
95+
const auto expectedSize = calc_texture_size(*format, width, height, 1);
96+
if (expectedSize == 0 || sizeof(ASTCHeader) + expectedSize > bytes.size()) {
97+
return std::nullopt;
98+
}
99+
100+
ByteBuffer data{expectedSize};
101+
std::memcpy(data.data(), bytes.data() + sizeof(ASTCHeader), data.size());
102+
return ConvertedTexture{
103+
.format = *format,
104+
.width = width,
105+
.height = height,
106+
.mips = 1,
107+
.data = std::move(data),
108+
};
109+
}
110+
111+
std::optional<ConvertedTexture> load_astc_file(const std::filesystem::path& path) noexcept {
112+
const auto bytes = dds::read_binary_file(path);
113+
if (!bytes.has_value()) {
114+
return std::nullopt;
115+
}
116+
return parse_astc_bytes(*bytes);
117+
}
118+
} // namespace aurora::gfx::astc

lib/gfx/astc_io.hpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#pragma once
2+
3+
#include "common.hpp"
4+
#include "texture_convert.hpp"
5+
6+
#include <filesystem>
7+
#include <optional>
8+
9+
namespace aurora::gfx::astc {
10+
std::optional<ConvertedTexture> parse_astc_bytes(ArrayRef<uint8_t> bytes) noexcept;
11+
std::optional<ConvertedTexture> load_astc_file(const std::filesystem::path& path) noexcept;
12+
} // namespace aurora::gfx::astc

lib/gfx/texture_replacement.cpp

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include "../gx/gx.hpp"
55
#include "../internal.hpp"
66
#include "../webgpu/gpu.hpp"
7+
#include "astc_io.hpp"
78
#include "dds_io.hpp"
89
#include "png_io.hpp"
910
#include "texture_convert.hpp"
@@ -112,6 +113,10 @@ bool iequals_ascii(std::string_view lhs, std::string_view rhs) noexcept {
112113
return true;
113114
}
114115

116+
bool is_supported_texture_file_extension(std::string_view extension) noexcept {
117+
return iequals_ascii(extension, ".astc") || iequals_ascii(extension, ".dds") || iequals_ascii(extension, ".png");
118+
}
119+
115120
int compare_ascii_ci(std::string_view lhs, std::string_view rhs) noexcept {
116121
const size_t count = std::min(lhs.size(), rhs.size());
117122
for (size_t i = 0; i < count; ++i) {
@@ -260,7 +265,8 @@ aurora::ArrayRef<uint8_t> tlut_bytes(const GXTlutObj_& tlut) noexcept {
260265
return {static_cast<const uint8_t*>(tlut.data), static_cast<size_t>(tlut.numEntries) * sizeof(uint16_t)};
261266
}
262267

263-
std::optional<uint64_t> compute_referenced_tlut_hash(const GXTexObj_& obj, aurora::ArrayRef<uint8_t> tlutData) noexcept {
268+
std::optional<uint64_t> compute_referenced_tlut_hash(const GXTexObj_& obj,
269+
aurora::ArrayRef<uint8_t> tlutData) noexcept {
264270
const uint32_t textureSize = texture_base_level_size(obj);
265271
const auto* textureData = static_cast<const uint8_t*>(obj.data);
266272
if (!is_palette_format(obj.format()) || !obj.has_data() || textureSize == 0 || tlutData.empty()) {
@@ -391,7 +397,7 @@ std::optional<aurora::texture::TextureSourceKey> parse_replacement_filename(std:
391397
return std::nullopt;
392398
}
393399

394-
if (!iequals_ascii(filename.substr(dot), ".dds") && !iequals_ascii(filename.substr(dot), ".png")) {
400+
if (!is_supported_texture_file_extension(filename.substr(dot))) {
395401
return std::nullopt;
396402
}
397403

@@ -480,10 +486,17 @@ std::optional<aurora::texture::TextureSourceKey> parse_replacement_filename(std:
480486
}
481487

482488
std::optional<aurora::gfx::ConvertedTexture> load_texture_file(const std::filesystem::path& path) {
483-
if (iequals_ascii(fs_path_to_string(path.extension()), ".png")) {
489+
const auto extension = fs_path_to_string(path.extension());
490+
if (iequals_ascii(extension, ".astc")) {
491+
return aurora::gfx::astc::load_astc_file(path);
492+
}
493+
if (iequals_ascii(extension, ".png")) {
484494
return aurora::gfx::png::load_png_file(path);
485495
}
486-
return aurora::gfx::dds::load_dds_file(path);
496+
if (iequals_ascii(extension, ".dds")) {
497+
return aurora::gfx::dds::load_dds_file(path);
498+
}
499+
return std::nullopt;
487500
}
488501

489502
bool remove_mipmaps(aurora::gfx::ConvertedTexture& texture) noexcept {
@@ -1091,7 +1104,7 @@ ReplacementGroup load_replacement_directory(const std::filesystem::path& root, R
10911104
}
10921105

10931106
const auto extension = fs_path_to_string(path.extension());
1094-
if (!iequals_ascii(extension, ".dds") && !iequals_ascii(extension, ".png")) {
1107+
if (!is_supported_texture_file_extension(extension)) {
10951108
continue;
10961109
}
10971110

@@ -1147,7 +1160,9 @@ std::optional<TextureHandle> find_source_replacement_locked(const GXTexObj_& obj
11471160
const auto replacementKey = find_source_replacement_key_locked(sourceKey);
11481161
if (!replacementKey.has_value()) {
11491162
const bool alwaysReportMissingKey = false; // Enable for debugging
1150-
if (g_config.allowTextureDumps || alwaysReportMissingKey) { report_missing_key(sourceKey, obj); }
1163+
if (g_config.allowTextureDumps || alwaysReportMissingKey) {
1164+
report_missing_key(sourceKey, obj);
1165+
}
11511166
return std::nullopt;
11521167
}
11531168

0 commit comments

Comments
 (0)