变更初始
This commit is contained in:
parent
7134b61dc2
commit
0f9ea65066
|
|
@ -1,92 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <fostbite2D/core/math_types.h>
|
|
||||||
#include <fostbite2D/core/types.h>
|
|
||||||
#include <glm/mat4x4.hpp>
|
|
||||||
|
|
||||||
namespace frostbite2D {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 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 frostbite2D
|
|
||||||
|
|
@ -1,87 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <fostbite2D/core/color.h>
|
|
||||||
#include <fostbite2D/core/types.h>
|
|
||||||
#include <fostbite2D/core/math_types.h>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
namespace frostbite2D {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 字形信息
|
|
||||||
// ============================================================================
|
|
||||||
struct Glyph {
|
|
||||||
float u0, v0; // 纹理坐标左下角
|
|
||||||
float u1, v1; // 纹理坐标右上角
|
|
||||||
float width; // 字形宽度(像素)
|
|
||||||
float height; // 字形高度(像素)
|
|
||||||
float bearingX; // 水平偏移
|
|
||||||
float bearingY; // 垂直偏移
|
|
||||||
float advance; // 前进距离
|
|
||||||
};
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 字体图集接口
|
|
||||||
// ============================================================================
|
|
||||||
class FontAtlas {
|
|
||||||
public:
|
|
||||||
virtual ~FontAtlas() = default;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取字形信息
|
|
||||||
* @param codepoint Unicode码点
|
|
||||||
* @return 字形信息指针
|
|
||||||
*/
|
|
||||||
virtual const Glyph *getGlyph(char32_t codepoint) const = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取纹理
|
|
||||||
* @return 纹理指针
|
|
||||||
*/
|
|
||||||
virtual class Texture *getTexture() const = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取字体大小
|
|
||||||
* @return 字体大小(像素)
|
|
||||||
*/
|
|
||||||
virtual int getFontSize() const = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取字体上升高度
|
|
||||||
* @return 上升高度
|
|
||||||
*/
|
|
||||||
virtual float getAscent() const = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取字体下降高度
|
|
||||||
* @return 下降高度
|
|
||||||
*/
|
|
||||||
virtual float getDescent() const = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取行间距
|
|
||||||
* @return 行间距
|
|
||||||
*/
|
|
||||||
virtual float getLineGap() const = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取行高
|
|
||||||
* @return 行高
|
|
||||||
*/
|
|
||||||
virtual float getLineHeight() const = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 计算文字尺寸
|
|
||||||
* @param text 要测量的文本
|
|
||||||
* @return 文本的宽度和高度
|
|
||||||
*/
|
|
||||||
virtual Vec2 measureText(const std::string &text) = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 是否支持 SDF 渲染
|
|
||||||
* @return 支持SDF返回true
|
|
||||||
*/
|
|
||||||
virtual bool isSDF() const = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace frostbite2D
|
|
||||||
|
|
@ -1,85 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <fostbite2D/core/color.h>
|
|
||||||
#include <fostbite2D/core/math_types.h>
|
|
||||||
#include <fostbite2D/core/types.h>
|
|
||||||
#include <fostbite2D/render/font.h>
|
|
||||||
#include <fostbite2D/render/opengl/gl_texture.h>
|
|
||||||
#include <fostbite2D/render/texture.h>
|
|
||||||
#include <memory>
|
|
||||||
#include <stb/stb_rect_pack.h>
|
|
||||||
#include <stb/stb_truetype.h>
|
|
||||||
#include <unordered_map>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace frostbite2D {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// OpenGL 字体图集实现 - 使用 stb_rect_pack 进行矩形打包
|
|
||||||
// ============================================================================
|
|
||||||
class GLFontAtlas : public FontAtlas {
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief 构造函数,从字体文件初始化字体图集
|
|
||||||
* @param filepath 字体文件路径
|
|
||||||
* @param fontSize 字体大小(像素)
|
|
||||||
* @param useSDF 是否使用有符号距离场渲染
|
|
||||||
*/
|
|
||||||
GLFontAtlas(const std::string &filepath, int fontSize, bool useSDF = false);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 析构函数
|
|
||||||
*/
|
|
||||||
~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_;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 创建字体图集纹理
|
|
||||||
*/
|
|
||||||
void createAtlas();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 缓存字形到图集
|
|
||||||
* @param codepoint Unicode码点
|
|
||||||
*/
|
|
||||||
void cacheGlyph(char32_t codepoint) const;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace frostbite2D
|
|
||||||
|
|
@ -1,316 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <fostbite2D/core/color.h>
|
|
||||||
#include <fostbite2D/core/math_types.h>
|
|
||||||
#include <fostbite2D/core/types.h>
|
|
||||||
#include <fostbite2D/render/font.h>
|
|
||||||
#include <fostbite2D/render/opengl/gl_sprite_batch.h>
|
|
||||||
#include <fostbite2D/render/shader/shader_interface.h>
|
|
||||||
#include <fostbite2D/render/texture.h>
|
|
||||||
|
|
||||||
#include <array>
|
|
||||||
#include <glad/glad.h>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
struct SDL_Window;
|
|
||||||
|
|
||||||
namespace frostbite2D {
|
|
||||||
|
|
||||||
// 混合模式枚举
|
|
||||||
enum class BlendMode { None, Alpha, Additive, Multiply };
|
|
||||||
|
|
||||||
// 渲染统计信息
|
|
||||||
struct RenderStats {
|
|
||||||
uint32_t drawCalls = 0;
|
|
||||||
uint32_t triangleCount = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// OpenGL 渲染器实现
|
|
||||||
// ============================================================================
|
|
||||||
class GLRenderer {
|
|
||||||
public:
|
|
||||||
GLRenderer();
|
|
||||||
~GLRenderer();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 初始化OpenGL渲染器
|
|
||||||
* @param window SDL窗口指针
|
|
||||||
* @return 初始化成功返回true,失败返回false
|
|
||||||
*/
|
|
||||||
bool init(SDL_Window *window);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 关闭渲染器,释放所有GPU资源
|
|
||||||
*/
|
|
||||||
void shutdown();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 开始新帧,清除颜色缓冲区并重置统计信息
|
|
||||||
* @param clearColor 清屏颜色
|
|
||||||
*/
|
|
||||||
void beginFrame(const Color &clearColor);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 结束当前帧,刷新所有待处理的渲染批次
|
|
||||||
*/
|
|
||||||
void endFrame();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置视口区域
|
|
||||||
* @param x 视口左下角X坐标
|
|
||||||
* @param y 视口左下角Y坐标
|
|
||||||
* @param width 视口宽度
|
|
||||||
* @param height 视口高度
|
|
||||||
*/
|
|
||||||
void setViewport(int x, int y, int width, int height);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置垂直同步
|
|
||||||
* @param enabled true启用垂直同步,false禁用
|
|
||||||
*/
|
|
||||||
void setVSync(bool enabled);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置混合模式
|
|
||||||
* @param mode 混合模式枚举值
|
|
||||||
*/
|
|
||||||
void setBlendMode(BlendMode mode);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置视图投影矩阵
|
|
||||||
* @param matrix 4x4视图投影矩阵
|
|
||||||
*/
|
|
||||||
void setViewProjection(const glm::mat4 &matrix);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 压入变换矩阵到变换栈
|
|
||||||
* @param transform 变换矩阵
|
|
||||||
*/
|
|
||||||
void pushTransform(const glm::mat4 &transform);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 从变换栈弹出顶部变换矩阵
|
|
||||||
*/
|
|
||||||
void popTransform();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取当前累积的变换矩阵
|
|
||||||
* @return 当前变换矩阵,如果栈为空则返回单位矩阵
|
|
||||||
*/
|
|
||||||
glm::mat4 getCurrentTransform() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 创建纹理对象
|
|
||||||
* @param width 纹理宽度
|
|
||||||
* @param height 纹理高度
|
|
||||||
* @param pixels 像素数据指针
|
|
||||||
* @param channels 颜色通道数
|
|
||||||
* @return 创建的纹理智能指针
|
|
||||||
*/
|
|
||||||
Ptr<Texture> createTexture(int width, int height, const uint8_t *pixels,
|
|
||||||
int channels);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 从文件加载纹理
|
|
||||||
* @param filepath 纹理文件路径
|
|
||||||
* @return 加载的纹理智能指针
|
|
||||||
*/
|
|
||||||
Ptr<Texture> loadTexture(const std::string &filepath);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 开始精灵批处理
|
|
||||||
*/
|
|
||||||
void beginSpriteBatch();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 绘制精灵(带完整参数)
|
|
||||||
* @param texture 纹理引用
|
|
||||||
* @param destRect 目标矩形(屏幕坐标)
|
|
||||||
* @param srcRect 源矩形(纹理坐标)
|
|
||||||
* @param tint 着色颜色
|
|
||||||
* @param rotation 旋转角度(度)
|
|
||||||
* @param anchor 锚点位置(0-1范围)
|
|
||||||
*/
|
|
||||||
void drawSprite(const Texture &texture, const Rect &destRect,
|
|
||||||
const Rect &srcRect, const Color &tint, float rotation,
|
|
||||||
const Vec2 &anchor);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 绘制精灵(简化版本)
|
|
||||||
* @param texture 纹理引用
|
|
||||||
* @param position 绘制位置
|
|
||||||
* @param tint 着色颜色
|
|
||||||
*/
|
|
||||||
void drawSprite(const Texture &texture, const Vec2 &position,
|
|
||||||
const Color &tint);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 结束精灵批处理并提交绘制
|
|
||||||
*/
|
|
||||||
void endSpriteBatch();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 绘制线段
|
|
||||||
* @param start 起点坐标
|
|
||||||
* @param end 终点坐标
|
|
||||||
* @param color 线条颜色
|
|
||||||
* @param width 线条宽度
|
|
||||||
*/
|
|
||||||
void drawLine(const Vec2 &start, const Vec2 &end, const Color &color,
|
|
||||||
float width);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 绘制矩形边框
|
|
||||||
* @param rect 矩形区域
|
|
||||||
* @param color 边框颜色
|
|
||||||
* @param width 线条宽度
|
|
||||||
*/
|
|
||||||
void drawRect(const Rect &rect, const Color &color, float width);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 填充矩形
|
|
||||||
* @param rect 矩形区域
|
|
||||||
* @param color 填充颜色
|
|
||||||
*/
|
|
||||||
void fillRect(const Rect &rect, const Color &color);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 绘制圆形边框
|
|
||||||
* @param center 圆心坐标
|
|
||||||
* @param radius 半径
|
|
||||||
* @param color 边框颜色
|
|
||||||
* @param segments 分段数
|
|
||||||
* @param width 线条宽度
|
|
||||||
*/
|
|
||||||
void drawCircle(const Vec2 ¢er, float radius, const Color &color,
|
|
||||||
int segments, float width);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 填充圆形
|
|
||||||
* @param center 圆心坐标
|
|
||||||
* @param radius 半径
|
|
||||||
* @param color 填充颜色
|
|
||||||
* @param segments 分段数
|
|
||||||
*/
|
|
||||||
void fillCircle(const Vec2 ¢er, float radius, const Color &color,
|
|
||||||
int segments);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 绘制三角形边框
|
|
||||||
* @param p1 第一个顶点
|
|
||||||
* @param p2 第二个顶点
|
|
||||||
* @param p3 第三个顶点
|
|
||||||
* @param color 边框颜色
|
|
||||||
* @param width 线条宽度
|
|
||||||
*/
|
|
||||||
void drawTriangle(const Vec2 &p1, const Vec2 &p2, const Vec2 &p3,
|
|
||||||
const Color &color, float width);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 填充三角形
|
|
||||||
* @param p1 第一个顶点
|
|
||||||
* @param p2 第二个顶点
|
|
||||||
* @param p3 第三个顶点
|
|
||||||
* @param color 填充颜色
|
|
||||||
*/
|
|
||||||
void fillTriangle(const Vec2 &p1, const Vec2 &p2, const Vec2 &p3,
|
|
||||||
const Color &color);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 绘制多边形边框
|
|
||||||
* @param points 顶点数组
|
|
||||||
* @param color 边框颜色
|
|
||||||
* @param width 线条宽度
|
|
||||||
*/
|
|
||||||
void drawPolygon(const std::vector<Vec2> &points, const Color &color,
|
|
||||||
float width);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 填充多边形
|
|
||||||
* @param points 顶点数组
|
|
||||||
* @param color 填充颜色
|
|
||||||
*/
|
|
||||||
void fillPolygon(const std::vector<Vec2> &points, const Color &color);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 绘制文本
|
|
||||||
* @param font 字体图集引用
|
|
||||||
* @param text 文本内容
|
|
||||||
* @param position 绘制位置
|
|
||||||
* @param color 文本颜色
|
|
||||||
*/
|
|
||||||
void drawText(const FontAtlas &font, const std::string &text,
|
|
||||||
const Vec2 &position, const Color &color);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 绘制文本(使用浮点坐标)
|
|
||||||
* @param font 字体图集引用
|
|
||||||
* @param text 文本内容
|
|
||||||
* @param x X坐标
|
|
||||||
* @param y Y坐标
|
|
||||||
* @param color 文本颜色
|
|
||||||
*/
|
|
||||||
void drawText(const FontAtlas &font, const std::string &text, float x,
|
|
||||||
float y, const Color &color);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取渲染统计信息
|
|
||||||
* @return 渲染统计信息
|
|
||||||
*/
|
|
||||||
RenderStats getStats() const { return stats_; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 重置渲染统计信息
|
|
||||||
*/
|
|
||||||
void resetStats();
|
|
||||||
|
|
||||||
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;
|
|
||||||
};
|
|
||||||
|
|
||||||
SDL_Window *window_;
|
|
||||||
GLSpriteBatch spriteBatch_;
|
|
||||||
Ptr<IShader> shapeShader_;
|
|
||||||
|
|
||||||
GLuint shapeVao_;
|
|
||||||
GLuint shapeVbo_;
|
|
||||||
GLuint lineVao_; // 线条专用 VAO
|
|
||||||
GLuint lineVbo_; // 线条专用 VBO
|
|
||||||
|
|
||||||
glm::mat4 viewProjection_;
|
|
||||||
std::vector<glm::mat4> transformStack_;
|
|
||||||
RenderStats 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;
|
|
||||||
|
|
||||||
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 frostbite2D
|
|
||||||
|
|
@ -1,194 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <fostbite2D/core/color.h>
|
|
||||||
#include <fostbite2D/render/shader/shader_interface.h>
|
|
||||||
#include <glad/glad.h>
|
|
||||||
#include <unordered_map>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace frostbite2D {
|
|
||||||
|
|
||||||
class GLShader : public IShader {
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief 构造函数
|
|
||||||
*/
|
|
||||||
GLShader();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 析构函数
|
|
||||||
*/
|
|
||||||
~GLShader() override;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 绑定Shader程序
|
|
||||||
*/
|
|
||||||
void bind() const override;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 解绑Shader程序
|
|
||||||
*/
|
|
||||||
void unbind() const override;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置布尔类型uniform变量
|
|
||||||
* @param name uniform变量名
|
|
||||||
* @param value 布尔值
|
|
||||||
*/
|
|
||||||
void setBool(const std::string &name, bool value) override;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置整数类型uniform变量
|
|
||||||
* @param name uniform变量名
|
|
||||||
* @param value 整数值
|
|
||||||
*/
|
|
||||||
void setInt(const std::string &name, int value) override;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置浮点类型uniform变量
|
|
||||||
* @param name uniform变量名
|
|
||||||
* @param value 浮点值
|
|
||||||
*/
|
|
||||||
void setFloat(const std::string &name, float value) override;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置二维向量类型uniform变量
|
|
||||||
* @param name uniform变量名
|
|
||||||
* @param value 二维向量值
|
|
||||||
*/
|
|
||||||
void setVec2(const std::string &name, const glm::vec2 &value) override;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置三维向量类型uniform变量
|
|
||||||
* @param name uniform变量名
|
|
||||||
* @param value 三维向量值
|
|
||||||
*/
|
|
||||||
void setVec3(const std::string &name, const glm::vec3 &value) override;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置四维向量类型uniform变量
|
|
||||||
* @param name uniform变量名
|
|
||||||
* @param value 四维向量值
|
|
||||||
*/
|
|
||||||
void setVec4(const std::string &name, const glm::vec4 &value) override;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置4x4矩阵类型uniform变量
|
|
||||||
* @param name uniform变量名
|
|
||||||
* @param value 4x4矩阵值
|
|
||||||
*/
|
|
||||||
void setMat4(const std::string &name, const glm::mat4 &value) override;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置颜色类型uniform变量
|
|
||||||
* @param name uniform变量名
|
|
||||||
* @param color 颜色值
|
|
||||||
*/
|
|
||||||
void setColor(const std::string &name, const Color &color) override;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查Shader是否有效
|
|
||||||
* @return 有效返回true,否则返回false
|
|
||||||
*/
|
|
||||||
bool isValid() const override { return programID_ != 0; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取原生句柄(OpenGL程序ID)
|
|
||||||
* @return OpenGL程序ID
|
|
||||||
*/
|
|
||||||
uint32_t getNativeHandle() const override { return programID_; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取Shader名称
|
|
||||||
* @return Shader名称
|
|
||||||
*/
|
|
||||||
const std::string &getName() const override { return name_; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置Shader名称
|
|
||||||
* @param name Shader名称
|
|
||||||
*/
|
|
||||||
void setName(const std::string &name) override { name_ = name; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 从源码编译Shader
|
|
||||||
* @param vertexSource 顶点着色器源码
|
|
||||||
* @param fragmentSource 片段着色器源码
|
|
||||||
* @return 编译成功返回true,失败返回false
|
|
||||||
*/
|
|
||||||
bool compileFromSource(const char *vertexSource, const char *fragmentSource);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 从二进制数据创建Shader
|
|
||||||
* @param binary 二进制数据
|
|
||||||
* @return 创建成功返回true,失败返回false
|
|
||||||
*/
|
|
||||||
bool compileFromBinary(const std::vector<uint8_t> &binary);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取Shader二进制数据
|
|
||||||
* @param outBinary 输出的二进制数据
|
|
||||||
* @return 成功返回true,失败返回false
|
|
||||||
*/
|
|
||||||
bool getBinary(std::vector<uint8_t> &outBinary);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取OpenGL程序ID
|
|
||||||
* @return OpenGL程序ID
|
|
||||||
*/
|
|
||||||
GLuint getProgramID() const { return programID_; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
GLuint programID_ = 0;
|
|
||||||
std::string name_;
|
|
||||||
std::unordered_map<std::string, GLint> uniformCache_;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 编译单个着色器
|
|
||||||
* @param type 着色器类型
|
|
||||||
* @param source 着色器源码
|
|
||||||
* @return 着色器ID,失败返回0
|
|
||||||
*/
|
|
||||||
GLuint compileShader(GLenum type, const char *source);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取uniform位置
|
|
||||||
* @param name uniform变量名
|
|
||||||
* @return uniform位置
|
|
||||||
*/
|
|
||||||
GLint getUniformLocation(const std::string &name);
|
|
||||||
};
|
|
||||||
|
|
||||||
class GLShaderFactory : public IShaderFactory {
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief 从源码创建Shader
|
|
||||||
* @param name Shader名称
|
|
||||||
* @param vertSource 顶点着色器源码
|
|
||||||
* @param fragSource 片段着色器源码
|
|
||||||
* @return 创建的Shader实例
|
|
||||||
*/
|
|
||||||
Ptr<IShader> createFromSource(const std::string &name,
|
|
||||||
const std::string &vertSource,
|
|
||||||
const std::string &fragSource) override;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 从缓存二进制创建Shader
|
|
||||||
* @param name Shader名称
|
|
||||||
* @param binary 编译后的二进制数据
|
|
||||||
* @return 创建的Shader实例
|
|
||||||
*/
|
|
||||||
Ptr<IShader> createFromBinary(const std::string &name,
|
|
||||||
const std::vector<uint8_t> &binary) override;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取Shader的二进制数据
|
|
||||||
* @param shader Shader实例
|
|
||||||
* @param outBinary 输出的二进制数据
|
|
||||||
* @return 成功返回true,失败返回false
|
|
||||||
*/
|
|
||||||
bool getShaderBinary(const IShader &shader,
|
|
||||||
std::vector<uint8_t> &outBinary) override;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace frostbite2D
|
|
||||||
|
|
@ -1,98 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <array>
|
|
||||||
#include <fostbite2D/core/color.h>
|
|
||||||
#include <fostbite2D/core/math_types.h>
|
|
||||||
#include <fostbite2D/core/types.h>
|
|
||||||
#include <fostbite2D/render/shader/shader_interface.h>
|
|
||||||
#include <fostbite2D/render/texture.h>
|
|
||||||
#include <glm/mat4x4.hpp>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include <glad/glad.h>
|
|
||||||
|
|
||||||
namespace frostbite2D {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 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_;
|
|
||||||
Ptr<IShader> 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 frostbite2D
|
|
||||||
|
|
@ -1,79 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <fostbite2D/core/color.h>
|
|
||||||
#include <fostbite2D/core/types.h>
|
|
||||||
#include <fostbite2D/render/texture.h>
|
|
||||||
|
|
||||||
#include <glad/glad.h>
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
namespace frostbite2D {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 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 frostbite2D
|
|
||||||
|
|
@ -1,151 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <fostbite2D/core/types.h>
|
|
||||||
#include <glm/mat4x4.hpp>
|
|
||||||
#include <glm/vec2.hpp>
|
|
||||||
#include <glm/vec3.hpp>
|
|
||||||
#include <glm/vec4.hpp>
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace frostbite2D {
|
|
||||||
|
|
||||||
// 前向声明
|
|
||||||
struct Color;
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// Shader抽象接口 - 渲染后端无关
|
|
||||||
// ============================================================================
|
|
||||||
class IShader {
|
|
||||||
public:
|
|
||||||
virtual ~IShader() = default;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 绑定Shader程序
|
|
||||||
*/
|
|
||||||
virtual void bind() const = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 解绑Shader程序
|
|
||||||
*/
|
|
||||||
virtual void unbind() const = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置布尔类型uniform变量
|
|
||||||
* @param name uniform变量名
|
|
||||||
* @param value 布尔值
|
|
||||||
*/
|
|
||||||
virtual void setBool(const std::string &name, bool value) = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置整数类型uniform变量
|
|
||||||
* @param name uniform变量名
|
|
||||||
* @param value 整数值
|
|
||||||
*/
|
|
||||||
virtual void setInt(const std::string &name, int value) = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置浮点类型uniform变量
|
|
||||||
* @param name uniform变量名
|
|
||||||
* @param value 浮点值
|
|
||||||
*/
|
|
||||||
virtual void setFloat(const std::string &name, float value) = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置二维向量类型uniform变量
|
|
||||||
* @param name uniform变量名
|
|
||||||
* @param value 二维向量值
|
|
||||||
*/
|
|
||||||
virtual void setVec2(const std::string &name, const glm::vec2 &value) = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置三维向量类型uniform变量
|
|
||||||
* @param name uniform变量名
|
|
||||||
* @param value 三维向量值
|
|
||||||
*/
|
|
||||||
virtual void setVec3(const std::string &name, const glm::vec3 &value) = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置四维向量类型uniform变量
|
|
||||||
* @param name uniform变量名
|
|
||||||
* @param value 四维向量值
|
|
||||||
*/
|
|
||||||
virtual void setVec4(const std::string &name, const glm::vec4 &value) = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置4x4矩阵类型uniform变量
|
|
||||||
* @param name uniform变量名
|
|
||||||
* @param value 4x4矩阵值
|
|
||||||
*/
|
|
||||||
virtual void setMat4(const std::string &name, const glm::mat4 &value) = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置颜色类型uniform变量
|
|
||||||
* @param name uniform变量名
|
|
||||||
* @param color 颜色值
|
|
||||||
*/
|
|
||||||
virtual void setColor(const std::string &name, const Color &color) = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查Shader是否有效
|
|
||||||
* @return 有效返回true,否则返回false
|
|
||||||
*/
|
|
||||||
virtual bool isValid() const = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取原生句柄(如OpenGL程序ID)
|
|
||||||
* @return 原生句柄值
|
|
||||||
*/
|
|
||||||
virtual uint32_t getNativeHandle() const = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取Shader名称
|
|
||||||
* @return Shader名称
|
|
||||||
*/
|
|
||||||
virtual const std::string &getName() const = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置Shader名称
|
|
||||||
* @param name Shader名称
|
|
||||||
*/
|
|
||||||
virtual void setName(const std::string &name) = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// Shader工厂接口 - 用于创建渲染后端特定的Shader实例
|
|
||||||
// ============================================================================
|
|
||||||
class IShaderFactory {
|
|
||||||
public:
|
|
||||||
virtual ~IShaderFactory() = default;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 从源码创建Shader
|
|
||||||
* @param name Shader名称
|
|
||||||
* @param vertSource 顶点着色器源码
|
|
||||||
* @param fragSource 片段着色器源码
|
|
||||||
* @return 创建的Shader实例
|
|
||||||
*/
|
|
||||||
virtual Ptr<IShader> createFromSource(const std::string &name,
|
|
||||||
const std::string &vertSource,
|
|
||||||
const std::string &fragSource) = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 从缓存二进制创建Shader
|
|
||||||
* @param name Shader名称
|
|
||||||
* @param binary 编译后的二进制数据
|
|
||||||
* @return 创建的Shader实例
|
|
||||||
*/
|
|
||||||
virtual Ptr<IShader> createFromBinary(const std::string &name,
|
|
||||||
const std::vector<uint8_t> &binary) = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取Shader的二进制数据(用于缓存)
|
|
||||||
* @param shader Shader实例
|
|
||||||
* @param outBinary 输出的二进制数据
|
|
||||||
* @return 成功返回true,失败返回false
|
|
||||||
*/
|
|
||||||
virtual bool getShaderBinary(const IShader &shader,
|
|
||||||
std::vector<uint8_t> &outBinary) = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace frostbite2D
|
|
||||||
|
|
@ -1,129 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <fostbite2D/core/types.h>
|
|
||||||
#include <fostbite2D/render/shader/shader_interface.h>
|
|
||||||
#include <filesystem>
|
|
||||||
#include <functional>
|
|
||||||
#include <unordered_map>
|
|
||||||
|
|
||||||
namespace frostbite2D {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// Shader重载回调
|
|
||||||
// ============================================================================
|
|
||||||
using ShaderReloadCallback = std::function<void(Ptr<IShader> newShader)>;
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// Shader管理器 - 统一入口
|
|
||||||
// ============================================================================
|
|
||||||
class ShaderManager {
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief 获取单例实例
|
|
||||||
* @return Shader管理器实例引用
|
|
||||||
*/
|
|
||||||
static ShaderManager &getInstance();
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 初始化和关闭
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 初始化Shader系统
|
|
||||||
* @param factory 渲染后端Shader工厂
|
|
||||||
* @return 初始化成功返回true,失败返回false
|
|
||||||
*/
|
|
||||||
bool init(Ptr<IShaderFactory> factory);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 关闭Shader系统
|
|
||||||
*/
|
|
||||||
void shutdown();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查是否已初始化
|
|
||||||
* @return 已初始化返回true,否则返回false
|
|
||||||
*/
|
|
||||||
bool isInitialized() const { return initialized_; }
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// Shader加载
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 从源码加载Shader
|
|
||||||
* @param name Shader名称
|
|
||||||
* @param vertSource 顶点着色器源码
|
|
||||||
* @param fragSource 片段着色器源码
|
|
||||||
* @return 加载的Shader实例
|
|
||||||
*/
|
|
||||||
Ptr<IShader> loadFromSource(const std::string &name,
|
|
||||||
const std::string &vertSource,
|
|
||||||
const std::string &fragSource);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 从文件加载Shader
|
|
||||||
* @param name Shader名称
|
|
||||||
* @param vertPath 顶点着色器文件路径
|
|
||||||
* @param fragPath 片段着色器文件路径
|
|
||||||
* @return 加载的Shader实例,失败返回nullptr
|
|
||||||
*/
|
|
||||||
Ptr<IShader> loadFromFile(const std::string &name,
|
|
||||||
const std::filesystem::path &vertPath,
|
|
||||||
const std::filesystem::path &fragPath);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取已加载的Shader
|
|
||||||
* @param name Shader名称
|
|
||||||
* @return Shader实例,不存在返回nullptr
|
|
||||||
*/
|
|
||||||
Ptr<IShader> get(const std::string &name) const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查Shader是否存在
|
|
||||||
* @param name Shader名称
|
|
||||||
* @return 存在返回true,否则返回false
|
|
||||||
*/
|
|
||||||
bool has(const std::string &name) const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 移除Shader
|
|
||||||
* @param name Shader名称
|
|
||||||
*/
|
|
||||||
void remove(const std::string &name);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 清除所有Shader
|
|
||||||
*/
|
|
||||||
void clear();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 注册重载回调
|
|
||||||
* @param name Shader名称
|
|
||||||
* @param callback 重载回调函数
|
|
||||||
*/
|
|
||||||
void setReloadCallback(const std::string &name,
|
|
||||||
ShaderReloadCallback callback);
|
|
||||||
|
|
||||||
private:
|
|
||||||
ShaderManager() = default;
|
|
||||||
~ShaderManager() = default;
|
|
||||||
ShaderManager(const ShaderManager &) = delete;
|
|
||||||
ShaderManager &operator=(const ShaderManager &) = delete;
|
|
||||||
|
|
||||||
Ptr<IShaderFactory> factory_;
|
|
||||||
|
|
||||||
struct ShaderInfo {
|
|
||||||
Ptr<IShader> shader;
|
|
||||||
ShaderReloadCallback reloadCallback;
|
|
||||||
std::string vertSource;
|
|
||||||
std::string fragSource;
|
|
||||||
std::filesystem::path vertPath;
|
|
||||||
std::filesystem::path fragPath;
|
|
||||||
};
|
|
||||||
std::unordered_map<std::string, ShaderInfo> shaders_;
|
|
||||||
|
|
||||||
bool initialized_ = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace frostbite2D
|
|
||||||
|
|
@ -1,154 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <fostbite2D/core/math_types.h>
|
|
||||||
#include <fostbite2D/core/types.h>
|
|
||||||
|
|
||||||
namespace frostbite2D {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 像素格式枚举
|
|
||||||
// ============================================================================
|
|
||||||
enum class PixelFormat {
|
|
||||||
R8, // 单通道灰度
|
|
||||||
RG8, // 双通道
|
|
||||||
RGB8, // RGB
|
|
||||||
RGBA8, // RGBA
|
|
||||||
R16F, // 半精度浮点单通道
|
|
||||||
RG16F, // 半精度浮点双通道
|
|
||||||
RGB16F, // 半精度浮点RGB
|
|
||||||
RGBA16F, // 半精度浮点RGBA
|
|
||||||
R32F, // 单精度浮点单通道
|
|
||||||
RG32F, // 单精度浮点双通道
|
|
||||||
RGB32F, // 单精度浮点RGB
|
|
||||||
RGBA32F, // 单精度浮点RGBA
|
|
||||||
// 压缩格式
|
|
||||||
ETC2_RGB8,
|
|
||||||
ETC2_RGBA8,
|
|
||||||
ASTC_4x4,
|
|
||||||
ASTC_6x6,
|
|
||||||
ASTC_8x8,
|
|
||||||
};
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 纹理抽象基类
|
|
||||||
// ============================================================================
|
|
||||||
class Texture {
|
|
||||||
public:
|
|
||||||
virtual ~Texture() = default;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取纹理宽度
|
|
||||||
* @return 纹理宽度(像素)
|
|
||||||
*/
|
|
||||||
virtual int getWidth() const = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取纹理高度
|
|
||||||
* @return 纹理高度(像素)
|
|
||||||
*/
|
|
||||||
virtual int getHeight() const = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取纹理尺寸
|
|
||||||
* @return 纹理尺寸
|
|
||||||
*/
|
|
||||||
virtual Size getSize() const = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取颜色通道数
|
|
||||||
* @return 颜色通道数
|
|
||||||
*/
|
|
||||||
virtual int getChannels() const = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取像素格式
|
|
||||||
* @return 像素格式
|
|
||||||
*/
|
|
||||||
virtual PixelFormat getFormat() const = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取原生句柄
|
|
||||||
* @return 原生句柄(如OpenGL纹理ID)
|
|
||||||
*/
|
|
||||||
virtual void *getNativeHandle() const = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查纹理是否有效
|
|
||||||
* @return 有效返回true,否则返回false
|
|
||||||
*/
|
|
||||||
virtual bool isValid() const = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置纹理过滤模式
|
|
||||||
* @param linear true使用线性过滤,false使用最近邻过滤
|
|
||||||
*/
|
|
||||||
virtual void setFilter(bool linear) = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置纹理环绕模式
|
|
||||||
* @param repeat true使用重复模式,false使用边缘拉伸模式
|
|
||||||
*/
|
|
||||||
virtual void setWrap(bool repeat) = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// Alpha遮罩 - 用于像素级碰撞检测
|
|
||||||
// ============================================================================
|
|
||||||
class AlphaMask {
|
|
||||||
public:
|
|
||||||
AlphaMask() = default;
|
|
||||||
AlphaMask(AlphaMask &&) = default;
|
|
||||||
AlphaMask &operator=(AlphaMask &&) = default;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 从像素数据创建Alpha遮罩
|
|
||||||
* @param pixels 像素数据指针
|
|
||||||
* @param width 宽度
|
|
||||||
* @param height 高度
|
|
||||||
* @param channels 通道数
|
|
||||||
* @return 创建的AlphaMask
|
|
||||||
*/
|
|
||||||
static AlphaMask createFromPixels(const uint8_t *pixels, int width,
|
|
||||||
int height, int channels);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查遮罩是否有效
|
|
||||||
* @return 有效返回true
|
|
||||||
*/
|
|
||||||
bool isValid() const { return !data_.empty() && width_ > 0 && height_ > 0; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取指定位置的透明度
|
|
||||||
* @param x X坐标
|
|
||||||
* @param y Y坐标
|
|
||||||
* @return 透明度值(0-255)
|
|
||||||
*/
|
|
||||||
uint8_t getAlpha(int x, int y) const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查指定位置是否不透明
|
|
||||||
* @param x X坐标
|
|
||||||
* @param y Y坐标
|
|
||||||
* @return 不透明返回true
|
|
||||||
*/
|
|
||||||
bool isOpaque(int x, int y) const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取宽度
|
|
||||||
* @return 宽度
|
|
||||||
*/
|
|
||||||
int getWidth() const { return width_; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取高度
|
|
||||||
* @return 高度
|
|
||||||
*/
|
|
||||||
int getHeight() const { return height_; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::vector<uint8_t> data_;
|
|
||||||
int width_ = 0;
|
|
||||||
int height_ = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace frostbite2D
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
#include <SDL2/SDL.h>
|
#include <SDL2/SDL.h>
|
||||||
#include <fostbite2D/app/application.h>
|
#include <fostbite2D/core/application.h>
|
||||||
namespace frostbite2D {
|
namespace frostbite2D {
|
||||||
|
|
||||||
Application &Application::get() {
|
Application &Application::get() {
|
||||||
|
|
@ -1,318 +0,0 @@
|
||||||
#include <algorithm>
|
|
||||||
#include <fostbite2D/render/camera.h>
|
|
||||||
#include <glm/gtc/matrix_inverse.hpp>
|
|
||||||
#include <glm/gtc/matrix_transform.hpp>
|
|
||||||
|
|
||||||
namespace frostbite2D {
|
|
||||||
|
|
||||||
// 使用 math_types.h 中定义的 DEG_TO_RAD
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 默认构造函数
|
|
||||||
*
|
|
||||||
* 创建一个默认的正交相机,视口范围为 (-1, -1) 到 (1, 1)
|
|
||||||
*/
|
|
||||||
Camera::Camera() : left_(-1.0f), right_(1.0f), bottom_(-1.0f), top_(1.0f) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 构造函数
|
|
||||||
* @param left 视口左边界
|
|
||||||
* @param right 视口右边界
|
|
||||||
* @param bottom 视口底边界
|
|
||||||
* @param top 视口顶边界
|
|
||||||
*
|
|
||||||
* 创建一个指定视口范围的正交相机
|
|
||||||
*/
|
|
||||||
Camera::Camera(float left, float right, float bottom, float top)
|
|
||||||
: left_(left), right_(right), bottom_(bottom), top_(top) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 构造函数
|
|
||||||
* @param viewport 视口尺寸
|
|
||||||
*
|
|
||||||
* 根据视口尺寸创建相机,视口原点在左上角
|
|
||||||
*/
|
|
||||||
Camera::Camera(const Size &viewport)
|
|
||||||
: left_(0.0f), right_(viewport.width), bottom_(viewport.height),
|
|
||||||
top_(0.0f) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置相机位置
|
|
||||||
* @param position 新的位置坐标
|
|
||||||
*
|
|
||||||
* 设置相机在世界空间中的位置,会标记视图矩阵为脏
|
|
||||||
*/
|
|
||||||
void Camera::setPosition(const Vec2 &position) {
|
|
||||||
position_ = position;
|
|
||||||
viewDirty_ = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置相机位置
|
|
||||||
* @param x X坐标
|
|
||||||
* @param y Y坐标
|
|
||||||
*
|
|
||||||
* 设置相机在世界空间中的位置,会标记视图矩阵为脏
|
|
||||||
*/
|
|
||||||
void Camera::setPosition(float x, float y) {
|
|
||||||
position_.x = x;
|
|
||||||
position_.y = y;
|
|
||||||
viewDirty_ = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置相机旋转角度
|
|
||||||
* @param degrees 旋转角度(度数)
|
|
||||||
*
|
|
||||||
* 设置相机的旋转角度,会标记视图矩阵为脏
|
|
||||||
*/
|
|
||||||
void Camera::setRotation(float degrees) {
|
|
||||||
rotation_ = degrees;
|
|
||||||
viewDirty_ = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置相机缩放级别
|
|
||||||
* @param zoom 缩放值(1.0为正常大小)
|
|
||||||
*
|
|
||||||
* 设置相机的缩放级别,会同时标记视图矩阵和投影矩阵为脏
|
|
||||||
*/
|
|
||||||
void Camera::setZoom(float zoom) {
|
|
||||||
zoom_ = zoom;
|
|
||||||
viewDirty_ = true;
|
|
||||||
projDirty_ = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置视口范围
|
|
||||||
* @param left 左边界
|
|
||||||
* @param right 右边界
|
|
||||||
* @param bottom 底边界
|
|
||||||
* @param top 顶边界
|
|
||||||
*
|
|
||||||
* 设置相机的正交投影视口范围,会标记投影矩阵为脏
|
|
||||||
*/
|
|
||||||
void Camera::setViewport(float left, float right, float bottom, float top) {
|
|
||||||
left_ = left;
|
|
||||||
right_ = right;
|
|
||||||
bottom_ = bottom;
|
|
||||||
top_ = top;
|
|
||||||
projDirty_ = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置视口范围
|
|
||||||
* @param rect 视口矩形
|
|
||||||
*
|
|
||||||
* 使用矩形设置相机的正交投影视口范围,会标记投影矩阵为脏
|
|
||||||
*/
|
|
||||||
void Camera::setViewport(const Rect &rect) {
|
|
||||||
left_ = rect.left();
|
|
||||||
right_ = rect.right();
|
|
||||||
bottom_ = rect.bottom();
|
|
||||||
top_ = rect.top();
|
|
||||||
projDirty_ = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取视口矩形
|
|
||||||
* @return 当前视口的矩形表示
|
|
||||||
*
|
|
||||||
* 返回当前相机的视口范围
|
|
||||||
*/
|
|
||||||
Rect Camera::getViewport() const {
|
|
||||||
return Rect(left_, top_, right_ - left_, bottom_ - top_);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取视图矩阵
|
|
||||||
* @return 视图矩阵
|
|
||||||
*
|
|
||||||
* 变换顺序:平移 -> 旋转 -> 缩放(逆序应用)
|
|
||||||
* View = T(-position) × R(-rotation) × S(1/zoom)
|
|
||||||
*/
|
|
||||||
glm::mat4 Camera::getViewMatrix() const {
|
|
||||||
if (viewDirty_) {
|
|
||||||
viewMatrix_ = glm::mat4(1.0f);
|
|
||||||
|
|
||||||
// 1. 平移(最后应用)
|
|
||||||
viewMatrix_ = glm::translate(viewMatrix_,
|
|
||||||
glm::vec3(-position_.x, -position_.y, 0.0f));
|
|
||||||
|
|
||||||
// 2. 旋转(中间应用)
|
|
||||||
if (rotation_ != 0.0f) {
|
|
||||||
viewMatrix_ = glm::rotate(viewMatrix_, -rotation_ * DEG_TO_RAD,
|
|
||||||
glm::vec3(0.0f, 0.0f, 1.0f));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. 缩放(最先应用)
|
|
||||||
if (zoom_ != 1.0f) {
|
|
||||||
viewMatrix_ =
|
|
||||||
glm::scale(viewMatrix_, glm::vec3(1.0f / zoom_, 1.0f / zoom_, 1.0f));
|
|
||||||
}
|
|
||||||
|
|
||||||
viewDirty_ = false;
|
|
||||||
}
|
|
||||||
return viewMatrix_;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取投影矩阵
|
|
||||||
* @return 正交投影矩阵
|
|
||||||
*
|
|
||||||
* 对于2D游戏,Y轴向下增长(屏幕坐标系)
|
|
||||||
* OpenGL默认Y轴向上,所以需要反转Y轴
|
|
||||||
*/
|
|
||||||
glm::mat4 Camera::getProjectionMatrix() const {
|
|
||||||
if (projDirty_) {
|
|
||||||
// 对于2D游戏,Y轴向下增长(屏幕坐标系)
|
|
||||||
// OpenGL默认Y轴向上,所以需要反转Y轴
|
|
||||||
// glm::ortho(left, right, bottom, top)
|
|
||||||
// 为了Y轴向下:传入 bottom > top,这样Y轴翻转
|
|
||||||
projMatrix_ =
|
|
||||||
glm::ortho(left_, right_, // X轴:从左到右
|
|
||||||
bottom_, top_, // Y轴:从上到下(反转,实现Y轴向下增长)
|
|
||||||
-1.0f, 1.0f);
|
|
||||||
projDirty_ = false;
|
|
||||||
}
|
|
||||||
return projMatrix_;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取视图-投影矩阵
|
|
||||||
* @return 视图-投影矩阵
|
|
||||||
*/
|
|
||||||
glm::mat4 Camera::getViewProjectionMatrix() const {
|
|
||||||
return getProjectionMatrix() * getViewMatrix();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 将屏幕坐标转换为世界坐标
|
|
||||||
* @param screenPos 屏幕坐标
|
|
||||||
* @return 世界坐标
|
|
||||||
*/
|
|
||||||
Vec2 Camera::screenToWorld(const Vec2 &screenPos) const {
|
|
||||||
// 使用逆视图-投影矩阵转换
|
|
||||||
glm::mat4 invVP = glm::inverse(getViewProjectionMatrix());
|
|
||||||
glm::vec4 ndc(screenPos.x, screenPos.y, 0.0f, 1.0f);
|
|
||||||
glm::vec4 world = invVP * ndc;
|
|
||||||
return Vec2(world.x, world.y);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 将世界坐标转换为屏幕坐标
|
|
||||||
* @param worldPos 世界坐标
|
|
||||||
* @return 屏幕坐标
|
|
||||||
*/
|
|
||||||
Vec2 Camera::worldToScreen(const Vec2 &worldPos) const {
|
|
||||||
glm::vec4 world(worldPos.x, worldPos.y, 0.0f, 1.0f);
|
|
||||||
glm::vec4 screen = getViewProjectionMatrix() * world;
|
|
||||||
return Vec2(screen.x, screen.y);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 将屏幕坐标转换为世界坐标
|
|
||||||
* @param x 屏幕X坐标
|
|
||||||
* @param y 屏幕Y坐标
|
|
||||||
* @return 世界坐标
|
|
||||||
*/
|
|
||||||
Vec2 Camera::screenToWorld(float x, float y) const {
|
|
||||||
return screenToWorld(Vec2(x, y));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 将世界坐标转换为屏幕坐标
|
|
||||||
* @param x 世界X坐标
|
|
||||||
* @param y 世界Y坐标
|
|
||||||
* @return 屏幕坐标
|
|
||||||
*/
|
|
||||||
Vec2 Camera::worldToScreen(float x, float y) const {
|
|
||||||
return worldToScreen(Vec2(x, y));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 移动相机位置
|
|
||||||
* @param offset 位置偏移量
|
|
||||||
*
|
|
||||||
* 按指定偏移量移动相机位置,会标记视图矩阵为脏
|
|
||||||
*/
|
|
||||||
void Camera::move(const Vec2 &offset) {
|
|
||||||
position_ += offset;
|
|
||||||
viewDirty_ = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 移动相机位置
|
|
||||||
* @param x X方向偏移量
|
|
||||||
* @param y Y方向偏移量
|
|
||||||
*
|
|
||||||
* 按指定偏移量移动相机位置,会标记视图矩阵为脏
|
|
||||||
*/
|
|
||||||
void Camera::move(float x, float y) {
|
|
||||||
position_.x += x;
|
|
||||||
position_.y += y;
|
|
||||||
viewDirty_ = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置相机边界限制
|
|
||||||
* @param bounds 边界矩形
|
|
||||||
*
|
|
||||||
* 设置相机的移动边界,相机位置将被限制在此边界内
|
|
||||||
*/
|
|
||||||
void Camera::setBounds(const Rect &bounds) {
|
|
||||||
bounds_ = bounds;
|
|
||||||
hasBounds_ = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 清除相机边界限制
|
|
||||||
*
|
|
||||||
* 移除相机的移动边界限制
|
|
||||||
*/
|
|
||||||
void Camera::clearBounds() { hasBounds_ = false; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 将相机位置限制在边界内
|
|
||||||
*
|
|
||||||
* 如果设置了边界,将相机位置限制在边界矩形内
|
|
||||||
*/
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 将相机移动到目标位置
|
|
||||||
* @param target 目标位置
|
|
||||||
*
|
|
||||||
* 设置相机位置到指定的世界坐标
|
|
||||||
*/
|
|
||||||
void Camera::lookAt(const Vec2 &target) {
|
|
||||||
position_ = target;
|
|
||||||
viewDirty_ = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace frostbite2D
|
|
||||||
|
|
@ -1,313 +0,0 @@
|
||||||
#include <SDL.h>
|
|
||||||
#include <fostbite2D/render/opengl/gl_font_atlas.h>
|
|
||||||
#include <fstream>
|
|
||||||
#define STB_TRUETYPE_IMPLEMENTATION
|
|
||||||
#include <stb/stb_truetype.h>
|
|
||||||
#define STB_RECT_PACK_IMPLEMENTATION
|
|
||||||
#include <algorithm>
|
|
||||||
#include <stb/stb_rect_pack.h>
|
|
||||||
|
|
||||||
namespace frostbite2D {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 构造函数 - 初始化字体图集
|
|
||||||
// ============================================================================
|
|
||||||
/**
|
|
||||||
* @brief 构造函数,从字体文件初始化字体图集
|
|
||||||
* @param filepath 字体文件路径
|
|
||||||
* @param fontSize 字体大小(像素)
|
|
||||||
* @param useSDF 是否使用有符号距离场渲染
|
|
||||||
*/
|
|
||||||
GLFontAtlas::GLFontAtlas(const std::string &filepath, int fontSize, bool useSDF)
|
|
||||||
: fontSize_(fontSize), useSDF_(useSDF), currentY_(0), scale_(0.0f),
|
|
||||||
ascent_(0.0f), descent_(0.0f), lineGap_(0.0f) {
|
|
||||||
|
|
||||||
// 加载字体文件
|
|
||||||
std::ifstream file(filepath, std::ios::binary | std::ios::ate);
|
|
||||||
if (!file.is_open()) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to load font: %s",
|
|
||||||
filepath.c_str());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::streamsize size = file.tellg();
|
|
||||||
file.seekg(0, std::ios::beg);
|
|
||||||
fontData_.resize(size);
|
|
||||||
if (!file.read(reinterpret_cast<char *>(fontData_.data()), size)) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to read font file: %s",
|
|
||||||
filepath.c_str());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 初始化 stb_truetype
|
|
||||||
if (!stbtt_InitFont(&fontInfo_, fontData_.data(),
|
|
||||||
stbtt_GetFontOffsetForIndex(fontData_.data(), 0))) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to init font: %s",
|
|
||||||
filepath.c_str());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
scale_ = stbtt_ScaleForPixelHeight(&fontInfo_, static_cast<float>(fontSize_));
|
|
||||||
|
|
||||||
int ascent, descent, lineGap;
|
|
||||||
stbtt_GetFontVMetrics(&fontInfo_, &ascent, &descent, &lineGap);
|
|
||||||
ascent_ = static_cast<float>(ascent) * scale_;
|
|
||||||
descent_ = static_cast<float>(descent) * scale_;
|
|
||||||
lineGap_ = static_cast<float>(lineGap) * scale_;
|
|
||||||
|
|
||||||
createAtlas();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 析构函数
|
|
||||||
// ============================================================================
|
|
||||||
/**
|
|
||||||
* @brief 析构函数
|
|
||||||
*/
|
|
||||||
GLFontAtlas::~GLFontAtlas() = default;
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 获取字形 - 如果字形不存在则缓存它
|
|
||||||
// ============================================================================
|
|
||||||
/**
|
|
||||||
* @brief 获取字形信息,如果字形不存在则动态缓存
|
|
||||||
* @param codepoint Unicode码点
|
|
||||||
* @return 字形信息指针,如果获取失败返回nullptr
|
|
||||||
*/
|
|
||||||
const Glyph *GLFontAtlas::getGlyph(char32_t codepoint) const {
|
|
||||||
auto it = glyphs_.find(codepoint);
|
|
||||||
if (it == glyphs_.end()) {
|
|
||||||
cacheGlyph(codepoint);
|
|
||||||
it = glyphs_.find(codepoint);
|
|
||||||
}
|
|
||||||
return (it != glyphs_.end()) ? &it->second : nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 测量文本尺寸
|
|
||||||
// ============================================================================
|
|
||||||
/**
|
|
||||||
* @brief 测量文本渲染后的尺寸
|
|
||||||
* @param text 要测量的文本
|
|
||||||
* @return 文本的宽度和高度
|
|
||||||
*/
|
|
||||||
Vec2 GLFontAtlas::measureText(const std::string &text) {
|
|
||||||
float width = 0.0f;
|
|
||||||
float height = getAscent() - getDescent();
|
|
||||||
float currentWidth = 0.0f;
|
|
||||||
|
|
||||||
for (char c : text) {
|
|
||||||
char32_t codepoint = static_cast<char32_t>(static_cast<unsigned char>(c));
|
|
||||||
if (codepoint == '\n') {
|
|
||||||
width = std::max(width, currentWidth);
|
|
||||||
currentWidth = 0.0f;
|
|
||||||
height += getLineHeight();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Glyph *glyph = getGlyph(codepoint);
|
|
||||||
if (glyph) {
|
|
||||||
currentWidth += glyph->advance;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
width = std::max(width, currentWidth);
|
|
||||||
return Vec2(width, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 创建图集纹理 - 初始化空白纹理和矩形打包上下文
|
|
||||||
// ============================================================================
|
|
||||||
/**
|
|
||||||
* @brief 创建字体图集纹理,初始化空白纹理和矩形打包上下文
|
|
||||||
*/
|
|
||||||
void GLFontAtlas::createAtlas() {
|
|
||||||
// 统一使用 4 通道格式
|
|
||||||
int channels = 4;
|
|
||||||
std::vector<uint8_t> emptyData(ATLAS_WIDTH * ATLAS_HEIGHT * channels, 0);
|
|
||||||
texture_ = std::make_unique<GLTexture>(ATLAS_WIDTH, ATLAS_HEIGHT,
|
|
||||||
emptyData.data(), channels);
|
|
||||||
texture_->setFilter(true);
|
|
||||||
|
|
||||||
// 初始化矩形打包上下文
|
|
||||||
packNodes_.resize(ATLAS_WIDTH);
|
|
||||||
stbrp_init_target(&packContext_, ATLAS_WIDTH, ATLAS_HEIGHT, packNodes_.data(),
|
|
||||||
ATLAS_WIDTH);
|
|
||||||
|
|
||||||
// 预分配字形缓冲区
|
|
||||||
// 假设最大字形尺寸为 fontSize * fontSize * 4 (RGBA)
|
|
||||||
size_t maxGlyphSize = static_cast<size_t>(fontSize_ * fontSize_ * 4 * 4);
|
|
||||||
glyphBitmapCache_.reserve(maxGlyphSize);
|
|
||||||
glyphRgbaCache_.reserve(maxGlyphSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 缓存字形 - 渲染字形到图集并存储信息
|
|
||||||
// 使用 stb_rect_pack 进行矩形打包
|
|
||||||
// ============================================================================
|
|
||||||
/**
|
|
||||||
* @brief 缓存字形到图集,渲染字形位图并存储字形信息
|
|
||||||
* @param codepoint Unicode码点
|
|
||||||
*/
|
|
||||||
void GLFontAtlas::cacheGlyph(char32_t codepoint) const {
|
|
||||||
int advance = 0;
|
|
||||||
stbtt_GetCodepointHMetrics(&fontInfo_, static_cast<int>(codepoint), &advance,
|
|
||||||
nullptr);
|
|
||||||
float advancePx = advance * scale_;
|
|
||||||
|
|
||||||
if (useSDF_) {
|
|
||||||
constexpr int SDF_PADDING = 8;
|
|
||||||
constexpr unsigned char ONEDGE_VALUE = 128;
|
|
||||||
constexpr float PIXEL_DIST_SCALE = 64.0f;
|
|
||||||
|
|
||||||
int w = 0, h = 0, xoff = 0, yoff = 0;
|
|
||||||
unsigned char *sdf = stbtt_GetCodepointSDF(
|
|
||||||
&fontInfo_, scale_, static_cast<int>(codepoint), SDF_PADDING,
|
|
||||||
ONEDGE_VALUE, PIXEL_DIST_SCALE, &w, &h, &xoff, &yoff);
|
|
||||||
if (!sdf || w <= 0 || h <= 0) {
|
|
||||||
if (sdf)
|
|
||||||
stbtt_FreeSDF(sdf, nullptr);
|
|
||||||
Glyph glyph{};
|
|
||||||
glyph.advance = advancePx;
|
|
||||||
glyphs_[codepoint] = glyph;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
stbrp_rect rect;
|
|
||||||
rect.id = static_cast<int>(codepoint);
|
|
||||||
rect.w = w + PADDING * 2;
|
|
||||||
rect.h = h + PADDING * 2;
|
|
||||||
|
|
||||||
stbrp_pack_rects(&packContext_, &rect, 1);
|
|
||||||
if (!rect.was_packed) {
|
|
||||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Font atlas is full, cannot cache codepoint: %d",
|
|
||||||
static_cast<int>(codepoint));
|
|
||||||
stbtt_FreeSDF(sdf, nullptr);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int atlasX = rect.x + PADDING;
|
|
||||||
int atlasY = rect.y + PADDING;
|
|
||||||
|
|
||||||
Glyph glyph;
|
|
||||||
glyph.width = static_cast<float>(w);
|
|
||||||
glyph.height = static_cast<float>(h);
|
|
||||||
glyph.bearingX = static_cast<float>(xoff);
|
|
||||||
glyph.bearingY = static_cast<float>(yoff);
|
|
||||||
glyph.advance = advancePx;
|
|
||||||
|
|
||||||
// stb_rect_pack 使用左上角为原点,OpenGL纹理使用左下角为原点
|
|
||||||
// 需要翻转V坐标
|
|
||||||
float v0 = static_cast<float>(atlasY) / ATLAS_HEIGHT;
|
|
||||||
float v1 = static_cast<float>(atlasY + h) / ATLAS_HEIGHT;
|
|
||||||
glyph.u0 = static_cast<float>(atlasX) / ATLAS_WIDTH;
|
|
||||||
glyph.v0 = 1.0f - v1; // 翻转V坐标
|
|
||||||
glyph.u1 = static_cast<float>(atlasX + w) / ATLAS_WIDTH;
|
|
||||||
glyph.v1 = 1.0f - v0; // 翻转V坐标
|
|
||||||
|
|
||||||
glyphs_[codepoint] = glyph;
|
|
||||||
|
|
||||||
// 将 SDF 单通道数据转换为 RGBA 格式(统一格式)
|
|
||||||
size_t pixelCount = static_cast<size_t>(w) * static_cast<size_t>(h);
|
|
||||||
glyphRgbaCache_.resize(pixelCount * 4);
|
|
||||||
for (size_t i = 0; i < pixelCount; ++i) {
|
|
||||||
uint8_t alpha = sdf[i];
|
|
||||||
glyphRgbaCache_[i * 4 + 0] = 255; // R
|
|
||||||
glyphRgbaCache_[i * 4 + 1] = 255; // G
|
|
||||||
glyphRgbaCache_[i * 4 + 2] = 255; // B
|
|
||||||
glyphRgbaCache_[i * 4 + 3] = alpha; // A - SDF 值存储在 Alpha 通道
|
|
||||||
}
|
|
||||||
|
|
||||||
// 直接设置像素对齐为 4,无需查询当前状态
|
|
||||||
glBindTexture(GL_TEXTURE_2D, texture_->getTextureID());
|
|
||||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
|
|
||||||
// OpenGL纹理坐标原点在左下角,需要将Y坐标翻转
|
|
||||||
glTexSubImage2D(GL_TEXTURE_2D, 0, atlasX, ATLAS_HEIGHT - atlasY - h, w, h,
|
|
||||||
GL_RGBA, GL_UNSIGNED_BYTE, glyphRgbaCache_.data());
|
|
||||||
|
|
||||||
stbtt_FreeSDF(sdf, nullptr);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int x0 = 0, y0 = 0, x1 = 0, y1 = 0;
|
|
||||||
stbtt_GetCodepointBitmapBox(&fontInfo_, static_cast<int>(codepoint), scale_,
|
|
||||||
scale_, &x0, &y0, &x1, &y1);
|
|
||||||
int w = x1 - x0;
|
|
||||||
int h = y1 - y0;
|
|
||||||
int xoff = x0;
|
|
||||||
int yoff = y0;
|
|
||||||
|
|
||||||
if (w <= 0 || h <= 0) {
|
|
||||||
Glyph glyph{};
|
|
||||||
glyph.advance = advancePx;
|
|
||||||
glyphs_[codepoint] = glyph;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 使用预分配缓冲区
|
|
||||||
size_t pixelCount = static_cast<size_t>(w) * static_cast<size_t>(h);
|
|
||||||
glyphBitmapCache_.resize(pixelCount);
|
|
||||||
stbtt_MakeCodepointBitmap(&fontInfo_, glyphBitmapCache_.data(), w, h, w,
|
|
||||||
scale_, scale_, static_cast<int>(codepoint));
|
|
||||||
|
|
||||||
// 使用 stb_rect_pack 打包矩形
|
|
||||||
stbrp_rect rect;
|
|
||||||
rect.id = static_cast<int>(codepoint);
|
|
||||||
rect.w = w + PADDING * 2;
|
|
||||||
rect.h = h + PADDING * 2;
|
|
||||||
|
|
||||||
stbrp_pack_rects(&packContext_, &rect, 1);
|
|
||||||
|
|
||||||
if (!rect.was_packed) {
|
|
||||||
// 图集已满,无法缓存更多字形
|
|
||||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Font atlas is full, cannot cache codepoint: %d",
|
|
||||||
static_cast<int>(codepoint));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int atlasX = rect.x + PADDING;
|
|
||||||
int atlasY = rect.y + PADDING;
|
|
||||||
|
|
||||||
// 创建字形信息
|
|
||||||
Glyph glyph;
|
|
||||||
glyph.width = static_cast<float>(w);
|
|
||||||
glyph.height = static_cast<float>(h);
|
|
||||||
glyph.bearingX = static_cast<float>(xoff);
|
|
||||||
glyph.bearingY = static_cast<float>(yoff);
|
|
||||||
glyph.advance = advancePx;
|
|
||||||
|
|
||||||
// 计算纹理坐标(相对于图集)
|
|
||||||
// stb_rect_pack 使用左上角为原点,OpenGL纹理使用左下角为原点
|
|
||||||
// 需要翻转V坐标
|
|
||||||
float v0 = static_cast<float>(atlasY) / ATLAS_HEIGHT;
|
|
||||||
float v1 = static_cast<float>(atlasY + h) / ATLAS_HEIGHT;
|
|
||||||
glyph.u0 = static_cast<float>(atlasX) / ATLAS_WIDTH;
|
|
||||||
glyph.v0 = 1.0f - v1; // 翻转V坐标
|
|
||||||
glyph.u1 = static_cast<float>(atlasX + w) / ATLAS_WIDTH;
|
|
||||||
glyph.v1 = 1.0f - v0; // 翻转V坐标
|
|
||||||
|
|
||||||
// 存储字形
|
|
||||||
glyphs_[codepoint] = glyph;
|
|
||||||
|
|
||||||
// 将单通道字形数据转换为 RGBA 格式(白色字形,Alpha 通道存储灰度)
|
|
||||||
glyphRgbaCache_.resize(pixelCount * 4);
|
|
||||||
for (size_t i = 0; i < pixelCount; ++i) {
|
|
||||||
uint8_t alpha = glyphBitmapCache_[i];
|
|
||||||
glyphRgbaCache_[i * 4 + 0] = 255; // R
|
|
||||||
glyphRgbaCache_[i * 4 + 1] = 255; // G
|
|
||||||
glyphRgbaCache_[i * 4 + 2] = 255; // B
|
|
||||||
glyphRgbaCache_[i * 4 + 3] = alpha; // A
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新纹理 - 将字形数据上传到图集的指定位置
|
|
||||||
// 直接设置像素对齐为 4,无需查询当前状态
|
|
||||||
glBindTexture(GL_TEXTURE_2D, texture_->getTextureID());
|
|
||||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
|
|
||||||
// OpenGL纹理坐标原点在左下角,需要将Y坐标翻转
|
|
||||||
glTexSubImage2D(GL_TEXTURE_2D, 0, atlasX, ATLAS_HEIGHT - atlasY - h, w, h,
|
|
||||||
GL_RGBA, GL_UNSIGNED_BYTE, glyphRgbaCache_.data());
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace frostbite2D
|
|
||||||
|
|
@ -1,802 +0,0 @@
|
||||||
#include <SDL.h>
|
|
||||||
#include <cmath>
|
|
||||||
#include <cstring>
|
|
||||||
#include <fostbite2D/render/opengl/gl_renderer.h>
|
|
||||||
#include <fostbite2D/render/opengl/gl_texture.h>
|
|
||||||
#include <fostbite2D/render/shader/shader_manager.h>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace frostbite2D {
|
|
||||||
|
|
||||||
// VBO 初始大小(用于 VRAM 跟踪)
|
|
||||||
static constexpr size_t SHAPE_VBO_SIZE = 1024 * sizeof(float);
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// BlendMode 查找表 - 编译期构建,运行时 O(1) 查找
|
|
||||||
// ============================================================================
|
|
||||||
struct BlendState {
|
|
||||||
bool enable;
|
|
||||||
GLenum srcFactor;
|
|
||||||
GLenum dstFactor;
|
|
||||||
};
|
|
||||||
|
|
||||||
static constexpr BlendState BLEND_STATES[] = {
|
|
||||||
{false, 0, 0}, // BlendMode::None
|
|
||||||
{true, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA}, // BlendMode::Alpha
|
|
||||||
{true, GL_SRC_ALPHA, GL_ONE}, // BlendMode::Additive
|
|
||||||
{true, GL_DST_COLOR, GL_ONE_MINUS_SRC_ALPHA} // BlendMode::Multiply
|
|
||||||
};
|
|
||||||
|
|
||||||
static constexpr size_t BLEND_STATE_COUNT =
|
|
||||||
sizeof(BLEND_STATES) / sizeof(BLEND_STATES[0]);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 构造函数,初始化OpenGL渲染器成员变量
|
|
||||||
*/
|
|
||||||
GLRenderer::GLRenderer()
|
|
||||||
: window_(nullptr), shapeVao_(0), shapeVbo_(0), lineVao_(0), lineVbo_(0),
|
|
||||||
vsync_(true), shapeVertexCount_(0), currentShapeMode_(GL_TRIANGLES),
|
|
||||||
lineVertexCount_(0), currentLineWidth_(1.0f) {
|
|
||||||
resetStats();
|
|
||||||
for (auto &v : shapeVertexCache_) {
|
|
||||||
v = {0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f};
|
|
||||||
}
|
|
||||||
for (auto &v : lineVertexCache_) {
|
|
||||||
v = {0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 析构函数,调用shutdown释放资源
|
|
||||||
*/
|
|
||||||
GLRenderer::~GLRenderer() { shutdown(); }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 初始化OpenGL渲染器
|
|
||||||
* @param window SDL窗口指针
|
|
||||||
* @return 初始化成功返回true,失败返回false
|
|
||||||
*/
|
|
||||||
bool GLRenderer::init(SDL_Window *window) {
|
|
||||||
window_ = window;
|
|
||||||
|
|
||||||
// Switch: GL 上下文已通过 SDL2 + EGL 初始化,无需 glewInit()
|
|
||||||
|
|
||||||
// 初始化精灵批渲染器
|
|
||||||
if (!spriteBatch_.init()) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Failed to initialize sprite batch");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 初始化形状渲染
|
|
||||||
initShapeRendering();
|
|
||||||
|
|
||||||
// 设置 OpenGL 状态
|
|
||||||
glEnable(GL_BLEND);
|
|
||||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
|
||||||
|
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "OpenGL Renderer initialized");
|
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "OpenGL Version: %s",
|
|
||||||
reinterpret_cast<const char *>(glGetString(GL_VERSION)));
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 关闭渲染器,释放所有GPU资源
|
|
||||||
*/
|
|
||||||
void GLRenderer::shutdown() {
|
|
||||||
spriteBatch_.shutdown();
|
|
||||||
|
|
||||||
if (lineVbo_ != 0) {
|
|
||||||
glDeleteBuffers(1, &lineVbo_);
|
|
||||||
lineVbo_ = 0;
|
|
||||||
}
|
|
||||||
if (lineVao_ != 0) {
|
|
||||||
glDeleteVertexArrays(1, &lineVao_);
|
|
||||||
lineVao_ = 0;
|
|
||||||
}
|
|
||||||
if (shapeVbo_ != 0) {
|
|
||||||
glDeleteBuffers(1, &shapeVbo_);
|
|
||||||
shapeVbo_ = 0;
|
|
||||||
}
|
|
||||||
if (shapeVao_ != 0) {
|
|
||||||
glDeleteVertexArrays(1, &shapeVao_);
|
|
||||||
shapeVao_ = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 开始新帧,清除颜色缓冲区并重置统计信息
|
|
||||||
* @param clearColor 清屏颜色
|
|
||||||
*/
|
|
||||||
void GLRenderer::beginFrame(const Color &clearColor) {
|
|
||||||
glClearColor(clearColor.r, clearColor.g, clearColor.b, clearColor.a);
|
|
||||||
glClear(GL_COLOR_BUFFER_BIT);
|
|
||||||
resetStats();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 结束当前帧,刷新所有待处理的渲染批次
|
|
||||||
*/
|
|
||||||
void GLRenderer::endFrame() {
|
|
||||||
// 刷新所有待处理的形状批次
|
|
||||||
flushShapeBatch();
|
|
||||||
// 刷新所有待处理的线条批次
|
|
||||||
flushLineBatch();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置视口区域
|
|
||||||
* @param x 视口左下角X坐标
|
|
||||||
* @param y 视口左下角Y坐标
|
|
||||||
* @param width 视口宽度
|
|
||||||
* @param height 视口高度
|
|
||||||
*/
|
|
||||||
void GLRenderer::setViewport(int x, int y, int width, int height) {
|
|
||||||
glViewport(x, y, width, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置垂直同步
|
|
||||||
* @param enabled true启用垂直同步,false禁用
|
|
||||||
*/
|
|
||||||
void GLRenderer::setVSync(bool enabled) {
|
|
||||||
vsync_ = enabled;
|
|
||||||
// 通过SDL设置垂直同步
|
|
||||||
SDL_GL_SetSwapInterval(enabled ? 1 : 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置混合模式
|
|
||||||
* @param mode 混合模式枚举值
|
|
||||||
*/
|
|
||||||
void GLRenderer::setBlendMode(BlendMode mode) {
|
|
||||||
// 状态缓存检查,避免冗余 GL 调用
|
|
||||||
if (cachedBlendMode_ == mode) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
cachedBlendMode_ = mode;
|
|
||||||
|
|
||||||
// 使用查找表替代 switch
|
|
||||||
size_t index = static_cast<size_t>(mode);
|
|
||||||
if (index >= BLEND_STATE_COUNT) {
|
|
||||||
index = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
const BlendState &state = BLEND_STATES[index];
|
|
||||||
if (state.enable) {
|
|
||||||
if (!blendEnabled_) {
|
|
||||||
glEnable(GL_BLEND);
|
|
||||||
blendEnabled_ = true;
|
|
||||||
}
|
|
||||||
glBlendFunc(state.srcFactor, state.dstFactor);
|
|
||||||
} else {
|
|
||||||
if (blendEnabled_) {
|
|
||||||
glDisable(GL_BLEND);
|
|
||||||
blendEnabled_ = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置视图投影矩阵
|
|
||||||
* @param matrix 4x4视图投影矩阵
|
|
||||||
*/
|
|
||||||
void GLRenderer::setViewProjection(const glm::mat4 &matrix) {
|
|
||||||
viewProjection_ = matrix;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 压入变换矩阵到变换栈
|
|
||||||
* @param transform 变换矩阵
|
|
||||||
*/
|
|
||||||
void GLRenderer::pushTransform(const glm::mat4 &transform) {
|
|
||||||
if (transformStack_.empty()) {
|
|
||||||
transformStack_.push_back(transform);
|
|
||||||
} else {
|
|
||||||
transformStack_.push_back(transformStack_.back() * transform);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 从变换栈弹出顶部变换矩阵
|
|
||||||
*/
|
|
||||||
void GLRenderer::popTransform() {
|
|
||||||
if (!transformStack_.empty()) {
|
|
||||||
transformStack_.pop_back();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取当前累积的变换矩阵
|
|
||||||
* @return 当前变换矩阵,如果栈为空则返回单位矩阵
|
|
||||||
*/
|
|
||||||
glm::mat4 GLRenderer::getCurrentTransform() const {
|
|
||||||
if (transformStack_.empty()) {
|
|
||||||
return glm::mat4(1.0f);
|
|
||||||
}
|
|
||||||
return transformStack_.back();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 创建纹理对象
|
|
||||||
* @param width 纹理宽度
|
|
||||||
* @param height 纹理高度
|
|
||||||
* @param pixels 像素数据指针
|
|
||||||
* @param channels 颜色通道数
|
|
||||||
* @return 创建的纹理智能指针
|
|
||||||
*/
|
|
||||||
Ptr<Texture> GLRenderer::createTexture(int width, int height,
|
|
||||||
const uint8_t *pixels, int channels) {
|
|
||||||
return makePtr<GLTexture>(width, height, pixels, channels);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 从文件加载纹理
|
|
||||||
* @param filepath 纹理文件路径
|
|
||||||
* @return 加载的纹理智能指针
|
|
||||||
*/
|
|
||||||
Ptr<Texture> GLRenderer::loadTexture(const std::string &filepath) {
|
|
||||||
return makePtr<GLTexture>(filepath);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 开始精灵批处理
|
|
||||||
*/
|
|
||||||
void GLRenderer::beginSpriteBatch() { spriteBatch_.begin(viewProjection_); }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 绘制精灵(带完整参数)
|
|
||||||
* @param texture 纹理引用
|
|
||||||
* @param destRect 目标矩形(屏幕坐标)
|
|
||||||
* @param srcRect 源矩形(纹理坐标)
|
|
||||||
* @param tint 着色颜色
|
|
||||||
* @param rotation 旋转角度(度)
|
|
||||||
* @param anchor 锚点位置(0-1范围)
|
|
||||||
*/
|
|
||||||
void GLRenderer::drawSprite(const Texture &texture, const Rect &destRect,
|
|
||||||
const Rect &srcRect, const Color &tint,
|
|
||||||
float rotation, const Vec2 &anchor) {
|
|
||||||
GLSpriteBatch::SpriteData data;
|
|
||||||
data.position = glm::vec2(destRect.origin.x, destRect.origin.y);
|
|
||||||
data.size = glm::vec2(destRect.size.width, destRect.size.height);
|
|
||||||
|
|
||||||
Texture *tex = const_cast<Texture *>(&texture);
|
|
||||||
float texW = static_cast<float>(tex->getWidth());
|
|
||||||
float texH = static_cast<float>(tex->getHeight());
|
|
||||||
|
|
||||||
// 纹理坐标计算
|
|
||||||
float u1 = srcRect.origin.x / texW;
|
|
||||||
float u2 = (srcRect.origin.x + srcRect.size.width) / texW;
|
|
||||||
float v1 = srcRect.origin.y / texH;
|
|
||||||
float v2 = (srcRect.origin.y + srcRect.size.height) / texH;
|
|
||||||
|
|
||||||
data.texCoordMin = glm::vec2(glm::min(u1, u2), glm::min(v1, v2));
|
|
||||||
data.texCoordMax = glm::vec2(glm::max(u1, u2), glm::max(v1, v2));
|
|
||||||
|
|
||||||
data.color = glm::vec4(tint.r, tint.g, tint.b, tint.a);
|
|
||||||
data.rotation = rotation * 3.14159f / 180.0f;
|
|
||||||
data.anchor = glm::vec2(anchor.x, anchor.y);
|
|
||||||
data.isSDF = false;
|
|
||||||
|
|
||||||
spriteBatch_.draw(texture, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 绘制精灵(简化版本)
|
|
||||||
* @param texture 纹理引用
|
|
||||||
* @param position 绘制位置
|
|
||||||
* @param tint 着色颜色
|
|
||||||
*/
|
|
||||||
void GLRenderer::drawSprite(const Texture &texture, const Vec2 &position,
|
|
||||||
const Color &tint) {
|
|
||||||
Rect destRect(position.x, position.y, static_cast<float>(texture.getWidth()),
|
|
||||||
static_cast<float>(texture.getHeight()));
|
|
||||||
Rect srcRect(0, 0, static_cast<float>(texture.getWidth()),
|
|
||||||
static_cast<float>(texture.getHeight()));
|
|
||||||
drawSprite(texture, destRect, srcRect, tint, 0.0f, Vec2(0, 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 结束精灵批处理并提交绘制
|
|
||||||
*/
|
|
||||||
void GLRenderer::endSpriteBatch() {
|
|
||||||
spriteBatch_.end();
|
|
||||||
stats_.drawCalls += spriteBatch_.getDrawCallCount();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 绘制线段
|
|
||||||
* @param start 起点坐标
|
|
||||||
* @param end 终点坐标
|
|
||||||
* @param color 线条颜色
|
|
||||||
* @param width 线条宽度
|
|
||||||
*/
|
|
||||||
void GLRenderer::drawLine(const Vec2 &start, const Vec2 &end,
|
|
||||||
const Color &color, float width) {
|
|
||||||
// 如果线宽改变,需要先刷新线条批次
|
|
||||||
if (width != currentLineWidth_) {
|
|
||||||
flushLineBatch();
|
|
||||||
currentLineWidth_ = width;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 添加两个顶点到线条缓冲区
|
|
||||||
addLineVertex(start.x, start.y, color);
|
|
||||||
addLineVertex(end.x, end.y, color);
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLRenderer::drawRect(const Rect &rect, const Color &color, float width) {
|
|
||||||
// 如果线宽改变,需要先刷新线条批次
|
|
||||||
if (width != currentLineWidth_) {
|
|
||||||
flushLineBatch();
|
|
||||||
currentLineWidth_ = width;
|
|
||||||
}
|
|
||||||
|
|
||||||
float x1 = rect.origin.x;
|
|
||||||
float y1 = rect.origin.y;
|
|
||||||
float x2 = rect.origin.x + rect.size.width;
|
|
||||||
float y2 = rect.origin.y + rect.size.height;
|
|
||||||
|
|
||||||
// 4条线段 = 8个顶点
|
|
||||||
// 上边
|
|
||||||
addLineVertex(x1, y1, color);
|
|
||||||
addLineVertex(x2, y1, color);
|
|
||||||
// 右边
|
|
||||||
addLineVertex(x2, y1, color);
|
|
||||||
addLineVertex(x2, y2, color);
|
|
||||||
// 下边
|
|
||||||
addLineVertex(x2, y2, color);
|
|
||||||
addLineVertex(x1, y2, color);
|
|
||||||
// 左边
|
|
||||||
addLineVertex(x1, y2, color);
|
|
||||||
addLineVertex(x1, y1, color);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 填充矩形
|
|
||||||
* @param rect 矩形区域
|
|
||||||
* @param color 填充颜色
|
|
||||||
*/
|
|
||||||
void GLRenderer::fillRect(const Rect &rect, const Color &color) {
|
|
||||||
// 提交当前批次(如果模式不同)
|
|
||||||
submitShapeBatch(GL_TRIANGLES);
|
|
||||||
|
|
||||||
// 添加两个三角形组成矩形(6个顶点)
|
|
||||||
float x1 = rect.origin.x;
|
|
||||||
float y1 = rect.origin.y;
|
|
||||||
float x2 = rect.origin.x + rect.size.width;
|
|
||||||
float y2 = rect.origin.y + rect.size.height;
|
|
||||||
|
|
||||||
// 三角形1: (x1,y1), (x2,y1), (x2,y2)
|
|
||||||
addShapeVertex(x1, y1, color);
|
|
||||||
addShapeVertex(x2, y1, color);
|
|
||||||
addShapeVertex(x2, y2, color);
|
|
||||||
|
|
||||||
// 三角形2: (x1,y1), (x2,y2), (x1,y2)
|
|
||||||
addShapeVertex(x1, y1, color);
|
|
||||||
addShapeVertex(x2, y2, color);
|
|
||||||
addShapeVertex(x1, y2, color);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 绘制圆形边框
|
|
||||||
* @param center 圆心坐标
|
|
||||||
* @param radius 半径
|
|
||||||
* @param color 边框颜色
|
|
||||||
* @param segments 分段数
|
|
||||||
* @param width 线条宽度
|
|
||||||
*/
|
|
||||||
void GLRenderer::drawCircle(const Vec2 ¢er, float radius,
|
|
||||||
const Color &color, int segments, float width) {
|
|
||||||
// 限制段数不超过缓存大小
|
|
||||||
if (segments > static_cast<int>(MAX_CIRCLE_SEGMENTS)) {
|
|
||||||
segments = static_cast<int>(MAX_CIRCLE_SEGMENTS);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果线宽改变,需要先刷新线条批次
|
|
||||||
if (width != currentLineWidth_) {
|
|
||||||
flushLineBatch();
|
|
||||||
currentLineWidth_ = width;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 使用线条批处理绘制圆形
|
|
||||||
for (int i = 0; i < segments; ++i) {
|
|
||||||
float angle1 =
|
|
||||||
2.0f * 3.14159f * static_cast<float>(i) / static_cast<float>(segments);
|
|
||||||
float angle2 = 2.0f * 3.14159f * static_cast<float>(i + 1) /
|
|
||||||
static_cast<float>(segments);
|
|
||||||
|
|
||||||
addLineVertex(center.x + radius * cosf(angle1),
|
|
||||||
center.y + radius * sinf(angle1), color);
|
|
||||||
addLineVertex(center.x + radius * cosf(angle2),
|
|
||||||
center.y + radius * sinf(angle2), color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 填充圆形
|
|
||||||
* @param center 圆心坐标
|
|
||||||
* @param radius 半径
|
|
||||||
* @param color 填充颜色
|
|
||||||
* @param segments 分段数
|
|
||||||
*/
|
|
||||||
void GLRenderer::fillCircle(const Vec2 ¢er, float radius,
|
|
||||||
const Color &color, int segments) {
|
|
||||||
// 限制段数不超过缓存大小
|
|
||||||
if (segments > static_cast<int>(MAX_CIRCLE_SEGMENTS)) {
|
|
||||||
segments = static_cast<int>(MAX_CIRCLE_SEGMENTS);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 提交当前批次(如果模式不同)
|
|
||||||
submitShapeBatch(GL_TRIANGLES);
|
|
||||||
|
|
||||||
// 使用三角形扇形填充圆
|
|
||||||
// 中心点 + 边缘点
|
|
||||||
for (int i = 0; i < segments; ++i) {
|
|
||||||
float angle1 =
|
|
||||||
2.0f * 3.14159f * static_cast<float>(i) / static_cast<float>(segments);
|
|
||||||
float angle2 = 2.0f * 3.14159f * static_cast<float>(i + 1) /
|
|
||||||
static_cast<float>(segments);
|
|
||||||
|
|
||||||
// 每个三角形:中心 -> 边缘点1 -> 边缘点2
|
|
||||||
addShapeVertex(center.x, center.y, color);
|
|
||||||
addShapeVertex(center.x + radius * cosf(angle1),
|
|
||||||
center.y + radius * sinf(angle1), color);
|
|
||||||
addShapeVertex(center.x + radius * cosf(angle2),
|
|
||||||
center.y + radius * sinf(angle2), color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 绘制三角形边框
|
|
||||||
* @param p1 第一个顶点
|
|
||||||
* @param p2 第二个顶点
|
|
||||||
* @param p3 第三个顶点
|
|
||||||
* @param color 边框颜色
|
|
||||||
* @param width 线条宽度
|
|
||||||
*/
|
|
||||||
void GLRenderer::drawTriangle(const Vec2 &p1, const Vec2 &p2, const Vec2 &p3,
|
|
||||||
const Color &color, float width) {
|
|
||||||
drawLine(p1, p2, color, width);
|
|
||||||
drawLine(p2, p3, color, width);
|
|
||||||
drawLine(p3, p1, color, width);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 填充三角形
|
|
||||||
* @param p1 第一个顶点
|
|
||||||
* @param p2 第二个顶点
|
|
||||||
* @param p3 第三个顶点
|
|
||||||
* @param color 填充颜色
|
|
||||||
*/
|
|
||||||
void GLRenderer::fillTriangle(const Vec2 &p1, const Vec2 &p2, const Vec2 &p3,
|
|
||||||
const Color &color) {
|
|
||||||
submitShapeBatch(GL_TRIANGLES);
|
|
||||||
|
|
||||||
addShapeVertex(p1.x, p1.y, color);
|
|
||||||
addShapeVertex(p2.x, p2.y, color);
|
|
||||||
addShapeVertex(p3.x, p3.y, color);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 绘制多边形边框
|
|
||||||
* @param points 顶点数组
|
|
||||||
* @param color 边框颜色
|
|
||||||
* @param width 线条宽度
|
|
||||||
*/
|
|
||||||
void GLRenderer::drawPolygon(const std::vector<Vec2> &points,
|
|
||||||
const Color &color, float width) {
|
|
||||||
if (points.size() < 2)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// 如果线宽改变,需要先刷新线条批次
|
|
||||||
if (width != currentLineWidth_) {
|
|
||||||
flushLineBatch();
|
|
||||||
currentLineWidth_ = width;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 绘制所有边
|
|
||||||
for (size_t i = 0; i < points.size(); ++i) {
|
|
||||||
const Vec2 &p1 = points[i];
|
|
||||||
const Vec2 &p2 = points[(i + 1) % points.size()];
|
|
||||||
addLineVertex(p1.x, p1.y, color);
|
|
||||||
addLineVertex(p2.x, p2.y, color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 填充多边形
|
|
||||||
* @param points 顶点数组
|
|
||||||
* @param color 填充颜色
|
|
||||||
*/
|
|
||||||
void GLRenderer::fillPolygon(const std::vector<Vec2> &points,
|
|
||||||
const Color &color) {
|
|
||||||
if (points.size() < 3)
|
|
||||||
return;
|
|
||||||
|
|
||||||
submitShapeBatch(GL_TRIANGLES);
|
|
||||||
|
|
||||||
// 使用三角形扇形填充
|
|
||||||
// 从第一个点开始,每两个相邻点组成一个三角形
|
|
||||||
for (size_t i = 1; i < points.size() - 1; ++i) {
|
|
||||||
addShapeVertex(points[0].x, points[0].y, color);
|
|
||||||
addShapeVertex(points[i].x, points[i].y, color);
|
|
||||||
addShapeVertex(points[i + 1].x, points[i + 1].y, color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 重置渲染统计信息
|
|
||||||
*/
|
|
||||||
void GLRenderer::resetStats() { stats_ = RenderStats{}; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 绘制文本(使用Vec2位置)
|
|
||||||
* @param font 字体图集引用
|
|
||||||
* @param text 文本内容
|
|
||||||
* @param position 绘制位置
|
|
||||||
* @param color 文本颜色
|
|
||||||
*/
|
|
||||||
void GLRenderer::drawText(const FontAtlas &font, const std::string &text,
|
|
||||||
const Vec2 &position, const Color &color) {
|
|
||||||
drawText(font, text, position.x, position.y, color);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 绘制文本(使用浮点坐标)
|
|
||||||
* @param font 字体图集引用
|
|
||||||
* @param text 文本内容
|
|
||||||
* @param x X坐标
|
|
||||||
* @param y Y坐标
|
|
||||||
* @param color 文本颜色
|
|
||||||
*
|
|
||||||
* 注意:此方法需要在 beginSpriteBatch() 和 endSpriteBatch() 之间调用
|
|
||||||
*/
|
|
||||||
void GLRenderer::drawText(const FontAtlas &font, const std::string &text,
|
|
||||||
float x, float y, const Color &color) {
|
|
||||||
// 获取字体纹理
|
|
||||||
Texture *texture = font.getTexture();
|
|
||||||
if (!texture) {
|
|
||||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "drawText: font texture is null");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 调试日志
|
|
||||||
static bool firstCall = true;
|
|
||||||
if (firstCall) {
|
|
||||||
SDL_Log("drawText called, texture size: %dx%d", texture->getWidth(), texture->getHeight());
|
|
||||||
SDL_Log("Font ascent: %f, line height: %f", font.getAscent(), font.getLineHeight());
|
|
||||||
firstCall = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
float cursorX = x;
|
|
||||||
float cursorY = y;
|
|
||||||
float baselineY = cursorY + font.getAscent();
|
|
||||||
int charCount = 0;
|
|
||||||
|
|
||||||
for (char c : text) {
|
|
||||||
char32_t codepoint = static_cast<char32_t>(static_cast<unsigned char>(c));
|
|
||||||
if (codepoint == '\n') {
|
|
||||||
cursorX = x;
|
|
||||||
cursorY += font.getLineHeight();
|
|
||||||
baselineY = cursorY + font.getAscent();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Glyph *glyph = font.getGlyph(codepoint);
|
|
||||||
if (glyph) {
|
|
||||||
float penX = cursorX;
|
|
||||||
cursorX += glyph->advance;
|
|
||||||
|
|
||||||
if (glyph->width <= 0.0f || glyph->height <= 0.0f) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 计算字形位置(与 Extra2D-dev 一致)
|
|
||||||
float xPos = penX + glyph->bearingX;
|
|
||||||
float yPos = baselineY + glyph->bearingY; // 注意是 +bearingY
|
|
||||||
|
|
||||||
GLSpriteBatch::SpriteData data;
|
|
||||||
data.position = glm::vec2(xPos, yPos);
|
|
||||||
data.size = glm::vec2(glyph->width, glyph->height);
|
|
||||||
data.texCoordMin = glm::vec2(glyph->u0, glyph->v0);
|
|
||||||
data.texCoordMax = glm::vec2(glyph->u1, glyph->v1);
|
|
||||||
data.color = glm::vec4(color.r, color.g, color.b, color.a);
|
|
||||||
data.rotation = 0.0f;
|
|
||||||
data.anchor = glm::vec2(0.0f, 0.0f); // 锚点在左上角
|
|
||||||
data.isSDF = font.isSDF();
|
|
||||||
|
|
||||||
// 调试第一个字符
|
|
||||||
if (charCount == 0) {
|
|
||||||
SDL_Log("First char: '%c' at (%.1f, %.1f), size: %.1fx%.1f, tex: (%.2f,%.2f)-(%.2f,%.2f)",
|
|
||||||
c, xPos, yPos, glyph->width, glyph->height,
|
|
||||||
glyph->u0, glyph->v0, glyph->u1, glyph->v1);
|
|
||||||
}
|
|
||||||
|
|
||||||
spriteBatch_.draw(*texture, data);
|
|
||||||
charCount++;
|
|
||||||
} else {
|
|
||||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Glyph not found for codepoint: %d", codepoint);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (charCount > 0) {
|
|
||||||
SDL_Log("drawText rendered %d characters", charCount);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 初始化形状渲染所需的OpenGL资源(VAO、VBO、着色器)
|
|
||||||
*/
|
|
||||||
void GLRenderer::initShapeRendering() {
|
|
||||||
// 从文件加载形状着色器
|
|
||||||
shapeShader_ = ShaderManager::getInstance().loadFromFile(
|
|
||||||
"shape", "shaders/shape.vert", "shaders/shape.frag");
|
|
||||||
if (!shapeShader_) {
|
|
||||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Failed to load shape shader from files");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建形状 VAO 和 VBO
|
|
||||||
glGenVertexArrays(1, &shapeVao_);
|
|
||||||
glGenBuffers(1, &shapeVbo_);
|
|
||||||
|
|
||||||
glBindVertexArray(shapeVao_);
|
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, shapeVbo_);
|
|
||||||
glBufferData(GL_ARRAY_BUFFER, MAX_SHAPE_VERTICES * sizeof(ShapeVertex),
|
|
||||||
nullptr, GL_DYNAMIC_DRAW);
|
|
||||||
|
|
||||||
// 位置属性 (location = 0)
|
|
||||||
glEnableVertexAttribArray(0);
|
|
||||||
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(ShapeVertex),
|
|
||||||
reinterpret_cast<void *>(offsetof(ShapeVertex, x)));
|
|
||||||
|
|
||||||
// 颜色属性 (location = 1)
|
|
||||||
glEnableVertexAttribArray(1);
|
|
||||||
glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, sizeof(ShapeVertex),
|
|
||||||
reinterpret_cast<void *>(offsetof(ShapeVertex, r)));
|
|
||||||
|
|
||||||
glBindVertexArray(0);
|
|
||||||
|
|
||||||
// 创建线条专用 VAO 和 VBO
|
|
||||||
glGenVertexArrays(1, &lineVao_);
|
|
||||||
glGenBuffers(1, &lineVbo_);
|
|
||||||
|
|
||||||
glBindVertexArray(lineVao_);
|
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, lineVbo_);
|
|
||||||
glBufferData(GL_ARRAY_BUFFER, MAX_LINE_VERTICES * sizeof(ShapeVertex),
|
|
||||||
nullptr, GL_DYNAMIC_DRAW);
|
|
||||||
|
|
||||||
// 位置属性 (location = 0)
|
|
||||||
glEnableVertexAttribArray(0);
|
|
||||||
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(ShapeVertex),
|
|
||||||
reinterpret_cast<void *>(offsetof(ShapeVertex, x)));
|
|
||||||
|
|
||||||
// 颜色属性 (location = 1)
|
|
||||||
glEnableVertexAttribArray(1);
|
|
||||||
glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, sizeof(ShapeVertex),
|
|
||||||
reinterpret_cast<void *>(offsetof(ShapeVertex, r)));
|
|
||||||
|
|
||||||
glBindVertexArray(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 添加形状顶点到缓存
|
|
||||||
* @param x X坐标
|
|
||||||
* @param y Y坐标
|
|
||||||
* @param color 顶点颜色
|
|
||||||
*/
|
|
||||||
void GLRenderer::addShapeVertex(float x, float y, const Color &color) {
|
|
||||||
if (shapeVertexCount_ >= MAX_SHAPE_VERTICES) {
|
|
||||||
flushShapeBatch();
|
|
||||||
}
|
|
||||||
|
|
||||||
glm::vec4 pos(x, y, 0.0f, 1.0f);
|
|
||||||
if (!transformStack_.empty()) {
|
|
||||||
pos = transformStack_.back() * pos;
|
|
||||||
}
|
|
||||||
|
|
||||||
ShapeVertex &v = shapeVertexCache_[shapeVertexCount_++];
|
|
||||||
v.x = pos.x;
|
|
||||||
v.y = pos.y;
|
|
||||||
v.r = color.r;
|
|
||||||
v.g = color.g;
|
|
||||||
v.b = color.b;
|
|
||||||
v.a = color.a;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 添加线条顶点到缓存
|
|
||||||
* @param x X坐标
|
|
||||||
* @param y Y坐标
|
|
||||||
* @param color 顶点颜色
|
|
||||||
*/
|
|
||||||
void GLRenderer::addLineVertex(float x, float y, const Color &color) {
|
|
||||||
if (lineVertexCount_ >= MAX_LINE_VERTICES) {
|
|
||||||
flushLineBatch();
|
|
||||||
}
|
|
||||||
|
|
||||||
glm::vec4 pos(x, y, 0.0f, 1.0f);
|
|
||||||
if (!transformStack_.empty()) {
|
|
||||||
pos = transformStack_.back() * pos;
|
|
||||||
}
|
|
||||||
|
|
||||||
ShapeVertex &v = lineVertexCache_[lineVertexCount_++];
|
|
||||||
v.x = pos.x;
|
|
||||||
v.y = pos.y;
|
|
||||||
v.r = color.r;
|
|
||||||
v.g = color.g;
|
|
||||||
v.b = color.b;
|
|
||||||
v.a = color.a;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 提交形状批次(如果需要切换绘制模式)
|
|
||||||
* @param mode OpenGL绘制模式
|
|
||||||
*/
|
|
||||||
void GLRenderer::submitShapeBatch(GLenum mode) {
|
|
||||||
if (shapeVertexCount_ == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// 如果模式改变,先刷新
|
|
||||||
if (currentShapeMode_ != mode && shapeVertexCount_ > 0) {
|
|
||||||
flushShapeBatch();
|
|
||||||
}
|
|
||||||
currentShapeMode_ = mode;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 刷新形状批次,执行实际的OpenGL绘制调用
|
|
||||||
*/
|
|
||||||
void GLRenderer::flushShapeBatch() {
|
|
||||||
if (shapeVertexCount_ == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (shapeShader_) {
|
|
||||||
shapeShader_->bind();
|
|
||||||
shapeShader_->setMat4("u_viewProjection", viewProjection_);
|
|
||||||
}
|
|
||||||
|
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, shapeVbo_);
|
|
||||||
glBufferSubData(GL_ARRAY_BUFFER, 0, shapeVertexCount_ * sizeof(ShapeVertex),
|
|
||||||
shapeVertexCache_.data());
|
|
||||||
|
|
||||||
glBindVertexArray(shapeVao_);
|
|
||||||
glDrawArrays(currentShapeMode_, 0, static_cast<GLsizei>(shapeVertexCount_));
|
|
||||||
|
|
||||||
stats_.drawCalls++;
|
|
||||||
stats_.triangleCount += static_cast<uint32_t>(shapeVertexCount_ / 3);
|
|
||||||
|
|
||||||
shapeVertexCount_ = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 刷新线条批次,执行实际的OpenGL绘制调用
|
|
||||||
*/
|
|
||||||
void GLRenderer::flushLineBatch() {
|
|
||||||
if (lineVertexCount_ == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// 先刷新形状批次
|
|
||||||
flushShapeBatch();
|
|
||||||
|
|
||||||
glLineWidth(currentLineWidth_);
|
|
||||||
if (shapeShader_) {
|
|
||||||
shapeShader_->bind();
|
|
||||||
shapeShader_->setMat4("u_viewProjection", viewProjection_);
|
|
||||||
}
|
|
||||||
|
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, lineVbo_);
|
|
||||||
glBufferSubData(GL_ARRAY_BUFFER, 0, lineVertexCount_ * sizeof(ShapeVertex),
|
|
||||||
lineVertexCache_.data());
|
|
||||||
|
|
||||||
glBindVertexArray(lineVao_);
|
|
||||||
glDrawArrays(GL_LINES, 0, static_cast<GLsizei>(lineVertexCount_));
|
|
||||||
|
|
||||||
stats_.drawCalls++;
|
|
||||||
|
|
||||||
lineVertexCount_ = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace frostbite2D
|
|
||||||
|
|
@ -1,355 +0,0 @@
|
||||||
#include <SDL.h>
|
|
||||||
#include <fostbite2D/render/opengl/gl_shader.h>
|
|
||||||
|
|
||||||
namespace frostbite2D {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 构造函数,初始化着色器程序ID为0
|
|
||||||
*/
|
|
||||||
GLShader::GLShader() : programID_(0) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 析构函数,删除OpenGL着色器程序
|
|
||||||
*/
|
|
||||||
GLShader::~GLShader() {
|
|
||||||
if (programID_ != 0) {
|
|
||||||
glDeleteProgram(programID_);
|
|
||||||
programID_ = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 绑定Shader程序
|
|
||||||
*/
|
|
||||||
void GLShader::bind() const { glUseProgram(programID_); }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 解绑Shader程序
|
|
||||||
*/
|
|
||||||
void GLShader::unbind() const { glUseProgram(0); }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置布尔类型uniform变量
|
|
||||||
* @param name uniform变量名
|
|
||||||
* @param value 布尔值
|
|
||||||
*/
|
|
||||||
void GLShader::setBool(const std::string &name, bool value) {
|
|
||||||
glUniform1i(getUniformLocation(name), value ? 1 : 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置整数类型uniform变量
|
|
||||||
* @param name uniform变量名
|
|
||||||
* @param value 整数值
|
|
||||||
*/
|
|
||||||
void GLShader::setInt(const std::string &name, int value) {
|
|
||||||
glUniform1i(getUniformLocation(name), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置浮点类型uniform变量
|
|
||||||
* @param name uniform变量名
|
|
||||||
* @param value 浮点值
|
|
||||||
*/
|
|
||||||
void GLShader::setFloat(const std::string &name, float value) {
|
|
||||||
glUniform1f(getUniformLocation(name), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置二维向量类型uniform变量
|
|
||||||
* @param name uniform变量名
|
|
||||||
* @param value 二维向量值
|
|
||||||
*/
|
|
||||||
void GLShader::setVec2(const std::string &name, const glm::vec2 &value) {
|
|
||||||
glUniform2fv(getUniformLocation(name), 1, &value[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置三维向量类型uniform变量
|
|
||||||
* @param name uniform变量名
|
|
||||||
* @param value 三维向量值
|
|
||||||
*/
|
|
||||||
void GLShader::setVec3(const std::string &name, const glm::vec3 &value) {
|
|
||||||
glUniform3fv(getUniformLocation(name), 1, &value[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置四维向量类型uniform变量
|
|
||||||
* @param name uniform变量名
|
|
||||||
* @param value 四维向量值
|
|
||||||
*/
|
|
||||||
void GLShader::setVec4(const std::string &name, const glm::vec4 &value) {
|
|
||||||
glUniform4fv(getUniformLocation(name), 1, &value[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置4x4矩阵类型uniform变量
|
|
||||||
* @param name uniform变量名
|
|
||||||
* @param value 4x4矩阵值
|
|
||||||
*/
|
|
||||||
void GLShader::setMat4(const std::string &name, const glm::mat4 &value) {
|
|
||||||
glUniformMatrix4fv(getUniformLocation(name), 1, GL_FALSE, &value[0][0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置颜色类型uniform变量
|
|
||||||
* @param name uniform变量名
|
|
||||||
* @param color 颜色值
|
|
||||||
*/
|
|
||||||
void GLShader::setColor(const std::string &name, const Color &color) {
|
|
||||||
glUniform4f(getUniformLocation(name), color.r, color.g, color.b, color.a);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 从源码编译Shader
|
|
||||||
* @param vertexSource 顶点着色器源码
|
|
||||||
* @param fragmentSource 片段着色器源码
|
|
||||||
* @return 编译成功返回true,失败返回false
|
|
||||||
*/
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (programID_ != 0) {
|
|
||||||
glDeleteProgram(programID_);
|
|
||||||
uniformCache_.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Shader program linking failed: %s", infoLog);
|
|
||||||
glDeleteProgram(programID_);
|
|
||||||
programID_ = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
glDeleteShader(vertexShader);
|
|
||||||
glDeleteShader(fragmentShader);
|
|
||||||
|
|
||||||
return success == GL_TRUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 从二进制数据创建Shader
|
|
||||||
* @param binary 二进制数据
|
|
||||||
* @return 创建成功返回true,失败返回false
|
|
||||||
*/
|
|
||||||
bool GLShader::compileFromBinary(const std::vector<uint8_t> &binary) {
|
|
||||||
if (binary.empty()) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Binary data is empty");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
GLint numFormats = 0;
|
|
||||||
glGetIntegerv(GL_NUM_PROGRAM_BINARY_FORMATS, &numFormats);
|
|
||||||
if (numFormats == 0) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Program binary formats not supported");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (programID_ != 0) {
|
|
||||||
glDeleteProgram(programID_);
|
|
||||||
uniformCache_.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
programID_ = glCreateProgram();
|
|
||||||
|
|
||||||
GLenum binaryFormat = 0;
|
|
||||||
glGetIntegerv(GL_PROGRAM_BINARY_FORMATS,
|
|
||||||
reinterpret_cast<GLint *>(&binaryFormat));
|
|
||||||
|
|
||||||
glProgramBinary(programID_, binaryFormat, binary.data(),
|
|
||||||
static_cast<GLsizei>(binary.size()));
|
|
||||||
|
|
||||||
GLint success = 0;
|
|
||||||
glGetProgramiv(programID_, GL_LINK_STATUS, &success);
|
|
||||||
if (!success) {
|
|
||||||
char infoLog[512];
|
|
||||||
glGetProgramInfoLog(programID_, 512, nullptr, infoLog);
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Failed to load shader from binary: %s", infoLog);
|
|
||||||
glDeleteProgram(programID_);
|
|
||||||
programID_ = 0;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取Shader二进制数据
|
|
||||||
* @param outBinary 输出的二进制数据
|
|
||||||
* @return 成功返回true,失败返回false
|
|
||||||
*/
|
|
||||||
bool GLShader::getBinary(std::vector<uint8_t> &outBinary) {
|
|
||||||
if (programID_ == 0) {
|
|
||||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Cannot get binary: shader program is 0");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
GLint binaryLength = 0;
|
|
||||||
glGetProgramiv(programID_, GL_PROGRAM_BINARY_LENGTH, &binaryLength);
|
|
||||||
|
|
||||||
if (binaryLength <= 0) {
|
|
||||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Shader binary length is 0 or negative");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
outBinary.resize(binaryLength);
|
|
||||||
|
|
||||||
GLenum binaryFormat = 0;
|
|
||||||
GLsizei actualLength = 0;
|
|
||||||
glGetProgramBinary(programID_, binaryLength, &actualLength, &binaryFormat,
|
|
||||||
outBinary.data());
|
|
||||||
|
|
||||||
GLenum err = glGetError();
|
|
||||||
if (err != GL_NO_ERROR) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"glGetProgramBinary failed with error: %d", err);
|
|
||||||
outBinary.clear();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (actualLength == 0) {
|
|
||||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"glGetProgramBinary returned 0 bytes");
|
|
||||||
outBinary.clear();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (actualLength != binaryLength) {
|
|
||||||
outBinary.resize(actualLength);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 编译单个着色器
|
|
||||||
* @param type 着色器类型
|
|
||||||
* @param source 着色器源码
|
|
||||||
* @return 着色器ID,失败返回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);
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Shader compilation failed: %s",
|
|
||||||
infoLog);
|
|
||||||
glDeleteShader(shader);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return shader;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取uniform位置
|
|
||||||
* @param name uniform变量名
|
|
||||||
* @return uniform位置
|
|
||||||
*/
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// GLShaderFactory 实现
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 从源码创建Shader
|
|
||||||
* @param name Shader名称
|
|
||||||
* @param vertSource 顶点着色器源码
|
|
||||||
* @param fragSource 片段着色器源码
|
|
||||||
* @return 创建的Shader实例
|
|
||||||
*/
|
|
||||||
Ptr<IShader> GLShaderFactory::createFromSource(const std::string &name,
|
|
||||||
const std::string &vertSource,
|
|
||||||
const std::string &fragSource) {
|
|
||||||
|
|
||||||
auto shader = std::make_shared<GLShader>();
|
|
||||||
shader->setName(name);
|
|
||||||
|
|
||||||
if (!shader->compileFromSource(vertSource.c_str(), fragSource.c_str())) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Failed to compile shader from source: %s", name.c_str());
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
return shader;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 从缓存二进制创建Shader
|
|
||||||
* @param name Shader名称
|
|
||||||
* @param binary 编译后的二进制数据
|
|
||||||
* @return 创建的Shader实例
|
|
||||||
*/
|
|
||||||
Ptr<IShader>
|
|
||||||
GLShaderFactory::createFromBinary(const std::string &name,
|
|
||||||
const std::vector<uint8_t> &binary) {
|
|
||||||
|
|
||||||
auto shader = std::make_shared<GLShader>();
|
|
||||||
shader->setName(name);
|
|
||||||
|
|
||||||
if (!shader->compileFromBinary(binary)) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Failed to create shader from binary: %s", name.c_str());
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
return shader;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取Shader的二进制数据
|
|
||||||
* @param shader Shader实例
|
|
||||||
* @param outBinary 输出的二进制数据
|
|
||||||
* @return 成功返回true,失败返回false
|
|
||||||
*/
|
|
||||||
bool GLShaderFactory::getShaderBinary(const IShader &shader,
|
|
||||||
std::vector<uint8_t> &outBinary) {
|
|
||||||
const GLShader *glShader = dynamic_cast<const GLShader *>(&shader);
|
|
||||||
if (!glShader) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Shader is not a GLShader instance");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return const_cast<GLShader *>(glShader)->getBinary(outBinary);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace frostbite2D
|
|
||||||
|
|
@ -1,382 +0,0 @@
|
||||||
#include <SDL.h>
|
|
||||||
#include <cstring>
|
|
||||||
#include <fostbite2D/render/opengl/gl_sprite_batch.h>
|
|
||||||
#include <fostbite2D/render/opengl/gl_texture.h>
|
|
||||||
#include <fostbite2D/render/shader/shader_manager.h>
|
|
||||||
#include <glm/gtc/matrix_transform.hpp>
|
|
||||||
|
|
||||||
namespace frostbite2D {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 三角函数查表 - 避免每帧重复计算 sin/cos
|
|
||||||
// ============================================================================
|
|
||||||
/**
|
|
||||||
* @brief 三角函数查表类,避免每帧重复计算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;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 查表获取sin值
|
|
||||||
* @param radians 弧度值
|
|
||||||
* @return sin值
|
|
||||||
*/
|
|
||||||
static float sinRad(float radians) {
|
|
||||||
return table_.sinTable[normalizeIndexRad(radians)];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 查表获取cos值
|
|
||||||
* @param radians 弧度值
|
|
||||||
* @return cos值
|
|
||||||
*/
|
|
||||||
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_;
|
|
||||||
|
|
||||||
// 静态索引生成函数
|
|
||||||
/**
|
|
||||||
* @brief 获取静态索引数组,用于精灵绘制
|
|
||||||
* @return 索引数组的常量引用
|
|
||||||
*/
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 构造函数,初始化精灵批处理器成员变量
|
|
||||||
*/
|
|
||||||
GLSpriteBatch::GLSpriteBatch()
|
|
||||||
: vao_(0), vbo_(0), ibo_(0), vertexCount_(0), currentTexture_(nullptr),
|
|
||||||
currentIsSDF_(false), drawCallCount_(0), spriteCount_(0), batchCount_(0) {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 析构函数,调用shutdown释放资源
|
|
||||||
*/
|
|
||||||
GLSpriteBatch::~GLSpriteBatch() { shutdown(); }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 初始化精灵批处理器,创建VAO、VBO、IBO和编译着色器
|
|
||||||
* @return 初始化成功返回true,失败返回false
|
|
||||||
*/
|
|
||||||
bool GLSpriteBatch::init() {
|
|
||||||
// 使用ShaderManager从文件加载着色器
|
|
||||||
shader_ = ShaderManager::getInstance().loadFromFile(
|
|
||||||
"sprite", "shaders/sprite.vert", "shaders/sprite.frag");
|
|
||||||
|
|
||||||
if (!shader_) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Failed to load sprite batch shader from ShaderManager");
|
|
||||||
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);
|
|
||||||
|
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"GLSpriteBatch initialized with capacity for %d sprites",
|
|
||||||
MAX_SPRITES);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 关闭精灵批处理器,释放OpenGL资源
|
|
||||||
*/
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
shader_.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 开始批处理,设置视图投影矩阵
|
|
||||||
* @param viewProjection 视图投影矩阵
|
|
||||||
*/
|
|
||||||
void GLSpriteBatch::begin(const glm::mat4 &viewProjection) {
|
|
||||||
viewProjection_ = viewProjection;
|
|
||||||
viewProjectionDirty_ = true;
|
|
||||||
vertexCount_ = 0;
|
|
||||||
currentTexture_ = nullptr;
|
|
||||||
currentIsSDF_ = false;
|
|
||||||
drawCallCount_ = 0;
|
|
||||||
spriteCount_ = 0;
|
|
||||||
batchCount_ = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 绘制单个精灵,自动处理批处理
|
|
||||||
* @param texture 纹理
|
|
||||||
* @param data 精灵数据
|
|
||||||
*/
|
|
||||||
void GLSpriteBatch::draw(const Texture &texture, const SpriteData &data) {
|
|
||||||
// 检查是否需要刷新批次
|
|
||||||
if (needsFlush(texture, data.isSDF)) {
|
|
||||||
flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设置当前纹理(如果是第一次绘制或刷新后)
|
|
||||||
if (currentTexture_ == nullptr) {
|
|
||||||
currentTexture_ = &texture;
|
|
||||||
currentIsSDF_ = data.isSDF;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 添加顶点到缓冲区
|
|
||||||
addVertices(data);
|
|
||||||
vertexCount_ += VERTICES_PER_SPRITE;
|
|
||||||
++spriteCount_;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 批量绘制多个精灵,优化性能
|
|
||||||
* @param texture 纹理
|
|
||||||
* @param sprites 精灵数据数组
|
|
||||||
*/
|
|
||||||
void GLSpriteBatch::drawBatch(const Texture &texture,
|
|
||||||
const std::vector<SpriteData> &sprites) {
|
|
||||||
for (const auto &sprite : sprites) {
|
|
||||||
draw(texture, sprite);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 立即绘制单个精灵,不缓存
|
|
||||||
* @param texture 纹理
|
|
||||||
* @param data 精灵数据
|
|
||||||
*/
|
|
||||||
void GLSpriteBatch::drawImmediate(const Texture &texture,
|
|
||||||
const SpriteData &data) {
|
|
||||||
flush(); // 先刷新当前批次
|
|
||||||
|
|
||||||
currentTexture_ = &texture;
|
|
||||||
currentIsSDF_ = data.isSDF;
|
|
||||||
|
|
||||||
addVertices(data);
|
|
||||||
vertexCount_ = VERTICES_PER_SPRITE;
|
|
||||||
++spriteCount_;
|
|
||||||
|
|
||||||
flush(); // 立即刷新
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 结束批处理,刷新剩余顶点
|
|
||||||
*/
|
|
||||||
void GLSpriteBatch::end() { flush(); }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查是否需要刷新批次
|
|
||||||
* @param texture 纹理
|
|
||||||
* @param isSDF 是否SDF字体
|
|
||||||
* @return 需要刷新返回true
|
|
||||||
*/
|
|
||||||
bool GLSpriteBatch::needsFlush(const Texture &texture, bool isSDF) const {
|
|
||||||
if (currentTexture_ == nullptr)
|
|
||||||
return false;
|
|
||||||
if (currentTexture_ != &texture)
|
|
||||||
return true;
|
|
||||||
if (currentIsSDF_ != isSDF)
|
|
||||||
return true;
|
|
||||||
if (vertexCount_ + VERTICES_PER_SPRITE > MAX_VERTICES)
|
|
||||||
return true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 刷新批次,提交顶点数据到GPU
|
|
||||||
*/
|
|
||||||
void GLSpriteBatch::flush() {
|
|
||||||
if (vertexCount_ == 0 || currentTexture_ == nullptr)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// 绑定纹理 - 将 Texture 转换为 GLTexture
|
|
||||||
static_cast<const GLTexture *>(currentTexture_)->bind(0);
|
|
||||||
|
|
||||||
// 设置Shader
|
|
||||||
setupShader();
|
|
||||||
|
|
||||||
// 更新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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置Shader uniform变量
|
|
||||||
*/
|
|
||||||
void GLSpriteBatch::setupShader() {
|
|
||||||
shader_->bind();
|
|
||||||
|
|
||||||
// 只在矩阵变化时更新
|
|
||||||
if (viewProjectionDirty_ || viewProjection_ != cachedViewProjection_) {
|
|
||||||
shader_->setMat4("uViewProjection", viewProjection_);
|
|
||||||
cachedViewProjection_ = viewProjection_;
|
|
||||||
viewProjectionDirty_ = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设置纹理单元
|
|
||||||
shader_->setInt("uTexture", 0);
|
|
||||||
|
|
||||||
shader_->setInt("uUseSDF", currentIsSDF_ ? 1 : 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 添加精灵顶点到缓冲区
|
|
||||||
* @param data 精灵数据
|
|
||||||
*/
|
|
||||||
void GLSpriteBatch::addVertices(const SpriteData &data) {
|
|
||||||
size_t baseIndex = vertexCount_;
|
|
||||||
|
|
||||||
// 计算旋转后的四个角
|
|
||||||
float cosRot = TrigLookup::cosRad(data.rotation);
|
|
||||||
float sinRot = TrigLookup::sinRad(data.rotation);
|
|
||||||
|
|
||||||
// 计算锚点偏移(与 Extra2D-dev 一致)
|
|
||||||
float anchorOffsetX = data.size.x * data.anchor.x;
|
|
||||||
float anchorOffsetY = data.size.y * data.anchor.y;
|
|
||||||
|
|
||||||
// 本地坐标(相对于锚点)
|
|
||||||
// rx0, ry0: 左下
|
|
||||||
// rx1, ry1: 右上
|
|
||||||
float rx0 = -anchorOffsetX;
|
|
||||||
float ry0 = -anchorOffsetY;
|
|
||||||
float rx1 = data.size.x - anchorOffsetX;
|
|
||||||
float ry1 = data.size.y - anchorOffsetY;
|
|
||||||
|
|
||||||
// 预计算旋转后的偏移
|
|
||||||
float cosRx0 = rx0 * cosRot, sinRx0 = rx0 * sinRot;
|
|
||||||
float cosRx1 = rx1 * cosRot, sinRx1 = rx1 * sinRot;
|
|
||||||
float cosRy0 = ry0 * cosRot, sinRy0 = ry0 * sinRot;
|
|
||||||
float cosRy1 = ry1 * cosRot, sinRy1 = ry1 * sinRot;
|
|
||||||
|
|
||||||
// 顶点布局(与 Extra2D-dev 完全一致):
|
|
||||||
// v3(左上) -- v2(右上)
|
|
||||||
// | |
|
|
||||||
// v0(左下) -- v1(右下)
|
|
||||||
//
|
|
||||||
// 索引: (0,1,2) 和 (0,2,3)
|
|
||||||
|
|
||||||
glm::vec4 color(data.color.r, data.color.g, data.color.b, data.color.a);
|
|
||||||
|
|
||||||
// v0: 左下 (u0, v0)
|
|
||||||
vertexBuffer_[baseIndex + 0] = {
|
|
||||||
glm::vec2(data.position.x + cosRx0 - sinRy0,
|
|
||||||
data.position.y + sinRx0 + cosRy0),
|
|
||||||
glm::vec2(data.texCoordMin.x, data.texCoordMin.y), color};
|
|
||||||
|
|
||||||
// v1: 右下 (u1, v0)
|
|
||||||
vertexBuffer_[baseIndex + 1] = {
|
|
||||||
glm::vec2(data.position.x + cosRx1 - sinRy0,
|
|
||||||
data.position.y + sinRx1 + cosRy0),
|
|
||||||
glm::vec2(data.texCoordMax.x, data.texCoordMin.y), color};
|
|
||||||
|
|
||||||
// v2: 右上 (u1, v1)
|
|
||||||
vertexBuffer_[baseIndex + 2] = {
|
|
||||||
glm::vec2(data.position.x + cosRx1 - sinRy1,
|
|
||||||
data.position.y + sinRx1 + cosRy1),
|
|
||||||
glm::vec2(data.texCoordMax.x, data.texCoordMax.y), color};
|
|
||||||
|
|
||||||
// v3: 左上 (u0, v1)
|
|
||||||
vertexBuffer_[baseIndex + 3] = {
|
|
||||||
glm::vec2(data.position.x + cosRx0 - sinRy1,
|
|
||||||
data.position.y + sinRx0 + cosRy1),
|
|
||||||
glm::vec2(data.texCoordMin.x, data.texCoordMax.y), color};
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace frostbite2D
|
|
||||||
|
|
@ -1,513 +0,0 @@
|
||||||
#include <SDL.h>
|
|
||||||
#include <fostbite2D/render/opengl/gl_texture.h>
|
|
||||||
#define STB_IMAGE_IMPLEMENTATION
|
|
||||||
#include <cstring>
|
|
||||||
#include <fstream>
|
|
||||||
#include <stb/stb_image.h>
|
|
||||||
|
|
||||||
namespace frostbite2D {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 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;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 生成四字符代码(FourCC)
|
|
||||||
* @param a 第一个字符
|
|
||||||
* @param b 第二个字符
|
|
||||||
* @param c 第三个字符
|
|
||||||
* @param d 第四个字符
|
|
||||||
* @return 组合后的32位无符号整数
|
|
||||||
*/
|
|
||||||
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 实现
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 从像素数据构造纹理对象
|
|
||||||
* @param width 纹理宽度(像素)
|
|
||||||
* @param height 纹理高度(像素)
|
|
||||||
* @param pixels 像素数据指针,可为nullptr创建空纹理
|
|
||||||
* @param channels 颜色通道数(1=R, 3=RGB, 4=RGBA)
|
|
||||||
*/
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 从文件路径构造纹理对象
|
|
||||||
* @param filepath 纹理文件路径,支持普通图片格式和压缩格式(KTX/DDS)
|
|
||||||
*/
|
|
||||||
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 {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to load texture: %s",
|
|
||||||
filepath.c_str());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
GLTexture::~GLTexture() {
|
|
||||||
if (textureID_ != 0) {
|
|
||||||
glDeleteTextures(1, &textureID_);
|
|
||||||
textureID_ = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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_);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 解绑当前纹理
|
|
||||||
*/
|
|
||||||
void GLTexture::unbind() const { glBindTexture(GL_TEXTURE_2D, 0); }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 创建OpenGL纹理对象并上传像素数据
|
|
||||||
* @param pixels 像素数据指针
|
|
||||||
*/
|
|
||||||
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);
|
|
||||||
|
|
||||||
// 计算数据大小
|
|
||||||
dataSize_ = static_cast<size_t>(width_ * height_ * channels_);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 压缩纹理加载
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Unsupported compressed texture format: %s", filepath.c_str());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 加载KTX格式压缩纹理
|
|
||||||
* @param filepath KTX文件路径
|
|
||||||
* @return 加载成功返回true,失败返回false
|
|
||||||
*/
|
|
||||||
bool GLTexture::loadKTX(const std::string &filepath) {
|
|
||||||
std::ifstream file(filepath, std::ios::binary);
|
|
||||||
if (!file.is_open()) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to open KTX file: %s",
|
|
||||||
filepath.c_str());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
KTXHeader header;
|
|
||||||
file.read(reinterpret_cast<char *>(&header), sizeof(header));
|
|
||||||
if (!file) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to read KTX header: %s",
|
|
||||||
filepath.c_str());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 验证标识符
|
|
||||||
if (std::memcmp(header.identifier, KTX_IDENTIFIER, 12) != 0) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Invalid KTX identifier: %s",
|
|
||||||
filepath.c_str());
|
|
||||||
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:
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Unsupported KTX internal format: %x", 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) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Failed to read KTX image size: %s", filepath.c_str());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<uint8_t> compressedData(imageSize);
|
|
||||||
file.read(reinterpret_cast<char *>(compressedData.data()), imageSize);
|
|
||||||
if (!file) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Failed to read KTX image data: %s", filepath.c_str());
|
|
||||||
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) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"glCompressedTexImage2D failed for KTX: %x", 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);
|
|
||||||
|
|
||||||
// 数据大小
|
|
||||||
dataSize_ = imageSize;
|
|
||||||
|
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Loaded compressed KTX texture: %s (%dx%d, format=%x)",
|
|
||||||
filepath.c_str(), width_, height_, glInternalFormat);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 加载DDS格式压缩纹理
|
|
||||||
* @param filepath DDS文件路径
|
|
||||||
* @return 加载成功返回true,失败返回false
|
|
||||||
*/
|
|
||||||
bool GLTexture::loadDDS(const std::string &filepath) {
|
|
||||||
std::ifstream file(filepath, std::ios::binary);
|
|
||||||
if (!file.is_open()) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to open DDS file: %s",
|
|
||||||
filepath.c_str());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
DDSHeader header;
|
|
||||||
file.read(reinterpret_cast<char *>(&header), sizeof(header));
|
|
||||||
if (!file) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to read DDS header: %s",
|
|
||||||
filepath.c_str());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (header.magic != DDS_MAGIC) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Invalid DDS magic: %s",
|
|
||||||
filepath.c_str());
|
|
||||||
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) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Failed to read DDS DX10 header: %s", filepath.c_str());
|
|
||||||
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:
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Unsupported DDS DX10 format: %d", dx10Header.dxgiFormat);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"DDS file does not use DX10 extension, unsupported: %s",
|
|
||||||
filepath.c_str());
|
|
||||||
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) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Failed to read DDS image data: %s", filepath.c_str());
|
|
||||||
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) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"glCompressedTexImage2D failed for DDS: %x", 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);
|
|
||||||
|
|
||||||
// 数据大小
|
|
||||||
dataSize_ = imageSize;
|
|
||||||
|
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Loaded compressed DDS texture: %s (%dx%d)", filepath.c_str(),
|
|
||||||
width_, height_);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLTexture::generateAlphaMask() {
|
|
||||||
if (pixelData_.empty() || width_ <= 0 || height_ <= 0) {
|
|
||||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Cannot generate alpha mask: no pixel data available");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
alphaMask_ = std::make_unique<AlphaMask>(AlphaMask::createFromPixels(
|
|
||||||
pixelData_.data(), width_, height_, channels_));
|
|
||||||
|
|
||||||
SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Generated alpha mask for texture: %dx%d", width_, height_);
|
|
||||||
}
|
|
||||||
|
|
||||||
PixelFormat GLTexture::getFormat() const { return format_; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 静态工厂方法,创建指定格式的空纹理
|
|
||||||
* @param width 纹理宽度
|
|
||||||
* @param height 纹理高度
|
|
||||||
* @param format 像素格式
|
|
||||||
* @return 创建的纹理智能指针
|
|
||||||
*/
|
|
||||||
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 frostbite2D
|
|
||||||
|
|
@ -1,231 +0,0 @@
|
||||||
#include <SDL.h>
|
|
||||||
#include <fostbite2D/render/shader/shader_manager.h>
|
|
||||||
#include <fstream>
|
|
||||||
#include <sstream>
|
|
||||||
|
|
||||||
namespace frostbite2D {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 从文件读取文本内容
|
|
||||||
* @param filepath 文件路径
|
|
||||||
* @return 文件内容字符串,失败返回空字符串
|
|
||||||
*/
|
|
||||||
static std::string readFile(const std::filesystem::path &filepath) {
|
|
||||||
if (!std::filesystem::exists(filepath)) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Shader file not found: %s",
|
|
||||||
filepath.string().c_str());
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
std::ifstream file(filepath);
|
|
||||||
if (!file.is_open()) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to open shader file: %s",
|
|
||||||
filepath.string().c_str());
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
std::stringstream buffer;
|
|
||||||
buffer << file.rdbuf();
|
|
||||||
return buffer.str();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取单例实例
|
|
||||||
* @return Shader管理器实例引用
|
|
||||||
*/
|
|
||||||
ShaderManager &ShaderManager::getInstance() {
|
|
||||||
static ShaderManager instance;
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 初始化Shader系统
|
|
||||||
* @param factory 渲染后端Shader工厂
|
|
||||||
* @return 初始化成功返回true,失败返回false
|
|
||||||
*/
|
|
||||||
bool ShaderManager::init(Ptr<IShaderFactory> factory) {
|
|
||||||
if (initialized_) {
|
|
||||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"ShaderManager already initialized");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!factory) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Shader factory is null");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
factory_ = factory;
|
|
||||||
initialized_ = true;
|
|
||||||
|
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "ShaderManager initialized");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 关闭Shader系统
|
|
||||||
*/
|
|
||||||
void ShaderManager::shutdown() {
|
|
||||||
if (!initialized_) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
shaders_.clear();
|
|
||||||
factory_.reset();
|
|
||||||
initialized_ = false;
|
|
||||||
|
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "ShaderManager shutdown");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 从源码加载Shader
|
|
||||||
* @param name Shader名称
|
|
||||||
* @param vertSource 顶点着色器源码
|
|
||||||
* @param fragSource 片段着色器源码
|
|
||||||
* @return 加载的Shader实例
|
|
||||||
*/
|
|
||||||
Ptr<IShader> ShaderManager::loadFromSource(const std::string &name,
|
|
||||||
const std::string &vertSource,
|
|
||||||
const std::string &fragSource) {
|
|
||||||
if (!initialized_) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "ShaderManager not initialized");
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto it = shaders_.find(name);
|
|
||||||
if (it != shaders_.end()) {
|
|
||||||
return it->second.shader;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ptr<IShader> shader =
|
|
||||||
factory_->createFromSource(name, vertSource, fragSource);
|
|
||||||
if (!shader) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Failed to create shader from source: %s", name.c_str());
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
ShaderInfo info;
|
|
||||||
info.shader = shader;
|
|
||||||
info.vertSource = vertSource;
|
|
||||||
info.fragSource = fragSource;
|
|
||||||
|
|
||||||
shaders_[name] = std::move(info);
|
|
||||||
|
|
||||||
SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "Shader loaded from source: %s",
|
|
||||||
name.c_str());
|
|
||||||
return shader;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 从文件加载Shader
|
|
||||||
* @param name Shader名称
|
|
||||||
* @param vertPath 顶点着色器文件路径
|
|
||||||
* @param fragPath 片段着色器文件路径
|
|
||||||
* @return 加载的Shader实例,失败返回nullptr
|
|
||||||
*/
|
|
||||||
Ptr<IShader> ShaderManager::loadFromFile(const std::string &name,
|
|
||||||
const std::filesystem::path &vertPath,
|
|
||||||
const std::filesystem::path &fragPath) {
|
|
||||||
if (!initialized_) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "ShaderManager not initialized");
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto it = shaders_.find(name);
|
|
||||||
if (it != shaders_.end()) {
|
|
||||||
return it->second.shader;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string vertSource = readFile(vertPath);
|
|
||||||
std::string fragSource = readFile(fragPath);
|
|
||||||
|
|
||||||
if (vertSource.empty()) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Failed to read vertex shader file: %s", vertPath.string().c_str());
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fragSource.empty()) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Failed to read fragment shader file: %s", fragPath.string().c_str());
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ptr<IShader> shader = factory_->createFromSource(name, vertSource, fragSource);
|
|
||||||
if (!shader) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Failed to create shader from files: %s", name.c_str());
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
ShaderInfo info;
|
|
||||||
info.shader = shader;
|
|
||||||
info.vertSource = vertSource;
|
|
||||||
info.fragSource = fragSource;
|
|
||||||
info.vertPath = vertPath;
|
|
||||||
info.fragPath = fragPath;
|
|
||||||
|
|
||||||
shaders_[name] = std::move(info);
|
|
||||||
|
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Shader loaded from file: %s", name.c_str());
|
|
||||||
return shader;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取已加载的Shader
|
|
||||||
* @param name Shader名称
|
|
||||||
* @return Shader实例,不存在返回nullptr
|
|
||||||
*/
|
|
||||||
Ptr<IShader> ShaderManager::get(const std::string &name) const {
|
|
||||||
auto it = shaders_.find(name);
|
|
||||||
if (it != shaders_.end()) {
|
|
||||||
return it->second.shader;
|
|
||||||
}
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查Shader是否存在
|
|
||||||
* @param name Shader名称
|
|
||||||
* @return 存在返回true,否则返回false
|
|
||||||
*/
|
|
||||||
bool ShaderManager::has(const std::string &name) const {
|
|
||||||
return shaders_.find(name) != shaders_.end();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 移除Shader
|
|
||||||
* @param name Shader名称
|
|
||||||
*/
|
|
||||||
void ShaderManager::remove(const std::string &name) {
|
|
||||||
auto it = shaders_.find(name);
|
|
||||||
if (it != shaders_.end()) {
|
|
||||||
shaders_.erase(it);
|
|
||||||
SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "Shader removed: %s",
|
|
||||||
name.c_str());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 清除所有Shader
|
|
||||||
*/
|
|
||||||
void ShaderManager::clear() {
|
|
||||||
shaders_.clear();
|
|
||||||
SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "All shaders cleared");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 注册重载回调
|
|
||||||
* @param name Shader名称
|
|
||||||
* @param callback 重载回调函数
|
|
||||||
*/
|
|
||||||
void ShaderManager::setReloadCallback(const std::string &name,
|
|
||||||
ShaderReloadCallback callback) {
|
|
||||||
auto it = shaders_.find(name);
|
|
||||||
if (it != shaders_.end()) {
|
|
||||||
it->second.reloadCallback = callback;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace frostbite2D
|
|
||||||
|
|
@ -1,66 +0,0 @@
|
||||||
#include <fostbite2D/render/texture.h>
|
|
||||||
|
|
||||||
namespace frostbite2D {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 从像素数据创建Alpha遮罩
|
|
||||||
* @param pixels 像素数据指针
|
|
||||||
* @param width 宽度
|
|
||||||
* @param height 高度
|
|
||||||
* @param channels 通道数
|
|
||||||
* @return 创建的AlphaMask
|
|
||||||
*/
|
|
||||||
AlphaMask AlphaMask::createFromPixels(const uint8_t *pixels, int width,
|
|
||||||
int height, int channels) {
|
|
||||||
AlphaMask mask;
|
|
||||||
mask.width_ = width;
|
|
||||||
mask.height_ = height;
|
|
||||||
mask.data_.resize(width * height);
|
|
||||||
|
|
||||||
for (int y = 0; y < height; ++y) {
|
|
||||||
for (int x = 0; x < width; ++x) {
|
|
||||||
int pixelIndex = (y * width + x) * channels;
|
|
||||||
uint8_t alpha;
|
|
||||||
|
|
||||||
if (channels == 4) {
|
|
||||||
// RGBA 格式
|
|
||||||
alpha = pixels[pixelIndex + 3];
|
|
||||||
} else if (channels == 1) {
|
|
||||||
// 灰度格式
|
|
||||||
alpha = pixels[pixelIndex];
|
|
||||||
} else {
|
|
||||||
// RGB 或其他格式,假设不透明
|
|
||||||
alpha = 255;
|
|
||||||
}
|
|
||||||
|
|
||||||
mask.data_[y * width + x] = alpha;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return mask;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取指定位置的透明度
|
|
||||||
* @param x X坐标
|
|
||||||
* @param y Y坐标
|
|
||||||
* @return 透明度值(0-255)
|
|
||||||
*/
|
|
||||||
uint8_t AlphaMask::getAlpha(int x, int y) const {
|
|
||||||
if (x < 0 || x >= width_ || y < 0 || y >= height_) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return data_[y * width_ + x];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查指定位置是否不透明
|
|
||||||
* @param x X坐标
|
|
||||||
* @param y Y坐标
|
|
||||||
* @return 不透明返回true
|
|
||||||
*/
|
|
||||||
bool AlphaMask::isOpaque(int x, int y) const {
|
|
||||||
return getAlpha(x, y) > 128; // 使用128作为阈值
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace frostbite2D
|
|
||||||
|
|
@ -1,13 +1,8 @@
|
||||||
|
#include "SDL_log.h"
|
||||||
#include <SDL2/SDL.h>
|
#include <SDL2/SDL.h>
|
||||||
#include <cmath>
|
#include <fostbite2D/core/application.h>
|
||||||
#include <fostbite2D/app/application.h>
|
|
||||||
#include <fostbite2D/core/color.h>
|
#include <fostbite2D/core/color.h>
|
||||||
#include <fostbite2D/platform/window.h>
|
#include <fostbite2D/platform/window.h>
|
||||||
#include <fostbite2D/render/camera.h>
|
|
||||||
#include <fostbite2D/render/opengl/gl_font_atlas.h>
|
|
||||||
#include <fostbite2D/render/opengl/gl_renderer.h>
|
|
||||||
#include <fostbite2D/render/opengl/gl_shader.h>
|
|
||||||
#include <fostbite2D/render/shader/shader_manager.h>
|
|
||||||
#include <glad/glad.h>
|
#include <glad/glad.h>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
|
|
@ -24,213 +19,13 @@ int main(int argc, char **argv) {
|
||||||
|
|
||||||
Application &app = Application::get();
|
Application &app = Application::get();
|
||||||
if (!app.init(config)) {
|
if (!app.init(config)) {
|
||||||
std::cerr << "Failed to initialize application!" << std::endl;
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"Failed to initialize application!");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 初始化 ShaderManager
|
|
||||||
auto shaderFactory = makePtr<GLShaderFactory>();
|
|
||||||
if (!ShaderManager::getInstance().init(shaderFactory)) {
|
|
||||||
std::cerr << "Failed to initialize ShaderManager!" << std::endl;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建 OpenGL 渲染器
|
|
||||||
GLRenderer renderer;
|
|
||||||
SDL_Window *sdlWindow = SDL_GL_GetCurrentWindow();
|
|
||||||
if (!sdlWindow) {
|
|
||||||
std::cerr << "Failed to get SDL window!" << std::endl;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!renderer.init(sdlWindow)) {
|
|
||||||
std::cerr << "Failed to initialize OpenGL renderer!" << std::endl;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 加载字体(使用系统默认字体或项目字体)
|
|
||||||
#ifdef _WIN32
|
|
||||||
std::string fontPath = "C:/Windows/Fonts/arial.ttf"; // Windows 系统字体
|
|
||||||
#else
|
|
||||||
std::string fontPath = "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"; // Linux 系统字体
|
|
||||||
#endif
|
|
||||||
FontAtlas *font = nullptr;
|
|
||||||
|
|
||||||
// 尝试加载字体
|
|
||||||
try {
|
|
||||||
font = new GLFontAtlas(fontPath, 24, false); // 24像素大小,不使用SDF
|
|
||||||
SDL_Log("Font loaded successfully: %s", fontPath.c_str());
|
|
||||||
} catch (...) {
|
|
||||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Failed to load font: %s",
|
|
||||||
fontPath.c_str());
|
|
||||||
SDL_Log("Text rendering will be disabled");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建相机 - 视口范围 (0,0) 到 (800,600),相机位置在 (0,0) 表示左上角
|
|
||||||
Camera camera(0.0f, 800.0f, 600.0f, 0.0f);
|
|
||||||
// 相机默认位置 (0,0),表示看向世界坐标的左上角
|
|
||||||
|
|
||||||
SDL_Log("Frostbite2D OpenGL Renderer initialized successfully!");
|
|
||||||
SDL_Log("Press ESC to exit");
|
|
||||||
SDL_Log("Use WASD to move camera, Q/E to zoom, R to reset");
|
|
||||||
|
|
||||||
// 主循环
|
|
||||||
bool running = true;
|
|
||||||
SDL_Event event;
|
|
||||||
float time = 0.0f;
|
|
||||||
float zoom = 1.0f;
|
|
||||||
|
|
||||||
while (running) {
|
|
||||||
// 处理事件
|
|
||||||
while (SDL_PollEvent(&event)) {
|
|
||||||
if (event.type == SDL_QUIT) {
|
|
||||||
running = false;
|
|
||||||
}
|
|
||||||
if (event.type == SDL_KEYDOWN) {
|
|
||||||
if (event.key.keysym.sym == SDLK_ESCAPE) {
|
|
||||||
running = false;
|
|
||||||
}
|
|
||||||
// 相机控制
|
|
||||||
if (event.key.keysym.sym == SDLK_w) {
|
|
||||||
camera.move(
|
|
||||||
0.0f,
|
|
||||||
10.0f); // 向上移动(Y轴向下,所以正方向是向下,反方向是向上)
|
|
||||||
}
|
|
||||||
if (event.key.keysym.sym == SDLK_s) {
|
|
||||||
camera.move(0.0f, -10.0f); // 向下移动
|
|
||||||
}
|
|
||||||
if (event.key.keysym.sym == SDLK_a) {
|
|
||||||
camera.move(10.0f, 0.0f); // 向左移动
|
|
||||||
}
|
|
||||||
if (event.key.keysym.sym == SDLK_d) {
|
|
||||||
camera.move(-10.0f, 0.0f); // 向右移动
|
|
||||||
}
|
|
||||||
if (event.key.keysym.sym == SDLK_q) {
|
|
||||||
zoom = std::max(0.5f, zoom - 0.1f);
|
|
||||||
camera.setZoom(zoom);
|
|
||||||
}
|
|
||||||
if (event.key.keysym.sym == SDLK_e) {
|
|
||||||
zoom = std::min(3.0f, zoom + 0.1f);
|
|
||||||
camera.setZoom(zoom);
|
|
||||||
}
|
|
||||||
if (event.key.keysym.sym == SDLK_r) {
|
|
||||||
camera.setPosition(0.0f, 0.0f);
|
|
||||||
zoom = 1.0f;
|
|
||||||
camera.setZoom(zoom);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新时间
|
|
||||||
time += 0.016f; // 假设 60 FPS
|
|
||||||
|
|
||||||
// 开始渲染帧
|
|
||||||
renderer.beginFrame(Color(0.1f, 0.1f, 0.15f, 1.0f)); // 深蓝灰色背景
|
|
||||||
|
|
||||||
// 设置视口
|
|
||||||
renderer.setViewport(0, 0, 800, 600);
|
|
||||||
|
|
||||||
// 使用相机的视图投影矩阵
|
|
||||||
renderer.setViewProjection(camera.getViewProjectionMatrix());
|
|
||||||
|
|
||||||
// 绘制测试图形(使用世界坐标)
|
|
||||||
|
|
||||||
// 1. 绘制红色矩形边框
|
|
||||||
renderer.drawRect(Rect(100.0f, 100.0f, 200.0f, 150.0f), Colors::Red, 2.0f);
|
|
||||||
|
|
||||||
// 2. 绘制绿色填充矩形
|
|
||||||
renderer.fillRect(Rect(350.0f, 100.0f, 200.0f, 150.0f), Colors::Green);
|
|
||||||
|
|
||||||
// 3. 绘制蓝色圆形
|
|
||||||
renderer.fillCircle(Vec2(650.0f, 175.0f), 75.0f, Colors::Blue, 32);
|
|
||||||
|
|
||||||
// 4. 绘制动态旋转的矩形(使用填充三角形组合)
|
|
||||||
float centerX = 400.0f;
|
|
||||||
float centerY = 400.0f;
|
|
||||||
float size = 100.0f;
|
|
||||||
float angle = time * 2.0f; // 旋转角度
|
|
||||||
|
|
||||||
// 计算旋转后的四个角
|
|
||||||
float cosA = cosf(angle);
|
|
||||||
float sinA = sinf(angle);
|
|
||||||
|
|
||||||
Vec2 p1(centerX + (-size * cosA - (-size) * sinA),
|
|
||||||
centerY + (-size * sinA + (-size) * cosA));
|
|
||||||
Vec2 p2(centerX + (size * cosA - (-size) * sinA),
|
|
||||||
centerY + (size * sinA + (-size) * cosA));
|
|
||||||
Vec2 p3(centerX + (size * cosA - size * sinA),
|
|
||||||
centerY + (size * sinA + size * cosA));
|
|
||||||
Vec2 p4(centerX + (-size * cosA - size * sinA),
|
|
||||||
centerY + (-size * sinA + size * cosA));
|
|
||||||
|
|
||||||
// 绘制旋转的四边形(分成两个三角形)
|
|
||||||
renderer.fillTriangle(p1, p2, p3, Colors::Yellow);
|
|
||||||
renderer.fillTriangle(p1, p3, p4, Colors::Yellow);
|
|
||||||
|
|
||||||
// 5. 绘制线条
|
|
||||||
renderer.drawLine(Vec2(50.0f, 550.0f), Vec2(750.0f, 550.0f), Colors::White,
|
|
||||||
3.0f);
|
|
||||||
|
|
||||||
// 6. 绘制三角形
|
|
||||||
renderer.fillTriangle(Vec2(200.0f, 300.0f), Vec2(300.0f, 300.0f),
|
|
||||||
Vec2(250.0f, 200.0f), Colors::Cyan);
|
|
||||||
|
|
||||||
// 7. 绘制网格(帮助观察相机移动)
|
|
||||||
for (int i = 0; i <= 800; i += 100) {
|
|
||||||
renderer.drawLine(Vec2((float)i, 0.0f), Vec2((float)i, 600.0f),
|
|
||||||
Color(0.2f, 0.2f, 0.2f, 0.5f), 1.0f);
|
|
||||||
}
|
|
||||||
for (int i = 0; i <= 600; i += 100) {
|
|
||||||
renderer.drawLine(Vec2(0.0f, (float)i), Vec2(800.0f, (float)i),
|
|
||||||
Color(0.2f, 0.2f, 0.2f, 0.5f), 1.0f);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 8. 绘制文本(使用 GLRenderer 的 drawText)
|
|
||||||
if (font) {
|
|
||||||
renderer.beginSpriteBatch();
|
|
||||||
|
|
||||||
// 测试:直接绘制字体纹理的一部分(第一个字符 'F')
|
|
||||||
Texture *fontTex = font->getTexture();
|
|
||||||
if (fontTex) {
|
|
||||||
// 绘制字体纹理的一部分来测试
|
|
||||||
Rect destRect(50.0f, 300.0f, 64.0f, 64.0f); // 目标位置和大小
|
|
||||||
Rect srcRect(0.0f, 0.0f, 64.0f, 64.0f); // 纹理的前64x64像素
|
|
||||||
renderer.drawSprite(*fontTex, destRect, srcRect, Colors::White, 0.0f,
|
|
||||||
Vec2(0, 0));
|
|
||||||
SDL_Log("Drawing font texture test at (50, 300)");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 绘制标题
|
|
||||||
renderer.drawText(*font, "Frostbite2D Engine", 50.0f, 50.0f,
|
|
||||||
Colors::White);
|
|
||||||
|
|
||||||
// 绘制说明文字
|
|
||||||
renderer.drawText(*font, "WASD: Move Camera", 50.0f, 100.0f,
|
|
||||||
Colors::Yellow);
|
|
||||||
renderer.drawText(*font, "Q/E: Zoom", 50.0f, 130.0f, Colors::Yellow);
|
|
||||||
renderer.drawText(*font, "R: Reset", 50.0f, 160.0f, Colors::Yellow);
|
|
||||||
renderer.drawText(*font, "ESC: Exit", 50.0f, 190.0f, Colors::Yellow);
|
|
||||||
|
|
||||||
// 绘制 FPS 信息
|
|
||||||
renderer.drawText(*font, "OpenGL Renderer Active", 50.0f, 250.0f,
|
|
||||||
Colors::Green);
|
|
||||||
|
|
||||||
renderer.endSpriteBatch();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 结束渲染帧
|
|
||||||
renderer.endFrame();
|
|
||||||
|
|
||||||
// 交换缓冲区
|
|
||||||
SDL_GL_SwapWindow(sdlWindow);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 清理资源
|
|
||||||
delete font;
|
|
||||||
renderer.shutdown();
|
|
||||||
ShaderManager::getInstance().shutdown();
|
|
||||||
app.shutdown();
|
app.shutdown();
|
||||||
|
|
||||||
std::cout << "程序正常退出" << std::endl;
|
SDL_Log("程序正常退出");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
|
||||||
|
target("Frostbite2D")
|
||||||
|
set_kind("binary")
|
||||||
|
add_files(path.join(os.projectdir(), "Fostbite2D/src/**.cpp"))
|
||||||
|
add_files(path.join(os.projectdir(), "Fostbite2D/src/**.c"))
|
||||||
|
add_includedirs(path.join(os.projectdir(), "Fostbite2D/include"))
|
||||||
|
|
||||||
|
-- 检查 DEVKITPRO 环境变量(Windows 上使用 C:/devkitPro)
|
||||||
|
local devkitPro = os.getenv("DEVKITPRO") or "L:/Switch/devkitPro"
|
||||||
|
local devkitA64 = path.join(devkitPro, "devkitA64")
|
||||||
|
|
||||||
|
-- 设置工具链路径
|
||||||
|
set_toolset("cc", path.join(devkitA64, "bin/aarch64-none-elf-gcc.exe"))
|
||||||
|
set_toolset("cxx", path.join(devkitA64, "bin/aarch64-none-elf-g++.exe"))
|
||||||
|
set_toolset("ld", path.join(devkitA64, "bin/aarch64-none-elf-g++.exe"))
|
||||||
|
set_toolset("ar", path.join(devkitA64, "bin/aarch64-none-elf-gcc-ar.exe"))
|
||||||
|
set_toolset("strip", path.join(devkitA64, "bin/aarch64-none-elf-strip.exe"))
|
||||||
|
|
||||||
|
-- 架构标志
|
||||||
|
local arch_flags = "-march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -fPIE"
|
||||||
|
add_cxflags(arch_flags)
|
||||||
|
-- 使用 devkitPro 提供的 switch.specs 文件
|
||||||
|
add_ldflags("-specs=" .. path.join(devkitPro, "libnx/switch.specs"), "-g", arch_flags)
|
||||||
|
|
||||||
|
-- 定义 Switch 平台宏
|
||||||
|
add_defines("__SWITCH__", "__NX__", "MA_SWITCH", "PFD_SWITCH")
|
||||||
|
|
||||||
|
-- SimpleIni 配置:不使用 Windows API
|
||||||
|
add_defines("SI_NO_CONVERSION")
|
||||||
|
|
||||||
|
-- libnx 路径 - 必须在工具链级别添加
|
||||||
|
add_includedirs(path.join(devkitPro, "libnx/include"))
|
||||||
|
add_linkdirs(path.join(devkitPro, "libnx/lib"))
|
||||||
|
|
||||||
|
-- portlibs 路径(EGL + 桌面 OpenGL + SDL2)
|
||||||
|
add_includedirs(path.join(devkitPro, "portlibs/switch/include"))
|
||||||
|
add_includedirs(path.join(devkitPro, "portlibs/switch/include/SDL2"))
|
||||||
|
add_linkdirs(path.join(devkitPro, "portlibs/switch/lib"))
|
||||||
|
|
||||||
|
add_syslinks("SDL2_mixer", "SDL2", "opusfile", "opus", "vorbisidec", "ogg",
|
||||||
|
"modplug", "mpg123", "FLAC", "GLESv2", "EGL", "glapi", "drm_nouveau",
|
||||||
|
{public = true})
|
||||||
|
add_syslinks("nx", "m")
|
||||||
|
|
||||||
|
-- 构建后生成 NRO 文件
|
||||||
|
after_build(function (target)
|
||||||
|
local elf_file = target:targetfile()
|
||||||
|
local output_dir = path.directory(elf_file)
|
||||||
|
local nacp_file = path.join(output_dir, "hello_world.nacp")
|
||||||
|
local nro_file = path.join(output_dir, "hello_world.nro")
|
||||||
|
local nacptool = path.join(devkitPro, "tools/bin/nacptool.exe")
|
||||||
|
local elf2nro = path.join(devkitPro, "tools/bin/elf2nro.exe")
|
||||||
|
|
||||||
|
if os.isfile(nacptool) and os.isfile(elf2nro) then
|
||||||
|
os.vrunv(nacptool, {"--create", "Hello World", "Extra2D Team", "1.0.0", nacp_file})
|
||||||
|
local romfs = path.join(example_dir, "romfs")
|
||||||
|
if os.isdir(romfs) then
|
||||||
|
os.vrunv(elf2nro, {elf_file, nro_file, "--nacp=" .. nacp_file, "--romfsdir=" .. romfs})
|
||||||
|
else
|
||||||
|
os.vrunv(elf2nro, {elf_file, nro_file, "--nacp=" .. nacp_file})
|
||||||
|
end
|
||||||
|
print("Generated NRO: " .. nro_file)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
target_end()
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
in vec4 vColor;
|
|
||||||
out vec4 fragColor;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
fragColor = vColor;
|
|
||||||
}
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
layout(location = 0) in vec2 aPosition;
|
|
||||||
layout(location = 1) in vec4 aColor;
|
|
||||||
|
|
||||||
uniform mat4 u_viewProjection;
|
|
||||||
|
|
||||||
out vec4 vColor;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
gl_Position = u_viewProjection * vec4(aPosition, 0.0, 1.0);
|
|
||||||
vColor = aColor;
|
|
||||||
}
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
#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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
#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;
|
|
||||||
}
|
|
||||||
|
|
@ -14,12 +14,13 @@ local supported_plats = {mingw = true, windows = true, linux = true, macosx = tr
|
||||||
|
|
||||||
-- 自动选择平台
|
-- 自动选择平台
|
||||||
if not supported_plats[target_plat] then
|
if not supported_plats[target_plat] then
|
||||||
raise("Unsupported platform: " .. target_plat .. ". Supported platforms: mingw, windows, linux, macosx, switch")
|
os.exists(1)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
-- 引入对应平台的配置文件
|
-- 引入对应平台的配置文件
|
||||||
local platform_config_file = "platform/" .. target_plat .. ".lua"
|
local platform_config_file = "platform/" .. target_plat .. ".lua"
|
||||||
|
|
||||||
if os.isfile(platform_config_file) then
|
if os.isfile(platform_config_file) then
|
||||||
includes(platform_config_file)
|
includes(platform_config_file)
|
||||||
else
|
else
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue