Drastically refactor glTexImage2D

This commit is contained in:
Luke Benstead 2023-08-26 20:34:11 +01:00
parent a05e1b01fa
commit 77531ca347
15 changed files with 9410 additions and 406 deletions

View File

@ -175,6 +175,8 @@ function(gen_sample sample)
endif() endif()
endfunction() endfunction()
add_subdirectory(tests)
gen_sample(blend_test samples/blend_test/main.c) gen_sample(blend_test samples/blend_test/main.c)
gen_sample(depth_funcs samples/depth_funcs/main.c) gen_sample(depth_funcs samples/depth_funcs/main.c)
gen_sample(depth_funcs_alpha_testing samples/depth_funcs_alpha_testing/main.c samples/depth_funcs_alpha_testing/gl_png.c) gen_sample(depth_funcs_alpha_testing samples/depth_funcs_alpha_testing/main.c samples/depth_funcs_alpha_testing/gl_png.c)
@ -206,9 +208,10 @@ gen_sample(zclip_trianglestrip samples/zclip_trianglestrip/main.c)
gen_sample(scissor samples/scissor/main.c) gen_sample(scissor samples/scissor/main.c)
gen_sample(polymark samples/polymark/main.c) gen_sample(polymark samples/polymark/main.c)
gen_sample(cubes samples/cubes/main.cpp) gen_sample(cubes samples/cubes/main.cpp)
gen_sample(zclip_test tests/zclip/main.cpp) gen_sample(zclip_test tests/zclip/main.cpp)
gen_sample(prof_texture_upload samples/prof_texture_upload/main.c)
if(PLATFORM_DREAMCAST) if(PLATFORM_DREAMCAST)
gen_sample(trimark samples/trimark/main.c) gen_sample(trimark samples/trimark/main.c)
gen_sample(quadmark samples/quadmark/main.c samples/profiler.c) gen_sample(quadmark samples/quadmark/main.c samples/profiler.c)

View File

