gamescope icon indicating copy to clipboard operation
gamescope copied to clipboard

Implemented bicubic downscaling

Open ruanformigoni opened this issue 2 years ago • 51 comments

This PR implements a bicubic filter to remedy issue #692.

Update 10-mar-2023

Nier Automata (2160p)

Current gamescope result: gamescope_2023-03-10_17-28-34 raw_encoded

Proposal: gamescope_2023-03-10_17-28-40 raw_encoded

Need for Speed ProStreet (2160p)

Current gamescope result: gamescope_2023-03-10_17-41-31 raw_encoded

Proposal: gamescope_2023-03-10_17-41-34 raw_encoded

GTA IV (4320p)

Current gamescope result: 20230310165941_1

Proposal: 20230310165952_1

Dark Souls II (4320p)

Current gamescope result: screenshot-158

Proposal: screenshot-159

ruanformigoni avatar Jan 10 '23 17:01 ruanformigoni

Downsampling from 2160p to 1080p looks okay, but everything lower looks weird. Here is comparison between 1440p and 1080p with FXAA. 1440p looks a bit aliased and pixelated. In my opinion forced FSR downsampling still looks better. Screenshot_2023-01-12_15-30_12 Screenshot_2023-01-12_15-31_56

vic-bay avatar Jan 12 '23 13:01 vic-bay

you should probably look into Catmull-Rom (Bicubic with b=0 and c=0.5, rather than than b=0.33 and c=0.33) for downscaling rather than using normal bicubic. it's sharper and preserves edges the same as the default.

EndlesslyFlowering avatar Jan 20 '23 01:01 EndlesslyFlowering

I can confirm that resolutions that aren't double the monitor resolution look weird.

rKsanu2MMYvypWePtQWM avatar Jan 29 '23 10:01 rKsanu2MMYvypWePtQWM

Hey @EndlesslyFlowering , I've included the Catmull-Rom algorithm, thanks for bringing it to my attention. Thanks for the heads up @rKsanu2MMYvypWePtQWM, I've fixed this in the latest commits.

Here's the ranges of are shown below, the numbers represent the resolution multiplier from the monitor resolution and the render resolution, e.g., render at 2160p and display in 1080p == 2160/1080 == 2.

  • Range of [1,2) uses FSR Bilinear
  • Range of [2,3) uses Catmull-Rom with a mean of 4 samples
  • Range of [3,4) uses Catmull-Rom with a mean of 9 samples
  • Range of (4,inf) uses Catmull-Rom with a mean of 16 samples

You can toggle the downscaling effect with super + K

ruanformigoni avatar Mar 10 '23 21:03 ruanformigoni

Definitely making really great progress. I think to cap it all off you should add the ability for user to adjust the bicubic parameters of B and C. Catmull-rom has ringing, which increases perceived sharpness but I want to be able to adjust it.

Examples of different bicubic setups with the 2 parameters:

B-Spline(blur only): B1 C0

Hermite(blocking only): B0 C0

Mitchell(balanced minor amounts of blocking, aliasing, and ringing): B0.33 C0.33

Catmull-Rom(balance of ringing and blocking): B0 C0.5

Explanation of different scalers: https://artoriuz.github.io/blog/mpv_upscaling.html https://en.wikipedia.org/wiki/Mitchell%E2%80%93Netravali_filters

Image showing the effects of different bicubic parameters on scaled quality: https://artoriuz.github.io/blog/images/mpv_upscaling/mitchell_survey.png

Also, if it's not too far out of scope, these scalers(especially the ringy ones like catmull-rom or lanzcos) work better when you convert gamma to sigmoid light before scaling, and back to the native gamma afterwards.

Pic showing that with upscaling: https://artoriuz.github.io/blog/images/mpv_upscaling/kanao/kanao_comparison.png

This applies also to the downscaling(your PR can be used as both an upscaler or downscaler, if it still works like the first iteration)

DisplayTalk avatar Mar 10 '23 22:03 DisplayTalk

@Displaysguy thanks a ton for the explanation and resources! I'll study how to implement this properly and look into the possible method for argument passing you mentioned here.

ruanformigoni avatar Mar 10 '23 22:03 ruanformigoni

Another tidbit, this one is specifically on sigmoidization:

"You may decrease halos and increase perceptual sharpness by increasing the sigmoidal contrast (up to 11.5, say). Higher contrasts are especially recommended with greyscale images (even “false RGB greyscale” that have three proportional color channels). The downside of sigmoidization is that it sometimes produces “color bleed” artefacts that look a bit like cheap flexographic (”gummidruck”) printing or chromatic aberration. In addition, sigmoidization’s “fattening” of extreme light and dark values may not work for your image content. If such artefacts are obvious, push the contrast value down from 7.5 (to 5, for example, or even lower). Setting the contrast to 0 is equivalent to enlarging through linear RGB. (Robidoux, 2012)"

