rendervulkan: Add PIXEL filter (band-limited pixel filter)
Adds a "pixel" filter which is a band-limited pixel filter based on https://github.com/Themaister/Granite/blob/master/assets/shaders/inc/bandlimited_pixel_filter.h by Hans-Kristian. Closes: #712
This commit is contained in:
parent
e17627beef
commit
766f9b459c
8 changed files with 79 additions and 17 deletions
|
|
@ -149,7 +149,7 @@ const char usage[] =
|
|||
" -r, --nested-refresh game refresh rate (frames per second)\n"
|
||||
" -m, --max-scale maximum scale factor\n"
|
||||
" -S, --scaler upscaler type (auto, integer, fit, fill, stretch)\n"
|
||||
" -F, --filter upscaler filter (linear, nearest, fsr, nis)\n"
|
||||
" -F, --filter upscaler filter (linear, nearest, fsr, nis, pixel)\n"
|
||||
" fsr => AMD FidelityFX™ Super Resolution 1.0\n"
|
||||
" nis => NVIDIA Image Scaling v1.0.3\n"
|
||||
" --sharpness, --fsr-sharpness upscaler sharpness from 0 (max) to 20 (min)\n"
|
||||
|
|
@ -391,6 +391,8 @@ static enum GamescopeUpscaleFilter parse_upscaler_filter(const char *str)
|
|||
return GamescopeUpscaleFilter::FSR;
|
||||
} else if (strcmp(str, "nis") == 0) {
|
||||
return GamescopeUpscaleFilter::NIS;
|
||||
} else if (strcmp(str, "pixel") == 0) {
|
||||
return GamescopeUpscaleFilter::PIXEL;
|
||||
} else {
|
||||
fprintf( stderr, "gamescope: invalid value for --filter\n" );
|
||||
exit(1);
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ enum class GamescopeUpscaleFilter : uint32_t
|
|||
NEAREST,
|
||||
FSR,
|
||||
NIS,
|
||||
PIXEL,
|
||||
|
||||
FROM_VIEW = 255, // internal
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3319,7 +3319,10 @@ void bind_all_layers(CVulkanCmdBuffer* cmdBuffer, const struct FrameInfo_t *fram
|
|||
{
|
||||
const FrameInfo_t::Layer_t *layer = &frameInfo->layers[i];
|
||||
|
||||
bool nearest = layer->isScreenSize() || layer->filter == GamescopeUpscaleFilter::NEAREST || (layer->filter == GamescopeUpscaleFilter::LINEAR && !layer->viewConvertsToLinearAutomatically());
|
||||
bool nearest = layer->isScreenSize()
|
||||
|| layer->filter == GamescopeUpscaleFilter::NEAREST
|
||||
|| (layer->filter == GamescopeUpscaleFilter::LINEAR && !layer->viewConvertsToLinearAutomatically());
|
||||
|
||||
cmdBuffer->bindTexture(i, layer->tex);
|
||||
cmdBuffer->setTextureSrgb(i, false);
|
||||
cmdBuffer->setSamplerNearest(i, nearest);
|
||||
|
|
|
|||
|
|
@ -246,7 +246,12 @@ struct vec2_t
|
|||
|
||||
static inline bool float_is_integer(float x)
|
||||
{
|
||||
return fabsf(ceilf(x) - x) <= 0.0001f;
|
||||
return fabsf(ceilf(x) - x) <= 0.001f;
|
||||
}
|
||||
|
||||
inline bool close_enough(float a, float b, float epsilon = 0.001f)
|
||||
{
|
||||
return fabsf(a - b) <= epsilon;
|
||||
}
|
||||
|
||||
struct FrameInfo_t
|
||||
|
|
@ -290,8 +295,8 @@ struct FrameInfo_t
|
|||
}
|
||||
|
||||
bool isScreenSize() const {
|
||||
return scale.x >= 0.99f && scale.x <= 1.01f &&
|
||||
scale.y >= 0.99f && scale.y <= 1.01f &&
|
||||
return close_enough(scale.x, 1.0f) &&
|
||||
close_enough(scale.y, 1.0f) &&
|
||||
float_is_integer(offset.x) &&
|
||||
float_is_integer(offset.y);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -266,7 +266,7 @@ void inputSDLThreadRun( void )
|
|||
SDL_SetWindowFullscreen( g_SDLWindow, g_bFullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0 );
|
||||
break;
|
||||
case KEY_N:
|
||||
g_wantedUpscaleFilter = GamescopeUpscaleFilter::NEAREST;
|
||||
g_wantedUpscaleFilter = GamescopeUpscaleFilter::PIXEL;
|
||||
break;
|
||||
case KEY_B:
|
||||
g_wantedUpscaleFilter = GamescopeUpscaleFilter::LINEAR;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,45 @@
|
|||
#include "colorimetry.h"
|
||||
|
||||
vec4 sampleRegular(sampler2D tex, vec2 coord, uint colorspace) {
|
||||
vec4 color = textureLod(tex, coord, 0);
|
||||
color.rgb = colorspace_plane_degamma_tf(color.rgb, colorspace);
|
||||
return color;
|
||||
}
|
||||
|
||||
// To be considered pseudo-bandlimited, upscaling factor must be at least 2x.
|
||||
|
||||
const float bandlimited_PI = 3.14159265359;
|
||||
const float bandlimited_PI_half = 0.5 * bandlimited_PI;
|
||||
// size: resolution of sampled texture
|
||||
// inv_size: inverse resolution of sampled texture
|
||||
// extent: Screen-space gradient of UV in texels. Typically computed as (texture resolution) / (viewport resolution).
|
||||
// If screen is rotated by 90 or 270 degrees, the derivatives need to be computed appropriately.
|
||||
// For uniform scaling, none of this matters.
|
||||
// extent can be multiplied to achieve LOD bias.
|
||||
// extent must be at least 1.0 / 256.0.
|
||||
vec4 sampleBandLimited(sampler2D samp, vec2 uv, vec2 size, vec2 inv_size, vec2 extent, uint colorspace, bool unnormalized)
|
||||
{
|
||||
// Josh:
|
||||
// Clamp to behaviour like 4x scale (0.25).
|
||||
//
|
||||
// Was defaulted to 2x before (0.5), which is 1px, but gives blurry result
|
||||
// on Cave Story (480p) -> 800p on Deck.
|
||||
// TODO: Maybe make this configurable?
|
||||
const float max_extent = 0.25f;
|
||||
|
||||
// Get base pixel and phase, range [0, 1).
|
||||
vec2 pixel = uv * (unnormalized ? vec2(1.0f) : size) - 0.5;
|
||||
vec2 base_pixel = floor(pixel);
|
||||
vec2 phase = pixel - base_pixel;
|
||||
|
||||
// We can resolve the filter by just sampling a single 2x2 block.
|
||||
// Lerp between normal sampling at LOD 0, and bandlimited pixel filter at LOD -1.
|
||||
vec2 shift = 0.5 + 0.5 * sin(bandlimited_PI_half * clamp((phase - 0.5) / min(extent, vec2(max_extent)), -1.0, 1.0));
|
||||
uv = (base_pixel + 0.5 + shift) * (unnormalized ? vec2(1.0f) : inv_size);
|
||||
|
||||
return sampleRegular(samp, uv, colorspace);
|
||||
}
|
||||
|
||||
uint pseudo_random(uint seed) {
|
||||
seed ^= (seed << 13);
|
||||
seed ^= (seed >> 17);
|
||||
|
|
@ -101,12 +141,6 @@ vec4 sampleBilinear(sampler2D tex, vec2 coord, uint colorspace, bool unnormalize
|
|||
return mix(temp1, temp0, filterWeight.y);
|
||||
}
|
||||
|
||||
vec4 sampleRegular(sampler2D tex, vec2 coord, uint colorspace, bool unnormalized) {
|
||||
vec4 color = textureLod(tex, coord, 0);
|
||||
color.rgb = colorspace_plane_degamma_tf(color.rgb, colorspace);
|
||||
return color;
|
||||
}
|
||||
|
||||
vec4 sampleLayerEx(sampler2D layerSampler, uint offsetLayerIdx, uint colorspaceLayerIdx, vec2 uv, bool unnormalized) {
|
||||
vec2 coord = ((uv + u_offset[offsetLayerIdx]) * u_scale[offsetLayerIdx]);
|
||||
vec2 texSize = textureSize(layerSampler, 0);
|
||||
|
|
@ -126,10 +160,17 @@ vec4 sampleLayerEx(sampler2D layerSampler, uint offsetLayerIdx, uint colorspaceL
|
|||
|
||||
uint colorspace = get_layer_colorspace(colorspaceLayerIdx);
|
||||
vec4 color;
|
||||
if (u_shaderFilter[offsetLayerIdx] == filter_linear_emulated)
|
||||
if (u_shaderFilter[offsetLayerIdx] == filter_pixel) {
|
||||
vec2 output_res = texSize / u_scale[offsetLayerIdx];
|
||||
vec2 extent = max((texSize / output_res), vec2(1.0 / 256.0));
|
||||
color = sampleBandLimited(layerSampler, coord, unnormalized ? vec2(1.0f) : texSize, unnormalized ? vec2(1.0f) : vec2(1.0f) / texSize, extent, colorspace, unnormalized);
|
||||
}
|
||||
else if (u_shaderFilter[offsetLayerIdx] == filter_linear_emulated) {
|
||||
color = sampleBilinear(layerSampler, coord, colorspace, unnormalized);
|
||||
else
|
||||
color = sampleRegular(layerSampler, coord, colorspace, unnormalized);
|
||||
}
|
||||
else {
|
||||
color = sampleRegular(layerSampler, coord, colorspace);
|
||||
}
|
||||
color.rgb = apply_layer_color_mgmt(color.rgb, colorspace);
|
||||
|
||||
return color;
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ const int filter_linear_emulated = 0;
|
|||
const int filter_nearest = 1;
|
||||
const int filter_fsr = 2;
|
||||
const int filter_nis = 3;
|
||||
const int filter_pixel = 4;
|
||||
const int filter_from_view = 255;
|
||||
|
||||
const int EOTF_Gamma22 = 0;
|
||||
|
|
|
|||
|
|
@ -2198,6 +2198,13 @@ paint_window(steamcompmgr_win_t *w, steamcompmgr_win_t *scaleW, struct FrameInfo
|
|||
layer->filter = (w->isOverlay || w->isExternalOverlay) ? GamescopeUpscaleFilter::LINEAR : g_upscaleFilter;
|
||||
layer->colorspace = lastCommit->colorspace();
|
||||
|
||||
if (layer->filter == GamescopeUpscaleFilter::PIXEL)
|
||||
{
|
||||
// Don't bother doing more expensive filtering if we are sharp + integer.
|
||||
if (float_is_integer(currentScaleRatio_x) && float_is_integer(currentScaleRatio_y))
|
||||
layer->filter = GamescopeUpscaleFilter::NEAREST;
|
||||
}
|
||||
|
||||
if ( flags & PaintWindowFlag::BasePlane )
|
||||
{
|
||||
BaseLayerInfo_t basePlane = {};
|
||||
|
|
@ -2485,7 +2492,9 @@ paint_all(bool async)
|
|||
if ( !BIsNested() && g_nOutputRefresh != nTargetRefresh && g_uDynamicRefreshEqualityTime + g_uDynamicRefreshDelay < now )
|
||||
drm_set_refresh( &g_DRM, nTargetRefresh );
|
||||
|
||||
bool bNeedsNearest = g_upscaleFilter == GamescopeUpscaleFilter::NEAREST && frameInfo.layers[0].scale.x != 1.0f && frameInfo.layers[0].scale.y != 1.0f;
|
||||
bool bLayer0ScreenSize = close_enough(frameInfo.layers[0].scale.x, 1.0f) && close_enough(frameInfo.layers[0].scale.y, 1.0f);
|
||||
|
||||
bool bNeedsCompositeFromFilter = (g_upscaleFilter == GamescopeUpscaleFilter::NEAREST || g_upscaleFilter == GamescopeUpscaleFilter::PIXEL) && !bLayer0ScreenSize;
|
||||
|
||||
// Disable partial composition for now until we get
|
||||
// composite priorities working in libliftoff + also
|
||||
|
|
@ -2501,7 +2510,7 @@ paint_all(bool async)
|
|||
bNeedsFullComposite |= frameInfo.useFSRLayer0;
|
||||
bNeedsFullComposite |= frameInfo.useNISLayer0;
|
||||
bNeedsFullComposite |= frameInfo.blurLayer0;
|
||||
bNeedsFullComposite |= bNeedsNearest;
|
||||
bNeedsFullComposite |= bNeedsCompositeFromFilter;
|
||||
bNeedsFullComposite |= bDrewCursor;
|
||||
bNeedsFullComposite |= g_bColorSliderInUse;
|
||||
bNeedsFullComposite |= fadingOut;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue