Skip to content

Stabilize viewport behavior under FSR2#110005

Closed
mechalynx wants to merge 1 commit intogodotengine:masterfrom
mechalynx:viewport_limit_fix
Closed

Stabilize viewport behavior under FSR2#110005
mechalynx wants to merge 1 commit intogodotengine:masterfrom
mechalynx:viewport_limit_fix

Conversation

@mechalynx
Copy link
Copy Markdown
Contributor

@mechalynx mechalynx commented Aug 27, 2025

Edit - Fix without the resizing is here:

Below is the original message


When a subviewport using FSR2 upscaling falls below 64px on at least one dimension it produces this error spam:

ERROR: Condition "p_job.uavMip[i] >= mip_slice_rids.size()" is true.
Returning: FFX_ERROR_INVALID_ARGUMENT
at: execute_gpu_job_compute_rd
(servers\rendering\renderer_rd\effects\fsr2.cpp:418)

Additionally, it was possible through the scaling factor to have either dimension fall below 2px, crashing the editor.

While testing using the MRP from the linked issue, I found that it was also possible to set the dimensions of the viewport above 16384, which does not crash the editor but causes updating of the subviewport to stop (at least when rendering to a viewport texture) until the editor is restarted (or presumably until rendering of the subviewport is restarted somehow). It seems to me that the code intended to clamp sizes within a valid range but the enforcing was inconsistent, possibly because it assumes the viewport provided to _configure_3d_render_buffers() has a valid size, as the original code makes much more sense with that assumption.

Also, when the code falls back to Bilinear from FSR2, the scaling type is not re-evaluated, leading to it rendering with Bilinear scaling but still using jitter, which causes a lot of wobble, especially on smaller subviewport sizes.

This PR:

  • Enforces an upper limit of 16384 to subviewport dimensions

    • This upper limit is hardcoded in various parts of the rendering code and is a sensible value, considering most modern devices support this upper limit and few support higher, so I just added it as a raw value where relevant. I feel it is used so much it should be defined by the preprocessor but it's not a big deal at the moment. This fixes crashing the render thread by exceeding maximum texture limits through a scale factor above 1.0.
  • Restores the proper scaling mode type after fallback to Bilinear. This fixes jitter that can occur due to fallback to Bilinear.

  • Ensures that the render size of a subviewport that uses FSR2 will always be of valid dimensions, preserving aspect ratio and also ensuring the target size is always equal to or larger than the render size, to preserve a scaling of 1.0 or less. This fixes the error spam and editor crash referenced in the linked issue.

    • This is done by first scaling up the render size so the smaller dimension is at least 2px, then scaling it up again so the larger dimension is at least 64px. The scaling is done on both dimensions proportionally on each pass so as to preserve the aspect ratio and prevent stretching. The scaling up is the minimal possible. The target size is then put through the same process to ensure it is always equal to or larger than the render size on both dimensions (so we're never accidentally downscaling, since this is not supposed to be done with FSR2).

    • Scaling factor is not preserved due to this adjustment but I feel that the intent of the user to use FSR2 is to improve performance and render to a lower resolution, so it makes more sense to preserve the aspect ratio while also keeping it as small as possible, rather than trying to preserve the user-defined render scale. Trying to preserve the original scaling factor can result in the target size growing enormously, which defeats the purpose of using FSR2 on any scale other than 1.0, and at a scaling of 1.0 all we need to do is make sure the dimensions are valid for the subviewport, which is already guaranteed due to the previous changes.

Possible concerns:

  • The changes don't fix FSR2 so that it won't choke on render sizes that are below 64px on both dimensions, nor does it address it crashing the editor when the size is below 2px on either of them. I'm not sure if this is due to FSR2's nature or if there is a bug somewhere but if it is the latter, this PR's fix will mask this error and make it harder to trigger, so likely harder to debug and fix in the future. Since the previous behavior can't be toggled back, anyone trying to fix the actual error might be in for a lot of trouble if they don't know about this adjustment.

    • I also never figured out why that specific error was occuring. I feel like it might be related to mipmap issues that occur in relation to FSR2 elsewhere but I didn't manage to find anything to prove or disprove this, so I gave up on investigating the mipmap detail.
  • Although the target size is adjusted, if the subviewport is set to 2x2, the preview seems to show just 4 pixels being rendered, when the render size internally should be 64x64 due to the adjustment and so should the target size. I'm not sure what to make of this or if it's a concern but it feels like I may have misunderstood how this whole thing works and I've made the wrong changes. I went through this code with gdb several times but I think the rendering is done on a separate thread so I could only step through the render buffer creation but not what follows it up to the point of rendering (and I'm guessing it would take forever just to step through all that, given how often even _configure_3d_render_buffers() gets triggered even on the UI).

  • The new code contains multiple if statements and I'm unsure if I should write it differently to make branch prediction easier. It isn't a particular performance issue in most cases but it might be if someone is just using the slider in the editor, since every change requires going through this code. However, this new code only runs if the dimensions are too small, so for most cases it doesn't run anyway but it might cause failed branch predictions on the cpu. I'm uncertain about whether something needs to be done about this or not. I wrote things in a way that seemed nice to me but i'm not sure if that's the most clear and maintainable way to do it. I would also have liked to do this whole thing branchless, with just one fancy calculation but I couldn't figure out how to do it and even then, it might be a bad idea for maintainability. Plus, the clamps and max macros already run conditionals as it is. Fingers crossed for the compiler to guess correctly and optimise it.

  • I would have preferred to make a utility function to do this size adjustment, since it's done once for the render size and once again for the target size and it's the same calculation. It can be done without side-effects, so it would be a pure function and it would make the code more concise and thus easier to read, whereas now it is a block of ifs, but i have no idea where i would put this function or even if it should start with an underscore and things like that. It feels icky because I removed repetitive code only to introduce repetitive code of my own but I was unsure about what is preferred within the codebase.

