diff --git a/app/qml/ApplicationSettings.qml b/app/qml/ApplicationSettings.qml index 9b34f4e..967e598 100644 --- a/app/qml/ApplicationSettings.qml +++ b/app/qml/ApplicationSettings.qml @@ -104,6 +104,7 @@ QtObject { readonly property int no_rasterization: 0 readonly property int scanline_rasterization: 1 readonly property int pixel_rasterization: 2 + readonly property int lcd_rasterization: 3 property int rasterization: no_rasterization @@ -145,6 +146,13 @@ QtObject { target: fontManager source: "FontPixels.qml" } + }, + State { + when: rasterization == lcd_rasterization + PropertyChanges { + target: fontManager + source: "FontPixels.qml" + } } ] diff --git a/app/qml/BurnInEffect.qml b/app/qml/BurnInEffect.qml index 3834d76..9061b9c 100644 --- a/app/qml/BurnInEffect.qml +++ b/app/qml/BurnInEffect.qml @@ -89,6 +89,10 @@ Loader { } } + ShaderLibrary { + id: shaderLibrary + } + ShaderEffect { id: burnInShaderEffect @@ -118,9 +122,7 @@ Loader { uniform highp float prevLastUpdate;" + - "float rgb2grey(vec3 v){ - return dot(v, vec3(0.21, 0.72, 0.04)); - }" + + shaderLibrary.rgb2grey + "void main() { vec2 coords = qt_TexCoord0; diff --git a/app/qml/NewTerminalFrame.qml b/app/qml/NewTerminalFrame.qml index 2bc17fb..8fb2817 100644 --- a/app/qml/NewTerminalFrame.qml +++ b/app/qml/NewTerminalFrame.qml @@ -34,6 +34,10 @@ ShaderEffect { property size aadelta: Qt.size(1.0 / width, 1.0 / height) + ShaderLibrary { + id: shaderLibrary + } + fragmentShader: " #ifdef GL_ES precision mediump float; @@ -52,22 +56,14 @@ ShaderEffect { float dist = dot(cc, cc) * screenCurvature; return (coords + cc * (1.0 + dist) * dist); } + " + - float max2(vec2 v) { - return max(v.x, v.y); - } + shaderLibrary.max2 + + shaderLibrary.min2 + + shaderLibrary.prod2 + + shaderLibrary.sum2 + - float min2(vec2 v) { - return min(v.x, v.y); - } - - float prod2(vec2 v) { - return v.x * v.y; - } - - float sum2(vec2 v) { - return v.x + v.y; - } + " void main(){ vec2 staticCoords = qt_TexCoord0; diff --git a/app/qml/SettingsTerminalTab.qml b/app/qml/SettingsTerminalTab.qml index c4907e7..80b1039 100644 --- a/app/qml/SettingsTerminalTab.qml +++ b/app/qml/SettingsTerminalTab.qml @@ -41,7 +41,7 @@ ColumnLayout { property string selectedElement: model[currentIndex] Layout.fillWidth: true - model: [qsTr("Default"), qsTr("Scanlines"), qsTr("Pixels")] + model: [qsTr("Default"), qsTr("Scanlines"), qsTr("Pixels"), qsTr("LCD")] currentIndex: appSettings.rasterization onCurrentIndexChanged: { appSettings.rasterization = currentIndex diff --git a/app/qml/ShaderLibrary.qml b/app/qml/ShaderLibrary.qml new file mode 100644 index 0000000..56eae11 --- /dev/null +++ b/app/qml/ShaderLibrary.qml @@ -0,0 +1,88 @@ +import QtQuick 2.0 + +QtObject { + property string rasterizationShader: + (appSettings.rasterization === appSettings.no_rasterization ? " + lowp vec3 applyRasterization(vec2 screenCoords, lowp vec3 texel, vec2 virtualResolution, float intensity) { + return texel; + }" : "") + + + (appSettings.rasterization === appSettings.scanline_rasterization ? " + #define INTENSITY 0.30 + #define BRIGHTBOOST 0.30 + + lowp vec3 applyRasterization(vec2 screenCoords, lowp vec3 texel, vec2 virtualResolution, float intensity) { + lowp vec3 result = texel; + + lowp vec3 pixelHigh = ((1.0 + BRIGHTBOOST) - (0.2 * texel)) * texel; + lowp vec3 pixelLow = ((1.0 - INTENSITY) + (0.1 * texel)) * texel; + + vec2 coords = fract(screenCoords * virtualResolution) * 2.0 - vec2(1.0); + lowp float mask = 1.0 - abs(coords.y); + + lowp vec3 rasterizationColor = mix(pixelLow, pixelHigh, mask); + return mix(texel, rasterizationColor, intensity); + }" : "") + + + (appSettings.rasterization === appSettings.pixel_rasterization ? " + #define INTENSITY 0.30 + #define BRIGHTBOOST 0.30 + + lowp vec3 applyRasterization(vec2 screenCoords, lowp vec3 texel, vec2 virtualResolution, float intensity) { + lowp vec3 result = texel; + + lowp vec3 pixelHigh = ((1.0 + BRIGHTBOOST) - (0.2 * texel)) * texel; + lowp vec3 pixelLow = ((1.0 - INTENSITY) + (0.1 * texel)) * texel; + + vec2 coords = fract(screenCoords * virtualResolution) * 2.0 - vec2(1.0); + coords = coords * coords; + lowp float mask = 1.0 - coords.x - coords.y; + + lowp vec3 rasterizationColor = mix(pixelLow, pixelHigh, mask); + return mix(texel, rasterizationColor, intensity); + }" : "") + + + (appSettings.rasterization === appSettings.lcd_rasterization ? " + #define brighten_scanlines 16.0 + #define brighten_lcd 4.0 + const vec3 offsets = vec3(3.141592654) * vec3(1.0/2.0,1.0/2.0 - 2.0/3.0,1.0/2.0-4.0/3.0); + + lowp vec3 applyRasterization(vec2 screenCoords, lowp vec3 texel, vec2 virtualResolution, float intensity) { + vec2 omega = vec2(3.141592654) * vec2(2.0) * virtualResolution; + + vec2 angle = screenCoords * omega; + + float yfactor = (brighten_scanlines + sin(angle.y)) / (brighten_scanlines + 1.0); + vec3 xfactors = (brighten_lcd + sin(angle.x + offsets)) / (brighten_lcd + 1.0); + + lowp vec3 rasterizationColor = yfactor * xfactors * texel; + return mix(texel, rasterizationColor, intensity); + }" : "") + + + "\n\n" + + property string min2: " + float min2(vec2 v) { + return min(v.x, v.y); + }\n\n" + + property string rgb2grey: " + float rgb2grey(vec3 v) { + return dot(v, vec3(0.21, 0.72, 0.04)); + }\n\n" + + property string max2: " + float max2(vec2 v) { + return max(v.x, v.y); + }\n\n" + + property string prod2: " + float prod2(vec2 v) { + return v.x * v.y; + }\n\n" + + property string sum2: " + float sum2(vec2 v) { + return v.x + v.y; + }\n\n" +} diff --git a/app/qml/ShaderTerminal.qml b/app/qml/ShaderTerminal.qml index c67cc09..8e17fe6 100644 --- a/app/qml/ShaderTerminal.qml +++ b/app/qml/ShaderTerminal.qml @@ -38,11 +38,19 @@ Item { property real ambientLight: appSettings.ambientLight * 0.2 - property size virtual_resolution + property size virtualResolution + property size screenResolution + + property real _screenDensity: Math.min( + screenResolution.width / virtualResolution.width, + screenResolution.height / virtualResolution.height + ) ShaderEffect { id: dynamicShader + property ShaderLibrary shaderLibrary: ShaderLibrary { } + property ShaderEffectSource screenBuffer: frameBuffer property ShaderEffectSource burnInSource: burnInEffect.source property ShaderEffectSource frameSource: terminalFrameLoader.item @@ -74,7 +82,11 @@ Item { property size scaleNoiseSize: Qt.size((width) / (noiseTexture.width * appSettings.windowScaling * appSettings.totalFontScaling), (height) / (noiseTexture.height * appSettings.windowScaling * appSettings.totalFontScaling)) - property size virtual_resolution: parent.virtual_resolution + property size virtualResolution: parent.virtualResolution + + // Rasterization might display oversamping issues if virtual resolution is close to physical display resolution. + // We progressively disable rasterization from 4x up to 2x resolution. + property real rasterizationIntensity: Utils.smoothstep(2.0, 4.0, _screenDensity) property real time: timeManager.time property ShaderEffectSource noiseSource: noiseShaderSource @@ -164,7 +176,8 @@ Item { uniform highp vec4 backgroundColor; uniform lowp float shadowLength; - uniform highp vec2 virtual_resolution;" + + uniform highp vec2 virtualResolution; + uniform lowp float rasterizationIntensity;\n" + (burnIn !== 0 ? " uniform sampler2D burnInSource; @@ -174,8 +187,7 @@ Item { uniform sampler2D slowBurnInSource;" : "") + (staticNoise !== 0 ? " uniform highp float staticNoise;" : "") + - (((staticNoise !== 0 || jitter !== 0) - ||(fallBack && (flickering || horizontalSync))) ? " + (((staticNoise !== 0 || jitter !== 0) ||(fallBack && (flickering || horizontalSync))) ? " uniform lowp sampler2D noiseSource; uniform highp vec2 scaleNoiseSize;" : "") + (screenCurvature !== 0 ? " @@ -203,17 +215,14 @@ Item { (glowingLine !== 0 ? " float randomPass(vec2 coords){ - return fract(smoothstep(-120.0, 0.0, coords.y - (virtual_resolution.y + 120.0) * fract(time * 0.00015))); + return fract(smoothstep(-120.0, 0.0, coords.y - (virtualResolution.y + 120.0) * fract(time * 0.00015))); }" : "") + - "float min2(vec2 v) { - return min(v.x, v.y); - } - - float rgb2grey(vec3 v){ - return dot(v, vec3(0.21, 0.72, 0.04)); - } + shaderLibrary.min2 + + shaderLibrary.rgb2grey + + shaderLibrary.rasterizationShader + + " float isInScreen(vec2 v) { return min2(step(0.0, v) - step(1.0, v)); } @@ -291,7 +300,7 @@ Item { color += noiseVal * noise * (1.0 - distance * 1.3);" : "") + (glowingLine !== 0 ? " - color += randomPass(coords * virtual_resolution) * glowingLine;" : "") + + color += randomPass(coords * virtualResolution) * glowingLine;" : "") + "vec3 txt_color = texture2D(screenBuffer, txt_coords).rgb;" + @@ -309,6 +318,8 @@ Item { "txt_color += fontColor.rgb * vec3(color);" + + "txt_color = applyRasterization(staticCoords, txt_color, virtualResolution, rasterizationIntensity);\n" + + "vec3 finalColor = txt_color;" + (flickering !== 0 ? " @@ -360,6 +371,10 @@ Item { } } + ShaderLibrary { + id: shaderLibrary + } + ShaderEffect { id: staticShader @@ -385,7 +400,7 @@ Item { property real ambientLight: parent.ambientLight - property size virtual_resolution: parent.virtual_resolution + property size virtualResolution: parent.virtualResolution blending: false visible: false @@ -408,7 +423,7 @@ Item { uniform highp vec4 backgroundColor; uniform lowp float screen_brightness; - uniform highp vec2 virtual_resolution;" + + uniform highp vec2 virtualResolution;" + (bloom !== 0 ? " uniform highp sampler2D bloomSource; @@ -426,25 +441,7 @@ Item { (ambientLight !== 0 ? " uniform lowp float ambientLight;" : "") + - "highp float getScanlineIntensity(vec2 coords) { - float result = 1.0;" + - - (appSettings.rasterization != appSettings.no_rasterization ? - "float val = 0.0; - vec2 rasterizationCoords = fract(coords * virtual_resolution); - val += smoothstep(0.0, 0.5, rasterizationCoords.y); - val -= smoothstep(0.5, 1.0, rasterizationCoords.y); - result *= mix(0.5, 1.0, val);" : "") + - - (appSettings.rasterization == appSettings.pixel_rasterization ? - "val = 0.0; - val += smoothstep(0.0, 0.5, rasterizationCoords.x); - val -= smoothstep(0.5, 1.0, rasterizationCoords.x); - result *= mix(0.5, 1.0, val);" : "") + " - - return result; - } - + " float min2(vec2 v) { return min(v.x, v.y); } @@ -468,6 +465,7 @@ Item { " return outColor; }" + + shaderLibrary.rasterizationShader + "void main() {" + "vec2 cc = vec2(0.5) - qt_TexCoord0;" + @@ -490,8 +488,6 @@ Item { txt_color.b = leftColor.b * 0.30 + rightColor.b * 0.10 + txt_color.b * 0.60; " : "") + - "txt_color *= getScanlineIntensity(txt_coords);" + - "txt_color += vec3(0.0001);" + "float greyscale_color = rgb2grey(txt_color);" + diff --git a/app/qml/TerminalContainer.qml b/app/qml/TerminalContainer.qml index 1740798..9896354 100644 --- a/app/qml/TerminalContainer.qml +++ b/app/qml/TerminalContainer.qml @@ -26,13 +26,19 @@ ShaderTerminal { property alias title: terminal.title property alias terminalSize: terminal.terminalSize + property real devicePixelRatio: terminalWindow.screen.devicePixelRatio + id: mainShader opacity: appSettings.windowOpacity * 0.3 + 0.7 source: terminal.mainSource burnInEffect: terminal.burnInEffect slowBurnInEffect: terminal.slowBurnInEffect - virtual_resolution: terminal.virtualResolution + virtualResolution: terminal.virtualResolution + screenResolution: Qt.size( + terminalWindow.width * devicePixelRatio * appSettings.windowScaling, + terminalWindow.height * devicePixelRatio * appSettings.windowScaling + ) TimeManager { id: timeManager diff --git a/app/qml/resources.qrc b/app/qml/resources.qrc index ac04e1d..04b2701 100644 --- a/app/qml/resources.qrc +++ b/app/qml/resources.qrc @@ -45,5 +45,6 @@ menus/WindowMenu.qml menus/FullContextMenu.qml menus/ShortContextMenu.qml + ShaderLibrary.qml diff --git a/app/qml/utils.js b/app/qml/utils.js index 79fcf95..3cb5a67 100644 --- a/app/qml/utils.js +++ b/app/qml/utils.js @@ -31,13 +31,18 @@ function lint(a, b, t) { return (1 - t) * a + (t) * b; } -function mix(c1, c2, alpha){ +function mix(c1, c2, alpha) { return Qt.rgba(c1.r * alpha + c2.r * (1-alpha), c1.g * alpha + c2.g * (1-alpha), c1.b * alpha + c2.b * (1-alpha), c1.a * alpha + c2.a * (1-alpha)) } +function smoothstep(min, max, value) { + let x = Math.max(0, Math.min(1, (value - min) / (max - min))); + return x * x * (3 - 2 * x); +} + function strToColor(s){ var r = parseInt(s.substring(1,3), 16) / 256; var g = parseInt(s.substring(3,5), 16) / 256;