Fill the scene's last column or row from the one next to it after a jitter to the left or up, before DLSS or FSR reads the scene.

With FSR 4 or FSR 3.1 a light line flickered along the bottom and right edges of the picture. The jitter moves the scene's viewport by under half a pixel, which keeps every pixel centre inside it. Yet on an RX 7900 XT, a viewport moved up left the last row undrawn in every scene pass, and one moved left the last column. A frame dump showed the depth's last row at its clear value and the light target's last row holding the fog's colour, which the upscaler blended into the edge. NX2NAVI_DLAA_EDGE_FILL=0 turns the fill off for testing.

Hyrule Field, 3416x1440, present screenshots: the outermost rows averaged 40 against 20 just inside without the fill, and within 1 of the inside with it. The same held with FSR 4.1 Native AA and Quality, FSR 3.1, SMAA and HDR. Jitter off, and no upscaler, gave no line either.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
epicmeme546
2026-11-12 08:23:25 -05:00
parent 1c654ba0c1
commit 2d09c220bf
3 changed files with 44 additions and 1 deletions

View File

@@ -1,3 +1,11 @@
## Fixed
- **A flickering light line along the bottom and right edges of the picture with FSR 4, FSR 3.1 or DLSS.** To let the
upscaler build a smoother picture, the scene is drawn moved by a fraction of a pixel, a little differently each frame.
When it moved up or to the left, AMD's driver left the last row or column of the scene undrawn, the fog's colour stayed
there, and the upscaler blended it into the edge. That row or column is now filled from the one next to it before the
upscaler reads the scene.
## Changed
- **The Anti-aliasing setting works with FSR 4, FSR 3.1 and DLSS.** Choosing Game SMAA, Off, FXAA or SMAA (Ultra) with

View File

