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:
Jonathan Kelley authored and GitHub committed 2025-10-03 18:26:19 -07:00
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;
}
}