possible questions:

  • "Why not just turn scaling to bilinear when going below 64x64 and be done with it?"
    • This is what I did at some point but FSR2 still has a much sharper look even at these low render sizes, which may be desirable. So I decided it wasn't good enough because there is some Legitimate Use Case (tm) for small viewports using FSR2 instead of bilinear
  • "Why not just clamp the render size?"
    • Because that can easily mess up the aspect ratio, which was obvious when I did do this (it was what I tried first) and then went into the editor and played around with the dimensions. The crash and errors were impossible to trigger but once you fell below 64px on either dimension, you got a square render size which, when applied to a non-square viewport size, resulted in squashing and distortion which is very unlikely to feel expected by the user. So it's wasteful but also, wrong.

addendum:

Edit - Added link to spreadsheet that shows how the resizing code behaves from small viewports, high aspect ratios, up to large viewports.

Edit 2 - added link to alternative PR at the top

@mechalynx mechalynx requested review from a team as code owners August 27, 2025 04:46
@AThousandShips AThousandShips changed the title Fix #105509 and stabilize viewport behavior under FSR2 Stabilize viewport behavior under FSR2 Aug 27, 2025
@AThousandShips AThousandShips added bug topic:rendering cherrypick:4.4 Considered for cherry-picking into a future 4.4.x release cherrypick:4.5 Considered for cherry-picking into a future 4.5.x release labels Aug 27, 2025
@AThousandShips AThousandShips added this to the 4.6 milestone Aug 27, 2025
@Calinou
Copy link
Copy Markdown
Member

Calinou commented Aug 27, 2025

  • "Why not just turn scaling to bilinear when going below 64x64 and be done with it?"
    • This is what I did at some point but FSR2 still has a much sharper look even at these low render sizes, which may be desirable. So I decided it wasn't good enough because there is some Legitimate Use Case (tm) for small viewports using FSR2 instead of bilinear

