aboutsummaryrefslogtreecommitdiff
path: root/src/render_helpers
diff options
context:
space:
mode:
authorIvan Molodetskikh <yalterz@gmail.com>2024-04-13 11:07:23 +0400
committerIvan Molodetskikh <yalterz@gmail.com>2024-04-13 11:07:23 +0400
commit71be19b234d58f4ec447e921633506beb81a52c0 (patch)
treea6b69d57a3e2edc9d8dac4c969548f227cbf341a /src/render_helpers
parent4fd9300bdb07e90c26df28461f9bd6591c3d1d41 (diff)
downloadniri-71be19b234d58f4ec447e921633506beb81a52c0.tar.gz
niri-71be19b234d58f4ec447e921633506beb81a52c0.tar.bz2
niri-71be19b234d58f4ec447e921633506beb81a52c0.zip
Implement window resize animations
Diffstat (limited to 'src/render_helpers')
-rw-r--r--src/render_helpers/crossfade.rs156
-rw-r--r--src/render_helpers/mod.rs3
-rw-r--r--src/render_helpers/primary_gpu_pixel_shader_with_textures.rs423
-rw-r--r--src/render_helpers/resources.rs106
-rw-r--r--src/render_helpers/shaders/crossfade.frag31
-rw-r--r--src/render_helpers/shaders/mod.rs24
-rw-r--r--src/render_helpers/shaders/texture.vert25
7 files changed, 767 insertions, 1 deletions
diff --git a/src/render_helpers/crossfade.rs b/src/render_helpers/crossfade.rs
new file mode 100644
index 00000000..65f96482
--- /dev/null
+++ b/src/render_helpers/crossfade.rs
@@ -0,0 +1,156 @@
+use std::collections::HashMap;
+
+use smithay::backend::renderer::element::{Element, Id, Kind, RenderElement, UnderlyingStorage};
+use smithay::backend::renderer::gles::{GlesError, GlesFrame, GlesRenderer, GlesTexture, Uniform};
+use smithay::backend::renderer::utils::{CommitCounter, DamageSet};
+use smithay::utils::{Buffer, Logical, Physical, Rectangle, Scale, Size, Transform};
+
+use super::primary_gpu_pixel_shader_with_textures::PrimaryGpuPixelShaderWithTexturesRenderElement;
+use super::renderer::AsGlesFrame;
+use super::shaders::Shaders;
+use crate::backend::tty::{TtyFrame, TtyRenderer, TtyRendererError};
+
+#[derive(Debug)]
+pub struct CrossfadeRenderElement(PrimaryGpuPixelShaderWithTexturesRenderElement);
+
+impl CrossfadeRenderElement {
+ #[allow(clippy::too_many_arguments)]
+ pub fn new(
+ renderer: &mut GlesRenderer,
+ area: Rectangle<i32, Logical>,
+ scale: Scale<f64>,
+ texture_from: (GlesTexture, Rectangle<i32, Physical>),
+ size_from: Size<i32, Logical>,
+ texture_to: (GlesTexture, Rectangle<i32, Physical>),
+ size_to: Size<i32, Logical>,
+ amount: f32,
+ result_alpha: f32,
+ ) -> Option<Self> {
+ let (texture_from, texture_from_geo) = texture_from;
+ let (texture_to, texture_to_geo) = texture_to;
+
+ let scale_from = area.size.to_f64() / size_from.to_f64();
+ let scale_to = area.size.to_f64() / size_to.to_f64();
+
+ let tex_from_geo = texture_from_geo.to_f64().upscale(scale_from);
+ let tex_to_geo = texture_to_geo.to_f64().upscale(scale_to);
+ let combined_geo = tex_from_geo.merge(tex_to_geo);
+
+ let area = Rectangle::from_loc_and_size(
+ area.loc + combined_geo.loc.to_logical(scale).to_i32_round(),
+ combined_geo.size.to_logical(scale).to_i32_round(),
+ );
+
+ let tex_from_loc = (tex_from_geo.loc - combined_geo.loc)
+ .downscale((combined_geo.size.w, combined_geo.size.h));
+ let tex_to_loc = (tex_to_geo.loc - combined_geo.loc)
+ .downscale((combined_geo.size.w, combined_geo.size.h));
+ let tex_from_size = tex_from_geo.size / combined_geo.size;
+ let tex_to_size = tex_to_geo.size / combined_geo.size;
+
+ // FIXME: cropping this element will mess up the coordinates.
+ Shaders::get(renderer).crossfade.clone().map(|shader| {
+ Self(PrimaryGpuPixelShaderWithTexturesRenderElement::new(
+ shader,
+ HashMap::from([
+ (String::from("tex_from"), texture_from),
+ (String::from("tex_to"), texture_to),
+ ]),
+ area,
+ None,
+ result_alpha,
+ vec![
+ Uniform::new(
+ "tex_from_loc",
+ (tex_from_loc.x as f32, tex_from_loc.y as f32),
+ ),
+ Uniform::new(
+ "tex_from_size",
+ (tex_from_size.x as f32, tex_from_size.y as f32),
+ ),
+ Uniform::new("tex_to_loc", (tex_to_loc.x as f32, tex_to_loc.y as f32)),
+ Uniform::new("tex_to_size", (tex_to_size.x as f32, tex_to_size.y as f32)),
+ Uniform::new("amount", amount),
+ ],
+ Kind::Unspecified,
+ ))
+ })
+ }
+}
+
+impl Element for CrossfadeRenderElement {
+ fn id(&self) -> &Id {
+ self.0.id()
+ }
+
+ fn current_commit(&self) -> CommitCounter {
+ self.0.current_commit()
+ }
+
+ fn geometry(&self, scale: Scale<f64>) -> Rectangle<i32, Physical> {
+ self.0.geometry(scale)
+ }
+
+ fn transform(&self) -> Transform {
+ self.0.transform()
+ }
+
+ fn src(&self) -> Rectangle<f64, Buffer> {
+ self.0.src()
+ }
+
+ fn damage_since(
+ &self,
+ scale: Scale<f64>,
+ commit: Option<CommitCounter>,
+ ) -> DamageSet<i32, Physical> {
+ self.0.damage_since(scale, commit)
+ }
+
+ fn opaque_regions(&self, scale: Scale<f64>) -> Vec<Rectangle<i32, Physical>> {
+ self.0.opaque_regions(scale)
+ }
+
+ fn alpha(&self) -> f32 {
+ self.0.alpha()
+ }
+
+ fn kind(&self) -> Kind {
+ self.0.kind()
+ }
+}
+
+impl RenderElement<GlesRenderer> for CrossfadeRenderElement {
+ fn draw(
+ &self,
+ frame: &mut GlesFrame<'_>,
+ src: Rectangle<f64, Buffer>,
+ dst: Rectangle<i32, Physical>,
+ damage: &[Rectangle<i32, Physical>],
+ ) -> Result<(), GlesError> {
+ RenderElement::<GlesRenderer>::draw(&self.0, frame, src, dst, damage)?;
+ Ok(())
+ }
+
+ fn underlying_storage(&self, renderer: &mut GlesRenderer) -> Option<UnderlyingStorage> {
+ self.0.underlying_storage(renderer)
+ }
+}
+
+impl<'render> RenderElement<TtyRenderer<'render>> for CrossfadeRenderElement {
+ fn draw(
+ &self,
+ frame: &mut TtyFrame<'_, '_>,
+ src: Rectangle<f64, Buffer>,
+ dst: Rectangle<i32, Physical>,
+ damage: &[Rectangle<i32, Physical>],
+ ) -> Result<(), TtyRendererError<'render>> {
+ let gles_frame = frame.as_gles_frame();
+ RenderElement::<GlesRenderer>::draw(&self.0, gles_frame, src, dst, damage)?;
+ Ok(())
+ }
+
+ fn underlying_storage(&self, renderer: &mut TtyRenderer<'render>) -> Option<UnderlyingStorage> {
+ self.0.underlying_storage(renderer)
+ }
+}
diff --git a/src/render_helpers/mod.rs b/src/render_helpers/mod.rs
index c445240d..ad3e91b8 100644
--- a/src/render_helpers/mod.rs
+++ b/src/render_helpers/mod.rs
@@ -17,12 +17,15 @@ use smithay::wayland::shm;
use self::primary_gpu_texture::PrimaryGpuTextureRenderElement;
+pub mod crossfade;
pub mod gradient;
pub mod offscreen;
pub mod primary_gpu_pixel_shader;
+pub mod primary_gpu_pixel_shader_with_textures;
pub mod primary_gpu_texture;
pub mod render_elements;
pub mod renderer;
+pub mod resources;
pub mod shaders;
pub mod surface;
diff --git a/src/render_helpers/primary_gpu_pixel_shader_with_textures.rs b/src/render_helpers/primary_gpu_pixel_shader_with_textures.rs
new file mode 100644
index 00000000..9f17cfdb
--- /dev/null
+++ b/src/render_helpers/primary_gpu_pixel_shader_with_textures.rs
@@ -0,0 +1,423 @@
+use std::collections::HashMap;
+use std::ffi::{CStr, CString};
+use std::rc::Rc;
+
+use glam::{Mat3, Vec2};
+use smithay::backend::renderer::element::{Element, Id, Kind, RenderElement, UnderlyingStorage};
+use smithay::backend::renderer::gles::{
+ ffi, link_program, Capability, GlesError, GlesFrame, GlesRenderer, GlesTexture, Uniform,
+ UniformDesc, UniformName,
+};
+use smithay::backend::renderer::utils::CommitCounter;
+use smithay::utils::{Buffer, Logical, Physical, Rectangle, Scale, Transform};
+
+use super::renderer::AsGlesFrame;
+use super::resources::Resources;
+use crate::backend::tty::{TtyFrame, TtyRenderer, TtyRendererError};
+
+/// Wrapper for a pixel shader from the primary GPU for rendering with the primary GPU.
+///
+/// The shader accepts textures as input.
+#[derive(Debug)]
+pub struct PrimaryGpuPixelShaderWithTexturesRenderElement {
+ shader: PixelWithTexturesProgram,
+ textures: HashMap<String, GlesTexture>,
+ id: Id,
+ commit_counter: CommitCounter,
+ area: Rectangle<i32, Logical>,
+ opaque_regions: Vec<Rectangle<i32, Logical>>,
+ alpha: f32,
+ additional_uniforms: Vec<Uniform<'static>>,
+ kind: Kind,
+}
+
+#[derive(Debug, Clone)]
+pub struct PixelWithTexturesProgram(Rc<PixelWithTexturesProgramInner>);
+
+#[derive(Debug)]
+struct PixelWithTexturesProgramInner {
+ program: ffi::types::GLuint,
+ uniform_tex_matrix: ffi::types::GLint,
+ uniform_matrix: ffi::types::GLint,
+ uniform_size: ffi::types::GLint,
+ uniform_alpha: ffi::types::GLint,
+ attrib_vert: ffi::types::GLint,
+ attrib_vert_position: ffi::types::GLint,
+ additional_uniforms: HashMap<String, UniformDesc>,
+ texture_uniforms: HashMap<String, ffi::types::GLint>,
+}
+
+unsafe fn compile_program(
+ gl: &ffi::Gles2,
+ src: &str,
+ additional_uniforms: &[UniformName<'_>],
+ texture_uniforms: &[&str],
+ // destruction_callback_sender: Sender<CleanupResource>,
+) -> Result<PixelWithTexturesProgram, GlesError> {
+ let shader = src;
+
+ let program = unsafe { link_program(gl, include_str!("shaders/texture.vert"), shader)? };
+
+ let vert = CStr::from_bytes_with_nul(b"vert\0").expect("NULL terminated");
+ let vert_position = CStr::from_bytes_with_nul(b"vert_position\0").expect("NULL terminated");
+ let matrix = CStr::from_bytes_with_nul(b"matrix\0").expect("NULL terminated");
+ let tex_matrix = CStr::from_bytes_with_nul(b"tex_matrix\0").expect("NULL terminated");
+ let size = CStr::from_bytes_with_nul(b"size\0").expect("NULL terminated");
+ let alpha = CStr::from_bytes_with_nul(b"alpha\0").expect("NULL terminated");
+
+ Ok(PixelWithTexturesProgram(Rc::new(
+ PixelWithTexturesProgramInner {
+ program,
+ uniform_matrix: gl
+ .GetUniformLocation(program, matrix.as_ptr() as *const ffi::types::GLchar),
+ uniform_tex_matrix: gl
+ .GetUniformLocation(program, tex_matrix.as_ptr() as *const ffi::types::GLchar),
+ uniform_size: gl
+ .GetUniformLocation(program, size.as_ptr() as *const ffi::types::GLchar),
+ uniform_alpha: gl
+ .GetUniformLocation(program, alpha.as_ptr() as *const ffi::types::GLchar),
+ attrib_vert: gl.GetAttribLocation(program, vert.as_ptr() as *const ffi::types::GLchar),
+ attrib_vert_position: gl
+ .GetAttribLocation(program, vert_position.as_ptr() as *const ffi::types::GLchar),
+ additional_uniforms: additional_uniforms
+ .iter()
+ .map(|uniform| {
+ let name =
+ CString::new(uniform.name.as_bytes()).expect("Interior null in name");
+ let location =
+ gl.GetUniformLocation(program, name.as_ptr() as *const ffi::types::GLchar);
+ (
+ uniform.name.clone().into_owned(),
+ UniformDesc {
+ location,
+ type_: uniform.type_,
+ },
+ )
+ })
+ .collect(),
+ texture_uniforms: texture_uniforms
+ .iter()
+ .map(|name_| {
+ let name = CString::new(name_.as_bytes()).expect("Interior null in name");
+ let location =
+ gl.GetUniformLocation(program, name.as_ptr() as *const ffi::types::GLchar);
+ (name_.to_string(), location)
+ })
+ .collect(),
+ },
+ )))
+}
+
+impl PixelWithTexturesProgram {
+ pub fn compile(
+ renderer: &mut GlesRenderer,
+ src: &str,
+ additional_uniforms: &[UniformName<'_>],
+ texture_uniforms: &[&str],
+ ) -> Result<Self, GlesError> {
+ renderer.with_context(move |gl| unsafe {
+ compile_program(gl, src, additional_uniforms, texture_uniforms)
+ })?
+ }
+}
+
+impl PrimaryGpuPixelShaderWithTexturesRenderElement {
+ pub fn new(
+ shader: PixelWithTexturesProgram,
+ textures: HashMap<String, GlesTexture>,
+ area: Rectangle<i32, Logical>,
+ opaque_regions: Option<Vec<Rectangle<i32, Logical>>>,
+ alpha: f32,
+ additional_uniforms: Vec<Uniform<'_>>,
+ kind: Kind,
+ ) -> Self {
+ Self {
+ shader,
+ textures,
+ id: Id::new(),
+ commit_counter: CommitCounter::default(),
+ area,
+ opaque_regions: opaque_regions.unwrap_or_default(),
+ alpha,
+ additional_uniforms: additional_uniforms
+ .into_iter()
+ .map(|u| u.into_owned())
+ .collect(),
+ kind,
+ }
+ }
+}
+
+impl Element for PrimaryGpuPixelShaderWithTexturesRenderElement {
+ fn id(&self) -> &Id {
+ &self.id
+ }
+
+ fn current_commit(&self) -> CommitCounter {
+ self.commit_counter
+ }
+
+ fn src(&self) -> Rectangle<f64, Buffer> {
+ self.area
+ .to_f64()
+ .to_buffer(1.0, Transform::Normal, &self.area.size.to_f64())
+ }
+
+ fn geometry(&self, scale: Scale<f64>) -> Rectangle<i32, Physical> {
+ self.area.to_physical_precise_round(scale)
+ }
+
+ fn opaque_regions(&self, scale: Scale<f64>) -> Vec<Rectangle<i32, Physical>> {
+ self.opaque_regions
+ .iter()
+ .map(|region| region.to_physical_precise_round(scale))
+ .collect()
+ }
+
+ fn alpha(&self) -> f32 {
+ 1.0
+ }
+
+ fn kind(&self) -> Kind {
+ self.kind
+ }
+}
+
+impl RenderElement<GlesRenderer> for PrimaryGpuPixelShaderWithTexturesRenderElement {
+ fn draw(
+ &self,
+ frame: &mut GlesFrame<'_>,
+ _src: Rectangle<f64, Buffer>,
+ dest: Rectangle<i32, Physical>,
+ damage: &[Rectangle<i32, Physical>],
+ ) -> Result<(), GlesError> {
+ let frame = frame.as_gles_frame();
+
+ let Some(resources) = Resources::get(frame) else {
+ return Ok(());
+ };
+ let mut resources = resources.borrow_mut();
+
+ let supports_instancing = frame.capabilities().contains(&Capability::Instancing);
+
+ // prepare the vertices
+ resources.vertices.clear();
+ if supports_instancing {
+ resources.vertices.extend(damage.iter().flat_map(|rect| {
+ let dest_size = dest.size;
+
+ let rect_constrained_loc = rect
+ .loc
+ .constrain(Rectangle::from_extemities((0, 0), dest_size.to_point()));
+ let rect_clamped_size = rect.size.clamp(
+ (0, 0),
+ (dest_size.to_point() - rect_constrained_loc).to_size(),
+ );
+
+ let rect = Rectangle::from_loc_and_size(rect_constrained_loc, rect_clamped_size);
+ [
+ rect.loc.x as f32,
+ rect.loc.y as f32,
+ rect.size.w as f32,
+ rect.size.h as f32,
+ ]
+ }));
+ } else {
+ resources.vertices.extend(damage.iter().flat_map(|rect| {
+ let dest_size = dest.size;
+
+ let rect_constrained_loc = rect
+ .loc
+ .constrain(Rectangle::from_extemities((0, 0), dest_size.to_point()));
+ let rect_clamped_size = rect.size.clamp(
+ (0, 0),
+ (dest_size.to_point() - rect_constrained_loc).to_size(),
+ );
+
+ let rect = Rectangle::from_loc_and_size(rect_constrained_loc, rect_clamped_size);
+ // Add the 4 f32s per damage rectangle for each of the 6 vertices.
+ (0..6).flat_map(move |_| {
+ [
+ rect.loc.x as f32,
+ rect.loc.y as f32,
+ rect.size.w as f32,
+ rect.size.h as f32,
+ ]
+ })
+ }));
+ }
+
+ if resources.vertices.is_empty() {
+ return Ok(());
+ }
+
+ // dest position and scale
+ let mut matrix = Mat3::from_translation(Vec2::new(dest.loc.x as f32, dest.loc.y as f32));
+ let tex_matrix = Mat3::from_scale(Vec2::new(
+ (1.0f64 / dest.size.w as f64) as f32,
+ (1.0f64 / dest.size.h as f64) as f32,
+ ));
+
+ //apply output transformation
+ matrix = Mat3::from_cols_array(frame.projection()) * matrix;
+
+ let program = &self.shader.0;
+
+ // render
+ frame.with_context(move |gl| -> Result<(), GlesError> {
+ unsafe {
+ for (i, texture) in self.textures.values().enumerate() {
+ gl.ActiveTexture(ffi::TEXTURE0 + i as u32);
+ gl.BindTexture(ffi::TEXTURE_2D, texture.tex_id());
+ gl.TexParameteri(ffi::TEXTURE_2D, ffi::TEXTURE_MIN_FILTER, ffi::LINEAR as i32);
+ gl.TexParameteri(ffi::TEXTURE_2D, ffi::TEXTURE_MAG_FILTER, ffi::LINEAR as i32);
+ gl.TexParameteri(
+ ffi::TEXTURE_2D,
+ ffi::TEXTURE_WRAP_S,
+ ffi::CLAMP_TO_BORDER as i32,
+ );
+ gl.TexParameteri(
+ ffi::TEXTURE_2D,
+ ffi::TEXTURE_WRAP_T,
+ ffi::CLAMP_TO_BORDER as i32,
+ );
+ }
+
+ gl.UseProgram(program.program);
+
+ for (i, name) in self.textures.keys().enumerate() {
+ gl.Uniform1i(program.texture_uniforms[name], i as i32);
+ }
+
+ gl.UniformMatrix3fv(
+ program.uniform_matrix,
+ 1,
+ ffi::FALSE,
+ matrix.as_ref().as_ptr(),
+ );
+ gl.UniformMatrix3fv(
+ program.uniform_tex_matrix,
+ 1,
+ ffi::FALSE,
+ tex_matrix.as_ref().as_ptr(),
+ );
+ gl.Uniform2f(program.uniform_size, dest.size.w as f32, dest.size.h as f32);
+ gl.Uniform1f(program.uniform_alpha, self.alpha);
+
+ for uniform in &self.additional_uniforms {
+ let desc =
+ program
+ .additional_uniforms
+ .get(&*uniform.name)
+ .ok_or_else(|| {
+ GlesError::UnknownUniform(uniform.name.clone().into_owned())
+ })?;
+ uniform.value.set(gl, desc)?;
+ }
+
+ gl.EnableVertexAttribArray(program.attrib_vert as u32);
+ gl.BindBuffer(ffi::ARRAY_BUFFER, resources.vbos[0]);
+ gl.VertexAttribPointer(
+ program.attrib_vert as u32,
+ 2,
+ ffi::FLOAT,
+ ffi::FALSE,
+ 0,
+ std::ptr::null(),
+ );
+
+ // vert_position
+ gl.EnableVertexAttribArray(program.attrib_vert_position as u32);
+ gl.BindBuffer(ffi::ARRAY_BUFFER, resources.vbos[1]);
+ gl.BufferData(
+ ffi::ARRAY_BUFFER,
+ (std::mem::size_of::<ffi::types::GLfloat>() * resources.vertices.len())
+ as isize,
+ resources.vertices.as_ptr() as *const _,
+ ffi::STREAM_DRAW,
+ );
+
+ gl.VertexAttribPointer(
+ program.attrib_vert_position as u32,
+ 4,
+ ffi::FLOAT,
+ ffi::FALSE,
+ 0,
+ std::ptr::null(),
+ );
+
+ let damage_len = damage.len() as i32;
+ if supports_instancing {
+ gl.VertexAttribDivisor(program.attrib_vert as u32, 0);
+ gl.VertexAttribDivisor(program.attrib_vert_position as u32, 1);
+ gl.DrawArraysInstanced(ffi::TRIANGLE_STRIP, 0, 4, damage_len);
+ } else {
+ // When we have more than 10 rectangles, draw them in batches of 10.
+ for i in 0..(damage_len - 1) / 10 {
+ gl.DrawArrays(ffi::TRIANGLES, 0, 6);
+
+ // Set damage pointer to the next 10 rectangles.
+ let offset =
+ (i + 1) as usize * 6 * 4 * std::mem::size_of::<ffi::types::GLfloat>();
+ gl.VertexAttribPointer(
+ program.attrib_vert_position as u32,
+ 4,
+ ffi::FLOAT,
+ ffi::FALSE,
+ 0,
+ offset as *const _,
+ );
+ }
+
+ // Draw the up to 10 remaining rectangles.
+ let count = ((damage_len - 1) % 10 + 1) * 6;
+ gl.DrawArrays(ffi::TRIANGLES, 0, count);
+ }
+
+ gl.BindBuffer(ffi::ARRAY_BUFFER, 0);
+ gl.BindTexture(ffi::TEXTURE_2D, 0);
+ gl.ActiveTexture(ffi::TEXTURE0);
+ gl.BindTexture(ffi::TEXTURE_2D, 0);
+ gl.DisableVertexAttribArray(program.attrib_vert as u32);
+ gl.DisableVertexAttribArray(program.attrib_vert_position as u32);
+ }
+
+ Ok(())
+ })??;
+
+ Ok(())
+ }
+
+ fn underlying_storage(&self, _renderer: &mut GlesRenderer) -> Option<UnderlyingStorage> {
+ // If scanout for things other than Wayland buffers is implemented, this will need to take
+ // the target GPU into account.
+ None
+ }
+}
+
+impl<'render> RenderElement<TtyRenderer<'render>>
+ for PrimaryGpuPixelShaderWithTexturesRenderElement
+{
+ fn draw(
+ &self,
+ frame: &mut TtyFrame<'_, '_>,
+ src: Rectangle<f64, Buffer>,
+ dst: Rectangle<i32, Physical>,
+ damage: &[Rectangle<i32, Physical>],
+ ) -> Result<(), TtyRendererError<'render>> {
+ let frame = frame.as_gles_frame();
+
+ RenderElement::<GlesRenderer>::draw(self, frame, src, dst, damage)?;
+
+ Ok(())
+ }
+
+ fn underlying_storage(
+ &self,
+ _renderer: &mut TtyRenderer<'render>,
+ ) -> Option<UnderlyingStorage> {
+ // If scanout for things other than Wayland buffers is implemented, this will need to take
+ // the target GPU into account.
+ None
+ }
+}
diff --git a/src/render_helpers/resources.rs b/src/render_helpers/resources.rs
new file mode 100644
index 00000000..0aa1fed4
--- /dev/null
+++ b/src/render_helpers/resources.rs
@@ -0,0 +1,106 @@
+use std::cell::RefCell;
+use std::rc::Rc;
+
+use smithay::backend::renderer::gles::{ffi, Capability, GlesError, GlesFrame, GlesRenderer};
+
+pub struct Resources {
+ pub vertices: Vec<f32>,
+ pub vbos: [ffi::types::GLuint; 2],
+}
+
+static INSTANCED_VERTS: [ffi::types::GLfloat; 8] = [
+ 1.0, 0.0, // top right
+ 0.0, 0.0, // top left
+ 1.0, 1.0, // bottom right
+ 0.0, 1.0, // bottom left
+];
+
+/// Vertices for rendering individual triangles.
+const MAX_RECTS_PER_DRAW: usize = 10;
+const TRIANGLE_VERTS: [ffi::types::GLfloat; 12 * MAX_RECTS_PER_DRAW] = triangle_verts();
+const fn triangle_verts() -> [ffi::types::GLfloat; 12 * MAX_RECTS_PER_DRAW] {
+ let mut verts = [0.; 12 * MAX_RECTS_PER_DRAW];
+ let mut i = 0;
+ loop {
+ // Top Left.
+ verts[i * 12] = 0.0;
+ verts[i * 12 + 1] = 0.0;
+
+ // Bottom left.
+ verts[i * 12 + 2] = 0.0;
+ verts[i * 12 + 3] = 1.0;
+
+ // Bottom right.
+ verts[i * 12 + 4] = 1.0;
+ verts[i * 12 + 5] = 1.0;
+
+ // Top left.
+ verts[i * 12 + 6] = 0.0;
+ verts[i * 12 + 7] = 0.0;
+
+ // Bottom right.
+ verts[i * 12 + 8] = 1.0;
+ verts[i * 12 + 9] = 1.0;
+
+ // Top right.
+ verts[i * 12 + 10] = 1.0;
+ verts[i * 12 + 11] = 0.0;
+
+ i += 1;
+ if i == MAX_RECTS_PER_DRAW {
+ break;
+ }
+ }
+ verts
+}
+
+impl Resources {
+ fn create(renderer: &mut GlesRenderer) -> Result<Self, GlesError> {
+ let _span = tracy_client::span!("Resources::init");
+
+ let supports_instancing = renderer.capabilities().contains(&Capability::Instancing);
+ renderer.with_context(|gl| unsafe {
+ let vertices: &[ffi::types::GLfloat] = if supports_instancing {
+ &INSTANCED_VERTS
+ } else {
+ &TRIANGLE_VERTS
+ };
+
+ let mut vbos = [0; 2];
+ gl.GenBuffers(vbos.len() as i32, vbos.as_mut_ptr());
+ gl.BindBuffer(ffi::ARRAY_BUFFER, vbos[0]);
+ gl.BufferData(
+ ffi::ARRAY_BUFFER,
+ std::mem::size_of_val(vertices) as isize,
+ vertices.as_ptr() as *const _,
+ ffi::STATIC_DRAW,
+ );
+
+ gl.BindBuffer(ffi::ARRAY_BUFFER, 0);
+
+ Self {
+ vertices: vec![],
+ vbos,
+ }
+ })
+ }
+
+ pub fn get(frame: &mut GlesFrame) -> Option<Rc<RefCell<Self>>> {
+ let data = frame.egl_context().user_data();
+ data.get().cloned()
+ }
+}
+
+pub fn init(renderer: &mut GlesRenderer) {
+ match Resources::create(renderer) {
+ Ok(resources) => {
+ let data = renderer.egl_context().user_data();
+ if !data.insert_if_missing(|| Rc::new(RefCell::new(resources))) {
+ error!("resources were already initialized");
+ }
+ }
+ Err(err) => {
+ warn!("error creating resources for rendering: {err:?}");
+ }
+ }
+}
diff --git a/src/render_helpers/shaders/crossfade.frag b/src/render_helpers/shaders/crossfade.frag
new file mode 100644
index 00000000..56280ad2
--- /dev/null
+++ b/src/render_helpers/shaders/crossfade.frag
@@ -0,0 +1,31 @@
+#version 100
+
+precision mediump float;
+
+uniform sampler2D tex_from;
+uniform vec2 tex_from_loc;
+uniform vec2 tex_from_size;
+
+uniform sampler2D tex_to;
+uniform vec2 tex_to_loc;
+uniform vec2 tex_to_size;
+
+uniform float alpha;
+uniform float amount;
+
+uniform vec2 size;
+varying vec2 v_coords;
+
+void main() {
+ vec2 coords_from = (v_coords - tex_from_loc) / tex_from_size;
+ vec2 coords_to = (v_coords - tex_to_loc) / tex_to_size;
+
+ vec4 color_from = texture2D(tex_from, coords_from);
+ vec4 color_to = texture2D(tex_to, coords_to);
+
+ vec4 color = mix(color_from, color_to, amount);
+ color = color * alpha;
+
+ gl_FragColor = color;
+}
+
diff --git a/src/render_helpers/shaders/mod.rs b/src/render_helpers/shaders/mod.rs
index 1adbaf82..ebe0d08c 100644
--- a/src/render_helpers/shaders/mod.rs
+++ b/src/render_helpers/shaders/mod.rs
@@ -1,9 +1,11 @@
use smithay::backend::renderer::gles::{GlesPixelProgram, GlesRenderer, UniformName, UniformType};
+use super::primary_gpu_pixel_shader_with_textures::PixelWithTexturesProgram;
use super::renderer::NiriRenderer;
pub struct Shaders {
pub gradient_border: Option<GlesPixelProgram>,
+ pub crossfade: Option<PixelWithTexturesProgram>,
}
impl Shaders {
@@ -26,7 +28,27 @@ impl Shaders {
})
.ok();
- Self { gradient_border }
+ let crossfade = PixelWithTexturesProgram::compile(
+ renderer,
+ include_str!("crossfade.frag"),
+ &[
+ UniformName::new("tex_from_loc", UniformType::_2f),
+ UniformName::new("tex_from_size", UniformType::_2f),
+ UniformName::new("tex_to_loc", UniformType::_2f),
+ UniformName::new("tex_to_size", UniformType::_2f),
+ UniformName::new("amount", UniformType::_1f),
+ ],
+ &["tex_from", "tex_to"],
+ )
+ .map_err(|err| {
+ warn!("error compiling crossfade shader: {err:?}");
+ })
+ .ok();
+
+ Self {
+ gradient_border,
+ crossfade,
+ }
}
pub fn get(renderer: &mut impl NiriRenderer) -> &Self {
diff --git a/src/render_helpers/shaders/texture.vert b/src/render_helpers/shaders/texture.vert
new file mode 100644
index 00000000..a59870b8
--- /dev/null
+++ b/src/render_helpers/shaders/texture.vert
@@ -0,0 +1,25 @@
+#version 100
+
+uniform mat3 matrix;
+uniform mat3 tex_matrix;
+
+attribute vec2 vert;
+attribute vec4 vert_position;
+
+varying vec2 v_coords;
+
+mat2 scale(vec2 scale_vec){
+ return mat2(
+ scale_vec.x, 0.0,
+ 0.0, scale_vec.y
+ );
+}
+
+void main() {
+ vec2 vert_transform_translation = vert_position.xy;
+ vec2 vert_transform_scale = vert_position.zw;
+ vec3 position = vec3(vert * scale(vert_transform_scale) + vert_transform_translation, 1.0);
+ v_coords = (tex_matrix * position).xy;
+ gl_Position = vec4(matrix * position, 1.0);
+}
+