diff --git a/code/showcase/compute/src/main.rs b/code/showcase/compute/src/main.rs index 4ed7e4e7..8545eae9 100644 --- a/code/showcase/compute/src/main.rs +++ b/code/showcase/compute/src/main.rs @@ -15,7 +15,7 @@ mod pipeline; // NEW! mod texture; use model::{DrawLight, DrawModel, Vertex}; -use pipeline::{create_render_pipeline}; +use pipeline::create_render_pipeline; const NUM_INSTANCES_PER_ROW: u32 = 10; @@ -50,7 +50,8 @@ impl Instance { fn to_raw(&self) -> InstanceRaw { InstanceRaw { model: (cgmath::Matrix4::from_translation(self.position) - * cgmath::Matrix4::from(self.rotation)).into(), + * cgmath::Matrix4::from(self.rotation)) + .into(), } } } @@ -139,7 +140,6 @@ struct State { mouse_pressed: bool, } - impl State { async fn new(window: &Window) -> Self { let size = window.inner_size(); @@ -270,28 +270,24 @@ impl State { let uniform_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - entries: &[ - wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT, - ty: wgpu::BindingType::UniformBuffer { - dynamic: false, - min_binding_size: None, - }, - count: None, + entries: &[wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT, + ty: wgpu::BindingType::UniformBuffer { + dynamic: false, + min_binding_size: None, }, - ], + count: None, + }], label: Some("uniform_bind_group_layout"), }); let uniform_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { layout: &uniform_bind_group_layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: wgpu::BindingResource::Buffer(uniform_buffer.slice(..)), - }, - ], + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::Buffer(uniform_buffer.slice(..)), + }], label: Some("uniform_bind_group"), }); @@ -299,13 +295,14 @@ impl State { let model_loader = model::ModelLoader::new(&device); // UPDATED! - let obj_model = model_loader.load( - &device, - &queue, - &texture_bind_group_layout, - res_dir.join("cube.obj"), - ) - .unwrap(); + let obj_model = model_loader + .load( + &device, + &queue, + &texture_bind_group_layout, + res_dir.join("cube.obj"), + ) + .unwrap(); let light = Light { position: [2.0, 2.0, 2.0], @@ -503,25 +500,24 @@ impl State { let old_position: cgmath::Vector3<_> = self.light.position.into(); self.light.position = (cgmath::Quaternion::from_axis_angle((0.0, 1.0, 0.0).into(), cgmath::Deg(1.0)) - * old_position).into(); + * old_position) + .into(); self.queue .write_buffer(&self.light_buffer, 0, bytemuck::cast_slice(&[self.light])); } fn render(&mut self) { - let frame = self - .swap_chain - .get_current_frame(); - + let frame = self.swap_chain.get_current_frame(); + match frame { Ok(frame) => { let frame = frame.output; - let mut encoder = self - .device - .create_command_encoder(&wgpu::CommandEncoderDescriptor { - label: Some("Render Encoder"), - }); - + let mut encoder = + self.device + .create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: Some("Render Encoder"), + }); + { let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor { @@ -537,16 +533,18 @@ impl State { store: true, }, }], - depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachmentDescriptor { - attachment: &self.depth_texture.view, - depth_ops: Some(wgpu::Operations { - load: wgpu::LoadOp::Clear(1.0), - store: true, - }), - stencil_ops: None, - }), + depth_stencil_attachment: Some( + wgpu::RenderPassDepthStencilAttachmentDescriptor { + attachment: &self.depth_texture.view, + depth_ops: Some(wgpu::Operations { + load: wgpu::LoadOp::Clear(1.0), + store: true, + }), + stencil_ops: None, + }, + ), }); - + render_pass.set_vertex_buffer(1, self.instance_buffer.slice(..)); render_pass.set_pipeline(&self.light_render_pipeline); render_pass.draw_light_model( @@ -554,7 +552,7 @@ impl State { &self.uniform_bind_group, &self.light_bind_group, ); - + render_pass.set_pipeline(&self.render_pipeline); render_pass.draw_model_instanced( &self.obj_model, @@ -570,7 +568,6 @@ impl State { } } } - } fn main() { diff --git a/code/showcase/compute/src/model.rs b/code/showcase/compute/src/model.rs index f4ac224f..cc8c4d97 100644 --- a/code/showcase/compute/src/model.rs +++ b/code/showcase/compute/src/model.rs @@ -4,8 +4,8 @@ use std::ops::Range; use std::path::Path; use wgpu::util::DeviceExt; -use crate::texture; use crate::pipeline; +use crate::texture; pub trait Vertex { fn desc<'a>() -> wgpu::VertexBufferDescriptor<'a>; @@ -21,7 +21,6 @@ pub struct ModelVertex { bitangent: [f32; 3], } - impl Vertex for ModelVertex { fn desc<'a>() -> wgpu::VertexBufferDescriptor<'a> { use std::mem; @@ -182,7 +181,7 @@ impl pipeline::Bindable for BitangentComputeBinding { min_binding_size: None, }, count: None, - } + }, ] } @@ -207,7 +206,7 @@ impl pipeline::Bindable for BitangentComputeBinding { wgpu::BindGroupEntry { binding: 3, resource: wgpu::BindingResource::Buffer(self.info_buffer.slice(..)), - } + }, ] } } @@ -224,12 +223,16 @@ pub struct ModelLoader { // UPDATED! impl ModelLoader { - // NEW! pub fn new(device: &wgpu::Device) -> Self { let binder = pipeline::Binder::new(device, Some("ModelLoader Binder")); let shader_src = wgpu::include_spirv!("model_load.comp.spv"); - let pipeline = pipeline::create_compute_pipeline(device, &[&binder.layout], shader_src, Some("ModelLoader ComputePipeline")); + let pipeline = pipeline::create_compute_pipeline( + device, + &[&binder.layout], + shader_src, + Some("ModelLoader ComputePipeline"), + ); Self { binder, pipeline } } @@ -303,18 +306,20 @@ impl ModelLoader { }) .collect::>(); - let src_vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some(&format!("{:?} Vertex Buffer", m.name)), - contents: bytemuck::cast_slice(&vertices), - // UPDATED! - usage: wgpu::BufferUsage::STORAGE, - }); - let dst_vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some(&format!("{:?} Vertex Buffer", m.name)), - contents: bytemuck::cast_slice(&vertices), - // UPDATED! - usage: wgpu::BufferUsage::VERTEX | wgpu::BufferUsage::STORAGE, - }); + let src_vertex_buffer = + device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some(&format!("{:?} Vertex Buffer", m.name)), + contents: bytemuck::cast_slice(&vertices), + // UPDATED! + usage: wgpu::BufferUsage::STORAGE, + }); + let dst_vertex_buffer = + device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some(&format!("{:?} Vertex Buffer", m.name)), + contents: bytemuck::cast_slice(&vertices), + // UPDATED! + usage: wgpu::BufferUsage::VERTEX | wgpu::BufferUsage::STORAGE, + }); let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some(&format!("{:?} Index Buffer", m.name)), contents: bytemuck::cast_slice(&m.mesh.indices), @@ -342,11 +347,9 @@ impl ModelLoader { }; // Calculate the tangents and bitangents - let calc_bind_group = self.binder.create_bind_group( - &binding, - device, - Some("Mesh BindGroup") - ); + let calc_bind_group = + self.binder + .create_bind_group(&binding, device, Some("Mesh BindGroup")); let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("Tangent and Bitangent Calc"), }); diff --git a/code/showcase/compute/src/pipeline.rs b/code/showcase/compute/src/pipeline.rs index c487eb74..cbf030d1 100644 --- a/code/showcase/compute/src/pipeline.rs +++ b/code/showcase/compute/src/pipeline.rs @@ -20,7 +20,12 @@ impl Binder { } } - pub fn create_bind_group(&self, data: &T, device: &wgpu::Device, label: Option<&str>) -> wgpu::BindGroup { + pub fn create_bind_group( + &self, + data: &T, + device: &wgpu::Device, + label: Option<&str>, + ) -> wgpu::BindGroup { device.create_bind_group(&wgpu::BindGroupDescriptor { label, layout: &self.layout, @@ -84,10 +89,10 @@ pub fn create_render_pipeline( } pub fn create_compute_pipeline( - device: &wgpu::Device, - bind_group_layouts: &[&wgpu::BindGroupLayout], - shader_src: wgpu::ShaderModuleSource, - label: Option<&str> + device: &wgpu::Device, + bind_group_layouts: &[&wgpu::BindGroupLayout], + shader_src: wgpu::ShaderModuleSource, + label: Option<&str>, ) -> wgpu::ComputePipeline { let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label, @@ -99,8 +104,8 @@ pub fn create_compute_pipeline( layout: Some(&layout), compute_stage: wgpu::ProgrammableStageDescriptor { module: &device.create_shader_module(shader_src), - entry_point: "main" + entry_point: "main", }, }); pipeline -} \ No newline at end of file +} diff --git a/code/showcase/framework/src/lib.rs b/code/showcase/framework/src/lib.rs index 3b67cead..ddc20c67 100644 --- a/code/showcase/framework/src/lib.rs +++ b/code/showcase/framework/src/lib.rs @@ -4,16 +4,16 @@ mod light; mod model; mod pipeline; pub mod prelude; -mod texture; mod shader_canvas; +mod texture; pub use buffer::*; pub use camera::*; pub use light::*; pub use model::*; pub use pipeline::*; -pub use texture::*; pub use shader_canvas::*; +pub use texture::*; use anyhow::*; use cgmath::*; diff --git a/code/showcase/framework/src/shader_canvas.rs b/code/showcase/framework/src/shader_canvas.rs index 87e48a5e..8f362c1c 100644 --- a/code/showcase/framework/src/shader_canvas.rs +++ b/code/showcase/framework/src/shader_canvas.rs @@ -7,7 +7,7 @@ use std::time::Instant; use thiserror::Error; -use wgpu::util::{DeviceExt, BufferInitDescriptor}; +use wgpu::util::{BufferInitDescriptor, DeviceExt}; #[repr(C)] #[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] @@ -29,7 +29,6 @@ pub enum ShaderBuildError { InvalidDisplayFormat, } - pub struct ShaderCanvas { pipeline: wgpu::RenderPipeline, start_time: Option, @@ -51,8 +50,8 @@ impl ShaderCanvas { } pub fn render( - &mut self, - queue: &wgpu::Queue, + &mut self, + queue: &wgpu::Queue, encoder: &mut wgpu::CommandEncoder, frame: &wgpu::TextureView, width: f32, @@ -65,7 +64,7 @@ impl ShaderCanvas { let t = current_time; self.start_time = Some(t); t - }, + } }; let last_time = self.last_time.unwrap_or(current_time); self.last_time = Some(current_time); @@ -73,19 +72,21 @@ impl ShaderCanvas { self.simulation_data.delta_time = (current_time - last_time).as_secs_f32(); self.simulation_data.canvas_size[0] = width; self.simulation_data.canvas_size[1] = height; - queue.write_buffer(&self.simulation_data_buffer, 0, bytemuck::cast_slice(&[self.simulation_data])); + queue.write_buffer( + &self.simulation_data_buffer, + 0, + bytemuck::cast_slice(&[self.simulation_data]), + ); let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - color_attachments: &[ - wgpu::RenderPassColorAttachmentDescriptor { - attachment: frame, - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Load, - store: true, - } + color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor { + attachment: frame, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Load, + store: true, }, - ], + }], depth_stencil_attachment: None, }); pass.set_bind_group(0, &self.simulation_bind_group, &[]); @@ -141,9 +142,17 @@ impl<'a> ShaderCanvasBuilder<'a> { } pub fn build(&mut self, device: &wgpu::Device) -> Result { - let display_format = self.display_format.ok_or(ShaderBuildError::InvalidDisplayFormat)?; - let frag_code = self.frag_code.take().ok_or(ShaderBuildError::InvalidFragmentShader)?; - let vert_code = self.vert_code.take().ok_or(ShaderBuildError::InvalidVertexShader)?; + let display_format = self + .display_format + .ok_or(ShaderBuildError::InvalidDisplayFormat)?; + let frag_code = self + .frag_code + .take() + .ok_or(ShaderBuildError::InvalidFragmentShader)?; + let vert_code = self + .vert_code + .take() + .ok_or(ShaderBuildError::InvalidVertexShader)?; let simulation_data = SimulationData { time: 0.0, @@ -158,30 +167,29 @@ impl<'a> ShaderCanvasBuilder<'a> { usage: wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST, }); - let simulation_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - label: self.label, - entries: &[ - // SimulationData - wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStage::FRAGMENT, - count: None, - ty: wgpu::BindingType::UniformBuffer { - dynamic: false, - min_binding_size: None, - } - } - ], - }); + let simulation_bind_group_layout = + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: self.label, + entries: &[ + // SimulationData + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStage::FRAGMENT, + count: None, + ty: wgpu::BindingType::UniformBuffer { + dynamic: false, + min_binding_size: None, + }, + }, + ], + }); let simulation_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { layout: &simulation_bind_group_layout, label: self.label, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: wgpu::BindingResource::Buffer(simulation_data_buffer.slice(..)) - } - ] + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::Buffer(simulation_data_buffer.slice(..)), + }], }); let vert_module = device.create_shader_module(vert_code); diff --git a/code/showcase/imgui-demo/src/main.rs b/code/showcase/imgui-demo/src/main.rs index 05c69138..cc3df7db 100644 --- a/code/showcase/imgui-demo/src/main.rs +++ b/code/showcase/imgui-demo/src/main.rs @@ -1,7 +1,7 @@ use anyhow::*; +use futures::executor::block_on; use imgui::*; use imgui_wgpu::{Renderer, RendererConfig}; -use futures::executor::block_on; use std::time::Duration; struct ImguiDemo { @@ -21,7 +21,7 @@ impl framework::Demo for ImguiDemo { let mut imgui = imgui::Context::create(); let mut platform = imgui_winit_support::WinitPlatform::init(&mut imgui); platform.attach_window( - imgui.io_mut(), + imgui.io_mut(), &display.window, imgui_winit_support::HiDpiMode::Default, ); @@ -37,15 +37,21 @@ impl framework::Demo for ImguiDemo { size_pixels: font_size, ..Default::default() }), - }]); + }]); let renderer_config = RendererConfig { texture_format: display.sc_desc.format, ..Default::default() }; let renderer = Renderer::new(&mut imgui, &display.device, &display.queue, renderer_config); - - Ok(Self { canvas, imgui, platform, renderer, last_cursor: None }) + + Ok(Self { + canvas, + imgui, + platform, + renderer, + last_cursor: None, + }) } fn process_mouse(&mut self, dx: f64, dy: f64) { @@ -89,15 +95,16 @@ impl framework::Demo for ImguiDemo { eprintln!("Error getting frame: {:?}", e); return; } - }.output; + } + .output; // Render the scene self.canvas.render( - &display.queue, - &mut encoder, - &output.view, - display.sc_desc.width as f32, - display.sc_desc.height as f32 + &display.queue, + &mut encoder, + &output.view, + display.sc_desc.width as f32, + display.sc_desc.height as f32, ); // Render the UI diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index 4c2ee23e..5125b7f6 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -52,6 +52,7 @@ module.exports = { '/showcase/pong/', '/showcase/compute/', '/showcase/alignment/', + '/showcase/imgui-demo/', ] }, '/news/' diff --git a/docs/README.md b/docs/README.md index f7e28cd7..6a24d87f 100644 --- a/docs/README.md +++ b/docs/README.md @@ -10,4 +10,10 @@ Wgpu actually has C bindings to allow you to write C/C++ code with it, as well a You should be fairly familiar with Rust before using this tutorial as I won't go into much detail on Rust syntax. If you're not super comfortable with Rust you can review the [Rust tutorial](https://www.rust-lang.org/learn). You should also be familiar about [Cargo](https://doc.rust-lang.org/cargo/). -I'm using this project as a way to learn wgpu myself, so I might miss some important details, or explain things wrong. I'm always open to constructive feedback. That being said, let's get started! +I'm using this project as a way to learn wgpu myself, so I might miss some important details, or explain things wrong. I'm always open to constructive feedback. + +## Contribution and Support + +* I accept pull requests for fixing issues with this tutorial such as typos, incorrect information, and other inconsistencies. +* Due to wgpu's rapidly changing api, I'm not accepting any new pull requests for showcase demos. +* If you want to support me directly, check out my [patreon](https://www.patreon.com/sotrh)! diff --git a/docs/news/README.md b/docs/news/README.md index 60392310..dc646ffc 100644 --- a/docs/news/README.md +++ b/docs/news/README.md @@ -1,5 +1,23 @@ # News +## November 2020 Cleanup, Content Freeze, and Patreon + +School is starting to ramp up, so I haven't had as much time to work on the site as I would like to. Because of that there were some issues piling up. I decided to tackle a bunch of them in one go. Here's a snapshot of what I did: + +* The tutorial now handles `SwapChainError` properly +* I'm now using bytemuck's derive feature on all buffer data structs. +* The [instancing tutorial](/beginner/tutorial7-instancing) now uses vertex buffers instead of storage buffers. +* `build.rs` now updates when individual shaders are changed, not whenever `/src` is changed. +* Had some help from Github user @kanerogers to clean up the [texturing tutorial](/beginner/tutorial5-textures). +* I made a [compute pipeline showcase](/showcase/compute) that computes the tangent and bitangent for each vertex in a model. +* I made a [imgui showcase](/showcase/imgui-demo). It's very basic, but it should be a good starting point. + +Now in the headline I mentioned a "Content Freeze". Wgpu is still a moving target. The migration from `0.4` to `0.5` was lot of work. The same goes for `0.5` to `0.6`. I'm expected the next migration to be just as much work. As such, I won't be added much content until the API becomes a bit more stable. That being said, I still plan on resolving any issues with the content. + +One more thing. This is actually quite awkward for me (especially since I'll be slowing down development), but I've started a [patreon](https://www.patreon.com/sotrh). My job doesn't give me a ton of hours, so things are a bit tight. You are by no means obligated to donate, but I would appreciate it. + +You can find out more about contributing to this project on the [introduction page](/#contribution-and-support) + ## 0.6 This took me way too long. The changes weren't difficult, but I had to do a lot of copy pasting. The main changes are using `queue.write_buffer()` and `queue.write_texture()` everywhere. I won't get into the nitty gritty, but you can checkout the [pull request](https://github.com/sotrh/learn-wgpu/pull/90) if you're interested. diff --git a/docs/showcase/imgui-demo/README.md b/docs/showcase/imgui-demo/README.md new file mode 100644 index 00000000..c37def2e --- /dev/null +++ b/docs/showcase/imgui-demo/README.md @@ -0,0 +1,135 @@ +# Basic Imgui Demo + +This is not an in depth guid on how to use Imgui. But here are some of the basics you'll need to get started. We'll need to import [imgui-rs](https://docs.rs/imgui), [imgui-wgpu](https://docs.rs/imgui-wgpu), and [imgui-winit-support](https://docs.rs/imgui-winit-support). + +```toml +imgui = "0.6" +imgui-wgpu = "0.12" +imgui-winit-support = "0.6" +``` + +
+ +I've excluded some dependencies for brevity. I'm also using the [framework crate](https://github.com/sotrh/learn-wgpu/tree/master/code/showcase/framework) I've created for showcases to simplify setup. If you see a `display` variable in code, it's from the `framework`. `Display` is where the the `device`, `queue`, `swap_chain`, and other basic wgpu objects are stored. + +
+ +We need to setup imgui and a `WinitPlatform` to get started. Do this after creating you're `winit::Window`. + +```rust +let mut imgui = imgui::Context::create(); +let mut platform = imgui_winit_support::WinitPlatform::init(&mut imgui); +platform.attach_window( + imgui.io_mut(), + &display.window, + imgui_winit_support::HiDpiMode::Default, +); +imgui.set_ini_filename(None); +``` + +Now we need to configure the default font. We'll using the window's scale factor to keep things from being too big or small. + +```rust +let hidpi_factor = display.window.scale_factor(); +let font_size = (13.0 * hidpi_factor) as f32; +imgui.io_mut().font_global_scale = (1.0 / hidpi_factor) as f32; +imgui.fonts().add_font(&[FontSource::DefaultFontData { + config: Some(imgui::FontConfig { + oversample_h: 1, + pixel_snap_h: true, + size_pixels: font_size, + ..Default::default() + }), +}]); +``` + +Then you need to create the renderer. We need to use the swap chains `TextureFormat` in order for things to work properly. + +```rust +let renderer_config = RendererConfig { + texture_format: display.sc_desc.format, + ..Default::default() +}; +let renderer = Renderer::new(&mut imgui, &display.device, &display.queue, renderer_config); +``` + +When we update the scene, we'll need to update imgui. + +```rust +self.imgui.io_mut().update_delta_time(dt); // dt: std::time::Duration +``` + +I'm not an expert with imgui, so I'll let the code speak for itself. + +```rust +// Build the UI +self.platform + .prepare_frame(self.imgui.io_mut(), &display.window) + .expect("Failed to prepare frame!"); +let ui = self.imgui.frame(); +{ + let window = imgui::Window::new(im_str!("Hello Imgui from WGPU!")); + window + .size([300.0, 100.0], Condition::FirstUseEver) + .build(&ui, || { + ui.text(im_str!("Hello world!")); + ui.text(im_str!("This is a demo of imgui-rs using imgui-wgpu!")); + ui.separator(); + let mouse_pos = ui.io().mouse_pos; + ui.text(im_str!( + "Mouse Position: ({:.1}, {:.1})", + mouse_pos[0], + mouse_pos[1], + )); + }); +} + +// Prepare to render +let mut encoder = display.device.create_command_encoder(&Default::default()); +let output = match display.swap_chain.get_current_frame() { + Ok(frame) => frame, + Err(e) => { + eprintln!("Error getting frame: {:?}", e); + return; + } +}.output; + +// Render the scene +self.canvas.render( + &display.queue, + &mut encoder, + &output.view, + display.sc_desc.width as f32, + display.sc_desc.height as f32 +); + +// Render the UI +if self.last_cursor != ui.mouse_cursor() { + self.last_cursor = ui.mouse_cursor(); + self.platform.prepare_render(&ui, &display.window); +} + +let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor { + attachment: &output.view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Load, + store: true, + }, + }], + depth_stencil_attachment: None, +}); +self.renderer + .render(ui.render(), &display.queue, &display.device, &mut pass) + .expect("Failed to render UI!"); +drop(pass); + +display.queue.submit(Some(encoder.finish())); +``` + +That's all there is to it. Here's a picture of the results! + +![./screenshot.png](./screenshot.png) + + \ No newline at end of file diff --git a/docs/showcase/imgui-demo/screenshot.png b/docs/showcase/imgui-demo/screenshot.png new file mode 100644 index 00000000..d0dff61d Binary files /dev/null and b/docs/showcase/imgui-demo/screenshot.png differ