统一 2D 渲染链路以消除格式分叉和热路径中转
将图片主路径收口为 RGBA5551 输入、RGB565 backbuffer 输出,并删除 USE_RGB565_BACKBUFFER 双路径。sprite/tilemap 主绘制改为直接 RGBA5551 -> RGB565,透明语义固定为 1-bit alpha test。同步收口 SDL/FB present 路径、sprite 资源加载/导出规范,以及相关测试与文档。 Constraint: 运行时图片主路径需统一到 RGBA5551,不能继续保留主要 RGBA8888 分支 Constraint: 透明仅支持 1-bit 语义,不引入 alpha blending Rejected: 保留 USE_RGB565_BACKBUFFER 宏双路径 | 持续增加维护成本并掩盖真实主链路 Rejected: 主绘制继续走 RGBA5551 -> RGBA8888 -> RGB565 | 热路径存在不必要中转 Confidence: high Scope-risk: moderate Directive: 后续图片资源默认按 RGBA5551 接入;不要重新引入通用 RGBA8888 主运行时路径 Directive: 字体 atlas 目前仍属兼容输入;若继续收口,优先改成 1-bit mask 而不是新的图片分支 Tested: render_pipeline_tests;SDL Demo Release 构建;搜索确认无 USE_RGB565_BACKBUFFER 残留 Not-tested: Linux framebuffer 目标实机构建与显示验证
This commit is contained in:
parent
8785368913
commit
5ac61dca0e
|
|
@ -55,11 +55,7 @@ AGENTS.md
|
|||
.omc
|
||||
.omx
|
||||
|
||||
build-win
|
||||
build-linux
|
||||
build-arm-fb
|
||||
build-arm-sdl
|
||||
build-check
|
||||
build-*
|
||||
|
||||
.idea
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
cmake_minimum_required(VERSION 3.16)
|
||||
project(IMX6U-Game)
|
||||
include(CTest)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 11)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
|
@ -9,7 +10,6 @@ if(NOT CMAKE_CONFIGURATION_TYPES AND NOT CMAKE_BUILD_TYPE)
|
|||
endif()
|
||||
|
||||
option(USE_FRAMEBUFFER "Use Linux framebuffer instead of SDL2" OFF)
|
||||
option(USE_RGB565_BACKBUFFER "Use RGB565 internal backbuffer to eliminate present conversion on RGB565 fb0" OFF)
|
||||
|
||||
set(CORE_SOURCES
|
||||
src/Core/Asset/ObjLoader.cpp
|
||||
|
|
@ -63,10 +63,6 @@ if(USE_FRAMEBUFFER)
|
|||
target_compile_definitions(imx6u_core PUBLIC USE_FRAMEBUFFER)
|
||||
endif()
|
||||
|
||||
if(USE_RGB565_BACKBUFFER)
|
||||
target_compile_definitions(imx6u_core PUBLIC USE_RGB565_BACKBUFFER)
|
||||
endif()
|
||||
|
||||
if(USE_FRAMEBUFFER)
|
||||
else()
|
||||
if(WIN32)
|
||||
|
|
@ -139,3 +135,38 @@ endfunction()
|
|||
|
||||
add_subdirectory(src/Apps/Game)
|
||||
add_subdirectory(src/Apps/Demo)
|
||||
|
||||
if(BUILD_TESTING AND NOT USE_FRAMEBUFFER)
|
||||
add_executable(render_pipeline_tests
|
||||
tests/render_pipeline_tests.cpp
|
||||
)
|
||||
target_link_libraries(render_pipeline_tests PRIVATE imx6u_core)
|
||||
target_include_directories(render_pipeline_tests PRIVATE ${CORE_INCLUDE_DIRS})
|
||||
|
||||
if(CMAKE_CONFIGURATION_TYPES)
|
||||
foreach(config ${CMAKE_CONFIGURATION_TYPES})
|
||||
string(TOUPPER "${config}" config_upper)
|
||||
set_target_properties(render_pipeline_tests PROPERTIES
|
||||
RUNTIME_OUTPUT_DIRECTORY_${config_upper} "${CMAKE_BINARY_DIR}/${config}"
|
||||
)
|
||||
endforeach()
|
||||
else()
|
||||
set_target_properties(render_pipeline_tests PROPERTIES
|
||||
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}"
|
||||
)
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
add_custom_command(TARGET render_pipeline_tests POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
"${SDL2_DLL}"
|
||||
"$<TARGET_FILE_DIR:render_pipeline_tests>"
|
||||
)
|
||||
endif()
|
||||
|
||||
if(MSVC)
|
||||
target_compile_options(render_pipeline_tests PRIVATE /utf-8 /W3)
|
||||
endif()
|
||||
|
||||
add_test(NAME render_pipeline_tests COMMAND render_pipeline_tests)
|
||||
endif()
|
||||
|
|
|
|||
12
README.md
12
README.md
|
|
@ -321,15 +321,15 @@ IMX6U-Game/
|
|||
|
||||
### Framebuffer 性能说明
|
||||
|
||||
`FBDisplay` 是直接写 `/dev/fb0` 的对照后端。当前实现会从 CPU 侧 `FrameBuffer` 提交到系统 framebuffer,并针对常见像素格式提供快速路径:
|
||||
`FBDisplay` 是直接写 `/dev/fb0` 的对照后端。当前实现会从 CPU 侧 `RGB565 FrameBuffer` 提交到系统 framebuffer,并针对常见像素格式提供快速路径:
|
||||
|
||||
- RGB565:使用专用 RGBA -> RGB565 转换;
|
||||
- ARGB8888 / XRGB8888 类 32bpp:使用专用通道重排;
|
||||
- RGBA8888 且行宽连续时:整块 `memcpy`。
|
||||
- RGB565:直接行拷贝;
|
||||
- ARGB8888 / XRGB8888 类 32bpp:使用专用 `RGB565 -> 32-bit` 转换;
|
||||
- 其他格式:走统一 display conversion。
|
||||
|
||||
板端性能测试必须使用 Release 构建。一次测试中,未优化 ARM 构建曾导致 `Frame:81ms / Present:69ms`,开启 Release 后同一轻量 2D demo 可达到约 76 FPS。该结果说明 `/dev/fb0` 整屏提交仍是关键热点,但构建类型会极大影响结论。后续优化优先级:
|
||||
|
||||
1. 直接使用与目标 fb0 一致的 backbuffer 像素格式,例如 RGB565,减少提交时转换;
|
||||
1. 对 32-bit fb0 增加更快的 `RGB565 -> 目标格式` 批量转换;
|
||||
2. 2D 场景使用 dirty rect / 局部提交,避免每帧整屏写入;
|
||||
3. 避免无 3D 内容时清理 depth buffer;
|
||||
4. 对 tile/sprite 增加不透明行拷贝、预转换资源或专用批处理路径。
|
||||
|
|
@ -339,7 +339,7 @@ IMX6U-Game/
|
|||
**已完成:**
|
||||
- 可旋转立方体的 3D 渲染(MVP 变换、背面剔除、扫描线填充、深度测试)
|
||||
- 双平台显示后端(SDL2 / Framebuffer)
|
||||
- 离线资源转换工具:PNG sprite -> C++ 头文件,像素字体 -> bitmap atlas/header
|
||||
- 离线资源转换工具:PNG sprite -> RGBA5551 C++ 头文件,像素字体 -> bitmap atlas/header
|
||||
- 基础 2D sprite、SpriteRegion、bitmap font 文本绘制和 tilemap 视口绘制,当前 demo 显示 FPS 文本、测试 sprite 和小型滚动 tilemap
|
||||
- Core 目录规范化,代码收敛到 `src/Core/`
|
||||
- `Core::DrawContext` 统一绘制入口,封装现有绘制能力
|
||||
|
|
|
|||
|
|
@ -1,25 +1,25 @@
|
|||
// Auto-generated by tools/png_to_header.py
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
|
||||
static const int32_t test_sprite_width = 16;
|
||||
static const int32_t test_sprite_height = 16;
|
||||
|
||||
static const uint32_t test_sprite_pixels[] = {
|
||||
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
|
||||
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
|
||||
0x00000000, 0x00000000, 0xDC2828FF, 0xDC2828FF, 0xDC2828FF, 0xDC2828FF, 0xDC2828FF, 0xDC2828FF, 0xDC2828FF, 0xDC2828FF, 0xDC2828FF, 0xDC2828FF, 0xDC2828FF, 0xDC2828FF, 0x00000000, 0x00000000,
|
||||
0x00000000, 0x00000000, 0xDC2828FF, 0xDC2828FF, 0xDC2828FF, 0xDC2828FF, 0xDC2828FF, 0xDC2828FF, 0xDC2828FF, 0xDC2828FF, 0xDC2828FF, 0xDC2828FF, 0xDC2828FF, 0xDC2828FF, 0x00000000, 0x00000000,
|
||||
0x00000000, 0x00000000, 0xDC2828FF, 0xDC2828FF, 0xFF6464FF, 0xFF6464FF, 0xFF6464FF, 0xFF6464FF, 0xFF6464FF, 0xFF6464FF, 0xFF6464FF, 0xFF6464FF, 0xDC2828FF, 0xDC2828FF, 0x00000000, 0x00000000,
|
||||
0x00000000, 0x00000000, 0xDC2828FF, 0xDC2828FF, 0xFF6464FF, 0xFF6464FF, 0xFF6464FF, 0xFF6464FF, 0xFF6464FF, 0xFF6464FF, 0xFF6464FF, 0xFF6464FF, 0xDC2828FF, 0xDC2828FF, 0x00000000, 0x00000000,
|
||||
0x00000000, 0x00000000, 0xDC2828FF, 0xDC2828FF, 0xFF6464FF, 0xFF6464FF, 0xFF6464FF, 0xFF6464FF, 0xFF6464FF, 0xFF6464FF, 0xFF6464FF, 0xFF6464FF, 0xDC2828FF, 0xDC2828FF, 0x00000000, 0x00000000,
|
||||
0x00000000, 0x00000000, 0xDC2828FF, 0xDC2828FF, 0xFF6464FF, 0xFF6464FF, 0xFF6464FF, 0xFF6464FF, 0xFF6464FF, 0xFF6464FF, 0xFF6464FF, 0xFF6464FF, 0xDC2828FF, 0xDC2828FF, 0x00000000, 0x00000000,
|
||||
0x00000000, 0x00000000, 0xDC2828FF, 0xDC2828FF, 0xFF6464FF, 0xFF6464FF, 0xFF6464FF, 0xFF6464FF, 0xFF6464FF, 0xFF6464FF, 0xFF6464FF, 0xFF6464FF, 0xDC2828FF, 0xDC2828FF, 0x00000000, 0x00000000,
|
||||
0x00000000, 0x00000000, 0xDC2828FF, 0xDC2828FF, 0xFF6464FF, 0xFF6464FF, 0xFF6464FF, 0xFF6464FF, 0xFF6464FF, 0xFF6464FF, 0xFF6464FF, 0xFF6464FF, 0xDC2828FF, 0xDC2828FF, 0x00000000, 0x00000000,
|
||||
0x00000000, 0x00000000, 0xDC2828FF, 0xDC2828FF, 0xFF6464FF, 0xFF6464FF, 0xFF6464FF, 0xFF6464FF, 0xFF6464FF, 0xFF6464FF, 0xFF6464FF, 0xFF6464FF, 0xDC2828FF, 0xDC2828FF, 0x00000000, 0x00000000,
|
||||
0x00000000, 0x00000000, 0xDC2828FF, 0xDC2828FF, 0xFF6464FF, 0xFF6464FF, 0xFF6464FF, 0xFF6464FF, 0xFF6464FF, 0xFF6464FF, 0xFF6464FF, 0xFF6464FF, 0xDC2828FF, 0xDC2828FF, 0x00000000, 0x00000000,
|
||||
0x00000000, 0x00000000, 0xDC2828FF, 0xDC2828FF, 0xDC2828FF, 0xDC2828FF, 0xDC2828FF, 0xDC2828FF, 0xDC2828FF, 0xDC2828FF, 0xDC2828FF, 0xDC2828FF, 0xDC2828FF, 0xDC2828FF, 0x00000000, 0x00000000,
|
||||
0x00000000, 0x00000000, 0xDC2828FF, 0xDC2828FF, 0xDC2828FF, 0xDC2828FF, 0xDC2828FF, 0xDC2828FF, 0xDC2828FF, 0xDC2828FF, 0xDC2828FF, 0xDC2828FF, 0xDC2828FF, 0xDC2828FF, 0x00000000, 0x00000000,
|
||||
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
|
||||
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
|
||||
};
|
||||
// Auto-generated by tools/png_to_header.py
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
|
||||
static const int32_t test_sprite_width = 16;
|
||||
static const int32_t test_sprite_height = 16;
|
||||
|
||||
static const uint16_t test_sprite_pixels[] = {
|
||||
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, 0xD94B, 0xD94B, 0xD94B, 0xD94B, 0xD94B, 0xD94B, 0xD94B, 0xD94B, 0xD94B, 0xD94B, 0xD94B, 0xD94B, 0x0000, 0x0000,
|
||||
0x0000, 0x0000, 0xD94B, 0xD94B, 0xD94B, 0xD94B, 0xD94B, 0xD94B, 0xD94B, 0xD94B, 0xD94B, 0xD94B, 0xD94B, 0xD94B, 0x0000, 0x0000,
|
||||
0x0000, 0x0000, 0xD94B, 0xD94B, 0xFB19, 0xFB19, 0xFB19, 0xFB19, 0xFB19, 0xFB19, 0xFB19, 0xFB19, 0xD94B, 0xD94B, 0x0000, 0x0000,
|
||||
0x0000, 0x0000, 0xD94B, 0xD94B, 0xFB19, 0xFB19, 0xFB19, 0xFB19, 0xFB19, 0xFB19, 0xFB19, 0xFB19, 0xD94B, 0xD94B, 0x0000, 0x0000,
|
||||
0x0000, 0x0000, 0xD94B, 0xD94B, 0xFB19, 0xFB19, 0xFB19, 0xFB19, 0xFB19, 0xFB19, 0xFB19, 0xFB19, 0xD94B, 0xD94B, 0x0000, 0x0000,
|
||||
0x0000, 0x0000, 0xD94B, 0xD94B, 0xFB19, 0xFB19, 0xFB19, 0xFB19, 0xFB19, 0xFB19, 0xFB19, 0xFB19, 0xD94B, 0xD94B, 0x0000, 0x0000,
|
||||
0x0000, 0x0000, 0xD94B, 0xD94B, 0xFB19, 0xFB19, 0xFB19, 0xFB19, 0xFB19, 0xFB19, 0xFB19, 0xFB19, 0xD94B, 0xD94B, 0x0000, 0x0000,
|
||||
0x0000, 0x0000, 0xD94B, 0xD94B, 0xFB19, 0xFB19, 0xFB19, 0xFB19, 0xFB19, 0xFB19, 0xFB19, 0xFB19, 0xD94B, 0xD94B, 0x0000, 0x0000,
|
||||
0x0000, 0x0000, 0xD94B, 0xD94B, 0xFB19, 0xFB19, 0xFB19, 0xFB19, 0xFB19, 0xFB19, 0xFB19, 0xFB19, 0xD94B, 0xD94B, 0x0000, 0x0000,
|
||||
0x0000, 0x0000, 0xD94B, 0xD94B, 0xFB19, 0xFB19, 0xFB19, 0xFB19, 0xFB19, 0xFB19, 0xFB19, 0xFB19, 0xD94B, 0xD94B, 0x0000, 0x0000,
|
||||
0x0000, 0x0000, 0xD94B, 0xD94B, 0xD94B, 0xD94B, 0xD94B, 0xD94B, 0xD94B, 0xD94B, 0xD94B, 0xD94B, 0xD94B, 0xD94B, 0x0000, 0x0000,
|
||||
0x0000, 0x0000, 0xD94B, 0xD94B, 0xD94B, 0xD94B, 0xD94B, 0xD94B, 0xD94B, 0xD94B, 0xD94B, 0xD94B, 0xD94B, 0xD94B, 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,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -105,7 +105,8 @@ Framebuffer 后端是 IMX6U 上最容易误判性能的路径。`FBDisplay::pres
|
|||
- ARM / framebuffer 性能测试必须使用 Release 构建;单配置生成器应确认 `CMAKE_BUILD_TYPE=Release`。
|
||||
- 性能结论必须拆分 `Frame` 和 `Present` 耗时;如果 `Present` 接近 `Frame`,优先优化显示提交,而不是游戏逻辑。
|
||||
- fb0 像素格式应在初始化时打印并据此走专用路径,常见格式包括 RGB565、ARGB8888/XRGB8888、RGBA8888。
|
||||
- 避免每帧重复做不必要的通用 RGBA 转换;可考虑目标格式 backbuffer、预转换资源、行拷贝、dirty rect 和局部提交。
|
||||
- 当前规范固定 `FrameBuffer` 为 `RGB565`;sprite/tilemap 主路径直接从 `RGBA5551` 写入 `RGB565`,透明仅支持 1-bit alpha test(`A=0` 跳过,`A=1` 覆写)。
|
||||
- 避免每帧重复做不必要的通用 RGBA 转换;优先使用 `RGB565` 行拷贝、预转换资源、dirty rect 和局部提交。
|
||||
- 直接写 `/dev/fb0` 不是原子换屏;LCD 控制器可能边扫描边显示正在写入的内存,因此肉眼流畅度不等同于完整帧率。
|
||||
|
||||
已观察到的板端测试结论:
|
||||
|
|
@ -139,9 +140,9 @@ Framebuffer 后端是 IMX6U 上最容易误判性能的路径。`FBDisplay::pres
|
|||
|
||||
- 图片、字体等外部资源应在离线阶段转换为运行时直接可用的数据格式,避免在 IMX6U 热路径或启动关键路径中引入 PNG/TTF 解码成本。
|
||||
- 当前工具约定:
|
||||
- `tools/png_to_header.py`:PNG -> `uint32_t` RGBA 头文件。
|
||||
- `tools/png_to_header.py`:PNG -> `uint16_t` `RGBA5551` 头文件(sprite 主路径)。
|
||||
- `tools/gen_font_atlas.py`:像素字体 TTF -> ASCII bitmap font atlas/header。
|
||||
- 生成像素格式统一为 `(R << 24) | (G << 16) | (B << 8) | A`,与 `RenderData::Color::to_rgba()` 和 `Core::FrameBuffer` 当前格式保持一致。
|
||||
- sprite 运行时输入规范统一为 `RGBA5551`,内部 backbuffer 统一为 `RGB565`;字体 atlas 目前仍允许局部兼容 `RGBA8888` 输入,但只使用 1-bit alpha 语义,不支持 alpha blending。
|
||||
- 源资源、生成脚本和生成头文件应同时提交,保证资源可追溯、可复现。
|
||||
- 运行时绘制 sprite/font/tilemap 时只做裁剪、透明判断、颜色替换、tile id 查表或必要的像素拷贝;不要在绘制函数内做文件 IO、图片解码、字体栅格化或动态分配。
|
||||
- Tilemap 绘制应按视口可见范围遍历 tile,不能每帧无条件扫描整张地图;视口边缘允许通过 sprite 像素裁剪显示半个 tile。
|
||||
|
|
|
|||
|
|
@ -133,13 +133,21 @@ int main(int argc, char* argv[])
|
|||
Platform::SteadyTimeSource time_source;
|
||||
|
||||
RenderData::BitmapFont font;
|
||||
font.atlas = RenderData::Image(font_atlas_pixels, font_atlas_width, font_atlas_height);
|
||||
font.atlas = RenderData::Image(
|
||||
font_atlas_pixels,
|
||||
font_atlas_width,
|
||||
font_atlas_height,
|
||||
RenderData::PixelFormat::RGBA8888);
|
||||
font.char_w = font_char_w;
|
||||
font.char_h = font_char_h;
|
||||
font.columns = font_columns;
|
||||
font.first_char = font_first_char;
|
||||
|
||||
RenderData::Image sprite_img(test_sprite_pixels, test_sprite_width, test_sprite_height, 0x00000000);
|
||||
RenderData::Image sprite_img(
|
||||
test_sprite_pixels,
|
||||
test_sprite_width,
|
||||
test_sprite_height,
|
||||
RenderData::PixelFormat::RGBA5551);
|
||||
RenderData::SpriteRegion sprite_region(&sprite_img, 0, 0, sprite_img.width, sprite_img.height);
|
||||
|
||||
const std::array<uint16_t, 8 * 4> tileIds = {
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -271,12 +271,25 @@ namespace
|
|||
return atlas_pixels;
|
||||
}
|
||||
|
||||
static void WritePixel(std::ofstream& file, uint32_t pixel)
|
||||
static uint16_t rgba8888_to_rgba5551(uint32_t c)
|
||||
{
|
||||
uint32_t r = (c >> 24) & 0xFFu;
|
||||
uint32_t g = (c >> 16) & 0xFFu;
|
||||
uint32_t b = (c >> 8) & 0xFFu;
|
||||
uint32_t a = c & 0xFFu;
|
||||
return static_cast<uint16_t>(
|
||||
((r >> 3) << 11) |
|
||||
((g >> 3) << 6) |
|
||||
((b >> 3) << 1) |
|
||||
(a ? 1u : 0u));
|
||||
}
|
||||
|
||||
static void WritePixel5551(std::ofstream& file, uint16_t pixel)
|
||||
{
|
||||
file << "0x"
|
||||
<< std::uppercase
|
||||
<< std::hex
|
||||
<< std::setw(8)
|
||||
<< std::setw(4)
|
||||
<< std::setfill('0')
|
||||
<< pixel
|
||||
<< std::dec
|
||||
|
|
@ -305,7 +318,7 @@ namespace
|
|||
file << "{\n";
|
||||
file << "\tstatic const int32_t tom_atlas_width = " << AtlasWidth << ";\n";
|
||||
file << "\tstatic const int32_t tom_atlas_height = " << atlas_height << ";\n\n";
|
||||
file << "\tstatic const uint32_t tom_atlas_pixels[] = {\n";
|
||||
file << "\tstatic const uint16_t tom_atlas_pixels[] = {\n";
|
||||
|
||||
for (size_t i = 0; i < atlas_pixels.size(); i += 12)
|
||||
{
|
||||
|
|
@ -313,7 +326,7 @@ namespace
|
|||
const size_t end = std::min(i + 12, atlas_pixels.size());
|
||||
for (size_t j = i; j < end; ++j)
|
||||
{
|
||||
WritePixel(file, atlas_pixels[j]);
|
||||
WritePixel5551(file, rgba8888_to_rgba5551(atlas_pixels[j]));
|
||||
file << ", ";
|
||||
}
|
||||
file << "\n";
|
||||
|
|
@ -321,7 +334,8 @@ namespace
|
|||
|
||||
file << "\t};\n\n";
|
||||
file << "\tstatic const RenderData::Image image("
|
||||
<< "tom_atlas_pixels, tom_atlas_width, tom_atlas_height, 0x00000000u);\n\n";
|
||||
<< "tom_atlas_pixels, tom_atlas_width, tom_atlas_height, 0x0000, "
|
||||
<< "RenderData::PixelFormat::RGBA5551);\n\n";
|
||||
|
||||
for (size_t i = 0; i < regions.size(); ++i)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,180 +0,0 @@
|
|||
#include "SpriteAssetLoader.h"
|
||||
#include <cstdint>
|
||||
#include <fstream>
|
||||
#include <limits>
|
||||
#include <vector>
|
||||
|
||||
namespace
|
||||
{
|
||||
static const char SpriteMagic[4] = { 'S', 'P', 'R', 'T' };
|
||||
static const size_t SpriteHeaderSize = 20;
|
||||
static const uint32_t SpriteVersion = 1;
|
||||
static const uint32_t SpriteFormatRgba8888 = 1;
|
||||
static const uint64_t MaxSpritePixels = 8192ull * 8192ull;
|
||||
|
||||
static bool ReadExact(std::ifstream& file, char* data, size_t size)
|
||||
{
|
||||
file.read(data, static_cast<std::streamsize>(size));
|
||||
return static_cast<size_t>(file.gcount()) == size;
|
||||
}
|
||||
|
||||
static bool ReadU32LE(const std::vector<uint8_t>& bytes, size_t offset, uint32_t& value)
|
||||
{
|
||||
if (offset + 4 > bytes.size())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
value =
|
||||
static_cast<uint32_t>(bytes[offset]) |
|
||||
(static_cast<uint32_t>(bytes[offset + 1]) << 8) |
|
||||
(static_cast<uint32_t>(bytes[offset + 2]) << 16) |
|
||||
(static_cast<uint32_t>(bytes[offset + 3]) << 24);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void WriteU32LE(std::ofstream& file, uint32_t value)
|
||||
{
|
||||
const char bytes[4] = {
|
||||
static_cast<char>(value & 0xFF),
|
||||
static_cast<char>((value >> 8) & 0xFF),
|
||||
static_cast<char>((value >> 16) & 0xFF),
|
||||
static_cast<char>((value >> 24) & 0xFF)
|
||||
};
|
||||
file.write(bytes, sizeof(bytes));
|
||||
}
|
||||
|
||||
static bool CheckPixelCount(uint32_t width, uint32_t height, size_t& pixelCount)
|
||||
{
|
||||
if (width == 0 || height == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint64_t total = static_cast<uint64_t>(width) * static_cast<uint64_t>(height);
|
||||
if (total > MaxSpritePixels ||
|
||||
total > static_cast<uint64_t>(std::numeric_limits<size_t>::max() / sizeof(uint32_t)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
pixelCount = static_cast<size_t>(total);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool ReadPixels(std::ifstream& file, std::vector<uint32_t>& pixels)
|
||||
{
|
||||
std::vector<uint8_t> bytes(pixels.size() * sizeof(uint32_t), 0);
|
||||
if (!ReadExact(file, reinterpret_cast<char*>(bytes.data()), bytes.size()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < pixels.size(); ++i)
|
||||
{
|
||||
uint32_t value = 0;
|
||||
if (!ReadU32LE(bytes, i * sizeof(uint32_t), value))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
pixels[i] = value;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void WritePixels(std::ofstream& file, const RenderData::Image& image)
|
||||
{
|
||||
for (size_t i = 0; i < image.total_pixels(); ++i)
|
||||
{
|
||||
WriteU32LE(file, image.data()[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace Asset
|
||||
{
|
||||
bool SpriteAssetLoader::Load(const std::string& path, RenderData::Image& image)
|
||||
{
|
||||
std::ifstream file(path.c_str(), std::ios::binary);
|
||||
if (!file.good())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> header(SpriteHeaderSize, 0);
|
||||
if (!ReadExact(file, reinterpret_cast<char*>(header.data()), header.size()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (header[0] != SpriteMagic[0] ||
|
||||
header[1] != SpriteMagic[1] ||
|
||||
header[2] != SpriteMagic[2] ||
|
||||
header[3] != SpriteMagic[3])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t version = 0;
|
||||
uint32_t width = 0;
|
||||
uint32_t height = 0;
|
||||
uint32_t format = 0;
|
||||
if (!ReadU32LE(header, 4, version) ||
|
||||
!ReadU32LE(header, 8, width) ||
|
||||
!ReadU32LE(header, 12, height) ||
|
||||
!ReadU32LE(header, 16, format))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t pixelCount = 0;
|
||||
if (version != SpriteVersion ||
|
||||
format != SpriteFormatRgba8888 ||
|
||||
!CheckPixelCount(width, height, pixelCount))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<uint32_t> pixels(pixelCount, 0);
|
||||
if (!ReadPixels(file, pixels))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
char trailingByte = 0;
|
||||
file.read(&trailingByte, 1);
|
||||
if (file.gcount() != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
image = RenderData::Image(
|
||||
static_cast<int32_t>(width),
|
||||
static_cast<int32_t>(height),
|
||||
pixels);
|
||||
return image.is_valid();
|
||||
}
|
||||
|
||||
bool SpriteAssetLoader::Save(const std::string& path, const RenderData::Image& image)
|
||||
{
|
||||
if (!image.is_valid())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
std::ofstream file(path.c_str(), std::ios::binary);
|
||||
if (!file.good())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
file.write(SpriteMagic, sizeof(SpriteMagic));
|
||||
WriteU32LE(file, SpriteVersion);
|
||||
WriteU32LE(file, static_cast<uint32_t>(image.get_width()));
|
||||
WriteU32LE(file, static_cast<uint32_t>(image.get_height()));
|
||||
WriteU32LE(file, SpriteFormatRgba8888);
|
||||
WritePixels(file, image);
|
||||
return file.good();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
#pragma once
|
||||
#include <string>
|
||||
#include "Image.h"
|
||||
|
||||
namespace Asset
|
||||
{
|
||||
class SpriteAssetLoader
|
||||
{
|
||||
public:
|
||||
static const char* GetFileExtension() { return ".sprite"; }
|
||||
static bool Load(const std::string& path, RenderData::Image& image);
|
||||
static bool Save(const std::string& path, const RenderData::Image& image);
|
||||
};
|
||||
}
|
||||
|
|
@ -8,7 +8,7 @@ namespace
|
|||
static const char SpriteMagic[4] = { 'S', 'P', 'R', 'T' };
|
||||
static const size_t SpriteHeaderSize = 20;
|
||||
static const uint32_t SpriteVersion = 1;
|
||||
static const uint32_t SpriteFormatRgba8888 = 1;
|
||||
static const uint32_t SpriteFormatRgba5551 = 2;
|
||||
static const uint64_t MaxSpritePixels = 8192ull * 8192ull;
|
||||
|
||||
static bool ReadExact(std::ifstream& file, char* data, size_t size)
|
||||
|
|
@ -52,7 +52,7 @@ namespace
|
|||
|
||||
const uint64_t total = static_cast<uint64_t>(width) * static_cast<uint64_t>(height);
|
||||
if (total > MaxSpritePixels ||
|
||||
total > static_cast<uint64_t>(std::numeric_limits<size_t>::max() / sizeof(uint32_t)))
|
||||
total > static_cast<uint64_t>(std::numeric_limits<size_t>::max() / sizeof(uint16_t)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
|
@ -61,9 +61,9 @@ namespace
|
|||
return true;
|
||||
}
|
||||
|
||||
static bool ReadPixels(std::ifstream& file, std::vector<uint32_t>& pixels)
|
||||
static bool ReadPixels5551(std::ifstream& file, std::vector<uint16_t>& pixels)
|
||||
{
|
||||
std::vector<uint8_t> bytes(pixels.size() * sizeof(uint32_t), 0);
|
||||
std::vector<uint8_t> bytes(pixels.size() * sizeof(uint16_t), 0);
|
||||
if (!ReadExact(file, reinterpret_cast<char*>(bytes.data()), bytes.size()))
|
||||
{
|
||||
return false;
|
||||
|
|
@ -71,23 +71,28 @@ namespace
|
|||
|
||||
for (size_t i = 0; i < pixels.size(); ++i)
|
||||
{
|
||||
uint32_t value = 0;
|
||||
if (!ReadU32LE(bytes, i * sizeof(uint32_t), value))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
pixels[i] = value;
|
||||
pixels[i] = static_cast<uint16_t>(bytes[i * 2]) |
|
||||
(static_cast<uint16_t>(bytes[i * 2 + 1]) << 8);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void WritePixel5551(std::ofstream& file, uint16_t pixel)
|
||||
{
|
||||
const char bytes[2] = {
|
||||
static_cast<char>(pixel & 0xFF),
|
||||
static_cast<char>((pixel >> 8) & 0xFF)
|
||||
};
|
||||
file.write(bytes, sizeof(bytes));
|
||||
}
|
||||
}
|
||||
|
||||
namespace Asset
|
||||
{
|
||||
bool SpriteAssetLoader::Load(
|
||||
const std::string& path,
|
||||
std::vector<uint32_t>& pixels,
|
||||
std::vector<uint16_t>& pixels,
|
||||
RenderData::Image& image)
|
||||
{
|
||||
std::ifstream file(path.c_str(), std::ios::binary);
|
||||
|
|
@ -124,14 +129,14 @@ namespace Asset
|
|||
|
||||
size_t pixelCount = 0;
|
||||
if (version != SpriteVersion ||
|
||||
format != SpriteFormatRgba8888 ||
|
||||
format != SpriteFormatRgba5551 ||
|
||||
!CheckPixelCount(width, height, pixelCount))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<uint32_t> loadedPixels(pixelCount, 0);
|
||||
if (!ReadPixels(file, loadedPixels))
|
||||
std::vector<uint16_t> loadedPixels(pixelCount, 0);
|
||||
if (!ReadPixels5551(file, loadedPixels))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
|
@ -144,7 +149,11 @@ namespace Asset
|
|||
}
|
||||
|
||||
pixels.swap(loadedPixels);
|
||||
image = RenderData::Image(pixels.data(), static_cast<int32_t>(width), static_cast<int32_t>(height));
|
||||
image = RenderData::Image(
|
||||
pixels.data(),
|
||||
static_cast<int32_t>(width),
|
||||
static_cast<int32_t>(height),
|
||||
RenderData::PixelFormat::RGBA5551);
|
||||
return image.pixels != nullptr && image.width > 0 && image.height > 0;
|
||||
}
|
||||
|
||||
|
|
@ -171,10 +180,23 @@ namespace Asset
|
|||
WriteU32LE(file, SpriteVersion);
|
||||
WriteU32LE(file, static_cast<uint32_t>(image.width));
|
||||
WriteU32LE(file, static_cast<uint32_t>(image.height));
|
||||
WriteU32LE(file, SpriteFormatRgba8888);
|
||||
for (size_t i = 0; i < pixelCount; ++i)
|
||||
WriteU32LE(file, SpriteFormatRgba5551);
|
||||
|
||||
if (image.format == RenderData::PixelFormat::RGBA5551)
|
||||
{
|
||||
WriteU32LE(file, image.pixels[i]);
|
||||
const uint16_t* pixels16 = static_cast<const uint16_t*>(image.pixels);
|
||||
for (size_t i = 0; i < pixelCount; ++i)
|
||||
{
|
||||
WritePixel5551(file, pixels16[i]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const uint32_t* pixels32 = static_cast<const uint32_t*>(image.pixels);
|
||||
for (size_t i = 0; i < pixelCount; ++i)
|
||||
{
|
||||
WritePixel5551(file, RenderData::rgba8888_to_rgba5551(pixels32[i]));
|
||||
}
|
||||
}
|
||||
|
||||
return file.good();
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ namespace Asset
|
|||
public:
|
||||
static const char* GetFileExtension() { return ".sprite"; }
|
||||
|
||||
static bool Load(const std::string& path, std::vector<uint32_t>& pixels, RenderData::Image& image);
|
||||
static bool Load(const std::string& path, std::vector<uint16_t>& pixels, RenderData::Image& image);
|
||||
static bool Save(const std::string& path, const RenderData::Image& image);
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@
|
|||
|
||||
namespace Core
|
||||
{
|
||||
#ifdef USE_RGB565_BACKBUFFER
|
||||
typedef uint16_t FramePixel;
|
||||
inline uint16_t rgba_to_frame_pixel(uint32_t rgba)
|
||||
{
|
||||
|
|
@ -16,10 +15,6 @@ namespace Core
|
|||
const uint32_t b = (rgba >> 8) & 0xFFu;
|
||||
return static_cast<uint16_t>(((r & 0xF8u) << 8) | ((g & 0xFCu) << 3) | (b >> 3));
|
||||
}
|
||||
#else
|
||||
typedef uint32_t FramePixel;
|
||||
inline uint32_t rgba_to_frame_pixel(uint32_t rgba) { return rgba; }
|
||||
#endif
|
||||
|
||||
class FrameBuffer
|
||||
{
|
||||
|
|
|
|||
|
|
@ -124,25 +124,67 @@ namespace Core
|
|||
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;
|
||||
if (img.format == RenderData::PixelFormat::RGBA5551)
|
||||
{
|
||||
Core::FramePixel* dst = static_cast<Core::FramePixel*>(frameBuffer->get_buffer());
|
||||
const int32_t fb_w = frameBuffer->get_width();
|
||||
const uint16_t* src = static_cast<const uint16_t*>(img.pixels);
|
||||
|
||||
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 int32_t dst_y_abs = dst_y + sy;
|
||||
Core::FramePixel* dst_row = dst + dst_y_abs * fb_w;
|
||||
|
||||
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 uint16_t pixel = src[read_y * img_w + read_x];
|
||||
if (!RenderData::rgba5551_is_opaque(pixel)) continue;
|
||||
dst_row[dst_x + sx] = RenderData::rgba5551_to_rgb565(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 dst_y_abs = dst_y + dy_abs;
|
||||
Core::FramePixel* dst_row = dst + dst_y_abs * fb_w;
|
||||
|
||||
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 uint16_t pixel = src[read_y * img_w + read_x];
|
||||
if (!RenderData::rgba5551_is_opaque(pixel)) continue;
|
||||
dst_row[dst_x + dx_abs] = RenderData::rgba5551_to_rgb565(pixel);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const bool has_key = img.has_color_key;
|
||||
const uint32_t key = img.color_key;
|
||||
const uint32_t key = RenderData::rgba5551_to_rgba8888(img.color_key);
|
||||
const uint32_t* src32 = static_cast<const uint32_t*>(img.pixels);
|
||||
|
||||
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];
|
||||
|
||||
const uint32_t pixel = src32[read_y * img_w + read_x];
|
||||
if ((pixel & 0xFFu) == 0u) continue;
|
||||
if (has_key && pixel == key) continue;
|
||||
|
||||
frameBuffer->set_pixel_unsafe(dst_x + sx, dst_y_abs, pixel);
|
||||
}
|
||||
}
|
||||
|
|
@ -153,17 +195,15 @@ namespace Core
|
|||
{
|
||||
const int32_t sy = dy_abs / scale;
|
||||
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 + dy_abs;
|
||||
|
||||
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 uint32_t pixel = row[read_x];
|
||||
|
||||
const uint32_t pixel = src32[read_y * img_w + read_x];
|
||||
if ((pixel & 0xFFu) == 0u) continue;
|
||||
if (has_key && pixel == key) continue;
|
||||
|
||||
frameBuffer->set_pixel_unsafe(dst_x + dx_abs, dst_y_abs, pixel);
|
||||
}
|
||||
}
|
||||
|
|
@ -273,13 +313,14 @@ namespace Core
|
|||
|
||||
if (x0 >= x1 || y0 >= y1) return;
|
||||
|
||||
uint32_t* buf = static_cast<uint32_t*>(frameBuffer->get_buffer());
|
||||
const Core::FramePixel pixel = Core::rgba_to_frame_pixel(rgba);
|
||||
Core::FramePixel* buf = static_cast<Core::FramePixel*>(frameBuffer->get_buffer());
|
||||
for (int32_t row = y0; row < y1; ++row)
|
||||
{
|
||||
uint32_t* dst = buf + row * fb_w + x0;
|
||||
Core::FramePixel* dst = buf + row * fb_w + x0;
|
||||
for (int32_t i = 0; i < x1 - x0; ++i)
|
||||
{
|
||||
dst[i] = rgba;
|
||||
dst[i] = pixel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -293,7 +334,7 @@ namespace Core
|
|||
const int32_t cw = font.char_w;
|
||||
const int32_t ch = font.char_h;
|
||||
const int32_t atlas_w = font.atlas.width;
|
||||
const uint32_t* src = font.atlas.pixels;
|
||||
const uint32_t* src = static_cast<const uint32_t*>(font.atlas.pixels);
|
||||
const int32_t screen_w = frameBuffer->get_width();
|
||||
const int32_t screen_h = frameBuffer->get_height();
|
||||
|
||||
|
|
|
|||
|
|
@ -15,18 +15,18 @@ namespace Platform
|
|||
{
|
||||
namespace
|
||||
{
|
||||
inline uint16_t rgba_to_rgb565(uint32_t rgba)
|
||||
{
|
||||
const uint32_t r = (rgba >> 24) & 0xFFu;
|
||||
const uint32_t g = (rgba >> 16) & 0xFFu;
|
||||
const uint32_t b = (rgba >> 8) & 0xFFu;
|
||||
return static_cast<uint16_t>(((r & 0xF8u) << 8) | ((g & 0xFCu) << 3) | (b >> 3));
|
||||
}
|
||||
|
||||
inline uint32_t rgba_to_argb8888(uint32_t rgba)
|
||||
{
|
||||
return ((rgba >> 8) & 0x00FFFFFFu) | ((rgba & 0xFFu) << 24);
|
||||
}
|
||||
|
||||
inline uint32_t rgb565_to_rgba8888(uint16_t c)
|
||||
{
|
||||
uint32_t r = (c >> 11) & 0x1Fu;
|
||||
uint32_t g = (c >> 5) & 0x3Fu;
|
||||
uint32_t b = c & 0x1Fu;
|
||||
return (r << 27) | (r << 24) | (g << 18) | (g << 16) | (b << 11) | (b << 8) | 0xFFu;
|
||||
}
|
||||
}
|
||||
|
||||
bool FBDisplay::init(int w, int h)
|
||||
|
|
@ -96,7 +96,7 @@ namespace Platform
|
|||
if (!fb_mem || !framebuffer)
|
||||
return;
|
||||
|
||||
const uint32_t* src = static_cast<const uint32_t*>(framebuffer->get_buffer());
|
||||
const uint16_t* src = static_cast<const uint16_t*>(framebuffer->get_buffer());
|
||||
const int src_width = framebuffer->get_width();
|
||||
const int dst_width = std::min(src_width, static_cast<int>(vinfo.xres));
|
||||
const int dst_height = std::min(framebuffer->get_height(), static_cast<int>(vinfo.yres));
|
||||
|
|
@ -119,8 +119,6 @@ namespace Platform
|
|||
|
||||
if (is_rgb565)
|
||||
{
|
||||
#ifdef USE_RGB565_BACKBUFFER
|
||||
// backbuffer 内部已是 RGB565,直接行拷贝提交
|
||||
const uint8_t* src_bytes = static_cast<const uint8_t*>(framebuffer->get_buffer());
|
||||
if (static_cast<int>(finfo.line_length) == dst_width * 2)
|
||||
{
|
||||
|
|
@ -135,17 +133,6 @@ namespace Platform
|
|||
dst_width * 2);
|
||||
}
|
||||
}
|
||||
#else
|
||||
for (int y = 0; y < dst_height; ++y)
|
||||
{
|
||||
const uint32_t* src_row = src + y * src_width;
|
||||
uint16_t* dst_row = reinterpret_cast<uint16_t*>(fb_mem + y * finfo.line_length);
|
||||
for (int x = 0; x < dst_width; ++x)
|
||||
{
|
||||
dst_row[x] = rgba_to_rgb565(src_row[x]);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -153,19 +140,27 @@ namespace Platform
|
|||
{
|
||||
for (int y = 0; y < dst_height; ++y)
|
||||
{
|
||||
const uint32_t* src_row = src + y * src_width;
|
||||
const uint16_t* src_row = src + y * src_width;
|
||||
uint32_t* dst_row = reinterpret_cast<uint32_t*>(fb_mem + y * finfo.line_length);
|
||||
for (int x = 0; x < dst_width; ++x)
|
||||
{
|
||||
dst_row[x] = rgba_to_argb8888(src_row[x]);
|
||||
dst_row[x] = rgba_to_argb8888(rgb565_to_rgba8888(src_row[x]));
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_rgba8888 && dst_width == src_width && static_cast<int>(finfo.line_length) == dst_width * 4)
|
||||
if (is_rgba8888)
|
||||
{
|
||||
std::memcpy(fb_mem, src, static_cast<size_t>(dst_height) * static_cast<size_t>(dst_width) * sizeof(uint32_t));
|
||||
for (int y = 0; y < dst_height; ++y)
|
||||
{
|
||||
const uint16_t* src_row = src + y * src_width;
|
||||
uint32_t* dst_row = reinterpret_cast<uint32_t*>(fb_mem + y * finfo.line_length);
|
||||
for (int x = 0; x < dst_width; ++x)
|
||||
{
|
||||
dst_row[x] = rgb565_to_rgba8888(src_row[x]);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -173,7 +168,7 @@ namespace Platform
|
|||
{
|
||||
for (int x = 0; x < dst_width; ++x)
|
||||
{
|
||||
uint32_t pixel = convert_pixel(src[y * src_width + x]);
|
||||
uint32_t pixel = convert_pixel(rgb565_to_rgba8888(src[y * src_width + x]));
|
||||
uint8_t* dst = fb_mem + y * finfo.line_length + x * bytes_per_pixel;
|
||||
if (vinfo.bits_per_pixel == 32)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -42,13 +42,8 @@ namespace Platform
|
|||
return false;
|
||||
}
|
||||
|
||||
#ifdef USE_RGB565_BACKBUFFER
|
||||
const Uint32 sdl_pixel_format = SDL_PIXELFORMAT_RGB565;
|
||||
const int pixel_bytes = 2;
|
||||
#else
|
||||
const Uint32 sdl_pixel_format = SDL_PIXELFORMAT_RGBA8888;
|
||||
const int pixel_bytes = 4;
|
||||
#endif
|
||||
texture = SDL_CreateTexture(
|
||||
renderer,
|
||||
sdl_pixel_format,
|
||||
|
|
@ -67,11 +62,7 @@ namespace Platform
|
|||
|
||||
void SDLDisplay::present(const Core::FrameBuffer* framebuffer)
|
||||
{
|
||||
#ifdef USE_RGB565_BACKBUFFER
|
||||
const int pixel_bytes = 2;
|
||||
#else
|
||||
const int pixel_bytes = 4;
|
||||
#endif
|
||||
SDL_UpdateTexture(texture, nullptr, framebuffer->get_buffer(), width * pixel_bytes);
|
||||
SDL_RenderClear(renderer);
|
||||
SDL_RenderCopy(renderer, texture, nullptr, nullptr);
|
||||
|
|
|
|||
|
|
@ -4,25 +4,86 @@
|
|||
|
||||
namespace RenderData
|
||||
{
|
||||
enum class PixelFormat
|
||||
{
|
||||
RGBA8888,
|
||||
RGBA5551
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
inline uint32_t rgba5551_to_rgba8888(uint16_t c)
|
||||
{
|
||||
uint32_t r = (c >> 11) & 0x1Fu;
|
||||
uint32_t g = (c >> 6) & 0x1Fu;
|
||||
uint32_t b = (c >> 1) & 0x1Fu;
|
||||
uint32_t a = c & 0x1u;
|
||||
return (r << 27) | (r << 24) |
|
||||
(g << 19) | (g << 16) |
|
||||
(b << 11) | (b << 8) |
|
||||
(a ? 0xFFu : 0x00u);
|
||||
}
|
||||
|
||||
inline uint16_t rgba8888_to_rgba5551(uint32_t c)
|
||||
{
|
||||
uint32_t r = (c >> 24) & 0xFFu;
|
||||
uint32_t g = (c >> 16) & 0xFFu;
|
||||
uint32_t b = (c >> 8) & 0xFFu;
|
||||
uint32_t a = c & 0xFFu;
|
||||
return static_cast<uint16_t>(
|
||||
((r >> 3) << 11) |
|
||||
((g >> 3) << 6) |
|
||||
((b >> 3) << 1) |
|
||||
(a ? 1u : 0u));
|
||||
}
|
||||
|
||||
struct Image
|
||||
{
|
||||
const uint32_t* pixels;
|
||||
const void* pixels;
|
||||
int32_t width;
|
||||
int32_t height;
|
||||
uint32_t color_key;
|
||||
uint16_t color_key;
|
||||
bool has_color_key;
|
||||
PixelFormat format;
|
||||
|
||||
Image() : pixels(nullptr), width(0), height(0), color_key(0), has_color_key(false) {}
|
||||
Image()
|
||||
: pixels(nullptr), width(0), height(0),
|
||||
color_key(0), has_color_key(false),
|
||||
format(PixelFormat::RGBA8888) {}
|
||||
|
||||
Image(const uint32_t* pixels, int32_t width, int32_t height)
|
||||
: pixels(pixels), width(width), height(height), color_key(0), has_color_key(false) {}
|
||||
Image(const void* pixels, int32_t width, int32_t height,
|
||||
PixelFormat fmt = PixelFormat::RGBA8888)
|
||||
: pixels(pixels), width(width), height(height),
|
||||
color_key(0), has_color_key(false),
|
||||
format(fmt) {}
|
||||
|
||||
Image(const uint32_t* pixels, int32_t width, int32_t height, uint32_t color_key)
|
||||
: pixels(pixels), width(width), height(height), color_key(color_key), has_color_key(true) {}
|
||||
Image(const void* pixels, int32_t width, int32_t height,
|
||||
uint16_t color_key,
|
||||
PixelFormat fmt = PixelFormat::RGBA8888)
|
||||
: pixels(pixels), width(width), height(height),
|
||||
color_key(color_key), has_color_key(true),
|
||||
format(fmt) {}
|
||||
|
||||
uint32_t get_pixel(int32_t x, int32_t y) const
|
||||
{
|
||||
return pixels[y * width + x];
|
||||
if (format == PixelFormat::RGBA5551)
|
||||
{
|
||||
return rgba5551_to_rgba8888(
|
||||
static_cast<const uint16_t*>(pixels)[y * width + x]);
|
||||
}
|
||||
return static_cast<const uint32_t*>(pixels)[y * width + x];
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,118 @@
|
|||
#include "Display.h"
|
||||
#include "DrawContext.h"
|
||||
#include "FrameBuffer.h"
|
||||
#include "Image.h"
|
||||
#include "SpriteAssetLoader.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdio>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace
|
||||
{
|
||||
class CaptureDisplay : public Platform::IDisplay
|
||||
{
|
||||
public:
|
||||
const Core::FrameBuffer* framebuffer;
|
||||
|
||||
CaptureDisplay()
|
||||
: framebuffer(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
bool init(int, int) override { return true; }
|
||||
void present(const Core::FrameBuffer* fb) override { framebuffer = fb; }
|
||||
void poll_events(bool&) override {}
|
||||
void shutdown() override {}
|
||||
};
|
||||
|
||||
void TestFramePixelIsRgb565()
|
||||
{
|
||||
static_assert(sizeof(Core::FramePixel) == sizeof(uint16_t), "FramePixel must stay RGB565");
|
||||
assert(RenderData::rgba5551_to_rgb565(0xF801u) == 0xF800u);
|
||||
assert(RenderData::rgba5551_to_rgb565(0x07C1u) == 0x07E0u);
|
||||
assert(RenderData::rgba5551_to_rgb565(0x003Fu) == 0x001Fu);
|
||||
}
|
||||
|
||||
void TestFrameBufferStoresRgb565()
|
||||
{
|
||||
Core::FrameBuffer framebuffer(2, 1);
|
||||
framebuffer.clear(RenderData::Color(255, 0, 0, 255));
|
||||
|
||||
const Core::FramePixel* pixels = static_cast<const Core::FramePixel*>(framebuffer.get_buffer());
|
||||
assert(pixels[0] == 0xF800u);
|
||||
assert(pixels[1] == 0xF800u);
|
||||
}
|
||||
|
||||
void TestDrawSpriteUsesOneBitAlpha()
|
||||
{
|
||||
const uint16_t sprite_pixels[] = {
|
||||
0xF800u,
|
||||
0x07C1u
|
||||
};
|
||||
|
||||
Core::DrawContext ctx(2, 1);
|
||||
ctx.clear_color(RenderData::Color(0, 0, 255, 255));
|
||||
|
||||
RenderData::Image sprite(
|
||||
sprite_pixels,
|
||||
2,
|
||||
1,
|
||||
RenderData::PixelFormat::RGBA5551);
|
||||
ctx.draw_sprite(0, 0, sprite);
|
||||
|
||||
CaptureDisplay display;
|
||||
ctx.present(&display);
|
||||
assert(display.framebuffer != nullptr);
|
||||
|
||||
const Core::FramePixel* pixels =
|
||||
static_cast<const Core::FramePixel*>(display.framebuffer->get_buffer());
|
||||
assert(pixels[0] == 0x001Fu);
|
||||
assert(pixels[1] == 0x07E0u);
|
||||
}
|
||||
|
||||
void TestSpriteAssetLoaderRoundTripsRgba5551()
|
||||
{
|
||||
const std::string path = "render_pipeline_test.sprite";
|
||||
const uint16_t original_pixels[] = {
|
||||
0x0000u,
|
||||
0xF801u,
|
||||
0x07C1u,
|
||||
0x003Fu
|
||||
};
|
||||
|
||||
RenderData::Image original(
|
||||
original_pixels,
|
||||
2,
|
||||
2,
|
||||
RenderData::PixelFormat::RGBA5551);
|
||||
assert(Asset::SpriteAssetLoader::Save(path, original));
|
||||
|
||||
std::vector<uint16_t> loaded_pixels;
|
||||
RenderData::Image loaded;
|
||||
const bool loaded_ok = Asset::SpriteAssetLoader::Load(path, loaded_pixels, loaded);
|
||||
std::remove(path.c_str());
|
||||
|
||||
assert(loaded_ok);
|
||||
assert(loaded.format == RenderData::PixelFormat::RGBA5551);
|
||||
assert(loaded.width == 2);
|
||||
assert(loaded.height == 2);
|
||||
assert(loaded_pixels.size() == 4u);
|
||||
for (size_t i = 0; i < loaded_pixels.size(); ++i)
|
||||
{
|
||||
assert(loaded_pixels[i] == original_pixels[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
TestFramePixelIsRgb565();
|
||||
TestFrameBufferStoresRgb565();
|
||||
TestDrawSpriteUsesOneBitAlpha();
|
||||
TestSpriteAssetLoaderRoundTripsRgba5551();
|
||||
std::cout << "render_pipeline_tests: PASS\n";
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -1,13 +1,11 @@
|
|||
"""
|
||||
将 PNG 图片转换为 C 头文件(uint32_t 像素数组)。
|
||||
将 PNG 图片转换为 C 头文件(RGBA5551 / uint16_t 像素数组)。
|
||||
|
||||
用法:
|
||||
python tools/png_to_header.py input.png output.h [var_name]
|
||||
|
||||
像素格式: (R << 24) | (G << 16) | (B << 8) | A
|
||||
与 FrameBuffer 的 uint32_t 格式一致。
|
||||
|
||||
如果图片没有 alpha 通道,A 默认为 0xFF。
|
||||
像素格式: RGBA5551(5-bit R, 5-bit G, 5-bit B, 1-bit A)
|
||||
运行时 sprite 主路径只保留 1-bit 透明:A=0 跳过,A=1 覆写。
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
|
@ -23,18 +21,23 @@ def convert(input_path: str, output_path: str, var_name: str):
|
|||
|
||||
packed = []
|
||||
for r, g, b, a in pixels:
|
||||
packed.append((r << 24) | (g << 16) | (b << 8) | a)
|
||||
packed.append(
|
||||
((r >> 3) << 11)
|
||||
| ((g >> 3) << 6)
|
||||
| ((b >> 3) << 1)
|
||||
| (1 if a else 0)
|
||||
)
|
||||
|
||||
with open(output_path, "w", encoding="utf-8") as f:
|
||||
f.write("// Auto-generated by tools/png_to_header.py\n")
|
||||
f.write(f"#pragma once\n#include <cstdint>\n\n")
|
||||
f.write(f"static const int32_t {var_name}_width = {w};\n")
|
||||
f.write(f"static const int32_t {var_name}_height = {h};\n\n")
|
||||
f.write(f"static const uint32_t {var_name}_pixels[] = {{\n")
|
||||
f.write(f"static const uint16_t {var_name}_pixels[] = {{\n")
|
||||
|
||||
for i in range(0, len(packed), 16):
|
||||
chunk = packed[i : i + 16]
|
||||
line = ", ".join(f"0x{p:08X}" for p in chunk)
|
||||
line = ", ".join(f"0x{p:04X}" for p in chunk)
|
||||
f.write(f" {line},\n")
|
||||
|
||||
f.write("};\n")
|
||||
|
|
|
|||
Loading…
Reference in New Issue