Merge pull request #1160 from derezzedex/dev/arm-support

feat: add support to ARM devices (and older hardware)
This commit is contained in:
Héctor Ramón 2022-01-26 13:54:21 +07:00 committed by GitHub
commit 9b3cab82ef
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 1109 additions and 281 deletions

View file

@ -82,3 +82,29 @@ jobs:
with:
name: todos-x86_64-apple-darwin
path: target/release/todos
todos_raspberry:
runs-on: ubuntu-latest
steps:
- uses: hecrj/setup-rust-action@v1
- uses: actions/checkout@master
- name: Install cross
run: cargo install cross
- name: Enable Link Time Optimizations
run: |
echo "[profile.release]" >> Cargo.toml
echo "lto = true" >> Cargo.toml
- name: Build todos binary for Raspberry Pi 3/4 (64 bits)
run: cross build --verbose --release --package todos --target aarch64-unknown-linux-gnu
- name: Archive todos binary
uses: actions/upload-artifact@v1
with:
name: todos-aarch64-unknown-linux-gnu
path: target/aarch64-unknown-linux-gnu/release/todos
- name: Build todos binary for Raspberry Pi 2/3/4 (32 bits)
run: cross build --verbose --release --package todos --target armv7-unknown-linux-gnueabihf
- name: Archive todos binary
uses: actions/upload-artifact@v1
with:
name: todos-armv7-unknown-linux-gnueabihf
path: target/armv7-unknown-linux-gnueabihf/release/todos

7
Cross.toml Normal file
View file

@ -0,0 +1,7 @@
[target.aarch64-unknown-linux-gnu]
image = "icedrs/iced:aarch64"
xargo = false
[target.armv7-unknown-linux-gnueabihf]
image = "icedrs/iced:armv7"
xargo = false

View file

@ -45,7 +45,7 @@ The widgets of a _graphical_ user interface produce some primitives that eventua
Currently, there are two different official renderers:
- [`iced_wgpu`] is powered by [`wgpu`] and supports Vulkan, DirectX 12, and Metal.
- [`iced_glow`] is powered by [`glow`] and supports OpenGL 3.3+.
- [`iced_glow`] is powered by [`glow`] and supports OpenGL 2.1+ and OpenGL ES 2.0+.
Additionally, the [`iced_graphics`] subcrate contains a bunch of backend-agnostic types that can be leveraged to build renderers. Both of the renderers rely on the graphical foundations provided by this crate.

View file

@ -34,7 +34,9 @@ Inspired by [Elm].
* First-class support for async actions (use futures!)
* [Modular ecosystem] split into reusable parts:
* A [renderer-agnostic native runtime] enabling integration with existing systems
* A [built-in renderer] supporting Vulkan, Metal, DX11, and DX12
* Two [built-in renderers] leveraging [`wgpu`] and [`glow`]
* [`iced_wgpu`] supporting Vulkan, Metal and DX12
* [`iced_glow`] supporting OpenGL 2.1+ and OpenGL ES 2.0+
* A [windowing shell]
* A [web runtime] leveraging the DOM
@ -49,7 +51,10 @@ __iced is currently experimental software.__ [Take a look at the roadmap],
[Modular ecosystem]: https://github.com/hecrj/iced/blob/master/ECOSYSTEM.md
[renderer-agnostic native runtime]: https://github.com/hecrj/iced/tree/master/native
[`wgpu`]: https://github.com/gfx-rs/wgpu-rs
[built-in renderer]: https://github.com/hecrj/iced/tree/master/wgpu
[`glow`]: https://github.com/grovesNL/glow
[`iced_wgpu`]: https://github.com/hecrj/iced/tree/master/wgpu
[`iced_glow`]: https://github.com/hecrj/iced/tree/master/glow
[built-in renderers]: https://github.com/hecrj/iced/blob/master/ECOSYSTEM.md#Renderers
[windowing shell]: https://github.com/hecrj/iced/tree/master/winit
[`dodrio`]: https://github.com/fitzgen/dodrio
[web runtime]: https://github.com/hecrj/iced/tree/master/web
@ -195,6 +200,32 @@ end-user-oriented GUI library, while keeping [the ecosystem] modular:
[`ggez`]: https://github.com/ggez/ggez
[the ecosystem]: https://github.com/hecrj/iced/blob/master/ECOSYSTEM.md
## Common problems
1. `Error: GraphicsAdapterNotFound`
This occurs when the selected [built-in renderer] is not able to create a context.
Often this will occur while using [`iced_wgpu`] as the renderer without
supported hardware (needs Vulkan, Metal or DX12). In this case, you could try using the
[`iced_glow`] renderer:
First, check if it works with
```console
$ cargo run --features "iced/glow iced/glow_canvas" --package game_of_life
```
and then use it in your project with
```toml
iced = { version = "0.3", default-features = false, features = ["glow"] }
```
**NOTE:** Chances are you have hardware that supports at least OpenGL 2.1 or OpenGL ES 2.0,
but if you don't, right now there's no software fallback, so it means your hardware
doesn't support Iced.
[built-in renderer]: https://github.com/hecrj/iced/blob/master/ECOSYSTEM.md#Renderers
## Contributing / Feedback
Contributions are greatly appreciated! If you want to contribute, please
read our [contributing guidelines] for more details.

