368 lines
11 KiB
C++
368 lines
11 KiB
C++
#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
|