//! Drawing a sky with WebGL2 (docs/backgrounds.md ยง6): every mark in one //! instanced draw, every link in one line buffer. Shaders are here as //! string literals; marks are drawn as signed-distance shapes, so they //! stay crisp at any device pixel ratio. use wasm_bindgen::prelude::*; use web_sys::{WebGl2RenderingContext as Gl, WebGlBuffer, WebGlProgram, WebGlShader, WebGlUniformLocation, WebGlVertexArrayObject}; const MARK_VS: &str = r#"#version 300 es layout(location = 0) in vec2 corner; layout(location = 1) in vec2 center; layout(location = 2) in float size; layout(location = 3) in float shape; layout(location = 4) in vec4 colour; uniform vec2 view; out vec2 uv; out float vshape; out vec4 vcolour; out float px; void main() { float r = size + 1.5; vec2 p = center + corner * r; uv = corner * r / size; vshape = shape; vcolour = colour; px = 1.0 / size; gl_Position = vec4(p.x / view.x * 2.0 - 1.0, 1.0 - p.y / view.y * 2.0, 0.0, 1.0); } "#; const MARK_FS: &str = r#"#version 300 es precision mediump float; in vec2 uv; in float vshape; in vec4 vcolour; in float px; out vec4 frag; void main() { vec2 a = abs(uv); float d; if (vshape < 0.5) { d = length(uv) - 1.0; // a push: a dot } else if (vshape < 1.5) { d = (a.x + a.y) * 0.85 - 1.0; // a proposal: a diamond } else if (vshape < 2.5) { d = abs(length(uv) - 0.72) - 0.28; // an issue: a ring } else if (vshape < 3.5) { d = min(a.x + 3.5 * a.y, 3.5 * a.x + a.y) * 0.5 - 0.5; // a release: a star } else if (vshape < 4.5) { d = abs(length(uv) - 0.85) - 0.12; // a repository: a thin ring } else { d = max(a.x, a.y) * 1.15 - 1.0; // a Flow run: a square } float cover = 1.0 - smoothstep(-px, px, d); frag = vcolour * cover; } "#; const LINE_VS: &str = r#"#version 300 es layout(location = 0) in vec2 pos; layout(location = 1) in vec4 colour; uniform vec2 view; out vec4 vcolour; void main() { vcolour = colour; gl_Position = vec4(pos.x / view.x * 2.0 - 1.0, 1.0 - pos.y / view.y * 2.0, 0.0, 1.0); } "#; const LINE_FS: &str = r#"#version 300 es precision mediump float; in vec4 vcolour; out vec4 frag; void main() { frag = vcolour; } "#; /// Floats per mark instance and per line vertex (sky::Sky's layouts). const MARK_FLOATS: i32 = 8; const LINE_FLOATS: i32 = 6; pub struct Renderer { gl: Gl, marks: WebGlProgram, lines: WebGlProgram, mark_view: Option, line_view: Option, mark_vao: WebGlVertexArrayObject, line_vao: WebGlVertexArrayObject, instances: WebGlBuffer, line_buf: WebGlBuffer, } fn shader(gl: &Gl, kind: u32, src: &str) -> Result { let s = gl.create_shader(kind).ok_or("no shader")?; gl.shader_source(&s, src); gl.compile_shader(&s); if gl.get_shader_parameter(&s, Gl::COMPILE_STATUS).as_bool() == Some(true) { Ok(s) } else { Err(gl.get_shader_info_log(&s).unwrap_or_default().into()) } } fn program(gl: &Gl, vs: &str, fs: &str) -> Result { let p = gl.create_program().ok_or("no program")?; gl.attach_shader(&p, &shader(gl, Gl::VERTEX_SHADER, vs)?); gl.attach_shader(&p, &shader(gl, Gl::FRAGMENT_SHADER, fs)?); gl.link_program(&p); if gl.get_program_parameter(&p, Gl::LINK_STATUS).as_bool() == Some(true) { Ok(p) } else { Err(gl.get_program_info_log(&p).unwrap_or_default().into()) } } fn float_attrib(gl: &Gl, loc: u32, size: i32, stride: i32, offset: i32, divisor: u32) { gl.enable_vertex_attrib_array(loc); gl.vertex_attrib_pointer_with_i32(loc, size, Gl::FLOAT, false, stride * 4, offset * 4); gl.vertex_attrib_divisor(loc, divisor); } impl Renderer { pub fn new(gl: Gl) -> Result { let marks = program(&gl, MARK_VS, MARK_FS)?; let lines = program(&gl, LINE_VS, LINE_FS)?; let mark_view = gl.get_uniform_location(&marks, "view"); let line_view = gl.get_uniform_location(&lines, "view"); // Marks: one shared quad, and one instance per mark. let mark_vao = gl.create_vertex_array().ok_or("no vertex array")?; gl.bind_vertex_array(Some(&mark_vao)); let quad = gl.create_buffer().ok_or("no buffer")?; gl.bind_buffer(Gl::ARRAY_BUFFER, Some(&quad)); let corners: [f32; 8] = [-1.0, -1.0, 1.0, -1.0, -1.0, 1.0, 1.0, 1.0]; // Safety: the view is used at once, before anything can allocate. unsafe { let view = js_sys::Float32Array::view(&corners); gl.buffer_data_with_array_buffer_view(Gl::ARRAY_BUFFER, &view, Gl::STATIC_DRAW); } float_attrib(&gl, 0, 2, 2, 0, 0); let instances = gl.create_buffer().ok_or("no buffer")?; gl.bind_buffer(Gl::ARRAY_BUFFER, Some(&instances)); float_attrib(&gl, 1, 2, MARK_FLOATS, 0, 1); float_attrib(&gl, 2, 1, MARK_FLOATS, 2, 1); float_attrib(&gl, 3, 1, MARK_FLOATS, 3, 1); float_attrib(&gl, 4, 4, MARK_FLOATS, 4, 1); // Links: plain lines, two vertices each. let line_vao = gl.create_vertex_array().ok_or("no vertex array")?; gl.bind_vertex_array(Some(&line_vao)); let line_buf = gl.create_buffer().ok_or("no buffer")?; gl.bind_buffer(Gl::ARRAY_BUFFER, Some(&line_buf)); float_attrib(&gl, 0, 2, LINE_FLOATS, 0, 0); float_attrib(&gl, 1, 4, LINE_FLOATS, 2, 0); gl.bind_vertex_array(None); gl.enable(Gl::BLEND); // Colours are premultiplied. gl.blend_func(Gl::ONE, Gl::ONE_MINUS_SRC_ALPHA); Ok(Renderer { gl, marks, lines, mark_view, line_view, mark_vao, line_vao, instances, line_buf }) } /// Draw one frame: `instances` and `lines` as sky::Sky lays them out, /// in a view of `w` by `h` CSS pixels on a canvas of `pw` by `ph`. pub fn draw(&self, w: f32, h: f32, pw: i32, ph: i32, instances: &[f32], lines: &[f32]) { let gl = &self.gl; gl.viewport(0, 0, pw, ph); gl.clear_color(0.0, 0.0, 0.0, 0.0); gl.clear(Gl::COLOR_BUFFER_BIT); if !lines.is_empty() { gl.use_program(Some(&self.lines)); gl.uniform2f(self.line_view.as_ref(), w, h); gl.bind_vertex_array(Some(&self.line_vao)); gl.bind_buffer(Gl::ARRAY_BUFFER, Some(&self.line_buf)); // Safety: as above, the view does not outlive this call. unsafe { let view = js_sys::Float32Array::view(lines); gl.buffer_data_with_array_buffer_view(Gl::ARRAY_BUFFER, &view, Gl::STREAM_DRAW); } gl.draw_arrays(Gl::LINES, 0, lines.len() as i32 / LINE_FLOATS); } if !instances.is_empty() { gl.use_program(Some(&self.marks)); gl.uniform2f(self.mark_view.as_ref(), w, h); gl.bind_vertex_array(Some(&self.mark_vao)); gl.bind_buffer(Gl::ARRAY_BUFFER, Some(&self.instances)); unsafe { let view = js_sys::Float32Array::view(instances); gl.buffer_data_with_array_buffer_view(Gl::ARRAY_BUFFER, &view, Gl::STREAM_DRAW); } gl.draw_arrays_instanced(Gl::TRIANGLE_STRIP, 0, 4, instances.len() as i32 / MARK_FLOATS); } gl.bind_vertex_array(None); } }