feat: DDS 纹理加载器(BC 压缩直传 + mip 链,参考 Horizon 实现)
- Assets::Dds 解析器:中性 DdsFormat 枚举(Assets 层禁 Vulkan 头铁律), 支持 legacy fourcc(DXT1/3/5/ATI1/2)+ DX10 头(BC1..BC7/RGBA8/BGRA8) + 未压缩 32bpp + 2D mip 链;数据不解码,直接上传由硬件采样解码 - Texture:按扩展名分发(.dds → InitFromDds);渲染侧负责格式映射与 sRGB 语义(颜色图走 SRGB 变体,BC4/5/6H 数据图保持线性);view/sampler 覆盖完整 mip 链;逐 mip copyBufferToImage(紧凑排布) - Model::GetOrLoadTexture:FBX/DCC 路径反斜杠归一化(否则 Linux 全挂) - DescriptorManager 池 1000 → 4096(SunTemple 1056 材质集打爆过; 材质去重属材质系统任务) - main.cpp 临时挂 SunTemple 场景验证:48 张 DDS 全部加载(2048²、12 mips), 1056 mesh 渲染正常,validation 零警告
This commit is contained in:
@@ -0,0 +1,170 @@
|
||||
//
|
||||
// Created by atdunbg on 2026/9/23.
|
||||
//
|
||||
|
||||
#include <Engine/Assets/DdsLoader.h>
|
||||
#include <Engine/Core/Log.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
|
||||
namespace Engine::Assets::Dds
|
||||
{
|
||||
namespace
|
||||
{
|
||||
constexpr u32 kMagic = 0x20534444; // "DDS "
|
||||
|
||||
// 头部偏移(dwSpfFourCC @ 84)
|
||||
constexpr size_t kOffsetHeight = 12;
|
||||
constexpr size_t kOffsetWidth = 16;
|
||||
constexpr size_t kOffsetMips = 28;
|
||||
constexpr size_t kOffsetFourCC = 84;
|
||||
constexpr size_t kHeaderSize = 128;
|
||||
constexpr size_t kDx10Size = 20;
|
||||
|
||||
u32 ReadU32(const u8* p) { return *reinterpret_cast<const u32*>(p); }
|
||||
|
||||
// legacy fourcc → 中性格式
|
||||
bool MapFourCC(u32 fourcc, DdsFormat& out)
|
||||
{
|
||||
switch (fourcc) {
|
||||
case 0x31545844: out = DdsFormat::BC1; return true; // "DXT1"
|
||||
case 0x33545844: out = DdsFormat::BC2; return true; // "DXT3"
|
||||
case 0x35545844: out = DdsFormat::BC3; return true; // "DXT5"
|
||||
case 0x31495441: out = DdsFormat::BC4; return true; // "ATI1"
|
||||
case 0x55344342: out = DdsFormat::BC4; return true; // "BC4U"
|
||||
case 0x32495441: out = DdsFormat::BC5; return true; // "ATI2"
|
||||
case 0x55354342: out = DdsFormat::BC5; return true; // "BC5U"
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
// DX10 DXGI 值 → 中性格式
|
||||
bool MapDxgi(u32 dxgi, DdsFormat& out)
|
||||
{
|
||||
switch (dxgi) {
|
||||
case 70: case 71: out = DdsFormat::BC1; return true; // BC1
|
||||
case 73: case 74: out = DdsFormat::BC2; return true; // BC2
|
||||
case 77: case 78: out = DdsFormat::BC3; return true; // BC3
|
||||
case 79: case 80: out = DdsFormat::BC4; return true; // BC4
|
||||
case 82: case 83: out = DdsFormat::BC5; return true; // BC5
|
||||
case 94: case 95: out = DdsFormat::BC6H; return true; // BC6H
|
||||
case 97: case 98: out = DdsFormat::BC7; return true; // BC7
|
||||
case 28: case 29: out = DdsFormat::RGBA8; return true; // R8G8B8A8
|
||||
case 87: case 91: out = DdsFormat::BGRA8; return true; // B8G8R8A8(近似)
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
u32 BytesPerBlock(DdsFormat format)
|
||||
{
|
||||
switch (format) {
|
||||
case DdsFormat::BC1:
|
||||
case DdsFormat::BC4:
|
||||
return 8;
|
||||
case DdsFormat::BC2:
|
||||
case DdsFormat::BC3:
|
||||
case DdsFormat::BC5:
|
||||
case DdsFormat::BC6H:
|
||||
case DdsFormat::BC7:
|
||||
return 16;
|
||||
default:
|
||||
return 4; // RGBA8/BGRA8 系
|
||||
}
|
||||
}
|
||||
|
||||
bool IsBlockCompressed(DdsFormat format)
|
||||
{
|
||||
return format != DdsFormat::RGBA8 && format != DdsFormat::BGRA8;
|
||||
}
|
||||
}
|
||||
|
||||
bool Load(const std::string& path, DdsData& out, std::string& errMsg)
|
||||
{
|
||||
std::ifstream f(path, std::ios::binary);
|
||||
if (!f) {
|
||||
errMsg = "无法打开文件";
|
||||
return false;
|
||||
}
|
||||
|
||||
u8 header[kHeaderSize];
|
||||
f.read(reinterpret_cast<char*>(header), kHeaderSize);
|
||||
if (!f || ReadU32(header) != kMagic) {
|
||||
errMsg = "DDS 魔数不符";
|
||||
return false;
|
||||
}
|
||||
|
||||
out.Width = ReadU32(header + kOffsetWidth);
|
||||
out.Height = ReadU32(header + kOffsetHeight);
|
||||
const u32 mips = ReadU32(header + kOffsetMips);
|
||||
out.MipLevels = mips ? mips : 1;
|
||||
|
||||
const u32 fourcc = ReadU32(header + kOffsetFourCC);
|
||||
size_t dataOffset = kHeaderSize;
|
||||
|
||||
if (fourcc == 0x30313044) { // "DX10"
|
||||
u8 dx10[kDx10Size];
|
||||
f.read(reinterpret_cast<char*>(dx10), kDx10Size);
|
||||
if (!f) {
|
||||
errMsg = "DX10 头不完整";
|
||||
return false;
|
||||
}
|
||||
const u32 dxgi = ReadU32(dx10);
|
||||
if (!MapDxgi(dxgi, out.Format)) {
|
||||
errMsg = "DX10 DXGI 格式不支持";
|
||||
return false;
|
||||
}
|
||||
dataOffset += kDx10Size;
|
||||
} else if (!MapFourCC(fourcc, out.Format)) {
|
||||
// 未压缩路径:仅支持 32bpp RGBA(DDPF_ALPHAPIXELS(0x1) | DDPF_RGB(0x40))
|
||||
const u32 ddspfFlags = ReadU32(header + 80);
|
||||
if (ddspfFlags & 0x41) {
|
||||
out.Format = DdsFormat::BGRA8; // 常见 32bpp DDS 是 B8G8R8A8 字节序
|
||||
ENGINE_WARN("DDS: 未压缩 32bpp 按 BGRA8 上传 '{}'", path);
|
||||
} else {
|
||||
errMsg = "fourcc 不支持";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
out.BlockCompressed = IsBlockCompressed(out.Format);
|
||||
f.seekg(dataOffset);
|
||||
|
||||
// 数据量校验 + 逐级读取(紧凑排布)
|
||||
const u32 bpb = BytesPerBlock(out.Format);
|
||||
const u32 cols = out.BlockCompressed ? 4 : 1;
|
||||
out.Mips.resize(out.MipLevels);
|
||||
|
||||
u32 w = out.Width, h = out.Height;
|
||||
for (u32 mip = 0; mip < out.MipLevels; ++mip) {
|
||||
const u32 blocksW = std::max(1u, (w + cols - 1) / cols);
|
||||
const u32 blocksH = std::max(1u, (h + cols - 1) / cols);
|
||||
const size_t bytes = size_t(blocksW) * blocksH * bpb;
|
||||
out.Mips[mip].resize(bytes);
|
||||
f.read(reinterpret_cast<char*>(out.Mips[mip].data()),
|
||||
std::streamsize(bytes));
|
||||
if (!f) {
|
||||
errMsg = "mip 数据不完整";
|
||||
return false;
|
||||
}
|
||||
w = std::max(1u, w / 2);
|
||||
h = std::max(1u, h / 2);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
u32 RowPitch(const DdsData& data, u32 mipLevel)
|
||||
{
|
||||
u32 w = data.Width;
|
||||
for (u32 m = 0; m < mipLevel; ++m) {
|
||||
w = std::max(1u, w / 2);
|
||||
}
|
||||
const u32 bpb = BytesPerBlock(data.Format);
|
||||
if (data.BlockCompressed) {
|
||||
return std::max(1u, (w + 3) / 4) * bpb;
|
||||
}
|
||||
return w * bpb;
|
||||
}
|
||||
|
||||
} // namespace Engine::Assets::Dds
|
||||
Reference in New Issue
Block a user