From 1e29caa18fe32e813f2d718acb65ba3bd4d9f0f5 Mon Sep 17 00:00:00 2001 From: myst6re Date: Tue, 17 Oct 2023 23:42:16 +0200 Subject: [PATCH] FF8: Add support for palette changes in external textures --- Changelog.md | 2 + docs/ff8/mods/external_textures.md | 13 +- src/common.cpp | 104 +-- src/ff8.h | 3 + src/ff8/mod.cpp | 637 +++++++++++++++++++ src/ff8/mod.h | 181 ++++++ src/ff8/texture_packer.cpp | 988 ++++++++++------------------- src/ff8/texture_packer.h | 219 +++---- src/ff8/vram.cpp | 123 +++- src/ff8_data.cpp | 3 + src/ff8_opengl.cpp | 6 +- src/image/image.cpp | 252 ++++++++ src/image/image.h | 30 + src/image/tim.h | 2 +- src/renderer.cpp | 240 +------ src/renderer.h | 19 +- 16 files changed, 1694 insertions(+), 1128 deletions(-) create mode 100644 src/ff8/mod.cpp create mode 100644 src/ff8/mod.h create mode 100644 src/image/image.cpp create mode 100644 src/image/image.h diff --git a/Changelog.md b/Changelog.md index bdad7eca..7c7991b9 100644 --- a/Changelog.md +++ b/Changelog.md @@ -33,6 +33,8 @@ - Graphics: Fix external texture blending ( https://github.com/julianxhokaxhiu/FFNx/pull/598 https://github.com/julianxhokaxhiu/FFNx/pull/601 ) - Graphics: Add chara.one worldmap texture replacement ( https://github.com/julianxhokaxhiu/FFNx/pull/615 ) - Graphics: Add support for external texture animation ( https://github.com/julianxhokaxhiu/FFNx/pull/616 ) +- Graphics: Add support for multiple palettes in external texture ( https://github.com/julianxhokaxhiu/FFNx/pull/617 ) +- Graphics: Minimize texture uploads when the palette is not set yet ( https://github.com/julianxhokaxhiu/FFNx/pull/617 ) - Graphics: Increase max texture size to 16384 for external textures ( https://github.com/julianxhokaxhiu/FFNx/pull/601 ) - Music: Add `ff8_external_music_force_original_filenames` option to use original music names (eg 018s-julia.ogg) instead of just the main identifier in external music ( https://github.com/julianxhokaxhiu/FFNx/pull/594 ) - Voice: Enable battle dialogs voice acting diff --git a/docs/ff8/mods/external_textures.md b/docs/ff8/mods/external_textures.md index e12bd538..943c58e2 100644 --- a/docs/ff8/mods/external_textures.md +++ b/docs/ff8/mods/external_textures.md @@ -9,12 +9,13 @@ Except for the Menu module, you can add the language at the beginning of the pat Path: `{mod_path}\cardgame` -| File name | Description | Animated | -| --------- | ----------------------- | -------- | -| cards_00 | Cards front and back | No | -| game_00 | Game background | No | -| icons_00 | Numbers, icons and text | No | -| intro_00 | Intro/outro background | No | +| File name | Description | Animated | Multi palettes | +| --------- | ----------------------- | -------- | -------------- | +| cards_00 | Cards front and back | No | No | +| game_00 | Game background | No | No | +| icons_00 | Numbers, icons and text | No | No | +| intro_00 | Intro/outro background | No | No | +| font_00 | Font (unused) | No | Yes (2) | ## Battle diff --git a/src/common.cpp b/src/common.cpp index d02d6b3e..763eb31f 100644 --- a/src/common.cpp +++ b/src/common.cpp @@ -169,9 +169,6 @@ GamepadAnalogueIntent gamepad_analogue_intent = INTENT_NONE; uint32_t *image_data_cache = nullptr; uint32_t image_data_size_cache = 0; -uint8_t *image_data_scaled_cache = nullptr; -uint32_t image_data_scaled_size_cache = 0; - uint32_t noop() { return 0; } uint32_t noop_a1(uint32_t a1) { return 0; } uint32_t noop_a2(uint32_t a1, uint32_t a2) { return 0; } @@ -1429,40 +1426,6 @@ void blit_framebuffer_texture(struct texture_set *texture_set, struct tex_header ); } - -// Scale 32-bit BGRA image in place -void scale_up_image_data_in_place(uint8_t *sourceAndTarget, int w, int h, int scale) -{ - if (scale <= 1) - { - return; - } - - uint32_t *source = (uint32_t *)sourceAndTarget + w * h, - *target = (uint32_t *)sourceAndTarget + (w * scale) * (h * scale); - - for (int y = 0; y < h; ++y) - { - uint32_t *source_line_start = source; - - for (int i = 0; i < scale; ++i) - { - source = source_line_start; - - for (int x = 0; x < w; ++x) - { - source -= 1; - target -= scale; - - for (int i = 0; i < scale; ++i) - { - target[i] = *source; - } - } - } - } -} - // load modpath texture for tex file, returns true if successful uint32_t load_external_texture(void* image_data, uint32_t dataSize, struct texture_set *texture_set, struct tex_header *tex_header, uint32_t originalWidth, uint32_t originalHeight) { @@ -1505,50 +1468,31 @@ uint32_t load_external_texture(void* image_data, uint32_t dataSize, struct textu } else if(ff8) { - uint8_t scale = texturePacker.getMaxScale(VREF(tex_header, image_data)); - uint8_t *image_data_scaled = (uint8_t *)image_data; - - if (scale > 1) - { - uint32_t image_data_size = originalWidth * scale * originalHeight * scale * 4; - // Allocate with cache - if (image_data_scaled_size_cache == 0 || image_data_size > image_data_scaled_size_cache) { - if (image_data_scaled_cache != nullptr) { - driver_free(image_data_scaled_cache); - } - image_data_scaled_cache = (uint8_t*)driver_malloc(image_data_size); - image_data_scaled_size_cache = image_data_size; - } - - image_data_scaled = image_data_scaled_cache; - - // convert source data - if (image_data_scaled != nullptr) - { - memcpy(image_data_scaled, image_data, dataSize); - scale_up_image_data_in_place(image_data_scaled, originalWidth, originalHeight, scale); - } - } + uint32_t *image_data_scaled = nullptr; + uint8_t scale = 1; + TexturePacker::TextureTypes textureType = texturePacker.drawTextures( + VREF(tex_header, image_data), reinterpret_cast(image_data), dataSize, originalWidth, originalHeight, + VREF(tex_header, palette_index) / 2, + &scale, &image_data_scaled + ); - TexturePacker::TextureTypes textureType = TexturePacker::NoTexture; + if (save_textures && textureType != TexturePacker::InternalTexture) return false; - if (image_data_scaled != nullptr && scale > 0) + if (textureType == TexturePacker::NoTexture) { - textureType = texturePacker.drawTextures(VREF(tex_header, image_data), tex_format, (uint32_t *)image_data_scaled, (uint32_t *)image_data, originalWidth, originalHeight, scale, VREF(tex_header, palette_index)); + if(VREF(texture_set, ogl.external)) stats.external_textures--; + VRASS(texture_set, ogl.external, false); } - - if(save_textures && textureType != TexturePacker::InternalTexture) return false; - - if (textureType != TexturePacker::NoTexture) + else if (textureType == TexturePacker::RemoveTexture) { - VREF(texture_set, ogl.width) = originalWidth * scale; - VREF(texture_set, ogl.height) = originalHeight * scale; - texture = newRenderer.createTexture(image_data_scaled, VREF(texture_set, ogl.width), VREF(texture_set, ogl.height)); + ffnx_trace("Remove texture\n"); + return true; } else { - if(VREF(texture_set, ogl.external)) stats.external_textures--; - VRASS(texture_set, ogl.external, false); + VREF(texture_set, ogl.width) = originalWidth * scale; + VREF(texture_set, ogl.height) = originalHeight * scale; + texture = newRenderer.createTexture(reinterpret_cast(image_data_scaled), VREF(texture_set, ogl.width), VREF(texture_set, ogl.height)); } if (textureType == TexturePacker::InternalTexture) @@ -1768,8 +1712,18 @@ struct texture_set *common_load_texture(struct texture_set *_texture_set, struct if(tex_format->palettes == 0) tex_format->palettes = VREF(tex_header, palette_entries); // convert texture data from source format and load it - if(texture_format != 0 && VREF(tex_header, image_data) != 0 && (! ff8 || ! texturePacker.drawTexturesBackgroundIsDisabled())) + if(texture_format != 0 && VREF(tex_header, image_data) != 0) { + if (ff8) + { + // optimization to not upload textures with undefined VRAM palette + TexturePacker::TiledTex tiledTex = texturePacker.getTiledTex(VREF(tex_header, image_data)); + if (tiledTex.isValid() && !tiledTex.isPaletteValid(VREF(tex_header, palette_index) / 2)) + { + return _texture_set; + } + } + // detect changes in palette data for FF8, we can't trust it to notify us if(ff8 && VREF(tex_header, palettes) > 0 && VREF(tex_header, version) != FB_TEX_VERSION && tex_format->bytesperpixel == 1) { @@ -1904,7 +1858,7 @@ uint32_t common_write_palette(uint32_t source_offset, uint32_t size, void *sourc VOBJ(texture_set, texture_set, texture_set); VOBJ(tex_header, tex_header, VREF(texture_set, tex_header)); - if(trace_all) ffnx_trace("dll_gfx: write_palette 0x%x, %i, %i, %i, 0x%x, 0x%x\n", texture_set, source_offset, dest_offset, size, source, palette->palette_entry); + if(trace_all) ffnx_trace("dll_gfx: write_palette 0x%x, %i, %i, %i, 0x%x, 0x%x, image_data=0x%X\n", texture_set, source_offset, dest_offset, size, source, palette->palette_entry, VREF(tex_header, image_data)); if(palette == 0) return false; diff --git a/src/ff8.h b/src/ff8.h index 6d361d9f..6746f73a 100644 --- a/src/ff8.h +++ b/src/ff8.h @@ -1218,6 +1218,9 @@ struct ff8_externals uint32_t sub_462DF0; uint32_t sub_461220; uint32_t ssigpu_tx_select_2_sub_465CE0; + uint32_t write_palette_texture_set_sub_466190; + uint32_t read_vram_palette_sub_467370; + uint32_t write_palette_to_driver_sub_467310; int (*sub_464F70)(struc_50 *, texture_page *, int, int, int, int, int, int, int, uint8_t *); void(*read_vram_1)(uint8_t *, int, uint8_t *, int, signed int, int, int); void(*read_vram_2_paletted)(uint8_t *, int, uint8_t *, int, signed int, int, int, uint16_t *); diff --git a/src/ff8/mod.cpp b/src/ff8/mod.cpp new file mode 100644 index 00000000..a9d8f8ea --- /dev/null +++ b/src/ff8/mod.cpp @@ -0,0 +1,637 @@ +/****************************************************************************/ +// Copyright (C) 2009 Aali132 // +// Copyright (C) 2018 quantumpencil // +// Copyright (C) 2018 Maxime Bacoux // +// Copyright (C) 2020 Chris Rizzitello // +// Copyright (C) 2020 John Pritchard // +// Copyright (C) 2023 myst6re // +// Copyright (C) 2023 Julian Xhokaxhiu // +// Copyright (C) 2023 Tang-Tang Zhou // +// // +// This file is part of FFNx // +// // +// FFNx is free software: you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation, either version 3 of the License // +// // +// FFNx is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License for more details. // +/****************************************************************************/ +#include "mod.h" + +#include + +#include "../image/image.h" +#include "../log.h" +#include "../renderer.h" +#include "file.h" + + +bx::DefaultAllocator TextureImage::defaultAllocator; + +TextureImage::TextureImage() : + _image(nullptr), _scale(1) +{ +} + +bool TextureImage::createImage(const char *filename, int originalTexturePixelWidth, int originalTextureHeight, int internalLodScale) +{ + if (_image != nullptr) + { + destroyImage(); + } + + _image = loadImageContainer(&defaultAllocator, filename, bimg::TextureFormat::BGRA8); + + if (_image == nullptr) + { + return false; + } + + setLod(computeLod(originalTexturePixelWidth, internalLodScale)); + + uint8_t scale = computeScale(originalTexturePixelWidth, originalTextureHeight); + + if (trace_all || trace_vram) + { + ffnx_info("TextureImage::%s: size=%dx%d scaled to %dx%d (x%d) format=%d depth=%d numMips=%d hasAlpha=%d\n", __func__, + _image->m_width, _image->m_height, _mip.m_width, _mip.m_height, scale, _mip.m_format, _mip.m_depth, _image->m_numMips, _mip.m_hasAlpha); + } + + if (scale == 0) + { + destroyImage(); + + return false; + } + + _scale = scale; + + return true; +} + +void TextureImage::destroyImage() +{ + if (_image != nullptr) + { + bimg::imageFree(_image); + _image = nullptr; + } +} + +void TextureImage::setLod(uint8_t lod) +{ + if (_image != nullptr) + { + bimg::imageGetRawData(*_image, 0, lod, _image->m_data, _image->m_size, _mip); + } +} + +int TextureImage::computeMaxScale() +{ + int resWidth = window_size_x * newRenderer.getScalingFactor(), + baseResW = FF8_BASE_RESOLUTION_X; + + return std::max(resWidth, FF8_BASE_RESOLUTION_X) / baseResW; +} + +uint8_t TextureImage::computeLod(int originalTexturePixelWidth, int internalScale) const +{ + if (_image == nullptr || _image->m_numMips <= 1) + { + return 0; + } + + int maxScale = computeMaxScale(), + maxWidth = originalTexturePixelWidth * maxScale * internalScale, + imageWidth = _image->m_width; + + if (trace_all || trace_vram) + { + ffnx_info("ModdedTexture::%s: maxScale=%d maxWidth=%d imageWidth=%d\n", __func__, maxScale, maxWidth, imageWidth); + } + + for (uint8_t lod = 0; lod < _image->m_numMips; ++lod) + { + if (imageWidth == maxWidth) + { + return lod; + } + + imageWidth /= 2; + } + + ffnx_warning("ModdedTexture::%s: Cannot detect the LOD of the texture\n", __func__); + + return 0; +} + +uint8_t TextureImage::computeScale(int sourcePixelW, int sourceH) const +{ + int targetPixelW = _mip.m_width, targetH = _mip.m_height; + + if (targetPixelW < sourcePixelW + || targetH < sourceH + || targetPixelW % sourcePixelW != 0 + || targetH % sourceH != 0) + { + ffnx_warning("Texture redirection size must be scaled to the original texture size with the same ratio (modded texture size: %dx%d, original texture size: %dx%d)\n", targetPixelW, targetH, sourcePixelW, sourceH); + + return 0; + } + + int scaleW = targetPixelW / sourcePixelW, scaleH = targetH / sourceH; + + if (scaleW != scaleH) + { + ffnx_warning("Texture redirection size must have the same ratio as the original texture: (%d / %d)\n", sourcePixelW, sourceH); + + return 0; + } + + if (scaleW > MAX_SCALE) + { + ffnx_warning("External texture size cannot exceed \"original size * %d\" (scale=%d)\n", MAX_SCALE, scaleW); + + return 0; + } + + return scaleW; +} + +ModdedTexture::ModdedTexture(const TexturePacker::IdentifiedTexture &originalTexture, bool isInternal): + _originalTexture(originalTexture), _isInternal(isInternal) +{ +} + +bool ModdedTexture::findExternalTexture(const char *name, char *filename, uint8_t palette_index, bool hasPal, const char *extension, char *found_extension) +{ + char langPath[16] = "/"; + + if(trace_all || trace_loaders) ffnx_trace("texture file name (VRAM): %s\n", name); + + if(save_textures) return false; + + ff8_fs_lang_string(langPath + 1); + + for (size_t idx = 0; idx < mod_ext.size(); idx++) + { + // Force extension + if (extension && stricmp(extension, mod_ext[idx].c_str()) != 0) + { + continue; + } + + for (uint8_t lang = 0; lang < 2; lang++) + { + if (hasPal) + { + _snprintf(filename, MAX_PATH, "%s/%s%s/%s_%02i.%s", basedir, mod_path.c_str(), langPath, name, palette_index, mod_ext[idx].c_str()); + } + else + { + _snprintf(filename, MAX_PATH, "%s/%s%s/%s.%s", basedir, mod_path.c_str(), langPath, name, mod_ext[idx].c_str()); + } + + if (fileExists(filename)) + { + if (trace_all || trace_loaders) ffnx_trace("Using texture: %s\n", filename); + + if (found_extension != nullptr) { + strncpy(found_extension, mod_ext[idx].c_str(), mod_ext[idx].size()); + } + + return true; + } + else if (trace_all || trace_loaders) + { + ffnx_warning("Texture does not exist, skipping: %s\n", filename); + } + + *langPath = '\0'; + } + + *langPath = '/'; + } + + return false; +} + +void ModdedTexture::drawImage( + const uint32_t *sourceRgba, int sourceRgbaW, uint8_t sourceScale, + uint32_t *targetRgba, int targetRgbaW, uint8_t targetScale, + int sourceX, int sourceY, int sourceW, int sourceH, + int targetX, int targetY) +{ + if (targetScale < sourceScale) + { + return; + } + + sourceRgbaW *= sourceScale; + targetRgbaW *= targetScale; + const uint8_t scaleRatio = targetScale / sourceScale; + + for (int y = 0; y < sourceH; ++y) + { + for (int x = 0; x < sourceW; ++x) + { + const uint32_t *sourceRgbaCur = sourceRgba + (sourceX + x) * sourceScale + (sourceY + y) * sourceScale * sourceRgbaW; + uint32_t *targetRgbaCur = targetRgba + (targetX + x) * targetScale + (targetY + y) * targetScale * targetRgbaW; + + for (int yPix = 0; yPix < targetScale; ++yPix) + { + for (int xPix = 0; xPix < targetScale; ++xPix) + { + uint32_t color = *(sourceRgbaCur + xPix / scaleRatio + yPix / scaleRatio * sourceRgbaW), + alpha = color & 0xFF000000; + *(targetRgbaCur + xPix + yPix * targetRgbaW) = (color & 0xFFFFFF) | (alpha >= 0x7F000000 ? 0x7F000000 : 0); + } + } + } + } +} + +void ModdedTexture::copyRect( + const uint32_t *sourceRgba, int sourceRgbaW, uint32_t *targetRgba, int targetRgbaW, uint8_t scale, Tim::Bpp depth, + int sourceXBpp2, int sourceY, int sourceWBpp2, int sourceH, int targetXBpp2, int targetY) +{ + int sourceX = sourceXBpp2 * (4 >> int(depth)), + sourceW = sourceWBpp2 * (4 >> int(depth)), + targetX = targetXBpp2 * (4 >> int(depth)); + + const uint32_t *source = sourceRgba + (sourceX + sourceY * sourceRgbaW) * scale; + uint32_t *target = targetRgba + (targetX + targetY * targetRgbaW) * scale; + + for (int y = 0; y < sourceH; ++y) + { + memcpy(target, source, sizeof(uint32_t) * sourceW); + + source += sourceRgbaW; + target += targetRgbaW; + } +} + +TextureModStandard::~TextureModStandard() +{ + for (std::pair image: _textures) + { + image.second.destroyImage(); + } +} + +bool TextureModStandard::createImages(int internalLodScale) +{ + if (trace_all || trace_vram) ffnx_trace("TextureModStandard::%s: internalLodScale=%d\n", __func__, internalLodScale); + + int paletteCount = originalTexture().palette().h(); // Works most of the time + int modCount = std::max(paletteCount, 1); + char filename[MAX_PATH] = {}, *extension = nullptr, found_extension[16] = {}; + + for (int paletteId = 0; paletteId < modCount; ++paletteId) { + if (!findExternalTexture(originalTexture().name().c_str(), filename, paletteId, true, extension, found_extension)) + { + continue; + } + + // Force the same extension for every palettes to reduce file existence checks + extension = found_extension; + + TextureImage externalTexture; + if (!externalTexture.createImage(filename, originalTexture().texture().pixelW(), originalTexture().texture().h(), internalLodScale)) + { + continue; + } + + _textures[paletteId] = externalTexture; + } + + return !_textures.empty(); +} + +uint8_t TextureModStandard::computePaletteId(int vramPalXBpp2, int vramPalY) const +{ + if (vramPalXBpp2 == originalTexture().palette().x() + && vramPalY >= originalTexture().palette().y() && vramPalY < originalTexture().palette().y() + originalTexture().palette().h()) + { + return vramPalY - originalTexture().palette().y(); + } + + return 0; +} + +const TextureImage &TextureModStandard::textureImage(uint8_t paletteId) const +{ + if (trace_all || trace_vram) ffnx_trace("TextureModStandard::%s paletteId=%d\n", __func__, paletteId); + + auto it = _textures.find(paletteId); + + if (it == _textures.end()) + { + if (trace_all || trace_vram) ffnx_warning("TextureModStandard::%s cannot find image for paletteId=%d, fallback to the first one\n", __func__, paletteId); + + return _textures.begin()->second; // Use the first image + } + + return it->second; +} + +uint8_t TextureModStandard::scale(int vramPalXBpp2, int vramPalY) const +{ + if (trace_all || trace_vram) ffnx_trace("TextureModStandard::%s %s vramPal=(%d, %d) original=(%d, %d)\n", __func__, originalTexture().name().c_str(), vramPalXBpp2, vramPalY, originalTexture().palette().x(), originalTexture().palette().y()); + + return textureImage(computePaletteId(vramPalXBpp2, vramPalY)).scale(); +} + +TexturePacker::TextureTypes TextureModStandard::drawToImage( + int offsetX, int offsetY, + uint32_t *targetRgba, int targetW, int targetH, uint8_t targetScale, Tim::Bpp targetBpp, + int16_t vramPalXBpp2, int16_t vramPalY) const +{ + const TexturePacker::TextureInfos &origTexture = originalTexture().texture(); + + if (origTexture.bpp() != targetBpp) + { + return TexturePacker::NoTexture; + } + + int sourceX = offsetX < 0 ? -offsetX : 0, + sourceY = offsetY < 0 ? -offsetY : 0, + targetX = offsetX > 0 ? offsetX : 0, + targetY = offsetY > 0 ? offsetY : 0, + width = std::min(origTexture.pixelW() - sourceX, targetW - targetX), + height = std::min(origTexture.h() - sourceY, targetH - targetY); + + if (width <= 0 || height <= 0) + { + return TexturePacker::NoTexture; + } + + uint8_t paletteId = computePaletteId(vramPalXBpp2, vramPalY); + const TextureImage &image = textureImage(paletteId); + const bimg::ImageMip &mip = image.mip(); + + drawImage( + (const uint32_t *)mip.m_data, mip.m_width / image.scale(), image.scale(), + targetRgba, targetW, targetScale, + sourceX, sourceY, width, height, + targetX, targetY + ); + + return TexturePacker::ExternalTexture; +} + +void TextureModStandard::copyRect(int sourceXBpp2, int sourceY, int sourceWBpp2, int sourceH, int targetXBpp2, int targetY) +{ + const TexturePacker::TextureInfos &texture = originalTexture().texture(); + + for (const std::pair &image: _textures) + { + const bimg::ImageMip &mip = image.second.mip(); + ModdedTexture::copyRect( + reinterpret_cast(mip.m_data), mip.m_width, + const_cast(reinterpret_cast(mip.m_data)), mip.m_width, + image.second.scale(), texture.bpp(), + sourceXBpp2 - texture.x(), sourceY - texture.y(), sourceWBpp2, sourceH, + targetXBpp2 - texture.x(), targetY - texture.y() + ); + } +} + +void TextureModStandard::copyRect( + int sourceXBpp2, int sourceY, int sourceWBpp2, int sourceH, int targetXBpp2, int targetY, + const TextureModStandard &targetTexture) +{ + const TexturePacker::TextureInfos &tex = originalTexture().texture(), + &targetTex = targetTexture.originalTexture().texture(); + + if (tex.bpp() != targetTex.bpp()) { + return; + } + + for (const std::pair &image: _textures) + { + for (const std::pair &targetImage: targetTexture._textures) + { + const bimg::ImageMip &mip = image.second.mip(); + const bimg::ImageMip &targetMip = targetImage.second.mip(); + + if (image.second.scale() != targetImage.second.scale()) { + continue; + } + + ModdedTexture::copyRect( + reinterpret_cast(mip.m_data), mip.m_width, + const_cast(reinterpret_cast(targetMip.m_data)), targetMip.m_width, + image.second.scale(), tex.bpp(), + sourceXBpp2 - tex.x(), sourceY - tex.y(), sourceWBpp2, sourceH, + targetXBpp2 - targetTex.x(), targetY - targetTex.y() + ); + } + } +} + +TextureBackground::TextureBackground( + const TexturePacker::IdentifiedTexture &originalTexture, + const std::vector &mapTiles, + int vramPageId +) : ModdedTexture(originalTexture), _mapTiles(mapTiles), _vramPageId(vramPageId) +{ +} + +TextureBackground::~TextureBackground() +{ + _texture.destroyImage(); +} + +bool TextureBackground::createImages(const char *extension, char *foundExtension) +{ + size_t size = _mapTiles.size(); + + _colsCount = size / (TEXTURE_HEIGHT / TILE_SIZE) + int(size % (TEXTURE_HEIGHT / TILE_SIZE) != 0); + + char filename[MAX_PATH] = {}; + + if (!findExternalTexture(originalTexture().name().c_str(), filename, 0, false, extension, foundExtension)) + { + return false; + } + + if (!_texture.createImage(filename, _vramPageId >= 0 ? TEXTURE_HEIGHT : _colsCount * TILE_SIZE, TEXTURE_HEIGHT)) + { + return false; + } + + // Build tileIdsByPosition for fast lookup + _tileIdsByTextureId.reserve(size); + + _usedBpps = 0; // 1: 4-bit, 2: 8-bit, 4: 16-bit + size_t tileId = 0; + for (const Tile &tile: _mapTiles) { + const uint8_t texId = tile.texID & 0xF; + if (_vramPageId < 0 || _vramPageId == texId) { + const uint8_t bpp = (tile.texID >> 7) & 3; + const uint16_t key = uint16_t(texId) | (uint16_t(tile.srcX / TILE_SIZE) << 4) | (uint16_t(tile.srcY / TILE_SIZE) << 8) | (uint16_t(bpp) << 12); + _usedBpps |= 1 << bpp; + + auto it = _tileIdsByTextureId.find(key); + // Remove some duplicates (but keep those which render differently) + if (it == _tileIdsByTextureId.end() || ! ff8_background_tiles_looks_alike(tile, _mapTiles.at(it->second))) { + _tileIdsByTextureId.insert(std::pair(key, tileId)); + } + } + ++tileId; + } + + return true; +} + +bool TextureBackground::isBpp(Tim::Bpp bpp) const +{ + return true; +} + +TexturePacker::TextureTypes TextureBackground::drawToImage( + int offsetX, int offsetY, + uint32_t *targetRgba, int targetW, int targetH, uint8_t targetScale, Tim::Bpp targetBpp, + int16_t vramPalXBpp2, int16_t vramPalY) const +{ + const bimg::ImageMip &mip = _texture.mip(); + const uint32_t *imgData = (const uint32_t *)mip.m_data; + const uint8_t imgScale = _texture.scale(); + const uint32_t imgWidth = mip.m_width / imgScale, imgHeight = mip.m_height / imgScale; + + // Tomberry way + if (_vramPageId >= 0) { + drawImage( + imgData, imgWidth, imgScale, + targetRgba, targetW, targetScale, + 0, 0, imgWidth, imgHeight, + 0, 0 + ); + + return TexturePacker::ExternalTexture; + } + + const uint8_t cols = targetW / TILE_SIZE, rows = targetH / TILE_SIZE; + const uint8_t colsBpp = TILE_SIZE / (1 << uint16_t(targetBpp)); + const uint8_t paletteId = vramPalY - 232 - 8; // The palette always is at (0, 232) and the first 8 palettes are unused + const uint8_t initialTextureId = -offsetX / (4 >> uint16_t(targetBpp)) / TEXTURE_WIDTH_BPP16; + + for (uint8_t y = 0; y < 16; ++y) { + for (uint8_t x = 0; x < 16; ++x) { + const uint8_t textureId = initialTextureId + x / colsBpp; + const uint8_t textureX = x % colsBpp; + const uint16_t key = uint16_t(textureId) | (uint16_t(textureX) << 4) | (uint16_t(y) << 8) | (uint16_t(targetBpp) << 12); + auto [begin, end] = _tileIdsByTextureId.equal_range(key); + if (begin == end) { + if (trace_all || trace_vram) ffnx_warning("TextureBackground::%s: No tile found for textureId=%d x=%d y=%d targetBpp=%d paletteId=%d\n", __func__, textureId, textureX, y, targetBpp, paletteId); + + continue; + } + + const size_t tileIdDefault = begin->second; + std::vector matched; + + // When matching multiple tiles for the same texture part, try to choose the good tile + for (const std::pair &pair: std::ranges::subrange{begin, end}) { + const Tile &tile = _mapTiles.at(pair.second); + uint8_t palId = (tile.palID >> 6) & 0xF; + + if (targetBpp != Tim::Bpp16 && paletteId != palId) { + continue; + } + + /* if (tile.parameter != 255) { + if (tile.parameter < *ff8_externals.field_state_background_count) { + const ff8_field_state_background *field_state_backgrounds = *ff8_externals.field_state_backgrounds; + + if (tile.state != field_state_backgrounds[tile.parameter].bgstate >> 6) { + continue; + } + } else { + if (trace_all || trace_vram) ffnx_warning("TextureBackground::%s: group script not found for background parameter %d\n", __func__, tile.parameter); + + continue; + } + } */ + + matched.push_back(pair.second); + } + + size_t matchedCount = matched.size(), + tileId = matchedCount > 0 ? matched.at(0) : tileIdDefault; + + if (matchedCount > 1) { + if (trace_all || trace_vram) ffnx_warning("TextureBackground::%s: tile %d conflict\n", __func__, tileIdDefault); + } + + const int col = tileId % _colsCount, row = tileId / _colsCount; + + drawImage( + imgData, imgWidth / imgScale, imgScale, + targetRgba, targetW, targetScale, + col * TILE_SIZE, row * TILE_SIZE, TILE_SIZE, TILE_SIZE, + x * TILE_SIZE, y * TILE_SIZE + ); + } + } + + return TexturePacker::ExternalTexture; +} + +TextureRawImage::TextureRawImage( + const TexturePacker::IdentifiedTexture &originalTexture, + uint32_t *imageData, int imageWidth, int imageHeight +) : ModdedTexture(originalTexture, true), _image(imageData), _width(imageWidth), _height(imageHeight), + _scale(computeScale()) +{ +} + +TextureRawImage::~TextureRawImage() +{ + if (_image != nullptr) { + driver_free(_image); + } +} + +uint8_t TextureRawImage::computeScale() const +{ + return _height / originalTexture().texture().h(); +} + +TexturePacker::TextureTypes TextureRawImage::drawToImage( + int offsetX, int offsetY, + uint32_t *targetRgba, int targetW, int targetH, uint8_t targetScale, Tim::Bpp targetBpp, + int16_t vramPalXBpp2, int16_t vramPalY) const +{ + const TexturePacker::TextureInfos &origTexture = originalTexture().texture(); + + if (origTexture.bpp() != targetBpp) + { + return TexturePacker::NoTexture; + } + + int sourceX = offsetX < 0 ? -offsetX : 0, + sourceY = offsetY < 0 ? -offsetY : 0, + targetX = offsetX > 0 ? offsetX : 0, + targetY = offsetY > 0 ? offsetY : 0, + width = std::min(origTexture.pixelW() - sourceX, targetW - targetX), + height = std::min(origTexture.h() - sourceY, targetH - targetY); + + if (width <= 0 || height <= 0) + { + return TexturePacker::NoTexture; + } + + drawImage( + _image, _width / _scale, _scale, + targetRgba, targetW, targetScale, + sourceX, sourceY, width, height, + targetX, targetY + ); + + return TexturePacker::InternalTexture; +} diff --git a/src/ff8/mod.h b/src/ff8/mod.h new file mode 100644 index 00000000..a61e3e13 --- /dev/null +++ b/src/ff8/mod.h @@ -0,0 +1,181 @@ +/****************************************************************************/ +// Copyright (C) 2009 Aali132 // +// Copyright (C) 2018 quantumpencil // +// Copyright (C) 2018 Maxime Bacoux // +// Copyright (C) 2020 Chris Rizzitello // +// Copyright (C) 2020 John Pritchard // +// Copyright (C) 2023 myst6re // +// Copyright (C) 2023 Julian Xhokaxhiu // +// Copyright (C) 2023 Tang-Tang Zhou // +// // +// This file is part of FFNx // +// // +// FFNx is free software: you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation, either version 3 of the License // +// // +// FFNx is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License for more details. // +/****************************************************************************/ + +#pragma once + +#include +#include +#include +#include +#include + +#include "texture_packer.h" +#include "field/background.h" + +class TextureImage { +public: + TextureImage(); + bool createImage(const char *filename, int originalTexturePixelWidth, int originalTextureHeight, int internalLodScale = 1); + void destroyImage(); + inline uint8_t scale() const { + return _scale; + } + inline void setScale(uint8_t scale) { + _scale = scale; + } + inline bool hasImage() const { + return _image != nullptr; + } + inline const bimg::ImageMip &mip() const { + return _mip; + } + static int computeMaxScale(); +private: + void setLod(uint8_t lod); + uint8_t computeLod(int originalTexturePixelWidth, int internalScale = 1) const; + uint8_t computeScale(int originalTexturePixelWidth, int originalTextureHeight) const; + bimg::ImageContainer *_image; + bimg::ImageMip _mip; + uint8_t _scale; + + static bx::DefaultAllocator defaultAllocator; +}; + +class ModdedTexture { +public: + ModdedTexture(const TexturePacker::IdentifiedTexture &originalTexture, bool isInternal = false); + ModdedTexture(const ModdedTexture &other) = delete; + virtual ~ModdedTexture() {} + virtual bool isBpp(Tim::Bpp bpp) const { + return bpp == _originalTexture.texture().bpp(); + } + virtual uint8_t scale(int vramPalXBpp2, int vramPalY) const=0; + inline virtual bool canCopyRect() const { + return false; + } + inline bool isInternal() const { + return _isInternal; + } + virtual TexturePacker::TextureTypes drawToImage( + int offsetX, int offsetY, + uint32_t *targetRgba, int targetW, int targetH, uint8_t targetScale, Tim::Bpp targetBpp, + int16_t paletteVramX, int16_t paletteVramY + ) const=0; +protected: + static bool findExternalTexture(const char *name, char *outFilename, uint8_t palette_index, bool hasPal, const char *extension = nullptr, char *foundExtension = nullptr); + static void drawImage( + const uint32_t *sourceRgba, int sourceRgbaW, uint8_t sourceScale, + uint32_t *targetRgba, int targetRgbaW, uint8_t targetScale, + int sourceX, int sourceY, int sourceW, int sourceH, + int targetX, int targetY + ); + static void copyRect( + const uint32_t *sourceRgba, int sourceRgbaW, uint32_t *targetRgba, int targetRgbaW, uint8_t scale, Tim::Bpp depth, + int sourceXBpp2, int sourceY, int sourceWBpp2, int sourceH, + int targetXBpp2, int targetY + ); + inline const TexturePacker::IdentifiedTexture &originalTexture() const { + return _originalTexture; + } +private: + TexturePacker::IdentifiedTexture _originalTexture; + bool _isInternal; +}; + +class TextureModStandard : public ModdedTexture { +public: + TextureModStandard(const TexturePacker::IdentifiedTexture &originalTexture) : ModdedTexture(originalTexture), _lastDrawnPalette(0) {} + TextureModStandard(const TextureModStandard &other) = delete; + ~TextureModStandard(); + inline bool canCopyRect() const override { + return true; + } + bool createImages(int internalLodScale = 1); + uint8_t scale(int vramPalXBpp2, int vramPalY) const override; + TexturePacker::TextureTypes drawToImage( + int offsetX, int offsetY, + uint32_t *targetRgba, int targetW, int targetH, uint8_t targetScale, Tim::Bpp targetBpp, + int16_t vramPalXBpp2, int16_t vramPalY + ) const override; + // Copy rect inside the textures + void copyRect(int sourceXBpp2, int sourceY, int sourceWBpp2, int sourceH, int targetXBpp2, int targetY); + // Copy rect to another textures + void copyRect(int sourceXBpp2, int sourceY, int sourceWBpp2, int sourceH, int targetXBpp2, int targetY, const TextureModStandard &targetTexture); +private: + bool createImage(const char *name, int paletteId = -1, const char *extension = nullptr, char *foundExtension = nullptr); + uint8_t computePaletteId(int vramPalXBpp2, int vramPalY) const; + const TextureImage &textureImage(uint8_t paletteId) const; + std::map _textures; // Index: paletteId + uint8_t _lastDrawnPalette; +}; + +class TextureBackground : public ModdedTexture { +public: + TextureBackground( + const TexturePacker::IdentifiedTexture &originalTexture, + const std::vector &mapTiles, + int vramPageId + ); + TextureBackground(const TextureBackground &other) = delete; + ~TextureBackground(); + bool isBpp(Tim::Bpp bpp) const override; + uint8_t scale(int vramPalXBpp2, int vramPalY) const override { + return _texture.scale(); + } + bool createImages(const char *extension = nullptr, char *foundExtension = nullptr); + TexturePacker::TextureTypes drawToImage( + int offsetX, int offsetY, + uint32_t *targetRgba, int targetW, int targetH, uint8_t targetScale, Tim::Bpp targetBpp, + int16_t vramPalXBpp2, int16_t vramPalY + ) const override; +private: + std::vector _mapTiles; + std::unordered_multimap _tileIdsByTextureId; + TextureImage _texture; + int _vramPageId; + uint8_t _colsCount, _usedBpps; +}; + +struct TextureRawImage : public ModdedTexture { + TextureRawImage( + const TexturePacker::IdentifiedTexture &originalTexture, + uint32_t *imageData, int imageWidth, int imageHeight + ); + TextureRawImage(const TextureRawImage &other) = delete; + ~TextureRawImage(); + inline bool isValid() const { + return _scale != 0; + } + uint8_t scale(int vramPalXBpp2, int vramPalY) const override { + return _scale; + } + TexturePacker::TextureTypes drawToImage( + int offsetX, int offsetY, + uint32_t *targetRgba, int targetW, int targetH, uint8_t targetScale, Tim::Bpp targetBpp, + int16_t vramPalXBpp2, int16_t vramPalY + ) const override; +private: + uint8_t computeScale() const; + uint32_t *_image; + int _width, _height; + uint8_t _scale; +}; diff --git a/src/ff8/texture_packer.cpp b/src/ff8/texture_packer.cpp index ab9326b8..3c1eea7b 100644 --- a/src/ff8/texture_packer.cpp +++ b/src/ff8/texture_packer.cpp @@ -20,14 +20,49 @@ // GNU General Public License for more details. // /****************************************************************************/ #include "texture_packer.h" -#include "../renderer.h" #include "../saveload.h" #include "../patch.h" -#include "file.h" +#include "mod.h" #include +uint32_t *image_data_scaled_cache = nullptr; +uint32_t image_data_scaled_size_cache = 0; + +// Scale 32-bit BGRA image in place +void scale_up_image_data_in_place(uint32_t *sourceAndTarget, uint32_t w, uint32_t h, uint8_t scale) +{ + if (scale <= 1) + { + return; + } + + uint32_t *source = sourceAndTarget + w * h, + *target = sourceAndTarget + (w * scale) * (h * scale); + + for (int y = 0; y < h; ++y) + { + uint32_t *source_line_start = source; + + for (int i = 0; i < scale; ++i) + { + source = source_line_start; + + for (int x = 0; x < w; ++x) + { + source -= 1; + target -= scale; + + for (int i = 0; i < scale; ++i) + { + target[i] = *source; + } + } + } + } +} + TexturePacker::TexturePacker() : - _vramTextureIds(VRAM_WIDTH * VRAM_HEIGHT, INVALID_TEXTURE), _disableDrawTexturesBackground(false) + _vramTextureIds(VRAM_WIDTH * VRAM_HEIGHT, INVALID_TEXTURE) { } @@ -40,50 +75,38 @@ void TexturePacker::cleanVramTextureIds(const TextureInfos &texture) } } -void TexturePacker::cleanTextures(ModdedTextureId previousTextureId, bool keepMods) +void TexturePacker::cleanTextures(ModdedTextureId previousTextureId) { - ModdedTextureId previousTextureIdStandard = setTextureIdCategory(previousTextureId, TextureCategoryStandard), - previousTextureIdBackground = setTextureIdCategory(previousTextureId, TextureCategoryBackground), - previousTextureIdRedirection = setTextureIdCategory(previousTextureId, TextureCategoryRedirection); + auto it = _textures.find(previousTextureId); - if (!keepMods && _externalTextures.contains(previousTextureIdStandard)) + if (it == _textures.end()) { - Texture &previousTexture = _externalTextures.at(previousTextureIdStandard); - - if (trace_all || trace_vram) ffnx_info("TexturePacker::%s: clear texture %s (textureId = %d)\n", __func__, previousTexture.name().c_str(), previousTextureIdStandard); - - cleanVramTextureIds(previousTexture); - - previousTexture.destroyImage(); - _externalTextures.erase(previousTextureIdStandard); + return; } - else if (!keepMods && _backgroundTextures.contains(previousTextureIdBackground)) - { - TextureBackground &previousTexture = _backgroundTextures.at(previousTextureIdBackground); - if (trace_all || trace_vram) ffnx_info("TexturePacker::%s: clear texture background %s (textureId = %d)\n", __func__, previousTexture.name().c_str(), previousTextureIdBackground); + if (trace_all || trace_vram) ffnx_info("TexturePacker::%s: clear texture %s (textureId = %d)\n", __func__, it->second.name().empty() ? "N/A" : it->second.name().c_str(), previousTextureId); - cleanVramTextureIds(previousTexture); + cleanVramTextureIds(it->second.texture()); - previousTexture.destroyImage(); - _backgroundTextures.erase(previousTextureIdBackground); - } - else if (_textureRedirections.contains(previousTextureIdRedirection)) + if (it->second.mod() != nullptr) { - TextureRedirection &previousTexture = _textureRedirections.at(previousTextureIdRedirection); - - if (trace_all || trace_vram) ffnx_info("TexturePacker::%s: clear texture redirection (textureId = %d)\n", __func__, previousTextureIdRedirection); - - cleanVramTextureIds(previousTexture.oldTexture()); + delete it->second.mod(); + } - previousTexture.destroyImage(); - _textureRedirections.erase(previousTextureIdRedirection); + for (const std::pair &pair: it->second.redirections()) + { + if (pair.second.mod() != nullptr) + { + delete pair.second.mod(); + } } + + _textures.erase(it); } -void TexturePacker::setVramTextureId(ModdedTextureId textureId, int xBpp2, int y, int wBpp2, int h, bool keepMods) +void TexturePacker::setVramTextureId(ModdedTextureId textureId, int xBpp2, int y, int wBpp2, int h, bool clearOldTexture) { - if (trace_all || trace_vram) ffnx_trace("%s: textureId=%d xBpp2=%d y=%d wBpp2=%d h=%d keepMods=%d\n", __func__, textureId, xBpp2, y, wBpp2, h, keepMods); + if (trace_all || trace_vram) ffnx_trace("%s: textureId=%d xBpp2=%d y=%d wBpp2=%d h=%d\n", __func__, textureId, xBpp2, y, wBpp2, h); for (int i = 0; i < h; ++i) { @@ -93,15 +116,15 @@ void TexturePacker::setVramTextureId(ModdedTextureId textureId, int xBpp2, int y { int vramX = xBpp2 + j; int key = vramX + vramY * VRAM_WIDTH; - ModdedTextureId previousTextureId = _vramTextureIds.at(key); - if (previousTextureId != INVALID_TEXTURE) + if (clearOldTexture) { - if (keepMods) { - continue; - } + ModdedTextureId previousTextureId = _vramTextureIds.at(key); - cleanTextures(previousTextureId, keepMods); + if (previousTextureId != INVALID_TEXTURE) + { + cleanTextures(previousTextureId); + } } _vramTextureIds[key] = textureId; @@ -109,118 +132,158 @@ void TexturePacker::setVramTextureId(ModdedTextureId textureId, int xBpp2, int y } } -void TexturePacker::setTexture(const char *name, int xBpp2, int y, int wBpp2, int h, Tim::Bpp bpp, bool isPal) +bool TexturePacker::setTexture(const char *name, const TextureInfos &texture, const TextureInfos &palette, bool clearOldTexture) { bool hasNamedTexture = name != nullptr && *name != '\0'; - if (trace_all || trace_vram) ffnx_trace("TexturePacker::%s %s xBpp2=%d y=%d wBpp2=%d h=%d bpp=%d isPal=%d\n", __func__, hasNamedTexture ? name : "N/A", xBpp2, y, wBpp2, h, bpp, isPal); + if (trace_all || trace_vram) ffnx_trace("TexturePacker::%s %s xBpp2=%d y=%d wBpp2=%d h=%d bpp=%d xPal=%d yPal=%d wPal=%d hPal=%d\n", __func__, + hasNamedTexture ? name : "N/A", + texture.x(), texture.y(), texture.w(), texture.h(), texture.bpp(), + palette.x(), palette.y(), palette.w(), palette.h()); - ModdedTextureId textureId = INVALID_TEXTURE; - Texture tex; + ModdedTextureId textureId = makeTextureId(texture.x(), texture.y()); + setVramTextureId(textureId, texture.x(), texture.y(), texture.w(), texture.h(), clearOldTexture); - if (hasNamedTexture && !isPal) + IdentifiedTexture tex(name, texture, palette); + + if (hasNamedTexture) { - tex = Texture(name, xBpp2, y, wBpp2, h, bpp); + TextureModStandard *mod = new TextureModStandard(tex); - if (tex.createImage()) + if (mod->createImages()) + { + tex.setMod(mod); + } + else { - textureId = makeTextureId(xBpp2, y, TextureCategoryStandard); + delete mod; } } - setVramTextureId(textureId, xBpp2, y, wBpp2, h); + _textures[textureId] = tex; - if (textureId != INVALID_TEXTURE) - { - _externalTextures[textureId] = tex; - } + return tex.mod() != nullptr; } bool TexturePacker::setTextureBackground(const char *name, int x, int y, int w, int h, const std::vector &mapTiles, int bgTexId, const char *extension, char *found_extension) { if (trace_all || trace_vram) ffnx_trace("TexturePacker::%s %s x=%d y=%d w=%d h=%d tileCount=%d bgTexId=%d\n", __func__, name, x, y, w, h, mapTiles.size(), bgTexId); - TextureBackground tex(name, x, y, w, h, mapTiles, bgTexId); - ModdedTextureId textureId = INVALID_TEXTURE; + ModdedTextureId textureId = makeTextureId(x, y); + setVramTextureId(textureId, x, y, w, h); - if (tex.createImage(extension, found_extension)) + IdentifiedTexture tex(name, TextureInfos(x, y, w, h, Tim::Bpp16, true)); + + TextureBackground *mod = new TextureBackground(tex, mapTiles, bgTexId); + if (mod->createImages(extension, found_extension)) { - textureId = makeTextureId(x, y, TextureCategoryBackground); + tex.setMod(mod); } - - setVramTextureId(textureId, x, y, w, h); - - if (textureId != INVALID_TEXTURE) + else { - _backgroundTextures[textureId] = tex; - - return true; + delete mod; } - return false; + _textures[textureId] = tex; + + return tex.mod() != nullptr; } -bool TexturePacker::setTextureRedirection(const TextureInfos &oldTexture, const TextureInfos &newTexture, uint32_t *imageData) +bool TexturePacker::setTextureRedirection(const char *name, const TextureInfos &oldTexture, const TextureInfos &newTexture, uint32_t *imageData) { - if (trace_all || trace_vram) ffnx_info("TexturePacker::%s: old=(%d, %d, %d, %d) => new=(%d, %d, %d, %d)\n", __func__, + if (trace_all || trace_vram) ffnx_trace("TexturePacker::%s: %s old=(%d, %d, %d, %d) <= new=(%d, %d, %d, %d)\n", __func__, name, oldTexture.x(), oldTexture.y(), oldTexture.w(), oldTexture.h(), newTexture.x(), newTexture.y(), newTexture.w(), newTexture.h()); - TextureRedirection redirection(oldTexture, newTexture); - if (redirection.isValid() && redirection.createImage(imageData)) + ModdedTextureId textureId = _vramTextureIds.at(oldTexture.x() + oldTexture.y() * VRAM_WIDTH); + + if (textureId == INVALID_TEXTURE) + { + ffnx_warning("TexturePacker::%s cannot find original texture of redirection\n", __func__); + + return false; + } + + auto it = _textures.find(textureId); + + if (it == _textures.end()) { - ModdedTextureId textureId = makeTextureId(oldTexture.x(), oldTexture.y(), TextureCategoryRedirection); + ffnx_warning("TexturePacker::%s cannot find original texture of redirection 2\n", __func__); + + return false; + } - setVramTextureId(textureId, oldTexture.x(), oldTexture.y(), oldTexture.w(), oldTexture.h(), true); + if (trace_all || trace_vram) ffnx_trace("TexturePacker::%s: found %s\n", __func__, it->second.name().c_str()); - _textureRedirections[textureId] = redirection; + ModdedTextureId redirectionTextureId = makeTextureId(newTexture.x(), newTexture.y()); + auto it2 = it->second.redirections().find(redirectionTextureId); + if (it2 != it->second.redirections().end() && it2->second.mod() != nullptr) + { + delete it2->second.mod(); + } + IdentifiedTexture red(name, oldTexture); + TextureModStandard *mod = new TextureModStandard(red); + const int scaleLod = newTexture.h() / oldTexture.h(); // Force original texture size to be the one from new texture + + if (mod->createImages(scaleLod)) + { + // Use external texture + red.setMod(mod); + } + else + { + if (trace_all || trace_vram) ffnx_trace("TexturePacker::%s: use internal texture\n", __func__); - return true; + delete mod; + // Use internal texture + red.setMod(new TextureRawImage(red, imageData, newTexture.pixelW(), newTexture.h())); } - return false; + _textures[textureId].setRedirection(redirectionTextureId, red); + + return true; } -void TexturePacker::copyTexture(int sourceXBpp2, int sourceY, int sourceWBpp2, int sourceH, int targetXBpp2, int targetY) +void TexturePacker::animateTextureByCopy(int sourceXBpp2, int sourceY, int sourceWBpp2, int sourceH, int targetXBpp2, int targetY) { - if (_externalTextures.empty() && _backgroundTextures.empty() && _textureRedirections.empty()) + if (_textures.empty()) { return; } ModdedTextureId textureId = _vramTextureIds.at(sourceXBpp2 + sourceY * VRAM_WIDTH), textureIdTarget = _vramTextureIds.at(targetXBpp2 + targetY * VRAM_WIDTH); - if (textureId == INVALID_TEXTURE || textureCategoryFromTextureId(textureId) != TextureCategoryStandard) + if (textureId == INVALID_TEXTURE) { return; } - auto it = _externalTextures.find(textureId); + auto it = _textures.find(textureId); - if (it == _externalTextures.end()) + if (it == _textures.end() || it->second.mod() == nullptr || !it->second.mod()->canCopyRect()) { return; } if (textureId != textureIdTarget) { - if (textureIdTarget == INVALID_TEXTURE || textureCategoryFromTextureId(textureIdTarget) != TextureCategoryStandard) + if (textureIdTarget == INVALID_TEXTURE) { return; } - auto itTarget = _externalTextures.find(textureIdTarget); + auto itTarget = _textures.find(textureIdTarget); - if (itTarget == _externalTextures.end()) + if (itTarget == _textures.end() || itTarget->second.mod() == nullptr || !itTarget->second.mod()->canCopyRect()) { return; } - it->second.copyRect(sourceXBpp2, sourceY, sourceWBpp2, sourceH, targetXBpp2, targetY, itTarget->second); + dynamic_cast(it->second.mod())->copyRect(sourceXBpp2, sourceY, sourceWBpp2, sourceH, targetXBpp2, targetY, *dynamic_cast(itTarget->second.mod())); } else { - it->second.copyRect(sourceXBpp2, sourceY, sourceWBpp2, sourceH, targetXBpp2, targetY); + dynamic_cast(it->second.mod())->copyRect(sourceXBpp2, sourceY, sourceWBpp2, sourceH, targetXBpp2, targetY); } } @@ -237,54 +300,43 @@ void TexturePacker::clearTextures() std::fill_n(_vramTextureIds.begin(), _vramTextureIds.size(), INVALID_TEXTURE); - for (auto &texture: _externalTextures) { - texture.second.destroyImage(); - } - for (auto &texture: _textureRedirections) { - texture.second.destroyImage(); - } - for (auto &texture: _backgroundTextures) { - texture.second.destroyImage(); + for (const std::pair &texture: _textures) { + if (texture.second.mod() != nullptr) { + delete texture.second.mod(); + } } - _externalTextures.clear(); - _textureRedirections.clear(); - _backgroundTextures.clear(); + _textures.clear(); } -uint8_t TexturePacker::getMaxScale(const uint8_t *texData) const +std::list TexturePacker::matchTextures(const TiledTex &tiledTex, const TextureInfos &palette, bool withModsOnly) const { - if (trace_all || trace_vram) ffnx_trace("TexturePacker::%s\n", __func__); + std::list ret; - if (_externalTextures.empty() && _backgroundTextures.empty() && _textureRedirections.empty()) + if (_textures.empty()) { - return 1; + return ret; } - auto it = _tiledTexs.find(texData); - - if (it == _tiledTexs.end()) - { + if (!tiledTex.isValid()) { if (trace_all || trace_vram) ffnx_warning("TexturePacker::%s Unknown tex data\n", __func__); - return 0; + return ret; } - const TiledTex &tiledTex = it->second; - - uint8_t maxScale = 1; + std::set textureIds; - for (int y = 0; y < 256; ++y) + for (int y = 0; y < VRAM_PAGE_HEIGHT; ++y) { - int vramY = tiledTex.y + y; + int vramY = tiledTex.y() + y; if (vramY >= VRAM_HEIGHT) { break; } - for (int x = 0; x < 64; ++x) + for (int x = 0; x < VRAM_PAGE_WIDTH; ++x) { - int vramX = tiledTex.x + x; + int vramX = tiledTex.x() + x; if (vramX >= VRAM_WIDTH) { @@ -295,235 +347,207 @@ uint8_t TexturePacker::getMaxScale(const uint8_t *texData) const if (textureId != INVALID_TEXTURE) { - uint8_t scale = 0; - TextureCategory textureCategory = textureCategoryFromTextureId(textureId); - - if (textureCategory == TextureCategoryStandard && _externalTextures.contains(textureId)) - { - scale = _externalTextures.at(textureId).scale(); - } - else if (textureCategory == TextureCategoryBackground && _backgroundTextures.contains(textureId)) - { - scale = _backgroundTextures.at(textureId).scale(); - } - else if (textureCategory == TextureCategoryRedirection && _textureRedirections.contains(textureId)) - { - scale = _textureRedirections.at(textureId).scale(); - } - - if (scale > maxScale) - { - maxScale = scale; - } + textureIds.insert(textureId); } } } - return maxScale; -} - -void TexturePacker::getTextureNames(const uint8_t *texData, std::list &names) const -{ - if (_externalTextures.empty() && _backgroundTextures.empty()) - { - return; - } - - if (!_tiledTexs.contains(texData)) - { - ffnx_warning("TexturePacker::%s Unknown tex data %p\n", __func__, texData); - - return; - } - - const TiledTex &tiledTex = _tiledTexs.at(texData); - std::set textureIds; - - for (int y = 0; y < 256; ++y) + for (const ModdedTextureId &textureId: textureIds) { - int vramY = tiledTex.y + y; + auto it = _textures.find(textureId); - if (vramY >= VRAM_HEIGHT) + if (it == _textures.end()) { - break; + continue; } - for (int x = 0; x < 64; ++x) - { - int vramX = tiledTex.x + x; + const IdentifiedTexture &texture = it->second; + const ModdedTexture *mod = texture.mod(); - if (vramX >= VRAM_WIDTH) + // Filtering on BPP and notDrawnOnly/withModsOnly parameters + if (mod != nullptr) + { + if (!mod->isBpp(tiledTex.bpp())) { - break; + continue; } - - ModdedTextureId textureId = _vramTextureIds.at(vramX + vramY * VRAM_WIDTH); - - if (textureId != INVALID_TEXTURE) + } + else + { + if ((withModsOnly && texture.redirections().empty()) || (!texture.texture().hasMultiBpp() && texture.texture().bpp() != tiledTex.bpp())) { - TextureCategory textureCategory = textureCategoryFromTextureId(textureId); - - if (textureCategory == TextureCategoryStandard && _externalTextures.contains(textureId)) - { - textureIds.insert(textureId); - } - else if (textureCategory == TextureCategoryBackground && _backgroundTextures.contains(textureId)) - { - textureIds.insert(textureId); - } + continue; } } - } - for (ModdedTextureId textureId: textureIds) - { - if (_externalTextures.contains(textureId)) - { - names.push_back(_externalTextures.at(textureId).name()); - } - else if (_backgroundTextures.contains(textureId)) - { - names.push_back(_backgroundTextures.at(textureId).name()); - } + ret.push_back(texture); } -} -void TexturePacker::disableDrawTexturesBackground(bool disabled) -{ - _disableDrawTexturesBackground = disabled; + return ret; } -TexturePacker::TextureTypes TexturePacker::drawTextures(const uint8_t *texData, struct texture_format *tex_format, uint32_t *target, const uint32_t *originalImageData, int originalW, int originalH, uint8_t scale, uint32_t paletteIndex) +int textureId = 0; + +TexturePacker::TextureTypes TexturePacker::drawTextures( + const uint8_t *texData, uint32_t *rgbaImageData, uint32_t dataSize, int originalW, int originalH, + int palIndex, uint8_t *outScale, uint32_t **outTarget) const { - if (trace_all || trace_vram) ffnx_trace("TexturePacker::%s pointer=0x%X bitsperpixel=%d\n", __func__, texData, (tex_format ? tex_format->bitsperpixel : -1)); + if (trace_all || trace_vram) ffnx_trace("TexturePacker::%s texData=0x%X originalSize=(%d, %d) palIndex=%d\n", __func__, texData, originalW, originalH, palIndex); - auto it = _tiledTexs.find(texData); + textureId++; - if (it != _tiledTexs.end()) - { - const TiledTex &tex = it->second; + *outScale = 1; + *outTarget = rgbaImageData; - if (trace_all || trace_vram) ffnx_trace("TexturePacker::%s tex=(%d, %d) bpp=%d paletteIndex=%d\n", __func__, tex.x, tex.y, tex.bpp, paletteIndex); + if (_textures.empty()) + { + //debugSaveTexture(textureId, rgbaImageData, originalW, originalH, true, false, TexturePacker::NoTexture); - return drawTextures(target, tex, originalW, originalH, scale, paletteIndex); + return NoTexture; } - if (trace_all || trace_vram) ffnx_warning("TexturePacker::%s Unknown tex data\n", __func__); - - return NoTexture; -} + auto it = _tiledTexs.find(texData); -TexturePacker::TextureTypes TexturePacker::drawTextures(uint32_t *target, const TiledTex &tiledTex, int targetW, int targetH, uint8_t scale, uint32_t paletteIndex) -{ - if (_externalTextures.empty() && _backgroundTextures.empty() && _textureRedirections.empty()) + if (it == _tiledTexs.end()) { + if (trace_all || trace_vram) ffnx_warning("TexturePacker::%s Unknown tiledTex data\n", __func__); + return NoTexture; } - int w = targetW; + const TiledTex &tiledTex = it->second; - if (tiledTex.bpp <= int(Tim::Bpp16)) - { - w /= 4 >> tiledTex.bpp; - } - else + if (!tiledTex.isValid()) { - ffnx_warning("%s: Unknown bpp %d\n", tiledTex.bpp); + if (trace_all || trace_vram) ffnx_warning("TexturePacker::%s Invalid tiledTex data\n", __func__); return NoTexture; } - if (_disableDrawTexturesBackground && (trace_all || trace_vram)) ffnx_info("TexturePacker::%s disabled for field background\n", __func__); + if (!tiledTex.isPaletteValid(palIndex)) + { + if (trace_all || trace_vram) ffnx_warning("TexturePacker::%s Palette is not set yet, we should not consider this texture\n", __func__); + + //debugSaveTexture(textureId, rgbaImageData, originalW, originalH, true, false, TexturePacker::RemoveTexture); - if (_disableDrawTexturesBackground) { return NoTexture; } - TextureTypes drawnTextureTypes = NoTexture; - long double totalCopyRect = 0.0, totalCopyFind = 0.0; - std::unordered_map matchedTextures; + TextureInfos palette = tiledTex.palette(palIndex); + std::list textures = matchTextures(tiledTex, palette, true); - for (int y = 0; y < targetH; ++y) + if (textures.empty()) { - int vramY = tiledTex.y + y; + //debugSaveTexture(textureId, rgbaImageData, originalW, originalH, true, false, TexturePacker::NoTexture); - if (vramY >= VRAM_HEIGHT) - { - break; - } + return NoTexture; + } - for (int x = 0; x < w; ++x) - { - int vramX = tiledTex.x + x; + uint8_t scale = 0; - if (vramX >= VRAM_WIDTH) + for (const IdentifiedTexture &tex: textures) + { + for (const std::pair &pair: tex.redirections()) + { + if (pair.second.mod() != nullptr) { - break; + scale = std::max(scale, pair.second.mod()->scale(palette.x(), palette.y())); } + } - ModdedTextureId textureId = _vramTextureIds.at(vramX + vramY * VRAM_WIDTH); + if (tex.mod() != nullptr) + { + scale = std::max(scale, tex.mod()->scale(palette.x(), palette.y())); + } + } - if (textureId != INVALID_TEXTURE) - { - TextureCategory textureCategory = textureCategoryFromTextureId(textureId); + uint32_t *target = rgbaImageData; - if (textureCategory == TextureCategoryStandard) - { - auto it = _externalTextures.find(textureId); + if (scale > 1) + { + uint32_t image_data_size = originalW * scale * originalH * scale * 4; + // Allocate with cache + if (image_data_scaled_size_cache == 0 || image_data_size > image_data_scaled_size_cache) { + if (image_data_scaled_cache != nullptr) { + driver_free(image_data_scaled_cache); + } + image_data_scaled_cache = (uint32_t *)driver_malloc(image_data_size); + image_data_scaled_size_cache = image_data_size; + } - if (it != _externalTextures.end()) - { - const Texture &texture = it->second; + target = image_data_scaled_cache; - if (trace_all || trace_vram) matchedTextures.insert(std::pair(textureId, texture.name())); + // convert source data + if (target != nullptr) + { + memcpy(target, rgbaImageData, dataSize); + scale_up_image_data_in_place(target, originalW, originalH, scale); + } + } + else if (scale == 0) + { + return TextureTypes::NoTexture; + } - texture.copyRect(vramX, vramY, tiledTex.bpp, target, x, y, targetW, scale); + *outScale = scale; + *outTarget = target; - drawnTextureTypes = TextureTypes(int(drawnTextureTypes) | int(ExternalTexture)); - } - } - else if (textureCategory == TextureCategoryBackground && ! _disableDrawTexturesBackground) - { - auto it = _backgroundTextures.find(textureId); + //debugSaveTexture(textureId, target, originalW * scale, originalH * scale, false, false, TexturePacker::InternalTexture); - if (it != _backgroundTextures.end()) - { - const TextureBackground &texture = it->second; + TextureTypes ret = drawTextures(textures, tiledTex, palette, target, originalW, originalH, scale); - if (trace_all || trace_vram) matchedTextures.insert(std::pair(textureId, texture.name())); + //debugSaveTexture(textureId, target, originalW * scale, originalH * scale, false, true, ret); - texture.copyRect(vramX, vramY, tiledTex.bpp, target, x, y, targetW, scale); + if (trace_all || trace_vram) ffnx_trace("TexturePacker::%s tex=(%d, %d) bpp=%d paletteVram=(%d, %d) drawnTextureTypes=0x%X\n", __func__, tiledTex.x(), tiledTex.y(), tiledTex.bpp(), palette.x(), palette.y(), ret); - drawnTextureTypes = TextureTypes(int(drawnTextureTypes) | int(ExternalTexture)); - } - } - else if (textureCategory == TextureCategoryRedirection) - { - auto it = _textureRedirections.find(textureId); + return ret; +} - if (it != _textureRedirections.end()) - { - const TextureRedirection &texture = it->second; +TexturePacker::TextureTypes TexturePacker::drawTextures(const std::list &textures, const TiledTex &tiledTex, const TextureInfos &palette, uint32_t *target, int targetW, int targetH, uint8_t scale) const +{ + TextureTypes drawnTextureTypes = NoTexture; + int x = tiledTex.pixelX(), y = tiledTex.y(); - if (trace_all || trace_vram) matchedTextures.insert(std::pair(textureId, "worldmap_redirection")); + for (const IdentifiedTexture &texture: textures) + { + if (trace_all || trace_vram) ffnx_trace("TexturePacker::%s matches %s rect=(%d, %d, %d, %d)\n", __func__, texture.name().empty() ? "N/A" : texture.name().c_str(), texture.texture().x(), texture.texture().y(), texture.texture().w(), texture.texture().h()); - texture.copyRect(vramX, vramY, tiledTex.bpp, target, x, y, targetW, scale); + TextureTypes textureType = TextureTypes::NoTexture; - drawnTextureTypes = TextureTypes(int(drawnTextureTypes) | int(InternalTexture)); - } - } + for (const std::pair &pair: texture.redirections()) + { + if (pair.second.mod() != nullptr && pair.second.mod()->isInternal()) + { + textureType = pair.second.mod()->drawToImage( + pair.second.texture().pixelX() - x, pair.second.texture().y() - y, + target, targetW, targetH, scale, tiledTex.bpp(), + palette.x(), palette.y() + ); } } - } - if (trace_all || trace_vram) - { - ffnx_trace("TexturePacker::%s x=%d y=%d bpp=%d w=%d targetW=%d targetH=%d scale=%d drawnTextureTypes=0x%X\n", __func__, tiledTex.x, tiledTex.y, tiledTex.bpp, w, targetW, targetH, scale, drawnTextureTypes); + if (texture.mod() != nullptr) + { + textureType = texture.mod()->drawToImage( + texture.texture().pixelX() - x, texture.texture().y() - y, + target, targetW, targetH, scale, tiledTex.bpp(), + palette.x(), palette.y() + ); + } - for (const std::pair &p: matchedTextures) + for (const std::pair &pair: texture.redirections()) { - ffnx_trace("TexturePacker::%s matches %s (textureId=%d)\n", __func__, p.second.c_str(), p.first); + if (pair.second.mod() != nullptr && !pair.second.mod()->isInternal()) + { + textureType = pair.second.mod()->drawToImage( + pair.second.texture().pixelX() - x, pair.second.texture().y() - y, + target, targetW, targetH, scale, tiledTex.bpp(), + palette.x(), palette.y() + ); + } } + + drawnTextureTypes = TextureTypes(int(drawnTextureTypes) | int(textureType)); } return drawnTextureTypes; @@ -531,11 +555,29 @@ TexturePacker::TextureTypes TexturePacker::drawTextures(uint32_t *target, const void TexturePacker::registerTiledTex(const uint8_t *texData, int x, int y, Tim::Bpp bpp, int palX, int palY) { - if (trace_all || trace_vram) ffnx_trace("%s pointer=0x%X x=%d y=%d bpp=%d palX=%d palY=%d\n", __func__, texData, x, y, bpp, palX, palY); + if (trace_all || trace_vram) ffnx_trace("TexturePacker::%s pointer=0x%X x=%d y=%d bpp=%d palX=%d palY=%d\n", __func__, texData, x, y, bpp, palX, palY); + // If this entry already exist, override _tiledTexs[texData] = TiledTex(x, y, bpp, palX, palY); } +void TexturePacker::registerPaletteWrite(const uint8_t *texData, int palIndex, int palX, int palY) +{ + if (trace_all || trace_vram) ffnx_trace("%s pointer=0x%X palIndex=%d palX=%d palY=%d\n", __func__, texData, palIndex, palX, palY); + + if (_tiledTexs.contains(texData)) + { + TiledTex &tex = _tiledTexs[texData]; + tex.palettes[palIndex] = TextureInfos(palX, palY, 256, 1, Tim::Bpp16); + } + else + { + if (trace_all || trace_vram) ffnx_warning("TexturePacker::%s pointer=0x%X register palette before image\n", __func__, texData); + + _tiledTexs[texData] = TiledTex(-1, -1, Tim::Bpp4, palX, palY); + } +} + TexturePacker::TiledTex TexturePacker::getTiledTex(const uint8_t *texData) const { auto it = _tiledTexs.find(texData); @@ -568,415 +610,55 @@ void TexturePacker::debugSaveTexture(int textureId, const uint32_t *source, int } TexturePacker::TextureInfos::TextureInfos() : - _x(0), _y(0), _w(0), _h(0), _bpp(Tim::Bpp4) + _x(-1), _y(0), _w(0), _h(0), _bpp(Tim::Bpp4) { } TexturePacker::TextureInfos::TextureInfos( int xBpp2, int y, int wBpp2, int h, - Tim::Bpp bpp -) : _x(xBpp2), _y(y), _w(wBpp2), _h(h), _bpp(bpp) -{ -} - -bimg::ImageContainer *TexturePacker::TextureInfos::createImageContainer(const char *name, uint8_t palette_index, bool hasPal, const char *extension, char *found_extension) -{ - char filename[MAX_PATH] = {}, langPath[16] = {}; - - if(trace_all || trace_loaders || trace_vram) ffnx_trace("texture file name (VRAM): %s\n", name); - - if(save_textures) return nullptr; - - ff8_fs_lang_string(langPath); - strcat(langPath, "/"); - - for (size_t idx = 0; idx < mod_ext.size(); idx++) - { - // Force extension - if (extension && stricmp(extension, mod_ext[idx].c_str()) != 0) - { - continue; - } - - for (uint8_t lang = 0; lang < 2; lang++) - { - if (hasPal) { - _snprintf(filename, sizeof(filename), "%s/%s/%s%s_%02i.%s", basedir, mod_path.c_str(), langPath, name, palette_index, mod_ext[idx].c_str()); - } else { - _snprintf(filename, sizeof(filename), "%s/%s/%s%s.%s", basedir, mod_path.c_str(), langPath, name, mod_ext[idx].c_str()); - } - bimg::ImageContainer *image = nullptr; - if (fileExists(filename)) { - image = newRenderer.createImageContainer(filename, bimg::TextureFormat::BGRA8); - } - - if (image != nullptr) - { - if (trace_all || trace_loaders || trace_vram) ffnx_trace("Using texture: %s\n", filename); - - if (found_extension != nullptr) { - strncpy(found_extension, mod_ext[idx].c_str(), mod_ext[idx].size()); - } - - return image; - } - else if (trace_all || trace_loaders || trace_vram) - { - ffnx_warning("Texture does not exist, skipping: %s\n", filename); - } - - *langPath = '\0'; - } - } - - return nullptr; -} - -uint8_t TexturePacker::TextureInfos::computeScale(int sourcePixelW, int sourceH, int targetPixelW, int targetH) + Tim::Bpp bpp, bool multiBpp +) : _x(xBpp2), _y(y), _w(wBpp2), _h(h), _bpp(bpp), _multiBpp(multiBpp) { - if (targetPixelW < sourcePixelW - || targetH < sourceH - || targetPixelW % sourcePixelW != 0 - || targetH % sourceH != 0) - { - ffnx_warning("Texture redirection size must be scaled to the original texture size with the same ratio (modded texture size: %dx%d, original texture size: %dx%d)\n", targetPixelW, targetH, sourcePixelW, sourceH); - - return 0; - } - - int scaleW = targetPixelW / sourcePixelW, scaleH = targetH / sourceH; - - if (scaleW != scaleH) - { - ffnx_warning("Texture redirection size must have the same ratio as the original texture: (%d / %d)\n", sourcePixelW, sourceH); - - return 0; - } - - if (scaleW > MAX_SCALE) - { - ffnx_warning("External texture size cannot exceed \"original size * %d\" (scale=%d)\n", MAX_SCALE, scaleW); - - return 0; - } - - return scaleW; } -void TexturePacker::TextureInfos::copyRect( - const uint32_t *sourceRGBA, int sourceXBpp2, int sourceY, int sourceW, uint8_t sourceScale, Tim::Bpp sourceDepth, - uint32_t *targetRGBA, int targetX, int targetY, int targetW, uint8_t targetScale) +TexturePacker::TiledTex::TiledTex() + : TextureInfos() { - if (targetScale < sourceScale) - { - return; - } - - const uint8_t targetRectWidth = (4 >> int(sourceDepth)) * targetScale, - targetRectHeight = targetScale, - sourceRectWidth = (4 >> int(sourceDepth)) * sourceScale, - sourceRectHeight = sourceScale; - const uint8_t scaleRatio = targetScale / sourceScale; - - targetW *= targetScale; - - targetRGBA += targetX * targetRectWidth + targetY * targetRectHeight * targetW; - sourceRGBA += sourceXBpp2 * sourceRectWidth + sourceY * sourceRectHeight * sourceW; - - for (int y = 0; y < targetRectHeight; ++y) - { - for (int x = 0; x < targetRectWidth; ++x) - { - uint32_t color = *(sourceRGBA + x / scaleRatio + y / scaleRatio * sourceW); - uint32_t alpha = color & 0xFF000000; - *(targetRGBA + x + y * targetW) = (color & 0xFFFFFF) | (alpha >= 0x7F000000 ? 0x7F000000 : 0); - } - } } -void TexturePacker::TextureInfos::copyRect( - const uint32_t *sourceRGBA, int sourceRGBAW, uint32_t *targetRGBA, int targetRGBAW, uint8_t scale, Tim::Bpp depth, - int sourceXBpp2, int sourceY, int sourceWBpp2, int sourceH, int targetXBpp2, int targetY) +TexturePacker::TiledTex::TiledTex( + int x, int y, Tim::Bpp bpp, int palVramX, int palVramY +) : TextureInfos(x, y, 256 / (4 >> int(bpp)), 256, bpp) { - int sourceX = sourceXBpp2 * (4 >> int(depth)), - sourceW = sourceWBpp2 * (4 >> int(depth)), - targetX = targetXBpp2 * (4 >> int(depth)); - - sourceW *= scale; - sourceH *= scale; - - ffnx_trace("TexturePacker::TextureInfos::%s: w=%d scale=%d depth=%d " - "sourceXBpp2=%d sourceX=%d sourceY=%d sourceWBpp2=%d sourceW=%d sourceH=%d targetXBpp2=%d targetX=%d targetY=%d\n", - __func__, targetRGBAW, scale, depth, sourceXBpp2, sourceX, sourceY, sourceWBpp2, sourceW, sourceH, targetXBpp2, targetX, targetY - ); - - const uint32_t *sourceRgba = sourceRGBA + (sourceX + sourceY * sourceRGBAW) * scale; - uint32_t *targetRgba = targetRGBA + (targetX + targetY * targetRGBAW) * scale; - - for (int y = 0; y < sourceH; ++y) - { - memcpy(targetRgba, sourceRgba, sizeof(uint32_t) * sourceW); - - sourceRgba += sourceRGBAW; - targetRgba += targetRGBAW; + if (palVramX >= 0) { + palettes[0] = TextureInfos(palVramX, palVramY, 256, 1, Tim::Bpp16); } } -TexturePacker::Texture::Texture() : - TextureInfos(), _image(nullptr), _name(""), _scale(1) +TexturePacker::IdentifiedTexture::IdentifiedTexture() : + _texture(TextureInfos()), _palette(TextureInfos()), _name(""), _mod(nullptr) { } -TexturePacker::Texture::Texture( +TexturePacker::IdentifiedTexture::IdentifiedTexture( const char *name, - int xBpp2, int y, int wBpp2, int h, - Tim::Bpp bpp -) : TextureInfos(xBpp2, y, wBpp2, h, bpp), _image(nullptr), _name(name), _scale(1) + const TextureInfos &texture, + const TextureInfos &palette +) : _texture(texture), _palette(palette), _name(name == nullptr ? "" : name), _mod(nullptr) { } -bool TexturePacker::Texture::createImage(uint8_t palette_index, bool has_pal, const char *extension, char *found_extension) +void TexturePacker::IdentifiedTexture::setMod(ModdedTexture *mod) { - _image = createImageContainer(_name.c_str(), palette_index, has_pal, extension, found_extension); - - if (_image == nullptr) + if (_mod != nullptr) { - return false; - } - - uint8_t scale = computeScale(); - - if (trace_all || trace_vram) - { - ffnx_info("TexturePacker::Texture::%s: width=%d height=%d offset=%d format=%d size=%d depth=%d numLayers=%d numMips=%d hasAlpha=%d scale=%d\n", __func__, - _image->m_width, _image->m_height, _image->m_offset, _image->m_format, _image->m_size, _image->m_depth, _image->m_numLayers, _image->m_numMips, _image->m_hasAlpha); - } - - if (scale == 0) - { - destroyImage(); - - return false; - } - - _scale = scale; - - return true; -} - -void TexturePacker::Texture::destroyImage() -{ - if (_image != nullptr) { - bimg::imageFree(_image); - _image = nullptr; - } -} - -uint8_t TexturePacker::Texture::computeScale() const -{ - return TextureInfos::computeScale(pixelW(), h(), _image->m_width, _image->m_height); -} - -void TexturePacker::Texture::copyRect(int vramXBpp2, int vramY, Tim::Bpp textureBpp, uint32_t *target, int targetX, int targetY, int targetW, uint8_t targetScale) const -{ - if (bpp() == textureBpp) { - TextureInfos::copyRect( - (const uint32_t *)_image->m_data + _image->m_offset, vramXBpp2 - x(), vramY - y(), _image->m_width, _scale, bpp(), - target, targetX, targetY, targetW, targetScale - ); - } -} - -void TexturePacker::Texture::copyRect(int sourceXBpp2, int sourceY, int sourceWBpp2, int sourceH, int targetXBpp2, int targetY) const -{ - TextureInfos::copyRect( - (const uint32_t *)_image->m_data + _image->m_offset, _image->m_width, - (uint32_t *)_image->m_data + _image->m_offset, _image->m_width, - _scale, bpp(), - sourceXBpp2 - x(), sourceY - y(), sourceWBpp2, sourceH, - targetXBpp2 - x(), targetY - y() - ); -} - -void TexturePacker::Texture::copyRect(int sourceXBpp2, int sourceY, int sourceWBpp2, int sourceH, int targetXBpp2, int targetY, const Texture &targetTexture) const -{ - if (bpp() != targetTexture.bpp() || _scale != targetTexture.scale()) { - return; - } - - TextureInfos::copyRect( - (const uint32_t *)_image->m_data + _image->m_offset, _image->m_width, - (uint32_t *)targetTexture.image()->m_data + targetTexture.image()->m_offset, targetTexture.image()->m_width, - _scale, bpp(), - sourceXBpp2 - x(), sourceY - y(), sourceWBpp2, sourceH, - targetXBpp2 - targetTexture.x(), targetY - targetTexture.y() - ); -} - -TexturePacker::TextureBackground::TextureBackground() : - Texture() -{ -} - -TexturePacker::TextureBackground::TextureBackground( - const char *name, - int x, int y, int w, int h, - const std::vector &mapTiles, - int textureId -) : Texture(name, x, y, w, h, Tim::Bpp16), _mapTiles(mapTiles), _textureId(textureId) -{ -} - -bool TexturePacker::TextureBackground::createImage(const char *extension, char *foundExtension) -{ - size_t size = _mapTiles.size(); - - _colsCount = size / (TEXTURE_HEIGHT / TILE_SIZE) + int(size % (TEXTURE_HEIGHT / TILE_SIZE) != 0); - - if (! Texture::createImage(0, false, extension, foundExtension)) { - return false; - } - - // Build tileIdsByPosition for fast lookup - _tileIdsByPosition.reserve(size); - - size_t tileId = 0; - for (const Tile &tile: _mapTiles) { - const uint8_t texId = tile.texID & 0xF; - if (_textureId < 0 || _textureId == texId) { - const uint8_t bpp = (tile.texID >> 7) & 3; - const uint16_t key = uint16_t(texId) | (uint16_t(tile.srcX / TILE_SIZE) << 4) | (uint16_t(tile.srcY / TILE_SIZE) << 8) | (uint16_t(bpp) << 12); - - auto it = _tileIdsByPosition.find(key); - // Remove some duplicates (but keep those which render differently) - if (it == _tileIdsByPosition.end() || ! ff8_background_tiles_looks_alike(tile, _mapTiles.at(it->second))) { - _tileIdsByPosition.insert(std::pair(key, tileId)); - } - } - ++tileId; - } - - return true; -} - -void TexturePacker::TextureBackground::copyRect(int vramXBpp2, int vramY, Tim::Bpp textureBpp, uint32_t *target, int targetX, int targetY, int targetW, uint8_t targetScale) const -{ - const int sourceXBpp2 = vramXBpp2 - x(), sourceY = vramY - y(); - const bool withTextureId = _textureId >= 0; - const uint8_t textureId = withTextureId ? _textureId : sourceXBpp2 / TEXTURE_WIDTH_BPP16; - const uint16_t sourceX = (sourceXBpp2 % TEXTURE_WIDTH_BPP16) << (2 - int(textureBpp)); - const uint16_t key = uint16_t(textureId) | (uint16_t(sourceX / TILE_SIZE) << 4) | (uint16_t(sourceY / TILE_SIZE) << 8) | (uint16_t(textureBpp) << 12); - - auto [begin, end] = _tileIdsByPosition.equal_range(key); - if (begin == end) { - return; + delete _mod; } - const bimg::ImageContainer *img = image(); - const uint32_t *imgData = (const uint32_t *)img->m_data + img->m_offset; - uint32_t imgWidth = img->m_width; - uint8_t imgScale = scale(); - bool multiMatch = _tileIdsByPosition.count(key) > 1, matched = false; - - // Iterate over matching tiles - for (const std::pair &pair: std::ranges::subrange{begin, end}) { - const size_t tileId = pair.second; - const Tile &tile = _mapTiles.at(tileId); - - if (multiMatch && tile.parameter != 255) { - if (tile.parameter < *ff8_externals.field_state_background_count) { - const ff8_field_state_background *field_state_backgrounds = *ff8_externals.field_state_backgrounds; - - if (tile.state != field_state_backgrounds[tile.parameter].bgstate >> 6) { - continue; - } - } else { - if (trace_all || trace_vram) ffnx_warning("TextureBackground::%s: group script not found for background parameter %d\n", __func__, tile.parameter); - - continue; - } - } - - if (matched) { - if (trace_all || trace_vram) ffnx_warning("TextureBackground::%s: multiple tiles found for the same position (tile id %d)\n", __func__, tileId); - - break; - } - - const int col = tileId % _colsCount, row = tileId / _colsCount; - const int srcX = withTextureId ? tile.srcX : col * TILE_SIZE, srcY = withTextureId ? tile.srcY : row * TILE_SIZE; - const int imageXBpp2 = srcX / (4 >> int(textureBpp)) + sourceXBpp2 % (4 << int(textureBpp)), imageY = srcY + sourceY % TILE_SIZE; - - TextureInfos::copyRect( - imgData, imageXBpp2, imageY, imgWidth, imgScale, textureBpp, - target, targetX, targetY, targetW, targetScale - ); - - matched = true; - } - - if (matched == false && (trace_all || trace_vram)) { - ffnx_warning("TextureBackground::%s: tile not matched textureId=%d, sourceX=%d, sourceY=%d\n", __func__, textureId, sourceX, sourceY); - } + _mod = mod; } -uint8_t TexturePacker::TextureBackground::computeScale() const +void TexturePacker::IdentifiedTexture::setRedirection(ModdedTextureId textureId, const IdentifiedTexture &redirection) { - return _textureId >= 0 - ? TextureInfos::computeScale(TEXTURE_HEIGHT, TEXTURE_HEIGHT, image()->m_height, image()->m_height) - : TextureInfos::computeScale(_colsCount * TILE_SIZE, TEXTURE_HEIGHT, image()->m_width, image()->m_height); -} - -TexturePacker::TiledTex::TiledTex() - : x(-1), y(-1), palX(0), palY(0), bpp(Tim::Bpp4), renderedOnce(false) -{ -} - -TexturePacker::TiledTex::TiledTex( - int x, int y, Tim::Bpp bpp, int palX, int palY -) : x(x), y(y), palX(palX), palY(palY), bpp(bpp), renderedOnce(false) -{ -} - -TexturePacker::TextureRedirection::TextureRedirection() - : TextureInfos(), _image(nullptr), _scale(0) -{ -} - -TexturePacker::TextureRedirection::TextureRedirection( - const TextureInfos &oldTexture, - const TextureInfos &newTexture -) : TextureInfos(newTexture), _image(nullptr), _oldTexture(oldTexture), - _scale(computeScale()) -{ -} - -bool TexturePacker::TextureRedirection::createImage(uint32_t *imageData) -{ - _image = imageData; - - return _image != nullptr; -} - -void TexturePacker::TextureRedirection::destroyImage() -{ - if (_image != nullptr) { - driver_free(_image); - _image = nullptr; - } -} - -uint8_t TexturePacker::TextureRedirection::computeScale() const -{ - return TextureInfos::computeScale(_oldTexture.pixelW(), _oldTexture.h(), pixelW(), h()); -} - -void TexturePacker::TextureRedirection::copyRect(int vramXBpp2, int vramY, Tim::Bpp textureBpp, uint32_t *target, int targetX, int targetY, int targetW, uint8_t targetScale) const -{ - if (textureBpp == Tim::Bpp4) { - TextureInfos::copyRect( - _image, vramXBpp2 - _oldTexture.x(), vramY - _oldTexture.y(), pixelW(), _scale, _oldTexture.bpp(), - target, targetX, targetY, targetW, targetScale - ); - } + _redirections[textureId] = redirection; } diff --git a/src/ff8/texture_packer.h b/src/ff8/texture_packer.h index a808fa0b..a7171391 100644 --- a/src/ff8/texture_packer.h +++ b/src/ff8/texture_packer.h @@ -26,7 +26,7 @@ #include #include #include -#include +#include #include "../ff8.h" #include "../image/tim.h" @@ -36,9 +36,14 @@ typedef uint32_t ModdedTextureId; constexpr int VRAM_WIDTH = 1024; constexpr int VRAM_HEIGHT = 512; +constexpr int VRAM_PAGE_WIDTH = 64; +constexpr int VRAM_PAGE_HEIGHT = 256; constexpr int VRAM_DEPTH = 2; constexpr ModdedTextureId INVALID_TEXTURE = ModdedTextureId(0xFFFFFFFF); constexpr int MAX_SCALE = 128; +constexpr int FF8_BASE_RESOLUTION_X = 320; + +class ModdedTexture; class TexturePacker { public: @@ -47,11 +52,17 @@ class TexturePacker { TextureInfos(); TextureInfos( int x, int y, int w, int h, - Tim::Bpp bpp + Tim::Bpp bpp, bool multiBpp = false ); + inline bool isValid() const { + return _x >= 0; + } inline int x() const { return _x; } + inline int pixelX() const { + return _x * (4 >> int(_bpp)); + } inline int y() const { return _y; } @@ -59,7 +70,7 @@ class TexturePacker { return _w; } inline int pixelW() const { - return _w * (4 >> _bpp); + return _w * (4 >> int(_bpp)); } inline int h() const { return _h; @@ -67,167 +78,105 @@ class TexturePacker { inline Tim::Bpp bpp() const { return _bpp; } - protected: - static bimg::ImageContainer *createImageContainer(const char *name, uint8_t palette_index, bool hasPal, const char *extension = nullptr, char *foundExtension = nullptr); - static uint8_t computeScale(int sourcePixelW, int sourceH, int targetPixelW, int targetH); - static void copyRect( - const uint32_t *sourceRGBA, int sourceXBpp2, int sourceY, int sourceW, uint8_t sourceScale, Tim::Bpp sourceDepth, - uint32_t *targetRGBA, int targetX, int targetY, int targetW, uint8_t targetScale - ); - static void copyRect( - const uint32_t *sourceRGBA, int sourceRGBAW, uint32_t *targetRGBA, int targetRGBAW, uint8_t scale, Tim::Bpp depth, - int sourceXBpp2, int sourceY, int sourceWBpp2, int sourceH, - int targetXBpp2, int targetY - ); + inline bool hasMultiBpp() const { + return _multiBpp; + } private: int _x, _y; int _w, _h; Tim::Bpp _bpp; + bool _multiBpp; }; - struct TiledTex { + struct TiledTex : public TextureInfos { TiledTex(); - TiledTex(int x, int y, Tim::Bpp bpp, int palX, int palY); + TiledTex(int x, int y, Tim::Bpp bpp, int palVramX = -1, int palVramY = -1); + inline bool isPaletteValid(int palIndex) const { + return bpp() == Tim::Bpp16 || (palettes.contains(palIndex) && palettes.at(palIndex).isValid()); + } + inline TextureInfos palette(int palIndex) const { + return bpp() == Tim::Bpp16 || !palettes.contains(palIndex) ? TextureInfos() : palettes.at(palIndex); + } + std::map palettes; + }; + + class IdentifiedTexture { + public: + IdentifiedTexture(); + IdentifiedTexture( + const char *name, + const TextureInfos &texture, + const TextureInfos &palette = TextureInfos() + ); + void setMod(ModdedTexture *mod); + inline ModdedTexture *mod() const { + return _mod; + } + void setRedirection(ModdedTextureId textureId, const IdentifiedTexture &redirection); + const std::unordered_map &redirections() const { + return _redirections; + } + inline const TextureInfos &texture() const { + return _texture; + } + inline const TextureInfos &palette() const { + return _palette; + } + inline const std::string &name() const { + return _name; + } inline bool isValid() const { - return x >= 0; + return !_name.empty(); } - int x, y; - int palX, palY; - Tim::Bpp bpp; - bool renderedOnce; + private: + TextureInfos _texture, _palette; + std::string _name; + ModdedTexture *_mod; + std::unordered_map _redirections; }; enum TextureTypes { NoTexture = 0, ExternalTexture = 1, - InternalTexture = 2 + InternalTexture = 2, + RemoveTexture = 4 }; explicit TexturePacker(); - void setTexture(const char *name, int x, int y, int w, int h, Tim::Bpp bpp, bool isPal); + bool setTexture(const char *name, const TextureInfos &texture, const TextureInfos &palette = TextureInfos(), bool clearOldTexture = true); bool setTextureBackground(const char *name, int x, int y, int w, int h, const std::vector &mapTiles, int bgTexId = -1, const char *extension = nullptr, char *found_extension = nullptr); // Override a part of the VRAM from another part of the VRAM, typically with biggest textures (Worldmap) - bool setTextureRedirection(const TextureInfos &oldTexture, const TextureInfos &newTexture, uint32_t *imageData); - void copyTexture(int sourceXBpp2, int y, int sourceWBpp2, int sourceH, int targetXBpp2, int targetY); + bool setTextureRedirection(const char *name, const TextureInfos &oldTexture, const TextureInfos &newTexture, uint32_t *imageData); + void animateTextureByCopy(int sourceXBpp2, int y, int sourceWBpp2, int sourceH, int targetXBpp2, int targetY); void clearTiledTexs(); void clearTextures(); - uint8_t getMaxScale(const uint8_t *texData) const; - void getTextureNames(const uint8_t *texData, std::list &names) const; - void registerTiledTex(const uint8_t *texData, int x, int y, Tim::Bpp bpp, int palX = 0, int palY = 0); + // Returns the textures matching the tiledTex + std::list matchTextures(const TiledTex &tiledTex, const TextureInfos &palette, bool withModsOnly = false) const; + void registerTiledTex(const uint8_t *texData, int x, int y, Tim::Bpp bpp, int palX = -1, int palY = -1); + void registerPaletteWrite(const uint8_t *texData, int palIndex, int palX, int palY); TiledTex getTiledTex(const uint8_t *texData) const; - void disableDrawTexturesBackground(bool disabled); - inline bool drawTexturesBackgroundIsDisabled() const { - return _disableDrawTexturesBackground; - } - TextureTypes drawTextures(const uint8_t *texData, struct texture_format *tex_format, uint32_t *target, const uint32_t *originalImageData, int originalW, int originalH, uint8_t scale, uint32_t paletteIndex); + TextureTypes drawTextures( + const uint8_t *texData, uint32_t *rgbaImageData, uint32_t dataSize, int originalW, int originalH, + int palIndex, uint8_t *outScale, uint32_t **outTarget + ) const; static void debugSaveTexture(int textureId, const uint32_t *source, int w, int h, bool removeAlpha, bool after, TextureTypes textureType); private: - enum TextureCategory { - TextureCategoryStandard, - TextureCategoryBackground, - TextureCategoryRedirection - }; - inline static ModdedTextureId makeTextureId(int xBpp2, int y, TextureCategory textureCategory) { - return (xBpp2 + y * VRAM_WIDTH) | (uint32_t(textureCategory) << 28); - } - inline static TextureCategory textureCategoryFromTextureId(ModdedTextureId textureId) { - return TextureCategory(textureId >> 28 & 0xF); + inline static ModdedTextureId makeTextureId(int xBpp2, int y) { + return xBpp2 + y * VRAM_WIDTH; } - inline static ModdedTextureId setTextureIdCategory(ModdedTextureId textureId, TextureCategory textureCategory) { - return (textureId & 0xFFFFFFF) | (uint32_t(textureCategory) << 28); - } - class Texture : public TextureInfos { - public: - Texture(); - Texture( - const char *name, - int x, int y, int wBpp2, int h, - Tim::Bpp bpp - ); - inline const std::string &name() const { - return _name; - } - inline uint8_t scale() const { - return _scale; - } - bool createImage(uint8_t palette_index = 0, bool has_pal = true, const char *extension = nullptr, char *foundExtension = nullptr); - void destroyImage(); - inline bool hasImage() const { - return _image != nullptr; - } - inline bool isValid() const { - return _scale != 0; - } - void copyRect(int sourceXBpp2, int sourceYBpp2, Tim::Bpp textureBpp, uint32_t *target, int targetX, int targetY, int targetW, uint8_t targetScale) const; - void copyRect(int sourceXBpp2, int sourceY, int sourceWBpp2, int sourceH, int targetXBpp2, int targetY) const; - void copyRect(int sourceXBpp2, int sourceY, int sourceWBpp2, int sourceH, int targetXBpp2, int targetY, const Texture &targetTexture) const; - protected: - const bimg::ImageContainer *image() const { - return _image; - } - virtual uint8_t computeScale() const; - private: - bimg::ImageContainer *_image; - std::string _name; - uint8_t _scale; - }; - class TextureBackground : public Texture { - public: - TextureBackground(); - TextureBackground( - const char *name, - int x, int y, int w, int h, - const std::vector &mapTiles, - int textureId - ); - bool createImage(const char *extension = nullptr, char *foundExtension = nullptr); - void copyRect(int sourceXBpp2, int sourceYBpp2, Tim::Bpp textureBpp, uint32_t *target, int targetX, int targetY, int targetW, uint8_t targetScale) const; - private: - virtual uint8_t computeScale() const override; - std::vector _mapTiles; - std::unordered_multimap _tileIdsByPosition; - uint8_t _colsCount; - int _textureId; - }; - struct TextureRedirection : public TextureInfos { - TextureRedirection(); - TextureRedirection( - const TextureInfos &oldTexture, - const TextureInfos &newTexture - ); - inline bool isValid() const { - return _scale != 0; - } - inline uint8_t scale() const { - return _scale; - } - bool createImage(uint32_t *imageData); - void destroyImage(); - inline uint32_t *imageData() { - return _image; - } - inline const TextureInfos &oldTexture() const { - return _oldTexture; - } - void copyRect(int textureX, int textureY, Tim::Bpp textureBpp, uint32_t *target, int targetX, int targetY, int targetW, uint8_t targetScale) const; - private: - uint8_t computeScale() const; - uint32_t *_image; - TextureInfos _oldTexture; - uint8_t _scale; - }; - void setVramTextureId(ModdedTextureId textureId, int x, int y, int w, int h, bool keepMods = false); - TextureTypes drawTextures(uint32_t *target, const TiledTex &tiledTex, int w, int h, uint8_t scale, uint32_t paletteIndex); + + void setVramTextureId(ModdedTextureId textureId, int x, int y, int w, int h, bool clearOldTexture = true); + uint8_t getMaxScale(const TiledTex &tiledTex) const; + TextureTypes drawTextures(const std::list &textures, const TiledTex &tiledTex, const TextureInfos &palette, uint32_t *target, int w, int h, uint8_t scale) const; void cleanVramTextureIds(const TextureInfos &texture); - void cleanTextures(ModdedTextureId textureId, bool keepMods = false); + void cleanTextures(ModdedTextureId textureId); + // Link between texture data pointer sent to the graphic driver and VRAM coordinates std::unordered_map _tiledTexs; + // Keep track of where textures are uploaded to the VRAM std::vector _vramTextureIds; // ModdedTextureId[VRAM_WIDTH * VRAM_HEIGHT] - std::unordered_map _externalTextures; - std::unordered_map _textureRedirections; - std::unordered_map _backgroundTextures; - - bool _disableDrawTexturesBackground; + // List of uploaded textures to the VRAM + std::unordered_map _textures; }; diff --git a/src/ff8/vram.cpp b/src/ff8/vram.cpp index e8003235..f04b45d7 100644 --- a/src/ff8/vram.cpp +++ b/src/ff8/vram.cpp @@ -37,19 +37,21 @@ #include TexturePacker texturePacker; -uint8_t *ff8_vram = nullptr; // uint16_t[VRAM_WIDTH * VRAM_HEIGHT] aka uint8_t[VRAM_WIDTH * VRAM_HEIGHT * VRAM_DEPTH] char next_texture_name[MAX_PATH] = ""; +TexturePacker::TextureInfos texture_infos = TexturePacker::TextureInfos(); +TexturePacker::TextureInfos palette_infos = TexturePacker::TextureInfos(); uint16_t *next_pal_data = nullptr; int next_psxvram_x = -1; int next_psxvram_y = -1; -int next_psxvram_pal_x = -1; -int next_psxvram_pal_y = -1; int next_texl_id = 0; +int next_do_not_clear_old_texture = false; Tim::Bpp next_bpp = Tim::Bpp16; uint8_t next_scale = 1; int8_t texl_id_left = -1; int8_t texl_id_right = -1; +int last_CLUT = 0; + // Field background uint8_t *mim_texture_buffer = nullptr; // Field models @@ -105,7 +107,8 @@ void ff8_upload_vram(int16_t *pos_and_size, uint8_t *texture_buffer) const int16_t y = pos_and_size[1]; const int16_t w = pos_and_size[2]; const int16_t h = pos_and_size[3]; - bool isPal = next_pal_data != nullptr && (uint8_t *)next_pal_data == texture_buffer; + bool isPal = (next_pal_data != nullptr && (uint8_t *)next_pal_data == texture_buffer) + || (palette_infos.isValid() && palette_infos.x() == x && palette_infos.y() == y && palette_infos.w() == w && palette_infos.h() == h); if (trace_all || trace_vram) ffnx_trace("%s x=%d y=%d w=%d h=%d bpp=%d isPal=%d texture_buffer=0x%X\n", __func__, x, y, w, h, next_bpp, isPal, texture_buffer); @@ -120,21 +123,23 @@ void ff8_upload_vram(int16_t *pos_and_size, uint8_t *texture_buffer) vram += vramLineWidth; } - texturePacker.setTexture(next_texture_name, x, y, w, h, next_bpp, isPal); + if (texture_infos.isValid() && palette_infos.isValid()) { + texturePacker.setTexture(next_texture_name, texture_infos, palette_infos, !next_do_not_clear_old_texture); + + texture_infos = TexturePacker::TextureInfos(); + palette_infos = TexturePacker::TextureInfos(); + } else if (!texture_infos.isValid() && !palette_infos.isValid() && !isPal) { + texturePacker.setTexture(next_texture_name, TexturePacker::TextureInfos(x, y, w, h, next_bpp), TexturePacker::TextureInfos(), !next_do_not_clear_old_texture); + } ff8_externals.sub_464850(x, y, x + w - 1, h + y - 1); - next_pal_data = nullptr; *next_texture_name = '\0'; + next_do_not_clear_old_texture = false; } -void ff8_copy_vram_part(int16_t *pos_and_size, int target_x, int target_y) +void ff8_vram_copy_part(int x, int y, int w, int h, int target_x, int target_y) { - const int16_t x = pos_and_size[0]; - const int16_t y = pos_and_size[1]; - const int16_t w = pos_and_size[2]; - const int16_t h = pos_and_size[3]; - if (trace_all || trace_vram) ffnx_trace("%s x=%d y=%d w=%d h=%d target_x=%d target_y=%d\n", __func__, x, y, w, h, target_x, target_y); uint8_t *vram = ff8_vram_seek(x, y), *target = ff8_vram_seek(target_x, target_y); @@ -148,11 +153,21 @@ void ff8_copy_vram_part(int16_t *pos_and_size, int target_x, int target_y) target += vramLineWidth; } - texturePacker.copyTexture(x, y, w, h, target_x, target_y); + texturePacker.animateTextureByCopy(x, y, w, h, target_x, target_y); ff8_externals.sub_464850(x, y, x + w - 1, h + y - 1); } +void ff8_copy_vram_part(int16_t *pos_and_size, int target_x, int target_y) +{ + const int16_t x = pos_and_size[0]; + const int16_t y = pos_and_size[1]; + const int16_t w = pos_and_size[2]; + const int16_t h = pos_and_size[3]; + + ff8_vram_copy_part(x, y, w, h, target_x, target_y); +} + int read_vram_to_buffer_parent_call1(struc_50 *psxvram, texture_page *tex_page, int x, int y, int w, int h, int bpp, int rel_pos, int a9, uint8_t *target) { if (trace_all || trace_vram) ffnx_trace("%s: x=%d y=%d w=%d h=%d bpp=%d rel_pos=(%d, %d) a9=%d target=%X\n", __func__, x, y, w, h, bpp, rel_pos & 0xF, rel_pos >> 4, a9, target); @@ -229,6 +244,15 @@ void read_vram_to_buffer(uint8_t *vram, int vram_w_2048, uint8_t *target, int ta ff8_externals.read_vram_1(vram, vram_w_2048, target, target_w, w, h, bpp); } +void get_vram_palette_pos_from_texture_pages(uint16_t &x, uint16_t &y) +{ + int vram_page = (next_psxvram_x / 64) + (next_psxvram_y / 256) * 16; + uint16_t pos = ff8_externals.psx_texture_pages->struc_50_array[vram_page].vram_palette_pos; + + x = (pos & 0x3F) * 16; + y = (pos >> 6) & 0x1FF; +} + void read_vram_to_buffer_with_palette1(uint8_t *vram, int vram_w_2048, uint8_t *target, int target_w, int w, int h, int bpp, uint16_t *vram_palette) { if (trace_all || trace_vram) ffnx_trace("%s: vram_pos=(%d, %d) target=0x%X target_w=%d w=%d h=%d bpp=%d vram_palette=%X\n", __func__, next_psxvram_x, next_psxvram_y, int(target), target_w, w, h, bpp, int(vram_palette)); @@ -239,7 +263,9 @@ void read_vram_to_buffer_with_palette1(uint8_t *vram, int vram_w_2048, uint8_t * } else { - texturePacker.registerTiledTex(target, next_psxvram_x, next_psxvram_y, Tim::Bpp(bpp), next_psxvram_pal_x, next_psxvram_pal_y); + uint16_t psxvram_pal_x, psxvram_pal_y; + get_vram_palette_pos_from_texture_pages(psxvram_pal_x, psxvram_pal_y); + texturePacker.registerTiledTex(target, next_psxvram_x, next_psxvram_y, Tim::Bpp(bpp), psxvram_pal_x, psxvram_pal_y); } ff8_externals.read_vram_2_paletted(vram, vram_w_2048, target, target_w, w, h, bpp, vram_palette); @@ -255,12 +281,36 @@ void read_vram_to_buffer_with_palette2(uint8_t *vram, uint8_t *target, int w, in } else { - texturePacker.registerTiledTex(target, next_psxvram_x, next_psxvram_y, Tim::Bpp(bpp), next_psxvram_pal_x, next_psxvram_pal_y); + uint16_t psxvram_pal_x, psxvram_pal_y; + get_vram_palette_pos_from_texture_pages(psxvram_pal_x, psxvram_pal_y); + texturePacker.registerTiledTex(target, next_psxvram_x, next_psxvram_y, Tim::Bpp(bpp), psxvram_pal_x, psxvram_pal_y); } ff8_externals.read_vram_3_paletted(vram, target, w, h, bpp, vram_palette); } +void ff8_read_vram_palette(int CLUT, uint8_t *rgba, int size) +{ + if (trace_all || trace_vram) ffnx_trace("%s: CLUT=(%d, %d) size=%d\n", __func__, (CLUT & 0x3F) * 16, (CLUT >> 6) & 0x1FF, size); + + last_CLUT = CLUT; + + ((void(*)(int,uint8_t*,int))ff8_externals.read_vram_palette_sub_467370)(CLUT, rgba, size); +} + +int ff8_write_palette_to_driver(int source_offset, int size, uint32_t *source_rgba, int dest_offset, ff8_texture_set *texture_set) +{ + VOBJ(texture_set, texture_set, texture_set); + VOBJ(tex_header, tex_header, VREF(texture_set, tex_header)); + + uint16_t palette_x = (last_CLUT & 0x3F) * 16, palette_y = (last_CLUT >> 6) & 0x1FF; + int pal_index = dest_offset / size / 2; + + texturePacker.registerPaletteWrite(VREF(tex_header, image_data), pal_index, palette_x, palette_y); + + return ((int(*)(int,int,uint32_t*,int,struct ff8_texture_set*))ff8_externals.write_palette_to_driver_sub_467310)(source_offset, size, source_rgba, dest_offset, texture_set); +} + uint32_t ff8_credits_open_texture(char *fileName, char *buffer) { if (trace_all || trace_vram) ffnx_trace("%s: %s\n", __func__, fileName); @@ -335,6 +385,20 @@ void ff8_upload_vram_triple_triad_2_texture_name(uint8_t *texture_buffer) } next_bpp = Tim::Bpp4; } + else if (texture_buffer >= ff8_externals.cardgame_tim_texture_font) + { + strncpy(next_texture_name, "cardgame/font", sizeof(next_texture_name)); + Tim tim = Tim::fromTimData(texture_buffer - 20); + + palette_infos = TexturePacker::TextureInfos(tim.paletteX(), tim.paletteY(), tim.paletteWidth(), tim.paletteHeight(), Tim::Bpp16); + texture_infos = TexturePacker::TextureInfos(tim.imageX(), tim.imageY(), tim.imageWidth(), tim.imageHeight(), tim.bpp()); + + if (next_pal_data == (uint16_t *)texture_buffer) + { + if (save_textures) tim.save(next_texture_name, 0, 0, true); + } + next_bpp = tim.bpp(); + } } void ff8_upload_vram_triple_triad_2_palette(int16_t *pos_and_size, uint8_t *texture_buffer) @@ -382,7 +446,13 @@ uint32_t ff8_wm_section_38_prepare_texture_for_upload(uint8_t *tim_file_data, ff uint32_t ret = ff8_externals.worldmap_prepare_tim_for_upload(tim_file_data, tim_infos); - next_pal_data = tim_infos->pal_data; + if (timId >= 8) + { + texture_infos = TexturePacker::TextureInfos(tim_infos->img_x, tim_infos->img_y, tim_infos->img_w, tim_infos->img_h, next_bpp); + palette_infos = TexturePacker::TextureInfos(tim_infos->pal_x, tim_infos->pal_y, tim_infos->pal_w, tim_infos->pal_h, Tim::Bpp16); + } + + next_do_not_clear_old_texture = timId >= 16 && timId <= 18 || timId == 20; if (save_textures) { @@ -417,6 +487,8 @@ Tim ff8_wm_set_texture_name_from_section_position(uint8_t section_number, uint32 uint32_t searching_value = uint32_t(tim_file_data - (uint8_t *)section_toc); int timId = search_pos_in_toc(section_toc, searching_value); + next_do_not_clear_old_texture = section_number == 40 && timId == 0; + snprintf(next_texture_name, MAX_PATH, "world/dat/wmset/section%d/texture%d", section_number, timId); Tim tim = Tim::fromTimData(tim_file_data); @@ -558,8 +630,8 @@ void ff8_wm_texl_palette_upload_vram(int16_t *pos_and_size, uint8_t *texture_buf return; // TODO } - TexturePacker::TextureInfos oldTexture(oldX, oldY, tim.imageWidth() / 8, tim.imageHeight() / 2, Tim::Bpp4), - newTexture(newX, 256, tim.imageWidth() / 2, tim.imageHeight(), Tim::Bpp(tim.bpp())); + TexturePacker::TextureInfos oldTexture(oldX, oldY, tim.imageWidth() / 4, tim.imageHeight() / 2, Tim::Bpp4), + newTexture(newX, 256, tim.imageWidth(), tim.imageHeight(), Tim::Bpp(tim.bpp())); // Allow to mod via texl textures snprintf(next_texture_name, MAX_PATH, "world/dat/texl/texture%d", next_texl_id); @@ -569,10 +641,6 @@ void ff8_wm_texl_palette_upload_vram(int16_t *pos_and_size, uint8_t *texture_buf tim.saveMultiPaletteGrid(next_texture_name, 4, 4, 0, 4, true); } - texturePacker.setTexture(next_texture_name, oldTexture.x(), oldTexture.y(), oldTexture.w(), oldTexture.h(), oldTexture.bpp(), false); - - *next_texture_name = '\0'; - if (! ff8_worldmap_internal_highres_textures) { return; @@ -590,13 +658,14 @@ void ff8_wm_texl_palette_upload_vram(int16_t *pos_and_size, uint8_t *texture_buf return; } - if (! texturePacker.setTextureRedirection(oldTexture, newTexture, image)) + if (! texturePacker.setTextureRedirection(next_texture_name, oldTexture, newTexture, image)) { - if (trace_all || trace_vram) ffnx_warning("%s: invalid redirection\n"); driver_free(image); } } + *next_texture_name = '\0'; + int section = 16 + oldX / 64; // Reload texture struc_50 *stru50 = ff8_externals.psx_texture_pages[0].struc_50_array + section; @@ -809,7 +878,7 @@ void ff8_field_effects_upload_vram1(int16_t *pos_and_size, uint8_t *texture_buff ff8_upload_vram(pos_and_size, texture_buffer); // This upload and the next one together - texturePacker.setTexture(texture_name, pos_and_size[0], pos_and_size[1], pos_and_size[2], pos_and_size[3] * 2, bpp, false); + texturePacker.setTexture(texture_name, TexturePacker::TextureInfos(pos_and_size[0], pos_and_size[1], pos_and_size[2], pos_and_size[3] * 2, bpp)); } int16_t ff8_battle_open_and_read_file(int fileId, void *data, int a3, int callback) @@ -943,6 +1012,10 @@ void vram_init() // Not used? replace_call(ff8_externals.sub_4649A0 + 0x13F, read_vram_to_buffer_with_palette2); + // Read palette from VRAM to Graphic driver + replace_call(ff8_externals.write_palette_texture_set_sub_466190 + 0x2C, ff8_read_vram_palette); + replace_call(ff8_externals.write_palette_texture_set_sub_466190 + 0x7E, ff8_write_palette_to_driver); + //---- Misc // Fix missing textures in battle module by clearing custom textures diff --git a/src/ff8_data.cpp b/src/ff8_data.cpp index afe17770..37c616f3 100644 --- a/src/ff8_data.cpp +++ b/src/ff8_data.cpp @@ -337,6 +337,9 @@ void ff8_find_externals() ff8_externals.sub_464F70 = (int(*)(struc_50*,texture_page*,int,int,int,int,int,int,int,uint8_t*))get_relative_call(ff8_externals.ssigpu_tx_select_2_sub_465CE0, 0x281); ff8_externals.read_vram_1 = (void(*)(uint8_t*,int,uint8_t*,int,signed int,int,int))get_relative_call(uint32_t(ff8_externals.sub_464F70), 0x2C5); ff8_externals.sub_464DB0 = get_relative_call(ff8_externals.ssigpu_tx_select_2_sub_465CE0, 0x2CF); + ff8_externals.write_palette_texture_set_sub_466190 = get_relative_call(ff8_externals.ssigpu_tx_select_2_sub_465CE0, 0x315); + ff8_externals.read_vram_palette_sub_467370 = get_relative_call(ff8_externals.write_palette_texture_set_sub_466190, 0x2C); + ff8_externals.write_palette_to_driver_sub_467310 = get_relative_call(ff8_externals.write_palette_texture_set_sub_466190, 0x7E); ff8_externals.read_vram_2_paletted = (void(*)(uint8_t*,int,uint8_t*,int,signed int,int,int,uint16_t*))get_relative_call(ff8_externals.sub_464DB0, 0xEC); ff8_externals.sub_4649A0 = get_relative_call(ff8_externals.ssigpu_callbacks_2[100], 0x33); ff8_externals.read_vram_3_paletted = (void(*)(uint8_t*,uint8_t*,signed int,int,int,uint16_t*))get_relative_call(ff8_externals.sub_4649A0, 0x13F); diff --git a/src/ff8_opengl.cpp b/src/ff8_opengl.cpp index dd7be565..d8f6a668 100644 --- a/src/ff8_opengl.cpp +++ b/src/ff8_opengl.cpp @@ -216,10 +216,10 @@ void texture_reload_hack(struct ff8_texture_set *texture_set) TexturePacker::TiledTex tiledTex = texturePacker.getTiledTex(VREF(tex_header, image_data)); if (tiledTex.isValid()) { - Tim::Bpp bpp = tiledTex.bpp == 0 ? Tim::Bpp8 : tiledTex.bpp; + int bytesperpixel = int(tiledTex.bpp() == Tim::Bpp4 ? Tim::Bpp8 : tiledTex.bpp()); - if (VREF(tex_header, tex_format.bytesperpixel) != int(bpp)) { - if(trace_all || trace_vram) ffnx_trace("texture_reload_hack: ignore reload because BPP does not match 0x%X (bpp vram=%d, bpp tex=%d) image_data=0x%X\n", texture_set, tiledTex.bpp, VREF(tex_header, tex_format.bytesperpixel), VREF(tex_header, image_data)); + if (VREF(tex_header, tex_format.bytesperpixel) != bytesperpixel) { + if(trace_all || trace_vram) ffnx_trace("texture_reload_hack: ignore reload because BPP does not match 0x%X (bpp vram=%d, bpp tex=%d) image_data=0x%X\n", texture_set, tiledTex.bpp(), VREF(tex_header, tex_format.bytesperpixel), VREF(tex_header, image_data)); return; } diff --git a/src/image/image.cpp b/src/image/image.cpp new file mode 100644 index 00000000..8fb96f3c --- /dev/null +++ b/src/image/image.cpp @@ -0,0 +1,252 @@ +/****************************************************************************/ +// Copyright (C) 2009 Aali132 // +// Copyright (C) 2018 quantumpencil // +// Copyright (C) 2018 Maxime Bacoux // +// Copyright (C) 2020 Chris Rizzitello // +// Copyright (C) 2020 John Pritchard // +// Copyright (C) 2023 myst6re // +// Copyright (C) 2023 Julian Xhokaxhiu // +// Copyright (C) 2023 Tang-Tang Zhou // +// // +// This file is part of FFNx // +// // +// FFNx is free software: you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation, either version 3 of the License // +// // +// FFNx is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License for more details. // +/****************************************************************************/ + +#include "../common.h" +#include "../renderer.h" +#include +#include + +static void LibPngErrorCb(png_structp png_ptr, const char* error) +{ + ffnx_error("libpng error: %s\n", error); +} + +static void LibPngWarningCb(png_structp png_ptr, const char* warning) +{ + ffnx_info("libpng warning: %s\n", warning); +} + +bool loadPng(const char *filename, bimg::ImageMip &mip) +{ + FILE* file = fopen(filename, "rb"); + + if (!file) + { + return false; + } + + png_infop info_ptr = nullptr; + png_structp png_ptr = nullptr; + + png_uint_32 _width = 0, _height = 0; + png_byte color_type = 0, bit_depth = 0; + + png_bytepp rowptrs = nullptr; + size_t rowbytes = 0; + + uint8_t* data = nullptr; + size_t datasize = 0; + + fseek(file, 0, SEEK_END); + datasize = ftell(file); + fseek(file, 0, SEEK_SET); + + png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, (png_voidp)0, LibPngErrorCb, LibPngWarningCb); + + if (!png_ptr) + { + fclose(file); + + return false; + } + + info_ptr = png_create_info_struct(png_ptr); + + if (!info_ptr) + { + png_destroy_read_struct(&png_ptr, (png_infopp)NULL, (png_infopp)NULL); + + fclose(file); + + return false; + } + + if (setjmp(png_jmpbuf(png_ptr))) + { + png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL); + + fclose(file); + + return false; + } + + png_init_io(png_ptr, file); + + png_set_filter(png_ptr, 0, PNG_FILTER_NONE); + + if (!Renderer::doesItFitInMemory(datasize)) + { + png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL); + + fclose(file); + + return false; + } + + png_read_png(png_ptr, info_ptr, PNG_TRANSFORM_EXPAND, NULL); + + color_type = png_get_color_type(png_ptr, info_ptr); + bit_depth = png_get_bit_depth(png_ptr, info_ptr); + _width = png_get_image_width(png_ptr, info_ptr); + _height = png_get_image_height(png_ptr, info_ptr); + + rowptrs = png_get_rows(png_ptr, info_ptr); + rowbytes = png_get_rowbytes(png_ptr, info_ptr); + + datasize = rowbytes * _height; + + if (!Renderer::doesItFitInMemory(datasize)) + { + png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL); + + fclose(file); + + return false; + } + + data = (uint8_t*)driver_calloc(datasize, sizeof(uint8_t)); + + for (png_uint_32 y = 0; y < _height; y++) memcpy(data + (rowbytes * y), rowptrs[y], rowbytes); + + png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL); + + fclose(file); + + // ------------------------------------------------------------ + + bimg::TextureFormat::Enum texFmt = bimg::TextureFormat::Unknown; + + switch (bit_depth) + { + case 8: + { + switch (color_type) + { + case PNG_COLOR_TYPE_GRAY: + texFmt = bimg::TextureFormat::R8; + break; + case PNG_COLOR_TYPE_GRAY_ALPHA: + texFmt = bimg::TextureFormat::RG8; + break; + case PNG_COLOR_TYPE_RGB: + texFmt = bimg::TextureFormat::RGB8; + break; + case PNG_COLOR_TYPE_RGBA: + case PNG_COLOR_TYPE_PALETTE: + texFmt = bimg::TextureFormat::RGBA8; + break; + } + break; + } + case 16: + { + switch (color_type) + { + case PNG_COLOR_TYPE_GRAY: + texFmt = bimg::TextureFormat::R16; + break; + case PNG_COLOR_TYPE_GRAY_ALPHA: + texFmt = bimg::TextureFormat::RG16; + break; + case PNG_COLOR_TYPE_RGB: + case PNG_COLOR_TYPE_RGBA: + texFmt = bimg::TextureFormat::RGBA16; + break; + case PNG_COLOR_TYPE_PALETTE: + break; + } + break; + } + default: + break; + } + + if (texFmt != bimg::TextureFormat::Unknown) + { + mip.m_blockSize = 0; + mip.m_bpp = 0; + mip.m_data = data; + mip.m_depth = 0; + mip.m_format = texFmt; + mip.m_hasAlpha = true; + mip.m_size = datasize; + mip.m_width = _width; + mip.m_height = _height; + + return true; + } + else + { + driver_free(data); + } + + return false; +} + +bimg::ImageContainer *loadImageContainer(bx::AllocatorI *allocator, const char *filename, bimg::TextureFormat::Enum targetFormat, bool isPng) +{ + bimg::ImageContainer* img = nullptr; + + if (isPng) + { + bimg::ImageMip mip; + if (loadPng(filename, mip)) + { + img = bimg::imageAlloc(allocator, mip.m_format, mip.m_width, mip.m_height, mip.m_depth, 1, false, false, mip.m_data); + + driver_free((void *)mip.m_data); + + return img; + } + } + + FILE* file = fopen(filename, "rb"); + + if (!file) + { + return img; + } + + size_t filesize = 0; + char* buffer = nullptr; + + fseek(file, 0, SEEK_END); + filesize = ftell(file); + + if (Renderer::doesItFitInMemory(filesize + 1)) + { + buffer = (char*)driver_malloc(filesize + 1); + fseek(file, 0, SEEK_SET); + fread(buffer, filesize, 1, file); + } + + fclose(file); + + if (buffer != nullptr) + { + img = bimg::imageParse(allocator, buffer, filesize + 1, targetFormat); + + driver_free(buffer); + } + + return img; +} diff --git a/src/image/image.h b/src/image/image.h new file mode 100644 index 00000000..7e5d6a2e --- /dev/null +++ b/src/image/image.h @@ -0,0 +1,30 @@ +/****************************************************************************/ +// Copyright (C) 2009 Aali132 // +// Copyright (C) 2018 quantumpencil // +// Copyright (C) 2018 Maxime Bacoux // +// Copyright (C) 2020 Chris Rizzitello // +// Copyright (C) 2020 John Pritchard // +// Copyright (C) 2023 myst6re // +// Copyright (C) 2023 Julian Xhokaxhiu // +// Copyright (C) 2023 Tang-Tang Zhou // +// // +// This file is part of FFNx // +// // +// FFNx is free software: you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation, either version 3 of the License // +// // +// FFNx is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License for more details. // +/****************************************************************************/ + +#pragma once + +#include +#include + +bimg::ImageContainer *loadImageContainer(bx::AllocatorI *allocator, const char *filename, bimg::TextureFormat::Enum targetFormat = bimg::TextureFormat::Count, bool isPng = false); +// Fast PNG opening, you need to deallocate mip.m_data yourself +bool loadPng(const char *filename, bimg::ImageMip &mip); diff --git a/src/image/tim.h b/src/image/tim.h index 2af744ae..5df75bfa 100644 --- a/src/image/tim.h +++ b/src/image/tim.h @@ -127,7 +127,7 @@ class Tim { return _tim.img_y; } inline uint16_t imageWidth() const { - return _tim.img_w; + return _tim.img_w / (4 >> int(_bpp)); } inline uint16_t imageHeight() const { return _tim.img_h; diff --git a/src/renderer.cpp b/src/renderer.cpp index 393da4d8..d07ee2f8 100644 --- a/src/renderer.cpp +++ b/src/renderer.cpp @@ -33,6 +33,7 @@ #include "ff7/widescreen.h" #include "ff7/time.h" #include "cfg.h" +#include "image/image.h" #include #include @@ -1818,46 +1819,7 @@ uint32_t Renderer::createTexture(char* filename, uint32_t* width, uint32_t* heig bimg::ImageContainer* Renderer::createImageContainer(const char* filename, bimg::TextureFormat::Enum targetFormat) { - FILE* file = fopen(filename, "rb"); - bimg::ImageContainer* img = nullptr; - - if (file) - { - size_t filesize = 0; - char* buffer = nullptr; - - fseek(file, 0, SEEK_END); - filesize = ftell(file); - - if (doesItFitInMemory(filesize + 1)) - { - buffer = (char*)driver_malloc(filesize + 1); - fseek(file, 0, SEEK_SET); - fread(buffer, filesize, 1, file); - } - - fclose(file); - - if (buffer != nullptr) - { - img = bimg::imageParse(&defaultAllocator, buffer, filesize + 1); - - driver_free(buffer); - } - } - - if (img && targetFormat != bimg::TextureFormat::Enum::UnknownDepth && targetFormat != img->m_format) - { - if (trace_all || trace_renderer) ffnx_trace("Renderer::%s: convert image to format %d\n", __func__, targetFormat); - - bimg::ImageContainer* converted = bimg::imageConvert(&defaultAllocator, targetFormat, *img); - - bimg::imageFree(img); - - img = converted; - } - - return img; + return loadImageContainer(&defaultAllocator, filename, targetFormat); } bimg::ImageContainer* Renderer::createImageContainer(cmrc::file* file, bimg::TextureFormat::Enum targetFormat) @@ -1866,18 +1828,7 @@ bimg::ImageContainer* Renderer::createImageContainer(cmrc::file* file, bimg::Tex if (file->size() > 0) { - img = bimg::imageParse(&defaultAllocator, file->begin(), file->size()); - } - - if (img && targetFormat != bimg::TextureFormat::Enum::UnknownDepth && targetFormat != img->m_format) - { - if (trace_all || trace_renderer) ffnx_trace("Renderer::%s: convert image to format %d\n", __func__, targetFormat); - - bimg::ImageContainer* converted = bimg::imageConvert(&defaultAllocator, targetFormat, *img, false); - - bimg::imageFree(img); - - img = converted; + img = bimg::imageParse(&defaultAllocator, file->begin(), file->size(), targetFormat); } return img; @@ -1988,174 +1939,33 @@ bgfx::TextureHandle Renderer::createTextureHandle(cmrc::file* file, char* filena uint32_t Renderer::createTextureLibPng(char* filename, uint32_t* width, uint32_t* height, bool isSrgb) { bgfx::TextureHandle ret = FFNX_RENDERER_INVALID_HANDLE; + bimg::ImageMip mip; - FILE* file = fopen(filename, "rb"); - - if (file) - { - png_infop info_ptr = nullptr; - png_structp png_ptr = nullptr; - - png_uint_32 _width = 0, _height = 0; - png_byte color_type = 0, bit_depth = 0; - - png_bytepp rowptrs = nullptr; - size_t rowbytes = 0; - - uint8_t* data = nullptr; - size_t datasize = 0; - - fseek(file, 0, SEEK_END); - datasize = ftell(file); - fseek(file, 0, SEEK_SET); - - png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, (png_voidp)0, RendererLibPngErrorCb, RendererLibPngWarningCb); - - if (!png_ptr) - { - fclose(file); - - return ret.idx; - } - - info_ptr = png_create_info_struct(png_ptr); - - if (!info_ptr) - { - png_destroy_read_struct(&png_ptr, (png_infopp)NULL, (png_infopp)NULL); - - fclose(file); - - return ret.idx; - } - - if (setjmp(png_jmpbuf(png_ptr))) - { - png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL); - - fclose(file); - - return ret.idx; - } - - png_init_io(png_ptr, file); - - png_set_filter(png_ptr, 0, PNG_FILTER_NONE); - - if (!doesItFitInMemory(datasize)) - { - png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL); - - fclose(file); - - return ret.idx; - } - - png_read_png(png_ptr, info_ptr, PNG_TRANSFORM_EXPAND, NULL); - - color_type = png_get_color_type(png_ptr, info_ptr); - bit_depth = png_get_bit_depth(png_ptr, info_ptr); - _width = png_get_image_width(png_ptr, info_ptr); - _height = png_get_image_height(png_ptr, info_ptr); - - rowptrs = png_get_rows(png_ptr, info_ptr); - rowbytes = png_get_rowbytes(png_ptr, info_ptr); - - datasize = rowbytes * _height; - - if (!doesItFitInMemory(datasize)) - { - png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL); - - fclose(file); - - return ret.idx; - } - - data = (uint8_t*)driver_calloc(datasize, sizeof(uint8_t)); - - for (png_uint_32 y = 0; y < _height; y++) memcpy(data + (rowbytes * y), rowptrs[y], rowbytes); - - png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL); - - fclose(file); - - // ------------------------------------------------------------ - - bgfx::TextureFormat::Enum texFmt = bgfx::TextureFormat::Unknown; - - switch (bit_depth) - { - case 8: - { - switch (color_type) - { - case PNG_COLOR_TYPE_GRAY: - texFmt = bgfx::TextureFormat::R8; - break; - case PNG_COLOR_TYPE_GRAY_ALPHA: - texFmt = bgfx::TextureFormat::RG8; - break; - case PNG_COLOR_TYPE_RGB: - texFmt = bgfx::TextureFormat::RGB8; - break; - case PNG_COLOR_TYPE_RGBA: - case PNG_COLOR_TYPE_PALETTE: - texFmt = bgfx::TextureFormat::RGBA8; - break; - } - break; - } - case 16: - { - switch (color_type) - { - case PNG_COLOR_TYPE_GRAY: - texFmt = bgfx::TextureFormat::R16; - break; - case PNG_COLOR_TYPE_GRAY_ALPHA: - texFmt = bgfx::TextureFormat::RG16; - break; - case PNG_COLOR_TYPE_RGB: - case PNG_COLOR_TYPE_RGBA: - texFmt = bgfx::TextureFormat::RGBA16; - break; - case PNG_COLOR_TYPE_PALETTE: - break; - } - break; - } - default: - break; - } + if (!loadPng(filename, mip)) { + return ret.idx; + } - if (texFmt != bgfx::TextureFormat::Unknown) - { - const bgfx::Memory* mem = bgfx::makeRef(data, datasize, RendererReleaseData, data); + const bgfx::Memory* mem = bgfx::makeRef(mip.m_data, mip.m_size, RendererReleaseData, (void *)mip.m_data); - uint64_t flags = BGFX_SAMPLER_NONE; + uint64_t flags = BGFX_SAMPLER_NONE; - if (isSrgb) flags |= BGFX_TEXTURE_SRGB; - else flags |= BGFX_TEXTURE_NONE; + if (isSrgb) flags |= BGFX_TEXTURE_SRGB; + else flags |= BGFX_TEXTURE_NONE; - ret = bgfx::createTexture2D( - _width, - _height, - false, - 1, - texFmt, - flags, - mem - ); + ret = bgfx::createTexture2D( + mip.m_width, + mip.m_height, + false, + 1, + bgfx::TextureFormat::Enum(mip.m_format), + flags, + mem + ); - *width = _width; - *height = _height; - } - else - driver_free(data); + *width = mip.m_width; + *height = mip.m_height; - if (trace_all || trace_renderer) ffnx_trace("Renderer::%s: %u => %ux%u from filename %s\n", __func__, ret.idx, width, height, filename); - } + if (trace_all || trace_renderer) ffnx_trace("Renderer::%s: %u => %ux%u from filename %s\n", __func__, ret.idx, width, height, filename); return ret.idx; } @@ -2625,7 +2435,7 @@ void Renderer::setGameLightData(light_data* lightdata) internalState.gameLightColor3[3] = 1.0; if (mode->driver_mode == MODE_WORLDMAP) - { + { internalState.gameLightDir1[0] = lightdata->light_dir_1.x; internalState.gameLightDir1[1] = lightdata->light_dir_1.z; internalState.gameLightDir1[2] = lightdata->light_dir_1.y; @@ -2638,7 +2448,7 @@ void Renderer::setGameLightData(light_data* lightdata) internalState.gameLightDir3[1] = lightdata->light_dir_3.z; internalState.gameLightDir3[2] = lightdata->light_dir_3.y; } - else + else { internalState.gameLightDir1[0] = -lightdata->light_dir_1.x; internalState.gameLightDir1[1] = -lightdata->light_dir_1.y; diff --git a/src/renderer.h b/src/renderer.h index e9ff327b..8f0a48fc 100644 --- a/src/renderer.h +++ b/src/renderer.h @@ -37,7 +37,6 @@ #include #include #include -#include #include #include "log.h" #include "gl.h" @@ -159,16 +158,6 @@ static void RendererReleaseData(void* _ptr, void* _userData) driver_free(_userData); } -static void RendererLibPngErrorCb(png_structp png_ptr, const char* error) -{ - ffnx_error("libpng error: %s\n", error); -} - -static void RendererLibPngWarningCb(png_structp png_ptr, const char* warning) -{ - ffnx_info("libpng warning: %s\n", warning); -} - struct RendererCallbacks : public bgfx::CallbackI { std::string cachePath = R"(shaders\cache)"; @@ -384,8 +373,6 @@ class Renderer { void printMatrix(char* name, float* mat); - bool doesItFitInMemory(size_t size); - void recalcInternals(); void calcBackendProjMatrix(); void prepareFramebuffer(); @@ -443,8 +430,8 @@ class Renderer { uint32_t createTexture(uint8_t* data, size_t width, size_t height, int stride = 0, RendererTextureType type = RendererTextureType::BGRA, bool isSrgb = true); uint32_t createTexture(char* filename, uint32_t* width, uint32_t* height, uint32_t* mipCount, bool isSrgb = true); - bimg::ImageContainer* createImageContainer(const char* filename, bimg::TextureFormat::Enum targetFormat = bimg::TextureFormat::Enum::UnknownDepth); - bimg::ImageContainer* createImageContainer(cmrc::file* file, bimg::TextureFormat::Enum targetFormat = bimg::TextureFormat::Enum::UnknownDepth); + bimg::ImageContainer* createImageContainer(const char* filename, bimg::TextureFormat::Enum targetFormat = bimg::TextureFormat::Enum::Count); + bimg::ImageContainer* createImageContainer(cmrc::file* file, bimg::TextureFormat::Enum targetFormat = bimg::TextureFormat::Enum::Count); bgfx::TextureHandle createTextureHandle(char* filename, uint32_t* width, uint32_t* height, uint32_t* mipCount, bool isSrgb = true); bgfx::TextureHandle createTextureHandle(cmrc::file* file, char* filename, uint32_t* width, uint32_t* height, uint32_t* mipCount, bool isSrgb = true); uint32_t createTextureLibPng(char* filename, uint32_t* width, uint32_t* height, bool isSrgb = true); @@ -514,6 +501,8 @@ class Renderer { // Game lighting void setGameLightData(light_data* lightdata = nullptr); + + static bool doesItFitInMemory(size_t size); }; extern Renderer newRenderer;