scene: Add pixel grid snapping debug pass

The debug shader is targeted to help with debugging blurriness when
using fractional scaling.

The shader works as follows:

- if the vertex coordinate has fractional part, the item will be
  highlighted with blue color
- if the texture coordinate (in device pixels) has fractional part,
  the item will be highlighted with red color

The shader can be toggled by setting the KWIN_SCENE_VISUALIZE=fractional
environment variable.
This commit is contained in:
Vlad Zahorodnii 2023-04-13 11:16:56 +03:00
parent 6f5aa9e4f3
commit 07f6713a18
8 changed files with 249 additions and 4 deletions

View file

@ -120,6 +120,7 @@ target_sources(kwin PRIVATE
pointer_input.cpp
popup_input_filter.cpp
rootinfo_filter.cpp
resources.qrc
rulebooksettings.cpp
rules.cpp
scene/cursoritem.cpp

8
src/resources.qrc Normal file
View file

@ -0,0 +1,8 @@
<!DOCTYPE RCC><RCC version="1.0">
<qresource prefix="/">
<file>scene/shaders/debug_fractional.frag</file>
<file>scene/shaders/debug_fractional_core.frag</file>
<file>scene/shaders/debug_fractional.vert</file>
<file>scene/shaders/debug_fractional_core.vert</file>
</qresource>
</RCC>

View file

@ -19,6 +19,11 @@ namespace KWin
ItemRendererOpenGL::ItemRendererOpenGL()
{
const QString visualizeOptionsString = qEnvironmentVariable("KWIN_SCENE_VISUALIZE");
if (!visualizeOptionsString.isEmpty()) {
const QStringList visualtizeOptions = visualizeOptionsString.split(';');
m_debug.fractionalEnabled = visualtizeOptions.contains(QLatin1StringView("fractional"));
}
}
ImageItem *ItemRendererOpenGL::createImageItem(Scene *scene, Item *parent)
@ -255,6 +260,7 @@ void ItemRendererOpenGL::renderItem(const RenderTarget &renderTarget, const Rend
}
RenderContext renderContext{
.projectionMatrix = data.projectionMatrix(),
.clip = region,
.hardwareClipping = region != infiniteRegion() && ((mask & Scene::PAINT_WINDOW_TRANSFORMED) || (mask & Scene::PAINT_SCREEN_TRANSFORMED)),
.renderTargetScale = viewport.scale(),
@ -332,7 +338,6 @@ void ItemRendererOpenGL::renderItem(const RenderTarget &renderTarget, const Rend
scissorRegion = viewport.mapToRenderTarget(region);
}
const QMatrix4x4 projectionMatrix = data.projectionMatrix();
for (int i = 0; i < renderContext.renderNodes.count(); i++) {
const RenderNode &renderNode = renderContext.renderNodes[i];
if (renderNode.vertexCount == 0) {
@ -341,7 +346,7 @@ void ItemRendererOpenGL::renderItem(const RenderTarget &renderTarget, const Rend
setBlendEnabled(renderNode.hasAlpha || renderNode.opacity < 1.0);
shader->setUniform(GLShader::ModelViewProjectionMatrix, projectionMatrix * renderNode.transformMatrix);
shader->setUniform(GLShader::ModelViewProjectionMatrix, renderContext.projectionMatrix * renderNode.transformMatrix);
if (opacity != renderNode.opacity) {
shader->setUniform(GLShader::ModulationConstant,
modulate(renderNode.opacity, data.brightness()));
@ -356,15 +361,56 @@ void ItemRendererOpenGL::renderItem(const RenderTarget &renderTarget, const Rend
renderNode.vertexCount, renderContext.hardwareClipping);
}
ShaderManager::instance()->popShader();
if (m_debug.fractionalEnabled) {
visualizeFractional(viewport, scissorRegion, renderContext);
}
vbo->unbindArrays();
setBlendEnabled(false);
ShaderManager::instance()->popShader();
if (renderContext.hardwareClipping) {
glDisable(GL_SCISSOR_TEST);
}
}
void ItemRendererOpenGL::visualizeFractional(const RenderViewport &viewport, const QRegion &region, const RenderContext &renderContext)
{
if (!m_debug.fractionalShader) {
m_debug.fractionalShader = ShaderManager::instance()->generateShaderFromFile(
ShaderTrait::MapTexture,
QStringLiteral(":/scene/shaders/debug_fractional.vert"),
QStringLiteral(":/scene/shaders/debug_fractional.frag"));
}
if (!m_debug.fractionalShader) {
return;
}
ShaderBinder debugShaderBinder(m_debug.fractionalShader.get());
m_debug.fractionalShader->setUniform("fractionalPrecision", 0.01f);
auto screenSize = viewport.renderRect().size() * viewport.scale();
m_debug.fractionalShader->setUniform("screenSize", QVector2D(float(screenSize.width()), float(screenSize.height())));
GLVertexBuffer *vbo = GLVertexBuffer::streamingBuffer();
for (int i = 0; i < renderContext.renderNodes.count(); i++) {
const RenderNode &renderNode = renderContext.renderNodes[i];
if (renderNode.vertexCount == 0) {
continue;
}
setBlendEnabled(true);
m_debug.fractionalShader->setUniform("geometrySize", QVector2D(renderNode.texture->width(), renderNode.texture->height()));
m_debug.fractionalShader->setUniform(GLShader::ModelViewProjectionMatrix, renderContext.projectionMatrix * renderNode.transformMatrix);
vbo->draw(region, GL_TRIANGLES, renderNode.firstVertex,
renderNode.vertexCount, renderContext.hardwareClipping);
}
}
} // namespace KWin

View file

@ -34,6 +34,7 @@ public:
QVector<RenderNode> renderNodes;
QStack<QMatrix4x4> transformStack;
QStack<qreal> opacityStack;
const QMatrix4x4 projectionMatrix;
const QRegion clip;
const bool hardwareClipping;
const qreal renderTargetScale;
@ -53,8 +54,15 @@ private:
QVector4D modulate(float opacity, float brightness) const;
void setBlendEnabled(bool enabled);
void createRenderNode(Item *item, RenderContext *context);
void visualizeFractional(const RenderViewport &viewport, const QRegion &region, const RenderContext &renderContext);
bool m_blendingEnabled = false;
struct
{
bool fractionalEnabled = false;
std::unique_ptr<GLShader> fractionalShader;
} m_debug;
};
} // namespace KWin

View file

@ -0,0 +1,46 @@
/*
SPDX-FileCopyrightText: 2022 Arjen Hiemstra <ahiemstra@heimr.nl>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#version 130 compatibility
uniform float fractionalPrecision;
uniform vec2 geometrySize;
varying vec2 texcoord0;
varying float vertexFractional;
// paint every time we query textures at non-integer alignments
// it implies we're being upscaled in ways that will cause blurryness
// 2x scaling will go through fine
void main()
{
const float strength = 0.4;
// Calculate an error correction value based on the minimum precision we
// want to measure.
float errorCorrection = 1.0 / fractionalPrecision;
// Determine which exact pixel we are reading from the source texture.
// Texture sampling happens in the middle of a pixel so we need to add 0.5.
vec2 sourcePixel = texcoord0 * geometrySize + 0.5;
// Cancel out any precision artifacts below what we actually want to measure.
sourcePixel = round(sourcePixel * errorCorrection) / errorCorrection;
// The total error is the sum of the fractional parts of the source pixel.
float error = dot(fract(sourcePixel), vec2(1.0));
vec4 fragColor = vec4(0.0);
if (vertexFractional > 0.5) {
fragColor = mix(fragColor, vec4(0.0, 0.0, 1.0, 1.0), strength);
}
if (error > fractionalPrecision) {
fragColor = mix(fragColor, vec4(1.0, 0.0, 0.0, 1.0), strength);
}
gl_FragColor = fragColor;
}

View file

@ -0,0 +1,43 @@
/*
SPDX-FileCopyrightText: 2022 Arjen Hiemstra <ahiemstra@heimr.nl>
SPDX-License-Identifier: GPL-2.0-or-later
*/
uniform mat4 modelViewProjectionMatrix;
uniform float fractionalPrecision;
uniform vec2 screenSize;
uniform vec2 geometrySize;
attribute vec4 vertex;
attribute vec4 texcoord;
varying vec2 texcoord0;
varying float vertexFractional;
// This shader calculates the fractional component of the vertex position and
// passes 1 to the fragment shader if it is larger than the precision we want to
// measure, or 0 if it is not. The fragment shader can then use that information
// to color the pixel based on that value. 0 or 1 is used instead of something
// like vertex coloring because of vertex interpolation and the fragment shader
// having control over the final appearance.
void main(void)
{
float errorCorrection = 1.0 / fractionalPrecision;
gl_Position = modelViewProjectionMatrix * vertex;
vec2 screenPosition = ((gl_Position.xy / gl_Position.w + vec2(1.0)) / vec2(2.0)) * screenSize;
// Cancel out any floating point errors below what we want to measure.
screenPosition = round(screenPosition * errorCorrection) / errorCorrection;
// Dermine how far off the pixel grid this vertex is.
vec2 error = fract(screenPosition);
vertexFractional = dot(error, vec2(1.0)) > fractionalPrecision ? 1.0 : 0.0;
// Correct texture sampling for floating-point error on the vertices.
// This currently assumes UV coordinates are always from 0 to 1 over an
// entire triangle.
texcoord0 = texcoord.xy + (error / geometrySize);
}

View file

@ -0,0 +1,48 @@
/*
SPDX-FileCopyrightText: 2022 Arjen Hiemstra <ahiemstra@heimr.nl>
SPDX-FileCopyrightText: 2022 David Edmundson <davidedmundson@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#version 140
uniform float fractionalPrecision;
uniform vec2 geometrySize;
in vec2 texcoord0;
in float vertexFractional;
out vec4 fragColor;
// paint every time we query textures at non-integer alignments
// it implies we're being upscaled in ways that will cause blurryness
// 2x scaling will go through fine
void main()
{
const float strength = 0.4;
// Calculate an error correction value based on the minimum precision we
// want to measure.
float errorCorrection = 1.0 / fractionalPrecision;
// Determine which exact pixel we are reading from the source texture.
// Texture sampling happens in the middle of a pixel so we need to add 0.5.
vec2 sourcePixel = texcoord0 * geometrySize + 0.5;
// Cancel out any precision artifacts below what we actually want to measure.
sourcePixel = round(sourcePixel * errorCorrection) / errorCorrection;
// The total error is the sum of the fractional parts of the source pixel.
float error = dot(fract(sourcePixel), vec2(1.0));
fragColor = vec4(0.0);
if (vertexFractional > 0.5) {
fragColor = mix(fragColor, vec4(0.0, 0.0, 1.0, 1.0), strength);
}
if (error > fractionalPrecision) {
fragColor = mix(fragColor, vec4(1.0, 0.0, 0.0, 1.0), strength);
}
}

View file

@ -0,0 +1,45 @@
/*
SPDX-FileCopyrightText: 2022 Arjen Hiemstra <ahiemstra@heimr.nl>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#version 140
uniform mat4 modelViewProjectionMatrix;
uniform float fractionalPrecision;
uniform vec2 screenSize;
uniform vec2 geometrySize;
in vec4 vertex;
in vec4 texcoord;
out vec2 texcoord0;
out float vertexFractional;
// This shader calculates the fractional component of the vertex position and
// passes 1 to the fragment shader if it is larger than the precision we want to
// measure, or 0 if it is not. The fragment shader can then use that information
// to color the pixel based on that value. 0 or 1 is used instead of something
// like vertex coloring because of vertex interpolation and the fragment shader
// having control over the final appearance.
void main(void)
{
float errorCorrection = 1.0 / fractionalPrecision;
gl_Position = modelViewProjectionMatrix * vertex;
vec2 screenPosition = ((gl_Position.xy / gl_Position.w + vec2(1.0)) / vec2(2.0)) * screenSize;
// Cancel out any floating point errors below what we want to measure.
screenPosition = round(screenPosition * errorCorrection) / errorCorrection;
// Dermine how far off the pixel grid this vertex is.
vec2 error = fract(screenPosition);
vertexFractional = dot(error, vec2(1.0)) > fractionalPrecision ? 1.0 : 0.0;
// Correct texture sampling for floating-point error on the vertices.
// This currently assumes UV coordinates are always from 0 to 1 over an
// entire triangle.
texcoord0 = texcoord.xy + (error / geometrySize);
}