From 7154e85c0ab0fa297f9eb2a756ba8752d0361d23 Mon Sep 17 00:00:00 2001 From: Matt <5415177+ZehMatt@users.noreply.github.com> Date: Sat, 7 Dec 2024 14:08:00 +0200 Subject: [PATCH] Add stable paint sort (as a debug option) --- data/language/en-GB.txt | 1 + distribution/changelog.txt | 1 + src/openrct2-ui/UiStringIds.h | 1 + src/openrct2-ui/windows/DebugPaint.cpp | 10 ++- src/openrct2/paint/Paint.cpp | 115 ++++++++++++++++++++++--- src/openrct2/paint/Paint.h | 1 + 6 files changed, 117 insertions(+), 12 deletions(-) diff --git a/data/language/en-GB.txt b/data/language/en-GB.txt index b0a4e3afaf..ccea05dde4 100644 --- a/data/language/en-GB.txt +++ b/data/language/en-GB.txt @@ -3785,3 +3785,4 @@ STR_6706 :{WINDOW_COLOUR_2}Current image file: {BLACK}{STRING} STR_6707 :(none selected) STR_6708 :Smooth Strength STR_6709 :Enter Smooth Strength between {COMMA16} and {COMMA16} +STR_6710 :Stable sort diff --git a/distribution/changelog.txt b/distribution/changelog.txt index 627598ac12..f28ee9bdd4 100644 --- a/distribution/changelog.txt +++ b/distribution/changelog.txt @@ -5,6 +5,7 @@ - Improved: [#23051] Add large sloped turns and new inversions to the Twister, Vertical Drop, Hyper and Flying Roller Coasters. - Improved: [#23123] Improve sorting of roller coasters in build new ride menu. - Improved: [#23211] Add boosters to classic wooden roller coaster (cheats only). +- Improved: [#23229] Add debug option for making the sprite sorting algorithm stable. - Improved: [#23233] Add diagonal booster to LSM Launched Coaster. - Fix: [#20070, #22972] Missing and mismatched flat and sloped footpaths on several scenarios. - Fix: [#22726] ‘Force park rating’ cheat is not saved with the park. diff --git a/src/openrct2-ui/UiStringIds.h b/src/openrct2-ui/UiStringIds.h index 8cd4d4f85d..e706b417a4 100644 --- a/src/openrct2-ui/UiStringIds.h +++ b/src/openrct2-ui/UiStringIds.h @@ -455,6 +455,7 @@ namespace OpenRCT2 STR_DEBUG_PAINT_SHOW_DIRTY_VISUALS = 6144, STR_DEBUG_PAINT_SHOW_SEGMENT_HEIGHTS = 5901, STR_DEBUG_PAINT_SHOW_WIDE_PATHS = 6261, + STR_DEBUG_PAINT_STABLE_SORT = 6710, // Window: DemolishRidePrompt STR_DEMOLISH = 994, diff --git a/src/openrct2-ui/windows/DebugPaint.cpp b/src/openrct2-ui/windows/DebugPaint.cpp index c90559fbbc..92b64dac6a 100644 --- a/src/openrct2-ui/windows/DebugPaint.cpp +++ b/src/openrct2-ui/windows/DebugPaint.cpp @@ -27,10 +27,11 @@ namespace OpenRCT2::Ui::Windows WIDX_TOGGLE_SHOW_SEGMENT_HEIGHTS, WIDX_TOGGLE_SHOW_BOUND_BOXES, WIDX_TOGGLE_SHOW_DIRTY_VISUALS, + WIDX_TOGGLE_STABLE_PAINT_SORT, }; constexpr int32_t WINDOW_WIDTH = 200; - constexpr int32_t WINDOW_HEIGHT = 8 + 15 + 15 + 15 + 15 + 11 + 8; + constexpr int32_t WINDOW_HEIGHT = 8 + (15 * 6) + 8; // clang-format off static Widget window_debug_paint_widgets[] = { @@ -40,6 +41,7 @@ namespace OpenRCT2::Ui::Windows MakeWidget({8, 8 + 15 * 2}, { 185, 12}, WindowWidgetType::Checkbox, WindowColour::Secondary, STR_DEBUG_PAINT_SHOW_SEGMENT_HEIGHTS), MakeWidget({8, 8 + 15 * 3}, { 185, 12}, WindowWidgetType::Checkbox, WindowColour::Secondary, STR_DEBUG_PAINT_SHOW_BOUND_BOXES ), MakeWidget({8, 8 + 15 * 4}, { 185, 12}, WindowWidgetType::Checkbox, WindowColour::Secondary, STR_DEBUG_PAINT_SHOW_DIRTY_VISUALS ), + MakeWidget({8, 8 + 15 * 5}, { 185, 12}, WindowWidgetType::Checkbox, WindowColour::Secondary, STR_DEBUG_PAINT_STABLE_SORT ), kWidgetsEnd, }; // clang-format on @@ -91,6 +93,11 @@ namespace OpenRCT2::Ui::Windows gShowDirtyVisuals = !gShowDirtyVisuals; GfxInvalidateScreen(); break; + + case WIDX_TOGGLE_STABLE_PAINT_SORT: + gPaintStableSort = !gPaintStableSort; + GfxInvalidateScreen(); + break; } } @@ -136,6 +143,7 @@ namespace OpenRCT2::Ui::Windows WidgetSetCheckboxValue(*this, WIDX_TOGGLE_SHOW_SEGMENT_HEIGHTS, gShowSupportSegmentHeights); WidgetSetCheckboxValue(*this, WIDX_TOGGLE_SHOW_BOUND_BOXES, gPaintBoundingBoxes); WidgetSetCheckboxValue(*this, WIDX_TOGGLE_SHOW_DIRTY_VISUALS, gShowDirtyVisuals); + WidgetSetCheckboxValue(*this, WIDX_TOGGLE_STABLE_PAINT_SORT, gPaintStableSort); } void OnDraw(DrawPixelInfo& dpi) override diff --git a/src/openrct2/paint/Paint.cpp b/src/openrct2/paint/Paint.cpp index dcbd52ef9e..4b466ffab0 100644 --- a/src/openrct2/paint/Paint.cpp +++ b/src/openrct2/paint/Paint.cpp @@ -57,6 +57,7 @@ static constexpr uint8_t BoundBoxDebugColours[] = { bool gShowDirtyVisuals; bool gPaintBoundingBoxes; bool gPaintBlockedTiles; +bool gPaintStableSort; static void PaintAttachedPS(DrawPixelInfo& dpi, PaintStruct* ps, uint32_t viewFlags); static void PaintPSImageWithBoundingBoxes(PaintSession& session, PaintStruct* ps, ImageId imageId, int32_t x, int32_t y); @@ -403,13 +404,14 @@ static std::pair PaintStructsGetNextPending(PaintStr // Re-orders all nodes after the specified child node and marks the child node as traversed. The resulting // order of the children is the depth based on rotation and dimensions of the bounding box. template -static void PaintStructsSortQuadrant(PaintStruct* parent, PaintStruct* child) +static void PaintStructsSortQuadrantLegacy(PaintStruct* parent, PaintStruct* child) { // Mark visited. child->SortFlags &= ~PaintSortFlags::PendingVisit; // Compare all the children below the first child and move them up in the list if they intersect. const PaintStructBoundBox& initialBBox = child->Bounds; + for (;;) { auto* ps = child; @@ -443,7 +445,78 @@ static void PaintStructsSortQuadrant(PaintStruct* parent, PaintStruct* child) } } +// Re-orders all nodes after the specified child node and marks the child node as traversed. The resulting +// order of the children is the depth based on rotation and dimensions of the bounding box. template +static void PaintStructsSortQuadrantStable(PaintStruct* parent, PaintStruct* child) +{ + // Mark visited. + child->SortFlags &= ~PaintSortFlags::PendingVisit; + + // Compare all the children below the first child and move them up in the list if they intersect. + const PaintStructBoundBox& initialBBox = child->Bounds; + + // Create a temporary list to collect sorted nodes in stable order. + PaintStruct* sortedHead = nullptr; + PaintStruct* sortedTail = nullptr; + + // Traverse the list and reorder based on intersection. + for (;;) + { + PaintStruct* next = child->NextQuadrantEntry; + + if (next != nullptr) + { + PREFETCH(&next->Bounds); + } + + // Stop if at the end of the list or outside the quadrant range. + if (next == nullptr || next->SortFlags & PaintSortFlags::OutsideQuadrant) + { + break; + } + + // Ignore nodes that are not neighbors. + if (!(next->SortFlags & PaintSortFlags::Neighbour)) + { + child = next; + continue; + } + + // Detach the current node from the list if it intersects. + if (CheckBoundingBox(initialBBox, next->Bounds)) + { + child->NextQuadrantEntry = next->NextQuadrantEntry; + + if (sortedHead == nullptr) + { + sortedHead = next; + sortedTail = next; + next->NextQuadrantEntry = nullptr; + } + else + { + sortedTail->NextQuadrantEntry = next; + sortedTail = next; + next->NextQuadrantEntry = nullptr; + } + } + else + { + child = next; + } + } + + // Merge the sorted list back into the main list after parent. + if (sortedHead != nullptr) + { + PaintStruct* originalNext = parent->NextQuadrantEntry; + parent->NextQuadrantEntry = sortedHead; + sortedTail->NextQuadrantEntry = originalNext; + } +} + +template static PaintStruct* PaintArrangeStructsHelperRotation(PaintStruct* psQuadrantEntry, uint16_t quadrantIndex, uint8_t flag) { // We keep track of the first node in the quadrant so the next call with a higher quadrant index @@ -464,7 +537,15 @@ static PaintStruct* PaintArrangeStructsHelperRotation(PaintStruct* psQuadrantEnt break; } - PaintStructsSortQuadrant(parent, child); + if constexpr (TStableSort) + { + PaintStructsSortQuadrantStable(parent, child); + } + else + { + PaintStructsSortQuadrantLegacy(parent, child); + } + ps = parent; } @@ -496,7 +577,7 @@ static void PaintStructsLinkQuadrants(PaintSessionCore& session, PaintStruct& ps } while (++quadrantIndex <= session.QuadrantFrontIndex); } -template +template static void PaintSessionArrangeImpl(PaintSessionCore& session) { uint32_t quadrantIndex = session.QuadrantBackIndex; @@ -511,12 +592,13 @@ static void PaintSessionArrangeImpl(PaintSessionCore& session) PaintStruct psHead{}; PaintStructsLinkQuadrants(session, psHead); - PaintStruct* psNextQuadrant = PaintArrangeStructsHelperRotation( + PaintStruct* psNextQuadrant = PaintArrangeStructsHelperRotation( &psHead, session.QuadrantBackIndex, PaintSortFlags::Neighbour); while (++quadrantIndex < session.QuadrantFrontIndex) { - psNextQuadrant = PaintArrangeStructsHelperRotation(psNextQuadrant, quadrantIndex, PaintSortFlags::None); + psNextQuadrant = PaintArrangeStructsHelperRotation( + psNextQuadrant, quadrantIndex, PaintSortFlags::None); } session.PaintHead = psHead.NextQuadrantEntry; @@ -524,11 +606,18 @@ static void PaintSessionArrangeImpl(PaintSessionCore& session) using PaintArrangeWithRotation = void (*)(PaintSessionCore& session); -constexpr std::array _paintArrangeFuncs = { - PaintSessionArrangeImpl<0>, - PaintSessionArrangeImpl<1>, - PaintSessionArrangeImpl<2>, - PaintSessionArrangeImpl<3>, +constexpr std::array _paintArrangeFuncsLegacy = { + PaintSessionArrangeImpl, + PaintSessionArrangeImpl, + PaintSessionArrangeImpl, + PaintSessionArrangeImpl, +}; + +constexpr std::array _paintArrangeFuncsStable = { + PaintSessionArrangeImpl, + PaintSessionArrangeImpl, + PaintSessionArrangeImpl, + PaintSessionArrangeImpl, }; /** @@ -538,7 +627,11 @@ constexpr std::array _paintArrangeFuncs = { void PaintSessionArrange(PaintSessionCore& session) { PROFILED_FUNCTION(); - return _paintArrangeFuncs[session.CurrentRotation](session); + if (gPaintStableSort) + { + return _paintArrangeFuncsStable[session.CurrentRotation](session); + } + return _paintArrangeFuncsLegacy[session.CurrentRotation](session); } static void PaintDrawStruct(PaintSession& session, PaintStruct* ps) diff --git a/src/openrct2/paint/Paint.h b/src/openrct2/paint/Paint.h index 250c6b834a..a52b751a71 100644 --- a/src/openrct2/paint/Paint.h +++ b/src/openrct2/paint/Paint.h @@ -284,6 +284,7 @@ extern bool gShowDirtyVisuals; extern bool gPaintBoundingBoxes; extern bool gPaintBlockedTiles; extern bool gPaintWidePathsAsGhost; +extern bool gPaintStableSort; PaintStruct* PaintAddImageAsParent( PaintSession& session, const ImageId image_id, const CoordsXYZ& offset, const BoundBoxXYZ& boundBox);