diff --git a/src/openrct2/CMakeLists.txt b/src/openrct2/CMakeLists.txt index b7bfd67ba6..bc54c296c1 100644 --- a/src/openrct2/CMakeLists.txt +++ b/src/openrct2/CMakeLists.txt @@ -47,6 +47,8 @@ if (NOT DISABLE_NETWORK) find_package(OpenSSL 1.0.0 REQUIRED) endif () +find_package(benchmark) + if (NOT DISABLE_TTF) if (UNIX AND NOT APPLE AND NOT MSVC) PKG_CHECK_MODULES(FONTCONFIG REQUIRED fontconfig) @@ -73,6 +75,15 @@ project(${PROJECT} CXX) add_library(${PROJECT} ${OPENRCT2_CORE_SOURCES} ${OPENRCT2_CORE_MM_SOURCES} ${RCT2_SECTIONS}) set_target_properties(${PROJECT} PROPERTIES PREFIX "") +if (benchmark_FOUND) + message("Found Google benchmark, enabling support") + set_target_properties(${PROJECT} PROPERTIES COMPILE_DEFINITIONS USE_BENCHMARK) + target_link_libraries(${PROJECT} benchmark::benchmark) + target_include_directories(${PROJECT} PRIVATE ${benchmark_INCLUDE_DIRS}) +else () + message("Google benchmark not found, disabling support") +endif () + # Libraries if (STATIC) target_link_libraries(${PROJECT} ${JANSSON_STATIC_LIBRARIES} diff --git a/src/openrct2/cmdline/BenchSpriteSort.cpp b/src/openrct2/cmdline/BenchSpriteSort.cpp new file mode 100644 index 0000000000..325a0089d3 --- /dev/null +++ b/src/openrct2/cmdline/BenchSpriteSort.cpp @@ -0,0 +1,226 @@ +/***************************************************************************** + * Copyright (c) 2014-2019 OpenRCT2 developers + * + * For a complete list of all authors, please refer to contributors.md + * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2 + * + * OpenRCT2 is licensed under the GNU General Public License version 3. + *****************************************************************************/ + +#include "CommandLine.hpp" + +#ifdef USE_BENCHMARK + +# include "../Context.h" +# include "../Game.h" +# include "../Intro.h" +# include "../OpenRCT2.h" +# include "../audio/audio.h" +# include "../core/Console.hpp" +# include "../core/Imaging.h" +# include "../drawing/Drawing.h" +# include "../interface/Viewport.h" +# include "../localisation/Localisation.h" +# include "../paint/Paint.h" +# include "../platform/platform.h" +# include "../util/Util.h" +# include "../world/Climate.h" +# include "../world/Map.h" +# include "../world/Park.h" +# include "../world/Surface.h" + +# include +# include +# include +# include + +static void fixup_pointers(paint_session* s, size_t paint_session_entries, size_t paint_struct_entries, size_t quadrant_entries) +{ + for (size_t i = 0; i < paint_session_entries; i++) + { + for (size_t j = 0; j < paint_struct_entries; j++) + { + if (s[i].PaintStructs[j].basic.next_quadrant_ps == (paint_struct*)paint_struct_entries) + { + s[i].PaintStructs[j].basic.next_quadrant_ps = nullptr; + } + else + { + s[i].PaintStructs[j].basic.next_quadrant_ps = &s[i].PaintStructs + [(uintptr_t)s[i].PaintStructs[j].basic.next_quadrant_ps] + .basic; + } + } + for (size_t j = 0; j < quadrant_entries; j++) + { + if (s[i].Quadrants[j] == (paint_struct*)quadrant_entries) + { + s[i].Quadrants[j] = nullptr; + } + else + { + s[i].Quadrants[j] = &s[i].PaintStructs[(size_t)s[i].Quadrants[j]].basic; + } + } + } +} + +// This function is based on benchgfx_render_screenshots +static void BM_paint_session_arrange(benchmark::State& state) +{ + core_init(); + gOpenRCT2Headless = true; + auto context = OpenRCT2::CreateContext(); + std::vector sessions; + log_info("Starting..."); + if (context->Initialise()) + { + drawing_engine_init(); + if (!context->LoadParkFromFile("data.sv6")) + { + return; + } + + gIntroState = INTRO_STATE_NONE; + gScreenFlags = SCREEN_FLAGS_PLAYING; + + int32_t mapSize = gMapSize; + int32_t resolutionWidth = (mapSize * 32 * 2); + int32_t resolutionHeight = (mapSize * 32 * 1); + + resolutionWidth += 8; + resolutionHeight += 128; + + rct_viewport viewport; + viewport.x = 0; + viewport.y = 0; + viewport.width = resolutionWidth; + viewport.height = resolutionHeight; + viewport.view_width = viewport.width; + viewport.view_height = viewport.height; + viewport.var_11 = 0; + viewport.flags = 0; + + int32_t customX = (gMapSize / 2) * 32 + 16; + int32_t customY = (gMapSize / 2) * 32 + 16; + + int32_t x = 0, y = 0; + int32_t z = tile_element_height(customX, customY) & 0xFFFF; + x = customY - customX; + y = ((customX + customY) / 2) - z; + + viewport.view_x = x - ((viewport.view_width) / 2); + viewport.view_y = y - ((viewport.view_height) / 2); + viewport.zoom = 0; + gCurrentRotation = 0; + + // Ensure sprites appear regardless of rotation + reset_all_sprite_quadrant_placements(); + + rct_drawpixelinfo dpi; + dpi.x = 0; + dpi.y = 0; + dpi.width = resolutionWidth; + dpi.height = resolutionHeight; + dpi.pitch = 0; + dpi.bits = (uint8_t*)malloc(dpi.width * dpi.height); + + log_info("Obtaining sprite data..."); + viewport_render(&dpi, &viewport, 0, 0, viewport.width, viewport.height, &sessions); + + free(dpi.bits); + drawing_engine_dispose(); + } + log_info("Got %u paint sessions.", std::size(sessions)); + // Fixing up the pointers continuously is wasteful. Fix it up once for `sessions` and store a copy. + // Keep in mind we need bit-exact copy, as the lists use pointers. + // Once sorted, just restore the copy with the original fixed-up version. + paint_session* local_s = new paint_session[std::size(sessions)]; + fixup_pointers(&sessions[0], std::size(sessions), std::size(local_s->PaintStructs), std::size(local_s->Quadrants)); + std::copy_n(sessions.cbegin(), std::size(sessions), local_s); + for (auto _ : state) + { + state.PauseTiming(); + std::copy_n(local_s, std::size(sessions), sessions.begin()); + state.ResumeTiming(); + paint_session_arrange(&sessions[0]); + benchmark::DoNotOptimize(sessions); + } + state.SetItemsProcessed(state.iterations() * std::size(sessions)); + delete[] local_s; +} +BENCHMARK(BM_paint_session_arrange); + +// Provide some baseline performing similar things as the actual version of the benchmark does. +static void BM_baseline(benchmark::State& state) +{ + std::vector sessions(1); + paint_session* local_s = new paint_session[std::size(sessions)]; + std::copy_n(sessions.cbegin(), std::size(sessions), local_s); + for (auto _ : state) + { + state.PauseTiming(); + std::copy_n(local_s, std::size(sessions), sessions.begin()); + state.ResumeTiming(); + paint_session_arrange(local_s); + benchmark::DoNotOptimize(local_s); + } + state.SetItemsProcessed(state.iterations() * std::size(sessions)); + delete[] local_s; +} +BENCHMARK(BM_baseline); + +static int cmdline_for_bench_sprite_sort(int argc, const char** argv) +{ + // Google benchmark does stuff to argv. It doesn't modify the pointees, + // but it wants to reorder the pointers, so present a copy of them. + std::vector argv_for_benchmark; + // argv[0] is expected to contain the binary name. Don't bother. + argv_for_benchmark.push_back(nullptr); + for (int i = 0; i < argc; i++) + { + argv_for_benchmark.push_back((char*)argv[i]); + } + // Account for argv[0] + argc++; + ::benchmark::Initialize(&argc, &argv_for_benchmark[0]); + if (::benchmark::ReportUnrecognizedArguments(argc, &argv_for_benchmark[0])) + return -1; + ::benchmark::RunSpecifiedBenchmarks(); + return 0; +} + +static exitcode_t HandleBenchSpriteSort(CommandLineArgEnumerator* argEnumerator) +{ + const char** argv = (const char**)argEnumerator->GetArguments() + argEnumerator->GetIndex(); + int32_t argc = argEnumerator->GetCount() - argEnumerator->GetIndex(); + int32_t result = cmdline_for_bench_sprite_sort(argc, argv); + if (result < 0) + { + return EXITCODE_FAIL; + } + return EXITCODE_OK; +} + +#else +static exitcode_t HandleBenchSpriteSort(CommandLineArgEnumerator* argEnumerator) +{ + log_error("Sorry, Google benchmark not enabled in this build"); + return EXITCODE_FAIL; +} +#endif // USE_BENCHMARK + +const CommandLineCommand CommandLine::BenchSpriteSortCommands[]{ +#ifdef USE_BENCHMARK + DefineCommand( + "", + " [--benchmark_list_tests={true|false}] [--benchmark_filter=] [--benchmark_min_time=] " + "[--benchmark_repetitions=] [--benchmark_report_aggregates_only={true|false}] " + "[--benchmark_format=] [--benchmark_out=] [--benchmark_out_format=] " + "[--benchmark_color={auto|true|false}] [--benchmark_counters_tabular={true|false}] [--v=]", + nullptr, HandleBenchSpriteSort), + CommandTableEnd +#else + DefineCommand("", "*** SORRY NOT ENABLED IN THIS BUILD ***", nullptr, HandleBenchSpriteSort), CommandTableEnd +#endif // USE_BENCHMARK +}; diff --git a/src/openrct2/cmdline/CommandLine.hpp b/src/openrct2/cmdline/CommandLine.hpp index 6d7797086e..bba00a04cd 100644 --- a/src/openrct2/cmdline/CommandLine.hpp +++ b/src/openrct2/cmdline/CommandLine.hpp @@ -117,6 +117,7 @@ namespace CommandLine extern const CommandLineCommand ScreenshotCommands[]; extern const CommandLineCommand SpriteCommands[]; extern const CommandLineCommand BenchGfxCommands[]; + extern const CommandLineCommand BenchSpriteSortCommands[]; extern const CommandLineCommand SimulateCommands[]; extern const CommandLineExample RootExamples[]; diff --git a/src/openrct2/cmdline/RootCommands.cpp b/src/openrct2/cmdline/RootCommands.cpp index 5b866ef2c8..b2ade7507a 100644 --- a/src/openrct2/cmdline/RootCommands.cpp +++ b/src/openrct2/cmdline/RootCommands.cpp @@ -135,10 +135,11 @@ const CommandLineCommand CommandLine::RootCommands[] #endif // Sub-commands - DefineSubCommand("screenshot", CommandLine::ScreenshotCommands), - DefineSubCommand("sprite", CommandLine::SpriteCommands ), - DefineSubCommand("benchgfx", CommandLine::BenchGfxCommands ), - DefineSubCommand("simulate", CommandLine::SimulateCommands ), + DefineSubCommand("screenshot", CommandLine::ScreenshotCommands ), + DefineSubCommand("sprite", CommandLine::SpriteCommands ), + DefineSubCommand("benchgfx", CommandLine::BenchGfxCommands ), + DefineSubCommand("benchspritesort", CommandLine::BenchSpriteSortCommands ), + DefineSubCommand("simulate", CommandLine::SimulateCommands ), CommandTableEnd }; diff --git a/src/openrct2/interface/Viewport.cpp b/src/openrct2/interface/Viewport.cpp index d25bcc9e4b..f612b4ed36 100644 --- a/src/openrct2/interface/Viewport.cpp +++ b/src/openrct2/interface/Viewport.cpp @@ -60,7 +60,7 @@ static int16_t _interactionMapX; static int16_t _interactionMapY; static uint16_t _unk9AC154; -static void viewport_paint_column(rct_drawpixelinfo* dpi, uint32_t viewFlags); +static void viewport_paint_column(rct_drawpixelinfo* dpi, uint32_t viewFlags, std::vector* sessions); static void viewport_paint_weather_gloom(rct_drawpixelinfo* dpi); /** @@ -791,7 +791,9 @@ void viewport_update_smart_vehicle_follow(rct_window* window) * edi: dpi * ebp: bottom */ -void viewport_render(rct_drawpixelinfo* dpi, rct_viewport* viewport, int32_t left, int32_t top, int32_t right, int32_t bottom) +void viewport_render( + rct_drawpixelinfo* dpi, rct_viewport* viewport, int32_t left, int32_t top, int32_t right, int32_t bottom, + std::vector* sessions) { if (right <= viewport->x) return; @@ -821,7 +823,7 @@ void viewport_render(rct_drawpixelinfo* dpi, rct_viewport* viewport, int32_t lef top += viewport->view_y; bottom += viewport->view_y; - viewport_paint(viewport, dpi, left, top, right, bottom); + viewport_paint(viewport, dpi, left, top, right, bottom, sessions); #ifdef DEBUG_SHOW_DIRTY_BOX if (viewport != g_viewport_list) @@ -842,7 +844,9 @@ void viewport_render(rct_drawpixelinfo* dpi, rct_viewport* viewport, int32_t lef * edi: dpi * ebp: bottom */ -void viewport_paint(rct_viewport* viewport, rct_drawpixelinfo* dpi, int16_t left, int16_t top, int16_t right, int16_t bottom) +void viewport_paint( + rct_viewport* viewport, rct_drawpixelinfo* dpi, int16_t left, int16_t top, int16_t right, int16_t bottom, + std::vector* sessions) { uint32_t viewFlags = viewport->flags; uint16_t width = right - left; @@ -878,32 +882,37 @@ void viewport_paint(rct_viewport* viewport, rct_drawpixelinfo* dpi, int16_t left int16_t rightBorder = dpi1.x + dpi1.width; // Splits the area into 32 pixel columns and renders them - for (x = floor2(dpi1.x, 32); x < rightBorder; x += 32) + int16_t start_x = floor2(dpi1.x, 32); + if (sessions != nullptr) + { + sessions->reserve((rightBorder - start_x) / 32); + } + for (int16_t columnx = start_x; columnx < rightBorder; columnx += 32) { rct_drawpixelinfo dpi2 = dpi1; - if (x >= dpi2.x) + if (columnx >= dpi2.x) { - int16_t leftPitch = x - dpi2.x; + int16_t leftPitch = columnx - dpi2.x; dpi2.width -= leftPitch; dpi2.bits += leftPitch >> dpi2.zoom_level; dpi2.pitch += leftPitch >> dpi2.zoom_level; - dpi2.x = x; + dpi2.x = columnx; } int16_t paintRight = dpi2.x + dpi2.width; - if (paintRight >= x + 32) + if (paintRight >= columnx + 32) { - int16_t rightPitch = paintRight - x - 32; + int16_t rightPitch = paintRight - columnx - 32; paintRight -= rightPitch; dpi2.pitch += rightPitch >> dpi2.zoom_level; } dpi2.width = paintRight - dpi2.x; - viewport_paint_column(&dpi2, viewFlags); + viewport_paint_column(&dpi2, viewFlags, sessions); } } -static void viewport_paint_column(rct_drawpixelinfo* dpi, uint32_t viewFlags) +static void viewport_paint_column(rct_drawpixelinfo* dpi, uint32_t viewFlags, std::vector* sessions) { if (viewFlags & (VIEWPORT_FLAG_HIDE_VERTICAL | VIEWPORT_FLAG_HIDE_BASE | VIEWPORT_FLAG_UNDERGROUND_INSIDE | VIEWPORT_FLAG_CLIP_VIEW)) @@ -918,6 +927,23 @@ static void viewport_paint_column(rct_drawpixelinfo* dpi, uint32_t viewFlags) paint_session* session = paint_session_alloc(dpi, viewFlags); paint_session_generate(session); + // Perform a deep copy of the paint session, use relative offsets. + // This is done to extract the session for benchmark. + if (sessions != nullptr) + { + sessions->push_back(*session); + paint_session* session_copy = &sessions->at(sessions->size() - 1); + + // Mind the offset needs to be calculated against the original `session`, not `session_copy` + for (auto& ps : session_copy->PaintStructs) + { + ps.basic.next_quadrant_ps = (paint_struct*)(ps.basic.next_quadrant_ps ? int(ps.basic.next_quadrant_ps - &session->PaintStructs[0].basic) : std::size(session->PaintStructs)); + } + for (auto& quad : session_copy->Quadrants) + { + quad = (paint_struct*)(quad ? int(quad - &session->PaintStructs[0].basic) : std::size(session->Quadrants)); + } + } paint_session_arrange(session); paint_draw_structs(session); paint_session_free(session); diff --git a/src/openrct2/interface/Viewport.h b/src/openrct2/interface/Viewport.h index b0759eb2c7..20fd725939 100644 --- a/src/openrct2/interface/Viewport.h +++ b/src/openrct2/interface/Viewport.h @@ -13,6 +13,8 @@ #include "../world/Location.hpp" #include "Window.h" +#include + struct paint_session; struct paint_struct; struct rct_drawpixelinfo; @@ -127,8 +129,12 @@ void viewport_update_smart_sprite_follow(rct_window* window); void viewport_update_smart_guest_follow(rct_window* window, rct_peep* peep); void viewport_update_smart_staff_follow(rct_window* window, rct_peep* peep); void viewport_update_smart_vehicle_follow(rct_window* window); -void viewport_render(rct_drawpixelinfo* dpi, rct_viewport* viewport, int32_t left, int32_t top, int32_t right, int32_t bottom); -void viewport_paint(rct_viewport* viewport, rct_drawpixelinfo* dpi, int16_t left, int16_t top, int16_t right, int16_t bottom); +void viewport_render( + rct_drawpixelinfo* dpi, rct_viewport* viewport, int32_t left, int32_t top, int32_t right, int32_t bottom, + std::vector* sessions = nullptr); +void viewport_paint( + rct_viewport* viewport, rct_drawpixelinfo* dpi, int16_t left, int16_t top, int16_t right, int16_t bottom, + std::vector* sessions = nullptr); void viewport_adjust_for_map_height(int16_t* x, int16_t* y, int16_t* z);