feat: bind GUI real time rendering behind a feature
This commit is contained in:
@@ -25,6 +25,7 @@ cargo test # reference-orbit math, share-link round-trip, WGSL
|
|||||||
cargo test --test shader_valid # just the WGSL parse/validate tests (naga, no GPU needed)
|
cargo test --test shader_valid # just the WGSL parse/validate tests (naga, no GPU needed)
|
||||||
cargo clippy
|
cargo clippy
|
||||||
cargo fmt # rustfmt.toml just pins edition = "2024"
|
cargo fmt # rustfmt.toml just pins edition = "2024"
|
||||||
|
cargo build --release --no-default-features # headless-only binary: no eframe/egui (the default `gui` feature)
|
||||||
```
|
```
|
||||||
|
|
||||||
Web build (WebGPU):
|
Web build (WebGPU):
|
||||||
|
|||||||
+9
-2
@@ -3,11 +3,18 @@ name = "mandelbrot"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["gui"]
|
||||||
|
# Windowed egui app. Without it only `--headless` rendering is built.
|
||||||
|
gui = ["dep:eframe", "dep:egui"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bytemuck = { version = "1.25.2", features = ["derive"] }
|
bytemuck = { version = "1.25.2", features = ["derive"] }
|
||||||
dashu-float = "0.6.0"
|
dashu-float = "0.6.0"
|
||||||
eframe = { version = "0.36.2", default-features = false, features = ["wgpu", "default_fonts", "x11", "wayland", "accesskit"] }
|
eframe = { version = "0.36.2", default-features = false, features = ["wgpu", "default_fonts", "x11", "wayland", "accesskit"], optional = true }
|
||||||
egui = "0.36.2"
|
egui = { version = "0.36.2", optional = true }
|
||||||
|
ecolor = { version = "0.36.2", features = ["bytemuck"] }
|
||||||
|
wgpu = "30.0.1"
|
||||||
glam = "0.33.8"
|
glam = "0.33.8"
|
||||||
log = "0.4.34"
|
log = "0.4.34"
|
||||||
png = "0.18.1"
|
png = "0.18.1"
|
||||||
|
|||||||
+24
-6
@@ -1,9 +1,9 @@
|
|||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
#[cfg(feature = "gui")]
|
||||||
use eframe::CreationContext;
|
use eframe::CreationContext;
|
||||||
|
#[cfg(feature = "gui")]
|
||||||
use eframe::egui_wgpu;
|
use eframe::egui_wgpu;
|
||||||
#[cfg(target_arch = "wasm32")]
|
|
||||||
use eframe::egui_wgpu::wgpu;
|
|
||||||
use glam::Vec4;
|
use glam::Vec4;
|
||||||
use glam::Vec4Swizzles;
|
use glam::Vec4Swizzles;
|
||||||
|
|
||||||
@@ -316,6 +316,7 @@ impl ConstOrbit {
|
|||||||
|
|
||||||
/// Checkbox + speed/radius sliders; (re)centers the orbit on `current`
|
/// Checkbox + speed/radius sliders; (re)centers the orbit on `current`
|
||||||
/// when switched on.
|
/// when switched on.
|
||||||
|
#[cfg(feature = "gui")]
|
||||||
fn ui(&mut self, ui: &mut egui::Ui, name: &str, current: (f64, f64)) {
|
fn ui(&mut self, ui: &mut egui::Ui, name: &str, current: (f64, f64)) {
|
||||||
if ui.checkbox(&mut self.on, format!("Morph {name}")).changed() && self.on {
|
if ui.checkbox(&mut self.on, format!("Morph {name}")).changed() && self.on {
|
||||||
self.enable(current);
|
self.enable(current);
|
||||||
@@ -371,6 +372,7 @@ impl AxisOsc {
|
|||||||
self.base + self.amplitude * self.phase.sin()
|
self.base + self.amplitude * self.phase.sin()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "gui")]
|
||||||
fn ui(&mut self, ui: &mut egui::Ui, name: &str, current: f64) {
|
fn ui(&mut self, ui: &mut egui::Ui, name: &str, current: f64) {
|
||||||
if ui
|
if ui
|
||||||
.checkbox(&mut self.on, format!("Animate {name}"))
|
.checkbox(&mut self.on, format!("Animate {name}"))
|
||||||
@@ -594,6 +596,7 @@ pub struct FractalApp {
|
|||||||
/// PNG export resolution multiplier over the on-screen size.
|
/// PNG export resolution multiplier over the on-screen size.
|
||||||
export_scale: f32,
|
export_scale: f32,
|
||||||
/// Last on-screen fractal size in physical pixels (for export sizing).
|
/// Last on-screen fractal size in physical pixels (for export sizing).
|
||||||
|
#[cfg(feature = "gui")]
|
||||||
last_size_px: egui::Vec2,
|
last_size_px: egui::Vec2,
|
||||||
/// egui time (seconds) of the most recent pan/zoom. While recent (within
|
/// egui time (seconds) of the most recent pan/zoom. While recent (within
|
||||||
/// `INTERACT_SETTLE`) the fractal renders downscaled for smooth interaction.
|
/// `INTERACT_SETTLE`) the fractal renders downscaled for smooth interaction.
|
||||||
@@ -646,6 +649,7 @@ fn parse_bits_for(s: &str, min_bits: usize) -> usize {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl FractalApp {
|
impl FractalApp {
|
||||||
|
#[cfg(feature = "gui")]
|
||||||
pub fn new(cc: &CreationContext<'_>) -> Self {
|
pub fn new(cc: &CreationContext<'_>) -> Self {
|
||||||
let render_state = cc
|
let render_state = cc
|
||||||
.wgpu_render_state
|
.wgpu_render_state
|
||||||
@@ -740,6 +744,7 @@ impl FractalApp {
|
|||||||
pending: false,
|
pending: false,
|
||||||
export_scale: 2.0,
|
export_scale: 2.0,
|
||||||
render_scale_3d: 2.0,
|
render_scale_3d: 2.0,
|
||||||
|
#[cfg(feature = "gui")]
|
||||||
last_size_px: egui::vec2(1280.0, 720.0),
|
last_size_px: egui::vec2(1280.0, 720.0),
|
||||||
last_interact_time: -1.0e9,
|
last_interact_time: -1.0e9,
|
||||||
export_requested: false,
|
export_requested: false,
|
||||||
@@ -1414,6 +1419,7 @@ impl FractalApp {
|
|||||||
/// 3D-mode zoom toward the screen point `off` (points from the widget
|
/// 3D-mode zoom toward the screen point `off` (points from the widget
|
||||||
/// center): unproject it through the camera onto the z = 0 fractal plane,
|
/// center): unproject it through the camera onto the z = 0 fractal plane,
|
||||||
/// then zoom the 2D view about the matching fractal-texture pixel.
|
/// then zoom the 2D view about the matching fractal-texture pixel.
|
||||||
|
#[cfg(feature = "gui")]
|
||||||
fn zoom_3d_at(&mut self, off: egui::Vec2, rect: egui::Rect, height_px: f64, factor: f64) {
|
fn zoom_3d_at(&mut self, off: egui::Vec2, rect: egui::Rect, height_px: f64, factor: f64) {
|
||||||
let ndc = (off / rect.size()) * 2.;
|
let ndc = (off / rect.size()) * 2.;
|
||||||
let camera_ndc_pos = self
|
let camera_ndc_pos = self
|
||||||
@@ -1515,6 +1521,7 @@ impl FractalApp {
|
|||||||
/// Render the current view to a PNG at `export_scale` × the on-screen size,
|
/// Render the current view to a PNG at `export_scale` × the on-screen size,
|
||||||
/// then save it (native: file in cwd; web: browser download). Runs off the
|
/// then save it (native: file in cwd; web: browser download). Runs off the
|
||||||
/// UI thread so a progress bar can animate; progress lands in `self.export`.
|
/// UI thread so a progress bar can animate; progress lands in `self.export`.
|
||||||
|
#[cfg(feature = "gui")]
|
||||||
fn do_export(&mut self, frame: &mut eframe::Frame) {
|
fn do_export(&mut self, frame: &mut eframe::Frame) {
|
||||||
if self.export.is_some() {
|
if self.export.is_some() {
|
||||||
return; // one export at a time
|
return; // one export at a time
|
||||||
@@ -1641,6 +1648,7 @@ impl FractalApp {
|
|||||||
|
|
||||||
/// Pick up a finished export (setting the status line) and keep repainting
|
/// Pick up a finished export (setting the status line) and keep repainting
|
||||||
/// while one is in flight so its progress bar animates.
|
/// while one is in flight so its progress bar animates.
|
||||||
|
#[cfg(feature = "gui")]
|
||||||
fn poll_export(&mut self, ctx: &egui::Context) {
|
fn poll_export(&mut self, ctx: &egui::Context) {
|
||||||
if let Some(shared) = &self.export {
|
if let Some(shared) = &self.export {
|
||||||
let done = shared.lock().unwrap().result.take();
|
let done = shared.lock().unwrap().result.take();
|
||||||
@@ -1661,6 +1669,7 @@ impl FractalApp {
|
|||||||
/// Floating top-left overlay with the panel toggle and fullscreen toggle.
|
/// Floating top-left overlay with the panel toggle and fullscreen toggle.
|
||||||
/// Always on top of the fractal, so both stay reachable when the controls
|
/// Always on top of the fractal, so both stay reachable when the controls
|
||||||
/// panel is collapsed (the common case on a phone).
|
/// panel is collapsed (the common case on a phone).
|
||||||
|
#[cfg(feature = "gui")]
|
||||||
fn overlay_buttons(&mut self, ui: &mut egui::Ui) {
|
fn overlay_buttons(&mut self, ui: &mut egui::Ui) {
|
||||||
egui::Area::new(egui::Id::new("overlay_buttons"))
|
egui::Area::new(egui::Id::new("overlay_buttons"))
|
||||||
.anchor(egui::Align2::LEFT_TOP, egui::vec2(8.0, 8.0))
|
.anchor(egui::Align2::LEFT_TOP, egui::vec2(8.0, 8.0))
|
||||||
@@ -1719,6 +1728,7 @@ impl FractalApp {
|
|||||||
/// "Fractal Info" window. Kept separate from `overlay_buttons` (top-left)
|
/// "Fractal Info" window. Kept separate from `overlay_buttons` (top-left)
|
||||||
/// so it stays out of the way of the panel toggle / fullscreen controls,
|
/// so it stays out of the way of the panel toggle / fullscreen controls,
|
||||||
/// but is still reachable even when the controls panel is collapsed.
|
/// but is still reachable even when the controls panel is collapsed.
|
||||||
|
#[cfg(feature = "gui")]
|
||||||
fn info_button(&mut self, ui: &mut egui::Ui) {
|
fn info_button(&mut self, ui: &mut egui::Ui) {
|
||||||
egui::Area::new(egui::Id::new("info_button"))
|
egui::Area::new(egui::Id::new("info_button"))
|
||||||
.anchor(egui::Align2::LEFT_BOTTOM, egui::vec2(8.0, -8.0))
|
.anchor(egui::Align2::LEFT_BOTTOM, egui::vec2(8.0, -8.0))
|
||||||
@@ -1740,6 +1750,7 @@ impl FractalApp {
|
|||||||
/// Window with details about what's currently on screen: formula, active
|
/// Window with details about what's currently on screen: formula, active
|
||||||
/// per-kind constants, zoom depth, iteration count. Reads live state, so
|
/// per-kind constants, zoom depth, iteration count. Reads live state, so
|
||||||
/// it stays correct as the user pans/zooms/switches kinds.
|
/// it stays correct as the user pans/zooms/switches kinds.
|
||||||
|
#[cfg(feature = "gui")]
|
||||||
fn info_window(&mut self, ctx: &egui::Context) {
|
fn info_window(&mut self, ctx: &egui::Context) {
|
||||||
let mut open = self.info_open;
|
let mut open = self.info_open;
|
||||||
egui::Window::new("Fractal Info")
|
egui::Window::new("Fractal Info")
|
||||||
@@ -1796,6 +1807,7 @@ impl FractalApp {
|
|||||||
|
|
||||||
/// Help window: what the app does, plus a reference for mouse/touch and
|
/// Help window: what the app does, plus a reference for mouse/touch and
|
||||||
/// keyboard controls.
|
/// keyboard controls.
|
||||||
|
#[cfg(feature = "gui")]
|
||||||
fn help_window(&mut self, ctx: &egui::Context) {
|
fn help_window(&mut self, ctx: &egui::Context) {
|
||||||
let mut open = self.help_open;
|
let mut open = self.help_open;
|
||||||
egui::Window::new("Help")
|
egui::Window::new("Help")
|
||||||
@@ -1886,7 +1898,7 @@ impl FractalApp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Push the desired fullscreen state to the platform.
|
/// Push the desired fullscreen state to the platform.
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(all(feature = "gui", not(target_arch = "wasm32")))]
|
||||||
fn apply_fullscreen(&mut self, ctx: &egui::Context) {
|
fn apply_fullscreen(&mut self, ctx: &egui::Context) {
|
||||||
ctx.send_viewport_cmd(egui::ViewportCommand::Fullscreen(self.fullscreen));
|
ctx.send_viewport_cmd(egui::ViewportCommand::Fullscreen(self.fullscreen));
|
||||||
}
|
}
|
||||||
@@ -1894,7 +1906,7 @@ impl FractalApp {
|
|||||||
/// Push the desired fullscreen state to the browser. `request_fullscreen`
|
/// Push the desired fullscreen state to the browser. `request_fullscreen`
|
||||||
/// must run inside a user gesture; the button click provides the transient
|
/// must run inside a user gesture; the button click provides the transient
|
||||||
/// activation that carries into this frame.
|
/// activation that carries into this frame.
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(all(feature = "gui", target_arch = "wasm32"))]
|
||||||
fn apply_fullscreen(&mut self, _ctx: &egui::Context) {
|
fn apply_fullscreen(&mut self, _ctx: &egui::Context) {
|
||||||
let Some(doc) = web_sys::window().and_then(|w| w.document()) else {
|
let Some(doc) = web_sys::window().and_then(|w| w.document()) else {
|
||||||
return;
|
return;
|
||||||
@@ -1910,14 +1922,14 @@ impl FractalApp {
|
|||||||
|
|
||||||
/// Refresh `self.fullscreen` from the real platform state, so the label is
|
/// Refresh `self.fullscreen` from the real platform state, so the label is
|
||||||
/// correct even when fullscreen is left by Esc/F11 or the browser UI.
|
/// correct even when fullscreen is left by Esc/F11 or the browser UI.
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(all(feature = "gui", not(target_arch = "wasm32")))]
|
||||||
fn sync_fullscreen(&mut self, ctx: &egui::Context) {
|
fn sync_fullscreen(&mut self, ctx: &egui::Context) {
|
||||||
if let Some(fs) = ctx.input(|i| i.viewport().fullscreen) {
|
if let Some(fs) = ctx.input(|i| i.viewport().fullscreen) {
|
||||||
self.fullscreen = fs;
|
self.fullscreen = fs;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(all(feature = "gui", target_arch = "wasm32"))]
|
||||||
fn sync_fullscreen(&mut self, _ctx: &egui::Context) {
|
fn sync_fullscreen(&mut self, _ctx: &egui::Context) {
|
||||||
if let Some(doc) = web_sys::window().and_then(|w| w.document()) {
|
if let Some(doc) = web_sys::window().and_then(|w| w.document()) {
|
||||||
self.fullscreen = doc.fullscreen_element().is_some();
|
self.fullscreen = doc.fullscreen_element().is_some();
|
||||||
@@ -1929,6 +1941,7 @@ impl FractalApp {
|
|||||||
/// divides once the window closes, so the readout is steady rather than
|
/// divides once the window closes, so the readout is steady rather than
|
||||||
/// jittering every frame. Only advances when egui repaints — i.e. while the
|
/// jittering every frame. Only advances when egui repaints — i.e. while the
|
||||||
/// app is doing work — so an idle app shows its last measured rate.
|
/// app is doing work — so an idle app shows its last measured rate.
|
||||||
|
#[cfg(feature = "gui")]
|
||||||
fn update_fps(&mut self, ui: &egui::Ui) {
|
fn update_fps(&mut self, ui: &egui::Ui) {
|
||||||
let now = ui.input(|i| i.time);
|
let now = ui.input(|i| i.time);
|
||||||
// Reset the window if time went backwards or hasn't started yet.
|
// Reset the window if time went backwards or hasn't started yet.
|
||||||
@@ -1948,6 +1961,7 @@ impl FractalApp {
|
|||||||
/// Advance any enabled animations by the frame's elapsed time, and request a
|
/// Advance any enabled animations by the frame's elapsed time, and request a
|
||||||
/// repaint while active. Animations render at full resolution/AA (they do not
|
/// repaint while active. Animations render at full resolution/AA (they do not
|
||||||
/// trigger the interaction low-res pass).
|
/// trigger the interaction low-res pass).
|
||||||
|
#[cfg(feature = "gui")]
|
||||||
fn tick_animations(&mut self, ui: &egui::Ui) {
|
fn tick_animations(&mut self, ui: &egui::Ui) {
|
||||||
// Julia c only matters in Julia mode; Phoenix p only for the Phoenix kind; Lambda λ only for Lambda kind.
|
// Julia c only matters in Julia mode; Phoenix p only for the Phoenix kind; Lambda λ only for Lambda kind.
|
||||||
let julia_on = self.anim.julia.on && self.mode == FractalMode::Julia;
|
let julia_on = self.anim.julia.on && self.mode == FractalMode::Julia;
|
||||||
@@ -2092,6 +2106,7 @@ impl FractalApp {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "gui")]
|
||||||
fn controls_ui(&mut self, ui: &mut egui::Ui) {
|
fn controls_ui(&mut self, ui: &mut egui::Ui) {
|
||||||
ui.heading("Fractal Explorer");
|
ui.heading("Fractal Explorer");
|
||||||
ui.separator();
|
ui.separator();
|
||||||
@@ -2574,6 +2589,7 @@ impl FractalApp {
|
|||||||
|
|
||||||
/// Controls for Buddhabrot mode: nested iteration caps (Nebulabrot R/G/B
|
/// Controls for Buddhabrot mode: nested iteration caps (Nebulabrot R/G/B
|
||||||
/// coloring), exposure, and the progressive-accumulation toggle.
|
/// coloring), exposure, and the progressive-accumulation toggle.
|
||||||
|
#[cfg(feature = "gui")]
|
||||||
fn buddhabrot_ui(&mut self, ui: &mut egui::Ui) {
|
fn buddhabrot_ui(&mut self, ui: &mut egui::Ui) {
|
||||||
ui.separator();
|
ui.separator();
|
||||||
ui.add(
|
ui.add(
|
||||||
@@ -2614,6 +2630,7 @@ impl FractalApp {
|
|||||||
ui.small("PNG export isn't available in Buddhabrot mode yet.");
|
ui.small("PNG export isn't available in Buddhabrot mode yet.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "gui")]
|
||||||
fn fractal_ui(&mut self, ui: &mut egui::Ui) {
|
fn fractal_ui(&mut self, ui: &mut egui::Ui) {
|
||||||
let size = ui.available_size();
|
let size = ui.available_size();
|
||||||
let (rect, response) = ui.allocate_exact_size(size, egui::Sense::click_and_drag());
|
let (rect, response) = ui.allocate_exact_size(size, egui::Sense::click_and_drag());
|
||||||
@@ -2939,6 +2956,7 @@ impl FractalApp {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "gui")]
|
||||||
impl eframe::App for FractalApp {
|
impl eframe::App for FractalApp {
|
||||||
fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) {
|
fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) {
|
||||||
self.poll_export(ui.ctx());
|
self.poll_export(ui.ctx());
|
||||||
|
|||||||
@@ -5,7 +5,8 @@
|
|||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use eframe::egui_wgpu::{self, wgpu};
|
#[cfg(feature = "gui")]
|
||||||
|
use eframe::egui_wgpu;
|
||||||
|
|
||||||
/// Random samples dispatched per accumulating frame. Chosen so a frame stays
|
/// Random samples dispatched per accumulating frame. Chosen so a frame stays
|
||||||
/// interactive on a modest GPU even when most samples run the full `b_cap`
|
/// interactive on a modest GPU even when most samples run the full `b_cap`
|
||||||
@@ -336,6 +337,7 @@ pub struct BuddhabrotCallback {
|
|||||||
pub size_px: [u32; 2],
|
pub size_px: [u32; 2],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "gui")]
|
||||||
impl egui_wgpu::CallbackTrait for BuddhabrotCallback {
|
impl egui_wgpu::CallbackTrait for BuddhabrotCallback {
|
||||||
fn prepare(
|
fn prepare(
|
||||||
&self,
|
&self,
|
||||||
|
|||||||
@@ -12,7 +12,8 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use eframe::egui_wgpu::{self, wgpu};
|
#[cfg(feature = "gui")]
|
||||||
|
use eframe::egui_wgpu;
|
||||||
use wgpu::util::DeviceExt as _;
|
use wgpu::util::DeviceExt as _;
|
||||||
|
|
||||||
use super::kind::FractalKind;
|
use super::kind::FractalKind;
|
||||||
@@ -1884,6 +1885,7 @@ pub struct FractalCallback {
|
|||||||
pub size_px: [u32; 2],
|
pub size_px: [u32; 2],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "gui")]
|
||||||
impl egui_wgpu::CallbackTrait for FractalCallback {
|
impl egui_wgpu::CallbackTrait for FractalCallback {
|
||||||
fn prepare(
|
fn prepare(
|
||||||
&self,
|
&self,
|
||||||
|
|||||||
@@ -9,8 +9,6 @@ use std::collections::HashMap;
|
|||||||
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
||||||
use std::sync::{Mutex, mpsc};
|
use std::sync::{Mutex, mpsc};
|
||||||
|
|
||||||
use eframe::egui_wgpu::wgpu;
|
|
||||||
|
|
||||||
use crate::app::{FractalApp, RefJob, parse_complex_pair, unix_timestamp};
|
use crate::app::{FractalApp, RefJob, parse_complex_pair, unix_timestamp};
|
||||||
use crate::cli::Cli;
|
use crate::cli::Cli;
|
||||||
use crate::fractal::{
|
use crate::fractal::{
|
||||||
|
|||||||
+4
-1
@@ -1,7 +1,9 @@
|
|||||||
use std::f32::consts::PI;
|
use std::f32::consts::PI;
|
||||||
|
|
||||||
use bytemuck::{Pod, Zeroable};
|
use bytemuck::{Pod, Zeroable};
|
||||||
use egui::{Color32, Ui};
|
use ecolor::Color32;
|
||||||
|
#[cfg(feature = "gui")]
|
||||||
|
use egui::Ui;
|
||||||
|
|
||||||
/// Maximum number of simultaneous lights.
|
/// Maximum number of simultaneous lights.
|
||||||
pub const MAX_LIGHT_COUNT: usize = 16;
|
pub const MAX_LIGHT_COUNT: usize = 16;
|
||||||
@@ -27,6 +29,7 @@ impl Default for Light {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Light {
|
impl Light {
|
||||||
|
#[cfg(feature = "gui")]
|
||||||
pub fn widget(&mut self, ui: &mut Ui) -> bool {
|
pub fn widget(&mut self, ui: &mut Ui) -> bool {
|
||||||
let formater = |v, _| format!("{}°", ((v * 180. / std::f64::consts::PI) as u32));
|
let formater = |v, _| format!("{}°", ((v * 180. / std::f64::consts::PI) as u32));
|
||||||
let parser = |s: &str| {
|
let parser = |s: &str| {
|
||||||
|
|||||||
+21
-1
@@ -1,6 +1,8 @@
|
|||||||
// Without these, rust fails to infer Send/Sync trait impls
|
// Without these, rust fails to infer Send/Sync trait impls
|
||||||
// Probably caused by the new trait solver
|
// Probably caused by the new trait solver
|
||||||
#![recursion_limit = "256"]
|
#![recursion_limit = "256"]
|
||||||
|
// Without the `gui` feature, UI-only state and helpers go unused.
|
||||||
|
#![cfg_attr(not(feature = "gui"), allow(dead_code, unused_imports))]
|
||||||
|
|
||||||
// Fractal Explorer — Rust + wgpu + egui + WGSL deep-zoom Mandelbrot.
|
// Fractal Explorer — Rust + wgpu + egui + WGSL deep-zoom Mandelbrot.
|
||||||
//
|
//
|
||||||
@@ -21,8 +23,17 @@ mod headless;
|
|||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
mod worker;
|
mod worker;
|
||||||
|
|
||||||
|
#[cfg(all(target_arch = "wasm32", not(feature = "gui")))]
|
||||||
|
compile_error!("the web build needs the `gui` feature");
|
||||||
|
|
||||||
|
#[cfg(feature = "gui")]
|
||||||
use app::FractalApp;
|
use app::FractalApp;
|
||||||
|
|
||||||
|
#[cfg(all(feature = "gui", not(target_arch = "wasm32")))]
|
||||||
|
type MainResult = eframe::Result;
|
||||||
|
#[cfg(all(not(feature = "gui"), not(target_arch = "wasm32")))]
|
||||||
|
type MainResult = Result<(), String>;
|
||||||
|
|
||||||
/// wgpu configuration for eframe. The fractal fragment shader reads the
|
/// wgpu configuration for eframe. The fractal fragment shader reads the
|
||||||
/// reference orbit from a **storage buffer**, so the device must allow storage
|
/// reference orbit from a **storage buffer**, so the device must allow storage
|
||||||
/// buffers in the fragment stage. eframe's default requests WebGL2-downlevel
|
/// buffers in the fragment stage. eframe's default requests WebGL2-downlevel
|
||||||
@@ -30,6 +41,7 @@ use app::FractalApp;
|
|||||||
/// * request the adapter's real limits (which include storage buffers), and
|
/// * request the adapter's real limits (which include storage buffers), and
|
||||||
/// * force the WebGPU backend on the web (WebGL2 can't do storage buffers at
|
/// * force the WebGPU backend on the web (WebGL2 can't do storage buffers at
|
||||||
/// all) — failing cleanly on browsers without WebGPU, per the design.
|
/// all) — failing cleanly on browsers without WebGPU, per the design.
|
||||||
|
#[cfg(feature = "gui")]
|
||||||
fn wgpu_options() -> eframe::egui_wgpu::WgpuConfiguration {
|
fn wgpu_options() -> eframe::egui_wgpu::WgpuConfiguration {
|
||||||
use eframe::egui_wgpu::{WgpuSetup, wgpu};
|
use eframe::egui_wgpu::{WgpuSetup, wgpu};
|
||||||
|
|
||||||
@@ -51,7 +63,7 @@ fn wgpu_options() -> eframe::egui_wgpu::WgpuConfiguration {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
fn main() -> eframe::Result {
|
fn main() -> MainResult {
|
||||||
use clap::Parser as _;
|
use clap::Parser as _;
|
||||||
|
|
||||||
env_logger::builder()
|
env_logger::builder()
|
||||||
@@ -70,6 +82,13 @@ fn main() -> eframe::Result {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "gui"))]
|
||||||
|
{
|
||||||
|
eprintln!("error: built without the \"gui\" feature; only --headless is supported");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "gui")]
|
||||||
let native_options = eframe::NativeOptions {
|
let native_options = eframe::NativeOptions {
|
||||||
renderer: eframe::Renderer::Wgpu,
|
renderer: eframe::Renderer::Wgpu,
|
||||||
wgpu_options: wgpu_options(),
|
wgpu_options: wgpu_options(),
|
||||||
@@ -80,6 +99,7 @@ fn main() -> eframe::Result {
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[cfg(feature = "gui")]
|
||||||
eframe::run_native(
|
eframe::run_native(
|
||||||
"Fractal Explorer",
|
"Fractal Explorer",
|
||||||
native_options,
|
native_options,
|
||||||
|
|||||||
Reference in New Issue
Block a user