// // TextureData.cpp // // Low-level texture data functions. // #include "resources/TextureData.h" #include "math/Misc.h" #include "renderers/Renderer.h" #include "resources/ResourceManager.h" #include "ImageIO.h" #include "Log.h" #include #include #include #include #define DPI 96 TextureData::TextureData( bool tile) : mTile(tile), mTextureID(0), mDataRGBA(nullptr), mScalable(false), mWidth(0), mHeight(0), mSourceWidth(0.0f), mSourceHeight(0.0f) { } TextureData::~TextureData() { releaseVRAM(); releaseRAM(); } void TextureData::initFromPath(const std::string& path) { // Just set the path. It will be loaded later. mPath = path; // Only textures with paths are reloadable. mReloadable = true; } bool TextureData::initSVGFromMemory(const unsigned char* fileData, size_t length) { // If already initialised then don't read again. std::unique_lock lock(mMutex); if (mDataRGBA) return true; // nsvgParse excepts a modifiable, null-terminated string. char* copy = (char*)malloc(length + 1); assert(copy != NULL); memcpy(copy, fileData, length); copy[length] = '\0'; NSVGimage* svgImage = nsvgParse(copy, "px", DPI); free(copy); if (!svgImage) { LOG(LogError) << "Error parsing SVG image."; return false; } // We want to rasterise this texture at a specific resolution. If the source size // variables are set then use them otherwise set them from the parsed file. if ((mSourceWidth == 0.0f) && (mSourceHeight == 0.0f)) { mSourceWidth = svgImage->width; mSourceHeight = svgImage->height; } mWidth = (size_t)Math::round(mSourceWidth); mHeight = (size_t)Math::round(mSourceHeight); if (mWidth == 0) { // Autoscale width to keep aspect. mWidth = (size_t)Math::round(((float)mHeight / svgImage->height) * svgImage->width); } else if (mHeight == 0) { // Autoscale height to keep aspect. mHeight = (size_t)Math::round(((float)mWidth / svgImage->width) * svgImage->height); } unsigned char* dataRGBA = new unsigned char[mWidth * mHeight * 4]; NSVGrasterizer* rast = nsvgCreateRasterizer(); nsvgRasterize(rast, svgImage, 0, 0, mHeight / svgImage->height, dataRGBA, (int)mWidth, (int)mHeight, (int)mWidth * 4); nsvgDeleteRasterizer(rast); ImageIO::flipPixelsVert(dataRGBA, mWidth, mHeight); mDataRGBA = dataRGBA; return true; } bool TextureData::initImageFromMemory(const unsigned char* fileData, size_t length) { size_t width, height; // If already initialised then don't read again. { std::unique_lock lock(mMutex); if (mDataRGBA) return true; } std::vector imageRGBA = ImageIO::loadFromMemoryRGBA32( (const unsigned char*)(fileData), length, width, height); if (imageRGBA.size() == 0) { LOG(LogError) << "Could not initialize texture from memory, invalid data! (file path: " << mPath << ", data ptr: " << (size_t)fileData << ", reported size: " << length << ")"; return false; } mSourceWidth = (float) width; mSourceHeight = (float) height; mScalable = false; return initFromRGBA(imageRGBA.data(), width, height); } bool TextureData::initFromRGBA(const unsigned char* dataRGBA, size_t width, size_t height) { // If already initialised then don't read again. std::unique_lock lock(mMutex); if (mDataRGBA) return true; // Take a copy. mDataRGBA = new unsigned char[width * height * 4]; memcpy(mDataRGBA, dataRGBA, width * height * 4); mWidth = width; mHeight = height; return true; } bool TextureData::load() { bool retval = false; // Need to load. See if there is a file. if (!mPath.empty()) { std::shared_ptr& rm = ResourceManager::getInstance(); const ResourceData& data = rm->getFileData(mPath); // Is it an SVG? if (mPath.substr(mPath.size() - 4, std::string::npos) == ".svg") { mScalable = true; retval = initSVGFromMemory((const unsigned char*)data.ptr.get(), data.length); } else { retval = initImageFromMemory((const unsigned char*)data.ptr.get(), data.length); } } return retval; } bool TextureData::isLoaded() { std::unique_lock lock(mMutex); if (mDataRGBA || (mTextureID != 0)) return true; return false; } bool TextureData::uploadAndBind() { // See if it's already been uploaded. std::unique_lock lock(mMutex); if (mTextureID != 0) { Renderer::bindTexture(mTextureID); } else { // Load it if necessary. if (!mDataRGBA) return false; // Make sure we're ready to upload. if ((mWidth == 0) || (mHeight == 0) || (mDataRGBA == nullptr)) return false; // Upload texture. mTextureID = Renderer::createTexture(Renderer::Texture::RGBA, true, mTile, mWidth, mHeight, mDataRGBA); } return true; } void TextureData::releaseVRAM() { std::unique_lock lock(mMutex); if (mTextureID != 0) { Renderer::destroyTexture(mTextureID); mTextureID = 0; } } void TextureData::releaseRAM() { std::unique_lock lock(mMutex); delete[] mDataRGBA; mDataRGBA = 0; } size_t TextureData::width() { if (mWidth == 0) load(); return mWidth; } size_t TextureData::height() { if (mHeight == 0) load(); return mHeight; } float TextureData::sourceWidth() { if (mSourceWidth == 0) load(); return mSourceWidth; } float TextureData::sourceHeight() { if (mSourceHeight == 0) load(); return mSourceHeight; } void TextureData::setSourceSize(float width, float height) { if (mScalable) { if ((mSourceWidth != width) || (mSourceHeight != height)) { mSourceWidth = width; mSourceHeight = height; releaseVRAM(); releaseRAM(); } } } size_t TextureData::getVRAMUsage() { if ((mTextureID != 0) || (mDataRGBA != nullptr)) return mWidth * mHeight * 4; else return 0; }