Skip to content

Feature/lsfg frame gen - #697

Open
maxjivi05 wants to merge 32 commits into
WinNative-Emu:mainfrom
maxjivi05:feature/lsfg-frame-gen
Open

Feature/lsfg frame gen#697
maxjivi05 wants to merge 32 commits into
WinNative-Emu:mainfrom
maxjivi05:feature/lsfg-frame-gen

Conversation

@maxjivi05

Copy link
Copy Markdown
Contributor

No description provided.

Reviews the two existing Android LSFG integrations and traces WinNative's
own present path to decide where frame generation belongs.

LSFG-Android is a standalone overlay app. It cannot reach another app's
swapchain, so it runs on a MediaProjection capture and composites through a
system overlay, at a self-reported 50-80 ms latency cost. Its framegen
library also owns a separate VkDevice and synchronises with vkDeviceWaitIdle
because cross-device semaphores do not exist, which would put a full device
stall on our critical path.

Eden PR 4263 reimplements the same shader chain inside the emulator's own
Vulkan renderer: one device, one command stream, generated frames written
straight into an owned storage image and presented ahead of the real frame.
It also drops the DXBC translator - current Lossless Scaling resources are
already SPIR-V, so only a PE resource walk and a binding renumber are needed.

WinNative is structurally in eden's position, not LSFG-Android's: the guest
hands finished frames over DRI3 with the Android AHB modifier, and
vkr_texture_import_ahb turns them into VkImages in the compositor's own
device before anything is presented. Frame generation therefore belongs in
vk_renderer.c, entirely on the Android side, with no Wine involvement.

Documents the composite-target redirect the swapchain images cannot serve,
the multi-present ordering, the pacer's probe and backoff loop, and the
container-local Lossless.dll discovery that eden cannot offer. Also corrects
the latency premise: interpolation always adds one output interval, so the
Android-side placement removes the capture and guest round-trip overhead
rather than reducing input latency below baseline.
First phase of Android-side frame generation. Nothing here touches the
renderer yet, so the build is unchanged at runtime.

The shader chain lives as RCDATA resources inside the user's own copy of
Lossless.dll and is not redistributable, so it has to be extracted
on-device. Current Lossless Scaling builds already store those resources as
SPIR-V, which is why this needs only a PE resource walk and a descriptor
binding renumber rather than the DXBC translator upstream lsfg-vk carries.

lsfg_dll.c walks the PE headers, section table and resource tree to collect
the 25 RCDATA blobs (255, 256 and 280-302) for the native fp16 and fp32
variants, renumbers each module's binding decorations into set/binding
order, and writes them to an on-device cache through a temp file plus
rename so a failed build cannot leave a half-written cache behind. The DLL
is mapped read-only and never loaded or executed.

Both variants are cached at install time when the DLL carries both, so
flipping the fp16 preference later does not require the user to supply the
DLL again. The staged copy is deleted as soon as the cache is written.

lsfg_probe.c answers whether the selected driver can run the chain at all,
resolving its own entry points from a private dlopen rather than the global
dispatch table so it cannot disturb a live renderer. It requires a compute
queue plus storage and sampled support for the three formats the chain
needs, and reports unsupported instead of failing later at pipeline
creation.

LosslessScaling adds container-local discovery, which eden cannot offer:
Lossless Scaling installed into a container's Steam library is found
directly under drive_c or a mapped drive, with the storage picker as the
fallback.

Verified with a synthetic PE and SPIR-V generator: 23 assertions covering
variant selection and fallback, binding renumbering, cache round-trip and
every rejection path, run clean under ASan and UBSan on the host and on
device for both arm64 and x86_64. The two parse paths were additionally
fuzzed with 3000 mutated PE files and 2000 mutated cache files with
-fno-sanitize-recover, no crashes or undefined behaviour.
…pchain

Second phase of Android-side frame generation. Frame generation itself is
still absent; this only builds the target it will need and leaves it off.

Interpolation has to read the frame it just presented, and the next one has
to be generated straight into a storage image. Android swapchain images
serve neither: they are created COLOR_ATTACHMENT only, and storage support
on a swapchain format is not something a driver owes us. So when the path
is armed the scene and effect chain now end in a composite target the
renderer owns, and a blit moves that into the acquired swapchain image.

