/* KallistiGL for KallistiOS ##version## libgl/gl-texture.c Copyright (C) 2014 Josh Pearson Copyright (C) 2016 Joe Fenton Open GL Texture Submission implementation. */ #include #include #include #include "gl.h" #include "glext.h" #include "gl-api.h" #include "gl-rgb.h" #include "containers/named_array.h" //========================================================================================// //== Internal KOS Open GL Texture Unit Structures / Global Variables ==// #define GL_KOS_MAX_TEXTURE_UNITS 2 #define GL_KOS_CLAMP_U (1<<1) #define GL_KOS_CLAMP_V (1<<0) static GL_TEXTURE_OBJECT *GL_KOS_TEXTURE_UNIT[GL_KOS_MAX_TEXTURE_UNITS] = { NULL, NULL }; static NamedArray TEXTURE_OBJECTS; static GLubyte GL_KOS_ACTIVE_TEXTURE = GL_TEXTURE0_ARB & 0xF; //========================================================================================// static GLuint _determinePVRFormat(GLint internalFormat, GLenum type); GLubyte _glKosInitTextures() { named_array_init(&TEXTURE_OBJECTS, sizeof(GL_TEXTURE_OBJECT), 256); return 1; } static void _glKosBindTexture(GLuint index) { GL_KOS_TEXTURE_UNIT[GL_KOS_ACTIVE_TEXTURE] = (GL_TEXTURE_OBJECT*) named_array_get(&TEXTURE_OBJECTS, index); } static void _glKosUnbindTexture() { GL_KOS_TEXTURE_UNIT[GL_KOS_ACTIVE_TEXTURE] = NULL; } GLuint _glKosTextureWidth(GLuint index) { GL_TEXTURE_OBJECT *tex = (GL_TEXTURE_OBJECT*) named_array_get(&TEXTURE_OBJECTS, index); return tex->width; } GLuint _glKosTextureHeight(GLuint index) { GL_TEXTURE_OBJECT *tex = (GL_TEXTURE_OBJECT*) named_array_get(&TEXTURE_OBJECTS, index); return tex->height; } GLvoid *_glKosTextureData(GLuint index) { GL_TEXTURE_OBJECT *tex = (GL_TEXTURE_OBJECT*) named_array_get(&TEXTURE_OBJECTS, index); return tex->data; } void _glKosCompileHdrTx() { return GL_KOS_TEXTURE_UNIT[GL_TEXTURE0_ARB & 0xF] ? _glKosCompileHdrT(GL_KOS_TEXTURE_UNIT[GL_TEXTURE0_ARB & 0xF]) : _glKosCompileHdr(); } GL_TEXTURE_OBJECT *_glKosBoundMultiTexObject() { return GL_KOS_TEXTURE_UNIT[GL_TEXTURE1_ARB & 0xF]; } GLuint _glKosBoundMultiTexID() { return GL_KOS_TEXTURE_UNIT[GL_TEXTURE1_ARB & 0xF] ? GL_KOS_TEXTURE_UNIT[GL_TEXTURE1_ARB & 0xF]->index : 0; } GLuint _glKosBoundTexID() { return GL_KOS_TEXTURE_UNIT[GL_TEXTURE0_ARB & 0xF] ? GL_KOS_TEXTURE_UNIT[GL_TEXTURE0_ARB & 0xF]->index : 0; } GLuint _glKosActiveTextureBoundTexID() { return (GL_KOS_ACTIVE_TEXTURE) ? _glKosBoundMultiTexID() : _glKosBoundTexID(); } GLubyte _glKosMaxTextureUnits() { return GL_KOS_MAX_TEXTURE_UNITS; } //========================================================================================// //== Public KOS Open GL API Texture Unit Functionality ==// void APIENTRY glGenTextures(GLsizei n, GLuint *textures) { while(n--) { GLuint id = 0; GL_TEXTURE_OBJECT* txr = (GL_TEXTURE_OBJECT*) named_array_alloc(&TEXTURE_OBJECTS, &id); txr->index = id; txr->width = txr->height = 0; txr->mip_map = 0; txr->uv_clamp = 0; txr->env = PVR_TXRENV_MODULATEALPHA; txr->filter = PVR_FILTER_NONE; txr->data = NULL; *textures = id; textures++; } } void APIENTRY glDeleteTextures(GLsizei n, GLuint *textures) { while(n--) { GL_TEXTURE_OBJECT* txr = (GL_TEXTURE_OBJECT*) named_array_get(&TEXTURE_OBJECTS, *textures); if(txr == GL_KOS_TEXTURE_UNIT[GL_KOS_ACTIVE_TEXTURE]) { GL_KOS_TEXTURE_UNIT[GL_KOS_ACTIVE_TEXTURE] = NULL; } if(txr->data) { pvr_mem_free(txr->data); } named_array_release(&TEXTURE_OBJECTS, *textures++); } } void APIENTRY glBindTexture(GLenum target, GLuint texture) { if(target != GL_TEXTURE_2D) { _glKosThrowError(GL_INVALID_ENUM, "glBindTexture"); _glKosPrintError(); return; } texture ? _glKosBindTexture(texture) : _glKosUnbindTexture(); } void APIENTRY glCompressedTexImage2D(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const GLvoid *data) { if(target != GL_TEXTURE_2D) _glKosThrowError(GL_INVALID_ENUM, "glCompressedTexImage2D"); if(level < 0) _glKosThrowError(GL_INVALID_VALUE, "glCompressedTexImage2D"); if(border) _glKosThrowError(GL_INVALID_VALUE, "glCompressedTexImage2D"); if(internalformat != GL_UNSIGNED_SHORT_5_6_5_VQ_KOS) if(internalformat != GL_UNSIGNED_SHORT_5_6_5_VQ_TWID_KOS) if(internalformat != GL_UNSIGNED_SHORT_4_4_4_4_VQ_KOS) if(internalformat != GL_UNSIGNED_SHORT_4_4_4_4_REV_VQ_TWID_KOS) if(internalformat != GL_UNSIGNED_SHORT_1_5_5_5_REV_VQ_KOS) if(internalformat != GL_UNSIGNED_SHORT_1_5_5_5_REV_VQ_TWID_KOS) _glKosThrowError(GL_INVALID_OPERATION, "glCompressedTexImage2D"); if(GL_KOS_TEXTURE_UNIT[GL_KOS_ACTIVE_TEXTURE] == NULL) _glKosThrowError(GL_INVALID_OPERATION, "glCompressedTexImage2D"); if(_glKosHasError()) { _glKosPrintError(); return; } GL_KOS_TEXTURE_UNIT[GL_KOS_ACTIVE_TEXTURE]->width = width; GL_KOS_TEXTURE_UNIT[GL_KOS_ACTIVE_TEXTURE]->height = height; GL_KOS_TEXTURE_UNIT[GL_KOS_ACTIVE_TEXTURE]->mip_map = level; GL_KOS_TEXTURE_UNIT[GL_KOS_ACTIVE_TEXTURE]->color = _determinePVRFormat( internalformat, internalformat /* Doesn't matter (see determinePVRFormat) */ ); /* Odds are slim new data is same size as old, so free always */ if(GL_KOS_TEXTURE_UNIT[GL_KOS_ACTIVE_TEXTURE]->data) pvr_mem_free(GL_KOS_TEXTURE_UNIT[GL_KOS_ACTIVE_TEXTURE]->data); GL_KOS_TEXTURE_UNIT[GL_KOS_ACTIVE_TEXTURE]->data = pvr_mem_malloc(imageSize); if(data) sq_cpy(GL_KOS_TEXTURE_UNIT[GL_KOS_ACTIVE_TEXTURE]->data, data, imageSize); } static GLint _cleanInternalFormat(GLint internalFormat) { switch (internalFormat) { case GL_ALPHA: /* case GL_ALPHA4: case GL_ALPHA8: case GL_ALPHA12: case GL_ALPHA16:*/ return GL_ALPHA; case 1: case GL_LUMINANCE: /* case GL_LUMINANCE4: case GL_LUMINANCE8: case GL_LUMINANCE12: case GL_LUMINANCE16:*/ return GL_LUMINANCE; case 2: case GL_LUMINANCE_ALPHA: /* case GL_LUMINANCE4_ALPHA4: case GL_LUMINANCE6_ALPHA2: case GL_LUMINANCE8_ALPHA8: case GL_LUMINANCE12_ALPHA4: case GL_LUMINANCE12_ALPHA12: case GL_LUMINANCE16_ALPHA16: */ return GL_LUMINANCE_ALPHA; /* case GL_INTENSITY: case GL_INTENSITY4: case GL_INTENSITY8: case GL_INTENSITY12: case GL_INTENSITY16: return GL_INTENSITY; */ case 3: return GL_RGB; case GL_RGB: /* case GL_R3_G3_B2: case GL_RGB4: case GL_RGB5: case GL_RGB8: case GL_RGB10: case GL_RGB12: case GL_RGB16: */ return GL_RGB; case 4: return GL_RGBA; case GL_RGBA: /* case GL_RGBA2: case GL_RGBA4: case GL_RGB5_A1: case GL_RGBA8: case GL_RGB10_A2: case GL_RGBA12: case GL_RGBA16: */ return GL_RGBA; /* Support ARB_texture_rg */ case GL_RED: /* case GL_R8: case GL_R16: case GL_RED: case GL_COMPRESSED_RED: */ return GL_RED; /* case GL_RG: case GL_RG8: case GL_RG16: case GL_COMPRESSED_RG: return GL_RG;*/ default: return -1; } } static GLuint _determinePVRFormat(GLint internalFormat, GLenum type) { /* Given a cleaned internalFormat, return the Dreamcast format * that can hold it */ switch(internalFormat) { case GL_ALPHA: case GL_LUMINANCE: case GL_LUMINANCE_ALPHA: case GL_RGBA: /* OK so if we have something that requires alpha, we return 4444 unless * the type was already 1555 (1-bit alpha) in which case we return that */ return (type == GL_UNSIGNED_SHORT_1_5_5_5_REV) ? PVR_TXRFMT_ARGB1555 | PVR_TXRFMT_NONTWIDDLED : PVR_TXRFMT_ARGB4444 | PVR_TXRFMT_NONTWIDDLED; case GL_RED: case GL_RGB: /* No alpha? Return RGB565 which is the best we can do without using palettes */ return PVR_TXRFMT_RGB565 | PVR_TXRFMT_NONTWIDDLED; /* Compressed and twiddled versions */ case GL_UNSIGNED_SHORT_5_6_5_TWID_KOS: return PVR_TXRFMT_RGB565 | PVR_TXRFMT_TWIDDLED; case GL_UNSIGNED_SHORT_5_6_5_VQ_KOS: return PVR_TXRFMT_RGB565 | PVR_TXRFMT_NONTWIDDLED | PVR_TXRFMT_VQ_ENABLE; case GL_UNSIGNED_SHORT_5_6_5_VQ_TWID_KOS: return PVR_TXRFMT_RGB565 | PVR_TXRFMT_TWIDDLED | PVR_TXRFMT_VQ_ENABLE; case GL_UNSIGNED_SHORT_4_4_4_4_REV_TWID_KOS: return PVR_TXRFMT_ARGB4444 | PVR_TXRFMT_TWIDDLED; case GL_UNSIGNED_SHORT_4_4_4_4_REV_VQ_TWID_KOS: return PVR_TXRFMT_ARGB4444 | PVR_TXRFMT_TWIDDLED | PVR_TXRFMT_VQ_ENABLE; case GL_UNSIGNED_SHORT_4_4_4_4_VQ_KOS: return PVR_TXRFMT_ARGB4444 | PVR_TXRFMT_NONTWIDDLED | PVR_TXRFMT_VQ_ENABLE; case GL_UNSIGNED_SHORT_1_5_5_5_REV_TWID_KOS: return PVR_TXRFMT_ARGB1555 | PVR_TXRFMT_TWIDDLED; case GL_UNSIGNED_SHORT_1_5_5_5_REV_VQ_KOS: return PVR_TXRFMT_ARGB1555 | PVR_TXRFMT_NONTWIDDLED | PVR_TXRFMT_VQ_ENABLE; case GL_UNSIGNED_SHORT_1_5_5_5_REV_VQ_TWID_KOS: return PVR_TXRFMT_ARGB1555 | PVR_TXRFMT_TWIDDLED | PVR_TXRFMT_VQ_ENABLE; default: return 0; } } typedef void (*TextureConversionFunc)(const GLubyte*, GLushort*); static void _rgba8888_to_argb4444(const GLubyte* source, GLushort* dest) { *dest = (source[3] & 0xF0) << 8 | (source[0] & 0xF0) << 4 | (source[1] & 0xF0) | (source[2] & 0xF0) >> 4; } static void _rgb888_to_rgb565(const GLubyte* source, GLushort* dest) { *dest = ((source[0] & 0b11111000) << 8) | ((source[1] & 0b11111100) << 3) | (source[2] >> 3); } static void _rgba8888_to_a000(const GLubyte* source, GLushort* dest) { *dest = ((source[3] & 0b11111000) << 8); } static void _r8_to_rgb565(const GLubyte* source, GLushort* dest) { *dest = (source[0] & 0b11111000) << 8; } static void _rgba4444_to_argb4444(const GLubyte* source, GLushort* dest) { GLushort* src = (GLushort*) source; *dest = ((*src & 0x000F) << 12) | *src >> 4; } static TextureConversionFunc _determineConversion(GLint internalFormat, GLenum format, GLenum type) { switch(internalFormat) { case GL_ALPHA: { if(type == GL_UNSIGNED_BYTE && format == GL_RGBA) { return _rgba8888_to_a000; } else if(type == GL_BYTE && format == GL_RGBA) { return _rgba8888_to_a000; } } break; case GL_RGB: { if(type == GL_UNSIGNED_BYTE && format == GL_RGB) { return _rgb888_to_rgb565; } else if(type == GL_BYTE && format == GL_RGB) { return _rgb888_to_rgb565; } else if(type == GL_UNSIGNED_BYTE && format == GL_RED) { return _r8_to_rgb565; } } break; case GL_RGBA: { if(type == GL_UNSIGNED_BYTE && format == GL_RGBA) { return _rgba8888_to_argb4444; } else if (type == GL_BYTE && format == GL_RGBA) { return _rgba8888_to_argb4444; } else if(type == GL_UNSIGNED_SHORT_4_4_4_4 && format == GL_RGBA) { return _rgba4444_to_argb4444; } } break; default: break; } return 0; } static GLint _determineStride(GLenum format, GLenum type) { switch(type) { case GL_BYTE: case GL_UNSIGNED_BYTE: return (format == GL_RED) ? 1 : (format == GL_RGB) ? 3 : 4; case GL_UNSIGNED_SHORT: return (format == GL_RED) ? 2 : (format == GL_RGB) ? 6 : 8; case GL_UNSIGNED_SHORT_5_6_5_REV: case GL_UNSIGNED_SHORT_5_6_5_TWID_KOS: case GL_UNSIGNED_SHORT_5_5_5_1: case GL_UNSIGNED_SHORT_1_5_5_5_REV_TWID_KOS: case GL_UNSIGNED_SHORT_1_5_5_5_REV: case GL_UNSIGNED_SHORT_4_4_4_4: case GL_UNSIGNED_SHORT_4_4_4_4_REV_TWID_KOS: case GL_UNSIGNED_SHORT_4_4_4_4_REV: return 2; } return -1; } static GLboolean _isSupportedFormat(GLenum format) { switch(format) { case GL_RED: case GL_RGB: case GL_RGBA: case GL_BGRA: return GL_TRUE; default: return GL_FALSE; } } void APIENTRY glTexImage2D(GLenum target, GLint level, GLint internalFormat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *data) { if(target != GL_TEXTURE_2D) { _glKosThrowError(GL_INVALID_ENUM, "glTexImage2D"); } if(!_isSupportedFormat(format)) { _glKosThrowError(GL_INVALID_ENUM, "glTexImage2D"); } /* Abuse determineStride to see if type is valid */ if(_determineStride(GL_RGBA, type) == -1) { _glKosThrowError(GL_INVALID_ENUM, "glTexImage2D"); } internalFormat = _cleanInternalFormat(internalFormat); if(internalFormat == -1) { _glKosThrowError(GL_INVALID_VALUE, "glTexImage2D"); } GLint w = width; if(w == 0 || (w & -w) != w) { /* Width is not a power of two. Must be!*/ _glKosThrowError(GL_INVALID_VALUE, "glTexImage2D"); } GLint h = height; if(h == 0 || (h & -h) != h) { /* height is not a power of two. Must be!*/ _glKosThrowError(GL_INVALID_VALUE, "glTexImage2D"); } if(level < 0) { _glKosThrowError(GL_INVALID_VALUE, "glTexImage2D"); } if(border) { _glKosThrowError(GL_INVALID_VALUE, "glTexImage2D"); } if(!GL_KOS_TEXTURE_UNIT[GL_KOS_ACTIVE_TEXTURE]) { _glKosThrowError(GL_INVALID_OPERATION, "glTexImage2D"); } if(_glKosHasError()) { _glKosPrintError(); return; } /* Calculate the format that we need to convert the data to */ GLuint pvr_format = _determinePVRFormat(internalFormat, type); if(GL_KOS_TEXTURE_UNIT[GL_KOS_ACTIVE_TEXTURE]->data) { /* pre-existing texture - check if changed */ if(GL_KOS_TEXTURE_UNIT[GL_KOS_ACTIVE_TEXTURE]->width != width || GL_KOS_TEXTURE_UNIT[GL_KOS_ACTIVE_TEXTURE]->height != height || GL_KOS_TEXTURE_UNIT[GL_KOS_ACTIVE_TEXTURE]->mip_map != level || GL_KOS_TEXTURE_UNIT[GL_KOS_ACTIVE_TEXTURE]->color != pvr_format) { /* changed - free old texture memory */ pvr_mem_free(GL_KOS_TEXTURE_UNIT[GL_KOS_ACTIVE_TEXTURE]->data); GL_KOS_TEXTURE_UNIT[GL_KOS_ACTIVE_TEXTURE]->data = NULL; } } GLuint bytes = level ? glKosMipMapTexSize(width, height) : (width * height * 2); if(!GL_KOS_TEXTURE_UNIT[GL_KOS_ACTIVE_TEXTURE]->data) { /* need texture memory */ GL_KOS_TEXTURE_UNIT[GL_KOS_ACTIVE_TEXTURE]->width = width; GL_KOS_TEXTURE_UNIT[GL_KOS_ACTIVE_TEXTURE]->height = height; GL_KOS_TEXTURE_UNIT[GL_KOS_ACTIVE_TEXTURE]->mip_map = level; GL_KOS_TEXTURE_UNIT[GL_KOS_ACTIVE_TEXTURE]->color = pvr_format; GL_KOS_TEXTURE_UNIT[GL_KOS_ACTIVE_TEXTURE]->data = pvr_mem_malloc(bytes); } /* Let's assume we need to convert */ GLboolean needsConversion = GL_TRUE; /* * These are the only formats where the source format passed in matches the pvr format. * Note the REV formats + GL_BGRA will reverse to ARGB which is what the PVR supports */ if(format == GL_BGRA && type == GL_UNSIGNED_SHORT_4_4_4_4_REV && internalFormat == GL_RGBA) { needsConversion = GL_FALSE; } else if(format == GL_BGRA && type == GL_UNSIGNED_SHORT_1_5_5_5_REV && internalFormat == GL_RGBA) { needsConversion = GL_FALSE; } else if(format == GL_RGB && type == GL_UNSIGNED_SHORT_5_6_5 && internalFormat == GL_RGB) { needsConversion = GL_FALSE; } else if(format == GL_RGB && type == GL_UNSIGNED_SHORT_5_6_5_TWID_KOS && internalFormat == GL_RGB) { needsConversion = GL_FALSE; } else if(format == GL_BGRA && type == GL_UNSIGNED_SHORT_1_5_5_5_REV_TWID_KOS && internalFormat == GL_RGBA) { needsConversion = GL_FALSE; } else if(format == GL_BGRA && type == GL_UNSIGNED_SHORT_4_4_4_4_REV_TWID_KOS && internalFormat == GL_RGBA) { needsConversion = GL_FALSE; } if(!data) { /* No data? Do nothing! */ return; } else if(!needsConversion) { /* No conversion? Just copy the data, and the pvr_format is correct */ sq_cpy(GL_KOS_TEXTURE_UNIT[GL_KOS_ACTIVE_TEXTURE]->data, data, bytes); return; } else { TextureConversionFunc convert = _determineConversion( internalFormat, format, type ); if(!convert) { _glKosThrowError(GL_INVALID_OPERATION, "glTexImage2D"); return; } GLushort* dest = GL_KOS_TEXTURE_UNIT[GL_KOS_ACTIVE_TEXTURE]->data; const GLubyte* source = data; GLint stride = _determineStride(format, type); if(stride == -1) { _glKosThrowError(GL_INVALID_OPERATION, "glTexImage2D"); return; } /* Perform the conversion */ for(GLuint i = 0; i < bytes; i += 2) { convert(source, dest); dest++; source += stride; } } } void APIENTRY glTexParameteri(GLenum target, GLenum pname, GLint param) { if(target == GL_TEXTURE_2D) { switch(pname) { case GL_TEXTURE_MAG_FILTER: case GL_TEXTURE_MIN_FILTER: switch(param) { case GL_LINEAR: GL_KOS_TEXTURE_UNIT[GL_KOS_ACTIVE_TEXTURE]->filter = PVR_FILTER_BILINEAR; break; case GL_NEAREST: GL_KOS_TEXTURE_UNIT[GL_KOS_ACTIVE_TEXTURE]->filter = PVR_FILTER_NEAREST; break; case GL_FILTER_NONE: GL_KOS_TEXTURE_UNIT[GL_KOS_ACTIVE_TEXTURE]->filter = PVR_FILTER_NONE; break; case GL_FILTER_BILINEAR: GL_KOS_TEXTURE_UNIT[GL_KOS_ACTIVE_TEXTURE]->filter = PVR_FILTER_BILINEAR; break; default: break; } break; case GL_TEXTURE_WRAP_S: switch(param) { case GL_CLAMP: GL_KOS_TEXTURE_UNIT[GL_KOS_ACTIVE_TEXTURE]->uv_clamp |= GL_KOS_CLAMP_U; break; case GL_REPEAT: GL_KOS_TEXTURE_UNIT[GL_KOS_ACTIVE_TEXTURE]->uv_clamp &= ~GL_KOS_CLAMP_U; break; } break; case GL_TEXTURE_WRAP_T: switch(param) { case GL_CLAMP: GL_KOS_TEXTURE_UNIT[GL_KOS_ACTIVE_TEXTURE]->uv_clamp |= GL_KOS_CLAMP_V; break; case GL_REPEAT: GL_KOS_TEXTURE_UNIT[GL_KOS_ACTIVE_TEXTURE]->uv_clamp &= ~GL_KOS_CLAMP_V; break; } break; } } } void APIENTRY glTexEnvi(GLenum target, GLenum pname, GLint param) { if(target != GL_TEXTURE_ENV) _glKosThrowError(GL_INVALID_ENUM, "glTexEnvi"); if(pname != GL_TEXTURE_ENV_MODE) _glKosThrowError(GL_INVALID_ENUM, "glTexEnvi"); if(_glKosHasError()) { _glKosPrintError(); return; } if(param >= PVR_TXRENV_REPLACE && param <= PVR_TXRENV_MODULATEALPHA) GL_KOS_TEXTURE_UNIT[GL_KOS_ACTIVE_TEXTURE]->env = param; } void APIENTRY glTexEnvf(GLenum target, GLenum pname, GLfloat param) { glTexEnvi(target, pname, param); } void APIENTRY glActiveTextureARB(GLenum texture) { if(texture < GL_TEXTURE0_ARB || texture > GL_TEXTURE0_ARB + GL_KOS_MAX_TEXTURE_UNITS) _glKosThrowError(GL_INVALID_ENUM, "glActiveTextureARB"); if(_glKosHasError()) { _glKosPrintError(); return; } GL_KOS_ACTIVE_TEXTURE = texture & 0xF; }