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 11 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
18 changes: 18 additions & 0 deletions examples/custom_shader/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[package]
name = "custom_shader"
version = "0.1.0"
authors = ["Bingus <[email protected]>"]
edition = "2021"

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

image.workspace = true
wgpu.workspace = true
bytemuck.workspace = true

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

rand = "0.8.5"
53 changes: 53 additions & 0 deletions examples/custom_shader/src/camera.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
use glam::{mat4, vec3, vec4};
use iced::Rectangle;

#[derive(Copy, Clone)]
pub struct Camera {
eye: glam::Vec3,
target: glam::Vec3,
up: glam::Vec3,
fov_y: f32,
near: f32,
far: f32,
}

impl Default for Camera {
fn default() -> Self {
Self {
eye: vec3(0.0, 2.0, 3.0),
target: glam::Vec3::ZERO,
up: glam::Vec3::Y,
fov_y: 45.0,
near: 0.1,
far: 100.0,
}
}
}

pub const OPENGL_TO_WGPU_MATRIX: glam::Mat4 = mat4(
vec4(1.0, 0.0, 0.0, 0.0),
vec4(0.0, 1.0, 0.0, 0.0),
vec4(0.0, 0.0, 0.5, 0.0),
vec4(0.0, 0.0, 0.5, 1.0),
);

impl Camera {
pub fn build_view_proj_matrix(&self, bounds: Rectangle) -> glam::Mat4 {
//TODO looks distorted without padding; base on surface texture size instead?
let aspect_ratio = bounds.width / (bounds.height + 150.0);

let view = glam::Mat4::look_at_rh(self.eye, self.target, self.up);
let proj = glam::Mat4::perspective_rh(
self.fov_y,
aspect_ratio,
self.near,
self.far,
);

OPENGL_TO_WGPU_MATRIX * proj * view
}

pub fn position(&self) -> glam::Vec4 {
glam::Vec4::from((self.eye, 0.0))
}
}
99 changes: 99 additions & 0 deletions examples/custom_shader/src/cubes.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
use crate::camera::Camera;
use crate::primitive;
use crate::primitive::cube::Cube;
use glam::Vec3;
use iced::widget::shader;
use iced::{mouse, Color, Rectangle};
use rand::Rng;
use std::cmp::Ordering;
use std::iter;
use std::time::Duration;

pub const MAX: u32 = 500;

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

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

cubes.adjust_num_cubes(MAX);

cubes
}

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

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

match num_cubes.cmp(&curr_cubes) {
Ordering::Greater => {
// spawn
let cubes_2_spawn = (num_cubes - 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 - num_cubes;
let new_len = self.cubes.len() - cubes_2_cut as usize;
self.cubes.truncate(new_len);
}
Ordering::Equal => {}
}
}
}

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

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

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),
)
}
179 changes: 179 additions & 0 deletions examples/custom_shader/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
mod camera;
mod cubes;
mod pipeline;
mod primitive;

use crate::camera::Camera;
use crate::cubes::Cubes;
use crate::pipeline::Pipeline;

use iced::executor;
use iced::time::Instant;
use iced::widget::{
checkbox, column, container, row, slider, text, vertical_space, Shader,
};
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,
cubes: Cubes,
num_cubes_slider: u32,
}

impl Default for IcedCubes {
fn default() -> Self {
Self {
start: Instant::now(),
cubes: Cubes::new(),
num_cubes_slider: cubes::MAX,
}
}
}

#[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>) {
(IcedCubes::default(), Command::none())
}

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

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

Command::none()
}

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

let bottom_controls = row![
control(
"R",
slider(0.0..=1.0, self.cubes.light_color.r, move |r| {
Message::LightColorChanged(Color {
r,
..self.cubes.light_color
})
})
.step(0.01)
.width(100)
),
control(
"G",
slider(0.0..=1.0, self.cubes.light_color.g, move |g| {
Message::LightColorChanged(Color {
g,
..self.cubes.light_color
})
})
.step(0.01)
.width(100)
),
control(
"B",
slider(0.0..=1.0, self.cubes.light_color.b, move |b| {
Message::LightColorChanged(Color {
b,
..self.cubes.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::new(&self.cubes)
.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()
}
Loading
Loading