summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Cargo.toml3
-rw-r--r--README.md3
-rw-r--r--core/src/widget/image.rs28
-rw-r--r--core/src/widget/text.rs2
-rw-r--r--examples/README.md14
-rw-r--r--examples/tour.html5
-rw-r--r--examples/tour.rs38
-rw-r--r--native/Cargo.toml3
-rw-r--r--native/src/widget/image.rs18
-rw-r--r--src/lib.rs36
-rw-r--r--src/web.rs1
-rw-r--r--src/winit.rs11
-rw-r--r--web/Cargo.toml3
-rw-r--r--web/src/lib.rs13
-rw-r--r--web/src/widget/image.rs10
-rw-r--r--wgpu/Cargo.toml1
-rw-r--r--wgpu/src/image.rs438
-rw-r--r--wgpu/src/lib.rs2
-rw-r--r--wgpu/src/primitive.rs4
-rw-r--r--wgpu/src/renderer.rs24
-rw-r--r--wgpu/src/renderer/image.rs38
-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--winit/src/application.rs4
26 files changed, 650 insertions, 85 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 52fc483f..e8b53066 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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"
diff --git a/README.md b/README.md
index 6ffb6777..0aac9bc8 100644
--- a/README.md
+++ b/README.md
@@ -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)
}
}
diff --git a/src/lib.rs b/src/lib.rs
index 46574285..1bcdada2 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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
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/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");