refactor: 移除图形渲染和场景管理相关代码
移除不再需要的图形渲染、纹理、字体、场景管理等模块代码,清理相关头文件引用。主要变更包括: - 删除renderer、graphics、scene目录下的大部分文件 - 清理extra2d.h中的冗余头文件引用 - 简化Application类,移除渲染和场景管理相关功能
This commit is contained in:
parent
d81f0c1e45
commit
ea081b9dd3
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
#include <core/types.h>
|
||||
#include <string>
|
||||
#include <renderer/renderer.h>
|
||||
#include <memory>
|
||||
#include <platform/window.h>
|
||||
|
||||
|
|
@ -11,12 +10,9 @@ namespace extra2d {
|
|||
// 前向声明
|
||||
class Input;
|
||||
class AudioEngine;
|
||||
class SceneManager;
|
||||
class ResourceManager;
|
||||
class TimerManager;
|
||||
class EventQueue;
|
||||
class EventDispatcher;
|
||||
class Camera;
|
||||
|
||||
// ============================================================================
|
||||
// Application 配置
|
||||
|
|
@ -32,12 +28,10 @@ struct AppConfig {
|
|||
bool resizable = true;
|
||||
bool vsync = true;
|
||||
int fpsLimit = 0;
|
||||
BackendType Renderer = BackendType::OpenGL;
|
||||
int msaaSamples = 0;
|
||||
PlatformType platform = PlatformType::Auto;
|
||||
// 窗口高级配置
|
||||
bool enableCursors = true; // 是否启用光标
|
||||
bool enableDpiScale = false; // 是否启用DPI缩放
|
||||
bool enableCursors = true;
|
||||
bool enableDpiScale = false;
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
|
|
@ -45,53 +39,32 @@ struct AppConfig {
|
|||
// ============================================================================
|
||||
class Application {
|
||||
public:
|
||||
// Meyer's 单例
|
||||
static Application &instance();
|
||||
|
||||
// 禁止拷贝
|
||||
Application(const Application &) = delete;
|
||||
Application &operator=(const Application &) = delete;
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 生命周期
|
||||
// ------------------------------------------------------------------------
|
||||
bool init(const AppConfig &config);
|
||||
void shutdown();
|
||||
void run();
|
||||
void quit();
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 状态控制
|
||||
// ------------------------------------------------------------------------
|
||||
void pause();
|
||||
void resume();
|
||||
bool isPaused() const { return paused_; }
|
||||
bool isRunning() const { return running_; }
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 子系统访问
|
||||
// ------------------------------------------------------------------------
|
||||
Window &window() { return *window_; }
|
||||
Renderer &renderer() { return *renderer_; }
|
||||
Input &input();
|
||||
AudioEngine &audio();
|
||||
SceneManager &scenes();
|
||||
ResourceManager &resources();
|
||||
TimerManager &timers();
|
||||
EventQueue &eventQueue();
|
||||
EventDispatcher &eventDispatcher();
|
||||
Camera &camera();
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 便捷方法
|
||||
// ------------------------------------------------------------------------
|
||||
void enterScene(IntrusivePtr<class Scene> scene);
|
||||
|
||||
float deltaTime() const { return deltaTime_; }
|
||||
float totalTime() const { return totalTime_; }
|
||||
int fps() const { return currentFps_; }
|
||||
|
||||
// 获取配置
|
||||
const AppConfig &getConfig() const { return config_; }
|
||||
|
||||
private:
|
||||
|
|
@ -100,28 +73,19 @@ private:
|
|||
|
||||
void mainLoop();
|
||||
void update();
|
||||
void render();
|
||||
|
||||
// 配置
|
||||
AppConfig config_;
|
||||
|
||||
// 子系统
|
||||
UniquePtr<Window> window_;
|
||||
UniquePtr<Renderer> renderer_;
|
||||
UniquePtr<SceneManager> sceneManager_;
|
||||
UniquePtr<ResourceManager> resourceManager_;
|
||||
UniquePtr<TimerManager> timerManager_;
|
||||
UniquePtr<EventQueue> eventQueue_;
|
||||
UniquePtr<EventDispatcher> eventDispatcher_;
|
||||
UniquePtr<Camera> camera_;
|
||||
|
||||
// 状态
|
||||
bool initialized_ = false;
|
||||
bool running_ = false;
|
||||
bool paused_ = false;
|
||||
bool shouldQuit_ = false;
|
||||
|
||||
// 时间
|
||||
float deltaTime_ = 0.0f;
|
||||
float totalTime_ = 0.0f;
|
||||
double lastFrameTime_ = 0.0;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
#include <core/intrusive_ptr.h>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
|
|
@ -11,37 +10,38 @@ class Sound;
|
|||
|
||||
class AudioEngine {
|
||||
public:
|
||||
static AudioEngine& getInstance();
|
||||
static AudioEngine &getInstance();
|
||||
|
||||
AudioEngine(const AudioEngine&) = delete;
|
||||
AudioEngine& operator=(const AudioEngine&) = delete;
|
||||
AudioEngine(AudioEngine&&) = delete;
|
||||
AudioEngine& operator=(AudioEngine&&) = delete;
|
||||
AudioEngine(const AudioEngine &) = delete;
|
||||
AudioEngine &operator=(const AudioEngine &) = delete;
|
||||
AudioEngine(AudioEngine &&) = delete;
|
||||
AudioEngine &operator=(AudioEngine &&) = delete;
|
||||
|
||||
bool initialize();
|
||||
void shutdown();
|
||||
bool initialize();
|
||||
void shutdown();
|
||||
|
||||
IntrusivePtr<Sound> loadSound(const std::string& filePath);
|
||||
IntrusivePtr<Sound> loadSound(const std::string& name, const std::string& filePath);
|
||||
IntrusivePtr<Sound> loadSound(const std::string &filePath);
|
||||
IntrusivePtr<Sound> loadSound(const std::string &name,
|
||||
const std::string &filePath);
|
||||
|
||||
IntrusivePtr<Sound> getSound(const std::string& name);
|
||||
void unloadSound(const std::string& name);
|
||||
void unloadAllSounds();
|
||||
IntrusivePtr<Sound> getSound(const std::string &name);
|
||||
void unloadSound(const std::string &name);
|
||||
void unloadAllSounds();
|
||||
|
||||
void setMasterVolume(float volume);
|
||||
float getMasterVolume() const;
|
||||
void setMasterVolume(float volume);
|
||||
float getMasterVolume() const;
|
||||
|
||||
void pauseAll();
|
||||
void resumeAll();
|
||||
void stopAll();
|
||||
void pauseAll();
|
||||
void resumeAll();
|
||||
void stopAll();
|
||||
|
||||
private:
|
||||
AudioEngine() = default;
|
||||
~AudioEngine();
|
||||
AudioEngine() = default;
|
||||
~AudioEngine();
|
||||
|
||||
std::unordered_map<std::string, IntrusivePtr<Sound>> sounds_;
|
||||
float masterVolume_ = 1.0f;
|
||||
bool initialized_ = false;
|
||||
std::unordered_map<std::string, IntrusivePtr<Sound>> sounds_;
|
||||
float masterVolume_ = 1.0f;
|
||||
bool initialized_ = false;
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
|
|||
|
|
@ -16,23 +16,6 @@
|
|||
#include <platform/input.h>
|
||||
#include <platform/window.h>
|
||||
|
||||
// Graphics
|
||||
#include <graphics/font.h>
|
||||
#include <graphics/texture.h>
|
||||
#include <graphics/vram_manager.h>
|
||||
|
||||
// Renderer
|
||||
#include <renderer/camera.h>
|
||||
#include <renderer/render_target.h>
|
||||
#include <renderer/renderer.h>
|
||||
|
||||
// Scene
|
||||
#include <scene/node.h>
|
||||
#include <scene/scene.h>
|
||||
#include <scene/scene_manager.h>
|
||||
#include <scene/shape.h>
|
||||
#include <scene/sprite.h>
|
||||
|
||||
// Event
|
||||
#include <event/event.h>
|
||||
#include <event/event_dispatcher.h>
|
||||
|
|
@ -43,9 +26,6 @@
|
|||
#include <audio/audio_engine.h>
|
||||
#include <audio/sound.h>
|
||||
|
||||
// Resource
|
||||
#include <resource/resource_manager.h>
|
||||
|
||||
// Utils
|
||||
#include <utils/data.h>
|
||||
#include <utils/logger.h>
|
||||
|
|
|
|||
|
|
@ -1,50 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <core/rect.h>
|
||||
#include <core/types.h>
|
||||
#include <core/vec2.h>
|
||||
#include <vector>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// ============================================================================
|
||||
// Alpha 遮罩 - 存储图片的非透明区域信息
|
||||
// ============================================================================
|
||||
class AlphaMask {
|
||||
public:
|
||||
AlphaMask() = default;
|
||||
AlphaMask(int width, int height);
|
||||
|
||||
/// 从像素数据创建遮罩
|
||||
static AlphaMask createFromPixels(const uint8_t *pixels, int width,
|
||||
int height, int channels);
|
||||
|
||||
/// 获取指定位置的透明度(0-255)
|
||||
uint8_t getAlpha(int x, int y) const;
|
||||
|
||||
/// 检查指定位置是否不透明
|
||||
bool isOpaque(int x, int y, uint8_t threshold = 128) const;
|
||||
|
||||
/// 检查指定位置是否在遮罩范围内
|
||||
bool isValid(int x, int y) const;
|
||||
|
||||
/// 获取遮罩尺寸
|
||||
int width() const { return width_; }
|
||||
int height() const { return height_; }
|
||||
Size size() const {
|
||||
return Size(static_cast<float>(width_), static_cast<float>(height_));
|
||||
}
|
||||
|
||||
/// 获取原始数据
|
||||
const std::vector<uint8_t> &getData() const { return data_; }
|
||||
|
||||
/// 检查遮罩是否有效
|
||||
bool isValid() const { return !data_.empty() && width_ > 0 && height_ > 0; }
|
||||
|
||||
private:
|
||||
int width_ = 0;
|
||||
int height_ = 0;
|
||||
std::vector<uint8_t> data_; // Alpha值数组
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <core/color.h>
|
||||
#include <core/rect.h>
|
||||
#include <core/ref_counted.h>
|
||||
#include <core/vec2.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// ============================================================================
|
||||
// 字形信息
|
||||
// ============================================================================
|
||||
struct Glyph {
|
||||
float u0, v0; // 纹理坐标左下角
|
||||
float u1, v1; // 纹理坐标右上角
|
||||
float width; // 字形宽度(像素)
|
||||
float height; // 字形高度(像素)
|
||||
float bearingX; // 水平偏移
|
||||
float bearingY; // 垂直偏移
|
||||
float advance; // 前进距离
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// 字体图集接口
|
||||
// ============================================================================
|
||||
class FontAtlas : public RefCounted {
|
||||
public:
|
||||
virtual ~FontAtlas() = default;
|
||||
|
||||
// 获取字形信息
|
||||
virtual const Glyph *getGlyph(char32_t codepoint) const = 0;
|
||||
|
||||
// 获取纹理
|
||||
virtual class Texture *getTexture() const = 0;
|
||||
|
||||
// 获取字体大小
|
||||
virtual int getFontSize() const = 0;
|
||||
|
||||
virtual float getAscent() const = 0;
|
||||
virtual float getDescent() const = 0;
|
||||
virtual float getLineGap() const = 0;
|
||||
virtual float getLineHeight() const = 0;
|
||||
|
||||
// 计算文字尺寸
|
||||
virtual Vec2 measureText(const std::string &text) = 0;
|
||||
|
||||
// 是否支持 SDF 渲染
|
||||
virtual bool isSDF() const = 0;
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// ============================================================================
|
||||
// GPU 上下文状态管理器
|
||||
// 用于跟踪 OpenGL/Vulkan 等 GPU 上下文的生命周期状态
|
||||
// 确保在 GPU 资源析构时能安全地检查上下文是否有效
|
||||
// ============================================================================
|
||||
|
||||
class GPUContext {
|
||||
public:
|
||||
/// 获取单例实例
|
||||
static GPUContext& getInstance();
|
||||
|
||||
/// 标记 GPU 上下文为有效(在初始化完成后调用)
|
||||
void markValid();
|
||||
|
||||
/// 标记 GPU 上下文为无效(在销毁前调用)
|
||||
void markInvalid();
|
||||
|
||||
/// 检查 GPU 上下文是否有效
|
||||
bool isValid() const;
|
||||
|
||||
private:
|
||||
GPUContext() = default;
|
||||
~GPUContext() = default;
|
||||
GPUContext(const GPUContext&) = delete;
|
||||
GPUContext& operator=(const GPUContext&) = delete;
|
||||
|
||||
std::atomic<bool> valid_{false};
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -1,68 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <core/color.h>
|
||||
#include <core/rect.h>
|
||||
#include <core/types.h>
|
||||
#include <core/vec2.h>
|
||||
#include <graphics/font.h>
|
||||
#include <graphics/opengl/gl_texture.h>
|
||||
#include <graphics/texture.h>
|
||||
#include <memory>
|
||||
#include <stb/stb_rect_pack.h>
|
||||
#include <stb/stb_truetype.h>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// ============================================================================
|
||||
// OpenGL 字体图集实现 - 使用 stb_rect_pack 进行矩形打包
|
||||
// ============================================================================
|
||||
class GLFontAtlas : public FontAtlas {
|
||||
public:
|
||||
GLFontAtlas(const std::string &filepath, int fontSize, bool useSDF = false);
|
||||
~GLFontAtlas();
|
||||
|
||||
// FontAtlas 接口实现
|
||||
const Glyph *getGlyph(char32_t codepoint) const override;
|
||||
Texture *getTexture() const override { return texture_.get(); }
|
||||
int getFontSize() const override { return fontSize_; }
|
||||
float getAscent() const override { return ascent_; }
|
||||
float getDescent() const override { return descent_; }
|
||||
float getLineGap() const override { return lineGap_; }
|
||||
float getLineHeight() const override { return ascent_ - descent_ + lineGap_; }
|
||||
Vec2 measureText(const std::string &text) override;
|
||||
bool isSDF() const override { return useSDF_; }
|
||||
|
||||
private:
|
||||
// 图集配置 - 增大尺寸以支持更多字符
|
||||
static constexpr int ATLAS_WIDTH = 1024;
|
||||
static constexpr int ATLAS_HEIGHT = 1024;
|
||||
static constexpr int PADDING = 2; // 字形之间的间距
|
||||
|
||||
int fontSize_;
|
||||
bool useSDF_;
|
||||
mutable std::unique_ptr<GLTexture> texture_;
|
||||
mutable std::unordered_map<char32_t, Glyph> glyphs_;
|
||||
|
||||
// stb_rect_pack 上下文
|
||||
mutable stbrp_context packContext_;
|
||||
mutable std::vector<stbrp_node> packNodes_;
|
||||
mutable int currentY_;
|
||||
|
||||
std::vector<unsigned char> fontData_;
|
||||
stbtt_fontinfo fontInfo_;
|
||||
float scale_;
|
||||
float ascent_;
|
||||
float descent_;
|
||||
float lineGap_;
|
||||
|
||||
// 预分配字形位图缓冲区,避免每次动态分配
|
||||
mutable std::vector<uint8_t> glyphBitmapCache_;
|
||||
mutable std::vector<uint8_t> glyphRgbaCache_;
|
||||
|
||||
void createAtlas();
|
||||
void cacheGlyph(char32_t codepoint) const;
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -1,131 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <graphics/opengl/gl_shader.h>
|
||||
#include <graphics/opengl/gl_sprite_batch.h>
|
||||
#include <renderer/renderer.h>
|
||||
|
||||
#include <array>
|
||||
#include <glad/glad.h>
|
||||
#include <vector>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
class Window;
|
||||
|
||||
// ============================================================================
|
||||
// OpenGL 渲染器实现
|
||||
// ============================================================================
|
||||
class GLRenderer : public Renderer {
|
||||
public:
|
||||
GLRenderer();
|
||||
~GLRenderer() override;
|
||||
|
||||
// Renderer 接口实现
|
||||
bool init(Window *window) override;
|
||||
void shutdown() override;
|
||||
|
||||
void beginFrame(const Color &clearColor) override;
|
||||
void endFrame() override;
|
||||
void setViewport(int x, int y, int width, int height) override;
|
||||
void setVSync(bool enabled) override;
|
||||
|
||||
void setBlendMode(BlendMode mode) override;
|
||||
void setViewProjection(const glm::mat4 &matrix) override;
|
||||
|
||||
// 变换矩阵栈
|
||||
void pushTransform(const glm::mat4 &transform) override;
|
||||
void popTransform() override;
|
||||
glm::mat4 getCurrentTransform() const override;
|
||||
|
||||
IntrusivePtr<Texture> createTexture(int width, int height, const uint8_t *pixels,
|
||||
int channels) override;
|
||||
IntrusivePtr<Texture> loadTexture(const std::string &filepath) override;
|
||||
|
||||
void beginSpriteBatch() override;
|
||||
void drawSprite(const Texture &texture, const Rect &destRect,
|
||||
const Rect &srcRect, const Color &tint, float rotation,
|
||||
const Vec2 &anchor) override;
|
||||
void drawSprite(const Texture &texture, const Vec2 &position,
|
||||
const Color &tint) override;
|
||||
void endSpriteBatch() override;
|
||||
|
||||
void drawLine(const Vec2 &start, const Vec2 &end, const Color &color,
|
||||
float width) override;
|
||||
void drawRect(const Rect &rect, const Color &color, float width) override;
|
||||
void fillRect(const Rect &rect, const Color &color) override;
|
||||
void drawCircle(const Vec2 ¢er, float radius, const Color &color,
|
||||
int segments, float width) override;
|
||||
void fillCircle(const Vec2 ¢er, float radius, const Color &color,
|
||||
int segments) override;
|
||||
void drawTriangle(const Vec2 &p1, const Vec2 &p2, const Vec2 &p3,
|
||||
const Color &color, float width) override;
|
||||
void fillTriangle(const Vec2 &p1, const Vec2 &p2, const Vec2 &p3,
|
||||
const Color &color) override;
|
||||
void drawPolygon(const std::vector<Vec2> &points, const Color &color,
|
||||
float width) override;
|
||||
void fillPolygon(const std::vector<Vec2> &points,
|
||||
const Color &color) override;
|
||||
|
||||
IntrusivePtr<FontAtlas> createFontAtlas(const std::string &filepath, int fontSize,
|
||||
bool useSDF = false) override;
|
||||
void drawText(const FontAtlas &font, const std::string &text,
|
||||
const Vec2 &position, const Color &color) override;
|
||||
void drawText(const FontAtlas &font, const std::string &text, float x,
|
||||
float y, const Color &color) override;
|
||||
|
||||
Stats getStats() const override { return stats_; }
|
||||
void resetStats() override;
|
||||
|
||||
private:
|
||||
// 形状批处理常量
|
||||
static constexpr size_t MAX_CIRCLE_SEGMENTS = 128;
|
||||
static constexpr size_t MAX_SHAPE_VERTICES = 8192; // 最大形状顶点数
|
||||
static constexpr size_t MAX_LINE_VERTICES = 16384; // 最大线条顶点数
|
||||
|
||||
// 形状顶点结构(包含颜色)
|
||||
struct ShapeVertex {
|
||||
float x, y;
|
||||
float r, g, b, a;
|
||||
};
|
||||
|
||||
Window *window_;
|
||||
GLSpriteBatch spriteBatch_;
|
||||
GLShader shapeShader_;
|
||||
|
||||
GLuint shapeVao_;
|
||||
GLuint shapeVbo_;
|
||||
GLuint lineVao_; // 线条专用 VAO
|
||||
GLuint lineVbo_; // 线条专用 VBO
|
||||
|
||||
glm::mat4 viewProjection_;
|
||||
std::vector<glm::mat4> transformStack_;
|
||||
Stats stats_;
|
||||
bool vsync_;
|
||||
|
||||
// 形状批处理缓冲区(预分配,避免每帧内存分配)
|
||||
std::array<ShapeVertex, MAX_SHAPE_VERTICES> shapeVertexCache_;
|
||||
size_t shapeVertexCount_ = 0;
|
||||
GLenum currentShapeMode_ = GL_TRIANGLES;
|
||||
|
||||
// 线条批处理缓冲区
|
||||
std::array<ShapeVertex, MAX_LINE_VERTICES> lineVertexCache_;
|
||||
size_t lineVertexCount_ = 0;
|
||||
float currentLineWidth_ = 1.0f;
|
||||
|
||||
// OpenGL 状态缓存
|
||||
BlendMode cachedBlendMode_ = BlendMode::None;
|
||||
bool blendEnabled_ = false;
|
||||
int cachedViewportX_ = 0;
|
||||
int cachedViewportY_ = 0;
|
||||
int cachedViewportWidth_ = 0;
|
||||
int cachedViewportHeight_ = 0;
|
||||
|
||||
void initShapeRendering();
|
||||
void flushShapeBatch();
|
||||
void flushLineBatch();
|
||||
void addShapeVertex(float x, float y, const Color &color);
|
||||
void addLineVertex(float x, float y, const Color &color);
|
||||
void submitShapeBatch(GLenum mode);
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <glad/glad.h>
|
||||
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <glm/mat4x4.hpp>
|
||||
#include <glm/vec2.hpp>
|
||||
#include <glm/vec3.hpp>
|
||||
#include <glm/vec4.hpp>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// ============================================================================
|
||||
// OpenGL Shader 程序
|
||||
// ============================================================================
|
||||
class GLShader {
|
||||
public:
|
||||
GLShader();
|
||||
~GLShader();
|
||||
|
||||
// 从源码编译
|
||||
bool compileFromSource(const char* vertexSource, const char* fragmentSource);
|
||||
|
||||
// 从文件加载并编译
|
||||
bool compileFromFile(const std::string& vertexPath, const std::string& fragmentPath);
|
||||
|
||||
// 使用/激活
|
||||
void bind() const;
|
||||
void unbind() const;
|
||||
|
||||
// Uniform 设置
|
||||
void setBool(const std::string& name, bool value);
|
||||
void setInt(const std::string& name, int value);
|
||||
void setFloat(const std::string& name, float value);
|
||||
void setVec2(const std::string& name, const glm::vec2& value);
|
||||
void setVec3(const std::string& name, const glm::vec3& value);
|
||||
void setVec4(const std::string& name, const glm::vec4& value);
|
||||
void setMat4(const std::string& name, const glm::mat4& value);
|
||||
|
||||
// 获取程序 ID
|
||||
GLuint getProgramID() const { return programID_; }
|
||||
|
||||
// 检查是否有效
|
||||
bool isValid() const { return programID_ != 0; }
|
||||
|
||||
private:
|
||||
GLuint programID_;
|
||||
std::unordered_map<std::string, GLint> uniformCache_;
|
||||
|
||||
GLuint compileShader(GLenum type, const char* source);
|
||||
GLint getUniformLocation(const std::string& name);
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -1,97 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <core/color.h>
|
||||
#include <core/types.h>
|
||||
#include <core/vec2.h>
|
||||
#include <graphics/opengl/gl_shader.h>
|
||||
#include <graphics/texture.h>
|
||||
#include <glm/mat4x4.hpp>
|
||||
#include <vector>
|
||||
#include <array>
|
||||
|
||||
#include <glad/glad.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// ============================================================================
|
||||
// OpenGL 精灵批渲染器 - 优化版本
|
||||
// ============================================================================
|
||||
class GLSpriteBatch {
|
||||
public:
|
||||
static constexpr size_t MAX_SPRITES = 10000;
|
||||
static constexpr size_t VERTICES_PER_SPRITE = 4;
|
||||
static constexpr size_t INDICES_PER_SPRITE = 6;
|
||||
static constexpr size_t MAX_VERTICES = MAX_SPRITES * VERTICES_PER_SPRITE;
|
||||
static constexpr size_t MAX_INDICES = MAX_SPRITES * INDICES_PER_SPRITE;
|
||||
|
||||
struct Vertex {
|
||||
glm::vec2 position;
|
||||
glm::vec2 texCoord;
|
||||
glm::vec4 color;
|
||||
};
|
||||
|
||||
struct SpriteData {
|
||||
glm::vec2 position;
|
||||
glm::vec2 size;
|
||||
glm::vec2 texCoordMin;
|
||||
glm::vec2 texCoordMax;
|
||||
glm::vec4 color;
|
||||
float rotation;
|
||||
glm::vec2 anchor;
|
||||
bool isSDF = false;
|
||||
};
|
||||
|
||||
GLSpriteBatch();
|
||||
~GLSpriteBatch();
|
||||
|
||||
bool init();
|
||||
void shutdown();
|
||||
|
||||
void begin(const glm::mat4 &viewProjection);
|
||||
void draw(const Texture &texture, const SpriteData &data);
|
||||
void end();
|
||||
|
||||
// 批量绘制接口 - 用于自动批处理
|
||||
void drawBatch(const Texture& texture, const std::vector<SpriteData>& sprites);
|
||||
|
||||
// 立即绘制(不缓存)
|
||||
void drawImmediate(const Texture& texture, const SpriteData& data);
|
||||
|
||||
// 统计
|
||||
uint32_t getDrawCallCount() const { return drawCallCount_; }
|
||||
uint32_t getSpriteCount() const { return spriteCount_; }
|
||||
uint32_t getBatchCount() const { return batchCount_; }
|
||||
|
||||
// 检查是否需要刷新
|
||||
bool needsFlush(const Texture& texture, bool isSDF) const;
|
||||
|
||||
private:
|
||||
GLuint vao_;
|
||||
GLuint vbo_;
|
||||
GLuint ibo_;
|
||||
GLShader shader_;
|
||||
|
||||
// 使用固定大小数组减少内存分配
|
||||
std::array<Vertex, MAX_VERTICES> vertexBuffer_;
|
||||
size_t vertexCount_;
|
||||
|
||||
const Texture *currentTexture_;
|
||||
bool currentIsSDF_;
|
||||
glm::mat4 viewProjection_;
|
||||
|
||||
// 缓存上一帧的 viewProjection,避免重复设置
|
||||
glm::mat4 cachedViewProjection_;
|
||||
bool viewProjectionDirty_ = true;
|
||||
|
||||
uint32_t drawCallCount_;
|
||||
uint32_t spriteCount_;
|
||||
uint32_t batchCount_;
|
||||
|
||||
void flush();
|
||||
void setupShader();
|
||||
|
||||
// 添加顶点到缓冲区
|
||||
void addVertices(const SpriteData& data);
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -1,78 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <graphics/alpha_mask.h>
|
||||
#include <graphics/texture.h>
|
||||
|
||||
#include <glad/glad.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// ============================================================================
|
||||
// OpenGL 纹理实现
|
||||
// ============================================================================
|
||||
class GLTexture : public Texture {
|
||||
public:
|
||||
GLTexture(int width, int height, const uint8_t *pixels, int channels);
|
||||
GLTexture(const std::string &filepath);
|
||||
~GLTexture();
|
||||
|
||||
// Texture 接口实现
|
||||
int width() const override { return width_; }
|
||||
int height() const override { return height_; }
|
||||
Size size() const override {
|
||||
return Size(static_cast<float>(width_), static_cast<float>(height_));
|
||||
}
|
||||
int getChannels() const override { return channels_; }
|
||||
PixelFormat getFormat() const override;
|
||||
void *getNativeHandle() const override {
|
||||
return reinterpret_cast<void *>(static_cast<uintptr_t>(textureID_));
|
||||
}
|
||||
bool isValid() const override { return textureID_ != 0; }
|
||||
void setFilter(bool linear) override;
|
||||
void setWrap(bool repeat) override;
|
||||
|
||||
// 从参数创建纹理的工厂方法
|
||||
static IntrusivePtr<Texture> create(int width, int height, PixelFormat format);
|
||||
|
||||
// 加载压缩纹理(KTX/DDS 格式)
|
||||
bool loadCompressed(const std::string &filepath);
|
||||
|
||||
// OpenGL 特定
|
||||
GLuint getTextureID() const { return textureID_; }
|
||||
void bind(unsigned int slot = 0) const;
|
||||
void unbind() const;
|
||||
|
||||
// 获取纹理数据大小(字节),用于 VRAM 跟踪
|
||||
size_t getDataSize() const { return dataSize_; }
|
||||
|
||||
// Alpha 遮罩
|
||||
bool hasAlphaMask() const {
|
||||
return alphaMask_ != nullptr && alphaMask_->isValid();
|
||||
}
|
||||
const AlphaMask *getAlphaMask() const { return alphaMask_.get(); }
|
||||
void generateAlphaMask(); // 从当前纹理数据生成遮罩
|
||||
|
||||
private:
|
||||
GLuint textureID_;
|
||||
int width_;
|
||||
int height_;
|
||||
int channels_;
|
||||
PixelFormat format_;
|
||||
size_t dataSize_;
|
||||
|
||||
// 原始像素数据(用于生成遮罩)
|
||||
std::vector<uint8_t> pixelData_;
|
||||
std::unique_ptr<AlphaMask> alphaMask_;
|
||||
|
||||
void createTexture(const uint8_t *pixels);
|
||||
|
||||
// KTX 文件加载
|
||||
bool loadKTX(const std::string &filepath);
|
||||
// DDS 文件加载
|
||||
bool loadDDS(const std::string &filepath);
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <core/ref_counted.h>
|
||||
#include <core/size.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// ============================================================================
|
||||
// 像素格式枚举
|
||||
// ============================================================================
|
||||
enum class PixelFormat {
|
||||
R8, // 单通道灰度
|
||||
RG8, // 双通道
|
||||
RGB8, // RGB 24位
|
||||
RGBA8, // RGBA 32位(默认)
|
||||
RGB16F, // RGB 半精度浮点
|
||||
RGBA16F, // RGBA 半精度浮点
|
||||
RGB32F, // RGB 全精度浮点
|
||||
RGBA32F, // RGBA 全精度浮点
|
||||
Depth16, // 16位深度
|
||||
Depth24, // 24位深度
|
||||
Depth32F, // 32位浮点深度
|
||||
Depth24Stencil8, // 24位深度 + 8位模板
|
||||
|
||||
// 压缩纹理格式
|
||||
ETC2_RGB8, // ETC2 RGB 压缩
|
||||
ETC2_RGBA8, // ETC2 RGBA 压缩
|
||||
ASTC_4x4, // ASTC 4x4 压缩
|
||||
ASTC_6x6, // ASTC 6x6 压缩
|
||||
ASTC_8x8 // ASTC 8x8 压缩
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// 纹理接口
|
||||
// ============================================================================
|
||||
class Texture : public RefCounted {
|
||||
public:
|
||||
virtual ~Texture() = default;
|
||||
|
||||
// 获取尺寸
|
||||
virtual int width() const = 0;
|
||||
virtual int height() const = 0;
|
||||
virtual Size size() const = 0;
|
||||
|
||||
// 获取通道数
|
||||
virtual int getChannels() const = 0;
|
||||
|
||||
// 获取像素格式
|
||||
virtual PixelFormat getFormat() const = 0;
|
||||
|
||||
// 获取原始句柄(用于底层渲染)
|
||||
virtual void *getNativeHandle() const = 0;
|
||||
|
||||
// 是否有效
|
||||
virtual bool isValid() const = 0;
|
||||
|
||||
// 设置过滤模式
|
||||
virtual void setFilter(bool linear) = 0;
|
||||
|
||||
// 设置环绕模式
|
||||
virtual void setWrap(bool repeat) = 0;
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -1,62 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <mutex>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// ============================================================================
|
||||
// VRAM 管理器 - 跟踪显存使用情况
|
||||
// ============================================================================
|
||||
class VRAMManager {
|
||||
public:
|
||||
static VRAMManager& getInstance();
|
||||
|
||||
// 纹理显存跟踪
|
||||
void allocTexture(size_t size);
|
||||
void freeTexture(size_t size);
|
||||
|
||||
// VBO/FBO 显存跟踪
|
||||
void allocBuffer(size_t size);
|
||||
void freeBuffer(size_t size);
|
||||
|
||||
// 查询显存使用情况
|
||||
size_t getUsedVRAM() const;
|
||||
size_t getTextureVRAM() const;
|
||||
size_t getBufferVRAM() const;
|
||||
size_t getAvailableVRAM() const;
|
||||
|
||||
// 显存预算管理
|
||||
void setVRAMBudget(size_t budget);
|
||||
size_t getVRAMBudget() const;
|
||||
bool isOverBudget() const;
|
||||
|
||||
// 统计信息
|
||||
void printStats() const;
|
||||
|
||||
// 重置计数器
|
||||
void reset();
|
||||
|
||||
private:
|
||||
VRAMManager();
|
||||
~VRAMManager() = default;
|
||||
VRAMManager(const VRAMManager&) = delete;
|
||||
VRAMManager& operator=(const VRAMManager&) = delete;
|
||||
|
||||
mutable std::mutex mutex_;
|
||||
|
||||
size_t textureVRAM_;
|
||||
size_t bufferVRAM_;
|
||||
size_t vramBudget_;
|
||||
|
||||
// 统计
|
||||
uint32_t textureAllocCount_;
|
||||
uint32_t textureFreeCount_;
|
||||
uint32_t bufferAllocCount_;
|
||||
uint32_t bufferFreeCount_;
|
||||
size_t peakTextureVRAM_;
|
||||
size_t peakBufferVRAM_;
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -1,95 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <core/color.h>
|
||||
#include <core/rect.h>
|
||||
#include <core/ref_counted.h>
|
||||
#include <core/size.h>
|
||||
#include <core/vec2.h>
|
||||
#include <glm/mat4x4.hpp>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// ============================================================================
|
||||
// 2D 正交相机
|
||||
// ============================================================================
|
||||
class Camera : public RefCounted {
|
||||
public:
|
||||
Camera();
|
||||
Camera(float left, float right, float bottom, float top);
|
||||
Camera(const Size &viewport);
|
||||
~Camera() = default;
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 位置和变换
|
||||
// ------------------------------------------------------------------------
|
||||
void setPosition(const Vec2 &position);
|
||||
void setPosition(float x, float y);
|
||||
Vec2 pos() const { return position_; }
|
||||
|
||||
void setRotation(float degrees);
|
||||
float rot() const { return rotation_; }
|
||||
|
||||
void setZoom(float zoom);
|
||||
float getZoom() const { return zoom_; }
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 视口设置
|
||||
// ------------------------------------------------------------------------
|
||||
void setViewport(float left, float right, float bottom, float top);
|
||||
void setViewport(const Rect &rect);
|
||||
Rect getViewport() const;
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 矩阵获取
|
||||
// ------------------------------------------------------------------------
|
||||
glm::mat4 getViewMatrix() const;
|
||||
glm::mat4 getProjectionMatrix() const;
|
||||
glm::mat4 getViewProjectionMatrix() const;
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 坐标转换
|
||||
// ------------------------------------------------------------------------
|
||||
Vec2 screenToWorld(const Vec2 &screenPos) const;
|
||||
Vec2 worldToScreen(const Vec2 &worldPos) const;
|
||||
Vec2 screenToWorld(float x, float y) const;
|
||||
Vec2 worldToScreen(float x, float y) const;
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 移动相机
|
||||
// ------------------------------------------------------------------------
|
||||
void move(const Vec2 &offset);
|
||||
void move(float x, float y);
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 边界限制
|
||||
// ------------------------------------------------------------------------
|
||||
void setBounds(const Rect &bounds);
|
||||
void clearBounds();
|
||||
void clampToBounds();
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 快捷方法:看向某点
|
||||
// ------------------------------------------------------------------------
|
||||
void lookAt(const Vec2 &target);
|
||||
|
||||
private:
|
||||
Vec2 position_ = Vec2::Zero();
|
||||
float rotation_ = 0.0f;
|
||||
float zoom_ = 1.0f;
|
||||
|
||||
float left_ = -1.0f;
|
||||
float right_ = 1.0f;
|
||||
float bottom_ = -1.0f;
|
||||
float top_ = 1.0f;
|
||||
|
||||
Rect bounds_;
|
||||
bool hasBounds_ = false;
|
||||
|
||||
mutable glm::mat4 viewMatrix_;
|
||||
mutable glm::mat4 projMatrix_;
|
||||
mutable glm::mat4 vpMatrix_;
|
||||
mutable bool viewDirty_ = true;
|
||||
mutable bool projDirty_ = true;
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -1,226 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <core/color.h>
|
||||
#include <core/rect.h>
|
||||
#include <core/types.h>
|
||||
#include <core/vec2.h>
|
||||
#include <cstdint>
|
||||
#include <glm/mat4x4.hpp>
|
||||
#include <graphics/texture.h>
|
||||
#include <string>
|
||||
#include <variant>
|
||||
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// 前向声明
|
||||
class Texture;
|
||||
class FontAtlas;
|
||||
|
||||
/**
|
||||
* @brief 渲染命令类型枚举
|
||||
*/
|
||||
enum class RenderCommandType : uint8_t {
|
||||
None = 0,
|
||||
Sprite, // 精灵绘制
|
||||
Line, // 线条绘制
|
||||
Rect, // 矩形绘制
|
||||
FilledRect, // 填充矩形
|
||||
Circle, // 圆形绘制
|
||||
FilledCircle, // 填充圆形
|
||||
Triangle, // 三角形绘制
|
||||
FilledTriangle, // 填充三角形
|
||||
Polygon, // 多边形绘制
|
||||
FilledPolygon, // 填充多边形
|
||||
Text, // 文本绘制
|
||||
Custom // 自定义绘制
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 精灵渲染命令数据
|
||||
*/
|
||||
struct SpriteCommandData {
|
||||
const Texture *texture;
|
||||
Rect destRect;
|
||||
Rect srcRect;
|
||||
Color tint;
|
||||
float rotation;
|
||||
Vec2 anchor;
|
||||
uint32_t sortKey; // 用于自动排序的键值
|
||||
|
||||
SpriteCommandData()
|
||||
: texture(nullptr), destRect(), srcRect(), tint(Colors::White),
|
||||
rotation(0.0f), anchor(0.0f, 0.0f), sortKey(0) {}
|
||||
SpriteCommandData(const Texture *tex, const Rect &dest, const Rect &src,
|
||||
const Color &t, float rot, const Vec2 &anc, uint32_t key)
|
||||
: texture(tex), destRect(dest), srcRect(src), tint(t), rotation(rot),
|
||||
anchor(anc), sortKey(key) {}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 线条渲染命令数据
|
||||
*/
|
||||
struct LineCommandData {
|
||||
Vec2 start;
|
||||
Vec2 end;
|
||||
Color color;
|
||||
float width;
|
||||
|
||||
LineCommandData() : start(), end(), color(Colors::White), width(1.0f) {}
|
||||
LineCommandData(const Vec2 &s, const Vec2 &e, const Color &c, float w)
|
||||
: start(s), end(e), color(c), width(w) {}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 矩形渲染命令数据
|
||||
*/
|
||||
struct RectCommandData {
|
||||
Rect rect;
|
||||
Color color;
|
||||
float width;
|
||||
bool filled;
|
||||
|
||||
RectCommandData()
|
||||
: rect(), color(Colors::White), width(1.0f), filled(false) {}
|
||||
RectCommandData(const Rect &r, const Color &c, float w, bool f)
|
||||
: rect(r), color(c), width(w), filled(f) {}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 圆形渲染命令数据
|
||||
*/
|
||||
struct CircleCommandData {
|
||||
Vec2 center;
|
||||
float radius;
|
||||
Color color;
|
||||
int segments;
|
||||
float width;
|
||||
bool filled;
|
||||
|
||||
CircleCommandData()
|
||||
: center(), radius(0.0f), color(Colors::White), segments(32), width(1.0f),
|
||||
filled(false) {}
|
||||
CircleCommandData(const Vec2 &c, float r, const Color &col, int seg, float w,
|
||||
bool f)
|
||||
: center(c), radius(r), color(col), segments(seg), width(w), filled(f) {}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 三角形渲染命令数据
|
||||
*/
|
||||
struct TriangleCommandData {
|
||||
Vec2 p1, p2, p3;
|
||||
Color color;
|
||||
float width;
|
||||
bool filled;
|
||||
|
||||
TriangleCommandData()
|
||||
: p1(), p2(), p3(), color(Colors::White), width(1.0f), filled(false) {}
|
||||
TriangleCommandData(const Vec2 &a, const Vec2 &b, const Vec2 &c,
|
||||
const Color &col, float w, bool f)
|
||||
: p1(a), p2(b), p3(c), color(col), width(w), filled(f) {}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 多边形渲染命令数据
|
||||
*/
|
||||
struct PolygonCommandData {
|
||||
std::vector<Vec2> points;
|
||||
Color color;
|
||||
float width;
|
||||
bool filled;
|
||||
|
||||
PolygonCommandData() : color(Colors::White), width(1.0f), filled(false) {}
|
||||
PolygonCommandData(std::vector<Vec2> pts, const Color &col, float w, bool f)
|
||||
: points(std::move(pts)), color(col), width(w), filled(f) {}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 文本渲染命令数据
|
||||
*/
|
||||
struct TextCommandData {
|
||||
const FontAtlas *font;
|
||||
std::string text;
|
||||
Vec2 position;
|
||||
Color color;
|
||||
|
||||
TextCommandData() : font(nullptr), text(), position(), color(Colors::White) {}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 统一渲染命令结构
|
||||
* 使用 variant 存储不同类型的命令数据,减少内存分配
|
||||
*/
|
||||
struct RenderCommand {
|
||||
RenderCommandType type;
|
||||
uint32_t layer; // 渲染层级,用于排序
|
||||
uint32_t order; // 提交顺序,保证同层级内稳定排序
|
||||
glm::mat4 transform; // 变换矩阵
|
||||
|
||||
// 使用 variant 存储具体数据
|
||||
std::variant<SpriteCommandData, LineCommandData, RectCommandData,
|
||||
CircleCommandData, TriangleCommandData, PolygonCommandData,
|
||||
TextCommandData>
|
||||
data;
|
||||
|
||||
RenderCommand()
|
||||
: type(RenderCommandType::None), layer(0), order(0), transform(1.0f) {}
|
||||
|
||||
// 便捷构造函数
|
||||
static RenderCommand makeSprite(const Texture *tex, const Rect &dest,
|
||||
const Rect &src, const Color &tint,
|
||||
float rot = 0.0f,
|
||||
const Vec2 &anc = Vec2(0, 0),
|
||||
uint32_t lyr = 0);
|
||||
static RenderCommand makeLine(const Vec2 &s, const Vec2 &e, const Color &c,
|
||||
float w = 1.0f, uint32_t lyr = 0);
|
||||
static RenderCommand makeRect(const Rect &r, const Color &c, float w = 1.0f,
|
||||
bool fill = false, uint32_t lyr = 0);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 渲染命令缓冲区
|
||||
* 用于收集和批量处理渲染命令
|
||||
*/
|
||||
class RenderCommandBuffer {
|
||||
public:
|
||||
static constexpr size_t INITIAL_CAPACITY = 1024;
|
||||
static constexpr size_t MAX_CAPACITY = 65536;
|
||||
|
||||
RenderCommandBuffer();
|
||||
~RenderCommandBuffer();
|
||||
|
||||
// 添加渲染命令
|
||||
void addCommand(const RenderCommand &cmd);
|
||||
void addCommand(RenderCommand &&cmd);
|
||||
|
||||
// 批量添加(预留空间后使用)
|
||||
RenderCommand &emplaceCommand();
|
||||
|
||||
// 排序命令(按纹理、层级等)
|
||||
void sortCommands();
|
||||
|
||||
// 清空缓冲区
|
||||
void clear();
|
||||
|
||||
// 获取命令列表
|
||||
const std::vector<RenderCommand> &getCommands() const { return commands_; }
|
||||
std::vector<RenderCommand> &getCommands() { return commands_; }
|
||||
|
||||
// 统计
|
||||
size_t size() const { return commands_.size(); }
|
||||
bool empty() const { return commands_.empty(); }
|
||||
size_t capacity() const { return commands_.capacity(); }
|
||||
|
||||
// 预分配空间
|
||||
void reserve(size_t capacity);
|
||||
|
||||
private:
|
||||
std::vector<RenderCommand> commands_;
|
||||
uint32_t nextOrder_;
|
||||
|
||||
// 排序比较函数
|
||||
static bool compareCommands(const RenderCommand &a, const RenderCommand &b);
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -1,333 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <core/color.h>
|
||||
#include <core/ref_counted.h>
|
||||
#include <graphics/opengl/gl_texture.h>
|
||||
#include <graphics/texture.h>
|
||||
#include <mutex>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// ============================================================================
|
||||
// 渲染目标配置
|
||||
// ============================================================================
|
||||
struct RenderTargetConfig {
|
||||
int width = 800; // 宽度
|
||||
int height = 600; // 高度
|
||||
PixelFormat colorFormat = PixelFormat::RGBA8; // 颜色格式
|
||||
bool hasDepth = true; // 是否包含深度缓冲
|
||||
bool hasDepthBuffer = true; // 兼容旧API的别名 (同hasDepth)
|
||||
bool hasStencil = false; // 是否包含模板缓冲
|
||||
int samples = 1; // 多重采样数 (1 = 无MSAA)
|
||||
bool autoResize = true; // 是否自动调整大小
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// 渲染目标 - 基于FBO的离屏渲染
|
||||
// ============================================================================
|
||||
class RenderTarget : public RefCounted {
|
||||
public:
|
||||
RenderTarget();
|
||||
~RenderTarget();
|
||||
|
||||
// 禁止拷贝
|
||||
RenderTarget(const RenderTarget &) = delete;
|
||||
RenderTarget &operator=(const RenderTarget &) = delete;
|
||||
|
||||
// 允许移动
|
||||
RenderTarget(RenderTarget &&other) noexcept;
|
||||
RenderTarget &operator=(RenderTarget &&other) noexcept;
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 创建和销毁
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 创建渲染目标
|
||||
*/
|
||||
bool create(const RenderTargetConfig &config);
|
||||
|
||||
/**
|
||||
* @brief 初始化渲染目标(create的别名,兼容旧API)
|
||||
*/
|
||||
bool init(const RenderTargetConfig &config) { return create(config); }
|
||||
|
||||
/**
|
||||
* @brief 从现有纹理创建渲染目标
|
||||
*/
|
||||
bool createFromTexture(IntrusivePtr<Texture> texture, bool hasDepth = false);
|
||||
|
||||
/**
|
||||
* @brief 销毁渲染目标
|
||||
*/
|
||||
void destroy();
|
||||
|
||||
/**
|
||||
* @brief 关闭渲染目标(destroy的别名,兼容旧API)
|
||||
*/
|
||||
void shutdown() { destroy(); }
|
||||
|
||||
/**
|
||||
* @brief 检查是否有效
|
||||
*/
|
||||
bool isValid() const { return fbo_ != 0; }
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 尺寸和格式
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
int width() const { return width_; }
|
||||
int height() const { return height_; }
|
||||
Vec2 size() const {
|
||||
return Vec2(static_cast<float>(width_), static_cast<float>(height_));
|
||||
}
|
||||
PixelFormat getColorFormat() const { return colorFormat_; }
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 绑定和解绑
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 绑定为当前渲染目标
|
||||
*/
|
||||
void bind();
|
||||
|
||||
/**
|
||||
* @brief 解绑(恢复默认渲染目标)
|
||||
*/
|
||||
void unbind();
|
||||
|
||||
/**
|
||||
* @brief 清除渲染目标
|
||||
*/
|
||||
void clear(const Color &color = Colors::Transparent);
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 纹理访问
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 获取颜色纹理
|
||||
*/
|
||||
IntrusivePtr<Texture> getColorTexture() const { return colorTexture_; }
|
||||
|
||||
/**
|
||||
* @brief 获取深度纹理(如果有)
|
||||
*/
|
||||
IntrusivePtr<Texture> getDepthTexture() const { return depthTexture_; }
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 视口和裁剪
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 设置视口(相对于渲染目标)
|
||||
*/
|
||||
void setViewport(int x, int y, int width, int height);
|
||||
|
||||
/**
|
||||
* @brief 获取完整视口
|
||||
*/
|
||||
void getFullViewport(int &x, int &y, int &width, int &height) const;
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 工具方法
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 调整大小(会销毁并重新创建)
|
||||
*/
|
||||
bool resize(int width, int height);
|
||||
|
||||
/**
|
||||
* @brief 复制到另一个渲染目标
|
||||
*/
|
||||
void copyTo(RenderTarget &target);
|
||||
|
||||
/**
|
||||
* @brief 复制到另一个渲染目标(blitTo的别名,兼容旧API)
|
||||
* @param target 目标渲染目标
|
||||
* @param color 是否复制颜色缓冲
|
||||
* @param depth 是否复制深度缓冲
|
||||
*/
|
||||
void blitTo(RenderTarget &target, bool color = true, bool depth = false);
|
||||
|
||||
/**
|
||||
* @brief 复制到屏幕
|
||||
*/
|
||||
void copyToScreen(int screenWidth, int screenHeight);
|
||||
|
||||
/**
|
||||
* @brief 保存为图像文件
|
||||
*/
|
||||
bool saveToFile(const std::string &filepath);
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 静态方法
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 创建渲染目标的静态工厂方法
|
||||
*/
|
||||
static IntrusivePtr<RenderTarget> createFromConfig(const RenderTargetConfig &config);
|
||||
|
||||
/**
|
||||
* @brief 获取当前绑定的渲染目标ID
|
||||
*/
|
||||
static GLuint getCurrentFBO();
|
||||
|
||||
/**
|
||||
* @brief 绑定默认渲染目标(屏幕)
|
||||
*/
|
||||
static void bindDefault();
|
||||
|
||||
/**
|
||||
* @brief 获取FBO ID(供内部使用)
|
||||
*/
|
||||
GLuint getFBO() const { return fbo_; }
|
||||
|
||||
protected:
|
||||
GLuint fbo_ = 0; // 帧缓冲对象
|
||||
GLuint rbo_ = 0; // 渲染缓冲对象(深度/模板)
|
||||
|
||||
IntrusivePtr<Texture> colorTexture_; // 颜色纹理
|
||||
IntrusivePtr<Texture> depthTexture_; // 深度纹理(可选)
|
||||
|
||||
int width_ = 0;
|
||||
int height_ = 0;
|
||||
PixelFormat colorFormat_ = PixelFormat::RGBA8;
|
||||
bool hasDepth_ = false;
|
||||
bool hasStencil_ = false;
|
||||
int samples_ = 1;
|
||||
|
||||
bool createFBO();
|
||||
void deleteFBO();
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// 多重采样渲染目标(用于MSAA)
|
||||
// ============================================================================
|
||||
class MultisampleRenderTarget : public RenderTarget {
|
||||
public:
|
||||
/**
|
||||
* @brief 创建多重采样渲染目标
|
||||
*/
|
||||
bool create(int width, int height, int samples = 4);
|
||||
|
||||
/**
|
||||
* @brief 解析到普通渲染目标(用于显示)
|
||||
*/
|
||||
void resolveTo(RenderTarget &target);
|
||||
|
||||
/**
|
||||
* @brief 销毁渲染目标
|
||||
*/
|
||||
void destroy();
|
||||
|
||||
private:
|
||||
GLuint colorRBO_ = 0; // 多重采样颜色渲染缓冲
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// 渲染目标栈(用于嵌套渲染)
|
||||
// ============================================================================
|
||||
class RenderTargetStack {
|
||||
public:
|
||||
static RenderTargetStack &getInstance();
|
||||
|
||||
/**
|
||||
* @brief 压入渲染目标
|
||||
*/
|
||||
void push(RenderTarget *target);
|
||||
|
||||
/**
|
||||
* @brief 弹出渲染目标
|
||||
*/
|
||||
void pop();
|
||||
|
||||
/**
|
||||
* @brief 获取当前渲染目标
|
||||
*/
|
||||
RenderTarget *getCurrent() const;
|
||||
|
||||
/**
|
||||
* @brief 获取栈大小
|
||||
*/
|
||||
size_t size() const;
|
||||
|
||||
/**
|
||||
* @brief 清空栈
|
||||
*/
|
||||
void clear();
|
||||
|
||||
private:
|
||||
RenderTargetStack() = default;
|
||||
~RenderTargetStack() = default;
|
||||
|
||||
std::vector<RenderTarget *> stack_;
|
||||
mutable std::mutex mutex_;
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// 渲染目标管理器 - 全局渲染目标管理
|
||||
// ============================================================================
|
||||
class RenderTargetManager {
|
||||
public:
|
||||
/**
|
||||
* @brief 获取单例实例
|
||||
*/
|
||||
static RenderTargetManager &getInstance();
|
||||
|
||||
/**
|
||||
* @brief 初始化渲染目标管理器
|
||||
* @param width 默认宽度
|
||||
* @param height 默认高度
|
||||
*/
|
||||
bool init(int width, int height);
|
||||
|
||||
/**
|
||||
* @brief 关闭渲染目标管理器
|
||||
*/
|
||||
void shutdown();
|
||||
|
||||
/**
|
||||
* @brief 创建新的渲染目标
|
||||
*/
|
||||
IntrusivePtr<RenderTarget> createRenderTarget(const RenderTargetConfig &config);
|
||||
|
||||
/**
|
||||
* @brief 获取默认渲染目标
|
||||
*/
|
||||
RenderTarget *getDefaultRenderTarget() const {
|
||||
return defaultRenderTarget_.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 调整所有受管渲染目标的大小
|
||||
*/
|
||||
void resize(int width, int height);
|
||||
|
||||
/**
|
||||
* @brief 检查是否已初始化
|
||||
*/
|
||||
bool isInitialized() const { return initialized_; }
|
||||
|
||||
private:
|
||||
RenderTargetManager() = default;
|
||||
~RenderTargetManager() = default;
|
||||
RenderTargetManager(const RenderTargetManager &) = delete;
|
||||
RenderTargetManager &operator=(const RenderTargetManager &) = delete;
|
||||
|
||||
IntrusivePtr<RenderTarget> defaultRenderTarget_;
|
||||
std::vector<IntrusivePtr<RenderTarget>> renderTargets_;
|
||||
bool initialized_ = false;
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// 便捷宏
|
||||
// ============================================================================
|
||||
#define E2D_RENDER_TARGET_STACK() ::extra2d::RenderTargetStack::getInstance()
|
||||
#define E2D_RENDER_TARGET_MANAGER() \
|
||||
::extra2d::RenderTargetManager::getInstance()
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -1,140 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <core/color.h>
|
||||
#include <core/rect.h>
|
||||
#include <core/transform.h>
|
||||
#include <core/types.h>
|
||||
#include <core/vec2.h>
|
||||
#include <glm/mat4x4.hpp>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// 前向声明
|
||||
class Window;
|
||||
class Texture;
|
||||
class FontAtlas;
|
||||
class Shader;
|
||||
|
||||
// ============================================================================
|
||||
// 渲染后端类型
|
||||
// ============================================================================
|
||||
enum class BackendType {
|
||||
OpenGL,
|
||||
// Vulkan,
|
||||
// Metal,
|
||||
// D3D11,
|
||||
// D3D12
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// 混合模式
|
||||
// ============================================================================
|
||||
enum class BlendMode {
|
||||
None, // 不混合
|
||||
Alpha, // 标准 Alpha 混合
|
||||
Additive, // 加法混合
|
||||
Multiply // 乘法混合
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// 渲染后端抽象接口
|
||||
// ============================================================================
|
||||
class Renderer {
|
||||
public:
|
||||
virtual ~Renderer() = default;
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 生命周期
|
||||
// ------------------------------------------------------------------------
|
||||
virtual bool init(Window *window) = 0;
|
||||
virtual void shutdown() = 0;
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 帧管理
|
||||
// ------------------------------------------------------------------------
|
||||
virtual void beginFrame(const Color &clearColor) = 0;
|
||||
virtual void endFrame() = 0;
|
||||
virtual void setViewport(int x, int y, int width, int height) = 0;
|
||||
virtual void setVSync(bool enabled) = 0;
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 状态设置
|
||||
// ------------------------------------------------------------------------
|
||||
virtual void setBlendMode(BlendMode mode) = 0;
|
||||
virtual void setViewProjection(const glm::mat4 &matrix) = 0;
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 变换矩阵栈
|
||||
// ------------------------------------------------------------------------
|
||||
virtual void pushTransform(const glm::mat4 &transform) = 0;
|
||||
virtual void popTransform() = 0;
|
||||
virtual glm::mat4 getCurrentTransform() const = 0;
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 纹理
|
||||
// ------------------------------------------------------------------------
|
||||
virtual IntrusivePtr<Texture> createTexture(int width, int height,
|
||||
const uint8_t *pixels, int channels) = 0;
|
||||
virtual IntrusivePtr<Texture> loadTexture(const std::string &filepath) = 0;
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 精灵批渲染
|
||||
// ------------------------------------------------------------------------
|
||||
virtual void beginSpriteBatch() = 0;
|
||||
virtual void drawSprite(const Texture &texture, const Rect &destRect,
|
||||
const Rect &srcRect, const Color &tint,
|
||||
float rotation, const Vec2 &anchor) = 0;
|
||||
virtual void drawSprite(const Texture &texture, const Vec2 &position,
|
||||
const Color &tint) = 0;
|
||||
virtual void endSpriteBatch() = 0;
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 形状渲染
|
||||
// ------------------------------------------------------------------------
|
||||
virtual void drawLine(const Vec2 &start, const Vec2 &end, const Color &color,
|
||||
float width = 1.0f) = 0;
|
||||
virtual void drawRect(const Rect &rect, const Color &color,
|
||||
float width = 1.0f) = 0;
|
||||
virtual void fillRect(const Rect &rect, const Color &color) = 0;
|
||||
virtual void drawCircle(const Vec2 ¢er, float radius, const Color &color,
|
||||
int segments = 32, float width = 1.0f) = 0;
|
||||
virtual void fillCircle(const Vec2 ¢er, float radius, const Color &color,
|
||||
int segments = 32) = 0;
|
||||
virtual void drawTriangle(const Vec2 &p1, const Vec2 &p2, const Vec2 &p3,
|
||||
const Color &color, float width = 1.0f) = 0;
|
||||
virtual void fillTriangle(const Vec2 &p1, const Vec2 &p2, const Vec2 &p3,
|
||||
const Color &color) = 0;
|
||||
virtual void drawPolygon(const std::vector<Vec2> &points, const Color &color,
|
||||
float width = 1.0f) = 0;
|
||||
virtual void fillPolygon(const std::vector<Vec2> &points,
|
||||
const Color &color) = 0;
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 文字渲染
|
||||
// ------------------------------------------------------------------------
|
||||
virtual IntrusivePtr<FontAtlas> createFontAtlas(const std::string &filepath,
|
||||
int fontSize, bool useSDF = false) = 0;
|
||||
virtual void drawText(const FontAtlas &font, const std::string &text,
|
||||
const Vec2 &position, const Color &color) = 0;
|
||||
virtual void drawText(const FontAtlas &font, const std::string &text, float x,
|
||||
float y, const Color &color) = 0;
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 统计信息
|
||||
// ------------------------------------------------------------------------
|
||||
struct Stats {
|
||||
uint32_t drawCalls = 0;
|
||||
uint32_t triangleCount = 0;
|
||||
uint32_t textureBinds = 0;
|
||||
uint32_t shaderBinds = 0;
|
||||
};
|
||||
virtual Stats getStats() const = 0;
|
||||
virtual void resetStats() = 0;
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 工厂方法
|
||||
// ------------------------------------------------------------------------
|
||||
static UniquePtr<Renderer> create(BackendType type);
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -1,362 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <audio/sound.h>
|
||||
#include <core/types.h>
|
||||
#include <functional>
|
||||
#include <future>
|
||||
#include <graphics/alpha_mask.h>
|
||||
#include <graphics/font.h>
|
||||
#include <graphics/texture.h>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// ============================================================================
|
||||
// 资源管理器 - 统一管理纹理、字体、音效等资源
|
||||
// 支持异步加载和纹理压缩
|
||||
// ============================================================================
|
||||
|
||||
// 纹理格式枚举
|
||||
enum class TextureFormat {
|
||||
Auto = 0, // 自动选择最佳格式
|
||||
RGBA8, // 32位 RGBA
|
||||
RGB8, // 24位 RGB
|
||||
DXT1, // BC1/DXT1 压缩(1 bit alpha)
|
||||
DXT5, // BC3/DXT5 压缩(完整 alpha)
|
||||
ETC2, // ETC2 压缩(移动平台)
|
||||
ASTC4x4, // ASTC 4x4 压缩(高质量)
|
||||
ASTC8x8, // ASTC 8x8 压缩(高压缩率)
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// 纹理LRU缓存项
|
||||
// ============================================================================
|
||||
struct TextureCacheEntry {
|
||||
IntrusivePtr<Texture> texture;
|
||||
size_t size = 0; // 纹理大小(字节)
|
||||
float lastAccessTime = 0.0f; // 最后访问时间
|
||||
uint32_t accessCount = 0; // 访问次数
|
||||
};
|
||||
|
||||
// 异步加载回调类型
|
||||
using TextureLoadCallback = std::function<void(IntrusivePtr<Texture>)>;
|
||||
|
||||
class ResourceManager {
|
||||
public:
|
||||
// ------------------------------------------------------------------------
|
||||
// 单例访问
|
||||
// ------------------------------------------------------------------------
|
||||
static ResourceManager &getInstance();
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 纹理资源 - 同步加载
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/// 加载纹理(带缓存)
|
||||
IntrusivePtr<Texture> loadTexture(const std::string &filepath);
|
||||
|
||||
/// 加载纹理(指定是否异步)
|
||||
IntrusivePtr<Texture> loadTexture(const std::string &filepath, bool async);
|
||||
|
||||
/// 加载纹理(完整参数:异步 + 压缩格式)
|
||||
IntrusivePtr<Texture> loadTexture(const std::string &filepath, bool async,
|
||||
TextureFormat format);
|
||||
|
||||
/// 异步加载纹理(带回调)
|
||||
void loadTextureAsync(const std::string &filepath,
|
||||
TextureLoadCallback callback);
|
||||
|
||||
/// 异步加载纹理(指定格式 + 回调)
|
||||
void loadTextureAsync(const std::string &filepath, TextureFormat format,
|
||||
TextureLoadCallback callback);
|
||||
|
||||
/// 加载纹理并生成Alpha遮罩(用于不规则形状图片)
|
||||
IntrusivePtr<Texture> loadTextureWithAlphaMask(const std::string &filepath);
|
||||
|
||||
/// 通过key获取已缓存的纹理
|
||||
IntrusivePtr<Texture> getTexture(const std::string &key) const;
|
||||
|
||||
/// 检查纹理是否已缓存
|
||||
bool hasTexture(const std::string &key) const;
|
||||
|
||||
/// 卸载指定纹理
|
||||
void unloadTexture(const std::string &key);
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Alpha遮罩资源
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/// 获取纹理的Alpha遮罩(如果已生成)
|
||||
const AlphaMask *getAlphaMask(const std::string &textureKey) const;
|
||||
|
||||
/// 为已加载的纹理生成Alpha遮罩
|
||||
bool generateAlphaMask(const std::string &textureKey);
|
||||
|
||||
/// 检查纹理是否有Alpha遮罩
|
||||
bool hasAlphaMask(const std::string &textureKey) const;
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 字体图集资源
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/// 加载字体图集(带缓存)
|
||||
IntrusivePtr<FontAtlas> loadFont(const std::string &filepath, int fontSize,
|
||||
bool useSDF = false);
|
||||
|
||||
/// 通过key获取已缓存的字体图集
|
||||
IntrusivePtr<FontAtlas> getFont(const std::string &key) const;
|
||||
|
||||
/// 检查字体是否已缓存
|
||||
bool hasFont(const std::string &key) const;
|
||||
|
||||
/// 卸载指定字体
|
||||
void unloadFont(const std::string &key);
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 音效资源
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/// 加载音效(带缓存)
|
||||
IntrusivePtr<Sound> loadSound(const std::string &filepath);
|
||||
IntrusivePtr<Sound> loadSound(const std::string &name, const std::string &filepath);
|
||||
|
||||
/// 通过key获取已缓存的音效
|
||||
IntrusivePtr<Sound> getSound(const std::string &key) const;
|
||||
|
||||
/// 检查音效是否已缓存
|
||||
bool hasSound(const std::string &key) const;
|
||||
|
||||
/// 卸载指定音效
|
||||
void unloadSound(const std::string &key);
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 文本文件资源
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/// 加载文本文件(带缓存)
|
||||
/// @param filepath 文件路径,支持 romfs:/ 前缀
|
||||
/// @return 文件内容字符串,加载失败返回空字符串
|
||||
std::string loadTextFile(const std::string &filepath);
|
||||
|
||||
/// 加载文本文件(指定编码)
|
||||
/// @param filepath 文件路径
|
||||
/// @param encoding 文件编码(默认 UTF-8)
|
||||
/// @return 文件内容字符串
|
||||
std::string loadTextFile(const std::string &filepath,
|
||||
const std::string &encoding);
|
||||
|
||||
/// 通过key获取已缓存的文本内容
|
||||
std::string getTextFile(const std::string &key) const;
|
||||
|
||||
/// 检查文本文件是否已缓存
|
||||
bool hasTextFile(const std::string &key) const;
|
||||
|
||||
/// 卸载指定文本文件
|
||||
void unloadTextFile(const std::string &key);
|
||||
|
||||
/// 清理所有文本文件缓存
|
||||
void clearTextFileCache();
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// JSON 文件资源
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/// 加载并解析 JSON 文件
|
||||
/// @param filepath 文件路径,支持 romfs:/ 前缀
|
||||
/// @return JSON 字符串内容,加载或解析失败返回空字符串
|
||||
/// @note 返回的是原始 JSON 字符串,需要自行解析
|
||||
std::string loadJsonFile(const std::string &filepath);
|
||||
|
||||
/// 通过key获取已缓存的 JSON 内容
|
||||
std::string getJsonFile(const std::string &key) const;
|
||||
|
||||
/// 检查 JSON 文件是否已缓存
|
||||
bool hasJsonFile(const std::string &key) const;
|
||||
|
||||
/// 卸载指定 JSON 文件
|
||||
void unloadJsonFile(const std::string &key);
|
||||
|
||||
/// 清理所有 JSON 文件缓存
|
||||
void clearJsonFileCache();
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 缓存清理
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/// 清理所有失效的弱引用(自动清理已释放的资源)
|
||||
void purgeUnused();
|
||||
|
||||
/// 清理指定类型的所有缓存
|
||||
void clearTextureCache();
|
||||
void clearFontCache();
|
||||
void clearSoundCache();
|
||||
|
||||
/// 清理所有资源缓存
|
||||
void clearAllCaches();
|
||||
|
||||
/// 获取各类资源的缓存数量
|
||||
size_t getTextureCacheSize() const;
|
||||
size_t getFontCacheSize() const;
|
||||
size_t getSoundCacheSize() const;
|
||||
size_t getTextFileCacheSize() const;
|
||||
size_t getJsonFileCacheSize() const;
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// LRU 缓存管理
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/// 设置纹理缓存参数
|
||||
void setTextureCache(size_t maxCacheSize, size_t maxTextureCount,
|
||||
float unloadInterval);
|
||||
|
||||
/// 获取当前缓存的总大小(字节)
|
||||
size_t getTextureCacheMemoryUsage() const;
|
||||
|
||||
/// 获取缓存命中率
|
||||
float getTextureCacheHitRate() const;
|
||||
|
||||
/// 打印缓存统计信息
|
||||
void printTextureCacheStats() const;
|
||||
|
||||
/// 更新缓存(在主循环中调用,用于自动清理)
|
||||
void update(float dt);
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 异步加载控制
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/// 初始化异步加载系统(可选,自动在首次异步加载时初始化)
|
||||
void initAsyncLoader();
|
||||
|
||||
/// 关闭异步加载系统
|
||||
void shutdownAsyncLoader();
|
||||
|
||||
/// 等待所有异步加载完成
|
||||
void waitForAsyncLoads();
|
||||
|
||||
/// 检查是否有正在进行的异步加载
|
||||
bool hasPendingAsyncLoads() const;
|
||||
|
||||
ResourceManager();
|
||||
~ResourceManager();
|
||||
ResourceManager(const ResourceManager &) = delete;
|
||||
ResourceManager &operator=(const ResourceManager &) = delete;
|
||||
|
||||
private:
|
||||
// 生成字体缓存key
|
||||
std::string makeFontKey(const std::string &filepath, int fontSize,
|
||||
bool useSDF) const;
|
||||
|
||||
// 内部加载实现
|
||||
IntrusivePtr<Texture> loadTextureInternal(const std::string &filepath,
|
||||
TextureFormat format);
|
||||
|
||||
// 选择最佳纹理格式
|
||||
TextureFormat selectBestFormat(TextureFormat requested) const;
|
||||
|
||||
// 压缩纹理数据
|
||||
std::vector<uint8_t> compressTexture(const uint8_t *data, int width,
|
||||
int height, int channels,
|
||||
TextureFormat format);
|
||||
|
||||
// 互斥锁保护缓存
|
||||
mutable std::mutex textureMutex_;
|
||||
mutable std::mutex fontMutex_;
|
||||
mutable std::mutex soundMutex_;
|
||||
mutable std::mutex textFileMutex_;
|
||||
mutable std::mutex jsonFileMutex_;
|
||||
|
||||
// 资源缓存 - 使用 IntrusivePtr 强引用
|
||||
std::unordered_map<std::string, IntrusivePtr<FontAtlas>> fontCache_;
|
||||
std::unordered_map<std::string, IntrusivePtr<Sound>> soundCache_;
|
||||
|
||||
// 文本文件缓存 - 使用强引用(字符串值类型)
|
||||
std::unordered_map<std::string, std::string> textFileCache_;
|
||||
std::unordered_map<std::string, std::string> jsonFileCache_;
|
||||
|
||||
// ============================================================================
|
||||
// 纹理LRU缓存
|
||||
// ============================================================================
|
||||
|
||||
// LRU链表节点
|
||||
struct LRUNode {
|
||||
std::string key;
|
||||
uint32_t prev = 0; // 数组索引,0表示无效
|
||||
uint32_t next = 0; // 数组索引,0表示无效
|
||||
bool valid = false;
|
||||
};
|
||||
|
||||
// 纹理缓存配置
|
||||
size_t maxCacheSize_ = 64 * 1024 * 1024; // 最大缓存大小 (64MB)
|
||||
size_t maxTextureCount_ = 256; // 最大纹理数量
|
||||
float unloadInterval_ = 30.0f; // 自动清理间隔 (秒)
|
||||
|
||||
// 纹理缓存 - 使用强指针保持引用
|
||||
std::unordered_map<std::string, TextureCacheEntry> textureCache_;
|
||||
|
||||
// 侵入式LRU链表 - 使用数组索引代替指针,提高缓存局部性
|
||||
std::vector<LRUNode> lruNodes_;
|
||||
uint32_t lruHead_ = 0; // 最近使用
|
||||
uint32_t lruTail_ = 0; // 最久未使用
|
||||
uint32_t freeList_ = 0; // 空闲节点链表
|
||||
|
||||
// 统计
|
||||
size_t totalTextureSize_ = 0;
|
||||
uint64_t textureHitCount_ = 0;
|
||||
uint64_t textureMissCount_ = 0;
|
||||
float autoUnloadTimer_ = 0.0f;
|
||||
|
||||
// 异步加载相关
|
||||
struct AsyncLoadTask {
|
||||
std::string filepath;
|
||||
TextureFormat format;
|
||||
TextureLoadCallback callback;
|
||||
std::promise<IntrusivePtr<Texture>> promise;
|
||||
};
|
||||
|
||||
std::queue<AsyncLoadTask> asyncTaskQueue_;
|
||||
std::mutex asyncQueueMutex_;
|
||||
std::condition_variable asyncCondition_;
|
||||
std::unique_ptr<std::thread> asyncThread_;
|
||||
std::atomic<bool> asyncRunning_{false};
|
||||
std::atomic<int> pendingAsyncLoads_{0};
|
||||
|
||||
void asyncLoadLoop();
|
||||
|
||||
// ============================================================================
|
||||
// LRU 缓存内部方法
|
||||
// ============================================================================
|
||||
|
||||
/// 分配LRU节点
|
||||
uint32_t allocateLRUNode(const std::string &key);
|
||||
|
||||
/// 释放LRU节点
|
||||
void freeLRUNode(uint32_t index);
|
||||
|
||||
/// 将节点移到链表头部(最近使用)
|
||||
void moveToFront(uint32_t index);
|
||||
|
||||
/// 从链表中移除节点
|
||||
void removeFromList(uint32_t index);
|
||||
|
||||
/// 驱逐最久未使用的纹理
|
||||
std::string evictLRU();
|
||||
|
||||
/// 访问纹理(更新LRU位置)
|
||||
void touchTexture(const std::string &key);
|
||||
|
||||
/// 驱逐纹理直到满足大小限制
|
||||
void evictTexturesIfNeeded();
|
||||
|
||||
/// 计算纹理大小
|
||||
size_t calculateTextureSize(int width, int height, PixelFormat format) const;
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -1,239 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <core/color.h>
|
||||
#include <core/rect.h>
|
||||
#include <core/types.h>
|
||||
#include <core/vec2.h>
|
||||
#include <event/event_dispatcher.h>
|
||||
#include <renderer/renderer.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// 前向声明
|
||||
class Scene;
|
||||
class Renderer;
|
||||
struct RenderCommand;
|
||||
|
||||
// ============================================================================
|
||||
// 节点基类 - 场景图的基础
|
||||
// ============================================================================
|
||||
class Node : public RefCounted {
|
||||
public:
|
||||
Node();
|
||||
virtual ~Node();
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 层级管理
|
||||
// ------------------------------------------------------------------------
|
||||
void addChild(IntrusivePtr<Node> child);
|
||||
|
||||
/**
|
||||
* @brief 批量添加子节点
|
||||
* @param children 子节点列表
|
||||
*/
|
||||
void addChildren(std::vector<IntrusivePtr<Node>> &&children);
|
||||
|
||||
void removeChild(IntrusivePtr<Node> child);
|
||||
void removeChildByName(const std::string &name);
|
||||
void removeFromParent();
|
||||
void removeAllChildren();
|
||||
|
||||
Node* parent() const { return parent_; }
|
||||
const std::vector<IntrusivePtr<Node>> &children() const { return children_; }
|
||||
IntrusivePtr<Node> childByName(const std::string &name) const;
|
||||
IntrusivePtr<Node> childByTag(int tag) const;
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 变换属性
|
||||
// ------------------------------------------------------------------------
|
||||
void setPosition(const Vec2 &pos);
|
||||
void setPosition(float x, float y);
|
||||
Vec2 pos() const { return position_; }
|
||||
|
||||
void setRotation(float degrees);
|
||||
float rot() const { return rotation_; }
|
||||
|
||||
void setScale(const Vec2 &scale);
|
||||
void setScale(float scale);
|
||||
void setScale(float x, float y);
|
||||
Vec2 scale() const { return scale_; }
|
||||
|
||||
void setAnchor(const Vec2 &anchor);
|
||||
void setAnchor(float x, float y);
|
||||
Vec2 anchor() const { return anchor_; }
|
||||
|
||||
void setSkew(const Vec2 &skew);
|
||||
void setSkew(float x, float y);
|
||||
Vec2 skew() const { return skew_; }
|
||||
|
||||
void setOpacity(float opacity);
|
||||
float opacity() const { return opacity_; }
|
||||
|
||||
void setVisible(bool visible);
|
||||
bool visible() const { return visible_; }
|
||||
|
||||
/**
|
||||
* @brief 设置颜色
|
||||
* @param color RGB颜色
|
||||
*/
|
||||
void setColor(const Color3B &color);
|
||||
Color3B getColor() const { return color_; }
|
||||
|
||||
/**
|
||||
* @brief 设置X轴翻转
|
||||
*/
|
||||
void setFlipX(bool flipX);
|
||||
bool flipX() const { return flipX_; }
|
||||
|
||||
/**
|
||||
* @brief 设置Y轴翻转
|
||||
*/
|
||||
void setFlipY(bool flipY);
|
||||
bool flipY() const { return flipY_; }
|
||||
|
||||
void setZOrder(int zOrder);
|
||||
int zOrder() 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 &name() const { return name_; }
|
||||
|
||||
void setTag(int tag) { tag_ = tag; }
|
||||
int tag() const { return tag_; }
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 生命周期回调
|
||||
// ------------------------------------------------------------------------
|
||||
virtual void onEnter();
|
||||
virtual void onExit();
|
||||
virtual void onUpdate(float dt);
|
||||
virtual void onRender(Renderer &renderer);
|
||||
virtual void onAttachToScene(Scene *scene);
|
||||
virtual void onDetachFromScene();
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 边界框
|
||||
// ------------------------------------------------------------------------
|
||||
virtual Rect boundingBox() const;
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 事件系统
|
||||
// ------------------------------------------------------------------------
|
||||
EventDispatcher &getEventDispatcher() { return eventDispatcher_; }
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 内部方法
|
||||
// ------------------------------------------------------------------------
|
||||
void update(float dt);
|
||||
void render(Renderer &renderer);
|
||||
void sortChildren();
|
||||
|
||||
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_; }
|
||||
float getRotationRef() { return rotation_; }
|
||||
float getOpacityRef() { return opacity_; }
|
||||
|
||||
private:
|
||||
// ==========================================================================
|
||||
// 成员变量按类型大小降序排列,减少内存对齐填充
|
||||
// 64位系统对齐:std::string(32) > glm::mat4(64) > std::vector(24) >
|
||||
// double(8) > float(4) > int(4) > bool(1)
|
||||
// ==========================================================================
|
||||
|
||||
// 1. 大块内存(64字节)
|
||||
mutable glm::mat4 localTransform_; // 64 bytes
|
||||
mutable glm::mat4 worldTransform_; // 64 bytes
|
||||
|
||||
// 2. 字符串和容器(24-32字节)
|
||||
std::string name_; // 32 bytes
|
||||
std::vector<IntrusivePtr<Node>> children_; // 24 bytes
|
||||
|
||||
// 3. 子节点索引(加速查找)
|
||||
std::unordered_map<std::string, Node*> nameIndex_; // 56 bytes
|
||||
std::unordered_map<int, Node*> tagIndex_; // 56 bytes
|
||||
|
||||
// 4. 事件分发器
|
||||
EventDispatcher eventDispatcher_; // 大小取决于实现
|
||||
|
||||
// 5. 父节点引用
|
||||
Node* parent_ = nullptr; // 8 bytes
|
||||
|
||||
// 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
|
||||
|
||||
// 8. 浮点属性
|
||||
float rotation_ = 0.0f; // 4 bytes
|
||||
float opacity_ = 1.0f; // 4 bytes
|
||||
|
||||
// 10. 颜色属性
|
||||
Color3B color_ = Color3B(255, 255, 255); // 3 bytes
|
||||
|
||||
// 11. 整数属性
|
||||
int zOrder_ = 0; // 4 bytes
|
||||
int tag_ = -1; // 4 bytes
|
||||
|
||||
// 12. 布尔属性
|
||||
bool flipX_ = false; // 1 byte
|
||||
bool flipY_ = false; // 1 byte
|
||||
|
||||
// 11. 场景指针
|
||||
Scene *scene_ = nullptr; // 8 bytes
|
||||
|
||||
// 9. 布尔标志(打包在一起)
|
||||
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
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -1,84 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <core/color.h>
|
||||
#include <renderer/camera.h>
|
||||
#include <scene/node.h>
|
||||
#include <vector>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// 前向声明
|
||||
struct RenderCommand;
|
||||
|
||||
// ============================================================================
|
||||
// 场景类 - 节点容器,管理整个场景图
|
||||
// ============================================================================
|
||||
class Scene : public Node {
|
||||
public:
|
||||
Scene();
|
||||
~Scene() override = default;
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 场景属性
|
||||
// ------------------------------------------------------------------------
|
||||
void setBackgroundColor(const Color &color) { backgroundColor_ = color; }
|
||||
Color getBackgroundColor() const { return backgroundColor_; }
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 摄像机
|
||||
// ------------------------------------------------------------------------
|
||||
void setCamera(IntrusivePtr<Camera> camera);
|
||||
IntrusivePtr<Camera> getCamera() const { return camera_; }
|
||||
|
||||
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_; }
|
||||
|
||||
float width() const { return viewportSize_.width; }
|
||||
float height() const { return viewportSize_.height; }
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 场景状态
|
||||
// ------------------------------------------------------------------------
|
||||
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;
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 静态创建方法
|
||||
// ------------------------------------------------------------------------
|
||||
static IntrusivePtr<Scene> create();
|
||||
|
||||
protected:
|
||||
void onEnter() override;
|
||||
void onExit() override;
|
||||
|
||||
friend class SceneManager;
|
||||
|
||||
private:
|
||||
Color backgroundColor_ = Colors::Black;
|
||||
Size viewportSize_ = Size::Zero();
|
||||
|
||||
IntrusivePtr<Camera> camera_;
|
||||
IntrusivePtr<Camera> defaultCamera_;
|
||||
|
||||
bool paused_ = false;
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -1,105 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <core/types.h>
|
||||
#include <functional>
|
||||
#include <scene/scene.h>
|
||||
#include <stack>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// 前向声明
|
||||
struct RenderCommand;
|
||||
|
||||
// ============================================================================
|
||||
// 场景管理器 - 管理场景的生命周期和切换
|
||||
// ============================================================================
|
||||
class SceneManager {
|
||||
public:
|
||||
// ------------------------------------------------------------------------
|
||||
// 单例访问
|
||||
// ------------------------------------------------------------------------
|
||||
static SceneManager &getInstance();
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 场景栈操作
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
// 运行第一个场景
|
||||
void runWithScene(IntrusivePtr<Scene> scene);
|
||||
|
||||
// 替换当前场景
|
||||
void replaceScene(IntrusivePtr<Scene> scene);
|
||||
|
||||
// 压入新场景(当前场景暂停)
|
||||
void pushScene(IntrusivePtr<Scene> scene);
|
||||
|
||||
// 弹出当前场景(恢复上一个场景)
|
||||
void popScene();
|
||||
|
||||
// 弹出到根场景
|
||||
void popToRootScene();
|
||||
|
||||
// 弹出到指定场景
|
||||
void popToScene(const std::string &name);
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 获取场景
|
||||
// ------------------------------------------------------------------------
|
||||
IntrusivePtr<Scene> getCurrentScene() const;
|
||||
IntrusivePtr<Scene> getPreviousScene() const;
|
||||
IntrusivePtr<Scene> getRootScene() const;
|
||||
|
||||
// 通过名称获取场景
|
||||
IntrusivePtr<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);
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 清理
|
||||
// ------------------------------------------------------------------------
|
||||
void end();
|
||||
void purgeCachedScenes();
|
||||
|
||||
public:
|
||||
SceneManager() = default;
|
||||
~SceneManager() = default;
|
||||
SceneManager(const SceneManager &) = delete;
|
||||
SceneManager &operator=(const SceneManager &) = delete;
|
||||
|
||||
// 场景切换(供 Application 使用)
|
||||
void enterScene(IntrusivePtr<Scene> scene);
|
||||
|
||||
private:
|
||||
void doSceneSwitch();
|
||||
void dispatchPointerEvents(Scene &scene);
|
||||
|
||||
std::stack<IntrusivePtr<Scene>> sceneStack_;
|
||||
std::unordered_map<std::string, IntrusivePtr<Scene>> namedScenes_;
|
||||
|
||||
// Next scene to switch to (queued during transition)
|
||||
IntrusivePtr<Scene> nextScene_;
|
||||
bool sendCleanupToScene_ = false;
|
||||
|
||||
Node *hoverTarget_ = nullptr;
|
||||
Node *captureTarget_ = nullptr;
|
||||
Vec2 lastPointerWorld_ = Vec2::Zero();
|
||||
bool hasLastPointerWorld_ = false;
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -1,104 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <core/color.h>
|
||||
#include <core/vec2.h>
|
||||
#include <scene/node.h>
|
||||
#include <vector>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// ============================================================================
|
||||
// 形状类型
|
||||
// ============================================================================
|
||||
enum class ShapeType { Point, Line, Rect, Circle, Triangle, Polygon };
|
||||
|
||||
// ============================================================================
|
||||
// 形状节点 - 用于绘制几何形状
|
||||
// ============================================================================
|
||||
class ShapeNode : public Node {
|
||||
public:
|
||||
ShapeNode();
|
||||
~ShapeNode() override = default;
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 静态创建方法
|
||||
// ------------------------------------------------------------------------
|
||||
static IntrusivePtr<ShapeNode> create();
|
||||
|
||||
// 点
|
||||
static IntrusivePtr<ShapeNode> createPoint(const Vec2 &pos, const Color &color);
|
||||
|
||||
// 线
|
||||
static IntrusivePtr<ShapeNode> createLine(const Vec2 &start, const Vec2 &end,
|
||||
const Color &color, float width = 1.0f);
|
||||
|
||||
// 矩形
|
||||
static IntrusivePtr<ShapeNode> createRect(const Rect &rect, const Color &color,
|
||||
float width = 1.0f);
|
||||
static IntrusivePtr<ShapeNode> createFilledRect(const Rect &rect, const Color &color);
|
||||
|
||||
// 圆形
|
||||
static IntrusivePtr<ShapeNode> createCircle(const Vec2 ¢er, float radius,
|
||||
const Color &color, int segments = 32,
|
||||
float width = 1.0f);
|
||||
static IntrusivePtr<ShapeNode> createFilledCircle(const Vec2 ¢er, float radius,
|
||||
const Color &color,
|
||||
int segments = 32);
|
||||
|
||||
// 三角形
|
||||
static IntrusivePtr<ShapeNode> createTriangle(const Vec2 &p1, const Vec2 &p2,
|
||||
const Vec2 &p3, const Color &color,
|
||||
float width = 1.0f);
|
||||
static IntrusivePtr<ShapeNode> createFilledTriangle(const Vec2 &p1, const Vec2 &p2,
|
||||
const Vec2 &p3,
|
||||
const Color &color);
|
||||
|
||||
// 多边形
|
||||
static IntrusivePtr<ShapeNode> createPolygon(const std::vector<Vec2> &points,
|
||||
const Color &color, float width = 1.0f);
|
||||
static IntrusivePtr<ShapeNode> createFilledPolygon(const std::vector<Vec2> &points,
|
||||
const Color &color);
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 属性设置
|
||||
// ------------------------------------------------------------------------
|
||||
void setShapeType(ShapeType type) { shapeType_ = type; }
|
||||
ShapeType getShapeType() const { return shapeType_; }
|
||||
|
||||
void setColor(const Color &color) { color_ = color; }
|
||||
Color getColor() const { return color_; }
|
||||
|
||||
void setFilled(bool filled) { filled_ = filled; }
|
||||
bool isFilled() const { return filled_; }
|
||||
|
||||
void setLineWidth(float width) { lineWidth_ = width; }
|
||||
float getLineWidth() const { return lineWidth_; }
|
||||
|
||||
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();
|
||||
|
||||
Rect boundingBox() const override;
|
||||
|
||||
protected:
|
||||
void onDraw(Renderer &renderer) override;
|
||||
void generateRenderCommand(std::vector<RenderCommand> &commands,
|
||||
int zOrder) override;
|
||||
|
||||
private:
|
||||
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,55 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <graphics/texture.h>
|
||||
#include <scene/node.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// ============================================================================
|
||||
// 精灵节点
|
||||
// ============================================================================
|
||||
class Sprite : public Node {
|
||||
public:
|
||||
Sprite();
|
||||
explicit Sprite(IntrusivePtr<Texture> texture);
|
||||
~Sprite() override = default;
|
||||
|
||||
// 纹理
|
||||
void setTexture(IntrusivePtr<Texture> texture);
|
||||
IntrusivePtr<Texture> getTexture() const { return texture_; }
|
||||
|
||||
// 纹理矩形 (用于图集)
|
||||
void setTextureRect(const Rect &rect);
|
||||
Rect getTextureRect() const { return textureRect_; }
|
||||
|
||||
// 颜色混合
|
||||
void setColor(const Color &color);
|
||||
Color getColor() const { return color_; }
|
||||
|
||||
// 翻转
|
||||
void setFlipX(bool flip);
|
||||
void setFlipY(bool flip);
|
||||
bool flipX() const { return flipX_; }
|
||||
bool flipY() const { return flipY_; }
|
||||
|
||||
// 静态创建方法
|
||||
static IntrusivePtr<Sprite> create();
|
||||
static IntrusivePtr<Sprite> create(IntrusivePtr<Texture> texture);
|
||||
static IntrusivePtr<Sprite> create(IntrusivePtr<Texture> texture, const Rect &rect);
|
||||
|
||||
Rect boundingBox() const override;
|
||||
|
||||
protected:
|
||||
void onDraw(Renderer &renderer) override;
|
||||
void generateRenderCommand(std::vector<RenderCommand> &commands,
|
||||
int zOrder) override;
|
||||
|
||||
private:
|
||||
IntrusivePtr<Texture> texture_;
|
||||
Rect textureRect_;
|
||||
Color color_ = Colors::White;
|
||||
bool flipX_ = false;
|
||||
bool flipY_ = false;
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -2,17 +2,11 @@
|
|||
#include <audio/audio_engine.h>
|
||||
#include <event/event_dispatcher.h>
|
||||
#include <event/event_queue.h>
|
||||
#include <graphics/vram_manager.h>
|
||||
#include <platform/input.h>
|
||||
#include <platform/window.h>
|
||||
#include <renderer/camera.h>
|
||||
#include <renderer/renderer.h>
|
||||
#include <resource/resource_manager.h>
|
||||
#include <scene/scene_manager.h>
|
||||
#include <utils/logger.h>
|
||||
#include <utils/timer.h>
|
||||
|
||||
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
|
||||
|
|
@ -22,7 +16,9 @@
|
|||
|
||||
namespace extra2d {
|
||||
|
||||
// 获取当前时间(秒)
|
||||
/**
|
||||
* @brief 获取当前时间(秒)
|
||||
*/
|
||||
static double getTimeSeconds() {
|
||||
#ifdef __SWITCH__
|
||||
struct timespec ts;
|
||||
|
|
@ -30,7 +26,6 @@ static double getTimeSeconds() {
|
|||
return static_cast<double>(ts.tv_sec) +
|
||||
static_cast<double>(ts.tv_nsec) / 1000000000.0;
|
||||
#else
|
||||
// PC 平台使用 chrono
|
||||
using namespace std::chrono;
|
||||
auto now = steady_clock::now();
|
||||
auto duration = now.time_since_epoch();
|
||||
|
|
@ -53,7 +48,6 @@ bool Application::init(const AppConfig &config) {
|
|||
|
||||
config_ = config;
|
||||
|
||||
// 确定平台类型
|
||||
PlatformType platform = config_.platform;
|
||||
if (platform == PlatformType::Auto) {
|
||||
#ifdef __SWITCH__
|
||||
|
|
@ -65,9 +59,6 @@ bool Application::init(const AppConfig &config) {
|
|||
|
||||
if (platform == PlatformType::Switch) {
|
||||
#ifdef __SWITCH__
|
||||
// ========================================
|
||||
// 1. 初始化 RomFS 文件系统(Switch 平台)
|
||||
// ========================================
|
||||
Result rc;
|
||||
rc = romfsInit();
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
|
|
@ -77,9 +68,6 @@ bool Application::init(const AppConfig &config) {
|
|||
rc);
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// 2. 初始化 nxlink 调试输出(Switch 平台)
|
||||
// ========================================
|
||||
rc = socketInitializeDefault();
|
||||
if (R_FAILED(rc)) {
|
||||
E2D_LOG_WARN(
|
||||
|
|
@ -88,9 +76,6 @@ bool Application::init(const AppConfig &config) {
|
|||
#endif
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// 3. 创建窗口(包含 SDL_Init + GLES 3.2 上下文创建)
|
||||
// ========================================
|
||||
window_ = unique<Window>();
|
||||
WindowConfig winConfig;
|
||||
winConfig.title = config.title;
|
||||
|
|
@ -98,12 +83,11 @@ bool Application::init(const AppConfig &config) {
|
|||
winConfig.height = config.height;
|
||||
if (platform == PlatformType::Switch) {
|
||||
winConfig.fullscreen = true;
|
||||
winConfig.fullscreenDesktop = false; // Switch 使用固定分辨率全屏
|
||||
winConfig.fullscreenDesktop = false;
|
||||
winConfig.resizable = false;
|
||||
winConfig.enableCursors = false;
|
||||
winConfig.enableDpiScale = false;
|
||||
} else {
|
||||
// PC 平台默认窗口模式
|
||||
winConfig.fullscreen = config.fullscreen;
|
||||
winConfig.resizable = config.resizable;
|
||||
winConfig.enableCursors = config.enableCursors;
|
||||
|
|
@ -117,43 +101,10 @@ bool Application::init(const AppConfig &config) {
|
|||
return false;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// 4. 初始化渲染器
|
||||
// ========================================
|
||||
renderer_ = Renderer::create(config.Renderer);
|
||||
if (!renderer_ || !renderer_->init(window_.get())) {
|
||||
E2D_LOG_ERROR("Failed to initialize renderer");
|
||||
window_->destroy();
|
||||
return false;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// 5. 初始化其他子系统
|
||||
// ========================================
|
||||
sceneManager_ = unique<SceneManager>();
|
||||
resourceManager_ = unique<ResourceManager>();
|
||||
timerManager_ = unique<TimerManager>();
|
||||
eventQueue_ = unique<EventQueue>();
|
||||
eventDispatcher_ = unique<EventDispatcher>();
|
||||
camera_ = unique<Camera>(0, static_cast<float>(window_->width()),
|
||||
static_cast<float>(window_->height()), 0);
|
||||
|
||||
// 窗口大小回调
|
||||
window_->setResizeCallback([this](int width, int height) {
|
||||
if (camera_) {
|
||||
camera_->setViewport(0, static_cast<float>(width),
|
||||
static_cast<float>(height), 0);
|
||||
}
|
||||
if (sceneManager_) {
|
||||
auto currentScene = sceneManager_->getCurrentScene();
|
||||
if (currentScene) {
|
||||
currentScene->setViewportSize(static_cast<float>(width),
|
||||
static_cast<float>(height));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 初始化音频引擎
|
||||
AudioEngine::getInstance().initialize();
|
||||
|
||||
initialized_ = true;
|
||||
|
|
@ -169,50 +120,17 @@ void Application::shutdown() {
|
|||
|
||||
E2D_LOG_INFO("Shutting down application...");
|
||||
|
||||
// 打印 VRAM 统计
|
||||
VRAMManager::getInstance().printStats();
|
||||
|
||||
// 先结束所有场景,确保 onExit() 被正确调用
|
||||
if (sceneManager_) {
|
||||
sceneManager_->end();
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// 1. 先清理所有持有 GPU 资源的子系统
|
||||
// 必须在渲染器关闭前释放纹理等资源
|
||||
// ========================================
|
||||
sceneManager_.reset(); // 场景持有纹理引用
|
||||
resourceManager_.reset(); // 纹理缓存持有 GPU 纹理
|
||||
camera_.reset(); // 相机可能持有渲染目标
|
||||
|
||||
// ========================================
|
||||
// 2. 关闭音频(不依赖 GPU)
|
||||
// ========================================
|
||||
AudioEngine::getInstance().shutdown();
|
||||
|
||||
// ========================================
|
||||
// 3. 清理其他子系统
|
||||
// ========================================
|
||||
timerManager_.reset();
|
||||
eventQueue_.reset();
|
||||
eventDispatcher_.reset();
|
||||
|
||||
// ========================================
|
||||
// 4. 最后关闭渲染器和窗口
|
||||
// 必须在所有 GPU 资源释放后才能关闭 OpenGL 上下文
|
||||
// ========================================
|
||||
if (renderer_) {
|
||||
renderer_->shutdown();
|
||||
renderer_.reset();
|
||||
}
|
||||
|
||||
// 销毁窗口(包含 SDL_Quit,会销毁 OpenGL 上下文)
|
||||
if (window_) {
|
||||
window_->destroy();
|
||||
window_.reset();
|
||||
}
|
||||
|
||||
// Switch 平台清理
|
||||
PlatformType platform = config_.platform;
|
||||
if (platform == PlatformType::Auto) {
|
||||
#ifdef __SWITCH__
|
||||
|
|
@ -243,12 +161,10 @@ void Application::run() {
|
|||
lastFrameTime_ = getTimeSeconds();
|
||||
|
||||
#ifdef __SWITCH__
|
||||
// SDL2 on Switch 内部已处理 appletMainLoop
|
||||
while (running_ && !window_->shouldClose()) {
|
||||
mainLoop();
|
||||
}
|
||||
#else
|
||||
// PC 平台主循环
|
||||
while (running_ && !window_->shouldClose()) {
|
||||
mainLoop();
|
||||
}
|
||||
|
|
@ -276,14 +192,12 @@ void Application::resume() {
|
|||
}
|
||||
|
||||
void Application::mainLoop() {
|
||||
// 计算 delta time
|
||||
double currentTime = getTimeSeconds();
|
||||
deltaTime_ = static_cast<float>(currentTime - lastFrameTime_);
|
||||
lastFrameTime_ = currentTime;
|
||||
|
||||
totalTime_ += deltaTime_;
|
||||
|
||||
// 计算 FPS
|
||||
frameCount_++;
|
||||
fpsTimer_ += deltaTime_;
|
||||
if (fpsTimer_ >= 1.0f) {
|
||||
|
|
@ -292,21 +206,17 @@ void Application::mainLoop() {
|
|||
fpsTimer_ -= 1.0f;
|
||||
}
|
||||
|
||||
// 处理窗口事件(SDL_PollEvent + 输入更新)
|
||||
window_->pollEvents();
|
||||
|
||||
// 处理事件队列
|
||||
if (eventDispatcher_ && eventQueue_) {
|
||||
eventDispatcher_->processQueue(*eventQueue_);
|
||||
}
|
||||
|
||||
// 更新
|
||||
if (!paused_) {
|
||||
update();
|
||||
}
|
||||
|
||||
// 渲染
|
||||
render();
|
||||
window_->swapBuffers();
|
||||
|
||||
if (!config_.vsync && config_.fpsLimit > 0) {
|
||||
double frameEndTime = getTimeSeconds();
|
||||
|
|
@ -323,51 +233,16 @@ void Application::update() {
|
|||
if (timerManager_) {
|
||||
timerManager_->update(deltaTime_);
|
||||
}
|
||||
|
||||
if (sceneManager_) {
|
||||
sceneManager_->update(deltaTime_);
|
||||
}
|
||||
}
|
||||
|
||||
void Application::render() {
|
||||
if (!renderer_) {
|
||||
E2D_LOG_ERROR("Render failed: renderer is null");
|
||||
return;
|
||||
}
|
||||
|
||||
renderer_->setViewport(0, 0, window_->width(), window_->height());
|
||||
|
||||
if (sceneManager_) {
|
||||
sceneManager_->render(*renderer_);
|
||||
} else {
|
||||
E2D_LOG_WARN("Render: sceneManager is null");
|
||||
}
|
||||
|
||||
window_->swapBuffers();
|
||||
}
|
||||
|
||||
Input &Application::input() { return *window_->getInput(); }
|
||||
|
||||
AudioEngine &Application::audio() { return AudioEngine::getInstance(); }
|
||||
|
||||
SceneManager &Application::scenes() { return *sceneManager_; }
|
||||
|
||||
ResourceManager &Application::resources() { return *resourceManager_; }
|
||||
|
||||
TimerManager &Application::timers() { return *timerManager_; }
|
||||
|
||||
EventQueue &Application::eventQueue() { return *eventQueue_; }
|
||||
|
||||
EventDispatcher &Application::eventDispatcher() { return *eventDispatcher_; }
|
||||
|
||||
Camera &Application::camera() { return *camera_; }
|
||||
|
||||
void Application::enterScene(IntrusivePtr<Scene> scene) {
|
||||
if (sceneManager_ && scene) {
|
||||
scene->setViewportSize(static_cast<float>(window_->width()),
|
||||
static_cast<float>(window_->height()));
|
||||
sceneManager_->enterScene(scene);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
|
|||
|
|
@ -1,55 +0,0 @@
|
|||
#include <graphics/alpha_mask.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
AlphaMask::AlphaMask(int width, int height)
|
||||
: width_(width), height_(height), data_(width * height, 255) {}
|
||||
|
||||
AlphaMask AlphaMask::createFromPixels(const uint8_t *pixels, int width,
|
||||
int height, int channels) {
|
||||
AlphaMask mask(width, height);
|
||||
|
||||
if (!pixels || width <= 0 || height <= 0) {
|
||||
return mask;
|
||||
}
|
||||
|
||||
// 根据通道数提取Alpha值
|
||||
for (int y = 0; y < height; ++y) {
|
||||
for (int x = 0; x < width; ++x) {
|
||||
int pixelIndex = (y * width + x) * channels;
|
||||
uint8_t alpha = 255;
|
||||
|
||||
if (channels == 4) {
|
||||
// RGBA格式,Alpha在第四个通道
|
||||
alpha = pixels[pixelIndex + 3];
|
||||
} else if (channels == 1) {
|
||||
// 灰度图,直接作为Alpha
|
||||
alpha = pixels[pixelIndex];
|
||||
} else if (channels == 3) {
|
||||
// RGB格式,没有Alpha通道,视为不透明
|
||||
alpha = 255;
|
||||
}
|
||||
|
||||
mask.data_[y * width + x] = alpha;
|
||||
}
|
||||
}
|
||||
|
||||
return mask;
|
||||
}
|
||||
|
||||
uint8_t AlphaMask::getAlpha(int x, int y) const {
|
||||
if (!isValid(x, y)) {
|
||||
return 0;
|
||||
}
|
||||
return data_[y * width_ + x];
|
||||
}
|
||||
|
||||
bool AlphaMask::isOpaque(int x, int y, uint8_t threshold) const {
|
||||
return getAlpha(x, y) >= threshold;
|
||||
}
|
||||
|
||||
bool AlphaMask::isValid(int x, int y) const {
|
||||
return x >= 0 && x < width_ && y >= 0 && y < height_;
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
#include <graphics/gpu_context.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
GPUContext& GPUContext::getInstance() {
|
||||
static GPUContext instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void GPUContext::markValid() {
|
||||
valid_.store(true, std::memory_order_release);
|
||||
}
|
||||
|
||||
void GPUContext::markInvalid() {
|
||||
valid_.store(false, std::memory_order_release);
|
||||
}
|
||||
|
||||
bool GPUContext::isValid() const {
|
||||
return valid_.load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -1,330 +0,0 @@
|
|||
#include <graphics/opengl/gl_font_atlas.h>
|
||||
#include <utils/logger.h>
|
||||
#include <string>
|
||||
#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 {
|
||||
|
||||
// ============================================================================
|
||||
// UTF-8 到 UTF-32 转换(简化版)
|
||||
// ============================================================================
|
||||
static std::u32string utf8ToUtf32(const std::string& utf8) {
|
||||
std::u32string result;
|
||||
result.reserve(utf8.size());
|
||||
|
||||
const char* ptr = utf8.c_str();
|
||||
const char* end = ptr + utf8.size();
|
||||
|
||||
while (ptr < end) {
|
||||
char32_t ch = 0;
|
||||
unsigned char byte = static_cast<unsigned char>(*ptr);
|
||||
|
||||
if ((byte & 0x80) == 0) {
|
||||
// 1-byte sequence
|
||||
ch = byte;
|
||||
ptr += 1;
|
||||
} else if ((byte & 0xE0) == 0xC0) {
|
||||
// 2-byte sequence
|
||||
ch = (byte & 0x1F) << 6;
|
||||
ch |= (static_cast<unsigned char>(ptr[1]) & 0x3F);
|
||||
ptr += 2;
|
||||
} else if ((byte & 0xF0) == 0xE0) {
|
||||
// 3-byte sequence
|
||||
ch = (byte & 0x0F) << 12;
|
||||
ch |= (static_cast<unsigned char>(ptr[1]) & 0x3F) << 6;
|
||||
ch |= (static_cast<unsigned char>(ptr[2]) & 0x3F);
|
||||
ptr += 3;
|
||||
} else if ((byte & 0xF8) == 0xF0) {
|
||||
// 4-byte sequence
|
||||
ch = (byte & 0x07) << 18;
|
||||
ch |= (static_cast<unsigned char>(ptr[1]) & 0x3F) << 12;
|
||||
ch |= (static_cast<unsigned char>(ptr[2]) & 0x3F) << 6;
|
||||
ch |= (static_cast<unsigned char>(ptr[3]) & 0x3F);
|
||||
ptr += 4;
|
||||
} else {
|
||||
// Invalid UTF-8, skip
|
||||
ptr += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
result.push_back(ch);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 构造函数 - 初始化字体图集
|
||||
// ============================================================================
|
||||
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;
|
||||
|
||||
for (char32_t codepoint : utf8ToUtf32(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,674 +0,0 @@
|
|||
#include <SDL.h>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <graphics/gpu_context.h>
|
||||
#include <graphics/opengl/gl_font_atlas.h>
|
||||
#include <graphics/opengl/gl_renderer.h>
|
||||
#include <graphics/opengl/gl_texture.h>
|
||||
#include <graphics/vram_manager.h>
|
||||
#include <platform/window.h>
|
||||
#include <utils/logger.h>
|
||||
#include <vector>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// ============================================================================
|
||||
// UTF-8 到 UTF-32 转换(简化版)
|
||||
// ============================================================================
|
||||
static std::u32string utf8ToUtf32(const std::string& utf8) {
|
||||
std::u32string result;
|
||||
result.reserve(utf8.size());
|
||||
|
||||
const char* ptr = utf8.c_str();
|
||||
const char* end = ptr + utf8.size();
|
||||
|
||||
while (ptr < end) {
|
||||
char32_t ch = 0;
|
||||
unsigned char byte = static_cast<unsigned char>(*ptr);
|
||||
|
||||
if ((byte & 0x80) == 0) {
|
||||
// 1-byte sequence
|
||||
ch = byte;
|
||||
ptr += 1;
|
||||
} else if ((byte & 0xE0) == 0xC0) {
|
||||
// 2-byte sequence
|
||||
ch = (byte & 0x1F) << 6;
|
||||
ch |= (static_cast<unsigned char>(ptr[1]) & 0x3F);
|
||||
ptr += 2;
|
||||
} else if ((byte & 0xF0) == 0xE0) {
|
||||
// 3-byte sequence
|
||||
ch = (byte & 0x0F) << 12;
|
||||
ch |= (static_cast<unsigned char>(ptr[1]) & 0x3F) << 6;
|
||||
ch |= (static_cast<unsigned char>(ptr[2]) & 0x3F);
|
||||
ptr += 3;
|
||||
} else if ((byte & 0xF8) == 0xF0) {
|
||||
// 4-byte sequence
|
||||
ch = (byte & 0x07) << 18;
|
||||
ch |= (static_cast<unsigned char>(ptr[1]) & 0x3F) << 12;
|
||||
ch |= (static_cast<unsigned char>(ptr[2]) & 0x3F) << 6;
|
||||
ch |= (static_cast<unsigned char>(ptr[3]) & 0x3F);
|
||||
ptr += 4;
|
||||
} else {
|
||||
// Invalid UTF-8, skip
|
||||
ptr += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
result.push_back(ch);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// 形状渲染着色器 - 支持顶点颜色批处理 (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();
|
||||
}
|
||||
|
||||
IntrusivePtr<Texture> GLRenderer::createTexture(int width, int height,
|
||||
const uint8_t *pixels, int channels) {
|
||||
return makeRef<GLTexture>(width, height, pixels, channels);
|
||||
}
|
||||
|
||||
IntrusivePtr<Texture> GLRenderer::loadTexture(const std::string &filepath) {
|
||||
return makeRef<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->width());
|
||||
float texH = static_cast<float>(tex->height());
|
||||
|
||||
// 纹理坐标计算
|
||||
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.width()),
|
||||
static_cast<float>(texture.height()));
|
||||
Rect srcRect(0, 0, static_cast<float>(texture.width()),
|
||||
static_cast<float>(texture.height()));
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
IntrusivePtr<FontAtlas> GLRenderer::createFontAtlas(const std::string &filepath,
|
||||
int fontSize, bool useSDF) {
|
||||
return makeRef<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()); // 预分配空间
|
||||
|
||||
for (char32_t codepoint : utf8ToUtf32(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,129 +0,0 @@
|
|||
#include <graphics/opengl/gl_shader.h>
|
||||
#include <utils/logger.h>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
GLShader::GLShader() : programID_(0) {}
|
||||
|
||||
GLShader::~GLShader() {
|
||||
if (programID_ != 0) {
|
||||
glDeleteProgram(programID_);
|
||||
}
|
||||
}
|
||||
|
||||
bool GLShader::compileFromSource(const char *vertexSource,
|
||||
const char *fragmentSource) {
|
||||
GLuint vertexShader = compileShader(GL_VERTEX_SHADER, vertexSource);
|
||||
if (vertexShader == 0)
|
||||
return false;
|
||||
|
||||
GLuint fragmentShader = compileShader(GL_FRAGMENT_SHADER, fragmentSource);
|
||||
if (fragmentShader == 0) {
|
||||
glDeleteShader(vertexShader);
|
||||
return false;
|
||||
}
|
||||
|
||||
programID_ = glCreateProgram();
|
||||
glAttachShader(programID_, vertexShader);
|
||||
glAttachShader(programID_, fragmentShader);
|
||||
glLinkProgram(programID_);
|
||||
|
||||
GLint success;
|
||||
glGetProgramiv(programID_, GL_LINK_STATUS, &success);
|
||||
if (!success) {
|
||||
char infoLog[512];
|
||||
glGetProgramInfoLog(programID_, 512, nullptr, infoLog);
|
||||
E2D_LOG_ERROR("Shader program linking failed: {}", infoLog);
|
||||
glDeleteProgram(programID_);
|
||||
programID_ = 0;
|
||||
}
|
||||
|
||||
glDeleteShader(vertexShader);
|
||||
glDeleteShader(fragmentShader);
|
||||
|
||||
return success == GL_TRUE;
|
||||
}
|
||||
|
||||
bool GLShader::compileFromFile(const std::string &vertexPath,
|
||||
const std::string &fragmentPath) {
|
||||
std::ifstream vShaderFile(vertexPath);
|
||||
std::ifstream fShaderFile(fragmentPath);
|
||||
|
||||
if (!vShaderFile.is_open() || !fShaderFile.is_open()) {
|
||||
E2D_LOG_ERROR("Failed to open shader files: {}, {}", vertexPath,
|
||||
fragmentPath);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::stringstream vShaderStream, fShaderStream;
|
||||
vShaderStream << vShaderFile.rdbuf();
|
||||
fShaderStream << fShaderFile.rdbuf();
|
||||
|
||||
return compileFromSource(vShaderStream.str().c_str(),
|
||||
fShaderStream.str().c_str());
|
||||
}
|
||||
|
||||
void GLShader::bind() const { glUseProgram(programID_); }
|
||||
|
||||
void GLShader::unbind() const { glUseProgram(0); }
|
||||
|
||||
void GLShader::setBool(const std::string &name, bool value) {
|
||||
glUniform1i(getUniformLocation(name), value ? 1 : 0);
|
||||
}
|
||||
|
||||
void GLShader::setInt(const std::string &name, int value) {
|
||||
glUniform1i(getUniformLocation(name), value);
|
||||
}
|
||||
|
||||
void GLShader::setFloat(const std::string &name, float value) {
|
||||
glUniform1f(getUniformLocation(name), value);
|
||||
}
|
||||
|
||||
void GLShader::setVec2(const std::string &name, const glm::vec2 &value) {
|
||||
glUniform2fv(getUniformLocation(name), 1, &value[0]);
|
||||
}
|
||||
|
||||
void GLShader::setVec3(const std::string &name, const glm::vec3 &value) {
|
||||
glUniform3fv(getUniformLocation(name), 1, &value[0]);
|
||||
}
|
||||
|
||||
void GLShader::setVec4(const std::string &name, const glm::vec4 &value) {
|
||||
glUniform4fv(getUniformLocation(name), 1, &value[0]);
|
||||
}
|
||||
|
||||
void GLShader::setMat4(const std::string &name, const glm::mat4 &value) {
|
||||
glUniformMatrix4fv(getUniformLocation(name), 1, GL_FALSE, &value[0][0]);
|
||||
}
|
||||
|
||||
GLuint GLShader::compileShader(GLenum type, const char *source) {
|
||||
GLuint shader = glCreateShader(type);
|
||||
glShaderSource(shader, 1, &source, nullptr);
|
||||
glCompileShader(shader);
|
||||
|
||||
GLint success;
|
||||
glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
|
||||
if (!success) {
|
||||
char infoLog[512];
|
||||
glGetShaderInfoLog(shader, 512, nullptr, infoLog);
|
||||
E2D_LOG_ERROR("Shader compilation failed: {}", infoLog);
|
||||
glDeleteShader(shader);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return shader;
|
||||
}
|
||||
|
||||
GLint GLShader::getUniformLocation(const std::string &name) {
|
||||
auto it = uniformCache_.find(name);
|
||||
if (it != uniformCache_.end()) {
|
||||
return it->second;
|
||||
}
|
||||
|
||||
GLint location = glGetUniformLocation(programID_, name.c_str());
|
||||
uniformCache_[name] = location;
|
||||
return location;
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -1,363 +0,0 @@
|
|||
#include <cstring>
|
||||
#include <graphics/opengl/gl_sprite_batch.h>
|
||||
#include <utils/logger.h>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// ============================================================================
|
||||
// 三角函数查表 - 避免每帧重复计算 sin/cos
|
||||
// ============================================================================
|
||||
class TrigLookup {
|
||||
public:
|
||||
static constexpr size_t TABLE_SIZE = 360 * 4; // 0.25度精度
|
||||
static constexpr float INDEX_SCALE = 4.0f; // 每度4个采样点
|
||||
static constexpr float RAD_TO_INDEX = INDEX_SCALE * 180.0f / 3.14159265359f;
|
||||
|
||||
static float sinRad(float radians) {
|
||||
return table_.sinTable[normalizeIndexRad(radians)];
|
||||
}
|
||||
|
||||
static float cosRad(float radians) {
|
||||
return table_.cosTable[normalizeIndexRad(radians)];
|
||||
}
|
||||
|
||||
private:
|
||||
struct Tables {
|
||||
std::array<float, TABLE_SIZE> sinTable;
|
||||
std::array<float, TABLE_SIZE> cosTable;
|
||||
|
||||
Tables() {
|
||||
for (size_t i = 0; i < TABLE_SIZE; ++i) {
|
||||
float angle = static_cast<float>(i) / INDEX_SCALE * 3.14159265359f / 180.0f;
|
||||
sinTable[i] = std::sin(angle);
|
||||
cosTable[i] = std::cos(angle);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static size_t normalizeIndexRad(float radians) {
|
||||
int idx = static_cast<int>(radians * RAD_TO_INDEX) % static_cast<int>(TABLE_SIZE);
|
||||
if (idx < 0) {
|
||||
idx += static_cast<int>(TABLE_SIZE);
|
||||
}
|
||||
return static_cast<size_t>(idx);
|
||||
}
|
||||
|
||||
static const Tables table_;
|
||||
};
|
||||
|
||||
const TrigLookup::Tables TrigLookup::table_;
|
||||
|
||||
// 静态索引生成函数
|
||||
static const std::array<GLuint, GLSpriteBatch::MAX_INDICES>& getIndices() {
|
||||
static std::array<GLuint, GLSpriteBatch::MAX_INDICES> indices = []() {
|
||||
std::array<GLuint, GLSpriteBatch::MAX_INDICES> arr{};
|
||||
for (size_t i = 0; i < GLSpriteBatch::MAX_SPRITES; ++i) {
|
||||
GLuint base = static_cast<GLuint>(i * GLSpriteBatch::VERTICES_PER_SPRITE);
|
||||
arr[i * GLSpriteBatch::INDICES_PER_SPRITE + 0] = base + 0;
|
||||
arr[i * GLSpriteBatch::INDICES_PER_SPRITE + 1] = base + 1;
|
||||
arr[i * GLSpriteBatch::INDICES_PER_SPRITE + 2] = base + 2;
|
||||
arr[i * GLSpriteBatch::INDICES_PER_SPRITE + 3] = base + 0;
|
||||
arr[i * GLSpriteBatch::INDICES_PER_SPRITE + 4] = base + 2;
|
||||
arr[i * GLSpriteBatch::INDICES_PER_SPRITE + 5] = base + 3;
|
||||
}
|
||||
return arr;
|
||||
}();
|
||||
return indices;
|
||||
}
|
||||
|
||||
// 顶点着色器 (GLES 3.2)
|
||||
static const char *SPRITE_VERTEX_SHADER = R"(
|
||||
#version 300 es
|
||||
precision highp float;
|
||||
layout(location = 0) in vec2 aPosition;
|
||||
layout(location = 1) in vec2 aTexCoord;
|
||||
layout(location = 2) in vec4 aColor;
|
||||
|
||||
uniform mat4 uViewProjection;
|
||||
|
||||
out vec2 vTexCoord;
|
||||
out vec4 vColor;
|
||||
|
||||
void main() {
|
||||
gl_Position = uViewProjection * vec4(aPosition, 0.0, 1.0);
|
||||
vTexCoord = aTexCoord;
|
||||
vColor = aColor;
|
||||
}
|
||||
)";
|
||||
|
||||
// 片段着色器 (GLES 3.2)
|
||||
// SDF 常量硬编码:ONEDGE_VALUE=128/255=0.502, PIXEL_DIST_SCALE=255/64=3.98
|
||||
// SDF 值存储在 Alpha 通道
|
||||
static const char *SPRITE_FRAGMENT_SHADER = R"(
|
||||
#version 300 es
|
||||
precision highp float;
|
||||
in vec2 vTexCoord;
|
||||
in vec4 vColor;
|
||||
|
||||
uniform sampler2D uTexture;
|
||||
uniform int uUseSDF;
|
||||
|
||||
out vec4 fragColor;
|
||||
|
||||
void main() {
|
||||
if (uUseSDF == 1) {
|
||||
float dist = texture(uTexture, vTexCoord).a;
|
||||
float sd = (dist - 0.502) * 3.98;
|
||||
float w = fwidth(sd);
|
||||
float alpha = smoothstep(-w, w, sd);
|
||||
fragColor = vec4(vColor.rgb, vColor.a * alpha);
|
||||
} else {
|
||||
fragColor = texture(uTexture, vTexCoord) * vColor;
|
||||
}
|
||||
}
|
||||
)";
|
||||
|
||||
GLSpriteBatch::GLSpriteBatch()
|
||||
: vao_(0), vbo_(0), ibo_(0), vertexCount_(0), currentTexture_(nullptr),
|
||||
currentIsSDF_(false), drawCallCount_(0), spriteCount_(0), batchCount_(0) {
|
||||
}
|
||||
|
||||
GLSpriteBatch::~GLSpriteBatch() { shutdown(); }
|
||||
|
||||
bool GLSpriteBatch::init() {
|
||||
// 创建并编译着色器
|
||||
if (!shader_.compileFromSource(SPRITE_VERTEX_SHADER,
|
||||
SPRITE_FRAGMENT_SHADER)) {
|
||||
E2D_LOG_ERROR("Failed to compile sprite batch shader");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 生成 VAO、VBO、IBO
|
||||
glGenVertexArrays(1, &vao_);
|
||||
glGenBuffers(1, &vbo_);
|
||||
glGenBuffers(1, &ibo_);
|
||||
|
||||
glBindVertexArray(vao_);
|
||||
|
||||
// 设置 VBO - 使用动态绘制模式
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vbo_);
|
||||
glBufferData(GL_ARRAY_BUFFER, MAX_VERTICES * sizeof(Vertex), nullptr,
|
||||
GL_DYNAMIC_DRAW);
|
||||
|
||||
// 设置顶点属性
|
||||
glEnableVertexAttribArray(0);
|
||||
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex),
|
||||
(void *)offsetof(Vertex, position));
|
||||
|
||||
glEnableVertexAttribArray(1);
|
||||
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex),
|
||||
(void *)offsetof(Vertex, texCoord));
|
||||
|
||||
glEnableVertexAttribArray(2);
|
||||
glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex),
|
||||
(void *)offsetof(Vertex, color));
|
||||
|
||||
// 使用编译期生成的静态索引缓冲区
|
||||
const auto& indices = getIndices();
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo_);
|
||||
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(GLuint),
|
||||
indices.data(), GL_STATIC_DRAW);
|
||||
|
||||
glBindVertexArray(0);
|
||||
|
||||
E2D_LOG_INFO("GLSpriteBatch initialized with capacity for {} sprites",
|
||||
MAX_SPRITES);
|
||||
return true;
|
||||
}
|
||||
|
||||
void GLSpriteBatch::shutdown() {
|
||||
if (vao_ != 0) {
|
||||
glDeleteVertexArrays(1, &vao_);
|
||||
vao_ = 0;
|
||||
}
|
||||
if (vbo_ != 0) {
|
||||
glDeleteBuffers(1, &vbo_);
|
||||
vbo_ = 0;
|
||||
}
|
||||
if (ibo_ != 0) {
|
||||
glDeleteBuffers(1, &ibo_);
|
||||
ibo_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void GLSpriteBatch::begin(const glm::mat4 &viewProjection) {
|
||||
viewProjection_ = viewProjection;
|
||||
vertexCount_ = 0;
|
||||
currentTexture_ = nullptr;
|
||||
currentIsSDF_ = false;
|
||||
drawCallCount_ = 0;
|
||||
spriteCount_ = 0;
|
||||
batchCount_ = 0;
|
||||
}
|
||||
|
||||
bool GLSpriteBatch::needsFlush(const Texture &texture, bool isSDF) const {
|
||||
if (currentTexture_ == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查是否需要刷新:纹理改变、SDF 状态改变或缓冲区已满
|
||||
return (currentTexture_ != &texture) || (currentIsSDF_ != isSDF) ||
|
||||
(vertexCount_ + VERTICES_PER_SPRITE > MAX_VERTICES);
|
||||
}
|
||||
|
||||
void GLSpriteBatch::addVertices(const SpriteData &data) {
|
||||
// 计算锚点偏移
|
||||
float anchorOffsetX = data.size.x * data.anchor.x;
|
||||
float anchorOffsetY = data.size.y * data.anchor.y;
|
||||
|
||||
// 使用三角函数查表替代 cosf/sinf
|
||||
float cosR = TrigLookup::cosRad(data.rotation);
|
||||
float sinR = TrigLookup::sinRad(data.rotation);
|
||||
|
||||
glm::vec4 color(data.color.r, data.color.g, data.color.b, data.color.a);
|
||||
|
||||
// 直接计算变换后的位置
|
||||
float rx0 = -anchorOffsetX;
|
||||
float ry0 = -anchorOffsetY;
|
||||
float rx1 = data.size.x - anchorOffsetX;
|
||||
float ry1 = data.size.y - anchorOffsetY;
|
||||
|
||||
// 预计算旋转后的偏移
|
||||
float cosRx0 = rx0 * cosR, sinRx0 = rx0 * sinR;
|
||||
float cosRx1 = rx1 * cosR, sinRx1 = rx1 * sinR;
|
||||
float cosRy0 = ry0 * cosR, sinRy0 = ry0 * sinR;
|
||||
float cosRy1 = ry1 * cosR, sinRy1 = ry1 * sinR;
|
||||
|
||||
// v0: (0, 0) -> (rx0, ry0)
|
||||
vertexBuffer_[vertexCount_++] = {
|
||||
glm::vec2(data.position.x + cosRx0 - sinRy0, data.position.y + sinRx0 + cosRy0),
|
||||
glm::vec2(data.texCoordMin.x, data.texCoordMin.y),
|
||||
color
|
||||
};
|
||||
|
||||
// v1: (size.x, 0) -> (rx1, ry0)
|
||||
vertexBuffer_[vertexCount_++] = {
|
||||
glm::vec2(data.position.x + cosRx1 - sinRy0, data.position.y + sinRx1 + cosRy0),
|
||||
glm::vec2(data.texCoordMax.x, data.texCoordMin.y),
|
||||
color
|
||||
};
|
||||
|
||||
// v2: (size.x, size.y) -> (rx1, ry1)
|
||||
vertexBuffer_[vertexCount_++] = {
|
||||
glm::vec2(data.position.x + cosRx1 - sinRy1, data.position.y + sinRx1 + cosRy1),
|
||||
glm::vec2(data.texCoordMax.x, data.texCoordMax.y),
|
||||
color
|
||||
};
|
||||
|
||||
// v3: (0, size.y) -> (rx0, ry1)
|
||||
vertexBuffer_[vertexCount_++] = {
|
||||
glm::vec2(data.position.x + cosRx0 - sinRy1, data.position.y + sinRx0 + cosRy1),
|
||||
glm::vec2(data.texCoordMin.x, data.texCoordMax.y),
|
||||
color
|
||||
};
|
||||
}
|
||||
|
||||
void GLSpriteBatch::draw(const Texture &texture, const SpriteData &data) {
|
||||
// 如果需要刷新,先提交当前批次
|
||||
if (needsFlush(texture, data.isSDF)) {
|
||||
flush();
|
||||
}
|
||||
|
||||
currentTexture_ = &texture;
|
||||
currentIsSDF_ = data.isSDF;
|
||||
|
||||
addVertices(data);
|
||||
spriteCount_++;
|
||||
}
|
||||
|
||||
void GLSpriteBatch::drawBatch(const Texture &texture,
|
||||
const std::vector<SpriteData> &sprites) {
|
||||
if (sprites.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果当前有未提交的批次且纹理不同,先刷新
|
||||
if (currentTexture_ != nullptr && currentTexture_ != &texture) {
|
||||
flush();
|
||||
}
|
||||
|
||||
currentTexture_ = &texture;
|
||||
currentIsSDF_ = sprites[0].isSDF; // 假设批量中的精灵 SDF 状态一致
|
||||
|
||||
// 分批处理,避免超过缓冲区大小
|
||||
size_t index = 0;
|
||||
while (index < sprites.size()) {
|
||||
size_t remainingSpace = (MAX_VERTICES - vertexCount_) / VERTICES_PER_SPRITE;
|
||||
size_t batchSize = std::min(sprites.size() - index, remainingSpace);
|
||||
|
||||
for (size_t i = 0; i < batchSize; ++i) {
|
||||
addVertices(sprites[index + i]);
|
||||
spriteCount_++;
|
||||
}
|
||||
|
||||
index += batchSize;
|
||||
|
||||
// 如果还有更多精灵,刷新当前批次
|
||||
if (index < sprites.size()) {
|
||||
flush();
|
||||
}
|
||||
}
|
||||
|
||||
batchCount_++;
|
||||
}
|
||||
|
||||
void GLSpriteBatch::drawImmediate(const Texture &texture,
|
||||
const SpriteData &data) {
|
||||
// 立即绘制,不缓存 - 用于需要立即显示的情况
|
||||
flush(); // 先提交当前批次
|
||||
|
||||
currentTexture_ = &texture;
|
||||
currentIsSDF_ = data.isSDF;
|
||||
addVertices(data);
|
||||
spriteCount_++;
|
||||
|
||||
flush(); // 立即提交
|
||||
}
|
||||
|
||||
void GLSpriteBatch::end() {
|
||||
if (vertexCount_ > 0) {
|
||||
flush();
|
||||
}
|
||||
}
|
||||
|
||||
void GLSpriteBatch::flush() {
|
||||
if (vertexCount_ == 0 || currentTexture_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 绑定纹理
|
||||
GLuint texID = static_cast<GLuint>(
|
||||
reinterpret_cast<uintptr_t>(currentTexture_->getNativeHandle()));
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glBindTexture(GL_TEXTURE_2D, texID);
|
||||
|
||||
// 使用着色器
|
||||
shader_.bind();
|
||||
shader_.setMat4("uViewProjection", viewProjection_);
|
||||
shader_.setInt("uTexture", 0);
|
||||
shader_.setInt("uUseSDF", currentIsSDF_ ? 1 : 0);
|
||||
// SDF 常量已硬编码到着色器中
|
||||
|
||||
// 更新 VBO 数据 - 只更新实际使用的部分
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vbo_);
|
||||
glBufferSubData(GL_ARRAY_BUFFER, 0, vertexCount_ * sizeof(Vertex),
|
||||
vertexBuffer_.data());
|
||||
|
||||
// 绘制
|
||||
glBindVertexArray(vao_);
|
||||
GLsizei indexCount = static_cast<GLsizei>(
|
||||
(vertexCount_ / VERTICES_PER_SPRITE) * INDICES_PER_SPRITE);
|
||||
glDrawElements(GL_TRIANGLES, indexCount, GL_UNSIGNED_INT, nullptr);
|
||||
|
||||
drawCallCount_++;
|
||||
batchCount_++;
|
||||
|
||||
// 重置状态
|
||||
vertexCount_ = 0;
|
||||
currentTexture_ = nullptr;
|
||||
currentIsSDF_ = false;
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -1,463 +0,0 @@
|
|||
#include <graphics/gpu_context.h>
|
||||
#include <graphics/opengl/gl_texture.h>
|
||||
#include <graphics/vram_manager.h>
|
||||
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <stb/stb_image.h>
|
||||
#include <utils/logger.h>
|
||||
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// ============================================================================
|
||||
// KTX 文件头结构
|
||||
// ============================================================================
|
||||
#pragma pack(push, 1)
|
||||
struct KTXHeader {
|
||||
uint8_t identifier[12];
|
||||
uint32_t endianness;
|
||||
uint32_t glType;
|
||||
uint32_t glTypeSize;
|
||||
uint32_t glFormat;
|
||||
uint32_t glInternalFormat;
|
||||
uint32_t glBaseInternalFormat;
|
||||
uint32_t pixelWidth;
|
||||
uint32_t pixelHeight;
|
||||
uint32_t pixelDepth;
|
||||
uint32_t numberOfArrayElements;
|
||||
uint32_t numberOfFaces;
|
||||
uint32_t numberOfMipmapLevels;
|
||||
uint32_t bytesOfKeyValueData;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
// KTX 文件标识符
|
||||
static const uint8_t KTX_IDENTIFIER[12] = {0xAB, 0x4B, 0x54, 0x58, 0x20, 0x31,
|
||||
0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A};
|
||||
|
||||
// ============================================================================
|
||||
// DDS 文件头结构
|
||||
// ============================================================================
|
||||
#pragma pack(push, 1)
|
||||
struct DDSPixelFormat {
|
||||
uint32_t size;
|
||||
uint32_t flags;
|
||||
uint32_t fourCC;
|
||||
uint32_t rgbBitCount;
|
||||
uint32_t rBitMask;
|
||||
uint32_t gBitMask;
|
||||
uint32_t bBitMask;
|
||||
uint32_t aBitMask;
|
||||
};
|
||||
|
||||
struct DDSHeader {
|
||||
uint32_t magic;
|
||||
uint32_t size;
|
||||
uint32_t flags;
|
||||
uint32_t height;
|
||||
uint32_t width;
|
||||
uint32_t pitchOrLinearSize;
|
||||
uint32_t depth;
|
||||
uint32_t mipMapCount;
|
||||
uint32_t reserved1[11];
|
||||
DDSPixelFormat pixelFormat;
|
||||
uint32_t caps;
|
||||
uint32_t caps2;
|
||||
uint32_t caps3;
|
||||
uint32_t caps4;
|
||||
uint32_t reserved2;
|
||||
};
|
||||
|
||||
struct DDSHeaderDXT10 {
|
||||
uint32_t dxgiFormat;
|
||||
uint32_t resourceDimension;
|
||||
uint32_t miscFlag;
|
||||
uint32_t arraySize;
|
||||
uint32_t miscFlags2;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
static constexpr uint32_t DDS_MAGIC = 0x20534444; // "DDS "
|
||||
static constexpr uint32_t DDPF_FOURCC = 0x04;
|
||||
|
||||
static uint32_t makeFourCC(char a, char b, char c, char d) {
|
||||
return static_cast<uint32_t>(a) | (static_cast<uint32_t>(b) << 8) |
|
||||
(static_cast<uint32_t>(c) << 16) | (static_cast<uint32_t>(d) << 24);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// GLTexture 实现
|
||||
// ============================================================================
|
||||
|
||||
GLTexture::GLTexture(int width, int height, const uint8_t *pixels, int channels)
|
||||
: textureID_(0), width_(width), height_(height), channels_(channels),
|
||||
format_(PixelFormat::RGBA8), dataSize_(0) {
|
||||
// 保存像素数据用于生成遮罩
|
||||
if (pixels) {
|
||||
pixelData_.resize(width * height * channels);
|
||||
std::memcpy(pixelData_.data(), pixels, pixelData_.size());
|
||||
}
|
||||
createTexture(pixels);
|
||||
}
|
||||
|
||||
GLTexture::GLTexture(const std::string &filepath)
|
||||
: textureID_(0), width_(0), height_(0), channels_(0),
|
||||
format_(PixelFormat::RGBA8), dataSize_(0) {
|
||||
// 检查是否为压缩纹理格式
|
||||
std::string ext = filepath.substr(filepath.find_last_of('.') + 1);
|
||||
if (ext == "ktx" || ext == "KTX") {
|
||||
loadCompressed(filepath);
|
||||
return;
|
||||
}
|
||||
if (ext == "dds" || ext == "DDS") {
|
||||
loadCompressed(filepath);
|
||||
return;
|
||||
}
|
||||
|
||||
// 不翻转图片,保持原始方向
|
||||
stbi_set_flip_vertically_on_load(false);
|
||||
uint8_t *data = stbi_load(filepath.c_str(), &width_, &height_, &channels_, 0);
|
||||
if (data) {
|
||||
// 保存像素数据用于生成遮罩
|
||||
pixelData_.resize(width_ * height_ * channels_);
|
||||
std::memcpy(pixelData_.data(), data, pixelData_.size());
|
||||
|
||||
createTexture(data);
|
||||
stbi_image_free(data);
|
||||
} else {
|
||||
E2D_LOG_ERROR("Failed to load texture: {}", filepath);
|
||||
}
|
||||
}
|
||||
|
||||
GLTexture::~GLTexture() {
|
||||
if (textureID_ != 0) {
|
||||
// 检查 GPU 上下文是否仍然有效
|
||||
// 如果 OpenGL 上下文已销毁,则跳过 glDeleteTextures 调用
|
||||
if (GPUContext::getInstance().isValid()) {
|
||||
glDeleteTextures(1, &textureID_);
|
||||
}
|
||||
// VRAM 跟踪: 释放纹理显存(无论上下文是否有效都需要更新统计)
|
||||
if (dataSize_ > 0) {
|
||||
VRAMManager::getInstance().freeTexture(dataSize_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GLTexture::setFilter(bool linear) {
|
||||
bind();
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
|
||||
linear ? GL_LINEAR : GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,
|
||||
linear ? GL_LINEAR : GL_NEAREST);
|
||||
}
|
||||
|
||||
void GLTexture::setWrap(bool repeat) {
|
||||
bind();
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,
|
||||
repeat ? GL_REPEAT : GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,
|
||||
repeat ? GL_REPEAT : GL_CLAMP_TO_EDGE);
|
||||
}
|
||||
|
||||
void GLTexture::bind(unsigned int slot) const {
|
||||
glActiveTexture(GL_TEXTURE0 + slot);
|
||||
glBindTexture(GL_TEXTURE_2D, textureID_);
|
||||
}
|
||||
|
||||
void GLTexture::unbind() const { glBindTexture(GL_TEXTURE_2D, 0); }
|
||||
|
||||
void GLTexture::createTexture(const uint8_t *pixels) {
|
||||
GLenum format = GL_RGBA;
|
||||
GLenum internalFormat = GL_RGBA8;
|
||||
int unpackAlignment = 4;
|
||||
if (channels_ == 1) {
|
||||
format = GL_RED;
|
||||
internalFormat = GL_R8;
|
||||
unpackAlignment = 1;
|
||||
format_ = PixelFormat::R8;
|
||||
} else if (channels_ == 3) {
|
||||
format = GL_RGB;
|
||||
internalFormat = GL_RGB8;
|
||||
unpackAlignment = 1;
|
||||
format_ = PixelFormat::RGB8;
|
||||
} else if (channels_ == 4) {
|
||||
format = GL_RGBA;
|
||||
internalFormat = GL_RGBA8;
|
||||
unpackAlignment = 4;
|
||||
format_ = PixelFormat::RGBA8;
|
||||
}
|
||||
|
||||
glGenTextures(1, &textureID_);
|
||||
bind();
|
||||
|
||||
GLint prevUnpackAlignment = 4;
|
||||
glGetIntegerv(GL_UNPACK_ALIGNMENT, &prevUnpackAlignment);
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, unpackAlignment);
|
||||
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, width_, height_, 0, format,
|
||||
GL_UNSIGNED_BYTE, pixels);
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, prevUnpackAlignment);
|
||||
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
// 使用 NEAREST 过滤器,更适合像素艺术风格的精灵
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
|
||||
glGenerateMipmap(GL_TEXTURE_2D);
|
||||
|
||||
// VRAM 跟踪
|
||||
dataSize_ = static_cast<size_t>(width_ * height_ * channels_);
|
||||
VRAMManager::getInstance().allocTexture(dataSize_);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 压缩纹理加载
|
||||
// ============================================================================
|
||||
|
||||
bool GLTexture::loadCompressed(const std::string &filepath) {
|
||||
std::string ext = filepath.substr(filepath.find_last_of('.') + 1);
|
||||
if (ext == "ktx" || ext == "KTX") {
|
||||
return loadKTX(filepath);
|
||||
}
|
||||
if (ext == "dds" || ext == "DDS") {
|
||||
return loadDDS(filepath);
|
||||
}
|
||||
E2D_LOG_ERROR("Unsupported compressed texture format: {}", filepath);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GLTexture::loadKTX(const std::string &filepath) {
|
||||
std::ifstream file(filepath, std::ios::binary);
|
||||
if (!file.is_open()) {
|
||||
E2D_LOG_ERROR("Failed to open KTX file: {}", filepath);
|
||||
return false;
|
||||
}
|
||||
|
||||
KTXHeader header;
|
||||
file.read(reinterpret_cast<char *>(&header), sizeof(header));
|
||||
if (!file) {
|
||||
E2D_LOG_ERROR("Failed to read KTX header: {}", filepath);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 验证标识符
|
||||
if (std::memcmp(header.identifier, KTX_IDENTIFIER, 12) != 0) {
|
||||
E2D_LOG_ERROR("Invalid KTX identifier: {}", filepath);
|
||||
return false;
|
||||
}
|
||||
|
||||
width_ = static_cast<int>(header.pixelWidth);
|
||||
height_ = static_cast<int>(header.pixelHeight);
|
||||
channels_ = 4; // 压缩纹理通常解压为 RGBA
|
||||
|
||||
// 确定压缩格式
|
||||
GLenum glInternalFormat = header.glInternalFormat;
|
||||
switch (glInternalFormat) {
|
||||
case GL_COMPRESSED_RGB8_ETC2:
|
||||
format_ = PixelFormat::ETC2_RGB8;
|
||||
channels_ = 3;
|
||||
break;
|
||||
case GL_COMPRESSED_RGBA8_ETC2_EAC:
|
||||
format_ = PixelFormat::ETC2_RGBA8;
|
||||
break;
|
||||
case GL_COMPRESSED_RGBA_ASTC_4x4:
|
||||
format_ = PixelFormat::ASTC_4x4;
|
||||
break;
|
||||
case GL_COMPRESSED_RGBA_ASTC_6x6:
|
||||
format_ = PixelFormat::ASTC_6x6;
|
||||
break;
|
||||
case GL_COMPRESSED_RGBA_ASTC_8x8:
|
||||
format_ = PixelFormat::ASTC_8x8;
|
||||
break;
|
||||
default:
|
||||
E2D_LOG_ERROR("Unsupported KTX internal format: {:#06x}", glInternalFormat);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 跳过 key-value 数据
|
||||
file.seekg(header.bytesOfKeyValueData, std::ios::cur);
|
||||
|
||||
// 读取第一个 mipmap level
|
||||
uint32_t imageSize = 0;
|
||||
file.read(reinterpret_cast<char *>(&imageSize), sizeof(imageSize));
|
||||
if (!file || imageSize == 0) {
|
||||
E2D_LOG_ERROR("Failed to read KTX image size: {}", filepath);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> compressedData(imageSize);
|
||||
file.read(reinterpret_cast<char *>(compressedData.data()), imageSize);
|
||||
if (!file) {
|
||||
E2D_LOG_ERROR("Failed to read KTX image data: {}", filepath);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 创建 GL 纹理
|
||||
glGenTextures(1, &textureID_);
|
||||
bind();
|
||||
|
||||
glCompressedTexImage2D(GL_TEXTURE_2D, 0, glInternalFormat, width_, height_, 0,
|
||||
static_cast<GLsizei>(imageSize),
|
||||
compressedData.data());
|
||||
|
||||
GLenum err = glGetError();
|
||||
if (err != GL_NO_ERROR) {
|
||||
E2D_LOG_ERROR("glCompressedTexImage2D failed for KTX: {:#06x}", err);
|
||||
glDeleteTextures(1, &textureID_);
|
||||
textureID_ = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
|
||||
// VRAM 跟踪
|
||||
dataSize_ = imageSize;
|
||||
VRAMManager::getInstance().allocTexture(dataSize_);
|
||||
|
||||
E2D_LOG_INFO("Loaded compressed KTX texture: {} ({}x{}, format={:#06x})",
|
||||
filepath, width_, height_, glInternalFormat);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GLTexture::loadDDS(const std::string &filepath) {
|
||||
std::ifstream file(filepath, std::ios::binary);
|
||||
if (!file.is_open()) {
|
||||
E2D_LOG_ERROR("Failed to open DDS file: {}", filepath);
|
||||
return false;
|
||||
}
|
||||
|
||||
DDSHeader header;
|
||||
file.read(reinterpret_cast<char *>(&header), sizeof(header));
|
||||
if (!file) {
|
||||
E2D_LOG_ERROR("Failed to read DDS header: {}", filepath);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (header.magic != DDS_MAGIC) {
|
||||
E2D_LOG_ERROR("Invalid DDS magic: {}", filepath);
|
||||
return false;
|
||||
}
|
||||
|
||||
width_ = static_cast<int>(header.width);
|
||||
height_ = static_cast<int>(header.height);
|
||||
channels_ = 4;
|
||||
|
||||
GLenum glInternalFormat = 0;
|
||||
|
||||
// 检查 DX10 扩展头
|
||||
if ((header.pixelFormat.flags & DDPF_FOURCC) &&
|
||||
header.pixelFormat.fourCC == makeFourCC('D', 'X', '1', '0')) {
|
||||
DDSHeaderDXT10 dx10Header;
|
||||
file.read(reinterpret_cast<char *>(&dx10Header), sizeof(dx10Header));
|
||||
if (!file) {
|
||||
E2D_LOG_ERROR("Failed to read DDS DX10 header: {}", filepath);
|
||||
return false;
|
||||
}
|
||||
|
||||
// DXGI_FORMAT 映射到 GL 格式
|
||||
switch (dx10Header.dxgiFormat) {
|
||||
case 147: // DXGI_FORMAT_ETC2_RGB8
|
||||
glInternalFormat = GL_COMPRESSED_RGB8_ETC2;
|
||||
format_ = PixelFormat::ETC2_RGB8;
|
||||
channels_ = 3;
|
||||
break;
|
||||
case 148: // DXGI_FORMAT_ETC2_RGBA8
|
||||
glInternalFormat = GL_COMPRESSED_RGBA8_ETC2_EAC;
|
||||
format_ = PixelFormat::ETC2_RGBA8;
|
||||
break;
|
||||
default:
|
||||
E2D_LOG_ERROR("Unsupported DDS DX10 format: {}", dx10Header.dxgiFormat);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
E2D_LOG_ERROR("DDS file does not use DX10 extension, unsupported: {}",
|
||||
filepath);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 计算压缩数据大小
|
||||
size_t blockSize = (glInternalFormat == GL_COMPRESSED_RGB8_ETC2) ? 8 : 16;
|
||||
size_t blocksWide = (width_ + 3) / 4;
|
||||
size_t blocksHigh = (height_ + 3) / 4;
|
||||
size_t imageSize = blocksWide * blocksHigh * blockSize;
|
||||
|
||||
std::vector<uint8_t> compressedData(imageSize);
|
||||
file.read(reinterpret_cast<char *>(compressedData.data()), imageSize);
|
||||
if (!file) {
|
||||
E2D_LOG_ERROR("Failed to read DDS image data: {}", filepath);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 创建 GL 纹理
|
||||
glGenTextures(1, &textureID_);
|
||||
bind();
|
||||
|
||||
glCompressedTexImage2D(GL_TEXTURE_2D, 0, glInternalFormat, width_, height_, 0,
|
||||
static_cast<GLsizei>(imageSize),
|
||||
compressedData.data());
|
||||
|
||||
GLenum err = glGetError();
|
||||
if (err != GL_NO_ERROR) {
|
||||
E2D_LOG_ERROR("glCompressedTexImage2D failed for DDS: {:#06x}", err);
|
||||
glDeleteTextures(1, &textureID_);
|
||||
textureID_ = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
|
||||
// VRAM 跟踪
|
||||
dataSize_ = imageSize;
|
||||
VRAMManager::getInstance().allocTexture(dataSize_);
|
||||
|
||||
E2D_LOG_INFO("Loaded compressed DDS texture: {} ({}x{})", filepath, width_,
|
||||
height_);
|
||||
return true;
|
||||
}
|
||||
|
||||
void GLTexture::generateAlphaMask() {
|
||||
if (pixelData_.empty() || width_ <= 0 || height_ <= 0) {
|
||||
E2D_LOG_WARN("Cannot generate alpha mask: no pixel data available");
|
||||
return;
|
||||
}
|
||||
|
||||
alphaMask_ = std::make_unique<AlphaMask>(AlphaMask::createFromPixels(
|
||||
pixelData_.data(), width_, height_, channels_));
|
||||
|
||||
E2D_LOG_DEBUG("Generated alpha mask for texture: {}x{}", width_, height_);
|
||||
}
|
||||
|
||||
PixelFormat GLTexture::getFormat() const { return format_; }
|
||||
|
||||
IntrusivePtr<Texture> GLTexture::create(int width, int height, PixelFormat format) {
|
||||
int channels = 4;
|
||||
switch (format) {
|
||||
case PixelFormat::R8:
|
||||
channels = 1;
|
||||
break;
|
||||
case PixelFormat::RG8:
|
||||
channels = 2;
|
||||
break;
|
||||
case PixelFormat::RGB8:
|
||||
channels = 3;
|
||||
break;
|
||||
case PixelFormat::RGBA8:
|
||||
channels = 4;
|
||||
break;
|
||||
default:
|
||||
channels = 4;
|
||||
break;
|
||||
}
|
||||
return makeRef<GLTexture>(width, height, nullptr, channels);
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -1,113 +0,0 @@
|
|||
#include <algorithm>
|
||||
#include <graphics/vram_manager.h>
|
||||
#include <utils/logger.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// Switch 推荐 VRAM 预算 ~400MB
|
||||
static constexpr size_t DEFAULT_VRAM_BUDGET = 400 * 1024 * 1024;
|
||||
|
||||
VRAMManager::VRAMManager()
|
||||
: textureVRAM_(0), bufferVRAM_(0), vramBudget_(DEFAULT_VRAM_BUDGET),
|
||||
textureAllocCount_(0), textureFreeCount_(0), bufferAllocCount_(0),
|
||||
bufferFreeCount_(0), peakTextureVRAM_(0), peakBufferVRAM_(0) {}
|
||||
|
||||
VRAMManager &VRAMManager::getInstance() {
|
||||
static VRAMManager instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void VRAMManager::allocTexture(size_t size) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
textureVRAM_ += size;
|
||||
textureAllocCount_++;
|
||||
peakTextureVRAM_ = std::max(peakTextureVRAM_, textureVRAM_);
|
||||
|
||||
if (isOverBudget()) {
|
||||
E2D_LOG_WARN("VRAM over budget! Used: {} MB / Budget: {} MB",
|
||||
getUsedVRAM() / (1024 * 1024), vramBudget_ / (1024 * 1024));
|
||||
}
|
||||
}
|
||||
|
||||
void VRAMManager::freeTexture(size_t size) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
if (size <= textureVRAM_) {
|
||||
textureVRAM_ -= size;
|
||||
} else {
|
||||
textureVRAM_ = 0;
|
||||
}
|
||||
textureFreeCount_++;
|
||||
}
|
||||
|
||||
void VRAMManager::allocBuffer(size_t size) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
bufferVRAM_ += size;
|
||||
bufferAllocCount_++;
|
||||
peakBufferVRAM_ = std::max(peakBufferVRAM_, bufferVRAM_);
|
||||
|
||||
if (isOverBudget()) {
|
||||
E2D_LOG_WARN("VRAM over budget! Used: {} MB / Budget: {} MB",
|
||||
getUsedVRAM() / (1024 * 1024), vramBudget_ / (1024 * 1024));
|
||||
}
|
||||
}
|
||||
|
||||
void VRAMManager::freeBuffer(size_t size) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
if (size <= bufferVRAM_) {
|
||||
bufferVRAM_ -= size;
|
||||
} else {
|
||||
bufferVRAM_ = 0;
|
||||
}
|
||||
bufferFreeCount_++;
|
||||
}
|
||||
|
||||
size_t VRAMManager::getUsedVRAM() const { return textureVRAM_ + bufferVRAM_; }
|
||||
|
||||
size_t VRAMManager::getTextureVRAM() const { return textureVRAM_; }
|
||||
|
||||
size_t VRAMManager::getBufferVRAM() const { return bufferVRAM_; }
|
||||
|
||||
size_t VRAMManager::getAvailableVRAM() const {
|
||||
size_t used = getUsedVRAM();
|
||||
return (used < vramBudget_) ? (vramBudget_ - used) : 0;
|
||||
}
|
||||
|
||||
void VRAMManager::setVRAMBudget(size_t budget) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
vramBudget_ = budget;
|
||||
E2D_LOG_INFO("VRAM budget set to {} MB", budget / (1024 * 1024));
|
||||
}
|
||||
|
||||
size_t VRAMManager::getVRAMBudget() const { return vramBudget_; }
|
||||
|
||||
bool VRAMManager::isOverBudget() const { return getUsedVRAM() > vramBudget_; }
|
||||
|
||||
void VRAMManager::printStats() const {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
E2D_LOG_INFO("=== VRAM Stats ===");
|
||||
E2D_LOG_INFO(" Texture VRAM: {} MB (peak: {} MB)",
|
||||
textureVRAM_ / (1024 * 1024), peakTextureVRAM_ / (1024 * 1024));
|
||||
E2D_LOG_INFO(" Buffer VRAM: {} MB (peak: {} MB)",
|
||||
bufferVRAM_ / (1024 * 1024), peakBufferVRAM_ / (1024 * 1024));
|
||||
E2D_LOG_INFO(" Total Used: {} MB / {} MB budget",
|
||||
(textureVRAM_ + bufferVRAM_) / (1024 * 1024),
|
||||
vramBudget_ / (1024 * 1024));
|
||||
E2D_LOG_INFO(" Texture allocs/frees: {} / {}", textureAllocCount_,
|
||||
textureFreeCount_);
|
||||
E2D_LOG_INFO(" Buffer allocs/frees: {} / {}", bufferAllocCount_,
|
||||
bufferFreeCount_);
|
||||
}
|
||||
|
||||
void VRAMManager::reset() {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
textureVRAM_ = 0;
|
||||
bufferVRAM_ = 0;
|
||||
textureAllocCount_ = 0;
|
||||
textureFreeCount_ = 0;
|
||||
bufferAllocCount_ = 0;
|
||||
bufferFreeCount_ = 0;
|
||||
peakTextureVRAM_ = 0;
|
||||
peakBufferVRAM_ = 0;
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -1,158 +0,0 @@
|
|||
#include <algorithm>
|
||||
#include <renderer/camera.h>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
Camera::Camera() : left_(-1.0f), right_(1.0f), bottom_(-1.0f), top_(1.0f) {}
|
||||
|
||||
Camera::Camera(float left, float right, float bottom, float top)
|
||||
: left_(left), right_(right), bottom_(bottom), top_(top) {}
|
||||
|
||||
Camera::Camera(const Size &viewport)
|
||||
: left_(0.0f), right_(viewport.width), bottom_(viewport.height),
|
||||
top_(0.0f) {}
|
||||
|
||||
void Camera::setPosition(const Vec2 &position) {
|
||||
position_ = position;
|
||||
viewDirty_ = true;
|
||||
}
|
||||
|
||||
void Camera::setPosition(float x, float y) {
|
||||
position_.x = x;
|
||||
position_.y = y;
|
||||
viewDirty_ = true;
|
||||
}
|
||||
|
||||
void Camera::setRotation(float degrees) {
|
||||
rotation_ = degrees;
|
||||
viewDirty_ = true;
|
||||
}
|
||||
|
||||
void Camera::setZoom(float zoom) {
|
||||
zoom_ = zoom;
|
||||
viewDirty_ = true;
|
||||
projDirty_ = true;
|
||||
}
|
||||
|
||||
void Camera::setViewport(float left, float right, float bottom, float top) {
|
||||
left_ = left;
|
||||
right_ = right;
|
||||
bottom_ = bottom;
|
||||
top_ = top;
|
||||
projDirty_ = true;
|
||||
}
|
||||
|
||||
void Camera::setViewport(const Rect &rect) {
|
||||
left_ = rect.left();
|
||||
right_ = rect.right();
|
||||
bottom_ = rect.bottom();
|
||||
top_ = rect.top();
|
||||
projDirty_ = true;
|
||||
}
|
||||
|
||||
Rect Camera::getViewport() const {
|
||||
return Rect(left_, top_, right_ - left_, bottom_ - top_);
|
||||
}
|
||||
|
||||
glm::mat4 Camera::getViewMatrix() const {
|
||||
if (viewDirty_) {
|
||||
viewMatrix_ = glm::mat4(1.0f);
|
||||
// 对于2D相机,我们只需要平移(注意Y轴方向)
|
||||
viewMatrix_ = glm::translate(viewMatrix_,
|
||||
glm::vec3(-position_.x, -position_.y, 0.0f));
|
||||
viewDirty_ = false;
|
||||
}
|
||||
return viewMatrix_;
|
||||
}
|
||||
|
||||
glm::mat4 Camera::getProjectionMatrix() const {
|
||||
if (projDirty_) {
|
||||
// 对于2D游戏,Y轴向下增长(屏幕坐标系)
|
||||
// OpenGL默认Y轴向上,所以需要反转Y轴
|
||||
// glm::ortho(left, right, bottom, top)
|
||||
// 为了Y轴向下:传入 (bottom=height, top=0),这样Y轴翻转
|
||||
projMatrix_ = glm::ortho(
|
||||
left_, right_, // X轴:从左到右
|
||||
bottom_, top_, // Y轴:从下到上(传入bottom>top,实现Y轴向下增长)
|
||||
-1.0f, 1.0f);
|
||||
projDirty_ = false;
|
||||
}
|
||||
return projMatrix_;
|
||||
}
|
||||
|
||||
glm::mat4 Camera::getViewProjectionMatrix() const {
|
||||
// 对于2D相机,我们主要依赖投影矩阵
|
||||
// 视口变换已经处理了坐标系转换
|
||||
return getProjectionMatrix();
|
||||
}
|
||||
|
||||
Vec2 Camera::screenToWorld(const Vec2 &screenPos) const {
|
||||
// 屏幕坐标直接映射到世界坐标(在2D中通常相同)
|
||||
return screenPos;
|
||||
}
|
||||
|
||||
Vec2 Camera::worldToScreen(const Vec2 &worldPos) const {
|
||||
// 世界坐标直接映射到屏幕坐标(在2D中通常相同)
|
||||
return worldPos;
|
||||
}
|
||||
|
||||
Vec2 Camera::screenToWorld(float x, float y) const {
|
||||
return screenToWorld(Vec2(x, y));
|
||||
}
|
||||
|
||||
Vec2 Camera::worldToScreen(float x, float y) const {
|
||||
return worldToScreen(Vec2(x, y));
|
||||
}
|
||||
|
||||
void Camera::move(const Vec2 &offset) {
|
||||
position_ += offset;
|
||||
viewDirty_ = true;
|
||||
}
|
||||
|
||||
void Camera::move(float x, float y) {
|
||||
position_.x += x;
|
||||
position_.y += y;
|
||||
viewDirty_ = true;
|
||||
}
|
||||
|
||||
void Camera::setBounds(const Rect &bounds) {
|
||||
bounds_ = bounds;
|
||||
hasBounds_ = true;
|
||||
}
|
||||
|
||||
void Camera::clearBounds() { hasBounds_ = false; }
|
||||
|
||||
void Camera::clampToBounds() {
|
||||
if (!hasBounds_)
|
||||
return;
|
||||
|
||||
float viewportWidth = (right_ - left_) / zoom_;
|
||||
float viewportHeight = (bottom_ - top_) / zoom_;
|
||||
|
||||
float minX = bounds_.left() + viewportWidth * 0.5f;
|
||||
float maxX = bounds_.right() - viewportWidth * 0.5f;
|
||||
float minY = bounds_.top() + viewportHeight * 0.5f;
|
||||
float maxY = bounds_.bottom() - viewportHeight * 0.5f;
|
||||
|
||||
if (minX > maxX) {
|
||||
position_.x = bounds_.center().x;
|
||||
} else {
|
||||
position_.x = std::clamp(position_.x, minX, maxX);
|
||||
}
|
||||
|
||||
if (minY > maxY) {
|
||||
position_.y = bounds_.center().y;
|
||||
} else {
|
||||
position_.y = std::clamp(position_.y, minY, maxY);
|
||||
}
|
||||
|
||||
viewDirty_ = true;
|
||||
}
|
||||
|
||||
void Camera::lookAt(const Vec2 &target) {
|
||||
position_ = target;
|
||||
viewDirty_ = true;
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
#include <graphics/opengl/gl_renderer.h>
|
||||
#include <renderer/renderer.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
UniquePtr<Renderer> Renderer::create(BackendType type) {
|
||||
switch (type) {
|
||||
case BackendType::OpenGL:
|
||||
return unique<GLRenderer>();
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -1,169 +0,0 @@
|
|||
#include <renderer/render_command.h>
|
||||
#include <algorithm>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// ============================================================================
|
||||
// RenderCommand 便捷构造函数
|
||||
// ============================================================================
|
||||
|
||||
RenderCommand RenderCommand::makeSprite(const Texture* tex, const Rect& dest,
|
||||
const Rect& src, const Color& tint,
|
||||
float rot, const Vec2& anc,
|
||||
uint32_t lyr) {
|
||||
RenderCommand cmd;
|
||||
cmd.type = RenderCommandType::Sprite;
|
||||
cmd.layer = lyr;
|
||||
cmd.transform = glm::mat4(1.0f);
|
||||
|
||||
SpriteCommandData data;
|
||||
data.texture = tex;
|
||||
data.destRect = dest;
|
||||
data.srcRect = src;
|
||||
data.tint = tint;
|
||||
data.rotation = rot;
|
||||
data.anchor = anc;
|
||||
// 生成排序键:纹理指针的高位 + 层级的低位
|
||||
data.sortKey = (reinterpret_cast<uintptr_t>(tex) >> 4) & 0xFFFFFFF0;
|
||||
data.sortKey |= (lyr & 0xF);
|
||||
|
||||
cmd.data = data;
|
||||
return cmd;
|
||||
}
|
||||
|
||||
RenderCommand RenderCommand::makeLine(const Vec2& s, const Vec2& e, const Color& c,
|
||||
float w, uint32_t lyr) {
|
||||
RenderCommand cmd;
|
||||
cmd.type = RenderCommandType::Line;
|
||||
cmd.layer = lyr;
|
||||
cmd.transform = glm::mat4(1.0f);
|
||||
|
||||
LineCommandData data;
|
||||
data.start = s;
|
||||
data.end = e;
|
||||
data.color = c;
|
||||
data.width = w;
|
||||
|
||||
cmd.data = data;
|
||||
return cmd;
|
||||
}
|
||||
|
||||
RenderCommand RenderCommand::makeRect(const Rect& r, const Color& c,
|
||||
float w, bool fill, uint32_t lyr) {
|
||||
RenderCommand cmd;
|
||||
cmd.type = fill ? RenderCommandType::FilledRect : RenderCommandType::Rect;
|
||||
cmd.layer = lyr;
|
||||
cmd.transform = glm::mat4(1.0f);
|
||||
|
||||
RectCommandData data;
|
||||
data.rect = r;
|
||||
data.color = c;
|
||||
data.width = w;
|
||||
data.filled = fill;
|
||||
|
||||
cmd.data = data;
|
||||
return cmd;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// RenderCommandBuffer 实现
|
||||
// ============================================================================
|
||||
|
||||
RenderCommandBuffer::RenderCommandBuffer() : nextOrder_(0) {
|
||||
commands_.reserve(INITIAL_CAPACITY);
|
||||
}
|
||||
|
||||
RenderCommandBuffer::~RenderCommandBuffer() = default;
|
||||
|
||||
void RenderCommandBuffer::addCommand(const RenderCommand& cmd) {
|
||||
if (commands_.size() >= MAX_CAPACITY) {
|
||||
// 缓冲区已满,可能需要立即刷新
|
||||
return;
|
||||
}
|
||||
|
||||
RenderCommand copy = cmd;
|
||||
copy.order = nextOrder_++;
|
||||
commands_.push_back(std::move(copy));
|
||||
}
|
||||
|
||||
void RenderCommandBuffer::addCommand(RenderCommand&& cmd) {
|
||||
if (commands_.size() >= MAX_CAPACITY) {
|
||||
return;
|
||||
}
|
||||
|
||||
cmd.order = nextOrder_++;
|
||||
commands_.push_back(std::move(cmd));
|
||||
}
|
||||
|
||||
RenderCommand& RenderCommandBuffer::emplaceCommand() {
|
||||
if (commands_.size() >= MAX_CAPACITY) {
|
||||
// 如果已满,返回一个虚拟命令(不应该发生)
|
||||
static RenderCommand dummy;
|
||||
return dummy;
|
||||
}
|
||||
|
||||
commands_.emplace_back();
|
||||
commands_.back().order = nextOrder_++;
|
||||
return commands_.back();
|
||||
}
|
||||
|
||||
void RenderCommandBuffer::sortCommands() {
|
||||
// 按以下优先级排序:
|
||||
// 1. 层级 (layer) - 低层级先渲染
|
||||
// 2. 命令类型 - 精灵类命令优先批处理
|
||||
// 3. 纹理/材质 - 相同纹理的精灵连续渲染
|
||||
// 4. 提交顺序 - 保证稳定性
|
||||
|
||||
std::sort(commands_.begin(), commands_.end(), compareCommands);
|
||||
}
|
||||
|
||||
void RenderCommandBuffer::clear() {
|
||||
commands_.clear();
|
||||
nextOrder_ = 0;
|
||||
}
|
||||
|
||||
void RenderCommandBuffer::reserve(size_t capacity) {
|
||||
if (capacity <= MAX_CAPACITY) {
|
||||
commands_.reserve(capacity);
|
||||
}
|
||||
}
|
||||
|
||||
bool RenderCommandBuffer::compareCommands(const RenderCommand& a, const RenderCommand& b) {
|
||||
// 首先按层级排序
|
||||
if (a.layer != b.layer) {
|
||||
return a.layer < b.layer;
|
||||
}
|
||||
|
||||
// 然后按类型排序(精灵类命令放在一起以便批处理)
|
||||
if (a.type != b.type) {
|
||||
// 精灵和文本命令优先(需要纹理)
|
||||
bool aIsSprite = (a.type == RenderCommandType::Sprite ||
|
||||
a.type == RenderCommandType::Text);
|
||||
bool bIsSprite = (b.type == RenderCommandType::Sprite ||
|
||||
b.type == RenderCommandType::Text);
|
||||
|
||||
if (aIsSprite != bIsSprite) {
|
||||
return aIsSprite > bIsSprite; // 精灵类命令在前
|
||||
}
|
||||
|
||||
return static_cast<uint8_t>(a.type) < static_cast<uint8_t>(b.type);
|
||||
}
|
||||
|
||||
// 对于精灵命令,按纹理排序
|
||||
if (a.type == RenderCommandType::Sprite && b.type == RenderCommandType::Sprite) {
|
||||
const auto& dataA = std::get<SpriteCommandData>(a.data);
|
||||
const auto& dataB = std::get<SpriteCommandData>(b.data);
|
||||
if (dataA.texture != dataB.texture) {
|
||||
return dataA.texture < dataB.texture;
|
||||
}
|
||||
// 相同纹理时按 sortKey 排序
|
||||
if (dataA.sortKey != dataB.sortKey) {
|
||||
return dataA.sortKey < dataB.sortKey;
|
||||
}
|
||||
}
|
||||
|
||||
// 最后按提交顺序排序(保证稳定性)
|
||||
return a.order < b.order;
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -1,586 +0,0 @@
|
|||
#include <glad/glad.h>
|
||||
#include <graphics/opengl/gl_texture.h>
|
||||
#include <renderer/render_target.h>
|
||||
#include <utils/logger.h>
|
||||
|
||||
#define STB_IMAGE_WRITE_IMPLEMENTATION
|
||||
#include <stb/stb_image_write.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// ============================================================================
|
||||
// RenderTarget实现
|
||||
// ============================================================================
|
||||
|
||||
RenderTarget::RenderTarget() = default;
|
||||
|
||||
RenderTarget::~RenderTarget() { destroy(); }
|
||||
|
||||
RenderTarget::RenderTarget(RenderTarget &&other) noexcept
|
||||
: fbo_(other.fbo_), rbo_(other.rbo_),
|
||||
colorTexture_(std::move(other.colorTexture_)),
|
||||
depthTexture_(std::move(other.depthTexture_)), width_(other.width_),
|
||||
height_(other.height_), colorFormat_(other.colorFormat_),
|
||||
hasDepth_(other.hasDepth_), hasStencil_(other.hasStencil_),
|
||||
samples_(other.samples_) {
|
||||
other.fbo_ = 0;
|
||||
other.rbo_ = 0;
|
||||
other.width_ = 0;
|
||||
other.height_ = 0;
|
||||
}
|
||||
|
||||
RenderTarget &RenderTarget::operator=(RenderTarget &&other) noexcept {
|
||||
if (this != &other) {
|
||||
destroy();
|
||||
|
||||
fbo_ = other.fbo_;
|
||||
rbo_ = other.rbo_;
|
||||
colorTexture_ = std::move(other.colorTexture_);
|
||||
depthTexture_ = std::move(other.depthTexture_);
|
||||
width_ = other.width_;
|
||||
height_ = other.height_;
|
||||
colorFormat_ = other.colorFormat_;
|
||||
hasDepth_ = other.hasDepth_;
|
||||
hasStencil_ = other.hasStencil_;
|
||||
samples_ = other.samples_;
|
||||
|
||||
other.fbo_ = 0;
|
||||
other.rbo_ = 0;
|
||||
other.width_ = 0;
|
||||
other.height_ = 0;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool RenderTarget::create(const RenderTargetConfig &config) {
|
||||
destroy();
|
||||
|
||||
width_ = config.width;
|
||||
height_ = config.height;
|
||||
colorFormat_ = config.colorFormat;
|
||||
hasDepth_ = config.hasDepth;
|
||||
hasStencil_ = config.hasStencil;
|
||||
samples_ = config.samples;
|
||||
|
||||
if (!createFBO()) {
|
||||
E2D_ERROR("创建渲染目标失败: {}x{}", width_, height_);
|
||||
return false;
|
||||
}
|
||||
|
||||
E2D_INFO("创建渲染目标: {}x{} (深度:{}, 模板:{}, 采样:{})", width_, height_,
|
||||
hasDepth_, hasStencil_, samples_);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RenderTarget::createFromTexture(IntrusivePtr<Texture> texture, bool hasDepth) {
|
||||
if (!texture || !texture->isValid()) {
|
||||
E2D_ERROR("无效的颜色纹理");
|
||||
return false;
|
||||
}
|
||||
|
||||
destroy();
|
||||
|
||||
width_ = texture->width();
|
||||
height_ = texture->height();
|
||||
colorFormat_ = texture->getFormat();
|
||||
hasDepth_ = hasDepth;
|
||||
hasStencil_ = false;
|
||||
samples_ = 1;
|
||||
|
||||
colorTexture_ = texture;
|
||||
|
||||
// 创建FBO
|
||||
glGenFramebuffers(1, &fbo_);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, fbo_);
|
||||
|
||||
// 附加颜色纹理
|
||||
GLuint texId = static_cast<GLuint>(
|
||||
reinterpret_cast<uintptr_t>(texture->getNativeHandle()));
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
|
||||
texId, 0);
|
||||
|
||||
// 创建深度缓冲(如果需要)
|
||||
if (hasDepth_) {
|
||||
glGenRenderbuffers(1, &rbo_);
|
||||
glBindRenderbuffer(GL_RENDERBUFFER, rbo_);
|
||||
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, width_,
|
||||
height_);
|
||||
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
|
||||
GL_RENDERBUFFER, rbo_);
|
||||
glBindRenderbuffer(GL_RENDERBUFFER, 0);
|
||||
}
|
||||
|
||||
// 检查完整性
|
||||
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
|
||||
if (status != GL_FRAMEBUFFER_COMPLETE) {
|
||||
E2D_ERROR("FBO不完整: {:#x}", status);
|
||||
destroy();
|
||||
return false;
|
||||
}
|
||||
|
||||
E2D_INFO("从纹理创建渲染目标: {}x{}", width_, height_);
|
||||
return true;
|
||||
}
|
||||
|
||||
void RenderTarget::destroy() {
|
||||
deleteFBO();
|
||||
|
||||
colorTexture_.reset();
|
||||
depthTexture_.reset();
|
||||
|
||||
width_ = 0;
|
||||
height_ = 0;
|
||||
}
|
||||
|
||||
void RenderTarget::bind() {
|
||||
if (!isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, fbo_);
|
||||
glViewport(0, 0, width_, height_);
|
||||
}
|
||||
|
||||
void RenderTarget::unbind() { bindDefault(); }
|
||||
|
||||
void RenderTarget::clear(const Color &color) {
|
||||
if (!isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
bind();
|
||||
|
||||
GLbitfield mask = GL_COLOR_BUFFER_BIT;
|
||||
if (hasDepth_) {
|
||||
mask |= GL_DEPTH_BUFFER_BIT;
|
||||
glClearDepthf(1.0f); // GLES 使用 glClearDepthf
|
||||
}
|
||||
if (hasStencil_) {
|
||||
mask |= GL_STENCIL_BUFFER_BIT;
|
||||
glClearStencil(0);
|
||||
}
|
||||
|
||||
glClearColor(color.r, color.g, color.b, color.a);
|
||||
glClear(mask);
|
||||
}
|
||||
|
||||
void RenderTarget::setViewport(int x, int y, int width, int height) {
|
||||
if (!isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
bind();
|
||||
glViewport(x, y, width, height);
|
||||
}
|
||||
|
||||
void RenderTarget::getFullViewport(int &x, int &y, int &width,
|
||||
int &height) const {
|
||||
x = 0;
|
||||
y = 0;
|
||||
width = width_;
|
||||
height = height_;
|
||||
}
|
||||
|
||||
bool RenderTarget::resize(int width, int height) {
|
||||
if (!isValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RenderTargetConfig config;
|
||||
config.width = width;
|
||||
config.height = height;
|
||||
config.colorFormat = colorFormat_;
|
||||
config.hasDepth = hasDepth_;
|
||||
config.hasStencil = hasStencil_;
|
||||
config.samples = samples_;
|
||||
|
||||
return create(config);
|
||||
}
|
||||
|
||||
void RenderTarget::copyTo(RenderTarget &target) {
|
||||
if (!isValid() || !target.isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 使用glBlitFramebuffer复制
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo_);
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, target.fbo_);
|
||||
|
||||
GLbitfield mask = GL_COLOR_BUFFER_BIT;
|
||||
if (hasDepth_ && target.hasDepth_) {
|
||||
mask |= GL_DEPTH_BUFFER_BIT;
|
||||
}
|
||||
|
||||
glBlitFramebuffer(0, 0, width_, height_, 0, 0, target.width_, target.height_,
|
||||
mask, GL_LINEAR);
|
||||
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
}
|
||||
|
||||
void RenderTarget::blitTo(RenderTarget &target, bool color, bool depth) {
|
||||
if (!isValid() || !target.isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 使用glBlitFramebuffer复制
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo_);
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, target.getFBO());
|
||||
|
||||
GLbitfield mask = 0;
|
||||
if (color) {
|
||||
mask |= GL_COLOR_BUFFER_BIT;
|
||||
}
|
||||
if (depth && hasDepth_ && target.hasDepth_) {
|
||||
mask |= GL_DEPTH_BUFFER_BIT;
|
||||
}
|
||||
|
||||
if (mask != 0) {
|
||||
glBlitFramebuffer(0, 0, width_, height_, 0, 0, target.width(),
|
||||
target.height(), mask, GL_LINEAR);
|
||||
}
|
||||
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
}
|
||||
|
||||
void RenderTarget::copyToScreen(int screenWidth, int screenHeight) {
|
||||
if (!isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo_);
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
||||
|
||||
glBlitFramebuffer(0, 0, width_, height_, 0, 0, screenWidth, screenHeight,
|
||||
GL_COLOR_BUFFER_BIT, GL_LINEAR);
|
||||
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
}
|
||||
|
||||
bool RenderTarget::saveToFile(const std::string &filepath) {
|
||||
if (!isValid() || !colorTexture_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 读取像素数据
|
||||
std::vector<uint8_t> pixels(width_ * height_ * 4);
|
||||
|
||||
bind();
|
||||
glReadPixels(0, 0, width_, height_, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
|
||||
unbind();
|
||||
|
||||
// 翻转Y轴(OpenGL坐标系原点在左下角,PNG需要左上角原点)
|
||||
std::vector<uint8_t> flipped(width_ * height_ * 4);
|
||||
for (int y = 0; y < height_; ++y) {
|
||||
for (int x = 0; x < width_; ++x) {
|
||||
int srcIdx = ((height_ - 1 - y) * width_ + x) * 4;
|
||||
int dstIdx = (y * width_ + x) * 4;
|
||||
for (int c = 0; c < 4; ++c) {
|
||||
flipped[dstIdx + c] = pixels[srcIdx + c];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 使用stb_image_write保存为PNG
|
||||
int result = stbi_write_png(filepath.c_str(), width_, height_, 4,
|
||||
flipped.data(), width_ * 4);
|
||||
|
||||
if (result == 0) {
|
||||
E2D_ERROR("保存渲染目标到PNG失败: {}", filepath);
|
||||
return false;
|
||||
}
|
||||
|
||||
E2D_INFO("保存渲染目标到: {}", filepath);
|
||||
return true;
|
||||
}
|
||||
|
||||
IntrusivePtr<RenderTarget>
|
||||
RenderTarget::createFromConfig(const RenderTargetConfig &config) {
|
||||
auto rt = makeRef<RenderTarget>();
|
||||
if (rt->create(config)) {
|
||||
return rt;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
GLuint RenderTarget::getCurrentFBO() {
|
||||
GLint fbo = 0;
|
||||
glGetIntegerv(GL_FRAMEBUFFER_BINDING, &fbo);
|
||||
return static_cast<GLuint>(fbo);
|
||||
}
|
||||
|
||||
void RenderTarget::bindDefault() { glBindFramebuffer(GL_FRAMEBUFFER, 0); }
|
||||
|
||||
// ============================================================================
|
||||
// 内部方法
|
||||
// ============================================================================
|
||||
|
||||
bool RenderTarget::createFBO() {
|
||||
// 创建颜色纹理
|
||||
colorTexture_ = GLTexture::create(width_, height_, colorFormat_);
|
||||
if (!colorTexture_ || !colorTexture_->isValid()) {
|
||||
E2D_ERROR("创建颜色纹理失败");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 创建FBO
|
||||
glGenFramebuffers(1, &fbo_);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, fbo_);
|
||||
|
||||
// 附加颜色纹理
|
||||
GLuint colorTexId =
|
||||
static_cast<GLTexture *>(colorTexture_.get())->getTextureID();
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
|
||||
colorTexId, 0);
|
||||
|
||||
// 创建深度/模板缓冲
|
||||
if (hasDepth_ || hasStencil_) {
|
||||
glGenRenderbuffers(1, &rbo_);
|
||||
glBindRenderbuffer(GL_RENDERBUFFER, rbo_);
|
||||
|
||||
if (hasDepth_ && hasStencil_) {
|
||||
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, width_,
|
||||
height_);
|
||||
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
|
||||
GL_RENDERBUFFER, rbo_);
|
||||
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT,
|
||||
GL_RENDERBUFFER, rbo_);
|
||||
} else if (hasDepth_) {
|
||||
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, width_,
|
||||
height_);
|
||||
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
|
||||
GL_RENDERBUFFER, rbo_);
|
||||
}
|
||||
|
||||
glBindRenderbuffer(GL_RENDERBUFFER, 0);
|
||||
}
|
||||
|
||||
// 检查完整性
|
||||
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
|
||||
if (status != GL_FRAMEBUFFER_COMPLETE) {
|
||||
E2D_ERROR("FBO创建失败,状态: {:#x}", status);
|
||||
deleteFBO();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void RenderTarget::deleteFBO() {
|
||||
if (rbo_ != 0) {
|
||||
glDeleteRenderbuffers(1, &rbo_);
|
||||
rbo_ = 0;
|
||||
}
|
||||
|
||||
if (fbo_ != 0) {
|
||||
glDeleteFramebuffers(1, &fbo_);
|
||||
fbo_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// MultisampleRenderTarget实现
|
||||
// ============================================================================
|
||||
|
||||
bool MultisampleRenderTarget::create(int width, int height, int samples) {
|
||||
// 先销毁现有的
|
||||
destroy();
|
||||
|
||||
width_ = width;
|
||||
height_ = height;
|
||||
samples_ = samples;
|
||||
hasDepth_ = true;
|
||||
hasStencil_ = false;
|
||||
colorFormat_ = PixelFormat::RGBA8;
|
||||
|
||||
// 创建FBO
|
||||
glGenFramebuffers(1, &fbo_);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, fbo_);
|
||||
|
||||
// 创建多重采样颜色渲染缓冲
|
||||
glGenRenderbuffers(1, &colorRBO_);
|
||||
glBindRenderbuffer(GL_RENDERBUFFER, colorRBO_);
|
||||
glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, GL_RGBA8, width,
|
||||
height);
|
||||
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
|
||||
GL_RENDERBUFFER, colorRBO_);
|
||||
glBindRenderbuffer(GL_RENDERBUFFER, 0);
|
||||
|
||||
// 创建多重采样深度渲染缓冲
|
||||
glGenRenderbuffers(1, &rbo_);
|
||||
glBindRenderbuffer(GL_RENDERBUFFER, rbo_);
|
||||
glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples,
|
||||
GL_DEPTH_COMPONENT24, width, height);
|
||||
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
|
||||
GL_RENDERBUFFER, rbo_);
|
||||
glBindRenderbuffer(GL_RENDERBUFFER, 0);
|
||||
|
||||
// 检查完整性
|
||||
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
|
||||
if (status != GL_FRAMEBUFFER_COMPLETE) {
|
||||
E2D_ERROR("多重采样FBO创建失败,状态: {:#x}", status);
|
||||
destroy();
|
||||
return false;
|
||||
}
|
||||
|
||||
E2D_INFO("创建多重采样渲染目标: {}x{} (采样数: {})", width, height, samples);
|
||||
return true;
|
||||
}
|
||||
|
||||
void MultisampleRenderTarget::destroy() {
|
||||
// 删除颜色渲染缓冲
|
||||
if (colorRBO_ != 0) {
|
||||
glDeleteRenderbuffers(1, &colorRBO_);
|
||||
colorRBO_ = 0;
|
||||
}
|
||||
|
||||
// 调用基类destroy
|
||||
RenderTarget::destroy();
|
||||
}
|
||||
|
||||
void MultisampleRenderTarget::resolveTo(RenderTarget &target) {
|
||||
if (!isValid() || !target.isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 使用glBlitFramebuffer解析多重采样
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo_);
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, target.getFBO());
|
||||
|
||||
glBlitFramebuffer(0, 0, width_, height_, 0, 0, target.width(),
|
||||
target.height(), GL_COLOR_BUFFER_BIT, GL_LINEAR);
|
||||
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// RenderTargetStack实现
|
||||
// ============================================================================
|
||||
|
||||
RenderTargetStack &RenderTargetStack::getInstance() {
|
||||
static RenderTargetStack instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void RenderTargetStack::push(RenderTarget *target) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
if (target) {
|
||||
stack_.push_back(target);
|
||||
target->bind();
|
||||
}
|
||||
}
|
||||
|
||||
void RenderTargetStack::pop() {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
if (!stack_.empty()) {
|
||||
stack_.pop_back();
|
||||
}
|
||||
|
||||
// 绑定新的当前渲染目标
|
||||
if (!stack_.empty()) {
|
||||
stack_.back()->bind();
|
||||
} else {
|
||||
RenderTarget::bindDefault();
|
||||
}
|
||||
}
|
||||
|
||||
RenderTarget *RenderTargetStack::getCurrent() const {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
if (stack_.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
return stack_.back();
|
||||
}
|
||||
|
||||
size_t RenderTargetStack::size() const {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
return stack_.size();
|
||||
}
|
||||
|
||||
void RenderTargetStack::clear() {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
stack_.clear();
|
||||
RenderTarget::bindDefault();
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// RenderTargetManager实现
|
||||
// ============================================================================
|
||||
|
||||
RenderTargetManager &RenderTargetManager::getInstance() {
|
||||
static RenderTargetManager instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
bool RenderTargetManager::init(int width, int height) {
|
||||
if (initialized_) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 创建默认渲染目标
|
||||
RenderTargetConfig config;
|
||||
config.width = width;
|
||||
config.height = height;
|
||||
config.hasDepth = true;
|
||||
config.hasStencil = false;
|
||||
|
||||
defaultRenderTarget_ = RenderTarget::createFromConfig(config);
|
||||
if (!defaultRenderTarget_) {
|
||||
E2D_ERROR("创建默认渲染目标失败");
|
||||
return false;
|
||||
}
|
||||
|
||||
initialized_ = true;
|
||||
E2D_INFO("渲染目标管理器初始化完成: {}x{}", width, height);
|
||||
return true;
|
||||
}
|
||||
|
||||
void RenderTargetManager::shutdown() {
|
||||
if (!initialized_) {
|
||||
return;
|
||||
}
|
||||
|
||||
renderTargets_.clear();
|
||||
defaultRenderTarget_.reset();
|
||||
initialized_ = false;
|
||||
|
||||
E2D_INFO("渲染目标管理器已关闭");
|
||||
}
|
||||
|
||||
IntrusivePtr<RenderTarget>
|
||||
RenderTargetManager::createRenderTarget(const RenderTargetConfig &config) {
|
||||
if (!initialized_) {
|
||||
E2D_ERROR("渲染目标管理器未初始化");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto rt = RenderTarget::createFromConfig(config);
|
||||
if (rt) {
|
||||
renderTargets_.push_back(rt);
|
||||
}
|
||||
return rt;
|
||||
}
|
||||
|
||||
void RenderTargetManager::resize(int width, int height) {
|
||||
if (!initialized_) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 调整默认渲染目标大小
|
||||
if (defaultRenderTarget_) {
|
||||
defaultRenderTarget_->resize(width, height);
|
||||
}
|
||||
|
||||
E2D_INFO("渲染目标管理器调整大小: {}x{}", width, height);
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,422 +0,0 @@
|
|||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <renderer/render_command.h>
|
||||
#include <scene/node.h>
|
||||
#include <scene/scene.h>
|
||||
#include <utils/logger.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
Node::Node() = default;
|
||||
|
||||
Node::~Node() { removeAllChildren(); }
|
||||
|
||||
void Node::addChild(IntrusivePtr<Node> child) {
|
||||
if (!child || child.get() == this) {
|
||||
return;
|
||||
}
|
||||
|
||||
child->removeFromParent();
|
||||
child->parent_ = this;
|
||||
children_.push_back(child);
|
||||
childrenOrderDirty_ = true;
|
||||
|
||||
// 更新索引
|
||||
if (!child->name().empty()) {
|
||||
nameIndex_[child->name()] = child.get();
|
||||
}
|
||||
if (child->tag() != -1) {
|
||||
tagIndex_[child->tag()] = child.get();
|
||||
}
|
||||
|
||||
if (running_) {
|
||||
child->onEnter();
|
||||
if (scene_) {
|
||||
child->onAttachToScene(scene_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Node::addChildren(std::vector<IntrusivePtr<Node>> &&children) {
|
||||
// 预留空间,避免多次扩容
|
||||
size_t newSize = children_.size() + children.size();
|
||||
if (newSize > children_.capacity()) {
|
||||
children_.reserve(newSize);
|
||||
}
|
||||
|
||||
for (auto &child : children) {
|
||||
if (!child || child.get() == this) {
|
||||
continue;
|
||||
}
|
||||
|
||||
child->removeFromParent();
|
||||
child->parent_ = this;
|
||||
children_.push_back(child);
|
||||
|
||||
// 更新索引
|
||||
if (!child->name().empty()) {
|
||||
nameIndex_[child->name()] = child.get();
|
||||
}
|
||||
if (child->tag() != -1) {
|
||||
tagIndex_[child->tag()] = child.get();
|
||||
}
|
||||
|
||||
if (running_) {
|
||||
child->onEnter();
|
||||
if (scene_) {
|
||||
child->onAttachToScene(scene_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!children.empty()) {
|
||||
childrenOrderDirty_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
void Node::removeChild(IntrusivePtr<Node> child) {
|
||||
if (!child)
|
||||
return;
|
||||
|
||||
auto it = std::find(children_.begin(), children_.end(), child);
|
||||
if (it != children_.end()) {
|
||||
// 始终从空间索引中移除(无论 running_ 状态)
|
||||
// 这确保节点被正确清理
|
||||
(*it)->onDetachFromScene();
|
||||
|
||||
if (running_) {
|
||||
(*it)->onExit();
|
||||
}
|
||||
// 从索引中移除
|
||||
if (!(*it)->name().empty()) {
|
||||
nameIndex_.erase((*it)->name());
|
||||
}
|
||||
if ((*it)->tag() != -1) {
|
||||
tagIndex_.erase((*it)->tag());
|
||||
}
|
||||
(*it)->parent_ = nullptr;
|
||||
children_.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
void Node::removeChildByName(const std::string &name) {
|
||||
auto child = childByName(name);
|
||||
if (child) {
|
||||
removeChild(child);
|
||||
}
|
||||
}
|
||||
|
||||
void Node::removeFromParent() {
|
||||
auto p = parent_;
|
||||
if (p) {
|
||||
p->removeChild(IntrusivePtr<Node>(this));
|
||||
}
|
||||
}
|
||||
|
||||
void Node::removeAllChildren() {
|
||||
for (auto &child : children_) {
|
||||
if (running_) {
|
||||
child->onDetachFromScene();
|
||||
child->onExit();
|
||||
}
|
||||
child->parent_ = nullptr;
|
||||
}
|
||||
children_.clear();
|
||||
nameIndex_.clear();
|
||||
tagIndex_.clear();
|
||||
}
|
||||
|
||||
IntrusivePtr<Node> Node::childByName(const std::string &name) const {
|
||||
// 使用哈希索引,O(1) 查找
|
||||
auto it = nameIndex_.find(name);
|
||||
if (it != nameIndex_.end()) {
|
||||
return IntrusivePtr<Node>(it->second);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
IntrusivePtr<Node> Node::childByTag(int tag) const {
|
||||
// 使用哈希索引,O(1) 查找
|
||||
auto it = tagIndex_.find(tag);
|
||||
if (it != tagIndex_.end()) {
|
||||
return IntrusivePtr<Node>(it->second);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void Node::setPosition(const Vec2 &pos) {
|
||||
position_ = pos;
|
||||
markTransformDirty();
|
||||
}
|
||||
|
||||
void Node::setPosition(float x, float y) { setPosition(Vec2(x, y)); }
|
||||
|
||||
void Node::setRotation(float degrees) {
|
||||
rotation_ = degrees;
|
||||
markTransformDirty();
|
||||
}
|
||||
|
||||
void Node::setScale(const Vec2 &scale) {
|
||||
scale_ = scale;
|
||||
markTransformDirty();
|
||||
}
|
||||
|
||||
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::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);
|
||||
}
|
||||
|
||||
void Node::setVisible(bool visible) { visible_ = visible; }
|
||||
|
||||
void Node::setColor(const Color3B &color) { color_ = color; }
|
||||
|
||||
void Node::setFlipX(bool flipX) { flipX_ = flipX; }
|
||||
|
||||
void Node::setFlipY(bool flipY) { flipY_ = flipY; }
|
||||
|
||||
void Node::setZOrder(int zOrder) {
|
||||
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::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);
|
||||
|
||||
// T - R - S order
|
||||
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 (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_;
|
||||
}
|
||||
|
||||
glm::mat4 Node::getWorldTransform() const {
|
||||
if (worldTransformDirty_) {
|
||||
// 使用线程局部存储的固定数组,避免每帧内存分配
|
||||
// 限制最大深度为 256 层,足以覆盖绝大多数场景
|
||||
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;
|
||||
current = current->parent_;
|
||||
}
|
||||
|
||||
// 从根节点开始计算
|
||||
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;
|
||||
}
|
||||
return worldTransform_;
|
||||
}
|
||||
|
||||
void Node::markTransformDirty() {
|
||||
// 避免重复标记,提高性能
|
||||
if (!transformDirty_ || !worldTransformDirty_) {
|
||||
transformDirty_ = true;
|
||||
worldTransformDirty_ = true;
|
||||
|
||||
// 递归标记所有子节点
|
||||
for (auto &child : children_) {
|
||||
child->markTransformDirty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Node::batchUpdateTransforms() {
|
||||
// 如果本地变换脏了,先计算本地变换
|
||||
if (transformDirty_) {
|
||||
(void)getLocalTransform(); // 这会计算并缓存本地变换
|
||||
}
|
||||
|
||||
// 如果世界变换脏了,需要重新计算
|
||||
if (worldTransformDirty_) {
|
||||
auto parent = parent_;
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
void Node::onExit() {
|
||||
running_ = false;
|
||||
for (auto &child : children_) {
|
||||
child->onExit();
|
||||
}
|
||||
}
|
||||
|
||||
void Node::onUpdate(float dt) {
|
||||
onUpdateNode(dt);
|
||||
|
||||
// Update children
|
||||
for (auto &child : children_) {
|
||||
child->onUpdate(dt);
|
||||
}
|
||||
}
|
||||
|
||||
void Node::onRender(Renderer &renderer) {
|
||||
if (!visible_)
|
||||
return;
|
||||
|
||||
onDraw(renderer);
|
||||
|
||||
for (auto &child : children_) {
|
||||
child->onRender(renderer);
|
||||
}
|
||||
}
|
||||
|
||||
void Node::onAttachToScene(Scene *scene) {
|
||||
scene_ = scene;
|
||||
|
||||
for (auto &child : children_) {
|
||||
child->onAttachToScene(scene);
|
||||
}
|
||||
}
|
||||
|
||||
void Node::onDetachFromScene() {
|
||||
scene_ = nullptr;
|
||||
for (auto &child : children_) {
|
||||
child->onDetachFromScene();
|
||||
}
|
||||
}
|
||||
|
||||
Rect Node::boundingBox() const {
|
||||
// 默认返回一个以位置为中心的点矩形
|
||||
return Rect(position_.x, position_.y, 0, 0);
|
||||
}
|
||||
|
||||
void Node::update(float dt) { onUpdate(dt); }
|
||||
|
||||
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->zOrder();
|
||||
int j = static_cast<int>(i) - 1;
|
||||
|
||||
while (j >= 0 && children_[j]->zOrder() > keyZOrder) {
|
||||
children_[j + 1] = children_[j];
|
||||
--j;
|
||||
}
|
||||
children_[j + 1] = key;
|
||||
}
|
||||
} else {
|
||||
// 大数组使用标准排序
|
||||
std::sort(children_.begin(), children_.end(),
|
||||
[](const IntrusivePtr<Node> &a, const IntrusivePtr<Node> &b) {
|
||||
return a->zOrder() < b->zOrder();
|
||||
});
|
||||
}
|
||||
|
||||
childrenOrderDirty_ = false;
|
||||
}
|
||||
|
||||
void Node::collectRenderCommands(std::vector<RenderCommand> &commands,
|
||||
int parentZOrder) {
|
||||
if (!visible_)
|
||||
return;
|
||||
|
||||
// 计算累积 Z 序
|
||||
int accumulatedZOrder = parentZOrder + zOrder_;
|
||||
|
||||
// 生成当前节点的渲染命令
|
||||
generateRenderCommand(commands, accumulatedZOrder);
|
||||
|
||||
// 递归收集子节点的渲染命令
|
||||
// 注意:这里假设子节点已经按 Z 序排序
|
||||
for (auto &child : children_) {
|
||||
child->collectRenderCommands(commands, accumulatedZOrder);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -1,73 +0,0 @@
|
|||
#include <renderer/render_command.h>
|
||||
#include <renderer/renderer.h>
|
||||
#include <scene/scene.h>
|
||||
#include <utils/logger.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
Scene::Scene() { defaultCamera_ = makeRef<Camera>(); }
|
||||
|
||||
void Scene::setCamera(IntrusivePtr<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);
|
||||
}
|
||||
}
|
||||
|
||||
void Scene::setViewportSize(const Size &size) {
|
||||
setViewportSize(size.width, size.height);
|
||||
}
|
||||
|
||||
void Scene::renderScene(Renderer &renderer) {
|
||||
if (!visible())
|
||||
return;
|
||||
|
||||
// Begin frame with background color
|
||||
renderer.beginFrame(backgroundColor_);
|
||||
renderContent(renderer);
|
||||
renderer.endFrame();
|
||||
}
|
||||
|
||||
void Scene::renderContent(Renderer &renderer) {
|
||||
if (!visible())
|
||||
return;
|
||||
|
||||
// 在渲染前批量更新所有节点的世界变换
|
||||
batchUpdateTransforms();
|
||||
|
||||
Camera *activeCam = getActiveCamera();
|
||||
if (activeCam) {
|
||||
renderer.setViewProjection(activeCam->getViewProjectionMatrix());
|
||||
}
|
||||
|
||||
renderer.beginSpriteBatch();
|
||||
render(renderer);
|
||||
renderer.endSpriteBatch();
|
||||
}
|
||||
|
||||
void Scene::updateScene(float dt) {
|
||||
if (!paused_) {
|
||||
update(dt);
|
||||
}
|
||||
}
|
||||
|
||||
void Scene::onEnter() { Node::onEnter(); }
|
||||
|
||||
void Scene::onExit() { Node::onExit(); }
|
||||
|
||||
void Scene::collectRenderCommands(std::vector<RenderCommand> &commands,
|
||||
int parentZOrder) {
|
||||
if (!visible())
|
||||
return;
|
||||
|
||||
// 从场景的子节点开始收集渲染命令
|
||||
Node::collectRenderCommands(commands, parentZOrder);
|
||||
}
|
||||
|
||||
IntrusivePtr<Scene> Scene::create() { return makeRef<Scene>(); }
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -1,364 +0,0 @@
|
|||
#include <algorithm>
|
||||
#include <app/application.h>
|
||||
#include <renderer/render_command.h>
|
||||
#include <renderer/renderer.h>
|
||||
#include <platform/input.h>
|
||||
#include <scene/scene_manager.h>
|
||||
#include <utils/logger.h>
|
||||
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
namespace {
|
||||
|
||||
Node *hitTestTopmost(const IntrusivePtr<Node> &node, const Vec2 &worldPos) {
|
||||
if (!node || !node->visible()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::vector<IntrusivePtr<Node>> children = node->children();
|
||||
std::stable_sort(children.begin(), children.end(),
|
||||
[](const IntrusivePtr<Node> &a, const IntrusivePtr<Node> &b) {
|
||||
return a->zOrder() < b->zOrder();
|
||||
});
|
||||
|
||||
for (auto it = children.rbegin(); it != children.rend(); ++it) {
|
||||
if (Node *hit = hitTestTopmost(*it, worldPos)) {
|
||||
return hit;
|
||||
}
|
||||
}
|
||||
|
||||
if (node->getEventDispatcher().getTotalListenerCount() == 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Rect bounds = node->boundingBox();
|
||||
if (!bounds.empty() && bounds.containsPoint(worldPos)) {
|
||||
return node.get();
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void dispatchToNode(Node *node, Event &event) {
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
node->getEventDispatcher().dispatch(event);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
SceneManager &SceneManager::getInstance() {
|
||||
static SceneManager instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void SceneManager::runWithScene(IntrusivePtr<Scene> scene) {
|
||||
if (!scene) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!sceneStack_.empty()) {
|
||||
E2D_LOG_WARN("SceneManager: runWithScene should only be called once");
|
||||
return;
|
||||
}
|
||||
|
||||
scene->onEnter();
|
||||
scene->onAttachToScene(scene.get());
|
||||
sceneStack_.push(scene);
|
||||
}
|
||||
|
||||
void SceneManager::replaceScene(IntrusivePtr<Scene> scene) {
|
||||
if (!scene) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (sceneStack_.empty()) {
|
||||
runWithScene(scene);
|
||||
return;
|
||||
}
|
||||
|
||||
// Pop current scene
|
||||
auto oldScene = sceneStack_.top();
|
||||
oldScene->onExit();
|
||||
oldScene->onDetachFromScene();
|
||||
sceneStack_.pop();
|
||||
|
||||
// Push new scene
|
||||
scene->onEnter();
|
||||
scene->onAttachToScene(scene.get());
|
||||
sceneStack_.push(scene);
|
||||
}
|
||||
|
||||
void SceneManager::enterScene(IntrusivePtr<Scene> scene) {
|
||||
if (!scene) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (sceneStack_.empty()) {
|
||||
runWithScene(scene);
|
||||
} else {
|
||||
replaceScene(scene);
|
||||
}
|
||||
}
|
||||
|
||||
void SceneManager::pushScene(IntrusivePtr<Scene> scene) {
|
||||
if (!scene) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Pause current scene
|
||||
if (!sceneStack_.empty()) {
|
||||
sceneStack_.top()->pause();
|
||||
}
|
||||
|
||||
// Push new scene
|
||||
scene->onEnter();
|
||||
scene->onAttachToScene(scene.get());
|
||||
sceneStack_.push(scene);
|
||||
}
|
||||
|
||||
void SceneManager::popScene() {
|
||||
if (sceneStack_.size() <= 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto current = sceneStack_.top();
|
||||
current->onExit();
|
||||
current->onDetachFromScene();
|
||||
sceneStack_.pop();
|
||||
|
||||
// Resume previous scene
|
||||
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<IntrusivePtr<Scene>> tempStack;
|
||||
IntrusivePtr<Scene> target = nullptr;
|
||||
|
||||
while (!sceneStack_.empty()) {
|
||||
auto scene = sceneStack_.top();
|
||||
if (scene->name() == name) {
|
||||
target = scene;
|
||||
break;
|
||||
}
|
||||
scene->onExit();
|
||||
scene->onDetachFromScene();
|
||||
sceneStack_.pop();
|
||||
}
|
||||
|
||||
if (target) {
|
||||
target->resume();
|
||||
}
|
||||
}
|
||||
|
||||
IntrusivePtr<Scene> SceneManager::getCurrentScene() const {
|
||||
if (sceneStack_.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
return sceneStack_.top();
|
||||
}
|
||||
|
||||
IntrusivePtr<Scene> SceneManager::getPreviousScene() const {
|
||||
if (sceneStack_.size() < 2) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Copy stack to access second top
|
||||
auto tempStack = sceneStack_;
|
||||
tempStack.pop();
|
||||
return tempStack.top();
|
||||
}
|
||||
|
||||
IntrusivePtr<Scene> SceneManager::getRootScene() const {
|
||||
if (sceneStack_.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Copy stack to access bottom
|
||||
auto tempStack = sceneStack_;
|
||||
IntrusivePtr<Scene> root;
|
||||
while (!tempStack.empty()) {
|
||||
root = tempStack.top();
|
||||
tempStack.pop();
|
||||
}
|
||||
return root;
|
||||
}
|
||||
|
||||
IntrusivePtr<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->name() == 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()) {
|
||||
auto &scene = *sceneStack_.top();
|
||||
scene.updateScene(dt);
|
||||
dispatchPointerEvents(scene);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
renderer.endFrame();
|
||||
E2D_LOG_TRACE("SceneManager::render - endFrame");
|
||||
}
|
||||
|
||||
void SceneManager::collectRenderCommands(std::vector<RenderCommand> &commands) {
|
||||
if (!sceneStack_.empty()) {
|
||||
sceneStack_.top()->collectRenderCommands(commands, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void SceneManager::end() {
|
||||
while (!sceneStack_.empty()) {
|
||||
auto scene = sceneStack_.top();
|
||||
scene->onExit();
|
||||
scene->onDetachFromScene();
|
||||
sceneStack_.pop();
|
||||
}
|
||||
namedScenes_.clear();
|
||||
}
|
||||
|
||||
void SceneManager::purgeCachedScenes() { namedScenes_.clear(); }
|
||||
|
||||
void SceneManager::dispatchPointerEvents(Scene &scene) {
|
||||
auto &input = Application::instance().input();
|
||||
Vec2 screenPos = input.getMousePosition();
|
||||
|
||||
Vec2 worldPos = screenPos;
|
||||
if (auto *camera = scene.getActiveCamera()) {
|
||||
worldPos = camera->screenToWorld(screenPos);
|
||||
}
|
||||
|
||||
IntrusivePtr<Node> root = IntrusivePtr<Node>(&scene);
|
||||
Node *newHover = hitTestTopmost(root, worldPos);
|
||||
|
||||
if (newHover != hoverTarget_) {
|
||||
if (hoverTarget_) {
|
||||
Event evt;
|
||||
evt.type = EventType::UIHoverExit;
|
||||
evt.data = CustomEvent{0, hoverTarget_};
|
||||
dispatchToNode(hoverTarget_, evt);
|
||||
}
|
||||
hoverTarget_ = newHover;
|
||||
if (hoverTarget_) {
|
||||
Event evt;
|
||||
evt.type = EventType::UIHoverEnter;
|
||||
evt.data = CustomEvent{0, hoverTarget_};
|
||||
dispatchToNode(hoverTarget_, evt);
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasLastPointerWorld_) {
|
||||
lastPointerWorld_ = worldPos;
|
||||
hasLastPointerWorld_ = true;
|
||||
}
|
||||
|
||||
Vec2 delta = worldPos - lastPointerWorld_;
|
||||
if (hoverTarget_ && (delta.x != 0.0f || delta.y != 0.0f)) {
|
||||
Event evt = Event::createMouseMove(worldPos, delta);
|
||||
dispatchToNode(hoverTarget_, evt);
|
||||
}
|
||||
|
||||
float scrollDelta = input.getMouseScrollDelta();
|
||||
if (hoverTarget_ && scrollDelta != 0.0f) {
|
||||
Event evt = Event::createMouseScroll(Vec2(0.0f, scrollDelta), worldPos);
|
||||
dispatchToNode(hoverTarget_, evt);
|
||||
}
|
||||
|
||||
if (input.isMousePressed(MouseButton::Left)) {
|
||||
captureTarget_ = hoverTarget_;
|
||||
if (captureTarget_) {
|
||||
Event evt = Event::createMouseButtonPress(
|
||||
static_cast<int>(MouseButton::Left), 0, worldPos);
|
||||
dispatchToNode(captureTarget_, evt);
|
||||
|
||||
Event pressed;
|
||||
pressed.type = EventType::UIPressed;
|
||||
pressed.data = CustomEvent{0, captureTarget_};
|
||||
dispatchToNode(captureTarget_, pressed);
|
||||
}
|
||||
}
|
||||
|
||||
if (input.isMouseReleased(MouseButton::Left)) {
|
||||
Node *target = captureTarget_ ? captureTarget_ : hoverTarget_;
|
||||
if (target) {
|
||||
Event evt = Event::createMouseButtonRelease(
|
||||
static_cast<int>(MouseButton::Left), 0, worldPos);
|
||||
dispatchToNode(target, evt);
|
||||
|
||||
Event released;
|
||||
released.type = EventType::UIReleased;
|
||||
released.data = CustomEvent{0, target};
|
||||
dispatchToNode(target, released);
|
||||
}
|
||||
|
||||
if (captureTarget_ && captureTarget_ == hoverTarget_) {
|
||||
Event clicked;
|
||||
clicked.type = EventType::UIClicked;
|
||||
clicked.data = CustomEvent{0, captureTarget_};
|
||||
dispatchToNode(captureTarget_, clicked);
|
||||
}
|
||||
|
||||
captureTarget_ = nullptr;
|
||||
}
|
||||
|
||||
lastPointerWorld_ = worldPos;
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -1,340 +0,0 @@
|
|||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <renderer/render_command.h>
|
||||
#include <renderer/renderer.h>
|
||||
#include <scene/shape.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
ShapeNode::ShapeNode() = default;
|
||||
|
||||
IntrusivePtr<ShapeNode> ShapeNode::create() { return makeRef<ShapeNode>(); }
|
||||
|
||||
IntrusivePtr<ShapeNode> ShapeNode::createPoint(const Vec2 &pos, const Color &color) {
|
||||
auto node = makeRef<ShapeNode>();
|
||||
node->shapeType_ = ShapeType::Point;
|
||||
node->color_ = color;
|
||||
node->points_ = {pos};
|
||||
return node;
|
||||
}
|
||||
|
||||
IntrusivePtr<ShapeNode> ShapeNode::createLine(const Vec2 &start, const Vec2 &end,
|
||||
const Color &color, float width) {
|
||||
auto node = makeRef<ShapeNode>();
|
||||
node->shapeType_ = ShapeType::Line;
|
||||
node->color_ = color;
|
||||
node->lineWidth_ = width;
|
||||
node->points_ = {start, end};
|
||||
return node;
|
||||
}
|
||||
|
||||
IntrusivePtr<ShapeNode> ShapeNode::createRect(const Rect &rect, const Color &color,
|
||||
float width) {
|
||||
auto node = makeRef<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;
|
||||
}
|
||||
|
||||
IntrusivePtr<ShapeNode> ShapeNode::createFilledRect(const Rect &rect,
|
||||
const Color &color) {
|
||||
auto node = createRect(rect, color, 0);
|
||||
node->filled_ = true;
|
||||
return node;
|
||||
}
|
||||
|
||||
IntrusivePtr<ShapeNode> ShapeNode::createCircle(const Vec2 ¢er, float radius,
|
||||
const Color &color, int segments,
|
||||
float width) {
|
||||
auto node = makeRef<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;
|
||||
}
|
||||
|
||||
IntrusivePtr<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;
|
||||
}
|
||||
|
||||
IntrusivePtr<ShapeNode> ShapeNode::createTriangle(const Vec2 &p1, const Vec2 &p2,
|
||||
const Vec2 &p3, const Color &color,
|
||||
float width) {
|
||||
auto node = makeRef<ShapeNode>();
|
||||
node->shapeType_ = ShapeType::Triangle;
|
||||
node->color_ = color;
|
||||
node->lineWidth_ = width;
|
||||
node->filled_ = false;
|
||||
node->points_ = {p1, p2, p3};
|
||||
return node;
|
||||
}
|
||||
|
||||
IntrusivePtr<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;
|
||||
}
|
||||
|
||||
IntrusivePtr<ShapeNode> ShapeNode::createPolygon(const std::vector<Vec2> &points,
|
||||
const Color &color, float width) {
|
||||
auto node = makeRef<ShapeNode>();
|
||||
node->shapeType_ = ShapeType::Polygon;
|
||||
node->color_ = color;
|
||||
node->lineWidth_ = width;
|
||||
node->filled_ = false;
|
||||
node->points_ = points;
|
||||
return node;
|
||||
}
|
||||
|
||||
IntrusivePtr<ShapeNode> ShapeNode::createFilledPolygon(const std::vector<Vec2> &points,
|
||||
const Color &color) {
|
||||
auto node = createPolygon(points, color, 0);
|
||||
node->filled_ = true;
|
||||
return node;
|
||||
}
|
||||
|
||||
void ShapeNode::setPoints(const std::vector<Vec2> &points) { points_ = points; }
|
||||
|
||||
void ShapeNode::addPoint(const Vec2 &point) { points_.push_back(point); }
|
||||
|
||||
void ShapeNode::clearPoints() { points_.clear(); }
|
||||
|
||||
Rect ShapeNode::boundingBox() const {
|
||||
if (points_.empty()) {
|
||||
return Rect();
|
||||
}
|
||||
|
||||
Vec2 offset = pos();
|
||||
|
||||
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();
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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 = pos();
|
||||
|
||||
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) {
|
||||
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_);
|
||||
}
|
||||
}
|
||||
}
|
||||
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 = pos();
|
||||
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,163 +0,0 @@
|
|||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <graphics/texture.h>
|
||||
#include <renderer/render_command.h>
|
||||
#include <renderer/renderer.h>
|
||||
#include <scene/sprite.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
Sprite::Sprite() = default;
|
||||
|
||||
Sprite::Sprite(IntrusivePtr<Texture> texture) { setTexture(texture); }
|
||||
|
||||
void Sprite::setTexture(IntrusivePtr<Texture> texture) {
|
||||
texture_ = texture;
|
||||
if (texture_) {
|
||||
textureRect_ = Rect(0, 0, static_cast<float>(texture_->width()),
|
||||
static_cast<float>(texture_->height()));
|
||||
}
|
||||
}
|
||||
|
||||
void Sprite::setTextureRect(const Rect &rect) { textureRect_ = rect; }
|
||||
|
||||
void Sprite::setColor(const Color &color) { color_ = color; }
|
||||
|
||||
void Sprite::setFlipX(bool flip) { flipX_ = flip; }
|
||||
|
||||
void Sprite::setFlipY(bool flip) { flipY_ = flip; }
|
||||
|
||||
IntrusivePtr<Sprite> Sprite::create() { return makeRef<Sprite>(); }
|
||||
|
||||
IntrusivePtr<Sprite> Sprite::create(IntrusivePtr<Texture> texture) {
|
||||
return makeRef<Sprite>(texture);
|
||||
}
|
||||
|
||||
IntrusivePtr<Sprite> Sprite::create(IntrusivePtr<Texture> texture, const Rect &rect) {
|
||||
auto sprite = makeRef<Sprite>(texture);
|
||||
sprite->setTextureRect(rect);
|
||||
return sprite;
|
||||
}
|
||||
|
||||
Rect Sprite::boundingBox() const {
|
||||
if (!texture_ || !texture_->isValid()) {
|
||||
return Rect();
|
||||
}
|
||||
|
||||
float width = textureRect_.width();
|
||||
float height = textureRect_.height();
|
||||
|
||||
auto position = pos();
|
||||
auto anchorPt = anchor();
|
||||
auto scaleVal = scale();
|
||||
|
||||
float w = width * scaleVal.x;
|
||||
float h = height * scaleVal.y;
|
||||
float x0 = position.x - width * anchorPt.x * scaleVal.x;
|
||||
float y0 = position.y - height * anchorPt.y * scaleVal.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));
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
// 使用世界变换来获取最终的位置
|
||||
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 anchorPt = anchor();
|
||||
|
||||
// 锚点由 Renderer 在绘制时处理,这里只传递位置和尺寸
|
||||
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;
|
||||
}
|
||||
|
||||
// 从世界变换矩阵中提取旋转角度
|
||||
float worldRotation = std::atan2(worldTransform[0][1], worldTransform[0][0]);
|
||||
|
||||
renderer.drawSprite(*texture_, destRect, srcRect, color_, worldRotation,
|
||||
anchorPt);
|
||||
}
|
||||
|
||||
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 anchorPt = anchor();
|
||||
|
||||
// 锚点由 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, anchorPt, 0};
|
||||
|
||||
commands.push_back(std::move(cmd));
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
Loading…
Reference in New Issue