@ -49,7 +49,15 @@ void APIENTRY glKosInitConfig(GLdcConfig* config) {
config->internal_palette_format = GL_RGBA8; config->internal_palette_format = GL_RGBA8;
} }
static bool _initialized = false;
void APIENTRY glKosInitEx(GLdcConfig* config) { void APIENTRY glKosInitEx(GLdcConfig* config) {
if(_initialized) {
return;
}
_initialized = true;
TRACE(); TRACE();
printf("\nWelcome to GLdc! Git revision: %s\n\n", GLDC_VERSION); printf("\nWelcome to GLdc! Git revision: %s\n\n", GLDC_VERSION);
@ -83,6 +91,12 @@ void APIENTRY glKosInitEx(GLdcConfig* config) {
aligned_vector_reserve(&TR_LIST.vector, config->initial_tr_capacity); aligned_vector_reserve(&TR_LIST.vector, config->initial_tr_capacity);
} }
void APIENTRY glKosShutdown() {
aligned_vector_clear(&OP_LIST.vector);
aligned_vector_clear(&PT_LIST.vector);
aligned_vector_clear(&TR_LIST.vector);
}
void APIENTRY glKosInit() { void APIENTRY glKosInit() {
GLdcConfig config; GLdcConfig config;
glKosInitConfig(&config); glKosInitConfig(&config);
@ -117,4 +131,4 @@ void APIENTRY glKosSwapBuffers() {
aligned_vector_clear(&TR_LIST.vector); aligned_vector_clear(&TR_LIST.vector);
_glApplyScissor(true); _glApplyScissor(true);
} }

View File

@ -33,6 +33,10 @@ static VideoMode vid_mode = {
AlignedVector vbuffer; AlignedVector vbuffer;
void InitGPU(_Bool autosort, _Bool fsaa) { void InitGPU(_Bool autosort, _Bool fsaa) {
// 32-bit SDL has trouble with the wayland driver for some reason
setenv("SDL_VIDEODRIVER", "x11", 1);
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS); SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS);
WINDOW = SDL_CreateWindow( WINDOW = SDL_CreateWindow(

View File

@ -164,7 +164,9 @@ typedef struct {
GLboolean isCompressed; GLboolean isCompressed;
GLboolean isPaletted; GLboolean isPaletted;
//50 //50
GLubyte padding[14]; // Pad to 64-bytes GLenum internalFormat;
//54
GLubyte padding[10]; // Pad to 64-bytes
} __attribute__((aligned(32))) TextureObject; } __attribute__((aligned(32))) TextureObject;
typedef struct { typedef struct {
@ -376,6 +378,9 @@ extern GLubyte ACTIVE_TEXTURE;
extern GLboolean TEXTURES_ENABLED[]; extern GLboolean TEXTURES_ENABLED[];
GLubyte _glGetActiveTexture(); GLubyte _glGetActiveTexture();
GLint _glGetTextureInternalFormat();
GLboolean _glGetTextureTwiddle();
void _glSetTextureTwiddle(GLboolean v);
GLuint _glGetActiveClientTexture(); GLuint _glGetActiveClientTexture();
TexturePalette* _glGetSharedPalette(GLshort bank); TexturePalette* _glGetSharedPalette(GLshort bank);

View File

@ -494,7 +494,11 @@ GLAPI void APIENTRY glEnable(GLenum cap) {
GPUState.is_dirty = GL_TRUE; GPUState.is_dirty = GL_TRUE;
} }
break; break;
case GL_TEXTURE_TWIDDLE_KOS:
_glSetTextureTwiddle(GL_TRUE);
break;
default: default:
_glKosThrowError(GL_INVALID_VALUE, __func__);
break; break;
} }
} }
@ -596,7 +600,11 @@ GLAPI void APIENTRY glDisable(GLenum cap) {
GPUState.is_dirty = GL_TRUE; GPUState.is_dirty = GL_TRUE;
} }
break; break;
case GL_TEXTURE_TWIDDLE_KOS:
_glSetTextureTwiddle(GL_FALSE);
break;
default: default:
_glKosThrowError(GL_INVALID_VALUE, __func__);
break; break;
} }
} }
@ -977,6 +985,10 @@ void APIENTRY glGetIntegerv(GLenum pname, GLint *params) {
case GL_FREE_CONTIGUOUS_TEXTURE_MEMORY_KOS: case GL_FREE_CONTIGUOUS_TEXTURE_MEMORY_KOS:
*params = _glFreeContiguousTextureMemory(); *params = _glFreeContiguousTextureMemory();
break; break;
case GL_TEXTURE_INTERNAL_FORMAT_KOS:
*params = _glGetTextureInternalFormat();
break;
default: default:
_glKosThrowError(GL_INVALID_ENUM, __func__); _glKosThrowError(GL_INVALID_ENUM, __func__);
break; break;

View File

@ -23,16 +23,67 @@ GLubyte ACTIVE_TEXTURE = 0;
static TexturePalette* SHARED_PALETTES[MAX_GLDC_SHARED_PALETTES] = {NULL, NULL, NULL, NULL}; static TexturePalette* SHARED_PALETTES[MAX_GLDC_SHARED_PALETTES] = {NULL, NULL, NULL, NULL};
static GLuint _determinePVRFormat(GLint internalFormat, GLenum type); static GLuint _determinePVRFormat(GLint internalFormat);
static GLboolean BANKS_USED[MAX_GLDC_PALETTE_SLOTS]; // Each time a 256 colour bank is used, this is set to true 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 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 GLenum INTERNAL_PALETTE_FORMAT = GL_RGBA8;
static GLboolean TEXTURE_TWIDDLE_ENABLED = GL_FALSE;
static void* YALLOC_BASE = NULL; static void* YALLOC_BASE = NULL;
static size_t YALLOC_SIZE = 0; static size_t YALLOC_SIZE = 0;
static const unsigned short MortonTable256[256] =
{
0x0000, 0x0001, 0x0004, 0x0005, 0x0010, 0x0011, 0x0014, 0x0015,
0x0040, 0x0041, 0x0044, 0x0045, 0x0050, 0x0051, 0x0054, 0x0055,
0x0100, 0x0101, 0x0104, 0x0105, 0x0110, 0x0111, 0x0114, 0x0115,
0x0140, 0x0141, 0x0144, 0x0145, 0x0150, 0x0151, 0x0154, 0x0155,
0x0400, 0x0401, 0x0404, 0x0405, 0x0410, 0x0411, 0x0414, 0x0415,
0x0440, 0x0441, 0x0444, 0x0445, 0x0450, 0x0451, 0x0454, 0x0455,
0x0500, 0x0501, 0x0504, 0x0505, 0x0510, 0x0511, 0x0514, 0x0515,
0x0540, 0x0541, 0x0544, 0x0545, 0x0550, 0x0551, 0x0554, 0x0555,
0x1000, 0x1001, 0x1004, 0x1005, 0x1010, 0x1011, 0x1014, 0x1015,
0x1040, 0x1041, 0x1044, 0x1045, 0x1050, 0x1051, 0x1054, 0x1055,
0x1100, 0x1101, 0x1104, 0x1105, 0x1110, 0x1111, 0x1114, 0x1115,
0x1140, 0x1141, 0x1144, 0x1145, 0x1150, 0x1151, 0x1154, 0x1155,
0x1400, 0x1401, 0x1404, 0x1405, 0x1410, 0x1411, 0x1414, 0x1415,
0x1440, 0x1441, 0x1444, 0x1445, 0x1450, 0x1451, 0x1454, 0x1455,
0x1500, 0x1501, 0x1504, 0x1505, 0x1510, 0x1511, 0x1514, 0x1515,
0x1540, 0x1541, 0x1544, 0x1545, 0x1550, 0x1551, 0x1554, 0x1555,
0x4000, 0x4001, 0x4004, 0x4005, 0x4010, 0x4011, 0x4014, 0x4015,
0x4040, 0x4041, 0x4044, 0x4045, 0x4050, 0x4051, 0x4054, 0x4055,
0x4100, 0x4101, 0x4104, 0x4105, 0x4110, 0x4111, 0x4114, 0x4115,
0x4140, 0x4141, 0x4144, 0x4145, 0x4150, 0x4151, 0x4154, 0x4155,
0x4400, 0x4401, 0x4404, 0x4405, 0x4410, 0x4411, 0x4414, 0x4415,
0x4440, 0x4441, 0x4444, 0x4445, 0x4450, 0x4451, 0x4454, 0x4455,
0x4500, 0x4501, 0x4504, 0x4505, 0x4510, 0x4511, 0x4514, 0x4515,
0x4540, 0x4541, 0x4544, 0x4545, 0x4550, 0x4551, 0x4554, 0x4555,
0x5000, 0x5001, 0x5004, 0x5005, 0x5010, 0x5011, 0x5014, 0x5015,
0x5040, 0x5041, 0x5044, 0x5045, 0x5050, 0x5051, 0x5054, 0x5055,
0x5100, 0x5101, 0x5104, 0x5105, 0x5110, 0x5111, 0x5114, 0x5115,
0x5140, 0x5141, 0x5144, 0x5145, 0x5150, 0x5151, 0x5154, 0x5155,
0x5400, 0x5401, 0x5404, 0x5405, 0x5410, 0x5411, 0x5414, 0x5415,
0x5440, 0x5441, 0x5444, 0x5445, 0x5450, 0x5451, 0x5454, 0x5455,
0x5500, 0x5501, 0x5504, 0x5505, 0x5510, 0x5511, 0x5514, 0x5515,
0x5540, 0x5541, 0x5544, 0x5545, 0x5550, 0x5551, 0x5554, 0x5555
};
/* Given a 0-based texel location, and an image width/height. Return the
* new 0-based texel location */
GL_FORCE_INLINE uint32_t twid_location(uint32_t i, uint32_t w, uint32_t h) {
uint16_t y = i % w;
uint16_t x = i / w;
return MortonTable256[y >> 8] << 17 |
MortonTable256[x >> 8] << 16 |
MortonTable256[y & 0xFF] << 1 |
MortonTable256[x & 0xFF];
}
static void* yalloc_alloc_and_defrag(size_t size) { static void* yalloc_alloc_and_defrag(size_t size) {
void* ret = yalloc_alloc(YALLOC_BASE, size); void* ret = yalloc_alloc(YALLOC_BASE, size);
@ -146,78 +197,12 @@ static void _glReleasePaletteSlot(GLshort slot, GLushort size)
} }
} }
/* Linear/iterative twiddling algorithm from Marcus' tatest */ GLboolean _glGetTextureTwiddle() {
#define TWIDTAB(x) ( (x&1)|((x&2)<<1)|((x&4)<<2)|((x&8)<<3)|((x&16)<<4)| \ return TEXTURE_TWIDDLE_ENABLED;
((x&32)<<5)|((x&64)<<6)|((x&128)<<7)|((x&256)<<8)|((x&512)<<9) )
#define TWIDOUT(x, y) ( TWIDTAB((y)) | (TWIDTAB((x)) << 1) )
static void GPUTextureTwiddle8PPP(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_t* pixels;
uint16_t* vtex;
pixels = (uint8_t*) src;
vtex = (uint16_t*) dst;
for(y = 0; y < h; y += 2) {
yout = y;
for(x = 0; x < w; x++) {
int32_t idx = TWIDOUT((yout & mask) / 2, x & mask) +
(x / min + yout / min)*min * min / 2;
gl_assert(idx < (w * h));
vtex[idx] = pixels[y * w + x] | (pixels[(y + 1) * w + x] << 8);
}
}
} }
static void GPUTextureTwiddle4PPP(void* src, void* dst, uint32_t w, uint32_t h) { void _glSetTextureTwiddle(GLboolean v) {
uint32_t x, y, yout, min, mask; TEXTURE_TWIDDLE_ENABLED = v;
min = MIN(w, h);
mask = min - 1;
uint8_t* pixels;
uint16_t* vtex;
pixels = (uint8_t*) src;
vtex = (uint16_t*) dst;
for(y = 0; y < h; y += 2) {
yout = y;
for (x = 0; x < w; x += 2) {
vtex[TWIDOUT((x & mask) / 2, (yout & mask) / 2) +
(x / min + yout / min) * min * min / 4] =
vtex[TWIDOUT((x & mask) / 2, (yout & mask) / 2) +
(x / min + yout / min) * min * min / 4] =
((pixels[(x + y * w) >> 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);
}
}
}
static void GPUTextureTwiddle16BPP(void * src, void* dst, uint32_t w, uint32_t h) {
uint32_t x, y, yout, min, mask;
min = MIN(w, h);
mask = min - 1;
uint16_t* pixels;
uint16_t* vtex;
pixels = (uint16_t*) src;
vtex = (uint16_t*) dst;
for(y = 0; y < h; y++) {
yout = y;
for(x = 0; x < w; x++) {
vtex[TWIDOUT(x & mask, yout & mask) +
(x / min + yout / min)*min * min] = pixels[y * w + x];
}
}
} }
TexturePalette* _glGetSharedPalette(GLshort bank) { TexturePalette* _glGetSharedPalette(GLshort bank) {
@ -278,7 +263,27 @@ GLubyte _glGetActiveTexture() {
return ACTIVE_TEXTURE; return ACTIVE_TEXTURE;
} }
static GLint _determineStride(GLenum format, GLenum type) { static GLint _determineStrideInternal(GLenum internalFormat) {
switch(internalFormat) {
case GL_RGB565_KOS:
case GL_ARGB4444_KOS:
case GL_ARGB1555_KOS:
case GL_RGB565_TWID_KOS:
case GL_ARGB4444_TWID_KOS:
case GL_ARGB1555_TWID_KOS:
return 2;
case GL_COLOR_INDEX8_TWID_KOS:
case GL_COLOR_INDEX4_TWID_KOS:
case GL_COLOR_INDEX4_EXT:
case GL_COLOR_INDEX8_EXT:
return 1;
}
return -1;
}
static GLint _determineStride(GLenum format, GLenum type) {
switch(type) { switch(type) {
case GL_BYTE: case GL_BYTE:
case GL_UNSIGNED_BYTE: case GL_UNSIGNED_BYTE:
@ -528,6 +533,16 @@ TextureObject* _glGetBoundTexture() {
return TEXTURE_UNITS[ACTIVE_TEXTURE]; return TEXTURE_UNITS[ACTIVE_TEXTURE];
} }
GLint _glGetTextureInternalFormat() {
TextureObject* obj = _glGetBoundTexture();
if(!obj) {
return -1;
}
return obj->internalFormat;
}
void APIENTRY glActiveTextureARB(GLenum texture) { void APIENTRY glActiveTextureARB(GLenum texture) {
TRACE(); TRACE();
@ -538,8 +553,6 @@ void APIENTRY glActiveTextureARB(GLenum texture) {
ACTIVE_TEXTURE = texture & 0xF; ACTIVE_TEXTURE = texture & 0xF;
gl_assert(ACTIVE_TEXTURE < MAX_GLDC_TEXTURE_UNITS); gl_assert(ACTIVE_TEXTURE < MAX_GLDC_TEXTURE_UNITS);
gl_assert(ACTIVE_TEXTURE >= 0);
gl_assert(TEXTURE_OBJECTS.element_size > 0); gl_assert(TEXTURE_OBJECTS.element_size > 0);
} }
@ -837,10 +850,8 @@ void APIENTRY glCompressedTexImage2DARB(GLenum target,
/* Set the required mipmap count */ /* Set the required mipmap count */
active->width = width; active->width = width;
active->height = height; active->height = height;
active->color = _determinePVRFormat( active->internalFormat = internalFormat;
internalFormat, active->color = _determinePVRFormat(internalFormat);
internalFormat /* Doesn't matter (see determinePVRFormat) */
);
active->mipmapCount = _glGetMipmapLevelCount(active); active->mipmapCount = _glGetMipmapLevelCount(active);
active->mipmap = (mipmapped) ? ~0 : (1 << level); /* Set only a single bit if this wasn't mipmapped otherwise set all */ active->mipmap = (mipmapped) ? ~0 : (1 << level); /* Set only a single bit if this wasn't mipmapped otherwise set all */
active->isCompressed = GL_TRUE; active->isCompressed = GL_TRUE;
@ -868,53 +879,95 @@ void APIENTRY glCompressedTexImage2DARB(GLenum target,
_glGPUStateMarkDirty(); _glGPUStateMarkDirty();
} }
static GLboolean isTwiddledInternalFormat(GLint internalFormat) {
switch(internalFormat) {
case GL_RGB565_TWID_KOS:
case GL_ARGB4444_TWID_KOS:
case GL_ARGB1555_TWID_KOS:
case GL_COLOR_INDEX8_TWID_KOS:
case GL_COLOR_INDEX4_TWID_KOS:
case GL_RGB_TWID_KOS:
case GL_RGBA_TWID_KOS:
return true;
default:
return false;
}
}
/**
* Takes an internal format, and returns a GL format matching how we'd store
* it internally, so it'll return one of the following:
*
* - GL_RGB565_KOS,
* - GL_ARGB4444_KOS
* - GL_ARGB1555_KOS
* - GL_RGB565_TWID_KOS
* - GL_ARGB4444_TWID_KOS
* - GL_ARGB1555_TWID_KOS
* - GL_COLOR_INDEX8_EXT
* - GL_COLOR_INDEX4_EXT
* - GL_COLOR_INDEX8_TWID_KOS
* - GL_COLOR_INDEX4_TWID_KOS
*/
static GLint _cleanInternalFormat(GLint internalFormat) { static GLint _cleanInternalFormat(GLint internalFormat) {
switch (internalFormat) { switch (internalFormat) {
case GL_RGB565_KOS:
case GL_ARGB4444_KOS:
case GL_ARGB1555_KOS:
case GL_RGB565_TWID_KOS:
case GL_ARGB4444_TWID_KOS:
case GL_ARGB1555_TWID_KOS:
case GL_COLOR_INDEX8_TWID_KOS:
case GL_COLOR_INDEX4_TWID_KOS:
case GL_COLOR_INDEX4_EXT: case GL_COLOR_INDEX4_EXT:
return GL_COLOR_INDEX4_EXT;
case GL_COLOR_INDEX8_EXT: case GL_COLOR_INDEX8_EXT:
return GL_COLOR_INDEX8_EXT; /* All of these formats are fine as they are, no conversion needed */
return internalFormat;
case GL_RGB_TWID_KOS:
return GL_RGB565_TWID_KOS;
case GL_RGBA_TWID_KOS:
return GL_ARGB4444_TWID_KOS;
case GL_ALPHA: case GL_ALPHA:
/* case GL_ALPHA4: case GL_ALPHA4:
case GL_ALPHA8: case GL_ALPHA8:
case GL_ALPHA12: case GL_ALPHA12:
case GL_ALPHA16:*/ case GL_ALPHA16:
return GL_ALPHA; return (TEXTURE_TWIDDLE_ENABLED) ? GL_ARGB4444_TWID_KOS : GL_ARGB4444_KOS;
case 1: case 1:
case GL_LUMINANCE: case GL_LUMINANCE:
/* case GL_LUMINANCE4: case GL_LUMINANCE4:
case GL_LUMINANCE8: case GL_LUMINANCE8:
case GL_LUMINANCE12: case GL_LUMINANCE12:
case GL_LUMINANCE16:*/ case GL_LUMINANCE16:
return GL_LUMINANCE; return (TEXTURE_TWIDDLE_ENABLED) ? GL_ARGB1555_TWID_KOS : GL_ARGB1555_KOS;
case 2: case 2:
case GL_LUMINANCE_ALPHA: case GL_LUMINANCE_ALPHA:
/* case GL_LUMINANCE4_ALPHA4: case GL_LUMINANCE4_ALPHA4:
case GL_LUMINANCE6_ALPHA2: case GL_LUMINANCE6_ALPHA2:
case GL_LUMINANCE8_ALPHA8: case GL_LUMINANCE8_ALPHA8:
case GL_LUMINANCE12_ALPHA4: case GL_LUMINANCE12_ALPHA4:
case GL_LUMINANCE12_ALPHA12: case GL_LUMINANCE12_ALPHA12:
case GL_LUMINANCE16_ALPHA16: */ case GL_LUMINANCE16_ALPHA16:
return GL_LUMINANCE_ALPHA; return (TEXTURE_TWIDDLE_ENABLED) ? GL_ARGB4444_TWID_KOS : GL_ARGB4444_KOS;
/* case GL_INTENSITY: case GL_INTENSITY:
case GL_INTENSITY4: case GL_INTENSITY4:
case GL_INTENSITY8: case GL_INTENSITY8:
case GL_INTENSITY12: case GL_INTENSITY12:
case GL_INTENSITY16: case GL_INTENSITY16:
return GL_INTENSITY; */ return (TEXTURE_TWIDDLE_ENABLED) ? GL_ARGB4444_TWID_KOS : GL_ARGB4444_KOS;
case 3: case 3:
return GL_RGB; return (TEXTURE_TWIDDLE_ENABLED) ? GL_RGB565_TWID_KOS : GL_RGB565_KOS;
case GL_RGB: case GL_RGB:
/* case GL_R3_G3_B2: */ case GL_R3_G3_B2:
case GL_RGB4: case GL_RGB4:
case GL_RGB5: case GL_RGB5:
case GL_RGB8: case GL_RGB8:
case GL_RGB10: case GL_RGB10:
case GL_RGB12: case GL_RGB12:
case GL_RGB16: case GL_RGB16:
return GL_RGB; return (TEXTURE_TWIDDLE_ENABLED) ? GL_RGB565_TWID_KOS : GL_RGB565_KOS;
case 4: case 4:
return GL_RGBA; return (TEXTURE_TWIDDLE_ENABLED) ? GL_ARGB4444_TWID_KOS : GL_ARGB4444_KOS;
case GL_RGBA: case GL_RGBA:
case GL_RGBA2: case GL_RGBA2:
case GL_RGBA4: case GL_RGBA4:
@ -923,92 +976,51 @@ static GLint _cleanInternalFormat(GLint internalFormat) {
case GL_RGB10_A2: case GL_RGB10_A2:
case GL_RGBA12: case GL_RGBA12:
case GL_RGBA16: case GL_RGBA16:
return GL_RGBA; return (TEXTURE_TWIDDLE_ENABLED) ? GL_ARGB4444_TWID_KOS : GL_ARGB4444_KOS;
/* Support ARB_texture_rg */ /* Support ARB_texture_rg */
case GL_RED: case GL_RED:
/* case GL_R8: case GL_R8:
case GL_R16: case GL_R16:
case GL_RED: case GL_COMPRESSED_RED:
case GL_COMPRESSED_RED: */ return (TEXTURE_TWIDDLE_ENABLED) ? GL_RGB565_TWID_KOS : GL_RGB565_KOS;
return GL_RED; case GL_RG:
/* case GL_RG:
case GL_RG8: case GL_RG8:
case GL_RG16: case GL_RG16:
case GL_COMPRESSED_RG: case GL_COMPRESSED_RG:
return GL_RG;*/ return (TEXTURE_TWIDDLE_ENABLED) ? GL_RGB565_TWID_KOS : GL_RGB565_KOS;
default: default:
return -1; return -1;
} }
} }
static GLuint _determinePVRFormat(GLint internalFormat, GLenum type) { static GLuint _determinePVRFormat(GLint internalFormat) {
/* Given a cleaned internalFormat, return the Dreamcast format /* Given a cleaned internalFormat, return the Dreamcast format
* that can hold it * that can hold it
*/ */
switch(internalFormat) { switch(internalFormat) {
case GL_ALPHA: case GL_RGB565_KOS:
case GL_LUMINANCE: return GPU_TXRFMT_RGB565 | GPU_TXRFMT_NONTWIDDLED;
case GL_LUMINANCE_ALPHA: case GL_ARGB4444_KOS:
case GL_RGBA: return GPU_TXRFMT_ARGB4444 | GPU_TXRFMT_NONTWIDDLED;
/* OK so if we have something that requires alpha, we return 4444 unless case GL_ARGB1555_KOS:
* the type was already 1555 (1-bit alpha) in which case we return that return GPU_TXRFMT_ARGB1555 | GPU_TXRFMT_NONTWIDDLED;
*/ case GL_RGB565_TWID_KOS:
if(type == GL_UNSIGNED_SHORT_1_5_5_5_REV) { return GPU_TXRFMT_RGB565 | GPU_TXRFMT_TWIDDLED;
return GPU_TXRFMT_ARGB1555 | GPU_TXRFMT_NONTWIDDLED; case GL_ARGB4444_TWID_KOS:
} else if(type == GL_UNSIGNED_SHORT_1_5_5_5_REV_TWID_KOS) { return GPU_TXRFMT_ARGB4444 | GPU_TXRFMT_TWIDDLED;
return GPU_TXRFMT_ARGB1555 | GPU_TXRFMT_TWIDDLED; case GL_ARGB1555_TWID_KOS:
} else if(type == GL_UNSIGNED_SHORT_4_4_4_4_REV_TWID_KOS) { return GPU_TXRFMT_ARGB1555 | GPU_TXRFMT_TWIDDLED;
return GPU_TXRFMT_ARGB4444 | GPU_TXRFMT_TWIDDLED; case GL_COLOR_INDEX8_EXT:
} else { return GPU_TXRFMT_PAL8BPP | GPU_TXRFMT_NONTWIDDLED;
return GPU_TXRFMT_ARGB4444 | GPU_TXRFMT_NONTWIDDLED; case GL_COLOR_INDEX4_EXT:
} return GPU_TXRFMT_PAL4BPP | GPU_TXRFMT_NONTWIDDLED;
case GL_RED: case GL_COLOR_INDEX8_TWID_KOS:
case GL_RGB: return GPU_TXRFMT_PAL8BPP | GPU_TXRFMT_TWIDDLED;
switch(type) { case GL_COLOR_INDEX4_TWID_KOS:
case GL_UNSIGNED_SHORT_5_6_5_TWID_KOS: return GPU_TXRFMT_PAL4BPP | GPU_TXRFMT_TWIDDLED;
return GPU_TXRFMT_RGB565 | GPU_TXRFMT_TWIDDLED; default:
case GL_COMPRESSED_RGB_565_VQ_KOS: _glKosThrowError(GL_INVALID_ENUM, __func__);
case GL_COMPRESSED_RGB_565_VQ_MIPMAP_KOS: return 0;
return GPU_TXRFMT_RGB565 | GPU_TXRFMT_NONTWIDDLED | GPU_TXRFMT_VQ_ENABLE;
case GL_COMPRESSED_RGB_565_VQ_TWID_KOS:
case GL_COMPRESSED_RGB_565_VQ_MIPMAP_TWID_KOS:
return GPU_TXRFMT_RGB565 | GPU_TXRFMT_TWIDDLED | GPU_TXRFMT_VQ_ENABLE;
default:
return GPU_TXRFMT_RGB565 | GPU_TXRFMT_NONTWIDDLED;
}
break;
/* Compressed and twiddled versions */
case GL_UNSIGNED_SHORT_5_6_5_TWID_KOS:
return GPU_TXRFMT_RGB565 | GPU_TXRFMT_TWIDDLED;
case GL_UNSIGNED_SHORT_4_4_4_4_REV_TWID_KOS:
return GPU_TXRFMT_ARGB4444 | GPU_TXRFMT_TWIDDLED;
case GL_UNSIGNED_SHORT_1_5_5_5_REV_TWID_KOS:
return GPU_TXRFMT_ARGB1555 | GPU_TXRFMT_TWIDDLED;
case GL_COMPRESSED_RGB_565_VQ_KOS:
case GL_COMPRESSED_RGB_565_VQ_MIPMAP_KOS:
return GPU_TXRFMT_RGB565 | GPU_TXRFMT_NONTWIDDLED | GPU_TXRFMT_VQ_ENABLE;
case GL_COMPRESSED_RGB_565_VQ_TWID_KOS:
case GL_COMPRESSED_RGB_565_VQ_MIPMAP_TWID_KOS:
return GPU_TXRFMT_RGB565 | GPU_TXRFMT_TWIDDLED | GPU_TXRFMT_VQ_ENABLE;
case GL_COMPRESSED_ARGB_4444_VQ_TWID_KOS:
case GL_COMPRESSED_ARGB_4444_VQ_MIPMAP_TWID_KOS:
return GPU_TXRFMT_ARGB4444 | GPU_TXRFMT_TWIDDLED | GPU_TXRFMT_VQ_ENABLE;
case GL_COMPRESSED_ARGB_4444_VQ_KOS:
case GL_COMPRESSED_ARGB_4444_VQ_MIPMAP_KOS:
return GPU_TXRFMT_ARGB4444 | GPU_TXRFMT_NONTWIDDLED | GPU_TXRFMT_VQ_ENABLE;
case GL_COMPRESSED_ARGB_1555_VQ_KOS:
case GL_COMPRESSED_ARGB_1555_VQ_MIPMAP_KOS:
return GPU_TXRFMT_ARGB1555 | GPU_TXRFMT_NONTWIDDLED | GPU_TXRFMT_VQ_ENABLE;
case GL_COMPRESSED_ARGB_1555_VQ_TWID_KOS:
case GL_COMPRESSED_ARGB_1555_VQ_MIPMAP_TWID_KOS:
return GPU_TXRFMT_ARGB1555 | GPU_TXRFMT_TWIDDLED | GPU_TXRFMT_VQ_ENABLE;
case GL_COLOR_INDEX8_EXT:
return GPU_TXRFMT_PAL8BPP | GPU_TXRFMT_TWIDDLED;
case GL_COLOR_INDEX4_EXT:
return GPU_TXRFMT_PAL4BPP | GPU_TXRFMT_TWIDDLED;
default:
return 0;
} }
} }
@ -1121,109 +1133,68 @@ static inline void _a8_to_argb4444(const GLubyte* source, GLubyte* dest) {
*((GLushort*) dest) = (color << 8) | color; *((GLushort*) dest) = (color << 8) | color;
} }
static TextureConversionFunc _determineConversion(GLint internalFormat, GLenum format, GLenum type) { /* Given an cleaned internal format, and the passed format and type, this returns:
switch(internalFormat) { *
case GL_ALPHA: { * 0 if not conversion is necessary
if(format == GL_ALPHA) { * 1 if a conversion is necessary (func will be set)
/* Dreamcast doesn't really support GL_ALPHA internally, so store as argb with each rgb value as white */ * 2 if twiddling is necessary
/* Applying alpha values to all channels seems a better option*/ * 3 if twiddling and conversion is necessary (func will be set)
return _a8_to_argb4444; * -1 if a conversion is unsupported
} else if(type == GL_UNSIGNED_BYTE && format == GL_RGBA) { *
return _rgba8888_to_a000; */
} else if(type == GL_BYTE && format == GL_RGBA) { static int _determineConversion(GLint internalFormat, GLenum format, GLenum type, TextureConversionFunc* func) {
return _rgba8888_to_a000; static struct Entry {
} GLint internalFormat;
} break; GLenum format;
case GL_RED: { GLenum type;
if(type == GL_UNSIGNED_BYTE && format == GL_RED) { TextureConversionFunc func;
/* Dreamcast doesn't really support GL_RED internally, so store as rgb */ bool twiddle;
return _r8_to_rgb565; } conversions [] = {
} {GL_ARGB4444_KOS, GL_ALPHA, GL_UNSIGNED_BYTE, _a8_to_argb4444, false},
} break; {GL_ARGB4444_KOS, GL_RGBA, GL_UNSIGNED_BYTE, _rgba8888_to_argb4444, false},
case GL_RGB: { {GL_ARGB4444_KOS, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4, _rgba4444_to_argb4444, false},
if(type == GL_UNSIGNED_BYTE && format == GL_RGB) { {GL_ARGB4444_KOS, GL_BGRA, GL_UNSIGNED_SHORT_4_4_4_4_REV, NULL, false},
return _rgb888_to_rgb565; {GL_ARGB4444_TWID_KOS, GL_BGRA, GL_UNSIGNED_SHORT_4_4_4_4_REV_TWID_KOS, NULL, false},
} else if(type == GL_UNSIGNED_BYTE && format == GL_RGBA) { {GL_ARGB4444_TWID_KOS, GL_RGBA, GL_UNSIGNED_BYTE, _rgba8888_to_argb4444, true},
return _rgba8888_to_rgb565; {GL_ARGB1555_KOS, GL_BGRA, GL_UNSIGNED_SHORT_1_5_5_5_REV, NULL, false},
} else if(type == GL_BYTE && format == GL_RGB) { {GL_ARGB1555_TWID_KOS, GL_BGRA, GL_UNSIGNED_SHORT_1_5_5_5_REV_TWID_KOS, NULL, false},
return _rgb888_to_rgb565; {GL_RGB565_KOS, GL_RGBA, GL_UNSIGNED_BYTE, _rgba8888_to_rgb565, false},
} else if(type == GL_UNSIGNED_BYTE && format == GL_RED) { {GL_RGB565_KOS, GL_RED, GL_UNSIGNED_BYTE, _r8_to_rgb565, false},
return _r8_to_rgb565; {GL_RGB565_KOS, GL_RGB, GL_UNSIGNED_BYTE, _rgb888_to_rgb565, false},
} {GL_RGB565_KOS, GL_RGBA, GL_UNSIGNED_BYTE, _rgba8888_to_rgb565, false},
} break; {GL_RGB565_KOS, GL_RED, GL_UNSIGNED_BYTE, _r8_to_rgb565, false},
case GL_RGBA: { {GL_RGB565_KOS, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, NULL, false},
if(type == GL_UNSIGNED_BYTE && format == GL_RGBA) { {GL_RGB565_TWID_KOS, GL_RGB, GL_UNSIGNED_SHORT_5_6_5_TWID_KOS, NULL, false},
return _rgba8888_to_argb4444; {GL_RGB565_TWID_KOS, GL_RGB, GL_UNSIGNED_BYTE, _rgb888_to_rgb565, true},
} else if (type == GL_BYTE && format == GL_RGBA) { {GL_COLOR_INDEX8_EXT, GL_COLOR_INDEX, GL_UNSIGNED_BYTE, NULL, false},
return _rgba8888_to_argb4444; {GL_COLOR_INDEX8_EXT, GL_COLOR_INDEX, GL_BYTE, NULL, false},
} else if(type == GL_UNSIGNED_SHORT_4_4_4_4 && format == GL_RGBA) { {GL_COLOR_INDEX8_TWID_KOS, GL_COLOR_INDEX, GL_UNSIGNED_BYTE_TWID_KOS, NULL, false},
return _rgba4444_to_argb4444; };
}
} break;
case GL_RGBA8:
case GL_RGBA4:
case GL_RGB5_A1:
case GL_RGB565_KOS:
if(type == GL_UNSIGNED_BYTE && format == GL_RGBA) { for(size_t i = 0; i < sizeof(conversions) / sizeof(struct Entry); ++i) {
return _rgba8888_to_rgba8888; struct Entry* e = conversions + i;
if(e->format == format && e->internalFormat == internalFormat && e->type == type) {
*func = e->func;
int ret = (e->func) ? 1 : 0;
ret += (e->twiddle) ? 2 : 0;
return ret;
} }
else
if (type == GL_BYTE && format == GL_RGBA) {
return _rgba8888_to_rgba8888;
}
else
if(type == GL_UNSIGNED_BYTE && format == GL_RGB) {
return _rgb888_to_rgba8888;
}
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) {
return _rgba4444_to_rgba8888;
}
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) {
case GL_BYTE:
case GL_UNSIGNED_BYTE:
return _i8_to_i8;
default:
break;
}
}
break;
default:
fprintf(stderr, "Unsupported conversion: %x -> %x, %x\n", internalFormat, format, type);
break;
} }
return 0;
return -1;
} }
static GLboolean _isSupportedFormat(GLenum format) { static GLboolean _isSupportedFormat(GLenum format) {
switch(format) { switch(format) {
case GL_ALPHA: case GL_ALPHA:
case GL_LUMINANCE:
case GL_INTENSITY:
case GL_LUMINANCE_ALPHA:
case GL_RED: case GL_RED:
case GL_RGB: case GL_RGB:
case GL_RGBA: case GL_RGBA:
case GL_BGRA: case GL_BGRA:
case GL_COLOR_INDEX: case GL_COLOR_INDEX:
case GL_UNSIGNED_SHORT_4_4_4_4_REV_TWID_KOS:
return GL_TRUE; return GL_TRUE;
default: default:
return GL_FALSE; return GL_FALSE;
@ -1299,49 +1270,44 @@ void _glAllocateSpaceForMipmaps(TextureObject* active) {
#define TOSTRING(x) STRINGIFY(x) #define TOSTRING(x) STRINGIFY(x)
#define INFO_MSG(x) fprintf(stderr, "%s:%s > %s\n", __FILE__, TOSTRING(__LINE__), x) #define INFO_MSG(x) fprintf(stderr, "%s:%s > %s\n", __FILE__, TOSTRING(__LINE__), x)
void APIENTRY glTexImage2D(GLenum target, GLint level, GLint internalFormat, static bool _glTexImage2DValidate(GLenum target, GLint level, GLint internalFormat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type) {
GLsizei width, GLsizei height, GLint border,
GLenum format, GLenum type, const GLvoid *data) {
TRACE();
if(target != GL_TEXTURE_2D) { if(target != GL_TEXTURE_2D) {
INFO_MSG(""); INFO_MSG("");
_glKosThrowError(GL_INVALID_ENUM, __func__); _glKosThrowError(GL_INVALID_ENUM, __func__);
return; return false;
} }
if (width > 1024 || height > 1024){ if (width > 1024 || height > 1024){
INFO_MSG("Invalid texture size"); INFO_MSG("Invalid texture size");
_glKosThrowError(GL_INVALID_VALUE, __func__); _glKosThrowError(GL_INVALID_VALUE, __func__);
return; return false;
} }
if(format != GL_COLOR_INDEX) { if(format != GL_COLOR_INDEX) {
if(!_isSupportedFormat(format)) { if(!_isSupportedFormat(format)) {
INFO_MSG("Unsupported format"); INFO_MSG("Unsupported format");
_glKosThrowError(GL_INVALID_ENUM, __func__); _glKosThrowError(GL_INVALID_ENUM, __func__);
return; return false;
} }
/* Abuse determineStride to see if type is valid */ /* Abuse determineStride to see if type is valid */
if(_determineStride(GL_RGBA, type) == -1) { if(_determineStride(GL_RGBA, type) == -1) {
INFO_MSG(""); INFO_MSG("");
_glKosThrowError(GL_INVALID_ENUM, __func__); _glKosThrowError(GL_INVALID_ENUM, __func__);
return; return false;
} }
internalFormat = _cleanInternalFormat(internalFormat); internalFormat = _cleanInternalFormat(internalFormat);
if(internalFormat == -1) { if(internalFormat == -1) {
INFO_MSG(""); INFO_MSG("");
_glKosThrowError(GL_INVALID_VALUE, __func__); _glKosThrowError(GL_INVALID_VALUE, __func__);
return; return false;
} }
} else { } else {
if(internalFormat != GL_COLOR_INDEX8_EXT && internalFormat != GL_COLOR_INDEX4_EXT) { if(internalFormat != GL_COLOR_INDEX8_EXT && internalFormat != GL_COLOR_INDEX4_EXT) {
INFO_MSG(""); INFO_MSG("");
_glKosThrowError(GL_INVALID_ENUM, __func__); _glKosThrowError(GL_INVALID_ENUM, __func__);
return; return false;
} }
} }
@ -1352,7 +1318,7 @@ void APIENTRY glTexImage2D(GLenum target, GLint level, GLint internalFormat,
/* Width is not a power of two. Must be!*/ /* Width is not a power of two. Must be!*/
INFO_MSG(""); INFO_MSG("");
_glKosThrowError(GL_INVALID_VALUE, __func__); _glKosThrowError(GL_INVALID_VALUE, __func__);
return; return false;
} }
@ -1360,7 +1326,7 @@ void APIENTRY glTexImage2D(GLenum target, GLint level, GLint internalFormat,
/* height is not a power of two. Must be!*/ /* height is not a power of two. Must be!*/
INFO_MSG(""); INFO_MSG("");
_glKosThrowError(GL_INVALID_VALUE, __func__); _glKosThrowError(GL_INVALID_VALUE, __func__);
return; return false;
} }
} else { } else {
/* Mipmap Errors, kos crashes if 1x1 */ /* Mipmap Errors, kos crashes if 1x1 */
@ -1368,26 +1334,38 @@ void APIENTRY glTexImage2D(GLenum target, GLint level, GLint internalFormat,
gl_assert(ACTIVE_TEXTURE < MAX_GLDC_TEXTURE_UNITS); gl_assert(ACTIVE_TEXTURE < MAX_GLDC_TEXTURE_UNITS);
gl_assert(TEXTURE_UNITS[ACTIVE_TEXTURE]); gl_assert(TEXTURE_UNITS[ACTIVE_TEXTURE]);
TEXTURE_UNITS[ACTIVE_TEXTURE]->mipmap |= (1 << level); TEXTURE_UNITS[ACTIVE_TEXTURE]->mipmap |= (1 << level);
return; return false;
} }
} }
if(level < 0) { if(level < 0) {
INFO_MSG(""); INFO_MSG("");
_glKosThrowError(GL_INVALID_VALUE, __func__); _glKosThrowError(GL_INVALID_VALUE, __func__);
return; return false;
} }
if(level > 0 && width != height) { if(level > 0 && width != height) {
INFO_MSG("Tried to set non-square texture as a mipmap level"); INFO_MSG("Tried to set non-square texture as a mipmap level");
printf("[GL ERROR] Mipmaps cannot be supported on non-square textures\n"); printf("[GL ERROR] Mipmaps cannot be supported on non-square textures\n");
_glKosThrowError(GL_INVALID_OPERATION, __func__); _glKosThrowError(GL_INVALID_OPERATION, __func__);
return; return false;
} }
if(border) { if(border) {
INFO_MSG(""); INFO_MSG("");
_glKosThrowError(GL_INVALID_VALUE, __func__); _glKosThrowError(GL_INVALID_VALUE, __func__);
return false;
}
return true;
}
void APIENTRY glTexImage2D(GLenum target, GLint level, GLint internalFormat,
GLsizei width, GLsizei height, GLint border,
GLenum format, GLenum type, const GLvoid *data) {
TRACE();
if(!_glTexImage2DValidate(target, level, internalFormat, width, height, border, format, type)) {
return; return;
} }
@ -1400,19 +1378,16 @@ void APIENTRY glTexImage2D(GLenum target, GLint level, GLint internalFormat,
return; return;
} }
gl_assert(active);
GLuint original_id = active->index;
GLboolean isPaletted = (internalFormat == GL_COLOR_INDEX8_EXT || internalFormat == GL_COLOR_INDEX4_EXT) ? GL_TRUE : GL_FALSE; GLboolean isPaletted = (internalFormat == GL_COLOR_INDEX8_EXT || internalFormat == GL_COLOR_INDEX4_EXT) ? GL_TRUE : GL_FALSE;
GLenum cleanInternalFormat = _cleanInternalFormat(internalFormat);
/* Calculate the format that we need to convert the data to */ GLuint pvrFormat = _determinePVRFormat(cleanInternalFormat);
GLuint pvr_format = _determinePVRFormat(internalFormat, type); GLuint originalId = active->index;
if(active->data && (level == 0)) { if(active->data && (level == 0)) {
/* pre-existing texture - check if changed */ /* pre-existing texture - check if changed */
if(active->width != width || if(active->width != width ||
active->height != height || active->height != height ||
active->color != pvr_format) { active->internalFormat != cleanInternalFormat) {
/* changed - free old texture memory */ /* changed - free old texture memory */
yalloc_free(YALLOC_BASE, active->data); yalloc_free(YALLOC_BASE, active->data);
active->data = NULL; active->data = NULL;
@ -1445,7 +1420,8 @@ void APIENTRY glTexImage2D(GLenum target, GLint level, GLint internalFormat,
/* need texture memory */ /* need texture memory */
active->width = width; active->width = width;
active->height = height; active->height = height;
active->color = pvr_format; active->color = pvrFormat;
active->internalFormat = cleanInternalFormat;
/* Set the required mipmap count */ /* Set the required mipmap count */
active->mipmapCount = _glGetMipmapLevelCount(active); active->mipmapCount = _glGetMipmapLevelCount(active);
active->mipmap_bias = GL_KOS_INTERNAL_DEFAULT_MIPMAP_LOD_BIAS; active->mipmap_bias = GL_KOS_INTERNAL_DEFAULT_MIPMAP_LOD_BIAS;
@ -1476,139 +1452,66 @@ void APIENTRY glTexImage2D(GLenum target, GLint level, GLint internalFormat,
/* If we run out of PVR memory just return */ /* If we run out of PVR memory just return */
if(!active->data) { if(!active->data) {
_glKosThrowError(GL_OUT_OF_MEMORY, __func__); _glKosThrowError(GL_OUT_OF_MEMORY, __func__);
gl_assert(active->index == original_id); gl_assert(active->index == originalId);
return; return;
} }
/* Mark this level as set in the mipmap bitmask */ /* Mark this level as set in the mipmap bitmask */
active->mipmap |= (1 << level); active->mipmap |= (1 << level);
/* Let's assume we need to convert */
GLboolean needsConversion = GL_TRUE;
/* Let's assume we need twiddling - we always store things twiddled! */
GLboolean needsTwiddling = 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_COLOR_INDEX) {
/* Don't convert color indexes */
needsConversion = GL_FALSE;
if(type == GL_UNSIGNED_BYTE_TWID_KOS) {
needsTwiddling = GL_FALSE;
}
} else 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;
needsTwiddling = GL_FALSE;
} else if(format == GL_BGRA && type == GL_UNSIGNED_SHORT_1_5_5_5_REV_TWID_KOS && internalFormat == GL_RGBA) {
needsConversion = GL_FALSE;
needsTwiddling = GL_FALSE;
} else if(format == GL_BGRA && type == GL_UNSIGNED_SHORT_4_4_4_4_REV_TWID_KOS && internalFormat == GL_RGBA) {
needsConversion = GL_FALSE;
needsTwiddling = GL_FALSE;
}
GLubyte* targetData = (active->baseDataOffset == 0) ? active->data : _glGetMipmapLocation(active, level); GLubyte* targetData = (active->baseDataOffset == 0) ? active->data : _glGetMipmapLocation(active, level);
gl_assert(targetData); gl_assert(targetData);
GLubyte* conversionBuffer = NULL; TextureConversionFunc conversion;
int needs_conversion = _determineConversion(cleanInternalFormat, format, type, &conversion);
if(!data) { if(needs_conversion < 0) {
/* No data? Do nothing! */ _glKosThrowError(GL_INVALID_VALUE, __func__);
gl_assert(active->index == original_id); INFO_MSG("Couldn't find necessary texture conversion\n");
return; return;
} else if(!needsConversion && !needsTwiddling) { } else if(needs_conversion > 0) {
/* Convert the data */
GLint sourceStride = _determineStride(format, type);
GLint destStride = _determineStrideInternal(cleanInternalFormat);
if(sourceStride == -1) {
INFO_MSG("Stride was not detected\n");
_glKosThrowError(GL_INVALID_OPERATION, __func__);
return;
}
GLubyte* conversionBuffer = (GLubyte*) malloc(bytes);
const GLubyte* src = data;
GLubyte* dst = conversionBuffer;
for(uint32_t i = 0; i < (width * height); ++i) {
if(needs_conversion > 1) {
// Needs twiddling. Set dst to the twiddle location
// for this texel
uint32_t newLocation = twid_location(i, width, height);
dst = conversionBuffer + (destStride * newLocation);
}
if(conversion) {
conversion(src, dst);
}
dst += destStride;
src += sourceStride;
}
FASTCPY(targetData, conversionBuffer, bytes);
free(conversionBuffer);
} else {
/* No conversion necessary, we can just upload data directly */
gl_assert(targetData); gl_assert(targetData);
gl_assert(data); gl_assert(data);
gl_assert(bytes); gl_assert(bytes);
/* No conversion? Just copy the data, and the pvr_format is correct */ /* No conversion? Just copy the data, and the pvr_format is correct */
FASTCPY(targetData, data, bytes); FASTCPY(targetData, data, bytes);
gl_assert(active->index == original_id); gl_assert(active->index == originalId);
return;
} else if(needsConversion) {
TextureConversionFunc convert = _determineConversion(
internalFormat,
format,
type
);
if(!convert) {
INFO_MSG("Couldn't find conversion\n");
_glKosThrowError(GL_INVALID_OPERATION, __func__);
return;
}
GLint stride = _determineStride(format, type);
gl_assert(stride > -1);
if(stride == -1) {
INFO_MSG("Stride was not detected\n");
_glKosThrowError(GL_INVALID_OPERATION, __func__);
return;
}
conversionBuffer = malloc(bytes);
GLubyte* dest = conversionBuffer;
const GLubyte* source = data;
gl_assert(conversionBuffer);
gl_assert(source);
/* Perform the conversion */
GLuint i;
for(i = 0; i < bytes; i += destStride) {
convert(source, dest);
dest += destStride;
source += stride;
}
} }
if(needsTwiddling) { gl_assert(active->index == originalId);
const GLubyte *pixels = (GLubyte*) (conversionBuffer) ? conversionBuffer : data;
if(internalFormat == GL_COLOR_INDEX8_EXT) {
GPUTextureTwiddle8PPP((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
* make it twiddled when determining the format but I worry that would make the
* code less flexible to change in the future */
active->color &= ~(1 << 26);
} else {
/* We should only get here if we converted twiddled data... which is never currently */
gl_assert(conversionBuffer);
// We've already converted the data and we
// don't need to twiddle it!
FASTCPY(targetData, conversionBuffer, bytes);
}
if(conversionBuffer) {
free(conversionBuffer);
conversionBuffer = NULL;
}
gl_assert(active->index == original_id);
_glGPUStateMarkDirty(); _glGPUStateMarkDirty();
} }
@ -1764,13 +1667,15 @@ GLAPI void APIENTRY glColorTableEXT(GLenum target, GLenum internalFormat, GLsize
gl_assert(sourceStride > -1); gl_assert(sourceStride > -1);
TextureConversionFunc convert = _determineConversion( TextureConversionFunc convert;
int ret = _determineConversion(
INTERNAL_PALETTE_FORMAT, INTERNAL_PALETTE_FORMAT,
format, format,
type type,
&convert
); );
if(!convert) { if(ret < 0) {
_glKosThrowError(GL_INVALID_OPERATION, __func__); _glKosThrowError(GL_INVALID_OPERATION, __func__);
return; return;
} }

View File

@ -310,7 +310,7 @@ __BEGIN_DECLS
#define GL_4_BYTES 0x1409 #define GL_4_BYTES 0x1409
/* ErrorCode */ /* ErrorCode */
#define GL_NO_ERROR 0 #define GL_NO_ERROR ((GLenum) 0)
#define GL_INVALID_ENUM 0x0500 #define GL_INVALID_ENUM 0x0500
#define GL_INVALID_VALUE 0x0501 #define GL_INVALID_VALUE 0x0501
#define GL_INVALID_OPERATION 0x0502 #define GL_INVALID_OPERATION 0x0502
@ -371,6 +371,31 @@ __BEGIN_DECLS
#define GL_RGBA 0x1908 #define GL_RGBA 0x1908
#define GL_LUMINANCE 0x1909 #define GL_LUMINANCE 0x1909
#define GL_LUMINANCE_ALPHA 0x190A #define GL_LUMINANCE_ALPHA 0x190A
#define GL_R3_G3_B2 0x2A10
#define GL_ALPHA4 0x803B
#define GL_ALPHA8 0x803C
#define GL_ALPHA12 0x803D
#define GL_ALPHA16 0x803E
#define GL_LUMINANCE4 0x803F
#define GL_LUMINANCE8 0x8040
#define GL_LUMINANCE12 0x8041
#define GL_LUMINANCE16 0x8042
#define GL_LUMINANCE4_ALPHA4 0x8043
#define GL_LUMINANCE6_ALPHA2 0x8044
#define GL_LUMINANCE8_ALPHA8 0x8045
#define GL_LUMINANCE12_ALPHA4 0x8046
#define GL_LUMINANCE12_ALPHA12 0x8047
#define GL_LUMINANCE16_ALPHA16 0x8048
#define GL_INTENSITY4 0x804A
#define GL_INTENSITY8 0x804B
#define GL_INTENSITY12 0x804C
#define GL_INTENSITY16 0x804D
#define GL_BGRA 0x80E1 #define GL_BGRA 0x80E1
#define GL_INTENSITY 0x8049 #define GL_INTENSITY 0x8049
#define GL_RGB4 0x804F #define GL_RGB4 0x804F
@ -387,6 +412,14 @@ __BEGIN_DECLS
#define GL_RGBA12 0x805A #define GL_RGBA12 0x805A
#define GL_RGBA16 0x805B #define GL_RGBA16 0x805B
#define GL_R8 0x8229
#define GL_RG8 0x822B
#define GL_RG 0x8227
#define GL_R16 0x822A
#define GL_RG16 0x822C
#define GL_COMPRESSED_RED 0x8225
#define GL_COMPRESSED_RG 0x8226
/* Polygons */ /* Polygons */
#define GL_POINT 0x1B00 #define GL_POINT 0x1B00
#define GL_LINE 0x1B01 #define GL_LINE 0x1B01

View File

@ -87,7 +87,7 @@ GLAPI void APIENTRY glKosInitConfig(GLdcConfig* config);
*/ */
GLAPI void APIENTRY glKosInitEx(GLdcConfig* config); GLAPI void APIENTRY glKosInitEx(GLdcConfig* config);
GLAPI void APIENTRY glKosSwapBuffers(); GLAPI void APIENTRY glKosSwapBuffers();
GLAPI void APIENTRY glKosShutdown();
/* /*
* CUSTOM EXTENSION multiple_shared_palette_KOS * CUSTOM EXTENSION multiple_shared_palette_KOS
@ -186,12 +186,28 @@ GLAPI void APIENTRY glKosSwapBuffers();
/* Memory allocation extension (GL_KOS_texture_memory_management) */ /* Memory allocation extension (GL_KOS_texture_memory_management) */
GLAPI GLvoid APIENTRY glDefragmentTextureMemory_KOS(void); GLAPI GLvoid APIENTRY glDefragmentTextureMemory_KOS(void);
/* glGet extensions */
#define GL_FREE_TEXTURE_MEMORY_KOS 0xEF3D #define GL_FREE_TEXTURE_MEMORY_KOS 0xEF3D
#define GL_USED_TEXTURE_MEMORY_KOS 0xEF3E #define GL_USED_TEXTURE_MEMORY_KOS 0xEF3E
#define GL_FREE_CONTIGUOUS_TEXTURE_MEMORY_KOS 0xEF3F #define GL_FREE_CONTIGUOUS_TEXTURE_MEMORY_KOS 0xEF3F
//for palette internal format (glfcConfig) //for palette internal format (glfcConfig)
#define GL_RGB565_KOS 0xEF40 #define GL_RGB565_KOS 0xEF40
#define GL_ARGB4444_KOS 0xEF41
#define GL_ARGB1555_KOS 0xEF42
#define GL_RGB565_TWID_KOS 0xEF43
#define GL_ARGB4444_TWID_KOS 0xEF44
#define GL_ARGB1555_TWID_KOS 0xEF45
#define GL_COLOR_INDEX8_TWID_KOS 0xEF46
#define GL_COLOR_INDEX4_TWID_KOS 0xEF47
#define GL_RGB_TWID_KOS 0xEF48
#define GL_RGBA_TWID_KOS 0xEF49
/* glGet extensions */
#define GL_TEXTURE_INTERNAL_FORMAT_KOS 0xEF50
/* If enabled, will twiddle texture uploads where possible */
#define GL_TEXTURE_TWIDDLE_KOS 0xEF51
__END_DECLS __END_DECLS

View File

@ -56,11 +56,12 @@ void LoadGLTextures() {
glTexImage2D(GL_TEXTURE_2D, 0, 3, image1->sizeX, image1->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, image1->data); glTexImage2D(GL_TEXTURE_2D, 0, 3, image1->sizeX, image1->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, image1->data);
free(image1); free(image1);
}; }
/* A general OpenGL initialization function. Sets all of the initial parameters. */ /* A general OpenGL initialization function. Sets all of the initial parameters. */
void InitGL(int Width, int Height) // We call this right after our OpenGL window is created. void InitGL(int Width, int Height) // We call this right after our OpenGL window is created.
{ {
glEnable(GL_TEXTURE_TWIDDLE_KOS);
LoadGLTextures(); LoadGLTextures();
glEnable(GL_TEXTURE_2D); glEnable(GL_TEXTURE_2D);
glClearColor(0.0f, 0.0f, 0.0f, 0.0f); // This Will Clear The Background Color To Black glClearColor(0.0f, 0.0f, 0.0f, 0.0f); // This Will Clear The Background Color To Black
@ -74,7 +75,7 @@ void InitGL(int Width, int Height) // We call this right after our OpenG
gluPerspective(45.0f,(GLfloat)Width/(GLfloat)Height,0.1f,100.0f); // Calculate The Aspect Ratio Of The Window gluPerspective(45.0f,(GLfloat)Width/(GLfloat)Height,0.1f,100.0f); // Calculate The Aspect Ratio Of The Window
glMatrixMode(GL_MODELVIEW); glMatrixMode(GL_MODELVIEW);
} }
/* The function called when our window is resized (which shouldn't happen, because we're fullscreen) */ /* The function called when our window is resized (which shouldn't happen, because we're fullscreen) */

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,47 @@
#include <stddef.h>
#include <time.h>
#include <stdio.h>
#ifdef __DREAMCAST__
#include <kos.h>
#endif
#include <GL/gl.h>
#include <GL/glkos.h>
#include "image.h"
int main(int argc, char* argv[]) {
(void) argc;
(void) argv;
glKosInit();
glClearColor(0.5f, 0.0f, 0.5f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glKosSwapBuffers();
GLuint texture_id = 0;
glGenTextures(1, &texture_id);
glBindTexture(GL_TEXTURE_2D, texture_id);
time_t start = time(NULL);
time_t end = start;
int counter = 0;
fprintf(stderr, "Starting test run...\n");
while((end - start) < 5) {
glTexImage2D(
GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, header_data
);
++counter;
end = time(NULL);
}
fprintf(stderr, "Called glTexImage2D %d times (%.4f per call)\n", counter, (float)(end - start) / (float)(counter));
return 0;
}

26
tests/CMakeLists.txt Normal file
View File

@ -0,0 +1,26 @@
FILE(GLOB GL_TESTS ${CMAKE_CURRENT_SOURCE_DIR}/test_*.h)
INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR})
SET(TEST_GENERATOR_BIN ${CMAKE_SOURCE_DIR}/tools/test_generator.py)
SET(TEST_MAIN_FILENAME ${CMAKE_CURRENT_BINARY_DIR}/main.cpp)
ADD_CUSTOM_COMMAND(
OUTPUT ${TEST_MAIN_FILENAME}
COMMAND ${TEST_GENERATOR_BIN} --output ${TEST_MAIN_FILENAME} ${TEST_FILES} ${GL_TESTS}
DEPENDS ${TEST_FILES} ${GL_TESTS} ${TEST_GENERATOR_BIN}
)
add_executable(gldc_tests ${TEST_FILES} ${TEST_SOURCES} ${TEST_MAIN_FILENAME})
target_link_libraries(gldc_tests GLdc)
if(!PLATFORM_DREAMCAST)
set_target_properties(
gldc_tests
PROPERTIES
COMPILE_OPTIONS "-m32"
LINK_OPTIONS "-m32"
)
endif()

74
tests/test_glteximage2d.h Normal file
View File

@ -0,0 +1,74 @@
#include "tools/test.h"
#include <stdint.h>
#include <GL/gl.h>
#include <GL/glkos.h>
class TexImage2DTests : public test::TestCase {
public:
uint8_t image_data[8 * 8 * 4] = {0};
void set_up() {
glKosInit();
/* Init image data so each texel RGBA value matches the
* position in the array */
for(int i = 0; i < 8 * 8 * 4; i += 4) {
image_data[i + 0] = i;
image_data[i + 1] = i;
image_data[i + 2] = i;
image_data[i + 3] = i;
}
}
void tear_down() {
glKosShutdown();
}
void test_rgb_to_rgb565() {
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 8, 8, 0, GL_RGB, GL_UNSIGNED_BYTE, image_data);
assert_equal(glGetError(), GL_NO_ERROR);
GLint internalFormat;
glGetIntegerv(GL_TEXTURE_INTERNAL_FORMAT_KOS, &internalFormat);
assert_equal(internalFormat, GL_RGB565_KOS);
}
void test_rgb_to_rgb565_twiddle() {
glEnable(GL_TEXTURE_TWIDDLE_KOS);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 8, 8, 0, GL_RGB, GL_UNSIGNED_BYTE, image_data);
glDisable(GL_TEXTURE_TWIDDLE_KOS);
assert_equal(glGetError(), GL_NO_ERROR);
GLint internalFormat;
glGetIntegerv(GL_TEXTURE_INTERNAL_FORMAT_KOS, &internalFormat);
assert_equal(internalFormat, GL_RGB565_TWID_KOS);
}
void test_rgba_to_argb4444() {
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 8, 8, 0, GL_RGBA, GL_UNSIGNED_BYTE, image_data);
assert_equal(glGetError(), GL_NO_ERROR);
GLint internalFormat;
glGetIntegerv(GL_TEXTURE_INTERNAL_FORMAT_KOS, &internalFormat);
assert_equal(internalFormat, GL_ARGB4444_KOS);
}
void test_rgba_to_argb4444_twiddle() {
glEnable(GL_TEXTURE_TWIDDLE_KOS);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 8, 8, 0, GL_RGBA, GL_UNSIGNED_BYTE, image_data);
glDisable(GL_TEXTURE_TWIDDLE_KOS);
assert_equal(glGetError(), GL_NO_ERROR);
GLint internalFormat;
glGetIntegerv(GL_TEXTURE_INTERNAL_FORMAT_KOS, &internalFormat);
assert_equal(internalFormat, GL_ARGB4444_TWID_KOS);
}
};

445
tools/test.h Normal file
View File

@ -0,0 +1,445 @@
/* * Copyright (c) 2011-2017 Luke Benstead https://simulant-engine.appspot.com
*
* This file is part of Simulant.
*
* Simulant is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Simulant is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Simulant. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <vector>
#include <functional>
#include <stdexcept>
#include <iostream>
#include <sstream>
#include <algorithm>
#include <fstream>
#include <memory>
#define assert_equal(expected, actual) _assert_equal((expected), (actual), __FILE__, __LINE__)
#define assert_not_equal(expected, actual) _assert_not_equal((expected), (actual), __FILE__, __LINE__)
#define assert_false(actual) _assert_false((actual), __FILE__, __LINE__)
#define assert_true(actual) _assert_true((actual), __FILE__, __LINE__)
#define assert_close(expected, actual, difference) _assert_close((expected), (actual), (difference), __FILE__, __LINE__)
#define assert_is_null(actual) _assert_is_null((actual), __FILE__, __LINE__)
#define assert_is_not_null(actual) _assert_is_not_null((actual), __FILE__, __LINE__)
#define assert_raises(exception, func) _assert_raises<exception>((func), __FILE__, __LINE__)
#define assert_items_equal(expected, actual) _assert_items_equal((actual), (expected), __FILE__, __LINE__)
#define not_implemented() _not_implemented(__FILE__, __LINE__)
namespace test {
class StringFormatter {
public:
StringFormatter(const std::string& templ):
templ_(templ) { }
struct Counter {
Counter(uint32_t c): c(c) {}
uint32_t c;
};
template<typename T>
std::string format(T value) {
std::stringstream ss;
ss << value;
return _do_format(0, ss.str());
}
template<typename T>
std::string format(Counter count, T value) {
std::stringstream ss;
ss << value;
return _do_format(count.c, ss.str());
}
template<typename T, typename... Args>
std::string format(T value, const Args&... args) {
std::stringstream ss;
ss << value;
return StringFormatter(_do_format(0, ss.str())).format(Counter(1), args...);
}
template<typename T, typename... Args>
std::string format(Counter count, T value, const Args&... args) {
std::stringstream ss;
ss << value;
return StringFormatter(_do_format(count.c, ss.str())).format(Counter(count.c + 1), args...);
}
std::string _do_format(uint32_t counter, const std::string& value) {
std::stringstream ss; // Can't use to_string on all platforms
ss << counter;
const std::string to_replace = "{" + ss.str() + "}";
std::string output = templ_;
auto replace = [](std::string& str, const std::string& from, const std::string& to) -> bool {
size_t start_pos = str.find(from);
if(start_pos == std::string::npos)
return false;
str.replace(start_pos, from.length(), to);
return true;
};
replace(output, to_replace, value);
return output;
}
private:
std::string templ_;
};
class StringSplitter {
public:
StringSplitter(const std::string& str):
str_(str) {
}
std::vector<std::string> split() {
std::vector<std::string> result;
std::string buffer;
for(auto c: str_) {
if(c == '\n') {
if(!buffer.empty()) {
result.push_back(buffer);
buffer.clear();
}
} else {
buffer.push_back(c);
}
}
if(!buffer.empty()) {
result.push_back(buffer);
}
return result;
}
private:
std::string str_;
};
typedef StringFormatter _Format;
class AssertionError : public std::logic_error {
public:
AssertionError(const std::string& what):
std::logic_error(what),
file(""),
line(-1) {
}
AssertionError(const std::pair<std::string, int> file_and_line, const std::string& what):
std::logic_error(what),
file(file_and_line.first),
line(file_and_line.second) {
}
~AssertionError() noexcept (true) {
}
std::string file;
int line;
};
class NotImplementedError: public std::logic_error {
public:
NotImplementedError(const std::string& file, int line):
std::logic_error(_Format("Not implemented at {0}:{1}").format(file, line)) {}
};
class SkippedTestError: public std::logic_error {
public:
SkippedTestError(const std::string& reason):
std::logic_error(reason) {
}
};
class TestCase {
public:
virtual ~TestCase() {}
virtual void set_up() {}
virtual void tear_down() {}
void skip_if(const bool& flag, const std::string& reason) {
if(flag) { throw test::SkippedTestError(reason); }
}
template<typename T, typename U>
void _assert_equal(T expected, U actual, std::string file, int line) {
if(expected != actual) {
auto file_and_line = std::make_pair(file, line);
throw test::AssertionError(file_and_line, test::_Format("{0} does not match {1}").format(actual, expected));
}
}
template<typename T, typename U>
void _assert_not_equal(T lhs, U rhs, std::string file, int line) {
if(lhs == (T) rhs) {
auto file_and_line = std::make_pair(file, line);
throw test::AssertionError(file_and_line, test::_Format("{0} should not match {1}").format(lhs, rhs));
}
}
template<typename T>
void _assert_true(T actual, std::string file, int line) {
if(!bool(actual)) {
auto file_and_line = std::make_pair(file, line);
throw test::AssertionError(file_and_line, test::_Format("{0} is not true").format(bool(actual) ? "true" : "false"));
}
}
template<typename T>
void _assert_false(T actual, std::string file, int line) {
if(bool(actual)) {
auto file_and_line = std::make_pair(file, line);
throw test::AssertionError(file_and_line, test::_Format("{0} is not false").format(bool(actual) ? "true" : "false"));
}
}
template<typename T, typename U, typename V>
void _assert_close(T expected, U actual, V difference, std::string file, int line) {
if(actual < expected - difference ||
actual > expected + difference) {
auto file_and_line = std::make_pair(file, line);
throw test::AssertionError(file_and_line, test::_Format("{0} is not close enough to {1}").format(actual, expected));
}
}
template<typename T>
void _assert_is_null(T* thing, std::string file, int line) {
if(thing != nullptr) {
auto file_and_line = std::make_pair(file, line);
throw test::AssertionError(file_and_line, "Pointer was not NULL");
}
}
template<typename T>
void _assert_is_not_null(T* thing, std::string file, int line) {
if(thing == nullptr) {
auto file_and_line = std::make_pair(file, line);
throw test::AssertionError(file_and_line, "Pointer was unexpectedly NULL");
}
}
template<typename T, typename Func>
void _assert_raises(Func func, std::string file, int line) {
try {
func();
auto file_and_line = std::make_pair(file, line);
throw test::AssertionError(file_and_line, test::_Format("Expected exception ({0}) was not thrown").format(typeid(T).name()));
} catch(T& e) {}
}
template<typename T, typename U>
void _assert_items_equal(const T& lhs, const U& rhs, std::string file, int line) {
auto file_and_line = std::make_pair(file, line);
if(lhs.size() != rhs.size()) {
throw test::AssertionError(file_and_line, "Containers are not the same length");
}
for(auto item: lhs) {
if(std::find(rhs.begin(), rhs.end(), item) == rhs.end()) {
throw test::AssertionError(file_and_line, test::_Format("Container does not contain {0}").format(item));
}
}
}
void _not_implemented(std::string file, int line) {
throw test::NotImplementedError(file, line);
}
};
class TestRunner {
public:
template<typename T, typename U>
void register_case(std::vector<U> methods, std::vector<std::string> names) {
std::shared_ptr<TestCase> instance = std::make_shared<T>();
instances_.push_back(instance); //Hold on to it
for(std::string name: names) {
names_.push_back(name);
}
for(U& method: methods) {
std::function<void()> func = std::bind(method, dynamic_cast<T*>(instance.get()));
tests_.push_back([=]() {
instance->set_up();
func();
instance->tear_down();
});
}
}
int32_t run(const std::string& test_case, const std::string& junit_output="") {
int failed = 0;
int skipped = 0;
int ran = 0;
int crashed = 0;
auto new_tests = tests_;
auto new_names = names_;
if(!test_case.empty()) {
new_tests.clear();
new_names.clear();
for(uint32_t i = 0; i < names_.size(); ++i) {
if(names_[i].find(test_case) == 0) {
new_tests.push_back(tests_[i]);
new_names.push_back(names_[i]);
}
}
}
std::cout << std::endl << "Running " << new_tests.size() << " tests" << std::endl << std::endl;
std::vector<std::string> junit_lines;
junit_lines.push_back("<testsuites>\n");
std::string klass = "";
for(std::function<void ()> test: new_tests) {
std::string name = new_names[ran];
std::string this_klass(name.begin(), name.begin() + name.find_first_of(":"));
bool close_klass = ran == (int) new_tests.size() - 1;
if(this_klass != klass) {
if(!klass.empty()) {
junit_lines.push_back(" </testsuite>\n");
}
klass = this_klass;
junit_lines.push_back(" <testsuite name=\"" + this_klass + "\">\n");
}
try {
junit_lines.push_back(" <testcase name=\"" + new_names[ran] + "\">\n");
std::string output = " " + new_names[ran];
for(int i = output.length(); i < 76; ++i) {
output += " ";
}
std::cout << output;
test();
std::cout << "\033[32m" << " OK " << "\033[0m" << std::endl;
junit_lines.push_back(" </testcase>\n");
} catch(test::NotImplementedError& e) {
std::cout << "\033[34m" << " SKIPPED" << "\033[0m" << std::endl;
++skipped;
junit_lines.push_back(" </testcase>\n");
} catch(test::SkippedTestError& e) {
std::cout << "\033[34m" << " SKIPPED" << "\033[0m" << std::endl;
++skipped;
junit_lines.push_back(" </testcase>\n");
} catch(test::AssertionError& e) {
std::cout << "\033[33m" << " FAILED " << "\033[0m" << std::endl;
std::cout << " " << e.what() << std::endl;
if(!e.file.empty()) {
std::cout << " " << e.file << ":" << e.line << std::endl;
std::ifstream ifs(e.file);
if(ifs.good()) {
std::string buffer;
std::vector<std::string> lines;
while(std::getline(ifs, buffer)) {
lines.push_back(buffer);
}
int line_count = lines.size();
if(line_count && e.line <= line_count) {
std::cout << lines.at(e.line - 1) << std::endl << std::endl;
}
}
}
++failed;
junit_lines.push_back(" <failure message=\"" + std::string(e.what()) + "\"/>\n");
junit_lines.push_back(" </testcase>\n");
} catch(std::exception& e) {
std::cout << "\033[31m" << " EXCEPT " << std::endl;
std::cout << " " << e.what() << "\033[0m" << std::endl;
++crashed;
junit_lines.push_back(" <failure message=\"" + std::string(e.what()) + "\"/>\n");
junit_lines.push_back(" </testcase>\n");
}
std::cout << "\033[0m";
++ran;
if(close_klass) {
junit_lines.push_back(" </testsuite>\n");
}
}
junit_lines.push_back("</testsuites>\n");
if(!junit_output.empty()) {
FILE* f = fopen(junit_output.c_str(), "wt");
if(f) {
for(auto& line: junit_lines) {
fwrite(line.c_str(), sizeof(char), line.length(), f);
}
}
fclose(f);
}
std::cout << "-----------------------" << std::endl;
if(!failed && !crashed && !skipped) {
std::cout << "All tests passed" << std::endl << std::endl;
} else {
if(skipped) {
std::cout << skipped << " tests skipped";
}
if(failed) {
if(skipped) {
std::cout << ", ";
}
std::cout << failed << " tests failed";
}
if(crashed) {
if(failed) {
std::cout << ", ";
}
std::cout << crashed << " tests crashed";
}
std::cout << std::endl << std::endl;
}
return failed + crashed;
}
private:
std::vector<std::shared_ptr<TestCase>> instances_;
std::vector<std::function<void()> > tests_;
std::vector<std::string> names_;
};
} // test

212
tools/test_generator.py Executable file
View File

@ -0,0 +1,212 @@
#!/usr/bin/env python3
import argparse
import re
import sys
parser = argparse.ArgumentParser(description="Generate C++ unit tests")
parser.add_argument("--output", type=str, nargs=1, help="The output source file for the generated test main()", required=True)
parser.add_argument("test_files", type=str, nargs="+", help="The list of C++ files containing your tests")
parser.add_argument("--verbose", help="Verbose logging", action="store_true", default=False)
CLASS_REGEX = r"\s*class\s+(\w+)\s*([\:|,]\s*(?:public|private|protected)\s+[\w|::]+\s*)*"
TEST_FUNC_REGEX = r"void\s+(?P<func_name>test_\S[^\(]+)\(\s*(void)?\s*\)"
INCLUDE_TEMPLATE = "#include \"%(file_path)s\""
REGISTER_TEMPLATE = """
runner->register_case<%(class_name)s>(
std::vector<void (%(class_name)s::*)()>({%(members)s}),
{%(names)s}
);"""
MAIN_TEMPLATE = """
#include <functional>
#include <memory>
#include <map>
#include "tools/test.h"
%(includes)s
std::map<std::string, std::string> parse_args(int argc, char* argv[]) {
std::map<std::string, std::string> ret;
for(int i = 1; i < argc; ++i) {
std::string arg = argv[i];
auto eq = arg.find('=');
if(eq != std::string::npos && arg[0] == '-' && arg[1] == '-') {
auto key = std::string(arg.begin(), arg.begin() + eq);
auto value = std::string(arg.begin() + eq + 1, arg.end());
ret[key] = value;
} else if(arg[0] == '-' && arg[1] == '-') {
auto key = arg;
if(i < (argc - 1)) {
auto value = argv[++i];
ret[key] = value;
} else {
ret[key] = "";
}
} else {
ret[arg] = ""; // Positional, not key=value
}
}
return ret;
}
int main(int argc, char* argv[]) {
auto runner = std::make_shared<test::TestRunner>();
auto args = parse_args(argc, argv);
std::string junit_xml;
auto junit_xml_it = args.find("--junit-xml");
if(junit_xml_it != args.end()) {
junit_xml = junit_xml_it->second;
std::cout << " Outputting junit XML to: " << junit_xml << std::endl;
args.erase(junit_xml_it);
}
std::string test_case;
if(args.size()) {
test_case = args.begin()->first;
}
%(registrations)s
return runner->run(test_case, junit_xml);
}
"""
VERBOSE = False
def log_verbose(message):
if VERBOSE:
print(message)
def find_tests(files):
subclasses = []
# First pass, find all class definitions
for path in files:
with open(path, "rt") as f:
source_file_data = f.read().replace("\r\n", "").replace("\n", "")
while True:
match = re.search(CLASS_REGEX, source_file_data)
if not match:
break
class_name = match.group().split(":")[0].replace("class", "").strip()
try:
parents = match.group().split(":", 1)[1]
except IndexError:
pass
else:
parents = [ x.strip() for x in parents.split(",") ]
parents = [
x.replace("public", "").replace("private", "").replace("protected", "").strip()
for x in parents
]
subclasses.append((path, class_name, parents, []))
log_verbose("Found: %s" % str(subclasses[-1]))
start = match.end()
# Find the next opening brace
while source_file_data[start] in (' ', '\t'):
start += 1
start -= 1
end = start
if source_file_data[start+1] == '{':
class_data = []
brace_counter = 1
for i in range(start+2, len(source_file_data)):
class_data.append(source_file_data[i])
if class_data[-1] == '{': brace_counter += 1
if class_data[-1] == '}': brace_counter -= 1
if not brace_counter:
end = i
break
class_data = "".join(class_data)
while True:
match = re.search(TEST_FUNC_REGEX, class_data)
if not match:
break
subclasses[-1][-1].append(match.group('func_name'))
class_data = class_data[match.end():]
source_file_data = source_file_data[end:]
# Now, simplify the list by finding all potential superclasses, and then keeping any classes
# that subclass them.
test_case_subclasses = []
i = 0
while i < len(subclasses):
subclass_names = [x.rsplit("::")[-1] for x in subclasses[i][2]]
# If this subclasses TestCase, or it subclasses any of the already found testcase subclasses
# then add it to the list
if "TestCase" in subclass_names or "SimulantTestCase" in subclass_names or any(x[1] in subclasses[i][2] for x in test_case_subclasses):
if subclasses[i] not in test_case_subclasses:
test_case_subclasses.append(subclasses[i])
i = 0 # Go back to the start, as we may have just found another parent class
continue
i += 1
log_verbose("\n".join([str(x) for x in test_case_subclasses]))
return test_case_subclasses
def main():
global VERBOSE
args = parser.parse_args()
VERBOSE = args.verbose
testcases = find_tests(args.test_files)
includes = "\n".join([ INCLUDE_TEMPLATE % { 'file_path' : x } for x in set([y[0] for y in testcases]) ])
registrations = []
for path, class_name, superclasses, funcs in testcases:
BIND_TEMPLATE = "&%(class_name)s::%(func)s"
members = ", ".join([ BIND_TEMPLATE % { 'class_name' : class_name, 'func' : x } for x in funcs ])
names = ", ".join([ '"%s::%s"' % (class_name, x) for x in funcs ])
registrations.append(REGISTER_TEMPLATE % { 'class_name' : class_name, 'members' : members, 'names' : names })
registrations = "\n".join(registrations)
final = MAIN_TEMPLATE % {
'registrations' : registrations,
'includes' : includes
}
open(args.output[0], "w").write(final)
return 0
if __name__ == '__main__':
sys.exit(main())