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

[Feature] Custom Shader Widget #2085

Merged
merged 26 commits into from
Nov 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
781ef1f
Added support for custom shader widget for iced_wgpu backend.
bungoboingo Sep 14, 2023
36e8521
Removed `Into` for Rectangle<f32> from u32
bungoboingo Sep 18, 2023
91fca02
Reexport Transformation from widget::shader
bungoboingo Sep 21, 2023
65f4ff0
Added redraw request handling to widget events.
bungoboingo Sep 28, 2023
de9420e
Fix latest `wgpu` changes
hecrj Nov 14, 2023
46a48af
Write missing documentation for `custom` module in `iced_wgpu`
hecrj Nov 14, 2023
3e8ed05
Update `wgpu` in `custom_shader` example
hecrj Nov 14, 2023
33f6262
Fix `clippy` lints :crab:
hecrj Nov 14, 2023
226eac3
Remove old `widget` modules in `iced_renderer`
hecrj Nov 14, 2023
2dda913
Run `cargo fmt`
hecrj Nov 14, 2023
9489e29
Re-organize `custom` module as `pipeline` module
hecrj Nov 14, 2023
882ae30
Enable `iced_renderer/wgpu` feature in `iced_widget`
hecrj Nov 14, 2023
c2baf18
Use `Instant` from `iced_core` instead of `std`
hecrj Nov 14, 2023
280d373
Fix broken intra-doc links
hecrj Nov 14, 2023
91d7df5
Create `shader` function helper in `iced_widget`
hecrj Nov 14, 2023
63f36b0
Export `wgpu` crate in `shader` module in `iced_widget`
hecrj Nov 14, 2023
78a0638
Use a single source for amount of cubes in `custom_shader` example
hecrj Nov 14, 2023
9ddfaf3
Rename `cubes` to `scene` in `custom_shader` example
hecrj Nov 14, 2023
34b5cb7
Remove `Default` implementation in `custom_shader` example
hecrj Nov 14, 2023
fee3bf0
Kill current render pass only when custom pipelines are present in layer
hecrj Nov 14, 2023
b1b2467
Fix render pass label in `iced_wgpu`
hecrj Nov 14, 2023
811aa67
Improve module hierarchy of `custom_shader` example
hecrj Nov 14, 2023
77dfa60
Move `textures` directory outside of `src` in `custom_shader` example
hecrj Nov 14, 2023
74b920a
Remove unnecessary `self` in `iced_style::theme`
hecrj Nov 14, 2023
8f384c8
Remove unsused `custom.rs` file in `iced_wgpu`
hecrj Nov 14, 2023
0968c5b
Remove unused import in `custom_shader` example
hecrj Nov 14, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ maintenance = { status = "actively-developed" }
[features]
default = ["wgpu"]
# Enable the `wgpu` GPU-accelerated renderer backend
wgpu = ["iced_renderer/wgpu"]
wgpu = ["iced_renderer/wgpu", "iced_widget/wgpu"]
# Enables the `Image` widget
image = ["iced_widget/image", "dep:image"]
# Enables the `Svg` widget
Expand Down
17 changes: 17 additions & 0 deletions examples/custom_shader/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[package]
name = "custom_shader"
version = "0.1.0"
authors = ["Bingus <shankern@protonmail.com>"]
edition = "2021"

[dependencies]
iced.workspace = true
iced.features = ["debug", "advanced"]

image.workspace = true
bytemuck.workspace = true

glam.workspace = true
glam.features = ["bytemuck"]

rand = "0.8.5"
168 changes: 168 additions & 0 deletions examples/custom_shader/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
mod scene;

use scene::Scene;

use iced::executor;
use iced::time::Instant;
use iced::widget::shader::wgpu;
use iced::widget::{
checkbox, column, container, row, shader, slider, text, vertical_space,
};
use iced::window;
use iced::{
Alignment, Application, Color, Command, Element, Length, Renderer,
Subscription, Theme,
};

fn main() -> iced::Result {
IcedCubes::run(iced::Settings::default())
}

struct IcedCubes {
start: Instant,
scene: Scene,
}

#[derive(Debug, Clone)]
enum Message {
CubeAmountChanged(u32),
CubeSizeChanged(f32),
Tick(Instant),
ShowDepthBuffer(bool),
LightColorChanged(Color),
}

impl Application for IcedCubes {
type Executor = executor::Default;
type Message = Message;
type Theme = Theme;
type Flags = ();

fn new(_flags: Self::Flags) -> (Self, Command<Self::Message>) {
(
Self {
start: Instant::now(),
scene: Scene::new(),
},
Command::none(),
)
}

fn title(&self) -> String {
"Iced Cubes".to_string()
}

fn update(&mut self, message: Self::Message) -> Command<Self::Message> {
match message {
Message::CubeAmountChanged(amount) => {
self.scene.change_amount(amount);
}
Message::CubeSizeChanged(size) => {
self.scene.size = size;
}
Message::Tick(time) => {
self.scene.update(time - self.start);
}
Message::ShowDepthBuffer(show) => {
self.scene.show_depth_buffer = show;
}
Message::LightColorChanged(color) => {
self.scene.light_color = color;
}
}

Command::none()
}

fn view(&self) -> Element<'_, Self::Message, Renderer<Self::Theme>> {
let top_controls = row![
control(
"Amount",
slider(
1..=scene::MAX,
self.scene.cubes.len() as u32,
Message::CubeAmountChanged
)
.width(100)
),
control(
"Size",
slider(0.1..=0.25, self.scene.size, Message::CubeSizeChanged)
.step(0.01)
.width(100),
),
checkbox(
"Show Depth Buffer",
self.scene.show_depth_buffer,
Message::ShowDepthBuffer
),
]
.spacing(40);

let bottom_controls = row![
control(
"R",
slider(0.0..=1.0, self.scene.light_color.r, move |r| {
Message::LightColorChanged(Color {
r,
..self.scene.light_color
})
})
.step(0.01)
.width(100)
),
control(
"G",
slider(0.0..=1.0, self.scene.light_color.g, move |g| {
Message::LightColorChanged(Color {
g,
..self.scene.light_color
})
})
.step(0.01)
.width(100)
),
control(
"B",
slider(0.0..=1.0, self.scene.light_color.b, move |b| {
Message::LightColorChanged(Color {
b,
..self.scene.light_color
})
})
.step(0.01)
.width(100)
)
]
.spacing(40);

let controls = column![top_controls, bottom_controls,]
.spacing(10)
.align_items(Alignment::Center);

let shader =
shader(&self.scene).width(Length::Fill).height(Length::Fill);

container(
column![shader, controls, vertical_space(20),]
.spacing(40)
.align_items(Alignment::Center),
)
.width(Length::Fill)
.height(Length::Fill)
.center_x()
.center_y()
.into()
}

