统一 Image 和 Sprite 语义到目前的 Image/Sprite 结构体而非之前的类定义

This commit is contained in:
SepComet 2026-06-09 10:28:29 +08:00
parent 56eec9e9d2
commit feb088a854
16 changed files with 166786 additions and 167030 deletions

View File

@ -212,7 +212,7 @@ cmake --build build-win --config Release --target GenerateTomAtlasHeader
src/Apps/Game/generated/tom_atlas.h src/Apps/Game/generated/tom_atlas.h
``` ```
该头文件包含 `tom_atlas_pixels` 和每张图的 `RenderData::SpriteRegion`,因此板端运行 Tom 游戏时不需要额外部署图片资源文件。尺寸规则、region 名称和 PNG 文件名统一记录在 `src/Apps/Game/tools/asset_pipeline/SpriteAssetTool.cpp` 顶部的 `Sources` 表里CMake 不再重复维护每张 PNG 的路径。 该头文件包含 `tom_atlas_pixels` 和每张图的 `RenderData::Sprite`,因此板端运行 Tom 游戏时不需要额外部署图片资源文件。尺寸规则、region 名称和 PNG 文件名统一记录在 `src/Apps/Game/tools/asset_pipeline/SpriteAssetTool.cpp` 顶部的 `Sources` 表里CMake 不再重复维护每张 PNG 的路径。
`src/Apps/Game/assets/sprites/` 属于旧的 `.sprite` 文件部署流程Tom 主游戏现在不再依赖它。只有旧的手动测试或未迁移工具还可能引用该目录;清理这些旧入口后可以删除这个目录。 `src/Apps/Game/assets/sprites/` 属于旧的 `.sprite` 文件部署流程Tom 主游戏现在不再依赖它。只有旧的手动测试或未迁移工具还可能引用该目录;清理这些旧入口后可以删除这个目录。
@ -307,8 +307,8 @@ IMX6U-Game/
## 模块说明 ## 模块说明
### Draw2D ### Draw2D
- **DrawContext**:统一绘制入口,封装 FrameBuffer、DepthBuffer、Rasterizer、TriangleRasterizer对外提供 `clear`、`draw_line`、`draw_triangle`、`draw_sprite`、`draw_sprite_region`、`draw_text`、`draw_tilemap`、`present` 接口 - **DrawContext**:统一绘制入口,封装 FrameBuffer、DepthBuffer、Rasterizer、TriangleRasterizer对外提供 `clear`、`draw_line`、`draw_triangle`、`draw_sprite`、`draw_text`、`draw_tilemap`、`present` 接口
- **SpriteRegion**:描述 atlas 中的子区域,`draw_sprite_region` / `draw_sprite_region_ex` 可直接绘制子图,底层复用 `draw_sprite_ex` - **Sprite**:描述 atlas 中的子区域,是对外 sprite 绘制单位;`draw_sprite` / `draw_sprite_ex` 读取其 atlas 子区域并写入 RGB565 framebuffer
- **Tilemap**:使用 `uint16_t` tile id 引用 atlas 中的固定大小 tile`draw_tilemap` 按视口可见范围遍历 tile并在视口边缘做像素级裁剪 - **Tilemap**:使用 `uint16_t` tile id 引用 atlas 中的固定大小 tile`draw_tilemap` 按视口可见范围遍历 tile并在视口边缘做像素级裁剪
### Core ### Core
@ -351,7 +351,7 @@ IMX6U-Game/
- 可旋转立方体的 3D 渲染MVP 变换、背面剔除、扫描线填充、深度测试) - 可旋转立方体的 3D 渲染MVP 变换、背面剔除、扫描线填充、深度测试)
- 双平台显示后端SDL2 / Framebuffer - 双平台显示后端SDL2 / Framebuffer
- 离线资源转换工具PNG sprite -> RGBA5551 C++ 头文件,像素字体 -> bitmap atlas/header - 离线资源转换工具PNG sprite -> RGBA5551 C++ 头文件,像素字体 -> bitmap atlas/header
- 基础 2D sprite、SpriteRegion、bitmap font 文本绘制和 tilemap 视口绘制,当前 demo 显示 FPS 文本、测试 sprite 和小型滚动 tilemap - 基础 2D sprite、atlas 子图 sprite、bitmap font 文本绘制和 tilemap 视口绘制,当前 demo 显示 FPS 文本、测试 sprite 和小型滚动 tilemap
- Core 目录规范化,代码收敛到 `src/Core/` - Core 目录规范化,代码收敛到 `src/Core/`
- `Core::DrawContext` 统一绘制入口,封装现有绘制能力 - `Core::DrawContext` 统一绘制入口,封装现有绘制能力
- C++11 兼容代码 - C++11 兼容代码

View File

@ -77,7 +77,7 @@ IMX6U-Game/
职责: 职责:
- 管理 framebuffer、depthbuffer、渲染上下文。 - 管理 framebuffer、depthbuffer、渲染上下文。
- 提供基础绘制接口点、线、矩形、四边形、三角形、sprite、SpriteRegion、tilemap、简单文本等。 - 提供基础绘制接口点、线、矩形、四边形、三角形、sprite、atlas 子图 sprite、tilemap、简单文本等。
- 提供颜色、矩形、定点数、纹理、裁剪区域等基础数据结构。 - 提供颜色、矩形、定点数、纹理、裁剪区域等基础数据结构。
- 封装 SDL2 / framebuffer 显示提交、输入轮询,并通过独立 `ITimeSource` 提供单调整数毫秒时间。 - 封装 SDL2 / framebuffer 显示提交、输入轮询,并通过独立 `ITimeSource` 提供单调整数毫秒时间。
- 提供音频输入、音频输出和按键输入的抽象接口。 - 提供音频输入、音频输出和按键输入的抽象接口。
@ -231,7 +231,7 @@ ALSA、evdev、SDL2、`/dev/fb0` 等平台细节只能出现在 `src/Core/Platfo
```cpp ```cpp
Core::DrawContext ctx(width, height); Core::DrawContext ctx(width, height);
ctx.clear(RenderData::Color(18, 18, 24, 255)); ctx.clear(RenderData::Color(18, 18, 24, 255));
ctx.draw_sprite(x, y, image); ctx.draw_sprite(x, y, sprite);
ctx.draw_text(font, x, y, color, "text"); ctx.draw_text(font, x, y, color, "text");
ctx.present(display); ctx.present(display);
``` ```
@ -281,7 +281,7 @@ poll input -> update current app -> render current app -> present framebuffer
1. ~~先抽出统一 `IApp` 和 `AppManager`,让当前 demo 成为一个 app。~~ 未实现,当前直接写主循环。 1. ~~先抽出统一 `IApp` 和 `AppManager`,让当前 demo 成为一个 app。~~ 未实现,当前直接写主循环。
2. ~~把 SDL2 初始化、输入、present 固定在平台层,应用层不直接碰 SDL。~~ **已完成** 2. ~~把 SDL2 初始化、输入、present 固定在平台层,应用层不直接碰 SDL。~~ **已完成**
3. ~~建立 `Core::DrawContext`,先封装 clear、pixel、line、rect、quad。~~ **已完成**`Core::DrawContext` 封装了 clear、draw_line、draw_triangle、draw_sprite、draw_sprite_region、draw_text、draw_tilemap、present 3. ~~建立 `Core::DrawContext`,先封装 clear、pixel、line、rect、quad。~~ **已完成**`Core::DrawContext` 封装了 clear、draw_line、draw_triangle、draw_sprite、draw_text、draw_tilemap、present
4. ~~底层代码统一放在 `src/Core/`Demo 入口迁移到 `src/Apps/Demo/`。~~ **已完成** 4. ~~底层代码统一放在 `src/Core/`Demo 入口迁移到 `src/Apps/Demo/`。~~ **已完成**
5. 新增 Launcher app只做最小菜单和应用切换。 5. 新增 Launcher app只做最小菜单和应用切换。
6. 新增 GameA/GameB 空壳,验证三应用切换。 6. 新增 GameA/GameB 空壳,验证三应用切换。
@ -309,10 +309,10 @@ poll input -> update current app -> render current app -> present framebuffer
- 生成头文件、源 PNG/TTF 和转换脚本应一起纳入仓库,保证资源可追溯、可再生成。 - 生成头文件、源 PNG/TTF 和转换脚本应一起纳入仓库,保证资源可追溯、可再生成。
- 生成数据目前面向简单直接的调试/小型游戏资源;后续如果资源体积增长,应继续评估 1-bit mask、RLE 或自定义资源包格式。 - 生成数据目前面向简单直接的调试/小型游戏资源;后续如果资源体积增长,应继续评估 1-bit mask、RLE 或自定义资源包格式。
## 12. SpriteRegion 与 Tilemap 约定 ## 12. Sprite 与 Tilemap 约定
- `RenderData::SpriteRegion` 只描述某张 atlas 中的子区域,不拥有像素数据;它通过 `const Image* atlas` 引用源图。 - `RenderData::Sprite` 只描述某张 atlas 中的子区域,不拥有像素数据;它通过 `const Image* atlas` 引用源图。
- `DrawContext::draw_sprite_ex` 是底层 sprite 绘制入口负责源区域检查、目标屏幕裁剪、scale 和 flip`draw_sprite_region` 系列只是对 atlas 子区域的语义包装 - `DrawContext::draw_sprite` 是对外 sprite 绘制入口;`draw_sprite_ex` 只在需要 scale / flip 时使用。两者都以 `RenderData::Sprite` 为渲染单位,内部再按 atlas 子区域读取像素
- `RenderData::Tilemap` 使用 `uint16_t` tile id 保存地图网格,`Tilemap::EmptyTile` (`0xFFFF`) 表示空 tile。 - `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 中的源区域。 - `Tilemap` 当前只支持一个 atlas、固定 tile 宽高和固定 `atlas_columns`tile id 通过 `tile_id % atlas_columns` / `tile_id / atlas_columns` 映射到 atlas 中的源区域。
- `DrawContext::draw_tilemap` 的裁剪分两层: - `DrawContext::draw_tilemap` 的裁剪分两层:

