feat(渲染): 添加自动批处理功能并实现图片显示示例
添加自动精灵批处理功能,优化渲染性能 新增图片显示示例,展示如何使用RenderBackend抽象接口加载和显示图片 重构文本渲染示例以使用RenderBackend接口 添加flush方法用于手动控制批处理提交时机
This commit is contained in:
parent
6b4ce69657
commit
32e12b8c99
|
|
@ -48,6 +48,7 @@ public:
|
||||||
void drawSprite(const Texture &texture, const Vec2 &position,
|
void drawSprite(const Texture &texture, const Vec2 &position,
|
||||||
const Color &tint) override;
|
const Color &tint) override;
|
||||||
void endSpriteBatch() override;
|
void endSpriteBatch() override;
|
||||||
|
void flush() override;
|
||||||
|
|
||||||
void drawLine(const Vec2 &start, const Vec2 &end, const Color &color,
|
void drawLine(const Vec2 &start, const Vec2 &end, const Color &color,
|
||||||
float width) override;
|
float width) override;
|
||||||
|
|
@ -120,7 +121,16 @@ private:
|
||||||
int cachedViewportWidth_ = 0;
|
int cachedViewportWidth_ = 0;
|
||||||
int cachedViewportHeight_ = 0;
|
int cachedViewportHeight_ = 0;
|
||||||
|
|
||||||
|
// 自动批处理状态
|
||||||
|
bool batchActive_ = false; // 批处理是否激活
|
||||||
|
bool autoBatchEnabled_ = true; // 是否启用自动批处理
|
||||||
|
const Texture* currentBatchTexture_ = nullptr; // 当前批处理的纹理
|
||||||
|
std::vector<SpriteData> pendingSprites_; // 待提交的精灵
|
||||||
|
static constexpr size_t MAX_BATCH_SPRITES = 1000; // 最大批处理精灵数
|
||||||
|
|
||||||
void initShapeRendering();
|
void initShapeRendering();
|
||||||
|
void ensureBatchActive(); // 确保批处理已激活
|
||||||
|
void submitPendingSprites(); // 提交待处理的精灵
|
||||||
void flushShapeBatch();
|
void flushShapeBatch();
|
||||||
void flushLineBatch();
|
void flushLineBatch();
|
||||||
void addShapeVertex(float x, float y, const Color &color);
|
void addShapeVertex(float x, float y, const Color &color);
|
||||||
|
|
|
||||||
|
|
@ -78,6 +78,10 @@ public:
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
// 精灵批渲染
|
// 精灵批渲染
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
|
/**
|
||||||
|
* @brief 开始手动精灵批处理(高级用法)
|
||||||
|
* @note 一般情况下不需要调用,drawSprite/drawText 会自动管理批处理
|
||||||
|
*/
|
||||||
virtual void beginSpriteBatch() = 0;
|
virtual void beginSpriteBatch() = 0;
|
||||||
virtual void drawSprite(const Texture &texture, const Rect &destRect,
|
virtual void drawSprite(const Texture &texture, const Rect &destRect,
|
||||||
const Rect &srcRect, const Color &tint,
|
const Rect &srcRect, const Color &tint,
|
||||||
|
|
@ -86,6 +90,12 @@ public:
|
||||||
const Color &tint) = 0;
|
const Color &tint) = 0;
|
||||||
virtual void endSpriteBatch() = 0;
|
virtual void endSpriteBatch() = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 立即提交当前批处理
|
||||||
|
* @note 手动控制批处理提交时机,一般情况下不需要调用
|
||||||
|
*/
|
||||||
|
virtual void flush() = 0;
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
// 形状渲染
|
// 形状渲染
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -134,6 +134,10 @@ void GLRenderer::beginFrame(const Color &clearColor) {
|
||||||
* @brief 结束当前帧,刷新所有待处理的渲染批次
|
* @brief 结束当前帧,刷新所有待处理的渲染批次
|
||||||
*/
|
*/
|
||||||
void GLRenderer::endFrame() {
|
void GLRenderer::endFrame() {
|
||||||
|
// 刷新所有待处理的精灵批次(自动批处理)
|
||||||
|
if (autoBatchEnabled_ && batchActive_) {
|
||||||
|
flush();
|
||||||
|
}
|
||||||
// 刷新所有待处理的形状批次
|
// 刷新所有待处理的形状批次
|
||||||
flushShapeBatch();
|
flushShapeBatch();
|
||||||
// 刷新所有待处理的线条批次
|
// 刷新所有待处理的线条批次
|
||||||
|
|
@ -258,9 +262,45 @@ Ptr<Texture> GLRenderer::loadTexture(const std::string &filepath) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 开始精灵批处理
|
* @brief 确保批处理已激活(自动批处理内部使用)
|
||||||
*/
|
*/
|
||||||
void GLRenderer::beginSpriteBatch() { spriteBatch_.begin(viewProjection_); }
|
void GLRenderer::ensureBatchActive() {
|
||||||
|
if (!batchActive_) {
|
||||||
|
spriteBatch_.begin(viewProjection_);
|
||||||
|
batchActive_ = true;
|
||||||
|
currentBatchTexture_ = nullptr;
|
||||||
|
pendingSprites_.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 提交待处理的精灵(自动批处理内部使用)
|
||||||
|
*/
|
||||||
|
void GLRenderer::submitPendingSprites() {
|
||||||
|
if (pendingSprites_.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交所有待处理的精灵
|
||||||
|
spriteBatch_.drawBatch(*currentBatchTexture_, pendingSprites_);
|
||||||
|
pendingSprites_.clear();
|
||||||
|
currentBatchTexture_ = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 开始手动精灵批处理(高级用法)
|
||||||
|
* @note 一般情况下不需要调用,drawSprite/drawText 会自动管理批处理
|
||||||
|
*/
|
||||||
|
void GLRenderer::beginSpriteBatch() {
|
||||||
|
// 如果自动批处理已激活,先提交
|
||||||
|
if (autoBatchEnabled_ && batchActive_) {
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
// 禁用自动批处理,进入手动模式
|
||||||
|
autoBatchEnabled_ = false;
|
||||||
|
spriteBatch_.begin(viewProjection_);
|
||||||
|
batchActive_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 绘制精灵(带完整参数)
|
* @brief 绘制精灵(带完整参数)
|
||||||
|
|
@ -274,30 +314,69 @@ void GLRenderer::beginSpriteBatch() { spriteBatch_.begin(viewProjection_); }
|
||||||
void GLRenderer::drawSprite(const Texture &texture, const Rect &destRect,
|
void GLRenderer::drawSprite(const Texture &texture, const Rect &destRect,
|
||||||
const Rect &srcRect, const Color &tint,
|
const Rect &srcRect, const Color &tint,
|
||||||
float rotation, const Vec2 &anchor) {
|
float rotation, const Vec2 &anchor) {
|
||||||
SpriteData data;
|
// 自动批处理模式
|
||||||
data.position = Vec2(destRect.origin.x, destRect.origin.y);
|
if (autoBatchEnabled_) {
|
||||||
data.size = Vec2(destRect.size.width, destRect.size.height);
|
ensureBatchActive();
|
||||||
|
|
||||||
Texture *tex = const_cast<Texture *>(&texture);
|
// 如果纹理变化或缓冲区满,先提交当前批次
|
||||||
float texW = static_cast<float>(tex->getWidth());
|
if (currentBatchTexture_ != &texture ||
|
||||||
float texH = static_cast<float>(tex->getHeight());
|
pendingSprites_.size() >= MAX_BATCH_SPRITES) {
|
||||||
|
submitPendingSprites();
|
||||||
|
currentBatchTexture_ = &texture;
|
||||||
|
}
|
||||||
|
|
||||||
// 纹理坐标计算
|
// 创建精灵数据
|
||||||
float u1 = srcRect.origin.x / texW;
|
SpriteData data;
|
||||||
float u2 = (srcRect.origin.x + srcRect.size.width) / texW;
|
data.position = Vec2(destRect.origin.x, destRect.origin.y);
|
||||||
float v1 = srcRect.origin.y / texH;
|
data.size = Vec2(destRect.size.width, destRect.size.height);
|
||||||
float v2 = (srcRect.origin.y + srcRect.size.height) / texH;
|
|
||||||
|
|
||||||
data.uvRect = Rect(
|
Texture *tex = const_cast<Texture *>(&texture);
|
||||||
Vec2(glm::min(u1, u2), glm::min(v1, v2)),
|
float texW = static_cast<float>(tex->getWidth());
|
||||||
Size(glm::abs(u2 - u1), glm::abs(v2 - v1))
|
float texH = static_cast<float>(tex->getHeight());
|
||||||
);
|
|
||||||
|
|
||||||
data.color = tint;
|
// 纹理坐标计算
|
||||||
data.rotation = rotation * 3.14159f / 180.0f;
|
float u1 = srcRect.origin.x / texW;
|
||||||
data.pivot = Vec2(anchor.x, anchor.y);
|
float u2 = (srcRect.origin.x + srcRect.size.width) / texW;
|
||||||
|
float v1 = srcRect.origin.y / texH;
|
||||||
|
float v2 = (srcRect.origin.y + srcRect.size.height) / texH;
|
||||||
|
|
||||||
spriteBatch_.draw(texture, data);
|
data.uvRect = Rect(
|
||||||
|
Vec2(glm::min(u1, u2), glm::min(v1, v2)),
|
||||||
|
Size(glm::abs(u2 - u1), glm::abs(v2 - v1))
|
||||||
|
);
|
||||||
|
|
||||||
|
data.color = tint;
|
||||||
|
data.rotation = rotation * 3.14159f / 180.0f;
|
||||||
|
data.pivot = Vec2(anchor.x, anchor.y);
|
||||||
|
|
||||||
|
// 添加到待处理列表
|
||||||
|
pendingSprites_.push_back(data);
|
||||||
|
} else {
|
||||||
|
// 手动批处理模式
|
||||||
|
SpriteData data;
|
||||||
|
data.position = Vec2(destRect.origin.x, destRect.origin.y);
|
||||||
|
data.size = Vec2(destRect.size.width, destRect.size.height);
|
||||||
|
|
||||||
|
Texture *tex = const_cast<Texture *>(&texture);
|
||||||
|
float texW = static_cast<float>(tex->getWidth());
|
||||||
|
float texH = static_cast<float>(tex->getHeight());
|
||||||
|
|
||||||
|
float u1 = srcRect.origin.x / texW;
|
||||||
|
float u2 = (srcRect.origin.x + srcRect.size.width) / texW;
|
||||||
|
float v1 = srcRect.origin.y / texH;
|
||||||
|
float v2 = (srcRect.origin.y + srcRect.size.height) / texH;
|
||||||
|
|
||||||
|
data.uvRect = Rect(
|
||||||
|
Vec2(glm::min(u1, u2), glm::min(v1, v2)),
|
||||||
|
Size(glm::abs(u2 - u1), glm::abs(v2 - v1))
|
||||||
|
);
|
||||||
|
|
||||||
|
data.color = tint;
|
||||||
|
data.rotation = rotation * 3.14159f / 180.0f;
|
||||||
|
data.pivot = Vec2(anchor.x, anchor.y);
|
||||||
|
|
||||||
|
spriteBatch_.draw(texture, data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -316,11 +395,34 @@ void GLRenderer::drawSprite(const Texture &texture, const Vec2 &position,
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 结束精灵批处理并提交绘制
|
* @brief 结束手动精灵批处理并提交绘制
|
||||||
|
* @note 一般情况下不需要调用
|
||||||
*/
|
*/
|
||||||
void GLRenderer::endSpriteBatch() {
|
void GLRenderer::endSpriteBatch() {
|
||||||
spriteBatch_.end();
|
if (autoBatchEnabled_) {
|
||||||
stats_.drawCalls += spriteBatch_.getDrawCallCount();
|
// 自动模式下,只是标记批处理结束
|
||||||
|
flush();
|
||||||
|
} else {
|
||||||
|
// 手动模式下,提交批处理并恢复自动模式
|
||||||
|
spriteBatch_.end();
|
||||||
|
stats_.drawCalls += spriteBatch_.getDrawCallCount();
|
||||||
|
batchActive_ = false;
|
||||||
|
autoBatchEnabled_ = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 立即提交当前批处理
|
||||||
|
* @note 手动控制批处理提交时机,一般情况下不需要调用
|
||||||
|
*/
|
||||||
|
void GLRenderer::flush() {
|
||||||
|
if (autoBatchEnabled_ && batchActive_) {
|
||||||
|
submitPendingSprites();
|
||||||
|
spriteBatch_.end();
|
||||||
|
stats_.drawCalls += spriteBatch_.getDrawCallCount();
|
||||||
|
batchActive_ = false;
|
||||||
|
currentBatchTexture_ = nullptr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -581,6 +683,11 @@ void GLRenderer::drawText(const FontAtlas &font, const std::string &text,
|
||||||
float cursorY = y;
|
float cursorY = y;
|
||||||
float baselineY = cursorY + font.getAscent();
|
float baselineY = cursorY + font.getAscent();
|
||||||
|
|
||||||
|
// 确保批处理已激活(自动批处理)
|
||||||
|
if (autoBatchEnabled_) {
|
||||||
|
ensureBatchActive();
|
||||||
|
}
|
||||||
|
|
||||||
// 收集所有字符数据用于批处理
|
// 收集所有字符数据用于批处理
|
||||||
std::vector<SpriteData> sprites;
|
std::vector<SpriteData> sprites;
|
||||||
sprites.reserve(text.size()); // 预分配空间
|
sprites.reserve(text.size()); // 预分配空间
|
||||||
|
|
@ -588,6 +695,11 @@ void GLRenderer::drawText(const FontAtlas &font, const std::string &text,
|
||||||
for (char c : text) {
|
for (char c : text) {
|
||||||
char32_t codepoint = static_cast<char32_t>(static_cast<unsigned char>(c));
|
char32_t codepoint = static_cast<char32_t>(static_cast<unsigned char>(c));
|
||||||
if (codepoint == '\n') {
|
if (codepoint == '\n') {
|
||||||
|
// 提交当前批次(换行时)
|
||||||
|
if (autoBatchEnabled_ && !sprites.empty()) {
|
||||||
|
spriteBatch_.drawBatch(*font.getTexture(), sprites);
|
||||||
|
sprites.clear();
|
||||||
|
}
|
||||||
cursorX = x;
|
cursorX = x;
|
||||||
cursorY += font.getLineHeight();
|
cursorY += font.getLineHeight();
|
||||||
baselineY = cursorY + font.getAscent();
|
baselineY = cursorY + font.getAscent();
|
||||||
|
|
@ -625,12 +737,27 @@ void GLRenderer::drawText(const FontAtlas &font, const std::string &text,
|
||||||
data.pivot = Vec2(0.5f, 0.5f);
|
data.pivot = Vec2(0.5f, 0.5f);
|
||||||
|
|
||||||
sprites.push_back(data);
|
sprites.push_back(data);
|
||||||
|
|
||||||
|
// 自动批处理:如果缓冲区满,先提交当前批次
|
||||||
|
if (autoBatchEnabled_ && sprites.size() >= MAX_BATCH_SPRITES) {
|
||||||
|
spriteBatch_.drawBatch(*font.getTexture(), sprites);
|
||||||
|
sprites.clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用批处理绘制所有字符
|
// 提交剩余的字符
|
||||||
if (!sprites.empty()) {
|
if (!sprites.empty()) {
|
||||||
spriteBatch_.drawBatch(*font.getTexture(), sprites);
|
if (autoBatchEnabled_) {
|
||||||
|
spriteBatch_.drawBatch(*font.getTexture(), sprites);
|
||||||
|
} else {
|
||||||
|
// 手动模式下,添加到待处理列表
|
||||||
|
if (currentBatchTexture_ != font.getTexture()) {
|
||||||
|
submitPendingSprites();
|
||||||
|
currentBatchTexture_ = font.getTexture();
|
||||||
|
}
|
||||||
|
pendingSprites_.insert(pendingSprites_.end(), sprites.begin(), sprites.end());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 73 KiB |
|
|
@ -0,0 +1,4 @@
|
||||||
|
请将 1.jpg 图片复制到此目录,并重命名为 demo.jpg
|
||||||
|
|
||||||
|
源文件位置: C:\Users\soulcoco\Desktop\Extra2D\1.jpg
|
||||||
|
目标文件位置: C:\Users\soulcoco\Desktop\Extra2D\examples\image_display\assets\images\demo.jpg
|
||||||
|
|
@ -0,0 +1,180 @@
|
||||||
|
/**
|
||||||
|
* @file main.cpp
|
||||||
|
* @brief Extra2D 图片显示示例
|
||||||
|
*
|
||||||
|
* 演示如何使用 RenderBackend 抽象接口加载和显示图片
|
||||||
|
* 此示例不依赖任何特定渲染后端(如 OpenGL)
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <extra2d/core/service_locator.h>
|
||||||
|
#include <extra2d/extra2d.h>
|
||||||
|
#include <extra2d/graphics/core/render_backend.h>
|
||||||
|
#include <extra2d/graphics/core/render_module.h>
|
||||||
|
#include <extra2d/graphics/texture/texture.h>
|
||||||
|
#include <extra2d/platform/input_module.h>
|
||||||
|
#include <extra2d/platform/window_module.h>
|
||||||
|
#include <extra2d/services/camera_service.h>
|
||||||
|
#include <extra2d/services/event_service.h>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
using namespace extra2d;
|
||||||
|
|
||||||
|
class ImageDisplayScene : public Scene {
|
||||||
|
public:
|
||||||
|
void onEnter() override {
|
||||||
|
// 加载图片
|
||||||
|
// 注意:请确保有图片文件在 assets/images/ 目录下
|
||||||
|
try {
|
||||||
|
texture_ = renderer_->loadTexture("assets/images/demo.jpg");
|
||||||
|
std::cout << "Image loaded successfully!" << std::endl;
|
||||||
|
std::cout << " Size: " << texture_->getWidth() << "x" << texture_->getHeight() << std::endl;
|
||||||
|
} catch (...) {
|
||||||
|
std::cerr << "Failed to load image from assets/images/demo.jpg" << std::endl;
|
||||||
|
// 尝试使用备用路径
|
||||||
|
try {
|
||||||
|
texture_ = renderer_->loadTexture("examples/image_display/assets/images/demo.jpg");
|
||||||
|
std::cout << "Image loaded from alternate path!" << std::endl;
|
||||||
|
} catch (...) {
|
||||||
|
std::cerr << "Failed to load image from alternate path!" << std::endl;
|
||||||
|
texture_ = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void onExit() override {
|
||||||
|
texture_.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void onRender(RenderBackend& renderer) override {
|
||||||
|
Scene::onRender(renderer);
|
||||||
|
|
||||||
|
if (!texture_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用 RenderBackend 的抽象接口绘制图片
|
||||||
|
// 不依赖任何特定后端(如 OpenGL)
|
||||||
|
// 自动批处理:无需手动调用 begin/endSpriteBatch
|
||||||
|
|
||||||
|
// 计算图片显示位置(居中显示)
|
||||||
|
float windowWidth = 1280.0f;
|
||||||
|
float windowHeight = 720.0f;
|
||||||
|
float imgWidth = static_cast<float>(texture_->getWidth());
|
||||||
|
float imgHeight = static_cast<float>(texture_->getHeight());
|
||||||
|
|
||||||
|
// 缩放图片以适应窗口(保持宽高比)
|
||||||
|
float scale = 1.0f;
|
||||||
|
if (imgWidth > windowWidth * 0.8f || imgHeight > windowHeight * 0.8f) {
|
||||||
|
float scaleX = (windowWidth * 0.8f) / imgWidth;
|
||||||
|
float scaleY = (windowHeight * 0.8f) / imgHeight;
|
||||||
|
scale = std::min(scaleX, scaleY);
|
||||||
|
}
|
||||||
|
|
||||||
|
float displayWidth = imgWidth * scale;
|
||||||
|
float displayHeight = imgHeight * scale;
|
||||||
|
float x = (windowWidth - displayWidth) * 0.5f;
|
||||||
|
float y = (windowHeight - displayHeight) * 0.5f;
|
||||||
|
|
||||||
|
// 使用 RenderBackend 的 drawSprite 方法绘制图片
|
||||||
|
// 参数:纹理、目标矩形、源矩形、颜色、旋转角度、锚点
|
||||||
|
Rect destRect(x, y, displayWidth, displayHeight);
|
||||||
|
Rect srcRect(0, 0, imgWidth, imgHeight);
|
||||||
|
renderer.drawSprite(*texture_, destRect, srcRect, Colors::White, 0.0f, Vec2(0, 0));
|
||||||
|
|
||||||
|
// 注意:无需手动调用 renderer.endSpriteBatch(),帧结束时会自动刷新
|
||||||
|
}
|
||||||
|
|
||||||
|
void setRenderer(RenderBackend* renderer) {
|
||||||
|
renderer_ = renderer;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Ptr<Texture> texture_;
|
||||||
|
RenderBackend* renderer_ = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
int main(int argc, char* argv[]) {
|
||||||
|
(void)argc;
|
||||||
|
(void)argv;
|
||||||
|
|
||||||
|
std::cout << "Extra2D Image Display Demo - Starting..." << std::endl;
|
||||||
|
|
||||||
|
Application& app = Application::get();
|
||||||
|
|
||||||
|
// 注册模块
|
||||||
|
app.use<WindowModule>([](auto& cfg) {
|
||||||
|
cfg.w = 1280;
|
||||||
|
cfg.h = 720;
|
||||||
|
cfg.title = "Extra2D Image Display Demo";
|
||||||
|
cfg.priority = 0;
|
||||||
|
cfg.backend = "glfw";
|
||||||
|
});
|
||||||
|
|
||||||
|
app.use<RenderModule>([](auto& cfg) { cfg.priority = 10; });
|
||||||
|
|
||||||
|
app.use<InputModule>([](auto& cfg) { cfg.priority = 20; });
|
||||||
|
|
||||||
|
std::cout << "Initializing application..." << std::endl;
|
||||||
|
if (!app.init()) {
|
||||||
|
std::cerr << "Failed to initialize application!" << std::endl;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "Application initialized successfully!" << std::endl;
|
||||||
|
|
||||||
|
auto* win = app.window();
|
||||||
|
if (win) {
|
||||||
|
std::cout << "Window: " << win->width() << "x" << win->height() << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置事件监听
|
||||||
|
auto eventService = ServiceLocator::instance().getService<IEventService>();
|
||||||
|
if (eventService) {
|
||||||
|
eventService->addListener(EventType::KeyPressed, [](Event& e) {
|
||||||
|
auto& keyEvent = std::get<KeyEvent>(e.data);
|
||||||
|
if (keyEvent.keyCode == static_cast<int>(Key::Escape)) {
|
||||||
|
e.handled = true;
|
||||||
|
Application::get().quit();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取渲染器
|
||||||
|
RenderBackend* renderer = app.renderer();
|
||||||
|
|
||||||
|
// 创建并配置场景
|
||||||
|
auto scene = makeShared<ImageDisplayScene>();
|
||||||
|
scene->setRenderer(renderer);
|
||||||
|
scene->setBackgroundColor(Color(0.1f, 0.1f, 0.15f, 1.0f));
|
||||||
|
|
||||||
|
if (win) {
|
||||||
|
scene->setViewportSize(static_cast<float>(win->width()),
|
||||||
|
static_cast<float>(win->height()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 配置相机
|
||||||
|
auto cameraService = ServiceLocator::instance().getService<ICameraService>();
|
||||||
|
if (cameraService && win) {
|
||||||
|
ViewportConfig vpConfig;
|
||||||
|
vpConfig.logicWidth = static_cast<float>(win->width());
|
||||||
|
vpConfig.logicHeight = static_cast<float>(win->height());
|
||||||
|
vpConfig.mode = ViewportMode::AspectRatio;
|
||||||
|
cameraService->setViewportConfig(vpConfig);
|
||||||
|
cameraService->updateViewport(win->width(), win->height());
|
||||||
|
cameraService->applyViewportAdapter();
|
||||||
|
}
|
||||||
|
|
||||||
|
app.enterScene(scene);
|
||||||
|
|
||||||
|
std::cout << "\nControls:" << std::endl;
|
||||||
|
std::cout << " ESC - Exit" << std::endl;
|
||||||
|
std::cout << "\nRunning main loop...\n" << std::endl;
|
||||||
|
|
||||||
|
app.run();
|
||||||
|
|
||||||
|
std::cout << "Shutting down..." << std::endl;
|
||||||
|
app.shutdown();
|
||||||
|
|
||||||
|
std::cout << "Goodbye!" << std::endl;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
@ -2,13 +2,13 @@
|
||||||
* @file main.cpp
|
* @file main.cpp
|
||||||
* @brief Extra2D 文字渲染示例
|
* @brief Extra2D 文字渲染示例
|
||||||
*
|
*
|
||||||
* 演示如何使用 GLFontAtlas 渲染文字
|
* 演示如何使用 RenderBackend 抽象接口渲染文字
|
||||||
|
* 此示例不依赖任何特定渲染后端(如 OpenGL)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <extra2d/core/service_locator.h>
|
#include <extra2d/core/service_locator.h>
|
||||||
#include <extra2d/extra2d.h>
|
#include <extra2d/extra2d.h>
|
||||||
#include <extra2d/graphics/backends/opengl/gl_font_atlas.h>
|
#include <extra2d/graphics/core/render_backend.h>
|
||||||
#include <extra2d/graphics/backends/opengl/gl_renderer.h>
|
|
||||||
#include <extra2d/graphics/core/render_module.h>
|
#include <extra2d/graphics/core/render_module.h>
|
||||||
#include <extra2d/graphics/texture/font.h>
|
#include <extra2d/graphics/texture/font.h>
|
||||||
#include <extra2d/platform/input_module.h>
|
#include <extra2d/platform/input_module.h>
|
||||||
|
|
@ -25,14 +25,19 @@ public:
|
||||||
// 加载字体
|
// 加载字体
|
||||||
// 注意:请确保有字体文件在 assets/fonts/ 目录下
|
// 注意:请确保有字体文件在 assets/fonts/ 目录下
|
||||||
// 如果没有,可以使用系统字体路径
|
// 如果没有,可以使用系统字体路径
|
||||||
|
if (!renderer_) {
|
||||||
|
std::cerr << "Renderer not available!" << std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
font_ = std::make_shared<GLFontAtlas>("assets/fonts/arial.ttf", 24);
|
font_ = renderer_->createFontAtlas("assets/fonts/arial.ttf", 24);
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
std::cerr << "Failed to load font from assets/fonts/arial.ttf"
|
std::cerr << "Failed to load font from assets/fonts/arial.ttf"
|
||||||
<< std::endl;
|
<< std::endl;
|
||||||
// 尝试使用备用路径
|
// 尝试使用备用路径
|
||||||
try {
|
try {
|
||||||
font_ = std::make_shared<GLFontAtlas>("C:/Windows/Fonts/arial.ttf", 24);
|
font_ = renderer_->createFontAtlas("C:/Windows/Fonts/arial.ttf", 24);
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
std::cerr << "Failed to load font from system path!" << std::endl;
|
std::cerr << "Failed to load font from system path!" << std::endl;
|
||||||
font_ = nullptr;
|
font_ = nullptr;
|
||||||
|
|
@ -57,69 +62,64 @@ public:
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取渲染器
|
|
||||||
auto *glRenderer = dynamic_cast<GLRenderer *>(&renderer);
|
|
||||||
if (!glRenderer) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 开始精灵批处理
|
|
||||||
glRenderer->beginSpriteBatch();
|
|
||||||
|
|
||||||
float y = 100.0f;
|
float y = 100.0f;
|
||||||
float x = 100.0f;
|
float x = 100.0f;
|
||||||
|
|
||||||
// 渲染标题
|
// 渲染标题(自动批处理,无需手动调用 begin/end)
|
||||||
renderText(glRenderer, "Extra2D Text Rendering Demo", x, y,
|
renderText(renderer, "Extra2D Text Rendering Demo", x, y,
|
||||||
Color(1.0f, 0.8f, 0.2f, 1.0f));
|
Color(1.0f, 0.8f, 0.2f, 1.0f));
|
||||||
y += font_->getLineHeight() * 2;
|
y += font_->getLineHeight() * 2;
|
||||||
|
|
||||||
// 渲染不同颜色的文字
|
// 渲染不同颜色的文字
|
||||||
renderText(glRenderer, "Red Text", x, y, Color(1.0f, 0.2f, 0.2f, 1.0f));
|
renderText(renderer, "Red Text", x, y, Color(1.0f, 0.2f, 0.2f, 1.0f));
|
||||||
y += font_->getLineHeight();
|
y += font_->getLineHeight();
|
||||||
|
|
||||||
renderText(glRenderer, "Green Text", x, y, Color(0.2f, 1.0f, 0.2f, 1.0f));
|
renderText(renderer, "Green Text", x, y, Color(0.2f, 1.0f, 0.2f, 1.0f));
|
||||||
y += font_->getLineHeight();
|
y += font_->getLineHeight();
|
||||||
|
|
||||||
renderText(glRenderer, "Blue Text", x, y, Color(0.2f, 0.2f, 1.0f, 1.0f));
|
renderText(renderer, "Blue Text", x, y, Color(0.2f, 0.2f, 1.0f, 1.0f));
|
||||||
y += font_->getLineHeight() * 2;
|
y += font_->getLineHeight() * 2;
|
||||||
|
|
||||||
// 渲染多行文字
|
// 渲染多行文字
|
||||||
renderText(glRenderer, "This is a multi-line text example.", x, y,
|
renderText(renderer, "This is a multi-line text example.", x, y,
|
||||||
Color(1.0f, 1.0f, 1.0f, 1.0f));
|
Color(1.0f, 1.0f, 1.0f, 1.0f));
|
||||||
y += font_->getLineHeight();
|
y += font_->getLineHeight();
|
||||||
renderText(glRenderer, "You can render text with different colors", x, y,
|
renderText(renderer, "You can render text with different colors", x, y,
|
||||||
Color(0.8f, 0.8f, 0.8f, 1.0f));
|
Color(0.8f, 0.8f, 0.8f, 1.0f));
|
||||||
y += font_->getLineHeight();
|
y += font_->getLineHeight();
|
||||||
renderText(glRenderer, "and styles using GLFontAtlas.", x, y,
|
renderText(renderer, "and styles using FontAtlas.", x, y,
|
||||||
Color(0.6f, 0.6f, 0.6f, 1.0f));
|
Color(0.6f, 0.6f, 0.6f, 1.0f));
|
||||||
y += font_->getLineHeight() * 2;
|
y += font_->getLineHeight() * 2;
|
||||||
|
|
||||||
// 渲染半透明文字
|
// 渲染半透明文字
|
||||||
renderText(glRenderer, "Semi-transparent text (50% opacity)", x, y,
|
renderText(renderer, "Semi-transparent text (50% opacity)", x, y,
|
||||||
Color(1.0f, 1.0f, 1.0f, 0.5f));
|
Color(1.0f, 1.0f, 1.0f, 0.5f));
|
||||||
y += font_->getLineHeight() * 2;
|
y += font_->getLineHeight() * 2;
|
||||||
|
|
||||||
// 渲染操作提示
|
// 渲染操作提示
|
||||||
renderText(glRenderer, "Press ESC to exit", x, y,
|
renderText(renderer, "Press ESC to exit", x, y,
|
||||||
Color(0.5f, 0.5f, 0.5f, 1.0f));
|
Color(0.5f, 0.5f, 0.5f, 1.0f));
|
||||||
|
|
||||||
// 结束精灵批处理
|
// 注意:无需手动调用 renderer.endSpriteBatch(),帧结束时会自动刷新
|
||||||
glRenderer->endSpriteBatch();
|
}
|
||||||
|
|
||||||
|
void setRenderer(RenderBackend* renderer) {
|
||||||
|
renderer_ = renderer;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void renderText(GLRenderer *renderer, const std::string &text, float x,
|
void renderText(RenderBackend &renderer, const std::string &text, float x,
|
||||||
float y, const Color &color) {
|
float y, const Color &color) {
|
||||||
if (!font_ || !renderer) {
|
if (!font_) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用渲染器的 drawText 方法
|
// 使用 RenderBackend 的 drawText 方法
|
||||||
renderer->drawText(*font_, text, Vec2(x, y), color);
|
renderer.drawText(*font_, text, Vec2(x, y), color);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<GLFontAtlas> font_;
|
Ptr<FontAtlas> font_;
|
||||||
|
RenderBackend* renderer_ = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
|
|
@ -169,8 +169,12 @@ int main(int argc, char *argv[]) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取渲染器
|
||||||
|
RenderBackend* renderer = app.renderer();
|
||||||
|
|
||||||
// 创建并配置场景
|
// 创建并配置场景
|
||||||
auto scene = makeShared<TextRenderingScene>();
|
auto scene = makeShared<TextRenderingScene>();
|
||||||
|
scene->setRenderer(renderer);
|
||||||
scene->setBackgroundColor(Color(0.1f, 0.1f, 0.15f, 1.0f));
|
scene->setBackgroundColor(Color(0.1f, 0.1f, 0.15f, 1.0f));
|
||||||
|
|
||||||
if (win) {
|
if (win) {
|
||||||
|
|
|
||||||
59
xmake.lua
59
xmake.lua
|
|
@ -302,3 +302,62 @@ target("demo_hello_module")
|
||||||
-- 构建后安装Shader文件
|
-- 构建后安装Shader文件
|
||||||
after_build(install_shaders)
|
after_build(install_shaders)
|
||||||
target_end()
|
target_end()
|
||||||
|
|
||||||
|
-- 图片显示示例
|
||||||
|
target("demo_image_display")
|
||||||
|
set_kind("binary")
|
||||||
|
set_default(false)
|
||||||
|
|
||||||
|
add_deps("extra2d")
|
||||||
|
add_files("examples/image_display/main.cpp")
|
||||||
|
|
||||||
|
-- 平台配置
|
||||||
|
local plat = get_config("plat") or os.host()
|
||||||
|
local backend = get_config("window_backend") or "sdl2"
|
||||||
|
if plat == "mingw" or plat == "windows" then
|
||||||
|
add_packages("glm", "nlohmann_json")
|
||||||
|
if backend == "glfw" then
|
||||||
|
add_packages("glfw")
|
||||||
|
else
|
||||||
|
add_packages("libsdl2")
|
||||||
|
end
|
||||||
|
add_syslinks("opengl32", "glu32", "winmm", "imm32", "version", "setupapi")
|
||||||
|
elseif plat == "linux" then
|
||||||
|
add_packages("glm", "nlohmann_json")
|
||||||
|
if backend == "glfw" then
|
||||||
|
add_packages("glfw")
|
||||||
|
else
|
||||||
|
add_packages("libsdl2")
|
||||||
|
end
|
||||||
|
add_syslinks("GL", "dl", "pthread")
|
||||||
|
elseif plat == "macosx" then
|
||||||
|
add_packages("glm", "nlohmann_json")
|
||||||
|
if backend == "glfw" then
|
||||||
|
add_packages("glfw")
|
||||||
|
else
|
||||||
|
add_packages("libsdl2")
|
||||||
|
end
|
||||||
|
add_frameworks("OpenGL", "Cocoa", "IOKit", "CoreVideo")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- 构建后安装Shader文件和assets
|
||||||
|
after_build(function (target)
|
||||||
|
-- 安装shaders
|
||||||
|
local plat = get_config("plat") or os.host()
|
||||||
|
local targetdir = target:targetdir()
|
||||||
|
local shader_src = "Extra2D/shaders"
|
||||||
|
local shader_dest = path.join(targetdir, "shaders")
|
||||||
|
os.rm(shader_dest)
|
||||||
|
os.cp(shader_src, shader_dest)
|
||||||
|
print("Shaders installed to: " .. shader_dest)
|
||||||
|
|
||||||
|
-- 复制assets目录
|
||||||
|
local assets_src = "examples/image_display/assets"
|
||||||
|
local assets_dest = path.join(targetdir, "assets")
|
||||||
|
if os.exists(assets_src) then
|
||||||
|
os.rm(assets_dest)
|
||||||
|
os.cp(assets_src, assets_dest)
|
||||||
|
print("Assets installed to: " .. assets_dest)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
target_end()
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue