diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index fe294ffd..579ada44 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -82,7 +82,7 @@ jobs: override: true - run: rustup target add wasm32-unknown-unknown - name: check - run: cargo check -p eframe --lib --no-default-features --features persistence --target wasm32-unknown-unknown + run: cargo check -p eframe --lib --no-default-features --features glow,persistence --target wasm32-unknown-unknown check_web_all_features: name: cargo check web --all-features diff --git a/Cargo.lock b/Cargo.lock index 10cc343c..c014a076 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -132,6 +132,15 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" +[[package]] +name = "ash" +version = "0.34.0+1.2.203" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0f780da53d0063880d45554306489f09dd8d1bda47688b4a57bc579119356df" +dependencies = [ + "libloading", +] + [[package]] name = "async-broadcast" version = "0.3.4" @@ -469,6 +478,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cfg_aliases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + [[package]] name = "cgl" version = "0.3.2" @@ -583,6 +598,16 @@ dependencies = [ "objc", ] +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + [[package]] name = "color_quant" version = "1.1.0" @@ -625,6 +650,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "copyless" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2df960f5d869b2dd8532793fde43eb5427cceb126c929747a26823ab0eeb536" + [[package]] name = "core-foundation" version = "0.7.0" @@ -865,6 +896,17 @@ dependencies = [ "eframe", ] +[[package]] +name = "d3d12" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2daefd788d1e96e0a9d66dee4b828b883509bc3ea9ce30665f04c3246372690c" +dependencies = [ + "bitflags", + "libloading", + "winapi", +] + [[package]] name = "dark-light" version = "0.2.2" @@ -1081,6 +1123,7 @@ dependencies = [ "dark-light", "directories-next", "egui", + "egui-wgpu", "egui-winit", "egui_glow", "glow", @@ -1110,6 +1153,18 @@ dependencies = [ "tracing", ] +[[package]] +name = "egui-wgpu" +version = "0.18.0" +dependencies = [ + "bytemuck", + "egui", + "pollster", + "tracing", + "wgpu", + "winit", +] + [[package]] name = "egui-winit" version = "0.18.0" @@ -1135,7 +1190,6 @@ dependencies = [ "egui", "egui_demo_lib", "egui_extras", - "egui_glow", "ehttp", "image", "poll-promise", @@ -1466,6 +1520,15 @@ dependencies = [ "slab", ] +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + [[package]] name = "gdk-pixbuf-sys" version = "0.15.10" @@ -1694,6 +1757,45 @@ dependencies = [ "system-deps", ] +[[package]] +name = "gpu-alloc" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fc59e5f710e310e76e6707f86c561dd646f69a8876da9131703b2f717de818d" +dependencies = [ + "bitflags", + "gpu-alloc-types", +] + +[[package]] +name = "gpu-alloc-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54804d0d6bc9d7f26db4eaec1ad10def69b599315f487d32c334a80d1efe67a5" +dependencies = [ + "bitflags", +] + +[[package]] +name = "gpu-descriptor" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a538f217be4d405ff4719a283ca68323cc2384003eca5baaa87501e821c81dda" +dependencies = [ + "bitflags", + "gpu-descriptor-types", + "hashbrown 0.11.2", +] + +[[package]] +name = "gpu-descriptor-types" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "363e3677e55ad168fef68cf9de3a4a310b53124c5e784c53a1d70e92d23f2126" +dependencies = [ + "bitflags", +] + [[package]] name = "gtk-sys" version = "0.15.3" @@ -1737,6 +1839,9 @@ name = "hashbrown" version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +dependencies = [ + "ahash 0.7.6", +] [[package]] name = "heck" @@ -1766,6 +1871,12 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hexf-parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" + [[package]] name = "humantime" version = "2.1.0" @@ -1815,6 +1926,12 @@ dependencies = [ "hashbrown 0.11.2", ] +[[package]] +name = "inplace_it" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90953f308a79fe6d62a4643e51f848fbfddcd05975a38e69fdf4ab86a7baf7ca" + [[package]] name = "instant" version = "0.1.12" @@ -1892,6 +2009,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "khronos-egl" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c2352bd1d0bceb871cb9d40f24360c8133c11d7486b68b5381c1dd1a32015e3" +dependencies = [ + "libc", + "libloading", +] + [[package]] name = "khronos_api" version = "3.1.0" @@ -2023,6 +2150,20 @@ dependencies = [ "autocfg", ] +[[package]] +name = "metal" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0514f491f4cc03632ab399ee01e2c1c1b12d3e1cf2d667c1ff5f87d6dcd2084" +dependencies = [ + "bitflags", + "block", + "core-graphics-types", + "foreign-types", + "log", + "objc", +] + [[package]] name = "minimal-lexical" version = "0.2.1" @@ -2067,6 +2208,24 @@ dependencies = [ "winapi", ] +[[package]] +name = "naga" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3012f2dbcc79e8e0b5825a4836a7106a75dd9b2fe42c528163be0f572538c705" +dependencies = [ + "bit-set", + "bitflags", + "codespan-reporting", + "hexf-parse", + "indexmap", + "log", + "num-traits", + "rustc-hash", + "spirv", + "thiserror", +] + [[package]] name = "ndk" version = "0.5.0" @@ -2566,6 +2725,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "pollster" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5da3b0203fd7ee5720aa0b5e790b591aa5d3f41c3ed2c34a3a393382198af2f7" + [[package]] name = "ppv-lite86" version = "0.2.16" @@ -2591,6 +2756,12 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "profiling" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9145ac0af1d93c638c98c40cf7d25665f427b2a44ad0a99b1dccf3e2f25bb987" + [[package]] name = "puffin" version = "0.13.1" @@ -2667,6 +2838,12 @@ dependencies = [ "getrandom", ] +[[package]] +name = "range-alloc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e935c45e09cc6dcf00d2f0b2d630a58f4095320223d47fc68918722f0538b6" + [[package]] name = "raw-window-handle" version = "0.4.3" @@ -2749,6 +2926,12 @@ version = "0.6.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" +[[package]] +name = "renderdoc-sys" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1382d1f0a252c4bf97dc20d979a2fdd05b024acd7c2ed0f7595d7817666a157" + [[package]] name = "resvg" version = "0.22.0" @@ -3153,6 +3336,16 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +[[package]] +name = "spirv" +version = "0.2.0+1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "246bfa38fe3db3f1dfc8ca5a2cdeb7348c78be2112740cc0ec8ef18b6d94f830" +dependencies = [ + "bitflags", + "num-traits", +] + [[package]] name = "static_assertions" version = "1.1.0" @@ -3506,7 +3699,7 @@ version = "1.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ee73e6e4924fe940354b8d4d98cad5231175d615cd855b758adc658c0aac6a0" dependencies = [ - "cfg-if 0.1.10", + "cfg-if 1.0.0", "static_assertions", ] @@ -3856,7 +4049,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc6a3cffdb686fbb24d9fb8f03a213803277ed2300f11026a3afe1f108dc021b" dependencies = [ "jni", - "ndk-glue 0.5.2", + "ndk-glue 0.6.2", "url", "web-sys", "widestring", @@ -3897,6 +4090,97 @@ dependencies = [ "cc", ] +[[package]] +name = "wgpu" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97cd781ff044d6d697b632a2e212032c2e957d1afaa21dbf58069cbb8f78567" +dependencies = [ + "arrayvec 0.7.2", + "js-sys", + "log", + "naga", + "parking_lot 0.11.2", + "raw-window-handle", + "smallvec", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "wgpu-core", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-core" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4688c000eb841ca55f7b35db659b78d6e1cd77d7caf8fb929f4e181f754047d" +dependencies = [ + "arrayvec 0.7.2", + "bitflags", + "cfg_aliases", + "codespan-reporting", + "copyless", + "fxhash", + "log", + "naga", + "parking_lot 0.11.2", + "profiling", + "raw-window-handle", + "smallvec", + "thiserror", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-hal" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d684ea6a34974a2fc19f1dfd183d11a62e22d75c4f187a574bb1224df8e056c2" +dependencies = [ + "arrayvec 0.7.2", + "ash", + "bit-set", + "bitflags", + "block", + "core-graphics-types", + "d3d12", + "foreign-types", + "fxhash", + "glow", + "gpu-alloc", + "gpu-descriptor", + "inplace_it", + "js-sys", + "khronos-egl", + "libloading", + "log", + "metal", + "naga", + "objc", + "parking_lot 0.11.2", + "profiling", + "range-alloc", + "raw-window-handle", + "renderdoc-sys", + "thiserror", + "wasm-bindgen", + "web-sys", + "wgpu-types", + "winapi", +] + +[[package]] +name = "wgpu-types" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "549533d9e1cdd4b4cda7718d33ff500fc4c34b5467b71d76b547ae0324f3b2a2" +dependencies = [ + "bitflags", +] + [[package]] name = "which" version = "4.2.5" diff --git a/Cargo.toml b/Cargo.toml index 25b3d8c4..fc26b062 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ members = [ "egui_extras", "egui_glium", "egui_glow", + "egui-wgpu", "egui-winit", "egui", "emath", diff --git a/README.md b/README.md index 361d4129..e78d4c76 100644 --- a/README.md +++ b/README.md @@ -165,6 +165,7 @@ These are the official egui integrations: * [`eframe`](https://github.com/emilk/egui/tree/master/eframe) for compiling the same app to web/wasm and desktop/native. Uses `egui_glow` and `egui-winit`. * [`egui_glium`](https://github.com/emilk/egui/tree/master/egui_glium) for compiling native apps with [Glium](https://github.com/glium/glium). * [`egui_glow`](https://github.com/emilk/egui/tree/master/egui_glow) for rendering egui with [glow](https://github.com/grovesNL/glow) on native and web, and for making native apps. +* [`egui-wgpu`](https://github.com/emilk/egui/tree/master/egui-wgpu) for [wgpu](https://crates.io/crates/wgpu) (WebGPU API). * [`egui-winit`](https://github.com/emilk/egui/tree/master/egui-winit) for integrating with [winit](https://github.com/rust-windowing/winit). ### 3rd party integrations @@ -174,7 +175,6 @@ These are the official egui integrations: * [`egui_glfw_gl`](https://github.com/cohaereo/egui_glfw_gl) for [GLFW](https://crates.io/crates/glfw). * [`egui_sdl2_gl`](https://crates.io/crates/egui_sdl2_gl) for [SDL2](https://crates.io/crates/sdl2). * [`egui_vulkano`](https://github.com/derivator/egui_vulkano) for [Vulkano](https://github.com/vulkano-rs/vulkano). -* [`egui_wgpu_backend`](https://crates.io/crates/egui_wgpu_backend) for [wgpu](https://crates.io/crates/wgpu) (WebGPU API). * [`egui_winit_vulkano`](https://github.com/hakolao/egui_winit_vulkano) for [Vulkano](https://github.com/vulkano-rs/vulkano). * [`egui-macroquad`](https://github.com/optozorax/egui-macroquad) for [macroquad](https://github.com/not-fl3/macroquad). * [`egui-miniquad`](https://github.com/not-fl3/egui-miniquad) for [Miniquad](https://github.com/not-fl3/miniquad). diff --git a/eframe/CHANGELOG.md b/eframe/CHANGELOG.md index c95e8998..fb049271 100644 --- a/eframe/CHANGELOG.md +++ b/eframe/CHANGELOG.md @@ -6,6 +6,9 @@ NOTE: [`egui-winit`](../egui-winit/CHANGELOG.md), [`egui_glium`](../egui_glium/C ## Unreleased * `egui_glow`: remove calls to `gl.get_error` in release builds to speed up rendering ([#1583](https://github.com/emilk/egui/pull/1583)). +* Add `wgpu` rendering backed ([#1564](https://github.com/emilk/egui/pull/1564)): + * Add features "wgpu" and "glow" + * Add `NativeOptions::renderer` to switch between the rendering backends ## 0.18.0 - 2022-04-30 diff --git a/eframe/Cargo.toml b/eframe/Cargo.toml index bde8b0e1..6fc63e0b 100644 --- a/eframe/Cargo.toml +++ b/eframe/Cargo.toml @@ -20,7 +20,7 @@ all-features = true [features] -default = ["default_fonts"] +default = ["default_fonts", "glow"] # detect dark mode system preference dark-light = ["dep:dark-light"] @@ -29,6 +29,8 @@ dark-light = ["dep:dark-light"] # If you plan on specifying your own fonts you may disable this feature. default_fonts = ["egui/default_fonts"] +glow = ["dep:glow", "egui_glow"] + # Enable saving app state to disk. persistence = [ "directories-next", @@ -49,19 +51,23 @@ screen_reader = [ "tts", ] +# Use WGPU as the backend instead of glow +wgpu = ["egui-wgpu"] + [dependencies] egui = { version = "0.18.0", path = "../egui", default-features = false, features = [ "bytemuck", "tracing", ] } -egui_glow = { version = "0.18.0", path = "../egui_glow", default-features = false } -glow = "0.11" tracing = "0.1" # optional: +egui_glow = { version = "0.18.0", path = "../egui_glow", optional = true, default-features = false } +egui-wgpu = { version = "0.18.0", path = "../egui-wgpu", optional = true, features = ["winit"] } +glow = { version = "0.11", optional = true } ron = { version = "0.7", optional = true } -serde = { version = "1", optional = true } +serde = { version = "1", optional = true, features = ["derive"] } # ------------------------------------------- # native: diff --git a/eframe/README.md b/eframe/README.md index 7554384c..1d66675a 100644 --- a/eframe/README.md +++ b/eframe/README.md @@ -27,6 +27,8 @@ sudo apt-get install libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev lib You need to either use `edition = "2021"`, or set `resolver = "2"` in the `[workspace`] section of your to-level `Cargo.toml`. See [this link](https://doc.rust-lang.org/edition-guide/rust-2021/default-cargo-resolver.html) for more info. +You can opt-in to the using [`egui_wgpu`](https://github.com/emilk/egui/tree/master/egui_wgpu) for rendering by enabling the `wgpu` feature and setting `NativeOptions::renderer` to `Renderer::Wgpu`. + ## Alternatives `eframe` is not the only way to write an app using `egui`! You can also try [`egui-miniquad`](https://github.com/not-fl3/egui-miniquad), [`bevy_egui`](https://github.com/mvlabat/bevy_egui), [`egui_sdl2_gl`](https://github.com/ArjunNair/egui_sdl2_gl), and others. diff --git a/eframe/src/epi.rs b/eframe/src/epi.rs index a7c3ece4..b2073230 100644 --- a/eframe/src/epi.rs +++ b/eframe/src/epi.rs @@ -27,7 +27,8 @@ pub struct CreationContext<'s> { /// The [`glow::Context`] allows you to initialize OpenGL resources (e.g. shaders) that /// you might want to use later from a [`egui::PaintCallback`]. - pub gl: std::rc::Rc, + #[cfg(feature = "glow")] + pub gl: Option>, } // ---------------------------------------------------------------------------- @@ -71,7 +72,17 @@ pub trait App { /// Called once on shutdown, after [`Self::save`]. /// /// If you need to abort an exit use [`Self::on_exit_event`]. - fn on_exit(&mut self, _gl: &glow::Context) {} + /// + /// To get a [`glow`] context you need to compile with the `glow` feature flag, + /// and run eframe with the glow backend. + #[cfg(feature = "glow")] + fn on_exit(&mut self, _gl: Option<&glow::Context>) {} + + /// Called once on shutdown, after [`Self::save`]. + /// + /// If you need to abort an exit use [`Self::on_exit_event`]. + #[cfg(not(feature = "glow"))] + fn on_exit(&mut self) {} // --------- // Settings: @@ -199,6 +210,9 @@ pub struct NativeOptions { /// /// `egui` doesn't need the stencil buffer, so the default value is 0. pub stencil_buffer: u8, + + /// What rendering backend to use. + pub renderer: Renderer, } impl Default for NativeOptions { @@ -219,10 +233,74 @@ impl Default for NativeOptions { multisampling: 0, depth_buffer: 0, stencil_buffer: 0, + renderer: Renderer::default(), } } } +// ---------------------------------------------------------------------------- + +/// What rendering backend to use. +/// +/// You need to enable the "glow" and "wgpu" features to have a choice. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] +pub enum Renderer { + /// Use [`egui_glow`] renderer for [`glow`](https://github.com/grovesNL/glow). + #[cfg(feature = "glow")] + Glow, + + /// Use [`egui_wgpu`] renderer for [`wgpu`](https://github.com/gfx-rs/wgpu). + #[cfg(feature = "wgpu")] + Wgpu, +} + +impl Default for Renderer { + fn default() -> Self { + #[cfg(feature = "glow")] + return Self::Glow; + + #[cfg(not(feature = "glow"))] + #[cfg(feature = "wgpu")] + return Self::Wgpu; + + #[cfg(not(feature = "glow"))] + #[cfg(not(feature = "wgpu"))] + compile_error!("eframe: you must enable at least one of the rendering backend features: 'glow' or 'wgpu'"); + } +} + +impl std::fmt::Display for Renderer { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + #[cfg(feature = "glow")] + Self::Glow => "glow".fmt(f), + + #[cfg(feature = "wgpu")] + Self::Wgpu => "wgpu".fmt(f), + } + } +} + +impl std::str::FromStr for Renderer { + type Err = String; + + fn from_str(name: &str) -> Result { + match name.to_lowercase().as_str() { + #[cfg(feature = "glow")] + "glow" => Ok(Self::Glow), + + #[cfg(feature = "wgpu")] + "wgpu" => Ok(Self::Wgpu), + + _ => Err(format!("eframe renderer {name:?} is not available. Make sure that the corresponding eframe feature is enabled.")) + } + } +} + +// ---------------------------------------------------------------------------- + /// Image data for an application icon. #[derive(Clone)] pub struct IconData { @@ -254,8 +332,9 @@ pub struct Frame { pub storage: Option>, /// A reference to the underlying [`glow`] (OpenGL) context. + #[cfg(feature = "glow")] #[doc(hidden)] - pub gl: std::rc::Rc, + pub gl: Option>, } impl Frame { @@ -288,8 +367,12 @@ impl Frame { /// /// Note that all egui painting is deferred to after the call to [`App::update`] /// ([`egui`] only collects [`egui::Shape`]s and then eframe paints them all in one go later on). - pub fn gl(&self) -> &std::rc::Rc { - &self.gl + /// + /// To get a [`glow`] context you need to compile with the `glow` feature flag, + /// and run eframe with the glow backend. + #[cfg(feature = "glow")] + pub fn gl(&self) -> Option<&std::rc::Rc> { + self.gl.as_ref() } /// Signal the app to stop/exit/quit the app (only works for native apps, not web apps). diff --git a/eframe/src/lib.rs b/eframe/src/lib.rs index 2a21f0aa..750093ba 100644 --- a/eframe/src/lib.rs +++ b/eframe/src/lib.rs @@ -56,7 +56,10 @@ #![allow(clippy::needless_doctest_main)] // Re-export all useful libraries: -pub use {egui, egui::emath, egui::epaint, glow}; +pub use {egui, egui::emath, egui::epaint}; + +#[cfg(feature = "glow")] +pub use {egui_glow, glow}; mod epi; @@ -142,7 +145,21 @@ mod native; #[cfg(not(target_arch = "wasm32"))] #[allow(clippy::needless_pass_by_value)] pub fn run_native(app_name: &str, native_options: NativeOptions, app_creator: AppCreator) -> ! { - native::run(app_name, &native_options, app_creator) + let renderer = native_options.renderer; + + match renderer { + #[cfg(feature = "glow")] + Renderer::Glow => { + tracing::debug!("Using the glow renderer"); + native::run::run_glow(app_name, &native_options, app_creator) + } + + #[cfg(feature = "wgpu")] + Renderer::Wgpu => { + tracing::debug!("Using the wgpu renderer"); + native::run::run_wgpu(app_name, &native_options, app_creator) + } + } } // --------------------------------------------------------------------------- diff --git a/eframe/src/native/epi_integration.rs b/eframe/src/native/epi_integration.rs index 3f9cd454..1b0ae8ec 100644 --- a/eframe/src/native/epi_integration.rs +++ b/eframe/src/native/epi_integration.rs @@ -28,6 +28,7 @@ pub fn window_builder( multisampling: _, // used in `fn create_display` depth_buffer: _, // used in `fn create_display` stencil_buffer: _, // used in `fn create_display` + renderer: _, // used in `fn run_native` } = native_options; let window_icon = icon_data.clone().and_then(load_icon); @@ -159,10 +160,10 @@ pub struct EpiIntegration { impl EpiIntegration { pub fn new( - gl: std::rc::Rc, max_texture_side: usize, window: &winit::window::Window, storage: Option>, + #[cfg(feature = "glow")] gl: Option>, ) -> Self { let egui_ctx = egui::Context::default(); @@ -179,6 +180,7 @@ impl EpiIntegration { }, output: Default::default(), storage, + #[cfg(feature = "glow")] gl, }; diff --git a/eframe/src/native/mod.rs b/eframe/src/native/mod.rs index bcb8ec2d..3718d60c 100644 --- a/eframe/src/native/mod.rs +++ b/eframe/src/native/mod.rs @@ -1,8 +1,6 @@ mod epi_integration; -mod run; +pub mod run; /// File storage which can be used by native backends. #[cfg(feature = "persistence")] pub mod file_storage; - -pub use run::run; diff --git a/eframe/src/native/run.rs b/eframe/src/native/run.rs index a2406d07..82b7cf47 100644 --- a/eframe/src/native/run.rs +++ b/eframe/src/native/run.rs @@ -4,6 +4,7 @@ use egui_winit::winit; struct RequestRepaintEvent; +#[cfg(feature = "glow")] #[allow(unsafe_code)] fn create_display( native_options: &NativeOptions, @@ -37,13 +38,18 @@ fn create_display( pub use epi::NativeOptions; /// Run an egui app -#[allow(unsafe_code)] -pub fn run(app_name: &str, native_options: &epi::NativeOptions, app_creator: epi::AppCreator) -> ! { +#[cfg(feature = "glow")] +pub fn run_glow( + app_name: &str, + native_options: &epi::NativeOptions, + app_creator: epi::AppCreator, +) -> ! { let storage = epi_integration::create_storage(app_name); let window_settings = epi_integration::load_window_settings(storage.as_deref()); + let event_loop = winit::event_loop::EventLoop::with_user_event(); + let window_builder = epi_integration::window_builder(native_options, &window_settings).with_title(app_name); - let event_loop = winit::event_loop::EventLoop::with_user_event(); let (gl_window, gl) = create_display(native_options, window_builder, &event_loop); let gl = std::rc::Rc::new(gl); @@ -51,10 +57,10 @@ pub fn run(app_name: &str, native_options: &epi::NativeOptions, app_creator: epi .unwrap_or_else(|error| panic!("some OpenGL error occurred {}\n", error)); let mut integration = epi_integration::EpiIntegration::new( - gl.clone(), painter.max_texture_side(), gl_window.window(), storage, + Some(gl.clone()), ); { @@ -68,7 +74,7 @@ pub fn run(app_name: &str, native_options: &epi::NativeOptions, app_creator: epi egui_ctx: integration.egui_ctx.clone(), integration_info: integration.frame.info(), storage: integration.frame.storage(), - gl: gl.clone(), + gl: Some(gl.clone()), }); if app.warm_up_enabled() { @@ -78,22 +84,14 @@ pub fn run(app_name: &str, native_options: &epi::NativeOptions, app_creator: epi let mut is_focused = true; event_loop.run(move |event, _, control_flow| { + let window = gl_window.window(); + let mut redraw = || { #[cfg(feature = "puffin")] puffin::GlobalProfiler::lock().new_frame(); - - if !is_focused { - // On Mac, a minimized Window uses up all CPU: https://github.com/emilk/egui/issues/325 - // We can't know if we are minimized: https://github.com/rust-windowing/winit/issues/208 - // But we know if we are focused (in foreground). When minimized, we are not focused. - // However, a user may want an egui with an animation in the background, - // so we still need to repaint quite fast. - crate::profile_scope!("bg_sleep"); - std::thread::sleep(std::time::Duration::from_millis(10)); - } - crate::profile_scope!("frame"); - let screen_size_in_pixels: [u32; 2] = gl_window.window().inner_size().into(); + + let screen_size_in_pixels: [u32; 2] = window.inner_size().into(); egui_glow::painter::clear( &gl, @@ -106,9 +104,9 @@ pub fn run(app_name: &str, native_options: &epi::NativeOptions, app_creator: epi needs_repaint, textures_delta, shapes, - } = integration.update(app.as_mut(), gl_window.window()); + } = integration.update(app.as_mut(), window); - integration.handle_platform_output(gl_window.window(), platform_output); + integration.handle_platform_output(window, platform_output); let clipped_primitives = { crate::profile_scope!("tessellate"); @@ -127,18 +125,26 @@ pub fn run(app_name: &str, native_options: &epi::NativeOptions, app_creator: epi gl_window.swap_buffers().unwrap(); } - { - *control_flow = if integration.should_quit() { - winit::event_loop::ControlFlow::Exit - } else if needs_repaint { - gl_window.window().request_redraw(); - winit::event_loop::ControlFlow::Poll - } else { - winit::event_loop::ControlFlow::Wait - }; - } + *control_flow = if integration.should_quit() { + winit::event_loop::ControlFlow::Exit + } else if needs_repaint { + window.request_redraw(); + winit::event_loop::ControlFlow::Poll + } else { + winit::event_loop::ControlFlow::Wait + }; - integration.maybe_autosave(app.as_mut(), gl_window.window()); + integration.maybe_autosave(app.as_mut(), window); + + if !is_focused { + // On Mac, a minimized Window uses up all CPU: https://github.com/emilk/egui/issues/325 + // We can't know if we are minimized: https://github.com/rust-windowing/winit/issues/208 + // But we know if we are focused (in foreground). When minimized, we are not focused. + // However, a user may want an egui with an animation in the background, + // so we still need to repaint quite fast. + crate::profile_scope!("bg_sleep"); + std::thread::sleep(std::time::Duration::from_millis(10)); + } }; match event { @@ -149,35 +155,194 @@ pub fn run(app_name: &str, native_options: &epi::NativeOptions, app_creator: epi winit::event::Event::RedrawRequested(_) if !cfg!(windows) => redraw(), winit::event::Event::WindowEvent { event, .. } => { - if let winit::event::WindowEvent::Focused(new_focused) = event { - is_focused = new_focused; - } - - if let winit::event::WindowEvent::Resized(physical_size) = &event { - gl_window.resize(*physical_size); - } else if let glutin::event::WindowEvent::ScaleFactorChanged { - new_inner_size, - .. - } = &event - { - gl_window.resize(**new_inner_size); + match &event { + winit::event::WindowEvent::Focused(new_focused) => { + is_focused = *new_focused; + } + winit::event::WindowEvent::Resized(physical_size) => { + // Resize with 0 width and height is used by winit to signal a minimize event on Windows. + // See: https://github.com/rust-windowing/winit/issues/208 + // This solves an issue where the app would panic when minimizing on Windows. + if physical_size.width > 0 && physical_size.height > 0 { + gl_window.resize(*physical_size); + } + } + winit::event::WindowEvent::ScaleFactorChanged { new_inner_size, .. } => { + gl_window.resize(**new_inner_size); + } + winit::event::WindowEvent::CloseRequested => { + *control_flow = winit::event_loop::ControlFlow::Exit; + } + _ => {} } integration.on_event(app.as_mut(), &event); if integration.should_quit() { *control_flow = winit::event_loop::ControlFlow::Exit; } - - gl_window.window().request_redraw(); // TODO: ask egui if the events warrants a repaint instead + window.request_redraw(); // TODO: ask egui if the events warrants a repaint instead } winit::event::Event::LoopDestroyed => { - integration.save(&mut *app, gl_window.window()); - app.on_exit(&gl); + integration.save(&mut *app, window); + app.on_exit(Some(&gl)); painter.destroy(); } - winit::event::Event::UserEvent(RequestRepaintEvent) => { - gl_window.window().request_redraw(); - } + winit::event::Event::UserEvent(RequestRepaintEvent) => window.request_redraw(), + _ => (), + } + }); +} + +// TODO: merge with with the clone above +/// Run an egui app +#[cfg(feature = "wgpu")] +pub fn run_wgpu( + app_name: &str, + native_options: &epi::NativeOptions, + app_creator: epi::AppCreator, +) -> ! { + let storage = epi_integration::create_storage(app_name); + let window_settings = epi_integration::load_window_settings(storage.as_deref()); + let event_loop = winit::event_loop::EventLoop::with_user_event(); + + let window = epi_integration::window_builder(native_options, &window_settings) + .with_title(app_name) + .build(&event_loop) + .unwrap(); + + // SAFETY: `window` must outlive `painter`. + #[allow(unsafe_code)] + let mut painter = unsafe { + egui_wgpu::winit::Painter::new(&window, native_options.multisampling.max(1) as _) + }; + + let mut integration = epi_integration::EpiIntegration::new( + painter.max_texture_side(), + &window, + storage, + #[cfg(feature = "glow")] + None, + ); + + { + let event_loop_proxy = egui::mutex::Mutex::new(event_loop.create_proxy()); + integration.egui_ctx.set_request_repaint_callback(move || { + event_loop_proxy.lock().send_event(RequestRepaintEvent).ok(); + }); + } + + let mut app = app_creator(&epi::CreationContext { + egui_ctx: integration.egui_ctx.clone(), + integration_info: integration.frame.info(), + storage: integration.frame.storage(), + #[cfg(feature = "glow")] + gl: None, + }); + + if app.warm_up_enabled() { + integration.warm_up(app.as_mut(), &window); + } + + let mut is_focused = true; + + event_loop.run(move |event, _, control_flow| { + let window = &window; + + let mut redraw = || { + #[cfg(feature = "puffin")] + puffin::GlobalProfiler::lock().new_frame(); + crate::profile_scope!("frame"); + + let egui::FullOutput { + platform_output, + needs_repaint, + textures_delta, + shapes, + } = integration.update(app.as_mut(), window); + + integration.handle_platform_output(window, platform_output); + + let clipped_primitives = { + crate::profile_scope!("tessellate"); + integration.egui_ctx.tessellate(shapes) + }; + + painter.paint_and_update_textures( + integration.egui_ctx.pixels_per_point(), + app.clear_color(&integration.egui_ctx.style().visuals), + &clipped_primitives, + &textures_delta, + ); + + *control_flow = if integration.should_quit() { + winit::event_loop::ControlFlow::Exit + } else if needs_repaint { + window.request_redraw(); + winit::event_loop::ControlFlow::Poll + } else { + winit::event_loop::ControlFlow::Wait + }; + + integration.maybe_autosave(app.as_mut(), window); + + if !is_focused { + // On Mac, a minimized Window uses up all CPU: https://github.com/emilk/egui/issues/325 + // We can't know if we are minimized: https://github.com/rust-windowing/winit/issues/208 + // But we know if we are focused (in foreground). When minimized, we are not focused. + // However, a user may want an egui with an animation in the background, + // so we still need to repaint quite fast. + crate::profile_scope!("bg_sleep"); + std::thread::sleep(std::time::Duration::from_millis(10)); + } + }; + + match event { + // Platform-dependent event handlers to workaround a winit bug + // See: https://github.com/rust-windowing/winit/issues/987 + // See: https://github.com/rust-windowing/winit/issues/1619 + winit::event::Event::RedrawEventsCleared if cfg!(windows) => redraw(), + winit::event::Event::RedrawRequested(_) if !cfg!(windows) => redraw(), + + winit::event::Event::WindowEvent { event, .. } => { + match &event { + winit::event::WindowEvent::Focused(new_focused) => { + is_focused = *new_focused; + } + winit::event::WindowEvent::Resized(physical_size) => { + // Resize with 0 width and height is used by winit to signal a minimize event on Windows. + // See: https://github.com/rust-windowing/winit/issues/208 + // This solves an issue where the app would panic when minimizing on Windows. + if physical_size.width > 0 && physical_size.height > 0 { + painter.on_window_resized(physical_size.width, physical_size.height); + } + } + winit::event::WindowEvent::ScaleFactorChanged { new_inner_size, .. } => { + painter.on_window_resized(new_inner_size.width, new_inner_size.height); + } + winit::event::WindowEvent::CloseRequested => { + *control_flow = winit::event_loop::ControlFlow::Exit; + } + _ => {} + }; + + integration.on_event(app.as_mut(), &event); + if integration.should_quit() { + *control_flow = winit::event_loop::ControlFlow::Exit; + } + window.request_redraw(); // TODO: ask egui if the events warrants a repaint instead + } + winit::event::Event::LoopDestroyed => { + integration.save(&mut *app, window); + + #[cfg(feature = "glow")] + app.on_exit(None); + + #[cfg(not(feature = "glow"))] + app.on_exit(); + + painter.destroy(); + } + winit::event::Event::UserEvent(RequestRepaintEvent) => window.request_redraw(), _ => (), } }); diff --git a/eframe/src/web/backend.rs b/eframe/src/web/backend.rs index 41608e8a..cf5eb652 100644 --- a/eframe/src/web/backend.rs +++ b/eframe/src/web/backend.rs @@ -167,14 +167,16 @@ impl AppRunner { egui_ctx: egui_ctx.clone(), integration_info: info.clone(), storage: Some(&storage), - gl: painter.painter.gl().clone(), + #[cfg(feature = "glow")] + gl: Some(painter.painter.gl().clone()), }); let frame = epi::Frame { info, output: Default::default(), storage: Some(Box::new(storage)), - gl: painter.gl().clone(), + #[cfg(feature = "glow")] + gl: Some(painter.gl().clone()), }; let needs_repaint: std::sync::Arc = Default::default(); diff --git a/egui-wgpu/Cargo.toml b/egui-wgpu/Cargo.toml new file mode 100644 index 00000000..ce913fa4 --- /dev/null +++ b/egui-wgpu/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "egui-wgpu" +version = "0.18.0" +description = "Bindings for using egui natively using the wgpu library" +authors = [ + "Nils Hasenbanck ", + "embotech ", + "Emil Ernerfeldt ", +] +edition = "2021" +rust-version = "1.60" +homepage = "https://github.com/emilk/egui/tree/master/egui-wgpu" +license = "MIT OR Apache-2.0" +readme = "README.md" +repository = "https://github.com/emilk/egui/tree/master/egui-wgpu" +categories = ["gui", "game-development"] +keywords = ["wgpu", "egui", "gui", "gamedev"] +include = ["../LICENSE-APACHE", "../LICENSE-MIT", "**/*.rs", "Cargo.toml"] + + +[features] +# Make it easy to create bindings for winit. +winit = ["dep:pollster", "dep:winit"] + + +[dependencies] +egui = { version = "0.18.1", path = "../egui", default-features = false, features = [ + "bytemuck", +] } + +bytemuck = "1.7" +tracing = "0.1" +wgpu = { version = "0.12", features = ["webgl"] } + +# Optional: +pollster = { version = "0.2", optional = true } +winit = { version = "0.26", optional = true } diff --git a/egui-wgpu/README.md b/egui-wgpu/README.md new file mode 100644 index 00000000..9aab3159 --- /dev/null +++ b/egui-wgpu/README.md @@ -0,0 +1,10 @@ +# egui-wgpu + +[![Latest version](https://img.shields.io/crates/v/egui-wgpu.svg)](https://crates.io/crates/egui-wgpu) +[![Documentation](https://docs.rs/egui-wgpu/badge.svg)](https://docs.rs/egui-wgpu) +![MIT](https://img.shields.io/badge/license-MIT-blue.svg) +![Apache](https://img.shields.io/badge/license-Apache-blue.svg) + +This crates provides bindings between [`egui`](https://github.com/emilk/egui) and [wgpu](https://crates.io/crates/wgpu). + +This was originally hosted at https://github.com/hasenbanck/egui_wgpu_backend diff --git a/egui-wgpu/src/egui.wgsl b/egui-wgpu/src/egui.wgsl new file mode 100644 index 00000000..a3b4f3b6 --- /dev/null +++ b/egui-wgpu/src/egui.wgsl @@ -0,0 +1,86 @@ +// Vertex shader bindings + +struct VertexOutput { + [[location(0)]] tex_coord: vec2; + [[location(1)]] color: vec4; + [[builtin(position)]] position: vec4; +}; + +struct Locals { + screen_size: vec2; +}; +[[group(0), binding(0)]] var r_locals: Locals; + +// 0-1 from 0-255 +fn linear_from_srgb(srgb: vec3) -> vec3 { + let cutoff = srgb < vec3(10.31475); + let lower = srgb / vec3(3294.6); + let higher = pow((srgb + vec3(14.025)) / vec3(269.025), vec3(2.4)); + return select(higher, lower, cutoff); +} + +[[stage(vertex)]] +fn vs_main( + [[location(0)]] a_pos: vec2, + [[location(1)]] a_tex_coord: vec2, + [[location(2)]] a_color: u32, +) -> VertexOutput { + var out: VertexOutput; + out.tex_coord = a_tex_coord; + + // [u8; 4] SRGB as u32 -> [r, g, b, a] + let color = vec4( + f32(a_color & 255u), + f32((a_color >> 8u) & 255u), + f32((a_color >> 16u) & 255u), + f32((a_color >> 24u) & 255u), + ); + out.color = vec4(linear_from_srgb(color.rgb), color.a / 255.0); + + out.position = vec4( + 2.0 * a_pos.x / r_locals.screen_size.x - 1.0, + 1.0 - 2.0 * a_pos.y / r_locals.screen_size.y, + 0.0, + 1.0, + ); + + return out; +} + +[[stage(vertex)]] +fn vs_conv_main( + [[location(0)]] a_pos: vec2, + [[location(1)]] a_tex_coord: vec2, + [[location(2)]] a_color: u32, +) -> VertexOutput { + var out: VertexOutput; + out.tex_coord = a_tex_coord; + + // [u8; 4] SRGB as u32 -> [r, g, b, a] + let color = vec4( + f32(a_color & 255u), + f32((a_color >> 8u) & 255u), + f32((a_color >> 16u) & 255u), + f32((a_color >> 24u) & 255u), + ); + out.color = vec4(color.rgba / 255.0); + + out.position = vec4( + 2.0 * a_pos.x / r_locals.screen_size.x - 1.0, + 1.0 - 2.0 * a_pos.y / r_locals.screen_size.y, + 0.0, + 1.0, + ); + + return out; +} + +// Fragment shader bindings + +[[group(1), binding(0)]] var r_tex_color: texture_2d; +[[group(1), binding(1)]] var r_tex_sampler: sampler; + +[[stage(fragment)]] +fn fs_main(in: VertexOutput) -> [[location(0)]] vec4 { + return in.color * textureSample(r_tex_color, r_tex_sampler, in.tex_coord); +} diff --git a/egui-wgpu/src/lib.rs b/egui-wgpu/src/lib.rs new file mode 100644 index 00000000..0fe21b84 --- /dev/null +++ b/egui-wgpu/src/lib.rs @@ -0,0 +1,12 @@ +//! This crates provides bindings between [`egui`](https://github.com/emilk/egui) and [wgpu](https://crates.io/crates/wgpu). + +#![allow(unsafe_code)] + +pub use wgpu; + +/// Low-level painting of [`egui`] on [`wgpu`]. +pub mod renderer; + +/// Module for painting [`egui`] with [`wgpu`] on [`winit`]. +#[cfg(feature = "winit")] +pub mod winit; diff --git a/egui-wgpu/src/renderer.rs b/egui-wgpu/src/renderer.rs new file mode 100644 index 00000000..78dc0d68 --- /dev/null +++ b/egui-wgpu/src/renderer.rs @@ -0,0 +1,544 @@ +#![allow(unsafe_code)] + +use std::{borrow::Cow, collections::HashMap, num::NonZeroU32}; + +use egui::epaint::Primitive; +use wgpu; +use wgpu::util::DeviceExt as _; + +/// Enum for selecting the right buffer type. +#[derive(Debug)] +enum BufferType { + Uniform, + Index, + Vertex, +} + +/// Information about the screen used for rendering. +pub struct ScreenDescriptor { + /// Size of the window in physical pixels. + pub size_in_pixels: [u32; 2], + + /// HiDPI scale factor (pixels per point). + pub pixels_per_point: f32, +} + +impl ScreenDescriptor { + /// size in "logical" points + fn screen_size_in_points(&self) -> [f32; 2] { + [ + self.size_in_pixels[0] as f32 / self.pixels_per_point, + self.size_in_pixels[1] as f32 / self.pixels_per_point, + ] + } +} + +/// Uniform buffer used when rendering. +#[derive(Clone, Copy, Debug, bytemuck::Pod, bytemuck::Zeroable)] +#[repr(C)] +struct UniformBuffer { + screen_size_in_points: [f32; 2], +} + +/// Wraps the buffers and includes additional information. +#[derive(Debug)] +struct SizedBuffer { + buffer: wgpu::Buffer, + /// number of bytes + size: usize, +} + +/// Render pass to render a egui based GUI. +pub struct RenderPass { + render_pipeline: wgpu::RenderPipeline, + index_buffers: Vec, + vertex_buffers: Vec, + uniform_buffer: SizedBuffer, + uniform_bind_group: wgpu::BindGroup, + texture_bind_group_layout: wgpu::BindGroupLayout, + textures: HashMap, +} + +impl RenderPass { + /// Creates a new render pass to render a egui UI. + /// + /// If the format passed is not a *Srgb format, the shader will automatically convert to `sRGB` colors in the shader. + pub fn new( + device: &wgpu::Device, + output_format: wgpu::TextureFormat, + msaa_samples: u32, + ) -> Self { + let shader = wgpu::ShaderModuleDescriptor { + label: Some("egui_shader"), + source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("egui.wgsl"))), + }; + let module = device.create_shader_module(&shader); + + let uniform_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("egui_uniform_buffer"), + contents: bytemuck::cast_slice(&[UniformBuffer { + screen_size_in_points: [0.0, 0.0], + }]), + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + }); + let uniform_buffer = SizedBuffer { + buffer: uniform_buffer, + size: std::mem::size_of::(), + }; + + let uniform_bind_group_layout = + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("egui_uniform_bind_group_layout"), + entries: &[wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::VERTEX, + ty: wgpu::BindingType::Buffer { + has_dynamic_offset: false, + min_binding_size: None, + ty: wgpu::BufferBindingType::Uniform, + }, + count: None, + }], + }); + + let uniform_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("egui_uniform_bind_group"), + layout: &uniform_bind_group_layout, + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding { + buffer: &uniform_buffer.buffer, + offset: 0, + size: None, + }), + }], + }); + + let texture_bind_group_layout = + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("egui_texture_bind_group_layout"), + entries: &[ + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + multisampled: false, + sample_type: wgpu::TextureSampleType::Float { filterable: true }, + view_dimension: wgpu::TextureViewDimension::D2, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), + count: None, + }, + ], + }); + + let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("egui_pipeline_layout"), + bind_group_layouts: &[&uniform_bind_group_layout, &texture_bind_group_layout], + push_constant_ranges: &[], + }); + + let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("egui_pipeline"), + layout: Some(&pipeline_layout), + vertex: wgpu::VertexState { + entry_point: if output_format.describe().srgb { + "vs_main" + } else { + "vs_conv_main" + }, + module: &module, + buffers: &[wgpu::VertexBufferLayout { + array_stride: 5 * 4, + step_mode: wgpu::VertexStepMode::Vertex, + // 0: vec2 position + // 1: vec2 texture coordinates + // 2: uint color + attributes: &wgpu::vertex_attr_array![0 => Float32x2, 1 => Float32x2, 2 => Uint32], + }], + }, + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + unclipped_depth: false, + conservative: false, + cull_mode: None, + front_face: wgpu::FrontFace::default(), + polygon_mode: wgpu::PolygonMode::default(), + strip_index_format: None, + }, + depth_stencil: None, + multisample: wgpu::MultisampleState { + alpha_to_coverage_enabled: false, + count: msaa_samples, + mask: !0, + }, + + fragment: Some(wgpu::FragmentState { + module: &module, + entry_point: "fs_main", + targets: &[wgpu::ColorTargetState { + format: output_format, + blend: Some(wgpu::BlendState { + color: wgpu::BlendComponent { + src_factor: wgpu::BlendFactor::One, + dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, + operation: wgpu::BlendOperation::Add, + }, + alpha: wgpu::BlendComponent { + src_factor: wgpu::BlendFactor::OneMinusDstAlpha, + dst_factor: wgpu::BlendFactor::One, + operation: wgpu::BlendOperation::Add, + }, + }), + write_mask: wgpu::ColorWrites::ALL, + }], + }), + multiview: None, + }); + + Self { + render_pipeline, + vertex_buffers: Vec::with_capacity(64), + index_buffers: Vec::with_capacity(64), + uniform_buffer, + uniform_bind_group, + texture_bind_group_layout, + textures: HashMap::new(), + } + } + + /// Executes the egui render pass. + pub fn execute( + &self, + encoder: &mut wgpu::CommandEncoder, + color_attachment: &wgpu::TextureView, + paint_jobs: &[egui::epaint::ClippedPrimitive], + screen_descriptor: &ScreenDescriptor, + clear_color: Option, + ) { + let load_operation = if let Some(color) = clear_color { + wgpu::LoadOp::Clear(color) + } else { + wgpu::LoadOp::Load + }; + + let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + color_attachments: &[wgpu::RenderPassColorAttachment { + view: color_attachment, + resolve_target: None, + ops: wgpu::Operations { + load: load_operation, + store: true, + }, + }], + depth_stencil_attachment: None, + label: Some("egui main render pass"), + }); + rpass.push_debug_group("egui_pass"); + + self.execute_with_renderpass(&mut rpass, paint_jobs, screen_descriptor); + + rpass.pop_debug_group(); + } + + /// Executes the egui render pass onto an existing wgpu renderpass. + pub fn execute_with_renderpass<'rpass>( + &'rpass self, + rpass: &mut wgpu::RenderPass<'rpass>, + paint_jobs: &[egui::epaint::ClippedPrimitive], + screen_descriptor: &ScreenDescriptor, + ) { + rpass.set_pipeline(&self.render_pipeline); + + rpass.set_bind_group(0, &self.uniform_bind_group, &[]); + + let pixels_per_point = screen_descriptor.pixels_per_point; + let size_in_pixels = screen_descriptor.size_in_pixels; + + for ( + ( + egui::ClippedPrimitive { + clip_rect, + primitive, + }, + vertex_buffer, + ), + index_buffer, + ) in paint_jobs + .iter() + .zip(&self.vertex_buffers) + .zip(&self.index_buffers) + { + // Transform clip rect to physical pixels. + let clip_min_x = pixels_per_point * clip_rect.min.x; + let clip_min_y = pixels_per_point * clip_rect.min.y; + let clip_max_x = pixels_per_point * clip_rect.max.x; + let clip_max_y = pixels_per_point * clip_rect.max.y; + + // Make sure clip rect can fit within an `u32`. + let clip_min_x = clip_min_x.clamp(0.0, size_in_pixels[0] as f32); + let clip_min_y = clip_min_y.clamp(0.0, size_in_pixels[1] as f32); + let clip_max_x = clip_max_x.clamp(clip_min_x, size_in_pixels[0] as f32); + let clip_max_y = clip_max_y.clamp(clip_min_y, size_in_pixels[1] as f32); + + let clip_min_x = clip_min_x.round() as u32; + let clip_min_y = clip_min_y.round() as u32; + let clip_max_x = clip_max_x.round() as u32; + let clip_max_y = clip_max_y.round() as u32; + + let width = (clip_max_x - clip_min_x).max(1); + let height = (clip_max_y - clip_min_y).max(1); + + { + // Clip scissor rectangle to target size. + let x = clip_min_x.min(size_in_pixels[0]); + let y = clip_min_y.min(size_in_pixels[1]); + let width = width.min(size_in_pixels[0] - x); + let height = height.min(size_in_pixels[1] - y); + + // Skip rendering with zero-sized clip areas. + if width == 0 || height == 0 { + continue; + } + + rpass.set_scissor_rect(x, y, width, height); + } + + match primitive { + Primitive::Mesh(mesh) => { + if let Some((_texture, bind_group)) = self.textures.get(&mesh.texture_id) { + rpass.set_bind_group(1, bind_group, &[]); + rpass.set_index_buffer( + index_buffer.buffer.slice(..), + wgpu::IndexFormat::Uint32, + ); + rpass.set_vertex_buffer(0, vertex_buffer.buffer.slice(..)); + rpass.draw_indexed(0..mesh.indices.len() as u32, 0, 0..1); + } else { + tracing::warn!("Missing texture: {:?}", mesh.texture_id); + } + } + Primitive::Callback(_) => { + // already warned about earlier + } + } + } + } + + /// Should be called before `execute()`. + pub fn update_texture( + &mut self, + device: &wgpu::Device, + queue: &wgpu::Queue, + id: egui::TextureId, + image_delta: &egui::epaint::ImageDelta, + ) { + let width = image_delta.image.width() as u32; + let height = image_delta.image.height() as u32; + + let size = wgpu::Extent3d { + width, + height, + depth_or_array_layers: 1, + }; + + let data_color32 = match &image_delta.image { + egui::ImageData::Color(image) => { + assert_eq!( + width as usize * height as usize, + image.pixels.len(), + "Mismatch between texture size and texel count" + ); + Cow::Borrowed(&image.pixels) + } + egui::ImageData::Font(image) => { + assert_eq!( + width as usize * height as usize, + image.pixels.len(), + "Mismatch between texture size and texel count" + ); + Cow::Owned(image.srgba_pixels(1.0).collect::>()) + } + }; + let data_bytes: &[u8] = bytemuck::cast_slice(data_color32.as_slice()); + + let queue_write_data_to_texture = |texture, origin| { + queue.write_texture( + wgpu::ImageCopyTexture { + texture, + mip_level: 0, + origin, + aspect: wgpu::TextureAspect::All, + }, + data_bytes, + wgpu::ImageDataLayout { + offset: 0, + bytes_per_row: NonZeroU32::new(4 * width), + rows_per_image: NonZeroU32::new(height), + }, + size, + ); + }; + + if let Some(pos) = image_delta.pos { + // update the existing texture + let (texture, _bind_group) = self + .textures + .get(&id) + .expect("Tried to update a texture that has not been allocated yet."); + let origin = wgpu::Origin3d { + x: pos[0] as u32, + y: pos[1] as u32, + z: 0, + }; + queue_write_data_to_texture(texture, origin); + } else { + // allocate a new texture + let texture = device.create_texture(&wgpu::TextureDescriptor { + label: None, + size, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Rgba8UnormSrgb, + usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, + }); + let sampler = device.create_sampler(&wgpu::SamplerDescriptor { + label: None, + mag_filter: wgpu::FilterMode::Linear, + min_filter: wgpu::FilterMode::Linear, + ..Default::default() + }); + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + label: None, + layout: &self.texture_bind_group_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView( + &texture.create_view(&wgpu::TextureViewDescriptor::default()), + ), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Sampler(&sampler), + }, + ], + }); + let origin = wgpu::Origin3d::ZERO; + queue_write_data_to_texture(&texture, origin); + self.textures.insert(id, (texture, bind_group)); + }; + } + + /// Should be called before `execute()`. + pub fn free_texture(&mut self, id: &egui::TextureId) { + self.textures.remove(id); + } + + /// Uploads the uniform, vertex and index data used by the render pass. + /// Should be called before `execute()`. + pub fn update_buffers( + &mut self, + device: &wgpu::Device, + queue: &wgpu::Queue, + paint_jobs: &[egui::epaint::ClippedPrimitive], + screen_descriptor: &ScreenDescriptor, + ) { + let screen_size_in_points = screen_descriptor.screen_size_in_points(); + + self.update_buffer( + device, + queue, + &BufferType::Uniform, + 0, + bytemuck::cast_slice(&[UniformBuffer { + screen_size_in_points, + }]), + ); + + for (i, egui::ClippedPrimitive { primitive, .. }) in paint_jobs.iter().enumerate() { + match primitive { + Primitive::Mesh(mesh) => { + let data: &[u8] = bytemuck::cast_slice(&mesh.indices); + if i < self.index_buffers.len() { + self.update_buffer(device, queue, &BufferType::Index, i, data); + } else { + let buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("egui_index_buffer"), + contents: data, + usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST, + }); + self.index_buffers.push(SizedBuffer { + buffer, + size: data.len(), + }); + } + + let data: &[u8] = bytemuck::cast_slice(&mesh.vertices); + if i < self.vertex_buffers.len() { + self.update_buffer(device, queue, &BufferType::Vertex, i, data); + } else { + let buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("egui_vertex_buffer"), + contents: data, + usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, + }); + + self.vertex_buffers.push(SizedBuffer { + buffer, + size: data.len(), + }); + } + } + Primitive::Callback(_) => { + tracing::warn!("Painting callbacks not supported by egui-wgpu (yet)"); + } + } + } + } + + /// Updates the buffers used by egui. Will properly re-size the buffers if needed. + fn update_buffer( + &mut self, + device: &wgpu::Device, + queue: &wgpu::Queue, + buffer_type: &BufferType, + index: usize, + data: &[u8], + ) { + let (buffer, storage, label) = match buffer_type { + BufferType::Index => ( + &mut self.index_buffers[index], + wgpu::BufferUsages::INDEX, + "egui_index_buffer", + ), + BufferType::Vertex => ( + &mut self.vertex_buffers[index], + wgpu::BufferUsages::VERTEX, + "egui_vertex_buffer", + ), + BufferType::Uniform => ( + &mut self.uniform_buffer, + wgpu::BufferUsages::UNIFORM, + "egui_uniform_buffer", + ), + }; + + if data.len() > buffer.size { + buffer.size = data.len(); + buffer.buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some(label), + contents: bytemuck::cast_slice(data), + usage: storage | wgpu::BufferUsages::COPY_DST, + }); + } else { + queue.write_buffer(&buffer.buffer, 0, data); + } + } +} diff --git a/egui-wgpu/src/winit.rs b/egui-wgpu/src/winit.rs new file mode 100644 index 00000000..dff84250 --- /dev/null +++ b/egui-wgpu/src/winit.rs @@ -0,0 +1,148 @@ +use crate::renderer; + +/// Everything you need to paint egui with [`wgpu`] on [`winit`]. +/// +/// Alternatively you can use [`crate::renderer`] directly. +pub struct Painter { + device: wgpu::Device, + queue: wgpu::Queue, + surface_config: wgpu::SurfaceConfiguration, + surface: wgpu::Surface, + egui_rpass: renderer::RenderPass, +} + +impl Painter { + /// Creates a [`wgpu`] surface for the given window, and things required to render egui onto it. + /// + /// # Safety + /// The given `window` must outlive the returned [`Painter`]. + pub unsafe fn new(window: &winit::window::Window, msaa_samples: u32) -> Self { + let instance = wgpu::Instance::new(wgpu::Backends::PRIMARY | wgpu::Backends::GL); + let surface = instance.create_surface(&window); + + let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions { + power_preference: wgpu::PowerPreference::HighPerformance, + compatible_surface: Some(&surface), + force_fallback_adapter: false, + })) + .unwrap(); + + let (device, queue) = pollster::block_on(adapter.request_device( + &wgpu::DeviceDescriptor { + features: wgpu::Features::default(), + limits: wgpu::Limits::default(), + label: None, + }, + None, + )) + .unwrap(); + + let size = window.inner_size(); + let surface_format = surface.get_preferred_format(&adapter).unwrap(); + let surface_config = wgpu::SurfaceConfiguration { + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + format: surface_format, + width: size.width as u32, + height: size.height as u32, + present_mode: wgpu::PresentMode::Fifo, // TODO: make vsync configurable + }; + surface.configure(&device, &surface_config); + + let egui_rpass = renderer::RenderPass::new(&device, surface_format, msaa_samples); + + Self { + device, + queue, + surface_config, + surface, + egui_rpass, + } + } + + pub fn max_texture_side(&self) -> usize { + self.device.limits().max_texture_dimension_2d as usize + } + + pub fn on_window_resized(&mut self, width_in_pixels: u32, height_in_pixels: u32) { + self.surface_config.width = width_in_pixels; + self.surface_config.height = height_in_pixels; + self.surface.configure(&self.device, &self.surface_config); + } + + pub fn paint_and_update_textures( + &mut self, + pixels_per_point: f32, + clear_color: egui::Rgba, + clipped_primitives: &[egui::ClippedPrimitive], + textures_delta: &egui::TexturesDelta, + ) { + let output_frame = match self.surface.get_current_texture() { + Ok(frame) => frame, + Err(wgpu::SurfaceError::Outdated) => { + // This error occurs when the app is minimized on Windows. + // Silently return here to prevent spamming the console with: + // "The underlying surface has changed, and therefore the swap chain must be updated" + return; + } + Err(e) => { + tracing::warn!("Dropped frame with error: {e}"); + return; + } + }; + let output_view = output_frame + .texture + .create_view(&wgpu::TextureViewDescriptor::default()); + + let mut encoder = self + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: Some("encoder"), + }); + + // Upload all resources for the GPU. + let screen_descriptor = renderer::ScreenDescriptor { + size_in_pixels: [self.surface_config.width, self.surface_config.height], + pixels_per_point, + }; + + for (id, image_delta) in &textures_delta.set { + self.egui_rpass + .update_texture(&self.device, &self.queue, *id, image_delta); + } + for id in &textures_delta.free { + self.egui_rpass.free_texture(id); + } + + self.egui_rpass.update_buffers( + &self.device, + &self.queue, + clipped_primitives, + &screen_descriptor, + ); + + // Record all render passes. + self.egui_rpass.execute( + &mut encoder, + &output_view, + clipped_primitives, + &screen_descriptor, + Some(wgpu::Color { + r: clear_color.r() as f64, + g: clear_color.g() as f64, + b: clear_color.b() as f64, + a: clear_color.a() as f64, + }), + ); + + // Submit the commands. + self.queue.submit(std::iter::once(encoder.finish())); + + // Redraw egui + output_frame.present(); + } + + #[allow(clippy::unused_self)] + pub fn destroy(&mut self) { + // TODO: something here? + } +} diff --git a/egui_demo_app/Cargo.toml b/egui_demo_app/Cargo.toml index 43baed45..4f705f36 100644 --- a/egui_demo_app/Cargo.toml +++ b/egui_demo_app/Cargo.toml @@ -15,7 +15,7 @@ crate-type = ["cdylib", "rlib"] [features] -default = ["persistence"] +default = ["glow", "persistence"] http = ["ehttp", "image", "poll-promise", "egui_extras/image"] persistence = [ @@ -32,13 +32,15 @@ serde = [ ] syntax_highlighting = ["egui_demo_lib/syntax_highlighting"] +glow = ["eframe/glow"] +wgpu = ["eframe/wgpu"] + [dependencies] chrono = { version = "0.4", features = ["js-sys", "wasmbind"] } -eframe = { version = "0.18.0", path = "../eframe" } +eframe = { version = "0.18.0", path = "../eframe", default-features = false } egui = { version = "0.18.0", path = "../egui", features = ["extra_debug_asserts"] } egui_demo_lib = { version = "0.18.0", path = "../egui_demo_lib", features = ["chrono"] } -egui_glow = { version = "0.18.0", path = "../egui_glow" } # Optional dependencies: diff --git a/egui_demo_app/src/apps/custom3d.rs b/egui_demo_app/src/apps/custom3d.rs index 5d239fe2..95b86a6e 100644 --- a/egui_demo_app/src/apps/custom3d.rs +++ b/egui_demo_app/src/apps/custom3d.rs @@ -1,5 +1,6 @@ use std::sync::Arc; +use eframe::egui_glow; use egui::mutex::Mutex; use egui_glow::glow; @@ -39,8 +40,10 @@ impl eframe::App for Custom3d { }); } - fn on_exit(&mut self, gl: &glow::Context) { - self.rotating_triangle.lock().destroy(gl); + fn on_exit(&mut self, gl: Option<&glow::Context>) { + if let Some(gl) = gl { + self.rotating_triangle.lock().destroy(gl); + } } } diff --git a/egui_demo_app/src/apps/mod.rs b/egui_demo_app/src/apps/mod.rs index e7d652d5..62ad4a47 100644 --- a/egui_demo_app/src/apps/mod.rs +++ b/egui_demo_app/src/apps/mod.rs @@ -1,9 +1,15 @@ +#[cfg(feature = "glow")] mod custom3d; + mod fractal_clock; + #[cfg(feature = "http")] mod http_app; +#[cfg(feature = "glow")] pub use custom3d::Custom3d; + pub use fractal_clock::FractalClock; + #[cfg(feature = "http")] pub use http_app::HttpApp; diff --git a/egui_demo_app/src/main.rs b/egui_demo_app/src/main.rs index 91f13e4e..917e9835 100644 --- a/egui_demo_app/src/main.rs +++ b/egui_demo_app/src/main.rs @@ -9,6 +9,10 @@ fn main() { let options = eframe::NativeOptions { drag_and_drop_support: true, + + #[cfg(feature = "wgpu")] + renderer: eframe::Renderer::Wgpu, + ..Default::default() }; eframe::run_native( diff --git a/egui_demo_app/src/wrap_app.rs b/egui_demo_app/src/wrap_app.rs index b6410af9..4a5e0a50 100644 --- a/egui_demo_app/src/wrap_app.rs +++ b/egui_demo_app/src/wrap_app.rs @@ -1,5 +1,7 @@ use egui_demo_lib::is_mobile; -use egui_glow::glow; + +#[cfg(feature = "glow")] +use eframe::glow; #[derive(Default)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] @@ -91,7 +93,8 @@ pub struct State { pub struct WrapApp { state: State, // not serialized (because it contains OpenGL buffers etc) - custom3d: crate::apps::Custom3d, + #[cfg(feature = "glow")] + custom3d: Option, dropped_files: Vec, } @@ -100,7 +103,8 @@ impl WrapApp { #[allow(unused_mut)] let mut slf = Self { state: State::default(), - custom3d: crate::apps::Custom3d::new(&cc.gl), + #[cfg(feature = "glow")] + custom3d: cc.gl.as_ref().map(|gl| crate::apps::Custom3d::new(gl)), dropped_files: Default::default(), }; @@ -121,7 +125,7 @@ impl WrapApp { } fn apps_iter_mut(&mut self) -> impl Iterator { - vec![ + let mut vec = vec![ ( "✨ Demos", "demo", @@ -143,18 +147,24 @@ impl WrapApp { "clock", &mut self.state.clock as &mut dyn eframe::App, ), - ( + ]; + + #[cfg(feature = "glow")] + if let Some(custom3d) = &mut self.custom3d { + vec.push(( "🔺 3D painting", "custom3e", - &mut self.custom3d as &mut dyn eframe::App, - ), - ( - "🎨 Color test", - "colors", - &mut self.state.color_test as &mut dyn eframe::App, - ), - ] - .into_iter() + custom3d as &mut dyn eframe::App, + )); + } + + vec.push(( + "🎨 Color test", + "colors", + &mut self.state.color_test as &mut dyn eframe::App, + )); + + vec.into_iter() } } @@ -212,8 +222,11 @@ impl eframe::App for WrapApp { self.ui_file_drag_and_drop(ctx); } - fn on_exit(&mut self, gl: &glow::Context) { - self.custom3d.on_exit(gl); + #[cfg(feature = "glow")] + fn on_exit(&mut self, gl: Option<&glow::Context>) { + if let Some(custom3d) = &mut self.custom3d { + custom3d.on_exit(gl); + } } } diff --git a/egui_glow/src/painter.rs b/egui_glow/src/painter.rs index e97d5e04..f05e41a9 100644 --- a/egui_glow/src/painter.rs +++ b/egui_glow/src/painter.rs @@ -649,6 +649,7 @@ impl Painter { } pub fn clear(gl: &glow::Context, screen_size_in_pixels: [u32; 2], clear_color: egui::Rgba) { + crate::profile_function!(); unsafe { gl.disable(glow::SCISSOR_TEST); diff --git a/examples/custom_3d_glow/Cargo.toml b/examples/custom_3d_glow/Cargo.toml index 74268e5f..645531b7 100644 --- a/examples/custom_3d_glow/Cargo.toml +++ b/examples/custom_3d_glow/Cargo.toml @@ -9,6 +9,6 @@ publish = false [dependencies] -eframe = { path = "../../eframe" } +eframe = { path = "../../eframe", features = ["glow"] } egui_glow = { path = "../../egui_glow" } glow = "0.11" diff --git a/examples/custom_3d_glow/src/main.rs b/examples/custom_3d_glow/src/main.rs index f6ed237f..b1cdd1d2 100644 --- a/examples/custom_3d_glow/src/main.rs +++ b/examples/custom_3d_glow/src/main.rs @@ -10,6 +10,7 @@ fn main() { let options = eframe::NativeOptions { initial_window_size: Some(egui::vec2(350.0, 380.0)), multisampling: 8, + renderer: eframe::Renderer::Glow, ..Default::default() }; eframe::run_native( @@ -27,8 +28,12 @@ struct MyApp { impl MyApp { fn new(cc: &eframe::CreationContext<'_>) -> Self { + let gl = cc + .gl + .as_ref() + .expect("You need to run eframe with the glow backend"); Self { - rotating_triangle: Arc::new(Mutex::new(RotatingTriangle::new(&cc.gl))), + rotating_triangle: Arc::new(Mutex::new(RotatingTriangle::new(gl))), angle: 0.0, } } @@ -51,8 +56,10 @@ impl eframe::App for MyApp { }); } - fn on_exit(&mut self, gl: &glow::Context) { - self.rotating_triangle.lock().destroy(gl); + fn on_exit(&mut self, gl: Option<&glow::Context>) { + if let Some(gl) = gl { + self.rotating_triangle.lock().destroy(gl); + } } } diff --git a/examples/custom_3d_three-d/Cargo.toml b/examples/custom_3d_three-d/Cargo.toml index 83ec88ba..0fa96aae 100644 --- a/examples/custom_3d_three-d/Cargo.toml +++ b/examples/custom_3d_three-d/Cargo.toml @@ -9,7 +9,7 @@ publish = false [dependencies] -eframe = { path = "../../eframe" } +eframe = { path = "../../eframe", features = ["glow"] } egui_glow = { path = "../../egui_glow" } glow = "0.11" three-d = { version = "0.11", default-features = false } diff --git a/examples/custom_3d_three-d/src/main.rs b/examples/custom_3d_three-d/src/main.rs index efe1b473..cf2f9723 100644 --- a/examples/custom_3d_three-d/src/main.rs +++ b/examples/custom_3d_three-d/src/main.rs @@ -6,6 +6,7 @@ fn main() { let options = eframe::NativeOptions { initial_window_size: Some(egui::vec2(550.0, 610.0)), multisampling: 8, + renderer: eframe::Renderer::Glow, ..Default::default() }; eframe::run_native( diff --git a/sh/build_demo_web.sh b/sh/build_demo_web.sh index 2f68ce59..3d634bb1 100755 --- a/sh/build_demo_web.sh +++ b/sh/build_demo_web.sh @@ -4,7 +4,7 @@ script_path=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P ) cd "$script_path/.." CRATE_NAME="egui_demo_app" -FEATURES="http,persistence,screen_reader" +FEATURES="glow,http,persistence,screen_reader" OPEN=false FAST=false diff --git a/sh/check.sh b/sh/check.sh index 766583ac..78331c96 100755 --- a/sh/check.sh +++ b/sh/check.sh @@ -20,9 +20,11 @@ cargo fmt --all -- --check cargo doc -p eframe -p egui -p egui_demo_lib -p egui_extras -p egui_glium -p egui_glow -p egui-winit -p emath -p epaint --lib --no-deps --all-features cargo doc --document-private-items --no-deps --all-features -(cd eframe && cargo check --no-default-features) +(cd eframe && cargo check --no-default-features --features "glow") +(cd eframe && cargo check --no-default-features --features "wgpu") (cd egui && cargo check --no-default-features --features "serde") -(cd egui_demo_app && cargo check --no-default-features) +(cd egui_demo_app && cargo check --no-default-features --features "glow") +(cd egui_demo_app && cargo check --no-default-features --features "wgpu") (cd egui_demo_lib && cargo check --no-default-features) (cd egui_extras && cargo check --no-default-features) (cd egui_glium && cargo check --no-default-features) diff --git a/sh/wasm_bindgen_check.sh b/sh/wasm_bindgen_check.sh index a50947a2..e00858e1 100755 --- a/sh/wasm_bindgen_check.sh +++ b/sh/wasm_bindgen_check.sh @@ -4,7 +4,7 @@ script_path=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P ) cd "$script_path/.." CRATE_NAME="egui_demo_app" -FEATURES="http,persistence,screen_reader" +FEATURES="glow,http,persistence,screen_reader" # This is required to enable the web_sys clipboard API which eframe web uses # https://rustwasm.github.io/wasm-bindgen/api/web_sys/struct.Clipboard.html