diff --git a/CMakeLists.txt b/CMakeLists.txt index f95b5e2..8db1bad 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,6 +2,7 @@ cmake_minimum_required(VERSION 3.13) project(OpenGX VERSION 0.1) set(CMAKE_C_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) set(TARGET opengx) @@ -15,9 +16,10 @@ add_library(${TARGET} STATIC src/image_DXT.c src/image_DXT.h src/opengx.h - src/pixels.c + src/pixels.cpp src/pixels.h src/state.h + src/texture.c src/utils.h ) set_target_properties(${TARGET} PROPERTIES diff --git a/src/gc_gl.c b/src/gc_gl.c index 630a1ed..840af6d 100644 --- a/src/gc_gl.c +++ b/src/gc_gl.c @@ -47,9 +47,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "call_lists.h" #include "debug.h" -#include "image_DXT.h" #include "opengx.h" -#include "pixels.h" #include "state.h" #include "utils.h" @@ -603,56 +601,6 @@ void glCullFace(GLenum mode) glDisable(GL_CULL_FACE); } -void glBindTexture(GLenum target, GLuint texture) -{ - if (texture < 0 || texture >= _MAX_GL_TEX) - return; - - HANDLE_CALL_LIST(BIND_TEXTURE, target, texture); - - // If the texture has been initialized (data!=0) then load it to GX reg 0 - if (TEXTURE_IS_RESERVED(texture_list[texture])) { - glparamstate.glcurtex = texture; - - if (TEXTURE_IS_USED(texture_list[texture])) - GX_LoadTexObj(&texture_list[glparamstate.glcurtex].texobj, GX_TEXMAP0); - } -} - -void glDeleteTextures(GLsizei n, const GLuint *textures) -{ - const GLuint *texlist = textures; - GX_DrawDone(); - while (n-- > 0) { - int i = *texlist++; - if (!(i < 0 || i >= _MAX_GL_TEX)) { - void *data = GX_GetTexObjData(&texture_list[i].texobj); - if (data != 0) - free(MEM_PHYSICAL_TO_K0(data)); - memset(&texture_list[i], 0, sizeof(texture_list[i])); - } - } -} - -void glGenTextures(GLsizei n, GLuint *textures) -{ - GLuint *texlist = textures; - int i; - for (i = 0; i < _MAX_GL_TEX && n > 0; i++) { - if (!TEXTURE_IS_RESERVED(texture_list[i])) { - GXTexObj *texobj = &texture_list[i].texobj; - GX_InitTexObj(texobj, NULL, 0, 0, 0, GX_REPEAT, GX_REPEAT, 0); - TEXTURE_RESERVE(texture_list[i]); - *texlist++ = i; - n--; - } - } - - if (n > 0) { - warning("Could not allocate %d textures", n); - set_error(GL_OUT_OF_MEMORY); - } -} void glBegin(GLenum mode) { // Just discard all the data! @@ -1332,294 +1280,6 @@ void glLineWidth(GLfloat width) GX_SetLineWidth((unsigned int)(width * 16), GX_TO_ZERO); } -void glTexEnvf(GLenum target, GLenum pname, GLfloat param) -{ - /* For the time being, all the parameters we support take integer values */ - glTexEnvi(target, pname, param); -} - -void glTexEnvi(GLenum target, GLenum pname, GLint param) -{ - HANDLE_CALL_LIST(TEX_ENV, target, pname, param); - - switch (pname) { - case GL_TEXTURE_ENV_MODE: - glparamstate.texture_env_mode = param; - break; - } -} - -static uint32_t calc_memory(int w, int h, uint32_t format) -{ - return GX_GetTexBufferSize(w, h, format, GX_FALSE, 0); -} - -// Returns the number of bytes required to store a texture with all its bitmaps -static uint32_t calc_tex_size(int w, int h, uint32_t format) -{ - return GX_GetTexBufferSize(w, h, format, GX_TRUE, 20); -} -// Deduce the original texture size given the current size and level -static int calc_original_size(int level, int s) -{ - while (level > 0) { - s = 2 * s; - level--; - } - return s; -} -// Given w,h,level,and bpp, returns the offset to the mipmap at level "level" -static uint32_t calc_mipmap_offset(int level, int w, int h, uint32_t format) -{ - return level > 0 ? - GX_GetTexBufferSize(w, h, format, GX_TRUE, level - 1) : 0; -} - -void glTexImage1D(GLenum target, GLint level, GLint internalFormat, - GLsizei width, GLint border, GLenum format, GLenum type, - const GLvoid *pixels) -{ - glTexImage2D(GL_TEXTURE_2D, level, internalFormat, width, 1, - border, format, type, pixels); -} - -void glTexImage2D(GLenum target, GLint level, GLint internalFormat, GLsizei width, GLsizei height, - GLint border, GLenum format, GLenum type, const GLvoid *data) -{ - - // Initial checks - if (!TEXTURE_IS_RESERVED(texture_list[glparamstate.glcurtex])) - return; - if (target != GL_TEXTURE_2D) - return; // FIXME Implement non 2D textures - - GX_DrawDone(); // Very ugly, we should have a list of used textures and only wait if we are using the curr tex. - // This way we are sure that we are not modifying a texture which is being drawn - - gltexture_ *currtex = &texture_list[glparamstate.glcurtex]; - - // Just simplify it a little ;) - if (internalFormat == GL_BGR) - internalFormat = GL_RGB; - else if (internalFormat == GL_BGRA) - internalFormat = GL_RGBA; - else if (internalFormat == GL_RGB4) - internalFormat = GL_RGB; - else if (internalFormat == GL_RGB5) - internalFormat = GL_RGB; - else if (internalFormat == GL_RGB8) - internalFormat = GL_RGB; - else if (internalFormat == 3) - internalFormat = GL_RGB; - else if (internalFormat == 4) - internalFormat = GL_RGBA; - - // Simplify but keep in mind the swapping - int needswap = 0; - if (format == GL_BGR) { - format = GL_RGB; - needswap = 1; - } - if (format == GL_BGRA) { - format = GL_RGBA; - needswap = 1; - } - - // Fallbacks for formats which we can't handle - if (internalFormat == GL_COMPRESSED_RGBA_ARB) - internalFormat = GL_RGBA; // Cannot compress RGBA! - - // Simplify and avoid stupid conversions (which waste space for no gain) - if (format == GL_RGB && internalFormat == GL_RGBA) - internalFormat = GL_RGB; - - if (format == GL_LUMINANCE_ALPHA && internalFormat == GL_RGBA) - internalFormat = GL_LUMINANCE_ALPHA; - - // TODO: Implement GL_LUMINANCE/GL_INTENSITY? and fallback from GL_LUM_ALPHA to GL_LUM instead of RGB (2bytes to 1byte) - // if (format == GL_LUMINANCE_ALPHA && internalFormat == GL_RGB) internalFormat = GL_LUMINANCE_ALPHA; - - int bytesperpixelinternal = 4, bytesperpixelsrc = 4; - uint32_t gx_format; - - if (format == GL_RGB) - bytesperpixelsrc = 3; - else if (format == GL_RGBA) - bytesperpixelsrc = 4; - else if (format == GL_LUMINANCE_ALPHA) - bytesperpixelsrc = 2; - - if (internalFormat == GL_RGB) { - bytesperpixelinternal = 2; - gx_format = GX_TF_RGB565; - } else if (internalFormat == GL_RGBA) { - bytesperpixelinternal = 4; - gx_format = GX_TF_RGBA8; - } else if (internalFormat == GL_LUMINANCE_ALPHA) { - bytesperpixelinternal = 2; - gx_format = GX_TF_IA8; - } else if (internalFormat == GL_LUMINANCE) { - bytesperpixelinternal = 1; - gx_format = GX_TF_I8; - } else if (internalFormat == GL_ALPHA) { - bytesperpixelinternal = 1; - /* GX_TF_A8 is not supported by Dolphin and it's not properly handed by - * a real Wii either. */ - gx_format = GX_TF_I8; - } else { - gx_format = GX_TF_CMPR; - } - - if (internalFormat == GL_COMPRESSED_RGB_ARB && format == GL_RGB) // Only compress on demand and non-alpha textures - bytesperpixelinternal = -2; // 0.5 bytes per pixel - - if (bytesperpixelinternal < 0 && (width < 8 || height < 8)) - return; // Cannot take compressed textures under 8x8 (4 blocks of 4x4, 32B) - - // We *may* need to delete and create a new texture, depending if the user wants to add some mipmap levels - // or wants to create a new texture from scratch - int wi = calc_original_size(level, width); - int he = calc_original_size(level, height); - - void *gx_data; - u16 gx_w, gx_h; - u8 curr_gx_format, wraps, wrapt, mipmap; - GX_GetTexObjAll(&currtex->texobj, &gx_data, &gx_w, &gx_h, - &curr_gx_format, &wraps, &wrapt, &mipmap); - if (gx_data) { - gx_data = MEM_PHYSICAL_TO_K0(gx_data); - } - float minlevel, maxlevel; - GX_GetTexObjLOD(&currtex->texobj, &minlevel, &maxlevel); - char onelevel = minlevel == 0.0; - - // Check if the texture has changed its geometry and proceed to delete it - // If the specified level is zero, create a onelevel texture to save memory - if (wi != gx_w || he != gx_h) { - if (gx_data != 0) - free(gx_data); - if (level == 0) { - uint32_t required_size = calc_memory(width, height, gx_format); - gx_data = memalign(32, required_size); - onelevel = 1; - } else { - uint32_t required_size = calc_tex_size(wi, he, gx_format); - gx_data = memalign(32, required_size); - onelevel = 0; - } - minlevel = level; - maxlevel = level; - gx_w = wi; - gx_h = he; - } - if (maxlevel < level) - maxlevel = level; - if (minlevel > level) - minlevel = level; - - if (onelevel == 1 && level != 0) { - // We allocated a onelevel texture (base level 0) but now - // we are uploading a non-zero level, so we need to create a mipmap capable buffer - // and copy the level zero texture - uint32_t tsize = calc_memory(wi, he, gx_format); - unsigned char *tempbuf = malloc(tsize); - if (!tempbuf) { - warning("Failed to allocate memory for texture mipmap (%d)", errno); - set_error(GL_OUT_OF_MEMORY); - return; - } - memcpy(tempbuf, gx_data, tsize); - free(gx_data); - - uint32_t required_size = calc_tex_size(wi, he, gx_format); - gx_data = memalign(32, required_size); - onelevel = 0; - - memcpy(gx_data, tempbuf, tsize); - free(tempbuf); - } - - // Inconditionally convert to 565 all inputs without alpha channel - // Alpha inputs may be stripped if the user specifies an alpha-free internal format - if (bytesperpixelinternal > 0) { - /* Add pixels on each direction so that the scrambling functions can - * read whole 4x4 (or 8x4) tiles without bound checking */ - unsigned char *tempbuf = malloc((width + 7) * (height + 3) * bytesperpixelinternal); - if (!tempbuf) { - warning("Failed to allocate memory for texture (%d)", errno); - set_error(GL_OUT_OF_MEMORY); - return; - } - - if (format == GL_RGB) { - _ogx_conv_rgb_to_rgb565(data, type, tempbuf, width, height); - } else if (format == GL_RGBA) { - if (internalFormat == GL_RGB) { - _ogx_conv_rgba_to_rgb565(data, type, tempbuf, width, height); - } else if (internalFormat == GL_RGBA) { - _ogx_conv_rgba_to_rgba32(data, type, tempbuf, width, height); - } else if (internalFormat == GL_LUMINANCE_ALPHA) { - _ogx_conv_rgba_to_luminance_alpha((unsigned char *)data, tempbuf, width, height); - } - } else if (format == GL_LUMINANCE_ALPHA) { - if (internalFormat == GL_RGB) { - // TODO - } else if (internalFormat == GL_LUMINANCE_ALPHA) { - _ogx_conv_luminance_alpha_to_ia8(data, type, tempbuf, width, height); - } - } else if (format == GL_ALPHA || format == GL_LUMINANCE) { - _ogx_conv_intensity_to_i8(data, type, tempbuf, width, height); - } - - // Swap R<->B if necessary - if (needswap && bytesperpixelinternal > 1 && - internalFormat != GL_LUMINANCE_ALPHA) { - if (bytesperpixelinternal == 4) - _ogx_swap_rgba(tempbuf, width * height); - else - _ogx_swap_rgb565((unsigned short *)tempbuf, width * height); - } - - // Calculate the offset and address of the mipmap - uint32_t offset = calc_mipmap_offset(level, gx_w, gx_h, gx_format); - unsigned char *dst_addr = gx_data; - dst_addr += offset; - - // Finally write to the dest. buffer scrambling the data - if (bytesperpixelinternal == 4) { - _ogx_scramble_4b(tempbuf, dst_addr, width, height); - } else if (bytesperpixelinternal == 2) { - _ogx_scramble_2b((unsigned short *)tempbuf, dst_addr, width, height); - } else if (bytesperpixelinternal == 1) { - _ogx_scramble_1b(tempbuf, dst_addr, width, height); - } - free(tempbuf); - - DCFlushRange(dst_addr, width * height * bytesperpixelinternal); - } else { - // Compressed texture - - // Calculate the offset and address of the mipmap - uint32_t offset = calc_mipmap_offset(level, gx_w, gx_h, gx_format); - unsigned char *dst_addr = gx_data; - dst_addr += offset; - - _ogx_convert_rgb_image_to_DXT1((unsigned char *)data, dst_addr, - width, height, needswap); - - DCFlushRange(dst_addr, calc_memory(width, height, gx_format)); - } - - // Slow but necessary! The new textures may be in the same region of some old cached textures - GX_InvalidateTexAll(); - - GX_InitTexObj(&currtex->texobj, gx_data, - gx_w, gx_h, gx_format, wraps, wrapt, GX_TRUE); - GX_InitTexObjLOD(&currtex->texobj, GX_LIN_MIP_LIN, GX_LIN_MIP_LIN, - minlevel, maxlevel, 0, GX_ENABLE, GX_ENABLE, GX_ANISO_1); - TEXTURE_RESERVE(*currtex); -} - void glColorMask(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha) { if ((red | green | blue | alpha) != 0) @@ -2631,47 +2291,6 @@ void glStencilMask(GLuint mask) {} // Should use Alpha testing to achieve simila void glShadeModel(GLenum mode) {} // In theory we don't have GX equivalent? void glHint(GLenum target, GLenum mode) {} -static unsigned char gcgl_texwrap_conv(GLint param) -{ - switch (param) { - case GL_MIRRORED_REPEAT: - return GX_MIRROR; - case GL_CLAMP: - return GX_CLAMP; - case GL_REPEAT: - default: - return GX_REPEAT; - }; -} - -void glTexParameterf(GLenum target, GLenum pname, GLfloat param) -{ - /* For the time being, all the parameters we support take integer values */ - glTexParameteri(target, pname, param); -} - -void glTexParameteri(GLenum target, GLenum pname, GLint param) -{ - if (target != GL_TEXTURE_2D) - return; - - gltexture_ *currtex = &texture_list[glparamstate.glcurtex]; - u8 wraps, wrapt; - - switch (pname) { - case GL_TEXTURE_WRAP_S: - wrapt = GX_GetTexObjWrapT(&currtex->texobj); - wraps = gcgl_texwrap_conv(param); - GX_InitTexObjWrapMode(&currtex->texobj, wraps, wrapt); - break; - case GL_TEXTURE_WRAP_T: - wraps = GX_GetTexObjWrapS(&currtex->texobj); - wrapt = gcgl_texwrap_conv(param); - GX_InitTexObjWrapMode(&currtex->texobj, wraps, wrapt); - break; - }; -} - // XXX: Need to finish glGets, important!!! void glGetIntegerv(GLenum pname, GLint *params) { diff --git a/src/opengx.h b/src/opengx.h index b406d62..3dbb678 100644 --- a/src/opengx.h +++ b/src/opengx.h @@ -32,6 +32,46 @@ POSSIBILITY OF SUCH DAMAGE. #ifndef OPENGX_H #define OPENGX_H +#include + +#ifdef __cplusplus +extern "C" { +#endif + void ogx_initialize(void); +/* This function can be called to register an optimized converter for the + * texture data (used in glTex*Image* functions). + * + * The "format" and "internal_format" parameter corresponds to the homonymous + * parameters passed to the glTexImage2D() function, and the "converter" + * parameter must be one of the ogx_fast_conv_* variables declared below. + * + * The following fast converters are already enabled by default: + * - ogx_fast_conv_RGB_RGB565; + * - ogx_fast_conv_RGBA_RGBA8; + * - ogx_fast_conv_Intensity_I8; + */ +void ogx_register_tex_conversion(GLenum format, GLenum internal_format, + uintptr_t converter); + +extern uintptr_t ogx_fast_conv_RGBA_I8; +extern uintptr_t ogx_fast_conv_RGBA_A8; +extern uintptr_t ogx_fast_conv_RGBA_IA8; +extern uintptr_t ogx_fast_conv_RGBA_RGB565; +extern uintptr_t ogx_fast_conv_RGBA_RGBA8; +extern uintptr_t ogx_fast_conv_RGB_I8; +extern uintptr_t ogx_fast_conv_RGB_IA8; +extern uintptr_t ogx_fast_conv_RGB_RGB565; +extern uintptr_t ogx_fast_conv_RGB_RGBA8; +extern uintptr_t ogx_fast_conv_LA_I8; +extern uintptr_t ogx_fast_conv_LA_A8; +extern uintptr_t ogx_fast_conv_LA_IA8; +extern uintptr_t ogx_fast_conv_Intensity_I8; +extern uintptr_t ogx_fast_conv_Alpha_A8; + +#ifdef __cplusplus +} // extern C +#endif + #endif /* OPENGX_H */ diff --git a/src/pixels.c b/src/pixels.c deleted file mode 100644 index 90e9d1f..0000000 --- a/src/pixels.c +++ /dev/null @@ -1,393 +0,0 @@ -/***************************************************************************** -Copyright (c) 2011 David Guillen Fandos (david@davidgf.net) -Copyright (c) 2024 Alberto Mardegan (mardy@users.sourceforge.net) -All rights reserved. - -Attention! Contains pieces of code from others such as Mesa and GRRLib - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. -3. Neither the name of copyright holders nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED -TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDERS OR CONTRIBUTORS -BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. -*****************************************************************************/ - -#include "pixels.h" - -#include "debug.h" -#include -#include -#include - -#define FLOAT_TO_BYTE(f) ((GLbyte)(f * 255.0) & 0xff) - -void _ogx_swap_rgba(unsigned char *pixels, int num_pixels) -{ - while (num_pixels--) { - unsigned char temp; - temp = pixels[0]; - pixels[0] = pixels[3]; - pixels[3] = temp; - pixels += 4; - } -} - -void _ogx_swap_rgb565(unsigned short *pixels, int num_pixels) -{ - while (num_pixels--) { - unsigned int b = *pixels & 0x1F; - unsigned int r = (*pixels >> 11) & 0x1F; - unsigned int g = (*pixels >> 5) & 0x3F; - *pixels++ = (b << 11) | (g << 5) | r; - } -} - -static void conv_rgba32_to_rgb565(const int8_t *src, void *dst, int numpixels) -{ - unsigned short *out = dst; - while (numpixels--) { - *out++ = ((src[0] & 0xF8) << 8) | ((src[1] & 0xFC) << 3) | ((src[2] >> 3)); - src += 4; - } -} - -static void conv_rgbaf_to_rgb565(const float *src, void *dst, int numpixels) -{ - unsigned short *out = dst; - while (numpixels--) { - *out++ = ((FLOAT_TO_BYTE(src[0]) & 0xF8) << 8) | - ((FLOAT_TO_BYTE(src[1]) & 0xFC) << 3) | - ((FLOAT_TO_BYTE(src[2]) >> 3)); - src += 4; - } -} - -// Discards alpha and fits the texture in 16 bits -void _ogx_conv_rgba_to_rgb565(const void *data, GLenum type, - void *dst, int width, int height) -{ - int numpixels = width * height; - switch (type) { - case GL_BYTE: - case GL_UNSIGNED_BYTE: - conv_rgba32_to_rgb565(data, dst, numpixels); - break; - case GL_FLOAT: - conv_rgbaf_to_rgb565(data, dst, numpixels); - break; - default: - warning("Unsupported texture format %d", type); - } -} - -static void conv_rgb24_to_rgb565(const uint8_t *src, void *dst, int numpixels) -{ - unsigned short *out = dst; - while (numpixels--) { - *out++ = ((src[0] & 0xF8) << 8) | ((src[1] & 0xFC) << 3) | ((src[2] >> 3)); - src += 3; - } -} - -static void conv_rgbf_to_rgb565(const float *src, void *dst, int numpixels) -{ - unsigned short *out = dst; - while (numpixels--) { - *out++ = ((FLOAT_TO_BYTE(src[0]) & 0xF8) << 8) | - ((FLOAT_TO_BYTE(src[1]) & 0xFC) << 3) | - ((FLOAT_TO_BYTE(src[2]) >> 3)); - src += 3; - } -} - -// Fits the texture in 16 bits -void _ogx_conv_rgb_to_rgb565(const void *data, GLenum type, - void *dst, int width, int height) -{ - int numpixels = width * height; - switch (type) { - case GL_BYTE: - case GL_UNSIGNED_BYTE: - conv_rgb24_to_rgb565(data, dst, numpixels); - break; - case GL_FLOAT: - conv_rgbf_to_rgb565(data, dst, numpixels); - break; - default: - warning("Unsupported texture format %d", type); - } -} - -static void conv_rgbaf_to_rgba32(const float *src, void *dst, int numpixels) -{ - uint32_t *out = dst; - while (numpixels--) { - *out++ = (FLOAT_TO_BYTE(src[0]) << 24) | - (FLOAT_TO_BYTE(src[1]) << 16) | - (FLOAT_TO_BYTE(src[2]) << 8) | - FLOAT_TO_BYTE(src[3]); - src += 4; - } -} - -void _ogx_conv_rgba_to_rgba32(const void *data, GLenum type, - void *dest, int width, int height) -{ - int numpixels = width * height; - switch (type) { - case GL_BYTE: - case GL_UNSIGNED_BYTE: - memcpy(dest, data, numpixels * 4); - break; - case GL_FLOAT: - conv_rgbaf_to_rgba32(data, dest, numpixels); - break; - default: - warning("Unsupported texture format %d", type); - } -} - -static void conv_intensityf_to_i8(const float *src, void *dst, int numpixels) -{ - uint32_t *out = dst; - while (numpixels--) { - *out++ = FLOAT_TO_BYTE(src[0]); - src++; - } -} - -void _ogx_conv_intensity_to_i8(const void *data, GLenum type, - void *dest, int width, int height) -{ - int numpixels = width * height; - switch (type) { - case GL_BYTE: - case GL_UNSIGNED_BYTE: - memcpy(dest, data, numpixels); - break; - case GL_FLOAT: - conv_intensityf_to_i8(data, dest, numpixels); - break; - default: - warning("Unsupported texture format %d", type); - } -} - -static void conv_laf_to_ia8(const float *src, void *dst, int numpixels) -{ - uint16_t *out = dst; - while (numpixels--) { - *out++ = (FLOAT_TO_BYTE(src[1]) << 8) | FLOAT_TO_BYTE(src[0]); - src += 2; - } -} - -void _ogx_conv_luminance_alpha_to_ia8(const void *data, GLenum type, - void *dest, int width, int height) -{ - int numpixels = width * height; - switch (type) { - case GL_BYTE: - case GL_UNSIGNED_BYTE: - memcpy(dest, data, numpixels * 2); - break; - case GL_FLOAT: - conv_laf_to_ia8(data, dest, numpixels); - break; - default: - warning("Unsupported texture format %d", type); - } -} - -// Converts color into luminance and saves alpha -void _ogx_conv_rgba_to_luminance_alpha(unsigned char *src, void *dst, - const unsigned int width, const unsigned int height) -{ - int numpixels = width * height; - unsigned char *out = dst; - while (numpixels--) { - int lum = ((int)src[0]) + ((int)src[1]) + ((int)src[2]); - lum = lum / 3; - *out++ = src[3]; - *out++ = lum; - src += 4; - } -} - -// 4x4 tile scrambling -/* 1b texel scrambling */ -void _ogx_scramble_1b(void *src, void *dst, int width, int height) -{ - uint64_t *s = src; - uint64_t *p = dst; - - int width_blocks = (width + 7) / 8; - for (int y = 0; y < height; y += 4) { - int rows = MIN(height - y, 4); - for (int x = 0; x < width_blocks; x++) { - for (int row = 0; row < rows; row++) { - *p++ = s[(y + row) * width_blocks + x]; - } - } - } -} - -// 2b texel scrambling -void _ogx_scramble_2b(unsigned short *src, void *dst, - const unsigned int width, const unsigned int height) -{ - unsigned int block; - unsigned int i; - unsigned char c; - unsigned short *p = (unsigned short *)dst; - - for (block = 0; block < height; block += 4) { - for (i = 0; i < width; i += 4) { - for (c = 0; c < 4; c++) { - *p++ = src[(block + c) * width + i]; - *p++ = src[(block + c) * width + i + 1]; - *p++ = src[(block + c) * width + i + 2]; - *p++ = src[(block + c) * width + i + 3]; - } - } - } -} - -// 4b texel scrambling -void _ogx_scramble_4b(unsigned char *src, void *dst, - const unsigned int width, const unsigned int height) -{ - unsigned int block; - unsigned int i; - unsigned char c; - unsigned char argb; - unsigned char *p = (unsigned char *)dst; - - for (block = 0; block < height; block += 4) { - for (i = 0; i < width; i += 4) { - for (c = 0; c < 4; c++) { - for (argb = 0; argb < 4; argb++) { - *p++ = src[((i + argb) + ((block + c) * width)) * 4 + 3]; - *p++ = src[((i + argb) + ((block + c) * width)) * 4]; - } - } - for (c = 0; c < 4; c++) { - for (argb = 0; argb < 4; argb++) { - *p++ = src[(((i + argb) + ((block + c) * width)) * 4) + 1]; - *p++ = src[(((i + argb) + ((block + c) * width)) * 4) + 2]; - } - } - } - } -} - -//// Image scaling for arbitrary size taken from Mesa 3D and adapted by davidgf //// - -void _ogx_scale_internal(int components, int widthin, int heightin, - const unsigned char *datain, - int widthout, int heightout, unsigned char *dataout) -{ - float x, lowx, highx, convx, halfconvx; - float y, lowy, highy, convy, halfconvy; - float xpercent, ypercent; - float percent; - /* Max components in a format is 4, so... */ - float totals[4]; - float area; - int i, j, k, yint, xint, xindex, yindex; - int temp; - - convy = (float)heightin / heightout; - convx = (float)widthin / widthout; - halfconvx = convx / 2; - halfconvy = convy / 2; - for (i = 0; i < heightout; i++) { - y = convy * (i + 0.5); - if (heightin > heightout) { - highy = y + halfconvy; - lowy = y - halfconvy; - } else { - highy = y + 0.5; - lowy = y - 0.5; - } - for (j = 0; j < widthout; j++) { - x = convx * (j + 0.5); - if (widthin > widthout) { - highx = x + halfconvx; - lowx = x - halfconvx; - } else { - highx = x + 0.5; - lowx = x - 0.5; - } - - /* - ** Ok, now apply box filter to box that goes from (lowx, lowy) - ** to (highx, highy) on input data into this pixel on output - ** data. - */ - totals[0] = totals[1] = totals[2] = totals[3] = 0.0; - area = 0.0; - - y = lowy; - yint = floor(y); - while (y < highy) { - yindex = (yint + heightin) % heightin; - if (highy < yint + 1) { - ypercent = highy - y; - } else { - ypercent = yint + 1 - y; - } - - x = lowx; - xint = floor(x); - - while (x < highx) { - xindex = (xint + widthin) % widthin; - if (highx < xint + 1) { - xpercent = highx - x; - } else { - xpercent = xint + 1 - x; - } - - percent = xpercent * ypercent; - area += percent; - temp = (xindex + (yindex * widthin)) * components; - for (k = 0; k < components; k++) { - totals[k] += datain[temp + k] * percent; - } - - xint++; - x = xint; - } - yint++; - y = yint; - } - - temp = (j + (i * widthout)) * components; - for (k = 0; k < components; k++) { - /* totals[] should be rounded in the case of enlarging an RGB - * ramp when the type is 332 or 4444 - */ - dataout[temp + k] = (totals[k] + 0.5) / area; - } - } - } -} diff --git a/src/pixels.cpp b/src/pixels.cpp new file mode 100644 index 0000000..7acfa89 --- /dev/null +++ b/src/pixels.cpp @@ -0,0 +1,745 @@ +/***************************************************************************** +Copyright (c) 2011 David Guillen Fandos (david@davidgf.net) +Copyright (c) 2024 Alberto Mardegan (mardy@users.sourceforge.net) +All rights reserved. + +Attention! Contains pieces of code from others such as Mesa and GRRLib + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. Neither the name of copyright holders nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDERS OR CONTRIBUTORS +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +*****************************************************************************/ + +#include "pixels.h" + +#include "debug.h" +#include "opengx.h" + +#include +#include +#include +#include + +#define MAX_FAST_CONVERSIONS 8 + +typedef void (FastConverter)(const void *data, GLenum type, int width, int height, + void *dst, int x, int y, int dstpitch); +static struct FastConversion { + GLenum gl_format; + u8 gx_format; + union { + uintptr_t id; + FastConverter *func; + } conv; +} s_registered_conversions[MAX_FAST_CONVERSIONS] = { + { GL_RGB, GX_TF_RGB565, ogx_fast_conv_RGB_RGB565 }, + { GL_RGBA, GX_TF_RGBA8, ogx_fast_conv_RGBA_RGBA8 }, + { GL_LUMINANCE, GX_TF_I8, ogx_fast_conv_Intensity_I8 }, + 0, +}; + +static inline uint8_t luminance_from_rgb(uint8_t r, uint8_t g, uint8_t b) +{ + /* TODO: fix this, the three components do not have the same weight to the + * human eye. Use a formula from + * https://songho.ca/dsp/luminance/luminance.html + */ + int luminance = int(r) + int(g) + int(b); + return luminance / 3; +} + +struct Texel { + virtual void setColor(GXColor color) = 0; + virtual void store (void *texture, int x, int y, int pitch) = 0; + virtual int pitch_for_width(int width) = 0; +}; + +struct TexelRGBA8: public Texel { + static constexpr bool has_rgb = true; + static constexpr bool has_alpha = true; + static constexpr bool has_luminance = false; + + TexelRGBA8() = default; + TexelRGBA8(uint8_t r, uint8_t g, uint8_t b, uint8_t a): + r(r), g(g), b(b), a(a) {} + + void setColor(GXColor c) override { + r = c.r; g = c.g; b = c.b; a = c.a; + } + + void store(void *texture, int x, int y, int pitch) override { + int block_x = x / 4; + int block_y = y / 4; + uint8_t *d = static_cast(texture) + + block_y * pitch * 4 + block_x * 64 + (y % 4) * 8 + (x % 4) * 2; + d[0] = a; + d[1] = r; + d[32] = g; + d[33] = b; + } + + static inline int compute_pitch(int width) { + /* texel are in pairs of 4x4 blocks, each element 2 bytes wide */ + return ((width + 3) / 4) * 16; + } + + int pitch_for_width(int width) override { return compute_pitch(width); } + + uint8_t r; + uint8_t g; + uint8_t b; + uint8_t a; +}; + +struct Texel16: public Texel { + Texel16() = default; + Texel16(uint16_t value): word(value) {} + void store(void *texture, int x, int y, int pitch) override { + int block_x = x / 4; + int block_y = y / 4; + uint8_t *d = static_cast(texture) + + block_y * pitch * 4 + block_x * 32 + (y % 4) * 8 + (x % 4) * 2; + d[0] = byte0(); + d[1] = byte1(); + } + + static inline int compute_pitch(int width) { + /* texel are in 4x4 blocks, each element 2 bytes wide */ + return ((width + 3) / 4) * 8; + } + + int pitch_for_width(int width) override { return compute_pitch(width); } + + uint8_t byte0() const { return word >> 8; } + uint8_t byte1() const { return word & 0xff; } + + uint16_t word; +}; + +struct TexelIA8: public Texel16 { + static constexpr bool has_rgb = false; + static constexpr bool has_alpha = true; + static constexpr bool has_luminance = true; + + TexelIA8() = default; + TexelIA8(uint8_t intensity, uint8_t alpha): + Texel16((alpha << 8) | intensity) {} + + void setColor(GXColor c) override { + int luminance = luminance_from_rgb(c.r, c.g, c.b); + word = (c.a << 8) | luminance; + } +}; + +struct TexelRGB565: public Texel16 { + static constexpr bool has_alpha = false; + static constexpr bool has_rgb = true; + + TexelRGB565() = default; + TexelRGB565(uint8_t r, uint8_t g, uint8_t b): + Texel16(((r & 0xf8) << 8) | + ((g & 0xfc) << 3) | + ((b & 0xf8) >> 3)) {} + + void setColor(GXColor c) override { + word = ((c.r & 0xf8) << 8) | + ((c.g & 0xfc) << 3) | + ((c.b & 0xf8) >> 3); + } +}; + +struct Texel8: public Texel { + Texel8() = default; + Texel8(uint8_t value): + value(value) {} + + void store(void *texture, int x, int y, int pitch) override { + int block_x = x / 8; + int block_y = y / 4; + uint8_t *d = static_cast(texture) + + block_y * pitch * 4 + block_x * 32 + (y % 4) * 8 + (x % 8); + d[0] = value; + } + + static inline int compute_pitch(int width) { + /* texel are in 8x4 blocks, each element 1 bytes wide */ + return ((width + 7) / 8) * 8; + } + + int pitch_for_width(int width) override { return compute_pitch(width); } + + uint8_t value; +}; + +struct TexelI8: public Texel8 { + static constexpr bool has_rgb = false; + static constexpr bool has_alpha = false; + static constexpr bool has_luminance = true; + + using Texel8::Texel8; + void setColor(GXColor c) override { + value = luminance_from_rgb(c.r, c.g, c.b); + } +}; + +struct TexelA8: public Texel8 { + static constexpr bool has_rgb = false; + static constexpr bool has_alpha = true; + static constexpr bool has_luminance = false; + + using Texel8::Texel8; + void setColor(GXColor c) override { + value = c.a; + } +}; + +template static inline uint8_t component(T value); +template <> inline uint8_t component(uint8_t value) { return value; } +template <> inline uint8_t component(uint16_t value) { return value >> 8; } +template <> inline uint8_t component(uint32_t value) { return value >> 24; } +template <> inline uint8_t component(float value) { + return (uint8_t)(int(std::clamp(value, 0.0f, 1.0f) * 255.0f) & 0xff); +} + +/* This template class is used to perform reading of a pixel and storing it in + * the desired texture format in a single go. It does that in about 1/5th of + * the time the generic algorithm (on Dolphin the difference is even bigger, up + * to 1/10th), at the expense of a larger code size. + * + * Note that this class does not support packed pixel formats: each pixel + * component must be at least one byte wide. + */ +template +struct DataReader { + typedef T type; + + static inline int pitch_for_width(int width) { + return width * NUM_ELEMS * sizeof(T); + } + + static inline const T *row_ptr(const void *data, int y, int pitch) { + return static_cast(data) + y * pitch / sizeof(T); + } + + template + static inline const T *read(const T *data, P &pixel) { + if constexpr (NUM_ELEMS == 4 && P::has_rgb && P::has_alpha) { + pixel = P(component(data[0]), + component(data[1]), + component(data[2]), + component(data[3])); + } else if constexpr (NUM_ELEMS >= 3 && P::has_rgb && !P::has_alpha) { + pixel = P(component(data[0]), + component(data[1]), + component(data[2])); + } else if constexpr (NUM_ELEMS == 3 && P::has_rgb && P::has_alpha) { + pixel = P(component(data[0]), + component(data[1]), + component(data[2]), + 1.0f); + } else { + /* TODO (maybe) support converting from intensity to RGB */ + uint8_t luminance, alpha; + if constexpr (P::has_luminance) { + if constexpr (NUM_ELEMS >= 3) { + luminance = luminance_from_rgb(component(data[0]), + component(data[1]), + component(data[2])); + } else if constexpr (NUM_ELEMS == 2) { + luminance = component(data[0]); + } else { // Just a single component in the source data + if constexpr (FORMAT == GL_LUMINANCE) { + luminance = component(data[0]); + } else { + luminance = 0; + } + } + } + + if constexpr (P::has_alpha) { + if constexpr (NUM_ELEMS == 4) { + alpha = component(data[3]); + } else if constexpr (FORMAT == GL_LUMINANCE_ALPHA) { + alpha = component(data[1]); + } else if constexpr (FORMAT == GL_ALPHA) { + alpha = component(data[0]); + } else { + alpha = 255; + } + } + + if constexpr (P::has_luminance && P::has_alpha) { + pixel = P(luminance, alpha); + } else if constexpr (P::has_luminance) { + pixel = P(luminance); + } else { /* Only alpha */ + pixel = P(alpha); + } + } + + return data + NUM_ELEMS; + } +}; + +template +using DataReaderRGBA = DataReader; + +template +using DataReaderRGB = DataReader; + +template +using DataReaderLA = DataReader; + +template +using DataReaderIntensity = DataReader; + +template +using DataReaderAlpha = DataReader; + +/* Base class for the generic reader: this is used as base class by the + * CompoundDataReader and the GenericDataReader classes below. + * + * Note that for the time being we assume the pitch to be the minimum required + * to store a row of pixels. + */ +struct DataReaderBase { + virtual GXColor read() = 0; +}; + +static const struct MasksPerType { + GLenum type; + char bytes; /* number of bytes per pixel */ + char rbits; /* bits of data for each component */ + char gbits; + char bbits; + char abits; + char roff; /* offsets (relative to memory layout, not registers */ + char goff; + char boff; + char aoff; +} s_masks_per_type[] = { + {GL_UNSIGNED_BYTE_3_3_2, 1, 3, 3, 2, 0, 0, 3, 6, 0 }, + {GL_UNSIGNED_BYTE_2_3_3_REV, 1, 3, 3, 2, 0, 5, 2, 0, 0 }, + {GL_UNSIGNED_SHORT_5_6_5, 2, 5, 6, 5, 0, 0, 5, 11, 0 }, + {GL_UNSIGNED_SHORT_5_6_5_REV, 2, 5, 6, 5, 0, 11, 5, 0, 0 }, + {GL_UNSIGNED_SHORT_4_4_4_4, 2, 4, 4, 4, 4, 0, 4, 8, 12 }, + {GL_UNSIGNED_SHORT_4_4_4_4_REV, 2, 4, 4, 4, 4, 12, 8, 4, 0 }, + {GL_UNSIGNED_SHORT_5_5_5_1, 2, 5, 5, 5, 1, 0, 5, 10, 15 }, + {GL_UNSIGNED_SHORT_1_5_5_5_REV, 2, 5, 5, 5, 1, 11, 6, 1, 0 }, + {GL_UNSIGNED_INT_8_8_8_8, 4, 8, 8, 8, 8, 0, 8, 16, 24 }, + {GL_UNSIGNED_INT_8_8_8_8_REV, 4, 8, 8, 8, 8, 24, 16, 8, 0 }, + {GL_UNSIGNED_INT_10_10_10_2, 4, 10, 10, 10, 2, 0, 10, 20, 30 }, + {GL_UNSIGNED_INT_2_10_10_10_REV, 4, 10, 10, 10, 2, 22, 12, 2, 0 }, + {0, } +}; + +/* This class handles reading of pixels stored in one of the formats listed + * above, where each pixel is packed in at most 32 bits. */ +struct CompoundDataReader: public DataReaderBase { + CompoundDataReader() = default; + CompoundDataReader(const void *data, GLenum format, GLenum type): + data(static_cast(data)), + format(format), + mask_data(*find_mask_per_type(type)) { + if (format == GL_BGR || format == GL_BGRA) { /* swap red and blue */ + char tmp = mask_data.roff; + mask_data.roff = mask_data.boff; + mask_data.boff = tmp; + } + rmask = compute_mask(mask_data.rbits, mask_data.roff); + gmask = compute_mask(mask_data.gbits, mask_data.goff); + bmask = compute_mask(mask_data.bbits, mask_data.boff); + amask = compute_mask(mask_data.abits, mask_data.aoff); + } + + static const MasksPerType *find_mask_per_type(GLenum type) { + for (int i = 0; s_masks_per_type[i].type != 0; i++) { + if (s_masks_per_type[i].type == type) { + return &s_masks_per_type[i]; + } + } + return nullptr; + } + + inline uint32_t compute_mask(int nbits, int offset) { + uint32_t mask = (1 << nbits) - 1; + return mask << (mask_data.bytes * 8 - (nbits + offset)); + } + + inline uint8_t read_component(uint32_t pixel, uint32_t mask, + int nbits, int offset) { + uint32_t value = pixel & mask; + int shift = mask_data.bytes * 8 - offset - 8; + uint8_t c = shift > 0 ? (value >> shift) : (value << -shift); + if (nbits < 8) { + c |= (c >> nbits); + } + return c; + } + + inline uint32_t read_pixel() const { + uint32_t pixel = 0; + for (int i = 0; i < 4; i++) { + if (i < mask_data.bytes) { + pixel <<= 8; + pixel |= data[n_read + i]; + } + } + return pixel; + } + + GXColor read() override { + uint32_t pixel = read_pixel(); + GXColor c; + c.r = read_component(pixel, rmask, mask_data.rbits, mask_data.roff); + c.g = read_component(pixel, gmask, mask_data.gbits, mask_data.goff); + c.b = read_component(pixel, bmask, mask_data.bbits, mask_data.boff); + if (mask_data.abits > 0) { + c.a = read_component(pixel, amask, mask_data.abits, mask_data.aoff); + } else { + c.a = 255; + } + n_read += mask_data.bytes; + return c; + } + + const char *data; + int n_read = 0; + uint32_t rmask; + uint32_t gmask; + uint32_t bmask; + uint32_t amask; + MasksPerType mask_data; + GLenum format; +}; + +static const struct ComponentsPerFormat { + GLenum format; + char components_per_pixel; + char component_index[4]; /* component role (0=red, ..., 3=alpha) */ +} s_components_per_format[] = { + { GL_RGBA, 4, { 0, 1, 2, 3 }}, + { GL_BGRA, 4, { 2, 1, 0, 3 }}, + { GL_RGB, 3, { 0, 1, 2 }}, + { GL_BGR, 3, { 2, 1, 0 }}, + { GL_LUMINANCE_ALPHA, 2, { 0, 3 }}, + { GL_INTENSITY, 1, { 0 }}, + { GL_LUMINANCE, 1, { 0 }}, + { GL_RED, 1, { 0 }}, + { GL_GREEN, 1, { 1 }}, + { GL_BLUE, 1, { 2 }}, + { GL_ALPHA, 1, { 3 }}, + { 0, } +}; + +/* This is a generic class to read pixels whose components are expressed by 8, + * 16, 32 bit wide integers or by 32 bit floats. + */ +template +struct GenericDataReader: public DataReaderBase { + GenericDataReader(const void *data, GLenum format, GLenum type): + data(static_cast(data)), format(format), + component_data(*find_component_data(format)) {} + + static const ComponentsPerFormat *find_component_data(GLenum format) { + for (int i = 0; s_components_per_format[i].format != 0; i++) { + if (s_components_per_format[i].format == format) { + return &s_components_per_format[i]; + } + } + return nullptr; + } + + int pitch_for_width(int width) { + return width * component_data.components_per_pixel * sizeof(T); + } + + GXColor read() override { + union { + uint8_t components[4]; + GXColor c; + } pixel = { 0, 0, 0, 255 }; + + const ComponentsPerFormat &cd = component_data; + for (int i = 0; i < cd.components_per_pixel; i++) { + pixel.components[cd.component_index[i]] = component(data[n_read++]); + } + + /* Some formats require a special handling */ + if (cd.format == GL_INTENSITY || + cd.format == GL_LUMINANCE || + cd.format == GL_LUMINANCE_ALPHA) { + pixel.c.g = pixel.c.b = pixel.c.r; + if (cd.format == GL_INTENSITY) pixel.c.a = pixel.c.r; + } + + return pixel.c; + } + + const T *data; + GLenum format; + int n_read = 0; + ComponentsPerFormat component_data; +}; + +template static inline +void load_texture_typed(const void *src, int width, int height, + void *dest, int x, int y, int dstpitch) +{ + // TODO: add alignment options + using DataType = typename READER::type; + + int srcpitch = READER::pitch_for_width(width); + + for (int ry = 0; ry < height; ry++) { + const DataType *srcline = READER::row_ptr(src, ry, srcpitch); + for (int rx = 0; rx < width; rx++) { + TEXEL texel; + srcline = READER::read(srcline, texel); + texel.store(dest, rx + x, ry + y, dstpitch); + } + } +} + +template typename READERBASE, typename TEXEL> static inline +void load_texture(const void *data, GLenum type, int width, int height, + void *dst, int x, int y, int dstpitch) +{ + using Texel = TEXEL; + + switch (type) { + case GL_BYTE: + case GL_UNSIGNED_BYTE: + load_texture_typed,Texel>(data, width, height, + dst, x, y, dstpitch); + break; + case GL_FLOAT: + load_texture_typed,Texel>(data, width, height, + dst, x, y, dstpitch); + break; + default: + warning("Unsupported texture format %d", type); + } +} + +void _ogx_bytes_to_texture(const void *data, GLenum format, GLenum type, + int width, int height, + void *dst, uint32_t gx_format, + int x, int y, int dstpitch) +{ + /* Accelerate the most common transformations by using the specialized + * readers. We only do this for some transformations, since every + * instantiation of the template takes some space, and the number of + * possible combinations is polynomial. + */ + for (int i = 0; i < MAX_FAST_CONVERSIONS; i++) { + const FastConversion &c = s_registered_conversions[i]; + if (c.gl_format == 0) break; + + if (c.gl_format == format && c.gx_format == gx_format) { + c.conv.func(data, type, width, height, dst, x, y, dstpitch); + return; + } + } + + /* Here starts the code for the generic converter. We start by selecting + * the proper Texel subclass for the given GX texture format, then we + * select the reader based on the GL type parameter, and then we do the + * conversion pixel by pixel, by using GXColor as intermediate format. + * + * We use std::variant so that we can safely construct our objects on the + * stack. */ + std::variant< + TexelRGBA8, + TexelRGB565, + TexelIA8, + TexelI8, + TexelA8 + > texel_v; + Texel *texel; + + std::variant< + CompoundDataReader, + GenericDataReader, + GenericDataReader, + GenericDataReader, + GenericDataReader + > reader_v; + DataReaderBase *reader; + + switch (gx_format) { + case GX_TF_RGBA8: + texel_v = TexelRGBA8(); + texel = &std::get(texel_v); + break; + case GX_TF_RGB565: + texel_v = TexelRGB565(); + texel = &std::get(texel_v); + break; + case GX_TF_IA8: + texel_v = TexelIA8(); + texel = &std::get(texel_v); + break; + case GX_TF_I8: + texel_v = TexelI8(); + texel = &std::get(texel_v); + break; + case GX_TF_A8: + texel_v = TexelA8(); + texel = &std::get(texel_v); + break; + } + + switch (type) { + case GL_UNSIGNED_BYTE: + reader_v = GenericDataReader(data, format, type); + reader = &std::get>(reader_v); + break; + case GL_UNSIGNED_SHORT: + reader_v = GenericDataReader(data, format, type); + reader = &std::get>(reader_v); + break; + case GL_UNSIGNED_INT: + reader_v = GenericDataReader(data, format, type); + reader = &std::get>(reader_v); + break; + case GL_FLOAT: + reader_v = GenericDataReader(data, format, type); + reader = &std::get>(reader_v); + break; + case GL_UNSIGNED_BYTE_3_3_2: + case GL_UNSIGNED_BYTE_2_3_3_REV: + case GL_UNSIGNED_SHORT_5_6_5: + case GL_UNSIGNED_SHORT_5_6_5_REV: + case GL_UNSIGNED_SHORT_4_4_4_4: + case GL_UNSIGNED_SHORT_4_4_4_4_REV: + case GL_UNSIGNED_SHORT_5_5_5_1: + case GL_UNSIGNED_SHORT_1_5_5_5_REV: + case GL_UNSIGNED_INT_8_8_8_8: + case GL_UNSIGNED_INT_8_8_8_8_REV: + case GL_UNSIGNED_INT_10_10_10_2: + case GL_UNSIGNED_INT_2_10_10_10_REV: + reader_v = CompoundDataReader(data, format, type); + reader = &std::get(reader_v); + break; + default: + warning("Unknown texture data type %x\n", type); + } + + for (int ry = 0; ry < height; ry++) { + for (int rx = 0; rx < width; rx++) { + GXColor c = reader->read(); + texel->setColor(c); + texel->store(dst, x + rx, y + ry, dstpitch); + } + } +} + +int _ogx_pitch_for_width(uint32_t gx_format, int width) +{ + switch (gx_format) { + case GX_TF_RGBA8: + return TexelRGBA8::compute_pitch(width); + case GX_TF_RGB565: + case GX_TF_IA8: + return TexelRGB565::compute_pitch(width); + case GX_TF_I8: + case GX_TF_A8: + return TexelI8::compute_pitch(width); + default: + return -1; + } +} + +uint32_t _ogx_gl_format_to_gx(GLenum format) +{ + switch (format) { + case 3: + case GL_RGB: + case GL_BGR: + case GL_RGB4: + case GL_RGB5: + case GL_RGB8: + return GX_TF_RGB565; + case 4: + case GL_RGBA: + case GL_BGRA: + case GL_COMPRESSED_RGBA_ARB: /* No support for compressed alpha textures */ + return GX_TF_RGBA8; + case GL_LUMINANCE_ALPHA: return GX_TF_IA8; + case GL_LUMINANCE: return GX_TF_I8; + case GL_ALPHA: + /* Note, we won't be really passing this to GX */ + return GX_TF_A8; + default: + return GX_TF_CMPR; + } +} + +#define DEFINE_FAST_CONVERSION(reader, texel) \ + static void fast_conv_##reader##_##texel( \ + const void *data, GLenum type, int width, int height, \ + void *dst, int x, int y, int dstpitch) \ + { \ + load_texture( \ + data, type, width, height, dst, x, y, dstpitch); \ + } \ + uintptr_t ogx_fast_conv_##reader##_##texel = (uintptr_t)fast_conv_##reader##_##texel; + +/* Fast conversions marked by a star are enabled by default */ +DEFINE_FAST_CONVERSION(RGBA, I8) +DEFINE_FAST_CONVERSION(RGBA, A8) +DEFINE_FAST_CONVERSION(RGBA, IA8) +DEFINE_FAST_CONVERSION(RGBA, RGB565) +DEFINE_FAST_CONVERSION(RGBA, RGBA8) // * +DEFINE_FAST_CONVERSION(RGB, I8) +DEFINE_FAST_CONVERSION(RGB, IA8) +DEFINE_FAST_CONVERSION(RGB, RGB565) // * +DEFINE_FAST_CONVERSION(RGB, RGBA8) +DEFINE_FAST_CONVERSION(LA, I8) +DEFINE_FAST_CONVERSION(LA, A8) +DEFINE_FAST_CONVERSION(LA, IA8) +DEFINE_FAST_CONVERSION(Intensity, I8) // * +DEFINE_FAST_CONVERSION(Alpha, A8) + +void ogx_register_tex_conversion(GLenum format, GLenum internal_format, + uintptr_t converter) +{ + uint8_t gx_format = _ogx_gl_format_to_gx(internal_format); + int i; + for (i = 0; i < MAX_FAST_CONVERSIONS; i++) { + if (s_registered_conversions[i].gl_format == 0) break; + } + + if (i >= MAX_FAST_CONVERSIONS) { + /* Nothing especially bad happens, we'll just use the slower + * conversion. But print a warning in any case. */ + warning("ogx_register_tex_conversion: reached max num of entries"); + return; + } + + FastConversion &c = s_registered_conversions[i]; + c.gl_format = format; + c.gx_format = gx_format; + c.conv.id = converter; +} diff --git a/src/pixels.h b/src/pixels.h index c09b00f..2d166cf 100644 --- a/src/pixels.h +++ b/src/pixels.h @@ -35,28 +35,21 @@ POSSIBILITY OF SUCH DAMAGE. #include -void _ogx_swap_rgba(unsigned char *pixels, int num_pixels); -void _ogx_swap_rgb565(unsigned short *pixels, int num_pixels); -void _ogx_conv_rgba_to_rgb565(const void *data, GLenum type, - void *dest, int width, int height); -void _ogx_conv_rgb_to_rgb565(const void *data, GLenum type, - void *dest, int width, int height); -void _ogx_conv_rgba_to_rgba32(const void *data, GLenum type, - void *dest, int width, int height); -void _ogx_conv_luminance_alpha_to_ia8(const void *data, GLenum type, - void *dest, int width, int height); -void _ogx_conv_rgba_to_luminance_alpha(unsigned char *src, void *dst, - const unsigned int width, const unsigned int height); -void _ogx_conv_intensity_to_i8(const void *data, GLenum type, - void *dest, int width, int height); -void _ogx_scramble_1b(void *src, void *dst, int width, int height); -void _ogx_scramble_2b(unsigned short *src, void *dst, - const unsigned int width, const unsigned int height); -void _ogx_scramble_4b(unsigned char *src, void *dst, - const unsigned int width, const unsigned int height); - -void _ogx_scale_internal(int components, int widthin, int heightin, - const unsigned char *datain, - int widthout, int heightout, unsigned char *dataout); +#ifdef __cplusplus +extern "C" { +#endif + +void _ogx_bytes_to_texture(const void *data, GLenum format, GLenum type, + int width, int height, + void *dst, uint32_t gx_format, + int x, int y, int dstpitch); + +int _ogx_pitch_for_width(uint32_t gx_format, int width); +uint32_t _ogx_gl_format_to_gx(GLenum format); + + +#ifdef __cplusplus +} // extern C +#endif #endif /* OPENGX_PIXELS_H */ diff --git a/src/state.h b/src/state.h index 884eb4f..d9a7ffc 100644 --- a/src/state.h +++ b/src/state.h @@ -51,12 +51,6 @@ typedef struct gltexture_ { GXTexObj texobj; } gltexture_; -#define TEXTURE_IS_USED(texture) \ - (GX_GetTexObjData(&texture.texobj) != NULL) -#define TEXTURE_IS_RESERVED(texture) \ - (GX_GetTexObjUserData(&texture.texobj) == (void*)1) -#define TEXTURE_RESERVE(texture) \ - GX_InitTexObjUserData(&(texture).texobj, (void*)1) typedef struct glparams_ { diff --git a/src/texture.c b/src/texture.c new file mode 100644 index 0000000..9a7bd2b --- /dev/null +++ b/src/texture.c @@ -0,0 +1,414 @@ +/***************************************************************************** +Copyright (c) 2011 David Guillen Fandos (david@davidgf.net) +Copyright (c) 2024 Alberto Mardegan (mardy@users.sourceforge.net) +All rights reserved. + +Attention! Contains pieces of code from others such as Mesa and GRRLib + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. Neither the name of copyright holders nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDERS OR CONTRIBUTORS +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +*****************************************************************************/ + +#include "call_lists.h" +#include "debug.h" +#include "image_DXT.h" +#include "pixels.h" +#include "state.h" +#include "utils.h" + +#include + +/* The GX API allow storing a void * for user data into the GXTexObj; but for + * now we don't need a pointer, just some bits of information. Let's store them + * in the space reserved for the pointer, then. */ +typedef union { + void *ptr; + struct { + unsigned is_reserved: 1; + unsigned is_alpha: 1; + } d; +} UserData; + +#define TEXTURE_USER_DATA(texobj) \ + ((UserData)GX_GetTexObjUserData(texobj)) +#define TEXTURE_IS_USED(texture) \ + (GX_GetTexObjData(&texture.texobj) != NULL) +#define TEXTURE_IS_RESERVED(texture) \ + (TEXTURE_USER_DATA(&texture.texobj).d.is_reserved) +#define TEXTURE_RESERVE(texture) \ + { \ + UserData ud = TEXTURE_USER_DATA(&(texture).texobj); \ + ud.d.is_reserved = 1; \ + GX_InitTexObjUserData(&(texture).texobj, ud.ptr); \ + } + +typedef struct { + void *texels; + uint16_t width, height; + uint8_t format, wraps, wrapt, mipmap; + uint8_t minlevel, maxlevel; + UserData ud; +} TextureInfo; + +static uint32_t calc_memory(int w, int h, uint32_t format) +{ + return GX_GetTexBufferSize(w, h, format, GX_FALSE, 0); +} + +// Returns the number of bytes required to store a texture with all its bitmaps +static uint32_t calc_tex_size(int w, int h, uint32_t format) +{ + return GX_GetTexBufferSize(w, h, format, GX_TRUE, 20); +} +// Deduce the original texture size given the current size and level +static int calc_original_size(int level, int s) +{ + while (level > 0) { + s = 2 * s; + level--; + } + return s; +} +// Given w,h,level,and bpp, returns the offset to the mipmap at level "level" +static uint32_t calc_mipmap_offset(int level, int w, int h, uint32_t format) +{ + return level > 0 ? + GX_GetTexBufferSize(w, h, format, GX_TRUE, level - 1) : 0; +} + +static unsigned char gcgl_texwrap_conv(GLint param) +{ + switch (param) { + case GL_MIRRORED_REPEAT: + return GX_MIRROR; + case GL_CLAMP: + return GX_CLAMP; + case GL_REPEAT: + default: + return GX_REPEAT; + }; +} + +static void texture_get_info(const GXTexObj *obj, TextureInfo *info) +{ + GX_GetTexObjAll(obj, &info->texels, + &info->width, &info->height, + &info->format, + &info->wraps, &info->wrapt, + &info->mipmap); + if (info->texels) { + info->texels = MEM_PHYSICAL_TO_K0(info->texels); + } + + float minlevel, maxlevel; + GX_GetTexObjLOD(obj, &minlevel, &maxlevel); + info->minlevel = minlevel; + info->maxlevel = maxlevel; + info->ud.ptr = GX_GetTexObjUserData(obj); + + /* Check if we wanted an alpha channel instead */ + if (info->format == GX_TF_I8 && info->ud.d.is_alpha) + info->format = GX_TF_A8; +} + +void glTexParameterf(GLenum target, GLenum pname, GLfloat param) +{ + /* For the time being, all the parameters we support take integer values */ + glTexParameteri(target, pname, param); +} + +void glTexParameteri(GLenum target, GLenum pname, GLint param) +{ + if (target != GL_TEXTURE_2D) + return; + + gltexture_ *currtex = &texture_list[glparamstate.glcurtex]; + u8 wraps, wrapt; + + switch (pname) { + case GL_TEXTURE_WRAP_S: + wrapt = GX_GetTexObjWrapT(&currtex->texobj); + wraps = gcgl_texwrap_conv(param); + GX_InitTexObjWrapMode(&currtex->texobj, wraps, wrapt); + break; + case GL_TEXTURE_WRAP_T: + wraps = GX_GetTexObjWrapS(&currtex->texobj); + wrapt = gcgl_texwrap_conv(param); + GX_InitTexObjWrapMode(&currtex->texobj, wraps, wrapt); + break; + }; +} + +void glTexEnvf(GLenum target, GLenum pname, GLfloat param) +{ + /* For the time being, all the parameters we support take integer values */ + glTexEnvi(target, pname, param); +} + +void glTexEnvi(GLenum target, GLenum pname, GLint param) +{ + HANDLE_CALL_LIST(TEX_ENV, target, pname, param); + + switch (pname) { + case GL_TEXTURE_ENV_MODE: + glparamstate.texture_env_mode = param; + break; + } +} + +void glTexImage1D(GLenum target, GLint level, GLint internalFormat, + GLsizei width, GLint border, GLenum format, GLenum type, + const GLvoid *pixels) +{ + glTexImage2D(GL_TEXTURE_2D, level, internalFormat, width, 1, + border, format, type, pixels); +} + +static void update_texture(const void *data, int level, GLenum format, GLenum type, + int width, int height, + GXTexObj *obj, TextureInfo *ti, int x, int y) +{ + unsigned char *dst_addr = ti->texels; + // Inconditionally convert to 565 all inputs without alpha channel + // Alpha inputs may be stripped if the user specifies an alpha-free internal format + if (ti->format != GX_TF_CMPR) { + // Calculate the offset and address of the mipmap + uint32_t offset = calc_mipmap_offset(level, ti->width, ti->height, ti->format); + dst_addr += offset; + + int dstpitch = _ogx_pitch_for_width(ti->format, ti->width); + _ogx_bytes_to_texture(data, format, type, width, height, + dst_addr, ti->format, x, y, dstpitch); + /* GX_TF_A8 is not supported by Dolphin and it's not properly handed by + * a real Wii either. */ + if (ti->format == GX_TF_A8) { + ti->format = GX_TF_I8; + ti->ud.d.is_alpha = 1; /* Remember that we wanted alpha, though */ + } + } else { + // Compressed texture + if (x != 0 || y != 0 || ti->width != width) { + warning("Update of compressed textures not implemented!"); + return; + } + + // Calculate the offset and address of the mipmap + uint32_t offset = calc_mipmap_offset(level, ti->width, ti->height, ti->format); + dst_addr += offset; + + // Simplify but keep in mind the swapping + int needswap = 0; + if (format == GL_BGR) { + format = GL_RGB; + needswap = 1; + } + if (format == GL_BGRA) { + format = GL_RGBA; + needswap = 1; + } + + _ogx_convert_rgb_image_to_DXT1((unsigned char *)data, dst_addr, + width, height, needswap); + } + + DCFlushRange(dst_addr, calc_memory(width, height, ti->format)); + + // Slow but necessary! The new textures may be in the same region of some old cached textures + GX_InvalidateTexAll(); + + GX_InitTexObj(obj, ti->texels, + ti->width, ti->height, ti->format, ti->wraps, ti->wrapt, GX_TRUE); + GX_InitTexObjLOD(obj, GX_LIN_MIP_LIN, GX_LIN_MIP_LIN, + ti->minlevel, ti->maxlevel, 0, GX_ENABLE, GX_ENABLE, GX_ANISO_1); + GX_InitTexObjUserData(obj, ti->ud.ptr); +} + +void glTexImage2D(GLenum target, GLint level, GLint internalFormat, GLsizei width, GLsizei height, + GLint border, GLenum format, GLenum type, const GLvoid *data) +{ + + // Initial checks + if (!TEXTURE_IS_RESERVED(texture_list[glparamstate.glcurtex])) + return; + if (target != GL_TEXTURE_2D) + return; // FIXME Implement non 2D textures + + GX_DrawDone(); // Very ugly, we should have a list of used textures and only wait if we are using the curr tex. + // This way we are sure that we are not modifying a texture which is being drawn + + gltexture_ *currtex = &texture_list[glparamstate.glcurtex]; + + // Simplify and avoid stupid conversions (which waste space for no gain) + if (format == GL_RGB && internalFormat == GL_RGBA) + internalFormat = GL_RGB; + + if (format == GL_LUMINANCE_ALPHA && internalFormat == GL_RGBA) + internalFormat = GL_LUMINANCE_ALPHA; + + uint32_t gx_format = _ogx_gl_format_to_gx(internalFormat); + if (gx_format == GX_TF_CMPR && (width < 8 || height < 8)) + return; // Cannot take compressed textures under 8x8 (4 blocks of 4x4, 32B) + + // We *may* need to delete and create a new texture, depending if the user wants to add some mipmap levels + // or wants to create a new texture from scratch + int wi = calc_original_size(level, width); + int he = calc_original_size(level, height); + + TextureInfo ti; + texture_get_info(&currtex->texobj, &ti); + ti.format = gx_format; + ti.ud.d.is_reserved = 1; + char onelevel = ti.minlevel == 0.0; + + // Check if the texture has changed its geometry and proceed to delete it + // If the specified level is zero, create a onelevel texture to save memory + if (wi != ti.width || he != ti.height) { + if (ti.texels != 0) + free(ti.texels); + if (level == 0) { + uint32_t required_size = calc_memory(width, height, ti.format); + ti.texels = memalign(32, required_size); + onelevel = 1; + } else { + uint32_t required_size = calc_tex_size(wi, he, ti.format); + ti.texels = memalign(32, required_size); + onelevel = 0; + } + ti.minlevel = level; + ti.maxlevel = level; + ti.width = wi; + ti.height = he; + } + if (ti.maxlevel < level) + ti.maxlevel = level; + if (ti.minlevel > level) + ti.minlevel = level; + + if (onelevel == 1 && level != 0) { + // We allocated a onelevel texture (base level 0) but now + // we are uploading a non-zero level, so we need to create a mipmap capable buffer + // and copy the level zero texture + uint32_t tsize = calc_memory(wi, he, ti.format); + unsigned char *tempbuf = malloc(tsize); + if (!tempbuf) { + warning("Failed to allocate memory for texture mipmap (%d)", errno); + set_error(GL_OUT_OF_MEMORY); + return; + } + memcpy(tempbuf, ti.texels, tsize); + free(ti.texels); + + uint32_t required_size = calc_tex_size(wi, he, ti.format); + ti.texels = memalign(32, required_size); + onelevel = 0; + + memcpy(ti.texels, tempbuf, tsize); + free(tempbuf); + } + + update_texture(data, level, format, type, width, height, + &currtex->texobj, &ti, 0, 0); +} + +void glTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, + GLsizei width, GLsizei height, GLenum format, GLenum type, + const GLvoid *data) +{ + if (!TEXTURE_IS_USED(texture_list[glparamstate.glcurtex])) { + set_error(GL_INVALID_OPERATION); + return; + } + + if (target != GL_TEXTURE_2D) { + warning("glTexSubImage2D with target 0x%04x not supported", target); + return; + } + + gltexture_ *currtex = &texture_list[glparamstate.glcurtex]; + + TextureInfo ti; + texture_get_info(&currtex->texobj, &ti); + if (level > ti.maxlevel) { + /* OpenGL does not specify this as an error, so we should probably + * handle this by allocating new mipmap levels as needed. but let's + * leave it as a TODO for now. */ + warning("glTexSubImage2D called with level %d when max is %d", + level, ti.maxlevel); + return; + } + + update_texture(data, level, format, type, width, height, + &currtex->texobj, &ti, xoffset, yoffset); +} + +void glBindTexture(GLenum target, GLuint texture) +{ + if (texture < 0 || texture >= _MAX_GL_TEX) + return; + + HANDLE_CALL_LIST(BIND_TEXTURE, target, texture); + + // If the texture has been initialized (data!=0) then load it to GX reg 0 + if (TEXTURE_IS_RESERVED(texture_list[texture])) { + glparamstate.glcurtex = texture; + + if (TEXTURE_IS_USED(texture_list[texture])) + GX_LoadTexObj(&texture_list[glparamstate.glcurtex].texobj, GX_TEXMAP0); + } +} + +void glDeleteTextures(GLsizei n, const GLuint *textures) +{ + const GLuint *texlist = textures; + GX_DrawDone(); + while (n-- > 0) { + int i = *texlist++; + if (!(i < 0 || i >= _MAX_GL_TEX)) { + void *data = GX_GetTexObjData(&texture_list[i].texobj); + if (data != 0) + free(MEM_PHYSICAL_TO_K0(data)); + memset(&texture_list[i], 0, sizeof(texture_list[i])); + } + } +} + +void glGenTextures(GLsizei n, GLuint *textures) +{ + GLuint *texlist = textures; + int i; + for (i = 0; i < _MAX_GL_TEX && n > 0; i++) { + if (!TEXTURE_IS_RESERVED(texture_list[i])) { + GXTexObj *texobj = &texture_list[i].texobj; + GX_InitTexObj(texobj, NULL, 0, 0, 0, GX_REPEAT, GX_REPEAT, 0); + TEXTURE_RESERVE(texture_list[i]); + *texlist++ = i; + n--; + } + } + + if (n > 0) { + warning("Could not allocate %d textures", n); + set_error(GL_OUT_OF_MEMORY); + } +}