Skip to content

Commit 6fcb496

Browse files
authored
Upgrade Blitz to 0.1.0-alpha.3 and add wgpu-texture example (#4286)
* Use Blitz 0.1.0-alpha.3 * Add wgpu-texture example * Native: Support configuring wgpu Features and Limits
1 parent 544d8cc commit 6fcb496

File tree

11 files changed

+795
-161
lines changed

11 files changed

+795
-161
lines changed

Cargo.lock

Lines changed: 58 additions & 105 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ members = [
113113
"examples/fullstack-desktop",
114114
"examples/fullstack-auth",
115115
"examples/fullstack-websockets",
116+
"examples/wgpu-texture",
116117

117118
# Playwright tests
118119
"packages/playwright-tests/liveview",

examples/wgpu-texture/Cargo.toml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Copyright © SixtyFPS GmbH <[email protected]>
2+
# SPDX-License-Identifier: MIT
3+
4+
[package]
5+
name = "wgpu-texture"
6+
version = "0.0.0"
7+
edition = "2021"
8+
license = "MIT"
9+
publish = false
10+
11+
[features]
12+
default = ["desktop"]
13+
desktop = ["dioxus/desktop"]
14+
native = ["dioxus/native"]
15+
16+
[dependencies]
17+
dioxus-native = { path = "../../packages/native" }
18+
dioxus = { workspace = true }
19+
wgpu = "24"
20+
bytemuck = "1"
21+
color = "0.3"
Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
// Copyright © SixtyFPS GmbH <[email protected]>
2+
// SPDX-License-Identifier: MIT
3+
use crate::Color;
4+
use dioxus_native::{CustomPaintCtx, CustomPaintSource, TextureHandle};
5+
use std::sync::mpsc::{channel, Receiver, Sender};
6+
use std::time::Instant;
7+
use wgpu::{Device, Queue};
8+
9+
pub struct DemoPaintSource {
10+
state: DemoRendererState,
11+
start_time: std::time::Instant,
12+
tx: Sender<DemoMessage>,
13+
rx: Receiver<DemoMessage>,
14+
color: Color,
15+
}
16+
17+
impl CustomPaintSource for DemoPaintSource {
18+
fn resume(&mut self, device: &Device, queue: &Queue) {
19+
// TODO: work out what to do about width/height
20+
let active_state = ActiveDemoRenderer::new(device, queue);
21+
self.state = DemoRendererState::Active(Box::new(active_state));
22+
}
23+
24+
fn suspend(&mut self) {
25+
self.state = DemoRendererState::Suspended;
26+
}
27+
28+
fn render(
29+
&mut self,
30+
ctx: CustomPaintCtx<'_>,
31+
width: u32,
32+
height: u32,
33+
_scale: f64,
34+
) -> Option<TextureHandle> {
35+
self.process_messages();
36+
self.render(ctx, width, height)
37+
}
38+
}
39+
40+
pub enum DemoMessage {
41+
// Color in RGB format
42+
SetColor(Color),
43+
}
44+
45+
enum DemoRendererState {
46+
Active(Box<ActiveDemoRenderer>),
47+
Suspended,
48+
}
49+
50+
#[derive(Clone)]
51+
struct TextureAndHandle {
52+
texture: wgpu::Texture,
53+
handle: TextureHandle,
54+
}
55+
56+
struct ActiveDemoRenderer {
57+
device: wgpu::Device,
58+
queue: wgpu::Queue,
59+
pipeline: wgpu::RenderPipeline,
60+
displayed_texture: Option<TextureAndHandle>,
61+
next_texture: Option<TextureAndHandle>,
62+
}
63+
64+
impl DemoPaintSource {
65+
pub fn new() -> Self {
66+
let (tx, rx) = channel();
67+
Self::with_channel(tx, rx)
68+
}
69+
70+
pub fn with_channel(tx: Sender<DemoMessage>, rx: Receiver<DemoMessage>) -> Self {
71+
Self {
72+
state: DemoRendererState::Suspended,
73+
start_time: std::time::Instant::now(),
74+
tx,
75+
rx,
76+
color: Color::WHITE,
77+
}
78+
}
79+
80+
pub fn sender(&self) -> Sender<DemoMessage> {
81+
self.tx.clone()
82+
}
83+
84+
fn process_messages(&mut self) {
85+
loop {
86+
match self.rx.try_recv() {
87+
Err(_) => return,
88+
Ok(msg) => match msg {
89+
DemoMessage::SetColor(color) => self.color = color,
90+
},
91+
}
92+
}
93+
}
94+
95+
fn render(
96+
&mut self,
97+
ctx: CustomPaintCtx<'_>,
98+
width: u32,
99+
height: u32,
100+
) -> Option<TextureHandle> {
101+
if width == 0 || height == 0 {
102+
return None;
103+
}
104+
let DemoRendererState::Active(state) = &mut self.state else {
105+
return None;
106+
};
107+
108+
state.render(ctx, self.color.components, width, height, &self.start_time)
109+
}
110+
}
111+
112+
impl ActiveDemoRenderer {
113+
pub(crate) fn new(device: &Device, queue: &Queue) -> Self {
114+
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
115+
label: None,
116+
source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(include_str!(
117+
"shader.wgsl"
118+
))),
119+
});
120+
121+
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
122+
label: None,
123+
bind_group_layouts: &[],
124+
push_constant_ranges: &[wgpu::PushConstantRange {
125+
stages: wgpu::ShaderStages::FRAGMENT,
126+
range: 0..16, // full size in bytes, aligned
127+
}],
128+
});
129+
130+
let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
131+
label: None,
132+
layout: Some(&pipeline_layout),
133+
vertex: wgpu::VertexState {
134+
module: &shader,
135+
entry_point: Some("vs_main"),
136+
buffers: &[],
137+
compilation_options: Default::default(),
138+
},
139+
fragment: Some(wgpu::FragmentState {
140+
module: &shader,
141+
entry_point: Some("fs_main"),
142+
compilation_options: Default::default(),
143+
targets: &[Some(wgpu::TextureFormat::Rgba8Unorm.into())],
144+
}),
145+
primitive: wgpu::PrimitiveState::default(),
146+
depth_stencil: None,
147+
multisample: wgpu::MultisampleState::default(),
148+
multiview: None,
149+
cache: None,
150+
});
151+
152+
Self {
153+
device: device.clone(),
154+
queue: queue.clone(),
155+
pipeline,
156+
displayed_texture: None,
157+
next_texture: None,
158+
}
159+
}
160+
161+
pub(crate) fn render(
162+
&mut self,
163+
mut ctx: CustomPaintCtx<'_>,
164+
light: [f32; 3],
165+
width: u32,
166+
height: u32,
167+
start_time: &Instant,
168+
) -> Option<TextureHandle> {
169+
// If "next texture" size doesn't match specified size then unregister and drop texture
170+
if let Some(next) = &self.next_texture {
171+
if next.texture.width() != width || next.texture.height() != height {
172+
ctx.unregister_texture(next.handle);
173+
self.next_texture = None;
174+
}
175+
}
176+
177+
// If there is no "next texture" then create one and register it.
178+
let texture_and_handle = match &self.next_texture {
179+
Some(next) => next,
180+
None => {
181+
let texture = create_texture(&self.device, width, height);
182+
let handle = ctx.register_texture(texture.clone());
183+
self.next_texture = Some(TextureAndHandle { texture, handle });
184+
self.next_texture.as_ref().unwrap()
185+
}
186+
};
187+
188+
let next_texture = &texture_and_handle.texture;
189+
let next_texture_handle = texture_and_handle.handle;
190+
191+
let elapsed: f32 = start_time.elapsed().as_millis() as f32 / 500.;
192+
let [light_red, light_green, light_blue] = light;
193+
let push_constants = PushConstants {
194+
light_color_and_time: [light_red, light_green, light_blue, elapsed],
195+
};
196+
197+
let mut encoder = self
198+
.device
199+
.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
200+
{
201+
let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
202+
label: None,
203+
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
204+
view: &next_texture.create_view(&wgpu::TextureViewDescriptor::default()),
205+
resolve_target: None,
206+
ops: wgpu::Operations {
207+
load: wgpu::LoadOp::Clear(wgpu::Color::GREEN),
208+
store: wgpu::StoreOp::Store,
209+
},
210+
})],
211+
depth_stencil_attachment: None,
212+
timestamp_writes: None,
213+
occlusion_query_set: None,
214+
});
215+
rpass.set_pipeline(&self.pipeline);
216+
rpass.set_push_constants(
217+
wgpu::ShaderStages::FRAGMENT, // Stage (your constants are for fragment shader)
218+
0, // Offset in bytes (start at 0)
219+
bytemuck::bytes_of(&push_constants),
220+
);
221+
rpass.draw(0..3, 0..1);
222+
}
223+
224+
self.queue.submit(Some(encoder.finish()));
225+
226+
std::mem::swap(&mut self.next_texture, &mut self.displayed_texture);
227+
Some(next_texture_handle)
228+
}
229+
}
230+
231+
#[repr(C)]
232+
#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
233+
struct PushConstants {
234+
light_color_and_time: [f32; 4],
235+
}
236+
237+
fn create_texture(device: &wgpu::Device, width: u32, height: u32) -> wgpu::Texture {
238+
device.create_texture(&wgpu::TextureDescriptor {
239+
label: None,
240+
size: wgpu::Extent3d {
241+
width,
242+
height,
243+
depth_or_array_layers: 1,
244+
},
245+
mip_level_count: 1,
246+
sample_count: 1,
247+
dimension: wgpu::TextureDimension::D2,
248+
format: wgpu::TextureFormat::Rgba8Unorm,
249+
usage: wgpu::TextureUsages::RENDER_ATTACHMENT
250+
| wgpu::TextureUsages::TEXTURE_BINDING
251+
| wgpu::TextureUsages::COPY_SRC,
252+
view_formats: &[],
253+
})
254+
}