Upscaling needs a somewhat large viewport resolution to work well, it'll never be useful at sizes below 64x64. It's fine to fall back to bilinear in this case.

@mechalynx
Copy link
Copy Markdown
Contributor Author

mechalynx commented Aug 27, 2025

Upscaling needs a somewhat large viewport resolution to work well, it'll never be useful at sizes below 64x64. It's fine to fall back to bilinear in this case.

Perhaps. I just felt that the less the fix intrudes upon the user's choices, the better. Falling back to Bilinear is permanent unless the user sets it to FSR2 again. The fix already will cause surprise and problems for some people and by looking at the way people use SubViewport, I can't say I feel confident in predicting what people might want to do that might be creative but unexpected. A 64x64 FSR2 viewport may be useful to some people for generating a 3d portrait for instance (though I wouldn't really recommend that use). That's just how I see it though, I'm fine with whatever is decided.

I will however show an example of the difference so it's easier to decide if it's worth preserving FSR2 at these low resolutions or not.

FSR2 scaling 0.67 1 FSR2 scaling 1.0 Bilinear scaling 0.67 Bilinear scaling 1.0 2
64x64 image image image image
256x256 image image image image

All screenshots are from the artifact of this PR. You can see that, for example, the icon's edges are better antialiased with FSR2, whereas with Bilinear it eats the right, bottom and top edge but keeps the left edge sharp. The background is a bit sharper with FSR2 as well. Whether that's significant in practice or useful, I don't know.

I guess when I was testing this, I wrongly compared 0.67 bilinear with effectively 1.0 FSR2, so they looked much more different than they really are.

Edit - also, if the fix remains as I wrote it, I feel uncomfortable with it being backported to 4.4 because it may break projects. It's not severe enough to not be fixed in 4.5 in my opinion but I also don't know what's considered truly worth backporting or fixing early. But I felt like I had to clarify my position on that. I am cleaning up a spreadsheet I made back when I was writing the code to see how the size change code behaves with different combinations of dimensions, which should make clearer whether this fix is risky or not and how much, when it comes to potential compatibility with existing projects.

Edit 2 - added spreadsheet to main comment

Footnotes

  1. Due to the changes in this PR, this is effectively 1.0 scaling, not 0.67 (for the 64x64 case), I'm just using the setting in the inspector as the title.

  2. Prior to this PR if this was a fallback to Bilinear, it would have jitter. Now it won't, so this is static while the same sized viewport with FSR2 has jitter.

@mechalynx
Copy link
Copy Markdown
Contributor Author

mechalynx commented Aug 28, 2025

I have re-implemented this fix to not do any resizing and just fall back to Bilinear if any of the two dimensions of a viewport, after 3d scaling, fall below 64px. It's here: https://github.com/mechalynx/godot/tree/viewport_limit_fix_no_resize. If it's decided that resizing is not needed, then that version supercedes this one. I'm neutral on this: resizing is more minimal in taking control from the user but just falling back to bilinear is much simpler, much less code and will work for virtually everyone.

I'm not sure if I should push the changes into this pull request or make a new one that supercedes it, so I'll wait for advice on that so that I don't trigger build actions needlessly.

@mechalynx
Copy link
Copy Markdown
Contributor Author

Closing this in favor of #110119 since this one only does the extra resizing which is likely too tedious to review and also unnecessary as a fix.

Can reopen and rebase if needed of course.

@mechalynx mechalynx closed this Feb 27, 2026
@AThousandShips AThousandShips added archived and removed cherrypick:4.4 Considered for cherry-picking into a future 4.4.x release cherrypick:4.5 Considered for cherry-picking into a future 4.5.x release labels Feb 28, 2026
@AThousandShips AThousandShips removed this from the 4.x milestone Feb 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

FSR2 Crash and Error FFX_ERROR_INVALID_ARGUMENT

4 participants