Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

egui 0.16 #51

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from 5 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
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "egui_wgpu_backend"
version = "0.15.0"
version = "0.16.0"
authors = ["Nils Hasenbanck <nils@hasenbanck.de>"]
edition = "2018"
description = "Backend code to use egui with wgpu."
Expand All @@ -15,6 +15,6 @@ default = []
web = []

[dependencies]
epi = "0.15"
epi = "0.16"
wgpu = "0.12"
bytemuck = "1.7"
190 changes: 143 additions & 47 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ use bytemuck::{Pod, Zeroable};
pub use wgpu;
use wgpu::util::DeviceExt;

use std::collections::HashMap;
use std::sync::Arc;

pub use {epi, epi::egui};

/// Error that the backend can return.
Expand All @@ -37,9 +40,7 @@ impl std::fmt::Display for BackendError {

impl std::error::Error for BackendError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match *self {
_ => None,
}
None
}
}

Expand Down Expand Up @@ -98,8 +99,8 @@ pub struct RenderPass {
texture_bind_group: Option<wgpu::BindGroup>,
texture_version: Option<u64>,
next_user_texture_id: u64,
pending_user_textures: Vec<(u64, egui::Texture)>,
user_textures: Vec<Option<wgpu::BindGroup>>,
pending_user_textures: Vec<(u64, UserTexture)>,
user_textures: HashMap<u64, Arc<wgpu::BindGroup>>,
}

impl RenderPass {
Expand Down Expand Up @@ -255,7 +256,7 @@ impl RenderPass {
texture_bind_group: None,
next_user_texture_id: 0,
pending_user_textures: Vec::new(),
user_textures: Vec::new(),
user_textures: HashMap::new(),
}
}

Expand All @@ -264,7 +265,7 @@ impl RenderPass {
&self,
encoder: &mut wgpu::CommandEncoder,
color_attachment: &wgpu::TextureView,
paint_jobs: &[egui::paint::ClippedMesh],
paint_jobs: &[egui::epaint::ClippedMesh],
screen_descriptor: &ScreenDescriptor,
clear_color: Option<wgpu::Color>,
) -> Result<(), BackendError> {
Expand Down Expand Up @@ -299,7 +300,7 @@ impl RenderPass {
pub fn execute_with_renderpass<'rpass>(
&'rpass self,
rpass: &mut wgpu::RenderPass<'rpass>,
paint_jobs: &[egui::paint::ClippedMesh],
paint_jobs: &[egui::epaint::ClippedMesh],
screen_descriptor: &ScreenDescriptor,
) -> Result<(), BackendError> {
rpass.set_pipeline(&self.render_pipeline);
Expand Down Expand Up @@ -369,16 +370,13 @@ impl RenderPass {
BackendError::Internal("egui texture was not set before the first draw".to_string())
})?,
egui::TextureId::User(id) => {
let id = id as usize;
assert!(id < self.user_textures.len());
&(self
.user_textures
.get(id)
assert!(self.user_textures.contains_key(&id));
self.user_textures
.get(&id)
.ok_or_else(|| {
BackendError::Internal(format!("user texture {} not found", id))
})?
.as_ref()
.ok_or_else(|| BackendError::Internal(format!("user texture {} freed", id))))?
}
};

Expand All @@ -390,7 +388,7 @@ impl RenderPass {
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
egui_texture: &egui::Texture,
egui_texture: &egui::FontImage,
) {
// Don't update the texture if it hasn't changed.
if self.texture_version == Some(egui_texture.version) {
Expand All @@ -404,7 +402,7 @@ impl RenderPass {
pixels.push(srgba.b());
pixels.push(srgba.a());
}
let egui_texture = egui::Texture {
let egui_texture = egui::FontImage {
version: egui_texture.version,
width: egui_texture.width,
height: egui_texture.height,
Expand All @@ -418,30 +416,34 @@ impl RenderPass {

/// Updates the user textures that the app allocated. Should be called before `execute()`.
pub fn update_user_textures(&mut self, device: &wgpu::Device, queue: &wgpu::Queue) {
let pending_user_textures = std::mem::take(&mut self.pending_user_textures);
for (id, texture) in pending_user_textures {
for (id, texture) in self
.pending_user_textures
.drain(..)
.collect::<Vec<_>>()
.into_iter()
{
let bind_group = self.egui_texture_to_wgpu(
device,
queue,
&texture,
format!("user_texture{}", id).as_str(),
);
self.user_textures.push(Some(bind_group));
self.user_textures.insert(id, Arc::new(bind_group));
}
}

/// Assumes egui_texture contains srgb data.
/// This does not match how `egui::Texture` is documented as of writing, but this is how it is used for user textures.
fn egui_texture_to_wgpu(
fn egui_texture_to_wgpu<T: UploadableTexture>(
&self,
device: &wgpu::Device,
queue: &wgpu::Queue,
egui_texture: &egui::Texture,
egui_texture: &T,
label: &str,
) -> wgpu::BindGroup {
let size = wgpu::Extent3d {
width: egui_texture.width as u32,
height: egui_texture.height as u32,
width: egui_texture.width() as u32,
height: egui_texture.height() as u32,
depth_or_array_layers: 1,
};

Expand All @@ -462,13 +464,13 @@ impl RenderPass {
origin: wgpu::Origin3d::ZERO,
aspect: wgpu::TextureAspect::All,
},
egui_texture.pixels.as_slice(),
egui_texture.pixel_data(),
wgpu::ImageDataLayout {
offset: 0,
bytes_per_row: NonZeroU32::new(
(egui_texture.pixels.len() / egui_texture.height) as u32,
(egui_texture.pixel_data().len() / egui_texture.height()) as u32,
),
rows_per_image: NonZeroU32::new(egui_texture.height as u32),
rows_per_image: NonZeroU32::new(egui_texture.height() as u32),
},
size,
);
Expand Down Expand Up @@ -584,8 +586,9 @@ impl RenderPass {
},
],
});
let texture_id = egui::TextureId::User(self.next_user_texture_id);
self.user_textures.push(Some(bind_group));
let id = self.next_user_texture_id;
let texture_id = egui::TextureId::User(id);
self.user_textures.insert(id, Arc::new(bind_group));
self.next_user_texture_id += 1;

texture_id
Expand All @@ -611,7 +614,7 @@ impl RenderPass {
}
};

let user_texture = self.user_textures.get_mut(id as usize).ok_or_else(|| {
let user_texture = self.user_textures.get_mut(&id).ok_or_else(|| {
BackendError::InvalidTextureId(format!(
"user texture for TextureId {} could not be found",
id
Expand Down Expand Up @@ -641,7 +644,7 @@ impl RenderPass {
],
});

*user_texture = Some(bind_group);
*user_texture = Arc::new(bind_group);

Ok(())
}
Expand All @@ -652,7 +655,7 @@ impl RenderPass {
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
paint_jobs: &[egui::paint::ClippedMesh],
paint_jobs: &[egui::epaint::ClippedMesh],
screen_descriptor: &ScreenDescriptor,
) {
let index_size = self.index_buffers.len();
Expand Down Expand Up @@ -742,27 +745,22 @@ impl RenderPass {
queue.write_buffer(&buffer.buffer, 0, data);
}
}
}

impl epi::TextureAllocator for RenderPass {
fn alloc_srgba_premultiplied(
&mut self,
size: (usize, usize),
srgba_pixels: &[egui::Color32],
) -> egui::TextureId {
let id = self.next_user_texture_id;
self.next_user_texture_id += 1;
// -------

/// Creates a texture at a certain user texture location
pub fn set_texture(&mut self, id: u64, image: epi::Image) -> egui::TextureId {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't the id be selected for the caller? It's an internal implementation detail. The actual id allocated is already included in the return value, which should be good enough? I can't think of any cases where I would want to specify the id when creating a texture.

let [width, height] = image.size;
let srgba_pixels = image.pixels;

let mut pixels = vec![0u8; srgba_pixels.len() * 4];
for (target, given) in pixels.chunks_exact_mut(4).zip(srgba_pixels.iter()) {
target.copy_from_slice(&given.to_array());
}

let (width, height) = size;
self.pending_user_textures.push((
id,
egui::Texture {
version: 0,
UserTexture {
width,
height,
pixels,
Expand All @@ -772,14 +770,112 @@ impl epi::TextureAllocator for RenderPass {
egui::TextureId::User(id)
}

fn free(&mut self, id: egui::TextureId) {
/// Frees a user location
pub fn free_texture(&mut self, id: u64) {
self.user_textures.remove(&id);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's also remove this and just offer the other free_... method.


/// Frees a texture id (if that texture was a user texture)
pub fn free_texture_id(&mut self, id: egui::TextureId) {
if let egui::TextureId::User(id) = id {
self.user_textures.remove(&id);
}
}
}

// Describes something that can be uploaded to the GPU as a texture
trait UploadableTexture {
fn width(&self) -> usize;
fn height(&self) -> usize;
fn pixel_data(&self) -> &[u8];
}

impl UploadableTexture for egui::FontImage {
fn width(&self) -> usize {
self.width
}

fn height(&self) -> usize {
self.height
}

fn pixel_data(&self) -> &[u8] {
&self.pixels
}
}

struct UserTexture {
width: usize,
height: usize,
pixels: Vec<u8>,
}

impl UploadableTexture for UserTexture {
fn width(&self) -> usize {
self.width
}

fn height(&self) -> usize {
self.height
}

fn pixel_data(&self) -> &[u8] {
&self.pixels
}
}

impl epi::NativeTexture for RenderPass {
type Texture = Arc<wgpu::BindGroup>;

fn register_native_texture(&mut self, bg: Self::Texture) -> egui::TextureId {
let id = self.next_user_texture_id;
self.next_user_texture_id += 1;
self.user_textures.insert(id, bg);
egui::TextureId::User(id)
}

fn replace_native_texture(&mut self, id: egui::TextureId, tex: Self::Texture) {
if let egui::TextureId::User(id) = id {
self.user_textures
.get_mut(id as usize)
.and_then(|option| option.take());
self.user_textures.insert(id, tex);
}
}
}
//
// impl epi::TextureAllocator for Mutex<RenderPass> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And delete all this commented code.

// fn alloc(&self, image: epi::Image) -> egui::TextureId {
// let id = self.next_user_texture_id.get();
// self.next_user_texture_id.set(id + 1);
//
// let [width, height] = image.size;
// let srgba_pixels = image.pixels;
//
// let mut pixels = vec![0u8; srgba_pixels.len() * 4];
// for (target, given) in pixels.chunks_exact_mut(4).zip(srgba_pixels.iter()) {
// target.copy_from_slice(&given.to_array());
// }
//
// self.pending_user_textures.lock().unwrap().push((
// id,
// UserTexture {
// width,
// height,
// pixels,
// },
// ));
//
// egui::TextureId::User(id)
// }
//
// fn free(&self, id: egui::TextureId) {
// if let egui::TextureId::User(id) = id {
// self.user_textures
// .write()
// .unwrap()
// .get_mut(id as usize)
// .and_then(|option| option.take());
// }
// }
// }

// Needed since we can't use bytemuck for external types.
fn as_byte_slice<T>(slice: &[T]) -> &[u8] {
Expand Down