The composite pass carries the same attachment format as the swapchain pass
so every existing scene and effect pipeline stays render-pass compatible
with both, and only the final layout differs: GENERAL, so a compute
dispatch and the blit can both read the finished frame. Targets rotate on
frame_index, which gives the two-deep history the chain wants for free and
is already fenced by the per-frame in-flight wait.

Kept off the default path deliberately. With frame generation unrequested
the render pass, framebuffer and submit are the ones that ran before, and
the swapchain is created with exactly its previous usage flags - the
TRANSFER_DST the blit needs is requested only while the path is armed, and
toggling it recreates the swapchain the way the recorder's TRANSFER_SRC
already does, so nobody pays for framebuffer compression they lose to a
usage flag they never use.

Support is gated on the live swapchain format rather than assumed: storage,
sampled, colour attachment and both blit bits, plus TRANSFER_DST being
offered at all. A device that fails the check reports unsupported up front
instead of failing later at pipeline creation.

One bug found while reviewing this and fixed here rather than left for the
next phase: the acquire semaphore was waited on at COLOR_ATTACHMENT_OUTPUT,
which was correct while a render pass was the first thing to touch the
swapchain image. On the composite path the first access is a transfer
write, and TRANSFER precedes COLOR_ATTACHMENT_OUTPUT in pipeline order, so
the blit could have run before the presentation engine released the image.
The wait stage now includes TRANSFER whenever the composite path is active.

Native and Java both build clean. The composite path itself still needs
running on a real Wine session to confirm, which the x86_64 emulator here
cannot host; validation layers can be turned on through the existing
enable_vulkan_validation_layers preference when it is.
Testing against a real install corrected a premise I had recorded wrongly.
The design doc claimed current Lossless Scaling builds ship the chain as
SPIR-V so no DXBC translator is needed. The second half holds, the first
half does not: the base chain IDs have always been DXBC. What 3.2.2 added
was a second set of precompiled SPIR-V copies at base+49 (fp16, IDs
304-351) and base+98 (fp32, IDs 353-400), under the release note "Added
shaders intended for use by the lsfg-vk project".

Verified on device against Lossless Scaling 3.2.1.0: RCDATA IDs 101-302,
every one of them DXBC, and none of the variant IDs present. The parser
correctly refused it, but as a bare MISSING_SHADERS, which reads as a
corrupt or wrong file when the copy is in fact perfectly good and merely
one version behind.

LSFG_DLL_TOO_OLD now covers exactly that case - base chain present,
variants absent - so the UI can tell the user to update through Steam
rather than send them looking for a different file. Anything genuinely
incomplete still reports MISSING_SHADERS.

The alternative to a version floor is vendoring DXVK's DXBC translator the
way upstream lsfg-vk does. That is a far larger dependency than the PE
resource walk it would sit next to, and it would exist only to support a
superseded version, so the floor is the better trade.

Covered by a dxbc-only fixture in the regression suite, and confirmed
against the real 3.2.1 DLL on the tablet.
Retracts the previous commit's premise. It claimed Lossless Scaling 3.2.2
adds precompiled SPIR-V variants and set that as a version floor. Measuring
the actual Steam data on device shows there is no version to move to.

The installed depot manifests match the public branch PICS gids exactly, so
this is the current build and not a stale download. Public is buildId
19655272 from 2025-08-19; every other branch is older, and linux_testing is
byte-identical to public on depot 993091. There is nothing newer to fetch,
on any branch.

Scanning the whole 311 MB, 456-file install for the SPIR-V magic word finds
zero occurrences, so the blobs are not in some other file either. The DLL
carries RCDATA 101-302, all 202 of them DXBC.

This lines up with what the working Android implementation actually does:
upstream lsfg-vk and its Android fork run the DXBC path by default through
DXVK's translator in src/extract/trans.cpp, and expose the precompiled
SPIR-V only as an opt-in FP16 toggle for Mali parts missing
vulkanMemoryModel. Eden's translator-free path assumes a Lossless build
carrying those blobs, and nothing currently downloadable carries them.

So a DXBC to SPIR-V translator is not optional after all, and the work plan
gains a phase for it. The resource walk still stands and still picks up the
SPIR-V path for free should a future build ship the variants.

