refactor(graphics): 移除纹理图集功能及相关实现代码
This commit is contained in:
parent
b4a55239aa
commit
d81f0c1e45
|
|
@ -1,185 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <core/color.h>
|
|
||||||
#include <core/rect.h>
|
|
||||||
#include <core/types.h>
|
|
||||||
#include <core/vec2.h>
|
|
||||||
#include <graphics/opengl/gl_texture.h>
|
|
||||||
#include <graphics/texture.h>
|
|
||||||
#include <memory>
|
|
||||||
#include <string>
|
|
||||||
#include <unordered_map>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 纹理图集 - 自动将小纹理合并到大图集以减少 DrawCall
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 图集中的单个纹理条目
|
|
||||||
*/
|
|
||||||
struct AtlasEntry {
|
|
||||||
std::string name; // 原始纹理名称/路径
|
|
||||||
Rect uvRect; // 在图集中的 UV 坐标范围
|
|
||||||
Vec2 originalSize; // 原始纹理尺寸
|
|
||||||
uint32_t padding; // 边距(用于避免纹理 bleeding)
|
|
||||||
|
|
||||||
AtlasEntry() : uvRect(), originalSize(), padding(2) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 纹理图集页面
|
|
||||||
* 当单个图集放不下时,创建多个页面
|
|
||||||
*/
|
|
||||||
class TextureAtlasPage {
|
|
||||||
public:
|
|
||||||
static constexpr int DEFAULT_SIZE = 2048;
|
|
||||||
static constexpr int MAX_SIZE = 4096;
|
|
||||||
static constexpr int MIN_TEXTURE_SIZE = 32; // 小于此大小的纹理才考虑合并
|
|
||||||
static constexpr int PADDING = 2; // 纹理间边距
|
|
||||||
|
|
||||||
TextureAtlasPage(int width = DEFAULT_SIZE, int height = DEFAULT_SIZE);
|
|
||||||
~TextureAtlasPage();
|
|
||||||
|
|
||||||
// 尝试添加纹理到图集
|
|
||||||
// 返回是否成功,如果成功则输出 uvRect
|
|
||||||
bool tryAddTexture(const std::string &name, int texWidth, int texHeight,
|
|
||||||
const uint8_t *pixels, Rect &outUvRect);
|
|
||||||
|
|
||||||
// 获取图集纹理
|
|
||||||
IntrusivePtr<Texture> getTexture() const { return texture_; }
|
|
||||||
|
|
||||||
// 获取条目
|
|
||||||
const AtlasEntry *getEntry(const std::string &name) const;
|
|
||||||
|
|
||||||
// 获取使用率
|
|
||||||
float getUsageRatio() const;
|
|
||||||
|
|
||||||
// 获取尺寸
|
|
||||||
int width() const { return width_; }
|
|
||||||
int height() const { return height_; }
|
|
||||||
|
|
||||||
// 是否已满
|
|
||||||
bool isFull() const { return isFull_; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
int width_, height_;
|
|
||||||
IntrusivePtr<Texture> texture_;
|
|
||||||
std::unordered_map<std::string, AtlasEntry> entries_;
|
|
||||||
|
|
||||||
// 矩形打包数据
|
|
||||||
struct PackNode {
|
|
||||||
int x, y, width, height;
|
|
||||||
bool used;
|
|
||||||
std::unique_ptr<PackNode> left;
|
|
||||||
std::unique_ptr<PackNode> right;
|
|
||||||
|
|
||||||
PackNode(int x_, int y_, int w, int h)
|
|
||||||
: x(x_), y(y_), width(w), height(h), used(false) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
std::unique_ptr<PackNode> root_;
|
|
||||||
bool isFull_;
|
|
||||||
int usedArea_;
|
|
||||||
|
|
||||||
// 递归插入
|
|
||||||
PackNode *insert(PackNode *node, int width, int height);
|
|
||||||
void writePixels(int x, int y, int w, int h, const uint8_t *pixels);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 纹理图集管理器
|
|
||||||
* 自动管理多个图集页面,提供统一的纹理查询接口
|
|
||||||
*/
|
|
||||||
class TextureAtlas {
|
|
||||||
public:
|
|
||||||
TextureAtlas();
|
|
||||||
~TextureAtlas();
|
|
||||||
|
|
||||||
// 初始化
|
|
||||||
void init(int pageSize = TextureAtlasPage::DEFAULT_SIZE);
|
|
||||||
|
|
||||||
// 添加纹理到图集
|
|
||||||
// 如果纹理太大,返回 false,应该作为独立纹理加载
|
|
||||||
bool addTexture(const std::string &name, int width, int height,
|
|
||||||
const uint8_t *pixels);
|
|
||||||
|
|
||||||
// 查询纹理是否在图集中
|
|
||||||
bool contains(const std::string &name) const;
|
|
||||||
|
|
||||||
// 获取纹理在图集中的信息
|
|
||||||
// 返回图集纹理和 UV 坐标
|
|
||||||
const Texture *getAtlasTexture(const std::string &name) const;
|
|
||||||
Rect getUVRect(const std::string &name) const;
|
|
||||||
|
|
||||||
// 获取原始纹理尺寸
|
|
||||||
Vec2 getOriginalSize(const std::string &name) const;
|
|
||||||
|
|
||||||
// 获取所有图集页面
|
|
||||||
const std::vector<std::unique_ptr<TextureAtlasPage>> &getPages() const {
|
|
||||||
return pages_;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取总使用率
|
|
||||||
float getTotalUsageRatio() const;
|
|
||||||
|
|
||||||
// 清空所有图集
|
|
||||||
void clear();
|
|
||||||
|
|
||||||
// 设置是否启用自动图集
|
|
||||||
void setEnabled(bool enabled) { enabled_ = enabled; }
|
|
||||||
bool isEnabled() const { return enabled_; }
|
|
||||||
|
|
||||||
// 设置纹理大小阈值(小于此大小的纹理才进入图集)
|
|
||||||
void setSizeThreshold(int threshold) { sizeThreshold_ = threshold; }
|
|
||||||
int getSizeThreshold() const { return sizeThreshold_; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::vector<std::unique_ptr<TextureAtlasPage>> pages_;
|
|
||||||
std::unordered_map<std::string, TextureAtlasPage *> entryToPage_;
|
|
||||||
|
|
||||||
int pageSize_;
|
|
||||||
int sizeThreshold_;
|
|
||||||
bool enabled_;
|
|
||||||
bool initialized_;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 全局图集管理器(单例)
|
|
||||||
*/
|
|
||||||
class TextureAtlasManager {
|
|
||||||
public:
|
|
||||||
static TextureAtlasManager &getInstance();
|
|
||||||
|
|
||||||
// 获取主图集
|
|
||||||
TextureAtlas &getAtlas() { return atlas_; }
|
|
||||||
|
|
||||||
// 快捷方法
|
|
||||||
bool addTexture(const std::string &name, int width, int height,
|
|
||||||
const uint8_t *pixels) {
|
|
||||||
return atlas_.addTexture(name, width, height, pixels);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool contains(const std::string &name) const { return atlas_.contains(name); }
|
|
||||||
|
|
||||||
const Texture *getAtlasTexture(const std::string &name) const {
|
|
||||||
return atlas_.getAtlasTexture(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
Rect getUVRect(const std::string &name) const {
|
|
||||||
return atlas_.getUVRect(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
TextureAtlasManager() = default;
|
|
||||||
~TextureAtlasManager() = default;
|
|
||||||
|
|
||||||
TextureAtlasManager(const TextureAtlasManager &) = delete;
|
|
||||||
TextureAtlasManager &operator=(const TextureAtlasManager &) = delete;
|
|
||||||
|
|
||||||
TextureAtlas atlas_;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,272 +0,0 @@
|
||||||
#include <algorithm>
|
|
||||||
#include <cstring>
|
|
||||||
#include <graphics/texture_atlas.h>
|
|
||||||
#include <utils/logger.h>
|
|
||||||
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// TextureAtlasPage 实现
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
TextureAtlasPage::TextureAtlasPage(int width, int height)
|
|
||||||
: width_(width), height_(height), isFull_(false), usedArea_(0) {
|
|
||||||
// 创建空白纹理
|
|
||||||
std::vector<uint8_t> emptyData(width * height * 4, 0);
|
|
||||||
texture_ = makeRef<GLTexture>(width, height, emptyData.data(), 4);
|
|
||||||
|
|
||||||
// 初始化矩形打包根节点
|
|
||||||
root_ = std::make_unique<PackNode>(0, 0, width, height);
|
|
||||||
|
|
||||||
E2D_LOG_INFO("Created texture atlas page: {}x{}", width, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
TextureAtlasPage::~TextureAtlasPage() = default;
|
|
||||||
|
|
||||||
bool TextureAtlasPage::tryAddTexture(const std::string &name, int texWidth,
|
|
||||||
int texHeight, const uint8_t *pixels,
|
|
||||||
Rect &outUvRect) {
|
|
||||||
if (isFull_) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 添加边距
|
|
||||||
int paddedWidth = texWidth + 2 * PADDING;
|
|
||||||
int paddedHeight = texHeight + 2 * PADDING;
|
|
||||||
|
|
||||||
// 如果纹理太大,无法放入
|
|
||||||
if (paddedWidth > width_ || paddedHeight > height_) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 尝试插入
|
|
||||||
PackNode *node = insert(root_.get(), paddedWidth, paddedHeight);
|
|
||||||
if (node == nullptr) {
|
|
||||||
// 无法放入,标记为满
|
|
||||||
isFull_ = true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 写入像素数据(跳过边距区域)
|
|
||||||
writePixels(node->x + PADDING, node->y + PADDING, texWidth, texHeight,
|
|
||||||
pixels);
|
|
||||||
|
|
||||||
// 创建条目
|
|
||||||
AtlasEntry entry;
|
|
||||||
entry.name = name;
|
|
||||||
entry.originalSize =
|
|
||||||
Vec2(static_cast<float>(texWidth), static_cast<float>(texHeight));
|
|
||||||
entry.padding = PADDING;
|
|
||||||
|
|
||||||
// 计算 UV 坐标(考虑边距)
|
|
||||||
float u1 = static_cast<float>(node->x + PADDING) / width_;
|
|
||||||
float v1 = static_cast<float>(node->y + PADDING) / height_;
|
|
||||||
float u2 = static_cast<float>(node->x + PADDING + texWidth) / width_;
|
|
||||||
float v2 = static_cast<float>(node->y + PADDING + texHeight) / height_;
|
|
||||||
|
|
||||||
entry.uvRect = Rect(u1, v1, u2 - u1, v2 - v1);
|
|
||||||
outUvRect = entry.uvRect;
|
|
||||||
|
|
||||||
entries_[name] = std::move(entry);
|
|
||||||
usedArea_ += paddedWidth * paddedHeight;
|
|
||||||
|
|
||||||
E2D_LOG_DEBUG("Added texture '{}' to atlas: {}x{} at ({}, {})", name,
|
|
||||||
texWidth, texHeight, node->x, node->y);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
TextureAtlasPage::PackNode *TextureAtlasPage::insert(PackNode *node, int width,
|
|
||||||
int height) {
|
|
||||||
if (node == nullptr) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果节点已被使用,尝试子节点
|
|
||||||
if (node->used) {
|
|
||||||
PackNode *result = insert(node->left.get(), width, height);
|
|
||||||
if (result != nullptr) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
return insert(node->right.get(), width, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查是否适合
|
|
||||||
if (width > node->width || height > node->height) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果刚好合适,使用此节点
|
|
||||||
if (width == node->width && height == node->height) {
|
|
||||||
node->used = true;
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 需要分割节点
|
|
||||||
int dw = node->width - width;
|
|
||||||
int dh = node->height - height;
|
|
||||||
|
|
||||||
if (dw > dh) {
|
|
||||||
// 水平分割
|
|
||||||
node->left =
|
|
||||||
std::make_unique<PackNode>(node->x, node->y, width, node->height);
|
|
||||||
node->right =
|
|
||||||
std::make_unique<PackNode>(node->x + width, node->y, dw, node->height);
|
|
||||||
} else {
|
|
||||||
// 垂直分割
|
|
||||||
node->left =
|
|
||||||
std::make_unique<PackNode>(node->x, node->y, node->width, height);
|
|
||||||
node->right =
|
|
||||||
std::make_unique<PackNode>(node->x, node->y + height, node->width, dh);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 递归插入到左子节点
|
|
||||||
return insert(node->left.get(), width, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
void TextureAtlasPage::writePixels(int x, int y, int w, int h,
|
|
||||||
const uint8_t *pixels) {
|
|
||||||
if (texture_ == nullptr || pixels == nullptr) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 使用 glTexSubImage2D 更新纹理数据
|
|
||||||
GLuint texID = static_cast<GLuint>(
|
|
||||||
reinterpret_cast<uintptr_t>(texture_->getNativeHandle()));
|
|
||||||
|
|
||||||
glBindTexture(GL_TEXTURE_2D, texID);
|
|
||||||
glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, w, h, GL_RGBA, GL_UNSIGNED_BYTE,
|
|
||||||
pixels);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
const AtlasEntry *TextureAtlasPage::getEntry(const std::string &name) const {
|
|
||||||
auto it = entries_.find(name);
|
|
||||||
if (it != entries_.end()) {
|
|
||||||
return &it->second;
|
|
||||||
}
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
float TextureAtlasPage::getUsageRatio() const {
|
|
||||||
return static_cast<float>(usedArea_) / (width_ * height_);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// TextureAtlas 实现
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
TextureAtlas::TextureAtlas()
|
|
||||||
: pageSize_(TextureAtlasPage::DEFAULT_SIZE), sizeThreshold_(256),
|
|
||||||
enabled_(true), initialized_(false) {}
|
|
||||||
|
|
||||||
TextureAtlas::~TextureAtlas() = default;
|
|
||||||
|
|
||||||
void TextureAtlas::init(int pageSize) {
|
|
||||||
pageSize_ = pageSize;
|
|
||||||
initialized_ = true;
|
|
||||||
E2D_LOG_INFO("TextureAtlas initialized with page size: {}", pageSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool TextureAtlas::addTexture(const std::string &name, int width, int height,
|
|
||||||
const uint8_t *pixels) {
|
|
||||||
if (!enabled_ || !initialized_) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查是否已存在
|
|
||||||
if (contains(name)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查纹理大小
|
|
||||||
if (width > sizeThreshold_ || height > sizeThreshold_) {
|
|
||||||
E2D_LOG_DEBUG("Texture '{}' too large for atlas ({}x{} > {}), skipping",
|
|
||||||
name, width, height, sizeThreshold_);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 尝试添加到现有页面
|
|
||||||
Rect uvRect;
|
|
||||||
for (auto &page : pages_) {
|
|
||||||
if (page->tryAddTexture(name, width, height, pixels, uvRect)) {
|
|
||||||
entryToPage_[name] = page.get();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建新页面
|
|
||||||
auto newPage = std::make_unique<TextureAtlasPage>(pageSize_, pageSize_);
|
|
||||||
if (newPage->tryAddTexture(name, width, height, pixels, uvRect)) {
|
|
||||||
entryToPage_[name] = newPage.get();
|
|
||||||
pages_.push_back(std::move(newPage));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
E2D_LOG_WARN("Failed to add texture '{}' to atlas", name);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool TextureAtlas::contains(const std::string &name) const {
|
|
||||||
return entryToPage_.find(name) != entryToPage_.end();
|
|
||||||
}
|
|
||||||
|
|
||||||
const Texture *TextureAtlas::getAtlasTexture(const std::string &name) const {
|
|
||||||
auto it = entryToPage_.find(name);
|
|
||||||
if (it != entryToPage_.end()) {
|
|
||||||
return it->second->getTexture().get();
|
|
||||||
}
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
Rect TextureAtlas::getUVRect(const std::string &name) const {
|
|
||||||
auto it = entryToPage_.find(name);
|
|
||||||
if (it != entryToPage_.end()) {
|
|
||||||
const AtlasEntry *entry = it->second->getEntry(name);
|
|
||||||
if (entry != nullptr) {
|
|
||||||
return entry->uvRect;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Rect(0, 0, 1, 1); // 默认 UV
|
|
||||||
}
|
|
||||||
|
|
||||||
Vec2 TextureAtlas::getOriginalSize(const std::string &name) const {
|
|
||||||
auto it = entryToPage_.find(name);
|
|
||||||
if (it != entryToPage_.end()) {
|
|
||||||
const AtlasEntry *entry = it->second->getEntry(name);
|
|
||||||
if (entry != nullptr) {
|
|
||||||
return entry->originalSize;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Vec2(0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
float TextureAtlas::getTotalUsageRatio() const {
|
|
||||||
if (pages_.empty()) {
|
|
||||||
return 0.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
float total = 0.0f;
|
|
||||||
for (const auto &page : pages_) {
|
|
||||||
total += page->getUsageRatio();
|
|
||||||
}
|
|
||||||
return total / pages_.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
void TextureAtlas::clear() {
|
|
||||||
pages_.clear();
|
|
||||||
entryToPage_.clear();
|
|
||||||
E2D_LOG_INFO("TextureAtlas cleared");
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// TextureAtlasManager 单例实现
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
TextureAtlasManager &TextureAtlasManager::getInstance() {
|
|
||||||
static TextureAtlasManager instance;
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
Loading…
Reference in New Issue