Merge pull request #1697 from iced-rs/text-glyphon

Text shaping, font fallback, and `iced_wgpu` overhaul
This commit is contained in:
Héctor Ramón 2023-02-24 20:52:10 +01:00 committed by GitHub
commit 368cadd25a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
94 changed files with 1647 additions and 5395 deletions

View file

@ -38,4 +38,4 @@ jobs:
- name: Check compilation of `todos` example
run: cargo build --package todos --target wasm32-unknown-unknown
- name: Check compilation of `integration_wgpu` example
run: cargo build --package integration_wgpu --target wasm32-unknown-unknown
run: cargo build --package integration --target wasm32-unknown-unknown

View file

@ -12,21 +12,14 @@ keywords = ["gui", "ui", "graphics", "interface", "widgets"]
categories = ["gui"]
[features]
default = ["wgpu"]
# Enables the `Image` widget
image = ["iced_wgpu?/image", "iced_glow?/image", "image_rs"]
image = ["iced_wgpu/image", "image_rs"]
# Enables the `Svg` widget
svg = ["iced_wgpu?/svg", "iced_glow?/svg"]
svg = ["iced_wgpu/svg"]
# Enables the `Canvas` widget
canvas = ["iced_graphics/canvas"]
# Enables the `QRCode` widget
qr_code = ["iced_graphics/qr_code"]
# Enables the `iced_wgpu` renderer
wgpu = ["iced_wgpu"]
# Enables using system fonts
default_system_font = ["iced_wgpu?/default_system_font", "iced_glow?/default_system_font"]
# Enables the `iced_glow` renderer. Overrides `iced_wgpu`
glow = ["iced_glow", "iced_glutin"]
# Enables a debug view in native platforms (press F12)
debug = ["iced_winit/debug"]
# Enables `tokio` as the `executor::Default` on native platforms
@ -42,9 +35,7 @@ system = ["iced_winit/system"]
# Enables chrome traces
chrome-trace = [
"iced_winit/chrome-trace",
"iced_glutin?/trace",
"iced_wgpu?/tracing",
"iced_glow?/tracing",
"iced_wgpu/tracing",
]
[badges]
@ -55,8 +46,6 @@ members = [
"core",
"futures",
"graphics",
"glow",
"glutin",
"lazy",
"native",
"style",
@ -71,8 +60,6 @@ iced_futures = { version = "0.6", path = "futures" }
iced_native = { version = "0.9", path = "native" }
iced_graphics = { version = "0.7", path = "graphics" }
iced_winit = { version = "0.8", path = "winit", features = ["application"] }
iced_glutin = { version = "0.7", path = "glutin", optional = true }
iced_glow = { version = "0.7", path = "glow", optional = true }
thiserror = "1.0"
[dependencies.image_rs]
@ -81,10 +68,10 @@ package = "image"
optional = true
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
iced_wgpu = { version = "0.9", path = "wgpu", optional = true }
iced_wgpu = { version = "0.9", path = "wgpu" }
[target.'cfg(target_arch = "wasm32")'.dependencies]
iced_wgpu = { version = "0.9", path = "wgpu", features = ["webgl"], optional = true }
iced_wgpu = { version = "0.9", path = "wgpu", features = ["webgl"] }
[package.metadata.docs.rs]
rustdoc-args = ["--cfg", "docsrs"]

View file

@ -1,24 +1,45 @@
//! Load and use fonts.
use std::hash::Hash;
/// A font.
#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Font {
/// The default font.
///
/// This is normally a font configured in a renderer or loaded from the
/// system.
Default,
/// The name of a font family of choice.
Name(&'static str),
/// An external font.
External {
/// The name of the external font
name: &'static str,
/// Serif fonts represent the formal text style for a script.
Serif,
/// The bytes of the external font
bytes: &'static [u8],
},
/// Glyphs in sans-serif fonts, as the term is used in CSS, are generally low
/// contrast and have stroke endings that are plain — without any flaring,
/// cross stroke, or other ornamentation.
SansSerif,
/// Glyphs in cursive fonts generally use a more informal script style, and
/// the result looks more like handwritten pen or brush writing than printed
/// letterwork.
Cursive,
/// Fantasy fonts are primarily decorative or expressive fonts that contain
/// decorative or expressive representations of characters.
Fantasy,
/// The sole criterion of a monospace font is that all glyphs have the same
/// fixed width.
Monospace,
}
impl Default for Font {
fn default() -> Font {
Font::Default
}
/// The weight of some text.
#[allow(missing_docs)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Weight {
Thin,
ExtraLight,
Light,
Normal,
Medium,
Semibold,
Bold,
ExtraBold,
Black,
}

View file

@ -25,6 +25,7 @@
#![forbid(unsafe_code, rust_2018_idioms)]
#![allow(clippy::inherent_to_string, clippy::type_complexity)]
pub mod alignment;
pub mod font;
pub mod keyboard;
pub mod mouse;
pub mod time;
@ -32,7 +33,6 @@ pub mod time;
mod background;
mod color;
mod content_fit;
mod font;
mod length;
mod padding;
mod pixels;

Binary file not shown.

View file

@ -1,10 +1,9 @@
use iced::executor;
use iced::font::{self, Font};
use iced::widget::{checkbox, column, container};
use iced::{Element, Font, Length, Sandbox, Settings};
use iced::{Application, Command, Element, Length, Settings, Theme};
const ICON_FONT: Font = Font::External {
name: "Icons",
bytes: include_bytes!("../fonts/icons.ttf"),
};
const ICON_FONT: Font = Font::Name("icons");
pub fn main() -> iced::Result {
Example::run(Settings::default())
@ -20,24 +19,35 @@ struct Example {
enum Message {
DefaultChecked(bool),
CustomChecked(bool),
FontLoaded(Result<(), font::Error>),
}
impl Sandbox for Example {
impl Application for Example {
type Message = Message;
type Flags = ();
type Executor = executor::Default;
type Theme = Theme;
fn new() -> Self {
Default::default()
fn new(_flags: Self::Flags) -> (Self, Command<Message>) {
(
Self::default(),
font::load(include_bytes!("../fonts/icons.ttf").as_ref())
.map(Message::FontLoaded),
)
}
fn title(&self) -> String {
String::from("Checkbox - Iced")
}
fn update(&mut self, message: Message) {
fn update(&mut self, message: Message) -> Command<Message> {
match message {
Message::DefaultChecked(value) => self.default_checkbox = value,
Message::CustomChecked(value) => self.custom_checkbox = value,
Message::FontLoaded(_) => (),
}
Command::none()
}
fn view(&self) -> Element<Message> {

View file

@ -1,5 +1,5 @@
[package]
name = "integration_wgpu"
name = "integration"
version = "0.1.0"
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
edition = "2021"

View file

@ -8,8 +8,8 @@
<h1>integration_wgpu</h1>
<canvas id="iced_canvas"></canvas>
<script type="module">
import init from "./integration_wgpu.js";
init('./integration_wgpu_bg.wasm');
import init from "./integration.js";
init('./integration_bg.wasm');
</script>
<style>
body {

View file

@ -125,17 +125,18 @@ pub fn main() {
let mut resized = false;
// Initialize staging belt
let mut staging_belt = wgpu::util::StagingBelt::new(5 * 1024);
// Initialize scene and GUI controls
let scene = Scene::new(&device, format);
let controls = Controls::new();
// Initialize iced
let mut debug = Debug::new();
let mut renderer =
Renderer::new(Backend::new(&device, Settings::default(), format));
let mut renderer = Renderer::new(Backend::new(
&device,
&queue,
Settings::default(),
format,
));
let mut state = program::State::new(
controls,
@ -247,8 +248,9 @@ pub fn main() {
renderer.with_primitives(|backend, primitive| {
backend.present(
&device,
&mut staging_belt,
&queue,
&mut encoder,
None,
&view,
primitive,
&viewport,
@ -257,7 +259,6 @@ pub fn main() {
});
// Then we submit the work
staging_belt.finish();
queue.submit(Some(encoder.finish()));
frame.present();
@ -267,10 +268,6 @@ pub fn main() {
state.mouse_interaction(),
),
);
// And recall staging buffers
staging_belt.recall();
}
Err(error) => match error {
wgpu::SurfaceError::OutOfMemory => {

View file

@ -1,12 +0,0 @@
[package]
name = "integration_opengl"
version = "0.1.0"
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
edition = "2021"
publish = false
[dependencies]
iced_glutin = { path = "../../glutin" }
iced_glow = { path = "../../glow" }
iced_winit = { path = "../../winit" }
env_logger = "0.8"

View file

@ -1,16 +0,0 @@
## OpenGL integration
A demonstration of how to integrate Iced in an existing graphical OpenGL application.
The __[`main`]__ file contains all the code of the example.
<div align="center">
<a href="https://imgbox.com/9P9ETcod" target="_blank"><img src="https://images2.imgbox.com/2a/51/9P9ETcod_o.gif" alt="image host"/></a>
</div>
You can run it with `cargo run`:
```
cargo run --package integration_opengl
```
[`main`]: src/main.rs

View file

@ -1,101 +0,0 @@
use iced_glow::Renderer;
use iced_glutin::widget::Slider;
use iced_glutin::widget::{Column, Row, Text};
use iced_glutin::{Alignment, Color, Command, Element, Length, Program};
pub struct Controls {
background_color: Color,
}
#[derive(Debug, Clone)]
pub enum Message {
BackgroundColorChanged(Color),
}
impl Controls {
pub fn new() -> Controls {
Controls {
background_color: Color::BLACK,
}
}
pub fn background_color(&self) -> Color {
self.background_color
}
}
impl Program for Controls {
type Renderer = Renderer;
type Message = Message;
fn update(&mut self, message: Message) -> Command<Message> {
match message {
Message::BackgroundColorChanged(color) => {
self.background_color = color;
}
}
Command::none()
}
fn view(&self) -> Element<Message, Renderer> {
let background_color = self.background_color;
let sliders = Row::new()
.width(500)
.spacing(20)
.push(
Slider::new(0.0..=1.0, background_color.r, move |r| {
Message::BackgroundColorChanged(Color {
r,
..background_color
})
})
.step(0.01),
)
.push(
Slider::new(0.0..=1.0, background_color.g, move |g| {
Message::BackgroundColorChanged(Color {
g,
..background_color
})
})
.step(0.01),
)
.push(
Slider::new(0.0..=1.0, background_color.b, move |b| {
Message::BackgroundColorChanged(Color {
b,
..background_color
})
})
.step(0.01),
);
Row::new()
.width(Length::Fill)
.height(Length::Fill)
.align_items(Alignment::End)
.push(
Column::new()
.width(Length::Fill)
.align_items(Alignment::End)
.push(
Column::new()
.padding(10)
.spacing(10)
.push(
Text::new("Background color")
.style(Color::WHITE),
)
.push(sliders)
.push(
Text::new(format!("{background_color:?}"))
.size(14)
.style(Color::WHITE),
),
),
)
.into()
}
}

View file

@ -1,187 +0,0 @@
mod controls;
mod scene;
use controls::Controls;
use scene::Scene;
use glow::*;
use glutin::dpi::PhysicalPosition;
use glutin::event::{Event, ModifiersState, WindowEvent};
use glutin::event_loop::ControlFlow;
use iced_glow::glow;
use iced_glow::{Backend, Renderer, Settings, Viewport};
use iced_glutin::conversion;
use iced_glutin::glutin;
use iced_glutin::renderer;
use iced_glutin::{program, Clipboard, Color, Debug, Size};
pub fn main() {
env_logger::init();
let (gl, event_loop, windowed_context, shader_version) = {
let el = glutin::event_loop::EventLoop::new();
let wb = glutin::window::WindowBuilder::new()
.with_title("OpenGL integration example")
.with_inner_size(glutin::dpi::LogicalSize::new(1024.0, 768.0));
let windowed_context = glutin::ContextBuilder::new()
.with_vsync(true)
.build_windowed(wb, &el)
.unwrap();
unsafe {
let windowed_context = windowed_context.make_current().unwrap();
let gl = glow::Context::from_loader_function(|s| {
windowed_context.get_proc_address(s) as *const _
});
// Enable auto-conversion from/to sRGB
gl.enable(glow::FRAMEBUFFER_SRGB);
// Enable alpha blending
gl.enable(glow::BLEND);
gl.blend_func(glow::SRC_ALPHA, glow::ONE_MINUS_SRC_ALPHA);
// Disable multisampling by default
gl.disable(glow::MULTISAMPLE);
(gl, el, windowed_context, "#version 410")
}
};
let physical_size = windowed_context.window().inner_size();
let mut viewport = Viewport::with_physical_size(
Size::new(physical_size.width, physical_size.height),
windowed_context.window().scale_factor(),
);
let mut cursor_position = PhysicalPosition::new(-1.0, -1.0);
let mut modifiers = ModifiersState::default();
let mut clipboard = Clipboard::connect(windowed_context.window());
let mut renderer = Renderer::new(Backend::new(&gl, Settings::default()));
let mut debug = Debug::new();
let controls = Controls::new();
let mut state = program::State::new(
controls,
viewport.logical_size(),
&mut renderer,
&mut debug,
);
let mut resized = false;
let scene = Scene::new(&gl, shader_version);
event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;
match event {
Event::WindowEvent { event, .. } => {
match event {
WindowEvent::CursorMoved { position, .. } => {
cursor_position = position;
}
WindowEvent::ModifiersChanged(new_modifiers) => {
modifiers = new_modifiers;
}
WindowEvent::Resized(physical_size) => {
viewport = Viewport::with_physical_size(
Size::new(
physical_size.width,
physical_size.height,
),
windowed_context.window().scale_factor(),
);
resized = true;
}
WindowEvent::CloseRequested => {
scene.cleanup(&gl);
*control_flow = ControlFlow::Exit
}
_ => (),
}
// Map window event to iced event
if let Some(event) = iced_winit::conversion::window_event(
&event,
windowed_context.window().scale_factor(),
modifiers,
) {
state.queue_event(event);
}
}
Event::MainEventsCleared => {
// If there are events pending
if !state.is_queue_empty() {
// We update iced
let _ = state.update(
viewport.logical_size(),
conversion::cursor_position(
cursor_position,
viewport.scale_factor(),
),
&mut renderer,
&iced_glow::Theme::Dark,
&renderer::Style {
text_color: Color::WHITE,
},
&mut clipboard,
&mut debug,
);
// and request a redraw
windowed_context.window().request_redraw();
}
}
Event::RedrawRequested(_) => {
if resized {
let size = windowed_context.window().inner_size();
unsafe {
gl.viewport(
0,
0,
size.width as i32,
size.height as i32,
);
}
resized = false;
}
let program = state.program();
{
// We clear the frame
scene.clear(&gl, program.background_color());
// Draw the scene
scene.draw(&gl);
}
// And then iced on top
renderer.with_primitives(|backend, primitive| {
backend.present(
&gl,
primitive,
&viewport,
&debug.overlay(),
);
});
// Update the mouse cursor
windowed_context.window().set_cursor_icon(
iced_winit::conversion::mouse_interaction(
state.mouse_interaction(),
),
);
windowed_context.swap_buffers().unwrap();
}
_ => (),
}
});
}

View file

@ -1,102 +0,0 @@
use glow::*;
use iced_glow::glow;
use iced_glow::Color;
pub struct Scene {
program: glow::Program,
vertex_array: glow::VertexArray,
}
impl Scene {
pub fn new(gl: &glow::Context, shader_version: &str) -> Self {
unsafe {
let vertex_array = gl
.create_vertex_array()
.expect("Cannot create vertex array");
gl.bind_vertex_array(Some(vertex_array));
let program = gl.create_program().expect("Cannot create program");
let (vertex_shader_source, fragment_shader_source) = (
r#"const vec2 verts[3] = vec2[3](
vec2(0.5f, 1.0f),
vec2(0.0f, 0.0f),
vec2(1.0f, 0.0f)
);
out vec2 vert;
void main() {
vert = verts[gl_VertexID];
gl_Position = vec4(vert - 0.5, 0.0, 1.0);
}"#,
r#"precision highp float;
in vec2 vert;
out vec4 color;
void main() {
color = vec4(vert, 0.5, 1.0);
}"#,
);
let shader_sources = [
(glow::VERTEX_SHADER, vertex_shader_source),
(glow::FRAGMENT_SHADER, fragment_shader_source),
];
let mut shaders = Vec::with_capacity(shader_sources.len());
for (shader_type, shader_source) in shader_sources.iter() {
let shader = gl
.create_shader(*shader_type)
.expect("Cannot create shader");
gl.shader_source(
shader,
&format!("{shader_version}\n{shader_source}"),
);
gl.compile_shader(shader);
if !gl.get_shader_compile_status(shader) {
panic!("{}", gl.get_shader_info_log(shader));
}
gl.attach_shader(program, shader);
shaders.push(shader);
}
gl.link_program(program);
if !gl.get_program_link_status(program) {
panic!("{}", gl.get_program_info_log(program));
}
for shader in shaders {
gl.detach_shader(program, shader);
gl.delete_shader(shader);
}
gl.use_program(Some(program));
Self {
program,
vertex_array,
}
}
}
pub fn clear(&self, gl: &glow::Context, background_color: Color) {
let [r, g, b, a] = background_color.into_linear();
unsafe {
gl.clear_color(r, g, b, a);
gl.clear(glow::COLOR_BUFFER_BIT);
}
}
pub fn draw(&self, gl: &glow::Context) {
unsafe {
gl.bind_vertex_array(Some(self.vertex_array));
gl.use_program(Some(self.program));
gl.draw_arrays(glow::TRIANGLES, 0, 3);
}
}
pub fn cleanup(&self, gl: &glow::Context) {
unsafe {
gl.delete_program(self.program);
gl.delete_vertex_array(self.vertex_array);
}
}
}

Binary file not shown.

View file

@ -1,5 +1,6 @@
use iced::alignment::{self, Alignment};
use iced::event::{self, Event};
use iced::font::{self, Font};
use iced::keyboard;
use iced::subscription;
use iced::theme::{self, Theme};
@ -9,7 +10,7 @@ use iced::widget::{
};
use iced::window;
use iced::{Application, Element};
use iced::{Color, Command, Font, Length, Settings, Subscription};
use iced::{Color, Command, Length, Settings, Subscription};
use once_cell::sync::Lazy;
use serde::{Deserialize, Serialize};
@ -44,6 +45,7 @@ struct State {
#[derive(Debug, Clone)]
enum Message {
Loaded(Result<SavedState, LoadError>),
FontLoaded(Result<(), font::Error>),
Saved(Result<(), SaveError>),
InputChanged(String),
CreateTask,
@ -61,7 +63,11 @@ impl Application for Todos {
fn new(_flags: ()) -> (Todos, Command<Message>) {
(
Todos::Loading,
Command::perform(SavedState::load(), Message::Loaded),
Command::batch(vec![
font::load(include_bytes!("../fonts/icons.ttf").as_slice())
.map(Message::FontLoaded),
Command::perform(SavedState::load(), Message::Loaded),
]),
)
}
@ -384,7 +390,7 @@ fn view_controls(tasks: &[Task], current_filter: Filter) -> Element<Message> {
let tasks_left = tasks.iter().filter(|task| !task.completed).count();
let filter_button = |label, filter, current_filter| {
let label = text(label).size(16);
let label = text(label);
let button = button(label).style(if filter == current_filter {
theme::Button::Primary
@ -401,8 +407,7 @@ fn view_controls(tasks: &[Task], current_filter: Filter) -> Element<Message> {
tasks_left,
if tasks_left == 1 { "task" } else { "tasks" }
))
.width(Length::Fill)
.size(16),
.width(Length::Fill),
row![
filter_button("All", Filter::All, current_filter),
filter_button("Active", Filter::Active, current_filter),
@ -466,10 +471,7 @@ fn empty_message(message: &str) -> Element<'_, Message> {
}
// Fonts
const ICONS: Font = Font::External {
name: "Icons",
bytes: include_bytes!("../../todos/fonts/icons.ttf"),
};
const ICONS: Font = Font::Name("Iced-Todos-Icons");
fn icon(unicode: char) -> Text<'static> {
text(unicode.to_string())

View file

@ -1,51 +0,0 @@
[package]
name = "iced_glow"
version = "0.7.0"
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
edition = "2021"
description = "A glow renderer for iced"
license = "MIT AND OFL-1.1"
repository = "https://github.com/iced-rs/iced"
[features]
svg = ["iced_graphics/svg"]
image = ["iced_graphics/image"]
png = ["iced_graphics/png"]
jpeg = ["iced_graphics/jpeg"]
jpeg_rayon = ["iced_graphics/jpeg_rayon"]
gif = ["iced_graphics/gif"]
webp = ["iced_graphics/webp"]
pnm = ["iced_graphics/pnm"]
ico = ["iced_graphics/ico"]
bmp = ["iced_graphics/bmp"]
hdr = ["iced_graphics/hdr"]
dds = ["iced_graphics/dds"]
farbfeld = ["iced_graphics/farbfeld"]
canvas = ["iced_graphics/canvas"]
qr_code = ["iced_graphics/qr_code"]
default_system_font = ["iced_graphics/font-source"]
[dependencies]
glow = "0.11.1"
glow_glyph = "0.5.0"
glyph_brush = "0.7"
euclid = "0.22"
bytemuck = "1.4"
log = "0.4"
[dependencies.iced_native]
version = "0.9"
path = "../native"
[dependencies.iced_graphics]
version = "0.7"
path = "../graphics"
features = ["font-fallback", "font-icons", "opengl"]
[dependencies.tracing]
version = "0.1.6"
optional = true
[package.metadata.docs.rs]
rustdoc-args = ["--cfg", "docsrs"]
all-features = true

View file

@ -1,51 +0,0 @@
# `iced_glow`
[![Documentation](https://docs.rs/iced_glow/badge.svg)][documentation]
[![Crates.io](https://img.shields.io/crates/v/iced_glow.svg)](https://crates.io/crates/iced_glow)
[![License](https://img.shields.io/crates/l/iced_glow.svg)](https://github.com/iced-rs/iced/blob/master/LICENSE)
[![Discord Server](https://img.shields.io/discord/628993209984614400?label=&labelColor=6A7EC2&logo=discord&logoColor=ffffff&color=7389D8)](https://discord.gg/3xZJ65GAhd)
`iced_glow` is a [`glow`] renderer for [`iced_native`]. This renderer supports OpenGL 3.0+ and OpenGL ES 2.0.
This renderer is mostly used as a fallback for hardware that doesn't support [`wgpu`] (Vulkan, Metal or DX12).
Currently, `iced_glow` supports the following primitives:
- Text, which is rendered using [`glow_glyph`]. No shaping at all.
- Quads or rectangles, with rounded borders and a solid background color.
- Clip areas, useful to implement scrollables or hide overflowing content.
- Meshes of triangles, useful to draw geometry freely.
<p align="center">
<img alt="The native target" src="../docs/graphs/native.png" width="80%">
</p>
[documentation]: https://docs.rs/iced_glow
[`iced_native`]: ../native
[`glow`]: https://github.com/grovesNL/glow
[`wgpu`]: https://github.com/gfx-rs/wgpu
[`glow_glyph`]: https://github.com/hecrj/glow_glyph
## Installation
Add `iced_glow` as a dependency in your `Cargo.toml`:
```toml
iced_glow = "0.7"
```
__Iced moves fast and the `master` branch can contain breaking changes!__ If
you want to learn about a specific release, check out [the release list].
[the release list]: https://github.com/iced-rs/iced/releases
## Current limitations
The current implementation is quite naive, it uses:
- A different pipeline/shader for each primitive
- A very simplistic layer model: every `Clip` primitive will generate new layers
- _Many_ render passes instead of preparing everything upfront
- A glyph cache that is trimmed incorrectly when there are multiple layers (a [`glyph_brush`] limitation)
Some of these issues are already being worked on! If you want to help, [get in touch!]
[get in touch!]: ../CONTRIBUTING.md
[`glyph_brush`]: https://github.com/alexheretic/glyph-brush

View file

@ -1,280 +0,0 @@
#[cfg(any(feature = "image", feature = "svg"))]
use crate::image;
use crate::quad;
use crate::text;
use crate::{program, triangle};
use crate::{Settings, Transformation, Viewport};
use iced_graphics::backend;
use iced_graphics::font;
use iced_graphics::{Layer, Primitive};
use iced_native::alignment;
use iced_native::{Font, Size};
/// A [`glow`] graphics backend for [`iced`].
///
/// [`glow`]: https://github.com/grovesNL/glow
/// [`iced`]: https://github.com/iced-rs/iced
#[derive(Debug)]
pub struct Backend {
#[cfg(any(feature = "image", feature = "svg"))]
image_pipeline: image::Pipeline,
quad_pipeline: quad::Pipeline,
text_pipeline: text::Pipeline,
triangle_pipeline: triangle::Pipeline,
default_text_size: f32,
}
impl Backend {
/// Creates a new [`Backend`].
pub fn new(gl: &glow::Context, settings: Settings) -> Self {
let text_pipeline = text::Pipeline::new(
gl,
settings.default_font,
settings.text_multithreading,
);
let shader_version = program::Version::new(gl);
#[cfg(any(feature = "image", feature = "svg"))]
let image_pipeline = image::Pipeline::new(gl, &shader_version);
let quad_pipeline = quad::Pipeline::new(gl, &shader_version);
let triangle_pipeline = triangle::Pipeline::new(gl, &shader_version);
Self {
#[cfg(any(feature = "image", feature = "svg"))]
image_pipeline,
quad_pipeline,
text_pipeline,
triangle_pipeline,
default_text_size: settings.default_text_size,
}
}
/// Draws the provided primitives in the default framebuffer.
///
/// The text provided as overlay will be rendered on top of the primitives.
/// This is useful for rendering debug information.
pub fn present<T: AsRef<str>>(
&mut self,
gl: &glow::Context,
primitives: &[Primitive],
viewport: &Viewport,
overlay_text: &[T],
) {
let viewport_size = viewport.physical_size();
let scale_factor = viewport.scale_factor() as f32;
let projection = viewport.projection();
let mut layers = Layer::generate(primitives, viewport);
layers.push(Layer::overlay(overlay_text, viewport));
for layer in layers {
self.flush(
gl,
scale_factor,
projection,
&layer,
viewport_size.height,
);
}
#[cfg(any(feature = "image", feature = "svg"))]
self.image_pipeline.trim_cache(gl);
}
fn flush(
&mut self,
gl: &glow::Context,
scale_factor: f32,
transformation: Transformation,
layer: &Layer<'_>,
target_height: u32,
) {
let mut bounds = (layer.bounds * scale_factor).snap();
if bounds.width < 1 || bounds.height < 1 {
return;
}
bounds.height = bounds.height.min(target_height);
if !layer.quads.is_empty() {
self.quad_pipeline.draw(
gl,
target_height,
&layer.quads,
transformation,
scale_factor,
bounds,
);
}
if !layer.meshes.is_empty() {
let scaled = transformation
* Transformation::scale(scale_factor, scale_factor);
self.triangle_pipeline.draw(
&layer.meshes,
gl,
target_height,
scaled,
scale_factor,
);
}
#[cfg(any(feature = "image", feature = "svg"))]
if !layer.images.is_empty() {
let scaled = transformation
* Transformation::scale(scale_factor, scale_factor);
self.image_pipeline.draw(
gl,
target_height,
scaled,
scale_factor,
&layer.images,
bounds,
);
}
if !layer.text.is_empty() {
for text in layer.text.iter() {
// Target physical coordinates directly to avoid blurry text
let text = glow_glyph::Section {
// TODO: We `round` here to avoid rerasterizing text when
// its position changes slightly. This can make text feel a
// bit "jumpy". We may be able to do better once we improve
// our text rendering/caching pipeline.
screen_position: (
(text.bounds.x * scale_factor).round(),
(text.bounds.y * scale_factor).round(),
),
// TODO: Fix precision issues with some scale factors.
//
// The `ceil` here can cause some words to render on the
// same line when they should not.
//
// Ideally, `wgpu_glyph` should be able to compute layout
// using logical positions, and then apply the proper
// scaling when rendering. This would ensure that both
// measuring and rendering follow the same layout rules.
bounds: (
(text.bounds.width * scale_factor).ceil(),
(text.bounds.height * scale_factor).ceil(),
),
text: vec![glow_glyph::Text {
text: text.content,
scale: glow_glyph::ab_glyph::PxScale {
x: text.size * scale_factor,
y: text.size * scale_factor,
},
font_id: self.text_pipeline.find_font(text.font),
extra: glow_glyph::Extra {
color: text.color,
z: 0.0,
},
}],
layout: glow_glyph::Layout::default()
.h_align(match text.horizontal_alignment {
alignment::Horizontal::Left => {
glow_glyph::HorizontalAlign::Left
}
alignment::Horizontal::Center => {
glow_glyph::HorizontalAlign::Center
}
alignment::Horizontal::Right => {
glow_glyph::HorizontalAlign::Right
}
})
.v_align(match text.vertical_alignment {
alignment::Vertical::Top => {
glow_glyph::VerticalAlign::Top
}
alignment::Vertical::Center => {
glow_glyph::VerticalAlign::Center
}
alignment::Vertical::Bottom => {
glow_glyph::VerticalAlign::Bottom
}
}),
};
self.text_pipeline.queue(text);
}
self.text_pipeline.draw_queued(
gl,
transformation,
glow_glyph::Region {
x: bounds.x,
y: target_height - (bounds.y + bounds.height),
width: bounds.width,
height: bounds.height,
},
);
}
}
}
impl iced_graphics::Backend for Backend {
fn trim_measurements(&mut self) {
self.text_pipeline.trim_measurement_cache()
}
}
impl backend::Text for Backend {
const ICON_FONT: Font = font::ICONS;
const CHECKMARK_ICON: char = font::CHECKMARK_ICON;
const ARROW_DOWN_ICON: char = font::ARROW_DOWN_ICON;
fn default_size(&self) -> f32 {
self.default_text_size
}
fn measure(
&self,
contents: &str,
size: f32,
font: Font,
bounds: Size,
) -> (f32, f32) {
self.text_pipeline.measure(contents, size, font, bounds)
}
fn hit_test(
&self,
contents: &str,
size: f32,
font: Font,
bounds: Size,
point: iced_native::Point,
nearest_only: bool,
) -> Option<text::Hit> {
self.text_pipeline.hit_test(
contents,
size,
font,
bounds,
point,
nearest_only,
)
}
}
#[cfg(feature = "image")]
impl backend::Image for Backend {
fn dimensions(&self, handle: &iced_native::image::Handle) -> Size<u32> {
self.image_pipeline.dimensions(handle)
}
}
#[cfg(feature = "svg")]
impl backend::Svg for Backend {
fn viewport_dimensions(
&self,
handle: &iced_native::svg::Handle,
) -> Size<u32> {
self.image_pipeline.viewport_dimensions(handle)
}
}

View file

@ -1,254 +0,0 @@
mod storage;
use storage::Storage;
pub use iced_graphics::triangle::{Mesh2D, Vertex2D};
use crate::program::{self, Shader};
use crate::Transformation;
#[cfg(feature = "image")]
use iced_graphics::image::raster;
#[cfg(feature = "svg")]
use iced_graphics::image::vector;
use iced_graphics::layer;
use iced_graphics::Rectangle;
use iced_graphics::Size;
use glow::HasContext;
use std::cell::RefCell;
#[cfg(feature = "tracing")]
use tracing::info_span;
#[derive(Debug)]
pub(crate) struct Pipeline {
program: <glow::Context as HasContext>::Program,
vertex_array: <glow::Context as HasContext>::VertexArray,
vertex_buffer: <glow::Context as HasContext>::Buffer,
transform_location: <glow::Context as HasContext>::UniformLocation,
storage: Storage,
#[cfg(feature = "image")]
raster_cache: RefCell<raster::Cache<Storage>>,
#[cfg(feature = "svg")]
vector_cache: RefCell<vector::Cache<Storage>>,
}
impl Pipeline {
pub fn new(
gl: &glow::Context,
shader_version: &program::Version,
) -> Pipeline {
let program = unsafe {
let vertex_shader = Shader::vertex(
gl,
shader_version,
include_str!("shader/common/image.vert"),
);
let fragment_shader = Shader::fragment(
gl,
shader_version,
include_str!("shader/common/image.frag"),
);
program::create(
gl,
&[vertex_shader, fragment_shader],
&[(0, "i_Position")],
)
};
let transform_location =
unsafe { gl.get_uniform_location(program, "u_Transform") }
.expect("Get transform location");
unsafe {
gl.use_program(Some(program));
let transform: [f32; 16] = Transformation::identity().into();
gl.uniform_matrix_4_f32_slice(
Some(&transform_location),
false,
&transform,
);
gl.use_program(None);
}
let vertex_buffer =
unsafe { gl.create_buffer().expect("Create vertex buffer") };
let vertex_array =
unsafe { gl.create_vertex_array().expect("Create vertex array") };
unsafe {
gl.bind_vertex_array(Some(vertex_array));
gl.bind_buffer(glow::ARRAY_BUFFER, Some(vertex_buffer));
let vertices = &[0u8, 0, 1, 0, 0, 1, 1, 1];
gl.buffer_data_size(
glow::ARRAY_BUFFER,
vertices.len() as i32,
glow::STATIC_DRAW,
);
gl.buffer_sub_data_u8_slice(
glow::ARRAY_BUFFER,
0,
bytemuck::cast_slice(vertices),
);
gl.enable_vertex_attrib_array(0);
gl.vertex_attrib_pointer_f32(
0,
2,
glow::UNSIGNED_BYTE,
false,
0,
0,
);
gl.bind_buffer(glow::ARRAY_BUFFER, None);
gl.bind_vertex_array(None);
}
Pipeline {
program,
vertex_array,
vertex_buffer,
transform_location,
storage: Storage::default(),
#[cfg(feature = "image")]
raster_cache: RefCell::new(raster::Cache::default()),
#[cfg(feature = "svg")]
vector_cache: RefCell::new(vector::Cache::default()),
}
}
#[cfg(feature = "image")]
pub fn dimensions(&self, handle: &iced_native::image::Handle) -> Size<u32> {
self.raster_cache.borrow_mut().load(handle).dimensions()
}
#[cfg(feature = "svg")]
pub fn viewport_dimensions(
&self,
handle: &iced_native::svg::Handle,
) -> Size<u32> {
let mut cache = self.vector_cache.borrow_mut();
let svg = cache.load(handle);
svg.viewport_dimensions()
}
pub fn draw(
&mut self,
mut gl: &glow::Context,
target_height: u32,
transformation: Transformation,
_scale_factor: f32,
images: &[layer::Image],
layer_bounds: Rectangle<u32>,
) {
#[cfg(feature = "tracing")]
let _ = info_span!("Glow::Image", "DRAW").entered();
unsafe {
gl.use_program(Some(self.program));
gl.bind_vertex_array(Some(self.vertex_array));
gl.bind_buffer(glow::ARRAY_BUFFER, Some(self.vertex_buffer));
gl.enable(glow::SCISSOR_TEST);
}
#[cfg(feature = "image")]
let mut raster_cache = self.raster_cache.borrow_mut();
#[cfg(feature = "svg")]
let mut vector_cache = self.vector_cache.borrow_mut();
for image in images {
let (entry, bounds) = match &image {
#[cfg(feature = "image")]
layer::Image::Raster { handle, bounds } => (
raster_cache.upload(handle, &mut gl, &mut self.storage),
bounds,
),
#[cfg(not(feature = "image"))]
layer::Image::Raster { handle: _, bounds } => (None, bounds),
#[cfg(feature = "svg")]
layer::Image::Vector {
handle,
color,
bounds,
} => {
let size = [bounds.width, bounds.height];
(
vector_cache.upload(
handle,
*color,
size,
_scale_factor,
&mut gl,
&mut self.storage,
),
bounds,
)
}
#[cfg(not(feature = "svg"))]
layer::Image::Vector { bounds, .. } => (None, bounds),
};
unsafe {
gl.scissor(
layer_bounds.x as i32,
(target_height - (layer_bounds.y + layer_bounds.height))
as i32,
layer_bounds.width as i32,
layer_bounds.height as i32,
);
if let Some(storage::Entry { texture, .. }) = entry {
gl.bind_texture(glow::TEXTURE_2D, Some(*texture))
} else {
continue;
}
let translate = Transformation::translate(bounds.x, bounds.y);
let scale = Transformation::scale(bounds.width, bounds.height);
let transformation = transformation * translate * scale;
let matrix: [f32; 16] = transformation.into();
gl.uniform_matrix_4_f32_slice(
Some(&self.transform_location),
false,
&matrix,
);
gl.draw_arrays(glow::TRIANGLE_STRIP, 0, 4);
gl.bind_texture(glow::TEXTURE_2D, None);
}
}
unsafe {
gl.bind_buffer(glow::ARRAY_BUFFER, None);
gl.bind_vertex_array(None);
gl.use_program(None);
gl.disable(glow::SCISSOR_TEST);
}
}
pub fn trim_cache(&mut self, mut gl: &glow::Context) {
#[cfg(feature = "image")]
self.raster_cache
.borrow_mut()
.trim(&mut self.storage, &mut gl);
#[cfg(feature = "svg")]
self.vector_cache
.borrow_mut()
.trim(&mut self.storage, &mut gl);
}
}

View file

@ -1,78 +0,0 @@
use iced_graphics::image;
use iced_graphics::Size;
use glow::HasContext;
#[derive(Debug, Default)]
pub struct Storage;
impl image::Storage for Storage {
type Entry = Entry;
type State<'a> = &'a glow::Context;
fn upload(
&mut self,
width: u32,
height: u32,
data: &[u8],
gl: &mut &glow::Context,
) -> Option<Self::Entry> {
unsafe {
let texture = gl.create_texture().expect("create texture");
gl.bind_texture(glow::TEXTURE_2D, Some(texture));
gl.tex_image_2d(
glow::TEXTURE_2D,
0,
glow::SRGB8_ALPHA8 as i32,
width as i32,
height as i32,
0,
glow::RGBA,
glow::UNSIGNED_BYTE,
Some(data),
);
gl.tex_parameter_i32(
glow::TEXTURE_2D,
glow::TEXTURE_WRAP_S,
glow::CLAMP_TO_EDGE as _,
);
gl.tex_parameter_i32(
glow::TEXTURE_2D,
glow::TEXTURE_WRAP_T,
glow::CLAMP_TO_EDGE as _,
);
gl.tex_parameter_i32(
glow::TEXTURE_2D,
glow::TEXTURE_MIN_FILTER,
glow::LINEAR as _,
);
gl.tex_parameter_i32(
glow::TEXTURE_2D,
glow::TEXTURE_MAG_FILTER,
glow::LINEAR as _,
);
gl.bind_texture(glow::TEXTURE_2D, None);
Some(Entry {
size: Size::new(width, height),
texture,
})
}
}
fn remove(&mut self, entry: &Entry, gl: &mut &glow::Context) {
unsafe { gl.delete_texture(entry.texture) }
}
}
#[derive(Debug)]
pub struct Entry {
size: Size<u32>,
pub(super) texture: glow::NativeTexture,
}
impl image::storage::Entry for Entry {
fn size(&self) -> Size<u32> {
self.size
}
}

View file

@ -1,53 +0,0 @@
//! A [`glow`] renderer for [`iced_native`].
//!
//! ![The native path of the Iced ecosystem](https://github.com/iced-rs/iced/blob/0525d76ff94e828b7b21634fa94a747022001c83/docs/graphs/native.png?raw=true)
//!
//! [`glow`]: https://github.com/grovesNL/glow
//! [`iced_native`]: https://github.com/iced-rs/iced/tree/0.8/native
#![doc(
html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg"
)]
#![deny(
missing_debug_implementations,
missing_docs,
unused_results,
clippy::extra_unused_lifetimes,
clippy::from_over_into,
clippy::needless_borrow,
clippy::new_without_default,
clippy::useless_conversion
)]
#![forbid(rust_2018_idioms)]
#![allow(clippy::inherent_to_string, clippy::type_complexity)]
#![cfg_attr(docsrs, feature(doc_cfg))]
pub use glow;
mod backend;
#[cfg(any(feature = "image", feature = "svg"))]
mod image;
mod program;
mod quad;
mod text;
mod triangle;
pub mod settings;
pub mod window;
pub use backend::Backend;
pub use settings::Settings;
pub(crate) use iced_graphics::Transformation;
pub use iced_graphics::{Error, Viewport};
pub use iced_native::Theme;
pub use iced_native::alignment;
pub use iced_native::{Alignment, Background, Color, Command, Length, Vector};
/// A [`glow`] graphics renderer for [`iced`].
///
/// [`glow`]: https://github.com/grovesNL/glow
/// [`iced`]: https://github.com/iced-rs/iced
pub type Renderer<Theme = iced_native::Theme> =
iced_graphics::Renderer<Backend, Theme>;

View file

@ -1,133 +0,0 @@
use glow::HasContext;
/// The [`Version`] of a `Program`.
pub struct Version {
vertex: String,
fragment: String,
}
impl Version {
pub fn new(gl: &glow::Context) -> Version {
let version = gl.version();
let (vertex, fragment) = match (
version.major,
version.minor,
version.is_embedded,
) {
// OpenGL 3.0+
(3, 0 | 1 | 2, false) => (
format!("#version 1{}0\n#extension GL_ARB_explicit_attrib_location : enable", version.minor + 3),
format!(
"#version 1{}0\n#extension GL_ARB_explicit_attrib_location : enable\n#define HIGHER_THAN_300 1",
version.minor + 3
),
),
// OpenGL 3.3+
(3 | 4, _, false) => (
format!("#version {}{}0\n#extension GL_ARB_explicit_attrib_location : enable", version.major, version.minor),
format!(
"#version {}{}0\n#extension GL_ARB_explicit_attrib_location : enable\n#define HIGHER_THAN_300 1",
version.major, version.minor
),
),
// OpenGL ES 3.0+
(3, _, true) => (
format!("#version 3{}0 es", version.minor),
format!(
"#version 3{}0 es\n#define HIGHER_THAN_300 1",
version.minor
),
),
// OpenGL ES 2.0+
(2, _, true) => (
String::from(
"#version 100\n#define in attribute\n#define out varying",
),
String::from("#version 100\n#define in varying"),
),
// OpenGL 2.1
(2, _, false) => (
String::from(
"#version 120\n#define in attribute\n#define out varying",
),
String::from("#version 120\n#define in varying"),
),
// OpenGL 1.1+
_ => panic!("Incompatible context version: {version:?}"),
};
log::info!("Shader directive: {}", vertex.lines().next().unwrap());
Version { vertex, fragment }
}
}
pub struct Shader(<glow::Context as HasContext>::Shader);
impl Shader {
fn compile(gl: &glow::Context, stage: u32, content: &str) -> Shader {
unsafe {
let shader = gl.create_shader(stage).expect("Cannot create shader");
gl.shader_source(shader, content);
gl.compile_shader(shader);
if !gl.get_shader_compile_status(shader) {
panic!("{}", gl.get_shader_info_log(shader));
}
Shader(shader)
}
}
/// Creates a vertex [`Shader`].
pub fn vertex(
gl: &glow::Context,
version: &Version,
content: &'static str,
) -> Self {
let content = format!("{}\n{}", version.vertex, content);
Shader::compile(gl, glow::VERTEX_SHADER, &content)
}
/// Creates a fragment [`Shader`].
pub fn fragment(
gl: &glow::Context,
version: &Version,
content: &'static str,
) -> Self {
let content = format!("{}\n{}", version.fragment, content);
Shader::compile(gl, glow::FRAGMENT_SHADER, &content)
}
}
pub unsafe fn create(
gl: &glow::Context,
shaders: &[Shader],
attributes: &[(u32, &str)],
) -> <glow::Context as HasContext>::Program {
let program = gl.create_program().expect("Cannot create program");
for shader in shaders {
gl.attach_shader(program, shader.0);
}
for (i, name) in attributes {
gl.bind_attrib_location(program, *i, name);
}
gl.link_program(program);
if !gl.get_program_link_status(program) {
panic!("{}", gl.get_program_info_log(program));
}
for shader in shaders {
gl.detach_shader(program, shader.0);
gl.delete_shader(shader.0);
}
program
}

View file

@ -1,74 +0,0 @@
mod compatibility;
mod core;
use crate::program;
use crate::Transformation;
use glow::HasContext;
use iced_graphics::layer;
use iced_native::Rectangle;
#[cfg(feature = "tracing")]
use tracing::info_span;
#[derive(Debug)]
pub enum Pipeline {
Core(core::Pipeline),
Compatibility(compatibility::Pipeline),
}
impl Pipeline {
pub fn new(
gl: &glow::Context,
shader_version: &program::Version,
) -> Pipeline {
let gl_version = gl.version();
// OpenGL 3.0+ and OpenGL ES 3.0+ have instancing (which is what separates `core` from `compatibility`)
if gl_version.major >= 3 {
log::info!("Mode: core");
Pipeline::Core(core::Pipeline::new(gl, shader_version))
} else {
log::info!("Mode: compatibility");
Pipeline::Compatibility(compatibility::Pipeline::new(
gl,
shader_version,
))
}
}
pub fn draw(
&mut self,
gl: &glow::Context,
target_height: u32,
instances: &[layer::Quad],
transformation: Transformation,
scale: f32,
bounds: Rectangle<u32>,
) {
#[cfg(feature = "tracing")]
let _ = info_span!("Glow::Quad", "DRAW").enter();
match self {
Pipeline::Core(pipeline) => {
pipeline.draw(
gl,
target_height,
instances,
transformation,
scale,
bounds,
);
}
Pipeline::Compatibility(pipeline) => {
pipeline.draw(
gl,
target_height,
instances,
transformation,
scale,
bounds,
);
}
}
}
}

View file

@ -1,349 +0,0 @@
use crate::program::{self, Shader};
use crate::Transformation;
use glow::HasContext;
use iced_graphics::layer;
use iced_native::Rectangle;
// Only change `MAX_QUADS`, otherwise you could cause problems
// by splitting a triangle into different render passes.
const MAX_QUADS: usize = 100_000;
const MAX_VERTICES: usize = MAX_QUADS * 4;
const MAX_INDICES: usize = MAX_QUADS * 6;
#[derive(Debug)]
pub struct Pipeline {
program: <glow::Context as HasContext>::Program,
vertex_array: <glow::Context as HasContext>::VertexArray,
vertex_buffer: <glow::Context as HasContext>::Buffer,
index_buffer: <glow::Context as HasContext>::Buffer,
transform_location: <glow::Context as HasContext>::UniformLocation,
scale_location: <glow::Context as HasContext>::UniformLocation,
screen_height_location: <glow::Context as HasContext>::UniformLocation,
current_transform: Transformation,
current_scale: f32,
current_target_height: u32,
}
impl Pipeline {
pub fn new(
gl: &glow::Context,
shader_version: &program::Version,
) -> Pipeline {
let program = unsafe {
let vertex_shader = Shader::vertex(
gl,
shader_version,
include_str!("../shader/compatibility/quad.vert"),
);
let fragment_shader = Shader::fragment(
gl,
shader_version,
include_str!("../shader/compatibility/quad.frag"),
);
program::create(
gl,
&[vertex_shader, fragment_shader],
&[
(0, "i_Pos"),
(1, "i_Scale"),
(2, "i_Color"),
(3, "i_BorderColor"),
(4, "i_BorderRadius"),
(5, "i_BorderWidth"),
],
)
};
let transform_location =
unsafe { gl.get_uniform_location(program, "u_Transform") }
.expect("Get transform location");
let scale_location =
unsafe { gl.get_uniform_location(program, "u_Scale") }
.expect("Get scale location");
let screen_height_location =
unsafe { gl.get_uniform_location(program, "u_ScreenHeight") }
.expect("Get target height location");
unsafe {
gl.use_program(Some(program));
gl.uniform_matrix_4_f32_slice(
Some(&transform_location),
false,
Transformation::identity().as_ref(),
);
gl.uniform_1_f32(Some(&scale_location), 1.0);
gl.uniform_1_f32(Some(&screen_height_location), 0.0);
gl.use_program(None);
}
let (vertex_array, vertex_buffer, index_buffer) =
unsafe { create_buffers(gl, MAX_VERTICES) };
Pipeline {
program,
vertex_array,
vertex_buffer,
index_buffer,
transform_location,
scale_location,
screen_height_location,
current_transform: Transformation::identity(),
current_scale: 1.0,
current_target_height: 0,
}
}
pub fn draw(
&mut self,
gl: &glow::Context,
target_height: u32,
instances: &[layer::Quad],
transformation: Transformation,
scale: f32,
bounds: Rectangle<u32>,
) {
// TODO: Remove this allocation (probably by changing the shader and removing the need of two `position`)
let vertices: Vec<Vertex> =
instances.iter().flat_map(Vertex::from_quad).collect();
// TODO: Remove this allocation (or allocate only when needed)
let indices: Vec<i32> = (0..instances.len().min(MAX_QUADS) as i32)
.flat_map(|i| {
[i * 4, 1 + i * 4, 2 + i * 4, 2 + i * 4, 1 + i * 4, 3 + i * 4]
})
.cycle()
.take(instances.len() * 6)
.collect();
unsafe {
gl.enable(glow::SCISSOR_TEST);
gl.scissor(
bounds.x as i32,
(target_height - (bounds.y + bounds.height)) as i32,
bounds.width as i32,
bounds.height as i32,
);
gl.use_program(Some(self.program));
gl.bind_vertex_array(Some(self.vertex_array));
gl.bind_buffer(glow::ARRAY_BUFFER, Some(self.vertex_buffer));
gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(self.index_buffer));
}
if transformation != self.current_transform {
unsafe {
gl.uniform_matrix_4_f32_slice(
Some(&self.transform_location),
false,
transformation.as_ref(),
);
self.current_transform = transformation;
}
}
if scale != self.current_scale {
unsafe {
gl.uniform_1_f32(Some(&self.scale_location), scale);
}
self.current_scale = scale;
}
if target_height != self.current_target_height {
unsafe {
gl.uniform_1_f32(
Some(&self.screen_height_location),
target_height as f32,
);
}
self.current_target_height = target_height;
}
let passes = vertices
.chunks(MAX_VERTICES)
.zip(indices.chunks(MAX_INDICES));
for (vertices, indices) in passes {
unsafe {
gl.buffer_sub_data_u8_slice(
glow::ARRAY_BUFFER,
0,
bytemuck::cast_slice(vertices),
);
gl.buffer_sub_data_u8_slice(
glow::ELEMENT_ARRAY_BUFFER,
0,
bytemuck::cast_slice(indices),
);
gl.draw_elements(
glow::TRIANGLES,
indices.len() as i32,
glow::UNSIGNED_INT,
0,
);
}
}
unsafe {
gl.bind_vertex_array(None);
gl.use_program(None);
gl.disable(glow::SCISSOR_TEST);
}
}
}
unsafe fn create_buffers(
gl: &glow::Context,
size: usize,
) -> (
<glow::Context as HasContext>::VertexArray,
<glow::Context as HasContext>::Buffer,
<glow::Context as HasContext>::Buffer,
) {
let vertex_array = gl.create_vertex_array().expect("Create vertex array");
let vertex_buffer = gl.create_buffer().expect("Create vertex buffer");
let index_buffer = gl.create_buffer().expect("Create index buffer");
gl.bind_vertex_array(Some(vertex_array));
gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(index_buffer));
gl.buffer_data_size(
glow::ELEMENT_ARRAY_BUFFER,
12 * size as i32,
glow::DYNAMIC_DRAW,
);
gl.bind_buffer(glow::ARRAY_BUFFER, Some(vertex_buffer));
gl.buffer_data_size(
glow::ARRAY_BUFFER,
(size * Vertex::SIZE) as i32,
glow::DYNAMIC_DRAW,
);
let stride = Vertex::SIZE as i32;
gl.enable_vertex_attrib_array(0);
gl.vertex_attrib_pointer_f32(0, 2, glow::FLOAT, false, stride, 0);
gl.enable_vertex_attrib_array(1);
gl.vertex_attrib_pointer_f32(1, 2, glow::FLOAT, false, stride, 4 * 2);
gl.enable_vertex_attrib_array(2);
gl.vertex_attrib_pointer_f32(2, 4, glow::FLOAT, false, stride, 4 * (2 + 2));
gl.enable_vertex_attrib_array(3);
gl.vertex_attrib_pointer_f32(
3,
4,
glow::FLOAT,
false,
stride,
4 * (2 + 2 + 4),
);
gl.enable_vertex_attrib_array(4);
gl.vertex_attrib_pointer_f32(
4,
4,
glow::FLOAT,
false,
stride,
4 * (2 + 2 + 4 + 4),
);
gl.enable_vertex_attrib_array(5);
gl.vertex_attrib_pointer_f32(
5,
1,
glow::FLOAT,
false,
stride,
4 * (2 + 2 + 4 + 4 + 4),
);
gl.enable_vertex_attrib_array(6);
gl.vertex_attrib_pointer_f32(
6,
2,
glow::FLOAT,
false,
stride,
4 * (2 + 2 + 4 + 4 + 4 + 1),
);
gl.bind_vertex_array(None);
gl.bind_buffer(glow::ARRAY_BUFFER, None);
gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, None);
(vertex_array, vertex_buffer, index_buffer)
}
/// The vertex of a colored rectangle with a border.
///
/// This type can be directly uploaded to GPU memory.
#[derive(Debug, Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
#[repr(C)]
pub struct Vertex {
/// The position of the [`Vertex`].
pub position: [f32; 2],
/// The size of the [`Vertex`].
pub size: [f32; 2],
/// The color of the [`Vertex`], in __linear RGB__.
pub color: [f32; 4],
/// The border color of the [`Vertex`], in __linear RGB__.
pub border_color: [f32; 4],
/// The border radius of the [`Vertex`].
pub border_radius: [f32; 4],
/// The border width of the [`Vertex`].
pub border_width: f32,
/// The __quad__ position of the [`Vertex`].
pub q_position: [f32; 2],
}
impl Vertex {
const SIZE: usize = std::mem::size_of::<Self>();
fn from_quad(quad: &layer::Quad) -> [Vertex; 4] {
let base = Vertex {
position: quad.position,
size: quad.size,
color: quad.color,
border_color: quad.color,
border_radius: quad.border_radius,
border_width: quad.border_width,
q_position: [0.0, 0.0],
};
[
base,
Self {
q_position: [0.0, 1.0],
..base
},
Self {
q_position: [1.0, 0.0],
..base
},
Self {
q_position: [1.0, 1.0],
..base
},
]
}
}

View file

@ -1,244 +0,0 @@
use crate::program::{self, Shader};
use crate::Transformation;
use glow::HasContext;
use iced_graphics::layer;
use iced_native::Rectangle;
const MAX_INSTANCES: usize = 100_000;
#[derive(Debug)]
pub struct Pipeline {
program: <glow::Context as HasContext>::Program,
vertex_array: <glow::Context as HasContext>::VertexArray,
instances: <glow::Context as HasContext>::Buffer,
transform_location: <glow::Context as HasContext>::UniformLocation,
scale_location: <glow::Context as HasContext>::UniformLocation,
screen_height_location: <glow::Context as HasContext>::UniformLocation,
current_transform: Transformation,
current_scale: f32,
current_target_height: u32,
}
impl Pipeline {
pub fn new(
gl: &glow::Context,
shader_version: &program::Version,
) -> Pipeline {
let program = unsafe {
let vertex_shader = Shader::vertex(
gl,
shader_version,
include_str!("../shader/core/quad.vert"),
);
let fragment_shader = Shader::fragment(
gl,
shader_version,
include_str!("../shader/core/quad.frag"),
);
program::create(
gl,
&[vertex_shader, fragment_shader],
&[
(0, "i_Pos"),
(1, "i_Scale"),
(2, "i_Color"),
(3, "i_BorderColor"),
(4, "i_BorderRadius"),
(5, "i_BorderWidth"),
],
)
};
let transform_location =
unsafe { gl.get_uniform_location(program, "u_Transform") }
.expect("Get transform location");
let scale_location =
unsafe { gl.get_uniform_location(program, "u_Scale") }
.expect("Get scale location");
let screen_height_location =
unsafe { gl.get_uniform_location(program, "u_ScreenHeight") }
.expect("Get target height location");
unsafe {
gl.use_program(Some(program));
gl.uniform_matrix_4_f32_slice(
Some(&transform_location),
false,
Transformation::identity().as_ref(),
);
gl.uniform_1_f32(Some(&scale_location), 1.0);
gl.uniform_1_f32(Some(&screen_height_location), 0.0);
gl.use_program(None);
}
let (vertex_array, instances) =
unsafe { create_instance_buffer(gl, MAX_INSTANCES) };
Pipeline {
program,
vertex_array,
instances,
transform_location,
scale_location,
screen_height_location,
current_transform: Transformation::identity(),
current_scale: 1.0,
current_target_height: 0,
}
}
pub fn draw(
&mut self,
gl: &glow::Context,
target_height: u32,
instances: &[layer::Quad],
transformation: Transformation,
scale: f32,
bounds: Rectangle<u32>,
) {
unsafe {
gl.enable(glow::SCISSOR_TEST);
gl.scissor(
bounds.x as i32,
(target_height - (bounds.y + bounds.height)) as i32,
bounds.width as i32,
bounds.height as i32,
);
gl.use_program(Some(self.program));
gl.bind_vertex_array(Some(self.vertex_array));
gl.bind_buffer(glow::ARRAY_BUFFER, Some(self.instances));
}
if transformation != self.current_transform {
unsafe {
gl.uniform_matrix_4_f32_slice(
Some(&self.transform_location),
false,
transformation.as_ref(),
);
self.current_transform = transformation;
}
}
if scale != self.current_scale {
unsafe {
gl.uniform_1_f32(Some(&self.scale_location), scale);
}
self.current_scale = scale;
}
if target_height != self.current_target_height {
unsafe {
gl.uniform_1_f32(
Some(&self.screen_height_location),
target_height as f32,
);
}
self.current_target_height = target_height;
}
for instances in instances.chunks(MAX_INSTANCES) {
unsafe {
gl.buffer_sub_data_u8_slice(
glow::ARRAY_BUFFER,
0,
bytemuck::cast_slice(instances),
);
gl.draw_arrays_instanced(
glow::TRIANGLE_STRIP,
0,
4,
instances.len() as i32,
);
}
}
unsafe {
gl.bind_vertex_array(None);
gl.use_program(None);
gl.disable(glow::SCISSOR_TEST);
}
}
}
unsafe fn create_instance_buffer(
gl: &glow::Context,
size: usize,
) -> (
<glow::Context as HasContext>::VertexArray,
<glow::Context as HasContext>::Buffer,
) {
let vertex_array = gl.create_vertex_array().expect("Create vertex array");
let buffer = gl.create_buffer().expect("Create instance buffer");
gl.bind_vertex_array(Some(vertex_array));
gl.bind_buffer(glow::ARRAY_BUFFER, Some(buffer));
gl.buffer_data_size(
glow::ARRAY_BUFFER,
(size * std::mem::size_of::<layer::Quad>()) as i32,
glow::DYNAMIC_DRAW,
);
let stride = std::mem::size_of::<layer::Quad>() as i32;
gl.enable_vertex_attrib_array(0);
gl.vertex_attrib_pointer_f32(0, 2, glow::FLOAT, false, stride, 0);
gl.vertex_attrib_divisor(0, 1);
gl.enable_vertex_attrib_array(1);
gl.vertex_attrib_pointer_f32(1, 2, glow::FLOAT, false, stride, 4 * 2);
gl.vertex_attrib_divisor(1, 1);
gl.enable_vertex_attrib_array(2);
gl.vertex_attrib_pointer_f32(2, 4, glow::FLOAT, false, stride, 4 * (2 + 2));
gl.vertex_attrib_divisor(2, 1);
gl.enable_vertex_attrib_array(3);
gl.vertex_attrib_pointer_f32(
3,
4,
glow::FLOAT,
false,
stride,
4 * (2 + 2 + 4),
);
gl.vertex_attrib_divisor(3, 1);
gl.enable_vertex_attrib_array(4);
gl.vertex_attrib_pointer_f32(
4,
4,
glow::FLOAT,
false,
stride,
4 * (2 + 2 + 4 + 4),
);
gl.vertex_attrib_divisor(4, 1);
gl.enable_vertex_attrib_array(5);
gl.vertex_attrib_pointer_f32(
5,
1,
glow::FLOAT,
false,
stride,
4 * (2 + 2 + 4 + 4 + 4),
);
gl.vertex_attrib_divisor(5, 1);
gl.bind_vertex_array(None);
gl.bind_buffer(glow::ARRAY_BUFFER, None);
(vertex_array, buffer)
}

View file

@ -1,61 +0,0 @@
//! Configure a renderer.
pub use iced_graphics::Antialiasing;
/// The settings of a [`Backend`].
///
/// [`Backend`]: crate::Backend
#[derive(Clone, Copy, PartialEq)]
pub struct Settings {
/// The bytes of the font that will be used by default.
///
/// If `None` is provided, a default system font will be chosen.
pub default_font: Option<&'static [u8]>,
/// The default size of text.
///
/// By default, it will be set to `20.0`.
pub default_text_size: f32,
/// If enabled, spread text workload in multiple threads when multiple cores
/// are available.
///
/// By default, it is disabled.
pub text_multithreading: bool,
/// The antialiasing strategy that will be used for triangle primitives.
///
/// By default, it is `None`.
pub antialiasing: Option<Antialiasing>,
}
impl Default for Settings {
fn default() -> Settings {
Settings {
default_font: None,
default_text_size: 20.0,
text_multithreading: false,
antialiasing: None,
}
}
}
impl std::fmt::Debug for Settings {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Settings")
// Instead of printing the font bytes, we simply show a `bool` indicating if using a default font or not.
.field("default_font", &self.default_font.is_some())
.field("default_text_size", &self.default_text_size)
.field("text_multithreading", &self.text_multithreading)
.field("antialiasing", &self.antialiasing)
.finish()
}
}
impl Settings {
/// Creates new [`Settings`] using environment configuration.
///
/// Currently, this is equivalent to calling [`Settings::default`].
pub fn from_env() -> Self {
Self::default()
}
}

View file

@ -1,59 +0,0 @@
#ifdef GL_ES
#ifdef GL_FRAGMENT_PRECISION_HIGH
precision highp float;
#else
precision mediump float;
#endif
#endif
#ifdef HIGHER_THAN_300
layout (location = 0) out vec4 fragColor;
#define gl_FragColor fragColor
#endif
in vec2 raw_position;
uniform vec4 gradient_direction;
uniform int color_stops_size;
// GLSL does not support dynamically sized arrays without SSBOs so this is capped to 16 stops
//stored as color(vec4) -> offset(vec4) sequentially;
uniform vec4 color_stops[32];
//TODO: rewrite without branching to make ALUs happy
void main() {
vec2 start = gradient_direction.xy;
vec2 end = gradient_direction.zw;
vec2 gradient_vec = vec2(end - start);
vec2 current_vec = vec2(raw_position.xy - start);
vec2 unit = normalize(gradient_vec);
float coord_offset = dot(unit, current_vec) / length(gradient_vec);
//if a gradient has a start/end stop that is identical, the mesh will have a transparent fill
gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0);
float min_offset = color_stops[1].x;
float max_offset = color_stops[color_stops_size - 1].x;
for (int i = 0; i < color_stops_size - 2; i += 2) {
float curr_offset = color_stops[i+1].x;
float next_offset = color_stops[i+3].x;
if (coord_offset <= min_offset) {
//current coordinate is before the first defined offset, set it to the start color
gl_FragColor = color_stops[0];
}
if (curr_offset <= coord_offset && coord_offset <= next_offset) {
//current fragment is between the current offset processing & the next one, interpolate colors
gl_FragColor = mix(color_stops[i], color_stops[i+2], smoothstep(
curr_offset,
next_offset,
coord_offset
));
}
if (coord_offset >= max_offset) {
//current coordinate is before the last defined offset, set it to the last color
gl_FragColor = color_stops[color_stops_size - 2];
}
}
}

View file

@ -1,9 +0,0 @@
uniform mat4 u_Transform;
in vec2 i_Position;
out vec2 raw_position;
void main() {
gl_Position = u_Transform * vec4(i_Position, 0.0, 1.0);
raw_position = i_Position;
}

View file

@ -1,22 +0,0 @@
#ifdef GL_ES
#ifdef GL_FRAGMENT_PRECISION_HIGH
precision highp float;
#else
precision mediump float;
#endif
#endif
uniform sampler2D tex;
in vec2 tex_pos;
#ifdef HIGHER_THAN_300
out vec4 fragColor;
#define gl_FragColor fragColor
#endif
#ifdef GL_ES
#define texture texture2D
#endif
void main() {
gl_FragColor = texture(tex, tex_pos);
}

View file

@ -1,9 +0,0 @@
uniform mat4 u_Transform;
in vec2 i_Position;
out vec2 tex_pos;
void main() {
gl_Position = u_Transform * vec4(i_Position, 0.0, 1.0);
tex_pos = i_Position;
}

View file

@ -1,18 +0,0 @@
#ifdef GL_ES
#ifdef GL_FRAGMENT_PRECISION_HIGH
precision highp float;
#else
precision mediump float;
#endif
#endif
#ifdef HIGHER_THAN_300
out vec4 fragColor;
#define gl_FragColor fragColor
#endif
in vec4 v_Color;
void main() {
gl_FragColor = v_Color;
}

View file

@ -1,11 +0,0 @@
uniform mat4 u_Transform;
in vec2 i_Position;
in vec4 i_Color;
out vec4 v_Color;
void main() {
gl_Position = u_Transform * vec4(i_Position, 0.0, 1.0);
v_Color = i_Color;
}

View file

@ -1,83 +0,0 @@
#ifdef GL_ES
#ifdef GL_FRAGMENT_PRECISION_HIGH
precision highp float;
#else
precision mediump float;
#endif
#endif
uniform float u_ScreenHeight;
varying vec4 v_Color;
varying vec4 v_BorderColor;
varying vec2 v_Pos;
varying vec2 v_Scale;
varying vec4 v_BorderRadius;
varying float v_BorderWidth;
float _distance(vec2 frag_coord, vec2 position, vec2 size, float radius)
{
// TODO: Try SDF approach: https://www.shadertoy.com/view/wd3XRN
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.0),
max(max(top_left_distance.y, bottom_right_distance.y), 0.0)
);
return sqrt(distance.x * distance.x + distance.y * distance.y);
}
float selectBorderRadius(vec4 radi, vec2 position, vec2 center)
{
float rx = radi.x;
float ry = radi.y;
rx = position.x > center.x ? radi.y : radi.x;
ry = position.x > center.x ? radi.z : radi.w;
rx = position.y > center.y ? ry : rx;
return rx;
}
void main() {
vec2 fragCoord = vec2(gl_FragCoord.x, u_ScreenHeight - gl_FragCoord.y);
float border_radius = selectBorderRadius(
v_BorderRadius,
fragCoord,
(v_Pos + v_Scale * 0.5).xy
);
float internal_border = max(border_radius - v_BorderWidth, 0.0);
float internal_distance = _distance(
fragCoord,
v_Pos + vec2(v_BorderWidth),
v_Scale - vec2(v_BorderWidth * 2.0),
internal_border
);
float border_mix = smoothstep(
max(internal_border - 0.5, 0.0),
internal_border + 0.5,
internal_distance
);
vec4 mixed_color = mix(v_Color, v_BorderColor, border_mix);
float d = _distance(
fragCoord,
v_Pos,
v_Scale,
border_radius
);
float radius_alpha =
1.0 - smoothstep(max(border_radius - 0.5, 0.0), border_radius + 0.5, d);
gl_FragColor = vec4(mixed_color.xyz, mixed_color.w * radius_alpha);
}

View file

@ -1,46 +0,0 @@
uniform mat4 u_Transform;
uniform float u_Scale;
attribute vec2 i_Pos;
attribute vec2 i_Scale;
attribute vec4 i_Color;
attribute vec4 i_BorderColor;
attribute vec4 i_BorderRadius;
attribute float i_BorderWidth;
attribute vec2 q_Pos;
varying vec4 v_Color;
varying vec4 v_BorderColor;
varying vec2 v_Pos;
varying vec2 v_Scale;
varying vec4 v_BorderRadius;
varying float v_BorderWidth;
void main() {
vec2 p_Pos = i_Pos * u_Scale;
vec2 p_Scale = i_Scale * u_Scale;
vec4 i_BorderRadius = vec4(
min(i_BorderRadius.x, min(i_Scale.x, i_Scale.y) / 2.0),
min(i_BorderRadius.y, min(i_Scale.x, i_Scale.y) / 2.0),
min(i_BorderRadius.z, min(i_Scale.x, i_Scale.y) / 2.0),
min(i_BorderRadius.w, min(i_Scale.x, i_Scale.y) / 2.0)
);
mat4 i_Transform = mat4(
vec4(p_Scale.x + 1.0, 0.0, 0.0, 0.0),
vec4(0.0, p_Scale.y + 1.0, 0.0, 0.0),
vec4(0.0, 0.0, 1.0, 0.0),
vec4(p_Pos - vec2(0.5, 0.5), 0.0, 1.0)
);
v_Color = i_Color;
v_BorderColor = i_BorderColor;
v_Pos = p_Pos;
v_Scale = p_Scale;
v_BorderRadius = i_BorderRadius * u_Scale;
v_BorderWidth = i_BorderWidth * u_Scale;
gl_Position = u_Transform * i_Transform * vec4(q_Pos, 0.0, 1.0);
}

View file

@ -1,95 +0,0 @@
#ifdef GL_ES
#ifdef GL_FRAGMENT_PRECISION_HIGH
precision highp float;
#else
precision mediump float;
#endif
#endif
#ifdef HIGHER_THAN_300
out vec4 fragColor;
#define gl_FragColor fragColor
#endif
uniform float u_ScreenHeight;
in vec4 v_Color;
in vec4 v_BorderColor;
in vec2 v_Pos;
in vec2 v_Scale;
in vec4 v_BorderRadius;
in float v_BorderWidth;
float fDistance(vec2 frag_coord, vec2 position, vec2 size, float radius)
{
// TODO: Try SDF approach: https://www.shadertoy.com/view/wd3XRN
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.0),
max(max(top_left_distance.y, bottom_right_distance.y), 0.0)
);
return sqrt(distance.x * distance.x + distance.y * distance.y);
}
float selectBorderRadius(vec4 radi, vec2 position, vec2 center)
{
float rx = radi.x;
float ry = radi.y;
rx = position.x > center.x ? radi.y : radi.x;
ry = position.x > center.x ? radi.z : radi.w;
rx = position.y > center.y ? ry : rx;
return rx;
}
void main() {
vec4 mixed_color;
vec2 fragCoord = vec2(gl_FragCoord.x, u_ScreenHeight - gl_FragCoord.y);
float border_radius = selectBorderRadius(
v_BorderRadius,
fragCoord,
(v_Pos + v_Scale * 0.5).xy
);
// TODO: Remove branching (?)
if(v_BorderWidth > 0.0) {
float internal_border = max(border_radius - v_BorderWidth, 0.0);
float internal_distance = fDistance(
fragCoord,
v_Pos + vec2(v_BorderWidth),
v_Scale - vec2(v_BorderWidth * 2.0),
internal_border
);
float border_mix = smoothstep(
max(internal_border - 0.5, 0.0),
internal_border + 0.5,
internal_distance
);
mixed_color = mix(v_Color, v_BorderColor, border_mix);
} else {
mixed_color = v_Color;
}
float d = fDistance(
fragCoord,
v_Pos,
v_Scale,
border_radius
);
float radius_alpha =
1.0 - smoothstep(max(border_radius - 0.5, 0.0), border_radius + 0.5, d);
gl_FragColor = vec4(mixed_color.xyz, mixed_color.w * radius_alpha);
}

View file

@ -1,52 +0,0 @@
uniform mat4 u_Transform;
uniform float u_Scale;
in vec2 i_Pos;
in vec2 i_Scale;
in vec4 i_Color;
in vec4 i_BorderColor;
in vec4 i_BorderRadius;
in float i_BorderWidth;
out vec4 v_Color;
out vec4 v_BorderColor;
out vec2 v_Pos;
out vec2 v_Scale;
out vec4 v_BorderRadius;
out float v_BorderWidth;
vec2 positions[4] = vec2[](
vec2(0.0, 0.0),
vec2(0.0, 1.0),
vec2(1.0, 0.0),
vec2(1.0, 1.0)
);
void main() {
vec2 q_Pos = positions[gl_VertexID];
vec2 p_Pos = i_Pos * u_Scale;
vec2 p_Scale = i_Scale * u_Scale;
vec4 i_BorderRadius = vec4(
min(i_BorderRadius.x, min(i_Scale.x, i_Scale.y) / 2.0),
min(i_BorderRadius.y, min(i_Scale.x, i_Scale.y) / 2.0),
min(i_BorderRadius.z, min(i_Scale.x, i_Scale.y) / 2.0),
min(i_BorderRadius.w, min(i_Scale.x, i_Scale.y) / 2.0)
);
mat4 i_Transform = mat4(
vec4(p_Scale.x + 1.0, 0.0, 0.0, 0.0),
vec4(0.0, p_Scale.y + 1.0, 0.0, 0.0),
vec4(0.0, 0.0, 1.0, 0.0),
vec4(p_Pos - vec2(0.5, 0.5), 0.0, 1.0)
);
v_Color = i_Color;
v_BorderColor = i_BorderColor;
v_Pos = p_Pos;
v_Scale = p_Scale;
v_BorderRadius = i_BorderRadius * u_Scale;
v_BorderWidth = i_BorderWidth * u_Scale;
gl_Position = u_Transform * i_Transform * vec4(q_Pos, 0.0, 1.0);
}

View file

@ -1,257 +0,0 @@
use crate::Transformation;
use iced_graphics::font;
use glow_glyph::ab_glyph;
use std::{cell::RefCell, collections::HashMap};
pub use iced_native::text::Hit;
#[derive(Debug)]
pub struct Pipeline {
draw_brush: RefCell<glow_glyph::GlyphBrush>,
draw_font_map: RefCell<HashMap<String, glow_glyph::FontId>>,
measure_brush: RefCell<glyph_brush::GlyphBrush<()>>,
}
impl Pipeline {
pub fn new(
gl: &glow::Context,
default_font: Option<&[u8]>,
multithreading: bool,
) -> Self {
let default_font = default_font.map(|slice| slice.to_vec());
// TODO: Font customization
#[cfg(feature = "default_system_font")]
let default_font = {
default_font.or_else(|| {
font::Source::new()
.load(&[font::Family::SansSerif, font::Family::Serif])
.ok()
})
};
let default_font =
default_font.unwrap_or_else(|| font::FALLBACK.to_vec());
let font = ab_glyph::FontArc::try_from_vec(default_font)
.unwrap_or_else(|_| {
log::warn!(
"System font failed to load. Falling back to \
embedded font..."
);
ab_glyph::FontArc::try_from_slice(font::FALLBACK)
.expect("Load fallback font")
});
let draw_brush_builder =
glow_glyph::GlyphBrushBuilder::using_font(font.clone())
.initial_cache_size((2048, 2048))
.draw_cache_multithread(multithreading);
#[cfg(target_arch = "wasm32")]
let draw_brush_builder = draw_brush_builder.draw_cache_align_4x4(true);
let draw_brush = draw_brush_builder.build(gl);
let measure_brush =
glyph_brush::GlyphBrushBuilder::using_font(font).build();
Pipeline {
draw_brush: RefCell::new(draw_brush),
draw_font_map: RefCell::new(HashMap::new()),
measure_brush: RefCell::new(measure_brush),
}
}
pub fn queue(&mut self, section: glow_glyph::Section<'_>) {
self.draw_brush.borrow_mut().queue(section);
}
pub fn draw_queued(
&mut self,
gl: &glow::Context,
transformation: Transformation,
region: glow_glyph::Region,
) {
self.draw_brush
.borrow_mut()
.draw_queued_with_transform_and_scissoring(
gl,
transformation.into(),
region,
)
.expect("Draw text");
}
pub fn measure(
&self,
content: &str,
size: f32,
font: iced_native::Font,
bounds: iced_native::Size,
) -> (f32, f32) {
use glow_glyph::GlyphCruncher;
let glow_glyph::FontId(font_id) = self.find_font(font);
let section = glow_glyph::Section {
bounds: (bounds.width, bounds.height),
text: vec![glow_glyph::Text {
text: content,
scale: size.into(),
font_id: glow_glyph::FontId(font_id),
extra: glow_glyph::Extra::default(),
}],
..Default::default()
};
if let Some(bounds) =
self.measure_brush.borrow_mut().glyph_bounds(section)
{
(bounds.width().ceil(), bounds.height().ceil())
} else {
(0.0, 0.0)
}
}
pub fn hit_test(
&self,
content: &str,
size: f32,
font: iced_native::Font,
bounds: iced_native::Size,
point: iced_native::Point,
nearest_only: bool,
) -> Option<Hit> {
use glow_glyph::GlyphCruncher;
let glow_glyph::FontId(font_id) = self.find_font(font);
let section = glow_glyph::Section {
bounds: (bounds.width, bounds.height),
text: vec![glow_glyph::Text {
text: content,
scale: size.into(),
font_id: glow_glyph::FontId(font_id),
extra: glow_glyph::Extra::default(),
}],
..Default::default()
};
let mut mb = self.measure_brush.borrow_mut();
// The underlying type is FontArc, so clones are cheap.
use ab_glyph::{Font, ScaleFont};
let font = mb.fonts()[font_id].clone().into_scaled(size);
// Implements an iterator over the glyph bounding boxes.
let bounds = mb.glyphs(section).map(
|glow_glyph::SectionGlyph {
byte_index, glyph, ..
}| {
(
*byte_index,
iced_native::Rectangle::new(
iced_native::Point::new(
glyph.position.x - font.h_side_bearing(glyph.id),
glyph.position.y - font.ascent(),
),
iced_native::Size::new(
font.h_advance(glyph.id),
font.ascent() - font.descent(),
),
),
)
},
);
// Implements computation of the character index based on the byte index
// within the input string.
let char_index = |byte_index| {
let mut b_count = 0;
for (i, utf8_len) in
content.chars().map(|c| c.len_utf8()).enumerate()
{
if byte_index < (b_count + utf8_len) {
return i;
}
b_count += utf8_len;
}
byte_index
};
if !nearest_only {
for (idx, bounds) in bounds.clone() {
if bounds.contains(point) {
return Some(Hit::CharOffset(char_index(idx)));
}
}
}
let nearest = bounds
.map(|(index, bounds)| (index, bounds.center()))
.min_by(|(_, center_a), (_, center_b)| {
center_a
.distance(point)
.partial_cmp(&center_b.distance(point))
.unwrap_or(std::cmp::Ordering::Greater)
});
nearest.map(|(idx, center)| {
Hit::NearestCharOffset(char_index(idx), point - center)
})
}
pub fn trim_measurement_cache(&mut self) {
// TODO: We should probably use a `GlyphCalculator` for this. However,
// it uses a lifetimed `GlyphCalculatorGuard` with side-effects on drop.
// This makes stuff quite inconvenient. A manual method for trimming the
// cache would make our lives easier.
loop {
let action = self
.measure_brush
.borrow_mut()
.process_queued(|_, _| {}, |_| {});
match action {
Ok(_) => break,
Err(glyph_brush::BrushError::TextureTooSmall { suggested }) => {
let (width, height) = suggested;
self.measure_brush
.borrow_mut()
.resize_texture(width, height);
}
}
}
}
pub fn find_font(&self, font: iced_native::Font) -> glow_glyph::FontId {
match font {
iced_native::Font::Default => glow_glyph::FontId(0),
iced_native::Font::External { name, bytes } => {
if let Some(font_id) = self.draw_font_map.borrow().get(name) {
return *font_id;
}
let font = ab_glyph::FontArc::try_from_slice(bytes)
.expect("Load font");
let _ = self.measure_brush.borrow_mut().add_font(font.clone());
let font_id = self.draw_brush.borrow_mut().add_font(font);
let _ = self
.draw_font_map
.borrow_mut()
.insert(String::from(name), font_id);
font_id
}
}
}
}

View file

@ -1,595 +0,0 @@
//! Draw meshes of triangles.
use crate::program;
use crate::Transformation;
use iced_graphics::gradient::Gradient;
use iced_graphics::layer::mesh::{self, Mesh};
use iced_graphics::triangle::{ColoredVertex2D, Vertex2D};
use glow::HasContext;
use std::marker::PhantomData;
#[cfg(feature = "tracing")]
use tracing::info_span;
const DEFAULT_VERTICES: usize = 1_000;
const DEFAULT_INDICES: usize = 1_000;
#[derive(Debug)]
pub(crate) struct Pipeline {
indices: Buffer<u32>,
solid: solid::Program,
gradient: gradient::Program,
}
impl Pipeline {
pub fn new(gl: &glow::Context, shader_version: &program::Version) -> Self {
let mut indices = unsafe {
Buffer::new(
gl,
glow::ELEMENT_ARRAY_BUFFER,
glow::DYNAMIC_DRAW,
DEFAULT_INDICES,
)
};
let solid = solid::Program::new(gl, shader_version);
let gradient = gradient::Program::new(gl, shader_version);
unsafe {
gl.bind_vertex_array(Some(solid.vertex_array));
indices.bind(gl, 0);
gl.bind_vertex_array(Some(gradient.vertex_array));
indices.bind(gl, 0);
gl.bind_vertex_array(None);
}
Self {
indices,
solid,
gradient,
}
}
pub fn draw(
&mut self,
meshes: &[Mesh<'_>],
gl: &glow::Context,
target_height: u32,
transformation: Transformation,
scale_factor: f32,
) {
#[cfg(feature = "tracing")]
let _ = info_span!("Glow::Triangle", "DRAW").enter();
unsafe {
gl.enable(glow::MULTISAMPLE);
gl.enable(glow::SCISSOR_TEST);
}
// Count the total amount of vertices & indices we need to handle
let count = mesh::attribute_count_of(meshes);
// Then we ensure the current attribute buffers are big enough, resizing if necessary
unsafe {
self.indices.bind(gl, count.indices);
}
// We upload all the vertices and indices upfront
let mut solid_vertex_offset = 0;
let mut gradient_vertex_offset = 0;
let mut index_offset = 0;
for mesh in meshes {
let indices = mesh.indices();
unsafe {
gl.buffer_sub_data_u8_slice(
glow::ELEMENT_ARRAY_BUFFER,
(index_offset * std::mem::size_of::<u32>()) as i32,
bytemuck::cast_slice(indices),
);
index_offset += indices.len();
}
match mesh {
Mesh::Solid { buffers, .. } => {
unsafe {
self.solid.vertices.bind(gl, count.solid_vertices);
gl.buffer_sub_data_u8_slice(
glow::ARRAY_BUFFER,
(solid_vertex_offset
* std::mem::size_of::<ColoredVertex2D>())
as i32,
bytemuck::cast_slice(&buffers.vertices),
);
}
solid_vertex_offset += buffers.vertices.len();
}
Mesh::Gradient { buffers, .. } => {
unsafe {
self.gradient
.vertices
.bind(gl, count.gradient_vertices);
gl.buffer_sub_data_u8_slice(
glow::ARRAY_BUFFER,
(gradient_vertex_offset
* std::mem::size_of::<Vertex2D>())
as i32,
bytemuck::cast_slice(&buffers.vertices),
);
}
gradient_vertex_offset += buffers.vertices.len();
}
}
}
// Then we draw each mesh using offsets
let mut last_solid_vertex = 0;
let mut last_gradient_vertex = 0;
let mut last_index = 0;
for mesh in meshes {
let indices = mesh.indices();
let origin = mesh.origin();
let transform =
transformation * Transformation::translate(origin.x, origin.y);
let clip_bounds = (mesh.clip_bounds() * scale_factor).snap();
unsafe {
gl.scissor(
clip_bounds.x as i32,
(target_height - (clip_bounds.y + clip_bounds.height))
as i32,
clip_bounds.width as i32,
clip_bounds.height as i32,
);
}
match mesh {
Mesh::Solid { buffers, .. } => unsafe {
gl.use_program(Some(self.solid.program));
gl.bind_vertex_array(Some(self.solid.vertex_array));
if transform != self.solid.uniforms.transform {
gl.uniform_matrix_4_f32_slice(
Some(&self.solid.uniforms.transform_location),
false,
transform.as_ref(),
);
self.solid.uniforms.transform = transform;
}
gl.draw_elements_base_vertex(
glow::TRIANGLES,
indices.len() as i32,
glow::UNSIGNED_INT,
(last_index * std::mem::size_of::<u32>()) as i32,
last_solid_vertex as i32,
);
last_solid_vertex += buffers.vertices.len();
},
Mesh::Gradient {
buffers, gradient, ..
} => unsafe {
gl.use_program(Some(self.gradient.program));
gl.bind_vertex_array(Some(self.gradient.vertex_array));
if transform != self.gradient.uniforms.transform {
gl.uniform_matrix_4_f32_slice(
Some(&self.gradient.uniforms.locations.transform),
false,
transform.as_ref(),
);
self.gradient.uniforms.transform = transform;
}
if &self.gradient.uniforms.gradient != *gradient {
match gradient {
Gradient::Linear(linear) => {
gl.uniform_4_f32(
Some(
&self
.gradient
.uniforms
.locations
.gradient_direction,
),
linear.start.x,
linear.start.y,
linear.end.x,
linear.end.y,
);
gl.uniform_1_i32(
Some(
&self
.gradient
.uniforms
.locations
.color_stops_size,
),
(linear.color_stops.len() * 2) as i32,
);
let mut stops = [0.0; 128];
for (index, stop) in linear
.color_stops
.iter()
.enumerate()
.take(16)
{
let [r, g, b, a] = stop.color.into_linear();
stops[index * 8] = r;
stops[(index * 8) + 1] = g;
stops[(index * 8) + 2] = b;
stops[(index * 8) + 3] = a;
stops[(index * 8) + 4] = stop.offset;
stops[(index * 8) + 5] = 0.;
stops[(index * 8) + 6] = 0.;
stops[(index * 8) + 7] = 0.;
}
gl.uniform_4_f32_slice(
Some(
&self
.gradient
.uniforms
.locations
.color_stops,
),
&stops,
);
}
}
self.gradient.uniforms.gradient = (*gradient).clone();
}
gl.draw_elements_base_vertex(
glow::TRIANGLES,
indices.len() as i32,
glow::UNSIGNED_INT,
(last_index * std::mem::size_of::<u32>()) as i32,
last_gradient_vertex as i32,
);
last_gradient_vertex += buffers.vertices.len();
},
}
last_index += indices.len();
}
unsafe {
gl.bind_vertex_array(None);
gl.disable(glow::SCISSOR_TEST);
gl.disable(glow::MULTISAMPLE);
}
}
}
#[derive(Debug)]
pub struct Buffer<T> {
raw: <glow::Context as HasContext>::Buffer,
target: u32,
usage: u32,
size: usize,
phantom: PhantomData<T>,
}
impl<T> Buffer<T> {
pub unsafe fn new(
gl: &glow::Context,
target: u32,
usage: u32,
size: usize,
) -> Self {
let raw = gl.create_buffer().expect("Create buffer");
let mut buffer = Buffer {
raw,
target,
usage,
size: 0,
phantom: PhantomData,
};
buffer.bind(gl, size);
buffer
}
pub unsafe fn bind(&mut self, gl: &glow::Context, size: usize) {
gl.bind_buffer(self.target, Some(self.raw));
if self.size < size {
gl.buffer_data_size(
self.target,
(size * std::mem::size_of::<T>()) as i32,
self.usage,
);
self.size = size;
}
}
}
mod solid {
use crate::program;
use crate::triangle;
use glow::{Context, HasContext, NativeProgram};
use iced_graphics::triangle::ColoredVertex2D;
use iced_graphics::Transformation;
#[derive(Debug)]
pub struct Program {
pub program: <Context as HasContext>::Program,
pub vertex_array: <glow::Context as HasContext>::VertexArray,
pub vertices: triangle::Buffer<ColoredVertex2D>,
pub uniforms: Uniforms,
}
impl Program {
pub fn new(gl: &Context, shader_version: &program::Version) -> Self {
let program = unsafe {
let vertex_shader = program::Shader::vertex(
gl,
shader_version,
include_str!("shader/common/solid.vert"),
);
let fragment_shader = program::Shader::fragment(
gl,
shader_version,
include_str!("shader/common/solid.frag"),
);
program::create(
gl,
&[vertex_shader, fragment_shader],
&[(0, "i_Position"), (1, "i_Color")],
)
};
let vertex_array = unsafe {
gl.create_vertex_array().expect("Create vertex array")
};
let vertices = unsafe {
triangle::Buffer::new(
gl,
glow::ARRAY_BUFFER,
glow::DYNAMIC_DRAW,
super::DEFAULT_VERTICES,
)
};
unsafe {
gl.bind_vertex_array(Some(vertex_array));
let stride = std::mem::size_of::<ColoredVertex2D>() as i32;
gl.enable_vertex_attrib_array(0);
gl.vertex_attrib_pointer_f32(
0,
2,
glow::FLOAT,
false,
stride,
0,
);
gl.enable_vertex_attrib_array(1);
gl.vertex_attrib_pointer_f32(
1,
4,
glow::FLOAT,
false,
stride,
4 * 2,
);
gl.bind_vertex_array(None);
};
Self {
program,
vertex_array,
vertices,
uniforms: Uniforms::new(gl, program),
}
}
}
#[derive(Debug)]
pub struct Uniforms {
pub transform: Transformation,
pub transform_location: <Context as HasContext>::UniformLocation,
}
impl Uniforms {
fn new(gl: &Context, program: NativeProgram) -> Self {
let transform = Transformation::identity();
let transform_location =
unsafe { gl.get_uniform_location(program, "u_Transform") }
.expect("Solid - Get u_Transform.");
unsafe {
gl.use_program(Some(program));
gl.uniform_matrix_4_f32_slice(
Some(&transform_location),
false,
transform.as_ref(),
);
gl.use_program(None);
}
Self {
transform,
transform_location,
}
}
}
}
mod gradient {
use crate::program;
use crate::triangle;
use glow::{Context, HasContext, NativeProgram};
use iced_graphics::gradient::{self, Gradient};
use iced_graphics::triangle::Vertex2D;
use iced_graphics::Transformation;
#[derive(Debug)]
pub struct Program {
pub program: <Context as HasContext>::Program,
pub vertex_array: <glow::Context as HasContext>::VertexArray,
pub vertices: triangle::Buffer<Vertex2D>,
pub uniforms: Uniforms,
}
impl Program {
pub fn new(gl: &Context, shader_version: &program::Version) -> Self {
let program = unsafe {
let vertex_shader = program::Shader::vertex(
gl,
shader_version,
include_str!("shader/common/gradient.vert"),
);
let fragment_shader = program::Shader::fragment(
gl,
shader_version,
include_str!("shader/common/gradient.frag"),
);
program::create(
gl,
&[vertex_shader, fragment_shader],
&[(0, "i_Position")],
)
};
let vertex_array = unsafe {
gl.create_vertex_array().expect("Create vertex array")
};
let vertices = unsafe {
triangle::Buffer::new(
gl,
glow::ARRAY_BUFFER,
glow::DYNAMIC_DRAW,
super::DEFAULT_VERTICES,
)
};
unsafe {
gl.bind_vertex_array(Some(vertex_array));
let stride = std::mem::size_of::<Vertex2D>() as i32;
gl.enable_vertex_attrib_array(0);
gl.vertex_attrib_pointer_f32(
0,
2,
glow::FLOAT,
false,
stride,
0,
);
gl.bind_vertex_array(None);
};
Self {
program,
vertex_array,
vertices,
uniforms: Uniforms::new(gl, program),
}
}
}
#[derive(Debug)]
pub struct Uniforms {
pub gradient: Gradient,
pub transform: Transformation,
pub locations: Locations,
}
#[derive(Debug)]
pub struct Locations {
pub gradient_direction: <Context as HasContext>::UniformLocation,
pub color_stops_size: <Context as HasContext>::UniformLocation,
//currently the maximum number of stops is 16 due to lack of SSBO in GL2.1
pub color_stops: <Context as HasContext>::UniformLocation,
pub transform: <Context as HasContext>::UniformLocation,
}
impl Uniforms {
fn new(gl: &Context, program: NativeProgram) -> Self {
let gradient_direction = unsafe {
gl.get_uniform_location(program, "gradient_direction")
}
.expect("Gradient - Get gradient_direction.");
let color_stops_size =
unsafe { gl.get_uniform_location(program, "color_stops_size") }
.expect("Gradient - Get color_stops_size.");
let color_stops = unsafe {
gl.get_uniform_location(program, "color_stops")
.expect("Gradient - Get color_stops.")
};
let transform = Transformation::identity();
let transform_location =
unsafe { gl.get_uniform_location(program, "u_Transform") }
.expect("Solid - Get u_Transform.");
unsafe {
gl.use_program(Some(program));
gl.uniform_matrix_4_f32_slice(
Some(&transform_location),
false,
transform.as_ref(),
);
gl.use_program(None);
}
Self {
gradient: Gradient::Linear(gradient::Linear {
start: Default::default(),
end: Default::default(),
color_stops: vec![],
}),
transform: Transformation::identity(),
locations: Locations {
gradient_direction,
color_stops_size,
color_stops,
transform: transform_location,
},
}
}
}
}

View file

@ -1,4 +0,0 @@
//! Display rendering results on windows.
mod compositor;
pub use compositor::Compositor;

View file

@ -1,111 +0,0 @@
use crate::{Backend, Color, Error, Renderer, Settings, Viewport};
use glow::HasContext;
use iced_graphics::{compositor, Antialiasing, Size};
use core::ffi::c_void;
use std::marker::PhantomData;
/// A window graphics backend for iced powered by `glow`.
#[allow(missing_debug_implementations)]
pub struct Compositor<Theme> {
gl: glow::Context,
theme: PhantomData<Theme>,
}
impl<Theme> iced_graphics::window::GLCompositor for Compositor<Theme> {
type Settings = Settings;
type Renderer = Renderer<Theme>;
unsafe fn new(
settings: Self::Settings,
loader_function: impl FnMut(&str) -> *const c_void,
) -> Result<(Self, Self::Renderer), Error> {
let gl = glow::Context::from_loader_function(loader_function);
log::info!("{:#?}", settings);
let version = gl.version();
log::info!(
"OpenGL version: {:?} (Embedded: {})",
version,
version.is_embedded
);
let renderer = gl.get_parameter_string(glow::RENDERER);
log::info!("Renderer: {}", renderer);
// Enable auto-conversion from/to sRGB
gl.enable(glow::FRAMEBUFFER_SRGB);
// Enable alpha blending
gl.enable(glow::BLEND);
gl.blend_func_separate(
glow::SRC_ALPHA,
glow::ONE_MINUS_SRC_ALPHA,
glow::ONE,
glow::ONE_MINUS_SRC_ALPHA,
);
// Disable multisampling by default
gl.disable(glow::MULTISAMPLE);
let renderer = Renderer::new(Backend::new(&gl, settings));
Ok((
Self {
gl,
theme: PhantomData,
},
renderer,
))
}
fn sample_count(settings: &Settings) -> u32 {
settings
.antialiasing
.map(Antialiasing::sample_count)
.unwrap_or(0)
}
fn resize_viewport(&mut self, physical_size: Size<u32>) {
unsafe {
self.gl.viewport(
0,
0,
physical_size.width as i32,
physical_size.height as i32,
);
}
}
fn fetch_information(&self) -> compositor::Information {
let adapter = unsafe { self.gl.get_parameter_string(glow::RENDERER) };
compositor::Information {
backend: format!("{:?}", self.gl.version()),
adapter,
}
}
fn present<T: AsRef<str>>(
&mut self,
renderer: &mut Self::Renderer,
viewport: &Viewport,
color: Color,
overlay: &[T],
) {
let gl = &self.gl;
let [r, g, b, a] = color.into_linear();
unsafe {
gl.clear_color(r, g, b, a);
gl.clear(glow::COLOR_BUFFER_BIT);
}
renderer.with_primitives(|backend, primitive| {
backend.present(gl, primitive, viewport, overlay);
});
}
}

View file

@ -1,42 +0,0 @@
[package]
name = "iced_glutin"
version = "0.7.0"
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
edition = "2021"
description = "A glutin runtime for Iced"
license = "MIT"
repository = "https://github.com/iced-rs/iced"
documentation = "https://docs.rs/iced_glutin"
keywords = ["gui", "ui", "graphics", "interface", "widgets"]
categories = ["gui"]
[features]
trace = ["iced_winit/trace"]
debug = ["iced_winit/debug"]
system = ["iced_winit/system"]
[dependencies]
log = "0.4"
[dependencies.glutin]
version = "0.29"
git = "https://github.com/iced-rs/glutin"
rev = "da8d291486b4c9bec12487a46c119c4b1d386abf"
[dependencies.iced_native]
version = "0.9"
path = "../native"
[dependencies.iced_winit]
version = "0.8"
path = "../winit"
features = ["application"]
[dependencies.iced_graphics]
version = "0.7"
path = "../graphics"
features = ["opengl"]
[dependencies.tracing]
version = "0.1.6"
optional = true

View file

@ -1,29 +0,0 @@
# `iced_glutin`
[![Documentation](https://docs.rs/iced_glutin/badge.svg)][documentation]
[![Crates.io](https://img.shields.io/crates/v/iced_glutin.svg)](https://crates.io/crates/iced_glutin)
[![License](https://img.shields.io/crates/l/iced_glutin.svg)](https://github.com/iced-rs/iced/blob/master/LICENSE)
[![Discord Server](https://img.shields.io/discord/628993209984614400?label=&labelColor=6A7EC2&logo=discord&logoColor=ffffff&color=7389D8)](https://discord.gg/3xZJ65GAhd)
`iced_glutin` offers some convenient abstractions on top of [`iced_native`] to quickstart development when using [`glutin`].
It exposes a renderer-agnostic `Application` trait that can be implemented and then run with a simple call. The use of this trait is optional. A `conversion` module is provided for users that decide to implement a custom event loop.
<p align="center">
<img alt="The native target" src="../docs/graphs/native.png" width="80%">
</p>
[documentation]: https://docs.rs/iced_glutin
[`iced_native`]: ../native
[`glutin`]: https://github.com/rust-windowing/glutin
## Installation
Add `iced_glutin` as a dependency in your `Cargo.toml`:
```toml
iced_glutin = "0.7"
```
__Iced moves fast and the `master` branch can contain breaking changes!__ If
you want to learn about a specific release, check out [the release list].
[the release list]: https://github.com/iced-rs/iced/releases

View file

@ -1,508 +0,0 @@
//! Create interactive, native cross-platform applications.
use crate::mouse;
use crate::{Error, Executor, Runtime};
pub use iced_winit::application::StyleSheet;
pub use iced_winit::Application;
use iced_graphics::window;
use iced_winit::application;
use iced_winit::conversion;
use iced_winit::futures;
use iced_winit::futures::channel::mpsc;
use iced_winit::renderer;
use iced_winit::time::Instant;
use iced_winit::user_interface;
use iced_winit::{Clipboard, Command, Debug, Event, Proxy, Settings};
use glutin::window::Window;
use std::mem::ManuallyDrop;
#[cfg(feature = "tracing")]
use tracing::{info_span, instrument::Instrument};
/// Runs an [`Application`] with an executor, compositor, and the provided
/// settings.
pub fn run<A, E, C>(
settings: Settings<A::Flags>,
compositor_settings: C::Settings,
) -> Result<(), Error>
where
A: Application + 'static,
E: Executor + 'static,
C: window::GLCompositor<Renderer = A::Renderer> + 'static,
<A::Renderer as iced_native::Renderer>::Theme: StyleSheet,
{
use futures::task;
use futures::Future;
use glutin::event_loop::EventLoopBuilder;
use glutin::platform::run_return::EventLoopExtRunReturn;
use glutin::ContextBuilder;
#[cfg(feature = "trace")]
let _guard = iced_winit::Profiler::init();
let mut debug = Debug::new();
debug.startup_started();
#[cfg(feature = "tracing")]
let _ = info_span!("Application::Glutin", "RUN").entered();
let mut event_loop = EventLoopBuilder::with_user_event().build();
let proxy = event_loop.create_proxy();
let runtime = {
let executor = E::new().map_err(Error::ExecutorCreationFailed)?;
let proxy = Proxy::new(event_loop.create_proxy());
Runtime::new(executor, proxy)
};
let (application, init_command) = {
let flags = settings.flags;
runtime.enter(|| A::new(flags))
};
let context = {
let builder = settings.window.into_builder(
&application.title(),
event_loop.primary_monitor(),
settings.id,
);
log::debug!("Window builder: {:#?}", builder);
let opengl_builder = ContextBuilder::new()
.with_vsync(true)
.with_multisampling(C::sample_count(&compositor_settings) as u16);
let opengles_builder = opengl_builder.clone().with_gl(
glutin::GlRequest::Specific(glutin::Api::OpenGlEs, (2, 0)),
);
let (first_builder, second_builder) = if settings.try_opengles_first {
(opengles_builder, opengl_builder)
} else {
(opengl_builder, opengles_builder)
};
log::info!("Trying first builder: {:#?}", first_builder);
let context = first_builder
.build_windowed(builder.clone(), &event_loop)
.or_else(|_| {
log::info!("Trying second builder: {:#?}", second_builder);
second_builder.build_windowed(builder, &event_loop)
})
.map_err(|error| {
use glutin::CreationError;
use iced_graphics::Error as ContextError;
match error {
CreationError::Window(error) => {
Error::WindowCreationFailed(error)
}
CreationError::OpenGlVersionNotSupported => {
Error::GraphicsCreationFailed(
ContextError::VersionNotSupported,
)
}
CreationError::NoAvailablePixelFormat => {
Error::GraphicsCreationFailed(
ContextError::NoAvailablePixelFormat,
)
}
error => Error::GraphicsCreationFailed(
ContextError::BackendError(error.to_string()),
),
}
})?;
#[allow(unsafe_code)]
unsafe {
context.make_current().expect("Make OpenGL context current")
}
};
#[allow(unsafe_code)]
let (compositor, renderer) = unsafe {
C::new(compositor_settings, |address| {
context.get_proc_address(address)
})?
};
let (mut event_sender, event_receiver) = mpsc::unbounded();
let (control_sender, mut control_receiver) = mpsc::unbounded();
let mut instance = Box::pin({
let run_instance = run_instance::<A, E, C>(
application,
compositor,
renderer,
runtime,
proxy,
debug,
event_receiver,
control_sender,
context,
init_command,
settings.exit_on_close_request,
);
#[cfg(feature = "tracing")]
let run_instance =
run_instance.instrument(info_span!("Application", "LOOP"));
run_instance
});
let mut context = task::Context::from_waker(task::noop_waker_ref());
let _ = event_loop.run_return(move |event, _, control_flow| {
use glutin::event_loop::ControlFlow;
if let ControlFlow::ExitWithCode(_) = control_flow {
return;
}
let event = match event {
glutin::event::Event::WindowEvent {
event:
glutin::event::WindowEvent::ScaleFactorChanged {
new_inner_size,
..
},
window_id,
} => Some(glutin::event::Event::WindowEvent {
event: glutin::event::WindowEvent::Resized(*new_inner_size),
window_id,
}),
_ => event.to_static(),
};
if let Some(event) = event {
event_sender.start_send(event).expect("Send event");
let poll = instance.as_mut().poll(&mut context);
match poll {
task::Poll::Pending => {
if let Ok(Some(flow)) = control_receiver.try_next() {
*control_flow = flow;
}
}
task::Poll::Ready(_) => {
*control_flow = ControlFlow::Exit;
}
}
}
});
Ok(())
}
async fn run_instance<A, E, C>(
mut application: A,
mut compositor: C,
mut renderer: A::Renderer,
mut runtime: Runtime<E, Proxy<A::Message>, A::Message>,
mut proxy: glutin::event_loop::EventLoopProxy<A::Message>,
mut debug: Debug,
mut event_receiver: mpsc::UnboundedReceiver<
glutin::event::Event<'_, A::Message>,
>,
mut control_sender: mpsc::UnboundedSender<glutin::event_loop::ControlFlow>,
mut context: glutin::ContextWrapper<glutin::PossiblyCurrent, Window>,
init_command: Command<A::Message>,
exit_on_close_request: bool,
) where
A: Application + 'static,
E: Executor + 'static,
C: window::GLCompositor<Renderer = A::Renderer> + 'static,
<A::Renderer as iced_native::Renderer>::Theme: StyleSheet,
{
use glutin::event;
use glutin::event_loop::ControlFlow;
use iced_winit::futures::stream::StreamExt;
let mut clipboard = Clipboard::connect(context.window());
let mut cache = user_interface::Cache::default();
let mut state = application::State::new(&application, context.window());
let mut viewport_version = state.viewport_version();
let mut should_exit = false;
application::run_command(
&application,
&mut cache,
&state,
&mut renderer,
init_command,
&mut runtime,
&mut clipboard,
&mut should_exit,
&mut proxy,
&mut debug,
context.window(),
|| compositor.fetch_information(),
);
runtime.track(application.subscription());
let mut user_interface =
ManuallyDrop::new(application::build_user_interface(
&application,
user_interface::Cache::default(),
&mut renderer,
state.logical_size(),
&mut debug,
));
let mut mouse_interaction = mouse::Interaction::default();
let mut events = Vec::new();
let mut messages = Vec::new();
let mut redraw_pending = false;
debug.startup_finished();
while let Some(event) = event_receiver.next().await {
match event {
event::Event::NewEvents(start_cause) => {
redraw_pending = matches!(
start_cause,
event::StartCause::Init
| event::StartCause::Poll
| event::StartCause::ResumeTimeReached { .. }
);
}
event::Event::MainEventsCleared => {
if !redraw_pending && events.is_empty() && messages.is_empty() {
continue;
}
debug.event_processing_started();
let (interface_state, statuses) = user_interface.update(
&events,
state.cursor_position(),
&mut renderer,
&mut clipboard,
&mut messages,
);
debug.event_processing_finished();
for event in events.drain(..).zip(statuses.into_iter()) {
runtime.broadcast(event);
}
if !messages.is_empty()
|| matches!(
interface_state,
user_interface::State::Outdated
)
{
let mut cache =
ManuallyDrop::into_inner(user_interface).into_cache();
// Update application
application::update(
&mut application,
&mut cache,
&state,
&mut renderer,
&mut runtime,
&mut clipboard,
&mut should_exit,
&mut proxy,
&mut debug,
&mut messages,
context.window(),
|| compositor.fetch_information(),
);
// Update window
state.synchronize(&application, context.window());
user_interface =
ManuallyDrop::new(application::build_user_interface(
&application,
cache,
&mut renderer,
state.logical_size(),
&mut debug,
));
if should_exit {
break;
}
}
// TODO: Avoid redrawing all the time by forcing widgets to
// request redraws on state changes
//
// Then, we can use the `interface_state` here to decide if a redraw
// is needed right away, or simply wait until a specific time.
let redraw_event = Event::Window(
crate::window::Event::RedrawRequested(Instant::now()),
);
let (interface_state, _) = user_interface.update(
&[redraw_event.clone()],
state.cursor_position(),
&mut renderer,
&mut clipboard,
&mut messages,
);
debug.draw_started();
let new_mouse_interaction = user_interface.draw(
&mut renderer,
state.theme(),
&renderer::Style {
text_color: state.text_color(),
},
state.cursor_position(),
);
debug.draw_finished();
if new_mouse_interaction != mouse_interaction {
context.window().set_cursor_icon(
conversion::mouse_interaction(new_mouse_interaction),
);
mouse_interaction = new_mouse_interaction;
}
context.window().request_redraw();
runtime
.broadcast((redraw_event, crate::event::Status::Ignored));
let _ = control_sender.start_send(match interface_state {
user_interface::State::Updated {
redraw_request: Some(redraw_request),
} => match redraw_request {
crate::window::RedrawRequest::NextFrame => {
ControlFlow::Poll
}
crate::window::RedrawRequest::At(at) => {
ControlFlow::WaitUntil(at)
}
},
_ => ControlFlow::Wait,
});
redraw_pending = false;
}
event::Event::PlatformSpecific(event::PlatformSpecific::MacOS(
event::MacOS::ReceivedUrl(url),
)) => {
use iced_native::event;
events.push(iced_native::Event::PlatformSpecific(
event::PlatformSpecific::MacOS(event::MacOS::ReceivedUrl(
url,
)),
));
}
event::Event::UserEvent(message) => {
messages.push(message);
}
event::Event::RedrawRequested(_) => {
#[cfg(feature = "tracing")]
let _ = info_span!("Application", "FRAME").entered();
debug.render_started();
#[allow(unsafe_code)]
unsafe {
if !context.is_current() {
context = context
.make_current()
.expect("Make OpenGL context current");
}
}
let current_viewport_version = state.viewport_version();
if viewport_version != current_viewport_version {
let physical_size = state.physical_size();
let logical_size = state.logical_size();
debug.layout_started();
user_interface = ManuallyDrop::new(
ManuallyDrop::into_inner(user_interface)
.relayout(logical_size, &mut renderer),
);
debug.layout_finished();
debug.draw_started();
let new_mouse_interaction = user_interface.draw(
&mut renderer,
state.theme(),
&renderer::Style {
text_color: state.text_color(),
},
state.cursor_position(),
);
debug.draw_finished();
if new_mouse_interaction != mouse_interaction {
context.window().set_cursor_icon(
conversion::mouse_interaction(
new_mouse_interaction,
),
);
mouse_interaction = new_mouse_interaction;
}
context.resize(glutin::dpi::PhysicalSize::new(
physical_size.width,
physical_size.height,
));
compositor.resize_viewport(physical_size);
viewport_version = current_viewport_version;
}
compositor.present(
&mut renderer,
state.viewport(),
state.background_color(),
&debug.overlay(),
);
context.swap_buffers().expect("Swap buffers");
debug.render_finished();
// TODO: Handle animations!
// Maybe we can use `ControlFlow::WaitUntil` for this.
}
event::Event::WindowEvent {
event: window_event,
..
} => {
if application::requests_exit(&window_event, state.modifiers())
&& exit_on_close_request
{
break;
}
state.update(context.window(), &window_event, &mut debug);
if let Some(event) = conversion::window_event(
&window_event,
state.scale_factor(),
state.modifiers(),
) {
events.push(event);
}
}
_ => {}
}
}
// Manually drop the user interface
drop(ManuallyDrop::into_inner(user_interface));
}

View file

@ -1,33 +0,0 @@
//! A windowing shell for [`iced`], on top of [`glutin`].
//!
//! ![The native path of the Iced ecosystem](https://github.com/iced-rs/iced/blob/0525d76ff94e828b7b21634fa94a747022001c83/docs/graphs/native.png?raw=true)
//!
//! [`iced`]: https://github.com/iced-rs/iced
//! [`glutin`]: https://github.com/rust-windowing/glutin
#![doc(
html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg"
)]
#![deny(
missing_docs,
missing_debug_implementations,
unsafe_code,
unused_results,
clippy::extra_unused_lifetimes,
clippy::from_over_into,
clippy::needless_borrow,
clippy::new_without_default,
clippy::useless_conversion
)]
#![forbid(rust_2018_idioms)]
#![allow(clippy::inherent_to_string, clippy::type_complexity)]
#![cfg_attr(docsrs, feature(doc_cfg))]
pub use glutin;
#[doc(no_inline)]
pub use iced_winit::*;
pub mod application;
#[doc(no_inline)]
pub use application::Application;

View file

@ -26,9 +26,6 @@ dds = ["image_rs/dds"]
farbfeld = ["image_rs/farbfeld"]
canvas = ["lyon"]
qr_code = ["qrcode", "canvas"]
font-source = ["font-kit"]
font-fallback = []
font-icons = []
opengl = []
image_rs = ["kamadak-exif"]
@ -60,10 +57,6 @@ version = "0.12"
optional = true
default-features = false
[dependencies.font-kit]
version = "0.10"
optional = true
[dependencies.image_rs]
version = "0.24"
package = "image"

Binary file not shown.

View file

@ -1,93 +0,0 @@
Copyright (c) 2010-2014 by tyPoland Lukasz Dziedzic (team@latofonts.com) with Reserved Font Name "Lato"
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

View file

@ -4,6 +4,8 @@ use iced_native::svg;
use iced_native::text;
use iced_native::{Font, Point, Size};
use std::borrow::Cow;
/// The graphics backend of a [`Renderer`].
///
/// [`Renderer`]: crate::Renderer
@ -31,6 +33,9 @@ pub trait Text {
/// [`ICON_FONT`]: Self::ICON_FONT
const ARROW_DOWN_ICON: char;
/// Returns the default [`Font`].
fn default_font(&self) -> Font;
/// Returns the default size of text.
fn default_size(&self) -> f32;
@ -61,6 +66,9 @@ pub trait Text {
point: Point,
nearest_only: bool,
) -> Option<text::Hit>;
/// Loads a [`Font`] from its bytes.
fn load_font(&mut self, font: Cow<'static, [u8]>);
}
/// A graphics backend that supports image rendering.

View file

@ -1,35 +0,0 @@
//! Find system fonts or use the built-in ones.
#[cfg(feature = "font-source")]
mod source;
#[cfg(feature = "font-source")]
#[cfg_attr(docsrs, doc(cfg(feature = "font-source")))]
pub use source::Source;
#[cfg(feature = "font-source")]
#[cfg_attr(docsrs, doc(cfg(feature = "font-source")))]
pub use font_kit::{
error::SelectionError as LoadError, family_name::FamilyName as Family,
};
/// A built-in fallback font, for convenience.
#[cfg(feature = "font-fallback")]
#[cfg_attr(docsrs, doc(cfg(feature = "font-fallback")))]
pub const FALLBACK: &[u8] = include_bytes!("../fonts/Lato-Regular.ttf");
/// A built-in icon font, for convenience.
#[cfg(feature = "font-icons")]
#[cfg_attr(docsrs, doc(cfg(feature = "font-icons")))]
pub const ICONS: iced_native::Font = iced_native::Font::External {
name: "iced_wgpu icons",
bytes: include_bytes!("../fonts/Icons.ttf"),
};
/// The `char` representing a ✔ icon in the built-in [`ICONS`] font.
#[cfg(feature = "font-icons")]
#[cfg_attr(docsrs, doc(cfg(feature = "font-icons")))]
pub const CHECKMARK_ICON: char = '\u{F00C}';
/// The `char` representing a ▼ icon in the built-in [`ICONS`] font.
#[cfg(feature = "font-icons")]
pub const ARROW_DOWN_ICON: char = '\u{E800}';

View file

@ -1,45 +0,0 @@
use crate::font::{Family, LoadError};
/// A font source that can find and load system fonts.
#[allow(missing_debug_implementations)]
pub struct Source {
raw: font_kit::source::SystemSource,
}
impl Source {
/// Creates a new [`Source`].
pub fn new() -> Self {
Source {
raw: font_kit::source::SystemSource::new(),
}
}
/// Finds and loads a font matching the set of provided family priorities.
pub fn load(&self, families: &[Family]) -> Result<Vec<u8>, LoadError> {
let font = self.raw.select_best_match(
families,
&font_kit::properties::Properties::default(),
)?;
match font {
font_kit::handle::Handle::Path { path, .. } => {
use std::io::Read;
let mut buf = Vec::new();
let mut reader = std::fs::File::open(path).expect("Read font");
let _ = reader.read_to_end(&mut buf);
Ok(buf)
}
font_kit::handle::Handle::Memory { bytes, .. } => {
Ok(bytes.as_ref().clone())
}
}
}
}
impl Default for Source {
fn default() -> Self {
Self::new()
}
}

View file

@ -12,7 +12,8 @@ pub use text::Text;
use crate::alignment;
use crate::{
Background, Font, Point, Primitive, Rectangle, Size, Vector, Viewport,
Background, Color, Font, Point, Primitive, Rectangle, Size, Vector,
Viewport,
};
/// A group of primitives that should be clipped together.
@ -60,9 +61,9 @@ impl<'a> Layer<'a> {
Point::new(11.0, 11.0 + 25.0 * i as f32),
Size::INFINITY,
),
color: [0.9, 0.9, 0.9, 1.0],
color: Color::new(0.9, 0.9, 0.9, 1.0),
size: 20.0,
font: Font::Default,
font: Font::Monospace,
horizontal_alignment: alignment::Horizontal::Left,
vertical_alignment: alignment::Vertical::Top,
};
@ -71,7 +72,7 @@ impl<'a> Layer<'a> {
overlay.text.push(Text {
bounds: text.bounds + Vector::new(-1.0, -1.0),
color: [0.0, 0.0, 0.0, 1.0],
color: Color::BLACK,
..text
});
}
@ -136,7 +137,7 @@ impl<'a> Layer<'a> {
content,
bounds: *bounds + translation,
size: *size,
color: color.into_linear(),
color: *color,
font: *font,
horizontal_alignment: *horizontal_alignment,
vertical_alignment: *vertical_alignment,

View file

@ -1,4 +1,4 @@
use crate::{alignment, Font, Rectangle};
use crate::{alignment, Color, Font, Rectangle};
/// A paragraph of text.
#[derive(Debug, Clone, Copy)]
@ -10,7 +10,7 @@ pub struct Text<'a> {
pub bounds: Rectangle,
/// The color of the [`Text`], in __linear RGB_.
pub color: [f32; 4],
pub color: Color,
/// The size of the [`Text`].
pub size: f32,

View file

@ -28,7 +28,6 @@ mod transformation;
mod viewport;
pub mod backend;
pub mod font;
pub mod gradient;
pub mod image;
pub mod layer;

View file

@ -10,6 +10,7 @@ use iced_native::{Background, Color, Element, Font, Point, Rectangle, Size};
pub use iced_native::renderer::Style;
use std::borrow::Cow;
use std::marker::PhantomData;
/// A backend-agnostic renderer that supports all the built-in widgets.
@ -130,6 +131,10 @@ where
const CHECKMARK_ICON: char = B::CHECKMARK_ICON;
const ARROW_DOWN_ICON: char = B::ARROW_DOWN_ICON;
fn default_font(&self) -> Self::Font {
self.backend().default_font()
}
fn default_size(&self) -> f32 {
self.backend().default_size()
}
@ -163,6 +168,10 @@ where
)
}
fn load_font(&mut self, bytes: Cow<'static, [u8]>) {
self.backend.load_font(bytes);
}
fn fill_text(&mut self, text: Text<'_, Self::Font>) {
self.primitives.push(Primitive::Text {
content: text.content.to_string(),

View file

@ -34,7 +34,7 @@ impl Default for Text {
position: Point::ORIGIN,
color: Color::BLACK,
size: 16.0,
font: Font::Default,
font: Font::SansSerif,
horizontal_alignment: alignment::Horizontal::Left,
vertical_alignment: alignment::Vertical::Top,
}

View file

@ -1,10 +1,12 @@
use crate::clipboard;
use crate::font;
use crate::system;
use crate::widget;
use crate::window;
use iced_futures::MaybeSend;
use std::borrow::Cow;
use std::fmt;
/// An action that a [`Command`] can perform.
@ -27,6 +29,15 @@ pub enum Action<T> {
/// Run a widget action.
Widget(widget::Action<T>),
/// Load a font from its bytes.
LoadFont {
/// The bytes of the font to load.
bytes: Cow<'static, [u8]>,
/// The message to produce when the font has been loaded.
tagger: Box<dyn Fn(Result<(), font::Error>) -> T>,
},
}
impl<T> Action<T> {
@ -49,6 +60,10 @@ impl<T> Action<T> {
Self::Window(window) => Action::Window(window.map(f)),
Self::System(system) => Action::System(system.map(f)),
Self::Widget(widget) => Action::Widget(widget.map(f)),
Self::LoadFont { bytes, tagger } => Action::LoadFont {
bytes,
tagger: Box::new(move |result| f(tagger(result))),
},
}
}
}
@ -63,6 +78,7 @@ impl<T> fmt::Debug for Action<T> {
Self::Window(action) => write!(f, "Action::Window({action:?})"),
Self::System(action) => write!(f, "Action::System({action:?})"),
Self::Widget(_action) => write!(f, "Action::Widget"),
Self::LoadFont { .. } => write!(f, "Action::LoadFont"),
}
}
}

19
native/src/font.rs Normal file
View file

@ -0,0 +1,19 @@
//! Load and use fonts.
pub use iced_core::font::*;
use crate::command::{self, Command};
use std::borrow::Cow;
/// An error while loading a font.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Error {}
/// Load a font from its bytes.
pub fn load(
bytes: impl Into<Cow<'static, [u8]>>,
) -> Command<Result<(), Error>> {
Command::single(command::Action::LoadFont {
bytes: bytes.into(),
tagger: Box::new(std::convert::identity),
})
}

View file

@ -47,6 +47,7 @@
pub mod clipboard;
pub mod command;
pub mod event;
pub mod font;
pub mod image;
pub mod keyboard;
pub mod layout;
@ -80,8 +81,8 @@ mod debug;
pub use iced_core::alignment;
pub use iced_core::time;
pub use iced_core::{
color, Alignment, Background, Color, ContentFit, Font, Length, Padding,
Pixels, Point, Rectangle, Size, Vector,
color, Alignment, Background, Color, ContentFit, Length, Padding, Pixels,
Point, Rectangle, Size, Vector,
};
pub use iced_futures::{executor, futures};
pub use iced_style::application;
@ -95,6 +96,7 @@ pub use command::Command;
pub use debug::Debug;
pub use element::Element;
pub use event::Event;
pub use font::Font;
pub use hasher::Hasher;
pub use layout::Layout;
pub use overlay::Overlay;

View file

@ -31,7 +31,7 @@ where
width: f32,
padding: Padding,
text_size: Option<f32>,
font: Renderer::Font,
font: Option<Renderer::Font>,
style: <Renderer::Theme as StyleSheet>::Style,
}
@ -58,7 +58,7 @@ where
width: 0.0,
padding: Padding::ZERO,
text_size: None,
font: Default::default(),
font: None,
style: Default::default(),
}
}
@ -82,8 +82,8 @@ where
}
/// Sets the font of the [`Menu`].
pub fn font(mut self, font: Renderer::Font) -> Self {
self.font = font;
pub fn font(mut self, font: impl Into<Renderer::Font>) -> Self {
self.font = Some(font.into());
self
}
@ -311,7 +311,7 @@ where
last_selection: &'a mut Option<T>,
padding: Padding,
text_size: Option<f32>,
font: Renderer::Font,
font: Option<Renderer::Font>,
style: <Renderer::Theme as StyleSheet>::Style,
}
@ -344,7 +344,7 @@ where
let size = {
let intrinsic = Size::new(
0.0,
(text_size + self.padding.vertical())
(text_size * 1.2 + self.padding.vertical())
* self.options.len() as f32,
);
@ -386,7 +386,7 @@ where
*self.hovered_option = Some(
((cursor_position.y - bounds.y)
/ (text_size + self.padding.vertical()))
/ (text_size * 1.2 + self.padding.vertical()))
as usize,
);
}
@ -401,7 +401,7 @@ where
*self.hovered_option = Some(
((cursor_position.y - bounds.y)
/ (text_size + self.padding.vertical()))
/ (text_size * 1.2 + self.padding.vertical()))
as usize,
);
@ -450,7 +450,8 @@ where
let text_size =
self.text_size.unwrap_or_else(|| renderer.default_size());
let option_height = (text_size + self.padding.vertical()) as usize;
let option_height =
(text_size * 1.2 + self.padding.vertical()) as usize;
let offset = viewport.y - bounds.y;
let start = (offset / option_height as f32) as usize;
@ -467,7 +468,7 @@ where
x: bounds.x,
y: bounds.y + (option_height * i) as f32,
width: bounds.width,
height: text_size + self.padding.vertical(),
height: text_size * 1.2 + self.padding.vertical(),
};
if is_selected {
@ -491,7 +492,7 @@ where
..bounds
},
size: text_size,
font: self.font.clone(),
font: self.font.unwrap_or_else(|| renderer.default_font()),
color: if is_selected {
appearance.selected_text_color
} else {

View file

@ -1,4 +1,5 @@
//! Build interactive programs using The Elm Architecture.
use crate::text;
use crate::{Command, Element, Renderer};
mod state;
@ -8,7 +9,7 @@ pub use state::State;
/// The core of a user interface application following The Elm Architecture.
pub trait Program: Sized {
/// The graphics backend to use to draw the [`Program`].
type Renderer: Renderer;
type Renderer: Renderer + text::Renderer;
/// The type of __messages__ your [`Program`] will produce.
type Message: std::fmt::Debug + Send;

View file

@ -2,6 +2,8 @@ use crate::renderer::{self, Renderer};
use crate::text::{self, Text};
use crate::{Background, Font, Point, Rectangle, Size, Theme, Vector};
use std::borrow::Cow;
/// A renderer that does nothing.
///
/// It can be useful if you are writing tests!
@ -40,14 +42,20 @@ impl Renderer for Null {
impl text::Renderer for Null {
type Font = Font;
const ICON_FONT: Font = Font::Default;
const ICON_FONT: Font = Font::SansSerif;
const CHECKMARK_ICON: char = '0';
const ARROW_DOWN_ICON: char = '0';
fn default_size(&self) -> f32 {
20.0
fn default_font(&self) -> Self::Font {
Font::SansSerif
}
fn default_size(&self) -> f32 {
16.0
}
fn load_font(&mut self, _font: Cow<'static, [u8]>) {}
fn measure(
&self,
_content: &str,

View file

@ -1,6 +1,8 @@
//! Draw and interact with text.
use crate::alignment;
use crate::{Color, Point, Rectangle, Size, Vector};
use crate::{Color, Point, Rectangle, Size};
use std::borrow::Cow;
/// A paragraph.
#[derive(Debug, Clone, Copy)]
@ -32,10 +34,6 @@ pub struct Text<'a, Font> {
pub enum Hit {
/// The point was within the bounds of the returned character index.
CharOffset(usize),
/// The provided point was not within the bounds of a glyph. The index
/// of the character with the closest centeroid position is returned,
/// as well as its delta.
NearestCharOffset(usize, Vector),
}
impl Hit {
@ -43,13 +41,6 @@ impl Hit {
pub fn cursor(self) -> usize {
match self {
Self::CharOffset(i) => i,
Self::NearestCharOffset(i, delta) => {
if delta.x > f32::EPSILON {
i + 1
} else {
i
}
}
}
}
}
@ -57,7 +48,7 @@ impl Hit {
/// A renderer capable of measuring and drawing [`Text`].
pub trait Renderer: crate::Renderer {
/// The font type used.
type Font: Default + Clone;
type Font: Copy;
/// The icon font of the backend.
const ICON_FONT: Self::Font;
@ -72,6 +63,9 @@ pub trait Renderer: crate::Renderer {
/// [`ICON_FONT`]: Self::ICON_FONT
const ARROW_DOWN_ICON: char;
/// Returns the default [`Self::Font`].
fn default_font(&self) -> Self::Font;
/// Returns the default size of [`Text`].
fn default_size(&self) -> f32;
@ -109,6 +103,9 @@ pub trait Renderer: crate::Renderer {
nearest_only: bool,
) -> Option<Hit>;
/// Loads a [`Self::Font`] from its bytes.
fn load_font(&mut self, font: Cow<'static, [u8]>);
/// Draws the given [`Text`].
fn fill_text(&mut self, text: Text<'_, Self::Font>);
}

View file

@ -55,7 +55,7 @@ where
size: f32,
spacing: f32,
text_size: Option<f32>,
font: Renderer::Font,
font: Option<Renderer::Font>,
icon: Icon<Renderer::Font>,
style: <Renderer::Theme as StyleSheet>::Style,
}
@ -91,7 +91,7 @@ where
size: Self::DEFAULT_SIZE,
spacing: Self::DEFAULT_SPACING,
text_size: None,
font: Renderer::Font::default(),
font: None,
icon: Icon {
font: Renderer::ICON_FONT,
code_point: Renderer::CHECKMARK_ICON,
@ -128,8 +128,8 @@ where
/// Sets the [`Font`] of the text of the [`Checkbox`].
///
/// [`Font`]: crate::text::Renderer::Font
pub fn font(mut self, font: Renderer::Font) -> Self {
self.font = font;
pub fn font(mut self, font: impl Into<Renderer::Font>) -> Self {
self.font = Some(font.into());
self
}
@ -175,7 +175,7 @@ where
.push(Row::new().width(self.size).height(self.size))
.push(
Text::new(&self.label)
.font(self.font.clone())
.font(self.font.unwrap_or_else(|| renderer.default_font()))
.width(self.width)
.size(
self.text_size
@ -267,12 +267,12 @@ where
code_point,
size,
} = &self.icon;
let size = size.map(f32::from).unwrap_or(bounds.height * 0.7);
let size = size.unwrap_or(bounds.height * 0.7);
if self.is_checked {
renderer.fill_text(text::Text {
content: &code_point.to_string(),
font: font.clone(),
font: *font,
size,
bounds: Rectangle {
x: bounds.center_x(),
@ -295,7 +295,7 @@ where
label_layout,
&self.label,
self.text_size,
self.font.clone(),
self.font,
widget::text::Appearance {
color: custom_style.text_color,
},

View file

@ -35,7 +35,7 @@ where
width: Length,
padding: Padding,
text_size: Option<f32>,
font: Renderer::Font,
font: Option<Renderer::Font>,
handle: Handle<Renderer::Font>,
style: <Renderer::Theme as StyleSheet>::Style,
}
@ -70,7 +70,7 @@ where
width: Length::Shrink,
padding: Self::DEFAULT_PADDING,
text_size: None,
font: Default::default(),
font: None,
handle: Default::default(),
style: Default::default(),
}
@ -101,8 +101,8 @@ where
}
/// Sets the font of the [`PickList`].
pub fn font(mut self, font: Renderer::Font) -> Self {
self.font = font;
pub fn font(mut self, font: impl Into<Renderer::Font>) -> Self {
self.font = Some(font.into());
self
}
@ -163,7 +163,7 @@ where
self.width,
self.padding,
self.text_size,
&self.font,
self.font,
self.placeholder.as_deref(),
&self.options,
)
@ -212,6 +212,7 @@ where
cursor_position: Point,
_viewport: &Rectangle,
) {
let font = self.font.unwrap_or_else(|| renderer.default_font());
draw(
renderer,
theme,
@ -219,7 +220,7 @@ where
cursor_position,
self.padding,
self.text_size,
&self.font,
font,
self.placeholder.as_deref(),
self.selected.as_ref(),
&self.handle,
@ -232,7 +233,7 @@ where
&'b mut self,
tree: &'b mut Tree,
layout: Layout<'_>,
_renderer: &Renderer,
renderer: &Renderer,
) -> Option<overlay::Element<'b, Message, Renderer>> {
let state = tree.state.downcast_mut::<State<T>>();
@ -241,7 +242,7 @@ where
state,
self.padding,
self.text_size,
self.font.clone(),
self.font.unwrap_or_else(|| renderer.default_font()),
&self.options,
self.style.clone(),
)
@ -343,7 +344,7 @@ pub fn layout<Renderer, T>(
width: Length,
padding: Padding,
text_size: Option<f32>,
font: &Renderer::Font,
font: Option<Renderer::Font>,
placeholder: Option<&str>,
options: &[T],
) -> layout::Node
@ -362,7 +363,7 @@ where
let (width, _) = renderer.measure(
label,
text_size,
font.clone(),
font.unwrap_or_else(|| renderer.default_font()),
Size::new(f32::INFINITY, f32::INFINITY),
);
@ -384,7 +385,7 @@ where
let size = {
let intrinsic =
Size::new(max_width + text_size + padding.left, text_size);
Size::new(max_width + text_size + padding.left, text_size * 1.2);
limits.resolve(intrinsic).pad(padding)
};
@ -560,7 +561,7 @@ pub fn draw<'a, T, Renderer>(
cursor_position: Point,
padding: Padding,
text_size: Option<f32>,
font: &Renderer::Font,
font: Renderer::Font,
placeholder: Option<&str>,
selected: Option<&T>,
handle: &Handle<Renderer::Font>,
@ -599,12 +600,12 @@ pub fn draw<'a, T, Renderer>(
font,
code_point,
size,
}) => Some((font.clone(), *code_point, *size)),
}) => Some((*font, *code_point, *size)),
Handle::Dynamic { open, closed } => {
if state().is_open {
Some((open.font.clone(), open.code_point, open.size))
Some((open.font, open.code_point, open.size))
} else {
Some((closed.font.clone(), closed.code_point, closed.size))
Some((closed.font, closed.code_point, closed.size))
}
}
Handle::None => None,
@ -620,12 +621,12 @@ pub fn draw<'a, T, Renderer>(
color: style.handle_color,
bounds: Rectangle {
x: bounds.x + bounds.width - padding.horizontal(),
y: bounds.center_y() - size / 2.0,
height: size,
y: bounds.center_y(),
height: size * 1.2,
..bounds
},
horizontal_alignment: alignment::Horizontal::Right,
vertical_alignment: alignment::Vertical::Top,
vertical_alignment: alignment::Vertical::Center,
});
}
@ -637,7 +638,7 @@ pub fn draw<'a, T, Renderer>(
renderer.fill_text(Text {
content: label,
size: text_size,
font: font.clone(),
font,
color: if is_selected {
style.text_color
} else {
@ -645,12 +646,12 @@ pub fn draw<'a, T, Renderer>(
},
bounds: Rectangle {
x: bounds.x + padding.left,
y: bounds.center_y() - text_size / 2.0,
y: bounds.center_y(),
width: bounds.width - padding.horizontal(),
height: text_size,
height: text_size * 1.2,
},
horizontal_alignment: alignment::Horizontal::Left,
vertical_alignment: alignment::Vertical::Top,
vertical_alignment: alignment::Vertical::Center,
});
}
}

View file

@ -53,7 +53,7 @@ where
size: f32,
spacing: f32,
text_size: Option<f32>,
font: Renderer::Font,
font: Option<Renderer::Font>,
style: <Renderer::Theme as StyleSheet>::Style,
}
@ -95,7 +95,7 @@ where
size: Self::DEFAULT_SIZE,
spacing: Self::DEFAULT_SPACING, //15
text_size: None,
font: Default::default(),
font: None,
style: Default::default(),
}
}
@ -125,8 +125,8 @@ where
}
/// Sets the text font of the [`Radio`] button.
pub fn font(mut self, font: Renderer::Font) -> Self {
self.font = font;
pub fn font(mut self, font: impl Into<Renderer::Font>) -> Self {
self.font = Some(font.into());
self
}
@ -275,7 +275,7 @@ where
label_layout,
&self.label,
self.text_size,
self.font.clone(),
self.font,
widget::text::Appearance {
color: custom_style.text_color,
},

View file

@ -37,7 +37,7 @@ where
height: Length,
horizontal_alignment: alignment::Horizontal,
vertical_alignment: alignment::Vertical,
font: Renderer::Font,
font: Option<Renderer::Font>,
style: <Renderer::Theme as StyleSheet>::Style,
}
@ -51,7 +51,7 @@ where
Text {
content: content.into(),
size: None,
font: Default::default(),
font: None,
width: Length::Shrink,
height: Length::Shrink,
horizontal_alignment: alignment::Horizontal::Left,
@ -70,7 +70,7 @@ where
///
/// [`Font`]: crate::text::Renderer::Font
pub fn font(mut self, font: impl Into<Renderer::Font>) -> Self {
self.font = font.into();
self.font = Some(font.into());
self
}
@ -138,8 +138,12 @@ where
let bounds = limits.max();
let (width, height) =
renderer.measure(&self.content, size, self.font.clone(), bounds);
let (width, height) = renderer.measure(
&self.content,
size,
self.font.unwrap_or_else(|| renderer.default_font()),
bounds,
);
let size = limits.resolve(Size::new(width, height));
@ -162,7 +166,7 @@ where
layout,
&self.content,
self.size,
self.font.clone(),
self.font,
theme.appearance(self.style),
self.horizontal_alignment,
self.vertical_alignment,
@ -186,7 +190,7 @@ pub fn draw<Renderer>(
layout: Layout<'_>,
content: &str,
size: Option<f32>,
font: Renderer::Font,
font: Option<Renderer::Font>,
appearance: Appearance,
horizontal_alignment: alignment::Horizontal,
vertical_alignment: alignment::Vertical,
@ -212,7 +216,7 @@ pub fn draw<Renderer>(
size: size.unwrap_or_else(|| renderer.default_size()),
bounds: Rectangle { x, y, ..bounds },
color: appearance.color.unwrap_or(style.text_color),
font,
font: font.unwrap_or_else(|| renderer.default_font()),
horizontal_alignment,
vertical_alignment,
});
@ -242,7 +246,7 @@ where
height: self.height,
horizontal_alignment: self.horizontal_alignment,
vertical_alignment: self.vertical_alignment,
font: self.font.clone(),
font: self.font,
style: self.style,
}
}

View file

@ -61,7 +61,7 @@ where
placeholder: String,
value: Value,
is_secure: bool,
font: Renderer::Font,
font: Option<Renderer::Font>,
width: Length,
padding: Padding,
size: Option<f32>,
@ -92,7 +92,7 @@ where
placeholder: String::from(placeholder),
value: Value::new(value),
is_secure: false,
font: Default::default(),
font: None,
width: Length::Fill,
padding: Padding::new(5.0),
size: None,
@ -129,7 +129,7 @@ where
///
/// [`Font`]: text::Renderer::Font
pub fn font(mut self, font: Renderer::Font) -> Self {
self.font = font;
self.font = Some(font);
self
}
/// Sets the width of the [`TextInput`].
@ -188,7 +188,7 @@ where
value.unwrap_or(&self.value),
&self.placeholder,
self.size,
&self.font,
self.font,
self.is_secure,
&self.style,
)
@ -258,7 +258,7 @@ where
shell,
&mut self.value,
self.size,
&self.font,
self.font,
self.is_secure,
self.on_change.as_ref(),
self.on_paste.as_deref(),
@ -286,7 +286,7 @@ where
&self.value,
&self.placeholder,
self.size,
&self.font,
self.font,
self.is_secure,
&self.style,
)
@ -385,9 +385,8 @@ where
Renderer: text::Renderer,
{
let text_size = size.unwrap_or_else(|| renderer.default_size());
let padding = padding.fit(Size::ZERO, limits.max());
let limits = limits.width(width).pad(padding).height(text_size);
let limits = limits.width(width).pad(padding).height(text_size * 1.2);
let mut text = layout::Node::new(limits.resolve(Size::ZERO));
text.move_to(Point::new(padding.left, padding.top));
@ -406,7 +405,7 @@ pub fn update<'a, Message, Renderer>(
shell: &mut Shell<'_, Message>,
value: &mut Value,
size: Option<f32>,
font: &Renderer::Font,
font: Option<Renderer::Font>,
is_secure: bool,
on_change: &dyn Fn(String) -> Message,
on_paste: Option<&dyn Fn(String) -> Message>,
@ -455,7 +454,7 @@ where
find_cursor_position(
renderer,
text_layout.bounds(),
font.clone(),
font,
size,
&value,
state,
@ -483,7 +482,7 @@ where
let position = find_cursor_position(
renderer,
text_layout.bounds(),
font.clone(),
font,
size,
value,
state,
@ -532,7 +531,7 @@ where
let position = find_cursor_position(
renderer,
text_layout.bounds(),
font.clone(),
font,
size,
&value,
state,
@ -812,7 +811,7 @@ pub fn draw<Renderer>(
value: &Value,
placeholder: &str,
size: Option<f32>,
font: &Renderer::Font,
font: Option<Renderer::Font>,
is_secure: bool,
style: &<Renderer::Theme as StyleSheet>::Style,
) where
@ -846,6 +845,7 @@ pub fn draw<Renderer>(
);
let text = value.to_string();
let font = font.unwrap_or_else(|| renderer.default_font());
let size = size.unwrap_or_else(|| renderer.default_size());
let (cursor, offset) = if let Some(focus) = &state.is_focused {
@ -858,7 +858,7 @@ pub fn draw<Renderer>(
value,
size,
position,
font.clone(),
font,
);
let is_cursor_visible = ((focus.now - focus.updated_at)
@ -899,7 +899,7 @@ pub fn draw<Renderer>(
value,
size,
left,
font.clone(),
font,
);
let (right_position, right_offset) =
@ -909,7 +909,7 @@ pub fn draw<Renderer>(
value,
size,
right,
font.clone(),
font,
);
let width = right_position - left_position;
@ -944,7 +944,7 @@ pub fn draw<Renderer>(
let text_width = renderer.measure_width(
if text.is_empty() { placeholder } else { &text },
size,
font.clone(),
font,
);
let render = |renderer: &mut Renderer| {
@ -959,7 +959,7 @@ pub fn draw<Renderer>(
} else {
theme.value_color(style)
},
font: font.clone(),
font,
bounds: Rectangle {
y: text_bounds.center_y(),
width: f32::INFINITY,
@ -1180,7 +1180,7 @@ where
fn find_cursor_position<Renderer>(
renderer: &Renderer,
text_bounds: Rectangle,
font: Renderer::Font,
font: Option<Renderer::Font>,
size: Option<f32>,
value: &Value,
state: &State,
@ -1189,21 +1189,30 @@ fn find_cursor_position<Renderer>(
where
Renderer: text::Renderer,
{
let font = font.unwrap_or_else(|| renderer.default_font());
let size = size.unwrap_or_else(|| renderer.default_size());
let offset =
offset(renderer, text_bounds, font.clone(), size, value, state);
let offset = offset(renderer, text_bounds, font, size, value, state);
let value = value.to_string();
renderer
let char_offset = renderer
.hit_test(
&value.to_string(),
&value,
size,
font,
Size::INFINITY,
Point::new(x + offset, text_bounds.height / 2.0),
true,
)
.map(text::Hit::cursor)
.map(text::Hit::cursor)?;
Some(
unicode_segmentation::UnicodeSegmentation::graphemes(
&value[..char_offset],
true,
)
.count(),
)
}
const CURSOR_BLINK_INTERVAL_MILLIS: u128 = 500;

View file

@ -42,7 +42,7 @@ where
text_size: Option<f32>,
text_alignment: alignment::Horizontal,
spacing: f32,
font: Renderer::Font,
font: Option<Renderer::Font>,
style: <Renderer::Theme as StyleSheet>::Style,
}
@ -79,7 +79,7 @@ where
text_size: None,
text_alignment: alignment::Horizontal::Left,
spacing: 0.0,
font: Renderer::Font::default(),
font: None,
style: Default::default(),
}
}
@ -117,8 +117,8 @@ where
/// Sets the [`Font`] of the text of the [`Toggler`]
///
/// [`Font`]: crate::text::Renderer::Font
pub fn font(mut self, font: Renderer::Font) -> Self {
self.font = font;
pub fn font(mut self, font: impl Into<Renderer::Font>) -> Self {
self.font = Some(font.into());
self
}
@ -160,7 +160,7 @@ where
row = row.push(
Text::new(label)
.horizontal_alignment(self.text_alignment)
.font(self.font.clone())
.font(self.font.unwrap_or_else(|| renderer.default_font()))
.width(self.width)
.size(
self.text_size
@ -243,7 +243,7 @@ where
label_layout,
label,
self.text_size,
self.font.clone(),
self.font,
Default::default(),
self.text_alignment,
alignment::Vertical::Center,

View file

@ -197,7 +197,6 @@ pub trait Application: Sized {
let renderer_settings = crate::renderer::Settings {
default_font: settings.default_font,
default_text_size: settings.default_text_size,
text_multithreading: settings.text_multithreading,
antialiasing: if settings.antialiasing {
Some(crate::renderer::settings::Antialiasing::MSAAx4)
} else {

View file

@ -182,20 +182,12 @@ pub mod touch;
pub mod widget;
pub mod window;
#[cfg(all(not(feature = "glow"), feature = "wgpu"))]
use iced_winit as runtime;
#[cfg(feature = "glow")]
use iced_glutin as runtime;
#[cfg(all(not(feature = "glow"), feature = "wgpu"))]
use iced_wgpu as renderer;
#[cfg(feature = "glow")]
use iced_glow as renderer;
use iced_winit as runtime;
pub use iced_native::theme;
pub use runtime::event;
pub use runtime::font;
pub use runtime::subscription;
pub use application::Application;
@ -203,6 +195,7 @@ pub use element::Element;
pub use error::Error;
pub use event::Event;
pub use executor::Executor;
pub use font::Font;
pub use renderer::Renderer;
pub use result::Result;
pub use sandbox::Sandbox;
@ -213,8 +206,8 @@ pub use theme::Theme;
pub use runtime::alignment;
pub use runtime::futures;
pub use runtime::{
color, Alignment, Background, Color, Command, ContentFit, Font, Length,
Padding, Point, Rectangle, Size, Vector,
color, Alignment, Background, Color, Command, ContentFit, Length, Padding,
Point, Rectangle, Size, Vector,
};
#[cfg(feature = "system")]

View file

@ -1,5 +1,6 @@
//! Configure your application.
use crate::window;
use crate::Font;
/// The settings of an application.
#[derive(Debug, Clone)]
@ -20,23 +21,16 @@ pub struct Settings<Flags> {
/// [`Application`]: crate::Application
pub flags: Flags,
/// The bytes of the font that will be used by default.
/// The default [`Font`] to be used.
///
/// If `None` is provided, a default system font will be chosen.
// TODO: Add `name` for web compatibility
pub default_font: Option<&'static [u8]>,
/// By default, it uses [`Font::SansSerif`].
pub default_font: Font,
/// The text size that will be used by default.
///
/// The default value is `20.0`.
/// The default value is `16.0`.
pub default_text_size: f32,
/// If enabled, spread text workload in multiple threads when multiple cores
/// are available.
///
/// By default, it is disabled.
pub text_multithreading: bool,
/// If set to true, the renderer will try to perform antialiasing for some
/// primitives.
///
@ -55,15 +49,6 @@ pub struct Settings<Flags> {
///
/// [`Application`]: crate::Application
pub exit_on_close_request: bool,
/// Whether the [`Application`] should try to build the context
/// using OpenGL ES first then OpenGL.
///
/// By default, it is disabled.
/// **Note:** Only works for the `glow` backend.
///
/// [`Application`]: crate::Application
pub try_opengles_first: bool,
}
impl<Flags> Settings<Flags> {
@ -79,10 +64,8 @@ impl<Flags> Settings<Flags> {
window: default_settings.window,
default_font: default_settings.default_font,
default_text_size: default_settings.default_text_size,
text_multithreading: default_settings.text_multithreading,
antialiasing: default_settings.antialiasing,
exit_on_close_request: default_settings.exit_on_close_request,
try_opengles_first: default_settings.try_opengles_first,
}
}
}
@ -96,12 +79,10 @@ where
id: None,
window: Default::default(),
flags: Default::default(),
default_font: Default::default(),
default_text_size: 20.0,
text_multithreading: false,
default_font: Font::SansSerif,
default_text_size: 16.0,
antialiasing: false,
exit_on_close_request: true,
try_opengles_first: false,
}
}
}
@ -113,7 +94,6 @@ impl<Flags> From<Settings<Flags>> for iced_winit::Settings<Flags> {
window: settings.window.into(),
flags: settings.flags,
exit_on_close_request: settings.exit_on_close_request,
try_opengles_first: settings.try_opengles_first,
}
}
}

View file

@ -23,19 +23,27 @@ dds = ["iced_graphics/dds"]
farbfeld = ["iced_graphics/farbfeld"]
canvas = ["iced_graphics/canvas"]
qr_code = ["iced_graphics/qr_code"]
default_system_font = ["iced_graphics/font-source"]
spirv = ["wgpu/spirv"]
webgl = ["wgpu/webgl"]
[dependencies]
wgpu = "0.14"
wgpu_glyph = "0.18"
glyph_brush = "0.7"
raw-window-handle = "0.5"
log = "0.4"
guillotiere = "0.6"
futures = "0.3"
bitflags = "1.2"
once_cell = "1.0"
rustc-hash = "1.1"
ouroboros = "0.15"
[dependencies.twox-hash]
version = "1.6"
default-features = false
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.twox-hash]
version = "1.6.1"
features = ["std"]
[dependencies.bytemuck]
version = "1.9"
@ -48,7 +56,11 @@ path = "../native"
[dependencies.iced_graphics]
version = "0.7"
path = "../graphics"
features = ["font-fallback", "font-icons"]
[dependencies.glyphon]
version = "0.2"
git = "https://github.com/hecrj/glyphon.git"
rev = "65b481d758f50fd13fc21af2cc5ef62ddee64955"
[dependencies.tracing]
version = "0.1.6"

View file

@ -4,11 +4,8 @@ use crate::triangle;
use crate::{Settings, Transformation};
use iced_graphics::backend;
use iced_graphics::font;
use iced_graphics::layer::Layer;
use iced_graphics::{Primitive, Viewport};
use iced_native::alignment;
use iced_native::{Font, Size};
use iced_graphics::{Color, Font, Primitive, Size, Viewport};
#[cfg(feature = "tracing")]
use tracing::info_span;
@ -16,11 +13,13 @@ use tracing::info_span;
#[cfg(any(feature = "image", feature = "svg"))]
use crate::image;
use std::borrow::Cow;
/// A [`wgpu`] graphics backend for [`iced`].
///
/// [`wgpu`]: https://github.com/gfx-rs/wgpu-rs
/// [`iced`]: https://github.com/iced-rs/iced
#[derive(Debug)]
#[allow(missing_debug_implementations)]
pub struct Backend {
quad_pipeline: quad::Pipeline,
text_pipeline: text::Pipeline,
@ -29,6 +28,7 @@ pub struct Backend {
#[cfg(any(feature = "image", feature = "svg"))]
image_pipeline: image::Pipeline,
default_font: Font,
default_text_size: f32,
}
@ -36,16 +36,11 @@ impl Backend {
/// Creates a new [`Backend`].
pub fn new(
device: &wgpu::Device,
queue: &wgpu::Queue,
settings: Settings,
format: wgpu::TextureFormat,
) -> Self {
let text_pipeline = text::Pipeline::new(
device,
format,
settings.default_font,
settings.text_multithreading,
);
let text_pipeline = text::Pipeline::new(device, queue, format);
let quad_pipeline = quad::Pipeline::new(device, format);
let triangle_pipeline =
triangle::Pipeline::new(device, format, settings.antialiasing);
@ -61,6 +56,7 @@ impl Backend {
#[cfg(any(feature = "image", feature = "svg"))]
image_pipeline,
default_font: settings.default_font,
default_text_size: settings.default_text_size,
}
}
@ -72,8 +68,9 @@ impl Backend {
pub fn present<T: AsRef<str>>(
&mut self,
device: &wgpu::Device,
staging_belt: &mut wgpu::util::StagingBelt,
queue: &wgpu::Queue,
encoder: &mut wgpu::CommandEncoder,
clear_color: Option<Color>,
frame: &wgpu::TextureView,
primitives: &[Primitive],
viewport: &Viewport,
@ -90,168 +87,246 @@ impl Backend {
let mut layers = Layer::generate(primitives, viewport);
layers.push(Layer::overlay(overlay_text, viewport));
for layer in layers {
self.flush(
device,
scale_factor,
transformation,
&layer,
staging_belt,
encoder,
frame,
target_size,
);
}
self.prepare(
device,
queue,
encoder,
scale_factor,
transformation,
&layers,
);
while !self.prepare_text(
device,
queue,
scale_factor,
target_size,
&layers,
) {}
self.render(
device,
encoder,
frame,
clear_color,
scale_factor,
target_size,
&layers,
);
self.quad_pipeline.end_frame();
self.text_pipeline.end_frame();
self.triangle_pipeline.end_frame();
#[cfg(any(feature = "image", feature = "svg"))]
self.image_pipeline.trim_cache(device, encoder);
self.image_pipeline.end_frame(device, queue, encoder);
}
fn flush(
fn prepare_text(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
scale_factor: f32,
target_size: Size<u32>,
layers: &[Layer<'_>],
) -> bool {
for layer in layers {
let bounds = (layer.bounds * scale_factor).snap();
if bounds.width < 1 || bounds.height < 1 {
continue;
}
if !layer.text.is_empty()
&& !self.text_pipeline.prepare(
device,
queue,
&layer.text,
layer.bounds,
scale_factor,
target_size,
)
{
return false;
}
}
true
}
fn prepare(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
_encoder: &mut wgpu::CommandEncoder,
scale_factor: f32,
transformation: Transformation,
layer: &Layer<'_>,
staging_belt: &mut wgpu::util::StagingBelt,
encoder: &mut wgpu::CommandEncoder,
target: &wgpu::TextureView,
target_size: Size<u32>,
layers: &[Layer<'_>],
) {
let bounds = (layer.bounds * scale_factor).snap();
for layer in layers {
let bounds = (layer.bounds * scale_factor).snap();
if bounds.width < 1 || bounds.height < 1 {
return;
}
if bounds.width < 1 || bounds.height < 1 {
continue;
}
if !layer.quads.is_empty() {
self.quad_pipeline.draw(
device,
staging_belt,
encoder,
&layer.quads,
transformation,
scale_factor,
bounds,
target,
);
}
if !layer.meshes.is_empty() {
let scaled = transformation
* Transformation::scale(scale_factor, scale_factor);
self.triangle_pipeline.draw(
device,
staging_belt,
encoder,
target,
target_size,
scaled,
scale_factor,
&layer.meshes,
);
}
#[cfg(any(feature = "image", feature = "svg"))]
{
if !layer.images.is_empty() {
let scaled = transformation
* Transformation::scale(scale_factor, scale_factor);
self.image_pipeline.draw(
if !layer.quads.is_empty() {
self.quad_pipeline.prepare(
device,
staging_belt,
encoder,
&layer.images,
scaled,
bounds,
target,
queue,
&layer.quads,
transformation,
scale_factor,
);
}
}
if !layer.text.is_empty() {
for text in layer.text.iter() {
// Target physical coordinates directly to avoid blurry text
let text = wgpu_glyph::Section {
// TODO: We `round` here to avoid rerasterizing text when
// its position changes slightly. This can make text feel a
// bit "jumpy". We may be able to do better once we improve
// our text rendering/caching pipeline.
screen_position: (
(text.bounds.x * scale_factor).round(),
(text.bounds.y * scale_factor).round(),
),
// TODO: Fix precision issues with some scale factors.
//
// The `ceil` here can cause some words to render on the
// same line when they should not.
//
// Ideally, `wgpu_glyph` should be able to compute layout
// using logical positions, and then apply the proper
// scaling when rendering. This would ensure that both
// measuring and rendering follow the same layout rules.
bounds: (
(text.bounds.width * scale_factor).ceil(),
(text.bounds.height * scale_factor).ceil(),
),
text: vec![wgpu_glyph::Text {
text: text.content,
scale: wgpu_glyph::ab_glyph::PxScale {
x: text.size * scale_factor,
y: text.size * scale_factor,
},
font_id: self.text_pipeline.find_font(text.font),
extra: wgpu_glyph::Extra {
color: text.color,
z: 0.0,
},
}],
layout: wgpu_glyph::Layout::default()
.h_align(match text.horizontal_alignment {
alignment::Horizontal::Left => {
wgpu_glyph::HorizontalAlign::Left
}
alignment::Horizontal::Center => {
wgpu_glyph::HorizontalAlign::Center
}
alignment::Horizontal::Right => {
wgpu_glyph::HorizontalAlign::Right
}
})
.v_align(match text.vertical_alignment {
alignment::Vertical::Top => {
wgpu_glyph::VerticalAlign::Top
}
alignment::Vertical::Center => {
wgpu_glyph::VerticalAlign::Center
}
alignment::Vertical::Bottom => {
wgpu_glyph::VerticalAlign::Bottom
}
}),
};
if !layer.meshes.is_empty() {
let scaled = transformation
* Transformation::scale(scale_factor, scale_factor);
self.text_pipeline.queue(text);
self.triangle_pipeline.prepare(
device,
queue,
&layer.meshes,
scaled,
);
}
self.text_pipeline.draw_queued(
device,
staging_belt,
encoder,
target,
transformation,
wgpu_glyph::Region {
x: bounds.x,
y: bounds.y,
width: bounds.width,
height: bounds.height,
},
);
#[cfg(any(feature = "image", feature = "svg"))]
{
if !layer.images.is_empty() {
let scaled = transformation
* Transformation::scale(scale_factor, scale_factor);
self.image_pipeline.prepare(
device,
queue,
_encoder,
&layer.images,
scaled,
scale_factor,
);
}
}
}
}
fn render(
&mut self,
device: &wgpu::Device,
encoder: &mut wgpu::CommandEncoder,
target: &wgpu::TextureView,
clear_color: Option<Color>,
scale_factor: f32,
target_size: Size<u32>,
layers: &[Layer<'_>],
) {
use std::mem::ManuallyDrop;
let mut quad_layer = 0;
let mut triangle_layer = 0;
#[cfg(any(feature = "image", feature = "svg"))]
let mut image_layer = 0;
let mut text_layer = 0;
let mut render_pass = ManuallyDrop::new(encoder.begin_render_pass(
&wgpu::RenderPassDescriptor {
label: Some("iced_wgpu::quad render pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: target,
resolve_target: None,
ops: wgpu::Operations {
load: match clear_color {
Some(background_color) => wgpu::LoadOp::Clear({
let [r, g, b, a] =
background_color.into_linear();
wgpu::Color {
r: f64::from(r),
g: f64::from(g),
b: f64::from(b),
a: f64::from(a),
}
}),
None => wgpu::LoadOp::Load,
},
store: true,
},
})],
depth_stencil_attachment: None,
},
));
for layer in layers {
let bounds = (layer.bounds * scale_factor).snap();
if bounds.width < 1 || bounds.height < 1 {
return;
}
if !layer.quads.is_empty() {
self.quad_pipeline
.render(quad_layer, bounds, &mut render_pass);
quad_layer += 1;
}
if !layer.meshes.is_empty() {
let _ = ManuallyDrop::into_inner(render_pass);
self.triangle_pipeline.render(
device,
encoder,
target,
triangle_layer,
target_size,
&layer.meshes,
scale_factor,
);
triangle_layer += 1;
render_pass = ManuallyDrop::new(encoder.begin_render_pass(
&wgpu::RenderPassDescriptor {
label: Some("iced_wgpu::quad render pass"),
color_attachments: &[Some(
wgpu::RenderPassColorAttachment {
view: target,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Load,
store: true,
},
},
)],
depth_stencil_attachment: None,
},
));
}
#[cfg(any(feature = "image", feature = "svg"))]
{
if !layer.images.is_empty() {
self.image_pipeline.render(
image_layer,
bounds,
&mut render_pass,
);
image_layer += 1;
}
}
if !layer.text.is_empty() {
self.text_pipeline
.render(text_layer, bounds, &mut render_pass);
text_layer += 1;
}
}
let _ = ManuallyDrop::into_inner(render_pass);
}
}
impl iced_graphics::Backend for Backend {
@ -261,9 +336,13 @@ impl iced_graphics::Backend for Backend {
}
impl backend::Text for Backend {
const ICON_FONT: Font = font::ICONS;
const CHECKMARK_ICON: char = font::CHECKMARK_ICON;
const ARROW_DOWN_ICON: char = font::ARROW_DOWN_ICON;
const ICON_FONT: Font = Font::Name("Iced-Icons");
const CHECKMARK_ICON: char = '\u{f00c}';
const ARROW_DOWN_ICON: char = '\u{e800}';
fn default_font(&self) -> Font {
self.default_font
}
fn default_size(&self) -> f32 {
self.default_text_size
@ -297,6 +376,10 @@ impl backend::Text for Backend {
nearest_only,
)
}
fn load_font(&mut self, font: Cow<'static, [u8]>) {
self.text_pipeline.load_font(font);
}
}
#[cfg(feature = "image")]

View file

@ -1,3 +1,89 @@
//! Utilities for buffer operations.
pub mod dynamic;
pub mod r#static;
use std::marker::PhantomData;
use std::ops::RangeBounds;
#[derive(Debug)]
pub struct Buffer<T> {
label: &'static str,
size: u64,
usage: wgpu::BufferUsages,
raw: wgpu::Buffer,
type_: PhantomData<T>,
}
impl<T: bytemuck::Pod> Buffer<T> {
pub fn new(
device: &wgpu::Device,
label: &'static str,
amount: usize,
usage: wgpu::BufferUsages,
) -> Self {
let size = next_copy_size::<T>(amount);
let raw = device.create_buffer(&wgpu::BufferDescriptor {
label: Some(label),
size,
usage,
mapped_at_creation: false,
});
Self {
label,
size,
usage,
raw,
type_: PhantomData,
}
}
pub fn resize(&mut self, device: &wgpu::Device, new_count: usize) -> bool {
let new_size = (std::mem::size_of::<T>() * new_count) as u64;
if self.size < new_size {
self.raw = device.create_buffer(&wgpu::BufferDescriptor {
label: Some(self.label),
size: new_size,
usage: self.usage,
mapped_at_creation: false,
});
self.size = new_size;
true
} else {
false
}
}
pub fn write(
&self,
queue: &wgpu::Queue,
offset_count: usize,
contents: &[T],
) {
queue.write_buffer(
&self.raw,
(std::mem::size_of::<T>() * offset_count) as u64,
bytemuck::cast_slice(contents),
);
}
pub fn slice(
&self,
bounds: impl RangeBounds<wgpu::BufferAddress>,
) -> wgpu::BufferSlice<'_> {
self.raw.slice(bounds)
}
}
fn next_copy_size<T>(amount: usize) -> u64 {
let align_mask = wgpu::COPY_BUFFER_ALIGNMENT - 1;
(((std::mem::size_of::<T>() * amount).next_power_of_two() as u64
+ align_mask)
& !align_mask)
.max(wgpu::COPY_BUFFER_ALIGNMENT)
}

View file

@ -112,25 +112,8 @@ impl<T: ShaderType + WriteInto> Buffer<T> {
}
/// Write the contents of this dynamic buffer to the GPU via staging belt command.
pub fn write(
&mut self,
device: &wgpu::Device,
staging_belt: &mut wgpu::util::StagingBelt,
encoder: &mut wgpu::CommandEncoder,
) {
let size = self.cpu.get_ref().len();
if let Some(buffer_size) = wgpu::BufferSize::new(size as u64) {
let mut buffer = staging_belt.write_buffer(
encoder,
&self.gpu,
0,
buffer_size,
device,
);
buffer.copy_from_slice(self.cpu.get_ref());
}
pub fn write(&mut self, queue: &wgpu::Queue) {
queue.write_buffer(&self.gpu, 0, self.cpu.get_ref());
}
// Gets the aligned offset at the given index from the CPU buffer.
@ -184,7 +167,7 @@ impl Internal {
}
/// Returns bytearray of aligned CPU buffer.
pub(super) fn get_ref(&self) -> &Vec<u8> {
pub(super) fn get_ref(&self) -> &[u8] {
match self {
Internal::Uniform(buf) => buf.as_ref(),
#[cfg(not(target_arch = "wasm32"))]

View file

@ -2,8 +2,7 @@ use bytemuck::{Pod, Zeroable};
use std::marker::PhantomData;
use std::mem;
//128 triangles/indices
const DEFAULT_STATIC_BUFFER_COUNT: wgpu::BufferAddress = 128;
const DEFAULT_COUNT: wgpu::BufferAddress = 128;
/// A generic buffer struct useful for items which have no alignment requirements
/// (e.g. Vertex, Index buffers) & no dynamic offsets.
@ -25,7 +24,7 @@ impl<T: Pod + Zeroable> Buffer<T> {
label: &'static str,
usages: wgpu::BufferUsages,
) -> Self {
let size = (mem::size_of::<T>() as u64) * DEFAULT_STATIC_BUFFER_COUNT;
let size = (mem::size_of::<T>() as u64) * DEFAULT_COUNT;
Self {
offsets: Vec::new(),
@ -57,9 +56,13 @@ impl<T: Pod + Zeroable> Buffer<T> {
let size = (mem::size_of::<T>() * new_count) as u64;
if self.size < size {
self.size =
(mem::size_of::<T>() * (new_count + new_count / 2)) as u64;
self.gpu =
Self::gpu_buffer(device, self.label, self.size, self.usages);
self.offsets.clear();
self.size = size;
self.gpu = Self::gpu_buffer(device, self.label, size, self.usages);
true
} else {
false
@ -71,28 +74,15 @@ impl<T: Pod + Zeroable> Buffer<T> {
/// Returns the size of the written bytes.
pub fn write(
&mut self,
device: &wgpu::Device,
staging_belt: &mut wgpu::util::StagingBelt,
encoder: &mut wgpu::CommandEncoder,
queue: &wgpu::Queue,
offset: u64,
content: &[T],
) -> u64 {
let bytes = bytemuck::cast_slice(content);
let bytes_size = bytes.len() as u64;
if let Some(buffer_size) = wgpu::BufferSize::new(bytes_size) {
let mut buffer = staging_belt.write_buffer(
encoder,
&self.gpu,
offset,
buffer_size,
device,
);
buffer.copy_from_slice(bytes);
self.offsets.push(offset);
}
queue.write_buffer(&self.gpu, offset, bytes);
self.offsets.push(offset);
bytes_size
}

View file

@ -6,7 +6,7 @@ use iced_graphics::image::raster;
#[cfg(feature = "svg")]
use iced_graphics::image::vector;
use crate::Transformation;
use crate::{Buffer, Transformation};
use atlas::Atlas;
use iced_graphics::layer;
@ -34,15 +34,107 @@ pub struct Pipeline {
vector_cache: RefCell<vector::Cache<Atlas>>,
pipeline: wgpu::RenderPipeline,
uniforms: wgpu::Buffer,
vertices: wgpu::Buffer,
indices: wgpu::Buffer,
instances: wgpu::Buffer,
constants: wgpu::BindGroup,
sampler: wgpu::Sampler,
texture: wgpu::BindGroup,
texture_version: usize,
texture_layout: wgpu::BindGroupLayout,
texture_atlas: Atlas,
texture_layout: wgpu::BindGroupLayout,
constant_layout: wgpu::BindGroupLayout,
layers: Vec<Layer>,
prepare_layer: usize,
}
#[derive(Debug)]
struct Layer {
uniforms: wgpu::Buffer,
constants: wgpu::BindGroup,
instances: Buffer<Instance>,
instance_count: usize,
}
impl Layer {
fn new(
device: &wgpu::Device,
constant_layout: &wgpu::BindGroupLayout,
sampler: &wgpu::Sampler,
) -> Self {
let uniforms = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("iced_wgpu::image uniforms buffer"),
size: mem::size_of::<Uniforms>() as u64,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let constants = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("iced_wgpu::image constants bind group"),
layout: constant_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::Buffer(
wgpu::BufferBinding {
buffer: &uniforms,
offset: 0,
size: None,
},
),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(sampler),
},
],
});
let instances = Buffer::new(
device,
"iced_wgpu::image instance buffer",
Instance::INITIAL,
wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
);
Self {
uniforms,
constants,
instances,
instance_count: 0,
}
}
fn prepare(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
instances: &[Instance],
transformation: Transformation,
) {
queue.write_buffer(
&self.uniforms,
0,
bytemuck::bytes_of(&Uniforms {
transform: transformation.into(),
}),
);
let _ = self.instances.resize(device, instances.len());
self.instances.write(queue, 0, instances);
self.instance_count = instances.len();
}
fn render<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) {
render_pass.set_bind_group(0, &self.constants, &[]);
render_pass.set_vertex_buffer(1, self.instances.slice(..));
render_pass.draw_indexed(
0..QUAD_INDICES.len() as u32,
0,
0..self.instance_count as u32,
);
}
}
impl Pipeline {
@ -86,35 +178,6 @@ impl Pipeline {
],
});
let uniforms_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("iced_wgpu::image uniforms buffer"),
size: mem::size_of::<Uniforms>() as u64,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let constant_bind_group =
device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("iced_wgpu::image constants bind group"),
layout: &constant_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::Buffer(
wgpu::BufferBinding {
buffer: &uniforms_buffer,
offset: 0,
size: None,
},
),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(&sampler),
},
],
});
let texture_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("iced_wgpu::image texture atlas layout"),
@ -225,13 +288,6 @@ impl Pipeline {
usage: wgpu::BufferUsages::INDEX,
});
let instances = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("iced_wgpu::image instance buffer"),
size: mem::size_of::<Instance>() as u64 * Instance::MAX as u64,
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let texture_atlas = Atlas::new(device);
let texture = device.create_bind_group(&wgpu::BindGroupDescriptor {
@ -253,15 +309,17 @@ impl Pipeline {
vector_cache: RefCell::new(vector::Cache::default()),
pipeline,
uniforms: uniforms_buffer,
vertices,
indices,
instances,
constants: constant_bind_group,
sampler,
texture,
texture_version: texture_atlas.layer_count(),
texture_layout,
texture_atlas,
texture_layout,
constant_layout,
layers: Vec::new(),
prepare_layer: 0,
}
}
@ -281,17 +339,18 @@ impl Pipeline {
svg.viewport_dimensions()
}
pub fn draw(
pub fn prepare(
&mut self,
device: &wgpu::Device,
staging_belt: &mut wgpu::util::StagingBelt,
queue: &wgpu::Queue,
encoder: &mut wgpu::CommandEncoder,
images: &[layer::Image],
transformation: Transformation,
bounds: Rectangle<u32>,
target: &wgpu::TextureView,
_scale: f32,
) {
#[cfg(feature = "tracing")]
let _ = info_span!("Wgpu::Image", "PREPARE").entered();
#[cfg(feature = "tracing")]
let _ = info_span!("Wgpu::Image", "DRAW").entered();
@ -309,7 +368,7 @@ impl Pipeline {
layer::Image::Raster { handle, bounds } => {
if let Some(atlas_entry) = raster_cache.upload(
handle,
&mut (device, encoder),
&mut (device, queue, encoder),
&mut self.texture_atlas,
) {
add_instances(
@ -336,7 +395,7 @@ impl Pipeline {
*color,
size,
_scale,
&mut (device, encoder),
&mut (device, queue, encoder),
&mut self.texture_atlas,
) {
add_instances(
@ -376,68 +435,28 @@ impl Pipeline {
self.texture_version = texture_version;
}
{
let mut uniforms_buffer = staging_belt.write_buffer(
encoder,
&self.uniforms,
0,
wgpu::BufferSize::new(mem::size_of::<Uniforms>() as u64)
.unwrap(),
if self.layers.len() <= self.prepare_layer {
self.layers.push(Layer::new(
device,
);
uniforms_buffer.copy_from_slice(bytemuck::bytes_of(&Uniforms {
transform: transformation.into(),
}));
&self.constant_layout,
&self.sampler,
));
}
let mut i = 0;
let total = instances.len();
let layer = &mut self.layers[self.prepare_layer];
layer.prepare(device, queue, instances, transformation);
while i < total {
let end = (i + Instance::MAX).min(total);
let amount = end - i;
let mut instances_buffer = staging_belt.write_buffer(
encoder,
&self.instances,
0,
wgpu::BufferSize::new(
(amount * std::mem::size_of::<Instance>()) as u64,
)
.unwrap(),
device,
);
instances_buffer.copy_from_slice(bytemuck::cast_slice(
&instances[i..i + amount],
));
let mut render_pass =
encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("iced_wgpu::image render pass"),
color_attachments: &[Some(
wgpu::RenderPassColorAttachment {
view: target,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Load,
store: true,
},
},
)],
depth_stencil_attachment: None,
});
self.prepare_layer += 1;
}
pub fn render<'a>(
&'a self,
layer: usize,
bounds: Rectangle<u32>,
render_pass: &mut wgpu::RenderPass<'a>,
) {
if let Some(layer) = self.layers.get(layer) {
render_pass.set_pipeline(&self.pipeline);
render_pass.set_bind_group(0, &self.constants, &[]);
render_pass.set_bind_group(1, &self.texture, &[]);
render_pass.set_index_buffer(
self.indices.slice(..),
wgpu::IndexFormat::Uint16,
);
render_pass.set_vertex_buffer(0, self.vertices.slice(..));
render_pass.set_vertex_buffer(1, self.instances.slice(..));
render_pass.set_scissor_rect(
bounds.x,
@ -446,30 +465,34 @@ impl Pipeline {
bounds.height,
);
render_pass.draw_indexed(
0..QUAD_INDICES.len() as u32,
0,
0..amount as u32,
render_pass.set_bind_group(1, &self.texture, &[]);
render_pass.set_index_buffer(
self.indices.slice(..),
wgpu::IndexFormat::Uint16,
);
render_pass.set_vertex_buffer(0, self.vertices.slice(..));
i += Instance::MAX;
layer.render(render_pass);
}
}
pub fn trim_cache(
pub fn end_frame(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
encoder: &mut wgpu::CommandEncoder,
) {
#[cfg(feature = "image")]
self.raster_cache
.borrow_mut()
.trim(&mut self.texture_atlas, &mut (device, encoder));
.trim(&mut self.texture_atlas, &mut (device, queue, encoder));
#[cfg(feature = "svg")]
self.vector_cache
.borrow_mut()
.trim(&mut self.texture_atlas, &mut (device, encoder));
.trim(&mut self.texture_atlas, &mut (device, queue, encoder));
self.prepare_layer = 0;
}
}
@ -507,7 +530,7 @@ struct Instance {
}
impl Instance {
pub const MAX: usize = 1_000;
pub const INITIAL: usize = 1_000;
}
#[repr(C)]

View file

@ -185,13 +185,13 @@ impl Atlas {
fn upload_allocation(
&mut self,
buffer: &wgpu::Buffer,
data: &[u8],
image_width: u32,
image_height: u32,
padding: u32,
offset: usize,
allocation: &Allocation,
encoder: &mut wgpu::CommandEncoder,
queue: &wgpu::Queue,
) {
let (x, y) = allocation.position();
let Size { width, height } = allocation.size();
@ -203,15 +203,7 @@ impl Atlas {
depth_or_array_layers: 1,
};
encoder.copy_buffer_to_texture(
wgpu::ImageCopyBuffer {
buffer,
layout: wgpu::ImageDataLayout {
offset: offset as u64,
bytes_per_row: NonZeroU32::new(4 * image_width + padding),
rows_per_image: NonZeroU32::new(image_height),
},
},
queue.write_texture(
wgpu::ImageCopyTexture {
texture: &self.texture,
mip_level: 0,
@ -222,6 +214,12 @@ impl Atlas {
},
aspect: wgpu::TextureAspect::default(),
},
data,
wgpu::ImageDataLayout {
offset: offset as u64,
bytes_per_row: NonZeroU32::new(4 * image_width + padding),
rows_per_image: NonZeroU32::new(image_height),
},
extent,
);
}
@ -301,17 +299,19 @@ impl Atlas {
impl image::Storage for Atlas {
type Entry = Entry;
type State<'a> = (&'a wgpu::Device, &'a mut wgpu::CommandEncoder);
type State<'a> = (
&'a wgpu::Device,
&'a wgpu::Queue,
&'a mut wgpu::CommandEncoder,
);
fn upload(
&mut self,
width: u32,
height: u32,
data: &[u8],
(device, encoder): &mut Self::State<'_>,
(device, queue, encoder): &mut Self::State<'_>,
) -> Option<Self::Entry> {
use wgpu::util::DeviceExt;
let entry = {
let current_size = self.layers.len();
let entry = self.allocate(width, height)?;
@ -344,17 +344,16 @@ impl image::Storage for Atlas {
)
}
let buffer =
device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("iced_wgpu::image staging buffer"),
contents: &padded_data,
usage: wgpu::BufferUsages::COPY_SRC,
});
match &entry {
Entry::Contiguous(allocation) => {
self.upload_allocation(
&buffer, width, height, padding, 0, allocation, encoder,
&padded_data,
width,
height,
padding,
0,
allocation,
queue,
);
}
Entry::Fragmented { fragments, .. } => {
@ -363,13 +362,13 @@ impl image::Storage for Atlas {
let offset = (y * padded_width as u32 + 4 * x) as usize;
self.upload_allocation(
&buffer,
&padded_data,
width,
height,
padding,
offset,
&fragment.allocation,
encoder,
queue,
);
}
}

View file

@ -47,14 +47,17 @@ mod quad;
mod text;
mod triangle;
pub use iced_graphics::{Antialiasing, Color, Error, Primitive, Viewport};
pub use iced_graphics::{
Antialiasing, Color, Error, Font, Primitive, Viewport,
};
pub use iced_native::Theme;
pub use wgpu;
pub use backend::Backend;
pub use settings::Settings;
pub(crate) use iced_graphics::Transformation;
use crate::buffer::Buffer;
use iced_graphics::Transformation;
#[cfg(any(feature = "image", feature = "svg"))]
mod image;

View file

@ -1,4 +1,4 @@
use crate::Transformation;
use crate::{Buffer, Transformation};
use iced_graphics::layer;
use iced_native::Rectangle;
@ -12,11 +12,11 @@ use tracing::info_span;
#[derive(Debug)]
pub struct Pipeline {
pipeline: wgpu::RenderPipeline,
constants: wgpu::BindGroup,
constants_buffer: wgpu::Buffer,
constant_layout: wgpu::BindGroupLayout,
vertices: wgpu::Buffer,
indices: wgpu::Buffer,
instances: wgpu::Buffer,
layers: Vec<Layer>,
prepare_layer: usize,
}
impl Pipeline {
@ -38,22 +38,6 @@ impl Pipeline {
}],
});
let constants_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("iced_wgpu::quad uniforms buffer"),
size: mem::size_of::<Uniforms>() as wgpu::BufferAddress,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let constants = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("iced_wgpu::quad uniforms bind group"),
layout: &constant_layout,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: constants_buffer.as_entire_binding(),
}],
});
let layout =
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("iced_wgpu::quad pipeline layout"),
@ -148,117 +132,145 @@ impl Pipeline {
usage: wgpu::BufferUsages::INDEX,
});
let instances = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("iced_wgpu::quad instance buffer"),
size: mem::size_of::<layer::Quad>() as u64 * MAX_INSTANCES as u64,
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
Pipeline {
pipeline,
constants,
constants_buffer,
constant_layout,
vertices,
indices,
instances,
layers: Vec::new(),
prepare_layer: 0,
}
}
pub fn draw(
pub fn prepare(
&mut self,
device: &wgpu::Device,
staging_belt: &mut wgpu::util::StagingBelt,
encoder: &mut wgpu::CommandEncoder,
queue: &wgpu::Queue,
instances: &[layer::Quad],
transformation: Transformation,
scale: f32,
) {
if self.layers.len() <= self.prepare_layer {
self.layers.push(Layer::new(device, &self.constant_layout));
}
let layer = &mut self.layers[self.prepare_layer];
layer.prepare(device, queue, instances, transformation, scale);
self.prepare_layer += 1;
}
pub fn render<'a>(
&'a self,
layer: usize,
bounds: Rectangle<u32>,
target: &wgpu::TextureView,
render_pass: &mut wgpu::RenderPass<'a>,
) {
if let Some(layer) = self.layers.get(layer) {
render_pass.set_pipeline(&self.pipeline);
render_pass.set_scissor_rect(
bounds.x,
bounds.y,
bounds.width,
bounds.height,
);
render_pass.set_index_buffer(
self.indices.slice(..),
wgpu::IndexFormat::Uint16,
);
render_pass.set_vertex_buffer(0, self.vertices.slice(..));
layer.draw(render_pass);
}
}
pub fn end_frame(&mut self) {
self.prepare_layer = 0;
}
}
#[derive(Debug)]
struct Layer {
constants: wgpu::BindGroup,
constants_buffer: wgpu::Buffer,
instances: Buffer<layer::Quad>,
instance_count: usize,
}
impl Layer {
pub fn new(
device: &wgpu::Device,
constant_layout: &wgpu::BindGroupLayout,
) -> Self {
let constants_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("iced_wgpu::quad uniforms buffer"),
size: mem::size_of::<Uniforms>() as wgpu::BufferAddress,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let constants = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("iced_wgpu::quad uniforms bind group"),
layout: constant_layout,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: constants_buffer.as_entire_binding(),
}],
});
let instances = Buffer::new(
device,
"iced_wgpu::quad instance buffer",
INITIAL_INSTANCES,
wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
);
Self {
constants,
constants_buffer,
instances,
instance_count: 0,
}
}
pub fn prepare(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
instances: &[layer::Quad],
transformation: Transformation,
scale: f32,
) {
#[cfg(feature = "tracing")]
let _ = info_span!("Wgpu::Quad", "DRAW").entered();
let _ = info_span!("Wgpu::Quad", "PREPARE").entered();
let uniforms = Uniforms::new(transformation, scale);
{
let mut constants_buffer = staging_belt.write_buffer(
encoder,
&self.constants_buffer,
0,
wgpu::BufferSize::new(mem::size_of::<Uniforms>() as u64)
.unwrap(),
device,
);
queue.write_buffer(
&self.constants_buffer,
0,
bytemuck::bytes_of(&uniforms),
);
constants_buffer.copy_from_slice(bytemuck::bytes_of(&uniforms));
}
let _ = self.instances.resize(device, instances.len());
self.instances.write(queue, 0, instances);
self.instance_count = instances.len();
}
let mut i = 0;
let total = instances.len();
pub fn draw<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) {
#[cfg(feature = "tracing")]
let _ = info_span!("Wgpu::Quad", "DRAW").entered();
while i < total {
let end = (i + MAX_INSTANCES).min(total);
let amount = end - i;
render_pass.set_bind_group(0, &self.constants, &[]);
render_pass.set_vertex_buffer(1, self.instances.slice(..));
let instance_bytes = bytemuck::cast_slice(&instances[i..end]);
let mut instance_buffer = staging_belt.write_buffer(
encoder,
&self.instances,
0,
wgpu::BufferSize::new(instance_bytes.len() as u64).unwrap(),
device,
);
instance_buffer.copy_from_slice(instance_bytes);
#[cfg(feature = "tracing")]
let _ = info_span!("Wgpu::Quad", "BEGIN_RENDER_PASS").enter();
{
let mut render_pass =
encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("iced_wgpu::quad render pass"),
color_attachments: &[Some(
wgpu::RenderPassColorAttachment {
view: target,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Load,
store: true,
},
},
)],
depth_stencil_attachment: None,
});
render_pass.set_pipeline(&self.pipeline);
render_pass.set_bind_group(0, &self.constants, &[]);
render_pass.set_index_buffer(
self.indices.slice(..),
wgpu::IndexFormat::Uint16,
);
render_pass.set_vertex_buffer(0, self.vertices.slice(..));
render_pass.set_vertex_buffer(1, self.instances.slice(..));
render_pass.set_scissor_rect(
bounds.x,
bounds.y,
bounds.width,
// TODO: Address anti-aliasing adjustments properly
bounds.height,
);
render_pass.draw_indexed(
0..QUAD_INDICES.len() as u32,
0,
0..amount as u32,
);
}
i += MAX_INSTANCES;
}
render_pass.draw_indexed(
0..QUAD_INDICES.len() as u32,
0,
0..self.instance_count as u32,
);
}
}
@ -285,7 +297,7 @@ const QUAD_VERTS: [Vertex; 4] = [
},
];
const MAX_INSTANCES: usize = 100_000;
const INITIAL_INSTANCES: usize = 10_000;
#[repr(C)]
#[derive(Debug, Clone, Copy, Zeroable, Pod)]

View file

@ -1,12 +1,12 @@
//! Configure a renderer.
use std::fmt;
pub use crate::Antialiasing;
use crate::Font;
/// The settings of a [`Backend`].
///
/// [`Backend`]: crate::Backend
#[derive(Clone, Copy, PartialEq)]
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Settings {
/// The present mode of the [`Backend`].
///
@ -16,42 +16,20 @@ pub struct Settings {
/// The internal graphics backend to use.
pub internal_backend: wgpu::Backends,
/// The bytes of the font that will be used by default.
///
/// If `None` is provided, a default system font will be chosen.
pub default_font: Option<&'static [u8]>,
/// The default [`Font`] to use.
pub default_font: Font,
/// The default size of text.
///
/// By default, it will be set to `16.0`.
pub default_text_size: f32,
/// If enabled, spread text workload in multiple threads when multiple cores
/// are available.
///
/// By default, it is disabled.
pub text_multithreading: bool,
/// The antialiasing strategy that will be used for triangle primitives.
///
/// By default, it is `None`.
pub antialiasing: Option<Antialiasing>,
}
impl fmt::Debug for Settings {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Settings")
.field("present_mode", &self.present_mode)
.field("internal_backend", &self.internal_backend)
// Instead of printing the font bytes, we simply show a `bool` indicating if using a default font or not.
.field("default_font", &self.default_font.is_some())
.field("default_text_size", &self.default_text_size)
.field("text_multithreading", &self.text_multithreading)
.field("antialiasing", &self.antialiasing)
.finish()
}
}
impl Settings {
/// Creates new [`Settings`] using environment configuration.
///
@ -81,9 +59,8 @@ impl Default for Settings {
Settings {
present_mode: wgpu::PresentMode::AutoVsync,
internal_backend: wgpu::Backends::all(),
default_font: None,
default_text_size: 20.0,
text_multithreading: false,
default_font: Font::SansSerif,
default_text_size: 16.0,
antialiasing: None,
}
}

View file

@ -1,128 +1,267 @@
use crate::Transformation;
use iced_graphics::font;
use std::{cell::RefCell, collections::HashMap};
use wgpu_glyph::ab_glyph;
pub use iced_native::text::Hit;
#[derive(Debug)]
use iced_graphics::layer::Text;
use iced_native::alignment;
use iced_native::{Color, Font, Rectangle, Size};
use rustc_hash::{FxHashMap, FxHashSet};
use std::borrow::Cow;
use std::cell::RefCell;
use std::collections::hash_map;
use std::hash::{BuildHasher, Hash, Hasher};
use std::sync::Arc;
#[allow(missing_debug_implementations)]
pub struct Pipeline {
draw_brush: RefCell<wgpu_glyph::GlyphBrush<()>>,
draw_font_map: RefCell<HashMap<String, wgpu_glyph::FontId>>,
measure_brush: RefCell<glyph_brush::GlyphBrush<()>>,
system: Option<System>,
renderers: Vec<glyphon::TextRenderer>,
atlas: glyphon::TextAtlas,
prepare_layer: usize,
}
#[ouroboros::self_referencing]
struct System {
fonts: glyphon::FontSystem,
#[borrows(fonts)]
#[not_covariant]
measurement_cache: RefCell<Cache<'this>>,
#[borrows(fonts)]
#[not_covariant]
render_cache: Cache<'this>,
}
impl Pipeline {
pub fn new(
device: &wgpu::Device,
queue: &wgpu::Queue,
format: wgpu::TextureFormat,
default_font: Option<&[u8]>,
multithreading: bool,
) -> Self {
let default_font = default_font.map(|slice| slice.to_vec());
// TODO: Font customization
#[cfg(not(target_os = "ios"))]
#[cfg(feature = "default_system_font")]
let default_font = {
default_font.or_else(|| {
font::Source::new()
.load(&[font::Family::SansSerif, font::Family::Serif])
.ok()
})
};
let default_font =
default_font.unwrap_or_else(|| font::FALLBACK.to_vec());
let font = ab_glyph::FontArc::try_from_vec(default_font)
.unwrap_or_else(|_| {
log::warn!(
"System font failed to load. Falling back to \
embedded font..."
);
ab_glyph::FontArc::try_from_slice(font::FALLBACK)
.expect("Load fallback font")
});
let draw_brush_builder =
wgpu_glyph::GlyphBrushBuilder::using_font(font.clone())
.initial_cache_size((2048, 2048))
.draw_cache_multithread(multithreading);
#[cfg(target_arch = "wasm32")]
let draw_brush_builder = draw_brush_builder.draw_cache_align_4x4(true);
let draw_brush = draw_brush_builder.build(device, format);
let measure_brush =
glyph_brush::GlyphBrushBuilder::using_font(font).build();
Pipeline {
draw_brush: RefCell::new(draw_brush),
draw_font_map: RefCell::new(HashMap::new()),
measure_brush: RefCell::new(measure_brush),
system: Some(
SystemBuilder {
fonts: glyphon::FontSystem::new_with_fonts(
[glyphon::fontdb::Source::Binary(Arc::new(
include_bytes!("../fonts/Iced-Icons.ttf")
.as_slice(),
))]
.into_iter(),
),
measurement_cache_builder: |_| RefCell::new(Cache::new()),
render_cache_builder: |_| Cache::new(),
}
.build(),
),
renderers: Vec::new(),
atlas: glyphon::TextAtlas::new(device, queue, format),
prepare_layer: 0,
}
}
pub fn queue(&mut self, section: wgpu_glyph::Section<'_>) {
self.draw_brush.borrow_mut().queue(section);
pub fn load_font(&mut self, bytes: Cow<'static, [u8]>) {
let heads = self.system.take().unwrap().into_heads();
let (locale, mut db) = heads.fonts.into_locale_and_db();
db.load_font_source(glyphon::fontdb::Source::Binary(Arc::new(
bytes.into_owned(),
)));
self.system = Some(
SystemBuilder {
fonts: glyphon::FontSystem::new_with_locale_and_db(locale, db),
measurement_cache_builder: |_| RefCell::new(Cache::new()),
render_cache_builder: |_| Cache::new(),
}
.build(),
);
}
pub fn draw_queued(
pub fn prepare(
&mut self,
device: &wgpu::Device,
staging_belt: &mut wgpu::util::StagingBelt,
encoder: &mut wgpu::CommandEncoder,
target: &wgpu::TextureView,
transformation: Transformation,
region: wgpu_glyph::Region,
) {
self.draw_brush
.borrow_mut()
.draw_queued_with_transform_and_scissoring(
queue: &wgpu::Queue,
sections: &[Text<'_>],
bounds: Rectangle,
scale_factor: f32,
target_size: Size<u32>,
) -> bool {
self.system.as_mut().unwrap().with_mut(|fields| {
if self.renderers.len() <= self.prepare_layer {
self.renderers
.push(glyphon::TextRenderer::new(device, queue));
}
let renderer = &mut self.renderers[self.prepare_layer];
let keys: Vec<_> = sections
.iter()
.map(|section| {
let (key, _) = fields.render_cache.allocate(
fields.fonts,
Key {
content: section.content,
size: section.size * scale_factor,
font: section.font,
bounds: Size {
width: (section.bounds.width * scale_factor)
.ceil(),
height: (section.bounds.height * scale_factor)
.ceil(),
},
color: section.color,
},
);
key
})
.collect();
let bounds = glyphon::TextBounds {
left: (bounds.x * scale_factor) as i32,
top: (bounds.y * scale_factor) as i32,
right: ((bounds.x + bounds.width) * scale_factor) as i32,
bottom: ((bounds.y + bounds.height) * scale_factor) as i32,
};
let text_areas =
sections.iter().zip(keys.iter()).map(|(section, key)| {
let buffer = fields
.render_cache
.get(key)
.expect("Get cached buffer");
let x = section.bounds.x * scale_factor;
let y = section.bounds.y * scale_factor;
let (total_lines, max_width) = buffer
.layout_runs()
.enumerate()
.fold((0, 0.0), |(_, max), (i, buffer)| {
(i + 1, buffer.line_w.max(max))
});
let total_height =
total_lines as f32 * section.size * 1.2 * scale_factor;
let left = match section.horizontal_alignment {
alignment::Horizontal::Left => x,
alignment::Horizontal::Center => x - max_width / 2.0,
alignment::Horizontal::Right => x - max_width,
};
let top = match section.vertical_alignment {
alignment::Vertical::Top => y,
alignment::Vertical::Center => y - total_height / 2.0,
alignment::Vertical::Bottom => y - total_height,
};
glyphon::TextArea {
buffer,
left: left as i32,
top: top as i32,
bounds,
}
});
let result = renderer.prepare(
device,
staging_belt,
encoder,
target,
transformation.into(),
region,
)
.expect("Draw text");
queue,
&mut self.atlas,
glyphon::Resolution {
width: target_size.width,
height: target_size.height,
},
text_areas,
glyphon::Color::rgb(0, 0, 0),
&mut glyphon::SwashCache::new(fields.fonts),
);
match result {
Ok(()) => {
self.prepare_layer += 1;
true
}
Err(glyphon::PrepareError::AtlasFull(content_type)) => {
self.prepare_layer = 0;
#[allow(clippy::needless_bool)]
if self.atlas.grow(device, content_type) {
false
} else {
// If the atlas cannot grow, then all bets are off.
// Instead of panicking, we will just pray that the result
// will be somewhat readable...
true
}
}
}
})
}
pub fn render<'a>(
&'a self,
layer: usize,
bounds: Rectangle<u32>,
render_pass: &mut wgpu::RenderPass<'a>,
) {
let renderer = &self.renderers[layer];
render_pass.set_scissor_rect(
bounds.x,
bounds.y,
bounds.width,
bounds.height,
);
renderer
.render(&self.atlas, render_pass)
.expect("Render text");
}
pub fn end_frame(&mut self) {
self.atlas.trim();
self.system
.as_mut()
.unwrap()
.with_render_cache_mut(|cache| cache.trim());
self.prepare_layer = 0;
}
pub fn measure(
&self,
content: &str,
size: f32,
font: iced_native::Font,
bounds: iced_native::Size,
font: Font,
bounds: Size,
) -> (f32, f32) {
use wgpu_glyph::GlyphCruncher;
self.system.as_ref().unwrap().with(|fields| {
let mut measurement_cache = fields.measurement_cache.borrow_mut();
let wgpu_glyph::FontId(font_id) = self.find_font(font);
let (_, paragraph) = measurement_cache.allocate(
fields.fonts,
Key {
content,
size,
font,
bounds,
color: Color::BLACK,
},
);
let section = wgpu_glyph::Section {
bounds: (bounds.width, bounds.height),
text: vec![wgpu_glyph::Text {
text: content,
scale: size.into(),
font_id: wgpu_glyph::FontId(font_id),
extra: wgpu_glyph::Extra::default(),
}],
..Default::default()
};
let (total_lines, max_width) = paragraph
.layout_runs()
.enumerate()
.fold((0, 0.0), |(_, max), (i, buffer)| {
(i + 1, buffer.line_w.max(max))
});
if let Some(bounds) =
self.measure_brush.borrow_mut().glyph_bounds(section)
{
(bounds.width().ceil(), bounds.height().ceil())
} else {
(0.0, 0.0)
}
(max_width, size * 1.2 * total_lines as f32)
})
}
pub fn hit_test(
@ -132,134 +271,148 @@ impl Pipeline {
font: iced_native::Font,
bounds: iced_native::Size,
point: iced_native::Point,
nearest_only: bool,
_nearest_only: bool,
) -> Option<Hit> {
use wgpu_glyph::GlyphCruncher;
self.system.as_ref().unwrap().with(|fields| {
let mut measurement_cache = fields.measurement_cache.borrow_mut();
let wgpu_glyph::FontId(font_id) = self.find_font(font);
let (_, paragraph) = measurement_cache.allocate(
fields.fonts,
Key {
content,
size,
font,
bounds,
color: Color::BLACK,
},
);
let section = wgpu_glyph::Section {
bounds: (bounds.width, bounds.height),
text: vec![wgpu_glyph::Text {
text: content,
scale: size.into(),
font_id: wgpu_glyph::FontId(font_id),
extra: wgpu_glyph::Extra::default(),
}],
..Default::default()
};
let cursor = paragraph.hit(point.x, point.y)?;
let mut mb = self.measure_brush.borrow_mut();
// The underlying type is FontArc, so clones are cheap.
use wgpu_glyph::ab_glyph::{Font, ScaleFont};
let font = mb.fonts()[font_id].clone().into_scaled(size);
// Implements an iterator over the glyph bounding boxes.
let bounds = mb.glyphs(section).map(
|wgpu_glyph::SectionGlyph {
byte_index, glyph, ..
}| {
(
*byte_index,
iced_native::Rectangle::new(
iced_native::Point::new(
glyph.position.x - font.h_side_bearing(glyph.id),
glyph.position.y - font.ascent(),
),
iced_native::Size::new(
font.h_advance(glyph.id),
font.ascent() - font.descent(),
),
),
)
},
);
// Implements computation of the character index based on the byte index
// within the input string.
let char_index = |byte_index| {
let mut b_count = 0;
for (i, utf8_len) in
content.chars().map(|c| c.len_utf8()).enumerate()
{
if byte_index < (b_count + utf8_len) {
return i;
}
b_count += utf8_len;
}
byte_index
};
if !nearest_only {
for (idx, bounds) in bounds.clone() {
if bounds.contains(point) {
return Some(Hit::CharOffset(char_index(idx)));
}
}
}
let nearest = bounds
.map(|(index, bounds)| (index, bounds.center()))
.min_by(|(_, center_a), (_, center_b)| {
center_a
.distance(point)
.partial_cmp(&center_b.distance(point))
.unwrap_or(std::cmp::Ordering::Greater)
});
nearest.map(|(idx, center)| {
Hit::NearestCharOffset(char_index(idx), point - center)
Some(Hit::CharOffset(cursor.index))
})
}
pub fn trim_measurement_cache(&mut self) {
// TODO: We should probably use a `GlyphCalculator` for this. However,
// it uses a lifetimed `GlyphCalculatorGuard` with side-effects on drop.
// This makes stuff quite inconvenient. A manual method for trimming the
// cache would make our lives easier.
loop {
let action = self
.measure_brush
.borrow_mut()
.process_queued(|_, _| {}, |_| {});
self.system
.as_mut()
.unwrap()
.with_measurement_cache_mut(|cache| cache.borrow_mut().trim());
}
}
match action {
Ok(_) => break,
Err(glyph_brush::BrushError::TextureTooSmall { suggested }) => {
let (width, height) = suggested;
fn to_family(font: Font) -> glyphon::Family<'static> {
match font {
Font::Name(name) => glyphon::Family::Name(name),
Font::SansSerif => glyphon::Family::SansSerif,
Font::Serif => glyphon::Family::Serif,
Font::Cursive => glyphon::Family::Cursive,
Font::Fantasy => glyphon::Family::Fantasy,
Font::Monospace => glyphon::Family::Monospace,
}
}
self.measure_brush
.borrow_mut()
.resize_texture(width, height);
}
}
struct Cache<'a> {
entries: FxHashMap<KeyHash, glyphon::Buffer<'a>>,
recently_used: FxHashSet<KeyHash>,
hasher: HashBuilder,
trim_count: usize,
}
#[cfg(not(target_arch = "wasm32"))]
type HashBuilder = twox_hash::RandomXxHashBuilder64;
#[cfg(target_arch = "wasm32")]
type HashBuilder = std::hash::BuildHasherDefault<twox_hash::XxHash64>;
impl<'a> Cache<'a> {
const TRIM_INTERVAL: usize = 300;
fn new() -> Self {
Self {
entries: FxHashMap::default(),
recently_used: FxHashSet::default(),
hasher: HashBuilder::default(),
trim_count: 0,
}
}
pub fn find_font(&self, font: iced_native::Font) -> wgpu_glyph::FontId {
match font {
iced_native::Font::Default => wgpu_glyph::FontId(0),
iced_native::Font::External { name, bytes } => {
if let Some(font_id) = self.draw_font_map.borrow().get(name) {
return *font_id;
}
fn get(&self, key: &KeyHash) -> Option<&glyphon::Buffer<'a>> {
self.entries.get(key)
}
let font = ab_glyph::FontArc::try_from_slice(bytes)
.expect("Load font");
fn allocate(
&mut self,
fonts: &'a glyphon::FontSystem,
key: Key<'_>,
) -> (KeyHash, &mut glyphon::Buffer<'a>) {
let hash = {
let mut hasher = self.hasher.build_hasher();
let _ = self.measure_brush.borrow_mut().add_font(font.clone());
key.content.hash(&mut hasher);
key.size.to_bits().hash(&mut hasher);
key.font.hash(&mut hasher);
key.bounds.width.to_bits().hash(&mut hasher);
key.bounds.height.to_bits().hash(&mut hasher);
key.color.into_rgba8().hash(&mut hasher);
let font_id = self.draw_brush.borrow_mut().add_font(font);
hasher.finish()
};
let _ = self
.draw_font_map
.borrow_mut()
.insert(String::from(name), font_id);
if let hash_map::Entry::Vacant(entry) = self.entries.entry(hash) {
let metrics = glyphon::Metrics::new(key.size, key.size * 1.2);
let mut buffer = glyphon::Buffer::new(fonts, metrics);
font_id
}
buffer.set_size(
key.bounds.width,
key.bounds.height.max(key.size * 1.2),
);
buffer.set_text(
key.content,
glyphon::Attrs::new()
.family(to_family(key.font))
.color({
let [r, g, b, a] = key.color.into_linear();
glyphon::Color::rgba(
(r * 255.0) as u8,
(g * 255.0) as u8,
(b * 255.0) as u8,
(a * 255.0) as u8,
)
})
.monospaced(matches!(key.font, Font::Monospace)),
);
let _ = entry.insert(buffer);
}
let _ = self.recently_used.insert(hash);
(hash, self.entries.get_mut(&hash).unwrap())
}
fn trim(&mut self) {
if self.trim_count >= Self::TRIM_INTERVAL {
self.entries
.retain(|key, _| self.recently_used.contains(key));
self.recently_used.clear();
self.trim_count = 0;
} else {
self.trim_count += 1;
}
}
}
#[derive(Debug, Clone, Copy)]
struct Key<'a> {
content: &'a str,
size: f32,
font: Font,
bounds: Size,
color: Color,
}
type KeyHash = u64;

View file

@ -14,50 +14,55 @@ use tracing::info_span;
#[derive(Debug)]
pub struct Pipeline {
blit: Option<msaa::Blit>,
index_buffer: Buffer<u32>,
index_strides: Vec<u32>,
solid: solid::Pipeline,
/// Gradients are currently not supported on WASM targets due to their need of storage buffers.
#[cfg(not(target_arch = "wasm32"))]
gradient: gradient::Pipeline,
layers: Vec<Layer>,
prepare_layer: usize,
}
impl Pipeline {
pub fn new(
#[derive(Debug)]
struct Layer {
index_buffer: Buffer<u32>,
index_strides: Vec<u32>,
solid: solid::Layer,
#[cfg(not(target_arch = "wasm32"))]
gradient: gradient::Layer,
}
impl Layer {
fn new(
device: &wgpu::Device,
format: wgpu::TextureFormat,
antialiasing: Option<settings::Antialiasing>,
) -> Pipeline {
Pipeline {
blit: antialiasing.map(|a| msaa::Blit::new(device, format, a)),
solid: &solid::Pipeline,
#[cfg(not(target_arch = "wasm32"))] gradient: &gradient::Pipeline,
) -> Self {
Self {
index_buffer: Buffer::new(
device,
"iced_wgpu::triangle vertex buffer",
"iced_wgpu::triangle index buffer",
wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST,
),
index_strides: Vec::new(),
solid: solid::Pipeline::new(device, format, antialiasing),
solid: solid::Layer::new(device, &solid.constants_layout),
#[cfg(not(target_arch = "wasm32"))]
gradient: gradient::Pipeline::new(device, format, antialiasing),
gradient: gradient::Layer::new(device, &gradient.constants_layout),
}
}
pub fn draw(
fn prepare(
&mut self,
device: &wgpu::Device,
staging_belt: &mut wgpu::util::StagingBelt,
encoder: &mut wgpu::CommandEncoder,
target: &wgpu::TextureView,
target_size: Size<u32>,
transformation: Transformation,
scale_factor: f32,
queue: &wgpu::Queue,
solid: &solid::Pipeline,
#[cfg(not(target_arch = "wasm32"))] gradient: &gradient::Pipeline,
meshes: &[Mesh<'_>],
transformation: Transformation,
) {
#[cfg(feature = "tracing")]
let _ = info_span!("Wgpu::Triangle", "DRAW").entered();
// Count the total amount of vertices & indices we need to handle
let count = mesh::attribute_count_of(meshes);
@ -75,6 +80,7 @@ impl Pipeline {
.resize(device, count.gradient_vertices);
// Prepare dynamic buffers & data store for writing
self.index_buffer.clear();
self.index_strides.clear();
self.solid.vertices.clear();
self.solid.uniforms.clear();
@ -99,13 +105,8 @@ impl Pipeline {
let transform =
transformation * Transformation::translate(origin.x, origin.y);
let new_index_offset = self.index_buffer.write(
device,
staging_belt,
encoder,
index_offset,
indices,
);
let new_index_offset =
self.index_buffer.write(queue, index_offset, indices);
index_offset += new_index_offset;
self.index_strides.push(indices.len() as u32);
@ -116,9 +117,7 @@ impl Pipeline {
self.solid.uniforms.push(&solid::Uniforms::new(transform));
let written_bytes = self.solid.vertices.write(
device,
staging_belt,
encoder,
queue,
solid_vertex_offset,
&buffers.vertices,
);
@ -130,9 +129,7 @@ impl Pipeline {
buffers, gradient, ..
} => {
let written_bytes = self.gradient.vertices.write(
device,
staging_belt,
encoder,
queue,
gradient_vertex_offset,
&buffers.vertices,
);
@ -196,14 +193,14 @@ impl Pipeline {
let uniforms_resized = self.solid.uniforms.resize(device);
if uniforms_resized {
self.solid.bind_group = solid::Pipeline::bind_group(
self.solid.constants = solid::Layer::bind_group(
device,
self.solid.uniforms.raw(),
&self.solid.bind_group_layout,
&solid.constants_layout,
)
}
self.solid.uniforms.write(device, staging_belt, encoder);
self.solid.uniforms.write(queue);
}
#[cfg(not(target_arch = "wasm32"))]
@ -218,22 +215,169 @@ impl Pipeline {
let storage_resized = self.gradient.storage.resize(device);
if uniforms_resized || storage_resized {
self.gradient.bind_group = gradient::Pipeline::bind_group(
self.gradient.constants = gradient::Layer::bind_group(
device,
self.gradient.uniforms.raw(),
self.gradient.storage.raw(),
&self.gradient.bind_group_layout,
&gradient.constants_layout,
);
}
// Write to GPU
self.gradient.uniforms.write(device, staging_belt, encoder);
self.gradient.storage.write(device, staging_belt, encoder);
self.gradient.uniforms.write(queue);
self.gradient.storage.write(queue);
// Cleanup
self.gradient.color_stop_offset = 0;
self.gradient.color_stops_pending_write.color_stops.clear();
}
}
fn render<'a>(
&'a self,
solid: &'a solid::Pipeline,
#[cfg(not(target_arch = "wasm32"))] gradient: &'a gradient::Pipeline,
meshes: &[Mesh<'_>],
scale_factor: f32,
render_pass: &mut wgpu::RenderPass<'a>,
) {
let mut num_solids = 0;
#[cfg(not(target_arch = "wasm32"))]
let mut num_gradients = 0;
let mut last_is_solid = None;
for (index, mesh) in meshes.iter().enumerate() {
let clip_bounds = (mesh.clip_bounds() * scale_factor).snap();
render_pass.set_scissor_rect(
clip_bounds.x,
clip_bounds.y,
clip_bounds.width,
clip_bounds.height,
);
match mesh {
Mesh::Solid { .. } => {
if !last_is_solid.unwrap_or(false) {
render_pass.set_pipeline(&solid.pipeline);
last_is_solid = Some(true);
}
render_pass.set_bind_group(
0,
&self.solid.constants,
&[self.solid.uniforms.offset_at_index(num_solids)],
);
render_pass.set_vertex_buffer(
0,
self.solid.vertices.slice_from_index(num_solids),
);
num_solids += 1;
}
#[cfg(not(target_arch = "wasm32"))]
Mesh::Gradient { .. } => {
if last_is_solid.unwrap_or(true) {
render_pass.set_pipeline(&gradient.pipeline);
last_is_solid = Some(false);
}
render_pass.set_bind_group(
0,
&self.gradient.constants,
&[self
.gradient
.uniforms
.offset_at_index(num_gradients)],
);
render_pass.set_vertex_buffer(
0,
self.gradient.vertices.slice_from_index(num_gradients),
);
num_gradients += 1;
}
#[cfg(target_arch = "wasm32")]
Mesh::Gradient { .. } => {}
};
render_pass.set_index_buffer(
self.index_buffer.slice_from_index(index),
wgpu::IndexFormat::Uint32,
);
render_pass.draw_indexed(0..self.index_strides[index], 0, 0..1);
}
}
}
impl Pipeline {
pub fn new(
device: &wgpu::Device,
format: wgpu::TextureFormat,
antialiasing: Option<settings::Antialiasing>,
) -> Pipeline {
Pipeline {
blit: antialiasing.map(|a| msaa::Blit::new(device, format, a)),
solid: solid::Pipeline::new(device, format, antialiasing),
#[cfg(not(target_arch = "wasm32"))]
gradient: gradient::Pipeline::new(device, format, antialiasing),
layers: Vec::new(),
prepare_layer: 0,
}
}
pub fn prepare(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
meshes: &[Mesh<'_>],
transformation: Transformation,
) {
#[cfg(feature = "tracing")]
let _ = info_span!("Wgpu::Triangle", "PREPARE").entered();
if self.layers.len() <= self.prepare_layer {
self.layers.push(Layer::new(
device,
&self.solid,
#[cfg(not(target_arch = "wasm32"))]
&self.gradient,
));
}
let layer = &mut self.layers[self.prepare_layer];
layer.prepare(
device,
queue,
&self.solid,
#[cfg(not(target_arch = "wasm32"))]
&self.gradient,
meshes,
transformation,
);
self.prepare_layer += 1;
}
pub fn render(
&mut self,
device: &wgpu::Device,
encoder: &mut wgpu::CommandEncoder,
target: &wgpu::TextureView,
layer: usize,
target_size: Size<u32>,
meshes: &[Mesh<'_>],
scale_factor: f32,
) {
#[cfg(feature = "tracing")]
let _ = info_span!("Wgpu::Triangle", "DRAW").entered();
// Configure render pass
{
@ -268,87 +412,26 @@ impl Pipeline {
depth_stencil_attachment: None,
});
let mut num_solids = 0;
#[cfg(not(target_arch = "wasm32"))]
let mut num_gradients = 0;
let mut last_is_solid = None;
let layer = &mut self.layers[layer];
for (index, mesh) in meshes.iter().enumerate() {
let clip_bounds = (mesh.clip_bounds() * scale_factor).snap();
render_pass.set_scissor_rect(
clip_bounds.x,
clip_bounds.y,
clip_bounds.width,
clip_bounds.height,
);
match mesh {
Mesh::Solid { .. } => {
if !last_is_solid.unwrap_or(false) {
render_pass.set_pipeline(&self.solid.pipeline);
last_is_solid = Some(true);
}
render_pass.set_bind_group(
0,
&self.solid.bind_group,
&[self.solid.uniforms.offset_at_index(num_solids)],
);
render_pass.set_vertex_buffer(
0,
self.solid.vertices.slice_from_index(num_solids),
);
num_solids += 1;
}
#[cfg(not(target_arch = "wasm32"))]
Mesh::Gradient { .. } => {
if last_is_solid.unwrap_or(true) {
render_pass.set_pipeline(&self.gradient.pipeline);
last_is_solid = Some(false);
}
render_pass.set_bind_group(
0,
&self.gradient.bind_group,
&[self
.gradient
.uniforms
.offset_at_index(num_gradients)],
);
render_pass.set_vertex_buffer(
0,
self.gradient
.vertices
.slice_from_index(num_gradients),
);
num_gradients += 1;
}
#[cfg(target_arch = "wasm32")]
Mesh::Gradient { .. } => {}
};
render_pass.set_index_buffer(
self.index_buffer.slice_from_index(index),
wgpu::IndexFormat::Uint32,
);
render_pass.draw_indexed(0..self.index_strides[index], 0, 0..1);
}
layer.render(
&self.solid,
#[cfg(not(target_arch = "wasm32"))]
&self.gradient,
meshes,
scale_factor,
&mut render_pass,
);
}
self.index_buffer.clear();
if let Some(blit) = &mut self.blit {
blit.draw(encoder, target);
}
}
pub fn end_frame(&mut self) {
self.prepare_layer = 0;
}
}
fn fragment_target(
@ -390,10 +473,62 @@ mod solid {
#[derive(Debug)]
pub struct Pipeline {
pub pipeline: wgpu::RenderPipeline,
pub constants_layout: wgpu::BindGroupLayout,
}
#[derive(Debug)]
pub struct Layer {
pub vertices: Buffer<triangle::ColoredVertex2D>,
pub uniforms: dynamic::Buffer<Uniforms>,
pub bind_group_layout: wgpu::BindGroupLayout,
pub bind_group: wgpu::BindGroup,
pub constants: wgpu::BindGroup,
}
impl Layer {
pub fn new(
device: &wgpu::Device,
constants_layout: &wgpu::BindGroupLayout,
) -> Self {
let vertices = Buffer::new(
device,
"iced_wgpu::triangle::solid vertex buffer",
wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
);
let uniforms = dynamic::Buffer::uniform(
device,
"iced_wgpu::triangle::solid uniforms",
);
let constants =
Self::bind_group(device, uniforms.raw(), constants_layout);
Self {
vertices,
uniforms,
constants,
}
}
pub fn bind_group(
device: &wgpu::Device,
buffer: &wgpu::Buffer,
layout: &wgpu::BindGroupLayout,
) -> wgpu::BindGroup {
device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("iced_wgpu::triangle::solid bind group"),
layout,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::Buffer(
wgpu::BufferBinding {
buffer,
offset: 0,
size: Some(Uniforms::min_size()),
},
),
}],
})
}
}
#[derive(Debug, Clone, Copy, ShaderType)]
@ -416,18 +551,7 @@ mod solid {
format: wgpu::TextureFormat,
antialiasing: Option<settings::Antialiasing>,
) -> Self {
let vertices = Buffer::new(
device,
"iced_wgpu::triangle::solid vertex buffer",
wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
);
let uniforms = dynamic::Buffer::uniform(
device,
"iced_wgpu::triangle::solid uniforms",
);
let bind_group_layout = device.create_bind_group_layout(
let constants_layout = device.create_bind_group_layout(
&wgpu::BindGroupLayoutDescriptor {
label: Some("iced_wgpu::triangle::solid bind group layout"),
entries: &[wgpu::BindGroupLayoutEntry {
@ -443,13 +567,10 @@ mod solid {
},
);
let bind_group =
Self::bind_group(device, uniforms.raw(), &bind_group_layout);
let layout = device.create_pipeline_layout(
&wgpu::PipelineLayoutDescriptor {
label: Some("iced_wgpu::triangle::solid pipeline layout"),
bind_group_layouts: &[&bind_group_layout],
bind_group_layouts: &[&constants_layout],
push_constant_ranges: &[],
},
);
@ -501,33 +622,9 @@ mod solid {
Self {
pipeline,
vertices,
uniforms,
bind_group_layout,
bind_group,
constants_layout,
}
}
pub fn bind_group(
device: &wgpu::Device,
buffer: &wgpu::Buffer,
layout: &wgpu::BindGroupLayout,
) -> wgpu::BindGroup {
device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("iced_wgpu::triangle::solid bind group"),
layout,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::Buffer(
wgpu::BufferBinding {
buffer,
offset: 0,
size: Some(Uniforms::min_size()),
},
),
}],
})
}
}
}
@ -545,15 +642,90 @@ mod gradient {
#[derive(Debug)]
pub struct Pipeline {
pub pipeline: wgpu::RenderPipeline,
pub constants_layout: wgpu::BindGroupLayout,
}
#[derive(Debug)]
pub struct Layer {
pub vertices: Buffer<Vertex2D>,
pub uniforms: dynamic::Buffer<Uniforms>,
pub storage: dynamic::Buffer<Storage>,
pub constants: wgpu::BindGroup,
pub color_stop_offset: i32,
//Need to store these and then write them all at once
//or else they will be padded to 256 and cause gaps in the storage buffer
pub color_stops_pending_write: Storage,
pub bind_group_layout: wgpu::BindGroupLayout,
pub bind_group: wgpu::BindGroup,
}
impl Layer {
pub fn new(
device: &wgpu::Device,
constants_layout: &wgpu::BindGroupLayout,
) -> Self {
let vertices = Buffer::new(
device,
"iced_wgpu::triangle::gradient vertex buffer",
wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
);
let uniforms = dynamic::Buffer::uniform(
device,
"iced_wgpu::triangle::gradient uniforms",
);
// Note: with a WASM target storage buffers are not supported. Will need to use UBOs & static
// sized array (eg like the 32-sized array on OpenGL side right now) to make gradients work
let storage = dynamic::Buffer::storage(
device,
"iced_wgpu::triangle::gradient storage",
);
let constants = Self::bind_group(
device,
uniforms.raw(),
storage.raw(),
constants_layout,
);
Self {
vertices,
uniforms,
storage,
constants,
color_stop_offset: 0,
color_stops_pending_write: Storage {
color_stops: vec![],
},
}
}
pub fn bind_group(
device: &wgpu::Device,
uniform_buffer: &wgpu::Buffer,
storage_buffer: &wgpu::Buffer,
layout: &wgpu::BindGroupLayout,
) -> wgpu::BindGroup {
device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("iced_wgpu::triangle::gradient bind group"),
layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::Buffer(
wgpu::BufferBinding {
buffer: uniform_buffer,
offset: 0,
size: Some(Uniforms::min_size()),
},
),
},
wgpu::BindGroupEntry {
binding: 1,
resource: storage_buffer.as_entire_binding(),
},
],
})
}
}
#[derive(Debug, ShaderType)]
@ -584,25 +756,7 @@ mod gradient {
format: wgpu::TextureFormat,
antialiasing: Option<settings::Antialiasing>,
) -> Self {
let vertices = Buffer::new(
device,
"iced_wgpu::triangle::gradient vertex buffer",
wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
);
let uniforms = dynamic::Buffer::uniform(
device,
"iced_wgpu::triangle::gradient uniforms",
);
//Note: with a WASM target storage buffers are not supported. Will need to use UBOs & static
// sized array (eg like the 32-sized array on OpenGL side right now) to make gradients work
let storage = dynamic::Buffer::storage(
device,
"iced_wgpu::triangle::gradient storage",
);
let bind_group_layout = device.create_bind_group_layout(
let constants_layout = device.create_bind_group_layout(
&wgpu::BindGroupLayoutDescriptor {
label: Some(
"iced_wgpu::triangle::gradient bind group layout",
@ -634,19 +788,12 @@ mod gradient {
},
);
let bind_group = Pipeline::bind_group(
device,
uniforms.raw(),
storage.raw(),
&bind_group_layout,
);
let layout = device.create_pipeline_layout(
&wgpu::PipelineLayoutDescriptor {
label: Some(
"iced_wgpu::triangle::gradient pipeline layout",
),
bind_group_layouts: &[&bind_group_layout],
bind_group_layouts: &[&constants_layout],
push_constant_ranges: &[],
},
);
@ -694,44 +841,8 @@ mod gradient {
Self {
pipeline,
vertices,
uniforms,
storage,
color_stop_offset: 0,
color_stops_pending_write: Storage {
color_stops: vec![],
},
bind_group_layout,
bind_group,
constants_layout,
}
}
pub fn bind_group(
device: &wgpu::Device,
uniform_buffer: &wgpu::Buffer,
storage_buffer: &wgpu::Buffer,
layout: &wgpu::BindGroupLayout,
) -> wgpu::BindGroup {
device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("iced_wgpu::triangle::gradient bind group"),
layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::Buffer(
wgpu::BufferBinding {
buffer: uniform_buffer,
offset: 0,
size: Some(Uniforms::min_size()),
},
),
},
wgpu::BindGroupEntry {
binding: 1,
resource: storage_buffer.as_entire_binding(),
},
],
})
}
}
}

View file

@ -16,14 +16,11 @@ pub struct Compositor<Theme> {
adapter: wgpu::Adapter,
device: wgpu::Device,
queue: wgpu::Queue,
staging_belt: wgpu::util::StagingBelt,
format: wgpu::TextureFormat,
theme: PhantomData<Theme>,
}
impl<Theme> Compositor<Theme> {
const CHUNK_SIZE: u64 = 10 * 1024;
/// Requests a new [`Compositor`] with the given [`Settings`].
///
/// Returns `None` if no compatible graphics adapter could be found.
@ -98,15 +95,12 @@ impl<Theme> Compositor<Theme> {
.next()
.await?;
let staging_belt = wgpu::util::StagingBelt::new(Self::CHUNK_SIZE);
Some(Compositor {
instance,
settings,
adapter,
device,
queue,
staging_belt,
format,
theme: PhantomData,
})
@ -114,7 +108,7 @@ impl<Theme> Compositor<Theme> {
/// Creates a new rendering [`Backend`] for this [`Compositor`].
pub fn create_backend(&self) -> Backend {
Backend::new(&self.device, self.settings, self.format)
Backend::new(&self.device, &self.queue, self.settings, self.format)
}
}
@ -196,39 +190,12 @@ impl<Theme> iced_graphics::window::Compositor for Compositor<Theme> {
.texture
.create_view(&wgpu::TextureViewDescriptor::default());
let _ =
encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some(
"iced_wgpu::window::Compositor render pass",
),
color_attachments: &[Some(
wgpu::RenderPassColorAttachment {
view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear({
let [r, g, b, a] =
background_color.into_linear();
wgpu::Color {
r: f64::from(r),
g: f64::from(g),
b: f64::from(b),
a: f64::from(a),
}
}),
store: true,
},
},
)],
depth_stencil_attachment: None,
});
renderer.with_primitives(|backend, primitives| {
backend.present(
&self.device,
&mut self.staging_belt,
&self.queue,
&mut encoder,
Some(background_color),
view,
primitives,
viewport,
@ -237,13 +204,9 @@ impl<Theme> iced_graphics::window::Compositor for Compositor<Theme> {
});
// Submit work
self.staging_belt.finish();
let _submission = self.queue.submit(Some(encoder.finish()));
frame.present();
// Recall staging buffers
self.staging_belt.recall();
Ok(())
}
Err(error) => match error {

View file

@ -851,6 +851,16 @@ pub fn run_command<A, E>(
current_cache = user_interface.into_cache();
*cache = current_cache;
}
command::Action::LoadFont { bytes, tagger } => {
use crate::text::Renderer;
// TODO: Error handling (?)
renderer.load_font(bytes);
proxy
.send_event(tagger(Ok(())))
.expect("Send message to event loop");
}
}
}
}

View file

@ -51,14 +51,6 @@ pub struct Settings<Flags> {
///
/// [`Application`]: crate::Application
pub exit_on_close_request: bool,
/// Whether the [`Application`] should try to build the context
/// using OpenGL ES first then OpenGL.
///
/// NOTE: Only works for the `glow` backend.
///
/// [`Application`]: crate::Application
pub try_opengles_first: bool,
}
/// The window settings of an application.