├── .github
└── workflows
│ ├── demos.yml
│ └── rust.yml
├── .gitignore
├── Cargo.toml
├── LICENSE
├── README.md
├── assets
├── 01-triangle.png
├── 02-buffer.png
├── 03-uniform.png
└── 05-primitives.png
├── examples
├── 01-triangle
│ ├── Cargo.toml
│ ├── index.html
│ ├── shaders
│ │ ├── shader.frag
│ │ └── shader.vert
│ └── src
│ │ └── main.rs
├── 02-buffer
│ ├── Cargo.toml
│ ├── index.html
│ ├── shaders
│ │ ├── shader.frag
│ │ └── shader.vert
│ └── src
│ │ └── main.rs
├── 03-uniform
│ ├── Cargo.toml
│ ├── index.html
│ ├── shaders
│ │ ├── shader.frag
│ │ └── shader.vert
│ └── src
│ │ └── main.rs
├── 04-animate
│ ├── Cargo.toml
│ ├── index.html
│ ├── shaders
│ │ ├── shader.frag
│ │ └── shader.vert
│ └── src
│ │ └── main.rs
├── 05-primitives
│ ├── Cargo.toml
│ ├── index.html
│ └── src
│ │ └── main.rs
├── README.md
├── instances
│ ├── Cargo.toml
│ ├── index.html
│ ├── shaders
│ │ ├── shader.frag
│ │ └── shader.vert
│ └── src
│ │ └── main.rs
├── pong
│ ├── Cargo.toml
│ ├── index.html
│ └── src
│ │ └── main.rs
├── primitive-scene
│ ├── Cargo.toml
│ ├── index.html
│ └── src
│ │ └── main.rs
├── shaderfun
│ ├── Cargo.toml
│ ├── index.html
│ ├── shaders
│ │ ├── shader.frag
│ │ └── shader.vert
│ └── src
│ │ └── main.rs
├── snowflake
│ ├── Cargo.toml
│ ├── index.html
│ └── src
│ │ └── main.rs
└── zoom-pan
│ ├── Cargo.toml
│ ├── index.html
│ ├── shaders
│ ├── shader.frag
│ └── shader.vert
│ └── src
│ └── main.rs
├── limelight
├── Cargo.toml
├── README.md
├── limelight-derive
│ ├── Cargo.toml
│ ├── README.md
│ └── src
│ │ └── lib.rs
├── src
│ ├── attribute.rs
│ ├── buffer.rs
│ ├── draw_modes.rs
│ ├── lib.rs
│ ├── program.rs
│ ├── renderer.rs
│ ├── shadow_gpu
│ │ ├── buffer.rs
│ │ ├── mod.rs
│ │ ├── program.rs
│ │ ├── state.rs
│ │ ├── uniforms.rs
│ │ └── vao.rs
│ ├── state
│ │ ├── blending.rs
│ │ ├── culling.rs
│ │ ├── depth.rs
│ │ ├── enable.rs
│ │ └── mod.rs
│ ├── uniform.rs
│ └── webgl
│ │ ├── buffer.rs
│ │ ├── error.rs
│ │ ├── mod.rs
│ │ └── types.rs
└── tests
│ └── test_vertex_attribute_derive.rs
├── primitives
├── Cargo.toml
├── README.md
└── src
│ ├── circle
│ ├── mod.rs
│ ├── shader.frag
│ └── shader.vert
│ ├── color.rs
│ ├── common.rs
│ ├── hairline
│ ├── mod.rs
│ ├── shader.frag
│ └── shader.vert
│ ├── lib.rs
│ ├── line
│ ├── mod.rs
│ ├── shader.frag
│ └── shader.vert
│ ├── line3d
│ ├── mod.rs
│ ├── shader.frag
│ └── shader.vert
│ └── rect
│ ├── mod.rs
│ ├── shader.frag
│ └── shader.vert
├── transform
├── Cargo.toml
├── README.md
└── src
│ └── lib.rs
└── yew
├── Cargo.toml
├── README.md
└── src
├── key_event.rs
└── lib.rs
/.github/workflows/demos.yml:
--------------------------------------------------------------------------------
1 | name: Deploy Demos
2 |
3 | on:
4 | push:
5 | branches: [ main ]
6 |
7 | env:
8 | CARGO_TERM_COLOR: always
9 |
10 | jobs:
11 | build:
12 | runs-on: ubuntu-latest
13 |
14 | steps:
15 | - uses: actions/checkout@v2
16 | - name: Install Trunk
17 | run: wget -qO- https://github.com/thedodd/trunk/releases/download/v0.14.0/trunk-x86_64-unknown-linux-gnu.tar.gz | tar -xzf-
18 | - name: Install wasm32 target
19 | run: rustup target add wasm32-unknown-unknown
20 | - name: Install wasm-pack
21 | run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
22 | - name: Build demos
23 | run: |
24 | mkdir dist
25 | cd examples
26 | for DEMO_DIR in $(ls -d */)
27 | do
28 | cd "${DEMO_DIR}"
29 | ../../trunk build --release --public-url="/limelight/${DEMO_DIR}/"
30 | mv dist "../../dist/${DEMO_DIR}"
31 | cd ../
32 | done
33 | - name: Deploy 🚀
34 | uses: JamesIves/github-pages-deploy-action@4.1.4
35 | with:
36 | branch: gh-pages
37 | folder: dist
38 |
--------------------------------------------------------------------------------
/.github/workflows/rust.yml:
--------------------------------------------------------------------------------
1 | name: Rust
2 |
3 | on:
4 | push:
5 | branches: [ main ]
6 | pull_request:
7 | branches: [ main ]
8 |
9 | env:
10 | CARGO_TERM_COLOR: always
11 |
12 | jobs:
13 | build:
14 |
15 | runs-on: ubuntu-latest
16 |
17 | steps:
18 | - uses: actions/checkout@v2
19 | - name: Build
20 | run: cargo build --verbose
21 | - name: Run tests
22 | run: cargo test --verbose
23 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | target
2 | Cargo.lock
3 | dist
4 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [workspace]
2 |
3 | members = [
4 | "limelight",
5 | "transform",
6 | "primitives",
7 | "yew",
8 | "examples/*",
9 | ]
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Paul Butler
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Limelight
2 |
3 | [](https://github.com/drifting-in-space/limelight)
4 | [](https://crates.io/crates/limelight)
5 | [](https://docs.rs/limelight/)
6 | [](https://github.com/drifting-in-space/limelight/actions/workflows/rust.yml)
7 |
8 | Limelight is a `WebGL2` wrapper with a focus on making high-performance WebAssembly graphics code easier to write and maintain.
9 |
10 | https://user-images.githubusercontent.com/46173/146214698-784404f2-633c-4180-acda-1e8d64189e76.mov
11 |
12 | [live demo](https://drifting-in-space.github.io/limelight/primitive-scene/) ([code](https://github.com/drifting-in-space/limelight/tree/main/examples/primitive-scene))
13 |
14 | Specifically, `limelight`:
15 | - Provides a functional interface that **abstracts away the statefulness of WebGL**.
16 | It accomplishes this by using a *shadow GPU* that tracks the GPU's state, diffs it with the
17 | desired state, and sends only the necessary instructions to WebGL.
18 | - Provides abstractions for buffers and uniforms that **defer GPU data transfer until the next draw cycle**.
19 | - Provides a **typed interface to uniforms and buffers**, and automatically generates bindings
20 | between shader attributes and Rust `struct`s through a derive macro.
21 | - Provides an **interface for transforms** like zoom and pan through [`limelight-transform`](https://github.com/drifting-in-space/limelight/tree/main/transform).
22 | - Provides 2D **shape primitives** like circles and lines through [`limelight-primitives`](https://github.com/drifting-in-space/limelight/tree/main/primitives).
23 |
24 | # Getting started
25 |
26 |
27 |
28 | ([full code](https://github.com/drifting-in-space/limelight/tree/main/examples/05-primitives),
29 | [demo](https://drifting-in-space.github.io/limelight/05-primitives/))
30 |
31 | This example uses [`limelight-primitives`](https://github.com/drifting-in-space/limelight/tree/main/primitives)
32 | and [`limelight-yew`](https://github.com/drifting-in-space/limelight/tree/main/yew) to construct a basic, static image
33 | made from circles and rectangles.
34 |
35 | ```rust
36 | use anyhow::Result;
37 | use limelight::{renderer::Drawable, Renderer};
38 | use limelight_primitives::{Circle, CircleLayer, Rect, RectLayer};
39 | use limelight_yew::{LimelightComponent, LimelightController};
40 |
41 | struct Primitives {
42 | rects: RectLayer,
43 | circles: CircleLayer,
44 | }
45 |
46 | impl LimelightController for Primitives {
47 | fn draw(
48 | &mut self,
49 | renderer: &mut Renderer,
50 | _ts: f64,
51 | ) -> Result {
52 | self.rects.draw(renderer)?;
53 | self.circles.draw(renderer)?;
54 |
55 | Ok(false)
56 | }
57 | }
58 |
59 | impl Default for Primitives {
60 | fn default() -> Self {
61 | let rects = RectLayer::new();
62 | let circles = CircleLayer::new();
63 |
64 | rects.buffer().set_data(vec![
65 | Rect {
66 | lower_right: [0.4, 0.1],
67 | upper_left: [-0.8, 0.2],
68 | color: palette::named::TOMATO.into(),
69 | },
70 | Rect {
71 | lower_right: [0.4, 0.25],
72 | upper_left: [-0.6, 0.5],
73 | color: palette::named::SLATEBLUE.into(),
74 | },
75 | ]);
76 |
77 | circles.buffer().set_data(vec![
78 | Circle {
79 | position: [0., 0.25],
80 | radius: 0.2,
81 | color: palette::named::WHITE.into(),
82 | },
83 | Circle {
84 | position: [0., 0.25],
85 | radius: 0.1,
86 | color: palette::named::ORANGERED.into(),
87 | },
88 | ]);
89 |
90 | Primitives { rects, circles }
91 | }
92 | }
93 |
94 | fn main() {
95 | console_error_panic_hook::set_once();
96 | wasm_logger::init(wasm_logger::Config::default());
97 | yew::start_app::>();
98 | }
99 | ```
100 |
101 | # More Examples
102 |
103 | - [Instancing](https://drifting-in-space.github.io/limelight/instances/) ([code](https://github.com/drifting-in-space/limelight/tree/main/examples/instances))
104 | - [Pong](https://drifting-in-space.github.io/limelight/pong/) ([code](https://github.com/drifting-in-space/limelight/tree/main/examples/pong))
105 | - [Zooming / panning](https://drifting-in-space.github.io/limelight/zoom-pan/) ([code](https://github.com/drifting-in-space/limelight/tree/main/examples/zoom-pan))
106 | - [Full-canvas shader](https://drifting-in-space.github.io/limelight/shaderfun/) ([code](https://github.com/drifting-in-space/limelight/tree/main/examples/shaderfun))
107 | - [Primitive scene](https://drifting-in-space.github.io/limelight/primitive-scene/) ([code](https://github.com/drifting-in-space/limelight/tree/main/examples/primitive-scene))
108 | - [3D snowflake](https://drifting-in-space.github.io/limelight/snowflake/) ([code](https://github.com/drifting-in-space/limelight/tree/main/examples/snowflake))
109 |
--------------------------------------------------------------------------------
/assets/01-triangle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamsocket/limelight/22467f93b1bf5486e5bc3b947b86fab16c95c1f7/assets/01-triangle.png
--------------------------------------------------------------------------------
/assets/02-buffer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamsocket/limelight/22467f93b1bf5486e5bc3b947b86fab16c95c1f7/assets/02-buffer.png
--------------------------------------------------------------------------------
/assets/03-uniform.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamsocket/limelight/22467f93b1bf5486e5bc3b947b86fab16c95c1f7/assets/03-uniform.png
--------------------------------------------------------------------------------
/assets/05-primitives.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamsocket/limelight/22467f93b1bf5486e5bc3b947b86fab16c95c1f7/assets/05-primitives.png
--------------------------------------------------------------------------------
/examples/01-triangle/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "limelight-example-triangle"
3 | version = "0.1.0"
4 | edition = "2021"
5 |
6 | [dependencies]
7 | console_error_panic_hook = "0.1.6"
8 | wasm-bindgen = "0.2.74"
9 | web-sys = { version="0.3.55", features = [
10 | 'HtmlCanvasElement',
11 | 'Window',
12 | 'Document',
13 | ] }
14 | limelight = { path="../../limelight" }
15 |
--------------------------------------------------------------------------------
/examples/01-triangle/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | limelight demos
6 |
7 |
8 |
11 |
12 |
13 |
14 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/examples/01-triangle/shaders/shader.frag:
--------------------------------------------------------------------------------
1 | #version 300 es
2 |
3 | precision highp float;
4 |
5 | out vec4 color;
6 | in vec2 v_position;
7 |
8 | void main() {
9 | color = vec4(v_position.xy / 1.5 + 0.5, 0.3, 1.0);
10 | }
--------------------------------------------------------------------------------
/examples/01-triangle/shaders/shader.vert:
--------------------------------------------------------------------------------
1 | #version 300 es
2 |
3 | const float PI = 3.141592653589793238;
4 | in vec2 position;
5 | out vec2 v_position;
6 |
7 | void main() {
8 | float angle = (float(gl_VertexID) + (3./4.)) * 2. * PI / 3.;
9 | v_position = vec2(cos(angle) / 1.2, sin(angle) / 1.2);
10 | gl_Position = vec4(v_position, 0., 1.);
11 | }
--------------------------------------------------------------------------------
/examples/01-triangle/src/main.rs:
--------------------------------------------------------------------------------
1 | use limelight::{DrawMode, DummyBuffer, Program, Renderer};
2 | use wasm_bindgen::JsCast;
3 | use web_sys::WebGl2RenderingContext;
4 |
5 | fn render_triangle(gl: WebGl2RenderingContext) {
6 | // limelight doesn't touch the DOM at all. Use your preferred
7 | // framework to create a canvas and create a WebGL2 context
8 | // from it.
9 |
10 | // Create a shader program by passing in GLSL code as strings for
11 | // the fragment and vertex shaders.
12 | let mut program = Program::new(
13 | include_str!("../shaders/shader.vert"),
14 | include_str!("../shaders/shader.frag"),
15 | DrawMode::Triangles,
16 | );
17 |
18 | // Create a renderer. The renderer becomes the owner of the
19 | // WebGl2RenderingContext, to ensure that its internal representation
20 | // of the GPU state is always accureate.
21 | let mut renderer = Renderer::new(gl);
22 |
23 | // Run the program, rendering the results to the screen. We are
24 | // not passing any vertex attribute data, so we use a `DummyBuffer`
25 | // which renders three vertices: one for each corner of a triangle.
26 | renderer.render(&mut program, &DummyBuffer::new(3)).unwrap();
27 | }
28 |
29 | fn get_gl() -> WebGl2RenderingContext {
30 | let document = web_sys::window().unwrap().document().unwrap();
31 | let canvas = document.get_element_by_id("canvas").unwrap();
32 | let canvas: web_sys::HtmlCanvasElement =
33 | canvas.dyn_into::().unwrap();
34 |
35 | canvas
36 | .get_context("webgl2")
37 | .unwrap()
38 | .unwrap()
39 | .dyn_into::()
40 | .unwrap()
41 | }
42 |
43 | fn main() {
44 | console_error_panic_hook::set_once();
45 | let gl = get_gl();
46 |
47 | render_triangle(gl);
48 | }
49 |
--------------------------------------------------------------------------------
/examples/02-buffer/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "limelight-example-buffer"
3 | version = "0.1.0"
4 | edition = "2021"
5 |
6 | [dependencies]
7 | console_error_panic_hook = "0.1.6"
8 | wasm-bindgen = "0.2.74"
9 | web-sys = { version="0.3.55", features = [
10 | 'HtmlCanvasElement',
11 | 'Window',
12 | 'Document',
13 | ] }
14 | limelight = { path="../../limelight" }
15 | bytemuck = { version = "1.7.2", features = ["derive"] }
16 |
--------------------------------------------------------------------------------
/examples/02-buffer/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | limelight demos
6 |
7 |
8 |
11 |
12 |
13 |
14 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/examples/02-buffer/shaders/shader.frag:
--------------------------------------------------------------------------------
1 | #version 300 es
2 |
3 | precision highp float;
4 |
5 | out vec4 color;
6 |
7 | void main() {
8 | color = vec4(0.5, 0.0, 0.3, 1.0);
9 | }
--------------------------------------------------------------------------------
/examples/02-buffer/shaders/shader.vert:
--------------------------------------------------------------------------------
1 | #version 300 es
2 |
3 | in vec2 position;
4 |
5 | void main() {
6 | gl_Position = vec4(position, 0., 1.);
7 | }
--------------------------------------------------------------------------------
/examples/02-buffer/src/main.rs:
--------------------------------------------------------------------------------
1 | use limelight::{attribute, Buffer, BufferUsageHint, DrawMode, Program, Renderer};
2 | use wasm_bindgen::JsCast;
3 | use web_sys::WebGl2RenderingContext;
4 |
5 | // This attribute macro derives a number of traits, including `VertexAttribute`, which
6 | // is required for a type to be used in an `AttributeBuffer`.
7 | #[attribute]
8 | struct VertexDescription {
9 | position: [f32; 2], // field names are mapped to variables in the shader.
10 | }
11 |
12 | impl VertexDescription {
13 | pub fn new(x: f32, y: f32) -> Self {
14 | VertexDescription { position: [x, y] }
15 | }
16 | }
17 |
18 | fn render_triangles(gl: WebGl2RenderingContext) {
19 | let mut program = Program::new(
20 | include_str!("../shaders/shader.vert"),
21 | include_str!("../shaders/shader.frag"),
22 | DrawMode::Triangles,
23 | );
24 |
25 | let mut renderer = Renderer::new(gl);
26 |
27 | let data = vec![
28 | // Lower-left triangle.
29 | VertexDescription::new(-0.1, -0.1),
30 | VertexDescription::new(-0.5, -0.1),
31 | VertexDescription::new(-0.5, -0.5),
32 | // Upper-right triangle.
33 | VertexDescription::new(0.1, 0.1),
34 | VertexDescription::new(0.5, 0.1),
35 | VertexDescription::new(0.5, 0.5),
36 | ];
37 |
38 | let buffer = Buffer::new(data, BufferUsageHint::StaticDraw);
39 |
40 | renderer.render(&mut program, &buffer).unwrap();
41 | }
42 |
43 | fn get_gl() -> WebGl2RenderingContext {
44 | let document = web_sys::window().unwrap().document().unwrap();
45 | let canvas = document.get_element_by_id("canvas").unwrap();
46 | let canvas: web_sys::HtmlCanvasElement =
47 | canvas.dyn_into::().unwrap();
48 |
49 | canvas
50 | .get_context("webgl2")
51 | .unwrap()
52 | .unwrap()
53 | .dyn_into::()
54 | .unwrap()
55 | }
56 |
57 | fn main() {
58 | console_error_panic_hook::set_once();
59 | let gl = get_gl();
60 |
61 | render_triangles(gl);
62 | }
63 |
--------------------------------------------------------------------------------
/examples/03-uniform/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "limelight-example-uniform"
3 | version = "0.1.0"
4 | edition = "2021"
5 |
6 | [dependencies]
7 | console_error_panic_hook = "0.1.6"
8 | wasm-bindgen = "0.2.74"
9 | web-sys = { version="0.3.55", features = [
10 | 'HtmlCanvasElement',
11 | 'Window',
12 | 'Document',
13 | ] }
14 | limelight = { path="../../limelight" }
15 |
--------------------------------------------------------------------------------
/examples/03-uniform/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | limelight demos
6 |
7 |
8 |
11 |
12 |
13 |
14 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/examples/03-uniform/shaders/shader.frag:
--------------------------------------------------------------------------------
1 | #version 300 es
2 |
3 | precision highp float;
4 |
5 | uniform vec3 u_color;
6 | out vec4 color;
7 |
8 | void main() {
9 | color = vec4(u_color, 1.0);
10 | }
--------------------------------------------------------------------------------
/examples/03-uniform/shaders/shader.vert:
--------------------------------------------------------------------------------
1 | #version 300 es
2 |
3 | const float PI = 3.141592653589793238;
4 | in vec2 position;
5 | uniform float u_rotate;
6 | uniform vec2 u_scale;
7 | out vec2 v_position;
8 |
9 | void main() {
10 | float angle = (float(gl_VertexID) + 3./4.) * 2. * PI / 3. + u_rotate;
11 | v_position = vec2(cos(angle) / 1.2, sin(angle) / 1.2) * u_scale;
12 | gl_Position = vec4(v_position, 0., 1.);
13 | }
--------------------------------------------------------------------------------
/examples/03-uniform/src/main.rs:
--------------------------------------------------------------------------------
1 | use limelight::{DrawMode, DummyBuffer, Program, Renderer, Uniform};
2 | use wasm_bindgen::JsCast;
3 | use web_sys::WebGl2RenderingContext;
4 |
5 | fn render_triangles_with_uniform(gl: WebGl2RenderingContext) {
6 | let rotate_uniform = Uniform::new(std::f32::consts::PI / 3.4);
7 | let scale_uniform = Uniform::new([0.5, 0.8]);
8 | let color_uniform = Uniform::new([0.9, 0.2, 0.3]);
9 |
10 | let mut program = Program::new(
11 | include_str!("../shaders/shader.vert"),
12 | include_str!("../shaders/shader.frag"),
13 | DrawMode::Triangles,
14 | )
15 | .with_uniform("u_rotate", rotate_uniform)
16 | .with_uniform("u_scale", scale_uniform)
17 | .with_uniform("u_color", color_uniform);
18 |
19 | let mut renderer = Renderer::new(gl);
20 | renderer.render(&mut program, &DummyBuffer::new(3)).unwrap();
21 | }
22 |
23 | fn get_gl() -> WebGl2RenderingContext {
24 | let document = web_sys::window().unwrap().document().unwrap();
25 | let canvas = document.get_element_by_id("canvas").unwrap();
26 | let canvas: web_sys::HtmlCanvasElement =
27 | canvas.dyn_into::().unwrap();
28 |
29 | canvas
30 | .get_context("webgl2")
31 | .unwrap()
32 | .unwrap()
33 | .dyn_into::()
34 | .unwrap()
35 | }
36 |
37 | fn main() {
38 | console_error_panic_hook::set_once();
39 | let gl = get_gl();
40 |
41 | render_triangles_with_uniform(gl);
42 | }
43 |
--------------------------------------------------------------------------------
/examples/04-animate/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "limelight-example-animate"
3 | version = "0.1.0"
4 | edition = "2021"
5 |
6 | [dependencies]
7 | anyhow = "1.0.51"
8 | bytemuck = "1.7.2"
9 | console_error_panic_hook = "0.1.6"
10 | limelight = { path="../../limelight" }
11 | limelight-yew = {path="../../yew"}
12 | wasm-bindgen = "0.2.78"
13 | wasm-logger = "0.2.0"
14 | web-sys = { version="0.3.55", features=["Window"] }
15 | yew = "0.19.3"
16 |
--------------------------------------------------------------------------------
/examples/04-animate/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | limelight demo
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/examples/04-animate/shaders/shader.frag:
--------------------------------------------------------------------------------
1 | #version 300 es
2 |
3 | precision highp float;
4 |
5 | out vec4 color;
6 | uniform vec3 u_color;
7 |
8 | void main() {
9 | color = vec4(u_color, 1.0);
10 | }
--------------------------------------------------------------------------------
/examples/04-animate/shaders/shader.vert:
--------------------------------------------------------------------------------
1 | #version 300 es
2 |
3 | in vec2 position;
4 |
5 | void main() {
6 | gl_Position = vec4(position * 0.5, 0., 1.);
7 | }
--------------------------------------------------------------------------------
/examples/04-animate/src/main.rs:
--------------------------------------------------------------------------------
1 | use anyhow::Result;
2 | use limelight::{attribute, Buffer, BufferUsageHint, DrawMode, Program, Renderer, Uniform};
3 | use limelight_yew::{LimelightComponent, LimelightController, ShouldRequestAnimationFrame};
4 |
5 | #[attribute]
6 | struct VertexDescription {
7 | position: [f32; 2],
8 | }
9 |
10 | impl VertexDescription {
11 | pub fn new(x: f32, y: f32) -> Self {
12 | VertexDescription { position: [x, y] }
13 | }
14 | }
15 |
16 | struct Animation {
17 | program: Program,
18 | buffer: Buffer,
19 | uniform: Uniform<[f32; 3]>,
20 | }
21 |
22 | impl Default for Animation {
23 | fn default() -> Self {
24 | let buffer = Buffer::new(vec![], BufferUsageHint::DynamicDraw);
25 | let uniform = Uniform::new([0., 0., 0.]);
26 |
27 | let program = Program::new(
28 | include_str!("../shaders/shader.vert"),
29 | include_str!("../shaders/shader.frag"),
30 | DrawMode::Triangles,
31 | )
32 | .with_uniform("u_color", uniform.clone());
33 |
34 | Animation {
35 | buffer,
36 | program,
37 | uniform,
38 | }
39 | }
40 | }
41 |
42 | impl LimelightController for Animation {
43 | fn draw(&mut self, renderer: &mut Renderer, time: f64) -> Result {
44 | let theta1 = time as f32 / 1000.;
45 | let theta2 = theta1 + (std::f32::consts::TAU / 3.);
46 | let theta3 = theta2 + (std::f32::consts::TAU / 3.);
47 |
48 | self.buffer.set_data(vec![
49 | VertexDescription::new(theta1.cos(), theta1.sin()),
50 | VertexDescription::new(theta2.cos(), theta2.sin()),
51 | VertexDescription::new(theta3.cos(), theta3.sin()),
52 | ]);
53 |
54 | let r = (time as f32 / 3000.).sin() / 2. + 0.5;
55 | let g = (time as f32 / 5000.).sin() / 2. + 0.5;
56 | let b = (time as f32 / 7000.).sin() / 2. + 0.5;
57 |
58 | self.uniform.set_value([r, g, b]);
59 |
60 | renderer.render(&mut self.program, &self.buffer)?;
61 |
62 | Ok(true)
63 | }
64 | }
65 |
66 | fn main() {
67 | console_error_panic_hook::set_once();
68 | wasm_logger::init(wasm_logger::Config::default());
69 |
70 | yew::start_app::>();
71 | }
72 |
--------------------------------------------------------------------------------
/examples/05-primitives/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "limelight-example-primitives"
3 | version = "0.1.0"
4 | edition = "2021"
5 |
6 | [dependencies]
7 | console_error_panic_hook = "0.1.6"
8 | wasm-bindgen = "0.2.74"
9 | web-sys = { version="0.3.55", features = [
10 | 'HtmlCanvasElement',
11 | 'Window',
12 | 'Document',
13 | ] }
14 | limelight = { path="../../limelight" }
15 | limelight-primitives = {path="../../primitives"}
16 | limelight-transform = {path="../../transform"}
17 | palette = { version = "0.6.0", features = ["named"] }
18 | wasm-logger = "0.2.0"
19 | limelight-yew = {path="../../yew"}
20 | yew = "0.19.3"
21 | anyhow = "1.0.51"
22 |
--------------------------------------------------------------------------------
/examples/05-primitives/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | limelight demos
6 |
7 |
8 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/examples/05-primitives/src/main.rs:
--------------------------------------------------------------------------------
1 | use anyhow::Result;
2 | use limelight::{renderer::Drawable, Renderer};
3 | use limelight_primitives::{Circle, CircleLayer, Rect, RectLayer};
4 | use limelight_yew::{LimelightComponent, LimelightController};
5 |
6 | struct Primitives {
7 | rects: RectLayer,
8 | circles: CircleLayer,
9 | }
10 |
11 | impl LimelightController for Primitives {
12 | fn draw(
13 | &mut self,
14 | renderer: &mut Renderer,
15 | _ts: f64,
16 | ) -> Result {
17 | self.rects.draw(renderer)?;
18 | self.circles.draw(renderer)?;
19 |
20 | Ok(false)
21 | }
22 | }
23 |
24 | impl Default for Primitives {
25 | fn default() -> Self {
26 | let rects = RectLayer::new();
27 | let circles = CircleLayer::new();
28 |
29 | rects.buffer().set_data(vec![
30 | Rect {
31 | lower_right: [0.4, 0.1],
32 | upper_left: [-0.8, 0.2],
33 | color: palette::named::TOMATO.into(),
34 | },
35 | Rect {
36 | lower_right: [0.4, 0.25],
37 | upper_left: [-0.6, 0.5],
38 | color: palette::named::SLATEBLUE.into(),
39 | },
40 | ]);
41 |
42 | circles.buffer().set_data(vec![
43 | Circle {
44 | position: [0., 0.25],
45 | radius: 0.2,
46 | color: palette::named::WHITE.into(),
47 | },
48 | Circle {
49 | position: [0., 0.25],
50 | radius: 0.1,
51 | color: palette::named::ORANGERED.into(),
52 | },
53 | ]);
54 |
55 | Primitives { rects, circles }
56 | }
57 | }
58 |
59 | fn main() {
60 | console_error_panic_hook::set_once();
61 | wasm_logger::init(wasm_logger::Config::default());
62 | yew::start_app::>();
63 | }
64 |
--------------------------------------------------------------------------------
/examples/README.md:
--------------------------------------------------------------------------------
1 | Use [trunk](https://github.com/thedodd/trunk) to run these examples, as in:
2 |
3 | ```bash
4 | $ cd animate-triangle
5 | $ trunk serve --release
6 | ```
7 |
8 | `--release` is recommended because WASM code runs pretty slow without it.
--------------------------------------------------------------------------------
/examples/instances/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "limelight-example-instances"
3 | version = "0.1.0"
4 | edition = "2021"
5 |
6 | [dependencies]
7 | console_error_panic_hook = "0.1.6"
8 | wasm-bindgen = "0.2.74"
9 | web-sys = { version="0.3.55", features = [
10 | 'HtmlCanvasElement',
11 | 'Window',
12 | 'Document',
13 | ] }
14 | limelight = { path="../../limelight" }
15 | bytemuck = { version = "1.7.2", features = ["derive"] }
16 |
--------------------------------------------------------------------------------
/examples/instances/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | limelight demos
6 |
7 |
8 |
11 |
12 |
13 |
14 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/examples/instances/shaders/shader.frag:
--------------------------------------------------------------------------------
1 | #version 300 es
2 |
3 | precision highp float;
4 |
5 | out vec4 color;
6 | in vec2 v_position;
7 | in vec2 m_position;
8 |
9 | void main() {
10 | color = vec4(m_position.xy, 0., 1.0);
11 | }
--------------------------------------------------------------------------------
/examples/instances/shaders/shader.vert:
--------------------------------------------------------------------------------
1 | #version 300 es
2 |
3 | const float PI = 3.141592653589793238;
4 | in int instance_index;
5 | in int vertex_index;
6 | out vec2 v_position;
7 | out vec2 m_position;
8 |
9 | void main() {
10 | float angle = (float(instance_index) + (3./4.)) * 2. * PI / 20.;
11 | v_position = vec2(cos(angle) / 2., sin(angle) / 2.);
12 |
13 | float angle2 = (float(vertex_index) + (3./4.)) * 2. * PI / 4.;
14 | v_position += vec2(cos(angle2) / 10., sin(angle2) / 10.);
15 |
16 | gl_PointSize = 10.0;
17 | gl_Position = vec4(v_position, 0., 1.);
18 | m_position = vec2(float(instance_index) / 20., float(vertex_index) / 4.);
19 | }
--------------------------------------------------------------------------------
/examples/instances/src/main.rs:
--------------------------------------------------------------------------------
1 | use limelight::{attribute, Buffer, BufferUsageHint, DrawMode, Program, Renderer};
2 | use wasm_bindgen::JsCast;
3 | use web_sys::WebGl2RenderingContext;
4 |
5 | #[attribute]
6 | struct InstanceAttribute {
7 | instance_index: i32,
8 | }
9 |
10 | #[attribute]
11 | struct VertexAttribute {
12 | vertex_index: i32,
13 | }
14 |
15 | fn render_triangle(gl: WebGl2RenderingContext) {
16 | let mut program = Program::new(
17 | include_str!("../shaders/shader.vert"),
18 | include_str!("../shaders/shader.frag"),
19 | DrawMode::TriangleFan,
20 | );
21 |
22 | let mut renderer = Renderer::new(gl);
23 |
24 | let instances = Buffer::new(
25 | (0..20)
26 | .map(|instance_index| InstanceAttribute { instance_index })
27 | .collect(),
28 | BufferUsageHint::StaticDraw,
29 | );
30 |
31 | let vertices = Buffer::new(
32 | (0..4)
33 | .map(|vertex_index| VertexAttribute { vertex_index })
34 | .collect(),
35 | BufferUsageHint::StaticDraw,
36 | );
37 |
38 | renderer
39 | .render_instanced(&mut program, &vertices, &instances)
40 | .unwrap();
41 | }
42 |
43 | fn get_gl() -> WebGl2RenderingContext {
44 | let document = web_sys::window().unwrap().document().unwrap();
45 | let canvas = document.get_element_by_id("canvas").unwrap();
46 | let canvas: web_sys::HtmlCanvasElement =
47 | canvas.dyn_into::().unwrap();
48 |
49 | canvas
50 | .get_context("webgl2")
51 | .unwrap()
52 | .unwrap()
53 | .dyn_into::()
54 | .unwrap()
55 | }
56 |
57 | fn main() {
58 | console_error_panic_hook::set_once();
59 | let gl = get_gl();
60 |
61 | render_triangle(gl);
62 | }
63 |
--------------------------------------------------------------------------------
/examples/pong/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "pong"
3 | version = "0.1.0"
4 | edition = "2021"
5 |
6 | [dependencies]
7 | console_error_panic_hook = "0.1.6"
8 | wasm-bindgen = "0.2.78"
9 | web-sys = { version="0.3.55", features=["Window"] }
10 | limelight = { path="../../limelight" }
11 | bytemuck = "1.7.2"
12 | limelight-yew = {path="../../yew"}
13 | limelight-primitives = {path="../../primitives"}
14 | yew = "0.19.3"
15 | anyhow = "1.0.51"
16 | wasm-logger = "0.2.0"
17 | log = "0.4.14"
18 | palette = "0.6.0"
19 |
--------------------------------------------------------------------------------
/examples/pong/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | limelight demo
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/examples/pong/src/main.rs:
--------------------------------------------------------------------------------
1 | use anyhow::Result;
2 | use limelight::renderer::Drawable;
3 | use limelight::Renderer;
4 | use limelight_primitives::{Circle, CircleLayer, Rect, RectLayer};
5 | use limelight_yew::{
6 | KeyCode, LimelightComponent, LimelightController, ShouldCancelEvent,
7 | ShouldRequestAnimationFrame,
8 | };
9 |
10 | const UPPER_BOUND: f32 = 1.;
11 | const LOWER_BOUND: f32 = -1.;
12 |
13 | const LEFT_BOUND: f32 = -1.;
14 | const RIGHT_BOUND: f32 = 1.;
15 | const PADDLE_BUFFER: f32 = 0.1;
16 |
17 | const PADDLE_HEIGHT: f32 = 0.3;
18 | const BALL_RADIUS: f32 = 0.025;
19 |
20 | const PADDLE_WIDTH: f32 = 0.05;
21 | const PADDLE_SPEED: f32 = 0.01;
22 |
23 | const BALL_SPEED: f32 = 0.01;
24 |
25 | struct GameState {
26 | paddle_position: f32,
27 | ball_position: [f32; 2],
28 | ball_direction: [f32; 2],
29 | last_time: f64,
30 | }
31 |
32 | impl GameState {
33 | fn step(&mut self, ts: f64, paddle_direction: f32) {
34 | let time_delta = ((ts - self.last_time) / 20.) as f32;
35 | self.last_time = ts;
36 |
37 | self.paddle_position += paddle_direction * PADDLE_SPEED * time_delta;
38 | self.ball_position = [
39 | self.ball_position[0] + self.ball_direction[0] * BALL_SPEED * time_delta,
40 | self.ball_position[1] + self.ball_direction[1] * BALL_SPEED * time_delta,
41 | ];
42 |
43 | if ((LEFT_BOUND + PADDLE_BUFFER - BALL_SPEED + PADDLE_WIDTH / 2.)
44 | ..=(LEFT_BOUND + PADDLE_BUFFER + BALL_SPEED + PADDLE_WIDTH / 2.))
45 | .contains(&self.ball_position[0])
46 | && ((self.paddle_position - PADDLE_HEIGHT / 2.)
47 | ..(self.paddle_position + PADDLE_HEIGHT / 2.))
48 | .contains(&self.ball_position[1])
49 | {
50 | self.ball_direction[0] = -self.ball_direction[0];
51 | }
52 |
53 | if ((RIGHT_BOUND - PADDLE_BUFFER - BALL_SPEED - PADDLE_WIDTH / 2.)
54 | ..=(RIGHT_BOUND - PADDLE_BUFFER + BALL_SPEED - PADDLE_WIDTH / 2.))
55 | .contains(&self.ball_position[0])
56 | && ((-self.paddle_position - PADDLE_HEIGHT / 2.)
57 | ..(-self.paddle_position + PADDLE_HEIGHT / 2.))
58 | .contains(&self.ball_position[1])
59 | {
60 | self.ball_direction[0] = -self.ball_direction[0];
61 | }
62 |
63 | if self.ball_position[1] + BALL_RADIUS > UPPER_BOUND {
64 | self.ball_position[1] = UPPER_BOUND - BALL_RADIUS;
65 | self.ball_direction[1] = -self.ball_direction[1];
66 | }
67 |
68 | if self.ball_position[1] - BALL_RADIUS < LOWER_BOUND {
69 | self.ball_position[1] = LOWER_BOUND + BALL_RADIUS;
70 | self.ball_direction[1] = -self.ball_direction[1];
71 | }
72 |
73 | // Losing Conditions
74 | if self.ball_position[0] > RIGHT_BOUND + BALL_RADIUS
75 | || self.ball_position[0] < LEFT_BOUND - BALL_RADIUS
76 | {
77 | self.ball_position = [0., 0.];
78 | self.ball_direction = [-self.ball_direction[0], self.ball_direction[1]];
79 | }
80 | }
81 | }
82 |
83 | impl Default for GameState {
84 | fn default() -> Self {
85 | GameState {
86 | paddle_position: 0.,
87 | ball_direction: [1., 1.],
88 | ball_position: [0., 0.],
89 | last_time: 0.,
90 | }
91 | }
92 | }
93 |
94 | struct PongGame {
95 | paddles: RectLayer,
96 | ball: CircleLayer,
97 | state: GameState,
98 | paddle_direction: f32,
99 | }
100 |
101 | impl LimelightController for PongGame {
102 | fn draw(&mut self, renderer: &mut Renderer, ts: f64) -> Result {
103 | self.state.step(ts, self.paddle_direction);
104 |
105 | let left_paddle_left = LEFT_BOUND + PADDLE_BUFFER - PADDLE_WIDTH / 2.;
106 | let left_paddle_right = LEFT_BOUND + PADDLE_BUFFER + PADDLE_WIDTH / 2.;
107 | let left_paddle_top = self.state.paddle_position + PADDLE_HEIGHT / 2.;
108 | let left_paddle_bottom = self.state.paddle_position - PADDLE_HEIGHT / 2.;
109 |
110 | let right_paddle_left = RIGHT_BOUND - PADDLE_BUFFER - PADDLE_WIDTH / 2.;
111 | let right_paddle_right = RIGHT_BOUND - PADDLE_BUFFER + PADDLE_WIDTH / 2.;
112 | let right_paddle_top = -self.state.paddle_position + PADDLE_HEIGHT / 2.;
113 | let right_paddle_bottom = -self.state.paddle_position - PADDLE_HEIGHT / 2.;
114 |
115 | self.paddles.buffer().set_data(vec![
116 | Rect {
117 | upper_left: [left_paddle_left, left_paddle_top],
118 | lower_right: [left_paddle_right, left_paddle_bottom],
119 | color: palette::named::ORANGERED.into(),
120 | },
121 | Rect {
122 | upper_left: [right_paddle_left, right_paddle_top],
123 | lower_right: [right_paddle_right, right_paddle_bottom],
124 | color: palette::named::ORANGERED.into(),
125 | },
126 | ]);
127 |
128 | self.ball.buffer().set_data(vec![Circle {
129 | position: self.state.ball_position,
130 | radius: BALL_RADIUS,
131 | color: palette::named::NAVY.into(),
132 | }]);
133 |
134 | self.paddles.draw(renderer)?;
135 | self.ball.draw(renderer)?;
136 |
137 | Ok(true)
138 | }
139 |
140 | fn handle_key_down(
141 | &mut self,
142 | key: KeyCode,
143 | ) -> (ShouldRequestAnimationFrame, ShouldCancelEvent) {
144 | match key {
145 | KeyCode::ArrowUp => self.paddle_direction = -1.,
146 | KeyCode::ArrowDown => self.paddle_direction = 1.,
147 | _ => return (false, false),
148 | }
149 |
150 | (false, true)
151 | }
152 |
153 | fn handle_key_up(&mut self, key: KeyCode) -> (ShouldRequestAnimationFrame, ShouldCancelEvent) {
154 | match key {
155 | KeyCode::ArrowUp | KeyCode::ArrowDown => self.paddle_direction = 0.,
156 | _ => return (false, false),
157 | }
158 |
159 | (false, true)
160 | }
161 | }
162 |
163 | impl Default for PongGame {
164 | fn default() -> Self {
165 | Self {
166 | state: GameState::default(),
167 | paddle_direction: 0.,
168 | ball: CircleLayer::new(),
169 | paddles: RectLayer::new(),
170 | }
171 | }
172 | }
173 |
174 | fn main() {
175 | console_error_panic_hook::set_once();
176 | wasm_logger::init(wasm_logger::Config::default());
177 | yew::start_app::>();
178 | }
179 |
--------------------------------------------------------------------------------
/examples/primitive-scene/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "limelight-example-primitive-scene"
3 | version = "0.1.0"
4 | edition = "2021"
5 |
6 | [dependencies]
7 | console_error_panic_hook = "0.1.6"
8 | wasm-bindgen = "0.2.74"
9 | web-sys = { version="0.3.55", features = [
10 | 'HtmlCanvasElement',
11 | 'Window',
12 | 'Document',
13 | ] }
14 | limelight = { path="../../limelight" }
15 | limelight-primitives = {path="../../primitives"}
16 | limelight-transform = {path="../../transform"}
17 | palette = { version = "0.6.0", features = ["named"] }
18 | wasm-logger = "0.2.0"
19 | limelight-yew = {path="../../yew"}
20 | yew = "0.19.3"
21 | anyhow = "1.0.51"
22 |
--------------------------------------------------------------------------------
/examples/primitive-scene/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | limelight demos
6 |
7 |
8 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/examples/primitive-scene/src/main.rs:
--------------------------------------------------------------------------------
1 | use anyhow::Result;
2 | use limelight::{renderer::Drawable, Renderer};
3 | use limelight_primitives::{
4 | Circle, CircleLayer, Color, Hairline, HairlineLayer, Line, LineLayer, Orientation, Rect,
5 | RectLayer,
6 | };
7 | use limelight_transform::TransformUniform;
8 | use limelight_yew::{
9 | LimelightComponent, LimelightController, ShouldCancelEvent, ShouldRequestAnimationFrame,
10 | };
11 |
12 | struct Primitives {
13 | layers: Vec>,
14 | fg_transform: TransformUniform,
15 | bg_transform: TransformUniform,
16 | }
17 |
18 | impl LimelightController for Primitives {
19 | fn draw(&mut self, renderer: &mut Renderer, _ts: f64) -> Result {
20 | for layer in self.layers.iter_mut() {
21 | layer.draw(renderer)?;
22 | }
23 |
24 | Ok(false)
25 | }
26 |
27 | fn handle_drag(&mut self, x: f32, y: f32) -> ShouldRequestAnimationFrame {
28 | self.fg_transform.pan((x, y));
29 | self.bg_transform.shear((x, y));
30 | true
31 | }
32 |
33 | fn handle_scroll(
34 | &mut self,
35 | _x_amount: f32,
36 | y_amount: f32,
37 | x_position: f32,
38 | y_position: f32,
39 | ) -> (ShouldRequestAnimationFrame, ShouldCancelEvent) {
40 | self.fg_transform
41 | .scale(1. + y_amount as f32 / 3., (x_position, y_position));
42 | self.bg_transform
43 | .scale(1. + y_amount as f32 / 3., (x_position, y_position));
44 | (true, true)
45 | }
46 | }
47 |
48 | impl Default for Primitives {
49 | fn default() -> Self {
50 | let mut layers: Vec> = Vec::new();
51 | let fg_transform = TransformUniform::new();
52 | let bg_transform = TransformUniform::new();
53 |
54 | let lines = LineLayer::new_transform(bg_transform.uniform());
55 | let rects = RectLayer::new_transform(bg_transform.uniform());
56 | let circles = CircleLayer::new_transform(fg_transform.uniform());
57 | let hairlines = HairlineLayer::new_transform(fg_transform.uniform());
58 |
59 | lines.buffer().set_data(vec![
60 | Line {
61 | start: [0., 0.],
62 | end: [0.4, 0.9],
63 | width: 0.03,
64 | color: palette::named::GOLD.into(),
65 | },
66 | Line {
67 | start: [-0.3, -0.3],
68 | end: [0.4, 0.9],
69 | width: 0.01,
70 | color: palette::named::FIREBRICK.into(),
71 | },
72 | ]);
73 |
74 | rects.buffer().set_data(vec![
75 | Rect {
76 | lower_right: [-0.3, 0.1],
77 | upper_left: [-0.8, 0.2],
78 | color: palette::named::SEAGREEN.into(),
79 | },
80 | Rect {
81 | lower_right: [-0.3, 0.25],
82 | upper_left: [-0.6, 0.35],
83 | color: palette::named::PALEVIOLETRED.into(),
84 | },
85 | Rect {
86 | lower_right: [-0.3, 0.4],
87 | upper_left: [-0.4, 0.5],
88 | color: palette::named::ORANGERED.into(),
89 | },
90 | ]);
91 |
92 | circles.buffer().set_data(vec![
93 | Circle {
94 | position: [-0.2, 0.3],
95 | radius: 0.2,
96 | color: Color(0x44332266),
97 | },
98 | Circle {
99 | position: [0.3, 0.3],
100 | radius: 0.1,
101 | color: Color(0x66111111),
102 | },
103 | ]);
104 |
105 | hairlines.buffer().set_data(vec![
106 | Hairline {
107 | orientation: Orientation::Horizontal,
108 | location: 0.65,
109 | color: palette::named::RED.into(),
110 | },
111 | Hairline {
112 | orientation: Orientation::Vertical,
113 | location: 0.65,
114 | color: Color::from(palette::named::DARKBLUE).opacity(0.4),
115 | },
116 | Hairline {
117 | orientation: Orientation::Vertical,
118 | location: 0.7,
119 | color: Color::from(palette::named::DARKCYAN).opacity(0.3),
120 | },
121 | Hairline {
122 | orientation: Orientation::Vertical,
123 | location: 0.75,
124 | color: Color::from(palette::named::DARKMAGENTA).opacity(0.2),
125 | },
126 | ]);
127 |
128 | layers.push(Box::new(lines));
129 | layers.push(Box::new(rects));
130 | layers.push(Box::new(circles));
131 | layers.push(Box::new(hairlines));
132 |
133 | Primitives {
134 | layers,
135 | fg_transform,
136 | bg_transform,
137 | }
138 | }
139 | }
140 |
141 | fn main() {
142 | console_error_panic_hook::set_once();
143 | wasm_logger::init(wasm_logger::Config::default());
144 | yew::start_app::>();
145 | }
146 |
--------------------------------------------------------------------------------
/examples/shaderfun/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "shaderfun"
3 | version = "0.1.0"
4 | edition = "2021"
5 |
6 | [dependencies]
7 | console_error_panic_hook = "0.1.6"
8 | wasm-bindgen = "0.2.78"
9 | web-sys = { version="0.3.55", features = [
10 | 'HtmlCanvasElement',
11 | 'Window',
12 | 'Document',
13 | ] }
14 | limelight = { path="../../limelight" }
15 | bytemuck = "1.7.2"
16 | yew = "0.19.3"
17 | limelight-yew = {path="../../yew"}
18 | anyhow = "1.0.53"
19 |
--------------------------------------------------------------------------------
/examples/shaderfun/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | limelight demo
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/examples/shaderfun/shaders/shader.frag:
--------------------------------------------------------------------------------
1 | #version 300 es
2 |
3 | precision highp float;
4 |
5 | uniform float u_time;
6 | uniform vec2 u_pos;
7 | in vec2 v_pos;
8 | out vec4 color;
9 |
10 | void main() {
11 | if (length(u_pos - v_pos) < (sin(u_time) + 1.) / 10.) {
12 | color = vec4(0.8, u_pos, 1.0);
13 | } else {
14 | color = vec4(u_pos, 0.5, 1.0);
15 | }
16 | }
--------------------------------------------------------------------------------
/examples/shaderfun/shaders/shader.vert:
--------------------------------------------------------------------------------
1 | #version 300 es
2 |
3 | in vec4 position;
4 | out vec2 v_pos;
5 |
6 | const vec2 positions[3] = vec2[3](
7 | vec2(-1., 1.),
8 | vec2(3., 1.),
9 | vec2(-1., -3.)
10 | );
11 |
12 | void main() {
13 | v_pos = positions[gl_VertexID];
14 | gl_Position = vec4(v_pos, 0., 1.);
15 | }
16 |
--------------------------------------------------------------------------------
/examples/shaderfun/src/main.rs:
--------------------------------------------------------------------------------
1 | use anyhow::Result;
2 | use limelight::{DrawMode, DummyBuffer, Program, Renderer, Uniform};
3 | use limelight_yew::{LimelightComponent, LimelightController, ShouldRequestAnimationFrame};
4 |
5 | struct Model {
6 | program: Program<(), ()>,
7 | time_uniform: Uniform,
8 | pos_uniform: Uniform<[f32; 2]>,
9 | }
10 |
11 | impl Default for Model {
12 | fn default() -> Model {
13 | let time_uniform = Uniform::new(0.0);
14 | let pos_uniform = Uniform::new([0.0, 0.0]);
15 |
16 | let program = Program::new(
17 | include_str!("../shaders/shader.vert"),
18 | include_str!("../shaders/shader.frag"),
19 | DrawMode::Triangles,
20 | )
21 | .with_uniform("u_time", time_uniform.clone())
22 | .with_uniform("u_pos", pos_uniform.clone());
23 |
24 | Self {
25 | program,
26 | time_uniform,
27 | pos_uniform,
28 | }
29 | }
30 | }
31 |
32 | impl LimelightController for Model {
33 | fn draw(&mut self, renderer: &mut Renderer, ts: f64) -> Result {
34 | let ts = ts / 1000.;
35 | self.time_uniform.set_value(ts as _);
36 |
37 | renderer
38 | .render(&mut self.program, &DummyBuffer::new(3))
39 | .unwrap();
40 |
41 | Ok(true)
42 | }
43 |
44 | fn handle_mousemove(&mut self, x: f32, y: f32) -> ShouldRequestAnimationFrame {
45 | self.pos_uniform.set_value([x, y]);
46 |
47 | true
48 | }
49 | }
50 |
51 | fn main() {
52 | console_error_panic_hook::set_once();
53 | yew::start_app::>();
54 | }
55 |
--------------------------------------------------------------------------------
/examples/snowflake/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "limelight-example-snowflake"
3 | version = "0.1.0"
4 | edition = "2021"
5 |
6 | [dependencies]
7 | console_error_panic_hook = "0.1.6"
8 | wasm-bindgen = "0.2.74"
9 | web-sys = { version="0.3.55", features = [
10 | 'HtmlCanvasElement',
11 | 'Window',
12 | 'Document',
13 | ] }
14 | limelight = { path="../../limelight" }
15 | limelight-primitives = {path="../../primitives"}
16 | limelight-transform = {path="../../transform"}
17 | palette = { version = "0.6.0", features = ["named"] }
18 | wasm-logger = "0.2.0"
19 | limelight-yew = {path="../../yew"}
20 | yew = "0.19.3"
21 | anyhow = "1.0.51"
22 | nalgebra = "0.30.1"
23 | rand = {version="0.8.4", default_features=false, features=["std_rng"]}
24 | getrandom = { version = "0.2.3", features = ["js"] }
25 |
--------------------------------------------------------------------------------
/examples/snowflake/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | limelight demos
7 |
8 |
9 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/examples/snowflake/src/main.rs:
--------------------------------------------------------------------------------
1 | use std::cell::RefCell;
2 | use std::rc::Rc;
3 |
4 | use anyhow::Result;
5 | use limelight::{renderer::Drawable, Renderer};
6 | use limelight_primitives::{Line3D, Line3DLayer, Color};
7 | use limelight_yew::{
8 | LimelightComponent, LimelightComponentProps, LimelightController, ShouldRequestAnimationFrame,
9 | };
10 | use palette::{Gradient, Hsl, Srgb};
11 | use rand::prelude::ThreadRng;
12 | use rand::{Rng, thread_rng};
13 | use std::f32::consts::TAU;
14 | use palette::chromatic_adaptation::AdaptInto;
15 | use rand::seq::SliceRandom;
16 |
17 | const MAX_LINES: usize = 500;
18 |
19 | struct Snowflake {
20 | snowflake: Line3DLayer,
21 | lines: Vec,
22 | rng: ThreadRng,
23 | colors: Vec,
24 | }
25 |
26 | impl LimelightController for Snowflake {
27 | fn draw(&mut self, renderer: &mut Renderer, _ts: f64) -> Result {
28 | if self.lines.len() < MAX_LINES {
29 | let start_on_line = self.rng.gen_range(0. .. 0.8);
30 | let rot = self.rng.gen_range(0. ..TAU);
31 | let z_delta = self.rng.gen_range(-0.1 .. 0.1);
32 | let size = self.rng.gen_range(0. .. 0.02f32).sqrt();
33 | let color = self.colors.choose(&mut self.rng).unwrap();
34 | let cc = Color(*limelight::bytemuck::from_bytes(&[
35 | (color.red * 255.) as u8,
36 | (color.green * 255.) as u8,
37 | (color.blue * 255.) as u8,
38 | 0xff,
39 | ]));
40 |
41 | for i in 0..6 {
42 | for j in [-1., 1.] {
43 | let theta = TAU * (i as f32 / 6.);
44 | let xx = start_on_line * theta.cos();
45 | let yy = start_on_line * theta.sin();
46 |
47 | let theta2 = theta + rot * j;
48 | let xx_delta = theta2.cos() * size;
49 | let yy_delta = theta2.sin() * size;
50 |
51 | self.lines.push(Line3D {
52 | start: [xx, yy, 0.],
53 | end: [xx + xx_delta, yy + yy_delta, z_delta],
54 | width: 0.003,
55 | color: cc,
56 | });
57 | }
58 | }
59 |
60 | self.snowflake.buffer().set_data(self.lines.clone());
61 | }
62 |
63 | self.snowflake.draw(renderer)?;
64 |
65 | Ok(true)
66 | }
67 |
68 | fn handle_mousemove(&mut self, x: f32, y: f32) -> ShouldRequestAnimationFrame {
69 | let rot = nalgebra::Rotation3::from_euler_angles(-y / 2., x / 2., 0.);
70 |
71 | self.snowflake.transform().set_value(rot.to_homogeneous().into());
72 |
73 | true
74 | }
75 | }
76 |
77 | impl Snowflake {
78 | fn new() -> Rc> {
79 | let mut rng = thread_rng();
80 | let c1 = Hsl::new(rng.gen_range(0. .. 360.), rng.gen_range(0.6 .. 1.), rng.gen_range(0.6 .. 0.8));
81 | let c2 = Hsl::new(rng.gen_range(0. .. 360.), rng.gen_range(0. .. 1.), rng.gen_range(0.6 .. 1.));
82 | let c3 = Hsl::new(rng.gen_range(0. .. 360.), rng.gen_range(0.6 .. 1.), rng.gen_range(0.6 .. 0.7));
83 | let colors: Vec = Gradient::new(vec![c1, c2, c3]).take(10).map(|d| d.adapt_into()).collect();
84 |
85 | Rc::new(RefCell::new(Snowflake {
86 | snowflake: Line3DLayer::default(),
87 | lines: Vec::new(),
88 | rng,
89 | colors,
90 | }))
91 | }
92 | }
93 |
94 | pub fn main() {
95 | console_error_panic_hook::set_once();
96 | yew::start_app_with_props::>(LimelightComponentProps {
97 | controller: Snowflake::new(),
98 | height: 500,
99 | width: 500,
100 | });
101 | }
102 |
--------------------------------------------------------------------------------
/examples/zoom-pan/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "limelight-example-zoom-pan"
3 | version = "0.1.0"
4 | edition = "2021"
5 |
6 | [dependencies]
7 | console_error_panic_hook = "0.1.6"
8 | wasm-bindgen = "0.2.78"
9 | web-sys = { version="0.3.55", features = [
10 | 'HtmlCanvasElement',
11 | 'Window',
12 | 'Document',
13 | ] }
14 | limelight = { path="../../limelight" }
15 | limelight-transform = { path="../../transform" }
16 | bytemuck = "1.7.2"
17 | wasm-logger = "0.2.0"
18 | limelight-yew = {path="../../yew"}
19 | anyhow = "1.0.51"
20 | yew = "0.19.3"
21 |
--------------------------------------------------------------------------------
/examples/zoom-pan/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | limelight demo
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/examples/zoom-pan/shaders/shader.frag:
--------------------------------------------------------------------------------
1 | #version 300 es
2 |
3 | precision highp float;
4 |
5 | out vec4 color;
6 |
7 | void main() {
8 | color = vec4(0., 0., 0., 1.0);
9 | }
--------------------------------------------------------------------------------
/examples/zoom-pan/shaders/shader.vert:
--------------------------------------------------------------------------------
1 | #version 300 es
2 |
3 | in vec2 position;
4 | uniform mat4 u_transform;
5 |
6 | void main() {
7 | gl_Position = vec4(position * 0.5, 0., 1.) * u_transform;
8 | }
--------------------------------------------------------------------------------
/examples/zoom-pan/src/main.rs:
--------------------------------------------------------------------------------
1 | use anyhow::Result;
2 | use limelight::{attribute, Buffer, BufferUsageHint, DrawMode, Program, Renderer};
3 | use limelight_transform::TransformUniform;
4 | use limelight_yew::{
5 | LimelightComponent, LimelightController, ShouldCancelEvent, ShouldRequestAnimationFrame,
6 | };
7 |
8 | struct ZoomPan {
9 | program: Program,
10 | buffer: Buffer,
11 | transform: TransformUniform,
12 | }
13 |
14 | impl Default for ZoomPan {
15 | fn default() -> Self {
16 | let theta1 = 0.;
17 | let theta2 = theta1 + (std::f32::consts::TAU / 3.);
18 | let theta3 = theta2 + (std::f32::consts::TAU / 3.);
19 |
20 | let data = vec![
21 | VertexDescription::new(theta1.cos(), theta1.sin()),
22 | VertexDescription::new(theta2.cos(), theta2.sin()),
23 | VertexDescription::new(theta3.cos(), theta3.sin()),
24 | ];
25 |
26 | let buffer = Buffer::new(data, BufferUsageHint::DynamicDraw);
27 | let transform = TransformUniform::new();
28 |
29 | let program = Program::new(
30 | include_str!("../shaders/shader.vert"),
31 | include_str!("../shaders/shader.frag"),
32 | DrawMode::Triangles,
33 | )
34 | .with_uniform("u_transform", transform.uniform());
35 |
36 | ZoomPan {
37 | buffer,
38 | program,
39 | transform,
40 | }
41 | }
42 | }
43 |
44 | impl LimelightController for ZoomPan {
45 | fn draw(
46 | &mut self,
47 | renderer: &mut Renderer,
48 | _ts: f64,
49 | ) -> Result {
50 | renderer.render(&mut self.program, &self.buffer)?;
51 | Ok(false)
52 | }
53 |
54 | fn handle_drag(&mut self, x: f32, y: f32) -> limelight_yew::ShouldRequestAnimationFrame {
55 | self.transform.pan((x, y));
56 | true
57 | }
58 |
59 | fn handle_scroll(
60 | &mut self,
61 | x_amount: f32,
62 | y_amount: f32,
63 | _x_position: f32,
64 | _y_position: f32,
65 | ) -> (ShouldRequestAnimationFrame, ShouldCancelEvent) {
66 | self.transform.pan((x_amount, y_amount));
67 | (true, true)
68 | }
69 |
70 | fn handle_pinch(
71 | &mut self,
72 | amount: f32,
73 | x: f32,
74 | y: f32,
75 | ) -> (ShouldRequestAnimationFrame, ShouldCancelEvent) {
76 | let scale_factor = 1. + amount / 100.;
77 | self.transform.scale(scale_factor, (x, y));
78 | (true, true)
79 | }
80 | }
81 |
82 | #[attribute]
83 | struct VertexDescription {
84 | position: [f32; 2],
85 | }
86 |
87 | impl VertexDescription {
88 | pub fn new(x: f32, y: f32) -> Self {
89 | VertexDescription { position: [x, y] }
90 | }
91 | }
92 |
93 | fn main() {
94 | console_error_panic_hook::set_once();
95 | wasm_logger::init(wasm_logger::Config::default());
96 | yew::start_app::>();
97 | }
98 |
--------------------------------------------------------------------------------
/limelight/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "limelight"
3 | version = "0.1.3"
4 | edition = "2021"
5 | repository = "https://github.com/drifting-in-space/limelight"
6 | description = "WebGL2 wrapper with a focus on making high-performance graphics code easier to write and maintain"
7 | license = "MIT"
8 | readme = "README.md"
9 | keywords = ["webgl"]
10 |
11 | [dependencies]
12 | bytemuck = { version = "1.7.2", features = ["derive"] }
13 | js-sys = "0.3.55"
14 | wasm-bindgen = "0.2.78"
15 | web-sys = { version="0.3.55", features = [
16 | 'WebGlActiveInfo',
17 | 'WebGlBuffer',
18 | 'WebGl2RenderingContext',
19 | 'WebGlVertexArrayObject',
20 | 'WebGlUniformLocation',
21 | 'WebGlProgram',
22 | 'WebGlShader',
23 | 'WebGlTexture',
24 | ] }
25 | limelight-derive = { version="0.1.1", path="./limelight-derive" }
26 | anyhow = "1.0.48"
27 | slice-of-array = "0.3.1"
28 | log = "0.4.14"
29 |
--------------------------------------------------------------------------------
/limelight/README.md:
--------------------------------------------------------------------------------
1 | # `limelight`
2 |
3 | [](https://github.com/drifting-in-space/limelight)
4 | [](https://crates.io/crates/limelight)
5 | [](https://docs.rs/limelight/)
6 | [](https://github.com/drifting-in-space/limelight/actions/workflows/rust.yml)
7 |
8 | This crate provides two layers of abstraction on top of WebGL. The first is the **shadow GPU** layer,
9 | which abstracts away the statefulness of WebGL to provide a more function- and data-oriented interface.
10 | The second layer is the `Renderer` API, which provides a typed interface on top of the (untyped)
11 | shadow GPU.
12 |
13 | If you don't want to use custom shaders and just want to draw lots of shapes, you can ignore the
14 | examples below and look at the examples in the [primitives](https://github.com/drifting-in-space/limelight/tree/main/primitives) crate ([docs](https://docs.rs/limelight-primitives/0.1.3/limelight_primitives/)).
15 |
16 | ## Getting started
17 |
18 | See the [examples](https://github.com/drifting-in-space/limelight/tree/main/examples) directory for
19 | runnable examples.
20 |
21 | This tutorial assumes you're familiar with basic WebGL terminology, like vertex and fragment shaders,
22 | uniforms, and buffers.
23 |
24 | ### Drawing a triangle
25 |
26 | ([full code](https://github.com/drifting-in-space/limelight/tree/main/examples/01-triangle),
27 | [demo](https://drifting-in-space.github.io/limelight/01-triangle/))
28 |
29 | [](https://drifting-in-space.github.io/limelight/01-triangle/)
30 |
31 | This example demonstrates the three main steps to produce an image with limelight:
32 | 1. Create a `Program` object. A `Program` in limelight contains the vertex and fragment shader pair
33 | (a `WebGLProgram` object), and also contains program-specific state.
34 | 2. Create a `Renderer`. After we have initialized all of our programs with the GL context, we transfer ownership
35 | of the GL context into a `Renderer`, which then becomes responsible for all GL-side state transitions.
36 | 3. We call `renderer.render(program, buffer)`, which causes the triangle to be drawn. We have not attached a
37 | vertex attribute buffer in this example, and instead use the vertex shader to generate the vertices. We
38 | still need to tell WebGL *how many* vertices (3) we want to generate, so we pass in a `DummyBuffer` of size `3`.
39 |
40 | ```rust
41 | use web_sys::WebGl2RenderingContext;
42 | use limelight::{Program, Renderer, DummyBuffer, DrawMode};
43 |
44 | fn render_triangle(gl: WebGl2RenderingContext) {
45 | // limelight doesn't touch the DOM at all. Use your preferred
46 | // framework to create a canvas and create a WebGL2 context
47 | // from it.
48 |
49 | // Create a shader program by passing in GLSL code as strings for
50 | // the fragment and vertex shaders.
51 | let mut program = Program::new(
52 | include_str!("../../examples/01-triangle/shaders/shader.vert"),
53 | include_str!("../../examples/01-triangle/shaders/shader.frag"),
54 | DrawMode::Triangles
55 | );
56 |
57 | // Create a renderer. The renderer becomes the owner of the
58 | // WebGl2RenderingContext, to ensure that its internal representation
59 | // of the GPU state is always accureate.
60 | let mut renderer = Renderer::new(gl);
61 |
62 | // Run the program, rendering the results to the screen. We are
63 | // not passing any vertex attribute data, so we use a `DummyBuffer`
64 | // which renders three vertices: one for each corner of a triangle.
65 | renderer.render(&mut program, &DummyBuffer::new(3)).unwrap();
66 | }
67 | ```
68 |
69 | ### Using buffers
70 |
71 | ([full code](https://github.com/drifting-in-space/limelight/tree/main/examples/02-buffer),
72 | [demo](https://drifting-in-space.github.io/limelight/02-buffer/))
73 |
74 | [](https://drifting-in-space.github.io/limelight/02-buffer/)
75 |
76 | Buffers enable arbitrary vertex attribute data to be passed into the shaders. Limelight provides a
77 | procedural macro (`attribute`) for mapping from a Rust-side `struct` to a GPU-side set of
78 | vertex attributes. To use this macro, your crate will also have to depend on [`bytemuck`](https://docs.rs/bytemuck/latest/bytemuck/) and its `derive` feature.
79 |
80 | ```rust
81 | use web_sys::WebGl2RenderingContext;
82 | use limelight::{Program, Renderer, Buffer, DrawMode, BufferUsageHint, attribute};
83 |
84 | // This attribute macro derives a number of traits, including `VertexAttribute`, which
85 | // is required for a type to be used in an `Buffer`.
86 | #[attribute]
87 | struct VertexDescription {
88 | position: [f32; 2], // field names are mapped to variables in the shader.
89 | }
90 |
91 | impl VertexDescription {
92 | pub fn new(x: f32, y: f32) -> Self {
93 | VertexDescription { position: [x, y] }
94 | }
95 | }
96 |
97 | fn render_triangles(gl: WebGl2RenderingContext) {
98 | let mut program = Program::new(
99 | include_str!("../../examples/02-buffer/shaders/shader.vert"),
100 | include_str!("../../examples/02-buffer/shaders/shader.frag"),
101 | DrawMode::Triangles
102 | );
103 |
104 | let mut renderer = Renderer::new(gl);
105 |
106 | let data = vec![
107 | // Lower-left triangle.
108 | VertexDescription::new(-0.1, -0.1),
109 | VertexDescription::new(-0.5, -0.1),
110 | VertexDescription::new(-0.5, -0.5),
111 | // Upper-right triangle.
112 | VertexDescription::new(0.1, 0.1),
113 | VertexDescription::new(0.5, 0.1),
114 | VertexDescription::new(0.5, 0.5),
115 | ];
116 |
117 | // Declare a buffer.
118 | let mut buffer: Buffer =
119 | Buffer::new(data, BufferUsageHint::StaticDraw);
120 |
121 | renderer.render(&mut program, &buffer).unwrap();
122 | }
123 | ```
124 |
125 | ### Uniforms
126 |
127 | ([full code](https://github.com/drifting-in-space/limelight/tree/main/examples/03-uniform),
128 | [demo](https://drifting-in-space.github.io/limelight/03-uniform/))
129 |
130 | [](https://drifting-in-space.github.io/limelight/03-uniform/)
131 |
132 | Uniforms are values that can be used in both shader and fragment programs. They can vary
133 | between `render` calls, but for a given render call each uniform has a constant value
134 | across all vertices and fragments.
135 |
136 | ```rust
137 | use limelight::{DrawMode, DummyBuffer, Program, Renderer, Uniform};
138 | use web_sys::WebGl2RenderingContext;
139 |
140 | fn render_triangles_with_uniform(gl: WebGl2RenderingContext) {
141 | // This will correspond to "uniform float u_rotate" in the vertex shader.
142 | let rotate_uniform = Uniform::new(std::f32::consts::PI / 3.4);
143 |
144 | // This will correspond to "uniform vec2 u_scale" in the vertex shader.
145 | let scale_uniform = Uniform::new([0.5, 0.8]);
146 |
147 | // This will correspond to "uniform vec3 u_color" in the fragment shader.
148 | let color_uniform = Uniform::new([0.9, 0.2, 0.3]);
149 |
150 | let mut program = Program::new(
151 | include_str!("../../examples/03-uniform/shaders/shader.vert"),
152 | include_str!("../../examples/03-uniform/shaders/shader.frag"),
153 | DrawMode::Triangles,
154 | )
155 | // We need to map the uniforms when we create the program.
156 | // The GPU-side types are automatically inferred from the Rust types.
157 | .with_uniform("u_rotate", rotate_uniform)
158 | .with_uniform("u_scale", scale_uniform)
159 | .with_uniform("u_color", color_uniform);
160 |
161 | let mut renderer = Renderer::new(gl);
162 | renderer.render(&mut program, &DummyBuffer::new(3)).unwrap();
163 | }
164 | ```
165 |
166 | ### Animation
167 |
168 | ([full code](https://github.com/drifting-in-space/limelight/tree/main/examples/04-animate),
169 | [demo](https://drifting-in-space.github.io/limelight/04-animate/))
170 |
171 | The previous examples have rendered static images, so we haven't had a need to separate code
172 | that sets up the initial data structures from code that updates GPU-side data and triggers an
173 | animation. In this example, we separate the code into a `new()` method that is called once,
174 | and a `render` method that is called on every frame.
175 |
176 | limelight is not a framework, and in order to integrate with other frameworks, it is not opinionated
177 | as to how you structure your code. This example shows one way you might choose to structure code for
178 | a simple animation (see the [full code](https://github.com/drifting-in-space/limelight/tree/main/examples/04-animate)
179 | to see how it can be integrated with the [Yew](https://yew.rs/) web framework).
180 |
181 | `buffer.set_data` and `uniform.set_data` are *lazy*: they do not result in any GPU activity until
182 | the next time the buffer is used in a render call. (See [WebGL Insights](http://www.webglinsights.com/)
183 | section 14.2, *Deferring until the Draw Cycle*.) If a buffer or uniform is unchanged between render
184 | calls, it is not re-written to the GPU.
185 |
186 | ```rust
187 | use limelight::{Buffer, BufferUsageHint, DrawMode, Program, Renderer, Uniform, attribute};
188 | use web_sys::WebGl2RenderingContext;
189 |
190 | struct Animation {
191 | program: Program,
192 | buffer: Buffer,
193 | uniform: Uniform<[f32; 3]>,
194 | }
195 |
196 | impl Animation {
197 | pub fn new(gl: &WebGl2RenderingContext) -> Self {
198 | let buffer = Buffer::new(vec![], BufferUsageHint::DynamicDraw);
199 | let uniform = Uniform::new([0., 0., 0.]);
200 |
201 | let program = Program::new(
202 | include_str!("../../examples/04-animate/shaders/shader.vert"),
203 | include_str!("../../examples/04-animate/shaders/shader.frag"),
204 | DrawMode::Triangles,
205 | )
206 | // Note that we clone uniform, so that we can retain a handle to it.
207 | // Cloning a `Uniform` results in a reference-counted pointer to the
208 | // same uniform.
209 | .with_uniform("u_color", uniform.clone());
210 |
211 | Animation {
212 | buffer,
213 | program,
214 | uniform
215 | }
216 | }
217 |
218 | pub fn render(&mut self, time: f64, renderer: &mut Renderer) {
219 | let theta1 = time as f32 / 1000.;
220 | let theta2 = theta1 + (std::f32::consts::TAU / 3.);
221 | let theta3 = theta2 + (std::f32::consts::TAU / 3.);
222 |
223 | self.buffer.set_data(vec![
224 | VertexDescription::new(theta1.cos(), theta1.sin()),
225 | VertexDescription::new(theta2.cos(), theta2.sin()),
226 | VertexDescription::new(theta3.cos(), theta3.sin()),
227 | ]);
228 |
229 | let r = (time as f32 / 3000.).sin() / 2. + 0.5;
230 | let g = (time as f32 / 5000.).sin() / 2. + 0.5;
231 | let b = (time as f32 / 7000.).sin() / 2. + 0.5;
232 |
233 | self.uniform.set_value([r, g, b]);
234 |
235 | renderer.render(&mut self.program, &self.buffer).unwrap();
236 | }
237 | }
238 |
239 | #[attribute]
240 | struct VertexDescription {
241 | position: [f32; 2],
242 | }
243 |
244 | impl VertexDescription {
245 | pub fn new(x: f32, y: f32) -> Self {
246 | VertexDescription { position: [x, y] }
247 | }
248 | }
249 | ```
250 |
--------------------------------------------------------------------------------
/limelight/limelight-derive/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "limelight-derive"
3 | version = "0.1.1"
4 | edition = "2021"
5 | repository = "https://github.com/drifting-in-space/limelight"
6 | description = "Derive macro for limelight"
7 | license = "MIT"
8 | readme = "README.md"
9 |
10 | [lib]
11 | proc-macro = true
12 |
13 | [dependencies]
14 | inflections = "1.1.1"
15 | proc-macro2 = "1.0.32"
16 | quote = "1.0.10"
17 | syn = { version = "1.0.81", features = ["full"] }
18 |
--------------------------------------------------------------------------------
/limelight/limelight-derive/README.md:
--------------------------------------------------------------------------------
1 | This crate is just a derive macro and should not be used directly.
2 |
3 | See [limelight](https://github.com/drifting-in-space/limelight).
--------------------------------------------------------------------------------
/limelight/limelight-derive/src/lib.rs:
--------------------------------------------------------------------------------
1 | extern crate proc_macro;
2 |
3 | use proc_macro2::TokenStream;
4 | use quote::quote;
5 | use syn::ItemStruct;
6 |
7 | #[proc_macro_attribute]
8 | pub fn attribute(
9 | _attr: proc_macro::TokenStream,
10 | item: proc_macro::TokenStream,
11 | ) -> proc_macro::TokenStream {
12 | let item: TokenStream = item.into();
13 |
14 | let r = quote! {
15 | #[repr(C)]
16 | #[derive(Clone, Copy, limelight::Attribute, limelight::bytemuck::Pod, limelight::bytemuck::Zeroable)]
17 | #item
18 | };
19 |
20 | r.into()
21 | }
22 |
23 | #[proc_macro_derive(Attribute)]
24 | pub fn vertex_attribute_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
25 | impl_vertex_attribute_derive(input.into()).into()
26 | }
27 |
28 | fn bind(field: &syn::Field) -> TokenStream {
29 | let name = field.ident.as_ref().unwrap().to_string();
30 | let kind = &field.ty;
31 |
32 | quote! {
33 | limelight::AttributeBinding {
34 | variable_name: (#name).to_string(),
35 | kind: <#kind as limelight::AsSizedDataType>::as_sized_data_type(),
36 | }
37 | }
38 | }
39 |
40 | fn impl_vertex_attribute_derive(input: TokenStream) -> TokenStream {
41 | let ast: ItemStruct = syn::parse2(input).expect("Should decorate a struct.");
42 |
43 | let name = &ast.ident;
44 |
45 | let bindings: Vec = match &ast.fields {
46 | syn::Fields::Named(fields) => fields.named.iter().map(bind).collect(),
47 | _ => panic!("Only structs with named fields can derive StateMachine currently."),
48 | };
49 |
50 | quote! {
51 | impl limelight::Attribute for #name {
52 | fn describe() -> Vec {
53 | vec![
54 | #(#bindings),*
55 | ]
56 | }
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/limelight/src/attribute.rs:
--------------------------------------------------------------------------------
1 | use crate::webgl::types::{DataType, SizedDataType};
2 |
3 | #[derive(Debug, PartialEq)]
4 | pub struct AttributeBinding {
5 | pub variable_name: String,
6 | pub kind: SizedDataType,
7 | }
8 |
9 | impl AttributeBinding {
10 | pub fn new(name: &str, data_type: DataType, size: i32) -> Self {
11 | AttributeBinding {
12 | variable_name: name.to_string(),
13 | kind: SizedDataType::new(data_type, size),
14 | }
15 | }
16 | }
17 |
18 | pub trait Attribute: bytemuck::Pod + bytemuck::Zeroable {
19 | fn describe() -> Vec;
20 | }
21 |
22 | impl Attribute for () {
23 | fn describe() -> Vec {
24 | Vec::new()
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/limelight/src/buffer.rs:
--------------------------------------------------------------------------------
1 | use crate::{shadow_gpu::BufferHandle, webgl::buffer::BufferUsageHint, Attribute};
2 | use std::marker::PhantomData;
3 |
4 | #[allow(clippy::len_without_is_empty)]
5 | pub trait BufferLike {
6 | fn get_buffer(&self) -> Option;
7 |
8 | fn len(&self) -> usize;
9 | }
10 |
11 | #[derive(Clone)]
12 | pub struct Buffer {
13 | handle: BufferHandle,
14 | _ph: PhantomData,
15 | }
16 |
17 | impl Buffer {
18 | pub fn new(data: Vec, usage_hint: BufferUsageHint) -> Self {
19 | let handle = BufferHandle::new(usage_hint);
20 | handle.set_data(data);
21 |
22 | Buffer {
23 | handle,
24 | _ph: PhantomData::default(),
25 | }
26 | }
27 |
28 | pub fn new_empty(usage_hint: BufferUsageHint) -> Self {
29 | Self::new(Vec::new(), usage_hint)
30 | }
31 |
32 | pub fn set_data(&self, data: Vec) {
33 | self.handle.set_data(data);
34 | }
35 | }
36 |
37 | impl BufferLike for Buffer {
38 | fn get_buffer(&self) -> Option {
39 | Some(self.handle.clone())
40 | }
41 |
42 | fn len(&self) -> usize {
43 | self.handle.len()
44 | }
45 | }
46 |
47 | pub struct DummyBuffer {
48 | size: usize,
49 | }
50 |
51 | impl DummyBuffer {
52 | pub fn new(size: usize) -> Self {
53 | DummyBuffer { size }
54 | }
55 | }
56 |
57 | impl BufferLike<()> for DummyBuffer {
58 | fn len(&self) -> usize {
59 | self.size
60 | }
61 |
62 | fn get_buffer(&self) -> Option {
63 | None
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/limelight/src/draw_modes.rs:
--------------------------------------------------------------------------------
1 | #[repr(u32)]
2 | #[derive(Clone, Copy, Debug)]
3 | pub enum DrawMode {
4 | Points = 0x0000,
5 | Lines = 0x0001,
6 | LineLoop = 0x0002,
7 | LineStrip = 0x0003,
8 | Triangles = 0x0004,
9 | TriangleStrip = 0x0005,
10 | TriangleFan = 0x0006,
11 | }
12 |
--------------------------------------------------------------------------------
/limelight/src/lib.rs:
--------------------------------------------------------------------------------
1 | #![doc = include_str!("../README.md")]
2 |
3 | mod attribute;
4 | pub mod buffer;
5 | pub mod draw_modes;
6 | pub mod program;
7 | pub mod renderer;
8 | pub mod shadow_gpu;
9 | pub mod state;
10 | pub mod uniform;
11 | pub mod webgl;
12 |
13 | pub use bytemuck;
14 | pub use limelight_derive::{attribute, Attribute};
15 |
16 | pub use attribute::{Attribute, AttributeBinding};
17 | pub use buffer::{Buffer, DummyBuffer};
18 | pub use draw_modes::DrawMode;
19 | pub use program::Program;
20 | pub use renderer::Renderer;
21 | pub use uniform::Uniform;
22 | pub use webgl::buffer::{BufferBindPoint, BufferUsageHint};
23 | pub use webgl::types::AsSizedDataType;
24 |
25 | // #[allow(unused)]
26 | // macro_rules! console_log {
27 | // ($($x: expr), +) => (
28 | // web_sys::console::log_1(&wasm_bindgen::JsValue::from(
29 | // format!($($x),+)));
30 | // )
31 | // }
32 |
33 | // #[allow(unused)]
34 | // pub(crate) use console_log;
35 |
--------------------------------------------------------------------------------
/limelight/src/program.rs:
--------------------------------------------------------------------------------
1 | use anyhow::Result;
2 | use std::{
3 | collections::{hash_map::Entry, HashMap},
4 | marker::PhantomData,
5 | };
6 |
7 | use crate::{
8 | shadow_gpu::{AttributeInfo, ProgramHandle, ShadowGpu, UniformHandle, UniformValueType},
9 | state::StateDescriptor,
10 | uniform::GenericUniform,
11 | Attribute, DrawMode, Uniform,
12 | };
13 |
14 | pub trait ProgramLike {
15 | fn get_program(&mut self, gpu: &ShadowGpu) -> Result<&BoundProgram>;
16 |
17 | fn globals(&self) -> StateDescriptor;
18 |
19 | fn draw_mode(&self) -> DrawMode;
20 | }
21 |
22 | pub struct BoundProgram {
23 | handle: ProgramHandle,
24 | pub uniforms: Vec<(UniformHandle, Box)>,
25 | draw_mode: DrawMode,
26 | state: StateDescriptor,
27 | _ph: PhantomData,
28 | _phi: PhantomData,
29 | }
30 |
31 | impl BoundProgram {
32 | pub fn handle(&self) -> ProgramHandle {
33 | self.handle.clone()
34 | }
35 |
36 | pub fn attributes(&self) -> &HashMap {
37 | &self.handle.attributes
38 | }
39 | }
40 |
41 | pub struct UnboundProgram {
42 | fragment_shader_source: String,
43 | vertex_shader_source: String,
44 | uniforms: HashMap>,
45 | draw_mode: DrawMode,
46 | state: StateDescriptor,
47 | _ph: PhantomData,
48 | _phi: PhantomData,
49 | }
50 |
51 | impl UnboundProgram {
52 | pub fn with_uniform(
53 | &mut self,
54 | name: &str,
55 | uniform: Uniform,
56 | ) -> &mut Self {
57 | match self.uniforms.entry(name.to_string()) {
58 | Entry::Occupied(_) => panic!("Tried to set uniform {} more than once.", name),
59 | Entry::Vacant(e) => e.insert(Box::new(uniform)),
60 | };
61 |
62 | self
63 | }
64 |
65 | fn new_dummy() -> Self {
66 | UnboundProgram {
67 | _ph: PhantomData::default(),
68 | _phi: PhantomData::default(),
69 | fragment_shader_source: "".to_string(),
70 | vertex_shader_source: "".to_string(),
71 | uniforms: HashMap::new(),
72 | state: StateDescriptor::default(),
73 | draw_mode: DrawMode::Triangles,
74 | }
75 | }
76 |
77 | pub fn bind(self, gpu: &ShadowGpu) -> Result> {
78 | let vertex_shader = gpu.compile_vertex_shader(&self.vertex_shader_source)?;
79 | let fragment_shader = gpu.compile_fragment_shader(&self.fragment_shader_source)?;
80 | let program = gpu.link_program(&fragment_shader, &vertex_shader)?;
81 |
82 | let mut bound_uniforms = Vec::with_capacity(self.uniforms.len());
83 |
84 | for (name, uniform) in self.uniforms {
85 | let loc = gpu.get_uniform_handle(&program, &name)?;
86 | bound_uniforms.push((loc, uniform));
87 | }
88 |
89 | Ok(BoundProgram {
90 | handle: program,
91 | uniforms: bound_uniforms,
92 | draw_mode: self.draw_mode,
93 | state: self.state,
94 | _ph: PhantomData::default(),
95 | _phi: PhantomData::default(),
96 | })
97 | }
98 | }
99 |
100 | pub enum Program {
101 | Unbound(UnboundProgram),
102 | Bound(BoundProgram),
103 | }
104 |
105 | impl Program {
106 | pub fn new(
107 | vertex_shader_source: &str,
108 | fragment_shader_source: &str,
109 | draw_mode: DrawMode,
110 | ) -> Self {
111 | Program::Unbound(UnboundProgram {
112 | fragment_shader_source: fragment_shader_source.to_string(),
113 | vertex_shader_source: vertex_shader_source.to_string(),
114 | uniforms: HashMap::new(),
115 | draw_mode,
116 | state: StateDescriptor::default(),
117 | _ph: PhantomData::default(),
118 | _phi: PhantomData::default(),
119 | })
120 | }
121 | }
122 |
123 | impl ProgramLike for BoundProgram {
124 | fn get_program(&mut self, _gpu: &ShadowGpu) -> Result<&BoundProgram> {
125 | Ok(self)
126 | }
127 |
128 | fn globals(&self) -> StateDescriptor {
129 | self.state.clone()
130 | }
131 |
132 | fn draw_mode(&self) -> DrawMode {
133 | self.draw_mode
134 | }
135 | }
136 |
137 | impl Program {
138 | pub fn with_uniform(mut self, name: &str, uniform: Uniform) -> Self {
139 | match &mut self {
140 | Program::Bound(_) => {
141 | panic!("Tried calling with_uniform on a program that is already bound.")
142 | }
143 | Program::Unbound(p) => {
144 | p.with_uniform(name, uniform);
145 | }
146 | }
147 |
148 | self
149 | }
150 |
151 | pub fn with_state(mut self, state: StateDescriptor) -> Self {
152 | match &mut self {
153 | Program::Bound(_) => {
154 | panic!("Tried calling with_uniform on a program that is already bound.")
155 | }
156 | Program::Unbound(p) => {
157 | p.state = state;
158 | }
159 | }
160 |
161 | self
162 | }
163 | }
164 |
165 | impl ProgramLike for Program {
166 | fn get_program(&mut self, gpu: &ShadowGpu) -> Result<&BoundProgram> {
167 | match self {
168 | Program::Bound(p) => Ok(p),
169 | Program::Unbound(p) => {
170 | let mut dummy_program = UnboundProgram::new_dummy();
171 | std::mem::swap(&mut dummy_program, p);
172 |
173 | let result = dummy_program.bind(gpu)?;
174 | *self = Program::Bound(result);
175 |
176 | match self {
177 | Program::Bound(result) => Ok(result),
178 | _ => panic!(),
179 | }
180 | }
181 | }
182 | }
183 |
184 | fn globals(&self) -> StateDescriptor {
185 | match self {
186 | Program::Bound(b) => b.state.clone(),
187 | Program::Unbound(b) => b.state.clone(),
188 | }
189 | }
190 |
191 | fn draw_mode(&self) -> DrawMode {
192 | match self {
193 | Program::Bound(p) => p.draw_mode,
194 | Program::Unbound(p) => p.draw_mode,
195 | }
196 | }
197 | }
198 |
--------------------------------------------------------------------------------
/limelight/src/renderer.rs:
--------------------------------------------------------------------------------
1 | use std::collections::{BTreeMap, HashMap};
2 |
3 | use crate::{
4 | attribute::Attribute,
5 | buffer::BufferLike,
6 | program::ProgramLike,
7 | shadow_gpu::{AttributeInfo, BufferBinding, BufferHandle, GpuState, ShadowGpu},
8 | };
9 | use anyhow::Result;
10 | use web_sys::WebGl2RenderingContext;
11 |
12 | pub struct Renderer {
13 | gpu: ShadowGpu,
14 | }
15 |
16 | enum DrawCall {
17 | DrawArrays {
18 | first: usize,
19 | count: usize,
20 | },
21 | DrawArraysInstanced {
22 | first: usize,
23 | count: usize,
24 | instances: usize,
25 | },
26 | }
27 |
28 | struct BufferBindingGroup {
29 | bindings: BTreeMap>,
30 | attributes: HashMap,
31 | }
32 |
33 | impl BufferBindingGroup {
34 | fn new(attributes: HashMap) -> Self {
35 | Self {
36 | attributes,
37 | bindings: BTreeMap::new(),
38 | }
39 | }
40 |
41 | fn add_buffer(&mut self, buffer: &impl BufferLike, divisor: u32) {
42 | if let Some(buffer) = buffer.get_buffer() {
43 | let stride = T::describe().into_iter().map(|d| d.kind.byte_size()).sum();
44 | let mut offset = 0;
45 | let mut bindings = Vec::new();
46 |
47 | for attribute in T::describe() {
48 | if let Some(program_binding) = self.attributes.get(&attribute.variable_name) {
49 | if attribute.kind != program_binding.kind.as_sized_type() {
50 | log::warn!(
51 | "The variable {} has type {:?} as an attribute, \
52 | but {:?} in the program definition.",
53 | attribute.variable_name,
54 | attribute.kind,
55 | program_binding.kind
56 | );
57 | }
58 |
59 | bindings.push(BufferBinding {
60 | kind: attribute.kind,
61 | divisor,
62 | location: program_binding.location as _,
63 | normalized: false,
64 | offset,
65 | stride,
66 | });
67 |
68 | offset += attribute.kind.byte_size();
69 | } else {
70 | log::warn!(
71 | "Attribute has variable {}, which isn't used in the program.",
72 | attribute.variable_name
73 | );
74 | }
75 | }
76 |
77 | if bindings.is_empty() {
78 | log::warn!("No attributes in the buffer overlapped with the program.");
79 | } else {
80 | self.bindings.insert(buffer, bindings);
81 | }
82 | }
83 | }
84 | }
85 |
86 | impl Renderer {
87 | pub fn new(gl: WebGl2RenderingContext) -> Self {
88 | let gpu = ShadowGpu::new(gl);
89 | Renderer { gpu }
90 | }
91 |
92 | fn render_impl(
93 | &mut self,
94 | draw_call: DrawCall,
95 | program: &mut impl ProgramLike,
96 |
97 | buffers: BTreeMap>,
98 | ) -> Result<()> {
99 | let bound_program = program.get_program(&self.gpu)?;
100 |
101 | let mut uniforms = HashMap::new();
102 | for (uniform_handle, uniform) in &bound_program.uniforms {
103 | uniforms.insert(uniform_handle.clone(), uniform.get_value());
104 | }
105 |
106 | let state: GpuState = GpuState {
107 | program: Some(bound_program.handle()),
108 | buffers,
109 | uniforms,
110 | globals: program.globals(),
111 | };
112 |
113 | match draw_call {
114 | DrawCall::DrawArrays { count, first } => {
115 | self.gpu
116 | .draw_arrays(&state, program.draw_mode(), first as _, count as _)?
117 | }
118 | DrawCall::DrawArraysInstanced {
119 | first,
120 | count,
121 | instances,
122 | } => self.gpu.draw_arrays_instanced(
123 | &state,
124 | program.draw_mode(),
125 | first as _,
126 | count as _,
127 | instances as _,
128 | )?,
129 | }
130 |
131 | Ok(())
132 | }
133 |
134 | pub fn render(
135 | &mut self,
136 | program: &mut impl ProgramLike,
137 | vertex_buffer: &impl BufferLike,
138 | ) -> Result<()> {
139 | let bound_program = program.get_program(&self.gpu)?;
140 | let program_attributes = bound_program.attributes();
141 |
142 | let mut bg = BufferBindingGroup::new(program_attributes.clone());
143 | bg.add_buffer(vertex_buffer, 0);
144 |
145 | self.render_impl(
146 | DrawCall::DrawArrays {
147 | first: 0,
148 | count: vertex_buffer.len(),
149 | },
150 | program,
151 | bg.bindings,
152 | )
153 | }
154 |
155 | pub fn render_instanced(
156 | &mut self,
157 | program: &mut impl ProgramLike,
158 | vertex_buffer: &impl BufferLike,
159 | instance_buffer: &impl BufferLike,
160 | ) -> Result<()> {
161 | let bound_program = program.get_program(&self.gpu)?;
162 | let program_attributes = bound_program.attributes();
163 |
164 | let mut bg = BufferBindingGroup::new(program_attributes.clone());
165 | bg.add_buffer(vertex_buffer, 0);
166 | bg.add_buffer(instance_buffer, 1);
167 |
168 | self.render_impl(
169 | DrawCall::DrawArraysInstanced {
170 | first: 0,
171 | count: vertex_buffer.len(),
172 | instances: instance_buffer.len(),
173 | },
174 | program,
175 | bg.bindings,
176 | )
177 | }
178 | }
179 |
180 | pub trait Drawable {
181 | fn draw(&mut self, renderer: &mut Renderer) -> Result<()>;
182 | }
183 |
--------------------------------------------------------------------------------
/limelight/src/shadow_gpu/buffer.rs:
--------------------------------------------------------------------------------
1 | use crate::webgl::buffer::{BufferBindPoint, BufferUsageHint};
2 | use anyhow::{anyhow, Result};
3 | use bytemuck::Pod;
4 | use std::{cell::RefCell, hash::Hash, rc::Rc};
5 | use web_sys::{WebGl2RenderingContext, WebGlBuffer};
6 |
7 | pub enum BindResult {
8 | BoundExisting,
9 | BoundNew,
10 | }
11 |
12 | struct BufferGlObjects {
13 | buffer: WebGlBuffer,
14 | capacity: usize,
15 | }
16 |
17 | trait AsBytes {
18 | fn as_bytes(&self) -> &[u8];
19 |
20 | fn byte_len(&self) -> usize;
21 | }
22 |
23 | impl AsBytes for Vec {
24 | fn as_bytes(&self) -> &[u8] {
25 | bytemuck::cast_slice(self)
26 | }
27 |
28 | fn byte_len(&self) -> usize {
29 | self.len() * std::mem::size_of::() / std::mem::size_of::()
30 | }
31 | }
32 |
33 | struct DataWithMarker {
34 | data: Box,
35 | length: usize,
36 | dirty: bool,
37 | }
38 |
39 | impl Default for DataWithMarker {
40 | fn default() -> Self {
41 | DataWithMarker {
42 | data: Box::new(Vec::::new()),
43 | length: 0,
44 | dirty: true,
45 | }
46 | }
47 | }
48 |
49 | pub struct BufferHandleInner {
50 | gl_objects: RefCell