#include #include #include #include #include #include #include #include #include #include "Color.h" #include #include "Timer.h" #include "DrawContext.h" #include "Image.h" #include "TimeSource.h" #include "Timer.h" #include "font_atlas.h" #include "test_sprite.h" #ifdef USE_FRAMEBUFFER #include "FBDisplay.h" #else #include "SDLDisplay.h" #endif const int32_t width = 1024; const int32_t height = 600; static void PrintUsage(const char *program_name) { std::cout << "Usage: " << program_name << " [--fps 30|45|60]\n" << " " << program_name << " [--fps=30|45|60]\n"; } struct ProgramOptions { uint32_t target_fps; bool show_help; ProgramOptions() : target_fps(Core::Timer::DefaultFps), show_help(false) { } }; static Platform::IDisplay* CreateDisplay() { #ifdef USE_FRAMEBUFFER return new Platform::FBDisplay(); #else return new Platform::SDLDisplay(); #endif } static void PrintUsage(const char* program_name) { std::cout << "Usage: " << program_name << " [--fps 30|45|60]\n" << " " << program_name << " [--fps=30|45|60]\n"; } static ProgramOptions ParseProgramOptions(int argc, char* argv[]) { ProgramOptions options; for (int i = 1; i < argc; ++i) { const char* arg = argv[i]; const char* fps_value = nullptr; if (std::strcmp(arg, "--help") == 0 || std::strcmp(arg, "-h") == 0) { options.show_help = true; return options; } else if (std::strcmp(arg, "--fps") == 0) { if (i + 1 < argc) { fps_value = argv[++i]; } else { std::cerr << "Missing value for --fps, falling back to 30. Supported: 30, 45, 60.\n"; } } else if (std::strncmp(arg, "--fps=", 6) == 0) { fps_value = arg + 6; } if (fps_value != nullptr) { const uint32_t parsed_fps = static_cast(std::strtoul(fps_value, nullptr, 10)); if (Core::Timer::is_supported_fps(parsed_fps)) { options.target_fps = parsed_fps; } else { std::cerr << "Unsupported FPS '" << fps_value << "', falling back to 30. Supported: 30, 45, 60.\n"; options.target_fps = Core::Timer::DefaultFps; } } } return options; } static void SleepRemainingFrameTime(const Core::Timer& timer, const Platform::ITimeSource& time_source) { const uint32_t sleep_ms = timer.remaining_frame_ms(time_source.get_time_ms()); if (sleep_ms > 0u) { std::this_thread::sleep_for(std::chrono::milliseconds(sleep_ms)); } } } int main(int argc, char* argv[]) { const ProgramOptions options = ParseProgramOptions(argc, argv); if (options.show_help) { PrintUsage(argv[0]); return 0; } Platform::IDisplay* display = CreateDisplay(); if (!display->init(DemoWidth, DemoHeight)) { delete display; return -1; } Core::DrawContext ctx(DemoWidth, DemoHeight); Core::Timer timer(options.target_fps); Platform::SteadyTimeSource time_source; RenderData::BitmapFont font; font.atlas = RenderData::Image(font_atlas_pixels, font_atlas_width, font_atlas_height); 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::SpriteRegion sprite_region(&sprite_img, 0, 0, sprite_img.width, sprite_img.height); const std::array 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); const RenderData::Color clearColor(18, 18, 24, 255); const RenderData::Color fpsColor(0, 255, 80, 255); const RenderData::Color fpsBg(0, 0, 0, 200); int32_t fps = 0; int32_t frame_count = 0; uint32_t last_fps_time = 0; uint32_t animation_time_ms = 0; char fps_text[32]; char perf_text[64]; uint32_t last_frame_ms = 0; uint32_t last_present_ms = 0; bool isRunning = true; while (isRunning) { const uint32_t frame_start_ms = time_source.get_time_ms(); timer.begin_frame(frame_start_ms); display->poll_events(isRunning); 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(frame_start_ms / 20u) % 32, 0); // FPS 计数 ++frame_count; const uint32_t now = time_source.get_time_ms(); if (now - last_fps_time >= 1000u) { fps = frame_count; frame_count = 0; last_fps_time = now; } 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); } display->shutdown(); delete display; return 0; }