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:
scawful
2025-11-20 01:35:33 -05:00
parent c2bb90a3f1
commit fa3da8fc27
600 changed files with 32605 additions and 27962 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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_;

View File

@@ -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) {

View File

@@ -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;

View File

@@ -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_);

View File

@@ -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;

View File

@@ -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;

View File

@@ -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

View File

@@ -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)

View File

@@ -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 &regions_[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

View File

@@ -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

View File

@@ -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);

View File

@@ -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 = {});

View File

@@ -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

View File

@@ -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_;

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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))

View File

@@ -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;
}

View File

@@ -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) {

View File

@@ -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

View File

@@ -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_;

View File

@@ -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;
}
}

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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));
}

View File

@@ -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