Added support for custom shader widget for iced_wgpu backend.
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -183,6 +183,17 @@ impl From<Rectangle<u32>> for Rectangle<f32> {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<Rectangle<f32>> for Rectangle<u32> {
|
||||
fn from(rectangle: Rectangle<f32>) -> Self {
|
||||
Rectangle {
|
||||
x: rectangle.x as u32,
|
||||
y: rectangle.y as u32,
|
||||
width: rectangle.width as u32,
|
||||
height: rectangle.height as u32,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> std::ops::Add<Vector<T>> for Rectangle<T>
|
||||
where
|
||||
T: std::ops::Add<Output = T>,
|
||||
|
|
|
|||
13
examples/custom_shader/Cargo.toml
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
[package]
|
||||
name = "custom_shader"
|
||||
version = "0.1.0"
|
||||
authors = ["Bingus <shankern@protonmail.com>"]
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
iced = { path = "../..", features = ["debug", "advanced"]}
|
||||
image = { version = "0.24.6"}
|
||||
wgpu = "0.17"
|
||||
bytemuck = { version = "1.13.1" }
|
||||
glam = { version = "0.24.0", features = ["bytemuck"] }
|
||||
rand = "0.8.5"
|
||||
53
examples/custom_shader/src/camera.rs
Normal file
|
|
@ -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
examples/custom_shader/src/cubes.rs
Normal file
|
|
@ -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);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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),
|
||||
)
|
||||
}
|
||||
174
examples/custom_shader/src/main.rs
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
mod camera;
|
||||
mod cubes;
|
||||
mod pipeline;
|
||||
mod primitive;
|
||||
|
||||
use crate::cubes::Cubes;
|
||||
use iced::widget::{
|
||||
checkbox, column, container, row, slider, text, vertical_space, Shader,
|
||||
};
|
||||
use iced::{
|
||||
executor, window, Alignment, Application, Color, Command, Element, Length,
|
||||
Renderer, Subscription, Theme,
|
||||
};
|
||||
use std::time::Instant;
|
||||
|
||||
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()
|
||||
}
|
||||
600
examples/custom_shader/src/pipeline.rs
Normal file
|
|
@ -0,0 +1,600 @@
|
|||
use crate::primitive;
|
||||
use crate::primitive::cube;
|
||||
use crate::primitive::{Buffer, Uniforms};
|
||||
use iced::{Rectangle, Size};
|
||||
use wgpu::util::DeviceExt;
|
||||
|
||||
const SKY_TEXTURE_SIZE: u32 = 128;
|
||||
|
||||
pub struct Pipeline {
|
||||
pipeline: wgpu::RenderPipeline,
|
||||
vertices: wgpu::Buffer,
|
||||
cubes: Buffer,
|
||||
uniforms: wgpu::Buffer,
|
||||
uniform_bind_group: wgpu::BindGroup,
|
||||
depth_texture_size: Size<u32>,
|
||||
depth_view: wgpu::TextureView,
|
||||
depth_pipeline: DepthPipeline,
|
||||
}
|
||||
|
||||
impl Pipeline {
|
||||
pub fn new(
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
format: wgpu::TextureFormat,
|
||||
target_size: Size<u32>,
|
||||
) -> Self {
|
||||
//vertices of one cube
|
||||
let vertices =
|
||||
device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("cubes vertex buffer"),
|
||||
contents: bytemuck::cast_slice(&cube::Raw::vertices()),
|
||||
usage: wgpu::BufferUsages::VERTEX,
|
||||
});
|
||||
|
||||
//cube instance data
|
||||
let cubes_buffer = Buffer::new(
|
||||
device,
|
||||
"cubes instance buffer",
|
||||
std::mem::size_of::<cube::Raw>() as u64,
|
||||
wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
|
||||
);
|
||||
|
||||
//uniforms for all cubes
|
||||
let uniforms = device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: Some("cubes uniform buffer"),
|
||||
size: std::mem::size_of::<Uniforms>() as u64,
|
||||
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
|
||||
mapped_at_creation: false,
|
||||
});
|
||||
|
||||
//depth buffer
|
||||
let depth_texture = device.create_texture(&wgpu::TextureDescriptor {
|
||||
label: Some("cubes depth texture"),
|
||||
size: wgpu::Extent3d {
|
||||
width: target_size.width,
|
||||
height: target_size.height,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format: wgpu::TextureFormat::Depth32Float,
|
||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT
|
||||
| wgpu::TextureUsages::TEXTURE_BINDING,
|
||||
view_formats: &[],
|
||||
});
|
||||
|
||||
let depth_view =
|
||||
depth_texture.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
|
||||
let normal_map_data = load_normal_map_data();
|
||||
|
||||
//normal map
|
||||
let normal_texture = device.create_texture_with_data(
|
||||
queue,
|
||||
&wgpu::TextureDescriptor {
|
||||
label: Some("cubes normal map texture"),
|
||||
size: wgpu::Extent3d {
|
||||
width: 1024,
|
||||
height: 1024,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format: wgpu::TextureFormat::Rgba8Unorm,
|
||||
usage: wgpu::TextureUsages::TEXTURE_BINDING,
|
||||
view_formats: &[],
|
||||
},
|
||||
&normal_map_data,
|
||||
);
|
||||
|
||||
let normal_view =
|
||||
normal_texture.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
|
||||
//skybox texture for reflection/refraction
|
||||
let skybox_data = load_skybox_data();
|
||||
|
||||
let skybox_texture = device.create_texture_with_data(
|
||||
queue,
|
||||
&wgpu::TextureDescriptor {
|
||||
label: Some("cubes skybox texture"),
|
||||
size: wgpu::Extent3d {
|
||||
width: SKY_TEXTURE_SIZE,
|
||||
height: SKY_TEXTURE_SIZE,
|
||||
depth_or_array_layers: 6, //one for each face of the cube
|
||||
},
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format: wgpu::TextureFormat::Rgba8Unorm,
|
||||
usage: wgpu::TextureUsages::TEXTURE_BINDING,
|
||||
view_formats: &[],
|
||||
},
|
||||
&skybox_data,
|
||||
);
|
||||
|
||||
let sky_view =
|
||||
skybox_texture.create_view(&wgpu::TextureViewDescriptor {
|
||||
label: Some("cubes skybox texture view"),
|
||||
dimension: Some(wgpu::TextureViewDimension::Cube),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let sky_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
|
||||
label: Some("cubes skybox sampler"),
|
||||
address_mode_u: wgpu::AddressMode::ClampToEdge,
|
||||
address_mode_v: wgpu::AddressMode::ClampToEdge,
|
||||
address_mode_w: wgpu::AddressMode::ClampToEdge,
|
||||
mag_filter: wgpu::FilterMode::Linear,
|
||||
min_filter: wgpu::FilterMode::Linear,
|
||||
mipmap_filter: wgpu::FilterMode::Linear,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let uniform_bind_group_layout =
|
||||
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
label: Some("cubes uniform bind group layout"),
|
||||
entries: &[
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Uniform,
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: None,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 1,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Texture {
|
||||
sample_type: wgpu::TextureSampleType::Float {
|
||||
filterable: true,
|
||||
},
|
||||
view_dimension: wgpu::TextureViewDimension::Cube,
|
||||
multisampled: false,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 2,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Sampler(
|
||||
wgpu::SamplerBindingType::Filtering,
|
||||
),
|
||||
count: None,
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 3,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Texture {
|
||||
sample_type: wgpu::TextureSampleType::Float {
|
||||
filterable: true,
|
||||
},
|
||||
view_dimension: wgpu::TextureViewDimension::D2,
|
||||
multisampled: false,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
let uniform_bind_group =
|
||||
device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: Some("cubes uniform bind group"),
|
||||
layout: &uniform_bind_group_layout,
|
||||
entries: &[
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: uniforms.as_entire_binding(),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 1,
|
||||
resource: wgpu::BindingResource::TextureView(&sky_view),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 2,
|
||||
resource: wgpu::BindingResource::Sampler(&sky_sampler),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 3,
|
||||
resource: wgpu::BindingResource::TextureView(
|
||||
&normal_view,
|
||||
),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
let layout =
|
||||
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("cubes pipeline layout"),
|
||||
bind_group_layouts: &[&uniform_bind_group_layout],
|
||||
push_constant_ranges: &[],
|
||||
});
|
||||
|
||||
let shader =
|
||||
device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: Some("cubes shader"),
|
||||
source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(
|
||||
include_str!("shaders/cubes.wgsl"),
|
||||
)),
|
||||
});
|
||||
|
||||
let pipeline =
|
||||
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
label: Some("cubes pipeline"),
|
||||
layout: Some(&layout),
|
||||
vertex: wgpu::VertexState {
|
||||
module: &shader,
|
||||
entry_point: "vs_main",
|
||||
buffers: &[primitive::Vertex::desc(), cube::Raw::desc()],
|
||||
},
|
||||
primitive: wgpu::PrimitiveState::default(),
|
||||
depth_stencil: Some(wgpu::DepthStencilState {
|
||||
format: wgpu::TextureFormat::Depth32Float,
|
||||
depth_write_enabled: true,
|
||||
depth_compare: wgpu::CompareFunction::Less,
|
||||
stencil: wgpu::StencilState::default(),
|
||||
bias: wgpu::DepthBiasState::default(),
|
||||
}),
|
||||
multisample: wgpu::MultisampleState {
|
||||
count: 1,
|
||||
mask: !0,
|
||||
alpha_to_coverage_enabled: false,
|
||||
},
|
||||
fragment: Some(wgpu::FragmentState {
|
||||
module: &shader,
|
||||
entry_point: "fs_main",
|
||||
targets: &[Some(wgpu::ColorTargetState {
|
||||
format,
|
||||
blend: Some(wgpu::BlendState {
|
||||
color: wgpu::BlendComponent {
|
||||
src_factor: wgpu::BlendFactor::SrcAlpha,
|
||||
dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
|
||||
operation: wgpu::BlendOperation::Add,
|
||||
},
|
||||
alpha: wgpu::BlendComponent {
|
||||
src_factor: wgpu::BlendFactor::One,
|
||||
dst_factor: wgpu::BlendFactor::One,
|
||||
operation: wgpu::BlendOperation::Max,
|
||||
},
|
||||
}),
|
||||
write_mask: wgpu::ColorWrites::ALL,
|
||||
})],
|
||||
}),
|
||||
multiview: None,
|
||||
});
|
||||
|
||||
let depth_pipeline = DepthPipeline::new(
|
||||
device,
|
||||
format,
|
||||
depth_texture.create_view(&wgpu::TextureViewDescriptor::default()),
|
||||
);
|
||||
|
||||
Self {
|
||||
pipeline,
|
||||
cubes: cubes_buffer,
|
||||
uniforms,
|
||||
uniform_bind_group,
|
||||
vertices,
|
||||
depth_texture_size: target_size,
|
||||
depth_view,
|
||||
depth_pipeline,
|
||||
}
|
||||
}
|
||||
|
||||
fn update_depth_texture(&mut self, device: &wgpu::Device, size: Size<u32>) {
|
||||
if self.depth_texture_size.height != size.height
|
||||
|| self.depth_texture_size.width != size.width
|
||||
{
|
||||
let text = device.create_texture(&wgpu::TextureDescriptor {
|
||||
label: Some("cubes depth texture"),
|
||||
size: wgpu::Extent3d {
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format: wgpu::TextureFormat::Depth32Float,
|
||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT
|
||||
| wgpu::TextureUsages::TEXTURE_BINDING,
|
||||
view_formats: &[],
|
||||
});
|
||||
|
||||
self.depth_view =
|
||||
text.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
self.depth_texture_size = size;
|
||||
|
||||
self.depth_pipeline.update(device, &text);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
target_size: Size<u32>,
|
||||
uniforms: &Uniforms,
|
||||
num_cubes: usize,
|
||||
cubes: &[cube::Raw],
|
||||
) {
|
||||
//recreate depth texture if surface texture size has changed
|
||||
self.update_depth_texture(device, target_size);
|
||||
|
||||
// update uniforms
|
||||
queue.write_buffer(&self.uniforms, 0, bytemuck::bytes_of(uniforms));
|
||||
|
||||
//resize cubes vertex buffer if cubes amount changed
|
||||
let new_size = num_cubes * std::mem::size_of::<cube::Raw>();
|
||||
self.cubes.resize(device, new_size as u64);
|
||||
|
||||
//always write new cube data since they are constantly rotating
|
||||
queue.write_buffer(&self.cubes.raw, 0, bytemuck::cast_slice(cubes));
|
||||
}
|
||||
|
||||
pub fn render(
|
||||
&self,
|
||||
target: &wgpu::TextureView,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
bounds: Rectangle<u32>,
|
||||
num_cubes: u32,
|
||||
show_depth: bool,
|
||||
) {
|
||||
{
|
||||
let mut pass =
|
||||
encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: Some("cubes.pipeline.pass"),
|
||||
color_attachments: &[Some(
|
||||
wgpu::RenderPassColorAttachment {
|
||||
view: target,
|
||||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
load: wgpu::LoadOp::Load,
|
||||
store: true,
|
||||
},
|
||||
},
|
||||
)],
|
||||
depth_stencil_attachment: Some(
|
||||
wgpu::RenderPassDepthStencilAttachment {
|
||||
view: &self.depth_view,
|
||||
depth_ops: Some(wgpu::Operations {
|
||||
load: wgpu::LoadOp::Clear(1.0),
|
||||
store: true,
|
||||
}),
|
||||
stencil_ops: None,
|
||||
},
|
||||
),
|
||||
});
|
||||
|
||||
pass.set_scissor_rect(
|
||||
bounds.x,
|
||||
bounds.y,
|
||||
bounds.width,
|
||||
bounds.height,
|
||||
);
|
||||
pass.set_pipeline(&self.pipeline);
|
||||
pass.set_bind_group(0, &self.uniform_bind_group, &[]);
|
||||
pass.set_vertex_buffer(0, self.vertices.slice(..));
|
||||
pass.set_vertex_buffer(1, self.cubes.raw.slice(..));
|
||||
pass.draw(0..36, 0..num_cubes);
|
||||
}
|
||||
|
||||
if show_depth {
|
||||
self.depth_pipeline.render(encoder, target, bounds);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct DepthPipeline {
|
||||
pipeline: wgpu::RenderPipeline,
|
||||
bind_group_layout: wgpu::BindGroupLayout,
|
||||
bind_group: wgpu::BindGroup,
|
||||
sampler: wgpu::Sampler,
|
||||
depth_view: wgpu::TextureView,
|
||||
}
|
||||
|
||||
impl DepthPipeline {
|
||||
pub fn new(
|
||||
device: &wgpu::Device,
|
||||
format: wgpu::TextureFormat,
|
||||
depth_texture: wgpu::TextureView,
|
||||
) -> Self {
|
||||
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
|
||||
label: Some("cubes.depth_pipeline.sampler"),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let bind_group_layout =
|
||||
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
label: Some("cubes.depth_pipeline.bind_group_layout"),
|
||||
entries: &[
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Sampler(
|
||||
wgpu::SamplerBindingType::NonFiltering,
|
||||
),
|
||||
count: None,
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 1,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Texture {
|
||||
sample_type: wgpu::TextureSampleType::Float {
|
||||
filterable: false,
|
||||
},
|
||||
view_dimension: wgpu::TextureViewDimension::D2,
|
||||
multisampled: false,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: Some("cubes.depth_pipeline.bind_group"),
|
||||
layout: &bind_group_layout,
|
||||
entries: &[
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::Sampler(&sampler),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 1,
|
||||
resource: wgpu::BindingResource::TextureView(
|
||||
&depth_texture,
|
||||
),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
let layout =
|
||||
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("cubes.depth_pipeline.layout"),
|
||||
bind_group_layouts: &[&bind_group_layout],
|
||||
push_constant_ranges: &[],
|
||||
});
|
||||
|
||||
let shader =
|
||||
device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: Some("cubes.depth_pipeline.shader"),
|
||||
source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(
|
||||
include_str!("shaders/depth.wgsl"),
|
||||
)),
|
||||
});
|
||||
|
||||
let pipeline =
|
||||
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
label: Some("cubes.depth_pipeline.pipeline"),
|
||||
layout: Some(&layout),
|
||||
vertex: wgpu::VertexState {
|
||||
module: &shader,
|
||||
entry_point: "vs_main",
|
||||
buffers: &[],
|
||||
},
|
||||
primitive: Default::default(),
|
||||
depth_stencil: Some(wgpu::DepthStencilState {
|
||||
format: wgpu::TextureFormat::Depth32Float,
|
||||
depth_write_enabled: false,
|
||||
depth_compare: wgpu::CompareFunction::Less,
|
||||
stencil: Default::default(),
|
||||
bias: Default::default(),
|
||||
}),
|
||||
multisample: Default::default(),
|
||||
fragment: Some(wgpu::FragmentState {
|
||||
module: &shader,
|
||||
entry_point: "fs_main",
|
||||
targets: &[Some(wgpu::ColorTargetState {
|
||||
format,
|
||||
blend: Some(wgpu::BlendState::REPLACE),
|
||||
write_mask: wgpu::ColorWrites::ALL,
|
||||
})],
|
||||
}),
|
||||
multiview: None,
|
||||
});
|
||||
|
||||
Self {
|
||||
pipeline,
|
||||
bind_group_layout,
|
||||
bind_group,
|
||||
sampler,
|
||||
depth_view: depth_texture,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
depth_texture: &wgpu::Texture,
|
||||
) {
|
||||
self.depth_view =
|
||||
depth_texture.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
|
||||
self.bind_group =
|
||||
device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: Some("cubes.depth_pipeline.bind_group"),
|
||||
layout: &self.bind_group_layout,
|
||||
entries: &[
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::Sampler(&self.sampler),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 1,
|
||||
resource: wgpu::BindingResource::TextureView(
|
||||
&self.depth_view,
|
||||
),
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
pub fn render(
|
||||
&self,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
target: &wgpu::TextureView,
|
||||
bounds: Rectangle<u32>,
|
||||
) {
|
||||
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: Some("cubes.pipeline.depth_pass"),
|
||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||
view: target,
|
||||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
load: wgpu::LoadOp::Load,
|
||||
store: true,
|
||||
},
|
||||
})],
|
||||
depth_stencil_attachment: Some(
|
||||
wgpu::RenderPassDepthStencilAttachment {
|
||||
view: &self.depth_view,
|
||||
depth_ops: None,
|
||||
stencil_ops: None,
|
||||
},
|
||||
),
|
||||
});
|
||||
|
||||
pass.set_scissor_rect(bounds.x, bounds.y, bounds.width, bounds.height);
|
||||
pass.set_pipeline(&self.pipeline);
|
||||
pass.set_bind_group(0, &self.bind_group, &[]);
|
||||
pass.draw(0..6, 0..1);
|
||||
}
|
||||
}
|
||||
|
||||
fn load_skybox_data() -> Vec<u8> {
|
||||
let pos_x: &[u8] = include_bytes!("textures/skybox/pos_x.jpg");
|
||||
let neg_x: &[u8] = include_bytes!("textures/skybox/neg_x.jpg");
|
||||
let pos_y: &[u8] = include_bytes!("textures/skybox/pos_y.jpg");
|
||||
let neg_y: &[u8] = include_bytes!("textures/skybox/neg_y.jpg");
|
||||
let pos_z: &[u8] = include_bytes!("textures/skybox/pos_z.jpg");
|
||||
let neg_z: &[u8] = include_bytes!("textures/skybox/neg_z.jpg");
|
||||
|
||||
let data: [&[u8]; 6] = [pos_x, neg_x, pos_y, neg_y, pos_z, neg_z];
|
||||
|
||||
data.iter().fold(vec![], |mut acc, bytes| {
|
||||
let i = image::load_from_memory_with_format(
|
||||
bytes,
|
||||
image::ImageFormat::Jpeg,
|
||||
)
|
||||
.unwrap()
|
||||
.to_rgba8()
|
||||
.into_raw();
|
||||
|
||||
acc.extend(i);
|
||||
acc
|
||||
})
|
||||
}
|
||||
|
||||
fn load_normal_map_data() -> Vec<u8> {
|
||||
let bytes: &[u8] = include_bytes!("textures/ice_cube_normal_map.png");
|
||||
|
||||
image::load_from_memory_with_format(bytes, image::ImageFormat::Png)
|
||||
.unwrap()
|
||||
.to_rgba8()
|
||||
.into_raw()
|
||||
}
|
||||
95
examples/custom_shader/src/primitive.rs
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
pub mod cube;
|
||||
pub mod vertex;
|
||||
|
||||
mod buffer;
|
||||
mod uniforms;
|
||||
|
||||
use crate::camera::Camera;
|
||||
use crate::pipeline::Pipeline;
|
||||
use crate::primitive::cube::Cube;
|
||||
use iced::advanced::graphics::Transformation;
|
||||
use iced::widget::shader;
|
||||
use iced::{Color, Rectangle, Size};
|
||||
|
||||
pub use crate::primitive::vertex::Vertex;
|
||||
pub use buffer::Buffer;
|
||||
pub use uniforms::Uniforms;
|
||||
|
||||
/// A collection of `Cube`s that can be rendered.
|
||||
#[derive(Debug)]
|
||||
pub struct Primitive {
|
||||
cubes: Vec<cube::Raw>,
|
||||
uniforms: 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 = 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: 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,
|
||||
)
|
||||
}
|
||||
}
|
||||
39
examples/custom_shader/src/primitive/buffer.rs
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
// A custom buffer container for dynamic resizing.
|
||||
pub struct Buffer {
|
||||
pub raw: wgpu::Buffer,
|
||||
label: &'static str,
|
||||
size: u64,
|
||||
usage: wgpu::BufferUsages,
|
||||
}
|
||||
|
||||
impl Buffer {
|
||||
pub fn new(
|
||||
device: &wgpu::Device,
|
||||
label: &'static str,
|
||||
size: u64,
|
||||
usage: wgpu::BufferUsages,
|
||||
) -> Self {
|
||||
Self {
|
||||
raw: device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: Some(label),
|
||||
size,
|
||||
usage,
|
||||
mapped_at_creation: false,
|
||||
}),
|
||||
label,
|
||||
size,
|
||||
usage,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resize(&mut self, device: &wgpu::Device, new_size: u64) {
|
||||
if new_size > self.size {
|
||||
self.raw = device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: Some(self.label),
|
||||
size: new_size,
|
||||
usage: self.usage,
|
||||
mapped_at_creation: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
324
examples/custom_shader/src/primitive/cube.rs
Normal file
|
|
@ -0,0 +1,324 @@
|
|||
use crate::primitive::Vertex;
|
||||
use glam::{vec2, vec3, Vec3};
|
||||
use rand::{thread_rng, Rng};
|
||||
|
||||
/// A single instance of a cube.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Cube {
|
||||
pub rotation: glam::Quat,
|
||||
pub position: Vec3,
|
||||
pub size: f32,
|
||||
rotation_dir: f32,
|
||||
rotation_axis: glam::Vec3,
|
||||
}
|
||||
|
||||
impl Default for Cube {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
rotation: glam::Quat::IDENTITY,
|
||||
position: glam::Vec3::ZERO,
|
||||
size: 0.1,
|
||||
rotation_dir: 1.0,
|
||||
rotation_axis: glam::Vec3::Y,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Cube {
|
||||
pub fn new(size: f32, origin: Vec3) -> Self {
|
||||
let rnd = thread_rng().gen_range(0.0..=1.0f32);
|
||||
|
||||
Self {
|
||||
rotation: glam::Quat::IDENTITY,
|
||||
position: origin + Vec3::new(0.1, 0.1, 0.1),
|
||||
size,
|
||||
rotation_dir: if rnd <= 0.5 { -1.0 } else { 1.0 },
|
||||
rotation_axis: if rnd <= 0.33 {
|
||||
glam::Vec3::Y
|
||||
} else if rnd <= 0.66 {
|
||||
glam::Vec3::X
|
||||
} else {
|
||||
glam::Vec3::Z
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(&mut self, size: f32, time: f32) {
|
||||
self.rotation = glam::Quat::from_axis_angle(
|
||||
self.rotation_axis,
|
||||
time / 2.0 * self.rotation_dir,
|
||||
);
|
||||
self.size = size;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable, Debug)]
|
||||
#[repr(C)]
|
||||
pub struct Raw {
|
||||
transformation: glam::Mat4,
|
||||
normal: glam::Mat3,
|
||||
_padding: [f32; 3],
|
||||
}
|
||||
|
||||
impl Raw {
|
||||
const ATTRIBS: [wgpu::VertexAttribute; 7] = wgpu::vertex_attr_array![
|
||||
//cube transformation matrix
|
||||
4 => Float32x4,
|
||||
5 => Float32x4,
|
||||
6 => Float32x4,
|
||||
7 => Float32x4,
|
||||
//normal rotation matrix
|
||||
8 => Float32x3,
|
||||
9 => Float32x3,
|
||||
10 => Float32x3,
|
||||
];
|
||||
|
||||
pub fn desc<'a>() -> wgpu::VertexBufferLayout<'a> {
|
||||
wgpu::VertexBufferLayout {
|
||||
array_stride: std::mem::size_of::<Self>() as wgpu::BufferAddress,
|
||||
step_mode: wgpu::VertexStepMode::Instance,
|
||||
attributes: &Self::ATTRIBS,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Raw {
|
||||
pub fn from_cube(cube: &Cube) -> Raw {
|
||||
Raw {
|
||||
transformation: glam::Mat4::from_scale_rotation_translation(
|
||||
glam::vec3(cube.size, cube.size, cube.size),
|
||||
cube.rotation,
|
||||
cube.position,
|
||||
),
|
||||
normal: glam::Mat3::from_quat(cube.rotation),
|
||||
_padding: [0.0; 3],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn vertices() -> [Vertex; 36] {
|
||||
[
|
||||
//face 1
|
||||
Vertex {
|
||||
pos: vec3(-0.5, -0.5, -0.5),
|
||||
normal: vec3(0.0, 0.0, -1.0),
|
||||
tangent: vec3(1.0, 0.0, 0.0),
|
||||
uv: vec2(0.0, 1.0),
|
||||
},
|
||||
Vertex {
|
||||
pos: vec3(0.5, -0.5, -0.5),
|
||||
normal: vec3(0.0, 0.0, -1.0),
|
||||
tangent: vec3(1.0, 0.0, 0.0),
|
||||
uv: vec2(1.0, 1.0),
|
||||
},
|
||||
Vertex {
|
||||
pos: vec3(0.5, 0.5, -0.5),
|
||||
normal: vec3(0.0, 0.0, -1.0),
|
||||
tangent: vec3(1.0, 0.0, 0.0),
|
||||
uv: vec2(1.0, 0.0),
|
||||
},
|
||||
Vertex {
|
||||
pos: vec3(0.5, 0.5, -0.5),
|
||||
normal: vec3(0.0, 0.0, -1.0),
|
||||
tangent: vec3(1.0, 0.0, 0.0),
|
||||
uv: vec2(1.0, 0.0),
|
||||
},
|
||||
Vertex {
|
||||
pos: vec3(-0.5, 0.5, -0.5),
|
||||
normal: vec3(0.0, 0.0, -1.0),
|
||||
tangent: vec3(1.0, 0.0, 0.0),
|
||||
uv: vec2(0.0, 0.0),
|
||||
},
|
||||
Vertex {
|
||||
pos: vec3(-0.5, -0.5, -0.5),
|
||||
normal: vec3(0.0, 0.0, -1.0),
|
||||
tangent: vec3(1.0, 0.0, 0.0),
|
||||
uv: vec2(0.0, 1.0),
|
||||
},
|
||||
//face 2
|
||||
Vertex {
|
||||
pos: vec3(-0.5, -0.5, 0.5),
|
||||
normal: vec3(0.0, 0.0, 1.0),
|
||||
tangent: vec3(1.0, 0.0, 0.0),
|
||||
uv: vec2(0.0, 1.0),
|
||||
},
|
||||
Vertex {
|
||||
pos: vec3(0.5, -0.5, 0.5),
|
||||
normal: vec3(0.0, 0.0, 1.0),
|
||||
tangent: vec3(1.0, 0.0, 0.0),
|
||||
uv: vec2(1.0, 1.0),
|
||||
},
|
||||
Vertex {
|
||||
pos: vec3(0.5, 0.5, 0.5),
|
||||
normal: vec3(0.0, 0.0, 1.0),
|
||||
tangent: vec3(1.0, 0.0, 0.0),
|
||||
uv: vec2(1.0, 0.0),
|
||||
},
|
||||
Vertex {
|
||||
pos: vec3(0.5, 0.5, 0.5),
|
||||
normal: vec3(0.0, 0.0, 1.0),
|
||||
tangent: vec3(1.0, 0.0, 0.0),
|
||||
uv: vec2(1.0, 0.0),
|
||||
},
|
||||
Vertex {
|
||||
pos: vec3(-0.5, 0.5, 0.5),
|
||||
normal: vec3(0.0, 0.0, 1.0),
|
||||
tangent: vec3(1.0, 0.0, 0.0),
|
||||
uv: vec2(0.0, 0.0),
|
||||
},
|
||||
Vertex {
|
||||
pos: vec3(-0.5, -0.5, 0.5),
|
||||
normal: vec3(0.0, 0.0, 1.0),
|
||||
tangent: vec3(1.0, 0.0, 0.0),
|
||||
uv: vec2(0.0, 1.0),
|
||||
},
|
||||
//face 3
|
||||
Vertex {
|
||||
pos: vec3(-0.5, 0.5, 0.5),
|
||||
normal: vec3(-1.0, 0.0, 0.0),
|
||||
tangent: vec3(0.0, 0.0, -1.0),
|
||||
uv: vec2(0.0, 1.0),
|
||||
},
|
||||
Vertex {
|
||||
pos: vec3(-0.5, 0.5, -0.5),
|
||||
normal: vec3(-1.0, 0.0, 0.0),
|
||||
tangent: vec3(0.0, 0.0, -1.0),
|
||||
uv: vec2(1.0, 1.0),
|
||||
},
|
||||
Vertex {
|
||||
pos: vec3(-0.5, -0.5, -0.5),
|
||||
normal: vec3(-1.0, 0.0, 0.0),
|
||||
tangent: vec3(0.0, 0.0, -1.0),
|
||||
uv: vec2(1.0, 0.0),
|
||||
},
|
||||
Vertex {
|
||||
pos: vec3(-0.5, -0.5, -0.5),
|
||||
normal: vec3(-1.0, 0.0, 0.0),
|
||||
tangent: vec3(0.0, 0.0, -1.0),
|
||||
uv: vec2(1.0, 0.0),
|
||||
},
|
||||
Vertex {
|
||||
pos: vec3(-0.5, -0.5, 0.5),
|
||||
normal: vec3(-1.0, 0.0, 0.0),
|
||||
tangent: vec3(0.0, 0.0, -1.0),
|
||||
uv: vec2(0.0, 0.0),
|
||||
},
|
||||
Vertex {
|
||||
pos: vec3(-0.5, 0.5, 0.5),
|
||||
normal: vec3(-1.0, 0.0, 0.0),
|
||||
tangent: vec3(0.0, 0.0, -1.0),
|
||||
uv: vec2(0.0, 1.0),
|
||||
},
|
||||
//face 4
|
||||
Vertex {
|
||||
pos: vec3(0.5, 0.5, 0.5),
|
||||
normal: vec3(1.0, 0.0, 0.0),
|
||||
tangent: vec3(0.0, 0.0, -1.0),
|
||||
uv: vec2(0.0, 1.0),
|
||||
},
|
||||
Vertex {
|
||||
pos: vec3(0.5, 0.5, -0.5),
|
||||
normal: vec3(1.0, 0.0, 0.0),
|
||||
tangent: vec3(0.0, 0.0, -1.0),
|
||||
uv: vec2(1.0, 1.0),
|
||||
},
|
||||
Vertex {
|
||||
pos: vec3(0.5, -0.5, -0.5),
|
||||
normal: vec3(1.0, 0.0, 0.0),
|
||||
tangent: vec3(0.0, 0.0, -1.0),
|
||||
uv: vec2(1.0, 0.0),
|
||||
},
|
||||
Vertex {
|
||||
pos: vec3(0.5, -0.5, -0.5),
|
||||
normal: vec3(1.0, 0.0, 0.0),
|
||||
tangent: vec3(0.0, 0.0, -1.0),
|
||||
uv: vec2(1.0, 0.0),
|
||||
},
|
||||
Vertex {
|
||||
pos: vec3(0.5, -0.5, 0.5),
|
||||
normal: vec3(1.0, 0.0, 0.0),
|
||||
tangent: vec3(0.0, 0.0, -1.0),
|
||||
uv: vec2(0.0, 0.0),
|
||||
},
|
||||
Vertex {
|
||||
pos: vec3(0.5, 0.5, 0.5),
|
||||
normal: vec3(1.0, 0.0, 0.0),
|
||||
tangent: vec3(0.0, 0.0, -1.0),
|
||||
uv: vec2(0.0, 1.0),
|
||||
},
|
||||
//face 5
|
||||
Vertex {
|
||||
pos: vec3(-0.5, -0.5, -0.5),
|
||||
normal: vec3(0.0, -1.0, 0.0),
|
||||
tangent: vec3(1.0, 0.0, 0.0),
|
||||
uv: vec2(0.0, 1.0),
|
||||
},
|
||||
Vertex {
|
||||
pos: vec3(0.5, -0.5, -0.5),
|
||||
normal: vec3(0.0, -1.0, 0.0),
|
||||
tangent: vec3(1.0, 0.0, 0.0),
|
||||
uv: vec2(1.0, 1.0),
|
||||
},
|
||||
Vertex {
|
||||
pos: vec3(0.5, -0.5, 0.5),
|
||||
normal: vec3(0.0, -1.0, 0.0),
|
||||
tangent: vec3(1.0, 0.0, 0.0),
|
||||
uv: vec2(1.0, 0.0),
|
||||
},
|
||||
Vertex {
|
||||
pos: vec3(0.5, -0.5, 0.5),
|
||||
normal: vec3(0.0, -1.0, 0.0),
|
||||
tangent: vec3(1.0, 0.0, 0.0),
|
||||
uv: vec2(1.0, 0.0),
|
||||
},
|
||||
Vertex {
|
||||
pos: vec3(-0.5, -0.5, 0.5),
|
||||
normal: vec3(0.0, -1.0, 0.0),
|
||||
tangent: vec3(1.0, 0.0, 0.0),
|
||||
uv: vec2(0.0, 0.0),
|
||||
},
|
||||
Vertex {
|
||||
pos: vec3(-0.5, -0.5, -0.5),
|
||||
normal: vec3(0.0, -1.0, 0.0),
|
||||
tangent: vec3(1.0, 0.0, 0.0),
|
||||
uv: vec2(0.0, 1.0),
|
||||
},
|
||||
//face 6
|
||||
Vertex {
|
||||
pos: vec3(-0.5, 0.5, -0.5),
|
||||
normal: vec3(0.0, 1.0, 0.0),
|
||||
tangent: vec3(1.0, 0.0, 0.0),
|
||||
uv: vec2(0.0, 1.0),
|
||||
},
|
||||
Vertex {
|
||||
pos: vec3(0.5, 0.5, -0.5),
|
||||
normal: vec3(0.0, 1.0, 0.0),
|
||||
tangent: vec3(1.0, 0.0, 0.0),
|
||||
uv: vec2(1.0, 1.0),
|
||||
},
|
||||
Vertex {
|
||||
pos: vec3(0.5, 0.5, 0.5),
|
||||
normal: vec3(0.0, 1.0, 0.0),
|
||||
tangent: vec3(1.0, 0.0, 0.0),
|
||||
uv: vec2(1.0, 0.0),
|
||||
},
|
||||
Vertex {
|
||||
pos: vec3(0.5, 0.5, 0.5),
|
||||
normal: vec3(0.0, 1.0, 0.0),
|
||||
tangent: vec3(1.0, 0.0, 0.0),
|
||||
uv: vec2(1.0, 0.0),
|
||||
},
|
||||
Vertex {
|
||||
pos: vec3(-0.5, 0.5, 0.5),
|
||||
normal: vec3(0.0, 1.0, 0.0),
|
||||
tangent: vec3(1.0, 0.0, 0.0),
|
||||
uv: vec2(0.0, 0.0),
|
||||
},
|
||||
Vertex {
|
||||
pos: vec3(-0.5, 0.5, -0.5),
|
||||
normal: vec3(0.0, 1.0, 0.0),
|
||||
tangent: vec3(1.0, 0.0, 0.0),
|
||||
uv: vec2(0.0, 1.0),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
22
examples/custom_shader/src/primitive/uniforms.rs
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
use crate::camera::Camera;
|
||||
use iced::{Color, Rectangle};
|
||||
|
||||
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
#[repr(C)]
|
||||
pub struct Uniforms {
|
||||
camera_proj: glam::Mat4,
|
||||
camera_pos: glam::Vec4,
|
||||
light_color: glam::Vec4,
|
||||
}
|
||||
|
||||
impl Uniforms {
|
||||
pub fn new(camera: &Camera, bounds: Rectangle, light_color: Color) -> Self {
|
||||
let camera_proj = camera.build_view_proj_matrix(bounds);
|
||||
|
||||
Self {
|
||||
camera_proj,
|
||||
camera_pos: camera.position(),
|
||||
light_color: glam::Vec4::from(light_color.into_linear()),
|
||||
}
|
||||
}
|
||||
}
|
||||
29
examples/custom_shader/src/primitive/vertex.rs
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
#[repr(C)]
|
||||
pub struct Vertex {
|
||||
pub pos: glam::Vec3,
|
||||
pub normal: glam::Vec3,
|
||||
pub tangent: glam::Vec3,
|
||||
pub uv: glam::Vec2,
|
||||
}
|
||||
|
||||
impl Vertex {
|
||||
const ATTRIBS: [wgpu::VertexAttribute; 4] = wgpu::vertex_attr_array![
|
||||
//position
|
||||
0 => Float32x3,
|
||||
//normal
|
||||
1 => Float32x3,
|
||||
//tangent
|
||||
2 => Float32x3,
|
||||
//uv
|
||||
3 => Float32x2,
|
||||
];
|
||||
|
||||
pub fn desc<'a>() -> wgpu::VertexBufferLayout<'a> {
|
||||
wgpu::VertexBufferLayout {
|
||||
array_stride: std::mem::size_of::<Self>() as wgpu::BufferAddress,
|
||||
step_mode: wgpu::VertexStepMode::Vertex,
|
||||
attributes: &Self::ATTRIBS,
|
||||
}
|
||||
}
|
||||
}
|
||||
123
examples/custom_shader/src/shaders/cubes.wgsl
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
struct Uniforms {
|
||||
projection: mat4x4<f32>,
|
||||
camera_pos: vec4<f32>,
|
||||
light_color: vec4<f32>,
|
||||
}
|
||||
|
||||
const LIGHT_POS: vec3<f32> = vec3<f32>(0.0, 3.0, 3.0);
|
||||
|
||||
@group(0) @binding(0) var<uniform> uniforms: Uniforms;
|
||||
@group(0) @binding(1) var sky_texture: texture_cube<f32>;
|
||||
@group(0) @binding(2) var tex_sampler: sampler;
|
||||
@group(0) @binding(3) var normal_texture: texture_2d<f32>;
|
||||
|
||||
struct Vertex {
|
||||
@location(0) position: vec3<f32>,
|
||||
@location(1) normal: vec3<f32>,
|
||||
@location(2) tangent: vec3<f32>,
|
||||
@location(3) uv: vec2<f32>,
|
||||
}
|
||||
|
||||
struct Cube {
|
||||
@location(4) matrix_0: vec4<f32>,
|
||||
@location(5) matrix_1: vec4<f32>,
|
||||
@location(6) matrix_2: vec4<f32>,
|
||||
@location(7) matrix_3: vec4<f32>,
|
||||
@location(8) normal_matrix_0: vec3<f32>,
|
||||
@location(9) normal_matrix_1: vec3<f32>,
|
||||
@location(10) normal_matrix_2: vec3<f32>,
|
||||
}
|
||||
|
||||
struct Output {
|
||||
@builtin(position) clip_pos: vec4<f32>,
|
||||
@location(0) uv: vec2<f32>,
|
||||
@location(1) tangent_pos: vec3<f32>,
|
||||
@location(2) tangent_camera_pos: vec3<f32>,
|
||||
@location(3) tangent_light_pos: vec3<f32>,
|
||||
}
|
||||
|
||||
@vertex
|
||||
fn vs_main(vertex: Vertex, cube: Cube) -> Output {
|
||||
let cube_matrix = mat4x4<f32>(
|
||||
cube.matrix_0,
|
||||
cube.matrix_1,
|
||||
cube.matrix_2,
|
||||
cube.matrix_3,
|
||||
);
|
||||
|
||||
let normal_matrix = mat3x3<f32>(
|
||||
cube.normal_matrix_0,
|
||||
cube.normal_matrix_1,
|
||||
cube.normal_matrix_2,
|
||||
);
|
||||
|
||||
//convert to tangent space to calculate lighting in same coordinate space as normal map sample
|
||||
let tangent = normalize(normal_matrix * vertex.tangent);
|
||||
let normal = normalize(normal_matrix * vertex.normal);
|
||||
let bitangent = cross(tangent, normal);
|
||||
|
||||
//shift everything into tangent space
|
||||
let tbn = transpose(mat3x3<f32>(tangent, bitangent, normal));
|
||||
|
||||
let world_pos = cube_matrix * vec4<f32>(vertex.position, 1.0);
|
||||
|
||||
var out: Output;
|
||||
out.clip_pos = uniforms.projection * world_pos;
|
||||
out.uv = vertex.uv;
|
||||
out.tangent_pos = tbn * world_pos.xyz;
|
||||
out.tangent_camera_pos = tbn * uniforms.camera_pos.xyz;
|
||||
out.tangent_light_pos = tbn * LIGHT_POS;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
//cube properties
|
||||
const CUBE_BASE_COLOR: vec4<f32> = vec4<f32>(0.294118, 0.462745, 0.611765, 0.6);
|
||||
const SHINE_DAMPER: f32 = 1.0;
|
||||
const REFLECTIVITY: f32 = 0.8;
|
||||
const REFRACTION_INDEX: f32 = 1.31;
|
||||
|
||||
//fog, for the ~* cinematic effect *~
|
||||
const FOG_DENSITY: f32 = 0.15;
|
||||
const FOG_GRADIENT: f32 = 8.0;
|
||||
const FOG_COLOR: vec4<f32> = vec4<f32>(1.0, 1.0, 1.0, 1.0);
|
||||
|
||||
@fragment
|
||||
fn fs_main(in: Output) -> @location(0) vec4<f32> {
|
||||
let to_camera = in.tangent_camera_pos - in.tangent_pos;
|
||||
|
||||
//normal sample from texture
|
||||
var normal = textureSample(normal_texture, tex_sampler, in.uv).xyz;
|
||||
normal = normal * 2.0 - 1.0;
|
||||
|
||||
//diffuse
|
||||
let dir_to_light: vec3<f32> = normalize(in.tangent_light_pos - in.tangent_pos);
|
||||
let brightness = max(dot(normal, dir_to_light), 0.0);
|
||||
let diffuse: vec3<f32> = brightness * uniforms.light_color.xyz;
|
||||
|
||||
//specular
|
||||
let dir_to_camera = normalize(to_camera);
|
||||
let light_dir = -dir_to_light;
|
||||
let reflected_light_dir = reflect(light_dir, normal);
|
||||
let specular_factor = max(dot(reflected_light_dir, dir_to_camera), 0.0);
|
||||
let damped_factor = pow(specular_factor, SHINE_DAMPER);
|
||||
let specular: vec3<f32> = damped_factor * uniforms.light_color.xyz * REFLECTIVITY;
|
||||
|
||||
//fog
|
||||
let distance = length(to_camera);
|
||||
let visibility = clamp(exp(-pow((distance * FOG_DENSITY), FOG_GRADIENT)), 0.0, 1.0);
|
||||
|
||||
//reflection
|
||||
let reflection_dir = reflect(dir_to_camera, normal);
|
||||
let reflection_color = textureSample(sky_texture, tex_sampler, reflection_dir);
|
||||
let refraction_dir = refract(dir_to_camera, normal, REFRACTION_INDEX);
|
||||
let refraction_color = textureSample(sky_texture, tex_sampler, refraction_dir);
|
||||
let final_reflect_color = mix(reflection_color, refraction_color, 0.5);
|
||||
|
||||
//mix it all together!
|
||||
var color = vec4<f32>(CUBE_BASE_COLOR.xyz * diffuse + specular, CUBE_BASE_COLOR.w);
|
||||
color = mix(color, final_reflect_color, 0.8);
|
||||
color = mix(FOG_COLOR, color, visibility);
|
||||
|
||||
return color;
|
||||
}
|
||||
48
examples/custom_shader/src/shaders/depth.wgsl
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
var<private> positions: array<vec2<f32>, 6> = array<vec2<f32>, 6>(
|
||||
vec2<f32>(-1.0, 1.0),
|
||||
vec2<f32>(-1.0, -1.0),
|
||||
vec2<f32>(1.0, -1.0),
|
||||
vec2<f32>(-1.0, 1.0),
|
||||
vec2<f32>(1.0, 1.0),
|
||||
vec2<f32>(1.0, -1.0)
|
||||
);
|
||||
|
||||
var<private> uvs: array<vec2<f32>, 6> = array<vec2<f32>, 6>(
|
||||
vec2<f32>(0.0, 0.0),
|
||||
vec2<f32>(0.0, 1.0),
|
||||
vec2<f32>(1.0, 1.0),
|
||||
vec2<f32>(0.0, 0.0),
|
||||
vec2<f32>(1.0, 0.0),
|
||||
vec2<f32>(1.0, 1.0)
|
||||
);
|
||||
|
||||
@group(0) @binding(0) var depth_sampler: sampler;
|
||||
@group(0) @binding(1) var depth_texture: texture_2d<f32>;
|
||||
|
||||
struct Output {
|
||||
@builtin(position) position: vec4<f32>,
|
||||
@location(0) uv: vec2<f32>,
|
||||
}
|
||||
|
||||
@vertex
|
||||
fn vs_main(@builtin(vertex_index) v_index: u32) -> Output {
|
||||
var out: Output;
|
||||
|
||||
out.position = vec4<f32>(positions[v_index], 0.0, 1.0);
|
||||
out.uv = uvs[v_index];
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fs_main(input: Output) -> @location(0) vec4<f32> {
|
||||
let depth = textureSample(depth_texture, depth_sampler, input.uv).r;
|
||||
|
||||
if (depth > .9999) {
|
||||
discard;
|
||||
}
|
||||
|
||||
let c = 1.0 - depth;
|
||||
|
||||
return vec4<f32>(c, c, c, 1.0);
|
||||
}
|
||||
BIN
examples/custom_shader/src/textures/ice_cube_normal_map.png
Normal file
|
After Width: | Height: | Size: 1.7 MiB |
BIN
examples/custom_shader/src/textures/skybox/neg_x.jpg
Normal file
|
After Width: | Height: | Size: 7.4 KiB |
BIN
examples/custom_shader/src/textures/skybox/neg_y.jpg
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
examples/custom_shader/src/textures/skybox/neg_z.jpg
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
examples/custom_shader/src/textures/skybox/pos_x.jpg
Normal file
|
After Width: | Height: | Size: 5.4 KiB |
BIN
examples/custom_shader/src/textures/skybox/pos_y.jpg
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
examples/custom_shader/src/textures/skybox/pos_z.jpg
Normal file
|
After Width: | Height: | Size: 5.1 KiB |
|
|
@ -271,6 +271,7 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
&queue,
|
||||
&mut encoder,
|
||||
None,
|
||||
frame.texture.format(),
|
||||
&view,
|
||||
primitive,
|
||||
&viewport,
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ all-features = true
|
|||
|
||||
[features]
|
||||
geometry = ["lyon_path"]
|
||||
opengl = []
|
||||
image = ["dep:image", "kamadak-exif"]
|
||||
web-colors = []
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ pub mod compositor;
|
|||
pub mod geometry;
|
||||
|
||||
mod settings;
|
||||
pub mod widget;
|
||||
|
||||
pub use iced_graphics as graphics;
|
||||
pub use iced_graphics::core;
|
||||
|
|
@ -59,6 +60,26 @@ impl<T> Renderer<T> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn draw_custom<P: widget::shader::Primitive>(
|
||||
&mut self,
|
||||
bounds: Rectangle,
|
||||
primitive: P,
|
||||
) {
|
||||
match self {
|
||||
Renderer::TinySkia(_) => {
|
||||
log::warn!(
|
||||
"Custom shader primitive is unavailable with tiny-skia."
|
||||
);
|
||||
}
|
||||
#[cfg(feature = "wgpu")]
|
||||
Renderer::Wgpu(renderer) => {
|
||||
renderer.draw_primitive(iced_wgpu::Primitive::Custom(
|
||||
iced_wgpu::primitive::Custom::shader(bounds, primitive),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> core::Renderer for Renderer<T> {
|
||||
|
|
|
|||
|
|
@ -9,3 +9,6 @@ pub mod qr_code;
|
|||
|
||||
#[cfg(feature = "qr_code")]
|
||||
pub use qr_code::QRCode;
|
||||
|
||||
#[cfg(feature = "wgpu")]
|
||||
pub mod shader;
|
||||
|
|
|
|||
215
renderer/src/widget/shader.rs
Normal file
|
|
@ -0,0 +1,215 @@
|
|||
//! A custom shader widget for wgpu applications.
|
||||
use crate::core::event::Status;
|
||||
use crate::core::layout::{Limits, Node};
|
||||
use crate::core::mouse::{Cursor, Interaction};
|
||||
use crate::core::renderer::Style;
|
||||
use crate::core::widget::tree::{State, Tag};
|
||||
use crate::core::widget::{tree, Tree};
|
||||
use crate::core::{
|
||||
self, layout, mouse, widget, Clipboard, Element, Layout, Length, Rectangle,
|
||||
Shell, Size, Widget,
|
||||
};
|
||||
use std::marker::PhantomData;
|
||||
|
||||
mod event;
|
||||
mod program;
|
||||
|
||||
pub use event::Event;
|
||||
pub use iced_wgpu::custom::Primitive;
|
||||
pub use iced_wgpu::custom::Storage;
|
||||
pub use program::Program;
|
||||
|
||||
/// A widget which can render custom shaders with Iced's `wgpu` backend.
|
||||
///
|
||||
/// Must be initialized with a [`Program`], which describes the internal widget state & how
|
||||
/// its [`Program::Primitive`]s are drawn.
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct Shader<Message, P: Program<Message>> {
|
||||
width: Length,
|
||||
height: Length,
|
||||
program: P,
|
||||
_message: PhantomData<Message>,
|
||||
}
|
||||
|
||||
impl<Message, P: Program<Message>> Shader<Message, P> {
|
||||
/// Create a new custom [`Shader`].
|
||||
pub fn new(program: P) -> Self {
|
||||
Self {
|
||||
width: Length::Fixed(100.0),
|
||||
height: Length::Fixed(100.0),
|
||||
program,
|
||||
_message: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the `width` of the custom [`Shader`].
|
||||
pub fn width(mut self, width: impl Into<Length>) -> Self {
|
||||
self.width = width.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the `height` of the custom [`Shader`].
|
||||
pub fn height(mut self, height: impl Into<Length>) -> Self {
|
||||
self.height = height.into();
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<P, Message, Theme> Widget<Message, crate::Renderer<Theme>>
|
||||
for Shader<Message, P>
|
||||
where
|
||||
P: Program<Message>,
|
||||
{
|
||||
fn tag(&self) -> Tag {
|
||||
struct Tag<T>(T);
|
||||
tree::Tag::of::<Tag<P::State>>()
|
||||
}
|
||||
|
||||
fn state(&self) -> State {
|
||||
tree::State::new(P::State::default())
|
||||
}
|
||||
|
||||
fn width(&self) -> Length {
|
||||
self.width
|
||||
}
|
||||
|
||||
fn height(&self) -> Length {
|
||||
self.height
|
||||
}
|
||||
|
||||
fn layout(
|
||||
&self,
|
||||
_tree: &mut Tree,
|
||||
_renderer: &crate::Renderer<Theme>,
|
||||
limits: &Limits,
|
||||
) -> Node {
|
||||
let limits = limits.width(self.width).height(self.height);
|
||||
let size = limits.resolve(Size::ZERO);
|
||||
|
||||
layout::Node::new(size)
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
event: crate::core::Event,
|
||||
layout: Layout<'_>,
|
||||
cursor: Cursor,
|
||||
_renderer: &crate::Renderer<Theme>,
|
||||
_clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
_viewport: &Rectangle,
|
||||
) -> Status {
|
||||
let bounds = layout.bounds();
|
||||
|
||||
let custom_shader_event = match event {
|
||||
core::Event::Mouse(mouse_event) => Some(Event::Mouse(mouse_event)),
|
||||
core::Event::Keyboard(keyboard_event) => {
|
||||
Some(Event::Keyboard(keyboard_event))
|
||||
}
|
||||
core::Event::Touch(touch_event) => Some(Event::Touch(touch_event)),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
if let Some(custom_shader_event) = custom_shader_event {
|
||||
let state = tree.state.downcast_mut::<P::State>();
|
||||
|
||||
let (event_status, message) = self.program.update(
|
||||
state,
|
||||
custom_shader_event,
|
||||
bounds,
|
||||
cursor,
|
||||
shell,
|
||||
);
|
||||
|
||||
if let Some(message) = message {
|
||||
shell.publish(message);
|
||||
}
|
||||
|
||||
return event_status;
|
||||
}
|
||||
|
||||
event::Status::Ignored
|
||||
}
|
||||
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
tree: &Tree,
|
||||
layout: Layout<'_>,
|
||||
cursor: Cursor,
|
||||
_viewport: &Rectangle,
|
||||
_renderer: &crate::Renderer<Theme>,
|
||||
) -> mouse::Interaction {
|
||||
let bounds = layout.bounds();
|
||||
let state = tree.state.downcast_ref::<P::State>();
|
||||
|
||||
self.program.mouse_interaction(state, bounds, cursor)
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
tree: &widget::Tree,
|
||||
renderer: &mut crate::Renderer<Theme>,
|
||||
_theme: &Theme,
|
||||
_style: &Style,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: mouse::Cursor,
|
||||
_viewport: &Rectangle,
|
||||
) {
|
||||
let bounds = layout.bounds();
|
||||
let state = tree.state.downcast_ref::<P::State>();
|
||||
|
||||
renderer.draw_custom(
|
||||
bounds,
|
||||
self.program.draw(state, cursor_position, bounds),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, M, P, Theme> From<Shader<M, P>>
|
||||
for Element<'a, M, crate::Renderer<Theme>>
|
||||
where
|
||||
M: 'a,
|
||||
P: Program<M> + 'a,
|
||||
{
|
||||
fn from(custom: Shader<M, P>) -> Element<'a, M, crate::Renderer<Theme>> {
|
||||
Element::new(custom)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Message, T> Program<Message> for &T
|
||||
where
|
||||
T: Program<Message>,
|
||||
{
|
||||
type State = T::State;
|
||||
type Primitive = T::Primitive;
|
||||
|
||||
fn update(
|
||||
&self,
|
||||
state: &mut Self::State,
|
||||
event: Event,
|
||||
bounds: Rectangle,
|
||||
cursor: Cursor,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
) -> (Status, Option<Message>) {
|
||||
T::update(self, state, event, bounds, cursor, shell)
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
state: &Self::State,
|
||||
cursor: Cursor,
|
||||
bounds: Rectangle,
|
||||
) -> Self::Primitive {
|
||||
T::draw(self, state, cursor, bounds)
|
||||
}
|
||||
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
state: &Self::State,
|
||||
bounds: Rectangle,
|
||||
cursor: Cursor,
|
||||
) -> Interaction {
|
||||
T::mouse_interaction(self, state, bounds, cursor)
|
||||
}
|
||||
}
|
||||
21
renderer/src/widget/shader/event.rs
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
//! Handle events of a custom shader widget.
|
||||
use crate::core::keyboard;
|
||||
use crate::core::mouse;
|
||||
use crate::core::touch;
|
||||
|
||||
pub use crate::core::event::Status;
|
||||
|
||||
/// A [`Shader`] event.
|
||||
///
|
||||
/// [`Shader`]: crate::widget::shader::Shader;
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum Event {
|
||||
/// A mouse event.
|
||||
Mouse(mouse::Event),
|
||||
|
||||
/// A touch event.
|
||||
Touch(touch::Event),
|
||||
|
||||
/// A keyboard event.
|
||||
Keyboard(keyboard::Event),
|
||||
}
|
||||
60
renderer/src/widget/shader/program.rs
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
use crate::core::{event, mouse, Rectangle, Shell};
|
||||
use crate::widget;
|
||||
use widget::shader;
|
||||
|
||||
/// The state and logic of a [`Shader`] widget.
|
||||
///
|
||||
/// A [`Program`] can mutate the internal state of a [`Shader`] widget
|
||||
/// and produce messages for an application.
|
||||
///
|
||||
/// [`Shader`]: crate::widget::shader::Shader
|
||||
pub trait Program<Message> {
|
||||
/// The internal state of the [`Program`].
|
||||
type State: Default + 'static;
|
||||
|
||||
/// The type of primitive this [`Program`] can draw.
|
||||
type Primitive: shader::Primitive + 'static;
|
||||
|
||||
/// Update the internal [`State`] of the [`Program`]. This can be used to reflect state changes
|
||||
/// based on mouse & other events. You can use the [`Shell`] to publish messages, request a
|
||||
/// redraw for the window, etc.
|
||||
///
|
||||
/// By default, this method does and returns nothing.
|
||||
///
|
||||
/// [`State`]: Self::State
|
||||
fn update(
|
||||
&self,
|
||||
_state: &mut Self::State,
|
||||
_event: shader::Event,
|
||||
_bounds: Rectangle,
|
||||
_cursor: mouse::Cursor,
|
||||
_shell: &mut Shell<'_, Message>,
|
||||
) -> (event::Status, Option<Message>) {
|
||||
(event::Status::Ignored, None)
|
||||
}
|
||||
|
||||
/// Draws the [`Primitive`].
|
||||
///
|
||||
/// [`Primitive`]: Self::Primitive
|
||||
fn draw(
|
||||
&self,
|
||||
state: &Self::State,
|
||||
cursor: mouse::Cursor,
|
||||
bounds: Rectangle,
|
||||
) -> Self::Primitive;
|
||||
|
||||
/// Returns the current mouse interaction of the [`Program`].
|
||||
///
|
||||
/// The interaction returned will be in effect even if the cursor position is out of
|
||||
/// bounds of the [`Shader`]'s program.
|
||||
///
|
||||
/// [`Shader`]: crate::widget::shader::Shader
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
_state: &Self::State,
|
||||
_bounds: Rectangle,
|
||||
_cursor: mouse::Cursor,
|
||||
) -> mouse::Interaction {
|
||||
mouse::Interaction::default()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
//! Use the built-in theme and styles.
|
||||
pub mod palette;
|
||||
|
||||
pub use palette::Palette;
|
||||
pub use self::palette::Palette;
|
||||
|
||||
use crate::application;
|
||||
use crate::button;
|
||||
|
|
|
|||
|
|
@ -3,9 +3,7 @@ use crate::graphics::backend;
|
|||
use crate::graphics::color;
|
||||
use crate::graphics::{Transformation, Viewport};
|
||||
use crate::primitive::{self, Primitive};
|
||||
use crate::quad;
|
||||
use crate::text;
|
||||
use crate::triangle;
|
||||
use crate::{custom, quad, text, triangle};
|
||||
use crate::{Layer, Settings};
|
||||
|
||||
#[cfg(feature = "tracing")]
|
||||
|
|
@ -25,6 +23,7 @@ pub struct Backend {
|
|||
quad_pipeline: quad::Pipeline,
|
||||
text_pipeline: text::Pipeline,
|
||||
triangle_pipeline: triangle::Pipeline,
|
||||
pipeline_storage: custom::Storage,
|
||||
|
||||
#[cfg(any(feature = "image", feature = "svg"))]
|
||||
image_pipeline: image::Pipeline,
|
||||
|
|
@ -50,6 +49,7 @@ impl Backend {
|
|||
quad_pipeline,
|
||||
text_pipeline,
|
||||
triangle_pipeline,
|
||||
pipeline_storage: custom::Storage::default(),
|
||||
|
||||
#[cfg(any(feature = "image", feature = "svg"))]
|
||||
image_pipeline,
|
||||
|
|
@ -66,6 +66,7 @@ impl Backend {
|
|||
queue: &wgpu::Queue,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
clear_color: Option<Color>,
|
||||
format: wgpu::TextureFormat,
|
||||
frame: &wgpu::TextureView,
|
||||
primitives: &[Primitive],
|
||||
viewport: &Viewport,
|
||||
|
|
@ -88,6 +89,7 @@ impl Backend {
|
|||
self.prepare(
|
||||
device,
|
||||
queue,
|
||||
format,
|
||||
encoder,
|
||||
scale_factor,
|
||||
target_size,
|
||||
|
|
@ -117,6 +119,7 @@ impl Backend {
|
|||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
format: wgpu::TextureFormat,
|
||||
_encoder: &mut wgpu::CommandEncoder,
|
||||
scale_factor: f32,
|
||||
target_size: Size<u32>,
|
||||
|
|
@ -179,6 +182,20 @@ impl Backend {
|
|||
target_size,
|
||||
);
|
||||
}
|
||||
|
||||
if !layer.shaders.is_empty() {
|
||||
for shader in &layer.shaders {
|
||||
shader.primitive.prepare(
|
||||
format,
|
||||
device,
|
||||
queue,
|
||||
target_size,
|
||||
scale_factor,
|
||||
transformation,
|
||||
&mut self.pipeline_storage,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -302,6 +319,47 @@ impl Backend {
|
|||
|
||||
text_layer += 1;
|
||||
}
|
||||
|
||||
// kill render pass to let custom shaders get mut access to encoder
|
||||
let _ = ManuallyDrop::into_inner(render_pass);
|
||||
|
||||
if !layer.shaders.is_empty() {
|
||||
for shader in &layer.shaders {
|
||||
//This extra check is needed since each custom pipeline must set it's own
|
||||
//scissor rect, which will panic if bounds.w/h < 1
|
||||
let bounds = shader.bounds * scale_factor;
|
||||
|
||||
if bounds.width < 1.0 || bounds.height < 1.0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
shader.primitive.render(
|
||||
&self.pipeline_storage,
|
||||
bounds.into(),
|
||||
target,
|
||||
target_size,
|
||||
encoder,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// recreate and continue processing layers
|
||||
render_pass = ManuallyDrop::new(encoder.begin_render_pass(
|
||||
&wgpu::RenderPassDescriptor {
|
||||
label: Some("iced_wgpu::quad render pass"),
|
||||
color_attachments: &[Some(
|
||||
wgpu::RenderPassColorAttachment {
|
||||
view: target,
|
||||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
load: wgpu::LoadOp::Load,
|
||||
store: true,
|
||||
},
|
||||
},
|
||||
)],
|
||||
depth_stencil_attachment: None,
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
let _ = ManuallyDrop::into_inner(render_pass);
|
||||
|
|
|
|||
66
wgpu/src/custom.rs
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
use crate::core::{Rectangle, Size};
|
||||
use crate::graphics::Transformation;
|
||||
use std::any::{Any, TypeId};
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Debug;
|
||||
|
||||
/// Stores custom, user-provided pipelines.
|
||||
#[derive(Default, Debug)]
|
||||
pub struct Storage {
|
||||
pipelines: HashMap<TypeId, Box<dyn Any>>,
|
||||
}
|
||||
|
||||
impl Storage {
|
||||
/// Returns `true` if `Storage` contains a pipeline with type `T`.
|
||||
pub fn has<T: 'static>(&self) -> bool {
|
||||
self.pipelines.get(&TypeId::of::<T>()).is_some()
|
||||
}
|
||||
|
||||
/// Inserts the pipeline `T` in to [`Storage`].
|
||||
pub fn store<T: 'static>(&mut self, pipeline: T) {
|
||||
let _ = self.pipelines.insert(TypeId::of::<T>(), Box::new(pipeline));
|
||||
}
|
||||
|
||||
/// Returns a reference to pipeline with type `T` if it exists in [`Storage`].
|
||||
pub fn get<T: 'static>(&self) -> Option<&T> {
|
||||
self.pipelines.get(&TypeId::of::<T>()).map(|pipeline| {
|
||||
pipeline
|
||||
.downcast_ref::<T>()
|
||||
.expect("Pipeline with this type does not exist in Storage.")
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to pipeline `T` if it exists in [`Storage`].
|
||||
pub fn get_mut<T: 'static>(&mut self) -> Option<&mut T> {
|
||||
self.pipelines.get_mut(&TypeId::of::<T>()).map(|pipeline| {
|
||||
pipeline
|
||||
.downcast_mut::<T>()
|
||||
.expect("Pipeline with this type does not exist in Storage.")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// A set of methods which allows a [`Primitive`] to be rendered.
|
||||
pub trait Primitive: Debug + Send + Sync + 'static {
|
||||
/// Processes the [`Primitive`], allowing for GPU buffer allocation.
|
||||
fn prepare(
|
||||
&self,
|
||||
format: wgpu::TextureFormat,
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
target_size: Size<u32>,
|
||||
scale_factor: f32,
|
||||
transform: Transformation,
|
||||
storage: &mut Storage,
|
||||
);
|
||||
|
||||
/// Renders the [`Primitive`].
|
||||
fn render(
|
||||
&self,
|
||||
storage: &Storage,
|
||||
bounds: Rectangle<u32>,
|
||||
target: &wgpu::TextureView,
|
||||
target_size: Size<u32>,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
);
|
||||
}
|
||||
|
|
@ -34,6 +34,9 @@ pub struct Layer<'a> {
|
|||
|
||||
/// The images of the [`Layer`].
|
||||
pub images: Vec<Image>,
|
||||
|
||||
/// The custom shader primitives of this [`Layer`].
|
||||
pub shaders: Vec<primitive::Shader>,
|
||||
}
|
||||
|
||||
impl<'a> Layer<'a> {
|
||||
|
|
@ -45,6 +48,7 @@ impl<'a> Layer<'a> {
|
|||
meshes: Vec::new(),
|
||||
text: Vec::new(),
|
||||
images: Vec::new(),
|
||||
shaders: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -308,6 +312,18 @@ impl<'a> Layer<'a> {
|
|||
}
|
||||
}
|
||||
},
|
||||
primitive::Custom::Shader(shader) => {
|
||||
let layer = &mut layers[current_layer];
|
||||
|
||||
let bounds = Rectangle::new(
|
||||
Point::new(translation.x, translation.y),
|
||||
shader.bounds.size(),
|
||||
);
|
||||
|
||||
if layer.bounds.intersection(&bounds).is_some() {
|
||||
layer.shaders.push(shader.clone());
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@
|
|||
rustdoc::broken_intra_doc_links
|
||||
)]
|
||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||
pub mod custom;
|
||||
pub mod layer;
|
||||
pub mod primitive;
|
||||
pub mod settings;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
//! Draw using different graphical primitives.
|
||||
use crate::core::Rectangle;
|
||||
use crate::custom;
|
||||
use crate::graphics::{Damage, Mesh};
|
||||
use std::any::Any;
|
||||
use std::fmt::Debug;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// The graphical primitives supported by `iced_wgpu`.
|
||||
pub type Primitive = crate::graphics::Primitive<Custom>;
|
||||
|
|
@ -10,12 +14,44 @@ pub type Primitive = crate::graphics::Primitive<Custom>;
|
|||
pub enum Custom {
|
||||
/// A mesh primitive.
|
||||
Mesh(Mesh),
|
||||
/// A custom shader primitive
|
||||
Shader(Shader),
|
||||
}
|
||||
|
||||
impl Custom {
|
||||
/// Create a custom [`Shader`] primitive.
|
||||
pub fn shader<P: custom::Primitive>(
|
||||
bounds: Rectangle,
|
||||
primitive: P,
|
||||
) -> Self {
|
||||
Self::Shader(Shader {
|
||||
bounds,
|
||||
primitive: Arc::new(primitive),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Damage for Custom {
|
||||
fn bounds(&self) -> Rectangle {
|
||||
match self {
|
||||
Self::Mesh(mesh) => mesh.bounds(),
|
||||
Self::Shader(shader) => shader.bounds,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
/// A custom primitive which can be used to render primitives associated with a custom pipeline.
|
||||
pub struct Shader {
|
||||
/// The bounds of the [`Shader`].
|
||||
pub bounds: Rectangle,
|
||||
|
||||
/// The [`custom::Primitive`] to render.
|
||||
pub primitive: Arc<dyn custom::Primitive>,
|
||||
}
|
||||
|
||||
impl PartialEq for Shader {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.primitive.type_id() == other.primitive.type_id()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -178,6 +178,7 @@ pub fn present<Theme, T: AsRef<str>>(
|
|||
&compositor.queue,
|
||||
&mut encoder,
|
||||
Some(background_color),
|
||||
frame.texture.format(),
|
||||
view,
|
||||
primitives,
|
||||
viewport,
|
||||
|
|
@ -357,6 +358,7 @@ pub fn screenshot<Theme, T: AsRef<str>>(
|
|||
&compositor.queue,
|
||||
&mut encoder,
|
||||
Some(background_color),
|
||||
texture.format(),
|
||||
&view,
|
||||
primitives,
|
||||
viewport,
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ image = ["iced_renderer/image"]
|
|||
svg = ["iced_renderer/svg"]
|
||||
canvas = ["iced_renderer/geometry"]
|
||||
qr_code = ["canvas", "qrcode"]
|
||||
wgpu = []
|
||||
|
||||
[dependencies]
|
||||
iced_renderer.workspace = true
|
||||
|
|
|
|||
|
|
@ -97,6 +97,9 @@ pub use tooltip::Tooltip;
|
|||
#[doc(no_inline)]
|
||||
pub use vertical_slider::VerticalSlider;
|
||||
|
||||
#[cfg(feature = "wgpu")]
|
||||
pub use renderer::widget::shader::{self, Shader};
|
||||
|
||||
#[cfg(feature = "svg")]
|
||||
pub mod svg;
|
||||
|
||||
|
|
|
|||