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>
This commit is contained in:
Trevor Anderson
2026-06-06 10:12:44 -04:00
committed by GitHub
parent 45a732e295
commit 19479a53e4
2 changed files with 64 additions and 4 deletions
+38 -4
View File
@@ -1,5 +1,6 @@
#include "dds_io.hpp"
#include <algorithm>
#include <cstring>
#include <filesystem>
#include <fstream>
@@ -52,6 +53,11 @@ struct DDSHeaderDX10 {
static_assert(sizeof(DDSHeaderDX10) == 20);
constexpr uint32_t kDDSMagic = 0x20534444; // "DDS"
constexpr uint32_t kDDSDMipmapCount = 0x00020000;
constexpr uint32_t kDDSCapsComplex = 0x00000008;
constexpr uint32_t kDDSCapsMipmap = 0x00400000;
constexpr uint32_t kDDSCaps2Cubemap = 0x00000200;
constexpr uint32_t kDDSCaps2Volume = 0x00200000;
bool ensure_directory(const std::filesystem::path& dir) noexcept {
std::error_code ec;
@@ -101,12 +107,36 @@ bool validate_dds_header(const DDSHeader& header) noexcept {
if (header.width == 0 || header.height == 0) {
return false;
}
if ((header.caps2 & (0x00000200 | 0x00200000)) != 0) { // Unsupported
if ((header.caps2 & (kDDSCaps2Cubemap | kDDSCaps2Volume)) != 0) { // Unsupported
return false;
}
return true;
}
uint32_t max_mip_count(uint32_t width, uint32_t height) noexcept {
uint32_t count = 1;
while (width > 1 || height > 1) {
width = std::max(width >> 1, 1u);
height = std::max(height >> 1, 1u);
++count;
}
return count;
}
std::optional<uint32_t> resolve_mip_count(const DDSHeader& header) noexcept {
if (header.mipMapCount <= 1) {
return 1;
}
const bool hasMipFlags = (header.flags & kDDSDMipmapCount) != 0 && (header.caps & kDDSCapsComplex) != 0 &&
(header.caps & kDDSCapsMipmap) != 0;
if (!hasMipFlags || header.mipMapCount > max_mip_count(header.width, header.height)) {
return std::nullopt;
}
return header.mipMapCount;
}
std::optional<wgpu::TextureFormat> resolve_dx10_format(uint32_t dxgiFormat) noexcept {
switch (dxgiFormat) {
case 28:
@@ -253,8 +283,12 @@ std::optional<ConvertedTexture> parse_dds_bytes(ArrayRef<uint8_t> bytes) noexcep
return std::nullopt;
}
const uint32_t mipCount = 1u;
const auto expectedSize = calc_texture_size(parsedLayout->format, header->width, header->height, mipCount);
const auto mipCount = resolve_mip_count(*header);
if (!mipCount.has_value()) {
return std::nullopt;
}
const auto expectedSize = calc_texture_size(parsedLayout->format, header->width, header->height, *mipCount);
if (expectedSize == 0 || parsedLayout->dataOffset + expectedSize > bytes.size()) {
return std::nullopt;
}
@@ -265,7 +299,7 @@ std::optional<ConvertedTexture> parse_dds_bytes(ArrayRef<uint8_t> bytes) noexcep
.format = parsedLayout->format,
.width = header->width,
.height = header->height,
.mips = mipCount,
.mips = *mipCount,
.data = std::move(data),
};
}
+26
View File
@@ -401,6 +401,23 @@ std::optional<aurora::gfx::ConvertedTexture> load_texture_file(const std::filesy
return aurora::gfx::dds::load_dds_file(path);
}
bool remove_mipmaps(aurora::gfx::ConvertedTexture& texture) noexcept {
if (texture.mips <= 1) {
return true;
}
const uint64_t size = aurora::gfx::calc_texture_size(texture.format, texture.width, texture.height, 1);
if (size == 0 || size > texture.data.size()) {
return false;
}
aurora::ByteBuffer data{static_cast<size_t>(size)};
std::memcpy(data.data(), texture.data.data(), static_cast<size_t>(size));
texture.mips = 1;
texture.data = std::move(data);
return true;
}
constexpr bool is_unsupported_texture_format(wgpu::TextureFormat format) {
switch (format) {
case wgpu::TextureFormat::BC1RGBAUnorm:
@@ -464,6 +481,10 @@ std::optional<aurora::gfx::ConvertedTexture> load_file_replacement(const Replace
return std::nullopt;
}
if (base->mips > 1) {
return base;
}
std::vector<aurora::gfx::ConvertedTexture> more;
std::error_code ec;
for (uint32_t mipLevel = 1;; ++mipLevel) {
@@ -487,6 +508,11 @@ std::optional<aurora::gfx::ConvertedTexture> load_file_replacement(const Replace
break;
}
// If a sidecar mip file contains mipmaps, keep only the top level mip.
if (!remove_mipmaps(*lvl)) {
Log.warn("texture_replacement: could not slice first mip {}", fs_path_to_string(mipPath));
break;
}
more.push_back(std::move(*lvl));
}