View File

@ -146,7 +146,7 @@ int main(int argc, char* argv[])
test_sprite_width, test_sprite_width,
test_sprite_height, test_sprite_height,
RenderData::PixelFormat::RGBA5551); RenderData::PixelFormat::RGBA5551);
RenderData::SpriteRegion sprite_region(&sprite_img, 0, 0, sprite_img.width, sprite_img.height); RenderData::Sprite sprite(&sprite_img, 0, 0, sprite_img.width, sprite_img.height);
const std::array<uint16_t, 8 * 4> tileIds = { const std::array<uint16_t, 8 * 4> tileIds = {
0, RenderData::Tilemap::EmptyTile, 0, RenderData::Tilemap::EmptyTile, 0, RenderData::Tilemap::EmptyTile, 0, RenderData::Tilemap::EmptyTile, 0, RenderData::Tilemap::EmptyTile, 0, RenderData::Tilemap::EmptyTile, 0, RenderData::Tilemap::EmptyTile, 0, RenderData::Tilemap::EmptyTile,
@ -179,9 +179,9 @@ int main(int argc, char* argv[])
ctx.clear_color(clearColor); ctx.clear_color(clearColor);
// sprite 测试 // sprite 测试
ctx.draw_sprite(10, 10, sprite_img); ctx.draw_sprite(10, 10, sprite);
ctx.draw_sprite_region_ex(30, 10, sprite_region, 2, false, false); ctx.draw_sprite_ex(30, 10, sprite, 2, false, false);
ctx.draw_sprite_region_ex(10, 30, sprite_region, 3, true, false); ctx.draw_sprite_ex(10, 30, sprite, 3, true, false);
ctx.draw_tilemap(testTilemap, 650, 500, 96, 48, static_cast<int32_t>(frame_start_ms / 20u) % 32, 0); ctx.draw_tilemap(testTilemap, 650, 500, 96, 48, static_cast<int32_t>(frame_start_ms / 20u) % 32, 0);
// FPS 计数 // FPS 计数

View File

@ -2,7 +2,7 @@
#pragma once #pragma once
#include <cstdint> #include <cstdint>
#include "Image.h" #include "Image.h"
#include "SpriteRegion.h" #include "Sprite.h"
namespace TomAtlas namespace TomAtlas
{ {
@ -166665,23 +166665,23 @@ namespace TomAtlas
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000
}; };
static const RenderData::Image image(tom_atlas_pixels, tom_atlas_width, tom_atlas_height, 0x0000, RenderData::PixelFormat::RGBA5551); static const RenderData::Image image(tom_atlas_pixels, tom_atlas_width, tom_atlas_height, 0x0000, RenderData::PixelFormat::RGBA5551);
static const RenderData::SpriteRegion background(&image, 0, 0, 1024, 600); static const RenderData::Sprite background(&image, 0, 0, 1024, 600);
static const RenderData::SpriteRegion tom_listhen(&image, 0, 601, 290, 450); static const RenderData::Sprite tom_listhen(&image, 0, 601, 290, 450);
static const RenderData::SpriteRegion tom_openmouse1(&image, 291, 601, 298, 450); static const RenderData::Sprite tom_openmouse1(&image, 291, 601, 298, 450);
static const RenderData::SpriteRegion tom_openmouse2(&image, 590, 601, 297, 450); static const RenderData::Sprite tom_openmouse2(&image, 590, 601, 297, 450);
static const RenderData::SpriteRegion tom_say1(&image, 0, 1052, 305, 450); static const RenderData::Sprite tom_say1(&image, 0, 1052, 305, 450);
static const RenderData::SpriteRegion tom_say2(&image, 306, 1052, 304, 450); static const RenderData::Sprite tom_say2(&image, 306, 1052, 304, 450);
static const RenderData::SpriteRegion tom_say3(&image, 611, 1052, 304, 450); static const RenderData::Sprite tom_say3(&image, 611, 1052, 304, 450);
static const RenderData::SpriteRegion tom_say4(&image, 0, 1503, 304, 450); static const RenderData::Sprite tom_say4(&image, 0, 1503, 304, 450);
static const RenderData::SpriteRegion tom_stand(&image, 305, 1503, 274, 450); static const RenderData::Sprite tom_stand(&image, 305, 1503, 274, 450);
static const RenderData::SpriteRegion ui_fat(&image, 580, 1503, 90, 90); static const RenderData::Sprite ui_fat(&image, 580, 1503, 90, 90);
static const RenderData::SpriteRegion ui_hand(&image, 671, 1503, 89, 90); static const RenderData::Sprite ui_hand(&image, 671, 1503, 89, 90);
static const RenderData::SpriteRegion ui_i(&image, 761, 1503, 89, 90); static const RenderData::Sprite ui_i(&image, 761, 1503, 89, 90);
static const RenderData::SpriteRegion ui_record(&image, 851, 1503, 86, 90); static const RenderData::Sprite ui_record(&image, 851, 1503, 86, 90);
static const RenderData::SpriteRegion ui_tom(&image, 938, 1503, 85, 90); static const RenderData::Sprite ui_tom(&image, 938, 1503, 85, 90);
} }

View File

@ -6,7 +6,7 @@
#include "Color.h" #include "Color.h"
#include "DrawContext.h" #include "DrawContext.h"
#include "PointerInput.h" #include "PointerInput.h"
#include "SpriteRegion.h" #include "Sprite.h"
#include "tom_atlas.h" #include "tom_atlas.h"
#include <algorithm> #include <algorithm>
#include <cstddef> #include <cstddef>
@ -19,7 +19,7 @@ namespace
const int32_t TomBottomPadding = 72; const int32_t TomBottomPadding = 72;
const int32_t ButtonBottomPadding = 16; const int32_t ButtonBottomPadding = 16;
const RenderData::SpriteRegion* const SpeakingFrames[] = { const RenderData::Sprite* const SpeakingFrames[] = {
&TomAtlas::tom_say1, &TomAtlas::tom_say1,
&TomAtlas::tom_say2, &TomAtlas::tom_say2,
&TomAtlas::tom_say3, &TomAtlas::tom_say3,
@ -28,7 +28,7 @@ namespace
&TomAtlas::tom_say2 &TomAtlas::tom_say2
}; };
static const RenderData::SpriteRegion& SelectSpeakingFrame(uint32_t animationMs) static const RenderData::Sprite& SelectSpeakingFrame(uint32_t animationMs)
{ {
const size_t frameCount = sizeof(SpeakingFrames) / sizeof(SpeakingFrames[0]); const size_t frameCount = sizeof(SpeakingFrames) / sizeof(SpeakingFrames[0]);
const size_t frameIndex = (animationMs / SpeakingFrameMs) % frameCount; const size_t frameIndex = (animationMs / SpeakingFrameMs) % frameCount;
@ -113,9 +113,9 @@ namespace Game
void TomGameApp::draw(Core::DrawContext& ctx) void TomGameApp::draw(Core::DrawContext& ctx)
{ {
ctx.clear_color(RenderData::Color(18, 18, 24, 255)); ctx.clear_color(RenderData::Color(18, 18, 24, 255));
ctx.draw_sprite_region(0, 0, TomAtlas::background); ctx.draw_sprite(0, 0, TomAtlas::background);
const RenderData::SpriteRegion* tom = &TomAtlas::tom_stand; const RenderData::Sprite* tom = &TomAtlas::tom_stand;
if (state == TomGameState::Recording) if (state == TomGameState::Recording)
{ {
tom = &TomAtlas::tom_listhen; tom = &TomAtlas::tom_listhen;
@ -132,8 +132,8 @@ namespace Game
tomY = 0; tomY = 0;
} }
ctx.draw_sprite_region(tomX, tomY, *tom); ctx.draw_sprite(tomX, tomY, *tom);
ctx.draw_sprite_region(buttonX, buttonY, TomAtlas::ui_record); ctx.draw_sprite(buttonX, buttonY, TomAtlas::ui_record);
} }
void TomGameApp::update_input(bool& recordTrigger) void TomGameApp::update_input(bool& recordTrigger)

View File

@ -1,5 +1,5 @@
#include "AnimationSystem.h" #include "AnimationSystem.h"
#include "SpriteRasterizer.h" #include "DrawContext.h"
namespace Game namespace Game
{ {
@ -35,7 +35,7 @@ namespace Game
} }
} }
void AnimationSystem::draw(Rasterizer::SpriteRasterizer& spriteRasterizer) const void AnimationSystem::draw(Core::DrawContext& drawContext) const
{ {
for (size_t i = 0; i < objects.size(); ++i) for (size_t i = 0; i < objects.size(); ++i)
{ {
@ -48,7 +48,7 @@ namespace Game
const RenderData::Sprite* sprite = object.animator->get_current_sprite(); const RenderData::Sprite* sprite = object.animator->get_current_sprite();
if (sprite != nullptr) if (sprite != nullptr)
{ {
spriteRasterizer.DrawSprite(*sprite, object.x, object.y); drawContext.draw_sprite(object.x, object.y, *sprite);
} }
} }
} }

