wgpu_child_window.rs 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. //! Demonstrate how to use dioxus as a child window for use in alternative renderers like wgpu.
  2. //!
  3. //! The code here is borrowed from wry's example:
  4. //! https://github.com/tauri-apps/wry/blob/dev/examples/wgpu.rs
  5. //!
  6. //! To use this feature set `with_as_child_window()` on your desktop config which will then let you
  7. use dioxus::desktop::{
  8. tao::{event::Event as WryEvent, window::WindowBuilder},
  9. use_wry_event_handler, window, Config, DesktopContext,
  10. };
  11. use dioxus::prelude::*;
  12. fn main() {
  13. let config = Config::new()
  14. .with_window(WindowBuilder::new().with_transparent(true))
  15. .with_as_child_window();
  16. dioxus::LaunchBuilder::desktop()
  17. .with_cfg(config)
  18. .launch(app);
  19. }
  20. fn app() -> Element {
  21. let graphics_resources = use_resource(move || async {
  22. GraphicsContextAsyncBuilder {
  23. desktop: window(),
  24. resources_builder: |ctx| Box::pin(GraphicsResources::new(ctx)),
  25. }
  26. .build()
  27. .await
  28. });
  29. // on first render request a redraw
  30. use_effect(|| {
  31. window().window.request_redraw();
  32. });
  33. use_wry_event_handler(move |event, _| {
  34. use dioxus::desktop::tao::event::WindowEvent;
  35. if let WryEvent::RedrawRequested(_id) = event {
  36. let resources = graphics_resources.read();
  37. if let Some(resources) = resources.as_ref() {
  38. resources.with_resources(|resources| resources.render());
  39. }
  40. }
  41. if let WryEvent::WindowEvent {
  42. event: WindowEvent::Resized(new_size),
  43. ..
  44. } = event
  45. {
  46. let ctx = graphics_resources.value();
  47. if let Some(ctx_ref) = ctx.as_ref() {
  48. ctx_ref.with_resources(|srcs| {
  49. let mut cfg = srcs.config.clone();
  50. cfg.width = new_size.width;
  51. cfg.height = new_size.height;
  52. srcs.surface.configure(&srcs.device, &cfg);
  53. });
  54. }
  55. window().window.request_redraw();
  56. }
  57. });
  58. rsx! {
  59. div {
  60. color: "blue",
  61. width: "100vw",
  62. height: "100vh",
  63. display: "flex",
  64. justify_content: "center",
  65. align_items: "center",
  66. font_size: "20px",
  67. div { "text overlaid on a wgpu surface!" }
  68. }
  69. }
  70. }
  71. /// This borrows from the `window` which is contained within an `Arc` so we need to wrap it in a self-borrowing struct
  72. /// to be able to borrow the window for the wgpu::Surface
  73. #[ouroboros::self_referencing]
  74. struct GraphicsContext {
  75. desktop: DesktopContext,
  76. #[borrows(desktop)]
  77. #[not_covariant]
  78. resources: GraphicsResources<'this>,
  79. }
  80. struct GraphicsResources<'a> {
  81. surface: wgpu::Surface<'a>,
  82. device: wgpu::Device,
  83. pipeline: wgpu::RenderPipeline,
  84. queue: wgpu::Queue,
  85. config: wgpu::SurfaceConfiguration,
  86. }
  87. impl<'a> GraphicsResources<'a> {
  88. async fn new(context: &'a DesktopContext) -> Self {
  89. let window = &context.window;
  90. let size = window.inner_size();
  91. let instance = wgpu::Instance::default();
  92. let surface: wgpu::Surface<'a> = instance.create_surface(window).unwrap();
  93. let adapter = instance
  94. .request_adapter(&wgpu::RequestAdapterOptions {
  95. power_preference: wgpu::PowerPreference::default(),
  96. force_fallback_adapter: false,
  97. // Request an adapter which can render to our surface
  98. compatible_surface: Some(&surface),
  99. })
  100. .await
  101. .expect("Failed to find an appropriate adapter");
  102. // Create the logical device and command queue
  103. let (device, queue) = adapter
  104. .request_device(
  105. &wgpu::DeviceDescriptor {
  106. label: None,
  107. required_features: wgpu::Features::empty(),
  108. // Make sure we use the texture resolution limits from the adapter, so we can support images the size of the swapchain.
  109. required_limits: wgpu::Limits::downlevel_webgl2_defaults()
  110. .using_resolution(adapter.limits()),
  111. },
  112. None,
  113. )
  114. .await
  115. .expect("Failed to create device");
  116. // Load the shaders from disk
  117. let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
  118. label: None,
  119. source: wgpu::ShaderSource::Wgsl(
  120. r#"
  121. @vertex
  122. fn vs_main(@builtin(vertex_index) in_vertex_index: u32) -> @builtin(position) vec4<f32> {
  123. let x = f32(i32(in_vertex_index) - 1);
  124. let y = f32(i32(in_vertex_index & 1u) * 2 - 1);
  125. return vec4<f32>(x, y, 0.0, 1.0);
  126. }
  127. @fragment
  128. fn fs_main() -> @location(0) vec4<f32> {
  129. return vec4<f32>(1.0, 0.0, 0.0, 1.0);
  130. }
  131. "#
  132. .into(),
  133. ),
  134. });
  135. let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
  136. label: None,
  137. bind_group_layouts: &[],
  138. push_constant_ranges: &[],
  139. });
  140. let swapchain_capabilities = surface.get_capabilities(&adapter);
  141. let swapchain_format = swapchain_capabilities.formats[0];
  142. let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
  143. label: None,
  144. layout: Some(&pipeline_layout),
  145. vertex: wgpu::VertexState {
  146. module: &shader,
  147. entry_point: "vs_main",
  148. buffers: &[],
  149. },
  150. fragment: Some(wgpu::FragmentState {
  151. module: &shader,
  152. entry_point: "fs_main",
  153. targets: &[Some(swapchain_format.into())],
  154. }),
  155. primitive: wgpu::PrimitiveState::default(),
  156. depth_stencil: None,
  157. multisample: wgpu::MultisampleState::default(),
  158. multiview: None,
  159. });
  160. let config = wgpu::SurfaceConfiguration {
  161. usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
  162. format: swapchain_format,
  163. width: size.width,
  164. height: size.height,
  165. present_mode: wgpu::PresentMode::Fifo,
  166. desired_maximum_frame_latency: 2,
  167. alpha_mode: wgpu::CompositeAlphaMode::PostMultiplied,
  168. view_formats: vec![],
  169. };
  170. surface.configure(&device, &config);
  171. GraphicsResources {
  172. surface,
  173. device,
  174. pipeline,
  175. queue,
  176. config,
  177. }
  178. }
  179. fn render(&self) {
  180. let GraphicsResources {
  181. surface,
  182. device,
  183. pipeline,
  184. queue,
  185. ..
  186. } = self;
  187. let frame = surface
  188. .get_current_texture()
  189. .expect("Failed to acquire next swap chain texture");
  190. let view = frame
  191. .texture
  192. .create_view(&wgpu::TextureViewDescriptor::default());
  193. let mut encoder =
  194. device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
  195. {
  196. let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
  197. label: None,
  198. color_attachments: &[Some(wgpu::RenderPassColorAttachment {
  199. view: &view,
  200. resolve_target: None,
  201. ops: wgpu::Operations {
  202. load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
  203. store: wgpu::StoreOp::Store,
  204. },
  205. })],
  206. depth_stencil_attachment: None,
  207. timestamp_writes: None,
  208. occlusion_query_set: None,
  209. });
  210. rpass.set_pipeline(pipeline);
  211. rpass.draw(0..3, 0..1);
  212. }
  213. queue.submit(Some(encoder.finish()));
  214. frame.present();
  215. }
  216. }