渲染后端加入
This commit is contained in:
14
.gitignore
vendored
Normal file
14
.gitignore
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
# Xmake cache
|
||||
.xmake/
|
||||
build/
|
||||
|
||||
# MacOS Cache
|
||||
.DS_Store
|
||||
|
||||
|
||||
.vscode/
|
||||
.cache/
|
||||
|
||||
*.json
|
||||
|
||||
.vscode/compile_commands.json
|
||||
567
Engine_Architecture.md
Normal file
567
Engine_Architecture.md
Normal file
@@ -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();
|
||||
```
|
||||
311
Fostbite2D/include/KHR/khrplatform.h
Normal file
311
Fostbite2D/include/KHR/khrplatform.h
Normal file
@@ -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_ */
|
||||
216
Fostbite2D/include/fostbite2D/app/application.h
Normal file
216
Fostbite2D/include/fostbite2D/app/application.h
Normal file
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
39
Fostbite2D/include/fostbite2D/config/app_config.h
Normal file
39
Fostbite2D/include/fostbite2D/config/app_config.h
Normal file
@@ -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
|
||||
86
Fostbite2D/include/fostbite2D/config/platform_config.h
Normal file
86
Fostbite2D/include/fostbite2D/config/platform_config.h
Normal file
@@ -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);
|
||||
|
||||
}
|
||||
156
Fostbite2D/include/fostbite2D/core/color.h
Normal file
156
Fostbite2D/include/fostbite2D/core/color.h
Normal file
@@ -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
|
||||
552
Fostbite2D/include/fostbite2D/core/math_types.h
Normal file
552
Fostbite2D/include/fostbite2D/core/math_types.h
Normal file
@@ -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
|
||||
58
Fostbite2D/include/fostbite2D/core/types.h
Normal file
58
Fostbite2D/include/fostbite2D/core/types.h
Normal file
@@ -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
|
||||
93
Fostbite2D/include/fostbite2D/module/module.h
Normal file
93
Fostbite2D/include/fostbite2D/module/module.h
Normal file
@@ -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
|
||||
272
Fostbite2D/include/fostbite2D/platform/window.h
Normal file
272
Fostbite2D/include/fostbite2D/platform/window.h
Normal file
@@ -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
|
||||
92
Fostbite2D/include/fostbite2D/render/camera.h
Normal file
92
Fostbite2D/include/fostbite2D/render/camera.h
Normal file
@@ -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
|
||||
87
Fostbite2D/include/fostbite2D/render/font.h
Normal file
87
Fostbite2D/include/fostbite2D/render/font.h
Normal file
@@ -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
|
||||
85
Fostbite2D/include/fostbite2D/render/opengl/gl_font_atlas.h
Normal file
85
Fostbite2D/include/fostbite2D/render/opengl/gl_font_atlas.h
Normal file
@@ -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
|
||||
316
Fostbite2D/include/fostbite2D/render/opengl/gl_renderer.h
Normal file
316
Fostbite2D/include/fostbite2D/render/opengl/gl_renderer.h
Normal file
@@ -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
|
||||
194
Fostbite2D/include/fostbite2D/render/opengl/gl_shader.h
Normal file
194
Fostbite2D/include/fostbite2D/render/opengl/gl_shader.h
Normal file
@@ -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
|
||||
79
Fostbite2D/include/fostbite2D/render/opengl/gl_texture.h
Normal file
79
Fostbite2D/include/fostbite2D/render/opengl/gl_texture.h
Normal file
@@ -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
|
||||
151
Fostbite2D/include/fostbite2D/render/shader/shader_interface.h
Normal file
151
Fostbite2D/include/fostbite2D/render/shader/shader_interface.h
Normal file
@@ -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
|
||||
129
Fostbite2D/include/fostbite2D/render/shader/shader_manager.h
Normal file
129
Fostbite2D/include/fostbite2D/render/shader/shader_manager.h
Normal file
@@ -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
|
||||
154
Fostbite2D/include/fostbite2D/render/texture.h
Normal file
154
Fostbite2D/include/fostbite2D/render/texture.h
Normal file
@@ -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
|
||||
7028
Fostbite2D/include/glad/glad.h
Normal file
7028
Fostbite2D/include/glad/glad.h
Normal file
File diff suppressed because one or more lines are too long
7988
Fostbite2D/include/stb/stb_image.h
Normal file
7988
Fostbite2D/include/stb/stb_image.h
Normal file
File diff suppressed because it is too large
Load Diff
10601
Fostbite2D/include/stb/stb_image_resize2.h
Normal file
10601
Fostbite2D/include/stb/stb_image_resize2.h
Normal file
File diff suppressed because it is too large
Load Diff
1724
Fostbite2D/include/stb/stb_image_write.h
Normal file
1724
Fostbite2D/include/stb/stb_image_write.h
Normal file
File diff suppressed because it is too large
Load Diff
428
Fostbite2D/include/stb/stb_perlin.h
Normal file
428
Fostbite2D/include/stb/stb_perlin.h
Normal file
@@ -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.
|
||||
------------------------------------------------------------------------------
|
||||
*/
|
||||
623
Fostbite2D/include/stb/stb_rect_pack.h
Normal file
623
Fostbite2D/include/stb/stb_rect_pack.h
Normal file
@@ -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.
|
||||
------------------------------------------------------------------------------
|
||||
*/
|
||||
5079
Fostbite2D/include/stb/stb_truetype.h
Normal file
5079
Fostbite2D/include/stb/stb_truetype.h
Normal file
File diff suppressed because it is too large
Load Diff
66
Fostbite2D/src/fostbite2D/app/application.cpp
Normal file
66
Fostbite2D/src/fostbite2D/app/application.cpp
Normal file
@@ -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
|
||||
20
Fostbite2D/src/fostbite2D/config/app_config.cpp
Normal file
20
Fostbite2D/src/fostbite2D/config/app_config.cpp
Normal file
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
149
Fostbite2D/src/fostbite2D/platform/window.cpp
Normal file
149
Fostbite2D/src/fostbite2D/platform/window.cpp
Normal file
@@ -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
|
||||
318
Fostbite2D/src/fostbite2D/render/camera.cpp
Normal file
318
Fostbite2D/src/fostbite2D/render/camera.cpp
Normal file
@@ -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
|
||||
313
Fostbite2D/src/fostbite2D/render/opengl/gl_font_atlas.cpp
Normal file
313
Fostbite2D/src/fostbite2D/render/opengl/gl_font_atlas.cpp
Normal file
@@ -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
|
||||
802
Fostbite2D/src/fostbite2D/render/opengl/gl_renderer.cpp
Normal file
802
Fostbite2D/src/fostbite2D/render/opengl/gl_renderer.cpp
Normal file
@@ -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
|
||||
355
Fostbite2D/src/fostbite2D/render/opengl/gl_shader.cpp
Normal file
355
Fostbite2D/src/fostbite2D/render/opengl/gl_shader.cpp
Normal file
@@ -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
|
||||
382
Fostbite2D/src/fostbite2D/render/opengl/gl_sprite_batch.cpp
Normal file
382
Fostbite2D/src/fostbite2D/render/opengl/gl_sprite_batch.cpp
Normal file
@@ -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
|
||||
513
Fostbite2D/src/fostbite2D/render/opengl/gl_texture.cpp
Normal file
513
Fostbite2D/src/fostbite2D/render/opengl/gl_texture.cpp
Normal file
@@ -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
|
||||
231
Fostbite2D/src/fostbite2D/render/shader/shader_manager.cpp
Normal file
231
Fostbite2D/src/fostbite2D/render/shader/shader_manager.cpp
Normal file
@@ -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
|
||||
66
Fostbite2D/src/fostbite2D/render/texture.cpp
Normal file
66
Fostbite2D/src/fostbite2D/render/texture.cpp
Normal file
@@ -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
|
||||
3602
Fostbite2D/src/glad/glad.c
Normal file
3602
Fostbite2D/src/glad/glad.c
Normal file
File diff suppressed because one or more lines are too long
232
Fostbite2D/src/main.cpp
Normal file
232
Fostbite2D/src/main.cpp
Normal file
@@ -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;
|
||||
}
|
||||
13
platform/linux.lua
Normal file
13
platform/linux.lua
Normal file
@@ -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()
|
||||
73
platform/mingw.lua
Normal file
73
platform/mingw.lua
Normal file
@@ -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()
|
||||
70
platform/windows.lua
Normal file
70
platform/windows.lua
Normal file
@@ -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()
|
||||
8
shaders/shape.frag
Normal file
8
shaders/shape.frag
Normal file
@@ -0,0 +1,8 @@
|
||||
#version 300 es
|
||||
precision highp float;
|
||||
in vec4 vColor;
|
||||
out vec4 fragColor;
|
||||
|
||||
void main() {
|
||||
fragColor = vColor;
|
||||
}
|
||||
13
shaders/shape.vert
Normal file
13
shaders/shape.vert
Normal file
@@ -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;
|
||||
}
|
||||
21
shaders/sprite.frag
Normal file
21
shaders/sprite.frag
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
16
shaders/sprite.vert
Normal file
16
shaders/sprite.vert
Normal file
@@ -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;
|
||||
}
|
||||
29
xmake.lua
Normal file
29
xmake.lua
Normal file
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user