2018-05-11 14:39:28 +00:00
|
|
|
#include "private.h"
|
2018-08-07 19:22:44 +00:00
|
|
|
|
2018-05-11 14:39:28 +00:00
|
|
|
#include <stdio.h>
|
|
|
|
|
2018-08-07 19:22:44 +00:00
|
|
|
#include "../include/glext.h"
|
|
|
|
#include "../include/glkos.h"
|
|
|
|
|
2018-05-11 14:39:28 +00:00
|
|
|
#define CLAMP_U (1<<1)
|
|
|
|
#define CLAMP_V (1<<0)
|
|
|
|
|
2018-08-08 08:50:57 +00:00
|
|
|
#define MAX(x, y) ((x > y) ? x : y)
|
|
|
|
|
2018-05-11 14:39:28 +00:00
|
|
|
static TextureObject* TEXTURE_UNITS[MAX_TEXTURE_UNITS] = {NULL, NULL};
|
|
|
|
static NamedArray TEXTURE_OBJECTS;
|
|
|
|
static GLubyte ACTIVE_TEXTURE = 0;
|
|
|
|
|
|
|
|
static GLuint _determinePVRFormat(GLint internalFormat, GLenum type);
|
|
|
|
|
2018-08-08 15:50:09 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
GLubyte* _glGetMipmapLocation(TextureObject* obj, GLuint level) {
|
|
|
|
GLuint offset = 0;
|
|
|
|
|
|
|
|
GLuint i = 0;
|
|
|
|
GLuint width = obj->width;
|
|
|
|
GLuint height = obj->height;
|
|
|
|
|
|
|
|
for(; i < level; ++i) {
|
|
|
|
offset += (width * height * obj->dataStride);
|
|
|
|
|
|
|
|
if(width > 1) {
|
|
|
|
width /= 2;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(height > 1) {
|
|
|
|
height /= 2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return ((GLubyte*) obj->data) + offset;
|
|
|
|
}
|
|
|
|
|
|
|
|
GLuint _glGetMipmapLevelCount(TextureObject* obj) {
|
|
|
|
return 1 + floor(log2(MAX(obj->width, obj->height)));
|
|
|
|
}
|
|
|
|
|
|
|
|
static GLuint _glGetMipmapDataSize(TextureObject* obj) {
|
|
|
|
GLuint size = 0;
|
|
|
|
|
|
|
|
GLuint i = 0;
|
|
|
|
GLuint width = obj->width;
|
|
|
|
GLuint height = obj->height;
|
|
|
|
|
|
|
|
for(; i < _glGetMipmapLevelCount(obj); ++i) {
|
|
|
|
size += (width * height * obj->dataStride);
|
|
|
|
|
|
|
|
if(width > 1) {
|
|
|
|
width /= 2;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(height > 1) {
|
|
|
|
height /= 2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return size;
|
|
|
|
}
|
|
|
|
|
2018-05-11 14:39:28 +00:00
|
|
|
GLubyte _glKosInitTextures() {
|
|
|
|
named_array_init(&TEXTURE_OBJECTS, sizeof(TextureObject), 256);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
TextureObject* getTexture0() {
|
|
|
|
return TEXTURE_UNITS[0];
|
|
|
|
}
|
|
|
|
|
|
|
|
TextureObject* getTexture1() {
|
|
|
|
return TEXTURE_UNITS[1];
|
|
|
|
}
|
|
|
|
|
|
|
|
TextureObject* getBoundTexture() {
|
|
|
|
return TEXTURE_UNITS[ACTIVE_TEXTURE];
|
|
|
|
}
|
|
|
|
|
|
|
|
GLubyte check_valid_enum(GLint param, GLenum* values, const char* func) {
|
|
|
|
GLubyte found = 0;
|
|
|
|
while(*values != 0) {
|
|
|
|
if(*values == param) {
|
|
|
|
found++;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
values++;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(!found) {
|
|
|
|
_glKosThrowError(GL_INVALID_ENUM, func);
|
|
|
|
_glKosPrintError();
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void APIENTRY glActiveTextureARB(GLenum texture) {
|
|
|
|
TRACE();
|
|
|
|
|
|
|
|
if(texture < GL_TEXTURE0_ARB || texture > GL_TEXTURE0_ARB + MAX_TEXTURE_UNITS)
|
|
|
|
_glKosThrowError(GL_INVALID_ENUM, "glActiveTextureARB");
|
|
|
|
|
|
|
|
if(_glKosHasError()) {
|
|
|
|
_glKosPrintError();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
ACTIVE_TEXTURE = texture & 0xF;
|
|
|
|
}
|
|
|
|
|
2018-05-20 20:28:48 +00:00
|
|
|
GLboolean APIENTRY glIsTexture(GLuint texture) {
|
|
|
|
return (named_array_used(&TEXTURE_OBJECTS, texture)) ? GL_TRUE : GL_FALSE;
|
|
|
|
}
|
|
|
|
|
2018-05-11 14:39:28 +00:00
|
|
|
void APIENTRY glGenTextures(GLsizei n, GLuint *textures) {
|
|
|
|
TRACE();
|
|
|
|
|
|
|
|
while(n--) {
|
|
|
|
GLuint id = 0;
|
|
|
|
TextureObject* txr = (TextureObject*) named_array_alloc(&TEXTURE_OBJECTS, &id);
|
|
|
|
txr->index = id;
|
|
|
|
txr->width = txr->height = 0;
|
2018-08-08 08:50:57 +00:00
|
|
|
txr->mipmap = 0;
|
2018-05-11 14:39:28 +00:00
|
|
|
txr->uv_clamp = 0;
|
|
|
|
txr->env = PVR_TXRENV_MODULATEALPHA;
|
|
|
|
txr->filter = PVR_FILTER_NONE;
|
|
|
|
txr->data = NULL;
|
2018-08-08 08:50:57 +00:00
|
|
|
txr->mipmapCount = 0;
|
2018-05-11 14:39:28 +00:00
|
|
|
|
|
|
|
*textures = id;
|
|
|
|
|
|
|
|
textures++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void APIENTRY glDeleteTextures(GLsizei n, GLuint *textures) {
|
|
|
|
TRACE();
|
|
|
|
|
|
|
|
while(n--) {
|
|
|
|
TextureObject* txr = (TextureObject*) named_array_get(&TEXTURE_OBJECTS, *textures);
|
|
|
|
|
2018-05-20 20:28:48 +00:00
|
|
|
/* Make sure we update framebuffer objects that have this texture attached */
|
|
|
|
wipeTextureOnFramebuffers(*textures);
|
|
|
|
|
2018-05-11 14:39:28 +00:00
|
|
|
if(txr == TEXTURE_UNITS[ACTIVE_TEXTURE]) {
|
|
|
|
TEXTURE_UNITS[ACTIVE_TEXTURE] = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(txr->data) {
|
|
|
|
pvr_mem_free(txr->data);
|
|
|
|
}
|
|
|
|
|
|
|
|
named_array_release(&TEXTURE_OBJECTS, *textures++);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void APIENTRY glBindTexture(GLenum target, GLuint texture) {
|
|
|
|
TRACE();
|
|
|
|
|
|
|
|
GLenum target_values [] = {GL_TEXTURE_2D, 0};
|
|
|
|
|
|
|
|
if(check_valid_enum(target, target_values, __func__) != 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(texture) {
|
|
|
|
TEXTURE_UNITS[ACTIVE_TEXTURE] = (TextureObject*) named_array_get(&TEXTURE_OBJECTS, texture);
|
|
|
|
} else {
|
|
|
|
TEXTURE_UNITS[ACTIVE_TEXTURE] = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void APIENTRY glTexEnvi(GLenum target, GLenum pname, GLint param) {
|
|
|
|
TRACE();
|
|
|
|
|
|
|
|
GLenum target_values [] = {GL_TEXTURE_ENV, 0};
|
|
|
|
GLenum pname_values [] = {GL_TEXTURE_ENV_MODE, 0};
|
|
|
|
GLenum param_values [] = {GL_MODULATE, GL_DECAL, GL_REPLACE, 0};
|
|
|
|
|
|
|
|
GLubyte failures = 0;
|
|
|
|
|
|
|
|
failures += check_valid_enum(target, target_values, __func__);
|
|
|
|
failures += check_valid_enum(pname, pname_values, __func__);
|
|
|
|
failures += check_valid_enum(param, param_values, __func__);
|
|
|
|
|
|
|
|
TextureObject* active = TEXTURE_UNITS[ACTIVE_TEXTURE];
|
|
|
|
|
|
|
|
if(!active) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(failures) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch(param) {
|
|
|
|
case GL_MODULATE:
|
|
|
|
active->env = PVR_TXRENV_MODULATEALPHA;
|
|
|
|
break;
|
|
|
|
case GL_DECAL:
|
|
|
|
active->env = PVR_TXRENV_DECAL;
|
|
|
|
break;
|
|
|
|
case GL_REPLACE:
|
|
|
|
active->env = PVR_TXRENV_REPLACE;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void APIENTRY glTexEnvf(GLenum target, GLenum pname, GLfloat param) {
|
|
|
|
glTexEnvi(target, pname, param);
|
|
|
|
}
|
|
|
|
|
2018-08-07 19:49:10 +00:00
|
|
|
void APIENTRY glCompressedTexImage2DARB(GLenum target,
|
2018-05-11 14:39:28 +00:00
|
|
|
GLint level,
|
2018-08-07 19:22:44 +00:00
|
|
|
GLenum internalFormat,
|
2018-05-11 14:39:28 +00:00
|
|
|
GLsizei width,
|
|
|
|
GLsizei height,
|
|
|
|
GLint border,
|
|
|
|
GLsizei imageSize,
|
|
|
|
const GLvoid *data) {
|
|
|
|
TRACE();
|
|
|
|
|
|
|
|
if(target != GL_TEXTURE_2D)
|
2018-08-07 19:49:10 +00:00
|
|
|
_glKosThrowError(GL_INVALID_ENUM, __func__);
|
2018-05-11 14:39:28 +00:00
|
|
|
|
2018-08-08 08:50:57 +00:00
|
|
|
if(level || border) {
|
|
|
|
/* We don't support setting mipmap levels manually with compressed textures
|
|
|
|
maybe one day */
|
2018-08-07 19:49:10 +00:00
|
|
|
_glKosThrowError(GL_INVALID_VALUE, __func__);
|
2018-08-08 08:50:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
GLboolean mipmapped = GL_FALSE;
|
2018-05-11 14:39:28 +00:00
|
|
|
|
2018-08-07 19:22:44 +00:00
|
|
|
switch(internalFormat) {
|
|
|
|
case GL_COMPRESSED_ARGB_1555_VQ_KOS:
|
|
|
|
case GL_COMPRESSED_ARGB_1555_VQ_TWID_KOS:
|
|
|
|
case GL_COMPRESSED_ARGB_4444_VQ_KOS:
|
|
|
|
case GL_COMPRESSED_ARGB_4444_VQ_TWID_KOS:
|
|
|
|
case GL_COMPRESSED_RGB_565_VQ_KOS:
|
|
|
|
case GL_COMPRESSED_RGB_565_VQ_TWID_KOS:
|
|
|
|
break;
|
2018-08-08 08:50:57 +00:00
|
|
|
case GL_COMPRESSED_ARGB_1555_VQ_MIPMAP_KOS:
|
|
|
|
case GL_COMPRESSED_ARGB_1555_VQ_MIPMAP_TWID_KOS:
|
|
|
|
case GL_COMPRESSED_ARGB_4444_VQ_MIPMAP_KOS:
|
|
|
|
case GL_COMPRESSED_ARGB_4444_VQ_MIPMAP_TWID_KOS:
|
|
|
|
case GL_COMPRESSED_RGB_565_VQ_MIPMAP_KOS:
|
|
|
|
case GL_COMPRESSED_RGB_565_VQ_MIPMAP_TWID_KOS:
|
|
|
|
mipmapped = GL_TRUE;
|
|
|
|
break;
|
2018-08-07 19:22:44 +00:00
|
|
|
default:
|
2018-08-07 19:49:10 +00:00
|
|
|
_glKosThrowError(GL_INVALID_OPERATION, __func__);
|
2018-08-07 19:22:44 +00:00
|
|
|
}
|
2018-05-11 14:39:28 +00:00
|
|
|
|
2018-08-07 19:22:44 +00:00
|
|
|
if(TEXTURE_UNITS[ACTIVE_TEXTURE] == NULL) {
|
2018-08-07 19:49:10 +00:00
|
|
|
_glKosThrowError(GL_INVALID_OPERATION, __func__);
|
2018-08-07 19:22:44 +00:00
|
|
|
}
|
2018-05-11 14:39:28 +00:00
|
|
|
|
2018-08-08 07:45:25 +00:00
|
|
|
/* Guess whether we have mipmaps or not */
|
|
|
|
|
|
|
|
/* `expected` is the uncompressed size */
|
|
|
|
GLuint expected = sizeof(GLshort) * width * height;
|
|
|
|
|
|
|
|
/* The ratio is the uncompressed vs compressed data size */
|
|
|
|
GLuint ratio = (GLuint) (((GLfloat) expected) / ((GLfloat) imageSize));
|
|
|
|
|
2018-08-08 08:50:57 +00:00
|
|
|
if(ratio < 7 && !mipmapped) {
|
2018-08-08 07:45:25 +00:00
|
|
|
/* If the ratio is less than 1:7 then we assume that the reason for that
|
|
|
|
is the extra data used for mipmaps. Testing shows that a single VQ compressed
|
|
|
|
image is around 1:7 or 1:8. We may need to tweak this if it detects false positives */
|
|
|
|
fprintf(stderr, "GL ERROR: Detected multiple mipmap levels being uploaded to %s\n", __func__);
|
|
|
|
_glKosThrowError(GL_INVALID_OPERATION, __func__);
|
|
|
|
}
|
|
|
|
|
2018-05-11 14:39:28 +00:00
|
|
|
if(_glKosHasError()) {
|
|
|
|
_glKosPrintError();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
TextureObject* active = TEXTURE_UNITS[ACTIVE_TEXTURE];
|
|
|
|
|
2018-08-08 08:50:57 +00:00
|
|
|
/* Set the required mipmap count */
|
2018-08-08 15:50:09 +00:00
|
|
|
active->mipmapCount = _glGetMipmapLevelCount(active);
|
2018-08-08 08:50:57 +00:00
|
|
|
active->mipmap = (mipmapped) ? ~0 : (1 << level); /* Set only a single bit if this wasn't mipmapped otherwise set all */
|
2018-05-11 14:39:28 +00:00
|
|
|
active->width = width;
|
|
|
|
active->height = height;
|
|
|
|
active->color = _determinePVRFormat(
|
2018-08-07 19:22:44 +00:00
|
|
|
internalFormat,
|
|
|
|
internalFormat /* Doesn't matter (see determinePVRFormat) */
|
2018-05-11 14:39:28 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
/* Odds are slim new data is same size as old, so free always */
|
|
|
|
if(active->data)
|
|
|
|
pvr_mem_free(active->data);
|
|
|
|
|
|
|
|
active->data = pvr_mem_malloc(imageSize);
|
|
|
|
|
|
|
|
if(data)
|
|
|
|
sq_cpy(active->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;
|
2018-08-08 08:50:57 +00:00
|
|
|
case GL_UNSIGNED_SHORT_4_4_4_4_REV_TWID_KOS:
|
|
|
|
return PVR_TXRFMT_ARGB4444 | PVR_TXRFMT_TWIDDLED;
|
|
|
|
case GL_UNSIGNED_SHORT_1_5_5_5_REV_TWID_KOS:
|
|
|
|
return PVR_TXRFMT_ARGB1555 | PVR_TXRFMT_TWIDDLED;
|
2018-08-07 19:22:44 +00:00
|
|
|
case GL_COMPRESSED_RGB_565_VQ_KOS:
|
2018-08-08 08:50:57 +00:00
|
|
|
case GL_COMPRESSED_RGB_565_VQ_MIPMAP_KOS:
|
2018-05-11 14:39:28 +00:00
|
|
|
return PVR_TXRFMT_RGB565 | PVR_TXRFMT_NONTWIDDLED | PVR_TXRFMT_VQ_ENABLE;
|
2018-08-07 19:22:44 +00:00
|
|
|
case GL_COMPRESSED_RGB_565_VQ_TWID_KOS:
|
2018-08-08 08:50:57 +00:00
|
|
|
case GL_COMPRESSED_RGB_565_VQ_MIPMAP_TWID_KOS:
|
2018-05-11 14:39:28 +00:00
|
|
|
return PVR_TXRFMT_RGB565 | PVR_TXRFMT_TWIDDLED | PVR_TXRFMT_VQ_ENABLE;
|
2018-08-07 19:22:44 +00:00
|
|
|
case GL_COMPRESSED_ARGB_4444_VQ_TWID_KOS:
|
2018-08-08 08:50:57 +00:00
|
|
|
case GL_COMPRESSED_ARGB_4444_VQ_MIPMAP_TWID_KOS:
|
2018-05-11 14:39:28 +00:00
|
|
|
return PVR_TXRFMT_ARGB4444 | PVR_TXRFMT_TWIDDLED | PVR_TXRFMT_VQ_ENABLE;
|
2018-08-07 19:22:44 +00:00
|
|
|
case GL_COMPRESSED_ARGB_4444_VQ_KOS:
|
2018-08-08 08:50:57 +00:00
|
|
|
case GL_COMPRESSED_ARGB_4444_VQ_MIPMAP_KOS:
|
2018-05-11 14:39:28 +00:00
|
|
|
return PVR_TXRFMT_ARGB4444 | PVR_TXRFMT_NONTWIDDLED | PVR_TXRFMT_VQ_ENABLE;
|
2018-08-07 19:22:44 +00:00
|
|
|
case GL_COMPRESSED_ARGB_1555_VQ_KOS:
|
2018-08-08 08:50:57 +00:00
|
|
|
case GL_COMPRESSED_ARGB_1555_VQ_MIPMAP_KOS:
|
2018-05-11 14:39:28 +00:00
|
|
|
return PVR_TXRFMT_ARGB1555 | PVR_TXRFMT_NONTWIDDLED | PVR_TXRFMT_VQ_ENABLE;
|
2018-08-07 19:22:44 +00:00
|
|
|
case GL_COMPRESSED_ARGB_1555_VQ_TWID_KOS:
|
2018-08-08 08:50:57 +00:00
|
|
|
case GL_COMPRESSED_ARGB_1555_VQ_MIPMAP_TWID_KOS:
|
2018-05-11 14:39:28 +00:00
|
|
|
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:
|
|
|
|
fprintf(stderr, "Unsupported conversion: %d -> %d, %d", internalFormat, format, type);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-08 08:50:57 +00:00
|
|
|
GLboolean _glIsMipmapComplete(TextureObject* obj) {
|
|
|
|
if(!obj->mipmap || !obj->mipmapCount) {
|
|
|
|
return GL_FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
GLsizei i = 0;
|
|
|
|
for(; i < obj->mipmapCount; ++i) {
|
|
|
|
if((obj->mipmap & (1 << i)) == 0) {
|
|
|
|
return GL_FALSE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return GL_TRUE;
|
|
|
|
}
|
2018-05-11 14:39:28 +00:00
|
|
|
|
|
|
|
void APIENTRY glTexImage2D(GLenum target, GLint level, GLint internalFormat,
|
|
|
|
GLsizei width, GLsizei height, GLint border,
|
|
|
|
GLenum format, GLenum type, const GLvoid *data) {
|
|
|
|
|
|
|
|
TRACE();
|
|
|
|
|
|
|
|
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");
|
|
|
|
}
|
|
|
|
|
2018-05-20 15:16:53 +00:00
|
|
|
/* FIXME: Mipmaps! */
|
|
|
|
if(level < 0 || level > 0) {
|
2018-05-11 14:39:28 +00:00
|
|
|
_glKosThrowError(GL_INVALID_VALUE, "glTexImage2D");
|
|
|
|
}
|
|
|
|
|
|
|
|
if(border) {
|
|
|
|
_glKosThrowError(GL_INVALID_VALUE, "glTexImage2D");
|
|
|
|
}
|
|
|
|
|
|
|
|
if(!TEXTURE_UNITS[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);
|
|
|
|
|
|
|
|
TextureObject* active = TEXTURE_UNITS[ACTIVE_TEXTURE];
|
|
|
|
|
|
|
|
if(active->data) {
|
|
|
|
/* pre-existing texture - check if changed */
|
|
|
|
if(active->width != width ||
|
|
|
|
active->height != height ||
|
|
|
|
active->color != pvr_format) {
|
|
|
|
/* changed - free old texture memory */
|
|
|
|
pvr_mem_free(active->data);
|
|
|
|
active->data = NULL;
|
2018-08-08 08:50:57 +00:00
|
|
|
active->mipmap = 0;
|
2018-08-08 15:50:09 +00:00
|
|
|
active->mipmapCount = 0;
|
|
|
|
active->dataStride = 0;
|
2018-05-11 14:39:28 +00:00
|
|
|
}
|
2018-08-07 19:22:44 +00:00
|
|
|
}
|
2018-05-11 14:39:28 +00:00
|
|
|
|
2018-05-20 15:16:53 +00:00
|
|
|
GLuint bytes = (width * height * sizeof(GLushort));
|
2018-05-11 14:39:28 +00:00
|
|
|
|
|
|
|
if(!active->data) {
|
|
|
|
/* need texture memory */
|
|
|
|
active->width = width;
|
|
|
|
active->height = height;
|
|
|
|
active->color = pvr_format;
|
2018-08-09 07:56:43 +00:00
|
|
|
/* Set the required mipmap count */
|
|
|
|
active->mipmapCount = _glGetMipmapLevelCount(active);
|
2018-08-08 15:50:09 +00:00
|
|
|
active->data = pvr_mem_malloc(_glGetMipmapDataSize(active));
|
2018-08-09 07:56:43 +00:00
|
|
|
active->dataStride = sizeof(GLshort);
|
2018-05-11 14:39:28 +00:00
|
|
|
}
|
|
|
|
|
2018-08-09 07:56:43 +00:00
|
|
|
/* Mark this level as set in the mipmap bitmask */
|
|
|
|
active->mipmap |= (1 << level);
|
|
|
|
|
2018-05-11 14:39:28 +00:00
|
|
|
/* 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;
|
|
|
|
}
|
|
|
|
|
2018-08-08 15:50:09 +00:00
|
|
|
GLubyte* targetData = _glGetMipmapLocation(active, level);
|
|
|
|
|
2018-05-11 14:39:28 +00:00
|
|
|
if(!data) {
|
|
|
|
/* No data? Do nothing! */
|
|
|
|
return;
|
|
|
|
} else if(!needsConversion) {
|
|
|
|
/* No conversion? Just copy the data, and the pvr_format is correct */
|
2018-08-08 15:50:09 +00:00
|
|
|
sq_cpy(targetData, data, bytes);
|
2018-05-11 14:39:28 +00:00
|
|
|
return;
|
|
|
|
} else {
|
|
|
|
TextureConversionFunc convert = _determineConversion(
|
|
|
|
internalFormat,
|
|
|
|
format,
|
|
|
|
type
|
|
|
|
);
|
|
|
|
|
|
|
|
if(!convert) {
|
2018-08-08 15:50:09 +00:00
|
|
|
_glKosThrowError(GL_INVALID_OPERATION, __func__);
|
2018-05-11 14:39:28 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-08-08 15:50:09 +00:00
|
|
|
GLushort* dest = (GLushort*) targetData;
|
2018-05-11 14:39:28 +00:00
|
|
|
const GLubyte* source = data;
|
|
|
|
|
2018-08-09 07:56:43 +00:00
|
|
|
GLint stride = _determineStride(format, type);
|
|
|
|
|
|
|
|
if(stride == -1) {
|
2018-08-08 15:50:09 +00:00
|
|
|
_glKosThrowError(GL_INVALID_OPERATION, __func__);
|
2018-05-11 14:39:28 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Perform the conversion */
|
2018-05-14 16:10:53 +00:00
|
|
|
GLuint i;
|
|
|
|
for(i = 0; i < bytes; i += 2) {
|
2018-05-11 14:39:28 +00:00
|
|
|
convert(source, dest);
|
|
|
|
|
|
|
|
dest++;
|
2018-08-09 07:56:43 +00:00
|
|
|
source += stride;
|
2018-05-11 14:39:28 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void APIENTRY glTexParameteri(GLenum target, GLenum pname, GLint param) {
|
|
|
|
TRACE();
|
|
|
|
|
|
|
|
TextureObject* active = getBoundTexture();
|
|
|
|
|
2018-05-28 06:16:40 +00:00
|
|
|
if(!active) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-05-11 14:39:28 +00:00
|
|
|
if(target == GL_TEXTURE_2D) {
|
|
|
|
switch(pname) {
|
|
|
|
case GL_TEXTURE_MAG_FILTER:
|
|
|
|
case GL_TEXTURE_MIN_FILTER:
|
|
|
|
switch(param) {
|
|
|
|
case GL_LINEAR:
|
|
|
|
active->filter = PVR_FILTER_BILINEAR;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case GL_NEAREST:
|
|
|
|
active->filter = PVR_FILTER_NEAREST;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case GL_FILTER_NONE:
|
|
|
|
active->filter = PVR_FILTER_NONE;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case GL_FILTER_BILINEAR:
|
|
|
|
active->filter = PVR_FILTER_BILINEAR;
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case GL_TEXTURE_WRAP_S:
|
|
|
|
switch(param) {
|
|
|
|
case GL_CLAMP:
|
|
|
|
active->uv_clamp |= CLAMP_U;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case GL_REPEAT:
|
|
|
|
active->uv_clamp &= ~CLAMP_U;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case GL_TEXTURE_WRAP_T:
|
|
|
|
switch(param) {
|
|
|
|
case GL_CLAMP:
|
|
|
|
active->uv_clamp |= CLAMP_V;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case GL_REPEAT:
|
|
|
|
active->uv_clamp &= ~CLAMP_V;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|