Browse Source

Reorganized the texture packing code to allow for multiple packing steps, with repeatable textures getting packed first.

master
Nico de Poel 3 years ago
parent
commit
435b62f280
  1. 1
      ps1bsp.h
  2. 134
      texture.cpp
  3. 1
      texture.h

1
ps1bsp.h

@ -58,6 +58,7 @@ typedef struct
{ {
unsigned short tpage; // Texture page in PS1 VRAM (precalculated when generating the texture atlas) unsigned short tpage; // Texture page in PS1 VRAM (precalculated when generating the texture atlas)
unsigned short nextframe; // If non-zero, the texture is animated and this points to the next texture in the sequence unsigned short nextframe; // If non-zero, the texture is animated and this points to the next texture in the sequence
unsigned int twin; // Texture window for repeating textures
} ps1bsp_texture_t; } ps1bsp_texture_t;
// This matches the SVECTOR data type; we can use the extra padding to store some more data. // This matches the SVECTOR data type; we can use the extra padding to store some more data.

134
texture.cpp

@ -25,6 +25,14 @@ Keep in mind that the coordinates will be rounded down to the next lowest textur
((((y) / 512) & 1) << 11) \ ((((y) / 512) & 1) << 11) \
) )
#define getTexWindow(x, y, w, h) ( \
0xe2000000 | \
((~((w)-1) & 0xFF)) >> 3 | \
(((~((h)-1) & 0xFF) >> 3) << 5) | \
((((x) & 0xFF) >> 3) << 10) | \
((((y) & 0xFF) >> 3) << 15) \
)
// A straight conversion of the Quake palette colors comes out looking rather over-saturated. // A straight conversion of the Quake palette colors comes out looking rather over-saturated.
// So we desaturate the palette ahead of time to more closely match the original Quake's look. // So we desaturate the palette ahead of time to more closely match the original Quake's look.
// Yoinked from: https://stackoverflow.com/questions/13328029/how-to-desaturate-a-color // Yoinked from: https://stackoverflow.com/questions/13328029/how-to-desaturate-a-color
@ -170,11 +178,11 @@ static void analyze_texture(unsigned char* texBytes, int numBytes, const Color p
desaturate(outTexture.dominantColor.channel, outTexture.dominantColor.channel); desaturate(outTexture.dominantColor.channel, outTexture.dominantColor.channel);
} }
bool process_textures(const world_t* world, TextureList& outTextures)
{
using spaces_type = rectpack2D::empty_spaces<false>;
using rect_type = rectpack2D::output_rect_t<spaces_type>;
using spaces_type = rectpack2D::empty_spaces<false>;
using rect_type = rectpack2D::output_rect_t<spaces_type>;
static rectpack2D::rect_wh pack_textures(const std::map<int, miptex_t*>& textures, const rectpack2D::rect_wh &max_bin, std::vector<rect_type>& outRectangles)
{
auto report_successful = [](rect_type&) { auto report_successful = [](rect_type&) {
return rectpack2D::callback_result::CONTINUE_PACKING; return rectpack2D::callback_result::CONTINUE_PACKING;
}; };
@ -184,21 +192,11 @@ bool process_textures(const world_t* world, TextureList& outTextures)
return rectpack2D::callback_result::ABORT_PACKING; return rectpack2D::callback_result::ABORT_PACKING;
}; };
const auto max_bin = rectpack2D::rect_wh(1024, 256); // 8-bit textures take up half the horizontal space so this is 512x256 in practice, or a quarter of the PS1's VRAM allocation.
const auto discard_step = -4; const auto discard_step = -4;
printf("Finding best texture packing...\n");
std::vector<rect_type> rectangles;
// Try some texture packing and see if we fit inside the PS1's VRAM
for (int texNum = 0; texNum < world->mipheader.numtex; ++texNum)
for (auto texIter = textures.cbegin(); texIter != textures.cend(); ++texIter)
{ {
miptex_t* miptex = &world->miptexes[texNum];
if (miptex->name[0] == '\0') // Weird edge case on N64START.bsp, corrupt data perhaps?
miptex->width = miptex->height = 0;
//printf("Texture %d (%dx%d): %.16s\n", texNum, miptex->width, miptex->height, miptex->name);
const miptex_t* miptex = texIter->second;
// Shrink the larger textures, but keep smaller ones at their original size // Shrink the larger textures, but keep smaller ones at their original size
int ps1mip = miptex->width > 64 || miptex->height > 64 ? 1 : 0; int ps1mip = miptex->width > 64 || miptex->height > 64 ? 1 : 0;
@ -220,13 +218,13 @@ bool process_textures(const world_t* world, TextureList& outTextures)
ps1mip--; ps1mip--;
if (strcmp(miptex->name, "clip") && strcmp(miptex->name, "trigger")) if (strcmp(miptex->name, "clip") && strcmp(miptex->name, "trigger"))
rectangles.emplace_back(rectpack2D::rect_xywh(0, 0, miptex->width >> ps1mip, miptex->height >> ps1mip));
outRectangles.emplace_back(rectpack2D::rect_xywh(0, 0, miptex->width >> ps1mip, miptex->height >> ps1mip));
else else
rectangles.emplace_back(rectpack2D::rect_xywh(0, 0, miptex->width >> 3, miptex->width >> 3)); // Add the lowest mip level so that it at least gets included in the final texture list, and we don't mess up the texture IDs
outRectangles.emplace_back(rectpack2D::rect_xywh(0, 0, miptex->width >> 3, miptex->width >> 3)); // Add the lowest mip level so that it at least gets included in the final texture list, and we don't mess up the texture IDs
} }
const auto result_size = rectpack2D::find_best_packing<spaces_type>(
rectangles,
return rectpack2D::find_best_packing<spaces_type>(
outRectangles,
rectpack2D::make_finder_input( rectpack2D::make_finder_input(
max_bin, max_bin,
discard_step, discard_step,
@ -235,9 +233,15 @@ bool process_textures(const world_t* world, TextureList& outTextures)
rectpack2D::flipping_option::DISABLED rectpack2D::flipping_option::DISABLED
) )
); );
}
printf("%d textures. Packed texture atlas size: %d x %d\n", world->mipheader.numtex, result_size.w, result_size.h);
static void build_atlas(const std::vector<miptex_t*>& textures, const std::vector<rect_type>& rectangles, const rectpack2D::rect_wh& size, unsigned char* outAtlas, TextureList& outTextures)
{
}
bool process_textures(const world_t* world, TextureList& outTextures)
{
// Construct palette CLUT and start building the output TIM structure
Color palette[PALETTE_SIZE]; Color palette[PALETTE_SIZE];
load_palette("palette.lmp", palette); load_palette("palette.lmp", palette);
@ -249,6 +253,29 @@ bool process_textures(const world_t* world, TextureList& outTextures)
outTim.clutYoffs = 0; outTim.clutYoffs = 0;
generate_clut(palette, &outTim); generate_clut(palette, &outTim);
const auto max_bin = rectpack2D::rect_wh(1024, 256); // 8-bit textures take up half the horizontal space so this is 512x256 in practice, or a quarter of the PS1's VRAM allocation.
printf("Finding best texture packing...\n");
std::map<int, miptex_t*> textures;
std::vector<rect_type> rectangles;
// Try some texture packing and see if we fit inside the PS1's VRAM
for (int texNum = 0; texNum < world->mipheader.numtex; ++texNum)
{
miptex_t* miptex = &world->miptexes[texNum];
if (miptex->name[0] == '\0') // Weird edge case on N64START.bsp, corrupt data perhaps?
miptex->width = miptex->height = 0;
//printf("Texture %d (%dx%d): %.16s\n", texNum, miptex->width, miptex->height, miptex->name);
textures[texNum] = miptex;
}
const auto result_size = pack_textures(textures, max_bin, rectangles);
printf("%d textures. Packed texture atlas size: %d x %d\n", world->mipheader.numtex, result_size.w, result_size.h);
outTim.imgWidth = result_size.w; outTim.imgWidth = result_size.w;
outTim.imgHeight = result_size.h; outTim.imgHeight = result_size.h;
outTim.imgData = malloc(result_size.w * result_size.h * sizeof(unsigned char)); outTim.imgData = malloc(result_size.w * result_size.h * sizeof(unsigned char));
@ -259,8 +286,6 @@ bool process_textures(const world_t* world, TextureList& outTextures)
printf("Constructing texture atlas...\n"); printf("Constructing texture atlas...\n");
std::unordered_map<std::string, std::unordered_map<int, int>> animationFrames;
// Try to construct the texture atlas, see what we get // Try to construct the texture atlas, see what we get
for (int texNum = 0; texNum < world->mipheader.numtex; ++texNum) for (int texNum = 0; texNum < world->mipheader.numtex; ++texNum)
{ {
@ -295,26 +320,7 @@ bool process_textures(const world_t* world, TextureList& outTextures)
tex.ps1tex.tpage = getTPage(outTim.format, 0, x, y); tex.ps1tex.tpage = getTPage(outTim.format, 0, x, y);
tex.uoffs = (u_char)((x % 64) << (2 - outTim.format)); tex.uoffs = (u_char)((x % 64) << (2 - outTim.format));
tex.voffs = (u_char)(y & 0xFF); tex.voffs = (u_char)(y & 0xFF);
if (miptex->name[0] == '+')
{
// Animated texture
int frameNum = miptex->name[1] - '0';
if (frameNum >= 0 && frameNum <= 9)
{
std::string animName = &miptex->name[2];
animationFrames[animName][frameNum] = texNum;
}
else
{
frameNum = miptex->name[1] - 'a';
if (frameNum >= 0 && frameNum <= 9)
{
std::string animName = std::string(&miptex->name[2]) + "_alt";
animationFrames[animName][frameNum] = texNum;
}
}
}
tex.ps1tex.twin = getTexWindow(tex.uoffs, tex.voffs, tex.w, tex.h); // TODO: figure out the right offsets that are multiples of w and h
} }
} }
@ -324,6 +330,32 @@ bool process_textures(const world_t* world, TextureList& outTextures)
outTextures.push_back(tex); outTextures.push_back(tex);
} }
// Identify animated textures
std::unordered_map<std::string, std::unordered_map<int, int>> animationFrames;
for (int texNum = 0; texNum < world->mipheader.numtex; ++texNum)
{
miptex_t* miptex = &world->miptexes[texNum];
if (miptex->name[0] == '+')
{
// Animated texture
int frameNum = miptex->name[1] - '0';
if (frameNum >= 0 && frameNum <= 9)
{
std::string animName = &miptex->name[2];
animationFrames[animName][frameNum] = texNum;
}
else
{
frameNum = miptex->name[1] - 'a';
if (frameNum >= 0 && frameNum <= 9)
{
std::string animName = std::string(&miptex->name[2]) + "_alt";
animationFrames[animName][frameNum] = texNum;
}
}
}
}
// Link animated texture frames together // Link animated texture frames together
for (auto animIter = animationFrames.cbegin(); animIter != animationFrames.cend(); ++animIter) for (auto animIter = animationFrames.cbegin(); animIter != animationFrames.cend(); ++animIter)
{ {
@ -349,3 +381,17 @@ bool process_textures(const world_t* world, TextureList& outTextures)
free(outTim.clutData); free(outTim.clutData);
return true; return true;
} }
bool texture_isRepeatable(miptex_t* miptex)
{
// Check if the texture is square
if (miptex->width != miptex->height)
return false;
// Check if width and height are powers of two (and a multiple of 8)
unsigned int pot = 8;
while (pot < miptex->width)
pot <<= 1;
return pot == miptex->width;
}

1
texture.h

@ -25,3 +25,4 @@ struct TextureDescriptor
typedef std::vector<TextureDescriptor> TextureList; typedef std::vector<TextureDescriptor> TextureList;
bool process_textures(const world_t* world, TextureList& outTextures); bool process_textures(const world_t* world, TextureList& outTextures);
bool texture_isRepeatable(miptex_t* miptex);
Loading…
Cancel
Save