OXYGENHUB
# f4d6ef418a96 oxygenhub / crates / backdrop / src / gl.rs
7.2 KiB · Rust · last changed feat(web): Backdrops, the engine in Rust and WASM, and the dashboard's Your sky (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 {
50
        d = abs(length(uv) - 0.85) - 0.12;          // a repository: a thin ring
51
    }
52
    float cover = 1.0 - smoothstep(-px, px, d);
53
    frag = vcolour * cover;
54
}
55
"#;
56
57
const LINE_VS: &str = r#"#version 300 es
58
layout(location = 0) in vec2 pos;
59
layout(location = 1) in vec4 colour;
60
uniform vec2 view;
61
out vec4 vcolour;
62
void main() {
63
    vcolour = colour;
64
    gl_Position = vec4(pos.x / view.x * 2.0 - 1.0, 1.0 - pos.y / view.y * 2.0, 0.0, 1.0);
65
}
66
"#;
67
68
const LINE_FS: &str = r#"#version 300 es
69
precision mediump float;
70
in vec4 vcolour;
71
out vec4 frag;
72
void main() { frag = vcolour; }
73
"#;
74
75
/// Floats per mark instance and per line vertex (sky::Sky's layouts).
76
const MARK_FLOATS: i32 = 8;
77
const LINE_FLOATS: i32 = 6;
78
79
pub struct Renderer {
80
    gl: Gl,
81
    marks: WebGlProgram,
82
    lines: WebGlProgram,
83
    mark_view: Option<WebGlUniformLocation>,
84
    line_view: Option<WebGlUniformLocation>,
85
    mark_vao: WebGlVertexArrayObject,
86
    line_vao: WebGlVertexArrayObject,
87
    instances: WebGlBuffer,
88
    line_buf: WebGlBuffer,
89
}
90
91
fn shader(gl: &Gl, kind: u32, src: &str) -> Result<WebGlShader, JsValue> {
92
    let s = gl.create_shader(kind).ok_or("no shader")?;
93
    gl.shader_source(&s, src);
94
    gl.compile_shader(&s);
95
    if gl.get_shader_parameter(&s, Gl::COMPILE_STATUS).as_bool() == Some(true) {
96
        Ok(s)
97
    } else {
98
        Err(gl.get_shader_info_log(&s).unwrap_or_default().into())
99
    }
100
}
101
102
fn program(gl: &Gl, vs: &str, fs: &str) -> Result<WebGlProgram, JsValue> {
103
    let p = gl.create_program().ok_or("no program")?;
104
    gl.attach_shader(&p, &shader(gl, Gl::VERTEX_SHADER, vs)?);
105
    gl.attach_shader(&p, &shader(gl, Gl::FRAGMENT_SHADER, fs)?);
106
    gl.link_program(&p);
107
    if gl.get_program_parameter(&p, Gl::LINK_STATUS).as_bool() == Some(true) {
108
        Ok(p)
109
    } else {
110
        Err(gl.get_program_info_log(&p).unwrap_or_default().into())
111
    }
112
}
113
114
fn float_attrib(gl: &Gl, loc: u32, size: i32, stride: i32, offset: i32, divisor: u32) {
115
    gl.enable_vertex_attrib_array(loc);
116
    gl.vertex_attrib_pointer_with_i32(loc, size, Gl::FLOAT, false, stride * 4, offset * 4);
117
    gl.vertex_attrib_divisor(loc, divisor);
118
}
119
120
impl Renderer {
121
    pub fn new(gl: Gl) -> Result<Renderer, JsValue> {
122
        let marks = program(&gl, MARK_VS, MARK_FS)?;
123
        let lines = program(&gl, LINE_VS, LINE_FS)?;
124
        let mark_view = gl.get_uniform_location(&marks, "view");
125
        let line_view = gl.get_uniform_location(&lines, "view");
126
127
        // Marks: one shared quad, and one instance per mark.
128
        let mark_vao = gl.create_vertex_array().ok_or("no vertex array")?;
129
        gl.bind_vertex_array(Some(&mark_vao));
130
        let quad = gl.create_buffer().ok_or("no buffer")?;
131
        gl.bind_buffer(Gl::ARRAY_BUFFER, Some(&quad));
132
        let corners: [f32; 8] = [-1.0, -1.0, 1.0, -1.0, -1.0, 1.0, 1.0, 1.0];
133
        // Safety: the view is used at once, before anything can allocate.
134
        unsafe {
135
            let view = js_sys::Float32Array::view(&corners);
136
            gl.buffer_data_with_array_buffer_view(Gl::ARRAY_BUFFER, &view, Gl::STATIC_DRAW);
137
        }
138
        float_attrib(&gl, 0, 2, 2, 0, 0);
139
        let instances = gl.create_buffer().ok_or("no buffer")?;
140
        gl.bind_buffer(Gl::ARRAY_BUFFER, Some(&instances));
141
        float_attrib(&gl, 1, 2, MARK_FLOATS, 0, 1);
142
        float_attrib(&gl, 2, 1, MARK_FLOATS, 2, 1);
143
        float_attrib(&gl, 3, 1, MARK_FLOATS, 3, 1);
144
        float_attrib(&gl, 4, 4, MARK_FLOATS, 4, 1);
145
146
        // Links: plain lines, two vertices each.
147
        let line_vao = gl.create_vertex_array().ok_or("no vertex array")?;
148
        gl.bind_vertex_array(Some(&line_vao));
149
        let line_buf = gl.create_buffer().ok_or("no buffer")?;
150
        gl.bind_buffer(Gl::ARRAY_BUFFER, Some(&line_buf));
151
        float_attrib(&gl, 0, 2, LINE_FLOATS, 0, 0);
152
        float_attrib(&gl, 1, 4, LINE_FLOATS, 2, 0);
153
        gl.bind_vertex_array(None);
154
155
        gl.enable(Gl::BLEND);
156
        // Colours are premultiplied.
157
        gl.blend_func(Gl::ONE, Gl::ONE_MINUS_SRC_ALPHA);
158
        Ok(Renderer { gl, marks, lines, mark_view, line_view, mark_vao, line_vao, instances, line_buf })
159
    }
160
161
    /// Draw one frame: `instances` and `lines` as sky::Sky lays them out,
162
    /// in a view of `w` by `h` CSS pixels on a canvas of `pw` by `ph`.
163
    pub fn draw(&self, w: f32, h: f32, pw: i32, ph: i32, instances: &[f32], lines: &[f32]) {
164
        let gl = &self.gl;
165
        gl.viewport(0, 0, pw, ph);
166
        gl.clear_color(0.0, 0.0, 0.0, 0.0);
167
        gl.clear(Gl::COLOR_BUFFER_BIT);
168
169
        if !lines.is_empty() {
170
            gl.use_program(Some(&self.lines));
171
            gl.uniform2f(self.line_view.as_ref(), w, h);
172
            gl.bind_vertex_array(Some(&self.line_vao));
173
            gl.bind_buffer(Gl::ARRAY_BUFFER, Some(&self.line_buf));
174
            // Safety: as above, the view does not outlive this call.
175
            unsafe {
176
                let view = js_sys::Float32Array::view(lines);
177
                gl.buffer_data_with_array_buffer_view(Gl::ARRAY_BUFFER, &view, Gl::STREAM_DRAW);
178
            }
179
            gl.draw_arrays(Gl::LINES, 0, lines.len() as i32 / LINE_FLOATS);
180
        }
181
182
        if !instances.is_empty() {
183
            gl.use_program(Some(&self.marks));
184
            gl.uniform2f(self.mark_view.as_ref(), w, h);
185
            gl.bind_vertex_array(Some(&self.mark_vao));
186
            gl.bind_buffer(Gl::ARRAY_BUFFER, Some(&self.instances));
187
            unsafe {
188
                let view = js_sys::Float32Array::view(instances);
189
                gl.buffer_data_with_array_buffer_view(Gl::ARRAY_BUFFER, &view, Gl::STREAM_DRAW);
190
            }
191
            gl.draw_arrays_instanced(Gl::TRIANGLE_STRIP, 0, 4, instances.len() as i32 / MARK_FLOATS);
192
        }
193
        gl.bind_vertex_array(None);
194
    }
195
}