Compare commits
2 Commits
9bf328b1dc
...
114c5254af
| Author | SHA1 | Date |
|---|---|---|
|
|
114c5254af | |
|
|
e5b13e8ec0 |
|
|
@ -6,6 +6,32 @@
|
||||||
// Core
|
// Core
|
||||||
#include <extra2d/core/color.h>
|
#include <extra2d/core/color.h>
|
||||||
#include <extra2d/core/math_types.h>
|
#include <extra2d/core/math_types.h>
|
||||||
|
<<<<<<< HEAD
|
||||||
|
#include <extra2d/core/string.h>
|
||||||
|
#include <extra2d/core/types.h>
|
||||||
|
|
||||||
|
// Window - SDL2 + OpenGL
|
||||||
|
#include <extra2d/window/window.h>
|
||||||
|
|
||||||
|
// Render (OpenGL 4.5)
|
||||||
|
#include <extra2d/render/buffer.h>
|
||||||
|
#include <extra2d/render/render_context.h>
|
||||||
|
#include <extra2d/render/render_device.h>
|
||||||
|
#include <extra2d/render/render_queue.h>
|
||||||
|
#include <extra2d/render/render_stats.h>
|
||||||
|
#include <extra2d/render/render_types.h>
|
||||||
|
#include <extra2d/render/shape_renderer.h>
|
||||||
|
#include <extra2d/render/sprite_renderer.h>
|
||||||
|
#include <extra2d/render/text_renderer.h>
|
||||||
|
#include <extra2d/render/vao.h>
|
||||||
|
|
||||||
|
// Scene
|
||||||
|
#include <extra2d/scene/node.h>
|
||||||
|
#include <extra2d/scene/scene.h>
|
||||||
|
#include <extra2d/scene/scene_manager.h>
|
||||||
|
#include <extra2d/scene/shape_node.h>
|
||||||
|
#include <extra2d/scene/sprite.h>
|
||||||
|
=======
|
||||||
#include <extra2d/core/module.h>
|
#include <extra2d/core/module.h>
|
||||||
#include <extra2d/core/registry.h>
|
#include <extra2d/core/registry.h>
|
||||||
#include <extra2d/core/types.h>
|
#include <extra2d/core/types.h>
|
||||||
|
|
@ -14,11 +40,31 @@
|
||||||
#include <extra2d/platform/glfw/glfw_window.h>
|
#include <extra2d/platform/glfw/glfw_window.h>
|
||||||
#include <extra2d/platform/keys.h>
|
#include <extra2d/platform/keys.h>
|
||||||
#include <extra2d/platform/window_module.h>
|
#include <extra2d/platform/window_module.h>
|
||||||
|
>>>>>>> 9bf328b1dca01df84a07724394abc9a238739869
|
||||||
|
|
||||||
// Event
|
// Event
|
||||||
#include <extra2d/event/event.h>
|
#include <extra2d/event/event.h>
|
||||||
#include <extra2d/event/event_dispatcher.h>
|
#include <extra2d/event/event_dispatcher.h>
|
||||||
#include <extra2d/event/event_queue.h>
|
#include <extra2d/event/event_queue.h>
|
||||||
|
<<<<<<< HEAD
|
||||||
|
#include <extra2d/event/input_codes.h>
|
||||||
|
|
||||||
|
// Audio
|
||||||
|
#include <extra2d/audio/audio_engine.h>
|
||||||
|
#include <extra2d/audio/sound.h>
|
||||||
|
|
||||||
|
// Utils
|
||||||
|
#include <extra2d/utils/data.h>
|
||||||
|
#include <extra2d/utils/logger.h>
|
||||||
|
#include <extra2d/utils/random.h>
|
||||||
|
#include <extra2d/utils/timer.h>
|
||||||
|
|
||||||
|
// Spatial
|
||||||
|
#include <extra2d/spatial/quadtree.h>
|
||||||
|
#include <extra2d/spatial/spatial_hash.h>
|
||||||
|
#include <extra2d/spatial/spatial_index.h>
|
||||||
|
#include <extra2d/spatial/spatial_manager.h>
|
||||||
|
=======
|
||||||
|
|
||||||
// Utils
|
// Utils
|
||||||
#include <extra2d/utils/random.h>
|
#include <extra2d/utils/random.h>
|
||||||
|
|
@ -38,6 +84,7 @@
|
||||||
#include <extra2d/asset/asset_pack.h>
|
#include <extra2d/asset/asset_pack.h>
|
||||||
#include <extra2d/asset/asset_types.h>
|
#include <extra2d/asset/asset_types.h>
|
||||||
#include <extra2d/asset/data_processor.h>
|
#include <extra2d/asset/data_processor.h>
|
||||||
|
>>>>>>> 9bf328b1dca01df84a07724394abc9a238739869
|
||||||
|
|
||||||
// Application
|
// Application
|
||||||
#include <extra2d/app/application.h>
|
#include <extra2d/app/application.h>
|
||||||
|
|
|
||||||
|
|
@ -1,49 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <extra2d/core/math_types.h>
|
|
||||||
#include <extra2d/core/types.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 getWidth() const { return width_; }
|
|
||||||
int getHeight() const { return height_; }
|
|
||||||
Size getSize() const {
|
|
||||||
return Size(static_cast<float>(width_), static_cast<float>(height_));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 获取原始数据
|
|
||||||
const std::vector<uint8_t> &getData() const { return data_; }
|
|
||||||
|
|
||||||
/// 检查遮罩是否有效
|
|
||||||
bool isValid() const { return !data_.empty() && width_ > 0 && height_ > 0; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
int width_ = 0;
|
|
||||||
int height_ = 0;
|
|
||||||
std::vector<uint8_t> data_; // Alpha值数组
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,93 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <extra2d/core/color.h>
|
|
||||||
#include <extra2d/core/math_types.h>
|
|
||||||
#include <extra2d/core/types.h>
|
|
||||||
#include <glm/mat4x4.hpp>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 2D 正交相机
|
|
||||||
// ============================================================================
|
|
||||||
class Camera {
|
|
||||||
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 getPosition() const { return position_; }
|
|
||||||
|
|
||||||
void setRotation(float degrees);
|
|
||||||
float getRotation() const { return rotation_; }
|
|
||||||
|
|
||||||
void setZoom(float zoom);
|
|
||||||
float getZoom() const { return zoom_; }
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 视口设置
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void setViewport(float left, float right, float bottom, float top);
|
|
||||||
void setViewport(const Rect &rect);
|
|
||||||
Rect getViewport() const;
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 矩阵获取
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
glm::mat4 getViewMatrix() const;
|
|
||||||
glm::mat4 getProjectionMatrix() const;
|
|
||||||
glm::mat4 getViewProjectionMatrix() const;
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 坐标转换
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
Vec2 screenToWorld(const Vec2 &screenPos) const;
|
|
||||||
Vec2 worldToScreen(const Vec2 &worldPos) const;
|
|
||||||
Vec2 screenToWorld(float x, float y) const;
|
|
||||||
Vec2 worldToScreen(float x, float y) const;
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 移动相机
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void move(const Vec2 &offset);
|
|
||||||
void move(float x, float y);
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 边界限制
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void setBounds(const Rect &bounds);
|
|
||||||
void clearBounds();
|
|
||||||
void clampToBounds();
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 快捷方法:看向某点
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void lookAt(const Vec2 &target);
|
|
||||||
|
|
||||||
private:
|
|
||||||
Vec2 position_ = Vec2::Zero();
|
|
||||||
float rotation_ = 0.0f;
|
|
||||||
float zoom_ = 1.0f;
|
|
||||||
|
|
||||||
float left_ = -1.0f;
|
|
||||||
float right_ = 1.0f;
|
|
||||||
float bottom_ = -1.0f;
|
|
||||||
float top_ = 1.0f;
|
|
||||||
|
|
||||||
Rect bounds_;
|
|
||||||
bool hasBounds_ = false;
|
|
||||||
|
|
||||||
mutable glm::mat4 viewMatrix_;
|
|
||||||
mutable glm::mat4 projMatrix_;
|
|
||||||
mutable glm::mat4 vpMatrix_;
|
|
||||||
mutable bool viewDirty_ = true;
|
|
||||||
mutable bool projDirty_ = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,50 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <extra2d/core/color.h>
|
|
||||||
#include <extra2d/core/math_types.h>
|
|
||||||
#include <extra2d/core/types.h>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 字形信息
|
|
||||||
// ============================================================================
|
|
||||||
struct Glyph {
|
|
||||||
float u0, v0; // 纹理坐标左下角
|
|
||||||
float u1, v1; // 纹理坐标右上角
|
|
||||||
float width; // 字形宽度(像素)
|
|
||||||
float height; // 字形高度(像素)
|
|
||||||
float bearingX; // 水平偏移
|
|
||||||
float bearingY; // 垂直偏移
|
|
||||||
float advance; // 前进距离
|
|
||||||
};
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 字体图集接口
|
|
||||||
// ============================================================================
|
|
||||||
class FontAtlas {
|
|
||||||
public:
|
|
||||||
virtual ~FontAtlas() = default;
|
|
||||||
|
|
||||||
// 获取字形信息
|
|
||||||
virtual const Glyph *getGlyph(char32_t codepoint) const = 0;
|
|
||||||
|
|
||||||
// 获取纹理
|
|
||||||
virtual class Texture *getTexture() const = 0;
|
|
||||||
|
|
||||||
// 获取字体大小
|
|
||||||
virtual int getFontSize() const = 0;
|
|
||||||
|
|
||||||
virtual float getAscent() const = 0;
|
|
||||||
virtual float getDescent() const = 0;
|
|
||||||
virtual float getLineGap() const = 0;
|
|
||||||
virtual float getLineHeight() const = 0;
|
|
||||||
|
|
||||||
// 计算文字尺寸
|
|
||||||
virtual Vec2 measureText(const std::string &text) = 0;
|
|
||||||
|
|
||||||
// 是否支持 SDF 渲染
|
|
||||||
virtual bool isSDF() const = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <atomic>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// GPU 上下文状态管理器
|
|
||||||
// 用于跟踪 OpenGL/Vulkan 等 GPU 上下文的生命周期状态
|
|
||||||
// 确保在 GPU 资源析构时能安全地检查上下文是否有效
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
class GPUContext {
|
|
||||||
public:
|
|
||||||
/// 获取单例实例
|
|
||||||
static GPUContext& getInstance();
|
|
||||||
|
|
||||||
/// 标记 GPU 上下文为有效(在初始化完成后调用)
|
|
||||||
void markValid();
|
|
||||||
|
|
||||||
/// 标记 GPU 上下文为无效(在销毁前调用)
|
|
||||||
void markInvalid();
|
|
||||||
|
|
||||||
/// 检查 GPU 上下文是否有效
|
|
||||||
bool isValid() const;
|
|
||||||
|
|
||||||
private:
|
|
||||||
GPUContext() = default;
|
|
||||||
~GPUContext() = default;
|
|
||||||
GPUContext(const GPUContext&) = delete;
|
|
||||||
GPUContext& operator=(const GPUContext&) = delete;
|
|
||||||
|
|
||||||
std::atomic<bool> valid_{false};
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,67 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <extra2d/core/color.h>
|
|
||||||
#include <extra2d/core/math_types.h>
|
|
||||||
#include <extra2d/core/types.h>
|
|
||||||
#include <extra2d/graphics/font.h>
|
|
||||||
#include <extra2d/graphics/opengl/gl_texture.h>
|
|
||||||
#include <extra2d/graphics/texture.h>
|
|
||||||
#include <memory>
|
|
||||||
#include <stb/stb_rect_pack.h>
|
|
||||||
#include <stb/stb_truetype.h>
|
|
||||||
#include <unordered_map>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// OpenGL 字体图集实现 - 使用 stb_rect_pack 进行矩形打包
|
|
||||||
// ============================================================================
|
|
||||||
class GLFontAtlas : public FontAtlas {
|
|
||||||
public:
|
|
||||||
GLFontAtlas(const std::string &filepath, int fontSize, bool useSDF = false);
|
|
||||||
~GLFontAtlas();
|
|
||||||
|
|
||||||
// FontAtlas 接口实现
|
|
||||||
const Glyph *getGlyph(char32_t codepoint) const override;
|
|
||||||
Texture *getTexture() const override { return texture_.get(); }
|
|
||||||
int getFontSize() const override { return fontSize_; }
|
|
||||||
float getAscent() const override { return ascent_; }
|
|
||||||
float getDescent() const override { return descent_; }
|
|
||||||
float getLineGap() const override { return lineGap_; }
|
|
||||||
float getLineHeight() const override { return ascent_ - descent_ + lineGap_; }
|
|
||||||
Vec2 measureText(const std::string &text) override;
|
|
||||||
bool isSDF() const override { return useSDF_; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
// 图集配置 - 增大尺寸以支持更多字符
|
|
||||||
static constexpr int ATLAS_WIDTH = 1024;
|
|
||||||
static constexpr int ATLAS_HEIGHT = 1024;
|
|
||||||
static constexpr int PADDING = 2; // 字形之间的间距
|
|
||||||
|
|
||||||
int fontSize_;
|
|
||||||
bool useSDF_;
|
|
||||||
mutable std::unique_ptr<GLTexture> texture_;
|
|
||||||
mutable std::unordered_map<char32_t, Glyph> glyphs_;
|
|
||||||
|
|
||||||
// stb_rect_pack 上下文
|
|
||||||
mutable stbrp_context packContext_;
|
|
||||||
mutable std::vector<stbrp_node> packNodes_;
|
|
||||||
mutable int currentY_;
|
|
||||||
|
|
||||||
std::vector<unsigned char> fontData_;
|
|
||||||
stbtt_fontinfo fontInfo_;
|
|
||||||
float scale_;
|
|
||||||
float ascent_;
|
|
||||||
float descent_;
|
|
||||||
float lineGap_;
|
|
||||||
|
|
||||||
// 预分配字形位图缓冲区,避免每次动态分配
|
|
||||||
mutable std::vector<uint8_t> glyphBitmapCache_;
|
|
||||||
mutable std::vector<uint8_t> glyphRgbaCache_;
|
|
||||||
|
|
||||||
void createAtlas();
|
|
||||||
void cacheGlyph(char32_t codepoint) const;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,131 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <extra2d/graphics/opengl/gl_shader.h>
|
|
||||||
#include <extra2d/graphics/opengl/gl_sprite_batch.h>
|
|
||||||
#include <extra2d/graphics/render_backend.h>
|
|
||||||
|
|
||||||
#include <array>
|
|
||||||
#include <glad/glad.h>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
class Window;
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// OpenGL 渲染器实现
|
|
||||||
// ============================================================================
|
|
||||||
class GLRenderer : public RenderBackend {
|
|
||||||
public:
|
|
||||||
GLRenderer();
|
|
||||||
~GLRenderer() override;
|
|
||||||
|
|
||||||
// RenderBackend 接口实现
|
|
||||||
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;
|
|
||||||
|
|
||||||
Ptr<Texture> createTexture(int width, int height, const uint8_t *pixels,
|
|
||||||
int channels) override;
|
|
||||||
Ptr<Texture> loadTexture(const std::string &filepath) override;
|
|
||||||
|
|
||||||
void beginSpriteBatch() override;
|
|
||||||
void drawSprite(const Texture &texture, const Rect &destRect,
|
|
||||||
const Rect &srcRect, const Color &tint, float rotation,
|
|
||||||
const Vec2 &anchor) override;
|
|
||||||
void drawSprite(const Texture &texture, const Vec2 &position,
|
|
||||||
const Color &tint) override;
|
|
||||||
void endSpriteBatch() override;
|
|
||||||
|
|
||||||
void drawLine(const Vec2 &start, const Vec2 &end, const Color &color,
|
|
||||||
float width) override;
|
|
||||||
void drawRect(const Rect &rect, const Color &color, float width) override;
|
|
||||||
void fillRect(const Rect &rect, const Color &color) override;
|
|
||||||
void drawCircle(const Vec2 ¢er, float radius, const Color &color,
|
|
||||||
int segments, float width) override;
|
|
||||||
void fillCircle(const Vec2 ¢er, float radius, const Color &color,
|
|
||||||
int segments) override;
|
|
||||||
void drawTriangle(const Vec2 &p1, const Vec2 &p2, const Vec2 &p3,
|
|
||||||
const Color &color, float width) override;
|
|
||||||
void fillTriangle(const Vec2 &p1, const Vec2 &p2, const Vec2 &p3,
|
|
||||||
const Color &color) override;
|
|
||||||
void drawPolygon(const std::vector<Vec2> &points, const Color &color,
|
|
||||||
float width) override;
|
|
||||||
void fillPolygon(const std::vector<Vec2> &points,
|
|
||||||
const Color &color) override;
|
|
||||||
|
|
||||||
Ptr<FontAtlas> createFontAtlas(const std::string &filepath, int fontSize,
|
|
||||||
bool useSDF = false) override;
|
|
||||||
void drawText(const FontAtlas &font, const std::string &text,
|
|
||||||
const Vec2 &position, const Color &color) override;
|
|
||||||
void drawText(const FontAtlas &font, const std::string &text, float x,
|
|
||||||
float y, const Color &color) override;
|
|
||||||
|
|
||||||
Stats getStats() const override { return stats_; }
|
|
||||||
void resetStats() override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
// 形状批处理常量
|
|
||||||
static constexpr size_t MAX_CIRCLE_SEGMENTS = 128;
|
|
||||||
static constexpr size_t MAX_SHAPE_VERTICES = 8192; // 最大形状顶点数
|
|
||||||
static constexpr size_t MAX_LINE_VERTICES = 16384; // 最大线条顶点数
|
|
||||||
|
|
||||||
// 形状顶点结构(包含颜色)
|
|
||||||
struct ShapeVertex {
|
|
||||||
float x, y;
|
|
||||||
float r, g, b, a;
|
|
||||||
};
|
|
||||||
|
|
||||||
Window *window_;
|
|
||||||
GLSpriteBatch spriteBatch_;
|
|
||||||
GLShader shapeShader_;
|
|
||||||
|
|
||||||
GLuint shapeVao_;
|
|
||||||
GLuint shapeVbo_;
|
|
||||||
GLuint lineVao_; // 线条专用 VAO
|
|
||||||
GLuint lineVbo_; // 线条专用 VBO
|
|
||||||
|
|
||||||
glm::mat4 viewProjection_;
|
|
||||||
std::vector<glm::mat4> transformStack_;
|
|
||||||
Stats stats_;
|
|
||||||
bool vsync_;
|
|
||||||
|
|
||||||
// 形状批处理缓冲区(预分配,避免每帧内存分配)
|
|
||||||
std::array<ShapeVertex, MAX_SHAPE_VERTICES> shapeVertexCache_;
|
|
||||||
size_t shapeVertexCount_ = 0;
|
|
||||||
GLenum currentShapeMode_ = GL_TRIANGLES;
|
|
||||||
|
|
||||||
// 线条批处理缓冲区
|
|
||||||
std::array<ShapeVertex, MAX_LINE_VERTICES> lineVertexCache_;
|
|
||||||
size_t lineVertexCount_ = 0;
|
|
||||||
float currentLineWidth_ = 1.0f;
|
|
||||||
|
|
||||||
// OpenGL 状态缓存
|
|
||||||
BlendMode cachedBlendMode_ = BlendMode::None;
|
|
||||||
bool blendEnabled_ = false;
|
|
||||||
int cachedViewportX_ = 0;
|
|
||||||
int cachedViewportY_ = 0;
|
|
||||||
int cachedViewportWidth_ = 0;
|
|
||||||
int cachedViewportHeight_ = 0;
|
|
||||||
|
|
||||||
void initShapeRendering();
|
|
||||||
void flushShapeBatch();
|
|
||||||
void flushLineBatch();
|
|
||||||
void addShapeVertex(float x, float y, const Color &color);
|
|
||||||
void addLineVertex(float x, float y, const Color &color);
|
|
||||||
void submitShapeBatch(GLenum mode);
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,55 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <glad/glad.h>
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <unordered_map>
|
|
||||||
#include <glm/mat4x4.hpp>
|
|
||||||
#include <glm/vec2.hpp>
|
|
||||||
#include <glm/vec3.hpp>
|
|
||||||
#include <glm/vec4.hpp>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// OpenGL Shader 程序
|
|
||||||
// ============================================================================
|
|
||||||
class GLShader {
|
|
||||||
public:
|
|
||||||
GLShader();
|
|
||||||
~GLShader();
|
|
||||||
|
|
||||||
// 从源码编译
|
|
||||||
bool compileFromSource(const char* vertexSource, const char* fragmentSource);
|
|
||||||
|
|
||||||
// 从文件加载并编译
|
|
||||||
bool compileFromFile(const std::string& vertexPath, const std::string& fragmentPath);
|
|
||||||
|
|
||||||
// 使用/激活
|
|
||||||
void bind() const;
|
|
||||||
void unbind() const;
|
|
||||||
|
|
||||||
// Uniform 设置
|
|
||||||
void setBool(const std::string& name, bool value);
|
|
||||||
void setInt(const std::string& name, int value);
|
|
||||||
void setFloat(const std::string& name, float value);
|
|
||||||
void setVec2(const std::string& name, const glm::vec2& value);
|
|
||||||
void setVec3(const std::string& name, const glm::vec3& value);
|
|
||||||
void setVec4(const std::string& name, const glm::vec4& value);
|
|
||||||
void setMat4(const std::string& name, const glm::mat4& value);
|
|
||||||
|
|
||||||
// 获取程序 ID
|
|
||||||
GLuint getProgramID() const { return programID_; }
|
|
||||||
|
|
||||||
// 检查是否有效
|
|
||||||
bool isValid() const { return programID_ != 0; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
GLuint programID_;
|
|
||||||
std::unordered_map<std::string, GLint> uniformCache_;
|
|
||||||
|
|
||||||
GLuint compileShader(GLenum type, const char* source);
|
|
||||||
GLint getUniformLocation(const std::string& name);
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,97 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <extra2d/core/color.h>
|
|
||||||
#include <extra2d/core/math_types.h>
|
|
||||||
#include <extra2d/core/types.h>
|
|
||||||
#include <extra2d/graphics/opengl/gl_shader.h>
|
|
||||||
#include <extra2d/graphics/texture.h>
|
|
||||||
#include <glm/mat4x4.hpp>
|
|
||||||
#include <vector>
|
|
||||||
#include <array>
|
|
||||||
|
|
||||||
#include <glad/glad.h>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// OpenGL 精灵批渲染器 - 优化版本
|
|
||||||
// ============================================================================
|
|
||||||
class GLSpriteBatch {
|
|
||||||
public:
|
|
||||||
static constexpr size_t MAX_SPRITES = 10000;
|
|
||||||
static constexpr size_t VERTICES_PER_SPRITE = 4;
|
|
||||||
static constexpr size_t INDICES_PER_SPRITE = 6;
|
|
||||||
static constexpr size_t MAX_VERTICES = MAX_SPRITES * VERTICES_PER_SPRITE;
|
|
||||||
static constexpr size_t MAX_INDICES = MAX_SPRITES * INDICES_PER_SPRITE;
|
|
||||||
|
|
||||||
struct Vertex {
|
|
||||||
glm::vec2 position;
|
|
||||||
glm::vec2 texCoord;
|
|
||||||
glm::vec4 color;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct SpriteData {
|
|
||||||
glm::vec2 position;
|
|
||||||
glm::vec2 size;
|
|
||||||
glm::vec2 texCoordMin;
|
|
||||||
glm::vec2 texCoordMax;
|
|
||||||
glm::vec4 color;
|
|
||||||
float rotation;
|
|
||||||
glm::vec2 anchor;
|
|
||||||
bool isSDF = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
GLSpriteBatch();
|
|
||||||
~GLSpriteBatch();
|
|
||||||
|
|
||||||
bool init();
|
|
||||||
void shutdown();
|
|
||||||
|
|
||||||
void begin(const glm::mat4 &viewProjection);
|
|
||||||
void draw(const Texture &texture, const SpriteData &data);
|
|
||||||
void end();
|
|
||||||
|
|
||||||
// 批量绘制接口 - 用于自动批处理
|
|
||||||
void drawBatch(const Texture& texture, const std::vector<SpriteData>& sprites);
|
|
||||||
|
|
||||||
// 立即绘制(不缓存)
|
|
||||||
void drawImmediate(const Texture& texture, const SpriteData& data);
|
|
||||||
|
|
||||||
// 统计
|
|
||||||
uint32_t getDrawCallCount() const { return drawCallCount_; }
|
|
||||||
uint32_t getSpriteCount() const { return spriteCount_; }
|
|
||||||
uint32_t getBatchCount() const { return batchCount_; }
|
|
||||||
|
|
||||||
// 检查是否需要刷新
|
|
||||||
bool needsFlush(const Texture& texture, bool isSDF) const;
|
|
||||||
|
|
||||||
private:
|
|
||||||
GLuint vao_;
|
|
||||||
GLuint vbo_;
|
|
||||||
GLuint ibo_;
|
|
||||||
GLShader shader_;
|
|
||||||
|
|
||||||
// 使用固定大小数组减少内存分配
|
|
||||||
std::array<Vertex, MAX_VERTICES> vertexBuffer_;
|
|
||||||
size_t vertexCount_;
|
|
||||||
|
|
||||||
const Texture *currentTexture_;
|
|
||||||
bool currentIsSDF_;
|
|
||||||
glm::mat4 viewProjection_;
|
|
||||||
|
|
||||||
// 缓存上一帧的 viewProjection,避免重复设置
|
|
||||||
glm::mat4 cachedViewProjection_;
|
|
||||||
bool viewProjectionDirty_ = true;
|
|
||||||
|
|
||||||
uint32_t drawCallCount_;
|
|
||||||
uint32_t spriteCount_;
|
|
||||||
uint32_t batchCount_;
|
|
||||||
|
|
||||||
void flush();
|
|
||||||
void setupShader();
|
|
||||||
|
|
||||||
// 添加顶点到缓冲区
|
|
||||||
void addVertices(const SpriteData& data);
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,72 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <extra2d/graphics/texture.h>
|
|
||||||
#include <extra2d/graphics/alpha_mask.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 getWidth() const override { return width_; }
|
|
||||||
int getHeight() const override { return height_; }
|
|
||||||
Size getSize() 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 Ptr<Texture> create(int width, int height, PixelFormat format);
|
|
||||||
|
|
||||||
// 加载压缩纹理(KTX/DDS 格式)
|
|
||||||
bool loadCompressed(const std::string& filepath);
|
|
||||||
|
|
||||||
// OpenGL 特定
|
|
||||||
GLuint getTextureID() const { return textureID_; }
|
|
||||||
void bind(unsigned int slot = 0) const;
|
|
||||||
void unbind() const;
|
|
||||||
|
|
||||||
// 获取纹理数据大小(字节),用于 VRAM 跟踪
|
|
||||||
size_t getDataSize() const { return dataSize_; }
|
|
||||||
|
|
||||||
// Alpha 遮罩
|
|
||||||
bool hasAlphaMask() const { return alphaMask_ != nullptr && alphaMask_->isValid(); }
|
|
||||||
const AlphaMask* getAlphaMask() const { return alphaMask_.get(); }
|
|
||||||
void generateAlphaMask(); // 从当前纹理数据生成遮罩
|
|
||||||
|
|
||||||
private:
|
|
||||||
GLuint textureID_;
|
|
||||||
int width_;
|
|
||||||
int height_;
|
|
||||||
int channels_;
|
|
||||||
PixelFormat format_;
|
|
||||||
size_t dataSize_;
|
|
||||||
|
|
||||||
// 原始像素数据(用于生成遮罩)
|
|
||||||
std::vector<uint8_t> pixelData_;
|
|
||||||
std::unique_ptr<AlphaMask> alphaMask_;
|
|
||||||
|
|
||||||
void createTexture(const uint8_t* pixels);
|
|
||||||
|
|
||||||
// KTX 文件加载
|
|
||||||
bool loadKTX(const std::string& filepath);
|
|
||||||
// DDS 文件加载
|
|
||||||
bool loadDDS(const std::string& filepath);
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,223 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <extra2d/core/color.h>
|
|
||||||
#include <extra2d/core/math_types.h>
|
|
||||||
#include <extra2d/core/types.h>
|
|
||||||
#include <extra2d/graphics/texture.h>
|
|
||||||
#include <glm/mat4x4.hpp>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <variant>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// 前向声明
|
|
||||||
class Texture;
|
|
||||||
class FontAtlas;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 渲染命令类型枚举
|
|
||||||
*/
|
|
||||||
enum class RenderCommandType : uint8_t {
|
|
||||||
None = 0,
|
|
||||||
Sprite, // 精灵绘制
|
|
||||||
Line, // 线条绘制
|
|
||||||
Rect, // 矩形绘制
|
|
||||||
FilledRect, // 填充矩形
|
|
||||||
Circle, // 圆形绘制
|
|
||||||
FilledCircle, // 填充圆形
|
|
||||||
Triangle, // 三角形绘制
|
|
||||||
FilledTriangle, // 填充三角形
|
|
||||||
Polygon, // 多边形绘制
|
|
||||||
FilledPolygon, // 填充多边形
|
|
||||||
Text, // 文本绘制
|
|
||||||
Custom // 自定义绘制
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 精灵渲染命令数据
|
|
||||||
*/
|
|
||||||
struct SpriteCommandData {
|
|
||||||
const Texture* texture;
|
|
||||||
Rect destRect;
|
|
||||||
Rect srcRect;
|
|
||||||
Color tint;
|
|
||||||
float rotation;
|
|
||||||
Vec2 anchor;
|
|
||||||
uint32_t sortKey; // 用于自动排序的键值
|
|
||||||
|
|
||||||
SpriteCommandData()
|
|
||||||
: texture(nullptr), destRect(), srcRect(), tint(Colors::White),
|
|
||||||
rotation(0.0f), anchor(0.0f, 0.0f), sortKey(0) {}
|
|
||||||
SpriteCommandData(const Texture* tex, const Rect& dest, const Rect& src,
|
|
||||||
const Color& t, float rot, const Vec2& anc, uint32_t key)
|
|
||||||
: texture(tex), destRect(dest), srcRect(src), tint(t),
|
|
||||||
rotation(rot), anchor(anc), sortKey(key) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 线条渲染命令数据
|
|
||||||
*/
|
|
||||||
struct LineCommandData {
|
|
||||||
Vec2 start;
|
|
||||||
Vec2 end;
|
|
||||||
Color color;
|
|
||||||
float width;
|
|
||||||
|
|
||||||
LineCommandData() : start(), end(), color(Colors::White), width(1.0f) {}
|
|
||||||
LineCommandData(const Vec2& s, const Vec2& e, const Color& c, float w)
|
|
||||||
: start(s), end(e), color(c), width(w) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 矩形渲染命令数据
|
|
||||||
*/
|
|
||||||
struct RectCommandData {
|
|
||||||
Rect rect;
|
|
||||||
Color color;
|
|
||||||
float width;
|
|
||||||
bool filled;
|
|
||||||
|
|
||||||
RectCommandData() : rect(), color(Colors::White), width(1.0f), filled(false) {}
|
|
||||||
RectCommandData(const Rect& r, const Color& c, float w, bool f)
|
|
||||||
: rect(r), color(c), width(w), filled(f) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 圆形渲染命令数据
|
|
||||||
*/
|
|
||||||
struct CircleCommandData {
|
|
||||||
Vec2 center;
|
|
||||||
float radius;
|
|
||||||
Color color;
|
|
||||||
int segments;
|
|
||||||
float width;
|
|
||||||
bool filled;
|
|
||||||
|
|
||||||
CircleCommandData() : center(), radius(0.0f), color(Colors::White),
|
|
||||||
segments(32), width(1.0f), filled(false) {}
|
|
||||||
CircleCommandData(const Vec2& c, float r, const Color& col, int seg, float w, bool f)
|
|
||||||
: center(c), radius(r), color(col), segments(seg), width(w), filled(f) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 三角形渲染命令数据
|
|
||||||
*/
|
|
||||||
struct TriangleCommandData {
|
|
||||||
Vec2 p1, p2, p3;
|
|
||||||
Color color;
|
|
||||||
float width;
|
|
||||||
bool filled;
|
|
||||||
|
|
||||||
TriangleCommandData() : p1(), p2(), p3(), color(Colors::White),
|
|
||||||
width(1.0f), filled(false) {}
|
|
||||||
TriangleCommandData(const Vec2& a, const Vec2& b, const Vec2& c, const Color& col, float w, bool f)
|
|
||||||
: p1(a), p2(b), p3(c), color(col), width(w), filled(f) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 多边形渲染命令数据
|
|
||||||
*/
|
|
||||||
struct PolygonCommandData {
|
|
||||||
std::vector<Vec2> points;
|
|
||||||
Color color;
|
|
||||||
float width;
|
|
||||||
bool filled;
|
|
||||||
|
|
||||||
PolygonCommandData() : color(Colors::White), width(1.0f), filled(false) {}
|
|
||||||
PolygonCommandData(std::vector<Vec2> pts, const Color& col, float w, bool f)
|
|
||||||
: points(std::move(pts)), color(col), width(w), filled(f) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 文本渲染命令数据
|
|
||||||
*/
|
|
||||||
struct TextCommandData {
|
|
||||||
const FontAtlas* font;
|
|
||||||
std::string text;
|
|
||||||
Vec2 position;
|
|
||||||
Color color;
|
|
||||||
|
|
||||||
TextCommandData() : font(nullptr), text(), position(), color(Colors::White) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 统一渲染命令结构
|
|
||||||
* 使用 variant 存储不同类型的命令数据,减少内存分配
|
|
||||||
*/
|
|
||||||
struct RenderCommand {
|
|
||||||
RenderCommandType type;
|
|
||||||
uint32_t layer; // 渲染层级,用于排序
|
|
||||||
uint32_t order; // 提交顺序,保证同层级内稳定排序
|
|
||||||
glm::mat4 transform; // 变换矩阵
|
|
||||||
|
|
||||||
// 使用 variant 存储具体数据
|
|
||||||
std::variant<
|
|
||||||
SpriteCommandData,
|
|
||||||
LineCommandData,
|
|
||||||
RectCommandData,
|
|
||||||
CircleCommandData,
|
|
||||||
TriangleCommandData,
|
|
||||||
PolygonCommandData,
|
|
||||||
TextCommandData
|
|
||||||
> data;
|
|
||||||
|
|
||||||
RenderCommand() : type(RenderCommandType::None), layer(0), order(0),
|
|
||||||
transform(1.0f) {}
|
|
||||||
|
|
||||||
// 便捷构造函数
|
|
||||||
static RenderCommand makeSprite(const Texture* tex, const Rect& dest,
|
|
||||||
const Rect& src, const Color& tint,
|
|
||||||
float rot = 0.0f, const Vec2& anc = Vec2(0, 0),
|
|
||||||
uint32_t lyr = 0);
|
|
||||||
static RenderCommand makeLine(const Vec2& s, const Vec2& e, const Color& c,
|
|
||||||
float w = 1.0f, uint32_t lyr = 0);
|
|
||||||
static RenderCommand makeRect(const Rect& r, const Color& c,
|
|
||||||
float w = 1.0f, bool fill = false, uint32_t lyr = 0);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 渲染命令缓冲区
|
|
||||||
* 用于收集和批量处理渲染命令
|
|
||||||
*/
|
|
||||||
class RenderCommandBuffer {
|
|
||||||
public:
|
|
||||||
static constexpr size_t INITIAL_CAPACITY = 1024;
|
|
||||||
static constexpr size_t MAX_CAPACITY = 65536;
|
|
||||||
|
|
||||||
RenderCommandBuffer();
|
|
||||||
~RenderCommandBuffer();
|
|
||||||
|
|
||||||
// 添加渲染命令
|
|
||||||
void addCommand(const RenderCommand& cmd);
|
|
||||||
void addCommand(RenderCommand&& cmd);
|
|
||||||
|
|
||||||
// 批量添加(预留空间后使用)
|
|
||||||
RenderCommand& emplaceCommand();
|
|
||||||
|
|
||||||
// 排序命令(按纹理、层级等)
|
|
||||||
void sortCommands();
|
|
||||||
|
|
||||||
// 清空缓冲区
|
|
||||||
void clear();
|
|
||||||
|
|
||||||
// 获取命令列表
|
|
||||||
const std::vector<RenderCommand>& getCommands() const { return commands_; }
|
|
||||||
std::vector<RenderCommand>& getCommands() { return commands_; }
|
|
||||||
|
|
||||||
// 统计
|
|
||||||
size_t size() const { return commands_.size(); }
|
|
||||||
bool empty() const { return commands_.empty(); }
|
|
||||||
size_t capacity() const { return commands_.capacity(); }
|
|
||||||
|
|
||||||
// 预分配空间
|
|
||||||
void reserve(size_t capacity);
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::vector<RenderCommand> commands_;
|
|
||||||
uint32_t nextOrder_;
|
|
||||||
|
|
||||||
// 排序比较函数
|
|
||||||
static bool compareCommands(const RenderCommand& a, const RenderCommand& b);
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,333 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <extra2d/core/color.h>
|
|
||||||
#include <extra2d/core/types.h>
|
|
||||||
#include <extra2d/graphics/opengl/gl_texture.h>
|
|
||||||
#include <extra2d/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:
|
|
||||||
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(Ptr<Texture> texture, bool hasDepth = false);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 销毁渲染目标
|
|
||||||
*/
|
|
||||||
void destroy();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 关闭渲染目标(destroy的别名,兼容旧API)
|
|
||||||
*/
|
|
||||||
void shutdown() { destroy(); }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查是否有效
|
|
||||||
*/
|
|
||||||
bool isValid() const { return fbo_ != 0; }
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 尺寸和格式
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
|
|
||||||
int getWidth() const { return width_; }
|
|
||||||
int getHeight() const { return height_; }
|
|
||||||
Vec2 getSize() 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 获取颜色纹理
|
|
||||||
*/
|
|
||||||
Ptr<Texture> getColorTexture() const { return colorTexture_; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取深度纹理(如果有)
|
|
||||||
*/
|
|
||||||
Ptr<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 Ptr<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; // 渲染缓冲对象(深度/模板)
|
|
||||||
|
|
||||||
Ptr<Texture> colorTexture_; // 颜色纹理
|
|
||||||
Ptr<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 创建新的渲染目标
|
|
||||||
*/
|
|
||||||
Ptr<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;
|
|
||||||
|
|
||||||
Ptr<RenderTarget> defaultRenderTarget_;
|
|
||||||
std::vector<Ptr<RenderTarget>> renderTargets_;
|
|
||||||
bool initialized_ = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 便捷宏
|
|
||||||
// ============================================================================
|
|
||||||
#define E2D_RENDER_TARGET_STACK() ::extra2d::RenderTargetStack::getInstance()
|
|
||||||
#define E2D_RENDER_TARGET_MANAGER() \
|
|
||||||
::extra2d::RenderTargetManager::getInstance()
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,51 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <extra2d/core/color.h>
|
|
||||||
#include <extra2d/core/math_types.h>
|
|
||||||
#include <extra2d/core/types.h>
|
|
||||||
#include <glm/mat4x4.hpp>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
class Window;
|
|
||||||
class Texture;
|
|
||||||
|
|
||||||
enum class BlendMode {
|
|
||||||
None,
|
|
||||||
Alpha,
|
|
||||||
Additive,
|
|
||||||
Multiply
|
|
||||||
};
|
|
||||||
|
|
||||||
class Renderer {
|
|
||||||
public:
|
|
||||||
Renderer();
|
|
||||||
~Renderer();
|
|
||||||
|
|
||||||
bool init(Window* window);
|
|
||||||
void shutdown();
|
|
||||||
|
|
||||||
void beginFrame(const Color& clearColor);
|
|
||||||
void endFrame();
|
|
||||||
void setViewport(int x, int y, int width, int height);
|
|
||||||
|
|
||||||
void setBlendMode(BlendMode mode);
|
|
||||||
void setViewProjection(const glm::mat4& matrix);
|
|
||||||
|
|
||||||
void pushTransform(const glm::mat4& transform);
|
|
||||||
void popTransform();
|
|
||||||
|
|
||||||
void drawRect(const Rect& rect, const Color& color);
|
|
||||||
void fillRect(const Rect& rect, const Color& color);
|
|
||||||
void drawLine(const Vec2& start, const Vec2& end, const Color& color, float width = 1.0f);
|
|
||||||
void drawCircle(const Vec2& center, float radius, const Color& color, int segments = 32);
|
|
||||||
|
|
||||||
private:
|
|
||||||
Window* window_;
|
|
||||||
glm::mat4 viewProjMatrix_;
|
|
||||||
std::vector<glm::mat4> transformStack_;
|
|
||||||
BlendMode currentBlendMode_;
|
|
||||||
bool initialized_;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,319 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <extra2d/graphics/opengl/gl_shader.h>
|
|
||||||
#include <extra2d/core/types.h>
|
|
||||||
#include <extra2d/core/color.h>
|
|
||||||
#include <glm/vec4.hpp>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
struct WaterParams {
|
|
||||||
float waveSpeed = 1.0f;
|
|
||||||
float waveAmplitude = 0.02f;
|
|
||||||
float waveFrequency = 4.0f;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct OutlineParams {
|
|
||||||
Color color = Colors::Black;
|
|
||||||
float thickness = 2.0f;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct DistortionParams {
|
|
||||||
float distortionAmount = 0.02f;
|
|
||||||
float timeScale = 1.0f;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct PixelateParams {
|
|
||||||
float pixelSize = 8.0f;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct InvertParams {
|
|
||||||
float strength = 1.0f;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct GrayscaleParams {
|
|
||||||
float intensity = 1.0f;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct BlurParams {
|
|
||||||
float radius = 5.0f;
|
|
||||||
};
|
|
||||||
|
|
||||||
namespace ShaderSource {
|
|
||||||
|
|
||||||
static const char* StandardVert = R"(
|
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
layout(location = 0) in vec2 a_position;
|
|
||||||
layout(location = 1) in vec2 a_texCoord;
|
|
||||||
layout(location = 2) in vec4 a_color;
|
|
||||||
|
|
||||||
uniform mat4 u_viewProjection;
|
|
||||||
uniform mat4 u_model;
|
|
||||||
|
|
||||||
out vec2 v_texCoord;
|
|
||||||
out vec4 v_color;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
gl_Position = u_viewProjection * u_model * vec4(a_position, 0.0, 1.0);
|
|
||||||
v_texCoord = a_texCoord;
|
|
||||||
v_color = a_color;
|
|
||||||
}
|
|
||||||
)";
|
|
||||||
|
|
||||||
static const char* StandardFrag = R"(
|
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
in vec2 v_texCoord;
|
|
||||||
in vec4 v_color;
|
|
||||||
|
|
||||||
uniform sampler2D u_texture;
|
|
||||||
uniform float u_opacity;
|
|
||||||
|
|
||||||
out vec4 fragColor;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
vec4 texColor = texture(u_texture, v_texCoord);
|
|
||||||
fragColor = texColor * v_color;
|
|
||||||
fragColor.a *= u_opacity;
|
|
||||||
|
|
||||||
if (fragColor.a < 0.01) {
|
|
||||||
discard;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)";
|
|
||||||
|
|
||||||
static const char* WaterFrag = R"(
|
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
in vec2 v_texCoord;
|
|
||||||
in vec4 v_color;
|
|
||||||
|
|
||||||
uniform sampler2D u_texture;
|
|
||||||
uniform float u_waveSpeed;
|
|
||||||
uniform float u_waveAmplitude;
|
|
||||||
uniform float u_waveFrequency;
|
|
||||||
uniform float u_time;
|
|
||||||
|
|
||||||
out vec4 fragColor;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
vec2 uv = v_texCoord;
|
|
||||||
|
|
||||||
// 水波纹效果
|
|
||||||
float wave = sin(uv.y * u_waveFrequency + u_time * u_waveSpeed) * u_waveAmplitude;
|
|
||||||
uv.x += wave;
|
|
||||||
|
|
||||||
vec4 texColor = texture(u_texture, uv);
|
|
||||||
fragColor = texColor * v_color;
|
|
||||||
|
|
||||||
if (fragColor.a < 0.01) {
|
|
||||||
discard;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)";
|
|
||||||
|
|
||||||
static const char* OutlineFrag = R"(
|
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
in vec2 v_texCoord;
|
|
||||||
in vec4 v_color;
|
|
||||||
|
|
||||||
uniform sampler2D u_texture;
|
|
||||||
uniform vec4 u_outlineColor;
|
|
||||||
uniform float u_thickness;
|
|
||||||
uniform vec2 u_textureSize;
|
|
||||||
|
|
||||||
out vec4 fragColor;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
vec4 color = texture(u_texture, v_texCoord);
|
|
||||||
|
|
||||||
// 简单的描边检测
|
|
||||||
float alpha = 0.0;
|
|
||||||
vec2 offset = u_thickness / u_textureSize;
|
|
||||||
|
|
||||||
alpha += texture(u_texture, v_texCoord + vec2(-offset.x, 0.0)).a;
|
|
||||||
alpha += texture(u_texture, v_texCoord + vec2(offset.x, 0.0)).a;
|
|
||||||
alpha += texture(u_texture, v_texCoord + vec2(0.0, -offset.y)).a;
|
|
||||||
alpha += texture(u_texture, v_texCoord + vec2(0.0, offset.y)).a;
|
|
||||||
|
|
||||||
if (color.a < 0.1 && alpha > 0.0) {
|
|
||||||
fragColor = u_outlineColor;
|
|
||||||
} else {
|
|
||||||
fragColor = color;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fragColor.a < 0.01) {
|
|
||||||
discard;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)";
|
|
||||||
|
|
||||||
static const char* DistortionFrag = R"(
|
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
in vec2 v_texCoord;
|
|
||||||
in vec4 v_color;
|
|
||||||
|
|
||||||
uniform sampler2D u_texture;
|
|
||||||
uniform float u_distortionAmount;
|
|
||||||
uniform float u_time;
|
|
||||||
uniform float u_timeScale;
|
|
||||||
|
|
||||||
out vec4 fragColor;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
vec2 uv = v_texCoord;
|
|
||||||
|
|
||||||
// 扭曲效果
|
|
||||||
float t = u_time * u_timeScale;
|
|
||||||
float dx = sin(uv.y * 10.0 + t) * u_distortionAmount;
|
|
||||||
float dy = cos(uv.x * 10.0 + t) * u_distortionAmount;
|
|
||||||
uv += vec2(dx, dy);
|
|
||||||
|
|
||||||
vec4 texColor = texture(u_texture, uv);
|
|
||||||
fragColor = texColor * v_color;
|
|
||||||
|
|
||||||
if (fragColor.a < 0.01) {
|
|
||||||
discard;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)";
|
|
||||||
|
|
||||||
static const char* PixelateFrag = R"(
|
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
in vec2 v_texCoord;
|
|
||||||
in vec4 v_color;
|
|
||||||
|
|
||||||
uniform sampler2D u_texture;
|
|
||||||
uniform float u_pixelSize;
|
|
||||||
uniform vec2 u_textureSize;
|
|
||||||
uniform float u_opacity;
|
|
||||||
|
|
||||||
out vec4 fragColor;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
vec2 pixel = u_pixelSize / u_textureSize;
|
|
||||||
vec2 uv = floor(v_texCoord / pixel) * pixel + pixel * 0.5;
|
|
||||||
|
|
||||||
vec4 texColor = texture(u_texture, uv);
|
|
||||||
fragColor = texColor * v_color;
|
|
||||||
fragColor.a *= u_opacity;
|
|
||||||
|
|
||||||
if (fragColor.a < 0.01) {
|
|
||||||
discard;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)";
|
|
||||||
|
|
||||||
static const char* InvertFrag = R"(
|
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
in vec2 v_texCoord;
|
|
||||||
in vec4 v_color;
|
|
||||||
|
|
||||||
uniform sampler2D u_texture;
|
|
||||||
uniform float u_strength;
|
|
||||||
uniform float u_opacity;
|
|
||||||
|
|
||||||
out vec4 fragColor;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
vec4 texColor = texture(u_texture, v_texCoord) * v_color;
|
|
||||||
vec3 inverted = vec3(1.0) - texColor.rgb;
|
|
||||||
texColor.rgb = mix(texColor.rgb, inverted, u_strength);
|
|
||||||
|
|
||||||
fragColor = texColor;
|
|
||||||
fragColor.a *= u_opacity;
|
|
||||||
|
|
||||||
if (fragColor.a < 0.01) {
|
|
||||||
discard;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)";
|
|
||||||
|
|
||||||
static const char* GrayscaleFrag = R"(
|
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
in vec2 v_texCoord;
|
|
||||||
in vec4 v_color;
|
|
||||||
|
|
||||||
uniform sampler2D u_texture;
|
|
||||||
uniform float u_intensity;
|
|
||||||
uniform float u_opacity;
|
|
||||||
|
|
||||||
out vec4 fragColor;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
vec4 texColor = texture(u_texture, v_texCoord) * v_color;
|
|
||||||
|
|
||||||
float gray = dot(texColor.rgb, vec3(0.299, 0.587, 0.114));
|
|
||||||
texColor.rgb = mix(texColor.rgb, vec3(gray), u_intensity);
|
|
||||||
|
|
||||||
fragColor = texColor;
|
|
||||||
fragColor.a *= u_opacity;
|
|
||||||
|
|
||||||
if (fragColor.a < 0.01) {
|
|
||||||
discard;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)";
|
|
||||||
|
|
||||||
static const char* BlurFrag = R"(
|
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
in vec2 v_texCoord;
|
|
||||||
in vec4 v_color;
|
|
||||||
|
|
||||||
uniform sampler2D u_texture;
|
|
||||||
uniform float u_radius;
|
|
||||||
uniform vec2 u_textureSize;
|
|
||||||
uniform float u_opacity;
|
|
||||||
|
|
||||||
out vec4 fragColor;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
vec2 texel = u_radius / u_textureSize;
|
|
||||||
|
|
||||||
vec4 sum = vec4(0.0);
|
|
||||||
sum += texture(u_texture, v_texCoord + texel * vec2(-1.0, -1.0));
|
|
||||||
sum += texture(u_texture, v_texCoord + texel * vec2( 0.0, -1.0));
|
|
||||||
sum += texture(u_texture, v_texCoord + texel * vec2( 1.0, -1.0));
|
|
||||||
sum += texture(u_texture, v_texCoord + texel * vec2(-1.0, 0.0));
|
|
||||||
sum += texture(u_texture, v_texCoord + texel * vec2( 0.0, 0.0));
|
|
||||||
sum += texture(u_texture, v_texCoord + texel * vec2( 1.0, 0.0));
|
|
||||||
sum += texture(u_texture, v_texCoord + texel * vec2(-1.0, 1.0));
|
|
||||||
sum += texture(u_texture, v_texCoord + texel * vec2( 0.0, 1.0));
|
|
||||||
sum += texture(u_texture, v_texCoord + texel * vec2( 1.0, 1.0));
|
|
||||||
|
|
||||||
vec4 texColor = sum / 9.0;
|
|
||||||
fragColor = texColor * v_color;
|
|
||||||
fragColor.a *= u_opacity;
|
|
||||||
|
|
||||||
if (fragColor.a < 0.01) {
|
|
||||||
discard;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)";
|
|
||||||
|
|
||||||
} // namespace ShaderSource
|
|
||||||
|
|
||||||
class ShaderPreset {
|
|
||||||
public:
|
|
||||||
static Ptr<GLShader> Water(const WaterParams& params);
|
|
||||||
static Ptr<GLShader> Outline(const OutlineParams& params);
|
|
||||||
static Ptr<GLShader> Distortion(const DistortionParams& params);
|
|
||||||
static Ptr<GLShader> Pixelate(const PixelateParams& params);
|
|
||||||
static Ptr<GLShader> Invert(const InvertParams& params);
|
|
||||||
static Ptr<GLShader> Grayscale(const GrayscaleParams& params);
|
|
||||||
static Ptr<GLShader> Blur(const BlurParams& params);
|
|
||||||
|
|
||||||
static Ptr<GLShader> GrayscaleOutline(const GrayscaleParams& grayParams,
|
|
||||||
const OutlineParams& outlineParams);
|
|
||||||
static Ptr<GLShader> PixelateInvert(const PixelateParams& pixParams,
|
|
||||||
const InvertParams& invParams);
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,179 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <extra2d/core/color.h>
|
|
||||||
#include <extra2d/core/types.h>
|
|
||||||
#include <extra2d/graphics/opengl/gl_shader.h>
|
|
||||||
#include <functional>
|
|
||||||
#include <string>
|
|
||||||
#include <unordered_map>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// Shader参数绑定回调
|
|
||||||
// ============================================================================
|
|
||||||
using ShaderBindCallback = std::function<void(GLShader &)>;
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// Shader系统 - 管理所有Shader的加载、缓存和热重载
|
|
||||||
// ============================================================================
|
|
||||||
class ShaderSystem {
|
|
||||||
public:
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 单例访问
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
static ShaderSystem &getInstance();
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 初始化和关闭
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
bool init();
|
|
||||||
void shutdown();
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// Shader加载
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 从文件加载Shader
|
|
||||||
* @param name Shader名称(用于缓存)
|
|
||||||
* @param vertPath 顶点着色器文件路径
|
|
||||||
* @param fragPath 片段着色器文件路径
|
|
||||||
* @return 加载的Shader,失败返回nullptr
|
|
||||||
*/
|
|
||||||
Ptr<GLShader> loadFromFile(const std::string &name,
|
|
||||||
const std::string &vertPath,
|
|
||||||
const std::string &fragPath);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 从源码字符串加载Shader
|
|
||||||
* @param name Shader名称(用于缓存)
|
|
||||||
* @param vertSource 顶点着色器源码
|
|
||||||
* @param fragSource 片段着色器源码
|
|
||||||
* @return 加载的Shader,失败返回nullptr
|
|
||||||
*/
|
|
||||||
Ptr<GLShader> loadFromSource(const std::string &name,
|
|
||||||
const std::string &vertSource,
|
|
||||||
const std::string &fragSource);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 从已编译的程序获取Shader
|
|
||||||
* @param name Shader名称
|
|
||||||
* @return 缓存的Shader,不存在返回nullptr
|
|
||||||
*/
|
|
||||||
Ptr<GLShader> get(const std::string &name);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查Shader是否存在
|
|
||||||
*/
|
|
||||||
bool has(const std::string &name) const;
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// Shader移除
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void remove(const std::string &name);
|
|
||||||
void clear();
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 热重载支持
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 启用/禁用文件监视
|
|
||||||
*/
|
|
||||||
void setFileWatching(bool enable);
|
|
||||||
bool isFileWatching() const { return fileWatching_; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 更新文件监视(在主循环中调用)
|
|
||||||
*/
|
|
||||||
void updateFileWatching();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 重载指定Shader
|
|
||||||
*/
|
|
||||||
bool reload(const std::string &name);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 重载所有Shader
|
|
||||||
*/
|
|
||||||
void reloadAll();
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 内置Shader获取
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
Ptr<GLShader> getBuiltinSpriteShader();
|
|
||||||
Ptr<GLShader> getBuiltinParticleShader();
|
|
||||||
Ptr<GLShader> getBuiltinPostProcessShader();
|
|
||||||
Ptr<GLShader> getBuiltinShapeShader();
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 工具方法
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 从文件读取文本内容
|
|
||||||
*/
|
|
||||||
static std::string readFile(const std::string &filepath);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取Shader文件的最后修改时间
|
|
||||||
*/
|
|
||||||
static uint64_t getFileModifiedTime(const std::string &filepath);
|
|
||||||
|
|
||||||
private:
|
|
||||||
ShaderSystem() = default;
|
|
||||||
~ShaderSystem() = default;
|
|
||||||
ShaderSystem(const ShaderSystem &) = delete;
|
|
||||||
ShaderSystem &operator=(const ShaderSystem &) = delete;
|
|
||||||
|
|
||||||
struct ShaderInfo {
|
|
||||||
Ptr<GLShader> shader;
|
|
||||||
std::string vertPath;
|
|
||||||
std::string fragPath;
|
|
||||||
uint64_t vertModifiedTime;
|
|
||||||
uint64_t fragModifiedTime;
|
|
||||||
bool isBuiltin;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::unordered_map<std::string, ShaderInfo> shaders_;
|
|
||||||
bool fileWatching_ = false;
|
|
||||||
float watchTimer_ = 0.0f;
|
|
||||||
static constexpr float WATCH_INTERVAL = 1.0f; // 检查间隔(秒)
|
|
||||||
|
|
||||||
// 内置Shader缓存
|
|
||||||
Ptr<GLShader> builtinSpriteShader_;
|
|
||||||
Ptr<GLShader> builtinParticleShader_;
|
|
||||||
Ptr<GLShader> builtinPostProcessShader_;
|
|
||||||
Ptr<GLShader> builtinShapeShader_;
|
|
||||||
|
|
||||||
bool loadBuiltinShaders();
|
|
||||||
void checkAndReload();
|
|
||||||
};
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// Shader参数包装器 - 简化Uniform设置
|
|
||||||
// ============================================================================
|
|
||||||
class ShaderParams {
|
|
||||||
public:
|
|
||||||
explicit ShaderParams(GLShader &shader);
|
|
||||||
|
|
||||||
ShaderParams &setBool(const std::string &name, bool value);
|
|
||||||
ShaderParams &setInt(const std::string &name, int value);
|
|
||||||
ShaderParams &setFloat(const std::string &name, float value);
|
|
||||||
ShaderParams &setVec2(const std::string &name, const glm::vec2 &value);
|
|
||||||
ShaderParams &setVec3(const std::string &name, const glm::vec3 &value);
|
|
||||||
ShaderParams &setVec4(const std::string &name, const glm::vec4 &value);
|
|
||||||
ShaderParams &setMat4(const std::string &name, const glm::mat4 &value);
|
|
||||||
ShaderParams &setColor(const std::string &name, const Color &color);
|
|
||||||
|
|
||||||
private:
|
|
||||||
GLShader &shader_;
|
|
||||||
};
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 便捷宏
|
|
||||||
// ============================================================================
|
|
||||||
#define E2D_SHADER_SYSTEM() ::extra2d::ShaderSystem::getInstance()
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,64 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <extra2d/core/types.h>
|
|
||||||
#include <extra2d/core/math_types.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:
|
|
||||||
virtual ~Texture() = default;
|
|
||||||
|
|
||||||
// 获取尺寸
|
|
||||||
virtual int getWidth() const = 0;
|
|
||||||
virtual int getHeight() const = 0;
|
|
||||||
virtual Size getSize() const = 0;
|
|
||||||
|
|
||||||
// 获取通道数
|
|
||||||
virtual int getChannels() const = 0;
|
|
||||||
|
|
||||||
// 获取像素格式
|
|
||||||
virtual PixelFormat getFormat() const = 0;
|
|
||||||
|
|
||||||
// 获取原始句柄(用于底层渲染)
|
|
||||||
virtual void* getNativeHandle() const = 0;
|
|
||||||
|
|
||||||
// 是否有效
|
|
||||||
virtual bool isValid() const = 0;
|
|
||||||
|
|
||||||
// 设置过滤模式
|
|
||||||
virtual void setFilter(bool linear) = 0;
|
|
||||||
|
|
||||||
// 设置环绕模式
|
|
||||||
virtual void setWrap(bool repeat) = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,184 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <extra2d/core/color.h>
|
|
||||||
#include <extra2d/core/math_types.h>
|
|
||||||
#include <extra2d/core/types.h>
|
|
||||||
#include <extra2d/graphics/texture.h>
|
|
||||||
#include <extra2d/graphics/opengl/gl_texture.h>
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
#include <unordered_map>
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 纹理图集 - 自动将小纹理合并到大图集以减少 DrawCall
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 图集中的单个纹理条目
|
|
||||||
*/
|
|
||||||
struct AtlasEntry {
|
|
||||||
std::string name; // 原始纹理名称/路径
|
|
||||||
Rect uvRect; // 在图集中的 UV 坐标范围
|
|
||||||
Vec2 originalSize; // 原始纹理尺寸
|
|
||||||
uint32_t padding; // 边距(用于避免纹理 bleeding)
|
|
||||||
|
|
||||||
AtlasEntry() : uvRect(), originalSize(), padding(2) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 纹理图集页面
|
|
||||||
* 当单个图集放不下时,创建多个页面
|
|
||||||
*/
|
|
||||||
class TextureAtlasPage {
|
|
||||||
public:
|
|
||||||
static constexpr int DEFAULT_SIZE = 2048;
|
|
||||||
static constexpr int MAX_SIZE = 4096;
|
|
||||||
static constexpr int MIN_TEXTURE_SIZE = 32; // 小于此大小的纹理才考虑合并
|
|
||||||
static constexpr int PADDING = 2; // 纹理间边距
|
|
||||||
|
|
||||||
TextureAtlasPage(int width = DEFAULT_SIZE, int height = DEFAULT_SIZE);
|
|
||||||
~TextureAtlasPage();
|
|
||||||
|
|
||||||
// 尝试添加纹理到图集
|
|
||||||
// 返回是否成功,如果成功则输出 uvRect
|
|
||||||
bool tryAddTexture(const std::string& name, int texWidth, int texHeight,
|
|
||||||
const uint8_t* pixels, Rect& outUvRect);
|
|
||||||
|
|
||||||
// 获取图集纹理
|
|
||||||
Ptr<Texture> getTexture() const { return texture_; }
|
|
||||||
|
|
||||||
// 获取条目
|
|
||||||
const AtlasEntry* getEntry(const std::string& name) const;
|
|
||||||
|
|
||||||
// 获取使用率
|
|
||||||
float getUsageRatio() const;
|
|
||||||
|
|
||||||
// 获取尺寸
|
|
||||||
int getWidth() const { return width_; }
|
|
||||||
int getHeight() const { return height_; }
|
|
||||||
|
|
||||||
// 是否已满
|
|
||||||
bool isFull() const { return isFull_; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
int width_, height_;
|
|
||||||
Ptr<Texture> texture_;
|
|
||||||
std::unordered_map<std::string, AtlasEntry> entries_;
|
|
||||||
|
|
||||||
// 矩形打包数据
|
|
||||||
struct PackNode {
|
|
||||||
int x, y, width, height;
|
|
||||||
bool used;
|
|
||||||
std::unique_ptr<PackNode> left;
|
|
||||||
std::unique_ptr<PackNode> right;
|
|
||||||
|
|
||||||
PackNode(int x_, int y_, int w, int h)
|
|
||||||
: x(x_), y(y_), width(w), height(h), used(false) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
std::unique_ptr<PackNode> root_;
|
|
||||||
bool isFull_;
|
|
||||||
int usedArea_;
|
|
||||||
|
|
||||||
// 递归插入
|
|
||||||
PackNode* insert(PackNode* node, int width, int height);
|
|
||||||
void writePixels(int x, int y, int w, int h, const uint8_t* pixels);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 纹理图集管理器
|
|
||||||
* 自动管理多个图集页面,提供统一的纹理查询接口
|
|
||||||
*/
|
|
||||||
class TextureAtlas {
|
|
||||||
public:
|
|
||||||
TextureAtlas();
|
|
||||||
~TextureAtlas();
|
|
||||||
|
|
||||||
// 初始化
|
|
||||||
void init(int pageSize = TextureAtlasPage::DEFAULT_SIZE);
|
|
||||||
|
|
||||||
// 添加纹理到图集
|
|
||||||
// 如果纹理太大,返回 false,应该作为独立纹理加载
|
|
||||||
bool addTexture(const std::string& name, int width, int height,
|
|
||||||
const uint8_t* pixels);
|
|
||||||
|
|
||||||
// 查询纹理是否在图集中
|
|
||||||
bool contains(const std::string& name) const;
|
|
||||||
|
|
||||||
// 获取纹理在图集中的信息
|
|
||||||
// 返回图集纹理和 UV 坐标
|
|
||||||
const Texture* getAtlasTexture(const std::string& name) const;
|
|
||||||
Rect getUVRect(const std::string& name) const;
|
|
||||||
|
|
||||||
// 获取原始纹理尺寸
|
|
||||||
Vec2 getOriginalSize(const std::string& name) const;
|
|
||||||
|
|
||||||
// 获取所有图集页面
|
|
||||||
const std::vector<std::unique_ptr<TextureAtlasPage>>& getPages() const { return pages_; }
|
|
||||||
|
|
||||||
// 获取总使用率
|
|
||||||
float getTotalUsageRatio() const;
|
|
||||||
|
|
||||||
// 清空所有图集
|
|
||||||
void clear();
|
|
||||||
|
|
||||||
// 设置是否启用自动图集
|
|
||||||
void setEnabled(bool enabled) { enabled_ = enabled; }
|
|
||||||
bool isEnabled() const { return enabled_; }
|
|
||||||
|
|
||||||
// 设置纹理大小阈值(小于此大小的纹理才进入图集)
|
|
||||||
void setSizeThreshold(int threshold) { sizeThreshold_ = threshold; }
|
|
||||||
int getSizeThreshold() const { return sizeThreshold_; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::vector<std::unique_ptr<TextureAtlasPage>> pages_;
|
|
||||||
std::unordered_map<std::string, TextureAtlasPage*> entryToPage_;
|
|
||||||
|
|
||||||
int pageSize_;
|
|
||||||
int sizeThreshold_;
|
|
||||||
bool enabled_;
|
|
||||||
bool initialized_;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 全局图集管理器(单例)
|
|
||||||
*/
|
|
||||||
class TextureAtlasManager {
|
|
||||||
public:
|
|
||||||
static TextureAtlasManager& getInstance();
|
|
||||||
|
|
||||||
// 获取主图集
|
|
||||||
TextureAtlas& getAtlas() { return atlas_; }
|
|
||||||
|
|
||||||
// 快捷方法
|
|
||||||
bool addTexture(const std::string& name, int width, int height,
|
|
||||||
const uint8_t* pixels) {
|
|
||||||
return atlas_.addTexture(name, width, height, pixels);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool contains(const std::string& name) const {
|
|
||||||
return atlas_.contains(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
const Texture* getAtlasTexture(const std::string& name) const {
|
|
||||||
return atlas_.getAtlasTexture(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
Rect getUVRect(const std::string& name) const {
|
|
||||||
return atlas_.getUVRect(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
TextureAtlasManager() = default;
|
|
||||||
~TextureAtlasManager() = default;
|
|
||||||
|
|
||||||
TextureAtlasManager(const TextureAtlasManager&) = delete;
|
|
||||||
TextureAtlasManager& operator=(const TextureAtlasManager&) = delete;
|
|
||||||
|
|
||||||
TextureAtlas atlas_;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,62 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cstddef>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <mutex>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// VRAM 管理器 - 跟踪显存使用情况
|
|
||||||
// ============================================================================
|
|
||||||
class VRAMManager {
|
|
||||||
public:
|
|
||||||
static VRAMManager& getInstance();
|
|
||||||
|
|
||||||
// 纹理显存跟踪
|
|
||||||
void allocTexture(size_t size);
|
|
||||||
void freeTexture(size_t size);
|
|
||||||
|
|
||||||
// VBO/FBO 显存跟踪
|
|
||||||
void allocBuffer(size_t size);
|
|
||||||
void freeBuffer(size_t size);
|
|
||||||
|
|
||||||
// 查询显存使用情况
|
|
||||||
size_t getUsedVRAM() const;
|
|
||||||
size_t getTextureVRAM() const;
|
|
||||||
size_t getBufferVRAM() const;
|
|
||||||
size_t getAvailableVRAM() const;
|
|
||||||
|
|
||||||
// 显存预算管理
|
|
||||||
void setVRAMBudget(size_t budget);
|
|
||||||
size_t getVRAMBudget() const;
|
|
||||||
bool isOverBudget() const;
|
|
||||||
|
|
||||||
// 统计信息
|
|
||||||
void printStats() const;
|
|
||||||
|
|
||||||
// 重置计数器
|
|
||||||
void reset();
|
|
||||||
|
|
||||||
private:
|
|
||||||
VRAMManager();
|
|
||||||
~VRAMManager() = default;
|
|
||||||
VRAMManager(const VRAMManager&) = delete;
|
|
||||||
VRAMManager& operator=(const VRAMManager&) = delete;
|
|
||||||
|
|
||||||
mutable std::mutex mutex_;
|
|
||||||
|
|
||||||
size_t textureVRAM_;
|
|
||||||
size_t bufferVRAM_;
|
|
||||||
size_t vramBudget_;
|
|
||||||
|
|
||||||
// 统计
|
|
||||||
uint32_t textureAllocCount_;
|
|
||||||
uint32_t textureFreeCount_;
|
|
||||||
uint32_t bufferAllocCount_;
|
|
||||||
uint32_t bufferFreeCount_;
|
|
||||||
size_t peakTextureVRAM_;
|
|
||||||
size_t peakBufferVRAM_;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -0,0 +1,161 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
#include <extra2d/render/render_types.h>
|
||||||
|
#include <glad/glad.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief GPU 缓冲区类
|
||||||
|
*
|
||||||
|
* 使用 OpenGL 4.5 DSA API 管理缓冲区资源
|
||||||
|
*/
|
||||||
|
class Buffer {
|
||||||
|
public:
|
||||||
|
Buffer();
|
||||||
|
~Buffer();
|
||||||
|
|
||||||
|
Buffer(const Buffer &) = delete;
|
||||||
|
Buffer &operator=(const Buffer &) = delete;
|
||||||
|
|
||||||
|
Buffer(Buffer &&other) noexcept;
|
||||||
|
Buffer &operator=(Buffer &&other) noexcept;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 创建缓冲区
|
||||||
|
* @param type 缓冲区类型
|
||||||
|
* @param usage 使用方式
|
||||||
|
* @param size 数据大小 (字节)
|
||||||
|
* @param data 初始数据 (可为 nullptr)
|
||||||
|
* @return 创建成功返回 true
|
||||||
|
*/
|
||||||
|
bool create(BufferType type, BufferUsage usage, size_t size,
|
||||||
|
const void *data = nullptr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 销毁缓冲区
|
||||||
|
*/
|
||||||
|
void destroy();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查缓冲区是否有效
|
||||||
|
*/
|
||||||
|
bool isValid() const { return id_ != 0; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取缓冲区 ID
|
||||||
|
*/
|
||||||
|
GLuint id() const { return id_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取缓冲区类型
|
||||||
|
*/
|
||||||
|
BufferType type() const { return type_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取缓冲区大小
|
||||||
|
*/
|
||||||
|
size_t size() const { return size_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 更新缓冲区数据
|
||||||
|
* @param offset 偏移量 (字节)
|
||||||
|
* @param size 数据大小 (字节)
|
||||||
|
* @param data 数据指针
|
||||||
|
* @return 更新成功返回 true
|
||||||
|
*/
|
||||||
|
bool update(size_t offset, size_t size, const void *data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 映射缓冲区到内存
|
||||||
|
* @param access 访问方式 (GL_READ_ONLY, GL_WRITE_ONLY, GL_READ_WRITE)
|
||||||
|
* @return 映射的内存指针
|
||||||
|
*/
|
||||||
|
void *map(GLenum access = GL_WRITE_ONLY);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 解除缓冲区映射
|
||||||
|
* @return 成功返回 true
|
||||||
|
*/
|
||||||
|
bool unmap();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 绑定到指定绑定点
|
||||||
|
* @param binding 绑定点索引 (用于 Uniform Buffer)
|
||||||
|
*/
|
||||||
|
void bindBase(uint32 binding);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 绑定到指定绑定点 (带偏移)
|
||||||
|
* @param binding 绑定点索引
|
||||||
|
* @param offset 偏移量
|
||||||
|
* @param size 大小
|
||||||
|
*/
|
||||||
|
void bindRange(uint32 binding, size_t offset, size_t size);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 绑定到当前上下文
|
||||||
|
*/
|
||||||
|
void bind();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 解除绑定
|
||||||
|
*/
|
||||||
|
void unbind();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 分配存储空间 (不初始化数据)
|
||||||
|
*/
|
||||||
|
bool allocate(size_t size);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置缓冲区数据 (重新分配)
|
||||||
|
*/
|
||||||
|
bool setData(const void *data, size_t size);
|
||||||
|
|
||||||
|
private:
|
||||||
|
GLuint id_ = 0;
|
||||||
|
BufferType type_ = BufferType::Vertex;
|
||||||
|
BufferUsage usage_ = BufferUsage::Static;
|
||||||
|
size_t size_ = 0;
|
||||||
|
bool mapped_ = false;
|
||||||
|
|
||||||
|
GLenum glTarget() const;
|
||||||
|
GLenum glUsage() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 顶点缓冲区
|
||||||
|
*/
|
||||||
|
class VertexBuffer : public Buffer {
|
||||||
|
public:
|
||||||
|
VertexBuffer() : Buffer() {}
|
||||||
|
bool create(BufferUsage usage, size_t size, const void *data = nullptr) {
|
||||||
|
return Buffer::create(BufferType::Vertex, usage, size, data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 索引缓冲区
|
||||||
|
*/
|
||||||
|
class IndexBuffer : public Buffer {
|
||||||
|
public:
|
||||||
|
IndexBuffer() : Buffer() {}
|
||||||
|
bool create(BufferUsage usage, size_t size, const void *data = nullptr) {
|
||||||
|
return Buffer::create(BufferType::Index, usage, size, data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Uniform 缓冲区
|
||||||
|
*/
|
||||||
|
class UniformBuffer : public Buffer {
|
||||||
|
public:
|
||||||
|
UniformBuffer() : Buffer() {}
|
||||||
|
bool create(BufferUsage usage, size_t size, const void *data = nullptr) {
|
||||||
|
return Buffer::create(BufferType::Uniform, usage, size, data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,164 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/render/buffer.h>
|
||||||
|
#include <extra2d/render/render_device.h>
|
||||||
|
#include <extra2d/render/render_queue.h>
|
||||||
|
#include <extra2d/render/render_types.h>
|
||||||
|
#include <extra2d/render/vao.h>
|
||||||
|
#include <glad/glad.h>
|
||||||
|
#include <stack>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 渲染上下文
|
||||||
|
*
|
||||||
|
* 管理渲染状态和帧流程
|
||||||
|
*/
|
||||||
|
class RenderContext {
|
||||||
|
public:
|
||||||
|
static RenderContext &instance();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 初始化渲染上下文
|
||||||
|
*/
|
||||||
|
bool init();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 关闭渲染上下文
|
||||||
|
*/
|
||||||
|
void shutdown();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查是否已初始化
|
||||||
|
*/
|
||||||
|
bool isInitialized() const { return initialized_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 开始帧渲染
|
||||||
|
*/
|
||||||
|
void beginFrame();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 结束帧渲染
|
||||||
|
*/
|
||||||
|
void endFrame();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取渲染队列
|
||||||
|
*/
|
||||||
|
RenderQueue &queue() { return queue_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 提交渲染命令
|
||||||
|
*/
|
||||||
|
void submit(const RenderCommand &cmd);
|
||||||
|
void submit(RenderCommand &&cmd);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 执行所有渲染命令
|
||||||
|
*/
|
||||||
|
void flush();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置视口
|
||||||
|
*/
|
||||||
|
void setViewport(int32 x, int32 y, int32 width, int32 height);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置视口 (使用 Viewport 结构)
|
||||||
|
*/
|
||||||
|
void setViewport(const Viewport &viewport);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取当前视口
|
||||||
|
*/
|
||||||
|
const Viewport &viewport() const { return viewport_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置裁剪矩形
|
||||||
|
*/
|
||||||
|
void setScissor(const ScissorRect &rect);
|
||||||
|
void setScissor(int32 x, int32 y, int32 width, int32 height);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取当前裁剪矩形
|
||||||
|
*/
|
||||||
|
const ScissorRect &scissor() const { return scissor_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 推送视口状态
|
||||||
|
*/
|
||||||
|
void pushViewport();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 弹出视口状态
|
||||||
|
*/
|
||||||
|
void popViewport();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 推送裁剪状态
|
||||||
|
*/
|
||||||
|
void pushScissor();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 弹出裁剪状态
|
||||||
|
*/
|
||||||
|
void popScissor();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置清除颜色
|
||||||
|
*/
|
||||||
|
void setClearColor(const Color &color);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 清除缓冲区
|
||||||
|
*/
|
||||||
|
void clear(bool color = true, bool depth = true, bool stencil = false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取渲染统计
|
||||||
|
*/
|
||||||
|
const RenderStats &stats() const { return stats_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 重置渲染统计
|
||||||
|
*/
|
||||||
|
void resetStats();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取渲染设备
|
||||||
|
*/
|
||||||
|
RenderDevice &device() { return device_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
RenderContext() = default;
|
||||||
|
~RenderContext() = default;
|
||||||
|
RenderContext(const RenderContext &) = delete;
|
||||||
|
RenderContext &operator=(const RenderContext &) = delete;
|
||||||
|
|
||||||
|
void executeCommand(const RenderCommand &cmd);
|
||||||
|
|
||||||
|
bool initialized_ = false;
|
||||||
|
RenderDevice &device_ = RenderDevice::instance();
|
||||||
|
RenderQueue queue_;
|
||||||
|
RenderStats stats_;
|
||||||
|
|
||||||
|
Viewport viewport_;
|
||||||
|
ScissorRect scissor_;
|
||||||
|
Color clearColor_;
|
||||||
|
|
||||||
|
std::stack<Viewport> viewportStack_;
|
||||||
|
std::stack<ScissorRect> scissorStack_;
|
||||||
|
|
||||||
|
BlendState currentBlend_;
|
||||||
|
DepthState currentDepth_;
|
||||||
|
RasterState currentRaster_;
|
||||||
|
GLuint currentProgram_ = 0;
|
||||||
|
GLuint currentTexture_ = 0;
|
||||||
|
VAO *currentVAO_ = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define E2D_RENDER_CONTEXT() ::extra2d::RenderContext::instance()
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,183 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/render/render_types.h>
|
||||||
|
#include <extra2d/render/buffer.h>
|
||||||
|
#include <extra2d/render/vao.h>
|
||||||
|
#include <glad/glad.h>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 渲染能力信息
|
||||||
|
*/
|
||||||
|
struct RenderCaps {
|
||||||
|
int32 maxTextureSize = 0;
|
||||||
|
int32 maxTextureUnits = 0;
|
||||||
|
int32 maxVertexAttribs = 0;
|
||||||
|
int32 maxUniformBlockBindings = 0;
|
||||||
|
int32 maxUniformBlockSize = 0;
|
||||||
|
int32 maxVertexUniformComponents = 0;
|
||||||
|
int32 maxFragmentUniformComponents = 0;
|
||||||
|
int32 maxDrawBuffers = 0;
|
||||||
|
int32 maxSamples = 0;
|
||||||
|
bool dsaSupported = false;
|
||||||
|
bool computeSupported = false;
|
||||||
|
bool tessellationSupported = false;
|
||||||
|
bool geometryShaderSupported = false;
|
||||||
|
String renderer;
|
||||||
|
String vendor;
|
||||||
|
String version;
|
||||||
|
String glslVersion;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 渲染设备
|
||||||
|
*
|
||||||
|
* 封装 OpenGL 4.5 核心功能,使用 DSA API
|
||||||
|
*/
|
||||||
|
class RenderDevice {
|
||||||
|
public:
|
||||||
|
static RenderDevice& instance();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 初始化渲染设备
|
||||||
|
* @return 初始化成功返回 true
|
||||||
|
*/
|
||||||
|
bool init();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 关闭渲染设备
|
||||||
|
*/
|
||||||
|
void shutdown();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查是否已初始化
|
||||||
|
*/
|
||||||
|
bool isInitialized() const { return initialized_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取渲染能力信息
|
||||||
|
*/
|
||||||
|
const RenderCaps& caps() const { return caps_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 创建缓冲区
|
||||||
|
*/
|
||||||
|
std::unique_ptr<Buffer> createBuffer(BufferType type, BufferUsage usage,
|
||||||
|
size_t size, const void* data = nullptr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 创建顶点缓冲区
|
||||||
|
*/
|
||||||
|
std::unique_ptr<VertexBuffer> createVertexBuffer(BufferUsage usage, size_t size,
|
||||||
|
const void* data = nullptr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 创建索引缓冲区
|
||||||
|
*/
|
||||||
|
std::unique_ptr<IndexBuffer> createIndexBuffer(BufferUsage usage, size_t size,
|
||||||
|
const void* data = nullptr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 创建 Uniform 缓冲区
|
||||||
|
*/
|
||||||
|
std::unique_ptr<UniformBuffer> createUniformBuffer(BufferUsage usage, size_t size,
|
||||||
|
const void* data = nullptr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 创建 VAO
|
||||||
|
*/
|
||||||
|
std::unique_ptr<VAO> createVAO();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置视口
|
||||||
|
*/
|
||||||
|
void setViewport(int32 x, int32 y, int32 width, int32 height);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置裁剪矩形
|
||||||
|
*/
|
||||||
|
void setScissor(int32 x, int32 y, int32 width, int32 height);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 启用/禁用裁剪测试
|
||||||
|
*/
|
||||||
|
void setScissorEnabled(bool enabled);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 清除缓冲区
|
||||||
|
*/
|
||||||
|
void clear(bool color, bool depth, bool stencil,
|
||||||
|
float r = 0.0f, float g = 0.0f, float b = 0.0f, float a = 1.0f);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置混合状态
|
||||||
|
*/
|
||||||
|
void setBlendState(const BlendState& state);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置深度状态
|
||||||
|
*/
|
||||||
|
void setDepthState(const DepthState& state);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置光栅化状态
|
||||||
|
*/
|
||||||
|
void setRasterState(const RasterState& state);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 绑定纹理到纹理单元
|
||||||
|
*/
|
||||||
|
void bindTexture(uint32 unit, GLuint texture);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 绑定着色器程序
|
||||||
|
*/
|
||||||
|
void bindProgram(GLuint program);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 绘制数组
|
||||||
|
*/
|
||||||
|
void drawArrays(PrimitiveType mode, int32 first, int32 count);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 绘制索引
|
||||||
|
*/
|
||||||
|
void drawElements(PrimitiveType mode, int32 count, IndexType type,
|
||||||
|
const void* indices = nullptr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 绘制实例化数组
|
||||||
|
*/
|
||||||
|
void drawArraysInstanced(PrimitiveType mode, int32 first, int32 count,
|
||||||
|
int32 instanceCount);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 绘制实例化索引
|
||||||
|
*/
|
||||||
|
void drawElementsInstanced(PrimitiveType mode, int32 count, IndexType type,
|
||||||
|
const void* indices, int32 instanceCount);
|
||||||
|
|
||||||
|
private:
|
||||||
|
RenderDevice() = default;
|
||||||
|
~RenderDevice() = default;
|
||||||
|
RenderDevice(const RenderDevice&) = delete;
|
||||||
|
RenderDevice& operator=(const RenderDevice&) = delete;
|
||||||
|
|
||||||
|
void queryCaps();
|
||||||
|
GLenum glPrimitiveType(PrimitiveType mode) const;
|
||||||
|
GLenum glIndexType(IndexType type) const;
|
||||||
|
|
||||||
|
bool initialized_ = false;
|
||||||
|
RenderCaps caps_;
|
||||||
|
|
||||||
|
BlendState currentBlend_;
|
||||||
|
DepthState currentDepth_;
|
||||||
|
RasterState currentRaster_;
|
||||||
|
GLuint currentProgram_ = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define E2D_RENDER_DEVICE() ::extra2d::RenderDevice::instance()
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,164 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/render/render_types.h>
|
||||||
|
#include <extra2d/render/buffer.h>
|
||||||
|
#include <extra2d/render/vao.h>
|
||||||
|
#include <glad/glad.h>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <functional>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
class Material;
|
||||||
|
class MaterialInstance;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 渲染命令类型
|
||||||
|
*/
|
||||||
|
enum class RenderCommandType : uint8 {
|
||||||
|
None = 0,
|
||||||
|
DrawArrays,
|
||||||
|
DrawElements,
|
||||||
|
DrawArraysInstanced,
|
||||||
|
DrawElementsInstanced,
|
||||||
|
Custom
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 渲染排序键
|
||||||
|
*/
|
||||||
|
union RenderKey {
|
||||||
|
uint64 value = 0;
|
||||||
|
struct {
|
||||||
|
uint64 depth : 16;
|
||||||
|
uint64 materialId : 16;
|
||||||
|
uint64 textureId : 16;
|
||||||
|
uint64 pass : 8;
|
||||||
|
uint64 order : 8;
|
||||||
|
};
|
||||||
|
|
||||||
|
static RenderKey create(uint16 depth, uint16 materialId,
|
||||||
|
uint16 textureId, uint8 pass, uint8 order) {
|
||||||
|
RenderKey key;
|
||||||
|
key.depth = depth;
|
||||||
|
key.materialId = materialId;
|
||||||
|
key.textureId = textureId;
|
||||||
|
key.pass = pass;
|
||||||
|
key.order = order;
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 渲染命令
|
||||||
|
*/
|
||||||
|
struct RenderCommand {
|
||||||
|
RenderCommandType type = RenderCommandType::None;
|
||||||
|
RenderKey key;
|
||||||
|
|
||||||
|
VAO* vao = nullptr;
|
||||||
|
GLuint program = 0;
|
||||||
|
GLuint texture = 0;
|
||||||
|
|
||||||
|
PrimitiveType primitive = PrimitiveType::Triangles;
|
||||||
|
int32 vertexCount = 0;
|
||||||
|
int32 firstVertex = 0;
|
||||||
|
int32 instanceCount = 1;
|
||||||
|
|
||||||
|
IndexType indexType = IndexType::UInt16;
|
||||||
|
int32 indexCount = 0;
|
||||||
|
const void* indices = nullptr;
|
||||||
|
|
||||||
|
BlendState blend;
|
||||||
|
DepthState depth;
|
||||||
|
|
||||||
|
std::function<void()> customCallback;
|
||||||
|
|
||||||
|
static RenderCommand makeDrawArrays(VAO* vao, GLuint program,
|
||||||
|
PrimitiveType primitive,
|
||||||
|
int32 first, int32 count) {
|
||||||
|
RenderCommand cmd;
|
||||||
|
cmd.type = RenderCommandType::DrawArrays;
|
||||||
|
cmd.vao = vao;
|
||||||
|
cmd.program = program;
|
||||||
|
cmd.primitive = primitive;
|
||||||
|
cmd.firstVertex = first;
|
||||||
|
cmd.vertexCount = count;
|
||||||
|
return cmd;
|
||||||
|
}
|
||||||
|
|
||||||
|
static RenderCommand makeDrawElements(VAO* vao, GLuint program,
|
||||||
|
PrimitiveType primitive,
|
||||||
|
int32 count, IndexType type,
|
||||||
|
const void* indices = nullptr) {
|
||||||
|
RenderCommand cmd;
|
||||||
|
cmd.type = RenderCommandType::DrawElements;
|
||||||
|
cmd.vao = vao;
|
||||||
|
cmd.program = program;
|
||||||
|
cmd.primitive = primitive;
|
||||||
|
cmd.indexCount = count;
|
||||||
|
cmd.indexType = type;
|
||||||
|
cmd.indices = indices;
|
||||||
|
return cmd;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 渲染命令队列
|
||||||
|
*/
|
||||||
|
class RenderQueue {
|
||||||
|
public:
|
||||||
|
static constexpr size_t INITIAL_CAPACITY = 1024;
|
||||||
|
|
||||||
|
RenderQueue();
|
||||||
|
~RenderQueue() = default;
|
||||||
|
|
||||||
|
void clear();
|
||||||
|
void reserve(size_t capacity);
|
||||||
|
|
||||||
|
void addCommand(const RenderCommand& cmd);
|
||||||
|
void addCommand(RenderCommand&& cmd);
|
||||||
|
|
||||||
|
void sort();
|
||||||
|
|
||||||
|
const Array<RenderCommand>& commands() const { return commands_; }
|
||||||
|
Array<RenderCommand>& commands() { return commands_; }
|
||||||
|
|
||||||
|
size_t size() const { return commands_.size(); }
|
||||||
|
bool empty() const { return commands_.empty(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
Array<RenderCommand> commands_;
|
||||||
|
uint8 currentOrder_ = 0;
|
||||||
|
|
||||||
|
static bool compareCommands(const RenderCommand& a, const RenderCommand& b);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 批处理辅助类
|
||||||
|
*/
|
||||||
|
class BatchHelper {
|
||||||
|
public:
|
||||||
|
void reset();
|
||||||
|
|
||||||
|
bool canBatch(const RenderCommand& cmd) const;
|
||||||
|
void addToBatch(const RenderCommand& cmd);
|
||||||
|
|
||||||
|
bool hasBatch() const { return batchCount_ > 0; }
|
||||||
|
int32 batchVertexCount() const { return batchVertexCount_; }
|
||||||
|
int32 batchIndexCount() const { return batchIndexCount_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
GLuint currentProgram_ = 0;
|
||||||
|
GLuint currentTexture_ = 0;
|
||||||
|
VAO* currentVAO_ = nullptr;
|
||||||
|
BlendState currentBlend_;
|
||||||
|
DepthState currentDepth_;
|
||||||
|
|
||||||
|
int32 batchCount_ = 0;
|
||||||
|
int32 batchVertexCount_ = 0;
|
||||||
|
int32 batchIndexCount_ = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/render/render_types.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 渲染统计管理器
|
||||||
|
*
|
||||||
|
* 跟踪渲染过程中的各种统计数据
|
||||||
|
*/
|
||||||
|
class RenderStatsTracker {
|
||||||
|
public:
|
||||||
|
static RenderStatsTracker& instance();
|
||||||
|
|
||||||
|
void addDrawCall(uint32 vertexCount, uint32 triangleCount);
|
||||||
|
void addStateChange();
|
||||||
|
void addTextureBind();
|
||||||
|
void addShaderBind();
|
||||||
|
void addBufferUpdate();
|
||||||
|
|
||||||
|
const RenderStats& stats() const { return stats_; }
|
||||||
|
void reset() { stats_.reset(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
RenderStatsTracker() = default;
|
||||||
|
RenderStats stats_;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define E2D_RENDER_STATS() ::extra2d::RenderStatsTracker::instance()
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,390 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 类型别名 (与 AssetService 计划保持一致)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
using String = std::string;
|
||||||
|
using String32 = std::u32string;
|
||||||
|
template <typename T> using Array = std::vector<T>;
|
||||||
|
template <typename K, typename V> using Map = std::unordered_map<K, V>;
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 基础类型别名
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
using int8 = std::int8_t;
|
||||||
|
using int16 = std::int16_t;
|
||||||
|
using int32 = std::int32_t;
|
||||||
|
using int64 = std::int64_t;
|
||||||
|
using uint8 = std::uint8_t;
|
||||||
|
using uint16 = std::uint16_t;
|
||||||
|
using uint32 = std::uint32_t;
|
||||||
|
using uint64 = std::uint64_t;
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 混合模式
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
enum class BlendMode : uint8 {
|
||||||
|
None,
|
||||||
|
Alpha,
|
||||||
|
Additive,
|
||||||
|
Multiply,
|
||||||
|
Screen,
|
||||||
|
Custom
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 深度比较函数
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
enum class CompareFunc : uint8 {
|
||||||
|
Never,
|
||||||
|
Less,
|
||||||
|
Equal,
|
||||||
|
LEqual,
|
||||||
|
Greater,
|
||||||
|
NotEqual,
|
||||||
|
GEqual,
|
||||||
|
Always
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 剔除模式
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
enum class CullMode : uint8 {
|
||||||
|
None,
|
||||||
|
Front,
|
||||||
|
Back
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 多边形模式
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
enum class PolygonMode : uint8 {
|
||||||
|
Fill,
|
||||||
|
Line,
|
||||||
|
Point
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 混合因子
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
enum class BlendFactor : uint8 {
|
||||||
|
Zero,
|
||||||
|
One,
|
||||||
|
SrcColor,
|
||||||
|
OneMinusSrcColor,
|
||||||
|
DstColor,
|
||||||
|
OneMinusDstColor,
|
||||||
|
SrcAlpha,
|
||||||
|
OneMinusSrcAlpha,
|
||||||
|
DstAlpha,
|
||||||
|
OneMinusDstAlpha,
|
||||||
|
ConstantColor,
|
||||||
|
OneMinusConstantColor,
|
||||||
|
ConstantAlpha,
|
||||||
|
OneMinusConstantAlpha
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 混合操作
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
enum class BlendOp : uint8 {
|
||||||
|
Add,
|
||||||
|
Subtract,
|
||||||
|
ReverseSubtract,
|
||||||
|
Min,
|
||||||
|
Max
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 缓冲区类型
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
enum class BufferType : uint8 {
|
||||||
|
Vertex,
|
||||||
|
Index,
|
||||||
|
Uniform,
|
||||||
|
PixelPack,
|
||||||
|
PixelUnpack
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 缓冲区使用方式
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
enum class BufferUsage : uint8 {
|
||||||
|
Static,
|
||||||
|
Dynamic,
|
||||||
|
Stream
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 纹理过滤模式
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
enum class FilterMode : uint8 {
|
||||||
|
Nearest,
|
||||||
|
Linear,
|
||||||
|
NearestMipmapNearest,
|
||||||
|
LinearMipmapNearest,
|
||||||
|
NearestMipmapLinear,
|
||||||
|
LinearMipmapLinear
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 纹理环绕模式
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
enum class WrapMode : uint8 {
|
||||||
|
Repeat,
|
||||||
|
Clamp,
|
||||||
|
Mirror,
|
||||||
|
MirrorClamp,
|
||||||
|
Border
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 图元类型
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
enum class PrimitiveType : uint8 {
|
||||||
|
Points,
|
||||||
|
Lines,
|
||||||
|
LineStrip,
|
||||||
|
LineLoop,
|
||||||
|
Triangles,
|
||||||
|
TriangleStrip,
|
||||||
|
TriangleFan
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 索引类型
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
enum class IndexType : uint8 {
|
||||||
|
UInt8,
|
||||||
|
UInt16,
|
||||||
|
UInt32
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 顶点属性语义
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
enum class VertexSemantic : uint8 {
|
||||||
|
Position,
|
||||||
|
Normal,
|
||||||
|
Tangent,
|
||||||
|
Bitangent,
|
||||||
|
Color0,
|
||||||
|
Color1,
|
||||||
|
TexCoord0,
|
||||||
|
TexCoord1,
|
||||||
|
TexCoord2,
|
||||||
|
TexCoord3,
|
||||||
|
BoneIndices,
|
||||||
|
BoneWeights
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 顶点属性类型
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
enum class VertexAttribType : uint8 {
|
||||||
|
Float,
|
||||||
|
Float2,
|
||||||
|
Float3,
|
||||||
|
Float4,
|
||||||
|
Int,
|
||||||
|
Int2,
|
||||||
|
Int3,
|
||||||
|
Int4,
|
||||||
|
UInt,
|
||||||
|
UInt2,
|
||||||
|
UInt3,
|
||||||
|
UInt4,
|
||||||
|
Byte4,
|
||||||
|
Byte4Norm,
|
||||||
|
UByte4,
|
||||||
|
UByte4Norm,
|
||||||
|
Short2,
|
||||||
|
Short2Norm,
|
||||||
|
Short4,
|
||||||
|
Short4Norm
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 混合状态
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
struct BlendState {
|
||||||
|
bool enabled = false;
|
||||||
|
BlendMode mode = BlendMode::Alpha;
|
||||||
|
BlendFactor srcRGB = BlendFactor::SrcAlpha;
|
||||||
|
BlendFactor dstRGB = BlendFactor::OneMinusSrcAlpha;
|
||||||
|
BlendFactor srcAlpha = BlendFactor::One;
|
||||||
|
BlendFactor dstAlpha = BlendFactor::OneMinusSrcAlpha;
|
||||||
|
BlendOp opRGB = BlendOp::Add;
|
||||||
|
BlendOp opAlpha = BlendOp::Add;
|
||||||
|
uint8 colorWriteMask = 0xF;
|
||||||
|
|
||||||
|
static BlendState opaque() {
|
||||||
|
return BlendState{false, BlendMode::None};
|
||||||
|
}
|
||||||
|
|
||||||
|
static BlendState alpha() {
|
||||||
|
return BlendState{true, BlendMode::Alpha};
|
||||||
|
}
|
||||||
|
|
||||||
|
static BlendState additive() {
|
||||||
|
BlendState state;
|
||||||
|
state.enabled = true;
|
||||||
|
state.mode = BlendMode::Additive;
|
||||||
|
state.srcRGB = BlendFactor::SrcAlpha;
|
||||||
|
state.dstRGB = BlendFactor::One;
|
||||||
|
state.srcAlpha = BlendFactor::One;
|
||||||
|
state.dstAlpha = BlendFactor::One;
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
static BlendState multiply() {
|
||||||
|
BlendState state;
|
||||||
|
state.enabled = true;
|
||||||
|
state.mode = BlendMode::Multiply;
|
||||||
|
state.srcRGB = BlendFactor::DstColor;
|
||||||
|
state.dstRGB = BlendFactor::OneMinusSrcAlpha;
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 深度状态
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
struct DepthState {
|
||||||
|
bool testEnabled = true;
|
||||||
|
bool writeEnabled = true;
|
||||||
|
CompareFunc compareFunc = CompareFunc::LEqual;
|
||||||
|
|
||||||
|
static DepthState readOnly() {
|
||||||
|
return DepthState{true, false, CompareFunc::LEqual};
|
||||||
|
}
|
||||||
|
|
||||||
|
static DepthState disabled() {
|
||||||
|
return DepthState{false, false, CompareFunc::Always};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 模板状态
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
struct StencilState {
|
||||||
|
bool enabled = false;
|
||||||
|
uint8 readMask = 0xFF;
|
||||||
|
uint8 writeMask = 0xFF;
|
||||||
|
uint8 ref = 0;
|
||||||
|
CompareFunc compareFunc = CompareFunc::Always;
|
||||||
|
BlendOp stencilFailOp = BlendOp::Add;
|
||||||
|
BlendOp depthFailOp = BlendOp::Add;
|
||||||
|
BlendOp passOp = BlendOp::Add;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 光栅化状态
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
struct RasterState {
|
||||||
|
CullMode cullMode = CullMode::Back;
|
||||||
|
PolygonMode polygonMode = PolygonMode::Fill;
|
||||||
|
bool depthBiasEnabled = false;
|
||||||
|
float depthBias = 0.0f;
|
||||||
|
float depthBiasSlope = 0.0f;
|
||||||
|
bool scissorEnabled = false;
|
||||||
|
bool multisampleEnabled = true;
|
||||||
|
float lineWidth = 1.0f;
|
||||||
|
float pointSize = 1.0f;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 渲染统计
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
struct RenderStats {
|
||||||
|
uint32 drawCalls = 0;
|
||||||
|
uint32 vertices = 0;
|
||||||
|
uint32 triangles = 0;
|
||||||
|
uint32 stateChanges = 0;
|
||||||
|
uint32 textureBinds = 0;
|
||||||
|
uint32 shaderBinds = 0;
|
||||||
|
uint32 bufferUpdates = 0;
|
||||||
|
|
||||||
|
void reset() {
|
||||||
|
drawCalls = 0;
|
||||||
|
vertices = 0;
|
||||||
|
triangles = 0;
|
||||||
|
stateChanges = 0;
|
||||||
|
textureBinds = 0;
|
||||||
|
shaderBinds = 0;
|
||||||
|
bufferUpdates = 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 视口
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
struct Viewport {
|
||||||
|
int32 x = 0;
|
||||||
|
int32 y = 0;
|
||||||
|
int32 width = 800;
|
||||||
|
int32 height = 600;
|
||||||
|
float minDepth = 0.0f;
|
||||||
|
float maxDepth = 1.0f;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 裁剪矩形
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
struct ScissorRect {
|
||||||
|
int32 x = 0;
|
||||||
|
int32 y = 0;
|
||||||
|
int32 width = 0;
|
||||||
|
int32 height = 0;
|
||||||
|
bool enabled = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 清除标志
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
struct ClearFlags {
|
||||||
|
bool color = true;
|
||||||
|
bool depth = true;
|
||||||
|
bool stencil = false;
|
||||||
|
|
||||||
|
static ClearFlags all() {
|
||||||
|
return ClearFlags{true, true, true};
|
||||||
|
}
|
||||||
|
|
||||||
|
static ClearFlags colorOnly() {
|
||||||
|
return ClearFlags{true, false, false};
|
||||||
|
}
|
||||||
|
|
||||||
|
static ClearFlags depthOnly() {
|
||||||
|
return ClearFlags{false, true, false};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 颜色 (RGBA)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
struct Color {
|
||||||
|
float r = 0.0f;
|
||||||
|
float g = 0.0f;
|
||||||
|
float b = 0.0f;
|
||||||
|
float a = 1.0f;
|
||||||
|
|
||||||
|
Color() = default;
|
||||||
|
Color(float r, float g, float b, float a = 1.0f) : r(r), g(g), b(b), a(a) {}
|
||||||
|
|
||||||
|
static Color white() { return Color(1.0f, 1.0f, 1.0f, 1.0f); }
|
||||||
|
static Color black() { return Color(0.0f, 0.0f, 0.0f, 1.0f); }
|
||||||
|
static Color red() { return Color(1.0f, 0.0f, 0.0f, 1.0f); }
|
||||||
|
static Color green() { return Color(0.0f, 1.0f, 0.0f, 1.0f); }
|
||||||
|
static Color blue() { return Color(0.0f, 0.0f, 1.0f, 1.0f); }
|
||||||
|
static Color transparent() { return Color(0.0f, 0.0f, 0.0f, 0.0f); }
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,119 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/render/render_types.h>
|
||||||
|
#include <extra2d/render/render_device.h>
|
||||||
|
#include <extra2d/render/buffer.h>
|
||||||
|
#include <extra2d/render/vao.h>
|
||||||
|
#include <glad/glad.h>
|
||||||
|
#include <glm/mat4x4.hpp>
|
||||||
|
#include <glm/vec2.hpp>
|
||||||
|
#include <glm/vec4.hpp>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 图形顶点
|
||||||
|
*/
|
||||||
|
struct ShapeVertex {
|
||||||
|
float x, y;
|
||||||
|
float r, g, b, a;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 图形渲染器
|
||||||
|
*
|
||||||
|
* 支持矩形、圆形、线条等基本图形的批量渲染
|
||||||
|
*/
|
||||||
|
class ShapeRenderer {
|
||||||
|
public:
|
||||||
|
static constexpr size_t MAX_VERTICES = 65536;
|
||||||
|
static constexpr size_t LINE_SEGMENTS = 32;
|
||||||
|
|
||||||
|
ShapeRenderer();
|
||||||
|
~ShapeRenderer();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 初始化渲染器
|
||||||
|
*/
|
||||||
|
bool init();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 关闭渲染器
|
||||||
|
*/
|
||||||
|
void shutdown();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 开始批量渲染
|
||||||
|
*/
|
||||||
|
void begin(const glm::mat4& viewProjection);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 绘制矩形 (填充)
|
||||||
|
*/
|
||||||
|
void fillRect(float x, float y, float width, float height, const glm::vec4& color);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 绘制矩形 (描边)
|
||||||
|
*/
|
||||||
|
void drawRect(float x, float y, float width, float height, const glm::vec4& color, float lineWidth = 1.0f);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 绘制圆形 (填充)
|
||||||
|
*/
|
||||||
|
void fillCircle(float x, float y, float radius, const glm::vec4& color, int segments = LINE_SEGMENTS);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 绘制圆形 (描边)
|
||||||
|
*/
|
||||||
|
void drawCircle(float x, float y, float radius, const glm::vec4& color, float lineWidth = 1.0f, int segments = LINE_SEGMENTS);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 绘制线条
|
||||||
|
*/
|
||||||
|
void drawLine(float x1, float y1, float x2, float y2, const glm::vec4& color, float lineWidth = 1.0f);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 绘制多边形 (填充)
|
||||||
|
*/
|
||||||
|
void fillPolygon(const glm::vec2* points, size_t count, const glm::vec4& color);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 绘制多边形 (描边)
|
||||||
|
*/
|
||||||
|
void drawPolygon(const glm::vec2* points, size_t count, const glm::vec4& color, float lineWidth = 1.0f);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 绘制三角形 (填充)
|
||||||
|
*/
|
||||||
|
void fillTriangle(float x1, float y1, float x2, float y2, float x3, float y3, const glm::vec4& color);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 结束批量渲染
|
||||||
|
*/
|
||||||
|
void end();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取绘制调用次数
|
||||||
|
*/
|
||||||
|
uint32 drawCalls() const { return drawCalls_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unique_ptr<VAO> vao_;
|
||||||
|
std::unique_ptr<VertexBuffer> vbo_;
|
||||||
|
Array<ShapeVertex> vertices_;
|
||||||
|
size_t vertexCount_ = 0;
|
||||||
|
|
||||||
|
GLuint fillShader_ = 0;
|
||||||
|
GLuint lineShader_ = 0;
|
||||||
|
glm::mat4 viewProjection_;
|
||||||
|
|
||||||
|
uint32 drawCalls_ = 0;
|
||||||
|
float currentLineWidth_ = 1.0f;
|
||||||
|
|
||||||
|
bool createShaders();
|
||||||
|
void flush(GLenum mode);
|
||||||
|
void addVertex(float x, float y, const glm::vec4& color);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,104 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/render/render_types.h>
|
||||||
|
#include <extra2d/render/render_context.h>
|
||||||
|
#include <extra2d/render/buffer.h>
|
||||||
|
#include <extra2d/render/vao.h>
|
||||||
|
#include <glad/glad.h>
|
||||||
|
#include <glm/mat4x4.hpp>
|
||||||
|
#include <glm/vec2.hpp>
|
||||||
|
#include <glm/vec4.hpp>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 精灵顶点
|
||||||
|
*/
|
||||||
|
struct SpriteVertex {
|
||||||
|
float x, y;
|
||||||
|
float u, v;
|
||||||
|
float r, g, b, a;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 精灵绘制数据
|
||||||
|
*/
|
||||||
|
struct SpriteData {
|
||||||
|
glm::vec2 position = glm::vec2(0.0f);
|
||||||
|
glm::vec2 size = glm::vec2(1.0f);
|
||||||
|
glm::vec2 anchor = glm::vec2(0.5f);
|
||||||
|
glm::vec4 color = glm::vec4(1.0f);
|
||||||
|
glm::vec4 texRect = glm::vec4(0.0f, 0.0f, 1.0f, 1.0f);
|
||||||
|
float rotation = 0.0f;
|
||||||
|
float depth = 0.0f;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 精灵渲染器
|
||||||
|
*
|
||||||
|
* 高性能批量精灵渲染器
|
||||||
|
*/
|
||||||
|
class SpriteRenderer {
|
||||||
|
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;
|
||||||
|
|
||||||
|
SpriteRenderer();
|
||||||
|
~SpriteRenderer();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 初始化渲染器
|
||||||
|
*/
|
||||||
|
bool init();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 关闭渲染器
|
||||||
|
*/
|
||||||
|
void shutdown();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 开始批量渲染
|
||||||
|
*/
|
||||||
|
void begin(const glm::mat4& viewProjection);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 绘制精灵
|
||||||
|
*/
|
||||||
|
void draw(GLuint texture, const SpriteData& data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 结束批量渲染
|
||||||
|
*/
|
||||||
|
void end();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取统计信息
|
||||||
|
*/
|
||||||
|
uint32 drawCalls() const { return drawCalls_; }
|
||||||
|
uint32 spriteCount() const { return spriteCount_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unique_ptr<VAO> vao_;
|
||||||
|
std::unique_ptr<VertexBuffer> vbo_;
|
||||||
|
std::unique_ptr<IndexBuffer> ibo_;
|
||||||
|
|
||||||
|
Array<SpriteVertex> vertices_;
|
||||||
|
size_t vertexCount_ = 0;
|
||||||
|
|
||||||
|
GLuint shader_ = 0;
|
||||||
|
GLuint currentTexture_ = 0;
|
||||||
|
glm::mat4 viewProjection_;
|
||||||
|
|
||||||
|
uint32 drawCalls_ = 0;
|
||||||
|
uint32 spriteCount_ = 0;
|
||||||
|
|
||||||
|
bool createShader();
|
||||||
|
void flush();
|
||||||
|
void addQuad(const SpriteData& data, GLuint texture);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,144 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/render/render_types.h>
|
||||||
|
#include <extra2d/render/render_device.h>
|
||||||
|
#include <extra2d/render/buffer.h>
|
||||||
|
#include <extra2d/render/vao.h>
|
||||||
|
#include <glad/glad.h>
|
||||||
|
#include <glm/mat4x4.hpp>
|
||||||
|
#include <glm/vec2.hpp>
|
||||||
|
#include <glm/vec4.hpp>
|
||||||
|
#include <memory>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 字形信息
|
||||||
|
*/
|
||||||
|
struct GlyphInfo {
|
||||||
|
char32_t codepoint = 0;
|
||||||
|
glm::vec2 uvMin = glm::vec2(0.0f);
|
||||||
|
glm::vec2 uvMax = glm::vec2(0.0f);
|
||||||
|
glm::vec2 size = glm::vec2(0.0f);
|
||||||
|
glm::vec2 bearing = glm::vec2(0.0f);
|
||||||
|
float advance = 0.0f;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 字体图集
|
||||||
|
*/
|
||||||
|
class FontAtlas {
|
||||||
|
public:
|
||||||
|
FontAtlas();
|
||||||
|
~FontAtlas();
|
||||||
|
|
||||||
|
bool create(int atlasWidth = 2048, int atlasHeight = 2048);
|
||||||
|
void destroy();
|
||||||
|
|
||||||
|
bool isValid() const { return texture_ != 0; }
|
||||||
|
GLuint texture() const { return texture_; }
|
||||||
|
int width() const { return width_; }
|
||||||
|
int height() const { return height_; }
|
||||||
|
|
||||||
|
void addGlyph(char32_t codepoint, const GlyphInfo& info);
|
||||||
|
const GlyphInfo* getGlyph(char32_t codepoint) const;
|
||||||
|
bool hasGlyph(char32_t codepoint) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
GLuint texture_ = 0;
|
||||||
|
int width_ = 0;
|
||||||
|
int height_ = 0;
|
||||||
|
std::unordered_map<char32_t, GlyphInfo> glyphs_;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 文本顶点
|
||||||
|
*/
|
||||||
|
struct TextVertex {
|
||||||
|
float x, y;
|
||||||
|
float u, v;
|
||||||
|
float r, g, b, a;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 文本渲染配置
|
||||||
|
*/
|
||||||
|
struct TextConfig {
|
||||||
|
float fontSize = 32.0f;
|
||||||
|
glm::vec4 color = glm::vec4(1.0f);
|
||||||
|
float outlineWidth = 0.0f;
|
||||||
|
glm::vec4 outlineColor = glm::vec4(0.0f);
|
||||||
|
float pxRange = 4.0f;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 文本渲染器
|
||||||
|
*
|
||||||
|
* 支持 MSDF 字体的高质量文本渲染
|
||||||
|
*/
|
||||||
|
class TextRenderer {
|
||||||
|
public:
|
||||||
|
static constexpr size_t MAX_VERTICES = 65536;
|
||||||
|
static constexpr size_t VERTICES_PER_CHAR = 4;
|
||||||
|
static constexpr size_t INDICES_PER_CHAR = 6;
|
||||||
|
|
||||||
|
TextRenderer();
|
||||||
|
~TextRenderer();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 初始化渲染器
|
||||||
|
*/
|
||||||
|
bool init();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 关闭渲染器
|
||||||
|
*/
|
||||||
|
void shutdown();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 开始批量渲染
|
||||||
|
*/
|
||||||
|
void begin(const glm::mat4& viewProjection);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 绘制文本
|
||||||
|
*/
|
||||||
|
void drawText(FontAtlas* font, const String32& text,
|
||||||
|
float x, float y, const TextConfig& config = {});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 测量文本尺寸
|
||||||
|
*/
|
||||||
|
glm::vec2 measureText(FontAtlas* font, const String32& text, float fontSize);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 结束批量渲染
|
||||||
|
*/
|
||||||
|
void end();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取绘制调用次数
|
||||||
|
*/
|
||||||
|
uint32 drawCalls() const { return drawCalls_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unique_ptr<VAO> vao_;
|
||||||
|
std::unique_ptr<VertexBuffer> vbo_;
|
||||||
|
std::unique_ptr<IndexBuffer> ibo_;
|
||||||
|
Array<TextVertex> vertices_;
|
||||||
|
size_t vertexCount_ = 0;
|
||||||
|
|
||||||
|
GLuint shader_ = 0;
|
||||||
|
glm::mat4 viewProjection_;
|
||||||
|
|
||||||
|
FontAtlas* currentFont_ = nullptr;
|
||||||
|
uint32 drawCalls_ = 0;
|
||||||
|
|
||||||
|
bool createShader();
|
||||||
|
void flush();
|
||||||
|
void addChar(const GlyphInfo& glyph, float x, float y,
|
||||||
|
float scale, const glm::vec4& color);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,224 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/render/render_types.h>
|
||||||
|
#include <extra2d/render/buffer.h>
|
||||||
|
#include <glad/glad.h>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 顶点属性描述
|
||||||
|
*/
|
||||||
|
struct VertexAttribDesc {
|
||||||
|
uint32 location = 0;
|
||||||
|
VertexAttribType type = VertexAttribType::Float;
|
||||||
|
uint32 offset = 0;
|
||||||
|
bool normalized = false;
|
||||||
|
|
||||||
|
VertexAttribDesc() = default;
|
||||||
|
VertexAttribDesc(uint32 loc, VertexAttribType t, uint32 off, bool norm = false)
|
||||||
|
: location(loc), type(t), offset(off), normalized(norm) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 顶点格式描述
|
||||||
|
*/
|
||||||
|
struct VertexFormatDesc {
|
||||||
|
uint32 stride = 0;
|
||||||
|
Array<VertexAttribDesc> attributes;
|
||||||
|
|
||||||
|
VertexFormatDesc& addAttribute(uint32 location, VertexAttribType type,
|
||||||
|
uint32 offset, bool normalized = false) {
|
||||||
|
attributes.push_back({location, type, offset, normalized});
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
VertexFormatDesc& setStride(uint32 s) {
|
||||||
|
stride = s;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 顶点数组对象 (VAO)
|
||||||
|
*
|
||||||
|
* 使用 OpenGL 4.5 DSA API 管理 VAO
|
||||||
|
*/
|
||||||
|
class VAO {
|
||||||
|
public:
|
||||||
|
VAO();
|
||||||
|
~VAO();
|
||||||
|
|
||||||
|
VAO(const VAO&) = delete;
|
||||||
|
VAO& operator=(const VAO&) = delete;
|
||||||
|
|
||||||
|
VAO(VAO&& other) noexcept;
|
||||||
|
VAO& operator=(VAO&& other) noexcept;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 创建 VAO
|
||||||
|
* @return 创建成功返回 true
|
||||||
|
*/
|
||||||
|
bool create();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 销毁 VAO
|
||||||
|
*/
|
||||||
|
void destroy();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查 VAO 是否有效
|
||||||
|
*/
|
||||||
|
bool isValid() const { return id_ != 0; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取 VAO ID
|
||||||
|
*/
|
||||||
|
GLuint id() const { return id_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 绑定 VAO
|
||||||
|
*/
|
||||||
|
void bind() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 解除绑定
|
||||||
|
*/
|
||||||
|
static void unbind();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置顶点格式
|
||||||
|
* @param format 顶点格式描述
|
||||||
|
*/
|
||||||
|
void setFormat(const VertexFormatDesc& format);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 绑定顶点缓冲区
|
||||||
|
* @param binding 绑定点索引
|
||||||
|
* @param buffer 缓冲区
|
||||||
|
* @param offset 偏移量
|
||||||
|
* @param stride 步长
|
||||||
|
*/
|
||||||
|
void bindVertexBuffer(uint32 binding, const Buffer& buffer,
|
||||||
|
size_t offset, uint32 stride);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 绑定索引缓冲区
|
||||||
|
* @param buffer 索引缓冲区
|
||||||
|
*/
|
||||||
|
void bindIndexBuffer(const Buffer& buffer);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 启用顶点属性
|
||||||
|
* @param location 属性位置
|
||||||
|
*/
|
||||||
|
void enableAttrib(uint32 location);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 禁用顶点属性
|
||||||
|
* @param location 属性位置
|
||||||
|
*/
|
||||||
|
void disableAttrib(uint32 location);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置顶点属性格式
|
||||||
|
* @param location 属性位置
|
||||||
|
* @param type 属性类型
|
||||||
|
* @param offset 偏移量
|
||||||
|
* @param normalized 是否归一化
|
||||||
|
*/
|
||||||
|
void setAttribFormat(uint32 location, VertexAttribType type,
|
||||||
|
uint32 offset, bool normalized = false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置顶点属性绑定
|
||||||
|
* @param location 属性位置
|
||||||
|
* @param binding 绑定点索引
|
||||||
|
*/
|
||||||
|
void setAttribBinding(uint32 location, uint32 binding);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置顶点属性除数 (用于实例化)
|
||||||
|
* @param binding 绑定点索引
|
||||||
|
* @param divisor 除数
|
||||||
|
*/
|
||||||
|
void setBindingDivisor(uint32 binding, uint32 divisor);
|
||||||
|
|
||||||
|
private:
|
||||||
|
GLuint id_ = 0;
|
||||||
|
|
||||||
|
static GLenum glAttribType(VertexAttribType type);
|
||||||
|
static int glAttribComponents(VertexAttribType type);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 常用顶点格式
|
||||||
|
*/
|
||||||
|
namespace VertexFormats {
|
||||||
|
|
||||||
|
struct Position2D {
|
||||||
|
float x, y;
|
||||||
|
|
||||||
|
static VertexFormatDesc format() {
|
||||||
|
return VertexFormatDesc()
|
||||||
|
.setStride(sizeof(Position2D))
|
||||||
|
.addAttribute(0, VertexAttribType::Float2, 0);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PositionTexCoord {
|
||||||
|
float x, y;
|
||||||
|
float u, v;
|
||||||
|
|
||||||
|
static VertexFormatDesc format() {
|
||||||
|
return VertexFormatDesc()
|
||||||
|
.setStride(sizeof(PositionTexCoord))
|
||||||
|
.addAttribute(0, VertexAttribType::Float2, 0)
|
||||||
|
.addAttribute(1, VertexAttribType::Float2, 8);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PositionColor {
|
||||||
|
float x, y;
|
||||||
|
float r, g, b, a;
|
||||||
|
|
||||||
|
static VertexFormatDesc format() {
|
||||||
|
return VertexFormatDesc()
|
||||||
|
.setStride(sizeof(PositionColor))
|
||||||
|
.addAttribute(0, VertexAttribType::Float2, 0)
|
||||||
|
.addAttribute(1, VertexAttribType::Float4, 8);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PositionColorTexCoord {
|
||||||
|
float x, y;
|
||||||
|
float r, g, b, a;
|
||||||
|
float u, v;
|
||||||
|
|
||||||
|
static VertexFormatDesc format() {
|
||||||
|
return VertexFormatDesc()
|
||||||
|
.setStride(sizeof(PositionColorTexCoord))
|
||||||
|
.addAttribute(0, VertexAttribType::Float2, 0)
|
||||||
|
.addAttribute(1, VertexAttribType::Float4, 8)
|
||||||
|
.addAttribute(2, VertexAttribType::Float2, 24);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SpriteVertex {
|
||||||
|
float x, y;
|
||||||
|
float u, v;
|
||||||
|
float r, g, b, a;
|
||||||
|
|
||||||
|
static VertexFormatDesc format() {
|
||||||
|
return VertexFormatDesc()
|
||||||
|
.setStride(sizeof(SpriteVertex))
|
||||||
|
.addAttribute(0, VertexAttribType::Float2, 0)
|
||||||
|
.addAttribute(1, VertexAttribType::Float2, 8)
|
||||||
|
.addAttribute(2, VertexAttribType::Float4, 16);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace VertexFormats
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -1,356 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <extra2d/audio/sound.h>
|
|
||||||
#include <extra2d/core/types.h>
|
|
||||||
#include <extra2d/graphics/alpha_mask.h>
|
|
||||||
#include <extra2d/graphics/font.h>
|
|
||||||
#include <extra2d/graphics/texture.h>
|
|
||||||
#include <functional>
|
|
||||||
#include <future>
|
|
||||||
#include <mutex>
|
|
||||||
#include <string>
|
|
||||||
#include <unordered_map>
|
|
||||||
#include <queue>
|
|
||||||
#include <thread>
|
|
||||||
#include <atomic>
|
|
||||||
#include <list>
|
|
||||||
#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 {
|
|
||||||
Ptr<Texture> texture;
|
|
||||||
size_t size = 0; // 纹理大小(字节)
|
|
||||||
float lastAccessTime = 0.0f; // 最后访问时间
|
|
||||||
uint32_t accessCount = 0; // 访问次数
|
|
||||||
};
|
|
||||||
|
|
||||||
// 异步加载回调类型
|
|
||||||
using TextureLoadCallback = std::function<void(Ptr<Texture>)>;
|
|
||||||
|
|
||||||
class ResourceManager {
|
|
||||||
public:
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 单例访问
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
static ResourceManager &getInstance();
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 纹理资源 - 同步加载
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/// 加载纹理(带缓存)
|
|
||||||
Ptr<Texture> loadTexture(const std::string &filepath);
|
|
||||||
|
|
||||||
/// 加载纹理(指定是否异步)
|
|
||||||
Ptr<Texture> loadTexture(const std::string &filepath, bool async);
|
|
||||||
|
|
||||||
/// 加载纹理(完整参数:异步 + 压缩格式)
|
|
||||||
Ptr<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遮罩(用于不规则形状图片)
|
|
||||||
Ptr<Texture> loadTextureWithAlphaMask(const std::string &filepath);
|
|
||||||
|
|
||||||
/// 通过key获取已缓存的纹理
|
|
||||||
Ptr<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;
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 字体图集资源
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/// 加载字体图集(带缓存)
|
|
||||||
Ptr<FontAtlas> loadFont(const std::string &filepath, int fontSize,
|
|
||||||
bool useSDF = false);
|
|
||||||
|
|
||||||
/// 通过key获取已缓存的字体图集
|
|
||||||
Ptr<FontAtlas> getFont(const std::string &key) const;
|
|
||||||
|
|
||||||
/// 检查字体是否已缓存
|
|
||||||
bool hasFont(const std::string &key) const;
|
|
||||||
|
|
||||||
/// 卸载指定字体
|
|
||||||
void unloadFont(const std::string &key);
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 音效资源
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/// 加载音效(带缓存)
|
|
||||||
Ptr<Sound> loadSound(const std::string &filepath);
|
|
||||||
Ptr<Sound> loadSound(const std::string &name, const std::string &filepath);
|
|
||||||
|
|
||||||
/// 通过key获取已缓存的音效
|
|
||||||
Ptr<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;
|
|
||||||
|
|
||||||
// 内部加载实现
|
|
||||||
Ptr<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_;
|
|
||||||
|
|
||||||
// 资源缓存 - 使用弱指针实现自动清理
|
|
||||||
std::unordered_map<std::string, WeakPtr<FontAtlas>> fontCache_;
|
|
||||||
std::unordered_map<std::string, WeakPtr<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<Ptr<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
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
#include <extra2d/core/math_types.h>
|
#include <extra2d/core/math_types.h>
|
||||||
#include <extra2d/core/types.h>
|
#include <extra2d/core/types.h>
|
||||||
#include <extra2d/event/event_dispatcher.h>
|
#include <extra2d/event/event_dispatcher.h>
|
||||||
#include <extra2d/graphics/render_backend.h>
|
#include <extra2d/graphics/renderer.h>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
@ -14,8 +14,7 @@ namespace extra2d {
|
||||||
|
|
||||||
// 前向声明
|
// 前向声明
|
||||||
class Scene;
|
class Scene;
|
||||||
class Action;
|
class Renderer;
|
||||||
class RenderBackend;
|
|
||||||
struct RenderCommand;
|
struct RenderCommand;
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
@ -139,7 +138,7 @@ public:
|
||||||
virtual void onEnter();
|
virtual void onEnter();
|
||||||
virtual void onExit();
|
virtual void onExit();
|
||||||
virtual void onUpdate(float dt);
|
virtual void onUpdate(float dt);
|
||||||
virtual void onRender(RenderBackend &renderer);
|
virtual void onRender(Renderer &renderer);
|
||||||
virtual void onAttachToScene(Scene *scene);
|
virtual void onAttachToScene(Scene *scene);
|
||||||
virtual void onDetachFromScene();
|
virtual void onDetachFromScene();
|
||||||
|
|
||||||
|
|
@ -155,58 +154,6 @@ public:
|
||||||
// 更新空间索引(手动调用,通常在边界框变化后)
|
// 更新空间索引(手动调用,通常在边界框变化后)
|
||||||
void updateSpatialIndex();
|
void updateSpatialIndex();
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 动作系统
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
/**
|
|
||||||
* @brief 运行动作
|
|
||||||
* @param action 动作指针(所有权转移)
|
|
||||||
* @return 动作指针
|
|
||||||
*/
|
|
||||||
Action* runAction(Action* action);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 停止所有动作
|
|
||||||
*/
|
|
||||||
void stopAllActions();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 停止指定动作
|
|
||||||
* @param action 动作指针
|
|
||||||
*/
|
|
||||||
void stopAction(Action* action);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 根据标签停止动作
|
|
||||||
* @param tag 标签值
|
|
||||||
*/
|
|
||||||
void stopActionByTag(int tag);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 根据标志位停止动作
|
|
||||||
* @param flags 标志位
|
|
||||||
*/
|
|
||||||
void stopActionsByFlags(unsigned int flags);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 根据标签获取动作
|
|
||||||
* @param tag 标签值
|
|
||||||
* @return 动作指针,未找到返回 nullptr
|
|
||||||
*/
|
|
||||||
Action* getActionByTag(int tag);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取运行中的动作数量
|
|
||||||
* @return 动作数量
|
|
||||||
*/
|
|
||||||
size_t getActionCount() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查是否有动作在运行
|
|
||||||
* @return true 如果有动作在运行
|
|
||||||
*/
|
|
||||||
bool isRunningActions() const;
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
// 事件系统
|
// 事件系统
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
|
|
@ -216,7 +163,7 @@ public:
|
||||||
// 内部方法
|
// 内部方法
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
void update(float dt);
|
void update(float dt);
|
||||||
void render(RenderBackend &renderer);
|
void render(Renderer &renderer);
|
||||||
void sortChildren();
|
void sortChildren();
|
||||||
|
|
||||||
bool isRunning() const { return running_; }
|
bool isRunning() const { return running_; }
|
||||||
|
|
@ -228,7 +175,7 @@ public:
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
// 子类重写
|
// 子类重写
|
||||||
virtual void onDraw(RenderBackend &renderer) {}
|
virtual void onDraw(Renderer &renderer) {}
|
||||||
virtual void onUpdateNode(float dt) {}
|
virtual void onUpdateNode(float dt) {}
|
||||||
virtual void generateRenderCommand(std::vector<RenderCommand> &commands,
|
virtual void generateRenderCommand(std::vector<RenderCommand> &commands,
|
||||||
int zOrder) {};
|
int zOrder) {};
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ namespace extra2d {
|
||||||
|
|
||||||
// 前向声明
|
// 前向声明
|
||||||
struct RenderCommand;
|
struct RenderCommand;
|
||||||
|
class Renderer;
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// 场景类 - 节点容器,管理整个场景图
|
// 场景类 - 节点容器,管理整个场景图
|
||||||
|
|
@ -55,8 +56,8 @@ public:
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
// 渲染和更新
|
// 渲染和更新
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
void renderScene(RenderBackend &renderer);
|
void renderScene(Renderer &renderer);
|
||||||
virtual void renderContent(RenderBackend &renderer);
|
virtual void renderContent(Renderer &renderer);
|
||||||
void updateScene(float dt);
|
void updateScene(float dt);
|
||||||
void collectRenderCommands(std::vector<RenderCommand> &commands,
|
void collectRenderCommands(std::vector<RenderCommand> &commands,
|
||||||
int parentZOrder = 0) override;
|
int parentZOrder = 0) override;
|
||||||
|
|
@ -92,12 +93,7 @@ protected:
|
||||||
void onEnter() override;
|
void onEnter() override;
|
||||||
void onExit() override;
|
void onExit() override;
|
||||||
|
|
||||||
// 过渡场景生命周期回调(供 TransitionScene 使用)
|
|
||||||
virtual void onExitTransitionDidStart() {}
|
|
||||||
virtual void onEnterTransitionDidFinish() {}
|
|
||||||
|
|
||||||
friend class SceneManager;
|
friend class SceneManager;
|
||||||
friend class TransitionScene;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Color backgroundColor_ = Colors::Black;
|
Color backgroundColor_ = Colors::Black;
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
#include <extra2d/core/types.h>
|
#include <extra2d/core/types.h>
|
||||||
#include <extra2d/scene/scene.h>
|
#include <extra2d/scene/scene.h>
|
||||||
#include <extra2d/scene/transition_scene.h>
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <stack>
|
#include <stack>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
@ -13,15 +12,13 @@ namespace extra2d {
|
||||||
|
|
||||||
// 前向声明
|
// 前向声明
|
||||||
struct RenderCommand;
|
struct RenderCommand;
|
||||||
class TransitionScene;
|
class Renderer;
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// 场景管理器 - 管理场景的生命周期和切换
|
// 场景管理器 - 管理场景的生命周期和切换
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
class SceneManager {
|
class SceneManager {
|
||||||
public:
|
public:
|
||||||
using TransitionCallback = std::function<void()>;
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
// 单例访问
|
// 单例访问
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
|
|
@ -36,26 +33,18 @@ public:
|
||||||
|
|
||||||
// 替换当前场景
|
// 替换当前场景
|
||||||
void replaceScene(Ptr<Scene> scene);
|
void replaceScene(Ptr<Scene> scene);
|
||||||
void replaceScene(Ptr<Scene> scene, TransitionType transition,
|
|
||||||
float duration = 0.5f);
|
|
||||||
|
|
||||||
// 压入新场景(当前场景暂停)
|
// 压入新场景(当前场景暂停)
|
||||||
void pushScene(Ptr<Scene> scene);
|
void pushScene(Ptr<Scene> scene);
|
||||||
void pushScene(Ptr<Scene> scene, TransitionType transition,
|
|
||||||
float duration = 0.5f);
|
|
||||||
|
|
||||||
// 弹出当前场景(恢复上一个场景)
|
// 弹出当前场景(恢复上一个场景)
|
||||||
void popScene();
|
void popScene();
|
||||||
void popScene(TransitionType transition, float duration = 0.5f);
|
|
||||||
|
|
||||||
// 弹出到根场景
|
// 弹出到根场景
|
||||||
void popToRootScene();
|
void popToRootScene();
|
||||||
void popToRootScene(TransitionType transition, float duration = 0.5f);
|
|
||||||
|
|
||||||
// 弹出到指定场景
|
// 弹出到指定场景
|
||||||
void popToScene(const std::string &name);
|
void popToScene(const std::string &name);
|
||||||
void popToScene(const std::string &name, TransitionType transition,
|
|
||||||
float duration = 0.5f);
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
// 获取场景
|
// 获取场景
|
||||||
|
|
@ -78,16 +67,13 @@ public:
|
||||||
// 更新和渲染
|
// 更新和渲染
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
void update(float dt);
|
void update(float dt);
|
||||||
void render(RenderBackend &renderer);
|
void render(Renderer &renderer);
|
||||||
void collectRenderCommands(std::vector<RenderCommand> &commands);
|
void collectRenderCommands(std::vector<RenderCommand> &commands);
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
// 过渡控制
|
// 过渡控制
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
bool isTransitioning() const { return isTransitioning_; }
|
bool isTransitioning() const { return false; }
|
||||||
void setTransitionCallback(TransitionCallback callback) {
|
|
||||||
transitionCallback_ = callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
// 清理
|
// 清理
|
||||||
|
|
@ -103,38 +89,10 @@ public:
|
||||||
|
|
||||||
// 场景切换(供 Application 使用)
|
// 场景切换(供 Application 使用)
|
||||||
void enterScene(Ptr<Scene> scene);
|
void enterScene(Ptr<Scene> scene);
|
||||||
void enterScene(Ptr<Scene> scene, Ptr<TransitionScene> transitionScene);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void doSceneSwitch();
|
|
||||||
void startTransition(Ptr<Scene> from, Ptr<Scene> to, TransitionType type,
|
|
||||||
float duration, Function<void()> stackAction);
|
|
||||||
void finishTransition();
|
|
||||||
void dispatchPointerEvents(Scene &scene);
|
|
||||||
|
|
||||||
// 创建过渡场景
|
|
||||||
Ptr<TransitionScene> createTransitionScene(TransitionType type,
|
|
||||||
float duration,
|
|
||||||
Ptr<Scene> inScene);
|
|
||||||
|
|
||||||
std::stack<Ptr<Scene>> sceneStack_;
|
std::stack<Ptr<Scene>> sceneStack_;
|
||||||
std::unordered_map<std::string, Ptr<Scene>> namedScenes_;
|
std::unordered_map<std::string, Ptr<Scene>> namedScenes_;
|
||||||
|
|
||||||
// Transition state
|
|
||||||
bool isTransitioning_ = false;
|
|
||||||
TransitionType currentTransition_ = TransitionType::None;
|
|
||||||
Ptr<TransitionScene> activeTransitionScene_;
|
|
||||||
Function<void()> transitionStackAction_;
|
|
||||||
TransitionCallback transitionCallback_;
|
|
||||||
|
|
||||||
// Next scene to switch to (queued during transition)
|
|
||||||
Ptr<Scene> nextScene_;
|
|
||||||
bool sendCleanupToScene_ = false;
|
|
||||||
|
|
||||||
Node *hoverTarget_ = nullptr;
|
|
||||||
Node *captureTarget_ = nullptr;
|
|
||||||
Vec2 lastPointerWorld_ = Vec2::Zero();
|
|
||||||
bool hasLastPointerWorld_ = false;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace extra2d
|
} // namespace extra2d
|
||||||
|
|
|
||||||
|
|
@ -88,7 +88,7 @@ public:
|
||||||
Rect getBoundingBox() const override;
|
Rect getBoundingBox() const override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void onDraw(RenderBackend &renderer) override;
|
void onDraw(Renderer &renderer) override;
|
||||||
void generateRenderCommand(std::vector<RenderCommand> &commands,
|
void generateRenderCommand(std::vector<RenderCommand> &commands,
|
||||||
int zOrder) override;
|
int zOrder) override;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ public:
|
||||||
Rect getBoundingBox() const override;
|
Rect getBoundingBox() const override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void onDraw(RenderBackend &renderer) override;
|
void onDraw(Renderer &renderer) override;
|
||||||
void generateRenderCommand(std::vector<RenderCommand> &commands,
|
void generateRenderCommand(std::vector<RenderCommand> &commands,
|
||||||
int zOrder) override;
|
int zOrder) override;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,492 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <extra2d/core/types.h>
|
|
||||||
#include <extra2d/utils/logger.h>
|
|
||||||
#include <atomic>
|
|
||||||
#include <chrono>
|
|
||||||
#include <cstddef>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <cstring>
|
|
||||||
#include <memory>
|
|
||||||
#include <mutex>
|
|
||||||
#include <new>
|
|
||||||
#include <type_traits>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 对象池 - 自动管理的高性能内存池
|
|
||||||
// 特性:
|
|
||||||
// - 自动内存对齐
|
|
||||||
// - 侵入式空闲链表(零额外内存开销)
|
|
||||||
// - 线程本地缓存(减少锁竞争)
|
|
||||||
// - 自动容量管理(自动扩展/收缩)
|
|
||||||
// - 自动预热
|
|
||||||
// - 异常安全
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
// 线程本地缓存配置
|
|
||||||
struct PoolConfig {
|
|
||||||
static constexpr size_t DEFAULT_BLOCK_SIZE = 64;
|
|
||||||
static constexpr size_t THREAD_CACHE_SIZE = 16;
|
|
||||||
static constexpr size_t SHRINK_THRESHOLD_MS = 30000;
|
|
||||||
static constexpr double SHRINK_RATIO = 0.5;
|
|
||||||
};
|
|
||||||
|
|
||||||
template <typename T, size_t BlockSize = PoolConfig::DEFAULT_BLOCK_SIZE>
|
|
||||||
class ObjectPool {
|
|
||||||
public:
|
|
||||||
static_assert(std::is_default_constructible_v<T>, "T must be default constructible");
|
|
||||||
static_assert(std::is_destructible_v<T>, "T must be destructible");
|
|
||||||
static_assert(BlockSize > 0, "BlockSize must be greater than 0");
|
|
||||||
static_assert(alignof(T) <= alignof(std::max_align_t),
|
|
||||||
"Alignment requirement too high");
|
|
||||||
|
|
||||||
ObjectPool()
|
|
||||||
: freeListHead_(nullptr)
|
|
||||||
, blocks_()
|
|
||||||
, allocatedCount_(0)
|
|
||||||
, totalCapacity_(0)
|
|
||||||
, isDestroyed_(false)
|
|
||||||
, lastShrinkCheck_(0)
|
|
||||||
, prewarmed_(false) {
|
|
||||||
}
|
|
||||||
|
|
||||||
~ObjectPool() {
|
|
||||||
clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
ObjectPool(const ObjectPool&) = delete;
|
|
||||||
ObjectPool& operator=(const ObjectPool&) = delete;
|
|
||||||
ObjectPool(ObjectPool&&) noexcept = delete;
|
|
||||||
ObjectPool& operator=(ObjectPool&&) noexcept = delete;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 分配一个对象(自动预热、自动扩展)
|
|
||||||
* @return 指向分配的对象的指针,失败返回 nullptr
|
|
||||||
*/
|
|
||||||
T* allocate() {
|
|
||||||
auto& cache = getThreadCache();
|
|
||||||
|
|
||||||
if (T* obj = cache.pop()) {
|
|
||||||
new (obj) T();
|
|
||||||
allocatedCount_.fetch_add(1, std::memory_order_relaxed);
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::lock_guard<std::mutex> lock(mutex_);
|
|
||||||
|
|
||||||
if (isDestroyed_) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!prewarmed_) {
|
|
||||||
prewarmInternal();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!freeListHead_) {
|
|
||||||
growInternal();
|
|
||||||
}
|
|
||||||
|
|
||||||
T* obj = popFreeList();
|
|
||||||
if (obj) {
|
|
||||||
new (obj) T();
|
|
||||||
allocatedCount_.fetch_add(1, std::memory_order_relaxed);
|
|
||||||
}
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 分配并构造一个对象(带参数)
|
|
||||||
*/
|
|
||||||
template <typename... Args>
|
|
||||||
T* allocate(Args&&... args) {
|
|
||||||
auto& cache = getThreadCache();
|
|
||||||
|
|
||||||
if (T* obj = cache.pop()) {
|
|
||||||
new (obj) T(std::forward<Args>(args)...);
|
|
||||||
allocatedCount_.fetch_add(1, std::memory_order_relaxed);
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::lock_guard<std::mutex> lock(mutex_);
|
|
||||||
|
|
||||||
if (isDestroyed_) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!prewarmed_) {
|
|
||||||
prewarmInternal();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!freeListHead_) {
|
|
||||||
growInternal();
|
|
||||||
}
|
|
||||||
|
|
||||||
T* obj = popFreeList();
|
|
||||||
if (obj) {
|
|
||||||
new (obj) T(std::forward<Args>(args)...);
|
|
||||||
allocatedCount_.fetch_add(1, std::memory_order_relaxed);
|
|
||||||
}
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 回收一个对象(自动异常处理)
|
|
||||||
* @param obj 要回收的对象指针
|
|
||||||
* @return true 如果对象成功回收
|
|
||||||
*/
|
|
||||||
bool deallocate(T* obj) {
|
|
||||||
if (!obj) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
obj->~T();
|
|
||||||
} catch (const std::exception& e) {
|
|
||||||
Logger::log(LogLevel::Error, "ObjectPool: Exception in destructor: {}", e.what());
|
|
||||||
} catch (...) {
|
|
||||||
Logger::log(LogLevel::Error, "ObjectPool: Unknown exception in destructor");
|
|
||||||
}
|
|
||||||
|
|
||||||
auto& cache = getThreadCache();
|
|
||||||
if (cache.push(obj)) {
|
|
||||||
allocatedCount_.fetch_sub(1, std::memory_order_relaxed);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::lock_guard<std::mutex> lock(mutex_);
|
|
||||||
if (!isDestroyed_) {
|
|
||||||
pushFreeList(obj);
|
|
||||||
allocatedCount_.fetch_sub(1, std::memory_order_relaxed);
|
|
||||||
tryAutoShrink();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取当前已分配的对象数量
|
|
||||||
*/
|
|
||||||
size_t allocatedCount() const {
|
|
||||||
return allocatedCount_.load(std::memory_order_relaxed);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取池中总的对象容量
|
|
||||||
*/
|
|
||||||
size_t capacity() const {
|
|
||||||
return totalCapacity_.load(std::memory_order_relaxed);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取内存使用量(字节)
|
|
||||||
*/
|
|
||||||
size_t memoryUsage() const {
|
|
||||||
std::lock_guard<std::mutex> lock(mutex_);
|
|
||||||
return blocks_.size() * BlockSize * sizeof(T);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 清空所有内存块
|
|
||||||
*/
|
|
||||||
void clear() {
|
|
||||||
std::lock_guard<std::mutex> lock(mutex_);
|
|
||||||
|
|
||||||
isDestroyed_ = true;
|
|
||||||
|
|
||||||
for (auto& block : blocks_) {
|
|
||||||
alignedFree(block);
|
|
||||||
}
|
|
||||||
blocks_.clear();
|
|
||||||
freeListHead_ = nullptr;
|
|
||||||
totalCapacity_.store(0, std::memory_order_relaxed);
|
|
||||||
allocatedCount_.store(0, std::memory_order_relaxed);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
struct FreeNode {
|
|
||||||
FreeNode* next;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ThreadCache {
|
|
||||||
T* objects[PoolConfig::THREAD_CACHE_SIZE];
|
|
||||||
size_t count = 0;
|
|
||||||
|
|
||||||
T* pop() {
|
|
||||||
if (count > 0) {
|
|
||||||
return objects[--count];
|
|
||||||
}
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool push(T* obj) {
|
|
||||||
if (count < PoolConfig::THREAD_CACHE_SIZE) {
|
|
||||||
objects[count++] = obj;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void clear() {
|
|
||||||
count = 0;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
static ThreadCache& getThreadCache() {
|
|
||||||
thread_local ThreadCache cache;
|
|
||||||
return cache;
|
|
||||||
}
|
|
||||||
|
|
||||||
static constexpr size_t Alignment = alignof(T);
|
|
||||||
static constexpr size_t AlignedSize = ((sizeof(T) + Alignment - 1) / Alignment) * Alignment;
|
|
||||||
|
|
||||||
static void* alignedAlloc(size_t size) {
|
|
||||||
#ifdef _WIN32
|
|
||||||
return _aligned_malloc(size, Alignment);
|
|
||||||
#else
|
|
||||||
return std::aligned_alloc(Alignment, size);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
static void alignedFree(void* ptr) {
|
|
||||||
#ifdef _WIN32
|
|
||||||
_aligned_free(ptr);
|
|
||||||
#else
|
|
||||||
std::free(ptr);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void prewarmInternal() {
|
|
||||||
if (!freeListHead_) {
|
|
||||||
growInternal();
|
|
||||||
}
|
|
||||||
prewarmed_ = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void growInternal() {
|
|
||||||
size_t blockSize = AlignedSize > sizeof(FreeNode) ? AlignedSize : sizeof(FreeNode);
|
|
||||||
size_t totalSize = blockSize * BlockSize;
|
|
||||||
|
|
||||||
void* block = alignedAlloc(totalSize);
|
|
||||||
if (!block) {
|
|
||||||
Logger::log(LogLevel::Error, "ObjectPool: Failed to allocate memory block");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
blocks_.push_back(block);
|
|
||||||
totalCapacity_.fetch_add(BlockSize, std::memory_order_relaxed);
|
|
||||||
|
|
||||||
char* ptr = static_cast<char*>(block);
|
|
||||||
for (size_t i = 0; i < BlockSize; ++i) {
|
|
||||||
pushFreeList(reinterpret_cast<T*>(ptr + i * blockSize));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void pushFreeList(T* obj) {
|
|
||||||
FreeNode* node = reinterpret_cast<FreeNode*>(obj);
|
|
||||||
node->next = freeListHead_;
|
|
||||||
freeListHead_ = node;
|
|
||||||
}
|
|
||||||
|
|
||||||
T* popFreeList() {
|
|
||||||
if (!freeListHead_) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
FreeNode* node = freeListHead_;
|
|
||||||
freeListHead_ = freeListHead_->next;
|
|
||||||
return reinterpret_cast<T*>(node);
|
|
||||||
}
|
|
||||||
|
|
||||||
void tryAutoShrink() {
|
|
||||||
auto now = std::chrono::steady_clock::now();
|
|
||||||
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
|
|
||||||
now.time_since_epoch()).count();
|
|
||||||
|
|
||||||
if (elapsed - lastShrinkCheck_ < PoolConfig::SHRINK_THRESHOLD_MS) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
lastShrinkCheck_ = elapsed;
|
|
||||||
|
|
||||||
size_t allocated = allocatedCount_.load(std::memory_order_relaxed);
|
|
||||||
size_t capacity = totalCapacity_.load(std::memory_order_relaxed);
|
|
||||||
|
|
||||||
if (capacity > BlockSize &&
|
|
||||||
static_cast<double>(allocated) / capacity < PoolConfig::SHRINK_RATIO) {
|
|
||||||
shrinkInternal();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void shrinkInternal() {
|
|
||||||
size_t toFree = 0;
|
|
||||||
size_t freeCount = 0;
|
|
||||||
|
|
||||||
FreeNode* node = freeListHead_;
|
|
||||||
while (node) {
|
|
||||||
++freeCount;
|
|
||||||
node = node->next;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (freeCount < BlockSize) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t blocksToKeep = blocks_.size();
|
|
||||||
if (allocatedCount_.load(std::memory_order_relaxed) > 0) {
|
|
||||||
blocksToKeep = (allocatedCount_.load() + BlockSize - 1) / BlockSize;
|
|
||||||
blocksToKeep = std::max(blocksToKeep, size_t(1));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (blocksToKeep >= blocks_.size()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t blocksToRemove = blocks_.size() - blocksToKeep;
|
|
||||||
for (size_t i = 0; i < blocksToRemove; ++i) {
|
|
||||||
if (blocks_.empty()) break;
|
|
||||||
|
|
||||||
void* block = blocks_.back();
|
|
||||||
blocks_.pop_back();
|
|
||||||
alignedFree(block);
|
|
||||||
totalCapacity_.fetch_sub(BlockSize, std::memory_order_relaxed);
|
|
||||||
}
|
|
||||||
|
|
||||||
rebuildFreeList();
|
|
||||||
}
|
|
||||||
|
|
||||||
void rebuildFreeList() {
|
|
||||||
freeListHead_ = nullptr;
|
|
||||||
size_t blockSize = AlignedSize > sizeof(FreeNode) ? AlignedSize : sizeof(FreeNode);
|
|
||||||
|
|
||||||
for (void* block : blocks_) {
|
|
||||||
char* ptr = static_cast<char*>(block);
|
|
||||||
for (size_t i = 0; i < BlockSize; ++i) {
|
|
||||||
pushFreeList(reinterpret_cast<T*>(ptr + i * blockSize));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mutable std::mutex mutex_;
|
|
||||||
FreeNode* freeListHead_;
|
|
||||||
std::vector<void*> blocks_;
|
|
||||||
std::atomic<size_t> allocatedCount_;
|
|
||||||
std::atomic<size_t> totalCapacity_;
|
|
||||||
bool isDestroyed_;
|
|
||||||
uint64_t lastShrinkCheck_;
|
|
||||||
bool prewarmed_;
|
|
||||||
};
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 智能指针支持的内存池分配器
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
template <typename T, size_t BlockSize = PoolConfig::DEFAULT_BLOCK_SIZE>
|
|
||||||
class PooledAllocator {
|
|
||||||
public:
|
|
||||||
using PoolType = ObjectPool<T, BlockSize>;
|
|
||||||
|
|
||||||
PooledAllocator() : pool_(std::make_shared<PoolType>()) {}
|
|
||||||
explicit PooledAllocator(std::shared_ptr<PoolType> pool) : pool_(pool) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 创建一个使用内存池的对象(自动管理)
|
|
||||||
*/
|
|
||||||
template <typename... Args>
|
|
||||||
Ptr<T> makeShared(Args&&... args) {
|
|
||||||
std::weak_ptr<PoolType> weakPool = pool_;
|
|
||||||
T* obj = pool_->allocate(std::forward<Args>(args)...);
|
|
||||||
if (!obj) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
return Ptr<T>(obj, Deleter{weakPool});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取底层内存池
|
|
||||||
*/
|
|
||||||
std::shared_ptr<PoolType> getPool() const {
|
|
||||||
return pool_;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
struct Deleter {
|
|
||||||
std::weak_ptr<PoolType> pool;
|
|
||||||
|
|
||||||
void operator()(T* obj) const {
|
|
||||||
if (auto sharedPool = pool.lock()) {
|
|
||||||
if (!sharedPool->deallocate(obj)) {
|
|
||||||
Logger::log(LogLevel::Warn, "PooledAllocator: Pool destroyed, memory leaked");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Logger::log(LogLevel::Warn, "PooledAllocator: Pool expired during deallocation");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
std::shared_ptr<PoolType> pool_;
|
|
||||||
};
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 全局内存池管理器 - 自动管理所有池的生命周期
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
class ObjectPoolManager {
|
|
||||||
public:
|
|
||||||
static ObjectPoolManager& getInstance() {
|
|
||||||
static ObjectPoolManager instance;
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取指定类型的内存池(自动管理)
|
|
||||||
*/
|
|
||||||
template <typename T, size_t BlockSize = PoolConfig::DEFAULT_BLOCK_SIZE>
|
|
||||||
std::shared_ptr<ObjectPool<T, BlockSize>> getPool() {
|
|
||||||
static auto pool = std::make_shared<ObjectPool<T, BlockSize>>();
|
|
||||||
return pool;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 创建使用内存池的对象(自动管理)
|
|
||||||
*/
|
|
||||||
template <typename T, size_t BlockSize = PoolConfig::DEFAULT_BLOCK_SIZE, typename... Args>
|
|
||||||
Ptr<T> makePooled(Args&&... args) {
|
|
||||||
auto pool = getPool<T, BlockSize>();
|
|
||||||
std::weak_ptr<ObjectPool<T, BlockSize>> weakPool = pool;
|
|
||||||
T* obj = pool->allocate(std::forward<Args>(args)...);
|
|
||||||
if (!obj) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
return Ptr<T>(obj, [weakPool](T* p) {
|
|
||||||
if (auto sharedPool = weakPool.lock()) {
|
|
||||||
if (!sharedPool->deallocate(p)) {
|
|
||||||
Logger::log(LogLevel::Warn, "ObjectPoolManager: Pool destroyed during deallocation");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Logger::log(LogLevel::Warn, "ObjectPoolManager: Pool expired");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
ObjectPoolManager() = default;
|
|
||||||
~ObjectPoolManager() = default;
|
|
||||||
ObjectPoolManager(const ObjectPoolManager&) = delete;
|
|
||||||
ObjectPoolManager& operator=(const ObjectPoolManager&) = delete;
|
|
||||||
};
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 内存池宏定义(便于使用)
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
#define E2D_DECLARE_POOL(T, BlockSize) \
|
|
||||||
static extra2d::ObjectPool<T, BlockSize>& getPool() { \
|
|
||||||
static extra2d::ObjectPool<T, BlockSize> pool; \
|
|
||||||
return pool; \
|
|
||||||
}
|
|
||||||
|
|
||||||
#define E2D_MAKE_POOLED(T, ...) \
|
|
||||||
extra2d::ObjectPoolManager::getInstance().makePooled<T>(__VA_ARGS__)
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,9 +1,16 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
|
#include <extra2d/core/math_types.h>
|
||||||
|
#include <extra2d/core/string.h>
|
||||||
|
#include <extra2d/core/types.h>
|
||||||
|
|
||||||
|
=======
|
||||||
#include <extra2d/core/types.h>
|
#include <extra2d/core/types.h>
|
||||||
#include <extra2d/core/math_types.h>
|
#include <extra2d/core/math_types.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
>>>>>>> 9bf328b1dca01df84a07724394abc9a238739869
|
||||||
|
|
||||||
#include <SDL.h>
|
#include <SDL.h>
|
||||||
|
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -1,428 +0,0 @@
|
||||||
// stb_perlin.h - v0.5 - perlin noise
|
|
||||||
// public domain single-file C implementation by Sean Barrett
|
|
||||||
//
|
|
||||||
// LICENSE
|
|
||||||
//
|
|
||||||
// See end of file.
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// to create the implementation,
|
|
||||||
// #define STB_PERLIN_IMPLEMENTATION
|
|
||||||
// in *one* C/CPP file that includes this file.
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// Documentation:
|
|
||||||
//
|
|
||||||
// float stb_perlin_noise3( float x,
|
|
||||||
// float y,
|
|
||||||
// float z,
|
|
||||||
// int x_wrap=0,
|
|
||||||
// int y_wrap=0,
|
|
||||||
// int z_wrap=0)
|
|
||||||
//
|
|
||||||
// This function computes a random value at the coordinate (x,y,z).
|
|
||||||
// Adjacent random values are continuous but the noise fluctuates
|
|
||||||
// its randomness with period 1, i.e. takes on wholly unrelated values
|
|
||||||
// at integer points. Specifically, this implements Ken Perlin's
|
|
||||||
// revised noise function from 2002.
|
|
||||||
//
|
|
||||||
// The "wrap" parameters can be used to create wraparound noise that
|
|
||||||
// wraps at powers of two. The numbers MUST be powers of two. Specify
|
|
||||||
// 0 to mean "don't care". (The noise always wraps every 256 due
|
|
||||||
// details of the implementation, even if you ask for larger or no
|
|
||||||
// wrapping.)
|
|
||||||
//
|
|
||||||
// float stb_perlin_noise3_seed( float x,
|
|
||||||
// float y,
|
|
||||||
// float z,
|
|
||||||
// int x_wrap=0,
|
|
||||||
// int y_wrap=0,
|
|
||||||
// int z_wrap=0,
|
|
||||||
// int seed)
|
|
||||||
//
|
|
||||||
// As above, but 'seed' selects from multiple different variations of the
|
|
||||||
// noise function. The current implementation only uses the bottom 8 bits
|
|
||||||
// of 'seed', but possibly in the future more bits will be used.
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// Fractal Noise:
|
|
||||||
//
|
|
||||||
// Three common fractal noise functions are included, which produce
|
|
||||||
// a wide variety of nice effects depending on the parameters
|
|
||||||
// provided. Note that each function will call stb_perlin_noise3
|
|
||||||
// 'octaves' times, so this parameter will affect runtime.
|
|
||||||
//
|
|
||||||
// float stb_perlin_ridge_noise3(float x, float y, float z,
|
|
||||||
// float lacunarity, float gain, float offset, int octaves)
|
|
||||||
//
|
|
||||||
// float stb_perlin_fbm_noise3(float x, float y, float z,
|
|
||||||
// float lacunarity, float gain, int octaves)
|
|
||||||
//
|
|
||||||
// float stb_perlin_turbulence_noise3(float x, float y, float z,
|
|
||||||
// float lacunarity, float gain, int octaves)
|
|
||||||
//
|
|
||||||
// Typical values to start playing with:
|
|
||||||
// octaves = 6 -- number of "octaves" of noise3() to sum
|
|
||||||
// lacunarity = ~ 2.0 -- spacing between successive octaves (use exactly 2.0 for wrapping output)
|
|
||||||
// gain = 0.5 -- relative weighting applied to each successive octave
|
|
||||||
// offset = 1.0? -- used to invert the ridges, may need to be larger, not sure
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// Contributors:
|
|
||||||
// Jack Mott - additional noise functions
|
|
||||||
// Jordan Peck - seeded noise
|
|
||||||
//
|
|
||||||
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
extern float stb_perlin_noise3(float x, float y, float z, int x_wrap, int y_wrap, int z_wrap);
|
|
||||||
extern float stb_perlin_noise3_seed(float x, float y, float z, int x_wrap, int y_wrap, int z_wrap, int seed);
|
|
||||||
extern float stb_perlin_ridge_noise3(float x, float y, float z, float lacunarity, float gain, float offset, int octaves);
|
|
||||||
extern float stb_perlin_fbm_noise3(float x, float y, float z, float lacunarity, float gain, int octaves);
|
|
||||||
extern float stb_perlin_turbulence_noise3(float x, float y, float z, float lacunarity, float gain, int octaves);
|
|
||||||
extern float stb_perlin_noise3_wrap_nonpow2(float x, float y, float z, int x_wrap, int y_wrap, int z_wrap, unsigned char seed);
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef STB_PERLIN_IMPLEMENTATION
|
|
||||||
|
|
||||||
#include <math.h> // fabs()
|
|
||||||
|
|
||||||
// not same permutation table as Perlin's reference to avoid copyright issues;
|
|
||||||
// Perlin's table can be found at http://mrl.nyu.edu/~perlin/noise/
|
|
||||||
static unsigned char stb__perlin_randtab[512] =
|
|
||||||
{
|
|
||||||
23, 125, 161, 52, 103, 117, 70, 37, 247, 101, 203, 169, 124, 126, 44, 123,
|
|
||||||
152, 238, 145, 45, 171, 114, 253, 10, 192, 136, 4, 157, 249, 30, 35, 72,
|
|
||||||
175, 63, 77, 90, 181, 16, 96, 111, 133, 104, 75, 162, 93, 56, 66, 240,
|
|
||||||
8, 50, 84, 229, 49, 210, 173, 239, 141, 1, 87, 18, 2, 198, 143, 57,
|
|
||||||
225, 160, 58, 217, 168, 206, 245, 204, 199, 6, 73, 60, 20, 230, 211, 233,
|
|
||||||
94, 200, 88, 9, 74, 155, 33, 15, 219, 130, 226, 202, 83, 236, 42, 172,
|
|
||||||
165, 218, 55, 222, 46, 107, 98, 154, 109, 67, 196, 178, 127, 158, 13, 243,
|
|
||||||
65, 79, 166, 248, 25, 224, 115, 80, 68, 51, 184, 128, 232, 208, 151, 122,
|
|
||||||
26, 212, 105, 43, 179, 213, 235, 148, 146, 89, 14, 195, 28, 78, 112, 76,
|
|
||||||
250, 47, 24, 251, 140, 108, 186, 190, 228, 170, 183, 139, 39, 188, 244, 246,
|
|
||||||
132, 48, 119, 144, 180, 138, 134, 193, 82, 182, 120, 121, 86, 220, 209, 3,
|
|
||||||
91, 241, 149, 85, 205, 150, 113, 216, 31, 100, 41, 164, 177, 214, 153, 231,
|
|
||||||
38, 71, 185, 174, 97, 201, 29, 95, 7, 92, 54, 254, 191, 118, 34, 221,
|
|
||||||
131, 11, 163, 99, 234, 81, 227, 147, 156, 176, 17, 142, 69, 12, 110, 62,
|
|
||||||
27, 255, 0, 194, 59, 116, 242, 252, 19, 21, 187, 53, 207, 129, 64, 135,
|
|
||||||
61, 40, 167, 237, 102, 223, 106, 159, 197, 189, 215, 137, 36, 32, 22, 5,
|
|
||||||
|
|
||||||
// and a second copy so we don't need an extra mask or static initializer
|
|
||||||
23, 125, 161, 52, 103, 117, 70, 37, 247, 101, 203, 169, 124, 126, 44, 123,
|
|
||||||
152, 238, 145, 45, 171, 114, 253, 10, 192, 136, 4, 157, 249, 30, 35, 72,
|
|
||||||
175, 63, 77, 90, 181, 16, 96, 111, 133, 104, 75, 162, 93, 56, 66, 240,
|
|
||||||
8, 50, 84, 229, 49, 210, 173, 239, 141, 1, 87, 18, 2, 198, 143, 57,
|
|
||||||
225, 160, 58, 217, 168, 206, 245, 204, 199, 6, 73, 60, 20, 230, 211, 233,
|
|
||||||
94, 200, 88, 9, 74, 155, 33, 15, 219, 130, 226, 202, 83, 236, 42, 172,
|
|
||||||
165, 218, 55, 222, 46, 107, 98, 154, 109, 67, 196, 178, 127, 158, 13, 243,
|
|
||||||
65, 79, 166, 248, 25, 224, 115, 80, 68, 51, 184, 128, 232, 208, 151, 122,
|
|
||||||
26, 212, 105, 43, 179, 213, 235, 148, 146, 89, 14, 195, 28, 78, 112, 76,
|
|
||||||
250, 47, 24, 251, 140, 108, 186, 190, 228, 170, 183, 139, 39, 188, 244, 246,
|
|
||||||
132, 48, 119, 144, 180, 138, 134, 193, 82, 182, 120, 121, 86, 220, 209, 3,
|
|
||||||
91, 241, 149, 85, 205, 150, 113, 216, 31, 100, 41, 164, 177, 214, 153, 231,
|
|
||||||
38, 71, 185, 174, 97, 201, 29, 95, 7, 92, 54, 254, 191, 118, 34, 221,
|
|
||||||
131, 11, 163, 99, 234, 81, 227, 147, 156, 176, 17, 142, 69, 12, 110, 62,
|
|
||||||
27, 255, 0, 194, 59, 116, 242, 252, 19, 21, 187, 53, 207, 129, 64, 135,
|
|
||||||
61, 40, 167, 237, 102, 223, 106, 159, 197, 189, 215, 137, 36, 32, 22, 5,
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
// perlin's gradient has 12 cases so some get used 1/16th of the time
|
|
||||||
// and some 2/16ths. We reduce bias by changing those fractions
|
|
||||||
// to 5/64ths and 6/64ths
|
|
||||||
|
|
||||||
// this array is designed to match the previous implementation
|
|
||||||
// of gradient hash: indices[stb__perlin_randtab[i]&63]
|
|
||||||
static unsigned char stb__perlin_randtab_grad_idx[512] =
|
|
||||||
{
|
|
||||||
7, 9, 5, 0, 11, 1, 6, 9, 3, 9, 11, 1, 8, 10, 4, 7,
|
|
||||||
8, 6, 1, 5, 3, 10, 9, 10, 0, 8, 4, 1, 5, 2, 7, 8,
|
|
||||||
7, 11, 9, 10, 1, 0, 4, 7, 5, 0, 11, 6, 1, 4, 2, 8,
|
|
||||||
8, 10, 4, 9, 9, 2, 5, 7, 9, 1, 7, 2, 2, 6, 11, 5,
|
|
||||||
5, 4, 6, 9, 0, 1, 1, 0, 7, 6, 9, 8, 4, 10, 3, 1,
|
|
||||||
2, 8, 8, 9, 10, 11, 5, 11, 11, 2, 6, 10, 3, 4, 2, 4,
|
|
||||||
9, 10, 3, 2, 6, 3, 6, 10, 5, 3, 4, 10, 11, 2, 9, 11,
|
|
||||||
1, 11, 10, 4, 9, 4, 11, 0, 4, 11, 4, 0, 0, 0, 7, 6,
|
|
||||||
10, 4, 1, 3, 11, 5, 3, 4, 2, 9, 1, 3, 0, 1, 8, 0,
|
|
||||||
6, 7, 8, 7, 0, 4, 6, 10, 8, 2, 3, 11, 11, 8, 0, 2,
|
|
||||||
4, 8, 3, 0, 0, 10, 6, 1, 2, 2, 4, 5, 6, 0, 1, 3,
|
|
||||||
11, 9, 5, 5, 9, 6, 9, 8, 3, 8, 1, 8, 9, 6, 9, 11,
|
|
||||||
10, 7, 5, 6, 5, 9, 1, 3, 7, 0, 2, 10, 11, 2, 6, 1,
|
|
||||||
3, 11, 7, 7, 2, 1, 7, 3, 0, 8, 1, 1, 5, 0, 6, 10,
|
|
||||||
11, 11, 0, 2, 7, 0, 10, 8, 3, 5, 7, 1, 11, 1, 0, 7,
|
|
||||||
9, 0, 11, 5, 10, 3, 2, 3, 5, 9, 7, 9, 8, 4, 6, 5,
|
|
||||||
|
|
||||||
// and a second copy so we don't need an extra mask or static initializer
|
|
||||||
7, 9, 5, 0, 11, 1, 6, 9, 3, 9, 11, 1, 8, 10, 4, 7,
|
|
||||||
8, 6, 1, 5, 3, 10, 9, 10, 0, 8, 4, 1, 5, 2, 7, 8,
|
|
||||||
7, 11, 9, 10, 1, 0, 4, 7, 5, 0, 11, 6, 1, 4, 2, 8,
|
|
||||||
8, 10, 4, 9, 9, 2, 5, 7, 9, 1, 7, 2, 2, 6, 11, 5,
|
|
||||||
5, 4, 6, 9, 0, 1, 1, 0, 7, 6, 9, 8, 4, 10, 3, 1,
|
|
||||||
2, 8, 8, 9, 10, 11, 5, 11, 11, 2, 6, 10, 3, 4, 2, 4,
|
|
||||||
9, 10, 3, 2, 6, 3, 6, 10, 5, 3, 4, 10, 11, 2, 9, 11,
|
|
||||||
1, 11, 10, 4, 9, 4, 11, 0, 4, 11, 4, 0, 0, 0, 7, 6,
|
|
||||||
10, 4, 1, 3, 11, 5, 3, 4, 2, 9, 1, 3, 0, 1, 8, 0,
|
|
||||||
6, 7, 8, 7, 0, 4, 6, 10, 8, 2, 3, 11, 11, 8, 0, 2,
|
|
||||||
4, 8, 3, 0, 0, 10, 6, 1, 2, 2, 4, 5, 6, 0, 1, 3,
|
|
||||||
11, 9, 5, 5, 9, 6, 9, 8, 3, 8, 1, 8, 9, 6, 9, 11,
|
|
||||||
10, 7, 5, 6, 5, 9, 1, 3, 7, 0, 2, 10, 11, 2, 6, 1,
|
|
||||||
3, 11, 7, 7, 2, 1, 7, 3, 0, 8, 1, 1, 5, 0, 6, 10,
|
|
||||||
11, 11, 0, 2, 7, 0, 10, 8, 3, 5, 7, 1, 11, 1, 0, 7,
|
|
||||||
9, 0, 11, 5, 10, 3, 2, 3, 5, 9, 7, 9, 8, 4, 6, 5,
|
|
||||||
};
|
|
||||||
|
|
||||||
static float stb__perlin_lerp(float a, float b, float t)
|
|
||||||
{
|
|
||||||
return a + (b-a) * t;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int stb__perlin_fastfloor(float a)
|
|
||||||
{
|
|
||||||
int ai = (int) a;
|
|
||||||
return (a < ai) ? ai-1 : ai;
|
|
||||||
}
|
|
||||||
|
|
||||||
// different grad function from Perlin's, but easy to modify to match reference
|
|
||||||
static float stb__perlin_grad(int grad_idx, float x, float y, float z)
|
|
||||||
{
|
|
||||||
static float basis[12][4] =
|
|
||||||
{
|
|
||||||
{ 1, 1, 0 },
|
|
||||||
{ -1, 1, 0 },
|
|
||||||
{ 1,-1, 0 },
|
|
||||||
{ -1,-1, 0 },
|
|
||||||
{ 1, 0, 1 },
|
|
||||||
{ -1, 0, 1 },
|
|
||||||
{ 1, 0,-1 },
|
|
||||||
{ -1, 0,-1 },
|
|
||||||
{ 0, 1, 1 },
|
|
||||||
{ 0,-1, 1 },
|
|
||||||
{ 0, 1,-1 },
|
|
||||||
{ 0,-1,-1 },
|
|
||||||
};
|
|
||||||
|
|
||||||
float *grad = basis[grad_idx];
|
|
||||||
return grad[0]*x + grad[1]*y + grad[2]*z;
|
|
||||||
}
|
|
||||||
|
|
||||||
float stb_perlin_noise3_internal(float x, float y, float z, int x_wrap, int y_wrap, int z_wrap, unsigned char seed)
|
|
||||||
{
|
|
||||||
float u,v,w;
|
|
||||||
float n000,n001,n010,n011,n100,n101,n110,n111;
|
|
||||||
float n00,n01,n10,n11;
|
|
||||||
float n0,n1;
|
|
||||||
|
|
||||||
unsigned int x_mask = (x_wrap-1) & 255;
|
|
||||||
unsigned int y_mask = (y_wrap-1) & 255;
|
|
||||||
unsigned int z_mask = (z_wrap-1) & 255;
|
|
||||||
int px = stb__perlin_fastfloor(x);
|
|
||||||
int py = stb__perlin_fastfloor(y);
|
|
||||||
int pz = stb__perlin_fastfloor(z);
|
|
||||||
int x0 = px & x_mask, x1 = (px+1) & x_mask;
|
|
||||||
int y0 = py & y_mask, y1 = (py+1) & y_mask;
|
|
||||||
int z0 = pz & z_mask, z1 = (pz+1) & z_mask;
|
|
||||||
int r0,r1, r00,r01,r10,r11;
|
|
||||||
|
|
||||||
#define stb__perlin_ease(a) (((a*6-15)*a + 10) * a * a * a)
|
|
||||||
|
|
||||||
x -= px; u = stb__perlin_ease(x);
|
|
||||||
y -= py; v = stb__perlin_ease(y);
|
|
||||||
z -= pz; w = stb__perlin_ease(z);
|
|
||||||
|
|
||||||
r0 = stb__perlin_randtab[x0+seed];
|
|
||||||
r1 = stb__perlin_randtab[x1+seed];
|
|
||||||
|
|
||||||
r00 = stb__perlin_randtab[r0+y0];
|
|
||||||
r01 = stb__perlin_randtab[r0+y1];
|
|
||||||
r10 = stb__perlin_randtab[r1+y0];
|
|
||||||
r11 = stb__perlin_randtab[r1+y1];
|
|
||||||
|
|
||||||
n000 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r00+z0], x , y , z );
|
|
||||||
n001 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r00+z1], x , y , z-1 );
|
|
||||||
n010 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r01+z0], x , y-1, z );
|
|
||||||
n011 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r01+z1], x , y-1, z-1 );
|
|
||||||
n100 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r10+z0], x-1, y , z );
|
|
||||||
n101 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r10+z1], x-1, y , z-1 );
|
|
||||||
n110 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r11+z0], x-1, y-1, z );
|
|
||||||
n111 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r11+z1], x-1, y-1, z-1 );
|
|
||||||
|
|
||||||
n00 = stb__perlin_lerp(n000,n001,w);
|
|
||||||
n01 = stb__perlin_lerp(n010,n011,w);
|
|
||||||
n10 = stb__perlin_lerp(n100,n101,w);
|
|
||||||
n11 = stb__perlin_lerp(n110,n111,w);
|
|
||||||
|
|
||||||
n0 = stb__perlin_lerp(n00,n01,v);
|
|
||||||
n1 = stb__perlin_lerp(n10,n11,v);
|
|
||||||
|
|
||||||
return stb__perlin_lerp(n0,n1,u);
|
|
||||||
}
|
|
||||||
|
|
||||||
float stb_perlin_noise3(float x, float y, float z, int x_wrap, int y_wrap, int z_wrap)
|
|
||||||
{
|
|
||||||
return stb_perlin_noise3_internal(x,y,z,x_wrap,y_wrap,z_wrap,0);
|
|
||||||
}
|
|
||||||
|
|
||||||
float stb_perlin_noise3_seed(float x, float y, float z, int x_wrap, int y_wrap, int z_wrap, int seed)
|
|
||||||
{
|
|
||||||
return stb_perlin_noise3_internal(x,y,z,x_wrap,y_wrap,z_wrap, (unsigned char) seed);
|
|
||||||
}
|
|
||||||
|
|
||||||
float stb_perlin_ridge_noise3(float x, float y, float z, float lacunarity, float gain, float offset, int octaves)
|
|
||||||
{
|
|
||||||
int i;
|
|
||||||
float frequency = 1.0f;
|
|
||||||
float prev = 1.0f;
|
|
||||||
float amplitude = 0.5f;
|
|
||||||
float sum = 0.0f;
|
|
||||||
|
|
||||||
for (i = 0; i < octaves; i++) {
|
|
||||||
float r = stb_perlin_noise3_internal(x*frequency,y*frequency,z*frequency,0,0,0,(unsigned char)i);
|
|
||||||
r = offset - (float) fabs(r);
|
|
||||||
r = r*r;
|
|
||||||
sum += r*amplitude*prev;
|
|
||||||
prev = r;
|
|
||||||
frequency *= lacunarity;
|
|
||||||
amplitude *= gain;
|
|
||||||
}
|
|
||||||
return sum;
|
|
||||||
}
|
|
||||||
|
|
||||||
float stb_perlin_fbm_noise3(float x, float y, float z, float lacunarity, float gain, int octaves)
|
|
||||||
{
|
|
||||||
int i;
|
|
||||||
float frequency = 1.0f;
|
|
||||||
float amplitude = 1.0f;
|
|
||||||
float sum = 0.0f;
|
|
||||||
|
|
||||||
for (i = 0; i < octaves; i++) {
|
|
||||||
sum += stb_perlin_noise3_internal(x*frequency,y*frequency,z*frequency,0,0,0,(unsigned char)i)*amplitude;
|
|
||||||
frequency *= lacunarity;
|
|
||||||
amplitude *= gain;
|
|
||||||
}
|
|
||||||
return sum;
|
|
||||||
}
|
|
||||||
|
|
||||||
float stb_perlin_turbulence_noise3(float x, float y, float z, float lacunarity, float gain, int octaves)
|
|
||||||
{
|
|
||||||
int i;
|
|
||||||
float frequency = 1.0f;
|
|
||||||
float amplitude = 1.0f;
|
|
||||||
float sum = 0.0f;
|
|
||||||
|
|
||||||
for (i = 0; i < octaves; i++) {
|
|
||||||
float r = stb_perlin_noise3_internal(x*frequency,y*frequency,z*frequency,0,0,0,(unsigned char)i)*amplitude;
|
|
||||||
sum += (float) fabs(r);
|
|
||||||
frequency *= lacunarity;
|
|
||||||
amplitude *= gain;
|
|
||||||
}
|
|
||||||
return sum;
|
|
||||||
}
|
|
||||||
|
|
||||||
float stb_perlin_noise3_wrap_nonpow2(float x, float y, float z, int x_wrap, int y_wrap, int z_wrap, unsigned char seed)
|
|
||||||
{
|
|
||||||
float u,v,w;
|
|
||||||
float n000,n001,n010,n011,n100,n101,n110,n111;
|
|
||||||
float n00,n01,n10,n11;
|
|
||||||
float n0,n1;
|
|
||||||
|
|
||||||
int px = stb__perlin_fastfloor(x);
|
|
||||||
int py = stb__perlin_fastfloor(y);
|
|
||||||
int pz = stb__perlin_fastfloor(z);
|
|
||||||
int x_wrap2 = (x_wrap ? x_wrap : 256);
|
|
||||||
int y_wrap2 = (y_wrap ? y_wrap : 256);
|
|
||||||
int z_wrap2 = (z_wrap ? z_wrap : 256);
|
|
||||||
int x0 = px % x_wrap2, x1;
|
|
||||||
int y0 = py % y_wrap2, y1;
|
|
||||||
int z0 = pz % z_wrap2, z1;
|
|
||||||
int r0,r1, r00,r01,r10,r11;
|
|
||||||
|
|
||||||
if (x0 < 0) x0 += x_wrap2;
|
|
||||||
if (y0 < 0) y0 += y_wrap2;
|
|
||||||
if (z0 < 0) z0 += z_wrap2;
|
|
||||||
x1 = (x0+1) % x_wrap2;
|
|
||||||
y1 = (y0+1) % y_wrap2;
|
|
||||||
z1 = (z0+1) % z_wrap2;
|
|
||||||
|
|
||||||
#define stb__perlin_ease(a) (((a*6-15)*a + 10) * a * a * a)
|
|
||||||
|
|
||||||
x -= px; u = stb__perlin_ease(x);
|
|
||||||
y -= py; v = stb__perlin_ease(y);
|
|
||||||
z -= pz; w = stb__perlin_ease(z);
|
|
||||||
|
|
||||||
r0 = stb__perlin_randtab[x0];
|
|
||||||
r0 = stb__perlin_randtab[r0+seed];
|
|
||||||
r1 = stb__perlin_randtab[x1];
|
|
||||||
r1 = stb__perlin_randtab[r1+seed];
|
|
||||||
|
|
||||||
r00 = stb__perlin_randtab[r0+y0];
|
|
||||||
r01 = stb__perlin_randtab[r0+y1];
|
|
||||||
r10 = stb__perlin_randtab[r1+y0];
|
|
||||||
r11 = stb__perlin_randtab[r1+y1];
|
|
||||||
|
|
||||||
n000 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r00+z0], x , y , z );
|
|
||||||
n001 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r00+z1], x , y , z-1 );
|
|
||||||
n010 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r01+z0], x , y-1, z );
|
|
||||||
n011 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r01+z1], x , y-1, z-1 );
|
|
||||||
n100 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r10+z0], x-1, y , z );
|
|
||||||
n101 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r10+z1], x-1, y , z-1 );
|
|
||||||
n110 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r11+z0], x-1, y-1, z );
|
|
||||||
n111 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r11+z1], x-1, y-1, z-1 );
|
|
||||||
|
|
||||||
n00 = stb__perlin_lerp(n000,n001,w);
|
|
||||||
n01 = stb__perlin_lerp(n010,n011,w);
|
|
||||||
n10 = stb__perlin_lerp(n100,n101,w);
|
|
||||||
n11 = stb__perlin_lerp(n110,n111,w);
|
|
||||||
|
|
||||||
n0 = stb__perlin_lerp(n00,n01,v);
|
|
||||||
n1 = stb__perlin_lerp(n10,n11,v);
|
|
||||||
|
|
||||||
return stb__perlin_lerp(n0,n1,u);
|
|
||||||
}
|
|
||||||
#endif // STB_PERLIN_IMPLEMENTATION
|
|
||||||
|
|
||||||
/*
|
|
||||||
------------------------------------------------------------------------------
|
|
||||||
This software is available under 2 licenses -- choose whichever you prefer.
|
|
||||||
------------------------------------------------------------------------------
|
|
||||||
ALTERNATIVE A - MIT License
|
|
||||||
Copyright (c) 2017 Sean Barrett
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
||||||
this software and associated documentation files (the "Software"), to deal in
|
|
||||||
the Software without restriction, including without limitation the rights to
|
|
||||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
|
||||||
of the Software, and to permit persons to whom the Software is furnished to do
|
|
||||||
so, subject to the following conditions:
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
------------------------------------------------------------------------------
|
|
||||||
ALTERNATIVE B - Public Domain (www.unlicense.org)
|
|
||||||
This is free and unencumbered software released into the public domain.
|
|
||||||
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
|
|
||||||
software, either in source code form or as a compiled binary, for any purpose,
|
|
||||||
commercial or non-commercial, and by any means.
|
|
||||||
In jurisdictions that recognize copyright laws, the author or authors of this
|
|
||||||
software dedicate any and all copyright interest in the software to the public
|
|
||||||
domain. We make this dedication for the benefit of the public at large and to
|
|
||||||
the detriment of our heirs and successors. We intend this dedication to be an
|
|
||||||
overt act of relinquishment in perpetuity of all present and future rights to
|
|
||||||
this software under copyright law.
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
|
||||||
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
||||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
------------------------------------------------------------------------------
|
|
||||||
*/
|
|
||||||
|
|
@ -1,623 +0,0 @@
|
||||||
// stb_rect_pack.h - v1.01 - public domain - rectangle packing
|
|
||||||
// Sean Barrett 2014
|
|
||||||
//
|
|
||||||
// Useful for e.g. packing rectangular textures into an atlas.
|
|
||||||
// Does not do rotation.
|
|
||||||
//
|
|
||||||
// Before #including,
|
|
||||||
//
|
|
||||||
// #define STB_RECT_PACK_IMPLEMENTATION
|
|
||||||
//
|
|
||||||
// in the file that you want to have the implementation.
|
|
||||||
//
|
|
||||||
// Not necessarily the awesomest packing method, but better than
|
|
||||||
// the totally naive one in stb_truetype (which is primarily what
|
|
||||||
// this is meant to replace).
|
|
||||||
//
|
|
||||||
// Has only had a few tests run, may have issues.
|
|
||||||
//
|
|
||||||
// More docs to come.
|
|
||||||
//
|
|
||||||
// No memory allocations; uses qsort() and assert() from stdlib.
|
|
||||||
// Can override those by defining STBRP_SORT and STBRP_ASSERT.
|
|
||||||
//
|
|
||||||
// This library currently uses the Skyline Bottom-Left algorithm.
|
|
||||||
//
|
|
||||||
// Please note: better rectangle packers are welcome! Please
|
|
||||||
// implement them to the same API, but with a different init
|
|
||||||
// function.
|
|
||||||
//
|
|
||||||
// Credits
|
|
||||||
//
|
|
||||||
// Library
|
|
||||||
// Sean Barrett
|
|
||||||
// Minor features
|
|
||||||
// Martins Mozeiko
|
|
||||||
// github:IntellectualKitty
|
|
||||||
//
|
|
||||||
// Bugfixes / warning fixes
|
|
||||||
// Jeremy Jaussaud
|
|
||||||
// Fabian Giesen
|
|
||||||
//
|
|
||||||
// Version history:
|
|
||||||
//
|
|
||||||
// 1.01 (2021-07-11) always use large rect mode, expose STBRP__MAXVAL in public section
|
|
||||||
// 1.00 (2019-02-25) avoid small space waste; gracefully fail too-wide rectangles
|
|
||||||
// 0.99 (2019-02-07) warning fixes
|
|
||||||
// 0.11 (2017-03-03) return packing success/fail result
|
|
||||||
// 0.10 (2016-10-25) remove cast-away-const to avoid warnings
|
|
||||||
// 0.09 (2016-08-27) fix compiler warnings
|
|
||||||
// 0.08 (2015-09-13) really fix bug with empty rects (w=0 or h=0)
|
|
||||||
// 0.07 (2015-09-13) fix bug with empty rects (w=0 or h=0)
|
|
||||||
// 0.06 (2015-04-15) added STBRP_SORT to allow replacing qsort
|
|
||||||
// 0.05: added STBRP_ASSERT to allow replacing assert
|
|
||||||
// 0.04: fixed minor bug in STBRP_LARGE_RECTS support
|
|
||||||
// 0.01: initial release
|
|
||||||
//
|
|
||||||
// LICENSE
|
|
||||||
//
|
|
||||||
// See end of file for license information.
|
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////////
|
|
||||||
//
|
|
||||||
// INCLUDE SECTION
|
|
||||||
//
|
|
||||||
|
|
||||||
#ifndef STB_INCLUDE_STB_RECT_PACK_H
|
|
||||||
#define STB_INCLUDE_STB_RECT_PACK_H
|
|
||||||
|
|
||||||
#define STB_RECT_PACK_VERSION 1
|
|
||||||
|
|
||||||
#ifdef STBRP_STATIC
|
|
||||||
#define STBRP_DEF static
|
|
||||||
#else
|
|
||||||
#define STBRP_DEF extern
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
typedef struct stbrp_context stbrp_context;
|
|
||||||
typedef struct stbrp_node stbrp_node;
|
|
||||||
typedef struct stbrp_rect stbrp_rect;
|
|
||||||
|
|
||||||
typedef int stbrp_coord;
|
|
||||||
|
|
||||||
#define STBRP__MAXVAL 0x7fffffff
|
|
||||||
// Mostly for internal use, but this is the maximum supported coordinate value.
|
|
||||||
|
|
||||||
STBRP_DEF int stbrp_pack_rects (stbrp_context *context, stbrp_rect *rects, int num_rects);
|
|
||||||
// Assign packed locations to rectangles. The rectangles are of type
|
|
||||||
// 'stbrp_rect' defined below, stored in the array 'rects', and there
|
|
||||||
// are 'num_rects' many of them.
|
|
||||||
//
|
|
||||||
// Rectangles which are successfully packed have the 'was_packed' flag
|
|
||||||
// set to a non-zero value and 'x' and 'y' store the minimum location
|
|
||||||
// on each axis (i.e. bottom-left in cartesian coordinates, top-left
|
|
||||||
// if you imagine y increasing downwards). Rectangles which do not fit
|
|
||||||
// have the 'was_packed' flag set to 0.
|
|
||||||
//
|
|
||||||
// You should not try to access the 'rects' array from another thread
|
|
||||||
// while this function is running, as the function temporarily reorders
|
|
||||||
// the array while it executes.
|
|
||||||
//
|
|
||||||
// To pack into another rectangle, you need to call stbrp_init_target
|
|
||||||
// again. To continue packing into the same rectangle, you can call
|
|
||||||
// this function again. Calling this multiple times with multiple rect
|
|
||||||
// arrays will probably produce worse packing results than calling it
|
|
||||||
// a single time with the full rectangle array, but the option is
|
|
||||||
// available.
|
|
||||||
//
|
|
||||||
// The function returns 1 if all of the rectangles were successfully
|
|
||||||
// packed and 0 otherwise.
|
|
||||||
|
|
||||||
struct stbrp_rect
|
|
||||||
{
|
|
||||||
// reserved for your use:
|
|
||||||
int id;
|
|
||||||
|
|
||||||
// input:
|
|
||||||
stbrp_coord w, h;
|
|
||||||
|
|
||||||
// output:
|
|
||||||
stbrp_coord x, y;
|
|
||||||
int was_packed; // non-zero if valid packing
|
|
||||||
|
|
||||||
}; // 16 bytes, nominally
|
|
||||||
|
|
||||||
|
|
||||||
STBRP_DEF void stbrp_init_target (stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes);
|
|
||||||
// Initialize a rectangle packer to:
|
|
||||||
// pack a rectangle that is 'width' by 'height' in dimensions
|
|
||||||
// using temporary storage provided by the array 'nodes', which is 'num_nodes' long
|
|
||||||
//
|
|
||||||
// You must call this function every time you start packing into a new target.
|
|
||||||
//
|
|
||||||
// There is no "shutdown" function. The 'nodes' memory must stay valid for
|
|
||||||
// the following stbrp_pack_rects() call (or calls), but can be freed after
|
|
||||||
// the call (or calls) finish.
|
|
||||||
//
|
|
||||||
// Note: to guarantee best results, either:
|
|
||||||
// 1. make sure 'num_nodes' >= 'width'
|
|
||||||
// or 2. call stbrp_allow_out_of_mem() defined below with 'allow_out_of_mem = 1'
|
|
||||||
//
|
|
||||||
// If you don't do either of the above things, widths will be quantized to multiples
|
|
||||||
// of small integers to guarantee the algorithm doesn't run out of temporary storage.
|
|
||||||
//
|
|
||||||
// If you do #2, then the non-quantized algorithm will be used, but the algorithm
|
|
||||||
// may run out of temporary storage and be unable to pack some rectangles.
|
|
||||||
|
|
||||||
STBRP_DEF void stbrp_setup_allow_out_of_mem (stbrp_context *context, int allow_out_of_mem);
|
|
||||||
// Optionally call this function after init but before doing any packing to
|
|
||||||
// change the handling of the out-of-temp-memory scenario, described above.
|
|
||||||
// If you call init again, this will be reset to the default (false).
|
|
||||||
|
|
||||||
|
|
||||||
STBRP_DEF void stbrp_setup_heuristic (stbrp_context *context, int heuristic);
|
|
||||||
// Optionally select which packing heuristic the library should use. Different
|
|
||||||
// heuristics will produce better/worse results for different data sets.
|
|
||||||
// If you call init again, this will be reset to the default.
|
|
||||||
|
|
||||||
enum
|
|
||||||
{
|
|
||||||
STBRP_HEURISTIC_Skyline_default=0,
|
|
||||||
STBRP_HEURISTIC_Skyline_BL_sortHeight = STBRP_HEURISTIC_Skyline_default,
|
|
||||||
STBRP_HEURISTIC_Skyline_BF_sortHeight
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////////
|
|
||||||
//
|
|
||||||
// the details of the following structures don't matter to you, but they must
|
|
||||||
// be visible so you can handle the memory allocations for them
|
|
||||||
|
|
||||||
struct stbrp_node
|
|
||||||
{
|
|
||||||
stbrp_coord x,y;
|
|
||||||
stbrp_node *next;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct stbrp_context
|
|
||||||
{
|
|
||||||
int width;
|
|
||||||
int height;
|
|
||||||
int align;
|
|
||||||
int init_mode;
|
|
||||||
int heuristic;
|
|
||||||
int num_nodes;
|
|
||||||
stbrp_node *active_head;
|
|
||||||
stbrp_node *free_head;
|
|
||||||
stbrp_node extra[2]; // we allocate two extra nodes so optimal user-node-count is 'width' not 'width+2'
|
|
||||||
};
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////////
|
|
||||||
//
|
|
||||||
// IMPLEMENTATION SECTION
|
|
||||||
//
|
|
||||||
|
|
||||||
#ifdef STB_RECT_PACK_IMPLEMENTATION
|
|
||||||
#ifndef STBRP_SORT
|
|
||||||
#include <stdlib.h>
|
|
||||||
#define STBRP_SORT qsort
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef STBRP_ASSERT
|
|
||||||
#include <assert.h>
|
|
||||||
#define STBRP_ASSERT assert
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef _MSC_VER
|
|
||||||
#define STBRP__NOTUSED(v) (void)(v)
|
|
||||||
#define STBRP__CDECL __cdecl
|
|
||||||
#else
|
|
||||||
#define STBRP__NOTUSED(v) (void)sizeof(v)
|
|
||||||
#define STBRP__CDECL
|
|
||||||
#endif
|
|
||||||
|
|
||||||
enum
|
|
||||||
{
|
|
||||||
STBRP__INIT_skyline = 1
|
|
||||||
};
|
|
||||||
|
|
||||||
STBRP_DEF void stbrp_setup_heuristic(stbrp_context *context, int heuristic)
|
|
||||||
{
|
|
||||||
switch (context->init_mode) {
|
|
||||||
case STBRP__INIT_skyline:
|
|
||||||
STBRP_ASSERT(heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight || heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight);
|
|
||||||
context->heuristic = heuristic;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
STBRP_ASSERT(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
STBRP_DEF void stbrp_setup_allow_out_of_mem(stbrp_context *context, int allow_out_of_mem)
|
|
||||||
{
|
|
||||||
if (allow_out_of_mem)
|
|
||||||
// if it's ok to run out of memory, then don't bother aligning them;
|
|
||||||
// this gives better packing, but may fail due to OOM (even though
|
|
||||||
// the rectangles easily fit). @TODO a smarter approach would be to only
|
|
||||||
// quantize once we've hit OOM, then we could get rid of this parameter.
|
|
||||||
context->align = 1;
|
|
||||||
else {
|
|
||||||
// if it's not ok to run out of memory, then quantize the widths
|
|
||||||
// so that num_nodes is always enough nodes.
|
|
||||||
//
|
|
||||||
// I.e. num_nodes * align >= width
|
|
||||||
// align >= width / num_nodes
|
|
||||||
// align = ceil(width/num_nodes)
|
|
||||||
|
|
||||||
context->align = (context->width + context->num_nodes-1) / context->num_nodes;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
STBRP_DEF void stbrp_init_target(stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes)
|
|
||||||
{
|
|
||||||
int i;
|
|
||||||
|
|
||||||
for (i=0; i < num_nodes-1; ++i)
|
|
||||||
nodes[i].next = &nodes[i+1];
|
|
||||||
nodes[i].next = NULL;
|
|
||||||
context->init_mode = STBRP__INIT_skyline;
|
|
||||||
context->heuristic = STBRP_HEURISTIC_Skyline_default;
|
|
||||||
context->free_head = &nodes[0];
|
|
||||||
context->active_head = &context->extra[0];
|
|
||||||
context->width = width;
|
|
||||||
context->height = height;
|
|
||||||
context->num_nodes = num_nodes;
|
|
||||||
stbrp_setup_allow_out_of_mem(context, 0);
|
|
||||||
|
|
||||||
// node 0 is the full width, node 1 is the sentinel (lets us not store width explicitly)
|
|
||||||
context->extra[0].x = 0;
|
|
||||||
context->extra[0].y = 0;
|
|
||||||
context->extra[0].next = &context->extra[1];
|
|
||||||
context->extra[1].x = (stbrp_coord) width;
|
|
||||||
context->extra[1].y = (1<<30);
|
|
||||||
context->extra[1].next = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// find minimum y position if it starts at x1
|
|
||||||
static int stbrp__skyline_find_min_y(stbrp_context *c, stbrp_node *first, int x0, int width, int *pwaste)
|
|
||||||
{
|
|
||||||
stbrp_node *node = first;
|
|
||||||
int x1 = x0 + width;
|
|
||||||
int min_y, visited_width, waste_area;
|
|
||||||
|
|
||||||
STBRP__NOTUSED(c);
|
|
||||||
|
|
||||||
STBRP_ASSERT(first->x <= x0);
|
|
||||||
|
|
||||||
#if 0
|
|
||||||
// skip in case we're past the node
|
|
||||||
while (node->next->x <= x0)
|
|
||||||
++node;
|
|
||||||
#else
|
|
||||||
STBRP_ASSERT(node->next->x > x0); // we ended up handling this in the caller for efficiency
|
|
||||||
#endif
|
|
||||||
|
|
||||||
STBRP_ASSERT(node->x <= x0);
|
|
||||||
|
|
||||||
min_y = 0;
|
|
||||||
waste_area = 0;
|
|
||||||
visited_width = 0;
|
|
||||||
while (node->x < x1) {
|
|
||||||
if (node->y > min_y) {
|
|
||||||
// raise min_y higher.
|
|
||||||
// we've accounted for all waste up to min_y,
|
|
||||||
// but we'll now add more waste for everything we've visted
|
|
||||||
waste_area += visited_width * (node->y - min_y);
|
|
||||||
min_y = node->y;
|
|
||||||
// the first time through, visited_width might be reduced
|
|
||||||
if (node->x < x0)
|
|
||||||
visited_width += node->next->x - x0;
|
|
||||||
else
|
|
||||||
visited_width += node->next->x - node->x;
|
|
||||||
} else {
|
|
||||||
// add waste area
|
|
||||||
int under_width = node->next->x - node->x;
|
|
||||||
if (under_width + visited_width > width)
|
|
||||||
under_width = width - visited_width;
|
|
||||||
waste_area += under_width * (min_y - node->y);
|
|
||||||
visited_width += under_width;
|
|
||||||
}
|
|
||||||
node = node->next;
|
|
||||||
}
|
|
||||||
|
|
||||||
*pwaste = waste_area;
|
|
||||||
return min_y;
|
|
||||||
}
|
|
||||||
|
|
||||||
typedef struct
|
|
||||||
{
|
|
||||||
int x,y;
|
|
||||||
stbrp_node **prev_link;
|
|
||||||
} stbrp__findresult;
|
|
||||||
|
|
||||||
static stbrp__findresult stbrp__skyline_find_best_pos(stbrp_context *c, int width, int height)
|
|
||||||
{
|
|
||||||
int best_waste = (1<<30), best_x, best_y = (1 << 30);
|
|
||||||
stbrp__findresult fr;
|
|
||||||
stbrp_node **prev, *node, *tail, **best = NULL;
|
|
||||||
|
|
||||||
// align to multiple of c->align
|
|
||||||
width = (width + c->align - 1);
|
|
||||||
width -= width % c->align;
|
|
||||||
STBRP_ASSERT(width % c->align == 0);
|
|
||||||
|
|
||||||
// if it can't possibly fit, bail immediately
|
|
||||||
if (width > c->width || height > c->height) {
|
|
||||||
fr.prev_link = NULL;
|
|
||||||
fr.x = fr.y = 0;
|
|
||||||
return fr;
|
|
||||||
}
|
|
||||||
|
|
||||||
node = c->active_head;
|
|
||||||
prev = &c->active_head;
|
|
||||||
while (node->x + width <= c->width) {
|
|
||||||
int y,waste;
|
|
||||||
y = stbrp__skyline_find_min_y(c, node, node->x, width, &waste);
|
|
||||||
if (c->heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight) { // actually just want to test BL
|
|
||||||
// bottom left
|
|
||||||
if (y < best_y) {
|
|
||||||
best_y = y;
|
|
||||||
best = prev;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// best-fit
|
|
||||||
if (y + height <= c->height) {
|
|
||||||
// can only use it if it first vertically
|
|
||||||
if (y < best_y || (y == best_y && waste < best_waste)) {
|
|
||||||
best_y = y;
|
|
||||||
best_waste = waste;
|
|
||||||
best = prev;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
prev = &node->next;
|
|
||||||
node = node->next;
|
|
||||||
}
|
|
||||||
|
|
||||||
best_x = (best == NULL) ? 0 : (*best)->x;
|
|
||||||
|
|
||||||
// if doing best-fit (BF), we also have to try aligning right edge to each node position
|
|
||||||
//
|
|
||||||
// e.g, if fitting
|
|
||||||
//
|
|
||||||
// ____________________
|
|
||||||
// |____________________|
|
|
||||||
//
|
|
||||||
// into
|
|
||||||
//
|
|
||||||
// | |
|
|
||||||
// | ____________|
|
|
||||||
// |____________|
|
|
||||||
//
|
|
||||||
// then right-aligned reduces waste, but bottom-left BL is always chooses left-aligned
|
|
||||||
//
|
|
||||||
// This makes BF take about 2x the time
|
|
||||||
|
|
||||||
if (c->heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight) {
|
|
||||||
tail = c->active_head;
|
|
||||||
node = c->active_head;
|
|
||||||
prev = &c->active_head;
|
|
||||||
// find first node that's admissible
|
|
||||||
while (tail->x < width)
|
|
||||||
tail = tail->next;
|
|
||||||
while (tail) {
|
|
||||||
int xpos = tail->x - width;
|
|
||||||
int y,waste;
|
|
||||||
STBRP_ASSERT(xpos >= 0);
|
|
||||||
// find the left position that matches this
|
|
||||||
while (node->next->x <= xpos) {
|
|
||||||
prev = &node->next;
|
|
||||||
node = node->next;
|
|
||||||
}
|
|
||||||
STBRP_ASSERT(node->next->x > xpos && node->x <= xpos);
|
|
||||||
y = stbrp__skyline_find_min_y(c, node, xpos, width, &waste);
|
|
||||||
if (y + height <= c->height) {
|
|
||||||
if (y <= best_y) {
|
|
||||||
if (y < best_y || waste < best_waste || (waste==best_waste && xpos < best_x)) {
|
|
||||||
best_x = xpos;
|
|
||||||
STBRP_ASSERT(y <= best_y);
|
|
||||||
best_y = y;
|
|
||||||
best_waste = waste;
|
|
||||||
best = prev;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tail = tail->next;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fr.prev_link = best;
|
|
||||||
fr.x = best_x;
|
|
||||||
fr.y = best_y;
|
|
||||||
return fr;
|
|
||||||
}
|
|
||||||
|
|
||||||
static stbrp__findresult stbrp__skyline_pack_rectangle(stbrp_context *context, int width, int height)
|
|
||||||
{
|
|
||||||
// find best position according to heuristic
|
|
||||||
stbrp__findresult res = stbrp__skyline_find_best_pos(context, width, height);
|
|
||||||
stbrp_node *node, *cur;
|
|
||||||
|
|
||||||
// bail if:
|
|
||||||
// 1. it failed
|
|
||||||
// 2. the best node doesn't fit (we don't always check this)
|
|
||||||
// 3. we're out of memory
|
|
||||||
if (res.prev_link == NULL || res.y + height > context->height || context->free_head == NULL) {
|
|
||||||
res.prev_link = NULL;
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
// on success, create new node
|
|
||||||
node = context->free_head;
|
|
||||||
node->x = (stbrp_coord) res.x;
|
|
||||||
node->y = (stbrp_coord) (res.y + height);
|
|
||||||
|
|
||||||
context->free_head = node->next;
|
|
||||||
|
|
||||||
// insert the new node into the right starting point, and
|
|
||||||
// let 'cur' point to the remaining nodes needing to be
|
|
||||||
// stiched back in
|
|
||||||
|
|
||||||
cur = *res.prev_link;
|
|
||||||
if (cur->x < res.x) {
|
|
||||||
// preserve the existing one, so start testing with the next one
|
|
||||||
stbrp_node *next = cur->next;
|
|
||||||
cur->next = node;
|
|
||||||
cur = next;
|
|
||||||
} else {
|
|
||||||
*res.prev_link = node;
|
|
||||||
}
|
|
||||||
|
|
||||||
// from here, traverse cur and free the nodes, until we get to one
|
|
||||||
// that shouldn't be freed
|
|
||||||
while (cur->next && cur->next->x <= res.x + width) {
|
|
||||||
stbrp_node *next = cur->next;
|
|
||||||
// move the current node to the free list
|
|
||||||
cur->next = context->free_head;
|
|
||||||
context->free_head = cur;
|
|
||||||
cur = next;
|
|
||||||
}
|
|
||||||
|
|
||||||
// stitch the list back in
|
|
||||||
node->next = cur;
|
|
||||||
|
|
||||||
if (cur->x < res.x + width)
|
|
||||||
cur->x = (stbrp_coord) (res.x + width);
|
|
||||||
|
|
||||||
#ifdef _DEBUG
|
|
||||||
cur = context->active_head;
|
|
||||||
while (cur->x < context->width) {
|
|
||||||
STBRP_ASSERT(cur->x < cur->next->x);
|
|
||||||
cur = cur->next;
|
|
||||||
}
|
|
||||||
STBRP_ASSERT(cur->next == NULL);
|
|
||||||
|
|
||||||
{
|
|
||||||
int count=0;
|
|
||||||
cur = context->active_head;
|
|
||||||
while (cur) {
|
|
||||||
cur = cur->next;
|
|
||||||
++count;
|
|
||||||
}
|
|
||||||
cur = context->free_head;
|
|
||||||
while (cur) {
|
|
||||||
cur = cur->next;
|
|
||||||
++count;
|
|
||||||
}
|
|
||||||
STBRP_ASSERT(count == context->num_nodes+2);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int STBRP__CDECL rect_height_compare(const void *a, const void *b)
|
|
||||||
{
|
|
||||||
const stbrp_rect *p = (const stbrp_rect *) a;
|
|
||||||
const stbrp_rect *q = (const stbrp_rect *) b;
|
|
||||||
if (p->h > q->h)
|
|
||||||
return -1;
|
|
||||||
if (p->h < q->h)
|
|
||||||
return 1;
|
|
||||||
return (p->w > q->w) ? -1 : (p->w < q->w);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int STBRP__CDECL rect_original_order(const void *a, const void *b)
|
|
||||||
{
|
|
||||||
const stbrp_rect *p = (const stbrp_rect *) a;
|
|
||||||
const stbrp_rect *q = (const stbrp_rect *) b;
|
|
||||||
return (p->was_packed < q->was_packed) ? -1 : (p->was_packed > q->was_packed);
|
|
||||||
}
|
|
||||||
|
|
||||||
STBRP_DEF int stbrp_pack_rects(stbrp_context *context, stbrp_rect *rects, int num_rects)
|
|
||||||
{
|
|
||||||
int i, all_rects_packed = 1;
|
|
||||||
|
|
||||||
// we use the 'was_packed' field internally to allow sorting/unsorting
|
|
||||||
for (i=0; i < num_rects; ++i) {
|
|
||||||
rects[i].was_packed = i;
|
|
||||||
}
|
|
||||||
|
|
||||||
// sort according to heuristic
|
|
||||||
STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_height_compare);
|
|
||||||
|
|
||||||
for (i=0; i < num_rects; ++i) {
|
|
||||||
if (rects[i].w == 0 || rects[i].h == 0) {
|
|
||||||
rects[i].x = rects[i].y = 0; // empty rect needs no space
|
|
||||||
} else {
|
|
||||||
stbrp__findresult fr = stbrp__skyline_pack_rectangle(context, rects[i].w, rects[i].h);
|
|
||||||
if (fr.prev_link) {
|
|
||||||
rects[i].x = (stbrp_coord) fr.x;
|
|
||||||
rects[i].y = (stbrp_coord) fr.y;
|
|
||||||
} else {
|
|
||||||
rects[i].x = rects[i].y = STBRP__MAXVAL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// unsort
|
|
||||||
STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_original_order);
|
|
||||||
|
|
||||||
// set was_packed flags and all_rects_packed status
|
|
||||||
for (i=0; i < num_rects; ++i) {
|
|
||||||
rects[i].was_packed = !(rects[i].x == STBRP__MAXVAL && rects[i].y == STBRP__MAXVAL);
|
|
||||||
if (!rects[i].was_packed)
|
|
||||||
all_rects_packed = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// return the all_rects_packed status
|
|
||||||
return all_rects_packed;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*
|
|
||||||
------------------------------------------------------------------------------
|
|
||||||
This software is available under 2 licenses -- choose whichever you prefer.
|
|
||||||
------------------------------------------------------------------------------
|
|
||||||
ALTERNATIVE A - MIT License
|
|
||||||
Copyright (c) 2017 Sean Barrett
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
||||||
this software and associated documentation files (the "Software"), to deal in
|
|
||||||
the Software without restriction, including without limitation the rights to
|
|
||||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
|
||||||
of the Software, and to permit persons to whom the Software is furnished to do
|
|
||||||
so, subject to the following conditions:
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
------------------------------------------------------------------------------
|
|
||||||
ALTERNATIVE B - Public Domain (www.unlicense.org)
|
|
||||||
This is free and unencumbered software released into the public domain.
|
|
||||||
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
|
|
||||||
software, either in source code form or as a compiled binary, for any purpose,
|
|
||||||
commercial or non-commercial, and by any means.
|
|
||||||
In jurisdictions that recognize copyright laws, the author or authors of this
|
|
||||||
software dedicate any and all copyright interest in the software to the public
|
|
||||||
domain. We make this dedication for the benefit of the public at large and to
|
|
||||||
the detriment of our heirs and successors. We intend this dedication to be an
|
|
||||||
overt act of relinquishment in perpetuity of all present and future rights to
|
|
||||||
this software under copyright law.
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
|
||||||
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
||||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
------------------------------------------------------------------------------
|
|
||||||
*/
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,10 +1,22 @@
|
||||||
#include <extra2d/app/application.h>
|
#include <extra2d/app/application.h>
|
||||||
|
<<<<<<< HEAD
|
||||||
|
#include <extra2d/audio/audio_engine.h>
|
||||||
|
#include <extra2d/event/event_dispatcher.h>
|
||||||
|
#include <extra2d/event/event_queue.h>
|
||||||
|
#include <extra2d/graphics/renderer.h>
|
||||||
|
#include <extra2d/utils/logger.h>
|
||||||
|
#include <extra2d/window/window.h>
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <thread>
|
||||||
|
=======
|
||||||
#include <extra2d/core/registry.h>
|
#include <extra2d/core/registry.h>
|
||||||
#include <extra2d/platform/glfw/glfw_window.h>
|
#include <extra2d/platform/glfw/glfw_window.h>
|
||||||
#include <extra2d/platform/window_module.h>
|
#include <extra2d/platform/window_module.h>
|
||||||
#include <extra2d/services/event_service.h>
|
#include <extra2d/services/event_service.h>
|
||||||
#include <extra2d/services/logger_service.h>
|
#include <extra2d/services/logger_service.h>
|
||||||
#include <extra2d/services/timer_service.h>
|
#include <extra2d/services/timer_service.h>
|
||||||
|
>>>>>>> 9bf328b1dca01df84a07724394abc9a238739869
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
|
|
@ -82,10 +94,14 @@ void Application::run() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
|
void Application::quit() { running_ = false; }
|
||||||
|
=======
|
||||||
void Application::quit() {
|
void Application::quit() {
|
||||||
shouldQuit_ = true;
|
shouldQuit_ = true;
|
||||||
running_ = false;
|
running_ = false;
|
||||||
}
|
}
|
||||||
|
>>>>>>> 9bf328b1dca01df84a07724394abc9a238739869
|
||||||
|
|
||||||
void Application::pause() {
|
void Application::pause() {
|
||||||
if (!paused_) {
|
if (!paused_) {
|
||||||
|
|
@ -141,7 +157,16 @@ void Application::render() {
|
||||||
if (!winMod || !winMod->win())
|
if (!winMod || !winMod->win())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
|
renderer_->beginFrame(Colors::Black);
|
||||||
|
|
||||||
|
// 渲染内容可以在这里添加
|
||||||
|
|
||||||
|
renderer_->endFrame();
|
||||||
|
window_->swapBuffers();
|
||||||
|
=======
|
||||||
winMod->win()->swap();
|
winMod->win()->swap();
|
||||||
|
>>>>>>> 9bf328b1dca01df84a07724394abc9a238739869
|
||||||
}
|
}
|
||||||
|
|
||||||
GLFWWindow *Application::window() {
|
GLFWWindow *Application::window() {
|
||||||
|
|
|
||||||
|
|
@ -1,55 +0,0 @@
|
||||||
#include <extra2d/graphics/alpha_mask.h>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
AlphaMask::AlphaMask(int width, int height)
|
|
||||||
: width_(width), height_(height), data_(width * height, 255) {}
|
|
||||||
|
|
||||||
AlphaMask AlphaMask::createFromPixels(const uint8_t *pixels, int width,
|
|
||||||
int height, int channels) {
|
|
||||||
AlphaMask mask(width, height);
|
|
||||||
|
|
||||||
if (!pixels || width <= 0 || height <= 0) {
|
|
||||||
return mask;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 根据通道数提取Alpha值
|
|
||||||
for (int y = 0; y < height; ++y) {
|
|
||||||
for (int x = 0; x < width; ++x) {
|
|
||||||
int pixelIndex = (y * width + x) * channels;
|
|
||||||
uint8_t alpha = 255;
|
|
||||||
|
|
||||||
if (channels == 4) {
|
|
||||||
// RGBA格式,Alpha在第四个通道
|
|
||||||
alpha = pixels[pixelIndex + 3];
|
|
||||||
} else if (channels == 1) {
|
|
||||||
// 灰度图,直接作为Alpha
|
|
||||||
alpha = pixels[pixelIndex];
|
|
||||||
} else if (channels == 3) {
|
|
||||||
// RGB格式,没有Alpha通道,视为不透明
|
|
||||||
alpha = 255;
|
|
||||||
}
|
|
||||||
|
|
||||||
mask.data_[y * width + x] = alpha;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return mask;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t AlphaMask::getAlpha(int x, int y) const {
|
|
||||||
if (!isValid(x, y)) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return data_[y * width_ + x];
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AlphaMask::isOpaque(int x, int y, uint8_t threshold) const {
|
|
||||||
return getAlpha(x, y) >= threshold;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AlphaMask::isValid(int x, int y) const {
|
|
||||||
return x >= 0 && x < width_ && y >= 0 && y < height_;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,158 +0,0 @@
|
||||||
#include <algorithm>
|
|
||||||
#include <extra2d/graphics/camera.h>
|
|
||||||
#include <glm/gtc/matrix_transform.hpp>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
Camera::Camera() : left_(-1.0f), right_(1.0f), bottom_(-1.0f), top_(1.0f) {}
|
|
||||||
|
|
||||||
Camera::Camera(float left, float right, float bottom, float top)
|
|
||||||
: left_(left), right_(right), bottom_(bottom), top_(top) {}
|
|
||||||
|
|
||||||
Camera::Camera(const Size &viewport)
|
|
||||||
: left_(0.0f), right_(viewport.width), bottom_(viewport.height),
|
|
||||||
top_(0.0f) {}
|
|
||||||
|
|
||||||
void Camera::setPosition(const Vec2 &position) {
|
|
||||||
position_ = position;
|
|
||||||
viewDirty_ = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Camera::setPosition(float x, float y) {
|
|
||||||
position_.x = x;
|
|
||||||
position_.y = y;
|
|
||||||
viewDirty_ = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Camera::setRotation(float degrees) {
|
|
||||||
rotation_ = degrees;
|
|
||||||
viewDirty_ = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Camera::setZoom(float zoom) {
|
|
||||||
zoom_ = zoom;
|
|
||||||
viewDirty_ = true;
|
|
||||||
projDirty_ = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Camera::setViewport(float left, float right, float bottom, float top) {
|
|
||||||
left_ = left;
|
|
||||||
right_ = right;
|
|
||||||
bottom_ = bottom;
|
|
||||||
top_ = top;
|
|
||||||
projDirty_ = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Camera::setViewport(const Rect &rect) {
|
|
||||||
left_ = rect.left();
|
|
||||||
right_ = rect.right();
|
|
||||||
bottom_ = rect.bottom();
|
|
||||||
top_ = rect.top();
|
|
||||||
projDirty_ = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Rect Camera::getViewport() const {
|
|
||||||
return Rect(left_, top_, right_ - left_, bottom_ - top_);
|
|
||||||
}
|
|
||||||
|
|
||||||
glm::mat4 Camera::getViewMatrix() const {
|
|
||||||
if (viewDirty_) {
|
|
||||||
viewMatrix_ = glm::mat4(1.0f);
|
|
||||||
// 对于2D相机,我们只需要平移(注意Y轴方向)
|
|
||||||
viewMatrix_ = glm::translate(viewMatrix_,
|
|
||||||
glm::vec3(-position_.x, -position_.y, 0.0f));
|
|
||||||
viewDirty_ = false;
|
|
||||||
}
|
|
||||||
return viewMatrix_;
|
|
||||||
}
|
|
||||||
|
|
||||||
glm::mat4 Camera::getProjectionMatrix() const {
|
|
||||||
if (projDirty_) {
|
|
||||||
// 对于2D游戏,Y轴向下增长(屏幕坐标系)
|
|
||||||
// OpenGL默认Y轴向上,所以需要反转Y轴
|
|
||||||
// glm::ortho(left, right, bottom, top)
|
|
||||||
// 为了Y轴向下:传入 (bottom=height, top=0),这样Y轴翻转
|
|
||||||
projMatrix_ = glm::ortho(
|
|
||||||
left_, right_, // X轴:从左到右
|
|
||||||
bottom_, top_, // Y轴:从下到上(传入bottom>top,实现Y轴向下增长)
|
|
||||||
-1.0f, 1.0f);
|
|
||||||
projDirty_ = false;
|
|
||||||
}
|
|
||||||
return projMatrix_;
|
|
||||||
}
|
|
||||||
|
|
||||||
glm::mat4 Camera::getViewProjectionMatrix() const {
|
|
||||||
// 对于2D相机,我们主要依赖投影矩阵
|
|
||||||
// 视口变换已经处理了坐标系转换
|
|
||||||
return getProjectionMatrix();
|
|
||||||
}
|
|
||||||
|
|
||||||
Vec2 Camera::screenToWorld(const Vec2 &screenPos) const {
|
|
||||||
// 屏幕坐标直接映射到世界坐标(在2D中通常相同)
|
|
||||||
return screenPos;
|
|
||||||
}
|
|
||||||
|
|
||||||
Vec2 Camera::worldToScreen(const Vec2 &worldPos) const {
|
|
||||||
// 世界坐标直接映射到屏幕坐标(在2D中通常相同)
|
|
||||||
return worldPos;
|
|
||||||
}
|
|
||||||
|
|
||||||
Vec2 Camera::screenToWorld(float x, float y) const {
|
|
||||||
return screenToWorld(Vec2(x, y));
|
|
||||||
}
|
|
||||||
|
|
||||||
Vec2 Camera::worldToScreen(float x, float y) const {
|
|
||||||
return worldToScreen(Vec2(x, y));
|
|
||||||
}
|
|
||||||
|
|
||||||
void Camera::move(const Vec2 &offset) {
|
|
||||||
position_ += offset;
|
|
||||||
viewDirty_ = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Camera::move(float x, float y) {
|
|
||||||
position_.x += x;
|
|
||||||
position_.y += y;
|
|
||||||
viewDirty_ = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Camera::setBounds(const Rect &bounds) {
|
|
||||||
bounds_ = bounds;
|
|
||||||
hasBounds_ = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Camera::clearBounds() { hasBounds_ = false; }
|
|
||||||
|
|
||||||
void Camera::clampToBounds() {
|
|
||||||
if (!hasBounds_)
|
|
||||||
return;
|
|
||||||
|
|
||||||
float viewportWidth = (right_ - left_) / zoom_;
|
|
||||||
float viewportHeight = (bottom_ - top_) / zoom_;
|
|
||||||
|
|
||||||
float minX = bounds_.left() + viewportWidth * 0.5f;
|
|
||||||
float maxX = bounds_.right() - viewportWidth * 0.5f;
|
|
||||||
float minY = bounds_.top() + viewportHeight * 0.5f;
|
|
||||||
float maxY = bounds_.bottom() - viewportHeight * 0.5f;
|
|
||||||
|
|
||||||
if (minX > maxX) {
|
|
||||||
position_.x = bounds_.center().x;
|
|
||||||
} else {
|
|
||||||
position_.x = std::clamp(position_.x, minX, maxX);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (minY > maxY) {
|
|
||||||
position_.y = bounds_.center().y;
|
|
||||||
} else {
|
|
||||||
position_.y = std::clamp(position_.y, minY, maxY);
|
|
||||||
}
|
|
||||||
|
|
||||||
viewDirty_ = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Camera::lookAt(const Vec2 &target) {
|
|
||||||
position_ = target;
|
|
||||||
viewDirty_ = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
#include <extra2d/graphics/gpu_context.h>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
GPUContext& GPUContext::getInstance() {
|
|
||||||
static GPUContext instance;
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
void GPUContext::markValid() {
|
|
||||||
valid_.store(true, std::memory_order_release);
|
|
||||||
}
|
|
||||||
|
|
||||||
void GPUContext::markInvalid() {
|
|
||||||
valid_.store(false, std::memory_order_release);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool GPUContext::isValid() const {
|
|
||||||
return valid_.load(std::memory_order_acquire);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,129 +0,0 @@
|
||||||
#include <extra2d/graphics/opengl/gl_shader.h>
|
|
||||||
#include <extra2d/utils/logger.h>
|
|
||||||
#include <fstream>
|
|
||||||
#include <sstream>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
GLShader::GLShader() : programID_(0) {}
|
|
||||||
|
|
||||||
GLShader::~GLShader() {
|
|
||||||
if (programID_ != 0) {
|
|
||||||
glDeleteProgram(programID_);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool GLShader::compileFromSource(const char *vertexSource,
|
|
||||||
const char *fragmentSource) {
|
|
||||||
GLuint vertexShader = compileShader(GL_VERTEX_SHADER, vertexSource);
|
|
||||||
if (vertexShader == 0)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
GLuint fragmentShader = compileShader(GL_FRAGMENT_SHADER, fragmentSource);
|
|
||||||
if (fragmentShader == 0) {
|
|
||||||
glDeleteShader(vertexShader);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
programID_ = glCreateProgram();
|
|
||||||
glAttachShader(programID_, vertexShader);
|
|
||||||
glAttachShader(programID_, fragmentShader);
|
|
||||||
glLinkProgram(programID_);
|
|
||||||
|
|
||||||
GLint success;
|
|
||||||
glGetProgramiv(programID_, GL_LINK_STATUS, &success);
|
|
||||||
if (!success) {
|
|
||||||
char infoLog[512];
|
|
||||||
glGetProgramInfoLog(programID_, 512, nullptr, infoLog);
|
|
||||||
E2D_LOG_ERROR("Shader program linking failed: {}", infoLog);
|
|
||||||
glDeleteProgram(programID_);
|
|
||||||
programID_ = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
glDeleteShader(vertexShader);
|
|
||||||
glDeleteShader(fragmentShader);
|
|
||||||
|
|
||||||
return success == GL_TRUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool GLShader::compileFromFile(const std::string &vertexPath,
|
|
||||||
const std::string &fragmentPath) {
|
|
||||||
std::ifstream vShaderFile(vertexPath);
|
|
||||||
std::ifstream fShaderFile(fragmentPath);
|
|
||||||
|
|
||||||
if (!vShaderFile.is_open() || !fShaderFile.is_open()) {
|
|
||||||
E2D_LOG_ERROR("Failed to open shader files: {}, {}", vertexPath,
|
|
||||||
fragmentPath);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::stringstream vShaderStream, fShaderStream;
|
|
||||||
vShaderStream << vShaderFile.rdbuf();
|
|
||||||
fShaderStream << fShaderFile.rdbuf();
|
|
||||||
|
|
||||||
return compileFromSource(vShaderStream.str().c_str(),
|
|
||||||
fShaderStream.str().c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLShader::bind() const { glUseProgram(programID_); }
|
|
||||||
|
|
||||||
void GLShader::unbind() const { glUseProgram(0); }
|
|
||||||
|
|
||||||
void GLShader::setBool(const std::string &name, bool value) {
|
|
||||||
glUniform1i(getUniformLocation(name), value ? 1 : 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLShader::setInt(const std::string &name, int value) {
|
|
||||||
glUniform1i(getUniformLocation(name), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLShader::setFloat(const std::string &name, float value) {
|
|
||||||
glUniform1f(getUniformLocation(name), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLShader::setVec2(const std::string &name, const glm::vec2 &value) {
|
|
||||||
glUniform2fv(getUniformLocation(name), 1, &value[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLShader::setVec3(const std::string &name, const glm::vec3 &value) {
|
|
||||||
glUniform3fv(getUniformLocation(name), 1, &value[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLShader::setVec4(const std::string &name, const glm::vec4 &value) {
|
|
||||||
glUniform4fv(getUniformLocation(name), 1, &value[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLShader::setMat4(const std::string &name, const glm::mat4 &value) {
|
|
||||||
glUniformMatrix4fv(getUniformLocation(name), 1, GL_FALSE, &value[0][0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
GLuint GLShader::compileShader(GLenum type, const char *source) {
|
|
||||||
GLuint shader = glCreateShader(type);
|
|
||||||
glShaderSource(shader, 1, &source, nullptr);
|
|
||||||
glCompileShader(shader);
|
|
||||||
|
|
||||||
GLint success;
|
|
||||||
glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
|
|
||||||
if (!success) {
|
|
||||||
char infoLog[512];
|
|
||||||
glGetShaderInfoLog(shader, 512, nullptr, infoLog);
|
|
||||||
E2D_LOG_ERROR("Shader compilation failed: {}", infoLog);
|
|
||||||
glDeleteShader(shader);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return shader;
|
|
||||||
}
|
|
||||||
|
|
||||||
GLint GLShader::getUniformLocation(const std::string &name) {
|
|
||||||
auto it = uniformCache_.find(name);
|
|
||||||
if (it != uniformCache_.end()) {
|
|
||||||
return it->second;
|
|
||||||
}
|
|
||||||
|
|
||||||
GLint location = glGetUniformLocation(programID_, name.c_str());
|
|
||||||
uniformCache_[name] = location;
|
|
||||||
return location;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,363 +0,0 @@
|
||||||
#include <cstring>
|
|
||||||
#include <extra2d/graphics/opengl/gl_sprite_batch.h>
|
|
||||||
#include <extra2d/utils/logger.h>
|
|
||||||
#include <glm/gtc/matrix_transform.hpp>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 三角函数查表 - 避免每帧重复计算 sin/cos
|
|
||||||
// ============================================================================
|
|
||||||
class TrigLookup {
|
|
||||||
public:
|
|
||||||
static constexpr size_t TABLE_SIZE = 360 * 4; // 0.25度精度
|
|
||||||
static constexpr float INDEX_SCALE = 4.0f; // 每度4个采样点
|
|
||||||
static constexpr float RAD_TO_INDEX = INDEX_SCALE * 180.0f / 3.14159265359f;
|
|
||||||
|
|
||||||
static float sinRad(float radians) {
|
|
||||||
return table_.sinTable[normalizeIndexRad(radians)];
|
|
||||||
}
|
|
||||||
|
|
||||||
static float cosRad(float radians) {
|
|
||||||
return table_.cosTable[normalizeIndexRad(radians)];
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
struct Tables {
|
|
||||||
std::array<float, TABLE_SIZE> sinTable;
|
|
||||||
std::array<float, TABLE_SIZE> cosTable;
|
|
||||||
|
|
||||||
Tables() {
|
|
||||||
for (size_t i = 0; i < TABLE_SIZE; ++i) {
|
|
||||||
float angle = static_cast<float>(i) / INDEX_SCALE * 3.14159265359f / 180.0f;
|
|
||||||
sinTable[i] = std::sin(angle);
|
|
||||||
cosTable[i] = std::cos(angle);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
static size_t normalizeIndexRad(float radians) {
|
|
||||||
int idx = static_cast<int>(radians * RAD_TO_INDEX) % static_cast<int>(TABLE_SIZE);
|
|
||||||
if (idx < 0) {
|
|
||||||
idx += static_cast<int>(TABLE_SIZE);
|
|
||||||
}
|
|
||||||
return static_cast<size_t>(idx);
|
|
||||||
}
|
|
||||||
|
|
||||||
static const Tables table_;
|
|
||||||
};
|
|
||||||
|
|
||||||
const TrigLookup::Tables TrigLookup::table_;
|
|
||||||
|
|
||||||
// 静态索引生成函数
|
|
||||||
static const std::array<GLuint, GLSpriteBatch::MAX_INDICES>& getIndices() {
|
|
||||||
static std::array<GLuint, GLSpriteBatch::MAX_INDICES> indices = []() {
|
|
||||||
std::array<GLuint, GLSpriteBatch::MAX_INDICES> arr{};
|
|
||||||
for (size_t i = 0; i < GLSpriteBatch::MAX_SPRITES; ++i) {
|
|
||||||
GLuint base = static_cast<GLuint>(i * GLSpriteBatch::VERTICES_PER_SPRITE);
|
|
||||||
arr[i * GLSpriteBatch::INDICES_PER_SPRITE + 0] = base + 0;
|
|
||||||
arr[i * GLSpriteBatch::INDICES_PER_SPRITE + 1] = base + 1;
|
|
||||||
arr[i * GLSpriteBatch::INDICES_PER_SPRITE + 2] = base + 2;
|
|
||||||
arr[i * GLSpriteBatch::INDICES_PER_SPRITE + 3] = base + 0;
|
|
||||||
arr[i * GLSpriteBatch::INDICES_PER_SPRITE + 4] = base + 2;
|
|
||||||
arr[i * GLSpriteBatch::INDICES_PER_SPRITE + 5] = base + 3;
|
|
||||||
}
|
|
||||||
return arr;
|
|
||||||
}();
|
|
||||||
return indices;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 顶点着色器 (GLES 3.2)
|
|
||||||
static const char *SPRITE_VERTEX_SHADER = R"(
|
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
layout(location = 0) in vec2 aPosition;
|
|
||||||
layout(location = 1) in vec2 aTexCoord;
|
|
||||||
layout(location = 2) in vec4 aColor;
|
|
||||||
|
|
||||||
uniform mat4 uViewProjection;
|
|
||||||
|
|
||||||
out vec2 vTexCoord;
|
|
||||||
out vec4 vColor;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
gl_Position = uViewProjection * vec4(aPosition, 0.0, 1.0);
|
|
||||||
vTexCoord = aTexCoord;
|
|
||||||
vColor = aColor;
|
|
||||||
}
|
|
||||||
)";
|
|
||||||
|
|
||||||
// 片段着色器 (GLES 3.2)
|
|
||||||
// SDF 常量硬编码:ONEDGE_VALUE=128/255=0.502, PIXEL_DIST_SCALE=255/64=3.98
|
|
||||||
// SDF 值存储在 Alpha 通道
|
|
||||||
static const char *SPRITE_FRAGMENT_SHADER = R"(
|
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
in vec2 vTexCoord;
|
|
||||||
in vec4 vColor;
|
|
||||||
|
|
||||||
uniform sampler2D uTexture;
|
|
||||||
uniform int uUseSDF;
|
|
||||||
|
|
||||||
out vec4 fragColor;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
if (uUseSDF == 1) {
|
|
||||||
float dist = texture(uTexture, vTexCoord).a;
|
|
||||||
float sd = (dist - 0.502) * 3.98;
|
|
||||||
float w = fwidth(sd);
|
|
||||||
float alpha = smoothstep(-w, w, sd);
|
|
||||||
fragColor = vec4(vColor.rgb, vColor.a * alpha);
|
|
||||||
} else {
|
|
||||||
fragColor = texture(uTexture, vTexCoord) * vColor;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)";
|
|
||||||
|
|
||||||
GLSpriteBatch::GLSpriteBatch()
|
|
||||||
: vao_(0), vbo_(0), ibo_(0), vertexCount_(0), currentTexture_(nullptr),
|
|
||||||
currentIsSDF_(false), drawCallCount_(0), spriteCount_(0), batchCount_(0) {
|
|
||||||
}
|
|
||||||
|
|
||||||
GLSpriteBatch::~GLSpriteBatch() { shutdown(); }
|
|
||||||
|
|
||||||
bool GLSpriteBatch::init() {
|
|
||||||
// 创建并编译着色器
|
|
||||||
if (!shader_.compileFromSource(SPRITE_VERTEX_SHADER,
|
|
||||||
SPRITE_FRAGMENT_SHADER)) {
|
|
||||||
E2D_LOG_ERROR("Failed to compile sprite batch shader");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 生成 VAO、VBO、IBO
|
|
||||||
glGenVertexArrays(1, &vao_);
|
|
||||||
glGenBuffers(1, &vbo_);
|
|
||||||
glGenBuffers(1, &ibo_);
|
|
||||||
|
|
||||||
glBindVertexArray(vao_);
|
|
||||||
|
|
||||||
// 设置 VBO - 使用动态绘制模式
|
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, vbo_);
|
|
||||||
glBufferData(GL_ARRAY_BUFFER, MAX_VERTICES * sizeof(Vertex), nullptr,
|
|
||||||
GL_DYNAMIC_DRAW);
|
|
||||||
|
|
||||||
// 设置顶点属性
|
|
||||||
glEnableVertexAttribArray(0);
|
|
||||||
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex),
|
|
||||||
(void *)offsetof(Vertex, position));
|
|
||||||
|
|
||||||
glEnableVertexAttribArray(1);
|
|
||||||
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex),
|
|
||||||
(void *)offsetof(Vertex, texCoord));
|
|
||||||
|
|
||||||
glEnableVertexAttribArray(2);
|
|
||||||
glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex),
|
|
||||||
(void *)offsetof(Vertex, color));
|
|
||||||
|
|
||||||
// 使用编译期生成的静态索引缓冲区
|
|
||||||
const auto& indices = getIndices();
|
|
||||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo_);
|
|
||||||
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(GLuint),
|
|
||||||
indices.data(), GL_STATIC_DRAW);
|
|
||||||
|
|
||||||
glBindVertexArray(0);
|
|
||||||
|
|
||||||
E2D_LOG_INFO("GLSpriteBatch initialized with capacity for {} sprites",
|
|
||||||
MAX_SPRITES);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLSpriteBatch::shutdown() {
|
|
||||||
if (vao_ != 0) {
|
|
||||||
glDeleteVertexArrays(1, &vao_);
|
|
||||||
vao_ = 0;
|
|
||||||
}
|
|
||||||
if (vbo_ != 0) {
|
|
||||||
glDeleteBuffers(1, &vbo_);
|
|
||||||
vbo_ = 0;
|
|
||||||
}
|
|
||||||
if (ibo_ != 0) {
|
|
||||||
glDeleteBuffers(1, &ibo_);
|
|
||||||
ibo_ = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLSpriteBatch::begin(const glm::mat4 &viewProjection) {
|
|
||||||
viewProjection_ = viewProjection;
|
|
||||||
vertexCount_ = 0;
|
|
||||||
currentTexture_ = nullptr;
|
|
||||||
currentIsSDF_ = false;
|
|
||||||
drawCallCount_ = 0;
|
|
||||||
spriteCount_ = 0;
|
|
||||||
batchCount_ = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool GLSpriteBatch::needsFlush(const Texture &texture, bool isSDF) const {
|
|
||||||
if (currentTexture_ == nullptr) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查是否需要刷新:纹理改变、SDF 状态改变或缓冲区已满
|
|
||||||
return (currentTexture_ != &texture) || (currentIsSDF_ != isSDF) ||
|
|
||||||
(vertexCount_ + VERTICES_PER_SPRITE > MAX_VERTICES);
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLSpriteBatch::addVertices(const SpriteData &data) {
|
|
||||||
// 计算锚点偏移
|
|
||||||
float anchorOffsetX = data.size.x * data.anchor.x;
|
|
||||||
float anchorOffsetY = data.size.y * data.anchor.y;
|
|
||||||
|
|
||||||
// 使用三角函数查表替代 cosf/sinf
|
|
||||||
float cosR = TrigLookup::cosRad(data.rotation);
|
|
||||||
float sinR = TrigLookup::sinRad(data.rotation);
|
|
||||||
|
|
||||||
glm::vec4 color(data.color.r, data.color.g, data.color.b, data.color.a);
|
|
||||||
|
|
||||||
// 直接计算变换后的位置
|
|
||||||
float rx0 = -anchorOffsetX;
|
|
||||||
float ry0 = -anchorOffsetY;
|
|
||||||
float rx1 = data.size.x - anchorOffsetX;
|
|
||||||
float ry1 = data.size.y - anchorOffsetY;
|
|
||||||
|
|
||||||
// 预计算旋转后的偏移
|
|
||||||
float cosRx0 = rx0 * cosR, sinRx0 = rx0 * sinR;
|
|
||||||
float cosRx1 = rx1 * cosR, sinRx1 = rx1 * sinR;
|
|
||||||
float cosRy0 = ry0 * cosR, sinRy0 = ry0 * sinR;
|
|
||||||
float cosRy1 = ry1 * cosR, sinRy1 = ry1 * sinR;
|
|
||||||
|
|
||||||
// v0: (0, 0) -> (rx0, ry0)
|
|
||||||
vertexBuffer_[vertexCount_++] = {
|
|
||||||
glm::vec2(data.position.x + cosRx0 - sinRy0, data.position.y + sinRx0 + cosRy0),
|
|
||||||
glm::vec2(data.texCoordMin.x, data.texCoordMin.y),
|
|
||||||
color
|
|
||||||
};
|
|
||||||
|
|
||||||
// v1: (size.x, 0) -> (rx1, ry0)
|
|
||||||
vertexBuffer_[vertexCount_++] = {
|
|
||||||
glm::vec2(data.position.x + cosRx1 - sinRy0, data.position.y + sinRx1 + cosRy0),
|
|
||||||
glm::vec2(data.texCoordMax.x, data.texCoordMin.y),
|
|
||||||
color
|
|
||||||
};
|
|
||||||
|
|
||||||
// v2: (size.x, size.y) -> (rx1, ry1)
|
|
||||||
vertexBuffer_[vertexCount_++] = {
|
|
||||||
glm::vec2(data.position.x + cosRx1 - sinRy1, data.position.y + sinRx1 + cosRy1),
|
|
||||||
glm::vec2(data.texCoordMax.x, data.texCoordMax.y),
|
|
||||||
color
|
|
||||||
};
|
|
||||||
|
|
||||||
// v3: (0, size.y) -> (rx0, ry1)
|
|
||||||
vertexBuffer_[vertexCount_++] = {
|
|
||||||
glm::vec2(data.position.x + cosRx0 - sinRy1, data.position.y + sinRx0 + cosRy1),
|
|
||||||
glm::vec2(data.texCoordMin.x, data.texCoordMax.y),
|
|
||||||
color
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLSpriteBatch::draw(const Texture &texture, const SpriteData &data) {
|
|
||||||
// 如果需要刷新,先提交当前批次
|
|
||||||
if (needsFlush(texture, data.isSDF)) {
|
|
||||||
flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
currentTexture_ = &texture;
|
|
||||||
currentIsSDF_ = data.isSDF;
|
|
||||||
|
|
||||||
addVertices(data);
|
|
||||||
spriteCount_++;
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLSpriteBatch::drawBatch(const Texture &texture,
|
|
||||||
const std::vector<SpriteData> &sprites) {
|
|
||||||
if (sprites.empty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果当前有未提交的批次且纹理不同,先刷新
|
|
||||||
if (currentTexture_ != nullptr && currentTexture_ != &texture) {
|
|
||||||
flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
currentTexture_ = &texture;
|
|
||||||
currentIsSDF_ = sprites[0].isSDF; // 假设批量中的精灵 SDF 状态一致
|
|
||||||
|
|
||||||
// 分批处理,避免超过缓冲区大小
|
|
||||||
size_t index = 0;
|
|
||||||
while (index < sprites.size()) {
|
|
||||||
size_t remainingSpace = (MAX_VERTICES - vertexCount_) / VERTICES_PER_SPRITE;
|
|
||||||
size_t batchSize = std::min(sprites.size() - index, remainingSpace);
|
|
||||||
|
|
||||||
for (size_t i = 0; i < batchSize; ++i) {
|
|
||||||
addVertices(sprites[index + i]);
|
|
||||||
spriteCount_++;
|
|
||||||
}
|
|
||||||
|
|
||||||
index += batchSize;
|
|
||||||
|
|
||||||
// 如果还有更多精灵,刷新当前批次
|
|
||||||
if (index < sprites.size()) {
|
|
||||||
flush();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
batchCount_++;
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLSpriteBatch::drawImmediate(const Texture &texture,
|
|
||||||
const SpriteData &data) {
|
|
||||||
// 立即绘制,不缓存 - 用于需要立即显示的情况
|
|
||||||
flush(); // 先提交当前批次
|
|
||||||
|
|
||||||
currentTexture_ = &texture;
|
|
||||||
currentIsSDF_ = data.isSDF;
|
|
||||||
addVertices(data);
|
|
||||||
spriteCount_++;
|
|
||||||
|
|
||||||
flush(); // 立即提交
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLSpriteBatch::end() {
|
|
||||||
if (vertexCount_ > 0) {
|
|
||||||
flush();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLSpriteBatch::flush() {
|
|
||||||
if (vertexCount_ == 0 || currentTexture_ == nullptr) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 绑定纹理
|
|
||||||
GLuint texID = static_cast<GLuint>(
|
|
||||||
reinterpret_cast<uintptr_t>(currentTexture_->getNativeHandle()));
|
|
||||||
glActiveTexture(GL_TEXTURE0);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, texID);
|
|
||||||
|
|
||||||
// 使用着色器
|
|
||||||
shader_.bind();
|
|
||||||
shader_.setMat4("uViewProjection", viewProjection_);
|
|
||||||
shader_.setInt("uTexture", 0);
|
|
||||||
shader_.setInt("uUseSDF", currentIsSDF_ ? 1 : 0);
|
|
||||||
// SDF 常量已硬编码到着色器中
|
|
||||||
|
|
||||||
// 更新 VBO 数据 - 只更新实际使用的部分
|
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, vbo_);
|
|
||||||
glBufferSubData(GL_ARRAY_BUFFER, 0, vertexCount_ * sizeof(Vertex),
|
|
||||||
vertexBuffer_.data());
|
|
||||||
|
|
||||||
// 绘制
|
|
||||||
glBindVertexArray(vao_);
|
|
||||||
GLsizei indexCount = static_cast<GLsizei>(
|
|
||||||
(vertexCount_ / VERTICES_PER_SPRITE) * INDICES_PER_SPRITE);
|
|
||||||
glDrawElements(GL_TRIANGLES, indexCount, GL_UNSIGNED_INT, nullptr);
|
|
||||||
|
|
||||||
drawCallCount_++;
|
|
||||||
batchCount_++;
|
|
||||||
|
|
||||||
// 重置状态
|
|
||||||
vertexCount_ = 0;
|
|
||||||
currentTexture_ = nullptr;
|
|
||||||
currentIsSDF_ = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,461 +0,0 @@
|
||||||
#include <extra2d/graphics/opengl/gl_texture.h>
|
|
||||||
#include <extra2d/graphics/gpu_context.h>
|
|
||||||
#include <extra2d/graphics/vram_manager.h>
|
|
||||||
#define STB_IMAGE_IMPLEMENTATION
|
|
||||||
#include <cstring>
|
|
||||||
#include <extra2d/utils/logger.h>
|
|
||||||
#include <fstream>
|
|
||||||
#include <stb/stb_image.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_; }
|
|
||||||
|
|
||||||
Ptr<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 makePtr<GLTexture>(width, height, nullptr, channels);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,169 +0,0 @@
|
||||||
#include <extra2d/graphics/render_command.h>
|
|
||||||
#include <algorithm>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// RenderCommand 便捷构造函数
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
RenderCommand RenderCommand::makeSprite(const Texture* tex, const Rect& dest,
|
|
||||||
const Rect& src, const Color& tint,
|
|
||||||
float rot, const Vec2& anc,
|
|
||||||
uint32_t lyr) {
|
|
||||||
RenderCommand cmd;
|
|
||||||
cmd.type = RenderCommandType::Sprite;
|
|
||||||
cmd.layer = lyr;
|
|
||||||
cmd.transform = glm::mat4(1.0f);
|
|
||||||
|
|
||||||
SpriteCommandData data;
|
|
||||||
data.texture = tex;
|
|
||||||
data.destRect = dest;
|
|
||||||
data.srcRect = src;
|
|
||||||
data.tint = tint;
|
|
||||||
data.rotation = rot;
|
|
||||||
data.anchor = anc;
|
|
||||||
// 生成排序键:纹理指针的高位 + 层级的低位
|
|
||||||
data.sortKey = (reinterpret_cast<uintptr_t>(tex) >> 4) & 0xFFFFFFF0;
|
|
||||||
data.sortKey |= (lyr & 0xF);
|
|
||||||
|
|
||||||
cmd.data = data;
|
|
||||||
return cmd;
|
|
||||||
}
|
|
||||||
|
|
||||||
RenderCommand RenderCommand::makeLine(const Vec2& s, const Vec2& e, const Color& c,
|
|
||||||
float w, uint32_t lyr) {
|
|
||||||
RenderCommand cmd;
|
|
||||||
cmd.type = RenderCommandType::Line;
|
|
||||||
cmd.layer = lyr;
|
|
||||||
cmd.transform = glm::mat4(1.0f);
|
|
||||||
|
|
||||||
LineCommandData data;
|
|
||||||
data.start = s;
|
|
||||||
data.end = e;
|
|
||||||
data.color = c;
|
|
||||||
data.width = w;
|
|
||||||
|
|
||||||
cmd.data = data;
|
|
||||||
return cmd;
|
|
||||||
}
|
|
||||||
|
|
||||||
RenderCommand RenderCommand::makeRect(const Rect& r, const Color& c,
|
|
||||||
float w, bool fill, uint32_t lyr) {
|
|
||||||
RenderCommand cmd;
|
|
||||||
cmd.type = fill ? RenderCommandType::FilledRect : RenderCommandType::Rect;
|
|
||||||
cmd.layer = lyr;
|
|
||||||
cmd.transform = glm::mat4(1.0f);
|
|
||||||
|
|
||||||
RectCommandData data;
|
|
||||||
data.rect = r;
|
|
||||||
data.color = c;
|
|
||||||
data.width = w;
|
|
||||||
data.filled = fill;
|
|
||||||
|
|
||||||
cmd.data = data;
|
|
||||||
return cmd;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// RenderCommandBuffer 实现
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
RenderCommandBuffer::RenderCommandBuffer() : nextOrder_(0) {
|
|
||||||
commands_.reserve(INITIAL_CAPACITY);
|
|
||||||
}
|
|
||||||
|
|
||||||
RenderCommandBuffer::~RenderCommandBuffer() = default;
|
|
||||||
|
|
||||||
void RenderCommandBuffer::addCommand(const RenderCommand& cmd) {
|
|
||||||
if (commands_.size() >= MAX_CAPACITY) {
|
|
||||||
// 缓冲区已满,可能需要立即刷新
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
RenderCommand copy = cmd;
|
|
||||||
copy.order = nextOrder_++;
|
|
||||||
commands_.push_back(std::move(copy));
|
|
||||||
}
|
|
||||||
|
|
||||||
void RenderCommandBuffer::addCommand(RenderCommand&& cmd) {
|
|
||||||
if (commands_.size() >= MAX_CAPACITY) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd.order = nextOrder_++;
|
|
||||||
commands_.push_back(std::move(cmd));
|
|
||||||
}
|
|
||||||
|
|
||||||
RenderCommand& RenderCommandBuffer::emplaceCommand() {
|
|
||||||
if (commands_.size() >= MAX_CAPACITY) {
|
|
||||||
// 如果已满,返回一个虚拟命令(不应该发生)
|
|
||||||
static RenderCommand dummy;
|
|
||||||
return dummy;
|
|
||||||
}
|
|
||||||
|
|
||||||
commands_.emplace_back();
|
|
||||||
commands_.back().order = nextOrder_++;
|
|
||||||
return commands_.back();
|
|
||||||
}
|
|
||||||
|
|
||||||
void RenderCommandBuffer::sortCommands() {
|
|
||||||
// 按以下优先级排序:
|
|
||||||
// 1. 层级 (layer) - 低层级先渲染
|
|
||||||
// 2. 命令类型 - 精灵类命令优先批处理
|
|
||||||
// 3. 纹理/材质 - 相同纹理的精灵连续渲染
|
|
||||||
// 4. 提交顺序 - 保证稳定性
|
|
||||||
|
|
||||||
std::sort(commands_.begin(), commands_.end(), compareCommands);
|
|
||||||
}
|
|
||||||
|
|
||||||
void RenderCommandBuffer::clear() {
|
|
||||||
commands_.clear();
|
|
||||||
nextOrder_ = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void RenderCommandBuffer::reserve(size_t capacity) {
|
|
||||||
if (capacity <= MAX_CAPACITY) {
|
|
||||||
commands_.reserve(capacity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RenderCommandBuffer::compareCommands(const RenderCommand& a, const RenderCommand& b) {
|
|
||||||
// 首先按层级排序
|
|
||||||
if (a.layer != b.layer) {
|
|
||||||
return a.layer < b.layer;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 然后按类型排序(精灵类命令放在一起以便批处理)
|
|
||||||
if (a.type != b.type) {
|
|
||||||
// 精灵和文本命令优先(需要纹理)
|
|
||||||
bool aIsSprite = (a.type == RenderCommandType::Sprite ||
|
|
||||||
a.type == RenderCommandType::Text);
|
|
||||||
bool bIsSprite = (b.type == RenderCommandType::Sprite ||
|
|
||||||
b.type == RenderCommandType::Text);
|
|
||||||
|
|
||||||
if (aIsSprite != bIsSprite) {
|
|
||||||
return aIsSprite > bIsSprite; // 精灵类命令在前
|
|
||||||
}
|
|
||||||
|
|
||||||
return static_cast<uint8_t>(a.type) < static_cast<uint8_t>(b.type);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 对于精灵命令,按纹理排序
|
|
||||||
if (a.type == RenderCommandType::Sprite && b.type == RenderCommandType::Sprite) {
|
|
||||||
const auto& dataA = std::get<SpriteCommandData>(a.data);
|
|
||||||
const auto& dataB = std::get<SpriteCommandData>(b.data);
|
|
||||||
if (dataA.texture != dataB.texture) {
|
|
||||||
return dataA.texture < dataB.texture;
|
|
||||||
}
|
|
||||||
// 相同纹理时按 sortKey 排序
|
|
||||||
if (dataA.sortKey != dataB.sortKey) {
|
|
||||||
return dataA.sortKey < dataB.sortKey;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 最后按提交顺序排序(保证稳定性)
|
|
||||||
return a.order < b.order;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,586 +0,0 @@
|
||||||
#include <glad/glad.h>
|
|
||||||
#include <extra2d/graphics/opengl/gl_texture.h>
|
|
||||||
#include <extra2d/graphics/render_target.h>
|
|
||||||
#include <extra2d/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(Ptr<Texture> texture, bool hasDepth) {
|
|
||||||
if (!texture || !texture->isValid()) {
|
|
||||||
E2D_ERROR("无效的颜色纹理");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
destroy();
|
|
||||||
|
|
||||||
width_ = texture->getWidth();
|
|
||||||
height_ = texture->getHeight();
|
|
||||||
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.getWidth(),
|
|
||||||
target.getHeight(), 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ptr<RenderTarget>
|
|
||||||
RenderTarget::createFromConfig(const RenderTargetConfig &config) {
|
|
||||||
auto rt = std::make_shared<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.getWidth(),
|
|
||||||
target.getHeight(), 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("渲染目标管理器已关闭");
|
|
||||||
}
|
|
||||||
|
|
||||||
Ptr<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
|
|
||||||
|
|
@ -1,231 +0,0 @@
|
||||||
#include <extra2d/graphics/shader_preset.h>
|
|
||||||
#include <extra2d/utils/logger.h>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// ShaderPreset实现
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
Ptr<GLShader> ShaderPreset::Water(const WaterParams ¶ms) {
|
|
||||||
auto shader = std::make_shared<GLShader>();
|
|
||||||
|
|
||||||
if (!shader->compileFromSource(ShaderSource::StandardVert,
|
|
||||||
ShaderSource::WaterFrag)) {
|
|
||||||
E2D_ERROR("编译水波纹Shader失败");
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设置默认参数
|
|
||||||
shader->setFloat("u_waveSpeed", params.waveSpeed);
|
|
||||||
shader->setFloat("u_waveAmplitude", params.waveAmplitude);
|
|
||||||
shader->setFloat("u_waveFrequency", params.waveFrequency);
|
|
||||||
|
|
||||||
E2D_INFO("创建水波纹Shader预设");
|
|
||||||
return shader;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ptr<GLShader> ShaderPreset::Outline(const OutlineParams ¶ms) {
|
|
||||||
auto shader = std::make_shared<GLShader>();
|
|
||||||
|
|
||||||
if (!shader->compileFromSource(ShaderSource::StandardVert,
|
|
||||||
ShaderSource::OutlineFrag)) {
|
|
||||||
E2D_ERROR("编译描边Shader失败");
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设置默认参数
|
|
||||||
shader->setVec4("u_outlineColor", glm::vec4(params.color.r, params.color.g,
|
|
||||||
params.color.b, params.color.a));
|
|
||||||
shader->setFloat("u_thickness", params.thickness);
|
|
||||||
|
|
||||||
E2D_INFO("创建描边Shader预设");
|
|
||||||
return shader;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ptr<GLShader> ShaderPreset::Distortion(const DistortionParams ¶ms) {
|
|
||||||
auto shader = std::make_shared<GLShader>();
|
|
||||||
|
|
||||||
if (!shader->compileFromSource(ShaderSource::StandardVert,
|
|
||||||
ShaderSource::DistortionFrag)) {
|
|
||||||
E2D_ERROR("编译扭曲Shader失败");
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设置默认参数
|
|
||||||
shader->setFloat("u_distortionAmount", params.distortionAmount);
|
|
||||||
shader->setFloat("u_timeScale", params.timeScale);
|
|
||||||
|
|
||||||
E2D_INFO("创建扭曲Shader预设");
|
|
||||||
return shader;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ptr<GLShader> ShaderPreset::Pixelate(const PixelateParams ¶ms) {
|
|
||||||
auto shader = std::make_shared<GLShader>();
|
|
||||||
|
|
||||||
if (!shader->compileFromSource(ShaderSource::StandardVert,
|
|
||||||
ShaderSource::PixelateFrag)) {
|
|
||||||
E2D_ERROR("编译像素化Shader失败");
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设置默认参数
|
|
||||||
shader->setFloat("u_pixelSize", params.pixelSize);
|
|
||||||
|
|
||||||
E2D_INFO("创建像素化Shader预设");
|
|
||||||
return shader;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ptr<GLShader> ShaderPreset::Invert(const InvertParams ¶ms) {
|
|
||||||
auto shader = std::make_shared<GLShader>();
|
|
||||||
|
|
||||||
if (!shader->compileFromSource(ShaderSource::StandardVert,
|
|
||||||
ShaderSource::InvertFrag)) {
|
|
||||||
E2D_ERROR("编译反相Shader失败");
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设置默认参数
|
|
||||||
shader->setFloat("u_strength", params.strength);
|
|
||||||
|
|
||||||
E2D_INFO("创建反相Shader预设");
|
|
||||||
return shader;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ptr<GLShader> ShaderPreset::Grayscale(const GrayscaleParams ¶ms) {
|
|
||||||
auto shader = std::make_shared<GLShader>();
|
|
||||||
|
|
||||||
if (!shader->compileFromSource(ShaderSource::StandardVert,
|
|
||||||
ShaderSource::GrayscaleFrag)) {
|
|
||||||
E2D_ERROR("编译灰度Shader失败");
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设置默认参数
|
|
||||||
shader->setFloat("u_intensity", params.intensity);
|
|
||||||
|
|
||||||
E2D_INFO("创建灰度Shader预设");
|
|
||||||
return shader;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ptr<GLShader> ShaderPreset::Blur(const BlurParams ¶ms) {
|
|
||||||
auto shader = std::make_shared<GLShader>();
|
|
||||||
|
|
||||||
if (!shader->compileFromSource(ShaderSource::StandardVert,
|
|
||||||
ShaderSource::BlurFrag)) {
|
|
||||||
E2D_ERROR("编译模糊Shader失败");
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设置默认参数
|
|
||||||
shader->setFloat("u_radius", params.radius);
|
|
||||||
|
|
||||||
E2D_INFO("创建模糊Shader预设");
|
|
||||||
return shader;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ptr<GLShader>
|
|
||||||
ShaderPreset::GrayscaleOutline(const GrayscaleParams &grayParams,
|
|
||||||
const OutlineParams &outlineParams) {
|
|
||||||
// 创建组合效果的片段着色器 (GLES 3.2)
|
|
||||||
const char *combinedFrag = R"(
|
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
in vec2 v_texCoord;
|
|
||||||
|
|
||||||
uniform sampler2D u_texture;
|
|
||||||
uniform float u_grayIntensity;
|
|
||||||
uniform vec4 u_outlineColor;
|
|
||||||
uniform float u_thickness;
|
|
||||||
uniform vec2 u_textureSize;
|
|
||||||
|
|
||||||
out vec4 fragColor;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
vec4 color = texture(u_texture, v_texCoord);
|
|
||||||
|
|
||||||
// 灰度效果
|
|
||||||
float gray = dot(color.rgb, vec3(0.299, 0.587, 0.114));
|
|
||||||
color.rgb = mix(color.rgb, vec3(gray), u_grayIntensity);
|
|
||||||
|
|
||||||
// 描边效果
|
|
||||||
float alpha = 0.0;
|
|
||||||
vec2 offset = u_thickness / u_textureSize;
|
|
||||||
|
|
||||||
alpha += texture(u_texture, v_texCoord + vec2(-offset.x, 0.0)).a;
|
|
||||||
alpha += texture(u_texture, v_texCoord + vec2(offset.x, 0.0)).a;
|
|
||||||
alpha += texture(u_texture, v_texCoord + vec2(0.0, -offset.y)).a;
|
|
||||||
alpha += texture(u_texture, v_texCoord + vec2(0.0, offset.y)).a;
|
|
||||||
|
|
||||||
if (color.a < 0.1 && alpha > 0.0) {
|
|
||||||
fragColor = u_outlineColor;
|
|
||||||
} else {
|
|
||||||
fragColor = color;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)";
|
|
||||||
|
|
||||||
auto shader = std::make_shared<GLShader>();
|
|
||||||
|
|
||||||
if (!shader->compileFromSource(ShaderSource::StandardVert, combinedFrag)) {
|
|
||||||
E2D_ERROR("编译灰度+描边Shader失败");
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设置默认参数
|
|
||||||
shader->setFloat("u_grayIntensity", grayParams.intensity);
|
|
||||||
shader->setVec4("u_outlineColor",
|
|
||||||
glm::vec4(outlineParams.color.r, outlineParams.color.g,
|
|
||||||
outlineParams.color.b, outlineParams.color.a));
|
|
||||||
shader->setFloat("u_thickness", outlineParams.thickness);
|
|
||||||
|
|
||||||
E2D_INFO("创建灰度+描边组合Shader预设");
|
|
||||||
return shader;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ptr<GLShader> ShaderPreset::PixelateInvert(const PixelateParams &pixParams,
|
|
||||||
const InvertParams &invParams) {
|
|
||||||
// 创建组合效果的片段着色器 (GLES 3.2)
|
|
||||||
const char *combinedFrag = R"(
|
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
in vec2 v_texCoord;
|
|
||||||
|
|
||||||
uniform sampler2D u_texture;
|
|
||||||
uniform float u_pixelSize;
|
|
||||||
uniform vec2 u_textureSize;
|
|
||||||
uniform float u_invertStrength;
|
|
||||||
|
|
||||||
out vec4 fragColor;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
// 像素化
|
|
||||||
vec2 pixel = u_pixelSize / u_textureSize;
|
|
||||||
vec2 uv = floor(v_texCoord / pixel) * pixel + pixel * 0.5;
|
|
||||||
|
|
||||||
vec4 color = texture(u_texture, uv);
|
|
||||||
|
|
||||||
// 反相
|
|
||||||
vec3 inverted = 1.0 - color.rgb;
|
|
||||||
color.rgb = mix(color.rgb, inverted, u_invertStrength);
|
|
||||||
|
|
||||||
fragColor = color;
|
|
||||||
}
|
|
||||||
)";
|
|
||||||
|
|
||||||
auto shader = std::make_shared<GLShader>();
|
|
||||||
|
|
||||||
if (!shader->compileFromSource(ShaderSource::StandardVert, combinedFrag)) {
|
|
||||||
E2D_ERROR("编译像素化+反相Shader失败");
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设置默认参数
|
|
||||||
shader->setFloat("u_pixelSize", pixParams.pixelSize);
|
|
||||||
shader->setFloat("u_invertStrength", invParams.strength);
|
|
||||||
|
|
||||||
E2D_INFO("创建像素化+反相组合Shader预设");
|
|
||||||
return shader;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,491 +0,0 @@
|
||||||
#include <extra2d/core/color.h>
|
|
||||||
#include <extra2d/graphics/shader_system.h>
|
|
||||||
#include <extra2d/utils/logger.h>
|
|
||||||
#include <fstream>
|
|
||||||
#include <sstream>
|
|
||||||
#include <sys/stat.h>
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
#include <windows.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 内置Shader源码
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
// 标准精灵着色器 (GLES 3.2)
|
|
||||||
static const char *BUILTIN_SPRITE_VERT = R"(
|
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
layout(location = 0) in vec2 a_position;
|
|
||||||
layout(location = 1) in vec2 a_texCoord;
|
|
||||||
layout(location = 2) in vec4 a_color;
|
|
||||||
|
|
||||||
uniform mat4 u_viewProjection;
|
|
||||||
uniform mat4 u_model;
|
|
||||||
|
|
||||||
out vec2 v_texCoord;
|
|
||||||
out vec4 v_color;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
gl_Position = u_viewProjection * u_model * vec4(a_position, 0.0, 1.0);
|
|
||||||
v_texCoord = a_texCoord;
|
|
||||||
v_color = a_color;
|
|
||||||
}
|
|
||||||
)";
|
|
||||||
|
|
||||||
static const char *BUILTIN_SPRITE_FRAG = R"(
|
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
in vec2 v_texCoord;
|
|
||||||
in vec4 v_color;
|
|
||||||
|
|
||||||
uniform sampler2D u_texture;
|
|
||||||
uniform float u_opacity;
|
|
||||||
|
|
||||||
out vec4 fragColor;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
vec4 texColor = texture(u_texture, v_texCoord);
|
|
||||||
fragColor = texColor * v_color;
|
|
||||||
fragColor.a *= u_opacity;
|
|
||||||
|
|
||||||
if (fragColor.a < 0.01) {
|
|
||||||
discard;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)";
|
|
||||||
|
|
||||||
// 粒子着色器 (GLES 3.2)
|
|
||||||
static const char *BUILTIN_PARTICLE_VERT = R"(
|
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
layout(location = 0) in vec2 a_position;
|
|
||||||
layout(location = 1) in vec2 a_texCoord;
|
|
||||||
layout(location = 2) in vec4 a_color;
|
|
||||||
layout(location = 3) in float a_size;
|
|
||||||
layout(location = 4) in float a_rotation;
|
|
||||||
|
|
||||||
uniform mat4 u_viewProjection;
|
|
||||||
|
|
||||||
out vec2 v_texCoord;
|
|
||||||
out vec4 v_color;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
float c = cos(a_rotation);
|
|
||||||
float s = sin(a_rotation);
|
|
||||||
mat2 rot = mat2(c, -s, s, c);
|
|
||||||
|
|
||||||
vec2 pos = rot * a_position * a_size;
|
|
||||||
gl_Position = u_viewProjection * vec4(pos, 0.0, 1.0);
|
|
||||||
|
|
||||||
v_texCoord = a_texCoord;
|
|
||||||
v_color = a_color;
|
|
||||||
}
|
|
||||||
)";
|
|
||||||
|
|
||||||
static const char *BUILTIN_PARTICLE_FRAG = R"(
|
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
in vec2 v_texCoord;
|
|
||||||
in vec4 v_color;
|
|
||||||
|
|
||||||
uniform sampler2D u_texture;
|
|
||||||
uniform int u_textureEnabled;
|
|
||||||
|
|
||||||
out vec4 fragColor;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
vec4 color = v_color;
|
|
||||||
|
|
||||||
if (u_textureEnabled > 0) {
|
|
||||||
color *= texture(u_texture, v_texCoord);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 圆形粒子
|
|
||||||
vec2 center = v_texCoord - vec2(0.5);
|
|
||||||
float dist = length(center);
|
|
||||||
float alpha = 1.0 - smoothstep(0.4, 0.5, dist);
|
|
||||||
color.a *= alpha;
|
|
||||||
|
|
||||||
if (color.a < 0.01) {
|
|
||||||
discard;
|
|
||||||
}
|
|
||||||
|
|
||||||
fragColor = color;
|
|
||||||
}
|
|
||||||
)";
|
|
||||||
|
|
||||||
// 后处理着色器 (GLES 3.2)
|
|
||||||
static const char *BUILTIN_POSTPROCESS_VERT = R"(
|
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
layout(location = 0) in vec2 a_position;
|
|
||||||
layout(location = 1) in vec2 a_texCoord;
|
|
||||||
|
|
||||||
out vec2 v_texCoord;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
gl_Position = vec4(a_position, 0.0, 1.0);
|
|
||||||
v_texCoord = a_texCoord;
|
|
||||||
}
|
|
||||||
)";
|
|
||||||
|
|
||||||
static const char *BUILTIN_POSTPROCESS_FRAG = R"(
|
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
in vec2 v_texCoord;
|
|
||||||
|
|
||||||
uniform sampler2D u_texture;
|
|
||||||
uniform vec2 u_resolution;
|
|
||||||
uniform float u_time;
|
|
||||||
|
|
||||||
out vec4 fragColor;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
fragColor = texture(u_texture, v_texCoord);
|
|
||||||
}
|
|
||||||
)";
|
|
||||||
|
|
||||||
// 形状渲染着色器 (GLES 3.2)
|
|
||||||
static const char *BUILTIN_SHAPE_VERT = R"(
|
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
layout(location = 0) in vec2 a_position;
|
|
||||||
layout(location = 1) in vec4 a_color;
|
|
||||||
|
|
||||||
uniform mat4 u_viewProjection;
|
|
||||||
|
|
||||||
out vec4 v_color;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
gl_Position = u_viewProjection * vec4(a_position, 0.0, 1.0);
|
|
||||||
v_color = a_color;
|
|
||||||
}
|
|
||||||
)";
|
|
||||||
|
|
||||||
static const char *BUILTIN_SHAPE_FRAG = R"(
|
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
in vec4 v_color;
|
|
||||||
|
|
||||||
out vec4 fragColor;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
fragColor = v_color;
|
|
||||||
}
|
|
||||||
)";
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// ShaderSystem实现
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
ShaderSystem &ShaderSystem::getInstance() {
|
|
||||||
static ShaderSystem instance;
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ShaderSystem::init() {
|
|
||||||
E2D_INFO("初始化Shader系统...");
|
|
||||||
|
|
||||||
if (!loadBuiltinShaders()) {
|
|
||||||
E2D_ERROR("加载内置Shader失败!");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
E2D_INFO("Shader系统初始化完成");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ShaderSystem::shutdown() {
|
|
||||||
E2D_INFO("关闭Shader系统...");
|
|
||||||
clear();
|
|
||||||
|
|
||||||
builtinSpriteShader_.reset();
|
|
||||||
builtinParticleShader_.reset();
|
|
||||||
builtinPostProcessShader_.reset();
|
|
||||||
builtinShapeShader_.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ShaderSystem::loadBuiltinShaders() {
|
|
||||||
// 加载精灵Shader
|
|
||||||
builtinSpriteShader_ = std::make_shared<GLShader>();
|
|
||||||
if (!builtinSpriteShader_->compileFromSource(BUILTIN_SPRITE_VERT,
|
|
||||||
BUILTIN_SPRITE_FRAG)) {
|
|
||||||
E2D_ERROR("编译内置精灵Shader失败!");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 加载粒子Shader
|
|
||||||
builtinParticleShader_ = std::make_shared<GLShader>();
|
|
||||||
if (!builtinParticleShader_->compileFromSource(BUILTIN_PARTICLE_VERT,
|
|
||||||
BUILTIN_PARTICLE_FRAG)) {
|
|
||||||
E2D_ERROR("编译内置粒子Shader失败!");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 加载后处理Shader
|
|
||||||
builtinPostProcessShader_ = std::make_shared<GLShader>();
|
|
||||||
if (!builtinPostProcessShader_->compileFromSource(BUILTIN_POSTPROCESS_VERT,
|
|
||||||
BUILTIN_POSTPROCESS_FRAG)) {
|
|
||||||
E2D_ERROR("编译内置后处理Shader失败!");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 加载形状Shader
|
|
||||||
builtinShapeShader_ = std::make_shared<GLShader>();
|
|
||||||
if (!builtinShapeShader_->compileFromSource(BUILTIN_SHAPE_VERT,
|
|
||||||
BUILTIN_SHAPE_FRAG)) {
|
|
||||||
E2D_ERROR("编译内置形状Shader失败!");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
E2D_INFO("内置Shader加载成功");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ptr<GLShader> ShaderSystem::loadFromFile(const std::string &name,
|
|
||||||
const std::string &vertPath,
|
|
||||||
const std::string &fragPath) {
|
|
||||||
// 读取文件内容
|
|
||||||
std::string vertSource = readFile(vertPath);
|
|
||||||
std::string fragSource = readFile(fragPath);
|
|
||||||
|
|
||||||
if (vertSource.empty()) {
|
|
||||||
E2D_ERROR("无法读取顶点着色器文件: {}", vertPath);
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fragSource.empty()) {
|
|
||||||
E2D_ERROR("无法读取片段着色器文件: {}", fragPath);
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 编译Shader
|
|
||||||
auto shader = std::make_shared<GLShader>();
|
|
||||||
if (!shader->compileFromSource(vertSource.c_str(), fragSource.c_str())) {
|
|
||||||
E2D_ERROR("编译Shader '{}' 失败", name);
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 存储Shader信息
|
|
||||||
ShaderInfo info;
|
|
||||||
info.shader = shader;
|
|
||||||
info.vertPath = vertPath;
|
|
||||||
info.fragPath = fragPath;
|
|
||||||
info.vertModifiedTime = getFileModifiedTime(vertPath);
|
|
||||||
info.fragModifiedTime = getFileModifiedTime(fragPath);
|
|
||||||
info.isBuiltin = false;
|
|
||||||
|
|
||||||
shaders_[name] = std::move(info);
|
|
||||||
|
|
||||||
E2D_INFO("加载Shader '{}' 成功", name);
|
|
||||||
return shader;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ptr<GLShader> ShaderSystem::loadFromSource(const std::string &name,
|
|
||||||
const std::string &vertSource,
|
|
||||||
const std::string &fragSource) {
|
|
||||||
auto shader = std::make_shared<GLShader>();
|
|
||||||
if (!shader->compileFromSource(vertSource.c_str(), fragSource.c_str())) {
|
|
||||||
E2D_ERROR("编译Shader '{}' 失败", name);
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
ShaderInfo info;
|
|
||||||
info.shader = shader;
|
|
||||||
info.vertModifiedTime = 0;
|
|
||||||
info.fragModifiedTime = 0;
|
|
||||||
info.isBuiltin = false;
|
|
||||||
|
|
||||||
shaders_[name] = std::move(info);
|
|
||||||
|
|
||||||
E2D_INFO("加载Shader '{}' 成功", name);
|
|
||||||
return shader;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ptr<GLShader> ShaderSystem::get(const std::string &name) {
|
|
||||||
auto it = shaders_.find(name);
|
|
||||||
if (it != shaders_.end()) {
|
|
||||||
return it->second.shader;
|
|
||||||
}
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ShaderSystem::has(const std::string &name) const {
|
|
||||||
return shaders_.find(name) != shaders_.end();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ShaderSystem::remove(const std::string &name) { shaders_.erase(name); }
|
|
||||||
|
|
||||||
void ShaderSystem::clear() { shaders_.clear(); }
|
|
||||||
|
|
||||||
void ShaderSystem::setFileWatching(bool enable) {
|
|
||||||
fileWatching_ = enable;
|
|
||||||
if (enable) {
|
|
||||||
E2D_INFO("启用Shader文件监视");
|
|
||||||
} else {
|
|
||||||
E2D_INFO("禁用Shader文件监视");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ShaderSystem::updateFileWatching() {
|
|
||||||
if (!fileWatching_)
|
|
||||||
return;
|
|
||||||
|
|
||||||
watchTimer_ += 0.016f; // 假设60fps
|
|
||||||
if (watchTimer_ >= WATCH_INTERVAL) {
|
|
||||||
watchTimer_ = 0.0f;
|
|
||||||
checkAndReload();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ShaderSystem::checkAndReload() {
|
|
||||||
for (auto &[name, info] : shaders_) {
|
|
||||||
if (info.isBuiltin)
|
|
||||||
continue;
|
|
||||||
if (info.vertPath.empty() || info.fragPath.empty())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
uint64_t vertTime = getFileModifiedTime(info.vertPath);
|
|
||||||
uint64_t fragTime = getFileModifiedTime(info.fragPath);
|
|
||||||
|
|
||||||
if (vertTime > info.vertModifiedTime || fragTime > info.fragModifiedTime) {
|
|
||||||
E2D_INFO("检测到Shader '{}' 文件变化,正在重载...", name);
|
|
||||||
reload(name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ShaderSystem::reload(const std::string &name) {
|
|
||||||
auto it = shaders_.find(name);
|
|
||||||
if (it == shaders_.end()) {
|
|
||||||
E2D_ERROR("无法重载不存在的Shader '{}'", name);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto &info = it->second;
|
|
||||||
if (info.isBuiltin) {
|
|
||||||
E2D_WARN("无法重载内置Shader '{}'", name);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (info.vertPath.empty() || info.fragPath.empty()) {
|
|
||||||
E2D_ERROR("Shader '{}' 没有关联的文件路径", name);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 重新加载
|
|
||||||
auto newShader = loadFromFile(name, info.vertPath, info.fragPath);
|
|
||||||
if (newShader) {
|
|
||||||
E2D_INFO("重载Shader '{}' 成功", name);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ShaderSystem::reloadAll() {
|
|
||||||
for (const auto &[name, info] : shaders_) {
|
|
||||||
if (!info.isBuiltin) {
|
|
||||||
reload(name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ptr<GLShader> ShaderSystem::getBuiltinSpriteShader() {
|
|
||||||
return builtinSpriteShader_;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ptr<GLShader> ShaderSystem::getBuiltinParticleShader() {
|
|
||||||
return builtinParticleShader_;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ptr<GLShader> ShaderSystem::getBuiltinPostProcessShader() {
|
|
||||||
return builtinPostProcessShader_;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ptr<GLShader> ShaderSystem::getBuiltinShapeShader() {
|
|
||||||
return builtinShapeShader_;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string ShaderSystem::readFile(const std::string &filepath) {
|
|
||||||
std::ifstream file(filepath, std::ios::in | std::ios::binary);
|
|
||||||
if (!file.is_open()) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
std::stringstream buffer;
|
|
||||||
buffer << file.rdbuf();
|
|
||||||
return buffer.str();
|
|
||||||
}
|
|
||||||
|
|
||||||
uint64_t ShaderSystem::getFileModifiedTime(const std::string &filepath) {
|
|
||||||
#ifdef _WIN32
|
|
||||||
struct _stat64 statBuf;
|
|
||||||
if (_stat64(filepath.c_str(), &statBuf) == 0) {
|
|
||||||
return static_cast<uint64_t>(statBuf.st_mtime);
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
struct stat statBuf;
|
|
||||||
if (stat(filepath.c_str(), &statBuf) == 0) {
|
|
||||||
return static_cast<uint64_t>(statBuf.st_mtime);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// ShaderParams实现
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
ShaderParams::ShaderParams(GLShader &shader) : shader_(shader) {}
|
|
||||||
|
|
||||||
ShaderParams &ShaderParams::setBool(const std::string &name, bool value) {
|
|
||||||
shader_.setBool(name, value);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
ShaderParams &ShaderParams::setInt(const std::string &name, int value) {
|
|
||||||
shader_.setInt(name, value);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
ShaderParams &ShaderParams::setFloat(const std::string &name, float value) {
|
|
||||||
shader_.setFloat(name, value);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
ShaderParams &ShaderParams::setVec2(const std::string &name,
|
|
||||||
const glm::vec2 &value) {
|
|
||||||
shader_.setVec2(name, value);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
ShaderParams &ShaderParams::setVec3(const std::string &name,
|
|
||||||
const glm::vec3 &value) {
|
|
||||||
shader_.setVec3(name, value);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
ShaderParams &ShaderParams::setVec4(const std::string &name,
|
|
||||||
const glm::vec4 &value) {
|
|
||||||
shader_.setVec4(name, value);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
ShaderParams &ShaderParams::setMat4(const std::string &name,
|
|
||||||
const glm::mat4 &value) {
|
|
||||||
shader_.setMat4(name, value);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
ShaderParams &ShaderParams::setColor(const std::string &name,
|
|
||||||
const Color &color) {
|
|
||||||
shader_.setVec4(name, glm::vec4(color.r, color.g, color.b, color.a));
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,264 +0,0 @@
|
||||||
#include <extra2d/graphics/texture_atlas.h>
|
|
||||||
#include <extra2d/utils/logger.h>
|
|
||||||
#include <algorithm>
|
|
||||||
#include <cstring>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// TextureAtlasPage 实现
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
TextureAtlasPage::TextureAtlasPage(int width, int height)
|
|
||||||
: width_(width), height_(height), isFull_(false), usedArea_(0) {
|
|
||||||
// 创建空白纹理
|
|
||||||
std::vector<uint8_t> emptyData(width * height * 4, 0);
|
|
||||||
texture_ = makePtr<GLTexture>(width, height, emptyData.data(), 4);
|
|
||||||
|
|
||||||
// 初始化矩形打包根节点
|
|
||||||
root_ = std::make_unique<PackNode>(0, 0, width, height);
|
|
||||||
|
|
||||||
E2D_LOG_INFO("Created texture atlas page: {}x{}", width, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
TextureAtlasPage::~TextureAtlasPage() = default;
|
|
||||||
|
|
||||||
bool TextureAtlasPage::tryAddTexture(const std::string& name, int texWidth, int texHeight,
|
|
||||||
const uint8_t* pixels, Rect& outUvRect) {
|
|
||||||
if (isFull_) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 添加边距
|
|
||||||
int paddedWidth = texWidth + 2 * PADDING;
|
|
||||||
int paddedHeight = texHeight + 2 * PADDING;
|
|
||||||
|
|
||||||
// 如果纹理太大,无法放入
|
|
||||||
if (paddedWidth > width_ || paddedHeight > height_) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 尝试插入
|
|
||||||
PackNode* node = insert(root_.get(), paddedWidth, paddedHeight);
|
|
||||||
if (node == nullptr) {
|
|
||||||
// 无法放入,标记为满
|
|
||||||
isFull_ = true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 写入像素数据(跳过边距区域)
|
|
||||||
writePixels(node->x + PADDING, node->y + PADDING, texWidth, texHeight, pixels);
|
|
||||||
|
|
||||||
// 创建条目
|
|
||||||
AtlasEntry entry;
|
|
||||||
entry.name = name;
|
|
||||||
entry.originalSize = Vec2(static_cast<float>(texWidth), static_cast<float>(texHeight));
|
|
||||||
entry.padding = PADDING;
|
|
||||||
|
|
||||||
// 计算 UV 坐标(考虑边距)
|
|
||||||
float u1 = static_cast<float>(node->x + PADDING) / width_;
|
|
||||||
float v1 = static_cast<float>(node->y + PADDING) / height_;
|
|
||||||
float u2 = static_cast<float>(node->x + PADDING + texWidth) / width_;
|
|
||||||
float v2 = static_cast<float>(node->y + PADDING + texHeight) / height_;
|
|
||||||
|
|
||||||
entry.uvRect = Rect(u1, v1, u2 - u1, v2 - v1);
|
|
||||||
outUvRect = entry.uvRect;
|
|
||||||
|
|
||||||
entries_[name] = std::move(entry);
|
|
||||||
usedArea_ += paddedWidth * paddedHeight;
|
|
||||||
|
|
||||||
E2D_LOG_DEBUG("Added texture '{}' to atlas: {}x{} at ({}, {})",
|
|
||||||
name, texWidth, texHeight, node->x, node->y);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
TextureAtlasPage::PackNode* TextureAtlasPage::insert(PackNode* node, int width, int height) {
|
|
||||||
if (node == nullptr) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果节点已被使用,尝试子节点
|
|
||||||
if (node->used) {
|
|
||||||
PackNode* result = insert(node->left.get(), width, height);
|
|
||||||
if (result != nullptr) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
return insert(node->right.get(), width, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查是否适合
|
|
||||||
if (width > node->width || height > node->height) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果刚好合适,使用此节点
|
|
||||||
if (width == node->width && height == node->height) {
|
|
||||||
node->used = true;
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 需要分割节点
|
|
||||||
int dw = node->width - width;
|
|
||||||
int dh = node->height - height;
|
|
||||||
|
|
||||||
if (dw > dh) {
|
|
||||||
// 水平分割
|
|
||||||
node->left = std::make_unique<PackNode>(node->x, node->y, width, node->height);
|
|
||||||
node->right = std::make_unique<PackNode>(node->x + width, node->y, dw, node->height);
|
|
||||||
} else {
|
|
||||||
// 垂直分割
|
|
||||||
node->left = std::make_unique<PackNode>(node->x, node->y, node->width, height);
|
|
||||||
node->right = std::make_unique<PackNode>(node->x, node->y + height, node->width, dh);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 递归插入到左子节点
|
|
||||||
return insert(node->left.get(), width, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
void TextureAtlasPage::writePixels(int x, int y, int w, int h, const uint8_t* pixels) {
|
|
||||||
if (texture_ == nullptr || pixels == nullptr) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 使用 glTexSubImage2D 更新纹理数据
|
|
||||||
GLuint texID = static_cast<GLuint>(
|
|
||||||
reinterpret_cast<uintptr_t>(texture_->getNativeHandle()));
|
|
||||||
|
|
||||||
glBindTexture(GL_TEXTURE_2D, texID);
|
|
||||||
glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, w, h, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
const AtlasEntry* TextureAtlasPage::getEntry(const std::string& name) const {
|
|
||||||
auto it = entries_.find(name);
|
|
||||||
if (it != entries_.end()) {
|
|
||||||
return &it->second;
|
|
||||||
}
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
float TextureAtlasPage::getUsageRatio() const {
|
|
||||||
return static_cast<float>(usedArea_) / (width_ * height_);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// TextureAtlas 实现
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
TextureAtlas::TextureAtlas()
|
|
||||||
: pageSize_(TextureAtlasPage::DEFAULT_SIZE),
|
|
||||||
sizeThreshold_(256),
|
|
||||||
enabled_(true),
|
|
||||||
initialized_(false) {
|
|
||||||
}
|
|
||||||
|
|
||||||
TextureAtlas::~TextureAtlas() = default;
|
|
||||||
|
|
||||||
void TextureAtlas::init(int pageSize) {
|
|
||||||
pageSize_ = pageSize;
|
|
||||||
initialized_ = true;
|
|
||||||
E2D_LOG_INFO("TextureAtlas initialized with page size: {}", pageSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool TextureAtlas::addTexture(const std::string& name, int width, int height,
|
|
||||||
const uint8_t* pixels) {
|
|
||||||
if (!enabled_ || !initialized_) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查是否已存在
|
|
||||||
if (contains(name)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查纹理大小
|
|
||||||
if (width > sizeThreshold_ || height > sizeThreshold_) {
|
|
||||||
E2D_LOG_DEBUG("Texture '{}' too large for atlas ({}x{} > {}), skipping",
|
|
||||||
name, width, height, sizeThreshold_);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 尝试添加到现有页面
|
|
||||||
Rect uvRect;
|
|
||||||
for (auto& page : pages_) {
|
|
||||||
if (page->tryAddTexture(name, width, height, pixels, uvRect)) {
|
|
||||||
entryToPage_[name] = page.get();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建新页面
|
|
||||||
auto newPage = std::make_unique<TextureAtlasPage>(pageSize_, pageSize_);
|
|
||||||
if (newPage->tryAddTexture(name, width, height, pixels, uvRect)) {
|
|
||||||
entryToPage_[name] = newPage.get();
|
|
||||||
pages_.push_back(std::move(newPage));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
E2D_LOG_WARN("Failed to add texture '{}' to atlas", name);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool TextureAtlas::contains(const std::string& name) const {
|
|
||||||
return entryToPage_.find(name) != entryToPage_.end();
|
|
||||||
}
|
|
||||||
|
|
||||||
const Texture* TextureAtlas::getAtlasTexture(const std::string& name) const {
|
|
||||||
auto it = entryToPage_.find(name);
|
|
||||||
if (it != entryToPage_.end()) {
|
|
||||||
return it->second->getTexture().get();
|
|
||||||
}
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
Rect TextureAtlas::getUVRect(const std::string& name) const {
|
|
||||||
auto it = entryToPage_.find(name);
|
|
||||||
if (it != entryToPage_.end()) {
|
|
||||||
const AtlasEntry* entry = it->second->getEntry(name);
|
|
||||||
if (entry != nullptr) {
|
|
||||||
return entry->uvRect;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Rect(0, 0, 1, 1); // 默认 UV
|
|
||||||
}
|
|
||||||
|
|
||||||
Vec2 TextureAtlas::getOriginalSize(const std::string& name) const {
|
|
||||||
auto it = entryToPage_.find(name);
|
|
||||||
if (it != entryToPage_.end()) {
|
|
||||||
const AtlasEntry* entry = it->second->getEntry(name);
|
|
||||||
if (entry != nullptr) {
|
|
||||||
return entry->originalSize;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Vec2(0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
float TextureAtlas::getTotalUsageRatio() const {
|
|
||||||
if (pages_.empty()) {
|
|
||||||
return 0.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
float total = 0.0f;
|
|
||||||
for (const auto& page : pages_) {
|
|
||||||
total += page->getUsageRatio();
|
|
||||||
}
|
|
||||||
return total / pages_.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
void TextureAtlas::clear() {
|
|
||||||
pages_.clear();
|
|
||||||
entryToPage_.clear();
|
|
||||||
E2D_LOG_INFO("TextureAtlas cleared");
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// TextureAtlasManager 单例实现
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
TextureAtlasManager& TextureAtlasManager::getInstance() {
|
|
||||||
static TextureAtlasManager instance;
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,113 +0,0 @@
|
||||||
#include <algorithm>
|
|
||||||
#include <extra2d/graphics/vram_manager.h>
|
|
||||||
#include <extra2d/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
|
|
||||||
|
|
@ -0,0 +1,196 @@
|
||||||
|
#include <extra2d/render/buffer.h>
|
||||||
|
#include <extra2d/render/render_stats.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
Buffer::Buffer() = default;
|
||||||
|
|
||||||
|
Buffer::~Buffer() {
|
||||||
|
destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
Buffer::Buffer(Buffer&& other) noexcept
|
||||||
|
: id_(other.id_)
|
||||||
|
, type_(other.type_)
|
||||||
|
, usage_(other.usage_)
|
||||||
|
, size_(other.size_)
|
||||||
|
, mapped_(other.mapped_) {
|
||||||
|
other.id_ = 0;
|
||||||
|
other.size_ = 0;
|
||||||
|
other.mapped_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Buffer& Buffer::operator=(Buffer&& other) noexcept {
|
||||||
|
if (this != &other) {
|
||||||
|
destroy();
|
||||||
|
id_ = other.id_;
|
||||||
|
type_ = other.type_;
|
||||||
|
usage_ = other.usage_;
|
||||||
|
size_ = other.size_;
|
||||||
|
mapped_ = other.mapped_;
|
||||||
|
other.id_ = 0;
|
||||||
|
other.size_ = 0;
|
||||||
|
other.mapped_ = false;
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Buffer::create(BufferType type, BufferUsage usage, size_t size, const void* data) {
|
||||||
|
destroy();
|
||||||
|
|
||||||
|
type_ = type;
|
||||||
|
usage_ = usage;
|
||||||
|
size_ = size;
|
||||||
|
|
||||||
|
glCreateBuffers(1, &id_);
|
||||||
|
|
||||||
|
if (id_ == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
GLenum glUsageFlags = 0;
|
||||||
|
switch (usage) {
|
||||||
|
case BufferUsage::Static:
|
||||||
|
glUsageFlags = GL_STATIC_DRAW;
|
||||||
|
break;
|
||||||
|
case BufferUsage::Dynamic:
|
||||||
|
glUsageFlags = GL_DYNAMIC_DRAW;
|
||||||
|
break;
|
||||||
|
case BufferUsage::Stream:
|
||||||
|
glUsageFlags = GL_STREAM_DRAW;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
glNamedBufferData(id_, static_cast<GLsizeiptr>(size), data, glUsageFlags);
|
||||||
|
|
||||||
|
E2D_RENDER_STATS().addBufferUpdate();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Buffer::destroy() {
|
||||||
|
if (id_ != 0) {
|
||||||
|
glDeleteBuffers(1, &id_);
|
||||||
|
id_ = 0;
|
||||||
|
size_ = 0;
|
||||||
|
mapped_ = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Buffer::update(size_t offset, size_t size, const void* data) {
|
||||||
|
if (id_ == 0 || offset + size > size_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
glNamedBufferSubData(id_, static_cast<GLintptr>(offset),
|
||||||
|
static_cast<GLsizeiptr>(size), data);
|
||||||
|
|
||||||
|
E2D_RENDER_STATS().addBufferUpdate();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void* Buffer::map(GLenum access) {
|
||||||
|
if (id_ == 0 || mapped_) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void* ptr = glMapNamedBuffer(id_, access);
|
||||||
|
if (ptr) {
|
||||||
|
mapped_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Buffer::unmap() {
|
||||||
|
if (id_ == 0 || !mapped_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
GLboolean result = glUnmapNamedBuffer(id_);
|
||||||
|
mapped_ = false;
|
||||||
|
|
||||||
|
return result == GL_TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Buffer::bindBase(uint32 binding) {
|
||||||
|
if (id_ == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
glBindBufferBase(glTarget(), binding, id_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Buffer::bindRange(uint32 binding, size_t offset, size_t size) {
|
||||||
|
if (id_ == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
glBindBufferRange(glTarget(), binding, id_,
|
||||||
|
static_cast<GLintptr>(offset),
|
||||||
|
static_cast<GLsizeiptr>(size));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Buffer::bind() {
|
||||||
|
if (id_ == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
glBindBuffer(glTarget(), id_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Buffer::unbind() {
|
||||||
|
glBindBuffer(glTarget(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Buffer::allocate(size_t size) {
|
||||||
|
return create(type_, usage_, size, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Buffer::setData(const void* data, size_t size) {
|
||||||
|
if (id_ == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (size != size_) {
|
||||||
|
return create(type_, usage_, size, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
glNamedBufferSubData(id_, 0, static_cast<GLsizeiptr>(size), data);
|
||||||
|
E2D_RENDER_STATS().addBufferUpdate();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
GLenum Buffer::glTarget() const {
|
||||||
|
switch (type_) {
|
||||||
|
case BufferType::Vertex:
|
||||||
|
return GL_ARRAY_BUFFER;
|
||||||
|
case BufferType::Index:
|
||||||
|
return GL_ELEMENT_ARRAY_BUFFER;
|
||||||
|
case BufferType::Uniform:
|
||||||
|
return GL_UNIFORM_BUFFER;
|
||||||
|
case BufferType::PixelPack:
|
||||||
|
return GL_PIXEL_PACK_BUFFER;
|
||||||
|
case BufferType::PixelUnpack:
|
||||||
|
return GL_PIXEL_UNPACK_BUFFER;
|
||||||
|
default:
|
||||||
|
return GL_ARRAY_BUFFER;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GLenum Buffer::glUsage() const {
|
||||||
|
switch (usage_) {
|
||||||
|
case BufferUsage::Static:
|
||||||
|
return GL_STATIC_DRAW;
|
||||||
|
case BufferUsage::Dynamic:
|
||||||
|
return GL_DYNAMIC_DRAW;
|
||||||
|
case BufferUsage::Stream:
|
||||||
|
return GL_STREAM_DRAW;
|
||||||
|
default:
|
||||||
|
return GL_STATIC_DRAW;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,208 @@
|
||||||
|
#include <extra2d/render/render_context.h>
|
||||||
|
#include <extra2d/render/render_stats.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
RenderContext& RenderContext::instance() {
|
||||||
|
static RenderContext instance;
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RenderContext::init() {
|
||||||
|
if (initialized_) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!device_.init()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
queue_.reserve(RenderQueue::INITIAL_CAPACITY);
|
||||||
|
|
||||||
|
viewport_ = {0, 0, 800, 600};
|
||||||
|
scissor_ = {0, 0, 0, 0, false};
|
||||||
|
clearColor_ = Color::black();
|
||||||
|
|
||||||
|
currentBlend_ = BlendState::opaque();
|
||||||
|
currentDepth_ = DepthState{};
|
||||||
|
currentRaster_ = RasterState{};
|
||||||
|
|
||||||
|
initialized_ = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderContext::shutdown() {
|
||||||
|
if (!initialized_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
queue_.clear();
|
||||||
|
while (!viewportStack_.empty()) viewportStack_.pop();
|
||||||
|
while (!scissorStack_.empty()) scissorStack_.pop();
|
||||||
|
|
||||||
|
device_.shutdown();
|
||||||
|
initialized_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderContext::beginFrame() {
|
||||||
|
queue_.clear();
|
||||||
|
resetStats();
|
||||||
|
E2D_RENDER_STATS().reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderContext::endFrame() {
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderContext::submit(const RenderCommand& cmd) {
|
||||||
|
queue_.addCommand(cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderContext::submit(RenderCommand&& cmd) {
|
||||||
|
queue_.addCommand(std::move(cmd));
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderContext::flush() {
|
||||||
|
queue_.sort();
|
||||||
|
|
||||||
|
for (const auto& cmd : queue_.commands()) {
|
||||||
|
executeCommand(cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
queue_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderContext::executeCommand(const RenderCommand& cmd) {
|
||||||
|
if (cmd.type == RenderCommandType::None) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cmd.type == RenderCommandType::Custom && cmd.customCallback) {
|
||||||
|
cmd.customCallback();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cmd.blend.enabled != currentBlend_.enabled ||
|
||||||
|
cmd.blend.mode != currentBlend_.mode) {
|
||||||
|
device_.setBlendState(cmd.blend);
|
||||||
|
currentBlend_ = cmd.blend;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cmd.depth.testEnabled != currentDepth_.testEnabled ||
|
||||||
|
cmd.depth.writeEnabled != currentDepth_.writeEnabled) {
|
||||||
|
device_.setDepthState(cmd.depth);
|
||||||
|
currentDepth_ = cmd.depth;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cmd.program != currentProgram_) {
|
||||||
|
device_.bindProgram(cmd.program);
|
||||||
|
currentProgram_ = cmd.program;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cmd.texture != currentTexture_) {
|
||||||
|
device_.bindTexture(0, cmd.texture);
|
||||||
|
currentTexture_ = cmd.texture;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cmd.vao != currentVAO_) {
|
||||||
|
if (cmd.vao) {
|
||||||
|
cmd.vao->bind();
|
||||||
|
}
|
||||||
|
currentVAO_ = cmd.vao;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (cmd.type) {
|
||||||
|
case RenderCommandType::DrawArrays:
|
||||||
|
device_.drawArrays(cmd.primitive, cmd.firstVertex, cmd.vertexCount);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RenderCommandType::DrawElements:
|
||||||
|
device_.drawElements(cmd.primitive, cmd.indexCount, cmd.indexType, cmd.indices);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RenderCommandType::DrawArraysInstanced:
|
||||||
|
device_.drawArraysInstanced(cmd.primitive, cmd.firstVertex,
|
||||||
|
cmd.vertexCount, cmd.instanceCount);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RenderCommandType::DrawElementsInstanced:
|
||||||
|
device_.drawElementsInstanced(cmd.primitive, cmd.indexCount,
|
||||||
|
cmd.indexType, cmd.indices, cmd.instanceCount);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderContext::setViewport(int32 x, int32 y, int32 width, int32 height) {
|
||||||
|
viewport_.x = x;
|
||||||
|
viewport_.y = y;
|
||||||
|
viewport_.width = width;
|
||||||
|
viewport_.height = height;
|
||||||
|
device_.setViewport(x, y, width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderContext::setViewport(const Viewport& vp) {
|
||||||
|
viewport_ = vp;
|
||||||
|
device_.setViewport(vp.x, vp.y, vp.width, vp.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderContext::setScissor(const ScissorRect& rect) {
|
||||||
|
scissor_ = rect;
|
||||||
|
device_.setScissorEnabled(rect.enabled);
|
||||||
|
if (rect.enabled) {
|
||||||
|
device_.setScissor(rect.x, rect.y, rect.width, rect.height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderContext::setScissor(int32 x, int32 y, int32 width, int32 height) {
|
||||||
|
scissor_.x = x;
|
||||||
|
scissor_.y = y;
|
||||||
|
scissor_.width = width;
|
||||||
|
scissor_.height = height;
|
||||||
|
scissor_.enabled = true;
|
||||||
|
device_.setScissorEnabled(true);
|
||||||
|
device_.setScissor(x, y, width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderContext::pushViewport() {
|
||||||
|
viewportStack_.push(viewport_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderContext::popViewport() {
|
||||||
|
if (!viewportStack_.empty()) {
|
||||||
|
setViewport(viewportStack_.top());
|
||||||
|
viewportStack_.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderContext::pushScissor() {
|
||||||
|
scissorStack_.push(scissor_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderContext::popScissor() {
|
||||||
|
if (!scissorStack_.empty()) {
|
||||||
|
setScissor(scissorStack_.top());
|
||||||
|
scissorStack_.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderContext::setClearColor(const Color& color) {
|
||||||
|
clearColor_ = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderContext::clear(bool color, bool depth, bool stencil) {
|
||||||
|
device_.clear(color, depth, stencil,
|
||||||
|
clearColor_.r, clearColor_.g, clearColor_.b, clearColor_.a);
|
||||||
|
}
|
||||||
|
|
||||||
|
const RenderStats& RenderContext::stats() const {
|
||||||
|
return E2D_RENDER_STATS().stats();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderContext::resetStats() {
|
||||||
|
E2D_RENDER_STATS().reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,322 @@
|
||||||
|
#include <extra2d/render/render_device.h>
|
||||||
|
#include <extra2d/render/render_stats.h>
|
||||||
|
#include <cstdio>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
RenderDevice& RenderDevice::instance() {
|
||||||
|
static RenderDevice instance;
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RenderDevice::init() {
|
||||||
|
if (initialized_) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
GLint majorVersion = 0;
|
||||||
|
GLint minorVersion = 0;
|
||||||
|
glGetIntegerv(GL_MAJOR_VERSION, &majorVersion);
|
||||||
|
glGetIntegerv(GL_MINOR_VERSION, &minorVersion);
|
||||||
|
|
||||||
|
if (majorVersion < 4 || (majorVersion == 4 && minorVersion < 5)) {
|
||||||
|
printf("OpenGL 4.5 is required, got %d.%d\n", majorVersion, minorVersion);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
queryCaps();
|
||||||
|
|
||||||
|
if (!caps_.dsaSupported) {
|
||||||
|
printf("OpenGL 4.5 DSA is not supported\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
glEnable(GL_TEXTURE_2D);
|
||||||
|
|
||||||
|
currentBlend_ = BlendState::opaque();
|
||||||
|
currentDepth_ = DepthState{};
|
||||||
|
currentRaster_ = RasterState{};
|
||||||
|
|
||||||
|
initialized_ = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderDevice::shutdown() {
|
||||||
|
if (!initialized_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentProgram_ = 0;
|
||||||
|
initialized_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderDevice::queryCaps() {
|
||||||
|
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &caps_.maxTextureSize);
|
||||||
|
glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &caps_.maxTextureUnits);
|
||||||
|
glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &caps_.maxVertexAttribs);
|
||||||
|
glGetIntegerv(GL_MAX_UNIFORM_BUFFER_BINDINGS, &caps_.maxUniformBlockBindings);
|
||||||
|
glGetIntegerv(GL_MAX_UNIFORM_BLOCK_SIZE, &caps_.maxUniformBlockSize);
|
||||||
|
glGetIntegerv(GL_MAX_VERTEX_UNIFORM_COMPONENTS, &caps_.maxVertexUniformComponents);
|
||||||
|
glGetIntegerv(GL_MAX_FRAGMENT_UNIFORM_COMPONENTS, &caps_.maxFragmentUniformComponents);
|
||||||
|
glGetIntegerv(GL_MAX_DRAW_BUFFERS, &caps_.maxDrawBuffers);
|
||||||
|
glGetIntegerv(GL_MAX_SAMPLES, &caps_.maxSamples);
|
||||||
|
|
||||||
|
GLint majorVersion = 0;
|
||||||
|
GLint minorVersion = 0;
|
||||||
|
glGetIntegerv(GL_MAJOR_VERSION, &majorVersion);
|
||||||
|
glGetIntegerv(GL_MINOR_VERSION, &minorVersion);
|
||||||
|
caps_.dsaSupported = (majorVersion > 4 || (majorVersion == 4 && minorVersion >= 5));
|
||||||
|
|
||||||
|
GLint extensions = 0;
|
||||||
|
glGetIntegerv(GL_NUM_EXTENSIONS, &extensions);
|
||||||
|
for (GLint i = 0; i < extensions; ++i) {
|
||||||
|
String ext = reinterpret_cast<const char*>(glGetStringi(GL_EXTENSIONS, i));
|
||||||
|
if (ext.find("compute_shader") != String::npos) {
|
||||||
|
caps_.computeSupported = true;
|
||||||
|
}
|
||||||
|
if (ext.find("tessellation_shader") != String::npos) {
|
||||||
|
caps_.tessellationSupported = true;
|
||||||
|
}
|
||||||
|
if (ext.find("geometry_shader") != String::npos) {
|
||||||
|
caps_.geometryShaderSupported = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
caps_.renderer = reinterpret_cast<const char*>(glGetString(GL_RENDERER));
|
||||||
|
caps_.vendor = reinterpret_cast<const char*>(glGetString(GL_VENDOR));
|
||||||
|
caps_.version = reinterpret_cast<const char*>(glGetString(GL_VERSION));
|
||||||
|
caps_.glslVersion = reinterpret_cast<const char*>(glGetString(GL_SHADING_LANGUAGE_VERSION));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<Buffer> RenderDevice::createBuffer(BufferType type, BufferUsage usage,
|
||||||
|
size_t size, const void* data) {
|
||||||
|
auto buffer = std::make_unique<Buffer>();
|
||||||
|
if (!buffer->create(type, usage, size, data)) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<VertexBuffer> RenderDevice::createVertexBuffer(BufferUsage usage, size_t size,
|
||||||
|
const void* data) {
|
||||||
|
auto buffer = std::make_unique<VertexBuffer>();
|
||||||
|
if (!buffer->create(usage, size, data)) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<IndexBuffer> RenderDevice::createIndexBuffer(BufferUsage usage, size_t size,
|
||||||
|
const void* data) {
|
||||||
|
auto buffer = std::make_unique<IndexBuffer>();
|
||||||
|
if (!buffer->create(usage, size, data)) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<UniformBuffer> RenderDevice::createUniformBuffer(BufferUsage usage, size_t size,
|
||||||
|
const void* data) {
|
||||||
|
auto buffer = std::make_unique<UniformBuffer>();
|
||||||
|
if (!buffer->create(usage, size, data)) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<VAO> RenderDevice::createVAO() {
|
||||||
|
auto vao = std::make_unique<VAO>();
|
||||||
|
if (!vao->create()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return vao;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderDevice::setViewport(int32 x, int32 y, int32 width, int32 height) {
|
||||||
|
glViewport(x, y, width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderDevice::setScissor(int32 x, int32 y, int32 width, int32 height) {
|
||||||
|
glScissor(x, y, width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderDevice::setScissorEnabled(bool enabled) {
|
||||||
|
if (enabled) {
|
||||||
|
glEnable(GL_SCISSOR_TEST);
|
||||||
|
} else {
|
||||||
|
glDisable(GL_SCISSOR_TEST);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderDevice::clear(bool color, bool depth, bool stencil,
|
||||||
|
float r, float g, float b, float a) {
|
||||||
|
GLbitfield mask = 0;
|
||||||
|
if (color) {
|
||||||
|
glClearColor(r, g, b, a);
|
||||||
|
mask |= GL_COLOR_BUFFER_BIT;
|
||||||
|
}
|
||||||
|
if (depth) {
|
||||||
|
glClearDepth(1.0);
|
||||||
|
mask |= GL_DEPTH_BUFFER_BIT;
|
||||||
|
}
|
||||||
|
if (stencil) {
|
||||||
|
glClearStencil(0);
|
||||||
|
mask |= GL_STENCIL_BUFFER_BIT;
|
||||||
|
}
|
||||||
|
glClear(mask);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderDevice::setBlendState(const BlendState& state) {
|
||||||
|
if (state.enabled != currentBlend_.enabled) {
|
||||||
|
if (state.enabled) {
|
||||||
|
glEnable(GL_BLEND);
|
||||||
|
} else {
|
||||||
|
glDisable(GL_BLEND);
|
||||||
|
}
|
||||||
|
E2D_RENDER_STATS().addStateChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.enabled) {
|
||||||
|
if (state.srcRGB != currentBlend_.srcRGB ||
|
||||||
|
state.dstRGB != currentBlend_.dstRGB ||
|
||||||
|
state.srcAlpha != currentBlend_.srcAlpha ||
|
||||||
|
state.dstAlpha != currentBlend_.dstAlpha) {
|
||||||
|
glBlendFuncSeparate(
|
||||||
|
static_cast<GLenum>(state.srcRGB) + GL_ZERO,
|
||||||
|
static_cast<GLenum>(state.dstRGB) + GL_ZERO,
|
||||||
|
static_cast<GLenum>(state.srcAlpha) + GL_ZERO,
|
||||||
|
static_cast<GLenum>(state.dstAlpha) + GL_ZERO
|
||||||
|
);
|
||||||
|
E2D_RENDER_STATS().addStateChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.opRGB != currentBlend_.opRGB || state.opAlpha != currentBlend_.opAlpha) {
|
||||||
|
glBlendEquationSeparate(
|
||||||
|
static_cast<GLenum>(state.opRGB) + GL_FUNC_ADD,
|
||||||
|
static_cast<GLenum>(state.opAlpha) + GL_FUNC_ADD
|
||||||
|
);
|
||||||
|
E2D_RENDER_STATS().addStateChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.colorWriteMask != currentBlend_.colorWriteMask) {
|
||||||
|
glColorMask(
|
||||||
|
(state.colorWriteMask & 0x1) != 0,
|
||||||
|
(state.colorWriteMask & 0x2) != 0,
|
||||||
|
(state.colorWriteMask & 0x4) != 0,
|
||||||
|
(state.colorWriteMask & 0x8) != 0
|
||||||
|
);
|
||||||
|
E2D_RENDER_STATS().addStateChange();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
currentBlend_ = state;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderDevice::setDepthState(const DepthState& state) {
|
||||||
|
if (state.testEnabled != currentDepth_.testEnabled) {
|
||||||
|
if (state.testEnabled) {
|
||||||
|
glEnable(GL_DEPTH_TEST);
|
||||||
|
} else {
|
||||||
|
glDisable(GL_DEPTH_TEST);
|
||||||
|
}
|
||||||
|
E2D_RENDER_STATS().addStateChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.testEnabled && state.compareFunc != currentDepth_.compareFunc) {
|
||||||
|
glDepthFunc(static_cast<GLenum>(state.compareFunc) + GL_NEVER);
|
||||||
|
E2D_RENDER_STATS().addStateChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.writeEnabled != currentDepth_.writeEnabled) {
|
||||||
|
glDepthMask(state.writeEnabled ? GL_TRUE : GL_FALSE);
|
||||||
|
E2D_RENDER_STATS().addStateChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
currentDepth_ = state;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderDevice::setRasterState(const RasterState& state) {
|
||||||
|
if (state.cullMode != currentRaster_.cullMode) {
|
||||||
|
if (state.cullMode == CullMode::None) {
|
||||||
|
glDisable(GL_CULL_FACE);
|
||||||
|
} else {
|
||||||
|
glEnable(GL_CULL_FACE);
|
||||||
|
glCullFace(state.cullMode == CullMode::Front ? GL_FRONT : GL_BACK);
|
||||||
|
}
|
||||||
|
E2D_RENDER_STATS().addStateChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.polygonMode != currentRaster_.polygonMode) {
|
||||||
|
glPolygonMode(GL_FRONT_AND_BACK,
|
||||||
|
state.polygonMode == PolygonMode::Line ? GL_LINE :
|
||||||
|
(state.polygonMode == PolygonMode::Point ? GL_POINT : GL_FILL));
|
||||||
|
E2D_RENDER_STATS().addStateChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.lineWidth != currentRaster_.lineWidth) {
|
||||||
|
glLineWidth(state.lineWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
currentRaster_ = state;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderDevice::bindTexture(uint32 unit, GLuint texture) {
|
||||||
|
glBindTextureUnit(unit, texture);
|
||||||
|
E2D_RENDER_STATS().addTextureBind();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderDevice::bindProgram(GLuint program) {
|
||||||
|
if (program != currentProgram_) {
|
||||||
|
glUseProgram(program);
|
||||||
|
currentProgram_ = program;
|
||||||
|
E2D_RENDER_STATS().addShaderBind();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderDevice::drawArrays(PrimitiveType mode, int32 first, int32 count) {
|
||||||
|
glDrawArrays(glPrimitiveType(mode), first, count);
|
||||||
|
E2D_RENDER_STATS().addDrawCall(count, count / 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderDevice::drawElements(PrimitiveType mode, int32 count, IndexType type,
|
||||||
|
const void* indices) {
|
||||||
|
glDrawElements(glPrimitiveType(mode), count, glIndexType(type), indices);
|
||||||
|
E2D_RENDER_STATS().addDrawCall(count, count / 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderDevice::drawArraysInstanced(PrimitiveType mode, int32 first,
|
||||||
|
int32 count, int32 instanceCount) {
|
||||||
|
glDrawArraysInstanced(glPrimitiveType(mode), first, count, instanceCount);
|
||||||
|
E2D_RENDER_STATS().addDrawCall(count * instanceCount, (count / 3) * instanceCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderDevice::drawElementsInstanced(PrimitiveType mode, int32 count, IndexType type,
|
||||||
|
const void* indices, int32 instanceCount) {
|
||||||
|
glDrawElementsInstanced(glPrimitiveType(mode), count, glIndexType(type),
|
||||||
|
indices, instanceCount);
|
||||||
|
E2D_RENDER_STATS().addDrawCall(count * instanceCount, (count / 3) * instanceCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
GLenum RenderDevice::glPrimitiveType(PrimitiveType mode) const {
|
||||||
|
switch (mode) {
|
||||||
|
case PrimitiveType::Points: return GL_POINTS;
|
||||||
|
case PrimitiveType::Lines: return GL_LINES;
|
||||||
|
case PrimitiveType::LineStrip: return GL_LINE_STRIP;
|
||||||
|
case PrimitiveType::LineLoop: return GL_LINE_LOOP;
|
||||||
|
case PrimitiveType::Triangles: return GL_TRIANGLES;
|
||||||
|
case PrimitiveType::TriangleStrip: return GL_TRIANGLE_STRIP;
|
||||||
|
case PrimitiveType::TriangleFan: return GL_TRIANGLE_FAN;
|
||||||
|
default: return GL_TRIANGLES;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GLenum RenderDevice::glIndexType(IndexType type) const {
|
||||||
|
switch (type) {
|
||||||
|
case IndexType::UInt8: return GL_UNSIGNED_BYTE;
|
||||||
|
case IndexType::UInt16: return GL_UNSIGNED_SHORT;
|
||||||
|
case IndexType::UInt32: return GL_UNSIGNED_INT;
|
||||||
|
default: return GL_UNSIGNED_INT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,74 @@
|
||||||
|
#include <extra2d/render/render_queue.h>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
RenderQueue::RenderQueue() {
|
||||||
|
commands_.reserve(INITIAL_CAPACITY);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderQueue::clear() {
|
||||||
|
commands_.clear();
|
||||||
|
currentOrder_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderQueue::reserve(size_t capacity) {
|
||||||
|
commands_.reserve(capacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderQueue::addCommand(const RenderCommand& cmd) {
|
||||||
|
commands_.push_back(cmd);
|
||||||
|
commands_.back().key.order = currentOrder_++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderQueue::addCommand(RenderCommand&& cmd) {
|
||||||
|
cmd.key.order = currentOrder_++;
|
||||||
|
commands_.push_back(std::move(cmd));
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderQueue::sort() {
|
||||||
|
std::sort(commands_.begin(), commands_.end(), compareCommands);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RenderQueue::compareCommands(const RenderCommand& a, const RenderCommand& b) {
|
||||||
|
return a.key.value < b.key.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BatchHelper::reset() {
|
||||||
|
currentProgram_ = 0;
|
||||||
|
currentTexture_ = 0;
|
||||||
|
currentVAO_ = nullptr;
|
||||||
|
currentBlend_ = BlendState{};
|
||||||
|
currentDepth_ = DepthState{};
|
||||||
|
batchCount_ = 0;
|
||||||
|
batchVertexCount_ = 0;
|
||||||
|
batchIndexCount_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BatchHelper::canBatch(const RenderCommand& cmd) const {
|
||||||
|
if (batchCount_ == 0) return true;
|
||||||
|
|
||||||
|
return cmd.program == currentProgram_ &&
|
||||||
|
cmd.texture == currentTexture_ &&
|
||||||
|
cmd.vao == currentVAO_ &&
|
||||||
|
cmd.blend.enabled == currentBlend_.enabled &&
|
||||||
|
cmd.blend.mode == currentBlend_.mode &&
|
||||||
|
cmd.depth.testEnabled == currentDepth_.testEnabled &&
|
||||||
|
cmd.depth.writeEnabled == currentDepth_.writeEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BatchHelper::addToBatch(const RenderCommand& cmd) {
|
||||||
|
if (batchCount_ == 0) {
|
||||||
|
currentProgram_ = cmd.program;
|
||||||
|
currentTexture_ = cmd.texture;
|
||||||
|
currentVAO_ = cmd.vao;
|
||||||
|
currentBlend_ = cmd.blend;
|
||||||
|
currentDepth_ = cmd.depth;
|
||||||
|
}
|
||||||
|
|
||||||
|
batchCount_++;
|
||||||
|
batchVertexCount_ += cmd.vertexCount;
|
||||||
|
batchIndexCount_ += cmd.indexCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
#include <extra2d/render/render_stats.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
RenderStatsTracker& RenderStatsTracker::instance() {
|
||||||
|
static RenderStatsTracker instance;
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderStatsTracker::addDrawCall(uint32 vertexCount, uint32 triangleCount) {
|
||||||
|
stats_.drawCalls++;
|
||||||
|
stats_.vertices += vertexCount;
|
||||||
|
stats_.triangles += triangleCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderStatsTracker::addStateChange() {
|
||||||
|
stats_.stateChanges++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderStatsTracker::addTextureBind() {
|
||||||
|
stats_.textureBinds++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderStatsTracker::addShaderBind() {
|
||||||
|
stats_.shaderBinds++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderStatsTracker::addBufferUpdate() {
|
||||||
|
stats_.bufferUpdates++;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,294 @@
|
||||||
|
#include <extra2d/render/shape_renderer.h>
|
||||||
|
#include <extra2d/render/render_device.h>
|
||||||
|
#include <extra2d/render/render_stats.h>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
static const char* SHAPE_VERTEX_SHADER = R"(
|
||||||
|
#version 450 core
|
||||||
|
layout(location = 0) in vec2 a_position;
|
||||||
|
layout(location = 1) in vec4 a_color;
|
||||||
|
|
||||||
|
uniform mat4 u_viewProjection;
|
||||||
|
|
||||||
|
out vec4 v_color;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
gl_Position = u_viewProjection * vec4(a_position, 0.0, 1.0);
|
||||||
|
v_color = a_color;
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
|
||||||
|
static const char* SHAPE_FRAGMENT_SHADER = R"(
|
||||||
|
#version 450 core
|
||||||
|
in vec4 v_color;
|
||||||
|
|
||||||
|
out vec4 fragColor;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
fragColor = v_color;
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
|
||||||
|
ShapeRenderer::ShapeRenderer() {
|
||||||
|
vertices_.resize(MAX_VERTICES);
|
||||||
|
}
|
||||||
|
|
||||||
|
ShapeRenderer::~ShapeRenderer() {
|
||||||
|
shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ShapeRenderer::init() {
|
||||||
|
auto& device = RenderDevice::instance();
|
||||||
|
|
||||||
|
vao_ = device.createVAO();
|
||||||
|
if (!vao_) return false;
|
||||||
|
|
||||||
|
vbo_ = device.createVertexBuffer(BufferUsage::Dynamic,
|
||||||
|
MAX_VERTICES * sizeof(ShapeVertex), nullptr);
|
||||||
|
if (!vbo_) return false;
|
||||||
|
|
||||||
|
VertexFormatDesc format;
|
||||||
|
format.stride = sizeof(ShapeVertex);
|
||||||
|
format.addAttribute(0, VertexAttribType::Float2, offsetof(ShapeVertex, x));
|
||||||
|
format.addAttribute(1, VertexAttribType::Float4, offsetof(ShapeVertex, r));
|
||||||
|
|
||||||
|
vao_->setFormat(format);
|
||||||
|
vao_->bindVertexBuffer(0, *vbo_, 0, sizeof(ShapeVertex));
|
||||||
|
|
||||||
|
if (!createShaders()) return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShapeRenderer::shutdown() {
|
||||||
|
if (fillShader_) {
|
||||||
|
glDeleteProgram(fillShader_);
|
||||||
|
fillShader_ = 0;
|
||||||
|
}
|
||||||
|
if (lineShader_) {
|
||||||
|
glDeleteProgram(lineShader_);
|
||||||
|
lineShader_ = 0;
|
||||||
|
}
|
||||||
|
vao_.reset();
|
||||||
|
vbo_.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShapeRenderer::begin(const glm::mat4& viewProjection) {
|
||||||
|
viewProjection_ = viewProjection;
|
||||||
|
vertexCount_ = 0;
|
||||||
|
drawCalls_ = 0;
|
||||||
|
currentLineWidth_ = 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShapeRenderer::fillRect(float x, float y, float width, float height, const glm::vec4& color) {
|
||||||
|
if (vertexCount_ + 6 > MAX_VERTICES) {
|
||||||
|
flush(GL_TRIANGLES);
|
||||||
|
}
|
||||||
|
|
||||||
|
addVertex(x, y, color);
|
||||||
|
addVertex(x + width, y, color);
|
||||||
|
addVertex(x + width, y + height, color);
|
||||||
|
|
||||||
|
addVertex(x, y, color);
|
||||||
|
addVertex(x + width, y + height, color);
|
||||||
|
addVertex(x, y + height, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShapeRenderer::drawRect(float x, float y, float width, float height, const glm::vec4& color, float lineWidth) {
|
||||||
|
if (vertexCount_ + 8 > MAX_VERTICES) {
|
||||||
|
flush(GL_LINES);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lineWidth != currentLineWidth_) {
|
||||||
|
flush(GL_LINES);
|
||||||
|
currentLineWidth_ = lineWidth;
|
||||||
|
glLineWidth(lineWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
addVertex(x, y, color);
|
||||||
|
addVertex(x + width, y, color);
|
||||||
|
|
||||||
|
addVertex(x + width, y, color);
|
||||||
|
addVertex(x + width, y + height, color);
|
||||||
|
|
||||||
|
addVertex(x + width, y + height, color);
|
||||||
|
addVertex(x, y + height, color);
|
||||||
|
|
||||||
|
addVertex(x, y + height, color);
|
||||||
|
addVertex(x, y, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShapeRenderer::fillCircle(float x, float y, float radius, const glm::vec4& color, int segments) {
|
||||||
|
if (vertexCount_ + static_cast<size_t>(segments) * 3 > MAX_VERTICES) {
|
||||||
|
flush(GL_TRIANGLES);
|
||||||
|
}
|
||||||
|
|
||||||
|
float angleStep = 2.0f * 3.14159265f / static_cast<float>(segments);
|
||||||
|
|
||||||
|
for (int i = 0; i < segments; ++i) {
|
||||||
|
float angle1 = static_cast<float>(i) * angleStep;
|
||||||
|
float angle2 = static_cast<float>(i + 1) * angleStep;
|
||||||
|
|
||||||
|
addVertex(x, y, color);
|
||||||
|
addVertex(x + radius * std::cos(angle1), y + radius * std::sin(angle1), color);
|
||||||
|
addVertex(x + radius * std::cos(angle2), y + radius * std::sin(angle2), color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShapeRenderer::drawCircle(float x, float y, float radius, const glm::vec4& color, float lineWidth, int segments) {
|
||||||
|
if (vertexCount_ + static_cast<size_t>(segments) * 2 > MAX_VERTICES) {
|
||||||
|
flush(GL_LINES);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lineWidth != currentLineWidth_) {
|
||||||
|
flush(GL_LINES);
|
||||||
|
currentLineWidth_ = lineWidth;
|
||||||
|
glLineWidth(lineWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
float angleStep = 2.0f * 3.14159265f / static_cast<float>(segments);
|
||||||
|
|
||||||
|
for (int i = 0; i < segments; ++i) {
|
||||||
|
float angle1 = static_cast<float>(i) * angleStep;
|
||||||
|
float angle2 = static_cast<float>(i + 1) * angleStep;
|
||||||
|
|
||||||
|
addVertex(x + radius * std::cos(angle1), y + radius * std::sin(angle1), color);
|
||||||
|
addVertex(x + radius * std::cos(angle2), y + radius * std::sin(angle2), color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShapeRenderer::drawLine(float x1, float y1, float x2, float y2, const glm::vec4& color, float lineWidth) {
|
||||||
|
if (vertexCount_ + 2 > MAX_VERTICES) {
|
||||||
|
flush(GL_LINES);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lineWidth != currentLineWidth_) {
|
||||||
|
flush(GL_LINES);
|
||||||
|
currentLineWidth_ = lineWidth;
|
||||||
|
glLineWidth(lineWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
addVertex(x1, y1, color);
|
||||||
|
addVertex(x2, y2, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShapeRenderer::fillPolygon(const glm::vec2* points, size_t count, const glm::vec4& color) {
|
||||||
|
if (count < 3) return;
|
||||||
|
|
||||||
|
if (vertexCount_ + (count - 2) * 3 > MAX_VERTICES) {
|
||||||
|
flush(GL_TRIANGLES);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 1; i < count - 1; ++i) {
|
||||||
|
addVertex(points[0].x, points[0].y, color);
|
||||||
|
addVertex(points[i].x, points[i].y, color);
|
||||||
|
addVertex(points[i + 1].x, points[i + 1].y, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShapeRenderer::drawPolygon(const glm::vec2* points, size_t count, const glm::vec4& color, float lineWidth) {
|
||||||
|
if (count < 2) return;
|
||||||
|
|
||||||
|
if (vertexCount_ + count * 2 > MAX_VERTICES) {
|
||||||
|
flush(GL_LINES);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lineWidth != currentLineWidth_) {
|
||||||
|
flush(GL_LINES);
|
||||||
|
currentLineWidth_ = lineWidth;
|
||||||
|
glLineWidth(lineWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < count; ++i) {
|
||||||
|
size_t next = (i + 1) % count;
|
||||||
|
addVertex(points[i].x, points[i].y, color);
|
||||||
|
addVertex(points[next].x, points[next].y, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShapeRenderer::fillTriangle(float x1, float y1, float x2, float y2, float x3, float y3, const glm::vec4& color) {
|
||||||
|
if (vertexCount_ + 3 > MAX_VERTICES) {
|
||||||
|
flush(GL_TRIANGLES);
|
||||||
|
}
|
||||||
|
|
||||||
|
addVertex(x1, y1, color);
|
||||||
|
addVertex(x2, y2, color);
|
||||||
|
addVertex(x3, y3, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShapeRenderer::end() {
|
||||||
|
if (vertexCount_ > 0) {
|
||||||
|
flush(GL_TRIANGLES);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ShapeRenderer::createShaders() {
|
||||||
|
GLuint vs = glCreateShader(GL_VERTEX_SHADER);
|
||||||
|
glShaderSource(vs, 1, &SHAPE_VERTEX_SHADER, nullptr);
|
||||||
|
glCompileShader(vs);
|
||||||
|
|
||||||
|
GLint success;
|
||||||
|
glGetShaderiv(vs, GL_COMPILE_STATUS, &success);
|
||||||
|
if (!success) {
|
||||||
|
glDeleteShader(vs);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
GLuint fs = glCreateShader(GL_FRAGMENT_SHADER);
|
||||||
|
glShaderSource(fs, 1, &SHAPE_FRAGMENT_SHADER, nullptr);
|
||||||
|
glCompileShader(fs);
|
||||||
|
|
||||||
|
glGetShaderiv(fs, GL_COMPILE_STATUS, &success);
|
||||||
|
if (!success) {
|
||||||
|
glDeleteShader(vs);
|
||||||
|
glDeleteShader(fs);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fillShader_ = glCreateProgram();
|
||||||
|
glAttachShader(fillShader_, vs);
|
||||||
|
glAttachShader(fillShader_, fs);
|
||||||
|
glLinkProgram(fillShader_);
|
||||||
|
|
||||||
|
glGetProgramiv(fillShader_, GL_LINK_STATUS, &success);
|
||||||
|
|
||||||
|
lineShader_ = fillShader_;
|
||||||
|
|
||||||
|
glDeleteShader(vs);
|
||||||
|
glDeleteShader(fs);
|
||||||
|
|
||||||
|
return success == GL_TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShapeRenderer::flush(GLenum mode) {
|
||||||
|
if (vertexCount_ == 0) return;
|
||||||
|
|
||||||
|
vbo_->update(0, vertexCount_ * sizeof(ShapeVertex), vertices_.data());
|
||||||
|
|
||||||
|
glUseProgram(fillShader_);
|
||||||
|
GLint vpLoc = glGetUniformLocation(fillShader_, "u_viewProjection");
|
||||||
|
glUniformMatrix4fv(vpLoc, 1, GL_FALSE, &viewProjection_[0][0]);
|
||||||
|
|
||||||
|
vao_->bind();
|
||||||
|
glDrawArrays(mode, 0, static_cast<GLsizei>(vertexCount_));
|
||||||
|
|
||||||
|
E2D_RENDER_STATS().addDrawCall(static_cast<uint32>(vertexCount_),
|
||||||
|
static_cast<uint32>(vertexCount_ / 3));
|
||||||
|
|
||||||
|
drawCalls_++;
|
||||||
|
vertexCount_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShapeRenderer::addVertex(float x, float y, const glm::vec4& color) {
|
||||||
|
ShapeVertex& v = vertices_[vertexCount_++];
|
||||||
|
v.x = x;
|
||||||
|
v.y = y;
|
||||||
|
v.r = color.r;
|
||||||
|
v.g = color.g;
|
||||||
|
v.b = color.b;
|
||||||
|
v.a = color.a;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,218 @@
|
||||||
|
#include <extra2d/render/sprite_renderer.h>
|
||||||
|
#include <extra2d/render/render_device.h>
|
||||||
|
#include <extra2d/render/render_stats.h>
|
||||||
|
#include <cmath>
|
||||||
|
#include <glm/gtc/matrix_transform.hpp>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
static const char* SPRITE_VERTEX_SHADER = R"(
|
||||||
|
#version 450 core
|
||||||
|
layout(location = 0) in vec2 a_position;
|
||||||
|
layout(location = 1) in vec2 a_texCoord;
|
||||||
|
layout(location = 2) in vec4 a_color;
|
||||||
|
|
||||||
|
uniform mat4 u_viewProjection;
|
||||||
|
|
||||||
|
out vec2 v_texCoord;
|
||||||
|
out vec4 v_color;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
gl_Position = u_viewProjection * vec4(a_position, 0.0, 1.0);
|
||||||
|
v_texCoord = a_texCoord;
|
||||||
|
v_color = a_color;
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
|
||||||
|
static const char* SPRITE_FRAGMENT_SHADER = R"(
|
||||||
|
#version 450 core
|
||||||
|
in vec2 v_texCoord;
|
||||||
|
in vec4 v_color;
|
||||||
|
|
||||||
|
uniform sampler2D u_texture;
|
||||||
|
|
||||||
|
out vec4 fragColor;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
fragColor = texture(u_texture, v_texCoord) * v_color;
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
|
||||||
|
SpriteRenderer::SpriteRenderer() {
|
||||||
|
vertices_.resize(MAX_VERTICES);
|
||||||
|
}
|
||||||
|
|
||||||
|
SpriteRenderer::~SpriteRenderer() {
|
||||||
|
shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SpriteRenderer::init() {
|
||||||
|
auto& device = RenderDevice::instance();
|
||||||
|
|
||||||
|
vao_ = device.createVAO();
|
||||||
|
if (!vao_) return false;
|
||||||
|
|
||||||
|
vbo_ = device.createVertexBuffer(BufferUsage::Dynamic,
|
||||||
|
MAX_VERTICES * sizeof(SpriteVertex), nullptr);
|
||||||
|
if (!vbo_) return false;
|
||||||
|
|
||||||
|
Array<uint16> indices(MAX_INDICES);
|
||||||
|
for (size_t i = 0; i < MAX_SPRITES; ++i) {
|
||||||
|
size_t base = i * VERTICES_PER_SPRITE;
|
||||||
|
indices[i * INDICES_PER_SPRITE + 0] = static_cast<uint16>(base + 0);
|
||||||
|
indices[i * INDICES_PER_SPRITE + 1] = static_cast<uint16>(base + 1);
|
||||||
|
indices[i * INDICES_PER_SPRITE + 2] = static_cast<uint16>(base + 2);
|
||||||
|
indices[i * INDICES_PER_SPRITE + 3] = static_cast<uint16>(base + 0);
|
||||||
|
indices[i * INDICES_PER_SPRITE + 4] = static_cast<uint16>(base + 2);
|
||||||
|
indices[i * INDICES_PER_SPRITE + 5] = static_cast<uint16>(base + 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
ibo_ = device.createIndexBuffer(BufferUsage::Static,
|
||||||
|
indices.size() * sizeof(uint16), indices.data());
|
||||||
|
if (!ibo_) return false;
|
||||||
|
|
||||||
|
VertexFormatDesc format;
|
||||||
|
format.stride = sizeof(SpriteVertex);
|
||||||
|
format.addAttribute(0, VertexAttribType::Float2, offsetof(SpriteVertex, x));
|
||||||
|
format.addAttribute(1, VertexAttribType::Float2, offsetof(SpriteVertex, u));
|
||||||
|
format.addAttribute(2, VertexAttribType::Float4, offsetof(SpriteVertex, r));
|
||||||
|
|
||||||
|
vao_->setFormat(format);
|
||||||
|
vao_->bindVertexBuffer(0, *vbo_, 0, sizeof(SpriteVertex));
|
||||||
|
vao_->bindIndexBuffer(*ibo_);
|
||||||
|
|
||||||
|
if (!createShader()) return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpriteRenderer::shutdown() {
|
||||||
|
if (shader_) {
|
||||||
|
glDeleteProgram(shader_);
|
||||||
|
shader_ = 0;
|
||||||
|
}
|
||||||
|
vao_.reset();
|
||||||
|
vbo_.reset();
|
||||||
|
ibo_.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpriteRenderer::begin(const glm::mat4& viewProjection) {
|
||||||
|
viewProjection_ = viewProjection;
|
||||||
|
vertexCount_ = 0;
|
||||||
|
currentTexture_ = 0;
|
||||||
|
drawCalls_ = 0;
|
||||||
|
spriteCount_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpriteRenderer::draw(GLuint texture, const SpriteData& data) {
|
||||||
|
if (texture != currentTexture_ || vertexCount_ + VERTICES_PER_SPRITE > MAX_VERTICES) {
|
||||||
|
flush();
|
||||||
|
currentTexture_ = texture;
|
||||||
|
}
|
||||||
|
|
||||||
|
addQuad(data, texture);
|
||||||
|
spriteCount_++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpriteRenderer::end() {
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SpriteRenderer::createShader() {
|
||||||
|
GLuint vs = glCreateShader(GL_VERTEX_SHADER);
|
||||||
|
glShaderSource(vs, 1, &SPRITE_VERTEX_SHADER, nullptr);
|
||||||
|
glCompileShader(vs);
|
||||||
|
|
||||||
|
GLint success;
|
||||||
|
glGetShaderiv(vs, GL_COMPILE_STATUS, &success);
|
||||||
|
if (!success) {
|
||||||
|
glDeleteShader(vs);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
GLuint fs = glCreateShader(GL_FRAGMENT_SHADER);
|
||||||
|
glShaderSource(fs, 1, &SPRITE_FRAGMENT_SHADER, nullptr);
|
||||||
|
glCompileShader(fs);
|
||||||
|
|
||||||
|
glGetShaderiv(fs, GL_COMPILE_STATUS, &success);
|
||||||
|
if (!success) {
|
||||||
|
glDeleteShader(vs);
|
||||||
|
glDeleteShader(fs);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
shader_ = glCreateProgram();
|
||||||
|
glAttachShader(shader_, vs);
|
||||||
|
glAttachShader(shader_, fs);
|
||||||
|
glLinkProgram(shader_);
|
||||||
|
|
||||||
|
glGetProgramiv(shader_, GL_LINK_STATUS, &success);
|
||||||
|
glDeleteShader(vs);
|
||||||
|
glDeleteShader(fs);
|
||||||
|
|
||||||
|
return success == GL_TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpriteRenderer::flush() {
|
||||||
|
if (vertexCount_ == 0) return;
|
||||||
|
|
||||||
|
vbo_->update(0, vertexCount_ * sizeof(SpriteVertex), vertices_.data());
|
||||||
|
|
||||||
|
glUseProgram(shader_);
|
||||||
|
GLint vpLoc = glGetUniformLocation(shader_, "u_viewProjection");
|
||||||
|
glUniformMatrix4fv(vpLoc, 1, GL_FALSE, &viewProjection_[0][0]);
|
||||||
|
|
||||||
|
GLint texLoc = glGetUniformLocation(shader_, "u_texture");
|
||||||
|
glUniform1i(texLoc, 0);
|
||||||
|
|
||||||
|
glBindTextureUnit(0, currentTexture_);
|
||||||
|
|
||||||
|
vao_->bind();
|
||||||
|
glDrawElements(GL_TRIANGLES, static_cast<GLsizei>(vertexCount_ / VERTICES_PER_SPRITE * INDICES_PER_SPRITE),
|
||||||
|
GL_UNSIGNED_SHORT, nullptr);
|
||||||
|
|
||||||
|
E2D_RENDER_STATS().addDrawCall(static_cast<uint32>(vertexCount_),
|
||||||
|
static_cast<uint32>(vertexCount_ / 3));
|
||||||
|
|
||||||
|
drawCalls_++;
|
||||||
|
vertexCount_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpriteRenderer::addQuad(const SpriteData& data, GLuint texture) {
|
||||||
|
float cosR = std::cos(data.rotation);
|
||||||
|
float sinR = std::sin(data.rotation);
|
||||||
|
|
||||||
|
float hw = data.size.x * data.anchor.x;
|
||||||
|
float hh = data.size.y * data.anchor.y;
|
||||||
|
|
||||||
|
float positions[4][2] = {
|
||||||
|
{-hw, -hh},
|
||||||
|
{data.size.x - hw, -hh},
|
||||||
|
{data.size.x - hw, data.size.y - hh},
|
||||||
|
{-hw, data.size.y - hh}
|
||||||
|
};
|
||||||
|
|
||||||
|
float uvs[4][2] = {
|
||||||
|
{data.texRect.x, data.texRect.y},
|
||||||
|
{data.texRect.x + data.texRect.z, data.texRect.y},
|
||||||
|
{data.texRect.x + data.texRect.z, data.texRect.y + data.texRect.w},
|
||||||
|
{data.texRect.x, data.texRect.y + data.texRect.w}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (int i = 0; i < 4; ++i) {
|
||||||
|
float rx = positions[i][0] * cosR - positions[i][1] * sinR;
|
||||||
|
float ry = positions[i][0] * sinR + positions[i][1] * cosR;
|
||||||
|
|
||||||
|
SpriteVertex& v = vertices_[vertexCount_++];
|
||||||
|
v.x = data.position.x + rx;
|
||||||
|
v.y = data.position.y + ry;
|
||||||
|
v.u = uvs[i][0];
|
||||||
|
v.v = uvs[i][1];
|
||||||
|
v.r = data.color.r;
|
||||||
|
v.g = data.color.g;
|
||||||
|
v.b = data.color.b;
|
||||||
|
v.a = data.color.a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,310 @@
|
||||||
|
#include <extra2d/render/render_device.h>
|
||||||
|
#include <extra2d/render/render_stats.h>
|
||||||
|
#include <extra2d/render/text_renderer.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
static const char *TEXT_VERTEX_SHADER = R"(
|
||||||
|
#version 450 core
|
||||||
|
layout(location = 0) in vec2 a_position;
|
||||||
|
layout(location = 1) in vec2 a_texCoord;
|
||||||
|
layout(location = 2) in vec4 a_color;
|
||||||
|
|
||||||
|
uniform mat4 u_viewProjection;
|
||||||
|
|
||||||
|
out vec2 v_texCoord;
|
||||||
|
out vec4 v_color;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
gl_Position = u_viewProjection * vec4(a_position, 0.0, 1.0);
|
||||||
|
v_texCoord = a_texCoord;
|
||||||
|
v_color = a_color;
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
|
||||||
|
static const char *TEXT_FRAGMENT_SHADER = R"(
|
||||||
|
#version 450 core
|
||||||
|
in vec2 v_texCoord;
|
||||||
|
in vec4 v_color;
|
||||||
|
|
||||||
|
uniform sampler2D u_texture;
|
||||||
|
uniform float u_pxRange;
|
||||||
|
|
||||||
|
out vec4 fragColor;
|
||||||
|
|
||||||
|
float median(float r, float g, float b) {
|
||||||
|
return max(min(r, g), min(max(r, g), b));
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec3 msdf = texture(u_texture, v_texCoord).rgb;
|
||||||
|
float sigDist = median(msdf.r, msdf.g, msdf.b);
|
||||||
|
|
||||||
|
float pxRange = u_pxRange;
|
||||||
|
vec2 texSize = vec2(textureSize(u_texture, 0));
|
||||||
|
vec2 unitRange = vec2(pxRange) / texSize;
|
||||||
|
vec2 screenTexSize = vec2(1.0) / fwidth(v_texCoord);
|
||||||
|
float screenPxRange = max(0.5 * dot(unitRange, screenTexSize), 1.0);
|
||||||
|
|
||||||
|
float alpha = smoothstep(0.5 - 0.5/screenPxRange, 0.5 + 0.5/screenPxRange, sigDist);
|
||||||
|
|
||||||
|
fragColor = vec4(v_color.rgb, v_color.a * alpha);
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
|
||||||
|
FontAtlas::FontAtlas() = default;
|
||||||
|
|
||||||
|
FontAtlas::~FontAtlas() { destroy(); }
|
||||||
|
|
||||||
|
bool FontAtlas::create(int atlasWidth, int atlasHeight) {
|
||||||
|
width_ = atlasWidth;
|
||||||
|
height_ = atlasHeight;
|
||||||
|
|
||||||
|
glCreateTextures(GL_TEXTURE_2D, 1, &texture_);
|
||||||
|
if (texture_ == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
glTextureStorage2D(texture_, 1, GL_RGBA8, width_, height_);
|
||||||
|
glTextureParameteri(texture_, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||||
|
glTextureParameteri(texture_, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||||
|
glTextureParameteri(texture_, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||||
|
glTextureParameteri(texture_, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FontAtlas::destroy() {
|
||||||
|
if (texture_ != 0) {
|
||||||
|
glDeleteTextures(1, &texture_);
|
||||||
|
texture_ = 0;
|
||||||
|
}
|
||||||
|
glyphs_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FontAtlas::addGlyph(char32_t codepoint, const GlyphInfo &info) {
|
||||||
|
glyphs_[codepoint] = info;
|
||||||
|
}
|
||||||
|
|
||||||
|
const GlyphInfo *FontAtlas::getGlyph(char32_t codepoint) const {
|
||||||
|
auto it = glyphs_.find(codepoint);
|
||||||
|
return it != glyphs_.end() ? &it->second : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FontAtlas::hasGlyph(char32_t codepoint) const {
|
||||||
|
return glyphs_.find(codepoint) != glyphs_.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
TextRenderer::TextRenderer() { vertices_.resize(MAX_VERTICES); }
|
||||||
|
|
||||||
|
TextRenderer::~TextRenderer() { shutdown(); }
|
||||||
|
|
||||||
|
bool TextRenderer::init() {
|
||||||
|
auto &device = RenderDevice::instance();
|
||||||
|
|
||||||
|
vao_ = device.createVAO();
|
||||||
|
if (!vao_)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
vbo_ = device.createVertexBuffer(BufferUsage::Dynamic,
|
||||||
|
MAX_VERTICES * sizeof(TextVertex), nullptr);
|
||||||
|
if (!vbo_)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
VertexFormatDesc format;
|
||||||
|
format.stride = sizeof(TextVertex);
|
||||||
|
format.addAttribute(0, VertexAttribType::Float2, offsetof(TextVertex, x));
|
||||||
|
format.addAttribute(1, VertexAttribType::Float2, offsetof(TextVertex, u));
|
||||||
|
format.addAttribute(2, VertexAttribType::Float4, offsetof(TextVertex, r));
|
||||||
|
|
||||||
|
vao_->setFormat(format);
|
||||||
|
vao_->bindVertexBuffer(0, *vbo_, 0, sizeof(TextVertex));
|
||||||
|
|
||||||
|
if (!createShader())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextRenderer::shutdown() {
|
||||||
|
if (shader_) {
|
||||||
|
glDeleteProgram(shader_);
|
||||||
|
shader_ = 0;
|
||||||
|
}
|
||||||
|
vao_.reset();
|
||||||
|
vbo_.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextRenderer::begin(const glm::mat4 &viewProjection) {
|
||||||
|
viewProjection_ = viewProjection;
|
||||||
|
vertexCount_ = 0;
|
||||||
|
currentFont_ = nullptr;
|
||||||
|
drawCalls_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextRenderer::drawText(FontAtlas *font, const String32 &text, float x,
|
||||||
|
float y, const TextConfig &config) {
|
||||||
|
if (!font || !font->isValid() || text.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (font != currentFont_ ||
|
||||||
|
vertexCount_ + text.size() * VERTICES_PER_CHAR > MAX_VERTICES) {
|
||||||
|
flush();
|
||||||
|
currentFont_ = font;
|
||||||
|
}
|
||||||
|
|
||||||
|
float scale = config.fontSize / 48.0f;
|
||||||
|
float cursorX = x;
|
||||||
|
float cursorY = y;
|
||||||
|
|
||||||
|
for (char32_t c : text) {
|
||||||
|
if (c == '\n') {
|
||||||
|
cursorX = x;
|
||||||
|
cursorY += config.fontSize * 1.2f;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const GlyphInfo *glyph = font->getGlyph(c);
|
||||||
|
if (!glyph)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
addChar(*glyph, cursorX, cursorY, scale, config.color);
|
||||||
|
|
||||||
|
cursorX += glyph->advance * scale;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
glm::vec2 TextRenderer::measureText(FontAtlas *font, const String32 &text,
|
||||||
|
float fontSize) {
|
||||||
|
if (!font || text.empty())
|
||||||
|
return glm::vec2(0.0f);
|
||||||
|
|
||||||
|
float scale = fontSize / 48.0f;
|
||||||
|
float width = 0.0f;
|
||||||
|
float maxWidth = 0.0f;
|
||||||
|
float height = fontSize;
|
||||||
|
float lineHeight = fontSize * 1.2f;
|
||||||
|
|
||||||
|
for (char32_t c : text) {
|
||||||
|
if (c == '\n') {
|
||||||
|
maxWidth = std::max(maxWidth, width);
|
||||||
|
width = 0.0f;
|
||||||
|
height += lineHeight;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const GlyphInfo *glyph = font->getGlyph(c);
|
||||||
|
if (glyph) {
|
||||||
|
width += glyph->advance * scale;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
maxWidth = std::max(maxWidth, width);
|
||||||
|
|
||||||
|
return glm::vec2(maxWidth, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextRenderer::end() { flush(); }
|
||||||
|
|
||||||
|
bool TextRenderer::createShader() {
|
||||||
|
GLuint vs = glCreateShader(GL_VERTEX_SHADER);
|
||||||
|
glShaderSource(vs, 1, &TEXT_VERTEX_SHADER, nullptr);
|
||||||
|
glCompileShader(vs);
|
||||||
|
|
||||||
|
GLint success;
|
||||||
|
glGetShaderiv(vs, GL_COMPILE_STATUS, &success);
|
||||||
|
if (!success) {
|
||||||
|
glDeleteShader(vs);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
GLuint fs = glCreateShader(GL_FRAGMENT_SHADER);
|
||||||
|
glShaderSource(fs, 1, &TEXT_FRAGMENT_SHADER, nullptr);
|
||||||
|
glCompileShader(fs);
|
||||||
|
|
||||||
|
glGetShaderiv(fs, GL_COMPILE_STATUS, &success);
|
||||||
|
if (!success) {
|
||||||
|
glDeleteShader(vs);
|
||||||
|
glDeleteShader(fs);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
shader_ = glCreateProgram();
|
||||||
|
glAttachShader(shader_, vs);
|
||||||
|
glAttachShader(shader_, fs);
|
||||||
|
glLinkProgram(shader_);
|
||||||
|
|
||||||
|
glGetProgramiv(shader_, GL_LINK_STATUS, &success);
|
||||||
|
glDeleteShader(vs);
|
||||||
|
glDeleteShader(fs);
|
||||||
|
|
||||||
|
return success == GL_TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextRenderer::flush() {
|
||||||
|
if (vertexCount_ == 0 || !currentFont_)
|
||||||
|
return;
|
||||||
|
|
||||||
|
vbo_->update(0, vertexCount_ * sizeof(TextVertex), vertices_.data());
|
||||||
|
|
||||||
|
glUseProgram(shader_);
|
||||||
|
|
||||||
|
GLint vpLoc = glGetUniformLocation(shader_, "u_viewProjection");
|
||||||
|
glUniformMatrix4fv(vpLoc, 1, GL_FALSE, &viewProjection_[0][0]);
|
||||||
|
|
||||||
|
GLint texLoc = glGetUniformLocation(shader_, "u_texture");
|
||||||
|
glUniform1i(texLoc, 0);
|
||||||
|
|
||||||
|
GLint pxRangeLoc = glGetUniformLocation(shader_, "u_pxRange");
|
||||||
|
glUniform1f(pxRangeLoc, 4.0f);
|
||||||
|
|
||||||
|
glBindTextureUnit(0, currentFont_->texture());
|
||||||
|
|
||||||
|
vao_->bind();
|
||||||
|
glDrawArrays(GL_TRIANGLE_STRIP, 0, static_cast<GLsizei>(vertexCount_));
|
||||||
|
|
||||||
|
E2D_RENDER_STATS().addDrawCall(static_cast<uint32>(vertexCount_),
|
||||||
|
static_cast<uint32>(vertexCount_ / 3));
|
||||||
|
|
||||||
|
drawCalls_++;
|
||||||
|
vertexCount_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextRenderer::addChar(const GlyphInfo &glyph, float x, float y,
|
||||||
|
float scale, const glm::vec4 &color) {
|
||||||
|
float x0 = x + glyph.bearing.x * scale;
|
||||||
|
float y0 = y + glyph.bearing.y * scale;
|
||||||
|
float x1 = x0 + glyph.size.x * scale;
|
||||||
|
float y1 = y0 + glyph.size.y * scale;
|
||||||
|
|
||||||
|
TextVertex v;
|
||||||
|
v.r = color.r;
|
||||||
|
v.g = color.g;
|
||||||
|
v.b = color.b;
|
||||||
|
v.a = color.a;
|
||||||
|
|
||||||
|
v.x = x0;
|
||||||
|
v.y = y0;
|
||||||
|
v.u = glyph.uvMin.x;
|
||||||
|
v.v = glyph.uvMin.y;
|
||||||
|
vertices_[vertexCount_++] = v;
|
||||||
|
|
||||||
|
v.x = x1;
|
||||||
|
v.y = y0;
|
||||||
|
v.u = glyph.uvMax.x;
|
||||||
|
v.v = glyph.uvMin.y;
|
||||||
|
vertices_[vertexCount_++] = v;
|
||||||
|
|
||||||
|
v.x = x0;
|
||||||
|
v.y = y1;
|
||||||
|
v.u = glyph.uvMin.x;
|
||||||
|
v.v = glyph.uvMax.y;
|
||||||
|
vertices_[vertexCount_++] = v;
|
||||||
|
|
||||||
|
v.x = x1;
|
||||||
|
v.y = y1;
|
||||||
|
v.u = glyph.uvMax.x;
|
||||||
|
v.v = glyph.uvMax.y;
|
||||||
|
vertices_[vertexCount_++] = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,202 @@
|
||||||
|
#include <extra2d/render/vao.h>
|
||||||
|
#include <extra2d/render/render_stats.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
VAO::VAO() = default;
|
||||||
|
|
||||||
|
VAO::~VAO() {
|
||||||
|
destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
VAO::VAO(VAO&& other) noexcept : id_(other.id_) {
|
||||||
|
other.id_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
VAO& VAO::operator=(VAO&& other) noexcept {
|
||||||
|
if (this != &other) {
|
||||||
|
destroy();
|
||||||
|
id_ = other.id_;
|
||||||
|
other.id_ = 0;
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VAO::create() {
|
||||||
|
destroy();
|
||||||
|
|
||||||
|
glCreateVertexArrays(1, &id_);
|
||||||
|
|
||||||
|
return id_ != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VAO::destroy() {
|
||||||
|
if (id_ != 0) {
|
||||||
|
glDeleteVertexArrays(1, &id_);
|
||||||
|
id_ = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VAO::bind() const {
|
||||||
|
if (id_ != 0) {
|
||||||
|
glBindVertexArray(id_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VAO::unbind() {
|
||||||
|
glBindVertexArray(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VAO::setFormat(const VertexFormatDesc& format) {
|
||||||
|
if (id_ == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& attr : format.attributes) {
|
||||||
|
setAttribFormat(attr.location, attr.type, attr.offset, attr.normalized);
|
||||||
|
setAttribBinding(attr.location, attr.location);
|
||||||
|
enableAttrib(attr.location);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VAO::bindVertexBuffer(uint32 binding, const Buffer& buffer,
|
||||||
|
size_t offset, uint32 stride) {
|
||||||
|
if (id_ == 0 || !buffer.isValid()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
glVertexArrayVertexBuffer(id_, binding, buffer.id(),
|
||||||
|
static_cast<GLintptr>(offset),
|
||||||
|
static_cast<GLsizei>(stride));
|
||||||
|
}
|
||||||
|
|
||||||
|
void VAO::bindIndexBuffer(const Buffer& buffer) {
|
||||||
|
if (id_ == 0 || !buffer.isValid()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
glVertexArrayElementBuffer(id_, buffer.id());
|
||||||
|
}
|
||||||
|
|
||||||
|
void VAO::enableAttrib(uint32 location) {
|
||||||
|
if (id_ == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
glEnableVertexArrayAttrib(id_, location);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VAO::disableAttrib(uint32 location) {
|
||||||
|
if (id_ == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
glDisableVertexArrayAttrib(id_, location);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VAO::setAttribFormat(uint32 location, VertexAttribType type,
|
||||||
|
uint32 offset, bool normalized) {
|
||||||
|
if (id_ == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GLenum glType = glAttribType(type);
|
||||||
|
int components = glAttribComponents(type);
|
||||||
|
bool isInteger = (type == VertexAttribType::Int ||
|
||||||
|
type == VertexAttribType::Int2 ||
|
||||||
|
type == VertexAttribType::Int3 ||
|
||||||
|
type == VertexAttribType::Int4 ||
|
||||||
|
type == VertexAttribType::UInt ||
|
||||||
|
type == VertexAttribType::UInt2 ||
|
||||||
|
type == VertexAttribType::UInt3 ||
|
||||||
|
type == VertexAttribType::UInt4);
|
||||||
|
|
||||||
|
if (isInteger) {
|
||||||
|
glVertexArrayAttribIFormat(id_, location, components, glType, offset);
|
||||||
|
} else {
|
||||||
|
glVertexArrayAttribFormat(id_, location, components, glType,
|
||||||
|
normalized ? GL_TRUE : GL_FALSE, offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VAO::setAttribBinding(uint32 location, uint32 binding) {
|
||||||
|
if (id_ == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
glVertexArrayAttribBinding(id_, location, binding);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VAO::setBindingDivisor(uint32 binding, uint32 divisor) {
|
||||||
|
if (id_ == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
glVertexArrayBindingDivisor(id_, binding, divisor);
|
||||||
|
}
|
||||||
|
|
||||||
|
GLenum VAO::glAttribType(VertexAttribType type) {
|
||||||
|
switch (type) {
|
||||||
|
case VertexAttribType::Float:
|
||||||
|
case VertexAttribType::Float2:
|
||||||
|
case VertexAttribType::Float3:
|
||||||
|
case VertexAttribType::Float4:
|
||||||
|
return GL_FLOAT;
|
||||||
|
case VertexAttribType::Int:
|
||||||
|
case VertexAttribType::Int2:
|
||||||
|
case VertexAttribType::Int3:
|
||||||
|
case VertexAttribType::Int4:
|
||||||
|
return GL_INT;
|
||||||
|
case VertexAttribType::UInt:
|
||||||
|
case VertexAttribType::UInt2:
|
||||||
|
case VertexAttribType::UInt3:
|
||||||
|
case VertexAttribType::UInt4:
|
||||||
|
return GL_UNSIGNED_INT;
|
||||||
|
case VertexAttribType::Byte4:
|
||||||
|
case VertexAttribType::Byte4Norm:
|
||||||
|
return GL_BYTE;
|
||||||
|
case VertexAttribType::UByte4:
|
||||||
|
case VertexAttribType::UByte4Norm:
|
||||||
|
return GL_UNSIGNED_BYTE;
|
||||||
|
case VertexAttribType::Short2:
|
||||||
|
case VertexAttribType::Short2Norm:
|
||||||
|
case VertexAttribType::Short4:
|
||||||
|
case VertexAttribType::Short4Norm:
|
||||||
|
return GL_SHORT;
|
||||||
|
default:
|
||||||
|
return GL_FLOAT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int VAO::glAttribComponents(VertexAttribType type) {
|
||||||
|
switch (type) {
|
||||||
|
case VertexAttribType::Float:
|
||||||
|
case VertexAttribType::Int:
|
||||||
|
case VertexAttribType::UInt:
|
||||||
|
return 1;
|
||||||
|
case VertexAttribType::Float2:
|
||||||
|
case VertexAttribType::Int2:
|
||||||
|
case VertexAttribType::UInt2:
|
||||||
|
case VertexAttribType::Short2:
|
||||||
|
case VertexAttribType::Short2Norm:
|
||||||
|
return 2;
|
||||||
|
case VertexAttribType::Float3:
|
||||||
|
case VertexAttribType::Int3:
|
||||||
|
case VertexAttribType::UInt3:
|
||||||
|
return 3;
|
||||||
|
case VertexAttribType::Float4:
|
||||||
|
case VertexAttribType::Int4:
|
||||||
|
case VertexAttribType::UInt4:
|
||||||
|
case VertexAttribType::Byte4:
|
||||||
|
case VertexAttribType::Byte4Norm:
|
||||||
|
case VertexAttribType::UByte4:
|
||||||
|
case VertexAttribType::UByte4Norm:
|
||||||
|
case VertexAttribType::Short4:
|
||||||
|
case VertexAttribType::Short4Norm:
|
||||||
|
return 4;
|
||||||
|
default:
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,7 +1,5 @@
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <extra2d/action/action.h>
|
|
||||||
#include <extra2d/action/action_manager.h>
|
|
||||||
#include <extra2d/graphics/render_command.h>
|
#include <extra2d/graphics/render_command.h>
|
||||||
#include <extra2d/scene/node.h>
|
#include <extra2d/scene/node.h>
|
||||||
#include <extra2d/scene/scene.h>
|
#include <extra2d/scene/scene.h>
|
||||||
|
|
@ -11,11 +9,7 @@ namespace extra2d {
|
||||||
|
|
||||||
Node::Node() = default;
|
Node::Node() = default;
|
||||||
|
|
||||||
Node::~Node() {
|
Node::~Node() { removeAllChildren(); }
|
||||||
removeAllChildren();
|
|
||||||
stopAllActions();
|
|
||||||
ActionManager::getInstance()->removeAllActionsFromTarget(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Node::addChild(Ptr<Node> child) {
|
void Node::addChild(Ptr<Node> child) {
|
||||||
if (!child || child.get() == this) {
|
if (!child || child.get() == this) {
|
||||||
|
|
@ -346,7 +340,7 @@ void Node::onUpdate(float dt) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Node::onRender(RenderBackend &renderer) {
|
void Node::onRender(Renderer &renderer) {
|
||||||
if (!visible_)
|
if (!visible_)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
@ -403,49 +397,9 @@ void Node::updateSpatialIndex() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 动作系统 - 新接口
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
Action* Node::runAction(Action* action) {
|
|
||||||
if (!action) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
ActionManager::getInstance()->addAction(action, this);
|
|
||||||
return action;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Node::stopAllActions() {
|
|
||||||
ActionManager::getInstance()->removeAllActionsFromTarget(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Node::stopAction(Action* action) {
|
|
||||||
ActionManager::getInstance()->removeAction(action);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Node::stopActionByTag(int tag) {
|
|
||||||
ActionManager::getInstance()->removeActionByTag(tag, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Node::stopActionsByFlags(unsigned int flags) {
|
|
||||||
ActionManager::getInstance()->removeActionsByFlags(flags, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
Action* Node::getActionByTag(int tag) {
|
|
||||||
return ActionManager::getInstance()->getActionByTag(tag, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t Node::getActionCount() const {
|
|
||||||
return ActionManager::getInstance()->getActionCount(const_cast<Node*>(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Node::isRunningActions() const {
|
|
||||||
return getActionCount() > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Node::update(float dt) { onUpdate(dt); }
|
void Node::update(float dt) { onUpdate(dt); }
|
||||||
|
|
||||||
void Node::render(RenderBackend &renderer) {
|
void Node::render(Renderer &renderer) {
|
||||||
if (childrenOrderDirty_) {
|
if (childrenOrderDirty_) {
|
||||||
sortChildren();
|
sortChildren();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
#include <extra2d/graphics/render_backend.h>
|
|
||||||
#include <extra2d/graphics/render_command.h>
|
#include <extra2d/graphics/render_command.h>
|
||||||
|
#include <extra2d/graphics/renderer.h>
|
||||||
#include <extra2d/scene/scene.h>
|
#include <extra2d/scene/scene.h>
|
||||||
#include <extra2d/utils/logger.h>
|
#include <extra2d/utils/logger.h>
|
||||||
|
|
||||||
|
|
@ -22,7 +22,7 @@ void Scene::setViewportSize(const Size &size) {
|
||||||
setViewportSize(size.width, size.height);
|
setViewportSize(size.width, size.height);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Scene::renderScene(RenderBackend &renderer) {
|
void Scene::renderScene(Renderer &renderer) {
|
||||||
if (!isVisible())
|
if (!isVisible())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
@ -32,7 +32,7 @@ void Scene::renderScene(RenderBackend &renderer) {
|
||||||
renderer.endFrame();
|
renderer.endFrame();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Scene::renderContent(RenderBackend &renderer) {
|
void Scene::renderContent(Renderer &renderer) {
|
||||||
if (!isVisible())
|
if (!isVisible())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
@ -44,9 +44,7 @@ void Scene::renderContent(RenderBackend &renderer) {
|
||||||
renderer.setViewProjection(activeCam->getViewProjectionMatrix());
|
renderer.setViewProjection(activeCam->getViewProjectionMatrix());
|
||||||
}
|
}
|
||||||
|
|
||||||
renderer.beginSpriteBatch();
|
|
||||||
render(renderer);
|
render(renderer);
|
||||||
renderer.endSpriteBatch();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Scene::updateScene(float dt) {
|
void Scene::updateScene(float dt) {
|
||||||
|
|
|
||||||
|
|
@ -1,59 +1,11 @@
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <extra2d/app/application.h>
|
|
||||||
#include <extra2d/graphics/render_backend.h>
|
|
||||||
#include <extra2d/graphics/render_command.h>
|
#include <extra2d/graphics/render_command.h>
|
||||||
#include <extra2d/platform/input.h>
|
#include <extra2d/graphics/renderer.h>
|
||||||
#include <extra2d/scene/scene_manager.h>
|
#include <extra2d/scene/scene_manager.h>
|
||||||
#include <extra2d/scene/transition_box_scene.h>
|
|
||||||
#include <extra2d/scene/transition_fade_scene.h>
|
|
||||||
#include <extra2d/scene/transition_flip_scene.h>
|
|
||||||
#include <extra2d/scene/transition_scale_scene.h>
|
|
||||||
#include <extra2d/scene/transition_scene.h>
|
|
||||||
#include <extra2d/scene/transition_slide_scene.h>
|
|
||||||
#include <extra2d/utils/logger.h>
|
#include <extra2d/utils/logger.h>
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
Node *hitTestTopmost(const Ptr<Node> &node, const Vec2 &worldPos) {
|
|
||||||
if (!node || !node->isVisible()) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<Ptr<Node>> children = node->getChildren();
|
|
||||||
std::stable_sort(children.begin(), children.end(),
|
|
||||||
[](const Ptr<Node> &a, const Ptr<Node> &b) {
|
|
||||||
return a->getZOrder() < b->getZOrder();
|
|
||||||
});
|
|
||||||
|
|
||||||
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->getBoundingBox();
|
|
||||||
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() {
|
SceneManager &SceneManager::getInstance() {
|
||||||
static SceneManager instance;
|
static SceneManager instance;
|
||||||
return instance;
|
return instance;
|
||||||
|
|
@ -75,7 +27,7 @@ void SceneManager::runWithScene(Ptr<Scene> scene) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void SceneManager::replaceScene(Ptr<Scene> scene) {
|
void SceneManager::replaceScene(Ptr<Scene> scene) {
|
||||||
if (!scene || isTransitioning_) {
|
if (!scene) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -97,7 +49,7 @@ void SceneManager::replaceScene(Ptr<Scene> scene) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void SceneManager::enterScene(Ptr<Scene> scene) {
|
void SceneManager::enterScene(Ptr<Scene> scene) {
|
||||||
if (!scene || isTransitioning_) {
|
if (!scene) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -108,115 +60,10 @@ void SceneManager::enterScene(Ptr<Scene> scene) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SceneManager::enterScene(Ptr<Scene> scene,
|
|
||||||
Ptr<TransitionScene> transitionScene) {
|
|
||||||
if (!scene || isTransitioning_) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果没有过渡场景,使用无过渡切换
|
|
||||||
if (!transitionScene) {
|
|
||||||
enterScene(scene);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto current = getCurrentScene();
|
|
||||||
if (!current) {
|
|
||||||
enterScene(scene);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 在过渡开始前,发送 UIHoverExit 给当前悬停的节点,重置按钮状态
|
|
||||||
if (hoverTarget_) {
|
|
||||||
Event evt;
|
|
||||||
evt.type = EventType::UIHoverExit;
|
|
||||||
evt.data = CustomEvent{0, hoverTarget_};
|
|
||||||
dispatchToNode(hoverTarget_, evt);
|
|
||||||
hoverTarget_ = nullptr;
|
|
||||||
}
|
|
||||||
captureTarget_ = nullptr;
|
|
||||||
hasLastPointerWorld_ = false;
|
|
||||||
|
|
||||||
// 设置过渡场景
|
|
||||||
transitionScene->setOutScene(current);
|
|
||||||
transitionScene->setFinishCallback([this]() { finishTransition(); });
|
|
||||||
|
|
||||||
// 暂停当前场景
|
|
||||||
current->pause();
|
|
||||||
|
|
||||||
// 推入过渡场景(作为中介场景)
|
|
||||||
transitionScene->onEnter();
|
|
||||||
transitionScene->onAttachToScene(transitionScene.get());
|
|
||||||
sceneStack_.push(transitionScene);
|
|
||||||
|
|
||||||
isTransitioning_ = true;
|
|
||||||
activeTransitionScene_ = transitionScene;
|
|
||||||
transitionStackAction_ = [this, transitionScene]() {
|
|
||||||
// 退出旧场景
|
|
||||||
auto outScene = transitionScene->getOutScene();
|
|
||||||
if (!sceneStack_.empty() && outScene) {
|
|
||||||
// 过渡场景已经在栈顶,弹出它
|
|
||||||
if (sceneStack_.top().get() == transitionScene.get()) {
|
|
||||||
sceneStack_.pop();
|
|
||||||
}
|
|
||||||
outScene->onExit();
|
|
||||||
outScene->onDetachFromScene();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 推入新场景
|
|
||||||
auto inScene = transitionScene->getInScene();
|
|
||||||
if (inScene) {
|
|
||||||
inScene->onAttachToScene(inScene.get());
|
|
||||||
sceneStack_.push(inScene);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
void SceneManager::replaceScene(Ptr<Scene> scene, TransitionType transition,
|
|
||||||
float duration) {
|
|
||||||
if (!scene || isTransitioning_) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sceneStack_.empty()) {
|
|
||||||
runWithScene(scene);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto oldScene = sceneStack_.top();
|
|
||||||
|
|
||||||
startTransition(oldScene, scene, transition, duration, [this]() {
|
|
||||||
// 过渡完成后,退出旧场景并从堆栈中移除
|
|
||||||
if (!sceneStack_.empty() && activeTransitionScene_) {
|
|
||||||
// 弹出过渡场景
|
|
||||||
if (sceneStack_.top().get() == activeTransitionScene_.get()) {
|
|
||||||
sceneStack_.pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 退出旧场景
|
|
||||||
auto outScene = activeTransitionScene_->getOutScene();
|
|
||||||
if (outScene) {
|
|
||||||
outScene->onExit();
|
|
||||||
outScene->onDetachFromScene();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 将新场景推入堆栈
|
|
||||||
if (activeTransitionScene_) {
|
|
||||||
auto inScene = activeTransitionScene_->getInScene();
|
|
||||||
if (inScene) {
|
|
||||||
inScene->onAttachToScene(inScene.get());
|
|
||||||
sceneStack_.push(inScene);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void SceneManager::pushScene(Ptr<Scene> scene) {
|
void SceneManager::pushScene(Ptr<Scene> scene) {
|
||||||
if (!scene || isTransitioning_) {
|
if (!scene) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pause current scene
|
// Pause current scene
|
||||||
if (!sceneStack_.empty()) {
|
if (!sceneStack_.empty()) {
|
||||||
sceneStack_.top()->pause();
|
sceneStack_.top()->pause();
|
||||||
|
|
@ -228,43 +75,8 @@ void SceneManager::pushScene(Ptr<Scene> scene) {
|
||||||
sceneStack_.push(scene);
|
sceneStack_.push(scene);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SceneManager::pushScene(Ptr<Scene> scene, TransitionType transition,
|
|
||||||
float duration) {
|
|
||||||
if (!scene || isTransitioning_) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sceneStack_.empty()) {
|
|
||||||
runWithScene(scene);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 暂停当前场景
|
|
||||||
sceneStack_.top()->pause();
|
|
||||||
|
|
||||||
auto currentScene = sceneStack_.top();
|
|
||||||
|
|
||||||
startTransition(currentScene, scene, transition, duration, [this]() {
|
|
||||||
// 过渡完成后,将新场景推入堆栈
|
|
||||||
if (!sceneStack_.empty() && activeTransitionScene_) {
|
|
||||||
// 弹出过渡场景
|
|
||||||
if (sceneStack_.top().get() == activeTransitionScene_.get()) {
|
|
||||||
sceneStack_.pop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (activeTransitionScene_) {
|
|
||||||
auto inScene = activeTransitionScene_->getInScene();
|
|
||||||
if (inScene) {
|
|
||||||
inScene->onAttachToScene(inScene.get());
|
|
||||||
sceneStack_.push(inScene);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void SceneManager::popScene() {
|
void SceneManager::popScene() {
|
||||||
if (sceneStack_.size() <= 1 || isTransitioning_) {
|
if (sceneStack_.size() <= 1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -279,42 +91,8 @@ void SceneManager::popScene() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SceneManager::popScene(TransitionType transition, float duration) {
|
|
||||||
if (sceneStack_.size() <= 1 || isTransitioning_) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto current = sceneStack_.top();
|
|
||||||
auto previous = getPreviousScene();
|
|
||||||
|
|
||||||
startTransition(current, previous, transition, duration, [this]() {
|
|
||||||
// 过渡完成后,退出当前场景并从堆栈中移除
|
|
||||||
if (!sceneStack_.empty() && activeTransitionScene_) {
|
|
||||||
// 弹出过渡场景
|
|
||||||
if (sceneStack_.top().get() == activeTransitionScene_.get()) {
|
|
||||||
sceneStack_.pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 退出当前场景
|
|
||||||
auto outScene = activeTransitionScene_->getOutScene();
|
|
||||||
if (outScene) {
|
|
||||||
outScene->onExit();
|
|
||||||
outScene->onDetachFromScene();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 恢复前一个场景
|
|
||||||
if (activeTransitionScene_) {
|
|
||||||
auto inScene = activeTransitionScene_->getInScene();
|
|
||||||
if (inScene && !sceneStack_.empty() && sceneStack_.top() == inScene) {
|
|
||||||
inScene->resume();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void SceneManager::popToRootScene() {
|
void SceneManager::popToRootScene() {
|
||||||
if (sceneStack_.size() <= 1 || isTransitioning_) {
|
if (sceneStack_.size() <= 1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -330,35 +108,7 @@ void SceneManager::popToRootScene() {
|
||||||
sceneStack_.top()->resume();
|
sceneStack_.top()->resume();
|
||||||
}
|
}
|
||||||
|
|
||||||
void SceneManager::popToRootScene(TransitionType transition, float duration) {
|
|
||||||
if (sceneStack_.size() <= 1 || isTransitioning_) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto root = getRootScene();
|
|
||||||
auto current = sceneStack_.top();
|
|
||||||
|
|
||||||
startTransition(current, root, transition, duration, [this, root]() {
|
|
||||||
// 退出所有场景直到根场景
|
|
||||||
while (!sceneStack_.empty() && sceneStack_.top().get() != root.get()) {
|
|
||||||
auto scene = sceneStack_.top();
|
|
||||||
scene->onExit();
|
|
||||||
scene->onDetachFromScene();
|
|
||||||
sceneStack_.pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 恢复根场景
|
|
||||||
if (!sceneStack_.empty() && sceneStack_.top().get() == root.get()) {
|
|
||||||
root->resume();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void SceneManager::popToScene(const std::string &name) {
|
void SceneManager::popToScene(const std::string &name) {
|
||||||
if (isTransitioning_) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find target scene in stack
|
// Find target scene in stack
|
||||||
std::stack<Ptr<Scene>> tempStack;
|
std::stack<Ptr<Scene>> tempStack;
|
||||||
Ptr<Scene> target = nullptr;
|
Ptr<Scene> target = nullptr;
|
||||||
|
|
@ -379,33 +129,6 @@ void SceneManager::popToScene(const std::string &name) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SceneManager::popToScene(const std::string &name,
|
|
||||||
TransitionType transition, float duration) {
|
|
||||||
if (isTransitioning_) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto target = getSceneByName(name);
|
|
||||||
if (target && target != sceneStack_.top()) {
|
|
||||||
auto current = sceneStack_.top();
|
|
||||||
|
|
||||||
startTransition(current, target, transition, duration, [this, target]() {
|
|
||||||
// 退出所有场景直到目标场景
|
|
||||||
while (!sceneStack_.empty() && sceneStack_.top() != target) {
|
|
||||||
auto scene = sceneStack_.top();
|
|
||||||
scene->onExit();
|
|
||||||
scene->onDetachFromScene();
|
|
||||||
sceneStack_.pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 恢复目标场景
|
|
||||||
if (!sceneStack_.empty() && sceneStack_.top() == target) {
|
|
||||||
target->resume();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ptr<Scene> SceneManager::getCurrentScene() const {
|
Ptr<Scene> SceneManager::getCurrentScene() const {
|
||||||
if (sceneStack_.empty()) {
|
if (sceneStack_.empty()) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
@ -463,23 +186,12 @@ bool SceneManager::hasScene(const std::string &name) const {
|
||||||
}
|
}
|
||||||
|
|
||||||
void SceneManager::update(float dt) {
|
void SceneManager::update(float dt) {
|
||||||
if (isTransitioning_) {
|
|
||||||
// 过渡场景在栈顶,正常更新即可
|
|
||||||
hoverTarget_ = nullptr;
|
|
||||||
captureTarget_ = nullptr;
|
|
||||||
hasLastPointerWorld_ = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!sceneStack_.empty()) {
|
if (!sceneStack_.empty()) {
|
||||||
auto &scene = *sceneStack_.top();
|
sceneStack_.top()->updateScene(dt);
|
||||||
scene.updateScene(dt);
|
|
||||||
if (!isTransitioning_) {
|
|
||||||
dispatchPointerEvents(scene);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SceneManager::render(RenderBackend &renderer) {
|
void SceneManager::render(Renderer &renderer) {
|
||||||
Color clearColor = Colors::Black;
|
Color clearColor = Colors::Black;
|
||||||
if (!sceneStack_.empty()) {
|
if (!sceneStack_.empty()) {
|
||||||
clearColor = sceneStack_.top()->getBackgroundColor();
|
clearColor = sceneStack_.top()->getBackgroundColor();
|
||||||
|
|
@ -518,197 +230,4 @@ void SceneManager::end() {
|
||||||
|
|
||||||
void SceneManager::purgeCachedScenes() { namedScenes_.clear(); }
|
void SceneManager::purgeCachedScenes() { namedScenes_.clear(); }
|
||||||
|
|
||||||
void SceneManager::startTransition(Ptr<Scene> from, Ptr<Scene> to,
|
|
||||||
TransitionType type, float duration,
|
|
||||||
Function<void()> stackAction) {
|
|
||||||
if (!from || !to) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建过渡场景
|
|
||||||
auto transitionScene = createTransitionScene(type, duration, to);
|
|
||||||
if (!transitionScene) {
|
|
||||||
// 回退到无过渡切换
|
|
||||||
replaceScene(to);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 在过渡开始前,发送 UIHoverExit 给当前悬停的节点,重置按钮状态
|
|
||||||
if (hoverTarget_) {
|
|
||||||
Event evt;
|
|
||||||
evt.type = EventType::UIHoverExit;
|
|
||||||
evt.data = CustomEvent{0, hoverTarget_};
|
|
||||||
dispatchToNode(hoverTarget_, evt);
|
|
||||||
hoverTarget_ = nullptr;
|
|
||||||
}
|
|
||||||
captureTarget_ = nullptr;
|
|
||||||
hasLastPointerWorld_ = false;
|
|
||||||
|
|
||||||
// 设置过渡场景
|
|
||||||
transitionScene->setOutScene(from);
|
|
||||||
transitionScene->setFinishCallback([this]() { finishTransition(); });
|
|
||||||
|
|
||||||
// 暂停当前场景
|
|
||||||
from->pause();
|
|
||||||
|
|
||||||
// 推入过渡场景(作为中介场景)
|
|
||||||
transitionScene->onEnter();
|
|
||||||
transitionScene->onAttachToScene(transitionScene.get());
|
|
||||||
sceneStack_.push(transitionScene);
|
|
||||||
|
|
||||||
isTransitioning_ = true;
|
|
||||||
currentTransition_ = type;
|
|
||||||
activeTransitionScene_ = transitionScene;
|
|
||||||
transitionStackAction_ = std::move(stackAction);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ptr<TransitionScene> SceneManager::createTransitionScene(TransitionType type,
|
|
||||||
float duration,
|
|
||||||
Ptr<Scene> inScene) {
|
|
||||||
if (!inScene) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (type) {
|
|
||||||
case TransitionType::Fade:
|
|
||||||
return TransitionFadeScene::create(duration, inScene);
|
|
||||||
case TransitionType::SlideLeft:
|
|
||||||
return TransitionSlideScene::create(duration, inScene,
|
|
||||||
TransitionDirection::Left);
|
|
||||||
case TransitionType::SlideRight:
|
|
||||||
return TransitionSlideScene::create(duration, inScene,
|
|
||||||
TransitionDirection::Right);
|
|
||||||
case TransitionType::SlideUp:
|
|
||||||
return TransitionSlideScene::create(duration, inScene,
|
|
||||||
TransitionDirection::Up);
|
|
||||||
case TransitionType::SlideDown:
|
|
||||||
return TransitionSlideScene::create(duration, inScene,
|
|
||||||
TransitionDirection::Down);
|
|
||||||
case TransitionType::Scale:
|
|
||||||
return TransitionScaleScene::create(duration, inScene);
|
|
||||||
case TransitionType::Flip:
|
|
||||||
return TransitionFlipScene::create(duration, inScene);
|
|
||||||
case TransitionType::Box:
|
|
||||||
return TransitionBoxScene::create(duration, inScene);
|
|
||||||
default:
|
|
||||||
// 默认使用淡入淡出
|
|
||||||
return TransitionFadeScene::create(duration, inScene);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SceneManager::finishTransition() {
|
|
||||||
// 先保存当前悬停的节点,然后在 transitionStackAction_ 之后发送 UIHoverExit
|
|
||||||
Node *lastHoverTarget = hoverTarget_;
|
|
||||||
|
|
||||||
isTransitioning_ = false;
|
|
||||||
hoverTarget_ = nullptr;
|
|
||||||
captureTarget_ = nullptr;
|
|
||||||
hasLastPointerWorld_ = false;
|
|
||||||
|
|
||||||
if (transitionStackAction_) {
|
|
||||||
transitionStackAction_();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 在 transitionStackAction_ 之后发送 UIHoverExit,确保旧场景仍然有效
|
|
||||||
if (lastHoverTarget) {
|
|
||||||
Event evt;
|
|
||||||
evt.type = EventType::UIHoverExit;
|
|
||||||
evt.data = CustomEvent{0, lastHoverTarget};
|
|
||||||
dispatchToNode(lastHoverTarget, evt);
|
|
||||||
}
|
|
||||||
|
|
||||||
activeTransitionScene_.reset();
|
|
||||||
transitionStackAction_ = nullptr;
|
|
||||||
|
|
||||||
if (transitionCallback_) {
|
|
||||||
transitionCallback_();
|
|
||||||
transitionCallback_ = nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ptr<Node> root = scene.shared_from_this();
|
|
||||||
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
|
} // namespace extra2d
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <extra2d/graphics/render_backend.h>
|
#include <extra2d/graphics/renderer.h>
|
||||||
#include <extra2d/graphics/render_command.h>
|
#include <extra2d/graphics/render_command.h>
|
||||||
#include <extra2d/scene/shape_node.h>
|
#include <extra2d/scene/shape_node.h>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
|
|
@ -166,7 +166,7 @@ Rect ShapeNode::getBoundingBox() const {
|
||||||
(maxY - minY) + inflate * 2.0f);
|
(maxY - minY) + inflate * 2.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ShapeNode::onDraw(RenderBackend &renderer) {
|
void ShapeNode::onDraw(Renderer &renderer) {
|
||||||
if (points_.empty()) {
|
if (points_.empty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <extra2d/graphics/render_backend.h>
|
#include <extra2d/graphics/renderer.h>
|
||||||
#include <extra2d/graphics/render_command.h>
|
#include <extra2d/graphics/render_command.h>
|
||||||
#include <extra2d/graphics/texture.h>
|
#include <extra2d/graphics/texture.h>
|
||||||
#include <extra2d/scene/sprite.h>
|
#include <extra2d/scene/sprite.h>
|
||||||
|
|
@ -67,7 +67,7 @@ Rect Sprite::getBoundingBox() const {
|
||||||
return Rect(l, t, std::abs(w), std::abs(h));
|
return Rect(l, t, std::abs(w), std::abs(h));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Sprite::onDraw(RenderBackend &renderer) {
|
void Sprite::onDraw(Renderer &renderer) {
|
||||||
if (!texture_ || !texture_->isValid()) {
|
if (!texture_ || !texture_->isValid()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -91,7 +91,7 @@ void Sprite::onDraw(RenderBackend &renderer) {
|
||||||
|
|
||||||
auto anchor = getAnchor();
|
auto anchor = getAnchor();
|
||||||
|
|
||||||
// 锚点由 RenderBackend 在绘制时处理,这里只传递位置和尺寸
|
// 锚点由 Renderer 在绘制时处理,这里只传递位置和尺寸
|
||||||
Rect destRect(worldX, worldY, width * worldScaleX, height * worldScaleY);
|
Rect destRect(worldX, worldY, width * worldScaleX, height * worldScaleY);
|
||||||
|
|
||||||
// Adjust source rect for flipping
|
// Adjust source rect for flipping
|
||||||
|
|
@ -137,7 +137,7 @@ void Sprite::generateRenderCommand(std::vector<RenderCommand> &commands,
|
||||||
|
|
||||||
auto anchor = getAnchor();
|
auto anchor = getAnchor();
|
||||||
|
|
||||||
// 锚点由 RenderBackend 在绘制时处理,这里只传递位置和尺寸
|
// 锚点由 Renderer 在绘制时处理,这里只传递位置和尺寸
|
||||||
Rect destRect(worldX, worldY, width * worldScaleX, height * worldScaleY);
|
Rect destRect(worldX, worldY, width * worldScaleX, height * worldScaleY);
|
||||||
|
|
||||||
// 调整源矩形(翻转)
|
// 调整源矩形(翻转)
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ public:
|
||||||
return Rect(pos.x - width_ / 2, pos.y - height_ / 2, width_, height_);
|
return Rect(pos.x - width_ / 2, pos.y - height_ / 2, width_, height_);
|
||||||
}
|
}
|
||||||
|
|
||||||
void onRender(RenderBackend &renderer) override {
|
void onRender(Renderer &renderer) override {
|
||||||
Vec2 pos = getPosition();
|
Vec2 pos = getPosition();
|
||||||
|
|
||||||
// 绘制填充矩形
|
// 绘制填充矩形
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@ function define_extra2d_engine()
|
||||||
|
|
||||||
-- 头文件路径
|
-- 头文件路径
|
||||||
add_includedirs("Extra2D/include", {public = true})
|
add_includedirs("Extra2D/include", {public = true})
|
||||||
add_includedirs("Extra2D/include/extra2d/platform", {public = true})
|
|
||||||
|
|
||||||
-- 平台配置
|
-- 平台配置
|
||||||
local plat = get_current_plat()
|
local plat = get_current_plat()
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue