OXYGENHUB
# 306aec1839aa oxygenhub / crates / backdrop / src / gl.rs
7.3 KiB · Rust · last changed feat(web): a denser sky, every change and run a mark; short links; the name above (refs #157) 22 days ago source blame history raw
1
//! Drawing a sky with WebGL2 (docs/backgrounds.md §6): every mark in one
2
//! instanced draw, every link in one line buffer. Shaders are here as
3
//! string literals; marks are drawn as signed-distance shapes, so they
4
//! stay crisp at any device pixel ratio.
5
6
use wasm_bindgen::prelude::*;
7
use web_sys::{WebGl2RenderingContext as Gl, WebGlBuffer, WebGlProgram, WebGlShader, WebGlUniformLocation, WebGlVertexArrayObject};
8
9
const MARK_VS: &str = r#"#version 300 es
10
layout(location = 0) in vec2 corner;
11
layout(location = 1) in vec2 center;
12
layout(location = 2) in float size;
13
layout(location = 3) in float shape;
14
layout(location = 4) in vec4 colour;
15
uniform vec2 view;
16
out vec2 uv;
17
out float vshape;
18
out vec4 vcolour;
19
out float px;
20
void main() {
21
    float r = size + 1.5;
22
    vec2 p = center + corner * r;
23
    uv = corner * r / size;
24
    vshape = shape;
25
    vcolour = colour;
26
    px = 1.0 / size;
27
    gl_Position = vec4(p.x / view.x * 2.0 - 1.0, 1.0 - p.y / view.y * 2.0, 0.0, 1.0);
28
}
29
"#;
30
31
const MARK_FS: &str = r#"#version 300 es
32
precision mediump float;
33
in vec2 uv;
34
in float vshape;
35
in vec4 vcolour;
36
in float px;
37
out vec4 frag;
38
void main() {
39
    vec2 a = abs(uv);
40
    float d;
41
    if (vshape < 0.5) {
42
        d = length(uv) - 1.0;                       // a push: a dot
43
    } else if (vshape < 1.5) {
44
        d = (a.x + a.y) * 0.85 - 1.0;               // a proposal: a diamond
45
    } else if (vshape < 2.5) {
46
        d = abs(length(uv) - 0.72) - 0.28;          // an issue: a ring
47
    } else if (vshape < 3.5) {
48
        d = min(a.x + 3.5 * a.y, 3.5 * a.x + a.y) * 0.5 - 0.5;  // a release: a star
49
    } else if (vshape < 4.5) {
50
        d = abs(length(uv) - 0.85) - 0.12;          // a repository: a thin ring
51
    } else {
52
        d = max(a.x, a.y) * 1.15 - 1.0;             // a Flow run: a square
53
    }
54
    float cover = 1.0 - smoothstep(-px, px, d);
55
    frag = vcolour * cover;
56
}
57
"#;
58
59
const LINE_VS: &str = r#"#version 300 es
60
layout(location = 0) in vec2 pos;
61
layout(location = 1) in vec4 colour;
62
uniform vec2 view;
63
out vec4 vcolour;
64
void main() {
65
    vcolour = colour;
66
    gl_Position = vec4(pos.x / view.x * 2.0 - 1.0, 1.0 - pos.y / view.y * 2.0, 0.0, 1.0);
67
}
68
"#;
69
70
const LINE_FS: &str = r#"#version 300 es
71
precision mediump float;
72
in vec4 vcolour;
73
out vec4 frag;
74
void main() { frag = vcolour; }
75
"#;
76
77
/// Floats per mark instance and per line vertex (sky::Sky's layouts).
78
const MARK_FLOATS: i32 = 8;
79
const LINE_FLOATS: i32 = 6;
80
81
pub struct Renderer {
82
    gl: Gl,
83
    marks: WebGlProgram,
84
    lines: WebGlProgram,
85
    mark_view: Option<WebGlUniformLocation>,
86
    line_view: Option<WebGlUniformLocation>,
87
    mark_vao: WebGlVertexArrayObject,
88
    line_vao: WebGlVertexArrayObject,
89
    instances: WebGlBuffer,
90
    line_buf: WebGlBuffer,
91
}
92
93
fn shader(gl: &Gl, kind: u32, src: &str) -> Result<WebGlShader, JsValue> {
94
    let s = gl.create_shader(kind).ok_or("no shader")?;
95
    gl.shader_source(&s, src);
96
    gl.compile_shader(&s);
97
    if gl.get_shader_parameter(&s, Gl::COMPILE_STATUS).as_bool() == Some(true) {
98
        Ok(s)
99
    } else {
100
        Err(gl.get_shader_info_log(&s).unwrap_or_default().into())
101
    }
102
}
103
104
fn program(gl: &Gl, vs: &str, fs: &str) -> Result<WebGlProgram, JsValue> {
105
    let p = gl.create_program().ok_or("no program")?;
106
    gl.attach_shader(&p, &shader(gl, Gl::VERTEX_SHADER, vs)?);
107
    gl.attach_shader(&p, &shader(gl, Gl::FRAGMENT_SHADER, fs)?);
108
    gl.link_program(&p);
109
    if gl.get_program_parameter(&p, Gl::LINK_STATUS).as_bool() == Some(true) {
110
        Ok(p)
111
    } else {
112
        Err(gl.get_program_info_log(&p).unwrap_or_default().into())
113
    }
114
}
115
116
fn float_attrib(gl: &Gl, loc: u32, size: i32, stride: i32, offset: i32, divisor: u32) {
117
    gl.enable_vertex_attrib_array(loc);
118
    gl.vertex_attrib_pointer_with_i32(loc, size, Gl::FLOAT, false, stride * 4, offset * 4);
119
    gl.vertex_attrib_divisor(loc, divisor);
120
}
121
122
impl Renderer {
123
    pub fn new(gl: Gl) -> Result<Renderer, JsValue> {
124
        let marks = program(&gl, MARK_VS, MARK_FS)?;
125
        let lines = program(&gl, LINE_VS, LINE_FS)?;
126
        let mark_view = gl.get_uniform_location(&marks, "view");
127
        let line_view = gl.get_uniform_location(&lines, "view");
128
129
        // Marks: one shared quad, and one instance per mark.
130
        let mark_vao = gl.create_vertex_array().ok_or("no vertex array")?;
131
        gl.bind_vertex_array(Some(&mark_vao));
132
        let quad = gl.create_buffer().ok_or("no buffer")?;
133
        gl.bind_buffer(Gl::ARRAY_BUFFER, Some(&quad));
134
        let corners: [f32; 8] = [-1.0, -1.0, 1.0, -1.0, -1.0, 1.0, 1.0, 1.0];
135
        // Safety: the view is used at once, before anything can allocate.
136
        unsafe {
137
            let view = js_sys::Float32Array::view(&corners);
138
            gl.buffer_data_with_array_buffer_view(Gl::ARRAY_BUFFER, &view, Gl::STATIC_DRAW);
139
        }
140
        float_attrib(&gl, 0, 2, 2, 0, 0);
141
        let instances = gl.create_buffer().ok_or("no buffer")?;
142
        gl.bind_buffer(Gl::ARRAY_BUFFER, Some(&instances));
143
        float_attrib(&gl, 1, 2, MARK_FLOATS, 0, 1);
144
        float_attrib(&gl, 2, 1, MARK_FLOATS, 2, 1);
145
        float_attrib(&gl, 3, 1, MARK_FLOATS, 3, 1);
146
        float_attrib(&gl, 4, 4, MARK_FLOATS, 4, 1);
147
148
        // Links: plain lines, two vertices each.
149
        let line_vao = gl.create_vertex_array().ok_or("no vertex array")?;
150
        gl.bind_vertex_array(Some(&line_vao));
151
        let line_buf = gl.create_buffer().ok_or("no buffer")?;
152
        gl.bind_buffer(Gl::ARRAY_BUFFER, Some(&line_buf));
153
        float_attrib(&gl, 0, 2, LINE_FLOATS, 0, 0);
154
        float_attrib(&gl, 1, 4, LINE_FLOATS, 2, 0);
155
        gl.bind_vertex_array(None);
156
157
        gl.enable(Gl::BLEND);
158
        // Colours are premultiplied.
159
        gl.blend_func(Gl::ONE, Gl::ONE_MINUS_SRC_ALPHA);
160
        Ok(Renderer { gl, marks, lines, mark_view, line_view, mark_vao, line_vao, instances, line_buf })
161
    }
162
163
    /// Draw one frame: `instances` and `lines` as sky::Sky lays them out,
164
    /// in a view of `w` by `h` CSS pixels on a canvas of `pw` by `ph`.
165
    pub fn draw(&self, w: f32, h: f32, pw: i32, ph: i32, instances: &[f32], lines: &[f32]) {
166
        let gl = &self.gl;
167
        gl.viewport(0, 0, pw, ph);
168
        gl.clear_color(0.0, 0.0, 0.0, 0.0);
169
        gl.clear(Gl::COLOR_BUFFER_BIT);
170
171
        if !lines.is_empty() {
172
            gl.use_program(Some(&self.lines));
173
            gl.uniform2f(self.line_view.as_ref(), w, h);
174
            gl.bind_vertex_array(Some(&self.line_vao));
175
            gl.bind_buffer(Gl::ARRAY_BUFFER, Some(&self.line_buf));
176
            // Safety: as above, the view does not outlive this call.
177
            unsafe {
178
                let view = js_sys::Float32Array::view(lines);
179
                gl.buffer_data_with_array_buffer_view(Gl::ARRAY_BUFFER, &view, Gl::STREAM_DRAW);
180
            }
181
            gl.draw_arrays(Gl::LINES, 0, lines.len() as i32 / LINE_FLOATS);
182
        }
183
184
        if !instances.is_empty() {
185
            gl.use_program(Some(&self.marks));
186
            gl.uniform2f(self.mark_view.as_ref(), w, h);
187
            gl.bind_vertex_array(Some(&self.mark_vao));
188
            gl.bind_buffer(Gl::ARRAY_BUFFER, Some(&self.instances));
189
            unsafe {
190
                let view = js_sys::Float32Array::view(instances);
191
                gl.buffer_data_with_array_buffer_view(Gl::ARRAY_BUFFER, &view, Gl::STREAM_DRAW);
192
            }
193
            gl.draw_arrays_instanced(Gl::TRIANGLE_STRIP, 0, 4, instances.len() as i32 / MARK_FLOATS);
194
        }
195
        gl.bind_vertex_array(None);
196
    }
197
}