From this(and the MPV scaling article) we know that sigmoidization can lower both light and dark overshoot(overshoot=ringing) with ringy scalers on both upscaling and downscaling. It's always superior to linearization because linearization only accounts for light overshoot and not dark overshoot.

However, it is not without downside. It has negative effects on the image and in certain situations(even where using it makes sense, which is only when using ringy scaling configurations) might not even be a net benefit. Because of this I wouldn't make adding sigmoidization a priority, and if you do add it make sure it is user-adustable and can be disabled(and is not enabled by default).

https://guide.encode.moe/encoding/resampling.html (ctrl-f sigmoid to find the quote)

DisplayTalk avatar Mar 14 '23 09:03 DisplayTalk

I've had a chance to test this out a bit. Here's some test screenshots.

specfreq avatar Mar 15 '23 19:03 specfreq

Hi @specfreq , could you also include a screenshot with the downsampling algorithm disabled?

ruanformigoni avatar Mar 15 '23 21:03 ruanformigoni

@ruanformigoni Here's some new test screenshots. One thing I'm doing is changing the desktop resolution to match my target scaled resolution (1024x768 in this case) and forcing on vsync through the OS.

Launch options: gamescope -D -f -W 1024 -H 768 -w 4096 -h 3072 -- %command%

1024x768 Compositor

Let me know if I don't have it set up correctly.

specfreq avatar Mar 16 '23 05:03 specfreq

Thanks for the followup @specfreq. I did the same as you (change the desktop resolution and downscale) and got way better results, here is a comparison. Did you set the in-game render resolution to 4096x3072 in the resolution settings?

ruanformigoni avatar Mar 16 '23 10:03 ruanformigoni

20230316130642_1 20230316130057_1

The resolution is set to 4096x3072

Here's my git pull: Screenshot_20230316_130902

specfreq avatar Mar 16 '23 20:03 specfreq

Are you able to toggle it with super + k?

ruanformigoni avatar Mar 16 '23 20:03 ruanformigoni

I am not able to toggle it with super + k. I'm pretty new to using Git, should I just delete it and reinstall?

specfreq avatar Mar 16 '23 20:03 specfreq

That should be fine, make sure that lines 176-179 of the file src/sdlwindow.cpp are these:

case KEY_K:
	g_wantedUpscaleFilter = (g_wantedUpscaleFilter == GamescopeUpscaleFilter::BICUBIC) ?
		GamescopeUpscaleFilter::LINEAR : GamescopeUpscaleFilter::BICUBIC;
	break;

In your launch command gamescope -D -f -W 1024 -H 768 -w 4096 -h 3072 -- %command%, do could you point it directly to where it is compiled? E.g.: ~/Repositories/gamescope/build/gamescope -D -f -W 1024 -H 768 -w 4096 -h 3072 -- %command%.

Edit: You can also try with other keys, maybe change KEY_K to KEY_P , recompile, and try with super + p. By your screenshots the bicubic filter is not enabled.

ruanformigoni avatar Mar 16 '23 20:03 ruanformigoni

I just rebuilt it and it is working now.

specfreq avatar Mar 17 '23 08:03 specfreq

Here's a test video: https://youtu.be/mbMsGK45p-U

And high quality: https://youtu.be/6RVYbmluInk

I'd love to be able to use this with the integer scaling.

specfreq avatar Mar 29 '23 07:03 specfreq

any news? would love to see this finally merged...

rKsanu2MMYvypWePtQWM avatar Sep 26 '23 09:09 rKsanu2MMYvypWePtQWM

I highly suggest to take a look at this more advanced downsampling method: https://github.com/dolphin-emu/dolphin/pull/11999/commits/ca93a5191fef40947a6fac935ce814fca8a2a295 It's heavier, but it looks damn fantastic when scaling from a relatively higher resolution. On older games, this would be perfect and very temporally stable.

Filoppi avatar Sep 26 '23 09:09 Filoppi

I don't understand why you are still running the whole RCAS pass, surely we don't want sharpening after doing downscaling?

What's the rationale behind doing FSR for bilinear at certain ranges? Does it look better? I have my doubts there.

I think it also makes sense to have a g_wantedDownscaleFilter and g_downscaleFilter. Shouldn't affect upscaling at all. I would be happy to default downscaling method.

