Extra2D/src/renderer/texture_atlas.cpp

368 lines
11 KiB
C++
Raw Normal View History

#include <renderer/texture_atlas.h>
#include <utils/logger.h>
#include <algorithm>
#include <cstring>
namespace extra2d {
// ========================================
// TextureAtlas 实现
// ========================================
TextureAtlas::TextureAtlas() = default;
TextureAtlas::~TextureAtlas() {
shutdown();
}
TextureAtlas::TextureAtlas(TextureAtlas&& other) noexcept
: width_(other.width_)
, height_(other.height_)
, atlasTexture_(std::move(other.atlasTexture_))
, regions_(std::move(other.regions_))
, pendingTextures_(std::move(other.pendingTextures_))
, packContext_(std::move(other.packContext_))
, packNodes_(std::move(other.packNodes_))
, finalized_(other.finalized_) {
other.width_ = 0;
other.height_ = 0;
other.finalized_ = false;
}
TextureAtlas& TextureAtlas::operator=(TextureAtlas&& other) noexcept {
if (this != &other) {
shutdown();
width_ = other.width_;
height_ = other.height_;
atlasTexture_ = std::move(other.atlasTexture_);
regions_ = std::move(other.regions_);
pendingTextures_ = std::move(other.pendingTextures_);
packContext_ = std::move(other.packContext_);
packNodes_ = std::move(other.packNodes_);
finalized_ = other.finalized_;
other.width_ = 0;
other.height_ = 0;
other.finalized_ = false;
}
return *this;
}
bool TextureAtlas::initialize(int width, int height) {
shutdown();
if (width <= 0 || height <= 0) {
E2D_LOG_ERROR("TextureAtlas::initialize: Invalid size {}x{}", width, height);
return false;
}
width_ = width;
height_ = height;
// 初始化 stb_rect_pack
packContext_ = std::make_unique<stbrp_context>();
packNodes_.resize(width);
stbrp_init_target(packContext_.get(), width, height, packNodes_.data(), width);
E2D_LOG_DEBUG("TextureAtlas initialized: {}x{}", width, height);
return true;
}
void TextureAtlas::shutdown() {
atlasTexture_.reset();
regions_.clear();
pendingTextures_.clear();
packContext_.reset();
packNodes_.clear();
width_ = 0;
height_ = 0;
finalized_ = false;
}
bool TextureAtlas::addTexture(const std::string& name, Ptr<Texture> texture) {
if (finalized_) {
E2D_LOG_WARN("TextureAtlas::addTexture: Cannot add texture to finalized atlas");
return false;
}
if (!texture) {
E2D_LOG_WARN("TextureAtlas::addTexture: Texture is null");
return false;
}
if (regions_.find(name) != regions_.end()) {
E2D_LOG_WARN("TextureAtlas::addTexture: Texture '{}' already exists", name);
return false;
}
// 获取纹理尺寸
int texWidth = static_cast<int>(texture->getWidth());
int texHeight = static_cast<int>(texture->getHeight());
if (texWidth <= 0 || texHeight <= 0) {
E2D_LOG_WARN("TextureAtlas::addTexture: Invalid texture size {}x{}", texWidth, texHeight);
return false;
}
// 检查纹理是否适合图集
if (texWidth > width_ || texHeight > height_) {
E2D_LOG_WARN("TextureAtlas::addTexture: Texture {}x{} too large for atlas {}x{}",
texWidth, texHeight, width_, height_);
return false;
}
// 创建待处理纹理信息
PendingTexture pending;
pending.name = name;
pending.width = texWidth;
pending.height = texHeight;
pending.format = texture->getFormat();
// 获取纹理数据
size_t dataSize = texWidth * texHeight * 4; // 假设 RGBA
pending.data.resize(dataSize);
// TODO: 实现 Texture::getData() 方法来读取像素数据
// 暂时使用占位数据
std::fill(pending.data.begin(), pending.data.end(), 255);
pendingTextures_.push_back(std::move(pending));
E2D_LOG_DEBUG("TextureAtlas: Added texture '{}' ({}x{})", name, texWidth, texHeight);
return true;
}
bool TextureAtlas::addTextureData(const std::string& name, const uint8_t* data,
int width, int height, TextureFormat format) {
if (finalized_) {
E2D_LOG_WARN("TextureAtlas::addTextureData: Cannot add texture to finalized atlas");
return false;
}
if (!data || width <= 0 || height <= 0) {
E2D_LOG_WARN("TextureAtlas::addTextureData: Invalid parameters");
return false;
}
if (regions_.find(name) != regions_.end()) {
E2D_LOG_WARN("TextureAtlas::addTextureData: Texture '{}' already exists", name);
return false;
}
if (width > width_ || height > height_) {
E2D_LOG_WARN("TextureAtlas::addTextureData: Texture {}x{} too large for atlas {}x{}",
width, height, width_, height_);
return false;
}
PendingTexture pending;
pending.name = name;
pending.width = width;
pending.height = height;
pending.format = format;
// 复制像素数据
int channels = (format == TextureFormat::RGBA8) ? 4 : 3;
size_t dataSize = width * height * channels;
pending.data.resize(dataSize);
std::memcpy(pending.data.data(), data, dataSize);
pendingTextures_.push_back(std::move(pending));
E2D_LOG_DEBUG("TextureAtlas: Added texture data '{}' ({}x{})", name, width, height);
return true;
}
bool TextureAtlas::finalize() {
if (finalized_) {
return true;
}
if (pendingTextures_.empty()) {
E2D_LOG_WARN("TextureAtlas::finalize: No textures to pack");
return false;
}
// 准备矩形数组
std::vector<stbrp_rect> rects;
rects.reserve(pendingTextures_.size());
for (size_t i = 0; i < pendingTextures_.size(); ++i) {
stbrp_rect rect;
rect.id = static_cast<int>(i);
rect.w = pendingTextures_[i].width;
rect.h = pendingTextures_[i].height;
rect.x = 0;
rect.y = 0;
rect.was_packed = 0;
rects.push_back(rect);
}
// 执行打包
int result = stbrp_pack_rects(packContext_.get(), rects.data(), static_cast<int>(rects.size()));
if (!result) {
E2D_LOG_ERROR("TextureAtlas::finalize: Failed to pack all textures");
return false;
}
// 创建图集纹理数据
std::vector<uint8_t> atlasData(width_ * height_ * 4, 0); // RGBA 黑色背景
// 处理打包结果
for (const auto& rect : rects) {
if (!rect.was_packed) {
E2D_LOG_WARN("TextureAtlas::finalize: Texture {} not packed", rect.id);
continue;
}
const auto& pending = pendingTextures_[rect.id];
// 创建区域信息
AtlasRegion region;
region.x = rect.x;
region.y = rect.y;
region.width = rect.w;
region.height = rect.h;
regions_[pending.name] = region;
// 复制像素数据到图集
// TODO: 实现正确的像素格式转换
// 目前假设所有纹理都是 RGBA8
for (int y = 0; y < pending.height; ++y) {
for (int x = 0; x < pending.width; ++x) {
int srcIdx = (y * pending.width + x) * 4;
int dstIdx = ((rect.y + y) * width_ + (rect.x + x)) * 4;
if (srcIdx + 3 < static_cast<int>(pending.data.size()) &&
dstIdx + 3 < static_cast<int>(atlasData.size())) {
atlasData[dstIdx + 0] = pending.data[srcIdx + 0];
atlasData[dstIdx + 1] = pending.data[srcIdx + 1];
atlasData[dstIdx + 2] = pending.data[srcIdx + 2];
atlasData[dstIdx + 3] = pending.data[srcIdx + 3];
}
}
}
E2D_LOG_DEBUG("TextureAtlas: Packed '{}' at ({}, {}) size {}x{}",
pending.name, rect.x, rect.y, rect.w, rect.h);
}
// 创建图集纹理
atlasTexture_ = makePtr<Texture>();
if (!atlasTexture_->loadFromMemory(atlasData.data(), width_, height_, TextureFormat::RGBA8)) {
E2D_LOG_ERROR("TextureAtlas::finalize: Failed to create atlas texture");
return false;
}
// 清理待处理列表
pendingTextures_.clear();
finalized_ = true;
E2D_LOG_INFO("TextureAtlas finalized: {} textures packed into {}x{} atlas ({}% usage)",
regions_.size(), width_, height_,
static_cast<int>(getUsageRatio() * 100));
return true;
}
const AtlasRegion* TextureAtlas::getRegion(const std::string& name) const {
auto it = regions_.find(name);
if (it != regions_.end()) {
return &it->second;
}
return nullptr;
}
Rect TextureAtlas::getUVRect(const std::string& name) const {
const AtlasRegion* region = getRegion(name);
if (region) {
return region->getUVRect(width_, height_);
}
return Rect(0.0f, 0.0f, 1.0f, 1.0f);
}
bool TextureAtlas::hasTexture(const std::string& name) const {
return regions_.find(name) != regions_.end();
}
float TextureAtlas::getUsageRatio() const {
if (!finalized_ || regions_.empty()) {
return 0.0f;
}
int totalArea = 0;
for (const auto& pair : regions_) {
totalArea += pair.second.width * pair.second.height;
}
return static_cast<float>(totalArea) / (width_ * height_);
}
// ========================================
// AtlasBuilder 实现
// ========================================
Ptr<TextureAtlas> AtlasBuilder::build() {
if (textures_.empty()) {
E2D_LOG_WARN("AtlasBuilder::build: No textures to build");
return nullptr;
}
auto atlas = makePtr<TextureAtlas>();
if (!atlas->initialize(width_, height_)) {
return nullptr;
}
for (const auto& pair : textures_) {
atlas->addTexture(pair.first, pair.second);
}
if (!atlas->finalize()) {
return nullptr;
}
return atlas;
}
Ptr<TextureAtlas> AtlasBuilder::buildAuto() {
if (textures_.empty()) {
E2D_LOG_WARN("AtlasBuilder::buildAuto: No textures to build");
return nullptr;
}
// 计算总面积
int totalArea = 0;
int maxWidth = 0;
int maxHeight = 0;
for (const auto& pair : textures_) {
if (pair.second) {
int w = static_cast<int>(pair.second->getWidth());
int h = static_cast<int>(pair.second->getHeight());
totalArea += w * h;
maxWidth = std::max(maxWidth, w);
maxHeight = std::max(maxHeight, h);
}
}
// 选择合适的大小2 的幂)
int atlasSize = 64;
while (atlasSize < maxWidth || atlasSize < maxHeight || atlasSize * atlasSize < totalArea * 1.5f) {
atlasSize *= 2;
if (atlasSize > 8192) {
E2D_LOG_ERROR("AtlasBuilder::buildAuto: Textures too large for atlas");
return nullptr;
}
}
width_ = atlasSize;
height_ = atlasSize;
return build();
}
} // namespace extra2d