View File

@ -4,9 +4,9 @@
#include <vector> #include <vector>
#include "../components/SpriteAnimator.h" #include "../components/SpriteAnimator.h"
namespace Rasterizer namespace Core
{ {
class SpriteRasterizer; class DrawContext;
} }
namespace Game namespace Game
@ -38,7 +38,7 @@ namespace Game
void clear(); void clear();
void update(float deltaTime); void update(float deltaTime);
void draw(Rasterizer::SpriteRasterizer& spriteRasterizer) const; void draw(Core::DrawContext& drawContext) const;
void set_position(size_t index, int32_t x, int32_t y); void set_position(size_t index, int32_t x, int32_t y);
void set_visible(size_t index, bool visible); void set_visible(size_t index, bool visible);

View File

@ -331,7 +331,7 @@ namespace
const std::vector<uint16_t>& atlas_pixels, const std::vector<uint16_t>& atlas_pixels,
int32_t atlas_height) int32_t atlas_height)
{ {
std::ofstream file(OutputHeaderPath); std::ofstream file(OutputHeaderPath, std::ios::binary);
if (!file.good()) if (!file.good())
{ {
std::cerr << "Open output header failed: " << OutputHeaderPath << std::endl; std::cerr << "Open output header failed: " << OutputHeaderPath << std::endl;
@ -342,7 +342,7 @@ namespace
file << "#pragma once\n"; file << "#pragma once\n";
file << "#include <cstdint>\n"; file << "#include <cstdint>\n";
file << "#include \"Image.h\"\n"; file << "#include \"Image.h\"\n";
file << "#include \"SpriteRegion.h\"\n\n"; file << "#include \"Sprite.h\"\n\n";
file << "namespace TomAtlas\n"; file << "namespace TomAtlas\n";
file << "{\n"; file << "{\n";
file << "\tstatic const int32_t tom_atlas_width = " << AtlasWidth << ";\n"; file << "\tstatic const int32_t tom_atlas_width = " << AtlasWidth << ";\n";
@ -355,9 +355,16 @@ namespace
const size_t end = std::min(i + 12, atlas_pixels.size()); const size_t end = std::min(i + 12, atlas_pixels.size());
for (size_t j = i; j < end; ++j) for (size_t j = i; j < end; ++j)
{ {
if (j > i)
{
file << " ";
}
WritePixel5551(file, atlas_pixels[j]); WritePixel5551(file, atlas_pixels[j]);
if (j + 1 < atlas_pixels.size())
{
file << ","; file << ",";
} }
}
file << "\n"; file << "\n";
} }
@ -369,7 +376,7 @@ namespace
for (size_t i = 0; i < regions.size(); ++i) for (size_t i = 0; i < regions.size(); ++i)
{ {
const AtlasRegion& region = regions[i]; const AtlasRegion& region = regions[i];
file << "\tstatic const RenderData::SpriteRegion " file << "\tstatic const RenderData::Sprite "
<< region.source->region_name << region.source->region_name
<< "(&image, " << "(&image, "
<< region.x << ", " << region.x << ", "

View File

@ -60,53 +60,39 @@ namespace Core
triangleRasterizer->DrawTriangle2D(triangle, color); triangleRasterizer->DrawTriangle2D(triangle, color);
} }
void DrawContext::draw_sprite(int32_t dst_x, int32_t dst_y, const RenderData::Image& img) void DrawContext::draw_sprite(int32_t dst_x, int32_t dst_y, const RenderData::Sprite& sprite)
{ {
draw_sprite_region(dst_x, dst_y, img, 0, 0, img.width, img.height); draw_sprite_ex(dst_x, dst_y, sprite, 1, false, false);
} }
void DrawContext::draw_sprite(int32_t dst_x, int32_t dst_y, const RenderData::SpriteRegion& region) void DrawContext::draw_sprite_ex(int32_t dst_x, int32_t dst_y, const RenderData::Sprite& sprite,
{
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,
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);
}
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) int32_t scale, bool flip_h, bool flip_v)
{ {
if (!region.atlas) return; if (!sprite.atlas) return;
draw_sprite_ex( blit_sprite_pixels(
dst_x, dst_x,
dst_y, dst_y,
*region.atlas, *sprite.atlas,
region.x, sprite.x,
region.y, sprite.y,
region.width, sprite.width,
region.height, sprite.height,
scale, scale,
flip_h, flip_h,
flip_v); flip_v);
} }
void DrawContext::draw_sprite_ex(int32_t dst_x, int32_t dst_y, const RenderData::Image& img, void DrawContext::blit_sprite_pixels(int32_t dst_x, int32_t dst_y,
int32_t src_x, int32_t src_y, int32_t src_w, int32_t src_h, const RenderData::Image& image,
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 || src_w <= 0 || src_h <= 0) return; if (scale < 1 || !image.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; if (src_x < 0 || src_y < 0 || src_x + src_w > image.width || src_y + src_h > image.height) return;
const int32_t img_w = img.width; const int32_t img_w = image.width;
const int32_t screen_w = frameBuffer->get_width(); const int32_t screen_w = frameBuffer->get_width();
const int32_t screen_h = frameBuffer->get_height(); const int32_t screen_h = frameBuffer->get_height();
const int32_t draw_w = src_w * scale; const int32_t draw_w = src_w * scale;
@ -126,7 +112,7 @@ namespace Core
Core::FramePixel* dst = static_cast<Core::FramePixel*>(frameBuffer->get_buffer()); Core::FramePixel* dst = static_cast<Core::FramePixel*>(frameBuffer->get_buffer());
const int32_t fb_w = frameBuffer->get_width(); const int32_t fb_w = frameBuffer->get_width();
const uint16_t* src = static_cast<const uint16_t*>(img.pixels); const uint16_t* src = static_cast<const uint16_t*>(image.pixels);
if (scale == 1) if (scale == 1)
{ {
@ -249,7 +235,7 @@ namespace Core
const int32_t clipped_w = clipped_right - clipped_left; const int32_t clipped_w = clipped_right - clipped_left;
const int32_t clipped_h = clipped_bottom - clipped_top; const int32_t clipped_h = clipped_bottom - clipped_top;
draw_sprite_ex(clipped_left, clipped_top, *tilemap.atlas, blit_sprite_pixels(clipped_left, clipped_top, *tilemap.atlas,
clipped_src_x, clipped_src_y, clipped_w, clipped_h, clipped_src_x, clipped_src_y, clipped_w, clipped_h,
1, false, false); 1, false, false);
} }

View File

@ -3,7 +3,7 @@
#include "Vector2.h" #include "Vector2.h"
#include "Triangle.h" #include "Triangle.h"
#include "Image.h" #include "Image.h"
#include "SpriteRegion.h" #include "Sprite.h"
#include "Tilemap.h" #include "Tilemap.h"
#include "BitmapFont.h" #include "BitmapFont.h"
#include <cstdint> #include <cstdint>
@ -41,6 +41,12 @@ namespace Core
int32_t dst_x, int32_t dst_y, int32_t dst_x, int32_t dst_y,
uint16_t pixel); uint16_t pixel);
void blit_sprite_pixels(int32_t dst_x, int32_t dst_y,
const RenderData::Image& image,
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);
public: public:
DrawContext(int32_t width, int32_t height); DrawContext(int32_t width, int32_t height);
~DrawContext(); ~DrawContext();
@ -58,15 +64,8 @@ namespace Core
void draw_line(const Math::Vector2Int& from, const Math::Vector2Int& to, const RenderData::Color& color); void draw_line(const Math::Vector2Int& from, const Math::Vector2Int& to, const RenderData::Color& color);
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::Sprite& sprite);
void draw_sprite(int32_t dst_x, int32_t dst_y, const RenderData::SpriteRegion& region); void draw_sprite_ex(int32_t dst_x, int32_t dst_y, const RenderData::Sprite& sprite,
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);
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,
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, void draw_tilemap(const RenderData::Tilemap& tilemap,
int32_t screen_x, int32_t screen_y, int32_t screen_x, int32_t screen_y,

View File

@ -4,7 +4,7 @@
namespace RenderData namespace RenderData
{ {
struct SpriteRegion struct Sprite
{ {
const Image* atlas; const Image* atlas;
int32_t x; int32_t x;
@ -12,10 +12,10 @@ namespace RenderData
int32_t width; int32_t width;
int32_t height; int32_t height;
SpriteRegion() Sprite()
: atlas(nullptr), x(0), y(0), width(0), height(0) {} : 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) Sprite(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) {} : atlas(atlas), x(x), y(y), width(width), height(height) {}
}; };
} }