Then do like:

				bool needsUpScaling = frameInfo.layers[0].scale.x < 0.999f && frameInfo.layers[0].scale.y < 0.999f;
                bool needsDownScaling = frameInfo.layers[0].scale.x > 1.001f && frameInfo.layers[0].scale.y > 1.001f;
                frameInfo.useBICUBICLayer0 = g_downscaleFilter == GamescopeDownscaleFilter::BICUBIC && needsDownScaling;
				frameInfo.useFSRLayer0 = g_upscaleFilter == GamescopeUpscaleFilter::FSR && needsUpScaling;
				frameInfo.useNISLayer0 = g_upscaleFilter == GamescopeUpscaleFilter::NIS && needsUpScaling;

misyltoad avatar Sep 29 '23 23:09 misyltoad

Hi @Joshua-Ashton , it was because I had issues running without it. I seem to have done so now, it uses blit instead, is that how it is supposed to work? I tried to only use a single cmdBuffer dispatch, but that didn't work well.

FSR (or its bilinear filter) looks better when the resolution is lower than 2X. I can try to experiment with bilinear in this range or leave as bicubic (since this PR is for a bicubic filter, using anything other would be misleading), to confirm this I've implemented another more computationally expensive bicubic method:

vec4 textureBicubic( sampler2D textureSampler, vec2 texCoord )
{
	AF1 fWidth = AF1_AU1(c0.z);
	AF1 fHeight = AF1_AU1(c0.w);

    // Size of one texel 
	AF1 texelSizeX = ARcpF1(fWidth);
	AF1 texelSizeY = ARcpF1(fHeight);

    // Result
	vec4 nSum = vec4( 0.0, 0.0, 0.0, 0.0 );
	vec4 nDenom = vec4( 0.0, 0.0, 0.0, 0.0 );

    // Get the decimal part
	AF1 a = fract( texCoord.x * fWidth );
	AF1 b = fract( texCoord.y * fHeight );

    // B & C parameters
	float B = AF1_AU1(c2.x)/AF1_(100);
	float C = AF1_AU1(c2.y)/AF1_(100);

    // Bicubic calculation
	for( int m = -1; m <= 2; m++ )
	{
        for( int n = -1; n <= 2; n++)
        {
            vec4 vecData = texture(textureSampler, texCoord + vec2(texelSizeX * float( m ), texelSizeY * float( n )));
            float f1 = catMullRom( B, C, float( m ) - a );
            float f2 = catMullRom( B, C, -( float( n ) - b ) );
            vec4 vecCooef1 = vec4( f1,f1,f1,f1 );
            vec4 vecCoeef2 = vec4( f2, f2, f2, f2 );
            nSum = nSum + ( vecData * vecCoeef2 * vecCooef1  );
            nDenom = nDenom + (( vecCoeef2 * vecCooef1 ));
        }
	}
	return nSum / nDenom;
}

The results were almost identical to the current bicubic implementation, which is way faster. What do you think of an option such as --force-needs-scaling (not on this PR), this way one could use fsr or other filter independently for downscaling or upscaling.

Also, what do you thing about the passing of the B and C parameters? Is this -B 0.0,0.5 and --bicubic=0.0,0.5 ok or would you like to have only fixed values?

I have implemented the requested g_downscaleFilter and g_wantedDownscaleFilter

ruanformigoni avatar Oct 08 '23 04:10 ruanformigoni

I am so sorry, I didn't see this until now as it was posted when I was on holiday.

I think it's best to just keep downscale filter separate

misyltoad avatar Jan 20 '24 00:01 misyltoad

Hi @Joshua-Ashton , no problem :smile: . I'll try to improve the filter when possible, there is still this issue pointed out by @DisplayTalk .

ruanformigoni avatar Jan 26 '24 19:01 ruanformigoni

Hey @ruanformigoni , any updates about this? :slightly_smiling_face: Seems it conflicts with the master branch

Also, is there going to be a way to use the bilinear filter for downscaling? For some people the current implementation of bicubic seeems a bit blurry in some cases, and those configurable B and C params (no clue what they do) seem to not affect the actual game on my end no matter what values I force them to, including small fractional and high integer. (6880x2880 down to 3440x1440)

One could technically hack FSR to also downscale (doesn't allow it by default), and it kinda works, but it wasn't designed for it and it's probably not a good idea.

gapys-krzysztof avatar Apr 04 '24 09:04 gapys-krzysztof

I'd love to have something like this on gamescope. It's really nice for older games because instead of having the GPU sit at 30% usage doing mostly nothing, you can push it a bit and get very nice image quality in return.

dustContributor avatar Jun 23 '24 03:06 dustContributor

Progress update?

LethalManBoob avatar Jul 20 '24 13:07 LethalManBoob

Is there a way to get this to work? I tried following the steps for building, but it refused to build. Using FSR for downsampling did build and run, however it was unusable.

Comptr-user avatar Aug 06 '24 02:08 Comptr-user