Fix rendering so that moving the cursor doesn't cause the whole screen to rerender

This commit is contained in:
wheaney 2025-09-05 22:19:04 -07:00
parent 638105667e
commit 205c80445a
11 changed files with 153 additions and 62 deletions

View File

@ -67,4 +67,6 @@ target_link_libraries(breezy_desktop
)
install(DIRECTORY qml DESTINATION ${KDE_INSTALL_DATADIR}/kwin/effects/breezy_desktop)
install(DIRECTORY qml DESTINATION ${KDE_INSTALL_DATADIR}/kwin/effects/breezy_desktop)
install(FILES qml/cursorOverlay.frag DESTINATION ${KDE_INSTALL_DATADIR}/kwin/effects/breezy_desktop/qml)
install(FILES qml/cursorOverlay.vert DESTINATION ${KDE_INSTALL_DATADIR}/kwin/effects/breezy_desktop/qml)

View File

@ -540,6 +540,11 @@ QString BreezyDesktopEffect::cursorImageSource() const
return m_cursorImageSource;
}
QSize BreezyDesktopEffect::cursorImageSize() const
{
return m_cursorImageSize;
}
QPointF BreezyDesktopEffect::cursorPos() const
{
return m_cursorPos;
@ -566,10 +571,12 @@ void BreezyDesktopEffect::updateCursorImage()
cursor.image().save(&buffer, "PNG");
m_cursorImageSource = QStringLiteral("data:image/png;base64,%1").arg(QString::fromLatin1(data.toBase64()));
m_cursorImageSize = cursor.image().size();
} else {
m_cursorImageSource = QString();
m_cursorImageSize = QSize();
}
Q_EMIT cursorImageChanged();
Q_EMIT cursorImageSourceChanged();
}
void BreezyDesktopEffect::updateCursorPos()

View File

@ -16,11 +16,12 @@ namespace KWin
Q_OBJECT
Q_PROPERTY(bool isEnabled READ isEnabled NOTIFY enabledStateChanged)
Q_PROPERTY(bool zoomOnFocusEnabled READ isZoomOnFocusEnabled WRITE setZoomOnFocusEnabled NOTIFY zoomOnFocusChanged)
Q_PROPERTY(bool imuResetState READ imuResetState NOTIFY imuRotationsChanged)
Q_PROPERTY(QList<QQuaternion> imuRotations READ imuRotations NOTIFY imuRotationsChanged)
Q_PROPERTY(quint32 imuTimeElapsedMs READ imuTimeElapsedMs NOTIFY imuRotationsChanged)
Q_PROPERTY(quint64 imuTimestamp READ imuTimestamp NOTIFY imuRotationsChanged)
Q_PROPERTY(QString cursorImageSource READ cursorImageSource NOTIFY cursorImageChanged)
Q_PROPERTY(bool imuResetState READ imuResetState)
Q_PROPERTY(QList<QQuaternion> imuRotations READ imuRotations)
Q_PROPERTY(quint32 imuTimeElapsedMs READ imuTimeElapsedMs)
Q_PROPERTY(quint64 imuTimestamp READ imuTimestamp)
Q_PROPERTY(QString cursorImageSource READ cursorImageSource NOTIFY cursorImageSourceChanged)
Q_PROPERTY(QSize cursorImageSize READ cursorImageSize NOTIFY cursorImageSourceChanged)
Q_PROPERTY(QPointF cursorPos READ cursorPos NOTIFY cursorPosChanged)
Q_PROPERTY(QList<qreal> lookAheadConfig READ lookAheadConfig NOTIFY devicePropertiesChanged)
Q_PROPERTY(QList<quint32> displayResolution READ displayResolution NOTIFY devicePropertiesChanged)
@ -44,6 +45,7 @@ namespace KWin
int requestedEffectChainPosition() const override;
QString cursorImageSource() const;
QSize cursorImageSize() const;
QPointF cursorPos() const;
bool isEnabled() const;
@ -91,7 +93,7 @@ namespace KWin
void enabledStateChanged();
void zoomOnFocusChanged();
void imuRotationsChanged();
void cursorImageChanged();
void cursorImageSourceChanged();
void cursorPosChanged();
void devicePropertiesChanged();
@ -107,6 +109,7 @@ namespace KWin
QTimer *m_shutdownTimer;
QString m_cursorImageSource;
QSize m_cursorImageSize;
bool m_enabled = false;
bool m_zoomOnFocusEnabled = false;

View File

