rendervulkan: Emulate bilinear sampling when we need to do custom degammas

Fixes blending of PQ/HDR10 and 10-bit sRGB surfaces
This commit is contained in:
Joshua Ashton 2023-10-05 03:53:33 +01:00 committed by Joshie
parent 23d2f8745c
commit e17627beef
17 changed files with 111 additions and 24 deletions

View file

@ -28,7 +28,9 @@ enum class GamescopeUpscaleFilter : uint32_t
LINEAR = 0,
NEAREST,
FSR,
NIS
NIS,
FROM_VIEW = 255, // internal
};
enum class GamescopeUpscaleScaler : uint32_t

View file

@ -3107,11 +3107,6 @@ std::shared_ptr<CVulkanTexture> vulkan_create_texture_from_bits( uint32_t width,
return pTex;
}
bool float_is_integer(float x)
{
return fabsf(ceilf(x) - x) <= 0.0001f;
}
static uint32_t s_frameId = 0;
void vulkan_garbage_collect( void )
@ -3167,6 +3162,9 @@ struct BlitPushData_t
uint32_t frameId;
uint32_t blurRadius;
uint8_t u_shaderFilter[k_nMaxLayers];
uint8_t u_padding[2];
float u_linearToNits; // unset
float u_nitsToLinear; // unset
float u_itmSdrNits; // unset
@ -3179,6 +3177,11 @@ struct BlitPushData_t
scale[i] = layer->scale;
offset[i] = layer->offsetPixelCenter();
opacity[i] = layer->opacity;
if (layer->isScreenSize() || (layer->filter == GamescopeUpscaleFilter::LINEAR && layer->viewConvertsToLinearAutomatically()))
u_shaderFilter[i] = (uint32_t)GamescopeUpscaleFilter::FROM_VIEW;
else
u_shaderFilter[i] = (uint32_t)layer->filter;
}
borderMask = frameInfo->borderMask();
frameId = s_frameId++;
@ -3194,6 +3197,7 @@ struct BlitPushData_t
scale[0] = { blit_scale, blit_scale };
offset[0] = { 0.5f, 0.5f };
opacity[0] = 1.0f;
u_shaderFilter[0] = (uint32_t)GamescopeUpscaleFilter::LINEAR;
borderMask = 0;
frameId = s_frameId;
@ -3258,6 +3262,9 @@ struct RcasPushData_t
uint32_t u_frameId;
uint32_t u_c1;
uint8_t u_shaderFilter[k_nMaxLayers];
uint8_t u_padding[2];
float u_linearToNits; // unset
float u_nitsToLinear; // unset
float u_itmSdrNits; // unset
@ -3270,6 +3277,7 @@ struct RcasPushData_t
u_layer0Offset.x = uint32_t(int32_t(frameInfo->layers[0].offset.x));
u_layer0Offset.y = uint32_t(int32_t(frameInfo->layers[0].offset.y));
u_opacity[0] = frameInfo->layers[0].opacity;
u_shaderFilter[0] = (uint32_t)GamescopeUpscaleFilter::FROM_VIEW;
u_borderMask = frameInfo->borderMask() >> 1u;
u_frameId = s_frameId++;
u_c1 = tmp.x;
@ -3311,12 +3319,7 @@ void bind_all_layers(CVulkanCmdBuffer* cmdBuffer, const struct FrameInfo_t *fram
{
const FrameInfo_t::Layer_t *layer = &frameInfo->layers[i];
bool bForceNearest = layer->scale.x == 1.0f &&
layer->scale.y == 1.0f &&
float_is_integer(layer->offset.x) &&
float_is_integer(layer->offset.y);
bool nearest = bForceNearest | !layer->linearFilter;
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);

View file

@ -10,6 +10,8 @@
#include <array>
#include <bitset>
#include "main.hpp"
#include "shaders/descriptor_set_constants.h"
class CVulkanCmdBuffer;
@ -242,6 +244,11 @@ struct vec2_t
float x, y;
};
static inline bool float_is_integer(float x)
{
return fabsf(ceilf(x) - x) <= 0.0001f;
}
struct FrameInfo_t
{
bool useFSRLayer0;
@ -266,8 +273,9 @@ struct FrameInfo_t
float opacity;
GamescopeUpscaleFilter filter = GamescopeUpscaleFilter::LINEAR;
bool blackBorder;
bool linearFilter;
bool applyColorMgmt; // drm only
bool allowBlending; // drm only
@ -281,6 +289,18 @@ struct FrameInfo_t
return tex->isYcbcr();
}
bool isScreenSize() const {
return scale.x >= 0.99f && scale.x <= 1.01f &&
scale.y >= 0.99f && scale.y <= 1.01f &&
float_is_integer(offset.x) &&
float_is_integer(offset.y);
}
bool viewConvertsToLinearAutomatically() const {
return colorspace == GAMESCOPE_APP_TEXTURE_COLORSPACE_LINEAR ||
colorspace == GAMESCOPE_APP_TEXTURE_COLORSPACE_SCRGB;
}
uint32_t integerWidth() const { return tex->width() / scale.x; }
uint32_t integerHeight() const { return tex->height() / scale.y; }
vec2_t offsetPixelCenter() const