LSFG_DLL_TOO_OLD is renamed LSFG_NO_SPIRV_VARIANTS, since it is the normal
result for every build on Steam today and a signal to take the DXBC path,
not an error telling the user to go update something.

No Steam downloader code was touched: there is no manifest pin to remove.
depot.config is WinNative's own record of what it installed, written by
DepotConfigStore, and downloadApp already force-refreshes PICS before
resolving depots.
Eden reaches vkCreateShaderModule with a map of shader id to SPIR-V words
and everything below that map is source-agnostic. That seam is what makes
eden's approach portable, so it is kept exactly: the precompiled-SPIR-V
path is unchanged, and a second producer fills the same map when the
variants are absent.

That second producer is DXVK's dxbc, vendored under cpp/thirdparty/dxbc
from the same subset lsfg-vk uses, zlib licensed. lsfg_dxbc.cpp follows
lsfg-vk's trans.cpp step for step, including its encounter-order binding
renumber, which is what pairs with DXVK's output. Eden's set/binding sort
stays on the precompiled path where it belongs. Which producer ran is
recorded in the cache header and surfaced as the variant, so the source is
visible in diagnostics without changing anything downstream.

Validation now matches eden's ParseShaderSpans as well: a DLL is valid when
the base chain ids are present, with no requirement that the SPIR-V variants
exist. The previous stricter check rejected files eden accepts.

Verified against the real 5.4 MB Lossless.dll on the tablet, arm64, Adreno
750: all 25 modules translate, 352889 SPIR-V words, about 40 ms for the
whole set, and all 25 pass spirv-val --target-env vulkan1.3.

Disassembly of the output pinned down two runtime requirements that were
being taken on faith. The modules are SPIR-V 1.6, so the device must expose
Vulkan 1.3 or vkCreateShaderModule will reject them, and they declare
OpCapability VulkanMemoryModel with OpMemoryModel Logical Vulkan. The
latter is exactly what eden's "This GPU driver does not support the Vulkan
memory model" string is about, which also confirms eden is running
DXVK-translated SPIR-V - it just gets it pre-translated when the DLL
supplies it. The probe now requires Vulkan 1.3, vulkanMemoryModel,
shaderStorageImageWriteWithoutFormat and shaderStorageImageExtendedFormats
rather than discovering the gap at pipeline creation.

A cache-header bug is fixed in passing: the variant written was the local
one, still NONE on the translated path, so the source was lost on reload.

Regression suite is 25 assertions, green on device. Two fixtures were wrong
rather than the code: they carried only variant ids and no base chain, which
no real DLL does.
…device

Phase 3a. The chain's building blocks are ported from eden and verified
against real hardware before the passes that sit on top of them are written.

Ported as C++ rather than hand-translated into C. vk_renderer.c stays C and
will reach this through a C entry point, but eden's originals are C++ with
RAII throughout, and rewriting 4700 lines of that into C by hand is how
subtle lifetime and barrier bugs get introduced. The CMake target already
compiles C++, so the faithful port is also the cheaper one.

lsfg_common carries LsfgImage, LsfgResources, LsfgBarriers,
LsfgDescriptorWriter and LsfgPass, plus the generation-slot and timestamp
maths, following eden structure for structure. What changes is only what has
to: eden's vk:: wrappers and MemoryAllocator become small move-only RAII
types over raw handles with one allocation per image, which is what
create_one_offscreen next door already does. lsfg_shaders turns the cached
modules into VkShaderModules and refuses to report valid unless all 25 are
present.

Everything the rest of the port rests on is now checked on the tablet
against the real Lossless.dll, not assumed:

  all 25 SPIR-V modules accepted by vkCreateShaderModule
  compute pipeline created for mipmaps, 10 descriptors
  compute pipeline created for generate, 9 descriptors
  storage images allocate in R8_UNORM and R16G16B16A16_SFLOAT
  sampler cache, uniform constants buffer and descriptor pool all build
  device creates with vulkanMemoryModel enabled

The descriptor layouts were read out of the translated SPIR-V rather than
guessed: mipmaps declares one uniform buffer, one sampler, one sampled
image and seven storage images, which independently confirms the seven mip
levels eden's LSFG_MIP_LEVELS asserts, and generate declares one uniform
buffer, two samplers, five sampled images and one storage image.

