Compare commits

..

2 commits

Author SHA1 Message Date
e254edabf7 refactored 2024-09-07 18:59:55 -04:00
8702cddf44 update 2024-08-30 10:02:02 -04:00
21 changed files with 1804 additions and 1116 deletions

1685
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -2,7 +2,6 @@
members = [ members = [
"libcxgraph", "libcxgraph",
"cxgraph-desktop",
"cxgraph-web", "cxgraph-web",
] ]
resolver = "2"

View file

@ -3,6 +3,15 @@
cxgraph is a complex function graphing tool built around WebGPU available cxgraph is a complex function graphing tool built around WebGPU available
[on the web](https://cx.trimill.xyz/) or (slightly) for desktop. [on the web](https://cx.trimill.xyz/) or (slightly) for desktop.
## building (web)
install `wasm-pack` through your package manager or with `cargo install wasm-pack`.
```sh
cd cxgraph-web
wasm-pack build --no-typescript --no-pack --target web
```
## documentation ## documentation
- [language](docs/language.md) - [language](docs/language.md)
- [web interface](docs/web.md) - [web interface](docs/web.md)

View file

@ -1,11 +0,0 @@
[package]
name = "cxgraph-desktop"
version = "0.1.0"
edition = "2021"
[dependencies]
libcxgraph = { path = "../libcxgraph" }
log = "0.4"
env_logger = "0.10"
winit = "0.28"
pollster = "0.3"

View file

@ -1,47 +0,0 @@
use std::collections::HashMap;
use libcxgraph::{renderer::WgpuState, language::compile};
use winit::{event_loop::EventLoop, window::Window, event::{Event, WindowEvent}};
fn main() {
env_logger::builder()
.filter_level(log::LevelFilter::Warn)
.init();
let src = "plot(z) = 27^z - 9^z - 3^z";
let wgsl = compile(src, &HashMap::new()).unwrap();
println!("{wgsl}");
let event_loop = EventLoop::new();
let window = Window::new(&event_loop).unwrap();
window.set_title("window");
pollster::block_on(run(event_loop, window, &wgsl));
}
async fn run(event_loop: EventLoop<()>, window: Window, code: &str) {
let size = window.inner_size();
let mut state = WgpuState::new(&window, size.into()).await;
state.load_shaders(code);
state.uniforms.bounds_min = (-5.0, -5.0).into();
state.uniforms.bounds_max = ( 5.0, 5.0).into();
state.uniforms.shading_intensity = 0.3;
event_loop.run(move |event, _, control_flow| {
control_flow.set_wait();
match event {
Event::WindowEvent { event: WindowEvent::CloseRequested, .. }
=> control_flow.set_exit(),
Event::RedrawRequested(_)
=> state.redraw(),
Event::WindowEvent { event: WindowEvent::Resized(size), .. } => {
state.resize(size.into());
window.request_redraw();
}
_ => (),
}
});
}

View file

@ -9,7 +9,7 @@ crate-type = ["cdylib", "rlib"]
[dependencies] [dependencies]
libcxgraph = { path = "../libcxgraph", features = ["webgl"] } libcxgraph = { path = "../libcxgraph", features = ["webgl"] }
log = "0.4" log = "0.4"
winit = "0.28" winit = "0.29"
console_error_panic_hook = "0.1" console_error_panic_hook = "0.1"
console_log = "1.0" console_log = "1.0"
wasm-bindgen = "0.2" wasm-bindgen = "0.2"

161
cxgraph-web/editor.js Normal file
View file

@ -0,0 +1,161 @@
//
// Keyboard handling
//
const TAB_WIDTH = 4;
let sourceFocused = false;
source_text.addEventListener("mousedown", () => { sourceFocused = true; });
source_text.addEventListener("focusout", () => { sourceFocused = false; });
source_text.addEventListener("keydown", (event) => {
console.log(event);
if (event.key != "Tab") {
sourceFocused = true;
}
if (event.key == "Enter" && event.shiftKey) {
event.preventDefault();
button_graph.click();
} else if (event.key == "Escape") {
sourceFocused = false;
} else if (event.key == "Backspace" && !event.ctrlKey) {
let selStart = source_text.selectionStart;
let selEnd = source_text.selectionEnd;
if (selStart == selEnd) {
let pre = source_text.value.slice(0, selStart);
let lineStart = pre.lastIndexOf("\n") + 1;
let preLine = pre.slice(lineStart);
if (preLine.length > 2 && preLine.trim() == "") {
let count = (selStart - lineStart - 1)%TAB_WIDTH + 1;
for (let i = 0; i < count; i++) {
if (pre[pre.length - i - 1] != " ") {
count = i;
}
}
let post = source_text.value.slice(selStart);
source_text.value = pre.slice(0, selStart - count) + post;
source_text.selectionStart = selStart - count;
source_text.selectionEnd = selEnd - count;
event.preventDefault();
}
}
} else if (event.key == "Tab" && sourceFocused) {
event.preventDefault();
let selStart = source_text.selectionStart;
let selEnd = source_text.selectionEnd;
let pre = source_text.value.slice(0, selStart);
let post = source_text.value.slice(selStart);
let lineStart = pre.lastIndexOf("\n") + 1;
if (event.shiftKey) {
let count = (selStart - lineStart - 1)%TAB_WIDTH + 1;
for (let i = 0; i < count; i++) {
if (pre[pre.length - i - 1] != " ") {
count = i;
}
}
if (count > 0) {
source_text.value = pre.slice(0, selStart - count) + post;
source_text.selectionStart = selStart - count;
source_text.selectionEnd = selEnd - count;
}
} else {
let count = TAB_WIDTH - (selStart - lineStart)%TAB_WIDTH;
source_text.value = pre + " ".repeat(count) + post;
source_text.selectionStart = selStart + count;
source_text.selectionEnd = selEnd + count;
}
}
});
//
// Special characters
//
export let charMap = {
"alpha": "\u03b1",
"beta": "\u03b2",
"gamma": "\u03b3",
"delta": "\u03b4",
"epsilon": "\u03b5",
"zeta": "\u03b6",
"eta": "\u03b7",
"theta": "\u03b8",
"iota": "\u03b9",
"kappa": "\u03ba",
"lambda": "\u03bb",
"mu": "\u03bc",
"nu": "\u03bd",
"xi": "\u03be",
"omicron": "\u03bf",
"pi": "\u03c0",
"rho": "\u03c1",
"fsigma": "\u03c2",
"sigma": "\u03c3",
"tau": "\u03c4",
"upsilon": "\u03c5",
"phi": "\u03c6",
"chi": "\u03c7",
"psi": "\u03c8",
"omega": "\u03c9",
"Alpha": "\u0391",
"Beta": "\u0392",
"Gamma": "\u0393",
"Delta": "\u0394",
"Epsilon": "\u0395",
"Zeta": "\u0396",
"Eta": "\u0397",
"Theta": "\u0398",
"Iota": "\u0399",
"Kappa": "\u039a",
"Lambda": "\u039b",
"Mu": "\u039c",
"Nu": "\u039d",
"Xi": "\u039e",
"Omicron": "\u039f",
"Pi": "\u03a0",
"Rho": "\u03a1",
"Sigma": "\u03a3",
"Tau": "\u03a4",
"Upsilon": "\u03a5",
"Phi": "\u03a6",
"Chi": "\u03a7",
"Psi": "\u03a8",
"Omega": "\u03a9",
"vartheta": "\u03d1",
"0": "\u2080",
"1": "\u2081",
"2": "\u2082",
"3": "\u2083",
"4": "\u2084",
"5": "\u2085",
"6": "\u2086",
"7": "\u2087",
"8": "\u2088",
"9": "\u2089",
};
let specialChars = new RegExp(
`\\\\(${Object.keys(charMap).join("|")})`
);
source_text.addEventListener("input", (event) => {
if(event.isComposing) return;
let e = source_text.selectionEnd;
let amnt = 0;
source_text.value = source_text.value.replace(
specialChars,
(m, p) => {
amnt += m.length - charMap[p].length;
return charMap[p];
}
);
source_text.selectionEnd = e - amnt;
});

View file

@ -1,7 +1,16 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html lang="en">
<head> <head>
<title>cxgraph</title> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="author" content="trimill">
<meta name="description" content="Plot complex functions">
<meta property="og:description" content="Plot complex functions">
<meta property="og:title" content="CXGraph">
<meta property="og:site_name" content="cx.trimill.xyz">
<title>CXGraph</title>
<link rel="stylesheet" href="style.css"> <link rel="stylesheet" href="style.css">
</head> </head>
<body> <body>
@ -12,8 +21,8 @@
<line id="svg_axis_y" x1="0" y1="0" x2="0" y2="0" stroke="#0006" stroke-width="1.5" visibility="hidden" /> <line id="svg_axis_y" x1="0" y1="0" x2="0" y2="0" stroke="#0006" stroke-width="1.5" visibility="hidden" />
<circle id="svg_unitcircle" cx="0" cy="0" r="0" stroke="#0006" fill="none" stroke-width="1.5" visibility="hidden" /> <circle id="svg_unitcircle" cx="0" cy="0" r="0" stroke="#0006" fill="none" stroke-width="1.5" visibility="hidden" />
<g id="svg_point_template" visibility="hidden"> <g id="svg_point_template" visibility="hidden">
<circle cx="0" cy="0" r="15" stroke="none" fill="#6664" /> <circle cx="0" cy="0" r="15" stroke="none" fill="#3337" />
<circle cx="0" cy="0" r="5" stroke="none" fill="#6666" /> <circle cx="0" cy="0" r="5" stroke="none" fill="#dddc" />
</g> </g>
<g id="overlay_points"> <g id="overlay_points">
</g> </g>
@ -32,7 +41,7 @@
</div> </div>
<div id="div_error_msg" hidden></div> <div id="div_error_msg" hidden></div>
<div><textarea id="source_text">f(z) = z^2 + 3i&#10;plot(z) = 5z^2 + f(1/z) - 1</textarea></div> <div><textarea id="source_text">f(z) = 6z^2 - 2i - 1&#10;plot(z) = f(1 + sin(z)) / 8</textarea></div>
</details> </details>
<div> <div>
@ -52,11 +61,6 @@
<input type="range" id="range_shading" min="0" max="1" step="0.01" value="0.3"> <input type="range" id="range_shading" min="0" max="1" step="0.01" value="0.3">
</div> </div>
<div>
<div><label for="range_contour">Contour intensity</label></div>
<input type="range" id="range_contour" min="0" max="1" step="0.01" value="0.0">
</div>
<div> <div>
<div> <div>
<input type="checkbox" class="decor" id="checkbox_decor_1" data-value="1"> <input type="checkbox" class="decor" id="checkbox_decor_1" data-value="1">
@ -77,11 +81,16 @@
<input type="checkbox" class="decor" id="checkbox_decor_8" data-value="8"> <input type="checkbox" class="decor" id="checkbox_decor_8" data-value="8">
<label for="checkbox_decor_8">Magnitude contours</label> <label for="checkbox_decor_8">Magnitude contours</label>
</div> </div>
<div>
<div><label for="range_contour">Contour intensity</label></div>
<input type="range" id="range_contour" min="0" max="1" step="0.01" value="0.0">
</div>
</div> </div>
<div> <div>
<div>Coloring</div> <div>Coloring</div>
<input type="radio" name="color_mode" id="radio_color_0" data-value="0" checked> <input type="radio" name="color_mode" id="radio_color_0" data-value="0">
<label for="radio_color_0">Standard</label><br> <label for="radio_color_0">Standard</label><br>
<input type="radio" name="color_mode" id="radio_color_1" data-value="1"> <input type="radio" name="color_mode" id="radio_color_1" data-value="1">
<label for="radio_color_1">Uniform</label><br> <label for="radio_color_1">Uniform</label><br>
@ -106,7 +115,7 @@
<summary>Variables</summary> <summary>Variables</summary>
<div id="slider_template" hidden> <div id="slider_template" hidden>
<div> <div>
<input type="text" class="var-name" style="width: 5ch;""> <input type="text" class="var-name" style="width: 5ch;" placeholder="a">
= =
<input type="number" class="var-value" style="width: 10ch;" value="0" required> <input type="number" class="var-value" style="width: 10ch;" value="0" required>
<input type="button" class="var-delete" value="X"> <input type="button" class="var-delete" value="X">
@ -122,7 +131,7 @@
<hr> <hr>
</div> </div>
<div id="point_template" hidden> <div id="point_template" hidden>
<input type="text" class="var-name" style="width: 5ch;"> <input type="text" class="var-name" style="width: 5ch;" placeholder="b">
= =
<input type="number" class="var-value-re" style="width: 8ch;" required value="0"> <input type="number" class="var-value-re" style="width: 8ch;" required value="0">
+ +
@ -140,7 +149,9 @@
</div> </div>
<script> <script>
import('./index.js').then(m => window.module = m) window.modules = {};
import('./index.js').then(m => window.modules.index = m);
import('./editor.js').then(m => window.modules.editor = m);
</script> </script>
</body> </body>
</html> </html>

View file

@ -4,15 +4,14 @@ import init, * as cxgraph from "./pkg/cxgraph_web.js";
await init(); await init();
let graphView = { let graphView = {
xoff: 0, xoff: 0.00001,
yoff: 0, yoff: 0.00001,
scale: 3, scale: 2.99932736,
res_mult: 1, res_mult: 1,
varNames: [], varNames: [],
}; };
let graphPoints = []; let graphPoints = [];
let graphSliders = [];
let mouseX = 0.0; let mouseX = 0.0;
let mouseY = 0.0; let mouseY = 0.0;
@ -111,20 +110,20 @@ function onWheel(e) {
onViewChange(); onViewChange();
} }
function onMouseDown(e) { function onPointerDown(e) {
mousePressed = true; mousePressed = true;
mouseX = e.offsetX; mouseX = e.offsetX;
mouseY = e.offsetY; mouseY = e.offsetY;
} }
function onMouseUp(e) { function onPointerUp() {
mousePressed = false; mousePressed = false;
for(let point of graphPoints) { for(let point of graphPoints) {
point.mousePressed = false; point.mousePressed = false;
} }
} }
function onMouseMove(e) { function onPointerMove(e) {
if(mousePressed) { if(mousePressed) {
let dX = e.offsetX - mouseX; let dX = e.offsetX - mouseX;
let dY = e.offsetY - mouseY; let dY = e.offsetY - mouseY;
@ -135,16 +134,16 @@ function onMouseMove(e) {
onViewChange(); onViewChange();
} else { } else {
for(let point of graphPoints) { for(let point of graphPoints) {
point.onMouseMove(e); point.onPointerMove(e);
} }
} }
} }
window.addEventListener("resize", onResize); window.addEventListener("resize", onResize);
canvas.addEventListener("wheel", onWheel); canvas.addEventListener("wheel", onWheel);
canvas.addEventListener("mousedown", onMouseDown); canvas.addEventListener("pointerdown", onPointerDown);
canvas.addEventListener("mouseup", onMouseUp); canvas.addEventListener("pointerup", onPointerUp);
canvas.addEventListener("mousemove", onMouseMove); canvas.addEventListener("pointermove", onPointerMove);
// //
// Graph/redraw // Graph/redraw
@ -166,76 +165,6 @@ function onGraph() {
button_graph.addEventListener("click", onGraph); button_graph.addEventListener("click", onGraph);
button_redraw.addEventListener("click", redraw); button_redraw.addEventListener("click", redraw);
let charMap = {
"alpha": "\u03b1",
"beta": "\u03b2",
"gamma": "\u03b3",
"delta": "\u03b4",
"epsilon": "\u03b5",
"zeta": "\u03b6",
"eta": "\u03b7",
"theta": "\u03b8",
"iota": "\u03b9",
"kappa": "\u03ba",
"lambda": "\u03bb",
"mu": "\u03bc",
"nu": "\u03bd",
"xi": "\u03be",
"omicron": "\u03bf",
"pi": "\u03c0",
"rho": "\u03c1",
"fsigma": "\u03c2",
"sigma": "\u03c3",
"tau": "\u03c4",
"upsilon": "\u03c5",
"phi": "\u03c6",
"chi": "\u03c7",
"psi": "\u03c8",
"omega": "\u03c9",
"Alpha": "\u0391",
"Beta": "\u0392",
"Gamma": "\u0393",
"Delta": "\u0394",
"Epsilon": "\u0395",
"Zeta": "\u0396",
"Eta": "\u0397",
"Theta": "\u0398",
"Iota": "\u0399",
"Kappa": "\u039a",
"Lambda": "\u039b",
"Mu": "\u039c",
"Nu": "\u039d",
"Xi": "\u039e",
"Omicron": "\u039f",
"Pi": "\u03a0",
"Rho": "\u03a1",
"Sigma": "\u03a3",
"Tau": "\u03a4",
"Upsilon": "\u03a5",
"Phi": "\u03a6",
"Chi": "\u03a7",
"Psi": "\u03a8",
"Omega": "\u03a9",
"vartheta": "\u03d1",
};
let specialChars = new RegExp(
`\\\\(${Object.keys(charMap).join("|")})`
);
console.log(specialChars);
source_text.addEventListener("input", () => {
let e = source_text.selectionEnd;
let amnt = 0;
source_text.value = source_text.value.replace(
specialChars,
(m, p) => {
amnt += m.length - charMap[p].length;
return charMap[p];
}
);
source_text.selectionEnd = e - amnt;
});
// //
// Options // Options
// //
@ -288,7 +217,8 @@ for(let e of nameColorMode) {
}); });
e.checked = false; e.checked = false;
} }
nameColorMode[0].checked = true; nameColorMode[1].checked = true;
cxgraph.set_coloring(1);
overlay_axes.addEventListener("change", () => { overlay_axes.addEventListener("change", () => {
let vis = overlay_axes.checked ? "visible" : "hidden"; let vis = overlay_axes.checked ? "visible" : "hidden";
@ -419,24 +349,24 @@ class Point {
tryRedraw(); tryRedraw();
}); });
svgPoint.addEventListener("mousedown", (e) => { svgPoint.addEventListener("pointerdown", (e) => {
this.mousePressed = true; this.mousePressed = true;
mouseX = e.offsetX; mouseX = e.offsetX;
mouseY = e.offsetY; mouseY = e.offsetY;
}); });
svgPoint.addEventListener("mouseup", () => { svgPoint.addEventListener("pointerup", () => {
this.mousePressed = false; this.mousePressed = false;
mousePressed = false; mousePressed = false;
}); });
svgPoint.addEventListener("mousemove", (e) => this.onMouseMove(e)); svgPoint.addEventListener("pointermove", (e) => this.onPointerMove(e));
this.onViewChange(); this.onViewChange();
genVarNames(); genVarNames();
} }
onMouseMove(e) { onPointerMove(e) {
if(this.mousePressed) { if(this.mousePressed) {
mouseX = e.offsetX; mouseX = e.offsetX;
mouseY = e.offsetY; mouseY = e.offsetY;
@ -486,3 +416,5 @@ onGraph();
export function show_ast() { export function show_ast() {
console.info(cxgraph.show_shader_ast(source_text.value)); console.info(cxgraph.show_shader_ast(source_text.value));
} }
export function get_cxgraph() { return cxgraph; }

View file

@ -20,7 +20,6 @@ where F: Fn(&mut WgpuState) {
#[wasm_bindgen(start)] #[wasm_bindgen(start)]
pub async fn start() { pub async fn start() {
use winit::dpi::PhysicalSize; use winit::dpi::PhysicalSize;
use winit::platform::web::WindowExtWebSys;
std::panic::set_hook(Box::new(console_error_panic_hook::hook)); std::panic::set_hook(Box::new(console_error_panic_hook::hook));
console_log::init_with_level(log::Level::Info).expect("Couldn't initialize logger"); console_log::init_with_level(log::Level::Info).expect("Couldn't initialize logger");
@ -34,16 +33,18 @@ pub async fn start() {
.dyn_into() .dyn_into()
.expect("Canvas was not a canvas"); .expect("Canvas was not a canvas");
let event_loop = EventLoop::new(); let event_loop = EventLoop::new().unwrap();
let window = WindowBuilder::new() let window = WindowBuilder::new()
.with_canvas(Some(canvas)) .with_canvas(Some(canvas))
.with_prevent_default(false)
.with_inner_size(PhysicalSize::new(100, 100)) .with_inner_size(PhysicalSize::new(100, 100))
.with_title("window") .with_title("window")
.build(&event_loop) .build(&event_loop)
.expect("Failed to build window"); .expect("Failed to build window");
let size = window.inner_size(); let window_ref = Box::leak(Box::new(window));
let mut state = WgpuState::new(&window, size.into()).await;
let mut state = WgpuState::new(window_ref, (100, 100)).await;
state.uniforms.bounds_min = (-5.0, -5.0).into(); state.uniforms.bounds_min = (-5.0, -5.0).into();
state.uniforms.bounds_max = ( 5.0, 5.0).into(); state.uniforms.bounds_max = ( 5.0, 5.0).into();
state.uniforms.shading_intensity = 0.3; state.uniforms.shading_intensity = 0.3;

View file

@ -26,6 +26,7 @@ canvas, #overlay {
#canvas { #canvas {
z-index: 0; z-index: 0;
-webkit-transform: translate3d(0, 0, 0);
} }
#overlay { #overlay {

View file

@ -1,47 +1,123 @@
# cxgraph language # CXGraph language
cxgraph uses a custom expression language that is compiled to WGSL. CXGraph uses a custom expression language that is compiled to WGSL.
## names ## Names
names must begin with any alphabetic character (lowercase or capital letters, Greek letters, Names must begin with any alphabetic character (lowercase or capital letters,
etc.) and may contain alphanumeric chararcters as well as underscores (`_`) and apostrophes (`'`). Greek letters, etc.) and may contain alphanumeric chararcters as well as
the words `sum`, `prod`, and `iter` may not be used for names. names may refer to either underscores (`_`) and apostrophes (`'`). The words `sum`, `prod`, `iter`,
functions or variables. and `if` may not be used for names. Names may refer to either functions
or variables.
examples of names include: Examples of names include:
``` ```
a A aaa ω z_3 __5__ f' Кαl'けx焼__검 a A aaa ω z_3 __5__ f' Кαl'けx焼__검
``` ```
names may either be **built-in**, **global**, or **local**. global or local names may shadow Names may either be **built-in**, **global**, or **local**. global or local names
built-in names, and local names may shadow global ones. may shadow built-in names, and local names may shadow global ones.
## declarations ## Declarations
a **function declaration** declares a new function. functions may have zero or more arguments. A **function declaration** declares a new function. Functions may have zero
or more arguments.
``` ```
f(x) = 3 f(x) = 3
``` ```
a **constant declaration** declares a new constant. A **constant declaration** declares a new constant.
``` ```
n = 5 n = 5
``` ```
declarations are separated by newlines. declarations may only reference functions and constants in Declarations are separated by newlines. Declarations may only reference functions
declarations that precede them. the name used in a declaration (`f` and `n` in the above examples) and constants in declarations that precede them. The name used in a declaration
is in the global scope (`f` and `n` in the above examples) is in the global scope.
## built-ins The `plot` function is special and serves as the entry point. It must exist and have exactly one argument.
## Operators
Below is a reference to all operators in the CXGraph language.
| operator | description | precedence |
|----------|----------------------------|------------|
| `,` | separate expressions | 0 |
| `->` | assign to | 1 |
| `==` | equal | 2 |
| `!=` | not equal | 2 |
| `>` | real part greater | 3 |
| `<` | real part less | 3 |
| `>=` | real part greater or equal | 3 |
| `<=` | real part less or equal | 3 |
| `+` | addition | 4 |
| `-` | subtraction | 4 |
| `*` | multiplication | 5 |
| `/` | division | 5 |
| `^` | power | 6 |
The comma `,` separates expressions in locations where multiple are allowed (ie.
in a definition or block).
The arrow `->` stores the value of the expression to its left to the local
variable on the right.
The equality operators `==` and `!=` compare two values, considering both their
real and imaginary components. The comparison operators `>`, `<`, `>=`, and `<=`
only consider the real component.
`+`, `-`, and `*` also function as the unary plus, minus, and conugation operators.
Multiplication can also be done via juxtaposition - `2x(x+1)` is equivalent to `2*x*(x+1)`.
Juxtaposition takes precedence over other operations, so `1/2x` is `1/(2*x)`, not `1/2*x`,
and `z^5(x+1)` is `z^(5*(x+1))`, not `z^5*(x+1)`.
## Grouping
Parentheses `( )` can be used to group within an expression. Braces `{ }` can be used to group multiple expressions, separated by commas. Newlines, which ordinarily separate declarations, are treated as whitespace between grouping symbols, allowing for multiline definitions.
## Repetition and conditionals
To allow for finite sums and products, the `sum` and `prod` expressions can be used.
```
plot(z) = sum(n: 0, 20) { z^n / n }
```
The first parameter to `sum` or `prod` is the name of the summation index, the next two
values are the lower and upper bounds (inclusive). These may be arbitrary expressions,
they are converted to integers by rounding down the real component. The value of the body
for each index value is summed or multiplied and the result is returned.
`iter` works similarly:
```
plot(c) = iter(20, 0 -> z) { z^2 + c }
```
The first parameter is the number of iterations, the second is an assignment to initialize
the iteration variable. For each iteration, the iteration variable will be updated to the
new value of the body.
`if` can be used to choose between two expressions based on a condition:
```
if(z > 0) { z^2 } { 2z }
```
If the argument's real part is positive, the first body will be evaluated, and otherwise the
second will be.
## Built-in functions and constants
arithmetic functions: arithmetic functions:
| name | description | | name | description |
|---------------|-------------------------------------------------------| |---------------|-------------------------------------------------------|
| `pos(z)` | equivalent to unary `+` | | `pos(z)` | identity, equivalent to unary `+` |
| `neg(z)` | equivalent to unary `-` | | `neg(z)` | equivalent to unary `-` |
| `conj(z)` | complex conjugate, equivalent to unary `*` | | `conj(z)` | complex conjugate, equivalent to unary `*` |
| `re(z)` | real part | | `re(z)` | real part |
@ -58,11 +134,11 @@ arithmetic functions:
power/exponential functions: power/exponential functions:
| name | description | | name | description |
|---------------|-------------------------------------------| |----------------|-------------------------------------------|
| `exp(z)` | exponential function, equivalent to `e^z` | | `exp(z)` | Exponential function, equivalent to `e^z` |
| `log(z)` | logarithm base `e` | | `log(z)` | Natural logarithm |
| `logbr(z,br)` | logarithm base `e` with specified branch | | `logbr(z,br)` | Natural logarithm with specified branch |
| `pow(z)` | power, equivalent to `^` | | `pow(z)` | Power, equivalent to `^` |
| `powbr(z,br)` | `pow` with specified branch | | `powbr(z,br)` | `pow` with specified branch |
| `sqrt(z)` | square root, equivalent to `z^0.5` | | `sqrt(z)` | square root, equivalent to `z^0.5` |
| `sqrtbr(z,br)` | square root with specified branch | | `sqrtbr(z,br)` | square root with specified branch |
@ -72,48 +148,47 @@ power/exponential functions:
trigonometric functions: trigonometric functions:
| name | description | | name | description |
|------------|-------------------------------------| |------------|-------------------------------------|
| `sin(z)` | sine function | | `sin(z)` | Sine function |
| `cos(z)` | cosine function | | `cos(z)` | Cosine function |
| `tan(z)` | tangent function | | `tan(z)` | Tangent function |
| `sinh(z)` | hyperbolic sine function | | `sinh(z)` | Hyperbolic sine function |
| `cosh(z)` | hyperbolic cosine function | | `cosh(z)` | Hyperbolic cosine function |
| `tanh(z)` | hyperbolic tangent function | | `tanh(z)` | Hyperbolic tangent function |
| `asin(z)` | inverse sine function | | `asin(z)` | Inverse sine function |
| `acos(z)` | inverse cosine function | | `acos(z)` | Inverse cosine function |
| `atan(z)` | inverse tangent function | | `atan(z)` | Inverse tangent function |
| `asinh(z)` | inverse hyperbolic sine function | | `asinh(z)` | Inverse hyperbolic sine function |
| `acosh(z)` | inverse hyperbolic cosine function | | `acosh(z)` | Inverse hyperbolic cosine function |
| `atanh(z)` | inverse hyperbolic tangent function | | `atanh(z)` | Inverse hyperbolic tangent function |
special functions: special functions:
| function | description | | function | description |
|--------------------------|--------------------------------------------------------------------| |--------------------------|------------------------------------------------------------------------|
| `gamma(z)`, `Γ(z)` | [gamma function](https://en.wikipedia.org/wiki/Gamma_function) | | `gamma(z)`, `Γ(z)` | [gamma function](https://en.wikipedia.org/wiki/Gamma_function) |
| `invgamma(z)`, `invΓ(z)` | reciprocal of the gamma function | | `invgamma(z)`, `invΓ(z)` | reciprocal of the gamma function |
| `loggamma(z)`, `logΓ(z)` | logarithm of the gamma function | | `loggamma(z)`, `logΓ(z)` | logarithm of the gamma function |
| `digamma(z)`, `ψ(z)` | [digamma function](https://en.wikipedia.org/wiki/Digamma_function) | | `digamma(z)`, `ψ(z)` | [digamma function](https://en.wikipedia.org/wiki/Digamma_function) |
| `lambertw(z)` | [Lambert W function](https://en.wikipedia.org/wiki/Lambert_W_function) |
| `lambertwbr(z,br)` | Lambert W function on specfied branch |
| `erf(z)` | The [Error function](https://en.wikipedia.org/wiki/Error_function) |
logic functions: logic functions:
| function | description | | function | description |
|-----------------|----------------------------------------------------------------------------| |-------------|----------------------------------------------------------------------------|
| `signre(z)` | sign of real part (1 if `re(z) > 0`, -1 if `re(z) < 0`, 0 if `re(z) == 0`) | | `signre(z)` | Sign of real part (1 if `re(z) > 0`, -1 if `re(z) < 0`, 0 if `re(z) == 0`) |
| `signim(z)` | sign of imaginary part | | `signim(z)` | Sign of imaginary part |
| `ifgt(p,q,z,w)` | evaluates to `z` if `re(p) > re(q)`, otherwise `w` | | `absre(z)` | Absolute value of real part |
| `iflt(p,q,z,w)` | evaluates to `z` if `re(p) < re(q)`, otherwise `w` | | `absim(z)` | Absolute value of imaginary part |
| `ifge(p,q,z,w)` | evaluates to `z` if `re(p) ≥ re(q)`, otherwise `w` | | `isnan(z)` | 1 if `z` is NaN, 0 otherwise |
| `ifle(p,q,z,w)` | evaluates to `z` if `re(p) ≤ re(q)`, otherwise `w` |
| `ifeq(p,q,z,w)` | evaluates to `z` if `re(p) = re(q)`, otherwise `w` |
| `ifne(p,q,z,w)` | evaluates to `z` if `re(p) ≠ re(q)`, otherwise `w` |
| `ifnan(p,z,w)` | evaluates to `z` if `p` is `NaN`, otherwise `w` |
constants: constants:
| name | description | | name | description |
|----------------|--------------------------------------------------------------------------------------------------------| |----------------|--------------------------------------------------------------------------------------------------------|
| `i` | the imaginary constant, equal to `sqrt(-1)` | | `i` | The imaginary constant, equal to `sqrt(-1)` |
| `e` | the [exponential constant](https://en.wikipedia.org/wiki/E_(mathematical_constant)), equal to `exp(1)` | | `e` | The [exponential constant](https://en.wikipedia.org/wiki/E_(mathematical_constant)), equal to `exp(1)` |
| `tau`, `τ` | the [circle constant](https://tauday.com/tau-manifesto) | | `tau`, `τ` | The [circle constant](https://tauday.com/tau-manifesto) |
| `emgamma`, `γ` | the [Euler-Mascheroni](https://en.wikipedia.org/wiki/Euler%27s_constant) constant, equal to `-ψ(1)` | | `emgamma`, `γ` | The [Euler-Mascheroni](https://en.wikipedia.org/wiki/Euler%27s_constant) constant, equal to `-ψ(1)` |
| `phi`, `φ` | the [golden ratio](https://en.wikipedia.org/wiki/Golden_ratio), equal to `1/2 + sqrt(5)/2` | | `phi`, `φ` | The [golden ratio](https://en.wikipedia.org/wiki/Golden_ratio), equal to `1/2 + sqrt(5)/2` |
## ebnf grammar ## ebnf grammar
@ -129,7 +204,17 @@ Exprs := (Expr ",")* Expr ","?
Expr := Store Expr := Store
Store := Store "->" NAME | Sum Store := Equality "->" NAME | Equality
Equality := Compare "==" Compare
| Compare "!=" Compare
| Compare
Compare := Sum ">" Sum
| Sum "<" Sum
| Sum ">=" Sum
| Sum "<=" Sum
| Sum
Sum := Sum "+" Product Sum := Sum "+" Product
| Sum "-" Product | Sum "-" Product
@ -151,15 +236,16 @@ Power := FnCall "^" Unary | FnCall
FnCall := NAME "(" Exprs ")" | Item FnCall := NAME "(" Exprs ")" | Item
PreJuxtapose := Number | "(" <Expr> ")" PreJuxtapose := NUMBER | "(" <Expr> ")"
Item := Number Block := "{" Exprs "}"
Item := NUMBER
| NAME | NAME
| "(" Expr ")" | "(" Expr ")"
| "{" Exprs "}" | Block
| "sum" "(" NAME ":" INT "," INT ")" "{" Exprs "}" | "sum" "(" NAME ":" Expr "," Expr ")" Block
| "prod" "(" NAME ":" INT "," INT ")" "{" Exprs "}" | "prod" "(" NAME ":" Expr "," Expr ")" block
| "iter" "(" INT "," NAME ":" Expr ")" "{" Exprs "}" | "iter" "(" Expr "," Expr "->" NAME ")" Block
| "if" "(" Expr ")" Block Block
Number = FLOAT | INT
``` ```

View file

@ -0,0 +1,33 @@
# CXGraph Web UI
## Source
Enter the program to plot into the text area. For more information, see [the language docs](language.md).
The Graph button compiles the program and redraws the screen. This must be pressed after changes are made to the program to see them. This can also be accomplished by pressing `Shift`+`Enter` in the text area.
The Redraw button redraws the screen. If Auto Redraw is enabled, the screen will be redrawn automatically after every change (eg. dragging, zooming, changing options or variables).
## The plot
Most of the screen is occupied by the plot. Click and drag with the mouse to move around, and use the scroll wheel to zoom in and out.
## Options
Reset View resets the plot's position and scale. Help opens the documentation.
The Resolution slider controls the canvas's resolution scale on a range from x0.25 to x4. This is set to x1 by default. Higher values provide better visuals at the expense of performance.
Shading Intensity controls how intense the black and white shading near zero/infinity is. Setting this to zero disables shading.
Contours can be toggled with the contour checkboxes. Real and imaginary contours show the integer grid, argument contours show angles around the origin divided into 16 segments, and magnitude contours show magnitudes delineated by powers of two.
Standard coloring directly maps argument to hue in HSV while keeping saturation and value constant. Uniform coloring uses a modified mapping that tries to avoid variation in perceptual brightness. None disables coloring entirely.
Draw Axes and Unit Circle enable or disable the axes and circle overlays.
## Variables
Sliders can be added with the "+slider" button. Once the slider has been named, the variable name can be used in the program and the plot will redraw automatically when the slider's value is changed. The slider's start, step, and end default to 1, 0.01, and -1, respectively. The slider's value can also be edited directly.
Points can be added with the "+point" button. They behave similarly to sliders, but add a draggable point to the plot. The position of the point can also be edited directly.

View file

@ -9,10 +9,11 @@ webgl = ["wgpu/webgl"]
[dependencies] [dependencies]
log = "0.4" log = "0.4"
lalrpop-util = { version = "0.20.0", features = ["lexer", "unicode"] } lalrpop-util = { version = "0.21.0", features = ["lexer", "unicode"] }
num-complex = "0.4" num-complex = "0.4"
wgpu = "0.16" wgpu = "22.1"
raw-window-handle = "0.5" raw-window-handle = "0.6"
unicode-xid = "0.2"
[build-dependencies] [build-dependencies]
lalrpop = "0.20.0" lalrpop = "0.21.0"

View file

@ -5,6 +5,7 @@ use num_complex::Complex64 as Complex;
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub enum BinaryOp { pub enum BinaryOp {
Add, Sub, Mul, Div, Pow, Add, Sub, Mul, Div, Pow,
Gt, Lt, Ge, Le, Eq, Ne,
} }
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
@ -21,9 +22,10 @@ pub enum ExpressionType<'a> {
Unary(UnaryOp), Unary(UnaryOp),
FnCall(&'a str), FnCall(&'a str),
Store(&'a str), Store(&'a str),
Sum { countvar: &'a str, min: i32, max: i32 }, If,
Prod { countvar: &'a str, min: i32, max: i32 }, Sum { countvar: &'a str },
Iter { itervar: &'a str, count: i32 }, Prod { countvar: &'a str },
Iter { itervar: &'a str },
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -61,25 +63,31 @@ impl<'a> Expression<'a> {
Self { ty: ExpressionType::Store(name), children: vec![expr] } Self { ty: ExpressionType::Store(name), children: vec![expr] }
} }
pub fn new_sum(countvar: &'a str, min: i32, max: i32, body: Vec<Self>) -> Self { pub fn new_if(cond: Self, t: Self, f: Self) -> Self {
Self { Self {
ty: ExpressionType::Sum { countvar, min, max }, ty: ExpressionType::If,
children: body, children: vec![cond, t, f],
} }
} }
pub fn new_prod(accvar: &'a str, min: i32, max: i32, body: Vec<Self>) -> Self { pub fn new_sum(countvar: &'a str, min: Self, max: Self, body: Self) -> Self {
Self { Self {
ty: ExpressionType::Prod { countvar: accvar, min, max }, ty: ExpressionType::Sum { countvar },
children: body, children: vec![min, max, body],
} }
} }
pub fn new_iter(itervar: &'a str, count: i32, init: Self, mut body: Vec<Self>) -> Self { pub fn new_prod(countvar: &'a str, min: Self, max: Self, body: Self) -> Self {
body.push(init);
Self { Self {
ty: ExpressionType::Iter { itervar, count }, ty: ExpressionType::Prod { countvar },
children: body, children: vec![min, max, body],
}
}
pub fn new_iter(itervar: &'a str, count: Self, init: Self, body: Self) -> Self {
Self {
ty: ExpressionType::Iter { itervar },
children: vec![count, init, body],
} }
} }
} }
@ -99,9 +107,10 @@ fn display_expr(w: &mut impl fmt::Write, expr: &Expression, depth: usize) -> fmt
ExpressionType::Unary(op) => write!(w, "{:indent$}OP {op:?}", "", indent=indent)?, ExpressionType::Unary(op) => write!(w, "{:indent$}OP {op:?}", "", indent=indent)?,
ExpressionType::FnCall(f) => write!(w, "{:indent$}CALL {f}", "", indent=indent)?, ExpressionType::FnCall(f) => write!(w, "{:indent$}CALL {f}", "", indent=indent)?,
ExpressionType::Store(n) => write!(w, "{:indent$}STORE {n}", "", indent=indent)?, ExpressionType::Store(n) => write!(w, "{:indent$}STORE {n}", "", indent=indent)?,
ExpressionType::Sum { countvar, min, max } => write!(w, "{:indent$}SUM {countvar} {min} {max}", "", indent=indent)?, ExpressionType::If => write!(w, "{:indent$}IF", "", indent=indent)?,
ExpressionType::Prod { countvar, min, max } => write!(w, "{:indent$}PROD {countvar} {min} {max}", "", indent=indent)?, ExpressionType::Sum { countvar } => write!(w, "{:indent$}SUM {countvar}", "", indent=indent)?,
ExpressionType::Iter { itervar, count } => write!(w, "{:indent$}ITER {itervar} {count}", "", indent=indent)?, ExpressionType::Prod { countvar } => write!(w, "{:indent$}PROD {countvar}", "", indent=indent)?,
ExpressionType::Iter { itervar } => write!(w, "{:indent$}ITER {itervar}", "", indent=indent)?,
} }
writeln!(w)?; writeln!(w)?;
for child in &expr.children { for child in &expr.children {

View file

@ -10,18 +10,13 @@ thread_local! {
m.insert("recip", ("c_recip", 1)); m.insert("recip", ("c_recip", 1));
m.insert("conj", ("c_conj", 1)); m.insert("conj", ("c_conj", 1));
m.insert("ifgt", ("c_ifgt", 4));
m.insert("iflt", ("c_iflt", 4));
m.insert("ifge", ("c_ifge", 4));
m.insert("ifle", ("c_ifle", 4));
m.insert("ifeq", ("c_ifeq", 4));
m.insert("ifne", ("c_ifne", 4));
m.insert("ifnan", ("c_ifnan", 3));
m.insert("re", ("c_re", 1)); m.insert("re", ("c_re", 1));
m.insert("im", ("c_im", 1)); m.insert("im", ("c_im", 1));
m.insert("signre", ("c_signre", 1)); m.insert("signre", ("c_signre", 1));
m.insert("signim", ("c_signim", 1)); m.insert("signim", ("c_signim", 1));
m.insert("absre", ("c_absre", 1));
m.insert("absim", ("c_absim", 1));
m.insert("isnan", ("c_isnan", 1));
m.insert("abs_sq", ("c_abs_sq", 1)); m.insert("abs_sq", ("c_abs_sq", 1));
m.insert("abs", ("c_abs", 1)); m.insert("abs", ("c_abs", 1));
m.insert("arg", ("c_arg", 1)); m.insert("arg", ("c_arg", 1));
@ -48,7 +43,6 @@ thread_local! {
m.insert("sinh", ("c_sinh", 1)); m.insert("sinh", ("c_sinh", 1));
m.insert("cosh", ("c_cosh", 1)); m.insert("cosh", ("c_cosh", 1));
m.insert("tanh", ("c_tanh", 1)); m.insert("tanh", ("c_tanh", 1));
m.insert("asin", ("c_asin", 1)); m.insert("asin", ("c_asin", 1));
m.insert("acos", ("c_acos", 1)); m.insert("acos", ("c_acos", 1));
m.insert("atan", ("c_atan", 1)); m.insert("atan", ("c_atan", 1));
@ -64,6 +58,9 @@ thread_local! {
m.insert("log\u{0393}", ("c_loggamma", 1)); m.insert("log\u{0393}", ("c_loggamma", 1));
m.insert("digamma", ("c_digamma", 1)); m.insert("digamma", ("c_digamma", 1));
m.insert("\u{03C8}", ("c_digamma", 1)); m.insert("\u{03C8}", ("c_digamma", 1));
m.insert("lambertw", ("c_lambertw", 1));
m.insert("lambertwbr", ("c_lambertwbr", 2));
m.insert("erf", ("c_erf", 1));
m m
}; };

View file

@ -29,6 +29,11 @@ fn format_char(buf: &mut String, c: char) {
match c { match c {
'_' => buf.push_str("u_"), '_' => buf.push_str("u_"),
'\'' => buf.push_str("p_"), '\'' => buf.push_str("p_"),
'\u{2080}'..='\u{2089}' => {
buf.push('s');
buf.push((c as u32 - 0x2080 + '0' as u32).try_into().expect("invalid codepoint"));
buf.push('_');
}
c => buf.push(c), c => buf.push(c),
} }
} }
@ -190,6 +195,12 @@ impl<'w, 'i, W: fmt::Write> Compiler<'w, 'i, W> {
BinaryOp::Mul => writeln!(self.buf, "var {name} = c_mul({a}, {b});")?, BinaryOp::Mul => writeln!(self.buf, "var {name} = c_mul({a}, {b});")?,
BinaryOp::Div => writeln!(self.buf, "var {name} = c_div({a}, {b});")?, BinaryOp::Div => writeln!(self.buf, "var {name} = c_div({a}, {b});")?,
BinaryOp::Pow => writeln!(self.buf, "var {name} = c_pow({a}, {b});")?, BinaryOp::Pow => writeln!(self.buf, "var {name} = c_pow({a}, {b});")?,
BinaryOp::Gt => writeln!(self.buf, "var {name} = select(C_ZERO, C_ONE, {a}.x > {b}.x);")?,
BinaryOp::Lt => writeln!(self.buf, "var {name} = select(C_ZERO, C_ONE, {a}.x < {b}.x);")?,
BinaryOp::Ge => writeln!(self.buf, "var {name} = select(C_ZERO, C_ONE, {a}.x >= {b}.x);")?,
BinaryOp::Le => writeln!(self.buf, "var {name} = select(C_ZERO, C_ONE, {a}.x <= {b}.x);")?,
BinaryOp::Eq => writeln!(self.buf, "var {name} = select(C_ZERO, C_ONE, ({a}.x == {b}.x) && ({a}.y == {b}.y));")?,
BinaryOp::Ne => writeln!(self.buf, "var {name} = select(C_ZERO, C_ONE, ({a}.x != {b}.x) || ({a}.y != {b}.y));")?,
} }
Ok(name) Ok(name)
@ -226,8 +237,28 @@ impl<'w, 'i, W: fmt::Write> Compiler<'w, 'i, W> {
Ok(name) Ok(name)
}, },
ExpressionType::Sum { countvar, min, max } ExpressionType::If => {
| ExpressionType::Prod { countvar, min, max } => { let cond = self.compile_expr(local, &expr.children[0])?;
let result = local.next_tmp();
writeln!(self.buf, "var {result}: vec2f;")?;
writeln!(self.buf, "if {cond}.x > 0.0 {{")?;
let t = self.compile_expr(local, &expr.children[1])?;
writeln!(self.buf, "{result} = {t};")?;
writeln!(self.buf, "}} else {{")?;
let f = self.compile_expr(local, &expr.children[2])?;
writeln!(self.buf, "{result} = {f};")?;
writeln!(self.buf, "}}")?;
Ok(result)
},
ExpressionType::Sum { countvar }
| ExpressionType::Prod { countvar } => {
let min = local.next_tmp();
let max = local.next_tmp();
let v = self.compile_expr(local, &expr.children[0])?;
writeln!(self.buf, "var {min} = i32(floor({v}.x));")?;
let v = self.compile_expr(local, &expr.children[1])?;
writeln!(self.buf, "var {max} = i32(floor({v}.x));")?;
let acc = local.next_tmp(); let acc = local.next_tmp();
let ivar = local.next_tmp(); let ivar = local.next_tmp();
if matches!(expr.ty, ExpressionType::Sum { .. }) { if matches!(expr.ty, ExpressionType::Sum { .. }) {
@ -235,35 +266,36 @@ impl<'w, 'i, W: fmt::Write> Compiler<'w, 'i, W> {
} else { } else {
writeln!(self.buf, "var {acc} = vec2f(1.0, 0.0);")?; writeln!(self.buf, "var {acc} = vec2f(1.0, 0.0);")?;
} }
writeln!(self.buf, "for(var {ivar}: i32 = {min}; {ivar} <= {max}; {ivar}++) {{")?; writeln!(self.buf, "for(var {ivar} = {min}; {ivar} <= {max}; {ivar}++) {{")?;
writeln!(self.buf, "var {} = vec2f(f32({ivar}), 0.0);", format_local(countvar))?; writeln!(self.buf, "var {} = vec2f(f32({ivar}), 0.0);", format_local(countvar))?;
let mut loop_local = local.clone(); let mut loop_local = local.clone();
loop_local.local_vars.insert(countvar); loop_local.local_vars.insert(countvar);
let mut last = String::new(); let body = self.compile_expr(&mut loop_local, &expr.children[2])?;
for child in &expr.children {
last = self.compile_expr(&mut loop_local, child)?;
}
if matches!(expr.ty, ExpressionType::Sum { .. }) { if matches!(expr.ty, ExpressionType::Sum { .. }) {
writeln!(self.buf, "{acc} = {acc} + {last};\n}}")?; writeln!(self.buf, "{acc} = {acc} + {body};")?;
} else { } else {
writeln!(self.buf, "{acc} = c_mul({acc}, {last});\n}}")?; writeln!(self.buf, "{acc} = c_mul({acc}, {body});")?;
} }
writeln!(self.buf, "}}")?;
Ok(acc) Ok(acc)
}, },
ExpressionType::Iter { itervar, count } => { ExpressionType::Iter { itervar } => {
let init = expr.children.last().unwrap(); let countvar = local.next_tmp();
let v = self.compile_expr(local, &expr.children[0])?;
writeln!(self.buf, "var {countvar} = i32(floor({v}.x));")?;
let init = &expr.children[1];
let itervar_fmt = format_local(itervar); let itervar_fmt = format_local(itervar);
let v = self.compile_expr(local, init)?; let v = self.compile_expr(local, init)?;
writeln!(self.buf, "var {itervar_fmt} = {v};")?; writeln!(self.buf, "var {itervar_fmt} = {v};")?;
let ivar = local.next_tmp(); let ivar = local.next_tmp();
writeln!(self.buf, "for(var {ivar}: i32 = 0; {ivar} < {count}; {ivar}++) {{")?; writeln!(self.buf, "for(var {ivar}: i32 = 0; {ivar} < {countvar}; {ivar}++) {{")?;
let mut loop_local = local.clone(); let mut loop_local = local.clone();
loop_local.local_vars.insert(itervar); loop_local.local_vars.insert(itervar);
let mut last = String::new(); let body = self.compile_expr(&mut loop_local, &expr.children[2])?;
for child in &expr.children[..expr.children.len() - 1] { writeln!(self.buf, "{itervar_fmt} = {body};")?;
last = self.compile_expr(&mut loop_local, child)?; writeln!(self.buf, "}}")?;
}
writeln!(self.buf, "{itervar_fmt} = {last};\n}}")?;
Ok(itervar_fmt) Ok(itervar_fmt)
} }
} }

View file

@ -21,12 +21,18 @@ extern {
"->" => Token::Arrow, "->" => Token::Arrow,
"=" => Token::Equal, "=" => Token::Equal,
":" => Token::Colon, ":" => Token::Colon,
">" => Token::Greater,
"<" => Token::Less,
">=" => Token::GreaterEqual,
"<=" => Token::LessEqual,
"==" => Token::EqualEqual,
"!=" => Token::BangEqual,
"\n" => Token::Newline, "\n" => Token::Newline,
"sum" => Token::Sum, "sum" => Token::Sum,
"prod" => Token::Prod, "prod" => Token::Prod,
"iter" => Token::Iter, "iter" => Token::Iter,
Float => Token::Float(<f64>), "if" => Token::If,
Int => Token::Int(<i32>), Number => Token::Number(<f64>),
Name => Token::Name(<&'input str>), Name => Token::Name(<&'input str>),
} }
} }
@ -60,7 +66,21 @@ Exprs: Vec<Expression<'input>> = {
Expr: Expression<'input> = Store; Expr: Expression<'input> = Store;
Store: Expression<'input> = { Store: Expression<'input> = {
<a:Store> "->" <n:Name> => Expression::new_store(a, n), <a:Equality> "->" <n:Name> => Expression::new_store(a, n),
Equality,
}
Equality: Expression<'input> = {
<a:Compare> "==" <b:Compare> => Expression::new_binary(BinaryOp::Eq, a, b),
<a:Compare> "!=" <b:Compare> => Expression::new_binary(BinaryOp::Ne, a, b),
Compare,
}
Compare: Expression<'input> = {
<a:Sum> ">" <b:Sum> => Expression::new_binary(BinaryOp::Gt, a, b),
<a:Sum> "<" <b:Sum> => Expression::new_binary(BinaryOp::Lt, a, b),
<a:Sum> ">=" <b:Sum> => Expression::new_binary(BinaryOp::Ge, a, b),
<a:Sum> "<=" <b:Sum> => Expression::new_binary(BinaryOp::Le, a, b),
Sum, Sum,
} }
@ -101,24 +121,25 @@ FnCall: Expression<'input> = {
} }
PreJuxtapose: Expression<'input> = { PreJuxtapose: Expression<'input> = {
Number, <n:Number> => Expression::new_number(n),
"(" <Expr> ")", "(" <Expr> ")",
} }
Block: Expression<'input> = {
"{" <exs:Exprs> "}" => Expression::new_block(exs),
}
Item: Expression<'input> = { Item: Expression<'input> = {
Number, <n:Number> => Expression::new_number(n),
<n:Name> => Expression::new_name(n), <n:Name> => Expression::new_name(n),
"(" <Expr> ")", "(" <Expr> ")",
"{" <exs:Exprs> "}" => Expression::new_block(exs), Block,
"sum" "(" <name:Name> ":" <min:Int> "," <max:Int> ")" "{" <exs:Exprs> "}" "sum" "(" <name:Name> ":" <min:Expr> "," <max:Expr> ")" <body:Block>
=> Expression::new_sum(name, min, max, exs), => Expression::new_sum(name, min, max, body),
"prod" "(" <name:Name> ":" <min:Int> "," <max:Int> ")" "{" <exs:Exprs> "}" "prod" "(" <name:Name> ":" <min:Expr> "," <max:Expr> ")" <body:Block>
=> Expression::new_prod(name, min, max, exs), => Expression::new_prod(name, min, max, body),
"iter" "(" <count:Int> "," <name:Name> ":" <init:Expr> ")" "{" <exs:Exprs> "}" "iter" "(" <count:Expr> "," <init:Equality> "->" <name:Name> ")" <body:Block>
=> Expression::new_iter(name, count, init, exs), => Expression::new_iter(name, count, init, body),
} "if" "(" <cond:Expr> ")" <t:Block> <f:Block>
=> Expression::new_if(cond, t, f),
Number: Expression<'input> = {
<n:Float> => Expression::new_number(n),
<n:Int> => Expression::new_number(n as f64),
} }