fn subscription(&self) -> Subscription<Self::Message> {
window::frames().map(Message::Tick)
}
}

fn control<'a>(
label: &'static str,
control: impl Into<Element<'a, Message>>,
) -> Element<'a, Message> {
row![text(label), control.into()].spacing(10).into()
}
186 changes: 186 additions & 0 deletions examples/custom_shader/src/scene.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
mod camera;
mod pipeline;

use camera::Camera;
use pipeline::Pipeline;

use crate::wgpu;
use pipeline::cube::{self, Cube};

use iced::mouse;
use iced::time::Duration;
use iced::widget::shader;
use iced::{Color, Rectangle, Size};

use glam::Vec3;
use rand::Rng;
use std::cmp::Ordering;
use std::iter;

pub const MAX: u32 = 500;

#[derive(Clone)]
pub struct Scene {
pub size: f32,
pub cubes: Vec<Cube>,
pub camera: Camera,
pub show_depth_buffer: bool,
pub light_color: Color,
}

impl Scene {
pub fn new() -> Self {
let mut scene = Self {
size: 0.2,
cubes: vec![],
camera: Camera::default(),
show_depth_buffer: false,
light_color: Color::WHITE,
};

scene.change_amount(MAX);

scene
}

pub fn update(&mut self, time: Duration) {
for cube in self.cubes.iter_mut() {
cube.update(self.size, time.as_secs_f32());
}
}

pub fn change_amount(&mut self, amount: u32) {
let curr_cubes = self.cubes.len() as u32;

match amount.cmp(&curr_cubes) {
Ordering::Greater => {
// spawn
let cubes_2_spawn = (amount - curr_cubes) as usize;

let mut cubes = 0;
self.cubes.extend(iter::from_fn(|| {
if cubes < cubes_2_spawn {
cubes += 1;
Some(Cube::new(self.size, rnd_origin()))
} else {
None
}
}));
}
Ordering::Less => {
// chop
let cubes_2_cut = curr_cubes - amount;
let new_len = self.cubes.len() - cubes_2_cut as usize;
self.cubes.truncate(new_len);
}
Ordering::Equal => {}
}
}
}

impl<Message> shader::Program<Message> for Scene {
type State = ();
type Primitive = Primitive;

fn draw(
&self,
_state: &Self::State,
_cursor: mouse::Cursor,
bounds: Rectangle,
) -> Self::Primitive {
Primitive::new(
&self.cubes,
&self.camera,
bounds,
self.show_depth_buffer,
self.light_color,
)
}
}

/// A collection of `Cube`s that can be rendered.
#[derive(Debug)]
pub struct Primitive {
cubes: Vec<cube::Raw>,
uniforms: pipeline::Uniforms,
show_depth_buffer: bool,
}

impl Primitive {
pub fn new(
cubes: &[Cube],
camera: &Camera,
bounds: Rectangle,
show_depth_buffer: bool,
light_color: Color,
) -> Self {
let uniforms = pipeline::Uniforms::new(camera, bounds, light_color);

Self {
cubes: cubes
.iter()
.map(cube::Raw::from_cube)
.collect::<Vec<cube::Raw>>(),
uniforms,
show_depth_buffer,
}
}
}

impl shader::Primitive for Primitive {
fn prepare(
&self,
format: wgpu::TextureFormat,
device: &wgpu::Device,
queue: &wgpu::Queue,
target_size: Size<u32>,
_scale_factor: f32,
_transform: shader::Transformation,
storage: &mut shader::Storage,
) {
if !storage.has::<Pipeline>() {
storage.store(Pipeline::new(device, queue, format, target_size));
}

let pipeline = storage.get_mut::<Pipeline>().unwrap();

//upload data to GPU
pipeline.update(
device,
queue,
target_size,
&self.uniforms,
self.cubes.len(),
&self.cubes,
);
}

fn render(
&self,
storage: &shader::Storage,
bounds: Rectangle<u32>,
target: &wgpu::TextureView,
_target_size: Size<u32>,
encoder: &mut wgpu::CommandEncoder,
) {
//at this point our pipeline should always be initialized
let pipeline = storage.get::<Pipeline>().unwrap();

//render primitive
pipeline.render(
target,
encoder,
bounds,
self.cubes.len() as u32,
self.show_depth_buffer,
);
}
}

fn rnd_origin() -> Vec3 {
Vec3::new(
rand::thread_rng().gen_range(-4.0..4.0),
rand::thread_rng().gen_range(-4.0..4.0),
rand::thread_rng().gen_range(-4.0..2.0),
)
}
Loading
Loading