修正板端性能测试误判并加速 framebuffer 提交

板端测试发现轻量 Demo 在未优化 ARM 构建下只有十几帧,但 Release 构建后可达到约 76 FPS。问题主要来自单配置 CMake 构建未默认启用 Release,以及 fb0 提交路径逐像素通用转换成本过高。

本次变更将单配置生成器默认构建类型设为 Release,避免 ARM/fb0 性能测试误用未优化构建;同时为 FBDisplay 增加 RGB565、ARGB8888/XRGB8888、RGBA8888 连续内存等常见格式快速路径。Demo 临时移除旋转正方体,保留 2D sprite、tilemap、FPS 和 Frame/Present 耗时显示,用于定位板端 framebuffer 提交瓶颈。

Constraint: IMX6U framebuffer 性能结论必须基于 Release 构建
Constraint: 核心代码保持 C++11 兼容,不引入新依赖
Rejected: 直接判断板子性能不足 | 未优化构建会严重放大逐像素循环成本
Rejected: 继续优化 3D 正方体路径 | 当前瓶颈已由 Frame/Present 计时证明主要在 fb0 present
Confidence: high
Scope-risk: moderate
Directive: 后续板端性能测试先确认 CMAKE_BUILD_TYPE=Release,再分析算法瓶颈
Directive: 2D-only 场景不要使用会清 depth buffer 的 DrawContext::clear()
Tested: cmake --build build-win --config Release
Tested: wsl bash -lc "cd /mnt/d/source/IMX6U-Game && cmake --build build-arm-fb"
Not-tested: 实机 framebuffer 像素格式以外的非常见 fb0 layout
This commit is contained in:
SepComet
2026-06-07 14:24:32 +08:00
parent a9bc9a59fb
commit d92b890528
13 changed files with 156 additions and 211 deletions
+13 -202
View File
@@ -5,17 +5,9 @@
#include <cstring>
#include <thread>
#include <chrono>
#include "Vector2.h"
#include "Vector3.h"
#include "Vector4.h"
#include "Matrix4x4.h"
#include "MathUtil.h"
#include "Color.h"
#include "Triangle.h"
#include "Camera.h"
#include <cstdlib>
#include "Timer.h"
#include "Vertex.h"
#include "DrawContext.h"
#include "test_sprite.h"
#include "font_atlas.h"
@@ -28,86 +20,9 @@
#include "SDLDisplay.h"
#endif
const int32_t width = 800;
const int32_t width = 1024;
const int32_t height = 600;
struct ProjectedVertex
{
Math::Vector3 screen;
bool visible = false;
};
struct CubeFace
{
std::array<int, 4> vertices;
};
struct CubeTriangle
{
std::array<int, 3> vertices;
};
static ProjectedVertex ProjectToScreen(
const Math::Vector3 &vertex,
const Math::Matrix4x4 &mvp,
const Math::Matrix4x4 &viewport)
{
using namespace Math;
const Vector4 clip = mvp * Vector4::Point(vertex);
if (std::abs(clip.w) < 1e-5f)
{
return {};
}
const float invW = 1.0f / clip.w;
const float ndcX = clip.x * invW;
const float ndcY = clip.y * invW;
const float ndcZ = clip.z * invW;
if (ndcX < -1.0f || ndcX > 1.0f || ndcY < -1.0f || ndcY > 1.0f || ndcZ < -1.0f || ndcZ > 1.0f)
{
return {};
}
const Vector4 screen = viewport * Vector4(ndcX, ndcY, ndcZ, 1.0f);
ProjectedVertex result;
result.screen = Vector3(screen.x, screen.y, screen.z);
result.visible = true;
return result;
}
static bool IsFaceVisible(const CubeFace &face, const std::array<Math::Vector3, 8> &viewSpaceVertices)
{
using namespace Math;
const Vector3 &v0 = viewSpaceVertices[face.vertices[0]];
const Vector3 &v1 = viewSpaceVertices[face.vertices[1]];
const Vector3 &v2 = viewSpaceVertices[face.vertices[2]];
const Vector3 faceNormal = (v1 - v0).cross(v2 - v0);
const Vector3 faceCenter =
(viewSpaceVertices[face.vertices[0]] +
viewSpaceVertices[face.vertices[1]] +
viewSpaceVertices[face.vertices[2]] +
viewSpaceVertices[face.vertices[3]]) /
4.0f;
return faceNormal.dot(faceCenter) > 0.0f;
}
static bool IsTriangleVisible(const CubeTriangle &triangle, const std::array<Math::Vector3, 8> &viewSpaceVertices)
{
using namespace Math;
const Vector3 &v0 = viewSpaceVertices[triangle.vertices[0]];
const Vector3 &v1 = viewSpaceVertices[triangle.vertices[1]];
const Vector3 &v2 = viewSpaceVertices[triangle.vertices[2]];
const Vector3 faceNormal = (v1 - v0).cross(v2 - v0);
const Vector3 faceCenter = (v0 + v1 + v2) / 3.0f;
return faceNormal.dot(faceCenter) > 0.0f;
}
static void PrintUsage(const char *program_name)
{
std::cout
@@ -227,142 +142,33 @@ int main(int argc, char *argv[])
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;
camera.transform.position = Math::Vector3(0.0f, 0.0f, 3.0f);
camera.transform.rotation = Math::Vector3(0.0f, 3.1415926535f, 0.0f);
const std::array<Math::Vector3, 8> cubeVertices = {
Math::Vector3(-0.5f, -0.5f, -0.5f),
Math::Vector3(0.5f, -0.5f, -0.5f),
Math::Vector3(0.5f, 0.5f, -0.5f),
Math::Vector3(-0.5f, 0.5f, -0.5f),
Math::Vector3(-0.5f, -0.5f, 0.5f),
Math::Vector3(0.5f, -0.5f, 0.5f),
Math::Vector3(0.5f, 0.5f, 0.5f),
Math::Vector3(-0.5f, 0.5f, 0.5f)};
const std::array<CubeFace, 6> cubeFaces = {
CubeFace{{0, 3, 2, 1}},
CubeFace{{4, 5, 6, 7}},
CubeFace{{0, 4, 7, 3}},
CubeFace{{1, 2, 6, 5}},
CubeFace{{0, 1, 5, 4}},
CubeFace{{3, 7, 6, 2}}};
const std::array<CubeTriangle, 12> cubeTriangles = {
CubeTriangle{{0, 3, 2}}, CubeTriangle{{0, 2, 1}},
CubeTriangle{{4, 5, 6}}, CubeTriangle{{4, 6, 7}},
CubeTriangle{{0, 4, 7}}, CubeTriangle{{0, 7, 3}},
CubeTriangle{{1, 2, 6}}, CubeTriangle{{1, 6, 5}},
CubeTriangle{{0, 1, 5}}, CubeTriangle{{0, 5, 4}},
CubeTriangle{{3, 7, 6}}, CubeTriangle{{3, 6, 2}}};
const RenderData::Color clearColor(18, 18, 24, 255);
const RenderData::Color cubeColor(240, 240, 240, 255);
const RenderData::Color fpsColor(0, 255, 80, 255);
const RenderData::Color fpsBg(0, 0, 0, 200);
const float aspectRatio = static_cast<float>(width) / static_cast<float>(height);
int32_t fps = 0;
int32_t frame_count = 0;
uint32_t last_fps_time = 0;
char fps_text[32];
char perf_text[64];
uint32_t last_frame_ms = 0;
uint32_t last_present_ms = 0;
bool isRunning = true;
uint32_t animation_time_ms = 0;
while (isRunning)
{
timer.begin_frame(time_source.get_time_ms());
const uint32_t fixed_delta_ms = timer.fixed_delta_ms();
animation_time_ms += fixed_delta_ms;
const uint32_t frame_start_ms = time_source.get_time_ms();
timer.begin_frame(frame_start_ms);
display->poll_events(isRunning);
ctx.clear(clearColor);
const float animation_time = static_cast<float>(animation_time_ms) / 1000.0f;
const Math::Matrix4x4 model =
Math::MathUtil::get_rotation_matrix_y(animation_time) *
Math::MathUtil::get_rotation_matrix_x(static_cast<float>(animation_time_ms * 6u) / 10000.0f);
const Math::Matrix4x4 view = camera.get_view_matrix();
const Math::Matrix4x4 modelView = view * model;
const Math::Matrix4x4 projection = camera.get_perspective_projection_matrix(aspectRatio);
const Math::Matrix4x4 viewport = camera.get_viewport_matrix(static_cast<float>(width), static_cast<float>(height));
const Math::Matrix4x4 mvp = projection * modelView;
std::array<Math::Vector3, 8> viewSpaceVertices;
std::array<ProjectedVertex, 8> projectedVertices;
for (size_t i = 0; i < cubeVertices.size(); ++i)
{
viewSpaceVertices[i] = (modelView * Math::Vector4::Point(cubeVertices[i])).to_vector3();
projectedVertices[i] = ProjectToScreen(cubeVertices[i], mvp, viewport);
}
std::array<bool, 6> visibleFaces = {};
for (size_t faceIndex = 0; faceIndex < cubeFaces.size(); ++faceIndex)
{
visibleFaces[faceIndex] = IsFaceVisible(cubeFaces[faceIndex], viewSpaceVertices);
}
std::array<RenderData::Triangle, 12> drawTriangles;
size_t drawCommandCount = 0;
for (const CubeTriangle &cubeTriangle : cubeTriangles)
{
if (!IsTriangleVisible(cubeTriangle, viewSpaceVertices))
{
continue;
}
const ProjectedVertex &v0 = projectedVertices[cubeTriangle.vertices[0]];
const ProjectedVertex &v1 = projectedVertices[cubeTriangle.vertices[1]];
const ProjectedVertex &v2 = projectedVertices[cubeTriangle.vertices[2]];
if (!v0.visible || !v1.visible || !v2.visible)
{
continue;
}
drawTriangles[drawCommandCount++] =
RenderData::Triangle(
Scene::Vertex(v0.screen),
Scene::Vertex(v1.screen),
Scene::Vertex(v2.screen));
}
for (size_t i = 0; i < drawCommandCount; ++i)
{
ctx.draw_triangle(drawTriangles[i], cubeColor);
}
for (size_t faceIndex = 0; faceIndex < cubeFaces.size(); ++faceIndex)
{
if (!visibleFaces[faceIndex])
{
continue;
}
const CubeFace &face = cubeFaces[faceIndex];
for (size_t edgeOffset = 0; edgeOffset < face.vertices.size(); ++edgeOffset)
{
const int startIndex = face.vertices[edgeOffset];
const int endIndex = face.vertices[(edgeOffset + 1) % face.vertices.size()];
const ProjectedVertex &start = projectedVertices[startIndex];
const ProjectedVertex &end = projectedVertices[endIndex];
if (!start.visible || !end.visible)
{
continue;
}
ctx.draw_line(
Math::Vector2(start.screen.x, start.screen.y).to_vector2Int(),
Math::Vector2(end.screen.x, end.screen.y).to_vector2Int(),
clearColor);
}
}
ctx.clear_color(clearColor);
// sprite 测试
ctx.draw_sprite(10, 10, sprite_img);
ctx.draw_sprite_region_ex(30, 10, sprite_region, 2, false, 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>(animation_time_ms / 20u) % 32, 0);
ctx.draw_tilemap(testTilemap, 650, 500, 96, 48, static_cast<int32_t>(frame_start_ms / 20u) % 32, 0);
// FPS 计数
++frame_count;
@@ -375,8 +181,13 @@ int main(int argc, char *argv[])
}
std::snprintf(fps_text, sizeof(fps_text), "FPS: %d", fps);
ctx.draw_text(font, 4, 4, fpsColor, fpsBg, fps_text);
std::snprintf(perf_text, sizeof(perf_text), "Frame:%ums Present:%ums", last_frame_ms, last_present_ms);
ctx.draw_text(font, 4, 4 + font.char_h, fpsColor, fpsBg, perf_text);
const uint32_t present_start_ms = time_source.get_time_ms();
ctx.present(display);
last_present_ms = time_source.get_time_ms() - present_start_ms;
last_frame_ms = time_source.get_time_ms() - frame_start_ms;
SleepRemainingFrameTime(timer, time_source);
}
+1
View File
@@ -3,6 +3,7 @@
#include <vector>
#include "Vector2.h"
#include <cmath>
#include <cstddef>
namespace Core
{
+1 -1
View File
@@ -18,6 +18,6 @@ namespace Core
}
// Row-major layout with y = 0 on the first row, matching a top-left screen origin.
size_t index = static_cast<size_t>(y) * width + x;
buffer.at(index) = color;
buffer[index] = color;
}
}
+2 -1
View File
@@ -1,6 +1,7 @@
#pragma once
#include "Color.h"
#include "Vector2.h"
#include <cstddef>
#include <cstdint>
#include <vector>
@@ -43,4 +44,4 @@ namespace Core
void set_pixel(const int32_t x, const int32_t y, const uint32_t color);
};
}
}
+5
View File
@@ -40,6 +40,11 @@ namespace Gfx
depthBuffer->clear();
}
void DrawContext::clear_color(const RenderData::Color& color)
{
frameBuffer->clear(color);
}
void DrawContext::clear_depth()
{
depthBuffer->clear();
+1
View File
@@ -46,6 +46,7 @@ namespace Gfx
int32_t get_height() const;
void clear(const RenderData::Color& color);
void clear_color(const RenderData::Color& color);
void clear_depth();
void draw_line(const Math::Vector2Int& from, const Math::Vector2Int& to, const RenderData::Color& color);
+1
View File
@@ -1,4 +1,5 @@
#pragma once
#include <cstddef>
#include <cmath>
#include "Vector3.h"
#include "Vector4.h"
+70 -4
View File
@@ -13,6 +13,22 @@
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);
}
}
bool FBDisplay::init(int w, int h)
{
width = w;
@@ -81,15 +97,65 @@ namespace Platform
return;
const uint32_t* src = static_cast<const uint32_t*>(framebuffer->get_buffer());
int dst_width = std::min(width, static_cast<int>(vinfo.xres));
int dst_height = std::min(height, static_cast<int>(vinfo.yres));
int bytes_per_pixel = vinfo.bits_per_pixel / 8;
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));
const int bytes_per_pixel = vinfo.bits_per_pixel / 8;
const bool is_rgb565 =
vinfo.bits_per_pixel == 16 &&
vinfo.red.offset == 11 && vinfo.red.length == 5 &&
vinfo.green.offset == 5 && vinfo.green.length == 6 &&
vinfo.blue.offset == 0 && vinfo.blue.length == 5;
const bool is_argb8888 =
vinfo.bits_per_pixel == 32 &&
vinfo.red.offset == 16 && vinfo.red.length == 8 &&
vinfo.green.offset == 8 && vinfo.green.length == 8 &&
vinfo.blue.offset == 0 && vinfo.blue.length == 8;
const bool is_rgba8888 =
vinfo.bits_per_pixel == 32 &&
vinfo.red.offset == 24 && vinfo.red.length == 8 &&
vinfo.green.offset == 16 && vinfo.green.length == 8 &&
vinfo.blue.offset == 8 && vinfo.blue.length == 8;
if (is_rgb565)
{
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]);
}
}
return;
}
if (is_argb8888)
{
for (int y = 0; y < dst_height; ++y)
{
const uint32_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]);
}
}
return;
}
if (is_rgba8888 && dst_width == src_width && static_cast<int>(finfo.line_length) == dst_width * 4)
{
std::memcpy(fb_mem, src, static_cast<size_t>(dst_height) * static_cast<size_t>(dst_width) * sizeof(uint32_t));
return;
}
for (int y = 0; y < dst_height; ++y)
{
for (int x = 0; x < dst_width; ++x)
{
uint32_t pixel = convert_pixel(src[y * width + x]);
uint32_t pixel = convert_pixel(src[y * src_width + x]);
uint8_t* dst = fb_mem + y * finfo.line_length + x * bytes_per_pixel;
if (vinfo.bits_per_pixel == 32)
{