add a median cut implementation for converting wad3 8bpp textures to 4bpp

This commit is contained in:
Shpuld Shpuldson 2023-09-21 23:13:06 +03:00
parent 070e651d7d
commit b2c43cbe5a
4 changed files with 289 additions and 13 deletions

View file

@ -29,7 +29,7 @@ void GL_Upload8(int texture_index, const byte *data, int width, int height);
void GL_Upload16(int texture_index, const byte *data, int width, int height);
int GL_LoadTexture(const char *identifier, int width, int height, const byte *data, qboolean stretch_to_power_of_two, int filter, int mipmap_level);
// CLUT4
int GL_LoadTexture4(const char *identifier, unsigned int width, unsigned int height, const byte *data, int filter);
int GL_LoadTexture4(const char *identifier, unsigned int width, unsigned int height, const byte *data, int filter, qboolean swizzled);
int GL_LoadTextureLM (const char *identifier, int width, int height, const byte *data, int bpp, int filter, qboolean update, int forcopy);
int GL_LoadImages (const char *identifier, int width, int height, const byte *data, qboolean stretch_to_power_of_two, int filter, int mipmap_level, int bpp);
@ -42,6 +42,8 @@ void GL_GetPixelsBGR (byte *buffer, int width, int height, int i);
void GL_GetPixelsRGB (byte *buffer, int width, int height, int i);
void GL_GetPixelsRGBA(byte *buffer, int width, int height, int i);
void swizzle_fast(u8* out, const u8* in, unsigned int width, unsigned int height);
#define PAL_RGB 24
#define PAL_RGBA 32
@ -329,7 +331,7 @@ void EmitSkyPolys (msurface_t *fa);
void EmitReflectivePolys (msurface_t *fa);
void EmitScrollPolys (msurface_t *fa);
void EmitBothSkyLayers (msurface_t *fa);
void EmitUnderWaterPolys (void);
// void EmitUnderWaterPolys (void);
void EmitDetailPolys (void);
void R_DrawSkyChain (msurface_t *s);
int R_FrustumCheckBox (vec3_t mins, vec3_t maxs);
@ -418,3 +420,6 @@ int D_DrawParticleBuffered (psp_particle* vertices, particle2_t *pparticl
extern int zombie_skins[2][2];
extern int faces_rejected, faces_checked, faces_clipped;

View file

@ -97,7 +97,6 @@ typedef struct
int loadtextureimage (char* filename, int matchwidth, int matchheight, qboolean complain, int filter);
void VID_SetPalette4(unsigned char* clut4pal);
int vid_palmode;
#define MAX_GLTEXTURES 1024
gltexture_t gltextures[MAX_GLTEXTURES];
@ -116,7 +115,6 @@ void GL_Bind4Part(int texture_index)
{
const gltexture_t& texture = gltextures[texture_index];
VID_SetPalette4(texture.palette);
vid_palmode = GU_PSM_T4;
sceGuTexMode(texture.format, 0, 0, GU_FALSE);
@ -209,7 +207,10 @@ void GL_Bind (int texture_index)
if (last_palette_wasnt_tx == qtrue)
VID_SetPaletteTX();
vid_palmode = GU_PSM_T8;
if (texture.format == GU_PSM_T4) {
VID_SetPalette4(texture.palette);
}
sceGuTexMode(texture.format, texture.mipmaps , 0, texture.swizzle);
// Set the Texture filter.
@ -1712,7 +1713,7 @@ void swizzle(u8* out, const u8* in, unsigned int width, unsigned int height)
swizzle_fast
================
*/
static void swizzle_fast(u8* out, const u8* in, unsigned int width, unsigned int height)
void swizzle_fast(u8* out, const u8* in, unsigned int width, unsigned int height)
{
unsigned int blockx, blocky;
unsigned int j;
@ -3338,13 +3339,12 @@ void GL_Upload4(int texture_index, const byte *data, int width, int height)
memcpy(texture.ram, data, buffer_size);
memcpy(texture.palette, data + buffer_size, 16 * 4);
int i;
// Flush the data cache.
sceKernelDcacheWritebackRange(texture.ram, buffer_size);
}
int GL_LoadTexture4(const char *identifier, unsigned int width, unsigned int height, const byte *data, int filter)
int GL_LoadTexture4(const char *identifier, unsigned int width, unsigned int height, const byte *data, int filter, qboolean swizzled)
{
int texture_index = -1;
@ -3390,6 +3390,7 @@ int GL_LoadTexture4(const char *identifier, unsigned int width, unsigned int hei
texture.original_width = texture.width = width;
texture.original_height = texture.height = height;
texture.stretch_to_power_of_two = qfalse;
texture.swizzle = swizzled;
// Fill in the texture description.
texture.format = GU_PSM_T4;

View file

@ -573,7 +573,7 @@ void Mod_LoadTextures (lump_t *l)
}
if (!f) {
Con_Printf("Loading texture %s as WAD3\n", mt->name); // didn't find the texture in the folder
Con_Printf("Loading texture %s as WAD3, %dx%d\n", mt->name, mt->width, mt->height); // didn't find the texture in the folder
// naievil -- try to push wad3 loading
int index = WAD3_LoadTexture(mt);
@ -611,7 +611,7 @@ void Mod_LoadTextures (lump_t *l)
w = *((int*)(f + 4));
h = *((int*)(f + 8));
tx->gl_texturenum = GL_LoadTexture4(mt->name, w, h, (byte*)(f + 16), GU_LINEAR);
tx->gl_texturenum = GL_LoadTexture4(mt->name, w, h, (byte*)(f + 16), GU_LINEAR, qfalse);
mapTextureNameList.push_back(tx->gl_texturenum);
}

View file

@ -25,7 +25,7 @@ extern "C"
#include "../quakedef.h"
}
#include <pspgu.h>
#include <malloc.h>
#include <list>
std::list<FILE*> UnloadFileList;
@ -189,6 +189,276 @@ int ConvertWad3ToRGBA(miptex_t *tex)
return index;
}
struct colentry_t {
byte r;
byte g;
byte b;
byte a;
unsigned short pixcount;
};
colentry_t colentries[256];
typedef byte bucket_t[256];
bucket_t buckets[16];
int bucket_counts[16];
int num_buckets = 0;
byte byte_to_halfbyte_map[256];
void add_to_bucket(int bucket, int color) {
buckets[bucket][bucket_counts[bucket]] = color;
bucket_counts[bucket]++;
}
void remove_last_from_bucket(int bucket) {
bucket_counts[bucket]--;
buckets[bucket][bucket_counts[bucket]] = 0;
}
void init_buckets() {
memset(buckets, 0, sizeof(buckets));
memset(bucket_counts, 0, sizeof(bucket_counts));
memset(colentries, 0, sizeof(colentries));
memset(byte_to_halfbyte_map, 0, sizeof(byte_to_halfbyte_map));
num_buckets = 0;
}
int get_new_bucket() {
// Con_Printf("Create new bucket %d\n", num_buckets);
num_buckets += 1;
return num_buckets - 1;
}
colentry_t * color_of_bucket(int bucket, int index) {
return &(colentries[buckets[bucket][index]]);
}
int get_color_index(int bucket, int index) {
return buckets[bucket][index];
}
void sort_and_cut_bucket(int current_bucket, int depth, int target) {
if (num_buckets >= target) {
// Con_Printf("quit due to too many buckets %d / %d\n", num_buckets, target);
return;
}
int count = bucket_counts[current_bucket];
// find the highest range and count pixels in bucket
byte minR = 255;
byte maxR = 0;
byte minG = 255;
byte maxG = 0;
byte minB = 255;
byte maxB = 0;
int pixelsInBucket = 0;
for (int i = 0; i < count; i++) {
colentry_t * col = color_of_bucket(current_bucket, i);
pixelsInBucket += col->pixcount;
if (col->r < minR) minR = col->r;
if (col->r > maxR) maxR = col->r;
if (col->g < minG) minG = col->g;
if (col->g > maxG) maxG = col->g;
if (col->b < minB) minB = col->b;
if (col->b > maxB) maxB = col->b;
}
int rangeR = maxR - minR;
int rangeG = maxG - minG;
int rangeB = maxB - minB;
// 0 = r, 1 = g, 2 = b
int sortBy = 0;
if (rangeG > rangeR && rangeG > rangeB) sortBy = 1;
if (rangeB > rangeR && rangeB > rangeG) sortBy = 2;
// Con_Printf("%d: depth: %d, count: %d, pixels: %d, sortBy: %d\n", current_bucket, depth, count, pixelsInBucket, sortBy);
// bubble sort, replace with comb sort or heap sort
bool sorted = false;
while (!sorted) {
sorted = true; // this will change if a swap is done
for (int i = 0; i < (count - 1); i++) {
colentry_t * col1 = color_of_bucket(current_bucket, i);
colentry_t * col2 = color_of_bucket(current_bucket, i + 1);
bool swap = false;
switch (sortBy) {
case 0: swap = col1->r > col2->r; break;
case 1: swap = col1->g > col2->g; break;
case 2: swap = col1->b > col2->b; break;
}
if (swap) {
byte prevval = buckets[current_bucket][i];
byte nextval = buckets[current_bucket][i + 1];
buckets[current_bucket][i] = nextval;
buckets[current_bucket][i + 1] = prevval;
sorted = false;
}
}
}
// median cut
int pixelsCounted = 0;
int split = 0;
for (int i = 0; i < count; i++) {
if (pixelsCounted > pixelsInBucket * 0.5) {
split = i;
break;
}
byte_to_halfbyte_map[get_color_index(current_bucket, i)] = current_bucket;
colentry_t * col = color_of_bucket(current_bucket, i);
pixelsCounted += col->pixcount;
}
int next_bucket = get_new_bucket();
for (int i = count-1; i >= split; i--) {
byte colorindex = get_color_index(current_bucket, i);
byte_to_halfbyte_map[colorindex] = next_bucket;
add_to_bucket(next_bucket, colorindex);
remove_last_from_bucket(current_bucket);
}
if (depth >= 3) return;
sort_and_cut_bucket(current_bucket, depth + 1, target);
sort_and_cut_bucket(next_bucket, depth + 1, target);
}
int ConvertWad3ToClut4(miptex_t *tex)
{
// Check that texture has data
if (!tex->offsets[0]) {
Sys_Error("ConvertWad3ToRGBA: tex->offsets[0] == 0");
}
// Get pointers to WAD3 data and palette
byte* wadData = ((byte*)tex) + tex->offsets[0];
byte* palette = ((byte*)tex) + tex->offsets[3] + (tex->width>>3)*(tex->height>>3) + 2;
//byte* palette = wadData + tex->offsets[MIPLEVELS]; // Palette starts 2 bytes after the last mipmap
init_buckets();
int current_bucket = get_new_bucket();
// Set bucket pixelcounts
for (int pixi = 0; pixi < tex->width * tex->height; pixi++) {
byte color = wadData[pixi];
colentries[color].pixcount += 1;
}
int MAX_COLORS = 256;
bool has_transparency = false;
byte transparent_index = 0;
int colors_used = 0;
// Set colors and fill first bucket
for (int i = 0; i < MAX_COLORS; i++) {
colentry_t * ce = &(colentries[i]);
if (ce->pixcount == 0) {
continue; // speed up by not processing unused colors
}
colors_used++;
ce->r = palette[i * 3 + 0];
ce->g = palette[i * 3 + 1];
ce->b = palette[i * 3 + 2];
ce->a = 255;
if (ce->r == 0 && ce->g == 0 && ce->b == 255) {
ce->r = ce->g = ce->b = 128;
ce->a = 255;
has_transparency = true;
transparent_index = i;
continue; // don't add transparencies to buckets
}
add_to_bucket(current_bucket, i);
}
// Con_Printf("Initial bucket %d: %d colors\n", current_bucket, bucket_counts[current_bucket]);
// could check if bucket has 16 colors or less and early out
sort_and_cut_bucket(current_bucket, 0, has_transparency ? 15 : 16);
if (has_transparency) {
int transparent_bucket = get_new_bucket();
byte_to_halfbyte_map[transparent_index] = transparent_bucket; // last index
add_to_bucket(transparent_bucket, transparent_index);
}
// Con_Printf("num buckets: %d, has transparency: %d, colors actually used: %d\n", num_buckets, has_transparency, colors_used);
/* hack to resample width/height if it's less than 32 */
bool stretch_sideways = false;
if (tex->width <= 16) {
tex->width *= 2;
stretch_sideways = true;
}
int imageSize = tex->width * tex->height * 0.5;
byte* texData = (byte*)memalign(16, 16 * 4 + imageSize);
byte* unswizzled = (byte*)memalign(16, imageSize);
unsigned int * newPal = (unsigned int*)&(texData[imageSize]);
/*
for (int y = 0; y < tex->height; y++) {
for (int x = 0; x < tex->width; x++) {
byte colorIndex = wadData[y * (tex->width / width_mult) / height_mult + x / width_mult * 2];
unswizzled[y * tex->width + x] = byte_to_halfbyte_map[colorIndex];
colorIndex = wadData[y * (tex->width / width_mult) / height_mult + x / width_mult * 2 + 1];
unswizzled[y * tex->width + x] = byte_to_halfbyte_map[colorIndex] << 4;
}
}
*/
if (stretch_sideways) {
for (int i = 0; i < imageSize; i++) {
byte colorIndex = wadData[i];
unswizzled[i] = byte_to_halfbyte_map[colorIndex] + (byte_to_halfbyte_map[colorIndex] << 4);
}
} else {
for (int i = 0; i < imageSize; i++) {
byte colorIndex = wadData[i*2];
unswizzled[i] = byte_to_halfbyte_map[colorIndex];
colorIndex = wadData[i*2 + 1];
unswizzled[i] += byte_to_halfbyte_map[colorIndex] << 4;
}
}
for (int i = 0; i < num_buckets; i++) {
int r = 0;
int g = 0;
int b = 0;
int alpha = 255;
colentry_t * col;
for (int j = 0; j < bucket_counts[i]; j++) {
byte colindex = get_color_index(i, j);
if (has_transparency && transparent_index == colindex) {
r = 128;
g = 128;
b = 128;
alpha = 0;
break;
}
col = color_of_bucket(i, j);
r += col->r;
g += col->g;
b += col->b;
}
r /= bucket_counts[i];
g /= bucket_counts[i];
b /= bucket_counts[i];
newPal[i] = (alpha << 24) + (b << 16) + (g << 8) + r;
//Con_Printf("Bucket %d count: %d\n", i, bucket_counts[i]);
//Con_Printf("Color %d: %d %d %d %x\n", i, r, g, b, newPal[i]);
}
//Con_Printf("_ _ _ \n");
swizzle_fast(texData, unswizzled, tex->width * 0.5, tex->height);
// memcpy(texData, unswizzled, imageSize);
int index = GL_LoadTexture4(tex->name, tex->width, tex->height, texData, GU_LINEAR, qtrue);
free(texData);
free(unswizzled);
return index;
}
int WAD3_LoadTexture(miptex_t *mt)
{
char texname[MAX_QPATH];
@ -198,7 +468,7 @@ int WAD3_LoadTexture(miptex_t *mt)
int index;
if (mt->offsets[0])
return ConvertWad3ToRGBA(mt);
return ConvertWad3ToClut4(mt); // ConvertWad3ToRGBA(mt);
texname[sizeof(texname) - 1] = 0;
W_CleanupName (mt->name, texname);
@ -242,7 +512,7 @@ int WAD3_LoadTexture(miptex_t *mt)
for (j = 0;j < MIPLEVELS;j++)
tex->offsets[j] = LittleLong(tex->offsets[j]);
index = ConvertWad3ToRGBA(tex);
index = ConvertWad3ToClut4(mt); // ConvertWad3ToRGBA(tex);
Hunk_FreeToLowMark(lowmark);