Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions .github/tidy-skip.txt
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
# First-party translation units the `host` preset does not compile, so the
# `tidy` job has no compile command for them. A changed .c/.cpp that is neither
# in build/host/compile_commands.json nor listed here fails the job, which keeps
# gaps in tidy coverage visible. Remove a line once its TU builds in `host`
# (most of these need RT64 or generated code; see host-full-nogen in the plan).
# gaps in tidy coverage visible. Listed paths are also excluded from
# clang-tidy-diff (analyzing them without compile flags fails on third-party
# includes). Remove a line once its TU builds in `host` (most of these need
# RT64 or generated code; see host-full-nogen in the plan).
# Paths are repo-relative, one per line, exact match.
modding/examples/ai-boss-difficulty/main.c
modding/examples/core-boot/main.c
Expand Down
27 changes: 25 additions & 2 deletions .github/workflows/tooling.yml
Original file line number Diff line number Diff line change
Expand Up @@ -152,9 +152,32 @@ jobs:
env:
MERGE_BASE: ${{ steps.changes.outputs.merge-base }}
run: |
mapfile -t changed <"$RUNNER_TEMP/changed.txt"
# tidy-skip TUs are absent from the host compile database. The gate
# above allows them; clang-tidy-diff must not analyze them, or it
# invents flags and fails on missing third-party includes (SDL3,
# ultramodern, …).
python3 - "$RUNNER_TEMP/changed.txt" "$RUNNER_TEMP/tidied.txt" <<'EOF'
import sys
skipped = {
line.strip()
for line in open(".github/tidy-skip.txt")
if line.strip() and not line.startswith("#")
}
tidied = [
line.strip()
for line in open(sys.argv[1])
if line.strip() and line.strip() not in skipped
]
open(sys.argv[2], "w").write("\n".join(tidied) + ("\n" if tidied else ""))
print(f"{len(tidied)} tidiable changed files ({len(skipped)} skip-listed)")
EOF
mapfile -t tidied <"$RUNNER_TEMP/tidied.txt"
if [[ ${#tidied[@]} -eq 0 ]]; then
echo 'No tidiable changed files (all listed in tidy-skip.txt).'
exit 0
fi
set +e
git diff -U0 "$MERGE_BASE" HEAD -- "${changed[@]}" |
git diff -U0 "$MERGE_BASE" HEAD -- "${tidied[@]}" |
python3 /usr/lib/llvm-18/share/clang/clang-tidy-diff.py -p1 -path build/host \
-clang-tidy-binary clang-tidy-18 -iregex '.*\.(c|cc|cpp|h|hpp)' -j 4 -quiet >"$RUNNER_TEMP/tidy.log" 2>&1
status=$?
Expand Down
4 changes: 4 additions & 0 deletions patches/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,10 @@
{
"path": "patches/plume/0003-compile-msl-source-shaders.patch",
"sha256": "80947b240d7a3bd5e3f9434df7501fc54d130d4e06942e36705f2238d8fb3565"
},
{
"path": "patches/plume/0004-live-cocoa-window-attributes.patch",
"sha256": "2cb8a57e9f0d9d344a9a2b4693aa4a8f98b0bf562a4b0d413b345f09c6be144b"
}
]
},
Expand Down
96 changes: 96 additions & 0 deletions patches/plume/0004-live-cocoa-window-attributes.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
--- a/plume_apple.mm
+++ b/plume_apple.mm
@@ -95,28 +95,20 @@
}