The GPL-3.0 and lsfg-vk SPDX headers are kept on every ported file, as the
licence requires. They are the only comments in the new sources.
Phase 3b. Two of the six chain passes are ported, and for the first time
the Lossless shaders actually execute on the tablet rather than merely
compiling.

mipmaps builds the seven-level flow pyramid from the input frame pair and
generate performs the final warp into a target view. Both follow eden pass
for pass, including the descriptor layouts, the double-buffered sets
indexed by frame parity, the barrier sequences, and the dispatch tile
shifts, which differ between the two: 6 for mipmaps against the flow
extent, 4 for generate against the output extent. generate keeps eden's
full slot and target matrix, so all six generation slots by seven targets
by two parities are allocated up front, 84 descriptor sets, with SetTarget
rewriting only when the bound view actually changes.

Verified on the Adreno 750 against the real Lossless.dll, recording both
passes into one command buffer and submitting for execution:

  25 shader modules created
  input pair allocated at 1920x1080
  mipmaps built, pyramid from 1920x1080 down to 30x16 across seven levels
  generate built, 84 descriptor sets allocated
  command buffer recorded and submitted
  fence signalled, so the GPU ran both dispatches without device loss

The pyramid dimensions are worth recording because they confirm the mip
chain is being derived the way eden derives it rather than by accident:
seven levels of halving from the flow extent lands exactly on 30x16.

Descriptor set allocation moves into lsfg_common as a shared helper, since
every remaining pass needs the same wrapped allocate.
Ports the four remaining passes from eden's PR 4263 onto the existing
foundation: alpha (4 stages over 7 mip levels with 3-deep history), beta
(5 stages, 6 flow outputs), gamma (5 stages per mip level) and delta (10
stages across 3 instances), then wires all six passes together in
LsfgChain.

Where eden binds VK_NULL_HANDLE for the first instance's absent history
and relies on VK_EXT_robustness2 nullDescriptor, this binds a shared 1x1
dummy instead. Disassembling shader 280 shows the only load of binding 7
sits inside a block guarded by first_iter == 0, so the descriptor is
never sampled when the history is absent and the two are equivalent -
minus the extension requirement, which WinNative's device range cannot
assume.

Verified on the Adreno 750 against the real Lossless.dll: the full chain
builds, records and executes all six generation slots over three frames,
and for a known translation the output is the exact midpoint frame -
mean absolute error 0.00 at k=shift/2 for shifts of 8, 12 and 20 px,
against ~shift error versus either input.
Adds the FrameGen orchestrator and eden's pacer behind a C bridge
(vkr_lsfg), then wires it into record_and_submit_frame: the composited
real frame is copied into the chain, the shared flow passes run, and each
generated frame is dispatched into its own composite target and blitted
to its own acquired swapchain image. Generated frames are queued to
present before the real frame, so FIFO paces them one vblank apart.

Fixes a latent bug in the earlier port: WinNative resolves every Vulkan
entry point through the vkd dispatch table because adrenotools drivers
live in an isolated linker namespace with no shared global symbols. The
ported LSFG code included <vulkan/vulkan.h> directly and so called the
system loader - correct on a stock driver, wrong on Turnip, where the
chain would have allocated against a different driver than the renderer's
device. All of it now routes through vkd, which gained CreateComputePipelines,
CmdDispatch and CmdCopyImage. vk_dispatch.h also gained the extern "C"
guard it needed once C++ began including it.

The generate pass caches the last view bound per target slot, so
recreating composite targets without rebuilding the chain would have left
descriptors pointing at destroyed views; ForgetTargets now clears that
cache whenever the targets are recreated.

Verified on the Adreno 750 through the bridge exactly as the renderer
drives it: 120 frames at a simulated 30 fps, 118 of them generating, and
the generated frame is still the exact midpoint (0.00 mean abs error).
The swapchain asked for VKR_LSFG_MAX_GENERATIONS extra images whenever
frame generation was requested, regardless of the configured multiplier.
Under FIFO every surplus image is another frame queued between render and
scanout, so at 2x this added two images of pure input lag for no benefit -
which is what a game felt on the sticks.

