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

wgpu windows (fixed) #3257

Merged
merged 13 commits into from
Nov 26, 2024
971 changes: 687 additions & 284 deletions Cargo.lock

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"] }
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down
242 changes: 242 additions & 0 deletions examples/wgpu.rs
Original file line number Diff line number Diff line change
@@ -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();
}
}
4 changes: 2 additions & 2 deletions packages/desktop/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
22 changes: 22 additions & 0 deletions packages/desktop/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use std::{
sync::Arc,
};
use tao::{
dpi::PhysicalSize,
event::Event,
event_loop::{ControlFlow, EventLoop, EventLoopBuilder, EventLoopProxy, EventLoopWindowTarget},
window::WindowId,
Expand Down Expand Up @@ -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();
Expand Down
8 changes: 8 additions & 0 deletions packages/desktop/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>,
Expand Down Expand Up @@ -92,6 +93,7 @@ impl Config {

Self {
window,
as_child_window: false,
event_loop: None,
menu: MenuBuilderState::Unset,
protocols: Vec::new(),
Expand Down Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions packages/desktop/src/launch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
_ => {}
},

Expand Down
13 changes: 12 additions & 1 deletion packages/desktop/src/webview.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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)
Expand Down
Loading