Skip to content

Commit f7f3686

Browse files
committed
v2.1.0
* Update dependencies * Add rgb shader function * Add ability to include arbitrary code in shader * Some refactoring
1 parent 77a74d9 commit f7f3686

File tree

8 files changed

+217
-198
lines changed

8 files changed

+217
-198
lines changed

Cargo.lock

Lines changed: 65 additions & 141 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "fractal_viewer"
3-
version = "2.0.1"
3+
version = "2.1.0"
44
edition = "2021"
55
description = "Cross-platform GPU-accelerated viewer for the Mandelbrot set and related fractals"
66
repository = "https://github.com/arthomnix/fractal_viewer"
@@ -14,10 +14,8 @@ path = "src/main.rs"
1414
crate-type = ["cdylib", "rlib"]
1515

1616
[dependencies]
17-
eframe = { version = "0.27", default-features = false, features = [ "wgpu", "accesskit", "default_fonts", "wayland", "web_screen_reader" ] }
18-
egui-wgpu = "0.27"
19-
wgpu = { version = "0.19", features = [ "webgpu", "webgl" ] }
20-
naga = "0.19"
17+
eframe = { version = "0.28", default-features = false, features = [ "wgpu", "accesskit", "default_fonts", "wayland", "web_screen_reader" ] }
18+
egui-wgpu = "0.28"
2119
log = "0.4"
2220
env_logger = "0.11"
2321
bytemuck = { version = "1.16", features = [ "derive" ] }
@@ -35,4 +33,5 @@ console_error_panic_hook = "0.1"
3533
console_log = { version = "1.0", features = [ "color" ] }
3634
wasm-bindgen = "0.2"
3735
wasm-bindgen-futures = "0.4"
38-
web-sys = { version = "0.3", features = [ "Window", "Location", "Document", "Element" ] }
36+
web-sys = { version = "0.3", features = [ "Window", "Location", "Document", "Element" ] }
37+
naga = "0.20"

src/lib.rs

Lines changed: 31 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@ mod uniforms;
33
#[cfg(target_arch = "wasm32")]
44
mod web;
55

6-
use crate::settings::UserSettings;
6+
use egui_wgpu::wgpu as wgpu;
7+
#[cfg(not(target_arch = "wasm32"))]
8+
use egui_wgpu::wgpu::naga as naga;
9+
10+
use crate::settings::{CustomShaderData, UserSettings};
711
use crate::uniforms::{calculate_scale, Uniforms};
812
#[allow(unused_imports)] // eframe::egui::ViewportCommand used on native but not web
913
use eframe::egui::{
@@ -27,10 +31,8 @@ use wgpu::{
2731

2832
static SHADER: &str = include_str!("shader.wgsl");
2933

30-
fn validate_shader(equation: &str, colour: &str) -> Result<(), String> {
31-
let shader_src = SHADER
32-
.replace("REPLACE_FRACTAL_EQN", equation)
33-
.replace("REPLACE_COLOR", colour);
34+
fn validate_shader(options: &CustomShaderData) -> Result<(), String> {
35+
let shader_src = options.shader();
3436

3537
let module = naga::front::wgsl::Frontend::new()
3638
.parse(&shader_src)
@@ -78,7 +80,7 @@ impl FractalViewerApp {
7880
};
7981

8082
#[cfg(target_arch = "wasm32")]
81-
if let Err(e) = validate_shader(&settings.equation, &settings.colour) {
83+
if let Err(e) = validate_shader(&settings.shader_data) {
8284
import_error = Some(format!("Invalid equation or colour expression: {e}"));
8385
settings = UserSettings::default();
8486
}
@@ -120,12 +122,7 @@ impl FractalViewerApp {
120122

121123
let shader = device.create_shader_module(ShaderModuleDescriptor {
122124
label: Some("fv_shader"),
123-
source: ShaderSource::Wgsl(
124-
SHADER
125-
.replace("REPLACE_FRACTAL_EQN", &settings.equation)
126-
.replace("REPLACE_COLOR", &settings.colour)
127-
.into(),
128-
),
125+
source: ShaderSource::Wgsl(settings.shader_data.shader().into()),
129126
});
130127

131128
let pipeline_layout = device.create_pipeline_layout(&PipelineLayoutDescriptor {
@@ -140,11 +137,13 @@ impl FractalViewerApp {
140137
vertex: VertexState {
141138
module: &shader,
142139
entry_point: "vs_main",
140+
compilation_options: Default::default(),
143141
buffers: &[],
144142
},
145143
fragment: Some(FragmentState {
146144
module: &shader,
147145
entry_point: "fs_main",
146+
compilation_options: Default::default(),
148147
targets: &[Some(wgpu_render_state.target_format.into())],
149148
}),
150149
primitive: PrimitiveState::default(),
@@ -222,7 +221,7 @@ impl FractalViewerApp {
222221
uniforms,
223222
shader_recompilation_options: if self.recompile_shader {
224223
self.recompile_shader = false;
225-
Some((self.settings.equation.clone(), self.settings.colour.clone()))
224+
Some(self.settings.shader_data.clone())
226225
} else {
227226
None
228227
},
@@ -374,40 +373,46 @@ impl eframe::App for FractalViewerApp {
374373
.selected_text("Select default equation")
375374
.show_ui(ui, |ui| {
376375
if ui.selectable_value(
377-
&mut self.settings.equation,
376+
&mut self.settings.shader_data.equation,
378377
"csquare(z) + c".to_string(),
379378
"Mandelbrot set",
380379
).clicked() || ui.selectable_value(
381-
&mut self.settings.equation,
380+
&mut self.settings.shader_data.equation,
382381
"csquare(abs(z)) + c".to_string(),
383382
"Burning ship fractal",
384383
).clicked() || ui.selectable_value(
385-
&mut self.settings.equation,
384+
&mut self.settings.shader_data.equation,
386385
"cdiv(cmul(csquare(z), z), vec2<f32>(1.0, 0.0) + z * z) + c"
387386
.to_string(),
388387
"Feather fractal",
389388
).clicked() || ui.selectable_value(
390-
&mut self.settings.equation,
389+
&mut self.settings.shader_data.equation,
391390
"csquare(vec2<f32>(z.x, -z.y)) + c".to_string(),
392391
"Tricorn fractal",
393392
).clicked() {
394393
self.recompile_shader = true;
395394
}
396395
});
397396
ui.label("...Or edit it yourself!");
398-
if ui.add(TextEdit::singleline(&mut self.settings.equation).desired_width(ui.max_rect().width())).changed() {
397+
if ui.add(TextEdit::singleline(&mut self.settings.shader_data.equation).desired_width(ui.max_rect().width())).changed() {
399398
self.recompile_shader = true;
400399
};
401400
ui.label("Colour expression:");
402401
ui.horizontal(|ui| {
403-
if ui.text_edit_singleline(&mut self.settings.colour).changed() {
402+
if ui.text_edit_singleline(&mut self.settings.shader_data.colour).changed() {
404403
self.recompile_shader = true;
405404
};
406405
if ui.button("Reset").clicked() {
407-
self.settings.colour = "hsv_rgb(vec3(log(n + 1.0) / log(f32(uniforms.iterations) + 1.0), 0.8, 0.8))".to_string();
406+
self.settings.shader_data.colour = "hsv_rgb(vec3(log(n + 1.0) / log(f32(uniforms.iterations) + 1.0), 0.8, 0.8))".to_string();
408407
self.recompile_shader = true;
409408
}
410409
});
410+
411+
ui.label("Additional code to include in shader:");
412+
if ui.add(TextEdit::multiline(&mut self.settings.shader_data.additional).code_editor()).changed() {
413+
self.recompile_shader = true;
414+
};
415+
411416
ui.checkbox(&mut self.settings.internal_black, "Always colour inside of set black");
412417

413418
if let Some(e) = &self.shader_error {
@@ -464,7 +469,7 @@ impl eframe::App for FractalViewerApp {
464469

465470
// Validate custom expressions
466471
if self.recompile_shader {
467-
if let Err(e) = validate_shader(&self.settings.equation, &self.settings.colour) {
472+
if let Err(e) = validate_shader(&self.settings.shader_data) {
468473
self.shader_error = Some(e);
469474
self.recompile_shader = false;
470475
} else {
@@ -493,15 +498,10 @@ struct FvRenderer {
493498

494499
impl FvRenderer {
495500
fn prepare(&mut self, queue: &Queue, callback: &FvRenderCallback) {
496-
if let Some((equation, colour)) = &callback.shader_recompilation_options {
501+
if let Some(data) = &callback.shader_recompilation_options {
497502
let shader = self.device.create_shader_module(ShaderModuleDescriptor {
498503
label: Some("fv_shader"),
499-
source: ShaderSource::Wgsl(
500-
SHADER
501-
.replace("REPLACE_FRACTAL_EQN", &equation)
502-
.replace("REPLACE_COLOR", &colour)
503-
.into(),
504-
),
504+
source: ShaderSource::Wgsl(data.shader().into()),
505505
});
506506

507507
let pipeline_layout = self
@@ -520,11 +520,13 @@ impl FvRenderer {
520520
vertex: VertexState {
521521
module: &shader,
522522
entry_point: "vs_main",
523+
compilation_options: Default::default(),
523524
buffers: &[],
524525
},
525526
fragment: Some(FragmentState {
526527
module: &shader,
527528
entry_point: "fs_main",
529+
compilation_options: Default::default(),
528530
targets: &[Some(self.target_format.clone())],
529531
}),
530532
primitive: PrimitiveState::default(),
@@ -552,7 +554,7 @@ impl FvRenderer {
552554

553555
struct FvRenderCallback {
554556
uniforms: Uniforms,
555-
shader_recompilation_options: Option<(String, String)>,
557+
shader_recompilation_options: Option<CustomShaderData>,
556558
}
557559

558560
impl egui_wgpu::CallbackTrait for FvRenderCallback {

src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,6 @@ fn main() -> Result<(), eframe::Error> {
77
eframe::run_native(
88
"fractal_viewer",
99
options,
10-
Box::new(|cc| Box::new(FractalViewerApp::new(cc).unwrap())),
10+
Box::new(|cc| Ok(Box::new(FractalViewerApp::new(cc).unwrap()))),
1111
)
1212
}

src/settings/compat.rs

Lines changed: 76 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
pub(crate) mod v0_3 {
2-
use crate::settings::InvalidSettingsImportError;
2+
use crate::settings::{CustomShaderData, InvalidSettingsImportError};
33

44
use base64::engine::general_purpose;
55
use base64::Engine;
@@ -34,23 +34,21 @@ pub(crate) mod v0_3 {
3434
zoom: self.zoom,
3535
centre: self.centre,
3636
iterations: self.iterations,
37-
equation: self.equation,
38-
colour:
39-
"hsv_rgb(vec3(log(n + 1.0) / log(f32(uniforms.iterations) + 1.0), 0.8, 0.8))"
40-
.to_string(),
4137
julia_set: self.julia_set,
42-
smoothen: false,
43-
internal_black: true,
4438
initial_value: self.initial_value,
4539
escape_threshold: self.escape_threshold,
46-
initial_c: false,
40+
shader_data: CustomShaderData {
41+
equation: self.equation,
42+
..Default::default()
43+
},
44+
..Default::default()
4745
}
4846
}
4947
}
5048
}
5149

5250
pub(crate) mod v0_4 {
53-
use crate::settings::InvalidSettingsImportError;
51+
use crate::settings::{CustomShaderData, InvalidSettingsImportError};
5452

5553
use base64::engine::general_purpose;
5654
use base64::Engine;
@@ -89,21 +87,24 @@ pub(crate) mod v0_4 {
8987
zoom: self.zoom,
9088
centre: self.centre,
9189
iterations: self.iterations,
92-
equation: self.equation,
93-
colour: self.colour,
9490
julia_set: self.julia_set,
9591
smoothen: self.smoothen,
9692
internal_black: self.internal_black,
9793
initial_value: self.initial_value,
9894
escape_threshold: self.escape_threshold,
99-
initial_c: false,
95+
shader_data: CustomShaderData {
96+
equation: self.equation,
97+
colour: self.colour,
98+
..Default::default()
99+
},
100+
..Default::default()
100101
}
101102
}
102103
}
103104
}
104105

105106
pub(crate) mod v0_5 {
106-
use crate::settings::InvalidSettingsImportError;
107+
use crate::settings::{CustomShaderData, InvalidSettingsImportError};
107108

108109
use base64::engine::general_purpose;
109110
use base64::Engine;
@@ -143,15 +144,75 @@ pub(crate) mod v0_5 {
143144
zoom: self.zoom,
144145
centre: self.centre,
145146
iterations: self.iterations,
146-
equation: self.equation,
147-
colour: self.colour,
148147
julia_set: self.julia_set,
149148
smoothen: self.smoothen,
150149
internal_black: self.internal_black,
151150
initial_value: self.initial_value,
152151
escape_threshold: self.escape_threshold,
153152
initial_c: self.initial_c,
153+
shader_data: CustomShaderData {
154+
equation: self.equation,
155+
colour: self.colour,
156+
..Default::default()
157+
},
158+
..Default::default()
154159
}
155160
}
156161
}
157162
}
163+
164+
165+
pub(crate) mod v2_0 {
166+
use crate::settings::{CustomShaderData, InvalidSettingsImportError};
167+
168+
use base64::Engine;
169+
use base64::engine::general_purpose;
170+
171+
#[derive(Clone, serde::Serialize, serde::Deserialize)]
172+
pub(crate) struct UserSettings {
173+
zoom: f32,
174+
centre: [f32; 2],
175+
iterations: i32,
176+
equation: String,
177+
colour: String,
178+
julia_set: bool,
179+
smoothen: bool,
180+
internal_black: bool,
181+
initial_value: [f32; 2],
182+
escape_threshold: f32,
183+
initial_c: bool,
184+
}
185+
186+
impl UserSettings {
187+
pub(crate) fn import_string(string: &str) -> Result<Self, InvalidSettingsImportError> {
188+
let bytes = general_purpose::STANDARD
189+
.decode(string)
190+
.map_err(|_| InvalidSettingsImportError::InvalidBase64)?;
191+
let result = bincode::deserialize::<'_, Self>(bytes.as_slice())
192+
.map_err(|_| InvalidSettingsImportError::DeserialisationFailed)?;
193+
Ok(result)
194+
}
195+
}
196+
197+
impl Into<crate::settings::UserSettings> for UserSettings {
198+
fn into(self) -> crate::settings::UserSettings {
199+
crate::settings::UserSettings {
200+
zoom: self.zoom,
201+
centre: self.centre,
202+
iterations: self.iterations,
203+
julia_set: self.julia_set,
204+
smoothen: self.smoothen,
205+
internal_black: self.internal_black,
206+
initial_value: self.initial_value,
207+
escape_threshold: self.escape_threshold,
208+
initial_c: self.initial_c,
209+
shader_data: CustomShaderData {
210+
equation: self.equation,
211+
colour: self.colour,
212+
..Default::default()
213+
},
214+
..Default::default()
215+
}
216+
}
217+
}
218+
}

0 commit comments

Comments
 (0)