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;