加入 SpriteRegion 与基础 Tilemap 绘制
DrawContext 现在支持从 atlas 子区域绘制 SpriteRegion,并在 draw_sprite_ex 中加入屏幕裁剪,避免边缘 sprite 逐像素依赖 FrameBuffer 越界保护。新增基础 Tilemap 数据结构和 draw_tilemap,按视口可见范围遍历 tile,并在视口边缘进行像素级裁剪,支持 camera 像素级滚动时显示半个 tile。 Demo 增加小型滚动 tilemap 测试,复用现有测试 sprite 作为单 tile atlas。文档同步记录 SpriteRegion、Tilemap、viewport 裁剪语义和后续优化方向。 Constraint: IMX6U 热路径应避免全地图扫描和运行时资源解码 Constraint: 当前 tilemap 先保持单 atlas、固定 tile 尺寸的简单模型 Rejected: 直接每帧遍历整张 tilemap | 大地图上会浪费 CPU Rejected: 立即引入复杂地图资源系统 | 当前阶段只需要验证绘制链路 Confidence: high Scope-risk: moderate Directive: 扩展 Tilemap 时保持 tile 范围遍历 + 像素级边缘裁剪的语义 Tested: cmake --build build-win --config Release Not-tested: 尚未在 IMX6U 真机上验证 framebuffer/SDL 后端性能
This commit is contained in:
parent
213fa7e961
commit
20d2422650
10
README.md
10
README.md
|
|
@ -35,7 +35,7 @@ IMX6U 运行时性能预算较紧,后续开发必须遵守 `docs/DEVELOPMENT_G
|
||||||
|
|
||||||
项目后续按“两个游戏 + 一个启动器 + 一套底层图形库”组织,详细边界见 `docs/APP_AND_GFX_ARCHITECTURE.md`:
|
项目后续按“两个游戏 + 一个启动器 + 一套底层图形库”组织,详细边界见 `docs/APP_AND_GFX_ARCHITECTURE.md`:
|
||||||
|
|
||||||
- `Gfx` / 底层图形库:只提供 framebuffer、SDL2/fb0 平台适配、输入/时间源、基础绘制能力,例如点、线、矩形、四边形、sprite、tile。
|
- `Gfx` / 底层图形库:只提供 framebuffer、SDL2/fb0 平台适配、输入/时间源、基础绘制能力,例如点、线、矩形、四边形、sprite、bitmap font、tilemap。
|
||||||
- `Apps/Launcher`:负责游戏选择、全局设置和启动流程。
|
- `Apps/Launcher`:负责游戏选择、全局设置和启动流程。
|
||||||
- `Apps/GameA`、`Apps/GameB`:分别维护自己的游戏规则、状态、资源和渲染调用。
|
- `Apps/GameA`、`Apps/GameB`:分别维护自己的游戏规则、状态、资源和渲染调用。
|
||||||
- `Shared`:只放应用层共享但不属于底层图形库的 UI、存档、配置、资源索引等。
|
- `Shared`:只放应用层共享但不属于底层图形库的 UI、存档、配置、资源索引等。
|
||||||
|
|
@ -244,7 +244,9 @@ IMX6U-Game/
|
||||||
## 模块说明
|
## 模块说明
|
||||||
|
|
||||||
### Draw2D
|
### Draw2D
|
||||||
- **DrawContext**:统一绘制入口,封装 FrameBuffer、DepthBuffer、Rasterizer、TriangleRasterizer,对外提供 `clear`、`draw_line`、`draw_triangle`、`draw_sprite`、`draw_text`、`present` 接口
|
- **DrawContext**:统一绘制入口,封装 FrameBuffer、DepthBuffer、Rasterizer、TriangleRasterizer,对外提供 `clear`、`draw_line`、`draw_triangle`、`draw_sprite`、`draw_sprite_region`、`draw_text`、`draw_tilemap`、`present` 接口
|
||||||
|
- **SpriteRegion**:描述 atlas 中的子区域,`draw_sprite_region` / `draw_sprite_region_ex` 可直接绘制子图,底层复用 `draw_sprite_ex`
|
||||||
|
- **Tilemap**:使用 `uint16_t` tile id 引用 atlas 中的固定大小 tile,`draw_tilemap` 按视口可见范围遍历 tile,并在视口边缘做像素级裁剪
|
||||||
|
|
||||||
### Core
|
### Core
|
||||||
- **FrameBuffer**:CPU 侧颜色缓冲,渲染结果先写在这里
|
- **FrameBuffer**:CPU 侧颜色缓冲,渲染结果先写在这里
|
||||||
|
|
@ -269,7 +271,7 @@ IMX6U-Game/
|
||||||
- 可旋转立方体的 3D 渲染(MVP 变换、背面剔除、扫描线填充、深度测试)
|
- 可旋转立方体的 3D 渲染(MVP 变换、背面剔除、扫描线填充、深度测试)
|
||||||
- 双平台显示后端(SDL2 / Framebuffer)
|
- 双平台显示后端(SDL2 / Framebuffer)
|
||||||
- 离线资源转换工具:PNG sprite -> C++ 头文件,像素字体 -> bitmap atlas/header
|
- 离线资源转换工具:PNG sprite -> C++ 头文件,像素字体 -> bitmap atlas/header
|
||||||
- 基础 2D sprite 与 bitmap font 文本绘制,当前 demo 显示 FPS 文本和测试 sprite
|
- 基础 2D sprite、SpriteRegion、bitmap font 文本绘制和 tilemap 视口绘制,当前 demo 显示 FPS 文本、测试 sprite 和小型滚动 tilemap
|
||||||
- Gfx 目录规范化,代码收敛到 `src/Gfx/`
|
- Gfx 目录规范化,代码收敛到 `src/Gfx/`
|
||||||
- `Gfx::DrawContext` 统一绘制入口,封装现有绘制能力
|
- `Gfx::DrawContext` 统一绘制入口,封装现有绘制能力
|
||||||
- C++11 兼容代码
|
- C++11 兼容代码
|
||||||
|
|
@ -279,7 +281,7 @@ IMX6U-Game/
|
||||||
1. FrameBuffer 性能优化(`memset` 清屏、去掉 `at()`、定点数/NEON)
|
1. FrameBuffer 性能优化(`memset` 清屏、去掉 `at()`、定点数/NEON)
|
||||||
2. 应用层拆分(Launcher / GameA / GameB / Shared)和统一 `IApp` 主循环
|
2. 应用层拆分(Launcher / GameA / GameB / Shared)和统一 `IApp` 主循环
|
||||||
3. SDL2 输入抽象(键盘/触摸/按键状态快照)
|
3. SDL2 输入抽象(键盘/触摸/按键状态快照)
|
||||||
4. Gfx 基础 2D 绘制接口(矩形、四边形、Tilemap,继续完善 Sprite/Text 的裁剪和批处理)
|
4. Gfx 基础 2D 绘制接口(矩形、四边形、继续完善 Sprite/Text/Tilemap 的批处理和专用快路径)
|
||||||
5. 纹理贴图、OBJ 模型加载与完整光照
|
5. 纹理贴图、OBJ 模型加载与完整光照
|
||||||
|
|
||||||
## 说明
|
## 说明
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,7 @@ IMX6U-Game/
|
||||||
职责:
|
职责:
|
||||||
|
|
||||||
- 管理 framebuffer、depthbuffer、渲染上下文。
|
- 管理 framebuffer、depthbuffer、渲染上下文。
|
||||||
- 提供基础绘制接口:点、线、矩形、四边形、三角形、sprite、tile、简单文本等。
|
- 提供基础绘制接口:点、线、矩形、四边形、三角形、sprite、SpriteRegion、tilemap、简单文本等。
|
||||||
- 提供颜色、矩形、定点数、纹理、裁剪区域等基础数据结构。
|
- 提供颜色、矩形、定点数、纹理、裁剪区域等基础数据结构。
|
||||||
- 封装 SDL2 / framebuffer 显示提交、输入轮询、时间源。
|
- 封装 SDL2 / framebuffer 显示提交、输入轮询、时间源。
|
||||||
- 使用离线转换后的紧凑资源数据(例如 PNG 转头文件、bitmap font atlas),运行时不直接依赖 PNG/TTF 解码。
|
- 使用离线转换后的紧凑资源数据(例如 PNG 转头文件、bitmap font atlas),运行时不直接依赖 PNG/TTF 解码。
|
||||||
|
|
@ -186,16 +186,20 @@ namespace Gfx
|
||||||
void draw_quad(PointI p0, PointI p1, PointI p2, PointI p3, Color32 color);
|
void draw_quad(PointI p0, PointI p1, PointI p2, PointI p3, Color32 color);
|
||||||
void fill_quad(PointI p0, PointI p1, PointI p2, PointI p3, Color32 color);
|
void fill_quad(PointI p0, PointI p1, PointI p2, PointI p3, Color32 color);
|
||||||
void draw_sprite(int32_t x, int32_t y, const RenderData::Image& image);
|
void draw_sprite(int32_t x, int32_t y, const RenderData::Image& image);
|
||||||
|
void draw_sprite_region(int32_t x, int32_t y,
|
||||||
|
const RenderData::SpriteRegion& region);
|
||||||
void draw_text(const RenderData::BitmapFont& font, int32_t x, int32_t y,
|
void draw_text(const RenderData::BitmapFont& font, int32_t x, int32_t y,
|
||||||
RenderData::Color color, const char* text);
|
RenderData::Color color, const char* text);
|
||||||
|
void draw_tilemap(const RenderData::Tilemap& tilemap,
|
||||||
|
int32_t screen_x, int32_t screen_y,
|
||||||
|
int32_t viewport_w, int32_t viewport_h,
|
||||||
|
int32_t camera_x, int32_t camera_y);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
后续再扩展:
|
后续再扩展:
|
||||||
|
|
||||||
- `draw_sprite_region`
|
|
||||||
- `draw_tilemap`
|
|
||||||
- `set_clip_rect`
|
- `set_clip_rect`
|
||||||
- `set_camera_2d`
|
- `set_camera_2d`
|
||||||
- `batch sprite/tile`
|
- `batch sprite/tile`
|
||||||
|
|
@ -218,7 +222,7 @@ namespace Gfx
|
||||||
|
|
||||||
1. ~~先抽出统一 `IApp` 和 `AppManager`,让当前 demo 成为一个 app。~~
|
1. ~~先抽出统一 `IApp` 和 `AppManager`,让当前 demo 成为一个 app。~~
|
||||||
2. ~~把 SDL2 初始化、输入、present 固定在平台层,应用层不直接碰 SDL。~~
|
2. ~~把 SDL2 初始化、输入、present 固定在平台层,应用层不直接碰 SDL。~~
|
||||||
3. ~~建立 `Gfx::DrawContext`,先封装 clear、pixel、line、rect、quad。~~ **已完成**(`Gfx::DrawContext` 封装了 clear、draw_line、draw_triangle、draw_sprite、draw_text、present)
|
3. ~~建立 `Gfx::DrawContext`,先封装 clear、pixel、line、rect、quad。~~ **已完成**(`Gfx::DrawContext` 封装了 clear、draw_line、draw_triangle、draw_sprite、draw_sprite_region、draw_text、draw_tilemap、present)
|
||||||
4. ~~底层代码迁移到 `src/Gfx/`,Demo 入口迁移到 `src/Apps/Demo/`。~~ **已完成**
|
4. ~~底层代码迁移到 `src/Gfx/`,Demo 入口迁移到 `src/Apps/Demo/`。~~ **已完成**
|
||||||
5. 新增 Launcher app,只做最小菜单和应用切换。
|
5. 新增 Launcher app,只做最小菜单和应用切换。
|
||||||
6. 新增 GameA/GameB 空壳,验证三应用切换。
|
6. 新增 GameA/GameB 空壳,验证三应用切换。
|
||||||
|
|
@ -241,3 +245,15 @@ namespace Gfx
|
||||||
- `tools/gen_font_atlas.py` 将共享像素字体转为 ASCII bitmap atlas,并输出同名 PNG 预览和 C++ 头文件。
|
- `tools/gen_font_atlas.py` 将共享像素字体转为 ASCII bitmap atlas,并输出同名 PNG 预览和 C++ 头文件。
|
||||||
- 生成头文件、源 PNG/TTF 和转换脚本应一起纳入仓库,保证资源可追溯、可再生成。
|
- 生成头文件、源 PNG/TTF 和转换脚本应一起纳入仓库,保证资源可追溯、可再生成。
|
||||||
- 生成数据目前面向简单直接的调试/小型游戏资源;后续如果资源体积增长,应再评估 1-bit/8-bit mask、RLE 或自定义资源包格式。
|
- 生成数据目前面向简单直接的调试/小型游戏资源;后续如果资源体积增长,应再评估 1-bit/8-bit mask、RLE 或自定义资源包格式。
|
||||||
|
|
||||||
|
## 10. SpriteRegion 与 Tilemap 约定
|
||||||
|
|
||||||
|
- `RenderData::SpriteRegion` 只描述某张 atlas 中的子区域,不拥有像素数据;它通过 `const Image* atlas` 引用源图。
|
||||||
|
- `DrawContext::draw_sprite_ex` 是底层 sprite 绘制入口,负责源区域检查、目标屏幕裁剪、scale 和 flip;`draw_sprite_region` 系列只是对 atlas 子区域的语义包装。
|
||||||
|
- `RenderData::Tilemap` 使用 `uint16_t` tile id 保存地图网格,`Tilemap::EmptyTile` (`0xFFFF`) 表示空 tile。
|
||||||
|
- `Tilemap` 当前只支持一个 atlas、固定 tile 宽高和固定 `atlas_columns`;tile id 通过 `tile_id % atlas_columns` / `tile_id / atlas_columns` 映射到 atlas 中的源区域。
|
||||||
|
- `DrawContext::draw_tilemap` 的裁剪分两层:
|
||||||
|
- tilemap 层按 tile 计算需要尝试绘制的可见范围;
|
||||||
|
- sprite 层按像素裁剪每个 tile,支持 camera 像素级滚动时显示半个 tile。
|
||||||
|
- 带 `viewport_w` / `viewport_h` 的 `draw_tilemap` 重载用于子视口绘制,`screen_x` / `screen_y` 是视口左上角;旧重载默认视口延伸到 framebuffer 右下角。
|
||||||
|
- 当前实现优先保证清晰语义和可验证行为;后续性能优化可增加 tile region 查表、不透明 tile 行拷贝、chunk/dirty rect 或专用 tile 快路径。
|
||||||
|
|
|
||||||
|
|
@ -121,7 +121,8 @@
|
||||||
- `tools/gen_font_atlas.py`:像素字体 TTF -> ASCII bitmap font atlas/header。
|
- `tools/gen_font_atlas.py`:像素字体 TTF -> ASCII bitmap font atlas/header。
|
||||||
- 生成像素格式统一为 `(R << 24) | (G << 16) | (B << 8) | A`,与 `RenderData::Color::to_rgba()` 和 `Core::FrameBuffer` 当前格式保持一致。
|
- 生成像素格式统一为 `(R << 24) | (G << 16) | (B << 8) | A`,与 `RenderData::Color::to_rgba()` 和 `Core::FrameBuffer` 当前格式保持一致。
|
||||||
- 源资源、生成脚本和生成头文件应同时提交,保证资源可追溯、可复现。
|
- 源资源、生成脚本和生成头文件应同时提交,保证资源可追溯、可复现。
|
||||||
- 运行时绘制 sprite/font 时只做裁剪、透明判断、颜色替换或必要的像素拷贝;不要在绘制函数内做文件 IO、图片解码、字体栅格化或动态分配。
|
- 运行时绘制 sprite/font/tilemap 时只做裁剪、透明判断、颜色替换、tile id 查表或必要的像素拷贝;不要在绘制函数内做文件 IO、图片解码、字体栅格化或动态分配。
|
||||||
|
- Tilemap 绘制应按视口可见范围遍历 tile,不能每帧无条件扫描整张地图;视口边缘允许通过 sprite 像素裁剪显示半个 tile。
|
||||||
- 字体资源当前走像素风路径,生成端会把 alpha 阈值化为 0/255;如果未来要恢复抗锯齿字体,必须同步设计 framebuffer alpha blending,而不能只在绘制端把所有非 0 alpha 当作实心像素。
|
- 字体资源当前走像素风路径,生成端会把 alpha 阈值化为 0/255;如果未来要恢复抗锯齿字体,必须同步设计 framebuffer alpha blending,而不能只在绘制端把所有非 0 alpha 当作实心像素。
|
||||||
|
|
||||||
## 9. 新代码提交前检查清单
|
## 9. 新代码提交前检查清单
|
||||||
|
|
|
||||||
|
|
@ -126,6 +126,16 @@ int main(int argc, char *argv[])
|
||||||
font.columns = font_columns;
|
font.columns = font_columns;
|
||||||
font.first_char = font_first_char;
|
font.first_char = font_first_char;
|
||||||
|
|
||||||
|
RenderData::Image sprite_img(test_sprite_pixels, test_sprite_width, test_sprite_height, 0x00000000);
|
||||||
|
RenderData::SpriteRegion sprite_region(&sprite_img, 0, 0, sprite_img.width, sprite_img.height);
|
||||||
|
|
||||||
|
const std::array<uint16_t, 8 * 4> tileIds = {
|
||||||
|
0, RenderData::Tilemap::EmptyTile, 0, RenderData::Tilemap::EmptyTile, 0, RenderData::Tilemap::EmptyTile, 0, RenderData::Tilemap::EmptyTile,
|
||||||
|
RenderData::Tilemap::EmptyTile, 0, RenderData::Tilemap::EmptyTile, 0, RenderData::Tilemap::EmptyTile, 0, RenderData::Tilemap::EmptyTile, 0,
|
||||||
|
0, RenderData::Tilemap::EmptyTile, 0, RenderData::Tilemap::EmptyTile, 0, RenderData::Tilemap::EmptyTile, 0, RenderData::Tilemap::EmptyTile,
|
||||||
|
RenderData::Tilemap::EmptyTile, 0, RenderData::Tilemap::EmptyTile, 0, RenderData::Tilemap::EmptyTile, 0, RenderData::Tilemap::EmptyTile, 0};
|
||||||
|
RenderData::Tilemap testTilemap(tileIds.data(), 8, 4, &sprite_img, sprite_img.width, sprite_img.height, 1);
|
||||||
|
|
||||||
Scene::Camera camera;
|
Scene::Camera camera;
|
||||||
camera.transform.position = Math::Vector3(0.0f, 0.0f, 3.0f);
|
camera.transform.position = Math::Vector3(0.0f, 0.0f, 3.0f);
|
||||||
camera.transform.rotation = Math::Vector3(0.0f, 3.1415926535f, 0.0f);
|
camera.transform.rotation = Math::Vector3(0.0f, 3.1415926535f, 0.0f);
|
||||||
|
|
@ -253,11 +263,10 @@ int main(int argc, char *argv[])
|
||||||
}
|
}
|
||||||
|
|
||||||
// sprite 测试
|
// sprite 测试
|
||||||
RenderData::Image sprite_img(test_sprite_pixels, test_sprite_width, test_sprite_height, 0x00000000);
|
|
||||||
|
|
||||||
ctx.draw_sprite(10, 10, sprite_img);
|
ctx.draw_sprite(10, 10, sprite_img);
|
||||||
ctx.draw_sprite_ex(30, 10, sprite_img, 0, 0, sprite_img.width, sprite_img.height, 2, false, false);
|
ctx.draw_sprite_region_ex(30, 10, sprite_region, 2, false, false);
|
||||||
ctx.draw_sprite_ex(10, 30, sprite_img, 0, 0, sprite_img.width, sprite_img.height, 3, true, false);
|
ctx.draw_sprite_region_ex(10, 30, sprite_region, 3, true, false);
|
||||||
|
ctx.draw_tilemap(testTilemap, 650, 500, 96, 48, static_cast<int32_t>(display->get_time_ms() / 20) % 32, 0);
|
||||||
|
|
||||||
// FPS 计数
|
// FPS 计数
|
||||||
++frame_count;
|
++frame_count;
|
||||||
|
|
|
||||||
|
|
@ -60,52 +60,197 @@ namespace Gfx
|
||||||
draw_sprite_region(dst_x, dst_y, img, 0, 0, img.width, img.height);
|
draw_sprite_region(dst_x, dst_y, img, 0, 0, img.width, img.height);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DrawContext::draw_sprite(int32_t dst_x, int32_t dst_y, const RenderData::SpriteRegion& region)
|
||||||
|
{
|
||||||
|
draw_sprite_region(dst_x, dst_y, region);
|
||||||
|
}
|
||||||
|
|
||||||
void DrawContext::draw_sprite_region(int32_t dst_x, int32_t dst_y, const RenderData::Image& img,
|
void DrawContext::draw_sprite_region(int32_t dst_x, int32_t dst_y, const RenderData::Image& img,
|
||||||
int32_t src_x, int32_t src_y, int32_t src_w, int32_t src_h)
|
int32_t src_x, int32_t src_y, int32_t src_w, int32_t src_h)
|
||||||
{
|
{
|
||||||
draw_sprite_ex(dst_x, dst_y, img, src_x, src_y, src_w, src_h, 1, false, false);
|
draw_sprite_ex(dst_x, dst_y, img, src_x, src_y, src_w, src_h, 1, false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DrawContext::draw_sprite_region(int32_t dst_x, int32_t dst_y, const RenderData::SpriteRegion& region)
|
||||||
|
{
|
||||||
|
draw_sprite_region_ex(dst_x, dst_y, region, 1, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DrawContext::draw_sprite_region_ex(int32_t dst_x, int32_t dst_y, const RenderData::SpriteRegion& region,
|
||||||
|
int32_t scale, bool flip_h, bool flip_v)
|
||||||
|
{
|
||||||
|
if (!region.atlas) return;
|
||||||
|
|
||||||
|
draw_sprite_ex(
|
||||||
|
dst_x,
|
||||||
|
dst_y,
|
||||||
|
*region.atlas,
|
||||||
|
region.x,
|
||||||
|
region.y,
|
||||||
|
region.width,
|
||||||
|
region.height,
|
||||||
|
scale,
|
||||||
|
flip_h,
|
||||||
|
flip_v);
|
||||||
|
}
|
||||||
|
|
||||||
void DrawContext::draw_sprite_ex(int32_t dst_x, int32_t dst_y, const RenderData::Image& img,
|
void DrawContext::draw_sprite_ex(int32_t dst_x, int32_t dst_y, const RenderData::Image& img,
|
||||||
int32_t src_x, int32_t src_y, int32_t src_w, int32_t src_h,
|
int32_t src_x, int32_t src_y, int32_t src_w, int32_t src_h,
|
||||||
int32_t scale, bool flip_h, bool flip_v)
|
int32_t scale, bool flip_h, bool flip_v)
|
||||||
{
|
{
|
||||||
if (scale < 1 || !img.pixels) return;
|
if (scale < 1 || !img.pixels || src_w <= 0 || src_h <= 0) return;
|
||||||
|
if (src_x < 0 || src_y < 0 || src_x + src_w > img.width || src_y + src_h > img.height) return;
|
||||||
|
|
||||||
const int32_t img_w = img.width;
|
const int32_t img_w = img.width;
|
||||||
|
const int32_t screen_w = frameBuffer->get_width();
|
||||||
|
const int32_t screen_h = frameBuffer->get_height();
|
||||||
|
const int32_t draw_w = src_w * scale;
|
||||||
|
const int32_t draw_h = src_h * scale;
|
||||||
|
|
||||||
|
if (dst_x >= screen_w || dst_y >= screen_h || dst_x + draw_w <= 0 || dst_y + draw_h <= 0) return;
|
||||||
|
|
||||||
|
int32_t start_dx = 0;
|
||||||
|
int32_t start_dy = 0;
|
||||||
|
int32_t end_dx = draw_w;
|
||||||
|
int32_t end_dy = draw_h;
|
||||||
|
|
||||||
|
if (dst_x < 0) start_dx = -dst_x;
|
||||||
|
if (dst_y < 0) start_dy = -dst_y;
|
||||||
|
if (dst_x + end_dx > screen_w) end_dx = screen_w - dst_x;
|
||||||
|
if (dst_y + end_dy > screen_h) end_dy = screen_h - dst_y;
|
||||||
|
|
||||||
const uint32_t* src = img.pixels;
|
const uint32_t* src = img.pixels;
|
||||||
const bool has_key = img.has_color_key;
|
const bool has_key = img.has_color_key;
|
||||||
const uint32_t key = img.color_key;
|
const uint32_t key = img.color_key;
|
||||||
|
|
||||||
for (int32_t sy = 0; sy < src_h; ++sy)
|
if (scale == 1)
|
||||||
{
|
{
|
||||||
|
for (int32_t sy = start_dy; sy < end_dy; ++sy)
|
||||||
|
{
|
||||||
|
const int32_t read_y = flip_v ? (src_y + src_h - 1 - sy) : (src_y + sy);
|
||||||
|
const uint32_t* row = src + read_y * img_w;
|
||||||
|
const int32_t dst_y_abs = dst_y + sy;
|
||||||
|
|
||||||
|
for (int32_t sx = start_dx; sx < end_dx; ++sx)
|
||||||
|
{
|
||||||
|
const int32_t read_x = flip_h ? (src_x + src_w - 1 - sx) : (src_x + sx);
|
||||||
|
const uint32_t pixel = row[read_x];
|
||||||
|
|
||||||
|
if (has_key && pixel == key) continue;
|
||||||
|
|
||||||
|
frameBuffer->set_pixel(dst_x + sx, dst_y_abs, pixel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int32_t dy_abs = start_dy; dy_abs < end_dy; ++dy_abs)
|
||||||
|
{
|
||||||
|
const int32_t sy = dy_abs / scale;
|
||||||
const int32_t read_y = flip_v ? (src_y + src_h - 1 - sy) : (src_y + sy);
|
const int32_t read_y = flip_v ? (src_y + src_h - 1 - sy) : (src_y + sy);
|
||||||
const uint32_t* row = src + read_y * img_w;
|
const uint32_t* row = src + read_y * img_w;
|
||||||
|
const int32_t dst_y_abs = dst_y + dy_abs;
|
||||||
|
|
||||||
for (int32_t sx = 0; sx < src_w; ++sx)
|
for (int32_t dx_abs = start_dx; dx_abs < end_dx; ++dx_abs)
|
||||||
{
|
{
|
||||||
|
const int32_t sx = dx_abs / scale;
|
||||||
const int32_t read_x = flip_h ? (src_x + src_w - 1 - sx) : (src_x + sx);
|
const int32_t read_x = flip_h ? (src_x + src_w - 1 - sx) : (src_x + sx);
|
||||||
const uint32_t pixel = row[read_x];
|
const uint32_t pixel = row[read_x];
|
||||||
|
|
||||||
if (has_key && pixel == key) continue;
|
if (has_key && pixel == key) continue;
|
||||||
|
|
||||||
const int32_t base_x = dst_x + sx * scale;
|
frameBuffer->set_pixel(dst_x + dx_abs, dst_y_abs, pixel);
|
||||||
const int32_t base_y = dst_y + sy * scale;
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (scale == 1)
|
void DrawContext::draw_tilemap(const RenderData::Tilemap& tilemap,
|
||||||
{
|
int32_t screen_x, int32_t screen_y,
|
||||||
frameBuffer->set_pixel(base_x, base_y, pixel);
|
int32_t camera_x, int32_t camera_y)
|
||||||
}
|
{
|
||||||
else
|
draw_tilemap(tilemap,
|
||||||
{
|
screen_x,
|
||||||
for (int32_t dy = 0; dy < scale; ++dy)
|
screen_y,
|
||||||
{
|
frameBuffer->get_width() - screen_x,
|
||||||
for (int32_t dx = 0; dx < scale; ++dx)
|
frameBuffer->get_height() - screen_y,
|
||||||
{
|
camera_x,
|
||||||
frameBuffer->set_pixel(base_x + dx, base_y + dy, pixel);
|
camera_y);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
void DrawContext::draw_tilemap(const RenderData::Tilemap& tilemap,
|
||||||
|
int32_t screen_x, int32_t screen_y,
|
||||||
|
int32_t viewport_w, int32_t viewport_h,
|
||||||
|
int32_t camera_x, int32_t camera_y)
|
||||||
|
{
|
||||||
|
if (!tilemap.tiles || !tilemap.atlas || !tilemap.atlas->pixels) return;
|
||||||
|
if (tilemap.width <= 0 || tilemap.height <= 0) return;
|
||||||
|
if (tilemap.tile_w <= 0 || tilemap.tile_h <= 0 || tilemap.atlas_columns <= 0) return;
|
||||||
|
if (viewport_w <= 0 || viewport_h <= 0) return;
|
||||||
|
|
||||||
|
const int32_t viewport_left = screen_x;
|
||||||
|
const int32_t viewport_top = screen_y;
|
||||||
|
const int32_t viewport_right = screen_x + viewport_w;
|
||||||
|
const int32_t viewport_bottom = screen_y + viewport_h;
|
||||||
|
|
||||||
|
int32_t start_tile_x = camera_x / tilemap.tile_w;
|
||||||
|
int32_t start_tile_y = camera_y / tilemap.tile_h;
|
||||||
|
int32_t offset_x = -(camera_x % tilemap.tile_w);
|
||||||
|
int32_t offset_y = -(camera_y % tilemap.tile_h);
|
||||||
|
|
||||||
|
if (camera_x < 0 && camera_x % tilemap.tile_w != 0)
|
||||||
|
{
|
||||||
|
--start_tile_x;
|
||||||
|
offset_x = -camera_x - (-start_tile_x * tilemap.tile_w);
|
||||||
|
}
|
||||||
|
if (camera_y < 0 && camera_y % tilemap.tile_h != 0)
|
||||||
|
{
|
||||||
|
--start_tile_y;
|
||||||
|
offset_y = -camera_y - (-start_tile_y * tilemap.tile_h);
|
||||||
|
}
|
||||||
|
|
||||||
|
const int32_t visible_cols = viewport_w / tilemap.tile_w + 2;
|
||||||
|
const int32_t visible_rows = viewport_h / tilemap.tile_h + 2;
|
||||||
|
|
||||||
|
for (int32_t row = 0; row < visible_rows; ++row)
|
||||||
|
{
|
||||||
|
const int32_t map_y = start_tile_y + row;
|
||||||
|
if (map_y < 0 || map_y >= tilemap.height) continue;
|
||||||
|
|
||||||
|
const int32_t dst_y = screen_y + offset_y + row * tilemap.tile_h;
|
||||||
|
for (int32_t col = 0; col < visible_cols; ++col)
|
||||||
|
{
|
||||||
|
const int32_t map_x = start_tile_x + col;
|
||||||
|
if (map_x < 0 || map_x >= tilemap.width) continue;
|
||||||
|
|
||||||
|
const uint16_t tile_id = tilemap.get_tile(map_x, map_y);
|
||||||
|
if (tile_id == RenderData::Tilemap::EmptyTile) continue;
|
||||||
|
|
||||||
|
const int32_t src_x = (tile_id % tilemap.atlas_columns) * tilemap.tile_w;
|
||||||
|
const int32_t src_y = (tile_id / tilemap.atlas_columns) * tilemap.tile_h;
|
||||||
|
const int32_t dst_x = screen_x + offset_x + col * tilemap.tile_w;
|
||||||
|
const int32_t tile_right = dst_x + tilemap.tile_w;
|
||||||
|
const int32_t tile_bottom = dst_y + tilemap.tile_h;
|
||||||
|
|
||||||
|
int32_t clipped_left = dst_x;
|
||||||
|
int32_t clipped_top = dst_y;
|
||||||
|
int32_t clipped_right = tile_right;
|
||||||
|
int32_t clipped_bottom = tile_bottom;
|
||||||
|
|
||||||
|
if (clipped_left < viewport_left) clipped_left = viewport_left;
|
||||||
|
if (clipped_top < viewport_top) clipped_top = viewport_top;
|
||||||
|
if (clipped_right > viewport_right) clipped_right = viewport_right;
|
||||||
|
if (clipped_bottom > viewport_bottom) clipped_bottom = viewport_bottom;
|
||||||
|
|
||||||
|
if (clipped_left >= clipped_right || clipped_top >= clipped_bottom) continue;
|
||||||
|
|
||||||
|
const int32_t clipped_src_x = src_x + (clipped_left - dst_x);
|
||||||
|
const int32_t clipped_src_y = src_y + (clipped_top - dst_y);
|
||||||
|
const int32_t clipped_w = clipped_right - clipped_left;
|
||||||
|
const int32_t clipped_h = clipped_bottom - clipped_top;
|
||||||
|
|
||||||
|
draw_sprite_ex(clipped_left, clipped_top, *tilemap.atlas,
|
||||||
|
clipped_src_x, clipped_src_y, clipped_w, clipped_h,
|
||||||
|
1, false, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@
|
||||||
#include "Vector2.h"
|
#include "Vector2.h"
|
||||||
#include "Triangle.h"
|
#include "Triangle.h"
|
||||||
#include "Image.h"
|
#include "Image.h"
|
||||||
|
#include "SpriteRegion.h"
|
||||||
|
#include "Tilemap.h"
|
||||||
#include "BitmapFont.h"
|
#include "BitmapFont.h"
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|
||||||
|
|
@ -50,11 +52,22 @@ namespace Gfx
|
||||||
void draw_triangle(const RenderData::Triangle& triangle, const RenderData::Color& color);
|
void draw_triangle(const RenderData::Triangle& triangle, const RenderData::Color& color);
|
||||||
|
|
||||||
void draw_sprite(int32_t dst_x, int32_t dst_y, const RenderData::Image& img);
|
void draw_sprite(int32_t dst_x, int32_t dst_y, const RenderData::Image& img);
|
||||||
|
void draw_sprite(int32_t dst_x, int32_t dst_y, const RenderData::SpriteRegion& region);
|
||||||
void draw_sprite_region(int32_t dst_x, int32_t dst_y, const RenderData::Image& img,
|
void draw_sprite_region(int32_t dst_x, int32_t dst_y, const RenderData::Image& img,
|
||||||
int32_t src_x, int32_t src_y, int32_t src_w, int32_t src_h);
|
int32_t src_x, int32_t src_y, int32_t src_w, int32_t src_h);
|
||||||
|
void draw_sprite_region(int32_t dst_x, int32_t dst_y, const RenderData::SpriteRegion& region);
|
||||||
|
void draw_sprite_region_ex(int32_t dst_x, int32_t dst_y, const RenderData::SpriteRegion& region,
|
||||||
|
int32_t scale, bool flip_h, bool flip_v);
|
||||||
void draw_sprite_ex(int32_t dst_x, int32_t dst_y, const RenderData::Image& img,
|
void draw_sprite_ex(int32_t dst_x, int32_t dst_y, const RenderData::Image& img,
|
||||||
int32_t src_x, int32_t src_y, int32_t src_w, int32_t src_h,
|
int32_t src_x, int32_t src_y, int32_t src_w, int32_t src_h,
|
||||||
int32_t scale, bool flip_h, bool flip_v);
|
int32_t scale, bool flip_h, bool flip_v);
|
||||||
|
void draw_tilemap(const RenderData::Tilemap& tilemap,
|
||||||
|
int32_t screen_x, int32_t screen_y,
|
||||||
|
int32_t camera_x, int32_t camera_y);
|
||||||
|
void draw_tilemap(const RenderData::Tilemap& tilemap,
|
||||||
|
int32_t screen_x, int32_t screen_y,
|
||||||
|
int32_t viewport_w, int32_t viewport_h,
|
||||||
|
int32_t camera_x, int32_t camera_y);
|
||||||
|
|
||||||
void fill_rect(int32_t x, int32_t y, int32_t w, int32_t h, const RenderData::Color& color);
|
void fill_rect(int32_t x, int32_t y, int32_t w, int32_t h, const RenderData::Color& color);
|
||||||
void draw_text(const RenderData::BitmapFont& font, int32_t x, int32_t y,
|
void draw_text(const RenderData::BitmapFont& font, int32_t x, int32_t y,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
#pragma once
|
||||||
|
#include <cstdint>
|
||||||
|
#include "Image.h"
|
||||||
|
|
||||||
|
namespace RenderData
|
||||||
|
{
|
||||||
|
struct SpriteRegion
|
||||||
|
{
|
||||||
|
const Image* atlas;
|
||||||
|
int32_t x;
|
||||||
|
int32_t y;
|
||||||
|
int32_t width;
|
||||||
|
int32_t height;
|
||||||
|
|
||||||
|
SpriteRegion()
|
||||||
|
: atlas(nullptr), x(0), y(0), width(0), height(0) {}
|
||||||
|
|
||||||
|
SpriteRegion(const Image* atlas, int32_t x, int32_t y, int32_t width, int32_t height)
|
||||||
|
: atlas(atlas), x(x), y(y), width(width), height(height) {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
#pragma once
|
||||||
|
#include <cstdint>
|
||||||
|
#include "Image.h"
|
||||||
|
|
||||||
|
namespace RenderData
|
||||||
|
{
|
||||||
|
struct Tilemap
|
||||||
|
{
|
||||||
|
static const uint16_t EmptyTile = 0xFFFF;
|
||||||
|
|
||||||
|
const uint16_t* tiles;
|
||||||
|
int32_t width;
|
||||||
|
int32_t height;
|
||||||
|
const Image* atlas;
|
||||||
|
int32_t tile_w;
|
||||||
|
int32_t tile_h;
|
||||||
|
int32_t atlas_columns;
|
||||||
|
|
||||||
|
Tilemap()
|
||||||
|
: tiles(nullptr),
|
||||||
|
width(0),
|
||||||
|
height(0),
|
||||||
|
atlas(nullptr),
|
||||||
|
tile_w(0),
|
||||||
|
tile_h(0),
|
||||||
|
atlas_columns(0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Tilemap(const uint16_t* tiles, int32_t width, int32_t height,
|
||||||
|
const Image* atlas, int32_t tile_w, int32_t tile_h, int32_t atlas_columns)
|
||||||
|
: tiles(tiles),
|
||||||
|
width(width),
|
||||||
|
height(height),
|
||||||
|
atlas(atlas),
|
||||||
|
tile_w(tile_w),
|
||||||
|
tile_h(tile_h),
|
||||||
|
atlas_columns(atlas_columns)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t get_tile(int32_t x, int32_t y) const
|
||||||
|
{
|
||||||
|
return tiles[y * width + x];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue