Compare commits

...

20 Commits

Author SHA1 Message Date
fe7a002a5f
Removed an argument into PanelsRenderer:render_panel_geometry. 2025-09-17 19:20:16 +01:00
84ff5bf4df
Each shader render function now returns it's own metrics type. 2025-09-17 19:17:05 +01:00
dba540ee87
Split out slide shader rendering. 2025-09-17 19:15:47 +01:00
cac0b400bf
Split out fade shader rendering. 2025-09-17 19:14:28 +01:00
82d39ac190
Split out none shader rendering. 2025-09-17 19:12:54 +01:00
9a32a9addb
Removed create_uniforms shader metrics. 2025-09-17 19:11:03 +01:00
1beee7dc11
Extracted panel rendering into it's own function. 2025-09-17 19:08:11 +01:00
1bd36da1fa
Extracted panel geometries rendering into it's own function. 2025-09-17 19:04:20 +01:00
cd52e98000
Removed todo. 2025-09-17 18:59:41 +01:00
f124a34320
Added a render panels metric for all shaders. 2025-09-17 18:53:24 +01:00
23c464949d
Moved shader geometry uniforms creation to their own modules. 2025-09-17 18:47:33 +01:00
3e1df2bf2d
Created Panel{None, Slide}ImagesShared.
Moved `PanelsRendererShared::{none, slide}_uniforms_bind_group_layout` to `Panel{None, Slide}ImagesShared`.
2025-09-17 18:38:02 +01:00
736686a31e
Moved PanelsRendererShared::fade_uniforms_bind_group_layout to PanelFadeImagesShared. 2025-09-17 18:34:25 +01:00
9cf59df867
Fade shader now renders the 3 images separately. 2025-09-17 18:30:16 +01:00
40188d9f4e
Metrics are now tracked separately per shader. 2025-09-17 17:47:34 +01:00
4702a15f16
Fixed slide shader using the wrong geometry uniforms layout. 2025-09-17 17:43:41 +01:00
dec17bb280
Removed write_uniforms macro. 2025-09-17 17:12:01 +01:00
085fc09dab
Moved extra bind groups binding to render_panel_geometry. 2025-09-17 17:00:29 +01:00
b90d926a6a
Each shader now has it's own uniforms. 2025-09-17 16:58:26 +01:00
84f3cfc252
Renamed PanelsRenderer::write_bind_uniforms to render_panel_geometry and made it responsible for the draw call. 2025-09-17 16:23:37 +01:00
16 changed files with 979 additions and 585 deletions

View File

@ -170,3 +170,14 @@ where
tracing::warn!("Unable to spawn task {name:?}: {}", err.pretty());
}
}
/// Iterator chain
pub macro iter_chain {
($only:expr $(,)?) => {
$only
},
($first:expr, $($rest:expr),* $(,)?) => {
std::iter::chain($first, $crate::iter_chain!($($rest,)*))
},
}

View File

