feat: Introduce TextureAtlas for efficient texture management
- Added TextureAtlas class to manage multiple textures packed into a single large texture, improving rendering performance and reducing GPU state changes. - Implemented methods for allocating regions, packing bitmaps, and drawing regions from the atlas. - Removed the DrawDungeonTabView function from DungeonCanvasViewer as it is no longer needed with the new EditorCard system. - Updated CMake configuration to include texture_atlas.cc in the build process. - Refactored Room class to eliminate dependency on Arena graphics sheets, transitioning to per-room graphics for rendering.
This commit is contained in:
@@ -12,6 +12,7 @@ set(
|
||||
app/gfx/snes_palette.cc
|
||||
app/gfx/snes_tile.cc
|
||||
app/gfx/snes_color.cc
|
||||
app/gfx/texture_atlas.cc
|
||||
app/gfx/tilemap.cc
|
||||
app/gfx/graphics_optimizer.cc
|
||||
app/gfx/bpp_format_manager.cc
|
||||
|
||||
152
src/app/gfx/texture_atlas.cc
Normal file
152
src/app/gfx/texture_atlas.cc
Normal file
@@ -0,0 +1,152 @@
|
||||
#include "texture_atlas.h"
|
||||
|
||||
#include "util/log.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace gfx {
|
||||
|
||||
TextureAtlas::TextureAtlas(int width, int height)
|
||||
: width_(width), height_(height) {
|
||||
// Create atlas bitmap with initial empty data
|
||||
std::vector<uint8_t> empty_data(width * height, 0);
|
||||
atlas_bitmap_ = Bitmap(width, height, 8, empty_data);
|
||||
LOG_DEBUG("[TextureAtlas]", "Created %dx%d atlas", width, 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);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
AtlasRegion region;
|
||||
region.x = pack_x;
|
||||
region.y = pack_y;
|
||||
region.width = 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);
|
||||
|
||||
return ®ions_[source_id];
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
void TextureAtlas::FreeRegion(int source_id) {
|
||||
auto it = regions_.find(source_id);
|
||||
if (it != regions_.end()) {
|
||||
it->second.in_use = false;
|
||||
LOG_DEBUG("[TextureAtlas]", "Freed region for source %d", source_id);
|
||||
}
|
||||
}
|
||||
|
||||
void TextureAtlas::Clear() {
|
||||
regions_.clear();
|
||||
next_x_ = 0;
|
||||
next_y_ = 0;
|
||||
row_height_ = 0;
|
||||
LOG_DEBUG("[TextureAtlas]", "Cleared all regions");
|
||||
}
|
||||
|
||||
const TextureAtlas::AtlasRegion* TextureAtlas::GetRegion(int source_id) const {
|
||||
auto it = regions_.find(source_id);
|
||||
if (it != regions_.end() && it->second.in_use) {
|
||||
return &it->second;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
bool TextureAtlas::TryPackRect(int width, int height, int& out_x, int& out_y) {
|
||||
// Simple shelf packing algorithm
|
||||
// Try to pack in current row
|
||||
if (next_x_ + width <= width_) {
|
||||
// Fits in current row
|
||||
out_x = next_x_;
|
||||
out_y = next_y_;
|
||||
next_x_ += width;
|
||||
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_;
|
||||
out_y = next_y_;
|
||||
next_x_ += width;
|
||||
row_height_ = height;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Atlas is full
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace gfx
|
||||
} // namespace yaze
|
||||
|
||||
150
src/app/gfx/texture_atlas.h
Normal file
150
src/app/gfx/texture_atlas.h
Normal file
@@ -0,0 +1,150 @@
|
||||
#ifndef YAZE_APP_GFX_TEXTURE_ATLAS_H
|
||||
#define YAZE_APP_GFX_TEXTURE_ATLAS_H
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "app/gfx/bitmap.h"
|
||||
#include "absl/status/status.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace gfx {
|
||||
|
||||
/**
|
||||
* @class TextureAtlas
|
||||
* @brief Manages multiple textures packed into a single large texture for performance
|
||||
*
|
||||
* Future-proof infrastructure for combining multiple room textures into one atlas.
|
||||
* This reduces GPU state changes and improves rendering performance when many rooms are open.
|
||||
*
|
||||
* Benefits:
|
||||
* - Fewer texture binds per frame
|
||||
* - Better memory locality
|
||||
* - Reduced VRAM fragmentation
|
||||
* - Easier batch rendering
|
||||
*
|
||||
* Usage (Future):
|
||||
* TextureAtlas atlas(2048, 2048);
|
||||
* auto region = atlas.AllocateRegion(room_id, 512, 512);
|
||||
* atlas.PackBitmap(room.bg1_buffer().bitmap(), *region);
|
||||
* atlas.DrawRegion(room_id, x, y);
|
||||
*/
|
||||
class TextureAtlas {
|
||||
public:
|
||||
/**
|
||||
* @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
|
||||
};
|
||||
|
||||
/**
|
||||
* @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)
|
||||
* @param width Required width in pixels
|
||||
* @param height Required height in pixels
|
||||
* @return Pointer to allocated region, or nullptr if no space
|
||||
*
|
||||
* 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
|
||||
* @param region Region to pack into (must be pre-allocated)
|
||||
* @return Status of packing operation
|
||||
*
|
||||
* 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)
|
||||
* @param dest_x Destination X coordinate
|
||||
* @param dest_y Destination Y coordinate
|
||||
* @return Status of drawing operation
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
struct AtlasStats {
|
||||
int total_regions = 0;
|
||||
int used_regions = 0;
|
||||
int total_pixels = 0;
|
||||
int used_pixels = 0;
|
||||
float utilization = 0.0f; // Percentage of atlas in use
|
||||
};
|
||||
AtlasStats GetStats() const;
|
||||
|
||||
private:
|
||||
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);
|
||||
};
|
||||
|
||||
} // namespace gfx
|
||||
} // namespace yaze
|
||||
|
||||
#endif // YAZE_APP_GFX_TEXTURE_ATLAS_H
|
||||
|
||||
Reference in New Issue
Block a user