View file

@ -10,3 +10,4 @@ iced = { path = "../..", features = ["canvas", "tokio", "debug"] }
tokio = { version = "1.0", features = ["sync"] }
itertools = "0.9"
rustc-hash = "1.1"
env_logger = "0.9"

View file

@ -18,6 +18,8 @@ use preset::Preset;
use std::time::{Duration, Instant};
pub fn main() -> iced::Result {
env_logger::builder().format_timestamp(None).init();
GameOfLife::run(Settings {
antialiasing: true,
window: window::Settings {

View file

@ -10,4 +10,3 @@ iced_glutin = { path = "../../glutin" }
iced_glow = { path = "../../glow" }
iced_winit = { path = "../../winit" }
env_logger = "0.8"
glow = "0.6"

View file

@ -4,16 +4,15 @@ mod scene;
use controls::Controls;
use scene::Scene;
use glow;
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::glutin::event::{Event, WindowEvent};
use iced_glutin::glutin::event_loop::ControlFlow;
use iced_glutin::{program, Clipboard, Debug, Size};
use iced_winit::conversion;
use iced_winit::winit;
use winit::{dpi::PhysicalPosition, event::ModifiersState};
pub fn main() {
env_logger::init();

View file

@ -1,4 +1,5 @@
use glow::*;
use iced_glow::glow;
use iced_glow::Color;
pub struct Scene {

View file

@ -16,8 +16,8 @@ image = []
svg = []
[dependencies]
glow = "0.6"
glow_glyph = "0.4"
glow = "0.11.1"
glow_glyph = "0.5.0"
glyph_brush = "0.7"
euclid = "0.22"
bytemuck = "1.4"

51
glow/README.md Normal file
View file

@ -0,0 +1,51 @@
# `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/hecrj/iced/blob/master/LICENSE)
[![project chat](https://img.shields.io/badge/chat-on_zulip-brightgreen.svg)](https://iced.zulipchat.com)
`iced_glow` is a [`glow`] renderer for [`iced_native`]. This renderer supports OpenGL 3.0+ and OpenGL ES 2.0.
This is 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.2"
```
__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/hecrj/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,3 +1,4 @@
use crate::program;
use crate::quad;
use crate::text;
use crate::triangle;
@ -30,8 +31,10 @@ impl Backend {
settings.text_multithreading,
);
let quad_pipeline = quad::Pipeline::new(gl);
let triangle_pipeline = triangle::Pipeline::new(gl);
let shader_version = program::Version::new(gl);
let quad_pipeline = quad::Pipeline::new(gl, &shader_version);
let triangle_pipeline = triangle::Pipeline::new(gl, &shader_version);
Self {
quad_pipeline,

View file

@ -13,6 +13,8 @@
#![forbid(rust_2018_idioms)]
#![cfg_attr(docsrs, feature(doc_cfg))]
pub use glow;
mod backend;
mod program;
mod quad;

View file

@ -1,28 +1,122 @@
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", version.minor + 3),
format!(
"#version 1{}0\n#define HIGHER_THAN_300 1",
version.minor + 3
),
),
// OpenGL 3.3+
(3 | 4, _, false) => (
format!("#version {}{}0", version.major, version.minor),
format!(
"#version {}{}0\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,
shader_sources: &[(u32, &str)],
shaders: &[Shader],
attributes: &[(u32, &str)],
) -> <glow::Context as HasContext>::Program {
let program = gl.create_program().expect("Cannot create program");
let mut shaders = Vec::with_capacity(shader_sources.len());
for shader in shaders {
gl.attach_shader(program, shader.0);
}
for (shader_type, shader_source) in shader_sources.iter() {
let shader = gl
.create_shader(*shader_type)
.expect("Cannot create shader");
gl.shader_source(shader, 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);
for (i, name) in attributes {
gl.bind_attrib_location(program, *i, name);
}
gl.link_program(program);
@ -31,8 +125,8 @@ pub unsafe fn create(
}
for shader in shaders {
gl.detach_shader(program, shader);
gl.delete_shader(shader);
gl.detach_shader(program, shader.0);
gl.delete_shader(shader.0);
}
program

View file

@ -1,77 +1,35 @@
mod compatibility;
mod core;
use crate::program;
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,
pub enum Pipeline {
Core(core::Pipeline),
Compatibility(compatibility::Pipeline),
}
impl Pipeline {
pub fn new(gl: &glow::Context) -> Pipeline {
let program = unsafe {
program::create(
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,
&[
(glow::VERTEX_SHADER, include_str!("shader/quad.vert")),
(glow::FRAGMENT_SHADER, include_str!("shader/quad.frag")),
],
)
};
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));
let matrix: [f32; 16] = Transformation::identity().into();
gl.uniform_matrix_4_f32_slice(
Some(&transform_location),
false,
&matrix,
);
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,
shader_version,
))
}
}
@ -84,152 +42,27 @@ impl Pipeline {
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 {
let matrix: [f32; 16] = transformation.into();
gl.uniform_matrix_4_f32_slice(
Some(&self.transform_location),
false,
&matrix,
);
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,
match self {
Pipeline::Core(pipeline) => {
pipeline.draw(
gl,
target_height,
instances,
transformation,
scale,
bounds,
);
}
self.current_target_height = target_height;
}
let mut i = 0;
let total = instances.len();
while i < total {
let end = (i + MAX_INSTANCES).min(total);
let amount = end - i;
unsafe {
gl.buffer_sub_data_u8_slice(
glow::ARRAY_BUFFER,
0,
bytemuck::cast_slice(&instances[i..end]),
);
gl.draw_arrays_instanced(
glow::TRIANGLE_STRIP,
0,
4,
amount as i32,
Pipeline::Compatibility(pipeline) => {
pipeline.draw(
gl,
target_height,
instances,
transformation,
scale,
bounds,
);
}
i += MAX_INSTANCES;
}
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,
1,
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 + 1),
);
gl.vertex_attrib_divisor(5, 1);
gl.bind_vertex_array(None);
gl.bind_buffer(glow::ARRAY_BUFFER, None);
(vertex_array, buffer)
}

View file

@ -0,0 +1,360 @@
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));
let matrix: [f32; 16] = Transformation::identity().into();
gl.uniform_matrix_4_f32_slice(
Some(&transform_location),
false,
&matrix,
);
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(|quad| Vertex::from_quad(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| {
[
0 + 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 {
let matrix: [f32; 16] = transformation.into();
gl.uniform_matrix_4_f32_slice(
Some(&self.transform_location),
false,
&matrix,
);
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,
1,
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 + 1),
);
gl.enable_vertex_attrib_array(6);
gl.vertex_attrib_pointer_f32(
6,
2,
glow::FLOAT,
false,
stride,
4 * (2 + 2 + 4 + 4 + 1 + 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,
/// 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
},
]
}
}

246
glow/src/quad/core.rs Normal file
View file

@ -0,0 +1,246 @@
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));
let matrix: [f32; 16] = Transformation::identity().into();
gl.uniform_matrix_4_f32_slice(
Some(&transform_location),
false,
&matrix,
);
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 {
let matrix: [f32; 16] = transformation.into();
gl.uniform_matrix_4_f32_slice(
Some(&self.transform_location),
false,
&matrix,
);
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,
1,
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 + 1),
);
gl.vertex_attrib_divisor(5, 1);
gl.bind_vertex_array(None);
gl.bind_buffer(glow::ARRAY_BUFFER, None);
(vertex_array, buffer)
}

View file

@ -0,0 +1,18 @@
#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,13 +1,11 @@
#version 330
uniform mat4 u_Transform;
layout(location = 0) in vec2 i_Position;
layout(location = 1) in vec4 i_Color;
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

@ -0,0 +1,67 @@
#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 float 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);
}
void main() {
vec2 fragCoord = vec2(gl_FragCoord.x, u_ScreenHeight - gl_FragCoord.y);
float internal_border = max(v_BorderRadius - 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,
v_BorderRadius
);
float radius_alpha =
1.0 - smoothstep(max(v_BorderRadius - 0.5, 0.0), v_BorderRadius + 0.5, d);
gl_FragColor = vec4(mixed_color.xyz, mixed_color.w * radius_alpha);
}

View file

@ -0,0 +1,44 @@
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 float 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 float v_BorderRadius;
varying float v_BorderWidth;
void main() {
vec2 p_Pos = i_Pos * u_Scale;
vec2 p_Scale = i_Scale * u_Scale;
float i_BorderRadius = min(
i_BorderRadius,
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,4 +1,15 @@
#version 330
#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;
@ -9,9 +20,7 @@ in vec2 v_Scale;
in float v_BorderRadius;
in float v_BorderWidth;
out vec4 o_Color;
float distance(in vec2 frag_coord, in vec2 position, in vec2 size, float radius)
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;
@ -35,10 +44,10 @@ void main() {
vec2 fragCoord = vec2(gl_FragCoord.x, u_ScreenHeight - gl_FragCoord.y);
// TODO: Remove branching (?)
if(v_BorderWidth > 0) {
if(v_BorderWidth > 0.0) {
float internal_border = max(v_BorderRadius - v_BorderWidth, 0.0);
float internal_distance = distance(
float internal_distance = fDistance(
fragCoord,
v_Pos + vec2(v_BorderWidth),
v_Scale - vec2(v_BorderWidth * 2.0),
@ -56,7 +65,7 @@ void main() {
mixed_color = v_Color;
}
float d = distance(
float d = fDistance(
fragCoord,
v_Pos,
v_Scale,
@ -66,5 +75,5 @@ void main() {
float radius_alpha =
1.0 - smoothstep(max(v_BorderRadius - 0.5, 0.0), v_BorderRadius + 0.5, d);
o_Color = vec4(mixed_color.xyz, mixed_color.w * radius_alpha);
gl_FragColor = vec4(mixed_color.xyz, mixed_color.w * radius_alpha);
}

View file

@ -1,14 +1,12 @@
#version 330
uniform mat4 u_Transform;
uniform float u_Scale;
layout(location = 0) in vec2 i_Pos;
layout(location = 1) in vec2 i_Scale;
layout(location = 2) in vec4 i_Color;
layout(location = 3) in vec4 i_BorderColor;
layout(location = 4) in float i_BorderRadius;
layout(location = 5) in float i_BorderWidth;
in vec2 i_Pos;
in vec2 i_Scale;
in vec4 i_Color;
in vec4 i_BorderColor;
in float i_BorderRadius;
in float i_BorderWidth;
out vec4 v_Color;
out vec4 v_BorderColor;
@ -17,7 +15,7 @@ out vec2 v_Scale;
out float v_BorderRadius;
out float v_BorderWidth;
const vec2 positions[4] = vec2[](
vec2 positions[4] = vec2[](
vec2(0.0, 0.0),
vec2(0.0, 1.0),
vec2(1.0, 0.0),

View file

@ -1,9 +0,0 @@
#version 330
in vec4 v_Color;
out vec4 o_Color;
void main() {
o_Color = v_Color;
}

View file

@ -1,5 +1,5 @@
//! Draw meshes of triangles.
use crate::program;
use crate::program::{self, Shader};
use crate::Transformation;
use glow::HasContext;
use iced_graphics::layer;
@ -21,17 +21,26 @@ pub(crate) struct Pipeline {
}
impl Pipeline {
pub fn new(gl: &glow::Context) -> 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/triangle.vert"),
);
let fragment_shader = Shader::fragment(
gl,
shader_version,
include_str!("shader/common/triangle.frag"),
);
program::create(
gl,
&[
(glow::VERTEX_SHADER, include_str!("shader/triangle.vert")),
(
glow::FRAGMENT_SHADER,
include_str!("shader/triangle.frag"),
),
],
&[vertex_shader, fragment_shader],
&[(0, "i_Position"), (1, "i_Color")],
)
};

View file

@ -20,6 +20,13 @@ impl iced_graphics::window::GLCompositor for Compositor {
) -> Result<(Self, Self::Renderer), Error> {
let gl = glow::Context::from_loader_function(loader_function);
let version = gl.version();
log::info!("Version: {:?}", version);
log::info!("Embedded: {}", 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);

View file

@ -61,10 +61,23 @@ where
settings.id,
);
let context = ContextBuilder::new()
let opengl_builder = ContextBuilder::new()
.with_vsync(true)
.with_multisampling(C::sample_count(&compositor_settings) as u16)
.build_windowed(builder, &event_loop)
.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)
};
let context = first_builder
.build_windowed(builder.clone(), &event_loop)
.or_else(|_| second_builder.build_windowed(builder, &event_loop))
.map_err(|error| {
use glutin::CreationError;

View file

@ -20,6 +20,7 @@ pub use iced_native::*;
pub mod application;
pub use iced_winit::clipboard;
pub use iced_winit::conversion;
pub use iced_winit::settings;
pub use iced_winit::window;
pub use iced_winit::{Error, Mode};

View file

@ -55,6 +55,15 @@ 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> {
@ -73,6 +82,7 @@ impl<Flags> Settings<Flags> {
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,
}
}
}
@ -91,6 +101,7 @@ where
text_multithreading: false,
antialiasing: false,
exit_on_close_request: true,
try_opengles_first: false,
}
}
}
@ -103,6 +114,7 @@ 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

@ -38,6 +38,12 @@ pub struct Settings<Flags> {
/// Whether the [`Application`] should exit when the user requests the
/// window to close (e.g. the user presses the close button).
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.
pub try_opengles_first: bool,
}
/// The window settings of an application.