From 8764804dd56ad1799a0637f515faeafc12642142 Mon Sep 17 00:00:00 2001 From: Michael Theall Date: Sat, 23 Sep 2017 18:45:55 -0500 Subject: [PATCH] Add tex3ds.h interface for loading assets converted by tex3ds --- Makefile | 3 +- include/tex3ds.h | 215 ++++++++++++++++++++++++++++++++++++++++++ source/tex3ds.c | 241 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 457 insertions(+), 2 deletions(-) create mode 100644 include/tex3ds.h create mode 100644 source/tex3ds.c diff --git a/Makefile b/Makefile index 53f98f5..d205e24 100644 --- a/Makefile +++ b/Makefile @@ -22,8 +22,7 @@ VERSION := $(CITRO3D_MAJOR).$(CITRO3D_MINOR).$(CITRO3D_PATCH) # INCLUDES is a list of directories containing header files #--------------------------------------------------------------------------------- TARGET := citro3d -SOURCES := source \ - source/maths +SOURCES := source source/maths DATA := data INCLUDES := include diff --git a/include/tex3ds.h b/include/tex3ds.h new file mode 100644 index 0000000..26d7444 --- /dev/null +++ b/include/tex3ds.h @@ -0,0 +1,215 @@ +/*------------------------------------------------------------------------------ + * Copyright (c) 2017 + * Michael Theall (mtheall) + * + * This file is part of citro3d. + * + * This software is provided 'as-is', without any express or implied warranty. + * In no event will the authors be held liable for any damages arising from the + * use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software in + * a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + *----------------------------------------------------------------------------*/ +/** @file tex3ds.h + * @brief tex3ds support + */ +#pragma once +#ifdef CITRO3D_BUILD +#include "c3d/texture.h" +#else +#include +#endif + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** @brief Subtexture + * @note If top > bottom, the subtexture is rotated 1/4 revolution counter-clockwise + */ +typedef struct Tex3DS_SubTexture +{ + u16 width; ///< Sub-texture width (pixels) + u16 height; ///< Sub-texture height (pixels) + float left; ///< Left u-coordinate + float top; ///< Top v-coordinate + float right; ///< Right u-coordinate + float bottom; ///< Bottom v-coordinate +} Tex3DS_SubTexture; + +/** @brief Texture */ +typedef struct Tex3DS_Texture_s* Tex3DS_Texture; + +/** @brief Import Tex3DS texture + * @param[in] input Input data + * @param[in] insize Size of the input data + * @param[out] tex citro3d texture + * @param[out] texcube citro3d texcube + * @param[in] vram Whether to store textures in VRAM + * @returns Tex3DS texture + */ +Tex3DS_Texture Tex3DS_TextureImport(const void* input, size_t insize, C3D_Tex* tex, C3D_TexCube* texcube, bool vram); + +/** @brief Import Tex3DS texture + * + * @description + * For example, use this if you want to import from a large file without + * pulling the entire file into memory. + * + * @param[out] tex citro3d texture + * @param[out] texcube citro3d texcube + * @param[in] vram Whether to store textures in VRAM + * @param[in] callback Data callback + * @param[in] userdata User data passed to callback + * @returns Tex3DS texture + */ +Tex3DS_Texture Tex3DS_TextureImportCallback(C3D_Tex* tex, C3D_TexCube* texcube, bool vram, decompressCallback callback, void* userdata); + +/** @brief Import Tex3DS texture + * + * Starts reading at the current file descriptor's offset. The file + * descriptor's position is left at the end of the decoded data. On error, the + * file descriptor's position is indeterminate. + * + * @param[in] fd Open file descriptor + * @param[out] tex citro3d texture + * @param[out] texcube citro3d texcube + * @param[in] vram Whether to store textures in VRAM + * @returns Tex3DS texture + */ +Tex3DS_Texture Tex3DS_TextureImportFD(int fd, C3D_Tex* tex, C3D_TexCube* texcube, bool vram); + +/** @brief Import Tex3DS texture + * + * Starts reading at the current file stream's offset. The file stream's + * position is left at the end of the decoded data. On error, the file + * stream's position is indeterminate. + * + * @param[in] fp Open file stream + * @param[out] tex citro3d texture + * @param[out] texcube citro3d texcube + * @param[in] vram Whether to store textures in VRAM + * @returns Tex3DS texture + */ +Tex3DS_Texture Tex3DS_TextureImportStdio(FILE* fp, C3D_Tex* tex, C3D_TexCube* texcube, bool vram); + +/** @brief Get number of subtextures + * @param[in] texture Tex3DS texture + * @returns Number of subtextures + */ +const size_t Tex3DS_GetNumSubTextures(const Tex3DS_Texture texture); + +/** @brief Get subtexture + * @param[in] texture Tex3DS texture + * @param[in] index Subtexture index + * @returns Subtexture info + */ +const Tex3DS_SubTexture* Tex3DS_GetSubTexture(const Tex3DS_Texture texture, size_t index); + +/** @brief Check if subtexture is rotated + * @param[in] subtex Subtexture to check + * @returns whether subtexture is rotated + */ +static inline bool +Tex3DS_SubTextureRotated(const Tex3DS_SubTexture* subtex) +{ + return subtex->top < subtex->bottom; +} + +/** @brief Get bottom-left texcoords + * @param[in] subtex Subtexture + * @param[out] u u-coordinate + * @param[out] v v-coordinate + */ +static inline void +Tex3DS_SubTextureBottomLeft(const Tex3DS_SubTexture* subtex, float* u, float* v) +{ + if (!Tex3DS_SubTextureRotated(subtex)) + { + *u = subtex->left; + *v = subtex->bottom; + } else + { + *u = subtex->bottom; + *v = subtex->left; + } +} + +/** @brief Get bottom-right texcoords + * @param[in] subtex Subtexture + * @param[out] u u-coordinate + * @param[out] v v-coordinate + */ +static inline void +Tex3DS_SubTextureBottomRight(const Tex3DS_SubTexture* subtex, float* u, float* v) +{ + if (!Tex3DS_SubTextureRotated(subtex)) + { + *u = subtex->right; + *v = subtex->bottom; + } else + { + *u = subtex->bottom; + *v = subtex->right; + } +} + +/** @brief Get top-left texcoords + * @param[in] subtex Subtexture + * @param[out] u u-coordinate + * @param[out] v v-coordinate + */ +static inline void +Tex3DS_SubTextureTopLeft(const Tex3DS_SubTexture* subtex, float* u, float* v) +{ + if (!Tex3DS_SubTextureRotated(subtex)) + { + *u = subtex->left; + *v = subtex->top; + } else + { + *u = subtex->top; + *v = subtex->left; + } +} + +/** @brief Get top-right texcoords + * @param[in] subtex Subtexture + * @param[out] u u-coordinate + * @param[out] v v-coordinate + */ +static inline void +Tex3DS_SubTextureTopRight(const Tex3DS_SubTexture* subtex, float* u, float* v) +{ + if (!Tex3DS_SubTextureRotated(subtex)) + { + *u = subtex->right; + *v = subtex->top; + } else + { + *u = subtex->top; + *v = subtex->right; + } +} + +/** @brief Free Tex3DS texture + * @param[in] texture Tex3DS texture to free + */ +void Tex3DS_TextureFree(Tex3DS_Texture texture); + +#ifdef __cplusplus +} +#endif diff --git a/source/tex3ds.c b/source/tex3ds.c new file mode 100644 index 0000000..d3b8696 --- /dev/null +++ b/source/tex3ds.c @@ -0,0 +1,241 @@ +/*------------------------------------------------------------------------------ + * Copyright (c) 2017 + * Michael Theall (mtheall) + * + * This file is part of citro3d. + * + * This software is provided 'as-is', without any express or implied warranty. + * In no event will the authors be held liable for any damages arising from the + * use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software in + * a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + *----------------------------------------------------------------------------*/ +/** @file tex3ds.c + * @brief Tex3DS routines + */ +#include +#include +#include +#include +#include + +/** @brief Tex3DS texture + */ +struct Tex3DS_Texture_s +{ + u16 numSubTextures; ///< Number of subtextures + u16 width; ///< Texture width + u16 height; ///< Texture height + u8 format; ///< Texture format + u8 mipmapLevels; ///< Number of mipmaps + Tex3DS_SubTexture subTextures[]; ///< Subtextures +}; + +typedef struct __attribute__((packed)) +{ + u16 numSubTextures; + u8 width_log2 : 3; + u8 height_log2 : 3; + u8 type : 1; + u8 format; + u8 mipmapLevels; +} Tex3DSi_Header; + +typedef struct +{ + u16 width, height; + u16 left, top, right, bottom; +} Tex3DSi_SubTexture; + +static inline bool Tex3DSi_ReadData(decompressCallback callback, void** userdata, void* buffer, size_t size, size_t* insize) +{ + if (callback) + return callback(*userdata, buffer, size) == size; + if (size > *insize) + return false; + + memcpy(buffer, *userdata, size); + *userdata = (u8*)*userdata + size; + *insize -= size; + return true; +} + +static Tex3DS_Texture +Tex3DSi_ImportCommon(C3D_Tex* tex, C3D_TexCube* texcube, bool vram, decompressCallback callback, void* userdata, size_t insize) +{ + // Read header + Tex3DSi_Header hdr; + if (!Tex3DSi_ReadData(callback, &userdata, &hdr, sizeof(hdr), &insize)) + return NULL; + + // Allocate space for header + subtextures + Tex3DS_Texture texture = (Tex3DS_Texture)malloc(sizeof(struct Tex3DS_Texture_s) + hdr.numSubTextures*sizeof(Tex3DS_SubTexture)); + if (!texture) + return NULL; + + // Fill texture metadata structure + texture->numSubTextures = hdr.numSubTextures; + texture->width = 1 << (hdr.width_log2 + 3); + texture->height = 1 << (hdr.height_log2 + 3); + texture->format = hdr.format; + texture->mipmapLevels = hdr.mipmapLevels; + + // Read subtexture info + for (size_t i = 0; i < hdr.numSubTextures; i ++) + { + Tex3DSi_SubTexture subtex; + if (!Tex3DSi_ReadData(callback, &userdata, &subtex, sizeof(Tex3DSi_SubTexture), &insize)) + { + free(texture); + return NULL; + } + texture->subTextures[i].width = subtex.width; + texture->subTextures[i].height = subtex.height; + texture->subTextures[i].left = subtex.left / 1024.0f; + texture->subTextures[i].top = subtex.top / 1024.0f; + texture->subTextures[i].right = subtex.right / 1024.0f; + texture->subTextures[i].bottom = subtex.bottom / 1024.0f; + } + + // Allocate texture memory + C3D_TexInitParams params; + params.width = texture->width; + params.height = texture->height; + params.maxLevel = texture->mipmapLevels; + params.format = texture->format; + params.type = (GPU_TEXTURE_MODE_PARAM)hdr.type; + params.onVram = vram; + if (!C3D_TexInitWithParams(tex, texcube, params)) + { + free(texture); + return NULL; + } + + // Get texture size, including mipmaps + size_t base_texsize = C3D_TexCalcTotalSize(tex->size, texture->mipmapLevels); + size_t texsize = base_texsize; + + // If this is a cubemap/skybox, there are 6 textures + if (params.type == GPU_TEX_CUBE_MAP) + texsize *= 6; + + if (vram) + { + // Allocate staging buffer in linear memory + void* texdata = linearAlloc(texsize); + if (!texdata) + { + C3D_TexDelete(tex); + free(texture); + return NULL; + } + + // Decompress into staging buffer for VRAM upload + if (!decompress(texdata, texsize, callback, userdata, insize)) + { + linearFree(texdata); + C3D_TexDelete(tex); + free(texture); + return NULL; + } + + // Flush buffer to prepare DMA to VRAM + GSPGPU_FlushDataCache(texdata, texsize); + + size_t texcount = 1; + if (params.type == GPU_TEX_CUBE_MAP) + texcount = 6; + + // Upload texture(s) to VRAM + for (size_t i = 0; i < texcount; ++i) + C3D_TexLoadImage(tex, (u8*)texdata + i * base_texsize, i, -1); + + linearFree(texdata); + } else if (params.type == GPU_TEX_CUBE_MAP) + { + decompressIOVec iov[6]; + + // Setup IO vectors + for (size_t i = 0; i < 6; ++i) + { + u32 size; + iov[i].data = C3D_TexCubeGetImagePtr(tex, i, -1, &size); + iov[i].size = size; + } + + // Decompress into texture memory + if (!decompressV(iov, 6, callback, userdata, insize)) + { + C3D_TexDelete(tex); + free(texture); + return NULL; + } + } else + { + u32 size; + void* data = C3D_Tex2DGetImagePtr(tex, -1, &size); + + // Decompress into texture memory + if (!decompress(data, size, callback, userdata, insize)) + { + C3D_TexDelete(tex); + free(texture); + return NULL; + } + } + + return texture; +} + +Tex3DS_Texture +Tex3DS_TextureImport(const void* input, size_t insize, C3D_Tex* tex, C3D_TexCube* texcube, bool vram) +{ + return Tex3DSi_ImportCommon(tex, texcube, vram, NULL, (void*)input, insize); +} + +Tex3DS_Texture +Tex3DS_TextureImportCallback(C3D_Tex* tex, C3D_TexCube* texcube, bool vram, decompressCallback callback, void* userdata) +{ + return Tex3DSi_ImportCommon(tex, texcube, vram, callback, userdata, 0); +} + +Tex3DS_Texture +Tex3DS_TextureImportFD(int fd, C3D_Tex* tex, C3D_TexCube* texcube, bool vram) +{ + return Tex3DSi_ImportCommon(tex, texcube, vram, decompressCallback_FD, &fd, 0); +} + +Tex3DS_Texture +Tex3DS_TextureImportStdio(FILE* fp, C3D_Tex* tex, C3D_TexCube* texcube, bool vram) +{ + return Tex3DSi_ImportCommon(tex, texcube, vram, decompressCallback_Stdio, fp, 0); +} + +const size_t +Tex3DS_GetNumSubTextures(const Tex3DS_Texture texture) +{ + return texture->numSubTextures; +} + +const Tex3DS_SubTexture* +Tex3DS_GetSubTexture(const Tex3DS_Texture texture, size_t index) +{ + if (index < texture->numSubTextures) + return &texture->subTextures[index]; + return NULL; +} + +void Tex3DS_TextureFree(Tex3DS_Texture texture) +{ + free(texture); +}