fix: apply clang-format to all source files
Fixes formatting violations that were causing CI failures. Applied clang-format-14 to ensure consistent code formatting across the codebase. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -10,7 +10,7 @@ namespace yaze {
|
||||
namespace gfx {
|
||||
class Bitmap;
|
||||
}
|
||||
}
|
||||
} // namespace yaze
|
||||
|
||||
namespace yaze {
|
||||
namespace gfx {
|
||||
@@ -33,34 +33,34 @@ using TextureHandle = void*;
|
||||
* concrete rendering backend to be swapped out with minimal changes to the application code.
|
||||
*/
|
||||
class IRenderer {
|
||||
public:
|
||||
virtual ~IRenderer() = default;
|
||||
public:
|
||||
virtual ~IRenderer() = default;
|
||||
|
||||
// --- Initialization and Lifecycle ---
|
||||
// --- Initialization and Lifecycle ---
|
||||
|
||||
/**
|
||||
/**
|
||||
* @brief Initializes the renderer with a given window.
|
||||
* @param window A pointer to the SDL_Window to render into.
|
||||
* @return True if initialization was successful, false otherwise.
|
||||
*/
|
||||
virtual bool Initialize(SDL_Window* window) = 0;
|
||||
virtual bool Initialize(SDL_Window* window) = 0;
|
||||
|
||||
/**
|
||||
/**
|
||||
* @brief Shuts down the renderer and releases all associated resources.
|
||||
*/
|
||||
virtual void Shutdown() = 0;
|
||||
virtual void Shutdown() = 0;
|
||||
|
||||
// --- Texture Management ---
|
||||
// --- Texture Management ---
|
||||
|
||||
/**
|
||||
/**
|
||||
* @brief Creates a new, empty texture.
|
||||
* @param width The width of the texture in pixels.
|
||||
* @param height The height of the texture in pixels.
|
||||
* @return An abstract TextureHandle to the newly created texture, or nullptr on failure.
|
||||
*/
|
||||
virtual TextureHandle CreateTexture(int width, int height) = 0;
|
||||
virtual TextureHandle CreateTexture(int width, int height) = 0;
|
||||
|
||||
/**
|
||||
/**
|
||||
* @brief Creates a new texture with a specific pixel format.
|
||||
* @param width The width of the texture in pixels.
|
||||
* @param height The height of the texture in pixels.
|
||||
@@ -68,60 +68,64 @@ public:
|
||||
* @param access The texture access pattern (e.g., SDL_TEXTUREACCESS_STREAMING).
|
||||
* @return An abstract TextureHandle to the newly created texture, or nullptr on failure.
|
||||
*/
|
||||
virtual TextureHandle CreateTextureWithFormat(int width, int height, uint32_t format, int access) = 0;
|
||||
virtual TextureHandle CreateTextureWithFormat(int width, int height,
|
||||
uint32_t format,
|
||||
int access) = 0;
|
||||
|
||||
/**
|
||||
/**
|
||||
* @brief Updates a texture with the pixel data from a Bitmap.
|
||||
* @param texture The handle of the texture to update.
|
||||
* @param bitmap The Bitmap containing the new pixel data.
|
||||
*/
|
||||
virtual void UpdateTexture(TextureHandle texture, const Bitmap& bitmap) = 0;
|
||||
virtual void UpdateTexture(TextureHandle texture, const Bitmap& bitmap) = 0;
|
||||
|
||||
/**
|
||||
/**
|
||||
* @brief Destroys a texture and frees its associated resources.
|
||||
* @param texture The handle of the texture to destroy.
|
||||
*/
|
||||
virtual void DestroyTexture(TextureHandle texture) = 0;
|
||||
virtual void DestroyTexture(TextureHandle texture) = 0;
|
||||
|
||||
// --- Direct Pixel Access ---
|
||||
virtual bool LockTexture(TextureHandle texture, SDL_Rect* rect, void** pixels, int* pitch) = 0;
|
||||
virtual void UnlockTexture(TextureHandle texture) = 0;
|
||||
// --- Direct Pixel Access ---
|
||||
virtual bool LockTexture(TextureHandle texture, SDL_Rect* rect, void** pixels,
|
||||
int* pitch) = 0;
|
||||
virtual void UnlockTexture(TextureHandle texture) = 0;
|
||||
|
||||
// --- Rendering Primitives ---
|
||||
// --- Rendering Primitives ---
|
||||
|
||||
/**
|
||||
/**
|
||||
* @brief Clears the entire render target with the current draw color.
|
||||
*/
|
||||
virtual void Clear() = 0;
|
||||
virtual void Clear() = 0;
|
||||
|
||||
/**
|
||||
/**
|
||||
* @brief Presents the back buffer to the screen, making the rendered content visible.
|
||||
*/
|
||||
virtual void Present() = 0;
|
||||
virtual void Present() = 0;
|
||||
|
||||
/**
|
||||
/**
|
||||
* @brief Copies a portion of a texture to the current render target.
|
||||
* @param texture The source texture handle.
|
||||
* @param srcrect A pointer to the source rectangle, or nullptr for the entire texture.
|
||||
* @param dstrect A pointer to the destination rectangle, or nullptr for the entire render target.
|
||||
*/
|
||||
virtual void RenderCopy(TextureHandle texture, const SDL_Rect* srcrect, const SDL_Rect* dstrect) = 0;
|
||||
virtual void RenderCopy(TextureHandle texture, const SDL_Rect* srcrect,
|
||||
const SDL_Rect* dstrect) = 0;
|
||||
|
||||
/**
|
||||
/**
|
||||
* @brief Sets the render target for subsequent drawing operations.
|
||||
* @param texture The texture to set as the render target, or nullptr to set it back to the default (the window).
|
||||
*/
|
||||
virtual void SetRenderTarget(TextureHandle texture) = 0;
|
||||
virtual void SetRenderTarget(TextureHandle texture) = 0;
|
||||
|
||||
/**
|
||||
/**
|
||||
* @brief Sets the color used for drawing operations (e.g., Clear).
|
||||
* @param color The SDL_Color to use.
|
||||
*/
|
||||
virtual void SetDrawColor(SDL_Color color) = 0;
|
||||
virtual void SetDrawColor(SDL_Color color) = 0;
|
||||
|
||||
// --- Backend-specific Access ---
|
||||
// --- Backend-specific Access ---
|
||||
|
||||
/**
|
||||
/**
|
||||
* @brief Provides an escape hatch to get the underlying, concrete renderer object.
|
||||
*
|
||||
* This is necessary for integrating with third-party libraries like ImGui that are tied
|
||||
@@ -130,8 +134,8 @@ public:
|
||||
* @return A void pointer to the backend-specific renderer object. The caller is responsible
|
||||
* for casting it to the correct type.
|
||||
*/
|
||||
virtual void* GetBackendRenderer() = 0;
|
||||
virtual void* GetBackendRenderer() = 0;
|
||||
};
|
||||
|
||||
} // namespace gfx
|
||||
} // namespace yaze
|
||||
} // namespace gfx
|
||||
} // namespace yaze
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace gfx {
|
||||
SDL2Renderer::SDL2Renderer() = default;
|
||||
|
||||
SDL2Renderer::~SDL2Renderer() {
|
||||
Shutdown();
|
||||
Shutdown();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -16,19 +16,19 @@ SDL2Renderer::~SDL2Renderer() {
|
||||
* This function creates an accelerated SDL2 renderer and attaches it to the given window.
|
||||
*/
|
||||
bool SDL2Renderer::Initialize(SDL_Window* window) {
|
||||
// Create an SDL2 renderer with hardware acceleration.
|
||||
renderer_ = std::unique_ptr<SDL_Renderer, util::SDL_Deleter>(
|
||||
SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED));
|
||||
|
||||
if (renderer_ == nullptr) {
|
||||
// Log an error if renderer creation fails.
|
||||
printf("SDL_CreateRenderer Error: %s\n", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
// Create an SDL2 renderer with hardware acceleration.
|
||||
renderer_ = std::unique_ptr<SDL_Renderer, util::SDL_Deleter>(
|
||||
SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED));
|
||||
|
||||
// Set the blend mode to allow for transparency.
|
||||
SDL_SetRenderDrawBlendMode(renderer_.get(), SDL_BLENDMODE_BLEND);
|
||||
return true;
|
||||
if (renderer_ == nullptr) {
|
||||
// Log an error if renderer creation fails.
|
||||
printf("SDL_CreateRenderer Error: %s\n", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set the blend mode to allow for transparency.
|
||||
SDL_SetRenderDrawBlendMode(renderer_.get(), SDL_BLENDMODE_BLEND);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -36,7 +36,7 @@ bool SDL2Renderer::Initialize(SDL_Window* window) {
|
||||
* The underlying SDL_Renderer is managed by a unique_ptr, so its destruction is handled automatically.
|
||||
*/
|
||||
void SDL2Renderer::Shutdown() {
|
||||
renderer_.reset();
|
||||
renderer_.reset();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -44,20 +44,21 @@ void SDL2Renderer::Shutdown() {
|
||||
* The texture is created with streaming access, which is suitable for textures that are updated frequently.
|
||||
*/
|
||||
TextureHandle SDL2Renderer::CreateTexture(int width, int height) {
|
||||
// The TextureHandle is a void*, so we cast the SDL_Texture* to it.
|
||||
return static_cast<TextureHandle>(
|
||||
SDL_CreateTexture(renderer_.get(), SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_STREAMING, width, height)
|
||||
);
|
||||
// The TextureHandle is a void*, so we cast the SDL_Texture* to it.
|
||||
return static_cast<TextureHandle>(
|
||||
SDL_CreateTexture(renderer_.get(), SDL_PIXELFORMAT_RGBA8888,
|
||||
SDL_TEXTUREACCESS_STREAMING, width, height));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Creates an SDL_Texture with a specific pixel format and access pattern.
|
||||
* This is useful for specialized textures like emulator PPU output.
|
||||
*/
|
||||
TextureHandle SDL2Renderer::CreateTextureWithFormat(int width, int height, uint32_t format, int access) {
|
||||
return static_cast<TextureHandle>(
|
||||
SDL_CreateTexture(renderer_.get(), format, access, width, height)
|
||||
);
|
||||
TextureHandle SDL2Renderer::CreateTextureWithFormat(int width, int height,
|
||||
uint32_t format,
|
||||
int access) {
|
||||
return static_cast<TextureHandle>(
|
||||
SDL_CreateTexture(renderer_.get(), format, access, width, height));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -65,81 +66,87 @@ TextureHandle SDL2Renderer::CreateTextureWithFormat(int width, int height, uint3
|
||||
* This involves converting the bitmap's surface to the correct format and updating the texture.
|
||||
*/
|
||||
void SDL2Renderer::UpdateTexture(TextureHandle texture, const Bitmap& bitmap) {
|
||||
SDL_Surface* surface = bitmap.surface();
|
||||
|
||||
// Validate texture, surface, and surface format
|
||||
if (!texture || !surface || !surface->format) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate surface has pixels
|
||||
if (!surface->pixels || surface->w <= 0 || surface->h <= 0) {
|
||||
return;
|
||||
}
|
||||
SDL_Surface* surface = bitmap.surface();
|
||||
|
||||
// Convert the bitmap's surface to RGBA8888 format for compatibility with the texture.
|
||||
auto converted_surface = std::unique_ptr<SDL_Surface, util::SDL_Surface_Deleter>(
|
||||
SDL_ConvertSurfaceFormat(surface, SDL_PIXELFORMAT_RGBA8888, 0));
|
||||
// Validate texture, surface, and surface format
|
||||
if (!texture || !surface || !surface->format) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!converted_surface || !converted_surface->pixels) {
|
||||
return;
|
||||
}
|
||||
// Validate surface has pixels
|
||||
if (!surface->pixels || surface->w <= 0 || surface->h <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the texture with the pixels from the converted surface.
|
||||
SDL_UpdateTexture(static_cast<SDL_Texture*>(texture), nullptr, converted_surface->pixels, converted_surface->pitch);
|
||||
// Convert the bitmap's surface to RGBA8888 format for compatibility with the texture.
|
||||
auto converted_surface =
|
||||
std::unique_ptr<SDL_Surface, util::SDL_Surface_Deleter>(
|
||||
SDL_ConvertSurfaceFormat(surface, SDL_PIXELFORMAT_RGBA8888, 0));
|
||||
|
||||
if (!converted_surface || !converted_surface->pixels) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the texture with the pixels from the converted surface.
|
||||
SDL_UpdateTexture(static_cast<SDL_Texture*>(texture), nullptr,
|
||||
converted_surface->pixels, converted_surface->pitch);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Destroys an SDL_Texture.
|
||||
*/
|
||||
void SDL2Renderer::DestroyTexture(TextureHandle texture) {
|
||||
if (texture) {
|
||||
SDL_DestroyTexture(static_cast<SDL_Texture*>(texture));
|
||||
}
|
||||
if (texture) {
|
||||
SDL_DestroyTexture(static_cast<SDL_Texture*>(texture));
|
||||
}
|
||||
}
|
||||
|
||||
bool SDL2Renderer::LockTexture(TextureHandle texture, SDL_Rect* rect, void** pixels, int* pitch) {
|
||||
return SDL_LockTexture(static_cast<SDL_Texture*>(texture), rect, pixels, pitch) == 0;
|
||||
bool SDL2Renderer::LockTexture(TextureHandle texture, SDL_Rect* rect,
|
||||
void** pixels, int* pitch) {
|
||||
return SDL_LockTexture(static_cast<SDL_Texture*>(texture), rect, pixels,
|
||||
pitch) == 0;
|
||||
}
|
||||
|
||||
void SDL2Renderer::UnlockTexture(TextureHandle texture) {
|
||||
SDL_UnlockTexture(static_cast<SDL_Texture*>(texture));
|
||||
SDL_UnlockTexture(static_cast<SDL_Texture*>(texture));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Clears the screen with the current draw color.
|
||||
*/
|
||||
void SDL2Renderer::Clear() {
|
||||
SDL_RenderClear(renderer_.get());
|
||||
SDL_RenderClear(renderer_.get());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Presents the rendered frame to the screen.
|
||||
*/
|
||||
void SDL2Renderer::Present() {
|
||||
SDL_RenderPresent(renderer_.get());
|
||||
SDL_RenderPresent(renderer_.get());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Copies a texture to the render target.
|
||||
*/
|
||||
void SDL2Renderer::RenderCopy(TextureHandle texture, const SDL_Rect* srcrect, const SDL_Rect* dstrect) {
|
||||
SDL_RenderCopy(renderer_.get(), static_cast<SDL_Texture*>(texture), srcrect, dstrect);
|
||||
void SDL2Renderer::RenderCopy(TextureHandle texture, const SDL_Rect* srcrect,
|
||||
const SDL_Rect* dstrect) {
|
||||
SDL_RenderCopy(renderer_.get(), static_cast<SDL_Texture*>(texture), srcrect,
|
||||
dstrect);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets the render target.
|
||||
*/
|
||||
void SDL2Renderer::SetRenderTarget(TextureHandle texture) {
|
||||
SDL_SetRenderTarget(renderer_.get(), static_cast<SDL_Texture*>(texture));
|
||||
SDL_SetRenderTarget(renderer_.get(), static_cast<SDL_Texture*>(texture));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets the draw color.
|
||||
*/
|
||||
void SDL2Renderer::SetDrawColor(SDL_Color color) {
|
||||
SDL_SetRenderDrawColor(renderer_.get(), color.r, color.g, color.b, color.a);
|
||||
SDL_SetRenderDrawColor(renderer_.get(), color.r, color.g, color.b, color.a);
|
||||
}
|
||||
|
||||
} // namespace gfx
|
||||
} // namespace yaze
|
||||
} // namespace gfx
|
||||
} // namespace yaze
|
||||
|
||||
@@ -16,41 +16,44 @@ namespace gfx {
|
||||
* to be independent of SDL2.
|
||||
*/
|
||||
class SDL2Renderer : public IRenderer {
|
||||
public:
|
||||
SDL2Renderer();
|
||||
~SDL2Renderer() override;
|
||||
public:
|
||||
SDL2Renderer();
|
||||
~SDL2Renderer() override;
|
||||
|
||||
// --- Lifecycle and Initialization ---
|
||||
bool Initialize(SDL_Window* window) override;
|
||||
void Shutdown() override;
|
||||
// --- Lifecycle and Initialization ---
|
||||
bool Initialize(SDL_Window* window) override;
|
||||
void Shutdown() override;
|
||||
|
||||
// --- Texture Management ---
|
||||
TextureHandle CreateTexture(int width, int height) override;
|
||||
TextureHandle CreateTextureWithFormat(int width, int height, uint32_t format, int access) override;
|
||||
void UpdateTexture(TextureHandle texture, const Bitmap& bitmap) override;
|
||||
void DestroyTexture(TextureHandle texture) override;
|
||||
// --- Texture Management ---
|
||||
TextureHandle CreateTexture(int width, int height) override;
|
||||
TextureHandle CreateTextureWithFormat(int width, int height, uint32_t format,
|
||||
int access) override;
|
||||
void UpdateTexture(TextureHandle texture, const Bitmap& bitmap) override;
|
||||
void DestroyTexture(TextureHandle texture) override;
|
||||
|
||||
// --- Direct Pixel Access ---
|
||||
bool LockTexture(TextureHandle texture, SDL_Rect* rect, void** pixels, int* pitch) override;
|
||||
void UnlockTexture(TextureHandle texture) override;
|
||||
// --- Direct Pixel Access ---
|
||||
bool LockTexture(TextureHandle texture, SDL_Rect* rect, void** pixels,
|
||||
int* pitch) override;
|
||||
void UnlockTexture(TextureHandle texture) override;
|
||||
|
||||
// --- Rendering Primitives ---
|
||||
void Clear() override;
|
||||
void Present() override;
|
||||
void RenderCopy(TextureHandle texture, const SDL_Rect* srcrect, const SDL_Rect* dstrect) override;
|
||||
void SetRenderTarget(TextureHandle texture) override;
|
||||
void SetDrawColor(SDL_Color color) override;
|
||||
// --- Rendering Primitives ---
|
||||
void Clear() override;
|
||||
void Present() override;
|
||||
void RenderCopy(TextureHandle texture, const SDL_Rect* srcrect,
|
||||
const SDL_Rect* dstrect) override;
|
||||
void SetRenderTarget(TextureHandle texture) override;
|
||||
void SetDrawColor(SDL_Color color) override;
|
||||
|
||||
/**
|
||||
/**
|
||||
* @brief Provides access to the underlying SDL_Renderer*.
|
||||
* @return A void pointer that can be safely cast to an SDL_Renderer*.
|
||||
*/
|
||||
void* GetBackendRenderer() override { return renderer_.get(); }
|
||||
void* GetBackendRenderer() override { return renderer_.get(); }
|
||||
|
||||
private:
|
||||
// The core SDL2 renderer object, managed by a unique_ptr with a custom deleter.
|
||||
std::unique_ptr<SDL_Renderer, util::SDL_Deleter> renderer_;
|
||||
private:
|
||||
// The core SDL2 renderer object, managed by a unique_ptr with a custom deleter.
|
||||
std::unique_ptr<SDL_Renderer, util::SDL_Deleter> renderer_;
|
||||
};
|
||||
|
||||
} // namespace gfx
|
||||
} // namespace yaze
|
||||
} // namespace gfx
|
||||
} // namespace yaze
|
||||
|
||||
@@ -7,15 +7,14 @@
|
||||
#include <span>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "app/gfx/resource/arena.h"
|
||||
#include "app/gfx/debug/performance/performance_profiler.h"
|
||||
#include "app/gfx/resource/arena.h"
|
||||
#include "app/gfx/types/snes_palette.h"
|
||||
#include "util/log.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace gfx {
|
||||
|
||||
|
||||
class BitmapError : public std::runtime_error {
|
||||
public:
|
||||
using std::runtime_error::runtime_error;
|
||||
@@ -45,13 +44,13 @@ Uint32 GetSnesPixelFormat(int format) {
|
||||
}
|
||||
|
||||
Bitmap::Bitmap(int width, int height, int depth,
|
||||
const std::vector<uint8_t> &data)
|
||||
const std::vector<uint8_t>& data)
|
||||
: width_(width), height_(height), depth_(depth), data_(data) {
|
||||
Create(width, height, depth, data);
|
||||
}
|
||||
|
||||
Bitmap::Bitmap(int width, int height, int depth,
|
||||
const std::vector<uint8_t> &data, const SnesPalette &palette)
|
||||
const std::vector<uint8_t>& data, const SnesPalette& palette)
|
||||
: width_(width),
|
||||
height_(height),
|
||||
depth_(depth),
|
||||
@@ -72,8 +71,8 @@ Bitmap::Bitmap(const Bitmap& other)
|
||||
// Copy the data and recreate surface/texture with simple assignment
|
||||
pixel_data_ = data_.data();
|
||||
if (active_ && !data_.empty()) {
|
||||
surface_ = Arena::Get().AllocateSurface(width_, height_, depth_,
|
||||
GetSnesPixelFormat(BitmapFormat::kIndexed));
|
||||
surface_ = Arena::Get().AllocateSurface(
|
||||
width_, height_, depth_, GetSnesPixelFormat(BitmapFormat::kIndexed));
|
||||
if (surface_) {
|
||||
SDL_LockSurface(surface_);
|
||||
memcpy(surface_->pixels, pixel_data_, data_.size());
|
||||
@@ -91,12 +90,12 @@ Bitmap& Bitmap::operator=(const Bitmap& other) {
|
||||
modified_ = other.modified_;
|
||||
palette_ = other.palette_;
|
||||
data_ = other.data_;
|
||||
|
||||
|
||||
// Copy the data and recreate surface/texture
|
||||
pixel_data_ = data_.data();
|
||||
if (active_ && !data_.empty()) {
|
||||
surface_ = Arena::Get().AllocateSurface(width_, height_, depth_,
|
||||
GetSnesPixelFormat(BitmapFormat::kIndexed));
|
||||
surface_ = Arena::Get().AllocateSurface(
|
||||
width_, height_, depth_, GetSnesPixelFormat(BitmapFormat::kIndexed));
|
||||
if (surface_) {
|
||||
SDL_LockSurface(surface_);
|
||||
memcpy(surface_->pixels, pixel_data_, data_.size());
|
||||
@@ -144,7 +143,7 @@ Bitmap& Bitmap::operator=(Bitmap&& other) noexcept {
|
||||
data_ = std::move(other.data_);
|
||||
surface_ = other.surface_;
|
||||
texture_ = other.texture_;
|
||||
|
||||
|
||||
// Reset the moved-from object
|
||||
other.width_ = 0;
|
||||
other.height_ = 0;
|
||||
@@ -165,7 +164,7 @@ void Bitmap::Create(int width, int height, int depth, std::span<uint8_t> data) {
|
||||
}
|
||||
|
||||
void Bitmap::Create(int width, int height, int depth,
|
||||
const std::vector<uint8_t> &data) {
|
||||
const std::vector<uint8_t>& data) {
|
||||
Create(width, height, depth, static_cast<int>(BitmapFormat::kIndexed), data);
|
||||
}
|
||||
|
||||
@@ -184,7 +183,7 @@ void Bitmap::Create(int width, int height, int depth,
|
||||
* - Sets active flag for rendering pipeline
|
||||
*/
|
||||
void Bitmap::Create(int width, int height, int depth, int format,
|
||||
const std::vector<uint8_t> &data) {
|
||||
const std::vector<uint8_t>& data) {
|
||||
if (data.empty()) {
|
||||
SDL_Log("Bitmap data is empty\n");
|
||||
active_ = false;
|
||||
@@ -209,7 +208,7 @@ void Bitmap::Create(int width, int height, int depth, int format,
|
||||
active_ = false;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// CRITICAL FIX: Use proper SDL surface operations instead of direct pointer assignment
|
||||
// Direct assignment breaks SDL's memory management and causes malloc errors on shutdown
|
||||
if (surface_ && data_.size() > 0) {
|
||||
@@ -218,7 +217,7 @@ void Bitmap::Create(int width, int height, int depth, int format,
|
||||
SDL_UnlockSurface(surface_);
|
||||
}
|
||||
active_ = true;
|
||||
|
||||
|
||||
// Apply the stored palette if one exists
|
||||
if (!palette_.empty()) {
|
||||
ApplyStoredPalette();
|
||||
@@ -228,7 +227,7 @@ void Bitmap::Create(int width, int height, int depth, int format,
|
||||
void Bitmap::Reformat(int format) {
|
||||
surface_ = Arena::Get().AllocateSurface(width_, height_, depth_,
|
||||
GetSnesPixelFormat(format));
|
||||
|
||||
|
||||
// CRITICAL FIX: Use proper SDL surface operations instead of direct pointer assignment
|
||||
if (surface_ && data_.size() > 0) {
|
||||
SDL_LockSurface(surface_);
|
||||
@@ -247,8 +246,6 @@ void Bitmap::UpdateTexture() {
|
||||
Arena::Get().QueueTextureCommand(Arena::TextureCommandType::UPDATE, this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @brief Apply the stored palette to the SDL surface
|
||||
*
|
||||
@@ -280,7 +277,7 @@ void Bitmap::ApplyStoredPalette() {
|
||||
InvalidatePaletteCache();
|
||||
|
||||
// For indexed surfaces, ensure palette exists
|
||||
SDL_Palette *sdl_palette = surface_->format->palette;
|
||||
SDL_Palette* sdl_palette = surface_->format->palette;
|
||||
if (sdl_palette == nullptr) {
|
||||
// Non-indexed surface or palette not created - can't apply palette
|
||||
SDL_Log("Warning: Bitmap surface has no palette (non-indexed format?)\n");
|
||||
@@ -288,20 +285,20 @@ void Bitmap::ApplyStoredPalette() {
|
||||
}
|
||||
|
||||
SDL_UnlockSurface(surface_);
|
||||
|
||||
|
||||
// Build SDL color array from SnesPalette
|
||||
// Only set the colors that exist in the palette - don't fill unused entries
|
||||
std::vector<SDL_Color> colors(palette_.size());
|
||||
for (size_t i = 0; i < palette_.size(); ++i) {
|
||||
const auto& pal_color = palette_[i];
|
||||
|
||||
|
||||
// Get RGB values - stored as 0-255 in ImVec4 (unconventional!)
|
||||
ImVec4 rgb_255 = pal_color.rgb();
|
||||
|
||||
|
||||
colors[i].r = static_cast<Uint8>(rgb_255.x);
|
||||
colors[i].g = static_cast<Uint8>(rgb_255.y);
|
||||
colors[i].b = static_cast<Uint8>(rgb_255.z);
|
||||
|
||||
|
||||
// Only apply transparency if explicitly set
|
||||
if (pal_color.is_transparent()) {
|
||||
colors[i].a = 0; // Fully transparent
|
||||
@@ -309,12 +306,13 @@ void Bitmap::ApplyStoredPalette() {
|
||||
colors[i].a = 255; // Fully opaque
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Apply palette to surface using SDL_SetPaletteColors
|
||||
// Only set the colors we have - leave rest of palette unchanged
|
||||
// This prevents breaking systems that use small palettes (8-16 colors)
|
||||
SDL_SetPaletteColors(sdl_palette, colors.data(), 0, static_cast<int>(palette_.size()));
|
||||
|
||||
SDL_SetPaletteColors(sdl_palette, colors.data(), 0,
|
||||
static_cast<int>(palette_.size()));
|
||||
|
||||
SDL_LockSurface(surface_);
|
||||
}
|
||||
|
||||
@@ -322,22 +320,24 @@ void Bitmap::UpdateSurfacePixels() {
|
||||
if (!surface_ || data_.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Copy pixel data from data_ vector to SDL surface
|
||||
SDL_LockSurface(surface_);
|
||||
if (surface_->pixels && data_.size() > 0) {
|
||||
memcpy(surface_->pixels, data_.data(), std::min(data_.size(), static_cast<size_t>(surface_->pitch * surface_->h)));
|
||||
memcpy(surface_->pixels, data_.data(),
|
||||
std::min(data_.size(),
|
||||
static_cast<size_t>(surface_->pitch * surface_->h)));
|
||||
}
|
||||
SDL_UnlockSurface(surface_);
|
||||
}
|
||||
|
||||
void Bitmap::SetPalette(const SnesPalette &palette) {
|
||||
void Bitmap::SetPalette(const SnesPalette& palette) {
|
||||
// Store palette even if surface isn't ready yet
|
||||
palette_ = palette;
|
||||
|
||||
|
||||
// Apply it immediately if surface is ready
|
||||
ApplyStoredPalette();
|
||||
|
||||
|
||||
// Mark as modified to trigger texture update
|
||||
modified_ = true;
|
||||
}
|
||||
@@ -357,7 +357,8 @@ void Bitmap::SetPalette(const SnesPalette &palette) {
|
||||
* @param palette Source palette to apply
|
||||
* @param sub_palette_index Index within palette for sub-palette extraction (default 0)
|
||||
*/
|
||||
void Bitmap::ApplyPaletteByMetadata(const SnesPalette& palette, int sub_palette_index) {
|
||||
void Bitmap::ApplyPaletteByMetadata(const SnesPalette& palette,
|
||||
int sub_palette_index) {
|
||||
if (metadata_.palette_format == 1) {
|
||||
// Sub-palette: need transparent black + 7 colors from palette
|
||||
// Common for 3BPP graphics sheets (title screen, etc.)
|
||||
@@ -400,11 +401,11 @@ void Bitmap::ApplyPaletteByMetadata(const SnesPalette& palette, int sub_palette_
|
||||
* @param index Start index in source palette (0-based)
|
||||
* @param length Number of colors to extract (default 7, max 7)
|
||||
*/
|
||||
void Bitmap::SetPaletteWithTransparent(const SnesPalette &palette, size_t index,
|
||||
void Bitmap::SetPaletteWithTransparent(const SnesPalette& palette, size_t index,
|
||||
int length) {
|
||||
// Store the full palette for reference (not modified)
|
||||
palette_ = palette;
|
||||
|
||||
|
||||
// If surface isn't created yet, just store the palette for later
|
||||
if (surface_ == nullptr) {
|
||||
return; // Palette will be applied when surface is created
|
||||
@@ -416,7 +417,8 @@ void Bitmap::SetPaletteWithTransparent(const SnesPalette &palette, size_t index,
|
||||
}
|
||||
|
||||
if (length < 0 || length > 7) {
|
||||
throw std::invalid_argument("Invalid palette length (must be 0-7 for SNES palettes)");
|
||||
throw std::invalid_argument(
|
||||
"Invalid palette length (must be 0-7 for SNES palettes)");
|
||||
}
|
||||
|
||||
if (index + length > palette.size()) {
|
||||
@@ -425,43 +427,49 @@ void Bitmap::SetPaletteWithTransparent(const SnesPalette &palette, size_t index,
|
||||
|
||||
// Build 8-color SNES sub-palette
|
||||
std::vector<ImVec4> colors;
|
||||
|
||||
|
||||
// Color 0: Transparent (SNES hardware requirement)
|
||||
colors.push_back(ImVec4(0, 0, 0, 0)); // Transparent black
|
||||
|
||||
|
||||
// Colors 1-7: Extract from source palette
|
||||
// NOTE: palette[i].rgb() returns 0-255 values in ImVec4 (unconventional!)
|
||||
for (size_t i = 0; i < 7 && (index + i) < palette.size(); ++i) {
|
||||
const auto &pal_color = palette[index + i];
|
||||
const auto& pal_color = palette[index + i];
|
||||
ImVec4 rgb_255 = pal_color.rgb(); // 0-255 range (unconventional storage)
|
||||
|
||||
|
||||
// Convert to standard ImVec4 0-1 range for SDL
|
||||
colors.push_back(ImVec4(rgb_255.x / 255.0f, rgb_255.y / 255.0f,
|
||||
rgb_255.z / 255.0f, 1.0f)); // Always opaque
|
||||
colors.push_back(ImVec4(rgb_255.x / 255.0f, rgb_255.y / 255.0f,
|
||||
rgb_255.z / 255.0f, 1.0f)); // Always opaque
|
||||
}
|
||||
|
||||
|
||||
// Ensure we have exactly 8 colors
|
||||
while (colors.size() < 8) {
|
||||
colors.push_back(ImVec4(0, 0, 0, 1.0f)); // Fill with opaque black
|
||||
colors.push_back(ImVec4(0, 0, 0, 1.0f)); // Fill with opaque black
|
||||
}
|
||||
|
||||
|
||||
// Update palette cache with full palette (for color lookup)
|
||||
InvalidatePaletteCache();
|
||||
|
||||
// Apply the 8-color SNES sub-palette to SDL surface
|
||||
SDL_UnlockSurface(surface_);
|
||||
for (int color_index = 0; color_index < 8 && color_index < static_cast<int>(colors.size()); ++color_index) {
|
||||
for (int color_index = 0;
|
||||
color_index < 8 && color_index < static_cast<int>(colors.size());
|
||||
++color_index) {
|
||||
if (color_index < surface_->format->palette->ncolors) {
|
||||
surface_->format->palette->colors[color_index].r = static_cast<Uint8>(colors[color_index].x * 255.0f);
|
||||
surface_->format->palette->colors[color_index].g = static_cast<Uint8>(colors[color_index].y * 255.0f);
|
||||
surface_->format->palette->colors[color_index].b = static_cast<Uint8>(colors[color_index].z * 255.0f);
|
||||
surface_->format->palette->colors[color_index].a = static_cast<Uint8>(colors[color_index].w * 255.0f);
|
||||
surface_->format->palette->colors[color_index].r =
|
||||
static_cast<Uint8>(colors[color_index].x * 255.0f);
|
||||
surface_->format->palette->colors[color_index].g =
|
||||
static_cast<Uint8>(colors[color_index].y * 255.0f);
|
||||
surface_->format->palette->colors[color_index].b =
|
||||
static_cast<Uint8>(colors[color_index].z * 255.0f);
|
||||
surface_->format->palette->colors[color_index].a =
|
||||
static_cast<Uint8>(colors[color_index].w * 255.0f);
|
||||
}
|
||||
}
|
||||
SDL_LockSurface(surface_);
|
||||
}
|
||||
|
||||
void Bitmap::SetPalette(const std::vector<SDL_Color> &palette) {
|
||||
void Bitmap::SetPalette(const std::vector<SDL_Color>& palette) {
|
||||
SDL_UnlockSurface(surface_);
|
||||
for (size_t i = 0; i < palette.size(); ++i) {
|
||||
surface_->format->palette->colors[i].r = palette[i].r;
|
||||
@@ -475,66 +483,70 @@ void Bitmap::SetPalette(const std::vector<SDL_Color> &palette) {
|
||||
void Bitmap::WriteToPixel(int position, uint8_t value) {
|
||||
// Bounds checking to prevent crashes
|
||||
if (position < 0 || position >= static_cast<int>(data_.size())) {
|
||||
SDL_Log("ERROR: WriteToPixel - position %d out of bounds (size: %zu)",
|
||||
SDL_Log("ERROR: WriteToPixel - position %d out of bounds (size: %zu)",
|
||||
position, data_.size());
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Safety check: ensure bitmap is active and has valid data
|
||||
if (!active_ || data_.empty()) {
|
||||
SDL_Log("ERROR: WriteToPixel - bitmap not active or data empty (active=%s, size=%zu)",
|
||||
active_ ? "true" : "false", data_.size());
|
||||
SDL_Log(
|
||||
"ERROR: WriteToPixel - bitmap not active or data empty (active=%s, "
|
||||
"size=%zu)",
|
||||
active_ ? "true" : "false", data_.size());
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (pixel_data_ == nullptr) {
|
||||
pixel_data_ = data_.data();
|
||||
}
|
||||
|
||||
|
||||
// Safety check: ensure surface exists and is valid
|
||||
if (!surface_ || !surface_->pixels) {
|
||||
SDL_Log("ERROR: WriteToPixel - surface or pixels are null (surface=%p, pixels=%p)",
|
||||
surface_, surface_ ? surface_->pixels : nullptr);
|
||||
SDL_Log(
|
||||
"ERROR: WriteToPixel - surface or pixels are null (surface=%p, "
|
||||
"pixels=%p)",
|
||||
surface_, surface_ ? surface_->pixels : nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Additional validation: ensure pixel_data_ is valid
|
||||
if (pixel_data_ == nullptr) {
|
||||
SDL_Log("ERROR: WriteToPixel - pixel_data_ is null after assignment");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// CRITICAL FIX: Update both data_ and surface_ properly
|
||||
data_[position] = value;
|
||||
pixel_data_[position] = value;
|
||||
|
||||
|
||||
// Update surface if it exists
|
||||
if (surface_) {
|
||||
SDL_LockSurface(surface_);
|
||||
static_cast<uint8_t*>(surface_->pixels)[position] = value;
|
||||
SDL_UnlockSurface(surface_);
|
||||
}
|
||||
|
||||
|
||||
// Mark as modified for traditional update path
|
||||
modified_ = true;
|
||||
}
|
||||
|
||||
void Bitmap::WriteColor(int position, const ImVec4 &color) {
|
||||
void Bitmap::WriteColor(int position, const ImVec4& color) {
|
||||
// Bounds checking to prevent crashes
|
||||
if (position < 0 || position >= static_cast<int>(data_.size())) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Safety check: ensure bitmap is active and has valid data
|
||||
if (!active_ || data_.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Safety check: ensure surface exists and is valid
|
||||
if (!surface_ || !surface_->pixels || !surface_->format) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Convert ImVec4 (RGBA) to SDL_Color (RGBA)
|
||||
SDL_Color sdl_color;
|
||||
sdl_color.r = static_cast<Uint8>(color.x * 255);
|
||||
@@ -552,20 +564,20 @@ void Bitmap::WriteColor(int position, const ImVec4 &color) {
|
||||
}
|
||||
data_[position] = ConvertRgbToSnes(color);
|
||||
pixel_data_[position] = index;
|
||||
|
||||
|
||||
// Update surface if it exists
|
||||
if (surface_) {
|
||||
SDL_LockSurface(surface_);
|
||||
static_cast<uint8_t*>(surface_->pixels)[position] = index;
|
||||
SDL_UnlockSurface(surface_);
|
||||
}
|
||||
|
||||
|
||||
modified_ = true;
|
||||
}
|
||||
|
||||
void Bitmap::Get8x8Tile(int tile_index, int x, int y,
|
||||
std::vector<uint8_t> &tile_data,
|
||||
int &tile_data_offset) {
|
||||
std::vector<uint8_t>& tile_data,
|
||||
int& tile_data_offset) {
|
||||
int tile_offset = tile_index * (width_ * height_);
|
||||
int tile_x = (x * 8) % width_;
|
||||
int tile_y = (y * 8) % height_;
|
||||
@@ -580,8 +592,8 @@ void Bitmap::Get8x8Tile(int tile_index, int x, int y,
|
||||
}
|
||||
|
||||
void Bitmap::Get16x16Tile(int tile_x, int tile_y,
|
||||
std::vector<uint8_t> &tile_data,
|
||||
int &tile_data_offset) {
|
||||
std::vector<uint8_t>& tile_data,
|
||||
int& tile_data_offset) {
|
||||
for (int ty = 0; ty < 16; ty++) {
|
||||
for (int tx = 0; tx < 16; tx++) {
|
||||
// Calculate the pixel position in the bitmap
|
||||
@@ -597,7 +609,6 @@ void Bitmap::Get16x16Tile(int tile_x, int tile_y,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Set a pixel at the given coordinates with SNES color
|
||||
* @param x X coordinate (0 to width-1)
|
||||
@@ -616,26 +627,26 @@ void Bitmap::Get16x16Tile(int tile_x, int tile_y,
|
||||
*/
|
||||
void Bitmap::SetPixel(int x, int y, const SnesColor& color) {
|
||||
if (x < 0 || x >= width_ || y < 0 || y >= height_) {
|
||||
return; // Bounds check
|
||||
return; // Bounds check
|
||||
}
|
||||
|
||||
|
||||
int position = y * width_ + x;
|
||||
if (position >= 0 && position < static_cast<int>(data_.size())) {
|
||||
uint8_t color_index = FindColorIndex(color);
|
||||
data_[position] = color_index;
|
||||
|
||||
|
||||
// Update pixel_data_ to maintain consistency
|
||||
if (pixel_data_) {
|
||||
pixel_data_[position] = color_index;
|
||||
}
|
||||
|
||||
|
||||
// Update surface if it exists
|
||||
if (surface_) {
|
||||
SDL_LockSurface(surface_);
|
||||
static_cast<uint8_t*>(surface_->pixels)[position] = color_index;
|
||||
SDL_UnlockSurface(surface_);
|
||||
}
|
||||
|
||||
|
||||
// Update dirty region for efficient texture updates
|
||||
dirty_region_.AddPoint(x, y);
|
||||
modified_ = true;
|
||||
@@ -644,11 +655,11 @@ void Bitmap::SetPixel(int x, int y, const SnesColor& color) {
|
||||
|
||||
void Bitmap::Resize(int new_width, int new_height) {
|
||||
if (new_width <= 0 || new_height <= 0) {
|
||||
return; // Invalid dimensions
|
||||
return; // Invalid dimensions
|
||||
}
|
||||
|
||||
|
||||
std::vector<uint8_t> new_data(new_width * new_height, 0);
|
||||
|
||||
|
||||
// Copy existing data, handling size changes
|
||||
if (!data_.empty()) {
|
||||
for (int y = 0; y < std::min(height_, new_height); y++) {
|
||||
@@ -661,15 +672,15 @@ void Bitmap::Resize(int new_width, int new_height) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
width_ = new_width;
|
||||
height_ = new_height;
|
||||
data_ = std::move(new_data);
|
||||
pixel_data_ = data_.data();
|
||||
|
||||
|
||||
// Recreate surface with new dimensions
|
||||
surface_ = Arena::Get().AllocateSurface(width_, height_, depth_,
|
||||
GetSnesPixelFormat(BitmapFormat::kIndexed));
|
||||
surface_ = Arena::Get().AllocateSurface(
|
||||
width_, height_, depth_, GetSnesPixelFormat(BitmapFormat::kIndexed));
|
||||
if (surface_) {
|
||||
SDL_LockSurface(surface_);
|
||||
memcpy(surface_->pixels, pixel_data_, data_.size());
|
||||
@@ -678,7 +689,7 @@ void Bitmap::Resize(int new_width, int new_height) {
|
||||
} else {
|
||||
active_ = false;
|
||||
}
|
||||
|
||||
|
||||
modified_ = true;
|
||||
}
|
||||
|
||||
@@ -698,7 +709,7 @@ uint32_t Bitmap::HashColor(const ImVec4& color) {
|
||||
uint32_t g = static_cast<uint32_t>(color.y * 255.0F) & 0xFF;
|
||||
uint32_t b = static_cast<uint32_t>(color.z * 255.0F) & 0xFF;
|
||||
uint32_t a = static_cast<uint32_t>(color.w * 255.0F) & 0xFF;
|
||||
|
||||
|
||||
// Simple hash combining all components
|
||||
return (r << 24) | (g << 16) | (b << 8) | a;
|
||||
}
|
||||
@@ -714,7 +725,7 @@ uint32_t Bitmap::HashColor(const ImVec4& color) {
|
||||
*/
|
||||
void Bitmap::InvalidatePaletteCache() {
|
||||
color_to_index_cache_.clear();
|
||||
|
||||
|
||||
// Rebuild cache with current palette
|
||||
for (size_t i = 0; i < palette_.size(); i++) {
|
||||
uint32_t color_hash = HashColor(palette_[i].rgb());
|
||||
@@ -740,23 +751,23 @@ uint8_t Bitmap::FindColorIndex(const SnesColor& color) {
|
||||
return (it != color_to_index_cache_.end()) ? it->second : 0;
|
||||
}
|
||||
|
||||
void Bitmap::set_data(const std::vector<uint8_t> &data) {
|
||||
void Bitmap::set_data(const std::vector<uint8_t>& data) {
|
||||
// Validate input data
|
||||
if (data.empty()) {
|
||||
SDL_Log("Warning: set_data called with empty data vector");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
data_ = data;
|
||||
pixel_data_ = data_.data();
|
||||
|
||||
|
||||
// CRITICAL FIX: Use proper SDL surface operations instead of direct pointer assignment
|
||||
if (surface_ && !data_.empty()) {
|
||||
SDL_LockSurface(surface_);
|
||||
memcpy(surface_->pixels, pixel_data_, data_.size());
|
||||
SDL_UnlockSurface(surface_);
|
||||
}
|
||||
|
||||
|
||||
modified_ = true;
|
||||
}
|
||||
|
||||
@@ -765,24 +776,24 @@ bool Bitmap::ValidateDataSurfaceSync() {
|
||||
SDL_Log("ValidateDataSurfaceSync: surface or data is null/empty");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Check if data and surface are synchronized
|
||||
size_t surface_size = static_cast<size_t>(surface_->h * surface_->pitch);
|
||||
size_t data_size = data_.size();
|
||||
size_t compare_size = std::min(data_size, surface_size);
|
||||
|
||||
|
||||
if (compare_size == 0) {
|
||||
SDL_Log("ValidateDataSurfaceSync: invalid sizes - surface: %zu, data: %zu",
|
||||
SDL_Log("ValidateDataSurfaceSync: invalid sizes - surface: %zu, data: %zu",
|
||||
surface_size, data_size);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Compare first few bytes to check synchronization
|
||||
if (memcmp(surface_->pixels, data_.data(), compare_size) != 0) {
|
||||
SDL_Log("ValidateDataSurfaceSync: data and surface are not synchronized");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -37,7 +37,6 @@ enum BitmapFormat {
|
||||
k8bpp = 2,
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Represents a bitmap image optimized for SNES ROM hacking.
|
||||
*
|
||||
@@ -74,7 +73,7 @@ class Bitmap {
|
||||
* @param depth Color depth in bits per pixel (4, 8, or 16 for SNES)
|
||||
* @param data Raw pixel data (indexed color values for SNES graphics)
|
||||
*/
|
||||
Bitmap(int width, int height, int depth, const std::vector<uint8_t> &data);
|
||||
Bitmap(int width, int height, int depth, const std::vector<uint8_t>& data);
|
||||
|
||||
/**
|
||||
* @brief Create a bitmap with the given dimensions, data, and SNES palette
|
||||
@@ -84,8 +83,8 @@ class Bitmap {
|
||||
* @param data Raw pixel data (indexed color values)
|
||||
* @param palette SNES palette for color mapping (15-bit RGB format)
|
||||
*/
|
||||
Bitmap(int width, int height, int depth, const std::vector<uint8_t> &data,
|
||||
const SnesPalette &palette);
|
||||
Bitmap(int width, int height, int depth, const std::vector<uint8_t>& data,
|
||||
const SnesPalette& palette);
|
||||
|
||||
/**
|
||||
* @brief Copy constructor - creates a deep copy
|
||||
@@ -121,13 +120,13 @@ class Bitmap {
|
||||
* @brief Create a bitmap with the given dimensions and data
|
||||
*/
|
||||
void Create(int width, int height, int depth,
|
||||
const std::vector<uint8_t> &data);
|
||||
const std::vector<uint8_t>& data);
|
||||
|
||||
/**
|
||||
* @brief Create a bitmap with the given dimensions, format, and data
|
||||
*/
|
||||
void Create(int width, int height, int depth, int format,
|
||||
const std::vector<uint8_t> &data);
|
||||
const std::vector<uint8_t>& data);
|
||||
|
||||
/**
|
||||
* @brief Reformat the bitmap to use a different pixel format
|
||||
@@ -149,7 +148,7 @@ class Bitmap {
|
||||
* @param renderer SDL renderer for texture operations
|
||||
* @note Use this for better performance when multiple textures need updating
|
||||
*/
|
||||
void QueueTextureUpdate(IRenderer *renderer);
|
||||
void QueueTextureUpdate(IRenderer* renderer);
|
||||
|
||||
/**
|
||||
* @brief Updates the texture data from the surface
|
||||
@@ -159,25 +158,26 @@ class Bitmap {
|
||||
/**
|
||||
* @brief Set the palette for the bitmap
|
||||
*/
|
||||
void SetPalette(const SnesPalette &palette);
|
||||
void SetPalette(const SnesPalette& palette);
|
||||
|
||||
/**
|
||||
* @brief Set the palette with a transparent color
|
||||
*/
|
||||
void SetPaletteWithTransparent(const SnesPalette &palette, size_t index,
|
||||
void SetPaletteWithTransparent(const SnesPalette& palette, size_t index,
|
||||
int length = 7);
|
||||
|
||||
/**
|
||||
* @brief Apply palette using metadata-driven strategy
|
||||
* Chooses between SetPalette and SetPaletteWithTransparent based on metadata
|
||||
*/
|
||||
void ApplyPaletteByMetadata(const SnesPalette& palette, int sub_palette_index = 0);
|
||||
void ApplyPaletteByMetadata(const SnesPalette& palette,
|
||||
int sub_palette_index = 0);
|
||||
|
||||
/**
|
||||
* @brief Apply the stored palette to the surface (internal helper)
|
||||
*/
|
||||
void ApplyStoredPalette();
|
||||
|
||||
|
||||
/**
|
||||
* @brief Update SDL surface with current pixel data from data_ vector
|
||||
* Call this after modifying pixel data via mutable_data()
|
||||
@@ -187,7 +187,7 @@ class Bitmap {
|
||||
/**
|
||||
* @brief Set the palette using SDL colors
|
||||
*/
|
||||
void SetPalette(const std::vector<SDL_Color> &palette);
|
||||
void SetPalette(const std::vector<SDL_Color>& palette);
|
||||
|
||||
/**
|
||||
* @brief Write a value to a pixel at the given position
|
||||
@@ -197,7 +197,7 @@ class Bitmap {
|
||||
/**
|
||||
* @brief Write a color to a pixel at the given position
|
||||
*/
|
||||
void WriteColor(int position, const ImVec4 &color);
|
||||
void WriteColor(int position, const ImVec4& color);
|
||||
|
||||
/**
|
||||
* @brief Set a pixel at the given x,y coordinates with SNES color
|
||||
@@ -246,8 +246,8 @@ class Bitmap {
|
||||
* @param tile_data_offset Current offset in tile_data buffer
|
||||
* @note Used for ROM tile editing and tile extraction
|
||||
*/
|
||||
void Get8x8Tile(int tile_index, int x, int y, std::vector<uint8_t> &tile_data,
|
||||
int &tile_data_offset);
|
||||
void Get8x8Tile(int tile_index, int x, int y, std::vector<uint8_t>& tile_data,
|
||||
int& tile_data_offset);
|
||||
|
||||
/**
|
||||
* @brief Extract a 16x16 tile from the bitmap (SNES metatile size)
|
||||
@@ -257,46 +257,50 @@ class Bitmap {
|
||||
* @param tile_data_offset Current offset in tile_data buffer
|
||||
* @note Used for ROM metatile editing and large tile extraction
|
||||
*/
|
||||
void Get16x16Tile(int tile_x, int tile_y, std::vector<uint8_t> &tile_data,
|
||||
int &tile_data_offset);
|
||||
void Get16x16Tile(int tile_x, int tile_y, std::vector<uint8_t>& tile_data,
|
||||
int& tile_data_offset);
|
||||
|
||||
/**
|
||||
* @brief Metadata for tracking bitmap source format and palette requirements
|
||||
*/
|
||||
struct BitmapMetadata {
|
||||
int source_bpp = 8; // Original bits per pixel (3, 4, 8)
|
||||
int source_bpp = 8; // Original bits per pixel (3, 4, 8)
|
||||
int palette_format = 0; // 0=full palette, 1=sub-palette with transparent
|
||||
std::string source_type; // "graphics_sheet", "tilemap", "screen_buffer", "mode7"
|
||||
std::string
|
||||
source_type; // "graphics_sheet", "tilemap", "screen_buffer", "mode7"
|
||||
int palette_colors = 256; // Expected palette size
|
||||
|
||||
|
||||
BitmapMetadata() = default;
|
||||
BitmapMetadata(int bpp, int format, const std::string& type, int colors = 256)
|
||||
: source_bpp(bpp), palette_format(format), source_type(type), palette_colors(colors) {}
|
||||
BitmapMetadata(int bpp, int format, const std::string& type,
|
||||
int colors = 256)
|
||||
: source_bpp(bpp),
|
||||
palette_format(format),
|
||||
source_type(type),
|
||||
palette_colors(colors) {}
|
||||
};
|
||||
|
||||
const SnesPalette &palette() const { return palette_; }
|
||||
SnesPalette *mutable_palette() { return &palette_; }
|
||||
const SnesPalette& palette() const { return palette_; }
|
||||
SnesPalette* mutable_palette() { return &palette_; }
|
||||
BitmapMetadata& metadata() { return metadata_; }
|
||||
const BitmapMetadata& metadata() const { return metadata_; }
|
||||
|
||||
|
||||
int width() const { return width_; }
|
||||
int height() const { return height_; }
|
||||
int depth() const { return depth_; }
|
||||
auto size() const { return data_.size(); }
|
||||
const uint8_t *data() const { return data_.data(); }
|
||||
std::vector<uint8_t> &mutable_data() { return data_; }
|
||||
SDL_Surface *surface() const { return surface_; }
|
||||
const uint8_t* data() const { return data_.data(); }
|
||||
std::vector<uint8_t>& mutable_data() { return data_; }
|
||||
SDL_Surface* surface() const { return surface_; }
|
||||
TextureHandle texture() const { return texture_; }
|
||||
const std::vector<uint8_t> &vector() const { return data_; }
|
||||
const std::vector<uint8_t>& vector() const { return data_; }
|
||||
uint8_t at(int i) const { return data_[i]; }
|
||||
bool modified() const { return modified_; }
|
||||
bool is_active() const { return active_; }
|
||||
void set_active(bool active) { active_ = active; }
|
||||
void set_data(const std::vector<uint8_t> &data);
|
||||
void set_data(const std::vector<uint8_t>& data);
|
||||
void set_modified(bool modified) { modified_ = modified; }
|
||||
void set_texture(TextureHandle texture) { texture_ = texture; }
|
||||
|
||||
|
||||
private:
|
||||
int width_ = 0;
|
||||
int height_ = 0;
|
||||
@@ -306,10 +310,10 @@ class Bitmap {
|
||||
bool modified_ = false;
|
||||
|
||||
// Pointer to the texture pixels
|
||||
void *texture_pixels = nullptr;
|
||||
void* texture_pixels = nullptr;
|
||||
|
||||
// Pointer to the pixel data
|
||||
uint8_t *pixel_data_ = nullptr;
|
||||
uint8_t* pixel_data_ = nullptr;
|
||||
|
||||
// Palette for the bitmap
|
||||
gfx::SnesPalette palette_;
|
||||
@@ -321,7 +325,7 @@ class Bitmap {
|
||||
std::vector<uint8_t> data_;
|
||||
|
||||
// Surface for the bitmap (managed by Arena)
|
||||
SDL_Surface *surface_ = nullptr;
|
||||
SDL_Surface* surface_ = nullptr;
|
||||
|
||||
// Texture for the bitmap (managed by Arena)
|
||||
TextureHandle texture_ = nullptr;
|
||||
@@ -333,12 +337,12 @@ class Bitmap {
|
||||
struct DirtyRegion {
|
||||
int min_x = 0, min_y = 0, max_x = 0, max_y = 0;
|
||||
bool is_dirty = false;
|
||||
|
||||
|
||||
void Reset() {
|
||||
min_x = min_y = max_x = max_y = 0;
|
||||
is_dirty = false;
|
||||
}
|
||||
|
||||
|
||||
void AddPoint(int x, int y) {
|
||||
if (!is_dirty) {
|
||||
min_x = max_x = x;
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
#ifndef YAZE_APP_GFX_GRAPHICS_OPTIMIZER_H
|
||||
#define YAZE_APP_GFX_GRAPHICS_OPTIMIZER_H
|
||||
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "app/gfx/util/bpp_format_manager.h"
|
||||
#include "app/gfx/debug/performance/performance_profiler.h"
|
||||
#include "app/gfx/util/bpp_format_manager.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace gfx {
|
||||
@@ -15,10 +15,10 @@ namespace gfx {
|
||||
* @brief Graphics optimization strategy
|
||||
*/
|
||||
enum class OptimizationStrategy {
|
||||
kMemoryOptimized, ///< Minimize memory usage
|
||||
kPerformanceOptimized, ///< Maximize rendering performance
|
||||
kQualityOptimized, ///< Maintain highest quality
|
||||
kBalanced ///< Balance memory, performance, and quality
|
||||
kMemoryOptimized, ///< Minimize memory usage
|
||||
kPerformanceOptimized, ///< Maximize rendering performance
|
||||
kQualityOptimized, ///< Maintain highest quality
|
||||
kBalanced ///< Balance memory, performance, and quality
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -32,8 +32,12 @@ struct OptimizationResult {
|
||||
float quality_loss;
|
||||
std::vector<BppFormat> recommended_formats;
|
||||
std::unordered_map<int, BppFormat> sheet_recommendations;
|
||||
|
||||
OptimizationResult() : success(false), memory_saved(0), performance_gain(0.0f), quality_loss(0.0f) {}
|
||||
|
||||
OptimizationResult()
|
||||
: success(false),
|
||||
memory_saved(0),
|
||||
performance_gain(0.0f),
|
||||
quality_loss(0.0f) {}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -49,11 +53,16 @@ struct SheetOptimizationData {
|
||||
int colors_used;
|
||||
bool is_convertible;
|
||||
std::string optimization_reason;
|
||||
|
||||
SheetOptimizationData() : sheet_id(-1), current_format(BppFormat::kBpp8),
|
||||
recommended_format(BppFormat::kBpp8), current_size(0),
|
||||
optimized_size(0), compression_ratio(1.0f), colors_used(0),
|
||||
is_convertible(false) {}
|
||||
|
||||
SheetOptimizationData()
|
||||
: sheet_id(-1),
|
||||
current_format(BppFormat::kBpp8),
|
||||
recommended_format(BppFormat::kBpp8),
|
||||
current_size(0),
|
||||
optimized_size(0),
|
||||
compression_ratio(1.0f),
|
||||
colors_used(0),
|
||||
is_convertible(false) {}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -86,12 +95,12 @@ struct SheetOptimizationData {
|
||||
class GraphicsOptimizer {
|
||||
public:
|
||||
static GraphicsOptimizer& Get();
|
||||
|
||||
|
||||
/**
|
||||
* @brief Initialize the graphics optimizer
|
||||
*/
|
||||
void Initialize();
|
||||
|
||||
|
||||
/**
|
||||
* @brief Optimize a single graphics sheet
|
||||
* @param sheet_data Graphics sheet data
|
||||
@@ -100,11 +109,11 @@ class GraphicsOptimizer {
|
||||
* @param strategy Optimization strategy
|
||||
* @return Optimization result
|
||||
*/
|
||||
OptimizationResult OptimizeSheet(const std::vector<uint8_t>& sheet_data,
|
||||
int sheet_id,
|
||||
const SnesPalette& palette,
|
||||
OptimizationStrategy strategy = OptimizationStrategy::kBalanced);
|
||||
|
||||
OptimizationResult OptimizeSheet(
|
||||
const std::vector<uint8_t>& sheet_data, int sheet_id,
|
||||
const SnesPalette& palette,
|
||||
OptimizationStrategy strategy = OptimizationStrategy::kBalanced);
|
||||
|
||||
/**
|
||||
* @brief Optimize multiple graphics sheets
|
||||
* @param sheets Map of sheet ID to sheet data
|
||||
@@ -112,10 +121,11 @@ class GraphicsOptimizer {
|
||||
* @param strategy Optimization strategy
|
||||
* @return Optimization result
|
||||
*/
|
||||
OptimizationResult OptimizeSheets(const std::unordered_map<int, std::vector<uint8_t>>& sheets,
|
||||
const std::unordered_map<int, SnesPalette>& palettes,
|
||||
OptimizationStrategy strategy = OptimizationStrategy::kBalanced);
|
||||
|
||||
OptimizationResult OptimizeSheets(
|
||||
const std::unordered_map<int, std::vector<uint8_t>>& sheets,
|
||||
const std::unordered_map<int, SnesPalette>& palettes,
|
||||
OptimizationStrategy strategy = OptimizationStrategy::kBalanced);
|
||||
|
||||
/**
|
||||
* @brief Analyze graphics sheet for optimization opportunities
|
||||
* @param sheet_data Graphics sheet data
|
||||
@@ -124,9 +134,8 @@ class GraphicsOptimizer {
|
||||
* @return Optimization data
|
||||
*/
|
||||
SheetOptimizationData AnalyzeSheet(const std::vector<uint8_t>& sheet_data,
|
||||
int sheet_id,
|
||||
const SnesPalette& palette);
|
||||
|
||||
int sheet_id, const SnesPalette& palette);
|
||||
|
||||
/**
|
||||
* @brief Get optimization recommendations for all sheets
|
||||
* @param sheets Map of sheet ID to sheet data
|
||||
@@ -136,7 +145,7 @@ class GraphicsOptimizer {
|
||||
std::unordered_map<int, SheetOptimizationData> GetOptimizationRecommendations(
|
||||
const std::unordered_map<int, std::vector<uint8_t>>& sheets,
|
||||
const std::unordered_map<int, SnesPalette>& palettes);
|
||||
|
||||
|
||||
/**
|
||||
* @brief Apply optimization recommendations
|
||||
* @param recommendations Optimization recommendations
|
||||
@@ -148,18 +157,18 @@ class GraphicsOptimizer {
|
||||
const std::unordered_map<int, SheetOptimizationData>& recommendations,
|
||||
std::unordered_map<int, std::vector<uint8_t>>& sheets,
|
||||
std::unordered_map<int, SnesPalette>& palettes);
|
||||
|
||||
|
||||
/**
|
||||
* @brief Get optimization statistics
|
||||
* @return Map of optimization statistics
|
||||
*/
|
||||
std::unordered_map<std::string, double> GetOptimizationStats() const;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Clear optimization cache
|
||||
*/
|
||||
void ClearCache();
|
||||
|
||||
|
||||
/**
|
||||
* @brief Set optimization parameters
|
||||
* @param max_quality_loss Maximum acceptable quality loss (0.0-1.0)
|
||||
@@ -167,41 +176,44 @@ class GraphicsOptimizer {
|
||||
* @param performance_threshold Minimum performance gain threshold
|
||||
*/
|
||||
void SetOptimizationParameters(float max_quality_loss = 0.1f,
|
||||
size_t min_memory_savings = 1024,
|
||||
float performance_threshold = 0.05f);
|
||||
size_t min_memory_savings = 1024,
|
||||
float performance_threshold = 0.05f);
|
||||
|
||||
private:
|
||||
GraphicsOptimizer() = default;
|
||||
~GraphicsOptimizer() = default;
|
||||
|
||||
|
||||
// Optimization parameters
|
||||
float max_quality_loss_;
|
||||
size_t min_memory_savings_;
|
||||
float performance_threshold_;
|
||||
|
||||
|
||||
// Statistics tracking
|
||||
std::unordered_map<std::string, double> optimization_stats_;
|
||||
|
||||
|
||||
// Cache for optimization results
|
||||
std::unordered_map<std::string, SheetOptimizationData> optimization_cache_;
|
||||
|
||||
|
||||
// Helper methods
|
||||
BppFormat DetermineOptimalFormat(const std::vector<uint8_t>& data,
|
||||
const SnesPalette& palette,
|
||||
OptimizationStrategy strategy);
|
||||
const SnesPalette& palette,
|
||||
OptimizationStrategy strategy);
|
||||
float CalculateQualityLoss(BppFormat from_format, BppFormat to_format,
|
||||
const std::vector<uint8_t>& data);
|
||||
const std::vector<uint8_t>& data);
|
||||
size_t CalculateMemorySavings(BppFormat from_format, BppFormat to_format,
|
||||
const std::vector<uint8_t>& data);
|
||||
const std::vector<uint8_t>& data);
|
||||
float CalculatePerformanceGain(BppFormat from_format, BppFormat to_format);
|
||||
bool ShouldOptimize(const SheetOptimizationData& data, OptimizationStrategy strategy);
|
||||
bool ShouldOptimize(const SheetOptimizationData& data,
|
||||
OptimizationStrategy strategy);
|
||||
std::string GenerateOptimizationReason(const SheetOptimizationData& data);
|
||||
|
||||
|
||||
// Analysis helpers
|
||||
int CountUsedColors(const std::vector<uint8_t>& data, const SnesPalette& palette);
|
||||
float CalculateColorEfficiency(const std::vector<uint8_t>& data, const SnesPalette& palette);
|
||||
int CountUsedColors(const std::vector<uint8_t>& data,
|
||||
const SnesPalette& palette);
|
||||
float CalculateColorEfficiency(const std::vector<uint8_t>& data,
|
||||
const SnesPalette& palette);
|
||||
std::vector<int> AnalyzeColorDistribution(const std::vector<uint8_t>& data);
|
||||
|
||||
|
||||
// Cache management
|
||||
std::string GenerateCacheKey(const std::vector<uint8_t>& data, int sheet_id);
|
||||
void UpdateOptimizationStats(const std::string& operation, double value);
|
||||
@@ -214,10 +226,10 @@ class GraphicsOptimizationScope {
|
||||
public:
|
||||
GraphicsOptimizationScope(OptimizationStrategy strategy, int sheet_count);
|
||||
~GraphicsOptimizationScope();
|
||||
|
||||
|
||||
void AddSheet(int sheet_id, size_t original_size, size_t optimized_size);
|
||||
void SetResult(const OptimizationResult& result);
|
||||
|
||||
|
||||
private:
|
||||
OptimizationStrategy strategy_;
|
||||
int sheet_count_;
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
|
||||
#include "app/gfx/debug/performance/performance_profiler.h"
|
||||
#include "app/gfx/render/atlas_renderer.h"
|
||||
#include "app/gfx/resource/memory_pool.h"
|
||||
#include "app/gfx/debug/performance/performance_profiler.h"
|
||||
#include "imgui/imgui.h"
|
||||
|
||||
namespace yaze {
|
||||
@@ -250,7 +250,7 @@ void PerformanceDashboard::RenderMemoryUsage() {
|
||||
for (double value : memory_usage_history_) {
|
||||
float_history.push_back(static_cast<float>(value));
|
||||
}
|
||||
|
||||
|
||||
ImGui::PlotLines("Memory (MB)", float_history.data(),
|
||||
static_cast<int>(float_history.size()));
|
||||
}
|
||||
@@ -262,15 +262,18 @@ void PerformanceDashboard::RenderMemoryUsage() {
|
||||
float pool_usage =
|
||||
total_bytes > 0 ? static_cast<float>(used_bytes) / total_bytes : 0.0F;
|
||||
ImGui::ProgressBar(pool_usage, ImVec2(-1, 0), "Memory Pool Usage");
|
||||
|
||||
|
||||
// Atlas renderer stats
|
||||
auto atlas_stats = AtlasRenderer::Get().GetStats();
|
||||
ImGui::Text("Atlas Renderer: %d atlases, %d/%d entries used",
|
||||
atlas_stats.total_atlases, atlas_stats.used_entries, atlas_stats.total_entries);
|
||||
ImGui::Text("Atlas Memory: %s", FormatMemory(atlas_stats.total_memory).c_str());
|
||||
|
||||
ImGui::Text("Atlas Renderer: %d atlases, %d/%d entries used",
|
||||
atlas_stats.total_atlases, atlas_stats.used_entries,
|
||||
atlas_stats.total_entries);
|
||||
ImGui::Text("Atlas Memory: %s",
|
||||
FormatMemory(atlas_stats.total_memory).c_str());
|
||||
|
||||
if (atlas_stats.total_entries > 0) {
|
||||
float atlas_usage = static_cast<float>(atlas_stats.used_entries) / atlas_stats.total_entries;
|
||||
float atlas_usage = static_cast<float>(atlas_stats.used_entries) /
|
||||
atlas_stats.total_entries;
|
||||
ImGui::ProgressBar(atlas_usage, ImVec2(-1, 0), "Atlas Utilization");
|
||||
}
|
||||
}
|
||||
@@ -327,17 +330,17 @@ void PerformanceDashboard::RenderRecommendations() const {
|
||||
if (ImGui::Checkbox("Enable Performance Monitoring", &monitoring_enabled)) {
|
||||
PerformanceProfiler::SetEnabled(monitoring_enabled);
|
||||
}
|
||||
|
||||
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Clear All Data")) {
|
||||
PerformanceProfiler::Get().Clear();
|
||||
}
|
||||
|
||||
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Generate Report")) {
|
||||
std::string report = PerformanceProfiler::Get().GenerateReport(true);
|
||||
}
|
||||
|
||||
|
||||
// Export button
|
||||
if (ImGui::Button("Export Performance Report")) {
|
||||
std::string report = ExportReport();
|
||||
@@ -373,23 +376,23 @@ void PerformanceDashboard::CollectMetrics() {
|
||||
// Calculate cache hit ratio based on actual performance data
|
||||
double total_cache_operations = 0.0;
|
||||
double total_cache_time = 0.0;
|
||||
|
||||
|
||||
// Look for cache-related operations
|
||||
for (const auto& op_name : profiler.GetOperationNames()) {
|
||||
if (op_name.find("cache") != std::string::npos ||
|
||||
if (op_name.find("cache") != std::string::npos ||
|
||||
op_name.find("tile_cache") != std::string::npos) {
|
||||
auto stats = profiler.GetStats(op_name);
|
||||
total_cache_operations += stats.sample_count;
|
||||
total_cache_time += stats.total_time_ms;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Estimate cache hit ratio based on operation speed
|
||||
if (total_cache_operations > 0) {
|
||||
double avg_cache_time = total_cache_time / total_cache_operations;
|
||||
// Assume cache hits are < 10μs, misses are > 50μs
|
||||
current_metrics_.cache_hit_ratio = std::max(0.0, std::min(1.0,
|
||||
1.0 - (avg_cache_time - 10.0) / 40.0));
|
||||
current_metrics_.cache_hit_ratio =
|
||||
std::max(0.0, std::min(1.0, 1.0 - (avg_cache_time - 10.0) / 40.0));
|
||||
} else {
|
||||
current_metrics_.cache_hit_ratio = 0.85; // Default estimate
|
||||
}
|
||||
@@ -397,18 +400,18 @@ void PerformanceDashboard::CollectMetrics() {
|
||||
// Count draw calls and texture updates from profiler data
|
||||
int draw_calls = 0;
|
||||
int texture_updates = 0;
|
||||
|
||||
|
||||
for (const auto& op_name : profiler.GetOperationNames()) {
|
||||
if (op_name.find("draw") != std::string::npos ||
|
||||
if (op_name.find("draw") != std::string::npos ||
|
||||
op_name.find("render") != std::string::npos) {
|
||||
draw_calls += profiler.GetOperationCount(op_name);
|
||||
}
|
||||
if (op_name.find("texture_update") != std::string::npos ||
|
||||
if (op_name.find("texture_update") != std::string::npos ||
|
||||
op_name.find("texture") != std::string::npos) {
|
||||
texture_updates += profiler.GetOperationCount(op_name);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
current_metrics_.draw_calls_per_frame = draw_calls;
|
||||
current_metrics_.texture_updates_per_frame = texture_updates;
|
||||
|
||||
@@ -427,27 +430,28 @@ void PerformanceDashboard::CollectMetrics() {
|
||||
void PerformanceDashboard::UpdateOptimizationStatus() {
|
||||
auto profiler = PerformanceProfiler::Get();
|
||||
auto [used_bytes, total_bytes] = MemoryPool::Get().GetMemoryStats();
|
||||
|
||||
|
||||
// Check optimization status based on actual performance data
|
||||
optimization_status_.palette_lookup_optimized = false;
|
||||
optimization_status_.dirty_region_tracking_enabled = false;
|
||||
optimization_status_.resource_pooling_active = (total_bytes > 0);
|
||||
optimization_status_.batch_operations_enabled = false;
|
||||
optimization_status_.atlas_rendering_enabled = true; // AtlasRenderer is implemented
|
||||
optimization_status_.atlas_rendering_enabled =
|
||||
true; // AtlasRenderer is implemented
|
||||
optimization_status_.memory_pool_active = (total_bytes > 0);
|
||||
|
||||
|
||||
// Analyze palette lookup performance
|
||||
auto palette_stats = profiler.GetStats("palette_lookup_optimized");
|
||||
if (palette_stats.avg_time_us > 0 && palette_stats.avg_time_us < 5.0) {
|
||||
optimization_status_.palette_lookup_optimized = true;
|
||||
}
|
||||
|
||||
|
||||
// Analyze texture update performance
|
||||
auto texture_stats = profiler.GetStats("texture_update_optimized");
|
||||
if (texture_stats.avg_time_us > 0 && texture_stats.avg_time_us < 200.0) {
|
||||
optimization_status_.dirty_region_tracking_enabled = true;
|
||||
}
|
||||
|
||||
|
||||
// Check for batch operations
|
||||
auto batch_stats = profiler.GetStats("texture_batch_queue");
|
||||
if (batch_stats.sample_count > 0) {
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
#ifndef YAZE_APP_GFX_PERFORMANCE_PERFORMANCE_DASHBOARD_H
|
||||
#define YAZE_APP_GFX_PERFORMANCE_PERFORMANCE_DASHBOARD_H
|
||||
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <chrono>
|
||||
|
||||
#include "app/gfx/debug/performance/performance_profiler.h"
|
||||
#include "app/gfx/resource/memory_pool.h"
|
||||
#include "app/gfx/render/atlas_renderer.h"
|
||||
#include "app/gfx/resource/memory_pool.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace gfx {
|
||||
@@ -16,16 +16,19 @@ namespace gfx {
|
||||
/**
|
||||
* @brief Performance summary for external consumption
|
||||
*/
|
||||
struct PerformanceSummary {
|
||||
struct PerformanceSummary {
|
||||
double average_frame_time_ms;
|
||||
double memory_usage_mb;
|
||||
double cache_hit_ratio;
|
||||
int optimization_score; // 0-100
|
||||
std::string status_message;
|
||||
std::vector<std::string> recommendations;
|
||||
|
||||
PerformanceSummary() : average_frame_time_ms(0.0), memory_usage_mb(0.0),
|
||||
cache_hit_ratio(0.0), optimization_score(0) {}
|
||||
|
||||
PerformanceSummary()
|
||||
: average_frame_time_ms(0.0),
|
||||
memory_usage_mb(0.0),
|
||||
cache_hit_ratio(0.0),
|
||||
optimization_score(0) {}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -104,11 +107,16 @@ class PerformanceDashboard {
|
||||
double cache_hit_ratio;
|
||||
int draw_calls_per_frame;
|
||||
int texture_updates_per_frame;
|
||||
|
||||
PerformanceMetrics() : frame_time_ms(0.0), palette_lookup_time_us(0.0),
|
||||
texture_update_time_us(0.0), batch_operation_time_us(0.0),
|
||||
memory_usage_mb(0.0), cache_hit_ratio(0.0),
|
||||
draw_calls_per_frame(0), texture_updates_per_frame(0) {}
|
||||
|
||||
PerformanceMetrics()
|
||||
: frame_time_ms(0.0),
|
||||
palette_lookup_time_us(0.0),
|
||||
texture_update_time_us(0.0),
|
||||
batch_operation_time_us(0.0),
|
||||
memory_usage_mb(0.0),
|
||||
cache_hit_ratio(0.0),
|
||||
draw_calls_per_frame(0),
|
||||
texture_updates_per_frame(0) {}
|
||||
};
|
||||
|
||||
struct OptimizationStatus {
|
||||
@@ -118,23 +126,27 @@ class PerformanceDashboard {
|
||||
bool batch_operations_enabled;
|
||||
bool atlas_rendering_enabled;
|
||||
bool memory_pool_active;
|
||||
|
||||
OptimizationStatus() : palette_lookup_optimized(false), dirty_region_tracking_enabled(false),
|
||||
resource_pooling_active(false), batch_operations_enabled(false),
|
||||
atlas_rendering_enabled(false), memory_pool_active(false) {}
|
||||
|
||||
OptimizationStatus()
|
||||
: palette_lookup_optimized(false),
|
||||
dirty_region_tracking_enabled(false),
|
||||
resource_pooling_active(false),
|
||||
batch_operations_enabled(false),
|
||||
atlas_rendering_enabled(false),
|
||||
memory_pool_active(false) {}
|
||||
};
|
||||
|
||||
bool visible_;
|
||||
PerformanceMetrics current_metrics_;
|
||||
PerformanceMetrics previous_metrics_;
|
||||
OptimizationStatus optimization_status_;
|
||||
|
||||
|
||||
std::chrono::high_resolution_clock::time_point last_update_time_;
|
||||
std::vector<double> frame_time_history_;
|
||||
std::vector<double> memory_usage_history_;
|
||||
|
||||
|
||||
static constexpr size_t kHistorySize = 100;
|
||||
static constexpr double kUpdateIntervalMs = 100.0; // Update every 100ms
|
||||
static constexpr double kUpdateIntervalMs = 100.0; // Update every 100ms
|
||||
|
||||
// UI rendering methods
|
||||
void RenderMetricsPanel() const;
|
||||
@@ -142,15 +154,16 @@ class PerformanceDashboard {
|
||||
void RenderMemoryUsage();
|
||||
void RenderFrameRateGraph();
|
||||
void RenderRecommendations() const;
|
||||
|
||||
|
||||
// Data collection methods
|
||||
void CollectMetrics();
|
||||
void UpdateOptimizationStatus();
|
||||
void AnalyzePerformance();
|
||||
|
||||
|
||||
// Helper methods
|
||||
static double CalculateAverage(const std::vector<double>& values);
|
||||
static double CalculatePercentile(const std::vector<double>& values, double percentile);
|
||||
static double CalculatePercentile(const std::vector<double>& values,
|
||||
double percentile);
|
||||
static std::string FormatTime(double time_us);
|
||||
static std::string FormatMemory(size_t bytes);
|
||||
std::string GetOptimizationRecommendation() const;
|
||||
|
||||
@@ -17,78 +17,82 @@ PerformanceProfiler& PerformanceProfiler::Get() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
PerformanceProfiler::PerformanceProfiler() : enabled_(true), is_shutting_down_(false) {
|
||||
PerformanceProfiler::PerformanceProfiler()
|
||||
: enabled_(true), is_shutting_down_(false) {
|
||||
// Initialize with memory pool for efficient data storage
|
||||
// Reserve space for common operations to avoid reallocations
|
||||
active_timers_.reserve(50);
|
||||
operation_times_.reserve(100);
|
||||
operation_totals_.reserve(100);
|
||||
operation_counts_.reserve(100);
|
||||
|
||||
|
||||
// Register destructor to set shutdown flag
|
||||
std::atexit([]() {
|
||||
Get().is_shutting_down_ = true;
|
||||
});
|
||||
std::atexit([]() { Get().is_shutting_down_ = true; });
|
||||
}
|
||||
|
||||
void PerformanceProfiler::StartTimer(const std::string& operation_name) {
|
||||
if (!enabled_ || is_shutting_down_) return;
|
||||
|
||||
if (!enabled_ || is_shutting_down_)
|
||||
return;
|
||||
|
||||
active_timers_[operation_name] = std::chrono::high_resolution_clock::now();
|
||||
}
|
||||
|
||||
void PerformanceProfiler::EndTimer(const std::string& operation_name) {
|
||||
if (!enabled_ || is_shutting_down_) return;
|
||||
|
||||
if (!enabled_ || is_shutting_down_)
|
||||
return;
|
||||
|
||||
auto timer_iter = active_timers_.find(operation_name);
|
||||
if (timer_iter == active_timers_.end()) {
|
||||
// During shutdown, silently ignore missing timers to avoid log spam
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
auto end_time = std::chrono::high_resolution_clock::now();
|
||||
auto duration = std::chrono::duration_cast<std::chrono::microseconds>(
|
||||
end_time - timer_iter->second).count();
|
||||
|
||||
end_time - timer_iter->second)
|
||||
.count();
|
||||
|
||||
double duration_ms = duration / 1000.0;
|
||||
|
||||
|
||||
// Store timing data using memory pool for efficiency
|
||||
operation_times_[operation_name].push_back(static_cast<double>(duration));
|
||||
operation_totals_[operation_name] += duration_ms;
|
||||
operation_counts_[operation_name]++;
|
||||
|
||||
|
||||
active_timers_.erase(timer_iter);
|
||||
}
|
||||
|
||||
PerformanceProfiler::TimingStats PerformanceProfiler::GetStats(
|
||||
const std::string& operation_name) const {
|
||||
TimingStats stats;
|
||||
|
||||
|
||||
auto times_iter = operation_times_.find(operation_name);
|
||||
auto total_iter = operation_totals_.find(operation_name);
|
||||
|
||||
|
||||
if (times_iter == operation_times_.end() || times_iter->second.empty()) {
|
||||
return stats;
|
||||
}
|
||||
|
||||
|
||||
const auto& times = times_iter->second;
|
||||
stats.sample_count = times.size();
|
||||
stats.total_time_ms = (total_iter != operation_totals_.end()) ? total_iter->second : 0.0;
|
||||
|
||||
stats.total_time_ms =
|
||||
(total_iter != operation_totals_.end()) ? total_iter->second : 0.0;
|
||||
|
||||
if (times.empty()) {
|
||||
return stats;
|
||||
}
|
||||
|
||||
|
||||
// Calculate min, max, and average
|
||||
stats.min_time_us = *std::min_element(times.begin(), times.end());
|
||||
stats.max_time_us = *std::max_element(times.begin(), times.end());
|
||||
stats.avg_time_us = std::accumulate(times.begin(), times.end(), 0.0) / times.size();
|
||||
|
||||
stats.avg_time_us =
|
||||
std::accumulate(times.begin(), times.end(), 0.0) / times.size();
|
||||
|
||||
// Calculate median
|
||||
std::vector<double> sorted_times = times;
|
||||
std::sort(sorted_times.begin(), sorted_times.end());
|
||||
stats.median_time_us = PerformanceProfiler::CalculateMedian(sorted_times);
|
||||
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
@@ -96,26 +100,33 @@ std::string PerformanceProfiler::GenerateReport(bool log_to_sdl) const {
|
||||
std::ostringstream report;
|
||||
report << "\n=== YAZE Unified Performance Report ===\n";
|
||||
report << "Total Operations Tracked: " << operation_times_.size() << "\n";
|
||||
report << "Performance Monitoring: " << (enabled_ ? "ENABLED" : "DISABLED") << "\n\n";
|
||||
|
||||
report << "Performance Monitoring: " << (enabled_ ? "ENABLED" : "DISABLED")
|
||||
<< "\n\n";
|
||||
|
||||
// Memory pool statistics
|
||||
auto [used_bytes, total_bytes] = MemoryPool::Get().GetMemoryStats();
|
||||
report << "Memory Pool Usage: " << std::fixed << std::setprecision(2)
|
||||
report << "Memory Pool Usage: " << std::fixed << std::setprecision(2)
|
||||
<< (used_bytes / (1024.0 * 1024.0)) << " MB / "
|
||||
<< (total_bytes / (1024.0 * 1024.0)) << " MB\n\n";
|
||||
|
||||
|
||||
for (const auto& [operation, times] : operation_times_) {
|
||||
if (times.empty()) continue;
|
||||
|
||||
if (times.empty())
|
||||
continue;
|
||||
|
||||
auto stats = GetStats(operation);
|
||||
report << "Operation: " << operation << "\n";
|
||||
report << " Samples: " << stats.sample_count << "\n";
|
||||
report << " Min: " << std::fixed << std::setprecision(2) << stats.min_time_us << " μs\n";
|
||||
report << " Max: " << std::fixed << std::setprecision(2) << stats.max_time_us << " μs\n";
|
||||
report << " Average: " << std::fixed << std::setprecision(2) << stats.avg_time_us << " μs\n";
|
||||
report << " Median: " << std::fixed << std::setprecision(2) << stats.median_time_us << " μs\n";
|
||||
report << " Total: " << std::fixed << std::setprecision(2) << stats.total_time_ms << " ms\n";
|
||||
|
||||
report << " Min: " << std::fixed << std::setprecision(2)
|
||||
<< stats.min_time_us << " μs\n";
|
||||
report << " Max: " << std::fixed << std::setprecision(2)
|
||||
<< stats.max_time_us << " μs\n";
|
||||
report << " Average: " << std::fixed << std::setprecision(2)
|
||||
<< stats.avg_time_us << " μs\n";
|
||||
report << " Median: " << std::fixed << std::setprecision(2)
|
||||
<< stats.median_time_us << " μs\n";
|
||||
report << " Total: " << std::fixed << std::setprecision(2)
|
||||
<< stats.total_time_ms << " ms\n";
|
||||
|
||||
// Performance analysis
|
||||
if (operation.find("palette_lookup") != std::string::npos) {
|
||||
if (stats.avg_time_us < 1.0) {
|
||||
@@ -145,34 +156,34 @@ std::string PerformanceProfiler::GenerateReport(bool log_to_sdl) const {
|
||||
report << " Status: ⚠ SLOW LOADING (> 1000ms)\n";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
report << "\n";
|
||||
}
|
||||
|
||||
|
||||
// Overall performance summary
|
||||
report << "=== Performance Summary ===\n";
|
||||
size_t total_samples = 0;
|
||||
double total_time = 0.0;
|
||||
|
||||
|
||||
for (const auto& [operation, times] : operation_times_) {
|
||||
total_samples += times.size();
|
||||
total_time += std::accumulate(times.begin(), times.end(), 0.0);
|
||||
}
|
||||
|
||||
|
||||
if (total_samples > 0) {
|
||||
report << "Total Samples: " << total_samples << "\n";
|
||||
report << "Total Time: " << std::fixed << std::setprecision(2)
|
||||
report << "Total Time: " << std::fixed << std::setprecision(2)
|
||||
<< total_time / 1000.0 << " ms\n";
|
||||
report << "Average Time per Operation: " << std::fixed << std::setprecision(2)
|
||||
<< total_time / total_samples << " μs\n";
|
||||
report << "Average Time per Operation: " << std::fixed
|
||||
<< std::setprecision(2) << total_time / total_samples << " μs\n";
|
||||
}
|
||||
|
||||
|
||||
std::string report_str = report.str();
|
||||
|
||||
|
||||
if (log_to_sdl) {
|
||||
SDL_Log("%s", report_str.c_str());
|
||||
}
|
||||
|
||||
|
||||
return report_str;
|
||||
}
|
||||
|
||||
@@ -203,52 +214,55 @@ bool PerformanceProfiler::IsTiming(const std::string& operation_name) const {
|
||||
return active_timers_.find(operation_name) != active_timers_.end();
|
||||
}
|
||||
|
||||
double PerformanceProfiler::GetAverageTime(const std::string& operation_name) const {
|
||||
double PerformanceProfiler::GetAverageTime(
|
||||
const std::string& operation_name) const {
|
||||
auto total_it = operation_totals_.find(operation_name);
|
||||
auto count_it = operation_counts_.find(operation_name);
|
||||
|
||||
if (total_it == operation_totals_.end() || count_it == operation_counts_.end() ||
|
||||
count_it->second == 0) {
|
||||
|
||||
if (total_it == operation_totals_.end() ||
|
||||
count_it == operation_counts_.end() || count_it->second == 0) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
|
||||
return total_it->second / count_it->second;
|
||||
}
|
||||
|
||||
double PerformanceProfiler::GetTotalTime(const std::string& operation_name) const {
|
||||
double PerformanceProfiler::GetTotalTime(
|
||||
const std::string& operation_name) const {
|
||||
auto total_it = operation_totals_.find(operation_name);
|
||||
return (total_it != operation_totals_.end()) ? total_it->second : 0.0;
|
||||
}
|
||||
|
||||
int PerformanceProfiler::GetOperationCount(const std::string& operation_name) const {
|
||||
int PerformanceProfiler::GetOperationCount(
|
||||
const std::string& operation_name) const {
|
||||
auto count_it = operation_counts_.find(operation_name);
|
||||
return (count_it != operation_counts_.end()) ? count_it->second : 0;
|
||||
}
|
||||
|
||||
void PerformanceProfiler::PrintSummary() const {
|
||||
std::cout << "\n=== Performance Summary ===\n";
|
||||
std::cout << std::left << std::setw(30) << "Operation"
|
||||
<< std::setw(12) << "Count"
|
||||
<< std::setw(15) << "Total (ms)"
|
||||
<< std::setw(15) << "Average (ms)" << "\n";
|
||||
std::cout << std::left << std::setw(30) << "Operation" << std::setw(12)
|
||||
<< "Count" << std::setw(15) << "Total (ms)" << std::setw(15)
|
||||
<< "Average (ms)" << "\n";
|
||||
std::cout << std::string(72, '-') << "\n";
|
||||
|
||||
for (const auto& [operation_name, times] : operation_times_) {
|
||||
if (times.empty()) continue;
|
||||
|
||||
if (times.empty())
|
||||
continue;
|
||||
|
||||
auto total_it = operation_totals_.find(operation_name);
|
||||
auto count_it = operation_counts_.find(operation_name);
|
||||
|
||||
if (total_it != operation_totals_.end() && count_it != operation_counts_.end()) {
|
||||
|
||||
if (total_it != operation_totals_.end() &&
|
||||
count_it != operation_counts_.end()) {
|
||||
double total_time = total_it->second;
|
||||
int count = count_it->second;
|
||||
double avg_time = (count > 0) ? total_time / count : 0.0;
|
||||
|
||||
std::cout << std::left << std::setw(30) << operation_name
|
||||
<< std::setw(12) << count
|
||||
<< std::setw(15) << std::fixed << std::setprecision(2) << total_time
|
||||
<< std::setw(15) << std::fixed << std::setprecision(2) << avg_time
|
||||
<< "\n";
|
||||
|
||||
std::cout << std::left << std::setw(30) << operation_name << std::setw(12)
|
||||
<< count << std::setw(15) << std::fixed << std::setprecision(2)
|
||||
<< total_time << std::setw(15) << std::fixed
|
||||
<< std::setprecision(2) << avg_time << "\n";
|
||||
}
|
||||
}
|
||||
std::cout << std::string(72, '-') << "\n";
|
||||
@@ -258,7 +272,7 @@ double PerformanceProfiler::CalculateMedian(std::vector<double> values) {
|
||||
if (values.empty()) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
|
||||
size_t size = values.size();
|
||||
if (size % 2 == 0) {
|
||||
return (values[size / 2 - 1] + values[size / 2]) / 2.0;
|
||||
@@ -267,7 +281,7 @@ double PerformanceProfiler::CalculateMedian(std::vector<double> values) {
|
||||
}
|
||||
|
||||
// ScopedTimer implementation
|
||||
ScopedTimer::ScopedTimer(const std::string& operation_name)
|
||||
ScopedTimer::ScopedTimer(const std::string& operation_name)
|
||||
: operation_name_(operation_name) {
|
||||
if (PerformanceProfiler::IsEnabled() && PerformanceProfiler::IsValid()) {
|
||||
PerformanceProfiler::Get().StartTimer(operation_name_);
|
||||
|
||||
@@ -44,46 +44,40 @@ namespace gfx {
|
||||
class PerformanceProfiler {
|
||||
public:
|
||||
static PerformanceProfiler& Get();
|
||||
|
||||
|
||||
/**
|
||||
* @brief Enable or disable performance monitoring
|
||||
*
|
||||
* When disabled, ScopedTimer operations become no-ops for better performance
|
||||
* in production builds or when monitoring is not needed.
|
||||
*/
|
||||
static void SetEnabled(bool enabled) {
|
||||
Get().enabled_ = enabled;
|
||||
}
|
||||
|
||||
static void SetEnabled(bool enabled) { Get().enabled_ = enabled; }
|
||||
|
||||
/**
|
||||
* @brief Check if performance monitoring is enabled
|
||||
*/
|
||||
static bool IsEnabled() {
|
||||
return Get().enabled_;
|
||||
}
|
||||
|
||||
static bool IsEnabled() { return Get().enabled_; }
|
||||
|
||||
/**
|
||||
* @brief Check if the profiler is in a valid state (not shutting down)
|
||||
* This prevents crashes during static destruction order issues
|
||||
*/
|
||||
static bool IsValid() {
|
||||
return !Get().is_shutting_down_;
|
||||
}
|
||||
|
||||
static bool IsValid() { return !Get().is_shutting_down_; }
|
||||
|
||||
/**
|
||||
* @brief Start timing an operation
|
||||
* @param operation_name Name of the operation to time
|
||||
* @note Multiple operations can be timed simultaneously
|
||||
*/
|
||||
void StartTimer(const std::string& operation_name);
|
||||
|
||||
|
||||
/**
|
||||
* @brief End timing an operation
|
||||
* @param operation_name Name of the operation to end timing
|
||||
* @note Must match a previously started timer
|
||||
*/
|
||||
void EndTimer(const std::string& operation_name);
|
||||
|
||||
|
||||
/**
|
||||
* @brief Get timing statistics for an operation
|
||||
* @param operation_name Name of the operation
|
||||
@@ -97,61 +91,61 @@ class PerformanceProfiler {
|
||||
double total_time_ms = 0.0;
|
||||
size_t sample_count = 0;
|
||||
};
|
||||
|
||||
|
||||
TimingStats GetStats(const std::string& operation_name) const;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Generate a comprehensive performance report
|
||||
* @param log_to_sdl Whether to log results to SDL_Log
|
||||
* @return Formatted performance report string
|
||||
*/
|
||||
std::string GenerateReport(bool log_to_sdl = true) const;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Clear all timing data
|
||||
*/
|
||||
void Clear();
|
||||
|
||||
|
||||
/**
|
||||
* @brief Clear timing data for a specific operation
|
||||
* @param operation_name Name of the operation to clear
|
||||
*/
|
||||
void ClearOperation(const std::string& operation_name);
|
||||
|
||||
|
||||
/**
|
||||
* @brief Get list of all tracked operations
|
||||
* @return Vector of operation names
|
||||
*/
|
||||
std::vector<std::string> GetOperationNames() const;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Check if an operation is currently being timed
|
||||
* @param operation_name Name of the operation to check
|
||||
* @return True if operation is being timed
|
||||
*/
|
||||
bool IsTiming(const std::string& operation_name) const;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Get the average time for an operation in milliseconds
|
||||
* @param operation_name Name of the operation
|
||||
* @return Average time in milliseconds
|
||||
*/
|
||||
double GetAverageTime(const std::string& operation_name) const;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Get the total time for an operation in milliseconds
|
||||
* @param operation_name Name of the operation
|
||||
* @return Total time in milliseconds
|
||||
*/
|
||||
double GetTotalTime(const std::string& operation_name) const;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Get the number of times an operation was measured
|
||||
* @param operation_name Name of the operation
|
||||
* @return Number of measurements
|
||||
*/
|
||||
int GetOperationCount(const std::string& operation_name) const;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Print a summary of all operations to console
|
||||
*/
|
||||
@@ -159,18 +153,21 @@ class PerformanceProfiler {
|
||||
|
||||
private:
|
||||
PerformanceProfiler();
|
||||
|
||||
|
||||
using TimePoint = std::chrono::high_resolution_clock::time_point;
|
||||
using Duration = std::chrono::microseconds;
|
||||
|
||||
|
||||
std::unordered_map<std::string, TimePoint> active_timers_;
|
||||
std::unordered_map<std::string, std::vector<double>> operation_times_;
|
||||
std::unordered_map<std::string, double> operation_totals_; // Total time per operation
|
||||
std::unordered_map<std::string, int> operation_counts_; // Count per operation
|
||||
|
||||
bool enabled_ = true; // Performance monitoring enabled by default
|
||||
bool is_shutting_down_ = false; // Flag to prevent operations during destruction
|
||||
|
||||
std::unordered_map<std::string, double>
|
||||
operation_totals_; // Total time per operation
|
||||
std::unordered_map<std::string, int>
|
||||
operation_counts_; // Count per operation
|
||||
|
||||
bool enabled_ = true; // Performance monitoring enabled by default
|
||||
bool is_shutting_down_ =
|
||||
false; // Flag to prevent operations during destruction
|
||||
|
||||
/**
|
||||
* @brief Calculate median value from a sorted vector
|
||||
* @param values Sorted vector of values
|
||||
@@ -192,7 +189,7 @@ class ScopedTimer {
|
||||
public:
|
||||
explicit ScopedTimer(const std::string& operation_name);
|
||||
~ScopedTimer();
|
||||
|
||||
|
||||
// Disable copy and move
|
||||
ScopedTimer(const ScopedTimer&) = delete;
|
||||
ScopedTimer& operator=(const ScopedTimer&) = delete;
|
||||
|
||||
@@ -16,79 +16,85 @@ void AtlasRenderer::Initialize(IRenderer* renderer, int initial_size) {
|
||||
renderer_ = renderer;
|
||||
next_atlas_id_ = 0;
|
||||
current_atlas_ = 0;
|
||||
|
||||
|
||||
// Clear any existing atlases
|
||||
Clear();
|
||||
|
||||
|
||||
// Create initial atlas
|
||||
CreateNewAtlas();
|
||||
}
|
||||
|
||||
int AtlasRenderer::AddBitmap(const Bitmap& bitmap) {
|
||||
if (!bitmap.is_active() || !bitmap.texture()) {
|
||||
return -1; // Invalid bitmap
|
||||
return -1; // Invalid bitmap
|
||||
}
|
||||
|
||||
|
||||
ScopedTimer timer("atlas_add_bitmap");
|
||||
|
||||
|
||||
// Try to pack into current atlas
|
||||
SDL_Rect uv_rect;
|
||||
if (PackBitmap(*atlases_[current_atlas_], bitmap, uv_rect)) {
|
||||
int atlas_id = next_atlas_id_++;
|
||||
auto& atlas = *atlases_[current_atlas_];
|
||||
|
||||
|
||||
// Copy bitmap data to atlas texture
|
||||
renderer_->SetRenderTarget(atlas.texture);
|
||||
renderer_->RenderCopy(bitmap.texture(), nullptr, &uv_rect);
|
||||
renderer_->SetRenderTarget(nullptr);
|
||||
|
||||
|
||||
return atlas_id;
|
||||
}
|
||||
|
||||
|
||||
// Current atlas is full, create new one
|
||||
CreateNewAtlas();
|
||||
if (PackBitmap(*atlases_[current_atlas_], bitmap, uv_rect)) {
|
||||
int atlas_id = next_atlas_id_++;
|
||||
auto& atlas = *atlases_[current_atlas_];
|
||||
|
||||
BppFormat bpp_format = BppFormatManager::Get().DetectFormat(bitmap.vector(), bitmap.width(), bitmap.height());
|
||||
atlas.entries.emplace_back(atlas_id, uv_rect, bitmap.texture(), bpp_format, bitmap.width(), bitmap.height());
|
||||
|
||||
BppFormat bpp_format = BppFormatManager::Get().DetectFormat(
|
||||
bitmap.vector(), bitmap.width(), bitmap.height());
|
||||
atlas.entries.emplace_back(atlas_id, uv_rect, bitmap.texture(), bpp_format,
|
||||
bitmap.width(), bitmap.height());
|
||||
atlas_lookup_[atlas_id] = &atlas.entries.back();
|
||||
|
||||
|
||||
// Copy bitmap data to atlas texture
|
||||
renderer_->SetRenderTarget(atlas.texture);
|
||||
renderer_->RenderCopy(bitmap.texture(), nullptr, &uv_rect);
|
||||
renderer_->SetRenderTarget(nullptr);
|
||||
|
||||
|
||||
return atlas_id;
|
||||
}
|
||||
|
||||
return -1; // Failed to add
|
||||
|
||||
return -1; // Failed to add
|
||||
}
|
||||
|
||||
int AtlasRenderer::AddBitmapWithBppOptimization(const Bitmap& bitmap, BppFormat target_bpp) {
|
||||
int AtlasRenderer::AddBitmapWithBppOptimization(const Bitmap& bitmap,
|
||||
BppFormat target_bpp) {
|
||||
if (!bitmap.is_active() || !bitmap.texture()) {
|
||||
return -1; // Invalid bitmap
|
||||
return -1; // Invalid bitmap
|
||||
}
|
||||
|
||||
|
||||
ScopedTimer timer("atlas_add_bitmap_bpp_optimized");
|
||||
|
||||
|
||||
// Detect current BPP format
|
||||
BppFormat current_bpp = BppFormatManager::Get().DetectFormat(bitmap.vector(), bitmap.width(), bitmap.height());
|
||||
|
||||
BppFormat current_bpp = BppFormatManager::Get().DetectFormat(
|
||||
bitmap.vector(), bitmap.width(), bitmap.height());
|
||||
|
||||
// If formats match, use standard addition
|
||||
if (current_bpp == target_bpp) {
|
||||
return AddBitmap(bitmap);
|
||||
}
|
||||
|
||||
|
||||
// Convert bitmap to target BPP format
|
||||
auto converted_data = BppFormatManager::Get().ConvertFormat(
|
||||
bitmap.vector(), current_bpp, target_bpp, bitmap.width(), bitmap.height());
|
||||
|
||||
bitmap.vector(), current_bpp, target_bpp, bitmap.width(),
|
||||
bitmap.height());
|
||||
|
||||
// Create temporary bitmap with converted data
|
||||
Bitmap converted_bitmap(bitmap.width(), bitmap.height(), bitmap.depth(), converted_data, bitmap.palette());
|
||||
Bitmap converted_bitmap(bitmap.width(), bitmap.height(), bitmap.depth(),
|
||||
converted_data, bitmap.palette());
|
||||
converted_bitmap.CreateTexture();
|
||||
|
||||
|
||||
// Add converted bitmap to atlas
|
||||
return AddBitmap(converted_bitmap);
|
||||
}
|
||||
@@ -98,10 +104,10 @@ void AtlasRenderer::RemoveBitmap(int atlas_id) {
|
||||
if (it == atlas_lookup_.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
AtlasEntry* entry = it->second;
|
||||
entry->in_use = false;
|
||||
|
||||
|
||||
// Mark region as free
|
||||
for (auto& atlas : atlases_) {
|
||||
for (auto& atlas_entry : atlas->entries) {
|
||||
@@ -111,7 +117,7 @@ void AtlasRenderer::RemoveBitmap(int atlas_id) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
atlas_lookup_.erase(it);
|
||||
}
|
||||
|
||||
@@ -120,28 +126,30 @@ void AtlasRenderer::UpdateBitmap(int atlas_id, const Bitmap& bitmap) {
|
||||
if (it == atlas_lookup_.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
AtlasEntry* entry = it->second;
|
||||
entry->texture = bitmap.texture();
|
||||
|
||||
|
||||
// Update UV coordinates if size changed
|
||||
if (bitmap.width() != entry->uv_rect.w || bitmap.height() != entry->uv_rect.h) {
|
||||
if (bitmap.width() != entry->uv_rect.w ||
|
||||
bitmap.height() != entry->uv_rect.h) {
|
||||
// Remove old entry and add new one
|
||||
RemoveBitmap(atlas_id);
|
||||
AddBitmap(bitmap);
|
||||
}
|
||||
}
|
||||
|
||||
void AtlasRenderer::RenderBatch(const std::vector<RenderCommand>& render_commands) {
|
||||
void AtlasRenderer::RenderBatch(
|
||||
const std::vector<RenderCommand>& render_commands) {
|
||||
if (render_commands.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
ScopedTimer timer("atlas_batch_render");
|
||||
|
||||
|
||||
// Group commands by atlas for efficient rendering
|
||||
std::unordered_map<int, std::vector<const RenderCommand*>> atlas_groups;
|
||||
|
||||
|
||||
for (const auto& cmd : render_commands) {
|
||||
auto it = atlas_lookup_.find(cmd.atlas_id);
|
||||
if (it != atlas_lookup_.end() && it->second->in_use) {
|
||||
@@ -156,31 +164,30 @@ void AtlasRenderer::RenderBatch(const std::vector<RenderCommand>& render_command
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Render each atlas group
|
||||
for (const auto& [atlas_index, commands] : atlas_groups) {
|
||||
if (commands.empty()) continue;
|
||||
|
||||
if (commands.empty())
|
||||
continue;
|
||||
|
||||
auto& atlas = *atlases_[atlas_index];
|
||||
|
||||
|
||||
// Set atlas texture
|
||||
// SDL_SetTextureBlendMode(atlas.texture, SDL_BLENDMODE_BLEND);
|
||||
|
||||
|
||||
// Render all commands for this atlas
|
||||
for (const auto* cmd : commands) {
|
||||
auto it = atlas_lookup_.find(cmd->atlas_id);
|
||||
if (it == atlas_lookup_.end()) continue;
|
||||
|
||||
if (it == atlas_lookup_.end())
|
||||
continue;
|
||||
|
||||
AtlasEntry* entry = it->second;
|
||||
|
||||
|
||||
// Calculate destination rectangle
|
||||
SDL_Rect dest_rect = {
|
||||
static_cast<int>(cmd->x),
|
||||
static_cast<int>(cmd->y),
|
||||
static_cast<int>(entry->uv_rect.w * cmd->scale_x),
|
||||
static_cast<int>(entry->uv_rect.h * cmd->scale_y)
|
||||
};
|
||||
|
||||
SDL_Rect dest_rect = {static_cast<int>(cmd->x), static_cast<int>(cmd->y),
|
||||
static_cast<int>(entry->uv_rect.w * cmd->scale_x),
|
||||
static_cast<int>(entry->uv_rect.h * cmd->scale_y)};
|
||||
|
||||
// Apply rotation if needed
|
||||
if (std::abs(cmd->rotation) > 0.001F) {
|
||||
// For rotation, we'd need to use SDL_RenderCopyEx
|
||||
@@ -193,26 +200,30 @@ void AtlasRenderer::RenderBatch(const std::vector<RenderCommand>& render_command
|
||||
}
|
||||
}
|
||||
|
||||
void AtlasRenderer::RenderBatchWithBppOptimization(const std::vector<RenderCommand>& render_commands,
|
||||
const std::unordered_map<BppFormat, std::vector<int>>& bpp_groups) {
|
||||
void AtlasRenderer::RenderBatchWithBppOptimization(
|
||||
const std::vector<RenderCommand>& render_commands,
|
||||
const std::unordered_map<BppFormat, std::vector<int>>& bpp_groups) {
|
||||
if (render_commands.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
ScopedTimer timer("atlas_batch_render_bpp_optimized");
|
||||
|
||||
|
||||
// Render each BPP group separately for optimal performance
|
||||
for (const auto& [bpp_format, command_indices] : bpp_groups) {
|
||||
if (command_indices.empty()) continue;
|
||||
|
||||
if (command_indices.empty())
|
||||
continue;
|
||||
|
||||
// Group commands by atlas for this BPP format
|
||||
std::unordered_map<int, std::vector<const RenderCommand*>> atlas_groups;
|
||||
|
||||
|
||||
for (int cmd_index : command_indices) {
|
||||
if (cmd_index >= 0 && cmd_index < static_cast<int>(render_commands.size())) {
|
||||
if (cmd_index >= 0 &&
|
||||
cmd_index < static_cast<int>(render_commands.size())) {
|
||||
const auto& cmd = render_commands[cmd_index];
|
||||
auto it = atlas_lookup_.find(cmd.atlas_id);
|
||||
if (it != atlas_lookup_.end() && it->second->in_use && it->second->bpp_format == bpp_format) {
|
||||
if (it != atlas_lookup_.end() && it->second->in_use &&
|
||||
it->second->bpp_format == bpp_format) {
|
||||
// Find which atlas contains this entry
|
||||
for (size_t i = 0; i < atlases_.size(); ++i) {
|
||||
for (const auto& entry : atlases_[i]->entries) {
|
||||
@@ -225,31 +236,31 @@ void AtlasRenderer::RenderBatchWithBppOptimization(const std::vector<RenderComma
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Render each atlas group for this BPP format
|
||||
for (const auto& [atlas_index, commands] : atlas_groups) {
|
||||
if (commands.empty()) continue;
|
||||
|
||||
if (commands.empty())
|
||||
continue;
|
||||
|
||||
auto& atlas = *atlases_[atlas_index];
|
||||
|
||||
|
||||
// Set atlas texture with BPP-specific blend mode
|
||||
// SDL_SetTextureBlendMode(atlas.texture, SDL_BLENDMODE_BLEND);
|
||||
|
||||
|
||||
// Render all commands for this atlas and BPP format
|
||||
for (const auto* cmd : commands) {
|
||||
auto it = atlas_lookup_.find(cmd->atlas_id);
|
||||
if (it == atlas_lookup_.end()) continue;
|
||||
|
||||
if (it == atlas_lookup_.end())
|
||||
continue;
|
||||
|
||||
AtlasEntry* entry = it->second;
|
||||
|
||||
|
||||
// Calculate destination rectangle
|
||||
SDL_Rect dest_rect = {
|
||||
static_cast<int>(cmd->x),
|
||||
static_cast<int>(cmd->y),
|
||||
static_cast<int>(entry->uv_rect.w * cmd->scale_x),
|
||||
static_cast<int>(entry->uv_rect.h * cmd->scale_y)
|
||||
};
|
||||
|
||||
static_cast<int>(cmd->x), static_cast<int>(cmd->y),
|
||||
static_cast<int>(entry->uv_rect.w * cmd->scale_x),
|
||||
static_cast<int>(entry->uv_rect.h * cmd->scale_y)};
|
||||
|
||||
// Apply rotation if needed
|
||||
if (std::abs(cmd->rotation) > 0.001F) {
|
||||
renderer_->RenderCopy(atlas.texture, &entry->uv_rect, &dest_rect);
|
||||
@@ -263,35 +274,37 @@ void AtlasRenderer::RenderBatchWithBppOptimization(const std::vector<RenderComma
|
||||
|
||||
AtlasStats AtlasRenderer::GetStats() const {
|
||||
AtlasStats stats;
|
||||
|
||||
|
||||
stats.total_atlases = atlases_.size();
|
||||
|
||||
|
||||
for (const auto& atlas : atlases_) {
|
||||
stats.total_entries += atlas->entries.size();
|
||||
stats.used_entries += std::count_if(atlas->entries.begin(), atlas->entries.end(),
|
||||
[](const AtlasEntry& entry) { return entry.in_use; });
|
||||
|
||||
stats.used_entries +=
|
||||
std::count_if(atlas->entries.begin(), atlas->entries.end(),
|
||||
[](const AtlasEntry& entry) { return entry.in_use; });
|
||||
|
||||
// Calculate memory usage (simplified)
|
||||
stats.total_memory += atlas->size * atlas->size * 4; // RGBA8888
|
||||
stats.total_memory += atlas->size * atlas->size * 4; // RGBA8888
|
||||
}
|
||||
|
||||
|
||||
if (stats.total_entries > 0) {
|
||||
stats.utilization_percent = (static_cast<float>(stats.used_entries) / stats.total_entries) * 100.0F;
|
||||
stats.utilization_percent =
|
||||
(static_cast<float>(stats.used_entries) / stats.total_entries) * 100.0F;
|
||||
}
|
||||
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
void AtlasRenderer::Defragment() {
|
||||
ScopedTimer timer("atlas_defragment");
|
||||
|
||||
|
||||
for (auto& atlas : atlases_) {
|
||||
// Remove unused entries
|
||||
atlas->entries.erase(
|
||||
std::remove_if(atlas->entries.begin(), atlas->entries.end(),
|
||||
[](const AtlasEntry& entry) { return !entry.in_use; }),
|
||||
atlas->entries.end());
|
||||
|
||||
std::remove_if(atlas->entries.begin(), atlas->entries.end(),
|
||||
[](const AtlasEntry& entry) { return !entry.in_use; }),
|
||||
atlas->entries.end());
|
||||
|
||||
// Rebuild atlas texture
|
||||
RebuildAtlas(*atlas);
|
||||
}
|
||||
@@ -304,7 +317,7 @@ void AtlasRenderer::Clear() {
|
||||
renderer_->DestroyTexture(atlas->texture);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
atlases_.clear();
|
||||
atlas_lookup_.clear();
|
||||
next_atlas_id_ = 0;
|
||||
@@ -315,26 +328,24 @@ AtlasRenderer::~AtlasRenderer() {
|
||||
Clear();
|
||||
}
|
||||
|
||||
void AtlasRenderer::RenderBitmap(int atlas_id, float x, float y, float scale_x, float scale_y) {
|
||||
void AtlasRenderer::RenderBitmap(int atlas_id, float x, float y, float scale_x,
|
||||
float scale_y) {
|
||||
auto it = atlas_lookup_.find(atlas_id);
|
||||
if (it == atlas_lookup_.end() || !it->second->in_use) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
AtlasEntry* entry = it->second;
|
||||
|
||||
|
||||
// Find which atlas contains this entry
|
||||
for (auto& atlas : atlases_) {
|
||||
for (const auto& atlas_entry : atlas->entries) {
|
||||
if (atlas_entry.atlas_id == atlas_id) {
|
||||
// Calculate destination rectangle
|
||||
SDL_Rect dest_rect = {
|
||||
static_cast<int>(x),
|
||||
static_cast<int>(y),
|
||||
static_cast<int>(entry->uv_rect.w * scale_x),
|
||||
static_cast<int>(entry->uv_rect.h * scale_y)
|
||||
};
|
||||
|
||||
SDL_Rect dest_rect = {static_cast<int>(x), static_cast<int>(y),
|
||||
static_cast<int>(entry->uv_rect.w * scale_x),
|
||||
static_cast<int>(entry->uv_rect.h * scale_y)};
|
||||
|
||||
// Render using atlas texture
|
||||
// SDL_SetTextureBlendMode(atlas->texture, SDL_BLENDMODE_BLEND);
|
||||
renderer_->RenderCopy(atlas->texture, &entry->uv_rect, &dest_rect);
|
||||
@@ -349,47 +360,43 @@ SDL_Rect AtlasRenderer::GetUVCoordinates(int atlas_id) const {
|
||||
if (it == atlas_lookup_.end() || !it->second->in_use) {
|
||||
return {0, 0, 0, 0};
|
||||
}
|
||||
|
||||
|
||||
return it->second->uv_rect;
|
||||
}
|
||||
|
||||
bool AtlasRenderer::PackBitmap(Atlas& atlas, const Bitmap& bitmap, SDL_Rect& uv_rect) {
|
||||
bool AtlasRenderer::PackBitmap(Atlas& atlas, const Bitmap& bitmap,
|
||||
SDL_Rect& uv_rect) {
|
||||
int width = bitmap.width();
|
||||
int height = bitmap.height();
|
||||
|
||||
|
||||
// Find free region
|
||||
SDL_Rect free_rect = FindFreeRegion(atlas, width, height);
|
||||
if (free_rect.w == 0 || free_rect.h == 0) {
|
||||
return false; // No space available
|
||||
return false; // No space available
|
||||
}
|
||||
|
||||
|
||||
// Mark region as used
|
||||
MarkRegionUsed(atlas, free_rect, true);
|
||||
|
||||
|
||||
// Set UV coordinates (normalized to 0-1 range)
|
||||
uv_rect = {
|
||||
free_rect.x,
|
||||
free_rect.y,
|
||||
width,
|
||||
height
|
||||
};
|
||||
|
||||
uv_rect = {free_rect.x, free_rect.y, width, height};
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void AtlasRenderer::CreateNewAtlas() {
|
||||
int size = 1024; // Default size
|
||||
int size = 1024; // Default size
|
||||
if (!atlases_.empty()) {
|
||||
size = atlases_.back()->size * 2; // Double size for new atlas
|
||||
size = atlases_.back()->size * 2; // Double size for new atlas
|
||||
}
|
||||
|
||||
|
||||
atlases_.push_back(std::make_unique<Atlas>(size));
|
||||
current_atlas_ = atlases_.size() - 1;
|
||||
|
||||
|
||||
// Create SDL texture for the atlas
|
||||
auto& atlas = *atlases_[current_atlas_];
|
||||
atlas.texture = renderer_->CreateTexture(size, size);
|
||||
|
||||
|
||||
if (!atlas.texture) {
|
||||
SDL_Log("Failed to create atlas texture: %s", SDL_GetError());
|
||||
}
|
||||
@@ -398,19 +405,19 @@ void AtlasRenderer::CreateNewAtlas() {
|
||||
void AtlasRenderer::RebuildAtlas(Atlas& atlas) {
|
||||
// Clear used regions
|
||||
std::fill(atlas.used_regions.begin(), atlas.used_regions.end(), false);
|
||||
|
||||
|
||||
// Rebuild atlas texture by copying from source textures
|
||||
renderer_->SetRenderTarget(atlas.texture);
|
||||
renderer_->SetDrawColor({0, 0, 0, 0});
|
||||
renderer_->Clear();
|
||||
|
||||
|
||||
for (auto& entry : atlas.entries) {
|
||||
if (entry.in_use && entry.texture) {
|
||||
renderer_->RenderCopy(entry.texture, nullptr, &entry.uv_rect);
|
||||
MarkRegionUsed(atlas, entry.uv_rect, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
renderer_->SetRenderTarget(nullptr);
|
||||
}
|
||||
|
||||
@@ -419,27 +426,29 @@ SDL_Rect AtlasRenderer::FindFreeRegion(Atlas& atlas, int width, int height) {
|
||||
for (int y = 0; y <= atlas.size - height; ++y) {
|
||||
for (int x = 0; x <= atlas.size - width; ++x) {
|
||||
bool can_fit = true;
|
||||
|
||||
|
||||
// Check if region is free
|
||||
for (int dy = 0; dy < height && can_fit; ++dy) {
|
||||
for (int dx = 0; dx < width && can_fit; ++dx) {
|
||||
int index = (y + dy) * atlas.size + (x + dx);
|
||||
if (index >= static_cast<int>(atlas.used_regions.size()) || atlas.used_regions[index]) {
|
||||
if (index >= static_cast<int>(atlas.used_regions.size()) ||
|
||||
atlas.used_regions[index]) {
|
||||
can_fit = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (can_fit) {
|
||||
return {x, y, width, height};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {0, 0, 0, 0}; // No space found
|
||||
|
||||
return {0, 0, 0, 0}; // No space found
|
||||
}
|
||||
|
||||
void AtlasRenderer::MarkRegionUsed(Atlas& atlas, const SDL_Rect& rect, bool used) {
|
||||
void AtlasRenderer::MarkRegionUsed(Atlas& atlas, const SDL_Rect& rect,
|
||||
bool used) {
|
||||
for (int y = rect.y; y < rect.y + rect.h; ++y) {
|
||||
for (int x = rect.x; x < rect.x + rect.w; ++x) {
|
||||
int index = y * atlas.size + x;
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
#define YAZE_APP_GFX_ATLAS_RENDERER_H
|
||||
|
||||
#include <SDL.h>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "app/gfx/core/bitmap.h"
|
||||
#include "app/gfx/debug/performance/performance_profiler.h"
|
||||
@@ -16,18 +16,23 @@ namespace gfx {
|
||||
/**
|
||||
* @brief Render command for batch rendering
|
||||
*/
|
||||
struct RenderCommand {
|
||||
int atlas_id; ///< Atlas ID of bitmap to render
|
||||
float x, y; ///< Screen coordinates
|
||||
struct RenderCommand {
|
||||
int atlas_id; ///< Atlas ID of bitmap to render
|
||||
float x, y; ///< Screen coordinates
|
||||
float scale_x, scale_y; ///< Scale factors
|
||||
float rotation; ///< Rotation angle in degrees
|
||||
SDL_Color tint; ///< Color tint
|
||||
|
||||
RenderCommand(int id, float x_pos, float y_pos,
|
||||
float sx = 1.0f, float sy = 1.0f,
|
||||
float rot = 0.0f, SDL_Color color = {255, 255, 255, 255})
|
||||
: atlas_id(id), x(x_pos), y(y_pos),
|
||||
scale_x(sx), scale_y(sy), rotation(rot), tint(color) {}
|
||||
float rotation; ///< Rotation angle in degrees
|
||||
SDL_Color tint; ///< Color tint
|
||||
|
||||
RenderCommand(int id, float x_pos, float y_pos, float sx = 1.0f,
|
||||
float sy = 1.0f, float rot = 0.0f,
|
||||
SDL_Color color = {255, 255, 255, 255})
|
||||
: atlas_id(id),
|
||||
x(x_pos),
|
||||
y(y_pos),
|
||||
scale_x(sx),
|
||||
scale_y(sy),
|
||||
rotation(rot),
|
||||
tint(color) {}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -40,9 +45,14 @@ struct AtlasStats {
|
||||
size_t total_memory;
|
||||
size_t used_memory;
|
||||
float utilization_percent;
|
||||
|
||||
AtlasStats() : total_atlases(0), total_entries(0), used_entries(0),
|
||||
total_memory(0), used_memory(0), utilization_percent(0.0f) {}
|
||||
|
||||
AtlasStats()
|
||||
: total_atlases(0),
|
||||
total_entries(0),
|
||||
used_entries(0),
|
||||
total_memory(0),
|
||||
used_memory(0),
|
||||
utilization_percent(0.0f) {}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -121,8 +131,9 @@ class AtlasRenderer {
|
||||
* @param render_commands Vector of render commands
|
||||
* @param bpp_groups Map of BPP format to command groups for optimization
|
||||
*/
|
||||
void RenderBatchWithBppOptimization(const std::vector<RenderCommand>& render_commands,
|
||||
const std::unordered_map<BppFormat, std::vector<int>>& bpp_groups);
|
||||
void RenderBatchWithBppOptimization(
|
||||
const std::vector<RenderCommand>& render_commands,
|
||||
const std::unordered_map<BppFormat, std::vector<int>>& bpp_groups);
|
||||
|
||||
/**
|
||||
* @brief Get atlas statistics
|
||||
@@ -148,7 +159,8 @@ class AtlasRenderer {
|
||||
* @param scale_x Horizontal scale factor
|
||||
* @param scale_y Vertical scale factor
|
||||
*/
|
||||
void RenderBitmap(int atlas_id, float x, float y, float scale_x = 1.0f, float scale_y = 1.0f);
|
||||
void RenderBitmap(int atlas_id, float x, float y, float scale_x = 1.0f,
|
||||
float scale_y = 1.0f);
|
||||
|
||||
/**
|
||||
* @brief Get UV coordinates for a bitmap in the atlas
|
||||
@@ -169,11 +181,16 @@ class AtlasRenderer {
|
||||
BppFormat bpp_format; // BPP format of this entry
|
||||
int original_width;
|
||||
int original_height;
|
||||
|
||||
AtlasEntry(int id, const SDL_Rect& rect, TextureHandle tex, BppFormat bpp = BppFormat::kBpp8,
|
||||
int width = 0, int height = 0)
|
||||
: atlas_id(id), uv_rect(rect), texture(tex), in_use(true),
|
||||
bpp_format(bpp), original_width(width), original_height(height) {}
|
||||
|
||||
AtlasEntry(int id, const SDL_Rect& rect, TextureHandle tex,
|
||||
BppFormat bpp = BppFormat::kBpp8, int width = 0, int height = 0)
|
||||
: atlas_id(id),
|
||||
uv_rect(rect),
|
||||
texture(tex),
|
||||
in_use(true),
|
||||
bpp_format(bpp),
|
||||
original_width(width),
|
||||
original_height(height) {}
|
||||
};
|
||||
|
||||
struct Atlas {
|
||||
@@ -181,7 +198,7 @@ class AtlasRenderer {
|
||||
int size;
|
||||
std::vector<AtlasEntry> entries;
|
||||
std::vector<bool> used_regions; // Track used regions for packing
|
||||
|
||||
|
||||
Atlas(int s) : size(s), used_regions(s * s, false) {}
|
||||
};
|
||||
|
||||
@@ -199,7 +216,6 @@ class AtlasRenderer {
|
||||
void MarkRegionUsed(Atlas& atlas, const SDL_Rect& rect, bool used);
|
||||
};
|
||||
|
||||
|
||||
} // namespace gfx
|
||||
} // namespace yaze
|
||||
|
||||
|
||||
@@ -18,21 +18,26 @@ BackgroundBuffer::BackgroundBuffer(int width, int height)
|
||||
}
|
||||
|
||||
void BackgroundBuffer::SetTileAt(int x, int y, uint16_t value) {
|
||||
if (x < 0 || y < 0) return;
|
||||
if (x < 0 || y < 0)
|
||||
return;
|
||||
int tiles_w = width_ / 8;
|
||||
int tiles_h = height_ / 8;
|
||||
if (x >= tiles_w || y >= tiles_h) return;
|
||||
if (x >= tiles_w || y >= tiles_h)
|
||||
return;
|
||||
buffer_[y * tiles_w + x] = value;
|
||||
}
|
||||
|
||||
uint16_t BackgroundBuffer::GetTileAt(int x, int y) const {
|
||||
int tiles_w = width_ / 8;
|
||||
int tiles_h = height_ / 8;
|
||||
if (x < 0 || y < 0 || x >= tiles_w || y >= tiles_h) return 0;
|
||||
if (x < 0 || y < 0 || x >= tiles_w || y >= tiles_h)
|
||||
return 0;
|
||||
return buffer_[y * tiles_w + x];
|
||||
}
|
||||
|
||||
void BackgroundBuffer::ClearBuffer() { std::ranges::fill(buffer_, 0); }
|
||||
void BackgroundBuffer::ClearBuffer() {
|
||||
std::ranges::fill(buffer_, 0);
|
||||
}
|
||||
|
||||
void BackgroundBuffer::DrawTile(const TileInfo& tile, uint8_t* canvas,
|
||||
const uint8_t* tiledata, int indexoffset) {
|
||||
@@ -40,12 +45,16 @@ void BackgroundBuffer::DrawTile(const TileInfo& tile, uint8_t* canvas,
|
||||
// Calculate tile position in the tilesheet
|
||||
int tile_x = (tile.id_ % 16) * 8; // 16 tiles per row, 8 pixels per tile
|
||||
int tile_y = (tile.id_ / 16) * 8; // Each row is 16 tiles
|
||||
|
||||
|
||||
// DEBUG: For floor tiles, check what we're actually reading
|
||||
static int debug_count = 0;
|
||||
if (debug_count < 4 && (tile.id_ == 0xEC || tile.id_ == 0xED || tile.id_ == 0xFC || tile.id_ == 0xFD)) {
|
||||
LOG_DEBUG("[DrawTile]", "Floor tile 0x%02X at sheet pos (%d,%d), palette=%d, mirror=(%d,%d)",
|
||||
tile.id_, tile_x, tile_y, tile.palette_, tile.horizontal_mirror_, tile.vertical_mirror_);
|
||||
if (debug_count < 4 && (tile.id_ == 0xEC || tile.id_ == 0xED ||
|
||||
tile.id_ == 0xFC || tile.id_ == 0xFD)) {
|
||||
LOG_DEBUG(
|
||||
"[DrawTile]",
|
||||
"Floor tile 0x%02X at sheet pos (%d,%d), palette=%d, mirror=(%d,%d)",
|
||||
tile.id_, tile_x, tile_y, tile.palette_, tile.horizontal_mirror_,
|
||||
tile.vertical_mirror_);
|
||||
LOG_DEBUG("[DrawTile]", "First row (8 pixels): ");
|
||||
for (int i = 0; i < 8; i++) {
|
||||
int src_index = tile_y * 128 + (tile_x + i);
|
||||
@@ -58,7 +67,7 @@ void BackgroundBuffer::DrawTile(const TileInfo& tile, uint8_t* canvas,
|
||||
}
|
||||
debug_count++;
|
||||
}
|
||||
|
||||
|
||||
// Dungeon graphics are 3BPP: 8 colors per palette (0-7, 8-15, 16-23, etc.)
|
||||
// NOT 4BPP which would be 16 colors per palette!
|
||||
// Clamp palette to 0-10 (90 colors / 8 = 11.25, so max palette is 10)
|
||||
@@ -66,7 +75,7 @@ void BackgroundBuffer::DrawTile(const TileInfo& tile, uint8_t* canvas,
|
||||
if (clamped_palette > 10) {
|
||||
clamped_palette = clamped_palette % 11;
|
||||
}
|
||||
|
||||
|
||||
// For 3BPP: palette offset = palette * 8 (not * 16!)
|
||||
uint8_t palette_offset = (uint8_t)(clamped_palette * 8);
|
||||
|
||||
@@ -76,11 +85,11 @@ void BackgroundBuffer::DrawTile(const TileInfo& tile, uint8_t* canvas,
|
||||
// Apply mirroring
|
||||
int src_x = tile.horizontal_mirror_ ? (7 - px) : px;
|
||||
int src_y = tile.vertical_mirror_ ? (7 - py) : py;
|
||||
|
||||
|
||||
// Read pixel from tiledata (128-pixel-wide bitmap)
|
||||
int src_index = (tile_y + src_y) * 128 + (tile_x + src_x);
|
||||
uint8_t pixel_index = tiledata[src_index];
|
||||
|
||||
|
||||
// Apply palette offset and write to canvas
|
||||
// For 3BPP: final color = base_pixel (0-7) + palette_offset (0, 8, 16, 24, ...)
|
||||
if (pixel_index == 0) {
|
||||
@@ -99,11 +108,12 @@ void BackgroundBuffer::DrawBackground(std::span<uint8_t> gfx16_data) {
|
||||
if ((int)buffer_.size() < tiles_w * tiles_h) {
|
||||
buffer_.resize(tiles_w * tiles_h);
|
||||
}
|
||||
|
||||
|
||||
// NEVER recreate bitmap here - it should be created by DrawFloor or initialized earlier
|
||||
// If bitmap doesn't exist, create it ONCE with zeros
|
||||
if (!bitmap_.is_active() || bitmap_.width() == 0) {
|
||||
bitmap_.Create(width_, height_, 8, std::vector<uint8_t>(width_ * height_, 0));
|
||||
bitmap_.Create(width_, height_, 8,
|
||||
std::vector<uint8_t>(width_ * height_, 0));
|
||||
}
|
||||
|
||||
// For each tile on the tile buffer
|
||||
@@ -112,33 +122,34 @@ void BackgroundBuffer::DrawBackground(std::span<uint8_t> gfx16_data) {
|
||||
for (int yy = 0; yy < tiles_h; yy++) {
|
||||
for (int xx = 0; xx < tiles_w; xx++) {
|
||||
uint16_t word = buffer_[xx + yy * tiles_w];
|
||||
|
||||
|
||||
// Skip empty tiles (0xFFFF) - these show the floor
|
||||
if (word == 0xFFFF) {
|
||||
skipped_count++;
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
// Skip zero tiles - also show the floor
|
||||
if (word == 0) {
|
||||
skipped_count++;
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
auto tile = gfx::WordToTileInfo(word);
|
||||
|
||||
|
||||
// Skip floor tiles (0xEC-0xFD) - don't overwrite DrawFloor's work
|
||||
// These are the animated floor tiles, already drawn by DrawFloor
|
||||
if (tile.id_ >= 0xEC && tile.id_ <= 0xFD) {
|
||||
skipped_count++;
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
// Calculate pixel offset for tile position (xx, yy) in the 512x512 bitmap
|
||||
// Each tile is 8x8, so pixel Y = yy * 8, pixel X = xx * 8
|
||||
// Linear offset = (pixel_y * width) + pixel_x = (yy * 8 * 512) + (xx * 8)
|
||||
int tile_offset = (yy * 8 * width_) + (xx * 8);
|
||||
DrawTile(tile, bitmap_.mutable_data().data(), gfx16_data.data(), tile_offset);
|
||||
DrawTile(tile, bitmap_.mutable_data().data(), gfx16_data.data(),
|
||||
tile_offset);
|
||||
drawn_count++;
|
||||
}
|
||||
}
|
||||
@@ -146,7 +157,8 @@ void BackgroundBuffer::DrawBackground(std::span<uint8_t> gfx16_data) {
|
||||
// DrawTile() writes to bitmap_.mutable_data(), but the SDL surface needs updating
|
||||
if (bitmap_.surface() && bitmap_.mutable_data().size() > 0) {
|
||||
SDL_LockSurface(bitmap_.surface());
|
||||
memcpy(bitmap_.surface()->pixels, bitmap_.mutable_data().data(), bitmap_.mutable_data().size());
|
||||
memcpy(bitmap_.surface()->pixels, bitmap_.mutable_data().data(),
|
||||
bitmap_.mutable_data().size());
|
||||
SDL_UnlockSurface(bitmap_.surface());
|
||||
}
|
||||
}
|
||||
@@ -156,16 +168,18 @@ void BackgroundBuffer::DrawFloor(const std::vector<uint8_t>& rom_data,
|
||||
uint8_t floor_graphics) {
|
||||
// Create bitmap ONCE at the start if it doesn't exist
|
||||
if (!bitmap_.is_active() || bitmap_.width() == 0) {
|
||||
LOG_DEBUG("[DrawFloor]", "Creating bitmap: %dx%d, active=%d, width=%d",
|
||||
width_, height_, bitmap_.is_active(), bitmap_.width());
|
||||
bitmap_.Create(width_, height_, 8, std::vector<uint8_t>(width_ * height_, 0));
|
||||
LOG_DEBUG("[DrawFloor]", "After Create: active=%d, width=%d, height=%d",
|
||||
bitmap_.is_active(), bitmap_.width(), bitmap_.height());
|
||||
LOG_DEBUG("[DrawFloor]", "Creating bitmap: %dx%d, active=%d, width=%d",
|
||||
width_, height_, bitmap_.is_active(), bitmap_.width());
|
||||
bitmap_.Create(width_, height_, 8,
|
||||
std::vector<uint8_t>(width_ * height_, 0));
|
||||
LOG_DEBUG("[DrawFloor]", "After Create: active=%d, width=%d, height=%d",
|
||||
bitmap_.is_active(), bitmap_.width(), bitmap_.height());
|
||||
} else {
|
||||
LOG_DEBUG("[DrawFloor]", "Bitmap already exists: active=%d, width=%d, height=%d",
|
||||
bitmap_.is_active(), bitmap_.width(), bitmap_.height());
|
||||
LOG_DEBUG("[DrawFloor]",
|
||||
"Bitmap already exists: active=%d, width=%d, height=%d",
|
||||
bitmap_.is_active(), bitmap_.width(), bitmap_.height());
|
||||
}
|
||||
|
||||
|
||||
auto f = (uint8_t)(floor_graphics << 4);
|
||||
|
||||
// Create floor tiles from ROM data
|
||||
@@ -186,7 +200,7 @@ void BackgroundBuffer::DrawFloor(const std::vector<uint8_t>& rom_data,
|
||||
rom_data[tile_address_floor + f + 5]);
|
||||
gfx::TileInfo floorTile8(rom_data[tile_address_floor + f + 6],
|
||||
rom_data[tile_address_floor + f + 7]);
|
||||
|
||||
|
||||
// Floor tiles specify which 8-color sub-palette from the 90-color dungeon palette
|
||||
// e.g., palette 6 = colors 48-55 (6 * 8 = 48)
|
||||
|
||||
|
||||
@@ -13,17 +13,19 @@ TextureAtlas::TextureAtlas(int width, int height)
|
||||
LOG_DEBUG("[TextureAtlas]", "Created %dx%d atlas", width, height);
|
||||
}
|
||||
|
||||
TextureAtlas::AtlasRegion* TextureAtlas::AllocateRegion(int source_id, int width, int height) {
|
||||
TextureAtlas::AtlasRegion* TextureAtlas::AllocateRegion(int source_id,
|
||||
int width, int height) {
|
||||
// Simple linear packing algorithm
|
||||
// TODO: Implement more efficient rect packing (shelf, guillotine, etc.)
|
||||
|
||||
|
||||
int pack_x, pack_y;
|
||||
if (!TryPackRect(width, height, pack_x, pack_y)) {
|
||||
LOG_DEBUG("[TextureAtlas]", "Failed to allocate %dx%d region for source %d (atlas full)",
|
||||
width, height, source_id);
|
||||
LOG_DEBUG("[TextureAtlas]",
|
||||
"Failed to allocate %dx%d region for source %d (atlas full)",
|
||||
width, height, source_id);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
AtlasRegion region;
|
||||
region.x = pack_x;
|
||||
region.y = pack_y;
|
||||
@@ -31,46 +33,49 @@ TextureAtlas::AtlasRegion* TextureAtlas::AllocateRegion(int source_id, int width
|
||||
region.height = height;
|
||||
region.source_id = source_id;
|
||||
region.in_use = true;
|
||||
|
||||
|
||||
regions_[source_id] = region;
|
||||
|
||||
LOG_DEBUG("[TextureAtlas]", "Allocated region (%d,%d,%dx%d) for source %d",
|
||||
pack_x, pack_y, width, height, source_id);
|
||||
|
||||
|
||||
LOG_DEBUG("[TextureAtlas]", "Allocated region (%d,%d,%dx%d) for source %d",
|
||||
pack_x, pack_y, width, height, source_id);
|
||||
|
||||
return ®ions_[source_id];
|
||||
}
|
||||
|
||||
absl::Status TextureAtlas::PackBitmap(const Bitmap& src, const AtlasRegion& region) {
|
||||
absl::Status TextureAtlas::PackBitmap(const Bitmap& src,
|
||||
const AtlasRegion& region) {
|
||||
if (!region.in_use) {
|
||||
return absl::FailedPreconditionError("Region not allocated");
|
||||
}
|
||||
|
||||
|
||||
if (!src.is_active() || src.width() == 0 || src.height() == 0) {
|
||||
return absl::InvalidArgumentError("Source bitmap not active");
|
||||
}
|
||||
|
||||
|
||||
if (region.width < src.width() || region.height < src.height()) {
|
||||
return absl::InvalidArgumentError("Region too small for bitmap");
|
||||
}
|
||||
|
||||
|
||||
// TODO: Implement pixel copying from src to atlas_bitmap_ at region coordinates
|
||||
// For now, just return OK (stub implementation)
|
||||
|
||||
LOG_DEBUG("[TextureAtlas]", "Packed %dx%d bitmap into region at (%d,%d) for source %d",
|
||||
src.width(), src.height(), region.x, region.y, region.source_id);
|
||||
|
||||
|
||||
LOG_DEBUG("[TextureAtlas]",
|
||||
"Packed %dx%d bitmap into region at (%d,%d) for source %d",
|
||||
src.width(), src.height(), region.x, region.y, region.source_id);
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status TextureAtlas::DrawRegion(int source_id, int /*dest_x*/, int /*dest_y*/) {
|
||||
absl::Status TextureAtlas::DrawRegion(int source_id, int /*dest_x*/,
|
||||
int /*dest_y*/) {
|
||||
auto it = regions_.find(source_id);
|
||||
if (it == regions_.end() || !it->second.in_use) {
|
||||
return absl::NotFoundError("Region not found or not in use");
|
||||
}
|
||||
|
||||
|
||||
// TODO: Integrate with renderer to draw atlas region at (dest_x, dest_y)
|
||||
// For now, just return OK (stub implementation)
|
||||
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
@@ -102,18 +107,19 @@ TextureAtlas::AtlasStats TextureAtlas::GetStats() const {
|
||||
AtlasStats stats;
|
||||
stats.total_pixels = width_ * height_;
|
||||
stats.total_regions = regions_.size();
|
||||
|
||||
|
||||
for (const auto& [id, region] : regions_) {
|
||||
if (region.in_use) {
|
||||
stats.used_regions++;
|
||||
stats.used_pixels += region.width * region.height;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (stats.total_pixels > 0) {
|
||||
stats.utilization = static_cast<float>(stats.used_pixels) / stats.total_pixels * 100.0f;
|
||||
stats.utilization =
|
||||
static_cast<float>(stats.used_pixels) / stats.total_pixels * 100.0f;
|
||||
}
|
||||
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
@@ -128,12 +134,12 @@ bool TextureAtlas::TryPackRect(int width, int height, int& out_x, int& out_y) {
|
||||
row_height_ = std::max(row_height_, height);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// Move to next row
|
||||
next_x_ = 0;
|
||||
next_y_ += row_height_;
|
||||
row_height_ = 0;
|
||||
|
||||
|
||||
// Check if fits in new row
|
||||
if (next_y_ + height <= height_ && width <= width_) {
|
||||
out_x = next_x_;
|
||||
@@ -142,11 +148,10 @@ bool TextureAtlas::TryPackRect(int width, int height, int& out_x, int& out_y) {
|
||||
row_height_ = height;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// Atlas is full
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace gfx
|
||||
} // namespace yaze
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "app/gfx/core/bitmap.h"
|
||||
#include "absl/status/status.h"
|
||||
#include "app/gfx/core/bitmap.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace gfx {
|
||||
@@ -36,21 +36,21 @@ class TextureAtlas {
|
||||
* @brief Region within the atlas texture
|
||||
*/
|
||||
struct AtlasRegion {
|
||||
int x = 0; // X position in atlas
|
||||
int y = 0; // Y position in atlas
|
||||
int width = 0; // Region width
|
||||
int height = 0; // Region height
|
||||
int source_id = -1; // ID of source (e.g., room_id)
|
||||
bool in_use = false; // Whether this region is allocated
|
||||
int x = 0; // X position in atlas
|
||||
int y = 0; // Y position in atlas
|
||||
int width = 0; // Region width
|
||||
int height = 0; // Region height
|
||||
int source_id = -1; // ID of source (e.g., room_id)
|
||||
bool in_use = false; // Whether this region is allocated
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Construct texture atlas with specified dimensions
|
||||
* @param width Atlas width in pixels (typically 2048 or 4096)
|
||||
* @param height Atlas height in pixels (typically 2048 or 4096)
|
||||
*/
|
||||
explicit TextureAtlas(int width = 2048, int height = 2048);
|
||||
|
||||
|
||||
/**
|
||||
* @brief Allocate a region in the atlas for a source texture
|
||||
* @param source_id Identifier for the source (e.g., room_id)
|
||||
@@ -61,7 +61,7 @@ class TextureAtlas {
|
||||
* Uses simple rect packing algorithm. Future: implement more efficient packing.
|
||||
*/
|
||||
AtlasRegion* AllocateRegion(int source_id, int width, int height);
|
||||
|
||||
|
||||
/**
|
||||
* @brief Pack a bitmap into an allocated region
|
||||
* @param src Source bitmap to pack
|
||||
@@ -71,7 +71,7 @@ class TextureAtlas {
|
||||
* Copies pixel data from source bitmap into atlas at region coordinates.
|
||||
*/
|
||||
absl::Status PackBitmap(const Bitmap& src, const AtlasRegion& region);
|
||||
|
||||
|
||||
/**
|
||||
* @brief Draw a region from the atlas to screen coordinates
|
||||
* @param source_id Source identifier (e.g., room_id)
|
||||
@@ -82,38 +82,38 @@ class TextureAtlas {
|
||||
* Future: Integrate with renderer to draw atlas regions.
|
||||
*/
|
||||
absl::Status DrawRegion(int source_id, int dest_x, int dest_y);
|
||||
|
||||
|
||||
/**
|
||||
* @brief Free a region and mark it as available
|
||||
* @param source_id Source identifier to free
|
||||
*/
|
||||
void FreeRegion(int source_id);
|
||||
|
||||
|
||||
/**
|
||||
* @brief Clear all regions and reset atlas
|
||||
*/
|
||||
void Clear();
|
||||
|
||||
|
||||
/**
|
||||
* @brief Get the atlas bitmap (contains all packed textures)
|
||||
* @return Reference to atlas bitmap
|
||||
*/
|
||||
Bitmap& GetAtlasBitmap() { return atlas_bitmap_; }
|
||||
const Bitmap& GetAtlasBitmap() const { return atlas_bitmap_; }
|
||||
|
||||
|
||||
/**
|
||||
* @brief Get region for a specific source
|
||||
* @param source_id Source identifier
|
||||
* @return Pointer to region, or nullptr if not found
|
||||
*/
|
||||
const AtlasRegion* GetRegion(int source_id) const;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Get atlas dimensions
|
||||
*/
|
||||
int width() const { return width_; }
|
||||
int height() const { return height_; }
|
||||
|
||||
|
||||
/**
|
||||
* @brief Get atlas utilization statistics
|
||||
*/
|
||||
@@ -130,15 +130,15 @@ class TextureAtlas {
|
||||
int width_;
|
||||
int height_;
|
||||
Bitmap atlas_bitmap_; // Large combined bitmap
|
||||
|
||||
|
||||
// Simple linear packing for now (future: more efficient algorithms)
|
||||
int next_x_ = 0;
|
||||
int next_y_ = 0;
|
||||
int row_height_ = 0; // Current row height for packing
|
||||
|
||||
|
||||
// Map source_id → region
|
||||
std::map<int, AtlasRegion> regions_;
|
||||
|
||||
|
||||
// Simple rect packing helper
|
||||
bool TryPackRect(int width, int height, int& out_x, int& out_y);
|
||||
};
|
||||
@@ -147,4 +147,3 @@ class TextureAtlas {
|
||||
} // namespace yaze
|
||||
|
||||
#endif // YAZE_APP_GFX_TEXTURE_ATLAS_H
|
||||
|
||||
|
||||
@@ -2,17 +2,18 @@
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "app/gfx/resource/arena.h"
|
||||
#include "app/gfx/render/atlas_renderer.h"
|
||||
#include "app/gfx/core/bitmap.h"
|
||||
#include "app/gfx/debug/performance/performance_profiler.h"
|
||||
#include "app/gfx/render/atlas_renderer.h"
|
||||
#include "app/gfx/resource/arena.h"
|
||||
#include "app/gfx/types/snes_tile.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace gfx {
|
||||
|
||||
Tilemap CreateTilemap(IRenderer* renderer, std::vector<uint8_t> &data, int width, int height,
|
||||
int tile_size, int num_tiles, SnesPalette &palette) {
|
||||
Tilemap CreateTilemap(IRenderer* renderer, std::vector<uint8_t>& data,
|
||||
int width, int height, int tile_size, int num_tiles,
|
||||
SnesPalette& palette) {
|
||||
Tilemap tilemap;
|
||||
tilemap.tile_size.x = tile_size;
|
||||
tilemap.tile_size.y = tile_size;
|
||||
@@ -20,74 +21,80 @@ Tilemap CreateTilemap(IRenderer* renderer, std::vector<uint8_t> &data, int width
|
||||
tilemap.map_size.y = num_tiles;
|
||||
tilemap.atlas = Bitmap(width, height, 8, data);
|
||||
tilemap.atlas.SetPalette(palette);
|
||||
|
||||
|
||||
// Queue texture creation directly via Arena
|
||||
if (tilemap.atlas.is_active() && tilemap.atlas.surface()) {
|
||||
Arena::Get().QueueTextureCommand(Arena::TextureCommandType::CREATE, &tilemap.atlas);
|
||||
Arena::Get().QueueTextureCommand(Arena::TextureCommandType::CREATE,
|
||||
&tilemap.atlas);
|
||||
}
|
||||
|
||||
|
||||
return tilemap;
|
||||
}
|
||||
|
||||
void UpdateTilemap(IRenderer* renderer, Tilemap &tilemap, const std::vector<uint8_t> &data) {
|
||||
void UpdateTilemap(IRenderer* renderer, Tilemap& tilemap,
|
||||
const std::vector<uint8_t>& data) {
|
||||
tilemap.atlas.set_data(data);
|
||||
|
||||
|
||||
// Queue texture update directly via Arena
|
||||
if (tilemap.atlas.texture() && tilemap.atlas.is_active() && tilemap.atlas.surface()) {
|
||||
Arena::Get().QueueTextureCommand(Arena::TextureCommandType::UPDATE, &tilemap.atlas);
|
||||
} else if (!tilemap.atlas.texture() && tilemap.atlas.is_active() && tilemap.atlas.surface()) {
|
||||
if (tilemap.atlas.texture() && tilemap.atlas.is_active() &&
|
||||
tilemap.atlas.surface()) {
|
||||
Arena::Get().QueueTextureCommand(Arena::TextureCommandType::UPDATE,
|
||||
&tilemap.atlas);
|
||||
} else if (!tilemap.atlas.texture() && tilemap.atlas.is_active() &&
|
||||
tilemap.atlas.surface()) {
|
||||
// Create if doesn't exist yet
|
||||
Arena::Get().QueueTextureCommand(Arena::TextureCommandType::CREATE, &tilemap.atlas);
|
||||
Arena::Get().QueueTextureCommand(Arena::TextureCommandType::CREATE,
|
||||
&tilemap.atlas);
|
||||
}
|
||||
}
|
||||
|
||||
void RenderTile(IRenderer* renderer, Tilemap &tilemap, int tile_id) {
|
||||
void RenderTile(IRenderer* renderer, Tilemap& tilemap, int tile_id) {
|
||||
// Validate tilemap state before proceeding
|
||||
if (!tilemap.atlas.is_active() || tilemap.atlas.vector().empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (tile_id < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Get tile data without using problematic tile cache
|
||||
auto tile_data = GetTilemapData(tilemap, tile_id);
|
||||
if (tile_data.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Note: Tile cache disabled to prevent std::move() related crashes
|
||||
}
|
||||
|
||||
void RenderTile16(IRenderer* renderer, Tilemap &tilemap, int tile_id) {
|
||||
void RenderTile16(IRenderer* renderer, Tilemap& tilemap, int tile_id) {
|
||||
// Validate tilemap state before proceeding
|
||||
if (!tilemap.atlas.is_active() || tilemap.atlas.vector().empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (tile_id < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
int tiles_per_row = tilemap.atlas.width() / tilemap.tile_size.x;
|
||||
if (tiles_per_row <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
int tile_x = (tile_id % tiles_per_row) * tilemap.tile_size.x;
|
||||
int tile_y = (tile_id / tiles_per_row) * tilemap.tile_size.y;
|
||||
|
||||
|
||||
// Validate tile position
|
||||
if (tile_x < 0 || tile_x >= tilemap.atlas.width() ||
|
||||
tile_y < 0 || tile_y >= tilemap.atlas.height()) {
|
||||
if (tile_x < 0 || tile_x >= tilemap.atlas.width() || tile_y < 0 ||
|
||||
tile_y >= tilemap.atlas.height()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Note: Tile cache disabled to prevent std::move() related crashes
|
||||
}
|
||||
|
||||
void UpdateTile16(IRenderer* renderer, Tilemap &tilemap, int tile_id) {
|
||||
void UpdateTile16(IRenderer* renderer, Tilemap& tilemap, int tile_id) {
|
||||
// Check if tile is cached
|
||||
Bitmap* cached_tile = tilemap.tile_cache.GetTile(tile_id);
|
||||
if (cached_tile) {
|
||||
@@ -95,14 +102,16 @@ void UpdateTile16(IRenderer* renderer, Tilemap &tilemap, int tile_id) {
|
||||
int tiles_per_row = tilemap.atlas.width() / tilemap.tile_size.x;
|
||||
int tile_x = (tile_id % tiles_per_row) * tilemap.tile_size.x;
|
||||
int tile_y = (tile_id / tiles_per_row) * tilemap.tile_size.y;
|
||||
std::vector<uint8_t> tile_data(tilemap.tile_size.x * tilemap.tile_size.y, 0x00);
|
||||
std::vector<uint8_t> tile_data(tilemap.tile_size.x * tilemap.tile_size.y,
|
||||
0x00);
|
||||
int tile_data_offset = 0;
|
||||
tilemap.atlas.Get16x16Tile(tile_x, tile_y, tile_data, tile_data_offset);
|
||||
cached_tile->set_data(tile_data);
|
||||
|
||||
|
||||
// Queue texture update directly via Arena
|
||||
if (cached_tile->texture() && cached_tile->is_active()) {
|
||||
Arena::Get().QueueTextureCommand(Arena::TextureCommandType::UPDATE, cached_tile);
|
||||
Arena::Get().QueueTextureCommand(Arena::TextureCommandType::UPDATE,
|
||||
cached_tile);
|
||||
}
|
||||
} else {
|
||||
// Tile not cached, render it fresh
|
||||
@@ -111,7 +120,7 @@ void UpdateTile16(IRenderer* renderer, Tilemap &tilemap, int tile_id) {
|
||||
}
|
||||
|
||||
std::vector<uint8_t> FetchTileDataFromGraphicsBuffer(
|
||||
const std::vector<uint8_t> &data, int tile_id, int sheet_offset) {
|
||||
const std::vector<uint8_t>& data, int tile_id, int sheet_offset) {
|
||||
const int tile_width = 8;
|
||||
const int tile_height = 8;
|
||||
const int buffer_width = 128;
|
||||
@@ -144,7 +153,7 @@ std::vector<uint8_t> FetchTileDataFromGraphicsBuffer(
|
||||
|
||||
namespace {
|
||||
|
||||
void MirrorTileDataVertically(std::vector<uint8_t> &tile_data) {
|
||||
void MirrorTileDataVertically(std::vector<uint8_t>& tile_data) {
|
||||
for (int y = 0; y < 4; ++y) {
|
||||
for (int x = 0; x < 8; ++x) {
|
||||
std::swap(tile_data[y * 8 + x], tile_data[(7 - y) * 8 + x]);
|
||||
@@ -152,7 +161,7 @@ void MirrorTileDataVertically(std::vector<uint8_t> &tile_data) {
|
||||
}
|
||||
}
|
||||
|
||||
void MirrorTileDataHorizontally(std::vector<uint8_t> &tile_data) {
|
||||
void MirrorTileDataHorizontally(std::vector<uint8_t>& tile_data) {
|
||||
for (int y = 0; y < 8; ++y) {
|
||||
for (int x = 0; x < 4; ++x) {
|
||||
std::swap(tile_data[y * 8 + x], tile_data[y * 8 + (7 - x)]);
|
||||
@@ -160,8 +169,8 @@ void MirrorTileDataHorizontally(std::vector<uint8_t> &tile_data) {
|
||||
}
|
||||
}
|
||||
|
||||
void ComposeAndPlaceTilePart(Tilemap &tilemap, const std::vector<uint8_t> &data,
|
||||
const TileInfo &tile_info, int base_x, int base_y,
|
||||
void ComposeAndPlaceTilePart(Tilemap& tilemap, const std::vector<uint8_t>& data,
|
||||
const TileInfo& tile_info, int base_x, int base_y,
|
||||
int sheet_offset) {
|
||||
std::vector<uint8_t> tile_data =
|
||||
FetchTileDataFromGraphicsBuffer(data, tile_info.id_, sheet_offset);
|
||||
@@ -185,9 +194,9 @@ void ComposeAndPlaceTilePart(Tilemap &tilemap, const std::vector<uint8_t> &data,
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void ModifyTile16(Tilemap &tilemap, const std::vector<uint8_t> &data,
|
||||
const TileInfo &top_left, const TileInfo &top_right,
|
||||
const TileInfo &bottom_left, const TileInfo &bottom_right,
|
||||
void ModifyTile16(Tilemap& tilemap, const std::vector<uint8_t>& data,
|
||||
const TileInfo& top_left, const TileInfo& top_right,
|
||||
const TileInfo& bottom_left, const TileInfo& bottom_right,
|
||||
int sheet_offset, int tile_id) {
|
||||
// Calculate the base position for this Tile16 in the full-size bitmap
|
||||
int tiles_per_row = tilemap.atlas.width() / tilemap.tile_size.x;
|
||||
@@ -209,9 +218,9 @@ void ModifyTile16(Tilemap &tilemap, const std::vector<uint8_t> &data,
|
||||
tilemap.tile_info[tile_id] = {top_left, top_right, bottom_left, bottom_right};
|
||||
}
|
||||
|
||||
void ComposeTile16(Tilemap &tilemap, const std::vector<uint8_t> &data,
|
||||
const TileInfo &top_left, const TileInfo &top_right,
|
||||
const TileInfo &bottom_left, const TileInfo &bottom_right,
|
||||
void ComposeTile16(Tilemap& tilemap, const std::vector<uint8_t>& data,
|
||||
const TileInfo& top_left, const TileInfo& top_right,
|
||||
const TileInfo& bottom_left, const TileInfo& bottom_right,
|
||||
int sheet_offset) {
|
||||
int num_tiles = tilemap.tile_info.size();
|
||||
int tiles_per_row = tilemap.atlas.width() / tilemap.tile_size.x;
|
||||
@@ -232,56 +241,56 @@ void ComposeTile16(Tilemap &tilemap, const std::vector<uint8_t> &data,
|
||||
tilemap.tile_info.push_back({top_left, top_right, bottom_left, bottom_right});
|
||||
}
|
||||
|
||||
std::vector<uint8_t> GetTilemapData(Tilemap &tilemap, int tile_id) {
|
||||
|
||||
std::vector<uint8_t> GetTilemapData(Tilemap& tilemap, int tile_id) {
|
||||
|
||||
// Comprehensive validation to prevent crashes
|
||||
if (tile_id < 0) {
|
||||
SDL_Log("GetTilemapData: Invalid tile_id %d (negative)", tile_id);
|
||||
return std::vector<uint8_t>(256, 0); // Return empty 16x16 tile data
|
||||
return std::vector<uint8_t>(256, 0); // Return empty 16x16 tile data
|
||||
}
|
||||
|
||||
|
||||
if (!tilemap.atlas.is_active()) {
|
||||
SDL_Log("GetTilemapData: Atlas is not active for tile_id %d", tile_id);
|
||||
return std::vector<uint8_t>(256, 0); // Return empty 16x16 tile data
|
||||
return std::vector<uint8_t>(256, 0); // Return empty 16x16 tile data
|
||||
}
|
||||
|
||||
|
||||
if (tilemap.atlas.vector().empty()) {
|
||||
SDL_Log("GetTilemapData: Atlas vector is empty for tile_id %d", tile_id);
|
||||
return std::vector<uint8_t>(256, 0); // Return empty 16x16 tile data
|
||||
return std::vector<uint8_t>(256, 0); // Return empty 16x16 tile data
|
||||
}
|
||||
|
||||
|
||||
if (tilemap.tile_size.x <= 0 || tilemap.tile_size.y <= 0) {
|
||||
SDL_Log("GetTilemapData: Invalid tile size (%d, %d) for tile_id %d",
|
||||
SDL_Log("GetTilemapData: Invalid tile size (%d, %d) for tile_id %d",
|
||||
tilemap.tile_size.x, tilemap.tile_size.y, tile_id);
|
||||
return std::vector<uint8_t>(256, 0); // Return empty 16x16 tile data
|
||||
return std::vector<uint8_t>(256, 0); // Return empty 16x16 tile data
|
||||
}
|
||||
|
||||
|
||||
int tile_size = tilemap.tile_size.x;
|
||||
int width = tilemap.atlas.width();
|
||||
int height = tilemap.atlas.height();
|
||||
|
||||
|
||||
// Validate atlas dimensions
|
||||
if (width <= 0 || height <= 0) {
|
||||
SDL_Log("GetTilemapData: Invalid atlas dimensions (%d, %d) for tile_id %d",
|
||||
SDL_Log("GetTilemapData: Invalid atlas dimensions (%d, %d) for tile_id %d",
|
||||
width, height, tile_id);
|
||||
return std::vector<uint8_t>(tile_size * tile_size, 0);
|
||||
}
|
||||
|
||||
|
||||
// Calculate maximum possible tile_id based on atlas size
|
||||
int tiles_per_row = width / tile_size;
|
||||
int tiles_per_column = height / tile_size;
|
||||
int max_tile_id = tiles_per_row * tiles_per_column - 1;
|
||||
|
||||
|
||||
if (tile_id > max_tile_id) {
|
||||
SDL_Log("GetTilemapData: tile_id %d exceeds maximum %d (atlas: %dx%d, tile_size: %d)",
|
||||
tile_id, max_tile_id, width, height, tile_size);
|
||||
SDL_Log(
|
||||
"GetTilemapData: tile_id %d exceeds maximum %d (atlas: %dx%d, "
|
||||
"tile_size: %d)",
|
||||
tile_id, max_tile_id, width, height, tile_size);
|
||||
return std::vector<uint8_t>(tile_size * tile_size, 0);
|
||||
}
|
||||
|
||||
std::vector<uint8_t> data(tile_size * tile_size);
|
||||
|
||||
|
||||
|
||||
for (int ty = 0; ty < tile_size; ty++) {
|
||||
for (int tx = 0; tx < tile_size; tx++) {
|
||||
// Calculate atlas position more safely
|
||||
@@ -290,17 +299,20 @@ std::vector<uint8_t> GetTilemapData(Tilemap &tilemap, int tile_id) {
|
||||
int atlas_x = tile_col * tile_size + tx;
|
||||
int atlas_y = tile_row * tile_size + ty;
|
||||
int atlas_index = atlas_y * width + atlas_x;
|
||||
|
||||
|
||||
// Comprehensive bounds checking
|
||||
if (atlas_x >= 0 && atlas_x < width &&
|
||||
atlas_y >= 0 && atlas_y < height &&
|
||||
atlas_index >= 0 && atlas_index < static_cast<int>(tilemap.atlas.vector().size())) {
|
||||
if (atlas_x >= 0 && atlas_x < width && atlas_y >= 0 && atlas_y < height &&
|
||||
atlas_index >= 0 &&
|
||||
atlas_index < static_cast<int>(tilemap.atlas.vector().size())) {
|
||||
uint8_t value = tilemap.atlas.vector()[atlas_index];
|
||||
data[ty * tile_size + tx] = value;
|
||||
} else {
|
||||
SDL_Log("GetTilemapData: Atlas position (%d, %d) or index %d out of bounds (atlas: %dx%d, size: %zu)",
|
||||
atlas_x, atlas_y, atlas_index, width, height, tilemap.atlas.vector().size());
|
||||
data[ty * tile_size + tx] = 0; // Default to 0 if out of bounds
|
||||
SDL_Log(
|
||||
"GetTilemapData: Atlas position (%d, %d) or index %d out of bounds "
|
||||
"(atlas: %dx%d, size: %zu)",
|
||||
atlas_x, atlas_y, atlas_index, width, height,
|
||||
tilemap.atlas.vector().size());
|
||||
data[ty * tile_size + tx] = 0; // Default to 0 if out of bounds
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -308,15 +320,17 @@ std::vector<uint8_t> GetTilemapData(Tilemap &tilemap, int tile_id) {
|
||||
return data;
|
||||
}
|
||||
|
||||
void RenderTilesBatch(IRenderer* renderer, Tilemap& tilemap, const std::vector<int>& tile_ids,
|
||||
void RenderTilesBatch(IRenderer* renderer, Tilemap& tilemap,
|
||||
const std::vector<int>& tile_ids,
|
||||
const std::vector<std::pair<float, float>>& positions,
|
||||
const std::vector<std::pair<float, float>>& scales) {
|
||||
if (tile_ids.empty() || positions.empty() || tile_ids.size() != positions.size()) {
|
||||
if (tile_ids.empty() || positions.empty() ||
|
||||
tile_ids.size() != positions.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
ScopedTimer timer("tilemap_batch_render");
|
||||
|
||||
|
||||
// Initialize atlas renderer if not already done
|
||||
auto& atlas_renderer = AtlasRenderer::Get();
|
||||
if (!renderer) {
|
||||
@@ -324,16 +338,16 @@ void RenderTilesBatch(IRenderer* renderer, Tilemap& tilemap, const std::vector<i
|
||||
// In a full implementation, we'd get the renderer from the core system
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Prepare render commands
|
||||
std::vector<RenderCommand> render_commands;
|
||||
render_commands.reserve(tile_ids.size());
|
||||
|
||||
|
||||
for (size_t i = 0; i < tile_ids.size(); ++i) {
|
||||
int tile_id = tile_ids[i];
|
||||
float x = positions[i].first;
|
||||
float y = positions[i].second;
|
||||
|
||||
|
||||
// Get scale factors (default to 1.0 if not provided)
|
||||
float scale_x = 1.0F;
|
||||
float scale_y = 1.0F;
|
||||
@@ -341,7 +355,7 @@ void RenderTilesBatch(IRenderer* renderer, Tilemap& tilemap, const std::vector<i
|
||||
scale_x = scales[i].first;
|
||||
scale_y = scales[i].second;
|
||||
}
|
||||
|
||||
|
||||
// Try to get tile from cache first
|
||||
Bitmap* cached_tile = tilemap.tile_cache.GetTile(tile_id);
|
||||
if (!cached_tile) {
|
||||
@@ -355,13 +369,14 @@ void RenderTilesBatch(IRenderer* renderer, Tilemap& tilemap, const std::vector<i
|
||||
cached_tile->CreateTexture();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (cached_tile && cached_tile->is_active()) {
|
||||
// Queue texture creation if needed
|
||||
if (!cached_tile->texture() && cached_tile->surface()) {
|
||||
Arena::Get().QueueTextureCommand(Arena::TextureCommandType::CREATE, cached_tile);
|
||||
Arena::Get().QueueTextureCommand(Arena::TextureCommandType::CREATE,
|
||||
cached_tile);
|
||||
}
|
||||
|
||||
|
||||
// Add to atlas renderer
|
||||
int atlas_id = atlas_renderer.AddBitmap(*cached_tile);
|
||||
if (atlas_id >= 0) {
|
||||
@@ -369,7 +384,7 @@ void RenderTilesBatch(IRenderer* renderer, Tilemap& tilemap, const std::vector<i
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Render all commands in batch
|
||||
if (!render_commands.empty()) {
|
||||
atlas_renderer.RenderBatch(render_commands);
|
||||
|
||||
@@ -33,7 +33,7 @@ struct TileCache {
|
||||
static constexpr size_t MAX_CACHE_SIZE = 1024;
|
||||
std::unordered_map<int, Bitmap> cache_;
|
||||
std::list<int> access_order_;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Get a cached tile by ID
|
||||
* @param tile_id Tile identifier
|
||||
@@ -49,7 +49,7 @@ struct TileCache {
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Cache a tile bitmap
|
||||
* @param tile_id Tile identifier
|
||||
@@ -62,11 +62,11 @@ struct TileCache {
|
||||
access_order_.pop_back();
|
||||
cache_.erase(lru_tile);
|
||||
}
|
||||
|
||||
|
||||
cache_[tile_id] = std::move(bitmap);
|
||||
access_order_.push_front(tile_id);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Clear the cache
|
||||
*/
|
||||
@@ -74,7 +74,7 @@ struct TileCache {
|
||||
cache_.clear();
|
||||
access_order_.clear();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Get cache statistics
|
||||
* @return Number of cached tiles
|
||||
@@ -107,37 +107,40 @@ struct TileCache {
|
||||
* - Integration with SNES graphics buffer format
|
||||
*/
|
||||
struct Tilemap {
|
||||
Bitmap atlas; ///< Master bitmap containing all tiles
|
||||
TileCache tile_cache; ///< Smart tile cache with LRU eviction
|
||||
std::vector<std::array<gfx::TileInfo, 4>> tile_info; ///< Tile metadata (4 tiles per 16x16)
|
||||
Pair tile_size; ///< Size of individual tiles (8x8 or 16x16)
|
||||
Pair map_size; ///< Size of tilemap in tiles
|
||||
Bitmap atlas; ///< Master bitmap containing all tiles
|
||||
TileCache tile_cache; ///< Smart tile cache with LRU eviction
|
||||
std::vector<std::array<gfx::TileInfo, 4>>
|
||||
tile_info; ///< Tile metadata (4 tiles per 16x16)
|
||||
Pair tile_size; ///< Size of individual tiles (8x8 or 16x16)
|
||||
Pair map_size; ///< Size of tilemap in tiles
|
||||
};
|
||||
|
||||
std::vector<uint8_t> FetchTileDataFromGraphicsBuffer(
|
||||
const std::vector<uint8_t> &data, int tile_id, int sheet_offset);
|
||||
const std::vector<uint8_t>& data, int tile_id, int sheet_offset);
|
||||
|
||||
Tilemap CreateTilemap(IRenderer* renderer, std::vector<uint8_t> &data, int width, int height,
|
||||
int tile_size, int num_tiles, SnesPalette &palette);
|
||||
Tilemap CreateTilemap(IRenderer* renderer, std::vector<uint8_t>& data,
|
||||
int width, int height, int tile_size, int num_tiles,
|
||||
SnesPalette& palette);
|
||||
|
||||
void UpdateTilemap(IRenderer* renderer, Tilemap &tilemap, const std::vector<uint8_t> &data);
|
||||
void UpdateTilemap(IRenderer* renderer, Tilemap& tilemap,
|
||||
const std::vector<uint8_t>& data);
|
||||
|
||||
void RenderTile(IRenderer* renderer, Tilemap &tilemap, int tile_id);
|
||||
void RenderTile(IRenderer* renderer, Tilemap& tilemap, int tile_id);
|
||||
|
||||
void RenderTile16(IRenderer* renderer, Tilemap &tilemap, int tile_id);
|
||||
void UpdateTile16(IRenderer* renderer, Tilemap &tilemap, int tile_id);
|
||||
void RenderTile16(IRenderer* renderer, Tilemap& tilemap, int tile_id);
|
||||
void UpdateTile16(IRenderer* renderer, Tilemap& tilemap, int tile_id);
|
||||
|
||||
void ModifyTile16(Tilemap &tilemap, const std::vector<uint8_t> &data,
|
||||
const TileInfo &top_left, const TileInfo &top_right,
|
||||
const TileInfo &bottom_left, const TileInfo &bottom_right,
|
||||
int sheet_offset, int tile_id);
|
||||
void ModifyTile16(Tilemap& tilemap, const std::vector<uint8_t>& data,
|
||||
const TileInfo& top_left, const TileInfo& top_right,
|
||||
const TileInfo& bottom_left, const TileInfo& bottom_right,
|
||||
int sheet_offset, int tile_id);
|
||||
|
||||
void ComposeTile16(Tilemap &tilemap, const std::vector<uint8_t> &data,
|
||||
const TileInfo &top_left, const TileInfo &top_right,
|
||||
const TileInfo &bottom_left, const TileInfo &bottom_right,
|
||||
void ComposeTile16(Tilemap& tilemap, const std::vector<uint8_t>& data,
|
||||
const TileInfo& top_left, const TileInfo& top_right,
|
||||
const TileInfo& bottom_left, const TileInfo& bottom_right,
|
||||
int sheet_offset);
|
||||
|
||||
std::vector<uint8_t> GetTilemapData(Tilemap &tilemap, int tile_id);
|
||||
std::vector<uint8_t> GetTilemapData(Tilemap& tilemap, int tile_id);
|
||||
|
||||
/**
|
||||
* @brief Render multiple tiles using atlas rendering for improved performance
|
||||
@@ -147,7 +150,8 @@ std::vector<uint8_t> GetTilemapData(Tilemap &tilemap, int tile_id);
|
||||
* @param scales Vector of scale factors for each tile (optional, defaults to 1.0)
|
||||
* @note This function uses atlas rendering to reduce draw calls significantly
|
||||
*/
|
||||
void RenderTilesBatch(IRenderer* renderer, Tilemap& tilemap, const std::vector<int>& tile_ids,
|
||||
void RenderTilesBatch(IRenderer* renderer, Tilemap& tilemap,
|
||||
const std::vector<int>& tile_ids,
|
||||
const std::vector<std::pair<float, float>>& positions,
|
||||
const std::vector<std::pair<float, float>>& scales = {});
|
||||
|
||||
|
||||
@@ -10,7 +10,9 @@
|
||||
namespace yaze {
|
||||
namespace gfx {
|
||||
|
||||
void Arena::Initialize(IRenderer* renderer) { renderer_ = renderer; }
|
||||
void Arena::Initialize(IRenderer* renderer) {
|
||||
renderer_ = renderer;
|
||||
}
|
||||
|
||||
Arena& Arena::Get() {
|
||||
static Arena instance;
|
||||
@@ -27,8 +29,6 @@ Arena::~Arena() {
|
||||
Shutdown();
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Arena::QueueTextureCommand(TextureCommandType type, Bitmap* bitmap) {
|
||||
texture_command_queue_.push_back({type, bitmap});
|
||||
}
|
||||
@@ -36,12 +36,12 @@ void Arena::QueueTextureCommand(TextureCommandType type, Bitmap* bitmap) {
|
||||
void Arena::ProcessTextureQueue(IRenderer* renderer) {
|
||||
// Use provided renderer if available, otherwise use stored renderer
|
||||
IRenderer* active_renderer = renderer ? renderer : renderer_;
|
||||
|
||||
|
||||
if (!active_renderer) {
|
||||
// Arena not initialized yet - defer processing
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (texture_command_queue_.empty()) {
|
||||
return;
|
||||
}
|
||||
@@ -50,28 +50,28 @@ void Arena::ProcessTextureQueue(IRenderer* renderer) {
|
||||
// Process up to 8 texture operations per frame to avoid frame drops
|
||||
constexpr size_t kMaxTexturesPerFrame = 8;
|
||||
size_t processed = 0;
|
||||
|
||||
|
||||
auto it = texture_command_queue_.begin();
|
||||
while (it != texture_command_queue_.end() && processed < kMaxTexturesPerFrame) {
|
||||
while (it != texture_command_queue_.end() &&
|
||||
processed < kMaxTexturesPerFrame) {
|
||||
const auto& command = *it;
|
||||
bool should_remove = true;
|
||||
|
||||
|
||||
// CRITICAL: Replicate the exact short-circuit evaluation from working code
|
||||
// We MUST check command.bitmap AND command.bitmap->surface() in one expression
|
||||
// to avoid dereferencing invalid pointers
|
||||
|
||||
|
||||
switch (command.type) {
|
||||
case TextureCommandType::CREATE: {
|
||||
// Create a new texture and update it with bitmap data
|
||||
// Use short-circuit evaluation - if bitmap is invalid, never call ->surface()
|
||||
if (command.bitmap && command.bitmap->surface() &&
|
||||
command.bitmap->surface()->format &&
|
||||
command.bitmap->is_active() &&
|
||||
command.bitmap->surface()->format && command.bitmap->is_active() &&
|
||||
command.bitmap->width() > 0 && command.bitmap->height() > 0) {
|
||||
|
||||
|
||||
try {
|
||||
auto texture = active_renderer->CreateTexture(command.bitmap->width(),
|
||||
command.bitmap->height());
|
||||
auto texture = active_renderer->CreateTexture(
|
||||
command.bitmap->width(), command.bitmap->height());
|
||||
if (texture) {
|
||||
command.bitmap->set_texture(texture);
|
||||
active_renderer->UpdateTexture(texture, *command.bitmap);
|
||||
@@ -88,11 +88,11 @@ void Arena::ProcessTextureQueue(IRenderer* renderer) {
|
||||
}
|
||||
case TextureCommandType::UPDATE: {
|
||||
// Update existing texture with current bitmap data
|
||||
if (command.bitmap->texture() &&
|
||||
command.bitmap->surface() && command.bitmap->surface()->format &&
|
||||
command.bitmap->is_active()) {
|
||||
if (command.bitmap->texture() && command.bitmap->surface() &&
|
||||
command.bitmap->surface()->format && command.bitmap->is_active()) {
|
||||
try {
|
||||
active_renderer->UpdateTexture(command.bitmap->texture(), *command.bitmap);
|
||||
active_renderer->UpdateTexture(command.bitmap->texture(),
|
||||
*command.bitmap);
|
||||
processed++;
|
||||
} catch (...) {
|
||||
LOG_ERROR("Arena", "Exception during texture update");
|
||||
@@ -113,7 +113,7 @@ void Arena::ProcessTextureQueue(IRenderer* renderer) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (should_remove) {
|
||||
it = texture_command_queue_.erase(it);
|
||||
} else {
|
||||
@@ -122,12 +122,13 @@ void Arena::ProcessTextureQueue(IRenderer* renderer) {
|
||||
}
|
||||
}
|
||||
|
||||
SDL_Surface* Arena::AllocateSurface(int width, int height, int depth, int format) {
|
||||
SDL_Surface* Arena::AllocateSurface(int width, int height, int depth,
|
||||
int format) {
|
||||
// Try to get a surface from the pool first
|
||||
for (auto it = surface_pool_.available_surfaces_.begin();
|
||||
for (auto it = surface_pool_.available_surfaces_.begin();
|
||||
it != surface_pool_.available_surfaces_.end(); ++it) {
|
||||
auto& info = surface_pool_.surface_info_[*it];
|
||||
if (std::get<0>(info) == width && std::get<1>(info) == height &&
|
||||
if (std::get<0>(info) == width && std::get<1>(info) == height &&
|
||||
std::get<2>(info) == depth && std::get<3>(info) == format) {
|
||||
SDL_Surface* surface = *it;
|
||||
surface_pool_.available_surfaces_.erase(it);
|
||||
@@ -137,20 +138,24 @@ SDL_Surface* Arena::AllocateSurface(int width, int height, int depth, int format
|
||||
|
||||
// Create new surface if none available in pool
|
||||
Uint32 sdl_format = GetSnesPixelFormat(format);
|
||||
SDL_Surface* surface = SDL_CreateRGBSurfaceWithFormat(0, width, height, depth, sdl_format);
|
||||
|
||||
SDL_Surface* surface =
|
||||
SDL_CreateRGBSurfaceWithFormat(0, width, height, depth, sdl_format);
|
||||
|
||||
if (surface) {
|
||||
auto surface_ptr = std::unique_ptr<SDL_Surface, util::SDL_Surface_Deleter>(surface);
|
||||
auto surface_ptr =
|
||||
std::unique_ptr<SDL_Surface, util::SDL_Surface_Deleter>(surface);
|
||||
surfaces_[surface] = std::move(surface_ptr);
|
||||
surface_pool_.surface_info_[surface] = std::make_tuple(width, height, depth, format);
|
||||
surface_pool_.surface_info_[surface] =
|
||||
std::make_tuple(width, height, depth, format);
|
||||
}
|
||||
|
||||
|
||||
return surface;
|
||||
}
|
||||
|
||||
void Arena::FreeSurface(SDL_Surface* surface) {
|
||||
if (!surface) return;
|
||||
|
||||
if (!surface)
|
||||
return;
|
||||
|
||||
// Return surface to pool if space available
|
||||
if (surface_pool_.available_surfaces_.size() < surface_pool_.MAX_POOL_SIZE) {
|
||||
surface_pool_.available_surfaces_.push_back(surface);
|
||||
@@ -164,45 +169,49 @@ void Arena::FreeSurface(SDL_Surface* surface) {
|
||||
void Arena::Shutdown() {
|
||||
// Process any remaining batch updates before shutdown
|
||||
ProcessTextureQueue(renderer_);
|
||||
|
||||
|
||||
// Clear pool references first to prevent reuse during shutdown
|
||||
surface_pool_.available_surfaces_.clear();
|
||||
surface_pool_.surface_info_.clear();
|
||||
texture_pool_.available_textures_.clear();
|
||||
texture_pool_.texture_sizes_.clear();
|
||||
|
||||
|
||||
// CRITICAL FIX: Clear containers in reverse order to prevent cleanup issues
|
||||
// This ensures that dependent resources are freed before their dependencies
|
||||
textures_.clear();
|
||||
surfaces_.clear();
|
||||
|
||||
|
||||
// Clear any remaining queue items
|
||||
texture_command_queue_.clear();
|
||||
}
|
||||
|
||||
void Arena::NotifySheetModified(int sheet_index) {
|
||||
if (sheet_index < 0 || sheet_index >= 223) {
|
||||
LOG_WARN("Arena", "Invalid sheet index %d, ignoring notification", sheet_index);
|
||||
LOG_WARN("Arena", "Invalid sheet index %d, ignoring notification",
|
||||
sheet_index);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
auto& sheet = gfx_sheets_[sheet_index];
|
||||
if (!sheet.is_active() || !sheet.surface()) {
|
||||
LOG_DEBUG("Arena", "Sheet %d not active or no surface, skipping notification", sheet_index);
|
||||
LOG_DEBUG("Arena",
|
||||
"Sheet %d not active or no surface, skipping notification",
|
||||
sheet_index);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Queue texture update so changes are visible in all editors
|
||||
if (sheet.texture()) {
|
||||
QueueTextureCommand(TextureCommandType::UPDATE, &sheet);
|
||||
LOG_DEBUG("Arena", "Queued texture update for modified sheet %d", sheet_index);
|
||||
LOG_DEBUG("Arena", "Queued texture update for modified sheet %d",
|
||||
sheet_index);
|
||||
} else {
|
||||
// Create texture if it doesn't exist
|
||||
QueueTextureCommand(TextureCommandType::CREATE, &sheet);
|
||||
LOG_DEBUG("Arena", "Queued texture creation for modified sheet %d", sheet_index);
|
||||
LOG_DEBUG("Arena", "Queued texture creation for modified sheet %d",
|
||||
sheet_index);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} // namespace gfx
|
||||
} // namespace yaze
|
||||
@@ -9,9 +9,9 @@
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "util/sdl_deleter.h"
|
||||
#include "app/gfx/render/background_buffer.h"
|
||||
#include "app/gfx/core/bitmap.h"
|
||||
#include "app/gfx/render/background_buffer.h"
|
||||
#include "util/sdl_deleter.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace gfx {
|
||||
@@ -52,7 +52,7 @@ class Arena {
|
||||
enum class TextureCommandType { CREATE, UPDATE, DESTROY };
|
||||
struct TextureCommand {
|
||||
TextureCommandType type;
|
||||
Bitmap* bitmap; // The bitmap that needs a texture operation
|
||||
Bitmap* bitmap; // The bitmap that needs a texture operation
|
||||
};
|
||||
|
||||
void QueueTextureCommand(TextureCommandType type, Bitmap* bitmap);
|
||||
@@ -61,14 +61,18 @@ class Arena {
|
||||
// --- Surface Management (unchanged) ---
|
||||
SDL_Surface* AllocateSurface(int width, int height, int depth, int format);
|
||||
void FreeSurface(SDL_Surface* surface);
|
||||
|
||||
|
||||
void Shutdown();
|
||||
|
||||
|
||||
// Resource tracking for debugging
|
||||
size_t GetTextureCount() const { return textures_.size(); }
|
||||
size_t GetSurfaceCount() const { return surfaces_.size(); }
|
||||
size_t GetPooledTextureCount() const { return texture_pool_.available_textures_.size(); }
|
||||
size_t GetPooledSurfaceCount() const { return surface_pool_.available_surfaces_.size(); }
|
||||
size_t GetPooledTextureCount() const {
|
||||
return texture_pool_.available_textures_.size();
|
||||
}
|
||||
size_t GetPooledSurfaceCount() const {
|
||||
return surface_pool_.available_surfaces_.size();
|
||||
}
|
||||
|
||||
// Graphics sheet access (223 total sheets in YAZE)
|
||||
/**
|
||||
@@ -76,27 +80,27 @@ class Arena {
|
||||
* @return Reference to array of 223 Bitmap objects
|
||||
*/
|
||||
std::array<gfx::Bitmap, 223>& gfx_sheets() { return gfx_sheets_; }
|
||||
|
||||
|
||||
/**
|
||||
* @brief Get a specific graphics sheet by index
|
||||
* @param i Sheet index (0-222)
|
||||
* @return Copy of the Bitmap at index i
|
||||
*/
|
||||
auto gfx_sheet(int i) { return gfx_sheets_[i]; }
|
||||
|
||||
|
||||
/**
|
||||
* @brief Get mutable reference to a specific graphics sheet
|
||||
* @param i Sheet index (0-222)
|
||||
* @return Pointer to mutable Bitmap at index i
|
||||
*/
|
||||
auto mutable_gfx_sheet(int i) { return &gfx_sheets_[i]; }
|
||||
|
||||
|
||||
/**
|
||||
* @brief Get mutable reference to all graphics sheets
|
||||
* @return Pointer to mutable array of 223 Bitmap objects
|
||||
*/
|
||||
auto mutable_gfx_sheets() { return &gfx_sheets_; }
|
||||
|
||||
|
||||
/**
|
||||
* @brief Notify Arena that a graphics sheet has been modified
|
||||
* @param sheet_index Index of the modified sheet (0-222)
|
||||
@@ -110,7 +114,7 @@ class Arena {
|
||||
* @return Reference to BackgroundBuffer for layer 1
|
||||
*/
|
||||
auto& bg1() { return bg1_; }
|
||||
|
||||
|
||||
/**
|
||||
* @brief Get reference to background layer 2 buffer
|
||||
* @return Reference to BackgroundBuffer for layer 2
|
||||
@@ -149,7 +153,8 @@ class Arena {
|
||||
|
||||
struct SurfacePool {
|
||||
std::vector<SDL_Surface*> available_surfaces_;
|
||||
std::unordered_map<SDL_Surface*, std::tuple<int, int, int, int>> surface_info_;
|
||||
std::unordered_map<SDL_Surface*, std::tuple<int, int, int, int>>
|
||||
surface_info_;
|
||||
static constexpr size_t MAX_POOL_SIZE = 100;
|
||||
} surface_pool_;
|
||||
|
||||
|
||||
@@ -14,17 +14,23 @@ MemoryPool& MemoryPool::Get() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
MemoryPool::MemoryPool()
|
||||
: total_allocations_(0), total_deallocations_(0),
|
||||
total_used_bytes_(0), total_allocated_bytes_(0) {
|
||||
MemoryPool::MemoryPool()
|
||||
: total_allocations_(0),
|
||||
total_deallocations_(0),
|
||||
total_used_bytes_(0),
|
||||
total_allocated_bytes_(0) {
|
||||
// Initialize block pools with common graphics sizes
|
||||
InitializeBlockPool(small_blocks_, kSmallBlockSize, 100); // 100KB for small tiles
|
||||
InitializeBlockPool(medium_blocks_, kMediumBlockSize, 50); // 200KB for medium tiles
|
||||
InitializeBlockPool(large_blocks_, kLargeBlockSize, 20); // 320KB for large tiles
|
||||
InitializeBlockPool(huge_blocks_, kHugeBlockSize, 10); // 640KB for graphics sheets
|
||||
|
||||
total_allocated_bytes_ = (100 * kSmallBlockSize) + (50 * kMediumBlockSize) +
|
||||
(20 * kLargeBlockSize) + (10 * kHugeBlockSize);
|
||||
InitializeBlockPool(small_blocks_, kSmallBlockSize,
|
||||
100); // 100KB for small tiles
|
||||
InitializeBlockPool(medium_blocks_, kMediumBlockSize,
|
||||
50); // 200KB for medium tiles
|
||||
InitializeBlockPool(large_blocks_, kLargeBlockSize,
|
||||
20); // 320KB for large tiles
|
||||
InitializeBlockPool(huge_blocks_, kHugeBlockSize,
|
||||
10); // 640KB for graphics sheets
|
||||
|
||||
total_allocated_bytes_ = (100 * kSmallBlockSize) + (50 * kMediumBlockSize) +
|
||||
(20 * kLargeBlockSize) + (10 * kHugeBlockSize);
|
||||
}
|
||||
|
||||
MemoryPool::~MemoryPool() {
|
||||
@@ -33,43 +39,44 @@ MemoryPool::~MemoryPool() {
|
||||
|
||||
void* MemoryPool::Allocate(size_t size) {
|
||||
total_allocations_++;
|
||||
|
||||
|
||||
MemoryBlock* block = FindFreeBlock(size);
|
||||
if (!block) {
|
||||
// Fallback to system malloc if no pool block available
|
||||
void* data = std::malloc(size);
|
||||
if (data) {
|
||||
total_used_bytes_ += size;
|
||||
allocated_blocks_[data] = nullptr; // Mark as system allocated
|
||||
allocated_blocks_[data] = nullptr; // Mark as system allocated
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
block->in_use = true;
|
||||
total_used_bytes_ += block->size;
|
||||
allocated_blocks_[block->data] = block;
|
||||
|
||||
|
||||
return block->data;
|
||||
}
|
||||
|
||||
void MemoryPool::Deallocate(void* ptr) {
|
||||
if (!ptr) return;
|
||||
|
||||
if (!ptr)
|
||||
return;
|
||||
|
||||
total_deallocations_++;
|
||||
|
||||
|
||||
auto it = allocated_blocks_.find(ptr);
|
||||
if (it == allocated_blocks_.end()) {
|
||||
// System allocated, use free
|
||||
std::free(ptr);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
MemoryBlock* block = it->second;
|
||||
if (block) {
|
||||
block->in_use = false;
|
||||
total_used_bytes_ -= block->size;
|
||||
}
|
||||
|
||||
|
||||
allocated_blocks_.erase(it);
|
||||
}
|
||||
|
||||
@@ -78,13 +85,13 @@ void* MemoryPool::AllocateAligned(size_t size, size_t alignment) {
|
||||
// In a production system, you'd want more sophisticated alignment handling
|
||||
size_t aligned_size = size + alignment - 1;
|
||||
void* ptr = Allocate(aligned_size);
|
||||
|
||||
|
||||
if (ptr) {
|
||||
uintptr_t addr = reinterpret_cast<uintptr_t>(ptr);
|
||||
uintptr_t aligned_addr = (addr + alignment - 1) & ~(alignment - 1);
|
||||
return reinterpret_cast<void*>(aligned_addr);
|
||||
}
|
||||
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -110,7 +117,7 @@ void MemoryPool::Clear() {
|
||||
for (auto& block : huge_blocks_) {
|
||||
block.in_use = false;
|
||||
}
|
||||
|
||||
|
||||
allocated_blocks_.clear();
|
||||
total_used_bytes_ = 0;
|
||||
}
|
||||
@@ -118,28 +125,28 @@ void MemoryPool::Clear() {
|
||||
MemoryBlock* MemoryPool::FindFreeBlock(size_t size) {
|
||||
// Determine which pool to use based on size
|
||||
size_t pool_index = GetPoolIndex(size);
|
||||
|
||||
std::vector<MemoryBlock>* pools[] = {
|
||||
&small_blocks_, &medium_blocks_, &large_blocks_, &huge_blocks_
|
||||
};
|
||||
|
||||
|
||||
std::vector<MemoryBlock>* pools[] = {&small_blocks_, &medium_blocks_,
|
||||
&large_blocks_, &huge_blocks_};
|
||||
|
||||
if (pool_index >= 4) {
|
||||
return nullptr; // Size too large for any pool
|
||||
return nullptr; // Size too large for any pool
|
||||
}
|
||||
|
||||
|
||||
auto& pool = *pools[pool_index];
|
||||
|
||||
|
||||
// Find first unused block
|
||||
auto it = std::find_if(pool.begin(), pool.end(),
|
||||
[](const MemoryBlock& block) { return !block.in_use; });
|
||||
|
||||
auto it =
|
||||
std::find_if(pool.begin(), pool.end(),
|
||||
[](const MemoryBlock& block) { return !block.in_use; });
|
||||
|
||||
return (it != pool.end()) ? &(*it) : nullptr;
|
||||
}
|
||||
|
||||
void MemoryPool::InitializeBlockPool(std::vector<MemoryBlock>& pool,
|
||||
size_t block_size, size_t count) {
|
||||
void MemoryPool::InitializeBlockPool(std::vector<MemoryBlock>& pool,
|
||||
size_t block_size, size_t count) {
|
||||
pool.reserve(count);
|
||||
|
||||
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
void* data = std::malloc(block_size);
|
||||
if (data) {
|
||||
@@ -149,11 +156,15 @@ void MemoryPool::InitializeBlockPool(std::vector<MemoryBlock>& pool,
|
||||
}
|
||||
|
||||
size_t MemoryPool::GetPoolIndex(size_t size) const {
|
||||
if (size <= kSmallBlockSize) return 0;
|
||||
if (size <= kMediumBlockSize) return 1;
|
||||
if (size <= kLargeBlockSize) return 2;
|
||||
if (size <= kHugeBlockSize) return 3;
|
||||
return 4; // Too large for any pool
|
||||
if (size <= kSmallBlockSize)
|
||||
return 0;
|
||||
if (size <= kMediumBlockSize)
|
||||
return 1;
|
||||
if (size <= kLargeBlockSize)
|
||||
return 2;
|
||||
if (size <= kHugeBlockSize)
|
||||
return 3;
|
||||
return 4; // Too large for any pool
|
||||
}
|
||||
|
||||
} // namespace gfx
|
||||
|
||||
@@ -120,16 +120,14 @@ void SnesColor::set_rgb(const ImVec4 val) {
|
||||
void SnesColor::set_snes(uint16_t val) {
|
||||
// Store SNES 15-bit color
|
||||
snes_ = val;
|
||||
|
||||
|
||||
// Convert SNES to RGB (0-255)
|
||||
snes_color col = ConvertSnesToRgb(val);
|
||||
|
||||
|
||||
// Store 0-255 values in ImVec4 (unconventional but our internal format)
|
||||
rgb_ = ImVec4(static_cast<float>(col.red),
|
||||
static_cast<float>(col.green),
|
||||
static_cast<float>(col.blue),
|
||||
kColorByteMaxF);
|
||||
|
||||
rgb_ = ImVec4(static_cast<float>(col.red), static_cast<float>(col.green),
|
||||
static_cast<float>(col.blue), kColorByteMaxF);
|
||||
|
||||
rom_color_ = col;
|
||||
modified = true;
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ constexpr int NumberOfColors = 3143;
|
||||
// ============================================================================
|
||||
// SNES Color Conversion Functions
|
||||
// ============================================================================
|
||||
//
|
||||
//
|
||||
// Color Format Guide:
|
||||
// - SNES Color (uint16_t): 15-bit BGR format (0bbbbbgggggrrrrr)
|
||||
// - snes_color struct: RGB values in 0-255 range
|
||||
@@ -55,8 +55,8 @@ uint16_t ConvertRgbToSnes(const ImVec4& color);
|
||||
* @return ImVec4 with RGBA values in 0.0-1.0 range
|
||||
*/
|
||||
inline ImVec4 SnesColorToImVec4(const snes_color& color) {
|
||||
return ImVec4(color.red / 255.0f, color.green / 255.0f,
|
||||
color.blue / 255.0f, 1.0f);
|
||||
return ImVec4(color.red / 255.0f, color.green / 255.0f, color.blue / 255.0f,
|
||||
1.0f);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -168,7 +168,7 @@ class SnesColor {
|
||||
* @param val ImVec4 with RGB in standard 0.0-1.0 range
|
||||
*/
|
||||
void set_rgb(const ImVec4 val);
|
||||
|
||||
|
||||
/**
|
||||
* @brief Set color from SNES 15-bit format
|
||||
* @param val SNES color in 15-bit BGR format
|
||||
@@ -180,25 +180,25 @@ class SnesColor {
|
||||
* @return ImVec4 with RGB in 0-255 range (unconventional!)
|
||||
*/
|
||||
constexpr ImVec4 rgb() const { return rgb_; }
|
||||
|
||||
|
||||
/**
|
||||
* @brief Get snes_color struct (0-255 RGB)
|
||||
*/
|
||||
constexpr snes_color rom_color() const { return rom_color_; }
|
||||
|
||||
|
||||
/**
|
||||
* @brief Get SNES 15-bit color
|
||||
*/
|
||||
constexpr uint16_t snes() const { return snes_; }
|
||||
|
||||
|
||||
constexpr bool is_modified() const { return modified; }
|
||||
constexpr bool is_transparent() const { return transparent; }
|
||||
constexpr void set_transparent(bool t) { transparent = t; }
|
||||
constexpr void set_modified(bool m) { modified = m; }
|
||||
|
||||
private:
|
||||
ImVec4 rgb_; // Stores 0-255 values (unconventional!)
|
||||
uint16_t snes_; // 15-bit SNES format
|
||||
ImVec4 rgb_; // Stores 0-255 values (unconventional!)
|
||||
uint16_t snes_; // 15-bit SNES format
|
||||
snes_color rom_color_; // 0-255 RGB struct
|
||||
bool modified = false;
|
||||
bool transparent = false;
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
namespace yaze::gfx {
|
||||
|
||||
SnesPalette::SnesPalette(char *data) {
|
||||
SnesPalette::SnesPalette(char* data) {
|
||||
assert((sizeof(data) % 4 == 0) && (sizeof(data) <= 32));
|
||||
for (unsigned i = 0; i < sizeof(data); i += 2) {
|
||||
SnesColor col;
|
||||
@@ -28,7 +28,7 @@ SnesPalette::SnesPalette(char *data) {
|
||||
}
|
||||
}
|
||||
|
||||
SnesPalette::SnesPalette(const unsigned char *snes_pal) {
|
||||
SnesPalette::SnesPalette(const unsigned char* snes_pal) {
|
||||
assert((sizeof(snes_pal) % 4 == 0) && (sizeof(snes_pal) <= 32));
|
||||
for (unsigned i = 0; i < sizeof(snes_pal); i += 2) {
|
||||
SnesColor col;
|
||||
@@ -40,7 +40,7 @@ SnesPalette::SnesPalette(const unsigned char *snes_pal) {
|
||||
}
|
||||
}
|
||||
|
||||
SnesPalette::SnesPalette(const char *data, size_t length) : size_(0) {
|
||||
SnesPalette::SnesPalette(const char* data, size_t length) : size_(0) {
|
||||
for (size_t i = 0; i < length && size_ < kMaxColors; i += 2) {
|
||||
uint16_t color = (static_cast<uint8_t>(data[i + 1]) << 8) |
|
||||
static_cast<uint8_t>(data[i]);
|
||||
@@ -48,24 +48,24 @@ SnesPalette::SnesPalette(const char *data, size_t length) : size_(0) {
|
||||
}
|
||||
}
|
||||
|
||||
SnesPalette::SnesPalette(const std::vector<uint16_t> &colors) : size_(0) {
|
||||
for (const auto &color : colors) {
|
||||
SnesPalette::SnesPalette(const std::vector<uint16_t>& colors) : size_(0) {
|
||||
for (const auto& color : colors) {
|
||||
if (size_ < kMaxColors) {
|
||||
colors_[size_++] = SnesColor(color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SnesPalette::SnesPalette(const std::vector<SnesColor> &colors) : size_(0) {
|
||||
for (const auto &color : colors) {
|
||||
SnesPalette::SnesPalette(const std::vector<SnesColor>& colors) : size_(0) {
|
||||
for (const auto& color : colors) {
|
||||
if (size_ < kMaxColors) {
|
||||
colors_[size_++] = color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SnesPalette::SnesPalette(const std::vector<ImVec4> &colors) : size_(0) {
|
||||
for (const auto &color : colors) {
|
||||
SnesPalette::SnesPalette(const std::vector<ImVec4>& colors) : size_(0) {
|
||||
for (const auto& color : colors) {
|
||||
if (size_ < kMaxColors) {
|
||||
colors_[size_++] = SnesColor(color);
|
||||
}
|
||||
@@ -77,8 +77,8 @@ SnesPalette::SnesPalette(const std::vector<ImVec4> &colors) : size_(0) {
|
||||
* @brief Internal functions for loading palettes by group.
|
||||
*/
|
||||
namespace palette_group_internal {
|
||||
absl::Status LoadOverworldMainPalettes(const std::vector<uint8_t> &rom_data,
|
||||
gfx::PaletteGroupMap &palette_groups) {
|
||||
absl::Status LoadOverworldMainPalettes(const std::vector<uint8_t>& rom_data,
|
||||
gfx::PaletteGroupMap& palette_groups) {
|
||||
auto data = rom_data.data();
|
||||
for (int i = 0; i < 6; i++) {
|
||||
palette_groups.overworld_main.AddPalette(
|
||||
@@ -89,8 +89,8 @@ absl::Status LoadOverworldMainPalettes(const std::vector<uint8_t> &rom_data,
|
||||
}
|
||||
|
||||
absl::Status LoadOverworldAuxiliaryPalettes(
|
||||
const std::vector<uint8_t> &rom_data,
|
||||
gfx::PaletteGroupMap &palette_groups) {
|
||||
const std::vector<uint8_t>& rom_data,
|
||||
gfx::PaletteGroupMap& palette_groups) {
|
||||
auto data = rom_data.data();
|
||||
for (int i = 0; i < 20; i++) {
|
||||
palette_groups.overworld_aux.AddPalette(
|
||||
@@ -101,8 +101,8 @@ absl::Status LoadOverworldAuxiliaryPalettes(
|
||||
}
|
||||
|
||||
absl::Status LoadOverworldAnimatedPalettes(
|
||||
const std::vector<uint8_t> &rom_data,
|
||||
gfx::PaletteGroupMap &palette_groups) {
|
||||
const std::vector<uint8_t>& rom_data,
|
||||
gfx::PaletteGroupMap& palette_groups) {
|
||||
auto data = rom_data.data();
|
||||
for (int i = 0; i < 14; i++) {
|
||||
palette_groups.overworld_animated.AddPalette(gfx::ReadPaletteFromRom(
|
||||
@@ -111,8 +111,8 @@ absl::Status LoadOverworldAnimatedPalettes(
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status LoadHUDPalettes(const std::vector<uint8_t> &rom_data,
|
||||
gfx::PaletteGroupMap &palette_groups) {
|
||||
absl::Status LoadHUDPalettes(const std::vector<uint8_t>& rom_data,
|
||||
gfx::PaletteGroupMap& palette_groups) {
|
||||
auto data = rom_data.data();
|
||||
for (int i = 0; i < 2; i++) {
|
||||
palette_groups.hud.AddPalette(gfx::ReadPaletteFromRom(
|
||||
@@ -121,8 +121,8 @@ absl::Status LoadHUDPalettes(const std::vector<uint8_t> &rom_data,
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status LoadGlobalSpritePalettes(const std::vector<uint8_t> &rom_data,
|
||||
gfx::PaletteGroupMap &palette_groups) {
|
||||
absl::Status LoadGlobalSpritePalettes(const std::vector<uint8_t>& rom_data,
|
||||
gfx::PaletteGroupMap& palette_groups) {
|
||||
auto data = rom_data.data();
|
||||
palette_groups.global_sprites.AddPalette(
|
||||
gfx::ReadPaletteFromRom(kGlobalSpritesLW, /*num_colors=*/60, data));
|
||||
@@ -131,8 +131,8 @@ absl::Status LoadGlobalSpritePalettes(const std::vector<uint8_t> &rom_data,
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status LoadArmorPalettes(const std::vector<uint8_t> &rom_data,
|
||||
gfx::PaletteGroupMap &palette_groups) {
|
||||
absl::Status LoadArmorPalettes(const std::vector<uint8_t>& rom_data,
|
||||
gfx::PaletteGroupMap& palette_groups) {
|
||||
auto data = rom_data.data();
|
||||
for (int i = 0; i < 5; i++) {
|
||||
palette_groups.armors.AddPalette(gfx::ReadPaletteFromRom(
|
||||
@@ -141,8 +141,8 @@ absl::Status LoadArmorPalettes(const std::vector<uint8_t> &rom_data,
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status LoadSwordPalettes(const std::vector<uint8_t> &rom_data,
|
||||
gfx::PaletteGroupMap &palette_groups) {
|
||||
absl::Status LoadSwordPalettes(const std::vector<uint8_t>& rom_data,
|
||||
gfx::PaletteGroupMap& palette_groups) {
|
||||
auto data = rom_data.data();
|
||||
for (int i = 0; i < 4; i++) {
|
||||
palette_groups.swords.AddPalette(gfx::ReadPaletteFromRom(
|
||||
@@ -151,8 +151,8 @@ absl::Status LoadSwordPalettes(const std::vector<uint8_t> &rom_data,
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status LoadShieldPalettes(const std::vector<uint8_t> &rom_data,
|
||||
gfx::PaletteGroupMap &palette_groups) {
|
||||
absl::Status LoadShieldPalettes(const std::vector<uint8_t>& rom_data,
|
||||
gfx::PaletteGroupMap& palette_groups) {
|
||||
auto data = rom_data.data();
|
||||
for (int i = 0; i < 3; i++) {
|
||||
palette_groups.shields.AddPalette(gfx::ReadPaletteFromRom(
|
||||
@@ -161,8 +161,8 @@ absl::Status LoadShieldPalettes(const std::vector<uint8_t> &rom_data,
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status LoadSpriteAux1Palettes(const std::vector<uint8_t> &rom_data,
|
||||
gfx::PaletteGroupMap &palette_groups) {
|
||||
absl::Status LoadSpriteAux1Palettes(const std::vector<uint8_t>& rom_data,
|
||||
gfx::PaletteGroupMap& palette_groups) {
|
||||
auto data = rom_data.data();
|
||||
for (int i = 0; i < 12; i++) {
|
||||
palette_groups.sprites_aux1.AddPalette(gfx::ReadPaletteFromRom(
|
||||
@@ -171,8 +171,8 @@ absl::Status LoadSpriteAux1Palettes(const std::vector<uint8_t> &rom_data,
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status LoadSpriteAux2Palettes(const std::vector<uint8_t> &rom_data,
|
||||
gfx::PaletteGroupMap &palette_groups) {
|
||||
absl::Status LoadSpriteAux2Palettes(const std::vector<uint8_t>& rom_data,
|
||||
gfx::PaletteGroupMap& palette_groups) {
|
||||
auto data = rom_data.data();
|
||||
for (int i = 0; i < 11; i++) {
|
||||
palette_groups.sprites_aux2.AddPalette(gfx::ReadPaletteFromRom(
|
||||
@@ -181,8 +181,8 @@ absl::Status LoadSpriteAux2Palettes(const std::vector<uint8_t> &rom_data,
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status LoadSpriteAux3Palettes(const std::vector<uint8_t> &rom_data,
|
||||
gfx::PaletteGroupMap &palette_groups) {
|
||||
absl::Status LoadSpriteAux3Palettes(const std::vector<uint8_t>& rom_data,
|
||||
gfx::PaletteGroupMap& palette_groups) {
|
||||
auto data = rom_data.data();
|
||||
for (int i = 0; i < 24; i++) {
|
||||
palette_groups.sprites_aux3.AddPalette(gfx::ReadPaletteFromRom(
|
||||
@@ -191,8 +191,8 @@ absl::Status LoadSpriteAux3Palettes(const std::vector<uint8_t> &rom_data,
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status LoadDungeonMainPalettes(const std::vector<uint8_t> &rom_data,
|
||||
gfx::PaletteGroupMap &palette_groups) {
|
||||
absl::Status LoadDungeonMainPalettes(const std::vector<uint8_t>& rom_data,
|
||||
gfx::PaletteGroupMap& palette_groups) {
|
||||
auto data = rom_data.data();
|
||||
for (int i = 0; i < 20; i++) {
|
||||
palette_groups.dungeon_main.AddPalette(gfx::ReadPaletteFromRom(
|
||||
@@ -201,8 +201,8 @@ absl::Status LoadDungeonMainPalettes(const std::vector<uint8_t> &rom_data,
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status LoadGrassColors(const std::vector<uint8_t> &rom_data,
|
||||
gfx::PaletteGroupMap &palette_groups) {
|
||||
absl::Status LoadGrassColors(const std::vector<uint8_t>& rom_data,
|
||||
gfx::PaletteGroupMap& palette_groups) {
|
||||
palette_groups.grass.AddColor(
|
||||
gfx::ReadColorFromRom(kHardcodedGrassLW, rom_data.data()));
|
||||
palette_groups.grass.AddColor(
|
||||
@@ -212,8 +212,8 @@ absl::Status LoadGrassColors(const std::vector<uint8_t> &rom_data,
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status Load3DObjectPalettes(const std::vector<uint8_t> &rom_data,
|
||||
gfx::PaletteGroupMap &palette_groups) {
|
||||
absl::Status Load3DObjectPalettes(const std::vector<uint8_t>& rom_data,
|
||||
gfx::PaletteGroupMap& palette_groups) {
|
||||
auto data = rom_data.data();
|
||||
palette_groups.object_3d.AddPalette(
|
||||
gfx::ReadPaletteFromRom(kTriforcePalette, 8, data));
|
||||
@@ -223,8 +223,8 @@ absl::Status Load3DObjectPalettes(const std::vector<uint8_t> &rom_data,
|
||||
}
|
||||
|
||||
absl::Status LoadOverworldMiniMapPalettes(
|
||||
const std::vector<uint8_t> &rom_data,
|
||||
gfx::PaletteGroupMap &palette_groups) {
|
||||
const std::vector<uint8_t>& rom_data,
|
||||
gfx::PaletteGroupMap& palette_groups) {
|
||||
auto data = rom_data.data();
|
||||
for (int i = 0; i < 2; i++) {
|
||||
palette_groups.overworld_mini_map.AddPalette(gfx::ReadPaletteFromRom(
|
||||
@@ -260,7 +260,7 @@ const absl::flat_hash_map<std::string, uint32_t> kPaletteGroupColorCounts = {
|
||||
{"grass", 1}, {"3d_object", 8}, {"ow_mini_map", 128},
|
||||
};
|
||||
|
||||
uint32_t GetPaletteAddress(const std::string &group_name, size_t palette_index,
|
||||
uint32_t GetPaletteAddress(const std::string& group_name, size_t palette_index,
|
||||
size_t color_index) {
|
||||
// Retrieve the base address for the palette group
|
||||
uint32_t base_address = kPaletteGroupAddressMap.at(group_name);
|
||||
@@ -299,27 +299,27 @@ uint32_t GetPaletteAddress(const std::string &group_name, size_t palette_index,
|
||||
* @param rom Pointer to ROM data
|
||||
* @return SnesPalette containing the colors (no transparency flags set)
|
||||
*/
|
||||
SnesPalette ReadPaletteFromRom(int offset, int num_colors, const uint8_t *rom) {
|
||||
SnesPalette ReadPaletteFromRom(int offset, int num_colors, const uint8_t* rom) {
|
||||
int color_offset = 0;
|
||||
std::vector<gfx::SnesColor> colors(num_colors);
|
||||
|
||||
while (color_offset < num_colors) {
|
||||
// Read SNES 15-bit color (little endian)
|
||||
uint16_t snes_color_word = (uint16_t)((rom[offset + 1]) << 8) | rom[offset];
|
||||
|
||||
|
||||
// Extract RGB components (5-bit each) and expand to 8-bit (0-255)
|
||||
snes_color new_color;
|
||||
new_color.red = (snes_color_word & 0x1F) * 8; // Bits 0-4
|
||||
new_color.red = (snes_color_word & 0x1F) * 8; // Bits 0-4
|
||||
new_color.green = ((snes_color_word >> 5) & 0x1F) * 8; // Bits 5-9
|
||||
new_color.blue = ((snes_color_word >> 10) & 0x1F) * 8; // Bits 10-14
|
||||
|
||||
new_color.blue = ((snes_color_word >> 10) & 0x1F) * 8; // Bits 10-14
|
||||
|
||||
// Create SnesColor by converting RGB back to SNES format
|
||||
// (This ensures all internal representations are consistent)
|
||||
colors[color_offset].set_snes(ConvertRgbToSnes(new_color));
|
||||
|
||||
|
||||
// DO NOT mark as transparent - preserve actual ROM color data!
|
||||
// Transparency is handled at render time, not in the data
|
||||
|
||||
|
||||
color_offset++;
|
||||
offset += 2; // SNES colors are 2 bytes each
|
||||
}
|
||||
@@ -327,7 +327,7 @@ SnesPalette ReadPaletteFromRom(int offset, int num_colors, const uint8_t *rom) {
|
||||
return gfx::SnesPalette(colors);
|
||||
}
|
||||
|
||||
std::array<float, 4> ToFloatArray(const SnesColor &color) {
|
||||
std::array<float, 4> ToFloatArray(const SnesColor& color) {
|
||||
std::array<float, 4> colorArray;
|
||||
colorArray[0] = color.rgb().x / 255.0f;
|
||||
colorArray[1] = color.rgb().y / 255.0f;
|
||||
@@ -337,7 +337,7 @@ std::array<float, 4> ToFloatArray(const SnesColor &color) {
|
||||
}
|
||||
|
||||
absl::StatusOr<PaletteGroup> CreatePaletteGroupFromColFile(
|
||||
std::vector<SnesColor> &palette_rows) {
|
||||
std::vector<SnesColor>& palette_rows) {
|
||||
PaletteGroup palette_group;
|
||||
for (int i = 0; i < palette_rows.size(); i += 8) {
|
||||
SnesPalette palette;
|
||||
@@ -365,7 +365,7 @@ absl::StatusOr<PaletteGroup> CreatePaletteGroupFromColFile(
|
||||
* @return PaletteGroup containing the sub-palettes
|
||||
*/
|
||||
absl::StatusOr<PaletteGroup> CreatePaletteGroupFromLargePalette(
|
||||
SnesPalette &palette, int num_colors) {
|
||||
SnesPalette& palette, int num_colors) {
|
||||
PaletteGroup palette_group;
|
||||
for (int i = 0; i < palette.size(); i += num_colors) {
|
||||
SnesPalette new_palette;
|
||||
@@ -385,8 +385,8 @@ absl::StatusOr<PaletteGroup> CreatePaletteGroupFromLargePalette(
|
||||
using namespace palette_group_internal;
|
||||
|
||||
// TODO: Refactor LoadAllPalettes to use group names, move to zelda3 namespace
|
||||
absl::Status LoadAllPalettes(const std::vector<uint8_t> &rom_data,
|
||||
PaletteGroupMap &groups) {
|
||||
absl::Status LoadAllPalettes(const std::vector<uint8_t>& rom_data,
|
||||
PaletteGroupMap& groups) {
|
||||
RETURN_IF_ERROR(LoadOverworldMainPalettes(rom_data, groups))
|
||||
RETURN_IF_ERROR(LoadOverworldAuxiliaryPalettes(rom_data, groups))
|
||||
RETURN_IF_ERROR(LoadOverworldAnimatedPalettes(rom_data, groups))
|
||||
|
||||
@@ -212,7 +212,7 @@ struct PaletteGroup {
|
||||
PaletteGroup(const std::string& name) : name_(name) {}
|
||||
|
||||
// ========== Basic Operations ==========
|
||||
|
||||
|
||||
void AddPalette(SnesPalette pal) { palettes.emplace_back(pal); }
|
||||
|
||||
void AddColor(SnesColor color) {
|
||||
@@ -224,23 +224,23 @@ struct PaletteGroup {
|
||||
|
||||
void clear() { palettes.clear(); }
|
||||
void resize(size_t new_size) { palettes.resize(new_size); }
|
||||
|
||||
|
||||
// ========== Accessors ==========
|
||||
|
||||
|
||||
auto name() const { return name_; }
|
||||
auto size() const { return palettes.size(); }
|
||||
bool empty() const { return palettes.empty(); }
|
||||
|
||||
|
||||
// Const access
|
||||
auto palette(int i) const { return palettes[i]; }
|
||||
const SnesPalette& palette_ref(int i) const { return palettes[i]; }
|
||||
|
||||
|
||||
// Mutable access
|
||||
auto mutable_palette(int i) { return &palettes[i]; }
|
||||
SnesPalette& palette_ref(int i) { return palettes[i]; }
|
||||
|
||||
// ========== Color Operations ==========
|
||||
|
||||
|
||||
/**
|
||||
* @brief Get a specific color from a palette
|
||||
* @param palette_index The palette index
|
||||
@@ -256,7 +256,7 @@ struct PaletteGroup {
|
||||
}
|
||||
return SnesColor();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Set a specific color in a palette
|
||||
* @param palette_index The palette index
|
||||
@@ -274,13 +274,14 @@ struct PaletteGroup {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// ========== Operator Overloads ==========
|
||||
|
||||
SnesPalette operator[](int i) {
|
||||
if (i >= palettes.size()) {
|
||||
std::cout << "PaletteGroup: Index " << i << " out of bounds (size: "
|
||||
<< palettes.size() << ")" << std::endl;
|
||||
std::cout << "PaletteGroup: Index " << i
|
||||
<< " out of bounds (size: " << palettes.size() << ")"
|
||||
<< std::endl;
|
||||
return SnesPalette();
|
||||
}
|
||||
return palettes[i];
|
||||
@@ -288,8 +289,9 @@ struct PaletteGroup {
|
||||
|
||||
const SnesPalette& operator[](int i) const {
|
||||
if (i >= palettes.size()) {
|
||||
std::cout << "PaletteGroup: Index " << i << " out of bounds (size: "
|
||||
<< palettes.size() << ")" << std::endl;
|
||||
std::cout << "PaletteGroup: Index " << i
|
||||
<< " out of bounds (size: " << palettes.size() << ")"
|
||||
<< std::endl;
|
||||
static const SnesPalette empty_palette;
|
||||
return empty_palette;
|
||||
}
|
||||
|
||||
@@ -24,8 +24,8 @@ snes_tile8 UnpackBppTile(std::span<uint8_t> data, const uint32_t offset,
|
||||
const uint32_t bpp) {
|
||||
snes_tile8 tile = {}; // Initialize to zero
|
||||
assert(bpp >= 1 && bpp <= 8);
|
||||
unsigned int bpp_pos[8]; // More for conveniance and readibility
|
||||
for (int row = 0; row < 8; row++) { // Process rows first (Y coordinate)
|
||||
unsigned int bpp_pos[8]; // More for conveniance and readibility
|
||||
for (int row = 0; row < 8; row++) { // Process rows first (Y coordinate)
|
||||
for (int col = 0; col < 8; col++) { // Then columns (X coordinate)
|
||||
if (bpp == 1) {
|
||||
tile.data[row * 8 + col] = (data[offset + row] >> (7 - col)) & 0x01;
|
||||
@@ -83,7 +83,8 @@ std::vector<uint8_t> PackBppTile(const snes_tile8& tile, const uint32_t bpp) {
|
||||
}
|
||||
|
||||
// 1bpp format
|
||||
if (bpp == 1) output[row] += (uint8_t)((color & 1) << (7 - col));
|
||||
if (bpp == 1)
|
||||
output[row] += (uint8_t)((color & 1) << (7 - col));
|
||||
|
||||
// 2bpp format
|
||||
if (bpp >= 2) {
|
||||
@@ -92,7 +93,8 @@ std::vector<uint8_t> PackBppTile(const snes_tile8& tile, const uint32_t bpp) {
|
||||
}
|
||||
|
||||
// 3bpp format
|
||||
if (bpp == 3) output[16 + row] += (((color & 4) == 4) << (7 - col));
|
||||
if (bpp == 3)
|
||||
output[16 + row] += (((color & 4) == 4) << (7 - col));
|
||||
|
||||
// 4bpp format
|
||||
if (bpp >= 4) {
|
||||
|
||||
@@ -19,29 +19,26 @@ BppFormatManager& BppFormatManager::Get() {
|
||||
void BppFormatManager::Initialize() {
|
||||
InitializeFormatInfo();
|
||||
cache_memory_usage_ = 0;
|
||||
max_cache_size_ = 64 * 1024 * 1024; // 64MB cache limit
|
||||
max_cache_size_ = 64 * 1024 * 1024; // 64MB cache limit
|
||||
}
|
||||
|
||||
void BppFormatManager::InitializeFormatInfo() {
|
||||
format_info_[BppFormat::kBpp2] = BppFormatInfo(
|
||||
BppFormat::kBpp2, "2BPP", 2, 4, 16, 2048, true,
|
||||
"2 bits per pixel - 4 colors, used for simple graphics and UI elements"
|
||||
);
|
||||
|
||||
BppFormat::kBpp2, "2BPP", 2, 4, 16, 2048, true,
|
||||
"2 bits per pixel - 4 colors, used for simple graphics and UI elements");
|
||||
|
||||
format_info_[BppFormat::kBpp3] = BppFormatInfo(
|
||||
BppFormat::kBpp3, "3BPP", 3, 8, 24, 3072, true,
|
||||
"3 bits per pixel - 8 colors, common for SNES sprites and tiles"
|
||||
);
|
||||
|
||||
BppFormat::kBpp3, "3BPP", 3, 8, 24, 3072, true,
|
||||
"3 bits per pixel - 8 colors, common for SNES sprites and tiles");
|
||||
|
||||
format_info_[BppFormat::kBpp4] = BppFormatInfo(
|
||||
BppFormat::kBpp4, "4BPP", 4, 16, 32, 4096, true,
|
||||
"4 bits per pixel - 16 colors, standard for SNES backgrounds"
|
||||
);
|
||||
|
||||
format_info_[BppFormat::kBpp8] = BppFormatInfo(
|
||||
BppFormat::kBpp8, "8BPP", 8, 256, 64, 8192, false,
|
||||
"8 bits per pixel - 256 colors, high-color graphics and converted formats"
|
||||
);
|
||||
BppFormat::kBpp4, "4BPP", 4, 16, 32, 4096, true,
|
||||
"4 bits per pixel - 16 colors, standard for SNES backgrounds");
|
||||
|
||||
format_info_[BppFormat::kBpp8] =
|
||||
BppFormatInfo(BppFormat::kBpp8, "8BPP", 8, 256, 64, 8192, false,
|
||||
"8 bits per pixel - 256 colors, high-color graphics and "
|
||||
"converted formats");
|
||||
}
|
||||
|
||||
const BppFormatInfo& BppFormatManager::GetFormatInfo(BppFormat format) const {
|
||||
@@ -53,28 +50,30 @@ const BppFormatInfo& BppFormatManager::GetFormatInfo(BppFormat format) const {
|
||||
}
|
||||
|
||||
std::vector<BppFormat> BppFormatManager::GetAvailableFormats() const {
|
||||
return {BppFormat::kBpp2, BppFormat::kBpp3, BppFormat::kBpp4, BppFormat::kBpp8};
|
||||
return {BppFormat::kBpp2, BppFormat::kBpp3, BppFormat::kBpp4,
|
||||
BppFormat::kBpp8};
|
||||
}
|
||||
|
||||
std::vector<uint8_t> BppFormatManager::ConvertFormat(const std::vector<uint8_t>& data,
|
||||
BppFormat from_format, BppFormat to_format,
|
||||
int width, int height) {
|
||||
std::vector<uint8_t> BppFormatManager::ConvertFormat(
|
||||
const std::vector<uint8_t>& data, BppFormat from_format,
|
||||
BppFormat to_format, int width, int height) {
|
||||
if (from_format == to_format) {
|
||||
return data; // No conversion needed
|
||||
return data; // No conversion needed
|
||||
}
|
||||
|
||||
|
||||
ScopedTimer timer("bpp_format_conversion");
|
||||
|
||||
|
||||
// Check cache first
|
||||
std::string cache_key = GenerateCacheKey(data, from_format, to_format, width, height);
|
||||
std::string cache_key =
|
||||
GenerateCacheKey(data, from_format, to_format, width, height);
|
||||
auto cache_iter = conversion_cache_.find(cache_key);
|
||||
if (cache_iter != conversion_cache_.end()) {
|
||||
conversion_stats_["cache_hits"]++;
|
||||
return cache_iter->second;
|
||||
}
|
||||
|
||||
|
||||
std::vector<uint8_t> result;
|
||||
|
||||
|
||||
// Convert to 8BPP as intermediate format if needed
|
||||
std::vector<uint8_t> intermediate_data = data;
|
||||
if (from_format != BppFormat::kBpp8) {
|
||||
@@ -93,7 +92,7 @@ std::vector<uint8_t> BppFormatManager::ConvertFormat(const std::vector<uint8_t>&
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Convert from 8BPP to target format
|
||||
if (to_format != BppFormat::kBpp8) {
|
||||
switch (to_format) {
|
||||
@@ -113,43 +112,45 @@ std::vector<uint8_t> BppFormatManager::ConvertFormat(const std::vector<uint8_t>&
|
||||
} else {
|
||||
result = intermediate_data;
|
||||
}
|
||||
|
||||
|
||||
// Cache the result
|
||||
if (cache_memory_usage_ + result.size() < max_cache_size_) {
|
||||
conversion_cache_[cache_key] = result;
|
||||
cache_memory_usage_ += result.size();
|
||||
}
|
||||
|
||||
|
||||
conversion_stats_["conversions"]++;
|
||||
conversion_stats_["cache_misses"]++;
|
||||
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
GraphicsSheetAnalysis BppFormatManager::AnalyzeGraphicsSheet(const std::vector<uint8_t>& sheet_data,
|
||||
int sheet_id,
|
||||
const SnesPalette& palette) {
|
||||
GraphicsSheetAnalysis BppFormatManager::AnalyzeGraphicsSheet(
|
||||
const std::vector<uint8_t>& sheet_data, int sheet_id,
|
||||
const SnesPalette& palette) {
|
||||
// Check analysis cache
|
||||
auto cache_it = analysis_cache_.find(sheet_id);
|
||||
if (cache_it != analysis_cache_.end()) {
|
||||
return cache_it->second;
|
||||
}
|
||||
|
||||
|
||||
ScopedTimer timer("graphics_sheet_analysis");
|
||||
|
||||
|
||||
GraphicsSheetAnalysis analysis;
|
||||
analysis.sheet_id = sheet_id;
|
||||
analysis.original_size = sheet_data.size();
|
||||
analysis.current_size = sheet_data.size();
|
||||
|
||||
|
||||
// Detect current format
|
||||
analysis.current_format = DetectFormat(sheet_data, 128, 32); // Standard sheet size
|
||||
|
||||
analysis.current_format =
|
||||
DetectFormat(sheet_data, 128, 32); // Standard sheet size
|
||||
|
||||
// Analyze color usage
|
||||
analysis.palette_entries_used = CountUsedColors(sheet_data, palette.size());
|
||||
|
||||
|
||||
// Determine if this was likely converted from a lower BPP format
|
||||
if (analysis.current_format == BppFormat::kBpp8 && analysis.palette_entries_used <= 16) {
|
||||
if (analysis.current_format == BppFormat::kBpp8 &&
|
||||
analysis.palette_entries_used <= 16) {
|
||||
if (analysis.palette_entries_used <= 4) {
|
||||
analysis.original_format = BppFormat::kBpp2;
|
||||
} else if (analysis.palette_entries_used <= 8) {
|
||||
@@ -162,69 +163,74 @@ GraphicsSheetAnalysis BppFormatManager::AnalyzeGraphicsSheet(const std::vector<u
|
||||
analysis.original_format = analysis.current_format;
|
||||
analysis.was_converted = false;
|
||||
}
|
||||
|
||||
|
||||
// Generate conversion history
|
||||
if (analysis.was_converted) {
|
||||
std::ostringstream history;
|
||||
history << "Originally " << GetFormatInfo(analysis.original_format).name
|
||||
<< " (" << analysis.palette_entries_used << " colors used)";
|
||||
history << " -> Converted to " << GetFormatInfo(analysis.current_format).name;
|
||||
history << " -> Converted to "
|
||||
<< GetFormatInfo(analysis.current_format).name;
|
||||
analysis.conversion_history = history.str();
|
||||
} else {
|
||||
analysis.conversion_history = "No conversion - original format";
|
||||
}
|
||||
|
||||
|
||||
// Analyze tile usage pattern
|
||||
analysis.tile_usage_pattern = AnalyzeTileUsagePattern(sheet_data, 128, 32, 8);
|
||||
|
||||
|
||||
// Calculate compression ratio (simplified)
|
||||
analysis.compression_ratio = 1.0f; // Would need original compressed data for accurate calculation
|
||||
|
||||
analysis.compression_ratio =
|
||||
1.0f; // Would need original compressed data for accurate calculation
|
||||
|
||||
// Cache the analysis
|
||||
analysis_cache_[sheet_id] = analysis;
|
||||
|
||||
|
||||
return analysis;
|
||||
}
|
||||
|
||||
BppFormat BppFormatManager::DetectFormat(const std::vector<uint8_t>& data, int width, int height) {
|
||||
BppFormat BppFormatManager::DetectFormat(const std::vector<uint8_t>& data,
|
||||
int width, int height) {
|
||||
if (data.empty()) {
|
||||
return BppFormat::kBpp8; // Default
|
||||
return BppFormat::kBpp8; // Default
|
||||
}
|
||||
|
||||
|
||||
// Analyze color depth
|
||||
return AnalyzeColorDepth(data, width, height);
|
||||
}
|
||||
|
||||
SnesPalette BppFormatManager::OptimizePaletteForFormat(const SnesPalette& palette,
|
||||
BppFormat target_format,
|
||||
const std::vector<int>& used_colors) {
|
||||
SnesPalette BppFormatManager::OptimizePaletteForFormat(
|
||||
const SnesPalette& palette, BppFormat target_format,
|
||||
const std::vector<int>& used_colors) {
|
||||
const auto& format_info = GetFormatInfo(target_format);
|
||||
|
||||
|
||||
// Create optimized palette with target format size
|
||||
SnesPalette optimized_palette;
|
||||
|
||||
|
||||
// Add used colors first
|
||||
for (int color_index : used_colors) {
|
||||
if (color_index < static_cast<int>(palette.size()) &&
|
||||
if (color_index < static_cast<int>(palette.size()) &&
|
||||
static_cast<int>(optimized_palette.size()) < format_info.max_colors) {
|
||||
optimized_palette.AddColor(palette[color_index]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Fill remaining slots with unused colors or transparent
|
||||
while (static_cast<int>(optimized_palette.size()) < format_info.max_colors) {
|
||||
if (static_cast<int>(optimized_palette.size()) < static_cast<int>(palette.size())) {
|
||||
if (static_cast<int>(optimized_palette.size()) <
|
||||
static_cast<int>(palette.size())) {
|
||||
optimized_palette.AddColor(palette[optimized_palette.size()]);
|
||||
} else {
|
||||
// Add transparent color
|
||||
optimized_palette.AddColor(SnesColor(ImVec4(0, 0, 0, 0)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return optimized_palette;
|
||||
}
|
||||
|
||||
std::unordered_map<std::string, int> BppFormatManager::GetConversionStats() const {
|
||||
std::unordered_map<std::string, int> BppFormatManager::GetConversionStats()
|
||||
const {
|
||||
return conversion_stats_;
|
||||
}
|
||||
|
||||
@@ -241,34 +247,36 @@ std::pair<size_t, size_t> BppFormatManager::GetMemoryStats() const {
|
||||
|
||||
// Helper method implementations
|
||||
|
||||
std::string BppFormatManager::GenerateCacheKey(const std::vector<uint8_t>& data,
|
||||
BppFormat from_format, BppFormat to_format,
|
||||
int width, int height) {
|
||||
std::string BppFormatManager::GenerateCacheKey(const std::vector<uint8_t>& data,
|
||||
BppFormat from_format,
|
||||
BppFormat to_format, int width,
|
||||
int height) {
|
||||
std::ostringstream key;
|
||||
key << static_cast<int>(from_format) << "_" << static_cast<int>(to_format)
|
||||
key << static_cast<int>(from_format) << "_" << static_cast<int>(to_format)
|
||||
<< "_" << width << "x" << height << "_" << data.size();
|
||||
|
||||
|
||||
// Add hash of data for uniqueness
|
||||
size_t hash = 0;
|
||||
for (size_t i = 0; i < std::min(data.size(), size_t(1024)); ++i) {
|
||||
hash = hash * 31 + data[i];
|
||||
}
|
||||
key << "_" << hash;
|
||||
|
||||
|
||||
return key.str();
|
||||
}
|
||||
|
||||
BppFormat BppFormatManager::AnalyzeColorDepth(const std::vector<uint8_t>& data, int /*width*/, int /*height*/) {
|
||||
BppFormat BppFormatManager::AnalyzeColorDepth(const std::vector<uint8_t>& data,
|
||||
int /*width*/, int /*height*/) {
|
||||
if (data.empty()) {
|
||||
return BppFormat::kBpp8;
|
||||
}
|
||||
|
||||
|
||||
// Find maximum color index used
|
||||
uint8_t max_color = 0;
|
||||
for (uint8_t pixel : data) {
|
||||
max_color = std::max(max_color, pixel);
|
||||
}
|
||||
|
||||
|
||||
// Determine BPP based on color usage
|
||||
if (max_color < 4) {
|
||||
return BppFormat::kBpp2;
|
||||
@@ -282,14 +290,15 @@ BppFormat BppFormatManager::AnalyzeColorDepth(const std::vector<uint8_t>& data,
|
||||
return BppFormat::kBpp8;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> BppFormatManager::Convert2BppTo8Bpp(const std::vector<uint8_t>& data, int width, int height) {
|
||||
std::vector<uint8_t> BppFormatManager::Convert2BppTo8Bpp(
|
||||
const std::vector<uint8_t>& data, int width, int height) {
|
||||
std::vector<uint8_t> result(width * height);
|
||||
|
||||
|
||||
for (int row = 0; row < height; ++row) {
|
||||
for (int col = 0; col < width; col += 4) { // 4 pixels per byte in 2BPP
|
||||
for (int col = 0; col < width; col += 4) { // 4 pixels per byte in 2BPP
|
||||
if (col / 4 < static_cast<int>(data.size())) {
|
||||
uint8_t byte = data[row * (width / 4) + (col / 4)];
|
||||
|
||||
|
||||
// Extract 4 pixels from the byte
|
||||
for (int i = 0; i < 4 && (col + i) < width; ++i) {
|
||||
uint8_t pixel = (byte >> (6 - i * 2)) & 0x03;
|
||||
@@ -298,27 +307,29 @@ std::vector<uint8_t> BppFormatManager::Convert2BppTo8Bpp(const std::vector<uint8
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> BppFormatManager::Convert3BppTo8Bpp(const std::vector<uint8_t>& data, int width, int height) {
|
||||
std::vector<uint8_t> BppFormatManager::Convert3BppTo8Bpp(
|
||||
const std::vector<uint8_t>& data, int width, int height) {
|
||||
// 3BPP is more complex - typically stored as 4BPP with unused bits
|
||||
return Convert4BppTo8Bpp(data, width, height);
|
||||
}
|
||||
|
||||
std::vector<uint8_t> BppFormatManager::Convert4BppTo8Bpp(const std::vector<uint8_t>& data, int width, int height) {
|
||||
std::vector<uint8_t> BppFormatManager::Convert4BppTo8Bpp(
|
||||
const std::vector<uint8_t>& data, int width, int height) {
|
||||
std::vector<uint8_t> result(width * height);
|
||||
|
||||
|
||||
for (int row = 0; row < height; ++row) {
|
||||
for (int col = 0; col < width; col += 2) { // 2 pixels per byte in 4BPP
|
||||
for (int col = 0; col < width; col += 2) { // 2 pixels per byte in 4BPP
|
||||
if (col / 2 < static_cast<int>(data.size())) {
|
||||
uint8_t byte = data[row * (width / 2) + (col / 2)];
|
||||
|
||||
|
||||
// Extract 2 pixels from the byte
|
||||
uint8_t pixel1 = byte & 0x0F;
|
||||
uint8_t pixel2 = (byte >> 4) & 0x0F;
|
||||
|
||||
|
||||
result[row * width + col] = pixel1;
|
||||
if (col + 1 < width) {
|
||||
result[row * width + col + 1] = pixel2;
|
||||
@@ -326,115 +337,129 @@ std::vector<uint8_t> BppFormatManager::Convert4BppTo8Bpp(const std::vector<uint8
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> BppFormatManager::Convert8BppTo2Bpp(const std::vector<uint8_t>& data, int width, int height) {
|
||||
std::vector<uint8_t> result((width * height) / 4); // 4 pixels per byte
|
||||
|
||||
std::vector<uint8_t> BppFormatManager::Convert8BppTo2Bpp(
|
||||
const std::vector<uint8_t>& data, int width, int height) {
|
||||
std::vector<uint8_t> result((width * height) / 4); // 4 pixels per byte
|
||||
|
||||
for (int row = 0; row < height; ++row) {
|
||||
for (int col = 0; col < width; col += 4) {
|
||||
uint8_t byte = 0;
|
||||
|
||||
|
||||
// Pack 4 pixels into one byte
|
||||
for (int i = 0; i < 4 && (col + i) < width; ++i) {
|
||||
uint8_t pixel = data[row * width + col + i] & 0x03; // Clamp to 2 bits
|
||||
uint8_t pixel = data[row * width + col + i] & 0x03; // Clamp to 2 bits
|
||||
byte |= (pixel << (6 - i * 2));
|
||||
}
|
||||
|
||||
|
||||
result[row * (width / 4) + (col / 4)] = byte;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> BppFormatManager::Convert8BppTo3Bpp(const std::vector<uint8_t>& data, int width, int height) {
|
||||
std::vector<uint8_t> BppFormatManager::Convert8BppTo3Bpp(
|
||||
const std::vector<uint8_t>& data, int width, int height) {
|
||||
// Convert to 4BPP first, then optimize
|
||||
auto result_4bpp = Convert8BppTo4Bpp(data, width, height);
|
||||
// Note: 3BPP conversion would require more sophisticated palette optimization
|
||||
return result_4bpp;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> BppFormatManager::Convert8BppTo4Bpp(const std::vector<uint8_t>& data, int width, int height) {
|
||||
std::vector<uint8_t> result((width * height) / 2); // 2 pixels per byte
|
||||
|
||||
std::vector<uint8_t> BppFormatManager::Convert8BppTo4Bpp(
|
||||
const std::vector<uint8_t>& data, int width, int height) {
|
||||
std::vector<uint8_t> result((width * height) / 2); // 2 pixels per byte
|
||||
|
||||
for (int row = 0; row < height; ++row) {
|
||||
for (int col = 0; col < width; col += 2) {
|
||||
uint8_t pixel1 = data[row * width + col] & 0x0F; // Clamp to 4 bits
|
||||
uint8_t pixel2 = (col + 1 < width) ? (data[row * width + col + 1] & 0x0F) : 0;
|
||||
|
||||
uint8_t pixel1 = data[row * width + col] & 0x0F; // Clamp to 4 bits
|
||||
uint8_t pixel2 =
|
||||
(col + 1 < width) ? (data[row * width + col + 1] & 0x0F) : 0;
|
||||
|
||||
uint8_t byte = pixel1 | (pixel2 << 4);
|
||||
result[row * (width / 2) + (col / 2)] = byte;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int BppFormatManager::CountUsedColors(const std::vector<uint8_t>& data, int max_colors) {
|
||||
int BppFormatManager::CountUsedColors(const std::vector<uint8_t>& data,
|
||||
int max_colors) {
|
||||
std::vector<bool> used_colors(max_colors, false);
|
||||
|
||||
|
||||
for (uint8_t pixel : data) {
|
||||
if (pixel < max_colors) {
|
||||
used_colors[pixel] = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int count = 0;
|
||||
for (bool used : used_colors) {
|
||||
if (used) count++;
|
||||
if (used)
|
||||
count++;
|
||||
}
|
||||
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
float BppFormatManager::CalculateCompressionRatio(const std::vector<uint8_t>& original,
|
||||
const std::vector<uint8_t>& compressed) {
|
||||
if (compressed.empty()) return 1.0f;
|
||||
return static_cast<float>(original.size()) / static_cast<float>(compressed.size());
|
||||
float BppFormatManager::CalculateCompressionRatio(
|
||||
const std::vector<uint8_t>& original,
|
||||
const std::vector<uint8_t>& compressed) {
|
||||
if (compressed.empty())
|
||||
return 1.0f;
|
||||
return static_cast<float>(original.size()) /
|
||||
static_cast<float>(compressed.size());
|
||||
}
|
||||
|
||||
std::vector<int> BppFormatManager::AnalyzeTileUsagePattern(const std::vector<uint8_t>& data,
|
||||
int width, int height, int tile_size) {
|
||||
std::vector<int> BppFormatManager::AnalyzeTileUsagePattern(
|
||||
const std::vector<uint8_t>& data, int width, int height, int tile_size) {
|
||||
std::vector<int> usage_pattern;
|
||||
int tiles_x = width / tile_size;
|
||||
int tiles_y = height / tile_size;
|
||||
|
||||
|
||||
for (int tile_row = 0; tile_row < tiles_y; ++tile_row) {
|
||||
for (int tile_col = 0; tile_col < tiles_x; ++tile_col) {
|
||||
int non_zero_pixels = 0;
|
||||
|
||||
|
||||
// Count non-zero pixels in this tile
|
||||
for (int row = 0; row < tile_size; ++row) {
|
||||
for (int col = 0; col < tile_size; ++col) {
|
||||
int pixel_x = tile_col * tile_size + col;
|
||||
int pixel_y = tile_row * tile_size + row;
|
||||
int pixel_index = pixel_y * width + pixel_x;
|
||||
|
||||
if (pixel_index < static_cast<int>(data.size()) && data[pixel_index] != 0) {
|
||||
|
||||
if (pixel_index < static_cast<int>(data.size()) &&
|
||||
data[pixel_index] != 0) {
|
||||
non_zero_pixels++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
usage_pattern.push_back(non_zero_pixels);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return usage_pattern;
|
||||
}
|
||||
|
||||
// BppConversionScope implementation
|
||||
|
||||
BppConversionScope::BppConversionScope(BppFormat from_format, BppFormat to_format,
|
||||
int width, int height)
|
||||
: from_format_(from_format), to_format_(to_format), width_(width), height_(height),
|
||||
BppConversionScope::BppConversionScope(BppFormat from_format,
|
||||
BppFormat to_format, int width,
|
||||
int height)
|
||||
: from_format_(from_format),
|
||||
to_format_(to_format),
|
||||
width_(width),
|
||||
height_(height),
|
||||
timer_("bpp_convert_scope") {
|
||||
std::ostringstream op_name;
|
||||
op_name << "bpp_convert_" << static_cast<int>(from_format)
|
||||
<< "_to_" << static_cast<int>(to_format);
|
||||
op_name << "bpp_convert_" << static_cast<int>(from_format) << "_to_"
|
||||
<< static_cast<int>(to_format);
|
||||
operation_name_ = op_name.str();
|
||||
}
|
||||
|
||||
@@ -442,8 +467,10 @@ BppConversionScope::~BppConversionScope() {
|
||||
// Timer automatically ends in destructor
|
||||
}
|
||||
|
||||
std::vector<uint8_t> BppConversionScope::Convert(const std::vector<uint8_t>& data) {
|
||||
return BppFormatManager::Get().ConvertFormat(data, from_format_, to_format_, width_, height_);
|
||||
std::vector<uint8_t> BppConversionScope::Convert(
|
||||
const std::vector<uint8_t>& data) {
|
||||
return BppFormatManager::Get().ConvertFormat(data, from_format_, to_format_,
|
||||
width_, height_);
|
||||
}
|
||||
|
||||
} // namespace gfx
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
#define YAZE_APP_GFX_BPP_FORMAT_MANAGER_H
|
||||
|
||||
#include <SDL.h>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "app/gfx/core/bitmap.h"
|
||||
#include "app/gfx/types/snes_palette.h"
|
||||
#include "app/gfx/debug/performance/performance_profiler.h"
|
||||
#include "app/gfx/types/snes_palette.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace gfx {
|
||||
@@ -18,10 +18,10 @@ namespace gfx {
|
||||
* @brief BPP format enumeration for SNES graphics
|
||||
*/
|
||||
enum class BppFormat {
|
||||
kBpp2 = 2, ///< 2 bits per pixel (4 colors)
|
||||
kBpp3 = 3, ///< 3 bits per pixel (8 colors)
|
||||
kBpp4 = 4, ///< 4 bits per pixel (16 colors)
|
||||
kBpp8 = 8 ///< 8 bits per pixel (256 colors)
|
||||
kBpp2 = 2, ///< 2 bits per pixel (4 colors)
|
||||
kBpp3 = 3, ///< 3 bits per pixel (8 colors)
|
||||
kBpp4 = 4, ///< 4 bits per pixel (16 colors)
|
||||
kBpp8 = 8 ///< 8 bits per pixel (256 colors)
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -36,14 +36,20 @@ struct BppFormatInfo {
|
||||
int bytes_per_sheet;
|
||||
bool is_compressed;
|
||||
std::string description;
|
||||
|
||||
|
||||
BppFormatInfo() = default;
|
||||
|
||||
BppFormatInfo(BppFormat fmt, const std::string& n, int bpp, int max_col,
|
||||
int bytes_tile, int bytes_sheet, bool compressed, const std::string& desc)
|
||||
: format(fmt), name(n), bits_per_pixel(bpp), max_colors(max_col),
|
||||
bytes_per_tile(bytes_tile), bytes_per_sheet(bytes_sheet),
|
||||
is_compressed(compressed), description(desc) {}
|
||||
|
||||
BppFormatInfo(BppFormat fmt, const std::string& n, int bpp, int max_col,
|
||||
int bytes_tile, int bytes_sheet, bool compressed,
|
||||
const std::string& desc)
|
||||
: format(fmt),
|
||||
name(n),
|
||||
bits_per_pixel(bpp),
|
||||
max_colors(max_col),
|
||||
bytes_per_tile(bytes_tile),
|
||||
bytes_per_sheet(bytes_sheet),
|
||||
is_compressed(compressed),
|
||||
description(desc) {}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -60,11 +66,16 @@ struct GraphicsSheetAnalysis {
|
||||
size_t original_size;
|
||||
size_t current_size;
|
||||
std::vector<int> tile_usage_pattern;
|
||||
|
||||
GraphicsSheetAnalysis() : sheet_id(-1), original_format(BppFormat::kBpp8),
|
||||
current_format(BppFormat::kBpp8), was_converted(false),
|
||||
palette_entries_used(0), compression_ratio(1.0f),
|
||||
original_size(0), current_size(0) {}
|
||||
|
||||
GraphicsSheetAnalysis()
|
||||
: sheet_id(-1),
|
||||
original_format(BppFormat::kBpp8),
|
||||
current_format(BppFormat::kBpp8),
|
||||
was_converted(false),
|
||||
palette_entries_used(0),
|
||||
compression_ratio(1.0f),
|
||||
original_size(0),
|
||||
current_size(0) {}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -97,25 +108,25 @@ struct GraphicsSheetAnalysis {
|
||||
class BppFormatManager {
|
||||
public:
|
||||
static BppFormatManager& Get();
|
||||
|
||||
|
||||
/**
|
||||
* @brief Initialize the BPP format manager
|
||||
*/
|
||||
void Initialize();
|
||||
|
||||
|
||||
/**
|
||||
* @brief Get BPP format information
|
||||
* @param format BPP format to get info for
|
||||
* @return Format information structure
|
||||
*/
|
||||
const BppFormatInfo& GetFormatInfo(BppFormat format) const;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Get all available BPP formats
|
||||
* @return Vector of all supported BPP formats
|
||||
*/
|
||||
std::vector<BppFormat> GetAvailableFormats() const;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Convert bitmap data between BPP formats
|
||||
* @param data Source bitmap data
|
||||
@@ -126,9 +137,9 @@ class BppFormatManager {
|
||||
* @return Converted bitmap data
|
||||
*/
|
||||
std::vector<uint8_t> ConvertFormat(const std::vector<uint8_t>& data,
|
||||
BppFormat from_format, BppFormat to_format,
|
||||
int width, int height);
|
||||
|
||||
BppFormat from_format, BppFormat to_format,
|
||||
int width, int height);
|
||||
|
||||
/**
|
||||
* @brief Analyze graphics sheet to determine original and current BPP formats
|
||||
* @param sheet_data Graphics sheet data
|
||||
@@ -136,10 +147,10 @@ class BppFormatManager {
|
||||
* @param palette Palette data for analysis
|
||||
* @return Analysis result with format information
|
||||
*/
|
||||
GraphicsSheetAnalysis AnalyzeGraphicsSheet(const std::vector<uint8_t>& sheet_data,
|
||||
int sheet_id,
|
||||
const SnesPalette& palette);
|
||||
|
||||
GraphicsSheetAnalysis AnalyzeGraphicsSheet(
|
||||
const std::vector<uint8_t>& sheet_data, int sheet_id,
|
||||
const SnesPalette& palette);
|
||||
|
||||
/**
|
||||
* @brief Detect BPP format from bitmap data
|
||||
* @param data Bitmap data to analyze
|
||||
@@ -147,8 +158,9 @@ class BppFormatManager {
|
||||
* @param height Bitmap height
|
||||
* @return Detected BPP format
|
||||
*/
|
||||
BppFormat DetectFormat(const std::vector<uint8_t>& data, int width, int height);
|
||||
|
||||
BppFormat DetectFormat(const std::vector<uint8_t>& data, int width,
|
||||
int height);
|
||||
|
||||
/**
|
||||
* @brief Optimize palette for specific BPP format
|
||||
* @param palette Source palette
|
||||
@@ -157,20 +169,20 @@ class BppFormatManager {
|
||||
* @return Optimized palette
|
||||
*/
|
||||
SnesPalette OptimizePaletteForFormat(const SnesPalette& palette,
|
||||
BppFormat target_format,
|
||||
const std::vector<int>& used_colors);
|
||||
|
||||
BppFormat target_format,
|
||||
const std::vector<int>& used_colors);
|
||||
|
||||
/**
|
||||
* @brief Get conversion statistics
|
||||
* @return Map of conversion operation statistics
|
||||
*/
|
||||
std::unordered_map<std::string, int> GetConversionStats() const;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Clear conversion cache
|
||||
*/
|
||||
void ClearCache();
|
||||
|
||||
|
||||
/**
|
||||
* @brief Get memory usage statistics
|
||||
* @return Memory usage information
|
||||
@@ -180,42 +192,50 @@ class BppFormatManager {
|
||||
private:
|
||||
BppFormatManager() = default;
|
||||
~BppFormatManager() = default;
|
||||
|
||||
|
||||
// Format information storage
|
||||
std::unordered_map<BppFormat, BppFormatInfo> format_info_;
|
||||
|
||||
|
||||
// Conversion cache for performance
|
||||
std::unordered_map<std::string, std::vector<uint8_t>> conversion_cache_;
|
||||
|
||||
|
||||
// Analysis cache
|
||||
std::unordered_map<int, GraphicsSheetAnalysis> analysis_cache_;
|
||||
|
||||
|
||||
// Statistics tracking
|
||||
std::unordered_map<std::string, int> conversion_stats_;
|
||||
|
||||
|
||||
// Memory usage tracking
|
||||
size_t cache_memory_usage_;
|
||||
size_t max_cache_size_;
|
||||
|
||||
|
||||
// Helper methods
|
||||
void InitializeFormatInfo();
|
||||
std::string GenerateCacheKey(const std::vector<uint8_t>& data,
|
||||
BppFormat from_format, BppFormat to_format,
|
||||
int width, int height);
|
||||
BppFormat AnalyzeColorDepth(const std::vector<uint8_t>& data, int width, int height);
|
||||
std::vector<uint8_t> Convert2BppTo8Bpp(const std::vector<uint8_t>& data, int width, int height);
|
||||
std::vector<uint8_t> Convert3BppTo8Bpp(const std::vector<uint8_t>& data, int width, int height);
|
||||
std::vector<uint8_t> Convert4BppTo8Bpp(const std::vector<uint8_t>& data, int width, int height);
|
||||
std::vector<uint8_t> Convert8BppTo2Bpp(const std::vector<uint8_t>& data, int width, int height);
|
||||
std::vector<uint8_t> Convert8BppTo3Bpp(const std::vector<uint8_t>& data, int width, int height);
|
||||
std::vector<uint8_t> Convert8BppTo4Bpp(const std::vector<uint8_t>& data, int width, int height);
|
||||
|
||||
std::string GenerateCacheKey(const std::vector<uint8_t>& data,
|
||||
BppFormat from_format, BppFormat to_format,
|
||||
int width, int height);
|
||||
BppFormat AnalyzeColorDepth(const std::vector<uint8_t>& data, int width,
|
||||
int height);
|
||||
std::vector<uint8_t> Convert2BppTo8Bpp(const std::vector<uint8_t>& data,
|
||||
int width, int height);
|
||||
std::vector<uint8_t> Convert3BppTo8Bpp(const std::vector<uint8_t>& data,
|
||||
int width, int height);
|
||||
std::vector<uint8_t> Convert4BppTo8Bpp(const std::vector<uint8_t>& data,
|
||||
int width, int height);
|
||||
std::vector<uint8_t> Convert8BppTo2Bpp(const std::vector<uint8_t>& data,
|
||||
int width, int height);
|
||||
std::vector<uint8_t> Convert8BppTo3Bpp(const std::vector<uint8_t>& data,
|
||||
int width, int height);
|
||||
std::vector<uint8_t> Convert8BppTo4Bpp(const std::vector<uint8_t>& data,
|
||||
int width, int height);
|
||||
|
||||
// Analysis helpers
|
||||
int CountUsedColors(const std::vector<uint8_t>& data, int max_colors);
|
||||
float CalculateCompressionRatio(const std::vector<uint8_t>& original,
|
||||
const std::vector<uint8_t>& compressed);
|
||||
std::vector<int> AnalyzeTileUsagePattern(const std::vector<uint8_t>& data,
|
||||
int width, int height, int tile_size);
|
||||
float CalculateCompressionRatio(const std::vector<uint8_t>& original,
|
||||
const std::vector<uint8_t>& compressed);
|
||||
std::vector<int> AnalyzeTileUsagePattern(const std::vector<uint8_t>& data,
|
||||
int width, int height,
|
||||
int tile_size);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -223,11 +243,12 @@ class BppFormatManager {
|
||||
*/
|
||||
class BppConversionScope {
|
||||
public:
|
||||
BppConversionScope(BppFormat from_format, BppFormat to_format, int width, int height);
|
||||
BppConversionScope(BppFormat from_format, BppFormat to_format, int width,
|
||||
int height);
|
||||
~BppConversionScope();
|
||||
|
||||
|
||||
std::vector<uint8_t> Convert(const std::vector<uint8_t>& data);
|
||||
|
||||
|
||||
private:
|
||||
BppFormat from_format_;
|
||||
BppFormat to_format_;
|
||||
|
||||
@@ -36,9 +36,11 @@ std::vector<uint8_t> HyruleMagicCompress(uint8_t const* const src,
|
||||
m = oldsize - j;
|
||||
|
||||
for (n = 0; n < m; n++)
|
||||
if (src[n + j] != src[n + i]) break;
|
||||
if (src[n + j] != src[n + i])
|
||||
break;
|
||||
|
||||
if (n > k) k = n, o = j;
|
||||
if (n > k)
|
||||
k = n, o = j;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,11 +62,13 @@ std::vector<uint8_t> HyruleMagicCompress(uint8_t const* const src,
|
||||
m = src[i + 1];
|
||||
|
||||
for (n = i + 2; n < oldsize; n++) {
|
||||
if (src[n] != l) break;
|
||||
if (src[n] != l)
|
||||
break;
|
||||
|
||||
n++;
|
||||
|
||||
if (src[n] != m) break;
|
||||
if (src[n] != m)
|
||||
break;
|
||||
}
|
||||
|
||||
n -= i;
|
||||
@@ -75,7 +79,8 @@ std::vector<uint8_t> HyruleMagicCompress(uint8_t const* const src,
|
||||
m = oldsize - i;
|
||||
|
||||
for (n = 1; n < m; n++)
|
||||
if (src[i + n] != l + n) break;
|
||||
if (src[i + n] != l + n)
|
||||
break;
|
||||
|
||||
if (n > 1 + r)
|
||||
p = 3;
|
||||
@@ -84,7 +89,8 @@ std::vector<uint8_t> HyruleMagicCompress(uint8_t const* const src,
|
||||
}
|
||||
}
|
||||
|
||||
if (k > 3 + r && k > n + (p & 1)) p = 4, n = k;
|
||||
if (k > 3 + r && k > n + (p & 1))
|
||||
p = 4, n = k;
|
||||
|
||||
if (!p)
|
||||
q++, i++;
|
||||
@@ -179,7 +185,8 @@ std::vector<uint8_t> HyruleMagicDecompress(uint8_t const* src, int* const size,
|
||||
a = *(src++);
|
||||
|
||||
// end the decompression routine if we encounter 0xff.
|
||||
if (a == 0xff) break;
|
||||
if (a == 0xff)
|
||||
break;
|
||||
|
||||
// examine the top 3 bits of a.
|
||||
b = (a >> 5);
|
||||
@@ -291,7 +298,8 @@ std::vector<uint8_t> HyruleMagicDecompress(uint8_t const* src, int* const size,
|
||||
|
||||
b2 = (unsigned char*)realloc(b2, bd);
|
||||
|
||||
if (size) (*size) = bd;
|
||||
if (size)
|
||||
(*size) = bd;
|
||||
|
||||
// return the unsigned char* buffer b2, which contains the uncompressed data.
|
||||
std::vector<uint8_t> decompressed_data(b2, b2 + bd);
|
||||
@@ -1365,7 +1373,8 @@ void memfill(const uint8_t* data, std::vector<uint8_t>& buffer, int buffer_pos,
|
||||
auto b = data[offset + 1];
|
||||
for (int i = 0; i < length; i = i + 2) {
|
||||
buffer[buffer_pos + i] = a;
|
||||
if ((i + 1) < length) buffer[buffer_pos + i + 1] = b;
|
||||
if ((i + 1) < length)
|
||||
buffer[buffer_pos + i + 1] = b;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -124,17 +124,18 @@ void CompressionCommandAlternative(const uint8_t* rom_data,
|
||||
|
||||
// Compression V2
|
||||
|
||||
void CheckByteRepeatV2(const uint8_t* data, uint& src_pos, const unsigned int last_pos,
|
||||
CompressionCommand& cmd);
|
||||
void CheckByteRepeatV2(const uint8_t* data, uint& src_pos,
|
||||
const unsigned int last_pos, CompressionCommand& cmd);
|
||||
|
||||
void CheckWordRepeatV2(const uint8_t* data, uint& src_pos, const unsigned int last_pos,
|
||||
CompressionCommand& cmd);
|
||||
void CheckWordRepeatV2(const uint8_t* data, uint& src_pos,
|
||||
const unsigned int last_pos, CompressionCommand& cmd);
|
||||
|
||||
void CheckIncByteV2(const uint8_t* data, uint& src_pos, const unsigned int last_pos,
|
||||
CompressionCommand& cmd);
|
||||
void CheckIncByteV2(const uint8_t* data, uint& src_pos,
|
||||
const unsigned int last_pos, CompressionCommand& cmd);
|
||||
|
||||
void CheckIntraCopyV2(const uint8_t* data, uint& src_pos, const unsigned int last_pos,
|
||||
unsigned int start, CompressionCommand& cmd);
|
||||
void CheckIntraCopyV2(const uint8_t* data, uint& src_pos,
|
||||
const unsigned int last_pos, unsigned int start,
|
||||
CompressionCommand& cmd);
|
||||
|
||||
void ValidateForByteGainV2(const CompressionCommand& cmd, uint& max_win,
|
||||
uint& cmd_with_max);
|
||||
|
||||
@@ -20,11 +20,11 @@ void PaletteManager::Initialize(Rom* rom) {
|
||||
auto* palette_groups = rom_->mutable_palette_group();
|
||||
|
||||
// Snapshot all palette groups
|
||||
const char* group_names[] = {
|
||||
"ow_main", "ow_aux", "ow_animated", "hud", "global_sprites",
|
||||
"armors", "swords", "shields", "sprites_aux1", "sprites_aux2",
|
||||
"sprites_aux3", "dungeon_main", "grass", "3d_object", "ow_mini_map"
|
||||
};
|
||||
const char* group_names[] = {"ow_main", "ow_aux", "ow_animated",
|
||||
"hud", "global_sprites", "armors",
|
||||
"swords", "shields", "sprites_aux1",
|
||||
"sprites_aux2", "sprites_aux3", "dungeon_main",
|
||||
"grass", "3d_object", "ow_mini_map"};
|
||||
|
||||
for (const auto& group_name : group_names) {
|
||||
try {
|
||||
@@ -51,8 +51,7 @@ void PaletteManager::Initialize(Rom* rom) {
|
||||
// ========== Color Operations ==========
|
||||
|
||||
SnesColor PaletteManager::GetColor(const std::string& group_name,
|
||||
int palette_index,
|
||||
int color_index) const {
|
||||
int palette_index, int color_index) const {
|
||||
const auto* group = GetGroup(group_name);
|
||||
if (!group || palette_index < 0 || palette_index >= group->size()) {
|
||||
return SnesColor();
|
||||
@@ -67,8 +66,8 @@ SnesColor PaletteManager::GetColor(const std::string& group_name,
|
||||
}
|
||||
|
||||
absl::Status PaletteManager::SetColor(const std::string& group_name,
|
||||
int palette_index, int color_index,
|
||||
const SnesColor& new_color) {
|
||||
int palette_index, int color_index,
|
||||
const SnesColor& new_color) {
|
||||
if (!IsInitialized()) {
|
||||
return absl::FailedPreconditionError("PaletteManager not initialized");
|
||||
}
|
||||
@@ -80,16 +79,14 @@ absl::Status PaletteManager::SetColor(const std::string& group_name,
|
||||
}
|
||||
|
||||
if (palette_index < 0 || palette_index >= group->size()) {
|
||||
return absl::InvalidArgumentError(
|
||||
absl::StrFormat("Palette index %d out of range [0, %d)", palette_index,
|
||||
group->size()));
|
||||
return absl::InvalidArgumentError(absl::StrFormat(
|
||||
"Palette index %d out of range [0, %d)", palette_index, group->size()));
|
||||
}
|
||||
|
||||
auto* palette = group->mutable_palette(palette_index);
|
||||
if (color_index < 0 || color_index >= palette->size()) {
|
||||
return absl::InvalidArgumentError(
|
||||
absl::StrFormat("Color index %d out of range [0, %d)", color_index,
|
||||
palette->size()));
|
||||
return absl::InvalidArgumentError(absl::StrFormat(
|
||||
"Color index %d out of range [0, %d)", color_index, palette->size()));
|
||||
}
|
||||
|
||||
// Get original color
|
||||
@@ -108,9 +105,9 @@ absl::Status PaletteManager::SetColor(const std::string& group_name,
|
||||
now.time_since_epoch())
|
||||
.count();
|
||||
|
||||
PaletteColorChange change{group_name, palette_index, color_index,
|
||||
original_color, new_color,
|
||||
static_cast<uint64_t>(timestamp_ms)};
|
||||
PaletteColorChange change{group_name, palette_index,
|
||||
color_index, original_color,
|
||||
new_color, static_cast<uint64_t>(timestamp_ms)};
|
||||
RecordChange(change);
|
||||
|
||||
// Notify listeners
|
||||
@@ -123,30 +120,29 @@ absl::Status PaletteManager::SetColor(const std::string& group_name,
|
||||
auto timestamp_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
now.time_since_epoch())
|
||||
.count();
|
||||
batch_changes_.push_back(
|
||||
{group_name, palette_index, color_index, original_color, new_color,
|
||||
static_cast<uint64_t>(timestamp_ms)});
|
||||
batch_changes_.push_back({group_name, palette_index, color_index,
|
||||
original_color, new_color,
|
||||
static_cast<uint64_t>(timestamp_ms)});
|
||||
}
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status PaletteManager::ResetColor(const std::string& group_name,
|
||||
int palette_index, int color_index) {
|
||||
int palette_index, int color_index) {
|
||||
SnesColor original = GetOriginalColor(group_name, palette_index, color_index);
|
||||
return SetColor(group_name, palette_index, color_index, original);
|
||||
}
|
||||
|
||||
absl::Status PaletteManager::ResetPalette(const std::string& group_name,
|
||||
int palette_index) {
|
||||
int palette_index) {
|
||||
if (!IsInitialized()) {
|
||||
return absl::FailedPreconditionError("PaletteManager not initialized");
|
||||
}
|
||||
|
||||
// Check if original snapshot exists
|
||||
auto it = original_palettes_.find(group_name);
|
||||
if (it == original_palettes_.end() ||
|
||||
palette_index >= it->second.size()) {
|
||||
if (it == original_palettes_.end() || palette_index >= it->second.size()) {
|
||||
return absl::NotFoundError("Original palette not found");
|
||||
}
|
||||
|
||||
@@ -163,8 +159,8 @@ absl::Status PaletteManager::ResetPalette(const std::string& group_name,
|
||||
modified_colors_[group_name].erase(palette_index);
|
||||
|
||||
// Notify listeners
|
||||
PaletteChangeEvent event{PaletteChangeEvent::Type::kPaletteReset,
|
||||
group_name, palette_index, -1};
|
||||
PaletteChangeEvent event{PaletteChangeEvent::Type::kPaletteReset, group_name,
|
||||
palette_index, -1};
|
||||
NotifyListeners(event);
|
||||
|
||||
return absl::OkStatus();
|
||||
@@ -190,7 +186,7 @@ bool PaletteManager::IsGroupModified(const std::string& group_name) const {
|
||||
}
|
||||
|
||||
bool PaletteManager::IsPaletteModified(const std::string& group_name,
|
||||
int palette_index) const {
|
||||
int palette_index) const {
|
||||
auto it = modified_palettes_.find(group_name);
|
||||
if (it == modified_palettes_.end()) {
|
||||
return false;
|
||||
@@ -199,8 +195,7 @@ bool PaletteManager::IsPaletteModified(const std::string& group_name,
|
||||
}
|
||||
|
||||
bool PaletteManager::IsColorModified(const std::string& group_name,
|
||||
int palette_index,
|
||||
int color_index) const {
|
||||
int palette_index, int color_index) const {
|
||||
auto group_it = modified_colors_.find(group_name);
|
||||
if (group_it == modified_colors_.end()) {
|
||||
return false;
|
||||
@@ -253,7 +248,8 @@ absl::Status PaletteManager::SaveGroup(const std::string& group_name) {
|
||||
if (color_it != modified_colors_[group_name].end()) {
|
||||
for (int color_idx : color_it->second) {
|
||||
// Calculate ROM address using the helper function
|
||||
uint32_t address = GetPaletteAddress(group_name, palette_idx, color_idx);
|
||||
uint32_t address =
|
||||
GetPaletteAddress(group_name, palette_idx, color_idx);
|
||||
|
||||
// Write color to ROM - write the 16-bit SNES color value
|
||||
rom_->WriteShort(address, (*palette)[color_idx].snes());
|
||||
@@ -347,8 +343,7 @@ void PaletteManager::DiscardAllChanges() {
|
||||
ClearHistory();
|
||||
|
||||
// Notify listeners
|
||||
PaletteChangeEvent event{PaletteChangeEvent::Type::kAllDiscarded, "", -1,
|
||||
-1};
|
||||
PaletteChangeEvent event{PaletteChangeEvent::Type::kAllDiscarded, "", -1, -1};
|
||||
NotifyListeners(event);
|
||||
}
|
||||
|
||||
@@ -485,8 +480,8 @@ const PaletteGroup* PaletteManager::GetGroup(
|
||||
}
|
||||
|
||||
SnesColor PaletteManager::GetOriginalColor(const std::string& group_name,
|
||||
int palette_index,
|
||||
int color_index) const {
|
||||
int palette_index,
|
||||
int color_index) const {
|
||||
auto it = original_palettes_.find(group_name);
|
||||
if (it == original_palettes_.end() || palette_index >= it->second.size()) {
|
||||
return SnesColor();
|
||||
@@ -519,7 +514,7 @@ void PaletteManager::NotifyListeners(const PaletteChangeEvent& event) {
|
||||
}
|
||||
|
||||
void PaletteManager::MarkModified(const std::string& group_name,
|
||||
int palette_index, int color_index) {
|
||||
int palette_index, int color_index) {
|
||||
modified_palettes_[group_name].insert(palette_index);
|
||||
modified_colors_[group_name][palette_index].insert(color_index);
|
||||
}
|
||||
|
||||
@@ -23,12 +23,12 @@ namespace gfx {
|
||||
* @brief Represents a single color change operation
|
||||
*/
|
||||
struct PaletteColorChange {
|
||||
std::string group_name; ///< Palette group name (e.g., "ow_main")
|
||||
int palette_index; ///< Index of palette within group
|
||||
int color_index; ///< Index of color within palette
|
||||
SnesColor original_color; ///< Original color before change
|
||||
SnesColor new_color; ///< New color after change
|
||||
uint64_t timestamp_ms; ///< Timestamp in milliseconds
|
||||
std::string group_name; ///< Palette group name (e.g., "ow_main")
|
||||
int palette_index; ///< Index of palette within group
|
||||
int color_index; ///< Index of color within palette
|
||||
SnesColor original_color; ///< Original color before change
|
||||
SnesColor new_color; ///< New color after change
|
||||
uint64_t timestamp_ms; ///< Timestamp in milliseconds
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -36,12 +36,12 @@ struct PaletteColorChange {
|
||||
*/
|
||||
struct PaletteChangeEvent {
|
||||
enum class Type {
|
||||
kColorChanged, ///< Single color was modified
|
||||
kPaletteReset, ///< Entire palette was reset
|
||||
kGroupSaved, ///< Palette group was saved to ROM
|
||||
kGroupDiscarded, ///< Palette group changes were discarded
|
||||
kAllSaved, ///< All changes saved to ROM
|
||||
kAllDiscarded ///< All changes discarded
|
||||
kColorChanged, ///< Single color was modified
|
||||
kPaletteReset, ///< Entire palette was reset
|
||||
kGroupSaved, ///< Palette group was saved to ROM
|
||||
kGroupDiscarded, ///< Palette group changes were discarded
|
||||
kAllSaved, ///< All changes saved to ROM
|
||||
kAllDiscarded ///< All changes discarded
|
||||
};
|
||||
|
||||
Type type;
|
||||
@@ -264,7 +264,7 @@ class PaletteManager {
|
||||
|
||||
/// Helper: Get original color from snapshot
|
||||
SnesColor GetOriginalColor(const std::string& group_name, int palette_index,
|
||||
int color_index) const;
|
||||
int color_index) const;
|
||||
|
||||
/// Helper: Record a change for undo
|
||||
void RecordChange(const PaletteColorChange& change);
|
||||
@@ -286,13 +286,11 @@ class PaletteManager {
|
||||
|
||||
/// Original palette snapshots (loaded from ROM for reset/comparison)
|
||||
/// Key: group_name, Value: vector of original palettes
|
||||
std::unordered_map<std::string, std::vector<SnesPalette>>
|
||||
original_palettes_;
|
||||
std::unordered_map<std::string, std::vector<SnesPalette>> original_palettes_;
|
||||
|
||||
/// Modified tracking
|
||||
/// Key: group_name, Value: set of modified palette indices
|
||||
std::unordered_map<std::string, std::unordered_set<int>>
|
||||
modified_palettes_;
|
||||
std::unordered_map<std::string, std::unordered_set<int>> modified_palettes_;
|
||||
|
||||
/// Detailed color modification tracking
|
||||
/// Key: group_name, Value: map of palette_index -> set of color indices
|
||||
|
||||
@@ -175,14 +175,13 @@ absl::Status SaveCgx(uint8_t bpp, std::string_view filename,
|
||||
|
||||
file.write(reinterpret_cast<const char*>(&header), sizeof(CgxHeader));
|
||||
file.write(reinterpret_cast<const char*>(cgx_data.data()), cgx_data.size());
|
||||
file.write(reinterpret_cast<const char*>(cgx_header.data()), cgx_header.size());
|
||||
file.write(reinterpret_cast<const char*>(cgx_header.data()),
|
||||
cgx_header.size());
|
||||
|
||||
file.close();
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
|
||||
|
||||
std::vector<SDL_Color> DecodeColFile(const std::string_view filename) {
|
||||
std::vector<SDL_Color> decoded_col;
|
||||
std::ifstream file(filename.data(), std::ios::binary | std::ios::ate);
|
||||
@@ -218,16 +217,16 @@ std::vector<SDL_Color> DecodeColFile(const std::string_view filename) {
|
||||
return decoded_col;
|
||||
}
|
||||
|
||||
absl::Status SaveCol(std::string_view filename, const std::vector<SDL_Color>& palette) {
|
||||
absl::Status SaveCol(std::string_view filename,
|
||||
const std::vector<SDL_Color>& palette) {
|
||||
std::ofstream file(filename.data(), std::ios::binary);
|
||||
if (!file.is_open()) {
|
||||
return absl::NotFoundError("Could not open file for writing.");
|
||||
}
|
||||
|
||||
for (const auto& color : palette) {
|
||||
uint16_t snes_color = ((color.b >> 3) << 10) |
|
||||
((color.g >> 3) << 5) |
|
||||
(color.r >> 3);
|
||||
uint16_t snes_color =
|
||||
((color.b >> 3) << 10) | ((color.g >> 3) << 5) | (color.r >> 3);
|
||||
file.write(reinterpret_cast<const char*>(&snes_color), sizeof(snes_color));
|
||||
}
|
||||
|
||||
|
||||
@@ -97,7 +97,8 @@ absl::Status DecodeObjFile(
|
||||
/**
|
||||
* @brief Save Col file (palette data)
|
||||
*/
|
||||
absl::Status SaveCol(std::string_view filename, const std::vector<SDL_Color>& palette);
|
||||
absl::Status SaveCol(std::string_view filename,
|
||||
const std::vector<SDL_Color>& palette);
|
||||
|
||||
} // namespace gfx
|
||||
} // namespace yaze
|
||||
|
||||
Reference in New Issue
Block a user