mirror of
https://github.com/emilk/egui.git
synced 2026-08-31 05:40:03 -04:00
Move all crates into a crates directory (#1940)
This commit is contained in:
80
crates/egui-wgpu/src/egui.wgsl
Normal file
80
crates/egui-wgpu/src/egui.wgsl
Normal file
@@ -0,0 +1,80 @@
|
||||
// Vertex shader bindings
|
||||
|
||||
struct VertexOutput {
|
||||
@location(0) tex_coord: vec2<f32>,
|
||||
@location(1) color: vec4<f32>,
|
||||
@builtin(position) position: vec4<f32>,
|
||||
};
|
||||
|
||||
struct Locals {
|
||||
screen_size: vec2<f32>,
|
||||
// Uniform buffers need to be at least 16 bytes in WebGL.
|
||||
// See https://github.com/gfx-rs/wgpu/issues/2072
|
||||
_padding: vec2<u32>,
|
||||
};
|
||||
@group(0) @binding(0) var<uniform> r_locals: Locals;
|
||||
|
||||
// 0-1 from 0-255
|
||||
fn linear_from_srgb(srgb: vec3<f32>) -> vec3<f32> {
|
||||
let cutoff = srgb < vec3<f32>(10.31475);
|
||||
let lower = srgb / vec3<f32>(3294.6);
|
||||
let higher = pow((srgb + vec3<f32>(14.025)) / vec3<f32>(269.025), vec3<f32>(2.4));
|
||||
return select(higher, lower, cutoff);
|
||||
}
|
||||
|
||||
// [u8; 4] SRGB as u32 -> [r, g, b, a]
|
||||
fn unpack_color(color: u32) -> vec4<f32> {
|
||||
return vec4<f32>(
|
||||
f32(color & 255u),
|
||||
f32((color >> 8u) & 255u),
|
||||
f32((color >> 16u) & 255u),
|
||||
f32((color >> 24u) & 255u),
|
||||
);
|
||||
}
|
||||
|
||||
fn position_from_screen(screen_pos: vec2<f32>) -> vec4<f32> {
|
||||
return vec4<f32>(
|
||||
2.0 * screen_pos.x / r_locals.screen_size.x - 1.0,
|
||||
1.0 - 2.0 * screen_pos.y / r_locals.screen_size.y,
|
||||
0.0,
|
||||
1.0,
|
||||
);
|
||||
}
|
||||
|
||||
@vertex
|
||||
fn vs_main(
|
||||
@location(0) a_pos: vec2<f32>,
|
||||
@location(1) a_tex_coord: vec2<f32>,
|
||||
@location(2) a_color: u32,
|
||||
) -> VertexOutput {
|
||||
var out: VertexOutput;
|
||||
out.tex_coord = a_tex_coord;
|
||||
let color = unpack_color(a_color);
|
||||
out.color = vec4<f32>(linear_from_srgb(color.rgb), color.a / 255.0);
|
||||
out.position = position_from_screen(a_pos);
|
||||
return out;
|
||||
}
|
||||
|
||||
@vertex
|
||||
fn vs_conv_main(
|
||||
@location(0) a_pos: vec2<f32>,
|
||||
@location(1) a_tex_coord: vec2<f32>,
|
||||
@location(2) a_color: u32,
|
||||
) -> VertexOutput {
|
||||
var out: VertexOutput;
|
||||
out.tex_coord = a_tex_coord;
|
||||
let color = unpack_color(a_color);
|
||||
out.color = vec4<f32>(color.rgba / 255.0);
|
||||
out.position = position_from_screen(a_pos);
|
||||
return out;
|
||||
}
|
||||
|
||||
// Fragment shader bindings
|
||||
|
||||
@group(1) @binding(0) var r_tex_color: texture_2d<f32>;
|
||||
@group(1) @binding(1) var r_tex_sampler: sampler;
|
||||
|
||||
@fragment
|
||||
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||
return in.color * textureSample(r_tex_color, r_tex_sampler, in.tex_coord);
|
||||
}
|
||||
20
crates/egui-wgpu/src/lib.rs
Normal file
20
crates/egui-wgpu/src/lib.rs
Normal file
@@ -0,0 +1,20 @@
|
||||
//! This crates provides bindings between [`egui`](https://github.com/emilk/egui) and [wgpu](https://crates.io/crates/wgpu).
|
||||
//!
|
||||
//! ## Feature flags
|
||||
#![cfg_attr(feature = "document-features", doc = document_features::document_features!())]
|
||||
//!
|
||||
|
||||
#![allow(unsafe_code)]
|
||||
|
||||
pub use wgpu;
|
||||
|
||||
/// Low-level painting of [`egui`] on [`wgpu`].
|
||||
pub mod renderer;
|
||||
pub use renderer::CallbackFn;
|
||||
|
||||
/// Module for painting [`egui`] with [`wgpu`] on [`winit`].
|
||||
#[cfg(feature = "winit")]
|
||||
pub mod winit;
|
||||
|
||||
#[cfg(feature = "winit")]
|
||||
pub use crate::winit::RenderState;
|
||||
815
crates/egui-wgpu/src/renderer.rs
Normal file
815
crates/egui-wgpu/src/renderer.rs
Normal file
@@ -0,0 +1,815 @@
|
||||
#![allow(unsafe_code)]
|
||||
|
||||
use std::{borrow::Cow, collections::HashMap, num::NonZeroU32};
|
||||
|
||||
use egui::{epaint::Primitive, NumExt, PaintCallbackInfo};
|
||||
use type_map::concurrent::TypeMap;
|
||||
use wgpu;
|
||||
use wgpu::util::DeviceExt as _;
|
||||
|
||||
/// A callback function that can be used to compose an [`egui::PaintCallback`] for custom WGPU
|
||||
/// rendering.
|
||||
///
|
||||
/// The callback is composed of two functions: `prepare` and `paint`.
|
||||
///
|
||||
/// `prepare` is called every frame before `paint`, and can use the passed-in [`wgpu::Device`] and
|
||||
/// [`wgpu::Buffer`] to allocate or modify GPU resources such as buffers.
|
||||
///
|
||||
/// `paint` is called after `prepare` and is given access to the the [`wgpu::RenderPass`] so that it
|
||||
/// can issue draw commands.
|
||||
///
|
||||
/// The final argument of both the `prepare` and `paint` callbacks is a the
|
||||
/// [`paint_callback_resources`][crate::renderer::RenderPass::paint_callback_resources].
|
||||
/// `paint_callback_resources` has the same lifetime as the Egui render pass, so it can be used to
|
||||
/// store buffers, pipelines, and other information that needs to be accessed during the render
|
||||
/// pass.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// See the [`custom3d_glow`](https://github.com/emilk/egui/blob/master/egui_demo_app/src/apps/custom3d_wgpu.rs) demo source for a detailed usage example.
|
||||
pub struct CallbackFn {
|
||||
prepare: Box<PrepareCallback>,
|
||||
paint: Box<PaintCallback>,
|
||||
}
|
||||
|
||||
type PrepareCallback = dyn Fn(&wgpu::Device, &wgpu::Queue, &mut TypeMap) + Sync + Send;
|
||||
|
||||
type PaintCallback =
|
||||
dyn for<'a, 'b> Fn(PaintCallbackInfo, &'a mut wgpu::RenderPass<'b>, &'b TypeMap) + Sync + Send;
|
||||
|
||||
impl Default for CallbackFn {
|
||||
fn default() -> Self {
|
||||
CallbackFn {
|
||||
prepare: Box::new(|_, _, _| ()),
|
||||
paint: Box::new(|_, _, _| ()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CallbackFn {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// Set the prepare callback
|
||||
pub fn prepare<F>(mut self, prepare: F) -> Self
|
||||
where
|
||||
F: Fn(&wgpu::Device, &wgpu::Queue, &mut TypeMap) + Sync + Send + 'static,
|
||||
{
|
||||
self.prepare = Box::new(prepare) as _;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the paint callback
|
||||
pub fn paint<F>(mut self, paint: F) -> Self
|
||||
where
|
||||
F: for<'a, 'b> Fn(PaintCallbackInfo, &'a mut wgpu::RenderPass<'b>, &'b TypeMap)
|
||||
+ Sync
|
||||
+ Send
|
||||
+ 'static,
|
||||
{
|
||||
self.paint = Box::new(paint) as _;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// 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],
|
||||
// Uniform buffers need to be at least 16 bytes in WebGL.
|
||||
// See https://github.com/gfx-rs/wgpu/issues/2072
|
||||
_padding: [u32; 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<SizedBuffer>,
|
||||
vertex_buffers: Vec<SizedBuffer>,
|
||||
uniform_buffer: SizedBuffer,
|
||||
uniform_bind_group: wgpu::BindGroup,
|
||||
texture_bind_group_layout: wgpu::BindGroupLayout,
|
||||
/// Map of egui texture IDs to textures and their associated bindgroups (texture view +
|
||||
/// sampler). The texture may be None if the TextureId is just a handle to a user-provided
|
||||
/// sampler.
|
||||
textures: HashMap<egui::TextureId, (Option<wgpu::Texture>, wgpu::BindGroup)>,
|
||||
next_user_texture_id: u64,
|
||||
/// Storage for use by [`egui::PaintCallback`]'s that need to store resources such as render
|
||||
/// pipelines that must have the lifetime of the renderpass.
|
||||
pub paint_callback_resources: TypeMap,
|
||||
}
|
||||
|
||||
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],
|
||||
_padding: Default::default(),
|
||||
}]),
|
||||
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
|
||||
});
|
||||
let uniform_buffer = SizedBuffer {
|
||||
buffer: uniform_buffer,
|
||||
size: std::mem::size_of::<UniformBuffer>(),
|
||||
};
|
||||
|
||||
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: &[Some(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(),
|
||||
next_user_texture_id: 0,
|
||||
paint_callback_resources: TypeMap::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// 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<wgpu::Color>,
|
||||
) {
|
||||
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: &[Some(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,
|
||||
) {
|
||||
let pixels_per_point = screen_descriptor.pixels_per_point;
|
||||
let size_in_pixels = screen_descriptor.size_in_pixels;
|
||||
|
||||
// Whether or not we need to reset the renderpass state because a paint callback has just
|
||||
// run.
|
||||
let mut needs_reset = true;
|
||||
|
||||
let mut index_buffers = self.index_buffers.iter();
|
||||
let mut vertex_buffers = self.vertex_buffers.iter();
|
||||
|
||||
for egui::ClippedPrimitive {
|
||||
clip_rect,
|
||||
primitive,
|
||||
} in paint_jobs
|
||||
{
|
||||
if needs_reset {
|
||||
rpass.set_viewport(
|
||||
0.0,
|
||||
0.0,
|
||||
size_in_pixels[0] as f32,
|
||||
size_in_pixels[1] as f32,
|
||||
0.0,
|
||||
1.0,
|
||||
);
|
||||
rpass.set_pipeline(&self.render_pipeline);
|
||||
rpass.set_bind_group(0, &self.uniform_bind_group, &[]);
|
||||
needs_reset = false;
|
||||
}
|
||||
|
||||
{
|
||||
let rect = ScissorRect::new(clip_rect, pixels_per_point, size_in_pixels);
|
||||
|
||||
if rect.width == 0 || rect.height == 0 {
|
||||
// Skip rendering with zero-sized clip areas.
|
||||
if let Primitive::Mesh(_) = primitive {
|
||||
// If this is a mesh, we need to advance the index and vertex buffer iterators
|
||||
index_buffers.next().unwrap();
|
||||
vertex_buffers.next().unwrap();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
rpass.set_scissor_rect(rect.x, rect.y, rect.width, rect.height);
|
||||
}
|
||||
|
||||
match primitive {
|
||||
Primitive::Mesh(mesh) => {
|
||||
let index_buffer = index_buffers.next().unwrap();
|
||||
let vertex_buffer = vertex_buffers.next().unwrap();
|
||||
|
||||
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(callback) => {
|
||||
let cbfn = if let Some(c) = callback.callback.downcast_ref::<CallbackFn>() {
|
||||
c
|
||||
} else {
|
||||
// We already warned in the `prepare` callback
|
||||
continue;
|
||||
};
|
||||
|
||||
if callback.rect.is_positive() {
|
||||
needs_reset = true;
|
||||
|
||||
{
|
||||
// Set the viewport rect
|
||||
// Transform callback rect to physical pixels:
|
||||
let rect_min_x = pixels_per_point * callback.rect.min.x;
|
||||
let rect_min_y = pixels_per_point * callback.rect.min.y;
|
||||
let rect_max_x = pixels_per_point * callback.rect.max.x;
|
||||
let rect_max_y = pixels_per_point * callback.rect.max.y;
|
||||
|
||||
let rect_min_x = rect_min_x.round();
|
||||
let rect_min_y = rect_min_y.round();
|
||||
let rect_max_x = rect_max_x.round();
|
||||
let rect_max_y = rect_max_y.round();
|
||||
|
||||
rpass.set_viewport(
|
||||
rect_min_x,
|
||||
rect_min_y,
|
||||
rect_max_x - rect_min_x,
|
||||
rect_max_y - rect_min_y,
|
||||
0.0,
|
||||
1.0,
|
||||
);
|
||||
}
|
||||
|
||||
(cbfn.paint)(
|
||||
PaintCallbackInfo {
|
||||
viewport: callback.rect,
|
||||
clip_rect: *clip_rect,
|
||||
pixels_per_point,
|
||||
screen_size_px: size_in_pixels,
|
||||
},
|
||||
rpass,
|
||||
&self.paint_callback_resources,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rpass.set_scissor_rect(0, 0, size_in_pixels[0], size_in_pixels[1]);
|
||||
}
|
||||
|
||||
/// 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::<Vec<_>>())
|
||||
}
|
||||
};
|
||||
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.as_ref().expect("Tried to update user 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 filter = match image_delta.filter {
|
||||
egui::TextureFilter::Nearest => wgpu::FilterMode::Nearest,
|
||||
egui::TextureFilter::Linear => wgpu::FilterMode::Linear,
|
||||
};
|
||||
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
|
||||
label: None,
|
||||
mag_filter: filter,
|
||||
min_filter: filter,
|
||||
..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, (Some(texture), bind_group));
|
||||
};
|
||||
}
|
||||
|
||||
pub fn free_texture(&mut self, id: &egui::TextureId) {
|
||||
self.textures.remove(id);
|
||||
}
|
||||
|
||||
/// Get the WGPU texture and bind group associated to a texture that has been allocated by egui.
|
||||
///
|
||||
/// This could be used by custom paint hooks to render images that have been added through with
|
||||
/// [`egui_extras::RetainedImage`](https://docs.rs/egui_extras/latest/egui_extras/image/struct.RetainedImage.html)
|
||||
/// or [`egui::Context::load_texture`].
|
||||
pub fn texture(
|
||||
&self,
|
||||
id: &egui::TextureId,
|
||||
) -> Option<&(Option<wgpu::Texture>, wgpu::BindGroup)> {
|
||||
self.textures.get(id)
|
||||
}
|
||||
|
||||
/// Registers a `wgpu::Texture` with a `egui::TextureId`.
|
||||
///
|
||||
/// This enables the application to reference the texture inside an image ui element.
|
||||
/// This effectively enables off-screen rendering inside the egui UI. Texture must have
|
||||
/// the texture format `TextureFormat::Rgba8UnormSrgb` and
|
||||
/// Texture usage `TextureUsage::SAMPLED`.
|
||||
pub fn register_native_texture(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
texture: &wgpu::TextureView,
|
||||
texture_filter: wgpu::FilterMode,
|
||||
) -> egui::TextureId {
|
||||
self.register_native_texture_with_sampler_options(
|
||||
device,
|
||||
texture,
|
||||
wgpu::SamplerDescriptor {
|
||||
label: Some(
|
||||
format!(
|
||||
"egui_user_image_{}_texture_sampler",
|
||||
self.next_user_texture_id
|
||||
)
|
||||
.as_str(),
|
||||
),
|
||||
mag_filter: texture_filter,
|
||||
min_filter: texture_filter,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/// Registers a `wgpu::Texture` with a `egui::TextureId` while also accepting custom
|
||||
/// `wgpu::SamplerDescriptor` options.
|
||||
///
|
||||
/// This allows applications to specify individual minification/magnification filters as well as
|
||||
/// custom mipmap and tiling options.
|
||||
///
|
||||
/// The `Texture` must have the format `TextureFormat::Rgba8UnormSrgb` and usage
|
||||
/// `TextureUsage::SAMPLED`. Any compare function supplied in the `SamplerDescriptor` will be
|
||||
/// ignored.
|
||||
#[allow(clippy::needless_pass_by_value)] // false positive
|
||||
pub fn register_native_texture_with_sampler_options(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
texture: &wgpu::TextureView,
|
||||
sampler_descriptor: wgpu::SamplerDescriptor<'_>,
|
||||
) -> egui::TextureId {
|
||||
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
|
||||
compare: None,
|
||||
..sampler_descriptor
|
||||
});
|
||||
|
||||
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: Some(
|
||||
format!(
|
||||
"egui_user_image_{}_texture_bind_group",
|
||||
self.next_user_texture_id
|
||||
)
|
||||
.as_str(),
|
||||
),
|
||||
layout: &self.texture_bind_group_layout,
|
||||
entries: &[
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::TextureView(texture),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 1,
|
||||
resource: wgpu::BindingResource::Sampler(&sampler),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
let id = egui::TextureId::User(self.next_user_texture_id);
|
||||
self.textures.insert(id, (None, bind_group));
|
||||
self.next_user_texture_id += 1;
|
||||
|
||||
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,
|
||||
_padding: Default::default(),
|
||||
}]),
|
||||
);
|
||||
|
||||
let mut mesh_idx = 0;
|
||||
for egui::ClippedPrimitive { primitive, .. } in paint_jobs.iter() {
|
||||
match primitive {
|
||||
Primitive::Mesh(mesh) => {
|
||||
let data: &[u8] = bytemuck::cast_slice(&mesh.indices);
|
||||
if mesh_idx < self.index_buffers.len() {
|
||||
self.update_buffer(device, queue, &BufferType::Index, mesh_idx, 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 mesh_idx < self.vertex_buffers.len() {
|
||||
self.update_buffer(device, queue, &BufferType::Vertex, mesh_idx, 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(),
|
||||
});
|
||||
}
|
||||
|
||||
mesh_idx += 1;
|
||||
}
|
||||
Primitive::Callback(callback) => {
|
||||
let cbfn = if let Some(c) = callback.callback.downcast_ref::<CallbackFn>() {
|
||||
c
|
||||
} else {
|
||||
tracing::warn!("Unknown paint callback: expected `egui_gpu::CallbackFn`");
|
||||
continue;
|
||||
};
|
||||
|
||||
(cbfn.prepare)(device, queue, &mut self.paint_callback_resources);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A Rect in physical pixel space, used for setting cliipping rectangles.
|
||||
struct ScissorRect {
|
||||
x: u32,
|
||||
y: u32,
|
||||
width: u32,
|
||||
height: u32,
|
||||
}
|
||||
|
||||
impl ScissorRect {
|
||||
fn new(clip_rect: &egui::Rect, pixels_per_point: f32, target_size: [u32; 2]) -> Self {
|
||||
// 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;
|
||||
|
||||
// Round to integer:
|
||||
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;
|
||||
|
||||
// Clamp:
|
||||
let clip_min_x = clip_min_x.clamp(0, target_size[0]);
|
||||
let clip_min_y = clip_min_y.clamp(0, target_size[1]);
|
||||
let clip_max_x = clip_max_x.clamp(clip_min_x, target_size[0]);
|
||||
let clip_max_y = clip_max_y.clamp(clip_min_y, target_size[1]);
|
||||
|
||||
let width = (clip_max_x - clip_min_x).at_least(1);
|
||||
let height = (clip_max_y - clip_min_y).at_least(1);
|
||||
|
||||
ScissorRect {
|
||||
x: clip_min_x,
|
||||
y: clip_min_y,
|
||||
width,
|
||||
height,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_pass_impl_send_sync() {
|
||||
fn assert_send_sync<T: Send + Sync>() {}
|
||||
assert_send_sync::<RenderPass>();
|
||||
}
|
||||
308
crates/egui-wgpu/src/winit.rs
Normal file
308
crates/egui-wgpu/src/winit.rs
Normal file
@@ -0,0 +1,308 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use egui::mutex::RwLock;
|
||||
use tracing::error;
|
||||
use wgpu::{Adapter, Instance, Surface};
|
||||
|
||||
use crate::renderer;
|
||||
|
||||
/// Access to the render state for egui, which can be useful in combination with
|
||||
/// [`egui::PaintCallback`]s for custom rendering using WGPU.
|
||||
#[derive(Clone)]
|
||||
pub struct RenderState {
|
||||
pub device: Arc<wgpu::Device>,
|
||||
pub queue: Arc<wgpu::Queue>,
|
||||
pub target_format: wgpu::TextureFormat,
|
||||
pub egui_rpass: Arc<RwLock<renderer::RenderPass>>,
|
||||
}
|
||||
|
||||
struct SurfaceState {
|
||||
surface: Surface,
|
||||
width: u32,
|
||||
height: u32,
|
||||
}
|
||||
|
||||
/// Everything you need to paint egui with [`wgpu`] on [`winit`].
|
||||
///
|
||||
/// Alternatively you can use [`crate::renderer`] directly.
|
||||
pub struct Painter<'a> {
|
||||
power_preference: wgpu::PowerPreference,
|
||||
device_descriptor: wgpu::DeviceDescriptor<'a>,
|
||||
present_mode: wgpu::PresentMode,
|
||||
msaa_samples: u32,
|
||||
|
||||
instance: Instance,
|
||||
adapter: Option<Adapter>,
|
||||
render_state: Option<RenderState>,
|
||||
surface_state: Option<SurfaceState>,
|
||||
}
|
||||
|
||||
impl<'a> Painter<'a> {
|
||||
/// Manages [`wgpu`] state, including surface state, required to render egui.
|
||||
///
|
||||
/// Only the [`wgpu::Instance`] is initialized here. Device selection and the initialization
|
||||
/// of render + surface state is deferred until the painter is given its first window target
|
||||
/// via [`set_window()`](Self::set_window). (Ensuring that a device that's compatible with the
|
||||
/// native window is chosen)
|
||||
///
|
||||
/// Before calling [`paint_and_update_textures()`](Self::paint_and_update_textures) a
|
||||
/// [`wgpu::Surface`] must be initialized (and corresponding render state) by calling
|
||||
/// [`set_window()`](Self::set_window) once you have
|
||||
/// a [`winit::window::Window`] with a valid `.raw_window_handle()`
|
||||
/// associated.
|
||||
pub fn new(
|
||||
backends: wgpu::Backends,
|
||||
power_preference: wgpu::PowerPreference,
|
||||
device_descriptor: wgpu::DeviceDescriptor<'a>,
|
||||
present_mode: wgpu::PresentMode,
|
||||
msaa_samples: u32,
|
||||
) -> Self {
|
||||
let instance = wgpu::Instance::new(backends);
|
||||
|
||||
Self {
|
||||
power_preference,
|
||||
device_descriptor,
|
||||
present_mode,
|
||||
msaa_samples,
|
||||
|
||||
instance,
|
||||
adapter: None,
|
||||
render_state: None,
|
||||
surface_state: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the [`RenderState`].
|
||||
///
|
||||
/// Will return [`None`] if the render state has not been initialized yet.
|
||||
pub fn render_state(&self) -> Option<RenderState> {
|
||||
self.render_state.as_ref().cloned()
|
||||
}
|
||||
|
||||
async fn init_render_state(
|
||||
&self,
|
||||
adapter: &Adapter,
|
||||
target_format: wgpu::TextureFormat,
|
||||
) -> RenderState {
|
||||
let (device, queue) =
|
||||
pollster::block_on(adapter.request_device(&self.device_descriptor, None)).unwrap();
|
||||
|
||||
let rpass = renderer::RenderPass::new(&device, target_format, self.msaa_samples);
|
||||
|
||||
RenderState {
|
||||
device: Arc::new(device),
|
||||
queue: Arc::new(queue),
|
||||
target_format,
|
||||
egui_rpass: Arc::new(RwLock::new(rpass)),
|
||||
}
|
||||
}
|
||||
|
||||
// We want to defer the initialization of our render state until we have a surface
|
||||
// so we can take its format into account.
|
||||
//
|
||||
// After we've initialized our render state once though we expect all future surfaces
|
||||
// will have the same format and so this render state will remain valid.
|
||||
fn ensure_render_state_for_surface(&mut self, surface: &Surface) {
|
||||
self.adapter.get_or_insert_with(|| {
|
||||
pollster::block_on(self.instance.request_adapter(&wgpu::RequestAdapterOptions {
|
||||
power_preference: self.power_preference,
|
||||
compatible_surface: Some(surface),
|
||||
force_fallback_adapter: false,
|
||||
}))
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
if self.render_state.is_none() {
|
||||
let adapter = self.adapter.as_ref().unwrap();
|
||||
let swapchain_format = surface.get_supported_formats(adapter)[0];
|
||||
|
||||
let rs = pollster::block_on(self.init_render_state(adapter, swapchain_format));
|
||||
self.render_state = Some(rs);
|
||||
}
|
||||
}
|
||||
|
||||
fn configure_surface(&mut self, width_in_pixels: u32, height_in_pixels: u32) {
|
||||
let render_state = self
|
||||
.render_state
|
||||
.as_ref()
|
||||
.expect("Render state should exist before surface configuration");
|
||||
let format = render_state.target_format;
|
||||
|
||||
let config = wgpu::SurfaceConfiguration {
|
||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
|
||||
format,
|
||||
width: width_in_pixels,
|
||||
height: height_in_pixels,
|
||||
present_mode: self.present_mode,
|
||||
};
|
||||
|
||||
let surface_state = self
|
||||
.surface_state
|
||||
.as_mut()
|
||||
.expect("Surface state should exist before surface configuration");
|
||||
surface_state
|
||||
.surface
|
||||
.configure(&render_state.device, &config);
|
||||
surface_state.width = width_in_pixels;
|
||||
surface_state.height = height_in_pixels;
|
||||
}
|
||||
|
||||
/// Updates (or clears) the [`winit::window::Window`] associated with the [`Painter`]
|
||||
///
|
||||
/// This creates a [`wgpu::Surface`] for the given Window (as well as initializing render
|
||||
/// state if needed) that is used for egui rendering.
|
||||
///
|
||||
/// This must be called before trying to render via
|
||||
/// [`paint_and_update_textures`](Self::paint_and_update_textures)
|
||||
///
|
||||
/// # Portability
|
||||
///
|
||||
/// _In particular it's important to note that on Android a it's only possible to create
|
||||
/// a window surface between `Resumed` and `Paused` lifecycle events, and Winit will panic on
|
||||
/// attempts to query the raw window handle while paused._
|
||||
///
|
||||
/// On Android [`set_window`](Self::set_window) should be called with `Some(window)` for each
|
||||
/// `Resumed` event and `None` for each `Paused` event. Currently, on all other platforms
|
||||
/// [`set_window`](Self::set_window) may be called with `Some(window)` as soon as you have a
|
||||
/// valid [`winit::window::Window`].
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The raw Window handle associated with the given `window` must be a valid object to create a
|
||||
/// surface upon and must remain valid for the lifetime of the created surface. (The surface may
|
||||
/// be cleared by passing `None`).
|
||||
pub unsafe fn set_window(&mut self, window: Option<&winit::window::Window>) {
|
||||
match window {
|
||||
Some(window) => {
|
||||
let surface = self.instance.create_surface(&window);
|
||||
|
||||
self.ensure_render_state_for_surface(&surface);
|
||||
|
||||
let size = window.inner_size();
|
||||
let width = size.width;
|
||||
let height = size.height;
|
||||
self.surface_state = Some(SurfaceState {
|
||||
surface,
|
||||
width,
|
||||
height,
|
||||
});
|
||||
self.configure_surface(width, height);
|
||||
}
|
||||
None => {
|
||||
self.surface_state = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the maximum texture dimension supported if known
|
||||
///
|
||||
/// This API will only return a known dimension after `set_window()` has been called
|
||||
/// at least once, since the underlying device and render state are initialized lazily
|
||||
/// once we have a window (that may determine the choice of adapter/device).
|
||||
pub fn max_texture_side(&self) -> Option<usize> {
|
||||
self.render_state
|
||||
.as_ref()
|
||||
.map(|rs| rs.device.limits().max_texture_dimension_2d as usize)
|
||||
}
|
||||
|
||||
pub fn on_window_resized(&mut self, width_in_pixels: u32, height_in_pixels: u32) {
|
||||
if self.surface_state.is_some() {
|
||||
self.configure_surface(width_in_pixels, height_in_pixels);
|
||||
} else {
|
||||
error!("Ignoring window resize notification with no surface created via Painter::set_window()");
|
||||
}
|
||||
}
|
||||
|
||||
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 render_state = match self.render_state.as_mut() {
|
||||
Some(rs) => rs,
|
||||
None => return,
|
||||
};
|
||||
let surface_state = match self.surface_state.as_ref() {
|
||||
Some(rs) => rs,
|
||||
None => return,
|
||||
};
|
||||
|
||||
let output_frame = match surface_state.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 =
|
||||
render_state
|
||||
.device
|
||||
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||
label: Some("encoder"),
|
||||
});
|
||||
|
||||
// Upload all resources for the GPU.
|
||||
let screen_descriptor = renderer::ScreenDescriptor {
|
||||
size_in_pixels: [surface_state.width, surface_state.height],
|
||||
pixels_per_point,
|
||||
};
|
||||
|
||||
{
|
||||
let mut rpass = render_state.egui_rpass.write();
|
||||
for (id, image_delta) in &textures_delta.set {
|
||||
rpass.update_texture(&render_state.device, &render_state.queue, *id, image_delta);
|
||||
}
|
||||
|
||||
rpass.update_buffers(
|
||||
&render_state.device,
|
||||
&render_state.queue,
|
||||
clipped_primitives,
|
||||
&screen_descriptor,
|
||||
);
|
||||
}
|
||||
|
||||
// Record all render passes.
|
||||
render_state.egui_rpass.read().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,
|
||||
}),
|
||||
);
|
||||
|
||||
{
|
||||
let mut rpass = render_state.egui_rpass.write();
|
||||
for id in &textures_delta.free {
|
||||
rpass.free_texture(id);
|
||||
}
|
||||
}
|
||||
|
||||
// Submit the commands.
|
||||
render_state.queue.submit(std::iter::once(encoder.finish()));
|
||||
|
||||
// Redraw egui
|
||||
output_frame.present();
|
||||
}
|
||||
|
||||
#[allow(clippy::unused_self)]
|
||||
pub fn destroy(&mut self) {
|
||||
// TODO(emilk): something here?
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user