wgpu windows (fixed) (#3257)
* Working triangle rendering with wgpu in examples. Resize needs work in order to not reinitialize everything but only the bits needed. Naming of variables and resources are wonky and not the greatest. * The child window wasn't being resized along with the parent. This works. the abstractions in place should be passed the bounds rect down into the desktopservice to resize as opposed to in the app impl instead of how they are rn. --------- Co-authored-by: Vineeth Yeevani <vineethyeevani@Vineeths-MacBook-Air.local> Co-authored-by: Vineeth Yeevani <vineeth.yeevani@gmail.com>
This commit is contained in:
8 files changed
+983
-289
No files matched your search
Generated
+689
-286
File diff suppressed because it is too large.
Load diff
@@ -274,6 +274,8 @@ reqwest = { workspace = true, features = ["json"], optional = true }
|
||||
ciborium = { workspace = true, optional = true }
|
||||
base64 = { workspace = true, optional = true }
|
||||
http-range = { version = "0.1.5", optional = true }
|
||||
wgpu = { version = "0.19", optional = true }
|
||||
ouroboros = { version = "*", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
dioxus = { workspace = true, features = ["router"] }
|
||||
@@ -315,6 +317,7 @@ server = ["dioxus/server"]
|
||||
mobile = ["dioxus/mobile"]
|
||||
web = ["dioxus/web"]
|
||||
http = ["dep:reqwest", "dep:http-range"]
|
||||
gpu = ["dep:ouroboros", "dep:wgpu"]
|
||||
|
||||
[[example]]
|
||||
name = "login_form"
|
||||
@@ -448,6 +451,10 @@ name = "window_zoom"
|
||||
required-features = ["desktop"]
|
||||
doc-scrape-examples = true
|
||||
|
||||
[[example]]
|
||||
name = "wgpu"
|
||||
required-features = ["desktop", "gpu"]
|
||||
|
||||
[[example]]
|
||||
name = "control_focus"
|
||||
doc-scrape-examples = true
|
||||
|
||||
@@ -0,0 +1,242 @@
|
||||
//! Demonstrate how to use dioxus as a child window for use in alternative renderers like wgpu.
|
||||
//!
|
||||
//! The code here is borrowed from wry's example:
|
||||
//! https://github.com/tauri-apps/wry/blob/dev/examples/wgpu.rs
|
||||
//!
|
||||
//! To use this feature set `with_as_child_window()` on your desktop config which will then let you
|
||||
|
||||
use dioxus::desktop::{
|
||||
tao::{event::Event as WryEvent, window::WindowBuilder},
|
||||
use_wry_event_handler, window, Config, DesktopContext,
|
||||
};
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
let config = Config::new()
|
||||
.with_window(WindowBuilder::new().with_transparent(true))
|
||||
.with_as_child_window();
|
||||
|
||||
dioxus::LaunchBuilder::desktop()
|
||||
.with_cfg(config)
|
||||
.launch(app);
|
||||
}
|
||||
|
||||
fn app() -> Element {
|
||||
let graphics_resources = use_resource(move || async {
|
||||
GraphicsContextAsyncBuilder {
|
||||
desktop: window(),
|
||||
resources_builder: |ctx| Box::pin(GraphicsResources::new(ctx)),
|
||||
}
|
||||
.build()
|
||||
.await
|
||||
});
|
||||
|
||||
// on first render request a redraw
|
||||
use_effect(|| {
|
||||
window().window.request_redraw();
|
||||
});
|
||||
|
||||
use_wry_event_handler(move |event, _| {
|
||||
use dioxus::desktop::tao::event::WindowEvent;
|
||||
|
||||
if let WryEvent::RedrawRequested(_id) = event {
|
||||
let resources = graphics_resources.read();
|
||||
if let Some(resources) = resources.as_ref() {
|
||||
resources.with_resources(|resources| resources.render());
|
||||
}
|
||||
}
|
||||
|
||||
if let WryEvent::WindowEvent {
|
||||
event: WindowEvent::Resized(new_size),
|
||||
..
|
||||
} = event
|
||||
{
|
||||
let ctx = graphics_resources.value();
|
||||
ctx.as_ref().unwrap().with_resources(|srcs| {
|
||||
let mut cfg = srcs.config.clone();
|
||||
cfg.width = new_size.width;
|
||||
cfg.height = new_size.height;
|
||||
srcs.surface.configure(&srcs.device, &cfg);
|
||||
});
|
||||
window().window.request_redraw();
|
||||
}
|
||||
});
|
||||
|
||||
rsx! {
|
||||
div {
|
||||
color: "blue",
|
||||
width: "100vw",
|
||||
height: "100vh",
|
||||
display: "flex",
|
||||
justify_content: "center",
|
||||
align_items: "center",
|
||||
font_size: "20px",
|
||||
div { "text overlaied on wgpu surface!" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This borrows from the `window` which is contained within an `Arc` so we need to wrap it in a self-borrowing struct
|
||||
/// to be able to borrow the window for the wgpu::Surface
|
||||
#[ouroboros::self_referencing]
|
||||
struct GraphicsContext {
|
||||
desktop: DesktopContext,
|
||||
#[borrows(desktop)]
|
||||
#[not_covariant]
|
||||
resources: GraphicsResources<'this>,
|
||||
}
|
||||
|
||||
struct GraphicsResources<'a> {
|
||||
surface: wgpu::Surface<'a>,
|
||||
device: wgpu::Device,
|
||||
pipeline: wgpu::RenderPipeline,
|
||||
queue: wgpu::Queue,
|
||||
config: wgpu::SurfaceConfiguration,
|
||||
}
|
||||
|
||||
impl<'a> GraphicsResources<'a> {
|
||||
async fn new(context: &'a DesktopContext) -> Self {
|
||||
let window = &context.window;
|
||||
let size = window.inner_size();
|
||||
|
||||
let instance = wgpu::Instance::default();
|
||||
|
||||
let surface: wgpu::Surface<'a> = instance.create_surface(window).unwrap();
|
||||
let adapter = instance
|
||||
.request_adapter(&wgpu::RequestAdapterOptions {
|
||||
power_preference: wgpu::PowerPreference::default(),
|
||||
force_fallback_adapter: false,
|
||||
// Request an adapter which can render to our surface
|
||||
compatible_surface: Some(&surface),
|
||||
})
|
||||
.await
|
||||
.expect("Failed to find an appropriate adapter");
|
||||
|
||||
// Create the logical device and command queue
|
||||
let (device, queue) = adapter
|
||||
.request_device(
|
||||
&wgpu::DeviceDescriptor {
|
||||
label: None,
|
||||
required_features: wgpu::Features::empty(),
|
||||
// Make sure we use the texture resolution limits from the adapter, so we can support images the size of the swapchain.
|
||||
required_limits: wgpu::Limits::downlevel_webgl2_defaults()
|
||||
.using_resolution(adapter.limits()),
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.expect("Failed to create device");
|
||||
|
||||
// Load the shaders from disk
|
||||
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: None,
|
||||
source: wgpu::ShaderSource::Wgsl(
|
||||
r#"
|
||||
@vertex
|
||||
fn vs_main(@builtin(vertex_index) in_vertex_index: u32) -> @builtin(position) vec4<f32> {
|
||||
let x = f32(i32(in_vertex_index) - 1);
|
||||
let y = f32(i32(in_vertex_index & 1u) * 2 - 1);
|
||||
return vec4<f32>(x, y, 0.0, 1.0);
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fs_main() -> @location(0) vec4<f32> {
|
||||
return vec4<f32>(1.0, 0.0, 0.0, 1.0);
|
||||
}
|
||||
"#
|
||||
.into(),
|
||||
),
|
||||
});
|
||||
|
||||
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: None,
|
||||
bind_group_layouts: &[],
|
||||
push_constant_ranges: &[],
|
||||
});
|
||||
|
||||
let swapchain_capabilities = surface.get_capabilities(&adapter);
|
||||
let swapchain_format = swapchain_capabilities.formats[0];
|
||||
|
||||
let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
label: None,
|
||||
layout: Some(&pipeline_layout),
|
||||
vertex: wgpu::VertexState {
|
||||
module: &shader,
|
||||
entry_point: "vs_main",
|
||||
buffers: &[],
|
||||
},
|
||||
fragment: Some(wgpu::FragmentState {
|
||||
module: &shader,
|
||||
entry_point: "fs_main",
|
||||
targets: &[Some(swapchain_format.into())],
|
||||
}),
|
||||
primitive: wgpu::PrimitiveState::default(),
|
||||
depth_stencil: None,
|
||||
multisample: wgpu::MultisampleState::default(),
|
||||
multiview: None,
|
||||
});
|
||||
|
||||
let config = wgpu::SurfaceConfiguration {
|
||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
|
||||
format: swapchain_format,
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
present_mode: wgpu::PresentMode::Fifo,
|
||||
desired_maximum_frame_latency: 2,
|
||||
alpha_mode: wgpu::CompositeAlphaMode::PostMultiplied,
|
||||
view_formats: vec![],
|
||||
};
|
||||
|
||||
surface.configure(&device, &config);
|
||||
|
||||
GraphicsResources {
|
||||
surface,
|
||||
device,
|
||||
pipeline,
|
||||
queue,
|
||||
config,
|
||||
}
|
||||
}
|
||||
|
||||
fn render(&self) {
|
||||
let GraphicsResources {
|
||||
surface,
|
||||
device,
|
||||
pipeline,
|
||||
queue,
|
||||
..
|
||||
} = self;
|
||||
|
||||
let frame = surface
|
||||
.get_current_texture()
|
||||
.expect("Failed to acquire next swap chain texture");
|
||||
let view = frame
|
||||
.texture
|
||||
.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
|
||||
let mut encoder =
|
||||
device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
|
||||
|
||||
{
|
||||
let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: None,
|
||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||
view: &view,
|
||||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
|
||||
store: wgpu::StoreOp::Store,
|
||||
},
|
||||
})],
|
||||
depth_stencil_attachment: None,
|
||||
timestamp_writes: None,
|
||||
occlusion_query_set: None,
|
||||
});
|
||||
rpass.set_pipeline(pipeline);
|
||||
rpass.draw(0..3, 0..1);
|
||||
}
|
||||
|
||||
queue.submit(Some(encoder.finish()));
|
||||
frame.present();
|
||||
}
|
||||
}
|
||||
@@ -101,12 +101,12 @@ objc = "0.2.7"
|
||||
lazy-js-bundle = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = ["tokio_runtime", "exception", "devtools"]
|
||||
default = ["tokio_runtime", "exception", "transparent", "devtools"]
|
||||
tokio_runtime = ["dep:tokio"]
|
||||
fullscreen = ["wry/fullscreen"]
|
||||
transparent = ["wry/transparent"]
|
||||
devtools = ["wry/devtools", "dep:dioxus-devtools", "dioxus-signals"]
|
||||
exception = ["wry/objc-exception"]
|
||||
transparent = ["wry/transparent"]
|
||||
gnu = []
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
|
||||
@@ -17,6 +17,7 @@ use std::{
|
||||
sync::Arc,
|
||||
};
|
||||
use tao::{
|
||||
dpi::PhysicalSize,
|
||||
event::Event,
|
||||
event_loop::{ControlFlow, EventLoop, EventLoopBuilder, EventLoopProxy, EventLoopWindowTarget},
|
||||
window::WindowId,
|
||||
@@ -222,6 +223,27 @@ impl App {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resize_window(&self, size: PhysicalSize<u32>) {
|
||||
// TODO: the app layer should avoid directly manipulating the webview webview instance internals.
|
||||
// Window creation and modification is the responsibility of the webview instance so it makes sense to
|
||||
// encapsulate that there.
|
||||
self.webviews.values().for_each(|webview_instance| {
|
||||
use wry::Rect;
|
||||
|
||||
webview_instance
|
||||
.desktop_context
|
||||
.webview
|
||||
.set_bounds(Rect {
|
||||
position: wry::dpi::Position::Logical(wry::dpi::LogicalPosition::new(0.0, 0.0)),
|
||||
size: wry::dpi::Size::Physical(wry::dpi::PhysicalSize::new(
|
||||
size.width,
|
||||
size.height,
|
||||
)),
|
||||
})
|
||||
.unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
pub fn handle_start_cause_init(&mut self) {
|
||||
let virtual_dom = self.unmounted_dom.take().unwrap();
|
||||
let mut cfg = self.cfg.take().unwrap();
|
||||
|
||||
@@ -49,6 +49,7 @@ impl From<MenuBuilderState> for Option<DioxusMenu> {
|
||||
pub struct Config {
|
||||
pub(crate) event_loop: Option<EventLoop<UserWindowEvent>>,
|
||||
pub(crate) window: WindowBuilder,
|
||||
pub(crate) as_child_window: bool,
|
||||
pub(crate) menu: MenuBuilderState,
|
||||
pub(crate) protocols: Vec<WryProtocol>,
|
||||
pub(crate) asynchronous_protocols: Vec<AsyncWryProtocol>,
|
||||
@@ -92,6 +93,7 @@ impl Config {
|
||||
|
||||
Self {
|
||||
window,
|
||||
as_child_window: false,
|
||||
event_loop: None,
|
||||
menu: MenuBuilderState::Unset,
|
||||
protocols: Vec::new(),
|
||||
@@ -152,6 +154,12 @@ impl Config {
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the window as child
|
||||
pub fn with_as_child_window(mut self) -> Self {
|
||||
self.as_child_window = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the behaviour of the application when the last window is closed.
|
||||
pub fn with_close_behaviour(mut self, behaviour: WindowCloseBehaviour) -> Self {
|
||||
self.last_window_close_behavior = behaviour;
|
||||
|
||||
@@ -32,6 +32,7 @@ pub fn launch_virtual_dom_blocking(virtual_dom: VirtualDom, mut desktop_config:
|
||||
} => match event {
|
||||
WindowEvent::CloseRequested => app.handle_close_requested(window_id),
|
||||
WindowEvent::Destroyed { .. } => app.window_destroyed(window_id),
|
||||
WindowEvent::Resized(new_size) => app.resize_window(new_size),
|
||||
_ => {}
|
||||
},
|
||||
|
||||
|
||||
@@ -296,7 +296,11 @@ impl WebviewInstance {
|
||||
target_os = "ios",
|
||||
target_os = "android"
|
||||
))]
|
||||
let mut webview = WebViewBuilder::new(&window);
|
||||
let mut webview = if cfg.as_child_window {
|
||||
WebViewBuilder::new_as_child(&window)
|
||||
} else {
|
||||
WebViewBuilder::new(&window)
|
||||
};
|
||||
|
||||
#[cfg(not(any(
|
||||
target_os = "windows",
|
||||
@@ -319,6 +323,13 @@ impl WebviewInstance {
|
||||
}
|
||||
|
||||
webview = webview
|
||||
.with_bounds(wry::Rect {
|
||||
position: wry::dpi::Position::Logical(wry::dpi::LogicalPosition::new(0.0, 0.0)),
|
||||
size: wry::dpi::Size::Physical(wry::dpi::PhysicalSize::new(
|
||||
window.inner_size().width,
|
||||
window.inner_size().height,
|
||||
)),
|
||||
})
|
||||
.with_transparent(cfg.window.window.transparent)
|
||||
.with_url("dioxus://index.html/")
|
||||
.with_ipc_handler(ipc_handler)
|
||||
|
||||
Reference in new issue
Block a user