View File

@ -1,65 +0,0 @@
#include "SpriteRasterizer.h"
#include "FrameBuffer.h"
#include "../RenderData/Image.h"
#include "../RenderData/Sprite.h"
namespace Rasterizer
{
namespace
{
inline uint16_t rgba5551_to_rgb565(uint16_t c)
{
const uint16_t r = static_cast<uint16_t>((c >> 11) & 0x1Fu);
const uint16_t g = static_cast<uint16_t>((c >> 6) & 0x1Fu);
const uint16_t b = static_cast<uint16_t>((c >> 1) & 0x1Fu);
const uint16_t g6 = static_cast<uint16_t>((g << 1) | (g >> 4));
return static_cast<uint16_t>((r << 11) | (g6 << 5) | b);
}
inline bool rgba5551_is_opaque(uint16_t c)
{
return (c & 0x1u) != 0;
}
}
void SpriteRasterizer::DrawImage(const RenderData::Image& image, int32_t x, int32_t y)
{
DrawSprite(RenderData::Sprite(&image), x, y);
}
void SpriteRasterizer::DrawSprite(const RenderData::Sprite& sprite, int32_t x, int32_t y)
{
if (frameBuffer == nullptr || !sprite.is_valid())
{
return;
}
const int32_t minX = x < 0 ? -x : 0;
const int32_t minY = y < 0 ? -y : 0;
const int32_t maxX = x + sprite.width > frameBuffer->get_width() ? frameBuffer->get_width() - x : sprite.width;
const int32_t maxY = y + sprite.height > frameBuffer->get_height() ? frameBuffer->get_height() - y : sprite.height;
if (minX >= maxX || minY >= maxY)
{
return;
}
Core::FramePixel* dst = static_cast<Core::FramePixel*>(frameBuffer->get_buffer());
const int32_t fb_w = frameBuffer->get_width();
for (int32_t sy = minY; sy < maxY; ++sy)
{
Core::FramePixel* dst_row = dst + (y + sy) * fb_w;
for (int32_t sx = minX; sx < maxX; ++sx)
{
const uint16_t color = sprite.get_pixel_fast(sx, sy);
if (!rgba5551_is_opaque(color))
{
continue;
}
dst_row[x + sx] = rgba5551_to_rgb565(color);
}
}
}
}