View file

@ -7,6 +7,9 @@ uniform layers_t {
uint u_frameId;
uint u_blur_radius;
uint8_t u_shaderFilter[VKR_MAX_LAYERS];
uint8_t u_padding[2];
// hdr
float u_linearToNits; // sdr -> hdr
float u_nitsToLinear; // hdr -> sdr

View file

@ -71,6 +71,41 @@ vec3 apply_layer_color_mgmt(vec3 color, uint colorspace) {
return color;
}
vec4 sampleBilinear(sampler2D tex, vec2 coord, uint colorspace, bool unnormalized) {
vec2 scale = unnormalized ? vec2(1.0) : vec2(textureSize(tex, 0));
vec2 pixCoord = coord * scale - 0.5f;
vec2 originPixCoord = floor(pixCoord);
vec2 gatherUV = (originPixCoord * scale + 1.0f) / scale;
vec4 red = textureGather(tex, gatherUV, 0);
vec4 green = textureGather(tex, gatherUV, 1);
vec4 blue = textureGather(tex, gatherUV, 2);
vec4 alpha = textureGather(tex, gatherUV, 3);
vec4 c00 = vec4(red.w, green.w, blue.w, alpha.w);
vec4 c01 = vec4(red.x, green.x, blue.x, alpha.x);
vec4 c11 = vec4(red.y, green.y, blue.y, alpha.y);
vec4 c10 = vec4(red.z, green.z, blue.z, alpha.z);
c00.rgb = colorspace_plane_degamma_tf(c00.rgb, colorspace);
c01.rgb = colorspace_plane_degamma_tf(c01.rgb, colorspace);
c11.rgb = colorspace_plane_degamma_tf(c11.rgb, colorspace);
c10.rgb = colorspace_plane_degamma_tf(c10.rgb, colorspace);
vec2 filterWeight = pixCoord - originPixCoord;
vec4 temp0 = mix(c01, c11, filterWeight.x);
vec4 temp1 = mix(c00, c10, filterWeight.x);
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]);
@ -89,13 +124,12 @@ vec4 sampleLayerEx(sampler2D layerSampler, uint offsetLayerIdx, uint colorspaceL
if (!unnormalized)
coord /= texSize;
vec4 color = textureLod(layerSampler, coord, 0.0f);
// TODO(Josh): If colorspace != linear, emulate bilinear ourselves to blend
// in linear space!
// Split this into two parts!
uint colorspace = get_layer_colorspace(colorspaceLayerIdx);
color.rgb = colorspace_plane_degamma_tf(color.rgb, colorspace);
vec4 color;
if (u_shaderFilter[offsetLayerIdx] == filter_linear_emulated)
color = sampleBilinear(layerSampler, coord, colorspace, unnormalized);
else
color = sampleRegular(layerSampler, coord, colorspace, unnormalized);
color.rgb = apply_layer_color_mgmt(color.rgb, colorspace);
return color;

View file

@ -1,6 +1,7 @@
#version 450
#extension GL_GOOGLE_include_directive : require
#extension GL_EXT_shader_explicit_arithmetic_types_int8 : require
#include "descriptor_set.h"

View file

@ -1,6 +1,7 @@
#version 450
#extension GL_GOOGLE_include_directive : require
#extension GL_EXT_shader_explicit_arithmetic_types_int8 : require
#include "descriptor_set.h"

View file

@ -1,6 +1,7 @@
#version 450
#extension GL_GOOGLE_include_directive : require
#extension GL_EXT_shader_explicit_arithmetic_types_int8 : require
#include "descriptor_set.h"

View file

@ -1,6 +1,7 @@
#version 460
#extension GL_GOOGLE_include_directive : require
#extension GL_EXT_shader_explicit_arithmetic_types_int8 : require
#include "descriptor_set.h"
@ -19,6 +20,9 @@ uniform layers_t {
uint u_frameId;
uint u_c1;
uint8_t u_shaderFilter[VKR_MAX_LAYERS];
uint8_t u_padding[2];
// hdr
float u_linearToNits;
float u_nitsToLinear;

View file

@ -1,6 +1,7 @@
#version 460
#extension GL_GOOGLE_include_directive : require
#extension GL_EXT_shader_explicit_arithmetic_types_int8 : require
#include "descriptor_set.h"

View file

@ -2,6 +2,7 @@
#extension GL_GOOGLE_include_directive : require
#extension GL_EXT_shader_explicit_arithmetic_types_float16 : require
#extension GL_EXT_shader_explicit_arithmetic_types_int8 : require
#include "descriptor_set.h"

View file

@ -1,6 +1,7 @@
#version 460
#extension GL_GOOGLE_include_directive : require
#extension GL_EXT_shader_explicit_arithmetic_types_int8 : require
#include "descriptor_set.h"
@ -18,6 +19,9 @@ uniform layers_t {
uint u_frameId;
uint u_blur_radius;
uint8_t u_shaderFilter[VKR_MAX_LAYERS];
uint8_t u_padding[2];
// hdr
float u_linearToNits;
float u_nitsToLinear;

View file

@ -1,6 +1,7 @@
#version 450
#extension GL_GOOGLE_include_directive : enable
#extension GL_EXT_shader_explicit_arithmetic_types_int8 : require
#define NIS_GLSL 1
#define NIS_SCALER 1

View file

@ -2,6 +2,7 @@
#extension GL_GOOGLE_include_directive : enable
#extension GL_EXT_shader_explicit_arithmetic_types_float16 : require
#extension GL_EXT_shader_explicit_arithmetic_types_int8 : require
#define NIS_GLSL 1
#define NIS_USE_HALF_PRECISION 1

View file

@ -1,6 +1,7 @@
#version 450
#extension GL_GOOGLE_include_directive : require
#extension GL_EXT_shader_explicit_arithmetic_types_int8 : require
#include "descriptor_set.h"
@ -15,6 +16,7 @@ layout(
// UVUVUVUVUVUVUVU...
const uint u_frameId = 0;
const uint8_t u_shaderFilter[VKR_MAX_LAYERS] = { uint8_t(0), uint8_t(0), uint8_t(0), uint8_t(0), uint8_t(0), uint8_t(0) };
const float u_linearToNits = 400.0f;
const float u_nitsToLinear = 1.0f / 100.0f;
const float u_itmSdrNits = 100.f;

View file

@ -16,6 +16,12 @@ const int colorspace_pq = 3;
const int colorspace_reserved = 3;
const int colorspace_max_bits = 2;
const int filter_linear_emulated = 0;
const int filter_nearest = 1;
const int filter_fsr = 2;
const int filter_nis = 3;
const int filter_from_view = 255;
const int EOTF_Gamma22 = 0;
const int EOTF_PQ = 1;
const int EOTF_Count = 2;

View file

@ -1943,7 +1943,7 @@ void MouseCursor::paint(steamcompmgr_win_t *window, steamcompmgr_win_t *fit, str
layer->tex = m_texture;
layer->fbid = BIsNested() ? 0 : m_texture->fbid();
layer->linearFilter = cursor_scale != 1.0f;
layer->filter = cursor_scale != 1.0f ? GamescopeUpscaleFilter::LINEAR : GamescopeUpscaleFilter::NEAREST;
layer->blackBorder = false;
layer->colorspace = GAMESCOPE_APP_TEXTURE_COLORSPACE_SRGB;
}
@ -1973,6 +1973,7 @@ struct BaseLayerInfo_t
float scale[2];
float offset[2];
float opacity;
GamescopeUpscaleFilter filter;
};
std::array< BaseLayerInfo_t, HELD_COMMIT_COUNT > g_CachedPlanes = {};
@ -1994,7 +1995,7 @@ paint_cached_base_layer(const std::shared_ptr<commit_t>& commit, const BaseLayer
layer->tex = commit->vulkanTex;
layer->fbid = commit->fb_id;
layer->linearFilter = true;
layer->filter = base.filter;
layer->blackBorder = true;
}
@ -2194,7 +2195,7 @@ paint_window(steamcompmgr_win_t *w, steamcompmgr_win_t *scaleW, struct FrameInfo
layer->tex = lastCommit->vulkanTex;
layer->fbid = lastCommit->fb_id;
layer->linearFilter = (w->isOverlay || w->isExternalOverlay) ? true : g_upscaleFilter != GamescopeUpscaleFilter::NEAREST;
layer->filter = (w->isOverlay || w->isExternalOverlay) ? GamescopeUpscaleFilter::LINEAR : g_upscaleFilter;
layer->colorspace = lastCommit->colorspace();
if ( flags & PaintWindowFlag::BasePlane )
@ -2205,6 +2206,7 @@ paint_window(steamcompmgr_win_t *w, steamcompmgr_win_t *scaleW, struct FrameInfo
basePlane.offset[0] = layer->offset.x;
basePlane.offset[1] = layer->offset.y;
basePlane.opacity = layer->opacity;
basePlane.filter = layer->filter;
g_CachedPlanes[ HELD_COMMIT_BASE ] = basePlane;
if ( !(flags & PaintWindowFlag::FadeTarget) )
@ -2666,7 +2668,7 @@ paint_all(bool async)
baseLayer->applyColorMgmt = false;
baseLayer->allowBlending = false;
baseLayer->linearFilter = false;
baseLayer->filter = GamescopeUpscaleFilter::NEAREST;
baseLayer->colorspace = g_bOutputHDREnabled ? GAMESCOPE_APP_TEXTURE_COLORSPACE_HDR10_PQ : GAMESCOPE_APP_TEXTURE_COLORSPACE_SRGB;
g_bWasPartialComposite = false;
@ -2692,7 +2694,7 @@ paint_all(bool async)
overlayLayer->applyColorMgmt = g_ColorMgmt.pending.enabled;
overlayLayer->allowBlending = g_ColorMgmt.pending.enabled;
overlayLayer->linearFilter = false;
overlayLayer->filter = GamescopeUpscaleFilter::NEAREST;
// Partial composition stuff has the same colorspace.
// So read that from the composite frame info
overlayLayer->colorspace = compositeFrameInfo.layers[0].colorspace;