Skip to content

Commit 19479a5

Browse files
Use mipmaps from .dds file if present (#198)
* Use mipmaps from .dds file if present * Don't load mips from disk if they're present in the base file * Keep mipmap count >= 1 * Remove unintended formatting changes * Validate DDS mipmap handling --------- Co-authored-by: Luke Street <luke@street.dev>
1 parent 45a732e commit 19479a5

2 files changed

Lines changed: 64 additions & 4 deletions

File tree

lib/gfx/dds_io.cpp

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#include "dds_io.hpp"
22

3+
#include <algorithm>
34
#include <cstring>
45
#include <filesystem>
56
#include <fstream>
@@ -52,6 +53,11 @@ struct DDSHeaderDX10 {
5253
static_assert(sizeof(DDSHeaderDX10) == 20);
5354

5455
constexpr uint32_t kDDSMagic = 0x20534444; // "DDS"
56+
constexpr uint32_t kDDSDMipmapCount = 0x00020000;
57+
constexpr uint32_t kDDSCapsComplex = 0x00000008;
58+
constexpr uint32_t kDDSCapsMipmap = 0x00400000;
59+
constexpr uint32_t kDDSCaps2Cubemap = 0x00000200;
60+
constexpr uint32_t kDDSCaps2Volume = 0x00200000;
5561

5662
bool ensure_directory(const std::filesystem::path& dir) noexcept {
5763
std::error_code ec;
@@ -101,12 +107,36 @@ bool validate_dds_header(const DDSHeader& header) noexcept {
101107
if (header.width == 0 || header.height == 0) {
102108
return false;
103109
}
104-
if ((header.caps2 & (0x00000200 | 0x00200000)) != 0) { // Unsupported
110+
if ((header.caps2 & (kDDSCaps2Cubemap | kDDSCaps2Volume)) != 0) { // Unsupported
105111
return false;
106112
}
107113
return true;
108114
}
109115

116+
uint32_t max_mip_count(uint32_t width, uint32_t height) noexcept {
117+
uint32_t count = 1;
118+
while (width > 1 || height > 1) {
119+
width = std::max(width >> 1, 1u);
120+
height = std::max(height >> 1, 1u);
121+
++count;
122+
}
123+
return count;
124+
}
125+
126+
std::optional<uint32_t> resolve_mip_count(const DDSHeader& header) noexcept {
127+
if (header.mipMapCount <= 1) {
128+
return 1;
129+
}
130+
131+
const bool hasMipFlags = (header.flags & kDDSDMipmapCount) != 0 && (header.caps & kDDSCapsComplex) != 0 &&
132+
(header.caps & kDDSCapsMipmap) != 0;
133+
if (!hasMipFlags || header.mipMapCount > max_mip_count(header.width, header.height)) {
134+
return std::nullopt;
135+
}
136+
137+
return header.mipMapCount;
138+
}
139+
110140
std::optional<wgpu::TextureFormat> resolve_dx10_format(uint32_t dxgiFormat) noexcept {
111141
switch (dxgiFormat) {
112142
case 28:
@@ -253,8 +283,12 @@ std::optional<ConvertedTexture> parse_dds_bytes(ArrayRef<uint8_t> bytes) noexcep
253283
return std::nullopt;
254284
}
255285

256-
const uint32_t mipCount = 1u;
257-
const auto expectedSize = calc_texture_size(parsedLayout->format, header->width, header->height, mipCount);
286+
const auto mipCount = resolve_mip_count(*header);
287+
if (!mipCount.has_value()) {
288+
return std::nullopt;
289+
}
290+
291+
const auto expectedSize = calc_texture_size(parsedLayout->format, header->width, header->height, *mipCount);
258292
if (expectedSize == 0 || parsedLayout->dataOffset + expectedSize > bytes.size()) {
259293
return std::nullopt;
260294
}
@@ -265,7 +299,7 @@ std::optional<ConvertedTexture> parse_dds_bytes(ArrayRef<uint8_t> bytes) noexcep
265299
.format = parsedLayout->format,
266300
.width = header->width,
267301
.height = header->height,
268-
.mips = mipCount,
302+
.mips = *mipCount,
269303
.data = std::move(data),
270304
};
271305
}

lib/gfx/texture_replacement.cpp

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,23 @@ std::optional<aurora::gfx::ConvertedTexture> load_texture_file(const std::filesy
401401
return aurora::gfx::dds::load_dds_file(path);
402402
}
403403

404+
bool remove_mipmaps(aurora::gfx::ConvertedTexture& texture) noexcept {
405+
if (texture.mips <= 1) {
406+
return true;
407+
}
408+
409+
const uint64_t size = aurora::gfx::calc_texture_size(texture.format, texture.width, texture.height, 1);
410+
if (size == 0 || size > texture.data.size()) {
411+
return false;
412+
}
413+
414+
aurora::ByteBuffer data{static_cast<size_t>(size)};
415+
std::memcpy(data.data(), texture.data.data(), static_cast<size_t>(size));
416+
texture.mips = 1;
417+
texture.data = std::move(data);
418+
return true;
419+
}
420+
404421
constexpr bool is_unsupported_texture_format(wgpu::TextureFormat format) {
405422
switch (format) {
406423
case wgpu::TextureFormat::BC1RGBAUnorm:
@@ -464,6 +481,10 @@ std::optional<aurora::gfx::ConvertedTexture> load_file_replacement(const Replace
464481
return std::nullopt;
465482
}
466483

484+
if (base->mips > 1) {
485+
return base;
486+
}
487+
467488
std::vector<aurora::gfx::ConvertedTexture> more;
468489
std::error_code ec;
469490
for (uint32_t mipLevel = 1;; ++mipLevel) {
@@ -487,6 +508,11 @@ std::optional<aurora::gfx::ConvertedTexture> load_file_replacement(const Replace
487508

488509
break;
489510
}
511+
// If a sidecar mip file contains mipmaps, keep only the top level mip.
512+
if (!remove_mipmaps(*lvl)) {
513+
Log.warn("texture_replacement: could not slice first mip {}", fs_path_to_string(mipPath));
514+
break;
515+
}
490516
more.push_back(std::move(*lvl));
491517
}
492518

0 commit comments

Comments
 (0)