diff --git a/MakePHAT b/MakePHAT index 78eb51f..324abe0 100644 --- a/MakePHAT +++ b/MakePHAT @@ -103,7 +103,8 @@ HARDWARE_VIDEO_ONLY_OBJS = \ source/psp/video_hardware_surface.o \ source/psp/video_hardware_warp.o \ source/psp/video_hardware_fog.o \ - source/psp/video_hardware_dxtn.o + source/psp/video_hardware_dxtn.o \ + source/psp/video_hardware_colorquant.o HARDWARE_VIDEO_ONLY_FLAGS = -DPSP_HARDWARE_VIDEO OBJS = $(COMMON_OBJS) $(HARDWARE_VIDEO_ONLY_OBJS) diff --git a/MakeSLIM b/MakeSLIM index ffbc23c..a292481 100644 --- a/MakeSLIM +++ b/MakeSLIM @@ -103,7 +103,8 @@ HARDWARE_VIDEO_ONLY_OBJS = \ source/psp/video_hardware_surface.o \ source/psp/video_hardware_warp.o \ source/psp/video_hardware_fog.o \ - source/psp/video_hardware_dxtn.o + source/psp/video_hardware_dxtn.o \ + source/psp/video_hardware_colorquant.o HARDWARE_VIDEO_ONLY_FLAGS = -DPSP_HARDWARE_VIDEO OBJS = $(COMMON_OBJS) $(HARDWARE_VIDEO_ONLY_OBJS) diff --git a/source/psp/video_hardware.h b/source/psp/video_hardware.h index 70f7a2a..c18e351 100644 --- a/source/psp/video_hardware.h +++ b/source/psp/video_hardware.h @@ -30,6 +30,7 @@ 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, qboolean swizzled); +int GL_LoadTexture8to4(const char *identifier, unsigned int width, unsigned int height, const byte *data, const byte *pal, int filter); 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); @@ -421,5 +422,6 @@ int D_DrawParticleBuffered (psp_particle* vertices, particle2_t *pparticl extern int zombie_skins[2][2]; +extern int faces_rejected, faces_checked, faces_clipped; -extern int faces_rejected, faces_checked, faces_clipped; \ No newline at end of file +void convert_8bpp_to_4bpp(const byte* indata, const byte* inpal, int width, int height, byte* outdata, byte* outpal); \ No newline at end of file diff --git a/source/psp/video_hardware_colorquant.cpp b/source/psp/video_hardware_colorquant.cpp new file mode 100644 index 0000000..01b5c53 --- /dev/null +++ b/source/psp/video_hardware_colorquant.cpp @@ -0,0 +1,240 @@ +/* +Copyright (C) 2023 Shpuld and NZP Team. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +extern "C" +{ +#include "../quakedef.h" +} +#include +#include + +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() { + 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) { + 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; + + // 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); +} + +void convert_8bpp_to_4bpp(const byte* indata, const byte* inpal, int width, int height, byte* outdata, byte* outpal) +{ + init_buckets(); + int current_bucket = get_new_bucket(); + + // Set bucket pixelcounts + for (int pixi = 0; pixi < width * height; pixi++) { + byte color = indata[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 = inpal[i * 3 + 0]; + ce->g = inpal[i * 3 + 1]; + ce->b = inpal[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); + } + + // 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); + } + + int image_size = width * height * 0.5; + + for (int i = 0; i < image_size; i++) { + byte color_index = indata[i*2]; + outdata[i] = byte_to_halfbyte_map[color_index]; + color_index = indata[i*2 + 1]; + outdata[i] += byte_to_halfbyte_map[color_index] << 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]; + + ((unsigned int*)outpal)[i] = (alpha << 24) + (b << 16) + (g << 8) + r; + } +} \ No newline at end of file diff --git a/source/psp/video_hardware_draw.cpp b/source/psp/video_hardware_draw.cpp index 189bbe5..7eee14d 100644 --- a/source/psp/video_hardware_draw.cpp +++ b/source/psp/video_hardware_draw.cpp @@ -3347,8 +3347,6 @@ void GL_Upload4(int texture_index, const byte *data, int width, int height) int GL_LoadTexture4(const char *identifier, unsigned int width, unsigned int height, const byte *data, int filter, qboolean swizzled) { int texture_index = -1; - - tex_scale_down = r_tex_scale_down.value == qtrue; if (identifier[0]) { @@ -3424,3 +3422,45 @@ int GL_LoadTexture4(const char *identifier, unsigned int width, unsigned int hei // Done. return texture_index; } + +int GL_LoadTexture8to4(const char *identifier, unsigned int width, unsigned int height, const byte *data, const byte *pal, int filter) +{ + tex_scale_down = r_tex_scale_down.value == qtrue; + int new_width = width; + int new_height = height; + if (tex_scale_down == true) + { + new_width = std::max(round_down(width), 32U); + new_height = std::max(round_down(height),16U); + } + else + { + new_width = std::max(round_up(width), 32U); + new_height = std::max(round_up(height),16U); + } + + std::size_t resamp_size = new_width * new_height; + byte * resamp_data = static_cast(memalign(16, resamp_size)); + // Resamp required? + if ((new_width != width) || (new_height != height)) { + GL_ResampleTexture(data, width, height, resamp_data, new_width, new_height); + } else { + memcpy(resamp_data, data, resamp_size); + } + + std::size_t buffer_size = resamp_size * 0.5; + byte * clut4data = static_cast(memalign(16, buffer_size + 16 * 4)); + byte * clut4pal = &(clut4data[buffer_size]); + byte * unswizzled_data = static_cast(memalign(16, buffer_size)); + + convert_8bpp_to_4bpp(resamp_data, pal, new_width, new_height, unswizzled_data, clut4pal); + swizzle_fast(clut4data, unswizzled_data, new_width * 0.5, new_height); + + free(unswizzled_data); + + int id = GL_LoadTexture4(identifier, new_width, new_height, clut4data, filter, qtrue); + + free(clut4data); + + return id; +} \ No newline at end of file diff --git a/source/psp/wad3.cpp b/source/psp/wad3.cpp index d691a17..603aba5 100644 --- a/source/psp/wad3.cpp +++ b/source/psp/wad3.cpp @@ -189,140 +189,6 @@ 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 @@ -331,134 +197,12 @@ int ConvertWad3ToClut4(miptex_t *tex) } // 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 + const byte* wadData = ((byte*)tex) + tex->offsets[0]; + const byte* palette = ((byte*)tex) + tex->offsets[3] + (tex->width>>3)*(tex->height>>3) + 2; - 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; + return GL_LoadTexture8to4(tex->name, tex->width, tex->height, wadData, palette, GU_LINEAR); } - int WAD3_LoadTexture(miptex_t *mt) { char texname[MAX_QPATH];