@ -1,17 +1,77 @@
//! Fade shader
//! None shader
// Imports
#import fade::vertex
#import fade::frag
#import fade::stage_io::{VertexInput, VertexOutput, FragOutput}
/// Uniforms
struct Uniforms {
pos_matrix: mat4x4<f32>,
image_ratio: vec2<f32>,
progress: f32,
alpha: f32,
#ifdef FADE_WHITE
mix_strength: f32,
#else ifdef FADE_OUT
strength: f32,
#else ifdef FADE_IN
strength: f32,
#endif
};
/// Uniforms
@group(0) @binding(0)
var<uniform> uniforms: Uniforms;
/// Vertex output
struct VertexOutput {
@builtin(position)
pos: vec4<f32>,
@location(0)
uvs: vec2<f32>,
};
@vertex
fn vs_main(in: VertexInput) -> VertexOutput {
return vertex::main(in);
fn vs_main(
@location(0) pos: vec2<f32>,
@location(1) uvs: vec2<f32>,
) -> VertexOutput {
var out: VertexOutput;
out.pos = uniforms.pos_matrix * vec4<f32>(pos, 0.0, 1.0);
out.uvs = uvs;
return out;
}
// Image
@group(1) @binding(0) var image: texture_2d<f32>;
@group(1) @binding(1) var image_sampler: sampler;
@fragment
fn fs_main(in: VertexOutput) -> FragOutput {
return frag::main(in);
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
// Calculate the uvs for this pixel
let uvs_offset = (vec2(1.0, 1.0) - uniforms.image_ratio) * uniforms.progress;
var uvs = in.uvs * uniforms.image_ratio + uvs_offset;
#ifdef FADE_OUT
let mid = uniforms.image_ratio / 2.0 + uvs_offset;
uvs = mid + (uvs - mid) * pow(uniforms.alpha, uniforms.strength);
#else ifdef FADE_IN
let mid = uniforms.image_ratio / 2.0 + uvs_offset;
uvs = mid + (uvs - mid) / pow(uniforms.alpha, uniforms.strength);
#endif
// If we'd sample outside the image, discard this pixel instead
// TODO: Set alpha to 0 instead of discarding?
if any(uvs < vec2(0.0, 0.0) | uvs > vec2(1.0, 1.0)) {
discard;
}
// Otherwise, we'll sample and return the color
var color = textureSample(image, image_sampler, uvs);
#ifdef FADE_WHITE
color = mix(
color,
vec4(1.0, 1.0, 1.0, 1.0),
uniforms.mix_strength,
);
#endif
return vec4(color.rgb, uniforms.alpha);
}

View File

@ -1,112 +0,0 @@
//! Frag shader
// Imports
#import fade::stage_io::{VertexOutput, FragOutput}
#import fade::uniforms::{uniforms, ImageUniforms}
// Bindings
@group(1) @binding(0) var image_prev: texture_2d<f32>;
@group(1) @binding(1) var image_cur: texture_2d<f32>;
@group(1) @binding(2) var image_next: texture_2d<f32>;
@group(1) @binding(3) var image_sampler: sampler;
struct Sampled {
color: vec4<f32>,
uvs : vec2<f32>,
}
// Samples an image
fn sample(image: texture_2d<f32>, in_uvs: vec2<f32>, image_uniforms: ImageUniforms, progress_raw: f32, alpha: f32) -> Sampled {
var sampled: Sampled;
var uvs = in_uvs;
var progress: f32;
if image_uniforms.swap_dir == 0 {
progress = progress_raw;
} else {
progress = 1.0 - progress_raw;
}
// Then apply the image ratio and delta
let uvs_delta = (vec2<f32>(1.0, 1.0) - image_uniforms.image_ratio) * progress;
uvs = uvs * image_uniforms.image_ratio + uvs_delta;
// Offset it, if necessary
#ifdef FADE_OUT
let mid = vec2<f32>(image_uniforms.image_ratio.x / 2.0 + uvs_delta.x, image_uniforms.image_ratio.y / 2.0 + uvs_delta.y);
uvs = (uvs.xy - mid) * pow(alpha, uniforms.strength) + mid;
#else ifdef FADE_IN
let mid = vec2<f32>(image_uniforms.image_ratio.x / 2.0 + uvs_delta.x, image_uniforms.image_ratio.y / 2.0 + uvs_delta.y);
uvs = (uvs.xy - mid) / pow(alpha, uniforms.strength) + mid;
#endif
sampled.color = textureSample(image, image_sampler, uvs);
sampled.uvs = uvs;
return sampled;
}
fn main(in: VertexOutput) -> FragOutput {
var out: FragOutput;
let p = uniforms.progress;
let f = uniforms.fade_duration;
// Full duration an image is on screen (including the fades)
let d = 1.0 + 2.0 * f;
let progress_prev = 1.0 - max((f - p) / d, 0.0);
let progress_cur = (p + f) / d;
let progress_next = max((p - 1.0 + f) / d, 0.0);
let alpha_prev = 0.5 * saturate(1.0 - ( p) / f);
let alpha_next = 0.5 * saturate(1.0 - (1.0 - p) / f);
let alpha_cur = 1.0 - max(alpha_prev, alpha_next);
// Sample the images
let sample_prev = sample(image_prev, in.uvs, uniforms.prev, progress_prev, alpha_prev);
let sample_cur = sample( image_cur, in.uvs, uniforms.cur , progress_cur , alpha_cur );
let sample_next = sample(image_next, in.uvs, uniforms.next, progress_next, alpha_next);
// Then mix the color
// TODO: Don't repeat this once we're able to use `defined(FADE_BASIC) || defined(FADE_OUT)`
#ifdef FADE_BASIC
out.color =
alpha_prev * sample_prev.color +
alpha_cur * sample_cur .color +
alpha_next * sample_next.color;
out.color.a = 1.0;
#else ifdef FADE_OUT
out.color =
alpha_prev * sample_prev.color +
alpha_cur * sample_cur .color +
alpha_next * sample_next.color;
out.color.a = 1.0;
#else ifdef FADE_WHITE
out.color =
alpha_prev * sample_prev.color +
alpha_cur * sample_cur .color +
alpha_next * sample_next.color;
out.color = mix(
out.color,
vec4(1.0, 1.0, 1.0, 1.0),
uniforms.strength * max(4.0 * alpha_cur * alpha_prev, 4.0 * alpha_cur * alpha_next)
);
out.color.a = 1.0;
#else ifdef FADE_IN
let contained_prev = sample_prev.uvs.x >= 0.0 && sample_prev.uvs.x <= 1.0 && sample_prev.uvs.y >= 0.0 && sample_prev.uvs.y <= 1.0;
let contained_cur = sample_cur .uvs.x >= 0.0 && sample_cur .uvs.x <= 1.0 && sample_cur .uvs.y >= 0.0 && sample_cur .uvs.y <= 1.0;
let contained_next = sample_next.uvs.x >= 0.0 && sample_next.uvs.x <= 1.0 && sample_next.uvs.y >= 0.0 && sample_next.uvs.y <= 1.0;
out.color =
alpha_prev * sample_prev.color * f32(contained_prev) +
alpha_cur * sample_cur .color * f32(contained_cur ) +
alpha_next * sample_next.color * f32(contained_next) ;
out.color.a = f32(contained_prev || contained_cur || contained_next);
#endif
return out;
}

View File

@ -1,25 +0,0 @@
//! Stage Input/Output
// Vertex input
struct VertexInput {
@location(0)
pos: vec2<f32>,
@location(1)
uvs: vec2<f32>,
};
// Vertex output / Frag Input
struct VertexOutput {
@builtin(position)
pos: vec4<f32>,
@location(0)
uvs: vec2<f32>,
};
// Frag output
struct FragOutput {
@location(0)
color: vec4<f32>,
};

View File

@ -1,30 +0,0 @@
//! Uniforms
/// Uniforms for each image
struct ImageUniforms {
image_ratio: vec2<f32>,
swap_dir: u32,
}
/// Uniforms
struct Uniforms {
pos_matrix: mat4x4<f32>,
prev: ImageUniforms,
cur: ImageUniforms,
next: ImageUniforms,
fade_duration: f32,
progress: f32,
// TODO: Reduce this repetition
#ifdef FADE_WHITE
strength: f32,
#else ifdef FADE_OUT
strength: f32,
#else ifdef FADE_IN
strength: f32,
#endif
};
// Uniforms
@group(0) @binding(0)
var<uniform> uniforms: Uniforms;

View File

@ -1,15 +0,0 @@
//! Vertex shader
// Imports
#import fade::stage_io::{VertexInput, VertexOutput}
#import fade::uniforms::uniforms
// Vertex entry
fn main(in: VertexInput) -> VertexOutput {
var out: VertexOutput;
out.pos = uniforms.pos_matrix * vec4<f32>(in.pos, 0.0, 1.0);
out.uvs = in.uvs;
return out;
}

View File

@ -2,10 +2,22 @@
// Imports
use {
crate::metrics::{FrameTimes, RenderPanelFrameTime, RenderPanelGeometryFrameTime, RenderPanelsFrameTime},
crate::{
metrics::{
FrameTimes,
RenderPanelFrameTime,
RenderPanelGeometryFadeImageFrameTime,
RenderPanelGeometryFrameTime,
RenderPanelGeometryNoneFrameTime,
RenderPanelGeometrySlideFrameTime,
RenderPanelsFrameTime,
},
panel::state::fade::PanelFadeImageSlot,
},
core::{iter, time::Duration},
itertools::Itertools,
std::collections::{HashMap, HashSet},
zsw_util::iter_chain,
};
/// Draws the render panel frame times
@ -18,7 +30,10 @@ pub fn draw(ui: &mut egui::Ui, render_frame_times: &mut FrameTimes<RenderPanelsF
panels: HashMap<usize, DurationPanelIdxTree>,
}
struct DurationPanelIdxTree {
geometries: HashSet<usize>,
geometries: HashMap<usize, DurationPanelGeometryIdxTree>,
}
struct DurationPanelGeometryIdxTree {
images: HashSet<PanelFadeImageSlot>,
}
let duration_idxs_tree = DurationIdxTree {
panels: render_frame_times
@ -26,7 +41,23 @@ pub fn draw(ui: &mut egui::Ui, render_frame_times: &mut FrameTimes<RenderPanelsF
.flat_map(|frame_time| {
frame_time.panels.iter().map(|(&panel_idx, panel)| {
let panel = DurationPanelIdxTree {
geometries: panel.geometries.keys().copied().collect(),
geometries: panel
.geometries
.iter()
.map(|(&geometry_idx, geometry)| {
let geometry = DurationPanelGeometryIdxTree {
#[expect(clippy::match_same_arms, reason = "They won't be in the future")]
images:
match geometry {
RenderPanelGeometryFrameTime::None(_) => HashSet::new(),
RenderPanelGeometryFrameTime::Fade(images) =>
images.images.iter().map(|(&image_slot, _image)| image_slot).collect(),
RenderPanelGeometryFrameTime::Slide(_) => HashSet::new(),
},
};
(geometry_idx, geometry)
})
.collect(),
};
(panel_idx, panel)
})
@ -42,10 +73,28 @@ pub fn draw(ui: &mut egui::Ui, render_frame_times: &mut FrameTimes<RenderPanelsF
iter::chain(
// Panel specific indices
[DurationPanelIdx::UpdatePanel, DurationPanelIdx::CreateRenderPipeline],
panel.geometries.into_iter().flat_map(|geometry_idx| {
panel.geometries.into_iter().flat_map(|(geometry_idx, geometry)| {
// Panel & geometry specific indices
[DurationPanelGeometryIdx::WriteUniforms, DurationPanelGeometryIdx::Draw]
.map(move |inner| DurationPanelIdx::Geometries { geometry_idx, inner })
iter_chain!(
[
DurationPanelGeometryNoneIdx::WriteUniforms,
DurationPanelGeometryNoneIdx::Draw,
]
.map(move |inner| DurationPanelGeometryIdx::None { inner }),
geometry.images.into_iter().flat_map(|image_slot| {
[
DurationPanelGeometryFadeIdx::WriteUniforms,
DurationPanelGeometryFadeIdx::Draw,
]
.map(move |inner| DurationPanelGeometryIdx::Fade { image_slot, inner })
}),
[
DurationPanelGeometrySlideIdx::WriteUniforms,
DurationPanelGeometrySlideIdx::Draw,
]
.map(move |inner| DurationPanelGeometryIdx::Slide { inner }),
)
.map(move |inner| DurationPanelIdx::Geometries { geometry_idx, inner })
}),
)
.map(move |inner| DurationIdx::Panels { panel_idx, inner })
@ -127,19 +176,104 @@ impl DurationPanelIdx {
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash, Debug)]
enum DurationPanelGeometryIdx {
WriteUniforms,
Draw,
None {
inner: DurationPanelGeometryNoneIdx,
},
Fade {
image_slot: PanelFadeImageSlot,
inner: DurationPanelGeometryFadeIdx,
},
Slide {
inner: DurationPanelGeometrySlideIdx,
},
}
impl DurationPanelGeometryIdx {
pub fn name(self, panel_idx: usize, geometry_idx: usize) -> String {
match self {
Self::WriteUniforms => format!("[Panel${panel_idx}] [Geometry${geometry_idx}] Write uniforms"),
Self::Draw => format!("[Panel${panel_idx}] [Geometry${geometry_idx}] Draw"),
Self::None { inner } => inner.name(panel_idx, geometry_idx),
Self::Fade { image_slot, inner } => inner.name(panel_idx, geometry_idx, image_slot),
Self::Slide { inner } => inner.name(panel_idx, geometry_idx),
}
}
pub fn duration(self, frame_time: &RenderPanelGeometryFrameTime) -> Option<Duration> {
match (self, frame_time) {
(Self::None { inner }, RenderPanelGeometryFrameTime::None(frame_time)) => inner.duration(frame_time),
(Self::Fade { image_slot, inner }, RenderPanelGeometryFrameTime::Fade(frame_time)) => frame_time
.images
.get(&image_slot)
.and_then(|image| inner.duration(image)),
(Self::Slide { inner }, RenderPanelGeometryFrameTime::Slide(frame_time)) => inner.duration(frame_time),
_ => None,
}
}
}
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash, Debug)]
enum DurationPanelGeometryNoneIdx {
WriteUniforms,
Draw,
}
impl DurationPanelGeometryNoneIdx {
pub fn name(self, panel_idx: usize, geometry_idx: usize) -> String {
match self {
Self::WriteUniforms =>
format!("[Panel${panel_idx}] [Geometry${geometry_idx}] [Shader$None] Write uniforms"),
Self::Draw => format!("[Panel${panel_idx}] [Geometry${geometry_idx}] [Shader$None] Draw"),
}
}
pub fn duration(self, frame_time: &RenderPanelGeometryNoneFrameTime) -> Option<Duration> {
match self {
Self::WriteUniforms => Some(frame_time.write_uniforms),
Self::Draw => Some(frame_time.draw),
}
}
}
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash, Debug)]
enum DurationPanelGeometryFadeIdx {
WriteUniforms,
Draw,
}
impl DurationPanelGeometryFadeIdx {
pub fn name(self, panel_idx: usize, geometry_idx: usize, image_slot: PanelFadeImageSlot) -> String {
match self {
Self::WriteUniforms => format!(
"[Panel${panel_idx}] [Geometry${geometry_idx}] [Shader$Fade] [Image${image_slot:?}] Write uniforms"
),
Self::Draw =>
format!("[Panel${panel_idx}] [Geometry${geometry_idx}] [Shader$Fade] [Image${image_slot:?}] Draw"),
}
}
pub fn duration(self, frame_time: &RenderPanelGeometryFadeImageFrameTime) -> Option<Duration> {
match self {
Self::WriteUniforms => Some(frame_time.write_uniforms),
Self::Draw => Some(frame_time.draw),
}
}
}
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash, Debug)]
enum DurationPanelGeometrySlideIdx {
WriteUniforms,
Draw,
}
impl DurationPanelGeometrySlideIdx {
pub fn name(self, panel_idx: usize, geometry_idx: usize) -> String {
match self {
Self::WriteUniforms =>
format!("[Panel${panel_idx}] [Geometry${geometry_idx}] [Shader$Slide] Write uniforms"),
Self::Draw => format!("[Panel${panel_idx}] [Geometry${geometry_idx}] [Shader$Slide] Draw"),
}
}
pub fn duration(self, frame_time: &RenderPanelGeometrySlideFrameTime) -> Option<Duration> {
match self {
Self::WriteUniforms => Some(frame_time.write_uniforms),
Self::Draw => Some(frame_time.draw),

View File

@ -2,6 +2,7 @@
// Imports
use {
crate::panel::state::fade::PanelFadeImageSlot,
core::{ops::DerefMut, time::Duration},
std::collections::{HashMap, VecDeque},
tokio::sync::{Mutex, MutexGuard},
@ -158,7 +159,36 @@ pub struct RenderPanelFrameTime {
/// Render panel geometry frame time.
#[derive(Clone, Debug)]
pub struct RenderPanelGeometryFrameTime {
#[derive(derive_more::From)]
pub enum RenderPanelGeometryFrameTime {
None(RenderPanelGeometryNoneFrameTime),
Fade(RenderPanelGeometryFadeFrameTime),
Slide(RenderPanelGeometrySlideFrameTime),
}
/// Render panel geometry none frame time.
#[derive(Clone, Debug)]
pub struct RenderPanelGeometryNoneFrameTime {
pub write_uniforms: Duration,
pub draw: Duration,
}
/// Render panel geometry fade frame time.
#[derive(Clone, Debug)]
pub struct RenderPanelGeometryFadeFrameTime {
pub images: HashMap<PanelFadeImageSlot, RenderPanelGeometryFadeImageFrameTime>,
}
/// Render panel geometry fade image frame time.
#[derive(Clone, Debug)]
pub struct RenderPanelGeometryFadeImageFrameTime {
pub write_uniforms: Duration,
pub draw: Duration,
}
/// Render panel geometry slide frame time.
#[derive(Clone, Debug)]
pub struct RenderPanelGeometrySlideFrameTime {
pub write_uniforms: Duration,
pub draw: Duration,
}

View File

@ -8,7 +8,7 @@ pub mod state;
// Exports
pub use self::{
geometry::{PanelGeometry, PanelGeometryUniforms},
geometry::PanelGeometry,
panels::Panels,
renderer::{PanelFadeShader, PanelShader, PanelSlideShader, PanelsRenderer, PanelsRendererShared},
state::PanelState,

View File

@ -1,8 +1,14 @@
//! Panel geometry
// Imports
use {std::collections::HashMap, winit::window::WindowId};
use {
super::state::{fade, none, slide},
crate::panel::state::fade::PanelFadeImageSlot,
std::collections::HashMap,
tokio::sync::OnceCell,
winit::window::WindowId,
zsw_wgpu::Wgpu,
};
/// Panel geometry
#[derive(Debug)]
@ -12,8 +18,72 @@ pub struct PanelGeometry {
}
/// Panel geometry uniforms
#[derive(Debug)]
#[derive(Default, Debug)]
pub struct PanelGeometryUniforms {
pub none: OnceCell<PanelGeometryNoneUniforms>,
pub fade: PanelGeometryFadeUniforms,
pub slide: OnceCell<PanelGeometrySlideUniforms>,
}
impl PanelGeometryUniforms {
/// Returns the none uniforms
pub async fn none(&self, wgpu: &Wgpu, layout: &wgpu::BindGroupLayout) -> &PanelGeometryNoneUniforms {
self.none
.get_or_init(async || none::create_geometry_uniforms(wgpu, layout))
.await
}
/// Returns the slide uniforms
pub async fn slide(&self, wgpu: &Wgpu, layout: &wgpu::BindGroupLayout) -> &PanelGeometrySlideUniforms {
self.slide
.get_or_init(async || slide::create_geometry_uniforms(wgpu, layout))
.await
}
}
/// Panel geometry none uniforms
#[derive(Debug)]
pub struct PanelGeometryNoneUniforms {
/// Buffer
pub buffer: wgpu::Buffer,
/// Bind group
pub bind_group: wgpu::BindGroup,
}
/// Panel geometry fade uniforms
#[derive(Default, Debug)]
pub struct PanelGeometryFadeUniforms {
/// Images
pub images: HashMap<PanelFadeImageSlot, PanelGeometryFadeImageUniforms>,
}
impl PanelGeometryFadeUniforms {
/// Returns an image's uniforms
pub fn image(
&mut self,
wgpu: &Wgpu,
layout: &wgpu::BindGroupLayout,
slot: PanelFadeImageSlot,
) -> &mut PanelGeometryFadeImageUniforms {
self.images
.entry(slot)
.or_insert_with(|| fade::images::create_image_geometry_uniforms(wgpu, layout))
}
}
/// Panel geometry fade image uniforms
#[derive(Debug)]
pub struct PanelGeometryFadeImageUniforms {
/// Buffer
pub buffer: wgpu::Buffer,
/// Bind group
pub bind_group: wgpu::BindGroup,
}
/// Panel geometry slide uniforms
#[derive(Debug)]
pub struct PanelGeometrySlideUniforms {
/// Buffer
pub buffer: wgpu::Buffer,

View File

@ -1,20 +1,37 @@
//! Panels renderer
// Modules
mod uniform;
pub mod uniform;
mod vertex;
// Exports
pub use self::{uniform::MAX_UNIFORM_SIZE, vertex::PanelVertex};
pub use self::vertex::PanelVertex;
// Imports
use {
self::uniform::PanelFadeImageUniforms,
super::{PanelGeometryUniforms, PanelState, Panels, state::fade::PanelFadeImagesShared},
super::{
Panel,
PanelState,
Panels,
geometry::{
PanelGeometryFadeUniforms,
PanelGeometryNoneUniforms,
PanelGeometrySlideUniforms,
PanelGeometryUniforms,
},
state::{
PanelFadeState,
PanelNoneState,
PanelSlideState,
fade::{PanelFadeImageSlot, PanelFadeImagesShared},
none::PanelNoneImagesShared,
slide::PanelSlideImagesShared,
},
},
crate::{
display::DisplayGeometry,
metrics::{self, Metrics},
panel::{PanelGeometry, state::fade::PanelFadeImage},
panel::PanelGeometry,
time,
},
app_error::Context,
@ -46,11 +63,14 @@ pub struct PanelsRendererShared {
/// Index buffer
indices: wgpu::Buffer,
/// Uniforms bind group layout
uniforms_bind_group_layout: wgpu::BindGroupLayout,
/// None
none: PanelNoneImagesShared,
/// Fade
fade: PanelFadeImagesShared,
/// Slide
slide: PanelSlideImagesShared,
}
impl PanelsRendererShared {
@ -60,14 +80,17 @@ impl PanelsRendererShared {
let indices = self::create_indices(wgpu);
let vertices = self::create_vertices(wgpu);
let uniforms_bind_group_layout = self::create_uniforms_bind_group_layout(wgpu);
let none = PanelNoneImagesShared::new(wgpu);
let fade = PanelFadeImagesShared::new(wgpu);
let slide = PanelSlideImagesShared::new(wgpu);
Self {
render_pipelines: Mutex::new(HashMap::new()),
vertices,
indices,
uniforms_bind_group_layout,
fade: PanelFadeImagesShared::default(),
none,
fade,
slide,
}
}
}
@ -113,7 +136,6 @@ impl PanelsRenderer {
}
/// Renders a panel
#[expect(clippy::too_many_lines, reason = "TODO: Split it up")]
pub async fn render(
&self,
frame: &mut FrameRender,
@ -178,128 +200,21 @@ impl PanelsRenderer {
.enumerate()
.map(|(panel_idx, panel)| async move { (panel_idx, panel.lock().await) })
.collect::<FuturesUnordered<_>>();
let mut panel_metrics = HashMap::new();
let mut panels_metrics = HashMap::new();
while let Some((panel_idx, mut panel)) = panels.next().await {
let panel = &mut *panel;
// Update the panel before drawing it
#[time(update_panel)]
let () = match &mut panel.state {
PanelState::None(_) => (),
PanelState::Fade(state) => state.update(wgpu).await,
#[expect(clippy::match_same_arms, reason = "We'll be changing them soon")]
PanelState::Slide(_) => (),
};
// If the panel images are empty, there's no sense in rendering it either
#[expect(clippy::match_same_arms, reason = "We'll be changing them soon")]
let are_images_empty = match &panel.state {
PanelState::None(_) => false,
PanelState::Fade(state) => state.images().is_empty(),
PanelState::Slide(_) => false,
};
if are_images_empty {
continue;
}
let render_pipeline_id = match &panel.state {
PanelState::None(_) => RenderPipelineId::None,
PanelState::Fade(state) => RenderPipelineId::Fade(match state.shader() {
PanelFadeShader::Basic => RenderPipelineFadeId::Basic,
PanelFadeShader::White { .. } => RenderPipelineFadeId::White,
PanelFadeShader::Out { .. } => RenderPipelineFadeId::Out,
PanelFadeShader::In { .. } => RenderPipelineFadeId::In,
}),
PanelState::Slide(state) => RenderPipelineId::Slide(match state.shader() {
PanelSlideShader::Basic => RenderPipelineSlideId::Basic,
}),
};
#[time(create_render_pipeline)]
let render_pipeline = match shared.render_pipelines.lock().await.entry(render_pipeline_id) {
hash_map::Entry::Occupied(entry) => Arc::clone(entry.get()),
hash_map::Entry::Vacant(entry) => {
let bind_group_layouts = match panel.state {
PanelState::None(_) => &[&shared.uniforms_bind_group_layout] as &[_],
PanelState::Fade(_) => &[
&shared.uniforms_bind_group_layout,
shared.fade.image_bind_group_layout(wgpu).await,
],
PanelState::Slide(_) => &[&shared.uniforms_bind_group_layout],
};
let render_pipeline = self::create_render_pipeline(
wgpu_renderer,
wgpu,
render_pipeline_id,
bind_group_layouts,
panel.state.shader(),
self.msaa_samples,
)
.context("Unable to create render pipeline")?;
Arc::clone(entry.insert(Arc::new(render_pipeline)))
},
};
// Bind the pipeline for the specific shader
render_pass.set_pipeline(&render_pipeline);
// Bind the extra bind groups
match &mut panel.state {
PanelState::None(_panel_state) => (),
PanelState::Fade(panel_state) =>
render_pass.set_bind_group(1, panel_state.images().image_bind_group(wgpu, &shared.fade).await, &[]),
PanelState::Slide(_panel_state) => (),
}
// The display might have changed asynchronously from the panel geometries,
// so resize it to ensure we have a panel geometry for each display geometry.
let display = panel.display.read().await;
panel
.geometries
.resize_with(display.geometries.len(), PanelGeometry::new);
let geometry_metrics = panel_metrics
.entry(panel_idx)
.or_insert_with(|| metrics::RenderPanelFrameTime {
update_panel,
create_render_pipeline,
geometries: HashMap::new(),
});
for (geometry_idx, (display_geometry, panel_geometry)) in
display.geometries.iter().zip_eq(&mut panel.geometries).enumerate()
{
// If this geometry is outside our window, we can safely ignore it
if !display_geometry.intersects_window(window_geometry) {
continue;
}
// Write and bind the uniforms
#[time(write_uniforms)]
Self::write_bind_uniforms(
wgpu,
shared,
frame.surface_size,
&panel.state,
window_geometry,
window,
display_geometry,
panel_geometry,
&mut render_pass,
);
// Finally draw
#[time(draw)]
render_pass.draw_indexed(0..6, 0, 0..1);
_ = geometry_metrics
.geometries
.insert(geometry_idx, metrics::RenderPanelGeometryFrameTime {
write_uniforms,
draw,
});
}
self.render_panel(
wgpu,
shared,
wgpu_renderer,
frame.surface_size,
window,
window_geometry,
&mut render_pass,
panel_idx,
&mut panel,
&mut panels_metrics,
)
.await?;
}
metrics
@ -308,138 +223,363 @@ impl PanelsRenderer {
.add(metrics::RenderPanelsFrameTime {
create_render_pass,
lock_panels,
panels: panel_metrics,
panels: panels_metrics,
});
Ok(())
}
/// Writes and binds the uniforms
pub fn write_bind_uniforms(
/// Renders a panel
async fn render_panel(
&self,
wgpu: &Wgpu,
shared: &PanelsRendererShared,
wgpu_renderer: &WgpuRenderer,
surface_size: PhysicalSize<u32>,
window: &Window,
window_geometry: Rect<i32, u32>,
render_pass: &mut wgpu::RenderPass<'_>,
panel_idx: usize,
panel: &mut Panel,
panels_metrics: &mut HashMap<usize, metrics::RenderPanelFrameTime>,
) -> Result<(), app_error::AppError> {
// Update the panel before drawing it
#[time(update_panel)]
let () = match &mut panel.state {
PanelState::None(_) => (),
PanelState::Fade(state) => state.update(wgpu).await,
#[expect(clippy::match_same_arms, reason = "We'll be changing them soon")]
PanelState::Slide(_) => (),
};
// If the panel images are empty, there's no sense in rendering it either
#[expect(clippy::match_same_arms, reason = "We'll be changing them soon")]
let are_images_empty = match &panel.state {
PanelState::None(_) => false,
PanelState::Fade(state) => state.images().is_empty(),
PanelState::Slide(_) => false,
};
if are_images_empty {
return Ok(());
}
let render_pipeline_id = match &panel.state {
PanelState::None(_) => RenderPipelineId::None,
PanelState::Fade(state) => RenderPipelineId::Fade(match state.shader() {
PanelFadeShader::Basic => RenderPipelineFadeId::Basic,
PanelFadeShader::White { .. } => RenderPipelineFadeId::White,
PanelFadeShader::Out { .. } => RenderPipelineFadeId::Out,
PanelFadeShader::In { .. } => RenderPipelineFadeId::In,
}),
PanelState::Slide(state) => RenderPipelineId::Slide(match state.shader() {
PanelSlideShader::Basic => RenderPipelineSlideId::Basic,
}),
};
#[time(create_render_pipeline)]
let render_pipeline = match shared.render_pipelines.lock().await.entry(render_pipeline_id) {
hash_map::Entry::Occupied(entry) => Arc::clone(entry.get()),
hash_map::Entry::Vacant(entry) => {
let bind_group_layouts = match panel.state {
PanelState::None(_) => &[&shared.none.geometry_uniforms_bind_group_layout] as &[_],
PanelState::Fade(_) => &[
&shared.fade.geometry_uniforms_bind_group_layout,
shared.fade.image_bind_group_layout(wgpu).await,
],
PanelState::Slide(_) => &[&shared.slide.geometry_uniforms_bind_group_layout],
};
let render_pipeline = self::create_render_pipeline(
wgpu_renderer,
wgpu,
render_pipeline_id,
bind_group_layouts,
panel.state.shader(),
self.msaa_samples,
)
.context("Unable to create render pipeline")?;
Arc::clone(entry.insert(Arc::new(render_pipeline)))
},
};
// Bind the pipeline for the specific shader
render_pass.set_pipeline(&render_pipeline);
let panel_metrics = panels_metrics
.entry(panel_idx)
.or_insert_with(|| metrics::RenderPanelFrameTime {
update_panel,
create_render_pipeline,
geometries: HashMap::new(),
});
// Then render the panel
Self::render_panel_geometries(
wgpu,
shared,
surface_size,
window,
window_geometry,
render_pass,
panel,
panel_metrics,
)
.await;
Ok(())
}
/// Renders a panel's geometries
async fn render_panel_geometries(
wgpu: &Wgpu,
shared: &PanelsRendererShared,
surface_size: PhysicalSize<u32>,
window: &Window,
window_geometry: Rect<i32, u32>,
render_pass: &mut wgpu::RenderPass<'_>,
panel: &mut Panel,
panel_metrics: &mut metrics::RenderPanelFrameTime,
) {
// The display might have changed asynchronously from the panel geometries,
// so resize it to ensure we have a panel geometry for each display geometry.
let display = panel.display.read().await;
panel
.geometries
.resize_with(display.geometries.len(), PanelGeometry::new);
// Go through all geometries of the panel display and render each one
for (geometry_idx, (display_geometry, panel_geometry)) in
display.geometries.iter().zip_eq(&mut panel.geometries).enumerate()
{
// If this geometry is outside our window, we can safely ignore it
if !display_geometry.intersects_window(window_geometry) {
continue;
}
// Render the panel geometry
let geometry_uniforms = panel_geometry.uniforms.entry(window.id()).or_default();
let geometry_metrics = Self::render_panel_geometry(
wgpu,
shared,
surface_size,
&panel.state,
window_geometry,
display_geometry,
geometry_uniforms,
render_pass,
)
.await;
_ = panel_metrics.geometries.insert(geometry_idx, geometry_metrics);
}
}
/// Renders a panel's geometry
pub async fn render_panel_geometry(
wgpu: &Wgpu,
shared: &PanelsRendererShared,
surface_size: PhysicalSize<u32>,
panel_state: &PanelState,
window_geometry: Rect<i32, u32>,
window: &Window,
display_geometry: &DisplayGeometry,
panel_geometry: &mut PanelGeometry,
geometry_uniforms: &mut PanelGeometryUniforms,
render_pass: &mut wgpu::RenderPass<'_>,
) {
) -> metrics::RenderPanelGeometryFrameTime {
// Calculate the position matrix for the panel
let pos_matrix = display_geometry.pos_matrix(window_geometry, surface_size);
let pos_matrix = uniform::Matrix4x4(pos_matrix.into());
// Writes uniforms `uniforms`
let geometry_uniforms = panel_geometry
.uniforms
.entry(window.id())
.or_insert_with(|| self::create_geometry_uniforms(wgpu, &shared.uniforms_bind_group_layout));
let write_uniforms = |uniforms_bytes| {
wgpu.queue.write_buffer(&geometry_uniforms.buffer, 0, uniforms_bytes);
};
macro write_uniforms($uniforms:expr) {
write_uniforms(bytemuck::bytes_of(&$uniforms))
}
match panel_state {
PanelState::None(panel_state) => write_uniforms!(uniform::None {
PanelState::None(panel_state) => Self::render_panel_none_geometry(
wgpu,
render_pass,
pos_matrix,
background_color: uniform::Vec4(panel_state.background_color),
}),
PanelState::Fade(panel_state) => {
let image_uniforms = |image: Option<&PanelFadeImage>| {
let (size, swap_dir) = match image {
None => (Vector2::new(0, 0), false),
Some(image) => {
let texture = image.texture_view.texture();
let size = Vector2::new(texture.width(), texture.height());
(size, image.swap_dir)
},
};
geometry_uniforms
.none(wgpu, &shared.none.geometry_uniforms_bind_group_layout)
.await,
panel_state,
)
.into(),
PanelState::Fade(panel_state) => Self::render_panel_fade_geometry(
wgpu,
shared,
display_geometry,
render_pass,
pos_matrix,
&mut geometry_uniforms.fade,
panel_state,
)
.await
.into(),
PanelState::Slide(panel_state) => Self::render_panel_slide_geometry(
wgpu,
render_pass,
pos_matrix,
geometry_uniforms
.slide(wgpu, &shared.slide.geometry_uniforms_bind_group_layout)
.await,
panel_state,
)
.into(),
}
}
let ratio = display_geometry.image_ratio(size);
PanelFadeImageUniforms::new(ratio, swap_dir)
};
/// Renders a panel none's geometry
fn render_panel_none_geometry(
wgpu: &Wgpu,
render_pass: &mut wgpu::RenderPass<'_>,
pos_matrix: uniform::Matrix4x4,
geometry_uniforms: &PanelGeometryNoneUniforms,
panel_state: &PanelNoneState,
) -> metrics::RenderPanelGeometryNoneFrameTime {
#[time(write_uniforms)]
let () = Self::write_uniforms(wgpu, &geometry_uniforms.buffer, uniform::None {
pos_matrix,
background_color: uniform::Vec4(panel_state.background_color),
});
let prev = image_uniforms(panel_state.images().prev.as_ref());
let cur = image_uniforms(panel_state.images().cur.as_ref());
let next = image_uniforms(panel_state.images().next.as_ref());
// Bind the geometry uniforms
render_pass.set_bind_group(0, &geometry_uniforms.bind_group, &[]);
let fade_duration = panel_state.fade_duration_norm();
let progress = panel_state.progress_norm();
match panel_state.shader() {
PanelFadeShader::Basic => write_uniforms!(uniform::Fade {
#[time(draw)]
render_pass.draw_indexed(0..6, 0, 0..1);
metrics::RenderPanelGeometryNoneFrameTime { write_uniforms, draw }
}
async fn render_panel_fade_geometry(
wgpu: &Wgpu,
shared: &PanelsRendererShared,
display_geometry: &DisplayGeometry,
render_pass: &mut wgpu::RenderPass<'_>,
pos_matrix: uniform::Matrix4x4,
geometry_uniforms: &mut PanelGeometryFadeUniforms,
panel_state: &PanelFadeState,
) -> metrics::RenderPanelGeometryFadeFrameTime {
let p = panel_state.progress_norm();
let f = panel_state.fade_duration_norm();
// Full duration an image is on screen (including the fades)
let d = 1.0 + 2.0 * f;
let mut image_metrics = HashMap::new();
for (panel_image_slot, panel_image) in panel_state.images().iter() {
let geometry_uniforms =
geometry_uniforms.image(wgpu, &shared.fade.geometry_uniforms_bind_group_layout, panel_image_slot);
let progress = match panel_image_slot {
PanelFadeImageSlot::Prev => 1.0 - f32::max((f - p) / d, 0.0),
PanelFadeImageSlot::Cur => (p + f) / d,
PanelFadeImageSlot::Next => f32::max((p - 1.0 + f) / d, 0.0),
};
let progress = match panel_image.swap_dir {
true => 1.0 - progress,
false => progress,
};
let alpha_prev = 0.5 * f32::clamp(1.0 - p / f, 0.0, 1.0);
let alpha_next = 0.5 * f32::clamp(1.0 - (1.0 - p) / f, 0.0, 1.0);
let alpha_cur = 1.0 - f32::max(alpha_prev, alpha_next);
let alpha = match panel_image_slot {
PanelFadeImageSlot::Prev => alpha_prev,
PanelFadeImageSlot::Cur => alpha_cur,
PanelFadeImageSlot::Next => alpha_next,
};
// If the alpha is 0, we can skip this image
if alpha == 0.0 {
continue;
}
// Calculate the position matrix for the panel
let image_size = panel_image.texture_view.texture().size();
let image_size = Vector2::new(image_size.width, image_size.height);
let image_ratio = display_geometry.image_ratio(image_size);
#[time(write_uniforms)]
let () = match panel_state.shader() {
PanelFadeShader::Basic => Self::write_uniforms(wgpu, &geometry_uniforms.buffer, uniform::FadeBasic {
pos_matrix,
image_ratio: uniform::Vec2(image_ratio.into()),
progress,
alpha,
}),
PanelFadeShader::White { strength } =>
Self::write_uniforms(wgpu, &geometry_uniforms.buffer, uniform::FadeWhite {
pos_matrix,
prev,
cur,
next,
fade_duration,
image_ratio: uniform::Vec2(image_ratio.into()),
progress,
_unused: [0; 2],
alpha,
mix_strength: strength * 4.0 * f32::max(alpha_cur * alpha_prev, alpha_cur * alpha_next),
_unused: [0; _],
}),
PanelFadeShader::White { strength } => write_uniforms!(uniform::FadeWhite {
PanelFadeShader::Out { strength } =>
Self::write_uniforms(wgpu, &geometry_uniforms.buffer, uniform::FadeOut {
pos_matrix,
prev,
cur,
next,
fade_duration,
image_ratio: uniform::Vec2(image_ratio.into()),
progress,
alpha,
strength,
_unused: 0,
_unused: [0; _],
}),
PanelFadeShader::Out { strength } => write_uniforms!(uniform::FadeOut {
PanelFadeShader::In { strength } =>
Self::write_uniforms(wgpu, &geometry_uniforms.buffer, uniform::FadeIn {
pos_matrix,
prev,
cur,
next,
fade_duration,
image_ratio: uniform::Vec2(image_ratio.into()),
progress,
alpha,
strength,
_unused: 0,
_unused: [0; _],
}),
PanelFadeShader::In { strength } => write_uniforms!(uniform::FadeIn {
pos_matrix,
prev,
cur,
next,
fade_duration,
progress,
strength,
_unused: 0,
}),
}
},
PanelState::Slide(_panel_state) => write_uniforms!(uniform::Slide { pos_matrix }),
};
// Bind the geometry uniforms
render_pass.set_bind_group(0, &geometry_uniforms.bind_group, &[]);
// Bind the image uniforms
let sampler = panel_state.images().image_sampler(wgpu).await;
render_pass.set_bind_group(1, panel_image.bind_group(wgpu, sampler, &shared.fade).await, &[]);
#[time(draw)]
render_pass.draw_indexed(0..6, 0, 0..1);
_ = image_metrics.insert(panel_image_slot, metrics::RenderPanelGeometryFadeImageFrameTime {
write_uniforms,
draw,
});
}
render_pass.set_bind_group(0, &geometry_uniforms.bind_group, &[]);
metrics::RenderPanelGeometryFadeFrameTime { images: image_metrics }
}
}
/// Creates the panel geometry uniforms
fn create_geometry_uniforms(wgpu: &Wgpu, layout: &wgpu::BindGroupLayout) -> PanelGeometryUniforms {
// Create the uniforms
let buffer_descriptor = wgpu::BufferDescriptor {
label: Some("zsw-panel-geometry-uniforms-buffer"),
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
size: u64::try_from(MAX_UNIFORM_SIZE).expect("Maximum uniform size didn't fit into a `u64`"),
mapped_at_creation: false,
};
let buffer = wgpu.device.create_buffer(&buffer_descriptor);
/// Renders a panel slide's geometry
fn render_panel_slide_geometry(
wgpu: &Wgpu,
render_pass: &mut wgpu::RenderPass<'_>,
pos_matrix: uniform::Matrix4x4,
geometry_uniforms: &PanelGeometrySlideUniforms,
_panel_state: &PanelSlideState,
) -> metrics::RenderPanelGeometrySlideFrameTime {
#[time(write_uniforms)]
let () = Self::write_uniforms(wgpu, &geometry_uniforms.buffer, uniform::Slide { pos_matrix });
// Create the uniform bind group
let bind_group_descriptor = wgpu::BindGroupDescriptor {
label: Some("zsw-panel-geometry-uniforms-bind-group"),
layout,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: buffer.as_entire_binding(),
}],
};
let bind_group = wgpu.device.create_bind_group(&bind_group_descriptor);
// Bind the geometry uniforms
render_pass.set_bind_group(0, &geometry_uniforms.bind_group, &[]);
PanelGeometryUniforms { buffer, bind_group }
#[time(draw)]
render_pass.draw_indexed(0..6, 0, 0..1);
metrics::RenderPanelGeometrySlideFrameTime { write_uniforms, draw }
}
/// Writes `uniforms` into `buffer`.
fn write_uniforms<T>(wgpu: &Wgpu, buffer: &wgpu::Buffer, uniforms: T)
where
T: bytemuck::NoUninit,
{
wgpu.queue.write_buffer(buffer, 0, bytemuck::bytes_of(&uniforms));
}
}
/// Creates the vertices
@ -635,25 +775,6 @@ fn create_msaa_framebuffer(
})
}
/// Creates the uniforms bind group layout
fn create_uniforms_bind_group_layout(wgpu: &Wgpu) -> wgpu::BindGroupLayout {
let descriptor = wgpu::BindGroupLayoutDescriptor {
label: Some("zsw-panel-geometry-uniforms-bind-group-layout"),
entries: &[wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
}],
};
wgpu.device.create_bind_group_layout(&descriptor)
}
/// Shader
#[derive(PartialEq, Clone, Copy, Debug)]
pub enum PanelShader {

View File

@ -21,26 +21,6 @@ pub struct Vec4(pub [f32; 4]);
#[repr(C, align(16))]
pub struct Matrix4x4(pub [[f32; 4]; 4]);
/// Panel fade image uniforms
#[derive(PartialEq, Clone, Copy, Default, Debug)]
#[derive(Zeroable, Pod)]
#[repr(C)]
pub struct PanelFadeImageUniforms {
ratio: Vec2,
swap_dir: u32,
_unused: u32,
}
impl PanelFadeImageUniforms {
pub fn new(ratio: impl Into<[f32; 2]>, swap_dir: bool) -> Self {
Self {
ratio: Vec2(ratio.into()),
swap_dir: swap_dir.into(),
_unused: 0,
}
}
}
/// None
#[derive(PartialEq, Clone, Copy, Default, Debug)]
#[derive(Zeroable, Pod)]
@ -54,15 +34,11 @@ pub struct None {
#[derive(PartialEq, Clone, Copy, Default, Debug)]
#[derive(Zeroable, Pod)]
#[repr(C)]
pub struct Fade {
pub pos_matrix: Matrix4x4,
pub prev: PanelFadeImageUniforms,
pub cur: PanelFadeImageUniforms,
pub next: PanelFadeImageUniforms,
pub fade_duration: f32,
pub progress: f32,
pub _unused: [u32; 2],
pub struct FadeBasic {
pub pos_matrix: Matrix4x4,
pub image_ratio: Vec2,
pub progress: f32,
pub alpha: f32,
}
/// Fade-white
@ -70,15 +46,13 @@ pub struct Fade {
#[derive(Zeroable, Pod)]
#[repr(C)]
pub struct FadeWhite {
pub pos_matrix: Matrix4x4,
pub prev: PanelFadeImageUniforms,
pub cur: PanelFadeImageUniforms,
pub next: PanelFadeImageUniforms,
pub fade_duration: f32,
pub progress: f32,
pub strength: f32,
pub pos_matrix: Matrix4x4,
pub image_ratio: Vec2,
pub progress: f32,
pub alpha: f32,
pub mix_strength: f32,
pub _unused: u32,
pub _unused: [u32; 3],
}
/// Fade-out
@ -86,15 +60,13 @@ pub struct FadeWhite {
#[derive(Zeroable, Pod)]
#[repr(C)]
pub struct FadeOut {
pub pos_matrix: Matrix4x4,
pub prev: PanelFadeImageUniforms,
pub cur: PanelFadeImageUniforms,
pub next: PanelFadeImageUniforms,
pub fade_duration: f32,
pub progress: f32,
pub strength: f32,
pub pos_matrix: Matrix4x4,
pub image_ratio: Vec2,
pub progress: f32,
pub alpha: f32,
pub strength: f32,
pub _unused: u32,
pub _unused: [u32; 3],
}
/// Fade-in
@ -102,15 +74,13 @@ pub struct FadeOut {
#[derive(Zeroable, Pod)]
#[repr(C)]
pub struct FadeIn {
pub pos_matrix: Matrix4x4,
pub prev: PanelFadeImageUniforms,
pub cur: PanelFadeImageUniforms,
pub next: PanelFadeImageUniforms,
pub fade_duration: f32,
pub progress: f32,
pub strength: f32,
pub pos_matrix: Matrix4x4,
pub image_ratio: Vec2,
pub progress: f32,
pub alpha: f32,
pub strength: f32,
pub _unused: u32,
pub _unused: [u32; 3],
}
/// Slide
@ -120,14 +90,3 @@ pub struct FadeIn {
pub struct Slide {
pub pos_matrix: Matrix4x4,
}
/// The maximum uniform size
pub const MAX_UNIFORM_SIZE: usize = zsw_util::array_max(&[
size_of::<None>(),
size_of::<Fade>(),
size_of::<FadeWhite>(),
size_of::<FadeOut>(),
size_of::<FadeIn>(),
size_of::<Slide>(),
])
.expect("No max uniform size");

View File

@ -4,7 +4,7 @@
pub mod images;
// Exports
pub use self::images::{PanelFadeImage, PanelFadeImages, PanelFadeImagesShared};
pub use self::images::{PanelFadeImage, PanelFadeImageSlot, PanelFadeImages, PanelFadeImagesShared};
// Imports
use {

View File

@ -2,7 +2,10 @@
// Imports
use {
crate::playlist::PlaylistPlayer,
crate::{
panel::{geometry::PanelGeometryFadeImageUniforms, renderer::uniform},
playlist::PlaylistPlayer,
},
app_error::Context,
image::{DynamicImage, imageops},
std::{self, mem, path::Path, sync::Arc},
@ -13,13 +16,26 @@ use {
};
/// Panel fade images shared
#[derive(Default, Debug)]
#[derive(Debug)]
pub struct PanelFadeImagesShared {
/// Geometry uniforms bind group layout
pub geometry_uniforms_bind_group_layout: wgpu::BindGroupLayout,
/// Image bind group layout
image_bind_group_layout: OnceCell<wgpu::BindGroupLayout>,
pub image_bind_group_layout: OnceCell<wgpu::BindGroupLayout>,
}
impl PanelFadeImagesShared {
/// Creates the shared
pub fn new(wgpu: &Wgpu) -> Self {
let geometry_uniforms_bind_group_layout = self::create_geometry_uniforms_bind_group_layout(wgpu);
Self {
geometry_uniforms_bind_group_layout,
image_bind_group_layout: OnceCell::new(),
}
}
/// Gets the image bind group layout, or initializes it, if uninitialized
pub async fn image_bind_group_layout(&self, wgpu: &Wgpu) -> &wgpu::BindGroupLayout {
self.image_bind_group_layout
@ -43,9 +59,6 @@ pub struct PanelFadeImages {
/// Image sampler
pub image_sampler: OnceCell<wgpu::Sampler>,
/// Image bind group
pub image_bind_group: OnceCell<wgpu::BindGroup>,
/// Next image
pub next_image: Loadable<ImageLoadRes>,
}
@ -56,6 +69,9 @@ pub struct PanelFadeImage {
/// Texture view
pub texture_view: wgpu::TextureView,
/// Bind group
pub bind_group: OnceCell<wgpu::BindGroup>,
/// Swap direction
pub swap_dir: bool,
@ -63,20 +79,47 @@ pub struct PanelFadeImage {
pub path: Arc<Path>,
}
impl PanelFadeImage {
/// Gets the bind group, or initializes it, if uninitialized
pub async fn bind_group(
&self,
wgpu: &Wgpu,
sampler: &wgpu::Sampler,
shared: &PanelFadeImagesShared,
) -> &wgpu::BindGroup {
self.bind_group
.get_or_init(async || {
let layout = shared.image_bind_group_layout(wgpu).await;
self::create_image_bind_group(wgpu, layout, &self.texture_view, sampler)
})
.await
}
}
impl PanelFadeImages {
/// Creates a new panel
#[must_use]
pub fn new() -> Self {
Self {
prev: None,
cur: None,
next: None,
image_sampler: OnceCell::new(),
image_bind_group: OnceCell::new(),
next_image: Loadable::new(),
prev: None,
cur: None,
next: None,
image_sampler: OnceCell::new(),
next_image: Loadable::new(),
}
}
/// Returns an iterator over all images
pub fn iter(&self) -> impl Iterator<Item = (PanelFadeImageSlot, &PanelFadeImage)> {
[
(PanelFadeImageSlot::Prev, &self.prev),
(PanelFadeImageSlot::Cur, &self.cur),
(PanelFadeImageSlot::Next, &self.next),
]
.into_iter()
.filter_map(|(slot, img)| img.as_ref().map(|img| (slot, img)))
}
/// Steps to the previous image, if any
///
/// If successful, starts loading any missing images
@ -87,7 +130,6 @@ impl PanelFadeImages {
mem::swap(&mut self.cur, &mut self.next);
mem::swap(&mut self.prev, &mut self.cur);
self.prev = None;
self.image_bind_group = OnceCell::new();
self.load_missing(playlist_player, wgpu);
Ok(())
@ -107,7 +149,6 @@ impl PanelFadeImages {
mem::swap(&mut self.prev, &mut self.cur);
mem::swap(&mut self.cur, &mut self.next);
self.next = None;
self.image_bind_group = OnceCell::new();
self.load_missing(playlist_player, wgpu);
Ok(())
@ -120,19 +161,6 @@ impl PanelFadeImages {
.await
}
/// Gets the image bind group, or initializes it, if uninitialized
pub async fn image_bind_group(&self, wgpu: &Wgpu, shared: &PanelFadeImagesShared) -> &wgpu::BindGroup {
self.image_bind_group
.get_or_init(async || {
let [prev, cur, next] = [&self.prev, &self.cur, &self.next]
.map(|img| img.as_ref().map_or(&wgpu.empty_texture_view, |img| &img.texture_view));
let image_sampler = self.image_sampler(wgpu).await;
let layout = shared.image_bind_group_layout(wgpu).await;
self::create_image_bind_group(wgpu, layout, prev, cur, next, image_sampler)
})
.await
}
/// Loads any missing images, prioritizing the current, then next, then previous.
///
/// Requests images if missing any.
@ -164,9 +192,9 @@ impl PanelFadeImages {
// Get which slot to load the image into
let slot = {
match res.playlist_pos {
pos if Some(pos) == playlist_player.prev_pos() => Some(Slot::Prev),
pos if pos == playlist_player.cur_pos() => Some(Slot::Cur),
pos if pos == playlist_player.next_pos() => Some(Slot::Next),
pos if Some(pos) == playlist_player.prev_pos() => Some(PanelFadeImageSlot::Prev),
pos if pos == playlist_player.cur_pos() => Some(PanelFadeImageSlot::Cur),
pos if pos == playlist_player.next_pos() => Some(PanelFadeImageSlot::Next),
pos => {
tracing::warn!(
pos,
@ -192,14 +220,14 @@ impl PanelFadeImages {
texture_view,
swap_dir: rand::random(),
path: res.path,
bind_group: OnceCell::new(),
};
match slot {
Slot::Prev => self.prev = Some(image),
Slot::Cur => self.cur = Some(image),
Slot::Next => self.next = Some(image),
PanelFadeImageSlot::Prev => self.prev = Some(image),
PanelFadeImageSlot::Cur => self.cur = Some(image),
PanelFadeImageSlot::Next => self.next = Some(image),
}
self.image_bind_group = OnceCell::new();
}
}
@ -255,8 +283,8 @@ impl PanelFadeImages {
}
/// Image slot
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
enum Slot {
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash, Debug)]
pub enum PanelFadeImageSlot {
Prev,
Cur,
Next,
@ -292,6 +320,25 @@ pub fn load(path: &Arc<Path>, max_image_size: u32) -> Result<DynamicImage, AppEr
Ok(image)
}
/// Creates the geometry uniforms bind group layout
fn create_geometry_uniforms_bind_group_layout(wgpu: &Wgpu) -> wgpu::BindGroupLayout {
let descriptor = wgpu::BindGroupLayoutDescriptor {
label: Some("zsw-panel-fade-geometry-uniforms-bind-group-layout"),
entries: &[wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
}],
};
wgpu.device.create_bind_group_layout(&descriptor)
}
/// Creates the fade image bind group layout
fn create_bind_group_layout(wgpu: &Wgpu) -> wgpu::BindGroupLayout {
let descriptor = wgpu::BindGroupLayoutDescriptor {
@ -310,26 +357,6 @@ fn create_bind_group_layout(wgpu: &Wgpu) -> wgpu::BindGroupLayout {
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
multisampled: false,
view_dimension: wgpu::TextureViewDimension::D2,
sample_type: wgpu::TextureSampleType::Float { filterable: true },
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 2,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
multisampled: false,
view_dimension: wgpu::TextureViewDimension::D2,
sample_type: wgpu::TextureSampleType::Float { filterable: true },
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 3,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
},
@ -343,9 +370,7 @@ fn create_bind_group_layout(wgpu: &Wgpu) -> wgpu::BindGroupLayout {
fn create_image_bind_group(
wgpu: &Wgpu,
bind_group_layout: &wgpu::BindGroupLayout,
view_prev: &wgpu::TextureView,
view_cur: &wgpu::TextureView,
view_next: &wgpu::TextureView,
view: &wgpu::TextureView,
sampler: &wgpu::Sampler,
) -> wgpu::BindGroup {
let descriptor = wgpu::BindGroupDescriptor {
@ -354,18 +379,10 @@ fn create_image_bind_group(
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(view_prev),
resource: wgpu::BindingResource::TextureView(view),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::TextureView(view_cur),
},
wgpu::BindGroupEntry {
binding: 2,
resource: wgpu::BindingResource::TextureView(view_next),
},
wgpu::BindGroupEntry {
binding: 3,
resource: wgpu::BindingResource::Sampler(sampler),
},
],
@ -373,6 +390,40 @@ fn create_image_bind_group(
wgpu.device.create_bind_group(&descriptor)
}
/// Creates the image geometry uniforms
pub fn create_image_geometry_uniforms(wgpu: &Wgpu, layout: &wgpu::BindGroupLayout) -> PanelGeometryFadeImageUniforms {
// Create the uniforms
let buffer_descriptor = wgpu::BufferDescriptor {
label: Some("zsw-panel-fade-geometry-uniforms-buffer"),
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
size: u64::try_from(
zsw_util::array_max(&[
size_of::<uniform::FadeBasic>(),
size_of::<uniform::FadeWhite>(),
size_of::<uniform::FadeOut>(),
size_of::<uniform::FadeIn>(),
])
.expect("No max uniform size"),
)
.expect("Maximum uniform size didn't fit into a `u64`"),
mapped_at_creation: false,
};
let buffer = wgpu.device.create_buffer(&buffer_descriptor);
// Create the uniform bind group
let bind_group_descriptor = wgpu::BindGroupDescriptor {
label: Some("zsw-panel-fade-geometry-uniforms-bind-group"),
layout,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: buffer.as_entire_binding(),
}],
};
let bind_group = wgpu.device.create_bind_group(&bind_group_descriptor);
PanelGeometryFadeImageUniforms { buffer, bind_group }
}
/// Creates the image sampler
fn create_image_sampler(wgpu: &Wgpu) -> wgpu::Sampler {
let descriptor = wgpu::SamplerDescriptor {

View File

@ -1,5 +1,11 @@
//! Panel none state
// Imports
use {
crate::panel::{geometry::PanelGeometryNoneUniforms, renderer::uniform},
zsw_wgpu::Wgpu,
};
/// Panel none state
#[derive(Debug)]
pub struct PanelNoneState {
@ -13,3 +19,68 @@ impl PanelNoneState {
Self { background_color }
}
}
/// Panel none images shared
#[derive(Debug)]
pub struct PanelNoneImagesShared {
/// Geometry uniforms bind group layout
pub geometry_uniforms_bind_group_layout: wgpu::BindGroupLayout,
}
impl PanelNoneImagesShared {
/// Creates the shared
pub fn new(wgpu: &Wgpu) -> Self {
let geometry_uniforms_bind_group_layout = self::create_geometry_uniforms_bind_group_layout(wgpu);
Self {
geometry_uniforms_bind_group_layout,
}
}
}
/// Creates the geometry uniforms bind group layout
fn create_geometry_uniforms_bind_group_layout(wgpu: &Wgpu) -> wgpu::BindGroupLayout {
let descriptor = wgpu::BindGroupLayoutDescriptor {
label: Some("zsw-panel-none-geometry-uniforms-bind-group-layout"),
entries: &[wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
}],
};
wgpu.device.create_bind_group_layout(&descriptor)
}
/// Creates the panel none geometry uniforms
pub fn create_geometry_uniforms(wgpu: &Wgpu, layout: &wgpu::BindGroupLayout) -> PanelGeometryNoneUniforms {
// Create the uniforms
let buffer_descriptor = wgpu::BufferDescriptor {
label: Some("zsw-panel-none-geometry-uniforms-buffer"),
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
size: u64::try_from(
zsw_util::array_max(&[size_of::<uniform::None>()]).expect("No max uniform size"),
)
.expect("Maximum uniform size didn't fit into a `u64`"),
mapped_at_creation: false,
};
let buffer = wgpu.device.create_buffer(&buffer_descriptor);
// Create the uniform bind group
let bind_group_descriptor = wgpu::BindGroupDescriptor {
label: Some("zsw-panel-none-geometry-uniforms-bind-group"),
layout,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: buffer.as_entire_binding(),
}],
};
let bind_group = wgpu.device.create_bind_group(&bind_group_descriptor);
PanelGeometryNoneUniforms { buffer, bind_group }
}

View File

@ -1,6 +1,10 @@
//! Panel slide state
use crate::panel::PanelSlideShader;
// Imports
use {
crate::panel::{PanelSlideShader, geometry::PanelGeometrySlideUniforms, renderer::uniform},
zsw_wgpu::Wgpu,
};
/// Panel slide state
#[derive(Debug)]
@ -25,3 +29,68 @@ impl PanelSlideState {
&mut self.shader
}
}
/// Panel slide images shared
#[derive(Debug)]
pub struct PanelSlideImagesShared {
/// Geometry uniforms bind group layout
pub geometry_uniforms_bind_group_layout: wgpu::BindGroupLayout,
}
impl PanelSlideImagesShared {
/// Creates the shared
pub fn new(wgpu: &Wgpu) -> Self {
let geometry_uniforms_bind_group_layout = self::create_geometry_uniforms_bind_group_layout(wgpu);
Self {
geometry_uniforms_bind_group_layout,
}
}
}
/// Creates the geometry uniforms bind group layout
fn create_geometry_uniforms_bind_group_layout(wgpu: &Wgpu) -> wgpu::BindGroupLayout {
let descriptor = wgpu::BindGroupLayoutDescriptor {
label: Some("zsw-panel-slide-geometry-uniforms-bind-group-layout"),
entries: &[wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
}],
};
wgpu.device.create_bind_group_layout(&descriptor)
}
/// Creates the panel none geometry uniforms
pub fn create_geometry_uniforms(wgpu: &Wgpu, layout: &wgpu::BindGroupLayout) -> PanelGeometrySlideUniforms {
// Create the uniforms
let buffer_descriptor = wgpu::BufferDescriptor {
label: Some("zsw-panel-none-geometry-uniforms-buffer"),
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
size: u64::try_from(
zsw_util::array_max(&[size_of::<uniform::Slide>()]).expect("No max uniform size"),
)
.expect("Maximum uniform size didn't fit into a `u64`"),
mapped_at_creation: false,
};
let buffer = wgpu.device.create_buffer(&buffer_descriptor);
// Create the uniform bind group
let bind_group_descriptor = wgpu::BindGroupDescriptor {
label: Some("zsw-panel-none-geometry-uniforms-bind-group"),
layout,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: buffer.as_entire_binding(),
}],
};
let bind_group = wgpu.device.create_bind_group(&bind_group_descriptor);
PanelGeometrySlideUniforms { buffer, bind_group }
}