#include "TextureConverter.h" #include "common/texture/texture_conversion.h" #include "common/util/Assert.h" #include "common/util/FileUtil.h" #include "third-party/fmt/core.h" TextureConverter::TextureConverter() { m_vram.resize(4 * 1024 * 1024); } void TextureConverter::upload(const u8* data, u32 dest, u32 size_vram_words) { // all textures are copied to vram 128 pixels wide, regardless of actual width int copy_width = 128; // scale the copy height to be whatever it needs to be to transfer the right amount of data. int copy_height = size_vram_words / copy_width; for (int y = 0; y < copy_height; y++) { for (int x = 0; x < copy_width; x++) { // VRAM address (bytes) auto addr32 = psmct32_addr(x, y, copy_width) + dest * 4; *(u32*)(m_vram.data() + addr32) = *((const u32*)(data) + (x + y * copy_width)); } } } void TextureConverter::download_rgba8888(u8* result, u32 vram_addr, u32 goal_tex_width, u32 w, u32 h, u32 psm, u32 clut_psm, u32 clut_vram_addr, u32 expected_size_bytes) { u32 out_offset = 0; if (psm == int(PSM::PSMT8) && clut_psm == int(CPSM::PSMCT32)) { // width is like the TEX0 register, in 64 texel units. // not sure what the other widths are yet. int read_width = 64 * goal_tex_width; // loop over pixels in output texture image for (u32 y = 0; y < h; y++) { for (u32 x = 0; x < w; x++) { // read as the PSMT8 type. The dest field tells us a block offset. auto addr8 = psmt8_addr(x, y, read_width) + vram_addr * 256; u8 value = *(u8*)(m_vram.data() + addr8); // there's yet another scramble from the CLUT. The palette index turns into an X, Y value // See GS manual 2.7.3 CLUT Storage Mode, IDTEX8 in CSM1 mode. u32 clut_chunk = value / 16; u32 off_in_chunk = value % 16; u8 clx = 0, cly = 0; if (clut_chunk & 1) { clx = 8; } cly = (clut_chunk >> 1) * 2; if (off_in_chunk >= 8) { off_in_chunk -= 8; cly++; } clx += off_in_chunk; // the x, y CLUT value is looked up in PSMCT32 mode u32 clut_addr = psmct32_addr(clx, cly, 64) + clut_vram_addr * 256; u32 clut_value = *(u32*)(m_vram.data() + clut_addr); memcpy(result + out_offset, &clut_value, 4); out_offset += 4; } } } else if (psm == int(PSM::PSMT8) && clut_psm == int(CPSM::PSMCT16)) { // width is like the TEX0 register, in 64 texel units. // not sure what the other widths are yet. int read_width = 64 * goal_tex_width; // loop over pixels in output texture image for (u32 y = 0; y < h; y++) { for (u32 x = 0; x < w; x++) { // read as the PSMT8 type. The dest field tells us a block offset. auto addr8 = psmt8_addr(x, y, read_width) + vram_addr * 256; u8 value = *(u8*)(m_vram.data() + addr8); // there's yet another scramble from the CLUT. The palette index turns into an X, Y value // See GS manual 2.7.3 CLUT Storage Mode, IDTEX8 in CSM1 mode. u32 clut_chunk = value / 16; u32 off_in_chunk = value % 16; u8 clx = 0, cly = 0; if (clut_chunk & 1) { clx = 8; } cly = (clut_chunk >> 1) * 2; if (off_in_chunk >= 8) { off_in_chunk -= 8; cly++; } clx += off_in_chunk; // the x, y CLUT value is looked up in PSMCT32 mode u32 clut_addr = psmct16_addr(clx, cly, 64) + clut_vram_addr * 256; u32 clut_value = *(u16*)(m_vram.data() + clut_addr); u32 rgba32 = rgba16_to_rgba32(clut_value); memcpy(result + out_offset, &rgba32, 4); out_offset += 4; } } } else if (psm == int(PSM::PSMT4) && clut_psm == int(CPSM::PSMCT16)) { // width is like the TEX0 register, in 64 texel units. // not sure what the other widths are yet. int read_width = 64 * goal_tex_width; // loop over pixels in output texture image for (u32 y = 0; y < h; y++) { for (u32 x = 0; x < w; x++) { // read as the PSMT4 type, use half byte addressing auto addr4 = psmt4_addr_half_byte(x, y, read_width) + vram_addr * 512; // read (half bytes) u8 value = *(u8*)(m_vram.data() + addr4 / 2); if (addr4 & 1) { value >>= 4; } else { value = value & 0x0f; } // there's yet another scramble from the CLUT. The palette index turns into an X, Y value // See GS manual 2.7.3 CLUT Storage Mode, IDTEX4 in CSM1 mode. u8 clx = value & 0x7; u8 cly = value >> 3; // the x, y CLUT value is looked up in PSMCT16 mode u32 clut_addr = psmct16_addr(clx, cly, 64) + clut_vram_addr * 256; u32 clut_value = *(u16*)(m_vram.data() + clut_addr); u32 rgba32 = rgba16_to_rgba32(clut_value); memcpy(result + out_offset, &rgba32, 4); out_offset += 4; } } } else if (psm == int(PSM::PSMT4) && clut_psm == int(CPSM::PSMCT32)) { // width is like the TEX0 register, in 64 texel units. // not sure what the other widths are yet. int read_width = 64 * goal_tex_width; // loop over pixels in output texture image for (u32 y = 0; y < h; y++) { for (u32 x = 0; x < w; x++) { // read as the PSMT4 type, use half byte addressing auto addr4 = psmt4_addr_half_byte(x, y, read_width) + vram_addr * 512; // read (half bytes) u8 value = *(u8*)(m_vram.data() + addr4 / 2); if (addr4 & 1) { value >>= 4; } else { value = value & 0x0f; } // there's yet another scramble from the CLUT. The palette index turns into an X, Y value // See GS manual 2.7.3 CLUT Storage Mode, IDTEX4 in CSM1 mode. u8 clx = value & 0x7; u8 cly = value >> 3; // the x, y CLUT value is looked up in PSMCT16 mode u32 clut_addr = psmct32_addr(clx, cly, 64) + clut_vram_addr * 256; u32 clut_value = *(u32*)(m_vram.data() + clut_addr); // fmt::print("{} {}\n", value, clut_value); memcpy(result + out_offset, &clut_value, 4); out_offset += 4; } } } else if (psm == int(PSM::PSMCT16) && clut_psm == 0) { // plain 16-bit texture // not a clut. // will store output pixels, rgba (8888) // width is like the TEX0 register, in 64 texel units. // not sure what the other widths are yet. int read_width = 64 * goal_tex_width; // loop over pixels in output texture image for (u32 y = 0; y < h; y++) { for (u32 x = 0; x < w; x++) { // read as the PSMT8 type. The dest field tells us a block offset. auto addr8 = psmct16_addr(x, y, read_width) + vram_addr * 256; u16 value = *(u16*)(m_vram.data() + addr8); u32 val32 = rgba16_to_rgba32(value); memcpy(result + out_offset, &val32, 4); out_offset += 4; } } } else { ASSERT(false); } ASSERT(out_offset == expected_size_bytes); } void TextureConverter::serialize(Serializer& ser) { ser.from_pod_vector(&m_vram); }