refactor: 移除图形渲染和场景管理相关代码

移除不再需要的图形渲染、纹理、字体、场景管理等模块代码,清理相关头文件引用。主要变更包括:
- 删除renderer、graphics、scene目录下的大部分文件
- 清理extra2d.h中的冗余头文件引用
- 简化Application类,移除渲染和场景管理相关功能
This commit is contained in:
ChestnutYueyue 2026-02-27 19:12:24 +08:00
parent d81f0c1e45
commit ea081b9dd3
42 changed files with 31 additions and 8151 deletions

View File

@ -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;

View File

@ -1,7 +1,6 @@
#pragma once
#include <core/intrusive_ptr.h>
#include <memory>
#include <string>
#include <unordered_map>
@ -11,21 +10,22 @@ 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();
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);
IntrusivePtr<Sound> getSound(const std::string &name);
void unloadSound(const std::string &name);
void unloadAllSounds();
void setMasterVolume(float volume);

View File

@ -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>

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 &center, float radius, const Color &color,
int segments, float width) override;
void fillCircle(const Vec2 &center, 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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 &center, float radius, const Color &color,
int segments = 32, float width = 1.0f) = 0;
virtual void fillCircle(const Vec2 &center, 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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 &center, float radius,
const Color &color, int segments = 32,
float width = 1.0f);
static IntrusivePtr<ShapeNode> createFilledCircle(const Vec2 &center, 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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 &center, 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 &center, 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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 &center, 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 &center, 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

View File

@ -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