summaryrefslogtreecommitdiffstats
path: root/wgpu
diff options
context:
space:
mode:
authorLibravatar Héctor Ramón <hector0193@gmail.com>2019-10-23 04:52:51 +0200
committerLibravatar GitHub <noreply@github.com>2019-10-23 04:52:51 +0200
commit4769272122e8cd0a4d666bb06c00cb27f8cad3c4 (patch)
tree68e513170347d804f55b3743f1fd960bbf700950 /wgpu
parente95e656fcd780264f7a3c9a2ba3d0bd471d4894e (diff)
parent99e1a3780a1ea3ccb173d1fb4cbe889bb08b9643 (diff)
downloadiced-4769272122e8cd0a4d666bb06c00cb27f8cad3c4.tar.gz
iced-4769272122e8cd0a4d666bb06c00cb27f8cad3c4.tar.bz2
iced-4769272122e8cd0a4d666bb06c00cb27f8cad3c4.zip
Merge pull request #22 from hecrj/basic-renderer
Basic `wgpu` renderer
Diffstat (limited to 'wgpu')
-rw-r--r--wgpu/Cargo.toml16
-rw-r--r--wgpu/src/image.rs438
-rw-r--r--wgpu/src/lib.rs12
-rw-r--r--wgpu/src/primitive.rs26
-rw-r--r--wgpu/src/quad.rs275
-rw-r--r--wgpu/src/renderer.rs329
-rw-r--r--wgpu/src/renderer/button.rs86
-rw-r--r--wgpu/src/renderer/checkbox.rs106
-rw-r--r--wgpu/src/renderer/column.rs34
-rw-r--r--wgpu/src/renderer/image.rs34
-rw-r--r--wgpu/src/renderer/radio.rs109
-rw-r--r--wgpu/src/renderer/row.rs34
-rw-r--r--wgpu/src/renderer/slider.rs128
-rw-r--r--wgpu/src/renderer/text.rs83
-rw-r--r--wgpu/src/shader/image.frag12
-rw-r--r--wgpu/src/shader/image.frag.spvbin0 -> 684 bytes
-rw-r--r--wgpu/src/shader/image.vert24
-rw-r--r--wgpu/src/shader/image.vert.spvbin0 -> 2136 bytes
-rw-r--r--wgpu/src/shader/quad.frag37
-rw-r--r--wgpu/src/shader/quad.frag.spvbin0 -> 3212 bytes
-rw-r--r--wgpu/src/shader/quad.vert32
-rw-r--r--wgpu/src/shader/quad.vert.spvbin0 -> 2544 bytes
-rw-r--r--wgpu/src/transformation.rs30
23 files changed, 1845 insertions, 0 deletions
diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml
new file mode 100644
index 00000000..cac5e113
--- /dev/null
+++ b/wgpu/Cargo.toml
@@ -0,0 +1,16 @@
+[package]
+name = "iced_wgpu"
+version = "0.1.0-alpha"
+authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
+edition = "2018"
+description = "A wgpu renderer for Iced"
+license = "MIT"
+repository = "https://github.com/hecrj/iced"
+
+[dependencies]
+iced_native = { version = "0.1.0-alpha", path = "../native" }
+wgpu = { version = "0.3", git = "https://github.com/gfx-rs/wgpu-rs", rev = "cb25914b95b58fee0dc139b400867e7a731d98f4" }
+wgpu_glyph = { version = "0.4", git = "https://github.com/hecrj/wgpu_glyph", rev = "48daa98f5f785963838b4345e86ac40eac095ba9" }
+raw-window-handle = "0.1"
+image = "0.22"
+log = "0.4"
diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs
new file mode 100644
index 00000000..c883eaa8
--- /dev/null
+++ b/wgpu/src/image.rs
@@ -0,0 +1,438 @@
+use crate::Transformation;
+
+use std::cell::RefCell;
+use std::collections::HashMap;
+use std::mem;
+use std::rc::Rc;
+
+pub struct Pipeline {
+ cache: RefCell<HashMap<String, Memory>>,
+
+ pipeline: wgpu::RenderPipeline,
+ transform: wgpu::Buffer,
+ vertices: wgpu::Buffer,
+ indices: wgpu::Buffer,
+ instances: wgpu::Buffer,
+ constants: wgpu::BindGroup,
+ texture_layout: wgpu::BindGroupLayout,
+}
+
+impl Pipeline {
+ pub fn new(device: &wgpu::Device) -> Self {
+ let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
+ 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,
+ lod_min_clamp: -100.0,
+ lod_max_clamp: 100.0,
+ compare_function: wgpu::CompareFunction::Always,
+ });
+
+ let constant_layout =
+ device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
+ bindings: &[
+ wgpu::BindGroupLayoutBinding {
+ binding: 0,
+ visibility: wgpu::ShaderStage::VERTEX,
+ ty: wgpu::BindingType::UniformBuffer { dynamic: false },
+ },
+ wgpu::BindGroupLayoutBinding {
+ binding: 1,
+ visibility: wgpu::ShaderStage::FRAGMENT,
+ ty: wgpu::BindingType::Sampler,
+ },
+ ],
+ });
+
+ let matrix: [f32; 16] = Transformation::identity().into();
+
+ let transform_buffer = device
+ .create_buffer_mapped(
+ 16,
+ wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST,
+ )
+ .fill_from_slice(&matrix[..]);
+
+ let constant_bind_group =
+ device.create_bind_group(&wgpu::BindGroupDescriptor {
+ layout: &constant_layout,
+ bindings: &[
+ wgpu::Binding {
+ binding: 0,
+ resource: wgpu::BindingResource::Buffer {
+ buffer: &transform_buffer,
+ range: 0..64,
+ },
+ },
+ wgpu::Binding {
+ binding: 1,
+ resource: wgpu::BindingResource::Sampler(&sampler),
+ },
+ ],
+ });
+
+ let texture_layout =
+ device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
+ bindings: &[wgpu::BindGroupLayoutBinding {
+ binding: 0,
+ visibility: wgpu::ShaderStage::FRAGMENT,
+ ty: wgpu::BindingType::SampledTexture {
+ multisampled: false,
+ dimension: wgpu::TextureViewDimension::D2,
+ },
+ }],
+ });
+
+ let layout =
+ device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
+ bind_group_layouts: &[&constant_layout, &texture_layout],
+ });
+
+ let vs = include_bytes!("shader/image.vert.spv");
+ let vs_module = device.create_shader_module(
+ &wgpu::read_spirv(std::io::Cursor::new(&vs[..]))
+ .expect("Read image vertex shader as SPIR-V"),
+ );
+
+ let fs = include_bytes!("shader/image.frag.spv");
+ let fs_module = device.create_shader_module(
+ &wgpu::read_spirv(std::io::Cursor::new(&fs[..]))
+ .expect("Read image fragment shader as SPIR-V"),
+ );
+
+ let pipeline =
+ device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
+ layout: &layout,
+ vertex_stage: wgpu::ProgrammableStageDescriptor {
+ module: &vs_module,
+ entry_point: "main",
+ },
+ fragment_stage: Some(wgpu::ProgrammableStageDescriptor {
+ module: &fs_module,
+ entry_point: "main",
+ }),
+ rasterization_state: Some(wgpu::RasterizationStateDescriptor {
+ front_face: wgpu::FrontFace::Cw,
+ cull_mode: wgpu::CullMode::None,
+ depth_bias: 0,
+ depth_bias_slope_scale: 0.0,
+ depth_bias_clamp: 0.0,
+ }),
+ primitive_topology: wgpu::PrimitiveTopology::TriangleList,
+ color_states: &[wgpu::ColorStateDescriptor {
+ format: wgpu::TextureFormat::Bgra8UnormSrgb,
+ color_blend: wgpu::BlendDescriptor {
+ src_factor: wgpu::BlendFactor::SrcAlpha,
+ dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
+ operation: wgpu::BlendOperation::Add,
+ },
+ alpha_blend: wgpu::BlendDescriptor {
+ src_factor: wgpu::BlendFactor::One,
+ dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
+ operation: wgpu::BlendOperation::Add,
+ },
+ write_mask: wgpu::ColorWrite::ALL,
+ }],
+ depth_stencil_state: None,
+ index_format: wgpu::IndexFormat::Uint16,
+ vertex_buffers: &[
+ wgpu::VertexBufferDescriptor {
+ stride: mem::size_of::<Vertex>() as u64,
+ step_mode: wgpu::InputStepMode::Vertex,
+ attributes: &[wgpu::VertexAttributeDescriptor {
+ shader_location: 0,
+ format: wgpu::VertexFormat::Float2,
+ offset: 0,
+ }],
+ },
+ wgpu::VertexBufferDescriptor {
+ stride: mem::size_of::<Instance>() as u64,
+ step_mode: wgpu::InputStepMode::Instance,
+ attributes: &[
+ wgpu::VertexAttributeDescriptor {
+ shader_location: 1,
+ format: wgpu::VertexFormat::Float2,
+ offset: 0,
+ },
+ wgpu::VertexAttributeDescriptor {
+ shader_location: 2,
+ format: wgpu::VertexFormat::Float2,
+ offset: 4 * 2,
+ },
+ ],
+ },
+ ],
+ sample_count: 1,
+ sample_mask: !0,
+ alpha_to_coverage_enabled: false,
+ });
+
+ let vertices = device
+ .create_buffer_mapped(QUAD_VERTS.len(), wgpu::BufferUsage::VERTEX)
+ .fill_from_slice(&QUAD_VERTS);
+
+ let indices = device
+ .create_buffer_mapped(QUAD_INDICES.len(), wgpu::BufferUsage::INDEX)
+ .fill_from_slice(&QUAD_INDICES);
+
+ let instances = device.create_buffer(&wgpu::BufferDescriptor {
+ size: mem::size_of::<Instance>() as u64,
+ usage: wgpu::BufferUsage::VERTEX | wgpu::BufferUsage::COPY_DST,
+ });
+
+ Pipeline {
+ cache: RefCell::new(HashMap::new()),
+
+ pipeline,
+ transform: transform_buffer,
+ vertices,
+ indices,
+ instances,
+ constants: constant_bind_group,
+ texture_layout,
+ }
+ }
+
+ pub fn dimensions(&self, path: &str) -> (u32, u32) {
+ self.load(path);
+
+ self.cache.borrow().get(path).unwrap().dimensions()
+ }
+
+ fn load(&self, path: &str) {
+ if !self.cache.borrow().contains_key(path) {
+ let image = image::open(path).expect("Load image").to_bgra();
+
+ self.cache
+ .borrow_mut()
+ .insert(path.to_string(), Memory::Host { image });
+ }
+ }
+
+ pub fn draw(
+ &mut self,
+ device: &mut wgpu::Device,
+ encoder: &mut wgpu::CommandEncoder,
+ instances: &[Image],
+ transformation: Transformation,
+ target: &wgpu::TextureView,
+ ) {
+ let matrix: [f32; 16] = transformation.into();
+
+ let transform_buffer = device
+ .create_buffer_mapped(16, wgpu::BufferUsage::COPY_SRC)
+ .fill_from_slice(&matrix[..]);
+
+ encoder.copy_buffer_to_buffer(
+ &transform_buffer,
+ 0,
+ &self.transform,
+ 0,
+ 16 * 4,
+ );
+
+ // TODO: Batch draw calls using a texture atlas
+ // Guillotière[1] by @nical can help us a lot here.
+ //
+ // [1]: https://github.com/nical/guillotiere
+ for image in instances {
+ self.load(&image.path);
+
+ let texture = self
+ .cache
+ .borrow_mut()
+ .get_mut(&image.path)
+ .unwrap()
+ .upload(device, encoder, &self.texture_layout);
+
+ let instance_buffer = device
+ .create_buffer_mapped(1, wgpu::BufferUsage::COPY_SRC)
+ .fill_from_slice(&[Instance {
+ _position: image.position,
+ _scale: image.scale,
+ }]);
+
+ encoder.copy_buffer_to_buffer(
+ &instance_buffer,
+ 0,
+ &self.instances,
+ 0,
+ mem::size_of::<Image>() as u64,
+ );
+
+ {
+ let mut render_pass =
+ encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
+ color_attachments: &[
+ wgpu::RenderPassColorAttachmentDescriptor {
+ attachment: target,
+ resolve_target: None,
+ load_op: wgpu::LoadOp::Load,
+ store_op: wgpu::StoreOp::Store,
+ clear_color: wgpu::Color {
+ r: 0.0,
+ g: 0.0,
+ b: 0.0,
+ a: 0.0,
+ },
+ },
+ ],
+ depth_stencil_attachment: None,
+ });
+
+ render_pass.set_pipeline(&self.pipeline);
+ render_pass.set_bind_group(0, &self.constants, &[]);
+ render_pass.set_bind_group(1, &texture, &[]);
+ render_pass.set_index_buffer(&self.indices, 0);
+ render_pass.set_vertex_buffers(
+ 0,
+ &[(&self.vertices, 0), (&self.instances, 0)],
+ );
+
+ render_pass.draw_indexed(
+ 0..QUAD_INDICES.len() as u32,
+ 0,
+ 0..1 as u32,
+ );
+ }
+ }
+ }
+}
+
+enum Memory {
+ Host {
+ image: image::ImageBuffer<image::Bgra<u8>, Vec<u8>>,
+ },
+ Device {
+ bind_group: Rc<wgpu::BindGroup>,
+ width: u32,
+ height: u32,
+ },
+}
+
+impl Memory {
+ fn dimensions(&self) -> (u32, u32) {
+ match self {
+ Memory::Host { image } => image.dimensions(),
+ Memory::Device { width, height, .. } => (*width, *height),
+ }
+ }
+
+ fn upload(
+ &mut self,
+ device: &wgpu::Device,
+ encoder: &mut wgpu::CommandEncoder,
+ texture_layout: &wgpu::BindGroupLayout,
+ ) -> Rc<wgpu::BindGroup> {
+ match self {
+ Memory::Host { image } => {
+ let (width, height) = image.dimensions();
+
+ let extent = wgpu::Extent3d {
+ width,
+ height,
+ depth: 1,
+ };
+
+ let texture = device.create_texture(&wgpu::TextureDescriptor {
+ size: extent,
+ array_layer_count: 1,
+ mip_level_count: 1,
+ sample_count: 1,
+ dimension: wgpu::TextureDimension::D2,
+ format: wgpu::TextureFormat::Bgra8UnormSrgb,
+ usage: wgpu::TextureUsage::COPY_DST
+ | wgpu::TextureUsage::SAMPLED,
+ });
+
+ let slice = image.clone().into_raw();
+
+ let temp_buf = device
+ .create_buffer_mapped(
+ slice.len(),
+ wgpu::BufferUsage::COPY_SRC,
+ )
+ .fill_from_slice(&slice[..]);
+
+ encoder.copy_buffer_to_texture(
+ wgpu::BufferCopyView {
+ buffer: &temp_buf,
+ offset: 0,
+ row_pitch: 4 * width as u32,
+ image_height: height as u32,
+ },
+ wgpu::TextureCopyView {
+ texture: &texture,
+ array_layer: 0,
+ mip_level: 0,
+ origin: wgpu::Origin3d {
+ x: 0.0,
+ y: 0.0,
+ z: 0.0,
+ },
+ },
+ extent,
+ );
+
+ let bind_group =
+ device.create_bind_group(&wgpu::BindGroupDescriptor {
+ layout: texture_layout,
+ bindings: &[wgpu::Binding {
+ binding: 0,
+ resource: wgpu::BindingResource::TextureView(
+ &texture.create_default_view(),
+ ),
+ }],
+ });
+
+ let bind_group = Rc::new(bind_group);
+
+ *self = Memory::Device {
+ bind_group: bind_group.clone(),
+ width,
+ height,
+ };
+
+ bind_group
+ }
+ Memory::Device { bind_group, .. } => bind_group.clone(),
+ }
+ }
+}
+
+pub struct Image {
+ pub path: String,
+ pub position: [f32; 2],
+ pub scale: [f32; 2],
+}
+
+#[derive(Clone, Copy)]
+pub struct Vertex {
+ _position: [f32; 2],
+}
+
+const QUAD_INDICES: [u16; 6] = [0, 1, 2, 0, 2, 3];
+
+const QUAD_VERTS: [Vertex; 4] = [
+ Vertex {
+ _position: [0.0, 0.0],
+ },
+ Vertex {
+ _position: [1.0, 0.0],
+ },
+ Vertex {
+ _position: [1.0, 1.0],
+ },
+ Vertex {
+ _position: [0.0, 1.0],
+ },
+];
+
+#[derive(Clone, Copy)]
+struct Instance {
+ _position: [f32; 2],
+ _scale: [f32; 2],
+}
diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs
new file mode 100644
index 00000000..01dc4c20
--- /dev/null
+++ b/wgpu/src/lib.rs
@@ -0,0 +1,12 @@
+mod image;
+mod primitive;
+mod quad;
+mod renderer;
+mod transformation;
+
+pub(crate) use crate::image::Image;
+pub(crate) use quad::Quad;
+pub(crate) use transformation::Transformation;
+
+pub use primitive::Primitive;
+pub use renderer::{Renderer, Target};
diff --git a/wgpu/src/primitive.rs b/wgpu/src/primitive.rs
new file mode 100644
index 00000000..0b9e2c41
--- /dev/null
+++ b/wgpu/src/primitive.rs
@@ -0,0 +1,26 @@
+use iced_native::{text, Background, Color, Rectangle};
+
+#[derive(Debug, Clone)]
+pub enum Primitive {
+ None,
+ Group {
+ primitives: Vec<Primitive>,
+ },
+ Text {
+ content: String,
+ bounds: Rectangle,
+ color: Color,
+ size: f32,
+ horizontal_alignment: text::HorizontalAlignment,
+ vertical_alignment: text::VerticalAlignment,
+ },
+ Quad {
+ bounds: Rectangle,
+ background: Background,
+ border_radius: u16,
+ },
+ Image {
+ path: String,
+ bounds: Rectangle,
+ },
+}
diff --git a/wgpu/src/quad.rs b/wgpu/src/quad.rs
new file mode 100644
index 00000000..adb294f0
--- /dev/null
+++ b/wgpu/src/quad.rs
@@ -0,0 +1,275 @@
+use crate::Transformation;
+
+use std::mem;
+
+pub struct Pipeline {
+ pipeline: wgpu::RenderPipeline,
+ constants: wgpu::BindGroup,
+ transform: wgpu::Buffer,
+ vertices: wgpu::Buffer,
+ indices: wgpu::Buffer,
+ instances: wgpu::Buffer,
+}
+
+impl Pipeline {
+ pub fn new(device: &mut wgpu::Device) -> Pipeline {
+ let constant_layout =
+ device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
+ bindings: &[wgpu::BindGroupLayoutBinding {
+ binding: 0,
+ visibility: wgpu::ShaderStage::VERTEX,
+ ty: wgpu::BindingType::UniformBuffer { dynamic: false },
+ }],
+ });
+
+ let matrix: [f32; 16] = Transformation::identity().into();
+
+ let transform = device
+ .create_buffer_mapped(
+ 16,
+ wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST,
+ )
+ .fill_from_slice(&matrix[..]);
+
+ let constants = device.create_bind_group(&wgpu::BindGroupDescriptor {
+ layout: &constant_layout,
+ bindings: &[wgpu::Binding {
+ binding: 0,
+ resource: wgpu::BindingResource::Buffer {
+ buffer: &transform,
+ range: 0..64,
+ },
+ }],
+ });
+
+ let layout =
+ device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
+ bind_group_layouts: &[&constant_layout],
+ });
+
+ let vs = include_bytes!("shader/quad.vert.spv");
+ let vs_module = device.create_shader_module(
+ &wgpu::read_spirv(std::io::Cursor::new(&vs[..]))
+ .expect("Read quad vertex shader as SPIR-V"),
+ );
+
+ let fs = include_bytes!("shader/quad.frag.spv");
+ let fs_module = device.create_shader_module(
+ &wgpu::read_spirv(std::io::Cursor::new(&fs[..]))
+ .expect("Read quad fragment shader as SPIR-V"),
+ );
+
+ let pipeline =
+ device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
+ layout: &layout,
+ vertex_stage: wgpu::ProgrammableStageDescriptor {
+ module: &vs_module,
+ entry_point: "main",
+ },
+ fragment_stage: Some(wgpu::ProgrammableStageDescriptor {
+ module: &fs_module,
+ entry_point: "main",
+ }),
+ rasterization_state: Some(wgpu::RasterizationStateDescriptor {
+ front_face: wgpu::FrontFace::Cw,
+ cull_mode: wgpu::CullMode::None,
+ depth_bias: 0,
+ depth_bias_slope_scale: 0.0,
+ depth_bias_clamp: 0.0,
+ }),
+ primitive_topology: wgpu::PrimitiveTopology::TriangleList,
+ color_states: &[wgpu::ColorStateDescriptor {
+ format: wgpu::TextureFormat::Bgra8UnormSrgb,
+ color_blend: wgpu::BlendDescriptor {
+ src_factor: wgpu::BlendFactor::SrcAlpha,
+ dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
+ operation: wgpu::BlendOperation::Add,
+ },
+ alpha_blend: wgpu::BlendDescriptor {
+ src_factor: wgpu::BlendFactor::One,
+ dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
+ operation: wgpu::BlendOperation::Add,
+ },
+ write_mask: wgpu::ColorWrite::ALL,
+ }],
+ depth_stencil_state: None,
+ index_format: wgpu::IndexFormat::Uint16,
+ vertex_buffers: &[
+ wgpu::VertexBufferDescriptor {
+ stride: mem::size_of::<Vertex>() as u64,
+ step_mode: wgpu::InputStepMode::Vertex,
+ attributes: &[wgpu::VertexAttributeDescriptor {
+ shader_location: 0,
+ format: wgpu::VertexFormat::Float2,
+ offset: 0,
+ }],
+ },
+ wgpu::VertexBufferDescriptor {
+ stride: mem::size_of::<Quad>() as u64,
+ step_mode: wgpu::InputStepMode::Instance,
+ attributes: &[
+ wgpu::VertexAttributeDescriptor {
+ shader_location: 1,
+ format: wgpu::VertexFormat::Float2,
+ offset: 0,
+ },
+ wgpu::VertexAttributeDescriptor {
+ shader_location: 2,
+ format: wgpu::VertexFormat::Float2,
+ offset: 4 * 2,
+ },
+ wgpu::VertexAttributeDescriptor {
+ shader_location: 3,
+ format: wgpu::VertexFormat::Float4,
+ offset: 4 * (2 + 2),
+ },
+ wgpu::VertexAttributeDescriptor {
+ shader_location: 4,
+ format: wgpu::VertexFormat::Uint,
+ offset: 4 * (2 + 2 + 4),
+ },
+ ],
+ },
+ ],
+ sample_count: 1,
+ sample_mask: !0,
+ alpha_to_coverage_enabled: false,
+ });
+
+ let vertices = device
+ .create_buffer_mapped(QUAD_VERTS.len(), wgpu::BufferUsage::VERTEX)
+ .fill_from_slice(&QUAD_VERTS);
+
+ let indices = device
+ .create_buffer_mapped(QUAD_INDICES.len(), wgpu::BufferUsage::INDEX)
+ .fill_from_slice(&QUAD_INDICES);
+
+ let instances = device.create_buffer(&wgpu::BufferDescriptor {
+ size: mem::size_of::<Quad>() as u64 * Quad::MAX as u64,
+ usage: wgpu::BufferUsage::VERTEX | wgpu::BufferUsage::COPY_DST,
+ });
+
+ Pipeline {
+ pipeline,
+ constants,
+ transform,
+ vertices,
+ indices,
+ instances,
+ }
+ }
+
+ pub fn draw(
+ &mut self,
+ device: &mut wgpu::Device,
+ encoder: &mut wgpu::CommandEncoder,
+ instances: &[Quad],
+ transformation: Transformation,
+ target: &wgpu::TextureView,
+ ) {
+ let matrix: [f32; 16] = transformation.into();
+
+ let transform_buffer = device
+ .create_buffer_mapped(16, wgpu::BufferUsage::COPY_SRC)
+ .fill_from_slice(&matrix[..]);
+
+ encoder.copy_buffer_to_buffer(
+ &transform_buffer,
+ 0,
+ &self.transform,
+ 0,
+ 16 * 4,
+ );
+
+ let mut i = 0;
+ let total = instances.len();
+
+ while i < total {
+ let end = (i + Quad::MAX).min(total);
+ let amount = end - i;
+
+ let instance_buffer = device
+ .create_buffer_mapped(amount, wgpu::BufferUsage::COPY_SRC)
+ .fill_from_slice(&instances[i..end]);
+
+ encoder.copy_buffer_to_buffer(
+ &instance_buffer,
+ 0,
+ &self.instances,
+ 0,
+ (mem::size_of::<Quad>() * amount) as u64,
+ );
+
+ {
+ let mut render_pass =
+ encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
+ color_attachments: &[
+ wgpu::RenderPassColorAttachmentDescriptor {
+ attachment: target,
+ resolve_target: None,
+ load_op: wgpu::LoadOp::Load,
+ store_op: wgpu::StoreOp::Store,
+ clear_color: wgpu::Color {
+ r: 0.0,
+ g: 0.0,
+ b: 0.0,
+ a: 0.0,
+ },
+ },
+ ],
+ depth_stencil_attachment: None,
+ });
+
+ render_pass.set_pipeline(&self.pipeline);
+ render_pass.set_bind_group(0, &self.constants, &[]);
+ render_pass.set_index_buffer(&self.indices, 0);
+ render_pass.set_vertex_buffers(
+ 0,
+ &[(&self.vertices, 0), (&self.instances, 0)],
+ );
+
+ render_pass.draw_indexed(
+ 0..QUAD_INDICES.len() as u32,
+ 0,
+ 0..amount as u32,
+ );
+ }
+
+ i += Quad::MAX;
+ }
+ }
+}
+
+#[derive(Clone, Copy)]
+pub struct Vertex {
+ _position: [f32; 2],
+}
+
+const QUAD_INDICES: [u16; 6] = [0, 1, 2, 0, 2, 3];
+
+const QUAD_VERTS: [Vertex; 4] = [
+ Vertex {
+ _position: [0.0, 0.0],
+ },
+ Vertex {
+ _position: [1.0, 0.0],
+ },
+ Vertex {
+ _position: [1.0, 1.0],
+ },
+ Vertex {
+ _position: [0.0, 1.0],
+ },
+];
+
+#[derive(Debug, Clone, Copy)]
+pub struct Quad {
+ pub position: [f32; 2],
+ pub scale: [f32; 2],
+ pub color: [f32; 4],
+ pub border_radius: u32,
+}
+
+impl Quad {
+ const MAX: usize = 100_000;
+}
diff --git a/wgpu/src/renderer.rs b/wgpu/src/renderer.rs
new file mode 100644
index 00000000..ab6f744f
--- /dev/null
+++ b/wgpu/src/renderer.rs
@@ -0,0 +1,329 @@
+use crate::{quad, Image, Primitive, Quad, Transformation};
+use iced_native::{
+ renderer::Debugger, renderer::Windowed, Background, Color, Layout,
+ MouseCursor, Point, Widget,
+};
+
+use raw_window_handle::HasRawWindowHandle;
+use wgpu::{
+ Adapter, BackendBit, CommandEncoderDescriptor, Device, DeviceDescriptor,
+ Extensions, Limits, PowerPreference, Queue, RequestAdapterOptions, Surface,
+ SwapChain, SwapChainDescriptor, TextureFormat, TextureUsage,
+};
+use wgpu_glyph::{GlyphBrush, GlyphBrushBuilder, Section};
+
+use std::{cell::RefCell, rc::Rc};
+
+mod button;
+mod checkbox;
+mod column;
+mod image;
+mod radio;
+mod row;
+mod slider;
+mod text;
+
+pub struct Renderer {
+ surface: Surface,
+ adapter: Adapter,
+ device: Device,
+ queue: Queue,
+ quad_pipeline: quad::Pipeline,
+ image_pipeline: crate::image::Pipeline,
+
+ quads: Vec<Quad>,
+ images: Vec<Image>,
+ glyph_brush: Rc<RefCell<GlyphBrush<'static, ()>>>,
+}
+
+pub struct Target {
+ width: u16,
+ height: u16,
+ transformation: Transformation,
+ swap_chain: SwapChain,
+}
+
+impl Renderer {
+ fn new<W: HasRawWindowHandle>(window: &W) -> Self {
+ let adapter = Adapter::request(&RequestAdapterOptions {
+ power_preference: PowerPreference::LowPower,
+ backends: BackendBit::all(),
+ })
+ .expect("Request adapter");
+
+ let (mut device, queue) = adapter.request_device(&DeviceDescriptor {
+ extensions: Extensions {
+ anisotropic_filtering: false,
+ },
+ limits: Limits { max_bind_groups: 1 },
+ });
+
+ let surface = Surface::create(window);
+
+ // TODO: Think about font loading strategy
+ // Loading system fonts with fallback may be a good idea
+ let font: &[u8] =
+ include_bytes!("../../examples/resources/Roboto-Regular.ttf");
+
+ let glyph_brush = GlyphBrushBuilder::using_font_bytes(font)
+ .build(&mut device, TextureFormat::Bgra8UnormSrgb);
+
+ let quad_pipeline = quad::Pipeline::new(&mut device);
+ let image_pipeline = crate::image::Pipeline::new(&mut device);
+
+ Self {
+ surface,
+ adapter,
+ device,
+ queue,
+ quad_pipeline,
+ image_pipeline,
+
+ quads: Vec::new(),
+ images: Vec::new(),
+ glyph_brush: Rc::new(RefCell::new(glyph_brush)),
+ }
+ }
+
+ fn target(&self, width: u16, height: u16) -> Target {
+ Target {
+ width,
+ height,
+ transformation: Transformation::orthographic(width, height),
+ swap_chain: self.device.create_swap_chain(
+ &self.surface,
+ &SwapChainDescriptor {
+ usage: TextureUsage::OUTPUT_ATTACHMENT,
+ format: TextureFormat::Bgra8UnormSrgb,
+ width: u32::from(width),
+ height: u32::from(height),
+ present_mode: wgpu::PresentMode::Vsync,
+ },
+ ),
+ }
+ }
+
+ fn draw(
+ &mut self,
+ (primitive, mouse_cursor): &(Primitive, MouseCursor),
+ target: &mut Target,
+ ) -> MouseCursor {
+ log::debug!("Drawing");
+
+ let frame = target.swap_chain.get_next_texture();
+
+ let mut encoder = self
+ .device
+ .create_command_encoder(&CommandEncoderDescriptor { todo: 0 });
+
+ let _ = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
+ color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor {
+ attachment: &frame.view,
+ resolve_target: None,
+ load_op: wgpu::LoadOp::Clear,
+ store_op: wgpu::StoreOp::Store,
+ clear_color: wgpu::Color {
+ r: 1.0,
+ g: 1.0,
+ b: 1.0,
+ a: 1.0,
+ },
+ }],
+ depth_stencil_attachment: None,
+ });
+
+ self.draw_primitive(primitive);
+
+ self.quad_pipeline.draw(
+ &mut self.device,
+ &mut encoder,
+ &self.quads,
+ target.transformation,
+ &frame.view,
+ );
+
+ self.quads.clear();
+
+ self.image_pipeline.draw(
+ &mut self.device,
+ &mut encoder,
+ &self.images,
+ target.transformation,
+ &frame.view,
+ );
+
+ self.images.clear();
+
+ self.glyph_brush
+ .borrow_mut()
+ .draw_queued(
+ &mut self.device,
+ &mut encoder,
+ &frame.view,
+ u32::from(target.width),
+ u32::from(target.height),
+ )
+ .expect("Draw text");
+
+ self.queue.submit(&[encoder.finish()]);
+
+ *mouse_cursor
+ }
+
+ fn draw_primitive(&mut self, primitive: &Primitive) {
+ match primitive {
+ Primitive::None => {}
+ Primitive::Group { primitives } => {
+ // TODO: Inspect a bit and regroup (?)
+ for primitive in primitives {
+ self.draw_primitive(primitive)
+ }
+ }
+ Primitive::Text {
+ content,
+ bounds,
+ size,
+ color,
+ horizontal_alignment,
+ vertical_alignment,
+ } => {
+ let x = match horizontal_alignment {
+ iced_native::text::HorizontalAlignment::Left => bounds.x,
+ iced_native::text::HorizontalAlignment::Center => {
+ bounds.x + bounds.width / 2.0
+ }
+ iced_native::text::HorizontalAlignment::Right => {
+ bounds.x + bounds.width
+ }
+ };
+
+ let y = match vertical_alignment {
+ iced_native::text::VerticalAlignment::Top => bounds.y,
+ iced_native::text::VerticalAlignment::Center => {
+ bounds.y + bounds.height / 2.0
+ }
+ iced_native::text::VerticalAlignment::Bottom => {
+ bounds.y + bounds.height
+ }
+ };
+
+ self.glyph_brush.borrow_mut().queue(Section {
+ text: &content,
+ screen_position: (x, y),
+ bounds: (bounds.width, bounds.height),
+ scale: wgpu_glyph::Scale { x: *size, y: *size },
+ color: color.into_linear(),
+ layout: wgpu_glyph::Layout::default()
+ .h_align(match horizontal_alignment {
+ iced_native::text::HorizontalAlignment::Left => {
+ wgpu_glyph::HorizontalAlign::Left
+ }
+ iced_native::text::HorizontalAlignment::Center => {
+ wgpu_glyph::HorizontalAlign::Center
+ }
+ iced_native::text::HorizontalAlignment::Right => {
+ wgpu_glyph::HorizontalAlign::Right
+ }
+ })
+ .v_align(match vertical_alignment {
+ iced_native::text::VerticalAlignment::Top => {
+ wgpu_glyph::VerticalAlign::Top
+ }
+ iced_native::text::VerticalAlignment::Center => {
+ wgpu_glyph::VerticalAlign::Center
+ }
+ iced_native::text::VerticalAlignment::Bottom => {
+ wgpu_glyph::VerticalAlign::Bottom
+ }
+ }),
+ ..Default::default()
+ })
+ }
+ Primitive::Quad {
+ bounds,
+ background,
+ border_radius,
+ } => {
+ self.quads.push(Quad {
+ position: [bounds.x, bounds.y],
+ scale: [bounds.width, bounds.height],
+ color: match background {
+ Background::Color(color) => color.into_linear(),
+ },
+ border_radius: u32::from(*border_radius),
+ });
+ }
+ Primitive::Image { path, bounds } => {
+ self.images.push(Image {
+ path: path.clone(),
+ position: [bounds.x, bounds.y],
+ scale: [bounds.width, bounds.height],
+ });
+ }
+ }
+ }
+}
+
+impl iced_native::Renderer for Renderer {
+ type Output = (Primitive, MouseCursor);
+}
+
+impl Windowed for Renderer {
+ type Target = Target;
+
+ fn new<W: HasRawWindowHandle>(window: &W) -> Self {
+ Self::new(window)
+ }
+
+ fn target(&self, width: u16, height: u16) -> Target {
+ self.target(width, height)
+ }
+
+ fn draw(
+ &mut self,
+ output: &Self::Output,
+ target: &mut Target,
+ ) -> MouseCursor {
+ self.draw(output, target)
+ }
+}
+
+impl Debugger for Renderer {
+ fn explain<Message>(
+ &mut self,
+ widget: &dyn Widget<Message, Self>,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ color: Color,
+ ) -> Self::Output {
+ let mut primitives = Vec::new();
+ let (primitive, cursor) = widget.draw(self, layout, cursor_position);
+
+ explain_layout(layout, color, &mut primitives);
+ primitives.push(primitive);
+
+ (Primitive::Group { primitives }, cursor)
+ }
+}
+
+fn explain_layout(
+ layout: Layout,
+ color: Color,
+ primitives: &mut Vec<Primitive>,
+) {
+ // TODO: Draw borders instead
+ primitives.push(Primitive::Quad {
+ bounds: layout.bounds(),
+ background: Background::Color(Color {
+ r: 0.0,
+ g: 0.0,
+ b: 0.0,
+ a: 0.05,
+ }),
+ border_radius: 0,
+ });
+
+ for child in layout.children() {
+ explain_layout(child, color, primitives);
+ }
+}
diff --git a/wgpu/src/renderer/button.rs b/wgpu/src/renderer/button.rs
new file mode 100644
index 00000000..ad2186d6
--- /dev/null
+++ b/wgpu/src/renderer/button.rs
@@ -0,0 +1,86 @@
+use crate::{Primitive, Renderer};
+use iced_native::{
+ button, Align, Background, Button, Color, Layout, Length, MouseCursor,
+ Node, Point, Rectangle, Style,
+};
+
+impl button::Renderer for Renderer {
+ fn node<Message>(&self, button: &Button<Message, Self>) -> Node {
+ let style = Style::default()
+ .width(button.width)
+ .padding(button.padding)
+ .min_width(Length::Units(100))
+ .align_self(button.align_self)
+ .align_items(Align::Stretch);
+
+ Node::with_children(style, vec![button.content.node(self)])
+ }
+
+ fn draw<Message>(
+ &mut self,
+ button: &Button<Message, Self>,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ ) -> Self::Output {
+ let bounds = layout.bounds();
+
+ let (content, _) = button.content.draw(
+ self,
+ layout.children().next().unwrap(),
+ cursor_position,
+ );
+
+ let is_mouse_over = bounds.contains(cursor_position);
+
+ // TODO: Render proper shadows
+ // TODO: Make hovering and pressed styles configurable
+ let shadow_offset = if is_mouse_over {
+ if button.state.is_pressed {
+ 0.0
+ } else {
+ 2.0
+ }
+ } else {
+ 1.0
+ };
+
+ (
+ Primitive::Group {
+ primitives: vec![
+ Primitive::Quad {
+ bounds: Rectangle {
+ x: bounds.x + 1.0,
+ y: bounds.y + shadow_offset,
+ ..bounds
+ },
+ background: Background::Color(Color {
+ r: 0.0,
+ b: 0.0,
+ g: 0.0,
+ a: 0.5,
+ }),
+ border_radius: button.border_radius,
+ },
+ Primitive::Quad {
+ bounds,
+ background: button.background.unwrap_or(
+ Background::Color(Color {
+ r: 0.8,
+ b: 0.8,
+ g: 0.8,
+ a: 1.0,
+ }),
+ ),
+ border_radius: button.border_radius,
+ },
+ content,
+ ],
+ },
+ if is_mouse_over {
+ MouseCursor::Pointer
+ } else {
+ MouseCursor::OutOfBounds
+ },
+ )
+ }
+}
diff --git a/wgpu/src/renderer/checkbox.rs b/wgpu/src/renderer/checkbox.rs
new file mode 100644
index 00000000..fd3f08b1
--- /dev/null
+++ b/wgpu/src/renderer/checkbox.rs
@@ -0,0 +1,106 @@
+use crate::{Primitive, Renderer};
+use iced_native::{
+ checkbox, text, text::HorizontalAlignment, text::VerticalAlignment, Align,
+ Background, Checkbox, Color, Column, Layout, Length, MouseCursor, Node,
+ Point, Rectangle, Row, Text, Widget,
+};
+
+const SIZE: f32 = 28.0;
+
+impl checkbox::Renderer for Renderer {
+ fn node<Message>(&self, checkbox: &Checkbox<Message>) -> Node {
+ Row::<(), Self>::new()
+ .spacing(15)
+ .align_items(Align::Center)
+ .push(
+ Column::new()
+ .width(Length::Units(SIZE as u16))
+ .height(Length::Units(SIZE as u16)),
+ )
+ .push(Text::new(&checkbox.label))
+ .node(self)
+ }
+
+ fn draw<Message>(
+ &mut self,
+ checkbox: &Checkbox<Message>,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ ) -> Self::Output {
+ let bounds = layout.bounds();
+ let mut children = layout.children();
+
+ let checkbox_layout = children.next().unwrap();
+ let label_layout = children.next().unwrap();
+ let checkbox_bounds = checkbox_layout.bounds();
+
+ let (label, _) = text::Renderer::draw(
+ self,
+ &Text::new(&checkbox.label),
+ label_layout,
+ );
+
+ let is_mouse_over = bounds.contains(cursor_position);
+
+ let (checkbox_border, checkbox_box) = (
+ Primitive::Quad {
+ bounds: checkbox_bounds,
+ background: Background::Color(Color {
+ r: 0.6,
+ g: 0.6,
+ b: 0.6,
+ a: 1.0,
+ }),
+ border_radius: 6,
+ },
+ Primitive::Quad {
+ bounds: Rectangle {
+ x: checkbox_bounds.x + 1.0,
+ y: checkbox_bounds.y + 1.0,
+ width: checkbox_bounds.width - 2.0,
+ height: checkbox_bounds.height - 2.0,
+ },
+ background: Background::Color(if is_mouse_over {
+ Color {
+ r: 0.90,
+ g: 0.90,
+ b: 0.90,
+ a: 1.0,
+ }
+ } else {
+ Color {
+ r: 0.95,
+ g: 0.95,
+ b: 0.95,
+ a: 1.0,
+ }
+ }),
+ border_radius: 6,
+ },
+ );
+
+ (
+ Primitive::Group {
+ primitives: if checkbox.is_checked {
+ // TODO: Draw an actual icon
+ let (check, _) = text::Renderer::draw(
+ self,
+ &Text::new("X")
+ .horizontal_alignment(HorizontalAlignment::Center)
+ .vertical_alignment(VerticalAlignment::Center),
+ checkbox_layout,
+ );
+
+ vec![checkbox_border, checkbox_box, check, label]
+ } else {
+ vec![checkbox_border, checkbox_box, label]
+ },
+ },
+ if is_mouse_over {
+ MouseCursor::Pointer
+ } else {
+ MouseCursor::OutOfBounds
+ },
+ )
+ }
+}
diff --git a/wgpu/src/renderer/column.rs b/wgpu/src/renderer/column.rs
new file mode 100644
index 00000000..cac6da77
--- /dev/null
+++ b/wgpu/src/renderer/column.rs
@@ -0,0 +1,34 @@
+use crate::{Primitive, Renderer};
+use iced_native::{column, Column, Layout, MouseCursor, Point};
+
+impl column::Renderer for Renderer {
+ fn draw<Message>(
+ &mut self,
+ column: &Column<'_, Message, Self>,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ ) -> Self::Output {
+ let mut mouse_cursor = MouseCursor::OutOfBounds;
+
+ (
+ Primitive::Group {
+ primitives: column
+ .children
+ .iter()
+ .zip(layout.children())
+ .map(|(child, layout)| {
+ let (primitive, new_mouse_cursor) =
+ child.draw(self, layout, cursor_position);
+
+ if new_mouse_cursor > mouse_cursor {
+ mouse_cursor = new_mouse_cursor;
+ }
+
+ primitive
+ })
+ .collect(),
+ },
+ mouse_cursor,
+ )
+ }
+}
diff --git a/wgpu/src/renderer/image.rs b/wgpu/src/renderer/image.rs
new file mode 100644
index 00000000..0e312706
--- /dev/null
+++ b/wgpu/src/renderer/image.rs
@@ -0,0 +1,34 @@
+use crate::{Primitive, Renderer};
+use iced_native::{image, Image, Layout, Length, MouseCursor, Node, Style};
+
+impl image::Renderer for Renderer {
+ fn node(&self, image: &Image) -> Node {
+ let (width, height) = self.image_pipeline.dimensions(&image.path);
+
+ let aspect_ratio = width as f32 / height as f32;
+
+ let mut style = Style::default().align_self(image.align_self);
+
+ // TODO: Deal with additional cases
+ style = match (image.width, image.height) {
+ (Length::Units(width), _) => style.width(image.width).height(
+ Length::Units((width as f32 / aspect_ratio).round() as u16),
+ ),
+ (_, _) => style
+ .width(Length::Units(width as u16))
+ .height(Length::Units(height as u16)),
+ };
+
+ Node::new(style)
+ }
+
+ fn draw(&mut self, image: &Image, layout: Layout<'_>) -> Self::Output {
+ (
+ Primitive::Image {
+ path: image.path.clone(),
+ bounds: layout.bounds(),
+ },
+ MouseCursor::OutOfBounds,
+ )
+ }
+}
diff --git a/wgpu/src/renderer/radio.rs b/wgpu/src/renderer/radio.rs
new file mode 100644
index 00000000..97b4f70e
--- /dev/null
+++ b/wgpu/src/renderer/radio.rs
@@ -0,0 +1,109 @@
+use crate::{Primitive, Renderer};
+use iced_native::{
+ radio, text, Align, Background, Color, Column, Layout, Length, MouseCursor,
+ Node, Point, Radio, Rectangle, Row, Text, Widget,
+};
+
+const SIZE: f32 = 28.0;
+const DOT_SIZE: f32 = SIZE / 2.0;
+
+impl radio::Renderer for Renderer {
+ fn node<Message>(&self, radio: &Radio<Message>) -> Node {
+ Row::<(), Self>::new()
+ .spacing(15)
+ .align_items(Align::Center)
+ .push(
+ Column::new()
+ .width(Length::Units(SIZE as u16))
+ .height(Length::Units(SIZE as u16)),
+ )
+ .push(Text::new(&radio.label))
+ .node(self)
+ }
+
+ fn draw<Message>(
+ &mut self,
+ radio: &Radio<Message>,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ ) -> Self::Output {
+ let bounds = layout.bounds();
+ let mut children = layout.children();
+
+ let radio_bounds = children.next().unwrap().bounds();
+ let label_layout = children.next().unwrap();
+
+ let (label, _) =
+ text::Renderer::draw(self, &Text::new(&radio.label), label_layout);
+
+ let is_mouse_over = bounds.contains(cursor_position);
+
+ let (radio_border, radio_box) = (
+ Primitive::Quad {
+ bounds: radio_bounds,
+ background: Background::Color(Color {
+ r: 0.6,
+ g: 0.6,
+ b: 0.6,
+ a: 1.0,
+ }),
+ border_radius: (SIZE / 2.0) as u16,
+ },
+ Primitive::Quad {
+ bounds: Rectangle {
+ x: radio_bounds.x + 1.0,
+ y: radio_bounds.y + 1.0,
+ width: radio_bounds.width - 2.0,
+ height: radio_bounds.height - 2.0,
+ },
+ background: Background::Color(if is_mouse_over {
+ Color {
+ r: 0.90,
+ g: 0.90,
+ b: 0.90,
+ a: 1.0,
+ }
+ } else {
+ Color {
+ r: 0.95,
+ g: 0.95,
+ b: 0.95,
+ a: 1.0,
+ }
+ }),
+ border_radius: (SIZE / 2.0 - 1.0) as u16,
+ },
+ );
+
+ (
+ Primitive::Group {
+ primitives: if radio.is_selected {
+ let radio_circle = Primitive::Quad {
+ bounds: Rectangle {
+ x: radio_bounds.x + DOT_SIZE / 2.0,
+ y: radio_bounds.y + DOT_SIZE / 2.0,
+ width: radio_bounds.width - DOT_SIZE,
+ height: radio_bounds.height - DOT_SIZE,
+ },
+ background: Background::Color(Color {
+ r: 0.30,
+ g: 0.30,
+ b: 0.30,
+ a: 1.0,
+ }),
+ border_radius: (DOT_SIZE / 2.0) as u16,
+ };
+
+ vec![radio_border, radio_box, radio_circle, label]
+ } else {
+ vec![radio_border, radio_box, label]
+ },
+ },
+ if is_mouse_over {
+ MouseCursor::Pointer
+ } else {
+ MouseCursor::OutOfBounds
+ },
+ )
+ }
+}
diff --git a/wgpu/src/renderer/row.rs b/wgpu/src/renderer/row.rs
new file mode 100644
index 00000000..bbfef9a1
--- /dev/null
+++ b/wgpu/src/renderer/row.rs
@@ -0,0 +1,34 @@
+use crate::{Primitive, Renderer};
+use iced_native::{row, Layout, MouseCursor, Point, Row};
+
+impl row::Renderer for Renderer {
+ fn draw<Message>(
+ &mut self,
+ row: &Row<'_, Message, Self>,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ ) -> Self::Output {
+ let mut mouse_cursor = MouseCursor::OutOfBounds;
+
+ (
+ Primitive::Group {
+ primitives: row
+ .children
+ .iter()
+ .zip(layout.children())
+ .map(|(child, layout)| {
+ let (primitive, new_mouse_cursor) =
+ child.draw(self, layout, cursor_position);
+
+ if new_mouse_cursor > mouse_cursor {
+ mouse_cursor = new_mouse_cursor;
+ }
+
+ primitive
+ })
+ .collect(),
+ },
+ mouse_cursor,
+ )
+ }
+}
diff --git a/wgpu/src/renderer/slider.rs b/wgpu/src/renderer/slider.rs
new file mode 100644
index 00000000..4ae3abc4
--- /dev/null
+++ b/wgpu/src/renderer/slider.rs
@@ -0,0 +1,128 @@
+use crate::{Primitive, Renderer};
+use iced_native::{
+ slider, Background, Color, Layout, Length, MouseCursor, Node, Point,
+ Rectangle, Slider, Style,
+};
+
+const HANDLE_WIDTH: f32 = 8.0;
+const HANDLE_HEIGHT: f32 = 22.0;
+
+impl slider::Renderer for Renderer {
+ fn node<Message>(&self, slider: &Slider<Message>) -> Node {
+ let style = Style::default()
+ .width(slider.width)
+ .height(Length::Units(HANDLE_HEIGHT as u16))
+ .min_width(Length::Units(100));
+
+ Node::new(style)
+ }
+
+ fn draw<Message>(
+ &mut self,
+ slider: &Slider<Message>,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ ) -> Self::Output {
+ let bounds = layout.bounds();
+
+ let is_mouse_over = bounds.contains(cursor_position);
+
+ let rail_y = bounds.y + (bounds.height / 2.0).round();
+
+ let (rail_top, rail_bottom) = (
+ Primitive::Quad {
+ bounds: Rectangle {
+ x: bounds.x,
+ y: rail_y,
+ width: bounds.width,
+ height: 2.0,
+ },
+ background: Background::Color(Color {
+ r: 0.6,
+ g: 0.6,
+ b: 0.6,
+ a: 1.0,
+ }),
+ border_radius: 0,
+ },
+ Primitive::Quad {
+ bounds: Rectangle {
+ x: bounds.x,
+ y: rail_y + 2.0,
+ width: bounds.width,
+ height: 2.0,
+ },
+ background: Background::Color(Color::WHITE),
+ border_radius: 0,
+ },
+ );
+
+ let (range_start, range_end) = slider.range.clone().into_inner();
+
+ let handle_offset = (bounds.width - HANDLE_WIDTH)
+ * ((slider.value - range_start)
+ / (range_end - range_start).max(1.0));
+
+ let (handle_border, handle) = (
+ Primitive::Quad {
+ bounds: Rectangle {
+ x: bounds.x + handle_offset.round() - 1.0,
+ y: rail_y - HANDLE_HEIGHT / 2.0 - 1.0,
+ width: HANDLE_WIDTH + 2.0,
+ height: HANDLE_HEIGHT + 2.0,
+ },
+ background: Background::Color(Color {
+ r: 0.6,
+ g: 0.6,
+ b: 0.6,
+ a: 1.0,
+ }),
+ border_radius: 5,
+ },
+ Primitive::Quad {
+ bounds: Rectangle {
+ x: bounds.x + handle_offset.round(),
+ y: rail_y - HANDLE_HEIGHT / 2.0,
+ width: HANDLE_WIDTH,
+ height: HANDLE_HEIGHT,
+ },
+ background: Background::Color(if slider.state.is_dragging() {
+ Color {
+ r: 0.85,
+ g: 0.85,
+ b: 0.85,
+ a: 1.0,
+ }
+ } else if is_mouse_over {
+ Color {
+ r: 0.9,
+ g: 0.9,
+ b: 0.9,
+ a: 1.0,
+ }
+ } else {
+ Color {
+ r: 0.95,
+ g: 0.95,
+ b: 0.95,
+ a: 1.0,
+ }
+ }),
+ border_radius: 4,
+ },
+ );
+
+ (
+ Primitive::Group {
+ primitives: vec![rail_top, rail_bottom, handle_border, handle],
+ },
+ if slider.state.is_dragging() {
+ MouseCursor::Grabbing
+ } else if is_mouse_over {
+ MouseCursor::Grab
+ } else {
+ MouseCursor::OutOfBounds
+ },
+ )
+ }
+}
diff --git a/wgpu/src/renderer/text.rs b/wgpu/src/renderer/text.rs
new file mode 100644
index 00000000..8fbade4e
--- /dev/null
+++ b/wgpu/src/renderer/text.rs
@@ -0,0 +1,83 @@
+use crate::{Primitive, Renderer};
+use iced_native::{text, Color, Layout, MouseCursor, Node, Style, Text};
+
+use wgpu_glyph::{GlyphCruncher, Section};
+
+use std::cell::RefCell;
+use std::f32;
+
+impl text::Renderer for Renderer {
+ fn node(&self, text: &Text) -> Node {
+ let glyph_brush = self.glyph_brush.clone();
+ let content = text.content.clone();
+
+ // TODO: Investigate why stretch tries to measure this MANY times
+ // with every ancestor's bounds.
+ // Bug? Using the library wrong? I should probably open an issue on
+ // the stretch repository.
+ // I noticed that the first measure is the one that matters in
+ // practice. Here, we use a RefCell to store the cached measurement.
+ let measure = RefCell::new(None);
+ let size = text.size.map(f32::from).unwrap_or(20.0);
+
+ let style = Style::default().width(text.width);
+
+ iced_native::Node::with_measure(style, move |bounds| {
+ let mut measure = measure.borrow_mut();
+
+ if measure.is_none() {
+ let bounds = (
+ match bounds.width {
+ iced_native::Number::Undefined => f32::INFINITY,
+ iced_native::Number::Defined(w) => w,
+ },
+ match bounds.height {
+ iced_native::Number::Undefined => f32::INFINITY,
+ iced_native::Number::Defined(h) => h,
+ },
+ );
+
+ let text = Section {
+ text: &content,
+ scale: wgpu_glyph::Scale { x: size, y: size },
+ bounds,
+ ..Default::default()
+ };
+
+ let (width, height) = if let Some(bounds) =
+ glyph_brush.borrow_mut().glyph_bounds(&text)
+ {
+ (bounds.width(), bounds.height())
+ } else {
+ (0.0, 0.0)
+ };
+
+ let size = iced_native::Size { width, height };
+
+ // If the text has no width boundary we avoid caching as the
+ // layout engine may just be measuring text in a row.
+ if bounds.0 == f32::INFINITY {
+ return size;
+ } else {
+ *measure = Some(size);
+ }
+ }
+
+ measure.unwrap()
+ })
+ }
+
+ fn draw(&mut self, text: &Text, layout: Layout<'_>) -> Self::Output {
+ (
+ Primitive::Text {
+ content: text.content.clone(),
+ size: f32::from(text.size.unwrap_or(20)),
+ bounds: layout.bounds(),
+ color: text.color.unwrap_or(Color::BLACK),
+ horizontal_alignment: text.horizontal_alignment,
+ vertical_alignment: text.vertical_alignment,
+ },
+ MouseCursor::OutOfBounds,
+ )
+ }
+}
diff --git a/wgpu/src/shader/image.frag b/wgpu/src/shader/image.frag
new file mode 100644
index 00000000..e35e455a
--- /dev/null
+++ b/wgpu/src/shader/image.frag
@@ -0,0 +1,12 @@
+#version 450
+
+layout(location = 0) in vec2 v_Uv;
+
+layout(set = 0, binding = 1) uniform sampler u_Sampler;
+layout(set = 1, binding = 0) uniform texture2D u_Texture;
+
+layout(location = 0) out vec4 o_Color;
+
+void main() {
+ o_Color = texture(sampler2D(u_Texture, u_Sampler), v_Uv);
+}
diff --git a/wgpu/src/shader/image.frag.spv b/wgpu/src/shader/image.frag.spv
new file mode 100644
index 00000000..ebee82ac
--- /dev/null
+++ b/wgpu/src/shader/image.frag.spv
Binary files differ
diff --git a/wgpu/src/shader/image.vert b/wgpu/src/shader/image.vert
new file mode 100644
index 00000000..688c2311
--- /dev/null
+++ b/wgpu/src/shader/image.vert
@@ -0,0 +1,24 @@
+#version 450
+
+layout(location = 0) in vec2 v_Pos;
+layout(location = 1) in vec2 i_Pos;
+layout(location = 2) in vec2 i_Scale;
+
+layout (set = 0, binding = 0) uniform Globals {
+ mat4 u_Transform;
+};
+
+layout(location = 0) out vec2 o_Uv;
+
+void main() {
+ o_Uv = v_Pos;
+
+ mat4 i_Transform = mat4(
+ vec4(i_Scale.x, 0.0, 0.0, 0.0),
+ vec4(0.0, i_Scale.y, 0.0, 0.0),
+ vec4(0.0, 0.0, 1.0, 0.0),
+ vec4(i_Pos, 0.0, 1.0)
+ );
+
+ gl_Position = u_Transform * i_Transform * vec4(v_Pos, 0.0, 1.0);
+}
diff --git a/wgpu/src/shader/image.vert.spv b/wgpu/src/shader/image.vert.spv
new file mode 100644
index 00000000..9ba702bc
--- /dev/null
+++ b/wgpu/src/shader/image.vert.spv
Binary files differ
diff --git a/wgpu/src/shader/quad.frag b/wgpu/src/shader/quad.frag
new file mode 100644
index 00000000..849f581e
--- /dev/null
+++ b/wgpu/src/shader/quad.frag
@@ -0,0 +1,37 @@
+#version 450
+
+layout(location = 0) in vec4 v_Color;
+layout(location = 1) in vec2 v_Pos;
+layout(location = 2) in vec2 v_Scale;
+layout(location = 3) in flat uint v_BorderRadius;
+
+layout(location = 0) out vec4 o_Color;
+
+float rounded(in vec2 frag_coord, in vec2 position, in vec2 size, float radius, float s)
+{
+ vec2 inner_size = size - vec2(radius, radius) * 2.0;
+ vec2 top_left = position + vec2(radius, radius);
+ vec2 bottom_right = top_left + inner_size;
+
+ vec2 top_left_distance = top_left - frag_coord;
+ vec2 bottom_right_distance = frag_coord - bottom_right;
+
+ vec2 distance = vec2(
+ max(max(top_left_distance.x, bottom_right_distance.x), 0),
+ max(max(top_left_distance.y, bottom_right_distance.y), 0)
+ );
+
+ float d = sqrt(distance.x * distance.x + distance.y * distance.y);
+
+ return 1.0 - smoothstep(radius - s, radius + s, d);
+}
+
+void main() {
+ float radius_alpha = 1.0;
+
+ if(v_BorderRadius > 0.0) {
+ radius_alpha = rounded(gl_FragCoord.xy, v_Pos, v_Scale, v_BorderRadius, 0.5);
+ }
+
+ o_Color = vec4(v_Color.xyz, v_Color.w * radius_alpha);
+}
diff --git a/wgpu/src/shader/quad.frag.spv b/wgpu/src/shader/quad.frag.spv
new file mode 100644
index 00000000..71b91b44
--- /dev/null
+++ b/wgpu/src/shader/quad.frag.spv
Binary files differ
diff --git a/wgpu/src/shader/quad.vert b/wgpu/src/shader/quad.vert
new file mode 100644
index 00000000..b7c5cf3e
--- /dev/null
+++ b/wgpu/src/shader/quad.vert
@@ -0,0 +1,32 @@
+#version 450
+
+layout(location = 0) in vec2 v_Pos;
+layout(location = 1) in vec2 i_Pos;
+layout(location = 2) in vec2 i_Scale;
+layout(location = 3) in vec4 i_Color;
+layout(location = 4) in uint i_BorderRadius;
+
+layout (set = 0, binding = 0) uniform Globals {
+ mat4 u_Transform;
+};
+
+layout(location = 0) out vec4 o_Color;
+layout(location = 1) out vec2 o_Pos;
+layout(location = 2) out vec2 o_Scale;
+layout(location = 3) out uint o_BorderRadius;
+
+void main() {
+ mat4 i_Transform = mat4(
+ vec4(i_Scale.x, 0.0, 0.0, 0.0),
+ vec4(0.0, i_Scale.y, 0.0, 0.0),
+ vec4(0.0, 0.0, 1.0, 0.0),
+ vec4(i_Pos, 0.0, 1.0)
+ );
+
+ o_Color = i_Color;
+ o_Pos = i_Pos;
+ o_Scale = i_Scale;
+ o_BorderRadius = i_BorderRadius;
+
+ gl_Position = u_Transform * i_Transform * vec4(v_Pos, 0.0, 1.0);
+}
diff --git a/wgpu/src/shader/quad.vert.spv b/wgpu/src/shader/quad.vert.spv
new file mode 100644
index 00000000..f62a160c
--- /dev/null
+++ b/wgpu/src/shader/quad.vert.spv
Binary files differ
diff --git a/wgpu/src/transformation.rs b/wgpu/src/transformation.rs
new file mode 100644
index 00000000..1101e135
--- /dev/null
+++ b/wgpu/src/transformation.rs
@@ -0,0 +1,30 @@
+#[derive(Debug, Clone, Copy)]
+pub struct Transformation([f32; 16]);
+
+impl Transformation {
+ #[rustfmt::skip]
+ pub fn identity() -> Self {
+ Transformation([
+ 1.0, 0.0, 0.0, 0.0,
+ 0.0, 1.0, 0.0, 0.0,
+ 0.0, 0.0, 1.0, 0.0,
+ 0.0, 0.0, 0.0, 1.0,
+ ])
+ }
+
+ #[rustfmt::skip]
+ pub fn orthographic(width: u16, height: u16) -> Self {
+ Transformation([
+ 2.0 / width as f32, 0.0, 0.0, 0.0,
+ 0.0, 2.0 / height as f32, 0.0, 0.0,
+ 0.0, 0.0, 1.0, 0.0,
+ -1.0, -1.0, 0.0, 1.0,
+ ])
+ }
+}
+
+impl From<Transformation> for [f32; 16] {
+ fn from(transformation: Transformation) -> [f32; 16] {
+ transformation.0
+ }
+}