diff options
| author | Duck912 <350528965+Duck912@users.noreply.github.com> | 2026-11-02 16:12:17 -0500 |
|---|---|---|
| committer | Duck912 <350528965+Duck912@users.noreply.github.com> | 2026-11-02 16:28:01 -0500 |
| commit | 11cebf56014a5015db28179a8acc81cad0caf641 (patch) | |
| tree | 16b9dcba1fb67c1309dac7c12fe7ee5cc6e54791 /src/video_core/texture_cache/texture_cache.h | |
| parent | 08dbcc7e3b3df9b594abe1f0cbaf7d30717b21ce (diff) | |
video_core: replace Fortune's Weave's DLSS with AMD FSR 3.1
Fire Emblem: Fortune's Weave's DLSS network can't run here, and the
temporal stand-in is only an approximation of it. FSR 3.1 takes the
same inputs DLSS does: the jittered colour, motion vectors, the jitter
and the depth, which the game's first DLSS pass reads through
cbuf0+0x298 (Z32, reversed, the colour's size). Kepler compute keeps it
next to the motion vectors.
Fsr3Upscaler ports the host side of the SDK's ffx_fsr3upscaler.cpp: the
eight passes (prepare inputs, luma and shading-change pyramids, shading
change, reactivity, luma instability, accumulate, RCAS), the internal
images and the constants, recorded through the scheduler with the
shaders from externals/FidelityFX-SDK. It takes HDR colour, render-size
motion vectors and reversed depth, computes its own exposure, and uses
the no-wave SPD variants on devices without compute quad operations.
The SDK's GLSL uses separate textures and samplers, so DescriptorBankInfo
gains sampled image and sampler counts (default 0). FSR's RCAS follows
the RCAS Sharpness slider. The result goes to FSR's own RGBA16F image
and is drawn into the game's output, which keeps its format.
TextureCacheRuntime::TemporalUpscale runs FSR 3.1 first. Without the
depth, at mismatched scales, on a device lacking FSR's formats or
features, or if its pipelines fail to build, the stand-in runs instead,
and the bilinear blit stays last. EDEN_NX2_DLSS_REPLACEMENT=standin
forces the stand-in. A gap of more than 250 ms between evaluations
restarts the history.
The BlitColor overload that takes a raw image view transitions its
source to SHADER_READ_ONLY_OPTIMAL but described it as GENERAL in the
descriptor (a validation error); it now describes the layout it uses.
Assisted-by: Claude Opus 5.5 (debugging, implementation and drafting)
Signed-off-by: Duck912 <350528965+Duck912@users.noreply.github.com>
Diffstat (limited to 'src/video_core/texture_cache/texture_cache.h')
| -rw-r--r-- | src/video_core/texture_cache/texture_cache.h | 49 |
1 files changed, 42 insertions, 7 deletions
diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h index 365137e7d6..ecd8429fa2 100644 --- a/src/video_core/texture_cache/texture_cache.h +++ b/src/video_core/texture_cache/texture_cache.h @@ -1097,6 +1097,7 @@ template <class P> bool TextureCache<P>::TemporalUpscale(const Tegra::Engines::Fermi2D::Surface& dst, const Tegra::Engines::Fermi2D::Surface& src, const Tegra::Engines::Fermi2D::Surface& motion_vectors, + const Tegra::Texture::TICEntry* depth, std::array<f32, 2> jitter, std::array<f32, 2> motion_scale) { if constexpr (FRAMEBUFFER_BLITS) { return false; @@ -1122,6 +1123,12 @@ bool TextureCache<P>::TemporalUpscale(const Tegra::Engines::Fermi2D::Surface& ds has_deleted_images = false; const ImageId motion_id = FindOrInsertImage(motion_info, motion_vectors.Address(), RelaxedOptions{}); + // The depth only feeds FSR 3.1 (looked up only when FSR can run); without it the stand-in + // runs. + ImageId depth_id{}; + if (depth && motion_id && runtime.WantsTemporalUpscaleDepth()) { + depth_id = FindOrInsertImage(ImageInfo(*depth), depth->Address(), RelaxedOptions{}); + } if (has_deleted_images || !motion_id) { return false; // the colour and output ids may be stale; the caller blits instead } @@ -1130,6 +1137,13 @@ bool TextureCache<P>::TemporalUpscale(const Tegra::Engines::Fermi2D::Surface& ds PrepareImage(src_id, false, false); PrepareImage(motion_id, false, false); PrepareImage(dst_id, true, false); + if (depth_id) { + PrepareImage(depth_id, false, false); + if (slot_images[depth_id].info.num_samples != 1 || + slot_images[depth_id].info.format != PixelFormat::D32_FLOAT) { + depth_id = {}; + } + } for (const ImageId id : {src_id, motion_id, dst_id}) { if (slot_images[id].info.num_samples != 1) { return false; @@ -1140,13 +1154,25 @@ bool TextureCache<P>::TemporalUpscale(const Tegra::Engines::Fermi2D::Surface& ds const auto rescaled = [&](ImageId id) { return True(slot_images[id].flags & ImageFlagBits::Rescaled); }; - if (rescaled(src_id) || rescaled(motion_id) || rescaled(dst_id)) { - for (const ImageId id : {src_id, motion_id, dst_id}) { - if (!rescaled(id) && ImageCanRescale(slot_images[id])) { + if (rescaled(src_id) || rescaled(motion_id) || rescaled(dst_id) || + (depth_id && rescaled(depth_id))) { + for (const ImageId id : {src_id, motion_id, dst_id, depth_id}) { + if (id && !rescaled(id) && ImageCanRescale(slot_images[id])) { ScaleUp(slot_images[id]); } } } + // FSR 3.1 reads colour, motion and depth texel for texel: all at the colour's scale. + if (depth_id && (rescaled(depth_id) != rescaled(src_id) || + rescaled(motion_id) != rescaled(src_id))) { + depth_id = {}; + } + const Extent3D src_size = slot_images[src_id].info.size; + const std::array<u32, 2> render_size = + rescaled(src_id) + ? std::array{Settings::values.resolution_info.ScaleUp(src_size.width), + Settings::values.resolution_info.ScaleUp(src_size.height)} + : std::array{src_size.width, src_size.height}; // The jitter is in the input's guest pixels. if (rescaled(src_id)) { const f32 factor = Settings::values.resolution_info.up_factor; @@ -1167,12 +1193,21 @@ bool TextureCache<P>::TemporalUpscale(const Tegra::Engines::Fermi2D::Surface& ds if (!src_view || !motion_view || !dst_view) { return false; } + ImageViewId depth_view{}; + if (depth_id) { + if (const std::optional base = slot_images[depth_id].TryFindBase(depth->Address())) { + const SubresourceRange range{.base = *base, .extent = {1, 1}}; + depth_view = FindOrEmplaceImageView( + depth_id, ImageViewInfo(ImageViewType::e2D, PixelFormat::D32_FLOAT, range)); + } + } // After the views: creating them may reallocate the framebuffer slots. Framebuffer* const dst_framebuffer = &slot_framebuffers[dst_view->first]; - return runtime.TemporalUpscale(dst_framebuffer, slot_image_views[dst_view->second], - slot_image_views[src_view->second], - slot_image_views[motion_view->second], jitter, - motion_scale); + return runtime.TemporalUpscale( + dst_framebuffer, slot_image_views[dst_view->second], slot_image_views[src_view->second], + slot_image_views[motion_view->second], + depth_view ? &slot_image_views[depth_view] : nullptr, render_size, jitter, + motion_scale); } } |
