Avoid copies when converting textures

This commit is contained in:
Luke Benstead 2026-05-03 18:47:58 +01:00
parent d85b8e515c
commit 57ab4b60a6
3 changed files with 89 additions and 108 deletions

View File

@ -1,7 +1,5 @@
#include <float.h>
#include <dc/sq.h>
#include "../platform.h"
#include "sh4.h"

View File

@ -442,7 +442,7 @@ GLfloat* _glGetLightModelSceneAmbient();
LightSource* _glLightAt(GLuint i);
#define MAX_GLDC_TEXTURE_UNITS 2
#define MAX_GLDC_LIGHTS 8
#define MAX_GLDC_LIGHTS 4
#define AMBIENT_MASK 1
#define DIFFUSE_MASK 2

View File

@ -42,12 +42,6 @@ static GLboolean TEXTURE_TWIDDLE_ENABLED = GL_FALSE;
static void* ALLOC_BASE = NULL;
static size_t ALLOC_SIZE = 0;
static struct TwiddleTable {
int32_t width;
int32_t height;
int32_t* table;
} TWIDDLE_TABLE = {0, 0, NULL};
static void calc_twiddle_factors(uint32_t w, uint32_t h, uint32_t* maskX, uint32_t* maskY) {
*maskX = 0;
*maskY = 0;
@ -71,36 +65,30 @@ static void calc_twiddle_factors(uint32_t w, uint32_t h, uint32_t* maskX, uint32
}
}
void build_twiddle_table(int32_t w, int32_t h) {
free(TWIDDLE_TABLE.table);
TWIDDLE_TABLE.table = (int32_t*) malloc(w * h * sizeof(int32_t));
TWIDDLE_TABLE.width = w;
TWIDDLE_TABLE.height = h;
/* Given a texel at (x, y), returns the twiddled linear index.
maskX and maskY must be computed via calc_twiddle_factors(w, h, &maskX, &maskY).
int32_t idx = 0;
uint32_t idxX = 0, idxY = 0, maskX, maskY;
calc_twiddle_factors(w, h, &maskX, &maskY);
The masks encode which bit positions in the result belong to X and Y.
Bit n of x goes into the nth set bit of maskX; bit n of y goes into the
nth set bit of maskY. */
static inline uint32_t twid_compute_index(uint32_t x, uint32_t y, uint32_t maskX, uint32_t maskY) {
uint32_t result = 0;
uint32_t tx = x, ty = y;
for (int32_t y = 0; y < h; y++) {
idxX = 0;
for (int32_t x = 0; x < w; x++) {
TWIDDLE_TABLE.table[idx++] = idxX | idxY;
idxX = (idxX - maskX) & maskX;
/* Walk through each bit position, placing x/y bits into mask positions */
for (uint32_t bit = 0; (1u << bit) <= (maskX | maskY); bit++) {
uint32_t pos = 1u << bit;
if (maskX & pos) {
result |= (tx & 1) << bit;
tx >>= 1;
}
if (maskY & pos) {
result |= (ty & 1) << bit;
ty >>= 1;
}
idxY = (idxY - maskY) & maskY;
}
}
static void twid_prepare_table(uint32_t w, uint32_t h) {
if(TWIDDLE_TABLE.width != w || TWIDDLE_TABLE.height != h || !TWIDDLE_TABLE.table) {
build_twiddle_table(w, h);
}
}
/* Given a 0-based texel location, returns new 0-based texel location */
/* NOTE: twid_prepare_table must have been called beforehand for correct behaviour */
GL_FORCE_INLINE uint32_t twid_location(uint32_t i) {
return TWIDDLE_TABLE.table[i];
return result;
}
@ -1446,7 +1434,7 @@ void _glAllocateSpaceForMipmaps(TextureObject* active) {
static bool _glTexImage2DValidate(GLenum target, GLint level, GLint internalFormat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type) {
if(target != GL_TEXTURE_2D) {
INFO_MSG("");
INFO_MSG("Target unsupported");
_glKosThrowError(GL_INVALID_ENUM, __func__);
return false;
}
@ -1669,8 +1657,8 @@ void APIENTRY glTexImage2D(GLenum target, GLint level, GLint internalFormat,
*/
GLint destStride = _determineStrideInternal(cleanInternalFormat);
GLint sourceStride = _determineStride(format, type);
GLuint srcBytes = (width * height * sourceStride);
GLuint destBytes = (width * height * destStride);
GLuint srcBytes = ((GLuint)width * (GLuint)height * (GLuint)sourceStride);
GLuint destBytes = ((GLuint)width * (GLuint)height * (GLuint)destStride);
TextureConversionFunc conversion = NULL;
int needs_conversion = _determineConversion(cleanInternalFormat, format, type, &conversion);
@ -1751,94 +1739,88 @@ void APIENTRY glTexImage2D(GLenum target, GLint level, GLint internalFormat,
return;
}
GLubyte* conversionBuffer = (GLubyte*) memalign(32, srcBytes);
const GLubyte* src = data;
GLubyte* dst = conversionBuffer;
bool pack = (needs_conversion & CONVERSION_TYPE_PACK) == CONVERSION_TYPE_PACK;
needs_conversion &= ~CONVERSION_TYPE_PACK;
if(needs_conversion == CONVERSION_TYPE_CONVERT) {
// Convert
for(uint32_t i = 0; i < (width * height); ++i) {
conversion(src, dst);
dst += destStride;
src += sourceStride;
}
} else if(needs_conversion == 2) {
// Twiddle
if(is4BPPFormat(internalFormat) && is4BPPFormat(format)) {
// Special case twiddling. We have to unpack each src value
// and repack into the right place
twid_prepare_table(width, height);
if(needs_conversion == 0 && !pack) {
/* No conversion or packing needed - direct copy to VRAM */
FASTCPY(targetData, data, destBytes);
} else if(is4BPPFormat(internalFormat) && is4BPPFormat(format)) {
/* 4BPP special case: unpack each nibble, twiddle, repack directly to VRAM */
uint32_t maskX, maskY;
calc_twiddle_factors(width, height, &maskX, &maskY);
for(uint32_t i = 0; i < (width * height); ++i) {
uint32_t newLocation = twid_location(i);
assert(newLocation < (width * height));
assert((newLocation / 2) < destBytes);
assert((i / 2) < srcBytes);
// This is the src/dest byte, but we need to figure
// out which half based on the odd/even of i
src = &((uint8_t*) data)[i / 2];
dst = &conversionBuffer[newLocation / 2];
uint8_t src_value = (i % 2) == 0 ? (*src >> 4) : (*src & 0xF);
if(newLocation % 2 == 1) {
*dst = (*dst & 0xF) | (src_value << 4);
} else {
*dst = (*dst & 0xF0) | (src_value & 0xF);
}
}
} else {
twid_prepare_table(width, height);
for(uint32_t i = 0; i < (width * height); ++i) {
uint32_t newLocation = twid_location(i);
dst = conversionBuffer + (destStride * newLocation);
for(int j = 0; j < destStride; ++j)
*dst++ = *(src + j);
src += sourceStride;
}
}
} else if(needs_conversion == 3) {
// Convert + twiddle
twid_prepare_table(width, height);
/* Clear destination buffer since we do read-modify-write on nibbles */
MEMSET4(targetData, 0x0, destBytes);
for(uint32_t i = 0; i < (width * height); ++i) {
uint32_t newLocation = twid_location(i);
dst = conversionBuffer + (destStride * newLocation);
src = data + (sourceStride * i);
conversion(src, dst);
uint32_t x = i % width;
uint32_t y = i / width;
uint32_t dstIndex = twid_compute_index(x, y, maskX, maskY);
assert(dstIndex < (width * height));
assert((dstIndex / 2) < destBytes);
assert((i / 2) < srcBytes);
const uint8_t* src_byte = &((uint8_t*) data)[i / 2];
uint8_t src_value = (i % 2) == 0 ? (*src_byte >> 4) : (*src_byte & 0xF);
uint8_t* dst_byte = &targetData[dstIndex / 2];
if(dstIndex % 2 == 1) {
*dst_byte = (*dst_byte & 0xF) | (src_value << 4);
} else {
*dst_byte = (*dst_byte & 0xF0) | (src_value & 0xF);
}
}
} else if(pack) {
FASTCPY(conversionBuffer, data, srcBytes);
}
if(pack) {
/* Pack case for 8bpp paletted textures: repack nibbles directly to VRAM */
assert(isPaletted);
size_t dst_byte = 0;
for(size_t src_byte = 0; src_byte < srcBytes; ++src_byte) {
uint8_t v = conversionBuffer[src_byte];
uint8_t v = ((uint8_t*) data)[src_byte];
if(src_byte % 1 == 0) {
conversionBuffer[dst_byte] = (conversionBuffer[dst_byte] & 0xF) | ((v & 0xF0) << 4);
if(src_byte % 2 == 0) {
targetData[dst_byte] = (targetData[dst_byte] & 0xF) | ((v & 0xF0) << 4);
} else {
conversionBuffer[dst_byte] = (conversionBuffer[dst_byte] & 0xF0) | (v & 0xF);
targetData[dst_byte] = (targetData[dst_byte] & 0xF0) | (v & 0xF);
}
if(src_byte % 1 == 0) {
if(src_byte % 2 == 0) {
dst_byte++;
}
}
}
} else {
/* General case: iterate source texels, compute destination, convert/write directly */
uint32_t maskX, maskY;
bool twiddle = (needs_conversion & CONVERSION_TYPE_TWIDDLE) != 0;
bool convert = (needs_conversion & CONVERSION_TYPE_CONVERT) != 0;
FASTCPY(targetData, conversionBuffer, destBytes);
free(conversionBuffer);
if(twiddle) {
calc_twiddle_factors(width, height, &maskX, &maskY);
}
for(uint32_t i = 0; i < (width * height); ++i) {
const GLubyte* src = data + (sourceStride * i);
GLubyte* dst;
if(twiddle) {
uint32_t x = i % width;
uint32_t y = i / width;
uint32_t newLocation = twid_compute_index(x, y, maskX, maskY);
dst = targetData + (destStride * newLocation);
} else {
dst = targetData + (destStride * i);
}
if(convert) {
conversion(src, dst);
} else {
for(int j = 0; j < destStride; ++j) {
dst[j] = src[j];
}
}
}
}
} else {
/* No conversion necessary, we can just upload data directly */
gl_assert(targetData);
@ -2228,12 +2210,13 @@ void APIENTRY glTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint y
}
}
} else if (needs_conversion == 2 || needs_conversion == 3) {
twid_prepare_table(textureWidth, textureHeight);
uint32_t maskX, maskY;
calc_twiddle_factors(textureWidth, textureHeight, &maskX, &maskY);
for (uint32_t y = yoffset; y < yoffset + height; ++y) {
for (uint32_t x = xoffset; x < xoffset + width; ++x) {
uint32_t srcIndex = (y - yoffset) * width + (x - xoffset);
uint32_t newLocation = twid_location(y * textureWidth + x);
uint32_t newLocation = twid_compute_index(x, y, maskX, maskY);
dst = conversionBuffer + (destStride * newLocation);
if (needs_conversion == 3) {