Sized to the multiplier instead: 2x asks for one extra image, and only
adaptive mode (target rate set, so the pacer may climb to 3 generations)
asks for the maximum.
The Screen Effects (FX) pane gains a Frame Generation section: an enable
toggle, Adaptive Target chips (Fixed plus every standard rate the panel
supports), Multiplier chips shown only in fixed mode, and a Flow Scale
slider. Changes apply to the running session immediately and persist to
the shortcut when one owns the settings, otherwise to the container.

Changing the multiplier now resizes the swapchain, because the number of
extra images is derived from it -- without that, raising the multiplier
mid-session silently capped generation at whatever the old swapchain
could spare, and lowering it left surplus queued frames behind as input
lag.

Fixes a deadlock that the new toggle would have hit on first use:
nativeSetFrameGenerationEnabled called lifecycle_begin while already
holding render_mutex, which is not recursive. Nothing toggled frame
generation with a live swapchain before, so it never fired.
…rt Lossless Scaling

Holding a stick called invalidate() on every motion sample, repainting the
full-screen overlay and redrawing every element in the profile, not just the
stick. gfxinfo showed 63% janky frames with Slow-issue-draw-commands accounting
for nearly all of them and a 13ms median GPU time for the overlay layer alone.
Frame generation is what made it obvious rather than merely wasteful: with the
GPU near saturation the overlay kept winning and the game's real frames starved,
so the stick glided while the world stuttered. The stick now uses the bounded
invalidateControlElement() the class already had for this, and onDraw skips
elements outside the damage rect.

Per-game Graphics settings gain a Frame Generation card. Opening it detects a
Steam-downloaded Lossless Scaling and imports its shaders with no prompting,
looking at the recorded install path, the resolved app dir, every configured
install root, and finally the container drives.
…cking the DLL

Import now requires a Steam license for 993090 or a recorded install of it, so
a stray Lossless.dll left in a container no longer unlocks the shaders on its
own. Without either the card says so and offers nothing to press.

The cache header already carried the source size and an FNV-1a of the DLL, but
nothing could read them back, so a cache once built was never rebuilt and a
Steam update to Lossless left stale shaders in place forever.
lsfg_cache_matches_source compares both against the DLL on disk and the card
re-imports when they diverge, reporting it as an update rather than a first
import. Verified on the tablet: a cache matches the DLL it came from and stops
matching after a single byte of that DLL changes.

Selecting the DLL by hand goes through the same ownership gate and the
installFrom(Uri) path that has been unused since it was written.
… on update

3x and 4x never reached their multiplier because the fixed-cadence path ran
through eden's adaptive gating on the way out. Any frame slower than 100ms hit
MINIMUM_BASE_RATE and called Stabilize(), which blanks generation for a full
second, and the bridge then needs two consecutive warm frames before it starts
again. A game at 25-30fps with ordinary hitches spends most of its time inside
that blackout, and raising the multiplier adds GPU load that produces more slow
frames, so asking for more gave less. Measured on the tablet at 4x: game 25 ->
compositor 40 before, game 19 -> compositor 77 after, settling at 103.

Fixed mode now returns the configured cadence directly and only skips a frame
when the gap exceeds 250ms, which is a real discontinuity worth re-priming the
history for rather than a hitch. Adaptive mode keeps eden's probe logic, which
is what it was written for.

The swapchain was never the constraint here - the surface reports minImageCount
5, so capacity sat at the 3-generation ceiling throughout. Logged it, along with
dropped generated-frame acquires, because guessing at those numbers cost a build.

Lossless now re-imports when its Steam download finishes and again at game
launch, so an update reaches the shaders without opening the settings card.
All 22 locales were exactly 20 strings behind the base after the frame
generation work; each now matches at 2349. Product and file names stay
untranslated, as do the fps unit and the multiplier format.
Every generated frame was a pixel-perfect interpolation, but within a frame
they came out reversed: at 4x the first shown sat three quarters along the
motion, then the midpoint, then a quarter. Motion stepped forward, jumped back,
forward again. At 2x there is only one generated frame, so the reversal had
nothing to show and 2x looked correct throughout - which is why this read as
"3x and 4x do not work" rather than as an ordering fault.

The shader timestamp runs from the newer frame backwards, so generation g needs
(count - g) / (count + 1), not (g + 1) / (count + 1). At one generation both
give one half, which is what hid it.

Measured on the tablet GPU against a shifted multi-sine pattern, 24px of true
motion: 2x lands at 12, 3x at 8 and 16, 4x at 6, 12 and 18, every one at 0.00
mean absolute error and strictly increasing.
…rtcut settings

Two faults kept 3x and 4x from ever reaching the screen on the phone while 2x
looked fine.

The display stayed at 60Hz. Android picks a mode from the guest's own frame
rate, so a 30fps game leaves the panel at 60 even though it supports 165, and
under FIFO that caps total presents at 60 a second. 2x from 30fps needs exactly
60 and fits; 3x needs 90 and 4x needs 120, so beyond 2x the generated frames
displaced real ones instead of adding to them and the game rate fell to
compensate. Enabling frame generation now requests the lowest mode at the
current resolution that can carry multiplier times the frame cap, and clears
the request when it is switched off.

Dinkum's shortcut carried frameGen=1 and frameGenMultiplier=4, but it also sets
use_container_defaults, and getSettingExtra returns the container value outright
for such shortcuts. Container 5 has no frame generation keys, so every launch
silently resolved to off, and anything saved from the drawer went to the same
ignored place. Frame generation now reads the shortcut's own extra first and
falls back to the container, which is where the settings UI has been writing all
along.
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

The FPS readout counted guest presentations, so with frame generation on it
reported the game's rate and said nothing about what actually reached the
panel. The renderer now keeps a monotonic count of frames that survived
vkQueuePresentKHR, real and interpolated alike, and the HUD samples it over
the same one-second window it already uses for the input rate, rendering the
pair as "30 -> 60".

The split appears only while interpolated frames landed during the sampled
window, so an unsupported or stalled generator falls back to the single
number instead of asserting a ratio that never happened.
The timestamp fell as the generation index rose, while the present loop
emits generation 0 first and the real frame last. The newest interpolated
position therefore went out ahead of the older ones, and motion ran forward
two thirds of a game frame, snapped back to one third, then jumped to the
real frame - a back-and-forth shimmer at the game's own rate.

At 2x the two forms both evaluate to one half, which is why that multiplier
alone was unaffected and why the fault read as "3x and 4x do nothing useful".

This reverses 1112371. That commit trusted an offline harness which fed the
two source frames to the interpolator in the opposite order from the
renderer, mirroring the mapping and making the wrong direction look right.
A 90 fps capture of the real pipeline disagrees: the per-frame temporal
placement matches a reversed-order reference and not an in-order one.
The frame generation controls move out of the FX tab and the FPS limiter
out of the HUD tab into a new FG tab, sitting between HUD and Gyro on the
rail. Frame pacing now lives in one place instead of being split across
two panes that were each about something else.

The limiter floor drops from 30 to 15, both in the session drawer and in
the per-game settings slider, so a game saved at 15 is not clamped back
to 30 the next time its settings are opened.
The pacer now sizes itself from the guest's present rate and the panel
refresh, so it only ever fills vblanks the game was not going to use.
Fixed multipliers take the integer headroom; target rates take the
fractional one so the credit accumulator can still reach the rate asked
for. Raising the count is verified against the rate it displaced and
reverted with an escalating backoff when it costs more than it returns.

Generated images are claimed before the command buffer is recorded, and
the chain interpolates for the count actually acquired, so a dropped
frame shortens the run instead of mistiming what is left. The acquire
timeout follows the refresh period rather than a fixed 3ms.

Frame generation owns the display mode while it is on, preferring a rate
that is both above its target and a whole multiple of the FPS limiter.
The refresh rate reaches the pacer through an atomic rather than the
render mutex, keeping it off the UI thread.
Frame generation shipped without a word in the README, including the
requirement to own Lossless Scaling, which makes the feature look broken
rather than gated. Adds a usage section and the attribution the GPL-3.0
sources ask for: lsfg-vk, the Eden Emulator Project, and DXVK's dxbc
translator under zlib.
Guest presents woke the compositor through the main thread's Choreographer,
which from an X client thread costs a message hop, a vsync request and a
doFrame dispatch before the render thread is told to draw. Only one request
could be armed at a time, so frames arriving in between were dropped. Wake
the render thread directly instead.

The source rate was estimated by smoothing elapsed/drawn and inverting it,
which biases the rate downward when the loop and the guest drift in phase.
Smooth the frames and the seconds separately and divide.

