Feature/lsfg frame gen - #697
Open
maxjivi05 wants to merge 32 commits into
Open
Conversation
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.
|
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.
|
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.
Contributor
Author
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.
Yes brother please fix this ❤️ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.