use crate::program;
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,
(vertex_version, fragment_version): &(String, String),
) -> Pipeline {
let program = unsafe {
program::create(
gl,
&[
(
glow::VERTEX_SHADER,
&format!(
"{}\n{}",
vertex_version,
include_str!("../shader/compatibility/quad.vert")
),
),
(
glow::FRAGMENT_SHADER,
&format!(
"{}\n{}",
fragment_version,
include_str!("../shader/compatibility/quad.frag")
),
),
],
&[
(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
},
]
}
}