View file

@ -1,15 +1,18 @@
use std::{str::CharIndices, iter::Peekable, fmt}; use std::{str::CharIndices, iter::Peekable, fmt};
use unicode_xid::UnicodeXID;
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub enum Token<'i> { pub enum Token<'i> {
Float(f64), Number(f64),
Int(i32),
Name(&'i str), Name(&'i str),
Sum, Prod, Iter, Sum, Prod, Iter, If,
LParen, RParen, LParen, RParen,
LBrace, RBrace, LBrace, RBrace,
Plus, Minus, Star, Slash, Caret, Plus, Minus, Star, Slash, Caret,
Greater, Less, GreaterEqual, LessEqual,
EqualEqual, BangEqual,
Comma, Arrow, Equal, Colon, Comma, Arrow, Equal, Colon,
Newline, Newline,
} }
@ -17,12 +20,12 @@ pub enum Token<'i> {
impl<'i> fmt::Display for Token<'i> { impl<'i> fmt::Display for Token<'i> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
Token::Float(n) => write!(f, "{n}"), Token::Number(n) => write!(f, "{n}"),
Token::Int(n) => write!(f, "{n}"),
Token::Name(n) => write!(f, "{n}"), Token::Name(n) => write!(f, "{n}"),
Token::Sum => f.write_str("sum"), Token::Sum => f.write_str("sum"),
Token::Prod => f.write_str("prod"), Token::Prod => f.write_str("prod"),
Token::Iter => f.write_str("iter"), Token::Iter => f.write_str("iter"),
Token::If => f.write_str("if"),
Token::LParen => f.write_str("("), Token::LParen => f.write_str("("),
Token::RParen => f.write_str(")"), Token::RParen => f.write_str(")"),
Token::LBrace => f.write_str("{"), Token::LBrace => f.write_str("{"),
@ -36,6 +39,12 @@ impl<'i> fmt::Display for Token<'i> {
Token::Arrow => f.write_str("->"), Token::Arrow => f.write_str("->"),
Token::Equal => f.write_str("="), Token::Equal => f.write_str("="),
Token::Colon => f.write_str(":"), Token::Colon => f.write_str(":"),
Token::Greater => f.write_str(">"),
Token::Less => f.write_str("<"),
Token::GreaterEqual => f.write_str(">="),
Token::LessEqual => f.write_str("<="),
Token::EqualEqual => f.write_str("=="),
Token::BangEqual => f.write_str("!="),
Token::Newline => f.write_str("newline") Token::Newline => f.write_str("newline")
} }
} }
@ -44,12 +53,14 @@ impl<'i> fmt::Display for Token<'i> {
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub enum LexerError { pub enum LexerError {
Unexpected(usize, char), Unexpected(usize, char),
UnexpectedEof,
InvalidNumber(usize, usize), InvalidNumber(usize, usize),
} }
impl fmt::Display for LexerError { impl fmt::Display for LexerError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
LexerError::UnexpectedEof => write!(f, "Unexpected EOF during lexing"),
LexerError::Unexpected(i, c) => write!(f, "Unexpected character {c:?} at {i}"), LexerError::Unexpected(i, c) => write!(f, "Unexpected character {c:?} at {i}"),
LexerError::InvalidNumber(i, j) => write!(f, "Invalid number at {i}:{j}"), LexerError::InvalidNumber(i, j) => write!(f, "Invalid number at {i}:{j}"),
} }
@ -61,15 +72,15 @@ pub type Spanned<T, L, E> = Result<(L, T, L), E>;
pub struct Lexer<'i> { pub struct Lexer<'i> {
src: &'i str, src: &'i str,
chars: Peekable<CharIndices<'i>>, chars: Peekable<CharIndices<'i>>,
bracket_depth: usize, bracket_depth: isize,
} }
fn is_ident_begin(c: char) -> bool { fn is_ident_begin(c: char) -> bool {
c.is_alphabetic() c.is_xid_start()
} }
fn is_ident_middle(c: char) -> bool { fn is_ident_middle(c: char) -> bool {
c.is_alphanumeric() || c == '_' || c == '\'' c.is_xid_continue() || matches!(c, '\'' | '\u{2080}'..='\u{2089}')
} }
impl<'i> Lexer<'i> { impl<'i> Lexer<'i> {
@ -81,7 +92,7 @@ impl<'i> Lexer<'i> {
} }
} }
fn next_number(&mut self, i: usize, mut has_dot: bool) -> Spanned<Token<'i>, usize, LexerError> { fn next_number(&mut self, i: usize, has_dot: bool) -> Spanned<Token<'i>, usize, LexerError> {
let mut j = i; let mut j = i;
while self.chars.peek().is_some_and(|(_, c)| c.is_ascii_digit()) { while self.chars.peek().is_some_and(|(_, c)| c.is_ascii_digit()) {
@ -90,20 +101,14 @@ impl<'i> Lexer<'i> {
if !has_dot && matches!(self.chars.peek(), Some((_, '.'))) { if !has_dot && matches!(self.chars.peek(), Some((_, '.'))) {
j = self.chars.next().unwrap().0; j = self.chars.next().unwrap().0;
has_dot = true;
while self.chars.peek().is_some_and(|(_, c)| c.is_ascii_digit()) { while self.chars.peek().is_some_and(|(_, c)| c.is_ascii_digit()) {
j = self.chars.next().unwrap().0; j = self.chars.next().unwrap().0;
} }
} }
let s = &self.src[i..j+1]; let s = &self.src[i..j+1];
if !has_dot {
if let Ok(n) = s.parse::<i32>() {
return Ok((i, Token::Int(n), j+1))
}
}
match s.parse::<f64>() { match s.parse::<f64>() {
Ok(n) => Ok((i, Token::Float(n), j+1)), Ok(n) => Ok((i, Token::Number(n), j+1)),
Err(_) => Err(LexerError::InvalidNumber(i, j+1)), Err(_) => Err(LexerError::InvalidNumber(i, j+1)),
} }
} }
@ -118,6 +123,7 @@ impl<'i> Lexer<'i> {
"sum" => Ok((i, Token::Sum, j)), "sum" => Ok((i, Token::Sum, j)),
"prod" => Ok((i, Token::Prod, j)), "prod" => Ok((i, Token::Prod, j)),
"iter" => Ok((i, Token::Iter, j)), "iter" => Ok((i, Token::Iter, j)),
"if" => Ok((i, Token::If, j)),
_ => Ok((i, Token::Name(s), j)), _ => Ok((i, Token::Name(s), j)),
} }
} }
@ -141,25 +147,48 @@ impl<'i> Lexer<'i> {
} }
self.next_token()? self.next_token()?
} }
(i, '\n') => Ok((i, Token::Newline, i + 1)),
(i, '(') => { self.bracket_depth += 1; Ok((i, Token::LParen, i + 1)) }, (i, '(') => { self.bracket_depth += 1; Ok((i, Token::LParen, i + 1)) },
(i, ')') => { self.bracket_depth -= 1; Ok((i, Token::RParen, i + 1)) }, (i, ')') => { self.bracket_depth -= 1; Ok((i, Token::RParen, i + 1)) },
(i, '{') => { self.bracket_depth += 1; Ok((i, Token::LBrace, i + 1)) }, (i, '{') => { self.bracket_depth += 1; Ok((i, Token::LBrace, i + 1)) },
(i, '}') => { self.bracket_depth -= 1; Ok((i, Token::RBrace, i + 1)) }, (i, '}') => { self.bracket_depth -= 1; Ok((i, Token::RBrace, i + 1)) },
(i, '+') => Ok((i, Token::Plus, i + 1)), (i, '+') => Ok((i, Token::Plus, i + 1)),
(i, '-') => match self.chars.peek() { (i, '-') => match self.chars.next_if(|(_, c)| *c == '>') {
Some((_, '>')) => { Some(_) => Ok((i, Token::Arrow, i + 2)),
self.chars.next();
Ok((i, Token::Arrow, i + 2))
},
_ => Ok((i, Token::Minus, i + 1)), _ => Ok((i, Token::Minus, i + 1)),
} },
(i, '*') => Ok((i, Token::Star, i + 1)), (i, '*') => Ok((i, Token::Star, i + 1)),
(i, '\u{22C5}') => Ok((i, Token::Star, i + '\u{22C5}'.len_utf8())),
(i, '/') => Ok((i, Token::Slash, i + 1)), (i, '/') => Ok((i, Token::Slash, i + 1)),
(i, '^') => Ok((i, Token::Caret, i + 1)), (i, '^') => Ok((i, Token::Caret, i + 1)),
(i, '<') => match self.chars.next_if(|(_, c)| *c == '=') {
Some(_) => Ok((i, Token::LessEqual, i + 2)),
_ => Ok((i, Token::Less, i + 1)),
},
(i, '\u{2264}') => Ok((i, Token::LessEqual, i + '\u{2264}'.len_utf8())),
(i, '>') => match self.chars.next_if(|(_, c)| *c == '=') {
Some(_) => Ok((i, Token::GreaterEqual, i + 2)),
_ => Ok((i, Token::Greater, i + 1)),
},
(i, '\u{2265}') => Ok((i, Token::GreaterEqual, i + '\u{2265}'.len_utf8())),
(i, '=') => match self.chars.next_if(|(_, c)| *c == '=') {
Some(_) => Ok((i, Token::EqualEqual, i + 2)),
_ => Ok((i, Token::Equal, i + 1)),
}
(i, '!') => match self.chars.next() {
Some((_, '=')) => Ok((i, Token::BangEqual, i + 2)),
Some((_, c)) => Err(LexerError::Unexpected(i+1, c)),
None => Err(LexerError::UnexpectedEof),
}
(i, '\u{2260}') => Ok((i, Token::BangEqual, i + '\u{2260}'.len_utf8())),
(i, ',') => Ok((i, Token::Comma, i + 1)), (i, ',') => Ok((i, Token::Comma, i + 1)),
(i, '=') => Ok((i, Token::Equal, i + 1)),
(i, ':') => Ok((i, Token::Colon, i + 1)), (i, ':') => Ok((i, Token::Colon, i + 1)),
(i, '\n') => Ok((i, Token::Newline, i + 1)),
(i, '0'..='9') => self.next_number(i, false), (i, '0'..='9') => self.next_number(i, false),
(i, '.') => self.next_number(i, true), (i, '.') => self.next_number(i, true),
(i, c) if is_ident_begin(c) => self.next_word(i, i + c.len_utf8()), (i, c) if is_ident_begin(c) => self.next_word(i, i + c.len_utf8()),

View file

@ -15,6 +15,26 @@ struct Uniforms {
@group(0) @binding(1) var<uniform> uniforms: Uniforms; @group(0) @binding(1) var<uniform> uniforms: Uniforms;
/////////////////
// constants //
/////////////////
const TAU = 6.283185307179586;
const E = 2.718281828459045;
const RECIP_SQRT2 = 0.7071067811865475;
const LOG_TAU = 1.8378770664093453;
const LOG_2 = 0.6931471805599453;
const RECIP_SQRT29 = 0.18569533817705186;
const C_TAU = vec2f(TAU, 0.0);
const C_E = vec2f(E, 0.0);
const C_I = vec2f(0.0, 1.0);
const C_EMGAMMA = vec2f(0.5772156649015329, 0.0);
const C_PHI = vec2f(1.618033988749895, 0.0);
const C_ZERO = vec2f(0.0, 0.0);
const C_ONE = vec2f(1.0, 0.0);
/////////////// ///////////////
// utility // // utility //
/////////////// ///////////////
@ -43,23 +63,6 @@ fn vlength(v: vec2f) -> f32 {
return max(max(a, b), max(c, d)); return max(max(a, b), max(c, d));
} }
/////////////////
// constants //
/////////////////
const TAU = 6.283185307179586;
const E = 2.718281828459045;
const RECIP_SQRT2 = 0.7071067811865475;
const LOG_TAU = 1.8378770664093453;
const LOG_2 = 0.6931471805599453;
const RECIP_SQRT29 = 0.18569533817705186;
const C_TAU = vec2f(TAU, 0.0);
const C_E = vec2f(E, 0.0);
const C_I = vec2f(0.0, 1.0);
const C_EMGAMMA = vec2f(0.5772156649015329, 0.0);
const C_PHI = vec2f(1.618033988749895, 0.0);
///////////////////////// /////////////////////////
// complex functions // // complex functions //
///////////////////////// /////////////////////////
@ -80,32 +83,16 @@ fn c_signim(z: vec2f) -> vec2f {
return vec2(sign(z.y), 0.0); return vec2(sign(z.y), 0.0);
} }
fn c_ifgt(p: vec2f, q: vec2f, z: vec2f, w: vec2f) -> vec2f { fn c_absre(z: vec2f) -> vec2f {
return select(w, z, p.x > q.x); return vec2(abs(z.x), 0.0);
} }
fn c_iflt(p: vec2f, q: vec2f, z: vec2f, w: vec2f) -> vec2f { fn c_absim(z: vec2f) -> vec2f {
return select(w, z, p.x < q.x); return vec2(abs(z.y), 0.0);
} }
fn c_ifge(p: vec2f, q: vec2f, z: vec2f, w: vec2f) -> vec2f { fn c_isnan(z: vec2f) -> vec2f {
return select(w, z, p.x >= q.x); return select(C_ZERO, C_ONE, z.x != z.x || z.y != z.y);
}
fn c_ifle(p: vec2f, q: vec2f, z: vec2f, w: vec2f) -> vec2f {
return select(w, z, p.x <= q.x);
}
fn c_ifeq(p: vec2f, q: vec2f, z: vec2f, w: vec2f) -> vec2f {
return select(w, z, p.x == q.x);
}
fn c_ifne(p: vec2f, q: vec2f, z: vec2f, w: vec2f) -> vec2f {
return select(w, z, p.x != q.x);
}
fn c_ifnan(p: vec2f, z: vec2f, w: vec2f) -> vec2f {
return select(w, z, p.x != p.x && p.y != p.y);
} }
fn c_conj(z: vec2f) -> vec2f { fn c_conj(z: vec2f) -> vec2f {
@ -128,9 +115,12 @@ fn c_arg(z: vec2f) -> vec2f {
} }
fn c_argbr(z: vec2f, br: vec2f) -> vec2f { fn c_argbr(z: vec2f, br: vec2f) -> vec2f {
if z.x < 0.0 && z.y == 0.0 {
return vec2(TAU/2.0 + floor(br.x/TAU) * TAU, 0.0);
}
let r = vec2(cos(-br.x), sin(-br.x)); let r = vec2(cos(-br.x), sin(-br.x));
let zr = c_mul(z, r); let zr = c_mul(z, r);
return c_arg(zr) + vec2(br.x, 0.0); return vec2(br.x + atan2(zr.y, zr.x), 0.0);
} }
fn c_add(u: vec2f, v: vec2f) -> vec2f { fn c_add(u: vec2f, v: vec2f) -> vec2f {
@ -222,15 +212,18 @@ fn c_tanh(z: vec2f) -> vec2f {
} }
fn c_asin(z: vec2f) -> vec2f { fn c_asin(z: vec2f) -> vec2f {
let m = select(-1.0, 1.0, z.y < 0.0 || (z.y == 0.0 && z.x > 0.0));
let u = c_sqrt(vec2(1.0, 0.0) - c_mul(z, z)); let u = c_sqrt(vec2(1.0, 0.0) - c_mul(z, z));
let v = c_log(u + vec2(-z.y, z.x)); let v = c_log(u + m*vec2(-z.y, z.x));
return vec2(v.y, -v.x); return m*vec2(v.y, -v.x);
} }
// TODO fix
fn c_acos(z: vec2f) -> vec2f { fn c_acos(z: vec2f) -> vec2f {
let m = select(-1.0, 1.0, z.y < 0.0 || (z.y == 0.0 && z.x > 0.0));
let u = c_sqrt(vec2(1.0, 0.0) - c_mul(z, z)); let u = c_sqrt(vec2(1.0, 0.0) - c_mul(z, z));
let v = c_log(u + vec2(-z.y, z.x)); let v = c_log(u + m*vec2(-z.y, z.x));
return vec2(TAU*0.25 - v.y, v.x); return C_TAU/4.0 + m*vec2(-v.y, v.x);
} }
fn c_atan(z: vec2f) -> vec2f { fn c_atan(z: vec2f) -> vec2f {
@ -241,19 +234,19 @@ fn c_atan(z: vec2f) -> vec2f {
} }
fn c_asinh(z: vec2f) -> vec2f { fn c_asinh(z: vec2f) -> vec2f {
let m = select(-1.0, 1.0, z.x > 0.0 || (z.x == 0.0 && z.y > 0.0));
let u = c_sqrt(vec2(1.0, 0.0) + c_mul(z, z)); let u = c_sqrt(vec2(1.0, 0.0) + c_mul(z, z));
return c_log(u + z); return c_log(u + z*m) * m;
} }
fn c_acosh(z: vec2f) -> vec2f { fn c_acosh(z: vec2f) -> vec2f {
let u = c_sqrt(vec2(-1.0, 0.0) + c_mul(z, z)); let b = select(0.0, TAU, z.x < 0.0 || (z.x == 0.0 && z.y < 0.0));
let u = c_sqrtbr(vec2(-1.0, 0.0) + c_mul(z, z), vec2(b, 0.0));
return c_log(u + z); return c_log(u + z);
} }
fn c_atanh(z: vec2f) -> vec2f { fn c_atanh(z: vec2f) -> vec2f {
let u = vec2(1.0, 0.0) + z; return 0.5 * (c_log(C_ONE + z) - c_log(C_ONE - z));
let v = vec2(1.0, 0.0) - z;
return 0.5 * c_log(c_div(u, v));
} }
// log gamma // // log gamma //
@ -321,6 +314,80 @@ fn c_digamma_inner2(z: vec2f) -> vec2f {
return w - l; return w - l;
} }
// lambert w //
fn c_lambertw(z: vec2f) -> vec2f {
var w = c_lambertw_init(z, 0.0);
return c_lambertw_iter(z, w);
}
fn c_lambertwbr(z: vec2f, br: vec2f) -> vec2f {
// branch number
let br_n = br.x / TAU;
// if -TAU/2 < br < TAU/2 then use -1/e as the branch point,
// otherwise use 0
let branch_point = select(C_ZERO, vec2(-1.0/E, 0.0), abs(br.x) < TAU / 2.0);
let arg = c_arg(z - branch_point).x;
// if we're past the branch cut then take branch ceil(br_n),
// otherwise take branch floor(br_n)
let take_ceil = (br_n - floor(br_n) >= arg / TAU + 0.5);
var init_br = select(floor(br_n), ceil(br_n), take_ceil);
var w = c_lambertw_init(z, init_br);
// newton's method
return c_lambertw_iter(z, w);
}
fn c_lambertw_iter(z: vec2f, init: vec2f) -> vec2f {
var w = init;
for(var i = 0; i < 5; i++) {
w = c_div(c_mul(w, w) + c_mul(z, c_exp(-w)), w + C_ONE);
}
return w;
}
fn c_lambertw_init(z: vec2f, br: f32) -> vec2f {
let b = vec2(TAU * br, 0.0);
let oz = z + vec2(1.25, 0.0);
if br == 0.0 && dot(z, z) <= 50.0
|| br == 1.0 && z.y < 0.0 && dot(oz, oz) < 1.0
|| br == -1.0 && z.y > 0.0 && dot(oz, oz) < 1.0 {
// accurate near 0, near principle branch
let w = C_ONE + c_sqrtbr(C_ONE + E*z, b);
return c_div(c_mul(E*z, c_log(w)), w + E*z);
} else {
// accurate asymptotically
let logz = c_logbr(z, b);
return logz - c_log(logz);
}
}
fn c_erf(z: vec2f) -> vec2f {
if z.x >= 0 {
return c_erf_plus(z);
} else {
return -c_erf_plus(-z);
}
}
const ERF_P = 0.3275911;
const ERF_A1 = 0.2548295922;
const ERF_A2 = -0.2844967358;
const ERF_A3 = 1.4214137412;
const ERF_A4 = -1.4531520268;
const ERF_A5 = 1.0614054292;
fn c_erf_plus(z: vec2f) -> vec2f {
let t = c_recip(vec2(1.0, 0.0) + ERF_P * z);
let m = c_exp(-c_mul(z, z));
let r = c_mul(t, vec2f(ERF_A1, 0.0)
+ c_mul(t, vec2f(ERF_A2, 0.0)
+ c_mul(t, vec2f(ERF_A3, 0.0)
+ c_mul(t, vec2f(ERF_A4, 0.0) + t * ERF_A5))));
return vec2f(1.0, 0.0) - c_mul(m, r);
}
///////////////// /////////////////
// rendering // // rendering //
///////////////// /////////////////

View file

@ -1,6 +1,6 @@
use std::{num::NonZeroU64, io::Cursor}; use std::{num::NonZeroU64, io::Cursor};
use wgpu::util::DeviceExt; use wgpu::{util::DeviceExt, MemoryHints};
#[derive(Debug)] #[derive(Debug)]
#[repr(C)] #[repr(C)]
@ -37,9 +37,9 @@ impl Uniforms {
} }
} }
pub struct WgpuState { pub struct WgpuState<'a> {
pub uniforms: Uniforms, pub uniforms: Uniforms,
surface: wgpu::Surface, surface: wgpu::Surface<'a>,
device: wgpu::Device, device: wgpu::Device,
config: wgpu::SurfaceConfiguration, config: wgpu::SurfaceConfiguration,
render_pipeline: Option<wgpu::RenderPipeline>, render_pipeline: Option<wgpu::RenderPipeline>,
@ -49,12 +49,12 @@ pub struct WgpuState {
queue: wgpu::Queue queue: wgpu::Queue
} }
impl WgpuState { impl<'a> WgpuState<'a> {
pub async fn new<W>(window: &W, size: (u32, u32)) -> Self pub async fn new<W>(window: &'a W, size: (u32, u32)) -> Self
where W: raw_window_handle::HasRawWindowHandle + raw_window_handle::HasRawDisplayHandle { where W: raw_window_handle::HasWindowHandle + raw_window_handle::HasDisplayHandle + Sync {
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor::default()); let instance = wgpu::Instance::new(wgpu::InstanceDescriptor::default());
let surface = unsafe { instance.create_surface(&window) }.unwrap(); let surface = instance.create_surface(window).unwrap();
let adapter = instance.request_adapter(&wgpu::RequestAdapterOptions { let adapter = instance.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::default(), power_preference: wgpu::PowerPreference::default(),
@ -65,8 +65,12 @@ impl WgpuState {
let (device, queue) = adapter.request_device( let (device, queue) = adapter.request_device(
&wgpu::DeviceDescriptor { &wgpu::DeviceDescriptor {
label: None, label: None,
features: wgpu::Features::empty(), required_features: wgpu::Features::empty(),
limits: wgpu::Limits::downlevel_webgl2_defaults(), required_limits: wgpu::Limits {
max_texture_dimension_2d: 8192,
..wgpu::Limits::downlevel_webgl2_defaults()
},
memory_hints: MemoryHints::Performance,
}, },
None None
).await.map_err(|e| e.to_string()).unwrap(); ).await.map_err(|e| e.to_string()).unwrap();
@ -81,6 +85,7 @@ impl WgpuState {
present_mode: wgpu::PresentMode::Fifo, present_mode: wgpu::PresentMode::Fifo,
alpha_mode: wgpu::CompositeAlphaMode::Auto, alpha_mode: wgpu::CompositeAlphaMode::Auto,
view_formats: vec![format], view_formats: vec![format],
desired_maximum_frame_latency: 2,
}; };
surface.configure(&device, &config); surface.configure(&device, &config);
@ -168,12 +173,14 @@ impl WgpuState {
let vertex = wgpu::VertexState { let vertex = wgpu::VertexState {
module: &vertex_module, module: &vertex_module,
entry_point: "main", entry_point: "main",
compilation_options: Default::default(),
buffers: &[] buffers: &[]
}; };
let fragment = wgpu::FragmentState { let fragment = wgpu::FragmentState {
module: &fragment_module, module: &fragment_module,
entry_point: "main", entry_point: "main",
compilation_options: Default::default(),
targets: &[Some(wgpu::ColorTargetState { targets: &[Some(wgpu::ColorTargetState {
format: self.config.format, format: self.config.format,
blend: Some(wgpu::BlendState { blend: Some(wgpu::BlendState {
@ -201,6 +208,7 @@ impl WgpuState {
depth_stencil: None, depth_stencil: None,
multisample: wgpu::MultisampleState::default(), multisample: wgpu::MultisampleState::default(),
multiview: None, multiview: None,
cache: None,
}); });
self.render_pipeline = Some(render_pipeline); self.render_pipeline = Some(render_pipeline);
@ -216,7 +224,7 @@ impl WgpuState {
resolve_target: None, resolve_target: None,
ops: wgpu::Operations { ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color::BLACK), load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
store: true, store: wgpu::StoreOp::Store,
} }
}; };
@ -224,6 +232,8 @@ impl WgpuState {
label: None, label: None,
color_attachments: &[Some(color_attachment)], color_attachments: &[Some(color_attachment)],
depth_stencil_attachment: None, depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
}); });
if let Some(pipeline) = &self.render_pipeline { if let Some(pipeline) = &self.render_pipeline {
rpass.set_pipeline(pipeline); rpass.set_pipeline(pipeline);