refactor(render): 重构渲染系统,整合纹理、相机和渲染器功能
feat(render): 新增Texture类封装OpenGL纹理操作 feat(render): 新增Camera类提供2D视图投影矩阵计算 feat(render): 新增Renderer类整合图形和精灵渲染功能 refactor(window): 简化Window类实现,移除冗余功能 refactor(app): 重构Application类,简化主循环逻辑 refactor(scene): 重构场景和节点系统,适配新渲染接口 fix(assets): 修复资源管理相关头文件包含问题 chore(build): 更新xmake构建配置,移除调试宏定义 style: 统一代码格式,修复头文件合并冲突
This commit is contained in:
parent
114c5254af
commit
e600730105
|
|
@ -1,113 +1,58 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <extra2d/core/module.h>
|
|
||||||
#include <extra2d/core/registry.h>
|
|
||||||
#include <extra2d/core/service_locator.h>
|
#include <extra2d/core/service_locator.h>
|
||||||
#include <extra2d/core/types.h>
|
#include <extra2d/core/types.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
class GLFWWindow;
|
class Window;
|
||||||
class WindowModule;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 应用程序类
|
* @brief 应用程序类
|
||||||
*/
|
*/
|
||||||
class Application {
|
class Application {
|
||||||
public:
|
public:
|
||||||
static Application &get();
|
static Application& instance();
|
||||||
|
|
||||||
Application(const Application&) = delete;
|
Application(const Application&) = delete;
|
||||||
Application& operator=(const Application&) = delete;
|
Application& operator=(const Application&) = delete;
|
||||||
|
|
||||||
/**
|
std::string name = "Extra2D App";
|
||||||
* @brief 应用信息
|
std::string version = "1.0.0";
|
||||||
*/
|
|
||||||
std::string appName = "Extra2D App";
|
|
||||||
std::string appVersion = "1.0.0";
|
|
||||||
std::string organization = "";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 注册模块
|
|
||||||
* @tparam T 模块类型
|
|
||||||
* @tparam Args 构造函数参数
|
|
||||||
* @return 模块指针
|
|
||||||
*/
|
|
||||||
template <typename T, typename... Args> T *use(Args &&...args) {
|
|
||||||
return Registry::instance().use<T>(std::forward<Args>(args)...);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取模块
|
|
||||||
* @tparam T 模块类型
|
|
||||||
* @return 模块指针
|
|
||||||
*/
|
|
||||||
template <typename T> T *get() const { return Registry::instance().get<T>(); }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 初始化
|
|
||||||
* @return 初始化成功返回 true
|
|
||||||
*/
|
|
||||||
bool init();
|
bool init();
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 关闭
|
|
||||||
*/
|
|
||||||
void shutdown();
|
void shutdown();
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 运行主循环
|
|
||||||
*/
|
|
||||||
void run();
|
void run();
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 请求退出
|
|
||||||
*/
|
|
||||||
void quit();
|
void quit();
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 暂停
|
|
||||||
*/
|
|
||||||
void pause();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 恢复
|
|
||||||
*/
|
|
||||||
void resume();
|
|
||||||
|
|
||||||
bool paused() const { return paused_; }
|
bool paused() const { return paused_; }
|
||||||
bool running() const { return running_; }
|
bool running() const { return running_; }
|
||||||
|
Window* window() const { return window_; }
|
||||||
/**
|
|
||||||
* @brief 获取窗口
|
|
||||||
* @return 窗口指针
|
|
||||||
*/
|
|
||||||
GLFWWindow *window();
|
|
||||||
|
|
||||||
f32 dt() const { return dt_; }
|
f32 dt() const { return dt_; }
|
||||||
f32 totalTime() const { return totalTime_; }
|
f32 totalTime() const { return totalTime_; }
|
||||||
int fps() const { return fps_; }
|
int fps() const { return fps_; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Application();
|
Application() = default;
|
||||||
~Application();
|
~Application();
|
||||||
|
|
||||||
void mainLoop();
|
void mainLoop();
|
||||||
void update();
|
|
||||||
void render();
|
|
||||||
|
|
||||||
bool initialized_ = false;
|
bool initialized_ = false;
|
||||||
bool running_ = false;
|
bool running_ = false;
|
||||||
bool paused_ = false;
|
bool paused_ = false;
|
||||||
bool shouldQuit_ = false;
|
Window* window_ = nullptr;
|
||||||
|
|
||||||
f32 dt_ = 0.0f;
|
f32 dt_ = 0.0f;
|
||||||
f32 totalTime_ = 0.0f;
|
f32 totalTime_ = 0.0f;
|
||||||
f64 lastFrameTime_ = 0.0;
|
u64 lastFrameTime_ = 0;
|
||||||
int frameCount_ = 0;
|
int frameCount_ = 0;
|
||||||
f32 fpsTimer_ = 0.0f;
|
f32 fpsTimer_ = 0.0f;
|
||||||
int fps_ = 0;
|
int fps_ = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#define E2D_APP() ::extra2d::Application::instance()
|
||||||
|
|
||||||
} // namespace extra2d
|
} // namespace extra2d
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,16 @@
|
||||||
// Core
|
// Core
|
||||||
#include <extra2d/core/color.h>
|
#include <extra2d/core/color.h>
|
||||||
#include <extra2d/core/math_types.h>
|
#include <extra2d/core/math_types.h>
|
||||||
<<<<<<< HEAD
|
#include <extra2d/core/module.h>
|
||||||
|
#include <extra2d/core/registry.h>
|
||||||
#include <extra2d/core/string.h>
|
#include <extra2d/core/string.h>
|
||||||
#include <extra2d/core/types.h>
|
#include <extra2d/core/types.h>
|
||||||
|
|
||||||
|
// Platform
|
||||||
|
#include <extra2d/platform/glfw/glfw_window.h>
|
||||||
|
#include <extra2d/platform/keys.h>
|
||||||
|
#include <extra2d/platform/window_module.h>
|
||||||
|
|
||||||
// Window - SDL2 + OpenGL
|
// Window - SDL2 + OpenGL
|
||||||
#include <extra2d/window/window.h>
|
#include <extra2d/window/window.h>
|
||||||
|
|
||||||
|
|
@ -31,22 +37,11 @@
|
||||||
#include <extra2d/scene/scene_manager.h>
|
#include <extra2d/scene/scene_manager.h>
|
||||||
#include <extra2d/scene/shape_node.h>
|
#include <extra2d/scene/shape_node.h>
|
||||||
#include <extra2d/scene/sprite.h>
|
#include <extra2d/scene/sprite.h>
|
||||||
=======
|
|
||||||
#include <extra2d/core/module.h>
|
|
||||||
#include <extra2d/core/registry.h>
|
|
||||||
#include <extra2d/core/types.h>
|
|
||||||
|
|
||||||
// Platform
|
|
||||||
#include <extra2d/platform/glfw/glfw_window.h>
|
|
||||||
#include <extra2d/platform/keys.h>
|
|
||||||
#include <extra2d/platform/window_module.h>
|
|
||||||
>>>>>>> 9bf328b1dca01df84a07724394abc9a238739869
|
|
||||||
|
|
||||||
// Event
|
// Event
|
||||||
#include <extra2d/event/event.h>
|
#include <extra2d/event/event.h>
|
||||||
#include <extra2d/event/event_dispatcher.h>
|
#include <extra2d/event/event_dispatcher.h>
|
||||||
#include <extra2d/event/event_queue.h>
|
#include <extra2d/event/event_queue.h>
|
||||||
<<<<<<< HEAD
|
|
||||||
#include <extra2d/event/input_codes.h>
|
#include <extra2d/event/input_codes.h>
|
||||||
|
|
||||||
// Audio
|
// Audio
|
||||||
|
|
@ -55,7 +50,6 @@
|
||||||
|
|
||||||
// Utils
|
// Utils
|
||||||
#include <extra2d/utils/data.h>
|
#include <extra2d/utils/data.h>
|
||||||
#include <extra2d/utils/logger.h>
|
|
||||||
#include <extra2d/utils/random.h>
|
#include <extra2d/utils/random.h>
|
||||||
#include <extra2d/utils/timer.h>
|
#include <extra2d/utils/timer.h>
|
||||||
|
|
||||||
|
|
@ -64,11 +58,6 @@
|
||||||
#include <extra2d/spatial/spatial_hash.h>
|
#include <extra2d/spatial/spatial_hash.h>
|
||||||
#include <extra2d/spatial/spatial_index.h>
|
#include <extra2d/spatial/spatial_index.h>
|
||||||
#include <extra2d/spatial/spatial_manager.h>
|
#include <extra2d/spatial/spatial_manager.h>
|
||||||
=======
|
|
||||||
|
|
||||||
// Utils
|
|
||||||
#include <extra2d/utils/random.h>
|
|
||||||
#include <extra2d/utils/timer.h>
|
|
||||||
|
|
||||||
// Services
|
// Services
|
||||||
#include <extra2d/services/asset_service.h>
|
#include <extra2d/services/asset_service.h>
|
||||||
|
|
@ -84,7 +73,6 @@
|
||||||
#include <extra2d/asset/asset_pack.h>
|
#include <extra2d/asset/asset_pack.h>
|
||||||
#include <extra2d/asset/asset_types.h>
|
#include <extra2d/asset/asset_types.h>
|
||||||
#include <extra2d/asset/data_processor.h>
|
#include <extra2d/asset/data_processor.h>
|
||||||
>>>>>>> 9bf328b1dca01df84a07724394abc9a238739869
|
|
||||||
|
|
||||||
// Application
|
// Application
|
||||||
#include <extra2d/app/application.h>
|
#include <extra2d/app/application.h>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,223 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/core/math_types.h>
|
||||||
|
#include <glm/gtc/matrix_transform.hpp>
|
||||||
|
#include <glm/mat4x4.hpp>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 2D 摄像机类
|
||||||
|
*
|
||||||
|
* 提供视图和投影矩阵,支持平移、缩放和旋转
|
||||||
|
*/
|
||||||
|
class Camera {
|
||||||
|
public:
|
||||||
|
Camera() = default;
|
||||||
|
~Camera() = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置视口
|
||||||
|
*/
|
||||||
|
void setViewport(int x, int y, int width, int height) {
|
||||||
|
viewportX_ = x;
|
||||||
|
viewportY_ = y;
|
||||||
|
viewportWidth_ = width;
|
||||||
|
viewportHeight_ = height;
|
||||||
|
dirty_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置位置
|
||||||
|
*/
|
||||||
|
void setPosition(const Vec2& pos) {
|
||||||
|
position_ = pos;
|
||||||
|
dirty_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置缩放
|
||||||
|
*/
|
||||||
|
void setZoom(float zoom) {
|
||||||
|
zoom_ = zoom;
|
||||||
|
dirty_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置旋转角度(度数)
|
||||||
|
*/
|
||||||
|
void setRotation(float degrees) {
|
||||||
|
rotation_ = degrees;
|
||||||
|
dirty_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 移动摄像机
|
||||||
|
*/
|
||||||
|
void move(const Vec2& delta) {
|
||||||
|
position_ += delta;
|
||||||
|
dirty_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 缩放摄像机
|
||||||
|
*/
|
||||||
|
void zoom(float factor) {
|
||||||
|
zoom_ *= factor;
|
||||||
|
dirty_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 旋转摄像机
|
||||||
|
*/
|
||||||
|
void rotate(float degrees) {
|
||||||
|
rotation_ += degrees;
|
||||||
|
dirty_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取位置
|
||||||
|
*/
|
||||||
|
const Vec2& position() const { return position_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取缩放
|
||||||
|
*/
|
||||||
|
float zoom() const { return zoom_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取旋转角度
|
||||||
|
*/
|
||||||
|
float rotation() const { return rotation_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取视图矩阵
|
||||||
|
*/
|
||||||
|
const glm::mat4& viewMatrix() const {
|
||||||
|
if (dirty_) {
|
||||||
|
updateMatrices();
|
||||||
|
}
|
||||||
|
return viewMatrix_;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取投影矩阵
|
||||||
|
*/
|
||||||
|
const glm::mat4& projectionMatrix() const {
|
||||||
|
if (dirty_) {
|
||||||
|
updateMatrices();
|
||||||
|
}
|
||||||
|
return projectionMatrix_;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取视图投影矩阵
|
||||||
|
*/
|
||||||
|
const glm::mat4& viewProjectionMatrix() const {
|
||||||
|
if (dirty_) {
|
||||||
|
updateMatrices();
|
||||||
|
}
|
||||||
|
return viewProjectionMatrix_;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取视图投影矩阵(便捷方法)
|
||||||
|
*/
|
||||||
|
const glm::mat4& getViewProjectionMatrix() {
|
||||||
|
if (dirty_) {
|
||||||
|
updateMatrices();
|
||||||
|
}
|
||||||
|
return viewProjectionMatrix_;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 屏幕坐标转世界坐标
|
||||||
|
*/
|
||||||
|
Vec2 screenToWorld(const Vec2& screenPos) const {
|
||||||
|
if (dirty_) {
|
||||||
|
updateMatrices();
|
||||||
|
}
|
||||||
|
|
||||||
|
glm::vec4 ndc(
|
||||||
|
(screenPos.x - viewportX_) / viewportWidth_ * 2.0f - 1.0f,
|
||||||
|
1.0f - (screenPos.y - viewportY_) / viewportHeight_ * 2.0f,
|
||||||
|
0.0f, 1.0f
|
||||||
|
);
|
||||||
|
|
||||||
|
glm::mat4 invVP = glm::inverse(viewProjectionMatrix_);
|
||||||
|
glm::vec4 world = invVP * ndc;
|
||||||
|
|
||||||
|
return Vec2(world.x, world.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 世界坐标转屏幕坐标
|
||||||
|
*/
|
||||||
|
Vec2 worldToScreen(const Vec2& worldPos) const {
|
||||||
|
if (dirty_) {
|
||||||
|
updateMatrices();
|
||||||
|
}
|
||||||
|
|
||||||
|
glm::vec4 clip = viewProjectionMatrix_ * glm::vec4(worldPos.x, worldPos.y, 0.0f, 1.0f);
|
||||||
|
glm::vec3 ndc(clip.x / clip.w, clip.y / clip.w, clip.z / clip.w);
|
||||||
|
|
||||||
|
return Vec2(
|
||||||
|
(ndc.x + 1.0f) * 0.5f * viewportWidth_ + viewportX_,
|
||||||
|
(1.0f - ndc.y) * 0.5f * viewportHeight_ + viewportY_
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取视口宽度
|
||||||
|
*/
|
||||||
|
int viewportWidth() const { return viewportWidth_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取视口高度
|
||||||
|
*/
|
||||||
|
int viewportHeight() const { return viewportHeight_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
void updateMatrices() const {
|
||||||
|
glm::vec3 eye(position_.x, position_.y, 0.0f);
|
||||||
|
glm::vec3 center(position_.x, position_.y, -1.0f);
|
||||||
|
glm::vec3 up(0.0f, 1.0f, 0.0f);
|
||||||
|
|
||||||
|
viewMatrix_ = glm::lookAt(eye, center, up);
|
||||||
|
|
||||||
|
if (rotation_ != 0.0f) {
|
||||||
|
glm::mat4 rot = glm::rotate(glm::mat4(1.0f),
|
||||||
|
rotation_ * 3.14159265f / 180.0f,
|
||||||
|
glm::vec3(0.0f, 0.0f, 1.0f));
|
||||||
|
viewMatrix_ = rot * viewMatrix_;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (zoom_ != 1.0f) {
|
||||||
|
viewMatrix_ = glm::scale(glm::mat4(1.0f),
|
||||||
|
glm::vec3(zoom_, zoom_, 1.0f)) * viewMatrix_;
|
||||||
|
}
|
||||||
|
|
||||||
|
float halfW = viewportWidth_ * 0.5f;
|
||||||
|
float halfH = viewportHeight_ * 0.5f;
|
||||||
|
projectionMatrix_ = glm::ortho(-halfW, halfW, -halfH, halfH, -1.0f, 1.0f);
|
||||||
|
|
||||||
|
viewProjectionMatrix_ = projectionMatrix_ * viewMatrix_;
|
||||||
|
dirty_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec2 position_;
|
||||||
|
float zoom_ = 1.0f;
|
||||||
|
float rotation_ = 0.0f;
|
||||||
|
|
||||||
|
int viewportX_ = 0;
|
||||||
|
int viewportY_ = 0;
|
||||||
|
int viewportWidth_ = 800;
|
||||||
|
int viewportHeight_ = 600;
|
||||||
|
|
||||||
|
mutable glm::mat4 viewMatrix_{1.0f};
|
||||||
|
mutable glm::mat4 projectionMatrix_{1.0f};
|
||||||
|
mutable glm::mat4 viewProjectionMatrix_{1.0f};
|
||||||
|
mutable bool dirty_ = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/core/color.h>
|
||||||
#include <extra2d/render/buffer.h>
|
#include <extra2d/render/buffer.h>
|
||||||
#include <extra2d/render/render_device.h>
|
#include <extra2d/render/render_device.h>
|
||||||
#include <extra2d/render/render_queue.h>
|
#include <extra2d/render/render_queue.h>
|
||||||
|
|
@ -21,8 +22,10 @@ public:
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 初始化渲染上下文
|
* @brief 初始化渲染上下文
|
||||||
|
* @param window SDL窗口指针
|
||||||
|
* @param config 渲染设备配置
|
||||||
*/
|
*/
|
||||||
bool init();
|
bool init(SDL_Window *window, const RenderDeviceConfig &config = {});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 关闭渲染上下文
|
* @brief 关闭渲染上下文
|
||||||
|
|
@ -119,7 +122,7 @@ public:
|
||||||
/**
|
/**
|
||||||
* @brief 获取渲染统计
|
* @brief 获取渲染统计
|
||||||
*/
|
*/
|
||||||
const RenderStats &stats() const { return stats_; }
|
const RenderStats &stats() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 重置渲染统计
|
* @brief 重置渲染统计
|
||||||
|
|
@ -142,7 +145,6 @@ private:
|
||||||
bool initialized_ = false;
|
bool initialized_ = false;
|
||||||
RenderDevice &device_ = RenderDevice::instance();
|
RenderDevice &device_ = RenderDevice::instance();
|
||||||
RenderQueue queue_;
|
RenderQueue queue_;
|
||||||
RenderStats stats_;
|
|
||||||
|
|
||||||
Viewport viewport_;
|
Viewport viewport_;
|
||||||
ScissorRect scissor_;
|
ScissorRect scissor_;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <extra2d/render/render_types.h>
|
#include <SDL.h>
|
||||||
#include <extra2d/render/buffer.h>
|
#include <extra2d/render/buffer.h>
|
||||||
|
#include <extra2d/render/render_types.h>
|
||||||
#include <extra2d/render/vao.h>
|
#include <extra2d/render/vao.h>
|
||||||
#include <glad/glad.h>
|
#include <glad/glad.h>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
@ -31,20 +32,35 @@ struct RenderCaps {
|
||||||
String glslVersion;
|
String glslVersion;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 渲染设备配置
|
||||||
|
*/
|
||||||
|
struct RenderDeviceConfig {
|
||||||
|
int glMajor = 4;
|
||||||
|
int glMinor = 5;
|
||||||
|
bool useES = false;
|
||||||
|
int redBits = 8;
|
||||||
|
int greenBits = 8;
|
||||||
|
int blueBits = 8;
|
||||||
|
int alphaBits = 8;
|
||||||
|
int depthBits = 24;
|
||||||
|
int stencilBits = 8;
|
||||||
|
bool doubleBuffer = true;
|
||||||
|
int msaaSamples = 0;
|
||||||
|
bool vsync = true;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 渲染设备
|
* @brief 渲染设备
|
||||||
*
|
|
||||||
* 封装 OpenGL 4.5 核心功能,使用 DSA API
|
|
||||||
*/
|
*/
|
||||||
class RenderDevice {
|
class RenderDevice {
|
||||||
public:
|
public:
|
||||||
static RenderDevice &instance();
|
static RenderDevice &instance();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 初始化渲染设备
|
* @brief 初始化 OpenGL 上下文
|
||||||
* @return 初始化成功返回 true
|
|
||||||
*/
|
*/
|
||||||
bool init();
|
bool init(SDL_Window *window, const RenderDeviceConfig &config = {});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 关闭渲染设备
|
* @brief 关闭渲染设备
|
||||||
|
|
@ -61,6 +77,21 @@ public:
|
||||||
*/
|
*/
|
||||||
const RenderCaps &caps() const { return caps_; }
|
const RenderCaps &caps() const { return caps_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取 SDL GL 上下文
|
||||||
|
*/
|
||||||
|
SDL_GLContext glContext() const { return glContext_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 交换缓冲区
|
||||||
|
*/
|
||||||
|
void swapBuffers();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置 VSync
|
||||||
|
*/
|
||||||
|
void setVSync(bool enabled);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 创建缓冲区
|
* @brief 创建缓冲区
|
||||||
*/
|
*/
|
||||||
|
|
@ -70,7 +101,8 @@ public:
|
||||||
/**
|
/**
|
||||||
* @brief 创建顶点缓冲区
|
* @brief 创建顶点缓冲区
|
||||||
*/
|
*/
|
||||||
std::unique_ptr<VertexBuffer> createVertexBuffer(BufferUsage usage, size_t size,
|
std::unique_ptr<VertexBuffer> createVertexBuffer(BufferUsage usage,
|
||||||
|
size_t size,
|
||||||
const void *data = nullptr);
|
const void *data = nullptr);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -82,7 +114,8 @@ public:
|
||||||
/**
|
/**
|
||||||
* @brief 创建 Uniform 缓冲区
|
* @brief 创建 Uniform 缓冲区
|
||||||
*/
|
*/
|
||||||
std::unique_ptr<UniformBuffer> createUniformBuffer(BufferUsage usage, size_t size,
|
std::unique_ptr<UniformBuffer>
|
||||||
|
createUniformBuffer(BufferUsage usage, size_t size,
|
||||||
const void *data = nullptr);
|
const void *data = nullptr);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -108,8 +141,8 @@ public:
|
||||||
/**
|
/**
|
||||||
* @brief 清除缓冲区
|
* @brief 清除缓冲区
|
||||||
*/
|
*/
|
||||||
void clear(bool color, bool depth, bool stencil,
|
void clear(bool color, bool depth, bool stencil, float r = 0.0f,
|
||||||
float r = 0.0f, float g = 0.0f, float b = 0.0f, float a = 1.0f);
|
float g = 0.0f, float b = 0.0f, float a = 1.0f);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 设置混合状态
|
* @brief 设置混合状态
|
||||||
|
|
@ -165,11 +198,14 @@ private:
|
||||||
RenderDevice(const RenderDevice &) = delete;
|
RenderDevice(const RenderDevice &) = delete;
|
||||||
RenderDevice &operator=(const RenderDevice &) = delete;
|
RenderDevice &operator=(const RenderDevice &) = delete;
|
||||||
|
|
||||||
|
bool initGL(const RenderDeviceConfig &config);
|
||||||
void queryCaps();
|
void queryCaps();
|
||||||
GLenum glPrimitiveType(PrimitiveType mode) const;
|
GLenum glPrimitiveType(PrimitiveType mode) const;
|
||||||
GLenum glIndexType(IndexType type) const;
|
GLenum glIndexType(IndexType type) const;
|
||||||
|
|
||||||
bool initialized_ = false;
|
bool initialized_ = false;
|
||||||
|
SDL_Window *window_ = nullptr;
|
||||||
|
SDL_GLContext glContext_ = nullptr;
|
||||||
RenderCaps caps_;
|
RenderCaps caps_;
|
||||||
|
|
||||||
BlendState currentBlend_;
|
BlendState currentBlend_;
|
||||||
|
|
|
||||||
|
|
@ -56,20 +56,12 @@ enum class CompareFunc : uint8 {
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// 剔除模式
|
// 剔除模式
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
enum class CullMode : uint8 {
|
enum class CullMode : uint8 { None, Front, Back };
|
||||||
None,
|
|
||||||
Front,
|
|
||||||
Back
|
|
||||||
};
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// 多边形模式
|
// 多边形模式
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
enum class PolygonMode : uint8 {
|
enum class PolygonMode : uint8 { Fill, Line, Point };
|
||||||
Fill,
|
|
||||||
Line,
|
|
||||||
Point
|
|
||||||
};
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// 混合因子
|
// 混合因子
|
||||||
|
|
@ -94,13 +86,7 @@ enum class BlendFactor : uint8 {
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// 混合操作
|
// 混合操作
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
enum class BlendOp : uint8 {
|
enum class BlendOp : uint8 { Add, Subtract, ReverseSubtract, Min, Max };
|
||||||
Add,
|
|
||||||
Subtract,
|
|
||||||
ReverseSubtract,
|
|
||||||
Min,
|
|
||||||
Max
|
|
||||||
};
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// 缓冲区类型
|
// 缓冲区类型
|
||||||
|
|
@ -116,11 +102,7 @@ enum class BufferType : uint8 {
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// 缓冲区使用方式
|
// 缓冲区使用方式
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
enum class BufferUsage : uint8 {
|
enum class BufferUsage : uint8 { Static, Dynamic, Stream };
|
||||||
Static,
|
|
||||||
Dynamic,
|
|
||||||
Stream
|
|
||||||
};
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// 纹理过滤模式
|
// 纹理过滤模式
|
||||||
|
|
@ -137,13 +119,7 @@ enum class FilterMode : uint8 {
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// 纹理环绕模式
|
// 纹理环绕模式
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
enum class WrapMode : uint8 {
|
enum class WrapMode : uint8 { Repeat, Clamp, Mirror, MirrorClamp, Border };
|
||||||
Repeat,
|
|
||||||
Clamp,
|
|
||||||
Mirror,
|
|
||||||
MirrorClamp,
|
|
||||||
Border
|
|
||||||
};
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// 图元类型
|
// 图元类型
|
||||||
|
|
@ -161,11 +137,7 @@ enum class PrimitiveType : uint8 {
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// 索引类型
|
// 索引类型
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
enum class IndexType : uint8 {
|
enum class IndexType : uint8 { UInt8, UInt16, UInt32 };
|
||||||
UInt8,
|
|
||||||
UInt16,
|
|
||||||
UInt32
|
|
||||||
};
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// 顶点属性语义
|
// 顶点属性语义
|
||||||
|
|
@ -225,13 +197,9 @@ struct BlendState {
|
||||||
BlendOp opAlpha = BlendOp::Add;
|
BlendOp opAlpha = BlendOp::Add;
|
||||||
uint8 colorWriteMask = 0xF;
|
uint8 colorWriteMask = 0xF;
|
||||||
|
|
||||||
static BlendState opaque() {
|
static BlendState opaque() { return BlendState{false, BlendMode::None}; }
|
||||||
return BlendState{false, BlendMode::None};
|
|
||||||
}
|
|
||||||
|
|
||||||
static BlendState alpha() {
|
static BlendState alpha() { return BlendState{true, BlendMode::Alpha}; }
|
||||||
return BlendState{true, BlendMode::Alpha};
|
|
||||||
}
|
|
||||||
|
|
||||||
static BlendState additive() {
|
static BlendState additive() {
|
||||||
BlendState state;
|
BlendState state;
|
||||||
|
|
@ -354,37 +322,11 @@ struct ClearFlags {
|
||||||
bool depth = true;
|
bool depth = true;
|
||||||
bool stencil = false;
|
bool stencil = false;
|
||||||
|
|
||||||
static ClearFlags all() {
|
static ClearFlags all() { return ClearFlags{true, true, true}; }
|
||||||
return ClearFlags{true, true, true};
|
|
||||||
}
|
|
||||||
|
|
||||||
static ClearFlags colorOnly() {
|
static ClearFlags colorOnly() { return ClearFlags{true, false, false}; }
|
||||||
return ClearFlags{true, false, false};
|
|
||||||
}
|
|
||||||
|
|
||||||
static ClearFlags depthOnly() {
|
static ClearFlags depthOnly() { return ClearFlags{false, true, false}; }
|
||||||
return ClearFlags{false, true, false};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// 颜色 (RGBA)
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
struct Color {
|
|
||||||
float r = 0.0f;
|
|
||||||
float g = 0.0f;
|
|
||||||
float b = 0.0f;
|
|
||||||
float a = 1.0f;
|
|
||||||
|
|
||||||
Color() = default;
|
|
||||||
Color(float r, float g, float b, float a = 1.0f) : r(r), g(g), b(b), a(a) {}
|
|
||||||
|
|
||||||
static Color white() { return Color(1.0f, 1.0f, 1.0f, 1.0f); }
|
|
||||||
static Color black() { return Color(0.0f, 0.0f, 0.0f, 1.0f); }
|
|
||||||
static Color red() { return Color(1.0f, 0.0f, 0.0f, 1.0f); }
|
|
||||||
static Color green() { return Color(0.0f, 1.0f, 0.0f, 1.0f); }
|
|
||||||
static Color blue() { return Color(0.0f, 0.0f, 1.0f, 1.0f); }
|
|
||||||
static Color transparent() { return Color(0.0f, 0.0f, 0.0f, 0.0f); }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace extra2d
|
} // namespace extra2d
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,153 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/core/color.h>
|
||||||
|
#include <extra2d/core/math_types.h>
|
||||||
|
#include <extra2d/render/camera.h>
|
||||||
|
#include <extra2d/render/shape_renderer.h>
|
||||||
|
#include <extra2d/render/sprite_renderer.h>
|
||||||
|
#include <extra2d/render/texture.h>
|
||||||
|
#include <extra2d/render/render_context.h>
|
||||||
|
#include <glm/mat4x4.hpp>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 高层渲染器
|
||||||
|
*
|
||||||
|
* 封装 ShapeRenderer 和 SpriteRenderer,提供简单易用的 2D 渲染 API
|
||||||
|
*/
|
||||||
|
class Renderer {
|
||||||
|
public:
|
||||||
|
Renderer();
|
||||||
|
~Renderer();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 初始化渲染器
|
||||||
|
*/
|
||||||
|
bool init();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 关闭渲染器
|
||||||
|
*/
|
||||||
|
void shutdown();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 开始帧渲染
|
||||||
|
* @param clearColor 清屏颜色
|
||||||
|
*/
|
||||||
|
void beginFrame(const Color& clearColor = Colors::Black);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 结束帧渲染
|
||||||
|
*/
|
||||||
|
void endFrame();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置视图投影矩阵
|
||||||
|
*/
|
||||||
|
void setViewProjection(const glm::mat4& viewProjection);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取视图投影矩阵
|
||||||
|
*/
|
||||||
|
const glm::mat4& viewProjection() const { return viewProjection_; }
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// 图形绘制 API
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 绘制线条
|
||||||
|
*/
|
||||||
|
void drawLine(const Vec2& start, const Vec2& end, const Color& color, float width = 1.0f);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 绘制矩形(描边)
|
||||||
|
*/
|
||||||
|
void drawRect(const Rect& rect, const Color& color, float width = 1.0f);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 绘制矩形(填充)
|
||||||
|
*/
|
||||||
|
void fillRect(const Rect& rect, const Color& color);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 绘制圆形(描边)
|
||||||
|
*/
|
||||||
|
void drawCircle(const Vec2& center, float radius, const Color& color,
|
||||||
|
int segments = 32, float width = 1.0f);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 绘制圆形(填充)
|
||||||
|
*/
|
||||||
|
void fillCircle(const Vec2& center, float radius, const Color& color, int segments = 32);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 绘制三角形(描边)
|
||||||
|
*/
|
||||||
|
void drawTriangle(const Vec2& p1, const Vec2& p2, const Vec2& p3,
|
||||||
|
const Color& color, float width = 1.0f);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 绘制三角形(填充)
|
||||||
|
*/
|
||||||
|
void fillTriangle(const Vec2& p1, const Vec2& p2, const Vec2& p3, const Color& color);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 绘制多边形(描边)
|
||||||
|
*/
|
||||||
|
void drawPolygon(const std::vector<Vec2>& points, const Color& color, float width = 1.0f);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 绘制多边形(填充)
|
||||||
|
*/
|
||||||
|
void fillPolygon(const std::vector<Vec2>& points, const Color& color);
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// 精灵绘制 API
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 绘制精灵
|
||||||
|
*/
|
||||||
|
void drawSprite(const Texture& texture, const Rect& destRect, const Rect& srcRect,
|
||||||
|
const Color& color = Colors::White, float rotation = 0.0f,
|
||||||
|
const Vec2& anchor = Vec2(0.5f, 0.5f));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 绘制精灵(简化版)
|
||||||
|
*/
|
||||||
|
void drawSprite(const Texture& texture, const Vec2& position,
|
||||||
|
const Color& color = Colors::White);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 绘制精灵(带缩放)
|
||||||
|
*/
|
||||||
|
void drawSprite(const Texture& texture, const Vec2& position, const Vec2& scale,
|
||||||
|
const Color& color = Colors::White);
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// 统计信息
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取绘制调用次数
|
||||||
|
*/
|
||||||
|
uint32 drawCalls() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取精灵数量
|
||||||
|
*/
|
||||||
|
uint32 spriteCount() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
glm::vec4 toVec4(const Color& color) const;
|
||||||
|
|
||||||
|
std::unique_ptr<ShapeRenderer> shapeRenderer_;
|
||||||
|
std::unique_ptr<SpriteRenderer> spriteRenderer_;
|
||||||
|
glm::mat4 viewProjection_{1.0f};
|
||||||
|
bool inFrame_ = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,108 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/core/types.h>
|
||||||
|
#include <extra2d/render/render_types.h>
|
||||||
|
#include <glad/glad.h>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 纹理配置
|
||||||
|
*/
|
||||||
|
struct TextureConfig {
|
||||||
|
int width = 0;
|
||||||
|
int height = 0;
|
||||||
|
int channels = 4;
|
||||||
|
FilterMode filter = FilterMode::Linear;
|
||||||
|
WrapMode wrap = WrapMode::Clamp;
|
||||||
|
bool generateMips = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief GPU 纹理类
|
||||||
|
*
|
||||||
|
* 封装 OpenGL 纹理对象
|
||||||
|
*/
|
||||||
|
class Texture {
|
||||||
|
public:
|
||||||
|
Texture() = default;
|
||||||
|
~Texture();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 从像素数据创建纹理
|
||||||
|
*/
|
||||||
|
static std::unique_ptr<Texture> create(const u8* data, const TextureConfig& config);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 创建空纹理
|
||||||
|
*/
|
||||||
|
static std::unique_ptr<Texture> createEmpty(int width, int height, int channels = 4);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 更新纹理数据
|
||||||
|
*/
|
||||||
|
void update(const u8* data, int width, int height, int channels);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 更新纹理子区域
|
||||||
|
*/
|
||||||
|
void updateSubRegion(const u8* data, int x, int y, int width, int height);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 绑定到纹理单元
|
||||||
|
*/
|
||||||
|
void bind(uint32 unit = 0) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 解绑纹理
|
||||||
|
*/
|
||||||
|
void unbind() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取 OpenGL 纹理 ID
|
||||||
|
*/
|
||||||
|
GLuint glHandle() const { return glHandle_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取宽度
|
||||||
|
*/
|
||||||
|
int width() const { return width_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取高度
|
||||||
|
*/
|
||||||
|
int height() const { return height_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取通道数
|
||||||
|
*/
|
||||||
|
int channels() const { return channels_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查纹理是否有效
|
||||||
|
*/
|
||||||
|
bool isValid() const { return glHandle_ != 0; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置过滤模式
|
||||||
|
*/
|
||||||
|
void setFilter(FilterMode filter);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置环绕模式
|
||||||
|
*/
|
||||||
|
void setWrap(WrapMode wrap);
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool init(const u8* data, const TextureConfig& config);
|
||||||
|
GLenum glInternalFormat() const;
|
||||||
|
GLenum glFormat() const;
|
||||||
|
|
||||||
|
GLuint glHandle_ = 0;
|
||||||
|
int width_ = 0;
|
||||||
|
int height_ = 0;
|
||||||
|
int channels_ = 4;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -1,21 +1,17 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <extra2d/core/color.h>
|
#include <extra2d/core/color.h>
|
||||||
#include <extra2d/core/math_types.h>
|
#include <extra2d/core/math_types.h>
|
||||||
#include <extra2d/core/types.h>
|
#include <extra2d/core/types.h>
|
||||||
#include <extra2d/event/event_dispatcher.h>
|
#include <extra2d/event/event_dispatcher.h>
|
||||||
#include <extra2d/graphics/renderer.h>
|
#include <extra2d/render/renderer.h>
|
||||||
#include <functional>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
// 前向声明
|
|
||||||
class Scene;
|
class Scene;
|
||||||
class Renderer;
|
class Renderer;
|
||||||
struct RenderCommand;
|
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// 节点基类 - 场景图的基础
|
// 节点基类 - 场景图的基础
|
||||||
|
|
@ -25,30 +21,20 @@ public:
|
||||||
Node();
|
Node();
|
||||||
virtual ~Node();
|
virtual ~Node();
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 层级管理
|
// 层级管理
|
||||||
// ------------------------------------------------------------------------
|
void addChild(Ref<Node> child);
|
||||||
void addChild(Ptr<Node> child);
|
void addChildren(std::vector<Ref<Node>> &&children);
|
||||||
|
void removeChild(Ref<Node> child);
|
||||||
/**
|
|
||||||
* @brief 批量添加子节点
|
|
||||||
* @param children 子节点列表
|
|
||||||
*/
|
|
||||||
void addChildren(std::vector<Ptr<Node>> &&children);
|
|
||||||
|
|
||||||
void removeChild(Ptr<Node> child);
|
|
||||||
void removeChildByName(const std::string &name);
|
void removeChildByName(const std::string &name);
|
||||||
void removeFromParent();
|
void removeFromParent();
|
||||||
void removeAllChildren();
|
void removeAllChildren();
|
||||||
|
|
||||||
Ptr<Node> getParent() const { return parent_.lock(); }
|
Ref<Node> getParent() const { return parent_.lock(); }
|
||||||
const std::vector<Ptr<Node>> &getChildren() const { return children_; }
|
const std::vector<Ref<Node>> &getChildren() const { return children_; }
|
||||||
Ptr<Node> getChildByName(const std::string &name) const;
|
Ref<Node> getChildByName(const std::string &name) const;
|
||||||
Ptr<Node> getChildByTag(int tag) const;
|
Ref<Node> getChildByTag(int tag) const;
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 变换属性
|
// 变换属性
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void setPosition(const Vec2 &pos);
|
void setPosition(const Vec2 &pos);
|
||||||
void setPosition(float x, float y);
|
void setPosition(float x, float y);
|
||||||
Vec2 getPosition() const { return position_; }
|
Vec2 getPosition() const { return position_; }
|
||||||
|
|
@ -75,66 +61,39 @@ public:
|
||||||
void setVisible(bool visible);
|
void setVisible(bool visible);
|
||||||
bool isVisible() const { return visible_; }
|
bool isVisible() const { return visible_; }
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置颜色
|
|
||||||
* @param color RGB颜色
|
|
||||||
*/
|
|
||||||
void setColor(const Color3B &color);
|
void setColor(const Color3B &color);
|
||||||
Color3B getColor() const { return color_; }
|
Color3B getColor() const { return color_; }
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置X轴翻转
|
|
||||||
*/
|
|
||||||
void setFlipX(bool flipX);
|
void setFlipX(bool flipX);
|
||||||
bool isFlipX() const { return flipX_; }
|
bool isFlipX() const { return flipX_; }
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置Y轴翻转
|
|
||||||
*/
|
|
||||||
void setFlipY(bool flipY);
|
void setFlipY(bool flipY);
|
||||||
bool isFlipY() const { return flipY_; }
|
bool isFlipY() const { return flipY_; }
|
||||||
|
|
||||||
void setZOrder(int zOrder);
|
void setZOrder(int zOrder);
|
||||||
int getZOrder() const { return zOrder_; }
|
int getZOrder() const { return zOrder_; }
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 世界变换
|
// 世界变换
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
Vec2 convertToWorldSpace(const Vec2 &localPos) const;
|
Vec2 convertToWorldSpace(const Vec2 &localPos) const;
|
||||||
Vec2 convertToNodeSpace(const Vec2 &worldPos) const;
|
Vec2 convertToNodeSpace(const Vec2 &worldPos) const;
|
||||||
|
|
||||||
glm::mat4 getLocalTransform() const;
|
glm::mat4 getLocalTransform() const;
|
||||||
glm::mat4 getWorldTransform() const;
|
glm::mat4 getWorldTransform() const;
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 标记变换矩阵为脏状态,并传播到所有子节点
|
|
||||||
*/
|
|
||||||
void markTransformDirty();
|
void markTransformDirty();
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 批量更新变换矩阵
|
|
||||||
* 在渲染前统一计算所有脏节点的变换矩阵,避免逐节点计算时的重复递归
|
|
||||||
*/
|
|
||||||
void batchUpdateTransforms();
|
void batchUpdateTransforms();
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取变换脏标记状态
|
|
||||||
*/
|
|
||||||
bool isTransformDirty() const { return transformDirty_; }
|
bool isTransformDirty() const { return transformDirty_; }
|
||||||
bool isWorldTransformDirty() const { return worldTransformDirty_; }
|
bool isWorldTransformDirty() const { return worldTransformDirty_; }
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 名称和标签
|
// 名称和标签
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void setName(const std::string &name) { name_ = name; }
|
void setName(const std::string &name) { name_ = name; }
|
||||||
const std::string &getName() const { return name_; }
|
const std::string &getName() const { return name_; }
|
||||||
|
|
||||||
void setTag(int tag) { tag_ = tag; }
|
void setTag(int tag) { tag_ = tag; }
|
||||||
int getTag() const { return tag_; }
|
int getTag() const { return tag_; }
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 生命周期回调
|
// 生命周期回调
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
virtual void onEnter();
|
virtual void onEnter();
|
||||||
virtual void onExit();
|
virtual void onExit();
|
||||||
virtual void onUpdate(float dt);
|
virtual void onUpdate(float dt);
|
||||||
|
|
@ -142,26 +101,17 @@ public:
|
||||||
virtual void onAttachToScene(Scene *scene);
|
virtual void onAttachToScene(Scene *scene);
|
||||||
virtual void onDetachFromScene();
|
virtual void onDetachFromScene();
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 边界框(用于空间索引)
|
// 边界框(用于空间索引)
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
virtual Rect getBoundingBox() const;
|
virtual Rect getBoundingBox() const;
|
||||||
|
|
||||||
// 是否需要参与空间索引(默认 true)
|
|
||||||
void setSpatialIndexed(bool indexed) { spatialIndexed_ = indexed; }
|
void setSpatialIndexed(bool indexed) { spatialIndexed_ = indexed; }
|
||||||
bool isSpatialIndexed() const { return spatialIndexed_; }
|
bool isSpatialIndexed() const { return spatialIndexed_; }
|
||||||
|
|
||||||
// 更新空间索引(手动调用,通常在边界框变化后)
|
|
||||||
void updateSpatialIndex();
|
void updateSpatialIndex();
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 事件系统
|
// 事件系统
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
EventDispatcher &getEventDispatcher() { return eventDispatcher_; }
|
EventDispatcher &getEventDispatcher() { return eventDispatcher_; }
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 内部方法
|
// 内部方法
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void update(float dt);
|
void update(float dt);
|
||||||
void render(Renderer &renderer);
|
void render(Renderer &renderer);
|
||||||
void sortChildren();
|
void sortChildren();
|
||||||
|
|
@ -169,18 +119,10 @@ public:
|
||||||
bool isRunning() const { return running_; }
|
bool isRunning() const { return running_; }
|
||||||
Scene *getScene() const { return scene_; }
|
Scene *getScene() const { return scene_; }
|
||||||
|
|
||||||
// 多线程渲染命令收集
|
|
||||||
virtual void collectRenderCommands(std::vector<RenderCommand> &commands,
|
|
||||||
int parentZOrder = 0);
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
// 子类重写
|
|
||||||
virtual void onDraw(Renderer &renderer) {}
|
virtual void onDraw(Renderer &renderer) {}
|
||||||
virtual void onUpdateNode(float dt) {}
|
virtual void onUpdateNode(float dt) {}
|
||||||
virtual void generateRenderCommand(std::vector<RenderCommand> &commands,
|
|
||||||
int zOrder) {};
|
|
||||||
|
|
||||||
// 供子类访问的内部状态
|
|
||||||
Vec2 &getPositionRef() { return position_; }
|
Vec2 &getPositionRef() { return position_; }
|
||||||
Vec2 &getScaleRef() { return scale_; }
|
Vec2 &getScaleRef() { return scale_; }
|
||||||
Vec2 &getAnchorRef() { return anchor_; }
|
Vec2 &getAnchorRef() { return anchor_; }
|
||||||
|
|
@ -188,65 +130,45 @@ protected:
|
||||||
float getOpacityRef() { return opacity_; }
|
float getOpacityRef() { return opacity_; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// ==========================================================================
|
mutable glm::mat4 localTransform_{1.0f};
|
||||||
// 成员变量按类型大小降序排列,减少内存对齐填充
|
mutable glm::mat4 worldTransform_{1.0f};
|
||||||
// 64位系统对齐:std::string(32) > glm::mat4(64) > std::vector(24) >
|
|
||||||
// double(8) > float(4) > int(4) > bool(1)
|
|
||||||
// ==========================================================================
|
|
||||||
|
|
||||||
// 1. 大块内存(64字节)
|
std::string name_;
|
||||||
mutable glm::mat4 localTransform_; // 64 bytes
|
std::vector<Ref<Node>> children_;
|
||||||
mutable glm::mat4 worldTransform_; // 64 bytes
|
|
||||||
|
|
||||||
// 2. 字符串和容器(24-32字节)
|
std::unordered_map<std::string, std::weak_ptr<Node>> nameIndex_;
|
||||||
std::string name_; // 32 bytes
|
std::unordered_map<int, std::weak_ptr<Node>> tagIndex_;
|
||||||
std::vector<Ptr<Node>> children_; // 24 bytes
|
|
||||||
|
|
||||||
// 3. 子节点索引(加速查找)
|
EventDispatcher eventDispatcher_;
|
||||||
std::unordered_map<std::string, WeakPtr<Node>> nameIndex_; // 56 bytes
|
|
||||||
std::unordered_map<int, WeakPtr<Node>> tagIndex_; // 56 bytes
|
|
||||||
|
|
||||||
// 4. 事件分发器
|
std::weak_ptr<Node> parent_;
|
||||||
EventDispatcher eventDispatcher_; // 大小取决于实现
|
|
||||||
|
|
||||||
// 5. 父节点引用
|
Vec2 position_ = Vec2::Zero();
|
||||||
WeakPtr<Node> parent_; // 16 bytes
|
Vec2 scale_ = Vec2(1.0f, 1.0f);
|
||||||
|
Vec2 anchor_ = Vec2(0.5f, 0.5f);
|
||||||
|
Vec2 skew_ = Vec2::Zero();
|
||||||
|
|
||||||
// 7. 变换属性(按访问频率分组)
|
Rect lastSpatialBounds_;
|
||||||
Vec2 position_ = Vec2::Zero(); // 8 bytes
|
|
||||||
Vec2 scale_ = Vec2(1.0f, 1.0f); // 8 bytes
|
|
||||||
Vec2 anchor_ = Vec2(0.5f, 0.5f); // 8 bytes
|
|
||||||
Vec2 skew_ = Vec2::Zero(); // 8 bytes
|
|
||||||
|
|
||||||
// 8. 边界框(用于空间索引)
|
float rotation_ = 0.0f;
|
||||||
Rect lastSpatialBounds_; // 16 bytes
|
float opacity_ = 1.0f;
|
||||||
|
|
||||||
// 9. 浮点属性
|
Color3B color_ = Color3B(255, 255, 255);
|
||||||
float rotation_ = 0.0f; // 4 bytes
|
|
||||||
float opacity_ = 1.0f; // 4 bytes
|
|
||||||
|
|
||||||
// 10. 颜色属性
|
int zOrder_ = 0;
|
||||||
Color3B color_ = Color3B(255, 255, 255); // 3 bytes
|
int tag_ = -1;
|
||||||
|
|
||||||
// 11. 整数属性
|
bool flipX_ = false;
|
||||||
int zOrder_ = 0; // 4 bytes
|
bool flipY_ = false;
|
||||||
int tag_ = -1; // 4 bytes
|
|
||||||
|
|
||||||
// 12. 布尔属性
|
Scene *scene_ = nullptr;
|
||||||
bool flipX_ = false; // 1 byte
|
|
||||||
bool flipY_ = false; // 1 byte
|
|
||||||
|
|
||||||
// 11. 场景指针
|
mutable bool transformDirty_ = true;
|
||||||
Scene *scene_ = nullptr; // 8 bytes
|
mutable bool worldTransformDirty_ = true;
|
||||||
|
bool childrenOrderDirty_ = false;
|
||||||
// 12. 布尔标志(打包在一起)
|
bool visible_ = true;
|
||||||
mutable bool transformDirty_ = true; // 1 byte
|
bool running_ = false;
|
||||||
mutable bool worldTransformDirty_ = true; // 1 byte
|
bool spatialIndexed_ = true;
|
||||||
bool childrenOrderDirty_ = false; // 1 byte
|
|
||||||
bool visible_ = true; // 1 byte
|
|
||||||
bool running_ = false; // 1 byte
|
|
||||||
bool spatialIndexed_ = true; // 1 byte
|
|
||||||
// 填充 2 bytes 到 8 字节对齐
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace extra2d
|
} // namespace extra2d
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,13 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <extra2d/core/color.h>
|
#include <extra2d/core/color.h>
|
||||||
#include <extra2d/graphics/camera.h>
|
#include <extra2d/render/camera.h>
|
||||||
#include <extra2d/scene/node.h>
|
#include <extra2d/scene/node.h>
|
||||||
#include <extra2d/spatial/spatial_manager.h>
|
#include <extra2d/spatial/spatial_manager.h>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
// 前向声明
|
|
||||||
struct RenderCommand;
|
|
||||||
class Renderer;
|
class Renderer;
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
@ -20,25 +18,19 @@ public:
|
||||||
Scene();
|
Scene();
|
||||||
~Scene() override = default;
|
~Scene() override = default;
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 场景属性
|
// 场景属性
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void setBackgroundColor(const Color& color) { backgroundColor_ = color; }
|
void setBackgroundColor(const Color& color) { backgroundColor_ = color; }
|
||||||
Color getBackgroundColor() const { return backgroundColor_; }
|
Color getBackgroundColor() const { return backgroundColor_; }
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 摄像机
|
// 摄像机
|
||||||
// ------------------------------------------------------------------------
|
void setCamera(Ref<Camera> camera);
|
||||||
void setCamera(Ptr<Camera> camera);
|
Ref<Camera> getCamera() const { return camera_; }
|
||||||
Ptr<Camera> getCamera() const { return camera_; }
|
|
||||||
|
|
||||||
Camera* getActiveCamera() const {
|
Camera* getActiveCamera() const {
|
||||||
return camera_ ? camera_.get() : defaultCamera_.get();
|
return camera_ ? camera_.get() : defaultCamera_.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 视口和尺寸
|
// 视口和尺寸
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void setViewportSize(float width, float height);
|
void setViewportSize(float width, float height);
|
||||||
void setViewportSize(const Size& size);
|
void setViewportSize(const Size& size);
|
||||||
Size getViewportSize() const { return viewportSize_; }
|
Size getViewportSize() const { return viewportSize_; }
|
||||||
|
|
@ -46,48 +38,34 @@ public:
|
||||||
float getWidth() const { return viewportSize_.width; }
|
float getWidth() const { return viewportSize_.width; }
|
||||||
float getHeight() const { return viewportSize_.height; }
|
float getHeight() const { return viewportSize_.height; }
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 场景状态
|
// 场景状态
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
bool isPaused() const { return paused_; }
|
bool isPaused() const { return paused_; }
|
||||||
void pause() { paused_ = true; }
|
void pause() { paused_ = true; }
|
||||||
void resume() { paused_ = false; }
|
void resume() { paused_ = false; }
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 渲染和更新
|
// 渲染和更新
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void renderScene(Renderer& renderer);
|
void renderScene(Renderer& renderer);
|
||||||
virtual void renderContent(Renderer& renderer);
|
virtual void renderContent(Renderer& renderer);
|
||||||
void updateScene(float dt);
|
void updateScene(float dt);
|
||||||
void collectRenderCommands(std::vector<RenderCommand> &commands,
|
|
||||||
int parentZOrder = 0) override;
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 空间索引系统
|
// 空间索引系统
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
SpatialManager& getSpatialManager() { return spatialManager_; }
|
SpatialManager& getSpatialManager() { return spatialManager_; }
|
||||||
const SpatialManager& getSpatialManager() const { return spatialManager_; }
|
const SpatialManager& getSpatialManager() const { return spatialManager_; }
|
||||||
|
|
||||||
// 启用/禁用空间索引
|
|
||||||
void setSpatialIndexingEnabled(bool enabled) {
|
void setSpatialIndexingEnabled(bool enabled) {
|
||||||
spatialIndexingEnabled_ = enabled;
|
spatialIndexingEnabled_ = enabled;
|
||||||
}
|
}
|
||||||
bool isSpatialIndexingEnabled() const { return spatialIndexingEnabled_; }
|
bool isSpatialIndexingEnabled() const { return spatialIndexingEnabled_; }
|
||||||
|
|
||||||
// 节点空间索引管理(内部使用)
|
void updateNodeInSpatialIndex(Node* node, const Rect& oldBounds, const Rect& newBounds);
|
||||||
void updateNodeInSpatialIndex(Node *node, const Rect &oldBounds,
|
|
||||||
const Rect &newBounds);
|
|
||||||
void removeNodeFromSpatialIndex(Node* node);
|
void removeNodeFromSpatialIndex(Node* node);
|
||||||
|
|
||||||
// 碰撞检测查询
|
|
||||||
std::vector<Node*> queryNodesInArea(const Rect& area) const;
|
std::vector<Node*> queryNodesInArea(const Rect& area) const;
|
||||||
std::vector<Node*> queryNodesAtPoint(const Vec2& point) const;
|
std::vector<Node*> queryNodesAtPoint(const Vec2& point) const;
|
||||||
std::vector<std::pair<Node*, Node*>> queryCollisions() const;
|
std::vector<std::pair<Node*, Node*>> queryCollisions() const;
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 静态创建方法
|
// 静态创建方法
|
||||||
// ------------------------------------------------------------------------
|
static Ref<Scene> create();
|
||||||
static Ptr<Scene> create();
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void onEnter() override;
|
void onEnter() override;
|
||||||
|
|
@ -99,12 +77,11 @@ private:
|
||||||
Color backgroundColor_ = Colors::Black;
|
Color backgroundColor_ = Colors::Black;
|
||||||
Size viewportSize_ = Size::Zero();
|
Size viewportSize_ = Size::Zero();
|
||||||
|
|
||||||
Ptr<Camera> camera_;
|
Ref<Camera> camera_;
|
||||||
Ptr<Camera> defaultCamera_;
|
Ref<Camera> defaultCamera_;
|
||||||
|
|
||||||
bool paused_ = false;
|
bool paused_ = false;
|
||||||
|
|
||||||
// 空间索引系统
|
|
||||||
SpatialManager spatialManager_;
|
SpatialManager spatialManager_;
|
||||||
bool spatialIndexingEnabled_ = true;
|
bool spatialIndexingEnabled_ = true;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,98 +1,62 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <extra2d/core/types.h>
|
#include <extra2d/core/types.h>
|
||||||
|
#include <extra2d/render/renderer.h>
|
||||||
#include <extra2d/scene/scene.h>
|
#include <extra2d/scene/scene.h>
|
||||||
#include <functional>
|
|
||||||
#include <stack>
|
#include <stack>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
// 前向声明
|
|
||||||
struct RenderCommand;
|
|
||||||
class Renderer;
|
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// 场景管理器 - 管理场景的生命周期和切换
|
// 场景管理器 - 管理场景的生命周期和切换
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
class SceneManager {
|
class SceneManager {
|
||||||
public:
|
public:
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 单例访问
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
static SceneManager &getInstance();
|
static SceneManager &getInstance();
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 场景栈操作
|
// 场景栈操作
|
||||||
// ------------------------------------------------------------------------
|
void runWithScene(Ref<Scene> scene);
|
||||||
|
void replaceScene(Ref<Scene> scene);
|
||||||
// 运行第一个场景
|
void pushScene(Ref<Scene> scene);
|
||||||
void runWithScene(Ptr<Scene> scene);
|
|
||||||
|
|
||||||
// 替换当前场景
|
|
||||||
void replaceScene(Ptr<Scene> scene);
|
|
||||||
|
|
||||||
// 压入新场景(当前场景暂停)
|
|
||||||
void pushScene(Ptr<Scene> scene);
|
|
||||||
|
|
||||||
// 弹出当前场景(恢复上一个场景)
|
|
||||||
void popScene();
|
void popScene();
|
||||||
|
|
||||||
// 弹出到根场景
|
|
||||||
void popToRootScene();
|
void popToRootScene();
|
||||||
|
|
||||||
// 弹出到指定场景
|
|
||||||
void popToScene(const std::string &name);
|
void popToScene(const std::string &name);
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 获取场景
|
// 获取场景
|
||||||
// ------------------------------------------------------------------------
|
Ref<Scene> getCurrentScene() const;
|
||||||
Ptr<Scene> getCurrentScene() const;
|
Ref<Scene> getPreviousScene() const;
|
||||||
Ptr<Scene> getPreviousScene() const;
|
Ref<Scene> getRootScene() const;
|
||||||
Ptr<Scene> getRootScene() const;
|
Ref<Scene> getSceneByName(const std::string &name) const;
|
||||||
|
|
||||||
// 通过名称获取场景
|
|
||||||
Ptr<Scene> getSceneByName(const std::string &name) const;
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 查询
|
// 查询
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
size_t getSceneCount() const { return sceneStack_.size(); }
|
size_t getSceneCount() const { return sceneStack_.size(); }
|
||||||
bool isEmpty() const { return sceneStack_.empty(); }
|
bool isEmpty() const { return sceneStack_.empty(); }
|
||||||
bool hasScene(const std::string &name) const;
|
bool hasScene(const std::string &name) const;
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 更新和渲染
|
// 更新和渲染
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void update(float dt);
|
void update(float dt);
|
||||||
void render(Renderer &renderer);
|
void render(Renderer &renderer);
|
||||||
void collectRenderCommands(std::vector<RenderCommand> &commands);
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 过渡控制
|
// 过渡控制
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
bool isTransitioning() const { return false; }
|
bool isTransitioning() const { return false; }
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 清理
|
// 清理
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void end();
|
void end();
|
||||||
void purgeCachedScenes();
|
void purgeCachedScenes();
|
||||||
|
|
||||||
public:
|
// 场景切换(供 Application 使用)
|
||||||
|
void enterScene(Ref<Scene> scene);
|
||||||
|
|
||||||
|
private:
|
||||||
SceneManager() = default;
|
SceneManager() = default;
|
||||||
~SceneManager() = default;
|
~SceneManager() = default;
|
||||||
SceneManager(const SceneManager &) = delete;
|
SceneManager(const SceneManager &) = delete;
|
||||||
SceneManager &operator=(const SceneManager &) = delete;
|
SceneManager &operator=(const SceneManager &) = delete;
|
||||||
|
|
||||||
// 场景切换(供 Application 使用)
|
std::stack<Ref<Scene>> sceneStack_;
|
||||||
void enterScene(Ptr<Scene> scene);
|
std::unordered_map<std::string, Ref<Scene>> namedScenes_;
|
||||||
|
|
||||||
private:
|
|
||||||
std::stack<Ptr<Scene>> sceneStack_;
|
|
||||||
std::unordered_map<std::string, Ptr<Scene>> namedScenes_;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace extra2d
|
} // namespace extra2d
|
||||||
|
|
|
||||||
|
|
@ -20,48 +20,31 @@ public:
|
||||||
ShapeNode();
|
ShapeNode();
|
||||||
~ShapeNode() override = default;
|
~ShapeNode() override = default;
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 静态创建方法
|
// 静态创建方法
|
||||||
// ------------------------------------------------------------------------
|
static Ref<ShapeNode> create();
|
||||||
static Ptr<ShapeNode> create();
|
|
||||||
|
|
||||||
// 点
|
static Ref<ShapeNode> createPoint(const Vec2& pos, const Color& color);
|
||||||
static Ptr<ShapeNode> createPoint(const Vec2 &pos, const Color &color);
|
static Ref<ShapeNode> createLine(const Vec2& start, const Vec2& end,
|
||||||
|
|
||||||
// 线
|
|
||||||
static Ptr<ShapeNode> createLine(const Vec2 &start, const Vec2 &end,
|
|
||||||
const Color& color, float width = 1.0f);
|
const Color& color, float width = 1.0f);
|
||||||
|
static Ref<ShapeNode> createRect(const Rect& rect, const Color& color,
|
||||||
// 矩形
|
|
||||||
static Ptr<ShapeNode> createRect(const Rect &rect, const Color &color,
|
|
||||||
float width = 1.0f);
|
float width = 1.0f);
|
||||||
static Ptr<ShapeNode> createFilledRect(const Rect &rect, const Color &color);
|
static Ref<ShapeNode> createFilledRect(const Rect& rect, const Color& color);
|
||||||
|
static Ref<ShapeNode> createCircle(const Vec2& center, float radius,
|
||||||
// 圆形
|
|
||||||
static Ptr<ShapeNode> createCircle(const Vec2 ¢er, float radius,
|
|
||||||
const Color& color, int segments = 32,
|
const Color& color, int segments = 32,
|
||||||
float width = 1.0f);
|
float width = 1.0f);
|
||||||
static Ptr<ShapeNode> createFilledCircle(const Vec2 ¢er, float radius,
|
static Ref<ShapeNode> createFilledCircle(const Vec2& center, float radius,
|
||||||
const Color &color,
|
const Color& color, int segments = 32);
|
||||||
int segments = 32);
|
static Ref<ShapeNode> createTriangle(const Vec2& p1, const Vec2& p2,
|
||||||
|
|
||||||
// 三角形
|
|
||||||
static Ptr<ShapeNode> createTriangle(const Vec2 &p1, const Vec2 &p2,
|
|
||||||
const Vec2& p3, const Color& color,
|
const Vec2& p3, const Color& color,
|
||||||
float width = 1.0f);
|
float width = 1.0f);
|
||||||
static Ptr<ShapeNode> createFilledTriangle(const Vec2 &p1, const Vec2 &p2,
|
static Ref<ShapeNode> createFilledTriangle(const Vec2& p1, const Vec2& p2,
|
||||||
const Vec2 &p3,
|
const Vec2& p3, const Color& color);
|
||||||
const Color &color);
|
static Ref<ShapeNode> createPolygon(const std::vector<Vec2>& points,
|
||||||
|
|
||||||
// 多边形
|
|
||||||
static Ptr<ShapeNode> createPolygon(const std::vector<Vec2> &points,
|
|
||||||
const Color& color, float width = 1.0f);
|
const Color& color, float width = 1.0f);
|
||||||
static Ptr<ShapeNode> createFilledPolygon(const std::vector<Vec2> &points,
|
static Ref<ShapeNode> createFilledPolygon(const std::vector<Vec2>& points,
|
||||||
const Color& color);
|
const Color& color);
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 属性设置
|
// 属性设置
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void setShapeType(ShapeType type) { shapeType_ = type; }
|
void setShapeType(ShapeType type) { shapeType_ = type; }
|
||||||
ShapeType getShapeType() const { return shapeType_; }
|
ShapeType getShapeType() const { return shapeType_; }
|
||||||
|
|
||||||
|
|
@ -77,9 +60,6 @@ public:
|
||||||
void setSegments(int segments) { segments_ = segments; }
|
void setSegments(int segments) { segments_ = segments; }
|
||||||
int getSegments() const { return segments_; }
|
int getSegments() const { return segments_; }
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 点设置
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void setPoints(const std::vector<Vec2>& points);
|
void setPoints(const std::vector<Vec2>& points);
|
||||||
const std::vector<Vec2>& getPoints() const { return points_; }
|
const std::vector<Vec2>& getPoints() const { return points_; }
|
||||||
void addPoint(const Vec2& point);
|
void addPoint(const Vec2& point);
|
||||||
|
|
@ -89,8 +69,6 @@ public:
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void onDraw(Renderer& renderer) override;
|
void onDraw(Renderer& renderer) override;
|
||||||
void generateRenderCommand(std::vector<RenderCommand> &commands,
|
|
||||||
int zOrder) override;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ShapeType shapeType_ = ShapeType::Rect;
|
ShapeType shapeType_ = ShapeType::Rect;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <extra2d/graphics/texture.h>
|
#include <extra2d/render/texture.h>
|
||||||
#include <extra2d/scene/node.h>
|
#include <extra2d/scene/node.h>
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
@ -11,12 +11,12 @@ namespace extra2d {
|
||||||
class Sprite : public Node {
|
class Sprite : public Node {
|
||||||
public:
|
public:
|
||||||
Sprite();
|
Sprite();
|
||||||
explicit Sprite(Ptr<Texture> texture);
|
explicit Sprite(Ref<Texture> texture);
|
||||||
~Sprite() override = default;
|
~Sprite() override = default;
|
||||||
|
|
||||||
// 纹理
|
// 纹理
|
||||||
void setTexture(Ptr<Texture> texture);
|
void setTexture(Ref<Texture> texture);
|
||||||
Ptr<Texture> getTexture() const { return texture_; }
|
Ref<Texture> getTexture() const { return texture_; }
|
||||||
|
|
||||||
// 纹理矩形 (用于图集)
|
// 纹理矩形 (用于图集)
|
||||||
void setTextureRect(const Rect& rect);
|
void setTextureRect(const Rect& rect);
|
||||||
|
|
@ -33,19 +33,17 @@ public:
|
||||||
bool isFlipY() const { return flipY_; }
|
bool isFlipY() const { return flipY_; }
|
||||||
|
|
||||||
// 静态创建方法
|
// 静态创建方法
|
||||||
static Ptr<Sprite> create();
|
static Ref<Sprite> create();
|
||||||
static Ptr<Sprite> create(Ptr<Texture> texture);
|
static Ref<Sprite> create(Ref<Texture> texture);
|
||||||
static Ptr<Sprite> create(Ptr<Texture> texture, const Rect &rect);
|
static Ref<Sprite> create(Ref<Texture> texture, const Rect& rect);
|
||||||
|
|
||||||
Rect getBoundingBox() const override;
|
Rect getBoundingBox() const override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void onDraw(Renderer& renderer) override;
|
void onDraw(Renderer& renderer) override;
|
||||||
void generateRenderCommand(std::vector<RenderCommand> &commands,
|
|
||||||
int zOrder) override;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Ptr<Texture> texture_;
|
Ref<Texture> texture_;
|
||||||
Rect textureRect_;
|
Rect textureRect_;
|
||||||
Color color_ = Colors::White;
|
Color color_ = Colors::White;
|
||||||
bool flipX_ = false;
|
bool flipX_ = false;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
#include <extra2d/core/service_interface.h>
|
#include <extra2d/core/service_interface.h>
|
||||||
#include <extra2d/core/service_locator.h>
|
#include <extra2d/core/service_locator.h>
|
||||||
#include <extra2d/core/types.h>
|
#include <extra2d/core/types.h>
|
||||||
|
|
@ -10,7 +9,7 @@
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 日志颜色结构
|
* @brief 日志颜色
|
||||||
*/
|
*/
|
||||||
struct LogColor {
|
struct LogColor {
|
||||||
u8 r, g, b;
|
u8 r, g, b;
|
||||||
|
|
@ -18,36 +17,22 @@ struct LogColor {
|
||||||
constexpr LogColor() : r(255), g(255), b(255) {}
|
constexpr LogColor() : r(255), g(255), b(255) {}
|
||||||
constexpr LogColor(u8 r, u8 g, u8 b) : r(r), g(g), b(b) {}
|
constexpr LogColor(u8 r, u8 g, u8 b) : r(r), g(g), b(b) {}
|
||||||
|
|
||||||
static constexpr LogColor White() { return LogColor(255, 255, 255); }
|
static constexpr LogColor White() { return {255, 255, 255}; }
|
||||||
static constexpr LogColor Gray() { return LogColor(128, 128, 128); }
|
static constexpr LogColor Gray() { return {128, 128, 128}; }
|
||||||
static constexpr LogColor Red() { return LogColor(255, 85, 85); }
|
static constexpr LogColor Red() { return {255, 85, 85}; }
|
||||||
static constexpr LogColor Green() { return LogColor(85, 255, 85); }
|
static constexpr LogColor Green() { return {85, 255, 85}; }
|
||||||
static constexpr LogColor Yellow() { return LogColor(255, 255, 85); }
|
static constexpr LogColor Yellow() { return {255, 255, 85}; }
|
||||||
static constexpr LogColor Blue() { return LogColor(85, 85, 255); }
|
static constexpr LogColor Blue() { return {85, 85, 255}; }
|
||||||
static constexpr LogColor Magenta() { return LogColor(255, 85, 255); }
|
static constexpr LogColor Magenta() { return {255, 85, 255}; }
|
||||||
static constexpr LogColor Cyan() { return LogColor(85, 255, 255); }
|
static constexpr LogColor Cyan() { return {85, 255, 255}; }
|
||||||
static constexpr LogColor Orange() { return LogColor(255, 165, 0); }
|
static constexpr LogColor SkyLight() { return {125, 211, 252}; }
|
||||||
|
static constexpr LogColor IndigoLight() { return {165, 180, 252}; }
|
||||||
static constexpr LogColor Slate() { return LogColor(100, 116, 139); }
|
|
||||||
static constexpr LogColor SlateLight() { return LogColor(148, 163, 184); }
|
|
||||||
static constexpr LogColor Sky() { return LogColor(14, 165, 233); }
|
|
||||||
static constexpr LogColor SkyLight() { return LogColor(125, 211, 252); }
|
|
||||||
static constexpr LogColor Emerald() { return LogColor(16, 185, 129); }
|
|
||||||
static constexpr LogColor EmeraldLight() { return LogColor(110, 231, 183); }
|
|
||||||
static constexpr LogColor Amber() { return LogColor(245, 158, 11); }
|
|
||||||
static constexpr LogColor AmberLight() { return LogColor(252, 211, 77); }
|
|
||||||
static constexpr LogColor Rose() { return LogColor(244, 63, 94); }
|
|
||||||
static constexpr LogColor RoseLight() { return LogColor(253, 164, 175); }
|
|
||||||
static constexpr LogColor Violet() { return LogColor(139, 92, 246); }
|
|
||||||
static constexpr LogColor VioletLight() { return LogColor(196, 181, 253); }
|
|
||||||
static constexpr LogColor Indigo() { return LogColor(99, 102, 241); }
|
|
||||||
static constexpr LogColor IndigoLight() { return LogColor(165, 180, 252); }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 日志级别枚举
|
* @brief 日志级别
|
||||||
*/
|
*/
|
||||||
enum class LogLevel {
|
enum class LogLevel : u8 {
|
||||||
Trace = 0,
|
Trace = 0,
|
||||||
Debug = 1,
|
Debug = 1,
|
||||||
Info = 2,
|
Info = 2,
|
||||||
|
|
@ -65,102 +50,36 @@ class ILogger : public IService {
|
||||||
public:
|
public:
|
||||||
virtual ~ILogger() = default;
|
virtual ~ILogger() = default;
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置日志级别
|
|
||||||
*/
|
|
||||||
virtual void level(LogLevel lvl) = 0;
|
virtual void level(LogLevel lvl) = 0;
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取日志级别
|
|
||||||
*/
|
|
||||||
virtual LogLevel level() const = 0;
|
virtual LogLevel level() const = 0;
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查日志级别是否启用
|
|
||||||
*/
|
|
||||||
virtual bool enabled(LogLevel lvl) const = 0;
|
virtual bool enabled(LogLevel lvl) const = 0;
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 记录日志(格式化)
|
|
||||||
*/
|
|
||||||
virtual void log(LogLevel lvl, const char *fmt, ...) = 0;
|
virtual void log(LogLevel lvl, const char *fmt, ...) = 0;
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 记录日志(字符串)
|
|
||||||
*/
|
|
||||||
virtual void log(LogLevel lvl, const std::string &msg) = 0;
|
virtual void log(LogLevel lvl, const std::string &msg) = 0;
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Trace级别日志
|
|
||||||
*/
|
|
||||||
virtual void trace(const char *fmt, ...) = 0;
|
virtual void trace(const char *fmt, ...) = 0;
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Debug级别日志
|
|
||||||
*/
|
|
||||||
virtual void debug(const char *fmt, ...) = 0;
|
virtual void debug(const char *fmt, ...) = 0;
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Info级别日志
|
|
||||||
*/
|
|
||||||
virtual void info(const char *fmt, ...) = 0;
|
virtual void info(const char *fmt, ...) = 0;
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Registry级别日志(用于模块/服务注册显示)
|
|
||||||
*/
|
|
||||||
virtual void registry(const char *fmt, ...) = 0;
|
virtual void registry(const char *fmt, ...) = 0;
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Warn级别日志
|
|
||||||
*/
|
|
||||||
virtual void warn(const char *fmt, ...) = 0;
|
virtual void warn(const char *fmt, ...) = 0;
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Error级别日志
|
|
||||||
*/
|
|
||||||
virtual void error(const char *fmt, ...) = 0;
|
virtual void error(const char *fmt, ...) = 0;
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Fatal级别日志
|
|
||||||
*/
|
|
||||||
virtual void fatal(const char *fmt, ...) = 0;
|
virtual void fatal(const char *fmt, ...) = 0;
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置日志级别颜色
|
|
||||||
* @param lvl 日志级别
|
|
||||||
* @param c 颜色
|
|
||||||
*/
|
|
||||||
virtual void levelColor(LogLevel lvl, const LogColor &c) = 0;
|
virtual void levelColor(LogLevel lvl, const LogColor &c) = 0;
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取日志级别颜色
|
|
||||||
* @param lvl 日志级别
|
|
||||||
* @return 颜色
|
|
||||||
*/
|
|
||||||
virtual LogColor levelColor(LogLevel lvl) const = 0;
|
virtual LogColor levelColor(LogLevel lvl) const = 0;
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 启用/禁用颜色输出
|
|
||||||
* @param on 是否启用
|
|
||||||
*/
|
|
||||||
virtual void colors(bool on) = 0;
|
virtual void colors(bool on) = 0;
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 是否启用颜色输出
|
|
||||||
*/
|
|
||||||
virtual bool colors() const = 0;
|
virtual bool colors() const = 0;
|
||||||
|
|
||||||
ServiceInfo info() const override {
|
ServiceInfo info() const override {
|
||||||
ServiceInfo i;
|
ServiceInfo i;
|
||||||
i.name = "Logger";
|
i.name = "Logger";
|
||||||
i.priority = ServicePriority::Core;
|
i.priority = ServicePriority::Core;
|
||||||
i.enabled = true;
|
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 控制台日志服务实现
|
* @brief 控制台日志服务
|
||||||
*/
|
*/
|
||||||
class ConsoleLogger : public ILogger {
|
class ConsoleLogger : public ILogger {
|
||||||
public:
|
public:
|
||||||
|
|
@ -195,81 +114,66 @@ private:
|
||||||
const char *levelString(LogLevel lvl);
|
const char *levelString(LogLevel lvl);
|
||||||
std::string ansiColor(LogLevel lvl);
|
std::string ansiColor(LogLevel lvl);
|
||||||
|
|
||||||
LogLevel level_;
|
LogLevel level_ = LogLevel::Info;
|
||||||
bool colors_;
|
bool colors_ = true;
|
||||||
LogColor levelColors_[7];
|
LogColor levelColors_[7];
|
||||||
class Impl;
|
class Impl;
|
||||||
Unique<Impl> impl_;
|
Unique<Impl> impl_;
|
||||||
|
|
||||||
// 服务注册元数据
|
|
||||||
E2D_AUTO_REGISTER_SERVICE(ILogger, ConsoleLogger);
|
E2D_AUTO_REGISTER_SERVICE(ILogger, ConsoleLogger);
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace extra2d
|
} // namespace extra2d
|
||||||
|
|
||||||
// 格式化辅助函数 - 将参数转换为字符串
|
// 格式化辅助
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
namespace detail {
|
namespace detail {
|
||||||
template <typename T> std::string to_string(T &&value) {
|
template <typename T> std::string to_string(T &&value) {
|
||||||
using Decayed = std::decay_t<T>;
|
using D = std::decay_t<T>;
|
||||||
if constexpr (std::is_same_v<Decayed, std::string>) {
|
if constexpr (std::is_same_v<D, std::string>)
|
||||||
return value;
|
return value;
|
||||||
} else if constexpr (std::is_same_v<Decayed, const char *>) {
|
else if constexpr (std::is_same_v<D, const char *>)
|
||||||
return value ? value : "(null)";
|
return value ? value : "(null)";
|
||||||
} else if constexpr (std::is_arithmetic_v<Decayed>) {
|
else if constexpr (std::is_same_v<D, bool>)
|
||||||
if constexpr (std::is_same_v<Decayed, bool>) {
|
|
||||||
return value ? "true" : "false";
|
return value ? "true" : "false";
|
||||||
} else if constexpr (std::is_floating_point_v<Decayed>) {
|
else if constexpr (std::is_arithmetic_v<D>)
|
||||||
return std::to_string(value);
|
return std::to_string(value);
|
||||||
} else {
|
else
|
||||||
return std::to_string(value);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return "<?>";
|
return "<?>";
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
inline void format_impl(std::string &result, const char *fmt) { result += fmt; }
|
inline void format_impl(std::string &result, const char *fmt) { result += fmt; }
|
||||||
|
|
||||||
template <typename T, typename... Args>
|
template <typename T, typename... Args>
|
||||||
void format_impl(std::string &result, const char *fmt, T &&value,
|
void format_impl(std::string &result, const char *fmt, T &&value,
|
||||||
Args &&...args) {
|
Args &&...args) {
|
||||||
const char *p = fmt;
|
while (*fmt) {
|
||||||
while (*p) {
|
if (*fmt == '{' && *(fmt + 1) == '}') {
|
||||||
if (*p == '{' && *(p + 1) == '}') {
|
|
||||||
result += to_string(std::forward<T>(value));
|
result += to_string(std::forward<T>(value));
|
||||||
format_impl(result, p + 2, std::forward<Args>(args)...);
|
return format_impl(result, fmt + 2, std::forward<Args>(args)...);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
result += *p++;
|
result += *fmt++;
|
||||||
}
|
}
|
||||||
result += " ";
|
result += " " + to_string(std::forward<T>(value));
|
||||||
result += to_string(std::forward<T>(value));
|
format_impl(result, fmt, std::forward<Args>(args)...);
|
||||||
format_impl(result, p, std::forward<Args>(args)...);
|
|
||||||
}
|
}
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
|
|
||||||
template <typename... Args>
|
template <typename... Args>
|
||||||
std::string format_str(const char *fmt, Args &&...args) {
|
std::string format_str(const char *fmt, Args &&...args) {
|
||||||
if constexpr (sizeof...(args) == 0) {
|
|
||||||
return std::string(fmt);
|
|
||||||
} else {
|
|
||||||
std::string result;
|
std::string result;
|
||||||
detail::format_impl(result, fmt, std::forward<Args>(args)...);
|
detail::format_impl(result, fmt, std::forward<Args>(args)...);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} // namespace extra2d
|
} // namespace extra2d
|
||||||
|
|
||||||
// 便捷宏 - 自动获取日志服务
|
// 日志宏
|
||||||
#define E2D_LOG(lvl, ...) \
|
#define E2D_LOG(lvl, ...) \
|
||||||
do { \
|
do { \
|
||||||
if (auto logService = ::extra2d::ServiceLocator::instance() \
|
if (auto log = ::extra2d::ServiceLocator::instance() \
|
||||||
.tryGet<::extra2d::ILogger>()) { \
|
.tryGet<::extra2d::ILogger>()) \
|
||||||
if (logService->enabled(lvl)) { \
|
if (log->enabled(lvl)) \
|
||||||
logService->log(lvl, ::extra2d::format_str(__VA_ARGS__)); \
|
log->log(lvl, ::extra2d::format_str(__VA_ARGS__)); \
|
||||||
} \
|
|
||||||
} \
|
|
||||||
} while (0)
|
} while (0)
|
||||||
|
|
||||||
#define E2D_TRACE(...) E2D_LOG(::extra2d::LogLevel::Trace, __VA_ARGS__)
|
#define E2D_TRACE(...) E2D_LOG(::extra2d::LogLevel::Trace, __VA_ARGS__)
|
||||||
|
|
@ -279,32 +183,3 @@ std::string format_str(const char *fmt, Args &&...args) {
|
||||||
#define E2D_WARN(...) E2D_LOG(::extra2d::LogLevel::Warn, __VA_ARGS__)
|
#define E2D_WARN(...) E2D_LOG(::extra2d::LogLevel::Warn, __VA_ARGS__)
|
||||||
#define E2D_ERROR(...) E2D_LOG(::extra2d::LogLevel::Error, __VA_ARGS__)
|
#define E2D_ERROR(...) E2D_LOG(::extra2d::LogLevel::Error, __VA_ARGS__)
|
||||||
#define E2D_FATAL(...) E2D_LOG(::extra2d::LogLevel::Fatal, __VA_ARGS__)
|
#define E2D_FATAL(...) E2D_LOG(::extra2d::LogLevel::Fatal, __VA_ARGS__)
|
||||||
|
|
||||||
// 带颜色参数的日志宏
|
|
||||||
#define E2D_LOG_COLOR(lvl, c, ...) \
|
|
||||||
do { \
|
|
||||||
if (auto logService = ::extra2d::ServiceLocator::instance() \
|
|
||||||
.tryGet<::extra2d::ILogger>()) { \
|
|
||||||
if (logService->enabled(lvl)) { \
|
|
||||||
auto prevColor = logService->levelColor(lvl); \
|
|
||||||
logService->levelColor(lvl, c); \
|
|
||||||
logService->log(lvl, ::extra2d::format_str(__VA_ARGS__)); \
|
|
||||||
logService->levelColor(lvl, prevColor); \
|
|
||||||
} \
|
|
||||||
} \
|
|
||||||
} while (0)
|
|
||||||
|
|
||||||
#define E2D_TRACE_COLOR(c, ...) \
|
|
||||||
E2D_LOG_COLOR(::extra2d::LogLevel::Trace, c, __VA_ARGS__)
|
|
||||||
#define E2D_DEBUG_COLOR(c, ...) \
|
|
||||||
E2D_LOG_COLOR(::extra2d::LogLevel::Debug, c, __VA_ARGS__)
|
|
||||||
#define E2D_INFO_COLOR(c, ...) \
|
|
||||||
E2D_LOG_COLOR(::extra2d::LogLevel::Info, c, __VA_ARGS__)
|
|
||||||
#define E2D_REGISTRY_COLOR(c, ...) \
|
|
||||||
E2D_LOG_COLOR(::extra2d::LogLevel::Registry, c, __VA_ARGS__)
|
|
||||||
#define E2D_WARN_COLOR(c, ...) \
|
|
||||||
E2D_LOG_COLOR(::extra2d::LogLevel::Warn, c, __VA_ARGS__)
|
|
||||||
#define E2D_ERROR_COLOR(c, ...) \
|
|
||||||
E2D_LOG_COLOR(::extra2d::LogLevel::Error, c, __VA_ARGS__)
|
|
||||||
#define E2D_FATAL_COLOR(c, ...) \
|
|
||||||
E2D_LOG_COLOR(::extra2d::LogLevel::Fatal, c, __VA_ARGS__)
|
|
||||||
|
|
|
||||||
|
|
@ -1,270 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cstdarg>
|
|
||||||
#include <cstdio>
|
|
||||||
#include <extra2d/core/types.h>
|
|
||||||
#include <sstream>
|
|
||||||
#include <string>
|
|
||||||
#include <type_traits>
|
|
||||||
|
|
||||||
// SDL2 日志头文件
|
|
||||||
#include <SDL.h>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 日志级别枚举 - 映射到 SDL_LogPriority
|
|
||||||
// ============================================================================
|
|
||||||
enum class LogLevel {
|
|
||||||
Trace = SDL_LOG_PRIORITY_VERBOSE, // SDL 详细日志
|
|
||||||
Debug = SDL_LOG_PRIORITY_DEBUG, // SDL 调试日志
|
|
||||||
Info = SDL_LOG_PRIORITY_INFO, // SDL 信息日志
|
|
||||||
Warn = SDL_LOG_PRIORITY_WARN, // SDL 警告日志
|
|
||||||
Error = SDL_LOG_PRIORITY_ERROR, // SDL 错误日志
|
|
||||||
Fatal = SDL_LOG_PRIORITY_CRITICAL, // SDL 严重日志
|
|
||||||
Off = SDL_LOG_PRIORITY_CRITICAL + 1 // 关闭日志 (使用 Critical+1 作为关闭标记)
|
|
||||||
};
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 简单的 fmt-style {} 格式化器
|
|
||||||
// ============================================================================
|
|
||||||
namespace detail {
|
|
||||||
|
|
||||||
// 将单个参数转为字符串
|
|
||||||
template <typename T> inline std::string to_string_arg(const T &value) {
|
|
||||||
if constexpr (std::is_same_v<T, std::string>) {
|
|
||||||
return value;
|
|
||||||
} else if constexpr (std::is_same_v<T, const char *> ||
|
|
||||||
std::is_same_v<T, char *>) {
|
|
||||||
return value ? std::string(value) : std::string("(null)");
|
|
||||||
} else if constexpr (std::is_same_v<T, bool>) {
|
|
||||||
return value ? "true" : "false";
|
|
||||||
} else if constexpr (std::is_arithmetic_v<T>) {
|
|
||||||
// 对浮点数使用特殊格式
|
|
||||||
if constexpr (std::is_floating_point_v<T>) {
|
|
||||||
char buf[64];
|
|
||||||
snprintf(buf, sizeof(buf), "%.2f", static_cast<double>(value));
|
|
||||||
return buf;
|
|
||||||
} else {
|
|
||||||
return std::to_string(value);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
std::ostringstream oss;
|
|
||||||
oss << value;
|
|
||||||
return oss.str();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 格式化基础情况:没有更多参数
|
|
||||||
inline std::string format_impl(const char *fmt) {
|
|
||||||
std::string result;
|
|
||||||
while (*fmt) {
|
|
||||||
if (*fmt == '{' && *(fmt + 1) == '}') {
|
|
||||||
result += "{}"; // 无参数可替换,保留原样
|
|
||||||
fmt += 2;
|
|
||||||
} else {
|
|
||||||
result += *fmt;
|
|
||||||
++fmt;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 格式化递归:替换第一个 {} 并递归处理剩余
|
|
||||||
template <typename T, typename... Args>
|
|
||||||
inline std::string format_impl(const char *fmt, const T &first,
|
|
||||||
const Args &...rest) {
|
|
||||||
std::string result;
|
|
||||||
while (*fmt) {
|
|
||||||
if (*fmt == '{') {
|
|
||||||
// 检查 {:#x} 等格式说明符
|
|
||||||
if (*(fmt + 1) == '}') {
|
|
||||||
result += to_string_arg(first);
|
|
||||||
fmt += 2;
|
|
||||||
result += format_impl(fmt, rest...);
|
|
||||||
return result;
|
|
||||||
} else if (*(fmt + 1) == ':') {
|
|
||||||
// 跳过格式说明符直到 }
|
|
||||||
const char *end = fmt + 2;
|
|
||||||
while (*end && *end != '}')
|
|
||||||
++end;
|
|
||||||
if (*end == '}') {
|
|
||||||
// 检查是否是十六进制格式
|
|
||||||
std::string spec(fmt + 2, end);
|
|
||||||
if (spec.find('x') != std::string::npos ||
|
|
||||||
spec.find('X') != std::string::npos) {
|
|
||||||
if constexpr (std::is_integral_v<T>) {
|
|
||||||
char buf[32];
|
|
||||||
snprintf(buf, sizeof(buf), "0x%x",
|
|
||||||
static_cast<unsigned int>(first));
|
|
||||||
result += buf;
|
|
||||||
} else {
|
|
||||||
result += to_string_arg(first);
|
|
||||||
}
|
|
||||||
} else if (spec.find('f') != std::string::npos ||
|
|
||||||
spec.find('.') != std::string::npos) {
|
|
||||||
if constexpr (std::is_arithmetic_v<T>) {
|
|
||||||
// 解析精度
|
|
||||||
int precision = 2;
|
|
||||||
auto dot = spec.find('.');
|
|
||||||
if (dot != std::string::npos) {
|
|
||||||
precision = 0;
|
|
||||||
for (size_t i = dot + 1;
|
|
||||||
i < spec.size() && spec[i] >= '0' && spec[i] <= '9'; ++i) {
|
|
||||||
precision = precision * 10 + (spec[i] - '0');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
char fmtbuf[16];
|
|
||||||
snprintf(fmtbuf, sizeof(fmtbuf), "%%.%df", precision);
|
|
||||||
char buf[64];
|
|
||||||
snprintf(buf, sizeof(buf), fmtbuf, static_cast<double>(first));
|
|
||||||
result += buf;
|
|
||||||
} else {
|
|
||||||
result += to_string_arg(first);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
result += to_string_arg(first);
|
|
||||||
}
|
|
||||||
fmt = end + 1;
|
|
||||||
result += format_impl(fmt, rest...);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result += *fmt;
|
|
||||||
++fmt;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace detail
|
|
||||||
|
|
||||||
// 顶层格式化函数
|
|
||||||
template <typename... Args>
|
|
||||||
inline std::string e2d_format(const char *fmt, const Args &...args) {
|
|
||||||
return detail::format_impl(fmt, args...);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 无参数版本
|
|
||||||
inline std::string e2d_format(const char *fmt) { return std::string(fmt); }
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// Logger 类 - 使用 SDL2 日志系统
|
|
||||||
// ============================================================================
|
|
||||||
class Logger {
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief 初始化日志系统
|
|
||||||
*/
|
|
||||||
static void init();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 关闭日志系统
|
|
||||||
*/
|
|
||||||
static void shutdown();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置日志级别
|
|
||||||
* @param level 日志级别
|
|
||||||
*/
|
|
||||||
static void setLevel(LogLevel level);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置是否输出到控制台
|
|
||||||
* @param enable 是否启用
|
|
||||||
*/
|
|
||||||
static void setConsoleOutput(bool enable);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置日志输出到文件
|
|
||||||
* @param filename 日志文件名
|
|
||||||
*/
|
|
||||||
static void setFileOutput(const std::string &filename);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取当前日志级别
|
|
||||||
* @return 当前日志级别
|
|
||||||
*/
|
|
||||||
static LogLevel getLevel() { return level_; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 日志记录模板函数
|
|
||||||
* @param level 日志级别
|
|
||||||
* @param fmt 格式化字符串
|
|
||||||
* @param args 可变参数
|
|
||||||
*/
|
|
||||||
template <typename... Args>
|
|
||||||
static void log(LogLevel level, const char *fmt, const Args &...args) {
|
|
||||||
if (static_cast<int>(level) < static_cast<int>(level_))
|
|
||||||
return;
|
|
||||||
std::string msg = e2d_format(fmt, args...);
|
|
||||||
SDL_LogMessage(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
static_cast<SDL_LogPriority>(level), "[%s] %s",
|
|
||||||
getLevelString(level), msg.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 日志记录无参数版本
|
|
||||||
* @param level 日志级别
|
|
||||||
* @param msg 日志消息
|
|
||||||
*/
|
|
||||||
static void log(LogLevel level, const char *msg) {
|
|
||||||
if (static_cast<int>(level) < static_cast<int>(level_))
|
|
||||||
return;
|
|
||||||
SDL_LogMessage(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
static_cast<SDL_LogPriority>(level), "[%s] %s",
|
|
||||||
getLevelString(level), msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
static LogLevel level_; // 当前日志级别
|
|
||||||
static bool initialized_; // 是否已初始化
|
|
||||||
static bool consoleOutput_; // 是否输出到控制台
|
|
||||||
static bool fileOutput_; // 是否输出到文件
|
|
||||||
static std::string logFile_; // 日志文件路径
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取日志级别字符串
|
|
||||||
* @param level 日志级别
|
|
||||||
* @return 级别字符串
|
|
||||||
*/
|
|
||||||
static const char *getLevelString(LogLevel level);
|
|
||||||
};
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 日志宏
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
#ifdef E2D_DEBUG
|
|
||||||
#define E2D_LOG_TRACE(...) \
|
|
||||||
::extra2d::Logger::log(::extra2d::LogLevel::Trace, __VA_ARGS__)
|
|
||||||
#define E2D_LOG_DEBUG(...) \
|
|
||||||
::extra2d::Logger::log(::extra2d::LogLevel::Debug, __VA_ARGS__)
|
|
||||||
#else
|
|
||||||
#define E2D_LOG_TRACE(...)
|
|
||||||
#define E2D_LOG_DEBUG(...)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define E2D_LOG_INFO(...) \
|
|
||||||
::extra2d::Logger::log(::extra2d::LogLevel::Info, __VA_ARGS__)
|
|
||||||
#define E2D_LOG_WARN(...) \
|
|
||||||
::extra2d::Logger::log(::extra2d::LogLevel::Warn, __VA_ARGS__)
|
|
||||||
#define E2D_LOG_ERROR(...) \
|
|
||||||
::extra2d::Logger::log(::extra2d::LogLevel::Error, __VA_ARGS__)
|
|
||||||
#define E2D_LOG_FATAL(...) \
|
|
||||||
::extra2d::Logger::log(::extra2d::LogLevel::Fatal, __VA_ARGS__)
|
|
||||||
|
|
||||||
// 简化的日志宏
|
|
||||||
#define E2D_INFO(...) E2D_LOG_INFO(__VA_ARGS__)
|
|
||||||
#define E2D_WARN(...) E2D_LOG_WARN(__VA_ARGS__)
|
|
||||||
#define E2D_ERROR(...) E2D_LOG_ERROR(__VA_ARGS__)
|
|
||||||
#define E2D_FATAL(...) E2D_LOG_FATAL(__VA_ARGS__)
|
|
||||||
#ifdef E2D_DEBUG
|
|
||||||
#define E2D_DEBUG_LOG(...) E2D_LOG_DEBUG(__VA_ARGS__)
|
|
||||||
#define E2D_TRACE(...) E2D_LOG_TRACE(__VA_ARGS__)
|
|
||||||
#else
|
|
||||||
#define E2D_DEBUG_LOG(...)
|
|
||||||
#define E2D_TRACE(...)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,31 +1,18 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
<<<<<<< HEAD
|
|
||||||
#include <extra2d/core/math_types.h>
|
|
||||||
#include <extra2d/core/string.h>
|
|
||||||
#include <extra2d/core/types.h>
|
#include <extra2d/core/types.h>
|
||||||
|
|
||||||
=======
|
|
||||||
#include <extra2d/core/types.h>
|
|
||||||
#include <extra2d/core/math_types.h>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <functional>
|
|
||||||
>>>>>>> 9bf328b1dca01df84a07724394abc9a238739869
|
|
||||||
|
|
||||||
#include <SDL.h>
|
#include <SDL.h>
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
class Input;
|
|
||||||
|
|
||||||
struct WindowConfig {
|
struct WindowConfig {
|
||||||
std::string title = "Extra2D Application";
|
std::string title = "Extra2D Application";
|
||||||
int width = 1280;
|
int width = 1280;
|
||||||
int height = 720;
|
int height = 720;
|
||||||
bool fullscreen = true;
|
bool fullscreen = true;
|
||||||
bool resizable = false;
|
bool resizable = false;
|
||||||
bool vsync = true;
|
|
||||||
int msaaSamples = 0;
|
|
||||||
bool centerWindow = true;
|
bool centerWindow = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -38,38 +25,25 @@ public:
|
||||||
void destroy();
|
void destroy();
|
||||||
|
|
||||||
void pollEvents();
|
void pollEvents();
|
||||||
void swapBuffers();
|
|
||||||
bool shouldClose() const;
|
bool shouldClose() const;
|
||||||
void setShouldClose(bool close);
|
void setShouldClose(bool close);
|
||||||
|
|
||||||
void setTitle(const std::string& title);
|
void setTitle(const std::string& title);
|
||||||
void setSize(int width, int height);
|
void setSize(int width, int height);
|
||||||
void setFullscreen(bool fullscreen);
|
void setFullscreen(bool fullscreen);
|
||||||
void setVSync(bool enabled);
|
|
||||||
|
|
||||||
int getWidth() const { return width_; }
|
int getWidth() const { return width_; }
|
||||||
int getHeight() const { return height_; }
|
int getHeight() const { return height_; }
|
||||||
bool isFullscreen() const { return fullscreen_; }
|
bool isFullscreen() const { return fullscreen_; }
|
||||||
bool isVSync() const { return vsync_; }
|
|
||||||
|
|
||||||
SDL_Window *getSDLWindow() const { return sdlWindow_; }
|
SDL_Window* sdlWindow() const { return sdlWindow_; }
|
||||||
SDL_GLContext getGLContext() const { return glContext_; }
|
|
||||||
|
|
||||||
Input *getInput() const { return input_.get(); }
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
SDL_Window *sdlWindow_;
|
SDL_Window* sdlWindow_ = nullptr;
|
||||||
SDL_GLContext glContext_;
|
int width_ = 1280;
|
||||||
|
int height_ = 720;
|
||||||
int width_;
|
bool fullscreen_ = true;
|
||||||
int height_;
|
bool shouldClose_ = false;
|
||||||
bool vsync_;
|
|
||||||
bool shouldClose_;
|
|
||||||
bool fullscreen_;
|
|
||||||
UniquePtr<Input> input_;
|
|
||||||
|
|
||||||
bool initSDL(const WindowConfig &config);
|
|
||||||
void deinitSDL();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace extra2d
|
} // namespace extra2d
|
||||||
|
|
|
||||||
|
|
@ -1,68 +1,49 @@
|
||||||
#include <extra2d/app/application.h>
|
#include <extra2d/app/application.h>
|
||||||
<<<<<<< HEAD
|
#include <extra2d/render/render_device.h>
|
||||||
#include <extra2d/audio/audio_engine.h>
|
#include <extra2d/services/logger_service.h>
|
||||||
#include <extra2d/event/event_dispatcher.h>
|
|
||||||
#include <extra2d/event/event_queue.h>
|
|
||||||
#include <extra2d/graphics/renderer.h>
|
|
||||||
#include <extra2d/utils/logger.h>
|
|
||||||
#include <extra2d/window/window.h>
|
#include <extra2d/window/window.h>
|
||||||
|
|
||||||
#include <chrono>
|
#include <SDL.h>
|
||||||
#include <thread>
|
|
||||||
=======
|
|
||||||
#include <extra2d/core/registry.h>
|
|
||||||
#include <extra2d/platform/glfw/glfw_window.h>
|
|
||||||
#include <extra2d/platform/window_module.h>
|
|
||||||
#include <extra2d/services/event_service.h>
|
|
||||||
#include <extra2d/services/logger_service.h>
|
|
||||||
#include <extra2d/services/timer_service.h>
|
|
||||||
>>>>>>> 9bf328b1dca01df84a07724394abc9a238739869
|
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
static f64 getTimeSeconds() {
|
Application &Application::instance() {
|
||||||
#ifdef __SWITCH__
|
static Application app;
|
||||||
struct timespec ts;
|
return app;
|
||||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
|
||||||
return static_cast<f64>(ts.tv_sec) +
|
|
||||||
static_cast<f64>(ts.tv_nsec) / 1000000000.0;
|
|
||||||
#else
|
|
||||||
using namespace std::chrono;
|
|
||||||
auto now = steady_clock::now();
|
|
||||||
auto duration = now.time_since_epoch();
|
|
||||||
return duration_cast<std::chrono::duration<f64>>(duration).count();
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Application &Application::get() {
|
Application::~Application() { shutdown(); }
|
||||||
static Application instance;
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
Application::Application() { Registry::instance().setApp(this); }
|
|
||||||
|
|
||||||
Application::~Application() {
|
|
||||||
if (initialized_) {
|
|
||||||
shutdown();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Application::init() {
|
bool Application::init() {
|
||||||
if (initialized_) {
|
if (initialized_)
|
||||||
return true;
|
return true;
|
||||||
}
|
|
||||||
|
|
||||||
// 初始化所有模块(拓扑排序)
|
E2D_INFO("Application initializing: {}", name);
|
||||||
// 服务通过 E2D_AUTO_REGISTER_SERVICE 宏自动注册
|
|
||||||
if (!Registry::instance().init()) {
|
ServiceLocator::instance().init();
|
||||||
|
|
||||||
|
window_ = new Window();
|
||||||
|
if (!window_->create({.title = name, .width = 1280, .height = 720})) {
|
||||||
|
E2D_ERROR("Failed to create window");
|
||||||
|
delete window_;
|
||||||
|
window_ = nullptr;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 初始化所有服务
|
auto &device = RenderDevice::instance();
|
||||||
ServiceLocator::instance().init();
|
if (!device.init(window_->sdlWindow())) {
|
||||||
|
E2D_ERROR("Failed to initialize render device");
|
||||||
|
window_->destroy();
|
||||||
|
delete window_;
|
||||||
|
window_ = nullptr;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
initialized_ = true;
|
initialized_ = true;
|
||||||
running_ = true;
|
running_ = true;
|
||||||
|
lastFrameTime_ = SDL_GetPerformanceCounter();
|
||||||
|
|
||||||
|
E2D_INFO("Application initialized successfully");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -70,10 +51,17 @@ void Application::shutdown() {
|
||||||
if (!initialized_)
|
if (!initialized_)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
E2D_INFO("Application shutting down");
|
||||||
|
|
||||||
|
RenderDevice::instance().shutdown();
|
||||||
|
|
||||||
|
if (window_) {
|
||||||
|
window_->destroy();
|
||||||
|
delete window_;
|
||||||
|
window_ = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
ServiceLocator::instance().shutdown();
|
ServiceLocator::instance().shutdown();
|
||||||
ServiceLocator::instance().clear();
|
|
||||||
Registry::instance().shutdown();
|
|
||||||
Registry::instance().clear();
|
|
||||||
|
|
||||||
initialized_ = false;
|
initialized_ = false;
|
||||||
running_ = false;
|
running_ = false;
|
||||||
|
|
@ -83,95 +71,33 @@ void Application::run() {
|
||||||
if (!initialized_)
|
if (!initialized_)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
auto *winMod = get<WindowModule>();
|
while (running_ && !window_->shouldClose()) {
|
||||||
if (!winMod || !winMod->win())
|
|
||||||
return;
|
|
||||||
|
|
||||||
lastFrameTime_ = getTimeSeconds();
|
|
||||||
|
|
||||||
while (running_ && !winMod->win()->shouldClose()) {
|
|
||||||
mainLoop();
|
mainLoop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
<<<<<<< HEAD
|
|
||||||
void Application::quit() { running_ = false; }
|
void Application::quit() { running_ = false; }
|
||||||
=======
|
|
||||||
void Application::quit() {
|
|
||||||
shouldQuit_ = true;
|
|
||||||
running_ = false;
|
|
||||||
}
|
|
||||||
>>>>>>> 9bf328b1dca01df84a07724394abc9a238739869
|
|
||||||
|
|
||||||
void Application::pause() {
|
|
||||||
if (!paused_) {
|
|
||||||
paused_ = true;
|
|
||||||
ServiceLocator::instance().pause();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Application::resume() {
|
|
||||||
if (paused_) {
|
|
||||||
paused_ = false;
|
|
||||||
ServiceLocator::instance().resume();
|
|
||||||
lastFrameTime_ = getTimeSeconds();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Application::mainLoop() {
|
void Application::mainLoop() {
|
||||||
f64 currentTime = getTimeSeconds();
|
u64 currentTime = SDL_GetPerformanceCounter();
|
||||||
dt_ = static_cast<f32>(currentTime - lastFrameTime_);
|
u64 frequency = SDL_GetPerformanceFrequency();
|
||||||
|
dt_ = static_cast<f32>(currentTime - lastFrameTime_) /
|
||||||
|
static_cast<f32>(frequency);
|
||||||
lastFrameTime_ = currentTime;
|
lastFrameTime_ = currentTime;
|
||||||
|
|
||||||
totalTime_ += dt_;
|
totalTime_ += dt_;
|
||||||
|
|
||||||
frameCount_++;
|
frameCount_++;
|
||||||
fpsTimer_ += dt_;
|
fpsTimer_ += dt_;
|
||||||
|
|
||||||
if (fpsTimer_ >= 1.0f) {
|
if (fpsTimer_ >= 1.0f) {
|
||||||
fps_ = frameCount_;
|
fps_ = frameCount_;
|
||||||
frameCount_ = 0;
|
frameCount_ = 0;
|
||||||
fpsTimer_ -= 1.0f;
|
fpsTimer_ -= 1.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto *winMod = get<WindowModule>();
|
window_->pollEvents();
|
||||||
if (winMod && winMod->win()) {
|
ServiceLocator::instance().update(dt_);
|
||||||
winMod->win()->poll();
|
RenderDevice::instance().swapBuffers();
|
||||||
}
|
|
||||||
|
|
||||||
auto eventService = ServiceLocator::instance().get<IEventService>();
|
|
||||||
if (eventService) {
|
|
||||||
eventService->process();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!paused_) {
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
render();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Application::update() { ServiceLocator::instance().update(dt_); }
|
|
||||||
|
|
||||||
void Application::render() {
|
|
||||||
auto *winMod = get<WindowModule>();
|
|
||||||
if (!winMod || !winMod->win())
|
|
||||||
return;
|
|
||||||
|
|
||||||
<<<<<<< HEAD
|
|
||||||
renderer_->beginFrame(Colors::Black);
|
|
||||||
|
|
||||||
// 渲染内容可以在这里添加
|
|
||||||
|
|
||||||
renderer_->endFrame();
|
|
||||||
window_->swapBuffers();
|
|
||||||
=======
|
|
||||||
winMod->win()->swap();
|
|
||||||
>>>>>>> 9bf328b1dca01df84a07724394abc9a238739869
|
|
||||||
}
|
|
||||||
|
|
||||||
GLFWWindow *Application::window() {
|
|
||||||
auto *winMod = get<WindowModule>();
|
|
||||||
return winMod ? winMod->win() : nullptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace extra2d
|
} // namespace extra2d
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
#include "extra2d/audio/audio_engine.h"
|
#include "extra2d/audio/audio_engine.h"
|
||||||
#include "extra2d/audio/sound.h"
|
#include "extra2d/audio/sound.h"
|
||||||
#include "extra2d/utils/logger.h"
|
#include <extra2d/services/logger_service.h>
|
||||||
|
|
||||||
#include <SDL2/SDL.h>
|
#include <SDL2/SDL.h>
|
||||||
#include <SDL2/SDL_mixer.h>
|
#include <SDL2/SDL_mixer.h>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
#include <SDL2/SDL_mixer.h>
|
#include <SDL2/SDL_mixer.h>
|
||||||
#include <extra2d/audio/sound.h>
|
#include <extra2d/audio/sound.h>
|
||||||
#include <extra2d/utils/logger.h>
|
#include <extra2d/services/logger_service.h>
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,285 +0,0 @@
|
||||||
#include <extra2d/graphics/opengl/gl_font_atlas.h>
|
|
||||||
#include <extra2d/utils/logger.h>
|
|
||||||
#include <simdutf/simdutf.h>
|
|
||||||
#include <fstream>
|
|
||||||
#define STB_TRUETYPE_IMPLEMENTATION
|
|
||||||
#include <stb/stb_truetype.h>
|
|
||||||
#define STB_RECT_PACK_IMPLEMENTATION
|
|
||||||
#include <stb/stb_rect_pack.h>
|
|
||||||
#include <algorithm>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 构造函数 - 初始化字体图集
|
|
||||||
// ============================================================================
|
|
||||||
GLFontAtlas::GLFontAtlas(const std::string &filepath, int fontSize, bool useSDF)
|
|
||||||
: fontSize_(fontSize), useSDF_(useSDF), currentY_(0), scale_(0.0f),
|
|
||||||
ascent_(0.0f), descent_(0.0f), lineGap_(0.0f) {
|
|
||||||
|
|
||||||
// 加载字体文件
|
|
||||||
std::ifstream file(filepath, std::ios::binary | std::ios::ate);
|
|
||||||
if (!file.is_open()) {
|
|
||||||
E2D_LOG_ERROR("Failed to load font: {}", filepath);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::streamsize size = file.tellg();
|
|
||||||
file.seekg(0, std::ios::beg);
|
|
||||||
fontData_.resize(size);
|
|
||||||
if (!file.read(reinterpret_cast<char *>(fontData_.data()), size)) {
|
|
||||||
E2D_LOG_ERROR("Failed to read font file: {}", filepath);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 初始化 stb_truetype
|
|
||||||
if (!stbtt_InitFont(&fontInfo_, fontData_.data(),
|
|
||||||
stbtt_GetFontOffsetForIndex(fontData_.data(), 0))) {
|
|
||||||
E2D_LOG_ERROR("Failed to init font: {}", filepath);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
scale_ = stbtt_ScaleForPixelHeight(&fontInfo_, static_cast<float>(fontSize_));
|
|
||||||
|
|
||||||
int ascent, descent, lineGap;
|
|
||||||
stbtt_GetFontVMetrics(&fontInfo_, &ascent, &descent, &lineGap);
|
|
||||||
ascent_ = static_cast<float>(ascent) * scale_;
|
|
||||||
descent_ = static_cast<float>(descent) * scale_;
|
|
||||||
lineGap_ = static_cast<float>(lineGap) * scale_;
|
|
||||||
|
|
||||||
createAtlas();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 析构函数
|
|
||||||
// ============================================================================
|
|
||||||
GLFontAtlas::~GLFontAtlas() = default;
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 获取字形 - 如果字形不存在则缓存它
|
|
||||||
// ============================================================================
|
|
||||||
const Glyph *GLFontAtlas::getGlyph(char32_t codepoint) const {
|
|
||||||
auto it = glyphs_.find(codepoint);
|
|
||||||
if (it == glyphs_.end()) {
|
|
||||||
cacheGlyph(codepoint);
|
|
||||||
it = glyphs_.find(codepoint);
|
|
||||||
}
|
|
||||||
return (it != glyphs_.end()) ? &it->second : nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 测量文本尺寸
|
|
||||||
// ============================================================================
|
|
||||||
Vec2 GLFontAtlas::measureText(const std::string &text) {
|
|
||||||
float width = 0.0f;
|
|
||||||
float height = getAscent() - getDescent();
|
|
||||||
float currentWidth = 0.0f;
|
|
||||||
|
|
||||||
std::u32string utf32_text;
|
|
||||||
utf32_text.resize(simdutf::utf32_length_from_utf8(text.data(), text.size()));
|
|
||||||
simdutf::convert_utf8_to_utf32(text.data(), text.size(), utf32_text.data());
|
|
||||||
for (char32_t codepoint : utf32_text) {
|
|
||||||
if (codepoint == '\n') {
|
|
||||||
width = std::max(width, currentWidth);
|
|
||||||
currentWidth = 0.0f;
|
|
||||||
height += getLineHeight();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Glyph *glyph = getGlyph(codepoint);
|
|
||||||
if (glyph) {
|
|
||||||
currentWidth += glyph->advance;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
width = std::max(width, currentWidth);
|
|
||||||
return Vec2(width, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 创建图集纹理 - 初始化空白纹理和矩形打包上下文
|
|
||||||
// ============================================================================
|
|
||||||
void GLFontAtlas::createAtlas() {
|
|
||||||
// 统一使用 4 通道格式
|
|
||||||
int channels = 4;
|
|
||||||
std::vector<uint8_t> emptyData(ATLAS_WIDTH * ATLAS_HEIGHT * channels, 0);
|
|
||||||
texture_ = std::make_unique<GLTexture>(ATLAS_WIDTH, ATLAS_HEIGHT,
|
|
||||||
emptyData.data(), channels);
|
|
||||||
texture_->setFilter(true);
|
|
||||||
|
|
||||||
// 初始化矩形打包上下文
|
|
||||||
packNodes_.resize(ATLAS_WIDTH);
|
|
||||||
stbrp_init_target(&packContext_, ATLAS_WIDTH, ATLAS_HEIGHT, packNodes_.data(),
|
|
||||||
ATLAS_WIDTH);
|
|
||||||
|
|
||||||
// 预分配字形缓冲区
|
|
||||||
// 假设最大字形尺寸为 fontSize * fontSize * 4 (RGBA)
|
|
||||||
size_t maxGlyphSize = static_cast<size_t>(fontSize_ * fontSize_ * 4 * 4);
|
|
||||||
glyphBitmapCache_.reserve(maxGlyphSize);
|
|
||||||
glyphRgbaCache_.reserve(maxGlyphSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 缓存字形 - 渲染字形到图集并存储信息
|
|
||||||
// 使用 stb_rect_pack 进行矩形打包
|
|
||||||
// ============================================================================
|
|
||||||
void GLFontAtlas::cacheGlyph(char32_t codepoint) const {
|
|
||||||
int advance = 0;
|
|
||||||
stbtt_GetCodepointHMetrics(&fontInfo_, static_cast<int>(codepoint), &advance,
|
|
||||||
nullptr);
|
|
||||||
float advancePx = advance * scale_;
|
|
||||||
|
|
||||||
if (useSDF_) {
|
|
||||||
constexpr int SDF_PADDING = 8;
|
|
||||||
constexpr unsigned char ONEDGE_VALUE = 128;
|
|
||||||
constexpr float PIXEL_DIST_SCALE = 64.0f;
|
|
||||||
|
|
||||||
int w = 0, h = 0, xoff = 0, yoff = 0;
|
|
||||||
unsigned char *sdf = stbtt_GetCodepointSDF(
|
|
||||||
&fontInfo_, scale_, static_cast<int>(codepoint), SDF_PADDING,
|
|
||||||
ONEDGE_VALUE, PIXEL_DIST_SCALE, &w, &h, &xoff, &yoff);
|
|
||||||
if (!sdf || w <= 0 || h <= 0) {
|
|
||||||
if (sdf)
|
|
||||||
stbtt_FreeSDF(sdf, nullptr);
|
|
||||||
Glyph glyph{};
|
|
||||||
glyph.advance = advancePx;
|
|
||||||
glyphs_[codepoint] = glyph;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
stbrp_rect rect;
|
|
||||||
rect.id = static_cast<int>(codepoint);
|
|
||||||
rect.w = w + PADDING * 2;
|
|
||||||
rect.h = h + PADDING * 2;
|
|
||||||
|
|
||||||
stbrp_pack_rects(&packContext_, &rect, 1);
|
|
||||||
if (!rect.was_packed) {
|
|
||||||
E2D_LOG_WARN("Font atlas is full, cannot cache codepoint: {}",
|
|
||||||
static_cast<int>(codepoint));
|
|
||||||
stbtt_FreeSDF(sdf, nullptr);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int atlasX = rect.x + PADDING;
|
|
||||||
int atlasY = rect.y + PADDING;
|
|
||||||
|
|
||||||
Glyph glyph;
|
|
||||||
glyph.width = static_cast<float>(w);
|
|
||||||
glyph.height = static_cast<float>(h);
|
|
||||||
glyph.bearingX = static_cast<float>(xoff);
|
|
||||||
glyph.bearingY = static_cast<float>(yoff);
|
|
||||||
glyph.advance = advancePx;
|
|
||||||
|
|
||||||
// stb_rect_pack 使用左上角为原点,OpenGL纹理使用左下角为原点
|
|
||||||
// 需要翻转V坐标
|
|
||||||
float v0 = static_cast<float>(atlasY) / ATLAS_HEIGHT;
|
|
||||||
float v1 = static_cast<float>(atlasY + h) / ATLAS_HEIGHT;
|
|
||||||
glyph.u0 = static_cast<float>(atlasX) / ATLAS_WIDTH;
|
|
||||||
glyph.v0 = 1.0f - v1; // 翻转V坐标
|
|
||||||
glyph.u1 = static_cast<float>(atlasX + w) / ATLAS_WIDTH;
|
|
||||||
glyph.v1 = 1.0f - v0; // 翻转V坐标
|
|
||||||
|
|
||||||
glyphs_[codepoint] = glyph;
|
|
||||||
|
|
||||||
// 将 SDF 单通道数据转换为 RGBA 格式(统一格式)
|
|
||||||
size_t pixelCount = static_cast<size_t>(w) * static_cast<size_t>(h);
|
|
||||||
glyphRgbaCache_.resize(pixelCount * 4);
|
|
||||||
for (size_t i = 0; i < pixelCount; ++i) {
|
|
||||||
uint8_t alpha = sdf[i];
|
|
||||||
glyphRgbaCache_[i * 4 + 0] = 255; // R
|
|
||||||
glyphRgbaCache_[i * 4 + 1] = 255; // G
|
|
||||||
glyphRgbaCache_[i * 4 + 2] = 255; // B
|
|
||||||
glyphRgbaCache_[i * 4 + 3] = alpha; // A - SDF 值存储在 Alpha 通道
|
|
||||||
}
|
|
||||||
|
|
||||||
// 直接设置像素对齐为 4,无需查询当前状态
|
|
||||||
glBindTexture(GL_TEXTURE_2D, texture_->getTextureID());
|
|
||||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
|
|
||||||
// OpenGL纹理坐标原点在左下角,需要将Y坐标翻转
|
|
||||||
glTexSubImage2D(GL_TEXTURE_2D, 0, atlasX, ATLAS_HEIGHT - atlasY - h, w, h,
|
|
||||||
GL_RGBA, GL_UNSIGNED_BYTE, glyphRgbaCache_.data());
|
|
||||||
|
|
||||||
stbtt_FreeSDF(sdf, nullptr);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int x0 = 0, y0 = 0, x1 = 0, y1 = 0;
|
|
||||||
stbtt_GetCodepointBitmapBox(&fontInfo_, static_cast<int>(codepoint), scale_,
|
|
||||||
scale_, &x0, &y0, &x1, &y1);
|
|
||||||
int w = x1 - x0;
|
|
||||||
int h = y1 - y0;
|
|
||||||
int xoff = x0;
|
|
||||||
int yoff = y0;
|
|
||||||
|
|
||||||
if (w <= 0 || h <= 0) {
|
|
||||||
Glyph glyph{};
|
|
||||||
glyph.advance = advancePx;
|
|
||||||
glyphs_[codepoint] = glyph;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 使用预分配缓冲区
|
|
||||||
size_t pixelCount = static_cast<size_t>(w) * static_cast<size_t>(h);
|
|
||||||
glyphBitmapCache_.resize(pixelCount);
|
|
||||||
stbtt_MakeCodepointBitmap(&fontInfo_, glyphBitmapCache_.data(), w, h, w, scale_, scale_,
|
|
||||||
static_cast<int>(codepoint));
|
|
||||||
|
|
||||||
// 使用 stb_rect_pack 打包矩形
|
|
||||||
stbrp_rect rect;
|
|
||||||
rect.id = static_cast<int>(codepoint);
|
|
||||||
rect.w = w + PADDING * 2;
|
|
||||||
rect.h = h + PADDING * 2;
|
|
||||||
|
|
||||||
stbrp_pack_rects(&packContext_, &rect, 1);
|
|
||||||
|
|
||||||
if (!rect.was_packed) {
|
|
||||||
// 图集已满,无法缓存更多字形
|
|
||||||
E2D_LOG_WARN("Font atlas is full, cannot cache codepoint: {}",
|
|
||||||
static_cast<int>(codepoint));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int atlasX = rect.x + PADDING;
|
|
||||||
int atlasY = rect.y + PADDING;
|
|
||||||
|
|
||||||
// 创建字形信息
|
|
||||||
Glyph glyph;
|
|
||||||
glyph.width = static_cast<float>(w);
|
|
||||||
glyph.height = static_cast<float>(h);
|
|
||||||
glyph.bearingX = static_cast<float>(xoff);
|
|
||||||
glyph.bearingY = static_cast<float>(yoff);
|
|
||||||
glyph.advance = advancePx;
|
|
||||||
|
|
||||||
// 计算纹理坐标(相对于图集)
|
|
||||||
// stb_rect_pack 使用左上角为原点,OpenGL纹理使用左下角为原点
|
|
||||||
// 需要翻转V坐标
|
|
||||||
float v0 = static_cast<float>(atlasY) / ATLAS_HEIGHT;
|
|
||||||
float v1 = static_cast<float>(atlasY + h) / ATLAS_HEIGHT;
|
|
||||||
glyph.u0 = static_cast<float>(atlasX) / ATLAS_WIDTH;
|
|
||||||
glyph.v0 = 1.0f - v1; // 翻转V坐标
|
|
||||||
glyph.u1 = static_cast<float>(atlasX + w) / ATLAS_WIDTH;
|
|
||||||
glyph.v1 = 1.0f - v0; // 翻转V坐标
|
|
||||||
|
|
||||||
// 存储字形
|
|
||||||
glyphs_[codepoint] = glyph;
|
|
||||||
|
|
||||||
// 将单通道字形数据转换为 RGBA 格式(白色字形,Alpha 通道存储灰度)
|
|
||||||
glyphRgbaCache_.resize(pixelCount * 4);
|
|
||||||
for (size_t i = 0; i < pixelCount; ++i) {
|
|
||||||
uint8_t alpha = glyphBitmapCache_[i];
|
|
||||||
glyphRgbaCache_[i * 4 + 0] = 255; // R
|
|
||||||
glyphRgbaCache_[i * 4 + 1] = 255; // G
|
|
||||||
glyphRgbaCache_[i * 4 + 2] = 255; // B
|
|
||||||
glyphRgbaCache_[i * 4 + 3] = alpha; // A
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新纹理 - 将字形数据上传到图集的指定位置
|
|
||||||
// 直接设置像素对齐为 4,无需查询当前状态
|
|
||||||
glBindTexture(GL_TEXTURE_2D, texture_->getTextureID());
|
|
||||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
|
|
||||||
// OpenGL纹理坐标原点在左下角,需要将Y坐标翻转
|
|
||||||
glTexSubImage2D(GL_TEXTURE_2D, 0, atlasX, ATLAS_HEIGHT - atlasY - h, w, h,
|
|
||||||
GL_RGBA, GL_UNSIGNED_BYTE, glyphRgbaCache_.data());
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,629 +0,0 @@
|
||||||
#include <SDL.h>
|
|
||||||
#include <algorithm>
|
|
||||||
#include <cmath>
|
|
||||||
#include <cstring>
|
|
||||||
#include <simdutf/simdutf.h>
|
|
||||||
#include <extra2d/graphics/gpu_context.h>
|
|
||||||
#include <extra2d/graphics/opengl/gl_font_atlas.h>
|
|
||||||
#include <extra2d/graphics/opengl/gl_renderer.h>
|
|
||||||
#include <extra2d/graphics/opengl/gl_texture.h>
|
|
||||||
#include <extra2d/graphics/vram_manager.h>
|
|
||||||
#include <extra2d/platform/window.h>
|
|
||||||
#include <extra2d/utils/logger.h>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// 形状渲染着色器 - 支持顶点颜色批处理 (GLES 3.2)
|
|
||||||
static const char *SHAPE_VERTEX_SHADER = R"(
|
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
layout(location = 0) in vec2 aPosition;
|
|
||||||
layout(location = 1) in vec4 aColor;
|
|
||||||
uniform mat4 uViewProjection;
|
|
||||||
out vec4 vColor;
|
|
||||||
void main() {
|
|
||||||
gl_Position = uViewProjection * vec4(aPosition, 0.0, 1.0);
|
|
||||||
vColor = aColor;
|
|
||||||
}
|
|
||||||
)";
|
|
||||||
|
|
||||||
static const char *SHAPE_FRAGMENT_SHADER = R"(
|
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
in vec4 vColor;
|
|
||||||
out vec4 fragColor;
|
|
||||||
void main() {
|
|
||||||
fragColor = vColor;
|
|
||||||
}
|
|
||||||
)";
|
|
||||||
|
|
||||||
// VBO 初始大小(用于 VRAM 跟踪)
|
|
||||||
static constexpr size_t SHAPE_VBO_SIZE = 1024 * sizeof(float);
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// BlendMode 查找表 - 编译期构建,运行时 O(1) 查找
|
|
||||||
// ============================================================================
|
|
||||||
struct BlendState {
|
|
||||||
bool enable;
|
|
||||||
GLenum srcFactor;
|
|
||||||
GLenum dstFactor;
|
|
||||||
};
|
|
||||||
|
|
||||||
static constexpr BlendState BLEND_STATES[] = {
|
|
||||||
{false, 0, 0}, // BlendMode::None
|
|
||||||
{true, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA}, // BlendMode::Alpha
|
|
||||||
{true, GL_SRC_ALPHA, GL_ONE}, // BlendMode::Additive
|
|
||||||
{true, GL_DST_COLOR, GL_ONE_MINUS_SRC_ALPHA} // BlendMode::Multiply
|
|
||||||
};
|
|
||||||
|
|
||||||
static constexpr size_t BLEND_STATE_COUNT =
|
|
||||||
sizeof(BLEND_STATES) / sizeof(BLEND_STATES[0]);
|
|
||||||
|
|
||||||
GLRenderer::GLRenderer()
|
|
||||||
: window_(nullptr), shapeVao_(0), shapeVbo_(0), lineVao_(0), lineVbo_(0),
|
|
||||||
vsync_(true), shapeVertexCount_(0), currentShapeMode_(GL_TRIANGLES),
|
|
||||||
lineVertexCount_(0), currentLineWidth_(1.0f) {
|
|
||||||
resetStats();
|
|
||||||
for (auto &v : shapeVertexCache_) {
|
|
||||||
v = {0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f};
|
|
||||||
}
|
|
||||||
for (auto &v : lineVertexCache_) {
|
|
||||||
v = {0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
GLRenderer::~GLRenderer() { shutdown(); }
|
|
||||||
|
|
||||||
bool GLRenderer::init(Window *window) {
|
|
||||||
window_ = window;
|
|
||||||
|
|
||||||
// Switch: GL 上下文已通过 SDL2 + EGL 初始化,无需 glewInit()
|
|
||||||
|
|
||||||
// 初始化精灵批渲染器
|
|
||||||
if (!spriteBatch_.init()) {
|
|
||||||
E2D_LOG_ERROR("Failed to initialize sprite batch");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 初始化形状渲染
|
|
||||||
initShapeRendering();
|
|
||||||
|
|
||||||
// 设置 OpenGL 状态
|
|
||||||
glEnable(GL_BLEND);
|
|
||||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
|
||||||
|
|
||||||
// 标记 GPU 上下文为有效
|
|
||||||
GPUContext::getInstance().markValid();
|
|
||||||
|
|
||||||
E2D_LOG_INFO("OpenGL Renderer initialized");
|
|
||||||
E2D_LOG_INFO("OpenGL Version: {}",
|
|
||||||
reinterpret_cast<const char *>(glGetString(GL_VERSION)));
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLRenderer::shutdown() {
|
|
||||||
// 标记 GPU 上下文为无效
|
|
||||||
// 这会在销毁 OpenGL 上下文之前通知所有 GPU 资源
|
|
||||||
GPUContext::getInstance().markInvalid();
|
|
||||||
|
|
||||||
spriteBatch_.shutdown();
|
|
||||||
|
|
||||||
if (lineVbo_ != 0) {
|
|
||||||
glDeleteBuffers(1, &lineVbo_);
|
|
||||||
VRAMManager::getInstance().freeBuffer(MAX_LINE_VERTICES *
|
|
||||||
sizeof(ShapeVertex));
|
|
||||||
lineVbo_ = 0;
|
|
||||||
}
|
|
||||||
if (lineVao_ != 0) {
|
|
||||||
glDeleteVertexArrays(1, &lineVao_);
|
|
||||||
lineVao_ = 0;
|
|
||||||
}
|
|
||||||
if (shapeVbo_ != 0) {
|
|
||||||
glDeleteBuffers(1, &shapeVbo_);
|
|
||||||
VRAMManager::getInstance().freeBuffer(MAX_SHAPE_VERTICES *
|
|
||||||
sizeof(ShapeVertex));
|
|
||||||
shapeVbo_ = 0;
|
|
||||||
}
|
|
||||||
if (shapeVao_ != 0) {
|
|
||||||
glDeleteVertexArrays(1, &shapeVao_);
|
|
||||||
shapeVao_ = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLRenderer::beginFrame(const Color &clearColor) {
|
|
||||||
glClearColor(clearColor.r, clearColor.g, clearColor.b, clearColor.a);
|
|
||||||
glClear(GL_COLOR_BUFFER_BIT);
|
|
||||||
resetStats();
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLRenderer::endFrame() {
|
|
||||||
// 刷新所有待处理的形状批次
|
|
||||||
flushShapeBatch();
|
|
||||||
// 刷新所有待处理的线条批次
|
|
||||||
flushLineBatch();
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLRenderer::setViewport(int x, int y, int width, int height) {
|
|
||||||
glViewport(x, y, width, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLRenderer::setVSync(bool enabled) {
|
|
||||||
vsync_ = enabled;
|
|
||||||
// 使用 SDL2 设置交换间隔
|
|
||||||
SDL_GL_SetSwapInterval(enabled ? 1 : 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLRenderer::setBlendMode(BlendMode mode) {
|
|
||||||
// 状态缓存检查,避免冗余 GL 调用
|
|
||||||
if (cachedBlendMode_ == mode) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
cachedBlendMode_ = mode;
|
|
||||||
|
|
||||||
// 使用查找表替代 switch
|
|
||||||
size_t index = static_cast<size_t>(mode);
|
|
||||||
if (index >= BLEND_STATE_COUNT) {
|
|
||||||
index = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
const BlendState &state = BLEND_STATES[index];
|
|
||||||
if (state.enable) {
|
|
||||||
if (!blendEnabled_) {
|
|
||||||
glEnable(GL_BLEND);
|
|
||||||
blendEnabled_ = true;
|
|
||||||
}
|
|
||||||
glBlendFunc(state.srcFactor, state.dstFactor);
|
|
||||||
} else {
|
|
||||||
if (blendEnabled_) {
|
|
||||||
glDisable(GL_BLEND);
|
|
||||||
blendEnabled_ = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLRenderer::setViewProjection(const glm::mat4 &matrix) {
|
|
||||||
viewProjection_ = matrix;
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLRenderer::pushTransform(const glm::mat4 &transform) {
|
|
||||||
if (transformStack_.empty()) {
|
|
||||||
transformStack_.push_back(transform);
|
|
||||||
} else {
|
|
||||||
transformStack_.push_back(transformStack_.back() * transform);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLRenderer::popTransform() {
|
|
||||||
if (!transformStack_.empty()) {
|
|
||||||
transformStack_.pop_back();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
glm::mat4 GLRenderer::getCurrentTransform() const {
|
|
||||||
if (transformStack_.empty()) {
|
|
||||||
return glm::mat4(1.0f);
|
|
||||||
}
|
|
||||||
return transformStack_.back();
|
|
||||||
}
|
|
||||||
|
|
||||||
Ptr<Texture> GLRenderer::createTexture(int width, int height,
|
|
||||||
const uint8_t *pixels, int channels) {
|
|
||||||
return makePtr<GLTexture>(width, height, pixels, channels);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ptr<Texture> GLRenderer::loadTexture(const std::string &filepath) {
|
|
||||||
return makePtr<GLTexture>(filepath);
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLRenderer::beginSpriteBatch() { spriteBatch_.begin(viewProjection_); }
|
|
||||||
|
|
||||||
void GLRenderer::drawSprite(const Texture &texture, const Rect &destRect,
|
|
||||||
const Rect &srcRect, const Color &tint,
|
|
||||||
float rotation, const Vec2 &anchor) {
|
|
||||||
GLSpriteBatch::SpriteData data;
|
|
||||||
data.position = glm::vec2(destRect.origin.x, destRect.origin.y);
|
|
||||||
data.size = glm::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.texCoordMin = glm::vec2(glm::min(u1, u2), glm::min(v1, v2));
|
|
||||||
data.texCoordMax = glm::vec2(glm::max(u1, u2), glm::max(v1, v2));
|
|
||||||
|
|
||||||
data.color = glm::vec4(tint.r, tint.g, tint.b, tint.a);
|
|
||||||
data.rotation = rotation * 3.14159f / 180.0f;
|
|
||||||
data.anchor = glm::vec2(anchor.x, anchor.y);
|
|
||||||
data.isSDF = false;
|
|
||||||
|
|
||||||
spriteBatch_.draw(texture, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLRenderer::drawSprite(const Texture &texture, const Vec2 &position,
|
|
||||||
const Color &tint) {
|
|
||||||
Rect destRect(position.x, position.y, static_cast<float>(texture.getWidth()),
|
|
||||||
static_cast<float>(texture.getHeight()));
|
|
||||||
Rect srcRect(0, 0, static_cast<float>(texture.getWidth()),
|
|
||||||
static_cast<float>(texture.getHeight()));
|
|
||||||
drawSprite(texture, destRect, srcRect, tint, 0.0f, Vec2(0, 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLRenderer::endSpriteBatch() {
|
|
||||||
spriteBatch_.end();
|
|
||||||
stats_.drawCalls += spriteBatch_.getDrawCallCount();
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLRenderer::drawLine(const Vec2 &start, const Vec2 &end,
|
|
||||||
const Color &color, float width) {
|
|
||||||
// 如果线宽改变,需要先刷新线条批次
|
|
||||||
if (width != currentLineWidth_) {
|
|
||||||
flushLineBatch();
|
|
||||||
currentLineWidth_ = width;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 添加两个顶点到线条缓冲区
|
|
||||||
addLineVertex(start.x, start.y, color);
|
|
||||||
addLineVertex(end.x, end.y, color);
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLRenderer::drawRect(const Rect &rect, const Color &color, float width) {
|
|
||||||
// 如果线宽改变,需要先刷新线条批次
|
|
||||||
if (width != currentLineWidth_) {
|
|
||||||
flushLineBatch();
|
|
||||||
currentLineWidth_ = width;
|
|
||||||
}
|
|
||||||
|
|
||||||
float x1 = rect.origin.x;
|
|
||||||
float y1 = rect.origin.y;
|
|
||||||
float x2 = rect.origin.x + rect.size.width;
|
|
||||||
float y2 = rect.origin.y + rect.size.height;
|
|
||||||
|
|
||||||
// 4条线段 = 8个顶点
|
|
||||||
// 上边
|
|
||||||
addLineVertex(x1, y1, color);
|
|
||||||
addLineVertex(x2, y1, color);
|
|
||||||
// 右边
|
|
||||||
addLineVertex(x2, y1, color);
|
|
||||||
addLineVertex(x2, y2, color);
|
|
||||||
// 下边
|
|
||||||
addLineVertex(x2, y2, color);
|
|
||||||
addLineVertex(x1, y2, color);
|
|
||||||
// 左边
|
|
||||||
addLineVertex(x1, y2, color);
|
|
||||||
addLineVertex(x1, y1, color);
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLRenderer::fillRect(const Rect &rect, const Color &color) {
|
|
||||||
// 提交当前批次(如果模式不同)
|
|
||||||
submitShapeBatch(GL_TRIANGLES);
|
|
||||||
|
|
||||||
// 添加两个三角形组成矩形(6个顶点)
|
|
||||||
float x1 = rect.origin.x;
|
|
||||||
float y1 = rect.origin.y;
|
|
||||||
float x2 = rect.origin.x + rect.size.width;
|
|
||||||
float y2 = rect.origin.y + rect.size.height;
|
|
||||||
|
|
||||||
// 三角形1: (x1,y1), (x2,y1), (x2,y2)
|
|
||||||
addShapeVertex(x1, y1, color);
|
|
||||||
addShapeVertex(x2, y1, color);
|
|
||||||
addShapeVertex(x2, y2, color);
|
|
||||||
|
|
||||||
// 三角形2: (x1,y1), (x2,y2), (x1,y2)
|
|
||||||
addShapeVertex(x1, y1, color);
|
|
||||||
addShapeVertex(x2, y2, color);
|
|
||||||
addShapeVertex(x1, y2, color);
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLRenderer::drawCircle(const Vec2 ¢er, float radius,
|
|
||||||
const Color &color, int segments, float width) {
|
|
||||||
// 限制段数不超过缓存大小
|
|
||||||
if (segments > static_cast<int>(MAX_CIRCLE_SEGMENTS)) {
|
|
||||||
segments = static_cast<int>(MAX_CIRCLE_SEGMENTS);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果线宽改变,需要先刷新线条批次
|
|
||||||
if (width != currentLineWidth_) {
|
|
||||||
flushLineBatch();
|
|
||||||
currentLineWidth_ = width;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 使用线条批处理绘制圆形
|
|
||||||
for (int i = 0; i < segments; ++i) {
|
|
||||||
float angle1 =
|
|
||||||
2.0f * 3.14159f * static_cast<float>(i) / static_cast<float>(segments);
|
|
||||||
float angle2 = 2.0f * 3.14159f * static_cast<float>(i + 1) /
|
|
||||||
static_cast<float>(segments);
|
|
||||||
|
|
||||||
addLineVertex(center.x + radius * cosf(angle1),
|
|
||||||
center.y + radius * sinf(angle1), color);
|
|
||||||
addLineVertex(center.x + radius * cosf(angle2),
|
|
||||||
center.y + radius * sinf(angle2), color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLRenderer::fillCircle(const Vec2 ¢er, float radius,
|
|
||||||
const Color &color, int segments) {
|
|
||||||
// 限制段数不超过缓存大小
|
|
||||||
if (segments > static_cast<int>(MAX_CIRCLE_SEGMENTS)) {
|
|
||||||
segments = static_cast<int>(MAX_CIRCLE_SEGMENTS);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 提交当前批次(如果模式不同)
|
|
||||||
submitShapeBatch(GL_TRIANGLES);
|
|
||||||
|
|
||||||
// 使用三角形扇形填充圆
|
|
||||||
// 中心点 + 边缘点
|
|
||||||
for (int i = 0; i < segments; ++i) {
|
|
||||||
float angle1 =
|
|
||||||
2.0f * 3.14159f * static_cast<float>(i) / static_cast<float>(segments);
|
|
||||||
float angle2 = 2.0f * 3.14159f * static_cast<float>(i + 1) /
|
|
||||||
static_cast<float>(segments);
|
|
||||||
|
|
||||||
// 每个三角形:中心 -> 边缘点1 -> 边缘点2
|
|
||||||
addShapeVertex(center.x, center.y, color);
|
|
||||||
addShapeVertex(center.x + radius * cosf(angle1),
|
|
||||||
center.y + radius * sinf(angle1), color);
|
|
||||||
addShapeVertex(center.x + radius * cosf(angle2),
|
|
||||||
center.y + radius * sinf(angle2), color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLRenderer::drawTriangle(const Vec2 &p1, const Vec2 &p2, const Vec2 &p3,
|
|
||||||
const Color &color, float width) {
|
|
||||||
drawLine(p1, p2, color, width);
|
|
||||||
drawLine(p2, p3, color, width);
|
|
||||||
drawLine(p3, p1, color, width);
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLRenderer::fillTriangle(const Vec2 &p1, const Vec2 &p2, const Vec2 &p3,
|
|
||||||
const Color &color) {
|
|
||||||
submitShapeBatch(GL_TRIANGLES);
|
|
||||||
|
|
||||||
addShapeVertex(p1.x, p1.y, color);
|
|
||||||
addShapeVertex(p2.x, p2.y, color);
|
|
||||||
addShapeVertex(p3.x, p3.y, color);
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLRenderer::drawPolygon(const std::vector<Vec2> &points,
|
|
||||||
const Color &color, float width) {
|
|
||||||
if (points.size() < 2)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// 如果线宽改变,需要先刷新线条批次
|
|
||||||
if (width != currentLineWidth_) {
|
|
||||||
flushLineBatch();
|
|
||||||
currentLineWidth_ = width;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 绘制所有边
|
|
||||||
for (size_t i = 0; i < points.size(); ++i) {
|
|
||||||
const Vec2 &p1 = points[i];
|
|
||||||
const Vec2 &p2 = points[(i + 1) % points.size()];
|
|
||||||
addLineVertex(p1.x, p1.y, color);
|
|
||||||
addLineVertex(p2.x, p2.y, color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLRenderer::fillPolygon(const std::vector<Vec2> &points,
|
|
||||||
const Color &color) {
|
|
||||||
if (points.size() < 3)
|
|
||||||
return;
|
|
||||||
|
|
||||||
submitShapeBatch(GL_TRIANGLES);
|
|
||||||
|
|
||||||
// 使用三角形扇形填充
|
|
||||||
// 从第一个点开始,每两个相邻点组成一个三角形
|
|
||||||
for (size_t i = 1; i < points.size() - 1; ++i) {
|
|
||||||
addShapeVertex(points[0].x, points[0].y, color);
|
|
||||||
addShapeVertex(points[i].x, points[i].y, color);
|
|
||||||
addShapeVertex(points[i + 1].x, points[i + 1].y, color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ptr<FontAtlas> GLRenderer::createFontAtlas(const std::string &filepath,
|
|
||||||
int fontSize, bool useSDF) {
|
|
||||||
return makePtr<GLFontAtlas>(filepath, fontSize, useSDF);
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLRenderer::drawText(const FontAtlas &font, const std::string &text,
|
|
||||||
const Vec2 &position, const Color &color) {
|
|
||||||
drawText(font, text, position.x, position.y, color);
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLRenderer::drawText(const FontAtlas &font, const std::string &text,
|
|
||||||
float x, float y, const Color &color) {
|
|
||||||
float cursorX = x;
|
|
||||||
float cursorY = y;
|
|
||||||
float baselineY = cursorY + font.getAscent();
|
|
||||||
|
|
||||||
// 收集所有字符数据用于批处理
|
|
||||||
std::vector<GLSpriteBatch::SpriteData> sprites;
|
|
||||||
sprites.reserve(text.size()); // 预分配空间
|
|
||||||
|
|
||||||
std::u32string utf32_text;
|
|
||||||
utf32_text.resize(simdutf::utf32_length_from_utf8(text.data(), text.size()));
|
|
||||||
simdutf::convert_utf8_to_utf32(text.data(), text.size(), utf32_text.data());
|
|
||||||
for (char32_t codepoint : utf32_text) {
|
|
||||||
if (codepoint == '\n') {
|
|
||||||
cursorX = x;
|
|
||||||
cursorY += font.getLineHeight();
|
|
||||||
baselineY = cursorY + font.getAscent();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Glyph *glyph = font.getGlyph(codepoint);
|
|
||||||
if (glyph) {
|
|
||||||
float penX = cursorX;
|
|
||||||
cursorX += glyph->advance;
|
|
||||||
|
|
||||||
if (glyph->width <= 0.0f || glyph->height <= 0.0f) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
float xPos = penX + glyph->bearingX;
|
|
||||||
float yPos = baselineY + glyph->bearingY;
|
|
||||||
|
|
||||||
GLSpriteBatch::SpriteData data;
|
|
||||||
data.position = glm::vec2(xPos, yPos);
|
|
||||||
data.size = glm::vec2(glyph->width, glyph->height);
|
|
||||||
data.texCoordMin = glm::vec2(glyph->u0, glyph->v0);
|
|
||||||
data.texCoordMax = glm::vec2(glyph->u1, glyph->v1);
|
|
||||||
data.color = glm::vec4(color.r, color.g, color.b, color.a);
|
|
||||||
data.rotation = 0.0f;
|
|
||||||
data.anchor = glm::vec2(0.0f, 0.0f);
|
|
||||||
data.isSDF = font.isSDF();
|
|
||||||
|
|
||||||
sprites.push_back(data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 使用批处理绘制所有字符
|
|
||||||
if (!sprites.empty()) {
|
|
||||||
spriteBatch_.drawBatch(*font.getTexture(), sprites);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLRenderer::resetStats() { stats_ = Stats{}; }
|
|
||||||
|
|
||||||
void GLRenderer::initShapeRendering() {
|
|
||||||
// 编译形状着色器
|
|
||||||
shapeShader_.compileFromSource(SHAPE_VERTEX_SHADER, SHAPE_FRAGMENT_SHADER);
|
|
||||||
|
|
||||||
// 创建形状 VAO 和 VBO
|
|
||||||
glGenVertexArrays(1, &shapeVao_);
|
|
||||||
glGenBuffers(1, &shapeVbo_);
|
|
||||||
|
|
||||||
glBindVertexArray(shapeVao_);
|
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, shapeVbo_);
|
|
||||||
glBufferData(GL_ARRAY_BUFFER, MAX_SHAPE_VERTICES * sizeof(ShapeVertex),
|
|
||||||
nullptr, GL_DYNAMIC_DRAW);
|
|
||||||
|
|
||||||
// 位置属性 (location = 0)
|
|
||||||
glEnableVertexAttribArray(0);
|
|
||||||
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(ShapeVertex),
|
|
||||||
reinterpret_cast<void *>(offsetof(ShapeVertex, x)));
|
|
||||||
|
|
||||||
// 颜色属性 (location = 1)
|
|
||||||
glEnableVertexAttribArray(1);
|
|
||||||
glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, sizeof(ShapeVertex),
|
|
||||||
reinterpret_cast<void *>(offsetof(ShapeVertex, r)));
|
|
||||||
|
|
||||||
glBindVertexArray(0);
|
|
||||||
|
|
||||||
// 创建线条专用 VAO 和 VBO
|
|
||||||
glGenVertexArrays(1, &lineVao_);
|
|
||||||
glGenBuffers(1, &lineVbo_);
|
|
||||||
|
|
||||||
glBindVertexArray(lineVao_);
|
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, lineVbo_);
|
|
||||||
glBufferData(GL_ARRAY_BUFFER, MAX_LINE_VERTICES * sizeof(ShapeVertex),
|
|
||||||
nullptr, GL_DYNAMIC_DRAW);
|
|
||||||
|
|
||||||
// 位置属性 (location = 0)
|
|
||||||
glEnableVertexAttribArray(0);
|
|
||||||
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(ShapeVertex),
|
|
||||||
reinterpret_cast<void *>(offsetof(ShapeVertex, x)));
|
|
||||||
|
|
||||||
// 颜色属性 (location = 1)
|
|
||||||
glEnableVertexAttribArray(1);
|
|
||||||
glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, sizeof(ShapeVertex),
|
|
||||||
reinterpret_cast<void *>(offsetof(ShapeVertex, r)));
|
|
||||||
|
|
||||||
glBindVertexArray(0);
|
|
||||||
|
|
||||||
// VRAM 跟踪
|
|
||||||
VRAMManager::getInstance().allocBuffer(MAX_SHAPE_VERTICES *
|
|
||||||
sizeof(ShapeVertex));
|
|
||||||
VRAMManager::getInstance().allocBuffer(MAX_LINE_VERTICES *
|
|
||||||
sizeof(ShapeVertex));
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLRenderer::addShapeVertex(float x, float y, const Color &color) {
|
|
||||||
if (shapeVertexCount_ >= MAX_SHAPE_VERTICES) {
|
|
||||||
flushShapeBatch();
|
|
||||||
}
|
|
||||||
ShapeVertex &v = shapeVertexCache_[shapeVertexCount_++];
|
|
||||||
v.x = x;
|
|
||||||
v.y = y;
|
|
||||||
v.r = color.r;
|
|
||||||
v.g = color.g;
|
|
||||||
v.b = color.b;
|
|
||||||
v.a = color.a;
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLRenderer::addLineVertex(float x, float y, const Color &color) {
|
|
||||||
if (lineVertexCount_ >= MAX_LINE_VERTICES) {
|
|
||||||
flushLineBatch();
|
|
||||||
}
|
|
||||||
ShapeVertex &v = lineVertexCache_[lineVertexCount_++];
|
|
||||||
v.x = x;
|
|
||||||
v.y = y;
|
|
||||||
v.r = color.r;
|
|
||||||
v.g = color.g;
|
|
||||||
v.b = color.b;
|
|
||||||
v.a = color.a;
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLRenderer::submitShapeBatch(GLenum mode) {
|
|
||||||
if (shapeVertexCount_ == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// 如果模式改变,先刷新
|
|
||||||
if (currentShapeMode_ != mode && shapeVertexCount_ > 0) {
|
|
||||||
flushShapeBatch();
|
|
||||||
}
|
|
||||||
currentShapeMode_ = mode;
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLRenderer::flushShapeBatch() {
|
|
||||||
if (shapeVertexCount_ == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
shapeShader_.bind();
|
|
||||||
shapeShader_.setMat4("uViewProjection", viewProjection_);
|
|
||||||
|
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, shapeVbo_);
|
|
||||||
glBufferSubData(GL_ARRAY_BUFFER, 0, shapeVertexCount_ * sizeof(ShapeVertex),
|
|
||||||
shapeVertexCache_.data());
|
|
||||||
|
|
||||||
glBindVertexArray(shapeVao_);
|
|
||||||
glDrawArrays(currentShapeMode_, 0, static_cast<GLsizei>(shapeVertexCount_));
|
|
||||||
|
|
||||||
stats_.drawCalls++;
|
|
||||||
stats_.triangleCount += static_cast<uint32_t>(shapeVertexCount_ / 3);
|
|
||||||
|
|
||||||
shapeVertexCount_ = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLRenderer::flushLineBatch() {
|
|
||||||
if (lineVertexCount_ == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// 先刷新形状批次
|
|
||||||
flushShapeBatch();
|
|
||||||
|
|
||||||
glLineWidth(currentLineWidth_);
|
|
||||||
shapeShader_.bind();
|
|
||||||
shapeShader_.setMat4("uViewProjection", viewProjection_);
|
|
||||||
|
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, lineVbo_);
|
|
||||||
glBufferSubData(GL_ARRAY_BUFFER, 0, lineVertexCount_ * sizeof(ShapeVertex),
|
|
||||||
lineVertexCache_.data());
|
|
||||||
|
|
||||||
glBindVertexArray(lineVao_);
|
|
||||||
glDrawArrays(GL_LINES, 0, static_cast<GLsizei>(lineVertexCount_));
|
|
||||||
|
|
||||||
stats_.drawCalls++;
|
|
||||||
|
|
||||||
lineVertexCount_ = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
#include <extra2d/core/color.h>
|
||||||
#include <extra2d/render/render_context.h>
|
#include <extra2d/render/render_context.h>
|
||||||
#include <extra2d/render/render_stats.h>
|
#include <extra2d/render/render_stats.h>
|
||||||
|
|
||||||
|
|
@ -8,12 +9,12 @@ RenderContext& RenderContext::instance() {
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RenderContext::init() {
|
bool RenderContext::init(SDL_Window *window, const RenderDeviceConfig &config) {
|
||||||
if (initialized_) {
|
if (initialized_) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!device_.init()) {
|
if (!device_.init(window, config)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -21,7 +22,7 @@ bool RenderContext::init() {
|
||||||
|
|
||||||
viewport_ = {0, 0, 800, 600};
|
viewport_ = {0, 0, 800, 600};
|
||||||
scissor_ = {0, 0, 0, 0, false};
|
scissor_ = {0, 0, 0, 0, false};
|
||||||
clearColor_ = Color::black();
|
clearColor_ = Colors::Black;
|
||||||
|
|
||||||
currentBlend_ = BlendState::opaque();
|
currentBlend_ = BlendState::opaque();
|
||||||
currentDepth_ = DepthState{};
|
currentDepth_ = DepthState{};
|
||||||
|
|
@ -37,8 +38,10 @@ void RenderContext::shutdown() {
|
||||||
}
|
}
|
||||||
|
|
||||||
queue_.clear();
|
queue_.clear();
|
||||||
while (!viewportStack_.empty()) viewportStack_.pop();
|
while (!viewportStack_.empty())
|
||||||
while (!scissorStack_.empty()) scissorStack_.pop();
|
viewportStack_.pop();
|
||||||
|
while (!scissorStack_.empty())
|
||||||
|
scissorStack_.pop();
|
||||||
|
|
||||||
device_.shutdown();
|
device_.shutdown();
|
||||||
initialized_ = false;
|
initialized_ = false;
|
||||||
|
|
@ -50,13 +53,9 @@ void RenderContext::beginFrame() {
|
||||||
E2D_RENDER_STATS().reset();
|
E2D_RENDER_STATS().reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
void RenderContext::endFrame() {
|
void RenderContext::endFrame() { flush(); }
|
||||||
flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
void RenderContext::submit(const RenderCommand& cmd) {
|
void RenderContext::submit(const RenderCommand &cmd) { queue_.addCommand(cmd); }
|
||||||
queue_.addCommand(cmd);
|
|
||||||
}
|
|
||||||
|
|
||||||
void RenderContext::submit(RenderCommand &&cmd) {
|
void RenderContext::submit(RenderCommand &&cmd) {
|
||||||
queue_.addCommand(std::move(cmd));
|
queue_.addCommand(std::move(cmd));
|
||||||
|
|
@ -117,17 +116,18 @@ void RenderContext::executeCommand(const RenderCommand& cmd) {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case RenderCommandType::DrawElements:
|
case RenderCommandType::DrawElements:
|
||||||
device_.drawElements(cmd.primitive, cmd.indexCount, cmd.indexType, cmd.indices);
|
device_.drawElements(cmd.primitive, cmd.indexCount, cmd.indexType,
|
||||||
|
cmd.indices);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case RenderCommandType::DrawArraysInstanced:
|
case RenderCommandType::DrawArraysInstanced:
|
||||||
device_.drawArraysInstanced(cmd.primitive, cmd.firstVertex,
|
device_.drawArraysInstanced(cmd.primitive, cmd.firstVertex, cmd.vertexCount,
|
||||||
cmd.vertexCount, cmd.instanceCount);
|
cmd.instanceCount);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case RenderCommandType::DrawElementsInstanced:
|
case RenderCommandType::DrawElementsInstanced:
|
||||||
device_.drawElementsInstanced(cmd.primitive, cmd.indexCount,
|
device_.drawElementsInstanced(cmd.primitive, cmd.indexCount, cmd.indexType,
|
||||||
cmd.indexType, cmd.indices, cmd.instanceCount);
|
cmd.indices, cmd.instanceCount);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|
@ -166,9 +166,7 @@ void RenderContext::setScissor(int32 x, int32 y, int32 width, int32 height) {
|
||||||
device_.setScissor(x, y, width, height);
|
device_.setScissor(x, y, width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
void RenderContext::pushViewport() {
|
void RenderContext::pushViewport() { viewportStack_.push(viewport_); }
|
||||||
viewportStack_.push(viewport_);
|
|
||||||
}
|
|
||||||
|
|
||||||
void RenderContext::popViewport() {
|
void RenderContext::popViewport() {
|
||||||
if (!viewportStack_.empty()) {
|
if (!viewportStack_.empty()) {
|
||||||
|
|
@ -177,9 +175,7 @@ void RenderContext::popViewport() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void RenderContext::pushScissor() {
|
void RenderContext::pushScissor() { scissorStack_.push(scissor_); }
|
||||||
scissorStack_.push(scissor_);
|
|
||||||
}
|
|
||||||
|
|
||||||
void RenderContext::popScissor() {
|
void RenderContext::popScissor() {
|
||||||
if (!scissorStack_.empty()) {
|
if (!scissorStack_.empty()) {
|
||||||
|
|
@ -188,21 +184,17 @@ void RenderContext::popScissor() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void RenderContext::setClearColor(const Color& color) {
|
void RenderContext::setClearColor(const Color &color) { clearColor_ = color; }
|
||||||
clearColor_ = color;
|
|
||||||
}
|
|
||||||
|
|
||||||
void RenderContext::clear(bool color, bool depth, bool stencil) {
|
void RenderContext::clear(bool color, bool depth, bool stencil) {
|
||||||
device_.clear(color, depth, stencil,
|
device_.clear(color, depth, stencil, clearColor_.r, clearColor_.g,
|
||||||
clearColor_.r, clearColor_.g, clearColor_.b, clearColor_.a);
|
clearColor_.b, clearColor_.a);
|
||||||
}
|
}
|
||||||
|
|
||||||
const RenderStats &RenderContext::stats() const {
|
const RenderStats &RenderContext::stats() const {
|
||||||
return E2D_RENDER_STATS().stats();
|
return E2D_RENDER_STATS().stats();
|
||||||
}
|
}
|
||||||
|
|
||||||
void RenderContext::resetStats() {
|
void RenderContext::resetStats() { E2D_RENDER_STATS().reset(); }
|
||||||
E2D_RENDER_STATS().reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace extra2d
|
} // namespace extra2d
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
|
#include <cstdio>
|
||||||
#include <extra2d/render/render_device.h>
|
#include <extra2d/render/render_device.h>
|
||||||
#include <extra2d/render/render_stats.h>
|
#include <extra2d/render/render_stats.h>
|
||||||
#include <cstdio>
|
#include <extra2d/services/logger_service.h>
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
|
|
@ -9,45 +10,105 @@ RenderDevice& RenderDevice::instance() {
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RenderDevice::init() {
|
bool RenderDevice::init(SDL_Window *window, const RenderDeviceConfig &config) {
|
||||||
if (initialized_) {
|
if (initialized_)
|
||||||
return true;
|
return true;
|
||||||
}
|
|
||||||
|
|
||||||
GLint majorVersion = 0;
|
window_ = window;
|
||||||
GLint minorVersion = 0;
|
|
||||||
glGetIntegerv(GL_MAJOR_VERSION, &majorVersion);
|
|
||||||
glGetIntegerv(GL_MINOR_VERSION, &minorVersion);
|
|
||||||
|
|
||||||
if (majorVersion < 4 || (majorVersion == 4 && minorVersion < 5)) {
|
if (!initGL(config)) {
|
||||||
printf("OpenGL 4.5 is required, got %d.%d\n", majorVersion, minorVersion);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
queryCaps();
|
queryCaps();
|
||||||
|
|
||||||
if (!caps_.dsaSupported) {
|
|
||||||
printf("OpenGL 4.5 DSA is not supported\n");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
glEnable(GL_TEXTURE_2D);
|
|
||||||
|
|
||||||
currentBlend_ = BlendState::opaque();
|
currentBlend_ = BlendState::opaque();
|
||||||
currentDepth_ = DepthState{};
|
currentDepth_ = DepthState{};
|
||||||
currentRaster_ = RasterState{};
|
currentRaster_ = RasterState{};
|
||||||
|
|
||||||
initialized_ = true;
|
initialized_ = true;
|
||||||
|
|
||||||
|
E2D_INFO("RenderDevice initialized");
|
||||||
|
E2D_INFO("OpenGL Version: {}", caps_.version);
|
||||||
|
E2D_INFO("OpenGL Renderer: {}", caps_.renderer);
|
||||||
|
E2D_INFO("GLSL Version: {}", caps_.glslVersion);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RenderDevice::initGL(const RenderDeviceConfig &config) {
|
||||||
|
if (config.useES) {
|
||||||
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
|
||||||
|
} else {
|
||||||
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK,
|
||||||
|
SDL_GL_CONTEXT_PROFILE_CORE);
|
||||||
|
}
|
||||||
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, config.glMajor);
|
||||||
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, config.glMinor);
|
||||||
|
|
||||||
|
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, config.redBits);
|
||||||
|
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, config.greenBits);
|
||||||
|
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, config.blueBits);
|
||||||
|
SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, config.alphaBits);
|
||||||
|
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, config.depthBits);
|
||||||
|
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, config.stencilBits);
|
||||||
|
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, config.doubleBuffer ? 1 : 0);
|
||||||
|
|
||||||
|
if (config.msaaSamples > 0) {
|
||||||
|
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
|
||||||
|
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, config.msaaSamples);
|
||||||
|
}
|
||||||
|
|
||||||
|
glContext_ = SDL_GL_CreateContext(window_);
|
||||||
|
if (!glContext_) {
|
||||||
|
E2D_ERROR("SDL_GL_CreateContext failed: {}", SDL_GetError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SDL_GL_MakeCurrent(window_, glContext_) != 0) {
|
||||||
|
E2D_ERROR("SDL_GL_MakeCurrent failed: {}", SDL_GetError());
|
||||||
|
SDL_GL_DeleteContext(glContext_);
|
||||||
|
glContext_ = nullptr;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gladLoadGLLoader(reinterpret_cast<GLADloadproc>(SDL_GL_GetProcAddress)) ==
|
||||||
|
0) {
|
||||||
|
E2D_ERROR("gladLoadGLLoader failed");
|
||||||
|
SDL_GL_DeleteContext(glContext_);
|
||||||
|
glContext_ = nullptr;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_GL_SetSwapInterval(config.vsync ? 1 : 0);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void RenderDevice::shutdown() {
|
void RenderDevice::shutdown() {
|
||||||
if (!initialized_) {
|
if (!initialized_)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if (glContext_) {
|
||||||
|
SDL_GL_DeleteContext(glContext_);
|
||||||
|
glContext_ = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
window_ = nullptr;
|
||||||
currentProgram_ = 0;
|
currentProgram_ = 0;
|
||||||
initialized_ = false;
|
initialized_ = false;
|
||||||
|
|
||||||
|
E2D_INFO("RenderDevice shutdown");
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderDevice::swapBuffers() {
|
||||||
|
if (window_) {
|
||||||
|
SDL_GL_SwapWindow(window_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderDevice::setVSync(bool enabled) {
|
||||||
|
SDL_GL_SetSwapInterval(enabled ? 1 : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void RenderDevice::queryCaps() {
|
void RenderDevice::queryCaps() {
|
||||||
|
|
@ -56,8 +117,10 @@ void RenderDevice::queryCaps() {
|
||||||
glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &caps_.maxVertexAttribs);
|
glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &caps_.maxVertexAttribs);
|
||||||
glGetIntegerv(GL_MAX_UNIFORM_BUFFER_BINDINGS, &caps_.maxUniformBlockBindings);
|
glGetIntegerv(GL_MAX_UNIFORM_BUFFER_BINDINGS, &caps_.maxUniformBlockBindings);
|
||||||
glGetIntegerv(GL_MAX_UNIFORM_BLOCK_SIZE, &caps_.maxUniformBlockSize);
|
glGetIntegerv(GL_MAX_UNIFORM_BLOCK_SIZE, &caps_.maxUniformBlockSize);
|
||||||
glGetIntegerv(GL_MAX_VERTEX_UNIFORM_COMPONENTS, &caps_.maxVertexUniformComponents);
|
glGetIntegerv(GL_MAX_VERTEX_UNIFORM_COMPONENTS,
|
||||||
glGetIntegerv(GL_MAX_FRAGMENT_UNIFORM_COMPONENTS, &caps_.maxFragmentUniformComponents);
|
&caps_.maxVertexUniformComponents);
|
||||||
|
glGetIntegerv(GL_MAX_FRAGMENT_UNIFORM_COMPONENTS,
|
||||||
|
&caps_.maxFragmentUniformComponents);
|
||||||
glGetIntegerv(GL_MAX_DRAW_BUFFERS, &caps_.maxDrawBuffers);
|
glGetIntegerv(GL_MAX_DRAW_BUFFERS, &caps_.maxDrawBuffers);
|
||||||
glGetIntegerv(GL_MAX_SAMPLES, &caps_.maxSamples);
|
glGetIntegerv(GL_MAX_SAMPLES, &caps_.maxSamples);
|
||||||
|
|
||||||
|
|
@ -65,31 +128,20 @@ void RenderDevice::queryCaps() {
|
||||||
GLint minorVersion = 0;
|
GLint minorVersion = 0;
|
||||||
glGetIntegerv(GL_MAJOR_VERSION, &majorVersion);
|
glGetIntegerv(GL_MAJOR_VERSION, &majorVersion);
|
||||||
glGetIntegerv(GL_MINOR_VERSION, &minorVersion);
|
glGetIntegerv(GL_MINOR_VERSION, &minorVersion);
|
||||||
caps_.dsaSupported = (majorVersion > 4 || (majorVersion == 4 && minorVersion >= 5));
|
caps_.dsaSupported =
|
||||||
|
(majorVersion > 4 || (majorVersion == 4 && minorVersion >= 5));
|
||||||
GLint extensions = 0;
|
|
||||||
glGetIntegerv(GL_NUM_EXTENSIONS, &extensions);
|
|
||||||
for (GLint i = 0; i < extensions; ++i) {
|
|
||||||
String ext = reinterpret_cast<const char*>(glGetStringi(GL_EXTENSIONS, i));
|
|
||||||
if (ext.find("compute_shader") != String::npos) {
|
|
||||||
caps_.computeSupported = true;
|
|
||||||
}
|
|
||||||
if (ext.find("tessellation_shader") != String::npos) {
|
|
||||||
caps_.tessellationSupported = true;
|
|
||||||
}
|
|
||||||
if (ext.find("geometry_shader") != String::npos) {
|
|
||||||
caps_.geometryShaderSupported = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
caps_.renderer = reinterpret_cast<const char *>(glGetString(GL_RENDERER));
|
caps_.renderer = reinterpret_cast<const char *>(glGetString(GL_RENDERER));
|
||||||
caps_.vendor = reinterpret_cast<const char *>(glGetString(GL_VENDOR));
|
caps_.vendor = reinterpret_cast<const char *>(glGetString(GL_VENDOR));
|
||||||
caps_.version = reinterpret_cast<const char *>(glGetString(GL_VERSION));
|
caps_.version = reinterpret_cast<const char *>(glGetString(GL_VERSION));
|
||||||
caps_.glslVersion = reinterpret_cast<const char*>(glGetString(GL_SHADING_LANGUAGE_VERSION));
|
caps_.glslVersion =
|
||||||
|
reinterpret_cast<const char *>(glGetString(GL_SHADING_LANGUAGE_VERSION));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<Buffer> RenderDevice::createBuffer(BufferType type, BufferUsage usage,
|
std::unique_ptr<Buffer> RenderDevice::createBuffer(BufferType type,
|
||||||
size_t size, const void* data) {
|
BufferUsage usage,
|
||||||
|
size_t size,
|
||||||
|
const void *data) {
|
||||||
auto buffer = std::make_unique<Buffer>();
|
auto buffer = std::make_unique<Buffer>();
|
||||||
if (!buffer->create(type, usage, size, data)) {
|
if (!buffer->create(type, usage, size, data)) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
@ -97,7 +149,8 @@ std::unique_ptr<Buffer> RenderDevice::createBuffer(BufferType type, BufferUsage
|
||||||
return buffer;
|
return buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<VertexBuffer> RenderDevice::createVertexBuffer(BufferUsage usage, size_t size,
|
std::unique_ptr<VertexBuffer>
|
||||||
|
RenderDevice::createVertexBuffer(BufferUsage usage, size_t size,
|
||||||
const void *data) {
|
const void *data) {
|
||||||
auto buffer = std::make_unique<VertexBuffer>();
|
auto buffer = std::make_unique<VertexBuffer>();
|
||||||
if (!buffer->create(usage, size, data)) {
|
if (!buffer->create(usage, size, data)) {
|
||||||
|
|
@ -106,7 +159,8 @@ std::unique_ptr<VertexBuffer> RenderDevice::createVertexBuffer(BufferUsage usage
|
||||||
return buffer;
|
return buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<IndexBuffer> RenderDevice::createIndexBuffer(BufferUsage usage, size_t size,
|
std::unique_ptr<IndexBuffer> RenderDevice::createIndexBuffer(BufferUsage usage,
|
||||||
|
size_t size,
|
||||||
const void *data) {
|
const void *data) {
|
||||||
auto buffer = std::make_unique<IndexBuffer>();
|
auto buffer = std::make_unique<IndexBuffer>();
|
||||||
if (!buffer->create(usage, size, data)) {
|
if (!buffer->create(usage, size, data)) {
|
||||||
|
|
@ -115,7 +169,8 @@ std::unique_ptr<IndexBuffer> RenderDevice::createIndexBuffer(BufferUsage usage,
|
||||||
return buffer;
|
return buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<UniformBuffer> RenderDevice::createUniformBuffer(BufferUsage usage, size_t size,
|
std::unique_ptr<UniformBuffer>
|
||||||
|
RenderDevice::createUniformBuffer(BufferUsage usage, size_t size,
|
||||||
const void *data) {
|
const void *data) {
|
||||||
auto buffer = std::make_unique<UniformBuffer>();
|
auto buffer = std::make_unique<UniformBuffer>();
|
||||||
if (!buffer->create(usage, size, data)) {
|
if (!buffer->create(usage, size, data)) {
|
||||||
|
|
@ -148,8 +203,8 @@ void RenderDevice::setScissorEnabled(bool enabled) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void RenderDevice::clear(bool color, bool depth, bool stencil,
|
void RenderDevice::clear(bool color, bool depth, bool stencil, float r, float g,
|
||||||
float r, float g, float b, float a) {
|
float b, float a) {
|
||||||
GLbitfield mask = 0;
|
GLbitfield mask = 0;
|
||||||
if (color) {
|
if (color) {
|
||||||
glClearColor(r, g, b, a);
|
glClearColor(r, g, b, a);
|
||||||
|
|
@ -181,30 +236,24 @@ void RenderDevice::setBlendState(const BlendState& state) {
|
||||||
state.dstRGB != currentBlend_.dstRGB ||
|
state.dstRGB != currentBlend_.dstRGB ||
|
||||||
state.srcAlpha != currentBlend_.srcAlpha ||
|
state.srcAlpha != currentBlend_.srcAlpha ||
|
||||||
state.dstAlpha != currentBlend_.dstAlpha) {
|
state.dstAlpha != currentBlend_.dstAlpha) {
|
||||||
glBlendFuncSeparate(
|
glBlendFuncSeparate(static_cast<GLenum>(state.srcRGB) + GL_ZERO,
|
||||||
static_cast<GLenum>(state.srcRGB) + GL_ZERO,
|
|
||||||
static_cast<GLenum>(state.dstRGB) + GL_ZERO,
|
static_cast<GLenum>(state.dstRGB) + GL_ZERO,
|
||||||
static_cast<GLenum>(state.srcAlpha) + GL_ZERO,
|
static_cast<GLenum>(state.srcAlpha) + GL_ZERO,
|
||||||
static_cast<GLenum>(state.dstAlpha) + GL_ZERO
|
static_cast<GLenum>(state.dstAlpha) + GL_ZERO);
|
||||||
);
|
|
||||||
E2D_RENDER_STATS().addStateChange();
|
E2D_RENDER_STATS().addStateChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state.opRGB != currentBlend_.opRGB || state.opAlpha != currentBlend_.opAlpha) {
|
if (state.opRGB != currentBlend_.opRGB ||
|
||||||
glBlendEquationSeparate(
|
state.opAlpha != currentBlend_.opAlpha) {
|
||||||
static_cast<GLenum>(state.opRGB) + GL_FUNC_ADD,
|
glBlendEquationSeparate(static_cast<GLenum>(state.opRGB) + GL_FUNC_ADD,
|
||||||
static_cast<GLenum>(state.opAlpha) + GL_FUNC_ADD
|
static_cast<GLenum>(state.opAlpha) + GL_FUNC_ADD);
|
||||||
);
|
|
||||||
E2D_RENDER_STATS().addStateChange();
|
E2D_RENDER_STATS().addStateChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state.colorWriteMask != currentBlend_.colorWriteMask) {
|
if (state.colorWriteMask != currentBlend_.colorWriteMask) {
|
||||||
glColorMask(
|
glColorMask(
|
||||||
(state.colorWriteMask & 0x1) != 0,
|
(state.colorWriteMask & 0x1) != 0, (state.colorWriteMask & 0x2) != 0,
|
||||||
(state.colorWriteMask & 0x2) != 0,
|
(state.colorWriteMask & 0x4) != 0, (state.colorWriteMask & 0x8) != 0);
|
||||||
(state.colorWriteMask & 0x4) != 0,
|
|
||||||
(state.colorWriteMask & 0x8) != 0
|
|
||||||
);
|
|
||||||
E2D_RENDER_STATS().addStateChange();
|
E2D_RENDER_STATS().addStateChange();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -247,9 +296,11 @@ void RenderDevice::setRasterState(const RasterState& state) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state.polygonMode != currentRaster_.polygonMode) {
|
if (state.polygonMode != currentRaster_.polygonMode) {
|
||||||
glPolygonMode(GL_FRONT_AND_BACK,
|
glPolygonMode(
|
||||||
state.polygonMode == PolygonMode::Line ? GL_LINE :
|
GL_FRONT_AND_BACK,
|
||||||
(state.polygonMode == PolygonMode::Point ? GL_POINT : GL_FILL));
|
state.polygonMode == PolygonMode::Line
|
||||||
|
? GL_LINE
|
||||||
|
: (state.polygonMode == PolygonMode::Point ? GL_POINT : GL_FILL));
|
||||||
E2D_RENDER_STATS().addStateChange();
|
E2D_RENDER_STATS().addStateChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -287,35 +338,50 @@ void RenderDevice::drawElements(PrimitiveType mode, int32 count, IndexType type,
|
||||||
void RenderDevice::drawArraysInstanced(PrimitiveType mode, int32 first,
|
void RenderDevice::drawArraysInstanced(PrimitiveType mode, int32 first,
|
||||||
int32 count, int32 instanceCount) {
|
int32 count, int32 instanceCount) {
|
||||||
glDrawArraysInstanced(glPrimitiveType(mode), first, count, instanceCount);
|
glDrawArraysInstanced(glPrimitiveType(mode), first, count, instanceCount);
|
||||||
E2D_RENDER_STATS().addDrawCall(count * instanceCount, (count / 3) * instanceCount);
|
E2D_RENDER_STATS().addDrawCall(count * instanceCount,
|
||||||
|
(count / 3) * instanceCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
void RenderDevice::drawElementsInstanced(PrimitiveType mode, int32 count, IndexType type,
|
void RenderDevice::drawElementsInstanced(PrimitiveType mode, int32 count,
|
||||||
const void* indices, int32 instanceCount) {
|
IndexType type, const void *indices,
|
||||||
|
int32 instanceCount) {
|
||||||
glDrawElementsInstanced(glPrimitiveType(mode), count, glIndexType(type),
|
glDrawElementsInstanced(glPrimitiveType(mode), count, glIndexType(type),
|
||||||
indices, instanceCount);
|
indices, instanceCount);
|
||||||
E2D_RENDER_STATS().addDrawCall(count * instanceCount, (count / 3) * instanceCount);
|
E2D_RENDER_STATS().addDrawCall(count * instanceCount,
|
||||||
|
(count / 3) * instanceCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
GLenum RenderDevice::glPrimitiveType(PrimitiveType mode) const {
|
GLenum RenderDevice::glPrimitiveType(PrimitiveType mode) const {
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case PrimitiveType::Points: return GL_POINTS;
|
case PrimitiveType::Points:
|
||||||
case PrimitiveType::Lines: return GL_LINES;
|
return GL_POINTS;
|
||||||
case PrimitiveType::LineStrip: return GL_LINE_STRIP;
|
case PrimitiveType::Lines:
|
||||||
case PrimitiveType::LineLoop: return GL_LINE_LOOP;
|
return GL_LINES;
|
||||||
case PrimitiveType::Triangles: return GL_TRIANGLES;
|
case PrimitiveType::LineStrip:
|
||||||
case PrimitiveType::TriangleStrip: return GL_TRIANGLE_STRIP;
|
return GL_LINE_STRIP;
|
||||||
case PrimitiveType::TriangleFan: return GL_TRIANGLE_FAN;
|
case PrimitiveType::LineLoop:
|
||||||
default: return GL_TRIANGLES;
|
return GL_LINE_LOOP;
|
||||||
|
case PrimitiveType::Triangles:
|
||||||
|
return GL_TRIANGLES;
|
||||||
|
case PrimitiveType::TriangleStrip:
|
||||||
|
return GL_TRIANGLE_STRIP;
|
||||||
|
case PrimitiveType::TriangleFan:
|
||||||
|
return GL_TRIANGLE_FAN;
|
||||||
|
default:
|
||||||
|
return GL_TRIANGLES;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
GLenum RenderDevice::glIndexType(IndexType type) const {
|
GLenum RenderDevice::glIndexType(IndexType type) const {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case IndexType::UInt8: return GL_UNSIGNED_BYTE;
|
case IndexType::UInt8:
|
||||||
case IndexType::UInt16: return GL_UNSIGNED_SHORT;
|
return GL_UNSIGNED_BYTE;
|
||||||
case IndexType::UInt32: return GL_UNSIGNED_INT;
|
case IndexType::UInt16:
|
||||||
default: return GL_UNSIGNED_INT;
|
return GL_UNSIGNED_SHORT;
|
||||||
|
case IndexType::UInt32:
|
||||||
|
return GL_UNSIGNED_INT;
|
||||||
|
default:
|
||||||
|
return GL_UNSIGNED_INT;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,219 @@
|
||||||
|
#include <extra2d/render/renderer.h>
|
||||||
|
#include <extra2d/render/render_context.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
Renderer::Renderer() = default;
|
||||||
|
Renderer::~Renderer() = default;
|
||||||
|
|
||||||
|
bool Renderer::init() {
|
||||||
|
shapeRenderer_ = std::make_unique<ShapeRenderer>();
|
||||||
|
if (!shapeRenderer_->init()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
spriteRenderer_ = std::make_unique<SpriteRenderer>();
|
||||||
|
if (!spriteRenderer_->init()) {
|
||||||
|
shapeRenderer_->shutdown();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Renderer::shutdown() {
|
||||||
|
if (spriteRenderer_) {
|
||||||
|
spriteRenderer_->shutdown();
|
||||||
|
spriteRenderer_.reset();
|
||||||
|
}
|
||||||
|
if (shapeRenderer_) {
|
||||||
|
shapeRenderer_->shutdown();
|
||||||
|
shapeRenderer_.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Renderer::beginFrame(const Color& clearColor) {
|
||||||
|
auto& ctx = RenderContext::instance();
|
||||||
|
ctx.beginFrame();
|
||||||
|
ctx.setClearColor(clearColor);
|
||||||
|
ctx.clear(true, true, false);
|
||||||
|
inFrame_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Renderer::endFrame() {
|
||||||
|
if (!inFrame_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (spriteRenderer_) {
|
||||||
|
spriteRenderer_->end();
|
||||||
|
}
|
||||||
|
if (shapeRenderer_) {
|
||||||
|
shapeRenderer_->end();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& ctx = RenderContext::instance();
|
||||||
|
ctx.endFrame();
|
||||||
|
inFrame_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Renderer::setViewProjection(const glm::mat4& viewProjection) {
|
||||||
|
viewProjection_ = viewProjection;
|
||||||
|
|
||||||
|
if (shapeRenderer_) {
|
||||||
|
shapeRenderer_->end();
|
||||||
|
shapeRenderer_->begin(viewProjection_);
|
||||||
|
}
|
||||||
|
if (spriteRenderer_) {
|
||||||
|
spriteRenderer_->end();
|
||||||
|
spriteRenderer_->begin(viewProjection_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Renderer::drawLine(const Vec2& start, const Vec2& end,
|
||||||
|
const Color& color, float width) {
|
||||||
|
if (shapeRenderer_) {
|
||||||
|
shapeRenderer_->drawLine(start.x, start.y, end.x, end.y,
|
||||||
|
toVec4(color), width);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Renderer::drawRect(const Rect& rect, const Color& color, float width) {
|
||||||
|
if (shapeRenderer_) {
|
||||||
|
shapeRenderer_->drawRect(rect.origin.x, rect.origin.y,
|
||||||
|
rect.size.width, rect.size.height,
|
||||||
|
toVec4(color), width);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Renderer::fillRect(const Rect& rect, const Color& color) {
|
||||||
|
if (shapeRenderer_) {
|
||||||
|
shapeRenderer_->fillRect(rect.origin.x, rect.origin.y,
|
||||||
|
rect.size.width, rect.size.height,
|
||||||
|
toVec4(color));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Renderer::drawCircle(const Vec2& center, float radius, const Color& color,
|
||||||
|
int segments, float width) {
|
||||||
|
if (shapeRenderer_) {
|
||||||
|
shapeRenderer_->drawCircle(center.x, center.y, radius,
|
||||||
|
toVec4(color), width, segments);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Renderer::fillCircle(const Vec2& center, float radius,
|
||||||
|
const Color& color, int segments) {
|
||||||
|
if (shapeRenderer_) {
|
||||||
|
shapeRenderer_->fillCircle(center.x, center.y, radius,
|
||||||
|
toVec4(color), segments);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Renderer::drawTriangle(const Vec2& p1, const Vec2& p2, const Vec2& p3,
|
||||||
|
const Color& color, float width) {
|
||||||
|
if (shapeRenderer_) {
|
||||||
|
shapeRenderer_->drawLine(p1.x, p1.y, p2.x, p2.y, toVec4(color), width);
|
||||||
|
shapeRenderer_->drawLine(p2.x, p2.y, p3.x, p3.y, toVec4(color), width);
|
||||||
|
shapeRenderer_->drawLine(p3.x, p3.y, p1.x, p1.y, toVec4(color), width);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Renderer::fillTriangle(const Vec2& p1, const Vec2& p2, const Vec2& p3,
|
||||||
|
const Color& color) {
|
||||||
|
if (shapeRenderer_) {
|
||||||
|
shapeRenderer_->fillTriangle(p1.x, p1.y, p2.x, p2.y, p3.x, p3.y,
|
||||||
|
toVec4(color));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Renderer::drawPolygon(const std::vector<Vec2>& points, const Color& color,
|
||||||
|
float width) {
|
||||||
|
if (shapeRenderer_ && points.size() >= 2) {
|
||||||
|
std::vector<glm::vec2> glmPoints;
|
||||||
|
glmPoints.reserve(points.size());
|
||||||
|
for (const auto& p : points) {
|
||||||
|
glmPoints.emplace_back(p.x, p.y);
|
||||||
|
}
|
||||||
|
shapeRenderer_->drawPolygon(glmPoints.data(), glmPoints.size(),
|
||||||
|
toVec4(color), width);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Renderer::fillPolygon(const std::vector<Vec2>& points, const Color& color) {
|
||||||
|
if (shapeRenderer_ && points.size() >= 3) {
|
||||||
|
std::vector<glm::vec2> glmPoints;
|
||||||
|
glmPoints.reserve(points.size());
|
||||||
|
for (const auto& p : points) {
|
||||||
|
glmPoints.emplace_back(p.x, p.y);
|
||||||
|
}
|
||||||
|
shapeRenderer_->fillPolygon(glmPoints.data(), glmPoints.size(),
|
||||||
|
toVec4(color));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Renderer::drawSprite(const Texture& texture, const Rect& destRect,
|
||||||
|
const Rect& srcRect, const Color& color,
|
||||||
|
float rotation, const Vec2& anchor) {
|
||||||
|
if (spriteRenderer_ && texture.isValid()) {
|
||||||
|
SpriteData data;
|
||||||
|
data.position = glm::vec2(destRect.origin.x, destRect.origin.y);
|
||||||
|
data.size = glm::vec2(destRect.size.width, destRect.size.height);
|
||||||
|
data.anchor = glm::vec2(anchor.x, anchor.y);
|
||||||
|
data.color = toVec4(color);
|
||||||
|
data.texRect = glm::vec4(
|
||||||
|
srcRect.origin.x / static_cast<float>(texture.width()),
|
||||||
|
srcRect.origin.y / static_cast<float>(texture.height()),
|
||||||
|
srcRect.size.width / static_cast<float>(texture.width()),
|
||||||
|
srcRect.size.height / static_cast<float>(texture.height())
|
||||||
|
);
|
||||||
|
data.rotation = rotation;
|
||||||
|
|
||||||
|
spriteRenderer_->draw(texture.glHandle(), data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Renderer::drawSprite(const Texture& texture, const Vec2& position,
|
||||||
|
const Color& color) {
|
||||||
|
if (texture.isValid()) {
|
||||||
|
Rect destRect(position.x, position.y,
|
||||||
|
static_cast<float>(texture.width()),
|
||||||
|
static_cast<float>(texture.height()));
|
||||||
|
Rect srcRect(0, 0, static_cast<float>(texture.width()),
|
||||||
|
static_cast<float>(texture.height()));
|
||||||
|
drawSprite(texture, destRect, srcRect, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Renderer::drawSprite(const Texture& texture, const Vec2& position,
|
||||||
|
const Vec2& scale, const Color& color) {
|
||||||
|
if (texture.isValid()) {
|
||||||
|
Rect destRect(position.x, position.y,
|
||||||
|
static_cast<float>(texture.width()) * scale.x,
|
||||||
|
static_cast<float>(texture.height()) * scale.y);
|
||||||
|
Rect srcRect(0, 0, static_cast<float>(texture.width()),
|
||||||
|
static_cast<float>(texture.height()));
|
||||||
|
drawSprite(texture, destRect, srcRect, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32 Renderer::drawCalls() const {
|
||||||
|
uint32 calls = 0;
|
||||||
|
if (shapeRenderer_) {
|
||||||
|
calls += shapeRenderer_->drawCalls();
|
||||||
|
}
|
||||||
|
if (spriteRenderer_) {
|
||||||
|
calls += spriteRenderer_->drawCalls();
|
||||||
|
}
|
||||||
|
return calls;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32 Renderer::spriteCount() const {
|
||||||
|
return spriteRenderer_ ? spriteRenderer_->spriteCount() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
glm::vec4 Renderer::toVec4(const Color& color) const {
|
||||||
|
return glm::vec4(color.r, color.g, color.b, color.a);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,189 @@
|
||||||
|
#include <extra2d/render/texture.h>
|
||||||
|
#include <glad/glad.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
Texture::~Texture() {
|
||||||
|
if (glHandle_ != 0) {
|
||||||
|
glDeleteTextures(1, &glHandle_);
|
||||||
|
glHandle_ = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<Texture> Texture::create(const u8 *data,
|
||||||
|
const TextureConfig &config) {
|
||||||
|
auto texture = std::make_unique<Texture>();
|
||||||
|
if (!texture->init(data, config)) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return texture;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<Texture> Texture::createEmpty(int width, int height,
|
||||||
|
int channels) {
|
||||||
|
TextureConfig config;
|
||||||
|
config.width = width;
|
||||||
|
config.height = height;
|
||||||
|
config.channels = channels;
|
||||||
|
return create(nullptr, config);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Texture::init(const u8 *data, const TextureConfig &config) {
|
||||||
|
width_ = config.width;
|
||||||
|
height_ = config.height;
|
||||||
|
channels_ = config.channels;
|
||||||
|
|
||||||
|
glCreateTextures(GL_TEXTURE_2D, 1, &glHandle_);
|
||||||
|
if (glHandle_ == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
glTextureStorage2D(glHandle_, 1, glInternalFormat(), width_, height_);
|
||||||
|
|
||||||
|
if (data != nullptr) {
|
||||||
|
glTextureSubImage2D(glHandle_, 0, 0, 0, width_, height_, glFormat(),
|
||||||
|
GL_UNSIGNED_BYTE, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
setFilter(config.filter);
|
||||||
|
setWrap(config.wrap);
|
||||||
|
|
||||||
|
if (config.generateMips) {
|
||||||
|
glGenerateTextureMipmap(glHandle_);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Texture::update(const u8 *data, int width, int height, int channels) {
|
||||||
|
if (glHandle_ == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (width != width_ || height != height_ || channels != channels_) {
|
||||||
|
glDeleteTextures(1, &glHandle_);
|
||||||
|
width_ = width;
|
||||||
|
height_ = height;
|
||||||
|
channels_ = channels;
|
||||||
|
glCreateTextures(GL_TEXTURE_2D, 1, &glHandle_);
|
||||||
|
glTextureStorage2D(glHandle_, 1, glInternalFormat(), width_, height_);
|
||||||
|
}
|
||||||
|
|
||||||
|
glTextureSubImage2D(glHandle_, 0, 0, 0, width_, height_, glFormat(),
|
||||||
|
GL_UNSIGNED_BYTE, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Texture::updateSubRegion(const u8 *data, int x, int y, int width,
|
||||||
|
int height) {
|
||||||
|
if (glHandle_ == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
glTextureSubImage2D(glHandle_, 0, x, y, width, height, glFormat(),
|
||||||
|
GL_UNSIGNED_BYTE, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Texture::bind(uint32 unit) const {
|
||||||
|
if (glHandle_ != 0) {
|
||||||
|
glBindTextureUnit(unit, glHandle_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Texture::unbind() const { glBindTextureUnit(0, 0); }
|
||||||
|
|
||||||
|
void Texture::setFilter(FilterMode filter) {
|
||||||
|
if (glHandle_ == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GLenum minFilter, magFilter;
|
||||||
|
switch (filter) {
|
||||||
|
case FilterMode::Nearest:
|
||||||
|
minFilter = GL_NEAREST;
|
||||||
|
magFilter = GL_NEAREST;
|
||||||
|
break;
|
||||||
|
case FilterMode::NearestMipmapNearest:
|
||||||
|
minFilter = GL_NEAREST_MIPMAP_NEAREST;
|
||||||
|
magFilter = GL_NEAREST;
|
||||||
|
break;
|
||||||
|
case FilterMode::LinearMipmapNearest:
|
||||||
|
minFilter = GL_LINEAR_MIPMAP_NEAREST;
|
||||||
|
magFilter = GL_LINEAR;
|
||||||
|
break;
|
||||||
|
case FilterMode::NearestMipmapLinear:
|
||||||
|
minFilter = GL_NEAREST_MIPMAP_LINEAR;
|
||||||
|
magFilter = GL_NEAREST;
|
||||||
|
break;
|
||||||
|
case FilterMode::LinearMipmapLinear:
|
||||||
|
minFilter = GL_LINEAR_MIPMAP_LINEAR;
|
||||||
|
magFilter = GL_LINEAR;
|
||||||
|
break;
|
||||||
|
case FilterMode::Linear:
|
||||||
|
default:
|
||||||
|
minFilter = GL_LINEAR;
|
||||||
|
magFilter = GL_LINEAR;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
glTextureParameteri(glHandle_, GL_TEXTURE_MIN_FILTER, minFilter);
|
||||||
|
glTextureParameteri(glHandle_, GL_TEXTURE_MAG_FILTER, magFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Texture::setWrap(WrapMode wrap) {
|
||||||
|
if (glHandle_ == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GLenum glWrap;
|
||||||
|
switch (wrap) {
|
||||||
|
case WrapMode::Repeat:
|
||||||
|
glWrap = GL_REPEAT;
|
||||||
|
break;
|
||||||
|
case WrapMode::Mirror:
|
||||||
|
glWrap = GL_MIRRORED_REPEAT;
|
||||||
|
break;
|
||||||
|
case WrapMode::MirrorClamp:
|
||||||
|
glWrap = GL_MIRROR_CLAMP_TO_EDGE;
|
||||||
|
break;
|
||||||
|
case WrapMode::Border:
|
||||||
|
glWrap = GL_CLAMP_TO_BORDER;
|
||||||
|
break;
|
||||||
|
case WrapMode::Clamp:
|
||||||
|
default:
|
||||||
|
glWrap = GL_CLAMP_TO_EDGE;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
glTextureParameteri(glHandle_, GL_TEXTURE_WRAP_S, glWrap);
|
||||||
|
glTextureParameteri(glHandle_, GL_TEXTURE_WRAP_T, glWrap);
|
||||||
|
}
|
||||||
|
|
||||||
|
GLenum Texture::glInternalFormat() const {
|
||||||
|
switch (channels_) {
|
||||||
|
case 1:
|
||||||
|
return GL_R8;
|
||||||
|
case 2:
|
||||||
|
return GL_RG8;
|
||||||
|
case 3:
|
||||||
|
return GL_RGB8;
|
||||||
|
case 4:
|
||||||
|
default:
|
||||||
|
return GL_RGBA8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GLenum Texture::glFormat() const {
|
||||||
|
switch (channels_) {
|
||||||
|
case 1:
|
||||||
|
return GL_RED;
|
||||||
|
case 2:
|
||||||
|
return GL_RG;
|
||||||
|
case 3:
|
||||||
|
return GL_RGB;
|
||||||
|
case 4:
|
||||||
|
default:
|
||||||
|
return GL_RGBA;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -1,17 +1,18 @@
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <extra2d/graphics/render_command.h>
|
|
||||||
#include <extra2d/scene/node.h>
|
#include <extra2d/scene/node.h>
|
||||||
#include <extra2d/scene/scene.h>
|
#include <extra2d/scene/scene.h>
|
||||||
#include <extra2d/utils/logger.h>
|
#include <extra2d/services/logger_service.h>
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
Node::Node() = default;
|
Node::Node() = default;
|
||||||
|
|
||||||
Node::~Node() { removeAllChildren(); }
|
Node::~Node() {
|
||||||
|
removeAllChildren();
|
||||||
|
}
|
||||||
|
|
||||||
void Node::addChild(Ptr<Node> child) {
|
void Node::addChild(Ref<Node> child) {
|
||||||
if (!child || child.get() == this) {
|
if (!child || child.get() == this) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -21,7 +22,6 @@ void Node::addChild(Ptr<Node> child) {
|
||||||
children_.push_back(child);
|
children_.push_back(child);
|
||||||
childrenOrderDirty_ = true;
|
childrenOrderDirty_ = true;
|
||||||
|
|
||||||
// 更新索引
|
|
||||||
if (!child->getName().empty()) {
|
if (!child->getName().empty()) {
|
||||||
nameIndex_[child->getName()] = child;
|
nameIndex_[child->getName()] = child;
|
||||||
}
|
}
|
||||||
|
|
@ -37,8 +37,7 @@ void Node::addChild(Ptr<Node> child) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Node::addChildren(std::vector<Ptr<Node>> &&children) {
|
void Node::addChildren(std::vector<Ref<Node>>&& children) {
|
||||||
// 预留空间,避免多次扩容
|
|
||||||
size_t newSize = children_.size() + children.size();
|
size_t newSize = children_.size() + children.size();
|
||||||
if (newSize > children_.capacity()) {
|
if (newSize > children_.capacity()) {
|
||||||
children_.reserve(newSize);
|
children_.reserve(newSize);
|
||||||
|
|
@ -53,7 +52,6 @@ void Node::addChildren(std::vector<Ptr<Node>> &&children) {
|
||||||
child->parent_ = weak_from_this();
|
child->parent_ = weak_from_this();
|
||||||
children_.push_back(child);
|
children_.push_back(child);
|
||||||
|
|
||||||
// 更新索引
|
|
||||||
if (!child->getName().empty()) {
|
if (!child->getName().empty()) {
|
||||||
nameIndex_[child->getName()] = child;
|
nameIndex_[child->getName()] = child;
|
||||||
}
|
}
|
||||||
|
|
@ -74,20 +72,19 @@ void Node::addChildren(std::vector<Ptr<Node>> &&children) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Node::removeChild(Ptr<Node> child) {
|
void Node::removeChild(Ref<Node> child) {
|
||||||
if (!child)
|
if (!child) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
auto it = std::find(children_.begin(), children_.end(), child);
|
auto it = std::find(children_.begin(), children_.end(), child);
|
||||||
if (it != children_.end()) {
|
if (it != children_.end()) {
|
||||||
// 始终从空间索引中移除(无论 running_ 状态)
|
|
||||||
// 这确保节点被正确清理
|
|
||||||
(*it)->onDetachFromScene();
|
(*it)->onDetachFromScene();
|
||||||
|
|
||||||
if (running_) {
|
if (running_) {
|
||||||
(*it)->onExit();
|
(*it)->onExit();
|
||||||
}
|
}
|
||||||
// 从索引中移除
|
|
||||||
if (!(*it)->getName().empty()) {
|
if (!(*it)->getName().empty()) {
|
||||||
nameIndex_.erase((*it)->getName());
|
nameIndex_.erase((*it)->getName());
|
||||||
}
|
}
|
||||||
|
|
@ -109,12 +106,10 @@ void Node::removeChildByName(const std::string &name) {
|
||||||
void Node::removeFromParent() {
|
void Node::removeFromParent() {
|
||||||
auto p = parent_.lock();
|
auto p = parent_.lock();
|
||||||
if (p) {
|
if (p) {
|
||||||
// 安全获取 shared_ptr,避免在对象未由 shared_ptr 管理时崩溃
|
Ref<Node> self;
|
||||||
Ptr<Node> self;
|
|
||||||
try {
|
try {
|
||||||
self = shared_from_this();
|
self = shared_from_this();
|
||||||
} catch (const std::bad_weak_ptr&) {
|
} catch (const std::bad_weak_ptr&) {
|
||||||
// 对象不是由 shared_ptr 管理的,直接重置父节点引用
|
|
||||||
parent_.reset();
|
parent_.reset();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -135,8 +130,7 @@ void Node::removeAllChildren() {
|
||||||
tagIndex_.clear();
|
tagIndex_.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
Ptr<Node> Node::getChildByName(const std::string &name) const {
|
Ref<Node> Node::getChildByName(const std::string& name) const {
|
||||||
// 使用哈希索引,O(1) 查找
|
|
||||||
auto it = nameIndex_.find(name);
|
auto it = nameIndex_.find(name);
|
||||||
if (it != nameIndex_.end()) {
|
if (it != nameIndex_.end()) {
|
||||||
return it->second.lock();
|
return it->second.lock();
|
||||||
|
|
@ -144,8 +138,7 @@ Ptr<Node> Node::getChildByName(const std::string &name) const {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ptr<Node> Node::getChildByTag(int tag) const {
|
Ref<Node> Node::getChildByTag(int tag) const {
|
||||||
// 使用哈希索引,O(1) 查找
|
|
||||||
auto it = tagIndex_.find(tag);
|
auto it = tagIndex_.find(tag);
|
||||||
if (it != tagIndex_.end()) {
|
if (it != tagIndex_.end()) {
|
||||||
return it->second.lock();
|
return it->second.lock();
|
||||||
|
|
@ -159,7 +152,9 @@ void Node::setPosition(const Vec2 &pos) {
|
||||||
updateSpatialIndex();
|
updateSpatialIndex();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Node::setPosition(float x, float y) { setPosition(Vec2(x, y)); }
|
void Node::setPosition(float x, float y) {
|
||||||
|
setPosition(Vec2(x, y));
|
||||||
|
}
|
||||||
|
|
||||||
void Node::setRotation(float degrees) {
|
void Node::setRotation(float degrees) {
|
||||||
rotation_ = degrees;
|
rotation_ = degrees;
|
||||||
|
|
@ -173,35 +168,51 @@ void Node::setScale(const Vec2 &scale) {
|
||||||
updateSpatialIndex();
|
updateSpatialIndex();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Node::setScale(float scale) { setScale(Vec2(scale, scale)); }
|
void Node::setScale(float scale) {
|
||||||
|
setScale(Vec2(scale, scale));
|
||||||
|
}
|
||||||
|
|
||||||
void Node::setScale(float x, float y) { setScale(Vec2(x, y)); }
|
void Node::setScale(float x, float y) {
|
||||||
|
setScale(Vec2(x, y));
|
||||||
|
}
|
||||||
|
|
||||||
void Node::setAnchor(const Vec2& anchor) {
|
void Node::setAnchor(const Vec2& anchor) {
|
||||||
anchor_ = anchor;
|
anchor_ = anchor;
|
||||||
markTransformDirty();
|
markTransformDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Node::setAnchor(float x, float y) { setAnchor(Vec2(x, y)); }
|
void Node::setAnchor(float x, float y) {
|
||||||
|
setAnchor(Vec2(x, y));
|
||||||
|
}
|
||||||
|
|
||||||
void Node::setSkew(const Vec2& skew) {
|
void Node::setSkew(const Vec2& skew) {
|
||||||
skew_ = skew;
|
skew_ = skew;
|
||||||
markTransformDirty();
|
markTransformDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Node::setSkew(float x, float y) { setSkew(Vec2(x, y)); }
|
void Node::setSkew(float x, float y) {
|
||||||
|
setSkew(Vec2(x, y));
|
||||||
|
}
|
||||||
|
|
||||||
void Node::setOpacity(float opacity) {
|
void Node::setOpacity(float opacity) {
|
||||||
opacity_ = std::clamp(opacity, 0.0f, 1.0f);
|
opacity_ = std::clamp(opacity, 0.0f, 1.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Node::setVisible(bool visible) { visible_ = visible; }
|
void Node::setVisible(bool visible) {
|
||||||
|
visible_ = visible;
|
||||||
|
}
|
||||||
|
|
||||||
void Node::setColor(const Color3B &color) { color_ = color; }
|
void Node::setColor(const Color3B& color) {
|
||||||
|
color_ = color;
|
||||||
|
}
|
||||||
|
|
||||||
void Node::setFlipX(bool flipX) { flipX_ = flipX; }
|
void Node::setFlipX(bool flipX) {
|
||||||
|
flipX_ = flipX;
|
||||||
|
}
|
||||||
|
|
||||||
void Node::setFlipY(bool flipY) { flipY_ = flipY; }
|
void Node::setFlipY(bool flipY) {
|
||||||
|
flipY_ = flipY;
|
||||||
|
}
|
||||||
|
|
||||||
void Node::setZOrder(int zOrder) {
|
void Node::setZOrder(int zOrder) {
|
||||||
if (zOrder_ != zOrder) {
|
if (zOrder_ != zOrder) {
|
||||||
|
|
@ -211,8 +222,7 @@ void Node::setZOrder(int zOrder) {
|
||||||
}
|
}
|
||||||
|
|
||||||
Vec2 Node::convertToWorldSpace(const Vec2& localPos) const {
|
Vec2 Node::convertToWorldSpace(const Vec2& localPos) const {
|
||||||
glm::vec4 worldPos =
|
glm::vec4 worldPos = getWorldTransform() * glm::vec4(localPos.x, localPos.y, 0.0f, 1.0f);
|
||||||
getWorldTransform() * glm::vec4(localPos.x, localPos.y, 0.0f, 1.0f);
|
|
||||||
return Vec2(worldPos.x, worldPos.y);
|
return Vec2(worldPos.x, worldPos.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -226,7 +236,6 @@ glm::mat4 Node::getLocalTransform() const {
|
||||||
if (transformDirty_) {
|
if (transformDirty_) {
|
||||||
localTransform_ = glm::mat4(1.0f);
|
localTransform_ = glm::mat4(1.0f);
|
||||||
|
|
||||||
// T - R - S order
|
|
||||||
localTransform_ = glm::translate(localTransform_,
|
localTransform_ = glm::translate(localTransform_,
|
||||||
glm::vec3(position_.x, position_.y, 0.0f));
|
glm::vec3(position_.x, position_.y, 0.0f));
|
||||||
|
|
||||||
|
|
@ -242,11 +251,7 @@ glm::mat4 Node::getLocalTransform() const {
|
||||||
localTransform_ *= skewMatrix;
|
localTransform_ *= skewMatrix;
|
||||||
}
|
}
|
||||||
|
|
||||||
localTransform_ =
|
localTransform_ = glm::scale(localTransform_, glm::vec3(scale_.x, scale_.y, 1.0f));
|
||||||
glm::scale(localTransform_, glm::vec3(scale_.x, scale_.y, 1.0f));
|
|
||||||
|
|
||||||
// 注意:锚点偏移在渲染时处理,不在本地变换中处理
|
|
||||||
// 这样可以避免锚点偏移被父节点的缩放影响
|
|
||||||
|
|
||||||
transformDirty_ = false;
|
transformDirty_ = false;
|
||||||
}
|
}
|
||||||
|
|
@ -255,8 +260,6 @@ glm::mat4 Node::getLocalTransform() const {
|
||||||
|
|
||||||
glm::mat4 Node::getWorldTransform() const {
|
glm::mat4 Node::getWorldTransform() const {
|
||||||
if (worldTransformDirty_) {
|
if (worldTransformDirty_) {
|
||||||
// 使用线程局部存储的固定数组,避免每帧内存分配
|
|
||||||
// 限制最大深度为 256 层,足以覆盖绝大多数场景
|
|
||||||
thread_local std::array<const Node*, 256> nodeChainCache;
|
thread_local std::array<const Node*, 256> nodeChainCache;
|
||||||
thread_local size_t chainCount = 0;
|
thread_local size_t chainCount = 0;
|
||||||
|
|
||||||
|
|
@ -268,7 +271,6 @@ glm::mat4 Node::getWorldTransform() const {
|
||||||
current = p.get();
|
current = p.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 从根节点开始计算
|
|
||||||
glm::mat4 transform = glm::mat4(1.0f);
|
glm::mat4 transform = glm::mat4(1.0f);
|
||||||
for (size_t i = chainCount; i > 0; --i) {
|
for (size_t i = chainCount; i > 0; --i) {
|
||||||
transform = transform * nodeChainCache[i - 1]->getLocalTransform();
|
transform = transform * nodeChainCache[i - 1]->getLocalTransform();
|
||||||
|
|
@ -280,12 +282,10 @@ glm::mat4 Node::getWorldTransform() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Node::markTransformDirty() {
|
void Node::markTransformDirty() {
|
||||||
// 避免重复标记,提高性能
|
|
||||||
if (!transformDirty_ || !worldTransformDirty_) {
|
if (!transformDirty_ || !worldTransformDirty_) {
|
||||||
transformDirty_ = true;
|
transformDirty_ = true;
|
||||||
worldTransformDirty_ = true;
|
worldTransformDirty_ = true;
|
||||||
|
|
||||||
// 递归标记所有子节点
|
|
||||||
for (auto& child : children_) {
|
for (auto& child : children_) {
|
||||||
child->markTransformDirty();
|
child->markTransformDirty();
|
||||||
}
|
}
|
||||||
|
|
@ -293,25 +293,20 @@ void Node::markTransformDirty() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Node::batchUpdateTransforms() {
|
void Node::batchUpdateTransforms() {
|
||||||
// 如果本地变换脏了,先计算本地变换
|
|
||||||
if (transformDirty_) {
|
if (transformDirty_) {
|
||||||
(void)getLocalTransform(); // 这会计算并缓存本地变换
|
(void)getLocalTransform();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果世界变换脏了,需要重新计算
|
|
||||||
if (worldTransformDirty_) {
|
if (worldTransformDirty_) {
|
||||||
auto parent = parent_.lock();
|
auto parent = parent_.lock();
|
||||||
if (parent) {
|
if (parent) {
|
||||||
// 使用父节点的世界变换(确保父节点已经更新)
|
|
||||||
worldTransform_ = parent->getWorldTransform() * localTransform_;
|
worldTransform_ = parent->getWorldTransform() * localTransform_;
|
||||||
} else {
|
} else {
|
||||||
// 根节点
|
|
||||||
worldTransform_ = localTransform_;
|
worldTransform_ = localTransform_;
|
||||||
}
|
}
|
||||||
worldTransformDirty_ = false;
|
worldTransformDirty_ = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 递归更新子节点
|
|
||||||
for (auto& child : children_) {
|
for (auto& child : children_) {
|
||||||
child->batchUpdateTransforms();
|
child->batchUpdateTransforms();
|
||||||
}
|
}
|
||||||
|
|
@ -334,15 +329,15 @@ void Node::onExit() {
|
||||||
void Node::onUpdate(float dt) {
|
void Node::onUpdate(float dt) {
|
||||||
onUpdateNode(dt);
|
onUpdateNode(dt);
|
||||||
|
|
||||||
// Update children
|
|
||||||
for (auto& child : children_) {
|
for (auto& child : children_) {
|
||||||
child->onUpdate(dt);
|
child->onUpdate(dt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Node::onRender(Renderer& renderer) {
|
void Node::onRender(Renderer& renderer) {
|
||||||
if (!visible_)
|
if (!visible_) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
onDraw(renderer);
|
onDraw(renderer);
|
||||||
|
|
||||||
|
|
@ -354,7 +349,6 @@ void Node::onRender(Renderer &renderer) {
|
||||||
void Node::onAttachToScene(Scene* scene) {
|
void Node::onAttachToScene(Scene* scene) {
|
||||||
scene_ = scene;
|
scene_ = scene;
|
||||||
|
|
||||||
// 添加到场景的空间索引
|
|
||||||
if (spatialIndexed_ && scene_) {
|
if (spatialIndexed_ && scene_) {
|
||||||
lastSpatialBounds_ = Rect();
|
lastSpatialBounds_ = Rect();
|
||||||
updateSpatialIndex();
|
updateSpatialIndex();
|
||||||
|
|
@ -366,9 +360,6 @@ void Node::onAttachToScene(Scene *scene) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Node::onDetachFromScene() {
|
void Node::onDetachFromScene() {
|
||||||
// 从场景的空间索引移除
|
|
||||||
// 注意:即使 lastSpatialBounds_ 为空也要尝试移除,
|
|
||||||
// 因为节点可能通过其他方式被插入到空间索引中
|
|
||||||
if (spatialIndexed_ && scene_) {
|
if (spatialIndexed_ && scene_) {
|
||||||
scene_->removeNodeFromSpatialIndex(this);
|
scene_->removeNodeFromSpatialIndex(this);
|
||||||
lastSpatialBounds_ = Rect();
|
lastSpatialBounds_ = Rect();
|
||||||
|
|
@ -381,7 +372,6 @@ void Node::onDetachFromScene() {
|
||||||
}
|
}
|
||||||
|
|
||||||
Rect Node::getBoundingBox() const {
|
Rect Node::getBoundingBox() const {
|
||||||
// 默认返回一个以位置为中心的点矩形
|
|
||||||
return Rect(position_.x, position_.y, 0, 0);
|
return Rect(position_.x, position_.y, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -397,7 +387,9 @@ void Node::updateSpatialIndex() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Node::update(float dt) { onUpdate(dt); }
|
void Node::update(float dt) {
|
||||||
|
onUpdate(dt);
|
||||||
|
}
|
||||||
|
|
||||||
void Node::render(Renderer& renderer) {
|
void Node::render(Renderer& renderer) {
|
||||||
if (childrenOrderDirty_) {
|
if (childrenOrderDirty_) {
|
||||||
|
|
@ -407,17 +399,13 @@ void Node::render(Renderer &renderer) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Node::sortChildren() {
|
void Node::sortChildren() {
|
||||||
// 使用插入排序优化小范围更新场景
|
|
||||||
// 插入排序在大部分已有序的情况下性能接近O(n)
|
|
||||||
size_t n = children_.size();
|
size_t n = children_.size();
|
||||||
if (n <= 1) {
|
if (n <= 1) {
|
||||||
childrenOrderDirty_ = false;
|
childrenOrderDirty_ = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 小数组使用插入排序,大数组使用std::sort
|
|
||||||
if (n < 32) {
|
if (n < 32) {
|
||||||
// 插入排序
|
|
||||||
for (size_t i = 1; i < n; ++i) {
|
for (size_t i = 1; i < n; ++i) {
|
||||||
auto key = children_[i];
|
auto key = children_[i];
|
||||||
int keyZOrder = key->getZOrder();
|
int keyZOrder = key->getZOrder();
|
||||||
|
|
@ -430,9 +418,8 @@ void Node::sortChildren() {
|
||||||
children_[j + 1] = key;
|
children_[j + 1] = key;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 大数组使用标准排序
|
|
||||||
std::sort(children_.begin(), children_.end(),
|
std::sort(children_.begin(), children_.end(),
|
||||||
[](const Ptr<Node> &a, const Ptr<Node> &b) {
|
[](const Ref<Node>& a, const Ref<Node>& b) {
|
||||||
return a->getZOrder() < b->getZOrder();
|
return a->getZOrder() < b->getZOrder();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -440,22 +427,4 @@ void Node::sortChildren() {
|
||||||
childrenOrderDirty_ = false;
|
childrenOrderDirty_ = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Node::collectRenderCommands(std::vector<RenderCommand> &commands,
|
|
||||||
int parentZOrder) {
|
|
||||||
if (!visible_)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// 计算累积 Z 序
|
|
||||||
int accumulatedZOrder = parentZOrder + zOrder_;
|
|
||||||
|
|
||||||
// 生成当前节点的渲染命令
|
|
||||||
generateRenderCommand(commands, accumulatedZOrder);
|
|
||||||
|
|
||||||
// 递归收集子节点的渲染命令
|
|
||||||
// 注意:这里假设子节点已经按 Z 序排序
|
|
||||||
for (auto &child : children_) {
|
|
||||||
child->collectRenderCommands(commands, accumulatedZOrder);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace extra2d
|
} // namespace extra2d
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,23 @@
|
||||||
#include <extra2d/graphics/render_command.h>
|
#include <extra2d/render/renderer.h>
|
||||||
#include <extra2d/graphics/renderer.h>
|
|
||||||
#include <extra2d/scene/scene.h>
|
#include <extra2d/scene/scene.h>
|
||||||
#include <extra2d/utils/logger.h>
|
#include <extra2d/services/logger_service.h>
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
Scene::Scene() { defaultCamera_ = makePtr<Camera>(); }
|
Scene::Scene() {
|
||||||
|
defaultCamera_ = ptr::make<Camera>();
|
||||||
|
}
|
||||||
|
|
||||||
void Scene::setCamera(Ptr<Camera> camera) { camera_ = camera; }
|
void Scene::setCamera(Ref<Camera> camera) {
|
||||||
|
camera_ = camera;
|
||||||
|
}
|
||||||
|
|
||||||
void Scene::setViewportSize(float width, float height) {
|
void Scene::setViewportSize(float width, float height) {
|
||||||
viewportSize_ = Size(width, height);
|
viewportSize_ = Size(width, height);
|
||||||
if (defaultCamera_) {
|
if (defaultCamera_) {
|
||||||
defaultCamera_->setViewport(0, width, height, 0);
|
defaultCamera_->setViewport(0, static_cast<int>(width), static_cast<int>(height), 0);
|
||||||
} else if (camera_) {
|
} else if (camera_) {
|
||||||
camera_->setViewport(0, width, height, 0);
|
camera_->setViewport(0, static_cast<int>(width), static_cast<int>(height), 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -23,20 +26,20 @@ void Scene::setViewportSize(const Size &size) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Scene::renderScene(Renderer& renderer) {
|
void Scene::renderScene(Renderer& renderer) {
|
||||||
if (!isVisible())
|
if (!isVisible()) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Begin frame with background color
|
|
||||||
renderer.beginFrame(backgroundColor_);
|
renderer.beginFrame(backgroundColor_);
|
||||||
renderContent(renderer);
|
renderContent(renderer);
|
||||||
renderer.endFrame();
|
renderer.endFrame();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Scene::renderContent(Renderer& renderer) {
|
void Scene::renderContent(Renderer& renderer) {
|
||||||
if (!isVisible())
|
if (!isVisible()) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// 在渲染前批量更新所有节点的世界变换
|
|
||||||
batchUpdateTransforms();
|
batchUpdateTransforms();
|
||||||
|
|
||||||
Camera* activeCam = getActiveCamera();
|
Camera* activeCam = getActiveCamera();
|
||||||
|
|
@ -56,7 +59,6 @@ void Scene::updateScene(float dt) {
|
||||||
void Scene::onEnter() {
|
void Scene::onEnter() {
|
||||||
Node::onEnter();
|
Node::onEnter();
|
||||||
|
|
||||||
// 初始化空间索引世界边界
|
|
||||||
if (spatialIndexingEnabled_) {
|
if (spatialIndexingEnabled_) {
|
||||||
spatialManager_.setWorldBounds(
|
spatialManager_.setWorldBounds(
|
||||||
Rect(0, 0, viewportSize_.width, viewportSize_.height));
|
Rect(0, 0, viewportSize_.width, viewportSize_.height));
|
||||||
|
|
@ -64,23 +66,19 @@ void Scene::onEnter() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Scene::onExit() {
|
void Scene::onExit() {
|
||||||
// 清理空间索引
|
|
||||||
spatialManager_.clear();
|
spatialManager_.clear();
|
||||||
Node::onExit();
|
Node::onExit();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Scene::updateNodeInSpatialIndex(Node *node, const Rect &oldBounds,
|
void Scene::updateNodeInSpatialIndex(Node* node, const Rect& oldBounds, const Rect& newBounds) {
|
||||||
const Rect &newBounds) {
|
|
||||||
if (!spatialIndexingEnabled_ || !node || !node->isSpatialIndexed()) {
|
if (!spatialIndexingEnabled_ || !node || !node->isSpatialIndexed()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果旧边界有效,先移除
|
|
||||||
if (!oldBounds.empty()) {
|
if (!oldBounds.empty()) {
|
||||||
spatialManager_.remove(node);
|
spatialManager_.remove(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果新边界有效,插入
|
|
||||||
if (!newBounds.empty()) {
|
if (!newBounds.empty()) {
|
||||||
spatialManager_.insert(node, newBounds);
|
spatialManager_.insert(node, newBounds);
|
||||||
}
|
}
|
||||||
|
|
@ -115,15 +113,8 @@ std::vector<std::pair<Node *, Node *>> Scene::queryCollisions() const {
|
||||||
return spatialManager_.queryCollisions();
|
return spatialManager_.queryCollisions();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Scene::collectRenderCommands(std::vector<RenderCommand> &commands,
|
Ref<Scene> Scene::create() {
|
||||||
int parentZOrder) {
|
return ptr::make<Scene>();
|
||||||
if (!isVisible())
|
|
||||||
return;
|
|
||||||
|
|
||||||
// 从场景的子节点开始收集渲染命令
|
|
||||||
Node::collectRenderCommands(commands, parentZOrder);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ptr<Scene> Scene::create() { return makePtr<Scene>(); }
|
|
||||||
|
|
||||||
} // namespace extra2d
|
} // namespace extra2d
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
#include <algorithm>
|
#include <extra2d/render/renderer.h>
|
||||||
#include <extra2d/graphics/render_command.h>
|
|
||||||
#include <extra2d/graphics/renderer.h>
|
|
||||||
#include <extra2d/scene/scene_manager.h>
|
#include <extra2d/scene/scene_manager.h>
|
||||||
#include <extra2d/utils/logger.h>
|
#include <extra2d/services/logger_service.h>
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
|
|
@ -11,22 +9,21 @@ SceneManager &SceneManager::getInstance() {
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SceneManager::runWithScene(Ptr<Scene> scene) {
|
void SceneManager::runWithScene(Ref<Scene> scene) {
|
||||||
if (!scene) {
|
if (!scene) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!sceneStack_.empty()) {
|
if (!sceneStack_.empty()) {
|
||||||
E2D_LOG_WARN("SceneManager: runWithScene should only be called once");
|
E2D_WARN("SceneManager: runWithScene should only be called once");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
scene->onEnter();
|
scene->onEnter();
|
||||||
scene->onAttachToScene(scene.get());
|
|
||||||
sceneStack_.push(scene);
|
sceneStack_.push(scene);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SceneManager::replaceScene(Ptr<Scene> scene) {
|
void SceneManager::replaceScene(Ref<Scene> scene) {
|
||||||
if (!scene) {
|
if (!scene) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -36,19 +33,15 @@ void SceneManager::replaceScene(Ptr<Scene> scene) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pop current scene
|
|
||||||
auto oldScene = sceneStack_.top();
|
auto oldScene = sceneStack_.top();
|
||||||
oldScene->onExit();
|
oldScene->onExit();
|
||||||
oldScene->onDetachFromScene();
|
|
||||||
sceneStack_.pop();
|
sceneStack_.pop();
|
||||||
|
|
||||||
// Push new scene
|
|
||||||
scene->onEnter();
|
scene->onEnter();
|
||||||
scene->onAttachToScene(scene.get());
|
|
||||||
sceneStack_.push(scene);
|
sceneStack_.push(scene);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SceneManager::enterScene(Ptr<Scene> scene) {
|
void SceneManager::enterScene(Ref<Scene> scene) {
|
||||||
if (!scene) {
|
if (!scene) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -60,18 +53,16 @@ void SceneManager::enterScene(Ptr<Scene> scene) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SceneManager::pushScene(Ptr<Scene> scene) {
|
void SceneManager::pushScene(Ref<Scene> scene) {
|
||||||
if (!scene) {
|
if (!scene) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Pause current scene
|
|
||||||
if (!sceneStack_.empty()) {
|
if (!sceneStack_.empty()) {
|
||||||
sceneStack_.top()->pause();
|
sceneStack_.top()->pause();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Push new scene
|
|
||||||
scene->onEnter();
|
scene->onEnter();
|
||||||
scene->onAttachToScene(scene.get());
|
|
||||||
sceneStack_.push(scene);
|
sceneStack_.push(scene);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -82,10 +73,8 @@ void SceneManager::popScene() {
|
||||||
|
|
||||||
auto current = sceneStack_.top();
|
auto current = sceneStack_.top();
|
||||||
current->onExit();
|
current->onExit();
|
||||||
current->onDetachFromScene();
|
|
||||||
sceneStack_.pop();
|
sceneStack_.pop();
|
||||||
|
|
||||||
// Resume previous scene
|
|
||||||
if (!sceneStack_.empty()) {
|
if (!sceneStack_.empty()) {
|
||||||
sceneStack_.top()->resume();
|
sceneStack_.top()->resume();
|
||||||
}
|
}
|
||||||
|
|
@ -96,22 +85,18 @@ void SceneManager::popToRootScene() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exit all scenes except root
|
|
||||||
while (sceneStack_.size() > 1) {
|
while (sceneStack_.size() > 1) {
|
||||||
auto scene = sceneStack_.top();
|
auto scene = sceneStack_.top();
|
||||||
scene->onExit();
|
scene->onExit();
|
||||||
scene->onDetachFromScene();
|
|
||||||
sceneStack_.pop();
|
sceneStack_.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resume root
|
|
||||||
sceneStack_.top()->resume();
|
sceneStack_.top()->resume();
|
||||||
}
|
}
|
||||||
|
|
||||||
void SceneManager::popToScene(const std::string& name) {
|
void SceneManager::popToScene(const std::string& name) {
|
||||||
// Find target scene in stack
|
std::stack<Ref<Scene>> tempStack;
|
||||||
std::stack<Ptr<Scene>> tempStack;
|
Ref<Scene> target = nullptr;
|
||||||
Ptr<Scene> target = nullptr;
|
|
||||||
|
|
||||||
while (!sceneStack_.empty()) {
|
while (!sceneStack_.empty()) {
|
||||||
auto scene = sceneStack_.top();
|
auto scene = sceneStack_.top();
|
||||||
|
|
@ -120,7 +105,6 @@ void SceneManager::popToScene(const std::string &name) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
scene->onExit();
|
scene->onExit();
|
||||||
scene->onDetachFromScene();
|
|
||||||
sceneStack_.pop();
|
sceneStack_.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -129,32 +113,30 @@ void SceneManager::popToScene(const std::string &name) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ptr<Scene> SceneManager::getCurrentScene() const {
|
Ref<Scene> SceneManager::getCurrentScene() const {
|
||||||
if (sceneStack_.empty()) {
|
if (sceneStack_.empty()) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
return sceneStack_.top();
|
return sceneStack_.top();
|
||||||
}
|
}
|
||||||
|
|
||||||
Ptr<Scene> SceneManager::getPreviousScene() const {
|
Ref<Scene> SceneManager::getPreviousScene() const {
|
||||||
if (sceneStack_.size() < 2) {
|
if (sceneStack_.size() < 2) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy stack to access second top
|
|
||||||
auto tempStack = sceneStack_;
|
auto tempStack = sceneStack_;
|
||||||
tempStack.pop();
|
tempStack.pop();
|
||||||
return tempStack.top();
|
return tempStack.top();
|
||||||
}
|
}
|
||||||
|
|
||||||
Ptr<Scene> SceneManager::getRootScene() const {
|
Ref<Scene> SceneManager::getRootScene() const {
|
||||||
if (sceneStack_.empty()) {
|
if (sceneStack_.empty()) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy stack to access bottom
|
|
||||||
auto tempStack = sceneStack_;
|
auto tempStack = sceneStack_;
|
||||||
Ptr<Scene> root;
|
Ref<Scene> root;
|
||||||
while (!tempStack.empty()) {
|
while (!tempStack.empty()) {
|
||||||
root = tempStack.top();
|
root = tempStack.top();
|
||||||
tempStack.pop();
|
tempStack.pop();
|
||||||
|
|
@ -162,13 +144,12 @@ Ptr<Scene> SceneManager::getRootScene() const {
|
||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ptr<Scene> SceneManager::getSceneByName(const std::string &name) const {
|
Ref<Scene> SceneManager::getSceneByName(const std::string& name) const {
|
||||||
auto it = namedScenes_.find(name);
|
auto it = namedScenes_.find(name);
|
||||||
if (it != namedScenes_.end()) {
|
if (it != namedScenes_.end()) {
|
||||||
return it->second;
|
return it->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Search in stack
|
|
||||||
auto tempStack = sceneStack_;
|
auto tempStack = sceneStack_;
|
||||||
while (!tempStack.empty()) {
|
while (!tempStack.empty()) {
|
||||||
auto scene = tempStack.top();
|
auto scene = tempStack.top();
|
||||||
|
|
@ -197,37 +178,31 @@ void SceneManager::render(Renderer &renderer) {
|
||||||
clearColor = sceneStack_.top()->getBackgroundColor();
|
clearColor = sceneStack_.top()->getBackgroundColor();
|
||||||
}
|
}
|
||||||
|
|
||||||
E2D_LOG_TRACE("SceneManager::render - beginFrame with color({}, {}, {})",
|
E2D_TRACE("SceneManager::render - beginFrame");
|
||||||
clearColor.r, clearColor.g, clearColor.b);
|
|
||||||
renderer.beginFrame(clearColor);
|
renderer.beginFrame(clearColor);
|
||||||
|
|
||||||
if (!sceneStack_.empty()) {
|
if (!sceneStack_.empty()) {
|
||||||
E2D_LOG_TRACE("SceneManager::render - rendering scene content");
|
E2D_TRACE("SceneManager::render - rendering scene content");
|
||||||
sceneStack_.top()->renderContent(renderer);
|
sceneStack_.top()->renderContent(renderer);
|
||||||
} else {
|
} else {
|
||||||
E2D_LOG_WARN("SceneManager::render - no scene to render");
|
E2D_WARN("SceneManager::render - no scene to render");
|
||||||
}
|
}
|
||||||
|
|
||||||
renderer.endFrame();
|
renderer.endFrame();
|
||||||
E2D_LOG_TRACE("SceneManager::render - endFrame");
|
E2D_TRACE("SceneManager::render - endFrame");
|
||||||
}
|
|
||||||
|
|
||||||
void SceneManager::collectRenderCommands(std::vector<RenderCommand> &commands) {
|
|
||||||
if (!sceneStack_.empty()) {
|
|
||||||
sceneStack_.top()->collectRenderCommands(commands, 0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SceneManager::end() {
|
void SceneManager::end() {
|
||||||
while (!sceneStack_.empty()) {
|
while (!sceneStack_.empty()) {
|
||||||
auto scene = sceneStack_.top();
|
auto scene = sceneStack_.top();
|
||||||
scene->onExit();
|
scene->onExit();
|
||||||
scene->onDetachFromScene();
|
|
||||||
sceneStack_.pop();
|
sceneStack_.pop();
|
||||||
}
|
}
|
||||||
namedScenes_.clear();
|
namedScenes_.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void SceneManager::purgeCachedScenes() { namedScenes_.clear(); }
|
void SceneManager::purgeCachedScenes() {
|
||||||
|
namedScenes_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace extra2d
|
} // namespace extra2d
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <extra2d/graphics/renderer.h>
|
#include <extra2d/render/renderer.h>
|
||||||
#include <extra2d/graphics/render_command.h>
|
|
||||||
#include <extra2d/scene/shape_node.h>
|
#include <extra2d/scene/shape_node.h>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
|
|
||||||
|
|
@ -9,19 +8,21 @@ namespace extra2d {
|
||||||
|
|
||||||
ShapeNode::ShapeNode() = default;
|
ShapeNode::ShapeNode() = default;
|
||||||
|
|
||||||
Ptr<ShapeNode> ShapeNode::create() { return makePtr<ShapeNode>(); }
|
Ref<ShapeNode> ShapeNode::create() {
|
||||||
|
return ptr::make<ShapeNode>();
|
||||||
|
}
|
||||||
|
|
||||||
Ptr<ShapeNode> ShapeNode::createPoint(const Vec2 &pos, const Color &color) {
|
Ref<ShapeNode> ShapeNode::createPoint(const Vec2& pos, const Color& color) {
|
||||||
auto node = makePtr<ShapeNode>();
|
auto node = ptr::make<ShapeNode>();
|
||||||
node->shapeType_ = ShapeType::Point;
|
node->shapeType_ = ShapeType::Point;
|
||||||
node->color_ = color;
|
node->color_ = color;
|
||||||
node->points_ = {pos};
|
node->points_ = {pos};
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ptr<ShapeNode> ShapeNode::createLine(const Vec2 &start, const Vec2 &end,
|
Ref<ShapeNode> ShapeNode::createLine(const Vec2& start, const Vec2& end,
|
||||||
const Color& color, float width) {
|
const Color& color, float width) {
|
||||||
auto node = makePtr<ShapeNode>();
|
auto node = ptr::make<ShapeNode>();
|
||||||
node->shapeType_ = ShapeType::Line;
|
node->shapeType_ = ShapeType::Line;
|
||||||
node->color_ = color;
|
node->color_ = color;
|
||||||
node->lineWidth_ = width;
|
node->lineWidth_ = width;
|
||||||
|
|
@ -29,52 +30,50 @@ Ptr<ShapeNode> ShapeNode::createLine(const Vec2 &start, const Vec2 &end,
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ptr<ShapeNode> ShapeNode::createRect(const Rect &rect, const Color &color,
|
Ref<ShapeNode> ShapeNode::createRect(const Rect& rect, const Color& color, float width) {
|
||||||
float width) {
|
auto node = ptr::make<ShapeNode>();
|
||||||
auto node = makePtr<ShapeNode>();
|
|
||||||
node->shapeType_ = ShapeType::Rect;
|
node->shapeType_ = ShapeType::Rect;
|
||||||
node->color_ = color;
|
node->color_ = color;
|
||||||
node->lineWidth_ = width;
|
node->lineWidth_ = width;
|
||||||
node->filled_ = false;
|
node->filled_ = false;
|
||||||
node->points_ = {
|
node->points_ = {
|
||||||
Vec2(rect.left(), rect.top()), Vec2(rect.right(), rect.top()),
|
Vec2(rect.left(), rect.top()),
|
||||||
Vec2(rect.right(), rect.bottom()), Vec2(rect.left(), rect.bottom())};
|
Vec2(rect.right(), rect.top()),
|
||||||
|
Vec2(rect.right(), rect.bottom()),
|
||||||
|
Vec2(rect.left(), rect.bottom())
|
||||||
|
};
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ptr<ShapeNode> ShapeNode::createFilledRect(const Rect &rect,
|
Ref<ShapeNode> ShapeNode::createFilledRect(const Rect& rect, const Color& color) {
|
||||||
const Color &color) {
|
|
||||||
auto node = createRect(rect, color, 0);
|
auto node = createRect(rect, color, 0);
|
||||||
node->filled_ = true;
|
node->filled_ = true;
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ptr<ShapeNode> ShapeNode::createCircle(const Vec2 ¢er, float radius,
|
Ref<ShapeNode> ShapeNode::createCircle(const Vec2& center, float radius,
|
||||||
const Color &color, int segments,
|
const Color& color, int segments, float width) {
|
||||||
float width) {
|
auto node = ptr::make<ShapeNode>();
|
||||||
auto node = makePtr<ShapeNode>();
|
|
||||||
node->shapeType_ = ShapeType::Circle;
|
node->shapeType_ = ShapeType::Circle;
|
||||||
node->color_ = color;
|
node->color_ = color;
|
||||||
node->lineWidth_ = width;
|
node->lineWidth_ = width;
|
||||||
node->segments_ = segments;
|
node->segments_ = segments;
|
||||||
node->filled_ = false;
|
node->filled_ = false;
|
||||||
node->points_ = {center};
|
node->points_ = {center};
|
||||||
// Store radius in a point for simplicity
|
|
||||||
node->addPoint(Vec2(radius, 0));
|
node->addPoint(Vec2(radius, 0));
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ptr<ShapeNode> ShapeNode::createFilledCircle(const Vec2 ¢er, float radius,
|
Ref<ShapeNode> ShapeNode::createFilledCircle(const Vec2& center, float radius,
|
||||||
const Color& color, int segments) {
|
const Color& color, int segments) {
|
||||||
auto node = createCircle(center, radius, color, segments, 0);
|
auto node = createCircle(center, radius, color, segments, 0);
|
||||||
node->filled_ = true;
|
node->filled_ = true;
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ptr<ShapeNode> ShapeNode::createTriangle(const Vec2 &p1, const Vec2 &p2,
|
Ref<ShapeNode> ShapeNode::createTriangle(const Vec2& p1, const Vec2& p2,
|
||||||
const Vec2 &p3, const Color &color,
|
const Vec2& p3, const Color& color, float width) {
|
||||||
float width) {
|
auto node = ptr::make<ShapeNode>();
|
||||||
auto node = makePtr<ShapeNode>();
|
|
||||||
node->shapeType_ = ShapeType::Triangle;
|
node->shapeType_ = ShapeType::Triangle;
|
||||||
node->color_ = color;
|
node->color_ = color;
|
||||||
node->lineWidth_ = width;
|
node->lineWidth_ = width;
|
||||||
|
|
@ -83,17 +82,16 @@ Ptr<ShapeNode> ShapeNode::createTriangle(const Vec2 &p1, const Vec2 &p2,
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ptr<ShapeNode> ShapeNode::createFilledTriangle(const Vec2 &p1, const Vec2 &p2,
|
Ref<ShapeNode> ShapeNode::createFilledTriangle(const Vec2& p1, const Vec2& p2,
|
||||||
const Vec2 &p3,
|
const Vec2& p3, const Color& color) {
|
||||||
const Color &color) {
|
|
||||||
auto node = createTriangle(p1, p2, p3, color, 0);
|
auto node = createTriangle(p1, p2, p3, color, 0);
|
||||||
node->filled_ = true;
|
node->filled_ = true;
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ptr<ShapeNode> ShapeNode::createPolygon(const std::vector<Vec2> &points,
|
Ref<ShapeNode> ShapeNode::createPolygon(const std::vector<Vec2>& points,
|
||||||
const Color& color, float width) {
|
const Color& color, float width) {
|
||||||
auto node = makePtr<ShapeNode>();
|
auto node = ptr::make<ShapeNode>();
|
||||||
node->shapeType_ = ShapeType::Polygon;
|
node->shapeType_ = ShapeType::Polygon;
|
||||||
node->color_ = color;
|
node->color_ = color;
|
||||||
node->lineWidth_ = width;
|
node->lineWidth_ = width;
|
||||||
|
|
@ -102,7 +100,7 @@ Ptr<ShapeNode> ShapeNode::createPolygon(const std::vector<Vec2> &points,
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ptr<ShapeNode> ShapeNode::createFilledPolygon(const std::vector<Vec2> &points,
|
Ref<ShapeNode> ShapeNode::createFilledPolygon(const std::vector<Vec2>& points,
|
||||||
const Color& color) {
|
const Color& color) {
|
||||||
auto node = createPolygon(points, color, 0);
|
auto node = createPolygon(points, color, 0);
|
||||||
node->filled_ = true;
|
node->filled_ = true;
|
||||||
|
|
@ -134,8 +132,7 @@ Rect ShapeNode::getBoundingBox() const {
|
||||||
if (shapeType_ == ShapeType::Circle && points_.size() >= 2) {
|
if (shapeType_ == ShapeType::Circle && points_.size() >= 2) {
|
||||||
float radius = std::abs(points_[1].x);
|
float radius = std::abs(points_[1].x);
|
||||||
Vec2 center = points_[0] + offset;
|
Vec2 center = points_[0] + offset;
|
||||||
return Rect(center.x - radius, center.y - radius, radius * 2.0f,
|
return Rect(center.x - radius, center.y - radius, radius * 2.0f, radius * 2.0f);
|
||||||
radius * 2.0f);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
float minX = std::numeric_limits<float>::infinity();
|
float minX = std::numeric_limits<float>::infinity();
|
||||||
|
|
@ -152,8 +149,7 @@ Rect ShapeNode::getBoundingBox() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
float inflate = 0.0f;
|
float inflate = 0.0f;
|
||||||
if (!filled_ &&
|
if (!filled_ && (shapeType_ == ShapeType::Line || shapeType_ == ShapeType::Rect ||
|
||||||
(shapeType_ == ShapeType::Line || shapeType_ == ShapeType::Rect ||
|
|
||||||
shapeType_ == ShapeType::Triangle || shapeType_ == ShapeType::Polygon ||
|
shapeType_ == ShapeType::Triangle || shapeType_ == ShapeType::Polygon ||
|
||||||
shapeType_ == ShapeType::Point)) {
|
shapeType_ == ShapeType::Point)) {
|
||||||
inflate = std::max(0.0f, lineWidth_ * 0.5f);
|
inflate = std::max(0.0f, lineWidth_ * 0.5f);
|
||||||
|
|
@ -182,23 +178,19 @@ void ShapeNode::onDraw(Renderer &renderer) {
|
||||||
|
|
||||||
case ShapeType::Line:
|
case ShapeType::Line:
|
||||||
if (points_.size() >= 2) {
|
if (points_.size() >= 2) {
|
||||||
renderer.drawLine(points_[0] + offset, points_[1] + offset, color_,
|
renderer.drawLine(points_[0] + offset, points_[1] + offset, color_, lineWidth_);
|
||||||
lineWidth_);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ShapeType::Rect:
|
case ShapeType::Rect:
|
||||||
if (points_.size() >= 4) {
|
if (points_.size() >= 4) {
|
||||||
if (filled_) {
|
|
||||||
Rect rect(points_[0].x, points_[0].y, points_[2].x - points_[0].x,
|
Rect rect(points_[0].x, points_[0].y, points_[2].x - points_[0].x,
|
||||||
points_[2].y - points_[0].y);
|
points_[2].y - points_[0].y);
|
||||||
renderer.fillRect(Rect(rect.origin + offset, rect.size), color_);
|
Rect worldRect(rect.origin + offset, rect.size);
|
||||||
|
if (filled_) {
|
||||||
|
renderer.fillRect(worldRect, color_);
|
||||||
} else {
|
} else {
|
||||||
for (size_t i = 0; i < points_.size(); ++i) {
|
renderer.drawRect(worldRect, color_, lineWidth_);
|
||||||
Vec2 start = points_[i] + offset;
|
|
||||||
Vec2 end = points_[(i + 1) % points_.size()] + offset;
|
|
||||||
renderer.drawLine(start, end, color_, lineWidth_);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
@ -209,8 +201,7 @@ void ShapeNode::onDraw(Renderer &renderer) {
|
||||||
if (filled_) {
|
if (filled_) {
|
||||||
renderer.fillCircle(points_[0] + offset, radius, color_, segments_);
|
renderer.fillCircle(points_[0] + offset, radius, color_, segments_);
|
||||||
} else {
|
} else {
|
||||||
renderer.drawCircle(points_[0] + offset, radius, color_, segments_,
|
renderer.drawCircle(points_[0] + offset, radius, color_, segments_, lineWidth_);
|
||||||
lineWidth_);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
@ -223,9 +214,7 @@ void ShapeNode::onDraw(Renderer &renderer) {
|
||||||
if (filled_) {
|
if (filled_) {
|
||||||
renderer.fillTriangle(p1, p2, p3, color_);
|
renderer.fillTriangle(p1, p2, p3, color_);
|
||||||
} else {
|
} else {
|
||||||
renderer.drawLine(p1, p2, color_, lineWidth_);
|
renderer.drawTriangle(p1, p2, p3, color_, lineWidth_);
|
||||||
renderer.drawLine(p2, p3, color_, lineWidth_);
|
|
||||||
renderer.drawLine(p3, p1, color_, lineWidth_);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
@ -248,101 +237,4 @@ void ShapeNode::onDraw(Renderer &renderer) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ShapeNode::generateRenderCommand(std::vector<RenderCommand> &commands,
|
|
||||||
int zOrder) {
|
|
||||||
if (points_.empty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Vec2 offset = getPosition();
|
|
||||||
RenderCommand cmd;
|
|
||||||
cmd.layer = zOrder;
|
|
||||||
|
|
||||||
switch (shapeType_) {
|
|
||||||
case ShapeType::Point:
|
|
||||||
if (!points_.empty()) {
|
|
||||||
cmd.type = RenderCommandType::FilledCircle;
|
|
||||||
cmd.data =
|
|
||||||
CircleCommandData{points_[0] + offset, lineWidth_ * 0.5f, color_, 8, 0.0f, true};
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ShapeType::Line:
|
|
||||||
if (points_.size() >= 2) {
|
|
||||||
cmd.type = RenderCommandType::Line;
|
|
||||||
cmd.data = LineCommandData{points_[0] + offset, points_[1] + offset, color_,
|
|
||||||
lineWidth_};
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ShapeType::Rect:
|
|
||||||
if (points_.size() >= 4) {
|
|
||||||
if (filled_) {
|
|
||||||
cmd.type = RenderCommandType::FilledRect;
|
|
||||||
Rect rect(points_[0].x, points_[0].y, points_[2].x - points_[0].x,
|
|
||||||
points_[2].y - points_[0].y);
|
|
||||||
cmd.data =
|
|
||||||
RectCommandData{Rect(rect.origin + offset, rect.size), color_, 0.0f, true};
|
|
||||||
} else {
|
|
||||||
cmd.type = RenderCommandType::Rect;
|
|
||||||
Rect rect(points_[0].x, points_[0].y, points_[2].x - points_[0].x,
|
|
||||||
points_[2].y - points_[0].y);
|
|
||||||
cmd.data =
|
|
||||||
RectCommandData{Rect(rect.origin + offset, rect.size), color_, lineWidth_, false};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ShapeType::Circle:
|
|
||||||
if (points_.size() >= 2) {
|
|
||||||
float radius = points_[1].x;
|
|
||||||
if (filled_) {
|
|
||||||
cmd.type = RenderCommandType::FilledCircle;
|
|
||||||
cmd.data =
|
|
||||||
CircleCommandData{points_[0] + offset, radius, color_, segments_, 0.0f, true};
|
|
||||||
} else {
|
|
||||||
cmd.type = RenderCommandType::Circle;
|
|
||||||
cmd.data = CircleCommandData{points_[0] + offset, radius, color_, segments_,
|
|
||||||
lineWidth_, false};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ShapeType::Triangle:
|
|
||||||
if (points_.size() >= 3) {
|
|
||||||
Vec2 p1 = points_[0] + offset;
|
|
||||||
Vec2 p2 = points_[1] + offset;
|
|
||||||
Vec2 p3 = points_[2] + offset;
|
|
||||||
if (filled_) {
|
|
||||||
cmd.type = RenderCommandType::FilledTriangle;
|
|
||||||
cmd.data = TriangleCommandData{p1, p2, p3, color_, 0.0f, true};
|
|
||||||
} else {
|
|
||||||
cmd.type = RenderCommandType::Triangle;
|
|
||||||
cmd.data = TriangleCommandData{p1, p2, p3, color_, lineWidth_, false};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ShapeType::Polygon:
|
|
||||||
if (!points_.empty()) {
|
|
||||||
std::vector<Vec2> transformedPoints;
|
|
||||||
transformedPoints.reserve(points_.size());
|
|
||||||
for (const auto &p : points_) {
|
|
||||||
transformedPoints.push_back(p + offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (filled_) {
|
|
||||||
cmd.type = RenderCommandType::FilledPolygon;
|
|
||||||
cmd.data = PolygonCommandData{transformedPoints, color_, 0.0f, true};
|
|
||||||
} else {
|
|
||||||
cmd.type = RenderCommandType::Polygon;
|
|
||||||
cmd.data = PolygonCommandData{transformedPoints, color_, lineWidth_, false};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
commands.push_back(std::move(cmd));
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace extra2d
|
} // namespace extra2d
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,22 @@
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <extra2d/graphics/renderer.h>
|
#include <extra2d/render/renderer.h>
|
||||||
#include <extra2d/graphics/render_command.h>
|
#include <extra2d/render/texture.h>
|
||||||
#include <extra2d/graphics/texture.h>
|
|
||||||
#include <extra2d/scene/sprite.h>
|
#include <extra2d/scene/sprite.h>
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
Sprite::Sprite() = default;
|
Sprite::Sprite() = default;
|
||||||
|
|
||||||
Sprite::Sprite(Ptr<Texture> texture) { setTexture(texture); }
|
Sprite::Sprite(Ref<Texture> texture) {
|
||||||
|
setTexture(texture);
|
||||||
|
}
|
||||||
|
|
||||||
void Sprite::setTexture(Ptr<Texture> texture) {
|
void Sprite::setTexture(Ref<Texture> texture) {
|
||||||
texture_ = texture;
|
texture_ = texture;
|
||||||
if (texture_) {
|
if (texture_) {
|
||||||
textureRect_ = Rect(0, 0, static_cast<float>(texture_->getWidth()),
|
textureRect_ = Rect(0, 0, static_cast<float>(texture_->width()),
|
||||||
static_cast<float>(texture_->getHeight()));
|
static_cast<float>(texture_->height()));
|
||||||
}
|
}
|
||||||
updateSpatialIndex();
|
updateSpatialIndex();
|
||||||
}
|
}
|
||||||
|
|
@ -25,20 +26,28 @@ void Sprite::setTextureRect(const Rect &rect) {
|
||||||
updateSpatialIndex();
|
updateSpatialIndex();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Sprite::setColor(const Color &color) { color_ = color; }
|
void Sprite::setColor(const Color& color) {
|
||||||
|
color_ = color;
|
||||||
void Sprite::setFlipX(bool flip) { flipX_ = flip; }
|
|
||||||
|
|
||||||
void Sprite::setFlipY(bool flip) { flipY_ = flip; }
|
|
||||||
|
|
||||||
Ptr<Sprite> Sprite::create() { return makePtr<Sprite>(); }
|
|
||||||
|
|
||||||
Ptr<Sprite> Sprite::create(Ptr<Texture> texture) {
|
|
||||||
return makePtr<Sprite>(texture);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ptr<Sprite> Sprite::create(Ptr<Texture> texture, const Rect &rect) {
|
void Sprite::setFlipX(bool flip) {
|
||||||
auto sprite = makePtr<Sprite>(texture);
|
flipX_ = flip;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sprite::setFlipY(bool flip) {
|
||||||
|
flipY_ = flip;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ref<Sprite> Sprite::create() {
|
||||||
|
return ptr::make<Sprite>();
|
||||||
|
}
|
||||||
|
|
||||||
|
Ref<Sprite> Sprite::create(Ref<Texture> texture) {
|
||||||
|
return ptr::make<Sprite>(texture);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ref<Sprite> Sprite::create(Ref<Texture> texture, const Rect& rect) {
|
||||||
|
auto sprite = ptr::make<Sprite>(texture);
|
||||||
sprite->setTextureRect(rect);
|
sprite->setTextureRect(rect);
|
||||||
return sprite;
|
return sprite;
|
||||||
}
|
}
|
||||||
|
|
@ -72,29 +81,21 @@ void Sprite::onDraw(Renderer &renderer) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate destination rectangle based on texture rect
|
|
||||||
float width = textureRect_.width();
|
float width = textureRect_.width();
|
||||||
float height = textureRect_.height();
|
float height = textureRect_.height();
|
||||||
|
|
||||||
// 使用世界变换来获取最终的位置
|
|
||||||
auto worldTransform = getWorldTransform();
|
auto worldTransform = getWorldTransform();
|
||||||
|
|
||||||
// 从世界变换矩阵中提取位置(第四列)
|
|
||||||
float worldX = worldTransform[3][0];
|
float worldX = worldTransform[3][0];
|
||||||
float worldY = worldTransform[3][1];
|
float worldY = worldTransform[3][1];
|
||||||
|
|
||||||
// 从世界变换矩阵中提取缩放
|
float worldScaleX = glm::length(glm::vec2(worldTransform[0][0], worldTransform[0][1]));
|
||||||
float worldScaleX =
|
float worldScaleY = glm::length(glm::vec2(worldTransform[1][0], worldTransform[1][1]));
|
||||||
glm::length(glm::vec2(worldTransform[0][0], worldTransform[0][1]));
|
|
||||||
float worldScaleY =
|
|
||||||
glm::length(glm::vec2(worldTransform[1][0], worldTransform[1][1]));
|
|
||||||
|
|
||||||
auto anchor = getAnchor();
|
auto anchor = getAnchor();
|
||||||
|
|
||||||
// 锚点由 Renderer 在绘制时处理,这里只传递位置和尺寸
|
|
||||||
Rect destRect(worldX, worldY, width * worldScaleX, height * worldScaleY);
|
Rect destRect(worldX, worldY, width * worldScaleX, height * worldScaleY);
|
||||||
|
|
||||||
// Adjust source rect for flipping
|
|
||||||
Rect srcRect = textureRect_;
|
Rect srcRect = textureRect_;
|
||||||
if (flipX_) {
|
if (flipX_) {
|
||||||
srcRect.origin.x = srcRect.right();
|
srcRect.origin.x = srcRect.right();
|
||||||
|
|
@ -105,63 +106,9 @@ void Sprite::onDraw(Renderer &renderer) {
|
||||||
srcRect.size.height = -srcRect.size.height;
|
srcRect.size.height = -srcRect.size.height;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 从世界变换矩阵中提取旋转角度
|
|
||||||
float worldRotation = std::atan2(worldTransform[0][1], worldTransform[0][0]);
|
float worldRotation = std::atan2(worldTransform[0][1], worldTransform[0][0]);
|
||||||
|
|
||||||
renderer.drawSprite(*texture_, destRect, srcRect, color_, worldRotation,
|
renderer.drawSprite(*texture_, destRect, srcRect, color_, worldRotation, anchor);
|
||||||
anchor);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Sprite::generateRenderCommand(std::vector<RenderCommand> &commands,
|
|
||||||
int zOrder) {
|
|
||||||
if (!texture_ || !texture_->isValid()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 计算目标矩形(与 onDraw 一致,使用世界变换)
|
|
||||||
float width = textureRect_.width();
|
|
||||||
float height = textureRect_.height();
|
|
||||||
|
|
||||||
// 使用世界变换来获取最终的位置
|
|
||||||
auto worldTransform = getWorldTransform();
|
|
||||||
|
|
||||||
// 从世界变换矩阵中提取位置(第四列)
|
|
||||||
float worldX = worldTransform[3][0];
|
|
||||||
float worldY = worldTransform[3][1];
|
|
||||||
|
|
||||||
// 从世界变换矩阵中提取缩放
|
|
||||||
float worldScaleX =
|
|
||||||
glm::length(glm::vec2(worldTransform[0][0], worldTransform[0][1]));
|
|
||||||
float worldScaleY =
|
|
||||||
glm::length(glm::vec2(worldTransform[1][0], worldTransform[1][1]));
|
|
||||||
|
|
||||||
auto anchor = getAnchor();
|
|
||||||
|
|
||||||
// 锚点由 Renderer 在绘制时处理,这里只传递位置和尺寸
|
|
||||||
Rect destRect(worldX, worldY, width * worldScaleX, height * worldScaleY);
|
|
||||||
|
|
||||||
// 调整源矩形(翻转)
|
|
||||||
Rect srcRect = textureRect_;
|
|
||||||
if (flipX_) {
|
|
||||||
srcRect.origin.x = srcRect.right();
|
|
||||||
srcRect.size.width = -srcRect.size.width;
|
|
||||||
}
|
|
||||||
if (flipY_) {
|
|
||||||
srcRect.origin.y = srcRect.bottom();
|
|
||||||
srcRect.size.height = -srcRect.size.height;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 从世界变换矩阵中提取旋转角度
|
|
||||||
float worldRotation = std::atan2(worldTransform[0][1], worldTransform[0][0]);
|
|
||||||
|
|
||||||
// 创建渲染命令
|
|
||||||
RenderCommand cmd;
|
|
||||||
cmd.type = RenderCommandType::Sprite;
|
|
||||||
cmd.layer = zOrder;
|
|
||||||
cmd.data = SpriteCommandData{texture_.get(), destRect, srcRect, color_,
|
|
||||||
worldRotation, anchor, 0};
|
|
||||||
|
|
||||||
commands.push_back(std::move(cmd));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace extra2d
|
} // namespace extra2d
|
||||||
|
|
|
||||||
|
|
@ -1,46 +1,89 @@
|
||||||
#include <chrono>
|
|
||||||
#include <cstdarg>
|
#include <cstdarg>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <extra2d/services/logger_service.h>
|
#include <extra2d/services/logger_service.h>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
#include <SDL.h>
|
||||||
#ifdef _WIN32
|
|
||||||
#include <windows.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
static bool enableWindowsConsoleFeatures() {
|
|
||||||
bool success = true;
|
|
||||||
|
|
||||||
HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
|
|
||||||
if (hOut != INVALID_HANDLE_VALUE) {
|
|
||||||
DWORD dwMode = 0;
|
|
||||||
if (GetConsoleMode(hOut, &dwMode)) {
|
|
||||||
dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
|
|
||||||
if (!SetConsoleMode(hOut, dwMode)) {
|
|
||||||
success = false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
success = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SetConsoleOutputCP(CP_UTF8);
|
|
||||||
SetConsoleCP(CP_UTF8);
|
|
||||||
|
|
||||||
return success;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool g_windowsConsoleInitialized = false;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
class ConsoleLogger::Impl {
|
class ConsoleLogger::Impl {
|
||||||
public:
|
public:
|
||||||
std::mutex mutex_;
|
std::mutex mutex_;
|
||||||
|
SDL_LogPriority sdlPriorities_[7];
|
||||||
|
bool customOutputSet_ = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static SDL_LogPriority toSDLPriority(LogLevel lvl) {
|
||||||
|
switch (lvl) {
|
||||||
|
case LogLevel::Trace: return SDL_LOG_PRIORITY_VERBOSE;
|
||||||
|
case LogLevel::Debug: return SDL_LOG_PRIORITY_DEBUG;
|
||||||
|
case LogLevel::Info: return SDL_LOG_PRIORITY_INFO;
|
||||||
|
case LogLevel::Registry: return SDL_LOG_PRIORITY_INFO;
|
||||||
|
case LogLevel::Warn: return SDL_LOG_PRIORITY_WARN;
|
||||||
|
case LogLevel::Error: return SDL_LOG_PRIORITY_ERROR;
|
||||||
|
case LogLevel::Fatal: return SDL_LOG_PRIORITY_CRITICAL;
|
||||||
|
default: return SDL_LOG_PRIORITY_INFO;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static LogLevel fromSDLPriority(SDL_LogPriority priority) {
|
||||||
|
switch (priority) {
|
||||||
|
case SDL_LOG_PRIORITY_VERBOSE: return LogLevel::Trace;
|
||||||
|
case SDL_LOG_PRIORITY_DEBUG: return LogLevel::Debug;
|
||||||
|
case SDL_LOG_PRIORITY_INFO: return LogLevel::Info;
|
||||||
|
case SDL_LOG_PRIORITY_WARN: return LogLevel::Warn;
|
||||||
|
case SDL_LOG_PRIORITY_ERROR: return LogLevel::Error;
|
||||||
|
case SDL_LOG_PRIORITY_CRITICAL: return LogLevel::Fatal;
|
||||||
|
default: return LogLevel::Info;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static ConsoleLogger* g_loggerInstance = nullptr;
|
||||||
|
|
||||||
|
static void SDLCALL sdlLogOutputFunction(void* userdata, int category,
|
||||||
|
SDL_LogPriority priority,
|
||||||
|
const char* message) {
|
||||||
|
if (!g_loggerInstance) return;
|
||||||
|
|
||||||
|
ConsoleLogger* logger = static_cast<ConsoleLogger*>(g_loggerInstance);
|
||||||
|
LogLevel lvl = fromSDLPriority(priority);
|
||||||
|
|
||||||
|
if (!logger->enabled(lvl)) return;
|
||||||
|
|
||||||
|
const char* levelStr = logger->levelString(lvl);
|
||||||
|
const char* categoryStr = "";
|
||||||
|
switch (category) {
|
||||||
|
case SDL_LOG_CATEGORY_APPLICATION: categoryStr = "APP"; break;
|
||||||
|
case SDL_LOG_CATEGORY_ERROR: categoryStr = "ERR"; break;
|
||||||
|
case SDL_LOG_CATEGORY_ASSERT: categoryStr = "ASR"; break;
|
||||||
|
case SDL_LOG_CATEGORY_SYSTEM: categoryStr = "SYS"; break;
|
||||||
|
case SDL_LOG_CATEGORY_AUDIO: categoryStr = "AUD"; break;
|
||||||
|
case SDL_LOG_CATEGORY_VIDEO: categoryStr = "VID"; break;
|
||||||
|
case SDL_LOG_CATEGORY_RENDER: categoryStr = "RDR"; break;
|
||||||
|
case SDL_LOG_CATEGORY_INPUT: categoryStr = "INP"; break;
|
||||||
|
case SDL_LOG_CATEGORY_TEST: categoryStr = "TST"; break;
|
||||||
|
default: categoryStr = "GEN"; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Uint32 ticks = SDL_GetTicks();
|
||||||
|
Uint32 seconds = ticks / 1000;
|
||||||
|
Uint32 minutes = seconds / 60;
|
||||||
|
Uint32 hours = minutes / 60;
|
||||||
|
|
||||||
|
if (logger->colors()) {
|
||||||
|
std::string color = logger->ansiColor(lvl);
|
||||||
|
const char* reset = "\033[0m";
|
||||||
|
printf("%s[%02u:%02u:%02u.%03u] [%s][%s] %s%s\n",
|
||||||
|
color.c_str(),
|
||||||
|
hours % 24, minutes % 60, seconds % 60, ticks % 1000,
|
||||||
|
levelStr, categoryStr, message, reset);
|
||||||
|
} else {
|
||||||
|
printf("[%02u:%02u:%02u.%03u] [%s][%s] %s\n",
|
||||||
|
hours % 24, minutes % 60, seconds % 60, ticks % 1000,
|
||||||
|
levelStr, categoryStr, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ConsoleLogger::ConsoleLogger()
|
ConsoleLogger::ConsoleLogger()
|
||||||
: level_(LogLevel::Info), colors_(true),
|
: level_(LogLevel::Info), colors_(true),
|
||||||
impl_(std::make_unique<Impl>()) {
|
impl_(std::make_unique<Impl>()) {
|
||||||
|
|
@ -55,124 +98,155 @@ ConsoleLogger::ConsoleLogger()
|
||||||
levelColors_[static_cast<int>(LogLevel::Error)] = LogColor::Red();
|
levelColors_[static_cast<int>(LogLevel::Error)] = LogColor::Red();
|
||||||
levelColors_[static_cast<int>(LogLevel::Fatal)] = LogColor::Magenta();
|
levelColors_[static_cast<int>(LogLevel::Fatal)] = LogColor::Magenta();
|
||||||
|
|
||||||
#ifdef _WIN32
|
for (int i = 0; i < 7; ++i) {
|
||||||
if (!g_windowsConsoleInitialized) {
|
impl_->sdlPriorities_[i] = toSDLPriority(static_cast<LogLevel>(i));
|
||||||
g_windowsConsoleInitialized = enableWindowsConsoleFeatures();
|
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ConsoleLogger::~ConsoleLogger() = default;
|
ConsoleLogger::~ConsoleLogger() {
|
||||||
|
if (impl_->customOutputSet_) {
|
||||||
|
SDL_LogSetOutputFunction(nullptr, nullptr);
|
||||||
|
}
|
||||||
|
g_loggerInstance = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
bool ConsoleLogger::init() {
|
bool ConsoleLogger::init() {
|
||||||
|
g_loggerInstance = this;
|
||||||
|
|
||||||
|
SDL_LogSetOutputFunction(sdlLogOutputFunction, this);
|
||||||
|
impl_->customOutputSet_ = true;
|
||||||
|
|
||||||
|
SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION, toSDLPriority(level_));
|
||||||
|
|
||||||
setState(ServiceState::Running);
|
setState(ServiceState::Running);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConsoleLogger::shutdown() { setState(ServiceState::Stopped); }
|
void ConsoleLogger::shutdown() {
|
||||||
|
if (impl_->customOutputSet_) {
|
||||||
|
SDL_LogSetOutputFunction(nullptr, nullptr);
|
||||||
|
impl_->customOutputSet_ = false;
|
||||||
|
}
|
||||||
|
g_loggerInstance = nullptr;
|
||||||
|
setState(ServiceState::Stopped);
|
||||||
|
}
|
||||||
|
|
||||||
void ConsoleLogger::level(LogLevel lvl) { level_ = lvl; }
|
void ConsoleLogger::level(LogLevel lvl) {
|
||||||
|
level_ = lvl;
|
||||||
|
SDL_LogPriority priority = toSDLPriority(lvl);
|
||||||
|
SDL_LogSetAllPriority(priority);
|
||||||
|
}
|
||||||
|
|
||||||
LogLevel ConsoleLogger::level() const { return level_; }
|
LogLevel ConsoleLogger::level() const {
|
||||||
|
return level_;
|
||||||
|
}
|
||||||
|
|
||||||
bool ConsoleLogger::enabled(LogLevel lvl) const {
|
bool ConsoleLogger::enabled(LogLevel lvl) const {
|
||||||
return static_cast<int>(lvl) >= static_cast<int>(level_);
|
return static_cast<int>(lvl) >= static_cast<int>(level_);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConsoleLogger::log(LogLevel lvl, const char *fmt, ...) {
|
void ConsoleLogger::log(LogLevel lvl, const char *fmt, ...) {
|
||||||
if (!enabled(lvl))
|
if (!enabled(lvl)) return;
|
||||||
return;
|
|
||||||
|
|
||||||
char buffer[1024];
|
char buffer[2048];
|
||||||
va_list args;
|
va_list args;
|
||||||
va_start(args, fmt);
|
va_start(args, fmt);
|
||||||
vsnprintf(buffer, sizeof(buffer), fmt, args);
|
vsnprintf(buffer, sizeof(buffer), fmt, args);
|
||||||
va_end(args);
|
va_end(args);
|
||||||
|
|
||||||
output(lvl, buffer);
|
SDL_LogPriority priority = toSDLPriority(lvl);
|
||||||
|
SDL_LogMessage(SDL_LOG_CATEGORY_APPLICATION, priority, "%s", buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConsoleLogger::log(LogLevel lvl, const std::string &msg) {
|
void ConsoleLogger::log(LogLevel lvl, const std::string &msg) {
|
||||||
if (!enabled(lvl))
|
if (!enabled(lvl)) return;
|
||||||
return;
|
|
||||||
output(lvl, msg.c_str());
|
SDL_LogPriority priority = toSDLPriority(lvl);
|
||||||
|
SDL_LogMessage(SDL_LOG_CATEGORY_APPLICATION, priority, "%s", msg.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConsoleLogger::trace(const char *fmt, ...) {
|
void ConsoleLogger::trace(const char *fmt, ...) {
|
||||||
if (!enabled(LogLevel::Trace))
|
if (!enabled(LogLevel::Trace)) return;
|
||||||
return;
|
|
||||||
char buffer[1024];
|
char buffer[2048];
|
||||||
va_list args;
|
va_list args;
|
||||||
va_start(args, fmt);
|
va_start(args, fmt);
|
||||||
vsnprintf(buffer, sizeof(buffer), fmt, args);
|
vsnprintf(buffer, sizeof(buffer), fmt, args);
|
||||||
va_end(args);
|
va_end(args);
|
||||||
output(LogLevel::Trace, buffer);
|
|
||||||
|
SDL_LogVerbose(SDL_LOG_CATEGORY_APPLICATION, "%s", buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConsoleLogger::debug(const char *fmt, ...) {
|
void ConsoleLogger::debug(const char *fmt, ...) {
|
||||||
if (!enabled(LogLevel::Debug))
|
if (!enabled(LogLevel::Debug)) return;
|
||||||
return;
|
|
||||||
char buffer[1024];
|
char buffer[2048];
|
||||||
va_list args;
|
va_list args;
|
||||||
va_start(args, fmt);
|
va_start(args, fmt);
|
||||||
vsnprintf(buffer, sizeof(buffer), fmt, args);
|
vsnprintf(buffer, sizeof(buffer), fmt, args);
|
||||||
va_end(args);
|
va_end(args);
|
||||||
output(LogLevel::Debug, buffer);
|
|
||||||
|
SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "%s", buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConsoleLogger::info(const char *fmt, ...) {
|
void ConsoleLogger::info(const char *fmt, ...) {
|
||||||
if (!enabled(LogLevel::Info))
|
if (!enabled(LogLevel::Info)) return;
|
||||||
return;
|
|
||||||
char buffer[1024];
|
char buffer[2048];
|
||||||
va_list args;
|
va_list args;
|
||||||
va_start(args, fmt);
|
va_start(args, fmt);
|
||||||
vsnprintf(buffer, sizeof(buffer), fmt, args);
|
vsnprintf(buffer, sizeof(buffer), fmt, args);
|
||||||
va_end(args);
|
va_end(args);
|
||||||
output(LogLevel::Info, buffer);
|
|
||||||
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "%s", buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConsoleLogger::registry(const char *fmt, ...) {
|
void ConsoleLogger::registry(const char *fmt, ...) {
|
||||||
if (!enabled(LogLevel::Registry))
|
if (!enabled(LogLevel::Registry)) return;
|
||||||
return;
|
|
||||||
char buffer[1024];
|
char buffer[2048];
|
||||||
va_list args;
|
va_list args;
|
||||||
va_start(args, fmt);
|
va_start(args, fmt);
|
||||||
vsnprintf(buffer, sizeof(buffer), fmt, args);
|
vsnprintf(buffer, sizeof(buffer), fmt, args);
|
||||||
va_end(args);
|
va_end(args);
|
||||||
output(LogLevel::Registry, buffer);
|
|
||||||
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "[REGISTRY] %s", buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConsoleLogger::warn(const char *fmt, ...) {
|
void ConsoleLogger::warn(const char *fmt, ...) {
|
||||||
if (!enabled(LogLevel::Warn))
|
if (!enabled(LogLevel::Warn)) return;
|
||||||
return;
|
|
||||||
char buffer[1024];
|
char buffer[2048];
|
||||||
va_list args;
|
va_list args;
|
||||||
va_start(args, fmt);
|
va_start(args, fmt);
|
||||||
vsnprintf(buffer, sizeof(buffer), fmt, args);
|
vsnprintf(buffer, sizeof(buffer), fmt, args);
|
||||||
va_end(args);
|
va_end(args);
|
||||||
output(LogLevel::Warn, buffer);
|
|
||||||
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "%s", buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConsoleLogger::error(const char *fmt, ...) {
|
void ConsoleLogger::error(const char *fmt, ...) {
|
||||||
if (!enabled(LogLevel::Error))
|
if (!enabled(LogLevel::Error)) return;
|
||||||
return;
|
|
||||||
char buffer[1024];
|
char buffer[2048];
|
||||||
va_list args;
|
va_list args;
|
||||||
va_start(args, fmt);
|
va_start(args, fmt);
|
||||||
vsnprintf(buffer, sizeof(buffer), fmt, args);
|
vsnprintf(buffer, sizeof(buffer), fmt, args);
|
||||||
va_end(args);
|
va_end(args);
|
||||||
output(LogLevel::Error, buffer);
|
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "%s", buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConsoleLogger::fatal(const char *fmt, ...) {
|
void ConsoleLogger::fatal(const char *fmt, ...) {
|
||||||
if (!enabled(LogLevel::Fatal))
|
if (!enabled(LogLevel::Fatal)) return;
|
||||||
return;
|
|
||||||
char buffer[1024];
|
char buffer[2048];
|
||||||
va_list args;
|
va_list args;
|
||||||
va_start(args, fmt);
|
va_start(args, fmt);
|
||||||
vsnprintf(buffer, sizeof(buffer), fmt, args);
|
vsnprintf(buffer, sizeof(buffer), fmt, args);
|
||||||
va_end(args);
|
va_end(args);
|
||||||
output(LogLevel::Fatal, buffer);
|
|
||||||
|
SDL_LogCritical(SDL_LOG_CATEGORY_APPLICATION, "%s", buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConsoleLogger::levelColor(LogLevel lvl, const LogColor &c) {
|
void ConsoleLogger::levelColor(LogLevel lvl, const LogColor &c) {
|
||||||
|
|
@ -201,53 +275,16 @@ std::string ConsoleLogger::ansiColor(LogLevel lvl) {
|
||||||
return std::string(buf);
|
return std::string(buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConsoleLogger::output(LogLevel lvl, const char *msg) {
|
|
||||||
std::lock_guard<std::mutex> lock(impl_->mutex_);
|
|
||||||
|
|
||||||
auto now = std::chrono::system_clock::now();
|
|
||||||
auto time = std::chrono::system_clock::to_time_t(now);
|
|
||||||
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
|
|
||||||
now.time_since_epoch()) %
|
|
||||||
1000;
|
|
||||||
|
|
||||||
std::tm tm;
|
|
||||||
#ifdef _WIN32
|
|
||||||
localtime_s(&tm, &time);
|
|
||||||
#else
|
|
||||||
localtime_r(&time, &tm);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
const char *levelStr = levelString(lvl);
|
|
||||||
const char *reset = "\033[0m";
|
|
||||||
|
|
||||||
if (colors_) {
|
|
||||||
std::string color = ansiColor(lvl);
|
|
||||||
printf("%s[%02d:%02d:%02d.%03d] [%s] %s%s\n", color.c_str(), tm.tm_hour,
|
|
||||||
tm.tm_min, tm.tm_sec, (int)ms.count(), levelStr, msg, reset);
|
|
||||||
} else {
|
|
||||||
printf("[%02d:%02d:%02d.%03d] [%s] %s\n", tm.tm_hour, tm.tm_min, tm.tm_sec,
|
|
||||||
(int)ms.count(), levelStr, msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const char *ConsoleLogger::levelString(LogLevel lvl) {
|
const char *ConsoleLogger::levelString(LogLevel lvl) {
|
||||||
switch (lvl) {
|
switch (lvl) {
|
||||||
case LogLevel::Trace:
|
case LogLevel::Trace: return "TRACE";
|
||||||
return "TRACE";
|
case LogLevel::Debug: return "DEBUG";
|
||||||
case LogLevel::Debug:
|
case LogLevel::Info: return "INFO";
|
||||||
return "DEBUG";
|
case LogLevel::Registry: return "REGISTRY";
|
||||||
case LogLevel::Info:
|
case LogLevel::Warn: return "WARN";
|
||||||
return "INFO";
|
case LogLevel::Error: return "ERROR";
|
||||||
case LogLevel::Registry:
|
case LogLevel::Fatal: return "FATAL";
|
||||||
return "REGISTRY";
|
default: return "UNKNOWN";
|
||||||
case LogLevel::Warn:
|
|
||||||
return "WARN";
|
|
||||||
case LogLevel::Error:
|
|
||||||
return "ERROR";
|
|
||||||
case LogLevel::Fatal:
|
|
||||||
return "FATAL";
|
|
||||||
default:
|
|
||||||
return "UNKNOWN";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
#include <extra2d/utils/data.h>
|
#include <extra2d/utils/data.h>
|
||||||
#include <extra2d/utils/logger.h>
|
#include <extra2d/services/logger_service.h>
|
||||||
#include <simpleini/SimpleIni.h>
|
#include <simpleini/SimpleIni.h>
|
||||||
|
|
||||||
// Switch 平台特定头文件
|
// Switch 平台特定头文件
|
||||||
|
|
|
||||||
|
|
@ -1,104 +0,0 @@
|
||||||
#include <extra2d/utils/logger.h>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// 静态成员定义
|
|
||||||
LogLevel Logger::level_ = LogLevel::Info;
|
|
||||||
bool Logger::initialized_ = false;
|
|
||||||
bool Logger::consoleOutput_ = true;
|
|
||||||
bool Logger::fileOutput_ = false;
|
|
||||||
std::string Logger::logFile_;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取日志级别字符串
|
|
||||||
* @param level 日志级别
|
|
||||||
* @return 级别字符串
|
|
||||||
*/
|
|
||||||
const char *Logger::getLevelString(LogLevel level) {
|
|
||||||
switch (level) {
|
|
||||||
case LogLevel::Trace:
|
|
||||||
return "TRACE";
|
|
||||||
case LogLevel::Debug:
|
|
||||||
return "DEBUG";
|
|
||||||
case LogLevel::Info:
|
|
||||||
return "INFO ";
|
|
||||||
case LogLevel::Warn:
|
|
||||||
return "WARN ";
|
|
||||||
case LogLevel::Error:
|
|
||||||
return "ERROR";
|
|
||||||
case LogLevel::Fatal:
|
|
||||||
return "FATAL";
|
|
||||||
default:
|
|
||||||
return "UNKNOWN";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 初始化日志系统
|
|
||||||
*/
|
|
||||||
void Logger::init() {
|
|
||||||
if (initialized_) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设置 SDL 日志级别为详细模式(允许所有级别的日志)
|
|
||||||
SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_VERBOSE);
|
|
||||||
|
|
||||||
initialized_ = true;
|
|
||||||
log(LogLevel::Info, "Logger initialized with SDL2");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 关闭日志系统
|
|
||||||
*/
|
|
||||||
void Logger::shutdown() {
|
|
||||||
if (initialized_) {
|
|
||||||
log(LogLevel::Info, "Logger shutting down");
|
|
||||||
}
|
|
||||||
initialized_ = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置日志级别
|
|
||||||
* @param level 日志级别
|
|
||||||
*/
|
|
||||||
void Logger::setLevel(LogLevel level) {
|
|
||||||
level_ = level;
|
|
||||||
// 同时设置 SDL 的日志级别
|
|
||||||
if (level != LogLevel::Off) {
|
|
||||||
SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
static_cast<SDL_LogPriority>(level));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置是否输出到控制台
|
|
||||||
* @param enable 是否启用
|
|
||||||
*/
|
|
||||||
void Logger::setConsoleOutput(bool enable) {
|
|
||||||
consoleOutput_ = enable;
|
|
||||||
// SDL2 日志默认输出到控制台,通过设置日志优先级控制
|
|
||||||
if (!enable) {
|
|
||||||
SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_CRITICAL);
|
|
||||||
} else {
|
|
||||||
SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
static_cast<SDL_LogPriority>(level_));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置日志输出到文件
|
|
||||||
* @param filename 日志文件名
|
|
||||||
*/
|
|
||||||
void Logger::setFileOutput(const std::string &filename) {
|
|
||||||
logFile_ = filename;
|
|
||||||
fileOutput_ = !filename.empty();
|
|
||||||
|
|
||||||
if (fileOutput_) {
|
|
||||||
// SDL2 使用 SDL_LogSetOutputFunction 可以重定向日志输出
|
|
||||||
// 这里我们记录文件路径,实际文件输出可以通过自定义回调实现
|
|
||||||
log(LogLevel::Info, "File output configured: {}", filename);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
#include <extra2d/utils/object_pool.h>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// ObjectPoolManager 单例实现
|
|
||||||
// 所有对象池通过静态局部变量自动管理生命周期
|
|
||||||
// 程序退出时自动清理,无需手动调用 cleanup
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,61 +1,31 @@
|
||||||
|
#include <extra2d/services/logger_service.h>
|
||||||
#include <extra2d/window/window.h>
|
#include <extra2d/window/window.h>
|
||||||
#include <extra2d/platform/input.h>
|
|
||||||
#include <extra2d/utils/logger.h>
|
|
||||||
|
|
||||||
#include <SDL.h>
|
#include <SDL.h>
|
||||||
#include <glad/glad.h>
|
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
Window::Window()
|
Window::Window()
|
||||||
: sdlWindow_(nullptr), glContext_(nullptr),
|
: sdlWindow_(nullptr), width_(1280), height_(720), fullscreen_(true),
|
||||||
width_(1280), height_(720), vsync_(true), shouldClose_(false),
|
shouldClose_(false) {}
|
||||||
fullscreen_(true) {
|
|
||||||
}
|
|
||||||
|
|
||||||
Window::~Window() { destroy(); }
|
Window::~Window() { destroy(); }
|
||||||
|
|
||||||
bool Window::create(const WindowConfig &config) {
|
bool Window::create(const WindowConfig &config) {
|
||||||
if (sdlWindow_ != nullptr) {
|
if (sdlWindow_ != nullptr) {
|
||||||
E2D_LOG_WARN("Window already created");
|
E2D_WARN("Window already created");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
width_ = config.width;
|
width_ = config.width;
|
||||||
height_ = config.height;
|
height_ = config.height;
|
||||||
vsync_ = config.vsync;
|
|
||||||
fullscreen_ = config.fullscreen;
|
fullscreen_ = config.fullscreen;
|
||||||
|
|
||||||
if (!initSDL(config)) {
|
|
||||||
E2D_LOG_ERROR("Failed to initialize SDL2");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
input_ = makeUnique<Input>();
|
|
||||||
input_->init();
|
|
||||||
|
|
||||||
E2D_LOG_INFO("Window created: {}x{}", width_, height_);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Window::initSDL(const WindowConfig &config) {
|
|
||||||
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER) != 0) {
|
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER) != 0) {
|
||||||
E2D_LOG_ERROR("SDL_Init failed: {}", SDL_GetError());
|
E2D_ERROR("SDL_Init failed: {}", SDL_GetError());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
|
|
||||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
|
|
||||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
|
|
||||||
|
|
||||||
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
|
|
||||||
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
|
|
||||||
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
|
|
||||||
SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8);
|
|
||||||
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
|
|
||||||
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
|
|
||||||
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
|
|
||||||
|
|
||||||
Uint32 windowFlags = SDL_WINDOW_OPENGL;
|
Uint32 windowFlags = SDL_WINDOW_OPENGL;
|
||||||
|
|
||||||
if (config.fullscreen) {
|
if (config.fullscreen) {
|
||||||
|
|
@ -73,68 +43,21 @@ bool Window::initSDL(const WindowConfig &config) {
|
||||||
width_, height_, windowFlags);
|
width_, height_, windowFlags);
|
||||||
|
|
||||||
if (!sdlWindow_) {
|
if (!sdlWindow_) {
|
||||||
E2D_LOG_ERROR("SDL_CreateWindow failed: {}", SDL_GetError());
|
E2D_ERROR("SDL_CreateWindow failed: {}", SDL_GetError());
|
||||||
SDL_Quit();
|
SDL_Quit();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
glContext_ = SDL_GL_CreateContext(sdlWindow_);
|
E2D_INFO("Window created: {}x{}", width_, height_);
|
||||||
if (!glContext_) {
|
|
||||||
E2D_LOG_ERROR("SDL_GL_CreateContext failed: {}", SDL_GetError());
|
|
||||||
SDL_DestroyWindow(sdlWindow_);
|
|
||||||
sdlWindow_ = nullptr;
|
|
||||||
SDL_Quit();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (SDL_GL_MakeCurrent(sdlWindow_, glContext_) != 0) {
|
|
||||||
E2D_LOG_ERROR("SDL_GL_MakeCurrent failed: {}", SDL_GetError());
|
|
||||||
SDL_GL_DeleteContext(glContext_);
|
|
||||||
glContext_ = nullptr;
|
|
||||||
SDL_DestroyWindow(sdlWindow_);
|
|
||||||
sdlWindow_ = nullptr;
|
|
||||||
SDL_Quit();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (gladLoadGLES2Loader(reinterpret_cast<GLADloadproc>(SDL_GL_GetProcAddress)) == 0) {
|
|
||||||
E2D_LOG_ERROR("gladLoadGLES2Loader failed");
|
|
||||||
SDL_GL_DeleteContext(glContext_);
|
|
||||||
glContext_ = nullptr;
|
|
||||||
SDL_DestroyWindow(sdlWindow_);
|
|
||||||
sdlWindow_ = nullptr;
|
|
||||||
SDL_Quit();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_GL_SetSwapInterval(vsync_ ? 1 : 0);
|
|
||||||
|
|
||||||
E2D_LOG_INFO("SDL2 + OpenGL ES 3.0 initialized successfully");
|
|
||||||
E2D_LOG_INFO("OpenGL Version: {}", reinterpret_cast<const char*>(glGetString(GL_VERSION)));
|
|
||||||
E2D_LOG_INFO("OpenGL Renderer: {}", reinterpret_cast<const char*>(glGetString(GL_RENDERER)));
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Window::deinitSDL() {
|
|
||||||
if (glContext_) {
|
|
||||||
SDL_GL_DeleteContext(glContext_);
|
|
||||||
glContext_ = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sdlWindow_) {
|
|
||||||
SDL_DestroyWindow(sdlWindow_);
|
|
||||||
sdlWindow_ = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_Quit();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Window::destroy() {
|
void Window::destroy() {
|
||||||
if (sdlWindow_ != nullptr) {
|
if (sdlWindow_ != nullptr) {
|
||||||
input_.reset();
|
SDL_DestroyWindow(sdlWindow_);
|
||||||
deinitSDL();
|
sdlWindow_ = nullptr;
|
||||||
E2D_LOG_INFO("Window destroyed");
|
SDL_Quit();
|
||||||
|
E2D_INFO("Window destroyed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -155,16 +78,6 @@ void Window::pollEvents() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (input_) {
|
|
||||||
input_->update();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Window::swapBuffers() {
|
|
||||||
if (sdlWindow_) {
|
|
||||||
SDL_GL_SwapWindow(sdlWindow_);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Window::shouldClose() const { return shouldClose_; }
|
bool Window::shouldClose() const { return shouldClose_; }
|
||||||
|
|
@ -193,9 +106,4 @@ void Window::setFullscreen(bool fullscreen) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Window::setVSync(bool enabled) {
|
|
||||||
vsync_ = enabled;
|
|
||||||
SDL_GL_SetSwapInterval(enabled ? 1 : 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace extra2d
|
} // namespace extra2d
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ function define_extra2d_engine()
|
||||||
add_cxxflags("-Wno-deprecated-copy", "-Wno-class-memaccess", {force = true})
|
add_cxxflags("-Wno-deprecated-copy", "-Wno-class-memaccess", {force = true})
|
||||||
|
|
||||||
if is_mode("debug") then
|
if is_mode("debug") then
|
||||||
add_defines("E2D_DEBUG", "_DEBUG", {public = true})
|
add_defines("_DEBUG", {public = true})
|
||||||
add_cxxflags("-O0", "-g", {force = true})
|
add_cxxflags("-O0", "-g", {force = true})
|
||||||
else
|
else
|
||||||
add_defines("NDEBUG", {public = true})
|
add_defines("NDEBUG", {public = true})
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue