├── .gitignore ├── .vscode └── launch.json ├── Cargo.toml ├── LICENSE ├── README.md ├── img └── architecture.png ├── nona ├── Cargo.toml ├── README.md └── src │ ├── cache.rs │ ├── color.rs │ ├── context.rs │ ├── errors.rs │ ├── fonts.rs │ ├── lib.rs │ ├── math.rs │ └── renderer.rs └── nonaquad ├── Cargo.toml ├── examples ├── Roboto-Bold.ttf ├── drawaa.rs └── index.html └── src ├── lib.rs ├── nvgimpl.rs ├── nvgimpl_orig.rs ├── shader.frag └── shader.vert /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "lldb", 9 | "request": "launch", 10 | "name": "Debug unit tests in library 'nonaquad'", 11 | "cargo": { 12 | "args": [ 13 | "test", 14 | "--no-run", 15 | "--lib", 16 | "--package=nonaquad" 17 | ], 18 | "filter": { 19 | "name": "nonaquad", 20 | "kind": "lib" 21 | } 22 | }, 23 | "args": [], 24 | "cwd": "${workspaceFolder}" 25 | }, 26 | { 27 | "type": "lldb", 28 | "request": "launch", 29 | "name": "Debug example 'drawaa'", 30 | "cargo": { 31 | "args": [ 32 | "build", 33 | "--example=drawaa", 34 | "--package=nonaquad" 35 | ], 36 | "filter": { 37 | "name": "drawaa", 38 | "kind": "example" 39 | } 40 | }, 41 | "args": [], 42 | "cwd": "${workspaceFolder}" 43 | }, 44 | { 45 | "type": "lldb", 46 | "request": "launch", 47 | "name": "Debug unit tests in example 'drawaa'", 48 | "cargo": { 49 | "args": [ 50 | "test", 51 | "--no-run", 52 | "--example=drawaa", 53 | "--package=nonaquad" 54 | ], 55 | "filter": { 56 | "name": "drawaa", 57 | "kind": "example" 58 | } 59 | }, 60 | "args": [], 61 | "cwd": "${workspaceFolder}" 62 | } 63 | ] 64 | } -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "nona", 4 | "nonaquad", 5 | ] 6 | 7 | # Uncomment for small builds 8 | [profile.release] 9 | lto = "thin" 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Nokola 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # nonaquad 3 | Vector anti-aliased graphics renderer for Android, WASM, Desktop in Rust, using miniquad. 4 | 5 | This library started as a port of [NanoVG](https://github.com/sunli829/nvg/tree/master/nvg-gl) for [miniquad](https://github.com/not-fl3/miniquad). Use this library if you want to draw graphics for a quick experiment (game, paint app, etc.) or if you want to build other libraries on top (e.g. UI library.) 6 | 7 | ## Notice 8 | 2022-02-18 9 | 10 | I think this library is not good enough yet. I haven't worked on it for a while and not sure when I'll be able to again. I'm working on another app for note taking and wellbeing that I'd like to spend most of my time on. 11 | 12 | I have a plan of making a very easy developer UI experience and interface for nona. However, at the moment I think it may be a mistake picking this library, unless you'd like to be a main contributor and help develop it further. 13 | 14 | This said, please read below and see if any of the goals and ideas resonate with you: 15 | 16 | ## Goals 17 | * small and fast executables for mobile, desktop and web. 18 | * safety 19 | * high-quality drawing: anti-aliasing in shaders, squircles, gradients, fast blur 20 | * 1-step or straight-forward build on all platforms 21 | * ease-of-use 22 | * minimal dependencies 23 | 24 | ## Supported platforms - same as miniquad: 25 | 26 | |OS|Platform| 27 | |---|----------| 28 | |Windows| OpenGl 3| 29 | |Linux| OpenGl 3| 30 | | macOS| OpenGL 3| 31 | | iOS| GLES 3| 32 | | WASM| WebGl1 - tested on ios safari, ff, chrome| 33 | | Android|GLES3| 34 | 35 | ## Not supported, but desirable platforms 36 | 37 | * Android, GLES2 - work in progress. 38 | * Metal 39 | 40 | # Example 41 | 42 | Located in [nonaquad/examples](nonaquad/examples). 43 | 44 | Start with: `cargo run --example drawaa` 45 | ```rust 46 | nona.begin_path(); 47 | nona.rect((100.0, 100.0, 300.0, 300.0)); 48 | nona.fill_paint(nona::Gradient::Linear { 49 | start: (100, 100).into(), 50 | end: (400, 400).into(), 51 | start_color: nona::Color::rgb_i(0xAA, 0x6C, 0x39), 52 | end_color: nona::Color::rgb_i(0x88, 0x2D, 0x60), 53 | }); 54 | nona.fill().unwrap(); 55 | 56 | let origin = (150.0, 140.0); 57 | nona.begin_path(); 58 | nona.circle(origin, 64.0); 59 | nona.move_to(origin); 60 | nona.line_to((origin.0 + 300.0, origin.1 - 50.0)); 61 | nona.stroke_paint(nona::Color::rgba(1.0, 1.0, 0.0, 1.0)); 62 | nona.stroke_width(3.0); 63 | nona.stroke().unwrap(); 64 | 65 | nona.end_frame().unwrap(); 66 | ``` 67 | 68 | # Screenshots 69 | Screenshots produced from above example. 70 | 71 | ## Windows 72 | ![Windows](https://user-images.githubusercontent.com/6869225/131318911-6bd99304-69cd-41e3-8633-058cb3d71500.png) 73 | 74 | ## Web 75 | ![WebGL](https://user-images.githubusercontent.com/6869225/131320931-d9155434-f4b3-480f-93fb-9af5f43df5d8.png) 76 | 77 | WASM size before size stripping 754KB. With basic stripping (see below) 391 KB 78 | 79 | ## Android 80 | APK size: 134KB 81 | 82 | ## iOS 83 | (not yet ready) 84 | 85 | # Building 86 | 87 | ## Linux 88 | 89 | ```bash 90 | # ubuntu system dependencies 91 | apt install libx11-dev libxi-dev libgl1-mesa-dev 92 | 93 | cargo run --example drawaa 94 | ``` 95 | 96 | ## Windows 97 | 98 | ```bash 99 | # both MSVC and GNU target is supported: 100 | rustup target add x86_64-pc-windows-msvc 101 | # or 102 | rustup target add x86_64-pc-windows-gnu 103 | 104 | cargo run --example drawaa 105 | ``` 106 | 107 | ## WASM 108 | First time setup: 109 | ```bash 110 | md examples 111 | copy ./nonaquad/examples/index.html ./examples 112 | rustup target add wasm32-unknown-unknown 113 | npm i -g simplehttpserver 114 | ``` 115 | 116 | Build and run: 117 | ```bash 118 | cargo build --example drawaa --target wasm32-unknown-unknown --release 119 | copy ".\target\wasm32-unknown-unknown\release\examples\drawaa.wasm" ".\examples\drawaa.wasm" /y 120 | 121 | cd examples 122 | simplehttpserver 123 | ``` 124 | Then open `http://localhost:8000` 125 | 126 | ### To reduce WASM size further 127 | 1. Install [binaryen toolkit](https://github.com/WebAssembly/binaryen/releases), then run: 128 | ```bash 129 | wasm-opt.exe -Os -o drawaa.wasm drawaa.wasm 130 | ``` 131 | 1. Run `cargo install twiggy` and check out the twiggy docs: https://rustwasm.github.io/twiggy/. E.g. you can run `twiggy top drawaa.wasm` to see where size is used most. 132 | 1. Set environment variable `RUSTFLAGS="-C link-arg=--strip-debug"`. **WARNING!** This will remove debug info from all cargo build-s. Make sure to revert RUSTFLAGS to "" (empty) after. Twiggy will also not report function details if you strip debug info. 133 | 1. Also check https://rustwasm.github.io/book/reference/code-size.html 134 | 135 | ## Android 136 | 137 | Recommended way to build for android is using Docker. 138 | miniquad use slightly modifed version of `cargo-apk` 139 | 140 | **Note:** on Windows if you see git error during `cargo apk build --example drawaa`, update your .git folder to be not read-only. See related [Docker issue #6016](https://github.com/docker/for-win/issues/6016) 141 | 142 | ``` 143 | docker run --rm -v $(pwd)":/root/src" -w /root/src notfl3/cargo-apk cargo apk build --example drawaa 144 | docker run -it -v %cd%":/root/src" -w /root/src notfl3/cargo-apk bash 145 | ``` 146 | 147 | APK file will be in `target/android-artifacts/(debug|release)/apk` 148 | 149 | With feature "log-impl" enabled all log calls will be forwarded to adb console. 150 | No code modifications for Android required, everything just works. 151 | 152 | ## iOS 153 | See build example for [miniquad](https://github.com/not-fl3/miniquad) 154 | 155 | # Roadmap 156 | The goal of nonaquad is to have a stable, high-quality vector library on mobile, web, and desktop from the same source code. 157 | 158 | I will use it as a building block for a general purpose cross-platform app framework. 159 | 160 | ## Features 161 | - [x] anti-aliased lines, circles, rect, rounded rect (signed distance field), curves 162 | - [x] polygons - convex and concave 163 | - [x] gradients 164 | - [x] clipping 165 | - [x] AA text 166 | - [ ] [Work in progress] image and textures 167 | - [ ] high-quality fast drop shadows and blur 168 | - [ ] gradients - high quality dithered 169 | - [ ] squircles 170 | 171 | # Architecture 172 | This is how the pieces fit together: 173 | 174 | ![Architecture](img/architecture.png) 175 | 176 | # Contributing 177 | See TODO-s in source code or anything else goes 178 | 179 | # License 180 | MIT or APACHE at your convenience 181 | -------------------------------------------------------------------------------- /img/architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nokola/nonaquad/b87fde1cb22a33b059ff5af3917ec10f8c7d1089/img/architecture.png -------------------------------------------------------------------------------- /nona/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Nokola"] 3 | description = "Pure Rust antialiasing graphics library" 4 | edition = "2018" 5 | homepage = "https://github.com/nokola/nonaquad/tree/main/nona" 6 | keywords = ["graphics", "vector", "2d"] 7 | license = "MIT OR Apache-2.0" 8 | name = "nona" 9 | publish = true 10 | repository = "https://github.com/nokola/nonaquad/tree/main/nona" 11 | version = "0.1.2" 12 | # documentation = "TODO, leave empty for default docs.rs" 13 | categories = ["graphics", "rendering"] 14 | readme = "README.md" 15 | 16 | [dependencies] 17 | bitflags = "1.2.1" 18 | clamped = "1.0.0" 19 | image = "0.23.8" 20 | rusttype = {version = "0.9.2", features = ["gpu_cache"]} 21 | slab = "0.4.2" 22 | thiserror = "1.0.20" 23 | -------------------------------------------------------------------------------- /nona/README.md: -------------------------------------------------------------------------------- 1 | # nona 2 | Antialiased vector drawing bring-your-own-renderer (or use ours) library 3 | 4 | For a renderer, look at https://github.com/nokola/nonaquad 5 | Based on nvg code from: https://github.com/sunli829/nvg. Going in fundamentally different direction from the original nvg/NanoVG to support higher quality anti-aliasing, ease of use, error handling, and speed. 6 | 7 | Nona is completely platform-independent and 100% Rust. 8 | -------------------------------------------------------------------------------- /nona/src/cache.rs: -------------------------------------------------------------------------------- 1 | use crate::context::{Command, Path, Vertex}; 2 | use crate::{Bounds, LineCap, LineJoin, Point, Solidity}; 3 | use clamped::Clamp; 4 | use core::mem::size_of; 5 | use std::f32::consts::PI; 6 | 7 | bitflags! { 8 | #[derive(Default)] 9 | struct PointFlags: u32 { 10 | const PT_CORNER = 0x1; 11 | const PT_LEFT = 0x2; 12 | const PT_BEVEL = 0x4; 13 | const PR_INNERBEVEL = 0x8; 14 | } 15 | } 16 | 17 | #[derive(Debug, Default, Copy, Clone)] 18 | pub(crate) struct VPoint { 19 | xy: Point, 20 | d: Point, 21 | len: f32, 22 | dm: Point, 23 | flags: PointFlags, 24 | } 25 | 26 | #[derive(Default, Debug)] 27 | pub(crate) struct PathCache { 28 | pub(crate) points: Vec, 29 | pub(crate) paths: Vec, 30 | pub(crate) vertexes: Vec, 31 | pub(crate) bounds: Bounds, 32 | } 33 | 34 | /// Copied from `rawpointer` rust crate https://docs.rs/rawpointer/0.1.0/i686-apple-darwin/src/rawpointer/lib.rs.html#15-22 35 | /// Return the number of elements of `T` from `start` to `end`.
36 | /// Return the arithmetic difference if `T` is zero size. 37 | #[inline(always)] 38 | pub fn ptrdistance(start: *const T, end: *const T) -> usize { 39 | let size = size_of::(); 40 | if size == 0 { 41 | (end as usize).wrapping_sub(start as usize) 42 | } else { 43 | (end as usize - start as usize) / size 44 | } 45 | } 46 | 47 | impl PathCache { 48 | pub fn clear(&mut self) { 49 | self.points.clear(); 50 | self.paths.clear(); 51 | } 52 | 53 | fn add_path(&mut self) -> &mut Path { 54 | self.paths.push(Path { 55 | first: self.points.len(), 56 | count: 0, 57 | closed: false, 58 | num_bevel: 0, 59 | solidity: Solidity::Solid, 60 | fill: std::ptr::null_mut(), 61 | num_fill: 0, 62 | stroke: std::ptr::null_mut(), 63 | num_stroke: 0, 64 | convex: false, 65 | }); 66 | self.paths.last_mut().unwrap() 67 | } 68 | 69 | fn add_point(&mut self, pt: Point, flags: PointFlags, dist_tol: f32) { 70 | if let Some(path) = self.paths.last_mut() { 71 | if let Some(last_pt) = self.points.last_mut() { 72 | if path.count > 0 { 73 | if last_pt.xy.equals(pt, dist_tol) { 74 | last_pt.flags |= flags; 75 | return; 76 | } 77 | } 78 | } 79 | 80 | self.points.push(VPoint { 81 | xy: pt, 82 | d: Default::default(), 83 | len: 0.0, 84 | dm: Default::default(), 85 | flags, 86 | }); 87 | path.count += 1; 88 | } 89 | } 90 | 91 | fn close_path(&mut self) { 92 | if let Some(path) = self.paths.last_mut() { 93 | path.closed = true; 94 | } 95 | } 96 | 97 | fn path_solidity(&mut self, solidity: Solidity) { 98 | if let Some(path) = self.paths.last_mut() { 99 | path.solidity = solidity; 100 | } 101 | } 102 | 103 | unsafe fn alloc_temp_vertexes(&mut self, count: usize) -> *mut Vertex { 104 | self.vertexes.resize(count, Default::default()); 105 | if self.vertexes.is_empty() { 106 | return std::ptr::null_mut(); 107 | } 108 | &mut self.vertexes[0] as *mut Vertex 109 | } 110 | 111 | fn tesselate_bezier( 112 | &mut self, 113 | pt1: Point, 114 | pt2: Point, 115 | pt3: Point, 116 | pt4: Point, 117 | level: usize, 118 | flags: PointFlags, 119 | tess_tol: f32, 120 | ) { 121 | if level > 10 { 122 | return; 123 | } 124 | 125 | let Point { x: x1, y: y1 } = pt1; 126 | let Point { x: x2, y: y2 } = pt2; 127 | let Point { x: x3, y: y3 } = pt3; 128 | let Point { x: x4, y: y4 } = pt4; 129 | 130 | let x12 = (x1 + x2) * 0.5; 131 | let y12 = (y1 + y2) * 0.5; 132 | let x23 = (x2 + x3) * 0.5; 133 | let y23 = (y2 + y3) * 0.5; 134 | let x34 = (x3 + x4) * 0.5; 135 | let y34 = (y3 + y4) * 0.5; 136 | let x123 = (x12 + x23) * 0.5; 137 | let y123 = (y12 + y23) * 0.5; 138 | 139 | let dx = x4 - x1; 140 | let dy = y4 - y1; 141 | let d2 = ((x2 - x4) * dy - (y2 - y4) * dx).abs(); 142 | let d3 = ((x3 - x4) * dy - (y3 - y4) * dx).abs(); 143 | 144 | if (d2 + d3) * (d2 + d3) < tess_tol * (dx * dx + dy * dy) { 145 | self.add_point(Point::new(x4, y4), flags, tess_tol); 146 | return; 147 | } 148 | 149 | let x234 = (x23 + x34) * 0.5; 150 | let y234 = (y23 + y34) * 0.5; 151 | let x1234 = (x123 + x234) * 0.5; 152 | let y1234 = (y123 + y234) * 0.5; 153 | 154 | self.tesselate_bezier( 155 | Point::new(x1, y1), 156 | Point::new(x12, y12), 157 | Point::new(x123, y123), 158 | Point::new(x1234, y1234), 159 | level + 1, 160 | PointFlags::empty(), 161 | tess_tol, 162 | ); 163 | self.tesselate_bezier( 164 | Point::new(x1234, y1234), 165 | Point::new(x234, y234), 166 | Point::new(x34, y34), 167 | Point::new(x4, y4), 168 | level + 1, 169 | flags, 170 | tess_tol, 171 | ); 172 | } 173 | 174 | pub(crate) fn flatten_paths(&mut self, commands: &[Command], dist_tol: f32, tess_tol: f32) { 175 | for cmd in commands { 176 | match cmd { 177 | Command::MoveTo(pt) => { 178 | self.add_path(); 179 | self.add_point(*pt, PointFlags::PT_CORNER, dist_tol); 180 | } 181 | Command::LineTo(pt) => { 182 | self.add_point(*pt, PointFlags::PT_CORNER, dist_tol); 183 | } 184 | Command::BezierTo(cp1, cp2, pt) => { 185 | if let Some(last) = self.points.last().map(|pt| *pt) { 186 | self.tesselate_bezier( 187 | last.xy, 188 | *cp1, 189 | *cp2, 190 | *pt, 191 | 0, 192 | PointFlags::PT_CORNER, 193 | tess_tol, 194 | ); 195 | } 196 | } 197 | Command::Close => self.close_path(), 198 | Command::Solidity(solidity) => self.path_solidity(*solidity), 199 | } 200 | } 201 | 202 | self.bounds.min = Point::new(std::f32::MAX, std::f32::MAX); 203 | self.bounds.max = Point::new(std::f32::MIN, std::f32::MIN); 204 | 205 | unsafe { 206 | for j in 0..self.paths.len() { 207 | let path = &mut self.paths[j]; 208 | let pts = &mut self.points[path.first] as *mut VPoint; 209 | let mut p0 = pts.offset(path.count as isize - 1); 210 | let mut p1 = pts; 211 | 212 | if (*p0).xy.equals((*p1).xy, dist_tol) { 213 | if path.count > 0 { 214 | path.count -= 1; 215 | } 216 | p0 = pts.offset(path.count as isize - 1); 217 | path.closed = true; 218 | } 219 | 220 | if path.count > 2 { 221 | let area = poly_area(std::slice::from_raw_parts(pts, path.count)); 222 | if path.solidity == Solidity::Solid && area < 0.0 { 223 | poly_reverse(std::slice::from_raw_parts_mut(pts, path.count)); 224 | } 225 | if path.solidity == Solidity::Hole && area > 0.0 { 226 | poly_reverse(std::slice::from_raw_parts_mut(pts, path.count)); 227 | } 228 | } 229 | 230 | for _ in 0..path.count { 231 | (*p0).d.x = (*p1).xy.x - (*p0).xy.x; 232 | (*p0).d.y = (*p1).xy.y - (*p0).xy.y; 233 | (*p0).len = (*p0).d.normalize(); 234 | 235 | self.bounds.min.x = self.bounds.min.x.min((*p0).xy.x); 236 | self.bounds.min.y = self.bounds.min.y.min((*p0).xy.y); 237 | self.bounds.max.x = self.bounds.max.x.max((*p0).xy.x); 238 | self.bounds.max.y = self.bounds.max.y.max((*p0).xy.y); 239 | 240 | p0 = p1; 241 | p1 = p1.add(1); 242 | } 243 | } 244 | } 245 | } 246 | 247 | fn calculate_joins(&mut self, w: f32, line_join: LineJoin, miter_limit: f32) { 248 | let mut iw = 0.0; 249 | if w > 0.0 { 250 | iw = 1.0 / w; 251 | } 252 | 253 | unsafe { 254 | for i in 0..self.paths.len() { 255 | let path = &mut self.paths[i]; 256 | let pts = &mut self.points[path.first] as *mut VPoint; 257 | let mut p0 = pts.offset(path.count as isize - 1); 258 | let mut p1 = pts; 259 | let mut nleft = 0; 260 | 261 | path.num_bevel = 0; 262 | 263 | for _ in 0..path.count { 264 | let dlx0 = (*p0).d.y; 265 | let dly0 = -(*p0).d.x; 266 | let dlx1 = (*p1).d.y; 267 | let dly1 = -(*p1).d.x; 268 | 269 | (*p1).dm.x = (dlx0 + dlx1) * 0.5; 270 | (*p1).dm.y = (dly0 + dly1) * 0.5; 271 | let dmr2 = (*p1).dm.x * (*p1).dm.x + (*p1).dm.y * (*p1).dm.y; 272 | 273 | if dmr2 > 0.000001 { 274 | let mut scale = 1.0 / dmr2; 275 | if scale > 600.0 { 276 | scale = 600.0; 277 | } 278 | (*p1).dm.x *= scale; 279 | (*p1).dm.y *= scale; 280 | } 281 | 282 | (*p1).flags &= PointFlags::PT_CORNER; 283 | 284 | let cross = (*p1).d.x * (*p0).d.y - (*p0).d.x * (*p1).d.y; 285 | if cross > 0.0 { 286 | nleft += 1; 287 | (*p1).flags |= PointFlags::PT_LEFT; 288 | } 289 | 290 | let limit = (((*p0).len.min((*p1).len) as f32) * iw).max(1.01); 291 | if (dmr2 * limit * limit) < 1.0 { 292 | (*p1).flags |= PointFlags::PR_INNERBEVEL; 293 | } 294 | 295 | if (*p1).flags.contains(PointFlags::PT_CORNER) { 296 | if (dmr2 * miter_limit * miter_limit) < 1.0 297 | || line_join == LineJoin::Bevel 298 | || line_join == LineJoin::Round 299 | { 300 | (*p1).flags |= PointFlags::PT_BEVEL; 301 | } 302 | } 303 | 304 | if (*p1).flags.contains(PointFlags::PT_BEVEL) 305 | || (*p1).flags.contains(PointFlags::PR_INNERBEVEL) 306 | { 307 | path.num_bevel += 1; 308 | } 309 | 310 | p0 = p1; 311 | p1 = p1.add(1); 312 | } 313 | 314 | path.convex = nleft == path.count; 315 | } 316 | } 317 | } 318 | 319 | pub(crate) fn expand_stroke( 320 | &mut self, 321 | mut w: f32, 322 | fringe: f32, 323 | line_cap: LineCap, 324 | line_join: LineJoin, 325 | miter_limit: f32, 326 | tess_tol: f32, 327 | ) { 328 | let aa = fringe; 329 | let mut u0 = 0.0; 330 | let mut u1 = 1.0; 331 | let ncap = curve_divs(w, PI, tess_tol); 332 | 333 | w += aa * 0.5; 334 | 335 | if aa == 0.0 { 336 | u0 = 0.5; 337 | u1 = 0.5; 338 | } 339 | 340 | self.calculate_joins(w, line_join, miter_limit); 341 | 342 | let mut cverts = 0; 343 | for path in &self.paths { 344 | let loop_ = path.closed; 345 | if line_join == LineJoin::Round { 346 | cverts += (path.count + path.num_bevel * (ncap + 2) + 1) * 2; 347 | } else { 348 | cverts += (path.count + path.num_bevel * 5 + 1) * 2; 349 | if !loop_ { 350 | if line_cap == LineCap::Round { 351 | cverts += (ncap * 2 + 2) * 2; 352 | } else { 353 | cverts += (3 + 3) * 2; 354 | } 355 | } 356 | } 357 | } 358 | 359 | unsafe { 360 | let mut vertexes = self.alloc_temp_vertexes(cverts); 361 | if vertexes.is_null() { 362 | return; 363 | } 364 | 365 | for i in 0..self.paths.len() { 366 | let path = &mut self.paths[i]; 367 | let pts = &mut self.points[path.first] as *mut VPoint; 368 | 369 | path.fill = std::ptr::null_mut(); 370 | path.num_fill = 0; 371 | 372 | let loop_ = path.closed; 373 | let mut dst = vertexes; 374 | path.stroke = dst; 375 | 376 | let (mut p0, mut p1, s, e) = if loop_ { 377 | (pts.offset(path.count as isize - 1), pts, 0, path.count) 378 | } else { 379 | (pts, pts.add(1), 1, path.count - 1) 380 | }; 381 | 382 | if !loop_ { 383 | let mut d = Point::new((*p1).xy.x - (*p0).xy.x, (*p1).xy.y - (*p0).xy.y); 384 | d.normalize(); 385 | match line_cap { 386 | LineCap::Butt => { 387 | dst = butt_cap_start( 388 | dst, 389 | p0.as_mut().unwrap(), 390 | d.x, 391 | d.y, 392 | w, 393 | -aa * 0.5, 394 | aa, 395 | u0, 396 | u1, 397 | ) 398 | } 399 | LineCap::Square => { 400 | dst = butt_cap_start( 401 | dst, 402 | p0.as_mut().unwrap(), 403 | d.x, 404 | d.y, 405 | w, 406 | w - aa, 407 | aa, 408 | u0, 409 | u1, 410 | ) 411 | } 412 | LineCap::Round => { 413 | dst = round_cap_start( 414 | dst, 415 | p0.as_mut().unwrap(), 416 | d.x, 417 | d.y, 418 | w, 419 | ncap, 420 | aa, 421 | u0, 422 | u1, 423 | ) 424 | } 425 | } 426 | } 427 | 428 | for _ in s..e { 429 | if (*p1).flags.contains(PointFlags::PT_BEVEL) 430 | || (*p1).flags.contains(PointFlags::PR_INNERBEVEL) 431 | { 432 | if line_join == LineJoin::Round { 433 | dst = round_join( 434 | dst, 435 | p0.as_mut().unwrap(), 436 | p1.as_mut().unwrap(), 437 | w, 438 | w, 439 | u0, 440 | u1, 441 | ncap, 442 | aa, 443 | ); 444 | } else { 445 | dst = bevel_join( 446 | dst, 447 | p0.as_mut().unwrap(), 448 | p1.as_mut().unwrap(), 449 | w, 450 | w, 451 | u0, 452 | u1, 453 | aa, 454 | ); 455 | } 456 | } else { 457 | *dst = Vertex::new( 458 | (*p1).xy.x + ((*p1).dm.x * w), 459 | (*p1).xy.y + ((*p1).dm.y * w), 460 | u0, 461 | 1.0, 462 | ); 463 | dst = dst.add(1); 464 | 465 | *dst = Vertex::new( 466 | (*p1).xy.x - ((*p1).dm.x * w), 467 | (*p1).xy.y - ((*p1).dm.y * w), 468 | u1, 469 | 1.0, 470 | ); 471 | dst = dst.add(1); 472 | } 473 | p0 = p1; 474 | p1 = p1.add(1); 475 | } 476 | 477 | if loop_ { 478 | let v0 = vertexes; 479 | let v1 = vertexes.add(1); 480 | 481 | *dst = Vertex::new((*v0).x, (*v0).y, u0, 1.0); 482 | dst = dst.add(1); 483 | 484 | *dst = Vertex::new((*v1).x, (*v1).y, u1, 1.0); 485 | dst = dst.add(1); 486 | } else { 487 | let mut d = Point::new((*p1).xy.x - (*p0).xy.x, (*p1).xy.y - (*p0).xy.y); 488 | d.normalize(); 489 | match line_cap { 490 | LineCap::Butt => { 491 | dst = butt_cap_end( 492 | dst, 493 | p1.as_mut().unwrap(), 494 | d.x, 495 | d.y, 496 | w, 497 | -aa * 0.5, 498 | aa, 499 | u0, 500 | u1, 501 | ); 502 | } 503 | LineCap::Round => { 504 | dst = butt_cap_end( 505 | dst, 506 | p1.as_mut().unwrap(), 507 | d.x, 508 | d.y, 509 | w, 510 | w - aa, 511 | aa, 512 | u0, 513 | u1, 514 | ); 515 | } 516 | LineCap::Square => { 517 | dst = round_cap_end( 518 | dst, 519 | p1.as_mut().unwrap(), 520 | d.x, 521 | d.y, 522 | w, 523 | ncap, 524 | aa, 525 | u0, 526 | u1, 527 | ); 528 | } 529 | } 530 | } 531 | 532 | path.num_stroke = ptrdistance(vertexes, dst); 533 | vertexes = dst; 534 | } 535 | } 536 | } 537 | 538 | pub(crate) fn expand_fill( 539 | &mut self, 540 | w: f32, 541 | line_join: LineJoin, 542 | miter_limit: f32, 543 | fringe_width: f32, 544 | ) { 545 | let aa = fringe_width; 546 | let fringe = w > 0.0; 547 | 548 | self.calculate_joins(w, line_join, miter_limit); 549 | 550 | let mut cverts = 0; 551 | for path in &self.paths { 552 | cverts += path.count + path.num_bevel + 1; 553 | if fringe { 554 | cverts += (path.count + path.num_bevel * 5 + 1) * 2; 555 | } 556 | } 557 | 558 | unsafe { 559 | let mut vertexes = self.alloc_temp_vertexes(cverts); 560 | if vertexes.is_null() { 561 | return; 562 | } 563 | 564 | let convex = self.paths.len() == 1 && self.paths[0].convex; 565 | 566 | for i in 0..self.paths.len() { 567 | let path = &mut self.paths[i]; 568 | let pts = &mut self.points[path.first] as *mut VPoint; 569 | let woff = 0.5 * aa; 570 | let mut dst = vertexes; 571 | 572 | path.fill = dst; 573 | 574 | if fringe { 575 | let mut p0 = pts.offset(path.count as isize - 1); 576 | let mut p1 = pts; 577 | for _ in 0..path.count { 578 | if (*p1).flags.contains(PointFlags::PT_BEVEL) { 579 | let dlx0 = (*p0).d.y; 580 | let dly0 = -(*p0).d.x; 581 | let dlx1 = (*p1).d.y; 582 | let dly1 = -(*p1).d.x; 583 | if (*p1).flags.contains(PointFlags::PT_LEFT) { 584 | let lx = (*p1).xy.x + (*p1).dm.x * woff; 585 | let ly = (*p1).xy.y + (*p1).dm.y * woff; 586 | *dst = Vertex::new(lx, ly, 0.5, 1.0); 587 | dst = dst.add(1); 588 | } else { 589 | let lx0 = (*p1).xy.x + dlx0 * woff; 590 | let ly0 = (*p1).xy.y + dly0 * woff; 591 | let lx1 = (*p1).xy.x + dlx1 * woff; 592 | let ly1 = (*p1).xy.y + dly1 * woff; 593 | 594 | *dst = Vertex::new(lx0, ly0, 0.5, 1.0); 595 | dst = dst.add(1); 596 | 597 | *dst = Vertex::new(lx1, ly1, 0.5, 1.0); 598 | dst = dst.add(1); 599 | } 600 | } else { 601 | *dst = Vertex::new( 602 | (*p1).xy.x + ((*p1).dm.x * woff), 603 | (*p1).xy.y + ((*p1).dm.y * woff), 604 | 0.5, 605 | 1.0, 606 | ); 607 | dst = dst.add(1); 608 | } 609 | 610 | p0 = p1; 611 | p1 = p1.add(1); 612 | } 613 | } else { 614 | for j in 0..path.count { 615 | let pt = pts.add(j); 616 | *dst = Vertex::new((*pt).xy.x, (*pt).xy.y, 0.5, 1.0); 617 | dst = dst.add(1); 618 | } 619 | } 620 | 621 | path.num_fill = ptrdistance(vertexes, dst); 622 | vertexes = dst; 623 | 624 | if fringe { 625 | let mut lw = w + woff; 626 | let rw = w - woff; 627 | let mut lu = 0.0; 628 | let ru = 1.0; 629 | let mut dst = vertexes; 630 | path.stroke = dst; 631 | 632 | if convex { 633 | lw = woff; 634 | lu = 0.5; 635 | } 636 | 637 | let mut p0 = pts.offset(path.count as isize - 1); 638 | let mut p1 = pts; 639 | 640 | for _ in 0..path.count { 641 | if (*p1).flags.contains(PointFlags::PT_BEVEL) 642 | || (*p1).flags.contains(PointFlags::PR_INNERBEVEL) 643 | { 644 | dst = bevel_join( 645 | dst, 646 | p0.as_mut().unwrap(), 647 | p1.as_mut().unwrap(), 648 | lw, 649 | rw, 650 | lu, 651 | ru, 652 | fringe_width, 653 | ); 654 | } else { 655 | *dst = Vertex::new( 656 | (*p1).xy.x + ((*p1).dm.x * lw), 657 | (*p1).xy.y + ((*p1).dm.y * lw), 658 | lu, 659 | 1.0, 660 | ); 661 | dst = dst.add(1); 662 | 663 | *dst = Vertex::new( 664 | (*p1).xy.x - ((*p1).dm.x * rw), 665 | (*p1).xy.y - ((*p1).dm.y * rw), 666 | ru, 667 | 1.0, 668 | ); 669 | dst = dst.add(1); 670 | } 671 | p0 = p1; 672 | p1 = p1.add(1); 673 | } 674 | 675 | let v0 = vertexes; 676 | let v1 = vertexes.add(1); 677 | 678 | *dst = Vertex::new((*v0).x, (*v0).y, lu, 1.0); 679 | dst = dst.add(1); 680 | 681 | *dst = Vertex::new((*v1).x, (*v1).y, ru, 1.0); 682 | dst = dst.add(1); 683 | 684 | path.num_stroke = ptrdistance(vertexes, dst); 685 | vertexes = dst; 686 | } else { 687 | path.stroke = std::ptr::null_mut(); 688 | path.num_stroke = 0; 689 | } 690 | } 691 | } 692 | } 693 | } 694 | 695 | fn triangle_area(a: &VPoint, b: &VPoint, c: &VPoint) -> f32 { 696 | let a = &a.xy; 697 | let b = &b.xy; 698 | let c = &c.xy; 699 | let abx = b.x - a.x; 700 | let aby = b.y - a.y; 701 | let acx = c.x - a.x; 702 | let acy = c.y - a.y; 703 | acx * aby - abx * acy 704 | } 705 | 706 | fn poly_area(pts: &[VPoint]) -> f32 { 707 | let mut area = 0.0; 708 | for i in 2..pts.len() { 709 | let a = &pts[0]; 710 | let b = &pts[i - 1]; 711 | let c = &pts[i]; 712 | area += triangle_area(a, b, c); 713 | } 714 | area * 0.5 715 | } 716 | 717 | fn poly_reverse(pts: &mut [VPoint]) { 718 | let mut i = 0; 719 | let mut j = pts.len() as i32 - 1; 720 | while i < j { 721 | pts.swap(i as usize, j as usize); 722 | i += 1; 723 | j -= 1; 724 | } 725 | } 726 | 727 | fn curve_divs(r: f32, arc: f32, tess_tol: f32) -> usize { 728 | let da = (r / (r + tess_tol)).acos() * 2.0; 729 | ((arc / da).ceil() as i32).max(2) as usize 730 | } 731 | 732 | fn choose_bevel(bevel: bool, p0: &mut VPoint, p1: &mut VPoint, w: f32) -> (f32, f32, f32, f32) { 733 | if bevel { 734 | let x0 = p1.xy.x + p0.d.y * w; 735 | let y0 = p1.xy.y - p0.d.x * w; 736 | let x1 = p1.xy.x + p1.d.y * w; 737 | let y1 = p1.xy.y - p1.d.x * w; 738 | (x0, y0, x1, y1) 739 | } else { 740 | let x0 = p1.xy.x + p1.dm.x * w; 741 | let y0 = p1.xy.y + p1.dm.y * w; 742 | let x1 = p1.xy.x + p1.dm.x * w; 743 | let y1 = p1.xy.y + p1.dm.y * w; 744 | (x0, y0, x1, y1) 745 | } 746 | } 747 | 748 | unsafe fn round_join( 749 | mut dst: *mut Vertex, 750 | p0: &mut VPoint, 751 | p1: &mut VPoint, 752 | lw: f32, 753 | rw: f32, 754 | lu: f32, 755 | ru: f32, 756 | ncap: usize, 757 | _fringe: f32, 758 | ) -> *mut Vertex { 759 | let dlx0 = p0.d.y; 760 | let dly0 = -p0.d.x; 761 | let dlx1 = p1.d.y; 762 | let dly1 = -p1.d.x; 763 | 764 | if p1.flags.contains(PointFlags::PT_LEFT) { 765 | let (lx0, ly0, lx1, ly1) = 766 | choose_bevel(p1.flags.contains(PointFlags::PR_INNERBEVEL), p0, p1, lw); 767 | let a0 = -dly0.atan2(-dlx0); 768 | let mut a1 = -dly1.atan2(-dlx1); 769 | if a1 > a0 { 770 | a1 -= PI * 2.0; 771 | } 772 | 773 | *dst = Vertex::new(lx0, ly0, lu, 1.0); 774 | dst = dst.add(1); 775 | 776 | *dst = Vertex::new(p1.xy.x - dlx0 * rw, p1.xy.y - dly0 * rw, ru, 1.0); 777 | dst = dst.add(1); 778 | 779 | let n = ((((a0 - a1) / PI) * (ncap as f32)).ceil() as i32).clamped(2, ncap as i32); 780 | for i in 0..n { 781 | let u = (i as f32) / ((n - 1) as f32); 782 | let a = a0 + u * (a1 - a0); 783 | let rx = p1.xy.x + a.cos() * rw; 784 | let ry = p1.xy.y + a.sin() * rw; 785 | 786 | *dst = Vertex::new(p1.xy.x, p1.xy.y, 0.5, 1.0); 787 | dst = dst.add(1); 788 | 789 | *dst = Vertex::new(rx, ry, ru, 1.0); 790 | dst = dst.add(1); 791 | } 792 | 793 | *dst = Vertex::new(lx1, ly1, lu, 1.0); 794 | dst = dst.add(1); 795 | 796 | *dst = Vertex::new(p1.xy.x - dlx1 * rw, p1.xy.y - dly1 * rw, ru, 1.0); 797 | dst = dst.add(1); 798 | } else { 799 | let (rx0, ry0, rx1, ry1) = 800 | choose_bevel(p1.flags.contains(PointFlags::PR_INNERBEVEL), p0, p1, -rw); 801 | let a0 = dly0.atan2(dlx0); 802 | let mut a1 = dly1.atan2(dlx1); 803 | if a1 < a0 { 804 | a1 += PI * 2.0; 805 | } 806 | 807 | *dst = Vertex::new(p1.xy.x + dlx0 * rw, p1.xy.y + dly0 * rw, lu, 1.0); 808 | dst = dst.add(1); 809 | 810 | *dst = Vertex::new(rx0, ry0, ru, 1.0); 811 | dst = dst.add(1); 812 | 813 | let n = ((((a0 - a1) / PI) * (ncap as f32)).ceil() as i32).clamped(2, ncap as i32); 814 | for i in 0..n { 815 | let u = (i as f32) / ((n - 1) as f32); 816 | let a = a0 + u * (a1 - a0); 817 | let lx = p1.xy.x + a.cos() * lw; 818 | let ly = p1.xy.y + a.cos() * lw; 819 | 820 | *dst = Vertex::new(lx, ly, lu, 1.0); 821 | dst = dst.add(1); 822 | 823 | *dst = Vertex::new(p1.xy.x, p1.xy.y, 0.5, 1.0); 824 | dst = dst.add(1); 825 | } 826 | 827 | *dst = Vertex::new(p1.xy.x + dlx1 * rw, p1.xy.y + dly1 * rw, lu, 1.0); 828 | dst = dst.add(1); 829 | 830 | *dst = Vertex::new(rx1, ry1, ru, 1.0); 831 | dst = dst.add(1); 832 | } 833 | 834 | dst 835 | } 836 | 837 | unsafe fn bevel_join( 838 | mut dst: *mut Vertex, 839 | p0: &mut VPoint, 840 | p1: &mut VPoint, 841 | lw: f32, 842 | rw: f32, 843 | lu: f32, 844 | ru: f32, 845 | _fringe: f32, 846 | ) -> *mut Vertex { 847 | let dlx0 = p0.d.y; 848 | let dly0 = -p0.d.x; 849 | let dlx1 = p1.d.y; 850 | let dly1 = -p1.d.x; 851 | 852 | if p1.flags.contains(PointFlags::PT_LEFT) { 853 | let (lx0, ly0, lx1, ly1) = 854 | choose_bevel(p1.flags.contains(PointFlags::PR_INNERBEVEL), p0, p1, lw); 855 | 856 | *dst = Vertex::new(lx0, ly0, lu, 1.0); 857 | dst = dst.add(1); 858 | 859 | *dst = Vertex::new(p1.xy.x - dlx0 * rw, p1.xy.y - dly0 * rw, ru, 1.0); 860 | dst = dst.add(1); 861 | 862 | if p1.flags.contains(PointFlags::PT_BEVEL) { 863 | *dst = Vertex::new(lx0, ly0, lu, 1.0); 864 | dst = dst.add(1); 865 | 866 | *dst = Vertex::new(p1.xy.x - dlx0 * rw, p1.xy.y - dly0 * rw, ru, 1.0); 867 | dst = dst.add(1); 868 | 869 | *dst = Vertex::new(lx1, ly1, lu, 1.0); 870 | dst = dst.add(1); 871 | 872 | *dst = Vertex::new(p1.xy.x - dlx1 * rw, p1.xy.y - dly1 * rw, ru, 1.0); 873 | dst = dst.add(1); 874 | } else { 875 | let rx0 = p1.xy.x - p1.dm.x * rw; 876 | let ry0 = p1.xy.y - p1.dm.y * rw; 877 | 878 | *dst = Vertex::new(p1.xy.x, p1.xy.y, 0.5, 1.0); 879 | dst = dst.add(1); 880 | 881 | *dst = Vertex::new(p1.xy.x - dlx0 * rw, p1.xy.y - dly0 * rw, ru, 1.0); 882 | dst = dst.add(1); 883 | 884 | *dst = Vertex::new(rx0, ry0, ru, 1.0); 885 | dst = dst.add(1); 886 | 887 | *dst = Vertex::new(rx0, ry0, ru, 1.0); 888 | dst = dst.add(1); 889 | 890 | *dst = Vertex::new(p1.xy.x, p1.xy.y, 0.5, 1.0); 891 | dst = dst.add(1); 892 | 893 | *dst = Vertex::new(p1.xy.x - dlx1 * rw, p1.xy.y - dly1 * rw, ru, 1.0); 894 | dst = dst.add(1); 895 | } 896 | 897 | *dst = Vertex::new(lx1, ly1, lu, 1.0); 898 | dst = dst.add(1); 899 | 900 | *dst = Vertex::new(p1.xy.x - dlx1 * rw, p1.xy.y - dly1 * rw, ru, 1.0); 901 | dst = dst.add(1); 902 | } else { 903 | let (rx0, ry0, rx1, ry1) = 904 | choose_bevel(p1.flags.contains(PointFlags::PR_INNERBEVEL), p0, p1, -rw); 905 | 906 | *dst = Vertex::new(p1.xy.x + dlx0 * lw, p1.xy.y + dly0 * lw, lu, 1.0); 907 | dst = dst.add(1); 908 | 909 | *dst = Vertex::new(rx0, ry0, ru, 1.0); 910 | dst = dst.add(1); 911 | 912 | if p1.flags.contains(PointFlags::PT_BEVEL) { 913 | *dst = Vertex::new(p1.xy.x + dlx0 * lw, p1.xy.y + dly0 * lw, lu, 1.0); 914 | dst = dst.add(1); 915 | 916 | *dst = Vertex::new(rx0, ry0, ru, 1.0); 917 | dst = dst.add(1); 918 | 919 | *dst = Vertex::new(p1.xy.x + dlx1 * lw, p1.xy.y + dly1 * lw, lu, 1.0); 920 | dst = dst.add(1); 921 | 922 | *dst = Vertex::new(rx1, ry1, ru, 1.0); 923 | dst = dst.add(1); 924 | } else { 925 | let lx0 = p1.xy.x + p1.dm.x * lw; 926 | let ly0 = p1.xy.y + p1.dm.y * lw; 927 | 928 | *dst = Vertex::new(p1.xy.x + dlx0 * lw, p1.xy.y + dly0 * lw, lu, 1.0); 929 | dst = dst.add(1); 930 | 931 | *dst = Vertex::new(p1.xy.x, p1.xy.y, 0.5, 1.0); 932 | dst = dst.add(1); 933 | 934 | *dst = Vertex::new(lx0, ly0, lu, 1.0); 935 | dst = dst.add(1); 936 | 937 | *dst = Vertex::new(lx0, ly0, lu, 1.0); 938 | dst = dst.add(1); 939 | 940 | *dst = Vertex::new(p1.xy.x + dlx1 * lw, p1.xy.y + dly1 * lw, lu, 1.0); 941 | dst = dst.add(1); 942 | 943 | *dst = Vertex::new(p1.xy.x, p1.xy.y, 0.5, 1.0); 944 | dst = dst.add(1); 945 | } 946 | 947 | *dst = Vertex::new(p1.xy.x + dlx1 * lw, p1.xy.y + dly1 * lw, lu, 1.0); 948 | dst = dst.add(1); 949 | 950 | *dst = Vertex::new(rx1, ry1, ru, 1.0); 951 | dst = dst.add(1); 952 | } 953 | 954 | dst 955 | } 956 | 957 | unsafe fn butt_cap_start( 958 | mut dst: *mut Vertex, 959 | p: &mut VPoint, 960 | dx: f32, 961 | dy: f32, 962 | w: f32, 963 | d: f32, 964 | aa: f32, 965 | u0: f32, 966 | u1: f32, 967 | ) -> *mut Vertex { 968 | let px = p.xy.x - dx * d; 969 | let py = p.xy.y - dy * d; 970 | let dlx = dy; 971 | let dly = -dx; 972 | 973 | *dst = Vertex::new(px + dlx * w - dx * aa, py + dly * w - dy * aa, u0, 0.0); 974 | dst = dst.add(1); 975 | 976 | *dst = Vertex::new(px - dlx * w - dx * aa, py - dly * w - dy * aa, u1, 0.0); 977 | dst = dst.add(1); 978 | 979 | *dst = Vertex::new(px + dlx * w, py + dly * w, u0, 1.0); 980 | dst = dst.add(1); 981 | 982 | *dst = Vertex::new(px - dlx * w, py - dly * w, u1, 1.0); 983 | dst = dst.add(1); 984 | 985 | dst 986 | } 987 | 988 | unsafe fn butt_cap_end( 989 | mut dst: *mut Vertex, 990 | p: &mut VPoint, 991 | dx: f32, 992 | dy: f32, 993 | w: f32, 994 | d: f32, 995 | aa: f32, 996 | u0: f32, 997 | u1: f32, 998 | ) -> *mut Vertex { 999 | let px = p.xy.x - dx * d; 1000 | let py = p.xy.y - dy * d; 1001 | let dlx = dy; 1002 | let dly = -dx; 1003 | 1004 | *dst = Vertex::new(px + dlx * w, py + dly * w, u0, 1.0); 1005 | dst = dst.add(1); 1006 | 1007 | *dst = Vertex::new(px - dlx * w, py - dly * w, u1, 1.0); 1008 | dst = dst.add(1); 1009 | 1010 | *dst = Vertex::new(px + dlx * w + dx * aa, py + dly * w + dy * aa, u0, 0.0); 1011 | dst = dst.add(1); 1012 | 1013 | *dst = Vertex::new(px - dlx * w + dx * aa, py - dly * w + dy * aa, u1, 0.0); 1014 | dst = dst.add(1); 1015 | 1016 | dst 1017 | } 1018 | 1019 | unsafe fn round_cap_start( 1020 | mut dst: *mut Vertex, 1021 | p: &mut VPoint, 1022 | dx: f32, 1023 | dy: f32, 1024 | w: f32, 1025 | ncap: usize, 1026 | _aa: f32, 1027 | u0: f32, 1028 | u1: f32, 1029 | ) -> *mut Vertex { 1030 | let px = p.xy.x; 1031 | let py = p.xy.y; 1032 | let dlx = dy; 1033 | let dly = -dx; 1034 | 1035 | for i in 0..ncap { 1036 | let a = (i as f32) / ((ncap - 1) as f32) * PI; 1037 | let ax = a.cos() * w; 1038 | let ay = a.sin() * w; 1039 | 1040 | *dst = Vertex::new(px - dlx * ax - dx * ay, py - dly * ax - dy * ay, u0, 1.0); 1041 | dst = dst.add(1); 1042 | 1043 | *dst = Vertex::new(px, py, 0.5, 1.0); 1044 | dst = dst.add(1); 1045 | } 1046 | 1047 | *dst = Vertex::new(px + dlx * w, py + dly * w, u0, 1.0); 1048 | dst = dst.add(1); 1049 | 1050 | *dst = Vertex::new(px - dlx * w, py - dly * w, u1, 1.0); 1051 | dst = dst.add(1); 1052 | 1053 | dst 1054 | } 1055 | 1056 | unsafe fn round_cap_end( 1057 | mut dst: *mut Vertex, 1058 | p: &mut VPoint, 1059 | dx: f32, 1060 | dy: f32, 1061 | w: f32, 1062 | ncap: usize, 1063 | _aa: f32, 1064 | u0: f32, 1065 | u1: f32, 1066 | ) -> *mut Vertex { 1067 | let px = p.xy.x; 1068 | let py = p.xy.y; 1069 | let dlx = dy; 1070 | let dly = -dx; 1071 | 1072 | *dst = Vertex::new(px + dlx * w, py + dly * w, u0, 1.0); 1073 | dst = dst.add(1); 1074 | 1075 | *dst = Vertex::new(px - dlx * w, py - dly * w, u1, 1.0); 1076 | dst = dst.add(1); 1077 | 1078 | for i in 0..ncap { 1079 | let a = (i as f32) / ((ncap - 1) as f32) * PI; 1080 | let ax = a.cos() * w; 1081 | let ay = a.sin() * w; 1082 | 1083 | *dst = Vertex::new(px, py, 0.5, 1.0); 1084 | dst = dst.add(1); 1085 | 1086 | *dst = Vertex::new(px - dlx * ax + dx * ay, py - dly * ax + dy * ay, u0, 1.0); 1087 | dst = dst.add(1); 1088 | } 1089 | 1090 | dst 1091 | } 1092 | -------------------------------------------------------------------------------- /nona/src/color.rs: -------------------------------------------------------------------------------- 1 | use clamped::Clamp; 2 | use std::ops::Rem; 3 | 4 | #[derive(Debug, Copy, Clone, Default)] 5 | pub struct Color { 6 | pub r: f32, 7 | pub g: f32, 8 | pub b: f32, 9 | pub a: f32, 10 | } 11 | 12 | impl Color { 13 | pub fn hex(rgba: u32) -> Color { 14 | Color::rgba_i( 15 | (rgba >> 24 & 0xff) as _, 16 | (rgba >> 16 & 0xff) as _, 17 | (rgba >> 8 & 0xff) as _, 18 | (rgba & 0xff) as _, 19 | ) 20 | } 21 | 22 | pub fn rgba(r: f32, g: f32, b: f32, a: f32) -> Color { 23 | Color { r, g, b, a } 24 | } 25 | 26 | pub fn rgb(r: f32, g: f32, b: f32) -> Color { 27 | Color { r, g, b, a: 1.0 } 28 | } 29 | 30 | pub fn rgba_i(r: u8, g: u8, b: u8, a: u8) -> Color { 31 | Color { 32 | r: r as f32 / 255.0, 33 | g: g as f32 / 255.0, 34 | b: b as f32 / 255.0, 35 | a: a as f32 / 255.0, 36 | } 37 | } 38 | 39 | pub fn rgb_i(r: u8, g: u8, b: u8) -> Color { 40 | Self::rgba_i(r, g, b, 255) 41 | } 42 | 43 | pub fn lerp(self, c: Color, u: f32) -> Color { 44 | let u = u.clamped(0.0, 1.0); 45 | let om = 1.0 - u; 46 | Color { 47 | r: self.r * om + c.r * u, 48 | g: self.g * om + c.g * u, 49 | b: self.b * om + c.b * u, 50 | a: self.a * om + c.a * u, 51 | } 52 | } 53 | 54 | pub fn hsla(h: f32, s: f32, l: f32, a: f32) -> Color { 55 | let mut h = h.rem(1.0); 56 | if h < 0.0 { 57 | h += 1.0; 58 | } 59 | let s = s.clamped(0.0, 1.0); 60 | let l = l.clamped(0.0, 1.0); 61 | let m2 = if l <= 0.5 { 62 | l * (1.0 + s) 63 | } else { 64 | l + s - l * s 65 | }; 66 | let m1 = 2.0 * l - m2; 67 | Color { 68 | r: hue(h + 1.0 / 3.0, m1, m2).clamped(0.0, 1.0), 69 | g: hue(h, m1, m2).clamped(0.0, 1.0), 70 | b: hue(h - 1.0 / 3.0, m1, m2).clamped(0.0, 1.0), 71 | a, 72 | } 73 | } 74 | 75 | pub fn hsl(h: f32, s: f32, l: f32) -> Color { 76 | Self::hsla(h, s, l, 1.0) 77 | } 78 | } 79 | 80 | impl From<(f32, f32, f32)> for Color { 81 | fn from((r, g, b): (f32, f32, f32)) -> Self { 82 | Color::rgb(r, g, b) 83 | } 84 | } 85 | 86 | impl From<(f32, f32, f32, f32)> for Color { 87 | fn from((r, g, b, a): (f32, f32, f32, f32)) -> Self { 88 | Color::rgba(r, g, b, a) 89 | } 90 | } 91 | 92 | fn hue(mut h: f32, m1: f32, m2: f32) -> f32 { 93 | if h < 0.0 { 94 | h += 1.0; 95 | } 96 | if h > 1.0 { 97 | h -= 1.0 98 | }; 99 | if h < 1.0 / 6.0 { 100 | return m1 + (m2 - m1) * h * 6.0; 101 | } else if h < 3.0 / 6.0 { 102 | m2 103 | } else if h < 4.0 / 6.0 { 104 | m1 + (m2 - m1) * (2.0 / 3.0 - h) * 6.0 105 | } else { 106 | m1 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /nona/src/context.rs: -------------------------------------------------------------------------------- 1 | use crate::cache::PathCache; 2 | use crate::fonts::{FontId, Fonts, LayoutChar}; 3 | use crate::renderer::{Renderer, Scissor, TextureType}; 4 | use crate::{Color, Extent, NonaError, Point, Rect, Transform}; 5 | use clamped::Clamp; 6 | use std::f32::consts::PI; 7 | 8 | pub type ImageId = usize; 9 | 10 | const KAPPA90: f32 = 0.5522847493; 11 | 12 | #[derive(Debug, Copy, Clone)] 13 | pub struct Paint { 14 | pub xform: Transform, 15 | pub extent: Extent, 16 | pub radius: f32, 17 | pub feather: f32, 18 | pub inner_color: Color, 19 | pub outer_color: Color, 20 | pub image: Option, 21 | } 22 | 23 | #[derive(Debug, Copy, Clone)] 24 | pub enum Gradient { 25 | Linear { 26 | start: Point, 27 | end: Point, 28 | start_color: Color, 29 | end_color: Color, 30 | }, 31 | Radial { 32 | center: Point, 33 | in_radius: f32, 34 | out_radius: f32, 35 | inner_color: Color, 36 | outer_color: Color, 37 | }, 38 | Box { 39 | rect: Rect, 40 | radius: f32, 41 | feather: f32, 42 | inner_color: Color, 43 | outer_color: Color, 44 | }, 45 | } 46 | 47 | #[derive(Debug, Copy, Clone)] 48 | pub struct ImagePattern { 49 | pub center: Point, 50 | pub size: Extent, 51 | pub angle: f32, 52 | pub img: ImageId, 53 | pub alpha: f32, 54 | } 55 | 56 | impl From for Paint { 57 | fn from(grad: Gradient) -> Self { 58 | match grad { 59 | Gradient::Linear { 60 | start, 61 | end, 62 | start_color: inner_color, 63 | end_color: outer_color, 64 | } => { 65 | const LARGE: f32 = 1e5; 66 | 67 | let mut dx = end.x - start.x; 68 | let mut dy = end.y - start.y; 69 | let d = (dx * dx + dy * dy).sqrt(); 70 | 71 | if d > 0.0001 { 72 | dx /= d; 73 | dy /= d; 74 | } else { 75 | dx = 0.0; 76 | dy = 1.0; 77 | } 78 | 79 | Paint { 80 | xform: Transform([dy, -dx, dx, dy, start.x - dx * LARGE, start.y - dy * LARGE]), 81 | extent: Extent { 82 | width: LARGE, 83 | height: LARGE + d * 0.5, 84 | }, 85 | radius: 0.0, 86 | feather: d.max(1.0), 87 | inner_color, 88 | outer_color, 89 | image: None, 90 | } 91 | } 92 | Gradient::Radial { 93 | center, 94 | in_radius, 95 | out_radius, 96 | inner_color, 97 | outer_color, 98 | } => { 99 | let r = (in_radius + out_radius) * 0.5; 100 | let f = out_radius - in_radius; 101 | Paint { 102 | xform: Transform([1.0, 0.0, 0.0, 1.0, center.x, center.y]), 103 | extent: Extent { 104 | width: r, 105 | height: r, 106 | }, 107 | radius: r, 108 | feather: f.max(1.0), 109 | inner_color, 110 | outer_color, 111 | image: None, 112 | } 113 | } 114 | Gradient::Box { 115 | rect, 116 | radius, 117 | feather, 118 | inner_color, 119 | outer_color, 120 | } => { 121 | let Rect { xy, size } = rect; 122 | Paint { 123 | xform: Transform([ 124 | 1.0, 125 | 0.0, 126 | 0.0, 127 | 1.0, 128 | xy.x + size.width * 0.5, 129 | xy.y + size.height * 0.5, 130 | ]), 131 | extent: Extent::new(size.width * 0.5, size.height * 0.5), 132 | radius, 133 | feather: feather.max(1.0), 134 | inner_color, 135 | outer_color, 136 | image: None, 137 | } 138 | } 139 | } 140 | } 141 | } 142 | 143 | impl From for Paint { 144 | fn from(pat: ImagePattern) -> Self { 145 | let mut xform = Transform::rotate(pat.angle); 146 | xform.0[4] = pat.center.x; 147 | xform.0[5] = pat.center.y; 148 | Paint { 149 | xform, 150 | extent: pat.size, 151 | radius: 0.0, 152 | feather: 0.0, 153 | inner_color: Color::rgba(1.0, 1.0, 1.0, pat.alpha), 154 | outer_color: Color::rgba(1.0, 1.0, 1.0, pat.alpha), 155 | image: Some(pat.img), 156 | } 157 | } 158 | } 159 | 160 | impl + Clone> From for Paint { 161 | fn from(color: T) -> Self { 162 | Paint { 163 | xform: Transform::identity(), 164 | extent: Default::default(), 165 | radius: 0.0, 166 | feather: 1.0, 167 | inner_color: color.clone().into(), 168 | outer_color: color.into(), 169 | image: None, 170 | } 171 | } 172 | } 173 | 174 | #[derive(Debug, Copy, Clone, Eq, PartialEq)] 175 | pub enum Solidity { 176 | Solid, 177 | Hole, 178 | } 179 | 180 | #[derive(Debug, Copy, Clone, Eq, PartialEq)] 181 | pub enum LineJoin { 182 | Miter, 183 | Round, 184 | Bevel, 185 | } 186 | 187 | #[derive(Debug, Copy, Clone, Eq, PartialEq)] 188 | pub enum LineCap { 189 | Butt, 190 | Round, 191 | Square, 192 | } 193 | 194 | bitflags! { 195 | pub struct Align: u32 { 196 | const LEFT = 0x1; 197 | const CENTER = 0x2; 198 | const RIGHT = 0x4; 199 | const TOP = 0x8; 200 | const MIDDLE = 0x10; 201 | const BOTTOM = 0x20; 202 | const BASELINE = 0x40; 203 | } 204 | } 205 | 206 | #[derive(Debug, Copy, Clone)] 207 | pub enum BlendFactor { 208 | Zero, 209 | One, 210 | SrcColor, 211 | OneMinusSrcColor, 212 | DstColor, 213 | OneMinusDstColor, 214 | SrcAlpha, 215 | OneMinusSrcAlpha, 216 | DstAlpha, 217 | OneMinusDstAlpha, 218 | SrcAlphaSaturate, 219 | } 220 | 221 | #[derive(Debug, Copy, Clone)] 222 | pub enum BasicCompositeOperation { 223 | SrcOver, 224 | SrcIn, 225 | SrcOut, 226 | Atop, 227 | DstOver, 228 | DstIn, 229 | DstOut, 230 | DstAtop, 231 | Lighter, 232 | Copy, 233 | Xor, 234 | } 235 | 236 | #[derive(Debug, Copy, Clone)] 237 | pub enum CompositeOperation { 238 | Basic(BasicCompositeOperation), 239 | BlendFunc { 240 | src: BlendFactor, 241 | dst: BlendFactor, 242 | }, 243 | BlendFuncSeparate { 244 | src_rgb: BlendFactor, 245 | dst_rgb: BlendFactor, 246 | src_alpha: BlendFactor, 247 | dst_alpha: BlendFactor, 248 | }, 249 | } 250 | 251 | impl Into for CompositeOperation { 252 | fn into(self) -> CompositeOperationState { 253 | match self { 254 | CompositeOperation::Basic(op) => { 255 | let (src_factor, dst_factor) = match op { 256 | BasicCompositeOperation::SrcOver => { 257 | (BlendFactor::One, BlendFactor::OneMinusSrcAlpha) 258 | } 259 | BasicCompositeOperation::SrcIn => (BlendFactor::DstAlpha, BlendFactor::Zero), 260 | BasicCompositeOperation::SrcOut => { 261 | (BlendFactor::OneMinusDstAlpha, BlendFactor::Zero) 262 | } 263 | BasicCompositeOperation::Atop => { 264 | (BlendFactor::DstAlpha, BlendFactor::OneMinusSrcAlpha) 265 | } 266 | BasicCompositeOperation::DstOver => { 267 | (BlendFactor::OneMinusDstAlpha, BlendFactor::One) 268 | } 269 | BasicCompositeOperation::DstIn => (BlendFactor::Zero, BlendFactor::SrcAlpha), 270 | BasicCompositeOperation::DstOut => { 271 | (BlendFactor::Zero, BlendFactor::OneMinusSrcAlpha) 272 | } 273 | BasicCompositeOperation::DstAtop => { 274 | (BlendFactor::OneMinusDstAlpha, BlendFactor::SrcAlpha) 275 | } 276 | BasicCompositeOperation::Lighter => (BlendFactor::One, BlendFactor::One), 277 | BasicCompositeOperation::Copy => (BlendFactor::One, BlendFactor::Zero), 278 | BasicCompositeOperation::Xor => { 279 | (BlendFactor::OneMinusDstAlpha, BlendFactor::OneMinusSrcAlpha) 280 | } 281 | }; 282 | 283 | CompositeOperationState { 284 | src_rgb: src_factor, 285 | dst_rgb: dst_factor, 286 | src_alpha: src_factor, 287 | dst_alpha: dst_factor, 288 | } 289 | } 290 | CompositeOperation::BlendFunc { src, dst } => CompositeOperationState { 291 | src_rgb: src, 292 | dst_rgb: dst, 293 | src_alpha: src, 294 | dst_alpha: dst, 295 | }, 296 | CompositeOperation::BlendFuncSeparate { 297 | src_rgb, 298 | dst_rgb, 299 | src_alpha, 300 | dst_alpha, 301 | } => CompositeOperationState { 302 | src_rgb, 303 | dst_rgb, 304 | src_alpha, 305 | dst_alpha, 306 | }, 307 | } 308 | } 309 | } 310 | 311 | #[derive(Debug, Copy, Clone)] 312 | pub struct CompositeOperationState { 313 | pub src_rgb: BlendFactor, 314 | pub dst_rgb: BlendFactor, 315 | pub src_alpha: BlendFactor, 316 | pub dst_alpha: BlendFactor, 317 | } 318 | 319 | bitflags! { 320 | pub struct ImageFlags: u32 { 321 | const GENERATE_MIPMAPS = 0x1; 322 | const REPEATX = 0x2; 323 | const REPEATY = 0x4; 324 | const FLIPY = 0x8; 325 | const PREMULTIPLIED = 0x10; 326 | const NEAREST = 0x20; 327 | } 328 | } 329 | 330 | #[derive(Debug, Copy, Clone, Default)] 331 | pub struct Vertex { 332 | pub x: f32, 333 | pub y: f32, 334 | pub u: f32, 335 | pub v: f32, 336 | } 337 | 338 | impl Vertex { 339 | pub fn new(x: f32, y: f32, u: f32, v: f32) -> Vertex { 340 | Vertex { x, y, u, v } 341 | } 342 | } 343 | 344 | #[derive(Debug, Copy, Clone)] 345 | pub struct Path { 346 | pub(crate) first: usize, 347 | pub(crate) count: usize, 348 | pub(crate) closed: bool, 349 | pub(crate) num_bevel: usize, 350 | pub(crate) solidity: Solidity, 351 | pub(crate) fill: *mut Vertex, 352 | pub(crate) num_fill: usize, 353 | pub(crate) stroke: *mut Vertex, 354 | pub(crate) num_stroke: usize, 355 | pub convex: bool, 356 | } 357 | 358 | impl Path { 359 | pub fn get_fill(&self) -> &[Vertex] { 360 | if self.fill.is_null() { 361 | &[] 362 | } else { 363 | unsafe { std::slice::from_raw_parts_mut(self.fill, self.num_fill) } 364 | } 365 | } 366 | 367 | pub fn get_stroke(&self) -> &[Vertex] { 368 | if self.stroke.is_null() { 369 | &[] 370 | } else { 371 | unsafe { std::slice::from_raw_parts_mut(self.stroke, self.num_stroke) } 372 | } 373 | } 374 | } 375 | 376 | #[derive(Copy, Clone)] 377 | pub struct TextMetrics { 378 | pub ascender: f32, 379 | pub descender: f32, 380 | pub line_gap: f32, 381 | } 382 | 383 | impl TextMetrics { 384 | pub fn line_height(&self) -> f32 { 385 | self.ascender - self.descender + self.line_gap 386 | } 387 | } 388 | 389 | #[derive(Clone)] 390 | struct State { 391 | composite_operation: CompositeOperationState, 392 | shape_antialias: bool, 393 | fill: Paint, 394 | stroke: Paint, 395 | stroke_width: f32, 396 | miter_limit: f32, 397 | line_join: LineJoin, 398 | line_cap: LineCap, 399 | alpha: f32, 400 | xform: Transform, 401 | scissor: Scissor, 402 | font_size: f32, 403 | letter_spacing: f32, 404 | line_height: f32, 405 | text_align: Align, 406 | font_id: FontId, 407 | } 408 | 409 | impl Default for State { 410 | fn default() -> Self { 411 | State { 412 | composite_operation: CompositeOperation::Basic(BasicCompositeOperation::SrcOver).into(), 413 | shape_antialias: true, 414 | fill: Color::rgb(1.0, 1.0, 1.0).into(), 415 | stroke: Color::rgb(0.0, 0.0, 0.0).into(), 416 | stroke_width: 1.0, 417 | miter_limit: 10.0, 418 | line_join: LineJoin::Miter, 419 | line_cap: LineCap::Butt, 420 | alpha: 1.0, 421 | xform: Transform::identity(), 422 | scissor: Scissor { 423 | xform: Default::default(), 424 | extent: Extent { 425 | width: -1.0, 426 | height: -1.0, 427 | }, 428 | }, 429 | font_size: 16.0, 430 | letter_spacing: 0.0, 431 | line_height: 1.0, 432 | text_align: Align::LEFT | Align::BASELINE, 433 | font_id: 0, 434 | } 435 | } 436 | } 437 | 438 | #[derive(Debug)] 439 | pub(crate) enum Command { 440 | MoveTo(Point), 441 | LineTo(Point), 442 | BezierTo(Point, Point, Point), 443 | Close, 444 | Solidity(Solidity), 445 | } 446 | 447 | pub struct Context { 448 | commands: Vec, 449 | last_position: Point, 450 | states: Vec, 451 | cache: PathCache, 452 | tess_tol: f32, 453 | dist_tol: f32, 454 | fringe_width: f32, 455 | device_pixel_ratio: f32, 456 | fonts: Fonts, 457 | layout_chars: Vec, 458 | draw_call_count: usize, 459 | fill_triangles_count: usize, 460 | stroke_triangles_count: usize, 461 | text_triangles_count: usize, 462 | } 463 | 464 | pub struct Canvas<'a, R: Renderer> { 465 | context: &'a mut Context, 466 | renderer: &'a mut R, 467 | } 468 | 469 | impl<'a, R: Renderer> std::ops::Deref for Canvas<'a, R> { 470 | type Target = Context; 471 | 472 | fn deref(&self) -> &Self::Target { 473 | self.context 474 | } 475 | } 476 | impl std::ops::DerefMut for Canvas<'_, R> { 477 | fn deref_mut(&mut self) -> &mut Self::Target { 478 | self.context 479 | } 480 | } 481 | 482 | impl<'a, R: Renderer> Canvas<'a, R> { 483 | pub fn begin_frame(&mut self, clear_color: Option) -> Result<(), NonaError> { 484 | self.context.begin_frame(self.renderer, clear_color) 485 | } 486 | 487 | pub fn end_frame(&mut self) -> Result<(), NonaError> { 488 | self.context.end_frame(self.renderer) 489 | } 490 | 491 | pub fn create_image>( 492 | &mut self, 493 | flags: ImageFlags, 494 | data: D, 495 | ) -> Result { 496 | self.context.create_image(self.renderer, flags, data) 497 | } 498 | 499 | pub fn update_image(&mut self, img: ImageId, data: &[u8]) -> Result<(), NonaError> { 500 | self.context.update_image(self.renderer, img, data) 501 | } 502 | 503 | pub fn image_size(&self, img: ImageId) -> Result<(usize, usize), NonaError> { 504 | self.context.image_size(self.renderer, img) 505 | } 506 | 507 | pub fn delete_image(&mut self, img: ImageId) -> Result<(), NonaError> { 508 | self.context.delete_image(self.renderer, img) 509 | } 510 | 511 | pub fn fill(&mut self) -> Result<(), NonaError> { 512 | self.context.fill(self.renderer) 513 | } 514 | 515 | pub fn stroke(&mut self) -> Result<(), NonaError> { 516 | self.context.stroke(self.renderer) 517 | } 518 | 519 | pub fn text, P: Into>(&mut self, pt: P, text: S) -> Result<(), NonaError> { 520 | self.context.text(self.renderer, pt, text) 521 | } 522 | } 523 | 524 | impl Context { 525 | pub fn create(renderer: &mut R) -> Result { 526 | let fonts = Fonts::new(renderer)?; 527 | Ok(Context { 528 | commands: Default::default(), 529 | last_position: Default::default(), 530 | states: vec![Default::default()], 531 | cache: Default::default(), 532 | tess_tol: 0.0, 533 | dist_tol: 0.0, 534 | fringe_width: 0.0, 535 | device_pixel_ratio: 0.0, 536 | fonts, 537 | layout_chars: Default::default(), 538 | draw_call_count: 0, 539 | fill_triangles_count: 0, 540 | stroke_triangles_count: 0, 541 | text_triangles_count: 0, 542 | }) 543 | } 544 | 545 | fn set_device_pixel_ratio(&mut self, ratio: f32) { 546 | self.tess_tol = 0.25 / ratio; 547 | self.dist_tol = 0.01 / ratio; 548 | self.fringe_width = 1.0 / ratio; 549 | self.device_pixel_ratio = ratio; 550 | } 551 | 552 | pub fn attach_renderer( 553 | &mut self, 554 | renderer: &mut R, 555 | call: impl Fn(&mut Canvas), 556 | ) { 557 | let mut canvas = Canvas { 558 | context: self, 559 | renderer, 560 | }; 561 | call(&mut canvas) 562 | } 563 | 564 | pub fn begin_frame( 565 | &mut self, 566 | renderer: &mut R, 567 | clear_color: Option, 568 | ) -> Result<(), NonaError> { 569 | let device_pixel_ratio = { 570 | renderer.viewport(renderer.view_size().into(), renderer.device_pixel_ratio())?; 571 | if let Some(color) = clear_color { 572 | renderer.clear_screen(color) 573 | } 574 | renderer.device_pixel_ratio() 575 | }; 576 | self.set_device_pixel_ratio(device_pixel_ratio); 577 | self.states.clear(); 578 | self.states.push(Default::default()); 579 | self.draw_call_count = 0; 580 | self.fill_triangles_count = 0; 581 | self.stroke_triangles_count = 0; 582 | self.text_triangles_count = 0; 583 | Ok(()) 584 | } 585 | 586 | pub fn end_frame(&mut self, renderer: &mut R) -> Result<(), NonaError> { 587 | renderer.flush() 588 | } 589 | 590 | pub fn save(&mut self) { 591 | if let Some(last) = self.states.last() { 592 | let last = last.clone(); 593 | self.states.push(last); 594 | } 595 | } 596 | 597 | pub fn restore(&mut self) { 598 | if self.states.len() <= 1 { 599 | return; 600 | } 601 | self.states.pop(); 602 | } 603 | 604 | fn state(&mut self) -> &State { 605 | self.states.last().unwrap() 606 | } 607 | 608 | fn state_mut(&mut self) -> &mut State { 609 | self.states.last_mut().unwrap() 610 | } 611 | 612 | pub fn reset(&mut self) { 613 | *self.state_mut() = Default::default(); 614 | } 615 | 616 | pub fn shape_antialias(&mut self, enabled: bool) { 617 | self.state_mut().shape_antialias = enabled; 618 | } 619 | 620 | pub fn stroke_width(&mut self, width: f32) { 621 | self.state_mut().stroke_width = width; 622 | } 623 | 624 | pub fn miter_limit(&mut self, limit: f32) { 625 | self.state_mut().miter_limit = limit; 626 | } 627 | 628 | pub fn line_cap(&mut self, cap: LineCap) { 629 | self.state_mut().line_cap = cap; 630 | } 631 | 632 | pub fn line_join(&mut self, join: LineJoin) { 633 | self.state_mut().line_join = join; 634 | } 635 | 636 | pub fn global_alpha(&mut self, alpha: f32) { 637 | self.state_mut().alpha = alpha; 638 | } 639 | 640 | pub fn transform(&mut self, xform: Transform) { 641 | let state = self.state_mut(); 642 | state.xform = xform * state.xform; 643 | } 644 | 645 | pub fn reset_transform(&mut self) { 646 | self.state_mut().xform = Transform::identity(); 647 | } 648 | 649 | pub fn translate(&mut self, tx: f32, ty: f32) { 650 | self.transform(Transform::translate(tx, ty)); 651 | } 652 | 653 | pub fn rotate(&mut self, angle: f32) { 654 | self.transform(Transform::rotate(angle)); 655 | } 656 | 657 | pub fn skew_x(&mut self, angle: f32) { 658 | self.transform(Transform::skew_x(angle)); 659 | } 660 | 661 | pub fn skew_y(&mut self, angle: f32) { 662 | self.transform(Transform::skew_y(angle)); 663 | } 664 | 665 | pub fn scale(&mut self, sx: f32, sy: f32) { 666 | self.transform(Transform::scale(sx, sy)); 667 | } 668 | 669 | pub fn current_transform(&mut self) -> Transform { 670 | self.state().xform 671 | } 672 | 673 | pub fn stroke_paint>(&mut self, paint: T) { 674 | let mut paint = paint.into(); 675 | paint.xform *= self.state().xform; 676 | self.state_mut().stroke = paint; 677 | } 678 | 679 | pub fn fill_paint>(&mut self, paint: T) { 680 | let mut paint = paint.into(); 681 | paint.xform *= self.state().xform; 682 | self.state_mut().fill = paint; 683 | } 684 | 685 | pub fn create_image, R: Renderer>( 686 | &mut self, 687 | renderer: &mut R, 688 | flags: ImageFlags, 689 | data: D, 690 | ) -> Result { 691 | let img = image::load_from_memory(data.as_ref()) 692 | .map_err(|err| NonaError::Texture(err.to_string()))?; 693 | let img = img.to_rgba8(); 694 | let dimensions = img.dimensions(); 695 | let img = renderer.create_texture( 696 | TextureType::RGBA, 697 | dimensions.0 as usize, 698 | dimensions.1 as usize, 699 | flags, 700 | Some(&img.into_raw()), 701 | )?; 702 | Ok(img) 703 | } 704 | 705 | pub fn create_image_from_file, R: Renderer>( 706 | &mut self, 707 | renderer: &mut R, 708 | flags: ImageFlags, 709 | path: P, 710 | ) -> Result { 711 | self.create_image( 712 | renderer, 713 | flags, 714 | std::fs::read(path) 715 | .map_err(|err| NonaError::Texture(format!("Error loading image: {}", err)))?, 716 | ) 717 | } 718 | 719 | pub fn update_image( 720 | &mut self, 721 | renderer: &mut R, 722 | img: ImageId, 723 | data: &[u8], 724 | ) -> Result<(), NonaError> { 725 | let (w, h) = renderer.texture_size(img.clone())?; 726 | renderer.update_texture(img, 0, 0, w, h, data)?; 727 | Ok(()) 728 | } 729 | 730 | pub fn image_size( 731 | &self, 732 | renderer: &R, 733 | img: ImageId, 734 | ) -> Result<(usize, usize), NonaError> { 735 | let res = renderer.texture_size(img)?; 736 | Ok(res) 737 | } 738 | 739 | pub fn delete_image( 740 | &mut self, 741 | renderer: &mut R, 742 | img: ImageId, 743 | ) -> Result<(), NonaError> { 744 | renderer.delete_texture(img)?; 745 | Ok(()) 746 | } 747 | 748 | pub fn scissor>(&mut self, rect: T) { 749 | let rect = rect.into(); 750 | let state = self.state_mut(); 751 | let x = rect.xy.x; 752 | let y = rect.xy.y; 753 | let width = rect.size.width.max(0.0); 754 | let height = rect.size.height.max(0.0); 755 | state.scissor.xform = Transform::identity(); 756 | state.scissor.xform.0[4] = x + width * 0.5; 757 | state.scissor.xform.0[5] = y + height * 0.5; 758 | state.scissor.xform *= state.xform; 759 | state.scissor.extent.width = width * 0.5; 760 | state.scissor.extent.height = height * 0.5; 761 | } 762 | 763 | pub fn intersect_scissor>(&mut self, rect: T) { 764 | let rect = rect.into(); 765 | let state = self.state_mut(); 766 | 767 | if state.scissor.extent.width < 0.0 { 768 | self.scissor(rect); 769 | return; 770 | } 771 | 772 | let Extent { 773 | width: ex, 774 | height: ey, 775 | } = state.scissor.extent; 776 | let invxorm = state.xform.inverse(); 777 | let pxform = state.scissor.xform * invxorm; 778 | let tex = ex * pxform.0[0].abs() + ey * pxform.0[2].abs(); 779 | let tey = ex * pxform.0[1].abs() + ey * pxform.0[3].abs(); 780 | self.scissor( 781 | Rect::new( 782 | Point::new(pxform.0[4] - tex, pxform.0[5] - tey), 783 | Extent::new(tex * 2.0, tey * 2.0), 784 | ) 785 | .intersect(rect), 786 | ); 787 | } 788 | 789 | pub fn reset_scissor(&mut self) { 790 | let state = self.state_mut(); 791 | state.scissor.xform = Transform::default(); 792 | state.scissor.extent.width = -1.0; 793 | state.scissor.extent.height = -1.0; 794 | } 795 | 796 | pub fn global_composite_operation(&mut self, op: CompositeOperation) { 797 | self.state_mut().composite_operation = op.into(); 798 | } 799 | 800 | fn append_command(&mut self, cmd: Command) { 801 | let state = self.states.last().unwrap(); 802 | let xform = &state.xform; 803 | match cmd { 804 | Command::MoveTo(pt) => { 805 | self.commands 806 | .push(Command::MoveTo(xform.transform_point(pt))); 807 | self.last_position = pt; 808 | } 809 | Command::LineTo(pt) => { 810 | self.commands 811 | .push(Command::LineTo(xform.transform_point(pt))); 812 | self.last_position = pt; 813 | } 814 | Command::BezierTo(pt1, pt2, pt3) => { 815 | self.last_position = pt3; 816 | self.commands.push(Command::BezierTo( 817 | xform.transform_point(pt1), 818 | xform.transform_point(pt2), 819 | xform.transform_point(pt3), 820 | )); 821 | } 822 | _ => { 823 | self.commands.push(cmd); 824 | } 825 | } 826 | } 827 | 828 | pub fn begin_path(&mut self) { 829 | self.commands.clear(); 830 | self.cache.clear(); 831 | } 832 | 833 | pub fn move_to>(&mut self, pt: P) { 834 | self.append_command(Command::MoveTo(pt.into())); 835 | } 836 | 837 | pub fn line_to>(&mut self, pt: P) { 838 | self.append_command(Command::LineTo(pt.into())); 839 | } 840 | 841 | pub fn bezier_to>(&mut self, cp1: P, cp2: P, pt: P) { 842 | self.append_command(Command::BezierTo(cp1.into(), cp2.into(), pt.into())); 843 | } 844 | 845 | pub fn quad_to>(&mut self, cp: P, pt: P) { 846 | let x0 = self.last_position.x; 847 | let y0 = self.last_position.y; 848 | let cp = cp.into(); 849 | let pt = pt.into(); 850 | self.append_command(Command::BezierTo( 851 | Point::new(x0 + 2.0 / 3.0 * (cp.x - x0), y0 + 2.0 / 3.0 * (cp.y - y0)), 852 | Point::new( 853 | pt.x + 2.0 / 3.0 * (cp.x - pt.x), 854 | pt.y + 2.0 / 3.0 * (cp.y - pt.y), 855 | ), 856 | pt, 857 | )); 858 | } 859 | 860 | pub fn arc_to>(&mut self, pt1: P, pt2: P, radius: f32) { 861 | let pt0 = self.last_position; 862 | 863 | if self.commands.is_empty() { 864 | return; 865 | } 866 | 867 | let pt1 = pt1.into(); 868 | let pt2 = pt2.into(); 869 | if pt0.equals(pt1, self.dist_tol) 870 | || pt1.equals(pt2, self.dist_tol) 871 | || pt1.dist_pt_seg(pt0, pt2) < self.dist_tol * self.dist_tol 872 | || radius < self.dist_tol 873 | { 874 | self.line_to(pt1); 875 | return; 876 | } 877 | 878 | let d0 = Point::new(pt0.x - pt1.x, pt0.y - pt1.y); 879 | let d1 = Point::new(pt2.x - pt1.x, pt2.y - pt1.y); 880 | let a = (d0.x * d1.x + d0.y * d1.y).cos(); 881 | let d = radius / (a / 2.0).tan(); 882 | 883 | if d > 10000.0 { 884 | self.line_to(pt1); 885 | return; 886 | } 887 | 888 | let (cx, cy, a0, a1, dir) = if Point::cross(d0, d1) > 0.0 { 889 | ( 890 | pt1.x + d0.x * d + d0.y * radius, 891 | pt1.y + d0.y * d + -d0.x * radius, 892 | d0.x.atan2(-d0.y), 893 | -d1.x.atan2(d1.y), 894 | Solidity::Hole, 895 | ) 896 | } else { 897 | ( 898 | pt1.x + d0.x * d + -d0.y * radius, 899 | pt1.y + d0.y * d + d0.x * radius, 900 | -d0.x.atan2(d0.y), 901 | d1.x.atan2(-d1.y), 902 | Solidity::Solid, 903 | ) 904 | }; 905 | 906 | self.arc(Point::new(cx, cy), radius, a0, a1, dir); 907 | } 908 | 909 | pub fn close_path(&mut self) { 910 | self.commands.push(Command::Close); 911 | } 912 | 913 | pub fn path_solidity(&mut self, dir: Solidity) { 914 | self.commands.push(Command::Solidity(dir)); 915 | } 916 | 917 | pub fn arc>(&mut self, cp: P, radius: f32, a0: f32, a1: f32, dir: Solidity) { 918 | let cp = cp.into(); 919 | let move_ = self.commands.is_empty(); 920 | 921 | let mut da = a1 - a0; 922 | if dir == Solidity::Hole { 923 | if da.abs() >= PI * 2.0 { 924 | da = PI * 2.0; 925 | } else { 926 | while da < 0.0 { 927 | da += PI * 2.0; 928 | } 929 | } 930 | } else { 931 | if da.abs() >= PI * 2.0 { 932 | da = -PI * 2.0; 933 | } else { 934 | while da > 0.0 { 935 | da -= PI * 2.0; 936 | } 937 | } 938 | } 939 | 940 | let ndivs = ((da.abs() / (PI * 0.5) + 0.5) as i32).min(5).max(1); 941 | let hda = (da / (ndivs as f32)) / 2.0; 942 | let mut kappa = (4.0 / 3.0 * (1.0 - hda.cos()) / hda.sin()).abs(); 943 | 944 | if dir == Solidity::Solid { 945 | kappa = -kappa; 946 | } 947 | 948 | let mut px = 0.0; 949 | let mut py = 0.0; 950 | let mut ptanx = 0.0; 951 | let mut ptany = 0.0; 952 | 953 | for i in 0..=ndivs { 954 | let a = a0 + da * ((i as f32) / (ndivs as f32)); 955 | let dx = a.cos(); 956 | let dy = a.sin(); 957 | let x = cp.x + dx * radius; 958 | let y = cp.y + dy * radius; 959 | let tanx = -dy * radius * kappa; 960 | let tany = dx * radius * kappa; 961 | 962 | if i == 0 { 963 | if move_ { 964 | self.append_command(Command::MoveTo(Point::new(x, y))); 965 | } else { 966 | self.append_command(Command::LineTo(Point::new(x, y))); 967 | } 968 | } else { 969 | self.append_command(Command::BezierTo( 970 | Point::new(px + ptanx, py + ptany), 971 | Point::new(x - tanx, y - tany), 972 | Point::new(x, y), 973 | )); 974 | } 975 | px = x; 976 | py = y; 977 | ptanx = tanx; 978 | ptany = tany; 979 | } 980 | } 981 | 982 | pub fn rect>(&mut self, rect: T) { 983 | let rect = rect.into(); 984 | self.append_command(Command::MoveTo(Point::new(rect.xy.x, rect.xy.y))); 985 | self.append_command(Command::LineTo(Point::new( 986 | rect.xy.x, 987 | rect.xy.y + rect.size.height, 988 | ))); 989 | self.append_command(Command::LineTo(Point::new( 990 | rect.xy.x + rect.size.width, 991 | rect.xy.y + rect.size.height, 992 | ))); 993 | self.append_command(Command::LineTo(Point::new( 994 | rect.xy.x + rect.size.width, 995 | rect.xy.y, 996 | ))); 997 | self.append_command(Command::Close); 998 | } 999 | 1000 | pub fn rounded_rect>(&mut self, rect: T, radius: f32) { 1001 | let rect = rect.into(); 1002 | self.rounded_rect_varying(rect, radius, radius, radius, radius); 1003 | } 1004 | 1005 | pub fn rounded_rect_varying>( 1006 | &mut self, 1007 | rect: T, 1008 | lt: f32, 1009 | rt: f32, 1010 | rb: f32, 1011 | lb: f32, 1012 | ) { 1013 | let rect = rect.into(); 1014 | if lt < 0.1 && rt < 0.1 && lb < 0.1 && rb < 0.1 { 1015 | self.rect(rect); 1016 | } else { 1017 | let halfw = rect.size.width.abs() * 0.5; 1018 | let halfh = rect.size.height.abs() * 0.5; 1019 | let rxlb = lb.min(halfw) * rect.size.width.signum(); 1020 | let rylb = lb.min(halfh) * rect.size.height.signum(); 1021 | let rxrb = rb.min(halfw) * rect.size.width.signum(); 1022 | let ryrb = rb.min(halfh) * rect.size.height.signum(); 1023 | let rxrt = rt.min(halfw) * rect.size.width.signum(); 1024 | let ryrt = rt.min(halfh) * rect.size.height.signum(); 1025 | let rxlt = lt.min(halfw) * rect.size.width.signum(); 1026 | let rylt = lt.min(halfh) * rect.size.height.signum(); 1027 | 1028 | self.append_command(Command::MoveTo(Point::new(rect.xy.x, rect.xy.y + rylt))); 1029 | self.append_command(Command::LineTo(Point::new( 1030 | rect.xy.x, 1031 | rect.xy.y + rect.size.height - rylb, 1032 | ))); 1033 | self.append_command(Command::BezierTo( 1034 | Point::new( 1035 | rect.xy.x, 1036 | rect.xy.y + rect.size.height - rylb * (1.0 - KAPPA90), 1037 | ), 1038 | Point::new( 1039 | rect.xy.x + rxlb * (1.0 - KAPPA90), 1040 | rect.xy.y + rect.size.height, 1041 | ), 1042 | Point::new(rect.xy.x + rxlb, rect.xy.y + rect.size.height), 1043 | )); 1044 | self.append_command(Command::LineTo(Point::new( 1045 | rect.xy.x + rect.size.width - rxrb, 1046 | rect.xy.y + rect.size.height, 1047 | ))); 1048 | self.append_command(Command::BezierTo( 1049 | Point::new( 1050 | rect.xy.x + rect.size.width - rxrb * (1.0 - KAPPA90), 1051 | rect.xy.y + rect.size.height, 1052 | ), 1053 | Point::new( 1054 | rect.xy.x + rect.size.width, 1055 | rect.xy.y + rect.size.height - ryrb * (1.0 - KAPPA90), 1056 | ), 1057 | Point::new( 1058 | rect.xy.x + rect.size.width, 1059 | rect.xy.y + rect.size.height - ryrb, 1060 | ), 1061 | )); 1062 | self.append_command(Command::LineTo(Point::new( 1063 | rect.xy.x + rect.size.width, 1064 | rect.xy.y + ryrt, 1065 | ))); 1066 | self.append_command(Command::BezierTo( 1067 | Point::new( 1068 | rect.xy.x + rect.size.width, 1069 | rect.xy.y + ryrt * (1.0 - KAPPA90), 1070 | ), 1071 | Point::new( 1072 | rect.xy.x + rect.size.width - rxrt * (1.0 - KAPPA90), 1073 | rect.xy.y, 1074 | ), 1075 | Point::new(rect.xy.x + rect.size.width - rxrt, rect.xy.y), 1076 | )); 1077 | self.append_command(Command::LineTo(Point::new(rect.xy.x + rxlt, rect.xy.y))); 1078 | self.append_command(Command::BezierTo( 1079 | Point::new(rect.xy.x + rxlt * (1.0 - KAPPA90), rect.xy.y), 1080 | Point::new(rect.xy.x, rect.xy.y + rylt * (1.0 - KAPPA90)), 1081 | Point::new(rect.xy.x, rect.xy.y + rylt), 1082 | )); 1083 | self.append_command(Command::Close); 1084 | } 1085 | } 1086 | 1087 | pub fn ellipse>(&mut self, center: P, radius_x: f32, radius_y: f32) { 1088 | let center = center.into(); 1089 | self.append_command(Command::MoveTo(Point::new(center.x - radius_x, center.y))); 1090 | self.append_command(Command::BezierTo( 1091 | Point::new(center.x - radius_x, center.y + radius_y * KAPPA90), 1092 | Point::new(center.x - radius_x * KAPPA90, center.y + radius_y), 1093 | Point::new(center.x, center.y + radius_y), 1094 | )); 1095 | self.append_command(Command::BezierTo( 1096 | Point::new(center.x + radius_x * KAPPA90, center.y + radius_y), 1097 | Point::new(center.x + radius_x, center.y + radius_y * KAPPA90), 1098 | Point::new(center.x + radius_x, center.y), 1099 | )); 1100 | self.append_command(Command::BezierTo( 1101 | Point::new(center.x + radius_x, center.y - radius_y * KAPPA90), 1102 | Point::new(center.x + radius_x * KAPPA90, center.y - radius_y), 1103 | Point::new(center.x, center.y - radius_y), 1104 | )); 1105 | self.append_command(Command::BezierTo( 1106 | Point::new(center.x - radius_x * KAPPA90, center.y - radius_y), 1107 | Point::new(center.x - radius_x, center.y - radius_y * KAPPA90), 1108 | Point::new(center.x - radius_x, center.y), 1109 | )); 1110 | self.append_command(Command::Close); 1111 | } 1112 | 1113 | pub fn circle>(&mut self, center: P, radius: f32) { 1114 | self.ellipse(center.into(), radius, radius); 1115 | } 1116 | 1117 | pub fn fill(&mut self, renderer: &mut R) -> Result<(), NonaError> { 1118 | let state = self.states.last_mut().unwrap(); 1119 | let mut fill_paint = state.fill.clone(); 1120 | 1121 | self.cache 1122 | .flatten_paths(&self.commands, self.dist_tol, self.tess_tol); 1123 | if renderer.edge_antialias() && state.shape_antialias { 1124 | self.cache 1125 | .expand_fill(self.fringe_width, LineJoin::Miter, 2.4, self.fringe_width); 1126 | } else { 1127 | self.cache 1128 | .expand_fill(0.0, LineJoin::Miter, 2.4, self.fringe_width); 1129 | } 1130 | 1131 | fill_paint.inner_color.a *= state.alpha; 1132 | fill_paint.outer_color.a *= state.alpha; 1133 | 1134 | renderer.fill( 1135 | &fill_paint, 1136 | state.composite_operation, 1137 | &state.scissor, 1138 | self.fringe_width, 1139 | self.cache.bounds, 1140 | &self.cache.paths, 1141 | )?; 1142 | 1143 | for path in &self.cache.paths { 1144 | if path.num_fill > 2 { 1145 | self.fill_triangles_count += path.num_fill - 2; 1146 | } 1147 | if path.num_stroke > 2 { 1148 | self.fill_triangles_count += path.num_stroke - 2; 1149 | } 1150 | self.draw_call_count += 2; 1151 | } 1152 | 1153 | Ok(()) 1154 | } 1155 | 1156 | pub fn stroke(&mut self, renderer: &mut R) -> Result<(), NonaError> { 1157 | let state = self.states.last_mut().unwrap(); 1158 | let scale = state.xform.average_scale(); 1159 | let mut stroke_width = (state.stroke_width * scale).clamped(0.0, 200.0); 1160 | let mut stroke_paint = state.stroke.clone(); 1161 | 1162 | if stroke_width < self.fringe_width { 1163 | let alpha = (stroke_width / self.fringe_width).clamped(0.0, 1.0); 1164 | stroke_paint.inner_color.a *= alpha * alpha; 1165 | stroke_paint.outer_color.a *= alpha * alpha; 1166 | stroke_width = self.fringe_width; 1167 | } 1168 | 1169 | stroke_paint.inner_color.a *= state.alpha; 1170 | stroke_paint.outer_color.a *= state.alpha; 1171 | 1172 | self.cache 1173 | .flatten_paths(&self.commands, self.dist_tol, self.tess_tol); 1174 | 1175 | if renderer.edge_antialias() && state.shape_antialias { 1176 | self.cache.expand_stroke( 1177 | stroke_width * 0.5, 1178 | self.fringe_width, 1179 | state.line_cap, 1180 | state.line_join, 1181 | state.miter_limit, 1182 | self.tess_tol, 1183 | ); 1184 | } else { 1185 | self.cache.expand_stroke( 1186 | stroke_width * 0.5, 1187 | 0.0, 1188 | state.line_cap, 1189 | state.line_join, 1190 | state.miter_limit, 1191 | self.tess_tol, 1192 | ); 1193 | } 1194 | 1195 | renderer.stroke( 1196 | &stroke_paint, 1197 | state.composite_operation, 1198 | &state.scissor, 1199 | self.fringe_width, 1200 | stroke_width, 1201 | &self.cache.paths, 1202 | )?; 1203 | 1204 | for path in &self.cache.paths { 1205 | self.fill_triangles_count += path.num_stroke - 2; 1206 | self.draw_call_count += 1; 1207 | } 1208 | 1209 | Ok(()) 1210 | } 1211 | 1212 | pub fn create_font_from_file, P: AsRef>( 1213 | &mut self, 1214 | name: N, 1215 | path: P, 1216 | ) -> Result { 1217 | self.create_font( 1218 | name, 1219 | std::fs::read(path) 1220 | .map_err(|err| NonaError::Texture(format!("Error loading image: {}", err)))?, 1221 | ) 1222 | } 1223 | 1224 | pub fn create_font, D: Into>>( 1225 | &mut self, 1226 | name: N, 1227 | data: D, 1228 | ) -> Result { 1229 | self.fonts.add_font(name, data) 1230 | } 1231 | 1232 | pub fn find_font>(&self, name: N) -> Option { 1233 | self.fonts.find(name.as_ref()) 1234 | } 1235 | 1236 | pub fn add_fallback_fontid(&mut self, base: FontId, fallback: FontId) { 1237 | self.fonts.add_fallback(base, fallback); 1238 | } 1239 | 1240 | pub fn add_fallback_font, N2: AsRef>(&mut self, base: N1, fallback: N2) { 1241 | if let (Some(base), Some(fallback)) = (self.find_font(base), self.find_font(fallback)) { 1242 | self.fonts.add_fallback(base, fallback); 1243 | } 1244 | } 1245 | 1246 | pub fn font_size(&mut self, size: f32) { 1247 | self.state_mut().font_size = size; 1248 | } 1249 | 1250 | pub fn text_letter_spacing(&mut self, spacing: f32) { 1251 | self.state_mut().letter_spacing = spacing; 1252 | } 1253 | 1254 | pub fn text_line_height(&mut self, line_height: f32) { 1255 | self.state_mut().line_height = line_height; 1256 | } 1257 | 1258 | pub fn text_align(&mut self, align: Align) { 1259 | self.state_mut().text_align = align; 1260 | } 1261 | 1262 | pub fn fontid(&mut self, id: FontId) { 1263 | self.state_mut().font_id = id; 1264 | } 1265 | 1266 | pub fn font>(&mut self, name: N) { 1267 | if let Some(id) = self.find_font(name) { 1268 | self.state_mut().font_id = id; 1269 | } 1270 | } 1271 | 1272 | pub fn text, P: Into, R: Renderer>( 1273 | &mut self, 1274 | renderer: &mut R, 1275 | pt: P, 1276 | text: S, 1277 | ) -> Result<(), NonaError> { 1278 | let state = self.states.last().unwrap(); 1279 | let scale = state.xform.font_scale() * self.device_pixel_ratio; 1280 | let invscale = 1.0 / scale; 1281 | let pt = pt.into(); 1282 | 1283 | self.fonts.layout_text( 1284 | renderer, 1285 | text.as_ref(), 1286 | state.font_id, 1287 | (pt.x * scale, pt.y * scale).into(), 1288 | state.font_size * scale, 1289 | state.text_align, 1290 | state.letter_spacing * scale, 1291 | true, 1292 | &mut self.layout_chars, 1293 | )?; 1294 | 1295 | self.cache.vertexes.clear(); 1296 | 1297 | for lc in &self.layout_chars { 1298 | let lt = Point::new(lc.bounds.min.x * invscale, lc.bounds.min.y * invscale); 1299 | let rt = Point::new(lc.bounds.max.x * invscale, lc.bounds.min.y * invscale); 1300 | let lb = Point::new(lc.bounds.min.x * invscale, lc.bounds.max.y * invscale); 1301 | let rb = Point::new(lc.bounds.max.x * invscale, lc.bounds.max.y * invscale); 1302 | 1303 | self.cache 1304 | .vertexes 1305 | .push(Vertex::new(lt.x, lt.y, lc.uv.min.x, lc.uv.min.y)); 1306 | self.cache 1307 | .vertexes 1308 | .push(Vertex::new(rb.x, rb.y, lc.uv.max.x, lc.uv.max.y)); 1309 | self.cache 1310 | .vertexes 1311 | .push(Vertex::new(rt.x, rt.y, lc.uv.max.x, lc.uv.min.y)); 1312 | 1313 | self.cache 1314 | .vertexes 1315 | .push(Vertex::new(lt.x, lt.y, lc.uv.min.x, lc.uv.min.y)); 1316 | self.cache 1317 | .vertexes 1318 | .push(Vertex::new(lb.x, lb.y, lc.uv.min.x, lc.uv.max.y)); 1319 | self.cache 1320 | .vertexes 1321 | .push(Vertex::new(rb.x, rb.y, lc.uv.max.x, lc.uv.max.y)); 1322 | } 1323 | 1324 | let mut paint = state.fill.clone(); 1325 | paint.image = Some(self.fonts.img.clone()); 1326 | paint.inner_color.a *= state.alpha; 1327 | paint.outer_color.a *= state.alpha; 1328 | 1329 | renderer.triangles( 1330 | &paint, 1331 | state.composite_operation, 1332 | &state.scissor, 1333 | &self.cache.vertexes, 1334 | )?; 1335 | Ok(()) 1336 | } 1337 | 1338 | pub fn text_metrics(&self) -> TextMetrics { 1339 | let state = self.states.last().unwrap(); 1340 | let scale = state.xform.font_scale() * self.device_pixel_ratio; 1341 | self.fonts 1342 | .text_metrics(state.font_id, state.font_size * scale) 1343 | } 1344 | 1345 | pub fn text_size>(&self, text: S) -> Extent { 1346 | let state = self.states.last().unwrap(); 1347 | let scale = state.xform.font_scale() * self.device_pixel_ratio; 1348 | self.fonts.text_size( 1349 | text.as_ref(), 1350 | state.font_id, 1351 | state.font_size * scale, 1352 | state.letter_spacing * scale, 1353 | ) 1354 | } 1355 | } 1356 | -------------------------------------------------------------------------------- /nona/src/errors.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | #[derive(Error, Debug)] 4 | pub enum NonaError { 5 | #[error("ERR_TEXTURE: {0}")] 6 | Texture(String), 7 | 8 | #[error("ERR_SHADER: {0}")] 9 | Shader(String), 10 | 11 | #[error("ERR_FONT: {0}")] 12 | Font(String), 13 | } 14 | -------------------------------------------------------------------------------- /nona/src/fonts.rs: -------------------------------------------------------------------------------- 1 | use crate::context::{ImageId, TextMetrics}; 2 | use crate::renderer::TextureType; 3 | use crate::{Align, Bounds, Extent, ImageFlags, NonaError, Renderer}; 4 | use bitflags::_core::borrow::Borrow; 5 | use rusttype::gpu_cache::Cache; 6 | use rusttype::{Font, Glyph, Point, PositionedGlyph, Scale}; 7 | use slab::Slab; 8 | use std::{ 9 | collections::HashMap, 10 | error::Error, 11 | fmt::{Debug, Display}, 12 | }; 13 | 14 | const TEX_WIDTH: usize = 1024; 15 | const TEX_HEIGHT: usize = 1024; 16 | 17 | pub type FontId = usize; 18 | 19 | #[derive(Debug)] 20 | pub struct LayoutChar { 21 | id: FontId, 22 | pub x: f32, 23 | pub next_x: f32, 24 | pub c: char, 25 | pub idx: usize, 26 | glyph: PositionedGlyph<'static>, 27 | pub uv: Bounds, 28 | pub bounds: Bounds, 29 | } 30 | 31 | #[derive(Debug)] 32 | struct FontData { 33 | font: Font<'static>, 34 | fallback_fonts: Vec, 35 | } 36 | 37 | pub struct Fonts { 38 | fonts: Slab, 39 | fonts_by_name: HashMap, 40 | cache: Cache<'static>, 41 | pub(crate) img: ImageId, 42 | } 43 | 44 | impl Debug for Fonts { 45 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 46 | write!(f, "Fonts") 47 | } 48 | } 49 | 50 | #[derive(Debug)] 51 | pub struct FontError { 52 | pub message: String, 53 | } 54 | 55 | impl Display for FontError { 56 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 57 | write!(f, "{:?}", self) 58 | } 59 | } 60 | impl Error for FontError { 61 | fn source(&self) -> Option<&(dyn Error + 'static)> { 62 | None 63 | } 64 | } 65 | 66 | impl Fonts { 67 | pub fn new(renderer: &mut R) -> Result { 68 | Ok(Fonts { 69 | fonts: Default::default(), 70 | fonts_by_name: Default::default(), 71 | img: renderer.create_texture( 72 | TextureType::Alpha, 73 | TEX_WIDTH, 74 | TEX_HEIGHT, 75 | ImageFlags::empty(), 76 | None, 77 | )?, 78 | cache: Cache::builder() 79 | .multithread(true) 80 | .dimensions(TEX_WIDTH as u32, TEX_HEIGHT as u32) 81 | .build(), 82 | }) 83 | } 84 | 85 | pub fn add_font, D: Into>>( 86 | &mut self, 87 | name: N, 88 | data: D, 89 | ) -> Result { 90 | let font = Font::try_from_vec(data.into()) 91 | .ok_or(NonaError::Font(String::from("Incorrect font data format")))?; 92 | let fd = FontData { 93 | font, 94 | fallback_fonts: Default::default(), 95 | }; 96 | let id = self.fonts.insert(fd); 97 | self.fonts_by_name.insert(name.into(), id); 98 | Ok(id) 99 | } 100 | 101 | pub fn find>(&self, name: N) -> Option { 102 | self.fonts_by_name.get(name.borrow()).map(ToOwned::to_owned) 103 | } 104 | 105 | pub fn add_fallback(&mut self, base: FontId, fallback: FontId) { 106 | if let Some(fd) = self.fonts.get_mut(base) { 107 | fd.fallback_fonts.push(fallback); 108 | } 109 | } 110 | 111 | fn glyph(&self, id: FontId, c: char) -> Option<(FontId, Glyph<'static>)> { 112 | if let Some(fd) = self.fonts.get(id) { 113 | let glyph = fd.font.glyph(c); 114 | if glyph.id().0 != 0 { 115 | Some((id, glyph)) 116 | } else { 117 | for id in &fd.fallback_fonts { 118 | if let Some(fd) = self.fonts.get(*id) { 119 | let glyph = fd.font.glyph(c); 120 | if glyph.id().0 != 0 { 121 | return Some((*id, glyph)); 122 | } 123 | } 124 | } 125 | None 126 | } 127 | } else { 128 | None 129 | } 130 | } 131 | 132 | fn render_texture(&mut self, renderer: &mut R) -> Result<(), NonaError> { 133 | let img = self.img.clone(); 134 | self.cache 135 | .cache_queued(move |rect, data| { 136 | renderer 137 | .update_texture( 138 | img.clone(), 139 | rect.min.x as usize, 140 | rect.min.y as usize, 141 | (rect.max.x - rect.min.x) as usize, 142 | (rect.max.y - rect.min.y) as usize, 143 | data, 144 | ) 145 | .unwrap(); 146 | }) 147 | .map_err(|err| NonaError::Texture(err.to_string()))?; 148 | Ok(()) 149 | } 150 | 151 | pub fn text_metrics(&self, id: FontId, size: f32) -> TextMetrics { 152 | if let Some(fd) = self.fonts.get(id) { 153 | let scale = Scale::uniform(size); 154 | let v_metrics = fd.font.v_metrics(scale); 155 | TextMetrics { 156 | ascender: v_metrics.descent, 157 | descender: v_metrics.descent, 158 | line_gap: v_metrics.line_gap, 159 | } 160 | } else { 161 | TextMetrics { 162 | ascender: 0.0, 163 | descender: 0.0, 164 | line_gap: 0.0, 165 | } 166 | } 167 | } 168 | 169 | pub fn text_size(&self, text: &str, id: FontId, size: f32, spacing: f32) -> Extent { 170 | if let Some(fd) = self.fonts.get(id) { 171 | let scale = Scale::uniform(size); 172 | let v_metrics = fd.font.v_metrics(scale); 173 | let mut extent = Extent::new( 174 | 0.0, 175 | v_metrics.ascent - v_metrics.descent + v_metrics.line_gap, 176 | ); 177 | let mut last_glyph = None; 178 | let mut char_count = 0; 179 | 180 | for c in text.chars() { 181 | if let Some((_, glyph)) = self.glyph(id, c) { 182 | let glyph = glyph.scaled(scale); 183 | let h_metrics = glyph.h_metrics(); 184 | extent.width += h_metrics.advance_width; 185 | 186 | if let Some(last_glyph) = last_glyph { 187 | extent.width += fd.font.pair_kerning(scale, last_glyph, glyph.id()); 188 | } 189 | 190 | last_glyph = Some(glyph.id()); 191 | char_count += 1; 192 | } 193 | } 194 | 195 | if char_count >= 2 { 196 | extent.width += spacing * (char_count - 1) as f32; 197 | } 198 | 199 | extent 200 | } else { 201 | Default::default() 202 | } 203 | } 204 | 205 | pub fn layout_text( 206 | &mut self, 207 | renderer: &mut R, 208 | text: &str, 209 | id: FontId, 210 | position: crate::Point, 211 | size: f32, 212 | align: Align, 213 | spacing: f32, 214 | cache: bool, 215 | result: &mut Vec, 216 | ) -> Result<(), NonaError> { 217 | result.clear(); 218 | 219 | if let Some(fd) = self.fonts.get(id) { 220 | let mut offset = Point { x: 0.0, y: 0.0 }; 221 | let scale = Scale::uniform(size); 222 | let v_metrics = fd.font.v_metrics(scale); 223 | 224 | let sz = if align.contains(Align::CENTER) 225 | || align.contains(Align::RIGHT) 226 | || align.contains(Align::MIDDLE) 227 | { 228 | self.text_size(text, id, size, spacing) 229 | } else { 230 | Extent::new(0.0, 0.0) 231 | }; 232 | 233 | if align.contains(Align::CENTER) { 234 | offset.x -= sz.width / 2.0; 235 | } else if align.contains(Align::RIGHT) { 236 | offset.x -= sz.width; 237 | } 238 | 239 | if align.contains(Align::MIDDLE) { 240 | offset.y = v_metrics.descent + sz.height / 2.0; 241 | } else if align.contains(Align::BOTTOM) { 242 | offset.y = v_metrics.descent; 243 | } else if align.contains(Align::TOP) { 244 | offset.y = v_metrics.ascent; 245 | } 246 | 247 | let mut position = Point { 248 | x: position.x + offset.x, 249 | y: position.y + offset.y, 250 | }; 251 | let mut last_glyph = None; 252 | 253 | for (idx, c) in text.chars().enumerate() { 254 | if let Some((id, glyph)) = self.glyph(id, c) { 255 | let g = glyph.scaled(scale); 256 | let h_metrics = g.h_metrics(); 257 | 258 | let glyph = g.positioned(Point { 259 | x: position.x, 260 | y: position.y, 261 | }); 262 | 263 | let mut next_x = position.x + h_metrics.advance_width; 264 | if let Some(last_glyph) = last_glyph { 265 | next_x += fd.font.pair_kerning(scale, last_glyph, glyph.id()); 266 | } 267 | 268 | if let Some(bb) = glyph.pixel_bounding_box() { 269 | self.cache.queue_glyph(id, glyph.clone()); 270 | 271 | result.push(LayoutChar { 272 | id, 273 | idx, 274 | c, 275 | x: position.x, 276 | next_x, 277 | glyph: glyph.clone(), 278 | uv: Default::default(), 279 | bounds: Bounds { 280 | min: (bb.min.x as f32, bb.min.y as f32).into(), 281 | max: (bb.max.x as f32, bb.max.y as f32).into(), 282 | }, 283 | }); 284 | } 285 | 286 | position.x = next_x; 287 | last_glyph = Some(glyph.id()); 288 | } 289 | } 290 | 291 | if cache { 292 | self.render_texture(renderer)?; 293 | 294 | for lc in result { 295 | if let Ok(Some((uv, _))) = self.cache.rect_for(lc.id, &lc.glyph) { 296 | lc.uv = Bounds { 297 | min: crate::Point { 298 | x: uv.min.x, 299 | y: uv.min.y, 300 | }, 301 | max: crate::Point { 302 | x: uv.max.x, 303 | y: uv.max.y, 304 | }, 305 | }; 306 | } 307 | } 308 | } 309 | } 310 | 311 | Ok(()) 312 | } 313 | } 314 | -------------------------------------------------------------------------------- /nona/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate bitflags; 3 | 4 | mod cache; 5 | mod color; 6 | mod context; 7 | mod errors; 8 | mod fonts; 9 | mod math; 10 | pub mod renderer; 11 | 12 | pub use color::*; 13 | pub use context::{ 14 | Align, BasicCompositeOperation, BlendFactor, Canvas, CompositeOperation, Context, Gradient, 15 | ImageFlags, ImageId, ImagePattern, LineCap, LineJoin, Paint, Solidity, TextMetrics, 16 | }; 17 | pub use errors::*; 18 | pub use fonts::FontId; 19 | pub use math::*; 20 | pub use renderer::Renderer; 21 | -------------------------------------------------------------------------------- /nona/src/math.rs: -------------------------------------------------------------------------------- 1 | use std::ops::{Add, Mul, MulAssign}; 2 | 3 | #[derive(Debug, Copy, Clone, Default)] 4 | pub struct Point { 5 | pub x: f32, 6 | pub y: f32, 7 | } 8 | 9 | impl Add for Point { 10 | type Output = Point; 11 | 12 | fn add(self, rhs: Self) -> Self::Output { 13 | Point { 14 | x: self.x + rhs.x, 15 | y: self.y + rhs.y, 16 | } 17 | } 18 | } 19 | 20 | impl Point { 21 | pub fn new(x: f32, y: f32) -> Point { 22 | Point { x, y } 23 | } 24 | 25 | pub(crate) fn equals(self, pt: Point, tol: f32) -> bool { 26 | let dx = pt.x - self.x; 27 | let dy = pt.y - self.y; 28 | dx * dx + dy * dy < tol * tol 29 | } 30 | 31 | pub(crate) fn dist_pt_seg(self, p: Point, q: Point) -> f32 { 32 | let pqx = q.x - p.x; 33 | let pqy = q.y - p.y; 34 | let dx = self.x - p.x; 35 | let dy = self.y - p.y; 36 | let d = pqx * pqx + pqy * pqy; 37 | let mut t = pqx * dx + pqy * dy; 38 | if d > 0.0 { 39 | t /= d; 40 | } 41 | if t < 0.0 { 42 | t = 0.0 43 | } else if t > 1.0 { 44 | t = 1.0 45 | }; 46 | let dx = p.x + t * pqx - self.x; 47 | let dy = p.y + t * pqy - self.y; 48 | dx * dx + dy * dy 49 | } 50 | 51 | pub(crate) fn normalize(&mut self) -> f32 { 52 | let d = ((self.x) * (self.x) + (self.y) * (self.y)).sqrt(); 53 | if d > 1e-6 { 54 | let id = 1.0 / d; 55 | self.x *= id; 56 | self.y *= id; 57 | } 58 | d 59 | } 60 | 61 | pub(crate) fn cross(pt1: Point, pt2: Point) -> f32 { 62 | pt2.x * pt1.y - pt1.x * pt2.y 63 | } 64 | 65 | pub fn offset(&self, tx: f32, ty: f32) -> Point { 66 | Point::new(self.x + tx, self.y + ty) 67 | } 68 | } 69 | 70 | impl From<(f32, f32)> for Point { 71 | fn from((x, y): (f32, f32)) -> Self { 72 | Point::new(x, y) 73 | } 74 | } 75 | 76 | impl From<(i32, i32)> for Point { 77 | fn from((x, y): (i32, i32)) -> Self { 78 | Point::new(x as f32, y as f32) 79 | } 80 | } 81 | 82 | #[derive(Debug, Copy, Clone, Default)] 83 | pub struct Extent { 84 | pub width: f32, 85 | pub height: f32, 86 | } 87 | 88 | impl Extent { 89 | pub fn new(width: f32, height: f32) -> Extent { 90 | Extent { width, height } 91 | } 92 | } 93 | 94 | impl From<(f32, f32)> for Extent { 95 | fn from((width, height): (f32, f32)) -> Self { 96 | Extent::new(width, height) 97 | } 98 | } 99 | 100 | #[derive(Debug, Copy, Clone, Default)] 101 | pub struct Rect { 102 | pub xy: Point, 103 | pub size: Extent, 104 | } 105 | 106 | impl Rect { 107 | pub fn new(xy: Point, size: Extent) -> Rect { 108 | Rect { xy, size } 109 | } 110 | 111 | pub fn intersect(self, rect: Rect) -> Rect { 112 | let Rect { 113 | xy: Point { x: ax, y: ay }, 114 | size: Extent { 115 | width: aw, 116 | height: ah, 117 | }, 118 | } = rect; 119 | 120 | let Rect { 121 | xy: Point { x: bx, y: by }, 122 | size: Extent { 123 | width: bw, 124 | height: bh, 125 | }, 126 | } = rect; 127 | 128 | let minx = ax.max(bx); 129 | let miny = ay.max(by); 130 | let maxx = (ax + aw).min(bx + bw); 131 | let maxy = (ay + ah).min(by + bh); 132 | Self::new( 133 | Point::new(minx, miny), 134 | Extent::new((maxx - minx).max(0.0), (maxy - miny).max(0.0)), 135 | ) 136 | } 137 | 138 | pub fn grow(&self, width: f32, height: f32) -> Rect { 139 | Rect::new( 140 | self.xy.offset(-width / 2.0, -height / 2.0), 141 | Extent::new(self.size.width + width, self.size.height + height), 142 | ) 143 | } 144 | } 145 | 146 | impl From<(f32, f32, f32, f32)> for Rect { 147 | fn from((x, y, w, h): (f32, f32, f32, f32)) -> Self { 148 | Rect::new((x, y).into(), (w, h).into()) 149 | } 150 | } 151 | 152 | #[derive(Debug, Copy, Clone, Default)] 153 | pub struct Bounds { 154 | pub min: Point, 155 | pub max: Point, 156 | } 157 | 158 | impl Bounds { 159 | pub fn width(&self) -> f32 { 160 | self.max.x - self.min.x 161 | } 162 | 163 | pub fn height(&self) -> f32 { 164 | self.max.y - self.min.y 165 | } 166 | 167 | pub fn left_top(&self) -> Point { 168 | self.min 169 | } 170 | 171 | pub fn right_top(&self) -> Point { 172 | Point::new(self.max.x, self.min.y) 173 | } 174 | 175 | pub fn left_bottom(&self) -> Point { 176 | Point::new(self.min.x, self.max.y) 177 | } 178 | 179 | pub fn right_bottom(&self) -> Point { 180 | self.max 181 | } 182 | } 183 | 184 | #[derive(Debug, Copy, Clone, Default)] 185 | pub struct Transform(pub [f32; 6]); 186 | 187 | impl Transform { 188 | pub fn identity() -> Transform { 189 | Transform([1.0, 0.0, 0.0, 1.0, 0.0, 0.0]) 190 | } 191 | 192 | pub fn translate(tx: f32, ty: f32) -> Transform { 193 | Transform([1.0, 0.0, 0.0, 1.0, tx, ty]) 194 | } 195 | 196 | pub fn scale(sx: f32, sy: f32) -> Transform { 197 | Transform([sx, 0.0, 0.0, sy, 0.0, 0.0]) 198 | } 199 | 200 | pub fn rotate(a: f32) -> Transform { 201 | let cs = a.cos(); 202 | let sn = a.sin(); 203 | Transform([cs, sn, -sn, cs, 0.0, 0.0]) 204 | } 205 | 206 | pub fn skew_x(a: f32) -> Transform { 207 | Transform([1.0, 0.0, a.tan(), 1.0, 0.0, 0.0]) 208 | } 209 | 210 | pub fn skew_y(a: f32) -> Transform { 211 | Transform([1.0, a.tan(), 0.0, 1.0, 0.0, 0.0]) 212 | } 213 | 214 | pub fn pre_multiply(self, rhs: Self) -> Self { 215 | rhs * self 216 | } 217 | 218 | pub fn inverse(self) -> Transform { 219 | let t = &self.0; 220 | let det = t[0] * t[3] - t[2] * t[1]; 221 | if det > -1e-6 && det < 1e-6 { 222 | return Transform::identity(); 223 | } 224 | let invdet = 1.0 / det; 225 | let mut inv = [0f32; 6]; 226 | inv[0] = t[3] * invdet; 227 | inv[2] = -t[2] * invdet; 228 | inv[4] = (t[2] * t[5] - t[3] * t[4]) * invdet; 229 | inv[1] = -t[1] * invdet; 230 | inv[3] = t[0] * invdet; 231 | inv[5] = (t[1] * t[4] - t[0] * t[5]) * invdet; 232 | Transform(inv) 233 | } 234 | 235 | pub fn transform_point(&self, pt: Point) -> Point { 236 | let t = &self.0; 237 | Point::new( 238 | pt.x * t[0] + pt.y * t[2] + t[4], 239 | pt.x * t[1] + pt.y * t[3] + t[5], 240 | ) 241 | } 242 | 243 | pub(crate) fn average_scale(&self) -> f32 { 244 | let t = &self.0; 245 | let sx = (t[0] * t[0] + t[2] * t[2]).sqrt(); 246 | let sy = (t[1] * t[1] + t[3] * t[3]).sqrt(); 247 | (sx + sy) * 0.5 248 | } 249 | 250 | pub(crate) fn font_scale(&self) -> f32 { 251 | let a = self.average_scale(); 252 | let d = 0.01f32; 253 | (a / d).ceil() * d 254 | } 255 | } 256 | 257 | impl Mul for Transform { 258 | type Output = Transform; 259 | 260 | fn mul(mut self, rhs: Self) -> Self::Output { 261 | let t = &mut self.0; 262 | let s = &rhs.0; 263 | let t0 = t[0] * s[0] + t[1] * s[2]; 264 | let t2 = t[2] * s[0] + t[3] * s[2]; 265 | let t4 = t[4] * s[0] + t[5] * s[2] + s[4]; 266 | t[1] = t[0] * s[1] + t[1] * s[3]; 267 | t[3] = t[2] * s[1] + t[3] * s[3]; 268 | t[5] = t[4] * s[1] + t[5] * s[3] + s[5]; 269 | t[0] = t0; 270 | t[2] = t2; 271 | t[4] = t4; 272 | self 273 | } 274 | } 275 | 276 | impl MulAssign for Transform { 277 | fn mul_assign(&mut self, rhs: Self) { 278 | *self = *self * rhs; 279 | } 280 | } 281 | 282 | impl From<(f32, f32, f32, f32, f32, f32)> for Transform { 283 | fn from((a1, a2, a3, a4, a5, a6): (f32, f32, f32, f32, f32, f32)) -> Self { 284 | Transform([a1, a2, a3, a4, a5, a6]) 285 | } 286 | } 287 | 288 | impl From<[f32; 6]> for Transform { 289 | fn from(values: [f32; 6]) -> Self { 290 | let mut values2 = [0.0; 6]; 291 | for i in 0..6 { 292 | values2[i] = values[i]; 293 | } 294 | Transform(values2) 295 | } 296 | } 297 | -------------------------------------------------------------------------------- /nona/src/renderer.rs: -------------------------------------------------------------------------------- 1 | pub use crate::context::{CompositeOperationState, ImageId, Path, Vertex}; 2 | pub use crate::*; 3 | 4 | #[derive(Debug, Copy, Clone)] 5 | pub enum TextureType { 6 | RGBA, 7 | Alpha, 8 | } 9 | 10 | #[derive(Debug, Copy, Clone)] 11 | pub struct Scissor { 12 | pub xform: Transform, 13 | pub extent: Extent, 14 | } 15 | 16 | pub trait Renderer { 17 | fn edge_antialias(&self) -> bool; 18 | 19 | fn view_size(&self) -> (f32, f32); 20 | 21 | fn device_pixel_ratio(&self) -> f32; 22 | 23 | fn create_texture( 24 | &mut self, 25 | texture_type: TextureType, 26 | width: usize, 27 | height: usize, 28 | flags: ImageFlags, 29 | data: Option<&[u8]>, 30 | ) -> Result; 31 | 32 | fn delete_texture(&mut self, img: ImageId) -> Result<(), NonaError>; 33 | 34 | fn update_texture( 35 | &mut self, 36 | img: ImageId, 37 | x: usize, 38 | y: usize, 39 | width: usize, 40 | height: usize, 41 | data: &[u8], 42 | ) -> Result<(), NonaError>; 43 | 44 | fn texture_size(&self, img: ImageId) -> Result<(usize, usize), NonaError>; 45 | 46 | fn viewport(&mut self, extent: Extent, device_pixel_ratio: f32) -> Result<(), NonaError>; 47 | 48 | fn clear_screen(&mut self, color: Color); 49 | 50 | fn flush(&mut self) -> Result<(), NonaError>; 51 | 52 | fn fill( 53 | &mut self, 54 | paint: &Paint, 55 | composite_operation: CompositeOperationState, 56 | scissor: &Scissor, 57 | fringe: f32, 58 | bounds: Bounds, 59 | paths: &[Path], 60 | ) -> Result<(), NonaError>; 61 | 62 | fn stroke( 63 | &mut self, 64 | paint: &Paint, 65 | composite_operation: CompositeOperationState, 66 | scissor: &Scissor, 67 | fringe: f32, 68 | stroke_width: f32, 69 | paths: &[Path], 70 | ) -> Result<(), NonaError>; 71 | 72 | fn triangles( 73 | &mut self, 74 | paint: &Paint, 75 | composite_operation: CompositeOperationState, 76 | scissor: &Scissor, 77 | vertexes: &[Vertex], 78 | ) -> Result<(), NonaError>; 79 | } 80 | -------------------------------------------------------------------------------- /nonaquad/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ['Nokola'] 3 | description = "Vector anti-aliased graphics renderer for Android, WebGL, iOS, Windows, Linux, and Mac using miniquad." 4 | edition = '2018' 5 | homepage = "https://github.com/nokola/nonaquad/" 6 | license = "MIT OR Apache-2.0" 7 | name = 'nonaquad' 8 | repository = "https://github.com/nokola/nonaquad/" 9 | version = '0.1.2' 10 | # documentation = "TODO, leave empty for default docs.rs" 11 | categories = ["graphics", "rendering"] 12 | keywords = ["gamedev", "graphics", "antialiased", "UI", "cross-platform"] 13 | readme = "../README.md" 14 | 15 | [dependencies] 16 | anyhow = "1.0.26" 17 | glam = {version = "0.17.3", features = ["scalar-math"]} 18 | nona = {path = "../nona", version = "0.1.1"} 19 | slab = "0.4.2" 20 | # note: color-backtrace does not work for wasm32-unknown-unknown due to memmap dependency 21 | # color-backtrace = { version = "0.3" } 22 | 23 | [dependencies.miniquad] 24 | # path = '..\miniquad' 25 | version = "0.3.0-alpha.37" 26 | # git = "https://github.com/not-fl3/miniquad" 27 | 28 | # Uncomment for small builds 29 | [profile.release] 30 | lto = "thin" 31 | 32 | [package.metadata.android] 33 | # Specifies the array of targets to build for. 34 | # Defaults to "armv7-linux-androideabi", "aarch64-linux-android", "i686-linux-android". 35 | build_targets = ["armv7-linux-androideabi"] 36 | fullscreen = true 37 | opengles_version_major = 2 38 | opengles_version_minor = 0 39 | package_name = "nokola.app.drawaa" 40 | 41 | [package.metadata.android.application_attributes] 42 | "android:debuggable" = "true" 43 | "android:hardwareAccelerated" = "true" 44 | -------------------------------------------------------------------------------- /nonaquad/examples/Roboto-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nokola/nonaquad/b87fde1cb22a33b059ff5af3917ec10f8c7d1089/nonaquad/examples/Roboto-Bold.ttf -------------------------------------------------------------------------------- /nonaquad/examples/drawaa.rs: -------------------------------------------------------------------------------- 1 | use miniquad::*; 2 | use nona::{Align, Color, Gradient, Paint, Point}; 3 | use nonaquad::nvgimpl; 4 | // use nona::widgets::{Widget, Button}; 5 | // use nonaquad::nvgimpl_orig as nvgimpl; 6 | 7 | struct Stage { 8 | renderer: nvgimpl::Renderer, 9 | nona: nona::Context, 10 | } 11 | 12 | impl Stage { 13 | pub fn new(ctx: &mut Context) -> Stage { 14 | let mut renderer = nvgimpl::Renderer::create(ctx).unwrap(); 15 | let mut nona = nona::Context::create(&mut renderer.with_context(ctx)).unwrap(); 16 | 17 | // for demo: load font by embedding into binary 18 | let font_data: &'static [u8] = include_bytes!("Roboto-Bold.ttf"); 19 | nona.create_font("roboto", font_data).unwrap(); 20 | 21 | // use this to load fonts dynamically at runtime: 22 | // nona.create_font_from_file("roboto", "examples/Roboto-Bold.ttf") 23 | // .unwrap(); 24 | Stage { renderer, nona } 25 | } 26 | } 27 | 28 | impl EventHandler for Stage { 29 | fn update(&mut self, _ctx: &mut Context) {} 30 | 31 | fn draw(&mut self, ctx: &mut Context) { 32 | // let ctx = get_context(); 33 | 34 | self.nona 35 | .attach_renderer(&mut self.renderer.with_context(ctx), |canvas| { 36 | canvas 37 | .begin_frame(Some(Color::rgb_i(128, 128, 255))) 38 | .unwrap(); 39 | 40 | // uncomment to draw a lot of circles - more than maximum GPU vertices on openGL ES 2/WebGL 41 | // note: performance is currently low, very CPU-bound. Something to fix in the future. 42 | // for i in 0..405 { 43 | // canvas.begin_path(); 44 | // // canvas.rect((100.0, 100.0, 400.0, 300.0)); 45 | // canvas.circle(Point::new(i as f32, 110.), 32.); 46 | // canvas.fill_paint(Paint::from(Color::rgb_i(255, (i as u32 % 256 as u32) as u8, 0))); 47 | // canvas.fill().unwrap(); 48 | // } 49 | 50 | canvas.begin_path(); 51 | // canvas.rect((100.0, 100.0, 400.0, 300.0)); 52 | canvas.rounded_rect((100.0, 100.0, 400.0, 300.0), 10.0); 53 | canvas.fill_paint(Gradient::Linear { 54 | start: (100, 100).into(), 55 | end: (400, 400).into(), 56 | start_color: Color::hex(0x2c21e8FF), 57 | end_color: Color::hex(0x3c78e6FF), 58 | }); 59 | canvas.fill().unwrap(); 60 | 61 | canvas.begin_path(); 62 | canvas.font("roboto"); 63 | canvas.font_size(28.0); 64 | canvas.text_align(Align::MIDDLE | Align::CENTER); 65 | canvas.fill_paint(Color::hex(0xff3138FF)); 66 | canvas 67 | .text((290, 250), format!("Hello world!")) 68 | .unwrap(); 69 | 70 | // canvas.begin_path(); 71 | // canvas.rect((100.0, 100.0, 300.0, 300.0)); 72 | // canvas.fill_paint(nona::Gradient::Linear { 73 | // start: (100, 100).into(), 74 | // end: (400, 400).into(), 75 | // start_color: nona::Color::rgb_i(0xAA, 0x6C, 0x39), 76 | // end_color: nona::Color::rgb_i(0x88, 0x2D, 0x60), 77 | // }); 78 | // canvas.fill().unwrap(); 79 | 80 | let origin = (150.0, 140.0); 81 | canvas.begin_path(); 82 | canvas.circle(origin, 64.0); 83 | canvas.move_to(origin); 84 | canvas.line_to((origin.0 + 300.0, origin.1 - 50.0)); 85 | canvas.stroke_paint(Color::rgba(1.0, 1.0, 0.0, 1.0)); 86 | canvas.stroke_width(3.0); 87 | canvas.stroke().unwrap(); 88 | 89 | let origin: Point = Point::new(100.0, 100.0); 90 | 91 | canvas.begin_path(); 92 | canvas.move_to(origin + nona::Point::new(2.0, 17.0)); 93 | canvas.bezier_to( 94 | origin + nona::Point::new(2.0, 17.0), 95 | origin + nona::Point::new(-15.0, 82.0), 96 | origin + nona::Point::new(-15.0, 83.0), 97 | ); 98 | canvas.bezier_to( 99 | origin + nona::Point::new(-16.0, 85.0), 100 | origin + nona::Point::new(167.0, 84.0), 101 | origin + nona::Point::new(42.0, 65.0), 102 | ); 103 | canvas.bezier_to( 104 | origin + nona::Point::new(11.0, 60.0), 105 | origin + nona::Point::new(71.0, 30.0), 106 | origin + nona::Point::new(71.0, 30.0), 107 | ); 108 | canvas.fill_paint(Color::rgba(0.0, 1.0, 0.0, 1.0)); 109 | canvas.fill().unwrap(); 110 | 111 | canvas.end_frame().unwrap(); 112 | }); 113 | 114 | ctx.commit_frame(); 115 | } 116 | } 117 | 118 | fn main() { 119 | // color_backtrace::install(); 120 | 121 | miniquad::start( 122 | conf::Conf { 123 | high_dpi: true, 124 | window_title: String::from("Draw test"), 125 | ..Default::default() 126 | }, 127 | |mut ctx| UserData::owning(Stage::new(&mut ctx), ctx), 128 | ); 129 | } 130 | -------------------------------------------------------------------------------- /nonaquad/examples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | TITLE 7 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /nonaquad/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate anyhow; 2 | 3 | pub mod nvgimpl; 4 | // pub mod nvgimpl_orig; 5 | 6 | #[cfg(test)] 7 | mod tests { 8 | #[test] 9 | fn it_works() { 10 | assert_eq!(2 + 2, 4); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /nonaquad/src/nvgimpl.rs: -------------------------------------------------------------------------------- 1 | use glam::{Mat4, Vec4}; 2 | use miniquad::graphics::Context as MiniContext; 3 | use miniquad::graphics::*; 4 | use nona::{renderer::*, NonaError}; 5 | use slab::Slab; 6 | 7 | enum ShaderType { 8 | FillGradient, 9 | FillImage, 10 | Simple, 11 | Image, 12 | } 13 | 14 | #[derive(PartialEq, Eq, Debug)] 15 | enum CallType { 16 | Fill, 17 | ConvexFill, 18 | Stroke, 19 | Triangles, 20 | } 21 | 22 | /// Color and Alpha blend states 23 | struct Blend { 24 | pub color: BlendState, 25 | pub alpha: BlendState, 26 | } 27 | 28 | impl From for Blend { 29 | fn from(state: CompositeOperationState) -> Self { 30 | Blend { 31 | color: BlendState::new( 32 | Equation::Add, 33 | convert_blend_factor(state.src_rgb), 34 | convert_blend_factor(state.dst_rgb), 35 | ), 36 | alpha: BlendState::new( 37 | Equation::Add, 38 | convert_blend_factor(state.src_alpha), 39 | convert_blend_factor(state.dst_alpha), 40 | ), 41 | } 42 | } 43 | } 44 | 45 | struct Call { 46 | call_type: CallType, 47 | image: Option, 48 | path_offset: usize, 49 | path_count: usize, 50 | triangle_offset: usize, 51 | triangle_count: usize, 52 | uniform_offset: usize, 53 | blend_func: Blend, 54 | } 55 | 56 | struct Texture { 57 | tex: miniquad::Texture, 58 | flags: ImageFlags, 59 | } 60 | 61 | impl Drop for Texture { 62 | fn drop(&mut self) { 63 | self.tex.delete(); 64 | } 65 | } 66 | 67 | struct GLPath { 68 | fill_offset: usize, 69 | fill_count: usize, 70 | stroke_offset: usize, 71 | stroke_count: usize, 72 | } 73 | 74 | pub struct Renderer { 75 | // shader: Shader, 76 | textures: Slab, // TODO_REPLACE: bindings.images 77 | view: Extent, 78 | // vert_buf: GLuint, TODO_REMOVE 79 | // vert_arr: GLuint, TODO_REMOVE 80 | pipeline: Pipeline, 81 | bindings: Bindings, 82 | calls: Vec, 83 | paths: Vec, 84 | vertexes: Vec, 85 | indices: Vec, 86 | uniforms: Vec, 87 | } 88 | 89 | pub struct RendererCtx<'a> { 90 | renderer: &'a mut Renderer, 91 | ctx: &'a mut MiniContext, 92 | } 93 | 94 | impl Renderer { 95 | pub fn with_context<'a>(&'a mut self, ctx: &'a mut MiniContext) -> RendererCtx<'a> { 96 | RendererCtx { 97 | renderer: self, 98 | ctx, 99 | } 100 | } 101 | } 102 | 103 | mod shader { 104 | use miniquad::*; 105 | 106 | pub const VERTEX: &str = include_str!("shader.vert"); 107 | pub const FRAGMENT: &str = include_str!("shader.frag"); 108 | 109 | pub const ATTRIBUTES: &[VertexAttribute] = &[ 110 | VertexAttribute::new("vertex", VertexFormat::Float2), 111 | VertexAttribute::new("tcoord", VertexFormat::Float2), 112 | ]; 113 | pub fn meta() -> ShaderMeta { 114 | ShaderMeta { 115 | images: vec!["tex".to_string()], 116 | uniforms: UniformBlockLayout { 117 | uniforms: vec![ 118 | UniformDesc::new("viewSize", UniformType::Float2), 119 | UniformDesc::new("scissorMat", UniformType::Mat4), 120 | UniformDesc::new("paintMat", UniformType::Mat4), 121 | UniformDesc::new("innerCol", UniformType::Float4), 122 | UniformDesc::new("outerCol", UniformType::Float4), 123 | UniformDesc::new("scissorExt", UniformType::Float2), 124 | UniformDesc::new("scissorScale", UniformType::Float2), 125 | UniformDesc::new("extent", UniformType::Float2), 126 | UniformDesc::new("radius", UniformType::Float1), 127 | UniformDesc::new("feather", UniformType::Float1), 128 | UniformDesc::new("strokeMult", UniformType::Float1), 129 | UniformDesc::new("strokeThr", UniformType::Float1), 130 | UniformDesc::new("texType", UniformType::Int1), 131 | UniformDesc::new("type", UniformType::Int1), 132 | ], 133 | }, 134 | } 135 | } 136 | 137 | #[derive(Default)] 138 | #[repr(C)] 139 | pub struct Uniforms { 140 | pub view_size: (f32, f32), 141 | pub scissor_mat: glam::Mat4, 142 | pub paint_mat: glam::Mat4, 143 | pub inner_col: (f32, f32, f32, f32), 144 | pub outer_col: (f32, f32, f32, f32), 145 | pub scissor_ext: (f32, f32), 146 | pub scissor_scale: (f32, f32), 147 | pub extent: (f32, f32), 148 | pub radius: f32, 149 | pub feather: f32, 150 | pub stroke_mult: f32, 151 | pub stroke_thr: f32, 152 | pub tex_type: i32, 153 | pub type_: i32, 154 | } 155 | } 156 | 157 | const MAX_VERTICES: usize = 21845; // u16.max / 3 due to index buffer limitations 158 | const MAX_INDICES: usize = u16::max_value() as usize; 159 | 160 | impl Renderer { 161 | pub fn create(ctx: &mut MiniContext) -> Result { 162 | let shader = Shader::new(ctx, shader::VERTEX, shader::FRAGMENT, shader::meta()) 163 | .map_err(|error| NonaError::Shader(error.to_string()))?; 164 | let pipeline = Pipeline::with_params( 165 | ctx, 166 | &[BufferLayout::default()], 167 | shader::ATTRIBUTES, 168 | shader, 169 | PipelineParams { 170 | depth_write: false, 171 | color_blend: None, // set during draws 172 | color_write: (true, true, true, true), 173 | front_face_order: FrontFaceOrder::CounterClockwise, 174 | ..Default::default() 175 | }, 176 | ); 177 | 178 | let vertex_buffer = Buffer::stream( 179 | ctx, 180 | BufferType::VertexBuffer, 181 | MAX_VERTICES * std::mem::size_of::(), 182 | ); 183 | let index_buffer = Buffer::stream( 184 | ctx, 185 | BufferType::IndexBuffer, 186 | MAX_INDICES * std::mem::size_of::(), 187 | ); 188 | 189 | let pixels: [u8; 4 * 4 * 4] = [ 190 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 191 | 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 192 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xFF, 193 | 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 194 | 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 195 | ]; 196 | let temp_texture = miniquad::Texture::from_rgba8(ctx, 4, 4, &pixels); 197 | 198 | let bindings = Bindings { 199 | vertex_buffers: vec![vertex_buffer], 200 | index_buffer, 201 | images: vec![temp_texture], // TODO: set and use image only if needed 202 | }; 203 | 204 | Ok(Renderer { 205 | pipeline, 206 | bindings, 207 | textures: Default::default(), 208 | view: Default::default(), 209 | calls: Default::default(), 210 | paths: Default::default(), 211 | vertexes: Default::default(), 212 | indices: Default::default(), 213 | uniforms: Default::default(), 214 | }) 215 | } 216 | 217 | fn set_uniforms(ctx: &mut MiniContext, uniforms: &shader::Uniforms, img: Option) { 218 | ctx.apply_uniforms(uniforms); 219 | 220 | // TODOKOLA: ADD support, see // // TODO: set image in a better way!!! in flush() 221 | // if let Some(img) = img { 222 | // if let Some(texture) = self.textures.get(img) { 223 | // glBindTexture(GL_TEXTURE_2D, texture.tex); 224 | // } 225 | // } else { 226 | // glBindTexture(GL_TEXTURE_2D, 0); 227 | // } 228 | } 229 | 230 | fn do_fill( 231 | ctx: &mut MiniContext, 232 | call: &Call, 233 | paths: &[GLPath], 234 | bindings: &Bindings, 235 | indices: &mut Vec, 236 | uniforms: &shader::Uniforms, 237 | uniforms_next: &shader::Uniforms, 238 | ) { 239 | indices.clear(); 240 | // TODO: test!!! 241 | 242 | ctx.set_stencil(Some(StencilState { 243 | front: StencilFaceState { 244 | fail_op: StencilOp::Keep, 245 | depth_fail_op: StencilOp::Keep, 246 | pass_op: StencilOp::IncrementWrap, 247 | test_func: CompareFunc::Always, 248 | test_ref: 0, 249 | test_mask: 0xff, 250 | write_mask: 0xff, 251 | }, 252 | back: StencilFaceState { 253 | fail_op: StencilOp::Keep, 254 | depth_fail_op: StencilOp::Keep, 255 | pass_op: StencilOp::DecrementWrap, 256 | test_func: CompareFunc::Always, 257 | test_ref: 0, 258 | test_mask: 0xff, 259 | write_mask: 0xff, 260 | }, 261 | })); 262 | ctx.set_color_write((false, false, false, false)); 263 | // glEnable(GL_STENCIL_TEST); 264 | // glStencilMask(0xff); 265 | // glStencilFunc(GL_ALWAYS, 0, 0xff); 266 | // glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); 267 | Self::set_uniforms(ctx, uniforms, call.image); 268 | // glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_INCR_WRAP); 269 | // glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_DECR_WRAP); 270 | // glDisable(GL_CULL_FACE); 271 | ctx.set_cull_face(CullFace::Nothing); 272 | for path in paths { 273 | // glDrawArrays(GL_TRIANGLE_FAN, path.fill_offset as i32, path.fill_count as i32); 274 | Self::add_triangle_fan(indices, path.fill_offset as u16, path.fill_count as u16); 275 | } 276 | 277 | // draw 278 | bindings.index_buffer.update(ctx, &indices); 279 | ctx.apply_bindings(bindings); 280 | ctx.draw(0, indices.len() as i32, 1); 281 | indices.clear(); 282 | 283 | // glEnable(GL_CULL_FACE); 284 | ctx.set_cull_face(CullFace::Back); 285 | 286 | // glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); 287 | ctx.set_color_write((true, true, true, true)); 288 | // self.set_uniforms(call.uniform_offset + 1, call.image); 289 | Self::set_uniforms(ctx, uniforms_next, call.image); 290 | // glStencilFunc(GL_EQUAL, 0x00, 0xff); 291 | // glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); 292 | ctx.set_stencil(Some(StencilState { 293 | front: StencilFaceState { 294 | fail_op: StencilOp::Keep, 295 | depth_fail_op: StencilOp::Keep, 296 | pass_op: StencilOp::Keep, 297 | test_func: CompareFunc::Equal, 298 | test_ref: 0, 299 | test_mask: 0xff, 300 | write_mask: 0xff, 301 | }, 302 | back: StencilFaceState { 303 | fail_op: StencilOp::Keep, 304 | depth_fail_op: StencilOp::Keep, 305 | pass_op: StencilOp::Keep, 306 | test_func: CompareFunc::Equal, 307 | test_ref: 0, 308 | test_mask: 0xff, 309 | write_mask: 0xff, 310 | }, 311 | })); 312 | for path in paths { 313 | // glDrawArrays(GL_TRIANGLE_STRIP, path.stroke_offset as i32, path.stroke_count as i32); 314 | Self::add_triangle_strip(indices, path.stroke_offset as u16, path.stroke_count as u16); 315 | } 316 | bindings.index_buffer.update(ctx, &indices); 317 | ctx.apply_bindings(bindings); 318 | ctx.draw(0, indices.len() as i32, 1); 319 | 320 | indices.clear(); 321 | // glStencilFunc(GL_NOTEQUAL, 0x00, 0xff); 322 | // glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO); 323 | ctx.set_stencil(Some(StencilState { 324 | front: StencilFaceState { 325 | fail_op: StencilOp::Zero, 326 | depth_fail_op: StencilOp::Zero, 327 | pass_op: StencilOp::Zero, 328 | test_func: CompareFunc::NotEqual, 329 | test_ref: 0, 330 | test_mask: 0xff, 331 | write_mask: 0xff, 332 | }, 333 | back: StencilFaceState { 334 | fail_op: StencilOp::Zero, 335 | depth_fail_op: StencilOp::Zero, 336 | pass_op: StencilOp::Zero, 337 | test_func: CompareFunc::NotEqual, 338 | test_ref: 0, 339 | test_mask: 0xff, 340 | write_mask: 0xff, 341 | }, 342 | })); 343 | // glDrawArrays(GL_TRIANGLE_STRIP, call.triangle_offset as i32, call.triangle_count as i32); 344 | Self::add_triangle_strip( 345 | indices, 346 | call.triangle_offset as u16, 347 | call.triangle_count as u16, 348 | ); 349 | bindings.index_buffer.update(ctx, &indices); 350 | ctx.apply_bindings(bindings); 351 | ctx.draw(0, indices.len() as i32, 1); 352 | 353 | ctx.set_stencil(None); 354 | // glDisable(GL_STENCIL_TEST); 355 | } 356 | 357 | // from https://www.khronos.org/opengl/wiki/Primitive: 358 | // GL_TRIANGLE_FAN: 359 | // Indices: 0 1 2 3 4 5 ... (6 total indices) 360 | // Triangles: {0 1 2} 361 | // {0} {2 3} 362 | // {0} {3 4} 363 | // {0} {4 5} (4 total triangles) 364 | // 365 | // GL_TRIANGLES: 366 | // Indices: 0 1 2 3 4 5 ... 367 | // Triangles: {0 1 2} 368 | // {3 4 5} 369 | /// Adds indices to convert from GL_TRIANGLE_FAN to GL_TRIANGLES 370 | #[inline] 371 | fn add_triangle_fan(indices: &mut Vec, first_vertex_index: u16, index_count: u16) { 372 | let start_index = first_vertex_index; 373 | for i in first_vertex_index..first_vertex_index + index_count - 2 { 374 | indices.push(start_index); 375 | indices.push(i + 1); 376 | indices.push(i + 2); 377 | } 378 | } 379 | 380 | // from https://www.khronos.org/opengl/wiki/Primitive: 381 | // GL_TRIANGLES: 382 | // Indices: 0 1 2 3 4 5 ... (6 total indices) 383 | // Triangles: {0 1 2} 384 | // {3 4 5} (2 total indices) 385 | /// Adds indices to draw GL_TRIANGLES 386 | #[inline] 387 | fn add_triangles(indices: &mut Vec, first_vertex_index: u16, index_count: u16) { 388 | // TODO: test! 389 | for i in (first_vertex_index..first_vertex_index + index_count).step_by(3) { 390 | indices.push(i); 391 | indices.push(i + 1); 392 | indices.push(i + 2); 393 | } 394 | } 395 | 396 | // from https://www.khronos.org/opengl/wiki/Primitive: 397 | // GL_TRIANGLE_STRIP: 398 | // Indices: 0 1 2 3 4 5 ... (6 total indices) 399 | // Triangles: {0 1 2} 400 | // {1 2 3} drawing order is (2 1 3) to maintain proper winding 401 | // {2 3 4} 402 | // {3 4 5} drawing order is (4 3 5) to maintain proper winding (4 total triangles) 403 | // 404 | // GL_TRIANGLES: 405 | // Indices: 0 1 2 3 4 5 ... 406 | // Triangles: {0 1 2} 407 | // {3 4 5} 408 | /// Adds indices to convert from GL_TRIANGLE_STRIP to GL_TRIANGLES 409 | #[inline] 410 | fn add_triangle_strip(indices: &mut Vec, first_vertex_index: u16, index_count: u16) { 411 | let mut draw_order_winding = true; // true to draw in straight (0 1 2) order; false to draw in (1 0 2) order to maintain proper winding 412 | for i in first_vertex_index..first_vertex_index + index_count - 2 { 413 | if draw_order_winding { 414 | indices.push(i); 415 | indices.push(i + 1); 416 | } else { 417 | indices.push(i + 1); 418 | indices.push(i); 419 | } 420 | draw_order_winding = !draw_order_winding; 421 | indices.push(i + 2); 422 | } 423 | } 424 | 425 | fn do_convex_fill( 426 | ctx: &mut MiniContext, 427 | call: &Call, 428 | paths: &[GLPath], 429 | bindings: &Bindings, 430 | indices: &mut Vec, 431 | uniforms: &shader::Uniforms, 432 | ) { 433 | indices.clear(); 434 | Self::set_uniforms(ctx, uniforms, call.image); 435 | 436 | // convert all fans and strips into single draw call 437 | // more info: https://gamedev.stackexchange.com/questions/133208/difference-in-gldrawarrays-and-gldrawelements 438 | for path in paths { 439 | // draw TRIANGLE_FAN from path.fill_offset with path.fill_count, same as 440 | // glDrawArrays(GL_TRIANGLE_FAN, path.fill_offset, path.fill_count); // note: count is "number of indices to render" 441 | Self::add_triangle_fan(indices, path.fill_offset as u16, path.fill_count as u16); 442 | 443 | if path.stroke_count > 0 { 444 | // draw TRIANGLE_STRIP from path.stroke_offset with path.stroke_count, same as 445 | // glDrawArrays(GL_TRIANGLE_STRIP,path.stroke_offset, path.stroke_count); 446 | Self::add_triangle_strip( 447 | indices, 448 | path.stroke_offset as u16, 449 | path.stroke_count as u16, 450 | ); 451 | } 452 | } 453 | 454 | bindings.index_buffer.update(ctx, &indices); 455 | ctx.apply_bindings(bindings); 456 | ctx.draw(0, indices.len() as i32, 1); 457 | } 458 | 459 | fn do_stroke( 460 | ctx: &mut MiniContext, 461 | call: &Call, 462 | paths: &[GLPath], 463 | bindings: &Bindings, 464 | indices: &mut Vec, 465 | uniforms: &shader::Uniforms, 466 | uniforms_next: &shader::Uniforms, 467 | ) { 468 | indices.clear(); 469 | 470 | // TODO glEnable(GL_STENCIL_TEST); 471 | 472 | // TODO glStencilMask(0xff); 473 | // TODO glStencilFunc(GL_EQUAL, 0x0, 0xff); 474 | // TODO glStencilOp(GL_KEEP, GL_KEEP, GL_INCR); 475 | 476 | // self.set_uniforms(call.uniform_offset + 1, call.image); 477 | Self::set_uniforms(ctx, uniforms_next, call.image); 478 | for path in paths { 479 | // glDrawArrays(GL_TRIANGLE_STRIP, path.stroke_offset as i32, path.stroke_count as i32); 480 | Self::add_triangle_strip(indices, path.stroke_offset as u16, path.stroke_count as u16); 481 | } 482 | bindings.index_buffer.update(ctx, &indices); 483 | ctx.apply_bindings(bindings); 484 | ctx.draw(0, indices.len() as i32, 1); 485 | 486 | // self.set_uniforms(call.uniform_offset, call.image); 487 | Self::set_uniforms(ctx, uniforms, call.image); 488 | // TODO glStencilFunc(GL_EQUAL, 0x0, 0xff); 489 | // TODO glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); 490 | ctx.draw(0, indices.len() as i32, 1); 491 | 492 | // TODO glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); 493 | // TODO glStencilFunc(GL_ALWAYS, 0x0, 0xff); 494 | // TODO glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO); 495 | // ctx.draw(0, indices.len() as i32, 1); TODO: uncomment once above TODOs are done 496 | // TODO glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); 497 | 498 | // TODO glDisable(GL_STENCIL_TEST); 499 | } 500 | 501 | fn do_triangles( 502 | ctx: &mut MiniContext, 503 | call: &Call, 504 | bindings: &Bindings, 505 | indices: &mut Vec, 506 | uniforms: &shader::Uniforms, 507 | ) { 508 | indices.clear(); 509 | Self::set_uniforms(ctx, uniforms, call.image); 510 | 511 | // draw TRIANGLES from call.triangle_offset with call.triangle_count, same as 512 | // glDrawArrays(GL_TRIANGLES, call.triangle_offset as i32, call.triangle_count as i32); // note: triangle_count is "number of indices to render", not number of triangles 513 | Self::add_triangles( 514 | indices, 515 | call.triangle_offset as u16, 516 | call.triangle_count as u16, 517 | ); 518 | 519 | bindings.index_buffer.update(ctx, &indices); 520 | ctx.apply_bindings(bindings); 521 | ctx.draw(0, indices.len() as i32, 1); 522 | } 523 | 524 | fn convert_paint( 525 | &self, 526 | paint: &Paint, 527 | scissor: &Scissor, 528 | width: f32, 529 | fringe: f32, 530 | stroke_thr: f32, 531 | ) -> shader::Uniforms { 532 | let mut frag = shader::Uniforms { 533 | view_size: Default::default(), 534 | scissor_mat: Mat4::ZERO, 535 | paint_mat: Default::default(), 536 | inner_col: premul_color(paint.inner_color).into_tuple(), 537 | outer_col: premul_color(paint.outer_color).into_tuple(), 538 | scissor_ext: Default::default(), 539 | scissor_scale: Default::default(), 540 | extent: Default::default(), 541 | radius: 0.0, 542 | feather: 0.0, 543 | stroke_mult: 0.0, 544 | stroke_thr, 545 | tex_type: 0, 546 | type_: 0, 547 | }; 548 | 549 | if scissor.extent.width < -0.5 || scissor.extent.height < -0.5 { 550 | frag.scissor_ext = (1.0, 1.0); 551 | frag.scissor_scale = (1.0, 1.0); 552 | } else { 553 | frag.scissor_mat = xform_to_4x4(scissor.xform.inverse()); 554 | frag.scissor_ext = (scissor.extent.width, scissor.extent.height); 555 | frag.scissor_scale = ( 556 | (scissor.xform.0[0] * scissor.xform.0[0] + scissor.xform.0[2] * scissor.xform.0[2]) 557 | .sqrt() 558 | / fringe, 559 | (scissor.xform.0[1] * scissor.xform.0[1] + scissor.xform.0[3] * scissor.xform.0[3]) 560 | .sqrt() 561 | / fringe, 562 | ); 563 | } 564 | 565 | frag.extent = (paint.extent.width, paint.extent.height); 566 | frag.stroke_mult = (width * 0.5 + fringe * 0.5) / fringe; 567 | 568 | let mut invxform = Transform::default(); 569 | 570 | if let Some(img) = paint.image { 571 | if let Some(texture) = self.textures.get(img) { 572 | if texture.flags.contains(ImageFlags::FLIPY) { 573 | let m1 = Transform::translate(0.0, frag.extent.1 * 0.5) * paint.xform; 574 | let m2 = Transform::scale(1.0, -1.0) * m1; 575 | let m1 = Transform::translate(0.0, -frag.extent.1 * 0.5) * m2; 576 | invxform = m1.inverse(); 577 | } else { 578 | invxform = paint.xform.inverse(); 579 | }; 580 | 581 | frag.type_ = ShaderType::FillImage as i32; 582 | match texture.tex.format { 583 | TextureFormat::RGBA8 => { 584 | frag.tex_type = if texture.flags.contains(ImageFlags::PREMULTIPLIED) { 585 | 0 586 | } else { 587 | 1 588 | } 589 | } 590 | TextureFormat::Alpha => frag.tex_type = 2, 591 | _ => todo!("Unsupported texture type"), 592 | } 593 | } 594 | } else { 595 | frag.type_ = ShaderType::FillGradient as i32; 596 | frag.radius = paint.radius; 597 | frag.feather = paint.feather; 598 | invxform = paint.xform.inverse(); 599 | } 600 | 601 | frag.paint_mat = xform_to_4x4(invxform); 602 | 603 | frag 604 | } 605 | 606 | fn append_uniforms(&mut self, uniforms: shader::Uniforms) { 607 | self.uniforms.push(uniforms); 608 | } 609 | } 610 | 611 | trait IntoTuple4 { 612 | fn into_tuple(self) -> (T, T, T, T); 613 | } 614 | 615 | impl IntoTuple4 for Color { 616 | fn into_tuple(self) -> (f32, f32, f32, f32) { 617 | (self.r, self.g, self.b, self.a) 618 | } 619 | } 620 | 621 | impl renderer::Renderer for RendererCtx<'_> { 622 | fn edge_antialias(&self) -> bool { 623 | self.renderer.edge_antialias() 624 | } 625 | 626 | fn view_size(&self) -> (f32, f32) { 627 | self.renderer.view_size(self.ctx) 628 | } 629 | 630 | fn device_pixel_ratio(&self) -> f32 { 631 | self.renderer.device_pixel_ratio(self.ctx) 632 | } 633 | 634 | fn create_texture( 635 | &mut self, 636 | texture_type: TextureType, 637 | width: usize, 638 | height: usize, 639 | flags: ImageFlags, 640 | data: Option<&[u8]>, 641 | ) -> Result { 642 | self.renderer 643 | .create_texture(self.ctx, texture_type, width, height, flags, data) 644 | } 645 | 646 | fn delete_texture(&mut self, img: ImageId) -> Result<(), NonaError> { 647 | self.renderer.delete_texture(img) 648 | } 649 | 650 | fn update_texture( 651 | &mut self, 652 | img: ImageId, 653 | x: usize, 654 | y: usize, 655 | width: usize, 656 | height: usize, 657 | data: &[u8], 658 | ) -> Result<(), NonaError> { 659 | self.renderer 660 | .update_texture(self.ctx, img, x, y, width, height, data) 661 | } 662 | 663 | fn texture_size(&self, img: ImageId) -> Result<(usize, usize), NonaError> { 664 | self.renderer.texture_size(img) 665 | } 666 | 667 | fn viewport(&mut self, extent: Extent, device_pixel_ratio: f32) -> Result<(), NonaError> { 668 | self.renderer.viewport(extent, device_pixel_ratio) 669 | } 670 | 671 | fn clear_screen(&mut self, color: Color) { 672 | self.renderer.clear_screen(self.ctx, color) 673 | } 674 | 675 | fn flush(&mut self) -> Result<(), NonaError> { 676 | self.renderer.flush(self.ctx) 677 | } 678 | 679 | fn fill( 680 | &mut self, 681 | paint: &Paint, 682 | composite_operation: CompositeOperationState, 683 | scissor: &Scissor, 684 | fringe: f32, 685 | bounds: Bounds, 686 | paths: &[Path], 687 | ) -> Result<(), NonaError> { 688 | self.renderer.fill( 689 | self.ctx, 690 | paint, 691 | composite_operation, 692 | scissor, 693 | fringe, 694 | bounds, 695 | paths, 696 | ) 697 | } 698 | 699 | fn stroke( 700 | &mut self, 701 | paint: &Paint, 702 | composite_operation: CompositeOperationState, 703 | scissor: &Scissor, 704 | fringe: f32, 705 | stroke_width: f32, 706 | paths: &[Path], 707 | ) -> Result<(), NonaError> { 708 | self.renderer.stroke( 709 | self.ctx, 710 | paint, 711 | composite_operation, 712 | scissor, 713 | fringe, 714 | stroke_width, 715 | paths, 716 | ) 717 | } 718 | 719 | fn triangles( 720 | &mut self, 721 | paint: &Paint, 722 | composite_operation: CompositeOperationState, 723 | scissor: &Scissor, 724 | vertexes: &[Vertex], 725 | ) -> Result<(), NonaError> { 726 | self.renderer 727 | .triangles(self.ctx, paint, composite_operation, scissor, vertexes) 728 | } 729 | } 730 | 731 | impl Renderer { 732 | fn edge_antialias(&self) -> bool { 733 | true 734 | } 735 | 736 | fn view_size(&self, ctx: &MiniContext) -> (f32, f32) { 737 | ctx.screen_size() 738 | } 739 | 740 | fn device_pixel_ratio(&self, ctx: &MiniContext) -> f32 { 741 | ctx.dpi_scale() 742 | } 743 | 744 | fn create_texture( 745 | &mut self, 746 | ctx: &mut MiniContext, 747 | texture_type: TextureType, 748 | width: usize, 749 | height: usize, 750 | flags: ImageFlags, 751 | data: Option<&[u8]>, 752 | ) -> Result { 753 | let format = match texture_type { 754 | TextureType::RGBA => TextureFormat::RGBA8, 755 | TextureType::Alpha => TextureFormat::Alpha, 756 | }; 757 | let tex: miniquad::Texture = miniquad::Texture::new( 758 | ctx, 759 | TextureAccess::Static, 760 | data, 761 | TextureParams { 762 | format, 763 | wrap: TextureWrap::Clamp, // TODO: support repeatx/y/mirror 764 | filter: if flags.contains(ImageFlags::NEAREST) { 765 | FilterMode::Nearest 766 | } else { 767 | FilterMode::Linear 768 | }, 769 | width: width as u32, 770 | height: height as u32, 771 | }, 772 | ); 773 | 774 | // TODO: support ImageFlags::GENERATE_MIPMAPS) with/without if flags.contains(ImageFlags::NEAREST) { 775 | 776 | let id = self.textures.insert(Texture { tex, flags }); 777 | Ok(id) 778 | } 779 | 780 | fn delete_texture(&mut self, img: ImageId) -> Result<(), NonaError> { 781 | if let Some(texture) = self.textures.get(img) { 782 | texture.tex.delete(); 783 | self.textures.remove(img); 784 | Ok(()) 785 | } else { 786 | Err(NonaError::Texture(format!("texture '{}' not found", img))) 787 | } 788 | } 789 | 790 | fn update_texture( 791 | &mut self, 792 | ctx: &mut MiniContext, 793 | img: ImageId, 794 | x: usize, 795 | y: usize, 796 | width: usize, 797 | height: usize, 798 | data: &[u8], 799 | ) -> Result<(), NonaError> { 800 | if let Some(texture) = self.textures.get(img) { 801 | texture 802 | .tex 803 | .update_texture_part(ctx, x as _, y as _, width as _, height as _, data); 804 | Ok(()) 805 | } else { 806 | Err(NonaError::Texture(format!("texture '{}' not found", img))) 807 | } 808 | } 809 | 810 | fn texture_size(&self, img: ImageId) -> Result<(usize, usize), NonaError> { 811 | if let Some(texture) = self.textures.get(img) { 812 | Ok((texture.tex.width as usize, texture.tex.height as usize)) 813 | } else { 814 | Err(NonaError::Texture(format!("texture '{}' not found", img))) 815 | } 816 | } 817 | 818 | fn viewport(&mut self, extent: Extent, _device_pixel_ratio: f32) -> Result<(), NonaError> { 819 | self.view = extent; 820 | Ok(()) 821 | } 822 | 823 | fn clear_screen(&mut self, ctx: &mut MiniContext, color: Color) { 824 | ctx.clear(Some((color.r, color.g, color.b, color.a)), None, None); 825 | } 826 | 827 | fn flush(&mut self, ctx: &mut MiniContext) -> Result<(), NonaError> { 828 | if self.calls.is_empty() { 829 | self.vertexes.clear(); 830 | self.paths.clear(); 831 | self.calls.clear(); 832 | self.uniforms.clear(); 833 | 834 | return Ok(()); 835 | } 836 | ctx.begin_default_pass(PassAction::Nothing); 837 | 838 | // glUseProgram(self.shader.prog); DONE 839 | ctx.apply_pipeline(&self.pipeline); 840 | ctx.apply_bindings(&self.bindings); // NEEDED - must be called before vertex buffer update; TODO_BUG: can be optimized in miniquad; we only need to update index buffer in most cases, see do_convex_fill() 841 | self.bindings.vertex_buffers[0].update(ctx, &self.vertexes); // TODO: miniquad BUG? this line must show after apply_bindings otherwise no display of vertex buffer can happen 842 | 843 | // glEnable(GL_CULL_FACE); 844 | // glCullFace(GL_BACK); 845 | ctx.set_cull_face(CullFace::Back); 846 | // glFrontFace(GL_CCW); // DONE front_face_order 847 | 848 | // glEnable(GL_BLEND); // TODO_BELOW 849 | // glDisable(GL_DEPTH_TEST); DONE: depth_write: false, on PipelineParams 850 | // glDisable(GL_SCISSOR_TEST); // TODO: support in miniquad 851 | 852 | // glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); // DONE color_write 853 | // glStencilMask(0xffffffff); // TODO: support in miniquad 854 | // glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); // TODO: support in miniquad 855 | // glStencilFunc(GL_ALWAYS, 0, 0xffffffff); // TODO: support in miniquad 856 | 857 | // glActiveTexture(GL_TEXTURE0); // TODO: implement 858 | // glBindTexture(GL_TEXTURE_2D, 0); // TODO: implement 859 | 860 | // TODOKOLA: commented: 861 | // glBindVertexArray(self.vert_arr); 862 | // glBindBuffer(GL_ARRAY_BUFFER, self.vert_buf); 863 | // glBufferData( 864 | // GL_ARRAY_BUFFER, 865 | // (self.vertexes.len() * std::mem::size_of::()) as GLsizeiptr, 866 | // self.vertexes.as_ptr() as *const c_void, 867 | // GL_STREAM_DRAW, 868 | // ); 869 | // glEnableVertexAttribArray(self.shader.loc_vertex); 870 | // glEnableVertexAttribArray(self.shader.loc_tcoord); 871 | // glVertexAttribPointer( 872 | // self.shader.loc_vertex, 873 | // 2, // size in floats 874 | // GL_FLOAT, 875 | // GL_FALSE as GLboolean, 876 | // std::mem::size_of::() as i32, 877 | // std::ptr::null(), 878 | // ); 879 | // glVertexAttribPointer( 880 | // self.shader.loc_tcoord, 881 | // 2, // size in floats 882 | // GL_FLOAT, 883 | // GL_FALSE as GLboolean, 884 | // std::mem::size_of::() as i32, 885 | // (2 * std::mem::size_of::()) as *const c_void, // use GL_ARRAY_BUFFER, and skip x,y (2 floats) to start sampling at u, v 886 | // ); 887 | // glUniform1i(self.shader.loc_tex, 0); 888 | // glUniform2fv( 889 | // self.shader.loc_viewsize, 890 | // 1, 891 | // &self.view as *const Extent as *const f32, 892 | // ); 893 | 894 | let calls = &self.calls[..]; 895 | // println!("START CALLS"); // DEBUG 896 | 897 | for call in calls { 898 | let call: &Call = call; // added to make rust-analyzer type inferrence work. See https://github.com/rust-analyzer/rust-analyzer/issues/4160 899 | let blend = &call.blend_func; 900 | 901 | ctx.set_blend(Some(blend.color), Some(blend.alpha)); 902 | 903 | // { 904 | // // TODO: set image in a better way!!! 905 | // self.bindings.images = vec![]; 906 | // ctx.apply_bindings(&self.bindings); 907 | // } 908 | 909 | // glBlendFuncSeparate( // TODO: DELETE once tested 910 | // blend.src_rgb, 911 | // blend.dst_rgb, 912 | // blend.src_alpha, 913 | // blend.dst_alpha, 914 | // ); 915 | 916 | // println!("Call {:?}", call.call_type); // DEBUG 917 | 918 | // update view size for the uniforms that may be in use 919 | self.uniforms[call.uniform_offset].view_size = ctx.screen_size(); 920 | if self.uniforms.len() > call.uniform_offset + 1 { 921 | self.uniforms[call.uniform_offset + 1].view_size = ctx.screen_size(); 922 | } 923 | let uniforms: &shader::Uniforms = &self.uniforms[call.uniform_offset]; 924 | if let Some(image_index) = call.image { 925 | self.bindings.images[0] = self.textures[image_index].tex; 926 | // ctx.apply_bindings(&self.bindings); // not needed - will be called in the call_type handlers below 927 | } 928 | 929 | match call.call_type { 930 | CallType::Fill => { 931 | // TODO: test! 932 | let paths = &self.paths[call.path_offset..call.path_offset + call.path_count]; 933 | 934 | let uniforms_next: &shader::Uniforms = &self.uniforms[call.uniform_offset + 1]; 935 | 936 | Self::do_fill( 937 | ctx, 938 | call, 939 | paths, 940 | &self.bindings, 941 | &mut self.indices, 942 | &uniforms, 943 | &uniforms_next, 944 | ); 945 | } 946 | CallType::ConvexFill => { 947 | // test data: 948 | // let val = 0.0; 949 | // #[rustfmt::skip] 950 | // let vertices: [Vertex; 4] = [ 951 | // Vertex { x: 100.0, y: 100.0, u: 0., v: 0. }, 952 | // Vertex { x: 150.0, y: 50.0, u: 1., v: 0. }, 953 | // Vertex { x: 100.0, y: 50.0, u: 1., v: 1. }, 954 | // Vertex { x: -0.5 + val, y: 0.5 + val, u: 0., v: 1. }, 955 | // ]; 956 | // let indices: [u16; 6] = [0, 1, 2, 0, 2, 3]; 957 | 958 | // self.bindings.vertex_buffers[0].update(ctx, &vertices); 959 | // self.bindings 960 | // .index_buffer 961 | // .update(ctx, &indices); 962 | 963 | // ctx.apply_bindings(&self.bindings); 964 | // Self::set_uniforms(ctx, uniforms, call.image); 965 | 966 | // ctx.draw(0, 3, 1); 967 | 968 | let paths = &self.paths[call.path_offset..call.path_offset + call.path_count]; 969 | 970 | Self::do_convex_fill( 971 | ctx, 972 | call, 973 | paths, 974 | &self.bindings, 975 | &mut self.indices, 976 | uniforms, 977 | ); 978 | } 979 | CallType::Stroke => { 980 | let paths = &self.paths[call.path_offset..call.path_offset + call.path_count]; 981 | let uniforms_next: &shader::Uniforms = &self.uniforms[call.uniform_offset + 1]; 982 | 983 | Self::do_stroke( 984 | ctx, 985 | call, 986 | paths, 987 | &self.bindings, 988 | &mut self.indices, 989 | &uniforms, 990 | &uniforms_next, 991 | ); 992 | } 993 | CallType::Triangles => { 994 | Self::do_triangles(ctx, call, &self.bindings, &mut self.indices, uniforms); 995 | } 996 | } 997 | } 998 | 999 | ctx.end_render_pass(); 1000 | 1001 | // TODO: commented, not needed?? 1002 | // glDisableVertexAttribArray(self.shader.loc_vertex); 1003 | // glDisableVertexAttribArray(self.shader.loc_tcoord); 1004 | // glBindVertexArray(0); 1005 | 1006 | // glDisable(GL_CULL_FACE); 1007 | ctx.set_cull_face(CullFace::Nothing); 1008 | 1009 | // glBindBuffer(GL_ARRAY_BUFFER, 0); 1010 | // glUseProgram(0); 1011 | // glBindTexture(GL_TEXTURE_2D, 0); 1012 | 1013 | self.vertexes.clear(); 1014 | self.paths.clear(); 1015 | self.calls.clear(); 1016 | self.uniforms.clear(); 1017 | Ok(()) 1018 | } 1019 | 1020 | fn fill( 1021 | &mut self, 1022 | ctx: &mut MiniContext, 1023 | paint: &Paint, 1024 | composite_operation: CompositeOperationState, 1025 | scissor: &Scissor, 1026 | fringe: f32, 1027 | bounds: Bounds, 1028 | paths: &[Path], 1029 | ) -> Result<(), NonaError> { 1030 | let mut new_vertex_count = self.vertexes.len(); 1031 | for path in paths { 1032 | new_vertex_count += path.get_fill().len(); 1033 | new_vertex_count += path.get_stroke().len(); 1034 | } 1035 | 1036 | let call_type = if paths.len() == 1 && paths[0].convex { 1037 | CallType::ConvexFill 1038 | } else { 1039 | CallType::Fill 1040 | }; 1041 | 1042 | if call_type == CallType::Fill { 1043 | new_vertex_count += 4; 1044 | } 1045 | 1046 | // if GPU overflow 1047 | if new_vertex_count >= MAX_VERTICES { 1048 | self.flush(ctx)?; 1049 | } 1050 | 1051 | let mut call = Call { 1052 | call_type, 1053 | image: paint.image, 1054 | path_offset: self.paths.len(), 1055 | path_count: paths.len(), 1056 | triangle_offset: 0, 1057 | triangle_count: 4, 1058 | uniform_offset: 0, 1059 | blend_func: composite_operation.into(), 1060 | }; 1061 | 1062 | let mut offset = self.vertexes.len(); 1063 | for path in paths { 1064 | let fill = path.get_fill(); 1065 | let mut gl_path = GLPath { 1066 | fill_offset: 0, 1067 | fill_count: 0, 1068 | stroke_offset: 0, 1069 | stroke_count: 0, 1070 | }; 1071 | 1072 | if !fill.is_empty() { 1073 | gl_path.fill_offset = offset; 1074 | gl_path.fill_count = fill.len(); 1075 | self.vertexes.extend(fill); 1076 | offset += fill.len(); 1077 | } 1078 | 1079 | let stroke = path.get_stroke(); 1080 | if !stroke.is_empty() { 1081 | gl_path.stroke_offset = offset; 1082 | gl_path.stroke_count = stroke.len(); 1083 | self.vertexes.extend(stroke); 1084 | offset += stroke.len(); 1085 | } 1086 | 1087 | self.paths.push(gl_path); 1088 | } 1089 | 1090 | if call.call_type == CallType::Fill { 1091 | call.triangle_offset = offset; 1092 | self.vertexes 1093 | .push(Vertex::new(bounds.max.x, bounds.max.y, 0.5, 1.0)); 1094 | self.vertexes 1095 | .push(Vertex::new(bounds.max.x, bounds.min.y, 0.5, 1.0)); 1096 | self.vertexes 1097 | .push(Vertex::new(bounds.min.x, bounds.max.y, 0.5, 1.0)); 1098 | self.vertexes 1099 | .push(Vertex::new(bounds.min.x, bounds.min.y, 0.5, 1.0)); 1100 | 1101 | call.uniform_offset = self.uniforms.len(); 1102 | self.append_uniforms(shader::Uniforms { 1103 | stroke_thr: -1.0, 1104 | type_: ShaderType::Simple as i32, 1105 | ..shader::Uniforms::default() 1106 | }); 1107 | self.append_uniforms(self.convert_paint(paint, scissor, fringe, fringe, -1.0)); 1108 | } else { 1109 | call.uniform_offset = self.uniforms.len(); 1110 | self.append_uniforms(self.convert_paint(paint, scissor, fringe, fringe, -1.0)); 1111 | } 1112 | 1113 | self.calls.push(call); 1114 | Ok(()) 1115 | } 1116 | 1117 | fn stroke( 1118 | &mut self, 1119 | ctx: &mut MiniContext, 1120 | paint: &Paint, 1121 | composite_operation: CompositeOperationState, 1122 | scissor: &Scissor, 1123 | fringe: f32, 1124 | stroke_width: f32, 1125 | paths: &[Path], 1126 | ) -> Result<(), NonaError> { 1127 | let mut new_vertex_count = self.vertexes.len(); 1128 | for path in paths { 1129 | new_vertex_count += path.get_stroke().len(); 1130 | } 1131 | 1132 | // if GPU overflow 1133 | if new_vertex_count >= MAX_VERTICES { 1134 | self.flush(ctx)?; 1135 | } 1136 | 1137 | let mut call = Call { 1138 | call_type: CallType::Stroke, 1139 | image: paint.image, 1140 | path_offset: self.paths.len(), 1141 | path_count: paths.len(), 1142 | triangle_offset: 0, 1143 | triangle_count: 0, 1144 | uniform_offset: 0, 1145 | blend_func: composite_operation.into(), 1146 | }; 1147 | 1148 | let mut offset = self.vertexes.len(); 1149 | for path in paths { 1150 | let mut gl_path = GLPath { 1151 | fill_offset: 0, 1152 | fill_count: 0, 1153 | stroke_offset: 0, 1154 | stroke_count: 0, 1155 | }; 1156 | 1157 | let stroke = path.get_stroke(); 1158 | if !stroke.is_empty() { 1159 | gl_path.stroke_offset = offset; 1160 | gl_path.stroke_count = stroke.len(); 1161 | self.vertexes.extend(stroke); 1162 | offset += stroke.len(); 1163 | self.paths.push(gl_path); 1164 | } 1165 | } 1166 | 1167 | call.uniform_offset = self.uniforms.len(); 1168 | self.append_uniforms(self.convert_paint(paint, scissor, stroke_width, fringe, -1.0)); 1169 | self.append_uniforms(self.convert_paint( 1170 | paint, 1171 | scissor, 1172 | stroke_width, 1173 | fringe, 1174 | 1.0 - 0.5 / 255.0, 1175 | )); 1176 | 1177 | self.calls.push(call); 1178 | Ok(()) 1179 | } 1180 | 1181 | fn triangles( 1182 | &mut self, 1183 | ctx: &mut MiniContext, 1184 | paint: &Paint, 1185 | composite_operation: CompositeOperationState, 1186 | scissor: &Scissor, 1187 | vertexes: &[Vertex], 1188 | ) -> Result<(), NonaError> { 1189 | let mut new_vertex_count = self.vertexes.len(); 1190 | new_vertex_count += vertexes.len(); 1191 | 1192 | // if GPU overflow 1193 | if new_vertex_count >= MAX_VERTICES { 1194 | self.flush(ctx)?; 1195 | } 1196 | 1197 | let call = Call { 1198 | call_type: CallType::Triangles, 1199 | image: paint.image, 1200 | path_offset: 0, 1201 | path_count: 0, 1202 | triangle_offset: self.vertexes.len(), 1203 | triangle_count: vertexes.len(), 1204 | uniform_offset: self.uniforms.len(), 1205 | blend_func: composite_operation.into(), 1206 | }; 1207 | 1208 | self.calls.push(call); 1209 | self.vertexes.extend(vertexes); 1210 | 1211 | let mut uniforms = self.convert_paint(paint, scissor, 1.0, 1.0, -1.0); 1212 | uniforms.type_ = ShaderType::Image as i32; 1213 | self.append_uniforms(uniforms); 1214 | Ok(()) 1215 | } 1216 | } 1217 | 1218 | fn convert_blend_factor(factor: nona::BlendFactor) -> miniquad::BlendFactor { 1219 | match factor { 1220 | nona::BlendFactor::Zero => miniquad::BlendFactor::Zero, 1221 | nona::BlendFactor::One => miniquad::BlendFactor::One, 1222 | 1223 | nona::BlendFactor::SrcColor => miniquad::BlendFactor::Value(BlendValue::SourceColor), 1224 | nona::BlendFactor::OneMinusSrcColor => { 1225 | miniquad::BlendFactor::OneMinusValue(BlendValue::SourceColor) 1226 | } 1227 | nona::BlendFactor::DstColor => miniquad::BlendFactor::Value(BlendValue::DestinationColor), 1228 | nona::BlendFactor::OneMinusDstColor => { 1229 | miniquad::BlendFactor::OneMinusValue(BlendValue::DestinationColor) 1230 | } 1231 | 1232 | nona::BlendFactor::SrcAlpha => miniquad::BlendFactor::Value(BlendValue::SourceAlpha), 1233 | nona::BlendFactor::OneMinusSrcAlpha => { 1234 | miniquad::BlendFactor::OneMinusValue(BlendValue::SourceAlpha) 1235 | } 1236 | nona::BlendFactor::DstAlpha => miniquad::BlendFactor::Value(BlendValue::DestinationAlpha), 1237 | nona::BlendFactor::OneMinusDstAlpha => { 1238 | miniquad::BlendFactor::OneMinusValue(BlendValue::DestinationAlpha) 1239 | } 1240 | 1241 | nona::BlendFactor::SrcAlphaSaturate => miniquad::BlendFactor::SourceAlphaSaturate, 1242 | } 1243 | } 1244 | 1245 | #[inline] 1246 | fn premul_color(color: Color) -> Color { 1247 | Color { 1248 | r: color.r * color.a, 1249 | g: color.g * color.a, 1250 | b: color.b * color.a, 1251 | a: color.a, 1252 | } 1253 | } 1254 | 1255 | #[inline] 1256 | fn _xform_to_3x4(xform: Transform) -> [f32; 12] { 1257 | // 3 col 4 rows 1258 | let mut m = [0f32; 12]; 1259 | let t = &xform.0; 1260 | m[0] = t[0]; 1261 | m[1] = t[1]; 1262 | m[2] = 0.0; 1263 | m[3] = 0.0; 1264 | m[4] = t[2]; 1265 | m[5] = t[3]; 1266 | m[6] = 0.0; 1267 | m[7] = 0.0; 1268 | m[8] = t[4]; 1269 | m[9] = t[5]; 1270 | m[10] = 1.0; 1271 | m[11] = 0.0; 1272 | m 1273 | } 1274 | 1275 | #[inline] 1276 | fn xform_to_4x4(xform: Transform) -> Mat4 { 1277 | let t = &xform.0; 1278 | 1279 | // Mat4::from_cols( 1280 | // Vec4::new(t[0], t[2], t[4], 0.0), 1281 | // Vec4::new(t[1], t[3], t[5], 0.0), 1282 | // Vec4::new(0.0, 0.0, 1.0, 0.0), 1283 | // Vec4::new(0.0, 0.0, 0.0, 0.0), 1284 | // ) 1285 | 1286 | Mat4::from_cols( 1287 | Vec4::new(t[0], t[1], 0.0, 0.0), 1288 | Vec4::new(t[2], t[3], 0.0, 0.0), 1289 | Vec4::new(t[4], t[5], 1.0, 0.0), 1290 | Vec4::new(0.0, 0.0, 0.0, 0.0), 1291 | ) 1292 | } 1293 | -------------------------------------------------------------------------------- /nonaquad/src/nvgimpl_orig.rs: -------------------------------------------------------------------------------- 1 | use nvg::renderer::*; 2 | use slab::Slab; 3 | use std::ffi::c_void; 4 | use miniquad::sapp::*; 5 | 6 | struct Shader { 7 | prog: GLuint, 8 | frag: GLuint, 9 | vert: GLuint, 10 | loc_viewsize: i32, 11 | loc_tex: i32, 12 | loc_vertex: u32, 13 | loc_tcoord: u32, 14 | loc_scissor_mat: i32, 15 | loc_paint_mat: i32, 16 | loc_inner_col: i32, 17 | loc_outer_col: i32, 18 | loc_scissor_ext: i32, 19 | loc_scissor_scale: i32, 20 | loc_extent: i32, 21 | loc_radius: i32, 22 | loc_feather: i32, 23 | loc_stroke_mult: i32, 24 | loc_stroke_thr: i32, 25 | loc_tex_type: i32, 26 | loc_type: i32, 27 | } 28 | 29 | impl Drop for Shader { 30 | fn drop(&mut self) { 31 | unsafe { 32 | // glDeleteProgram(self.prog); // TODO 33 | glDeleteShader(self.vert); 34 | glDeleteShader(self.frag); 35 | } 36 | } 37 | } 38 | 39 | impl Shader { 40 | unsafe fn load() -> anyhow::Result { 41 | let mut status: GLint = std::mem::zeroed(); 42 | let prog = glCreateProgram(); 43 | let vert = glCreateShader(GL_VERTEX_SHADER); 44 | let frag = glCreateShader(GL_FRAGMENT_SHADER); 45 | let vert_source = 46 | std::ffi::CString::from_vec_unchecked(include_bytes!("shader.vert").to_vec()); 47 | let frag_source = 48 | std::ffi::CString::from_vec_unchecked(include_bytes!("shader.frag").to_vec()); 49 | 50 | glShaderSource( 51 | vert, 52 | 1, 53 | [vert_source.as_ptr()].as_ptr() as *const *const GLchar, 54 | std::ptr::null(), 55 | ); 56 | glShaderSource( 57 | frag, 58 | 1, 59 | [frag_source.as_ptr()].as_ptr() as *const *const GLchar, 60 | std::ptr::null(), 61 | ); 62 | 63 | glCompileShader(vert); 64 | glGetShaderiv(vert, GL_COMPILE_STATUS, &mut status); 65 | if status != GL_TRUE as i32 { 66 | return Err(shader_error(vert, "shader.vert")); 67 | } 68 | 69 | glCompileShader(frag); 70 | glGetShaderiv(frag, GL_COMPILE_STATUS, &mut status); 71 | if status != GL_TRUE as i32 { 72 | return Err(shader_error(vert, "shader.frag")); 73 | } 74 | 75 | glAttachShader(prog, vert); 76 | glAttachShader(prog, frag); 77 | 78 | // let name_vertex = std::ffi::CString::new("vertex").unwrap(); 79 | // let name_tcoord = std::ffi::CString::new("tcoord").unwrap(); 80 | // glBindAttribLocation(prog, 0, name_vertex.as_ptr() as *const i8); // TODO_INFO: commented out since unsupported for linking on Windows 81 | // glBindAttribLocation(prog, 1, name_tcoord.as_ptr() as *const i8); // TODO_INFO: commented out since unsupported for linking on Windows 82 | 83 | glLinkProgram(prog); 84 | glGetProgramiv(prog, GL_LINK_STATUS, &mut status); 85 | if status != GL_TRUE as i32 { 86 | return Err(program_error(prog)); 87 | } 88 | 89 | let name_viewsize = std::ffi::CString::new("viewSize").unwrap(); 90 | let name_tex = std::ffi::CString::new("tex").unwrap(); 91 | 92 | Ok(Shader { 93 | prog, 94 | frag, 95 | vert, 96 | loc_viewsize: glGetUniformLocation(prog, name_viewsize.as_ptr() as *const GLchar), 97 | loc_tex: glGetUniformLocation(prog, name_tex.as_ptr() as *const GLchar), 98 | 99 | loc_vertex: glGetAttribLocation(prog, std::ffi::CString::new("vertex").unwrap().as_ptr() as *const GLchar) as u32, 100 | loc_tcoord: glGetAttribLocation(prog, std::ffi::CString::new("tcoord").unwrap().as_ptr() as *const GLchar) as u32, 101 | 102 | loc_scissor_mat: glGetUniformLocation(prog, std::ffi::CString::new("scissorMat").unwrap().as_ptr() as *const GLchar), 103 | loc_paint_mat: glGetUniformLocation(prog, std::ffi::CString::new("paintMat").unwrap().as_ptr() as *const GLchar), 104 | loc_inner_col: glGetUniformLocation(prog, std::ffi::CString::new("innerCol").unwrap().as_ptr() as *const GLchar), 105 | loc_outer_col: glGetUniformLocation(prog, std::ffi::CString::new("outerCol").unwrap().as_ptr() as *const GLchar), 106 | loc_scissor_ext: glGetUniformLocation(prog, std::ffi::CString::new("scissorExt").unwrap().as_ptr() as *const GLchar), 107 | loc_scissor_scale: glGetUniformLocation(prog, std::ffi::CString::new("scissorScale").unwrap().as_ptr() as *const GLchar), 108 | loc_extent: glGetUniformLocation(prog, std::ffi::CString::new("extent").unwrap().as_ptr() as *const GLchar), 109 | loc_radius: glGetUniformLocation(prog, std::ffi::CString::new("radius").unwrap().as_ptr() as *const GLchar), 110 | loc_feather: glGetUniformLocation(prog, std::ffi::CString::new("feather").unwrap().as_ptr() as *const GLchar), 111 | loc_stroke_mult: glGetUniformLocation(prog, std::ffi::CString::new("strokeMult").unwrap().as_ptr() as *const GLchar), 112 | loc_stroke_thr: glGetUniformLocation(prog, std::ffi::CString::new("strokeThr").unwrap().as_ptr() as *const GLchar), 113 | loc_tex_type: glGetUniformLocation(prog, std::ffi::CString::new("texType").unwrap().as_ptr() as *const GLchar), 114 | loc_type: glGetUniformLocation(prog, std::ffi::CString::new("type").unwrap().as_ptr() as *const GLchar), 115 | }) 116 | } 117 | } 118 | 119 | enum ShaderType { 120 | FillGradient, 121 | FillImage, 122 | Simple, 123 | Image, 124 | } 125 | 126 | #[derive(PartialEq, Eq)] 127 | enum CallType { 128 | Fill, 129 | ConvexFill, 130 | Stroke, 131 | Triangles, 132 | } 133 | 134 | struct Blend { 135 | src_rgb: GLenum, 136 | dst_rgb: GLenum, 137 | src_alpha: GLenum, 138 | dst_alpha: GLenum, 139 | } 140 | 141 | impl From for Blend { 142 | fn from(state: CompositeOperationState) -> Self { 143 | Blend { 144 | src_rgb: convert_blend_factor(state.src_rgb), 145 | dst_rgb: convert_blend_factor(state.dst_rgb), 146 | src_alpha: convert_blend_factor(state.src_alpha), 147 | dst_alpha: convert_blend_factor(state.dst_alpha), 148 | } 149 | } 150 | } 151 | 152 | struct Call { 153 | call_type: CallType, 154 | image: Option, 155 | path_offset: usize, 156 | path_count: usize, 157 | triangle_offset: usize, 158 | triangle_count: usize, 159 | uniform_offset: usize, 160 | blend_func: Blend, 161 | } 162 | 163 | struct Texture { 164 | tex: GLuint, 165 | width: usize, 166 | height: usize, 167 | texture_type: TextureType, 168 | flags: ImageFlags, 169 | } 170 | 171 | impl Drop for Texture { 172 | fn drop(&mut self) { 173 | unsafe { glDeleteTextures(1, &self.tex) } 174 | } 175 | } 176 | 177 | struct GLPath { 178 | fill_offset: usize, 179 | fill_count: usize, 180 | stroke_offset: usize, 181 | stroke_count: usize, 182 | } 183 | 184 | #[derive(Default)] 185 | #[allow(dead_code)] 186 | struct FragUniforms { 187 | scissor_mat: [f32; 12], 188 | paint_mat: [f32; 12], 189 | inner_color: Color, 190 | outer_color: Color, 191 | scissor_ext: [f32; 2], 192 | scissor_scale: [f32; 2], 193 | extent: [f32; 2], 194 | radius: f32, 195 | feather: f32, 196 | stroke_mult: f32, 197 | stroke_thr: f32, 198 | tex_type: i32, 199 | type_: i32, 200 | } 201 | 202 | pub struct Renderer<'a> { 203 | shader: Shader, 204 | textures: Slab, 205 | view: Extent, 206 | vert_buf: GLuint, 207 | vert_arr: GLuint, 208 | calls: Vec, 209 | paths: Vec, 210 | vertexes: Vec, 211 | uniforms: Vec, 212 | phantom: std::marker::PhantomData<&'a i32>, 213 | } 214 | 215 | impl Drop for Renderer<'_> { 216 | fn drop(&mut self) { 217 | unsafe { 218 | // glDeleteBuffers(1, &self.frag_buf); TODOKOLA 219 | glDeleteBuffers(1, &self.vert_buf); 220 | // glDeleteVertexArrays(1, &self.vert_arr); TODOKOLA 221 | } 222 | } 223 | } 224 | 225 | impl Renderer<'_> { 226 | pub fn create(_ctx: &mut miniquad::Context) -> anyhow::Result { 227 | unsafe { 228 | let shader = Shader::load()?; 229 | 230 | let mut vert_arr: GLuint = std::mem::zeroed(); 231 | glGenVertexArrays(1, &mut vert_arr); 232 | 233 | let mut vert_buf: GLuint = std::mem::zeroed(); 234 | glGenBuffers(1, &mut vert_buf); 235 | 236 | // glUniformBlockBinding(shader.prog, shader.loc_frag, 0); 237 | // let mut frag_buf: GLuint = std::mem::zeroed(); 238 | // glGenBuffers(1, &mut frag_buf); 239 | 240 | // let mut align = std::mem::zeroed(); 241 | // glGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, &mut align); 242 | 243 | // let frag_size = std::mem::size_of::() + (align as usize) 244 | // - std::mem::size_of::() % (align as usize); 245 | 246 | // glFinish(); 247 | 248 | Ok(Renderer { 249 | shader, 250 | textures: Default::default(), 251 | view: Default::default(), 252 | vert_buf, 253 | vert_arr, 254 | calls: Default::default(), 255 | paths: Default::default(), 256 | vertexes: Default::default(), 257 | uniforms: Default::default(), 258 | phantom: std::marker::PhantomData, 259 | }) 260 | } 261 | } 262 | 263 | unsafe fn set_uniforms(&self, offset: usize, img: Option) { 264 | let uniforms = &self.uniforms[offset]; 265 | // glBindBuffer(GL_UNIFORM_BUFFER, self.frag_buf); // TODOKOLA: added 266 | glUniformMatrix4fv(self.shader.loc_scissor_mat, 1, 0, &uniforms.scissor_mat as *const _ as *const f32); 267 | glUniformMatrix4fv(self.shader.loc_paint_mat, 1, 0, &uniforms.paint_mat as *const _ as *const f32); 268 | 269 | glUniform4fv(self.shader.loc_inner_col, 1, &uniforms.inner_color as *const _ as *const f32); 270 | glUniform4fv(self.shader.loc_outer_col, 1, &uniforms.outer_color as *const _ as *const f32); 271 | 272 | glUniform2fv(self.shader.loc_scissor_ext, 1, &uniforms.scissor_ext as *const _ as *const f32); 273 | glUniform2fv(self.shader.loc_scissor_scale, 1, &uniforms.scissor_scale as *const _ as *const f32); 274 | glUniform2fv(self.shader.loc_extent, 1, &uniforms.extent as *const _ as *const f32); 275 | 276 | glUniform1f(self.shader.loc_radius, uniforms.radius); 277 | glUniform1f(self.shader.loc_feather, uniforms.feather); 278 | glUniform1f(self.shader.loc_stroke_mult, uniforms.stroke_mult); 279 | glUniform1f(self.shader.loc_stroke_thr, uniforms.stroke_thr); 280 | 281 | glUniform1i(self.shader.loc_tex_type, uniforms.tex_type); 282 | glUniform1i(self.shader.loc_type, uniforms.type_); 283 | 284 | // glBindBufferRange( // TODOKOLA 285 | // GL_UNIFORM_BUFFER, 286 | // 0, 287 | // self.frag_buf, 288 | // (offset * self.frag_size) as isize, 289 | // std::mem::size_of::() as GLsizeiptr, 290 | // ); 291 | 292 | if let Some(img) = img { 293 | if let Some(texture) = self.textures.get(img) { 294 | glBindTexture(GL_TEXTURE_2D, texture.tex); 295 | } 296 | } else { 297 | glBindTexture(GL_TEXTURE_2D, 0); 298 | } 299 | } 300 | 301 | unsafe fn do_fill(&self, call: &Call) { 302 | let paths = &self.paths[call.path_offset..call.path_offset + call.path_count]; 303 | 304 | glEnable(GL_STENCIL_TEST); 305 | glStencilMask(0xff); 306 | glStencilFunc(GL_ALWAYS, 0, 0xff); 307 | glColorMask(GL_FALSE as _, GL_FALSE as _, GL_FALSE as _, GL_FALSE as _); 308 | 309 | self.set_uniforms(call.uniform_offset, call.image); 310 | 311 | glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_INCR_WRAP); 312 | glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_DECR_WRAP); 313 | glDisable(GL_CULL_FACE); 314 | for path in paths { 315 | glDrawArrays( 316 | GL_TRIANGLE_FAN, 317 | path.fill_offset as i32, 318 | path.fill_count as i32, 319 | ); 320 | } 321 | glEnable(GL_CULL_FACE); 322 | 323 | glColorMask(GL_TRUE as _, GL_TRUE as _, GL_TRUE as _, GL_TRUE as _); 324 | 325 | self.set_uniforms(call.uniform_offset + 1, call.image); 326 | 327 | glStencilFunc(GL_EQUAL, 0x00, 0xff); 328 | glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); 329 | for path in paths { 330 | glDrawArrays( 331 | GL_TRIANGLE_STRIP, 332 | path.stroke_offset as i32, 333 | path.stroke_count as i32, 334 | ); 335 | } 336 | 337 | glStencilFunc(GL_NOTEQUAL, 0x00, 0xff); 338 | glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO); 339 | glDrawArrays( 340 | GL_TRIANGLE_STRIP, 341 | call.triangle_offset as i32, 342 | call.triangle_count as i32, 343 | ); 344 | 345 | glDisable(GL_STENCIL_TEST); 346 | } 347 | 348 | unsafe fn do_convex_fill(&self, call: &Call) { 349 | let paths = &self.paths[call.path_offset..call.path_offset + call.path_count]; 350 | self.set_uniforms(call.uniform_offset, call.image); 351 | for path in paths { 352 | glDrawArrays( 353 | GL_TRIANGLE_FAN, 354 | path.fill_offset as i32, 355 | path.fill_count as i32, 356 | ); 357 | if path.stroke_count > 0 { 358 | glDrawArrays( 359 | GL_TRIANGLE_STRIP, 360 | path.stroke_offset as i32, 361 | path.stroke_count as i32, 362 | ); 363 | } 364 | } 365 | } 366 | 367 | unsafe fn do_stroke(&self, call: &Call) { 368 | let paths = &self.paths[call.path_offset..call.path_offset + call.path_count]; 369 | 370 | glEnable(GL_STENCIL_TEST); 371 | glStencilMask(0xff); 372 | glStencilFunc(GL_EQUAL, 0x0, 0xff); 373 | glStencilOp(GL_KEEP, GL_KEEP, GL_INCR); 374 | self.set_uniforms(call.uniform_offset + 1, call.image); 375 | for path in paths { 376 | glDrawArrays( 377 | GL_TRIANGLE_STRIP, 378 | path.stroke_offset as i32, 379 | path.stroke_count as i32, 380 | ); 381 | } 382 | 383 | self.set_uniforms(call.uniform_offset, call.image); 384 | glStencilFunc(GL_EQUAL, 0x0, 0xff); 385 | glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); 386 | for path in paths { 387 | glDrawArrays( 388 | GL_TRIANGLE_STRIP, 389 | path.stroke_offset as i32, 390 | path.stroke_count as i32, 391 | ); 392 | } 393 | 394 | glColorMask(GL_FALSE as _, GL_FALSE as _, GL_FALSE as _, GL_FALSE as _); 395 | glStencilFunc(GL_ALWAYS, 0x0, 0xff); 396 | glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO); 397 | for path in paths { 398 | glDrawArrays( 399 | GL_TRIANGLE_STRIP, 400 | path.stroke_offset as i32, 401 | path.stroke_count as i32, 402 | ); 403 | } 404 | glColorMask(GL_TRUE as _, GL_TRUE as _, GL_TRUE as _, GL_TRUE as _); 405 | 406 | glDisable(GL_STENCIL_TEST); 407 | } 408 | 409 | unsafe fn do_triangles(&self, call: &Call) { 410 | self.set_uniforms(call.uniform_offset, call.image); 411 | glDrawArrays( 412 | GL_TRIANGLES, 413 | call.triangle_offset as i32, 414 | call.triangle_count as i32, 415 | ); 416 | } 417 | 418 | fn convert_paint( 419 | &self, 420 | paint: &Paint, 421 | scissor: &Scissor, 422 | width: f32, 423 | fringe: f32, 424 | stroke_thr: f32, 425 | ) -> FragUniforms { 426 | let mut frag = FragUniforms { 427 | scissor_mat: Default::default(), 428 | paint_mat: Default::default(), 429 | inner_color: premul_color(paint.inner_color), 430 | outer_color: premul_color(paint.outer_color), 431 | scissor_ext: Default::default(), 432 | scissor_scale: Default::default(), 433 | extent: Default::default(), 434 | radius: 0.0, 435 | feather: 0.0, 436 | stroke_mult: 0.0, 437 | stroke_thr, 438 | tex_type: 0, 439 | type_: 0, 440 | }; 441 | 442 | if scissor.extent.width < -0.5 || scissor.extent.height < -0.5 { 443 | frag.scissor_ext[0] = 1.0; 444 | frag.scissor_ext[1] = 1.0; 445 | frag.scissor_scale[0] = 1.0; 446 | frag.scissor_scale[1] = 1.0; 447 | } else { 448 | frag.scissor_mat = xform_to_3x4(scissor.xform.inverse()); 449 | frag.scissor_ext[0] = scissor.extent.width; 450 | frag.scissor_ext[1] = scissor.extent.height; 451 | frag.scissor_scale[0] = (scissor.xform.0[0] * scissor.xform.0[0] 452 | + scissor.xform.0[2] * scissor.xform.0[2]) 453 | .sqrt() 454 | / fringe; 455 | frag.scissor_scale[1] = (scissor.xform.0[1] * scissor.xform.0[1] 456 | + scissor.xform.0[3] * scissor.xform.0[3]) 457 | .sqrt() 458 | / fringe; 459 | } 460 | 461 | frag.extent = [paint.extent.width, paint.extent.height]; 462 | frag.stroke_mult = (width * 0.5 + fringe * 0.5) / fringe; 463 | 464 | let mut invxform = Transform::default(); 465 | 466 | if let Some(img) = paint.image { 467 | if let Some(texture) = self.textures.get(img) { 468 | if texture.flags.contains(ImageFlags::FLIPY) { 469 | let m1 = Transform::translate(0.0, frag.extent[1] * 0.5) * paint.xform; 470 | let m2 = Transform::scale(1.0, -1.0) * m1; 471 | let m1 = Transform::translate(0.0, -frag.extent[1] * 0.5) * m2; 472 | invxform = m1.inverse(); 473 | } else { 474 | invxform = paint.xform.inverse(); 475 | }; 476 | 477 | frag.type_ = ShaderType::FillImage as i32; 478 | match texture.texture_type { 479 | TextureType::RGBA => { 480 | frag.tex_type = if texture.flags.contains(ImageFlags::PREMULTIPLIED) { 481 | 0 482 | } else { 483 | 1 484 | } 485 | } 486 | TextureType::Alpha => frag.tex_type = 2, 487 | } 488 | } 489 | } else { 490 | frag.type_ = ShaderType::FillGradient as i32; 491 | frag.radius = paint.radius; 492 | frag.feather = paint.feather; 493 | invxform = paint.xform.inverse(); 494 | } 495 | 496 | frag.paint_mat = xform_to_3x4(invxform); 497 | 498 | frag 499 | } 500 | 501 | fn append_uniforms(&mut self, uniforms: FragUniforms) { 502 | self.uniforms.push(uniforms); 503 | // .resize(self.uniforms.len() + self.frag_size, 0); 504 | // unsafe { 505 | // let idx = self.uniforms.len() - self.frag_size; 506 | // let p = self.uniforms.as_mut_ptr().add(idx) as *mut FragUniforms; 507 | // *p = uniforms; 508 | // } 509 | } 510 | } 511 | 512 | impl renderer::Renderer for Renderer<'_> { 513 | fn edge_antialias(&self) -> bool { 514 | true 515 | } 516 | 517 | fn create_texture( 518 | &mut self, 519 | texture_type: TextureType, 520 | width: usize, 521 | height: usize, 522 | flags: ImageFlags, 523 | data: Option<&[u8]>, 524 | ) -> anyhow::Result { 525 | let tex = unsafe { 526 | let mut tex: GLuint = std::mem::zeroed(); 527 | glGenTextures(1, &mut tex); 528 | glBindTexture(GL_TEXTURE_2D, tex); 529 | // glPixelStorei(GL_UNPACK_ALIGNMENT, 1);TODOKOLA 530 | 531 | match texture_type { 532 | TextureType::RGBA => { 533 | glTexImage2D( 534 | GL_TEXTURE_2D, 535 | 0, 536 | GL_RGBA as i32, 537 | width as i32, 538 | height as i32, 539 | 0, 540 | GL_RGBA, 541 | GL_UNSIGNED_BYTE, 542 | match data { 543 | Some(data) => data.as_ptr() as *const c_void, 544 | Option::None => std::ptr::null(), 545 | }, 546 | ); 547 | } 548 | TextureType::Alpha => { 549 | glTexImage2D( 550 | GL_TEXTURE_2D, 551 | 0, 552 | GL_R8 as i32, 553 | width as i32, 554 | height as i32, 555 | 0, 556 | GL_RED, 557 | GL_UNSIGNED_BYTE, 558 | match data { 559 | Some(data) => data.as_ptr() as *const c_void, 560 | Option::None => std::ptr::null(), 561 | }, 562 | ); 563 | } 564 | } 565 | 566 | if flags.contains(ImageFlags::GENERATE_MIPMAPS) { 567 | if flags.contains(ImageFlags::NEAREST) { 568 | glTexParameteri( 569 | GL_TEXTURE_2D, 570 | GL_TEXTURE_MIN_FILTER, 571 | GL_NEAREST_MIPMAP_NEAREST as i32, 572 | ); 573 | } else { 574 | glTexParameteri( 575 | GL_TEXTURE_2D, 576 | GL_TEXTURE_MIN_FILTER, 577 | GL_LINEAR_MIPMAP_LINEAR as i32, 578 | ); 579 | } 580 | } else { 581 | if flags.contains(ImageFlags::NEAREST) { 582 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST as i32); 583 | } else { 584 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR as i32); 585 | } 586 | } 587 | 588 | if flags.contains(ImageFlags::NEAREST) { 589 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST as i32); 590 | } else { 591 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR as i32); 592 | } 593 | 594 | if flags.contains(ImageFlags::REPEATX) { 595 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT as i32); 596 | } else { 597 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE as i32); 598 | } 599 | 600 | if flags.contains(ImageFlags::REPEATY) { 601 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT as i32); 602 | } else { 603 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE as i32); 604 | } 605 | 606 | // glPixelStorei(GL_UNPACK_ALIGNMENT, 4); TODOKOLA 607 | 608 | // if flags.contains(ImageFlags::GENERATE_MIPMAPS) { TODOKOLA 609 | // glGenerateMipmap(GL_TEXTURE_2D); 610 | // } 611 | 612 | glBindTexture(GL_TEXTURE_2D, 0); 613 | tex 614 | }; 615 | 616 | let id = self.textures.insert(Texture { 617 | tex, 618 | width, 619 | height, 620 | texture_type, 621 | flags, 622 | }); 623 | Ok(id) 624 | } 625 | 626 | fn delete_texture(&mut self, img: ImageId) -> anyhow::Result<()> { 627 | if let Some(texture) = self.textures.get(img) { 628 | unsafe { glDeleteTextures(1, &texture.tex) } 629 | self.textures.remove(img); 630 | Ok(()) 631 | } else { 632 | bail!("texture '{}' not found", img); 633 | } 634 | } 635 | 636 | fn update_texture( 637 | &mut self, 638 | img: ImageId, 639 | x: usize, 640 | y: usize, 641 | width: usize, 642 | height: usize, 643 | data: &[u8], 644 | ) -> anyhow::Result<()> { 645 | if let Some(texture) = self.textures.get(img) { 646 | unsafe { 647 | glBindTexture(GL_TEXTURE_2D, texture.tex); 648 | // glPixelStorei(GL_UNPACK_ALIGNMENT, 1); TODOKOLA 649 | 650 | match texture.texture_type { 651 | TextureType::RGBA => glTexSubImage2D( 652 | GL_TEXTURE_2D, 653 | 0, 654 | x as i32, 655 | y as i32, 656 | width as i32, 657 | height as i32, 658 | GL_RGBA, 659 | GL_UNSIGNED_BYTE, 660 | data.as_ptr() as *const c_void, 661 | ), 662 | TextureType::Alpha => glTexSubImage2D( 663 | GL_TEXTURE_2D, 664 | 0, 665 | x as i32, 666 | y as i32, 667 | width as i32, 668 | height as i32, 669 | GL_RED, 670 | GL_UNSIGNED_BYTE, 671 | data.as_ptr() as *const c_void, 672 | ), 673 | } 674 | 675 | // glPixelStorei(GL_UNPACK_ALIGNMENT, 4); TODOKOLA 676 | glBindTexture(GL_TEXTURE_2D, 0); 677 | } 678 | Ok(()) 679 | } else { 680 | bail!("texture '{}' not found", img); 681 | } 682 | } 683 | 684 | fn texture_size(&self, img: ImageId) -> anyhow::Result<(usize, usize)> { 685 | if let Some(texture) = self.textures.get(img) { 686 | Ok((texture.width, texture.height)) 687 | } else { 688 | bail!("texture '{}' not found", img); 689 | } 690 | } 691 | 692 | fn viewport(&mut self, extent: Extent, _device_pixel_ratio: f32) -> anyhow::Result<()> { 693 | self.view = extent; 694 | Ok(()) 695 | } 696 | 697 | fn cancel(&mut self) -> anyhow::Result<()> { 698 | self.vertexes.clear(); 699 | self.paths.clear(); 700 | self.calls.clear(); 701 | self.uniforms.clear(); 702 | Ok(()) 703 | } 704 | 705 | fn flush(&mut self) -> anyhow::Result<()> { 706 | if !self.calls.is_empty() { 707 | unsafe { 708 | glUseProgram(self.shader.prog); 709 | 710 | glEnable(GL_CULL_FACE); 711 | glCullFace(GL_BACK); 712 | glFrontFace(GL_CCW); 713 | glEnable(GL_BLEND); 714 | glDisable(GL_DEPTH_TEST); 715 | glDisable(GL_SCISSOR_TEST); 716 | glColorMask(GL_TRUE as _, GL_TRUE as _, GL_TRUE as _, GL_TRUE as _); 717 | glStencilMask(0xffffffff); 718 | glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); 719 | glStencilFunc(GL_ALWAYS, 0, 0xffffffff); 720 | glActiveTexture(GL_TEXTURE0); 721 | glBindTexture(GL_TEXTURE_2D, 0); 722 | 723 | // glBindBuffer(GL_UNIFORM_BUFFER, self.frag_buf); TODOKOLA commented 724 | // glBufferData( 725 | // GL_UNIFORM_BUFFER, 726 | // self.uniforms.len() as GLsizeiptr, 727 | // self.uniforms.as_ptr() as *const c_void, 728 | // GL_STREAM_DRAW, 729 | // ); 730 | 731 | glBindVertexArray(self.vert_arr); 732 | glBindBuffer(GL_ARRAY_BUFFER, self.vert_buf); 733 | glBufferData( 734 | GL_ARRAY_BUFFER, 735 | (self.vertexes.len() * std::mem::size_of::()) as GLsizeiptr, 736 | self.vertexes.as_ptr() as *const c_void, 737 | GL_STREAM_DRAW, 738 | ); 739 | glEnableVertexAttribArray(self.shader.loc_vertex); 740 | glEnableVertexAttribArray(self.shader.loc_tcoord); 741 | glVertexAttribPointer( 742 | self.shader.loc_vertex, 743 | 2, // size in floats 744 | GL_FLOAT, 745 | GL_FALSE as GLboolean, 746 | std::mem::size_of::() as i32, 747 | std::ptr::null(), 748 | ); 749 | glVertexAttribPointer( 750 | self.shader.loc_tcoord, 751 | 2, // size in floats 752 | GL_FLOAT, 753 | GL_FALSE as GLboolean, 754 | std::mem::size_of::() as i32, 755 | (2 * std::mem::size_of::()) as *const c_void, // use GL_ARRAY_BUFFER, and skip x,y (2 floats) to start sampling at u, v 756 | ); 757 | 758 | glUniform1i(self.shader.loc_tex, 0); 759 | glUniform2fv( 760 | self.shader.loc_viewsize, 761 | 1, 762 | &self.view as *const Extent as *const f32, 763 | ); 764 | 765 | // glBindBuffer(GL_UNIFORM_BUFFER, self.frag_buf); TODOKOLA 766 | 767 | for call in &self.calls { 768 | let blend = &call.blend_func; 769 | 770 | glBlendFuncSeparate( 771 | blend.src_rgb, 772 | blend.dst_rgb, 773 | blend.src_alpha, 774 | blend.dst_alpha, 775 | ); 776 | 777 | match call.call_type { 778 | CallType::Fill => self.do_fill(&call), 779 | CallType::ConvexFill => self.do_convex_fill(&call), 780 | CallType::Stroke => self.do_stroke(&call), 781 | CallType::Triangles => self.do_triangles(&call), 782 | } 783 | } 784 | 785 | glDisableVertexAttribArray(self.shader.loc_vertex); 786 | glDisableVertexAttribArray(self.shader.loc_tcoord); 787 | glBindVertexArray(0); 788 | glDisable(GL_CULL_FACE); 789 | glBindBuffer(GL_ARRAY_BUFFER, 0); 790 | glUseProgram(0); 791 | glBindTexture(GL_TEXTURE_2D, 0); 792 | } 793 | } 794 | 795 | self.vertexes.clear(); 796 | self.paths.clear(); 797 | self.calls.clear(); 798 | self.uniforms.clear(); 799 | Ok(()) 800 | } 801 | 802 | fn fill( 803 | &mut self, 804 | paint: &Paint, 805 | composite_operation: CompositeOperationState, 806 | scissor: &Scissor, 807 | fringe: f32, 808 | bounds: Bounds, 809 | paths: &[Path], 810 | ) -> anyhow::Result<()> { 811 | let mut call = Call { 812 | call_type: CallType::Fill, 813 | image: paint.image, 814 | path_offset: self.paths.len(), 815 | path_count: paths.len(), 816 | triangle_offset: 0, 817 | triangle_count: 4, 818 | uniform_offset: 0, 819 | blend_func: composite_operation.into(), 820 | }; 821 | 822 | if paths.len() == 1 && paths[0].convex { 823 | call.call_type = CallType::ConvexFill; 824 | } 825 | 826 | let mut offset = self.vertexes.len(); 827 | for path in paths { 828 | let fill = path.get_fill(); 829 | let mut gl_path = GLPath { 830 | fill_offset: 0, 831 | fill_count: 0, 832 | stroke_offset: 0, 833 | stroke_count: 0, 834 | }; 835 | 836 | if !fill.is_empty() { 837 | gl_path.fill_offset = offset; 838 | gl_path.fill_count = fill.len(); 839 | self.vertexes.extend(fill); 840 | offset += fill.len(); 841 | } 842 | 843 | let stroke = path.get_stroke(); 844 | if !stroke.is_empty() { 845 | gl_path.stroke_offset = offset; 846 | gl_path.stroke_count = stroke.len(); 847 | self.vertexes.extend(stroke); 848 | offset += stroke.len(); 849 | } 850 | 851 | self.paths.push(gl_path); 852 | } 853 | 854 | if call.call_type == CallType::Fill { 855 | call.triangle_offset = offset; 856 | self.vertexes 857 | .push(Vertex::new(bounds.max.x, bounds.max.y, 0.5, 1.0)); 858 | self.vertexes 859 | .push(Vertex::new(bounds.max.x, bounds.min.y, 0.5, 1.0)); 860 | self.vertexes 861 | .push(Vertex::new(bounds.min.x, bounds.max.y, 0.5, 1.0)); 862 | self.vertexes 863 | .push(Vertex::new(bounds.min.x, bounds.min.y, 0.5, 1.0)); 864 | 865 | call.uniform_offset = self.uniforms.len(); 866 | self.append_uniforms(FragUniforms { 867 | stroke_thr: -1.0, 868 | type_: ShaderType::Simple as i32, 869 | ..FragUniforms::default() 870 | }); 871 | self.append_uniforms(self.convert_paint(paint, scissor, fringe, fringe, -1.0)); 872 | } else { 873 | call.uniform_offset = self.uniforms.len(); 874 | self.append_uniforms(self.convert_paint(paint, scissor, fringe, fringe, -1.0)); 875 | } 876 | 877 | self.calls.push(call); 878 | Ok(()) 879 | } 880 | 881 | fn stroke( 882 | &mut self, 883 | paint: &Paint, 884 | composite_operation: CompositeOperationState, 885 | scissor: &Scissor, 886 | fringe: f32, 887 | stroke_width: f32, 888 | paths: &[Path], 889 | ) -> anyhow::Result<()> { 890 | let mut call = Call { 891 | call_type: CallType::Stroke, 892 | image: paint.image, 893 | path_offset: self.paths.len(), 894 | path_count: paths.len(), 895 | triangle_offset: 0, 896 | triangle_count: 0, 897 | uniform_offset: 0, 898 | blend_func: composite_operation.into(), 899 | }; 900 | 901 | let mut offset = self.vertexes.len(); 902 | for path in paths { 903 | let mut gl_path = GLPath { 904 | fill_offset: 0, 905 | fill_count: 0, 906 | stroke_offset: 0, 907 | stroke_count: 0, 908 | }; 909 | 910 | let stroke = path.get_stroke(); 911 | if !stroke.is_empty() { 912 | gl_path.stroke_offset = offset; 913 | gl_path.stroke_count = stroke.len(); 914 | self.vertexes.extend(stroke); 915 | offset += stroke.len(); 916 | self.paths.push(gl_path); 917 | } 918 | } 919 | 920 | call.uniform_offset = self.uniforms.len(); 921 | self.append_uniforms(self.convert_paint(paint, scissor, stroke_width, fringe, -1.0)); 922 | self.append_uniforms(self.convert_paint( 923 | paint, 924 | scissor, 925 | stroke_width, 926 | fringe, 927 | 1.0 - 0.5 / 255.0, 928 | )); 929 | 930 | self.calls.push(call); 931 | Ok(()) 932 | } 933 | 934 | fn triangles( 935 | &mut self, 936 | paint: &Paint, 937 | composite_operation: CompositeOperationState, 938 | scissor: &Scissor, 939 | vertexes: &[Vertex], 940 | ) -> anyhow::Result<()> { 941 | let call = Call { 942 | call_type: CallType::Triangles, 943 | image: paint.image, 944 | path_offset: 0, 945 | path_count: 0, 946 | triangle_offset: self.vertexes.len(), 947 | triangle_count: vertexes.len(), 948 | uniform_offset: self.uniforms.len(), 949 | blend_func: composite_operation.into(), 950 | }; 951 | 952 | self.calls.push(call); 953 | self.vertexes.extend(vertexes); 954 | 955 | let mut uniforms = self.convert_paint(paint, scissor, 1.0, 1.0, -1.0); 956 | uniforms.type_ = ShaderType::Image as i32; 957 | self.append_uniforms(uniforms); 958 | Ok(()) 959 | } 960 | } 961 | 962 | fn shader_error(shader: GLuint, filename: &str) -> anyhow::Error { 963 | unsafe { 964 | let mut data: [GLchar; 512 + 1] = std::mem::zeroed(); 965 | let mut len: GLsizei = std::mem::zeroed(); 966 | glGetShaderInfoLog(shader, 512, &mut len, data.as_mut_ptr()); 967 | if len > 512 { 968 | len = 512; 969 | } 970 | data[len as usize] = 0; 971 | let err_msg = std::ffi::CStr::from_ptr(data.as_ptr()); 972 | anyhow!( 973 | "failed to compile shader: {}: {}", 974 | filename, 975 | err_msg.to_string_lossy() 976 | ) 977 | } 978 | } 979 | 980 | fn program_error(prog: GLuint) -> anyhow::Error { 981 | unsafe { 982 | let mut data: [GLchar; 512 + 1] = std::mem::zeroed(); 983 | let mut len: GLsizei = std::mem::zeroed(); 984 | glGetProgramInfoLog(prog, 512, &mut len, data.as_mut_ptr()); 985 | if len > 512 { 986 | len = 512; 987 | } 988 | data[len as usize] = 0; 989 | let err_msg = std::ffi::CStr::from_ptr(data.as_ptr()); 990 | anyhow!("failed to link program: {}", err_msg.to_string_lossy()) 991 | } 992 | } 993 | 994 | fn convert_blend_factor(factor: BlendFactor) -> GLenum { 995 | match factor { 996 | BlendFactor::Zero => GL_ZERO, 997 | BlendFactor::One => GL_ONE, 998 | BlendFactor::SrcColor => GL_SRC_COLOR, 999 | BlendFactor::OneMinusSrcColor => GL_ONE_MINUS_SRC_COLOR, 1000 | BlendFactor::DstColor => GL_DST_COLOR, 1001 | BlendFactor::OneMinusDstColor => GL_ONE_MINUS_DST_COLOR, 1002 | BlendFactor::SrcAlpha => GL_SRC_ALPHA, 1003 | BlendFactor::OneMinusSrcAlpha => GL_ONE_MINUS_SRC_ALPHA, 1004 | BlendFactor::DstAlpha => GL_DST_ALPHA, 1005 | BlendFactor::OneMinusDstAlpha => GL_ONE_MINUS_DST_ALPHA, 1006 | BlendFactor::SrcAlphaSaturate => GL_SRC_ALPHA_SATURATE, 1007 | } 1008 | } 1009 | 1010 | #[inline] 1011 | fn premul_color(color: Color) -> Color { 1012 | Color { 1013 | r: color.r * color.a, 1014 | g: color.g * color.a, 1015 | b: color.b * color.a, 1016 | a: color.a, 1017 | } 1018 | } 1019 | 1020 | #[inline] 1021 | fn xform_to_3x4(xform: Transform) -> [f32; 12] { 1022 | let mut m = [0f32; 12]; 1023 | let t = &xform.0; 1024 | m[0] = t[0]; 1025 | m[1] = t[1]; 1026 | m[2] = 0.0; 1027 | m[3] = 0.0; 1028 | m[4] = t[2]; 1029 | m[5] = t[3]; 1030 | m[6] = 0.0; 1031 | m[7] = 0.0; 1032 | m[8] = t[4]; 1033 | m[9] = t[5]; 1034 | m[10] = 1.0; 1035 | m[11] = 0.0; 1036 | m 1037 | } 1038 | -------------------------------------------------------------------------------- /nonaquad/src/shader.frag: -------------------------------------------------------------------------------- 1 | #version 100 2 | 3 | precision highp float; 4 | 5 | uniform mat4 scissorMat; 6 | uniform mat4 paintMat; 7 | uniform vec4 innerCol; 8 | uniform vec4 outerCol; 9 | uniform vec2 scissorExt; 10 | uniform vec2 scissorScale; 11 | uniform vec2 extent; 12 | uniform float radius; 13 | uniform float feather; 14 | uniform float strokeMult; 15 | uniform float strokeThr; 16 | 17 | // texture type: 18 | // 0: RGBA, premultiplied 19 | // 1: RGBA, not premultiplied 20 | // 2: Alpha texture, alpha value is stored in .a (miniquad always stores in .a for alpha textures) 21 | uniform int texType; 22 | uniform int type; 23 | 24 | uniform sampler2D tex; 25 | varying vec2 ftcoord; 26 | varying vec2 fpos; 27 | //out vec4 outColor; 28 | 29 | float sdroundrect(vec2 pt, vec2 ext, float rad) { 30 | vec2 ext2 = ext - vec2(rad,rad); 31 | vec2 d = abs(pt) - ext2; 32 | return min(max(d.x, d.y), 0.0) + length(max(d, 0.0)) - rad; 33 | } 34 | 35 | float scissorMask(vec2 p) { 36 | vec2 sc = (abs((mat3(scissorMat) * vec3(p, 1.0)).xy) - scissorExt); 37 | sc = vec2(0.5,0.5) - sc * scissorScale; 38 | return clamp(sc.x, 0.0, 1.0) * clamp(sc.y, 0.0, 1.0); 39 | } 40 | 41 | float strokeMask() { 42 | return min(1.0, (1.0 - abs(ftcoord.x * 2.0 - 1.0)) * strokeMult) * min(1.0, ftcoord.y); 43 | } 44 | 45 | void main(void) { 46 | vec4 result; 47 | float scissor = scissorMask(fpos); 48 | float strokeAlpha = strokeMask(); 49 | if (strokeAlpha < strokeThr) discard; 50 | 51 | if (type == 0) { 52 | // Gradient 53 | vec2 pt = (mat3(paintMat) * vec3(fpos,1.0)).xy; 54 | float d = clamp((sdroundrect(pt, extent, radius) + feather * 0.5) / feather, 0.0, 1.0); 55 | vec4 color = mix(innerCol, outerCol, d); 56 | color *= strokeAlpha * scissor; 57 | result = color; 58 | } else if (type == 1) { 59 | // Image 60 | vec2 pt = (mat3(paintMat) * vec3(fpos, 1.0)).xy / extent; 61 | vec4 color = texture2D(tex, pt); 62 | if (texType == 1) color = vec4(color.xyz * color.w, color.w); // premultiply non-premultiplied texture 63 | if (texType == 2) color = vec4(color.a); // alpha texture 64 | color *= innerCol; 65 | color *= strokeAlpha * scissor; 66 | result = color; 67 | } else if (type == 2) { 68 | // Stencil fill 69 | result = vec4(1, 1, 1, 1); 70 | } else if (type == 3) { 71 | // Textured tris 72 | vec4 color = texture2D(tex, ftcoord); 73 | if (texType == 1) color = vec4(color.xyz * color.w, color.w); // premultiply non-premultiplied texture 74 | if (texType == 2) color = vec4(color.a); // alpha texture 75 | color *= scissor; 76 | result = color * innerCol; 77 | } 78 | 79 | gl_FragColor = result; 80 | // gl_FragColor = vec4(1,0,0,1); 81 | } 82 | -------------------------------------------------------------------------------- /nonaquad/src/shader.vert: -------------------------------------------------------------------------------- 1 | #version 100 2 | 3 | // shader validator: http://shdr.bkcore.com/ 4 | uniform vec2 viewSize; 5 | // layout(location = 0) in vec2 vertex; 6 | // layout(location = 1) in vec2 tcoord; 7 | attribute vec2 vertex; 8 | attribute vec2 tcoord; 9 | varying lowp vec2 ftcoord; 10 | varying highp vec2 fpos; 11 | 12 | void main(void) { 13 | ftcoord = tcoord; 14 | fpos = vertex; 15 | 16 | // y = -1..1 (bottom to top) 17 | // x = -1..1 (left to right) 18 | // convert coordinates from topleft: (0,0)->(viewSize.x, viewSize.y) to topleft: (-1, 1)->(1, -1) 19 | gl_Position = vec4(2.0 * vertex.x / viewSize.x - 1.0, 1.0 - 2.0 * vertex.y / viewSize.y, 0, 1); 20 | // gl_Position = vec4(vertex.x / viewSize.x, vertex.y / viewSize.y, 0, 1); // uncomment to only normalize coords 21 | } 22 | --------------------------------------------------------------------------------