diff --git a/code/renderercommon/tr_image_pvr.c b/code/renderercommon/tr_image_pvr.c index b26e6a6f..b51b3d53 100644 --- a/code/renderercommon/tr_image_pvr.c +++ b/code/renderercommon/tr_image_pvr.c @@ -22,16 +22,37 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #include "tr_common.h" +typedef struct gbix { + unsigned int magic; + unsigned int len; +} gbix_t; + typedef struct pvr { - unsigned char magic[4]; + unsigned int magic; unsigned int len_file; unsigned int type; unsigned short width; unsigned short height; - unsigned short codebook[1024]; - unsigned char indices[1]; } pvr_t; +typedef unsigned int (*pvr_pixel_func_t)(unsigned short color); +typedef byte *(*pvr_image_func_t)(pvr_t *pvr, int offset, qboolean detwiddle, pvr_pixel_func_t pixel_func); + +enum { + PVR_PIXEL_TYPE_ARGB1555 = 0, + PVR_PIXEL_TYPE_RGB565 = 1, + PVR_PIXEL_TYPE_ARGB4444 = 2 +}; + +enum { + PVR_IMAGE_TYPE_TWIDDLED = 1, + PVR_IMAGE_TYPE_TWIDDLED_MM = 2, + PVR_IMAGE_TYPE_VQ = 3, + PVR_IMAGE_TYPE_VQ_MM = 4, + PVR_IMAGE_TYPE_RECTANGULAR = 9, + PVR_IMAGE_TYPE_RECTANGULAR_MM = 10 +}; + static int log2approx(int x) { switch (x) @@ -48,7 +69,7 @@ static int log2approx(int x) } } -static int from_xy(int x, int y, int w, int h) +static int pvr_detwiddle(int x, int y, int w, int h) { int wmax, hmax; int i, idx = 0; @@ -83,7 +104,17 @@ static int from_xy(int x, int y, int w, int h) return idx; } -static unsigned int rgb565_to_rgba24(unsigned short color) +static unsigned int argb1555_to_rgba8888(unsigned short color) +{ + unsigned char r, g, b, a; + r = ((color >> 10) & 31) << 4; + g = ((color >> 5) & 31) << 4; + b = ((color >> 0) & 31) << 4; + a = ((color >> 15) & 1) * 255; + return (a << 24) | (b << 16) | (g << 8) | r; +} + +static unsigned int rgb565_to_rgba8888(unsigned short color) { unsigned char r, g, b, a; r = ((color >> 11) & 31) << 3; @@ -93,13 +124,153 @@ static unsigned int rgb565_to_rgba24(unsigned short color) return (a << 24) | (b << 16) | (g << 8) | r; } +static unsigned int argb4444_to_rgba8888(unsigned short color) +{ + unsigned char r, g, b, a; + r = ((color >> 8) & 15) << 4; + g = ((color >> 4) & 15) << 4; + b = ((color >> 0) & 15) << 4; + a = ((color >> 12) & 15) | 0xF; + return (a << 24) | (b << 16) | (g << 8) | r; +} + +static int mm_offset(int w) +{ + switch (w) + { + case 1: return 0x00006; + case 2: return 0x00008; + case 4: return 0x00010; + case 8: return 0x00030; + case 16: return 0x000B0; + case 32: return 0x002B0; + case 64: return 0x00AB0; + case 128: return 0x02AB0; + case 256: return 0x0AAB0; + case 512: return 0x2AAB0; + case 1024: return 0xAAAB0; + default: return -1; + } +} + +static int mm_offset_vq(int w) +{ + switch (w) + { + case 1: return 0x00000; + case 2: return 0x00001; + case 4: return 0x00002; + case 8: return 0x00006; + case 16: return 0x00016; + case 32: return 0x00056; + case 64: return 0x00156; + case 128: return 0x00556; + case 256: return 0x01556; + case 512: return 0x05556; + case 1024: return 0x15556; + default: return -1; + } +} + +static byte *decode_mm(pvr_t *pvr, pvr_image_func_t image_func, pvr_pixel_func_t pixel_func, qboolean vq, qboolean detwiddle) +{ + int offset = vq ? mm_offset_vq(pvr->width) : mm_offset(pvr->width); + return image_func(pvr, offset, detwiddle, pixel_func); +} + +static byte *decode(pvr_t *pvr, int offset, qboolean detwiddle, pvr_pixel_func_t pixel_func) +{ + int x, y; + unsigned int *rgba32; + byte *ret; + + ret = (byte *)ri.Malloc(pvr->width * pvr->height * sizeof(unsigned int)); + rgba32 = (unsigned int *)ret; + + for (y = 0; y < pvr->height; y++) + { + for (x = 0; x < pvr->width; x++) + { + unsigned short color; + int ofs; + if (detwiddle) + ofs = offset + pvr_detwiddle(x, y, pvr->width, pvr->height) * 2; + else + ofs = offset + (y * pvr->width + x) * 2; + + if (ofs < 0) + { + ri.Free(ret); + return NULL; + } + + color = *(unsigned short *)(((byte *)(pvr + 1)) + ofs); + + rgba32[y * pvr->width + x] = pixel_func(color); + } + } + + return ret; +} + +static byte *decode_vq(pvr_t *pvr, int offset, qboolean detwiddle, pvr_pixel_func_t pixel_func) +{ + int x, y; + unsigned int *rgba32; + unsigned short *codebook; + unsigned char *indices; + byte *ret; + + ret = (byte *)ri.Malloc(pvr->width * pvr->height * (pixel_func ? sizeof(unsigned int) : sizeof(unsigned short))); + rgba32 = (unsigned int *)ret; + + codebook = (unsigned short *)(pvr + 1); + indices = ((unsigned char *)(codebook + 1024)) + offset; + + for (y = 0; y < pvr->height / 2; y++) + { + for (x = 0; x < pvr->width / 2; x++) + { + unsigned short *colors; + int a, b, c, d; + int idx; + if (detwiddle) + idx = pvr_detwiddle(x, y, pvr->width, pvr->height); + else + idx = y * (pvr->width / 2) + x; + + if (idx < 0) + { + ri.Free(ret); + return NULL; + } + + colors = &codebook[indices[idx] * 4]; + + a = ((y * 2) + 0) * pvr->width + ((x * 2) + 0); + b = ((y * 2) + 1) * pvr->width + ((x * 2) + 0); + c = ((y * 2) + 0) * pvr->width + ((x * 2) + 1); + d = ((y * 2) + 1) * pvr->width + ((x * 2) + 1); + + rgba32[a] = pixel_func(colors[0]); + rgba32[b] = pixel_func(colors[1]); + rgba32[c] = pixel_func(colors[2]); + rgba32[d] = pixel_func(colors[3]); + } + } + + return ret; +} + void R_LoadPVR(const char *name, byte **pic, int *width, int *height) { unsigned int length; void *buffer; + byte *ptr; pvr_t *pvr; - unsigned int *rgba; - int x, y; + int pixel_type, image_type; + pvr_pixel_func_t pixel_func; + byte *ret = NULL; *pic = NULL; if (width) @@ -112,51 +283,104 @@ void R_LoadPVR(const char *name, byte **pic, int *width, int *height) if (!buffer || length < 0) return; + ptr = (byte *)buffer; + + // skip global index + if (memcmp(ptr, "GBIX", 4) == 0) + { + gbix_t *gbix = (gbix_t *)ptr; + ptr += sizeof(gbix_t) + LittleLong(gbix->len); + } + // check magic identifier - pvr = (pvr_t *)buffer; - if (memcmp(pvr->magic, "PVRT", sizeof(pvr->magic)) != 0) + if (memcmp(ptr, "PVRT", 4) != 0) ri.Error(ERR_DROP, "LoadPVR: magic identifier does not match expected (%s)", name); // fix up header + pvr = (pvr_t *)ptr; pvr->len_file = LittleLong(pvr->len_file); pvr->type = LittleLong(pvr->type); pvr->width = LittleShort(pvr->width); pvr->height = LittleShort(pvr->height); - // allocate rgba buffer - rgba = (unsigned int *)ri.Malloc(pvr->width * pvr->height * 4); + // break out type values + pixel_type = pvr->type & 0xFF; + image_type = (pvr->type & 0xFF00) >> 8; + + // get pixel function + switch (pixel_type) + { + case PVR_PIXEL_TYPE_ARGB1555: + { + pixel_func = argb1555_to_rgba8888; + break; + } + case PVR_PIXEL_TYPE_RGB565: + { + pixel_func = rgb565_to_rgba8888; + break; + } + case PVR_PIXEL_TYPE_ARGB4444: + { + pixel_func = argb4444_to_rgba8888; + break; + } + default: + { + ri.Error(ERR_DROP, "LoadPVR: unsupported pixel type 0x%02x (%s)", pixel_type, name); + break; + } + } // decompress image - for (y = 0; y < pvr->height / 2; y++) + switch (image_type) { - for (x = 0; x < pvr->width / 2; x++) + case PVR_IMAGE_TYPE_TWIDDLED: { - unsigned short *colors; - int a, b, c, d; - int idx = from_xy(x, y, pvr->width, pvr->height); - - if (idx < 0) - ri.Error(ERR_DROP, "LoadPVR: invalid data passed to decompressor (%s)", name); - - colors = &pvr->codebook[pvr->indices[idx] * 4]; - - a = ((y * 2) + 0) * pvr->width + ((x * 2) + 0); - b = ((y * 2) + 1) * pvr->width + ((x * 2) + 0); - c = ((y * 2) + 0) * pvr->width + ((x * 2) + 1); - d = ((y * 2) + 1) * pvr->width + ((x * 2) + 1); - - rgba[a] = rgb565_to_rgba24(colors[0]); - rgba[b] = rgb565_to_rgba24(colors[1]); - rgba[c] = rgb565_to_rgba24(colors[2]); - rgba[d] = rgb565_to_rgba24(colors[3]); + ret = decode(pvr, 0, qtrue, pixel_func); + break; + } + case PVR_IMAGE_TYPE_TWIDDLED_MM: + { + ret = decode_mm(pvr, decode, pixel_func, qfalse, qtrue); + break; + } + case PVR_IMAGE_TYPE_VQ: + { + ret = decode_vq(pvr, 0, qtrue, pixel_func); + break; + } + case PVR_IMAGE_TYPE_VQ_MM: + { + ret = decode_mm(pvr, decode_vq, pixel_func, qtrue, qtrue); + break; + } + case PVR_IMAGE_TYPE_RECTANGULAR: + { + ret = decode(pvr, 0, qfalse, pixel_func); + break; + } + case PVR_IMAGE_TYPE_RECTANGULAR_MM: + { + ret = decode_mm(pvr, decode, pixel_func, qfalse, qfalse); + break; + } + default: + { + ri.Error(ERR_DROP, "LoadPVR: unsupported image type 0x%02x (%s)", image_type, name); + break; } } // clean up ri.FS_FreeFile(buffer); + // something failed + if (!ret) + return; + // return stuff - *pic = (byte *)rgba; + *pic = ret; if (width) *width = pvr->width; if (height)