View File

@ -1,28 +0,0 @@
#pragma once
#include <cstdint>
namespace Core
{
class FrameBuffer;
}
namespace RenderData
{
class Image;
struct Sprite;
}
namespace Rasterizer
{
class SpriteRasterizer
{
private:
Core::FrameBuffer* frameBuffer;
public:
explicit SpriteRasterizer(Core::FrameBuffer* frameBuffer) : frameBuffer(frameBuffer) {}
void DrawImage(const RenderData::Image& image, int32_t x, int32_t y);
void DrawSprite(const RenderData::Sprite& sprite, int32_t x, int32_t y);
};
}

View File

@ -1,105 +0,0 @@
#pragma once
#include <cstddef>
#include <cstdint>
#include <limits>
#include <vector>
namespace RenderData
{
class Image
{
private:
int32_t width;
int32_t height;
std::vector<uint16_t> pixels;
static bool calculate_pixel_count(int32_t width, int32_t height, size_t& count)
{
if (width <= 0 || height <= 0)
{
count = 0;
return false;
}
const size_t safeWidth = static_cast<size_t>(width);
const size_t safeHeight = static_cast<size_t>(height);
if (safeWidth > std::numeric_limits<size_t>::max() / safeHeight)
{
count = 0;
return false;
}
count = safeWidth * safeHeight;
return true;
}
public:
Image() : width(0), height(0) {}
Image(int32_t width, int32_t height) : width(0), height(0)
{
size_t pixelCount = 0;
if (calculate_pixel_count(width, height, pixelCount))
{
this->width = width;
this->height = height;
pixels.assign(pixelCount, 0);
}
}
Image(int32_t width, int32_t height, const std::vector<uint16_t>& pixels) : width(0), height(0)
{
size_t pixelCount = 0;
if (calculate_pixel_count(width, height, pixelCount) && pixels.size() == pixelCount)
{
this->width = width;
this->height = height;
this->pixels = pixels;
}
}
int32_t get_width() const { return width; }
int32_t get_height() const { return height; }
size_t total_pixels() const { return pixels.size(); }
const uint16_t* data() const { return pixels.data(); }
uint16_t* data() { return pixels.data(); }
bool is_valid() const
{
size_t pixelCount = 0;
return calculate_pixel_count(width, height, pixelCount) && pixels.size() == pixelCount;
}
uint16_t get_pixel(int32_t x, int32_t y) const
{
if (x < 0 || x >= width || y < 0 || y >= height)
{
return 0;
}
size_t index = static_cast<size_t>(y) * width + x;
return pixels[index];
}
uint16_t get_pixel_fast(int32_t x, int32_t y) const
{
size_t index = static_cast<size_t>(y) * width + x;
return pixels[index];
}
void set_pixel(int32_t x, int32_t y, uint16_t color)
{
if (x < 0 || x >= width || y < 0 || y >= height)
{
return;
}
size_t index = static_cast<size_t>(y) * width + x;
pixels.at(index) = color;
}
};
}

