1
//! FXAA (Fast Approximate Anti-Aliasing) shader implementation.
2
//!
3
//! Post-processing AA that detects edges via luminance and selectively blurs them.
4
//! Faster than supersampling and works without hardware MSAA support.
5
//!
6
//! Shader compilation: `GlContextPtrInner.fxaa_shader` (see `core/src/gl.rs`).
7
//! FXAA pass: `apply_fxaa` / `apply_fxaa_with_config` (see `layout/src/xml/svg.rs`).
8
//!
9
//! Presets: `FxaaConfig::enabled()`, `::high_quality()`, `::balanced()`, `::performance()`
10

            
11
/// FXAA shader configuration
12
#[derive(Debug, Clone)]
13
pub struct FxaaConfig {
14
    /// Enable/disable FXAA
15
    pub enabled: bool,
16
    /// Edge detection threshold (0.063 - 0.333, default: 0.125)
17
    /// Lower = more edges detected = more AA but potential blur
18
    pub edge_threshold: f32,
19
    /// Minimum edge threshold (0.0312 - 0.0833, default: 0.0312)
20
    /// Prevents AA on very low contrast edges
21
    pub edge_threshold_min: f32,
22
}
23

            
24
impl Default for FxaaConfig {
25
    fn default() -> Self {
26
        Self {
27
            enabled: false, // Disabled by default for performance
28
            edge_threshold: 0.125,
29
            edge_threshold_min: 0.0312,
30
        }
31
    }
32
}
33

            
34
impl FxaaConfig {
35
    /// Create config with FXAA enabled and default quality settings
36
    pub fn enabled() -> Self {
37
        Self {
38
            enabled: true,
39
            ..Default::default()
40
        }
41
    }
42

            
43
    /// High quality preset - more aggressive edge detection
44
    pub fn high_quality() -> Self {
45
        Self {
46
            enabled: true,
47
            edge_threshold: 0.063,
48
            edge_threshold_min: 0.0312,
49
        }
50
    }
51

            
52
    /// Balanced preset - default settings
53
    pub fn balanced() -> Self {
54
        Self {
55
            enabled: true,
56
            edge_threshold: 0.125,
57
            edge_threshold_min: 0.0312,
58
        }
59
    }
60

            
61
    /// Performance preset - less aggressive, faster
62
    pub fn performance() -> Self {
63
        Self {
64
            enabled: true,
65
            edge_threshold: 0.25,
66
            edge_threshold_min: 0.0625,
67
        }
68
    }
69
}
70

            
71
/// FXAA vertex shader - simple fullscreen quad pass-through
72
pub static FXAA_VERTEX_SHADER: &[u8] = b"#version 150
73

            
74
#if __VERSION__ != 100
75
    #define varying out
76
    #define attribute in
77
#endif
78

            
79
attribute vec2 vAttrXY;
80
varying vec2 vTexCoord;
81

            
82
void main() {
83
    vTexCoord = vAttrXY * 0.5 + 0.5; // Convert from [-1,1] to [0,1]
84
    gl_Position = vec4(vAttrXY, 0.0, 1.0);
85
}
86
";
87

            
88
/// FXAA fragment shader - implements edge-based anti-aliasing
89
pub static FXAA_FRAGMENT_SHADER: &[u8] = b"#version 150
90

            
91
precision highp float;
92

            
93
#if __VERSION__ == 100
94
    #define oFragColor gl_FragColor
95
    #define texture texture2D
96
#else
97
    out vec4 oFragColor;
98
#endif
99

            
100
#if __VERSION__ != 100
101
    #define varying in
102
#endif
103

            
104
uniform sampler2D uTexture;
105
uniform vec2 uTexelSize; // 1.0 / texture dimensions
106
uniform float uEdgeThreshold;
107
uniform float uEdgeThresholdMin;
108

            
109
varying vec2 vTexCoord;
110

            
111
// Luminance conversion (Rec. 709)
112
float luminance(vec3 color) {
113
    return dot(color, vec3(0.2126, 0.7152, 0.0722));
114
}
115

            
116
void main() {
117
    // Sample center and 4-neighborhood
118
    vec3 colorCenter = texture(uTexture, vTexCoord).rgb;
119
    vec3 colorN = texture(uTexture, vTexCoord + vec2(0.0, -uTexelSize.y)).rgb;
120
    vec3 colorS = texture(uTexture, vTexCoord + vec2(0.0, uTexelSize.y)).rgb;
121
    vec3 colorE = texture(uTexture, vTexCoord + vec2(uTexelSize.x, 0.0)).rgb;
122
    vec3 colorW = texture(uTexture, vTexCoord + vec2(-uTexelSize.x, 0.0)).rgb;
123
    
124
    // Calculate luminance
125
    float lumCenter = luminance(colorCenter);
126
    float lumN = luminance(colorN);
127
    float lumS = luminance(colorS);
128
    float lumE = luminance(colorE);
129
    float lumW = luminance(colorW);
130
    
131
    // Find min/max luminance in neighborhood
132
    float lumMin = min(lumCenter, min(min(lumN, lumS), min(lumE, lumW)));
133
    float lumMax = max(lumCenter, max(max(lumN, lumS), max(lumE, lumW)));
134
    float lumRange = lumMax - lumMin;
135
    
136
    // Early exit if no edge detected
137
    if (lumRange < max(uEdgeThresholdMin, lumMax * uEdgeThreshold)) {
138
        oFragColor = vec4(colorCenter, 1.0);
139
        return;
140
    }
141
    
142
    // Calculate edge direction
143
    float lumNS = lumN + lumS;
144
    float lumEW = lumE + lumW;
145
    
146
    vec2 dir;
147
    dir.x = lumNS - lumEW;
148
    dir.y = lumN - lumS;
149
    
150
    // Normalize edge direction
151
    float dirReduce = max((lumN + lumS + lumE + lumW) * 0.25 * 0.25, 0.0078125);
152
    float rcpDirMin = 1.0 / (min(abs(dir.x), abs(dir.y)) + dirReduce);
153
    dir = min(vec2(8.0), max(vec2(-8.0), dir * rcpDirMin)) * uTexelSize;
154
    
155
    // Sample along edge direction
156
    vec3 color1 = 0.5 * (
157
        texture(uTexture, vTexCoord + dir * (1.0/3.0 - 0.5)).rgb +
158
        texture(uTexture, vTexCoord + dir * (2.0/3.0 - 0.5)).rgb
159
    );
160
    
161
    vec3 color2 = color1 * 0.5 + 0.25 * (
162
        texture(uTexture, vTexCoord + dir * -0.5).rgb +
163
        texture(uTexture, vTexCoord + dir * 0.5).rgb
164
    );
165
    
166
    float lum2 = luminance(color2);
167
    
168
    // Choose appropriate sample based on luminance range
169
    if (lum2 < lumMin || lum2 > lumMax) {
170
        oFragColor = vec4(color1, 1.0);
171
    } else {
172
        oFragColor = vec4(color2, 1.0);
173
    }
174
}
175
";