Skip to content

Add opt-in frame resolution scaling to the shared Vulkan renderer - #9

Closed
anxiousintrovert wants to merge 1 commit into
TheMoonPeople:mainfrom
anxiousintrovert:codex/android-support-upstream
Closed

anxiousintrovert wants to merge 1 commit into
TheMoonPeople:mainfrom
anxiousintrovert:codex/android-support-upstream

Conversation

@anxiousintrovert

@anxiousintrovert anxiousintrovert commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Rebased onto current main (04bd301) and narrowed to the remaining opt-in frame resolution scaling functionality. Upstream 99f13ee already adopted the core Android support from this PR; this revision retains that implementation rather than replaying the old Android patches.

  • Add RendererConfig::frame_scale and gfx::SetFrameScale(float) for canonical and interpolated frame color/depth targets.
  • Keep the swapchain at native resolution and scale frames during presentation.
  • Default to 1.0, preserving existing clients' behavior. The existing auxiliary render_scale API keeps its semantics.
  • Apply changes at a safe frame boundary and invalidate frame history when targets change.
  • Compare against the scaled target dimensions to avoid reallocating attachments every frame.
  • Validate the 0.5–2.0 range and respect device image-size limits.

This remains in the shared Vulkan renderer. No ps2/ files are modified. The API is available to all host platforms, but callers must explicitly opt in. Game UI scales with the frame; the Android native touch overlay remains at native resolution.

Chronicle integration

The local Chronicle integration wires its Android resolution setting to this API under Android guards. That consumer change and the current upstream audio/API adapter were used for APK validation; they are not included in this Kazarov PR. A consumer update is required to enable this feature in Chronicle. The prior description's recommendation to close this PR as entirely superseded is replaced by this narrower scope.

Validation at da1eac5

  • Windows game and tests build successfully. RTX 3080: 112 renderer tests ran, 110 passed; two validation-layer tests skipped because layers were unavailable.
  • Ubuntu game and tests build successfully. llvmpipe: 112 renderer tests ran, 111 passed; deliberate validation misuse test skipped in release. This is software-renderer correctness coverage, not hardware performance evidence.
  • Five new GPU tests cover default behavior, allocation stability, deferred and invalid changes, record/replay, and window resizing.
  • Android optimized profile APK builds successfully for ARM64 and x86_64.
  • GPU-backed Android emulator: live resolution changes between 60% and 100% rendered successfully with the touch overlay visible. Final APK normal startup progressed from language selection to the animated title without a persistent black screen; the crash buffer was empty.
  • Formatted using Chronicle's Clang Format configuration. Clang Tidy reported an existing unnamed DebugCallback parameter warning and dependency warnings; no new change-specific finding was identified.

