Extra2D/src/renderer/texture_atlas.cpp

368 lines
11 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#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