渲染后端加入

This commit is contained in:
lenheart 2026-02-17 13:28:38 +08:00
commit a5379b3816
49 changed files with 44512 additions and 0 deletions

14
.gitignore vendored Normal file
View 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
View 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();
```

View 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_ */

View 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;
};
}

View 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

View 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);
}

View 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

View 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 &center, 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 &center1, float radius1,
const Vec2 &center2, 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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 truefalse
*/
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 &center, float radius, const Color &color,
int segments, float width);
/**
* @brief
* @param center
* @param radius
* @param color
* @param segments
*/
void fillCircle(const Vec2 &center, 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

View 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 truefalse
*/
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 truefalse
*/
bool compileFromSource(const char *vertexSource, const char *fragmentSource);
/**
* @brief Shader
* @param binary
* @return truefalse
*/
bool compileFromBinary(const std::vector<uint8_t> &binary);
/**
* @brief Shader二进制数据
* @param outBinary
* @return truefalse
*/
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 ID0
*/
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 truefalse
*/
bool getShaderBinary(const IShader &shader,
std::vector<uint8_t> &outBinary) override;
};
} // namespace frostbite2D

View File

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

View 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

View 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 truefalse
*/
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 truefalse
*/
virtual bool getShaderBinary(const IShader &shader,
std::vector<uint8_t> &outBinary) = 0;
};
} // namespace frostbite2D

View 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 truefalse
*/
bool init(Ptr<IShaderFactory> factory);
/**
* @brief Shader系统
*/
void shutdown();
/**
* @brief
* @return truefalse
*/
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 truefalse
*/
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

View 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 truefalse
*/
virtual bool isValid() const = 0;
/**
* @brief
* @param linear true使用线性过滤false使用最近邻过滤
*/
virtual void setFilter(bool linear) = 0;
/**
* @brief
* @param repeat true使用重复模式false使用边缘拉伸模式
*/
virtual void setWrap(bool repeat) = 0;
};
// ============================================================================
// Alpha遮罩 - 用于像素级碰撞检测
// ============================================================================
class AlphaMask {
public:
AlphaMask() = default;
AlphaMask(AlphaMask &&) = default;
AlphaMask &operator=(AlphaMask &&) = default;
/**
* @brief Alpha遮罩
* @param pixels
* @param width
* @param height
* @param channels
* @return AlphaMask
*/
static AlphaMask createFromPixels(const uint8_t *pixels, int width,
int height, int channels);
/**
* @brief
* @return true
*/
bool isValid() const { return !data_.empty() && width_ > 0 && height_ > 0; }
/**
* @brief
* @param x X坐标
* @param y Y坐标
* @return 0-255
*/
uint8_t getAlpha(int x, int y) const;
/**
* @brief
* @param x X坐标
* @param y Y坐标
* @return true
*/
bool isOpaque(int x, int y) const;
/**
* @brief
* @return
*/
int getWidth() const { return width_; }
/**
* @brief
* @return
*/
int getHeight() const { return height_; }
private:
std::vector<uint8_t> data_;
int width_ = 0;
int height_ = 0;
};
} // namespace frostbite2D

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View 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.
------------------------------------------------------------------------------
*/

View 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.
------------------------------------------------------------------------------
*/

File diff suppressed because it is too large Load Diff

View 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

View 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;
}
}

View 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

View 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

View 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

View 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 truefalse
*/
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 &center, float radius,
const Color &color, int segments, float width) {
// 限制段数不超过缓存大小
if (segments > static_cast<int>(MAX_CIRCLE_SEGMENTS)) {
segments = static_cast<int>(MAX_CIRCLE_SEGMENTS);
}
// 如果线宽改变,需要先刷新线条批次
if (width != currentLineWidth_) {
flushLineBatch();
currentLineWidth_ = width;
}
// 使用线条批处理绘制圆形
for (int i = 0; i < segments; ++i) {
float angle1 =
2.0f * 3.14159f * static_cast<float>(i) / static_cast<float>(segments);
float angle2 = 2.0f * 3.14159f * static_cast<float>(i + 1) /
static_cast<float>(segments);
addLineVertex(center.x + radius * cosf(angle1),
center.y + radius * sinf(angle1), color);
addLineVertex(center.x + radius * cosf(angle2),
center.y + radius * sinf(angle2), color);
}
}
/**
* @brief
* @param center
* @param radius
* @param color
* @param segments
*/
void GLRenderer::fillCircle(const Vec2 &center, float radius,
const Color &color, int segments) {
// 限制段数不超过缓存大小
if (segments > static_cast<int>(MAX_CIRCLE_SEGMENTS)) {
segments = static_cast<int>(MAX_CIRCLE_SEGMENTS);
}
// 提交当前批次(如果模式不同)
submitShapeBatch(GL_TRIANGLES);
// 使用三角形扇形填充圆
// 中心点 + 边缘点
for (int i = 0; i < segments; ++i) {
float angle1 =
2.0f * 3.14159f * static_cast<float>(i) / static_cast<float>(segments);
float angle2 = 2.0f * 3.14159f * static_cast<float>(i + 1) /
static_cast<float>(segments);
// 每个三角形:中心 -> 边缘点1 -> 边缘点2
addShapeVertex(center.x, center.y, color);
addShapeVertex(center.x + radius * cosf(angle1),
center.y + radius * sinf(angle1), color);
addShapeVertex(center.x + radius * cosf(angle2),
center.y + radius * sinf(angle2), color);
}
}
/**
* @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资源VAOVBO
*/
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

View 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 truefalse
*/
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 truefalse
*/
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 truefalse
*/
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 ID0
*/
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 truefalse
*/
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

View 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 VAOVBOIBO和编译着色器
* @return truefalse
*/
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

View 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 truefalse
*/
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 truefalse
*/
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

View 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 truefalse
*/
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 truefalse
*/
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

View 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

File diff suppressed because one or more lines are too long

232
Fostbite2D/src/main.cpp Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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