渲染后端加入
This commit is contained in:
commit
a5379b3816
|
|
@ -0,0 +1,14 @@
|
|||
# Xmake cache
|
||||
.xmake/
|
||||
build/
|
||||
|
||||
# MacOS Cache
|
||||
.DS_Store
|
||||
|
||||
|
||||
.vscode/
|
||||
.cache/
|
||||
|
||||
*.json
|
||||
|
||||
.vscode/compile_commands.json
|
||||
|
|
@ -0,0 +1,567 @@
|
|||
# Frostbite2D 引擎架构说明
|
||||
|
||||
## 1. 整体架构图
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ 用户应用层 (main.cpp) │
|
||||
│ 创建配置 → 初始化应用 → 游戏循环 → 清理 │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ 应用程序层 (Application) │
|
||||
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
|
||||
│ │ 单例管理 │ │ 模块系统 │ │ 时间管理 │ │ 窗口管理 │ │
|
||||
│ │ get() │ │ use/init │ │ deltaTime() │ │ Window │ │
|
||||
│ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │
|
||||
│ │ │
|
||||
│ ┌───────────┴───────────┐ │
|
||||
│ ▼ ▼ │
|
||||
│ mainLoop() ────────► update()/render() │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
┌────────────────────────────┼────────────────────────────┐
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
┌─────────────────┐ ┌─────────────────────┐ ┌─────────────────────┐
|
||||
│ 渲染系统 │ │ 着色器系统 │ │ 输入系统 │
|
||||
│ (GLRenderer) │◄────►│ (ShaderManager) │ │ (SDL Events) │
|
||||
└─────────────────┘ └─────────────────────┘ └─────────────────────┘
|
||||
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ 渲染核心层 │
|
||||
│ ┌────────────────┐ ┌────────────────┐ ┌────────────────┐ ┌───────────┐ │
|
||||
│ │ 2D图形绘制 │ │ 精灵批处理 │ │ 字体渲染 │ │ 相机 │ │
|
||||
│ │ drawRect() │ │ GLSpriteBatch │ │ GLFontAtlas │ │ Camera │ │
|
||||
│ │ fillCircle() │ │ 10000 sprites │ │ stb_truetype │ │ view/proj │ │
|
||||
│ │ drawLine() │ │ drawSprite() │ │ cache glyphs │ │ move/zoom │ │
|
||||
│ │ drawText() │ │ endBatch() │ │ measureText() │ │ │ │
|
||||
│ └────────────────┘ └────────────────┘ └────────────────┘ └───────────┘ │
|
||||
│ │
|
||||
│ ┌────────────────┐ ┌────────────────┐ ┌────────────────┐ │
|
||||
│ │ 纹理管理 │ │ 着色器实现 │ │ OpenGL资源 │ │
|
||||
│ │ GLTexture │ │ GLShader │ │ VAO/VBO │ │
|
||||
│ │ load from file│ │ vertex/frag │ │ shape/line │ │
|
||||
│ └────────────────┘ └────────────────┘ └────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ 平台抽象层 │
|
||||
│ ┌─────────────────────────┐ ┌─────────────────────────────────────┐ │
|
||||
│ │ Window 窗口 │ │ SDL2 跨平台库 │ │
|
||||
│ │ - 创建/销毁窗口 │ │ 窗口管理 | 事件处理 | OpenGL上下文 │ │
|
||||
│ │ - OpenGL 上下文 │ │ │ │
|
||||
│ │ - 事件轮询 │ │ 支持: Windows | Linux | macOS │ │
|
||||
│ │ - 交换缓冲区 │ │ Android | iOS | Switch │ │
|
||||
│ └─────────────────────────┘ └─────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ 第三方库 │
|
||||
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │
|
||||
│ │ OpenGL │ │ SDL2 │ │ GLAD │ │ GLM │ │ stb_truetype │ │
|
||||
│ │ ES 3.2 │ │ 2.32.2 │ │ Loader │ │ 1.0.3 │ │ rect_pack │ │
|
||||
│ │ 图形API │ │ 窗口/输入 │ │ 函数加载 │ │ 数学库 │ │ 字体渲染 │ │
|
||||
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ └──────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## 2. 模块详细说明
|
||||
|
||||
### 2.1 应用程序层 (Application)
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────┐
|
||||
│ Application 单例 │
|
||||
├─────────────────────────────────────────┤
|
||||
│ + get() : Application& │
|
||||
│ + init(config) : bool │
|
||||
│ + run() : void │
|
||||
│ + quit() : void │
|
||||
│ + deltaTime() : float │
|
||||
│ + fps() : int │
|
||||
├─────────────────────────────────────────┤
|
||||
│ - window_ : Window* │
|
||||
│ - modules_ : vector<Module*> │
|
||||
│ - running_ : bool │
|
||||
│ - deltaTime_ : float │
|
||||
│ - currentFps_ : int │
|
||||
└─────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**生命周期流程:**
|
||||
```
|
||||
main()
|
||||
│
|
||||
├─► AppConfig config = AppConfig::createDefault()
|
||||
│
|
||||
├─► Application::get().init(config)
|
||||
│ ├─► 创建 Window
|
||||
│ ├─► 初始化 SDL
|
||||
│ └─► 初始化 GLAD (OpenGL 函数加载)
|
||||
│
|
||||
├─► ShaderManager::getInstance().init(factory)
|
||||
│ ├─► 加载 sprite shader
|
||||
│ └─► 加载 shape shader
|
||||
│
|
||||
├─► GLRenderer.init(window)
|
||||
│ ├─► 创建 VAO/VBO
|
||||
│ ├─► 初始化 SpriteBatch
|
||||
│ └─► 设置 OpenGL 状态
|
||||
│
|
||||
├─► 游戏主循环
|
||||
│ ├─► 处理输入事件 (SDL_PollEvent)
|
||||
│ ├─► renderer.beginFrame(color) // 清除缓冲区
|
||||
│ ├─► 绘制图形
|
||||
│ │ ├─► fillRect() 填充矩形
|
||||
│ │ ├─► fillCircle() 填充圆形
|
||||
│ │ ├─► drawLine() 绘制线条
|
||||
│ │ ├─► drawText() 绘制文字
|
||||
│ │ └─► ...
|
||||
│ ├─► renderer.endFrame() // 提交批次
|
||||
│ └─► SDL_GL_SwapWindow() // 显示到屏幕
|
||||
│
|
||||
├─► 清理资源
|
||||
│ ├─► renderer.shutdown()
|
||||
│ ├─► ShaderManager::shutdown()
|
||||
│ └─► Application::shutdown()
|
||||
│
|
||||
└─► return 0
|
||||
```
|
||||
|
||||
### 2.2 渲染系统 (GLRenderer)
|
||||
|
||||
**核心功能:**
|
||||
|
||||
| 功能类别 | 方法 | 说明 |
|
||||
|---------|------|------|
|
||||
| **帧管理** | `beginFrame()` / `endFrame()` | 开始/结束渲染帧 |
|
||||
| **2D形状** | `fillRect()` / `drawRect()` | 填充/描边矩形 |
|
||||
| **圆形** | `fillCircle()` / `drawCircle()` | 填充/描边圆形 |
|
||||
| **线条** | `drawLine()` | 绘制线段 |
|
||||
| **三角形** | `fillTriangle()` / `drawTriangle()` | 填充/描边三角形 |
|
||||
| **多边形** | `fillPolygon()` / `drawPolygon()` | 填充/描边多边形 |
|
||||
| **精灵** | `drawSprite()` | 绘制纹理精灵 |
|
||||
| **文字** | `drawText()` | 渲染文字 |
|
||||
| **变换** | `pushTransform()` / `popTransform()` | 矩阵变换栈 |
|
||||
| **视口** | `setViewport()` | 设置渲染区域 |
|
||||
| **混合** | `setBlendMode()` | 设置混合模式 |
|
||||
|
||||
**渲染管线:**
|
||||
|
||||
```
|
||||
┌──────────────┐ ┌────────────────────────────────────────────────┐
|
||||
│ drawRect() │────►│ │
|
||||
└──────────────┘ │ 形状批处理缓冲区 │
|
||||
┌──────────────┐ │ shapeVertexCache_[MAX_SHAPE_VERTICES] │
|
||||
│ fillCircle() │────►│ │
|
||||
└──────────────┘ │ 顶点格式: {x, y, r, g, b, a} │
|
||||
┌──────────────┐ │ │
|
||||
│fillTriangle()│────►│ 当缓冲区满或模式改变时: flushShapeBatch() │
|
||||
└──────────────┘ │ │
|
||||
└────────────────────┬───────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌────────────────────────────────────────────────┐
|
||||
│ 线条批处理缓冲区 │
|
||||
│ lineVertexCache_[MAX_LINE_VERTICES] │
|
||||
│ │
|
||||
│ 绘制模式: GL_LINES (每线条2顶点) │
|
||||
│ 支持线宽设置 │
|
||||
└────────────────────┬───────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌────────────────────────────────────────────────┐
|
||||
│ 精灵批处理系统 │
|
||||
│ GLSpriteBatch (10000精灵) │
|
||||
│ │
|
||||
│ 图集纹理 → 顶点数据 → VBO → GPU 批量绘制 │
|
||||
│ │
|
||||
│ beginSpriteBatch() → drawSprite() → endBatch()│
|
||||
└────────────────────┬───────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌────────────────────────────────────────────────┐
|
||||
│ OpenGL 渲染 │
|
||||
│ │
|
||||
│ glDrawArrays(GL_TRIANGLES, ...) │
|
||||
│ glDrawArrays(GL_LINES, ...) │
|
||||
└────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 2.3 着色器系统 (ShaderManager)
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ ShaderManager 单例 │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌──────────────┐ ┌────────────────────────────────────┐ │
|
||||
│ │ IShader │◄───┤ GLShader (OpenGL实现) │ │
|
||||
│ │ 接口 │ │ - 编译 vertex/fragment shader │ │
|
||||
│ │ │ │ - 创建 program │ │
|
||||
│ │ bind() │ │ - 设置 uniform │ │
|
||||
│ │ setUniform() │ │ - 绑定/解绑 │ │
|
||||
│ └──────────────┘ └────────────────────────────────────┘ │
|
||||
│ ▲ │
|
||||
│ │ │
|
||||
│ ┌──────────────┐ ┌────────────────────────────────────┐ │
|
||||
│ │ IShaderFactory│◄──┤ GLShaderFactory │ │
|
||||
│ │ 工厂接口 │ │ - createShader() │ │
|
||||
│ │ │ │ - 创建 OpenGL Shader 实例 │ │
|
||||
│ └──────────────┘ └────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ 管理: unordered_map<string, ShaderInfo> shaders_ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**内置着色器:**
|
||||
|
||||
| 着色器 | 文件 | 用途 |
|
||||
|-------|------|------|
|
||||
| **sprite** | sprite.vert / sprite.frag | 纹理精灵渲染 (支持颜色混合) |
|
||||
| **shape** | shape.vert / shape.frag | 2D形状渲染 (顶点颜色) |
|
||||
|
||||
### 2.4 字体渲染 (GLFontAtlas)
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ 字体图集系统 │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ 初始化流程: │
|
||||
│ ┌───────────┐ ┌───────────┐ ┌──────────────────────┐ │
|
||||
│ │ 加载字体 │───►│ 初始化stb │───►│ 创建 1024x1024 图集 │ │
|
||||
│ │ 文件(.ttf)│ │ _truetype │ │ 纹理 (RGBA) │ │
|
||||
│ └───────────┘ └───────────┘ └──────────────────────┘ │
|
||||
│ │ │
|
||||
│ 渲染字符时: │ │
|
||||
│ ┌───────────┐ ┌───────────┐ ┌────────▼──────────┐ │
|
||||
│ │ 字符查缓存 │───►│ 缓存未命中 │───►│ stbtt_MakeCodepoint│ │
|
||||
│ │ │ │ │ │ _Bitmap() 栅格化 │ │
|
||||
│ └─────┬─────┘ └───────────┘ └────────┬──────────┘ │
|
||||
│ │ │ │
|
||||
│ │ ┌───────────┐ ┌─────────▼──────────┐ │
|
||||
│ └────────►│ 返回字形 │◄───│ stb_rect_pack 打包 │ │
|
||||
│ │ 信息 │ │ 到图集纹理 │ │
|
||||
│ └───────────┘ └────────────────────┘ │
|
||||
│ │
|
||||
│ Glyph 信息: │
|
||||
│ - 尺寸 (width, height) │
|
||||
│ - 偏移 (bearingX, bearingY) │
|
||||
│ - 步进 (advance) │
|
||||
│ - 纹理坐标 (u0, v0, u1, v1) │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 2.5 相机系统 (Camera)
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────┐
|
||||
│ Camera 相机 │
|
||||
├─────────────────────────────────────────┤
|
||||
│ │
|
||||
│ 视口配置: │
|
||||
│ ┌─────────────────────────────────┐ │
|
||||
│ │ left = 0 │ │
|
||||
│ │ right = 800 ─────────────── │ │
|
||||
│ │ top = 0 │ │ │ │
|
||||
│ │ bottom = 600 │ 世界坐标 │ │ │
|
||||
│ │ │ │ │ │
|
||||
│ │ 默认位置(0,0) │ │ │ │
|
||||
│ │ 看向左上角 └─────────────┘ │ │
|
||||
│ │ 视口 (0,0) │ │
|
||||
│ └─────────────────────────────────┘ │
|
||||
│ │
|
||||
│ 变换矩阵: │
|
||||
│ ┌───────────────┐ │
|
||||
│ │ viewMatrix │ 视图矩阵 (相机位置) │
|
||||
│ ├───────────────┤ │
|
||||
│ │ projMatrix │ 正交投影矩阵 │
|
||||
│ ├───────────────┤ │
|
||||
│ │ viewProj │ view * projection │
|
||||
│ └───────────────┘ │
|
||||
│ │
|
||||
│ 控制: │
|
||||
│ - move(x, y) 移动相机 │
|
||||
│ - setZoom(z) 设置缩放 │
|
||||
│ - setPosition() 设置位置 │
|
||||
│ │
|
||||
└─────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 2.6 模块系统 (Module)
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────┐
|
||||
│ Module 模块基类 │
|
||||
├─────────────────────────────────────────┤
|
||||
│ + setupModule() 初始化 │
|
||||
│ + destroyModule() 销毁 │
|
||||
│ + onUpdate() 每帧更新 │
|
||||
│ + beforeRender() 渲染前 │
|
||||
│ + onRender() 渲染时 │
|
||||
│ + afterRender() 渲染后 │
|
||||
│ + handleEvent() 事件处理 │
|
||||
│ + getName() 模块名称 │
|
||||
│ + getPriority() 优先级 (越小越优先) │
|
||||
└─────────────────────────────────────────┘
|
||||
▲
|
||||
│ 继承
|
||||
┌──────────────┼──────────────┐
|
||||
│ │ │
|
||||
┌───┴───┐ ┌─────┴─────┐ ┌────┴────┐
|
||||
│渲染模块│ │ 物理模块 │ │音频模块 │
|
||||
│ │ │ │ │ │
|
||||
└───────┘ └───────────┘ └─────────┘
|
||||
```
|
||||
|
||||
## 3. 数据流图
|
||||
|
||||
### 3.1 一帧的渲染流程
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────────────┐
|
||||
│ 游戏主循环 │
|
||||
└─────────────────────────────────┬────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌──────────────────────────────────────────────────────────────────────┐
|
||||
│ ① 处理输入事件 │
|
||||
│ SDL_PollEvent() → 键盘/鼠标事件 → 更新相机/游戏逻辑 │
|
||||
└─────────────────────────────────┬────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌──────────────────────────────────────────────────────────────────────┐
|
||||
│ ② 开始新帧 (beginFrame) │
|
||||
│ glClear() 清除颜色缓冲区 + 重置渲染统计 │
|
||||
└─────────────────────────────────┬────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌──────────────────────────────────────────────────────────────────────┐
|
||||
│ ③ 设置渲染状态 │
|
||||
│ setViewport() + setViewProjection(camera.getViewProjectionMatrix)│
|
||||
└─────────────────────────────────┬────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌──────────────────────────────────────────────────────────────────────┐
|
||||
│ ④ 绘制各种图形 │
|
||||
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
|
||||
│ │ fillRect() │ │fillCircle() │ │ drawLine() │ │drawSprite() │ │
|
||||
│ │ │ │ │ │ │ │ │ │
|
||||
│ │ 顶点数据 ──►│ │ 顶点数据 ──►│ │ 顶点数据 ──►│ │ 精灵数据 ──►│ │
|
||||
│ │ 形状缓冲区 │ │ 形状缓冲区 │ │ 线条缓冲区 │ │ 精灵批次 │ │
|
||||
│ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │
|
||||
└─────────────────────────────────┬────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌──────────────────────────────────────────────────────────────────────┐
|
||||
│ ⑤ 结束帧 (endFrame) │
|
||||
│ flushShapeBatch() + flushLineBatch() + endSpriteBatch() │
|
||||
│ 提交所有待渲染数据到 GPU │
|
||||
└─────────────────────────────────┬────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌──────────────────────────────────────────────────────────────────────┐
|
||||
│ ⑥ 交换缓冲区 │
|
||||
│ SDL_GL_SwapWindow() 将渲染结果显示到屏幕 │
|
||||
└──────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 3.2 资源加载流程
|
||||
|
||||
```
|
||||
纹理加载:
|
||||
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
|
||||
│ 文件路径 │───►│ stb_image│───►│ 像素数据 │───►│ GLTexture │
|
||||
│ .png/.jpg│ │ 解码 │ │ RGBA │ │ glTexImage2D
|
||||
└──────────┘ └──────────┘ └──────────┘ └──────────┘
|
||||
|
||||
字体加载:
|
||||
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
|
||||
│ .ttf文件 │───►│读取到内存│───►│stbtt_Init│───►│GLFontAtlas│
|
||||
│ │ │ │ │Font() │ │ 创建图集 │
|
||||
└──────────┘ └──────────┘ └──────────┘ └──────────┘
|
||||
|
||||
着色器加载:
|
||||
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
|
||||
│.vert文件 │───►│读取源码 │───►│glCompile │───►│glLink │
|
||||
│.frag文件 │───►│ │ │Shader() │ │Program │
|
||||
└──────────┘ └──────────┘ └──────────┘ └──────────┘
|
||||
```
|
||||
|
||||
## 4. 项目目录结构
|
||||
|
||||
```
|
||||
Frostbite2D/
|
||||
├── xmake.lua # 主构建配置
|
||||
├── platform/
|
||||
│ └── windows.lua # Windows 平台配置
|
||||
├── shaders/
|
||||
│ ├── shape.vert # 形状着色器 - 顶点
|
||||
│ ├── shape.frag # 形状着色器 - 片段
|
||||
│ ├── sprite.vert # 精灵着色器 - 顶点
|
||||
│ └── sprite.frag # 精灵着色器 - 片段
|
||||
└── Fostbite2D/
|
||||
├── include/
|
||||
│ ├── fostbite2D/
|
||||
│ │ ├── app/
|
||||
│ │ │ └── application.h # 应用主类
|
||||
│ │ ├── config/
|
||||
│ │ │ ├── app_config.h # 应用配置
|
||||
│ │ │ └── platform_config.h # 平台配置
|
||||
│ │ ├── core/
|
||||
│ │ │ ├── color.h # 颜色类
|
||||
│ │ │ ├── math_types.h # 数学类型
|
||||
│ │ │ └── types.h # 基础类型定义
|
||||
│ │ ├── module/
|
||||
│ │ │ └── module.h # 模块基类
|
||||
│ │ ├── platform/
|
||||
│ │ │ └── window.h # 窗口类
|
||||
│ │ └── render/
|
||||
│ │ ├── camera.h # 相机
|
||||
│ │ ├── font.h # 字体接口
|
||||
│ │ ├── texture.h # 纹理接口
|
||||
│ │ ├── opengl/
|
||||
│ │ │ ├── gl_font_atlas.h # OpenGL字体图集
|
||||
│ │ │ ├── gl_renderer.h # OpenGL渲染器
|
||||
│ │ │ ├── gl_shader.h # OpenGL着色器
|
||||
│ │ │ ├── gl_sprite_batch.h # 精灵批处理
|
||||
│ │ │ └── gl_texture.h # OpenGL纹理
|
||||
│ │ └── shader/
|
||||
│ │ ├── shader_interface.h # 着色器接口
|
||||
│ │ └── shader_manager.h # 着色器管理器
|
||||
│ ├── glad/
|
||||
│ │ └── glad.h # OpenGL 加载器
|
||||
│ ├── KHR/
|
||||
│ │ └── khrplatform.h # Khronos 平台定义
|
||||
│ └── stb/
|
||||
│ ├── stb_image.h # 图片加载
|
||||
│ ├── stb_rect_pack.h # 矩形打包
|
||||
│ └── stb_truetype.h # 字体渲染
|
||||
└── src/
|
||||
├── main.cpp # 程序入口
|
||||
├── glad/
|
||||
│ └── glad.c # GLAD 实现
|
||||
└── fostbite2D/
|
||||
├── app/
|
||||
│ └── application.cpp # Application 实现
|
||||
├── config/
|
||||
│ └── app_config.cpp # 配置实现
|
||||
├── platform/
|
||||
│ └── window.cpp # Window 实现
|
||||
└── render/
|
||||
├── camera.cpp # Camera 实现
|
||||
├── texture.cpp # Texture 实现
|
||||
├── opengl/ # OpenGL 实现
|
||||
└── shader/ # Shader 实现
|
||||
```
|
||||
|
||||
## 5. 核心类关系图
|
||||
|
||||
```
|
||||
┌─────────────────┐
|
||||
│ Application │
|
||||
│ 单例 │
|
||||
└────────┬────────┘
|
||||
│
|
||||
┌───────────────────────────┼───────────────────────────┐
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
||||
│ Window │◄───────►│ ShaderManager │◄───────►│ GLRenderer │
|
||||
│ (SDL2) │ │ 单例 │ │ │
|
||||
└─────────────────┘ └────────┬────────┘ └───────┬─────────┘
|
||||
│ │
|
||||
▼ ▼
|
||||
┌─────────────────┐ ┌─────────────────┐
|
||||
│ IShader │ │ GLSpriteBatch │
|
||||
│ 接口 │ │ 精灵批处理 │
|
||||
└────────┬────────┘ └───────┬─────────┘
|
||||
│ │
|
||||
▼ ▼
|
||||
┌─────────────────┐ ┌─────────────────┐
|
||||
│ GLShader │ │ GLTexture │
|
||||
│ OpenGL实现 │ │ 纹理管理 │
|
||||
└─────────────────┘ └─────────────────┘
|
||||
|
||||
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
||||
│ Camera │◄───────►│ GLFontAtlas │◄───────►│ FontAtlas │
|
||||
│ 2D相机 │ │ 字体图集 │ │ 接口 │
|
||||
│ view/proj矩阵 │ │ stb_truetype │ │ │
|
||||
└─────────────────┘ └─────────────────┘ └─────────────────┘
|
||||
```
|
||||
|
||||
## 6. 关键技术点
|
||||
|
||||
### 6.1 批处理渲染 (Batch Rendering)
|
||||
|
||||
- **形状批处理**: 8192 个顶点缓冲区,减少 draw call
|
||||
- **线条批处理**: 16384 个顶点,支持线宽变化时 flush
|
||||
- **精灵批处理**: 10000 个精灵,使用单一 draw call
|
||||
|
||||
### 6.2 字体图集 (Font Atlas)
|
||||
|
||||
- 使用 `stb_rect_pack` 进行矩形打包
|
||||
- 1024x1024 RGBA 纹理存储字形
|
||||
- 动态缓存:首次使用字符时渲染到图集
|
||||
|
||||
### 6.3 坐标系
|
||||
|
||||
- **世界坐标**: Y轴向下 (0,0) 在左上角
|
||||
- **纹理坐标**: OpenGL 标准,(0,0) 在左下角
|
||||
- **相机**: 正交投影,可移动/缩放
|
||||
|
||||
### 6.4 渲染状态管理
|
||||
|
||||
- 自动处理 OpenGL 状态缓存
|
||||
- 混合模式切换 (None/Alpha/Additive/Multiply)
|
||||
- 变换矩阵栈支持嵌套变换
|
||||
|
||||
## 7. 使用示例
|
||||
|
||||
```cpp
|
||||
// 1. 初始化
|
||||
AppConfig config = AppConfig::createDefault();
|
||||
config.windowConfig.title = "My Game";
|
||||
Application::get().init(config);
|
||||
|
||||
// 2. 初始化渲染
|
||||
GLRenderer renderer;
|
||||
renderer.init(sdlWindow);
|
||||
|
||||
// 3. 加载字体
|
||||
GLFontAtlas font("C:/Windows/Fonts/arial.ttf", 24);
|
||||
|
||||
// 4. 游戏循环
|
||||
while (running) {
|
||||
// 处理事件...
|
||||
|
||||
// 开始渲染
|
||||
renderer.beginFrame(Color(0.1f, 0.1f, 0.15f, 1.0f));
|
||||
renderer.setViewProjection(camera.getViewProjectionMatrix());
|
||||
|
||||
// 绘制图形
|
||||
renderer.fillRect(Rect(100, 100, 200, 150), Colors::Red);
|
||||
renderer.fillCircle(Vec2(400, 300), 50.0f, Colors::Blue, 32);
|
||||
renderer.drawLine(Vec2(0, 0), Vec2(800, 600), Colors::White, 2.0f);
|
||||
|
||||
// 绘制文字
|
||||
renderer.beginSpriteBatch();
|
||||
renderer.drawText(font, "Hello World!", 100, 100, Colors::White);
|
||||
renderer.endSpriteBatch();
|
||||
|
||||
// 结束渲染
|
||||
renderer.endFrame();
|
||||
SDL_GL_SwapWindow(sdlWindow);
|
||||
}
|
||||
|
||||
// 5. 清理
|
||||
renderer.shutdown();
|
||||
Application::get().shutdown();
|
||||
```
|
||||
|
|
@ -0,0 +1,311 @@
|
|||
#ifndef __khrplatform_h_
|
||||
#define __khrplatform_h_
|
||||
|
||||
/*
|
||||
** Copyright (c) 2008-2018 The Khronos Group Inc.
|
||||
**
|
||||
** Permission is hereby granted, free of charge, to any person obtaining a
|
||||
** copy of this software and/or associated documentation files (the
|
||||
** "Materials"), to deal in the Materials without restriction, including
|
||||
** without limitation the rights to use, copy, modify, merge, publish,
|
||||
** distribute, sublicense, and/or sell copies of the Materials, and to
|
||||
** permit persons to whom the Materials are furnished to do so, subject to
|
||||
** the following conditions:
|
||||
**
|
||||
** The above copyright notice and this permission notice shall be included
|
||||
** in all copies or substantial portions of the Materials.
|
||||
**
|
||||
** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
|
||||
*/
|
||||
|
||||
/* Khronos platform-specific types and definitions.
|
||||
*
|
||||
* The master copy of khrplatform.h is maintained in the Khronos EGL
|
||||
* Registry repository at https://github.com/KhronosGroup/EGL-Registry
|
||||
* The last semantic modification to khrplatform.h was at commit ID:
|
||||
* 67a3e0864c2d75ea5287b9f3d2eb74a745936692
|
||||
*
|
||||
* Adopters may modify this file to suit their platform. Adopters are
|
||||
* encouraged to submit platform specific modifications to the Khronos
|
||||
* group so that they can be included in future versions of this file.
|
||||
* Please submit changes by filing pull requests or issues on
|
||||
* the EGL Registry repository linked above.
|
||||
*
|
||||
*
|
||||
* See the Implementer's Guidelines for information about where this file
|
||||
* should be located on your system and for more details of its use:
|
||||
* http://www.khronos.org/registry/implementers_guide.pdf
|
||||
*
|
||||
* This file should be included as
|
||||
* #include <KHR/khrplatform.h>
|
||||
* by Khronos client API header files that use its types and defines.
|
||||
*
|
||||
* The types in khrplatform.h should only be used to define API-specific types.
|
||||
*
|
||||
* Types defined in khrplatform.h:
|
||||
* khronos_int8_t signed 8 bit
|
||||
* khronos_uint8_t unsigned 8 bit
|
||||
* khronos_int16_t signed 16 bit
|
||||
* khronos_uint16_t unsigned 16 bit
|
||||
* khronos_int32_t signed 32 bit
|
||||
* khronos_uint32_t unsigned 32 bit
|
||||
* khronos_int64_t signed 64 bit
|
||||
* khronos_uint64_t unsigned 64 bit
|
||||
* khronos_intptr_t signed same number of bits as a pointer
|
||||
* khronos_uintptr_t unsigned same number of bits as a pointer
|
||||
* khronos_ssize_t signed size
|
||||
* khronos_usize_t unsigned size
|
||||
* khronos_float_t signed 32 bit floating point
|
||||
* khronos_time_ns_t unsigned 64 bit time in nanoseconds
|
||||
* khronos_utime_nanoseconds_t unsigned time interval or absolute time in
|
||||
* nanoseconds
|
||||
* khronos_stime_nanoseconds_t signed time interval in nanoseconds
|
||||
* khronos_boolean_enum_t enumerated boolean type. This should
|
||||
* only be used as a base type when a client API's boolean type is
|
||||
* an enum. Client APIs which use an integer or other type for
|
||||
* booleans cannot use this as the base type for their boolean.
|
||||
*
|
||||
* Tokens defined in khrplatform.h:
|
||||
*
|
||||
* KHRONOS_FALSE, KHRONOS_TRUE Enumerated boolean false/true values.
|
||||
*
|
||||
* KHRONOS_SUPPORT_INT64 is 1 if 64 bit integers are supported; otherwise 0.
|
||||
* KHRONOS_SUPPORT_FLOAT is 1 if floats are supported; otherwise 0.
|
||||
*
|
||||
* Calling convention macros defined in this file:
|
||||
* KHRONOS_APICALL
|
||||
* KHRONOS_APIENTRY
|
||||
* KHRONOS_APIATTRIBUTES
|
||||
*
|
||||
* These may be used in function prototypes as:
|
||||
*
|
||||
* KHRONOS_APICALL void KHRONOS_APIENTRY funcname(
|
||||
* int arg1,
|
||||
* int arg2) KHRONOS_APIATTRIBUTES;
|
||||
*/
|
||||
|
||||
#if defined(__SCITECH_SNAP__) && !defined(KHRONOS_STATIC)
|
||||
# define KHRONOS_STATIC 1
|
||||
#endif
|
||||
|
||||
/*-------------------------------------------------------------------------
|
||||
* Definition of KHRONOS_APICALL
|
||||
*-------------------------------------------------------------------------
|
||||
* This precedes the return type of the function in the function prototype.
|
||||
*/
|
||||
#if defined(KHRONOS_STATIC)
|
||||
/* If the preprocessor constant KHRONOS_STATIC is defined, make the
|
||||
* header compatible with static linking. */
|
||||
# define KHRONOS_APICALL
|
||||
#elif defined(_WIN32)
|
||||
# define KHRONOS_APICALL __declspec(dllimport)
|
||||
#elif defined (__SYMBIAN32__)
|
||||
# define KHRONOS_APICALL IMPORT_C
|
||||
#elif defined(__ANDROID__)
|
||||
# define KHRONOS_APICALL __attribute__((visibility("default")))
|
||||
#else
|
||||
# define KHRONOS_APICALL
|
||||
#endif
|
||||
|
||||
/*-------------------------------------------------------------------------
|
||||
* Definition of KHRONOS_APIENTRY
|
||||
*-------------------------------------------------------------------------
|
||||
* This follows the return type of the function and precedes the function
|
||||
* name in the function prototype.
|
||||
*/
|
||||
#if defined(_WIN32) && !defined(_WIN32_WCE) && !defined(__SCITECH_SNAP__)
|
||||
/* Win32 but not WinCE */
|
||||
# define KHRONOS_APIENTRY __stdcall
|
||||
#else
|
||||
# define KHRONOS_APIENTRY
|
||||
#endif
|
||||
|
||||
/*-------------------------------------------------------------------------
|
||||
* Definition of KHRONOS_APIATTRIBUTES
|
||||
*-------------------------------------------------------------------------
|
||||
* This follows the closing parenthesis of the function prototype arguments.
|
||||
*/
|
||||
#if defined (__ARMCC_2__)
|
||||
#define KHRONOS_APIATTRIBUTES __softfp
|
||||
#else
|
||||
#define KHRONOS_APIATTRIBUTES
|
||||
#endif
|
||||
|
||||
/*-------------------------------------------------------------------------
|
||||
* basic type definitions
|
||||
*-----------------------------------------------------------------------*/
|
||||
#if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(__GNUC__) || defined(__SCO__) || defined(__USLC__)
|
||||
|
||||
|
||||
/*
|
||||
* Using <stdint.h>
|
||||
*/
|
||||
#include <stdint.h>
|
||||
typedef int32_t khronos_int32_t;
|
||||
typedef uint32_t khronos_uint32_t;
|
||||
typedef int64_t khronos_int64_t;
|
||||
typedef uint64_t khronos_uint64_t;
|
||||
#define KHRONOS_SUPPORT_INT64 1
|
||||
#define KHRONOS_SUPPORT_FLOAT 1
|
||||
/*
|
||||
* To support platform where unsigned long cannot be used interchangeably with
|
||||
* inptr_t (e.g. CHERI-extended ISAs), we can use the stdint.h intptr_t.
|
||||
* Ideally, we could just use (u)intptr_t everywhere, but this could result in
|
||||
* ABI breakage if khronos_uintptr_t is changed from unsigned long to
|
||||
* unsigned long long or similar (this results in different C++ name mangling).
|
||||
* To avoid changes for existing platforms, we restrict usage of intptr_t to
|
||||
* platforms where the size of a pointer is larger than the size of long.
|
||||
*/
|
||||
#if defined(__SIZEOF_LONG__) && defined(__SIZEOF_POINTER__)
|
||||
#if __SIZEOF_POINTER__ > __SIZEOF_LONG__
|
||||
#define KHRONOS_USE_INTPTR_T
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#elif defined(__VMS ) || defined(__sgi)
|
||||
|
||||
/*
|
||||
* Using <inttypes.h>
|
||||
*/
|
||||
#include <inttypes.h>
|
||||
typedef int32_t khronos_int32_t;
|
||||
typedef uint32_t khronos_uint32_t;
|
||||
typedef int64_t khronos_int64_t;
|
||||
typedef uint64_t khronos_uint64_t;
|
||||
#define KHRONOS_SUPPORT_INT64 1
|
||||
#define KHRONOS_SUPPORT_FLOAT 1
|
||||
|
||||
#elif defined(_WIN32) && !defined(__SCITECH_SNAP__)
|
||||
|
||||
/*
|
||||
* Win32
|
||||
*/
|
||||
typedef __int32 khronos_int32_t;
|
||||
typedef unsigned __int32 khronos_uint32_t;
|
||||
typedef __int64 khronos_int64_t;
|
||||
typedef unsigned __int64 khronos_uint64_t;
|
||||
#define KHRONOS_SUPPORT_INT64 1
|
||||
#define KHRONOS_SUPPORT_FLOAT 1
|
||||
|
||||
#elif defined(__sun__) || defined(__digital__)
|
||||
|
||||
/*
|
||||
* Sun or Digital
|
||||
*/
|
||||
typedef int khronos_int32_t;
|
||||
typedef unsigned int khronos_uint32_t;
|
||||
#if defined(__arch64__) || defined(_LP64)
|
||||
typedef long int khronos_int64_t;
|
||||
typedef unsigned long int khronos_uint64_t;
|
||||
#else
|
||||
typedef long long int khronos_int64_t;
|
||||
typedef unsigned long long int khronos_uint64_t;
|
||||
#endif /* __arch64__ */
|
||||
#define KHRONOS_SUPPORT_INT64 1
|
||||
#define KHRONOS_SUPPORT_FLOAT 1
|
||||
|
||||
#elif 0
|
||||
|
||||
/*
|
||||
* Hypothetical platform with no float or int64 support
|
||||
*/
|
||||
typedef int khronos_int32_t;
|
||||
typedef unsigned int khronos_uint32_t;
|
||||
#define KHRONOS_SUPPORT_INT64 0
|
||||
#define KHRONOS_SUPPORT_FLOAT 0
|
||||
|
||||
#else
|
||||
|
||||
/*
|
||||
* Generic fallback
|
||||
*/
|
||||
#include <stdint.h>
|
||||
typedef int32_t khronos_int32_t;
|
||||
typedef uint32_t khronos_uint32_t;
|
||||
typedef int64_t khronos_int64_t;
|
||||
typedef uint64_t khronos_uint64_t;
|
||||
#define KHRONOS_SUPPORT_INT64 1
|
||||
#define KHRONOS_SUPPORT_FLOAT 1
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
* Types that are (so far) the same on all platforms
|
||||
*/
|
||||
typedef signed char khronos_int8_t;
|
||||
typedef unsigned char khronos_uint8_t;
|
||||
typedef signed short int khronos_int16_t;
|
||||
typedef unsigned short int khronos_uint16_t;
|
||||
|
||||
/*
|
||||
* Types that differ between LLP64 and LP64 architectures - in LLP64,
|
||||
* pointers are 64 bits, but 'long' is still 32 bits. Win64 appears
|
||||
* to be the only LLP64 architecture in current use.
|
||||
*/
|
||||
#ifdef KHRONOS_USE_INTPTR_T
|
||||
typedef intptr_t khronos_intptr_t;
|
||||
typedef uintptr_t khronos_uintptr_t;
|
||||
#elif defined(_WIN64)
|
||||
typedef signed long long int khronos_intptr_t;
|
||||
typedef unsigned long long int khronos_uintptr_t;
|
||||
#else
|
||||
typedef signed long int khronos_intptr_t;
|
||||
typedef unsigned long int khronos_uintptr_t;
|
||||
#endif
|
||||
|
||||
#if defined(_WIN64)
|
||||
typedef signed long long int khronos_ssize_t;
|
||||
typedef unsigned long long int khronos_usize_t;
|
||||
#else
|
||||
typedef signed long int khronos_ssize_t;
|
||||
typedef unsigned long int khronos_usize_t;
|
||||
#endif
|
||||
|
||||
#if KHRONOS_SUPPORT_FLOAT
|
||||
/*
|
||||
* Float type
|
||||
*/
|
||||
typedef float khronos_float_t;
|
||||
#endif
|
||||
|
||||
#if KHRONOS_SUPPORT_INT64
|
||||
/* Time types
|
||||
*
|
||||
* These types can be used to represent a time interval in nanoseconds or
|
||||
* an absolute Unadjusted System Time. Unadjusted System Time is the number
|
||||
* of nanoseconds since some arbitrary system event (e.g. since the last
|
||||
* time the system booted). The Unadjusted System Time is an unsigned
|
||||
* 64 bit value that wraps back to 0 every 584 years. Time intervals
|
||||
* may be either signed or unsigned.
|
||||
*/
|
||||
typedef khronos_uint64_t khronos_utime_nanoseconds_t;
|
||||
typedef khronos_int64_t khronos_stime_nanoseconds_t;
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Dummy value used to pad enum types to 32 bits.
|
||||
*/
|
||||
#ifndef KHRONOS_MAX_ENUM
|
||||
#define KHRONOS_MAX_ENUM 0x7FFFFFFF
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Enumerated boolean type
|
||||
*
|
||||
* Values other than zero should be considered to be true. Therefore
|
||||
* comparisons should not be made against KHRONOS_TRUE.
|
||||
*/
|
||||
typedef enum {
|
||||
KHRONOS_FALSE = 0,
|
||||
KHRONOS_TRUE = 1,
|
||||
KHRONOS_BOOLEAN_ENUM_FORCE_SIZE = KHRONOS_MAX_ENUM
|
||||
} khronos_boolean_enum_t;
|
||||
|
||||
#endif /* __khrplatform_h_ */
|
||||
|
|
@ -0,0 +1,216 @@
|
|||
#pragma once
|
||||
|
||||
#include <fostbite2D/module/module.h>
|
||||
#include <fostbite2D/config/app_config.h>
|
||||
#include <fostbite2D/platform/window.h>
|
||||
#include <string>
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
/**
|
||||
* @brief 应用程序类
|
||||
*/
|
||||
class Application {
|
||||
public:
|
||||
/**
|
||||
* @brief 获取单例实例
|
||||
* @return 应用程序实例引用
|
||||
*/
|
||||
static Application& get();
|
||||
|
||||
Application(const Application&) = delete;
|
||||
Application& operator=(const Application&) = delete;
|
||||
|
||||
/**
|
||||
* @brief 添加模块
|
||||
* @param m 模块引用
|
||||
*/
|
||||
void use(Module& m);
|
||||
|
||||
/**
|
||||
* @brief 批量添加模块
|
||||
* @param modules 模块指针列表
|
||||
*/
|
||||
void use(std::initializer_list<Module*> modules);
|
||||
|
||||
/**
|
||||
* @brief 使用默认配置初始化
|
||||
* @return 初始化成功返回 true
|
||||
*/
|
||||
bool init();
|
||||
|
||||
/**
|
||||
* @brief 使用指定配置初始化
|
||||
* @param config 应用配置
|
||||
* @return 初始化成功返回 true
|
||||
*/
|
||||
bool init(const AppConfig& config);
|
||||
|
||||
/**
|
||||
* @brief 使用配置文件初始化
|
||||
* @param configPath 配置文件路径
|
||||
* @return 初始化成功返回 true
|
||||
*/
|
||||
bool init(const std::string& configPath);
|
||||
|
||||
/**
|
||||
* @brief 关闭应用程序
|
||||
*/
|
||||
void shutdown();
|
||||
|
||||
/**
|
||||
* @brief 运行主循环
|
||||
*/
|
||||
void run();
|
||||
|
||||
/**
|
||||
* @brief 请求退出
|
||||
*/
|
||||
void quit();
|
||||
|
||||
/**
|
||||
* @brief 暂停应用程序
|
||||
*/
|
||||
void pause();
|
||||
|
||||
/**
|
||||
* @brief 恢复应用程序
|
||||
*/
|
||||
void resume();
|
||||
|
||||
/**
|
||||
* @brief 检查是否暂停
|
||||
* @return 暂停状态返回 true
|
||||
*/
|
||||
bool isPaused() const { return paused_; }
|
||||
|
||||
/**
|
||||
* @brief 检查是否运行中
|
||||
* @return 运行中返回 true
|
||||
*/
|
||||
bool isRunning() const { return running_; }
|
||||
|
||||
// /**
|
||||
// * @brief 获取窗口
|
||||
// * @return 窗口引用
|
||||
// */
|
||||
// IWindow& window() { return *window_; }
|
||||
|
||||
// /**
|
||||
// * @brief 获取渲染器
|
||||
// * @return 渲染器引用
|
||||
// */
|
||||
// RenderBackend& renderer();
|
||||
|
||||
// /**
|
||||
// * @brief 获取场景服务
|
||||
// * @return 场景服务共享指针
|
||||
// */
|
||||
// SharedPtr<class ISceneService> scenes();
|
||||
|
||||
// /**
|
||||
// * @brief 获取计时器服务
|
||||
// * @return 计时器服务共享指针
|
||||
// */
|
||||
// SharedPtr<class ITimerService> timers();
|
||||
|
||||
// /**
|
||||
// * @brief 获取事件服务
|
||||
// * @return 事件服务共享指针
|
||||
// */
|
||||
// SharedPtr<class IEventService> events();
|
||||
|
||||
// /**
|
||||
// * @brief 获取相机服务
|
||||
// * @return 相机服务共享指针
|
||||
// */
|
||||
// SharedPtr<class ICameraService> camera();
|
||||
|
||||
// /**
|
||||
// * @brief 进入场景
|
||||
// * @param scene 场景指针
|
||||
// */
|
||||
// void enterScene(Ptr<class Scene> scene);
|
||||
|
||||
/**
|
||||
* @brief 获取帧间隔时间
|
||||
* @return 帧间隔时间(秒)
|
||||
*/
|
||||
float deltaTime() const { return deltaTime_; }
|
||||
|
||||
/**
|
||||
* @brief 获取总运行时间
|
||||
* @return 总运行时间(秒)
|
||||
*/
|
||||
float totalTime() const { return totalTime_; }
|
||||
|
||||
/**
|
||||
* @brief 获取当前帧率
|
||||
* @return 帧率
|
||||
*/
|
||||
int fps() const { return currentFps_; }
|
||||
|
||||
/**
|
||||
* @brief 获取应用配置
|
||||
* @return 应用配置常量引用
|
||||
*/
|
||||
const AppConfig& getConfig() const;
|
||||
|
||||
private:
|
||||
Application() = default;
|
||||
~Application();
|
||||
|
||||
/**
|
||||
* @brief 初始化核心模块
|
||||
* @return 初始化成功返回 true
|
||||
*/
|
||||
bool initCoreModules();
|
||||
|
||||
/**
|
||||
* @brief 设置所有模块
|
||||
*/
|
||||
void setupAllModules();
|
||||
|
||||
/**
|
||||
* @brief 销毁所有模块
|
||||
*/
|
||||
void destroyAllModules();
|
||||
|
||||
/**
|
||||
* @brief 注册核心服务
|
||||
*/
|
||||
void registerCoreServices();
|
||||
|
||||
/**
|
||||
* @brief 主循环
|
||||
*/
|
||||
void mainLoop();
|
||||
|
||||
/**
|
||||
* @brief 更新
|
||||
*/
|
||||
void update();
|
||||
|
||||
/**
|
||||
* @brief 渲染
|
||||
*/
|
||||
void render();
|
||||
|
||||
std::vector<Module*> modules_;
|
||||
Window* window_ = nullptr;
|
||||
|
||||
bool initialized_ = false;
|
||||
bool running_ = false;
|
||||
bool paused_ = false;
|
||||
bool shouldQuit_ = false;
|
||||
|
||||
float deltaTime_ = 0.0f;
|
||||
float totalTime_ = 0.0f;
|
||||
double lastFrameTime_ = 0.0;
|
||||
int frameCount_ = 0;
|
||||
float fpsTimer_ = 0.0f;
|
||||
int currentFps_ = 0;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
#pragma once
|
||||
|
||||
#include <fostbite2D/config/platform_config.h>
|
||||
#include <fostbite2D/platform/window.h>
|
||||
#include <string>
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
/**
|
||||
* @file app_config.h
|
||||
* @brief 应用级别配置
|
||||
*
|
||||
* 本文件仅包含应用级别的配置项,不包含任何模块特定配置。
|
||||
* 各模块应该在自己的模块文件中定义配置结构,并实现 IModuleConfig 接口。
|
||||
*
|
||||
* 模块配置通过 ModuleRegistry 注册,由 ConfigManager 统一管理。
|
||||
* 这种设计遵循开闭原则,新增模块无需修改引擎核心代码。
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief 应用配置结构体
|
||||
* 仅包含应用级别的配置项,模块配置由各模块自行管理
|
||||
*/
|
||||
struct AppConfig {
|
||||
std::string appName = "frostbite2D App";
|
||||
std::string appVersion = "1.0.0";
|
||||
std::string organization = "";
|
||||
std::string configFile = "config.json";
|
||||
WindowConfig windowConfig;
|
||||
PlatformType targetPlatform = PlatformType::Auto;
|
||||
|
||||
/**
|
||||
* @brief 创建默认配置
|
||||
* @return 默认的应用配置实例
|
||||
*/
|
||||
static AppConfig createDefault();
|
||||
};
|
||||
|
||||
} // namespace frostbite2D
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
#pragma once
|
||||
|
||||
#include <fostbite2D/core/types.h>
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
/**
|
||||
* @file platform_config.h
|
||||
* @brief 平台配置接口
|
||||
*
|
||||
* 平台配置只提供平台能力信息,不再直接修改应用配置。
|
||||
* 各模块通过 IModuleConfig::applyPlatformConstraints() 处理平台约束。
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief 平台类型枚举
|
||||
*/
|
||||
enum class PlatformType {
|
||||
Auto,
|
||||
Windows,
|
||||
Switch,
|
||||
Linux,
|
||||
macOS
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 平台能力结构
|
||||
*/
|
||||
struct PlatformCapabilities {
|
||||
bool supportsWindowed = true;
|
||||
bool supportsFullscreen = true;
|
||||
bool supportsBorderless = true;
|
||||
bool supportsCursor = true;
|
||||
bool supportsCursorHide = true;
|
||||
bool supportsDPIAwareness = true;
|
||||
bool supportsVSync = true;
|
||||
bool supportsMultiMonitor = true;
|
||||
bool supportsClipboard = true;
|
||||
bool supportsGamepad = true;
|
||||
bool supportsTouch = false;
|
||||
bool supportsKeyboard = true;
|
||||
bool supportsMouse = true;
|
||||
bool supportsResize = true;
|
||||
bool supportsHighDPI = true;
|
||||
int maxTextureSize = 16384;
|
||||
int preferredScreenWidth = 1920;
|
||||
int preferredScreenHeight = 1080;
|
||||
float defaultDPI = 96.0f;
|
||||
|
||||
bool hasWindowSupport() const { return supportsWindowed || supportsFullscreen || supportsBorderless; }
|
||||
bool hasInputSupport() const { return supportsKeyboard || supportsMouse || supportsGamepad || supportsTouch; }
|
||||
bool isDesktop() const { return supportsKeyboard && supportsMouse && supportsWindowed; }
|
||||
bool isConsole() const { return !supportsWindowed && supportsGamepad; }
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 平台配置抽象接口
|
||||
*/
|
||||
class PlatformConfig {
|
||||
public:
|
||||
virtual ~PlatformConfig() = default;
|
||||
|
||||
virtual PlatformType platformType() const = 0;
|
||||
virtual const char* platformName() const = 0;
|
||||
virtual const PlatformCapabilities& capabilities() const = 0;
|
||||
|
||||
virtual int getRecommendedWidth() const = 0;
|
||||
virtual int getRecommendedHeight() const = 0;
|
||||
virtual bool isResolutionSupported(int width, int height) const = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 创建平台配置实例
|
||||
* @param type 平台类型,默认为 Auto(自动检测)
|
||||
* @return 平台配置的智能指针
|
||||
*/
|
||||
UniquePtr<PlatformConfig> createPlatformConfig(PlatformType type = PlatformType::Auto);
|
||||
|
||||
/**
|
||||
* @brief 获取平台类型名称
|
||||
* @param type 平台类型枚举值
|
||||
* @return 平台名称字符串
|
||||
*/
|
||||
const char* getPlatformTypeName(PlatformType type);
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,156 @@
|
|||
#pragma once
|
||||
|
||||
#include <Fostbite2D/core/types.h>
|
||||
#include <algorithm>
|
||||
#include <glm/vec4.hpp>
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
/// RGB 颜色(字节,每通道 0-255)
|
||||
struct Color3B {
|
||||
uint8_t r = 255;
|
||||
uint8_t g = 255;
|
||||
uint8_t b = 255;
|
||||
|
||||
constexpr Color3B() = default;
|
||||
constexpr Color3B(uint8_t r, uint8_t g, uint8_t b) : r(r), g(g), b(b) {}
|
||||
|
||||
constexpr bool operator==(const Color3B &other) const {
|
||||
return r == other.r && g == other.g && b == other.b;
|
||||
}
|
||||
|
||||
constexpr bool operator!=(const Color3B &other) const {
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
Color3B operator+(const Color3B &other) const {
|
||||
return Color3B(
|
||||
static_cast<uint8_t>(std::min(255, static_cast<int>(r) + other.r)),
|
||||
static_cast<uint8_t>(std::min(255, static_cast<int>(g) + other.g)),
|
||||
static_cast<uint8_t>(std::min(255, static_cast<int>(b) + other.b)));
|
||||
}
|
||||
|
||||
Color3B operator-(const Color3B &other) const {
|
||||
return Color3B(
|
||||
static_cast<uint8_t>(std::max(0, static_cast<int>(r) - other.r)),
|
||||
static_cast<uint8_t>(std::max(0, static_cast<int>(g) - other.g)),
|
||||
static_cast<uint8_t>(std::max(0, static_cast<int>(b) - other.b)));
|
||||
}
|
||||
};
|
||||
|
||||
/// RGBA 颜色(浮点数,每通道 0.0 - 1.0)
|
||||
struct Color {
|
||||
float r = 0.0f;
|
||||
float g = 0.0f;
|
||||
float b = 0.0f;
|
||||
float a = 1.0f;
|
||||
|
||||
constexpr Color() = default;
|
||||
|
||||
constexpr Color(float r, float g, float b, float a = 1.0f)
|
||||
: r(r), g(g), b(b), a(a) {}
|
||||
|
||||
/// 从 0xRRGGBB 整数构造
|
||||
constexpr explicit Color(uint32_t rgb, float a = 1.0f)
|
||||
: r(static_cast<float>((rgb >> 16) & 0xFF) / 255.0f),
|
||||
g(static_cast<float>((rgb >> 8) & 0xFF) / 255.0f),
|
||||
b(static_cast<float>((rgb) & 0xFF) / 255.0f), a(a) {}
|
||||
|
||||
/// 从 0-255 整数构造
|
||||
static constexpr Color fromRGBA(uint8_t r, uint8_t g, uint8_t b,
|
||||
uint8_t a = 255) {
|
||||
return Color(r / 255.0f, g / 255.0f, b / 255.0f, a / 255.0f);
|
||||
}
|
||||
|
||||
/// 转换为 glm::vec4
|
||||
glm::vec4 toVec4() const { return {r, g, b, a}; }
|
||||
|
||||
/// 线性插值
|
||||
static Color lerp(const Color &a, const Color &b, float t) {
|
||||
t = std::clamp(t, 0.0f, 1.0f);
|
||||
return Color(a.r + (b.r - a.r) * t, a.g + (b.g - a.g) * t,
|
||||
a.b + (b.b - a.b) * t, a.a + (b.a - a.a) * t);
|
||||
}
|
||||
|
||||
bool operator==(const Color &other) const {
|
||||
return r == other.r && g == other.g && b == other.b && a == other.a;
|
||||
}
|
||||
|
||||
bool operator!=(const Color &other) const { return !(*this == other); }
|
||||
|
||||
// 算术运算符
|
||||
Color operator+(const Color &other) const {
|
||||
return Color(r + other.r, g + other.g, b + other.b, a + other.a);
|
||||
}
|
||||
|
||||
Color operator-(const Color &other) const {
|
||||
return Color(r - other.r, g - other.g, b - other.b, a - other.a);
|
||||
}
|
||||
|
||||
Color operator*(float scalar) const {
|
||||
return Color(r * scalar, g * scalar, b * scalar, a * scalar);
|
||||
}
|
||||
|
||||
Color operator/(float scalar) const {
|
||||
return Color(r / scalar, g / scalar, b / scalar, a / scalar);
|
||||
}
|
||||
|
||||
Color &operator+=(const Color &other) {
|
||||
r += other.r;
|
||||
g += other.g;
|
||||
b += other.b;
|
||||
a += other.a;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Color &operator-=(const Color &other) {
|
||||
r -= other.r;
|
||||
g -= other.g;
|
||||
b -= other.b;
|
||||
a -= other.a;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Color &operator*=(float scalar) {
|
||||
r *= scalar;
|
||||
g *= scalar;
|
||||
b *= scalar;
|
||||
a *= scalar;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Color &operator/=(float scalar) {
|
||||
r /= scalar;
|
||||
g /= scalar;
|
||||
b /= scalar;
|
||||
a /= scalar;
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
// 命名颜色常量
|
||||
namespace Colors {
|
||||
inline constexpr Color White{1.0f, 1.0f, 1.0f, 1.0f};
|
||||
inline constexpr Color Black{0.0f, 0.0f, 0.0f, 1.0f};
|
||||
inline constexpr Color Red{1.0f, 0.0f, 0.0f, 1.0f};
|
||||
inline constexpr Color Green{0.0f, 1.0f, 0.0f, 1.0f};
|
||||
inline constexpr Color Blue{0.0f, 0.0f, 1.0f, 1.0f};
|
||||
inline constexpr Color Yellow{1.0f, 1.0f, 0.0f, 1.0f};
|
||||
inline constexpr Color Cyan{0.0f, 1.0f, 1.0f, 1.0f};
|
||||
inline constexpr Color Magenta{1.0f, 0.0f, 1.0f, 1.0f};
|
||||
inline constexpr Color Orange{1.0f, 0.647f, 0.0f, 1.0f};
|
||||
inline constexpr Color Purple{0.502f, 0.0f, 0.502f, 1.0f};
|
||||
inline constexpr Color Pink{1.0f, 0.753f, 0.796f, 1.0f};
|
||||
inline constexpr Color Gray{0.502f, 0.502f, 0.502f, 1.0f};
|
||||
inline constexpr Color LightGray{0.827f, 0.827f, 0.827f, 1.0f};
|
||||
inline constexpr Color DarkGray{0.412f, 0.412f, 0.412f, 1.0f};
|
||||
inline constexpr Color Brown{0.647f, 0.165f, 0.165f, 1.0f};
|
||||
inline constexpr Color Gold{1.0f, 0.843f, 0.0f, 1.0f};
|
||||
inline constexpr Color Silver{0.753f, 0.753f, 0.753f, 1.0f};
|
||||
inline constexpr Color SkyBlue{0.529f, 0.808f, 0.922f, 1.0f};
|
||||
inline constexpr Color LimeGreen{0.196f, 0.804f, 0.196f, 1.0f};
|
||||
inline constexpr Color Coral{1.0f, 0.498f, 0.314f, 1.0f};
|
||||
inline constexpr Color Transparent{0.0f, 0.0f, 0.0f, 0.0f};
|
||||
} // namespace Colors
|
||||
|
||||
} // namespace frostbite2D
|
||||
|
|
@ -0,0 +1,552 @@
|
|||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <fostbite2D/core/types.h>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <glm/mat4x4.hpp>
|
||||
#include <glm/vec2.hpp>
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 常量
|
||||
// ---------------------------------------------------------------------------
|
||||
constexpr float PI_F = 3.14159265358979323846f;
|
||||
constexpr float DEG_TO_RAD = PI_F / 180.0f;
|
||||
constexpr float RAD_TO_DEG = 180.0f / PI_F;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 2D 向量
|
||||
// ---------------------------------------------------------------------------
|
||||
struct Vec2 {
|
||||
float x = 0.0f;
|
||||
float y = 0.0f;
|
||||
|
||||
constexpr Vec2() = default;
|
||||
constexpr Vec2(float x, float y) : x(x), y(y) {}
|
||||
explicit Vec2(const glm::vec2 &v) : x(v.x), y(v.y) {}
|
||||
|
||||
glm::vec2 toGlm() const { return {x, y}; }
|
||||
static Vec2 fromGlm(const glm::vec2 &v) { return {v.x, v.y}; }
|
||||
|
||||
// 基础运算
|
||||
Vec2 operator+(const Vec2 &v) const { return {x + v.x, y + v.y}; }
|
||||
Vec2 operator-(const Vec2 &v) const { return {x - v.x, y - v.y}; }
|
||||
Vec2 operator*(float s) const { return {x * s, y * s}; }
|
||||
Vec2 operator/(float s) const { return {x / s, y / s}; }
|
||||
Vec2 operator-() const { return {-x, -y}; }
|
||||
|
||||
Vec2 &operator+=(const Vec2 &v) {
|
||||
x += v.x;
|
||||
y += v.y;
|
||||
return *this;
|
||||
}
|
||||
Vec2 &operator-=(const Vec2 &v) {
|
||||
x -= v.x;
|
||||
y -= v.y;
|
||||
return *this;
|
||||
}
|
||||
Vec2 &operator*=(float s) {
|
||||
x *= s;
|
||||
y *= s;
|
||||
return *this;
|
||||
}
|
||||
Vec2 &operator/=(float s) {
|
||||
x /= s;
|
||||
y /= s;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool operator==(const Vec2 &v) const { return x == v.x && y == v.y; }
|
||||
bool operator!=(const Vec2 &v) const { return !(*this == v); }
|
||||
|
||||
// 向量运算
|
||||
float length() const { return std::sqrt(x * x + y * y); }
|
||||
float lengthSquared() const { return x * x + y * y; }
|
||||
|
||||
Vec2 normalized() const {
|
||||
float len = length();
|
||||
if (len > 0.0f)
|
||||
return {x / len, y / len};
|
||||
return {0.0f, 0.0f};
|
||||
}
|
||||
|
||||
float dot(const Vec2 &v) const { return x * v.x + y * v.y; }
|
||||
float cross(const Vec2 &v) const { return x * v.y - y * v.x; }
|
||||
|
||||
float distance(const Vec2 &v) const { return (*this - v).length(); }
|
||||
float angle() const { return std::atan2(y, x) * RAD_TO_DEG; }
|
||||
|
||||
static Vec2 lerp(const Vec2 &a, const Vec2 &b, float t) {
|
||||
return a + (b - a) * t;
|
||||
}
|
||||
|
||||
static constexpr Vec2 Zero() { return {0.0f, 0.0f}; }
|
||||
static constexpr Vec2 One() { return {1.0f, 1.0f}; }
|
||||
static constexpr Vec2 UnitX() { return {1.0f, 0.0f}; }
|
||||
static constexpr Vec2 UnitY() { return {0.0f, 1.0f}; }
|
||||
};
|
||||
|
||||
inline Vec2 operator*(float s, const Vec2 &v) { return v * s; }
|
||||
|
||||
using Point = Vec2;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 3D 向量 (用于3D动作)
|
||||
// ---------------------------------------------------------------------------
|
||||
struct Vec3 {
|
||||
float x = 0.0f;
|
||||
float y = 0.0f;
|
||||
float z = 0.0f;
|
||||
|
||||
constexpr Vec3() = default;
|
||||
constexpr Vec3(float x, float y, float z) : x(x), y(y), z(z) {}
|
||||
explicit Vec3(const glm::vec3 &v) : x(v.x), y(v.y), z(v.z) {}
|
||||
|
||||
glm::vec3 toGlm() const { return {x, y, z}; }
|
||||
static Vec3 fromGlm(const glm::vec3 &v) { return {v.x, v.y, v.z}; }
|
||||
|
||||
Vec3 operator+(const Vec3 &v) const { return {x + v.x, y + v.y, z + v.z}; }
|
||||
Vec3 operator-(const Vec3 &v) const { return {x - v.x, y - v.y, z - v.z}; }
|
||||
Vec3 operator*(float s) const { return {x * s, y * s, z * s}; }
|
||||
Vec3 operator/(float s) const { return {x / s, y / s, z / s}; }
|
||||
Vec3 operator-() const { return {-x, -y, -z}; }
|
||||
|
||||
Vec3 &operator+=(const Vec3 &v) {
|
||||
x += v.x;
|
||||
y += v.y;
|
||||
z += v.z;
|
||||
return *this;
|
||||
}
|
||||
Vec3 &operator-=(const Vec3 &v) {
|
||||
x -= v.x;
|
||||
y -= v.y;
|
||||
z -= v.z;
|
||||
return *this;
|
||||
}
|
||||
Vec3 &operator*=(float s) {
|
||||
x *= s;
|
||||
y *= s;
|
||||
z *= s;
|
||||
return *this;
|
||||
}
|
||||
Vec3 &operator/=(float s) {
|
||||
x /= s;
|
||||
y /= s;
|
||||
z /= s;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool operator==(const Vec3 &v) const {
|
||||
return x == v.x && y == v.y && z == v.z;
|
||||
}
|
||||
bool operator!=(const Vec3 &v) const { return !(*this == v); }
|
||||
|
||||
float length() const { return std::sqrt(x * x + y * y + z * z); }
|
||||
float lengthSquared() const { return x * x + y * y + z * z; }
|
||||
|
||||
Vec3 normalized() const {
|
||||
float len = length();
|
||||
if (len > 0.0f)
|
||||
return {x / len, y / len, z / len};
|
||||
return {0.0f, 0.0f, 0.0f};
|
||||
}
|
||||
|
||||
float dot(const Vec3 &v) const { return x * v.x + y * v.y + z * v.z; }
|
||||
|
||||
static Vec3 lerp(const Vec3 &a, const Vec3 &b, float t) {
|
||||
return a + (b - a) * t;
|
||||
}
|
||||
|
||||
static constexpr Vec3 Zero() { return {0.0f, 0.0f, 0.0f}; }
|
||||
static constexpr Vec3 One() { return {1.0f, 1.0f, 1.0f}; }
|
||||
};
|
||||
|
||||
inline Vec3 operator*(float s, const Vec3 &v) { return v * s; }
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 2D 尺寸
|
||||
// ---------------------------------------------------------------------------
|
||||
struct Size {
|
||||
float width = 0.0f;
|
||||
float height = 0.0f;
|
||||
|
||||
constexpr Size() = default;
|
||||
constexpr Size(float w, float h) : width(w), height(h) {}
|
||||
|
||||
bool operator==(const Size &s) const {
|
||||
return width == s.width && height == s.height;
|
||||
}
|
||||
bool operator!=(const Size &s) const { return !(*this == s); }
|
||||
|
||||
float area() const { return width * height; }
|
||||
bool empty() const { return width <= 0.0f || height <= 0.0f; }
|
||||
|
||||
static constexpr Size Zero() { return {0.0f, 0.0f}; }
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 2D 矩形
|
||||
// ---------------------------------------------------------------------------
|
||||
struct Rect {
|
||||
Point origin;
|
||||
Size size;
|
||||
|
||||
constexpr Rect() = default;
|
||||
constexpr Rect(float x, float y, float w, float h)
|
||||
: origin(x, y), size(w, h) {}
|
||||
constexpr Rect(const Point &o, const Size &s) : origin(o), size(s) {}
|
||||
|
||||
float left() const { return origin.x; }
|
||||
float top() const { return origin.y; }
|
||||
float right() const { return origin.x + size.width; }
|
||||
float bottom() const { return origin.y + size.height; }
|
||||
float width() const { return size.width; }
|
||||
float height() const { return size.height; }
|
||||
Point center() const {
|
||||
return {origin.x + size.width * 0.5f, origin.y + size.height * 0.5f};
|
||||
}
|
||||
|
||||
bool empty() const { return size.empty(); }
|
||||
|
||||
bool containsPoint(const Point &p) const {
|
||||
return p.x >= left() && p.x <= right() && p.y >= top() && p.y <= bottom();
|
||||
}
|
||||
|
||||
bool contains(const Rect &r) const {
|
||||
return r.left() >= left() && r.right() <= right() && r.top() >= top() &&
|
||||
r.bottom() <= bottom();
|
||||
}
|
||||
|
||||
bool intersects(const Rect &r) const {
|
||||
return !(left() > r.right() || right() < r.left() || top() > r.bottom() ||
|
||||
bottom() < r.top());
|
||||
}
|
||||
|
||||
Rect intersection(const Rect &r) const {
|
||||
float l = std::max(left(), r.left());
|
||||
float t = std::max(top(), r.top());
|
||||
float ri = std::min(right(), r.right());
|
||||
float b = std::min(bottom(), r.bottom());
|
||||
if (l < ri && t < b)
|
||||
return {l, t, ri - l, b - t};
|
||||
return {};
|
||||
}
|
||||
|
||||
Rect unionWith(const Rect &r) const {
|
||||
if (empty())
|
||||
return r;
|
||||
if (r.empty())
|
||||
return *this;
|
||||
float l = std::min(left(), r.left());
|
||||
float t = std::min(top(), r.top());
|
||||
float ri = std::max(right(), r.right());
|
||||
float b = std::max(bottom(), r.bottom());
|
||||
return {l, t, ri - l, b - t};
|
||||
}
|
||||
|
||||
bool operator==(const Rect &r) const {
|
||||
return origin == r.origin && size == r.size;
|
||||
}
|
||||
bool operator!=(const Rect &r) const { return !(*this == r); }
|
||||
|
||||
static constexpr Rect Zero() { return {0, 0, 0, 0}; }
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 2D 变换矩阵(基于 glm::mat4,兼容 OpenGL)
|
||||
// ---------------------------------------------------------------------------
|
||||
struct Transform2D {
|
||||
glm::mat4 matrix{1.0f}; // 单位矩阵
|
||||
|
||||
Transform2D() = default;
|
||||
explicit Transform2D(const glm::mat4 &m) : matrix(m) {}
|
||||
|
||||
static Transform2D identity() { return Transform2D{}; }
|
||||
|
||||
static Transform2D translation(float x, float y) {
|
||||
Transform2D t;
|
||||
t.matrix = glm::translate(glm::mat4(1.0f), glm::vec3(x, y, 0.0f));
|
||||
return t;
|
||||
}
|
||||
|
||||
static Transform2D translation(const Vec2 &v) {
|
||||
return translation(v.x, v.y);
|
||||
}
|
||||
|
||||
static Transform2D rotation(float degrees) {
|
||||
Transform2D t;
|
||||
t.matrix = glm::rotate(glm::mat4(1.0f), degrees * DEG_TO_RAD,
|
||||
glm::vec3(0.0f, 0.0f, 1.0f));
|
||||
return t;
|
||||
}
|
||||
|
||||
static Transform2D scaling(float sx, float sy) {
|
||||
Transform2D t;
|
||||
t.matrix = glm::scale(glm::mat4(1.0f), glm::vec3(sx, sy, 1.0f));
|
||||
return t;
|
||||
}
|
||||
|
||||
static Transform2D scaling(float s) { return scaling(s, s); }
|
||||
|
||||
static Transform2D skewing(float skewX, float skewY) {
|
||||
Transform2D t;
|
||||
t.matrix = glm::mat4(1.0f);
|
||||
t.matrix[1][0] = std::tan(skewX * DEG_TO_RAD);
|
||||
t.matrix[0][1] = std::tan(skewY * DEG_TO_RAD);
|
||||
return t;
|
||||
}
|
||||
|
||||
Transform2D operator*(const Transform2D &other) const {
|
||||
return Transform2D(matrix * other.matrix);
|
||||
}
|
||||
|
||||
Transform2D &operator*=(const Transform2D &other) {
|
||||
matrix *= other.matrix;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Vec2 transformPoint(const Vec2 &p) const {
|
||||
glm::vec4 result = matrix * glm::vec4(p.x, p.y, 0.0f, 1.0f);
|
||||
return {result.x, result.y};
|
||||
}
|
||||
|
||||
Transform2D inverse() const { return Transform2D(glm::inverse(matrix)); }
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 数学工具函数
|
||||
// ---------------------------------------------------------------------------
|
||||
namespace math {
|
||||
|
||||
inline float clamp(float value, float minVal, float maxVal) {
|
||||
return std::clamp(value, minVal, maxVal);
|
||||
}
|
||||
|
||||
inline float lerp(float a, float b, float t) { return a + (b - a) * t; }
|
||||
|
||||
inline float degrees(float radians) { return radians * RAD_TO_DEG; }
|
||||
|
||||
inline float radians(float degrees) { return degrees * DEG_TO_RAD; }
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 角度工具函数
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 规范化角度到 [0, 360) 范围
|
||||
* @param degrees 输入角度(度数)
|
||||
* @return 规范化后的角度,范围 [0, 360)
|
||||
*/
|
||||
inline float normalizeAngle360(float degrees) {
|
||||
degrees = std::fmod(degrees, 360.0f);
|
||||
if (degrees < 0.0f) {
|
||||
degrees += 360.0f;
|
||||
}
|
||||
return degrees;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 规范化角度到 [-180, 180) 范围
|
||||
* @param degrees 输入角度(度数)
|
||||
* @return 规范化后的角度,范围 [-180, 180)
|
||||
*/
|
||||
inline float normalizeAngle180(float degrees) {
|
||||
degrees = std::fmod(degrees + 180.0f, 360.0f);
|
||||
if (degrees < 0.0f) {
|
||||
degrees += 360.0f;
|
||||
}
|
||||
return degrees - 180.0f;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 计算两个角度之间的最短差值
|
||||
* @param from 起始角度(度数)
|
||||
* @param to 目标角度(度数)
|
||||
* @return 从 from 到 to 的最短角度差,范围 [-180, 180]
|
||||
*/
|
||||
inline float angleDifference(float from, float to) {
|
||||
float diff = normalizeAngle360(to - from);
|
||||
if (diff > 180.0f) {
|
||||
diff -= 360.0f;
|
||||
}
|
||||
return diff;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 线性插值角度
|
||||
* @param from 起始角度(度数)
|
||||
* @param to 目标角度(度数)
|
||||
* @param t 插值因子 [0, 1]
|
||||
* @return 插值后的角度
|
||||
*/
|
||||
inline float lerpAngle(float from, float to, float t) {
|
||||
return from + angleDifference(from, to) * t;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 向量工具函数
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 计算方向向量(从 from 指向 to 的单位向量)
|
||||
* @param from 起始点
|
||||
* @param to 目标点
|
||||
* @return 归一化的方向向量
|
||||
*/
|
||||
inline Vec2 direction(const Vec2 &from, const Vec2 &to) {
|
||||
return (to - from).normalized();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 计算两点之间的角度
|
||||
* @param from 起始点
|
||||
* @param to 目标点
|
||||
* @return 角度(度数),范围 [-180, 180]
|
||||
*/
|
||||
inline float angleBetween(const Vec2 &from, const Vec2 &to) {
|
||||
Vec2 dir = to - from;
|
||||
return std::atan2(dir.y, dir.x) * RAD_TO_DEG;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 根据角度创建方向向量
|
||||
* @param degrees 角度(度数),0度指向右方,逆时针为正
|
||||
* @return 单位方向向量
|
||||
*/
|
||||
inline Vec2 angleToVector(float degrees) {
|
||||
float rad = degrees * DEG_TO_RAD;
|
||||
return {std::cos(rad), std::sin(rad)};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 将向量旋转指定角度
|
||||
* @param v 原始向量
|
||||
* @param degrees 旋转角度(度数),正值为逆时针旋转
|
||||
* @return 旋转后的向量
|
||||
*/
|
||||
inline Vec2 rotateVector(const Vec2 &v, float degrees) {
|
||||
float rad = degrees * DEG_TO_RAD;
|
||||
float cosA = std::cos(rad);
|
||||
float sinA = std::sin(rad);
|
||||
return {v.x * cosA - v.y * sinA, v.x * sinA + v.y * cosA};
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 坐标系转换工具
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief Y轴向上坐标转Y轴向下坐标
|
||||
* @param pos Y轴向上坐标系中的位置
|
||||
* @param height 画布/屏幕高度
|
||||
* @return Y轴向下坐标系中的位置
|
||||
*/
|
||||
inline Vec2 flipY(const Vec2 &pos, float height) {
|
||||
return {pos.x, height - pos.y};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Y轴向下坐标转Y轴向上坐标
|
||||
* @param pos Y轴向下坐标系中的位置
|
||||
* @param height 画布/屏幕高度
|
||||
* @return Y轴向上坐标系中的位置
|
||||
*/
|
||||
inline Vec2 unflipY(const Vec2 &pos, float height) {
|
||||
return {pos.x, height - pos.y};
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 矩阵工具函数
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 从变换矩阵提取位置
|
||||
* @param matrix 4x4变换矩阵
|
||||
* @return 提取的位置向量
|
||||
*/
|
||||
inline Vec2 extractPosition(const glm::mat4 &matrix) {
|
||||
return {matrix[3][0], matrix[3][1]};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 从变换矩阵提取缩放
|
||||
* @param matrix 4x4变换矩阵
|
||||
* @return 提取的缩放向量
|
||||
*/
|
||||
inline Vec2 extractScale(const glm::mat4 &matrix) {
|
||||
float scaleX =
|
||||
std::sqrt(matrix[0][0] * matrix[0][0] + matrix[0][1] * matrix[0][1]);
|
||||
float scaleY =
|
||||
std::sqrt(matrix[1][0] * matrix[1][0] + matrix[1][1] * matrix[1][1]);
|
||||
return {scaleX, scaleY};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 从变换矩阵提取旋转角度
|
||||
* @param matrix 4x4变换矩阵
|
||||
* @return 提取的旋转角度(度数)
|
||||
*/
|
||||
inline float extractRotation(const glm::mat4 &matrix) {
|
||||
return std::atan2(matrix[0][1], matrix[0][0]) * RAD_TO_DEG;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 碰撞检测工具
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 判断点是否在矩形内
|
||||
* @param point 要检测的点
|
||||
* @param rect 矩形区域
|
||||
* @return 如果点在矩形内返回 true,否则返回 false
|
||||
*/
|
||||
inline bool pointInRect(const Vec2 &point, const Rect &rect) {
|
||||
return point.x >= rect.left() && point.x <= rect.right() &&
|
||||
point.y >= rect.top() && point.y <= rect.bottom();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 判断点是否在圆内
|
||||
* @param point 要检测的点
|
||||
* @param center 圆心
|
||||
* @param radius 圆的半径
|
||||
* @return 如果点在圆内返回 true,否则返回 false
|
||||
*/
|
||||
inline bool pointInCircle(const Vec2 &point, const Vec2 ¢er, float radius) {
|
||||
float dx = point.x - center.x;
|
||||
float dy = point.y - center.y;
|
||||
return (dx * dx + dy * dy) <= (radius * radius);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 判断两个矩形是否相交
|
||||
* @param a 第一个矩形
|
||||
* @param b 第二个矩形
|
||||
* @return 如果矩形相交返回 true,否则返回 false
|
||||
*/
|
||||
inline bool rectsIntersect(const Rect &a, const Rect &b) {
|
||||
return a.intersects(b);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 判断两个圆是否相交
|
||||
* @param center1 第一个圆的圆心
|
||||
* @param radius1 第一个圆的半径
|
||||
* @param center2 第二个圆的圆心
|
||||
* @param radius2 第二个圆的半径
|
||||
* @return 如果圆相交返回 true,否则返回 false
|
||||
*/
|
||||
inline bool circlesIntersect(const Vec2 ¢er1, float radius1,
|
||||
const Vec2 ¢er2, float radius2) {
|
||||
float dx = center2.x - center1.x;
|
||||
float dy = center2.y - center1.y;
|
||||
float distSq = dx * dx + dy * dy;
|
||||
float radiusSum = radius1 + radius2;
|
||||
return distSq <= (radiusSum * radiusSum);
|
||||
}
|
||||
|
||||
} // namespace math
|
||||
|
||||
} // namespace frostbite2D
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 宏定义
|
||||
// ---------------------------------------------------------------------------
|
||||
#define E2D_CONCAT_IMPL(a, b) a##b
|
||||
#define E2D_CONCAT(a, b) E2D_CONCAT_IMPL(a, b)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 智能指针别名
|
||||
// ---------------------------------------------------------------------------
|
||||
template <typename T> using Ptr = std::shared_ptr<T>;
|
||||
template <typename T> using SharedPtr = std::shared_ptr<T>;
|
||||
|
||||
template <typename T> using UniquePtr = std::unique_ptr<T>;
|
||||
|
||||
template <typename T> using WeakPtr = std::weak_ptr<T>;
|
||||
|
||||
/// 创建 shared_ptr 的便捷函数
|
||||
template <typename T, typename... Args> inline Ptr<T> makePtr(Args &&...args) {
|
||||
return std::make_shared<T>(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <typename T, typename... Args>
|
||||
inline SharedPtr<T> makeShared(Args &&...args) {
|
||||
return std::make_shared<T>(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/// 创建 unique_ptr 的便捷函数
|
||||
template <typename T, typename... Args>
|
||||
inline UniquePtr<T> makeUnique(Args &&...args) {
|
||||
return std::make_unique<T>(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 函数别名
|
||||
// ---------------------------------------------------------------------------
|
||||
template <typename Sig> using Function = std::function<Sig>;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 基础类型别名
|
||||
// ---------------------------------------------------------------------------
|
||||
using int8 = std::int8_t;
|
||||
using int16 = std::int16_t;
|
||||
using int32 = std::int32_t;
|
||||
using int64 = std::int64_t;
|
||||
using uint8 = std::uint8_t;
|
||||
using uint16 = std::uint16_t;
|
||||
using uint32 = std::uint32_t;
|
||||
using uint64 = std::uint64_t;
|
||||
|
||||
} // namespace frostbite2D
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
#pragma once
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
/**
|
||||
* @brief 模块基类
|
||||
* 所有模块只需继承此类,实现需要的生命周期方法
|
||||
*/
|
||||
class Module {
|
||||
public:
|
||||
/**
|
||||
* @brief 虚析构函数
|
||||
*/
|
||||
virtual ~Module() = default;
|
||||
|
||||
/**
|
||||
* @brief 设置模块(初始化)
|
||||
* 在 Application::run() 开始前调用
|
||||
*/
|
||||
virtual void setupModule() {}
|
||||
|
||||
/**
|
||||
* @brief 销毁模块
|
||||
* 在 Application 关闭时调用
|
||||
*/
|
||||
virtual void destroyModule() {}
|
||||
|
||||
/**
|
||||
* @brief 更新时
|
||||
* 每帧调用
|
||||
* @param ctx 更新上下文
|
||||
*/
|
||||
virtual void onUpdate() { }
|
||||
|
||||
/**
|
||||
* @brief 渲染前
|
||||
* 在渲染开始前调用
|
||||
* @param ctx 渲染上下文
|
||||
*/
|
||||
virtual void beforeRender() { }
|
||||
|
||||
/**
|
||||
* @brief 渲染时
|
||||
* 在渲染阶段调用
|
||||
* @param ctx 渲染上下文
|
||||
*/
|
||||
virtual void onRender() {}
|
||||
|
||||
/**
|
||||
* @brief 渲染后
|
||||
* 在渲染完成后调用
|
||||
* @param ctx 渲染上下文
|
||||
*/
|
||||
virtual void afterRender() { }
|
||||
|
||||
/**
|
||||
* @brief 事件处理
|
||||
* 处理系统事件
|
||||
* @param ctx 事件上下文
|
||||
*/
|
||||
virtual void handleEvent() { }
|
||||
|
||||
/**
|
||||
* @brief 获取模块名称
|
||||
* @return 模块名称字符串
|
||||
*/
|
||||
virtual const char *getName() const = 0;
|
||||
|
||||
/**
|
||||
* @brief 获取模块优先级
|
||||
* 数值越小越先执行
|
||||
* @return 优先级值
|
||||
*/
|
||||
virtual int getPriority() const { return 0; }
|
||||
|
||||
/**
|
||||
* @brief 检查模块是否已初始化
|
||||
* @return 已初始化返回 true
|
||||
*/
|
||||
bool isInitialized() const { return initialized_; }
|
||||
|
||||
protected:
|
||||
friend class Application;
|
||||
|
||||
/**
|
||||
* @brief 设置初始化状态
|
||||
* @param initialized 初始化状态
|
||||
*/
|
||||
void setInitialized(bool initialized) { initialized_ = initialized; }
|
||||
|
||||
bool initialized_ = false;
|
||||
};
|
||||
} // namespace frostbite2D
|
||||
|
|
@ -0,0 +1,272 @@
|
|||
#pragma once
|
||||
|
||||
#include <SDL2/SDL.h>
|
||||
#include <fostbite2D/core/math_types.h>
|
||||
#include <fostbite2D/core/types.h>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
/**
|
||||
* \~chinese
|
||||
* @brief 鼠标指针类型
|
||||
*/
|
||||
enum class CursorType {
|
||||
Arrow, ///< 指针
|
||||
TextInput, ///< 文本
|
||||
Hand, ///< 手
|
||||
SizeAll, ///< 指向四个方向的箭头
|
||||
SizeWE, ///< 指向左右方向的箭头
|
||||
SizeNS, ///< 指向上下方向的箭头
|
||||
SizeNESW, ///< 指向左下到右上方向的箭头
|
||||
SizeNWSE, ///< 指向左上到右下方向的箭头
|
||||
No, ///< 禁止
|
||||
};
|
||||
|
||||
/**
|
||||
* \~chinese
|
||||
* @brief 分辨率
|
||||
*/
|
||||
struct Resolution {
|
||||
uint32_t width = 0; ///< 分辨率宽度
|
||||
uint32_t height = 0; ///< 分辨率高度
|
||||
uint32_t refresh_rate = 0; ///< 刷新率
|
||||
|
||||
Resolution() = default;
|
||||
|
||||
Resolution(uint32_t width, uint32_t height, uint32_t refresh_rate)
|
||||
: width(width), height(height), refresh_rate(refresh_rate) {}
|
||||
};
|
||||
|
||||
/**
|
||||
* \~chinese
|
||||
* @brief 图标
|
||||
*/
|
||||
struct Icon {
|
||||
Icon() = default;
|
||||
|
||||
Icon(std::string file_path) : file_path(file_path) {}
|
||||
|
||||
std::string file_path; ///< 文件路径
|
||||
|
||||
#if defined(_WIN32)
|
||||
uint32_t resource_id = 0; ///< 资源ID,仅在windows上生效
|
||||
|
||||
Icon(uint32_t resource_id) : resource_id(resource_id) {}
|
||||
#endif
|
||||
};
|
||||
|
||||
/**
|
||||
* \~chinese
|
||||
* @brief 窗口设置
|
||||
*/
|
||||
struct WindowConfig {
|
||||
uint32_t width = 640; ///< 窗口宽度
|
||||
uint32_t height = 480; ///< 窗口高度
|
||||
std::string title = "fostbite2D Game"; ///< 窗口标题
|
||||
Icon icon; ///< 窗口图标
|
||||
bool resizable = false; ///< 窗口大小可调整
|
||||
bool fullscreen = false; ///< 窗口全屏
|
||||
bool borderless = false; ///< 无边框窗口
|
||||
bool decorated = true; ///< 窗口装饰
|
||||
int multisamples = 0; ///< 多重采样数
|
||||
bool centered = true; ///< 窗口是否居中
|
||||
bool vsync = true; ///< 是否启用垂直同步
|
||||
bool showCursor = true; ///< 是否显示光标
|
||||
};
|
||||
|
||||
class Window {
|
||||
public:
|
||||
Window() = default;
|
||||
virtual ~Window() = default;
|
||||
|
||||
/**
|
||||
* @brief 创建窗口
|
||||
* @param cfg 窗口配置
|
||||
* @return 创建是否成功
|
||||
*/
|
||||
virtual bool create(const WindowConfig &cfg);
|
||||
|
||||
/**
|
||||
* @brief 销毁窗口
|
||||
*/
|
||||
virtual void destroy();
|
||||
|
||||
/**
|
||||
* @brief 轮询事件
|
||||
*/
|
||||
virtual void poll();
|
||||
|
||||
/**
|
||||
* @brief 交换缓冲区
|
||||
*/
|
||||
virtual void swap();
|
||||
|
||||
/**
|
||||
* @brief 设置窗口关闭标志
|
||||
*/
|
||||
virtual void close();
|
||||
|
||||
/**
|
||||
* @brief 设置窗口标题
|
||||
*/
|
||||
virtual void setTitle(const std::string &title);
|
||||
|
||||
/**
|
||||
* @brief 设置窗口大小
|
||||
*/
|
||||
virtual void setSize(int w, int h);
|
||||
|
||||
/**
|
||||
* @brief 设置窗口位置
|
||||
*/
|
||||
virtual void setPos(int x, int y);
|
||||
|
||||
/**
|
||||
* @brief 设置全屏模式
|
||||
*/
|
||||
virtual void setFullscreen(bool fs);
|
||||
|
||||
/**
|
||||
* @brief 设置垂直同步
|
||||
*/
|
||||
virtual void setVSync(bool vsync);
|
||||
|
||||
/**
|
||||
* @brief 设置窗口可见性
|
||||
*/
|
||||
virtual void setVisible(bool visible);
|
||||
|
||||
/**
|
||||
* @brief 获取窗口宽度
|
||||
*/
|
||||
virtual int width() const;
|
||||
|
||||
/**
|
||||
* @brief 获取窗口高度
|
||||
*/
|
||||
virtual int height() const;
|
||||
|
||||
/**
|
||||
* @brief 获取窗口大小
|
||||
*/
|
||||
virtual Size size() const;
|
||||
|
||||
/**
|
||||
* @brief 获取窗口位置
|
||||
*/
|
||||
virtual Vec2 pos() const;
|
||||
|
||||
/**
|
||||
* @brief 是否全屏
|
||||
*/
|
||||
virtual bool fullscreen() const;
|
||||
|
||||
/**
|
||||
* @brief 是否启用垂直同步
|
||||
*/
|
||||
virtual bool vsync() const;
|
||||
|
||||
/**
|
||||
* @brief 窗口是否获得焦点
|
||||
*/
|
||||
virtual bool focused() const;
|
||||
|
||||
/**
|
||||
* @brief 窗口是否最小化
|
||||
*/
|
||||
virtual bool minimized() const;
|
||||
|
||||
/**
|
||||
* @brief 获取内容缩放X
|
||||
*/
|
||||
virtual float scaleX() const;
|
||||
|
||||
/**
|
||||
* @brief 获取内容缩放Y
|
||||
*/
|
||||
virtual float scaleY() const;
|
||||
|
||||
/**
|
||||
* @brief 设置光标形状
|
||||
*/
|
||||
virtual void setCursor(CursorType cursor);
|
||||
|
||||
/**
|
||||
* @brief 显示/隐藏光标
|
||||
*/
|
||||
virtual void showCursor(bool show);
|
||||
|
||||
/**
|
||||
* @brief 锁定/解锁光标
|
||||
*/
|
||||
virtual void lockCursor(bool lock);
|
||||
|
||||
/**
|
||||
* @brief 窗口大小改变回调
|
||||
*/
|
||||
using ResizeCb = std::function<void(int, int)>;
|
||||
|
||||
/**
|
||||
* @brief 窗口关闭回调
|
||||
*/
|
||||
using CloseCb = std::function<void()>;
|
||||
|
||||
/**
|
||||
* @brief 窗口焦点改变回调
|
||||
*/
|
||||
using FocusCb = std::function<void(bool)>;
|
||||
|
||||
/**
|
||||
* @brief 设置大小改变回调
|
||||
*/
|
||||
virtual void onResize(ResizeCb cb);
|
||||
|
||||
/**
|
||||
* @brief 设置关闭回调
|
||||
*/
|
||||
virtual void onClose(CloseCb cb);
|
||||
|
||||
/**
|
||||
* @brief 设置焦点改变回调
|
||||
*/
|
||||
virtual void onFocus(FocusCb cb);
|
||||
|
||||
/**
|
||||
* @brief 获取原生窗口句柄
|
||||
*/
|
||||
virtual void *native() const;
|
||||
|
||||
/**
|
||||
* @brief 获取 SDL 窗口句柄
|
||||
*/
|
||||
SDL_Window *sdlWindow() const { return sdlWindow_; }
|
||||
|
||||
/**
|
||||
* @brief 获取 OpenGL 上下文
|
||||
*/
|
||||
SDL_GLContext glContext() const { return glContext_; }
|
||||
|
||||
private:
|
||||
SDL_Window *sdlWindow_ = nullptr;
|
||||
SDL_GLContext glContext_ = nullptr;
|
||||
|
||||
int width_ = 1280;
|
||||
int height_ = 720;
|
||||
bool fullscreen_ = false;
|
||||
bool vsync_ = true;
|
||||
bool focused_ = true;
|
||||
bool minimized_ = false;
|
||||
bool shouldClose_ = false;
|
||||
float scaleX_ = 1.0f;
|
||||
float scaleY_ = 1.0f;
|
||||
bool cursorVisible_ = true;
|
||||
bool cursorLocked_ = false;
|
||||
|
||||
ResizeCb resizeCb_;
|
||||
CloseCb closeCb_;
|
||||
FocusCb focusCb_;
|
||||
};
|
||||
|
||||
} // namespace frostbite2D
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
#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
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
#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
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
#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
|
||||
|
|
@ -0,0 +1,316 @@
|
|||
#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
|
||||
|
|
@ -0,0 +1,194 @@
|
|||
#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
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
#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
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
#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
|
||||
|
|
@ -0,0 +1,151 @@
|
|||
#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
|
||||
|
|
@ -0,0 +1,129 @@
|
|||
#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
|
||||
|
|
@ -0,0 +1,154 @@
|
|||
#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
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,428 @@
|
|||
// stb_perlin.h - v0.5 - perlin noise
|
||||
// public domain single-file C implementation by Sean Barrett
|
||||
//
|
||||
// LICENSE
|
||||
//
|
||||
// See end of file.
|
||||
//
|
||||
//
|
||||
// to create the implementation,
|
||||
// #define STB_PERLIN_IMPLEMENTATION
|
||||
// in *one* C/CPP file that includes this file.
|
||||
//
|
||||
//
|
||||
// Documentation:
|
||||
//
|
||||
// float stb_perlin_noise3( float x,
|
||||
// float y,
|
||||
// float z,
|
||||
// int x_wrap=0,
|
||||
// int y_wrap=0,
|
||||
// int z_wrap=0)
|
||||
//
|
||||
// This function computes a random value at the coordinate (x,y,z).
|
||||
// Adjacent random values are continuous but the noise fluctuates
|
||||
// its randomness with period 1, i.e. takes on wholly unrelated values
|
||||
// at integer points. Specifically, this implements Ken Perlin's
|
||||
// revised noise function from 2002.
|
||||
//
|
||||
// The "wrap" parameters can be used to create wraparound noise that
|
||||
// wraps at powers of two. The numbers MUST be powers of two. Specify
|
||||
// 0 to mean "don't care". (The noise always wraps every 256 due
|
||||
// details of the implementation, even if you ask for larger or no
|
||||
// wrapping.)
|
||||
//
|
||||
// float stb_perlin_noise3_seed( float x,
|
||||
// float y,
|
||||
// float z,
|
||||
// int x_wrap=0,
|
||||
// int y_wrap=0,
|
||||
// int z_wrap=0,
|
||||
// int seed)
|
||||
//
|
||||
// As above, but 'seed' selects from multiple different variations of the
|
||||
// noise function. The current implementation only uses the bottom 8 bits
|
||||
// of 'seed', but possibly in the future more bits will be used.
|
||||
//
|
||||
//
|
||||
// Fractal Noise:
|
||||
//
|
||||
// Three common fractal noise functions are included, which produce
|
||||
// a wide variety of nice effects depending on the parameters
|
||||
// provided. Note that each function will call stb_perlin_noise3
|
||||
// 'octaves' times, so this parameter will affect runtime.
|
||||
//
|
||||
// float stb_perlin_ridge_noise3(float x, float y, float z,
|
||||
// float lacunarity, float gain, float offset, int octaves)
|
||||
//
|
||||
// float stb_perlin_fbm_noise3(float x, float y, float z,
|
||||
// float lacunarity, float gain, int octaves)
|
||||
//
|
||||
// float stb_perlin_turbulence_noise3(float x, float y, float z,
|
||||
// float lacunarity, float gain, int octaves)
|
||||
//
|
||||
// Typical values to start playing with:
|
||||
// octaves = 6 -- number of "octaves" of noise3() to sum
|
||||
// lacunarity = ~ 2.0 -- spacing between successive octaves (use exactly 2.0 for wrapping output)
|
||||
// gain = 0.5 -- relative weighting applied to each successive octave
|
||||
// offset = 1.0? -- used to invert the ridges, may need to be larger, not sure
|
||||
//
|
||||
//
|
||||
// Contributors:
|
||||
// Jack Mott - additional noise functions
|
||||
// Jordan Peck - seeded noise
|
||||
//
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
extern float stb_perlin_noise3(float x, float y, float z, int x_wrap, int y_wrap, int z_wrap);
|
||||
extern float stb_perlin_noise3_seed(float x, float y, float z, int x_wrap, int y_wrap, int z_wrap, int seed);
|
||||
extern float stb_perlin_ridge_noise3(float x, float y, float z, float lacunarity, float gain, float offset, int octaves);
|
||||
extern float stb_perlin_fbm_noise3(float x, float y, float z, float lacunarity, float gain, int octaves);
|
||||
extern float stb_perlin_turbulence_noise3(float x, float y, float z, float lacunarity, float gain, int octaves);
|
||||
extern float stb_perlin_noise3_wrap_nonpow2(float x, float y, float z, int x_wrap, int y_wrap, int z_wrap, unsigned char seed);
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef STB_PERLIN_IMPLEMENTATION
|
||||
|
||||
#include <math.h> // fabs()
|
||||
|
||||
// not same permutation table as Perlin's reference to avoid copyright issues;
|
||||
// Perlin's table can be found at http://mrl.nyu.edu/~perlin/noise/
|
||||
static unsigned char stb__perlin_randtab[512] =
|
||||
{
|
||||
23, 125, 161, 52, 103, 117, 70, 37, 247, 101, 203, 169, 124, 126, 44, 123,
|
||||
152, 238, 145, 45, 171, 114, 253, 10, 192, 136, 4, 157, 249, 30, 35, 72,
|
||||
175, 63, 77, 90, 181, 16, 96, 111, 133, 104, 75, 162, 93, 56, 66, 240,
|
||||
8, 50, 84, 229, 49, 210, 173, 239, 141, 1, 87, 18, 2, 198, 143, 57,
|
||||
225, 160, 58, 217, 168, 206, 245, 204, 199, 6, 73, 60, 20, 230, 211, 233,
|
||||
94, 200, 88, 9, 74, 155, 33, 15, 219, 130, 226, 202, 83, 236, 42, 172,
|
||||
165, 218, 55, 222, 46, 107, 98, 154, 109, 67, 196, 178, 127, 158, 13, 243,
|
||||
65, 79, 166, 248, 25, 224, 115, 80, 68, 51, 184, 128, 232, 208, 151, 122,
|
||||
26, 212, 105, 43, 179, 213, 235, 148, 146, 89, 14, 195, 28, 78, 112, 76,
|
||||
250, 47, 24, 251, 140, 108, 186, 190, 228, 170, 183, 139, 39, 188, 244, 246,
|
||||
132, 48, 119, 144, 180, 138, 134, 193, 82, 182, 120, 121, 86, 220, 209, 3,
|
||||
91, 241, 149, 85, 205, 150, 113, 216, 31, 100, 41, 164, 177, 214, 153, 231,
|
||||
38, 71, 185, 174, 97, 201, 29, 95, 7, 92, 54, 254, 191, 118, 34, 221,
|
||||
131, 11, 163, 99, 234, 81, 227, 147, 156, 176, 17, 142, 69, 12, 110, 62,
|
||||
27, 255, 0, 194, 59, 116, 242, 252, 19, 21, 187, 53, 207, 129, 64, 135,
|
||||
61, 40, 167, 237, 102, 223, 106, 159, 197, 189, 215, 137, 36, 32, 22, 5,
|
||||
|
||||
// and a second copy so we don't need an extra mask or static initializer
|
||||
23, 125, 161, 52, 103, 117, 70, 37, 247, 101, 203, 169, 124, 126, 44, 123,
|
||||
152, 238, 145, 45, 171, 114, 253, 10, 192, 136, 4, 157, 249, 30, 35, 72,
|
||||
175, 63, 77, 90, 181, 16, 96, 111, 133, 104, 75, 162, 93, 56, 66, 240,
|
||||
8, 50, 84, 229, 49, 210, 173, 239, 141, 1, 87, 18, 2, 198, 143, 57,
|
||||
225, 160, 58, 217, 168, 206, 245, 204, 199, 6, 73, 60, 20, 230, 211, 233,
|
||||
94, 200, 88, 9, 74, 155, 33, 15, 219, 130, 226, 202, 83, 236, 42, 172,
|
||||
165, 218, 55, 222, 46, 107, 98, 154, 109, 67, 196, 178, 127, 158, 13, 243,
|
||||
65, 79, 166, 248, 25, 224, 115, 80, 68, 51, 184, 128, 232, 208, 151, 122,
|
||||
26, 212, 105, 43, 179, 213, 235, 148, 146, 89, 14, 195, 28, 78, 112, 76,
|
||||
250, 47, 24, 251, 140, 108, 186, 190, 228, 170, 183, 139, 39, 188, 244, 246,
|
||||
132, 48, 119, 144, 180, 138, 134, 193, 82, 182, 120, 121, 86, 220, 209, 3,
|
||||
91, 241, 149, 85, 205, 150, 113, 216, 31, 100, 41, 164, 177, 214, 153, 231,
|
||||
38, 71, 185, 174, 97, 201, 29, 95, 7, 92, 54, 254, 191, 118, 34, 221,
|
||||
131, 11, 163, 99, 234, 81, 227, 147, 156, 176, 17, 142, 69, 12, 110, 62,
|
||||
27, 255, 0, 194, 59, 116, 242, 252, 19, 21, 187, 53, 207, 129, 64, 135,
|
||||
61, 40, 167, 237, 102, 223, 106, 159, 197, 189, 215, 137, 36, 32, 22, 5,
|
||||
};
|
||||
|
||||
|
||||
// perlin's gradient has 12 cases so some get used 1/16th of the time
|
||||
// and some 2/16ths. We reduce bias by changing those fractions
|
||||
// to 5/64ths and 6/64ths
|
||||
|
||||
// this array is designed to match the previous implementation
|
||||
// of gradient hash: indices[stb__perlin_randtab[i]&63]
|
||||
static unsigned char stb__perlin_randtab_grad_idx[512] =
|
||||
{
|
||||
7, 9, 5, 0, 11, 1, 6, 9, 3, 9, 11, 1, 8, 10, 4, 7,
|
||||
8, 6, 1, 5, 3, 10, 9, 10, 0, 8, 4, 1, 5, 2, 7, 8,
|
||||
7, 11, 9, 10, 1, 0, 4, 7, 5, 0, 11, 6, 1, 4, 2, 8,
|
||||
8, 10, 4, 9, 9, 2, 5, 7, 9, 1, 7, 2, 2, 6, 11, 5,
|
||||
5, 4, 6, 9, 0, 1, 1, 0, 7, 6, 9, 8, 4, 10, 3, 1,
|
||||
2, 8, 8, 9, 10, 11, 5, 11, 11, 2, 6, 10, 3, 4, 2, 4,
|
||||
9, 10, 3, 2, 6, 3, 6, 10, 5, 3, 4, 10, 11, 2, 9, 11,
|
||||
1, 11, 10, 4, 9, 4, 11, 0, 4, 11, 4, 0, 0, 0, 7, 6,
|
||||
10, 4, 1, 3, 11, 5, 3, 4, 2, 9, 1, 3, 0, 1, 8, 0,
|
||||
6, 7, 8, 7, 0, 4, 6, 10, 8, 2, 3, 11, 11, 8, 0, 2,
|
||||
4, 8, 3, 0, 0, 10, 6, 1, 2, 2, 4, 5, 6, 0, 1, 3,
|
||||
11, 9, 5, 5, 9, 6, 9, 8, 3, 8, 1, 8, 9, 6, 9, 11,
|
||||
10, 7, 5, 6, 5, 9, 1, 3, 7, 0, 2, 10, 11, 2, 6, 1,
|
||||
3, 11, 7, 7, 2, 1, 7, 3, 0, 8, 1, 1, 5, 0, 6, 10,
|
||||
11, 11, 0, 2, 7, 0, 10, 8, 3, 5, 7, 1, 11, 1, 0, 7,
|
||||
9, 0, 11, 5, 10, 3, 2, 3, 5, 9, 7, 9, 8, 4, 6, 5,
|
||||
|
||||
// and a second copy so we don't need an extra mask or static initializer
|
||||
7, 9, 5, 0, 11, 1, 6, 9, 3, 9, 11, 1, 8, 10, 4, 7,
|
||||
8, 6, 1, 5, 3, 10, 9, 10, 0, 8, 4, 1, 5, 2, 7, 8,
|
||||
7, 11, 9, 10, 1, 0, 4, 7, 5, 0, 11, 6, 1, 4, 2, 8,
|
||||
8, 10, 4, 9, 9, 2, 5, 7, 9, 1, 7, 2, 2, 6, 11, 5,
|
||||
5, 4, 6, 9, 0, 1, 1, 0, 7, 6, 9, 8, 4, 10, 3, 1,
|
||||
2, 8, 8, 9, 10, 11, 5, 11, 11, 2, 6, 10, 3, 4, 2, 4,
|
||||
9, 10, 3, 2, 6, 3, 6, 10, 5, 3, 4, 10, 11, 2, 9, 11,
|
||||
1, 11, 10, 4, 9, 4, 11, 0, 4, 11, 4, 0, 0, 0, 7, 6,
|
||||
10, 4, 1, 3, 11, 5, 3, 4, 2, 9, 1, 3, 0, 1, 8, 0,
|
||||
6, 7, 8, 7, 0, 4, 6, 10, 8, 2, 3, 11, 11, 8, 0, 2,
|
||||
4, 8, 3, 0, 0, 10, 6, 1, 2, 2, 4, 5, 6, 0, 1, 3,
|
||||
11, 9, 5, 5, 9, 6, 9, 8, 3, 8, 1, 8, 9, 6, 9, 11,
|
||||
10, 7, 5, 6, 5, 9, 1, 3, 7, 0, 2, 10, 11, 2, 6, 1,
|
||||
3, 11, 7, 7, 2, 1, 7, 3, 0, 8, 1, 1, 5, 0, 6, 10,
|
||||
11, 11, 0, 2, 7, 0, 10, 8, 3, 5, 7, 1, 11, 1, 0, 7,
|
||||
9, 0, 11, 5, 10, 3, 2, 3, 5, 9, 7, 9, 8, 4, 6, 5,
|
||||
};
|
||||
|
||||
static float stb__perlin_lerp(float a, float b, float t)
|
||||
{
|
||||
return a + (b-a) * t;
|
||||
}
|
||||
|
||||
static int stb__perlin_fastfloor(float a)
|
||||
{
|
||||
int ai = (int) a;
|
||||
return (a < ai) ? ai-1 : ai;
|
||||
}
|
||||
|
||||
// different grad function from Perlin's, but easy to modify to match reference
|
||||
static float stb__perlin_grad(int grad_idx, float x, float y, float z)
|
||||
{
|
||||
static float basis[12][4] =
|
||||
{
|
||||
{ 1, 1, 0 },
|
||||
{ -1, 1, 0 },
|
||||
{ 1,-1, 0 },
|
||||
{ -1,-1, 0 },
|
||||
{ 1, 0, 1 },
|
||||
{ -1, 0, 1 },
|
||||
{ 1, 0,-1 },
|
||||
{ -1, 0,-1 },
|
||||
{ 0, 1, 1 },
|
||||
{ 0,-1, 1 },
|
||||
{ 0, 1,-1 },
|
||||
{ 0,-1,-1 },
|
||||
};
|
||||
|
||||
float *grad = basis[grad_idx];
|
||||
return grad[0]*x + grad[1]*y + grad[2]*z;
|
||||
}
|
||||
|
||||
float stb_perlin_noise3_internal(float x, float y, float z, int x_wrap, int y_wrap, int z_wrap, unsigned char seed)
|
||||
{
|
||||
float u,v,w;
|
||||
float n000,n001,n010,n011,n100,n101,n110,n111;
|
||||
float n00,n01,n10,n11;
|
||||
float n0,n1;
|
||||
|
||||
unsigned int x_mask = (x_wrap-1) & 255;
|
||||
unsigned int y_mask = (y_wrap-1) & 255;
|
||||
unsigned int z_mask = (z_wrap-1) & 255;
|
||||
int px = stb__perlin_fastfloor(x);
|
||||
int py = stb__perlin_fastfloor(y);
|
||||
int pz = stb__perlin_fastfloor(z);
|
||||
int x0 = px & x_mask, x1 = (px+1) & x_mask;
|
||||
int y0 = py & y_mask, y1 = (py+1) & y_mask;
|
||||
int z0 = pz & z_mask, z1 = (pz+1) & z_mask;
|
||||
int r0,r1, r00,r01,r10,r11;
|
||||
|
||||
#define stb__perlin_ease(a) (((a*6-15)*a + 10) * a * a * a)
|
||||
|
||||
x -= px; u = stb__perlin_ease(x);
|
||||
y -= py; v = stb__perlin_ease(y);
|
||||
z -= pz; w = stb__perlin_ease(z);
|
||||
|
||||
r0 = stb__perlin_randtab[x0+seed];
|
||||
r1 = stb__perlin_randtab[x1+seed];
|
||||
|
||||
r00 = stb__perlin_randtab[r0+y0];
|
||||
r01 = stb__perlin_randtab[r0+y1];
|
||||
r10 = stb__perlin_randtab[r1+y0];
|
||||
r11 = stb__perlin_randtab[r1+y1];
|
||||
|
||||
n000 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r00+z0], x , y , z );
|
||||
n001 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r00+z1], x , y , z-1 );
|
||||
n010 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r01+z0], x , y-1, z );
|
||||
n011 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r01+z1], x , y-1, z-1 );
|
||||
n100 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r10+z0], x-1, y , z );
|
||||
n101 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r10+z1], x-1, y , z-1 );
|
||||
n110 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r11+z0], x-1, y-1, z );
|
||||
n111 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r11+z1], x-1, y-1, z-1 );
|
||||
|
||||
n00 = stb__perlin_lerp(n000,n001,w);
|
||||
n01 = stb__perlin_lerp(n010,n011,w);
|
||||
n10 = stb__perlin_lerp(n100,n101,w);
|
||||
n11 = stb__perlin_lerp(n110,n111,w);
|
||||
|
||||
n0 = stb__perlin_lerp(n00,n01,v);
|
||||
n1 = stb__perlin_lerp(n10,n11,v);
|
||||
|
||||
return stb__perlin_lerp(n0,n1,u);
|
||||
}
|
||||
|
||||
float stb_perlin_noise3(float x, float y, float z, int x_wrap, int y_wrap, int z_wrap)
|
||||
{
|
||||
return stb_perlin_noise3_internal(x,y,z,x_wrap,y_wrap,z_wrap,0);
|
||||
}
|
||||
|
||||
float stb_perlin_noise3_seed(float x, float y, float z, int x_wrap, int y_wrap, int z_wrap, int seed)
|
||||
{
|
||||
return stb_perlin_noise3_internal(x,y,z,x_wrap,y_wrap,z_wrap, (unsigned char) seed);
|
||||
}
|
||||
|
||||
float stb_perlin_ridge_noise3(float x, float y, float z, float lacunarity, float gain, float offset, int octaves)
|
||||
{
|
||||
int i;
|
||||
float frequency = 1.0f;
|
||||
float prev = 1.0f;
|
||||
float amplitude = 0.5f;
|
||||
float sum = 0.0f;
|
||||
|
||||
for (i = 0; i < octaves; i++) {
|
||||
float r = stb_perlin_noise3_internal(x*frequency,y*frequency,z*frequency,0,0,0,(unsigned char)i);
|
||||
r = offset - (float) fabs(r);
|
||||
r = r*r;
|
||||
sum += r*amplitude*prev;
|
||||
prev = r;
|
||||
frequency *= lacunarity;
|
||||
amplitude *= gain;
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
float stb_perlin_fbm_noise3(float x, float y, float z, float lacunarity, float gain, int octaves)
|
||||
{
|
||||
int i;
|
||||
float frequency = 1.0f;
|
||||
float amplitude = 1.0f;
|
||||
float sum = 0.0f;
|
||||
|
||||
for (i = 0; i < octaves; i++) {
|
||||
sum += stb_perlin_noise3_internal(x*frequency,y*frequency,z*frequency,0,0,0,(unsigned char)i)*amplitude;
|
||||
frequency *= lacunarity;
|
||||
amplitude *= gain;
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
float stb_perlin_turbulence_noise3(float x, float y, float z, float lacunarity, float gain, int octaves)
|
||||
{
|
||||
int i;
|
||||
float frequency = 1.0f;
|
||||
float amplitude = 1.0f;
|
||||
float sum = 0.0f;
|
||||
|
||||
for (i = 0; i < octaves; i++) {
|
||||
float r = stb_perlin_noise3_internal(x*frequency,y*frequency,z*frequency,0,0,0,(unsigned char)i)*amplitude;
|
||||
sum += (float) fabs(r);
|
||||
frequency *= lacunarity;
|
||||
amplitude *= gain;
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
float stb_perlin_noise3_wrap_nonpow2(float x, float y, float z, int x_wrap, int y_wrap, int z_wrap, unsigned char seed)
|
||||
{
|
||||
float u,v,w;
|
||||
float n000,n001,n010,n011,n100,n101,n110,n111;
|
||||
float n00,n01,n10,n11;
|
||||
float n0,n1;
|
||||
|
||||
int px = stb__perlin_fastfloor(x);
|
||||
int py = stb__perlin_fastfloor(y);
|
||||
int pz = stb__perlin_fastfloor(z);
|
||||
int x_wrap2 = (x_wrap ? x_wrap : 256);
|
||||
int y_wrap2 = (y_wrap ? y_wrap : 256);
|
||||
int z_wrap2 = (z_wrap ? z_wrap : 256);
|
||||
int x0 = px % x_wrap2, x1;
|
||||
int y0 = py % y_wrap2, y1;
|
||||
int z0 = pz % z_wrap2, z1;
|
||||
int r0,r1, r00,r01,r10,r11;
|
||||
|
||||
if (x0 < 0) x0 += x_wrap2;
|
||||
if (y0 < 0) y0 += y_wrap2;
|
||||
if (z0 < 0) z0 += z_wrap2;
|
||||
x1 = (x0+1) % x_wrap2;
|
||||
y1 = (y0+1) % y_wrap2;
|
||||
z1 = (z0+1) % z_wrap2;
|
||||
|
||||
#define stb__perlin_ease(a) (((a*6-15)*a + 10) * a * a * a)
|
||||
|
||||
x -= px; u = stb__perlin_ease(x);
|
||||
y -= py; v = stb__perlin_ease(y);
|
||||
z -= pz; w = stb__perlin_ease(z);
|
||||
|
||||
r0 = stb__perlin_randtab[x0];
|
||||
r0 = stb__perlin_randtab[r0+seed];
|
||||
r1 = stb__perlin_randtab[x1];
|
||||
r1 = stb__perlin_randtab[r1+seed];
|
||||
|
||||
r00 = stb__perlin_randtab[r0+y0];
|
||||
r01 = stb__perlin_randtab[r0+y1];
|
||||
r10 = stb__perlin_randtab[r1+y0];
|
||||
r11 = stb__perlin_randtab[r1+y1];
|
||||
|
||||
n000 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r00+z0], x , y , z );
|
||||
n001 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r00+z1], x , y , z-1 );
|
||||
n010 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r01+z0], x , y-1, z );
|
||||
n011 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r01+z1], x , y-1, z-1 );
|
||||
n100 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r10+z0], x-1, y , z );
|
||||
n101 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r10+z1], x-1, y , z-1 );
|
||||
n110 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r11+z0], x-1, y-1, z );
|
||||
n111 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r11+z1], x-1, y-1, z-1 );
|
||||
|
||||
n00 = stb__perlin_lerp(n000,n001,w);
|
||||
n01 = stb__perlin_lerp(n010,n011,w);
|
||||
n10 = stb__perlin_lerp(n100,n101,w);
|
||||
n11 = stb__perlin_lerp(n110,n111,w);
|
||||
|
||||
n0 = stb__perlin_lerp(n00,n01,v);
|
||||
n1 = stb__perlin_lerp(n10,n11,v);
|
||||
|
||||
return stb__perlin_lerp(n0,n1,u);
|
||||
}
|
||||
#endif // STB_PERLIN_IMPLEMENTATION
|
||||
|
||||
/*
|
||||
------------------------------------------------------------------------------
|
||||
This software is available under 2 licenses -- choose whichever you prefer.
|
||||
------------------------------------------------------------------------------
|
||||
ALTERNATIVE A - MIT License
|
||||
Copyright (c) 2017 Sean Barrett
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
------------------------------------------------------------------------------
|
||||
ALTERNATIVE B - Public Domain (www.unlicense.org)
|
||||
This is free and unencumbered software released into the public domain.
|
||||
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
|
||||
software, either in source code form or as a compiled binary, for any purpose,
|
||||
commercial or non-commercial, and by any means.
|
||||
In jurisdictions that recognize copyright laws, the author or authors of this
|
||||
software dedicate any and all copyright interest in the software to the public
|
||||
domain. We make this dedication for the benefit of the public at large and to
|
||||
the detriment of our heirs and successors. We intend this dedication to be an
|
||||
overt act of relinquishment in perpetuity of all present and future rights to
|
||||
this software under copyright law.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
------------------------------------------------------------------------------
|
||||
*/
|
||||
|
|
@ -0,0 +1,623 @@
|
|||
// stb_rect_pack.h - v1.01 - public domain - rectangle packing
|
||||
// Sean Barrett 2014
|
||||
//
|
||||
// Useful for e.g. packing rectangular textures into an atlas.
|
||||
// Does not do rotation.
|
||||
//
|
||||
// Before #including,
|
||||
//
|
||||
// #define STB_RECT_PACK_IMPLEMENTATION
|
||||
//
|
||||
// in the file that you want to have the implementation.
|
||||
//
|
||||
// Not necessarily the awesomest packing method, but better than
|
||||
// the totally naive one in stb_truetype (which is primarily what
|
||||
// this is meant to replace).
|
||||
//
|
||||
// Has only had a few tests run, may have issues.
|
||||
//
|
||||
// More docs to come.
|
||||
//
|
||||
// No memory allocations; uses qsort() and assert() from stdlib.
|
||||
// Can override those by defining STBRP_SORT and STBRP_ASSERT.
|
||||
//
|
||||
// This library currently uses the Skyline Bottom-Left algorithm.
|
||||
//
|
||||
// Please note: better rectangle packers are welcome! Please
|
||||
// implement them to the same API, but with a different init
|
||||
// function.
|
||||
//
|
||||
// Credits
|
||||
//
|
||||
// Library
|
||||
// Sean Barrett
|
||||
// Minor features
|
||||
// Martins Mozeiko
|
||||
// github:IntellectualKitty
|
||||
//
|
||||
// Bugfixes / warning fixes
|
||||
// Jeremy Jaussaud
|
||||
// Fabian Giesen
|
||||
//
|
||||
// Version history:
|
||||
//
|
||||
// 1.01 (2021-07-11) always use large rect mode, expose STBRP__MAXVAL in public section
|
||||
// 1.00 (2019-02-25) avoid small space waste; gracefully fail too-wide rectangles
|
||||
// 0.99 (2019-02-07) warning fixes
|
||||
// 0.11 (2017-03-03) return packing success/fail result
|
||||
// 0.10 (2016-10-25) remove cast-away-const to avoid warnings
|
||||
// 0.09 (2016-08-27) fix compiler warnings
|
||||
// 0.08 (2015-09-13) really fix bug with empty rects (w=0 or h=0)
|
||||
// 0.07 (2015-09-13) fix bug with empty rects (w=0 or h=0)
|
||||
// 0.06 (2015-04-15) added STBRP_SORT to allow replacing qsort
|
||||
// 0.05: added STBRP_ASSERT to allow replacing assert
|
||||
// 0.04: fixed minor bug in STBRP_LARGE_RECTS support
|
||||
// 0.01: initial release
|
||||
//
|
||||
// LICENSE
|
||||
//
|
||||
// See end of file for license information.
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// INCLUDE SECTION
|
||||
//
|
||||
|
||||
#ifndef STB_INCLUDE_STB_RECT_PACK_H
|
||||
#define STB_INCLUDE_STB_RECT_PACK_H
|
||||
|
||||
#define STB_RECT_PACK_VERSION 1
|
||||
|
||||
#ifdef STBRP_STATIC
|
||||
#define STBRP_DEF static
|
||||
#else
|
||||
#define STBRP_DEF extern
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct stbrp_context stbrp_context;
|
||||
typedef struct stbrp_node stbrp_node;
|
||||
typedef struct stbrp_rect stbrp_rect;
|
||||
|
||||
typedef int stbrp_coord;
|
||||
|
||||
#define STBRP__MAXVAL 0x7fffffff
|
||||
// Mostly for internal use, but this is the maximum supported coordinate value.
|
||||
|
||||
STBRP_DEF int stbrp_pack_rects (stbrp_context *context, stbrp_rect *rects, int num_rects);
|
||||
// Assign packed locations to rectangles. The rectangles are of type
|
||||
// 'stbrp_rect' defined below, stored in the array 'rects', and there
|
||||
// are 'num_rects' many of them.
|
||||
//
|
||||
// Rectangles which are successfully packed have the 'was_packed' flag
|
||||
// set to a non-zero value and 'x' and 'y' store the minimum location
|
||||
// on each axis (i.e. bottom-left in cartesian coordinates, top-left
|
||||
// if you imagine y increasing downwards). Rectangles which do not fit
|
||||
// have the 'was_packed' flag set to 0.
|
||||
//
|
||||
// You should not try to access the 'rects' array from another thread
|
||||
// while this function is running, as the function temporarily reorders
|
||||
// the array while it executes.
|
||||
//
|
||||
// To pack into another rectangle, you need to call stbrp_init_target
|
||||
// again. To continue packing into the same rectangle, you can call
|
||||
// this function again. Calling this multiple times with multiple rect
|
||||
// arrays will probably produce worse packing results than calling it
|
||||
// a single time with the full rectangle array, but the option is
|
||||
// available.
|
||||
//
|
||||
// The function returns 1 if all of the rectangles were successfully
|
||||
// packed and 0 otherwise.
|
||||
|
||||
struct stbrp_rect
|
||||
{
|
||||
// reserved for your use:
|
||||
int id;
|
||||
|
||||
// input:
|
||||
stbrp_coord w, h;
|
||||
|
||||
// output:
|
||||
stbrp_coord x, y;
|
||||
int was_packed; // non-zero if valid packing
|
||||
|
||||
}; // 16 bytes, nominally
|
||||
|
||||
|
||||
STBRP_DEF void stbrp_init_target (stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes);
|
||||
// Initialize a rectangle packer to:
|
||||
// pack a rectangle that is 'width' by 'height' in dimensions
|
||||
// using temporary storage provided by the array 'nodes', which is 'num_nodes' long
|
||||
//
|
||||
// You must call this function every time you start packing into a new target.
|
||||
//
|
||||
// There is no "shutdown" function. The 'nodes' memory must stay valid for
|
||||
// the following stbrp_pack_rects() call (or calls), but can be freed after
|
||||
// the call (or calls) finish.
|
||||
//
|
||||
// Note: to guarantee best results, either:
|
||||
// 1. make sure 'num_nodes' >= 'width'
|
||||
// or 2. call stbrp_allow_out_of_mem() defined below with 'allow_out_of_mem = 1'
|
||||
//
|
||||
// If you don't do either of the above things, widths will be quantized to multiples
|
||||
// of small integers to guarantee the algorithm doesn't run out of temporary storage.
|
||||
//
|
||||
// If you do #2, then the non-quantized algorithm will be used, but the algorithm
|
||||
// may run out of temporary storage and be unable to pack some rectangles.
|
||||
|
||||
STBRP_DEF void stbrp_setup_allow_out_of_mem (stbrp_context *context, int allow_out_of_mem);
|
||||
// Optionally call this function after init but before doing any packing to
|
||||
// change the handling of the out-of-temp-memory scenario, described above.
|
||||
// If you call init again, this will be reset to the default (false).
|
||||
|
||||
|
||||
STBRP_DEF void stbrp_setup_heuristic (stbrp_context *context, int heuristic);
|
||||
// Optionally select which packing heuristic the library should use. Different
|
||||
// heuristics will produce better/worse results for different data sets.
|
||||
// If you call init again, this will be reset to the default.
|
||||
|
||||
enum
|
||||
{
|
||||
STBRP_HEURISTIC_Skyline_default=0,
|
||||
STBRP_HEURISTIC_Skyline_BL_sortHeight = STBRP_HEURISTIC_Skyline_default,
|
||||
STBRP_HEURISTIC_Skyline_BF_sortHeight
|
||||
};
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// the details of the following structures don't matter to you, but they must
|
||||
// be visible so you can handle the memory allocations for them
|
||||
|
||||
struct stbrp_node
|
||||
{
|
||||
stbrp_coord x,y;
|
||||
stbrp_node *next;
|
||||
};
|
||||
|
||||
struct stbrp_context
|
||||
{
|
||||
int width;
|
||||
int height;
|
||||
int align;
|
||||
int init_mode;
|
||||
int heuristic;
|
||||
int num_nodes;
|
||||
stbrp_node *active_head;
|
||||
stbrp_node *free_head;
|
||||
stbrp_node extra[2]; // we allocate two extra nodes so optimal user-node-count is 'width' not 'width+2'
|
||||
};
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMPLEMENTATION SECTION
|
||||
//
|
||||
|
||||
#ifdef STB_RECT_PACK_IMPLEMENTATION
|
||||
#ifndef STBRP_SORT
|
||||
#include <stdlib.h>
|
||||
#define STBRP_SORT qsort
|
||||
#endif
|
||||
|
||||
#ifndef STBRP_ASSERT
|
||||
#include <assert.h>
|
||||
#define STBRP_ASSERT assert
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define STBRP__NOTUSED(v) (void)(v)
|
||||
#define STBRP__CDECL __cdecl
|
||||
#else
|
||||
#define STBRP__NOTUSED(v) (void)sizeof(v)
|
||||
#define STBRP__CDECL
|
||||
#endif
|
||||
|
||||
enum
|
||||
{
|
||||
STBRP__INIT_skyline = 1
|
||||
};
|
||||
|
||||
STBRP_DEF void stbrp_setup_heuristic(stbrp_context *context, int heuristic)
|
||||
{
|
||||
switch (context->init_mode) {
|
||||
case STBRP__INIT_skyline:
|
||||
STBRP_ASSERT(heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight || heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight);
|
||||
context->heuristic = heuristic;
|
||||
break;
|
||||
default:
|
||||
STBRP_ASSERT(0);
|
||||
}
|
||||
}
|
||||
|
||||
STBRP_DEF void stbrp_setup_allow_out_of_mem(stbrp_context *context, int allow_out_of_mem)
|
||||
{
|
||||
if (allow_out_of_mem)
|
||||
// if it's ok to run out of memory, then don't bother aligning them;
|
||||
// this gives better packing, but may fail due to OOM (even though
|
||||
// the rectangles easily fit). @TODO a smarter approach would be to only
|
||||
// quantize once we've hit OOM, then we could get rid of this parameter.
|
||||
context->align = 1;
|
||||
else {
|
||||
// if it's not ok to run out of memory, then quantize the widths
|
||||
// so that num_nodes is always enough nodes.
|
||||
//
|
||||
// I.e. num_nodes * align >= width
|
||||
// align >= width / num_nodes
|
||||
// align = ceil(width/num_nodes)
|
||||
|
||||
context->align = (context->width + context->num_nodes-1) / context->num_nodes;
|
||||
}
|
||||
}
|
||||
|
||||
STBRP_DEF void stbrp_init_target(stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i=0; i < num_nodes-1; ++i)
|
||||
nodes[i].next = &nodes[i+1];
|
||||
nodes[i].next = NULL;
|
||||
context->init_mode = STBRP__INIT_skyline;
|
||||
context->heuristic = STBRP_HEURISTIC_Skyline_default;
|
||||
context->free_head = &nodes[0];
|
||||
context->active_head = &context->extra[0];
|
||||
context->width = width;
|
||||
context->height = height;
|
||||
context->num_nodes = num_nodes;
|
||||
stbrp_setup_allow_out_of_mem(context, 0);
|
||||
|
||||
// node 0 is the full width, node 1 is the sentinel (lets us not store width explicitly)
|
||||
context->extra[0].x = 0;
|
||||
context->extra[0].y = 0;
|
||||
context->extra[0].next = &context->extra[1];
|
||||
context->extra[1].x = (stbrp_coord) width;
|
||||
context->extra[1].y = (1<<30);
|
||||
context->extra[1].next = NULL;
|
||||
}
|
||||
|
||||
// find minimum y position if it starts at x1
|
||||
static int stbrp__skyline_find_min_y(stbrp_context *c, stbrp_node *first, int x0, int width, int *pwaste)
|
||||
{
|
||||
stbrp_node *node = first;
|
||||
int x1 = x0 + width;
|
||||
int min_y, visited_width, waste_area;
|
||||
|
||||
STBRP__NOTUSED(c);
|
||||
|
||||
STBRP_ASSERT(first->x <= x0);
|
||||
|
||||
#if 0
|
||||
// skip in case we're past the node
|
||||
while (node->next->x <= x0)
|
||||
++node;
|
||||
#else
|
||||
STBRP_ASSERT(node->next->x > x0); // we ended up handling this in the caller for efficiency
|
||||
#endif
|
||||
|
||||
STBRP_ASSERT(node->x <= x0);
|
||||
|
||||
min_y = 0;
|
||||
waste_area = 0;
|
||||
visited_width = 0;
|
||||
while (node->x < x1) {
|
||||
if (node->y > min_y) {
|
||||
// raise min_y higher.
|
||||
// we've accounted for all waste up to min_y,
|
||||
// but we'll now add more waste for everything we've visted
|
||||
waste_area += visited_width * (node->y - min_y);
|
||||
min_y = node->y;
|
||||
// the first time through, visited_width might be reduced
|
||||
if (node->x < x0)
|
||||
visited_width += node->next->x - x0;
|
||||
else
|
||||
visited_width += node->next->x - node->x;
|
||||
} else {
|
||||
// add waste area
|
||||
int under_width = node->next->x - node->x;
|
||||
if (under_width + visited_width > width)
|
||||
under_width = width - visited_width;
|
||||
waste_area += under_width * (min_y - node->y);
|
||||
visited_width += under_width;
|
||||
}
|
||||
node = node->next;
|
||||
}
|
||||
|
||||
*pwaste = waste_area;
|
||||
return min_y;
|
||||
}
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int x,y;
|
||||
stbrp_node **prev_link;
|
||||
} stbrp__findresult;
|
||||
|
||||
static stbrp__findresult stbrp__skyline_find_best_pos(stbrp_context *c, int width, int height)
|
||||
{
|
||||
int best_waste = (1<<30), best_x, best_y = (1 << 30);
|
||||
stbrp__findresult fr;
|
||||
stbrp_node **prev, *node, *tail, **best = NULL;
|
||||
|
||||
// align to multiple of c->align
|
||||
width = (width + c->align - 1);
|
||||
width -= width % c->align;
|
||||
STBRP_ASSERT(width % c->align == 0);
|
||||
|
||||
// if it can't possibly fit, bail immediately
|
||||
if (width > c->width || height > c->height) {
|
||||
fr.prev_link = NULL;
|
||||
fr.x = fr.y = 0;
|
||||
return fr;
|
||||
}
|
||||
|
||||
node = c->active_head;
|
||||
prev = &c->active_head;
|
||||
while (node->x + width <= c->width) {
|
||||
int y,waste;
|
||||
y = stbrp__skyline_find_min_y(c, node, node->x, width, &waste);
|
||||
if (c->heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight) { // actually just want to test BL
|
||||
// bottom left
|
||||
if (y < best_y) {
|
||||
best_y = y;
|
||||
best = prev;
|
||||
}
|
||||
} else {
|
||||
// best-fit
|
||||
if (y + height <= c->height) {
|
||||
// can only use it if it first vertically
|
||||
if (y < best_y || (y == best_y && waste < best_waste)) {
|
||||
best_y = y;
|
||||
best_waste = waste;
|
||||
best = prev;
|
||||
}
|
||||
}
|
||||
}
|
||||
prev = &node->next;
|
||||
node = node->next;
|
||||
}
|
||||
|
||||
best_x = (best == NULL) ? 0 : (*best)->x;
|
||||
|
||||
// if doing best-fit (BF), we also have to try aligning right edge to each node position
|
||||
//
|
||||
// e.g, if fitting
|
||||
//
|
||||
// ____________________
|
||||
// |____________________|
|
||||
//
|
||||
// into
|
||||
//
|
||||
// | |
|
||||
// | ____________|
|
||||
// |____________|
|
||||
//
|
||||
// then right-aligned reduces waste, but bottom-left BL is always chooses left-aligned
|
||||
//
|
||||
// This makes BF take about 2x the time
|
||||
|
||||
if (c->heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight) {
|
||||
tail = c->active_head;
|
||||
node = c->active_head;
|
||||
prev = &c->active_head;
|
||||
// find first node that's admissible
|
||||
while (tail->x < width)
|
||||
tail = tail->next;
|
||||
while (tail) {
|
||||
int xpos = tail->x - width;
|
||||
int y,waste;
|
||||
STBRP_ASSERT(xpos >= 0);
|
||||
// find the left position that matches this
|
||||
while (node->next->x <= xpos) {
|
||||
prev = &node->next;
|
||||
node = node->next;
|
||||
}
|
||||
STBRP_ASSERT(node->next->x > xpos && node->x <= xpos);
|
||||
y = stbrp__skyline_find_min_y(c, node, xpos, width, &waste);
|
||||
if (y + height <= c->height) {
|
||||
if (y <= best_y) {
|
||||
if (y < best_y || waste < best_waste || (waste==best_waste && xpos < best_x)) {
|
||||
best_x = xpos;
|
||||
STBRP_ASSERT(y <= best_y);
|
||||
best_y = y;
|
||||
best_waste = waste;
|
||||
best = prev;
|
||||
}
|
||||
}
|
||||
}
|
||||
tail = tail->next;
|
||||
}
|
||||
}
|
||||
|
||||
fr.prev_link = best;
|
||||
fr.x = best_x;
|
||||
fr.y = best_y;
|
||||
return fr;
|
||||
}
|
||||
|
||||
static stbrp__findresult stbrp__skyline_pack_rectangle(stbrp_context *context, int width, int height)
|
||||
{
|
||||
// find best position according to heuristic
|
||||
stbrp__findresult res = stbrp__skyline_find_best_pos(context, width, height);
|
||||
stbrp_node *node, *cur;
|
||||
|
||||
// bail if:
|
||||
// 1. it failed
|
||||
// 2. the best node doesn't fit (we don't always check this)
|
||||
// 3. we're out of memory
|
||||
if (res.prev_link == NULL || res.y + height > context->height || context->free_head == NULL) {
|
||||
res.prev_link = NULL;
|
||||
return res;
|
||||
}
|
||||
|
||||
// on success, create new node
|
||||
node = context->free_head;
|
||||
node->x = (stbrp_coord) res.x;
|
||||
node->y = (stbrp_coord) (res.y + height);
|
||||
|
||||
context->free_head = node->next;
|
||||
|
||||
// insert the new node into the right starting point, and
|
||||
// let 'cur' point to the remaining nodes needing to be
|
||||
// stiched back in
|
||||
|
||||
cur = *res.prev_link;
|
||||
if (cur->x < res.x) {
|
||||
// preserve the existing one, so start testing with the next one
|
||||
stbrp_node *next = cur->next;
|
||||
cur->next = node;
|
||||
cur = next;
|
||||
} else {
|
||||
*res.prev_link = node;
|
||||
}
|
||||
|
||||
// from here, traverse cur and free the nodes, until we get to one
|
||||
// that shouldn't be freed
|
||||
while (cur->next && cur->next->x <= res.x + width) {
|
||||
stbrp_node *next = cur->next;
|
||||
// move the current node to the free list
|
||||
cur->next = context->free_head;
|
||||
context->free_head = cur;
|
||||
cur = next;
|
||||
}
|
||||
|
||||
// stitch the list back in
|
||||
node->next = cur;
|
||||
|
||||
if (cur->x < res.x + width)
|
||||
cur->x = (stbrp_coord) (res.x + width);
|
||||
|
||||
#ifdef _DEBUG
|
||||
cur = context->active_head;
|
||||
while (cur->x < context->width) {
|
||||
STBRP_ASSERT(cur->x < cur->next->x);
|
||||
cur = cur->next;
|
||||
}
|
||||
STBRP_ASSERT(cur->next == NULL);
|
||||
|
||||
{
|
||||
int count=0;
|
||||
cur = context->active_head;
|
||||
while (cur) {
|
||||
cur = cur->next;
|
||||
++count;
|
||||
}
|
||||
cur = context->free_head;
|
||||
while (cur) {
|
||||
cur = cur->next;
|
||||
++count;
|
||||
}
|
||||
STBRP_ASSERT(count == context->num_nodes+2);
|
||||
}
|
||||
#endif
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static int STBRP__CDECL rect_height_compare(const void *a, const void *b)
|
||||
{
|
||||
const stbrp_rect *p = (const stbrp_rect *) a;
|
||||
const stbrp_rect *q = (const stbrp_rect *) b;
|
||||
if (p->h > q->h)
|
||||
return -1;
|
||||
if (p->h < q->h)
|
||||
return 1;
|
||||
return (p->w > q->w) ? -1 : (p->w < q->w);
|
||||
}
|
||||
|
||||
static int STBRP__CDECL rect_original_order(const void *a, const void *b)
|
||||
{
|
||||
const stbrp_rect *p = (const stbrp_rect *) a;
|
||||
const stbrp_rect *q = (const stbrp_rect *) b;
|
||||
return (p->was_packed < q->was_packed) ? -1 : (p->was_packed > q->was_packed);
|
||||
}
|
||||
|
||||
STBRP_DEF int stbrp_pack_rects(stbrp_context *context, stbrp_rect *rects, int num_rects)
|
||||
{
|
||||
int i, all_rects_packed = 1;
|
||||
|
||||
// we use the 'was_packed' field internally to allow sorting/unsorting
|
||||
for (i=0; i < num_rects; ++i) {
|
||||
rects[i].was_packed = i;
|
||||
}
|
||||
|
||||
// sort according to heuristic
|
||||
STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_height_compare);
|
||||
|
||||
for (i=0; i < num_rects; ++i) {
|
||||
if (rects[i].w == 0 || rects[i].h == 0) {
|
||||
rects[i].x = rects[i].y = 0; // empty rect needs no space
|
||||
} else {
|
||||
stbrp__findresult fr = stbrp__skyline_pack_rectangle(context, rects[i].w, rects[i].h);
|
||||
if (fr.prev_link) {
|
||||
rects[i].x = (stbrp_coord) fr.x;
|
||||
rects[i].y = (stbrp_coord) fr.y;
|
||||
} else {
|
||||
rects[i].x = rects[i].y = STBRP__MAXVAL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// unsort
|
||||
STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_original_order);
|
||||
|
||||
// set was_packed flags and all_rects_packed status
|
||||
for (i=0; i < num_rects; ++i) {
|
||||
rects[i].was_packed = !(rects[i].x == STBRP__MAXVAL && rects[i].y == STBRP__MAXVAL);
|
||||
if (!rects[i].was_packed)
|
||||
all_rects_packed = 0;
|
||||
}
|
||||
|
||||
// return the all_rects_packed status
|
||||
return all_rects_packed;
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
------------------------------------------------------------------------------
|
||||
This software is available under 2 licenses -- choose whichever you prefer.
|
||||
------------------------------------------------------------------------------
|
||||
ALTERNATIVE A - MIT License
|
||||
Copyright (c) 2017 Sean Barrett
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
------------------------------------------------------------------------------
|
||||
ALTERNATIVE B - Public Domain (www.unlicense.org)
|
||||
This is free and unencumbered software released into the public domain.
|
||||
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
|
||||
software, either in source code form or as a compiled binary, for any purpose,
|
||||
commercial or non-commercial, and by any means.
|
||||
In jurisdictions that recognize copyright laws, the author or authors of this
|
||||
software dedicate any and all copyright interest in the software to the public
|
||||
domain. We make this dedication for the benefit of the public at large and to
|
||||
the detriment of our heirs and successors. We intend this dedication to be an
|
||||
overt act of relinquishment in perpetuity of all present and future rights to
|
||||
this software under copyright law.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
------------------------------------------------------------------------------
|
||||
*/
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,66 @@
|
|||
#include <SDL2/SDL.h>
|
||||
#include <fostbite2D/app/application.h>
|
||||
namespace frostbite2D {
|
||||
|
||||
Application &Application::get() {
|
||||
static Application instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void Application::use(Module &m) {
|
||||
for (auto *existing : modules_) {
|
||||
if (existing == &m) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
modules_.push_back(&m);
|
||||
}
|
||||
|
||||
void Application::use(std::initializer_list<Module *> modules) {
|
||||
for (auto *m : modules) {
|
||||
if (m) {
|
||||
use(*m);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Application::init() {
|
||||
AppConfig cfg;
|
||||
return init(cfg);
|
||||
}
|
||||
|
||||
bool Application::init(const AppConfig &config) {
|
||||
if (initialized_) {
|
||||
return true;
|
||||
}
|
||||
|
||||
this->window_ = new Window();
|
||||
|
||||
if (!window_->create(config.windowConfig)) {
|
||||
SDL_Log("Failed to create window");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Application::shutdown() {
|
||||
if (!initialized_)
|
||||
return;
|
||||
|
||||
// VRAMMgr::get().printStats();
|
||||
|
||||
// ServiceLocator::instance().clear();
|
||||
|
||||
// window_ = nullptr;
|
||||
|
||||
initialized_ = false;
|
||||
running_ = false;
|
||||
}
|
||||
|
||||
Application::~Application() {
|
||||
if (initialized_) {
|
||||
shutdown();
|
||||
}
|
||||
}
|
||||
} // namespace frostbite2D
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
#include <fostbite2D/config/app_config.h>
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
AppConfig AppConfig::createDefault() {
|
||||
AppConfig config;
|
||||
|
||||
config.appName = "Frostbite2D App";
|
||||
config.appVersion = "1.0.0";
|
||||
config.organization = "";
|
||||
config.configFile = "config.json";
|
||||
config.targetPlatform = PlatformType::Auto;
|
||||
config.windowConfig.title = "Frostbite2D";
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,149 @@
|
|||
#include "SDL_log.h"
|
||||
#include <SDL2/SDL.h>
|
||||
#include <fostbite2D/platform/window.h>
|
||||
#include <glad/glad.h>
|
||||
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
bool Window::create(const WindowConfig &cfg) {
|
||||
if (SDL_Init(SDL_INIT_EVERYTHING) != 0) {
|
||||
SDL_Log("Failed to initialize SDL: %s", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
Uint32 flags = SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN;
|
||||
#ifdef __SWITCH__
|
||||
flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
|
||||
#else
|
||||
if (cfg.fullscreen) {
|
||||
flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
|
||||
} else if (cfg.borderless) {
|
||||
flags |= SDL_WINDOW_BORDERLESS;
|
||||
}
|
||||
if (cfg.resizable) {
|
||||
flags |= SDL_WINDOW_RESIZABLE;
|
||||
}
|
||||
if (!cfg.decorated) {
|
||||
flags |= SDL_WINDOW_BORDERLESS;
|
||||
}
|
||||
#endif
|
||||
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
|
||||
|
||||
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
|
||||
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
|
||||
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
|
||||
|
||||
if (cfg.multisamples > 0) {
|
||||
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
|
||||
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, cfg.multisamples);
|
||||
}
|
||||
|
||||
int x = SDL_WINDOWPOS_CENTERED;
|
||||
int y = SDL_WINDOWPOS_CENTERED;
|
||||
if (!cfg.centered) {
|
||||
x = SDL_WINDOWPOS_UNDEFINED;
|
||||
y = SDL_WINDOWPOS_UNDEFINED;
|
||||
}
|
||||
|
||||
// 创建窗口
|
||||
sdlWindow_ =
|
||||
SDL_CreateWindow(cfg.title.c_str(), x, y, cfg.width, cfg.height, flags);
|
||||
|
||||
if (!sdlWindow_) {
|
||||
SDL_Log("Failed to create window: %s", SDL_GetError());
|
||||
SDL_Quit();
|
||||
return false;
|
||||
}
|
||||
|
||||
glContext_ = SDL_GL_CreateContext(sdlWindow_);
|
||||
if (!glContext_) {
|
||||
SDL_Log("Failed to create OpenGL context: %s", SDL_GetError());
|
||||
SDL_DestroyWindow(sdlWindow_);
|
||||
sdlWindow_ = nullptr;
|
||||
SDL_Quit();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!gladLoadGLES2Loader((GLADloadproc)SDL_GL_GetProcAddress)) {
|
||||
SDL_Log("Failed to initialize GLAD GLES2");
|
||||
SDL_GL_DeleteContext(glContext_);
|
||||
glContext_ = nullptr;
|
||||
SDL_DestroyWindow(sdlWindow_);
|
||||
sdlWindow_ = nullptr;
|
||||
SDL_Quit();
|
||||
return false;
|
||||
}
|
||||
|
||||
SDL_GL_SetSwapInterval(cfg.vsync ? 1 : 0);
|
||||
|
||||
SDL_GetWindowSize(sdlWindow_, &width_, &height_);
|
||||
fullscreen_ = (flags & SDL_WINDOW_FULLSCREEN_DESKTOP) != 0;
|
||||
vsync_ = cfg.vsync;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Window::destroy() {}
|
||||
|
||||
void Window::poll() {}
|
||||
|
||||
void Window::swap() {}
|
||||
|
||||
void Window::close() { shouldClose_ = true; }
|
||||
|
||||
void Window::setTitle(const std::string &title) {}
|
||||
|
||||
void Window::setSize(int w, int h) {
|
||||
width_ = w;
|
||||
height_ = h;
|
||||
}
|
||||
|
||||
void Window::setPos(int x, int y) {}
|
||||
|
||||
void Window::setFullscreen(bool fs) { fullscreen_ = fs; }
|
||||
|
||||
void Window::setVSync(bool vsync) { vsync_ = vsync; }
|
||||
|
||||
void Window::setVisible(bool visible) {}
|
||||
|
||||
int Window::width() const { return width_; }
|
||||
|
||||
int Window::height() const { return height_; }
|
||||
|
||||
Size Window::size() const {
|
||||
return Size{static_cast<float>(width_), static_cast<float>(height_)};
|
||||
}
|
||||
|
||||
Vec2 Window::pos() const { return Vec2{0.0f, 0.0f}; }
|
||||
|
||||
bool Window::fullscreen() const { return fullscreen_; }
|
||||
|
||||
bool Window::vsync() const { return vsync_; }
|
||||
|
||||
bool Window::focused() const { return focused_; }
|
||||
|
||||
bool Window::minimized() const { return minimized_; }
|
||||
|
||||
float Window::scaleX() const { return scaleX_; }
|
||||
|
||||
float Window::scaleY() const { return scaleY_; }
|
||||
|
||||
void Window::setCursor(CursorType cursor) {}
|
||||
|
||||
void Window::showCursor(bool show) { cursorVisible_ = show; }
|
||||
|
||||
void Window::lockCursor(bool lock) { cursorLocked_ = lock; }
|
||||
|
||||
void Window::onResize(ResizeCb cb) { resizeCb_ = cb; }
|
||||
|
||||
void Window::onClose(CloseCb cb) { closeCb_ = cb; }
|
||||
|
||||
void Window::onFocus(FocusCb cb) { focusCb_ = cb; }
|
||||
|
||||
void *Window::native() const { return nullptr; }
|
||||
|
||||
} // namespace frostbite2D
|
||||
|
|
@ -0,0 +1,318 @@
|
|||
#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
|
||||
|
|
@ -0,0 +1,313 @@
|
|||
#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
|
||||
|
|
@ -0,0 +1,802 @@
|
|||
#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
|
||||
|
|
@ -0,0 +1,355 @@
|
|||
#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
|
||||
|
|
@ -0,0 +1,382 @@
|
|||
#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
|
||||
|
|
@ -0,0 +1,513 @@
|
|||
#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
|
||||
|
|
@ -0,0 +1,231 @@
|
|||
#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
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
#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
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,232 @@
|
|||
#include <SDL2/SDL.h>
|
||||
#include <cmath>
|
||||
#include <fostbite2D/app/application.h>
|
||||
#include <fostbite2D/core/color.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 <iostream>
|
||||
|
||||
using namespace frostbite2D;
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
|
||||
AppConfig config = AppConfig::createDefault();
|
||||
config.appName = "Frostbite2D Render Test";
|
||||
config.appVersion = "1.0.0";
|
||||
config.windowConfig.width = 800;
|
||||
config.windowConfig.height = 600;
|
||||
config.windowConfig.title = "Frostbite2D - OpenGL Render Test";
|
||||
|
||||
Application &app = Application::get();
|
||||
if (!app.init(config)) {
|
||||
std::cerr << "Failed to initialize application!" << std::endl;
|
||||
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;
|
||||
}
|
||||
|
||||
// 加载字体(使用系统默认字体或项目字体)
|
||||
std::string fontPath = "C:/Windows/Fonts/arial.ttf"; // Windows 系统字体
|
||||
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();
|
||||
|
||||
std::cout << "程序正常退出" << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
|
||||
add_requires("libsdl2", {configs = {shared = true,wayland = true}})
|
||||
add_requires("glm")
|
||||
|
||||
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"))
|
||||
|
||||
add_packages("libsdl2")
|
||||
add_packages("glm")
|
||||
target_end()
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
-- MinGW 编译配置
|
||||
set_toolchains("mingw")
|
||||
|
||||
add_requires("libsdl2", {configs = {shared = true}})
|
||||
add_requires("glm")
|
||||
|
||||
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"))
|
||||
|
||||
add_packages("libsdl2")
|
||||
add_packages("glm")
|
||||
|
||||
-- 复制着色器文件到输出目录
|
||||
after_build(function (target)
|
||||
-- 复制 shaders 目录
|
||||
local shaders_dir = path.join(os.projectdir(), "shaders")
|
||||
local output_dir = target:targetdir()
|
||||
local target_shaders_dir = path.join(output_dir, "shaders")
|
||||
|
||||
if os.isdir(shaders_dir) then
|
||||
-- 确保目标目录存在
|
||||
if not os.isdir(target_shaders_dir) then
|
||||
os.mkdir(target_shaders_dir)
|
||||
end
|
||||
|
||||
-- 复制所有着色器文件
|
||||
for _, file in ipairs(os.files(path.join(shaders_dir, "*.*"))) do
|
||||
local filename = path.filename(file)
|
||||
local target_file = path.join(target_shaders_dir, filename)
|
||||
os.cp(file, target_file)
|
||||
print("Copy shader: " .. filename)
|
||||
end
|
||||
end
|
||||
|
||||
-- 复制 SDL2 DLL (Windows 平台)
|
||||
if is_plat("mingw") or is_plat("windows") then
|
||||
local sdl2_lib = target:pkg("libsdl2")
|
||||
if sdl2_lib then
|
||||
local libfiles = sdl2_lib:get("libfiles")
|
||||
if libfiles then
|
||||
for _, libfile in ipairs(libfiles) do
|
||||
-- 查找 DLL 文件
|
||||
if libfile:endswith(".dll") then
|
||||
local target_dll = path.join(output_dir, path.filename(libfile))
|
||||
os.cp(libfile, target_dll)
|
||||
print("Copy DLL: " .. path.filename(libfile))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- 尝试从 xmake 包目录复制 SDL2.dll
|
||||
local sdl2_dll_paths = {
|
||||
path.join(os.getenv("USERPROFILE") or "", ".xmake/packages/l/libsdl2/**/bin/SDL2.dll"),
|
||||
path.join(os.getenv("USERPROFILE") or "", ".xmake/packages/l/libsdl2/**/lib/SDL2.dll"),
|
||||
}
|
||||
|
||||
for _, dll_pattern in ipairs(sdl2_dll_paths) do
|
||||
local dll_files = os.files(dll_pattern)
|
||||
for _, dll_file in ipairs(dll_files) do
|
||||
local target_dll = path.join(output_dir, "SDL2.dll")
|
||||
if not os.isfile(target_dll) then
|
||||
os.cp(dll_file, target_dll)
|
||||
print("Copy SDL2.dll from: " .. dll_file)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
target_end()
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
-- MinGW 编译配置
|
||||
set_toolchains("mingw")
|
||||
|
||||
add_requires("libsdl2", {configs = {shared = true}})
|
||||
add_requires("glm")
|
||||
|
||||
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"))
|
||||
|
||||
add_packages("libsdl2")
|
||||
add_packages("glm")
|
||||
|
||||
-- 复制着色器文件到输出目录
|
||||
after_build(function (target)
|
||||
-- 复制 shaders 目录
|
||||
local shaders_dir = path.join(os.projectdir(), "shaders")
|
||||
local output_dir = target:targetdir()
|
||||
local target_shaders_dir = path.join(output_dir, "shaders")
|
||||
|
||||
if os.isdir(shaders_dir) then
|
||||
-- 确保目标目录存在
|
||||
if not os.isdir(target_shaders_dir) then
|
||||
os.mkdir(target_shaders_dir)
|
||||
end
|
||||
|
||||
-- 复制所有着色器文件
|
||||
for _, file in ipairs(os.files(path.join(shaders_dir, "*.*"))) do
|
||||
local filename = path.filename(file)
|
||||
local target_file = path.join(target_shaders_dir, filename)
|
||||
os.cp(file, target_file)
|
||||
end
|
||||
end
|
||||
|
||||
-- 复制 SDL2 DLL
|
||||
local sdl2_lib = target:pkg("libsdl2")
|
||||
if sdl2_lib then
|
||||
local libfiles = sdl2_lib:get("libfiles")
|
||||
if libfiles then
|
||||
for _, libfile in ipairs(libfiles) do
|
||||
-- 查找 DLL 文件
|
||||
if libfile:endswith(".dll") then
|
||||
local target_dll = path.join(output_dir, path.filename(libfile))
|
||||
os.cp(libfile, target_dll)
|
||||
print("Copy DLL: " .. path.filename(libfile))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- 尝试从 xmake 包目录复制 SDL2.dll
|
||||
local sdl2_dll_paths = {
|
||||
path.join(os.getenv("USERPROFILE") or "", ".xmake/packages/l/libsdl2/**/bin/SDL2.dll"),
|
||||
path.join(os.getenv("USERPROFILE") or "", ".xmake/packages/l/libsdl2/**/lib/SDL2.dll"),
|
||||
}
|
||||
|
||||
for _, dll_pattern in ipairs(sdl2_dll_paths) do
|
||||
local dll_files = os.files(dll_pattern)
|
||||
for _, dll_file in ipairs(dll_files) do
|
||||
local target_dll = path.join(output_dir, "SDL2.dll")
|
||||
if not os.isfile(target_dll) then
|
||||
os.cp(dll_file, target_dll)
|
||||
print("Copy SDL2.dll from: " .. dll_file)
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
target_end()
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
#version 300 es
|
||||
precision highp float;
|
||||
in vec4 vColor;
|
||||
out vec4 fragColor;
|
||||
|
||||
void main() {
|
||||
fragColor = vColor;
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
#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;
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
#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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
#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;
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
set_project("Frostbite2D")
|
||||
set_version("1.0.0")
|
||||
set_license("MIT")
|
||||
|
||||
-- 语言和编码设置
|
||||
set_languages("c++17")
|
||||
set_encodings("utf-8")
|
||||
|
||||
add_rules("mode.debug", "mode.release")
|
||||
|
||||
local host_plat = os.host()
|
||||
local target_plat = get_config("plat") or host_plat
|
||||
local supported_plats = {mingw = true, windows = true, linux = true, macosx = true, switch = true}
|
||||
|
||||
-- 自动选择平台
|
||||
if not supported_plats[target_plat] then
|
||||
raise("Unsupported platform: " .. target_plat .. ". Supported platforms: mingw, windows, linux, macosx, switch")
|
||||
end
|
||||
|
||||
|
||||
-- 引入对应平台的配置文件
|
||||
local platform_config_file = "platform/" .. target_plat .. ".lua"
|
||||
if os.isfile(platform_config_file) then
|
||||
includes(platform_config_file)
|
||||
else
|
||||
print("Platform config file not found: " .. platform_config_file)
|
||||
end
|
||||
|
||||
|
||||
Loading…
Reference in New Issue