Four paths signalled the same guest frame into one flag, so the loop ran at
the FIFO ceiling regardless of the game's rate and generated frames were
interpolated between identical inputs. Suppress the redundant wakes while
the guest is presenting; the guard lapses after 100ms so games that do not
use Present and the desktop keep their own wakes.
@sultanyasir802-png

Copy link
Copy Markdown

In gamepad layouts Need 2 buttons working in one click combo tool not needed please fix this problem this is really need in fighting games

…f when it costs frames

The flow pyramid ran at the swapchain extent, so a game rendering 720p into a
1080p surface paid for motion estimation at 1080p while the upscale added no
motion detail to find. The chain now takes the guest's own render extent, the
same one SGSR1 already computes, and derives the flow scale from its ratio to
the output, stepped to five percent and clamped to the quarter-to-full range.
Everything below generate scales with the square of that, so a 720p title at
2x drops from roughly 4.4 to 1.9 full-frame-equivalents of compute per real
frame. Manual scale stays available and its default falls from full to seventy
percent, which is the floor auto lands on for a native-resolution game.

The pacer sized itself purely from refresh rate against the guest's present
interval, which is a display-cadence budget rather than a cost one. Under GPU
contention that reads backwards: generation slows the game, the source interval
grows, more vblanks appear free, and the pacer asks for more. Nothing measured
whether generating had helped. It now probes one generation down for seven
hundred milliseconds every fifteen seconds and keeps the reduction when the
render loop speeds up by more than a tenth without it, and it reverts a raised
count within six hundred milliseconds when either the guest or the loop
interval regresses by a fifth, with the ceiling held through an escalating
eight, twenty, forty-five and ninety second backoff.

The delta instance count was a literal three that had to be kept in step by
hand with a first-level constant living in another file. Both bounds are named
in the header now and the count derives from them, so changing where the warp
field starts or stops is one edit rather than two that can disagree.
Both save paths wrote the extras with putExtra, so hasContainerOverride
stayed false and use_container_defaults was set back to 1, discarding the
edit. Disabling also nulled every key, which erased the shortcut's other
values and made a per-shortcut "off" unexpressible. Use saveOverride and
getSettingExtra like every other setting.
App 993090 ships Lossless.dll in two depots: 993091, which gets the
updates, and 993092, a stale twin. Both were selected, both wrote the same
path, and the winner was whichever finished last, so 5.18 MiB from the
stale depot usually landed instead of the current 7.17 MiB build.

Drop the superseded depot when its replacement is present, and clear both
from depot.config on an update check so the good one re-downloads. Nothing
else could repair this: the depot bookkeeping was already current, so the
update check correctly reported nothing to do.

Pick the DLL by what it carries rather than by scan order, via a new PE
probe that reports the shader variant, and log the source path and size on
import so the wrong file is visible next time.

The maintained build carries the precompiled SPIR-V, so frame generation
runs native fp16 and skips DXBC translation entirely.
…import

Add a four-notch Quality Preset slider (Ultra Performance / Performance /
Balanced / Quality) that drives flow scale directly, so there is no second
stored key that can disagree with it. Legacy custom percentages snap to the
nearest notch. Present in shortcut settings, container settings and the
in-game drawer.

Fold the "Match Motion To Game" toggle into the preset as an unconditional
ceiling and remove the switch. It previously returned the guest/output ratio
outright, which is 1.0 whenever a game renders at native resolution, so auto
silently forced the most expensive setting and could raise a Performance
preset back to full resolution. Flow scale is now min(guest ratio, preset),
which can only ever lower cost and never buys less motion detail.

Remove the pacer's self-throttles: the cost probe, the raise-regression
watchdog and the headroom cliff. A requested multiplier now holds for the
whole session instead of decaying from 4x to 2x under FIFO back-pressure.

Import Lossless shaders without visiting container settings, and rebuild the
cache on cold start so the in-game toggle can bootstrap itself.

Track the active display mode rather than the maximum supported refresh rate,
so a panel dropping to 60Hz is observed instead of being reported as 120.

Persist frame generation settings from container settings, which previously
loaded and saved none of the frameGen extras.
Three host-side changes, each validated bit-exact or quality-neutral on a
desktop Vulkan harness before landing. The Lossless shaders themselves are
untouched.