@@ -44,7 +44,7 @@ static const char *const known[] = {
"BIND_SKIP", "BIND_STATS", "BIND_UP", "BIND_X", "BIND_Y",
"BIND_ZL", "BIND_ZR", "BLOCK_VIEWS_3D", "CACHE_DIE", "CLOTH_REALTIME", "COLOR_BRIGHTNESS", "COMPUTE_KEEP", "COLOR_CONTRAST", "COLOR_GAMMA",
"COLOR_PRESET", "COLOR_SATURATION", "CONTACT_DEBUG", "CONTACT_SHADOWS", "CORE_DIAG", "CRIT", "CRIT_OUT",
"DESC_TEMPLATE", "DEVICE_PROFILE", "DISCORD_APP_ID", "DISCORD_IMAGE", "DISCORD_RPC", "DLAA", "DLAA_CAM_KEEP", "DLSS_IDLE_FREE", "DLAA_JITTER", "DLAA_MV", "DLAA_MASKDEBUG", "DLAA_MVDEBUG", "DLAA_OBJMV", "DLAA_SURVEY", "DLAA_TARGET_KEEP", "DLAA_TIMES", "DLSS", "DLSS_CHAR_MASK", "DLSS_PRESET", "DLSS_REACTIVE", "DUMP", "DUMP_BUF", "DUMP_DIR",
"DESC_TEMPLATE", "DEVICE_PROFILE", "DISCORD_APP_ID", "DISCORD_IMAGE", "DISCORD_RPC", "DLAA", "DLAA_CAM_KEEP", "DLSS_IDLE_FREE", "DLAA_EDGE_FILL", "DLAA_JITTER", "DLAA_MV", "DLAA_MASKDEBUG", "DLAA_MVDEBUG", "DLAA_OBJMV", "DLAA_SURVEY", "DLAA_TARGET_KEEP", "DLAA_TIMES", "DLSS", "DLSS_CHAR_MASK", "DLSS_PRESET", "DLSS_REACTIVE", "DUMP", "DUMP_BUF", "DUMP_DIR",
"DUMP_LOG", "DUMP_RAW", "DUMP_SHADERS", "DUMP_FILE", "DS3", "LOG_CS_BINDS", "LOG_TEX_FLAGS", "LOG_MESH", "MESH", "POOL", "NATIVE_TENSOR", "LOG_RCOND", "LOG_RT_SIZES", "DUMP_WHITE", "DUMP_AFTER", "DUMP_MOVIE", "SUBMIT_FINISH", "ACQUIRE_WAIT", "CRASH_FILTER_GUARD", "CS_FALLBACK", "EXEFS", "FEEDBACK", "FIX_C0", "FIX_ZERO", "FLIGHT", "FLIGHT_AT", "FLIGHT_DIR", "FLIGHT_FRAMES", "GPU_TIMELINE",
"FFX_PIPE_CACHE", "FLIGHT_SPIKE_MS", "FLIGHT_THUMBS", "FLIGHT_EVERY", "FLUSH_SPAN", "FLUSH_UPLOAD", "FOG_CULL", "FOLIAGE_SHADOW_CULL", "FOLIAGE_SHADOW_VRS", "FOV", "FOV_PATCH", "FG_HUDLESS_SHOW", "FG_MULT", "FPS60", "FPS_LIMIT", "FRAMEGEN", "FRAMEGEN_MULTIPLIER", "FREEZE_IDLE", "FREEZE_ASYNC", "FREEZE_BUDGET_US", "FREE_CORES",
"FULLSCREEN", "FXAA", "GAME_ASPECT", "GAME_RES", "GPA", "GPA_EMPTY", "GPA_TWIN", "GPA_TWIN_MAX_KB", "MIRROR_CAPTURE_KB", "WB_SKIP_MB", "GPL", "GPL_DRAW_BUILD", "GPL_FEEDBACK", "PIPE_AHEAD", "PIPE_FAST_WORKERS", "PIPE_FRAME_WAIT_MS", "PIPE_LINK_DRAW_MS", "PIPE_SKIP_STEADY", "PROG_LEAD", "GPU", "GPU_INDEX", "GPU_PASSES", "GPU_NAME", "HDR",

View File

@@ -1769,6 +1769,40 @@ static void release_kept(const struct Size *making, int all) {
making->w, making->h, all ? "after a release" : "larger inputs");
}
// The scene's last column and row after a jitter to the left or up. The jitter moves the scene's viewport by under half a
// pixel (vk_dlaa_viewport), which keeps every pixel centre inside it, yet on an RX 7900 XT (AMD's Windows driver) the
// draws of a viewport moved left left the last column out, and of one moved up the last row: the driver evidently limits
// drawing to the whole pixels inside the viewport. In a frame dump (NX2NAVI_DUMP, Hyrule Field, 3416x1440, FSR 4 Native
// AA) the depth's last row kept its clear value through every scene pass and the light target held the fog's colour
// there, which the upscaler blended into a grey line along the bottom and right edges of the picture that flickered with
// the jitter; with NX2NAVI_DLAA_JITTER=0, and without DLSS or FSR, there was no line. Before DLSS or FSR reads the scene,
// such a column or row of the light target and of the depth (which the motion vectors are made from) is filled from the
// one next to it. NX2NAVI_DLAA_EDGE_FILL=0 (test): not filled.
static void edge_fill(VkCommandBuffer cb, const VkExecTarget *t, const VkExecTarget *z, float jx, float jy) {
static int on = -1;
if (on < 0) on = rt_knob("DLAA_EDGE_FILL", "1");
uint32_t w = t->width, h = t->height;
if (!on || w < 2 || h < 2 || (jx >= 0 && jy >= 0)) return;
static int told;
if (!told++) rt_log("dlaa: the scene's last column or row after a jitter to the left or up is filled from the one before it");
// the column first, then the row over the whole width, so the corner comes from the filled column
for (int pass = 0; pass < 2; pass++) {
if (pass ? jy >= 0 : jx >= 0) continue;
VkOffset3D from = pass ? (VkOffset3D){0, (int32_t)h - 2, 0} : (VkOffset3D){(int32_t)w - 2, 0, 0};
VkOffset3D to = pass ? (VkOffset3D){0, (int32_t)h - 1, 0} : (VkOffset3D){(int32_t)w - 1, 0, 0};
VkExtent3D ext = pass ? (VkExtent3D){w, 1, 1} : (VkExtent3D){1, h, 1};
for (int i = 0; i < 2; i++) {
const VkExecTarget *g = i ? z : t;
if (!g->image || g->width != w || g->height != h) continue;
VkImageAspectFlags a = i ? VK_IMAGE_ASPECT_DEPTH_BIT : VK_IMAGE_ASPECT_COLOR_BIT;
VkImageCopy r = {.srcSubresource = {a, g->level, g->base_layer, 1}, .srcOffset = from,
.dstSubresource = {a, g->level, g->base_layer, 1}, .dstOffset = to, .extent = ext};
vkCmdCopyImage(cb, g->image, VK_IMAGE_LAYOUT_GENERAL, g->image, VK_IMAGE_LAYOUT_GENERAL, 1, &r);
}
full_barrier(cb);
}
}
// Set when the scene the upscaler runs on has another size than the last one; vk_dlaa_resized_take reads it.
static volatile int32_t size_changed;
@@ -1799,6 +1833,7 @@ static void evaluate(VkExec *ex, const VkExecTarget *dst) {
VkImageView zv = vk_exec_target_view(z->img, z->format, VK_IMAGE_VIEW_TYPE_2D, VK_IMAGE_ASPECT_DEPTH_BIT, z->level,
z->base_layer, 1, 0);
float jx = D.jit_used ? D.ux : 0.0f, jy = D.jit_used ? D.uy : 0.0f;
edge_fill(cb, t, z, jx, jy); // before the motion vectors read the depth
static int no_mv = -1;
if (no_mv < 0) no_mv = !rt_knob("DLAA_MV", "1"); // NX2NAVI_DLAA_MV=0: zero motion (test)
int masked = 0, mv = no_mv ? 0 : mv_pass(ex, e, zv, jx, jy, char_bias(), &masked);