render parity unit K: 40250 DDS mip levels
DXT DDS textures upload the file's own mip levels like 40250 CGraphicImageTexture::CreateDDSTexture (max(1, dwMipMapCount), level i read at sum(dwLinearSize>>2k), Copy of dwLinearSize>>2i bytes) instead of a regenerated chain. Non-DDS images keep the D3DX_DEFAULT full chain and mem: textures get one level. The invented is_ui_tex no-mip rule (icon/, ui/, locale/, <=128px) is deleted. dxt_decode_test is registered in ctest with mip-level cases. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
dda885a17a
commit
d2356d59c3
@@ -86,6 +86,10 @@ if(MT_HOST_BUILD)
|
||||
endif()
|
||||
|
||||
if(BUILD_TESTING AND MT_HOST_BUILD)
|
||||
add_executable(dxt_decode_test tests/dxt_decode_test.cpp src/dxt.cpp)
|
||||
target_include_directories(dxt_decode_test PRIVATE src)
|
||||
add_test(NAME dxt.decode COMMAND $<TARGET_FILE:dxt_decode_test>)
|
||||
|
||||
add_executable(net_cipher_test tests/net_cipher_test.cpp)
|
||||
target_link_libraries(net_cipher_test PRIVATE mtnet)
|
||||
add_test(NAME net.cipher_roundtrip COMMAND $<TARGET_FILE:net_cipher_test>)
|
||||
|
||||
+53
-23
@@ -1,6 +1,7 @@
|
||||
// engine/dxt —— DDS(DXT1/3/5) + 未压缩 BGRA8 → RGBA8 软解。M1 T3。
|
||||
#include "dxt.h"
|
||||
#include <cstdio>
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
namespace mtgodot {
|
||||
@@ -81,6 +82,35 @@ uint8_t channel(uint32_t pixel, uint32_t mask) {
|
||||
return uint8_t((uint64_t(pixel & mask) * 255 + mask / 2) / mask);
|
||||
}
|
||||
|
||||
void decode_dxt_level(Fmt fmt, const uint8_t* src, uint32_t w, uint32_t h, uint8_t* rgba) {
|
||||
const int block_bytes = (fmt == F_DXT1) ? 8 : 16;
|
||||
const size_t bx = (w + 3) / 4, by = (h + 3) / 4;
|
||||
for (size_t byi = 0; byi < by; ++byi) {
|
||||
for (size_t bxi = 0; bxi < bx; ++bxi) {
|
||||
const uint8_t* blk = src + (byi * bx + bxi) * block_bytes;
|
||||
uint8_t px[16][4];
|
||||
if (fmt == F_DXT1) {
|
||||
decode_color_block(blk, px, /*dxt1_alpha=*/true);
|
||||
} else {
|
||||
decode_color_block(blk + 8, px, /*dxt1_alpha=*/false);
|
||||
if (fmt == F_DXT3) decode_dxt3_alpha(blk, px);
|
||||
else decode_dxt5_alpha(blk, px);
|
||||
}
|
||||
for (int py = 0; py < 4; ++py) {
|
||||
size_t y = byi * 4 + py;
|
||||
if (y >= h) break;
|
||||
for (int pxx = 0; pxx < 4; ++pxx) {
|
||||
size_t x = bxi * 4 + pxx;
|
||||
if (x >= w) break;
|
||||
uint8_t* o = &rgba[(y * w + x) * 4];
|
||||
const uint8_t* s = px[py * 4 + pxx];
|
||||
o[0] = s[0]; o[1] = s[1]; o[2] = s[2]; o[3] = s[3];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Image decode(const uint8_t* d, size_t len) {
|
||||
Image img;
|
||||
if (len < 128 || std::memcmp(d, "DDS ", 4) != 0) return img;
|
||||
@@ -133,31 +163,31 @@ Image decode(const uint8_t* d, size_t len) {
|
||||
const int block_bytes = (fmt == F_DXT1) ? 8 : 16;
|
||||
const size_t bx = (w + 3) / 4, by = (h + 3) / 4;
|
||||
if (avail < bx * by * block_bytes) { img = Image{}; return img; }
|
||||
decode_dxt_level(fmt, src, uint32_t(w), uint32_t(h), img.rgba.data());
|
||||
|
||||
for (size_t byi = 0; byi < by; ++byi) {
|
||||
for (size_t bxi = 0; bxi < bx; ++bxi) {
|
||||
const uint8_t* blk = src + (byi * bx + bxi) * block_bytes;
|
||||
uint8_t px[16][4];
|
||||
if (fmt == F_DXT1) {
|
||||
decode_color_block(blk, px, /*dxt1_alpha=*/true);
|
||||
} else {
|
||||
decode_color_block(blk + 8, px, /*dxt1_alpha=*/false);
|
||||
if (fmt == F_DXT3) decode_dxt3_alpha(blk, px);
|
||||
else decode_dxt5_alpha(blk, px);
|
||||
}
|
||||
for (int py = 0; py < 4; ++py) {
|
||||
size_t y = byi * 4 + py;
|
||||
if (y >= h) break;
|
||||
for (int pxx = 0; pxx < 4; ++pxx) {
|
||||
size_t x = bxi * 4 + pxx;
|
||||
if (x >= w) break;
|
||||
uint8_t* o = &img.rgba[(y * w + x) * 4];
|
||||
const uint8_t* s = px[py * 4 + pxx];
|
||||
o[0] = s[0]; o[1] = s[1]; o[2] = s[2]; o[3] = s[3];
|
||||
}
|
||||
}
|
||||
}
|
||||
// 40250 CDXTCImage::LoadHeaderFromMemory + CGraphicImageTexture::CreateDDSTexture:
|
||||
// mipmapCount = max(1, min(dwMipMapCount, MAX_MIPLEVELS)); level i starts at
|
||||
// sum(dwLinearSize >> 2k) and Copy() memcpy's dwLinearSize >> 2i bytes of it. Only
|
||||
// with DDSD_MIPMAPCOUNT and without DDSD_PITCH are levels > 0 filled — otherwise 40250
|
||||
// creates them uninitialized; here that case is one level.
|
||||
const uint32_t dds_flags = rd_le32(d + 8);
|
||||
const uint32_t linear_size = rd_le32(d + 20);
|
||||
uint32_t mip_count = std::min<uint32_t>(rd_le32(d + 28), 12u);
|
||||
if (mip_count == 0) mip_count = 1;
|
||||
if (!(dds_flags & 0x20000u) || (dds_flags & 0x8u) || linear_size == 0) mip_count = 1;
|
||||
size_t offset = linear_size;
|
||||
for (uint32_t i = 1; i < mip_count; ++i) {
|
||||
const uint32_t lw = std::max<uint32_t>(1, w >> i), lh = std::max<uint32_t>(1, h >> i);
|
||||
const size_t level_bytes = ((lw + 3) / 4) * ((lh + 3) / 4) * size_t(block_bytes);
|
||||
const size_t copy_bytes = std::min<size_t>(size_t(linear_size) >> (2 * i), level_bytes);
|
||||
std::vector<uint8_t> blocks(level_bytes, 0);
|
||||
if (offset < avail) std::memcpy(blocks.data(), src + offset, std::min(copy_bytes, avail - offset));
|
||||
offset += size_t(linear_size) >> (2 * i);
|
||||
std::vector<uint8_t> level(size_t(lw) * lh * 4, 0);
|
||||
decode_dxt_level(fmt, blocks.data(), lw, lh, level.data());
|
||||
img.mips.push_back(std::move(level));
|
||||
}
|
||||
img.file_mip_levels = uint8_t(mip_count);
|
||||
img.format = (fmt == F_DXT1) ? "DXT1" : (fmt == F_DXT3) ? "DXT3" : "DXT5";
|
||||
return img;
|
||||
}
|
||||
|
||||
+5
-1
@@ -12,11 +12,15 @@ namespace mtgodot {
|
||||
struct Image {
|
||||
uint16_t w = 0, h = 0;
|
||||
std::vector<uint8_t> rgba; // w*h*4,level 0
|
||||
// DXT DDS only: level count 40250 CreateDDSTexture gives the texture (0 = not a DXT
|
||||
// DDS, caller follows the D3DX_DEFAULT full-chain path) and levels 1.. decoded to RGBA8.
|
||||
uint8_t file_mip_levels = 0;
|
||||
std::vector<std::vector<uint8_t>> mips;
|
||||
const char* format = ""; // "DXT1" / "DXT3" / "DXT5" / "BGRA8" / ""
|
||||
bool ok() const { return w && h && rgba.size() == size_t(w) * h * 4; }
|
||||
};
|
||||
|
||||
// 读整个 .dds 文件字节,解 level 0 到 RGBA8。失败返回 !ok()。
|
||||
// 读整个 .dds 文件字节,解 level 0 到 RGBA8(DXT 另解文件内 mip 到 mips)。失败返回 !ok()。
|
||||
Image load_dds(const uint8_t* bytes, size_t len);
|
||||
Image load_dds_path(const char* path);
|
||||
|
||||
|
||||
@@ -47,5 +47,23 @@ int main() {
|
||||
if (fourcc == 0x35545844) dxt[128] = 255;
|
||||
pixel(decode(dxt), 0, 255, 0, 0, 255);
|
||||
}
|
||||
std::puts("PASS: dxt_decode_test (masked RGB, pitch, invalid headers, DXT1/3/5)");
|
||||
{ // 40250 CreateDDSTexture levels: DDSD_MIPMAPCOUNT, level i at sum(linear >> 2k),
|
||||
// Copy() takes linear >> 2i bytes (the 2x2 level gets 2 of its 8 block bytes).
|
||||
auto mip = header(0, 0, 0, 0, 0, 32 + 8 + 2);
|
||||
put(mip, 8, 0x1007 | 0x20000 | 0x80000); put(mip, 12, 8); put(mip, 16, 8);
|
||||
put(mip, 20, 32); put(mip, 28, 3); put(mip, 80, 4); put(mip, 84, 0x31545844u);
|
||||
for (int blk = 0; blk < 4; ++blk) put(mip, 128 + blk * 8, 0x000007e0); // level 0 green
|
||||
put(mip, 160, 0x0000001f); // level 1 blue
|
||||
mip[168] = 0x00; mip[169] = 0xf8; // level 2 red
|
||||
auto im = decode(mip);
|
||||
assert(im.w == 8 && im.h == 8 && im.file_mip_levels == 3 && im.mips.size() == 2);
|
||||
pixel(im, 63, 0, 255, 0, 255);
|
||||
assert(im.mips[0].size() == 4 * 4 * 4 && im.mips[0][2] == 255 && im.mips[0][0] == 0);
|
||||
assert(im.mips[1].size() == 2 * 2 * 4 && im.mips[1][0] == 255 && im.mips[1][1] == 0);
|
||||
put(mip, 8, 0x1007 | 0x80000); // no DDSD_MIPMAPCOUNT -> only level 0 is filled
|
||||
im = decode(mip);
|
||||
assert(im.file_mip_levels == 1 && im.mips.empty());
|
||||
assert(decode(a1555).file_mip_levels == 0); // non-DXT -> D3DX full-chain path
|
||||
}
|
||||
std::puts("PASS: dxt_decode_test (masked RGB, pitch, invalid headers, DXT1/3/5, mip levels)");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user