codify "platform" into resolution logic, server-fn/fullstack overhaul (#4596)
overhaul dioxus fullstack using a new implementation of server_fn This PR is a complete rewrite of dioxus fullstack designed to improve compatibility with axum and to fix a number of compilation errors based on feature flag unification plumbing through macros.
This commit is contained in:
1 parent
8421c3aef0
commit
f2b1c6d97f
424 files changed
+17284
-6624
No files matched your search
@@ -0,0 +1,21 @@
|
||||
[package]
|
||||
name = "wgpu-texture"
|
||||
version = "0.0.0"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
publish = false
|
||||
|
||||
[features]
|
||||
default = ["desktop"]
|
||||
desktop = ["dioxus/desktop"]
|
||||
native = ["dioxus/native"]
|
||||
tracing = ["dep:tracing-subscriber", "dioxus-native/tracing"]
|
||||
|
||||
[dependencies]
|
||||
dioxus-native = { workspace = true }
|
||||
dioxus = { workspace = true }
|
||||
wgpu = { workspace = true }
|
||||
winit = "0.30"
|
||||
bytemuck = "1"
|
||||
color = "0.3"
|
||||
tracing-subscriber = { workspace = true, optional = true }
|
||||
@@ -0,0 +1,260 @@
|
||||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
use crate::Color;
|
||||
use dioxus_native::{CustomPaintCtx, CustomPaintSource, DeviceHandle, TextureHandle};
|
||||
use std::sync::mpsc::{channel, Receiver, Sender};
|
||||
use std::time::Instant;
|
||||
use wgpu::{
|
||||
CommandEncoderDescriptor, Device, Extent3d, FragmentState, Instance, LoadOp, MultisampleState,
|
||||
Operations, PipelineLayoutDescriptor, PrimitiveState, PushConstantRange, Queue,
|
||||
RenderPassColorAttachment, RenderPassDescriptor, RenderPipeline, RenderPipelineDescriptor,
|
||||
ShaderModuleDescriptor, ShaderSource, ShaderStages, StoreOp, Texture, TextureDescriptor,
|
||||
TextureDimension, TextureFormat, TextureUsages, TextureViewDescriptor, VertexState,
|
||||
};
|
||||
|
||||
pub struct DemoPaintSource {
|
||||
state: DemoRendererState,
|
||||
start_time: std::time::Instant,
|
||||
tx: Sender<DemoMessage>,
|
||||
rx: Receiver<DemoMessage>,
|
||||
color: Color,
|
||||
}
|
||||
|
||||
impl CustomPaintSource for DemoPaintSource {
|
||||
fn resume(&mut self, _instance: &Instance, device_handle: &DeviceHandle) {
|
||||
// Extract device and queue from device_handle
|
||||
let device = &device_handle.device;
|
||||
let queue = &device_handle.queue;
|
||||
let active_state = ActiveDemoRenderer::new(device, queue);
|
||||
self.state = DemoRendererState::Active(Box::new(active_state));
|
||||
}
|
||||
|
||||
fn suspend(&mut self) {
|
||||
self.state = DemoRendererState::Suspended;
|
||||
}
|
||||
|
||||
fn render(
|
||||
&mut self,
|
||||
ctx: CustomPaintCtx<'_>,
|
||||
width: u32,
|
||||
height: u32,
|
||||
_scale: f64,
|
||||
) -> Option<TextureHandle> {
|
||||
self.process_messages();
|
||||
self.render(ctx, width, height)
|
||||
}
|
||||
}
|
||||
|
||||
pub enum DemoMessage {
|
||||
// Color in RGB format
|
||||
SetColor(Color),
|
||||
}
|
||||
|
||||
enum DemoRendererState {
|
||||
Active(Box<ActiveDemoRenderer>),
|
||||
Suspended,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct TextureAndHandle {
|
||||
texture: Texture,
|
||||
handle: TextureHandle,
|
||||
}
|
||||
|
||||
struct ActiveDemoRenderer {
|
||||
device: Device,
|
||||
queue: Queue,
|
||||
pipeline: RenderPipeline,
|
||||
displayed_texture: Option<TextureAndHandle>,
|
||||
next_texture: Option<TextureAndHandle>,
|
||||
}
|
||||
|
||||
impl DemoPaintSource {
|
||||
pub fn new() -> Self {
|
||||
let (tx, rx) = channel();
|
||||
Self::with_channel(tx, rx)
|
||||
}
|
||||
|
||||
pub fn with_channel(tx: Sender<DemoMessage>, rx: Receiver<DemoMessage>) -> Self {
|
||||
Self {
|
||||
state: DemoRendererState::Suspended,
|
||||
start_time: std::time::Instant::now(),
|
||||
tx,
|
||||
rx,
|
||||
color: Color::WHITE,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sender(&self) -> Sender<DemoMessage> {
|
||||
self.tx.clone()
|
||||
}
|
||||
|
||||
fn process_messages(&mut self) {
|
||||
loop {
|
||||
match self.rx.try_recv() {
|
||||
Err(_) => return,
|
||||
Ok(msg) => match msg {
|
||||
DemoMessage::SetColor(color) => self.color = color,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn render(
|
||||
&mut self,
|
||||
ctx: CustomPaintCtx<'_>,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> Option<TextureHandle> {
|
||||
if width == 0 || height == 0 {
|
||||
return None;
|
||||
}
|
||||
let DemoRendererState::Active(state) = &mut self.state else {
|
||||
return None;
|
||||
};
|
||||
|
||||
state.render(ctx, self.color.components, width, height, &self.start_time)
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveDemoRenderer {
|
||||
pub(crate) fn new(device: &Device, queue: &Queue) -> Self {
|
||||
let shader = device.create_shader_module(ShaderModuleDescriptor {
|
||||
label: None,
|
||||
source: ShaderSource::Wgsl(std::borrow::Cow::Borrowed(include_str!("shader.wgsl"))),
|
||||
});
|
||||
|
||||
let pipeline_layout = device.create_pipeline_layout(&PipelineLayoutDescriptor {
|
||||
label: None,
|
||||
bind_group_layouts: &[],
|
||||
push_constant_ranges: &[PushConstantRange {
|
||||
stages: ShaderStages::FRAGMENT,
|
||||
range: 0..16, // full size in bytes, aligned
|
||||
}],
|
||||
});
|
||||
|
||||
let pipeline = device.create_render_pipeline(&RenderPipelineDescriptor {
|
||||
label: None,
|
||||
layout: Some(&pipeline_layout),
|
||||
vertex: VertexState {
|
||||
module: &shader,
|
||||
entry_point: Some("vs_main"),
|
||||
buffers: &[],
|
||||
compilation_options: Default::default(),
|
||||
},
|
||||
fragment: Some(FragmentState {
|
||||
module: &shader,
|
||||
entry_point: Some("fs_main"),
|
||||
compilation_options: Default::default(),
|
||||
targets: &[Some(TextureFormat::Rgba8Unorm.into())],
|
||||
}),
|
||||
primitive: PrimitiveState::default(),
|
||||
depth_stencil: None,
|
||||
multisample: MultisampleState::default(),
|
||||
multiview: None,
|
||||
cache: None,
|
||||
});
|
||||
|
||||
Self {
|
||||
device: device.clone(),
|
||||
queue: queue.clone(),
|
||||
pipeline,
|
||||
displayed_texture: None,
|
||||
next_texture: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn render(
|
||||
&mut self,
|
||||
mut ctx: CustomPaintCtx<'_>,
|
||||
light: [f32; 3],
|
||||
width: u32,
|
||||
height: u32,
|
||||
start_time: &Instant,
|
||||
) -> Option<TextureHandle> {
|
||||
// If "next texture" size doesn't match specified size then unregister and drop texture
|
||||
if let Some(next) = &self.next_texture {
|
||||
if next.texture.width() != width || next.texture.height() != height {
|
||||
ctx.unregister_texture(next.handle);
|
||||
self.next_texture = None;
|
||||
}
|
||||
}
|
||||
|
||||
// If there is no "next texture" then create one and register it.
|
||||
let texture_and_handle = match &self.next_texture {
|
||||
Some(next) => next,
|
||||
None => {
|
||||
let texture = create_texture(&self.device, width, height);
|
||||
let handle = ctx.register_texture(texture.clone());
|
||||
self.next_texture = Some(TextureAndHandle { texture, handle });
|
||||
self.next_texture.as_ref().unwrap()
|
||||
}
|
||||
};
|
||||
|
||||
let next_texture = &texture_and_handle.texture;
|
||||
let next_texture_handle = texture_and_handle.handle;
|
||||
|
||||
let elapsed: f32 = start_time.elapsed().as_millis() as f32 / 500.;
|
||||
let [light_red, light_green, light_blue] = light;
|
||||
let push_constants = PushConstants {
|
||||
light_color_and_time: [light_red, light_green, light_blue, elapsed],
|
||||
};
|
||||
|
||||
let mut encoder = self
|
||||
.device
|
||||
.create_command_encoder(&CommandEncoderDescriptor { label: None });
|
||||
{
|
||||
let mut rpass = encoder.begin_render_pass(&RenderPassDescriptor {
|
||||
label: None,
|
||||
color_attachments: &[Some(RenderPassColorAttachment {
|
||||
view: &next_texture.create_view(&TextureViewDescriptor::default()),
|
||||
resolve_target: None,
|
||||
ops: Operations {
|
||||
load: LoadOp::Clear(wgpu::Color::GREEN),
|
||||
store: StoreOp::Store,
|
||||
},
|
||||
})],
|
||||
depth_stencil_attachment: None,
|
||||
timestamp_writes: None,
|
||||
occlusion_query_set: None,
|
||||
});
|
||||
rpass.set_pipeline(&self.pipeline);
|
||||
rpass.set_push_constants(
|
||||
ShaderStages::FRAGMENT, // Stage (your constants are for fragment shader)
|
||||
0, // Offset in bytes (start at 0)
|
||||
bytemuck::bytes_of(&push_constants),
|
||||
);
|
||||
rpass.draw(0..3, 0..1);
|
||||
}
|
||||
|
||||
self.queue.submit(Some(encoder.finish()));
|
||||
|
||||
std::mem::swap(&mut self.next_texture, &mut self.displayed_texture);
|
||||
Some(next_texture_handle)
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
struct PushConstants {
|
||||
light_color_and_time: [f32; 4],
|
||||
}
|
||||
|
||||
fn create_texture(device: &Device, width: u32, height: u32) -> Texture {
|
||||
device.create_texture(&TextureDescriptor {
|
||||
label: None,
|
||||
size: Extent3d {
|
||||
width,
|
||||
height,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: TextureDimension::D2,
|
||||
format: TextureFormat::Rgba8Unorm,
|
||||
usage: TextureUsages::RENDER_ATTACHMENT
|
||||
| TextureUsages::TEXTURE_BINDING
|
||||
| TextureUsages::COPY_SRC,
|
||||
view_formats: &[],
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
use color::{palette::css::WHITE, parse_color};
|
||||
use color::{OpaqueColor, Srgb};
|
||||
use demo_renderer::{DemoMessage, DemoPaintSource};
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_native::use_wgpu;
|
||||
use std::any::Any;
|
||||
use wgpu::{Features, Limits};
|
||||
use winit::dpi::LogicalSize;
|
||||
use winit::window::WindowAttributes;
|
||||
|
||||
mod demo_renderer;
|
||||
|
||||
// CSS Styles
|
||||
static STYLES: Asset = asset!("/src/styles.css");
|
||||
|
||||
// WGPU settings required by this example
|
||||
const FEATURES: Features = Features::PUSH_CONSTANTS;
|
||||
fn limits() -> Limits {
|
||||
Limits {
|
||||
max_push_constant_size: 16,
|
||||
..Limits::default()
|
||||
}
|
||||
}
|
||||
fn window_attributes() -> WindowAttributes {
|
||||
// You can also use a `<title>` element to set the window title
|
||||
// but this demonstrates the use of `WindowAttributes`
|
||||
WindowAttributes::default()
|
||||
.with_title("WGPU Example")
|
||||
.with_inner_size(LogicalSize::new(800, 600))
|
||||
}
|
||||
|
||||
type Color = OpaqueColor<Srgb>;
|
||||
|
||||
fn main() {
|
||||
#[cfg(feature = "tracing")]
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
let config: Vec<Box<dyn Any>> = vec![
|
||||
Box::new(FEATURES),
|
||||
Box::new(limits()),
|
||||
Box::new(window_attributes()),
|
||||
];
|
||||
dioxus_native::launch_cfg(app, Vec::new(), config);
|
||||
}
|
||||
|
||||
fn app() -> Element {
|
||||
let mut show_cube = use_signal(|| true);
|
||||
|
||||
let color_str = use_signal(|| String::from("red"));
|
||||
let color = use_memo(move || {
|
||||
parse_color(&color_str())
|
||||
.map(|c| c.to_alpha_color())
|
||||
.unwrap_or(WHITE)
|
||||
.split()
|
||||
.0
|
||||
});
|
||||
|
||||
use_effect(move || println!("{:?}", color().components));
|
||||
|
||||
rsx!(
|
||||
Stylesheet { href: STYLES }
|
||||
div { id:"overlay",
|
||||
h2 { "Control Panel" },
|
||||
button {
|
||||
onclick: move |_| show_cube.toggle(),
|
||||
if show_cube() {
|
||||
"Hide cube"
|
||||
} else {
|
||||
"Show cube"
|
||||
}
|
||||
}
|
||||
br {}
|
||||
ColorControl { label: "Color:", color_str },
|
||||
p { "This overlay demonstrates that the custom WGPU content can be rendered beneath layers of HTML content" }
|
||||
}
|
||||
div { id:"underlay",
|
||||
h2 { "Underlay" },
|
||||
p { "This underlay demonstrates that the custom WGPU content can be rendered above layers and blended with the content underneath" }
|
||||
}
|
||||
header {
|
||||
h1 { "Blitz WGPU Demo" }
|
||||
}
|
||||
if show_cube() {
|
||||
SpinningCube { color }
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn ColorControl(label: &'static str, color_str: WriteSignal<String>) -> Element {
|
||||
rsx!(div {
|
||||
class: "color-control",
|
||||
{ label },
|
||||
input {
|
||||
value: color_str(),
|
||||
oninput: move |evt| {
|
||||
*color_str.write() = evt.value()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn SpinningCube(color: Memo<Color>) -> Element {
|
||||
// Create custom paint source and register it with the renderer
|
||||
let paint_source = DemoPaintSource::new();
|
||||
let sender = paint_source.sender();
|
||||
let paint_source_id = use_wgpu(move || paint_source);
|
||||
|
||||
use_effect(move || {
|
||||
sender.send(DemoMessage::SetColor(color())).unwrap();
|
||||
});
|
||||
|
||||
rsx!(
|
||||
div { id:"canvas-container",
|
||||
canvas {
|
||||
id: "demo-canvas",
|
||||
"src": paint_source_id
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) position: vec4<f32>,
|
||||
@location(0) frag_position: vec2<f32>,
|
||||
};
|
||||
|
||||
@vertex
|
||||
fn vs_main(
|
||||
@builtin(vertex_index) vertex_index: u32
|
||||
) -> VertexOutput {
|
||||
var output: VertexOutput;
|
||||
|
||||
var positions = array<vec2<f32>, 3>(
|
||||
vec2<f32>(-1.0, 3.0),
|
||||
vec2<f32>(-1.0, -1.0),
|
||||
vec2<f32>( 3.0, -1.0)
|
||||
);
|
||||
|
||||
let pos = positions[vertex_index];
|
||||
output.position = vec4<f32>(pos.x, -pos.y, 0.0, 1.0);
|
||||
output.frag_position = pos;
|
||||
return output;
|
||||
}
|
||||
|
||||
struct PushConstants {
|
||||
light_color_and_time: vec4<f32>,
|
||||
};
|
||||
|
||||
var<push_constant> pc: PushConstants;
|
||||
|
||||
fn sdRoundBox(p: vec3<f32>, b: vec3<f32>, r: f32) -> f32 {
|
||||
let q = abs(p) - b;
|
||||
return length(max(q, vec3<f32>(0.0))) + min(max(q.x, max(q.y, q.z)), 0.0) - r;
|
||||
}
|
||||
|
||||
fn rotateY(r: vec3<f32>, angle: f32) -> vec3<f32> {
|
||||
let c = cos(angle);
|
||||
let s = sin(angle);
|
||||
let rotation_matrix = mat3x3<f32>(
|
||||
vec3<f32>( c, 0.0, s),
|
||||
vec3<f32>(0.0, 1.0, 0.0),
|
||||
vec3<f32>(-s, 0.0, c)
|
||||
);
|
||||
return rotation_matrix * r;
|
||||
}
|
||||
|
||||
fn rotateZ(r: vec3<f32>, angle: f32) -> vec3<f32> {
|
||||
let c = cos(angle);
|
||||
let s = sin(angle);
|
||||
let rotation_matrix = mat3x3<f32>(
|
||||
vec3<f32>( c, -s, 0.0),
|
||||
vec3<f32>( s, c, 0.0),
|
||||
vec3<f32>(0.0, 0.0, 1.0)
|
||||
);
|
||||
return rotation_matrix * r;
|
||||
}
|
||||
|
||||
// Distance from the scene
|
||||
fn scene(r: vec3<f32>) -> f32 {
|
||||
let iTime = pc.light_color_and_time.w;
|
||||
let pos = rotateZ(rotateY(r + vec3<f32>(-1.0, -1.0, 4.0), iTime), iTime);
|
||||
let cube = vec3<f32>(0.5, 0.5, 0.5);
|
||||
let edge = 0.1;
|
||||
return sdRoundBox(pos, cube, edge);
|
||||
}
|
||||
|
||||
// https://iquilezles.org/articles/normalsSDF
|
||||
fn normal(pos: vec3<f32>) -> vec3<f32> {
|
||||
let e = vec2<f32>(1.0, -1.0) * 0.5773;
|
||||
let eps = 0.0005;
|
||||
return normalize(
|
||||
e.xyy * scene(pos + e.xyy * eps) +
|
||||
e.yyx * scene(pos + e.yyx * eps) +
|
||||
e.yxy * scene(pos + e.yxy * eps) +
|
||||
e.xxx * scene(pos + e.xxx * eps)
|
||||
);
|
||||
}
|
||||
|
||||
fn render(fragCoord: vec2<f32>, light_color: vec3<f32>) -> vec4<f32> {
|
||||
var color = vec4<f32>(0.0, 0.0, 0.0, 1.0);
|
||||
|
||||
var camera = vec3<f32>(1.0, 2.0, 1.0);
|
||||
var p = vec3<f32>(fragCoord.x, fragCoord.y + 1.0, -1.0);
|
||||
var dir = normalize(p - camera);
|
||||
|
||||
var i = 0;
|
||||
loop {
|
||||
if (i >= 90) { break; }
|
||||
let dist = scene(p);
|
||||
if (dist < 0.0001) { break; }
|
||||
p = p + dir * dist;
|
||||
i = i + 1;
|
||||
}
|
||||
|
||||
let surf_normal = normal(p);
|
||||
let light_position = vec3<f32>(2.0, 4.0, -0.5);
|
||||
var light = 7.0 + 2.0 * dot(surf_normal, light_position);
|
||||
light = light / (0.2 * pow(length(light_position - p), 3.5));
|
||||
|
||||
let alpha = select(0.0, 1.0, i < 90);
|
||||
return vec4<f32>(light * light_color, alpha) * 2.0;
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fs_main(@location(0) frag_position: vec2<f32>) -> @location(0) vec4<f32> {
|
||||
let selected_light_color = pc.light_color_and_time.xyz;
|
||||
let r = vec2<f32>(0.5 * frag_position.x + 1.0, 0.5 - 0.5 * frag_position.y);
|
||||
return render(r, selected_light_color);
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html, body, main {
|
||||
height: 100%;
|
||||
font-family: system-ui, sans;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
main {
|
||||
display: grid;
|
||||
grid-template-rows: 100px 1fr;
|
||||
grid-template-columns: 100%;
|
||||
background: #f4e8d2;
|
||||
}
|
||||
|
||||
#canvas-container {
|
||||
display: grid;
|
||||
opacity: 0.8;
|
||||
grid-row: 2;
|
||||
}
|
||||
|
||||
header {
|
||||
padding: 10px 40px;
|
||||
background-color: white;
|
||||
z-index: 100;
|
||||
grid-row: 1;
|
||||
}
|
||||
|
||||
#overlay {
|
||||
position: absolute;
|
||||
width: 33%;
|
||||
height: 100%;
|
||||
right: 0;
|
||||
z-index: 10;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
padding-top: 40%;
|
||||
padding-inline: 20px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
#underlay {
|
||||
position: absolute;
|
||||
width: 33%;
|
||||
height: 100%;
|
||||
z-index: -10;
|
||||
background-color: black;
|
||||
padding-top: 40%;
|
||||
padding-inline: 20px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.color-control {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
|
||||
> input {
|
||||
width: 150px;
|
||||
color: black;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user