View File

@ -1,63 +0,0 @@
#pragma once
#include <cstdint>
#include "Image.h"
namespace RenderData
{
struct Sprite
{
const Image* image;
int32_t x;
int32_t y;
int32_t width;
int32_t height;
Sprite() : image(nullptr), x(0), y(0), width(0), height(0) {}
Sprite(const Image* image) :
image(image),
x(0),
y(0),
width(image ? image->get_width() : 0),
height(image ? image->get_height() : 0)
{}
Sprite(const Image* image, int32_t x, int32_t y, int32_t width, int32_t height) :
image(image),
x(x),
y(y),
width(width),
height(height)
{}
bool is_valid() const
{
if (image == nullptr || !image->is_valid())
{
return false;
}
if (x < 0 || y < 0 || width <= 0 || height <= 0)
{
return false;
}
return x + width <= image->get_width() && y + height <= image->get_height();
}
uint16_t get_pixel(int32_t localX, int32_t localY) const
{
if (!is_valid() || localX < 0 || localX >= width || localY < 0 || localY >= height)
{
return 0;
}
return image->get_pixel(x + localX, y + localY);
}
uint16_t get_pixel_fast(int32_t localX, int32_t localY) const
{
return image->get_pixel_fast(x + localX, y + localY);
}
};
}

View File

@ -78,11 +78,12 @@ namespace
Core::DrawContext ctx(2, 1); Core::DrawContext ctx(2, 1);
ctx.clear_color(RenderData::Color(0, 0, 255, 255)); ctx.clear_color(RenderData::Color(0, 0, 255, 255));
RenderData::Image sprite( RenderData::Image image(
sprite_pixels, sprite_pixels,
2, 2,
1, 1,
RenderData::PixelFormat::RGBA5551); RenderData::PixelFormat::RGBA5551);
RenderData::Sprite sprite(&image, 0, 0, image.width, image.height);
ctx.draw_sprite(0, 0, sprite); ctx.draw_sprite(0, 0, sprite);
CaptureDisplay display; CaptureDisplay display;
@ -95,6 +96,29 @@ namespace
assert(pixels[1] == 0x07E0u); assert(pixels[1] == 0x07E0u);
} }
void TestDrawSpriteUsesAtlasSubrect()
{
const uint16_t atlas_pixels[] = {
0xF801u,
0x07C1u
};
RenderData::Image atlas(
atlas_pixels,
2,
1,
RenderData::PixelFormat::RGBA5551);
RenderData::Sprite sprite(&atlas, 1, 0, 1, 1);
Core::DrawContext ctx(1, 1);
ctx.clear_color(RenderData::Color::Blue());
ctx.draw_sprite(0, 0, sprite);
CaptureDisplay display;
const Core::FramePixel* pixels = PresentPixels(ctx, display);
assert(pixels[0] == 0x07E0u);
}
void TestSpriteAssetLoaderRoundTripsRgba5551() void TestSpriteAssetLoaderRoundTripsRgba5551()
{ {
const std::string path = "render_pipeline_test.sprite"; const std::string path = "render_pipeline_test.sprite";
@ -216,6 +240,7 @@ int main()
TestFrameBufferStoresRgb565(); TestFrameBufferStoresRgb565();
TestImageDefaultsToRgba5551AndReturnsRawPixels(); TestImageDefaultsToRgba5551AndReturnsRawPixels();
TestDrawSpriteUsesOneBitAlpha(); TestDrawSpriteUsesOneBitAlpha();
TestDrawSpriteUsesAtlasSubrect();
TestSpriteAssetLoaderRoundTripsRgba5551(); TestSpriteAssetLoaderRoundTripsRgba5551();
TestDrawTextUsesBitMaskAndColor(); TestDrawTextUsesBitMaskAndColor();
TestDrawTextColorChangesPerCallAndBackgroundFill(); TestDrawTextColorChangesPerCallAndBackgroundFill();