Enable shaderFloat16. The cache path always asked for the fp16 shader blobs
but the device never enabled VK_KHR_shader_float16_int8, so those pipelines
ran outside spec and the driver was free to reject them or quietly demote to
fp32. The feature is now probed and enabled, and pNext is a real chain rather
than being overwritten by the YCbCr struct alone. vkGetPhysicalDeviceFeatures2
was missing from the dispatch table and is now loaded. Measured on a desktop
harness, fp16 through the whole flow and interpolation pipeline is quality
neutral: mean +0.22 dB PSNR across nine motion types, SSIM identical to four
decimals, and sub-pixel precision better than an eighth of a pixel out to
128px of motion.

Write generated frames straight into the swapchain image. The swapchain was
created without STORAGE usage, so every generated frame was composited to an
intermediate and then blitted, costing a full-resolution read plus write per
generated frame. The swapchain now takes STORAGE when the surface reports it,
and generate targets the acquired image directly. Where the surface does not
support it the composite and blit path is kept unchanged. A readback
comparison of both paths differed in zero of 262144 bytes.

Interleave gamma with delta. Both read only the previous level's gamma output
and each owns its temporaries, so they were already independent but ran back
to back, spending 15 barriers where 10 suffice. Barrier commands per generated
frame drop from 67 to 52. Both were refactored into the step-wise form
LsfgAlpha already used; the outputs of the serial and interleaved orders are
byte identical.

Also log which shader variant the cache yielded, so the active precision can
be confirmed on device.
The preset slider shipped English-only while every other string in the frame
generation section is translated across all 22 locales. Adds the 13 preset
strings to each of them.

Drops frame_generation_preset_note, which was added with the slider but never
referenced from any layout or composable.
@maxjivi05

Copy link
Copy Markdown
Contributor Author

In gamepad layouts Need 2 buttons working in one click combo tool not needed please fix this problem this is really need in fighting games

I'll look into this, are you saying you can't use teh touch screen with 2 buttons at a time?

Writing generated frames straight into the swapchain introduced two defects
that were active on every frame.

The generated image's acquire semaphore was still waited on at the transfer
stage, which was correct while a blit produced the image but not once a
compute dispatch does. A transfer-stage wait does not block compute work, so
generate could begin writing a swapchain image before the presentation engine
released it. It now waits at the compute stage on the storage path.

The generate target was keyed by generation index, so its view changed every
frame as swapchain images rotated. SetTarget only skips work when the view
matches, so it rewrote both descriptor sets every frame, including the one the
previous frame was still executing against; the frame fence only waits on its
own slot and two frames are in flight. Updating an in-use descriptor set is
undefined and some drivers absorb it with an implicit stall. Targets are now
keyed by swapchain image index, giving each image a stable view so the write
happens once per image. A swapchain can hand back more images than there are
target slots, so that case keeps the composite path.

Three robustness gaps around the same path are fixed as well.

A failed present of a generated frame logged unconditionally from inside the
per-generation loop, the only unbounded log in the frame path; at 4x and
120Hz a persistent failure meant several hundred writes per second for as long
as it lasted. It is now rate limited like every neighbouring warning.

Only the main present result drove swapchain recreation, so a generated
present reporting out of date was logged and otherwise ignored and could never
heal, which is what kept the log above running. It now feeds the recreate
condition.

A failed submit returned without presenting the images already acquired for
this frame and left their acquire semaphores signalled, permanently dropping
images from circulation and leaving the next acquire on those semaphores
invalid. Frame semaphores outlive the swapchain, so recreation alone did not
clear them; the path now drains the device, rebuilds the frame's acquire
semaphores and recreates the swapchain.
Both captures ran logcat -f with no -r or -n, so each file grew unbounded for
the whole session while being written continuously. The full capture takes
everything at debug level, which is a lot of steady I/O on a long session.

Caps the full capture at 16MB across 4 files and the app log at 8MB across 2.
Both remain off unless one of the debug logging preferences is enabled.
@sultanyasir802-png

Copy link
Copy Markdown

In gamepad layouts Need 2 buttons working in one click combo tool not needed please fix this problem this is really need in fighting games

I'll look into this, are you saying you can't use teh touch screen with 2 buttons at a time?

Yes brother please fix this ❤️

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