Physical Fold behavior, Samsung screen recording, and sustained dungeon performance were not tested in this rebase pass. No performance improvement is claimed from these checks. Historical performance figures in the previous description applied to older revisions and are not validation of this head.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-14T23:27:05.871328Z 0f766eb PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0f766eb01b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/cpp/gfx/renderer.cpp Outdated
Comment on lines +936 to +940
g.main_depth = CreateImage(render_width, render_height, 1, g.depth_format,
VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT |
VK_IMAGE_USAGE_TRANSFER_DST_BIT);
g.display_depth = CreateImage(width, height, 1, g.depth_format,
VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep shared-depth attachments at the same resolution

When render_scale_factor != 1 and a display list uses a share_main_depth target, TargetPixelSize sizes its color image from g.main_color, while ResolveTarget pairs it with MainDepth() (g.display_depth) during the display pass. These allocations now give those attachments scaled and window extents respectively; above 1x the render area is larger than the depth attachment (invalid dynamic rendering), and below 1x depth/stencil coordinates address only a corner, breaking shared-depth effects such as shadows. The display pass needs a display-sized shared target or a matching depth image.

Useful? React with 👍 / 👎.

Comment thread src/cpp/gfx/pipelines.cpp Outdated
Comment on lines +680 to +681
for (std::thread &thread : threads) {
thread.detach();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Join pipeline workers before leaving their capture scope

During renderer initialization, reaching done == total only proves each final pipeline call incremented done; the last worker can still be executing progress.notify_one() and the loop's next++. Detaching lets this scope destroy progress, next, and the other reference-captured locals concurrently, causing a startup use-after-lifetime. Keep these threads joinable and join them after the progress loop.

Useful? React with 👍 / 👎.

Comment thread src/cpp/platform/input.cpp Outdated
Comment on lines +908 to +912
TouchOverlayHandleEvent({touch_kind, std::numeric_limits<std::uint64_t>::max(),
touch_x / width, touch_y / height},
true);
}
return;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Fall through when the touch overlay does not capture input

On Android, when an SDL mouse event is from the touch/pen synthesized device (or no physical mouse is detected), this unconditional return runs even if the overlay is hidden, and the documented result of TouchOverlayHandleEvent is discarded when it is visible. An uncaptured touchscreen event therefore never reaches MouseHandleEvent below, so taps and motion outside the virtual controls cannot drive menus or other pointer UI; return only when the overlay actually captures the event.

Useful? React with 👍 / 👎.

Comment thread src/cpp/gfx/renderer.cpp Outdated
Comment on lines 1494 to 1495
RecreateScaledMainTargets();
RecreateRenderTargets();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Invalidate the cached canonical frame after rescaling

For a surfaced display-list renderer after a canonical render, SetRenderScale calls RecreateScaledMainTargets, which clears both main images, but leaves g.canonical_newest true. A subsequent PresentCanonical or noncanonical RenderList therefore accepts and presents the cleared image until another canonical tick; either preserve the canonical image while resizing or invalidate the flag so the caller rerenders it.

Useful? React with 👍 / 👎.

Comment thread src/cpp/platform/input.cpp Outdated
mouse.dy = g_menu_dy + dy + g_scripted.mouse_dy + std::exchange(g_touch_dy, 0.0f);
mouse.wheel = MouseTakeWheel() + std::exchange(g_scripted_wheel, 0.0f) + std::exchange(g_touch_wheel, 0.0f);
mouse.buttons = MouseButtons() | g_scripted.mouse_buttons | g_touch_held;
mouse.buttons = MouseButtons() | g_scripted.mouse_buttons;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve held touchpad buttons in menu input

When menu mouse is enabled with a DualShock/DualSense touchpad, ReadTouchpad stores the pad click and Create-button states in g_touch_held via TouchpadStep::held, but this assignment no longer includes that state. Holding the touchpad or Create therefore never appears as a left/right click even though touchpad motion still works; include g_touch_held in the returned button mask.

Useful? React with 👍 / 👎.

Comment thread src/cpp/gfx/draw.cpp Outdated
nullptr);
#ifdef __ANDROID__
g.cmd_set_front_face(cmd, VK_FRONT_FACE_COUNTER_CLOCKWISE);
#else

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There’s a few instances of weirdness like this, ifdefs with an else that’s the same as the first case. Can we get rid of these

Adubbz pushed a commit that referenced this pull request Oct 18, 2026
Android builds import the NDK's Vulkan loader, ask for Vulkan 1.3 and
compile the shaders for it. vulkan_android.cpp defines the twelve 1.3
commands the renderer calls, forwarding to the device's own, so no
call site differs by platform. The swapchain leaves rotation to the
compositor and keeps itself through the SUBOPTIMAL results that
rotation reports while the surface is unchanged; a surface lost in the
background is made again on a later frame. gfx errors also go to the
system log.

The window is fixed fullscreen in landscape. The game's on-screen
gamepad passes its state to InputSetTouchOverlay, which pad 0 takes
while InputTouchOverlayVisible() reports that no gamepad, keyboard or
mouse is in use. Float parsing and the input script's step search avoid
the parts of libc++ Android lacks, and the pipeline workers are joined
std::threads.

Desktop builds are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Adubbz

Adubbz commented Oct 19, 2026

Copy link
Copy Markdown
Member

This is likely quite out of date and will need re-basing

@anxiousintrovert
anxiousintrovert force-pushed the codex/android-support-upstream branch from b08520d to da1eac5 Compare October 19, 2026 15:22
@anxiousintrovert anxiousintrovert changed the title Add Android support to the shared Vulkan renderer Add opt-in frame resolution scaling to the shared Vulkan renderer Oct 19, 2026
@Adubbz

Adubbz commented Oct 24, 2026

Copy link
Copy Markdown
Member

Closing for now as unnecessary: neither Chronicle nor ChronicleTwo calls an opt-in frame resolution scale (both use SetRenderScale), and it would need rework for the render thread. Happy to revisit with a consumer.

@Adubbz Adubbz closed this Oct 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants