diff options
-rw-r--r-- | Cargo.toml | 3 | ||||
-rw-r--r-- | README.md | 3 | ||||
-rw-r--r-- | core/src/widget/image.rs | 28 | ||||
-rw-r--r-- | core/src/widget/text.rs | 2 | ||||
-rw-r--r-- | examples/README.md | 14 | ||||
-rw-r--r-- | examples/tour.html | 5 | ||||
-rw-r--r-- | examples/tour.rs | 38 | ||||
-rw-r--r-- | native/Cargo.toml | 3 | ||||
-rw-r--r-- | native/src/widget/image.rs | 18 | ||||
-rw-r--r-- | src/lib.rs | 36 | ||||
-rw-r--r-- | src/web.rs | 1 | ||||
-rw-r--r-- | src/winit.rs | 11 | ||||
-rw-r--r-- | web/Cargo.toml | 3 | ||||
-rw-r--r-- | web/src/lib.rs | 13 | ||||
-rw-r--r-- | web/src/widget/image.rs | 10 | ||||
-rw-r--r-- | wgpu/Cargo.toml | 1 | ||||
-rw-r--r-- | wgpu/src/image.rs | 438 | ||||
-rw-r--r-- | wgpu/src/lib.rs | 2 | ||||
-rw-r--r-- | wgpu/src/primitive.rs | 4 | ||||
-rw-r--r-- | wgpu/src/renderer.rs | 24 | ||||
-rw-r--r-- | wgpu/src/renderer/image.rs | 38 | ||||
-rw-r--r-- | wgpu/src/shader/image.frag | 12 | ||||
-rw-r--r-- | wgpu/src/shader/image.frag.spv | bin | 0 -> 684 bytes | |||
-rw-r--r-- | wgpu/src/shader/image.vert | 24 | ||||
-rw-r--r-- | wgpu/src/shader/image.vert.spv | bin | 0 -> 2136 bytes | |||
-rw-r--r-- | winit/src/application.rs | 4 |
26 files changed, 650 insertions, 85 deletions
@@ -32,3 +32,6 @@ iced_web = { version = "0.1.0-alpha", path = "web" } [dev-dependencies] env_logger = "0.7" + +[target.'cfg(target_arch = "wasm32")'.dev-dependencies] +wasm-bindgen = "0.2.51" @@ -88,10 +88,9 @@ __view logic__: ```rust use iced::{Button, Column, Text}; -use iced_wgpu::Renderer; // Iced does not include a renderer! We need to bring our own! impl Counter { - pub fn view(&mut self) -> Column<Message, Renderer> { + pub fn view(&mut self) -> Column<Message> { // We use a column: a simple vertical layout Column::new() .push( diff --git a/core/src/widget/image.rs b/core/src/widget/image.rs index 110ba99a..6e410dce 100644 --- a/core/src/widget/image.rs +++ b/core/src/widget/image.rs @@ -9,12 +9,12 @@ use crate::{Align, Length, Rectangle}; /// ``` /// use iced_core::Image; /// -/// # let my_handle = String::from("some_handle"); -/// let image = Image::new(my_handle); +/// let image = Image::new("resources/ferris.png"); /// ``` -pub struct Image<I> { - /// The image handle - pub handle: I, +#[derive(Debug)] +pub struct Image { + /// The image path + pub path: String, /// The part of the image to show pub clip: Option<Rectangle<u16>>, @@ -28,23 +28,13 @@ pub struct Image<I> { pub align_self: Option<Align>, } -impl<I> std::fmt::Debug for Image<I> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Image") - .field("clip", &self.clip) - .field("width", &self.width) - .field("height", &self.height) - .finish() - } -} - -impl<I> Image<I> { - /// Creates a new [`Image`] with given image handle. +impl Image { + /// Creates a new [`Image`] with the given path. /// /// [`Image`]: struct.Image.html - pub fn new(handle: I) -> Self { + pub fn new<T: Into<String>>(path: T) -> Self { Image { - handle, + path: path.into(), clip: None, width: Length::Shrink, height: Length::Shrink, diff --git a/core/src/widget/text.rs b/core/src/widget/text.rs index cd94dbb2..427d9471 100644 --- a/core/src/widget/text.rs +++ b/core/src/widget/text.rs @@ -1,5 +1,5 @@ //! Write some text for your users to read. -use crate::{Align, Color, Length}; +use crate::{Color, Length}; /// A paragraph of text. /// diff --git a/examples/README.md b/examples/README.md index fe69b34a..0a06a012 100644 --- a/examples/README.md +++ b/examples/README.md @@ -37,7 +37,7 @@ __update logic__ and __view logic__. [`wgpu`]: https://github.com/gfx-rs/wgpu-rs #### Running the native version -Simply use [Cargo](https://doc.rust-lang.org/cargo/reference/manifest.html#examples) +Use [Cargo](https://doc.rust-lang.org/cargo/reference/manifest.html#examples) to run the example: ``` @@ -45,10 +45,20 @@ cargo run --example tour ``` #### Running the web version +Build using the `wasm32-unknown-unknown` target and use the [`wasm-bindgen`] CLI +to generate appropriate bindings in a `tour` directory. + ``` -TODO +cd examples +cargo build --example tour --target wasm32-unknown-unknown +wasm-bindgen ../target/wasm32-unknown-unknown/debug/examples/tour.wasm --out-dir tour --web ``` +Finally, serve the `examples` directory using an HTTP server and access the +`tour.html` file. + +[`wasm-bindgen`]: https://github.com/rustwasm/wasm-bindgen + ## [Coffee] diff --git a/examples/tour.html b/examples/tour.html index b17ac4a2..35360e59 100644 --- a/examples/tour.html +++ b/examples/tour.html @@ -6,8 +6,9 @@ </head> <body> <script type="module"> - import init from "./pkg/iced_tour.js"; - init("./pkg/iced_tour_bg.wasm"); + import init from "./tour/tour.js"; + + init('./tour/tour_bg.wasm'); </script> </body> </html> diff --git a/examples/tour.rs b/examples/tour.rs index f77dc241..59a8c525 100644 --- a/examples/tour.rs +++ b/examples/tour.rs @@ -74,7 +74,7 @@ impl Application for Tour { } let element: Element<_> = Column::new() - .max_width(Length::Units(500)) + .max_width(Length::Units(540)) .spacing(20) .padding(20) .push(steps.view(self.debug).map(Message::StepMessage)) @@ -308,7 +308,7 @@ impl<'a> Step { that can be easily implemented on top of Iced.", )) .push(Text::new( - "Iced is a renderer-agnostic GUI library for Rust focused on \ + "Iced is a cross-platform GUI library for Rust focused on \ simplicity and type-safety. It is heavily inspired by Elm.", )) .push(Text::new( @@ -316,9 +316,9 @@ impl<'a> Step { 2D game engine for Rust.", )) .push(Text::new( - "Iced does not provide a built-in renderer. On native \ - platforms, this example runs on a fairly simple renderer \ - built on top of ggez, another game library.", + "On native platforms, Iced provides by default a renderer \ + built on top of wgpu, a graphics library supporting Vulkan, \ + Metal, DX11, and DX12.", )) .push(Text::new( "Additionally, this tour can also run on WebAssembly thanks \ @@ -503,9 +503,18 @@ impl<'a> Step { Self::container("Image") .push(Text::new("An image that tries to keep its aspect ratio.")) .push( - Image::new("resources/ferris.png") - .width(Length::Units(width)) - .align_self(Align::Center), + // This should go away once we unify resource loading on native + // platforms + if cfg!(target_arch = "wasm32") { + Image::new("resources/ferris.png") + } else { + Image::new(format!( + "{}/examples/resources/ferris.png", + env!("CARGO_MANIFEST_DIR") + )) + } + .width(Length::Units(width)) + .align_self(Align::Center), ) .push(Slider::new( slider, @@ -625,3 +634,16 @@ pub enum Layout { Row, Column, } + +// This should be gracefully handled by Iced in the future. Probably using our +// own proc macro, or maybe the whole process is streamlined by `wasm-pack` at +// some point. +#[cfg(target_arch = "wasm32")] +mod wasm { + use wasm_bindgen::prelude::*; + + #[wasm_bindgen(start)] + pub fn run() { + super::main() + } +} diff --git a/native/Cargo.toml b/native/Cargo.toml index 6870649a..8cabe94c 100644 --- a/native/Cargo.toml +++ b/native/Cargo.toml @@ -7,9 +7,6 @@ description = "A renderer-agnostic library for native GUIs" license = "MIT" repository = "https://github.com/hecrj/iced" -[package.metadata.docs.rs] -features = ["winit"] - [dependencies] iced_core = { version = "0.1.0-alpha", path = "../core" } stretch = "0.2" diff --git a/native/src/widget/image.rs b/native/src/widget/image.rs index 178dd709..6255a7b5 100644 --- a/native/src/widget/image.rs +++ b/native/src/widget/image.rs @@ -6,10 +6,9 @@ use std::hash::Hash; pub use iced_core::Image; -impl<I, Message, Renderer> Widget<Message, Renderer> for Image<I> +impl<Message, Renderer> Widget<Message, Renderer> for Image where - Renderer: self::Renderer<I>, - I: Clone, + Renderer: self::Renderer, { fn node(&self, renderer: &Renderer) -> Node { renderer.node(&self) @@ -38,27 +37,26 @@ where /// /// [`Image`]: struct.Image.html /// [renderer]: ../../renderer/index.html -pub trait Renderer<I>: crate::Renderer { +pub trait Renderer: crate::Renderer { /// Creates a [`Node`] for the provided [`Image`]. /// /// You should probably keep the original aspect ratio, if possible. /// /// [`Node`]: ../../struct.Node.html /// [`Image`]: struct.Image.html - fn node(&self, image: &Image<I>) -> Node; + fn node(&self, image: &Image) -> Node; /// Draws an [`Image`]. /// /// [`Image`]: struct.Image.html - fn draw(&mut self, image: &Image<I>, layout: Layout<'_>) -> Self::Output; + fn draw(&mut self, image: &Image, layout: Layout<'_>) -> Self::Output; } -impl<'a, I, Message, Renderer> From<Image<I>> for Element<'a, Message, Renderer> +impl<'a, Message, Renderer> From<Image> for Element<'a, Message, Renderer> where - Renderer: self::Renderer<I>, - I: Clone + 'a, + Renderer: self::Renderer, { - fn from(image: Image<I>) -> Element<'a, Message, Renderer> { + fn from(image: Image) -> Element<'a, Message, Renderer> { Element::new(image) } } @@ -1,13 +1,8 @@ -pub use iced_wgpu::{Primitive, Renderer}; -pub use iced_winit::{ - button, slider, text, winit, Align, Background, Checkbox, Color, Image, - Justify, Length, Radio, Slider, Text, -}; +#[cfg_attr(target_arch = "wasm32", path = "web.rs")] +#[cfg_attr(not(target_arch = "wasm32"), path = "winit.rs")] +mod platform; -pub type Element<'a, Message> = iced_winit::Element<'a, Message, Renderer>; -pub type Row<'a, Message> = iced_winit::Row<'a, Message, Renderer>; -pub type Column<'a, Message> = iced_winit::Column<'a, Message, Renderer>; -pub type Button<'a, Message> = iced_winit::Button<'a, Message, Renderer>; +pub use platform::*; pub trait Application { type Message; @@ -20,12 +15,17 @@ pub trait Application { where Self: 'static + Sized, { - iced_winit::Application::run(Instance(self)) + #[cfg(not(target_arch = "wasm32"))] + iced_winit::Application::run(Instance(self)); + + #[cfg(target_arch = "wasm32")] + iced_web::Application::run(Instance(self)); } } struct Instance<A: Application>(A); +#[cfg(not(target_arch = "wasm32"))] impl<A> iced_winit::Application for Instance<A> where A: Application, @@ -41,3 +41,19 @@ where self.0.view() } } + +#[cfg(target_arch = "wasm32")] +impl<A> iced_web::Application for Instance<A> +where + A: Application, +{ + type Message = A::Message; + + fn update(&mut self, message: Self::Message) { + self.0.update(message); + } + + fn view(&mut self) -> Element<Self::Message> { + self.0.view() + } +} diff --git a/src/web.rs b/src/web.rs new file mode 100644 index 00000000..31f1a6fc --- /dev/null +++ b/src/web.rs @@ -0,0 +1 @@ +pub use iced_web::*; diff --git a/src/winit.rs b/src/winit.rs new file mode 100644 index 00000000..64e301f4 --- /dev/null +++ b/src/winit.rs @@ -0,0 +1,11 @@ +pub use iced_wgpu::{Primitive, Renderer}; + +pub use iced_winit::{ + button, slider, text, winit, Align, Background, Checkbox, Color, Image, + Justify, Length, Radio, Slider, Text, +}; + +pub type Element<'a, Message> = iced_winit::Element<'a, Message, Renderer>; +pub type Row<'a, Message> = iced_winit::Row<'a, Message, Renderer>; +pub type Column<'a, Message> = iced_winit::Column<'a, Message, Renderer>; +pub type Button<'a, Message> = iced_winit::Button<'a, Message, Renderer>; diff --git a/web/Cargo.toml b/web/Cargo.toml index d5a987b0..473bde17 100644 --- a/web/Cargo.toml +++ b/web/Cargo.toml @@ -17,8 +17,7 @@ maintenance = { status = "actively-developed" } [dependencies] iced_core = { version = "0.1.0-alpha", path = "../core" } dodrio = "0.1.0" -futures-preview = "=0.3.0-alpha.18" -wasm-bindgen = "0.2.50" +wasm-bindgen = "0.2.51" [dependencies.web-sys] version = "0.3.27" diff --git a/web/src/lib.rs b/web/src/lib.rs index 04848d07..559a5af0 100644 --- a/web/src/lib.rs +++ b/web/src/lib.rs @@ -1,5 +1,4 @@ use dodrio::bumpalo; -use futures::Future; use std::cell::RefCell; mod bus; @@ -8,16 +7,13 @@ pub mod widget; pub use bus::Bus; pub use element::Element; -pub use iced_core::{Align, Color, Justify, Length}; +pub use iced_core::{Align, Background, Color, Justify, Length}; pub use widget::*; pub trait Application { type Message; - fn update( - &mut self, - message: Self::Message, - ) -> Option<Box<dyn Future<Output = Self::Message>>>; + fn update(&mut self, message: Self::Message); fn view(&mut self) -> Element<Self::Message>; @@ -48,10 +44,7 @@ impl<Message> Instance<Message> { } fn update(&mut self, message: Message) { - let mut ui = self.ui.borrow_mut(); - - // TODO: Resolve futures and publish resulting messages - let _ = ui.update(message); + self.ui.borrow_mut().update(message); } } diff --git a/web/src/widget/image.rs b/web/src/widget/image.rs index fd4ff0df..bd3e5daf 100644 --- a/web/src/widget/image.rs +++ b/web/src/widget/image.rs @@ -2,9 +2,9 @@ use crate::{Bus, Element, Length, Widget}; use dodrio::bumpalo; -pub type Image<'a> = iced_core::Image<&'a str>; +pub use iced_core::Image; -impl<'a, Message> Widget<Message> for Image<'a> { +impl<Message> Widget<Message> for Image { fn node<'b>( &self, bump: &'b bumpalo::Bump, @@ -12,7 +12,7 @@ impl<'a, Message> Widget<Message> for Image<'a> { ) -> dodrio::Node<'b> { use dodrio::builder::*; - let src = bumpalo::format!(in bump, "{}", self.handle); + let src = bumpalo::format!(in bump, "{}", self.path); let mut image = img(bump).attr("src", src.into_bump_str()); @@ -35,8 +35,8 @@ impl<'a, Message> Widget<Message> for Image<'a> { } } -impl<'a, Message> From<Image<'a>> for Element<'a, Message> { - fn from(image: Image<'a>) -> Element<'a, Message> { +impl<'a, Message> From<Image> for Element<'a, Message> { + fn from(image: Image) -> Element<'a, Message> { Element::new(image) } } diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 781abec2..cac5e113 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -12,4 +12,5 @@ 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 index 46849aab..01dc4c20 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -1,8 +1,10 @@ +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; diff --git a/wgpu/src/primitive.rs b/wgpu/src/primitive.rs index b664689b..0b9e2c41 100644 --- a/wgpu/src/primitive.rs +++ b/wgpu/src/primitive.rs @@ -19,4 +19,8 @@ pub enum Primitive { background: Background, border_radius: u16, }, + Image { + path: String, + bounds: Rectangle, + }, } diff --git a/wgpu/src/renderer.rs b/wgpu/src/renderer.rs index 8930e9df..ab6f744f 100644 --- a/wgpu/src/renderer.rs +++ b/wgpu/src/renderer.rs @@ -1,4 +1,4 @@ -use crate::{quad, Primitive, Quad, Transformation}; +use crate::{quad, Image, Primitive, Quad, Transformation}; use iced_native::{ renderer::Debugger, renderer::Windowed, Background, Color, Layout, MouseCursor, Point, Widget, @@ -29,8 +29,10 @@ pub struct Renderer { 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, ()>>>, } @@ -67,6 +69,7 @@ impl Renderer { .build(&mut device, TextureFormat::Bgra8UnormSrgb); let quad_pipeline = quad::Pipeline::new(&mut device); + let image_pipeline = crate::image::Pipeline::new(&mut device); Self { surface, @@ -74,8 +77,10 @@ impl Renderer { device, queue, quad_pipeline, + image_pipeline, quads: Vec::new(), + images: Vec::new(), glyph_brush: Rc::new(RefCell::new(glyph_brush)), } } @@ -139,6 +144,16 @@ impl Renderer { 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( @@ -238,6 +253,13 @@ impl Renderer { 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], + }); + } } } } diff --git a/wgpu/src/renderer/image.rs b/wgpu/src/renderer/image.rs index a29a3d49..0e312706 100644 --- a/wgpu/src/renderer/image.rs +++ b/wgpu/src/renderer/image.rs @@ -1,16 +1,34 @@ use crate::{Primitive, Renderer}; -use iced_native::{image, Image, Layout, MouseCursor, Node, Style}; +use iced_native::{image, Image, Layout, Length, MouseCursor, Node, Style}; -impl image::Renderer<&str> for Renderer { - fn node(&self, _image: &Image<&str>) -> Node { - Node::new(Style::default()) +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<&str>, - _layout: Layout<'_>, - ) -> Self::Output { - (Primitive::None, MouseCursor::OutOfBounds) + 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/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 Binary files differnew file mode 100644 index 00000000..ebee82ac --- /dev/null +++ b/wgpu/src/shader/image.frag.spv 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 Binary files differnew file mode 100644 index 00000000..9ba702bc --- /dev/null +++ b/wgpu/src/shader/image.vert.spv diff --git a/winit/src/application.rs b/winit/src/application.rs index 8345a5ed..418ee3c4 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -26,6 +26,10 @@ pub trait Application { // TODO: Ask for window settings and configure this properly let window = WindowBuilder::new() + .with_inner_size(winit::dpi::LogicalSize { + width: 1280.0, + height: 1024.0, + }) .build(&event_loop) .expect("Open window"); |