Skip to main content

azul_core/
gl_fxaa.rs

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//! Currently TODO: shader compilation (see `GlContextPtrInner.fxaa_shader`).
7//!
8//! Presets: `FxaaConfig::enabled()`, `::high_quality()`, `::balanced()`, `::performance()`
9
10use gl_context_loader::{GLint, GLuint};
11
12/// FXAA shader configuration
13#[derive(Debug, Clone)]
14pub struct FxaaConfig {
15    /// Enable/disable FXAA
16    pub enabled: bool,
17    /// Edge detection threshold (0.063 - 0.333, default: 0.125)
18    /// Lower = more edges detected = more AA but potential blur
19    pub edge_threshold: f32,
20    /// Minimum edge threshold (0.0312 - 0.0833, default: 0.0312)
21    /// Prevents AA on very low contrast edges
22    pub edge_threshold_min: f32,
23}
24
25impl Default for FxaaConfig {
26    fn default() -> Self {
27        Self {
28            enabled: false, // Disabled by default for performance
29            edge_threshold: 0.125,
30            edge_threshold_min: 0.0312,
31        }
32    }
33}
34
35impl FxaaConfig {
36    /// Create config with FXAA enabled and default quality settings
37    pub fn enabled() -> Self {
38        Self {
39            enabled: true,
40            ..Default::default()
41        }
42    }
43
44    /// High quality preset - more aggressive edge detection
45    pub fn high_quality() -> Self {
46        Self {
47            enabled: true,
48            edge_threshold: 0.063,
49            edge_threshold_min: 0.0312,
50        }
51    }
52
53    /// Balanced preset - default settings
54    pub fn balanced() -> Self {
55        Self {
56            enabled: true,
57            edge_threshold: 0.125,
58            edge_threshold_min: 0.0312,
59        }
60    }
61
62    /// Performance preset - less aggressive, faster
63    pub fn performance() -> Self {
64        Self {
65            enabled: true,
66            edge_threshold: 0.25,
67            edge_threshold_min: 0.0625,
68        }
69    }
70}
71
72/// FXAA vertex shader - simple fullscreen quad pass-through
73pub static FXAA_VERTEX_SHADER: &[u8] = b"#version 150
74
75#if __VERSION__ != 100
76    #define varying out
77    #define attribute in
78#endif
79
80attribute vec2 vAttrXY;
81varying vec2 vTexCoord;
82
83void main() {
84    vTexCoord = vAttrXY * 0.5 + 0.5; // Convert from [-1,1] to [0,1]
85    gl_Position = vec4(vAttrXY, 0.0, 1.0);
86}
87";
88
89/// FXAA fragment shader - implements edge-based anti-aliasing
90pub static FXAA_FRAGMENT_SHADER: &[u8] = b"#version 150
91
92precision highp float;
93
94#if __VERSION__ == 100
95    #define oFragColor gl_FragColor
96    #define texture texture2D
97#else
98    out vec4 oFragColor;
99#endif
100
101#if __VERSION__ != 100
102    #define varying in
103#endif
104
105uniform sampler2D uTexture;
106uniform vec2 uTexelSize; // 1.0 / texture dimensions
107uniform float uEdgeThreshold;
108uniform float uEdgeThresholdMin;
109
110varying vec2 vTexCoord;
111
112// Luminance conversion (Rec. 709)
113float luminance(vec3 color) {
114    return dot(color, vec3(0.2126, 0.7152, 0.0722));
115}
116
117void main() {
118    // Sample center and 4-neighborhood
119    vec3 colorCenter = texture(uTexture, vTexCoord).rgb;
120    vec3 colorN = texture(uTexture, vTexCoord + vec2(0.0, -uTexelSize.y)).rgb;
121    vec3 colorS = texture(uTexture, vTexCoord + vec2(0.0, uTexelSize.y)).rgb;
122    vec3 colorE = texture(uTexture, vTexCoord + vec2(uTexelSize.x, 0.0)).rgb;
123    vec3 colorW = texture(uTexture, vTexCoord + vec2(-uTexelSize.x, 0.0)).rgb;
124    
125    // Calculate luminance
126    float lumCenter = luminance(colorCenter);
127    float lumN = luminance(colorN);
128    float lumS = luminance(colorS);
129    float lumE = luminance(colorE);
130    float lumW = luminance(colorW);
131    
132    // Find min/max luminance in neighborhood
133    float lumMin = min(lumCenter, min(min(lumN, lumS), min(lumE, lumW)));
134    float lumMax = max(lumCenter, max(max(lumN, lumS), max(lumE, lumW)));
135    float lumRange = lumMax - lumMin;
136    
137    // Early exit if no edge detected
138    if (lumRange < max(uEdgeThresholdMin, lumMax * uEdgeThreshold)) {
139        oFragColor = vec4(colorCenter, 1.0);
140        return;
141    }
142    
143    // Calculate edge direction
144    float lumNS = lumN + lumS;
145    float lumEW = lumE + lumW;
146    
147    vec2 dir;
148    dir.x = lumNS - lumEW;
149    dir.y = lumN - lumS;
150    
151    // Normalize edge direction
152    float dirReduce = max((lumN + lumS + lumE + lumW) * 0.25 * 0.25, 0.0078125);
153    float rcpDirMin = 1.0 / (min(abs(dir.x), abs(dir.y)) + dirReduce);
154    dir = min(vec2(8.0), max(vec2(-8.0), dir * rcpDirMin)) * uTexelSize;
155    
156    // Sample along edge direction
157    vec3 color1 = 0.5 * (
158        texture(uTexture, vTexCoord + dir * (1.0/3.0 - 0.5)).rgb +
159        texture(uTexture, vTexCoord + dir * (2.0/3.0 - 0.5)).rgb
160    );
161    
162    vec3 color2 = color1 * 0.5 + 0.25 * (
163        texture(uTexture, vTexCoord + dir * -0.5).rgb +
164        texture(uTexture, vTexCoord + dir * 0.5).rgb
165    );
166    
167    float lum2 = luminance(color2);
168    
169    // Choose appropriate sample based on luminance range
170    if (lum2 < lumMin || lum2 > lumMax) {
171        oFragColor = vec4(color1, 1.0);
172    } else {
173        oFragColor = vec4(color2, 1.0);
174    }
175}
176";