mirror of
https://github.com/encounter/aurora.git
synced 2026-07-09 18:19:33 -07:00
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:
+38
-4
@@ -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),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user