├── .gitignore ├── .vscode └── tasks.json ├── Cargo.toml ├── README.md ├── benches └── sprite.rs ├── doc ├── favicon.png ├── logo.png └── spritesheet.png ├── examples ├── 01_empty.rs ├── 02_sprites.rs ├── 03_text.rs ├── 04_matrix.rs ├── 05_tiles.rs ├── 06_program.rs ├── 07_to_texture.rs ├── 08_postprocessor.rs ├── 09_combined.rs ├── 10_multi_window.rs ├── 98_threads.rs ├── demo_blobs.rs ├── demo_glare.rs ├── glium_less.rs ├── glium_more.rs └── res │ ├── effects │ ├── bloom.rs │ ├── blur.fs │ ├── combine.fs │ └── ripple.fs │ ├── glium_utils.rs │ ├── limerick.txt │ ├── sprites │ ├── ball_v2_32x32x18.jpg │ ├── battery_lightmapped_128x128x15x2.png │ ├── sparkles2_64x64x1.png │ └── sparkles_64x64x1.png │ └── tiles │ ├── iso.tmx │ ├── iso_64x128.png │ └── iso_64x128.txt ├── src ├── backends │ ├── glium.rs │ ├── mod.rs │ └── null.rs ├── core │ ├── blendmode │ │ ├── blendmodes.rs │ │ └── mod.rs │ ├── builder │ │ ├── displaybuilder.rs │ │ ├── drawbuilder.rs │ │ ├── fontbuilder.rs │ │ ├── fontquerybuilder.rs │ │ ├── mod.rs │ │ └── texturebuilder.rs │ ├── color.rs │ ├── context.rs │ ├── display.rs │ ├── font.rs │ ├── input.rs │ ├── layer.rs │ ├── math.rs │ ├── mod.rs │ ├── monitor.rs │ ├── postprocessor │ │ ├── basic.rs │ │ ├── bloom.rs │ │ └── mod.rs │ ├── program.rs │ ├── renderer.rs │ ├── rendertarget.rs │ ├── sprite.rs │ ├── texture.rs │ └── uniform.rs ├── lib.rs ├── prelude.rs ├── public.rs └── shader │ ├── default.fs │ ├── postprocess │ ├── blur.fs │ └── combine.fs │ ├── sprite.inc.fs │ ├── sprite.vs │ ├── texture.inc.fs │ └── texture.vs ├── tests └── display.rs └── tools └── spritesheet.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "presentation": { 4 | "reveal": "always", 5 | "panel": "new" 6 | }, 7 | "tasks": [ 8 | { 9 | "label": "cargo build", 10 | "type": "shell", 11 | "command": "cargo", 12 | "args": [ 13 | "build" 14 | ], 15 | "group": "build", 16 | "problemMatcher": "$rustc" 17 | }, 18 | { 19 | "label": "cargo build (release)", 20 | "type": "shell", 21 | "command": "cargo", 22 | "args": [ 23 | "build", 24 | "--release" 25 | ], 26 | "group": "build", 27 | "problemMatcher": "$rustc" 28 | }, 29 | { 30 | "label": "cargo run", 31 | "type": "shell", 32 | "command": "cargo", 33 | "args": [ 34 | "run" 35 | ], 36 | "group": "build", 37 | "problemMatcher": "$rustc" 38 | }, 39 | { 40 | "label": "cargo run (release)", 41 | "type": "shell", 42 | "command": "cargo", 43 | "args": [ 44 | "run", 45 | "--release" 46 | ], 47 | "group": "build", 48 | "problemMatcher": "$rustc" 49 | }, 50 | { 51 | "label": "cargo run example", 52 | "type": "shell", 53 | "command": "cargo", 54 | "args": [ 55 | "run", 56 | "--example", 57 | "${fileBasenameNoExtension}" 58 | ], 59 | "group": "build", 60 | "problemMatcher": "$rustc" 61 | }, 62 | { 63 | "label": "cargo run example (release)", 64 | "type": "shell", 65 | "command": "cargo", 66 | "args": [ 67 | "run", 68 | "--release", 69 | "--example", 70 | "${fileBasenameNoExtension}" 71 | ], 72 | "group": "build", 73 | "problemMatcher": "$rustc" 74 | }, 75 | { 76 | "label": "cargo test", 77 | "type": "shell", 78 | "command": "cargo", 79 | "args": [ 80 | "test" 81 | ], 82 | "group": "test", 83 | "problemMatcher": "$rustc" 84 | }, 85 | { 86 | "label": "cargo clean", 87 | "type": "shell", 88 | "command": "cargo", 89 | "args": [ 90 | "clean" 91 | ], 92 | "problemMatcher": [] 93 | }, 94 | { 95 | "label": "cargo update", 96 | "type": "shell", 97 | "command": "cargo", 98 | "args": [ 99 | "update" 100 | ], 101 | "problemMatcher": [] 102 | }, 103 | { 104 | "label": "cargo rustdoc", 105 | "type": "shell", 106 | "command": "cargo", 107 | "args": [ 108 | "rustdoc", 109 | "--open" 110 | ], 111 | "problemMatcher": [] 112 | }, 113 | ] 114 | } -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "radiant-rs" 3 | version = "0.14.0" 4 | authors = [ "Dennis Möhlmann " ] 5 | description = "Thread-safe Rust sprite rendering engine with a friendly API and custom shader support" 6 | documentation = "https://docs.rs/radiant-rs/" 7 | repository = "https://github.com/sinesc/radiant-rs.git" 8 | license = "MIT" 9 | readme = "README.md" 10 | keywords = [ "sprite", "sprites", "text", "prototyping", "glium" ] 11 | categories = [ "rendering::engine", "game-engines" ] 12 | 13 | [features] 14 | default = [ "backend-glium" ] 15 | backend-glium = [ "glium" ] 16 | backend-null = [ ] 17 | serialize-serde = [ "serde", "serde_derive" ] 18 | 19 | [dependencies] 20 | glium = { version = "0.22", optional = true } 21 | image = "0.19" 22 | regex = "1.0" 23 | lazy_static = "1.0" 24 | rusttype = { version = "0.6", features = [ "gpu_cache" ] } 25 | unicode-normalization = "0.1.5" 26 | font-loader = "0.7" 27 | enum_primitive = "0.1" 28 | avec = "0.1.0" 29 | palette = "0.4" 30 | serde = { version = "1.0", optional = true } 31 | serde_derive = { version = "1.0", optional = true } 32 | 33 | [dev-dependencies] 34 | tiled = "0.4" 35 | glium = "0.22" 36 | radiant-utils = { version = "0.4" } 37 | 38 | [lib] 39 | name = "radiant_rs" 40 | path = "src/lib.rs" 41 | 42 | [[bin]] 43 | doc = false 44 | name = "spritesheet" 45 | path = "tools/spritesheet.rs" 46 | 47 | [[example]] 48 | name = "01_empty" 49 | path = "examples/01_empty.rs" 50 | 51 | [[example]] 52 | name = "02_sprites" 53 | path = "examples/02_sprites.rs" 54 | 55 | [[example]] 56 | name = "03_text" 57 | path = "examples/03_text.rs" 58 | 59 | [[example]] 60 | name = "04_matrix" 61 | path = "examples/04_matrix.rs" 62 | 63 | [[example]] 64 | name = "05_tiles" 65 | path = "examples/05_tiles.rs" 66 | 67 | [[example]] 68 | name = "06_program" 69 | path = "examples/06_program.rs" 70 | 71 | [[example]] 72 | name = "07_to_texture" 73 | path = "examples/07_to_texture.rs" 74 | 75 | [[example]] 76 | name = "08_postprocessor" 77 | path = "examples/08_postprocessor.rs" 78 | 79 | [[example]] 80 | name = "09_combined" 81 | path = "examples/09_combined.rs" 82 | 83 | [[example]] 84 | name = "10_multi_window" 85 | path = "examples/10_multi_window.rs" 86 | 87 | [[example]] 88 | name = "98_threads" 89 | path = "examples/98_threads.rs" 90 | 91 | [[example]] 92 | name = "glium_less" 93 | path = "examples/glium_less.rs" 94 | 95 | [[example]] 96 | name = "glium_more" 97 | path = "examples/glium_more.rs" 98 | 99 | [[example]] 100 | name = "demo_blobs" 101 | path = "examples/demo_blobs.rs" 102 | 103 | [[example]] 104 | name = "demo_glare" 105 | path = "examples/demo_glare.rs" 106 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # radiant-rs 2 | Rust sprite rendering engine with a friendly API, wait-free send+sync drawing targets and custom shader support. 3 | 4 | [Getting started and reference here](https://docs.rs/radiant-rs/). 5 | 6 | To compile the examples, use e.g. `cargo run --release --example demo_glare`. See examples folder for other available examples. 7 | 8 | ## Some screenshots from the examples 9 | 10 | ![examples/demo_glare.rs](https://sinesc.github.io/images/radiant/glare.png "examples/demo_glare.rs") 11 | ![examples/05_tiles.rs](https://sinesc.github.io/images/radiant/tiles2.png "examples/05_tiles.rs") 12 | ![examples/demo_blobs.rs](https://sinesc.github.io/images/radiant/bloom.png "examples/demo_blobs.rs") 13 | ![examples/07_to_texture.rs](https://sinesc.github.io/images/radiant/to_texture.png "examples/07_to_texture.rs") 14 | 15 | ## Installation 16 | 17 | ## Linux 18 | 19 | On Linux the font loader depends on servo-fontconfig-sys which may require manual installation of cmake, freetype6, expat and fontconfig e.g. 20 | 21 | `sudo apt install cmake libfreetype6-dev libexpat-dev libfontconfig1` 22 | 23 | ## Windows 24 | 25 | Should just work. -------------------------------------------------------------------------------- /benches/sprite.rs: -------------------------------------------------------------------------------- 1 | #![feature(test)] 2 | extern crate test; 3 | use test::Bencher; 4 | 5 | extern crate radiant_rs; 6 | use radiant_rs::*; 7 | 8 | const SPRITE_ITER: u32 = 100000; 9 | 10 | #[bench] 11 | fn sprite_drawing(b: &mut Bencher) { 12 | 13 | let display = Display::builder().hidden().build().unwrap(); 14 | let renderer = Renderer::new(&display).unwrap(); 15 | let sprite = Sprite::from_file(display.context(), r"examples/res/sprites/ball_v2_32x32x18.jpg").unwrap(); 16 | let layer = Layer::new((640., 480.)); 17 | 18 | display.clear_frame(Color::black()); 19 | 20 | // make sure layer is full allocated 21 | for i in 0..SPRITE_ITER { 22 | sprite.draw(&layer, i, (320., 200.), Color::white()); 23 | sprite.draw(&layer, i, (0., 0.), Color::red()); 24 | } 25 | 26 | b.iter(|| { 27 | layer.clear(); 28 | for i in 0..SPRITE_ITER { 29 | sprite.draw(&layer, i, (320., 200.), Color::white()); 30 | sprite.draw(&layer, i, (0., 0.), Color::red()); 31 | } 32 | }); 33 | 34 | display.swap_frame(); 35 | } 36 | 37 | #[bench] 38 | fn sprite_transformed_drawing(b: &mut Bencher) { 39 | 40 | let display = Display::builder().hidden().build().unwrap(); 41 | let renderer = Renderer::new(&display).unwrap(); 42 | let sprite = Sprite::from_file(display.context(), r"examples/res/sprites/ball_v2_32x32x18.jpg").unwrap(); 43 | let layer = Layer::new((640., 480.)); 44 | 45 | display.clear_frame(Color::black()); 46 | 47 | // make sure layer is full allocated 48 | for i in 0..SPRITE_ITER { 49 | sprite.draw(&layer, i, (320., 200.), Color::white()); 50 | sprite.draw(&layer, i, (0., 0.), Color::red()); 51 | } 52 | 53 | b.iter(|| { 54 | layer.clear(); 55 | for i in 0..SPRITE_ITER { 56 | sprite.draw_transformed(&layer, i, (320., 200.), Color::white(), 1.23, (2.34, 3.45)); 57 | sprite.draw_transformed(&layer, i, (0., 0.), Color::red(), 2.34, (0.67, 0.79)); 58 | } 59 | }); 60 | 61 | display.swap_frame(); 62 | } 63 | -------------------------------------------------------------------------------- /doc/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sinesc/radiant-rs/67c3e9c5a86b829642117849b6366c41afed15c8/doc/favicon.png -------------------------------------------------------------------------------- /doc/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sinesc/radiant-rs/67c3e9c5a86b829642117849b6366c41afed15c8/doc/logo.png -------------------------------------------------------------------------------- /doc/spritesheet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sinesc/radiant-rs/67c3e9c5a86b829642117849b6366c41afed15c8/doc/spritesheet.png -------------------------------------------------------------------------------- /examples/01_empty.rs: -------------------------------------------------------------------------------- 1 | extern crate radiant_rs; 2 | extern crate radiant_utils as ru; 3 | use radiant_rs::{Display, Color}; 4 | 5 | pub fn main() { 6 | 7 | // Create a display to render to 8 | let display = Display::builder().dimensions((640, 480)).vsync().title("Empty window example").build().unwrap(); 9 | 10 | // A simple mainloop helper (just an optional utility function) 11 | ru::renderloop(|_| { 12 | 13 | // Clear current backbuffer frame (black) 14 | display.clear_frame(Color::BLACK); 15 | 16 | // Here would be a good place to draw stuff 17 | 18 | // Swap back- and frontbuffer, making the changes visible 19 | display.swap_frame(); 20 | 21 | // Poll for new events on the display, exit loop if the window was closed 22 | !display.poll_events().was_closed() 23 | }); 24 | } 25 | -------------------------------------------------------------------------------- /examples/02_sprites.rs: -------------------------------------------------------------------------------- 1 | extern crate radiant_rs; 2 | extern crate radiant_utils as ru; 3 | use radiant_rs::{Display, Renderer, Layer, Sprite, Color, blendmodes}; 4 | 5 | pub fn main() { 6 | let display = Display::builder().dimensions((640, 480)).vsync().title("Sprites example").build().unwrap(); 7 | 8 | // Create a renderer. It is used to draw to a rendertarget (usually a frame). 9 | let renderer = Renderer::new(&display).unwrap(); 10 | 11 | // Create a sprite from a spritesheet file, extracting frame layout from filename. 12 | let sprite = Sprite::from_file(display.context(), r"examples/res/sprites/ball_v2_32x32x18.jpg").unwrap(); 13 | 14 | // A layer where 320x240 units correspond to the full window (which measures 640x480 pixels, so that one unit = two pixel). 15 | let layer = Layer::new((320., 240.)); 16 | 17 | // Layers have a blendmode setting that defines how their contents will be blended with the background on draw. 18 | layer.set_blendmode(blendmodes::LIGHTEN); 19 | 20 | ru::renderloop(|frame| { 21 | 22 | // Clear the layer (layers could also be drawn multiple times, e.g. a static UI might not need to be updated each frame) 23 | layer.clear(); 24 | 25 | // Draw three sprites to the layer, multiplied by colors red, green and blue as well as the original sprite (multiplied by white, which is the identity) 26 | let frame_id = (frame.elapsed_f32 * 30.0) as u32; 27 | sprite.draw(&layer, frame_id, (160., 120.), Color::WHITE); 28 | sprite.draw(&layer, frame_id, (130., 100.), Color::RED); 29 | sprite.draw(&layer, frame_id, (190., 100.), Color::GREEN); 30 | sprite.draw(&layer, frame_id, (160., 155.), Color::BLUE); 31 | 32 | // draw the layer to the frame after clearing it with solid black. 33 | display.clear_frame(Color::BLACK); 34 | renderer.draw_layer(&layer, 0); 35 | 36 | display.swap_frame(); 37 | !display.poll_events().was_closed() 38 | }); 39 | } 40 | -------------------------------------------------------------------------------- /examples/03_text.rs: -------------------------------------------------------------------------------- 1 | extern crate radiant_rs; 2 | extern crate radiant_utils as ru; 3 | use radiant_rs::{Display, Renderer, Layer, Font, Color}; 4 | 5 | pub fn main() { 6 | let display = Display::builder().dimensions((640, 480)).vsync().title("Text example").build().unwrap(); 7 | let renderer = Renderer::new(&display).unwrap(); 8 | let layer = Layer::new((640., 480.)); 9 | 10 | // A few fonts (here from a known systemfont, could also come from a file) 11 | let small = Font::builder(display.context()).family("Arial").size(16.0).build().unwrap(); 12 | let large = small.clone_with_size(48.0); 13 | let tiny_it = Font::builder(display.context()).family("Arial").italic().size(12.0).build().unwrap(); 14 | 15 | ru::renderloop(|frame| { 16 | layer.clear(); 17 | 18 | // Colorize entire layer. This is applied multiplicatively to the contents of the layer on draw. 19 | layer.set_color(Color::from_hsl((frame.elapsed_f32/5.0).fract(), 0.5, 0.5, 1.0)); 20 | 21 | // Write some text 22 | large.write(&layer, "Nine squared", (210., 100.), Color::WHITE); 23 | small.write(&layer, include_str!("res/limerick.txt"), (210., 160.), Color::WHITE); 24 | tiny_it.write(&layer, "https://en.wikipedia.org/wiki/Leigh_Mercer", (10., 460.), Color::WHITE); 25 | 26 | // The usual clear, draw, swap, repeat. 27 | display.clear_frame(Color::BLACK); 28 | renderer.draw_layer(&layer, 0); 29 | display.swap_frame(); 30 | !display.poll_events().was_closed() 31 | }); 32 | } 33 | -------------------------------------------------------------------------------- /examples/04_matrix.rs: -------------------------------------------------------------------------------- 1 | extern crate radiant_rs; 2 | extern crate radiant_utils as ru; 3 | use radiant_rs::{Display, Renderer, Layer, Sprite, Color, blendmodes}; 4 | use ru::Matrix; 5 | 6 | pub fn main() { 7 | let display = Display::builder().dimensions((640, 480)).vsync().title("Matrix example").build().unwrap(); 8 | let renderer = Renderer::new(&display).unwrap(); 9 | let sprite = Sprite::from_file(display.context(), r"examples/res/sprites/sparkles_64x64x1.png").unwrap(); 10 | let layer = Layer::new((320., 240.)); 11 | layer.set_blendmode(blendmodes::LIGHTEN); 12 | 13 | // Draw the usual sprites to the layer just once. We won't ever clear it, so we don't have to continuously redraw them. 14 | sprite.draw(&layer, 0, (160., 120.), Color::WHITE); 15 | sprite.draw(&layer, 0, (130., 100.), Color::WHITE); 16 | sprite.draw(&layer, 0, (190., 100.), Color::WHITE); 17 | sprite.draw(&layer, 0, (160., 155.), Color::WHITE); 18 | 19 | ru::renderloop(|frame| { 20 | display.clear_frame(Color::BLACK); 21 | let presentation_id = (frame.elapsed_f32 / 1.5) as u32 % 4; 22 | 23 | // Draw the layer in red, rotating only its view matrix. 24 | // The red sprites will all rotate around the center of the window. 25 | // Transformations to the view matrix apply globally to the layer. 26 | if presentation_id == 1 || presentation_id == 0 { 27 | layer.set_color(Color::RED); 28 | layer.view_matrix().push().rotate_at((160., 120.), frame.elapsed_f32); 29 | renderer.draw_layer(&layer, 0); 30 | layer.view_matrix().pop(); 31 | } 32 | 33 | // Draw the same layer in green again, rotating only its model matrix. 34 | // The green sprites will all rotate around their own, individual centers. 35 | // Transformations to the model matrix apply locally to each sprite on the layer. 36 | if presentation_id == 2 || presentation_id == 0 { 37 | layer.set_color(Color::GREEN); 38 | layer.model_matrix().push().rotate_at((0., 0.), frame.elapsed_f32); 39 | renderer.draw_layer(&layer, 0); 40 | layer.model_matrix().pop(); 41 | } 42 | 43 | // Draw the same layer in blue yet again, rotating both matrices. 44 | if presentation_id == 3 || presentation_id == 0 { 45 | layer.set_color(Color::BLUE); 46 | layer.view_matrix().push().rotate_at((160., 120.), frame.elapsed_f32); 47 | layer.model_matrix().push().rotate_at((0., 0.), frame.elapsed_f32); 48 | renderer.draw_layer(&layer, 0); 49 | layer.view_matrix().pop(); 50 | layer.model_matrix().pop(); 51 | } 52 | 53 | display.swap_frame(); 54 | !display.poll_events().was_closed() 55 | }); 56 | } 57 | -------------------------------------------------------------------------------- /examples/05_tiles.rs: -------------------------------------------------------------------------------- 1 | extern crate radiant_rs; 2 | extern crate tiled; 3 | extern crate radiant_utils as ru; 4 | use std::collections::HashMap; 5 | use std::f32::consts::PI; 6 | use std::path::Path; 7 | use std::fs::File; 8 | use radiant_rs::*; 9 | use ru::Matrix; 10 | 11 | // Note: Recommended to be run with --release. This example loads a large tileset 12 | // using the image library which appears to be a bit sedate in debug mode. 13 | 14 | pub fn main() { 15 | let display = Display::builder().dimensions((640, 480)).vsync().title("Tiles example").build().unwrap(); 16 | let renderer = Renderer::new(&display).unwrap(); 17 | 18 | // Load tile-sheet as sprite, each frame will be a tile. 19 | let tileset = Sprite::from_file(display.context(), r"examples/res/tiles/iso_64x128.png").unwrap(); 20 | 21 | // Create a HashMap that maps each tile-name to a frame_id. The sheet and the textfile were generated from a folder of images using tools/spritesheet.rs 22 | let name_to_frame_id = include_str!(r"res/tiles/iso_64x128.txt").trim().lines().enumerate().map(|(id, line)| (line, id as u32)).collect::>(); 23 | 24 | // Use rs-tiled to load a tilemap (free tiles from http://www.kenney.nl/) 25 | let map = tiled::parse(File::open("examples/res/tiles/iso.tmx").unwrap()).unwrap(); 26 | 27 | // Create another HashMap that maps each of tiled's local tile ids to their image file name. 28 | let tile_to_name = map.tilesets[0].tiles.iter().map(|tile| (tile.id, Path::new(&tile.images[0].source).file_name().unwrap().to_str().unwrap()) ).collect::>(); 29 | let first_gid = map.tilesets[0].first_gid; 30 | 31 | // Set up an isometric transformation matrix. 32 | let mut iso_transform = ru::Mat4::identity(); 33 | iso_transform.translate((320., 50., 0.)); 34 | iso_transform.scale((64. / 2f32.sqrt(), 36. / 2f32.sqrt())); 35 | iso_transform.rotate(PI / 4.); 36 | 37 | // Draw each tile-layer onto a single (radiant) layer. 38 | let mut layers = Vec::new(); 39 | 40 | for tile_layer in &map.layers { 41 | layers.push(Layer::new((640., 480.))); 42 | for x in 0..map.width as usize { 43 | for y in 0..map.height as usize { 44 | let tile_id = tile_layer.tiles[y][x]; 45 | if tile_id >= first_gid { 46 | let name = tile_to_name[&(tile_id - first_gid)]; 47 | let pos = iso_transform * ru::Vec2(x as f32, y as f32); 48 | tileset.draw(&layers.last().unwrap(), name_to_frame_id[name], (pos.0.round(), pos.1.round()), Color::WHITE); 49 | } 50 | } 51 | } 52 | } 53 | 54 | ru::renderloop(|frame| { 55 | display.clear_frame(Color::BLACK); 56 | 57 | // fade layers individually in 58 | let presentation = frame.elapsed_f32.floor() as usize % (layers.len() + 4); 59 | 60 | for i in 0..layers.len() { 61 | if presentation >= i { 62 | if presentation == i { 63 | layers[i].set_color(Color::alpha_pm( frame.elapsed_f32.fract() )); 64 | } 65 | renderer.draw_layer(&layers[i], 0); 66 | } 67 | } 68 | 69 | display.swap_frame(); 70 | !display.poll_events().was_closed() 71 | }); 72 | } 73 | -------------------------------------------------------------------------------- /examples/06_program.rs: -------------------------------------------------------------------------------- 1 | extern crate radiant_rs; 2 | extern crate radiant_utils as ru; 3 | use radiant_rs::{Display, Renderer, Layer, Sprite, Color, Program}; 4 | use ru::Matrix; 5 | 6 | pub fn main() { 7 | let display = Display::builder().dimensions((640, 480)).vsync().title("Custom program example").build().unwrap(); 8 | let renderer = Renderer::new(&display).unwrap(); 9 | let sprite = Sprite::from_file(display.context(), r"examples/res/sprites/ball_v2_32x32x18.jpg").unwrap(); 10 | 11 | // A custom shader program. 12 | let program = Program::from_string(display.context(), include_str!("res/effects/ripple.fs")).unwrap(); 13 | 14 | // Two layers, one with the default program, the other one with the custom program. 15 | // Cloning a layer like this creates a new layer that references the contents of the 16 | // source layer but has its own matrices, program, and so on. 17 | let layer = Layer::new((320., 240.)); 18 | let layer_custom = layer.clone_with_program(program); 19 | 20 | // Translate them to the left/right (this uses the fairyjar::Matrix trait) 21 | layer.view_matrix().translate((-80., 0.)); 22 | layer_custom.view_matrix().translate((80., 0.)); 23 | 24 | ru::renderloop(|frame| { 25 | display.clear_frame(Color::BLACK); 26 | layer.clear(); 27 | 28 | // Draw to "both" layers. 29 | let frame_id = (frame.elapsed_f32 * 30.0) as u32; 30 | sprite.draw(&layer, frame_id, (160., 120.), Color::WHITE); 31 | sprite.draw(&layer, frame_id, (130., 100.), Color::RED); 32 | sprite.draw(&layer, frame_id, (190., 100.), Color::GREEN); 33 | sprite.draw(&layer, frame_id, (160., 155.), Color::BLUE); 34 | 35 | // Draw both layers. 36 | renderer.draw_layer(&layer, 0); 37 | renderer.draw_layer(&layer_custom, 0); 38 | 39 | display.swap_frame(); 40 | !display.poll_events().was_closed() 41 | }); 42 | } 43 | -------------------------------------------------------------------------------- /examples/07_to_texture.rs: -------------------------------------------------------------------------------- 1 | extern crate radiant_rs; 2 | extern crate radiant_utils as ru; 3 | use radiant_rs::{Display, Renderer, Layer, Sprite, Color, Texture, TextureFilter, blendmodes}; 4 | use ru::Matrix; 5 | 6 | pub fn main() { 7 | let display = Display::builder().dimensions((640, 480)).vsync().title("Drawing to textures example").build().unwrap(); 8 | let renderer = Renderer::new(&display).unwrap(); 9 | let sprite = Sprite::from_file(display.context(), r"examples/res/sprites/sparkles_64x64x1.png").unwrap(); 10 | let layer = Layer::new((320., 240.)); 11 | layer.set_blendmode(blendmodes::LIGHTEN); 12 | 13 | sprite.draw(&layer, 0, (160., 120.), Color::WHITE); 14 | sprite.draw(&layer, 0, (130., 100.), Color::RED); 15 | sprite.draw(&layer, 0, (190., 100.), Color::GREEN); 16 | sprite.draw(&layer, 0, (160., 155.), Color::BLUE); 17 | 18 | // A texture. Each frame we'll draw the sprites to "surface", then blended with 19 | // a low opacity black to make old contents slowly disappear. 20 | let surface = Texture::new(display.context(), 640, 480); 21 | 22 | ru::renderloop(|frame| { 23 | display.clear_frame(Color::BLACK); 24 | 25 | // Rotate the sprite matrices (this uses the fairyjar::Matrix trait) 26 | layer.view_matrix().rotate_at((160., 120.), frame.delta_f32); 27 | layer.model_matrix().rotate(frame.delta_f32 * 1.1); 28 | 29 | // Drawing within Renderer::render_to() redirects the output to the given rendertarget. 30 | // First we draw the sprites, then we blend the low opacity black on top (to fade previously drawn contents) 31 | renderer.render_to(&surface, || { 32 | renderer.draw_layer(&layer, 0); 33 | renderer.fill().blendmode(blendmodes::ALPHA).color(Color(0., 0., 0., 0.04)).draw(); 34 | }); 35 | 36 | if (frame.elapsed_f32 / 1.5) as u32 % 2 == 0 { 37 | // Copies surface to the display. 38 | renderer.copy_from(&surface, TextureFilter::Linear); 39 | } else { 40 | // Draw the sprites to display. 41 | renderer.draw_layer(&layer, 0); 42 | } 43 | 44 | // Draw a small thumbnail of surface 45 | renderer.copy_rect_from(&surface, ((0, 0), (640, 480)), ((512, 384), (128, 96)), TextureFilter::Linear); 46 | 47 | display.swap_frame(); 48 | !display.poll_events().was_closed() 49 | }); 50 | } 51 | -------------------------------------------------------------------------------- /examples/08_postprocessor.rs: -------------------------------------------------------------------------------- 1 | extern crate radiant_rs; 2 | extern crate radiant_utils as ru; 3 | use radiant_rs::{Display, Renderer, Layer, Sprite, Color, Program, blendmodes, postprocessors}; 4 | use ru::Matrix; 5 | 6 | pub fn main() { 7 | let display = Display::builder().dimensions((640, 480)).vsync().title("Basic postprocessor example").build().unwrap(); 8 | let renderer = Renderer::new(&display).unwrap(); 9 | let sprite = Sprite::from_file(display.context(), r"examples/res/sprites/sparkles_64x64x1.png").unwrap(); 10 | let layer = Layer::new((320., 240.)); 11 | layer.set_blendmode(blendmodes::LIGHTEN); 12 | 13 | sprite.draw(&layer, 0, (160., 120.), Color::WHITE); 14 | sprite.draw(&layer, 0, (130., 100.), Color::RED); 15 | sprite.draw(&layer, 0, (190., 100.), Color::GREEN); 16 | sprite.draw(&layer, 0, (160., 155.), Color::BLUE); 17 | 18 | // Load a shader progam. 19 | let program = Program::from_string(display.context(), include_str!("res/effects/ripple.fs")).unwrap(); 20 | 21 | // Use a default Basic postprocessor with the given program. It simply draws the input 22 | // using the given program, but there is a trait to implement custom postprocessors. 23 | let ripple_effect = postprocessors::Basic::new(display.context(), program, (640, 480)); 24 | 25 | ru::renderloop(|frame| { 26 | display.clear_frame(Color::BLACK); 27 | layer.view_matrix().rotate_at((160., 120.), frame.delta_f32); 28 | layer.model_matrix().rotate(frame.delta_f32 * 1.1); 29 | 30 | // Drawing within Renderer::postprocess() applies the given postprocessor to the result 31 | // This particular postprocessor takes a blendmode as argument, which is provided here with blendmodes::LIGHTEN. 32 | renderer.postprocess(&ripple_effect, &blendmodes::LIGHTEN, || { 33 | renderer.clear(Color::TRANSPARENT); 34 | renderer.draw_layer(&layer, 0); 35 | }); 36 | 37 | renderer.draw_layer(&layer, 0); 38 | 39 | display.swap_frame(); 40 | !display.poll_events().was_closed() 41 | }); 42 | } 43 | -------------------------------------------------------------------------------- /examples/09_combined.rs: -------------------------------------------------------------------------------- 1 | extern crate radiant_rs; 2 | extern crate radiant_utils as ru; 3 | use radiant_rs::{Display, Renderer, Layer, Sprite, Color, Texture, TextureFilter, blendmodes}; 4 | use ru::Matrix; 5 | 6 | #[path="res/effects/bloom.rs"] 7 | mod bloom; 8 | 9 | pub fn main() { 10 | let display = Display::builder().dimensions((640, 480)).vsync().title("Draw to texture and postprocess example").build().unwrap(); 11 | let renderer = Renderer::new(&display).unwrap(); 12 | let sprite = Sprite::from_file(display.context(), r"examples/res/sprites/sparkles_64x64x1.png").unwrap(); 13 | let layer = Layer::new((320., 240.)); 14 | layer.set_blendmode(blendmodes::SCREEN); 15 | 16 | sprite.draw(&layer, 0, (160., 120.), Color::WHITE); 17 | sprite.draw(&layer, 0, (130., 100.), Color::RED); 18 | sprite.draw(&layer, 0, (190., 100.), Color::GREEN); 19 | sprite.draw(&layer, 0, (160., 155.), Color::BLUE); 20 | 21 | // A custom example bloom effect postprocessor. The arguments define 22 | // bloom quality, bloom spread and brightness. 23 | // note: Radiant now also includes a predefined Bloom postprocessor. This example uses a similar implementation. 24 | let bloom_effect = bloom::Bloom::new(display.context(), display.dimensions(), 2, 5, 10.0); 25 | 26 | let surface = Texture::new(display.context(), 640, 480); 27 | let thumbnail = Texture::new(display.context(), 640, 480); 28 | 29 | ru::renderloop(|frame| { 30 | display.prepare_frame(); 31 | layer.view_matrix().rotate_at((160., 120.), frame.delta_f32); 32 | layer.model_matrix().rotate(frame.delta_f32 * 1.1); 33 | 34 | // Back up view matrix, then scale it based on elapsed time 35 | let prev_view_matrix = layer.view_matrix().clone(); 36 | layer.view_matrix().scale_at((160., 120.), (frame.elapsed_f32.sin() + 2.) * 0.5); 37 | 38 | // This example simply combines rendering to textures with postprocessors. 39 | renderer.render_to(&surface, || { 40 | renderer.postprocess(&bloom_effect, &blendmodes::ALPHA, || { 41 | // Render to thumbnail... 42 | renderer.render_to(&thumbnail, || { 43 | renderer.clear(Color::BLACK); 44 | renderer.draw_layer(&layer, 0); 45 | }); 46 | // ...but also copy to current render-target (the postprocessor input) 47 | renderer.copy_from(&thumbnail, TextureFilter::Linear); 48 | }); 49 | renderer.draw_layer(&layer, 0); 50 | }); 51 | 52 | // Draw processed texture to display. Also draw the original layer ontop. 53 | renderer.fill().blendmode(blendmodes::ALPHA).color(Color::alpha_mask(0.15)).draw(); 54 | renderer.fill().blendmode(blendmodes::SCREEN).texture(&surface).draw(); 55 | 56 | // Draw small thumbnails of the intermediate and final surface. 57 | // Note: copy_* are fast pixel copy operations (no shaders/blending/transforms). Coordinates are in pixels (integers). 58 | renderer.copy_rect_from(&thumbnail, ((0, 0), (640, 480)), ((512, 288), (128, 96)), TextureFilter::Linear); 59 | renderer.copy_rect_from(&surface, ((0, 0), (640, 480)), ((512, 384), (128, 96)), TextureFilter::Linear); 60 | 61 | // Draw color filtered variants of the thumbnail. 62 | renderer.rect(((469., 384.), (43., 32.))).blendmode(blendmodes::ALPHA).texture(&surface).color(Color::RED).draw(); 63 | renderer.rect(((469., 416.), (43., 32.))).blendmode(blendmodes::ALPHA).texture(&surface).color(Color::GREEN).draw(); 64 | renderer.rect(((469., 448.), (43., 32.))).blendmode(blendmodes::ALPHA).texture(&surface).color(Color::BLUE).draw(); 65 | 66 | layer.set_view_matrix(prev_view_matrix); 67 | display.swap_frame(); 68 | !display.poll_events().was_closed() 69 | }); 70 | } 71 | -------------------------------------------------------------------------------- /examples/10_multi_window.rs: -------------------------------------------------------------------------------- 1 | extern crate radiant_rs; 2 | extern crate radiant_utils as ru; 3 | use radiant_rs::*; 4 | use ru::Matrix; 5 | 6 | pub fn main() { 7 | 8 | // NOTE: if this example fails with a "not yet implemented" panic it means that glutin does not yet support context- 9 | // sharing on your platform (yet). 10 | 11 | // Set up shared context 12 | let context = Context::new(); 13 | 14 | // Create three displays using the shared context. 15 | let display1 = Display::builder().dimensions((640, 480)).vsync().title("Window 1").context(&context).build().unwrap(); 16 | let display2 = Display::builder().dimensions((640, 480)).vsync().title("Window 2").context(&context).build().unwrap(); 17 | let display3 = Display::builder().dimensions((640, 480)).vsync().title("Window 3").context(&context).build().unwrap(); 18 | 19 | // Setup renderer defaulting to window 1. 20 | let renderer = Renderer::headless(&context).unwrap(); 21 | let input = Input::new(&display1); 22 | 23 | // Create a layers to draw to. 24 | let spark_layer = Layer::new((640., 480.)); 25 | spark_layer.set_blendmode(blendmodes::LIGHTEN); 26 | spark_layer.model_matrix().scale(4.0); 27 | 28 | // Load sprite and draw it three times, tinted red, green and blue. No need to do this each frame since we're 29 | // only going to manipulate the matrices. 30 | let sprite = Sprite::from_file(&context, r"examples/res/sprites/sparkles2_64x64x1.png").unwrap(); 31 | sprite.draw(&spark_layer, 0, (320., 180.), *Color::RED.scale(1.5)); 32 | sprite.draw(&spark_layer, 0, (300., 200.), *Color::GREEN.scale(1.5)); 33 | sprite.draw(&spark_layer, 0, (340., 200.), *Color::BLUE.scale(1.5)); 34 | 35 | // Clone a couple of layer matrices to play around with. 36 | let mut view1 = spark_layer.view_matrix().clone(); 37 | let mut view2 = spark_layer.view_matrix().clone(); 38 | let mut view3 = spark_layer.view_matrix().clone(); 39 | 40 | ru::renderloop(|frame| { 41 | display1.prepare_frame(); 42 | display2.prepare_frame(); 43 | display3.prepare_frame(); 44 | 45 | // Prepare some matrices. 46 | spark_layer.model_matrix().rotate(-frame.delta_f32 * 4.0); 47 | view1.rotate_at((320., 200.), frame.delta_f32 * 1.0); 48 | view2.rotate_at((320., 200.), frame.delta_f32 * 1.5); 49 | view3.rotate_at((320., 200.), frame.delta_f32 * 2.0); 50 | 51 | // Render to window 1. 52 | renderer.render_to(&display1, || { 53 | renderer.fill().color(Color(0.0, 0.0, 0.0, 0.03)).draw(); 54 | renderer.draw_layer(spark_layer.set_color(Color::RED).set_view_matrix(view1), 0); 55 | renderer.draw_layer(spark_layer.set_color(Color::RED).set_view_matrix(view2), 0); 56 | renderer.draw_layer(spark_layer.set_color(Color::RED).set_view_matrix(view3), 0); 57 | }); 58 | 59 | // Render to window 2. 60 | renderer.render_to(&display2, || { 61 | renderer.fill().color(Color(0.0, 0.0, 0.0, 0.03)).draw(); 62 | renderer.draw_layer(spark_layer.set_color(Color::GREEN).set_view_matrix(view1), 0); 63 | renderer.draw_layer(spark_layer.set_color(Color::GREEN).set_view_matrix(view2), 0); 64 | renderer.draw_layer(spark_layer.set_color(Color::GREEN).set_view_matrix(view3), 0); 65 | }); 66 | 67 | // Render to window 3. 68 | renderer.render_to(&display3, || { 69 | renderer.fill().color(Color(0.0, 0.0, 0.0, 0.03)).draw(); 70 | renderer.draw_layer(spark_layer.set_color(Color::BLUE).set_view_matrix(view1), 0); 71 | renderer.draw_layer(spark_layer.set_color(Color::BLUE).set_view_matrix(view2), 0); 72 | renderer.draw_layer(spark_layer.set_color(Color::BLUE).set_view_matrix(view3), 0); 73 | }); 74 | 75 | display1.swap_frame(); 76 | display2.swap_frame(); 77 | display3.swap_frame(); 78 | !display1.poll_events().was_closed() && !input.down(InputId::Escape) 79 | }); 80 | } 81 | -------------------------------------------------------------------------------- /examples/98_threads.rs: -------------------------------------------------------------------------------- 1 | extern crate radiant_rs; 2 | extern crate radiant_utils as ru; 3 | use radiant_rs::{Color, Renderer, Layer, Display, Font, blendmodes}; 4 | use std::thread; 5 | use std::sync::{Arc, Barrier}; 6 | 7 | pub fn main() { 8 | let display = Display::builder().dimensions((640, 480)).vsync().title("Threads example").build().unwrap(); 9 | let renderer = Renderer::new(&display).unwrap(); 10 | 11 | // Create a layer and a font, wrapped in Arcs 12 | let layer = Arc::new(Layer::new((640., 480.))); 13 | layer.set_blendmode(blendmodes::LIGHTEN); 14 | let font = Arc::new(Font::builder(display.context()).family("Arial").size(20.0).build().unwrap()); 15 | 16 | // Even though it would be safe to draw without coordination from multiple threads 17 | // while continously rendering from the main thread, you still want to present 18 | // completed frames. 19 | // Set up two barriers to ensure 1) all threads are done and before we show the frame 20 | // and 2) threads don't restart drawing before the main thread clears the layer. 21 | let num_threads = 15; 22 | let draw_start = Arc::new(Barrier::new(num_threads + 1)); 23 | let draw_done = Arc::new(Barrier::new(num_threads + 1)); 24 | 25 | for i in 0..num_threads { 26 | let (font, layer, draw_start, draw_done) = (font.clone(), layer.clone(), draw_start.clone(), draw_done.clone()); 27 | 28 | // draw from a bunch of threads in parallel 29 | thread::spawn(move || { 30 | let mut rot = 0.0; 31 | let pos = 120.0 + (i as f32) * 20.0; 32 | loop { 33 | font.write_transformed(&layer, &format!("Thread {} !", i+1), (pos, pos / 1.33), Color::WHITE, 0.0, rot, (1.0, 1.0)); 34 | rot += 0.01; 35 | 36 | // wait until all other threads have also drawn, then wait until the layers have been rendered 37 | draw_start.wait(); 38 | draw_done.wait(); 39 | } 40 | }); 41 | } 42 | 43 | ru::renderloop(|_| { 44 | 45 | // Unblock once all threads have finished drawing. 46 | draw_start.wait(); 47 | 48 | display.clear_frame(Color::BLACK); 49 | renderer.draw_layer(&layer, 0); 50 | layer.clear(); 51 | 52 | // Layer is drawn, let threads render their next frame. 53 | draw_done.wait(); 54 | 55 | display.swap_frame(); 56 | !display.poll_events().was_closed() 57 | }); 58 | } 59 | -------------------------------------------------------------------------------- /examples/demo_blobs.rs: -------------------------------------------------------------------------------- 1 | extern crate radiant_rs; 2 | extern crate radiant_utils as ru; 3 | use radiant_rs::*; 4 | use ru::Matrix; 5 | 6 | #[path="res/effects/bloom.rs"] 7 | mod bloom; 8 | 9 | pub fn main() { 10 | // Setup input/display. 11 | let display = Display::builder().dimensions((640, 480)).vsync().title("Swirling blobs demo").build().unwrap(); 12 | let renderer = Renderer::new(&display).unwrap(); 13 | let input = Input::new(&display); 14 | 15 | // Create two layers to draw to. 16 | let text_layer = Layer::new((640., 480.)); 17 | let spark_layer = Layer::new((640., 480.)); 18 | spark_layer.set_blendmode(blendmodes::LIGHTEN); 19 | spark_layer.model_matrix().scale(4.0); 20 | 21 | // This is a userdefined postprocessor to add a bloom effect. 22 | let bloom_effect = bloom::Bloom::new(display.context(), display.dimensions(), 2, 5, 4.0); 23 | 24 | // Load sprite and fonts. 25 | let sprite = Sprite::from_file(display.context(), r"examples/res/sprites/sparkles2_64x64x1.png").unwrap(); 26 | let font = Font::builder(display.context()).family("Arial").size(12.0).build().unwrap(); 27 | let big_font = font.clone_with_size(24.0); 28 | 29 | // Draw the sprite three times, tinted red, green and blue. No need to do this each frame since we're 30 | // only going to manipulate the matrices. Also write some text. 31 | sprite.draw(&spark_layer, 0, (320., 180.), *Color::RED.scale(1.5)); 32 | sprite.draw(&spark_layer, 0, (300., 200.), *Color::GREEN.scale(1.5)); 33 | sprite.draw(&spark_layer, 0, (340., 200.), *Color::BLUE.scale(1.5)); 34 | big_font.write(&text_layer, "blobs.rs", (355., 330.), Color::RED); 35 | font.write(&text_layer, "rotating colorful blobs since 2016", (370., 350.), Color::WHITE); 36 | 37 | // Clone a couple of layer matrices to play around with 38 | let mut view1 = spark_layer.view_matrix().clone(); 39 | let mut view2 = spark_layer.view_matrix().clone(); 40 | let mut view3 = spark_layer.view_matrix().clone(); 41 | 42 | ru::renderloop(|frame| { 43 | display.clear_frame(Color::BLACK); 44 | 45 | // Rotate the model matrix. 46 | spark_layer.model_matrix().rotate(-frame.delta_f32 * 4.0); 47 | 48 | // Rotate the three viewmatrix clones at different rates. 49 | view1.rotate_at((320., 200.), frame.delta_f32 * 1.0); 50 | view2.rotate_at((320., 200.), frame.delta_f32 * 1.5); 51 | view3.rotate_at((320., 200.), frame.delta_f32 * 2.0); 52 | 53 | // Draw the spark layer three times with different matrices and alpha levels. 54 | renderer.postprocess(&bloom_effect, &blendmodes::COPY, || { 55 | renderer.fill().color(Color(0.0, 0.0, 0.0, 0.02)).draw(); 56 | renderer.draw_layer(spark_layer.set_color(Color::alpha(0.125)).set_view_matrix(view1), 0); 57 | renderer.draw_layer(spark_layer.set_color(Color::alpha(0.5)).set_view_matrix(view2), 0); 58 | renderer.draw_layer(spark_layer.set_color(Color::alpha(1.0)).set_view_matrix(view3), 0); 59 | }); 60 | 61 | // Draw the text layer. 62 | renderer.draw_layer(&text_layer, 0); 63 | 64 | display.swap_frame(); 65 | !display.poll_events().was_closed() && !input.down(InputId::Escape) 66 | }); 67 | } 68 | -------------------------------------------------------------------------------- /examples/demo_glare.rs: -------------------------------------------------------------------------------- 1 | extern crate radiant_rs; 2 | extern crate radiant_utils as ru; 3 | use radiant_rs::*; 4 | 5 | #[path="res/effects/bloom.rs"] 6 | mod bloom; 7 | 8 | pub fn main() { 9 | let display = Display::builder().dimensions((640, 480)).vsync().title("Glare effect demo").build().unwrap(); 10 | let renderer = Renderer::new(&display).unwrap(); 11 | let input = Input::new(&display); 12 | let font = Font::builder(display.context()).family("Arial").size(12.0).build().unwrap(); 13 | 14 | // Load spritesheet containing components for rgba and a "lightmap". Create custom postprocessor. 15 | let sprite = Sprite::from_file(display.context(), r"examples/res/sprites/battery_lightmapped_128x128x15x2.png").unwrap(); 16 | let bloom_effect = bloom::Bloom::new(display.context(), display.dimensions(), 2, 5, 5.0); 17 | 18 | // A bunch of layers. The lightmap layers use component 1 (the "lightmap") of the sprite. 19 | let color_layer = Layer::new((640., 480.)); 20 | let lightmap_layer = Layer::new((640., 480.)); 21 | let unprocessed_lightmap_layer = Layer::new((640., 480.)); 22 | 23 | ru::renderloop(|frame| { 24 | display.clear_frame(Color::BLACK); 25 | 26 | color_layer.clear(); 27 | lightmap_layer.clear(); 28 | unprocessed_lightmap_layer.clear(); 29 | 30 | // Draw top row of sprites: unprocessed components. 31 | let frame_id = (frame.elapsed_f32 * 30.0) as u32; 32 | sprite.draw(&color_layer, frame_id, (120., 100.), Color::WHITE); 33 | font.write(&color_layer, "Sprite component 0", (80., 140.), Color::WHITE); 34 | 35 | sprite.draw(&unprocessed_lightmap_layer, frame_id, (320., 100.), Color::WHITE); 36 | font.write(&color_layer, "Sprite component 1", (280., 140.), Color::WHITE); 37 | 38 | sprite.draw(&color_layer, frame_id, (520., 100.), Color::WHITE); 39 | sprite.draw(&unprocessed_lightmap_layer, frame_id, (520., 100.), Color::WHITE); 40 | font.write(&color_layer, "Both components", (480., 140.), Color::WHITE); 41 | 42 | // Draw bottom sprite: this one uses lightmap_layer, which will be postprocessed below. 43 | sprite.draw(&color_layer, frame_id, (320., 380.), Color::WHITE); 44 | sprite.draw(&lightmap_layer, frame_id, (320., 380.), Color::WHITE); 45 | font.write(&color_layer, "Component 0 wihout postprocessing\nComponent 1 with postprocessing", (220., 440.), Color::WHITE); 46 | 47 | // Draw unprocessed layers. 48 | renderer.draw_layer(&color_layer, 0); 49 | renderer.draw_layer(&lightmap_layer, 1); 50 | renderer.draw_layer(&unprocessed_lightmap_layer, 1); 51 | 52 | // Draw light_map layer to postprocessor. 53 | renderer.postprocess(&bloom_effect, &blendmodes::SCREEN, || { 54 | renderer.clear(Color(0., 0., 0., 0.05)); 55 | renderer.draw_layer(&lightmap_layer, 1); 56 | }); 57 | 58 | display.poll_events(); 59 | 60 | if input.pressed(InputId::Return, false) { 61 | display.toggle_fullscreen(None).ok().unwrap(); 62 | } 63 | 64 | display.swap_frame(); 65 | !display.was_closed() && !input.down(InputId::Escape) 66 | }); 67 | } 68 | -------------------------------------------------------------------------------- /examples/glium_less.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate glium; 3 | extern crate radiant_rs; 4 | extern crate radiant_utils as ru; 5 | use radiant_rs::{backend, Renderer, Layer, Sprite, Color, blendmodes}; 6 | use glium::Surface; 7 | 8 | #[path="res/glium_utils.rs"] 9 | mod glium_utils; 10 | 11 | // This example uses Radiant for display/events handling and uses Glium (and Radiant) for drawing. 12 | pub fn main() { 13 | 14 | // Set up glium display. Much of the glium code in this example was taken from 15 | // https://github.com/glium/glium/blob/master/examples/triangle.rs 16 | let window = glium::glutin::WindowBuilder::new() 17 | .with_dimensions((640, 480).into()) 18 | .with_title("Glium example 1: Radiant with a little Glium"); 19 | 20 | let context = glium::glutin::ContextBuilder::new() 21 | .with_vsync(true); 22 | 23 | let events_loop = glium::glutin::EventsLoop::new(); 24 | let glium_display = glium::Display::new(window, context, &events_loop).unwrap(); 25 | 26 | // Build glium buffers, program and uniforms (see res/glium_utils.rs) 27 | let vertex_buffer = glium_utils::build_vertex_buffer(&glium_display); 28 | let index_buffer = glium_utils::build_index_buffer(&glium_display); 29 | let program = glium_utils::build_program(&glium_display); 30 | let uniforms = glium_utils::build_uniforms(); 31 | 32 | // Create a Radiant Display from an existing glium Display and glutin EventsLoop. 33 | backend::set_events_loop(events_loop); 34 | let display = backend::create_display(&glium_display); 35 | 36 | // Create the renderer, a sprite and a layer 37 | let renderer = Renderer::new(&display).unwrap(); 38 | let sprite = Sprite::from_file(display.context(), r"examples/res/sprites/ball_v2_32x32x18.jpg").unwrap(); 39 | let layer = Layer::new((320., 240.)); 40 | layer.set_blendmode(blendmodes::LIGHTEN); 41 | 42 | ru::renderloop(|frame| { 43 | 44 | // Clear the layer, draw a few sprites to it 45 | layer.clear(); 46 | let frame_id = (frame.elapsed_f32 * 30.0) as u32; 47 | sprite.draw(&layer, frame_id, (160., 120.), Color::WHITE); 48 | sprite.draw(&layer, frame_id, (130., 100.), Color::RED); 49 | sprite.draw(&layer, frame_id, (190., 100.), Color::GREEN); 50 | sprite.draw(&layer, frame_id, (160., 155.), Color::BLUE); 51 | 52 | // Clear the frame 53 | display.clear_frame(Color::BLACK); 54 | 55 | // "Borrow" Radiant's current frame to draw to it using Glium 56 | backend::take_frame(&display, |target| { 57 | target.draw(&vertex_buffer, &index_buffer, &program, &uniforms, &Default::default()).unwrap(); 58 | }); 59 | 60 | // Draw the sprites layer on top of the glium triangle 61 | renderer.draw_layer(&layer, 0); 62 | 63 | // Finish the frame and swap 64 | display.swap_frame(); 65 | 66 | // Handle window close event 67 | !display.poll_events().was_closed() 68 | }); 69 | } 70 | -------------------------------------------------------------------------------- /examples/glium_more.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate glium; 3 | extern crate radiant_rs; 4 | extern crate radiant_utils as ru; 5 | use radiant_rs::{backend, Layer, Sprite, Color, blendmodes}; 6 | use glium::Surface; 7 | 8 | #[path="res/glium_utils.rs"] 9 | mod glium_utils; 10 | 11 | // This example uses Radiant (and Glium) for rendering and relies on glium/glutin for display/events handling. 12 | pub fn main() { 13 | 14 | // Set up glium display. Much of the glium code in this example was taken from 15 | // https://github.com/glium/glium/blob/master/examples/triangle.rs 16 | let window = glium::glutin::WindowBuilder::new() 17 | .with_dimensions((640, 480).into()) 18 | .with_title("Glium example 2: Glium with a little Radiant"); 19 | 20 | let context = glium::glutin::ContextBuilder::new() 21 | .with_vsync(true); 22 | 23 | let mut events_loop = glium::glutin::EventsLoop::new(); 24 | let glium_display = glium::Display::new(window, context, &events_loop).unwrap(); 25 | 26 | // Build glium buffers, program and uniforms (see res/glium_utils.rs) 27 | let vertex_buffer = glium_utils::build_vertex_buffer(&glium_display); 28 | let index_buffer = glium_utils::build_index_buffer(&glium_display); 29 | let program = glium_utils::build_program(&glium_display); 30 | let uniforms = glium_utils::build_uniforms(); 31 | 32 | // Create a Radiant Renderer from an existing glium Display 33 | let renderer = backend::create_renderer(&glium_display).unwrap(); 34 | 35 | // Create a sprite and a layer 36 | let sprite = Sprite::from_file(&renderer.context(), r"examples/res/sprites/ball_v2_32x32x18.jpg").unwrap(); 37 | let layer = Layer::new((320., 240.)); 38 | layer.set_blendmode(blendmodes::LIGHTEN); 39 | 40 | ru::renderloop(|frame| { 41 | 42 | // Clear the layer, draw a few sprites to it 43 | layer.clear(); 44 | let frame_id = (frame.elapsed_f32 * 30.0) as u32; 45 | sprite.draw(&layer, frame_id, (160., 120.), Color::WHITE); 46 | sprite.draw(&layer, frame_id, (130., 100.), Color::RED); 47 | sprite.draw(&layer, frame_id, (190., 100.), Color::GREEN); 48 | sprite.draw(&layer, frame_id, (160., 155.), Color::BLUE); 49 | 50 | // Get and clear a glium frame 51 | let mut target = glium_display.draw(); 52 | target.clear_color(0.0, 0.0, 0.0, 0.0); 53 | 54 | // Draw to to the glium frame using Glium 55 | target.draw(&vertex_buffer, &index_buffer, &program, &uniforms, &Default::default()).unwrap(); 56 | 57 | // Draw the sprites layer on top of the glium triangle 58 | backend::target_frame(&renderer, &mut target, || { 59 | renderer.draw_layer(&layer, 0); 60 | }); 61 | 62 | // Finish the frame and swap 63 | target.finish().unwrap(); 64 | 65 | // Handle window close event 66 | !glium_utils::was_closed(&mut events_loop) 67 | }); 68 | } 69 | -------------------------------------------------------------------------------- /examples/res/effects/bloom.rs: -------------------------------------------------------------------------------- 1 | use radiant_rs::*; 2 | use std::sync::Mutex; 3 | 4 | pub struct Bloom { 5 | targets : [[Texture; 5]; 2], 6 | blur_program : Mutex, 7 | combine_program : Mutex, 8 | iterations : u32, 9 | spread : u8, 10 | } 11 | 12 | impl Postprocessor for Bloom { 13 | type T = BlendMode; 14 | 15 | /// Returns the target where the postprocessor expects the unprocessed input. 16 | fn target(self: &Self) -> &Texture { 17 | &self.targets[0][0] 18 | } 19 | 20 | /// Process received data. 21 | fn process(self: &Self, renderer: &Renderer, _: &Self::T) { 22 | use std::ops::DerefMut; 23 | 24 | // Copy to progressively smaller textures 25 | for i in 1..self.spread as usize { 26 | renderer.render_to(&self.targets[0][i], || { 27 | renderer.copy_from(&self.targets[0][i-1], TextureFilter::Linear); 28 | }); 29 | } 30 | 31 | let mut blur = self.blur_program.lock().unwrap(); 32 | let blur = blur.deref_mut(); 33 | 34 | for _ in 0..self.iterations { 35 | 36 | // Apply horizontal blur 37 | blur.set_uniform("horizontal", &true); 38 | for i in 0..self.spread as usize { 39 | renderer.render_to(&self.targets[1][i], || { 40 | renderer.fill().blendmode(blendmodes::ALPHA).program(&blur).texture(&self.targets[0][i]).draw(); 41 | }); 42 | } 43 | 44 | // Apply vertical blur 45 | blur.set_uniform("horizontal", &false); 46 | for i in 0..self.spread as usize { 47 | renderer.render_to(&self.targets[0][i], || { 48 | renderer.fill().blendmode(blendmodes::ALPHA).program(&blur).texture(&self.targets[1][i]).draw(); 49 | }); 50 | } 51 | } 52 | } 53 | 54 | /// Draw processed input. The renderer has already set the correct target. 55 | fn draw(self: &Self, renderer: &Renderer, blendmode: &Self::T) { 56 | use std::ops::DerefMut; 57 | let mut combine = self.combine_program.lock().unwrap(); 58 | let combine = combine.deref_mut(); 59 | renderer.fill().blendmode(*blendmode).program(&combine).draw(); 60 | self.targets[0][0].clear(Color::TRANSPARENT); 61 | } 62 | } 63 | 64 | impl Bloom { 65 | pub fn new(context: &Context, dimensions: (u32, u32), iterations: u32, spread: u8, brightness: f32) -> Self { 66 | use std::cmp::min; 67 | 68 | let blur_program = Program::from_string(&context, include_str!("blur.fs")).unwrap(); 69 | let mut combine_program = Program::from_string(&context, include_str!("combine.fs")).unwrap(); 70 | let (width, height) = dimensions; 71 | let builder = Texture::builder(context).format(TextureFormat::F16F16F16F16); 72 | 73 | let targets = [ [ 74 | builder.clone().dimensions((width / 2, height / 2)).build().unwrap(), 75 | builder.clone().dimensions((width / 4, height / 4)).build().unwrap(), 76 | builder.clone().dimensions((width / 8, height / 8)).build().unwrap(), 77 | builder.clone().dimensions((width / 16, height / 16)).build().unwrap(), 78 | builder.clone().dimensions((width / 32, height / 32)).build().unwrap(), 79 | ], [ 80 | builder.clone().dimensions((width / 2, height / 2)).build().unwrap(), 81 | builder.clone().dimensions((width / 4, height / 4)).build().unwrap(), 82 | builder.clone().dimensions((width / 8, height / 8)).build().unwrap(), 83 | builder.clone().dimensions((width / 16, height / 16)).build().unwrap(), 84 | builder.clone().dimensions((width / 32, height / 32)).build().unwrap(), 85 | ] ]; 86 | 87 | let spread = min(targets[0].len() as u8, spread); 88 | 89 | combine_program.set_uniform("brightness", &(brightness / spread as f32)); 90 | combine_program.set_uniform("sample0", &targets[0][0]); 91 | combine_program.set_uniform("sample1", &targets[0][1]); 92 | combine_program.set_uniform("sample2", &targets[0][2]); 93 | combine_program.set_uniform("sample3", &targets[0][3]); 94 | combine_program.set_uniform("sample4", &targets[0][4]); 95 | 96 | Bloom { 97 | blur_program : Mutex::new(blur_program), 98 | combine_program : Mutex::new(combine_program), 99 | iterations : iterations, 100 | targets : targets, 101 | spread : spread, 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /examples/res/effects/blur.fs: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | in vec2 v_tex_coords; 4 | uniform bool horizontal; 5 | const float weight[3] = float[] (0.3125, 0.375, 0.3125); 6 | out vec4 f_color; 7 | 8 | void main() { 9 | 10 | vec2 offset; 11 | 12 | if (horizontal) { 13 | offset = vec2(1.2 / sheetSize().x, 0.0); 14 | } else { 15 | offset = vec2(0.0, 1.2 / sheetSize().y); 16 | } 17 | 18 | vec2 sample0 = v_tex_coords - offset; 19 | vec2 sample2 = v_tex_coords + offset; 20 | 21 | vec4 color = weight[1] * sheet(v_tex_coords); 22 | 23 | if (sample0.x > offset.x && sample0.y > offset.y) { 24 | color += weight[0] * sheet(sample0); 25 | } 26 | 27 | if (sample2.x < 1.0 - offset.x && sample2.y < 1.0 - offset.y) { 28 | color += weight[2] * sheet(sample2); 29 | } 30 | 31 | f_color = clamp(color, 0.0, 1.0); 32 | } 33 | -------------------------------------------------------------------------------- /examples/res/effects/combine.fs: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | in vec2 v_tex_coords; 4 | uniform sampler2D sample0; 5 | uniform sampler2D sample1; 6 | uniform sampler2D sample2; 7 | uniform sampler2D sample3; 8 | uniform sampler2D sample4; 9 | uniform float brightness; 10 | out vec4 f_color; 11 | 12 | void main(void) { 13 | vec4 t0 = texture2D(sample0, v_tex_coords); 14 | vec4 t1 = texture2D(sample1, v_tex_coords); 15 | vec4 t2 = texture2D(sample2, v_tex_coords); 16 | vec4 t3 = texture2D(sample3, v_tex_coords); 17 | vec4 t4 = texture2D(sample4, v_tex_coords); 18 | f_color = clamp((t0 + t1 + t2 + t3 + t4) * brightness, 0.0, 1.0); 19 | } 20 | -------------------------------------------------------------------------------- /examples/res/effects/ripple.fs: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | in vec2 v_tex_coords; 4 | in vec4 v_color; 5 | 6 | out vec4 f_color; 7 | 8 | const float PI = 3.1415926538; 9 | const float intensity = 0.8; 10 | const float range = 0.16; 11 | 12 | void main() { 13 | 14 | float base = sin(v_tex_coords.x) + sin(v_tex_coords.y); 15 | float rand = fract(base * 10000.0); 16 | 17 | float radius = range * sqrt(rand); 18 | float angle = 2.0 * PI * rand; 19 | 20 | vec2 offset = vec2(radius * sin(angle), radius * cos(angle)); 21 | 22 | f_color = v_color * (sheet(v_tex_coords + offset) * intensity * 0.05 23 | + sheet(v_tex_coords + offset * 0.8) * intensity * 0.15 24 | + sheet(v_tex_coords + offset * 0.6) * intensity * 0.3 25 | + sheet(v_tex_coords + offset * 0.4) * intensity * 0.5 26 | + sheet(v_tex_coords) * (1.0 - intensity)); 27 | } 28 | -------------------------------------------------------------------------------- /examples/res/glium_utils.rs: -------------------------------------------------------------------------------- 1 | use glium; 2 | use glium::index::PrimitiveType; 3 | use glium::glutin; 4 | 5 | #[derive(Copy, Clone)] 6 | pub struct Vertex { 7 | position: [f32; 2], 8 | color: [f32; 3], 9 | } 10 | 11 | implement_vertex!(Vertex, position, color); 12 | 13 | pub fn build_vertex_buffer(glium_display: &glium::Display) -> glium::VertexBuffer { 14 | glium::VertexBuffer::new(glium_display, 15 | &[ 16 | Vertex { position: [-0.5, -0.5], color: [0.0, 1.0, 0.0] }, 17 | Vertex { position: [ 0.0, 0.5], color: [0.0, 0.0, 1.0] }, 18 | Vertex { position: [ 0.5, -0.5], color: [1.0, 0.0, 0.0] }, 19 | ] 20 | ).unwrap() 21 | } 22 | 23 | pub fn build_program(glium_display: &glium::Display) -> glium::Program { 24 | program!(glium_display, 25 | 140 => { 26 | vertex: " 27 | #version 140 28 | uniform mat4 matrix; 29 | in vec2 position; 30 | in vec3 color; 31 | out vec3 vColor; 32 | void main() { 33 | gl_Position = vec4(position, 0.0, 1.0) * matrix; 34 | vColor = color; 35 | } 36 | ", 37 | fragment: " 38 | #version 140 39 | in vec3 vColor; 40 | out vec4 f_color; 41 | void main() { 42 | f_color = vec4(vColor, 1.0); 43 | } 44 | " 45 | }, 46 | ).unwrap() 47 | } 48 | 49 | pub fn build_index_buffer(glium_display: &glium::Display) -> glium::IndexBuffer { 50 | glium::IndexBuffer::new(glium_display, PrimitiveType::TrianglesList, &[0u16, 1, 2]).unwrap() 51 | } 52 | 53 | pub fn build_uniforms<'a>() -> glium::uniforms::UniformsStorage<'a, [[f32; 4]; 4], glium::uniforms::EmptyUniforms> { 54 | uniform! { 55 | matrix: [ 56 | [1.0, 0.0, 0.0, 0.0], 57 | [0.0, 1.0, 0.0, 0.0], 58 | [0.0, 0.0, 1.0, 0.0], 59 | [0.0, 0.0, 0.0, 1.0f32] 60 | ] 61 | } 62 | } 63 | 64 | #[allow(dead_code)] 65 | pub fn was_closed(events_loop: &mut glium::glutin::EventsLoop) -> bool { 66 | 67 | let mut was_closed = false; 68 | 69 | events_loop.poll_events(|event| { 70 | match event { 71 | glutin::Event::WindowEvent { event, .. } => match event { 72 | // Break from the main loop when the window is closed. 73 | glutin::WindowEvent::CloseRequested => { was_closed = true; }, 74 | _ => (), 75 | }, 76 | _ => (), 77 | } 78 | }); 79 | 80 | was_closed 81 | } -------------------------------------------------------------------------------- /examples/res/limerick.txt: -------------------------------------------------------------------------------- 1 | A dozen, a gross, and a score 2 | Plus three times the square root of four 3 | Divided by seven 4 | Plus five times eleven 5 | Is nine squared and not a bit more. 6 | -------------------------------------------------------------------------------- /examples/res/sprites/ball_v2_32x32x18.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sinesc/radiant-rs/67c3e9c5a86b829642117849b6366c41afed15c8/examples/res/sprites/ball_v2_32x32x18.jpg -------------------------------------------------------------------------------- /examples/res/sprites/battery_lightmapped_128x128x15x2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sinesc/radiant-rs/67c3e9c5a86b829642117849b6366c41afed15c8/examples/res/sprites/battery_lightmapped_128x128x15x2.png -------------------------------------------------------------------------------- /examples/res/sprites/sparkles2_64x64x1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sinesc/radiant-rs/67c3e9c5a86b829642117849b6366c41afed15c8/examples/res/sprites/sparkles2_64x64x1.png -------------------------------------------------------------------------------- /examples/res/sprites/sparkles_64x64x1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sinesc/radiant-rs/67c3e9c5a86b829642117849b6366c41afed15c8/examples/res/sprites/sparkles_64x64x1.png -------------------------------------------------------------------------------- /examples/res/tiles/iso.tmx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | eJxjZGBgYARiVigNwugAlzgHELNDMbo6RjTMhianiUUNshi6e9DVYsP41DEgmY8NcGHxLyG7kN3IikMdJxa/qQGxOhBrYHE7AOtAAZk= 155 | 156 | 157 | 158 | 159 | eJxjYWBgYAJifigNwuiABYi1sIijA2Yg1kHSAzKLF0leCEkdM5TNQ4SZICBIQJ0AlObGIqeHxTxCQACJLccA8QvIT7JALEOGecSqAwBWvwGB 160 | 161 | 162 | 163 | 164 | eJxjYEAFhkCsAsWqSGLEAC00vhgQi2JRp0PAHH0c4hJQLE6ke0BAiQS1uIA8lFbEIW8ApXG5mxBQgNJ6QAwAhQIC1g== 165 | 166 | 167 | 168 | 169 | eJxjYKANkAZiYSge7oCafgQAVYgAVQ== 170 | 171 | 172 | 173 | 174 | eJxjYCAesAOxCBBzkKAHBHRJVD+SAQBmaABR 175 | 176 | 177 | 178 | -------------------------------------------------------------------------------- /examples/res/tiles/iso_64x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sinesc/radiant-rs/67c3e9c5a86b829642117849b6366c41afed15c8/examples/res/tiles/iso_64x128.png -------------------------------------------------------------------------------- /examples/res/tiles/iso_64x128.txt: -------------------------------------------------------------------------------- 1 | barrel_E.png 2 | barrel_N.png 3 | barrel_S.png 4 | barrel_W.png 5 | barrelsStacked_E.png 6 | barrelsStacked_N.png 7 | barrelsStacked_S.png 8 | barrelsStacked_W.png 9 | barrels_E.png 10 | barrels_N.png 11 | barrels_S.png 12 | barrels_W.png 13 | bridgeBroken_E.png 14 | bridgeBroken_N.png 15 | bridgeBroken_S.png 16 | bridgeBroken_W.png 17 | bridge_E.png 18 | bridge_N.png 19 | bridge_S.png 20 | bridge_W.png 21 | chair_E.png 22 | chair_N.png 23 | chair_S.png 24 | chair_W.png 25 | chestClosed_E.png 26 | chestClosed_N.png 27 | chestClosed_S.png 28 | chestClosed_W.png 29 | chestOpen_E.png 30 | chestOpen_N.png 31 | chestOpen_S.png 32 | chestOpen_W.png 33 | dirtTiles_E.png 34 | dirtTiles_N.png 35 | dirtTiles_S.png 36 | dirtTiles_W.png 37 | dirt_E.png 38 | dirt_N.png 39 | dirt_S.png 40 | dirt_W.png 41 | planksBroken_E.png 42 | planksBroken_N.png 43 | planksBroken_S.png 44 | planksBroken_W.png 45 | planksHole_E.png 46 | planksHole_N.png 47 | planksHole_S.png 48 | planksHole_W.png 49 | planks_E.png 50 | planks_N.png 51 | planks_S.png 52 | planks_W.png 53 | stairsAged_E.png 54 | stairsAged_N.png 55 | stairsAged_S.png 56 | stairsAged_W.png 57 | stairsSpiral_E.png 58 | stairsSpiral_N.png 59 | stairsSpiral_S.png 60 | stairsSpiral_W.png 61 | stairs_E.png 62 | stairs_N.png 63 | stairs_S.png 64 | stairs_W.png 65 | stoneColumnWood_E.png 66 | stoneColumnWood_N.png 67 | stoneColumnWood_S.png 68 | stoneColumnWood_W.png 69 | stoneColumn_E.png 70 | stoneColumn_N.png 71 | stoneColumn_S.png 72 | stoneColumn_W.png 73 | stoneLeft_E.png 74 | stoneLeft_N.png 75 | stoneLeft_S.png 76 | stoneLeft_W.png 77 | stoneMissingTiles_E.png 78 | stoneMissingTiles_N.png 79 | stoneMissingTiles_S.png 80 | stoneMissingTiles_W.png 81 | stoneRight_E.png 82 | stoneRight_N.png 83 | stoneRight_S.png 84 | stoneRight_W.png 85 | stoneSideUneven_E.png 86 | stoneSideUneven_N.png 87 | stoneSideUneven_S.png 88 | stoneSideUneven_W.png 89 | stoneSide_E.png 90 | stoneSide_N.png 91 | stoneSide_S.png 92 | stoneSide_W.png 93 | stoneStep_E.png 94 | stoneStep_N.png 95 | stoneStep_S.png 96 | stoneStep_W.png 97 | stoneSteps_E.png 98 | stoneSteps_N.png 99 | stoneSteps_S.png 100 | stoneSteps_W.png 101 | stoneTile_E.png 102 | stoneTile_N.png 103 | stoneTile_S.png 104 | stoneTile_W.png 105 | stoneUneven_E.png 106 | stoneUneven_N.png 107 | stoneUneven_S.png 108 | stoneUneven_W.png 109 | stoneWallAgedLeft_E.png 110 | stoneWallAgedLeft_N.png 111 | stoneWallAgedLeft_S.png 112 | stoneWallAgedLeft_W.png 113 | stoneWallAgedRight_E.png 114 | stoneWallAgedRight_N.png 115 | stoneWallAgedRight_S.png 116 | stoneWallAgedRight_W.png 117 | stoneWallAged_E.png 118 | stoneWallAged_N.png 119 | stoneWallAged_S.png 120 | stoneWallAged_W.png 121 | stoneWallArchway_E.png 122 | stoneWallArchway_N.png 123 | stoneWallArchway_S.png 124 | stoneWallArchway_W.png 125 | stoneWallBrokenLeft_E.png 126 | stoneWallBrokenLeft_N.png 127 | stoneWallBrokenLeft_S.png 128 | stoneWallBrokenLeft_W.png 129 | stoneWallBrokenRight_E.png 130 | stoneWallBrokenRight_N.png 131 | stoneWallBrokenRight_S.png 132 | stoneWallBrokenRight_W.png 133 | stoneWallBroken_E.png 134 | stoneWallBroken_N.png 135 | stoneWallBroken_S.png 136 | stoneWallBroken_W.png 137 | stoneWallColumnIn_E.png 138 | stoneWallColumnIn_N.png 139 | stoneWallColumnIn_S.png 140 | stoneWallColumnIn_W.png 141 | stoneWallColumn_E.png 142 | stoneWallColumn_N.png 143 | stoneWallColumn_S.png 144 | stoneWallColumn_W.png 145 | stoneWallCorner_E.png 146 | stoneWallCorner_N.png 147 | stoneWallCorner_S.png 148 | stoneWallCorner_W.png 149 | stoneWallDoorBars_E.png 150 | stoneWallDoorBars_N.png 151 | stoneWallDoorBars_S.png 152 | stoneWallDoorBars_W.png 153 | stoneWallDoorClosed_E.png 154 | stoneWallDoorClosed_N.png 155 | stoneWallDoorClosed_S.png 156 | stoneWallDoorClosed_W.png 157 | stoneWallDoorOpen_E.png 158 | stoneWallDoorOpen_N.png 159 | stoneWallDoorOpen_S.png 160 | stoneWallDoorOpen_W.png 161 | stoneWallDoor_E.png 162 | stoneWallDoor_N.png 163 | stoneWallDoor_S.png 164 | stoneWallDoor_W.png 165 | stoneWallGateBars_E.png 166 | stoneWallGateBars_N.png 167 | stoneWallGateBars_S.png 168 | stoneWallGateBars_W.png 169 | stoneWallGateClosed_E.png 170 | stoneWallGateClosed_N.png 171 | stoneWallGateClosed_S.png 172 | stoneWallGateClosed_W.png 173 | stoneWallGateOpen_E.png 174 | stoneWallGateOpen_N.png 175 | stoneWallGateOpen_S.png 176 | stoneWallGateOpen_W.png 177 | stoneWallGate_E.png 178 | stoneWallGate_N.png 179 | stoneWallGate_S.png 180 | stoneWallGate_W.png 181 | stoneWallHole_E.png 182 | stoneWallHole_N.png 183 | stoneWallHole_S.png 184 | stoneWallHole_W.png 185 | stoneWallTop_E.png 186 | stoneWallTop_N.png 187 | stoneWallTop_S.png 188 | stoneWallTop_W.png 189 | stoneWallWindowBars_E.png 190 | stoneWallWindowBars_N.png 191 | stoneWallWindowBars_S.png 192 | stoneWallWindowBars_W.png 193 | stoneWallWindow_E.png 194 | stoneWallWindow_N.png 195 | stoneWallWindow_S.png 196 | stoneWallWindow_W.png 197 | stoneWall_E.png 198 | stoneWall_N.png 199 | stoneWall_S.png 200 | stoneWall_W.png 201 | stone_E.png 202 | stone_N.png 203 | stone_S.png 204 | stone_W.png 205 | tableChairsBroken_E.png 206 | tableChairsBroken_N.png 207 | tableChairsBroken_S.png 208 | tableChairsBroken_W.png 209 | tableRoundChairs_E.png 210 | tableRoundChairs_N.png 211 | tableRoundChairs_S.png 212 | tableRoundChairs_W.png 213 | tableRound_E.png 214 | tableRound_N.png 215 | tableRound_S.png 216 | tableRound_W.png 217 | tableShortChairs_E.png 218 | tableShortChairs_N.png 219 | tableShortChairs_S.png 220 | tableShortChairs_W.png 221 | tableShort_E.png 222 | tableShort_N.png 223 | tableShort_S.png 224 | tableShort_W.png 225 | woodenCrate_E.png 226 | woodenCrate_N.png 227 | woodenCrate_S.png 228 | woodenCrate_W.png 229 | woodenCrates_E.png 230 | woodenCrates_N.png 231 | woodenCrates_S.png 232 | woodenCrates_W.png 233 | woodenPile_E.png 234 | woodenPile_N.png 235 | woodenPile_S.png 236 | woodenPile_W.png 237 | woodenSupportBeams_E.png 238 | woodenSupportBeams_N.png 239 | woodenSupportBeams_S.png 240 | woodenSupportBeams_W.png 241 | woodenSupportsBeam_E.png 242 | woodenSupportsBeam_N.png 243 | woodenSupportsBeam_S.png 244 | woodenSupportsBeam_W.png 245 | woodenSupportsBlock_E.png 246 | woodenSupportsBlock_N.png 247 | woodenSupportsBlock_S.png 248 | woodenSupportsBlock_W.png 249 | woodenSupports_E.png 250 | woodenSupports_N.png 251 | woodenSupports_S.png 252 | woodenSupports_W.png 253 | -------------------------------------------------------------------------------- /src/backends/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "backend-glium")] 2 | mod glium; 3 | 4 | #[cfg(feature = "backend-glium")] 5 | pub mod backend { 6 | pub use super::glium::*; 7 | } 8 | 9 | 10 | #[cfg(feature = "backend-null")] 11 | mod null; 12 | 13 | #[cfg(feature = "backend-null")] 14 | pub mod backend { 15 | pub use super::null::*; 16 | } -------------------------------------------------------------------------------- /src/backends/null.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_variables, dead_code, unused_mut)] 2 | /*! 3 | Null-Renderer 4 | 5 | This sample show the minimum required implementation of a backend. 6 | */ 7 | 8 | use core; 9 | use core::math::*; 10 | 11 | // -------------- 12 | // Public interface provided to Radiant-API-user in radiant_rs::backend 13 | // -------------- 14 | 15 | pub mod public { 16 | 17 | } 18 | 19 | // -------------- 20 | // Error 21 | // -------------- 22 | 23 | #[derive(Debug)] 24 | pub enum Error { 25 | Failed, 26 | } 27 | 28 | // -------------- 29 | // Display 30 | // -------------- 31 | 32 | #[derive(Clone)] 33 | pub struct Display(); 34 | 35 | impl Display { 36 | pub fn new(descriptor: core::DisplayInfo) -> core::Result { 37 | Ok(Display()) 38 | } 39 | pub fn draw(self: &Self) -> Frame { 40 | Frame() 41 | } 42 | pub fn framebuffer_dimensions(self: &Self) -> Point2 { 43 | (0, 0) 44 | } 45 | pub fn window_dimensions(self: &Self) -> Point2 { 46 | (0, 0) 47 | } 48 | pub fn set_fullscreen(self: &Self, monitor: Option) -> bool { 49 | false 50 | } 51 | pub fn set_cursor_position(self: &Self, position: Point2) { 52 | } 53 | pub fn set_cursor_state(self: &Self, state: core::CursorState) { 54 | } 55 | pub fn poll_events(self: &Self, mut callback: F) where F: FnMut(core::Event) -> () { 56 | } 57 | pub fn show(self: &Self) { 58 | } 59 | pub fn hide(self: &Self) { 60 | } 61 | pub fn set_title(self: &Self, title: &str) { 62 | } 63 | } 64 | 65 | // -------------- 66 | // Frame 67 | // -------------- 68 | 69 | pub struct Frame(); 70 | 71 | impl Frame { 72 | pub fn clear(self: &mut Self, color: core::Color) { 73 | } 74 | pub fn finish(self: Self) { 75 | } 76 | pub fn copy_from_texture(self: &Self, source: &core::Texture, filter: core::TextureFilter) { 77 | } 78 | pub fn copy_rect(self: &Self, source_rect: Rect, target_rect: Rect, filter: core::TextureFilter) { 79 | } 80 | pub fn copy_rect_from_texture(self: &Self, source: &core::Texture, source_rect: Rect, target_rect: Rect, filter: core::TextureFilter) { 81 | } 82 | pub fn dimensions(self: &Self) -> Point2 { 83 | (0, 0) 84 | } 85 | } 86 | 87 | // -------------- 88 | // Program 89 | // -------------- 90 | 91 | pub struct Program(); 92 | 93 | impl Program { 94 | pub fn new(display: &Display, vertex_shader: &str, fragment_shader: &str) -> core::Result { 95 | Ok(Program()) 96 | } 97 | } 98 | 99 | // -------------- 100 | // Monitor 101 | // -------------- 102 | 103 | #[derive(Clone)] 104 | pub struct Monitor(); 105 | 106 | impl Monitor { 107 | pub fn get_dimensions(self: &Self) -> Point2 { 108 | (0, 0) 109 | } 110 | pub fn get_name(self: &Self) -> Option { 111 | Some("Headless".to_string()) 112 | } 113 | } 114 | 115 | pub struct MonitorIterator(); 116 | 117 | impl MonitorIterator { 118 | pub fn new(display: &core::Display) -> Self { 119 | MonitorIterator() 120 | } 121 | } 122 | 123 | impl Iterator for MonitorIterator { 124 | type Item = Monitor; 125 | fn next(&mut self) -> Option { 126 | None 127 | } 128 | } 129 | 130 | // -------------- 131 | // Texture2d 132 | // -------------- 133 | 134 | pub struct Texture2d(); 135 | 136 | impl Texture2d { 137 | pub fn new(display: &Display, width: u32, height: u32, format: core::TextureFormat, data: Option) -> Texture2d { 138 | Texture2d() 139 | } 140 | pub fn clear(self: &Self, color: core::Color) { 141 | } 142 | pub fn write(self: &Self, rect: &Rect, data: &Vec) { 143 | } 144 | pub fn copy_from(self: &Self, src_texture: &core::Texture, filter: core::TextureFilter) { 145 | } 146 | pub fn copy_rect_from(self: &Self, src_texture: &core::Texture, source_rect: Rect, target_rect: Rect, filter: core::TextureFilter) { 147 | } 148 | pub fn copy_from_frame(self: &Self, src_frame: &Frame, filter: core::TextureFilter) { 149 | } 150 | pub fn copy_rect_from_frame(self: &Self, src_frame: &Frame, source_rect: Rect, target_rect: Rect, filter: core::TextureFilter) { 151 | } 152 | } 153 | 154 | // -------------- 155 | // Texture2dArray 156 | // -------------- 157 | 158 | pub struct Texture2dArray(); 159 | 160 | impl Texture2dArray { 161 | pub fn new(display: &Display, raw: &Vec) -> Self { 162 | Texture2dArray() 163 | } 164 | } 165 | 166 | // -------------- 167 | // Context 168 | // -------------- 169 | 170 | pub struct Context(); 171 | 172 | impl Context { 173 | pub fn new(display: &Display, initial_capacity: usize) -> Self { 174 | Context() 175 | } 176 | } 177 | 178 | // -------------- 179 | // Drawing 180 | // -------------- 181 | 182 | pub fn draw_layer(target: &core::RenderTarget, program: &core::Program, context: &mut core::ContextData, layer: &core::Layer, component: u32) { 183 | } 184 | 185 | pub fn draw_rect(target: &core::RenderTarget, program: &core::Program, context: &mut core::ContextData, blend: core::BlendMode, info: core::DrawRectInfo, view_matrix: Mat4, model_matrix: Mat4, color: core::Color, texture: &core::Texture) { 186 | } 187 | -------------------------------------------------------------------------------- /src/core/blendmode/blendmodes.rs: -------------------------------------------------------------------------------- 1 | 2 | //! A set of predefined blendmodes for use with `Layer::set_blendmode()`. 3 | //! 4 | //! See [`BlendMode`](../struct.BlendMode.html) to define your own blendmodes. 5 | 6 | use super::*; 7 | use core::{Color}; 8 | 9 | /// Replaces source into destination, overwriting color and alpha values. 10 | pub const COPY: BlendMode = BlendMode { 11 | color: BlendingFunction::AlwaysReplace, 12 | alpha: BlendingFunction::AlwaysReplace, 13 | constant_value: Color(0.0, 0.0, 0.0, 0.0) 14 | }; 15 | 16 | /// Alpha-blends source into destination. 17 | pub const ALPHA: BlendMode = BlendMode { 18 | color: BlendingFunction::Addition { 19 | source: LinearBlendingFactor::One, 20 | destination: LinearBlendingFactor::OneMinusSourceAlpha, 21 | }, 22 | alpha: BlendingFunction::Addition { 23 | source: LinearBlendingFactor::One, 24 | destination: LinearBlendingFactor::OneMinusSourceAlpha, 25 | }, 26 | constant_value: Color(0.0, 0.0, 0.0, 0.0) 27 | }; 28 | 29 | /// Adds source and destination. 30 | pub const ADD: BlendMode = BlendMode { 31 | color: BlendingFunction::Addition { 32 | source: LinearBlendingFactor::One, 33 | destination: LinearBlendingFactor::One, 34 | }, 35 | alpha: BlendingFunction::Addition { 36 | source: LinearBlendingFactor::One, 37 | destination: LinearBlendingFactor::One, 38 | }, 39 | constant_value: Color(0.0, 0.0, 0.0, 0.0) 40 | }; 41 | 42 | /// Subtracts source from destination. 43 | pub const SUBTRACT: BlendMode = BlendMode { 44 | color: BlendingFunction::Subtraction { 45 | source: LinearBlendingFactor::One, 46 | destination: LinearBlendingFactor::One, 47 | }, 48 | alpha: BlendingFunction::Subtraction { 49 | source: LinearBlendingFactor::One, 50 | destination: LinearBlendingFactor::One, 51 | }, 52 | constant_value: Color(0.0, 0.0, 0.0, 0.0) 53 | }; 54 | 55 | /// Subtracts destination from source. 56 | pub const REVERSE_SUBTRACT: BlendMode = BlendMode { 57 | color: BlendingFunction::ReverseSubtraction { 58 | source: LinearBlendingFactor::One, 59 | destination: LinearBlendingFactor::One, 60 | }, 61 | alpha: BlendingFunction::ReverseSubtraction { 62 | source: LinearBlendingFactor::One, 63 | destination: LinearBlendingFactor::One, 64 | }, 65 | constant_value: Color(0.0, 0.0, 0.0, 0.0) 66 | }; 67 | 68 | /// Writes the maximum of source and destination into destination. 69 | pub const LIGHTEN: BlendMode = BlendMode { 70 | color: BlendingFunction::Max, 71 | alpha: BlendingFunction::Max, 72 | constant_value: Color(0.0, 0.0, 0.0, 0.0), 73 | }; 74 | 75 | /// Writes the minimum of source and destination into destination. 76 | pub const DARKEN: BlendMode = BlendMode { 77 | color: BlendingFunction::Min, 78 | alpha: BlendingFunction::Min, 79 | constant_value: Color(0.0, 0.0, 0.0, 0.0), 80 | }; 81 | 82 | /// Alpha-blends source into destination. 83 | pub const SQUARED: BlendMode = BlendMode { 84 | color: BlendingFunction::Addition { 85 | source: LinearBlendingFactor::SourceColor, 86 | destination: LinearBlendingFactor::DestinationColor, 87 | }, 88 | alpha: BlendingFunction::Addition { 89 | source: LinearBlendingFactor::One, 90 | destination: LinearBlendingFactor::OneMinusSourceAlpha, 91 | }, 92 | constant_value: Color(0.0, 0.0, 0.0, 0.0) 93 | }; 94 | 95 | /// Overlays source and destination. 96 | pub const SCREEN: BlendMode = BlendMode { 97 | color: BlendingFunction::Addition { 98 | source: LinearBlendingFactor::One, 99 | destination: LinearBlendingFactor::OneMinusSourceColor, 100 | }, 101 | alpha: BlendingFunction::Addition { 102 | source: LinearBlendingFactor::One, 103 | destination: LinearBlendingFactor::OneMinusSourceAlpha, 104 | }, 105 | constant_value: Color(0.0, 0.0, 0.0, 0.0) 106 | }; 107 | 108 | /// Overlays source and destination, masking the destination where source alpha is low. 109 | pub const SCREEN_MASK: BlendMode = BlendMode { 110 | color: BlendingFunction::Addition { 111 | source: LinearBlendingFactor::One, 112 | destination: LinearBlendingFactor::OneMinusSourceColor, 113 | }, 114 | alpha: BlendingFunction::Addition { 115 | source: LinearBlendingFactor::One, 116 | destination: LinearBlendingFactor::OneMinusSourceAlpha, 117 | }, 118 | constant_value: Color(0.0, 0.0, 0.0, 0.0) 119 | }; 120 | 121 | /// Lika ALPHA but multiplies source with given color. 122 | pub fn colorize(color: Color) -> BlendMode { 123 | BlendMode { 124 | color: BlendingFunction::Addition { 125 | source: LinearBlendingFactor::ConstantAlpha, 126 | destination: LinearBlendingFactor::OneMinusSourceAlpha, 127 | }, 128 | alpha: BlendingFunction::Addition { 129 | source: LinearBlendingFactor::ConstantColor, 130 | destination: LinearBlendingFactor::OneMinusSourceAlpha, 131 | }, 132 | constant_value: color 133 | } 134 | } 135 | 136 | /// Fades between source at 1.0 and destination at 0.0 137 | pub fn fade(value: f32) -> BlendMode { 138 | BlendMode { 139 | color: BlendingFunction::Addition { 140 | source: LinearBlendingFactor::ConstantAlpha, 141 | destination: LinearBlendingFactor::ConstantAlpha, 142 | }, 143 | alpha: BlendingFunction::Addition { 144 | source: LinearBlendingFactor::OneMinusConstantAlpha, 145 | destination: LinearBlendingFactor::OneMinusConstantAlpha, 146 | }, 147 | constant_value: Color(1., 1., 1., value) 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /src/core/blendmode/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod blendmodes; 2 | use core::{Color}; 3 | 4 | /// A blendmode for use with [`Layer::set_blendmode()`](struct.Layer.html#method.set_blendmode). 5 | /// See [blendmodes](blendmodes/index.html) for a list of predefined modes. 6 | #[derive(Copy, Clone, Debug, PartialEq)] 7 | pub struct BlendMode { 8 | /// The blending function for color channels. 9 | pub color: BlendingFunction, 10 | /// The blending function for alpha channels. 11 | pub alpha: BlendingFunction, 12 | /// A constant color that can be used in the blending functions. 13 | pub constant_value: Color, 14 | } 15 | 16 | impl Default for BlendMode { 17 | fn default() -> BlendMode { 18 | BlendMode { 19 | color: BlendingFunction::AlwaysReplace, 20 | alpha: BlendingFunction::AlwaysReplace, 21 | constant_value: Color::WHITE, 22 | } 23 | } 24 | } 25 | 26 | /// Function that the GPU will use for blending. 27 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 28 | pub enum BlendingFunction { 29 | /// Simply overwrite the destination pixel with the source pixel. 30 | /// 31 | /// The alpha channels are simply ignored. This is the default mode. 32 | /// 33 | /// For example writing `(0.5, 0.9, 0.4, 0.2)` over `(0.9, 0.1, 0.4, 0.3)` will 34 | /// result in `(0.5, 0.9, 0.4, 0.2)`. 35 | AlwaysReplace, 36 | 37 | /// For each individual component (red, green, blue, and alpha), the minimum value is chosen 38 | /// between the source and the destination. 39 | /// 40 | /// For example writing `(0.5, 0.9, 0.4, 0.2)` over `(0.9, 0.1, 0.4, 0.3)` will 41 | /// result in `(0.5, 0.1, 0.4, 0.2)`. 42 | Min, 43 | 44 | /// For each individual component (red, green, blue, and alpha), the maximum value is chosen 45 | /// between the source and the destination. 46 | /// 47 | /// For example writing `(0.5, 0.9, 0.4, 0.2)` over `(0.9, 0.1, 0.4, 0.3)` will 48 | /// result in `(0.9, 0.9, 0.4, 0.3)`. 49 | Max, 50 | 51 | /// For each individual component (red, green, blue, and alpha), a weighted addition 52 | /// between the source and the destination. 53 | /// 54 | /// The result is equal to `source_component * source_factor + dest_component * dest_factor`, 55 | /// where `source_factor` and `dest_factor` are the values of `source` and `destination` of 56 | /// this enum. 57 | Addition { 58 | /// The factor to apply to the source pixel. 59 | source: LinearBlendingFactor, 60 | 61 | /// The factor to apply to the destination pixel. 62 | destination: LinearBlendingFactor, 63 | }, 64 | 65 | /// For each individual component (red, green, blue, and alpha), a weighted substraction 66 | /// of the source by the destination. 67 | /// 68 | /// The result is equal to `source_component * source_factor - dest_component * dest_factor`, 69 | /// where `source_factor` and `dest_factor` are the values of `source` and `destination` of 70 | /// this enum. 71 | Subtraction { 72 | /// The factor to apply to the source pixel. 73 | source: LinearBlendingFactor, 74 | 75 | /// The factor to apply to the destination pixel. 76 | destination: LinearBlendingFactor, 77 | }, 78 | 79 | /// For each individual component (red, green, blue, and alpha), a weighted substraction 80 | /// of the destination by the source. 81 | /// 82 | /// The result is equal to `-source_component * source_factor + dest_component * dest_factor`, 83 | /// where `source_factor` and `dest_factor` are the values of `source` and `destination` of 84 | /// this enum. 85 | ReverseSubtraction { 86 | /// The factor to apply to the source pixel. 87 | source: LinearBlendingFactor, 88 | 89 | /// The factor to apply to the destination pixel. 90 | destination: LinearBlendingFactor, 91 | }, 92 | } 93 | 94 | /// Indicates which value to multiply each component with. 95 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 96 | pub enum LinearBlendingFactor { 97 | /// Multiply the source or destination component by zero, which always 98 | /// gives `0.0`. 99 | Zero, 100 | 101 | /// Multiply the source or destination component by one, which always 102 | /// gives you the original value. 103 | One, 104 | 105 | /// Multiply the source or destination component by its corresponding value 106 | /// in the source. 107 | /// 108 | /// If you apply this to the source components, you get the values squared. 109 | SourceColor, 110 | 111 | /// Equivalent to `1 - SourceColor`. 112 | OneMinusSourceColor, 113 | 114 | /// Multiply the source or destination component by its corresponding value 115 | /// in the destination. 116 | /// 117 | /// If you apply this to the destination components, you get the values squared. 118 | DestinationColor, 119 | 120 | /// Equivalent to `1 - DestinationColor`. 121 | OneMinusDestinationColor, 122 | 123 | /// Multiply the source or destination component by the alpha value of the source. 124 | SourceAlpha, 125 | 126 | /// Multiply the source or destination component by the smallest value of 127 | /// `SourceAlpha` and `1 - DestinationAlpha`. 128 | SourceAlphaSaturate, 129 | 130 | /// Multiply the source or destination component by `1.0` minus the alpha value of the source. 131 | OneMinusSourceAlpha, 132 | 133 | /// Multiply the source or destination component by the alpha value of the destination. 134 | DestinationAlpha, 135 | 136 | /// Multiply the source or destination component by `1.0` minus the alpha value of the 137 | /// destination. 138 | OneMinusDestinationAlpha, 139 | 140 | /// Multiply the source or destination component by the corresponding value 141 | /// in `Blend::const_value`. 142 | ConstantColor, 143 | 144 | /// Multiply the source or destination compoent by `1.0` minus the corresponding 145 | /// value in `Blend::const_value`. 146 | OneMinusConstantColor, 147 | 148 | /// Multiply the source or destination component by the alpha value of `Blend::const_value`. 149 | ConstantAlpha, 150 | 151 | /// Multiply the source or destination componet by `1.0` minus the alpha value of 152 | /// `Blend::const_value`. 153 | OneMinusConstantAlpha, 154 | } 155 | -------------------------------------------------------------------------------- /src/core/builder/displaybuilder.rs: -------------------------------------------------------------------------------- 1 | use core::{Display, Monitor, Point2, Context, Result}; 2 | 3 | /// A display builder. 4 | /// 5 | /// Obtained from [`Display::builder()`](../struct.Display.html#method.builder). 6 | /// 7 | /// # Examples 8 | /// 9 | /// ```rust 10 | /// # use radiant_rs::*; 11 | /// let display = Display::builder().dimensions((640, 480)).vsync().title("Window!").build().unwrap(); 12 | /// ``` 13 | #[must_use] 14 | #[derive(Clone)] 15 | pub struct DisplayBuilder { 16 | pub(crate) width : u32, 17 | pub(crate) height : u32, 18 | pub(crate) title : String, 19 | pub(crate) transparent : bool, 20 | pub(crate) decorations : bool, 21 | pub(crate) monitor : Option, 22 | pub(crate) vsync : bool, 23 | pub(crate) visible : bool, 24 | pub(crate) context : Option, 25 | } 26 | 27 | impl DisplayBuilder { 28 | /// Sets a width for the display. 29 | pub fn width(mut self: Self, width: u32) -> Self { 30 | self.width = width; 31 | self 32 | } 33 | /// Sets a height for the display. 34 | pub fn height(mut self: Self, height: u32) -> Self { 35 | self.height = height; 36 | self 37 | } 38 | /// Sets dimensions for the display. 39 | pub fn dimensions(mut self: Self, dimensions: T) -> Self where Point2: From { 40 | let dimensions = Point2::::from(dimensions); 41 | self.width = dimensions.0; 42 | self.height = dimensions.1; 43 | self 44 | } 45 | /// Sets a title for the display. 46 | pub fn title(mut self: Self, title: &str) -> Self { 47 | self.title = title.to_string(); 48 | self 49 | } 50 | /// Flags the display to be transparent. 51 | pub fn transparent(mut self: Self) -> Self { 52 | self.transparent = true; 53 | self 54 | } 55 | /// Flags the display to be borderless. 56 | pub fn borderless(mut self: Self) -> Self { 57 | self.decorations = false; 58 | self 59 | } 60 | /// Sets the monitor to create the display on. 61 | /// note: currently monitor cannot be aquired prior to display construction due to changes in the glium backend 62 | pub fn monitor(mut self: Self, monitor: Monitor) -> Self { 63 | self.monitor = Some(monitor); 64 | self 65 | } 66 | /// Flags the display to use vsync. 67 | pub fn vsync(mut self: Self) -> Self { 68 | self.vsync = true; 69 | self 70 | } 71 | /// Use an existing context with this display. 72 | pub fn context(mut self: Self, context: &Context) -> Self { 73 | self.context = Some(context.clone()); 74 | self 75 | } 76 | /// Flags the display to be initialially hidden. 77 | pub fn hidden(mut self: Self) -> Self { 78 | self.visible = false; 79 | self 80 | } 81 | /// Returns the constructed display instance. 82 | pub fn build(self: Self) -> Result { 83 | Display::new(self) 84 | } 85 | /// Creates a new DisplayBuilder instance. 86 | pub(crate) fn new() -> Self { 87 | DisplayBuilder { ..DisplayBuilder::default() } 88 | } 89 | } 90 | 91 | impl Default for DisplayBuilder { 92 | fn default() -> DisplayBuilder { 93 | DisplayBuilder { 94 | width : 640, 95 | height : 480, 96 | title : "".to_string(), 97 | transparent : false, 98 | decorations : true, 99 | monitor : None, 100 | vsync : false, 101 | visible : true, 102 | context : None, 103 | } 104 | } 105 | } -------------------------------------------------------------------------------- /src/core/builder/drawbuilder.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | use core::{Color, Renderer, Texture, BlendMode, Program, Display}; 3 | use core::math::*; 4 | 5 | #[derive(Clone)] 6 | pub struct DrawBuilderFill; 7 | 8 | impl DrawBuilderFill { 9 | // Creates a new DrawBuilderFill instance. 10 | pub(crate) fn new(renderer: &Renderer) -> DrawBuilder { 11 | DrawBuilder { 12 | renderer : renderer, 13 | phantomdata : PhantomData, 14 | rect : ((0., 0.), (1., 1.)), 15 | color : None, 16 | texture : None, 17 | blendmode : None, 18 | view_matrix : DrawBuilderViewSource::One, 19 | model_matrix: None, 20 | program : None, 21 | } 22 | } 23 | } 24 | 25 | #[derive(Clone)] 26 | pub struct DrawBuilderRect; 27 | 28 | impl DrawBuilderRect { 29 | // Creates a new DrawBuilderRect instance. 30 | pub(crate) fn new(renderer: &Renderer, rect: Rect) -> DrawBuilder { 31 | DrawBuilder { 32 | renderer : renderer, 33 | phantomdata : PhantomData, 34 | rect : rect, 35 | color : None, 36 | texture : None, 37 | blendmode : None, 38 | view_matrix : DrawBuilderViewSource::Target, 39 | model_matrix: None, 40 | program : None, 41 | } 42 | } 43 | } 44 | 45 | /// The view matrix used when drawing a rectangle. 46 | #[derive(Clone)] 47 | pub enum DrawBuilderViewSource<'a> { 48 | Display(&'a Display), 49 | Target, 50 | Source, 51 | One, 52 | Matrix(Mat4) 53 | } 54 | 55 | /// A rectangle builder. 56 | /// 57 | /// Obtained from [`Renderer::rect()`](../struct.Renderer.html#method.rect) 58 | /// or [`Renderer::fill()`](../struct.Renderer.html#method.fill) 59 | #[must_use] 60 | #[derive(Clone)] 61 | pub struct DrawBuilder<'a, T: 'a> { 62 | renderer : &'a Renderer, 63 | phantomdata : PhantomData<&'a T>, 64 | pub(crate) rect : Rect, 65 | pub(crate) color : Option, 66 | pub(crate) texture : Option<&'a Texture>, 67 | pub(crate) blendmode : Option, 68 | pub(crate) view_matrix : DrawBuilderViewSource<'a>, 69 | pub(crate) model_matrix : Option, 70 | pub(crate) program : Option<&'a Program>, 71 | } 72 | 73 | /// The following implementations are available when drawing with [`Renderer::rect()`](../struct.Renderer.html#method.rect) 74 | /// or [`Renderer::fill()`](../struct.Renderer.html#method.fill). 75 | impl<'a, T> DrawBuilder<'a, T> { 76 | /// Sets a color for drawing. Defaults to white. If a texture is supplied in 77 | /// addtion to the color, each fragment will be computed from texel color * color. 78 | pub fn color(mut self: Self, color: Color) -> Self { 79 | self.color = Some(color); 80 | self 81 | } 82 | /// Sets the texture for drawing. If a color is supplied in 83 | /// addtion to the texture, each fragment will be computed from texel color * color. 84 | pub fn texture(mut self: Self, texture: &'a Texture) -> Self { 85 | self.texture = Some(texture); 86 | self 87 | } 88 | /// Sets the blendmode used to blend the source with the target. 89 | pub fn blendmode(mut self: Self, blendmode: BlendMode) -> Self { 90 | self.blendmode = Some(blendmode); 91 | self 92 | } 93 | /// Sets the fragment shader program used to draw. 94 | pub fn program(mut self: Self, program: &'a Program) -> Self { 95 | self.program = Some(program); 96 | self 97 | } 98 | /// Sets a model matrix for drawing. 99 | pub fn model_matrix(mut self: Self, model_matrix: Mat4) -> Self { 100 | self.model_matrix = Some(model_matrix); 101 | self 102 | } 103 | /// Draws the rectangle. 104 | pub fn draw(self: Self) { 105 | self.renderer.draw_rect(self); 106 | } 107 | } 108 | 109 | /// The following implementations are only available when drawing with [`Renderer::rect()`](../struct.Renderer.html#method.rect) 110 | impl<'a> DrawBuilder<'a, DrawBuilderRect> { 111 | /// Sets a view matrix for drawing. 112 | pub fn view_matrix(mut self: Self, view_matrix: Mat4) -> Self { 113 | self.view_matrix = DrawBuilderViewSource::Matrix(view_matrix); 114 | self 115 | } 116 | /// Uses a view matrix that maps the dimensions of the target to the pixel-size of the target. 117 | /// 118 | /// This is the default setting. It means that a point, e.g. (12., 34.) is mapped to the pixel 119 | /// (12, 34) on the target. 120 | pub fn view_target(mut self: Self) -> Self { 121 | self.view_matrix = DrawBuilderViewSource::Target; 122 | self 123 | } 124 | /// Uses a view matrix that maps the dimensions of the display to the pixel-size of the target. 125 | pub fn view_display(mut self: Self, display: &'a Display) -> Self { 126 | self.view_matrix = DrawBuilderViewSource::Display(display); 127 | self 128 | } 129 | /// Uses a view matrix that maps the dimensions of the source to the pixel-size of the target. 130 | pub fn view_source(mut self: Self) -> Self { 131 | self.view_matrix = DrawBuilderViewSource::Source; 132 | self 133 | } 134 | /// Uses a matrix that maps the entire target to a rectangle of (0., 0., 1., 1.) 135 | pub fn view_one(mut self: Self) -> Self { 136 | self.view_matrix = DrawBuilderViewSource::One; 137 | self 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/core/builder/fontbuilder.rs: -------------------------------------------------------------------------------- 1 | use core::{Result, Font, FontInfo, Context}; 2 | 3 | /// A font builder. 4 | /// 5 | /// Obtained from [`Font::builder()`](../struct.Font.html#method.builder). 6 | /// 7 | /// # Examples 8 | /// 9 | /// ```rust 10 | /// # use radiant_rs::*; 11 | /// # let display = Display::builder().hidden().build().unwrap(); 12 | /// # let renderer = Renderer::new(&display).unwrap(); 13 | /// # let context = display.context(); 14 | /// let my_font = Font::builder(&context).family("Arial").size(16.0).build().unwrap(); 15 | /// ``` 16 | #[must_use] 17 | #[derive(Clone)] 18 | pub struct FontBuilder<'a> { 19 | info : FontInfo, 20 | context : &'a Context, 21 | file : Option<&'a str>, 22 | } 23 | 24 | impl<'a> FontBuilder<'a> { 25 | /// Sets a family for the font. The font will be retrieved from the operating system. 26 | /// Mutually exclusive with file(). 27 | pub fn family(mut self: Self, family: &str) -> Self { 28 | self.info.family = family.to_string(); 29 | self 30 | } 31 | /// Sets file for the Font to be loaded from. 32 | /// Mutually exclusive with family(). 33 | pub fn file(mut self: Self, file: &'a str) -> Self { 34 | self.file = Some(file); 35 | self 36 | } 37 | /// Flags the font to be italic. 38 | pub fn italic(mut self: Self) -> Self { 39 | self.info.italic = true; 40 | self 41 | } 42 | /// Flags the font to be oblique. 43 | pub fn oblique(mut self: Self) -> Self { 44 | self.info.oblique = true; 45 | self 46 | } 47 | /// Flags the font to be monospace. 48 | pub fn monospace(mut self: Self) -> Self { 49 | self.info.monospace = true; 50 | self 51 | } 52 | /// Flags the font to be bold. 53 | pub fn bold(mut self: Self) -> Self { 54 | self.info.bold = true; 55 | self 56 | } 57 | /// Sets the fontsize. 58 | pub fn size(mut self: Self, size: f32) -> Self { 59 | self.info.size = size; 60 | self 61 | } 62 | /// Returns the constructed font instance. 63 | pub fn build(self: Self) -> Result { 64 | if let Some(file) = self.file { 65 | Font::from_file(self.context, file) 66 | } else { 67 | Font::from_info(self.context, self.info) 68 | } 69 | } 70 | // Creates a new FontBuilder instance. 71 | pub(crate) fn new<'b>(context: &'b Context) -> FontBuilder { 72 | FontBuilder { 73 | context : context, 74 | info : FontInfo { ..FontInfo::default() }, 75 | file : None, 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/core/builder/fontquerybuilder.rs: -------------------------------------------------------------------------------- 1 | use core::{FontInfo, Font}; 2 | 3 | /// A FontQueryBuilder builder, returned from Font::query(). 4 | #[must_use] 5 | #[derive(Clone)] 6 | pub struct FontQueryBuilder { 7 | info: FontInfo, 8 | } 9 | 10 | impl FontQueryBuilder { 11 | /// Sets a family for the Fonts. 12 | pub fn family(mut self: Self, family: &str) -> Self { 13 | self.info.family = family.to_string(); 14 | self 15 | } 16 | /// Requires the fonts to support italic. 17 | pub fn italic(mut self: Self) -> Self { 18 | self.info.italic = true; 19 | self 20 | } 21 | /// Requires the fonts to support oblique. 22 | pub fn oblique(mut self: Self) -> Self { 23 | self.info.oblique = true; 24 | self 25 | } 26 | /// Requires the fonts to support monospace. 27 | pub fn monospace(mut self: Self) -> Self { 28 | self.info.monospace = true; 29 | self 30 | } 31 | /// Requires the fonts to support bold. 32 | pub fn bold(mut self: Self) -> Self { 33 | self.info.bold = true; 34 | self 35 | } 36 | /// Returns a vector of matching font families. 37 | pub fn fetch(self: Self) -> Vec { 38 | Font::query_specific(self.info) 39 | } 40 | /// Creates a new FontQueryBuilder instance. 41 | pub(crate) fn new() -> FontQueryBuilder { 42 | FontQueryBuilder { 43 | info: FontInfo { ..FontInfo::default() }, 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/core/builder/mod.rs: -------------------------------------------------------------------------------- 1 | mod displaybuilder; 2 | mod fontbuilder; 3 | mod fontquerybuilder; 4 | mod texturebuilder; 5 | mod drawbuilder; 6 | 7 | pub use self::displaybuilder::DisplayBuilder; 8 | pub use self::fontbuilder::FontBuilder; 9 | pub use self::fontquerybuilder::FontQueryBuilder; 10 | pub use self::texturebuilder::TextureBuilder; 11 | pub use self::drawbuilder::{DrawBuilder, DrawBuilderFill, DrawBuilderRect, DrawBuilderViewSource}; 12 | -------------------------------------------------------------------------------- /src/core/builder/texturebuilder.rs: -------------------------------------------------------------------------------- 1 | use core::*; 2 | 3 | /// A Texture builder. 4 | #[must_use] 5 | #[derive(Clone)] 6 | pub struct TextureBuilder<'a> { 7 | pub(crate) minify : TextureFilter, 8 | pub(crate) magnify : TextureFilter, 9 | pub(crate) wrap : TextureWrap, 10 | pub(crate) format : TextureFormat, 11 | pub(crate) width : u32, 12 | pub(crate) height : u32, 13 | pub(crate) file : Option<&'a str>, 14 | pub(crate) data : Option, 15 | pub(crate) context : &'a Context, 16 | } 17 | 18 | impl<'a> TextureBuilder<'a> { 19 | /// Sets a width for the texture. 20 | pub fn width(mut self: Self, width: u32) -> Self { 21 | self.width = width; 22 | self 23 | } 24 | /// Sets a height for the texture. 25 | pub fn height(mut self: Self, height: u32) -> Self { 26 | self.height = height; 27 | self 28 | } 29 | /// Sets dimensions for the texture. 30 | pub fn dimensions(mut self: Self, dimensions: T) -> Self where Point2: From { 31 | let dimensions = Point2::::from(dimensions); 32 | self.width = dimensions.0; 33 | self.height = dimensions.1; 34 | self 35 | } 36 | /// Sets a minification filter for the texture. 37 | pub fn minify(mut self: Self, minify: TextureFilter) -> Self { 38 | self.minify = minify; 39 | self 40 | } 41 | /// Sets a magnification filter for the texture. 42 | pub fn magnify(mut self: Self, magnify: TextureFilter) -> Self { 43 | self.magnify = magnify; 44 | self 45 | } 46 | /// Sets a wrapping type for the texture. 47 | pub fn wrap(mut self: Self, wrap: TextureWrap) -> Self { 48 | self.wrap = wrap; 49 | self 50 | } 51 | /// Sets an internal format for the texture. 52 | pub fn format(mut self: Self, format: TextureFormat) -> Self { 53 | self.format = format; 54 | self 55 | } 56 | pub fn file(mut self: Self, file: &'a str) -> Self { 57 | self.file = Some(file); 58 | self 59 | } 60 | /// Returns the constructed Texture instance. 61 | pub fn build(self: Self) -> Result { 62 | Texture::from_builder(self) 63 | } 64 | pub(crate) fn new<'b>(context: &'b Context) -> TextureBuilder { 65 | TextureBuilder { 66 | context : context, 67 | minify : TextureFilter::Linear, 68 | magnify : TextureFilter::Linear, 69 | wrap : TextureWrap::Clamp, 70 | format : TextureFormat::F16F16F16F16, 71 | width : 1, 72 | height : 1, 73 | file : None, 74 | data : None, 75 | } 76 | } 77 | } -------------------------------------------------------------------------------- /src/core/color.rs: -------------------------------------------------------------------------------- 1 | use prelude::*; 2 | use core::{Uniform, AsUniform}; 3 | 4 | /// A color value consisting of four floating point values for the color channels red, green, blue 5 | /// and alpha. 6 | /// 7 | /// Various drawing methods accept color instances to be used as multiplicators in the drawing 8 | /// process, e.g. [`Sprite::draw()`](struct.Sprite.html#method.draw) allows multiplying the sprite- 9 | /// texture's color channels by given color. 10 | #[cfg_attr(feature = "serialize-serde", derive(Deserialize, Serialize))] 11 | #[derive(Copy, Clone, Debug, Default, PartialEq)] 12 | pub struct Color(pub f32, pub f32, pub f32, pub f32); 13 | 14 | impl Color { 15 | 16 | pub const TRANSPARENT: Color = Color(0.0, 0.0, 0.0, 0.0); 17 | pub const WHITE: Color = Color(1.0, 1.0, 1.0, 1.0); 18 | pub const BLACK: Color = Color(0.0, 0.0, 0.0, 1.0); 19 | pub const RED: Color = Color(1.0, 0.0, 0.0, 1.0); 20 | pub const GREEN: Color = Color(0.0, 1.0, 0.0, 1.0); 21 | pub const BLUE: Color = Color(0.0, 0.0, 1.0, 1.0); 22 | pub const YELLOW: Color = Color(1.0, 1.0, 0.0, 1.0); 23 | pub const CYAN: Color = Color(0.0, 1.0, 1.0, 1.0); 24 | pub const MAGENTA: Color = Color(1.0, 0.0, 1.0, 1.0); 25 | 26 | /// Creates a new instance with color channels set to one and the alpha channel set to given value. 27 | pub fn alpha(alpha: f32) -> Color { 28 | Color(1.0, 1.0, 1.0, alpha) 29 | } 30 | 31 | /// Creates a new instance with color channels set to zero and the alpha channel set to given value. 32 | pub fn alpha_mask(alpha: f32) -> Color { 33 | Color(0.0, 0.0, 0.0, alpha) 34 | } 35 | 36 | /// Creates a new instance with all channels set to given value. 37 | pub fn alpha_pm(alpha: f32) -> Color { 38 | Color(alpha, alpha, alpha, alpha) 39 | } 40 | 41 | /// Creates a new instance with color channels set to given value and the alpha channel set to one. 42 | pub fn lightness(value: f32) -> Color { 43 | Color(value, value, value, 1.0) 44 | } 45 | 46 | /// Creates a new instance from given HSL (range 0.0 - 1.0) 47 | pub fn from_hsl(hue: f32, saturation: f32, lightness: f32, alpha: f32) -> Color { 48 | if saturation == 0.0 { 49 | Color(lightness, lightness, lightness, alpha) 50 | } else { 51 | let q = if lightness < 0.5 { 52 | lightness * (1.0 + saturation) 53 | } else { 54 | lightness + saturation - lightness * saturation 55 | }; 56 | let p = 2.0 * lightness - q; 57 | Color( 58 | Self::hue_to_rgb(p, q, hue + 1.0 / 3.0), 59 | Self::hue_to_rgb(p, q, hue), 60 | Self::hue_to_rgb(p, q, hue - 1.0 / 3.0), 61 | alpha 62 | ) 63 | } 64 | } 65 | 66 | /// Creates a new instance from given color-temperature (~1000 to ~40000). 67 | /// 68 | /// Based on http://www.tannerhelland.com/4435/convert-temperature-rgb-algorithm-code/ 69 | pub fn from_temperature(temperature: f32, alpha: f32) -> Color { 70 | 71 | let value = (temperature / 100.0).floor(); 72 | let red; 73 | let green; 74 | let blue; 75 | 76 | if value <= 66.0 { 77 | red = 255; 78 | green = (99.4708025861 * value.ln() - 161.1195681661) as i32; 79 | } else { 80 | red = (329.698727466 * (value - 60.0).powf(-0.1332047592)) as i32; 81 | green = (288.1221695283 * (value - 60.0).powf(-0.0755148492)) as i32; 82 | } 83 | 84 | if value >= 66.0 { 85 | blue = 255; 86 | } else if value <= 19.0 { 87 | blue = 0; 88 | } else { 89 | blue = (138.5177312231 * (value - 10.0).ln() - 305.0447927307) as i32; 90 | } 91 | 92 | Color( 93 | cmp::max(0, cmp::min(255, red)) as f32 / 255.0, 94 | cmp::max(0, cmp::min(255, green)) as f32 / 255.0, 95 | cmp::max(0, cmp::min(255, blue)) as f32 / 255.0, 96 | alpha 97 | ) 98 | } 99 | 100 | /// Returns value of the instance's red channel. 101 | pub fn r(self: &Self) -> f32 { 102 | self.0 103 | } 104 | 105 | /// Returns value of the instance's green channel. 106 | pub fn g(self: &Self) -> f32 { 107 | self.1 108 | } 109 | 110 | /// Returns value of the instance's blue channel. 111 | pub fn b(self: &Self) -> f32 { 112 | self.2 113 | } 114 | 115 | /// Returns value of the instance's alpha channel. 116 | pub fn a(self: &Self) -> f32 { 117 | self.3 118 | } 119 | 120 | /// Sets the instance's channels from another color object. 121 | pub fn set(self: &mut Self, value: Color) { 122 | self.0 = value.0; 123 | self.1 = value.1; 124 | self.2 = value.2; 125 | self.3 = value.3; 126 | } 127 | 128 | /// Sets a value for the instance's red channel 129 | pub fn set_r(self: &mut Self, value: f32) -> &mut Color { 130 | self.0 = value; 131 | self 132 | } 133 | 134 | /// Sets a value for the instance's green channel. 135 | pub fn set_g(self: &mut Self, value: f32) -> &mut Color { 136 | self.1 = value; 137 | self 138 | } 139 | 140 | /// Sets a value for the instance's blue channel. 141 | pub fn set_b(self: &mut Self, value: f32) -> &mut Color { 142 | self.2 = value; 143 | self 144 | } 145 | 146 | /// Sets a value for the instance's alpha channel. 147 | pub fn set_a(self: &mut Self, value: f32) -> &mut Color { 148 | self.3 = value; 149 | self 150 | } 151 | 152 | /// Multiplies the instance's color channels by given scaling factor. Does not modify alpha. 153 | pub fn scale(self: &mut Self, scaling: f32) -> &mut Color { 154 | self.0 *= scaling; 155 | self.1 *= scaling; 156 | self.2 *= scaling; 157 | self 158 | } 159 | 160 | // Returns new instance with alpha applied to all color channels. 161 | pub fn to_pm(self: &Self) -> Color { 162 | Color(self.0 * self.3, self.1 * self.3, self.2 * self.3, self.3) 163 | } 164 | 165 | /// Hue to rgb helper function uses by hsl. 166 | fn hue_to_rgb(p: f32, q: f32, mut hue: f32) -> f32 { 167 | if hue < 0.0 { 168 | hue += 1.0; 169 | } 170 | if hue > 1.0 { 171 | hue -= 1.0; 172 | } 173 | if hue < 1.0 / 6.0 { 174 | return p + (q - p) * 6.0 * hue; 175 | } 176 | if hue < 1.0 / 2.0 { 177 | return q; 178 | } 179 | if hue < 2.0 / 3.0 { 180 | return p + (q - p) * (2.0/3.0 - hue) * 6.0; 181 | } 182 | return p; 183 | } 184 | } 185 | 186 | impl From<(T, T, T, T)> for Color where f32: From { 187 | fn from(source: (T, T, T, T)) -> Self { 188 | Color(source.0.into(), source.1.into(), source.2.into(), source.3.into()) 189 | } 190 | } 191 | 192 | impl From<[ T; 4 ]> for Color where T: Copy, f32: From { 193 | fn from(source: [ T; 4 ]) -> Self { 194 | Color(source[0].into(), source[1].into(), source[2].into(), source[3].into()) 195 | } 196 | } 197 | 198 | impl From for [ f32; 4 ] { 199 | fn from(source: Color) -> Self { 200 | [ source.0, source.1, source.2, source.3 ] 201 | } 202 | } 203 | 204 | impl<'a> From<&'a Color> for [ f32; 4 ] { 205 | fn from(source: &'a Color) -> Self { 206 | [ source.0, source.1, source.2, source.3 ] 207 | } 208 | } 209 | 210 | impl From for (f32, f32, f32, f32) { 211 | fn from(source: Color) -> Self { 212 | (source.0, source.1, source.2, source.3) 213 | } 214 | } 215 | 216 | impl<'a> From<&'a Color> for (f32, f32, f32, f32) { 217 | fn from(source: &'a Color) -> Self { 218 | (source.0, source.1, source.2, source.3) 219 | } 220 | } 221 | 222 | impl AsUniform for Color { 223 | fn as_uniform(self: &Self) -> Uniform { 224 | Uniform::Vec4([ self.0, self.1, self.2, self.3 ]) 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /src/core/context.rs: -------------------------------------------------------------------------------- 1 | use core::{self, font, SpriteData, Vertex}; 2 | use prelude::*; 3 | use std::default::Default; 4 | use backends::backend; 5 | 6 | /// Number of texture buckets. Also requires change to renderer.rs at "let uniforms = uniform! { ... }" 7 | pub const NUM_BUCKETS: usize = 6; 8 | 9 | /// Initial sprite capacity. Automatically increases. 10 | pub const INITIAL_CAPACITY: usize = 512; 11 | 12 | /// Texture generation (increases each cleanup) 13 | static GENERATION: AtomicUsize = ATOMIC_USIZE_INIT; 14 | 15 | /// A thread-safe render-context. 16 | /// 17 | /// Contains data relating to one or more windows and associated resources. 18 | #[derive(Clone)] 19 | pub struct Context (Arc>); 20 | 21 | unsafe impl Send for Context { } 22 | unsafe impl Sync for Context { } 23 | 24 | impl Debug for Context { 25 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 26 | write!(f, "Context") 27 | } 28 | } 29 | 30 | impl Context { 31 | /// Creates a new context for use with multiple displays. For a single display use cases, simply create the display and obtain 32 | /// its context via `Display::context()` 33 | pub fn new() -> Context { 34 | let context_data = ContextData::new(); 35 | Context(Arc::new(Mutex::new(context_data))) 36 | } 37 | /// Prunes no longer used textures. Requires all layers to be cleared before 38 | /// adding new sprites or rendering the layer. 39 | pub fn prune(self: &Self) { 40 | self.lock().prune(); 41 | } 42 | /// Mutex-locks the instance and returns the MutexGuard 43 | pub(crate) fn lock<'a>(self: &'a Self) -> MutexGuard<'a, ContextData> { 44 | self.0.lock().unwrap() 45 | } 46 | } 47 | 48 | /// Individual Texture. 49 | #[derive(Clone)] 50 | pub struct RawFrame { 51 | pub data : Vec, 52 | pub width : u32, 53 | pub height : u32, 54 | pub channels: u8, 55 | } 56 | 57 | /// A weak reference back to a sprite. 58 | struct SpriteBackRef (Weak); 59 | 60 | impl SpriteBackRef { 61 | /// Creates a new weak reference to SpriteData. 62 | fn new(data: Weak) -> Self { 63 | SpriteBackRef(data) 64 | } 65 | /// Returns a strong reference to the SpriteData. 66 | fn upgrade(self: &Self) -> Option> { 67 | self.0.upgrade() 68 | } 69 | /// Returns the texture id-range used by the referenced sprite or None, if it dropped. 70 | fn range(self: &Self) -> Option<(usize, usize)> { 71 | if let Some(data) = self.upgrade() { 72 | Some((data.texture_id.load(Ordering::Relaxed), data.num_frames as usize * data.components as usize)) 73 | } else { 74 | None 75 | } 76 | } 77 | } 78 | 79 | /// Texture data for a single texture array. 80 | pub struct RawFrameArray { 81 | pub dirty : bool, 82 | pub data : backend::Texture2dArray, 83 | pub raw : Vec, 84 | sprites : Vec, 85 | } 86 | 87 | impl RawFrameArray { 88 | fn new(context: &backend::Context) -> Self { 89 | RawFrameArray { 90 | dirty : false, 91 | data : backend::Texture2dArray::new(context, &Vec::new()), 92 | raw : Vec::new(), 93 | sprites : Vec::new(), 94 | } 95 | } 96 | /// Store given frames to texture arrays. 97 | pub fn store_frames<'a>(self: &mut Self, raw_frames: Vec) -> u32 { 98 | let texture_id = self.raw.len() as u32; 99 | for frame in raw_frames { 100 | self.raw.push(frame); 101 | } 102 | self.dirty = true; 103 | texture_id 104 | } 105 | /// Stores a weak sprite reference in the context so that the sprite's texture_id can be updated after a cleanup. 106 | pub fn store_sprite(self: &mut Self, sprite_data: Weak) { 107 | self.sprites.push(SpriteBackRef::new(sprite_data)); 108 | } 109 | /// Updates texture array in video memory. 110 | fn update(self: &mut Self, context: &backend::Context) { 111 | if self.dirty { 112 | self.dirty = false; 113 | self.data = backend::Texture2dArray::new(context, &self.raw); 114 | } 115 | } 116 | /// Returns a list of tuples containing current sprite texture_id and required negative offset. 117 | fn create_prune_map(self: &Self) -> Option> { 118 | let mut mapping = self.sprites.iter().filter_map(|sprite| sprite.range()).collect::>(); 119 | mapping.sort_by_key(|a| a.0); 120 | let mut num_items = 0; 121 | for i in 0..mapping.len() { 122 | let items = mapping[i].1; 123 | mapping[i].1 = mapping[i].0 - num_items; 124 | num_items += items; 125 | } 126 | if mapping.len() > 0 { Some(mapping) } else { None } 127 | } 128 | // Shrinks raw data array using given prune-map. Returns hashmap mapping old texture index -> new texture index. 129 | fn prune_raw_textures(self: &mut Self, mapping: &Vec<(usize, usize)>) -> HashMap { 130 | let new_size = self.raw.len() - mapping.last().unwrap().1; 131 | let mut destination_map = HashMap::new(); 132 | for m in 0..mapping.len() { 133 | destination_map.insert(mapping[m].0, mapping[m].0 - mapping[m].1); 134 | let end = if m + 1 < mapping.len() { mapping[m+1].0 } else { new_size -1 }; 135 | for i in (mapping[m].0)..end { 136 | let destination_index = i - mapping[m].1; 137 | self.raw.swap(i, destination_index); 138 | } 139 | } 140 | self.raw.truncate(new_size); 141 | destination_map 142 | } 143 | // Runs func on all sprites still referenced, removes unreferenced sprites from list. 144 | fn prune_sprites(self: &mut Self, mut func: T) where T: FnMut(&Arc) { 145 | let mut removed = Vec::new(); 146 | for (i, sprite) in self.sprites.iter().enumerate() { 147 | if let Some(sprite) = sprite.upgrade() { 148 | func(&sprite); 149 | } else { 150 | removed.push(i); 151 | } 152 | } 153 | for index in removed.iter().rev() { 154 | self.sprites.swap_remove(*index); 155 | } 156 | } 157 | /// Prunes no longer used textures from the array and update sprite texture ids and generations. 158 | fn prune(self: &mut Self, context: &backend::Context, generation: usize) { 159 | if let Some(mapping) = self.create_prune_map() { 160 | // Remove unused textures from raw data. 161 | let destination_map = self.prune_raw_textures(&mapping); 162 | self.dirty = true; 163 | self.update(context); 164 | // Update sprite texture ids and generation. 165 | self.prune_sprites(|sprite| { 166 | let texture_id = sprite.texture_id.load(Ordering::Relaxed); 167 | if let Some(new_texture_id) = destination_map.get(&texture_id) { 168 | sprite.texture_id.store(*new_texture_id, Ordering::Relaxed); 169 | } 170 | sprite.generation.store(generation, Ordering::Relaxed); 171 | }); 172 | } else { 173 | // Texure ids have not changed, simply update generation. 174 | self.prune_sprites(|sprite| { 175 | sprite.generation.store(generation, Ordering::Relaxed); 176 | }) 177 | } 178 | } 179 | } 180 | 181 | /// Internal data of a Context 182 | pub struct ContextData { 183 | pub backend_context : Option, 184 | pub tex_arrays : Vec, 185 | pub font_cache_dimensions: u32, 186 | pub font_cache : font::FontCache, 187 | pub font_texture : Option, 188 | pub single_rect : [core::Vertex; 4], 189 | generation : usize, 190 | } 191 | 192 | impl ContextData { 193 | 194 | /// Initializes the backend context. Happens lazily once the first window is created. 195 | fn init_backend(self: &mut Self, display: &backend::Display) { 196 | 197 | let backend_context = backend::Context::new(display, INITIAL_CAPACITY); 198 | 199 | // sprite texture arrays 200 | 201 | for _ in 0..NUM_BUCKETS { 202 | self.tex_arrays.push(RawFrameArray::new(&backend_context)); 203 | } 204 | 205 | // font cache texture 206 | 207 | let data = core::RawFrame { 208 | width : self.font_cache_dimensions, 209 | height : self.font_cache_dimensions, 210 | data : vec![0u8; self.font_cache_dimensions as usize * self.font_cache_dimensions as usize], 211 | channels: 1, 212 | }; 213 | 214 | let texture = backend::Texture2d::new(&backend_context, 0, 0, core::TextureFormat::U8, Some(data)); 215 | 216 | self.font_texture = Some(texture); 217 | self.backend_context = Some(backend_context); 218 | } 219 | 220 | /// Create a new instance 221 | fn new() -> Self { 222 | 223 | let font_cache_dimensions = 512; 224 | 225 | ContextData { 226 | backend_context : None, 227 | tex_arrays : Vec::new(), 228 | font_cache : font::FontCache::new(font_cache_dimensions, font_cache_dimensions, 0.01, 0.01), 229 | font_texture : None, 230 | font_cache_dimensions, 231 | single_rect : Self::create_single_rect(), 232 | generation : Self::create_generation(), 233 | } 234 | } 235 | 236 | /// Returns whether the context has already been associated with a display as required by some backends. 237 | pub fn has_primary_display(self: &Self) -> bool { 238 | self.backend_context.is_some() 239 | } 240 | 241 | /// Associates the context with a display as required by some backends. 242 | pub fn set_primary_display(self: &mut Self, display: &backend::Display) { 243 | self.init_backend(&display); 244 | } 245 | 246 | /// Returns the context's generation. 247 | pub fn generation(self: &Self) -> usize { 248 | self.generation 249 | } 250 | 251 | /// Update font-texture from cache 252 | pub fn update_font_cache(self: &Self) { 253 | self.font_cache.update(self.font_texture.as_ref().unwrap()); 254 | } 255 | 256 | /// Update texture arrays from registered textures 257 | pub fn update_tex_array(self: &mut Self) { 258 | for ref mut array in self.tex_arrays.iter_mut() { 259 | array.update(self.backend_context.as_ref().unwrap()); 260 | } 261 | } 262 | 263 | /// Store given frames to texture arrays 264 | pub fn store_frames(self: &mut Self, bucket_id: u32, raw_frames: Vec) -> u32 { 265 | self.tex_arrays[bucket_id as usize].store_frames(raw_frames) 266 | } 267 | 268 | /// Stores a weak sprite reference in the context so that the sprite's texture_id can be updated after a cleanup. 269 | pub fn store_sprite(self: &mut Self, bucket_id: u32, sprite_data: Weak) { 270 | self.tex_arrays[bucket_id as usize].store_sprite(sprite_data); 271 | } 272 | 273 | /// Prunes no longer used textures for all texture arrays. 274 | fn prune(self: &mut Self) { 275 | self.generation = Self::create_generation(); 276 | for array in self.tex_arrays.iter_mut() { 277 | array.prune(self.backend_context.as_ref().unwrap(), self.generation); 278 | } 279 | } 280 | 281 | /// creates a single rectangle vertex buffer 282 | fn create_single_rect() -> [core::Vertex; 4] { 283 | [ 284 | Vertex { position: [ 0.0, 0.0 ], texture_uv: [ 0.0, 1.0 ], ..Vertex::default() }, 285 | Vertex { position: [ 1.0, 0.0 ], texture_uv: [ 1.0, 1.0 ], ..Vertex::default() }, 286 | Vertex { position: [ 0.0, 1.0 ], texture_uv: [ 0.0, 0.0 ], ..Vertex::default() }, 287 | Vertex { position: [ 1.0, 1.0 ], texture_uv: [ 1.0, 0.0 ], ..Vertex::default() }, 288 | ] 289 | } 290 | 291 | // Creates a new generation and returns it 292 | fn create_generation() -> usize { 293 | // needs to start at 1 as 0 has special meaning 294 | GENERATION.fetch_add(1, Ordering::Relaxed) + 1 295 | } 296 | } 297 | -------------------------------------------------------------------------------- /src/core/display.rs: -------------------------------------------------------------------------------- 1 | use prelude::*; 2 | use core::*; 3 | use core::builder::*; 4 | use backends::backend; 5 | 6 | /// A target to render to, e.g. a window or full screen. 7 | #[derive(Clone)] 8 | pub struct Display { 9 | pub(crate) handle: backend::Display, 10 | pub(crate) context: Context, 11 | pub(crate) frame: Rc>>, 12 | pub(crate) input_data: Arc>, 13 | pub(crate) fullscreen: Rc>>, 14 | } 15 | 16 | impl Debug for Display { 17 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 18 | write!(f, "Display") 19 | } 20 | } 21 | 22 | impl Display { 23 | 24 | /// Returns a [display builder](support/struct.DisplayBuilder.html) for display construction. 25 | /// 26 | /// # Examples 27 | /// 28 | /// ```rust 29 | /// # use radiant_rs::*; 30 | /// let display = Display::builder().dimensions((640, 480)).vsync().title("Window!").build().unwrap(); 31 | /// ``` 32 | pub fn builder() -> DisplayBuilder { 33 | DisplayBuilder::new() 34 | } 35 | 36 | /// Sets the window title. 37 | pub fn set_title(self: &Self, title: &str) { 38 | self.handle.set_title(title); 39 | } 40 | 41 | /// Makes the previously hidden window visible. 42 | pub fn show(self: &Self) { 43 | self.handle.show(); 44 | } 45 | 46 | /// Hides the window. 47 | pub fn hide(self: &Self) { 48 | self.handle.hide(); 49 | } 50 | 51 | /// Switches to fullscreen mode on the primary monitor. 52 | pub fn set_fullscreen(self: &Self, monitor: Option) -> Result<()> { 53 | 54 | let target = if let Some(given_monitor) = monitor { 55 | given_monitor 56 | } else if let Some(default_monitor) = backend::MonitorIterator::new().next() { 57 | Monitor::new(default_monitor) 58 | } else { 59 | return Err(Error::Failed); 60 | }; 61 | 62 | if !self.handle.set_fullscreen(Some(target.clone())) { 63 | self.handle.set_fullscreen(None); 64 | Err(Error::FullscreenError("Failed to switch to fullscreen.".to_string())) 65 | } else { 66 | *self.fullscreen.borrow_mut() = Some(target); 67 | Ok(()) 68 | } 69 | } 70 | 71 | /// Switches to windowed mode. 72 | pub fn set_windowed(self: &Self) { 73 | self.handle.set_fullscreen(None); 74 | *self.fullscreen.borrow_mut() = None; 75 | } 76 | 77 | /// Switches between fullscreen and windowed mode. 78 | pub fn toggle_fullscreen(self: &Self, monitor: Option) -> Result<()> { 79 | if self.fullscreen.borrow().is_some() { 80 | self.set_windowed(); 81 | Ok(()) 82 | } else { 83 | self.set_fullscreen(monitor) 84 | } 85 | } 86 | 87 | /// Returns an input object for this display, used for retrieving polled inputs. 88 | pub fn input(self: &Self) -> Input { 89 | Input { input_data: self.input_data.clone() } 90 | } 91 | 92 | /// Prepares a frame for rendering. 93 | pub fn prepare_frame(self: &Self) { 94 | if self.frame.borrow().is_some() { 95 | panic!("Current frame needs to be swapped before a new frame can be prepared."); 96 | } 97 | *self.frame.borrow_mut() = Some(self.handle.draw()); 98 | } 99 | 100 | /// Prepares a frame for rendering and clears it. 101 | pub fn clear_frame(self: &Self, color: Color) { 102 | self.prepare_frame(); 103 | if let Some(ref mut frame) = self.frame.borrow_mut().as_mut() { 104 | frame.clear(color); 105 | } else { 106 | panic!("Failed to prepare a frame for clear."); 107 | } 108 | } 109 | 110 | /// Swaps current drawing frame with visible frame. 111 | pub fn swap_frame(self: &Self) { 112 | let frame = mem::replace(&mut *self.frame.borrow_mut(), None); 113 | if let Some(frame) = frame { 114 | frame.finish(); 115 | } else { 116 | panic!("No frame currently prepared, nothing to swap."); 117 | } 118 | } 119 | 120 | /// Enables cursor grab mode. While in this mode, the mouse cursor will be hidden and 121 | /// constrained to the window. 122 | /// 123 | /// Grab mode will be temporarily released when the window loses focus and automatically 124 | /// restored once it regains focus. 125 | pub fn grab_cursor(self: &Self) { 126 | let mut input_data = self.input_data.write().unwrap(); 127 | if input_data.has_focus { 128 | self.handle.set_cursor_state(CursorState::Grab); 129 | } 130 | input_data.cursor_grabbed = true; 131 | } 132 | 133 | /// Hides the mouse cursor while it is inside the window. 134 | pub fn hide_cursor(self: &Self) { 135 | self.handle.set_cursor_state(CursorState::Hide); 136 | self.input_data.write().unwrap().cursor_grabbed = false; 137 | } 138 | 139 | /// Releases a previously grabbed or hidden cursor and makes it visible again. 140 | pub fn free_cursor(self: &Self) { 141 | self.handle.set_cursor_state(CursorState::Normal); 142 | self.input_data.write().unwrap().cursor_grabbed = false; 143 | } 144 | 145 | /// Sets the mouse cursor position. 146 | pub fn set_cursor_position(self: &Self, position: Point2) { 147 | self.handle.set_cursor_position(position); 148 | } 149 | 150 | /// Returns the window dimensions. 151 | pub fn dimensions(self: &Self) -> Point2 { 152 | self.handle.framebuffer_dimensions() 153 | } 154 | 155 | /// Returns a vector of available monitors. 156 | pub fn monitors() -> Vec { 157 | let iter = backend::MonitorIterator::new(); 158 | let mut result = Vec::::new(); 159 | for monitor in iter { 160 | result.push(Monitor::new(monitor)); 161 | } 162 | result 163 | } 164 | 165 | /// Polls for events like keyboard or mouse input and changes to the window. See 166 | /// [`Input`](struct.Input.html) for basic keyboard and mouse support. 167 | pub fn poll_events(self: &Self) -> &Self { 168 | let mut input_data = self.input_data.write().unwrap(); 169 | input_data.reset(); 170 | self.handle.poll_events(|event| { 171 | match event { 172 | Event::KeyboardInput(key_id, down) => { 173 | let currently_down = match input_data.key[key_id] { 174 | InputState::Down | InputState::Pressed | InputState::Repeat => true, 175 | _ => false 176 | }; 177 | if !currently_down && down { 178 | input_data.key[key_id] = InputState::Pressed; 179 | } else if currently_down && !down { 180 | input_data.key[key_id] = InputState::Released; 181 | } else if currently_down && down { 182 | input_data.key[key_id] = InputState::Repeat; 183 | } 184 | }, 185 | Event::MouseDelta(x, y) => { 186 | input_data.mouse_delta = (x, y); 187 | }, 188 | Event::MousePosition(x, y) => { 189 | input_data.mouse = (x, y); 190 | }, 191 | Event::MouseInput(button_id, down) => { 192 | let currently_down = match input_data.button[button_id] { 193 | InputState::Down | InputState::Pressed => true, 194 | _ => false 195 | }; 196 | if !currently_down && down { 197 | input_data.button[button_id] = InputState::Pressed 198 | } else if currently_down && !down { 199 | input_data.button[button_id] = InputState::Released 200 | } 201 | }, 202 | Event::Focus => { 203 | input_data.has_focus = true; 204 | // restore grab after focus loss 205 | if input_data.cursor_grabbed { 206 | self.handle.set_cursor_state(CursorState::Grab); 207 | } 208 | } 209 | Event::Blur => { 210 | input_data.has_focus = false; 211 | self.handle.set_cursor_state(CursorState::Normal); 212 | } 213 | Event::Close => { 214 | input_data.should_close = true; 215 | } 216 | } 217 | }); 218 | 219 | input_data.dimensions = self.handle.window_dimensions().into(); 220 | 221 | self 222 | } 223 | 224 | /// Returns true once after the attached window was closed 225 | pub fn was_closed(self: &Self) -> bool { 226 | let mut input_data = self.input_data.write().unwrap(); 227 | let result = input_data.should_close; 228 | input_data.should_close = false; 229 | result 230 | } 231 | 232 | // Returns the context associated with this display. 233 | pub fn context(self: &Self) -> &Context { 234 | &self.context 235 | } 236 | 237 | /// Creates a new instance from given [`DisplayBuilder`](support/struct.DisplayBuilder.html). 238 | pub(crate) fn new(descriptor: DisplayBuilder) -> Result { 239 | 240 | // Reuse existing context or create new one 241 | 242 | let context = if let Some(existing_context) = descriptor.context.clone() { 243 | existing_context 244 | } else { 245 | Context::new() 246 | }; 247 | 248 | // Remember fullscreen state, create a new display for use with this context 249 | 250 | let fullscreen = descriptor.monitor.clone(); 251 | let display = backend::Display::new(descriptor)?; 252 | 253 | // Set primary context display to first created display 254 | // (this has no relevance to radiant but is to satisfy backend requirements) 255 | 256 | { 257 | let mut context = context.lock(); 258 | if !context.has_primary_display() { 259 | context.set_primary_display(&display); 260 | } 261 | } 262 | 263 | Ok(Display { 264 | handle : display, 265 | context : context, 266 | frame : Rc::new(RefCell::new(None)), 267 | input_data : Arc::new(RwLock::new(InputData::new())), 268 | fullscreen : Rc::new(RefCell::new(fullscreen)), 269 | }) 270 | } 271 | 272 | /// Provides a mutable reference to the backend frame to the given function. 273 | pub(crate) fn frame(self: &Self, func: T) where T: FnOnce(&mut backend::Frame) { 274 | let mut frame = self.frame.borrow_mut(); 275 | func(frame.as_mut().expect(NO_FRAME_PREPARED)); 276 | } 277 | } 278 | 279 | impl AsRenderTarget for Display { 280 | fn as_render_target(self: &Self) -> RenderTarget { 281 | RenderTarget::frame(&self.frame) 282 | } 283 | } 284 | 285 | /// The current state of the mouse cursor. 286 | #[derive(Debug)] 287 | pub enum CursorState { 288 | Normal, 289 | Hide, 290 | Grab, 291 | } 292 | 293 | // An input event. 294 | #[derive(Debug, PartialEq)] 295 | pub enum Event { 296 | KeyboardInput(usize, bool), 297 | MouseInput(usize, bool), 298 | MouseDelta(i32, i32), 299 | MousePosition(i32, i32), 300 | Focus, 301 | Blur, 302 | Close, 303 | } 304 | -------------------------------------------------------------------------------- /src/core/layer.rs: -------------------------------------------------------------------------------- 1 | use prelude::*; 2 | use avec; 3 | use core::{blendmodes, BlendMode, context, Color, Program, Vertex}; 4 | use core::math::*; 5 | 6 | static LAYER_COUNTER: AtomicUsize = ATOMIC_USIZE_INIT; 7 | 8 | /// A drawing surface for text and sprites that implements send+sync and is wait-free for drawing operations. 9 | /// 10 | /// In radiant_rs, sprite drawing happens on layers. Layers provide transformation capabilities in 11 | /// the form of model- and view-matrices and the layer's blendmode and color determine 12 | /// how sprites are rendered to the drawing target. Layers can be rendered multiple times using 13 | /// different matrices, blendmodes or colors without having to redraw their contents first. 14 | /// 15 | /// For convenience, layers are created with a view-matrix that maps the given dimensions to the 16 | /// entirety of the drawing target. The layer itself is infinite though, and can be transformed at any 17 | /// time before rendering. 18 | /// 19 | /// Drawing to a layer is a wait-free atomic operation that can be safely performed from multiple threads at 20 | /// the same time. Modifying layer properties like the matrices may cause other threads to wait. 21 | #[derive(Debug)] 22 | pub struct Layer { 23 | view_matrix : Mutex>, 24 | model_matrix : Mutex>, 25 | blend : Mutex, 26 | color : Mutex, 27 | contents : Arc, 28 | program : Option, 29 | } 30 | 31 | unsafe impl Send for Layer { } 32 | unsafe impl Sync for Layer { } 33 | 34 | impl Clone for Layer { 35 | /// Creates a new layer that references the contents of this layer but has its own 36 | /// color, blendmode and set of matrices. 37 | fn clone(self: &Self) -> Self { 38 | self.create_clone(None) 39 | } 40 | } 41 | 42 | /// Layer contents, shared among layer clones. 43 | struct LayerContents { 44 | vertex_data : avec::AVec, 45 | dirty : AtomicBool, 46 | generation : AtomicUsize, 47 | layer_id : usize, 48 | } 49 | 50 | impl Debug for LayerContents { 51 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 52 | f.debug_struct("LayerContents") 53 | .field("num_sprites", &(self.vertex_data.len() / 4)) 54 | .field("dirty", &self.dirty) 55 | .field("generation", &self.generation) 56 | .field("layer_id", &self.layer_id) 57 | .finish() 58 | } 59 | } 60 | 61 | impl Layer { 62 | 63 | /// Creates a new layer with given dimensions, meaning that is is created with 64 | /// a view matrix that maps the given dimensions to the entirety of the drawing target. 65 | pub fn new(dimensions: T) -> Self where Point2: From { 66 | Self::create(dimensions, None) 67 | } 68 | 69 | /// Creates a new layer with given dimensions and fragment program. 70 | pub fn with_program(dimensions: T, program: Program) -> Self where Point2: From { 71 | Self::create(dimensions, Some(program)) 72 | } 73 | 74 | /// Creates a new layer that references the contents of this layer but has its own 75 | /// color, blendmode, program and set of matrices. 76 | pub fn clone_with_program(self: &Self, program: Program) -> Self { 77 | self.create_clone(Some(program)) 78 | } 79 | 80 | /// Sets a global color multiplicator. Setting this to white means that the layer contents 81 | /// are renderered in their original colors. 82 | /// 83 | /// Note that [`Colors`](struct.Color.html) contain 84 | /// alpha information and are not clamped to any range, so it is possible to use an overbright 85 | /// color to brighten the result or use the alpha channel to apply global transparency. 86 | pub fn set_color(self: &Self, color: Color) -> &Self { 87 | self.color().set(color); 88 | self 89 | } 90 | 91 | /// Returns a mutex guarded mutable reference to the global color multiplicator. 92 | pub fn color(self: &Self) -> MutexGuard { 93 | self.color.lock().unwrap() 94 | } 95 | 96 | /// Sets the view matrix. 97 | /// 98 | /// View matrix transformation is applied after the objects are fully positioned on the layer. 99 | /// As a result, manipulating the view matrix has the effect of manipulating the layer itself, 100 | /// e.g. rotating the entire layer. 101 | pub fn set_view_matrix(self: &Self, matrix: T) -> &Self where Mat4: From { 102 | self.view_matrix().set(&matrix.into()); 103 | self 104 | } 105 | 106 | /// Returns a mutex guarded mutable reference to the view matrix. 107 | /// See [`set_view_matrix()`](#method.set_view_matrix) for a description of the view matrix. 108 | pub fn view_matrix(self: &Self) -> MutexGuard> { 109 | self.view_matrix.lock().unwrap() 110 | } 111 | 112 | /// Sets the model matrix. 113 | /// 114 | /// Model matrix transformation is applied before each object is transformed to its position 115 | /// on the layer. As a result, manipulating the model matrix has the effect of manipulating 116 | /// every object on the layer in the same way, e.g. rotating every individual object on the 117 | /// layer around a point relative to the individual object. 118 | pub fn set_model_matrix(self: &Self, matrix: T) -> &Self where Mat4: From { 119 | self.model_matrix().set(&matrix.into()); 120 | self 121 | } 122 | 123 | /// Returns a mutex guarded mutable reference to the model matrix. 124 | /// See [`set_model_matrix()`](#method.set_model_matrix) for a description of the model matrix. 125 | pub fn model_matrix(self: &Self) -> MutexGuard> { 126 | self.model_matrix.lock().unwrap() 127 | } 128 | 129 | /// Sets the blendmode. 130 | pub fn set_blendmode(self: &Self, blendmode: BlendMode) -> &Self { 131 | *self.blendmode() = blendmode; 132 | self 133 | } 134 | 135 | /// Returns a mutex guarded mutable reference to the blendmode. 136 | pub fn blendmode(self: &Self) -> MutexGuard { 137 | self.blend.lock().unwrap() 138 | } 139 | 140 | /// Removes all previously added objects from the layer. Typically invoked after the layer has 141 | /// been rendered. 142 | pub fn clear(self: &Self) -> &Self { 143 | self.set_dirty(true); 144 | self.set_generation(0); 145 | self.contents.vertex_data.clear(); 146 | self 147 | } 148 | 149 | /// Returns the number of sprites the layer can hold without having to perform a blocking reallocation. 150 | pub fn capacity(self: &Self) -> usize { 151 | self.contents.vertex_data.capacity() / 4 152 | } 153 | 154 | /// Returns the number of sprites currently stored the layer. 155 | pub fn len(self: &Self) -> usize { 156 | self.contents.vertex_data.len() / 4 157 | } 158 | 159 | /// Returns the layer wrapped in an std::Arc. 160 | pub fn arc(self: Self) -> Arc { 161 | Arc::new(self) 162 | } 163 | 164 | /// Draws a rectangle on given layer. 165 | pub(crate) fn add_rect(self: &Self, generation: Option, bucket_id: u8, texture_id: u32, components: u8, uv: Rect, pos: Point2, anchor: Point2, dim: Point2, color: Color, rotation: f32, scale: Point2) { 166 | 167 | self.set_dirty(true); 168 | if generation.is_some() && !self.set_generation(generation.unwrap()) { 169 | panic!("Layer contains garbage data. Note: Layers need to be cleared after performing a Context::prune()."); 170 | } 171 | 172 | // corner positions relative to x/y 173 | 174 | let offset_x0 = -anchor.0 * scale.0; 175 | let offset_x1 = (dim.0 - anchor.0) * scale.0; 176 | let offset_y0 = -anchor.1 * scale.1; 177 | let offset_y1 = (dim.1 - anchor.1) * scale.1; 178 | 179 | let bucket_id = bucket_id as u32; 180 | let components = components as u32; 181 | 182 | // get vertex_data slice and draw into it 183 | 184 | let map = self.contents.vertex_data.map(4); 185 | 186 | map.set(0, Vertex { 187 | position : [pos.0, pos.1], 188 | offset : [offset_x0, offset_y0], 189 | rotation : rotation, 190 | color : color.into(), 191 | bucket_id : bucket_id, 192 | texture_id : texture_id, 193 | texture_uv : uv.top_left().as_array(), 194 | components : components, 195 | }); 196 | 197 | map.set(1, Vertex { 198 | position : [pos.0, pos.1], 199 | offset : [offset_x1, offset_y0], 200 | rotation : rotation, 201 | color : color.into(), 202 | bucket_id : bucket_id, 203 | texture_id : texture_id, 204 | texture_uv : uv.top_right().as_array(), 205 | components : components, 206 | }); 207 | 208 | map.set(2, Vertex { 209 | position : [pos.0, pos.1], 210 | offset : [offset_x0, offset_y1], 211 | rotation : rotation, 212 | color : color.into(), 213 | bucket_id : bucket_id, 214 | texture_id : texture_id, 215 | texture_uv : uv.bottom_left().as_array(), 216 | components : components, 217 | }); 218 | 219 | map.set(3, Vertex { 220 | position : [pos.0, pos.1], 221 | offset : [offset_x1, offset_y1], 222 | rotation : rotation, 223 | color : color.into(), 224 | bucket_id : bucket_id, 225 | texture_id : texture_id, 226 | texture_uv : uv.bottom_right().as_array(), 227 | components : components, 228 | }); 229 | } 230 | 231 | /// Returns a reference to the program used by this layer. 232 | pub fn program(self: &Self) -> Option<&Program> { 233 | self.program.as_ref() 234 | } 235 | 236 | /// Returns the readguard protected vertex data. 237 | pub(crate) fn vertices(self: &Self) -> avec::AVecReadGuard { 238 | self.contents.vertex_data.get() 239 | } 240 | 241 | /// Returns the layer id. 242 | pub(crate) fn id(self: &Self) -> usize { 243 | self.contents.layer_id 244 | } 245 | 246 | /// Flags the layer as no longer dirty and returns whether it was dirty. 247 | pub(crate) fn undirty(self: &Self) -> bool { 248 | self.contents.dirty.swap(false, Ordering::Relaxed) 249 | } 250 | 251 | /// Creates a new layer 252 | fn create(dimensions: T, program: Option) -> Self where Point2: From { 253 | let dimensions = Point2::from(dimensions); 254 | Layer { 255 | view_matrix : Mutex::new(Mat4::viewport(dimensions.0, dimensions.1).into()), 256 | model_matrix : Mutex::new(Mat4::identity().into()), 257 | blend : Mutex::new(blendmodes::ALPHA), 258 | color : Mutex::new(Color::WHITE), 259 | contents : Arc::new(LayerContents { 260 | vertex_data : avec::AVec::new(context::INITIAL_CAPACITY * 4), 261 | dirty : AtomicBool::new(true), 262 | generation : AtomicUsize::new(0), 263 | layer_id : 1 + LAYER_COUNTER.fetch_add(1, Ordering::Relaxed), 264 | }), 265 | program : program, 266 | } 267 | } 268 | 269 | /// Creates a clone. 270 | fn create_clone(self: &Self, program: Option) -> Self { 271 | Layer { 272 | view_matrix : Mutex::new(self.view_matrix().clone().into()), 273 | model_matrix : Mutex::new(self.model_matrix().clone().into()), 274 | blend : Mutex::new(self.blendmode().clone()), 275 | color : Mutex::new(self.color().clone()), 276 | contents : self.contents.clone(), 277 | program : program, 278 | } 279 | } 280 | 281 | /// Sets or unsets the layer content generation. A generation can only be set 282 | /// if the current generation is unset (generation=0). Returns true on success. 283 | fn set_generation(self: &Self, generation: usize) -> bool { 284 | let previous = self.contents.generation.swap(generation, Ordering::Relaxed); 285 | previous == generation || generation == 0 || previous == 0 286 | } 287 | 288 | /// Sets or unsets the layers dirty state 289 | fn set_dirty(self: &Self, value: bool) { 290 | self.contents.dirty.store(value, Ordering::Relaxed); 291 | } 292 | } 293 | 294 | -------------------------------------------------------------------------------- /src/core/math.rs: -------------------------------------------------------------------------------- 1 | use prelude::*; 2 | use core::{AsUniform, Uniform}; 3 | 4 | /// A 4x4 matrix. 5 | pub type Mat4 = [ [ T; 4 ]; 4 ]; 6 | 7 | pub trait Mat4Trait { 8 | fn identity() -> Mat4; 9 | fn viewport(width: T, height: T) -> Mat4; 10 | fn set(self: &mut Self, other: &Mat4); 11 | } 12 | 13 | impl Mat4Trait for Mat4 { 14 | fn identity() -> Mat4 { 15 | [ 16 | [ 1., 0., 0., 0. ], 17 | [ 0., 1., 0., 0. ], 18 | [ 0., 0., 1., 0. ], 19 | [ 0., 0., 0., 1. ], 20 | ] 21 | } 22 | fn viewport(width: f32, height: f32) -> Mat4 { 23 | [ 24 | [ 2. / width, 0., 0., 0. ], 25 | [ 0., -2. / height, 0., 0. ], 26 | [ 0., 0., 1., 0. ], 27 | [ -1., 1., 0., 1. ], 28 | ] 29 | } 30 | fn set(self: &mut Self, other: &Mat4) { 31 | *self = *other; 32 | } 33 | } 34 | 35 | impl AsUniform for Mat4 { 36 | fn as_uniform(&self) -> Uniform { 37 | let a = &self; 38 | Uniform::Mat4([ 39 | [ a[0][0], a[0][1], a[0][2], a[0][3] ], 40 | [ a[1][0], a[1][1], a[1][2], a[1][3] ], 41 | [ a[2][0], a[2][1], a[2][2], a[2][3] ], 42 | [ a[3][0], a[3][1], a[3][2], a[3][3] ], 43 | ]) 44 | } 45 | } 46 | 47 | impl AsUniform for Mat4 { 48 | fn as_uniform(&self) -> Uniform { 49 | let a = &self; 50 | Uniform::DoubleMat4([ 51 | [ a[0][0], a[0][1], a[0][2], a[0][3] ], 52 | [ a[1][0], a[1][1], a[1][2], a[1][3] ], 53 | [ a[2][0], a[2][1], a[2][2], a[2][3] ], 54 | [ a[3][0], a[3][1], a[3][2], a[3][3] ], 55 | ]) 56 | } 57 | } 58 | 59 | /// A stack of 4x4 matrices. 60 | #[derive(Debug)] 61 | pub struct Mat4Stack (Vec>); 62 | 63 | impl Mat4Stack where T: Copy { 64 | 65 | /// Creates a new matrix stack. 66 | pub fn new(matrix: Mat4) -> Mat4Stack { 67 | Mat4Stack(vec![ matrix ]) 68 | } 69 | 70 | /// Pushes a copy of the current matrix on the stack and returns a reference to it. 71 | pub fn push(self: &mut Self) -> &mut Mat4 { 72 | let last = *self.0.last().unwrap(); 73 | self.0.push(last); 74 | self.0.last_mut().unwrap() 75 | } 76 | 77 | /// Removes the top matrix from the stack and replaces the current matrix with it. 78 | pub fn pop(self: &mut Self) -> &mut Mat4 { 79 | self.0.pop().unwrap(); 80 | self.0.last_mut().unwrap() 81 | } 82 | } 83 | 84 | impl Deref for Mat4Stack where T: Copy { 85 | type Target = Mat4; 86 | 87 | fn deref(&self) -> &Mat4 { 88 | self.0.last().unwrap() 89 | } 90 | } 91 | 92 | impl DerefMut for Mat4Stack where T: Copy { 93 | fn deref_mut(&mut self) -> &mut Mat4 { 94 | self.0.last_mut().unwrap() 95 | } 96 | } 97 | 98 | impl From> for Mat4Stack where T: Copy { 99 | fn from(source: Mat4) -> Self { 100 | Mat4Stack::new(source) 101 | } 102 | } 103 | 104 | /// A point in 2d space. 105 | pub type Point2 = (T, T); 106 | 107 | pub trait Point2Trait { 108 | fn x(self: &Self) -> T; 109 | fn y(self: &Self) -> T; 110 | fn as_array(self: &Self) -> [ T; 2 ]; 111 | } 112 | 113 | impl Point2Trait for Point2 where T: Copy { 114 | #[inline] 115 | fn x(self: &Self) -> T { 116 | self.0 117 | } 118 | #[inline] 119 | fn y(self: &Self) -> T { 120 | self.1 121 | } 122 | #[inline] 123 | fn as_array(self: &Self) -> [ T; 2 ] { 124 | [ self.0, self.1 ] 125 | } 126 | } 127 | 128 | /// A rectangle. 129 | pub type Rect = (Point2, Point2); 130 | 131 | pub trait RectTrait { 132 | fn top_left(self: &Self) -> Point2; 133 | fn top_right(self: &Self) -> Point2; 134 | fn bottom_left(self: &Self) -> Point2; 135 | fn bottom_right(self: &Self) -> Point2; 136 | fn as_array(self: &Self) -> [ T; 4 ]; 137 | } 138 | 139 | impl RectTrait for Rect where T: Copy { 140 | #[inline] 141 | fn top_left(self: &Self) -> Point2 { 142 | ((self.0).0, (self.0).1) 143 | } 144 | #[inline] 145 | fn top_right(self: &Self) -> Point2 { 146 | ((self.1).0, (self.0).1) 147 | } 148 | #[inline] 149 | fn bottom_left(self: &Self) -> Point2 { 150 | ((self.0).0, (self.1).1) 151 | } 152 | #[inline] 153 | fn bottom_right(self: &Self) -> Point2 { 154 | ((self.1).0, (self.1).1) 155 | } 156 | #[inline] 157 | fn as_array(self: &Self) -> [ T; 4 ] { 158 | [ (self.0).0, (self.0).1, (self.1).0, (self.1).1 ] 159 | } 160 | } -------------------------------------------------------------------------------- /src/core/mod.rs: -------------------------------------------------------------------------------- 1 | mod blendmode; 2 | mod display; 3 | mod input; 4 | mod layer; 5 | mod renderer; 6 | mod sprite; 7 | mod font; 8 | mod context; 9 | mod color; 10 | mod monitor; 11 | mod texture; 12 | mod program; 13 | mod uniform; 14 | mod postprocessor; 15 | mod builder; 16 | mod rendertarget; 17 | mod math; 18 | 19 | pub use self::blendmode::*; 20 | pub use self::input::*; 21 | pub use self::display::*; 22 | pub use self::sprite::*; 23 | pub use self::renderer::*; 24 | pub use self::font::*; 25 | pub use self::layer::*; 26 | pub use self::context::*; 27 | pub use self::color::*; 28 | pub use self::monitor::*; 29 | pub use self::texture::*; 30 | pub use self::program::*; 31 | pub use self::uniform::*; 32 | pub use self::postprocessor::*; 33 | pub use self::builder::*; 34 | pub use self::rendertarget::*; 35 | pub use self::math::*; 36 | use image; 37 | use prelude::*; 38 | use backends::backend; 39 | 40 | /// A vertex. 41 | #[derive(Copy, Clone, Debug, Default)] 42 | pub struct Vertex { 43 | pub position : [f32; 2], 44 | pub offset : [f32; 2], 45 | pub rotation : f32, 46 | pub color : (f32, f32, f32, f32), 47 | pub bucket_id : u32, 48 | pub texture_id : u32, 49 | pub texture_uv : [f32; 2], 50 | pub components : u32, 51 | } 52 | 53 | /// Radiant errors. 54 | #[derive(Debug)] 55 | pub enum Error { 56 | ImageError(String), 57 | ShaderError(String), 58 | IoError(io::Error), 59 | FullscreenError(String), 60 | FontError(String), 61 | BackendError(backend::Error), 62 | Failed, 63 | } 64 | 65 | impl From for Error { 66 | /// Converts io error to radiant error 67 | fn from(error: io::Error) -> Error { 68 | Error::IoError(error) 69 | } 70 | } 71 | 72 | impl From for Error { 73 | fn from(error: backend::Error) -> Error { 74 | Error::BackendError(error) 75 | } 76 | } 77 | 78 | impl From for Error { 79 | /// Converts image error to radiant error 80 | fn from(error: image::ImageError) -> Error { 81 | use image::ImageError; 82 | match error { 83 | ImageError::IoError(error) => { Error::IoError(error) } 84 | ImageError::FormatError(error) => { Error::ImageError(format!("Image format error: {}", error)) } 85 | ImageError::UnsupportedError(error) => { Error::ImageError(format!("Image unsupported: {}", error)) } 86 | ImageError::UnsupportedColor(_) => { Error::ImageError("Unsupported colorformat".to_string()) } 87 | _ => { Error::ImageError("Unknown image error".to_string()) } 88 | } 89 | } 90 | } 91 | 92 | /// Radiant result. 93 | pub type Result = result::Result; 94 | 95 | /// Converts Srgb to rgb and multiplies image color channels with alpha channel 96 | pub fn convert_color(mut image: image::RgbaImage) -> image::RgbaImage { 97 | use palette::Srgb; 98 | //use palette::pixel::Srgb; 99 | for (_, _, pixel) in image.enumerate_pixels_mut() { 100 | let alpha = pixel[3] as f32 / 255.0; 101 | let rgb = Srgb::new( 102 | pixel[0] as f32 / 255.0, 103 | pixel[1] as f32 / 255.0, 104 | pixel[2] as f32 / 255.0 105 | ).into_linear(); 106 | pixel[0] = (alpha * rgb.red * 255.0) as u8; 107 | pixel[1] = (alpha * rgb.green * 255.0) as u8; 108 | pixel[2] = (alpha * rgb.blue * 255.0) as u8; 109 | } 110 | image 111 | } -------------------------------------------------------------------------------- /src/core/monitor.rs: -------------------------------------------------------------------------------- 1 | use prelude::*; 2 | use backends::backend; 3 | 4 | /// An individual monitor, returned from [`Display::monitors()`](struct.Display.html#method.monitors). 5 | #[derive(Clone)] 6 | pub struct Monitor { 7 | pub(crate) inner: backend::Monitor, 8 | } 9 | 10 | impl fmt::Debug for Monitor { 11 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 12 | write!(f, "Monitor") 13 | } 14 | } 15 | 16 | impl Monitor { 17 | pub(crate) fn new(monitor: backend::Monitor) -> Self { 18 | Self { 19 | inner: monitor 20 | } 21 | } 22 | 23 | /// Returns the name of the device. 24 | pub fn name(self: &Self) -> String { 25 | self.inner.get_name().unwrap_or("".to_string()) 26 | } 27 | 28 | /// Returns the current width in pixels. 29 | pub fn width(self: &Self) -> u32 { 30 | let (width, _) = self.inner.get_dimensions().into(); 31 | width 32 | } 33 | 34 | /// Returns the current height in pixels. 35 | pub fn height(self: &Self) -> u32 { 36 | let (_, height) = self.inner.get_dimensions().into(); 37 | height 38 | } 39 | 40 | /// Returns the current width and height in pixels. 41 | pub fn dimensions(self: &Self) -> (u32, u32) { 42 | self.inner.get_dimensions().into() 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/core/postprocessor/basic.rs: -------------------------------------------------------------------------------- 1 | use core::{Texture, Renderer, Context, Program, BlendMode, Postprocessor, Color, Point2}; 2 | 3 | /// A basic postprocessor that applies a Program to the given input once. 4 | /// 5 | /// Postprocessors are used with [`Renderer::postprocess()`](../struct.Renderer.html#method.postprocess). 6 | /// The associated type for this postprocessor is `BlendMode` and is expected as second argument 7 | /// to the `postprocess()` method 8 | /// 9 | /// # Examples 10 | /// 11 | /// ```rust 12 | /// # use radiant_rs::*; 13 | /// # let display = Display::builder().hidden().build().unwrap(); 14 | /// # let renderer = Renderer::new(&display).unwrap(); 15 | /// # let context = display.context(); 16 | /// # let my_layer = Layer::new((1.0, 1.0)); 17 | /// # let program_source = "#version 140\nout vec4 f_color;\nvoid main() { f_color = vec4(0.0, 0.0, 0.0, 0.0); }"; 18 | /// // Load a shader progam. 19 | /// let my_program = Program::from_string(&context, &program_source).unwrap(); 20 | /// 21 | /// // Create the postprocessor with the program. 22 | /// let my_postprocessor = postprocessors::Basic::new(&context, my_program, display.dimensions()); 23 | /// 24 | /// // ... in your renderloop... 25 | /// # display.prepare_frame(); 26 | /// renderer.postprocess(&my_postprocessor, &blendmodes::ALPHA, || { 27 | /// renderer.clear(Color::BLACK); 28 | /// renderer.draw_layer(&my_layer, 0); 29 | /// }); 30 | /// # display.swap_frame(); 31 | /// ``` 32 | pub struct Basic { 33 | source : Texture, 34 | program : Program, 35 | } 36 | 37 | impl Postprocessor for Basic { 38 | /// The Basic postprocessor accepts a blendmode as argument to `Renderer::postprocess()`. 39 | type T = BlendMode; 40 | fn target(self: &Self) -> &Texture { 41 | &self.source 42 | } 43 | fn draw(self: &Self, renderer: &Renderer, blendmode: &Self::T) { 44 | renderer.fill().blendmode(*blendmode).program(&self.program).texture(&self.source).draw(); 45 | } 46 | } 47 | 48 | impl Basic { 49 | /// Creates a new instance. The shader can use `sheet*()` to access the input texture. 50 | pub fn new(context: &Context, program: Program, dimensions: T) -> Self where Point2: From { 51 | 52 | let (width, height) = Point2::::from(dimensions); 53 | 54 | let result = Basic { 55 | source : Texture::new(&context, width, height), 56 | program : program, 57 | }; 58 | 59 | result.source.clear(Color::TRANSPARENT); 60 | result 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/core/postprocessor/bloom.rs: -------------------------------------------------------------------------------- 1 | use core::*; 2 | use std::sync::Mutex; 3 | use std::mem::swap; 4 | use std::cmp::{min, max}; 5 | 6 | /// A simple bloom postprocessor. 7 | /// 8 | /// This effect internally uses textures of decreasing dimensions to amplify an initially small blur effect 9 | /// via linear interpolation performed by the gpu when scaling texture contents. 10 | pub struct Bloom { 11 | targets : [[Texture; 5]; 2], 12 | blur_program : Mutex, 13 | combine_program : Program, 14 | /// Number of blur iterations. 15 | pub iterations : u8, 16 | /// Blendmode to use in blur iterations. 17 | pub iter_blend : BlendMode, 18 | /// Blendmode to use for the final drawing operation. 19 | pub draw_blend : BlendMode, 20 | /// Color multiplicator for the final drawing operation. 21 | pub draw_color : Color, 22 | /// Clear internal textures before processing. 23 | pub clear : bool, 24 | /// Number of scaling steps used vertically. Limited to 5. 25 | pub vertical : u8, 26 | /// Number of scaling steps used horizontally. Limited to 5. 27 | pub horizontal : u8, 28 | } 29 | 30 | impl Postprocessor for Bloom { 31 | type T = (); 32 | 33 | /// Returns the target where the postprocessor expects the unprocessed input. 34 | fn target(self: &Self) -> &Texture { 35 | if self.clear { 36 | let horizontal = min(self.horizontal as usize, self.targets[0].len()); 37 | let vertical = min(self.vertical as usize, self.targets[0].len()); 38 | let spread = max(horizontal, vertical); 39 | for i in 0..spread as usize { 40 | self.targets[1][i].clear(Color::TRANSPARENT); 41 | } 42 | self.targets[0][0].clear(Color::TRANSPARENT); 43 | } 44 | &self.targets[0][0] 45 | } 46 | 47 | /// Process received data. 48 | fn process(self: &Self, renderer: &Renderer, _: &Self::T) { 49 | 50 | let horizontal = min(self.horizontal as usize, self.targets[0].len()); 51 | let vertical = min(self.vertical as usize, self.targets[0].len()); 52 | let spread = max(horizontal, vertical); 53 | 54 | // Copy to progressively smaller textures 55 | for i in 1..spread as usize { 56 | renderer.render_to(&self.targets[0][i], || { 57 | renderer.copy_from(&self.targets[0][i-1], TextureFilter::Linear); 58 | }); 59 | } 60 | 61 | let mut blur = self.blur_program.lock().unwrap(); 62 | let blur = blur.deref_mut(); 63 | let mut dst = 1; 64 | let mut src = 0; 65 | 66 | for _ in 0..self.iterations { 67 | 68 | // Apply horizontal blur 69 | if horizontal > 0 { 70 | blur.set_uniform("horizontal", &true); 71 | for i in 0..spread as usize { 72 | renderer.render_to(&self.targets[dst][i], || { 73 | let fill = renderer.fill().blendmode(self.iter_blend).texture(&self.targets[src][i]); 74 | if i < horizontal { 75 | fill.program(&blur).draw(); 76 | } else { 77 | fill.draw(); 78 | } 79 | }); 80 | } 81 | swap(&mut dst, &mut src); 82 | } 83 | 84 | // Apply vertical blur 85 | if vertical > 0 { 86 | blur.set_uniform("horizontal", &false); 87 | for i in 0..spread as usize { 88 | renderer.render_to(&self.targets[dst][i], || { 89 | let fill = renderer.fill().blendmode(self.iter_blend).texture(&self.targets[src][i]); 90 | if i < vertical { 91 | fill.program(&blur).draw(); 92 | } else { 93 | fill.draw(); 94 | } 95 | }); 96 | } 97 | swap(&mut dst, &mut src); 98 | } 99 | } 100 | } 101 | 102 | /// Draw processed input. The renderer has already set the correct target. 103 | fn draw(self: &Self, renderer: &Renderer, _: &Self::T) { 104 | renderer.fill().blendmode(self.draw_blend).color(self.draw_color).program(&self.combine_program).draw(); 105 | } 106 | } 107 | 108 | impl Bloom { 109 | /// Creates a new Bloom effect instance. 110 | /// The base texture for this effect uses the given dimensions. For each additional texture 111 | /// the dimensions are divided by `divider_factor`. 112 | pub fn new(context: &Context, dimensions: T, divider_factor: u32) -> Self where Point2: From { 113 | 114 | let dimensions = Point2::::from(dimensions); 115 | let blur_program = Program::from_string(&context, include_str!("../../shader/postprocess/blur.fs")).unwrap(); 116 | let mut combine_program = Program::from_string(&context, include_str!("../../shader/postprocess/combine.fs")).unwrap(); 117 | let targets = Self::create_targets(context, dimensions, divider_factor); 118 | let max_ops = targets[0].len(); 119 | 120 | combine_program.set_uniform("sample0", &targets[0][0]); 121 | combine_program.set_uniform("sample1", &targets[0][1]); 122 | combine_program.set_uniform("sample2", &targets[0][2]); 123 | combine_program.set_uniform("sample3", &targets[0][3]); 124 | combine_program.set_uniform("sample4", &targets[0][4]); 125 | 126 | Bloom { 127 | blur_program : Mutex::new(blur_program), 128 | combine_program : combine_program, 129 | targets : targets, 130 | iterations : 3, 131 | iter_blend : blendmodes::COPY, 132 | draw_blend : blendmodes::ADD, 133 | draw_color : Color::WHITE, 134 | clear : true, 135 | vertical : max_ops as u8, 136 | horizontal : max_ops as u8, 137 | } 138 | } 139 | 140 | /// Rebuilds internal textures to given dimensions. 141 | pub fn rebuild(self: &mut Self, context: &Context, dimensions: T, divider_factor: u32) where Point2: From { 142 | let targets = Self::create_targets(context, Point2::::from(dimensions), divider_factor); 143 | self.combine_program.set_uniform("sample0", &targets[0][0]); 144 | self.combine_program.set_uniform("sample1", &targets[0][1]); 145 | self.combine_program.set_uniform("sample2", &targets[0][2]); 146 | self.combine_program.set_uniform("sample3", &targets[0][3]); 147 | self.combine_program.set_uniform("sample4", &targets[0][4]); 148 | self.targets = targets; 149 | } 150 | 151 | /// Create scaling textures. 152 | fn create_targets(context: &Context, (width, height): Point2, divider_factor: u32) -> [[Texture; 5]; 2] { 153 | 154 | let builder = Texture::builder(context).format(TextureFormat::F16F16F16F16); 155 | 156 | let f0 = 1; 157 | let f1 = f0 * divider_factor; 158 | let f2 = f1 * divider_factor; 159 | let f3 = f2 * divider_factor; 160 | let f4 = f3 * divider_factor; 161 | 162 | [ [ 163 | builder.clone().dimensions((width / f0, height / f0)).build().unwrap(), 164 | builder.clone().dimensions((width / f1, height / f1)).build().unwrap(), 165 | builder.clone().dimensions((width / f2, height / f2)).build().unwrap(), 166 | builder.clone().dimensions((width / f3, height / f3)).build().unwrap(), 167 | builder.clone().dimensions((width / f4, height / f4)).build().unwrap(), 168 | ], [ 169 | builder.clone().dimensions((width / f0, height / f0)).build().unwrap(), 170 | builder.clone().dimensions((width / f1, height / f1)).build().unwrap(), 171 | builder.clone().dimensions((width / f2, height / f2)).build().unwrap(), 172 | builder.clone().dimensions((width / f3, height / f3)).build().unwrap(), 173 | builder.clone().dimensions((width / f4, height / f4)).build().unwrap(), 174 | ] ] 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /src/core/postprocessor/mod.rs: -------------------------------------------------------------------------------- 1 | use super::{Texture, Renderer}; 2 | 3 | /// A custom postprocessor. 4 | /// 5 | /// Postprocessing happens in three steps: 6 | /// 7 | /// - first, `target()` is invoked and expected to return an input texture target (to 8 | /// which the user will draw the input data to be postprocessed). 9 | /// - `process()` is invoked. Any drawing operations performed within will target the 10 | /// input texture. 11 | /// - `draw()` is invoked. Any drawing operations performed within will target the 12 | /// destination defined by the user. 13 | pub trait Postprocessor { 14 | /// Custom type for the args parameter supplied to `process()` and `draw()`. 15 | type T; 16 | /// Expected to return a texture for user drawing operations to target. 17 | fn target(self: &Self) -> &Texture; 18 | /// Optionally expected to processes input data. Draws issued within this function will 19 | /// target the texure returned by `target()` unless overridden via `Renderer::render_to()`. 20 | #[allow(unused_variables)] 21 | fn process(self: &Self, renderer: &Renderer, args: &Self::T) { } 22 | /// Expected to draw the final result. Draws issued within this function will 23 | /// target the rendertarget that the postprocessor is supposed to render to. 24 | fn draw(self: &Self, renderer: &Renderer, args: &Self::T); 25 | } 26 | 27 | mod basic; 28 | mod bloom; 29 | 30 | pub mod postprocessors { 31 | //! A set of predefined postprocessors for use with `Renderer::postprocess()`. 32 | //! 33 | //! Implement [`Postprocessor`](../trait.Postprocessor.html) to define your own postprocessors. 34 | pub use super::basic::*; 35 | pub use super::bloom::*; 36 | } 37 | -------------------------------------------------------------------------------- /src/core/program.rs: -------------------------------------------------------------------------------- 1 | use prelude::*; 2 | use core::{self, Context, AsUniform, UniformList, Color}; 3 | use core::math::*; 4 | use backends::backend; 5 | 6 | const SPRITE_INC: &'static str = include_str!("../shader/sprite.inc.fs"); 7 | const TEXTURE_INC: &'static str = include_str!("../shader/texture.inc.fs"); 8 | const SPRITE_VS: &'static str = include_str!("../shader/sprite.vs"); 9 | const TEXTURE_VS: &'static str = include_str!("../shader/texture.vs"); 10 | 11 | /// A shader program and its uniforms. 12 | /// 13 | /// Cloning a program creates a new program, referencing the internal shaders 14 | /// of the source program but using its own copy of the uniforms. 15 | #[derive(Clone)] 16 | pub struct Program { 17 | pub uniforms: UniformList, 18 | pub(crate) sprite_program: Arc, 19 | pub(crate) texture_program: Arc, 20 | } 21 | 22 | impl Debug for Program { 23 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 24 | f.debug_struct("Program") 25 | .field("uniforms", &self.uniforms) 26 | .finish() 27 | } 28 | } 29 | 30 | impl Program { 31 | /// Creates a program from a fragment shader file. 32 | pub fn from_file(context: &Context, file: &str) -> core::Result { 33 | use std::io::Read; 34 | let mut source = String::new(); 35 | let mut f = File::open(file)?; 36 | f.read_to_string(&mut source)?; 37 | Self::from_string(context, &source) 38 | } 39 | /// Creates a program from a fragment shader string. 40 | pub fn from_string(context: &Context, source: &str) -> core::Result { 41 | Self::new(context, source) 42 | } 43 | /// Sets a uniform value by name. 44 | pub fn set_uniform(self: &mut Self, name: &str, value: &T) where T: AsUniform { 45 | self.uniforms.insert(name, value.as_uniform()); 46 | } 47 | /// Removes a uniform value by name. 48 | pub fn remove_uniform(self: &mut Self, name: &str) -> bool { 49 | self.uniforms.remove(name) 50 | } 51 | /// Creates a new program. Used in context creation when the full context is not yet available. 52 | pub(crate) fn new(context: &Context, source: &str) -> core::Result { 53 | let sprite_fs = Self::insert_template(source, SPRITE_INC); 54 | let texture_fs = Self::insert_template(source, TEXTURE_INC); 55 | let mut uniforms = UniformList::new(); 56 | uniforms.insert("u_view", Mat4::viewport(1.0, 1.0).as_uniform()); 57 | uniforms.insert("u_model", Mat4::::identity().as_uniform()); 58 | uniforms.insert("_rd_color", Color::WHITE.as_uniform()); 59 | let context = context.lock(); 60 | let backend_context = context.backend_context.as_ref().unwrap(); 61 | Ok(Program { 62 | uniforms: uniforms, 63 | sprite_program: Arc::new(backend::Program::new(backend_context, SPRITE_VS, &sprite_fs)?), 64 | texture_program: Arc::new(backend::Program::new(backend_context, TEXTURE_VS, &texture_fs)?), 65 | }) 66 | } 67 | /// Inserts program boilterplate code into the shader source. 68 | fn insert_template(source: &str, template: &str) -> String { 69 | let mut result = String::new(); 70 | let mut lines = source.lines(); 71 | let mut inserted = false; 72 | while let Some(line) = lines.next() { 73 | result.push_str(line); 74 | result.push_str("\n"); 75 | if line.starts_with("#") { 76 | result.push_str(template); 77 | inserted = true; 78 | } 79 | } 80 | assert!(inserted, "Program is missing a version specifier."); 81 | result 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/core/rendertarget.rs: -------------------------------------------------------------------------------- 1 | use prelude::*; 2 | use core::{Texture, Color, TextureFilter, Rect, Point2}; 3 | use backends::backend; 4 | 5 | pub const NO_FRAME_PREPARED: &'static str = "Failed to get frame: None prepared."; 6 | 7 | /// A target for rendering. 8 | pub trait AsRenderTarget { 9 | /// Returns a RenderTarget representing a texture or a frame. 10 | fn as_render_target(self: &Self) -> RenderTarget; 11 | } 12 | 13 | /// An opaque type representing rendering targets like Display or Texture. 14 | #[derive(Clone)] 15 | pub struct RenderTarget(pub(crate) RenderTargetInner); 16 | 17 | impl RenderTarget { 18 | /// Creates a new frame rendertarget. 19 | pub(crate) fn frame(frame: &Rc>>) -> RenderTarget { 20 | RenderTarget(RenderTargetInner::Frame(frame.clone())) 21 | } 22 | /// Creates a new texture rendertarget. 23 | pub(crate) fn texture(texture: &Texture) -> RenderTarget{ 24 | RenderTarget(RenderTargetInner::Texture(texture.clone())) 25 | } 26 | /// Creates a null rendertarget. 27 | pub fn none() -> RenderTarget{ 28 | RenderTarget(RenderTargetInner::None) 29 | } 30 | } 31 | 32 | impl AsRenderTarget for RenderTarget { 33 | fn as_render_target(self: &Self) -> RenderTarget { 34 | self.clone() 35 | } 36 | } 37 | 38 | impl Debug for RenderTarget { 39 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 40 | let inner = match &self.0 { 41 | &RenderTargetInner::None => "None".to_string(), 42 | &RenderTargetInner::Frame(_) => "Frame".to_string(), 43 | &RenderTargetInner::Texture(ref texture) => format!("{:?}", texture) 44 | }; 45 | write!(f, "RenderTarget {{ {:?} }}", inner) 46 | } 47 | } 48 | 49 | /// An enum of render target type instances. 50 | #[derive(Clone)] 51 | pub enum RenderTargetInner { 52 | None, 53 | Frame(Rc>>), 54 | Texture(Texture), 55 | } 56 | 57 | impl RenderTargetInner { 58 | /// Clears the target. 59 | pub fn clear(self: &Self, color: Color) { 60 | match *self { 61 | RenderTargetInner::Frame(ref display) => { 62 | let mut frame = display.borrow_mut(); 63 | frame.as_mut().expect(NO_FRAME_PREPARED).clear(color); 64 | }, 65 | RenderTargetInner::Texture(ref texture) => { 66 | texture.clear(color); 67 | } 68 | RenderTargetInner::None => { } 69 | } 70 | } 71 | /// Returns the dimensions of the target. 72 | pub fn dimensions(self: &Self) -> Point2 { 73 | match *self { 74 | RenderTargetInner::Frame(ref display) => { 75 | let mut frame = display.borrow_mut(); 76 | frame.as_mut().expect(NO_FRAME_PREPARED).dimensions() 77 | }, 78 | RenderTargetInner::Texture(ref texture) => { 79 | texture.dimensions() 80 | } 81 | RenderTargetInner::None => { 82 | (0, 0) 83 | } 84 | } 85 | } 86 | /// Blits a source rect to a rect on the target. 87 | pub fn blit_rect(self: &Self, source: &RenderTarget, source_rect: Rect, target_rect: Rect, filter: TextureFilter) { 88 | match *self { 89 | RenderTargetInner::Frame(ref target_display) => { 90 | match source.0 { 91 | RenderTargetInner::Frame(_) => { 92 | let mut frame = target_display.borrow_mut(); 93 | frame.as_mut().expect(NO_FRAME_PREPARED).copy_rect(source_rect, target_rect, filter); 94 | }, 95 | RenderTargetInner::Texture(ref src_texture) => { 96 | let mut frame = target_display.borrow_mut(); 97 | frame.as_mut().expect(NO_FRAME_PREPARED).copy_rect_from_texture(src_texture, source_rect, target_rect, filter); 98 | } 99 | RenderTargetInner::None => { } 100 | } 101 | }, 102 | RenderTargetInner::Texture(ref target_texture) => { 103 | match source.0 { 104 | RenderTargetInner::Frame(ref src_display) => { 105 | let mut frame = src_display.borrow_mut(); 106 | target_texture.handle.copy_rect_from_frame(frame.as_mut().expect(NO_FRAME_PREPARED), source_rect, target_rect, filter); 107 | }, 108 | RenderTargetInner::Texture(ref src_texture) => { 109 | target_texture.handle.copy_rect_from(src_texture, source_rect, target_rect, filter); 110 | } 111 | RenderTargetInner::None => { } 112 | } 113 | } 114 | RenderTargetInner::None => { } 115 | } 116 | } 117 | /// Blits to the target. 118 | pub fn blit(self: &Self, source: &RenderTarget, filter: TextureFilter) { 119 | match *self { 120 | RenderTargetInner::Frame(ref target_display) => { 121 | match source.0 { 122 | RenderTargetInner::Frame(_) => { /* blitting entire frame to entire frame makes no sense */ }, 123 | RenderTargetInner::Texture(ref src_texture) => { 124 | let mut frame = target_display.borrow_mut(); 125 | frame.as_mut().expect(NO_FRAME_PREPARED).copy_from_texture(src_texture, filter); 126 | } 127 | RenderTargetInner::None => { } 128 | } 129 | }, 130 | RenderTargetInner::Texture(ref target_texture) => { 131 | match source.0 { 132 | RenderTargetInner::Frame(ref src_display) => { 133 | let mut frame = src_display.borrow_mut(); 134 | target_texture.handle.copy_from_frame(frame.as_mut().expect(NO_FRAME_PREPARED), filter); 135 | }, 136 | RenderTargetInner::Texture(ref src_texture) => { 137 | target_texture.handle.copy_from(src_texture, filter); 138 | } 139 | RenderTargetInner::None => { } 140 | } 141 | } 142 | RenderTargetInner::None => { } 143 | } 144 | } 145 | } -------------------------------------------------------------------------------- /src/core/texture.rs: -------------------------------------------------------------------------------- 1 | use prelude::*; 2 | use core::{self, Context, Color, Uniform, AsUniform, RenderTarget, AsRenderTarget, Point2}; 3 | use core::builder::*; 4 | use image::{self, GenericImage}; 5 | use backends::backend; 6 | 7 | /// A texture to draw or draw to. 8 | /// 9 | /// Textures serve as drawing targets for userdefined [`Postprocessors`](trait.Postprocessor.html) 10 | /// or custom [`Programs`](struct.Program.html). A texture can also be drawn with 11 | /// [`Renderer::rect()`](struct.Renderer.html#method.rect). 12 | #[derive(Clone)] 13 | pub struct Texture { 14 | pub(crate) handle : Rc, 15 | pub(crate) minify : TextureFilter, 16 | pub(crate) magnify : TextureFilter, 17 | pub(crate) wrap : TextureWrap, 18 | pub(crate) dimensions : Point2, 19 | } 20 | 21 | impl Debug for Texture { 22 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 23 | f.debug_struct("Texture") 24 | .field("minify", &self.minify) 25 | .field("magnify", &self.magnify) 26 | .field("wrap", &self.wrap) 27 | .field("dimensions", &self.dimensions) 28 | .finish() 29 | } 30 | } 31 | 32 | impl Texture { 33 | /// Returns a texture builder for texture construction. 34 | /// 35 | /// # Examples 36 | /// 37 | /// ```rust 38 | /// # use radiant_rs::*; 39 | /// # let display = Display::builder().hidden().build().unwrap(); 40 | /// # let renderer = Renderer::new(&display).unwrap(); 41 | /// # let context = display.context(); 42 | /// let tex = Texture::builder(&context) 43 | /// .dimensions((640, 480)) 44 | /// .magnify(TextureFilter::Nearest) 45 | /// .minify(TextureFilter::Linear) 46 | /// .build() 47 | /// .unwrap(); 48 | /// ``` 49 | pub fn builder(context: &Context) -> TextureBuilder { 50 | TextureBuilder::new(context) 51 | } 52 | /// Creates a new texture with given dimensions. The texture will use linear interpolation 53 | /// for magnification or minification and internally use the `F16F16F16F16` format. 54 | pub fn new(context: &Context, width: u32, height: u32) -> Self { 55 | Self::builder(context).width(width).height(height).build().unwrap() 56 | } 57 | /// Creates a new texture from given file. 58 | pub fn from_file(context: &Context, file: &str) -> core::Result { 59 | Self::builder(context).file(file).build() 60 | } 61 | /// Creates a new texture with given dimensions and filters. It will internally use the `F16F16F16F16` format. 62 | pub fn filtered(context: &Context, width: u32, height: u32, minify: TextureFilter, magnify: TextureFilter) -> Self { 63 | Self::builder(context).width(width).height(height).minify(minify).magnify(magnify).build().unwrap() 64 | } 65 | /// Clones texture with new filters and wrapping function. Both source and clone reference the same texture data. 66 | pub fn clone_with_options(self: &Self, minify: TextureFilter, magnify: TextureFilter, wrap: TextureWrap) -> Self { 67 | Texture { 68 | handle : self.handle.clone(), 69 | minify : minify, 70 | magnify : magnify, 71 | wrap : wrap, 72 | dimensions : self.dimensions, 73 | } 74 | } 75 | /// Clears the texture with given color. 76 | pub fn clear(self: &Self, color: Color) { 77 | self.handle.clear(color); 78 | } 79 | /// Returns the dimensions of the texture. 80 | pub fn dimensions(self: &Self) -> Point2 { 81 | self.dimensions 82 | } 83 | /// Creates a new texture from given TextureBuilder. 84 | pub(crate) fn from_builder(mut builder: TextureBuilder) -> core::Result { 85 | let mut context = builder.context.lock(); 86 | let context = context.deref_mut(); 87 | if let Some(filename) = builder.file { 88 | let image = image::open(filename)?; 89 | builder.width = image.dimensions().0; 90 | builder.height = image.dimensions().1; 91 | builder.data = Some(core::RawFrame { 92 | data: core::convert_color(image.to_rgba()).into_raw(), 93 | width: builder.width, 94 | height: builder.height, 95 | channels: 4, 96 | }); 97 | } 98 | let texture = backend::Texture2d::new(context.backend_context.as_ref().unwrap(), builder.width, builder.height, builder.format, builder.data); 99 | Ok(Texture { 100 | handle : Rc::new(texture), 101 | minify : builder.minify, 102 | magnify : builder.magnify, 103 | wrap : builder.wrap, 104 | dimensions : (builder.width, builder.height), 105 | }) 106 | } 107 | } 108 | 109 | impl AsRenderTarget for Texture { 110 | fn as_render_target(self: &Self) -> RenderTarget { 111 | RenderTarget::texture(self) 112 | } 113 | } 114 | 115 | impl AsUniform for Texture { 116 | fn as_uniform(self: &Self) -> Uniform { 117 | Uniform::Texture(self.clone()) 118 | } 119 | } 120 | 121 | /// Texture minify- or magnify filtering function. 122 | #[derive(Copy, Clone, Debug, PartialEq)] 123 | pub enum TextureFilter { 124 | /// All nearby texels will be loaded and their values will be merged. 125 | Linear, 126 | /// The nearest texel will be loaded. 127 | Nearest, 128 | } 129 | 130 | /// Texture wrapping function. 131 | #[derive(Copy, Clone, Debug, PartialEq)] 132 | pub enum TextureWrap { 133 | /// Samples at coord x + 1 map to coord x. 134 | Repeat, 135 | /// Samples at coord x + 1 map to coord 1 - x. 136 | Mirror, 137 | /// Samples at coord x + 1 map to coord 1. 138 | Clamp, 139 | /// Same as Mirror, but only for one repetition. 140 | MirrorClamp, 141 | } 142 | 143 | /// Internal texture format. Note that the shader will always see a floating 144 | /// point representation. U[n]* will have their minimum value mapped to 0.0 and 145 | /// their maximum to 1.0. 146 | #[derive(Copy, Clone, Debug, PartialEq)] 147 | pub enum TextureFormat { 148 | U8, 149 | U16, 150 | U8U8, 151 | U16U16, 152 | U10U10U10, 153 | U12U12U12, 154 | U16U16U16, 155 | U2U2U2U2, 156 | U4U4U4U4, 157 | U5U5U5U1, 158 | U8U8U8U8, 159 | U10U10U10U2, 160 | U12U12U12U12, 161 | U16U16U16U16, 162 | I16I16I16I16, 163 | F16, 164 | F16F16, 165 | F16F16F16F16, 166 | F32, 167 | F32F32, 168 | F32F32F32F32, 169 | F11F11F10, 170 | } 171 | -------------------------------------------------------------------------------- /src/core/uniform.rs: -------------------------------------------------------------------------------- 1 | use prelude::*; 2 | use core::texture::Texture; 3 | 4 | /// A uniform value. 5 | /// 6 | /// Uniforms are values that can be passed to [`Programs`](struct.Program.html). 7 | /// Various types also implement the [`AsUniform`](trait.AsUniform.html) trait 8 | /// and can be directly used with [`Program::set_uniform()`](struct.Program.html#method.set_uniform). 9 | #[derive(Clone, Debug)] 10 | pub enum Uniform { 11 | Bool(bool), 12 | SignedInt(i32), 13 | UnsignedInt(u32), 14 | Float(f32), 15 | Mat4([[f32; 4]; 4]), 16 | Vec2([f32; 2]), 17 | Vec3([f32; 3]), 18 | Vec4([f32; 4]), 19 | Double(f64), 20 | DoubleMat4([[f64; 4]; 4]), 21 | DoubleVec2([f64; 2]), 22 | DoubleVec3([f64; 3]), 23 | DoubleVec4([f64; 4]), 24 | Texture(Texture), 25 | } 26 | 27 | /// A value usable as a uniform. 28 | pub trait AsUniform { 29 | fn as_uniform(self: &Self) -> Uniform; 30 | } 31 | 32 | /// Multiple uniforms held by a program. 33 | #[derive(Clone, Debug)] 34 | pub struct UniformList (pub (crate) HashMap); 35 | 36 | impl UniformList { 37 | /// Creates a new uniform list. 38 | pub fn new() -> Self { 39 | UniformList(HashMap::new()) 40 | } 41 | /// Inserts a uniform into the list. 42 | pub fn insert(self: &mut Self, name: &str, uniform: Uniform) { 43 | self.0.insert(name.to_string(), uniform); 44 | } 45 | /// Removes a uniform from the list and returns whether it existed. 46 | pub fn remove(self: &mut Self, name: &str) -> bool { 47 | self.0.remove(name).is_some() 48 | } 49 | } 50 | 51 | impl AsUniform for bool { 52 | fn as_uniform(self: &Self) -> Uniform { 53 | Uniform::Bool(*self) 54 | } 55 | } 56 | 57 | impl AsUniform for i32 { 58 | fn as_uniform(self: &Self) -> Uniform { 59 | Uniform::SignedInt(*self) 60 | } 61 | } 62 | 63 | impl AsUniform for u32 { 64 | fn as_uniform(self: &Self) -> Uniform { 65 | Uniform::UnsignedInt(*self) 66 | } 67 | } 68 | 69 | impl AsUniform for f32 { 70 | fn as_uniform(self: &Self) -> Uniform { 71 | Uniform::Float(*self) 72 | } 73 | } 74 | 75 | impl AsUniform for f64 { 76 | fn as_uniform(self: &Self) -> Uniform { 77 | Uniform::Double(*self) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc(html_logo_url = "https://raw.githubusercontent.com/sinesc/radiant-rs/master/doc/logo.png", 2 | html_favicon_url = "https://raw.githubusercontent.com/sinesc/radiant-rs/master/doc/favicon.png")] 3 | 4 | /*! 5 | Rust sprite rendering engine with a friendly API, wait-free send+sync drawing targets and custom shader support. 6 | 7 | It is intended for use in simple 2d games or prototypes but can also be combined with additional functionality from the backend 8 | library (currently Glium). 9 | 10 | # Examples 11 | 12 | Here is a short [video](https://www.youtube.com/watch?v=EcH1Sl5IuTQ&index=5&list=PLz6zhQmaeK57f67Fjw3GyxUK5gzTzpZbM) of a space 13 | shooter/demo game using Radiant (graphics), Specs (ECS), Rodio (audio), Serde (deserializing levels, entities, ...). GitHub link in the description. 14 | 15 | The examples folder contains multiple small examples. They can be run via `cargo run --example `, e.g. 16 | `cargo run --example demo_blobs` to run demo_blobs.rs. 17 | 18 | # Basic rendering 19 | 20 | 1. Create a [display](struct.Display.html) with `Display::builder()`. This represents the window/screen. **Note:** It is also 21 | possible to use backend window/event handling instead. See further below. 22 | 2. Create a [renderer](struct.Renderer.html) with `Renderer::new()`. It is used to draw to rendertargets like the display. 23 | 3. Grab a [context](struct.Context.html) from the display using the `context()` method. It ties everything together. 24 | 4. Load [sprites](struct.Sprite.html) or [fonts](struct.Font.html) using e.g. `Font::from_file()` or `Sprite::from_file()`. 25 | 5. Create as many drawing [layers](struct.Layer.html) as you need using `Layer::new()`. 26 | 6. Draw to the layer using the `Font::write()` or `Sprite::draw()` methods. 27 | 7. Prepare a new frame and clear it using `Display::clear_frame()` (or `Display::prepare_frame()` if you don't want to clear). 28 | 8. Draw the contents of your layers to the display using `Renderer::draw_layer()`. 29 | 9. Make the frame visible via `Display::swap_frame()`. 30 | 31 | # Multiple windows, shared context 32 | 33 | 1. Create a [context](struct.Context.html) with `Context::new()`. 34 | 2. Create as many displays as are needed using `Display::builder()` while using the builder's `context()` method to specify the 35 | previously created context. 36 | 3. Either... 37 | 1. Create a single headless renderer using `Renderer::headless()` and use `Renderer::render_to()` to render to a specific window or 38 | 2. Create a renderer for each `Display`. 39 | 4. Continue with step 4. from above. 40 | 41 | # Draw to texture/postprocess 42 | 43 | Postprocessors are custom effects that may be as simple as a single shader program or combine multiple shaders and textures into a single 44 | output. 45 | 46 | The renderer has a method [`Renderer::render_to()`](struct.Renderer.html#method.render_to) that accepts a rendertarget (e.g. texture) and a closure. Anything 47 | drawn within the closure will be rendered to the texture. 48 | 49 | Likewise, use [`Renderer::postprocess()`](struct.Renderer.html#method.postprocess) to render using a postprocessor. 50 | 51 | These methods can be combined/nested as shown here: 52 | 53 | ``` 54 | # use radiant_rs::*; 55 | # let display = Display::builder().build().unwrap(); 56 | # let renderer = Renderer::new(&display).unwrap(); 57 | # let layer = Layer::new((1.0, 1.0)); 58 | # let surface = Texture::new(&display.context(), 1, 1); 59 | # let program = Program::from_string(&display.context(), "#version 140\nout vec4 f_color;\nvoid main() { f_color = vec4(0.0, 0.0, 0.0, 0.0); }").unwrap(); 60 | # let p2 = program.clone(); 61 | # let effect1 = postprocessors::Basic::new(&display.context(), program, display.dimensions()); 62 | # let effect2 = postprocessors::Basic::new(&display.context(), p2, display.dimensions()); 63 | # let effect1_arguments = blendmodes::ALPHA; 64 | # let effect2_arguments = blendmodes::ALPHA; 65 | renderer.render_to(&surface, || { 66 | renderer.postprocess(&effect1, &effect1_arguments, || { 67 | renderer.postprocess(&effect2, &effect2_arguments, || { 68 | //... 69 | renderer.draw_layer(&layer, 1); 70 | }); 71 | //... maybe draw here with only effect 1? ... 72 | }); 73 | //... or here without any postprocessor? ... 74 | }); 75 | ``` 76 | 77 | # Sprite-sheets 78 | 79 | Currently sprite-sheets are required to be sheets of one or more either horizontally or vertically aligned sprite frames. Each frame 80 | can have multiple components aligned orthogonally to the frames. Components could be the sprite's color image, a light or distortion 81 | map for the shader etc. 82 | 83 | Sprites can be created from either raw image data and a [`SpriteParameters`](support/struct.SpriteParameters.html) struct describing the 84 | sprite layout, or directly from a file. 85 | When loading from file, filenames are required to express the sprite format, e.g. `battery_lightmapped_128x128x15x2` would be 15 frames 86 | of a 128x128 sprite using two components. This is a scaled version of how it could look. The color component is in the top row, a lightmap 87 | component in the bottom row: 88 | 89 | ![Spritesheet](https://raw.githubusercontent.com/sinesc/radiant-rs/master/doc/spritesheet.png "Spritesheet") 90 | 91 | # Custom shaders 92 | 93 | Radiant supports the use of custom fragment shaders. These are normal glsl shaders. To simplify access to the default 94 | sampler (which might be a sampler2DArray or sampler2D, depending on what is drawn) a wrapper is injected into the 95 | source. The wrapper provides `sheet*()` functions similar to glsl's `texture*()` functions. 96 | This only applies to the default sampler. It is possible to add custom uniforms, including samplers, to your shader 97 | that would be sampled using the `texture*()` functions. 98 | 99 | Available default inputs: 100 | 101 | - `uniform mat4 u_view` The view matrix if applicable, otherwise the identity. 102 | - `uniform mat4 u_model` The model matrix if applicable, otherwise the identity. 103 | - `in vec2 v_tex_coords` Texture coordinates. 104 | - `in vec4 v_color` Color multiplier. For layers this is sprite color * layer color. 105 | 106 | To access the default sampler, the following wrappers are provided: 107 | 108 | - `vec2 sheetSize()` Retrieves the dimensions of the texture. 109 | - `vec4 sheet(in vec2 texture_coords)` Retrieves texels from the texture. 110 | - `vec4 sheetComponent(in vec2 texture_coords, in uint component)` Samples a specific sprite 111 | component instead of the default one set by `Renderer::draw_layer()`. 112 | 113 | Example: (This is the default shader used by radiant.) 114 | 115 | ```text 116 | #version 140 117 | 118 | in vec2 v_tex_coords; 119 | in vec4 v_color; 120 | 121 | out vec4 f_color; 122 | 123 | void main() { 124 | f_color = sheet(v_tex_coords) * v_color; 125 | } 126 | ``` 127 | 128 | # Drawing from multiple threads 129 | 130 | Start with steps 1-5 from the *Basic rendering* list. Then... 131 | 132 | 1. Wrap fonts, sprites, and layers in `Arc`s. 133 | 2. Clone the `Arc`s for each thread that needs their contents. The context can be cloned directly. 134 | 3. Move the clones into the thread. 135 | 4. Draw onto your layers, load sprites etc. from any thread(s). Layers are non-blocking for drawing operations, 136 | blocking for other manipulations (e.g. matrix modification). 137 | 138 | Complete rendering with steps 7-9 from the *Basic rendering* list in the thread that created the `Renderer`; both it 139 | and `Display` do not implement `Send`. 140 | 141 | # Using Radiant with Glium 142 | 143 | The [backend](backend/index.html) module provides various methods to use Radiant along side Glium. The examples glium_less and glium_more 144 | demonstrate two possible approaches. 145 | 146 | Approach "more": Skip creating a Radiant Display and use [`backend::create_renderer()`](backend/fn.create_renderer.html) to create a renderer from a Glium Display. 147 | Then use [`backend::target_frame`](backend/fn.target_frame.html) to direct the renderer to target the given Glium Frame instead. 148 | 149 | Approach "less": Use [`backend::create_display()`](backend/fn.create_display.html) to create a Radiant Display from a Glium Display. Then use 150 | [`backend::take_frame()`](backend/fn.take_frame.html) to "borrow" a Glium Frame from Radiant. This approach let's you keep Radiant's window/event handling. 151 | 152 | # Found and issue? Missing a feature? 153 | 154 | Please file a bug report if you encounter any issues with this library. In particular, it has only been tested on a limited number of graphics cards 155 | so I would expect issues regarding untested hardware. 156 | */ 157 | 158 | #[cfg(feature = "glium")] 159 | #[macro_use] extern crate glium; 160 | #[macro_use] extern crate enum_primitive; 161 | #[macro_use] extern crate lazy_static; 162 | extern crate image; 163 | extern crate regex; 164 | extern crate rusttype; 165 | extern crate unicode_normalization; 166 | extern crate font_loader; 167 | extern crate avec; 168 | extern crate palette; 169 | #[cfg(feature = "serialize-serde")] 170 | extern crate serde; 171 | #[cfg(feature = "serialize-serde")] 172 | #[macro_use] extern crate serde_derive; 173 | 174 | mod prelude; 175 | mod backends; 176 | mod core; 177 | 178 | mod public; 179 | pub use public::*; 180 | -------------------------------------------------------------------------------- /src/prelude.rs: -------------------------------------------------------------------------------- 1 | pub use std::sync::atomic::{AtomicUsize, AtomicBool, Ordering, ATOMIC_USIZE_INIT}; 2 | pub use std::sync::{Arc, Mutex, MutexGuard, RwLock, RwLockReadGuard, RwLockWriteGuard, Weak}; 3 | pub use std::cell::RefCell; 4 | pub use std::rc::Rc; 5 | pub use std::ops::{Neg, Add, AddAssign, Sub, SubAssign, Mul, MulAssign, Div, DivAssign, Deref, DerefMut}; 6 | pub use std::collections::HashMap; 7 | pub use std::path::Path; 8 | pub use std::fmt::Debug; 9 | pub use std::fs::File; 10 | pub use std::cmp::PartialOrd; 11 | pub use std::convert::From; 12 | pub use std::{fmt, cmp, mem, f32, f64, io, result}; 13 | -------------------------------------------------------------------------------- /src/public.rs: -------------------------------------------------------------------------------- 1 | pub use core::{ 2 | BlendMode, BlendingFunction, LinearBlendingFactor, blendmodes, 3 | Display, Monitor, 4 | Renderer, RenderTarget, Context, AsRenderTarget, 5 | Layer, Sprite, Font, Color, 6 | Texture, TextureFormat, TextureFilter, TextureWrap, 7 | Program, Uniform, AsUniform, 8 | Postprocessor, postprocessors, 9 | Input, InputId, InputState, 10 | Result, Error 11 | }; 12 | 13 | pub mod support { 14 | //! Support structures returned by various methods. Usually not required to be created manually. 15 | pub use core::{InputIterator, InputUpIterator, InputDownIterator}; 16 | pub use core::{DrawBuilder, DisplayBuilder, FontBuilder, FontQueryBuilder, TextureBuilder}; 17 | pub use core::{SpriteParameters, SpriteLayout}; 18 | pub use core::Mat4Stack; 19 | } 20 | 21 | pub mod backend { 22 | //! Backend specific integration methods. Backends can be switched via [cargo features](http://doc.crates.io/manifest.html#the-features-section). 23 | //! The documentation shown here depends on the features it was generated with. 24 | pub use backends::backend::public::*; 25 | } -------------------------------------------------------------------------------- /src/shader/default.fs: -------------------------------------------------------------------------------- 1 | #version 150 2 | 3 | in vec2 v_tex_coords; 4 | in vec4 v_color; 5 | 6 | out vec4 f_color; 7 | 8 | void main() { 9 | f_color = sheet(v_tex_coords) * v_color; 10 | //f_color = f_color + vec4(0.5, 0.0, 0.0, 0.0); 11 | } 12 | -------------------------------------------------------------------------------- /src/shader/postprocess/blur.fs: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | in vec2 v_tex_coords; 4 | uniform bool horizontal; 5 | const float weight[3] = float[] (0.3125, 0.375, 0.3125); 6 | out vec4 f_color; 7 | 8 | void main() { 9 | 10 | vec2 offset; 11 | 12 | if (horizontal) { 13 | offset = vec2(1.2 / sheetSize().x, 0.0); 14 | } else { 15 | offset = vec2(0.0, 1.2 / sheetSize().y); 16 | } 17 | 18 | vec2 sample0 = v_tex_coords - offset; 19 | vec2 sample2 = v_tex_coords + offset; 20 | 21 | vec4 color = weight[1] * sheet(v_tex_coords); 22 | 23 | if (sample0.x > offset.x && sample0.y > offset.y) { 24 | color += weight[0] * sheet(sample0); 25 | } 26 | 27 | if (sample2.x < 1.0 - offset.x && sample2.y < 1.0 - offset.y) { 28 | color += weight[2] * sheet(sample2); 29 | } 30 | 31 | //f_color = clamp(color, 0.0, 1.0); 32 | f_color = color; 33 | } 34 | -------------------------------------------------------------------------------- /src/shader/postprocess/combine.fs: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | in vec2 v_tex_coords; 4 | in vec4 v_color; 5 | uniform sampler2D sample0; 6 | uniform sampler2D sample1; 7 | uniform sampler2D sample2; 8 | uniform sampler2D sample3; 9 | uniform sampler2D sample4; 10 | out vec4 f_color; 11 | 12 | void main(void) { 13 | vec4 t0 = texture2D(sample0, v_tex_coords); 14 | vec4 t1 = texture2D(sample1, v_tex_coords); 15 | vec4 t2 = texture2D(sample2, v_tex_coords); 16 | vec4 t3 = texture2D(sample3, v_tex_coords); 17 | vec4 t4 = texture2D(sample4, v_tex_coords); 18 | f_color = clamp((t0 + t1 + t2 + t3 + t4) * v_color, 0.0, 1.0); 19 | //f_color = (t0 + t1 + t2 + t3 + t4) * v_color; 20 | } 21 | -------------------------------------------------------------------------------- /src/shader/sprite.inc.fs: -------------------------------------------------------------------------------- 1 | uniform sampler2D _rd_tex; 2 | uniform sampler2DArray _rd_tex1; 3 | uniform sampler2DArray _rd_tex2; 4 | uniform sampler2DArray _rd_tex3; 5 | uniform sampler2DArray _rd_tex4; 6 | uniform sampler2DArray _rd_tex5; 7 | uniform uint _rd_comp; 8 | 9 | flat in uint _rd_v_texture_id; 10 | flat in uint _rd_v_bucket_id; 11 | flat in uint _rd_v_components; 12 | 13 | ivec2 sheetSize() { 14 | if (_rd_v_bucket_id == 0u) { 15 | return textureSize(_rd_tex, 0); 16 | } else if (_rd_v_bucket_id == 1u) { 17 | return textureSize(_rd_tex1, 0).xy; 18 | } else if (_rd_v_bucket_id == 2u) { 19 | return textureSize(_rd_tex2, 0).xy; 20 | } else if (_rd_v_bucket_id == 3u) { 21 | return textureSize(_rd_tex3, 0).xy; 22 | } else if (_rd_v_bucket_id == 4u) { 23 | return textureSize(_rd_tex4, 0).xy; 24 | } else /*if (_rd_v_bucket_id == 5u)*/ { 25 | return textureSize(_rd_tex5, 0).xy; 26 | } 27 | } 28 | 29 | vec4 sheetComponent(in vec2 texture_coords, in uint component) { 30 | if (_rd_v_bucket_id == 0u) { 31 | return texture(_rd_tex, texture_coords).rrrr; 32 | } else if (component >= _rd_v_components) { 33 | return vec4(0.0, 0.0, 0.0, 0.0); 34 | } else if (_rd_v_bucket_id == 1u) { 35 | return texture(_rd_tex1, vec3(texture_coords, float(_rd_v_texture_id + component))); 36 | } else if (_rd_v_bucket_id == 2u) { 37 | return texture(_rd_tex2, vec3(texture_coords, float(_rd_v_texture_id + component))); 38 | } else if (_rd_v_bucket_id == 3u) { 39 | return texture(_rd_tex3, vec3(texture_coords, float(_rd_v_texture_id + component))); 40 | } else if (_rd_v_bucket_id == 4u) { 41 | return texture(_rd_tex4, vec3(texture_coords, float(_rd_v_texture_id + component))); 42 | } else /*if (_rd_v_bucket_id == 5u)*/ { 43 | return texture(_rd_tex5, vec3(texture_coords, float(_rd_v_texture_id + component))); 44 | } 45 | } 46 | 47 | vec4 sheet(in vec2 texture_coords) { 48 | return sheetComponent(texture_coords, _rd_comp); 49 | } 50 | /* 51 | vec4 sheetOffset(in vec2 texture_coords, in ivec2 offset) { 52 | if (_rd_v_bucket_id == 0u) { 53 | return textureOffset(_rd_tex, texture_coords, offset).rrrr; 54 | } else if (_rd_comp >= _rd_v_components) { 55 | return vec4(0.0, 0.0, 0.0, 0.0); 56 | } else if (_rd_v_bucket_id == 1u) { 57 | return textureOffset(_rd_tex1, vec3(texture_coords, float(_rd_v_texture_id + _rd_comp)), offset); 58 | } else if (_rd_v_bucket_id == 2u) { 59 | return textureOffset(_rd_tex2, vec3(texture_coords, float(_rd_v_texture_id + _rd_comp)), offset); 60 | } else if (_rd_v_bucket_id == 3u) { 61 | return textureOffset(_rd_tex3, vec3(texture_coords, float(_rd_v_texture_id + _rd_comp)), offset); 62 | } else if (_rd_v_bucket_id == 4u) { 63 | return textureOffset(_rd_tex4, vec3(texture_coords, float(_rd_v_texture_id + _rd_comp)), offset); 64 | } else { 65 | return textureOffset(_rd_tex5, vec3(texture_coords, float(_rd_v_texture_id + _rd_comp)), offset); 66 | } 67 | } 68 | */ -------------------------------------------------------------------------------- /src/shader/sprite.vs: -------------------------------------------------------------------------------- 1 | #version 150 2 | 3 | uniform mat4 u_view; 4 | uniform mat4 u_model; 5 | uniform vec4 _rd_color; 6 | 7 | in vec2 position; 8 | in vec2 offset; 9 | in float rotation; 10 | in vec4 color; 11 | in uint bucket_id; 12 | in uint texture_id; 13 | in vec2 texture_uv; 14 | in uint components; 15 | 16 | out vec4 v_color; 17 | out vec2 v_tex_coords; 18 | flat out uint _rd_v_texture_id; 19 | flat out uint _rd_v_bucket_id; 20 | flat out uint _rd_v_components; 21 | 22 | void main() { 23 | 24 | // compute vertex positon 25 | 26 | vec2 trans; 27 | float sin_rotation = sin(rotation); 28 | float cos_rotation = cos(rotation); 29 | trans.x = offset.x * cos_rotation - offset.y * sin_rotation; 30 | trans.y = offset.x * sin_rotation + offset.y * cos_rotation; 31 | 32 | // apply global per sprite matrix (model) 33 | 34 | vec4 model_transformation = u_model * vec4(trans, 0.0, 1.0); 35 | 36 | gl_Position = u_view * vec4(vec3(position, 0.0) + vec3(model_transformation), 1.0); 37 | 38 | // pass along to fragment shader 39 | 40 | v_color = color * _rd_color; 41 | v_tex_coords = texture_uv; 42 | _rd_v_texture_id = texture_id; 43 | _rd_v_bucket_id = bucket_id; 44 | _rd_v_components = components; 45 | } 46 | -------------------------------------------------------------------------------- /src/shader/texture.inc.fs: -------------------------------------------------------------------------------- 1 | uniform sampler2D _rd_tex; 2 | uniform bool _rd_has_tex; 3 | 4 | ivec2 sheetSize() { 5 | return textureSize(_rd_tex, 0).xy; 6 | } 7 | 8 | vec4 sheet(in vec2 texture_coords) { 9 | if (_rd_has_tex) { 10 | return texture(_rd_tex, texture_coords); 11 | } else { 12 | return vec4(1.0, 1.0, 1.0, 1.0); 13 | } 14 | } 15 | 16 | vec4 sheetComponent(in vec2 texture_coords, in uint component) { 17 | if (component == 0u) { 18 | if (_rd_has_tex) { 19 | return texture(_rd_tex, texture_coords); 20 | } else { 21 | return vec4(1.0, 1.0, 1.0, 1.0); 22 | } 23 | } else { 24 | return vec4(0.0, 0.0, 0.0, 0.0); 25 | } 26 | } 27 | /* 28 | vec4 sheetOffset(in vec2 texture_coords, in ivec2 offset) { 29 | if (_rd_has_tex) { 30 | return textureOffset(_rd_tex, texture_coords, offset); 31 | } else { 32 | return vec4(1.0, 1.0, 1.0, 1.0); 33 | } 34 | } 35 | */ -------------------------------------------------------------------------------- /src/shader/texture.vs: -------------------------------------------------------------------------------- 1 | #version 150 2 | 3 | uniform mat4 u_view; 4 | uniform mat4 u_model; 5 | uniform vec4 _rd_color; 6 | uniform vec2 _rd_offset; 7 | uniform vec2 _rd_dimensions; 8 | 9 | in vec2 position; 10 | in vec2 texture_uv; 11 | 12 | out vec4 v_color; 13 | out vec2 v_tex_coords; 14 | 15 | void main() { 16 | 17 | vec4 model_transformation = u_model * vec4(position * _rd_dimensions, 0.0, 1.0); 18 | 19 | gl_Position = u_view * vec4(vec3(_rd_offset, 0.0) + vec3(model_transformation), 1.0); 20 | 21 | // pass along to fragment shade 22 | 23 | v_tex_coords = texture_uv; 24 | v_color = _rd_color; 25 | } 26 | -------------------------------------------------------------------------------- /tests/display.rs: -------------------------------------------------------------------------------- 1 | extern crate radiant_rs; 2 | use radiant_rs::*; 3 | 4 | #[test] 5 | fn build_default_window() { 6 | let display = Display::builder().build().unwrap(); 7 | display.prepare_frame(); 8 | display.swap_frame(); 9 | } 10 | -------------------------------------------------------------------------------- /tools/spritesheet.rs: -------------------------------------------------------------------------------- 1 | extern crate image; 2 | use std::{env, fs, cmp, path, collections, io, process}; 3 | use std::io::Write; 4 | use image::GenericImage; 5 | 6 | const OUTPUT_ASPECT: f32 = 1.0; 7 | 8 | const INVALID_ARGUMENT: i32 = 1; 9 | const INVALID_PATH: i32 = 2; 10 | const FILE_EXISTS: i32 = 3; 11 | const IMAGE_ERROR: i32 = 4; 12 | 13 | fn main() { 14 | let mut extension_map = collections::HashMap::new(); 15 | extension_map.insert("jpg", image::ImageFormat::JPEG); 16 | extension_map.insert("jpeg", image::ImageFormat::JPEG); 17 | extension_map.insert("png", image::ImageFormat::PNG); 18 | 19 | // get sourcec and target directory 20 | let source = env::args().nth(1).unwrap_or_else(|| error("Expected source directory as first argument", INVALID_ARGUMENT)); 21 | let target = env::args().nth(2).unwrap_or_else(|| error("Expected target file as second argument", INVALID_ARGUMENT)); 22 | let resize = env::args().nth(3).unwrap_or("0".to_string()).parse::().unwrap_or_else(|_| error("Expected floating point value scale as third argument", INVALID_ARGUMENT)); 23 | 24 | // load images 25 | let files = find_images(&source, &extension_map.keys().map(|&s| s).collect::>()); 26 | print!("Loading image files"); 27 | let ((max_width, max_height), images) = load_images(&files, resize); 28 | println!("."); 29 | 30 | // find nice layout 31 | let (cols, rows) = best_ratio(OUTPUT_ASPECT, images.len() as u32, (max_width, max_height)); 32 | 33 | // create output image 34 | println!("Generating spritesheet..."); 35 | let mut dest = image::DynamicImage::new_rgba8(cols * max_width, rows * max_height); 36 | let mut image_id = 0; 37 | 38 | for row in 0..rows { 39 | for col in 0..cols { 40 | if image_id < images.len() { 41 | dest.copy_from(&images[image_id], col * max_width, row * max_height); 42 | image_id += 1; 43 | } 44 | } 45 | } 46 | 47 | // construct file name 48 | let target = path::Path::new(&target); 49 | let basename = target.file_stem().unwrap_or_else(|| error("Invalid output file name", INVALID_ARGUMENT)); 50 | let extension = target.extension().unwrap_or_else(|| error("Invalid output file extension", INVALID_ARGUMENT)); 51 | 52 | // write file 53 | { 54 | let fullname = format!("{}_{}x{}.{}", basename.to_str().unwrap(), max_width, max_height, extension.to_str().unwrap()); 55 | 56 | let mut file = fs::OpenOptions::new().write(true).create_new(true).open(&fullname) 57 | .unwrap_or_else(|_| error(&format!("Target image {} exists or cannot be written", &fullname), FILE_EXISTS)); 58 | 59 | println!("Writing file {}...", &fullname); 60 | let format = extension_map.get(extension.to_str().unwrap()).unwrap_or_else(|| error("Output file type not supported", INVALID_ARGUMENT)); 61 | dest.write_to(&mut file, *format).unwrap_or_else(|_| error("Failed to encode image", IMAGE_ERROR)); 62 | } 63 | 64 | // write list of files 65 | { 66 | let fullname = format!("{}_{}x{}.txt", basename.to_str().unwrap(), max_width, max_height); 67 | 68 | let file = fs::OpenOptions::new().write(true).create_new(true).open(&fullname) 69 | .unwrap_or_else(|_| error(&format!("Target sprite list {} exists or cannot be written", &fullname), FILE_EXISTS)); 70 | 71 | println!("Writing file {}...", &fullname); 72 | let mut buffer = io::BufWriter::new(file); 73 | 74 | for file in files.iter() { 75 | buffer.write_all(file.file_name().unwrap().to_str().unwrap().as_bytes()).unwrap(); 76 | buffer.write(&['\n' as u8]).unwrap(); 77 | } 78 | } 79 | } 80 | 81 | 82 | // load images into vector 83 | fn load_images(files: &Vec, resize: f32) -> ((u32, u32), Vec) { 84 | let mut max_width = 0; 85 | let mut max_height = 0; 86 | let mut images = Vec::new(); 87 | for file in files.iter() { 88 | let image = image::open(&file).unwrap_or_else(|_| error(&format!("Could not open image file {}", &file.to_str().unwrap()), IMAGE_ERROR)); 89 | let mut image_dim = image.dimensions(); 90 | let image = if resize > 0.0 { 91 | image_dim.0 = (image_dim.0 as f32 * resize) as u32; 92 | image_dim.1 = (image_dim.1 as f32 * resize) as u32; 93 | image::imageops::resize(&image, image_dim.0, image_dim.1, image::FilterType::Lanczos3) 94 | } else { 95 | image.to_rgba() 96 | }; 97 | max_width = cmp::max(image_dim.0, max_width); 98 | max_height = cmp::max(image_dim.1, max_height); 99 | images.push(image); 100 | print!("."); 101 | io::stdout().flush().unwrap(); 102 | } 103 | ((max_width, max_height), images) 104 | } 105 | 106 | // read image file names from source directory 107 | fn find_images(source: &str, extensions: &[ &str ]) -> Vec { 108 | let mut files = Vec::new(); 109 | let entry_set = fs::read_dir(source).unwrap_or_else(|_| error("Cannot find source path", INVALID_PATH)); 110 | let mut entries = entry_set.collect::, _>>().unwrap_or_else(|_| error("Cannot read source path", INVALID_PATH)); 111 | entries.sort_by(|a, b| a.path().cmp(&b.path())); 112 | for entry in entries { 113 | let extension = entry.path().extension().map_or("", |p| p.to_str().unwrap()).to_string(); // !todo better solution or intended user experience ? 114 | if extensions.iter().find(|ext| **ext == extension).is_some() && entry.path().is_file() { 115 | files.push(entry.path().to_path_buf()); 116 | } 117 | } 118 | files 119 | } 120 | 121 | // finds number of rows/columns required to make output as close to desirect aspect as possible 122 | fn best_ratio(desired_aspect: f32, num_files: u32, max_dim: (u32, u32)) -> (u32, u32) { 123 | let mut best_ratio_diff = (1, 999999.0); // !todo inf 124 | for rows in 1..num_files { 125 | let cols = (num_files as f32 / rows as f32).ceil() as u32; 126 | let ratio = (cols * max_dim.0) as f32 / (rows * max_dim.1) as f32; 127 | let diff = (ratio - desired_aspect).abs(); 128 | if diff < best_ratio_diff.1 { 129 | best_ratio_diff = (rows, diff); 130 | } 131 | } 132 | ((num_files as f32 / best_ratio_diff.0 as f32).ceil() as u32, best_ratio_diff.0) 133 | } 134 | 135 | // together with unwrap_or_else replaces expect() to get some cheap error reporting 136 | fn error(message: &str, code: i32) -> ! { 137 | if code == INVALID_ARGUMENT { 138 | println!("spritesheet: A tool to generate spritesheets. An accompanying textfile contains the input filenames in frame_id order."); 139 | } 140 | println!("Error: {}.", message); 141 | if code == INVALID_ARGUMENT { 142 | println!("Usage: spritesheet . [ ]"); 143 | println!(" e.g. spritesheet /path/to/images/ sheet.png 0.25"); 144 | } 145 | process::exit(code) 146 | } 147 | --------------------------------------------------------------------------------