@ -9,7 +9,6 @@ Node {
property var screens: root.screens
property var fovDetails: root.fovDetails
property var monitorPlacements: root.monitorPlacements
property var imuRotations: effect.imuRotations
property int focusedMonitorIndex: -1
Displays {
@ -29,10 +28,17 @@ Node {
delegate: BreezyDesktopDisplay {
screen: breezyDesktop.screens[index]
monitorPlacement: breezyDesktop.monitorPlacements[index]
property real monitorDistance: effect.allDisplaysDistance
property real targetDistance: effect.allDisplaysDistance
property real screenRotationY: displays.radianToDegree(monitorPlacement.rotationAngleRadians.y)
property real screenRotationX: displays.radianToDegree(monitorPlacement.rotationAngleRadians.x)
property matrix4x4 rotationMatrix: {
const matrix = Qt.matrix4x4();
matrix.rotate(screenRotationY, Qt.vector3d(0, 1, 0));
matrix.rotate(screenRotationX, Qt.vector3d(1, 0, 0));
return matrix;
}
property vector3d screenScale: {
const geometry = screen.geometry;
@ -45,19 +51,12 @@ Node {
eulerRotation.y: screenRotationY
eulerRotation.x: screenRotationX
position: {
// camera looks along the negative Z axis
const positionVector =
displays.nwuToEusVector(monitorPlacement.centerNoRotate)
.times(monitorDistance / effect.allDisplaysDistance);
const displayNwu =
monitorPlacement.centerNoRotate
.times(monitorDistance / effect.allDisplaysDistance);
// position vector is only translated in flat directions, without rotations applied, so apply them here
const rotationMatrix = Qt.matrix4x4();
// only one of these should ever be non-zero, since we only rotate in the direction of the "wrap" preference
rotationMatrix.rotate(screenRotationY, Qt.vector3d(0, 1, 0));
rotationMatrix.rotate(screenRotationX, Qt.vector3d(1, 0, 0));
return rotationMatrix.times(positionVector);
return rotationMatrix.times(displays.nwuToEusVector(displayNwu));
}
}
}
@ -67,12 +66,12 @@ Node {
repeat: true
running: true
onTriggered: {
if (breezyDesktop.imuRotations && breezyDesktop.imuRotations.length > 0) {
if (effect.imuRotations && effect.imuRotations.length > 0) {
let focusedIndex = -1;
if (effect.zoomOnFocusEnabled) {
focusedIndex = displays.findFocusedMonitor(
displays.eusToNwuQuat(breezyDesktop.imuRotations[0]),
displays.eusToNwuQuat(effect.imuRotations[0]),
breezyDesktop.monitorPlacements.map(monitorVectors => monitorVectors.centerLook),
breezyDesktop.focusedMonitorIndex,
false, // TODO smooth follow

View File

@ -8,19 +8,46 @@ Model {
required property var monitorPlacement
required property int index
property string cursorImageSource: effect.cursorImageSource
property size cursorImageSize: effect.cursorImageSize
property point cursorPos: effect.cursorPos
source: "#Rectangle"
materials: [
DefaultMaterial {
cullMode: Material.NoCulling
lighting: DefaultMaterial.NoLighting
depthDrawMode: Material.AlwaysDepthDraw
diffuseMap: Texture {
sourceItem: DesktopView {
screen: display.screen
width: display.screen.geometry.width
height: display.screen.geometry.height
CustomMaterial {
id: customMat
depthDrawMode: CustomMaterial.AlwaysDepthDraw
shadingMode: CustomMaterial.Unshaded
property real screenWidth: display.screen.geometry.width
property real screenHeight: display.screen.geometry.height
property real cursorX: display.cursorPos.x - display.screen.geometry.x
property real cursorY: display.cursorPos.y - display.screen.geometry.y
property real cursorW: display.cursorImageSize.width
property real cursorH: display.cursorImageSize.height
property bool showCursor: cursorX >= 0 && cursorX < screenWidth && cursorY >= 0 && cursorY < screenHeight
property TextureInput desktopTex: TextureInput {
texture: Texture {
sourceItem: DesktopView {
screen: display.screen
width: display.screen.geometry.width
height: display.screen.geometry.height
}
}
}
property TextureInput cursorTex: TextureInput {
texture: Texture {
sourceItem: Image {
source: effect.cursorImageSource
width: effect.cursorImageSize.width
height: effect.cursorImageSize.height
}
}
}
fragmentShader: "cursorOverlay.frag"
vertexShader: "cursorOverlay.vert"
}
]
}

View File

@ -6,10 +6,6 @@ Item {
required property Camera camera
property var imuRotations: effect.imuRotations
property int imuTimeElapsedMs: effect.imuTimeElapsedMs
property double imuTimestamp: effect.imuTimestamp
property var lookAheadConfig: effect.lookAheadConfig
property var displayResolution: effect.displayResolution
property real diagonalFOV: effect.diagonalFOV
property real lensDistanceRatio: effect.lensDistanceRatio
@ -67,14 +63,18 @@ Item {
onDisplayResolutionChanged: updateFOV();
onDiagonalFOVChanged: updateFOV();
onImuRotationsChanged: {
if (root.imuRotations && root.imuRotations.length > 0) {
updateCamera(applyLookAhead(
root.imuRotations[0],
root.imuRotations[1],
root.imuTimeElapsedMs,
lookAheadMS(root.imuTimestamp, root.lookAheadConfig, -1)
));
FrameAnimation {
running: true
onTriggered: {
if (effect.imuRotations && effect.imuRotations.length > 0) {
updateCamera(applyLookAhead(
effect.imuRotations[0],
effect.imuRotations[1],
effect.imuTimeElapsedMs,
lookAheadMS(effect.imuTimestamp, effect.lookAheadConfig, -1)
));
}
}
}
}

View File

@ -40,18 +40,4 @@ Item {
visible: onThisScreen && !model.window.minimized
}
}
Image {
id: cursorImg
source: effect.cursorImageSource
cache: false
visible: true // TODO - cursor position bounds check?
x: effect.cursorPos.x - desktopView.screen.geometry.x
y: effect.cursorPos.y - desktopView.screen.geometry.y
z: 9999 // ensure on top
anchors.centerIn: undefined
layer.enabled: true
layer.smooth: true
}
}

View File

@ -0,0 +1,43 @@
import QtQuick
Item {
id: singleDesktopView
property point cursorPos: effect.cursorPos
function cursorInBounds() {
const x = cursorPos.x
const y = cursorPos.y
const screenGeom = targetScreen.geometry
return x >= screenGeom.x &&
x < screenGeom.x + screenGeom.width &&
y >= screenGeom.y &&
y < screenGeom.y + screenGeom.height
}
DesktopView {
screen: targetScreen
width: targetScreen.geometry.width
height: targetScreen.geometry.height
}
Image {
id: cursorImg
x: 0
y: 0
z: 9999 // ensure on top
}
onCursorPosChanged: {
if (singleDesktopView.cursorInBounds()) {
const newX = effect.cursorPos.x - targetScreen.geometry.x
const newY = effect.cursorPos.y - targetScreen.geometry.y
const newSrc = effect.cursorImageSource
if (cursorImg.x !== newX) cursorImg.x = newX
if (cursorImg.y !== newY) cursorImg.y = newY
if (cursorImg.source !== newSrc) cursorImg.source = newSrc
if (!cursorImg.visible) cursorImg.visible = true
} else if (cursorImg.visible) {
cursorImg.visible = false
}
}
}

View File

@ -0,0 +1,18 @@
VARYING vec3 pos;
VARYING vec2 texcoord;
void MAIN() {
vec2 tex = vec2(texcoord.x, 1.0 - texcoord.y);
vec4 color = texture(desktopTex, tex);
if (showCursor) {
vec2 fragCoord = tex * vec2(screenWidth, screenHeight);
vec2 cursorTopLeft = vec2(cursorX, cursorY);
vec2 cursorBottomRight = cursorTopLeft + vec2(cursorW, cursorH);
if (fragCoord.x >= cursorTopLeft.x && fragCoord.x < cursorBottomRight.x && fragCoord.y >= cursorTopLeft.y && fragCoord.y < cursorBottomRight.y) {
vec2 rel = (fragCoord - cursorTopLeft) / vec2(cursorW, cursorH);
vec4 cursorCol = texture(cursorTex, rel);
color = mix(color, cursorCol, cursorCol.a);
}
}
FRAGCOLOR = color;
}

View File

@ -0,0 +1,10 @@
VARYING vec3 pos;
VARYING vec2 texcoord;
// this is a no-op vertex shader, CustomMaterial required one
void MAIN()
{
pos = VERTEX;
texcoord = UV0;
POSITION = MODELVIEWPROJECTION_MATRIX * vec4(pos, 1.0);
}

View File

@ -89,11 +89,7 @@ Item {
Component {
id: desktopViewComponent
DesktopView {
screen: root.targetScreen
width: root.targetScreen.geometry.width
height: root.targetScreen.geometry.height
}
SingleDesktopView {}
}
Component {