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
|
||||
|
||||
#include <extra2d/core/module.h>
|
||||
#include <extra2d/core/registry.h>
|
||||
#include <extra2d/core/service_locator.h>
|
||||
#include <extra2d/core/types.h>
|
||||
#include <string>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
class GLFWWindow;
|
||||
class WindowModule;
|
||||
class Window;
|
||||
|
||||
/**
|
||||
* @brief 应用程序类
|
||||
*/
|
||||
class Application {
|
||||
public:
|
||||
static Application &get();
|
||||
static Application& instance();
|
||||
|
||||
Application(const Application &) = delete;
|
||||
Application &operator=(const Application &) = delete;
|
||||
Application(const Application&) = delete;
|
||||
Application& operator=(const Application&) = delete;
|
||||
|
||||
/**
|
||||
* @brief 应用信息
|
||||
*/
|
||||
std::string appName = "Extra2D App";
|
||||
std::string appVersion = "1.0.0";
|
||||
std::string organization = "";
|
||||
std::string name = "Extra2D App";
|
||||
std::string version = "1.0.0";
|
||||
|
||||
/**
|
||||
* @brief 注册模块
|
||||
* @tparam T 模块类型
|
||||
* @tparam Args 构造函数参数
|
||||
* @return 模块指针
|
||||
*/
|
||||
template <typename T, typename... Args> T *use(Args &&...args) {
|
||||
return Registry::instance().use<T>(std::forward<Args>(args)...);
|
||||
}
|
||||
bool init();
|
||||
void shutdown();
|
||||
void run();
|
||||
void quit();
|
||||
|
||||
/**
|
||||
* @brief 获取模块
|
||||
* @tparam T 模块类型
|
||||
* @return 模块指针
|
||||
*/
|
||||
template <typename T> T *get() const { return Registry::instance().get<T>(); }
|
||||
bool paused() const { return paused_; }
|
||||
bool running() const { return running_; }
|
||||
Window* window() const { return window_; }
|
||||
|
||||
/**
|
||||
* @brief 初始化
|
||||
* @return 初始化成功返回 true
|
||||
*/
|
||||
bool init();
|
||||
|
||||
/**
|
||||
* @brief 关闭
|
||||
*/
|
||||
void shutdown();
|
||||
|
||||
/**
|
||||
* @brief 运行主循环
|
||||
*/
|
||||
void run();
|
||||
|
||||
/**
|
||||
* @brief 请求退出
|
||||
*/
|
||||
void quit();
|
||||
|
||||
/**
|
||||
* @brief 暂停
|
||||
*/
|
||||
void pause();
|
||||
|
||||
/**
|
||||
* @brief 恢复
|
||||
*/
|
||||
void resume();
|
||||
|
||||
bool paused() const { return paused_; }
|
||||
bool running() const { return running_; }
|
||||
|
||||
/**
|
||||
* @brief 获取窗口
|
||||
* @return 窗口指针
|
||||
*/
|
||||
GLFWWindow *window();
|
||||
|
||||
f32 dt() const { return dt_; }
|
||||
f32 totalTime() const { return totalTime_; }
|
||||
int fps() const { return fps_; }
|
||||
f32 dt() const { return dt_; }
|
||||
f32 totalTime() const { return totalTime_; }
|
||||
int fps() const { return fps_; }
|
||||
|
||||
private:
|
||||
Application();
|
||||
~Application();
|
||||
Application() = default;
|
||||
~Application();
|
||||
|
||||
void mainLoop();
|
||||
void update();
|
||||
void render();
|
||||
void mainLoop();
|
||||
|
||||
bool initialized_ = false;
|
||||
bool running_ = false;
|
||||
bool paused_ = false;
|
||||
bool shouldQuit_ = false;
|
||||
bool initialized_ = false;
|
||||
bool running_ = false;
|
||||
bool paused_ = false;
|
||||
Window* window_ = nullptr;
|
||||
|
||||
f32 dt_ = 0.0f;
|
||||
f32 totalTime_ = 0.0f;
|
||||
f64 lastFrameTime_ = 0.0;
|
||||
int frameCount_ = 0;
|
||||
f32 fpsTimer_ = 0.0f;
|
||||
int fps_ = 0;
|
||||
f32 dt_ = 0.0f;
|
||||
f32 totalTime_ = 0.0f;
|
||||
u64 lastFrameTime_ = 0;
|
||||
int frameCount_ = 0;
|
||||
f32 fpsTimer_ = 0.0f;
|
||||
int fps_ = 0;
|
||||
};
|
||||
|
||||
#define E2D_APP() ::extra2d::Application::instance()
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
|
|||
|
|
@ -6,10 +6,16 @@
|
|||
// Core
|
||||
#include <extra2d/core/color.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/types.h>
|
||||
|
||||
// Platform
|
||||
#include <extra2d/platform/glfw/glfw_window.h>
|
||||
#include <extra2d/platform/keys.h>
|
||||
#include <extra2d/platform/window_module.h>
|
||||
|
||||
// Window - SDL2 + OpenGL
|
||||
#include <extra2d/window/window.h>
|
||||
|
||||
|
|
@ -31,22 +37,11 @@
|
|||
#include <extra2d/scene/scene_manager.h>
|
||||
#include <extra2d/scene/shape_node.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
|
||||
#include <extra2d/event/event.h>
|
||||
#include <extra2d/event/event_dispatcher.h>
|
||||
#include <extra2d/event/event_queue.h>
|
||||
<<<<<<< HEAD
|
||||
#include <extra2d/event/input_codes.h>
|
||||
|
||||
// Audio
|
||||
|
|
@ -55,7 +50,6 @@
|
|||
|
||||
// Utils
|
||||
#include <extra2d/utils/data.h>
|
||||
#include <extra2d/utils/logger.h>
|
||||
#include <extra2d/utils/random.h>
|
||||
#include <extra2d/utils/timer.h>
|
||||
|
||||
|
|
@ -64,11 +58,6 @@
|
|||
#include <extra2d/spatial/spatial_hash.h>
|
||||
#include <extra2d/spatial/spatial_index.h>
|
||||
#include <extra2d/spatial/spatial_manager.h>
|
||||
=======
|
||||
|
||||
// Utils
|
||||
#include <extra2d/utils/random.h>
|
||||
#include <extra2d/utils/timer.h>
|
||||
|
||||
// Services
|
||||
#include <extra2d/services/asset_service.h>
|
||||
|
|
@ -84,7 +73,6 @@
|
|||
#include <extra2d/asset/asset_pack.h>
|
||||
#include <extra2d/asset/asset_types.h>
|
||||
#include <extra2d/asset/data_processor.h>
|
||||
>>>>>>> 9bf328b1dca01df84a07724394abc9a238739869
|
||||
|
||||
// Application
|
||||
#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
|
||||
|
||||
#include <extra2d/core/color.h>
|
||||
#include <extra2d/render/buffer.h>
|
||||
#include <extra2d/render/render_device.h>
|
||||
#include <extra2d/render/render_queue.h>
|
||||
|
|
@ -21,8 +22,10 @@ public:
|
|||
|
||||
/**
|
||||
* @brief 初始化渲染上下文
|
||||
* @param window SDL窗口指针
|
||||
* @param config 渲染设备配置
|
||||
*/
|
||||
bool init();
|
||||
bool init(SDL_Window *window, const RenderDeviceConfig &config = {});
|
||||
|
||||
/**
|
||||
* @brief 关闭渲染上下文
|
||||
|
|
@ -119,7 +122,7 @@ public:
|
|||
/**
|
||||
* @brief 获取渲染统计
|
||||
*/
|
||||
const RenderStats &stats() const { return stats_; }
|
||||
const RenderStats &stats() const;
|
||||
|
||||
/**
|
||||
* @brief 重置渲染统计
|
||||
|
|
@ -142,7 +145,6 @@ private:
|
|||
bool initialized_ = false;
|
||||
RenderDevice &device_ = RenderDevice::instance();
|
||||
RenderQueue queue_;
|
||||
RenderStats stats_;
|
||||
|
||||
Viewport viewport_;
|
||||
ScissorRect scissor_;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/render/render_types.h>
|
||||
#include <SDL.h>
|
||||
#include <extra2d/render/buffer.h>
|
||||
#include <extra2d/render/render_types.h>
|
||||
#include <extra2d/render/vao.h>
|
||||
#include <glad/glad.h>
|
||||
#include <memory>
|
||||
|
|
@ -12,170 +13,205 @@ namespace extra2d {
|
|||
* @brief 渲染能力信息
|
||||
*/
|
||||
struct RenderCaps {
|
||||
int32 maxTextureSize = 0;
|
||||
int32 maxTextureUnits = 0;
|
||||
int32 maxVertexAttribs = 0;
|
||||
int32 maxUniformBlockBindings = 0;
|
||||
int32 maxUniformBlockSize = 0;
|
||||
int32 maxVertexUniformComponents = 0;
|
||||
int32 maxFragmentUniformComponents = 0;
|
||||
int32 maxDrawBuffers = 0;
|
||||
int32 maxSamples = 0;
|
||||
bool dsaSupported = false;
|
||||
bool computeSupported = false;
|
||||
bool tessellationSupported = false;
|
||||
bool geometryShaderSupported = false;
|
||||
String renderer;
|
||||
String vendor;
|
||||
String version;
|
||||
String glslVersion;
|
||||
int32 maxTextureSize = 0;
|
||||
int32 maxTextureUnits = 0;
|
||||
int32 maxVertexAttribs = 0;
|
||||
int32 maxUniformBlockBindings = 0;
|
||||
int32 maxUniformBlockSize = 0;
|
||||
int32 maxVertexUniformComponents = 0;
|
||||
int32 maxFragmentUniformComponents = 0;
|
||||
int32 maxDrawBuffers = 0;
|
||||
int32 maxSamples = 0;
|
||||
bool dsaSupported = false;
|
||||
bool computeSupported = false;
|
||||
bool tessellationSupported = false;
|
||||
bool geometryShaderSupported = false;
|
||||
String renderer;
|
||||
String vendor;
|
||||
String version;
|
||||
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 渲染设备
|
||||
*
|
||||
* 封装 OpenGL 4.5 核心功能,使用 DSA API
|
||||
*/
|
||||
class RenderDevice {
|
||||
public:
|
||||
static RenderDevice& instance();
|
||||
static RenderDevice &instance();
|
||||
|
||||
/**
|
||||
* @brief 初始化渲染设备
|
||||
* @return 初始化成功返回 true
|
||||
*/
|
||||
bool init();
|
||||
/**
|
||||
* @brief 初始化 OpenGL 上下文
|
||||
*/
|
||||
bool init(SDL_Window *window, const RenderDeviceConfig &config = {});
|
||||
|
||||
/**
|
||||
* @brief 关闭渲染设备
|
||||
*/
|
||||
void shutdown();
|
||||
/**
|
||||
* @brief 关闭渲染设备
|
||||
*/
|
||||
void shutdown();
|
||||
|
||||
/**
|
||||
* @brief 检查是否已初始化
|
||||
*/
|
||||
bool isInitialized() const { return initialized_; }
|
||||
/**
|
||||
* @brief 检查是否已初始化
|
||||
*/
|
||||
bool isInitialized() const { return initialized_; }
|
||||
|
||||
/**
|
||||
* @brief 获取渲染能力信息
|
||||
*/
|
||||
const RenderCaps& caps() const { return caps_; }
|
||||
/**
|
||||
* @brief 获取渲染能力信息
|
||||
*/
|
||||
const RenderCaps &caps() const { return caps_; }
|
||||
|
||||
/**
|
||||
* @brief 创建缓冲区
|
||||
*/
|
||||
std::unique_ptr<Buffer> createBuffer(BufferType type, BufferUsage usage,
|
||||
size_t size, const void* data = nullptr);
|
||||
/**
|
||||
* @brief 获取 SDL GL 上下文
|
||||
*/
|
||||
SDL_GLContext glContext() const { return glContext_; }
|
||||
|
||||
/**
|
||||
* @brief 创建顶点缓冲区
|
||||
*/
|
||||
std::unique_ptr<VertexBuffer> createVertexBuffer(BufferUsage usage, size_t size,
|
||||
const void* data = nullptr);
|
||||
/**
|
||||
* @brief 交换缓冲区
|
||||
*/
|
||||
void swapBuffers();
|
||||
|
||||
/**
|
||||
* @brief 创建索引缓冲区
|
||||
*/
|
||||
std::unique_ptr<IndexBuffer> createIndexBuffer(BufferUsage usage, size_t size,
|
||||
const void* data = nullptr);
|
||||
/**
|
||||
* @brief 设置 VSync
|
||||
*/
|
||||
void setVSync(bool enabled);
|
||||
|
||||
/**
|
||||
* @brief 创建 Uniform 缓冲区
|
||||
*/
|
||||
std::unique_ptr<UniformBuffer> createUniformBuffer(BufferUsage usage, size_t size,
|
||||
const void* data = nullptr);
|
||||
/**
|
||||
* @brief 创建缓冲区
|
||||
*/
|
||||
std::unique_ptr<Buffer> createBuffer(BufferType type, BufferUsage usage,
|
||||
size_t size, const void *data = nullptr);
|
||||
|
||||
/**
|
||||
* @brief 创建 VAO
|
||||
*/
|
||||
std::unique_ptr<VAO> createVAO();
|
||||
/**
|
||||
* @brief 创建顶点缓冲区
|
||||
*/
|
||||
std::unique_ptr<VertexBuffer> createVertexBuffer(BufferUsage usage,
|
||||
size_t size,
|
||||
const void *data = nullptr);
|
||||
|
||||
/**
|
||||
* @brief 设置视口
|
||||
*/
|
||||
void setViewport(int32 x, int32 y, int32 width, int32 height);
|
||||
/**
|
||||
* @brief 创建索引缓冲区
|
||||
*/
|
||||
std::unique_ptr<IndexBuffer> createIndexBuffer(BufferUsage usage, size_t size,
|
||||
const void *data = nullptr);
|
||||
|
||||
/**
|
||||
* @brief 设置裁剪矩形
|
||||
*/
|
||||
void setScissor(int32 x, int32 y, int32 width, int32 height);
|
||||
/**
|
||||
* @brief 创建 Uniform 缓冲区
|
||||
*/
|
||||
std::unique_ptr<UniformBuffer>
|
||||
createUniformBuffer(BufferUsage usage, size_t size,
|
||||
const void *data = nullptr);
|
||||
|
||||
/**
|
||||
* @brief 启用/禁用裁剪测试
|
||||
*/
|
||||
void setScissorEnabled(bool enabled);
|
||||
/**
|
||||
* @brief 创建 VAO
|
||||
*/
|
||||
std::unique_ptr<VAO> createVAO();
|
||||
|
||||
/**
|
||||
* @brief 清除缓冲区
|
||||
*/
|
||||
void clear(bool color, bool depth, bool stencil,
|
||||
float r = 0.0f, float g = 0.0f, float b = 0.0f, float a = 1.0f);
|
||||
/**
|
||||
* @brief 设置视口
|
||||
*/
|
||||
void setViewport(int32 x, int32 y, int32 width, int32 height);
|
||||
|
||||
/**
|
||||
* @brief 设置混合状态
|
||||
*/
|
||||
void setBlendState(const BlendState& state);
|
||||
/**
|
||||
* @brief 设置裁剪矩形
|
||||
*/
|
||||
void setScissor(int32 x, int32 y, int32 width, int32 height);
|
||||
|
||||
/**
|
||||
* @brief 设置深度状态
|
||||
*/
|
||||
void setDepthState(const DepthState& state);
|
||||
/**
|
||||
* @brief 启用/禁用裁剪测试
|
||||
*/
|
||||
void setScissorEnabled(bool enabled);
|
||||
|
||||
/**
|
||||
* @brief 设置光栅化状态
|
||||
*/
|
||||
void setRasterState(const RasterState& state);
|
||||
/**
|
||||
* @brief 清除缓冲区
|
||||
*/
|
||||
void clear(bool color, bool depth, bool stencil, float r = 0.0f,
|
||||
float g = 0.0f, float b = 0.0f, float a = 1.0f);
|
||||
|
||||
/**
|
||||
* @brief 绑定纹理到纹理单元
|
||||
*/
|
||||
void bindTexture(uint32 unit, GLuint texture);
|
||||
/**
|
||||
* @brief 设置混合状态
|
||||
*/
|
||||
void setBlendState(const BlendState &state);
|
||||
|
||||
/**
|
||||
* @brief 绑定着色器程序
|
||||
*/
|
||||
void bindProgram(GLuint program);
|
||||
/**
|
||||
* @brief 设置深度状态
|
||||
*/
|
||||
void setDepthState(const DepthState &state);
|
||||
|
||||
/**
|
||||
* @brief 绘制数组
|
||||
*/
|
||||
void drawArrays(PrimitiveType mode, int32 first, int32 count);
|
||||
/**
|
||||
* @brief 设置光栅化状态
|
||||
*/
|
||||
void setRasterState(const RasterState &state);
|
||||
|
||||
/**
|
||||
* @brief 绘制索引
|
||||
*/
|
||||
void drawElements(PrimitiveType mode, int32 count, IndexType type,
|
||||
const void* indices = nullptr);
|
||||
/**
|
||||
* @brief 绑定纹理到纹理单元
|
||||
*/
|
||||
void bindTexture(uint32 unit, GLuint texture);
|
||||
|
||||
/**
|
||||
* @brief 绘制实例化数组
|
||||
*/
|
||||
void drawArraysInstanced(PrimitiveType mode, int32 first, int32 count,
|
||||
int32 instanceCount);
|
||||
/**
|
||||
* @brief 绑定着色器程序
|
||||
*/
|
||||
void bindProgram(GLuint program);
|
||||
|
||||
/**
|
||||
* @brief 绘制实例化索引
|
||||
*/
|
||||
void drawElementsInstanced(PrimitiveType mode, int32 count, IndexType type,
|
||||
const void* indices, int32 instanceCount);
|
||||
/**
|
||||
* @brief 绘制数组
|
||||
*/
|
||||
void drawArrays(PrimitiveType mode, int32 first, int32 count);
|
||||
|
||||
/**
|
||||
* @brief 绘制索引
|
||||
*/
|
||||
void drawElements(PrimitiveType mode, int32 count, IndexType type,
|
||||
const void *indices = nullptr);
|
||||
|
||||
/**
|
||||
* @brief 绘制实例化数组
|
||||
*/
|
||||
void drawArraysInstanced(PrimitiveType mode, int32 first, int32 count,
|
||||
int32 instanceCount);
|
||||
|
||||
/**
|
||||
* @brief 绘制实例化索引
|
||||
*/
|
||||
void drawElementsInstanced(PrimitiveType mode, int32 count, IndexType type,
|
||||
const void *indices, int32 instanceCount);
|
||||
|
||||
private:
|
||||
RenderDevice() = default;
|
||||
~RenderDevice() = default;
|
||||
RenderDevice(const RenderDevice&) = delete;
|
||||
RenderDevice& operator=(const RenderDevice&) = delete;
|
||||
RenderDevice() = default;
|
||||
~RenderDevice() = default;
|
||||
RenderDevice(const RenderDevice &) = delete;
|
||||
RenderDevice &operator=(const RenderDevice &) = delete;
|
||||
|
||||
void queryCaps();
|
||||
GLenum glPrimitiveType(PrimitiveType mode) const;
|
||||
GLenum glIndexType(IndexType type) const;
|
||||
bool initGL(const RenderDeviceConfig &config);
|
||||
void queryCaps();
|
||||
GLenum glPrimitiveType(PrimitiveType mode) const;
|
||||
GLenum glIndexType(IndexType type) const;
|
||||
|
||||
bool initialized_ = false;
|
||||
RenderCaps caps_;
|
||||
bool initialized_ = false;
|
||||
SDL_Window *window_ = nullptr;
|
||||
SDL_GLContext glContext_ = nullptr;
|
||||
RenderCaps caps_;
|
||||
|
||||
BlendState currentBlend_;
|
||||
DepthState currentDepth_;
|
||||
RasterState currentRaster_;
|
||||
GLuint currentProgram_ = 0;
|
||||
BlendState currentBlend_;
|
||||
DepthState currentDepth_;
|
||||
RasterState currentRaster_;
|
||||
GLuint currentProgram_ = 0;
|
||||
};
|
||||
|
||||
#define E2D_RENDER_DEVICE() ::extra2d::RenderDevice::instance()
|
||||
|
|
|
|||
|
|
@ -31,360 +31,302 @@ using uint64 = std::uint64_t;
|
|||
// 混合模式
|
||||
// ---------------------------------------------------------------------------
|
||||
enum class BlendMode : uint8 {
|
||||
None,
|
||||
Alpha,
|
||||
Additive,
|
||||
Multiply,
|
||||
Screen,
|
||||
Custom
|
||||
None,
|
||||
Alpha,
|
||||
Additive,
|
||||
Multiply,
|
||||
Screen,
|
||||
Custom
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 深度比较函数
|
||||
// ---------------------------------------------------------------------------
|
||||
enum class CompareFunc : uint8 {
|
||||
Never,
|
||||
Less,
|
||||
Equal,
|
||||
LEqual,
|
||||
Greater,
|
||||
NotEqual,
|
||||
GEqual,
|
||||
Always
|
||||
Never,
|
||||
Less,
|
||||
Equal,
|
||||
LEqual,
|
||||
Greater,
|
||||
NotEqual,
|
||||
GEqual,
|
||||
Always
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 剔除模式
|
||||
// ---------------------------------------------------------------------------
|
||||
enum class CullMode : uint8 {
|
||||
None,
|
||||
Front,
|
||||
Back
|
||||
};
|
||||
enum class CullMode : uint8 { None, Front, Back };
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 多边形模式
|
||||
// ---------------------------------------------------------------------------
|
||||
enum class PolygonMode : uint8 {
|
||||
Fill,
|
||||
Line,
|
||||
Point
|
||||
};
|
||||
enum class PolygonMode : uint8 { Fill, Line, Point };
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 混合因子
|
||||
// ---------------------------------------------------------------------------
|
||||
enum class BlendFactor : uint8 {
|
||||
Zero,
|
||||
One,
|
||||
SrcColor,
|
||||
OneMinusSrcColor,
|
||||
DstColor,
|
||||
OneMinusDstColor,
|
||||
SrcAlpha,
|
||||
OneMinusSrcAlpha,
|
||||
DstAlpha,
|
||||
OneMinusDstAlpha,
|
||||
ConstantColor,
|
||||
OneMinusConstantColor,
|
||||
ConstantAlpha,
|
||||
OneMinusConstantAlpha
|
||||
Zero,
|
||||
One,
|
||||
SrcColor,
|
||||
OneMinusSrcColor,
|
||||
DstColor,
|
||||
OneMinusDstColor,
|
||||
SrcAlpha,
|
||||
OneMinusSrcAlpha,
|
||||
DstAlpha,
|
||||
OneMinusDstAlpha,
|
||||
ConstantColor,
|
||||
OneMinusConstantColor,
|
||||
ConstantAlpha,
|
||||
OneMinusConstantAlpha
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 混合操作
|
||||
// ---------------------------------------------------------------------------
|
||||
enum class BlendOp : uint8 {
|
||||
Add,
|
||||
Subtract,
|
||||
ReverseSubtract,
|
||||
Min,
|
||||
Max
|
||||
};
|
||||
enum class BlendOp : uint8 { Add, Subtract, ReverseSubtract, Min, Max };
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 缓冲区类型
|
||||
// ---------------------------------------------------------------------------
|
||||
enum class BufferType : uint8 {
|
||||
Vertex,
|
||||
Index,
|
||||
Uniform,
|
||||
PixelPack,
|
||||
PixelUnpack
|
||||
Vertex,
|
||||
Index,
|
||||
Uniform,
|
||||
PixelPack,
|
||||
PixelUnpack
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 缓冲区使用方式
|
||||
// ---------------------------------------------------------------------------
|
||||
enum class BufferUsage : uint8 {
|
||||
Static,
|
||||
Dynamic,
|
||||
Stream
|
||||
};
|
||||
enum class BufferUsage : uint8 { Static, Dynamic, Stream };
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 纹理过滤模式
|
||||
// ---------------------------------------------------------------------------
|
||||
enum class FilterMode : uint8 {
|
||||
Nearest,
|
||||
Linear,
|
||||
NearestMipmapNearest,
|
||||
LinearMipmapNearest,
|
||||
NearestMipmapLinear,
|
||||
LinearMipmapLinear
|
||||
Nearest,
|
||||
Linear,
|
||||
NearestMipmapNearest,
|
||||
LinearMipmapNearest,
|
||||
NearestMipmapLinear,
|
||||
LinearMipmapLinear
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 纹理环绕模式
|
||||
// ---------------------------------------------------------------------------
|
||||
enum class WrapMode : uint8 {
|
||||
Repeat,
|
||||
Clamp,
|
||||
Mirror,
|
||||
MirrorClamp,
|
||||
Border
|
||||
};
|
||||
enum class WrapMode : uint8 { Repeat, Clamp, Mirror, MirrorClamp, Border };
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 图元类型
|
||||
// ---------------------------------------------------------------------------
|
||||
enum class PrimitiveType : uint8 {
|
||||
Points,
|
||||
Lines,
|
||||
LineStrip,
|
||||
LineLoop,
|
||||
Triangles,
|
||||
TriangleStrip,
|
||||
TriangleFan
|
||||
Points,
|
||||
Lines,
|
||||
LineStrip,
|
||||
LineLoop,
|
||||
Triangles,
|
||||
TriangleStrip,
|
||||
TriangleFan
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 索引类型
|
||||
// ---------------------------------------------------------------------------
|
||||
enum class IndexType : uint8 {
|
||||
UInt8,
|
||||
UInt16,
|
||||
UInt32
|
||||
};
|
||||
enum class IndexType : uint8 { UInt8, UInt16, UInt32 };
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 顶点属性语义
|
||||
// ---------------------------------------------------------------------------
|
||||
enum class VertexSemantic : uint8 {
|
||||
Position,
|
||||
Normal,
|
||||
Tangent,
|
||||
Bitangent,
|
||||
Color0,
|
||||
Color1,
|
||||
TexCoord0,
|
||||
TexCoord1,
|
||||
TexCoord2,
|
||||
TexCoord3,
|
||||
BoneIndices,
|
||||
BoneWeights
|
||||
Position,
|
||||
Normal,
|
||||
Tangent,
|
||||
Bitangent,
|
||||
Color0,
|
||||
Color1,
|
||||
TexCoord0,
|
||||
TexCoord1,
|
||||
TexCoord2,
|
||||
TexCoord3,
|
||||
BoneIndices,
|
||||
BoneWeights
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 顶点属性类型
|
||||
// ---------------------------------------------------------------------------
|
||||
enum class VertexAttribType : uint8 {
|
||||
Float,
|
||||
Float2,
|
||||
Float3,
|
||||
Float4,
|
||||
Int,
|
||||
Int2,
|
||||
Int3,
|
||||
Int4,
|
||||
UInt,
|
||||
UInt2,
|
||||
UInt3,
|
||||
UInt4,
|
||||
Byte4,
|
||||
Byte4Norm,
|
||||
UByte4,
|
||||
UByte4Norm,
|
||||
Short2,
|
||||
Short2Norm,
|
||||
Short4,
|
||||
Short4Norm
|
||||
Float,
|
||||
Float2,
|
||||
Float3,
|
||||
Float4,
|
||||
Int,
|
||||
Int2,
|
||||
Int3,
|
||||
Int4,
|
||||
UInt,
|
||||
UInt2,
|
||||
UInt3,
|
||||
UInt4,
|
||||
Byte4,
|
||||
Byte4Norm,
|
||||
UByte4,
|
||||
UByte4Norm,
|
||||
Short2,
|
||||
Short2Norm,
|
||||
Short4,
|
||||
Short4Norm
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 混合状态
|
||||
// ---------------------------------------------------------------------------
|
||||
struct BlendState {
|
||||
bool enabled = false;
|
||||
BlendMode mode = BlendMode::Alpha;
|
||||
BlendFactor srcRGB = BlendFactor::SrcAlpha;
|
||||
BlendFactor dstRGB = BlendFactor::OneMinusSrcAlpha;
|
||||
BlendFactor srcAlpha = BlendFactor::One;
|
||||
BlendFactor dstAlpha = BlendFactor::OneMinusSrcAlpha;
|
||||
BlendOp opRGB = BlendOp::Add;
|
||||
BlendOp opAlpha = BlendOp::Add;
|
||||
uint8 colorWriteMask = 0xF;
|
||||
bool enabled = false;
|
||||
BlendMode mode = BlendMode::Alpha;
|
||||
BlendFactor srcRGB = BlendFactor::SrcAlpha;
|
||||
BlendFactor dstRGB = BlendFactor::OneMinusSrcAlpha;
|
||||
BlendFactor srcAlpha = BlendFactor::One;
|
||||
BlendFactor dstAlpha = BlendFactor::OneMinusSrcAlpha;
|
||||
BlendOp opRGB = BlendOp::Add;
|
||||
BlendOp opAlpha = BlendOp::Add;
|
||||
uint8 colorWriteMask = 0xF;
|
||||
|
||||
static BlendState opaque() {
|
||||
return BlendState{false, BlendMode::None};
|
||||
}
|
||||
static BlendState opaque() { return BlendState{false, BlendMode::None}; }
|
||||
|
||||
static BlendState alpha() {
|
||||
return BlendState{true, BlendMode::Alpha};
|
||||
}
|
||||
static BlendState alpha() { return BlendState{true, BlendMode::Alpha}; }
|
||||
|
||||
static BlendState additive() {
|
||||
BlendState state;
|
||||
state.enabled = true;
|
||||
state.mode = BlendMode::Additive;
|
||||
state.srcRGB = BlendFactor::SrcAlpha;
|
||||
state.dstRGB = BlendFactor::One;
|
||||
state.srcAlpha = BlendFactor::One;
|
||||
state.dstAlpha = BlendFactor::One;
|
||||
return state;
|
||||
}
|
||||
static BlendState additive() {
|
||||
BlendState state;
|
||||
state.enabled = true;
|
||||
state.mode = BlendMode::Additive;
|
||||
state.srcRGB = BlendFactor::SrcAlpha;
|
||||
state.dstRGB = BlendFactor::One;
|
||||
state.srcAlpha = BlendFactor::One;
|
||||
state.dstAlpha = BlendFactor::One;
|
||||
return state;
|
||||
}
|
||||
|
||||
static BlendState multiply() {
|
||||
BlendState state;
|
||||
state.enabled = true;
|
||||
state.mode = BlendMode::Multiply;
|
||||
state.srcRGB = BlendFactor::DstColor;
|
||||
state.dstRGB = BlendFactor::OneMinusSrcAlpha;
|
||||
return state;
|
||||
}
|
||||
static BlendState multiply() {
|
||||
BlendState state;
|
||||
state.enabled = true;
|
||||
state.mode = BlendMode::Multiply;
|
||||
state.srcRGB = BlendFactor::DstColor;
|
||||
state.dstRGB = BlendFactor::OneMinusSrcAlpha;
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 深度状态
|
||||
// ---------------------------------------------------------------------------
|
||||
struct DepthState {
|
||||
bool testEnabled = true;
|
||||
bool writeEnabled = true;
|
||||
CompareFunc compareFunc = CompareFunc::LEqual;
|
||||
bool testEnabled = true;
|
||||
bool writeEnabled = true;
|
||||
CompareFunc compareFunc = CompareFunc::LEqual;
|
||||
|
||||
static DepthState readOnly() {
|
||||
return DepthState{true, false, CompareFunc::LEqual};
|
||||
}
|
||||
static DepthState readOnly() {
|
||||
return DepthState{true, false, CompareFunc::LEqual};
|
||||
}
|
||||
|
||||
static DepthState disabled() {
|
||||
return DepthState{false, false, CompareFunc::Always};
|
||||
}
|
||||
static DepthState disabled() {
|
||||
return DepthState{false, false, CompareFunc::Always};
|
||||
}
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 模板状态
|
||||
// ---------------------------------------------------------------------------
|
||||
struct StencilState {
|
||||
bool enabled = false;
|
||||
uint8 readMask = 0xFF;
|
||||
uint8 writeMask = 0xFF;
|
||||
uint8 ref = 0;
|
||||
CompareFunc compareFunc = CompareFunc::Always;
|
||||
BlendOp stencilFailOp = BlendOp::Add;
|
||||
BlendOp depthFailOp = BlendOp::Add;
|
||||
BlendOp passOp = BlendOp::Add;
|
||||
bool enabled = false;
|
||||
uint8 readMask = 0xFF;
|
||||
uint8 writeMask = 0xFF;
|
||||
uint8 ref = 0;
|
||||
CompareFunc compareFunc = CompareFunc::Always;
|
||||
BlendOp stencilFailOp = BlendOp::Add;
|
||||
BlendOp depthFailOp = BlendOp::Add;
|
||||
BlendOp passOp = BlendOp::Add;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 光栅化状态
|
||||
// ---------------------------------------------------------------------------
|
||||
struct RasterState {
|
||||
CullMode cullMode = CullMode::Back;
|
||||
PolygonMode polygonMode = PolygonMode::Fill;
|
||||
bool depthBiasEnabled = false;
|
||||
float depthBias = 0.0f;
|
||||
float depthBiasSlope = 0.0f;
|
||||
bool scissorEnabled = false;
|
||||
bool multisampleEnabled = true;
|
||||
float lineWidth = 1.0f;
|
||||
float pointSize = 1.0f;
|
||||
CullMode cullMode = CullMode::Back;
|
||||
PolygonMode polygonMode = PolygonMode::Fill;
|
||||
bool depthBiasEnabled = false;
|
||||
float depthBias = 0.0f;
|
||||
float depthBiasSlope = 0.0f;
|
||||
bool scissorEnabled = false;
|
||||
bool multisampleEnabled = true;
|
||||
float lineWidth = 1.0f;
|
||||
float pointSize = 1.0f;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 渲染统计
|
||||
// ---------------------------------------------------------------------------
|
||||
struct RenderStats {
|
||||
uint32 drawCalls = 0;
|
||||
uint32 vertices = 0;
|
||||
uint32 triangles = 0;
|
||||
uint32 stateChanges = 0;
|
||||
uint32 textureBinds = 0;
|
||||
uint32 shaderBinds = 0;
|
||||
uint32 bufferUpdates = 0;
|
||||
uint32 drawCalls = 0;
|
||||
uint32 vertices = 0;
|
||||
uint32 triangles = 0;
|
||||
uint32 stateChanges = 0;
|
||||
uint32 textureBinds = 0;
|
||||
uint32 shaderBinds = 0;
|
||||
uint32 bufferUpdates = 0;
|
||||
|
||||
void reset() {
|
||||
drawCalls = 0;
|
||||
vertices = 0;
|
||||
triangles = 0;
|
||||
stateChanges = 0;
|
||||
textureBinds = 0;
|
||||
shaderBinds = 0;
|
||||
bufferUpdates = 0;
|
||||
}
|
||||
void reset() {
|
||||
drawCalls = 0;
|
||||
vertices = 0;
|
||||
triangles = 0;
|
||||
stateChanges = 0;
|
||||
textureBinds = 0;
|
||||
shaderBinds = 0;
|
||||
bufferUpdates = 0;
|
||||
}
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 视口
|
||||
// ---------------------------------------------------------------------------
|
||||
struct Viewport {
|
||||
int32 x = 0;
|
||||
int32 y = 0;
|
||||
int32 width = 800;
|
||||
int32 height = 600;
|
||||
float minDepth = 0.0f;
|
||||
float maxDepth = 1.0f;
|
||||
int32 x = 0;
|
||||
int32 y = 0;
|
||||
int32 width = 800;
|
||||
int32 height = 600;
|
||||
float minDepth = 0.0f;
|
||||
float maxDepth = 1.0f;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 裁剪矩形
|
||||
// ---------------------------------------------------------------------------
|
||||
struct ScissorRect {
|
||||
int32 x = 0;
|
||||
int32 y = 0;
|
||||
int32 width = 0;
|
||||
int32 height = 0;
|
||||
bool enabled = false;
|
||||
int32 x = 0;
|
||||
int32 y = 0;
|
||||
int32 width = 0;
|
||||
int32 height = 0;
|
||||
bool enabled = false;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 清除标志
|
||||
// ---------------------------------------------------------------------------
|
||||
struct ClearFlags {
|
||||
bool color = true;
|
||||
bool depth = true;
|
||||
bool stencil = false;
|
||||
bool color = true;
|
||||
bool depth = true;
|
||||
bool stencil = false;
|
||||
|
||||
static ClearFlags all() {
|
||||
return ClearFlags{true, true, true};
|
||||
}
|
||||
static ClearFlags all() { return ClearFlags{true, true, true}; }
|
||||
|
||||
static ClearFlags colorOnly() {
|
||||
return ClearFlags{true, false, false};
|
||||
}
|
||||
static ClearFlags colorOnly() { return ClearFlags{true, false, false}; }
|
||||
|
||||
static ClearFlags depthOnly() {
|
||||
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); }
|
||||
static ClearFlags depthOnly() { return ClearFlags{false, true, false}; }
|
||||
};
|
||||
|
||||
} // 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
|
||||
|
||||
#include <algorithm>
|
||||
#include <extra2d/core/color.h>
|
||||
#include <extra2d/core/math_types.h>
|
||||
#include <extra2d/core/types.h>
|
||||
#include <extra2d/event/event_dispatcher.h>
|
||||
#include <extra2d/graphics/renderer.h>
|
||||
#include <functional>
|
||||
#include <extra2d/render/renderer.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// 前向声明
|
||||
class Scene;
|
||||
class Renderer;
|
||||
struct RenderCommand;
|
||||
|
||||
// ============================================================================
|
||||
// 节点基类 - 场景图的基础
|
||||
|
|
@ -25,30 +21,20 @@ public:
|
|||
Node();
|
||||
virtual ~Node();
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 层级管理
|
||||
// ------------------------------------------------------------------------
|
||||
void addChild(Ptr<Node> child);
|
||||
|
||||
/**
|
||||
* @brief 批量添加子节点
|
||||
* @param children 子节点列表
|
||||
*/
|
||||
void addChildren(std::vector<Ptr<Node>> &&children);
|
||||
|
||||
void removeChild(Ptr<Node> child);
|
||||
void addChild(Ref<Node> child);
|
||||
void addChildren(std::vector<Ref<Node>> &&children);
|
||||
void removeChild(Ref<Node> child);
|
||||
void removeChildByName(const std::string &name);
|
||||
void removeFromParent();
|
||||
void removeAllChildren();
|
||||
|
||||
Ptr<Node> getParent() const { return parent_.lock(); }
|
||||
const std::vector<Ptr<Node>> &getChildren() const { return children_; }
|
||||
Ptr<Node> getChildByName(const std::string &name) const;
|
||||
Ptr<Node> getChildByTag(int tag) const;
|
||||
Ref<Node> getParent() const { return parent_.lock(); }
|
||||
const std::vector<Ref<Node>> &getChildren() const { return children_; }
|
||||
Ref<Node> getChildByName(const std::string &name) const;
|
||||
Ref<Node> getChildByTag(int tag) const;
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 变换属性
|
||||
// ------------------------------------------------------------------------
|
||||
void setPosition(const Vec2 &pos);
|
||||
void setPosition(float x, float y);
|
||||
Vec2 getPosition() const { return position_; }
|
||||
|
|
@ -75,66 +61,39 @@ public:
|
|||
void setVisible(bool 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_; }
|
||||
|
||||
/**
|
||||
* @brief 设置X轴翻转
|
||||
*/
|
||||
void setFlipX(bool flipX);
|
||||
bool isFlipX() const { return flipX_; }
|
||||
|
||||
/**
|
||||
* @brief 设置Y轴翻转
|
||||
*/
|
||||
void setFlipY(bool flipY);
|
||||
bool isFlipY() const { return flipY_; }
|
||||
|
||||
void setZOrder(int zOrder);
|
||||
int getZOrder() const { return zOrder_; }
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 世界变换
|
||||
// ------------------------------------------------------------------------
|
||||
Vec2 convertToWorldSpace(const Vec2 &localPos) const;
|
||||
Vec2 convertToNodeSpace(const Vec2 &worldPos) const;
|
||||
|
||||
glm::mat4 getLocalTransform() const;
|
||||
glm::mat4 getWorldTransform() const;
|
||||
|
||||
/**
|
||||
* @brief 标记变换矩阵为脏状态,并传播到所有子节点
|
||||
*/
|
||||
void markTransformDirty();
|
||||
|
||||
/**
|
||||
* @brief 批量更新变换矩阵
|
||||
* 在渲染前统一计算所有脏节点的变换矩阵,避免逐节点计算时的重复递归
|
||||
*/
|
||||
void batchUpdateTransforms();
|
||||
|
||||
/**
|
||||
* @brief 获取变换脏标记状态
|
||||
*/
|
||||
bool isTransformDirty() const { return transformDirty_; }
|
||||
bool isWorldTransformDirty() const { return worldTransformDirty_; }
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 名称和标签
|
||||
// ------------------------------------------------------------------------
|
||||
void setName(const std::string &name) { name_ = name; }
|
||||
const std::string &getName() const { return name_; }
|
||||
|
||||
void setTag(int tag) { tag_ = tag; }
|
||||
int getTag() const { return tag_; }
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 生命周期回调
|
||||
// ------------------------------------------------------------------------
|
||||
virtual void onEnter();
|
||||
virtual void onExit();
|
||||
virtual void onUpdate(float dt);
|
||||
|
|
@ -142,26 +101,17 @@ public:
|
|||
virtual void onAttachToScene(Scene *scene);
|
||||
virtual void onDetachFromScene();
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 边界框(用于空间索引)
|
||||
// ------------------------------------------------------------------------
|
||||
virtual Rect getBoundingBox() const;
|
||||
|
||||
// 是否需要参与空间索引(默认 true)
|
||||
void setSpatialIndexed(bool indexed) { spatialIndexed_ = indexed; }
|
||||
bool isSpatialIndexed() const { return spatialIndexed_; }
|
||||
|
||||
// 更新空间索引(手动调用,通常在边界框变化后)
|
||||
void updateSpatialIndex();
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 事件系统
|
||||
// ------------------------------------------------------------------------
|
||||
EventDispatcher &getEventDispatcher() { return eventDispatcher_; }
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 内部方法
|
||||
// ------------------------------------------------------------------------
|
||||
void update(float dt);
|
||||
void render(Renderer &renderer);
|
||||
void sortChildren();
|
||||
|
|
@ -169,18 +119,10 @@ public:
|
|||
bool isRunning() const { return running_; }
|
||||
Scene *getScene() const { return scene_; }
|
||||
|
||||
// 多线程渲染命令收集
|
||||
virtual void collectRenderCommands(std::vector<RenderCommand> &commands,
|
||||
int parentZOrder = 0);
|
||||
|
||||
protected:
|
||||
// 子类重写
|
||||
virtual void onDraw(Renderer &renderer) {}
|
||||
virtual void onUpdateNode(float dt) {}
|
||||
virtual void generateRenderCommand(std::vector<RenderCommand> &commands,
|
||||
int zOrder) {};
|
||||
|
||||
// 供子类访问的内部状态
|
||||
Vec2 &getPositionRef() { return position_; }
|
||||
Vec2 &getScaleRef() { return scale_; }
|
||||
Vec2 &getAnchorRef() { return anchor_; }
|
||||
|
|
@ -188,65 +130,45 @@ protected:
|
|||
float getOpacityRef() { return opacity_; }
|
||||
|
||||
private:
|
||||
// ==========================================================================
|
||||
// 成员变量按类型大小降序排列,减少内存对齐填充
|
||||
// 64位系统对齐:std::string(32) > glm::mat4(64) > std::vector(24) >
|
||||
// double(8) > float(4) > int(4) > bool(1)
|
||||
// ==========================================================================
|
||||
mutable glm::mat4 localTransform_{1.0f};
|
||||
mutable glm::mat4 worldTransform_{1.0f};
|
||||
|
||||
// 1. 大块内存(64字节)
|
||||
mutable glm::mat4 localTransform_; // 64 bytes
|
||||
mutable glm::mat4 worldTransform_; // 64 bytes
|
||||
std::string name_;
|
||||
std::vector<Ref<Node>> children_;
|
||||
|
||||
// 2. 字符串和容器(24-32字节)
|
||||
std::string name_; // 32 bytes
|
||||
std::vector<Ptr<Node>> children_; // 24 bytes
|
||||
std::unordered_map<std::string, std::weak_ptr<Node>> nameIndex_;
|
||||
std::unordered_map<int, std::weak_ptr<Node>> tagIndex_;
|
||||
|
||||
// 3. 子节点索引(加速查找)
|
||||
std::unordered_map<std::string, WeakPtr<Node>> nameIndex_; // 56 bytes
|
||||
std::unordered_map<int, WeakPtr<Node>> tagIndex_; // 56 bytes
|
||||
EventDispatcher eventDispatcher_;
|
||||
|
||||
// 4. 事件分发器
|
||||
EventDispatcher eventDispatcher_; // 大小取决于实现
|
||||
std::weak_ptr<Node> parent_;
|
||||
|
||||
// 5. 父节点引用
|
||||
WeakPtr<Node> parent_; // 16 bytes
|
||||
Vec2 position_ = Vec2::Zero();
|
||||
Vec2 scale_ = Vec2(1.0f, 1.0f);
|
||||
Vec2 anchor_ = Vec2(0.5f, 0.5f);
|
||||
Vec2 skew_ = Vec2::Zero();
|
||||
|
||||
// 7. 变换属性(按访问频率分组)
|
||||
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
|
||||
Rect lastSpatialBounds_;
|
||||
|
||||
// 8. 边界框(用于空间索引)
|
||||
Rect lastSpatialBounds_; // 16 bytes
|
||||
float rotation_ = 0.0f;
|
||||
float opacity_ = 1.0f;
|
||||
|
||||
// 9. 浮点属性
|
||||
float rotation_ = 0.0f; // 4 bytes
|
||||
float opacity_ = 1.0f; // 4 bytes
|
||||
Color3B color_ = Color3B(255, 255, 255);
|
||||
|
||||
// 10. 颜色属性
|
||||
Color3B color_ = Color3B(255, 255, 255); // 3 bytes
|
||||
int zOrder_ = 0;
|
||||
int tag_ = -1;
|
||||
|
||||
// 11. 整数属性
|
||||
int zOrder_ = 0; // 4 bytes
|
||||
int tag_ = -1; // 4 bytes
|
||||
bool flipX_ = false;
|
||||
bool flipY_ = false;
|
||||
|
||||
// 12. 布尔属性
|
||||
bool flipX_ = false; // 1 byte
|
||||
bool flipY_ = false; // 1 byte
|
||||
Scene *scene_ = nullptr;
|
||||
|
||||
// 11. 场景指针
|
||||
Scene *scene_ = nullptr; // 8 bytes
|
||||
|
||||
// 12. 布尔标志(打包在一起)
|
||||
mutable bool transformDirty_ = true; // 1 byte
|
||||
mutable bool worldTransformDirty_ = true; // 1 byte
|
||||
bool childrenOrderDirty_ = false; // 1 byte
|
||||
bool visible_ = true; // 1 byte
|
||||
bool running_ = false; // 1 byte
|
||||
bool spatialIndexed_ = true; // 1 byte
|
||||
// 填充 2 bytes 到 8 字节对齐
|
||||
mutable bool transformDirty_ = true;
|
||||
mutable bool worldTransformDirty_ = true;
|
||||
bool childrenOrderDirty_ = false;
|
||||
bool visible_ = true;
|
||||
bool running_ = false;
|
||||
bool spatialIndexed_ = true;
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
|
|||
|
|
@ -1,15 +1,13 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/core/color.h>
|
||||
#include <extra2d/graphics/camera.h>
|
||||
#include <extra2d/render/camera.h>
|
||||
#include <extra2d/scene/node.h>
|
||||
#include <extra2d/spatial/spatial_manager.h>
|
||||
#include <vector>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// 前向声明
|
||||
struct RenderCommand;
|
||||
class Renderer;
|
||||
|
||||
// ============================================================================
|
||||
|
|
@ -17,96 +15,75 @@ class Renderer;
|
|||
// ============================================================================
|
||||
class Scene : public Node {
|
||||
public:
|
||||
Scene();
|
||||
~Scene() override = default;
|
||||
Scene();
|
||||
~Scene() override = default;
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 场景属性
|
||||
// ------------------------------------------------------------------------
|
||||
void setBackgroundColor(const Color &color) { backgroundColor_ = color; }
|
||||
Color getBackgroundColor() const { return backgroundColor_; }
|
||||
// 场景属性
|
||||
void setBackgroundColor(const Color& color) { backgroundColor_ = color; }
|
||||
Color getBackgroundColor() const { return backgroundColor_; }
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 摄像机
|
||||
// ------------------------------------------------------------------------
|
||||
void setCamera(Ptr<Camera> camera);
|
||||
Ptr<Camera> getCamera() const { return camera_; }
|
||||
// 摄像机
|
||||
void setCamera(Ref<Camera> camera);
|
||||
Ref<Camera> getCamera() const { return camera_; }
|
||||
|
||||
Camera *getActiveCamera() const {
|
||||
return camera_ ? camera_.get() : defaultCamera_.get();
|
||||
}
|
||||
Camera* getActiveCamera() const {
|
||||
return camera_ ? camera_.get() : defaultCamera_.get();
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 视口和尺寸
|
||||
// ------------------------------------------------------------------------
|
||||
void setViewportSize(float width, float height);
|
||||
void setViewportSize(const Size &size);
|
||||
Size getViewportSize() const { return viewportSize_; }
|
||||
// 视口和尺寸
|
||||
void setViewportSize(float width, float height);
|
||||
void setViewportSize(const Size& size);
|
||||
Size getViewportSize() const { return viewportSize_; }
|
||||
|
||||
float getWidth() const { return viewportSize_.width; }
|
||||
float getHeight() const { return viewportSize_.height; }
|
||||
float getWidth() const { return viewportSize_.width; }
|
||||
float getHeight() const { return viewportSize_.height; }
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 场景状态
|
||||
// ------------------------------------------------------------------------
|
||||
bool isPaused() const { return paused_; }
|
||||
void pause() { paused_ = true; }
|
||||
void resume() { paused_ = false; }
|
||||
// 场景状态
|
||||
bool isPaused() const { return paused_; }
|
||||
void pause() { paused_ = true; }
|
||||
void resume() { paused_ = false; }
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 渲染和更新
|
||||
// ------------------------------------------------------------------------
|
||||
void renderScene(Renderer &renderer);
|
||||
virtual void renderContent(Renderer &renderer);
|
||||
void updateScene(float dt);
|
||||
void collectRenderCommands(std::vector<RenderCommand> &commands,
|
||||
int parentZOrder = 0) override;
|
||||
// 渲染和更新
|
||||
void renderScene(Renderer& renderer);
|
||||
virtual void renderContent(Renderer& renderer);
|
||||
void updateScene(float dt);
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 空间索引系统
|
||||
// ------------------------------------------------------------------------
|
||||
SpatialManager &getSpatialManager() { return spatialManager_; }
|
||||
const SpatialManager &getSpatialManager() const { return spatialManager_; }
|
||||
// 空间索引系统
|
||||
SpatialManager& getSpatialManager() { return spatialManager_; }
|
||||
const SpatialManager& getSpatialManager() const { return spatialManager_; }
|
||||
|
||||
// 启用/禁用空间索引
|
||||
void setSpatialIndexingEnabled(bool enabled) {
|
||||
spatialIndexingEnabled_ = enabled;
|
||||
}
|
||||
bool isSpatialIndexingEnabled() const { return spatialIndexingEnabled_; }
|
||||
void setSpatialIndexingEnabled(bool enabled) {
|
||||
spatialIndexingEnabled_ = enabled;
|
||||
}
|
||||
bool isSpatialIndexingEnabled() const { return spatialIndexingEnabled_; }
|
||||
|
||||
// 节点空间索引管理(内部使用)
|
||||
void updateNodeInSpatialIndex(Node *node, const Rect &oldBounds,
|
||||
const Rect &newBounds);
|
||||
void removeNodeFromSpatialIndex(Node *node);
|
||||
void updateNodeInSpatialIndex(Node* node, const Rect& oldBounds, const Rect& newBounds);
|
||||
void removeNodeFromSpatialIndex(Node* node);
|
||||
|
||||
// 碰撞检测查询
|
||||
std::vector<Node *> queryNodesInArea(const Rect &area) const;
|
||||
std::vector<Node *> queryNodesAtPoint(const Vec2 &point) const;
|
||||
std::vector<std::pair<Node *, Node *>> queryCollisions() const;
|
||||
std::vector<Node*> queryNodesInArea(const Rect& area) const;
|
||||
std::vector<Node*> queryNodesAtPoint(const Vec2& point) const;
|
||||
std::vector<std::pair<Node*, Node*>> queryCollisions() const;
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 静态创建方法
|
||||
// ------------------------------------------------------------------------
|
||||
static Ptr<Scene> create();
|
||||
// 静态创建方法
|
||||
static Ref<Scene> create();
|
||||
|
||||
protected:
|
||||
void onEnter() override;
|
||||
void onExit() override;
|
||||
void onEnter() override;
|
||||
void onExit() override;
|
||||
|
||||
friend class SceneManager;
|
||||
friend class SceneManager;
|
||||
|
||||
private:
|
||||
Color backgroundColor_ = Colors::Black;
|
||||
Size viewportSize_ = Size::Zero();
|
||||
Color backgroundColor_ = Colors::Black;
|
||||
Size viewportSize_ = Size::Zero();
|
||||
|
||||
Ptr<Camera> camera_;
|
||||
Ptr<Camera> defaultCamera_;
|
||||
Ref<Camera> camera_;
|
||||
Ref<Camera> defaultCamera_;
|
||||
|
||||
bool paused_ = false;
|
||||
bool paused_ = false;
|
||||
|
||||
// 空间索引系统
|
||||
SpatialManager spatialManager_;
|
||||
bool spatialIndexingEnabled_ = true;
|
||||
SpatialManager spatialManager_;
|
||||
bool spatialIndexingEnabled_ = true;
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
|
|||
|
|
@ -1,98 +1,62 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/core/types.h>
|
||||
#include <extra2d/render/renderer.h>
|
||||
#include <extra2d/scene/scene.h>
|
||||
#include <functional>
|
||||
#include <stack>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// 前向声明
|
||||
struct RenderCommand;
|
||||
class Renderer;
|
||||
|
||||
// ============================================================================
|
||||
// 场景管理器 - 管理场景的生命周期和切换
|
||||
// ============================================================================
|
||||
class SceneManager {
|
||||
public:
|
||||
// ------------------------------------------------------------------------
|
||||
// 单例访问
|
||||
// ------------------------------------------------------------------------
|
||||
static SceneManager &getInstance();
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 场景栈操作
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
// 运行第一个场景
|
||||
void runWithScene(Ptr<Scene> scene);
|
||||
|
||||
// 替换当前场景
|
||||
void replaceScene(Ptr<Scene> scene);
|
||||
|
||||
// 压入新场景(当前场景暂停)
|
||||
void pushScene(Ptr<Scene> scene);
|
||||
|
||||
// 弹出当前场景(恢复上一个场景)
|
||||
void runWithScene(Ref<Scene> scene);
|
||||
void replaceScene(Ref<Scene> scene);
|
||||
void pushScene(Ref<Scene> scene);
|
||||
void popScene();
|
||||
|
||||
// 弹出到根场景
|
||||
void popToRootScene();
|
||||
|
||||
// 弹出到指定场景
|
||||
void popToScene(const std::string &name);
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 获取场景
|
||||
// ------------------------------------------------------------------------
|
||||
Ptr<Scene> getCurrentScene() const;
|
||||
Ptr<Scene> getPreviousScene() const;
|
||||
Ptr<Scene> getRootScene() const;
|
||||
Ref<Scene> getCurrentScene() const;
|
||||
Ref<Scene> getPreviousScene() const;
|
||||
Ref<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(); }
|
||||
bool isEmpty() const { return sceneStack_.empty(); }
|
||||
bool hasScene(const std::string &name) const;
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 更新和渲染
|
||||
// ------------------------------------------------------------------------
|
||||
void update(float dt);
|
||||
void render(Renderer &renderer);
|
||||
void collectRenderCommands(std::vector<RenderCommand> &commands);
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 过渡控制
|
||||
// ------------------------------------------------------------------------
|
||||
bool isTransitioning() const { return false; }
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 清理
|
||||
// ------------------------------------------------------------------------
|
||||
void end();
|
||||
void purgeCachedScenes();
|
||||
|
||||
public:
|
||||
// 场景切换(供 Application 使用)
|
||||
void enterScene(Ref<Scene> scene);
|
||||
|
||||
private:
|
||||
SceneManager() = default;
|
||||
~SceneManager() = default;
|
||||
SceneManager(const SceneManager &) = delete;
|
||||
SceneManager &operator=(const SceneManager &) = delete;
|
||||
|
||||
// 场景切换(供 Application 使用)
|
||||
void enterScene(Ptr<Scene> scene);
|
||||
|
||||
private:
|
||||
std::stack<Ptr<Scene>> sceneStack_;
|
||||
std::unordered_map<std::string, Ptr<Scene>> namedScenes_;
|
||||
std::stack<Ref<Scene>> sceneStack_;
|
||||
std::unordered_map<std::string, Ref<Scene>> namedScenes_;
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
|
|||
|
|
@ -17,88 +17,66 @@ enum class ShapeType { Point, Line, Rect, Circle, Triangle, Polygon };
|
|||
// ============================================================================
|
||||
class ShapeNode : public Node {
|
||||
public:
|
||||
ShapeNode();
|
||||
~ShapeNode() override = default;
|
||||
ShapeNode();
|
||||
~ShapeNode() override = default;
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 静态创建方法
|
||||
// ------------------------------------------------------------------------
|
||||
static Ptr<ShapeNode> create();
|
||||
// 静态创建方法
|
||||
static Ref<ShapeNode> create();
|
||||
|
||||
// 点
|
||||
static Ptr<ShapeNode> createPoint(const Vec2 &pos, const Color &color);
|
||||
|
||||
// 线
|
||||
static Ptr<ShapeNode> createLine(const Vec2 &start, const Vec2 &end,
|
||||
const Color &color, float width = 1.0f);
|
||||
|
||||
// 矩形
|
||||
static Ptr<ShapeNode> createRect(const Rect &rect, const Color &color,
|
||||
float width = 1.0f);
|
||||
static Ptr<ShapeNode> createFilledRect(const Rect &rect, const Color &color);
|
||||
|
||||
// 圆形
|
||||
static Ptr<ShapeNode> createCircle(const Vec2 ¢er, float radius,
|
||||
const Color &color, int segments = 32,
|
||||
static Ref<ShapeNode> createPoint(const Vec2& pos, const Color& color);
|
||||
static Ref<ShapeNode> createLine(const Vec2& start, const Vec2& end,
|
||||
const Color& color, float width = 1.0f);
|
||||
static Ref<ShapeNode> createRect(const Rect& rect, const Color& color,
|
||||
float width = 1.0f);
|
||||
static Ptr<ShapeNode> createFilledCircle(const Vec2 ¢er, float radius,
|
||||
const Color &color,
|
||||
int segments = 32);
|
||||
|
||||
// 三角形
|
||||
static Ptr<ShapeNode> createTriangle(const Vec2 &p1, const Vec2 &p2,
|
||||
const Vec2 &p3, const Color &color,
|
||||
static Ref<ShapeNode> createFilledRect(const Rect& rect, const Color& color);
|
||||
static Ref<ShapeNode> createCircle(const Vec2& center, float radius,
|
||||
const Color& color, int segments = 32,
|
||||
float width = 1.0f);
|
||||
static Ptr<ShapeNode> createFilledTriangle(const Vec2 &p1, const Vec2 &p2,
|
||||
const Vec2 &p3,
|
||||
const Color &color);
|
||||
static Ref<ShapeNode> createFilledCircle(const Vec2& center, float radius,
|
||||
const Color& color, int segments = 32);
|
||||
static Ref<ShapeNode> createTriangle(const Vec2& p1, const Vec2& p2,
|
||||
const Vec2& p3, const Color& color,
|
||||
float width = 1.0f);
|
||||
static Ref<ShapeNode> createFilledTriangle(const Vec2& p1, const Vec2& p2,
|
||||
const Vec2& p3, const Color& color);
|
||||
static Ref<ShapeNode> createPolygon(const std::vector<Vec2>& points,
|
||||
const Color& color, float width = 1.0f);
|
||||
static Ref<ShapeNode> createFilledPolygon(const std::vector<Vec2>& points,
|
||||
const Color& color);
|
||||
|
||||
// 多边形
|
||||
static Ptr<ShapeNode> createPolygon(const std::vector<Vec2> &points,
|
||||
const Color &color, float width = 1.0f);
|
||||
static Ptr<ShapeNode> createFilledPolygon(const std::vector<Vec2> &points,
|
||||
const Color &color);
|
||||
// 属性设置
|
||||
void setShapeType(ShapeType type) { shapeType_ = type; }
|
||||
ShapeType getShapeType() const { return shapeType_; }
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 属性设置
|
||||
// ------------------------------------------------------------------------
|
||||
void setShapeType(ShapeType type) { shapeType_ = type; }
|
||||
ShapeType getShapeType() const { return shapeType_; }
|
||||
void setColor(const Color& color) { color_ = color; }
|
||||
Color getColor() const { return color_; }
|
||||
|
||||
void setColor(const Color &color) { color_ = color; }
|
||||
Color getColor() const { return color_; }
|
||||
void setFilled(bool filled) { filled_ = filled; }
|
||||
bool isFilled() const { return filled_; }
|
||||
|
||||
void setFilled(bool filled) { filled_ = filled; }
|
||||
bool isFilled() const { return filled_; }
|
||||
void setLineWidth(float width) { lineWidth_ = width; }
|
||||
float getLineWidth() const { return lineWidth_; }
|
||||
|
||||
void setLineWidth(float width) { lineWidth_ = width; }
|
||||
float getLineWidth() const { return lineWidth_; }
|
||||
void setSegments(int segments) { segments_ = segments; }
|
||||
int getSegments() const { return segments_; }
|
||||
|
||||
void setSegments(int segments) { segments_ = segments; }
|
||||
int getSegments() const { return segments_; }
|
||||
void setPoints(const std::vector<Vec2>& points);
|
||||
const std::vector<Vec2>& getPoints() const { return points_; }
|
||||
void addPoint(const Vec2& point);
|
||||
void clearPoints();
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 点设置
|
||||
// ------------------------------------------------------------------------
|
||||
void setPoints(const std::vector<Vec2> &points);
|
||||
const std::vector<Vec2> &getPoints() const { return points_; }
|
||||
void addPoint(const Vec2 &point);
|
||||
void clearPoints();
|
||||
|
||||
Rect getBoundingBox() const override;
|
||||
Rect getBoundingBox() const override;
|
||||
|
||||
protected:
|
||||
void onDraw(Renderer &renderer) override;
|
||||
void generateRenderCommand(std::vector<RenderCommand> &commands,
|
||||
int zOrder) override;
|
||||
void onDraw(Renderer& renderer) override;
|
||||
|
||||
private:
|
||||
ShapeType shapeType_ = ShapeType::Rect;
|
||||
Color color_ = Colors::White;
|
||||
bool filled_ = false;
|
||||
float lineWidth_ = 1.0f;
|
||||
int segments_ = 32;
|
||||
std::vector<Vec2> points_;
|
||||
ShapeType shapeType_ = ShapeType::Rect;
|
||||
Color color_ = Colors::White;
|
||||
bool filled_ = false;
|
||||
float lineWidth_ = 1.0f;
|
||||
int segments_ = 32;
|
||||
std::vector<Vec2> points_;
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/graphics/texture.h>
|
||||
#include <extra2d/render/texture.h>
|
||||
#include <extra2d/scene/node.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
|
@ -10,46 +10,44 @@ namespace extra2d {
|
|||
// ============================================================================
|
||||
class Sprite : public Node {
|
||||
public:
|
||||
Sprite();
|
||||
explicit Sprite(Ptr<Texture> texture);
|
||||
~Sprite() override = default;
|
||||
Sprite();
|
||||
explicit Sprite(Ref<Texture> texture);
|
||||
~Sprite() override = default;
|
||||
|
||||
// 纹理
|
||||
void setTexture(Ptr<Texture> texture);
|
||||
Ptr<Texture> getTexture() const { return texture_; }
|
||||
// 纹理
|
||||
void setTexture(Ref<Texture> texture);
|
||||
Ref<Texture> getTexture() const { return texture_; }
|
||||
|
||||
// 纹理矩形 (用于图集)
|
||||
void setTextureRect(const Rect &rect);
|
||||
Rect getTextureRect() const { return textureRect_; }
|
||||
// 纹理矩形 (用于图集)
|
||||
void setTextureRect(const Rect& rect);
|
||||
Rect getTextureRect() const { return textureRect_; }
|
||||
|
||||
// 颜色混合
|
||||
void setColor(const Color &color);
|
||||
Color getColor() const { return color_; }
|
||||
// 颜色混合
|
||||
void setColor(const Color& color);
|
||||
Color getColor() const { return color_; }
|
||||
|
||||
// 翻转
|
||||
void setFlipX(bool flip);
|
||||
void setFlipY(bool flip);
|
||||
bool isFlipX() const { return flipX_; }
|
||||
bool isFlipY() const { return flipY_; }
|
||||
// 翻转
|
||||
void setFlipX(bool flip);
|
||||
void setFlipY(bool flip);
|
||||
bool isFlipX() const { return flipX_; }
|
||||
bool isFlipY() const { return flipY_; }
|
||||
|
||||
// 静态创建方法
|
||||
static Ptr<Sprite> create();
|
||||
static Ptr<Sprite> create(Ptr<Texture> texture);
|
||||
static Ptr<Sprite> create(Ptr<Texture> texture, const Rect &rect);
|
||||
// 静态创建方法
|
||||
static Ref<Sprite> create();
|
||||
static Ref<Sprite> create(Ref<Texture> texture);
|
||||
static Ref<Sprite> create(Ref<Texture> texture, const Rect& rect);
|
||||
|
||||
Rect getBoundingBox() const override;
|
||||
Rect getBoundingBox() const override;
|
||||
|
||||
protected:
|
||||
void onDraw(Renderer &renderer) override;
|
||||
void generateRenderCommand(std::vector<RenderCommand> &commands,
|
||||
int zOrder) override;
|
||||
void onDraw(Renderer& renderer) override;
|
||||
|
||||
private:
|
||||
Ptr<Texture> texture_;
|
||||
Rect textureRect_;
|
||||
Color color_ = Colors::White;
|
||||
bool flipX_ = false;
|
||||
bool flipY_ = false;
|
||||
Ref<Texture> texture_;
|
||||
Rect textureRect_;
|
||||
Color color_ = Colors::White;
|
||||
bool flipX_ = false;
|
||||
bool flipY_ = false;
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <extra2d/core/service_interface.h>
|
||||
#include <extra2d/core/service_locator.h>
|
||||
#include <extra2d/core/types.h>
|
||||
|
|
@ -10,7 +9,7 @@
|
|||
namespace extra2d {
|
||||
|
||||
/**
|
||||
* @brief 日志颜色结构
|
||||
* @brief 日志颜色
|
||||
*/
|
||||
struct LogColor {
|
||||
u8 r, g, b;
|
||||
|
|
@ -18,36 +17,22 @@ struct LogColor {
|
|||
constexpr LogColor() : r(255), g(255), b(255) {}
|
||||
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 Gray() { return LogColor(128, 128, 128); }
|
||||
static constexpr LogColor Red() { return LogColor(255, 85, 85); }
|
||||
static constexpr LogColor Green() { return LogColor(85, 255, 85); }
|
||||
static constexpr LogColor Yellow() { return LogColor(255, 255, 85); }
|
||||
static constexpr LogColor Blue() { return LogColor(85, 85, 255); }
|
||||
static constexpr LogColor Magenta() { return LogColor(255, 85, 255); }
|
||||
static constexpr LogColor Cyan() { return LogColor(85, 255, 255); }
|
||||
static constexpr LogColor Orange() { return LogColor(255, 165, 0); }
|
||||
|
||||
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); }
|
||||
static constexpr LogColor White() { return {255, 255, 255}; }
|
||||
static constexpr LogColor Gray() { return {128, 128, 128}; }
|
||||
static constexpr LogColor Red() { return {255, 85, 85}; }
|
||||
static constexpr LogColor Green() { return {85, 255, 85}; }
|
||||
static constexpr LogColor Yellow() { return {255, 255, 85}; }
|
||||
static constexpr LogColor Blue() { return {85, 85, 255}; }
|
||||
static constexpr LogColor Magenta() { return {255, 85, 255}; }
|
||||
static constexpr LogColor Cyan() { return {85, 255, 255}; }
|
||||
static constexpr LogColor SkyLight() { return {125, 211, 252}; }
|
||||
static constexpr LogColor IndigoLight() { return {165, 180, 252}; }
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 日志级别枚举
|
||||
* @brief 日志级别
|
||||
*/
|
||||
enum class LogLevel {
|
||||
enum class LogLevel : u8 {
|
||||
Trace = 0,
|
||||
Debug = 1,
|
||||
Info = 2,
|
||||
|
|
@ -65,102 +50,36 @@ class ILogger : public IService {
|
|||
public:
|
||||
virtual ~ILogger() = default;
|
||||
|
||||
/**
|
||||
* @brief 设置日志级别
|
||||
*/
|
||||
virtual void level(LogLevel lvl) = 0;
|
||||
|
||||
/**
|
||||
* @brief 获取日志级别
|
||||
*/
|
||||
virtual LogLevel level() const = 0;
|
||||
|
||||
/**
|
||||
* @brief 检查日志级别是否启用
|
||||
*/
|
||||
virtual bool enabled(LogLevel lvl) const = 0;
|
||||
|
||||
/**
|
||||
* @brief 记录日志(格式化)
|
||||
*/
|
||||
virtual void log(LogLevel lvl, const char *fmt, ...) = 0;
|
||||
|
||||
/**
|
||||
* @brief 记录日志(字符串)
|
||||
*/
|
||||
virtual void log(LogLevel lvl, const std::string &msg) = 0;
|
||||
|
||||
/**
|
||||
* @brief Trace级别日志
|
||||
*/
|
||||
virtual void trace(const char *fmt, ...) = 0;
|
||||
|
||||
/**
|
||||
* @brief Debug级别日志
|
||||
*/
|
||||
virtual void debug(const char *fmt, ...) = 0;
|
||||
|
||||
/**
|
||||
* @brief Info级别日志
|
||||
*/
|
||||
virtual void info(const char *fmt, ...) = 0;
|
||||
|
||||
/**
|
||||
* @brief Registry级别日志(用于模块/服务注册显示)
|
||||
*/
|
||||
virtual void registry(const char *fmt, ...) = 0;
|
||||
|
||||
/**
|
||||
* @brief Warn级别日志
|
||||
*/
|
||||
virtual void warn(const char *fmt, ...) = 0;
|
||||
|
||||
/**
|
||||
* @brief Error级别日志
|
||||
*/
|
||||
virtual void error(const char *fmt, ...) = 0;
|
||||
|
||||
/**
|
||||
* @brief Fatal级别日志
|
||||
*/
|
||||
virtual void fatal(const char *fmt, ...) = 0;
|
||||
|
||||
/**
|
||||
* @brief 设置日志级别颜色
|
||||
* @param lvl 日志级别
|
||||
* @param c 颜色
|
||||
*/
|
||||
virtual void levelColor(LogLevel lvl, const LogColor &c) = 0;
|
||||
|
||||
/**
|
||||
* @brief 获取日志级别颜色
|
||||
* @param lvl 日志级别
|
||||
* @return 颜色
|
||||
*/
|
||||
virtual LogColor levelColor(LogLevel lvl) const = 0;
|
||||
|
||||
/**
|
||||
* @brief 启用/禁用颜色输出
|
||||
* @param on 是否启用
|
||||
*/
|
||||
virtual void colors(bool on) = 0;
|
||||
|
||||
/**
|
||||
* @brief 是否启用颜色输出
|
||||
*/
|
||||
virtual bool colors() const = 0;
|
||||
|
||||
ServiceInfo info() const override {
|
||||
ServiceInfo i;
|
||||
i.name = "Logger";
|
||||
i.priority = ServicePriority::Core;
|
||||
i.enabled = true;
|
||||
return i;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 控制台日志服务实现
|
||||
* @brief 控制台日志服务
|
||||
*/
|
||||
class ConsoleLogger : public ILogger {
|
||||
public:
|
||||
|
|
@ -195,38 +114,32 @@ private:
|
|||
const char *levelString(LogLevel lvl);
|
||||
std::string ansiColor(LogLevel lvl);
|
||||
|
||||
LogLevel level_;
|
||||
bool colors_;
|
||||
LogLevel level_ = LogLevel::Info;
|
||||
bool colors_ = true;
|
||||
LogColor levelColors_[7];
|
||||
class Impl;
|
||||
Unique<Impl> impl_;
|
||||
|
||||
// 服务注册元数据
|
||||
E2D_AUTO_REGISTER_SERVICE(ILogger, ConsoleLogger);
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
||||
// 格式化辅助函数 - 将参数转换为字符串
|
||||
// 格式化辅助
|
||||
namespace extra2d {
|
||||
namespace detail {
|
||||
template <typename T> std::string to_string(T &&value) {
|
||||
using Decayed = std::decay_t<T>;
|
||||
if constexpr (std::is_same_v<Decayed, std::string>) {
|
||||
using D = std::decay_t<T>;
|
||||
if constexpr (std::is_same_v<D, std::string>)
|
||||
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)";
|
||||
} else if constexpr (std::is_arithmetic_v<Decayed>) {
|
||||
if constexpr (std::is_same_v<Decayed, bool>) {
|
||||
return value ? "true" : "false";
|
||||
} else if constexpr (std::is_floating_point_v<Decayed>) {
|
||||
return std::to_string(value);
|
||||
} else {
|
||||
return std::to_string(value);
|
||||
}
|
||||
} else {
|
||||
else if constexpr (std::is_same_v<D, bool>)
|
||||
return value ? "true" : "false";
|
||||
else if constexpr (std::is_arithmetic_v<D>)
|
||||
return std::to_string(value);
|
||||
else
|
||||
return "<?>";
|
||||
}
|
||||
}
|
||||
|
||||
inline void format_impl(std::string &result, const char *fmt) { result += fmt; }
|
||||
|
|
@ -234,42 +147,33 @@ inline void format_impl(std::string &result, const char *fmt) { result += fmt; }
|
|||
template <typename T, typename... Args>
|
||||
void format_impl(std::string &result, const char *fmt, T &&value,
|
||||
Args &&...args) {
|
||||
const char *p = fmt;
|
||||
while (*p) {
|
||||
if (*p == '{' && *(p + 1) == '}') {
|
||||
while (*fmt) {
|
||||
if (*fmt == '{' && *(fmt + 1) == '}') {
|
||||
result += to_string(std::forward<T>(value));
|
||||
format_impl(result, p + 2, std::forward<Args>(args)...);
|
||||
return;
|
||||
return format_impl(result, fmt + 2, std::forward<Args>(args)...);
|
||||
}
|
||||
result += *p++;
|
||||
result += *fmt++;
|
||||
}
|
||||
result += " ";
|
||||
result += to_string(std::forward<T>(value));
|
||||
format_impl(result, p, std::forward<Args>(args)...);
|
||||
result += " " + to_string(std::forward<T>(value));
|
||||
format_impl(result, fmt, std::forward<Args>(args)...);
|
||||
}
|
||||
} // namespace detail
|
||||
|
||||
template <typename... Args>
|
||||
std::string format_str(const char *fmt, Args &&...args) {
|
||||
if constexpr (sizeof...(args) == 0) {
|
||||
return std::string(fmt);
|
||||
} else {
|
||||
std::string result;
|
||||
detail::format_impl(result, fmt, std::forward<Args>(args)...);
|
||||
return result;
|
||||
}
|
||||
std::string result;
|
||||
detail::format_impl(result, fmt, std::forward<Args>(args)...);
|
||||
return result;
|
||||
}
|
||||
} // namespace extra2d
|
||||
|
||||
// 便捷宏 - 自动获取日志服务
|
||||
#define E2D_LOG(lvl, ...) \
|
||||
// 日志宏
|
||||
#define E2D_LOG(lvl, ...) \
|
||||
do { \
|
||||
if (auto logService = ::extra2d::ServiceLocator::instance() \
|
||||
.tryGet<::extra2d::ILogger>()) { \
|
||||
if (logService->enabled(lvl)) { \
|
||||
logService->log(lvl, ::extra2d::format_str(__VA_ARGS__)); \
|
||||
} \
|
||||
} \
|
||||
if (auto log = ::extra2d::ServiceLocator::instance() \
|
||||
.tryGet<::extra2d::ILogger>()) \
|
||||
if (log->enabled(lvl)) \
|
||||
log->log(lvl, ::extra2d::format_str(__VA_ARGS__)); \
|
||||
} while (0)
|
||||
|
||||
#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_ERROR(...) E2D_LOG(::extra2d::LogLevel::Error, __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,75 +1,49 @@
|
|||
#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/math_types.h>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
>>>>>>> 9bf328b1dca01df84a07724394abc9a238739869
|
||||
|
||||
#include <SDL.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
class Input;
|
||||
|
||||
struct WindowConfig {
|
||||
std::string title = "Extra2D Application";
|
||||
int width = 1280;
|
||||
int height = 720;
|
||||
bool fullscreen = true;
|
||||
bool resizable = false;
|
||||
bool vsync = true;
|
||||
int msaaSamples = 0;
|
||||
bool centerWindow = true;
|
||||
std::string title = "Extra2D Application";
|
||||
int width = 1280;
|
||||
int height = 720;
|
||||
bool fullscreen = true;
|
||||
bool resizable = false;
|
||||
bool centerWindow = true;
|
||||
};
|
||||
|
||||
class Window {
|
||||
public:
|
||||
Window();
|
||||
~Window();
|
||||
Window();
|
||||
~Window();
|
||||
|
||||
bool create(const WindowConfig &config);
|
||||
void destroy();
|
||||
bool create(const WindowConfig& config);
|
||||
void destroy();
|
||||
|
||||
void pollEvents();
|
||||
void swapBuffers();
|
||||
bool shouldClose() const;
|
||||
void setShouldClose(bool close);
|
||||
void pollEvents();
|
||||
bool shouldClose() const;
|
||||
void setShouldClose(bool close);
|
||||
|
||||
void setTitle(const std::string &title);
|
||||
void setSize(int width, int height);
|
||||
void setFullscreen(bool fullscreen);
|
||||
void setVSync(bool enabled);
|
||||
void setTitle(const std::string& title);
|
||||
void setSize(int width, int height);
|
||||
void setFullscreen(bool fullscreen);
|
||||
|
||||
int getWidth() const { return width_; }
|
||||
int getHeight() const { return height_; }
|
||||
bool isFullscreen() const { return fullscreen_; }
|
||||
bool isVSync() const { return vsync_; }
|
||||
int getWidth() const { return width_; }
|
||||
int getHeight() const { return height_; }
|
||||
bool isFullscreen() const { return fullscreen_; }
|
||||
|
||||
SDL_Window *getSDLWindow() const { return sdlWindow_; }
|
||||
SDL_GLContext getGLContext() const { return glContext_; }
|
||||
|
||||
Input *getInput() const { return input_.get(); }
|
||||
SDL_Window* sdlWindow() const { return sdlWindow_; }
|
||||
|
||||
private:
|
||||
SDL_Window *sdlWindow_;
|
||||
SDL_GLContext glContext_;
|
||||
|
||||
int width_;
|
||||
int height_;
|
||||
bool vsync_;
|
||||
bool shouldClose_;
|
||||
bool fullscreen_;
|
||||
UniquePtr<Input> input_;
|
||||
|
||||
bool initSDL(const WindowConfig &config);
|
||||
void deinitSDL();
|
||||
SDL_Window* sdlWindow_ = nullptr;
|
||||
int width_ = 1280;
|
||||
int height_ = 720;
|
||||
bool fullscreen_ = true;
|
||||
bool shouldClose_ = false;
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
|
|||
|
|
@ -1,68 +1,49 @@
|
|||
#include <extra2d/app/application.h>
|
||||
<<<<<<< HEAD
|
||||
#include <extra2d/audio/audio_engine.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/render/render_device.h>
|
||||
#include <extra2d/services/logger_service.h>
|
||||
#include <extra2d/window/window.h>
|
||||
|
||||
#include <chrono>
|
||||
#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
|
||||
#include <SDL.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
static f64 getTimeSeconds() {
|
||||
#ifdef __SWITCH__
|
||||
struct timespec ts;
|
||||
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::instance() {
|
||||
static Application app;
|
||||
return app;
|
||||
}
|
||||
|
||||
Application &Application::get() {
|
||||
static Application instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
Application::Application() { Registry::instance().setApp(this); }
|
||||
|
||||
Application::~Application() {
|
||||
if (initialized_) {
|
||||
shutdown();
|
||||
}
|
||||
}
|
||||
Application::~Application() { shutdown(); }
|
||||
|
||||
bool Application::init() {
|
||||
if (initialized_) {
|
||||
if (initialized_)
|
||||
return true;
|
||||
}
|
||||
|
||||
// 初始化所有模块(拓扑排序)
|
||||
// 服务通过 E2D_AUTO_REGISTER_SERVICE 宏自动注册
|
||||
if (!Registry::instance().init()) {
|
||||
E2D_INFO("Application initializing: {}", name);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// 初始化所有服务
|
||||
ServiceLocator::instance().init();
|
||||
auto &device = RenderDevice::instance();
|
||||
if (!device.init(window_->sdlWindow())) {
|
||||
E2D_ERROR("Failed to initialize render device");
|
||||
window_->destroy();
|
||||
delete window_;
|
||||
window_ = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
initialized_ = true;
|
||||
running_ = true;
|
||||
lastFrameTime_ = SDL_GetPerformanceCounter();
|
||||
|
||||
E2D_INFO("Application initialized successfully");
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -70,10 +51,17 @@ void Application::shutdown() {
|
|||
if (!initialized_)
|
||||
return;
|
||||
|
||||
E2D_INFO("Application shutting down");
|
||||
|
||||
RenderDevice::instance().shutdown();
|
||||
|
||||
if (window_) {
|
||||
window_->destroy();
|
||||
delete window_;
|
||||
window_ = nullptr;
|
||||
}
|
||||
|
||||
ServiceLocator::instance().shutdown();
|
||||
ServiceLocator::instance().clear();
|
||||
Registry::instance().shutdown();
|
||||
Registry::instance().clear();
|
||||
|
||||
initialized_ = false;
|
||||
running_ = false;
|
||||
|
|
@ -83,95 +71,33 @@ void Application::run() {
|
|||
if (!initialized_)
|
||||
return;
|
||||
|
||||
auto *winMod = get<WindowModule>();
|
||||
if (!winMod || !winMod->win())
|
||||
return;
|
||||
|
||||
lastFrameTime_ = getTimeSeconds();
|
||||
|
||||
while (running_ && !winMod->win()->shouldClose()) {
|
||||
while (running_ && !window_->shouldClose()) {
|
||||
mainLoop();
|
||||
}
|
||||
}
|
||||
|
||||
<<<<<<< HEAD
|
||||
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() {
|
||||
f64 currentTime = getTimeSeconds();
|
||||
dt_ = static_cast<f32>(currentTime - lastFrameTime_);
|
||||
u64 currentTime = SDL_GetPerformanceCounter();
|
||||
u64 frequency = SDL_GetPerformanceFrequency();
|
||||
dt_ = static_cast<f32>(currentTime - lastFrameTime_) /
|
||||
static_cast<f32>(frequency);
|
||||
lastFrameTime_ = currentTime;
|
||||
|
||||
totalTime_ += dt_;
|
||||
|
||||
frameCount_++;
|
||||
fpsTimer_ += dt_;
|
||||
|
||||
if (fpsTimer_ >= 1.0f) {
|
||||
fps_ = frameCount_;
|
||||
frameCount_ = 0;
|
||||
fpsTimer_ -= 1.0f;
|
||||
}
|
||||
|
||||
auto *winMod = get<WindowModule>();
|
||||
if (winMod && winMod->win()) {
|
||||
winMod->win()->poll();
|
||||
}
|
||||
|
||||
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;
|
||||
window_->pollEvents();
|
||||
ServiceLocator::instance().update(dt_);
|
||||
RenderDevice::instance().swapBuffers();
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
#include "extra2d/audio/audio_engine.h"
|
||||
#include "extra2d/audio/sound.h"
|
||||
#include "extra2d/utils/logger.h"
|
||||
#include <extra2d/services/logger_service.h>
|
||||
|
||||
#include <SDL2/SDL.h>
|
||||
#include <SDL2/SDL_mixer.h>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
#include <SDL2/SDL_mixer.h>
|
||||
#include <extra2d/audio/sound.h>
|
||||
#include <extra2d/utils/logger.h>
|
||||
#include <extra2d/services/logger_service.h>
|
||||
|
||||
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,208 +1,200 @@
|
|||
#include <extra2d/core/color.h>
|
||||
#include <extra2d/render/render_context.h>
|
||||
#include <extra2d/render/render_stats.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
RenderContext& RenderContext::instance() {
|
||||
static RenderContext instance;
|
||||
return instance;
|
||||
RenderContext &RenderContext::instance() {
|
||||
static RenderContext instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
bool RenderContext::init() {
|
||||
if (initialized_) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!device_.init()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
queue_.reserve(RenderQueue::INITIAL_CAPACITY);
|
||||
|
||||
viewport_ = {0, 0, 800, 600};
|
||||
scissor_ = {0, 0, 0, 0, false};
|
||||
clearColor_ = Color::black();
|
||||
|
||||
currentBlend_ = BlendState::opaque();
|
||||
currentDepth_ = DepthState{};
|
||||
currentRaster_ = RasterState{};
|
||||
|
||||
initialized_ = true;
|
||||
bool RenderContext::init(SDL_Window *window, const RenderDeviceConfig &config) {
|
||||
if (initialized_) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!device_.init(window, config)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
queue_.reserve(RenderQueue::INITIAL_CAPACITY);
|
||||
|
||||
viewport_ = {0, 0, 800, 600};
|
||||
scissor_ = {0, 0, 0, 0, false};
|
||||
clearColor_ = Colors::Black;
|
||||
|
||||
currentBlend_ = BlendState::opaque();
|
||||
currentDepth_ = DepthState{};
|
||||
currentRaster_ = RasterState{};
|
||||
|
||||
initialized_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void RenderContext::shutdown() {
|
||||
if (!initialized_) {
|
||||
return;
|
||||
}
|
||||
if (!initialized_) {
|
||||
return;
|
||||
}
|
||||
|
||||
queue_.clear();
|
||||
while (!viewportStack_.empty()) viewportStack_.pop();
|
||||
while (!scissorStack_.empty()) scissorStack_.pop();
|
||||
queue_.clear();
|
||||
while (!viewportStack_.empty())
|
||||
viewportStack_.pop();
|
||||
while (!scissorStack_.empty())
|
||||
scissorStack_.pop();
|
||||
|
||||
device_.shutdown();
|
||||
initialized_ = false;
|
||||
device_.shutdown();
|
||||
initialized_ = false;
|
||||
}
|
||||
|
||||
void RenderContext::beginFrame() {
|
||||
queue_.clear();
|
||||
resetStats();
|
||||
E2D_RENDER_STATS().reset();
|
||||
queue_.clear();
|
||||
resetStats();
|
||||
E2D_RENDER_STATS().reset();
|
||||
}
|
||||
|
||||
void RenderContext::endFrame() {
|
||||
flush();
|
||||
}
|
||||
void RenderContext::endFrame() { flush(); }
|
||||
|
||||
void RenderContext::submit(const RenderCommand& cmd) {
|
||||
queue_.addCommand(cmd);
|
||||
}
|
||||
void RenderContext::submit(const RenderCommand &cmd) { queue_.addCommand(cmd); }
|
||||
|
||||
void RenderContext::submit(RenderCommand&& cmd) {
|
||||
queue_.addCommand(std::move(cmd));
|
||||
void RenderContext::submit(RenderCommand &&cmd) {
|
||||
queue_.addCommand(std::move(cmd));
|
||||
}
|
||||
|
||||
void RenderContext::flush() {
|
||||
queue_.sort();
|
||||
queue_.sort();
|
||||
|
||||
for (const auto& cmd : queue_.commands()) {
|
||||
executeCommand(cmd);
|
||||
}
|
||||
for (const auto &cmd : queue_.commands()) {
|
||||
executeCommand(cmd);
|
||||
}
|
||||
|
||||
queue_.clear();
|
||||
queue_.clear();
|
||||
}
|
||||
|
||||
void RenderContext::executeCommand(const RenderCommand& cmd) {
|
||||
if (cmd.type == RenderCommandType::None) {
|
||||
return;
|
||||
void RenderContext::executeCommand(const RenderCommand &cmd) {
|
||||
if (cmd.type == RenderCommandType::None) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (cmd.type == RenderCommandType::Custom && cmd.customCallback) {
|
||||
cmd.customCallback();
|
||||
return;
|
||||
}
|
||||
|
||||
if (cmd.blend.enabled != currentBlend_.enabled ||
|
||||
cmd.blend.mode != currentBlend_.mode) {
|
||||
device_.setBlendState(cmd.blend);
|
||||
currentBlend_ = cmd.blend;
|
||||
}
|
||||
|
||||
if (cmd.depth.testEnabled != currentDepth_.testEnabled ||
|
||||
cmd.depth.writeEnabled != currentDepth_.writeEnabled) {
|
||||
device_.setDepthState(cmd.depth);
|
||||
currentDepth_ = cmd.depth;
|
||||
}
|
||||
|
||||
if (cmd.program != currentProgram_) {
|
||||
device_.bindProgram(cmd.program);
|
||||
currentProgram_ = cmd.program;
|
||||
}
|
||||
|
||||
if (cmd.texture != currentTexture_) {
|
||||
device_.bindTexture(0, cmd.texture);
|
||||
currentTexture_ = cmd.texture;
|
||||
}
|
||||
|
||||
if (cmd.vao != currentVAO_) {
|
||||
if (cmd.vao) {
|
||||
cmd.vao->bind();
|
||||
}
|
||||
currentVAO_ = cmd.vao;
|
||||
}
|
||||
|
||||
if (cmd.type == RenderCommandType::Custom && cmd.customCallback) {
|
||||
cmd.customCallback();
|
||||
return;
|
||||
}
|
||||
switch (cmd.type) {
|
||||
case RenderCommandType::DrawArrays:
|
||||
device_.drawArrays(cmd.primitive, cmd.firstVertex, cmd.vertexCount);
|
||||
break;
|
||||
|
||||
if (cmd.blend.enabled != currentBlend_.enabled ||
|
||||
cmd.blend.mode != currentBlend_.mode) {
|
||||
device_.setBlendState(cmd.blend);
|
||||
currentBlend_ = cmd.blend;
|
||||
}
|
||||
case RenderCommandType::DrawElements:
|
||||
device_.drawElements(cmd.primitive, cmd.indexCount, cmd.indexType,
|
||||
cmd.indices);
|
||||
break;
|
||||
|
||||
if (cmd.depth.testEnabled != currentDepth_.testEnabled ||
|
||||
cmd.depth.writeEnabled != currentDepth_.writeEnabled) {
|
||||
device_.setDepthState(cmd.depth);
|
||||
currentDepth_ = cmd.depth;
|
||||
}
|
||||
case RenderCommandType::DrawArraysInstanced:
|
||||
device_.drawArraysInstanced(cmd.primitive, cmd.firstVertex, cmd.vertexCount,
|
||||
cmd.instanceCount);
|
||||
break;
|
||||
|
||||
if (cmd.program != currentProgram_) {
|
||||
device_.bindProgram(cmd.program);
|
||||
currentProgram_ = cmd.program;
|
||||
}
|
||||
case RenderCommandType::DrawElementsInstanced:
|
||||
device_.drawElementsInstanced(cmd.primitive, cmd.indexCount, cmd.indexType,
|
||||
cmd.indices, cmd.instanceCount);
|
||||
break;
|
||||
|
||||
if (cmd.texture != currentTexture_) {
|
||||
device_.bindTexture(0, cmd.texture);
|
||||
currentTexture_ = cmd.texture;
|
||||
}
|
||||
|
||||
if (cmd.vao != currentVAO_) {
|
||||
if (cmd.vao) {
|
||||
cmd.vao->bind();
|
||||
}
|
||||
currentVAO_ = cmd.vao;
|
||||
}
|
||||
|
||||
switch (cmd.type) {
|
||||
case RenderCommandType::DrawArrays:
|
||||
device_.drawArrays(cmd.primitive, cmd.firstVertex, cmd.vertexCount);
|
||||
break;
|
||||
|
||||
case RenderCommandType::DrawElements:
|
||||
device_.drawElements(cmd.primitive, cmd.indexCount, cmd.indexType, cmd.indices);
|
||||
break;
|
||||
|
||||
case RenderCommandType::DrawArraysInstanced:
|
||||
device_.drawArraysInstanced(cmd.primitive, cmd.firstVertex,
|
||||
cmd.vertexCount, cmd.instanceCount);
|
||||
break;
|
||||
|
||||
case RenderCommandType::DrawElementsInstanced:
|
||||
device_.drawElementsInstanced(cmd.primitive, cmd.indexCount,
|
||||
cmd.indexType, cmd.indices, cmd.instanceCount);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void RenderContext::setViewport(int32 x, int32 y, int32 width, int32 height) {
|
||||
viewport_.x = x;
|
||||
viewport_.y = y;
|
||||
viewport_.width = width;
|
||||
viewport_.height = height;
|
||||
device_.setViewport(x, y, width, height);
|
||||
viewport_.x = x;
|
||||
viewport_.y = y;
|
||||
viewport_.width = width;
|
||||
viewport_.height = height;
|
||||
device_.setViewport(x, y, width, height);
|
||||
}
|
||||
|
||||
void RenderContext::setViewport(const Viewport& vp) {
|
||||
viewport_ = vp;
|
||||
device_.setViewport(vp.x, vp.y, vp.width, vp.height);
|
||||
void RenderContext::setViewport(const Viewport &vp) {
|
||||
viewport_ = vp;
|
||||
device_.setViewport(vp.x, vp.y, vp.width, vp.height);
|
||||
}
|
||||
|
||||
void RenderContext::setScissor(const ScissorRect& rect) {
|
||||
scissor_ = rect;
|
||||
device_.setScissorEnabled(rect.enabled);
|
||||
if (rect.enabled) {
|
||||
device_.setScissor(rect.x, rect.y, rect.width, rect.height);
|
||||
}
|
||||
void RenderContext::setScissor(const ScissorRect &rect) {
|
||||
scissor_ = rect;
|
||||
device_.setScissorEnabled(rect.enabled);
|
||||
if (rect.enabled) {
|
||||
device_.setScissor(rect.x, rect.y, rect.width, rect.height);
|
||||
}
|
||||
}
|
||||
|
||||
void RenderContext::setScissor(int32 x, int32 y, int32 width, int32 height) {
|
||||
scissor_.x = x;
|
||||
scissor_.y = y;
|
||||
scissor_.width = width;
|
||||
scissor_.height = height;
|
||||
scissor_.enabled = true;
|
||||
device_.setScissorEnabled(true);
|
||||
device_.setScissor(x, y, width, height);
|
||||
scissor_.x = x;
|
||||
scissor_.y = y;
|
||||
scissor_.width = width;
|
||||
scissor_.height = height;
|
||||
scissor_.enabled = true;
|
||||
device_.setScissorEnabled(true);
|
||||
device_.setScissor(x, y, width, height);
|
||||
}
|
||||
|
||||
void RenderContext::pushViewport() {
|
||||
viewportStack_.push(viewport_);
|
||||
}
|
||||
void RenderContext::pushViewport() { viewportStack_.push(viewport_); }
|
||||
|
||||
void RenderContext::popViewport() {
|
||||
if (!viewportStack_.empty()) {
|
||||
setViewport(viewportStack_.top());
|
||||
viewportStack_.pop();
|
||||
}
|
||||
if (!viewportStack_.empty()) {
|
||||
setViewport(viewportStack_.top());
|
||||
viewportStack_.pop();
|
||||
}
|
||||
}
|
||||
|
||||
void RenderContext::pushScissor() {
|
||||
scissorStack_.push(scissor_);
|
||||
}
|
||||
void RenderContext::pushScissor() { scissorStack_.push(scissor_); }
|
||||
|
||||
void RenderContext::popScissor() {
|
||||
if (!scissorStack_.empty()) {
|
||||
setScissor(scissorStack_.top());
|
||||
scissorStack_.pop();
|
||||
}
|
||||
if (!scissorStack_.empty()) {
|
||||
setScissor(scissorStack_.top());
|
||||
scissorStack_.pop();
|
||||
}
|
||||
}
|
||||
|
||||
void RenderContext::setClearColor(const Color& color) {
|
||||
clearColor_ = color;
|
||||
}
|
||||
void RenderContext::setClearColor(const Color &color) { clearColor_ = color; }
|
||||
|
||||
void RenderContext::clear(bool color, bool depth, bool stencil) {
|
||||
device_.clear(color, depth, stencil,
|
||||
clearColor_.r, clearColor_.g, clearColor_.b, clearColor_.a);
|
||||
device_.clear(color, depth, stencil, clearColor_.r, clearColor_.g,
|
||||
clearColor_.b, clearColor_.a);
|
||||
}
|
||||
|
||||
const RenderStats& RenderContext::stats() const {
|
||||
return E2D_RENDER_STATS().stats();
|
||||
const RenderStats &RenderContext::stats() const {
|
||||
return E2D_RENDER_STATS().stats();
|
||||
}
|
||||
|
||||
void RenderContext::resetStats() {
|
||||
E2D_RENDER_STATS().reset();
|
||||
}
|
||||
void RenderContext::resetStats() { E2D_RENDER_STATS().reset(); }
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
|
|||
|
|
@ -1,322 +1,388 @@
|
|||
#include <cstdio>
|
||||
#include <extra2d/render/render_device.h>
|
||||
#include <extra2d/render/render_stats.h>
|
||||
#include <cstdio>
|
||||
#include <extra2d/services/logger_service.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
RenderDevice& RenderDevice::instance() {
|
||||
static RenderDevice instance;
|
||||
return instance;
|
||||
RenderDevice &RenderDevice::instance() {
|
||||
static RenderDevice instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
bool RenderDevice::init() {
|
||||
if (initialized_) {
|
||||
return true;
|
||||
}
|
||||
|
||||
GLint majorVersion = 0;
|
||||
GLint minorVersion = 0;
|
||||
glGetIntegerv(GL_MAJOR_VERSION, &majorVersion);
|
||||
glGetIntegerv(GL_MINOR_VERSION, &minorVersion);
|
||||
|
||||
if (majorVersion < 4 || (majorVersion == 4 && minorVersion < 5)) {
|
||||
printf("OpenGL 4.5 is required, got %d.%d\n", majorVersion, minorVersion);
|
||||
return false;
|
||||
}
|
||||
|
||||
queryCaps();
|
||||
|
||||
if (!caps_.dsaSupported) {
|
||||
printf("OpenGL 4.5 DSA is not supported\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
glEnable(GL_TEXTURE_2D);
|
||||
|
||||
currentBlend_ = BlendState::opaque();
|
||||
currentDepth_ = DepthState{};
|
||||
currentRaster_ = RasterState{};
|
||||
|
||||
initialized_ = true;
|
||||
bool RenderDevice::init(SDL_Window *window, const RenderDeviceConfig &config) {
|
||||
if (initialized_)
|
||||
return true;
|
||||
|
||||
window_ = window;
|
||||
|
||||
if (!initGL(config)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
queryCaps();
|
||||
|
||||
currentBlend_ = BlendState::opaque();
|
||||
currentDepth_ = DepthState{};
|
||||
currentRaster_ = RasterState{};
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
void RenderDevice::shutdown() {
|
||||
if (!initialized_) {
|
||||
return;
|
||||
}
|
||||
if (!initialized_)
|
||||
return;
|
||||
|
||||
currentProgram_ = 0;
|
||||
initialized_ = false;
|
||||
if (glContext_) {
|
||||
SDL_GL_DeleteContext(glContext_);
|
||||
glContext_ = nullptr;
|
||||
}
|
||||
|
||||
window_ = nullptr;
|
||||
currentProgram_ = 0;
|
||||
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() {
|
||||
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &caps_.maxTextureSize);
|
||||
glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &caps_.maxTextureUnits);
|
||||
glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &caps_.maxVertexAttribs);
|
||||
glGetIntegerv(GL_MAX_UNIFORM_BUFFER_BINDINGS, &caps_.maxUniformBlockBindings);
|
||||
glGetIntegerv(GL_MAX_UNIFORM_BLOCK_SIZE, &caps_.maxUniformBlockSize);
|
||||
glGetIntegerv(GL_MAX_VERTEX_UNIFORM_COMPONENTS, &caps_.maxVertexUniformComponents);
|
||||
glGetIntegerv(GL_MAX_FRAGMENT_UNIFORM_COMPONENTS, &caps_.maxFragmentUniformComponents);
|
||||
glGetIntegerv(GL_MAX_DRAW_BUFFERS, &caps_.maxDrawBuffers);
|
||||
glGetIntegerv(GL_MAX_SAMPLES, &caps_.maxSamples);
|
||||
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &caps_.maxTextureSize);
|
||||
glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &caps_.maxTextureUnits);
|
||||
glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &caps_.maxVertexAttribs);
|
||||
glGetIntegerv(GL_MAX_UNIFORM_BUFFER_BINDINGS, &caps_.maxUniformBlockBindings);
|
||||
glGetIntegerv(GL_MAX_UNIFORM_BLOCK_SIZE, &caps_.maxUniformBlockSize);
|
||||
glGetIntegerv(GL_MAX_VERTEX_UNIFORM_COMPONENTS,
|
||||
&caps_.maxVertexUniformComponents);
|
||||
glGetIntegerv(GL_MAX_FRAGMENT_UNIFORM_COMPONENTS,
|
||||
&caps_.maxFragmentUniformComponents);
|
||||
glGetIntegerv(GL_MAX_DRAW_BUFFERS, &caps_.maxDrawBuffers);
|
||||
glGetIntegerv(GL_MAX_SAMPLES, &caps_.maxSamples);
|
||||
|
||||
GLint majorVersion = 0;
|
||||
GLint minorVersion = 0;
|
||||
glGetIntegerv(GL_MAJOR_VERSION, &majorVersion);
|
||||
glGetIntegerv(GL_MINOR_VERSION, &minorVersion);
|
||||
caps_.dsaSupported = (majorVersion > 4 || (majorVersion == 4 && minorVersion >= 5));
|
||||
GLint majorVersion = 0;
|
||||
GLint minorVersion = 0;
|
||||
glGetIntegerv(GL_MAJOR_VERSION, &majorVersion);
|
||||
glGetIntegerv(GL_MINOR_VERSION, &minorVersion);
|
||||
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_.vendor = reinterpret_cast<const char*>(glGetString(GL_VENDOR));
|
||||
caps_.version = reinterpret_cast<const char*>(glGetString(GL_VERSION));
|
||||
caps_.glslVersion = reinterpret_cast<const char*>(glGetString(GL_SHADING_LANGUAGE_VERSION));
|
||||
caps_.renderer = reinterpret_cast<const char *>(glGetString(GL_RENDERER));
|
||||
caps_.vendor = reinterpret_cast<const char *>(glGetString(GL_VENDOR));
|
||||
caps_.version = reinterpret_cast<const char *>(glGetString(GL_VERSION));
|
||||
caps_.glslVersion =
|
||||
reinterpret_cast<const char *>(glGetString(GL_SHADING_LANGUAGE_VERSION));
|
||||
}
|
||||
|
||||
std::unique_ptr<Buffer> RenderDevice::createBuffer(BufferType type, BufferUsage usage,
|
||||
size_t size, const void* data) {
|
||||
auto buffer = std::make_unique<Buffer>();
|
||||
if (!buffer->create(type, usage, size, data)) {
|
||||
return nullptr;
|
||||
}
|
||||
return buffer;
|
||||
std::unique_ptr<Buffer> RenderDevice::createBuffer(BufferType type,
|
||||
BufferUsage usage,
|
||||
size_t size,
|
||||
const void *data) {
|
||||
auto buffer = std::make_unique<Buffer>();
|
||||
if (!buffer->create(type, usage, size, data)) {
|
||||
return nullptr;
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
std::unique_ptr<VertexBuffer> RenderDevice::createVertexBuffer(BufferUsage usage, size_t size,
|
||||
const void* data) {
|
||||
auto buffer = std::make_unique<VertexBuffer>();
|
||||
if (!buffer->create(usage, size, data)) {
|
||||
return nullptr;
|
||||
}
|
||||
return buffer;
|
||||
std::unique_ptr<VertexBuffer>
|
||||
RenderDevice::createVertexBuffer(BufferUsage usage, size_t size,
|
||||
const void *data) {
|
||||
auto buffer = std::make_unique<VertexBuffer>();
|
||||
if (!buffer->create(usage, size, data)) {
|
||||
return nullptr;
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
std::unique_ptr<IndexBuffer> RenderDevice::createIndexBuffer(BufferUsage usage, size_t size,
|
||||
const void* data) {
|
||||
auto buffer = std::make_unique<IndexBuffer>();
|
||||
if (!buffer->create(usage, size, data)) {
|
||||
return nullptr;
|
||||
}
|
||||
return buffer;
|
||||
std::unique_ptr<IndexBuffer> RenderDevice::createIndexBuffer(BufferUsage usage,
|
||||
size_t size,
|
||||
const void *data) {
|
||||
auto buffer = std::make_unique<IndexBuffer>();
|
||||
if (!buffer->create(usage, size, data)) {
|
||||
return nullptr;
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
std::unique_ptr<UniformBuffer> RenderDevice::createUniformBuffer(BufferUsage usage, size_t size,
|
||||
const void* data) {
|
||||
auto buffer = std::make_unique<UniformBuffer>();
|
||||
if (!buffer->create(usage, size, data)) {
|
||||
return nullptr;
|
||||
}
|
||||
return buffer;
|
||||
std::unique_ptr<UniformBuffer>
|
||||
RenderDevice::createUniformBuffer(BufferUsage usage, size_t size,
|
||||
const void *data) {
|
||||
auto buffer = std::make_unique<UniformBuffer>();
|
||||
if (!buffer->create(usage, size, data)) {
|
||||
return nullptr;
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
std::unique_ptr<VAO> RenderDevice::createVAO() {
|
||||
auto vao = std::make_unique<VAO>();
|
||||
if (!vao->create()) {
|
||||
return nullptr;
|
||||
}
|
||||
return vao;
|
||||
auto vao = std::make_unique<VAO>();
|
||||
if (!vao->create()) {
|
||||
return nullptr;
|
||||
}
|
||||
return vao;
|
||||
}
|
||||
|
||||
void RenderDevice::setViewport(int32 x, int32 y, int32 width, int32 height) {
|
||||
glViewport(x, y, width, height);
|
||||
glViewport(x, y, width, height);
|
||||
}
|
||||
|
||||
void RenderDevice::setScissor(int32 x, int32 y, int32 width, int32 height) {
|
||||
glScissor(x, y, width, height);
|
||||
glScissor(x, y, width, height);
|
||||
}
|
||||
|
||||
void RenderDevice::setScissorEnabled(bool enabled) {
|
||||
if (enabled) {
|
||||
glEnable(GL_SCISSOR_TEST);
|
||||
} else {
|
||||
glDisable(GL_SCISSOR_TEST);
|
||||
}
|
||||
if (enabled) {
|
||||
glEnable(GL_SCISSOR_TEST);
|
||||
} else {
|
||||
glDisable(GL_SCISSOR_TEST);
|
||||
}
|
||||
}
|
||||
|
||||
void RenderDevice::clear(bool color, bool depth, bool stencil,
|
||||
float r, float g, float b, float a) {
|
||||
GLbitfield mask = 0;
|
||||
if (color) {
|
||||
glClearColor(r, g, b, a);
|
||||
mask |= GL_COLOR_BUFFER_BIT;
|
||||
}
|
||||
if (depth) {
|
||||
glClearDepth(1.0);
|
||||
mask |= GL_DEPTH_BUFFER_BIT;
|
||||
}
|
||||
if (stencil) {
|
||||
glClearStencil(0);
|
||||
mask |= GL_STENCIL_BUFFER_BIT;
|
||||
}
|
||||
glClear(mask);
|
||||
void RenderDevice::clear(bool color, bool depth, bool stencil, float r, float g,
|
||||
float b, float a) {
|
||||
GLbitfield mask = 0;
|
||||
if (color) {
|
||||
glClearColor(r, g, b, a);
|
||||
mask |= GL_COLOR_BUFFER_BIT;
|
||||
}
|
||||
if (depth) {
|
||||
glClearDepth(1.0);
|
||||
mask |= GL_DEPTH_BUFFER_BIT;
|
||||
}
|
||||
if (stencil) {
|
||||
glClearStencil(0);
|
||||
mask |= GL_STENCIL_BUFFER_BIT;
|
||||
}
|
||||
glClear(mask);
|
||||
}
|
||||
|
||||
void RenderDevice::setBlendState(const BlendState& state) {
|
||||
if (state.enabled != currentBlend_.enabled) {
|
||||
if (state.enabled) {
|
||||
glEnable(GL_BLEND);
|
||||
} else {
|
||||
glDisable(GL_BLEND);
|
||||
}
|
||||
E2D_RENDER_STATS().addStateChange();
|
||||
}
|
||||
|
||||
void RenderDevice::setBlendState(const BlendState &state) {
|
||||
if (state.enabled != currentBlend_.enabled) {
|
||||
if (state.enabled) {
|
||||
if (state.srcRGB != currentBlend_.srcRGB ||
|
||||
state.dstRGB != currentBlend_.dstRGB ||
|
||||
state.srcAlpha != currentBlend_.srcAlpha ||
|
||||
state.dstAlpha != currentBlend_.dstAlpha) {
|
||||
glBlendFuncSeparate(
|
||||
static_cast<GLenum>(state.srcRGB) + GL_ZERO,
|
||||
static_cast<GLenum>(state.dstRGB) + GL_ZERO,
|
||||
static_cast<GLenum>(state.srcAlpha) + GL_ZERO,
|
||||
static_cast<GLenum>(state.dstAlpha) + GL_ZERO
|
||||
);
|
||||
E2D_RENDER_STATS().addStateChange();
|
||||
}
|
||||
glEnable(GL_BLEND);
|
||||
} else {
|
||||
glDisable(GL_BLEND);
|
||||
}
|
||||
E2D_RENDER_STATS().addStateChange();
|
||||
}
|
||||
|
||||
if (state.opRGB != currentBlend_.opRGB || state.opAlpha != currentBlend_.opAlpha) {
|
||||
glBlendEquationSeparate(
|
||||
static_cast<GLenum>(state.opRGB) + GL_FUNC_ADD,
|
||||
static_cast<GLenum>(state.opAlpha) + GL_FUNC_ADD
|
||||
);
|
||||
E2D_RENDER_STATS().addStateChange();
|
||||
}
|
||||
|
||||
if (state.colorWriteMask != currentBlend_.colorWriteMask) {
|
||||
glColorMask(
|
||||
(state.colorWriteMask & 0x1) != 0,
|
||||
(state.colorWriteMask & 0x2) != 0,
|
||||
(state.colorWriteMask & 0x4) != 0,
|
||||
(state.colorWriteMask & 0x8) != 0
|
||||
);
|
||||
E2D_RENDER_STATS().addStateChange();
|
||||
}
|
||||
if (state.enabled) {
|
||||
if (state.srcRGB != currentBlend_.srcRGB ||
|
||||
state.dstRGB != currentBlend_.dstRGB ||
|
||||
state.srcAlpha != currentBlend_.srcAlpha ||
|
||||
state.dstAlpha != currentBlend_.dstAlpha) {
|
||||
glBlendFuncSeparate(static_cast<GLenum>(state.srcRGB) + GL_ZERO,
|
||||
static_cast<GLenum>(state.dstRGB) + GL_ZERO,
|
||||
static_cast<GLenum>(state.srcAlpha) + GL_ZERO,
|
||||
static_cast<GLenum>(state.dstAlpha) + GL_ZERO);
|
||||
E2D_RENDER_STATS().addStateChange();
|
||||
}
|
||||
|
||||
currentBlend_ = state;
|
||||
if (state.opRGB != currentBlend_.opRGB ||
|
||||
state.opAlpha != currentBlend_.opAlpha) {
|
||||
glBlendEquationSeparate(static_cast<GLenum>(state.opRGB) + GL_FUNC_ADD,
|
||||
static_cast<GLenum>(state.opAlpha) + GL_FUNC_ADD);
|
||||
E2D_RENDER_STATS().addStateChange();
|
||||
}
|
||||
|
||||
if (state.colorWriteMask != currentBlend_.colorWriteMask) {
|
||||
glColorMask(
|
||||
(state.colorWriteMask & 0x1) != 0, (state.colorWriteMask & 0x2) != 0,
|
||||
(state.colorWriteMask & 0x4) != 0, (state.colorWriteMask & 0x8) != 0);
|
||||
E2D_RENDER_STATS().addStateChange();
|
||||
}
|
||||
}
|
||||
|
||||
currentBlend_ = state;
|
||||
}
|
||||
|
||||
void RenderDevice::setDepthState(const DepthState& state) {
|
||||
if (state.testEnabled != currentDepth_.testEnabled) {
|
||||
if (state.testEnabled) {
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
} else {
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
}
|
||||
E2D_RENDER_STATS().addStateChange();
|
||||
void RenderDevice::setDepthState(const DepthState &state) {
|
||||
if (state.testEnabled != currentDepth_.testEnabled) {
|
||||
if (state.testEnabled) {
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
} else {
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
}
|
||||
E2D_RENDER_STATS().addStateChange();
|
||||
}
|
||||
|
||||
if (state.testEnabled && state.compareFunc != currentDepth_.compareFunc) {
|
||||
glDepthFunc(static_cast<GLenum>(state.compareFunc) + GL_NEVER);
|
||||
E2D_RENDER_STATS().addStateChange();
|
||||
}
|
||||
if (state.testEnabled && state.compareFunc != currentDepth_.compareFunc) {
|
||||
glDepthFunc(static_cast<GLenum>(state.compareFunc) + GL_NEVER);
|
||||
E2D_RENDER_STATS().addStateChange();
|
||||
}
|
||||
|
||||
if (state.writeEnabled != currentDepth_.writeEnabled) {
|
||||
glDepthMask(state.writeEnabled ? GL_TRUE : GL_FALSE);
|
||||
E2D_RENDER_STATS().addStateChange();
|
||||
}
|
||||
if (state.writeEnabled != currentDepth_.writeEnabled) {
|
||||
glDepthMask(state.writeEnabled ? GL_TRUE : GL_FALSE);
|
||||
E2D_RENDER_STATS().addStateChange();
|
||||
}
|
||||
|
||||
currentDepth_ = state;
|
||||
currentDepth_ = state;
|
||||
}
|
||||
|
||||
void RenderDevice::setRasterState(const RasterState& state) {
|
||||
if (state.cullMode != currentRaster_.cullMode) {
|
||||
if (state.cullMode == CullMode::None) {
|
||||
glDisable(GL_CULL_FACE);
|
||||
} else {
|
||||
glEnable(GL_CULL_FACE);
|
||||
glCullFace(state.cullMode == CullMode::Front ? GL_FRONT : GL_BACK);
|
||||
}
|
||||
E2D_RENDER_STATS().addStateChange();
|
||||
void RenderDevice::setRasterState(const RasterState &state) {
|
||||
if (state.cullMode != currentRaster_.cullMode) {
|
||||
if (state.cullMode == CullMode::None) {
|
||||
glDisable(GL_CULL_FACE);
|
||||
} else {
|
||||
glEnable(GL_CULL_FACE);
|
||||
glCullFace(state.cullMode == CullMode::Front ? GL_FRONT : GL_BACK);
|
||||
}
|
||||
E2D_RENDER_STATS().addStateChange();
|
||||
}
|
||||
|
||||
if (state.polygonMode != currentRaster_.polygonMode) {
|
||||
glPolygonMode(GL_FRONT_AND_BACK,
|
||||
state.polygonMode == PolygonMode::Line ? GL_LINE :
|
||||
(state.polygonMode == PolygonMode::Point ? GL_POINT : GL_FILL));
|
||||
E2D_RENDER_STATS().addStateChange();
|
||||
}
|
||||
if (state.polygonMode != currentRaster_.polygonMode) {
|
||||
glPolygonMode(
|
||||
GL_FRONT_AND_BACK,
|
||||
state.polygonMode == PolygonMode::Line
|
||||
? GL_LINE
|
||||
: (state.polygonMode == PolygonMode::Point ? GL_POINT : GL_FILL));
|
||||
E2D_RENDER_STATS().addStateChange();
|
||||
}
|
||||
|
||||
if (state.lineWidth != currentRaster_.lineWidth) {
|
||||
glLineWidth(state.lineWidth);
|
||||
}
|
||||
if (state.lineWidth != currentRaster_.lineWidth) {
|
||||
glLineWidth(state.lineWidth);
|
||||
}
|
||||
|
||||
currentRaster_ = state;
|
||||
currentRaster_ = state;
|
||||
}
|
||||
|
||||
void RenderDevice::bindTexture(uint32 unit, GLuint texture) {
|
||||
glBindTextureUnit(unit, texture);
|
||||
E2D_RENDER_STATS().addTextureBind();
|
||||
glBindTextureUnit(unit, texture);
|
||||
E2D_RENDER_STATS().addTextureBind();
|
||||
}
|
||||
|
||||
void RenderDevice::bindProgram(GLuint program) {
|
||||
if (program != currentProgram_) {
|
||||
glUseProgram(program);
|
||||
currentProgram_ = program;
|
||||
E2D_RENDER_STATS().addShaderBind();
|
||||
}
|
||||
if (program != currentProgram_) {
|
||||
glUseProgram(program);
|
||||
currentProgram_ = program;
|
||||
E2D_RENDER_STATS().addShaderBind();
|
||||
}
|
||||
}
|
||||
|
||||
void RenderDevice::drawArrays(PrimitiveType mode, int32 first, int32 count) {
|
||||
glDrawArrays(glPrimitiveType(mode), first, count);
|
||||
E2D_RENDER_STATS().addDrawCall(count, count / 3);
|
||||
glDrawArrays(glPrimitiveType(mode), first, count);
|
||||
E2D_RENDER_STATS().addDrawCall(count, count / 3);
|
||||
}
|
||||
|
||||
void RenderDevice::drawElements(PrimitiveType mode, int32 count, IndexType type,
|
||||
const void* indices) {
|
||||
glDrawElements(glPrimitiveType(mode), count, glIndexType(type), indices);
|
||||
E2D_RENDER_STATS().addDrawCall(count, count / 3);
|
||||
void RenderDevice::drawElements(PrimitiveType mode, int32 count, IndexType type,
|
||||
const void *indices) {
|
||||
glDrawElements(glPrimitiveType(mode), count, glIndexType(type), indices);
|
||||
E2D_RENDER_STATS().addDrawCall(count, count / 3);
|
||||
}
|
||||
|
||||
void RenderDevice::drawArraysInstanced(PrimitiveType mode, int32 first,
|
||||
int32 count, int32 instanceCount) {
|
||||
glDrawArraysInstanced(glPrimitiveType(mode), first, count, instanceCount);
|
||||
E2D_RENDER_STATS().addDrawCall(count * instanceCount, (count / 3) * instanceCount);
|
||||
void RenderDevice::drawArraysInstanced(PrimitiveType mode, int32 first,
|
||||
int32 count, int32 instanceCount) {
|
||||
glDrawArraysInstanced(glPrimitiveType(mode), first, count, instanceCount);
|
||||
E2D_RENDER_STATS().addDrawCall(count * instanceCount,
|
||||
(count / 3) * instanceCount);
|
||||
}
|
||||
|
||||
void RenderDevice::drawElementsInstanced(PrimitiveType mode, int32 count, IndexType type,
|
||||
const void* indices, int32 instanceCount) {
|
||||
glDrawElementsInstanced(glPrimitiveType(mode), count, glIndexType(type),
|
||||
indices, instanceCount);
|
||||
E2D_RENDER_STATS().addDrawCall(count * instanceCount, (count / 3) * instanceCount);
|
||||
void RenderDevice::drawElementsInstanced(PrimitiveType mode, int32 count,
|
||||
IndexType type, const void *indices,
|
||||
int32 instanceCount) {
|
||||
glDrawElementsInstanced(glPrimitiveType(mode), count, glIndexType(type),
|
||||
indices, instanceCount);
|
||||
E2D_RENDER_STATS().addDrawCall(count * instanceCount,
|
||||
(count / 3) * instanceCount);
|
||||
}
|
||||
|
||||
GLenum RenderDevice::glPrimitiveType(PrimitiveType mode) const {
|
||||
switch (mode) {
|
||||
case PrimitiveType::Points: return GL_POINTS;
|
||||
case PrimitiveType::Lines: return GL_LINES;
|
||||
case PrimitiveType::LineStrip: return GL_LINE_STRIP;
|
||||
case PrimitiveType::LineLoop: 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;
|
||||
}
|
||||
switch (mode) {
|
||||
case PrimitiveType::Points:
|
||||
return GL_POINTS;
|
||||
case PrimitiveType::Lines:
|
||||
return GL_LINES;
|
||||
case PrimitiveType::LineStrip:
|
||||
return GL_LINE_STRIP;
|
||||
case PrimitiveType::LineLoop:
|
||||
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 {
|
||||
switch (type) {
|
||||
case IndexType::UInt8: return GL_UNSIGNED_BYTE;
|
||||
case IndexType::UInt16: return GL_UNSIGNED_SHORT;
|
||||
case IndexType::UInt32: return GL_UNSIGNED_INT;
|
||||
default: return GL_UNSIGNED_INT;
|
||||
}
|
||||
switch (type) {
|
||||
case IndexType::UInt8:
|
||||
return GL_UNSIGNED_BYTE;
|
||||
case IndexType::UInt16:
|
||||
return GL_UNSIGNED_SHORT;
|
||||
case IndexType::UInt32:
|
||||
return GL_UNSIGNED_INT;
|
||||
default:
|
||||
return GL_UNSIGNED_INT;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
|
|||
|
|
@ -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,461 +1,430 @@
|
|||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <extra2d/graphics/render_command.h>
|
||||
#include <extra2d/scene/node.h>
|
||||
#include <extra2d/scene/scene.h>
|
||||
#include <extra2d/utils/logger.h>
|
||||
#include <extra2d/services/logger_service.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
Node::Node() = default;
|
||||
|
||||
Node::~Node() { removeAllChildren(); }
|
||||
|
||||
void Node::addChild(Ptr<Node> child) {
|
||||
if (!child || child.get() == this) {
|
||||
return;
|
||||
}
|
||||
|
||||
child->removeFromParent();
|
||||
child->parent_ = weak_from_this();
|
||||
children_.push_back(child);
|
||||
childrenOrderDirty_ = true;
|
||||
|
||||
// 更新索引
|
||||
if (!child->getName().empty()) {
|
||||
nameIndex_[child->getName()] = child;
|
||||
}
|
||||
if (child->getTag() != -1) {
|
||||
tagIndex_[child->getTag()] = child;
|
||||
}
|
||||
|
||||
if (running_) {
|
||||
child->onEnter();
|
||||
if (scene_) {
|
||||
child->onAttachToScene(scene_);
|
||||
}
|
||||
}
|
||||
Node::~Node() {
|
||||
removeAllChildren();
|
||||
}
|
||||
|
||||
void Node::addChildren(std::vector<Ptr<Node>> &&children) {
|
||||
// 预留空间,避免多次扩容
|
||||
size_t newSize = children_.size() + children.size();
|
||||
if (newSize > children_.capacity()) {
|
||||
children_.reserve(newSize);
|
||||
}
|
||||
|
||||
for (auto &child : children) {
|
||||
void Node::addChild(Ref<Node> child) {
|
||||
if (!child || child.get() == this) {
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
|
||||
child->removeFromParent();
|
||||
child->parent_ = weak_from_this();
|
||||
children_.push_back(child);
|
||||
childrenOrderDirty_ = true;
|
||||
|
||||
// 更新索引
|
||||
if (!child->getName().empty()) {
|
||||
nameIndex_[child->getName()] = child;
|
||||
nameIndex_[child->getName()] = child;
|
||||
}
|
||||
if (child->getTag() != -1) {
|
||||
tagIndex_[child->getTag()] = child;
|
||||
tagIndex_[child->getTag()] = child;
|
||||
}
|
||||
|
||||
if (running_) {
|
||||
child->onEnter();
|
||||
if (scene_) {
|
||||
child->onAttachToScene(scene_);
|
||||
}
|
||||
child->onEnter();
|
||||
if (scene_) {
|
||||
child->onAttachToScene(scene_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!children.empty()) {
|
||||
childrenOrderDirty_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
void Node::removeChild(Ptr<Node> child) {
|
||||
if (!child)
|
||||
return;
|
||||
void Node::addChildren(std::vector<Ref<Node>>&& children) {
|
||||
size_t newSize = children_.size() + children.size();
|
||||
if (newSize > children_.capacity()) {
|
||||
children_.reserve(newSize);
|
||||
}
|
||||
|
||||
auto it = std::find(children_.begin(), children_.end(), child);
|
||||
if (it != children_.end()) {
|
||||
// 始终从空间索引中移除(无论 running_ 状态)
|
||||
// 这确保节点被正确清理
|
||||
(*it)->onDetachFromScene();
|
||||
for (auto& child : children) {
|
||||
if (!child || child.get() == this) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (running_) {
|
||||
(*it)->onExit();
|
||||
child->removeFromParent();
|
||||
child->parent_ = weak_from_this();
|
||||
children_.push_back(child);
|
||||
|
||||
if (!child->getName().empty()) {
|
||||
nameIndex_[child->getName()] = child;
|
||||
}
|
||||
if (child->getTag() != -1) {
|
||||
tagIndex_[child->getTag()] = child;
|
||||
}
|
||||
|
||||
if (running_) {
|
||||
child->onEnter();
|
||||
if (scene_) {
|
||||
child->onAttachToScene(scene_);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 从索引中移除
|
||||
if (!(*it)->getName().empty()) {
|
||||
nameIndex_.erase((*it)->getName());
|
||||
|
||||
if (!children.empty()) {
|
||||
childrenOrderDirty_ = true;
|
||||
}
|
||||
if ((*it)->getTag() != -1) {
|
||||
tagIndex_.erase((*it)->getTag());
|
||||
}
|
||||
(*it)->parent_.reset();
|
||||
children_.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
void Node::removeChildByName(const std::string &name) {
|
||||
auto child = getChildByName(name);
|
||||
if (child) {
|
||||
removeChild(child);
|
||||
}
|
||||
void Node::removeChild(Ref<Node> child) {
|
||||
if (!child) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto it = std::find(children_.begin(), children_.end(), child);
|
||||
if (it != children_.end()) {
|
||||
(*it)->onDetachFromScene();
|
||||
|
||||
if (running_) {
|
||||
(*it)->onExit();
|
||||
}
|
||||
|
||||
if (!(*it)->getName().empty()) {
|
||||
nameIndex_.erase((*it)->getName());
|
||||
}
|
||||
if ((*it)->getTag() != -1) {
|
||||
tagIndex_.erase((*it)->getTag());
|
||||
}
|
||||
(*it)->parent_.reset();
|
||||
children_.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
void Node::removeChildByName(const std::string& name) {
|
||||
auto child = getChildByName(name);
|
||||
if (child) {
|
||||
removeChild(child);
|
||||
}
|
||||
}
|
||||
|
||||
void Node::removeFromParent() {
|
||||
auto p = parent_.lock();
|
||||
if (p) {
|
||||
// 安全获取 shared_ptr,避免在对象未由 shared_ptr 管理时崩溃
|
||||
Ptr<Node> self;
|
||||
try {
|
||||
self = shared_from_this();
|
||||
} catch (const std::bad_weak_ptr &) {
|
||||
// 对象不是由 shared_ptr 管理的,直接重置父节点引用
|
||||
parent_.reset();
|
||||
return;
|
||||
auto p = parent_.lock();
|
||||
if (p) {
|
||||
Ref<Node> self;
|
||||
try {
|
||||
self = shared_from_this();
|
||||
} catch (const std::bad_weak_ptr&) {
|
||||
parent_.reset();
|
||||
return;
|
||||
}
|
||||
p->removeChild(self);
|
||||
}
|
||||
p->removeChild(self);
|
||||
}
|
||||
}
|
||||
|
||||
void Node::removeAllChildren() {
|
||||
for (auto &child : children_) {
|
||||
if (running_) {
|
||||
child->onDetachFromScene();
|
||||
child->onExit();
|
||||
for (auto& child : children_) {
|
||||
if (running_) {
|
||||
child->onDetachFromScene();
|
||||
child->onExit();
|
||||
}
|
||||
child->parent_.reset();
|
||||
}
|
||||
child->parent_.reset();
|
||||
}
|
||||
children_.clear();
|
||||
nameIndex_.clear();
|
||||
tagIndex_.clear();
|
||||
children_.clear();
|
||||
nameIndex_.clear();
|
||||
tagIndex_.clear();
|
||||
}
|
||||
|
||||
Ptr<Node> Node::getChildByName(const std::string &name) const {
|
||||
// 使用哈希索引,O(1) 查找
|
||||
auto it = nameIndex_.find(name);
|
||||
if (it != nameIndex_.end()) {
|
||||
return it->second.lock();
|
||||
}
|
||||
return nullptr;
|
||||
Ref<Node> Node::getChildByName(const std::string& name) const {
|
||||
auto it = nameIndex_.find(name);
|
||||
if (it != nameIndex_.end()) {
|
||||
return it->second.lock();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Ptr<Node> Node::getChildByTag(int tag) const {
|
||||
// 使用哈希索引,O(1) 查找
|
||||
auto it = tagIndex_.find(tag);
|
||||
if (it != tagIndex_.end()) {
|
||||
return it->second.lock();
|
||||
}
|
||||
return nullptr;
|
||||
Ref<Node> Node::getChildByTag(int tag) const {
|
||||
auto it = tagIndex_.find(tag);
|
||||
if (it != tagIndex_.end()) {
|
||||
return it->second.lock();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void Node::setPosition(const Vec2 &pos) {
|
||||
position_ = pos;
|
||||
markTransformDirty();
|
||||
updateSpatialIndex();
|
||||
void Node::setPosition(const Vec2& pos) {
|
||||
position_ = pos;
|
||||
markTransformDirty();
|
||||
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) {
|
||||
rotation_ = degrees;
|
||||
markTransformDirty();
|
||||
updateSpatialIndex();
|
||||
rotation_ = degrees;
|
||||
markTransformDirty();
|
||||
updateSpatialIndex();
|
||||
}
|
||||
|
||||
void Node::setScale(const Vec2 &scale) {
|
||||
scale_ = scale;
|
||||
markTransformDirty();
|
||||
updateSpatialIndex();
|
||||
void Node::setScale(const Vec2& scale) {
|
||||
scale_ = scale;
|
||||
markTransformDirty();
|
||||
updateSpatialIndex();
|
||||
}
|
||||
|
||||
void Node::setScale(float scale) { setScale(Vec2(scale, scale)); }
|
||||
|
||||
void Node::setScale(float x, float y) { setScale(Vec2(x, y)); }
|
||||
|
||||
void Node::setAnchor(const Vec2 &anchor) {
|
||||
anchor_ = anchor;
|
||||
markTransformDirty();
|
||||
void Node::setScale(float scale) {
|
||||
setScale(Vec2(scale, scale));
|
||||
}
|
||||
|
||||
void Node::setAnchor(float x, float y) { setAnchor(Vec2(x, y)); }
|
||||
|
||||
void Node::setSkew(const Vec2 &skew) {
|
||||
skew_ = skew;
|
||||
markTransformDirty();
|
||||
void Node::setScale(float x, float y) {
|
||||
setScale(Vec2(x, y));
|
||||
}
|
||||
|
||||
void Node::setSkew(float x, float y) { setSkew(Vec2(x, y)); }
|
||||
void Node::setAnchor(const Vec2& anchor) {
|
||||
anchor_ = anchor;
|
||||
markTransformDirty();
|
||||
}
|
||||
|
||||
void Node::setAnchor(float x, float y) {
|
||||
setAnchor(Vec2(x, y));
|
||||
}
|
||||
|
||||
void Node::setSkew(const Vec2& skew) {
|
||||
skew_ = skew;
|
||||
markTransformDirty();
|
||||
}
|
||||
|
||||
void Node::setSkew(float x, float y) {
|
||||
setSkew(Vec2(x, y));
|
||||
}
|
||||
|
||||
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) {
|
||||
if (zOrder_ != zOrder) {
|
||||
zOrder_ = zOrder;
|
||||
childrenOrderDirty_ = true;
|
||||
}
|
||||
if (zOrder_ != zOrder) {
|
||||
zOrder_ = zOrder;
|
||||
childrenOrderDirty_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
Vec2 Node::convertToWorldSpace(const Vec2 &localPos) const {
|
||||
glm::vec4 worldPos =
|
||||
getWorldTransform() * glm::vec4(localPos.x, localPos.y, 0.0f, 1.0f);
|
||||
return Vec2(worldPos.x, worldPos.y);
|
||||
Vec2 Node::convertToWorldSpace(const Vec2& localPos) const {
|
||||
glm::vec4 worldPos = getWorldTransform() * glm::vec4(localPos.x, localPos.y, 0.0f, 1.0f);
|
||||
return Vec2(worldPos.x, worldPos.y);
|
||||
}
|
||||
|
||||
Vec2 Node::convertToNodeSpace(const Vec2 &worldPos) const {
|
||||
glm::mat4 invWorld = glm::inverse(getWorldTransform());
|
||||
glm::vec4 localPos = invWorld * glm::vec4(worldPos.x, worldPos.y, 0.0f, 1.0f);
|
||||
return Vec2(localPos.x, localPos.y);
|
||||
Vec2 Node::convertToNodeSpace(const Vec2& worldPos) const {
|
||||
glm::mat4 invWorld = glm::inverse(getWorldTransform());
|
||||
glm::vec4 localPos = invWorld * glm::vec4(worldPos.x, worldPos.y, 0.0f, 1.0f);
|
||||
return Vec2(localPos.x, localPos.y);
|
||||
}
|
||||
|
||||
glm::mat4 Node::getLocalTransform() const {
|
||||
if (transformDirty_) {
|
||||
localTransform_ = glm::mat4(1.0f);
|
||||
if (transformDirty_) {
|
||||
localTransform_ = glm::mat4(1.0f);
|
||||
|
||||
// T - R - S order
|
||||
localTransform_ = glm::translate(localTransform_,
|
||||
glm::vec3(position_.x, position_.y, 0.0f));
|
||||
localTransform_ = glm::translate(localTransform_,
|
||||
glm::vec3(position_.x, position_.y, 0.0f));
|
||||
|
||||
if (rotation_ != 0.0f) {
|
||||
localTransform_ = glm::rotate(localTransform_, rotation_ * DEG_TO_RAD,
|
||||
glm::vec3(0.0f, 0.0f, 1.0f));
|
||||
if (rotation_ != 0.0f) {
|
||||
localTransform_ = glm::rotate(localTransform_, rotation_ * DEG_TO_RAD,
|
||||
glm::vec3(0.0f, 0.0f, 1.0f));
|
||||
}
|
||||
|
||||
if (skew_.x != 0.0f || skew_.y != 0.0f) {
|
||||
glm::mat4 skewMatrix(1.0f);
|
||||
skewMatrix[1][0] = std::tan(skew_.x * DEG_TO_RAD);
|
||||
skewMatrix[0][1] = std::tan(skew_.y * DEG_TO_RAD);
|
||||
localTransform_ *= skewMatrix;
|
||||
}
|
||||
|
||||
localTransform_ = glm::scale(localTransform_, glm::vec3(scale_.x, scale_.y, 1.0f));
|
||||
|
||||
transformDirty_ = false;
|
||||
}
|
||||
|
||||
if (skew_.x != 0.0f || skew_.y != 0.0f) {
|
||||
glm::mat4 skewMatrix(1.0f);
|
||||
skewMatrix[1][0] = std::tan(skew_.x * DEG_TO_RAD);
|
||||
skewMatrix[0][1] = std::tan(skew_.y * DEG_TO_RAD);
|
||||
localTransform_ *= skewMatrix;
|
||||
}
|
||||
|
||||
localTransform_ =
|
||||
glm::scale(localTransform_, glm::vec3(scale_.x, scale_.y, 1.0f));
|
||||
|
||||
// 注意:锚点偏移在渲染时处理,不在本地变换中处理
|
||||
// 这样可以避免锚点偏移被父节点的缩放影响
|
||||
|
||||
transformDirty_ = false;
|
||||
}
|
||||
return localTransform_;
|
||||
return localTransform_;
|
||||
}
|
||||
|
||||
glm::mat4 Node::getWorldTransform() const {
|
||||
if (worldTransformDirty_) {
|
||||
// 使用线程局部存储的固定数组,避免每帧内存分配
|
||||
// 限制最大深度为 256 层,足以覆盖绝大多数场景
|
||||
thread_local std::array<const Node *, 256> nodeChainCache;
|
||||
thread_local size_t chainCount = 0;
|
||||
if (worldTransformDirty_) {
|
||||
thread_local std::array<const Node*, 256> nodeChainCache;
|
||||
thread_local size_t chainCount = 0;
|
||||
|
||||
chainCount = 0;
|
||||
const Node *current = this;
|
||||
while (current && chainCount < nodeChainCache.size()) {
|
||||
nodeChainCache[chainCount++] = current;
|
||||
auto p = current->parent_.lock();
|
||||
current = p.get();
|
||||
}
|
||||
chainCount = 0;
|
||||
const Node* current = this;
|
||||
while (current && chainCount < nodeChainCache.size()) {
|
||||
nodeChainCache[chainCount++] = current;
|
||||
auto p = current->parent_.lock();
|
||||
current = p.get();
|
||||
}
|
||||
|
||||
// 从根节点开始计算
|
||||
glm::mat4 transform = glm::mat4(1.0f);
|
||||
for (size_t i = chainCount; i > 0; --i) {
|
||||
transform = transform * nodeChainCache[i - 1]->getLocalTransform();
|
||||
glm::mat4 transform = glm::mat4(1.0f);
|
||||
for (size_t i = chainCount; i > 0; --i) {
|
||||
transform = transform * nodeChainCache[i - 1]->getLocalTransform();
|
||||
}
|
||||
worldTransform_ = transform;
|
||||
worldTransformDirty_ = false;
|
||||
}
|
||||
worldTransform_ = transform;
|
||||
worldTransformDirty_ = false;
|
||||
}
|
||||
return worldTransform_;
|
||||
return worldTransform_;
|
||||
}
|
||||
|
||||
void Node::markTransformDirty() {
|
||||
// 避免重复标记,提高性能
|
||||
if (!transformDirty_ || !worldTransformDirty_) {
|
||||
transformDirty_ = true;
|
||||
worldTransformDirty_ = true;
|
||||
if (!transformDirty_ || !worldTransformDirty_) {
|
||||
transformDirty_ = true;
|
||||
worldTransformDirty_ = true;
|
||||
|
||||
// 递归标记所有子节点
|
||||
for (auto &child : children_) {
|
||||
child->markTransformDirty();
|
||||
for (auto& child : children_) {
|
||||
child->markTransformDirty();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Node::batchUpdateTransforms() {
|
||||
// 如果本地变换脏了,先计算本地变换
|
||||
if (transformDirty_) {
|
||||
(void)getLocalTransform(); // 这会计算并缓存本地变换
|
||||
}
|
||||
|
||||
// 如果世界变换脏了,需要重新计算
|
||||
if (worldTransformDirty_) {
|
||||
auto parent = parent_.lock();
|
||||
if (parent) {
|
||||
// 使用父节点的世界变换(确保父节点已经更新)
|
||||
worldTransform_ = parent->getWorldTransform() * localTransform_;
|
||||
} else {
|
||||
// 根节点
|
||||
worldTransform_ = localTransform_;
|
||||
if (transformDirty_) {
|
||||
(void)getLocalTransform();
|
||||
}
|
||||
worldTransformDirty_ = false;
|
||||
}
|
||||
|
||||
// 递归更新子节点
|
||||
for (auto &child : children_) {
|
||||
child->batchUpdateTransforms();
|
||||
}
|
||||
if (worldTransformDirty_) {
|
||||
auto parent = parent_.lock();
|
||||
if (parent) {
|
||||
worldTransform_ = parent->getWorldTransform() * localTransform_;
|
||||
} else {
|
||||
worldTransform_ = localTransform_;
|
||||
}
|
||||
worldTransformDirty_ = false;
|
||||
}
|
||||
|
||||
for (auto& child : children_) {
|
||||
child->batchUpdateTransforms();
|
||||
}
|
||||
}
|
||||
|
||||
void Node::onEnter() {
|
||||
running_ = true;
|
||||
for (auto &child : children_) {
|
||||
child->onEnter();
|
||||
}
|
||||
running_ = true;
|
||||
for (auto& child : children_) {
|
||||
child->onEnter();
|
||||
}
|
||||
}
|
||||
|
||||
void Node::onExit() {
|
||||
running_ = false;
|
||||
for (auto &child : children_) {
|
||||
child->onExit();
|
||||
}
|
||||
running_ = false;
|
||||
for (auto& child : children_) {
|
||||
child->onExit();
|
||||
}
|
||||
}
|
||||
|
||||
void Node::onUpdate(float dt) {
|
||||
onUpdateNode(dt);
|
||||
onUpdateNode(dt);
|
||||
|
||||
// Update children
|
||||
for (auto &child : children_) {
|
||||
child->onUpdate(dt);
|
||||
}
|
||||
for (auto& child : children_) {
|
||||
child->onUpdate(dt);
|
||||
}
|
||||
}
|
||||
|
||||
void Node::onRender(Renderer &renderer) {
|
||||
if (!visible_)
|
||||
return;
|
||||
void Node::onRender(Renderer& renderer) {
|
||||
if (!visible_) {
|
||||
return;
|
||||
}
|
||||
|
||||
onDraw(renderer);
|
||||
onDraw(renderer);
|
||||
|
||||
for (auto &child : children_) {
|
||||
child->onRender(renderer);
|
||||
}
|
||||
for (auto& child : children_) {
|
||||
child->onRender(renderer);
|
||||
}
|
||||
}
|
||||
|
||||
void Node::onAttachToScene(Scene *scene) {
|
||||
scene_ = scene;
|
||||
void Node::onAttachToScene(Scene* scene) {
|
||||
scene_ = scene;
|
||||
|
||||
// 添加到场景的空间索引
|
||||
if (spatialIndexed_ && scene_) {
|
||||
lastSpatialBounds_ = Rect();
|
||||
updateSpatialIndex();
|
||||
}
|
||||
if (spatialIndexed_ && scene_) {
|
||||
lastSpatialBounds_ = Rect();
|
||||
updateSpatialIndex();
|
||||
}
|
||||
|
||||
for (auto &child : children_) {
|
||||
child->onAttachToScene(scene);
|
||||
}
|
||||
for (auto& child : children_) {
|
||||
child->onAttachToScene(scene);
|
||||
}
|
||||
}
|
||||
|
||||
void Node::onDetachFromScene() {
|
||||
// 从场景的空间索引移除
|
||||
// 注意:即使 lastSpatialBounds_ 为空也要尝试移除,
|
||||
// 因为节点可能通过其他方式被插入到空间索引中
|
||||
if (spatialIndexed_ && scene_) {
|
||||
scene_->removeNodeFromSpatialIndex(this);
|
||||
lastSpatialBounds_ = Rect();
|
||||
}
|
||||
if (spatialIndexed_ && scene_) {
|
||||
scene_->removeNodeFromSpatialIndex(this);
|
||||
lastSpatialBounds_ = Rect();
|
||||
}
|
||||
|
||||
scene_ = nullptr;
|
||||
for (auto &child : children_) {
|
||||
child->onDetachFromScene();
|
||||
}
|
||||
scene_ = nullptr;
|
||||
for (auto& child : children_) {
|
||||
child->onDetachFromScene();
|
||||
}
|
||||
}
|
||||
|
||||
Rect Node::getBoundingBox() const {
|
||||
// 默认返回一个以位置为中心的点矩形
|
||||
return Rect(position_.x, position_.y, 0, 0);
|
||||
return Rect(position_.x, position_.y, 0, 0);
|
||||
}
|
||||
|
||||
void Node::updateSpatialIndex() {
|
||||
if (!spatialIndexed_ || !scene_) {
|
||||
return;
|
||||
}
|
||||
if (!spatialIndexed_ || !scene_) {
|
||||
return;
|
||||
}
|
||||
|
||||
Rect newBounds = getBoundingBox();
|
||||
if (newBounds != lastSpatialBounds_) {
|
||||
scene_->updateNodeInSpatialIndex(this, lastSpatialBounds_, newBounds);
|
||||
lastSpatialBounds_ = newBounds;
|
||||
}
|
||||
Rect newBounds = getBoundingBox();
|
||||
if (newBounds != lastSpatialBounds_) {
|
||||
scene_->updateNodeInSpatialIndex(this, lastSpatialBounds_, newBounds);
|
||||
lastSpatialBounds_ = newBounds;
|
||||
}
|
||||
}
|
||||
|
||||
void Node::update(float dt) { onUpdate(dt); }
|
||||
void Node::update(float dt) {
|
||||
onUpdate(dt);
|
||||
}
|
||||
|
||||
void Node::render(Renderer &renderer) {
|
||||
if (childrenOrderDirty_) {
|
||||
sortChildren();
|
||||
}
|
||||
onRender(renderer);
|
||||
void Node::render(Renderer& renderer) {
|
||||
if (childrenOrderDirty_) {
|
||||
sortChildren();
|
||||
}
|
||||
onRender(renderer);
|
||||
}
|
||||
|
||||
void Node::sortChildren() {
|
||||
// 使用插入排序优化小范围更新场景
|
||||
// 插入排序在大部分已有序的情况下性能接近O(n)
|
||||
size_t n = children_.size();
|
||||
if (n <= 1) {
|
||||
childrenOrderDirty_ = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// 小数组使用插入排序,大数组使用std::sort
|
||||
if (n < 32) {
|
||||
// 插入排序
|
||||
for (size_t i = 1; i < n; ++i) {
|
||||
auto key = children_[i];
|
||||
int keyZOrder = key->getZOrder();
|
||||
int j = static_cast<int>(i) - 1;
|
||||
|
||||
while (j >= 0 && children_[j]->getZOrder() > keyZOrder) {
|
||||
children_[j + 1] = children_[j];
|
||||
--j;
|
||||
}
|
||||
children_[j + 1] = key;
|
||||
size_t n = children_.size();
|
||||
if (n <= 1) {
|
||||
childrenOrderDirty_ = false;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// 大数组使用标准排序
|
||||
std::sort(children_.begin(), children_.end(),
|
||||
[](const Ptr<Node> &a, const Ptr<Node> &b) {
|
||||
return a->getZOrder() < b->getZOrder();
|
||||
});
|
||||
}
|
||||
|
||||
childrenOrderDirty_ = false;
|
||||
}
|
||||
if (n < 32) {
|
||||
for (size_t i = 1; i < n; ++i) {
|
||||
auto key = children_[i];
|
||||
int keyZOrder = key->getZOrder();
|
||||
int j = static_cast<int>(i) - 1;
|
||||
|
||||
void Node::collectRenderCommands(std::vector<RenderCommand> &commands,
|
||||
int parentZOrder) {
|
||||
if (!visible_)
|
||||
return;
|
||||
while (j >= 0 && children_[j]->getZOrder() > keyZOrder) {
|
||||
children_[j + 1] = children_[j];
|
||||
--j;
|
||||
}
|
||||
children_[j + 1] = key;
|
||||
}
|
||||
} else {
|
||||
std::sort(children_.begin(), children_.end(),
|
||||
[](const Ref<Node>& a, const Ref<Node>& b) {
|
||||
return a->getZOrder() < b->getZOrder();
|
||||
});
|
||||
}
|
||||
|
||||
// 计算累积 Z 序
|
||||
int accumulatedZOrder = parentZOrder + zOrder_;
|
||||
|
||||
// 生成当前节点的渲染命令
|
||||
generateRenderCommand(commands, accumulatedZOrder);
|
||||
|
||||
// 递归收集子节点的渲染命令
|
||||
// 注意:这里假设子节点已经按 Z 序排序
|
||||
for (auto &child : children_) {
|
||||
child->collectRenderCommands(commands, accumulatedZOrder);
|
||||
}
|
||||
childrenOrderDirty_ = false;
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
|
|||
|
|
@ -1,129 +1,120 @@
|
|||
#include <extra2d/graphics/render_command.h>
|
||||
#include <extra2d/graphics/renderer.h>
|
||||
#include <extra2d/render/renderer.h>
|
||||
#include <extra2d/scene/scene.h>
|
||||
#include <extra2d/utils/logger.h>
|
||||
#include <extra2d/services/logger_service.h>
|
||||
|
||||
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) {
|
||||
viewportSize_ = Size(width, height);
|
||||
if (defaultCamera_) {
|
||||
defaultCamera_->setViewport(0, width, height, 0);
|
||||
} else if (camera_) {
|
||||
camera_->setViewport(0, width, height, 0);
|
||||
}
|
||||
viewportSize_ = Size(width, height);
|
||||
if (defaultCamera_) {
|
||||
defaultCamera_->setViewport(0, static_cast<int>(width), static_cast<int>(height), 0);
|
||||
} else if (camera_) {
|
||||
camera_->setViewport(0, static_cast<int>(width), static_cast<int>(height), 0);
|
||||
}
|
||||
}
|
||||
|
||||
void Scene::setViewportSize(const Size &size) {
|
||||
setViewportSize(size.width, size.height);
|
||||
void Scene::setViewportSize(const Size& size) {
|
||||
setViewportSize(size.width, size.height);
|
||||
}
|
||||
|
||||
void Scene::renderScene(Renderer &renderer) {
|
||||
if (!isVisible())
|
||||
return;
|
||||
void Scene::renderScene(Renderer& renderer) {
|
||||
if (!isVisible()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Begin frame with background color
|
||||
renderer.beginFrame(backgroundColor_);
|
||||
renderContent(renderer);
|
||||
renderer.endFrame();
|
||||
renderer.beginFrame(backgroundColor_);
|
||||
renderContent(renderer);
|
||||
renderer.endFrame();
|
||||
}
|
||||
|
||||
void Scene::renderContent(Renderer &renderer) {
|
||||
if (!isVisible())
|
||||
return;
|
||||
void Scene::renderContent(Renderer& renderer) {
|
||||
if (!isVisible()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 在渲染前批量更新所有节点的世界变换
|
||||
batchUpdateTransforms();
|
||||
batchUpdateTransforms();
|
||||
|
||||
Camera *activeCam = getActiveCamera();
|
||||
if (activeCam) {
|
||||
renderer.setViewProjection(activeCam->getViewProjectionMatrix());
|
||||
}
|
||||
Camera* activeCam = getActiveCamera();
|
||||
if (activeCam) {
|
||||
renderer.setViewProjection(activeCam->getViewProjectionMatrix());
|
||||
}
|
||||
|
||||
render(renderer);
|
||||
render(renderer);
|
||||
}
|
||||
|
||||
void Scene::updateScene(float dt) {
|
||||
if (!paused_) {
|
||||
update(dt);
|
||||
}
|
||||
if (!paused_) {
|
||||
update(dt);
|
||||
}
|
||||
}
|
||||
|
||||
void Scene::onEnter() {
|
||||
Node::onEnter();
|
||||
Node::onEnter();
|
||||
|
||||
// 初始化空间索引世界边界
|
||||
if (spatialIndexingEnabled_) {
|
||||
spatialManager_.setWorldBounds(
|
||||
Rect(0, 0, viewportSize_.width, viewportSize_.height));
|
||||
}
|
||||
if (spatialIndexingEnabled_) {
|
||||
spatialManager_.setWorldBounds(
|
||||
Rect(0, 0, viewportSize_.width, viewportSize_.height));
|
||||
}
|
||||
}
|
||||
|
||||
void Scene::onExit() {
|
||||
// 清理空间索引
|
||||
spatialManager_.clear();
|
||||
Node::onExit();
|
||||
spatialManager_.clear();
|
||||
Node::onExit();
|
||||
}
|
||||
|
||||
void Scene::updateNodeInSpatialIndex(Node *node, const Rect &oldBounds,
|
||||
const Rect &newBounds) {
|
||||
if (!spatialIndexingEnabled_ || !node || !node->isSpatialIndexed()) {
|
||||
return;
|
||||
}
|
||||
void Scene::updateNodeInSpatialIndex(Node* node, const Rect& oldBounds, const Rect& newBounds) {
|
||||
if (!spatialIndexingEnabled_ || !node || !node->isSpatialIndexed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!oldBounds.empty()) {
|
||||
spatialManager_.remove(node);
|
||||
}
|
||||
|
||||
if (!newBounds.empty()) {
|
||||
spatialManager_.insert(node, newBounds);
|
||||
}
|
||||
}
|
||||
|
||||
void Scene::removeNodeFromSpatialIndex(Node* node) {
|
||||
if (!spatialIndexingEnabled_ || !node) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果旧边界有效,先移除
|
||||
if (!oldBounds.empty()) {
|
||||
spatialManager_.remove(node);
|
||||
}
|
||||
|
||||
// 如果新边界有效,插入
|
||||
if (!newBounds.empty()) {
|
||||
spatialManager_.insert(node, newBounds);
|
||||
}
|
||||
}
|
||||
|
||||
void Scene::removeNodeFromSpatialIndex(Node *node) {
|
||||
if (!spatialIndexingEnabled_ || !node) {
|
||||
return;
|
||||
}
|
||||
|
||||
spatialManager_.remove(node);
|
||||
std::vector<Node*> Scene::queryNodesInArea(const Rect& area) const {
|
||||
if (!spatialIndexingEnabled_) {
|
||||
return {};
|
||||
}
|
||||
return spatialManager_.query(area);
|
||||
}
|
||||
|
||||
std::vector<Node *> Scene::queryNodesInArea(const Rect &area) const {
|
||||
if (!spatialIndexingEnabled_) {
|
||||
return {};
|
||||
}
|
||||
return spatialManager_.query(area);
|
||||
std::vector<Node*> Scene::queryNodesAtPoint(const Vec2& point) const {
|
||||
if (!spatialIndexingEnabled_) {
|
||||
return {};
|
||||
}
|
||||
return spatialManager_.query(point);
|
||||
}
|
||||
|
||||
std::vector<Node *> Scene::queryNodesAtPoint(const Vec2 &point) const {
|
||||
if (!spatialIndexingEnabled_) {
|
||||
return {};
|
||||
}
|
||||
return spatialManager_.query(point);
|
||||
std::vector<std::pair<Node*, Node*>> Scene::queryCollisions() const {
|
||||
if (!spatialIndexingEnabled_) {
|
||||
return {};
|
||||
}
|
||||
return spatialManager_.queryCollisions();
|
||||
}
|
||||
|
||||
std::vector<std::pair<Node *, Node *>> Scene::queryCollisions() const {
|
||||
if (!spatialIndexingEnabled_) {
|
||||
return {};
|
||||
}
|
||||
return spatialManager_.queryCollisions();
|
||||
Ref<Scene> Scene::create() {
|
||||
return ptr::make<Scene>();
|
||||
}
|
||||
|
||||
void Scene::collectRenderCommands(std::vector<RenderCommand> &commands,
|
||||
int parentZOrder) {
|
||||
if (!isVisible())
|
||||
return;
|
||||
|
||||
// 从场景的子节点开始收集渲染命令
|
||||
Node::collectRenderCommands(commands, parentZOrder);
|
||||
}
|
||||
|
||||
Ptr<Scene> Scene::create() { return makePtr<Scene>(); }
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
|
|||
|
|
@ -1,233 +1,208 @@
|
|||
#include <algorithm>
|
||||
#include <extra2d/graphics/render_command.h>
|
||||
#include <extra2d/graphics/renderer.h>
|
||||
#include <extra2d/render/renderer.h>
|
||||
#include <extra2d/scene/scene_manager.h>
|
||||
#include <extra2d/utils/logger.h>
|
||||
#include <extra2d/services/logger_service.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
SceneManager &SceneManager::getInstance() {
|
||||
static SceneManager instance;
|
||||
return instance;
|
||||
SceneManager& SceneManager::getInstance() {
|
||||
static SceneManager instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void SceneManager::runWithScene(Ptr<Scene> scene) {
|
||||
if (!scene) {
|
||||
return;
|
||||
}
|
||||
void SceneManager::runWithScene(Ref<Scene> scene) {
|
||||
if (!scene) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!sceneStack_.empty()) {
|
||||
E2D_LOG_WARN("SceneManager: runWithScene should only be called once");
|
||||
return;
|
||||
}
|
||||
if (!sceneStack_.empty()) {
|
||||
E2D_WARN("SceneManager: runWithScene should only be called once");
|
||||
return;
|
||||
}
|
||||
|
||||
scene->onEnter();
|
||||
scene->onAttachToScene(scene.get());
|
||||
sceneStack_.push(scene);
|
||||
scene->onEnter();
|
||||
sceneStack_.push(scene);
|
||||
}
|
||||
|
||||
void SceneManager::replaceScene(Ptr<Scene> scene) {
|
||||
if (!scene) {
|
||||
return;
|
||||
}
|
||||
void SceneManager::replaceScene(Ref<Scene> scene) {
|
||||
if (!scene) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (sceneStack_.empty()) {
|
||||
runWithScene(scene);
|
||||
return;
|
||||
}
|
||||
if (sceneStack_.empty()) {
|
||||
runWithScene(scene);
|
||||
return;
|
||||
}
|
||||
|
||||
// Pop current scene
|
||||
auto oldScene = sceneStack_.top();
|
||||
oldScene->onExit();
|
||||
oldScene->onDetachFromScene();
|
||||
sceneStack_.pop();
|
||||
auto oldScene = sceneStack_.top();
|
||||
oldScene->onExit();
|
||||
sceneStack_.pop();
|
||||
|
||||
// Push new scene
|
||||
scene->onEnter();
|
||||
scene->onAttachToScene(scene.get());
|
||||
sceneStack_.push(scene);
|
||||
scene->onEnter();
|
||||
sceneStack_.push(scene);
|
||||
}
|
||||
|
||||
void SceneManager::enterScene(Ptr<Scene> scene) {
|
||||
if (!scene) {
|
||||
return;
|
||||
}
|
||||
void SceneManager::enterScene(Ref<Scene> scene) {
|
||||
if (!scene) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (sceneStack_.empty()) {
|
||||
runWithScene(scene);
|
||||
} else {
|
||||
replaceScene(scene);
|
||||
}
|
||||
if (sceneStack_.empty()) {
|
||||
runWithScene(scene);
|
||||
} else {
|
||||
replaceScene(scene);
|
||||
}
|
||||
}
|
||||
|
||||
void SceneManager::pushScene(Ptr<Scene> scene) {
|
||||
if (!scene) {
|
||||
return;
|
||||
}
|
||||
// Pause current scene
|
||||
if (!sceneStack_.empty()) {
|
||||
sceneStack_.top()->pause();
|
||||
}
|
||||
void SceneManager::pushScene(Ref<Scene> scene) {
|
||||
if (!scene) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Push new scene
|
||||
scene->onEnter();
|
||||
scene->onAttachToScene(scene.get());
|
||||
sceneStack_.push(scene);
|
||||
if (!sceneStack_.empty()) {
|
||||
sceneStack_.top()->pause();
|
||||
}
|
||||
|
||||
scene->onEnter();
|
||||
sceneStack_.push(scene);
|
||||
}
|
||||
|
||||
void SceneManager::popScene() {
|
||||
if (sceneStack_.size() <= 1) {
|
||||
return;
|
||||
}
|
||||
if (sceneStack_.size() <= 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto current = sceneStack_.top();
|
||||
current->onExit();
|
||||
current->onDetachFromScene();
|
||||
sceneStack_.pop();
|
||||
auto current = sceneStack_.top();
|
||||
current->onExit();
|
||||
sceneStack_.pop();
|
||||
|
||||
// Resume previous scene
|
||||
if (!sceneStack_.empty()) {
|
||||
sceneStack_.top()->resume();
|
||||
}
|
||||
if (!sceneStack_.empty()) {
|
||||
sceneStack_.top()->resume();
|
||||
}
|
||||
}
|
||||
|
||||
void SceneManager::popToRootScene() {
|
||||
if (sceneStack_.size() <= 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Exit all scenes except root
|
||||
while (sceneStack_.size() > 1) {
|
||||
auto scene = sceneStack_.top();
|
||||
scene->onExit();
|
||||
scene->onDetachFromScene();
|
||||
sceneStack_.pop();
|
||||
}
|
||||
|
||||
// Resume root
|
||||
sceneStack_.top()->resume();
|
||||
}
|
||||
|
||||
void SceneManager::popToScene(const std::string &name) {
|
||||
// Find target scene in stack
|
||||
std::stack<Ptr<Scene>> tempStack;
|
||||
Ptr<Scene> target = nullptr;
|
||||
|
||||
while (!sceneStack_.empty()) {
|
||||
auto scene = sceneStack_.top();
|
||||
if (scene->getName() == name) {
|
||||
target = scene;
|
||||
break;
|
||||
if (sceneStack_.size() <= 1) {
|
||||
return;
|
||||
}
|
||||
scene->onExit();
|
||||
scene->onDetachFromScene();
|
||||
sceneStack_.pop();
|
||||
}
|
||||
|
||||
if (target) {
|
||||
target->resume();
|
||||
}
|
||||
}
|
||||
|
||||
Ptr<Scene> SceneManager::getCurrentScene() const {
|
||||
if (sceneStack_.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
return sceneStack_.top();
|
||||
}
|
||||
|
||||
Ptr<Scene> SceneManager::getPreviousScene() const {
|
||||
if (sceneStack_.size() < 2) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Copy stack to access second top
|
||||
auto tempStack = sceneStack_;
|
||||
tempStack.pop();
|
||||
return tempStack.top();
|
||||
}
|
||||
|
||||
Ptr<Scene> SceneManager::getRootScene() const {
|
||||
if (sceneStack_.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Copy stack to access bottom
|
||||
auto tempStack = sceneStack_;
|
||||
Ptr<Scene> root;
|
||||
while (!tempStack.empty()) {
|
||||
root = tempStack.top();
|
||||
tempStack.pop();
|
||||
}
|
||||
return root;
|
||||
}
|
||||
|
||||
Ptr<Scene> SceneManager::getSceneByName(const std::string &name) const {
|
||||
auto it = namedScenes_.find(name);
|
||||
if (it != namedScenes_.end()) {
|
||||
return it->second;
|
||||
}
|
||||
|
||||
// Search in stack
|
||||
auto tempStack = sceneStack_;
|
||||
while (!tempStack.empty()) {
|
||||
auto scene = tempStack.top();
|
||||
if (scene->getName() == name) {
|
||||
return scene;
|
||||
while (sceneStack_.size() > 1) {
|
||||
auto scene = sceneStack_.top();
|
||||
scene->onExit();
|
||||
sceneStack_.pop();
|
||||
}
|
||||
tempStack.pop();
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
sceneStack_.top()->resume();
|
||||
}
|
||||
|
||||
bool SceneManager::hasScene(const std::string &name) const {
|
||||
return getSceneByName(name) != nullptr;
|
||||
void SceneManager::popToScene(const std::string& name) {
|
||||
std::stack<Ref<Scene>> tempStack;
|
||||
Ref<Scene> target = nullptr;
|
||||
|
||||
while (!sceneStack_.empty()) {
|
||||
auto scene = sceneStack_.top();
|
||||
if (scene->getName() == name) {
|
||||
target = scene;
|
||||
break;
|
||||
}
|
||||
scene->onExit();
|
||||
sceneStack_.pop();
|
||||
}
|
||||
|
||||
if (target) {
|
||||
target->resume();
|
||||
}
|
||||
}
|
||||
|
||||
Ref<Scene> SceneManager::getCurrentScene() const {
|
||||
if (sceneStack_.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
return sceneStack_.top();
|
||||
}
|
||||
|
||||
Ref<Scene> SceneManager::getPreviousScene() const {
|
||||
if (sceneStack_.size() < 2) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto tempStack = sceneStack_;
|
||||
tempStack.pop();
|
||||
return tempStack.top();
|
||||
}
|
||||
|
||||
Ref<Scene> SceneManager::getRootScene() const {
|
||||
if (sceneStack_.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto tempStack = sceneStack_;
|
||||
Ref<Scene> root;
|
||||
while (!tempStack.empty()) {
|
||||
root = tempStack.top();
|
||||
tempStack.pop();
|
||||
}
|
||||
return root;
|
||||
}
|
||||
|
||||
Ref<Scene> SceneManager::getSceneByName(const std::string& name) const {
|
||||
auto it = namedScenes_.find(name);
|
||||
if (it != namedScenes_.end()) {
|
||||
return it->second;
|
||||
}
|
||||
|
||||
auto tempStack = sceneStack_;
|
||||
while (!tempStack.empty()) {
|
||||
auto scene = tempStack.top();
|
||||
if (scene->getName() == name) {
|
||||
return scene;
|
||||
}
|
||||
tempStack.pop();
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool SceneManager::hasScene(const std::string& name) const {
|
||||
return getSceneByName(name) != nullptr;
|
||||
}
|
||||
|
||||
void SceneManager::update(float dt) {
|
||||
if (!sceneStack_.empty()) {
|
||||
sceneStack_.top()->updateScene(dt);
|
||||
}
|
||||
if (!sceneStack_.empty()) {
|
||||
sceneStack_.top()->updateScene(dt);
|
||||
}
|
||||
}
|
||||
|
||||
void SceneManager::render(Renderer &renderer) {
|
||||
Color clearColor = Colors::Black;
|
||||
if (!sceneStack_.empty()) {
|
||||
clearColor = sceneStack_.top()->getBackgroundColor();
|
||||
}
|
||||
void SceneManager::render(Renderer& renderer) {
|
||||
Color clearColor = Colors::Black;
|
||||
if (!sceneStack_.empty()) {
|
||||
clearColor = sceneStack_.top()->getBackgroundColor();
|
||||
}
|
||||
|
||||
E2D_LOG_TRACE("SceneManager::render - beginFrame with color({}, {}, {})",
|
||||
clearColor.r, clearColor.g, clearColor.b);
|
||||
renderer.beginFrame(clearColor);
|
||||
E2D_TRACE("SceneManager::render - beginFrame");
|
||||
renderer.beginFrame(clearColor);
|
||||
|
||||
if (!sceneStack_.empty()) {
|
||||
E2D_LOG_TRACE("SceneManager::render - rendering scene content");
|
||||
sceneStack_.top()->renderContent(renderer);
|
||||
} else {
|
||||
E2D_LOG_WARN("SceneManager::render - no scene to render");
|
||||
}
|
||||
if (!sceneStack_.empty()) {
|
||||
E2D_TRACE("SceneManager::render - rendering scene content");
|
||||
sceneStack_.top()->renderContent(renderer);
|
||||
} else {
|
||||
E2D_WARN("SceneManager::render - no scene to render");
|
||||
}
|
||||
|
||||
renderer.endFrame();
|
||||
E2D_LOG_TRACE("SceneManager::render - endFrame");
|
||||
}
|
||||
|
||||
void SceneManager::collectRenderCommands(std::vector<RenderCommand> &commands) {
|
||||
if (!sceneStack_.empty()) {
|
||||
sceneStack_.top()->collectRenderCommands(commands, 0);
|
||||
}
|
||||
renderer.endFrame();
|
||||
E2D_TRACE("SceneManager::render - endFrame");
|
||||
}
|
||||
|
||||
void SceneManager::end() {
|
||||
while (!sceneStack_.empty()) {
|
||||
auto scene = sceneStack_.top();
|
||||
scene->onExit();
|
||||
scene->onDetachFromScene();
|
||||
sceneStack_.pop();
|
||||
}
|
||||
namedScenes_.clear();
|
||||
while (!sceneStack_.empty()) {
|
||||
auto scene = sceneStack_.top();
|
||||
scene->onExit();
|
||||
sceneStack_.pop();
|
||||
}
|
||||
namedScenes_.clear();
|
||||
}
|
||||
|
||||
void SceneManager::purgeCachedScenes() { namedScenes_.clear(); }
|
||||
void SceneManager::purgeCachedScenes() {
|
||||
namedScenes_.clear();
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <extra2d/graphics/renderer.h>
|
||||
#include <extra2d/graphics/render_command.h>
|
||||
#include <extra2d/render/renderer.h>
|
||||
#include <extra2d/scene/shape_node.h>
|
||||
#include <limits>
|
||||
|
||||
|
|
@ -9,340 +8,233 @@ namespace extra2d {
|
|||
|
||||
ShapeNode::ShapeNode() = default;
|
||||
|
||||
Ptr<ShapeNode> ShapeNode::create() { return makePtr<ShapeNode>(); }
|
||||
|
||||
Ptr<ShapeNode> ShapeNode::createPoint(const Vec2 &pos, const Color &color) {
|
||||
auto node = makePtr<ShapeNode>();
|
||||
node->shapeType_ = ShapeType::Point;
|
||||
node->color_ = color;
|
||||
node->points_ = {pos};
|
||||
return node;
|
||||
Ref<ShapeNode> ShapeNode::create() {
|
||||
return ptr::make<ShapeNode>();
|
||||
}
|
||||
|
||||
Ptr<ShapeNode> ShapeNode::createLine(const Vec2 &start, const Vec2 &end,
|
||||
const Color &color, float width) {
|
||||
auto node = makePtr<ShapeNode>();
|
||||
node->shapeType_ = ShapeType::Line;
|
||||
node->color_ = color;
|
||||
node->lineWidth_ = width;
|
||||
node->points_ = {start, end};
|
||||
return node;
|
||||
Ref<ShapeNode> ShapeNode::createPoint(const Vec2& pos, const Color& color) {
|
||||
auto node = ptr::make<ShapeNode>();
|
||||
node->shapeType_ = ShapeType::Point;
|
||||
node->color_ = color;
|
||||
node->points_ = {pos};
|
||||
return node;
|
||||
}
|
||||
|
||||
Ptr<ShapeNode> ShapeNode::createRect(const Rect &rect, const Color &color,
|
||||
float width) {
|
||||
auto node = makePtr<ShapeNode>();
|
||||
node->shapeType_ = ShapeType::Rect;
|
||||
node->color_ = color;
|
||||
node->lineWidth_ = width;
|
||||
node->filled_ = false;
|
||||
node->points_ = {
|
||||
Vec2(rect.left(), rect.top()), Vec2(rect.right(), rect.top()),
|
||||
Vec2(rect.right(), rect.bottom()), Vec2(rect.left(), rect.bottom())};
|
||||
return node;
|
||||
Ref<ShapeNode> ShapeNode::createLine(const Vec2& start, const Vec2& end,
|
||||
const Color& color, float width) {
|
||||
auto node = ptr::make<ShapeNode>();
|
||||
node->shapeType_ = ShapeType::Line;
|
||||
node->color_ = color;
|
||||
node->lineWidth_ = width;
|
||||
node->points_ = {start, end};
|
||||
return node;
|
||||
}
|
||||
|
||||
Ptr<ShapeNode> ShapeNode::createFilledRect(const Rect &rect,
|
||||
const Color &color) {
|
||||
auto node = createRect(rect, color, 0);
|
||||
node->filled_ = true;
|
||||
return node;
|
||||
Ref<ShapeNode> ShapeNode::createRect(const Rect& rect, const Color& color, float width) {
|
||||
auto node = ptr::make<ShapeNode>();
|
||||
node->shapeType_ = ShapeType::Rect;
|
||||
node->color_ = color;
|
||||
node->lineWidth_ = width;
|
||||
node->filled_ = false;
|
||||
node->points_ = {
|
||||
Vec2(rect.left(), rect.top()),
|
||||
Vec2(rect.right(), rect.top()),
|
||||
Vec2(rect.right(), rect.bottom()),
|
||||
Vec2(rect.left(), rect.bottom())
|
||||
};
|
||||
return node;
|
||||
}
|
||||
|
||||
Ptr<ShapeNode> ShapeNode::createCircle(const Vec2 ¢er, float radius,
|
||||
const Color &color, int segments,
|
||||
float width) {
|
||||
auto node = makePtr<ShapeNode>();
|
||||
node->shapeType_ = ShapeType::Circle;
|
||||
node->color_ = color;
|
||||
node->lineWidth_ = width;
|
||||
node->segments_ = segments;
|
||||
node->filled_ = false;
|
||||
node->points_ = {center};
|
||||
// Store radius in a point for simplicity
|
||||
node->addPoint(Vec2(radius, 0));
|
||||
return node;
|
||||
Ref<ShapeNode> ShapeNode::createFilledRect(const Rect& rect, const Color& color) {
|
||||
auto node = createRect(rect, color, 0);
|
||||
node->filled_ = true;
|
||||
return node;
|
||||
}
|
||||
|
||||
Ptr<ShapeNode> ShapeNode::createFilledCircle(const Vec2 ¢er, float radius,
|
||||
const Color &color, int segments) {
|
||||
auto node = createCircle(center, radius, color, segments, 0);
|
||||
node->filled_ = true;
|
||||
return node;
|
||||
Ref<ShapeNode> ShapeNode::createCircle(const Vec2& center, float radius,
|
||||
const Color& color, int segments, float width) {
|
||||
auto node = ptr::make<ShapeNode>();
|
||||
node->shapeType_ = ShapeType::Circle;
|
||||
node->color_ = color;
|
||||
node->lineWidth_ = width;
|
||||
node->segments_ = segments;
|
||||
node->filled_ = false;
|
||||
node->points_ = {center};
|
||||
node->addPoint(Vec2(radius, 0));
|
||||
return node;
|
||||
}
|
||||
|
||||
Ptr<ShapeNode> ShapeNode::createTriangle(const Vec2 &p1, const Vec2 &p2,
|
||||
const Vec2 &p3, const Color &color,
|
||||
float width) {
|
||||
auto node = makePtr<ShapeNode>();
|
||||
node->shapeType_ = ShapeType::Triangle;
|
||||
node->color_ = color;
|
||||
node->lineWidth_ = width;
|
||||
node->filled_ = false;
|
||||
node->points_ = {p1, p2, p3};
|
||||
return node;
|
||||
Ref<ShapeNode> ShapeNode::createFilledCircle(const Vec2& center, float radius,
|
||||
const Color& color, int segments) {
|
||||
auto node = createCircle(center, radius, color, segments, 0);
|
||||
node->filled_ = true;
|
||||
return node;
|
||||
}
|
||||
|
||||
Ptr<ShapeNode> ShapeNode::createFilledTriangle(const Vec2 &p1, const Vec2 &p2,
|
||||
const Vec2 &p3,
|
||||
const Color &color) {
|
||||
auto node = createTriangle(p1, p2, p3, color, 0);
|
||||
node->filled_ = true;
|
||||
return node;
|
||||
Ref<ShapeNode> ShapeNode::createTriangle(const Vec2& p1, const Vec2& p2,
|
||||
const Vec2& p3, const Color& color, float width) {
|
||||
auto node = ptr::make<ShapeNode>();
|
||||
node->shapeType_ = ShapeType::Triangle;
|
||||
node->color_ = color;
|
||||
node->lineWidth_ = width;
|
||||
node->filled_ = false;
|
||||
node->points_ = {p1, p2, p3};
|
||||
return node;
|
||||
}
|
||||
|
||||
Ptr<ShapeNode> ShapeNode::createPolygon(const std::vector<Vec2> &points,
|
||||
const Color &color, float width) {
|
||||
auto node = makePtr<ShapeNode>();
|
||||
node->shapeType_ = ShapeType::Polygon;
|
||||
node->color_ = color;
|
||||
node->lineWidth_ = width;
|
||||
node->filled_ = false;
|
||||
node->points_ = points;
|
||||
return node;
|
||||
Ref<ShapeNode> ShapeNode::createFilledTriangle(const Vec2& p1, const Vec2& p2,
|
||||
const Vec2& p3, const Color& color) {
|
||||
auto node = createTriangle(p1, p2, p3, color, 0);
|
||||
node->filled_ = true;
|
||||
return node;
|
||||
}
|
||||
|
||||
Ptr<ShapeNode> ShapeNode::createFilledPolygon(const std::vector<Vec2> &points,
|
||||
const Color &color) {
|
||||
auto node = createPolygon(points, color, 0);
|
||||
node->filled_ = true;
|
||||
return node;
|
||||
Ref<ShapeNode> ShapeNode::createPolygon(const std::vector<Vec2>& points,
|
||||
const Color& color, float width) {
|
||||
auto node = ptr::make<ShapeNode>();
|
||||
node->shapeType_ = ShapeType::Polygon;
|
||||
node->color_ = color;
|
||||
node->lineWidth_ = width;
|
||||
node->filled_ = false;
|
||||
node->points_ = points;
|
||||
return node;
|
||||
}
|
||||
|
||||
void ShapeNode::setPoints(const std::vector<Vec2> &points) {
|
||||
points_ = points;
|
||||
updateSpatialIndex();
|
||||
Ref<ShapeNode> ShapeNode::createFilledPolygon(const std::vector<Vec2>& points,
|
||||
const Color& color) {
|
||||
auto node = createPolygon(points, color, 0);
|
||||
node->filled_ = true;
|
||||
return node;
|
||||
}
|
||||
|
||||
void ShapeNode::addPoint(const Vec2 &point) {
|
||||
points_.push_back(point);
|
||||
updateSpatialIndex();
|
||||
void ShapeNode::setPoints(const std::vector<Vec2>& points) {
|
||||
points_ = points;
|
||||
updateSpatialIndex();
|
||||
}
|
||||
|
||||
void ShapeNode::addPoint(const Vec2& point) {
|
||||
points_.push_back(point);
|
||||
updateSpatialIndex();
|
||||
}
|
||||
|
||||
void ShapeNode::clearPoints() {
|
||||
points_.clear();
|
||||
updateSpatialIndex();
|
||||
points_.clear();
|
||||
updateSpatialIndex();
|
||||
}
|
||||
|
||||
Rect ShapeNode::getBoundingBox() const {
|
||||
if (points_.empty()) {
|
||||
return Rect();
|
||||
}
|
||||
if (points_.empty()) {
|
||||
return Rect();
|
||||
}
|
||||
|
||||
Vec2 offset = getPosition();
|
||||
Vec2 offset = getPosition();
|
||||
|
||||
if (shapeType_ == ShapeType::Circle && points_.size() >= 2) {
|
||||
float radius = std::abs(points_[1].x);
|
||||
Vec2 center = points_[0] + offset;
|
||||
return Rect(center.x - radius, center.y - radius, radius * 2.0f,
|
||||
radius * 2.0f);
|
||||
}
|
||||
if (shapeType_ == ShapeType::Circle && points_.size() >= 2) {
|
||||
float radius = std::abs(points_[1].x);
|
||||
Vec2 center = points_[0] + offset;
|
||||
return Rect(center.x - radius, center.y - radius, radius * 2.0f, radius * 2.0f);
|
||||
}
|
||||
|
||||
float minX = std::numeric_limits<float>::infinity();
|
||||
float minY = std::numeric_limits<float>::infinity();
|
||||
float maxX = -std::numeric_limits<float>::infinity();
|
||||
float maxY = -std::numeric_limits<float>::infinity();
|
||||
float minX = std::numeric_limits<float>::infinity();
|
||||
float minY = std::numeric_limits<float>::infinity();
|
||||
float maxX = -std::numeric_limits<float>::infinity();
|
||||
float maxY = -std::numeric_limits<float>::infinity();
|
||||
|
||||
for (const auto &p : points_) {
|
||||
Vec2 world = p + offset;
|
||||
minX = std::min(minX, world.x);
|
||||
minY = std::min(minY, world.y);
|
||||
maxX = std::max(maxX, world.x);
|
||||
maxY = std::max(maxY, world.y);
|
||||
}
|
||||
for (const auto& p : points_) {
|
||||
Vec2 world = p + offset;
|
||||
minX = std::min(minX, world.x);
|
||||
minY = std::min(minY, world.y);
|
||||
maxX = std::max(maxX, world.x);
|
||||
maxY = std::max(maxY, world.y);
|
||||
}
|
||||
|
||||
float inflate = 0.0f;
|
||||
if (!filled_ &&
|
||||
(shapeType_ == ShapeType::Line || shapeType_ == ShapeType::Rect ||
|
||||
shapeType_ == ShapeType::Triangle || shapeType_ == ShapeType::Polygon ||
|
||||
shapeType_ == ShapeType::Point)) {
|
||||
inflate = std::max(0.0f, lineWidth_ * 0.5f);
|
||||
}
|
||||
if (shapeType_ == ShapeType::Point) {
|
||||
inflate = std::max(inflate, lineWidth_ * 0.5f);
|
||||
}
|
||||
float inflate = 0.0f;
|
||||
if (!filled_ && (shapeType_ == ShapeType::Line || shapeType_ == ShapeType::Rect ||
|
||||
shapeType_ == ShapeType::Triangle || shapeType_ == ShapeType::Polygon ||
|
||||
shapeType_ == ShapeType::Point)) {
|
||||
inflate = std::max(0.0f, lineWidth_ * 0.5f);
|
||||
}
|
||||
if (shapeType_ == ShapeType::Point) {
|
||||
inflate = std::max(inflate, lineWidth_ * 0.5f);
|
||||
}
|
||||
|
||||
return Rect(minX - inflate, minY - inflate, (maxX - minX) + inflate * 2.0f,
|
||||
(maxY - minY) + inflate * 2.0f);
|
||||
return Rect(minX - inflate, minY - inflate, (maxX - minX) + inflate * 2.0f,
|
||||
(maxY - minY) + inflate * 2.0f);
|
||||
}
|
||||
|
||||
void ShapeNode::onDraw(Renderer &renderer) {
|
||||
if (points_.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Vec2 offset = getPosition();
|
||||
|
||||
switch (shapeType_) {
|
||||
case ShapeType::Point:
|
||||
if (!points_.empty()) {
|
||||
renderer.fillCircle(points_[0] + offset, lineWidth_ * 0.5f, color_, 8);
|
||||
void ShapeNode::onDraw(Renderer& renderer) {
|
||||
if (points_.empty()) {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case ShapeType::Line:
|
||||
if (points_.size() >= 2) {
|
||||
renderer.drawLine(points_[0] + offset, points_[1] + offset, color_,
|
||||
lineWidth_);
|
||||
}
|
||||
break;
|
||||
Vec2 offset = getPosition();
|
||||
|
||||
case ShapeType::Rect:
|
||||
if (points_.size() >= 4) {
|
||||
if (filled_) {
|
||||
Rect rect(points_[0].x, points_[0].y, points_[2].x - points_[0].x,
|
||||
points_[2].y - points_[0].y);
|
||||
renderer.fillRect(Rect(rect.origin + offset, rect.size), color_);
|
||||
} else {
|
||||
for (size_t i = 0; i < points_.size(); ++i) {
|
||||
Vec2 start = points_[i] + offset;
|
||||
Vec2 end = points_[(i + 1) % points_.size()] + offset;
|
||||
renderer.drawLine(start, end, color_, lineWidth_);
|
||||
switch (shapeType_) {
|
||||
case ShapeType::Point:
|
||||
if (!points_.empty()) {
|
||||
renderer.fillCircle(points_[0] + offset, lineWidth_ * 0.5f, color_, 8);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case ShapeType::Line:
|
||||
if (points_.size() >= 2) {
|
||||
renderer.drawLine(points_[0] + offset, points_[1] + offset, color_, lineWidth_);
|
||||
}
|
||||
break;
|
||||
|
||||
case ShapeType::Rect:
|
||||
if (points_.size() >= 4) {
|
||||
Rect rect(points_[0].x, points_[0].y, points_[2].x - points_[0].x,
|
||||
points_[2].y - points_[0].y);
|
||||
Rect worldRect(rect.origin + offset, rect.size);
|
||||
if (filled_) {
|
||||
renderer.fillRect(worldRect, color_);
|
||||
} else {
|
||||
renderer.drawRect(worldRect, color_, lineWidth_);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case ShapeType::Circle:
|
||||
if (points_.size() >= 2) {
|
||||
float radius = points_[1].x;
|
||||
if (filled_) {
|
||||
renderer.fillCircle(points_[0] + offset, radius, color_, segments_);
|
||||
} else {
|
||||
renderer.drawCircle(points_[0] + offset, radius, color_, segments_, lineWidth_);
|
||||
}
|
||||
}
|
||||
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_) {
|
||||
renderer.fillTriangle(p1, p2, p3, color_);
|
||||
} else {
|
||||
renderer.drawTriangle(p1, p2, p3, color_, lineWidth_);
|
||||
}
|
||||
}
|
||||
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_) {
|
||||
renderer.fillPolygon(transformedPoints, color_);
|
||||
} else {
|
||||
renderer.drawPolygon(transformedPoints, color_, lineWidth_);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case ShapeType::Circle:
|
||||
if (points_.size() >= 2) {
|
||||
float radius = points_[1].x;
|
||||
if (filled_) {
|
||||
renderer.fillCircle(points_[0] + offset, radius, color_, segments_);
|
||||
} else {
|
||||
renderer.drawCircle(points_[0] + offset, radius, color_, segments_,
|
||||
lineWidth_);
|
||||
}
|
||||
}
|
||||
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_) {
|
||||
renderer.fillTriangle(p1, p2, p3, color_);
|
||||
} else {
|
||||
renderer.drawLine(p1, p2, color_, lineWidth_);
|
||||
renderer.drawLine(p2, p3, color_, lineWidth_);
|
||||
renderer.drawLine(p3, p1, color_, lineWidth_);
|
||||
}
|
||||
}
|
||||
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_) {
|
||||
renderer.fillPolygon(transformedPoints, color_);
|
||||
} else {
|
||||
renderer.drawPolygon(transformedPoints, color_, lineWidth_);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -1,167 +1,114 @@
|
|||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <extra2d/graphics/renderer.h>
|
||||
#include <extra2d/graphics/render_command.h>
|
||||
#include <extra2d/graphics/texture.h>
|
||||
#include <extra2d/render/renderer.h>
|
||||
#include <extra2d/render/texture.h>
|
||||
#include <extra2d/scene/sprite.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
Sprite::Sprite() = default;
|
||||
|
||||
Sprite::Sprite(Ptr<Texture> texture) { setTexture(texture); }
|
||||
|
||||
void Sprite::setTexture(Ptr<Texture> texture) {
|
||||
texture_ = texture;
|
||||
if (texture_) {
|
||||
textureRect_ = Rect(0, 0, static_cast<float>(texture_->getWidth()),
|
||||
static_cast<float>(texture_->getHeight()));
|
||||
}
|
||||
updateSpatialIndex();
|
||||
Sprite::Sprite(Ref<Texture> texture) {
|
||||
setTexture(texture);
|
||||
}
|
||||
|
||||
void Sprite::setTextureRect(const Rect &rect) {
|
||||
textureRect_ = rect;
|
||||
updateSpatialIndex();
|
||||
void Sprite::setTexture(Ref<Texture> texture) {
|
||||
texture_ = texture;
|
||||
if (texture_) {
|
||||
textureRect_ = Rect(0, 0, static_cast<float>(texture_->width()),
|
||||
static_cast<float>(texture_->height()));
|
||||
}
|
||||
updateSpatialIndex();
|
||||
}
|
||||
|
||||
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);
|
||||
void Sprite::setTextureRect(const Rect& rect) {
|
||||
textureRect_ = rect;
|
||||
updateSpatialIndex();
|
||||
}
|
||||
|
||||
Ptr<Sprite> Sprite::create(Ptr<Texture> texture, const Rect &rect) {
|
||||
auto sprite = makePtr<Sprite>(texture);
|
||||
sprite->setTextureRect(rect);
|
||||
return sprite;
|
||||
void Sprite::setColor(const Color& color) {
|
||||
color_ = color;
|
||||
}
|
||||
|
||||
void Sprite::setFlipX(bool flip) {
|
||||
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);
|
||||
return sprite;
|
||||
}
|
||||
|
||||
Rect Sprite::getBoundingBox() const {
|
||||
if (!texture_ || !texture_->isValid()) {
|
||||
return Rect();
|
||||
}
|
||||
if (!texture_ || !texture_->isValid()) {
|
||||
return Rect();
|
||||
}
|
||||
|
||||
float width = textureRect_.width();
|
||||
float height = textureRect_.height();
|
||||
float width = textureRect_.width();
|
||||
float height = textureRect_.height();
|
||||
|
||||
auto pos = getPosition();
|
||||
auto anchor = getAnchor();
|
||||
auto scale = getScale();
|
||||
auto pos = getPosition();
|
||||
auto anchor = getAnchor();
|
||||
auto scale = getScale();
|
||||
|
||||
float w = width * scale.x;
|
||||
float h = height * scale.y;
|
||||
float x0 = pos.x - width * anchor.x * scale.x;
|
||||
float y0 = pos.y - height * anchor.y * scale.y;
|
||||
float x1 = x0 + w;
|
||||
float y1 = y0 + h;
|
||||
float w = width * scale.x;
|
||||
float h = height * scale.y;
|
||||
float x0 = pos.x - width * anchor.x * scale.x;
|
||||
float y0 = pos.y - height * anchor.y * scale.y;
|
||||
float x1 = x0 + w;
|
||||
float y1 = y0 + h;
|
||||
|
||||
float l = std::min(x0, x1);
|
||||
float t = std::min(y0, y1);
|
||||
return Rect(l, t, std::abs(w), std::abs(h));
|
||||
float l = std::min(x0, x1);
|
||||
float t = std::min(y0, y1);
|
||||
return Rect(l, t, std::abs(w), std::abs(h));
|
||||
}
|
||||
|
||||
void Sprite::onDraw(Renderer &renderer) {
|
||||
if (!texture_ || !texture_->isValid()) {
|
||||
return;
|
||||
}
|
||||
void Sprite::onDraw(Renderer& renderer) {
|
||||
if (!texture_ || !texture_->isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate destination rectangle based on texture rect
|
||||
float width = textureRect_.width();
|
||||
float height = textureRect_.height();
|
||||
float width = textureRect_.width();
|
||||
float height = textureRect_.height();
|
||||
|
||||
// 使用世界变换来获取最终的位置
|
||||
auto worldTransform = getWorldTransform();
|
||||
auto worldTransform = getWorldTransform();
|
||||
|
||||
// 从世界变换矩阵中提取位置(第四列)
|
||||
float worldX = worldTransform[3][0];
|
||||
float worldY = worldTransform[3][1];
|
||||
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]));
|
||||
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();
|
||||
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_;
|
||||
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;
|
||||
}
|
||||
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]);
|
||||
float worldRotation = std::atan2(worldTransform[0][1], worldTransform[0][0]);
|
||||
|
||||
renderer.drawSprite(*texture_, destRect, srcRect, color_, worldRotation,
|
||||
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));
|
||||
renderer.drawSprite(*texture_, destRect, srcRect, color_, worldRotation, anchor);
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
|
|||
|
|
@ -1,193 +1,267 @@
|
|||
#include <chrono>
|
||||
#include <cstdarg>
|
||||
#include <cstdio>
|
||||
#include <extra2d/services/logger_service.h>
|
||||
#include <mutex>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#endif
|
||||
#include <SDL.h>
|
||||
|
||||
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 {
|
||||
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()
|
||||
: level_(LogLevel::Info), colors_(true),
|
||||
impl_(std::make_unique<Impl>()) {
|
||||
info_.name = "ConsoleLogger";
|
||||
info_.priority = ServicePriority::Core;
|
||||
info_.name = "ConsoleLogger";
|
||||
info_.priority = ServicePriority::Core;
|
||||
|
||||
levelColors_[static_cast<int>(LogLevel::Trace)] = LogColor::Gray();
|
||||
levelColors_[static_cast<int>(LogLevel::Debug)] = LogColor::Cyan();
|
||||
levelColors_[static_cast<int>(LogLevel::Info)] = LogColor::SkyLight();
|
||||
levelColors_[static_cast<int>(LogLevel::Registry)] = LogColor::IndigoLight();
|
||||
levelColors_[static_cast<int>(LogLevel::Warn)] = LogColor::Yellow();
|
||||
levelColors_[static_cast<int>(LogLevel::Error)] = LogColor::Red();
|
||||
levelColors_[static_cast<int>(LogLevel::Fatal)] = LogColor::Magenta();
|
||||
levelColors_[static_cast<int>(LogLevel::Trace)] = LogColor::Gray();
|
||||
levelColors_[static_cast<int>(LogLevel::Debug)] = LogColor::Cyan();
|
||||
levelColors_[static_cast<int>(LogLevel::Info)] = LogColor::SkyLight();
|
||||
levelColors_[static_cast<int>(LogLevel::Registry)] = LogColor::IndigoLight();
|
||||
levelColors_[static_cast<int>(LogLevel::Warn)] = LogColor::Yellow();
|
||||
levelColors_[static_cast<int>(LogLevel::Error)] = LogColor::Red();
|
||||
levelColors_[static_cast<int>(LogLevel::Fatal)] = LogColor::Magenta();
|
||||
|
||||
#ifdef _WIN32
|
||||
if (!g_windowsConsoleInitialized) {
|
||||
g_windowsConsoleInitialized = enableWindowsConsoleFeatures();
|
||||
}
|
||||
#endif
|
||||
for (int i = 0; i < 7; ++i) {
|
||||
impl_->sdlPriorities_[i] = toSDLPriority(static_cast<LogLevel>(i));
|
||||
}
|
||||
}
|
||||
|
||||
ConsoleLogger::~ConsoleLogger() = default;
|
||||
ConsoleLogger::~ConsoleLogger() {
|
||||
if (impl_->customOutputSet_) {
|
||||
SDL_LogSetOutputFunction(nullptr, nullptr);
|
||||
}
|
||||
g_loggerInstance = nullptr;
|
||||
}
|
||||
|
||||
bool ConsoleLogger::init() {
|
||||
setState(ServiceState::Running);
|
||||
return true;
|
||||
g_loggerInstance = this;
|
||||
|
||||
SDL_LogSetOutputFunction(sdlLogOutputFunction, this);
|
||||
impl_->customOutputSet_ = true;
|
||||
|
||||
SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION, toSDLPriority(level_));
|
||||
|
||||
setState(ServiceState::Running);
|
||||
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 {
|
||||
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, ...) {
|
||||
if (!enabled(lvl))
|
||||
return;
|
||||
if (!enabled(lvl)) return;
|
||||
|
||||
char buffer[1024];
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
vsnprintf(buffer, sizeof(buffer), fmt, args);
|
||||
va_end(args);
|
||||
char buffer[2048];
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
vsnprintf(buffer, sizeof(buffer), fmt, 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) {
|
||||
if (!enabled(lvl))
|
||||
return;
|
||||
output(lvl, msg.c_str());
|
||||
if (!enabled(lvl)) return;
|
||||
|
||||
SDL_LogPriority priority = toSDLPriority(lvl);
|
||||
SDL_LogMessage(SDL_LOG_CATEGORY_APPLICATION, priority, "%s", msg.c_str());
|
||||
}
|
||||
|
||||
void ConsoleLogger::trace(const char *fmt, ...) {
|
||||
if (!enabled(LogLevel::Trace))
|
||||
return;
|
||||
char buffer[1024];
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
vsnprintf(buffer, sizeof(buffer), fmt, args);
|
||||
va_end(args);
|
||||
output(LogLevel::Trace, buffer);
|
||||
if (!enabled(LogLevel::Trace)) return;
|
||||
|
||||
char buffer[2048];
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
vsnprintf(buffer, sizeof(buffer), fmt, args);
|
||||
va_end(args);
|
||||
|
||||
SDL_LogVerbose(SDL_LOG_CATEGORY_APPLICATION, "%s", buffer);
|
||||
}
|
||||
|
||||
void ConsoleLogger::debug(const char *fmt, ...) {
|
||||
if (!enabled(LogLevel::Debug))
|
||||
return;
|
||||
char buffer[1024];
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
vsnprintf(buffer, sizeof(buffer), fmt, args);
|
||||
va_end(args);
|
||||
output(LogLevel::Debug, buffer);
|
||||
if (!enabled(LogLevel::Debug)) return;
|
||||
|
||||
char buffer[2048];
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
vsnprintf(buffer, sizeof(buffer), fmt, args);
|
||||
va_end(args);
|
||||
|
||||
SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "%s", buffer);
|
||||
}
|
||||
|
||||
void ConsoleLogger::info(const char *fmt, ...) {
|
||||
if (!enabled(LogLevel::Info))
|
||||
return;
|
||||
char buffer[1024];
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
vsnprintf(buffer, sizeof(buffer), fmt, args);
|
||||
va_end(args);
|
||||
output(LogLevel::Info, buffer);
|
||||
if (!enabled(LogLevel::Info)) return;
|
||||
|
||||
char buffer[2048];
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
vsnprintf(buffer, sizeof(buffer), fmt, args);
|
||||
va_end(args);
|
||||
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "%s", buffer);
|
||||
}
|
||||
|
||||
void ConsoleLogger::registry(const char *fmt, ...) {
|
||||
if (!enabled(LogLevel::Registry))
|
||||
return;
|
||||
char buffer[1024];
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
vsnprintf(buffer, sizeof(buffer), fmt, args);
|
||||
va_end(args);
|
||||
output(LogLevel::Registry, buffer);
|
||||
if (!enabled(LogLevel::Registry)) return;
|
||||
|
||||
char buffer[2048];
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
vsnprintf(buffer, sizeof(buffer), fmt, args);
|
||||
va_end(args);
|
||||
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "[REGISTRY] %s", buffer);
|
||||
}
|
||||
|
||||
void ConsoleLogger::warn(const char *fmt, ...) {
|
||||
if (!enabled(LogLevel::Warn))
|
||||
return;
|
||||
char buffer[1024];
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
vsnprintf(buffer, sizeof(buffer), fmt, args);
|
||||
va_end(args);
|
||||
output(LogLevel::Warn, buffer);
|
||||
if (!enabled(LogLevel::Warn)) return;
|
||||
|
||||
char buffer[2048];
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
vsnprintf(buffer, sizeof(buffer), fmt, args);
|
||||
va_end(args);
|
||||
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "%s", buffer);
|
||||
}
|
||||
|
||||
void ConsoleLogger::error(const char *fmt, ...) {
|
||||
if (!enabled(LogLevel::Error))
|
||||
return;
|
||||
char buffer[1024];
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
vsnprintf(buffer, sizeof(buffer), fmt, args);
|
||||
va_end(args);
|
||||
output(LogLevel::Error, buffer);
|
||||
if (!enabled(LogLevel::Error)) return;
|
||||
|
||||
char buffer[2048];
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
vsnprintf(buffer, sizeof(buffer), fmt, args);
|
||||
va_end(args);
|
||||
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "%s", buffer);
|
||||
}
|
||||
|
||||
void ConsoleLogger::fatal(const char *fmt, ...) {
|
||||
if (!enabled(LogLevel::Fatal))
|
||||
return;
|
||||
char buffer[1024];
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
vsnprintf(buffer, sizeof(buffer), fmt, args);
|
||||
va_end(args);
|
||||
output(LogLevel::Fatal, buffer);
|
||||
if (!enabled(LogLevel::Fatal)) return;
|
||||
|
||||
char buffer[2048];
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
vsnprintf(buffer, sizeof(buffer), fmt, args);
|
||||
va_end(args);
|
||||
|
||||
SDL_LogCritical(SDL_LOG_CATEGORY_APPLICATION, "%s", buffer);
|
||||
}
|
||||
|
||||
void ConsoleLogger::levelColor(LogLevel lvl, const LogColor &c) {
|
||||
int idx = static_cast<int>(lvl);
|
||||
if (idx >= 0 && idx < 7) {
|
||||
levelColors_[idx] = c;
|
||||
}
|
||||
int idx = static_cast<int>(lvl);
|
||||
if (idx >= 0 && idx < 7) {
|
||||
levelColors_[idx] = c;
|
||||
}
|
||||
}
|
||||
|
||||
LogColor ConsoleLogger::levelColor(LogLevel lvl) const {
|
||||
int idx = static_cast<int>(lvl);
|
||||
if (idx >= 0 && idx < 7) {
|
||||
return levelColors_[idx];
|
||||
}
|
||||
return LogColor::White();
|
||||
int idx = static_cast<int>(lvl);
|
||||
if (idx >= 0 && idx < 7) {
|
||||
return levelColors_[idx];
|
||||
}
|
||||
return LogColor::White();
|
||||
}
|
||||
|
||||
void ConsoleLogger::colors(bool on) { colors_ = on; }
|
||||
|
|
@ -195,60 +269,23 @@ void ConsoleLogger::colors(bool on) { colors_ = on; }
|
|||
bool ConsoleLogger::colors() const { return colors_; }
|
||||
|
||||
std::string ConsoleLogger::ansiColor(LogLevel lvl) {
|
||||
const LogColor &c = levelColor(lvl);
|
||||
char buf[32];
|
||||
snprintf(buf, sizeof(buf), "\033[38;2;%d;%d;%dm", c.r, c.g, c.b);
|
||||
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 LogColor &c = levelColor(lvl);
|
||||
char buf[32];
|
||||
snprintf(buf, sizeof(buf), "\033[38;2;%d;%d;%dm", c.r, c.g, c.b);
|
||||
return std::string(buf);
|
||||
}
|
||||
|
||||
const char *ConsoleLogger::levelString(LogLevel lvl) {
|
||||
switch (lvl) {
|
||||
case LogLevel::Trace:
|
||||
return "TRACE";
|
||||
case LogLevel::Debug:
|
||||
return "DEBUG";
|
||||
case LogLevel::Info:
|
||||
return "INFO";
|
||||
case LogLevel::Registry:
|
||||
return "REGISTRY";
|
||||
case LogLevel::Warn:
|
||||
return "WARN";
|
||||
case LogLevel::Error:
|
||||
return "ERROR";
|
||||
case LogLevel::Fatal:
|
||||
return "FATAL";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
switch (lvl) {
|
||||
case LogLevel::Trace: return "TRACE";
|
||||
case LogLevel::Debug: return "DEBUG";
|
||||
case LogLevel::Info: return "INFO";
|
||||
case LogLevel::Registry: return "REGISTRY";
|
||||
case LogLevel::Warn: return "WARN";
|
||||
case LogLevel::Error: return "ERROR";
|
||||
case LogLevel::Fatal: return "FATAL";
|
||||
default: return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
#include <extra2d/utils/data.h>
|
||||
#include <extra2d/utils/logger.h>
|
||||
#include <extra2d/services/logger_service.h>
|
||||
#include <simpleini/SimpleIni.h>
|
||||
|
||||
// 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,63 +1,33 @@
|
|||
#include <extra2d/services/logger_service.h>
|
||||
#include <extra2d/window/window.h>
|
||||
#include <extra2d/platform/input.h>
|
||||
#include <extra2d/utils/logger.h>
|
||||
|
||||
#include <SDL.h>
|
||||
#include <glad/glad.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
Window::Window()
|
||||
: sdlWindow_(nullptr), glContext_(nullptr),
|
||||
width_(1280), height_(720), vsync_(true), shouldClose_(false),
|
||||
fullscreen_(true) {
|
||||
}
|
||||
: sdlWindow_(nullptr), width_(1280), height_(720), fullscreen_(true),
|
||||
shouldClose_(false) {}
|
||||
|
||||
Window::~Window() { destroy(); }
|
||||
|
||||
bool Window::create(const WindowConfig &config) {
|
||||
if (sdlWindow_ != nullptr) {
|
||||
E2D_LOG_WARN("Window already created");
|
||||
E2D_WARN("Window already created");
|
||||
return false;
|
||||
}
|
||||
|
||||
width_ = config.width;
|
||||
height_ = config.height;
|
||||
vsync_ = config.vsync;
|
||||
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) {
|
||||
E2D_LOG_ERROR("SDL_Init failed: {}", SDL_GetError());
|
||||
E2D_ERROR("SDL_Init failed: {}", SDL_GetError());
|
||||
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;
|
||||
|
||||
|
||||
if (config.fullscreen) {
|
||||
windowFlags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
|
||||
} else {
|
||||
|
|
@ -71,70 +41,23 @@ bool Window::initSDL(const WindowConfig &config) {
|
|||
config.centerWindow ? SDL_WINDOWPOS_CENTERED : SDL_WINDOWPOS_UNDEFINED,
|
||||
config.centerWindow ? SDL_WINDOWPOS_CENTERED : SDL_WINDOWPOS_UNDEFINED,
|
||||
width_, height_, windowFlags);
|
||||
|
||||
|
||||
if (!sdlWindow_) {
|
||||
E2D_LOG_ERROR("SDL_CreateWindow failed: {}", SDL_GetError());
|
||||
E2D_ERROR("SDL_CreateWindow failed: {}", SDL_GetError());
|
||||
SDL_Quit();
|
||||
return false;
|
||||
}
|
||||
|
||||
glContext_ = SDL_GL_CreateContext(sdlWindow_);
|
||||
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)));
|
||||
|
||||
E2D_INFO("Window created: {}x{}", width_, height_);
|
||||
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() {
|
||||
if (sdlWindow_ != nullptr) {
|
||||
input_.reset();
|
||||
deinitSDL();
|
||||
E2D_LOG_INFO("Window destroyed");
|
||||
SDL_DestroyWindow(sdlWindow_);
|
||||
sdlWindow_ = nullptr;
|
||||
SDL_Quit();
|
||||
E2D_INFO("Window destroyed");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -155,16 +78,6 @@ void Window::pollEvents() {
|
|||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (input_) {
|
||||
input_->update();
|
||||
}
|
||||
}
|
||||
|
||||
void Window::swapBuffers() {
|
||||
if (sdlWindow_) {
|
||||
SDL_GL_SwapWindow(sdlWindow_);
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ function define_extra2d_engine()
|
|||
add_cxxflags("-Wno-deprecated-copy", "-Wno-class-memaccess", {force = true})
|
||||
|
||||
if is_mode("debug") then
|
||||
add_defines("E2D_DEBUG", "_DEBUG", {public = true})
|
||||
add_defines("_DEBUG", {public = true})
|
||||
add_cxxflags("-O0", "-g", {force = true})
|
||||
else
|
||||
add_defines("NDEBUG", {public = true})
|
||||
|
|
|
|||
Loading…
Reference in New Issue