examples/wgpu-texture/src/main.rs

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
use color::{palette::css::WHITE, parse_color};
2+
use color::{OpaqueColor, Srgb};
3+
use demo_renderer::{DemoMessage, DemoPaintSource};
4+
use dioxus::prelude::*;
5+
use dioxus_native::use_wgpu;
6+
use std::any::Any;
7+
use wgpu::{Features, Limits};
8+
9+
mod demo_renderer;
10+
11+
// CSS Styles
12+
static STYLES: &str = include_str!("./styles.css");
13+
14+
// WGPU settings required by this example
15+
const FEATURES: Features = Features::PUSH_CONSTANTS;
16+
fn limits() -> Limits {
17+
Limits {
18+
max_push_constant_size: 16,
19+
..Limits::default()
20+
}
21+
}
22+
23+
type Color = OpaqueColor<Srgb>;
24+
25+
fn main() {
26+
let config: Vec<Box<dyn Any>> = vec![Box::new(FEATURES), Box::new(limits())];
27+
dioxus_native::launch_cfg(app, Vec::new(), config);
28+
}
29+
30+
fn app() -> Element {
31+
let mut show_cube = use_signal(|| true);
32+
33+
let color_str = use_signal(|| String::from("red"));
34+
let color = use_memo(move || {
35+
parse_color(&color_str())
36+
.map(|c| c.to_alpha_color())
37+
.unwrap_or(WHITE)
38+
.split()
39+
.0
40+
});
41+
42+
use_effect(move || println!("{:?}", color().components));
43+
44+
rsx!(
45+
style { {STYLES} }
46+
div { id:"overlay",
47+
h2 { "Control Panel" },
48+
button {
49+
onclick: move |_| *show_cube.write() = !show_cube(),
50+
if show_cube() {
51+
"Hide cube"
52+
} else {
53+
"Show cube"
54+
}
55+
}
56+
br {}
57+
ColorControl { label: "Color:", color_str },
58+
p { "This overlay demonstrates that the custom WGPU content can be rendered beneath layers of HTML content" }
59+
}
60+
div { id:"underlay",
61+
h2 { "Underlay" },
62+
p { "This underlay demonstrates that the custom WGPU content can be rendered above layers and blended with the content underneath" }
63+
}
64+
header {
65+
h2 { "Blitz WGPU Demo" }
66+
}
67+
if show_cube() {
68+
SpinningCube { color }
69+
}
70+
)
71+
}
72+
73+
#[component]
74+
fn ColorControl(label: &'static str, color_str: Signal<String>) -> Element {
75+
rsx!(div {
76+
class: "color-control",
77+
{ label },
78+
input {
79+
value: color_str(),
80+
oninput: move |evt| {
81+
*color_str.write() = evt.value()
82+
}
83+
}
84+
})
85+
}
86+
87+
#[component]
88+
fn SpinningCube(color: Memo<Color>) -> Element {
89+
// Create custom paint source and register it with the renderer
90+
let paint_source = DemoPaintSource::new();
91+
let sender = paint_source.sender();
92+
let paint_source_id = use_wgpu(move || paint_source);
93+
94+
use_effect(move || {
95+
sender.send(DemoMessage::SetColor(color())).unwrap();
96+
});
97+
98+
rsx!(
99+
div { id:"canvas-container",
100+
canvas {
101+
id: "demo-canvas",
102+
data: paint_source_id
103+
}
104+
}
105+
)
106+
}

0 commit comments

Comments
 (0)