nitro-engine/source/NETexture.c

780 lines
20 KiB
C

// SPDX-License-Identifier: MIT
//
// Copyright (c) 2008-2011, 2019, 2022 Antonio Niño Díaz
//
// This file is part of Nitro Engine
#include "NEMain.h"
#include "NEAlloc.h"
/// @file NETexture.c
typedef struct {
u32 param;
char *adress;
NE_Palette *palette;
int uses;
int sizex, sizey;
} ne_textureinfo_t;
static ne_textureinfo_t *NE_Texture = NULL;
static NE_Material **NE_UserMaterials = NULL;
static NEChunk *NE_TexAllocList; // See NEAlloc.h
static bool ne_texture_system_inited = false;
static int NE_MAX_TEXTURES;
// Default material propierties
static u32 ne_default_diffuse_ambient;
static u32 ne_default_specular_emission;
static int __NE_GetValidSize(int size)
{
for (int i = 0; i < 8; i++)
{
if (size <= (8 << i))
return (8 << i);
}
return 0;
}
static int __NE_ConvertSizeRaw(int size)
{
for (int i = 0; i < 8; i++)
{
if (size == 8)
return i;
size >>= 1;
}
return 0;
}
static inline void NE_MaterialTexParam(NE_Material *tex, int sizeX, int sizeY,
uint32 *addr, GL_TEXTURE_TYPE_ENUM mode,
u32 param)
{
NE_AssertPointer(tex, "NULL pointer");
NE_Assert(tex->texindex != NE_NO_TEXTURE, "No assigned texture");
NE_Texture[tex->texindex].param = param
| (__NE_ConvertSizeRaw(sizeX) << 20)
| (__NE_ConvertSizeRaw(sizeY) << 23)
| (((uint32) addr >> 3) & 0xFFFF) | (mode << 26);
}
//--------------------------------------------------------------------------
NE_Material *NE_MaterialCreate(void)
{
if (!ne_texture_system_inited)
return NULL;
for (int i = 0; i < NE_MAX_TEXTURES; i++)
{
if (NE_UserMaterials[i] != NULL)
continue;
NE_Material *mat = calloc(1, sizeof(NE_Material));
NE_AssertPointer(mat, "Not enough memory");
NE_UserMaterials[i] = mat;
mat->texindex = NE_NO_TEXTURE;
mat->color = NE_White;
mat->diffuse_ambient = ne_default_diffuse_ambient;
mat->specular_emission = ne_default_specular_emission;
return mat;
}
NE_DebugPrint("No free slots");
return NULL;
}
void NE_MaterialColorSet(NE_Material *tex, u32 color)
{
NE_AssertPointer(tex, "NULL pointer");
tex->color = color;
}
void NE_MaterialColorDelete(NE_Material *tex)
{
NE_AssertPointer(tex, "NULL pointer");
tex->color = NE_White;
}
int NE_MaterialTexLoadFAT(NE_Material *tex, NE_TextureFormat fmt,
int sizeX, int sizeY, NE_TextureFlags flags,
char *path)
{
NE_AssertPointer(tex, "NULL material pointer");
NE_AssertPointer(path, "NULL path pointer");
NE_Assert(sizeX > 0 && sizeY > 0, "Size must be positive");
char *ptr = NE_FATLoadData(path);
if (ptr == NULL)
{
NE_DebugPrint("Couldn't load file from FAT");
return 0;
}
int ret = NE_MaterialTexLoad(tex, fmt, sizeX, sizeY, flags, (u8 *)ptr);
free(ptr);
return ret;
}
static int __NE_TextureResizeWidth(void *source, void *dest,
NE_TextureFormat fmt,
int height, int width, int newwidth)
{
NE_AssertPointer(source, "NULL source pointer");
NE_AssertPointer(dest, "NULL dest pointer");
static const int __NE_TextureDepth[] = {
0, // Nothing
8, // NE_A3PAL32
2, // NE_PAL4
4, // NE_PAL16
8, // NE_PAL256
0, // NE_COMPRESSED
8, // NE_A5PAL8
16, // NE_A1RGB5
16 // NE_RGB5
};
int bits = __NE_TextureDepth[fmt];
if (bits == 16)
{
// NE_A1RGB5 or NE_RGB5
// --------------------
// Cast to correct width
u16 *d = dest;
u16 *s = source;
for (int y = 0; y < height; y++)
{
for (int x = 0; x < newwidth; x++)
{
if (x < width)
*d = *s++;
d++;
}
}
return 1;
}
else if (bits == 8)
{
// NE_PAL256, NE_A3PAL32 or NE_A5PAL8
// ----------------------------------
// Cast to correct width
u8 *d = dest;
u8 *s = source;
for (int y = 0; y < height; y++)
{
for (int x = 0; x < newwidth; x++)
{
if (x < width)
*d = *s++;
d++;
}
}
return 1;
}
else if (bits == 4)
{
// NE_PAL16
// --------
// Cast to correct width
u8 *d = dest;
u8 *s = source;
int src_idx = 0;
int dst_idx = 0;
for (int y = 0; y < height; y++)
{
for (int x = 0; x < newwidth; x++)
{
if (x < width)
{
if (dst_idx == 0)
*d = 0;
*d |= ((*s >> (src_idx << 2)) & 0xF)
<< (dst_idx << 2);
if (src_idx == 1)
s++;
src_idx ^= 1;
}
if (dst_idx == 1)
d++;
dst_idx ^= 1;
}
}
return 1;
}
else if (bits == 2)
{
// NE_PAL4
// -------
// Cast to correct width
u8 *d = dest;
u8 *s = source;
int src_idx = 0;
int dst_idx = 0;
for (int y = 0; y < height; y++)
{
for (int x = 0; x < newwidth; x++)
{
if (x < width)
{
if (dst_idx == 0)
*d = 0;
*d |= ((*s >> (src_idx << 1)) & 0x3)
<< (dst_idx << 1);
if (src_idx == 3)
s++;
src_idx = (src_idx + 1) & 3;
}
if (dst_idx == 3)
d++;
dst_idx = (dst_idx + 1) & 3;
}
}
return 1;
}
return 0;
}
static const int __NE_TextureSizeShift[] = {
0, // Nothing
1, // NE_A3PAL32
3, // NE_PAL4
2, // NE_PAL16
1, // NE_PAL256
0, // NE_COMPRESSED
1, // NE_A5PAL8
0, // NE_A1RGB5
0, // NE_RGB5
};
int NE_MaterialTexLoad(NE_Material *tex, NE_TextureFormat fmt,
int sizeX, int sizeY, NE_TextureFlags flags,
void *texture)
{
NE_AssertPointer(tex, "NULL material pointer");
// Check if texture exists
if (tex->texindex != NE_NO_TEXTURE)
{
NE_MaterialDelete(tex);
tex = NE_MaterialCreate();
}
// Get free slot
tex->texindex = NE_NO_TEXTURE;
for (int i = 0; i < NE_MAX_TEXTURES; i++)
{
if (NE_Texture[i].adress == NULL)
{
tex->texindex = i;
break;
}
}
if (tex->texindex == NE_NO_TEXTURE)
{
NE_DebugPrint("No free slots");
tex->texindex = 0;
return 0;
}
int slot = tex->texindex;
// Save real size
NE_Texture[slot].sizex = sizeX;
NE_Texture[slot].sizey = sizeY;
u32 size = 0;
// If width is not a power of 2
bool invalidwidth = false;
if (__NE_GetValidSize(sizeX) != sizeX)
{
invalidwidth = true;
// The width must be a power of 2, but this texture has an invalid size.
// Let's expand the texture so that it is valid, load it to VRAM, and
// delete the temporary buffer used to expand it.
size = (__NE_GetValidSize(sizeX) * sizeY << 1) >>
__NE_TextureSizeShift[fmt];
void *newbuffer = malloc(size);
NE_AssertPointer(newbuffer, "Not enough memory for temporary buffer");
if (__NE_TextureResizeWidth(texture, newbuffer, fmt, sizeY, sizeX,
__NE_GetValidSize(sizeX)) == 0)
{
free(newbuffer);
return 0;
}
// New width
sizeX = __NE_GetValidSize(sizeX);
// Use new data
texture = newbuffer;
}
// The height doesn't need to be power of 2, but we will have to cheat later
// and make the DS believe it is a power of 2.
if (!invalidwidth)
size = (sizeX * sizeY << 1) >> __NE_TextureSizeShift[fmt];
u32 *addr = (u32 *) NE_Alloc(NE_TexAllocList, size); // Aligned to 8 bytes
if (!addr)
{
NE_DebugPrint("Not enough memory");
if (invalidwidth)
free(texture); // Free temp data
return 0;
}
NE_Texture[slot].adress = (void *)addr;
// Initially only this material is using this texture
NE_Texture[slot].uses = 1;
// unlock texture memory
u32 vramTemp = vramSetPrimaryBanks(VRAM_A_LCD, VRAM_B_LCD, VRAM_C_LCD,
VRAM_D_LCD);
// We do NE_RGB5 as NE_A1RGB5, but we set each alpha bit to 1 during the
// copy to VRAM.
if (fmt == NE_RGB5)
{
u16 *src = (u16 *)texture;
u16 *dest = (u16 *)addr;
NE_MaterialTexParam(tex, sizeX, __NE_GetValidSize(sizeY), addr,
NE_A1RGB5, flags);
while (size--)
{
*dest++ = *src | (1 << 15);
src++;
}
}
else
{
// For everything else, we do a straight copy
NE_MaterialTexParam(tex, sizeX, __NE_GetValidSize(sizeY), addr,
fmt, flags);
swiCopy((u32 *)texture, addr, (size >> 2) | COPY_MODE_WORD);
}
vramRestorePrimaryBanks(vramTemp);
if (invalidwidth)
free(texture); // Free temp data
return 1;
}
void NE_MaterialClone(NE_Material *source, NE_Material *dest)
{
NE_AssertPointer(source, "NULL source pointer");
NE_AssertPointer(dest, "NULL dest pointer");
NE_Assert(source->texindex != NE_NO_TEXTURE,
"No texture asigned to source material");
// Increase count of materials using this texture
NE_Texture[source->texindex].uses++;
memcpy(dest, source, sizeof(NE_Material));
}
void NE_MaterialSetPalette(NE_Material *tex, NE_Palette *pal)
{
NE_AssertPointer(tex, "NULL material pointer");
NE_AssertPointer(pal, "NULL palette pointer");
NE_Assert(tex->texindex != NE_NO_TEXTURE, "No texture asigned to material");
NE_Texture[tex->texindex].palette = pal;
}
void NE_MaterialUse(const NE_Material *tex)
{
if (tex == NULL)
{
GFX_TEX_FORMAT = 0;
GFX_COLOR = NE_White;
GFX_DIFFUSE_AMBIENT = ne_default_diffuse_ambient;
GFX_SPECULAR_EMISSION = ne_default_specular_emission;
return;
}
GFX_DIFFUSE_AMBIENT = tex->diffuse_ambient;
GFX_SPECULAR_EMISSION = tex->specular_emission;
NE_Assert(tex->texindex != NE_NO_TEXTURE, "No texture asigned to material");
if (NE_Texture[tex->texindex].palette)
NE_PaletteUse(NE_Texture[tex->texindex].palette);
GFX_COLOR = tex->color;
GFX_TEX_FORMAT = NE_Texture[tex->texindex].param;
}
extern bool NE_Dual;
void NE_TextureSystemReset(int max_textures, int max_palettes,
NE_VRAMBankFlags bank_flags)
{
if (ne_texture_system_inited)
NE_TextureSystemEnd();
if (max_textures < 1)
NE_MAX_TEXTURES = NE_DEFAULT_TEXTURES;
else
NE_MAX_TEXTURES = max_textures;
NE_AllocInit(&NE_TexAllocList, (void *)VRAM_A, (void *)VRAM_E);
NE_Assert((bank_flags & 0xF) != 0, "No VRAM banks selected");
// Prevent user from not selecting any bank
if ((bank_flags & 0xF) == 0)
bank_flags = NE_VRAM_ABCD;
// VRAM_C and VRAM_D can't be used in dual 3D mode
if (NE_Dual)
bank_flags &= ~NE_VRAM_CD;
// Now, configure allocation system. The buffer size always sees the
// four banks of VRAM. It is needed to allocate one chunk for each and
// lock the ones that aren't allowed to be used by Nitro Engine.
NE_Alloc(NE_TexAllocList, 128 * 1024); // VRAM_A
NE_Alloc(NE_TexAllocList, 128 * 1024); // VRAM_B
NE_Alloc(NE_TexAllocList, 128 * 1024); // VRAM_C
NE_Alloc(NE_TexAllocList, 128 * 1024); // VRAM_D
if (bank_flags & NE_VRAM_A)
{
vramSetBankA(VRAM_A_TEXTURE_SLOT0);
NE_Free(NE_TexAllocList, VRAM_A);
}
else
{
NE_Lock(NE_TexAllocList, VRAM_A);
}
if (bank_flags & NE_VRAM_B)
{
vramSetBankB(VRAM_B_TEXTURE_SLOT1);
NE_Free(NE_TexAllocList, VRAM_B);
}
else
{
NE_Lock(NE_TexAllocList, VRAM_B);
}
if (bank_flags & NE_VRAM_C)
{
vramSetBankC(VRAM_C_TEXTURE_SLOT2);
NE_Free(NE_TexAllocList, VRAM_C);
}
else
{
NE_Lock(NE_TexAllocList, VRAM_C);
}
if (bank_flags & NE_VRAM_D)
{
vramSetBankD(VRAM_D_TEXTURE_SLOT3);
NE_Free(NE_TexAllocList, VRAM_D);
}
else
{
NE_Lock(NE_TexAllocList, VRAM_D);
}
NE_Texture = calloc(NE_MAX_TEXTURES, sizeof(ne_textureinfo_t));
NE_AssertPointer(NE_Texture, "Not enough memory");
NE_UserMaterials = calloc(NE_MAX_TEXTURES, sizeof(NE_UserMaterials));
NE_AssertPointer(NE_UserMaterials, "Not enough memory");
NE_PaletteSystemReset(max_palettes);
GFX_TEX_FORMAT = 0;
ne_texture_system_inited = true;
}
void NE_MaterialDelete(NE_Material *tex)
{
NE_AssertPointer(tex, "NULL pointer");
// If there is an asigned texture
if (tex->texindex != NE_NO_TEXTURE)
{
int slot = tex->texindex;
// A texture may be used by several materials
NE_Texture[slot].uses--;
// If this is the only material to use it, delete it
if (NE_Texture[slot].uses == 0)
{
NE_Free(NE_TexAllocList, NE_Texture[slot].adress);
NE_Texture[slot].adress = NULL;
NE_Texture[slot].param = 0;
NE_Texture[slot].palette = 0;
}
}
for (int i = 0; i < NE_MAX_TEXTURES; i++)
{
if (NE_UserMaterials[i] == tex)
{
NE_UserMaterials[i] = NULL;
free(tex);
return;
}
}
NE_DebugPrint("Object not found");
}
int NE_TextureFreeMem(void)
{
if (!ne_texture_system_inited)
return 0;
NEMemInfo info;
NE_MemGetInformation(NE_TexAllocList, &info);
return info.free;
}
int NE_TextureFreeMemPercent(void)
{
if (!ne_texture_system_inited)
return 0;
NEMemInfo info;
NE_MemGetInformation(NE_TexAllocList, &info);
return info.free_percent;
}
void NE_TextureDefragMem(void)
{
NE_Assert(0, "This function doesn't work");
return;
/*
// REALLY OLD CODE -- DOESN'T WORK
if (!ne_texture_system_inited)
return;
uint32 vramTemp = vramSetMainBanks(VRAM_A_LCD, VRAM_B_LCD, VRAM_C_LCD,
VRAM_D_LCD);
bool ok = false;
while (!ok)
{
ok = true;
int i;
for (i = 0; i < NE_MAX_TEXTURES; i++)
{
int size = NE_GetSize(NE_TexAllocList, (void*)NE_Texture[i].adress);
NE_Free(NE_TexAllocList,(void*)NE_Texture[i].adress);
void *pointer = NE_Alloc(NE_TexAllocList, size);
// Aligned to 8 bytes
NE_AssertPointer(pointer, "Couldn't reallocate texture");
if (pointer != NE_Texture[i].adress)
{
dmaCopy((void*) NE_Texture[i].adress, pointer, size);
NE_Texture[i].adress = pointer;
NE_Texture[i].param &= 0xFFFF0000;
NE_Texture[i].param |= ((uint32)pointer >> 3) & 0xFFFF;
ok = false;
}
}
}
vramRestoreMainBanks(vramTemp);
*/
}
void NE_TextureSystemEnd(void)
{
if (!ne_texture_system_inited)
return;
NE_AllocEnd(&NE_TexAllocList);
free(NE_Texture);
for (int i = 0; i < NE_MAX_TEXTURES; i++)
{
if (NE_UserMaterials[i])
free(NE_UserMaterials[i]);
}
free(NE_UserMaterials);
NE_Texture = NULL;
NE_PaletteSystemEnd();
ne_texture_system_inited = false;
}
// Internal use
int __NE_TextureGetRawX(const NE_Material *tex)
{
NE_AssertPointer(tex, "NULL pointer");
NE_Assert(tex->texindex != NE_NO_TEXTURE,
"No texture asigned to material");
return (NE_Texture[tex->texindex].param & (0x7 << 20)) >> 20;
}
// Internal use
int __NE_TextureGetRawY(const NE_Material *tex)
{
NE_AssertPointer(tex, "NULL pointer");
NE_Assert(tex->texindex != NE_NO_TEXTURE, "No texture asigned to material");
return (NE_Texture[tex->texindex].param & (0x7 << 23)) >> 23;
}
int NE_TextureGetRealSizeX(const NE_Material *tex)
{
NE_AssertPointer(tex, "NULL pointer");
NE_Assert(tex->texindex != NE_NO_TEXTURE, "No texture asigned to material");
return 8 << __NE_TextureGetRawX(tex);
}
int NE_TextureGetRealSizeY(const NE_Material *tex)
{
NE_AssertPointer(tex, "NULL pointer");
NE_Assert(tex->texindex != NE_NO_TEXTURE, "No texture asigned to material");
return 8 << __NE_TextureGetRawY(tex);
}
int NE_TextureGetSizeX(const NE_Material *tex)
{
NE_AssertPointer(tex, "NULL pointer");
NE_Assert(tex->texindex != NE_NO_TEXTURE, "No texture asigned to material");
return NE_Texture[tex->texindex].sizex;
}
int NE_TextureGetSizeY(const NE_Material *tex)
{
NE_AssertPointer(tex, "NULL pointer");
NE_Assert(tex->texindex != NE_NO_TEXTURE, "No texture asigned to material");
return NE_Texture[tex->texindex].sizey;
}
void NE_MaterialSetPropierties(NE_Material *tex, u32 diffuse,
u32 ambient, u32 specular, u32 emission,
bool vtxcolor, bool useshininess)
{
NE_AssertPointer(tex, "NULL pointer");
tex->diffuse_ambient = diffuse | (ambient << 16) | (vtxcolor << 15);
tex->specular_emission = specular | (emission << 16) | (useshininess << 15);
}
void NE_MaterialSetDefaultPropierties(u32 diffuse, u32 ambient,
u32 specular, u32 emission,
bool vtxcolor, bool useshininess)
{
ne_default_diffuse_ambient = diffuse | (ambient << 16) | (vtxcolor << 15);
ne_default_specular_emission = specular | (emission << 16)
| (useshininess << 15);
GFX_DIFFUSE_AMBIENT = ne_default_diffuse_ambient;
GFX_SPECULAR_EMISSION = ne_default_specular_emission;
}
static u16 *drawingtexture_adress = NULL;
static int drawingtexture_x, drawingtexture_y;
static int drawingtexture_type;
static int drawingtexture_realx;
static u32 ne_vram_saved;
void *NE_TextureDrawingStart(const NE_Material *tex)
{
NE_AssertPointer(tex, "NULL pointer");
NE_Assert(tex->texindex != NE_NO_TEXTURE, "No texture asigned to material");
NE_Assert(drawingtexture_adress == NULL,
"Another texture is already active");
drawingtexture_x = NE_TextureGetSizeX(tex);
drawingtexture_realx = NE_TextureGetRealSizeX(tex);
drawingtexture_y = NE_TextureGetSizeY(tex);
drawingtexture_adress = (u16 *) ((uintptr_t)VRAM_A
+ ((NE_Texture[tex->texindex].param & 0xFFFF) << 3));
drawingtexture_type = ((NE_Texture[tex->texindex].param >> 26) & 0x7);
ne_vram_saved = vramSetPrimaryBanks(VRAM_A_LCD, VRAM_B_LCD, VRAM_C_LCD,
VRAM_D_LCD);
return drawingtexture_adress;
}
void NE_TexturePutPixelRGBA(u32 x, u32 y, u16 color)
{
NE_AssertPointer(drawingtexture_adress,
"No texture active for drawing");
NE_Assert(drawingtexture_type == NE_A1RGB5,
"Ative texture isn't NE_A1RGB5");
if (x >= drawingtexture_x || y >= drawingtexture_y)
return;
drawingtexture_adress[x + (y * drawingtexture_realx)] = color;
}
void NE_TexturePutPixelRGB256(u32 x, u32 y, u8 palettecolor)
{
NE_AssertPointer(drawingtexture_adress,
"No texture active for drawing.");
NE_Assert(drawingtexture_type == NE_PAL256,
"Active texture isn't NE_PAL256");
if (x >= drawingtexture_x || y >= drawingtexture_y)
return;
int position = (x + (y * drawingtexture_realx)) >> 1;
int desp = (x & 1) << 3;
drawingtexture_adress[position] &= 0xFF00 >> desp;
drawingtexture_adress[position] |= ((u16) palettecolor) << desp;
}
void NE_TextureDrawingEnd(void)
{
NE_Assert(drawingtexture_adress != NULL, "No active texture");
vramRestorePrimaryBanks(ne_vram_saved);
drawingtexture_adress = NULL;
}