void CocoaWindow::getWindowAttributes(CocoaWindowAttributes* attributes) const {
- if ([NSThread isMainThread]) {
- UIWindow *uiWindow = (__bridge UIWindow *)windowHandle;
- CGRect bounds = [uiWindow bounds];
- CGFloat scaleFactor = getScaleFactor(uiWindow);
+ // Always read live bounds. The present/resize path calls this off the
+ // main thread; returning a stale cache (and only scheduling async refresh)
+ // leaves the Metal swapchain and overlay target at the pre-resize size.
+ UIWindow *uiWindow = (__bridge UIWindow *)windowHandle;
+ CGRect bounds = [uiWindow bounds];
+ CGFloat scaleFactor = getScaleFactor(uiWindow);

- {
- std::lock_guard<std::mutex> lock(attributesMutex);
- const_cast<CocoaWindow*>(this)->cachedAttributes.x = 0;
- const_cast<CocoaWindow*>(this)->cachedAttributes.y = 0;
- const_cast<CocoaWindow*>(this)->cachedAttributes.width = (int)round(bounds.size.width * scaleFactor);
- const_cast<CocoaWindow*>(this)->cachedAttributes.height = (int)round(bounds.size.height * scaleFactor);
+ std::lock_guard<std::mutex> lock(attributesMutex);
+ const_cast<CocoaWindow*>(this)->cachedAttributes.x = 0;
+ const_cast<CocoaWindow*>(this)->cachedAttributes.y = 0;
+ const_cast<CocoaWindow*>(this)->cachedAttributes.width = (int)round(bounds.size.width * scaleFactor);
+ const_cast<CocoaWindow*>(this)->cachedAttributes.height = (int)round(bounds.size.height * scaleFactor);

- *attributes = cachedAttributes;
- }
- } else {
- {
- std::lock_guard<std::mutex> lock(attributesMutex);
- *attributes = cachedAttributes;
- }
-
- const_cast<CocoaWindow*>(this)->updateWindowAttributesInternal(false);
- }
+ *attributes = cachedAttributes;
}

int CocoaWindow::getRefreshRate() const {
@@ -257,28 +249,20 @@
}

void CocoaWindow::getWindowAttributes(CocoaWindowAttributes* attributes) const {
- if ([NSThread isMainThread]) {
- NSWindow *nsWindow = (__bridge NSWindow *)windowHandle;
- NSRect contentFrame = [[nsWindow contentView] frame];
- CGFloat scaleFactor = getScaleFactor(nsWindow);
+ // Always read live content size. RT64's present thread calls needsResize
+ // / resize off the main thread; a cached size plus async refresh leaves
+ // the drawable and ImGui target stuck until something else refreshes.
+ NSWindow *nsWindow = (__bridge NSWindow *)windowHandle;
+ NSRect contentFrame = [[nsWindow contentView] frame];
+ CGFloat scaleFactor = getScaleFactor(nsWindow);

- {
- std::lock_guard<std::mutex> lock(attributesMutex);
- const_cast<CocoaWindow*>(this)->cachedAttributes.x = (int)round(contentFrame.origin.x);
- const_cast<CocoaWindow*>(this)->cachedAttributes.y = (int)round(contentFrame.origin.y);
- const_cast<CocoaWindow*>(this)->cachedAttributes.width = (int)round(contentFrame.size.width * scaleFactor);
- const_cast<CocoaWindow*>(this)->cachedAttributes.height = (int)round(contentFrame.size.height * scaleFactor);
+ std::lock_guard<std::mutex> lock(attributesMutex);
+ const_cast<CocoaWindow*>(this)->cachedAttributes.x = (int)round(contentFrame.origin.x);
+ const_cast<CocoaWindow*>(this)->cachedAttributes.y = (int)round(contentFrame.origin.y);
+ const_cast<CocoaWindow*>(this)->cachedAttributes.width = (int)round(contentFrame.size.width * scaleFactor);
+ const_cast<CocoaWindow*>(this)->cachedAttributes.height = (int)round(contentFrame.size.height * scaleFactor);

- *attributes = cachedAttributes;
- }
- } else {
- {
- std::lock_guard<std::mutex> lock(attributesMutex);
- *attributes = cachedAttributes;
- }
-
- const_cast<CocoaWindow*>(this)->updateWindowAttributesInternal(false);
- }
+ *attributes = cachedAttributes;
}

