Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions game/graphics/opengl_renderer/TextureAnimator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2005,7 +2005,7 @@ void TextureAnimator::load_clut_to_converter() {

switch (clut_lookup->second.kind) {
case VramEntry::Kind::CLUT16_16_IN_PSM32:
m_converter.upload_width(clut_lookup->second.data.data(), m_current_shader.tex0.cbp(), 16,
m_converter.upload(clut_lookup->second.data.data(), m_current_shader.tex0.cbp(), 16,
16);
break;
default:
Expand Down Expand Up @@ -2104,8 +2104,8 @@ GLuint TextureAnimator::make_or_get_gpu_texture_for_current_shader(TexturePool&
}
} else {
Timer timer;
m_converter.upload_width(vram_entry->data.data(), m_current_shader.tex0.tbp0(),
vram_entry->tex_width, vram_entry->tex_height);
m_converter.upload(vram_entry->data.data(), m_current_shader.tex0.tbp0(),
vram_entry->tex_width, vram_entry->tex_height);

// also needs clut lookup
load_clut_to_converter();
Expand Down
230 changes: 63 additions & 167 deletions game/graphics/texture/TextureConverter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
}
Expand All @@ -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++;
Copy link
Collaborator

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.)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True. Fixed it

}

} 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 {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

x and y are not used in this lambda

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yea i refactored everything again

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment is not very meaningful, and it's not intuitive to figure out what this function does. Is addr an address into the clut or the index texture? What does it return? Something like

auto lookup_rgba_from_clut = [&](u32 index_texture_value, int clut_psm) -> u32

may be more clear.

Or, since this lambda is only used one, you could avoid creating this entirely.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The lambda has been renamed, but since its called twice i avoided removing it

u8 value = *(u8*)(m_vram.data() + addr);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method of reading doesn't handle 4-bit index textures correctly (PSMT4)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed it

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;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This addr variable is used incorrectly - in some cases it is treated as an address into the index texture, and others it is a value from the index texture (which is an encoding of an address into the clut)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to initialize address depending on the old code. Now it should be right. Hopefully

if (psm == int(PSM::PSMT8)) {
addr = psmt8_addr(x, y, read_width) + vram_addr * 256;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here, it is an address into the index texture.

} 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;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here, it is set to a value from the index texture, which contains an encoded address into the clut. (notice the u8 value = *(u8*)(m_vram.data() + addr4 / 2); that's reading the texture).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Jep that was wrong. Fixed it

} 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);
}
37 changes: 35 additions & 2 deletions game/graphics/texture/TextureConverter.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Copy link
Collaborator

Choose a reason for hiding this comment

The 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, converter.upload(data, dest); will fail the validation, but looks like a valid call.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the test-case with converter.upload(data,dest) is validated by:
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.");
}


/**
* @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,
Expand All @@ -19,6 +51,7 @@ class TextureConverter {
u32 clut_vram_addr,
u32 expected_size_bytes);


private:
std::vector<u8> m_vram;
};