mirror of
https://github.com/AntonioND/nitro-engine.git
synced 2025-06-18 16:45:33 -04:00
library: examples: Simplify texture loading code
This is done to make it easier to implement compressed texture loading. The main change is to stop allowing widths that aren't a power of two: - Supporting them complicates the loading code a lot. - Compressed textures can't really be expanded because they are composed by many 4x4 sub-images. - They don't save space in VRAM. - They save space in the final ROM, but you can achieve the same effect compressing them with LZSS compression, for example. The example incomplete_texture has been updated to reflect the new limitations.
This commit is contained in:
parent
68588dd2fa
commit
8aa6b382d2
Binary file not shown.
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 15 KiB |
Binary file not shown.
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 15 KiB |
Binary file not shown.
Binary file not shown.
@ -1 +1 @@
|
||||
<EFBFBD><05>j\<EFBFBD>A
|
||||
<EFBFBD><05><EFBFBD>Aj\
|
Binary file not shown.
@ -14,30 +14,32 @@
|
||||
NE_Material *Material, *Material2;
|
||||
NE_Palette *Palette, *Palette2;
|
||||
|
||||
// This is an example to show how Nitro Engine can load textures of any size.
|
||||
// If the width is not a power of 2, it will take much more time than normal to
|
||||
// load. The height doesn't matter.
|
||||
// This is an example to show how Nitro Engine can load textures of any height.
|
||||
// Internally, the NDS thinks that the texture is bigger, but Nitro Engine only
|
||||
// uses the parts that the user has loaded.
|
||||
//
|
||||
// Nitro Engine resizes the width to the nearest power of two (if needed) when
|
||||
// loading a texture. This means:
|
||||
// The width needs to be a power of two because:
|
||||
//
|
||||
// - It needs more time than normal to load because it needs time to resize it.
|
||||
// - It won't save space in VRAM, the DS still needs to see the full width in
|
||||
// VRAM. It will just save some space in the ROM.
|
||||
// - Supporting them complicates the loading code a lot.
|
||||
//
|
||||
// If the height is different it will actually save some space in VRAM (apart
|
||||
// from saving space in the ROM).
|
||||
// - Compressed textures can't really be expanded because they are composed
|
||||
// by many 4x4 subimages.
|
||||
//
|
||||
// - They don't save space in VRAM.
|
||||
//
|
||||
// - They save space in the final ROM, but you can achieve the same effect
|
||||
// compressing them with LZSS compression, for example.
|
||||
|
||||
void Draw3DScene(void)
|
||||
{
|
||||
NE_2DViewInit();
|
||||
|
||||
NE_2DDrawTexturedQuad(40, 10,
|
||||
40 + 50, 10 + 128,
|
||||
40 + 32, 10 + 100,
|
||||
0, Material);
|
||||
|
||||
NE_2DDrawTexturedQuad(128, 10,
|
||||
128 + 100, 10 + 100,
|
||||
128 + 64, 10 + 100,
|
||||
0, Material2);
|
||||
}
|
||||
|
||||
@ -56,7 +58,7 @@ int main(void)
|
||||
|
||||
NE_MaterialTexLoad(Material,
|
||||
NE_A3PAL32, // Texture type
|
||||
100, 256, // Width, height (in pixels)
|
||||
64, 200, // Width, height (in pixels)
|
||||
NE_TEXGEN_TEXCOORD, (u8 *)a3pal32_tex_bin);
|
||||
NE_PaletteLoad(Palette, (u16 *)a3pal32_pal_bin, 32, NE_A3PAL32);
|
||||
NE_MaterialSetPalette(Material, Palette);
|
||||
@ -65,7 +67,7 @@ int main(void)
|
||||
Material2 = NE_MaterialCreate();
|
||||
Palette2 = NE_PaletteCreate();
|
||||
|
||||
NE_MaterialTexLoad(Material2, NE_PAL4, 100, 100, NE_TEXGEN_TEXCOORD,
|
||||
NE_MaterialTexLoad(Material2, NE_PAL4, 64, 100, NE_TEXGEN_TEXCOORD,
|
||||
(u8 *)pal4_tex_bin);
|
||||
NE_PaletteLoad(Palette2, (u16 *)pal4_pal_bin, 4, NE_PAL4);
|
||||
NE_MaterialSetPalette(Material2, Palette2);
|
||||
|
@ -79,11 +79,18 @@ void NE_MaterialColorDelete(NE_Material *tex);
|
||||
|
||||
/// Loads a texture from the filesystem and assigns it to a material object.
|
||||
///
|
||||
/// The sizes don't need to be a power of two, but it will be much more
|
||||
/// efficient if the width is a power of two. Textures with a non-power-of-two
|
||||
/// width need to be resized manually, and they don't save any VRAM when loaded.
|
||||
/// Textures with a non-power-of-two height don't need to be resized, and they
|
||||
/// actually save VRAM space.
|
||||
/// The height doesn't need to be a power of two, but he width must be a power
|
||||
/// of two.
|
||||
///
|
||||
/// Textures with width that isn't a power of two need to be resized manually,
|
||||
/// which is very slow, and they don't save any VRAM when loaded compared to a
|
||||
/// texture with the full width. The only advantage is that they need less
|
||||
/// storage space, but you can achieve the same effect by compressing them.
|
||||
///
|
||||
/// Textures with a height that isn't a power of two don't need to be resized,
|
||||
/// and they actually save VRAM space (you tell the GPU that the texture is
|
||||
/// bigger, but then you ignore the additional space, as it will be used by
|
||||
/// other textures).
|
||||
///
|
||||
/// @param tex Material.
|
||||
/// @param fmt Texture format.
|
||||
@ -98,11 +105,15 @@ int NE_MaterialTexLoadFAT(NE_Material *tex, NE_TextureFormat fmt,
|
||||
|
||||
/// Loads a texture from RAM and assigns it to a material object.
|
||||
///
|
||||
/// The sizes don't need to be a power of two, but it will be much more
|
||||
/// efficient if the width is a power of two. Textures with a non-power-of-two
|
||||
/// width need to be resized manually, and they don't save any VRAM when loaded.
|
||||
/// Textures with a non-power-of-two height don't need to be resized, and they
|
||||
/// actually save VRAM space.
|
||||
/// Textures with width that isn't a power of two need to be resized manually,
|
||||
/// which is very slow, and they don't save any VRAM when loaded compared to a
|
||||
/// texture with the full width. The only advantage is that they need less
|
||||
/// storage space, but you can achieve the same effect by compressing them.
|
||||
///
|
||||
/// Textures with a height that isn't a power of two don't need to be resized,
|
||||
/// and they actually save VRAM space (you tell the GPU that the texture is
|
||||
/// bigger, but then you ignore the additional space, as it will be used by
|
||||
/// other textures).
|
||||
///
|
||||
/// @param tex Material.
|
||||
/// @param fmt Texture format.
|
||||
|
@ -30,7 +30,7 @@ static int NE_MAX_TEXTURES;
|
||||
static u32 ne_default_diffuse_ambient;
|
||||
static u32 ne_default_specular_emission;
|
||||
|
||||
static int __NE_GetValidSize(int size)
|
||||
static int ne_is_valid_tex_size(int size)
|
||||
{
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
@ -40,7 +40,7 @@ static int __NE_GetValidSize(int size)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __NE_ConvertSizeRaw(int size)
|
||||
static int ne_tex_raw_size(int size)
|
||||
{
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
@ -51,16 +51,34 @@ static int __NE_ConvertSizeRaw(int size)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void NE_MaterialTexParam(NE_Material *tex, int sizeX, int sizeY,
|
||||
uint32 *addr, GL_TEXTURE_TYPE_ENUM mode,
|
||||
u32 param)
|
||||
static inline void ne_set_material_tex_param(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_Texture[tex->texindex].param =
|
||||
(ne_tex_raw_size(sizeX) << 20) |
|
||||
(ne_tex_raw_size(sizeY) << 23) |
|
||||
(((uint32)addr >> 3) & 0xFFFF) |
|
||||
(mode << 26) | param;
|
||||
}
|
||||
|
||||
static void ne_texture_delete(int texture_index)
|
||||
{
|
||||
int slot = texture_index;
|
||||
|
||||
// A texture may be used by several materials
|
||||
NE_Texture[slot].uses--;
|
||||
|
||||
// If the number of users is zero, 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;
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
@ -124,171 +142,46 @@ int NE_MaterialTexLoadFAT(NE_Material *tex, NE_TextureFormat fmt,
|
||||
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)
|
||||
{
|
||||
const int size_shift[] = {
|
||||
0, // Nothing
|
||||
1, // NE_A3PAL32
|
||||
3, // NE_PAL4
|
||||
2, // NE_PAL16
|
||||
1, // NE_PAL256
|
||||
0, // NE_COMPRESSED (This value isn't used)
|
||||
1, // NE_A5PAL8
|
||||
0, // NE_A1RGB5
|
||||
0, // NE_RGB5
|
||||
};
|
||||
|
||||
NE_AssertPointer(tex, "NULL material pointer");
|
||||
|
||||
// The width of a texture must be a power of 2. The height doesn't need to
|
||||
// be a power of 2, but we will have to cheat later and make the DS believe
|
||||
// it is a power of 2.
|
||||
if (ne_is_valid_tex_size(sizeX) != sizeX)
|
||||
{
|
||||
NE_DebugPrint("Width of textures must be powers of 2");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Compressed textures are organized in 4x4 chunks.
|
||||
if (fmt == NE_COMPRESSED)
|
||||
{
|
||||
if ((sizeY & 3) != 0)
|
||||
{
|
||||
NE_DebugPrint("Compressed textures need a height multiple of 4");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if texture exists
|
||||
if (tex->texindex != NE_NO_TEXTURE)
|
||||
{
|
||||
NE_MaterialDelete(tex);
|
||||
tex = NE_MaterialCreate();
|
||||
}
|
||||
ne_texture_delete(tex->texindex);
|
||||
|
||||
// Get free slot
|
||||
tex->texindex = NE_NO_TEXTURE;
|
||||
@ -307,92 +200,58 @@ int NE_MaterialTexLoad(NE_Material *tex, NE_TextureFormat fmt,
|
||||
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)
|
||||
// TODO: Support them
|
||||
if (fmt == NE_COMPRESSED)
|
||||
{
|
||||
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
|
||||
NE_DebugPrint("Compressed textures not supported");
|
||||
return 0;
|
||||
}
|
||||
|
||||
NE_Texture[slot].adress = (void *)addr;
|
||||
// Initially only this material is using this texture
|
||||
NE_Texture[slot].uses = 1;
|
||||
uint32_t size = (sizeX * sizeY << 1) >> size_shift[fmt];
|
||||
|
||||
// unlock texture memory
|
||||
// This pointer must be aligned to 8 bytes at least
|
||||
void *addr = NE_AllocFromEnd(NE_TexAllocList, size);
|
||||
if (!addr)
|
||||
{
|
||||
tex->texindex = NE_NO_TEXTURE;
|
||||
NE_DebugPrint("Not enough memory");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Save information
|
||||
int slot = tex->texindex;
|
||||
NE_Texture[slot].sizex = sizeX;
|
||||
NE_Texture[slot].sizey = sizeY;
|
||||
NE_Texture[slot].adress = addr;
|
||||
NE_Texture[slot].uses = 1; // Initially only this material uses the texture
|
||||
|
||||
// Unlock texture memory for writing
|
||||
// TODO: Only unlock the banks that Nitro Engine uses.
|
||||
u32 vramTemp = vramSetPrimaryBanks(VRAM_A_LCD, VRAM_B_LCD, VRAM_C_LCD,
|
||||
VRAM_D_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)
|
||||
{
|
||||
// Treat NE_RGB5 as NE_A1RGB5, but set each alpha bit to 1 during the
|
||||
// copy to VRAM.
|
||||
u16 *src = (u16 *)texture;
|
||||
u16 *dest = (u16 *)addr;
|
||||
NE_MaterialTexParam(tex, sizeX, __NE_GetValidSize(sizeY), addr,
|
||||
NE_A1RGB5, flags);
|
||||
|
||||
u16 *dest = addr;
|
||||
size >>= 1; // We are going to process two bytes each iteration
|
||||
while (size--)
|
||||
{
|
||||
*dest++ = *src | (1 << 15);
|
||||
src++;
|
||||
}
|
||||
*dest++ = *src++ | (1 << 15);
|
||||
|
||||
fmt = NE_A1RGB5;
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
int hardware_size_y = ne_is_valid_tex_size(sizeY);
|
||||
ne_set_material_tex_param(tex, sizeX, hardware_size_y, addr, fmt, flags);
|
||||
|
||||
vramRestorePrimaryBanks(vramTemp);
|
||||
if (invalidwidth)
|
||||
free(texture); // Free temp data
|
||||
|
||||
return 1;
|
||||
}
|
||||
@ -526,19 +385,7 @@ void NE_MaterialDelete(NE_Material *tex)
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
ne_texture_delete(tex->texindex);
|
||||
|
||||
for (int i = 0; i < NE_MAX_TEXTURES; i++)
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user