int CocoaWindow::getRefreshRate() const {

--- a/plume_apple.h
+++ b/plume_apple.h
@@ -31,7 +31,7 @@
CocoaWindow(void* window);
~CocoaWindow();

- // Get cached window attributes, may trigger async update
+ // Live window attributes (safe to call from the present thread)
void getWindowAttributes(CocoaWindowAttributes* attributes) const;

// Get cached refresh rate, may trigger async update
16 changes: 8 additions & 8 deletions patches/release-prep/v1.0.7/source-capsule-r2/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -2338,9 +2338,9 @@
"base_sha256": "033bae61b4d30a0f94177182dfa20da43d8d3296ddd68f1ae58c77f66d2a54dd"
},
"src/game/runtime_ui.cpp": {
"sha256": "a7605d72848020da28f2fae1b75b55c524a997c021a660b50b4acc51c05fa4fe",
"sha256": "0007cb4ab66bb03d65a5b70aa430ccc492ab9f4e0733dc307f1662a559ed90b3",
"base": "runtime-recomp/src/game/runtime_ui.cpp",
"base_sha256": "a7605d72848020da28f2fae1b75b55c524a997c021a660b50b4acc51c05fa4fe"
"base_sha256": "0007cb4ab66bb03d65a5b70aa430ccc492ab9f4e0733dc307f1662a559ed90b3"
},
"src/game/runtime_ui.hpp": {
"sha256": "ad7c87220d85b9c354590931036d6deeee5ca2492066c9becc1f052d91cf4d7d",
Expand Down Expand Up @@ -5093,7 +5093,7 @@
"src/game/runtime_ui.cpp": {
"sha256": "07f88dd1aa60690c4967bf104427773b9abc808cc5f90a7b968fe3171507d40b",
"base": "runtime-recomp/src/game/runtime_ui.cpp",
"base_sha256": "a7605d72848020da28f2fae1b75b55c524a997c021a660b50b4acc51c05fa4fe",
"base_sha256": "0007cb4ab66bb03d65a5b70aa430ccc492ab9f4e0733dc307f1662a559ed90b3",
"patch": "2c20ef4f18f9e2e62bdc92143d226e7bf5e851949b7a13d67f898b3c6a35fd19.patch"
},
"src/game/runtime_ui.hpp": {
Expand Down Expand Up @@ -5653,7 +5653,7 @@
"src/game/runtime_ui.cpp": {
"sha256": "dfb596090da7b860f358d7a5fc686e275b2f86557fe741460a3f4868201e04fa",
"base": "runtime-recomp/src/game/runtime_ui.cpp",
"base_sha256": "a7605d72848020da28f2fae1b75b55c524a997c021a660b50b4acc51c05fa4fe",
"base_sha256": "0007cb4ab66bb03d65a5b70aa430ccc492ab9f4e0733dc307f1662a559ed90b3",
"patch": "9119f8ab41db037f855ba836434393feb85abf5a1995ee83058177f1d38050dd.patch"
},
"launcher-ui-receipt.json": {
Expand Down Expand Up @@ -8282,7 +8282,7 @@
"native/src/game/runtime_ui.cpp": {
"sha256": "1930980ebeec7e07d8619c68b37495ef73e743299da0aa91f698f0b6bbb7bd75",
"base": "runtime-recomp/src/game/runtime_ui.cpp",
"base_sha256": "a7605d72848020da28f2fae1b75b55c524a997c021a660b50b4acc51c05fa4fe",
"base_sha256": "0007cb4ab66bb03d65a5b70aa430ccc492ab9f4e0733dc307f1662a559ed90b3",
"patch": "cb29ff8fb40932821e7bd5cb665dc8ceaced72d35617c816bf066f333e62fad4.patch"
},
"native/src/game/runtime_ui.hpp": {
Expand Down Expand Up @@ -8930,7 +8930,7 @@
"native/src/game/runtime_ui.cpp": {
"sha256": "64ec197f04746fa22cffd258f3b388198cb6d8857daae8f009d11177a9659c4e",
"base": "runtime-recomp/src/game/runtime_ui.cpp",
"base_sha256": "a7605d72848020da28f2fae1b75b55c524a997c021a660b50b4acc51c05fa4fe",
"base_sha256": "0007cb4ab66bb03d65a5b70aa430ccc492ab9f4e0733dc307f1662a559ed90b3",
"patch": "516011edbe66c7101c15b9b4744f2d85ec33a6fd2b823a6d3e63d5ea7715e6fa.patch"
},
"native/src/game/store_account_page.inl": {
Expand Down Expand Up @@ -9048,7 +9048,7 @@
"native/src/game/runtime_ui.cpp": {
"sha256": "4363f93073d81fb89ca1c3646fd65f7eea7f82936235a007de1b2a34ef2aa38d",
"base": "runtime-recomp/src/game/runtime_ui.cpp",
"base_sha256": "a7605d72848020da28f2fae1b75b55c524a997c021a660b50b4acc51c05fa4fe",
"base_sha256": "0007cb4ab66bb03d65a5b70aa430ccc492ab9f4e0733dc307f1662a559ed90b3",
"patch": "1d06998488c2b1ecdef3151d1a21cf7b808ea56c33b5eec034e1e541e55f3c92.patch"
},
"native/src/game/save_manager.cpp": {
Expand Down Expand Up @@ -12170,7 +12170,7 @@
"native/src/game/runtime_ui.cpp": {
"sha256": "1930980ebeec7e07d8619c68b37495ef73e743299da0aa91f698f0b6bbb7bd75",
"base": "runtime-recomp/src/game/runtime_ui.cpp",
"base_sha256": "a7605d72848020da28f2fae1b75b55c524a997c021a660b50b4acc51c05fa4fe",
"base_sha256": "0007cb4ab66bb03d65a5b70aa430ccc492ab9f4e0733dc307f1662a559ed90b3",
"patch": "cb29ff8fb40932821e7bd5cb665dc8ceaced72d35617c816bf066f333e62fad4.patch"
},
"native/src/game/runtime_ui.hpp": {
Expand Down
16 changes: 9 additions & 7 deletions runtime-recomp/src/game/runtime_ui.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9501,6 +9501,15 @@ void dkr::runtime::ui::draw(RT64::Application& application) {
if (application.presentQueue == nullptr || application.framebufferGraphicsWorker == nullptr) {
return;
}
// HUD Fit gutters read g_frame_state viewport aspect. Publish every present
// even when the ImGui overlay is closed — otherwise Expand world + stale HUD.
if (auto* hud_window = static_cast<SDL_Window*>(dkr::runtime::platform::sdl_window())) {
int hud_viewport_width = 0;
int hud_viewport_height = 0;
SDL_GetWindowSize(hud_window, &hud_viewport_width, &hud_viewport_height);
dkr::runtime::hud::set_viewport_extent(hud_viewport_width,
hud_viewport_height);
}
PumpFriendPresence();
const bool show_overlay =
g_overlay_visible.load(std::memory_order_acquire);
Expand Down Expand Up @@ -9563,13 +9572,6 @@ void dkr::runtime::ui::draw(RT64::Application& application) {
}
#endif
inspector->newFrame(application.framebufferGraphicsWorker.get());
if (auto* hud_window = static_cast<SDL_Window*>(dkr::runtime::platform::sdl_window())) {
int hud_viewport_width = 0;
int hud_viewport_height = 0;
SDL_GetWindowSize(hud_window, &hud_viewport_width, &hud_viewport_height);
dkr::runtime::hud::set_viewport_extent(hud_viewport_width,
hud_viewport_height);
}
ApplyStyle();
ImGui::GetIO().ConfigFlags |=
ImGuiConfigFlags_NavEnableKeyboard | ImGuiConfigFlags_NavEnableGamepad;
Expand Down
10 changes: 10 additions & 0 deletions runtime-recomp/tests/live_lod_pipeline_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,16 @@ def test_ui_publishes_before_hidden_overlay_early_return(self):
self.assertIn("RT64::setDefaultSamplerMipLODBias(", before_return)
self.assertIn("dkr::runtime::enhancements::effective_texture_lod_bias()", before_return)

def test_hud_viewport_publishes_before_overlay_closed_early_return(self):
# Fit gutters read published extent; with the menu closed, ui::draw must
# still refresh it every present (see Mac resize / Expand+Fit mismatch).
draw = read("runtime-recomp/src/game/runtime_ui.cpp").split(
"void dkr::runtime::ui::draw(RT64::Application& application) {", 1
)[1]
before_overlay_return = draw.split("!show_overlay && !show_fps", 1)[0]
self.assertIn("dkr::runtime::hud::set_viewport_extent(", before_overlay_return)
self.assertEqual(draw.count("dkr::runtime::hud::set_viewport_extent("), 1)

def test_raster_pass_uses_live_value_not_constructor_snapshot(self):
renderer = read("extern/rt64/src/render/rt64_framebuffer_renderer.cpp")
self.assertIn("rasterParams.mipLODBias = getDefaultSamplerMipLODBias();", renderer)
Expand Down
9 changes: 8 additions & 1 deletion runtime-recomp/tests/macos_metal_overlay_tests.mm
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,14 @@ int main() {
ImGui::GetIO().IniFilename = nullptr;
auto check_frame = [&] {
SDL_PumpEvents();
swapchain->resize();
// RT64 presents off the main thread. needsResize/resize must see
// the live Cocoa size there — main-thread-only checks miss the bug.
bool present_resized = false;
std::thread([&] {
if (swapchain->needsResize()) present_resized = swapchain->resize();
else present_resized = true;
}).join();
passed &= present_resized;
int pixels_w = 0, pixels_h = 0;
SDL_GL_GetDrawableSize(window, &pixels_w, &pixels_h);
CAMetalLayer* layer = (__bridge CAMetalLayer*)native.view;
Expand Down
Loading