extern crate alloc;
use alloc::string::String;
use alloc::sync::Arc;
use core::fmt;
use core::future::Future;
use std::time::{Duration, Instant};
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum EffectSetupError {
#[error("filter chain declares {declared} params, exceeding the {limit}-param uniform budget")]
TooManyParams {
declared: usize,
limit: usize,
},
#[error("filter graph produced no executable passes")]
EmptyGraph,
#[error("{stage} pipeline validation failed: {message}")]
PipelineValidation {
stage: &'static str,
message: String,
},
#[error("scratch texture format {format:?} is unsupported on this device")]
ScratchFormatUnsupported {
format: wgpu::TextureFormat,
},
#[error("HDR intermediates required by policy but unavailable: {0}")]
HdrRequiredUnavailable(#[source] alloc::boxed::Box<Self>),
#[error("filter planner invariant violated: {0}")]
PlannerInvariant(&'static str),
#[error("{0}")]
Other(&'static str),
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum EffectRenderError {
#[error("effect setup failed: {0}")]
SetupFailed(#[from] EffectSetupError),
#[error(
"render texture formats (input {input:?}, output {output:?}) do not match setup formats \
(input {setup_input:?}, output {setup_output:?})"
)]
FormatMismatch {
input: wgpu::TextureFormat,
output: wgpu::TextureFormat,
setup_input: wgpu::TextureFormat,
setup_output: wgpu::TextureFormat,
},
#[error("{0}")]
MissingResource(&'static str),
}
pub type EffectSetupResult = Result<(), EffectSetupError>;
pub type EffectRenderResult = Result<bool, EffectRenderError>;
pub type EffectRedrawCallback = Arc<dyn Fn() + Send + Sync>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct EffectFrameTiming {
presentation_time: Duration,
delta: Duration,
sequence: u64,
discontinuity: bool,
}
impl EffectFrameTiming {
#[must_use]
pub const fn new(presentation_time: Duration, delta: Duration, sequence: u64) -> Self {
Self {
presentation_time,
delta,
sequence,
discontinuity: false,
}
}
#[must_use]
pub const fn with_discontinuity(mut self, discontinuity: bool) -> Self {
self.discontinuity = discontinuity;
self
}
#[must_use]
pub const fn presentation_time(self) -> Duration {
self.presentation_time
}
#[must_use]
pub const fn delta(self) -> Duration {
self.delta
}
#[must_use]
pub const fn sequence(self) -> u64 {
self.sequence
}
#[must_use]
pub const fn is_discontinuity(self) -> bool {
self.discontinuity
}
}
#[derive(Debug)]
pub struct EffectFrameClock {
origin: Instant,
previous: Instant,
sequence: u64,
}
impl EffectFrameClock {
#[must_use]
pub fn new() -> Self {
let now = Instant::now();
Self {
origin: now,
previous: now,
sequence: 0,
}
}
#[must_use]
pub fn tick(&mut self) -> EffectFrameTiming {
let now = Instant::now();
let timing = EffectFrameTiming::new(
now.saturating_duration_since(self.origin),
now.saturating_duration_since(self.previous),
self.sequence,
);
self.previous = now;
self.sequence = self.sequence.saturating_add(1);
timing
}
}
impl Default for EffectFrameClock {
fn default() -> Self {
Self::new()
}
}
pub struct EffectContext<'a> {
pub device: &'a wgpu::Device,
pub queue: &'a wgpu::Queue,
pub shader_cache: &'a shaderloom::WgslModuleCache,
pub input_format: wgpu::TextureFormat,
pub output_format: wgpu::TextureFormat,
}
impl fmt::Debug for EffectContext<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("EffectContext")
.field("input_format", &self.input_format)
.field("output_format", &self.output_format)
.finish_non_exhaustive()
}
}
pub struct EffectInput<'a> {
pub device: &'a wgpu::Device,
pub queue: &'a wgpu::Queue,
pub texture: &'a wgpu::Texture,
pub view: wgpu::TextureView,
pub format: wgpu::TextureFormat,
pub width: u32,
pub height: u32,
pub timing: EffectFrameTiming,
}
impl fmt::Debug for EffectInput<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("EffectInput")
.field("format", &self.format)
.field("width", &self.width)
.field("height", &self.height)
.field("timing", &self.timing)
.finish_non_exhaustive()
}
}
pub struct EffectOutput<'a> {
pub device: &'a wgpu::Device,
pub queue: &'a wgpu::Queue,
pub texture: &'a wgpu::Texture,
pub view: wgpu::TextureView,
pub format: wgpu::TextureFormat,
pub width: u32,
pub height: u32,
}
impl fmt::Debug for EffectOutput<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("EffectOutput")
.field("format", &self.format)
.field("width", &self.width)
.field("height", &self.height)
.finish_non_exhaustive()
}
}
pub trait Effect: 'static {
fn set_redraw_callback(&mut self, _callback: EffectRedrawCallback) {}
fn setup(&mut self, ctx: &EffectContext) -> impl Future<Output = EffectSetupResult>;
fn encode_render(
&mut self,
input: &EffectInput,
output: &EffectOutput,
encoder: &mut wgpu::CommandEncoder,
) -> EffectRenderResult;
fn render(&mut self, input: &EffectInput, output: &EffectOutput) -> EffectRenderResult {
let mut encoder = input
.device
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("filter effect encoder"),
});
let result = self.encode_render(input, output, &mut encoder);
input.queue.submit([encoder.finish()]);
result
}
#[must_use]
fn output_size(&self, input_width: u32, input_height: u32) -> (u32, u32) {
(input_width, input_height)
}
fn redraw_hint(&self) -> bool {
false
}
}