ValveSoftware / gamescope

SteamOS session compositing window manager
Other
3.03k stars 198 forks source link

[Feature Request] Sharp Bilinear upscaling option for pixel-art games. #712

Closed Siam-Lights closed 11 months ago

Siam-Lights commented 1 year ago

Would it be possible to implement an option to use the Sharp-Bilinear-Simple GLSL shader on gamescope for better upscaling of pixel-art games? The point of this shader is to remove pixel shimmering caused by uneven pixels when games are upscaled to non-integer resolutions. Magpie has already implemented this, but that's a Windows-only application at the moment. Sharp bilinear example

sonic2kk commented 1 year ago

Just curious: Is this different from Integer scaling / Nearest Neighbor scaling? I haven't done any sort of extensive testing but -n improves the look of pixel art games to my eyes (e.g. Mega Man Legacy Collection) -- But I also haven't looked to deeply into it so it's possible there are various differences here.

ticky commented 1 year ago

Is this different from Integer scaling / Nearest Neighbor scaling?

Yes, this permits games taller than 400 pixels to have a clean looking upscale. For example, many RPG Maker VX Ace games are 416 pixels tall, so they cannot be integer scaled up to the Steam Deck's 800 pixels, for example. With nearest neighbour scaling, these generally have a less than pleasing appearance, with jagged looking uneven pixels as they swap between one and two pixels wide and tall, depending on the row or column.

Would absolutely love to see this option!

Siam-Lights commented 1 year ago

Oops, slip of the finger. As ticky said, nearest neighbor by itself can cause an ugly pixel shimmering on non-integer resolutions. It's ugly by itself and can look even more jarring in motion. Sharp-Bilinear upscales the image in two passes from what I understand. One is a nearest neighbor upscale to an integer scale, and the second upscales the rest with bilinear. stretched vs nonstretched

sonic2kk commented 1 year ago

For example, many RPG Maker VX Ace games are 416 pixels tall, so they cannot be integer scaled up to the Steam Deck's 800 pixels, for example.

Ahh, I didn't consider Steam Deck and this limitation.


Thanks for the image in the reply too.

What about GameScope's Integer scaling option though, with -i? If this is the same as Integer Scaling, does GameScope's integer scaling need improved (perhaps to use the mentioned shader in the OP)?

Also, just for clarity here, I'm not trying to be obtuse, or protest, or anything -- Really I am just curious of the benefits and if it was implemented, where there may be improvements :D Showing an image comparison helps a lot visually to see where an improvement could be made, and a user (like me) who may not have noticed before could say "Oh yeah, that would be awesome"

ticky commented 1 year ago

Here's a visual example I have prepared. Due to the Steam Deck not taking PNG screenshots I have prepared these examples in an image editor, but they use the same scaling algorithms we are discussing.

Please note that these images are likely downscaled in your browser within this GitHub comment (unless you click on them), and will not be accurate if you are viewing these images on a high density display (i.e. Retina display, Windows scaled to something other than 100%, etc.), or if they're displayed at a different scale in GitHub comments. On such devices, please consider opening these images in an image editor to view them at 1:1 scale.


Consider for example this screenshot of an RPG Maker VX Ace game. It is exactly 640 by 418 pixels high:

Q3AoP3@1x

On Steam Deck, this currently looks like this by default (scaled to 800 pixels high, black borders on left and right omitted):

Q3AoP3@1x@800h-bilinear

Take particular note of the blurry looking character outlines and the extra fuzzed out text.

Scaling to 800 pixels high with Nearest Neighbour would result in this:

Q3AoP3@800h-nearest

Note the unevenly shaped and sized pixels, most obvious on diagonal or curved lines like the edges of the fountain.

Here is the same screenshot, but first nearest-neighbour scaled to 816 pixels high (the nearest integer multiple which is larger than the target), and then bilinear scaled down to 800, equivalent to the Sharp Bilinear algorithm described in OP's shader (and available in emulators such as RetroArch for other games):

Q3AoP3@800h-sharp-bilinear

This image has perceptually better-preserved detail, and the sharpness of the pixel artwork is preserved. It's also beneficial versus nearest neighbour in motion, as it is not subject to a visual shimmering effect as details pass over the unevenly sized pixels.

Scaling graphics is obviously subjective, dependent on the game, and this one is my own preference, but I very much prefer the "sharp bilinear" look to the others in this situation.

sonic2kk commented 1 year ago

Thanks for the visual examples, I'm still not sure how this Sharp Bilinear filter is different from the -i flag you can pass to GameScope (which I think would be equivalent to Steam Deck's Integer option).

I agree that scaling is subjective and here I have no real preference between the last two screenshots, I'm moreso interested in how it differs from regular integer scaling. Particularly I'm interested because, is this filter something that should be changed with the integer scaling option already available in GameScope?

ticky commented 1 year ago

The Integer option on Steam Deck/Gamescope would display this at the original scale of 640 by 418, with black bars and unused space on all sides, because there is no integer multiple of 418 which fits within 800.

Integer scaling does not need this filter because it's always using an integer number of pixels on each axis to represent any given input pixel. This would be an enhancement to, or replacement of, the bilinear or nearest neighbour mode, which are often necessary when the output size is not capable of supporting an integer multiple of the input size.

zetaPRIME commented 1 year ago

This doesn't actually need a two-pass solution; it can be done in one shot via a simple shader that just applies a bit of math to the source image texture coordinates before it hits the GPU's bilinear filtering.

https://www.shadertoy.com/view/MlB3D3 https://www.shadertoy.com/view/ltBfRD

ticky commented 1 year ago

This doesn't actually need a two-pass solution

I hope I didn't give the impression that was required on the implementation end, I was just noting my methodology for simulating the appearance.

misyltoad commented 11 months ago

I added --filter pixel which uses this method https://themaister.net/blog/2018/08/25/pseudo-bandlimited-pixel-art-filtering-in-3d-a-mathematical-derivation/ but simplified for 2D.

It also limits the extent to 0.25 atm, but I would like to make that configurable at some point.

ticky commented 10 months ago

I notice as of the SteamOS 3.5.5 release (congrats to the team by the way!) that the build of gamescope includes this filter (maybe I missed it in an earlier stable release, not sure, only thought to check now), but it's not exposed in the Steam Deck UI, is there a way to enable it at run time or will it require invoking gamescope explicitly per game to adjust?

DPS2004 commented 8 months ago

I was wondering now that the Steam Deck UI has replaced the nearest neighbor filter with the pixel filter described in this issue, is there any way to access the nearest neighbor filter instead? As said earlier, what scaling method looks "best" is very subjective, and I prefer the nearest neighbor scaling for the vast majority of usecases.

If possible, I think a good solution would be to have them both be selectable in the UI, but a command line option to use nearest neighbor instead would also be appreciated greatly.

layercak3 commented 2 months ago

command line option to use nearest neighbor

-S nearest