From 37110ba47fbbc06f5e095c303e598d6188db5ca7 Mon Sep 17 00:00:00 2001 From: OzzyOuzo Date: Thu, 18 Nov 2021 11:59:37 +0200 Subject: [PATCH 1/5] 4bpp texture palette support (GL_RGBA8 pal components), modified paletted_pcx sample (USE_16C_PALETTE) --- samples/paletted_pcx/romdisk/NeHe-Alpha.bmp | Bin 0 -> 32886 bytes samples/paletted_pcx/romdisk/NeHe.tex | Bin 65552 -> 0 bytes samples/paletted_pcx/romdisk/NeHe.tex.pal | Bin 1032 -> 0 bytes 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 samples/paletted_pcx/romdisk/NeHe-Alpha.bmp delete mode 100644 samples/paletted_pcx/romdisk/NeHe.tex delete mode 100644 samples/paletted_pcx/romdisk/NeHe.tex.pal diff --git a/samples/paletted_pcx/romdisk/NeHe-Alpha.bmp b/samples/paletted_pcx/romdisk/NeHe-Alpha.bmp new file mode 100644 index 0000000000000000000000000000000000000000..db96b15c092cb4ca6756dd4886aeeb25a420961e GIT binary patch literal 32886 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 literal 0 HcmV?d00001 diff --git a/samples/paletted_pcx/romdisk/NeHe.tex b/samples/paletted_pcx/romdisk/NeHe.tex deleted file mode 100644 index 26ee1b4887942d9f74f4a6e5d144fe5777abc3e3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 65552 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%0Ao9d#)pFTz}25}zx~ zoSrb-yZ?0S7TSM0c1#OrF!oJS3NF^78%Za@oWLs8N@*-#_x$=;aau`)8RqMkE#gMr z@s=DdB~8(aV^y(vv3YrsIvOrO@%{3PTgi{%$2IWpR0EHCe)Un)|EufbQTc!H^q7IdcDx8F81!fTi^aO?7wfM%W4Mf-yf4?H1p~e3ZWG< z;jnM56i@=m{+Ruf*EjYx#0(fU0Nc34JC1WRVf6f?47d?pVXInX62Rb&J4r1Cmt#gw zwjSw2jvXy&q^J!k)+(ewAD5DF(h$hObpypxoR;R}By(;Dj_q!lku@P5_TOzxUK%Od ze{TQ-J4R?-igHThODT1ubN>9dH`ZRHSmv=Y>8h|Nhf!Q{t;qQVh2A^ooeAEOUH{XyG^! zoQcfJ%H5llxYtutvwKT&@>k{^Teekf`Klr*xf*tCtBr=5eY(82@Tuw zwrt@=Z22lF$?9xvEy!3QNb5S7Rm!v8)~Tb|WDFN0@aMGn`y@xilotjAzRT z3xi{}l$(*Cc1j&ey+TRZt!}q6RIRsLTZN8<#nNXBCqBF9mxLsg3N5NEQsecS%20Ki z68oiS;zar4SiuL%Q>VoHEGa1qdD6GzXEZc?liP1WMpRDDoN22s4Lg0{Kx5hAS%tt3 zx)Xua*oC1mzfnTx>1E9-vWjrhrP;CjZ!cbc@vrAj!_LlWc(m^jXP6?OHGf~z-Bd z7&dCs+Z(;y5CrT$K|6@`&64qZ_n)Ti+;`_LdGw5S} zkC^>;eyP`^^7BJWAL#i8ihtDl3Pj&0K9Y`&)R4F)&nC{x$73T6<(L8Wtr|GcTUM^g zju2ZcdHmQsRherWoCp&~0uD*LG$6I+@eA{GGLdDtQt6bSK6<#+GTd=YV!dpxsu7V| zSdyWwJaeHKK07hPy}Ytmi(L1H$h=G~3`yIatQQXp1;0 zA`czJjG$xTGaWP1yZYU-+%{qE*aZzi3o@Db>~m;vAuJJ@ znQ*sz5S*jD6m#mc&(h-c&kijbFyPdwyR~;(_(S*Z?YCg>&I5-LubCk4+s~X{y7a|! z?#xE7zR1OsW3uCyo;zRA^YpoM(}%j+K(hieZ0CM%dQ|(D{7kU_9~Rkv?7vBEsy1O}oXDE=X``sblOYWscE^bb+e aK=m6a-fvb9!M|VnKIxfwfPeI(AN?P-gg#{e diff --git a/samples/paletted_pcx/romdisk/NeHe.tex.pal b/samples/paletted_pcx/romdisk/NeHe.tex.pal deleted file mode 100644 index f1159f01a1b69b6725dfc622dc40dbec16b5b0c9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1032 zcmWlYSyNgE0EI6*7g;V`P~j>d%LQf80J3RV#G+7wxWpwi8uuh(lS~$CjGZ`>$)wZC zL#GdIr<2z{^*?^4=Lekm=6rLOd-aZ>5rkSpVM;Ocf9zHrozp4e@iZs$zaq8JXn)a6ooODfsa=ae8zi-mJ#l+osmg7 z(K-|S_2r7W@+yr-eu5zp_lSm#jR=WE0)xRovyx>#>clR&7@T*QonJ(&Gw`@N+~w<|0ZGOU#+@Quj?of_Uenzh8InZnSKj=8Qv(yKw1^z5z9kccUmO#+olg?Pe_q?dVr)?jva7E!Nb>pK~**Nft^ z@cAx>(~-k!9ijd3h+@KuQ;GBI?EtmVro0zmBJ5;bH1pt5m{ck`^grzAlt>0$SnMti zPY#*RRxoN!>=Z4GsS;CGiKiz+U(qx=%Q&r8i$+Y3C70aOba8V~rc{h$wCOqOjT7;E@cPXB{b7NqG>O4tXZ!GyM#hgl zFwZ}Kd?LScfJv#b+K+NQXmik&aH~~%`*~_xQFhL={P|OXQZ~ttZ-1uKf5q$9n<(Kd zJ8LUU&y|PUWy!Wfd>zC%?Jp4vN0`j7)9EcRQ{AS%ZRg{gV_saU96#wYvvkDWO?vo& zP4bHh59?84xj5I)GrW6#fZ~raINoLFaTNVTh~wQP%l%RAF2el#?H9~0mES(S<@5Ck zj?pkhpM$g-WP5jqS6e9x-`l8X?X;RX++IKXtu!mmCefOMUe-p~sM1^w&@QMHGcgu7 P6oTnxo}R}Mr3wB6Z?u>l From 148962254ab03d38d4ea9aff69568a84594eefd5 Mon Sep 17 00:00:00 2001 From: OzzyOuzo Date: Thu, 18 Nov 2021 12:04:46 +0200 Subject: [PATCH 2/5] 4bpp texture palette support, modified paletted_pcx sampled (USE_16C_PALETTE) --- GL/state.c | 7 +- GL/texture.c | 58 +++++- samples/paletted_pcx/main.c | 256 +++++++++++++++++++++++++- samples/paletted_pcx/romdisk/NeHe.bmp | Bin 196662 -> 65654 bytes 4 files changed, 311 insertions(+), 10 deletions(-) diff --git a/GL/state.c b/GL/state.c index 4b48966..a7a28ad 100644 --- a/GL/state.c +++ b/GL/state.c @@ -238,7 +238,12 @@ void _glUpdatePVRTextureContext(PolyContext *context, GLshort textureUnit) { TexturePalette* palette = _glGetSharedPalette(tx1->shared_bank); context->txr.format |= GPUPaletteSelect8BPP(palette->bank); } else { - context->txr.format |= GPUPaletteSelect8BPP((tx1->palette) ? tx1->palette->bank : 0); + if (tx1->palette->size != 16){ + context->txr.format |= GPUPaletteSelect8BPP((tx1->palette) ? tx1->palette->bank : 0); + } + else{ + context->txr.format |= GPUPaletteSelect4BPP((tx1->palette) ? tx1->palette->bank : 0); + } } } diff --git a/GL/texture.c b/GL/texture.c index 143aef0..150648f 100644 --- a/GL/texture.c +++ b/GL/texture.c @@ -139,6 +139,41 @@ static void GPUTextureTwiddle8PPP(void* src, void* dst, uint32_t w, uint32_t h) } } +static void GPUTextureTwiddle4PPP(void* src, void* dst, uint32_t w, uint32_t h) { + uint32_t x, y, yout, min, mask; + + min = MIN(w, h); + mask = min - 1; + + uint8 * pixels; + uint16 * vtex; + pixels = (uint8 *) src; + vtex = (uint16*)dst; + + for (y=0; y>1]&15)<<8) | ((pixels[(x+(y+1)*w) >>1]&15)<<12) | + ((pixels[(x+y*w) >>1]>>4)<<0) | ((pixels[(x+(y+1)*w) >>1]>>4)<<4); + + #else + vtex[TWIDOUT((x&mask)/2, (yout&mask)/2) + + (x/min + yout/min)*min*min/4] = + (pixels[(x+y*w) >>1]&15) | ((pixels[(x+(y+1)*w) >>1]&15)<<4) | + ((pixels[(x+y*w) >>1]>>4)<<8) | ((pixels[(x+(y+1)*w) >>1]>>4)<<12); + + #endif + } + } +} + static void GPUTextureTwiddle16BPP(void * src, void* dst, uint32_t w, uint32_t h) { uint32_t x, y, yout, min, mask; @@ -188,6 +223,7 @@ void _glApplyColorTable(TexturePalette* src) { GLushort i; GLushort offset = src->size * src->bank; + for(i = 0; i < src->width; ++i) { GLubyte* entry = &src->data[i * 4]; if(INTERNAL_PALETTE_FORMAT == GL_RGBA8) { @@ -925,6 +961,7 @@ static TextureConversionFunc _determineConversion(GLint internalFormat, GLenum f } } break; case GL_RGBA8: { + if(type == GL_UNSIGNED_BYTE && format == GL_RGBA) { return _rgba8888_to_rgba8888; } else if (type == GL_BYTE && format == GL_RGBA) { @@ -1071,7 +1108,7 @@ void APIENTRY glTexImage2D(GLenum target, GLint level, GLint internalFormat, return; } } else { - if(internalFormat != GL_COLOR_INDEX8_EXT) { + if(internalFormat != GL_COLOR_INDEX8_EXT && internalFormat != GL_COLOR_INDEX4_EXT) { INFO_MSG(""); _glKosThrowError(GL_INVALID_ENUM, __func__); return; @@ -1129,7 +1166,7 @@ void APIENTRY glTexImage2D(GLenum target, GLint level, GLint internalFormat, return; } - GLboolean isPaletted = (internalFormat == GL_COLOR_INDEX8_EXT) ? GL_TRUE : GL_FALSE; + GLboolean isPaletted = (internalFormat == GL_COLOR_INDEX8_EXT || internalFormat == GL_COLOR_INDEX4_EXT) ? GL_TRUE : GL_FALSE; /* Calculate the format that we need to convert the data to */ GLuint pvr_format = _determinePVRFormat(internalFormat, type); @@ -1161,6 +1198,11 @@ void APIENTRY glTexImage2D(GLenum target, GLint level, GLint internalFormat, GLint destStride = isPaletted ? 1 : 2; GLuint bytes = (width * height * destStride); + //special case 4bpp + if(internalFormat == GL_COLOR_INDEX4_EXT){ + bytes >>= 1; + } + if(!active->data) { assert(active); assert(width); @@ -1302,8 +1344,14 @@ void APIENTRY glTexImage2D(GLenum target, GLint level, GLint internalFormat, if(internalFormat == GL_COLOR_INDEX8_EXT) { GPUTextureTwiddle8PPP((void*) pixels, targetData, width, height); - } else { - GPUTextureTwiddle16BPP((void*) pixels, targetData, width, height); + } + else{ + if(internalFormat == GL_COLOR_INDEX4_EXT) { + GPUTextureTwiddle4PPP((void*) pixels, targetData, width, height); + } + else { + GPUTextureTwiddle16BPP((void*) pixels, targetData, width, height); + } } /* We make sure we remove nontwiddled and add twiddled. We could always @@ -1487,7 +1535,7 @@ GLAPI void APIENTRY glColorTableEXT(GLenum target, GLenum internalFormat, GLsize } palette->data = (GLubyte*) malloc(width * 4); - palette->format = format; + palette->format = format; //Ozzy:was previously forcing to GL_RGBA for testing. palette->width = width; palette->size = (width > 16) ? 256 : 16; assert(palette->size == 16 || palette->size == 256); diff --git a/samples/paletted_pcx/main.c b/samples/paletted_pcx/main.c index fac5f0f..ab1c625 100644 --- a/samples/paletted_pcx/main.c +++ b/samples/paletted_pcx/main.c @@ -19,19 +19,24 @@ KOS_INIT_ROMDISK(romdisk); #endif +/* using 4bpp textures from BMP files instead of 8bpp from PCX files */ +#define USE_16C_PALETTE + /* floats for x rotation, y rotation, z rotation */ float xrot, yrot, zrot; int textures[3]; typedef struct { - unsigned int height; - unsigned int width; - unsigned int palette_width; + uint32_t height; + uint32_t width; + uint32_t palette_width; char* palette; char* data; } Image; +#ifndef USE_16C_PALETTE + #pragma pack(push) #pragma pack(1) @@ -135,11 +140,234 @@ int LoadPalettedPCX(const char* filename, Image* image) { return 1; } +#else + +#define BMP_BI_RGB 0L +#define BMP_BI_UNCOMPRESSED 0L +#define BMP_BI_RLE8 1L +#define BMP_BI_RLE4 2L +#define BMP_BI_BITFIELDS 3L + +#pragma pack(push) +#pragma pack(1) + +typedef struct BITMAP_FILE_HEADER +{ + uint16_t Type; + uint32_t Size; + uint16_t Reserved1; + uint16_t Reserved2; + uint32_t OffBits; +} BITMAP_FILE_HEADER; + +typedef struct BITMAP_INFO_HEADER +{ + uint32_t Size; + int32_t Width; + int32_t Height; + uint16_t Planes; + uint16_t BitCount; + uint32_t Compression; + uint32_t SizeImage; + int32_t XPelsPerMeter; + int32_t YPelsPerMeter; + uint32_t ClrUsed; + uint32_t ClrImportant; +} BITMAP_INFO_HEADER; + +typedef struct RGB_QUAD +{ + uint8_t Blue; + uint8_t Green; + uint8_t Red; + uint8_t Reserved; +} RGB_QUAD; + +typedef struct BITMAP_INFO +{ + BITMAP_INFO_HEADER Header; + RGB_QUAD Colors[1]; +} BITMAP_INFO; + +#pragma pack(pop) + +/* some global variables used to load a 4bpp BMP file */ +static BITMAP_FILE_HEADER BmpFileHeader; +static BITMAP_INFO_HEADER BmpInfoHeader; +static RGB_QUAD BmpRgbQuad[256]; +static uint8_t BmpPal[256 * 3]; + +int BMP_Infos(FILE *pFile, uint32_t *width, uint32_t *height) +{ + if (!pFile) + return 0; + + if (fread(&BmpFileHeader.Type, 1, 2, pFile) != 2) + return 0; + if (fread(&BmpFileHeader.Size, 1, 4, pFile) != 4) + return 0; + if (fread(&BmpFileHeader.Reserved1, 1, 2, pFile) != 2) + return 0; + if (fread(&BmpFileHeader.Reserved2, 1, 2, pFile) != 2) + return 0; + if (fread(&BmpFileHeader.OffBits, 1, 4, pFile) != 4) + return 0; + if (fread(&BmpInfoHeader.Size, 1, 4, pFile) != 4) + return 0; + if (fread(&BmpInfoHeader.Width, 1, 4, pFile) != 4) + return 0; + if (fread(&BmpInfoHeader.Height, 1, 4, pFile) != 4) + return 0; + if (fread(&BmpInfoHeader.Planes, 1, 2, pFile) != 2) + return 0; + if (fread(&BmpInfoHeader.BitCount, 1, 2, pFile) != 2) + return 0; + if (fread(&BmpInfoHeader.Compression, 1, 4, pFile) != 4) + return 0; + if (fread(&BmpInfoHeader.SizeImage, 1, 4, pFile) != 4) + return 0; + if (fread(&BmpInfoHeader.XPelsPerMeter, 1, 4, pFile) != 4) + return 0; + if (fread(&BmpInfoHeader.YPelsPerMeter, 1, 4, pFile) != 4) + return 0; + if (fread(&BmpInfoHeader.ClrUsed, 1, 4, pFile) != 4) + return 0; + if (fread(&BmpInfoHeader.ClrImportant, 1, 4, pFile) != 4) + return 0; + + *width = (uint32_t)BmpInfoHeader.Width; + *height = (uint32_t)BmpInfoHeader.Height; + + return 1; +} + + +int BMP_GetPalette(FILE *pFile) +{ + int32_t i,bitCount; + + if (BmpInfoHeader.BitCount == 4) { + + if (!BmpInfoHeader.ClrImportant) { + BmpInfoHeader.ClrImportant = 16; + } + bitCount = BmpInfoHeader.ClrImportant * sizeof(RGB_QUAD); + + if (fread(BmpRgbQuad, 1, bitCount, pFile) != bitCount){ + return 0; + } + + for (i = 0; i < BmpInfoHeader.ClrImportant; i++) { + + BmpPal[i * 3] = (uint8_t)BmpRgbQuad[i].Red; + BmpPal[i * 3 + 1] = (uint8_t)BmpRgbQuad[i].Green; + BmpPal[i * 3 + 2] = (uint8_t)BmpRgbQuad[i].Blue; + } + return 1; + } + return 0; +} + +/* maybe not the best BMP loader... */ +int BMP_Depack(FILE *pFile,char *pZone) +{ + char PadRead[4]; + int32_t i, j, Offset, PadSize, pix, c; + + if (BmpInfoHeader.Compression != BMP_BI_RGB) + return 0; + + PadSize = (BmpInfoHeader.Width & 3); + + PadSize = (4 - (BmpInfoHeader.Width & 3)) & 3; + + for (i = BmpInfoHeader.Height - 1; (i > -1); i--) { + Offset = i * BmpInfoHeader.Width / 2; + + if (PadSize < 4) { + for (j = 0; (j < BmpInfoHeader.Width / 2); j++) { + if (!fread(&c, 1, 1, pFile)) { + return 0; + } + pZone[Offset + j] = c; + } + } + + if (PadSize) { + if (fread(PadRead, PadSize, 1, pFile) != PadSize) { + return 0; + } + } + } + + if (i != -1) { + return 0; + } + + return 1; +} + + +int LoadPalettedBMP(const char* filename, Image* image) +{ + FILE *fp; + uint32_t bytes; + + if (filename == NULL || image == NULL) { + printf("Invalid NULL argument\n"); + return 0; + } + + fp = fopen(filename, "rb"); + if (fp == NULL) { + printf("Unable to open file\n"); + return 0; + } + + if (!BMP_Infos(fp, &image->width, &image->height)) { + printf("Error reading BMP:%s header\n",filename); + return 0; + } + + if (!BMP_GetPalette(fp)) { + printf("Only 16c BMP are supported for this sample"); + return 0; + } + + /* store palette information */ + image->palette = BmpPal; + image->palette_width = 16; + + + bytes = sizeof(char) * image->width * image->height; + /* 4bpp is half byte size*/ + bytes >>= 1; + + image->data = (char*)malloc(bytes); + if (image->data == NULL) { + printf("Error allocating image data"); + return 0; + } + + if (!BMP_Depack(fp, image->data)) { + printf("Error depacking BMP:%s",filename); + return 0; + } + + fclose(fp); + + return 1; +} + + +#endif + // Load Bitmaps And Convert To Textures void LoadGLTextures() { // Load Texture Image image1, image2; + #ifndef USE_16C_PALETTE if(!LoadPalettedPCX("/rd/NeHe.pcx", &image1)) { exit(1); } @@ -147,6 +375,15 @@ void LoadGLTextures() { if(!LoadPalettedPCX("/rd/NeHe-Alpha.pcx", &image2)) { exit(1); } + #else + if (!LoadPalettedBMP("/rd/NeHe.bmp", &image1)) { + exit(1); + } + + if (!LoadPalettedBMP("/rd/NeHe-Alpha.bmp", &image2)) { + exit(1); + } + #endif glEnable(GL_SHARED_TEXTURE_PALETTE_EXT); @@ -173,7 +410,11 @@ void LoadGLTextures() { // 2d texture, level of detail 0 (normal), 3 components (red, green, blue), x size from image, y size from image, // border 0 (normal), rgb color data, unsigned byte data, and finally the data itself. + #ifndef USE_16C_PALETTE glTexImage2D(GL_TEXTURE_2D, 0, GL_COLOR_INDEX8_EXT, image1.width, image1.height, 0, GL_COLOR_INDEX, GL_UNSIGNED_BYTE, image1.data); + #else + glTexImage2D(GL_TEXTURE_2D, 0, GL_COLOR_INDEX4_EXT, image1.width, image1.height, 0, GL_COLOR_INDEX, GL_UNSIGNED_BYTE, image1.data); + #endif glBindTexture(GL_TEXTURE_2D, textures[1]); // 2d texture (x and y size) glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER, GL_LINEAR); // scale linearly when image bigger than texture @@ -184,7 +425,11 @@ void LoadGLTextures() { // 2d texture, level of detail 0 (normal), 3 components (red, green, blue), x size from image, y size from image, // border 0 (normal), rgb color data, unsigned byte data, and finally the data itself. + #ifndef USE_16C_PALETTE glTexImage2D(GL_TEXTURE_2D, 0, GL_COLOR_INDEX8_EXT, image1.width, image1.height, 0, GL_COLOR_INDEX, GL_UNSIGNED_BYTE, image1.data); + #else + glTexImage2D(GL_TEXTURE_2D, 0, GL_COLOR_INDEX4_EXT, image1.width, image1.height, 0, GL_COLOR_INDEX, GL_UNSIGNED_BYTE, image1.data); + #endif glBindTexture(GL_TEXTURE_2D, textures[2]); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER, GL_NEAREST); @@ -199,8 +444,11 @@ void LoadGLTextures() { } glColorTableEXT(GL_TEXTURE_2D, GL_RGBA8, image2.palette_width, GL_RGBA, GL_UNSIGNED_BYTE, new_palette); - + #ifndef USE_16C_PALETTE glTexImage2D(GL_TEXTURE_2D, 0, GL_COLOR_INDEX8_EXT, image2.width, image2.height, 0, GL_COLOR_INDEX, GL_UNSIGNED_BYTE, image2.data); + #else + glTexImage2D(GL_TEXTURE_2D, 0, GL_COLOR_INDEX4_EXT, image2.width, image2.height, 0, GL_COLOR_INDEX, GL_UNSIGNED_BYTE, image2.data); + #endif } /* A general OpenGL initialization function. Sets all of the initial parameters. */ diff --git a/samples/paletted_pcx/romdisk/NeHe.bmp b/samples/paletted_pcx/romdisk/NeHe.bmp index 6b3db10f2b8d60997f7e213255809961985c2ef3..6a92446a1bb63054ba3dfd96c55fc4d80926ea2d 100644 GIT binary patch literal 65654 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 literal 196662 zcmeFa_j4RccJJHXRabXcwTbrLn`rNWh7EWSpaDP=^xn=4gF|vSepb6HZAiPix=*_D zSN8|KYva8?@x4!>W-t^9atLCFOANaL>}Xb3Wu5%yH&32CdGgfkdPk$zC`X5XdF6{% z{ioIVmH+K~{D0$PjKGJDz`p%rd~%GyFCGE4-~ZyPIj-OsfqfAe^M7Ah#wR}z1jhXT zKpJ{n&3zFV^M7Ah#wR}z1jhXTKpJ{n&3zFV^M7Ah#wR}z1jhXTKpJ{n&3zFV^M7Ah z#wR}z1jhXTKpJ{n&3zFV^M7Ah#wR}z1jhXTKpJ{n&3zFV^M7Ah#wR}z1jhXTKpJ{n z&3zFV^M7Ah#wR}z1jhXTKpJ{n&3zFV^M7Ah#wR}z1jhXTKpJ{n&3zFV^M7Ah#wR}z z1jhXTKpJ{n&3zFV^M7Ah#wR}z1jhXTKpJ{n&3zFV^M7Ah#wR}z1jhXTKpJ{n&3zFV z^M7Ah#wR}z1jhXTKpJ{n&3zFV^M7Ah#wR}z1jhXTKpJ{n&3zFV^M7Ah#wR}z1jhXT zKpJ{n&3zFV^M7Ah#wR}z1jhXTKpJ{n&3zF#5dXD59QzhEJ~>9<$RThb{_B4@bUNKT ztEt0oOGW*IMsBuOobQ*0jch&{k{ouu{@uxNn99Dl^1p>(Kj=T@twQhS(Cc)P(_TnN zMxEl~L~X8L?o>0ekk@84|4{@u5dTdkli6$*MbY7Kr2Mvi(r~6LTwhN;xw`zD&#wK$ zZ=d}8-+ui+|LHgX{qKMEufPBLuYdLN*H11#`{>x?tFsT!&s^M`>^Jg0kMxFO>vh^# z(6c(-e|-JIcc0$;`oY%Y%M%Yzl|S9CetvWAtB<#?T|CxnmRwHpg#v>PO(&)~)B91d zfBfy!|NdXT`#=BkyZ`l1zx}sAeEa47%Zn3TpX}hqgwL|pi#)wH_2-|R`@6q<`0s!B z?f?FlzxW^j@Mr)0mtTB*Woy`|*llKuQCp6Q$7eH7t}XxevupqK7tgqW%l_}b`}QAx z^W^f$m2x)yf(-ok?c|4G=$MGCA7;!~W_%AXbie!L%x^!t_wD_wC*~*8QQylCN(F?q z>D*WMPX5hrKK=K<{d21M=+1>+tNikn>Y29_Oug6e!+`=6&6;M)ac;Fl)BMe^AN~8^ z{`UX*x8MKg-#*>hT#N+2m(yX@b+YosjoLRK@BGbgxaQCQ-~aZ9|Ms`PzH#AXF`Imq zgz9;tL7#~Sww9;AynFRGPw#yD(az^LXTP{K`25D~Hy@vQbm#oW!fYZMH0aege!af1 zSN?h+{@ZLem&+9h1TvXSrI0Qr{k5oNIv2V;SATYK_7Bgl(*^$Z_fP)Qw|76fyuLbF zYNUd-s4z?!PIg@Pwrk%$JooJ3jbn@Rkx<~30`30bw42+FgTO4~U7Nvl=x|<-%v?8jmi`|HT(K zA??5Y^|QbF^7g%p>$Ckb{7;76+%?QtRy(2V3svO*>(6ie)9*h2x4-`EyGNI=oSGYU zD)WPuBw8I7?I>@bYsRl1Ykq!v@`r+;K53X&!AkrZPQ*Q@etJNw90$;p>Zl>he=$1b|IrY0o7ykKoU;K~1|FhqJ z^YO#m=X%ZWdtXe}&sU}Ge(B-X;CG*1{O8|&{y+cm*Z<{jzNUw6Y%J_6p?XqwScaYK z?F(zDzK^b*?bLJ8M7mZ;=F&;Q?irL553kJr{#SQDxp{iB*RTt=*A(^opAN)-G=DG{ zg#U>|!k4Ito@P!6RC98(Gb(qM6UA;Q5DY}^^=AF-skLVxU;F6h=845jKX0pL0#Yzu z4HX|=SpEH1pItk*Q!bQV)pPA;U9T88yVk#Sa=zbg%7S}iX6?r5`JLHFKj^7Sa=Yv= z)$2}Q^X%Hxx1Zj-d}bq;PN>zCB&lBrq}Jk z$J5zHASx!aAufA(<2?HR1^ypKel$!x9&e>nF;_M3bT!M7;q3C(_Qj7bokRbt{D=H1 z3Hh{J^ryS_Yp1%6h3mD}d^i|(`#g-l|Md^wyxi`G5lk%ͰF-6LSL>~!sG^Y!B7 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 From 8caee68df5a0ead87024b7b6b10aa8cd774b6704 Mon Sep 17 00:00:00 2001 From: OzzyOuzo Date: Tue, 30 Nov 2021 19:23:49 +0200 Subject: [PATCH 3/5] added _glFreePaletteSlots(..) --- GL/texture.c | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/GL/texture.c b/GL/texture.c index 150648f..65350a4 100644 --- a/GL/texture.c +++ b/GL/texture.c @@ -87,6 +87,31 @@ static GLshort _glGenPaletteSlot(GLushort size) { fprintf(stderr, "GL ERROR: No palette slots remain\n"); return -1; } +/* ozzy: used for statistics */ +GLushort _glFreePaletteSlots(GLushort size) +{ + GLushort i, j , slots = 0; + + assert(size == 16 || size == 256); + + if(size == 16) { + for(i = 0; i < 4; ++i) { + for(j = 0; j < 16; ++j) { + if(!SUBBANKS_USED[i][j]) { + slots++; + } + } + } + } else { + for(i = 0; i < 4; ++i) { + if(!BANKS_USED[i]) { + slots++; + } + } + } + + return slots; +} static void _glReleasePaletteSlot(GLshort slot, GLushort size) { GLushort i; @@ -264,6 +289,8 @@ static GLuint _glGetMipmapDataOffset(const TextureObject* obj, GLuint level) { GLuint offset = 0; GLuint size = obj->height; + printf("\n_glGetMipmapDataOffset"); + if(obj->width != obj->height) { fprintf(stderr, "ERROR: Accessing memory location of mipmaps on non-square texture\n"); return obj->baseDataOffset; @@ -919,7 +946,16 @@ GL_FORCE_INLINE void _i8_to_i8(const GLubyte* source, GLubyte* dest) { } static inline void _alpha8_to_argb4444(const GLubyte* source, GLubyte* dest) { + #if 0 + /*A111*/ *((GLushort*) dest) = (*source & 0xF0) << 8 | (0xFF & 0xF0) << 4 | (0xFF & 0xF0) | (0xFF & 0xF0) >> 4; + #else + /* AAAA rather than A111*/ + GLushort color = *source&0xf0; + color |= color>>4; + + *((GLushort*) dest) = (color << 8)|color; + #endif } static TextureConversionFunc _determineConversion(GLint internalFormat, GLenum format, GLenum type) { @@ -927,6 +963,7 @@ static TextureConversionFunc _determineConversion(GLint internalFormat, GLenum f case GL_ALPHA: { if(format == GL_ALPHA) { /* Dreamcast doesn't really support GL_ALPHA internally, so store as argb with each rgb value as white */ + /* Ozzy : applying alpha values to all channels seems a better option*/ return _alpha8_to_argb4444; } else if(type == GL_UNSIGNED_BYTE && format == GL_RGBA) { return _rgba8888_to_a000; @@ -1087,6 +1124,12 @@ void APIENTRY glTexImage2D(GLenum target, GLint level, GLint internalFormat, return; } + if (width > 1024 || height > 1024){ + INFO_MSG("Invalid texture size"); + _glKosThrowError(GL_INVALID_VALUE, __func__); + return; + } + if(format != GL_COLOR_INDEX) { if(!_isSupportedFormat(format)) { INFO_MSG("Unsupported format"); @@ -1671,6 +1714,9 @@ GLAPI void APIENTRY glReadPixels(GLint x, GLint y, GLsizei width, GLsizei height _GL_UNUSED(pixels); assert(0 && "Not Implemented"); } +GLuint _glMaxTextureMemory() { + return YALLOC_SIZE; +} GLuint _glFreeTextureMemory() { return yalloc_count_free(YALLOC_BASE); From 1a456cb1a7893e736a9b4760745eb1362a500b1c Mon Sep 17 00:00:00 2001 From: OzzyOuzo Date: Fri, 3 Dec 2021 09:32:53 +0200 Subject: [PATCH 4/5] 16bit palette color format support, extended 64 shared palette slots (4bpp), GL_OES_compressed_paletted_texture support GL_ERROR generated when texture size > 1024 --- GL/draw.c | 2 +- GL/flush.c | 2 +- GL/lighting.c | 18 +-- GL/private.h | 9 +- GL/state.c | 16 ++- GL/texture.c | 334 ++++++++++++++++++++++++++++++++++----------- include/GL/glext.h | 14 ++ include/GL/glkos.h | 79 ++++++++++- 8 files changed, 375 insertions(+), 99 deletions(-) diff --git a/GL/draw.c b/GL/draw.c index edebc33..ee822d3 100644 --- a/GL/draw.c +++ b/GL/draw.c @@ -1333,7 +1333,7 @@ GLuint _glGetActiveClientTexture() { void APIENTRY glClientActiveTextureARB(GLenum texture) { TRACE(); - if(texture < GL_TEXTURE0_ARB || texture > GL_TEXTURE0_ARB + MAX_TEXTURE_UNITS) { + if(texture < GL_TEXTURE0_ARB || texture > GL_TEXTURE0_ARB + MAX_GLDC_TEXTURE_UNITS) { _glKosThrowError(GL_INVALID_ENUM, __func__); return; } diff --git a/GL/flush.c b/GL/flush.c index eec9d76..e1d9470 100644 --- a/GL/flush.c +++ b/GL/flush.c @@ -46,7 +46,7 @@ void APIENTRY glKosInitConfig(GLdcConfig* config) { config->initial_pt_capacity = 512 * 3; config->initial_tr_capacity = 1024 * 3; config->initial_immediate_capacity = 1024 * 3; - config->internal_palette_format = GL_RGBA4; + config->internal_palette_format = GL_RGBA8; } void APIENTRY glKosInitEx(GLdcConfig* config) { diff --git a/GL/lighting.c b/GL/lighting.c index 06e3bc5..4667669 100644 --- a/GL/lighting.c +++ b/GL/lighting.c @@ -27,7 +27,7 @@ static GLenum COLOR_MATERIAL_MODE = GL_AMBIENT_AND_DIFFUSE; static GLenum COLOR_MATERIAL_MASK = AMBIENT_MASK | DIFFUSE_MASK; -static LightSource LIGHTS[MAX_LIGHTS]; +static LightSource LIGHTS[MAX_GLDC_LIGHTS]; static GLuint ENABLED_LIGHT_COUNT = 0; static Material MATERIAL; @@ -37,7 +37,7 @@ static void recalcEnabledLights() { GLubyte i; ENABLED_LIGHT_COUNT = 0; - for(i = 0; i < MAX_LIGHTS; ++i) { + for(i = 0; i < MAX_GLDC_LIGHTS; ++i) { if(LIGHTS[i].isEnabled) { ENABLED_LIGHT_COUNT++; } @@ -57,7 +57,7 @@ void _glInitLights() { MATERIAL.exponent = 0.0f; GLubyte i; - for(i = 0; i < MAX_LIGHTS; ++i) { + for(i = 0; i < MAX_GLDC_LIGHTS; ++i) { memcpy(LIGHTS[i].ambient, ZERO, sizeof(GLfloat) * 4); memcpy(LIGHTS[i].diffuse, ONE, sizeof(GLfloat) * 4); memcpy(LIGHTS[i].specular, ONE, sizeof(GLfloat) * 4); @@ -97,7 +97,7 @@ GL_FORCE_INLINE void _glPrecalcLightingValues(GLuint mask) { GLshort i; if(mask & AMBIENT_MASK) { - for(i = 0; i < MAX_LIGHTS; ++i) { + for(i = 0; i < MAX_GLDC_LIGHTS; ++i) { LIGHTS[i].ambientMaterial[0] = LIGHTS[i].ambient[0] * MATERIAL.ambient[0]; LIGHTS[i].ambientMaterial[1] = LIGHTS[i].ambient[1] * MATERIAL.ambient[1]; LIGHTS[i].ambientMaterial[2] = LIGHTS[i].ambient[2] * MATERIAL.ambient[2]; @@ -106,7 +106,7 @@ GL_FORCE_INLINE void _glPrecalcLightingValues(GLuint mask) { } if(mask & DIFFUSE_MASK) { - for(i = 0; i < MAX_LIGHTS; ++i) { + for(i = 0; i < MAX_GLDC_LIGHTS; ++i) { LIGHTS[i].diffuseMaterial[0] = LIGHTS[i].diffuse[0] * MATERIAL.diffuse[0]; LIGHTS[i].diffuseMaterial[1] = LIGHTS[i].diffuse[1] * MATERIAL.diffuse[1]; LIGHTS[i].diffuseMaterial[2] = LIGHTS[i].diffuse[2] * MATERIAL.diffuse[2]; @@ -115,7 +115,7 @@ GL_FORCE_INLINE void _glPrecalcLightingValues(GLuint mask) { } if(mask & SPECULAR_MASK) { - for(i = 0; i < MAX_LIGHTS; ++i) { + for(i = 0; i < MAX_GLDC_LIGHTS; ++i) { LIGHTS[i].specularMaterial[0] = LIGHTS[i].specular[0] * MATERIAL.specular[0]; LIGHTS[i].specularMaterial[1] = LIGHTS[i].specular[1] * MATERIAL.specular[1]; LIGHTS[i].specularMaterial[2] = LIGHTS[i].specular[2] * MATERIAL.specular[2]; @@ -173,7 +173,7 @@ void APIENTRY glLightModeliv(GLenum pname, const GLint* params) { void APIENTRY glLightfv(GLenum light, GLenum pname, const GLfloat *params) { GLubyte idx = light & 0xF; - if(idx >= MAX_LIGHTS) { + if(idx >= MAX_GLDC_LIGHTS) { return; } @@ -227,7 +227,7 @@ void APIENTRY glLightfv(GLenum light, GLenum pname, const GLfloat *params) { void APIENTRY glLightf(GLenum light, GLenum pname, GLfloat param) { GLubyte idx = light & 0xF; - if(idx >= MAX_LIGHTS) { + if(idx >= MAX_GLDC_LIGHTS) { return; } @@ -495,7 +495,7 @@ void _glPerformLighting(Vertex* vertices, EyeSpaceData* es, const uint32_t count const float Ny = data->n[1]; const float Nz = data->n[2]; - for(i = 0; i < MAX_LIGHTS; ++i) { + for(i = 0; i < MAX_GLDC_LIGHTS; ++i) { if(!LIGHTS[i].isEnabled) { continue; } diff --git a/GL/private.h b/GL/private.h index 2bcee46..46287dd 100644 --- a/GL/private.h +++ b/GL/private.h @@ -14,6 +14,11 @@ #include "../containers/aligned_vector.h" #include "../containers/named_array.h" +#define MAX_GLDC_4BPP_PALETTE_SLOTS 16 +#define MAX_GLDC_PALETTE_SLOTS 4 +#define MAX_GLDC_SHARED_PALETTES (MAX_GLDC_PALETTE_SLOTS*MAX_GLDC_4BPP_PALETTE_SLOTS) + + extern void* memcpy4 (void *dest, const void *src, size_t count); #define GL_NO_INSTRUMENT inline __attribute__((no_instrument_function)) @@ -508,8 +513,8 @@ GLuint _glFreeContiguousTextureMemory(); void _glApplyScissor(bool force); -#define MAX_TEXTURE_UNITS 2 -#define MAX_LIGHTS 8 +#define MAX_GLDC_TEXTURE_UNITS 2 +#define MAX_GLDC_LIGHTS 8 /* This is from KOS pvr_buffers.c */ #define PVR_MIN_Z 0.0001f diff --git a/GL/state.c b/GL/state.c index a7a28ad..f4ec6de 100644 --- a/GL/state.c +++ b/GL/state.c @@ -236,8 +236,14 @@ void _glUpdatePVRTextureContext(PolyContext *context, GLshort textureUnit) { if(tx1->isPaletted) { if(_glIsSharedTexturePaletteEnabled()) { TexturePalette* palette = _glGetSharedPalette(tx1->shared_bank); - context->txr.format |= GPUPaletteSelect8BPP(palette->bank); - } else { + if (palette->size != 16){ + context->txr.format |= GPUPaletteSelect8BPP(palette->bank); + } + else{ + context->txr.format |= GPUPaletteSelect4BPP(palette->bank); + } + } + else { if (tx1->palette->size != 16){ context->txr.format |= GPUPaletteSelect8BPP((tx1->palette) ? tx1->palette->bank : 0); } @@ -295,7 +301,7 @@ void _glInitContext() { glDisable(GL_LIGHTING); GLubyte i; - for(i = 0; i < MAX_LIGHTS; ++i) { + for(i = 0; i < MAX_GLDC_LIGHTS; ++i) { glDisable(GL_LIGHT0 + i); } } @@ -719,7 +725,7 @@ void APIENTRY glGetFloatv(GLenum pname, GLfloat* params) { void APIENTRY glGetIntegerv(GLenum pname, GLint *params) { switch(pname) { case GL_MAX_LIGHTS: - *params = MAX_LIGHTS; + *params = MAX_GLDC_LIGHTS; break; case GL_TEXTURE_BINDING_2D: *params = (_glGetBoundTexture()) ? _glGetBoundTexture()->index : 0; @@ -779,7 +785,7 @@ const GLubyte *glGetString(GLenum name) { return (const GLubyte*) "1.2 (partial) - GLdc 1.1"; case GL_EXTENSIONS: - return (const GLubyte*) "GL_ARB_framebuffer_object, GL_ARB_multitexture, GL_ARB_texture_rg, GL_EXT_paletted_texture, GL_EXT_shared_texture_palette, GL_KOS_multiple_shared_palette, GL_ARB_vertex_array_bgra, GL_ARB_vertex_type_2_10_10_10_rev, GL_KOS_texture_memory_management, GL_ATI_meminfo"; + return (const GLubyte*)"GL_ARB_framebuffer_object, GL_ARB_multitexture, GL_ARB_texture_rg, GL_OES_compressed_paletted_texture,GL_EXT_paletted_texture, GL_EXT_shared_texture_palette, GL_KOS_multiple_shared_palette, GL_ARB_vertex_array_bgra, GL_ARB_vertex_type_2_10_10_10_rev, GL_KOS_texture_memory_management, GL_ATI_meminfo"; } return (const GLubyte*) "GL_KOS_ERROR: ENUM Unsupported\n"; diff --git a/GL/texture.c b/GL/texture.c index 65350a4..b8f9e05 100644 --- a/GL/texture.c +++ b/GL/texture.c @@ -18,17 +18,18 @@ #define CLAMP_U (1<<1) #define CLAMP_V (1<<0) -static TextureObject* TEXTURE_UNITS[MAX_TEXTURE_UNITS] = {NULL, NULL}; +static TextureObject* TEXTURE_UNITS[MAX_GLDC_TEXTURE_UNITS] = {NULL, NULL}; static NamedArray TEXTURE_OBJECTS; GLubyte ACTIVE_TEXTURE = 0; -static TexturePalette* SHARED_PALETTES[4] = {NULL, NULL, NULL, NULL}; +static TexturePalette* SHARED_PALETTES[MAX_GLDC_SHARED_PALETTES]; static GLuint _determinePVRFormat(GLint internalFormat, GLenum type); -static GLboolean BANKS_USED[4]; // Each time a 256 colour bank is used, this is set to true -static GLboolean SUBBANKS_USED[4][16]; // 4 counts of the used 16 colour banks within the 256 ones -static GLenum INTERNAL_PALETTE_FORMAT = GL_RGBA4; +static GLboolean BANKS_USED[MAX_GLDC_PALETTE_SLOTS]; // Each time a 256 colour bank is used, this is set to true +static GLboolean SUBBANKS_USED[MAX_GLDC_PALETTE_SLOTS][MAX_GLDC_4BPP_PALETTE_SLOTS]; // 4 counts of the used 16 colour banks within the 256 ones + +static GLenum INTERNAL_PALETTE_FORMAT = GL_RGBA8; static void* YALLOC_BASE = NULL; static size_t YALLOC_SIZE = 0; @@ -63,20 +64,21 @@ static GLshort _glGenPaletteSlot(GLushort size) { assert(size == 16 || size == 256); if(size == 16) { - for(i = 0; i < 4; ++i) { - for(j = 0; j < 16; ++j) { + for(i = 0; i < MAX_GLDC_PALETTE_SLOTS; ++i) { + for(j = 0; j < MAX_GLDC_4BPP_PALETTE_SLOTS; ++j) { if(!SUBBANKS_USED[i][j]) { BANKS_USED[i] = GL_TRUE; SUBBANKS_USED[i][j] = GL_TRUE; - return (i * 16) + j; + return (i * MAX_GLDC_4BPP_PALETTE_SLOTS) + j; } } } - } else { - for(i = 0; i < 4; ++i) { + } + else { + for(i = 0; i < MAX_GLDC_PALETTE_SLOTS; ++i) { if(!BANKS_USED[i]) { BANKS_USED[i] = GL_TRUE; - for(j = 0; j < 16; ++j) { + for(j = 0; j < MAX_GLDC_4BPP_PALETTE_SLOTS; ++j) { SUBBANKS_USED[i][j] = GL_TRUE; } return i; @@ -87,6 +89,7 @@ static GLshort _glGenPaletteSlot(GLushort size) { fprintf(stderr, "GL ERROR: No palette slots remain\n"); return -1; } + /* ozzy: used for statistics */ GLushort _glFreePaletteSlots(GLushort size) { @@ -95,15 +98,15 @@ GLushort _glFreePaletteSlots(GLushort size) assert(size == 16 || size == 256); if(size == 16) { - for(i = 0; i < 4; ++i) { - for(j = 0; j < 16; ++j) { + for(i = 0; i < MAX_GLDC_PALETTE_SLOTS; ++i) { + for(j = 0; j < MAX_GLDC_4BPP_PALETTE_SLOTS; ++j) { if(!SUBBANKS_USED[i][j]) { slots++; } } } } else { - for(i = 0; i < 4; ++i) { + for(i = 0; i < MAX_GLDC_PALETTE_SLOTS; ++i) { if(!BANKS_USED[i]) { slots++; } @@ -113,25 +116,29 @@ GLushort _glFreePaletteSlots(GLushort size) return slots; } -static void _glReleasePaletteSlot(GLshort slot, GLushort size) { - GLushort i; +static void _glReleasePaletteSlot(GLshort slot, GLushort size) +{ + GLushort i, j; assert(size == 16 || size == 256); - if(size == 16) { - GLushort bank = slot / 4; - GLushort subbank = slot % 4; + + + if (size == 16) { + GLushort bank = slot / MAX_GLDC_PALETTE_SLOTS; + GLushort subbank = slot % MAX_GLDC_PALETTE_SLOTS; SUBBANKS_USED[bank][subbank] = GL_FALSE; - for(i = 0; i < 16; ++i) { - if(SUBBANKS_USED[bank][i]) { + + for (i = 0; i < MAX_GLDC_4BPP_PALETTE_SLOTS; ++i) { + if (SUBBANKS_USED[bank][i]) { return; } } - BANKS_USED[bank] = GL_FALSE; - } else { + } + else { BANKS_USED[slot] = GL_FALSE; - for(i = 0; i < 16; ++i) { + for (i = 0; i < MAX_GLDC_4BPP_PALETTE_SLOTS; ++i) { SUBBANKS_USED[slot][i] = GL_FALSE; } } @@ -221,27 +228,31 @@ static void GPUTextureTwiddle16BPP(void * src, void* dst, uint32_t w, uint32_t h } TexturePalette* _glGetSharedPalette(GLshort bank) { - assert(bank >= 0 && bank < 4); + assert(bank >= 0 && bank < MAX_GLDC_SHARED_PALETTES); return SHARED_PALETTES[bank]; } - void _glSetInternalPaletteFormat(GLenum val) { INTERNAL_PALETTE_FORMAT = val; - if(INTERNAL_PALETTE_FORMAT == GL_RGBA4) { - GPUSetPaletteFormat(GPU_PAL_ARGB4444); - } else { - assert(INTERNAL_PALETTE_FORMAT == GL_RGBA8); - GPUSetPaletteFormat(GPU_PAL_ARGB8888); + switch(INTERNAL_PALETTE_FORMAT){ + case GL_RGBA8: + GPUSetPaletteFormat(GPU_PAL_ARGB8888); + break; + case GL_RGBA4: + GPUSetPaletteFormat(GPU_PAL_ARGB4444); + break; + case GL_RGB5_A1: + GPUSetPaletteFormat(GPU_PAL_ARGB1555); + break; + case GL_RGB565_KOS: + GPUSetPaletteFormat(GPU_PAL_RGB565); + break; + default: + assert(0); + } } - void _glApplyColorTable(TexturePalette* src) { - /* - * FIXME: - * - * - Different palette formats (GL_RGB -> PVR_PAL_RGB565) - */ if(!src || !src->data) { return; } @@ -251,10 +262,21 @@ void _glApplyColorTable(TexturePalette* src) { for(i = 0; i < src->width; ++i) { GLubyte* entry = &src->data[i * 4]; - if(INTERNAL_PALETTE_FORMAT == GL_RGBA8) { - GPUSetPaletteEntry(offset + i, PACK_ARGB8888(entry[3], entry[0], entry[1], entry[2])); - } else { - GPUSetPaletteEntry(offset + i, PACK_ARGB4444(entry[3], entry[0], entry[1], entry[2])); + + switch(INTERNAL_PALETTE_FORMAT) + { + case GL_RGBA8: + GPUSetPaletteEntry(offset + i, PACK_ARGB8888(entry[3], entry[0], entry[1], entry[2])); + break; + case GL_RGBA4: + GPUSetPaletteEntry(offset + i, PACK_ARGB4444(entry[3], entry[0], entry[1], entry[2])); + break; + case GL_RGB5_A1: + GPUSetPaletteEntry(offset + i, PACK_ARGB1555(entry[3], entry[0], entry[1], entry[2])); + break; + case GL_RGB565_KOS: + GPUSetPaletteEntry(offset + i, PACK_RGB565(entry[0], entry[1], entry[2])); + break; } } } @@ -289,8 +311,6 @@ static GLuint _glGetMipmapDataOffset(const TextureObject* obj, GLuint level) { GLuint offset = 0; GLuint size = obj->height; - printf("\n_glGetMipmapDataOffset"); - if(obj->width != obj->height) { fprintf(stderr, "ERROR: Accessing memory location of mipmaps on non-square texture\n"); return obj->baseDataOffset; @@ -428,15 +448,17 @@ static GLuint _glGetMipmapDataSize(TextureObject* obj) { } GLubyte _glInitTextures() { + + uint32_t i; + named_array_init(&TEXTURE_OBJECTS, sizeof(TextureObject), MAX_TEXTURE_COUNT); // Reserve zero so that it is never given to anyone as an ID! named_array_reserve(&TEXTURE_OBJECTS, 0); - SHARED_PALETTES[0] = _initTexturePalette(); - SHARED_PALETTES[1] = _initTexturePalette(); - SHARED_PALETTES[2] = _initTexturePalette(); - SHARED_PALETTES[3] = _initTexturePalette(); + for (i=0; i < MAX_GLDC_SHARED_PALETTES;i++){ + SHARED_PALETTES[i] = _initTexturePalette(); + } memset((void*) BANKS_USED, 0x0, sizeof(BANKS_USED)); memset((void*) SUBBANKS_USED, 0x0, sizeof(SUBBANKS_USED)); @@ -469,7 +491,7 @@ TextureObject* _glGetBoundTexture() { void APIENTRY glActiveTextureARB(GLenum texture) { TRACE(); - if(texture < GL_TEXTURE0_ARB || texture > GL_TEXTURE0_ARB + MAX_TEXTURE_UNITS) { + if(texture < GL_TEXTURE0_ARB || texture > GL_TEXTURE0_ARB + MAX_GLDC_TEXTURE_UNITS) { _glKosThrowError(GL_INVALID_ENUM, "glActiveTextureARB"); return; } @@ -539,6 +561,11 @@ void APIENTRY glDeleteTextures(GLsizei n, GLuint *textures) { } if(txr->palette && txr->palette->data) { + + if (txr->palette->bank > -1) { + _glReleasePaletteSlot(txr->palette->bank, txr->palette->size); + txr->palette->bank = -1; + } free(txr->palette->data); txr->palette->data = NULL; } @@ -679,6 +706,8 @@ void APIENTRY glCompressedTexImage2DARB(GLenum target, } GLboolean mipmapped = GL_FALSE; + //GLboolean paletted = GL_FALSE; + GLbyte *ptr = (GLbyte*)data; switch(internalFormat) { case GL_COMPRESSED_ARGB_1555_VQ_KOS: @@ -696,6 +725,50 @@ void APIENTRY glCompressedTexImage2DARB(GLenum target, case GL_COMPRESSED_RGB_565_VQ_MIPMAP_TWID_KOS: mipmapped = GL_TRUE; break; + case GL_PALETTE4_RGB8_OES: + glColorTableEXT(GL_TEXTURE_2D, GL_RGBA8, 16, internalFormat, GL_UNSIGNED_BYTE, data); + ptr += 16*3; + glTexImage2D(GL_TEXTURE_2D, level, GL_COLOR_INDEX4_EXT, width, height, border, GL_COLOR_INDEX, GL_UNSIGNED_BYTE, ptr); + return; + + case GL_PALETTE4_RGBA8_OES: + glColorTableEXT(GL_TEXTURE_2D, GL_RGBA8, 16, internalFormat, GL_UNSIGNED_BYTE, data); + ptr += 16*4; + glTexImage2D(GL_TEXTURE_2D, level, GL_COLOR_INDEX4_EXT, width, height, border, GL_COLOR_INDEX, GL_UNSIGNED_BYTE, ptr); + return; + + case GL_PALETTE4_R5_G6_B5_OES: + case GL_PALETTE4_RGBA4_OES: + case GL_PALETTE4_RGB5_A1_OES: + glColorTableEXT(GL_TEXTURE_2D, GL_RGBA8, 16, internalFormat, GL_UNSIGNED_BYTE, data); + ptr += 16*2; + glTexImage2D(GL_TEXTURE_2D, level, GL_COLOR_INDEX4_EXT, width, height, border, GL_COLOR_INDEX, GL_UNSIGNED_BYTE, ptr); + return; + + case GL_PALETTE8_RGB8_OES: + glColorTableEXT(GL_TEXTURE_2D, GL_RGBA8, 256, internalFormat, GL_UNSIGNED_BYTE, data); + ptr += 256*3; + glTexImage2D(GL_TEXTURE_2D, level, GL_COLOR_INDEX8_EXT, width, height, border, GL_COLOR_INDEX, GL_UNSIGNED_BYTE, ptr); + return; + + + case GL_PALETTE8_RGBA8_OES: + // + glColorTableEXT(GL_TEXTURE_2D, GL_RGBA8, 256, internalFormat, GL_UNSIGNED_BYTE, data); + ptr += 256*4; + glTexImage2D(GL_TEXTURE_2D, level, GL_COLOR_INDEX8_EXT, width, height, border, GL_COLOR_INDEX, GL_UNSIGNED_BYTE, ptr); + return; + + + case GL_PALETTE8_RGBA4_OES: + case GL_PALETTE8_RGB5_A1_OES: + case GL_PALETTE8_R5_G6_B5_OES: + + glColorTableEXT(GL_TEXTURE_2D, GL_RGBA8, 256, internalFormat, GL_UNSIGNED_BYTE, data); + ptr += 256*2; + glTexImage2D(GL_TEXTURE_2D, level, GL_COLOR_INDEX8_EXT, width, height, border, GL_COLOR_INDEX, GL_UNSIGNED_BYTE, ptr); + return; + default: { _glKosThrowError(GL_INVALID_OPERATION, __func__); return; @@ -887,9 +960,21 @@ static GLint _cleanInternalFormat(GLint internalFormat) { typedef void (*TextureConversionFunc)(const GLubyte*, GLubyte*); GL_FORCE_INLINE void _rgba8888_to_argb4444(const GLubyte* source, GLubyte* dest) { - *((GLushort*) dest) = (source[3] & 0xF0) << 8 | (source[0] & 0xF0) << 4 | (source[1] & 0xF0) | (source[2] & 0xF0) >> 4; + + *((GLushort*) dest) = (source[1] & 0xF0) << 8 | (source[2] & 0xF0) << 4 | (source[0] & 0xF0) | (source[3] & 0xF0) >> 4; } +GL_FORCE_INLINE void _rgba8888_to_rgba4444(const GLubyte* source, GLubyte* dest) { + + *((GLushort*) dest) = (source[3] & 0xF0) << 8 | (source[2] & 0xF0) << 4 | (source[1] & 0xF0) | (source[0] & 0xF0) >> 4; +} + +GL_FORCE_INLINE void _rgb888_to_argb4444(const GLubyte* source, GLubyte* dest) { + + *((GLushort*) dest) = 0xF << 8 | (source[0] & 0xF0) << 4 | (source[1] & 0xF0) | (source[2] & 0xF0) >> 4; +} + + GL_FORCE_INLINE void _rgba8888_to_rgba8888(const GLubyte* source, GLubyte* dest) { /* Noop */ GLubyte* dst = (GLubyte*) dest; @@ -899,7 +984,15 @@ GL_FORCE_INLINE void _rgba8888_to_rgba8888(const GLubyte* source, GLubyte* dest) dst[3] = source[3]; } +GL_FORCE_INLINE void _rgba4444_to_rgba4444(const GLubyte* source, GLubyte* dest) { + /* Noop */ + GLubyte* dst = (GLubyte*) dest; + dst[0] = source[0]; + dst[1] = source[1]; +} + GL_FORCE_INLINE void _rgba8888_to_rgb565(const GLubyte* source, GLubyte* dest) { + *((GLushort*) dest) = ((source[0] & 0b11111000) << 8) | ((source[1] & 0b11111100) << 3) | (source[2] >> 3); } @@ -912,10 +1005,24 @@ GL_FORCE_INLINE void _rgb888_to_rgba8888(const GLubyte* source, GLubyte* dest) { dst[3] = 255; } +GL_FORCE_INLINE void _rgb888_to_rgba4444(const GLubyte* source, GLubyte* dest) { + /* Noop */ + *((GLushort*) dest) = 0xF << 8 | (source[2] & 0xF0) << 4 | (source[1] & 0xF0) | (source[0] & 0xF0) >> 4; + +} GL_FORCE_INLINE void _rgb888_to_rgb565(const GLubyte* source, GLubyte* dest) { *((GLushort*) dest) = ((source[0] & 0b11111000) << 8) | ((source[1] & 0b11111100) << 3) | (source[2] >> 3); } +GL_FORCE_INLINE void _rgb565_to_rgb8888(const GLubyte* source, GLubyte* dest) { + GLushort src = *((GLushort*) source); + + dest[3] = (src&0x1f)<<3; + dest[2] = ((src>>5)&0x3f)<<2; + dest[1] = ((src>>11)&0x1f)<<3; + dest[0] = 0xff; +} + GL_FORCE_INLINE void _rgba8888_to_a000(const GLubyte* source, GLubyte* dest) { *((GLushort*) dest) = ((source[3] & 0b11111000) << 8); } @@ -933,12 +1040,23 @@ GL_FORCE_INLINE void _rgba4444_to_rgba8888(const GLubyte* source, GLubyte* dest) GLushort src = *((GLushort*) source); GLubyte* dst = (GLubyte*) dest; - dst[0] = ((src & 0xF000) >> 12) * 2; - dst[1] = ((src & 0x0F00) >> 8) * 2; - dst[2] = ((src & 0x00F0) >> 4) * 2; - dst[3] = ((src & 0x000F)) * 2; + dst[0] = (src&0xf)<<4; + dst[1] = ((src>>4)&0xf) << 4; + dst[2] = ((src>>8)&0xf) << 4; + dst[3] = (src>>12) << 4; } +GL_FORCE_INLINE void _rgba5551_to_rgba8888(const GLubyte* source, GLubyte* dest) { + GLushort src = *((GLushort*) source); + GLubyte* dst = (GLubyte*) dest; + + dst[0] = (src&0x1f)<<3; + dst[1] = ((src>>5)&0x1f) << 3; + dst[2] = ((src>>5)&0x1f) << 3; + dst[3] = (src>>15) << 7; +} + + GL_FORCE_INLINE void _i8_to_i8(const GLubyte* source, GLubyte* dest) { /* For indexes */ GLubyte* dst = (GLubyte*) dest; @@ -950,6 +1068,7 @@ static inline void _alpha8_to_argb4444(const GLubyte* source, GLubyte* dest) { /*A111*/ *((GLushort*) dest) = (*source & 0xF0) << 8 | (0xFF & 0xF0) << 4 | (0xFF & 0xF0) | (0xFF & 0xF0) >> 4; #else + //Tested with multi-texturing sample , unit0: texture map unit1:alpha map /* AAAA rather than A111*/ GLushort color = *source&0xf0; color |= color>>4; @@ -997,20 +1116,43 @@ static TextureConversionFunc _determineConversion(GLint internalFormat, GLenum f return _rgba4444_to_argb4444; } } break; - case GL_RGBA8: { + case GL_RGBA8: + case GL_RGBA4: + case GL_RGB5_A1: + case GL_RGB565_KOS: if(type == GL_UNSIGNED_BYTE && format == GL_RGBA) { return _rgba8888_to_rgba8888; - } else if (type == GL_BYTE && format == GL_RGBA) { + } + else + if (type == GL_BYTE && format == GL_RGBA) { return _rgba8888_to_rgba8888; - } else if(type == GL_UNSIGNED_BYTE && format == GL_RGB) { + } + else + if(type == GL_UNSIGNED_BYTE && format == GL_RGB) { return _rgb888_to_rgba8888; - } else if (type == GL_BYTE && format == GL_RGB) { + } + else + if (type == GL_BYTE && format == GL_RGB) { return _rgb888_to_rgba8888; - } else if(type == GL_UNSIGNED_SHORT_4_4_4_4 && format == GL_RGBA) { + } + else + if(type == GL_UNSIGNED_SHORT_4_4_4_4 && format == GL_RGBA) { return _rgba4444_to_rgba8888; } - } break; + else + if(type == GL_UNSIGNED_BYTE && format == GL_RGBA4) { + return _rgba4444_to_rgba8888; + } + else + if(type == GL_UNSIGNED_BYTE && format == GL_RGB5_A1) { + return _rgba5551_to_rgba8888; + } + else + if(type == GL_UNSIGNED_BYTE && format == GL_RGB565_KOS) { + return _rgb565_to_rgb8888; + } + break; case GL_COLOR_INDEX8_EXT: if(format == GL_COLOR_INDEX) { switch(type) { @@ -1494,27 +1636,61 @@ void APIENTRY glTexParameterf(GLenum target, GLenum pname, GLint param) { } GLAPI void APIENTRY glColorTableEXT(GLenum target, GLenum internalFormat, GLsizei width, GLenum format, GLenum type, const GLvoid *data) { + GLint validTargets[] = { GL_TEXTURE_2D, GL_SHARED_TEXTURE_PALETTE_EXT, - GL_SHARED_TEXTURE_PALETTE_0_KOS, - GL_SHARED_TEXTURE_PALETTE_1_KOS, - GL_SHARED_TEXTURE_PALETTE_2_KOS, - GL_SHARED_TEXTURE_PALETTE_3_KOS, - 0 - }; + GL_SHARED_TEXTURE_PALETTE_0_KOS,GL_SHARED_TEXTURE_PALETTE_1_KOS,GL_SHARED_TEXTURE_PALETTE_2_KOS,GL_SHARED_TEXTURE_PALETTE_3_KOS,GL_SHARED_TEXTURE_PALETTE_4_KOS,GL_SHARED_TEXTURE_PALETTE_5_KOS,GL_SHARED_TEXTURE_PALETTE_6_KOS,GL_SHARED_TEXTURE_PALETTE_7_KOS,GL_SHARED_TEXTURE_PALETTE_8_KOS,GL_SHARED_TEXTURE_PALETTE_9_KOS, + GL_SHARED_TEXTURE_PALETTE_10_KOS,GL_SHARED_TEXTURE_PALETTE_11_KOS,GL_SHARED_TEXTURE_PALETTE_12_KOS,GL_SHARED_TEXTURE_PALETTE_13_KOS,GL_SHARED_TEXTURE_PALETTE_14_KOS,GL_SHARED_TEXTURE_PALETTE_15_KOS,GL_SHARED_TEXTURE_PALETTE_16_KOS,GL_SHARED_TEXTURE_PALETTE_17_KOS,GL_SHARED_TEXTURE_PALETTE_18_KOS,GL_SHARED_TEXTURE_PALETTE_19_KOS, + GL_SHARED_TEXTURE_PALETTE_20_KOS,GL_SHARED_TEXTURE_PALETTE_21_KOS,GL_SHARED_TEXTURE_PALETTE_22_KOS,GL_SHARED_TEXTURE_PALETTE_23_KOS,GL_SHARED_TEXTURE_PALETTE_24_KOS,GL_SHARED_TEXTURE_PALETTE_25_KOS,GL_SHARED_TEXTURE_PALETTE_26_KOS,GL_SHARED_TEXTURE_PALETTE_27_KOS,GL_SHARED_TEXTURE_PALETTE_28_KOS,GL_SHARED_TEXTURE_PALETTE_29_KOS, + GL_SHARED_TEXTURE_PALETTE_30_KOS,GL_SHARED_TEXTURE_PALETTE_31_KOS,GL_SHARED_TEXTURE_PALETTE_32_KOS,GL_SHARED_TEXTURE_PALETTE_33_KOS,GL_SHARED_TEXTURE_PALETTE_34_KOS,GL_SHARED_TEXTURE_PALETTE_35_KOS,GL_SHARED_TEXTURE_PALETTE_36_KOS,GL_SHARED_TEXTURE_PALETTE_37_KOS,GL_SHARED_TEXTURE_PALETTE_38_KOS,GL_SHARED_TEXTURE_PALETTE_39_KOS, + GL_SHARED_TEXTURE_PALETTE_40_KOS,GL_SHARED_TEXTURE_PALETTE_41_KOS,GL_SHARED_TEXTURE_PALETTE_42_KOS,GL_SHARED_TEXTURE_PALETTE_43_KOS,GL_SHARED_TEXTURE_PALETTE_44_KOS,GL_SHARED_TEXTURE_PALETTE_45_KOS,GL_SHARED_TEXTURE_PALETTE_46_KOS,GL_SHARED_TEXTURE_PALETTE_47_KOS,GL_SHARED_TEXTURE_PALETTE_48_KOS,GL_SHARED_TEXTURE_PALETTE_49_KOS, + GL_SHARED_TEXTURE_PALETTE_50_KOS,GL_SHARED_TEXTURE_PALETTE_51_KOS,GL_SHARED_TEXTURE_PALETTE_52_KOS,GL_SHARED_TEXTURE_PALETTE_53_KOS,GL_SHARED_TEXTURE_PALETTE_54_KOS,GL_SHARED_TEXTURE_PALETTE_55_KOS,GL_SHARED_TEXTURE_PALETTE_56_KOS,GL_SHARED_TEXTURE_PALETTE_57_KOS,GL_SHARED_TEXTURE_PALETTE_58_KOS,GL_SHARED_TEXTURE_PALETTE_59_KOS, + GL_SHARED_TEXTURE_PALETTE_60_KOS,GL_SHARED_TEXTURE_PALETTE_61_KOS,GL_SHARED_TEXTURE_PALETTE_62_KOS,GL_SHARED_TEXTURE_PALETTE_63_KOS, + 0}; - GLint validInternalFormats[] = {GL_RGB8, GL_RGBA8, 0}; - GLint validFormats[] = {GL_RGB, GL_RGBA, 0}; + //GLint validInternalFormats[] = {GL_RGB8, GL_RGBA8,GL_RGBA4, 0}; + GLint validFormats[] = {GL_RGB, GL_RGBA,GL_RGB5_A1,GL_RGB5_A1,GL_RGB565_KOS,GL_RGBA4, 0}; GLint validTypes[] = {GL_UNSIGNED_BYTE, GL_BYTE, GL_UNSIGNED_SHORT, GL_SHORT, 0}; if(_glCheckValidEnum(target, validTargets, __func__) != 0) { return; } + /*ozzy:let's consider internalFormat param as a hint coz it's already set into gldcConfig if(_glCheckValidEnum(internalFormat, validInternalFormats, __func__) != 0) { return; } + */ + + + switch(format){ + case GL_PALETTE4_RGBA8_OES: + case GL_PALETTE8_RGBA8_OES: + format = GL_RGBA; + break; + case GL_PALETTE4_RGB8_OES: + case GL_PALETTE8_RGB8_OES: + format = GL_RGB; + break; + case GL_PALETTE4_R5_G6_B5_OES: + case GL_PALETTE8_R5_G6_B5_OES: + case GL_UNSIGNED_SHORT_5_6_5: + format = GL_RGB565_KOS; + break; + case GL_PALETTE4_RGB5_A1_OES: + case GL_PALETTE8_RGB5_A1_OES: + case GL_UNSIGNED_SHORT_5_5_5_1: + format = GL_RGB5_A1; + break; + case GL_PALETTE4_RGBA4_OES: + case GL_PALETTE8_RGBA4_OES: + case GL_UNSIGNED_SHORT_4_4_4_4: + format = GL_RGBA4; + break; + + } + if(_glCheckValidEnum(format, validFormats, __func__) != 0) { return; @@ -1535,7 +1711,7 @@ GLAPI void APIENTRY glColorTableEXT(GLenum target, GLenum internalFormat, GLsize assert(sourceStride > -1); TextureConversionFunc convert = _determineConversion( - GL_RGBA8, /* We always store palettes in this format */ + INTERNAL_PALETTE_FORMAT, //was previously forced to GL_RGBA8 format, type ); @@ -1547,21 +1723,25 @@ GLAPI void APIENTRY glColorTableEXT(GLenum target, GLenum internalFormat, GLsize TexturePalette* palette = NULL; + GLboolean sharedPaletteUsed = GL_FALSE; + /* Custom extension - allow uploading to one of 4 custom palettes */ if(target == GL_SHARED_TEXTURE_PALETTE_EXT || target == GL_SHARED_TEXTURE_PALETTE_0_KOS) { palette = SHARED_PALETTES[0]; - } else if(target == GL_SHARED_TEXTURE_PALETTE_1_KOS) { - palette = SHARED_PALETTES[1]; - } else if(target == GL_SHARED_TEXTURE_PALETTE_2_KOS) { - palette = SHARED_PALETTES[2]; - } else if(target == GL_SHARED_TEXTURE_PALETTE_3_KOS) { - palette = SHARED_PALETTES[3]; - } else { + sharedPaletteUsed = GL_TRUE; + } + + for (GLbyte i = 1; i < 64; ++i) { + if (target == GL_SHARED_TEXTURE_PALETTE_0_KOS + i) { + palette = SHARED_PALETTES[i]; + sharedPaletteUsed = GL_TRUE; + } + } + if (sharedPaletteUsed == GL_FALSE){ TextureObject* active = _glGetBoundTexture(); if(!active->palette) { active->palette = _initTexturePalette(); } - palette = active->palette; } @@ -1578,7 +1758,7 @@ GLAPI void APIENTRY glColorTableEXT(GLenum target, GLenum internalFormat, GLsize } palette->data = (GLubyte*) malloc(width * 4); - palette->format = format; //Ozzy:was previously forcing to GL_RGBA for testing. + palette->format = format; palette->width = width; palette->size = (width > 16) ? 256 : 16; assert(palette->size == 16 || palette->size == 256); diff --git a/include/GL/glext.h b/include/GL/glext.h index 210d86b..688288f 100644 --- a/include/GL/glext.h +++ b/include/GL/glext.h @@ -155,6 +155,20 @@ GLAPI GLboolean APIENTRY glIsFramebufferEXT(GLuint framebuffer); #define GL_SHARED_TEXTURE_PALETTE_EXT 0x81FB +/* ext OES_compressed_paletted_texture */ + +/* PixelInternalFormat */ +#define GL_PALETTE4_RGB8_OES 0x8B90 +#define GL_PALETTE4_RGBA8_OES 0x8B91 +#define GL_PALETTE4_R5_G6_B5_OES 0x8B92 +#define GL_PALETTE4_RGBA4_OES 0x8B93 +#define GL_PALETTE4_RGB5_A1_OES 0x8B94 +#define GL_PALETTE8_RGB8_OES 0x8B95 +#define GL_PALETTE8_RGBA8_OES 0x8B96 +#define GL_PALETTE8_R5_G6_B5_OES 0x8B97 +#define GL_PALETTE8_RGBA4_OES 0x8B98 +#define GL_PALETTE8_RGB5_A1_OES 0x8B99 + GLAPI void APIENTRY glColorTableEXT(GLenum target, GLenum internalFormat, GLsizei width, GLenum format, GLenum type, const GLvoid *data); GLAPI void APIENTRY glColorSubTableEXT(GLenum target, GLsizei start, GLsizei count, GLenum format, GLenum type, const GLvoid *data); GLAPI void APIENTRY glGetColorTableEXT(GLenum target, GLenum format, GLenum type, GLvoid *data); diff --git a/include/GL/glkos.h b/include/GL/glkos.h index a46277e..e2ef11a 100644 --- a/include/GL/glkos.h +++ b/include/GL/glkos.h @@ -88,6 +88,7 @@ GLAPI void APIENTRY glKosInitConfig(GLdcConfig* config); GLAPI void APIENTRY glKosInitEx(GLdcConfig* config); GLAPI void APIENTRY glKosSwapBuffers(); + /* * CUSTOM EXTENSION multiple_shared_palette_KOS * @@ -107,20 +108,90 @@ GLAPI void APIENTRY glKosSwapBuffers(); * by default textures use shared palette 0. */ + #define GL_SHARED_TEXTURE_PALETTE_0_KOS 0xEEFC #define GL_SHARED_TEXTURE_PALETTE_1_KOS 0xEEFD #define GL_SHARED_TEXTURE_PALETTE_2_KOS 0xEEFE #define GL_SHARED_TEXTURE_PALETTE_3_KOS 0xEEFF +#define GL_SHARED_TEXTURE_PALETTE_4_KOS 0xEF00 +#define GL_SHARED_TEXTURE_PALETTE_5_KOS 0xEF01 +#define GL_SHARED_TEXTURE_PALETTE_6_KOS 0xEF02 +#define GL_SHARED_TEXTURE_PALETTE_7_KOS 0xEF03 +#define GL_SHARED_TEXTURE_PALETTE_8_KOS 0xEF04 +#define GL_SHARED_TEXTURE_PALETTE_9_KOS 0xEF05 + +#define GL_SHARED_TEXTURE_PALETTE_10_KOS 0xEF06 +#define GL_SHARED_TEXTURE_PALETTE_11_KOS 0xEF07 +#define GL_SHARED_TEXTURE_PALETTE_12_KOS 0xEF08 +#define GL_SHARED_TEXTURE_PALETTE_13_KOS 0xEF09 +#define GL_SHARED_TEXTURE_PALETTE_14_KOS 0xEF0A +#define GL_SHARED_TEXTURE_PALETTE_15_KOS 0xEF0B +#define GL_SHARED_TEXTURE_PALETTE_16_KOS 0xEF0C +#define GL_SHARED_TEXTURE_PALETTE_17_KOS 0xEF0D +#define GL_SHARED_TEXTURE_PALETTE_18_KOS 0xEF0E +#define GL_SHARED_TEXTURE_PALETTE_19_KOS 0xEF0F + +#define GL_SHARED_TEXTURE_PALETTE_20_KOS 0xEF10 +#define GL_SHARED_TEXTURE_PALETTE_21_KOS 0xEF11 +#define GL_SHARED_TEXTURE_PALETTE_22_KOS 0xEF12 +#define GL_SHARED_TEXTURE_PALETTE_23_KOS 0xEF13 +#define GL_SHARED_TEXTURE_PALETTE_24_KOS 0xEF14 +#define GL_SHARED_TEXTURE_PALETTE_25_KOS 0xEF15 +#define GL_SHARED_TEXTURE_PALETTE_26_KOS 0xEF16 +#define GL_SHARED_TEXTURE_PALETTE_27_KOS 0xEF17 +#define GL_SHARED_TEXTURE_PALETTE_28_KOS 0xEF18 +#define GL_SHARED_TEXTURE_PALETTE_29_KOS 0xEF19 + +#define GL_SHARED_TEXTURE_PALETTE_30_KOS 0xEF1A +#define GL_SHARED_TEXTURE_PALETTE_31_KOS 0xEF1B +#define GL_SHARED_TEXTURE_PALETTE_32_KOS 0xEF1C +#define GL_SHARED_TEXTURE_PALETTE_33_KOS 0xEF1D +#define GL_SHARED_TEXTURE_PALETTE_34_KOS 0xEF1E +#define GL_SHARED_TEXTURE_PALETTE_35_KOS 0xEF1F +#define GL_SHARED_TEXTURE_PALETTE_36_KOS 0xEF20 +#define GL_SHARED_TEXTURE_PALETTE_37_KOS 0xEF21 +#define GL_SHARED_TEXTURE_PALETTE_38_KOS 0xEF22 +#define GL_SHARED_TEXTURE_PALETTE_39_KOS 0xEF23 + +#define GL_SHARED_TEXTURE_PALETTE_40_KOS 0xEF24 +#define GL_SHARED_TEXTURE_PALETTE_41_KOS 0xEF25 +#define GL_SHARED_TEXTURE_PALETTE_42_KOS 0xEF26 +#define GL_SHARED_TEXTURE_PALETTE_43_KOS 0xEF27 +#define GL_SHARED_TEXTURE_PALETTE_44_KOS 0xEF28 +#define GL_SHARED_TEXTURE_PALETTE_45_KOS 0xEF29 +#define GL_SHARED_TEXTURE_PALETTE_46_KOS 0xEF2A +#define GL_SHARED_TEXTURE_PALETTE_47_KOS 0xEF2B +#define GL_SHARED_TEXTURE_PALETTE_48_KOS 0xEF2C +#define GL_SHARED_TEXTURE_PALETTE_49_KOS 0xEF2D + +#define GL_SHARED_TEXTURE_PALETTE_50_KOS 0xEF2E +#define GL_SHARED_TEXTURE_PALETTE_51_KOS 0xEF2F +#define GL_SHARED_TEXTURE_PALETTE_52_KOS 0xEF30 +#define GL_SHARED_TEXTURE_PALETTE_53_KOS 0xEF31 +#define GL_SHARED_TEXTURE_PALETTE_54_KOS 0xEF32 +#define GL_SHARED_TEXTURE_PALETTE_55_KOS 0xEF33 +#define GL_SHARED_TEXTURE_PALETTE_56_KOS 0xEF34 +#define GL_SHARED_TEXTURE_PALETTE_57_KOS 0xEF35 +#define GL_SHARED_TEXTURE_PALETTE_58_KOS 0xEF36 +#define GL_SHARED_TEXTURE_PALETTE_59_KOS 0xEF37 + +#define GL_SHARED_TEXTURE_PALETTE_60_KOS 0xEF38 +#define GL_SHARED_TEXTURE_PALETTE_61_KOS 0xEF39 +#define GL_SHARED_TEXTURE_PALETTE_62_KOS 0xEF3A +#define GL_SHARED_TEXTURE_PALETTE_63_KOS 0xEF3B /* Pass to glTexParameteri to set the shared bank */ -#define GL_SHARED_TEXTURE_BANK_KOS 0xEF00 +#define GL_SHARED_TEXTURE_BANK_KOS 0xEF3C /* Memory allocation extension (GL_KOS_texture_memory_management) */ GLAPI GLvoid APIENTRY glDefragmentTextureMemory_KOS(void); -#define GL_FREE_TEXTURE_MEMORY_KOS 0xEF01 -#define GL_USED_TEXTURE_MEMORY_KOS 0xEF02 -#define GL_FREE_CONTIGUOUS_TEXTURE_MEMORY_KOS 0xEF03 +#define GL_FREE_TEXTURE_MEMORY_KOS 0xEF3D +#define GL_USED_TEXTURE_MEMORY_KOS 0xEF3E +#define GL_FREE_CONTIGUOUS_TEXTURE_MEMORY_KOS 0xEF3F + +//for palette internal format (glfcConfig) +#define GL_RGB565_KOS 0xEF40 __END_DECLS From c48bbfe07c247d935b608a8d73960916754ea32f Mon Sep 17 00:00:00 2001 From: OzzyOuzo Date: Tue, 3 May 2022 16:12:27 +0300 Subject: [PATCH 5/5] Fixed glMultMatrixf(...) Added _glResetSharedPalettes --- GL/immediate.c | 2 +- GL/matrix.c | 14 +++++++--- GL/texture.c | 64 +++++++++++++++++++++++++++++++++++++++++++--- include/GL/glext.h | 12 +++++---- 4 files changed, 80 insertions(+), 12 deletions(-) diff --git a/GL/immediate.c b/GL/immediate.c index f4f1ba7..ae42c94 100644 --- a/GL/immediate.c +++ b/GL/immediate.c @@ -166,7 +166,7 @@ void APIENTRY glVertex3f(GLfloat x, GLfloat y, GLfloat z) { vert->x = x; vert->y = y; - vert->z = z; + vert->z = z; vert->u = UV_COORD[0]; vert->v = UV_COORD[1]; vert->s = ST_COORD[0]; diff --git a/GL/matrix.c b/GL/matrix.c index e01298d..74768d0 100644 --- a/GL/matrix.c +++ b/GL/matrix.c @@ -288,11 +288,19 @@ void APIENTRY glFrustum(GLfloat left, GLfloat right, /* Multiply the current matrix by an arbitrary matrix */ void glMultMatrixf(const GLfloat *m) { - Matrix4x4 TEMP; - MEMCPY4(TEMP, m, sizeof(Matrix4x4)); + Matrix4x4 TEMP __attribute__((aligned(32))); + const Matrix4x4 *pMatrix; + + if (((GLint)m)&0xf){ /* Unaligned matrix */ + pMatrix = &TEMP; + MEMCPY4(TEMP, m, sizeof(Matrix4x4)); + } + else{ + pMatrix = (const Matrix4x4*) m; + } UploadMatrix4x4(stack_top(MATRIX_STACKS + MATRIX_IDX)); - MultiplyMatrix4x4((const Matrix4x4*) &TEMP); + MultiplyMatrix4x4(pMatrix); DownloadMatrix4x4(stack_top(MATRIX_STACKS + MATRIX_IDX)); if(MATRIX_MODE == GL_MODELVIEW) { diff --git a/GL/texture.c b/GL/texture.c index b8f9e05..4953987 100644 --- a/GL/texture.c +++ b/GL/texture.c @@ -89,6 +89,38 @@ static GLshort _glGenPaletteSlot(GLushort size) { fprintf(stderr, "GL ERROR: No palette slots remain\n"); return -1; } +#if 0 +static GLshort _glGenSharedPaletteSlot(GLushort size) { + GLushort i, j; + TextureObject* active = _glGetBoundTexture(); + + assert(size == 16 || size == 256); + + if(size == 16) { + + i = active->shared_bank / MAX_GLDC_4BPP_PALETTE_SLOTS; + BANKS_USED[i] = GL_TRUE; + j = i * MAX_GLDC_4BPP_PALETTE_SLOTS; + j = active->shared_bank - j; + SUBBANKS_USED[i][j] = GL_TRUE; + return (i * MAX_GLDC_4BPP_PALETTE_SLOTS) + j; + } + else { + for(i = 0; i < MAX_GLDC_PALETTE_SLOTS; ++i) { + if(!BANKS_USED[i]) { + BANKS_USED[i] = GL_TRUE; + for(j = 0; j < MAX_GLDC_4BPP_PALETTE_SLOTS; ++j) { + SUBBANKS_USED[i][j] = GL_TRUE; + } + return i; + } + } + } + + fprintf(stderr, "GL ERROR: No palette slots remain\n"); + return -1; +} +#endif /* ozzy: used for statistics */ GLushort _glFreePaletteSlots(GLushort size) @@ -447,6 +479,21 @@ static GLuint _glGetMipmapDataSize(TextureObject* obj) { return imageSize + offset; } + +void _glResetSharedPalettes() +{ + uint32_t i; + + for (i=0; i < MAX_GLDC_SHARED_PALETTES;i++){ + + MEMSET4(SHARED_PALETTES[i], 0x0, sizeof(TexturePalette)); + SHARED_PALETTES[i]->bank = -1; + } + + memset((void*) BANKS_USED, 0x0, sizeof(BANKS_USED)); + memset((void*) SUBBANKS_USED, 0x0, sizeof(SUBBANKS_USED)); + +} GLubyte _glInitTextures() { uint32_t i; @@ -460,8 +507,10 @@ GLubyte _glInitTextures() { SHARED_PALETTES[i] = _initTexturePalette(); } - memset((void*) BANKS_USED, 0x0, sizeof(BANKS_USED)); - memset((void*) SUBBANKS_USED, 0x0, sizeof(SUBBANKS_USED)); + _glResetSharedPalettes(); + + //memset((void*) BANKS_USED, 0x0, sizeof(BANKS_USED)); + //memset((void*) SUBBANKS_USED, 0x0, sizeof(SUBBANKS_USED)); size_t vram_free = GPUMemoryAvailable(); YALLOC_SIZE = vram_free - PVR_MEM_BUFFER_SIZE; /* Take all but 64kb VRAM */ @@ -1731,7 +1780,7 @@ GLAPI void APIENTRY glColorTableEXT(GLenum target, GLenum internalFormat, GLsize sharedPaletteUsed = GL_TRUE; } - for (GLbyte i = 1; i < 64; ++i) { + for (GLbyte i = 1; i < MAX_GLDC_SHARED_PALETTES; ++i) { if (target == GL_SHARED_TEXTURE_PALETTE_0_KOS + i) { palette = SHARED_PALETTES[i]; sharedPaletteUsed = GL_TRUE; @@ -1763,7 +1812,16 @@ GLAPI void APIENTRY glColorTableEXT(GLenum target, GLenum internalFormat, GLsize palette->size = (width > 16) ? 256 : 16; assert(palette->size == 16 || palette->size == 256); + #if 0 + if (sharedPaletteUsed == GL_FALSE){ + palette->bank = _glGenPaletteSlot(palette->size); + } + else{ + palette->bank = _glGenSharedPaletteSlot(palette->size); + } + #else palette->bank = _glGenPaletteSlot(palette->size); + #endif if(palette->bank < 0) { /* We ran out of slots! */ diff --git a/include/GL/glext.h b/include/GL/glext.h index 688288f..dacaea6 100644 --- a/include/GL/glext.h +++ b/include/GL/glext.h @@ -155,9 +155,16 @@ GLAPI GLboolean APIENTRY glIsFramebufferEXT(GLuint framebuffer); #define GL_SHARED_TEXTURE_PALETTE_EXT 0x81FB +GLAPI void APIENTRY glColorTableEXT(GLenum target, GLenum internalFormat, GLsizei width, GLenum format, GLenum type, const GLvoid *data); +GLAPI void APIENTRY glColorSubTableEXT(GLenum target, GLsizei start, GLsizei count, GLenum format, GLenum type, const GLvoid *data); +GLAPI void APIENTRY glGetColorTableEXT(GLenum target, GLenum format, GLenum type, GLvoid *data); +GLAPI void APIENTRY glGetColorTableParameterivEXT(GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetColorTableParameterfvEXT(GLenum target, GLenum pname, GLfloat *params); + /* ext OES_compressed_paletted_texture */ /* PixelInternalFormat */ +//Ozzy: used MesaGL definitions please adjust if it causes probs. #define GL_PALETTE4_RGB8_OES 0x8B90 #define GL_PALETTE4_RGBA8_OES 0x8B91 #define GL_PALETTE4_R5_G6_B5_OES 0x8B92 @@ -169,11 +176,6 @@ GLAPI GLboolean APIENTRY glIsFramebufferEXT(GLuint framebuffer); #define GL_PALETTE8_RGBA4_OES 0x8B98 #define GL_PALETTE8_RGB5_A1_OES 0x8B99 -GLAPI void APIENTRY glColorTableEXT(GLenum target, GLenum internalFormat, GLsizei width, GLenum format, GLenum type, const GLvoid *data); -GLAPI void APIENTRY glColorSubTableEXT(GLenum target, GLsizei start, GLsizei count, GLenum format, GLenum type, const GLvoid *data); -GLAPI void APIENTRY glGetColorTableEXT(GLenum target, GLenum format, GLenum type, GLvoid *data); -GLAPI void APIENTRY glGetColorTableParameterivEXT(GLenum target, GLenum pname, GLint *params); -GLAPI void APIENTRY glGetColorTableParameterfvEXT(GLenum target, GLenum pname, GLfloat *params); /* Loads VQ compressed texture from SH4 RAM into PVR VRAM */ /* internalformat must be one of the following constants: