Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ constify = { path = "constify", version = "0.0.1"}
rayon = { version = "1.11.0", optional = true }
log = { version = "0.4.28", optional = true }
winit = {version = "0.30", optional = true }
# When upgrading `softbuffer` always check if macos or android still can't use buffered modes
softbuffer = { version = "0.4", optional = true }
egui-winit = { version = "0.33", default-features = false, optional = true}
bytemuck = { version = "1.23", optional = true }
Expand All @@ -34,12 +35,12 @@ egui_extras = { version = "0.33", features = ["all_loaders"] }
egui-winit = { version = "0.33", default-features = false }
epaint_default_fonts = "0.33"

softbuffer = { version = "0.4" }
softbuffer = { version = "0.4" }
image = { version = "0.25", features = ["jpeg", "png"] }
winit = { version = "0.30" }
bytemuck = { version = "1.23" }
dify = "0.7"
argh = "0.1"
argh = "0.1.14"


# Enable optimization in debug mode
Expand Down
21 changes: 6 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,34 +1,24 @@
# CPU software render backend for [egui](https://github.com/emilk/egui)
![License](https://img.shields.io/badge/license-MIT%2FApache-blue.svg) [![Crates.io](https://img.shields.io/crates/v/egui_software_backend.svg)](https://crates.io/crates/egui_software_backend)
[![Docs](https://docs.rs/egui_software_backend/badge.svg)](https://docs.rs/egui_software_backend/latest/egui_software_backend/)

![demo](demo.png)

```rs
use egui_software_backend::{BufferMutRef, ColorFieldOrder, EguiSoftwareRender};
let buffer = &mut vec![[0u8; 4]; 512 * 512];
let mut buffer_ref = BufferMutRef::new(buffer, 512, 512);
let ctx = egui::Context::default();
let mut demo = egui_demo_lib::DemoWindows::default();
let mut sw_render = EguiSoftwareRender::new(ColorFieldOrder::Bgra);

let out = ctx.run(egui::RawInput::default(), |ctx| {
let out = ctx.run(raw_input, |ctx| {
demo.ui(ctx);
});

let primitives = ctx.tessellate(out.shapes, out.pixels_per_point);

sw_render.render(
&mut buffer_ref,
&primitives,
&out.textures_delta,
out.pixels_per_point,
);
sw_render.render(buffer, &primitives, &out.textures_delta, out.pixels_per_point);
```

## winit quickstart
```rust
use egui::vec2;
use egui::Vec2;
use egui_software_backend::{SoftwareBackend, SoftwareBackendAppConfiguration};

struct EguiApp {}
Expand All @@ -50,7 +40,8 @@ impl egui_software_backend::App for EguiApp {

fn main() {
let settings = SoftwareBackendAppConfiguration::new()
.inner_size(Some(vec2(500.0, 300.0)))
.inner_size(Some(Vec2::new(500f32, 300f32)))
.resizable(Some(false))
.title(Some("Simple example".to_string()));

egui_software_backend::run_app_with_software_backend(settings, EguiApp::new)
Expand All @@ -62,4 +53,4 @@ fn main() {
[egui_backend_selector](https://github.com/AlexanderSchuetz97/egui_backend_selector) can be used in conjunction with this crate to automatically fallback to using this software renderer at runtime.

## Other examples
- bevy + softbuffer see examples/bevy_example folder
- bevy + softbuffer see examples/bevy_example folder
31 changes: 24 additions & 7 deletions examples/winit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,36 @@ impl EguiApp {
frame_times: Vec::new(),
}
}

fn ui(&mut self, ctx: &egui::Context) {
//egui::CentralPanel::default().show(ctx, |_ui| {
self.demo.ui(ctx);

egui::Window::new("Color Test").show(ctx, |ui| {
egui::ScrollArea::both().auto_shrink(false).show(ui, |ui| {
self.color_test.ui(ui);
});
});
//});
}
}

impl eframe::App for EguiApp {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
egui::CentralPanel::default().show(ctx, |_ui| {
self.ui(ctx);
});
}
}

impl egui_software_backend::App for EguiApp {
fn update(&mut self, ctx: &egui::Context, backend: &mut SoftwareBackend) {
backend.set_capture_frame_time(true);

egui::CentralPanel::default().show(ctx, |_ui| {
self.demo.ui(ctx);
self.ui(ctx);

egui::Window::new("Color Test").show(ctx, |ui| {
egui::ScrollArea::both().auto_shrink(false).show(ui, |ui| {
self.color_test.ui(ui);
});
#[cfg(feature = "raster_stats")]
egui::Window::new("Stats").show(ctx, |ui| {
backend.display_stats(ui);
});

if self.frame_times.len() < 100 {
Expand Down
2 changes: 0 additions & 2 deletions examples/winit_hello.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@ impl EguiApp {

impl egui_software_backend::App for EguiApp {
fn update(&mut self, ctx: &egui::Context, backend: &mut SoftwareBackend) {
backend.set_capture_frame_time(true);

egui::CentralPanel::default().show(ctx, |ui| {
let last_frame_time = backend.last_frame_time().unwrap_or_default();

Expand Down
63 changes: 46 additions & 17 deletions examples/winit_raw.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
// Based on: https://github.com/rust-windowing/softbuffer/blob/046de9228d89369151599f3f50dc4b75bd5e522b/examples/winit.rs

use argh::FromArgs;
use argh::{FromArgValue, FromArgs};
use core::num::NonZeroU32;
use egui_demo_lib::ColorTest;
use egui_software_backend::{BufferMutRef, ColorFieldOrder, EguiSoftwareRender};
use egui_software_backend::{
BufferMutRef, ColorFieldOrder, EguiSoftwareRender, SoftwareRenderCaching,
};
use std::rc::Rc;
use std::time::Instant;
use winit::event::{Event, WindowEvent};
Expand All @@ -15,7 +17,15 @@ use crate::winit_app::WinitApp;
#[path = "../examples/utils/winit_app.rs"]
mod winit_app;

#[derive(FromArgs, Copy, Clone)]
#[derive(FromArgValue)]
enum CachingArg {
BlendTiled,
MeshTiled,
Mesh,
Direct,
}

#[derive(FromArgs)]
/// `bevy` example
struct Args {
/// disable raster optimizations. Rasterize everything with triangles, always calculate vertex colors, uvs, use
Expand All @@ -28,9 +38,9 @@ struct Args {
#[argh(switch)]
no_rect: bool,

/// render directly into buffer without cache. This is much slower and mainly intended for testing.
#[argh(switch)]
direct: bool,
/// select the caching mode, defaults to BlendTiled
#[argh(option)]
caching: Option<CachingArg>,
}

struct AppState {
Expand All @@ -44,10 +54,17 @@ fn main() {

let mut egui_demo = egui_demo_lib::DemoWindows::default();
let mut egui_color_test = ColorTest::default();
let caching = match args.caching {
Some(CachingArg::BlendTiled) | None => SoftwareRenderCaching::BlendTiled,
Some(CachingArg::MeshTiled) => SoftwareRenderCaching::MeshTiled,
Some(CachingArg::Mesh) => SoftwareRenderCaching::Mesh,
Some(CachingArg::Direct) => SoftwareRenderCaching::Direct,
};
let mut egui_software_render = EguiSoftwareRender::new(ColorFieldOrder::Bgra)
.with_allow_raster_opt(!args.no_opt)
.with_convert_tris_to_rects(!args.no_rect)
.with_caching(!args.direct);
.with_caching(caching);
let mut buffer_states = egui_software_backend::BufferStates::new();

let event_loop: EventLoop<()> = EventLoop::new().unwrap();

Expand Down Expand Up @@ -139,7 +156,7 @@ fn main() {

#[cfg(feature = "raster_stats")]
egui::Window::new("Stats").show(ctx, |ui| {
egui_software_render.stats.render(ui);
egui_software_render.display_stats(ui);
});
});

Expand All @@ -148,30 +165,42 @@ fn main() {
.tessellate(full_output.shapes, full_output.pixels_per_point);

let mut buffer = app.surface.buffer_mut().unwrap();
buffer.fill(0); // CLEAR

let age = buffer.age();
let buffer_ref = &mut BufferMutRef::new(
bytemuck::cast_slice_mut(&mut buffer),
width as usize,
height as usize,
width,
height,
);
let buffer_state = buffer_states.next(age, buffer_ref.data.len());
if buffer_state.is_new_zeroed() {
// age == 0 || resized
buffer_ref.data.fill(Default::default());
}

egui_software_render.render(
let dirty_rect = egui_software_render.render(
buffer_ref,
&clipped_primitives,
buffer_state,
clipped_primitives,
&full_output.textures_delta,
full_output.pixels_per_point,
);

buffer.present().unwrap();
if !dirty_rect.is_empty() {
let dirty_rect = softbuffer::Rect {
x: dirty_rect.min_x,
y: dirty_rect.min_y,
width: NonZeroU32::new(dirty_rect.width()).expect("non zero rect"),
height: NonZeroU32::new(dirty_rect.height()).expect("non zero rect"),
};
buffer.present_with_damage(&[dirty_rect]).unwrap();
}

let now = Instant::now();
if frame_times.len() < 100 {
frame_times.push(now.duration_since(last_frame_time).as_secs_f32());
} else {
let avg =
(frame_times.iter().sum::<f32>() / frame_times.len() as f32) * 1000.0;
window.set_title(&format!("Frame Time {avg:.2}ms"));
window.set_title(&format!("Frame Time {avg:.2}ms - {caching:?}"));
frame_times.clear();
}
last_frame_time = now;
Expand Down
Loading