tibia-rme/source/sprite_appearances.h
Victor 21b234e2ab
feat: deferred rendering pipeline, memory optimization, and async map cleanup (#173)
New Features:
- Scene caching with render-to-texture and blitting for faster map redraws.
- Explicit blend-mode controls and FBO-backed render-to-texture workflow.

Bug Fixes:
- More robust sprite-sheet reload and texture release behavior.
- Safer editor/tab shutdown with ensured live-server closure and deferred deletion.

Performance:
- Reworked GPU batching/streaming and command-based flushing for fewer draw calls and efficient uploads.
- Smarter scene invalidation to avoid unnecessary re-renders.

UX:
- Undo/redo now refreshes editor views more reliably.
2026-04-25 10:35:24 -03:00

230 lines
6.2 KiB
C++

//////////////////////////////////////////////////////////////////////
// This file is part of Canary Map Editor
//////////////////////////////////////////////////////////////////////
// Canary Map Editor 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, or
// (at your option) any later version.
//
// Canary Map Editor 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.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//////////////////////////////////////////////////////////////////////
#ifndef SPRITEAPPEARANCES_H
#define SPRITEAPPEARANCES_H
#include "definitions.h"
#include "main.h"
#include "graphics.h"
#include <chrono>
class GameSprite;
// APPEARANCES
#define LZMA_UNCOMPRESSED_SIZE BYTES_IN_SPRITE_SHEET + 122
#define LZMA_HEADER_SIZE LZMA_PROPS_SIZE + 8
#define SPRITE_SHEET_WIDTH 384
#define SPRITE_SHEET_HEIGHT 384
#define BYTES_IN_SPRITE_SHEET SPRITE_SHEET_WIDTH* SPRITE_SHEET_WIDTH * 4
#define SPRITE_SHEET_WIDTH_BYTES SPRITE_SHEET_WIDTH * 4
enum class SpriteLayout {
ONE_BY_ONE = 0,
ONE_BY_TWO = 1,
TWO_BY_ONE = 2,
TWO_BY_TWO = 3
};
struct SpritesSize {
public:
SpritesSize(int width, int height) :
height(height), width(width) { }
void resize(int width, int height) {
this->height = height;
this->width = width;
}
void setHeight(int height) {
this->height = height;
}
void setWidth(int width) {
this->width = width;
}
int area() const {
return width * height;
}
int height = 0;
int width = 0;
};
struct Sprites {
public:
Sprites(int32_t width, int32_t height) :
size(width, height) {
pixels.resize(width * height * 4, 0);
}
bool save(const std::string &file, bool fixMagenta = false) {
wxImage image(size.width, size.height);
for (int y = 0; y < size.height; ++y) {
for (int x = 0; x < size.width; ++x) {
const int index = (y * size.width + x) * 4;
const uint8_t r = pixels[index + 2];
const uint8_t g = pixels[index + 1];
const uint8_t b = pixels[index];
image.SetRGB(x, y, r, g, b);
}
}
return image.SaveFile(wxString(file), wxBITMAP_TYPE_PNG);
}
std::vector<uint8_t> pixels;
SpritesSize size;
};
class SpriteSheet {
public:
SpriteSheet(int firstId, int lastId, SpriteLayout spriteLayout, const std::string &path) :
firstId(firstId), lastId(lastId), spriteLayout(spriteLayout), path(path) { }
SpritesSize getSpriteSize() const {
SpritesSize size(rme::SpritePixels, rme::SpritePixels);
switch (spriteLayout) {
case SpriteLayout::ONE_BY_ONE:
break;
case SpriteLayout::ONE_BY_TWO:
size.setHeight(64);
break;
case SpriteLayout::TWO_BY_ONE:
size.setWidth(64);
break;
case SpriteLayout::TWO_BY_TWO:
size.resize(64, 64);
break;
default:
break;
}
return size;
}
int getTotalSprites() const {
return lastId - firstId + 1;
}
int getTotalRows() const {
auto spriteSize = getSpriteSize();
if (spriteSize.width == 0) {
return 0; // Avoid division by zero
}
int spritesPerRow = SPRITE_SHEET_WIDTH / spriteSize.width; // Use the actual sheet width
if (spritesPerRow == 0) {
return 0; // No rows if sprites don't fit in a line
}
// Calculate total sprites and round up division for rows
return (getTotalSprites() + spritesPerRow - 1) / spritesPerRow;
}
bool exportSheetImage(const std::string &file, bool fixMagenta = false) {
if (!data) {
return false;
}
wxImage image(384, 384, data.get(), true);
return image.SaveFile(wxString(file), wxBITMAP_TYPE_PNG);
};
GLuint getOrUploadGLTexture();
void releaseGLTexture();
SpriteUV getSpriteUVs(int spriteId) const;
int firstId = 0;
int lastId = 0;
SpriteLayout spriteLayout = SpriteLayout::ONE_BY_ONE;
std::unique_ptr<uint8_t[]> data;
std::string path;
bool loaded = false;
GLuint glTextureId = 0;
std::chrono::steady_clock::time_point lastaccess;
};
using SpritePtr = std::shared_ptr<Sprites>;
using SpriteSheetPtr = std::shared_ptr<SpriteSheet>;
//@bindsingleton g_spriteAppearances
class SpriteAppearances {
public:
void init();
void terminate();
void unload();
// sprites
void exportSpriteImage(int id, const std::string &path);
SpritePtr getSprite(int spriteId);
int getSpritesCount() {
return spritesCount;
}
void setSpritesCount(int count) {
spritesCount = count;
}
int getSpritesCount() const {
return spritesCount;
}
/**
Returns a wxImage object containing the sprite image data for a given sprite ID.
@param id The ID of the sprite to retrieve.
@param toSavePng Determines if the image should be saved as a PNG file.
@return A wxImage object containing the sprite image data.
@remarks The image data is retrieved from a sprite sheet using the provided ID.
If toSavePng is set to true, the black color (0, 0, 0) is set to transparent by default and the image will be saved as a PNG file (with transparent background).
Use to save image: image.SaveFile(g_gui.GetDataDirectory().ToStdString() + "image.png", wxBITMAP_TYPE_PNG);
*/
wxImage getWxImageBySpriteId(int id, bool toSavePng = false);
const std::string getAppearanceFileName() const {
return appearanceFile;
}
bool loadCatalogContent(const std::string &dir, bool loadData = true);
bool loadSpriteSheet(const SpriteSheetPtr &sheet);
void saveSheetToFileBySprite(int id, const std::string &file);
void saveSheetToFile(const SpriteSheetPtr &sheet, const std::string &file);
struct AtlasInfo {
GLuint textureId;
SpriteUV uvs;
};
AtlasInfo getAtlasInfo(int spriteId);
SpriteSheetPtr getSheetBySpriteId(int id, bool load = true);
std::vector<SpriteSheetPtr> &getSheets() {
return sheets;
}
void addSpriteSheet(SpriteSheetPtr sheet) {
sheets.push_back(sheet);
}
void saveSpriteToFile(int id, const std::string &file);
private:
int spritesCount = 0;
std::vector<SpriteSheetPtr> sheets;
std::map<int, SpritePtr> sprites;
std::string appearanceFile;
};
extern SpriteAppearances g_spriteAppearances;
#endif