-
Notifications
You must be signed in to change notification settings - Fork 208
Refactor Code of TextureConverter #3844
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,26 +10,22 @@ 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; | ||
| void TextureConverter::upload(const u8* data, u32 dest, u32 width, u32 height, u32 size_vram_words) { | ||
| // Validation (optional) | ||
| if ((size_vram_words == 0 && (width == 0 || height == 0)) || data == nullptr) { | ||
| throw std::invalid_argument( | ||
| "Invalid parameters: Provide either size_vram_words or width and height, and ensure data " | ||
| "is not null."); | ||
| } | ||
|
|
||
| // Calculate Width and Height from Parameters | ||
| int copy_width = (size_vram_words > 0) ? 128 : width; | ||
| int copy_height = (size_vram_words > 0) ? (size_vram_words / copy_width) : height; | ||
|
|
||
| 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::upload_width(const u8* data, u32 dest, u32 width, u32 height) { | ||
| for (u32 y = 0; y < height; y++) { | ||
| for (u32 x = 0; x < width; x++) { | ||
| // VRAM address (bytes) | ||
| auto addr32 = psmct32_addr(x, y, width) + dest * 256; | ||
| auto addr32 = psmct32_addr(x, y, copy_width) + dest * ((size_vram_words > 0) ? 4 : 256); | ||
| *(u32*)(m_vram.data() + addr32) = *((const u32*)(data) + (x + y * width)); | ||
| } | ||
| } | ||
|
|
@@ -45,167 +41,67 @@ void TextureConverter::download_rgba8888(u8* result, | |
| 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; | ||
| } | ||
| int read_width = 64 * goal_tex_width; | ||
|
|
||
| // Helper for CLUT addressing | ||
| auto calculate_clut_address = [&](u8 value, int clut_psm) -> u32 { | ||
| u32 clut_chunk = value / 16; | ||
| u32 off_in_chunk = value % 16; | ||
| u8 clx = (clut_chunk & 1) ? 8 : 0; | ||
| u8 cly = (clut_chunk >> 1) * 2; | ||
| if (off_in_chunk >= 8) { | ||
| off_in_chunk -= 8; | ||
| cly++; | ||
| } | ||
|
|
||
| } 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; | ||
| } | ||
| clx += off_in_chunk; | ||
| if (clut_psm == int(CPSM::PSMCT32)) { | ||
| return psmct32_addr(clx, cly, 64) + clut_vram_addr * 256; | ||
| } else if (clut_psm == int(CPSM::PSMCT16)) { | ||
| return psmct16_addr(clx, cly, 64) + clut_vram_addr * 256; | ||
| } | ||
|
|
||
| } 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; | ||
| } | ||
| ASSERT(false); // Invalid CLUT format | ||
| return 0; | ||
| }; | ||
|
|
||
| // Pixel processing function | ||
| auto process_pixel = [&](u32 x, u32 y, u32 addr, int clut_psm) -> u32 { | ||
|
||
| u8 value = *(u8*)(m_vram.data() + addr); | ||
|
||
| u32 clut_addr = calculate_clut_address(value, clut_psm); | ||
| if (clut_psm == int(CPSM::PSMCT32)) { | ||
| return *(u32*)(m_vram.data() + clut_addr); | ||
| } else if (clut_psm == int(CPSM::PSMCT16)) { | ||
| return rgba16_to_rgba32(*(u16*)(m_vram.data() + clut_addr)); | ||
| } | ||
| } 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 | ||
| ASSERT(false); | ||
| return 0; | ||
| }; | ||
|
|
||
| // Main loop | ||
| for (u32 y = 0; y < h; y++) { | ||
| for (u32 x = 0; x < w; x++) { | ||
| u32 addr = 0; | ||
|
||
| if (psm == int(PSM::PSMT8)) { | ||
| addr = psmt8_addr(x, y, read_width) + vram_addr * 256; | ||
|
||
| } else if (psm == int(PSM::PSMT4)) { | ||
| 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); | ||
| value = (addr4 & 1) ? (value >> 4) : (value & 0x0F); | ||
| addr = value; | ||
|
||
| } else if (psm == int(PSM::PSMCT16) && clut_psm == 0) { | ||
| addr = psmct16_addr(x, y, read_width) + vram_addr * 256; | ||
| u16 value = *(u16*)(m_vram.data() + addr); | ||
| u32 rgba32 = rgba16_to_rgba32(value); | ||
| memcpy(result + out_offset, &rgba32, 4); | ||
| out_offset += 4; | ||
| continue; | ||
| } | ||
| } | ||
| } 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; | ||
| } | ||
| // Process pixel through CLUT | ||
| u32 rgba_value = process_pixel(x, y, addr, clut_psm); | ||
| memcpy(result + out_offset, &rgba_value, 4); | ||
| out_offset += 4; | ||
| } | ||
| } | ||
|
|
||
| else { | ||
| ASSERT(false); | ||
| } | ||
|
|
||
| ASSERT(out_offset == expected_size_bytes); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,8 +7,40 @@ | |
| class TextureConverter { | ||
| public: | ||
| TextureConverter(); | ||
| void upload(const u8* data, u32 dest, u32 size_vram_words); | ||
| void upload_width(const u8* data, u32 dest, u32 width, u32 height); | ||
|
|
||
| /** | ||
| * @brief Copies PS2 texture data into simulated VRAM. | ||
| * | ||
| * Copies texture data from the provided source pointer (`data`) into the simulated | ||
| * PlayStation 2 VRAM (`m_vram`). The texture size can be defined using either | ||
| * `width` and `height` or `size_vram_words`. | ||
| * | ||
| * @param data Pointer to the source texture data. | ||
| * @param dest Destination address in the simulated VRAM. | ||
| * @param width (Optional) Texture width in texels. Used if `size_vram_words` is not provided. | ||
| * @param height (Optional) Texture height in texels. Used if `size_vram_words` is not provided. | ||
| * @param size_vram_words (Optional) Texture size in VRAM words. Assumes a width of 128 texels. | ||
| */ | ||
| void upload(const u8* data, u32 dest, u32 width = 0, u32 height = 0, u32 size_vram_words = 0); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I prefer to use default arguments only when the defaults are valid, and prefer multiple implementations over tricky use of defaults. For example,
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the test-case with converter.upload(data,dest) is validated by: |
||
|
|
||
| /** | ||
| * @brief Converts PS2 texture data from simulated VRAM into RGBA8888 format. | ||
| * | ||
| * This function processes PS2 texture data stored in simulated VRAM and converts it | ||
| * into the RGBA8888 format, storing the result in the provided buffer (`result`). | ||
| * It supports multiple PS2 formats, including PSMT8, PSMT4, and PSMCT16, and handles | ||
| * CLUT-based textures by accessing the appropriate color lookup table (CLUT). | ||
| * | ||
| * @param result Pointer to the output buffer where RGBA8888 data will be stored. | ||
| * @param vram_addr Starting address in the simulated VRAM for the texture data. | ||
| * @param goal_tex_width Width of the texture in 64-texel units, as defined by PS2 format. | ||
| * @param w Width of the texture in texels. | ||
| * @param h Height of the texture in texels. | ||
| * @param psm PS2 pixel storage format (e.g., PSMT8, PSMT4, PSMCT16). | ||
| * @param clut_psm PS2 CLUT format (e.g., PSMCT32, PSMCT16). | ||
| * @param clut_vram_addr Address of the CLUT in simulated VRAM, if applicable. | ||
| * @param expected_size_bytes Expected size of the output buffer in bytes for validation. | ||
| */ | ||
| void download_rgba8888(u8* result, | ||
| u32 vram_addr, | ||
| u32 goal_tex_width, | ||
|
|
@@ -19,6 +51,7 @@ class TextureConverter { | |
| u32 clut_vram_addr, | ||
| u32 expected_size_bytes); | ||
|
|
||
|
|
||
| private: | ||
| std::vector<u8> m_vram; | ||
| }; | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This scrambling is only valid for PSMT8 index textures and doesn't work for PSMT4 (see deleted comments about consulting
GS manual 2.7.3 CLUT Storage Mode, IDTEX8 in CSM1 mode.)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
True. Fixed it