#![deny(missing_docs)]
#![deny(clippy::disallowed_methods, clippy::disallowed_types)]
#![cfg_attr(
not(test),
deny(
clippy::indexing_slicing,
clippy::arithmetic_side_effects,
clippy::cast_possible_truncation
)
)]
#[cfg(doctest)]
#[doc = include_str!("../README.md")]
struct ReadmeExamples;
pub mod draw;
pub mod evaluate;
mod periodic;
pub use evaluate::{
Evaluation, Field, Grid, Input, Invocation, Lane, Layout, Raw, evaluate, evaluate_raw, evaluate_raw_with,
evaluate_with,
};
pub use periodic::{evaluate_periodic, evaluate_periodic_with};
pub mod texture;
pub mod views;
pub use texture::{Filtering, SampledTexture, SamplerUse, Unstated};
pub use views::{Absent, Image, View, Views};
use iridia::Interface;
mod compile;
mod flat;
pub mod geometry;
mod mask;
pub mod mesh;
mod pipeline;
mod pixel;
pub mod probe;
mod rayquery;
mod request;
mod run;
mod stage;
pub mod tessellation;
pub mod tessellator;
pub mod value;
pub use flat::Executable;
pub use mask::{Mask, Wide};
pub use pipeline::{DispatchStats, Pipeline, PipelineResources, ShaderTable};
pub use pixel::ProgramShader;
pub use rayquery::{Built, Geometry, Instance, InstanceFlags, Scene, flags as ray_flags};
pub use request::{
Backend, CallShader, Dim, Element, ImageAtomic, ImageAtomicOp, ImageLoad, ImageLod, ImageQuery,
ImageSample, ImageStore, QueryKind, ReportHit, TraceRay,
};
pub use run::{Bound, Decision, Dispatched, Output, Resources, Runner, STEP_LIMIT, Scratch};
pub use stage::Constants;
use iridia::Program;
#[allow(clippy::cast_possible_truncation)] pub(crate) fn narrow(v: f64) -> f32 {
v as f32
}
pub(crate) fn lane_mask(lanes: u8) -> u8 {
0xffu8
.checked_shr(8u32.saturating_sub(u32::from(lanes.min(8))))
.unwrap_or(0)
}
pub(crate) fn error(detail: impl Into<String>) -> Error {
Error {
bytecode: "program",
at: 0,
detail: detail.into(),
}
}
impl Executable {
pub fn new(program: &Program) -> Result<Self, Error> {
if iridia::stage_functions(&program.interface)
.map_err(error)?
.is_some()
{
return Err(error(if program.stage.is_ray_tracing() {
format!(
"a {:?} program does the ray tracing pipeline's work through operations only the pipeline has \
({}): run it with Executable::function inside a pipeline (Pipeline)",
program.stage,
iridia::rt::OPERATIONS.join(", ")
)
} else {
format!(
"a {:?} program is functions, not an entry point: run one with Executable::function",
program.stage
)
}));
}
let info = program.validate().map_err(|problems| {
error(
problems
.iter()
.map(ToString::to_string)
.collect::<Vec<_>>()
.join("; "),
)
})?;
compile::compile(program, &info, None).map_err(error)
}
pub fn function(program: &Program, name: &str) -> Result<Self, Error> {
let info = program.validate().map_err(|problems| {
error(
problems
.iter()
.map(ToString::to_string)
.collect::<Vec<_>>()
.join("; "),
)
})?;
compile::compile(program, &info, Some(name)).map_err(error)
}
pub fn functions(program: &Program) -> Result<Option<Vec<(&'static str, Self)>>, Error> {
let Some(signatures) = iridia::stage_functions(&program.interface).map_err(error)? else {
return Ok(None);
};
signatures
.iter()
.map(|sig| Self::function(program, sig.name).map(|executable| (sig.name, executable)))
.collect::<Result<Vec<_>, _>>()
.map(Some)
}
pub(crate) fn pointer_words(&self, k: usize) -> Option<u32> {
self.pointer_args.get(k).map(|a| a.2)
}
pub fn sampled(
&self,
) -> (
std::collections::BTreeSet<iridia::ResourceId>,
std::collections::BTreeSet<iridia::ResourceId>,
) {
let (mut textures, mut samplers) = (
std::collections::BTreeSet::new(),
std::collections::BTreeSet::new(),
);
for (_, op) in &self.ops {
match op {
flat::Op::Sample { tex, smp, .. } => {
textures.insert(tex.id);
samplers.insert(smp.id);
}
flat::Op::Texel {
tex, storage: false, ..
}
| flat::Op::Query { tex, .. } => {
textures.insert(tex.id);
}
_ => {}
}
}
(textures, samplers)
}
pub fn needs_quad(&self) -> bool {
self.ops.iter().any(|(_, op)| {
matches!(
op,
flat::Op::Deriv { .. }
| flat::Op::Sample {
level: flat::LevelOp::Auto | flat::LevelOp::Bias(_),
..
}
)
})
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Lod {
Implicit,
Bias(f32),
Level(f32),
Gradients([[f32; 2]; 2]),
GradientsBias([[f32; 2]; 2], f32),
}
impl From<ImageLod> for Lod {
fn from(lod: ImageLod) -> Self {
match lod {
ImageLod::Implicit => Lod::Implicit,
ImageLod::Bias(bias) => Lod::Bias(bias),
ImageLod::Level(level) => Lod::Level(level),
ImageLod::Gradients(g) => Lod::Gradients([[g[0][0], g[0][1]], [g[1][0], g[1][1]]]),
ImageLod::GradientsBias(g, bias) => {
Lod::GradientsBias([[g[0][0], g[0][1]], [g[1][0], g[1][1]]], bias)
}
}
}
}
impl From<Lod> for ImageLod {
fn from(lod: Lod) -> Self {
let lift = |g: [[f32; 2]; 2]| [[g[0][0], g[0][1], 0.], [g[1][0], g[1][1], 0.]];
match lod {
Lod::Implicit => ImageLod::Implicit,
Lod::Bias(bias) => ImageLod::Bias(bias),
Lod::Level(level) => ImageLod::Level(level),
Lod::Gradients(g) => ImageLod::Gradients(lift(g)),
Lod::GradientsBias(g, bias) => ImageLod::GradientsBias(lift(g), bias),
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct Sample {
pub texture: u32,
pub sampler: u32,
pub uv: [f32; 2],
pub layer: Option<f32>,
pub lod: Lod,
}
#[derive(Clone, Copy, Debug)]
pub struct Load {
pub texture: u32,
pub texel: [i32; 3],
pub mip: i32,
}
#[derive(Clone, Copy, Debug)]
pub struct Store {
pub view: u32,
pub address: [u32; 3],
pub value: [u32; 4],
pub mask: u8,
}
#[derive(Clone, Copy, Debug)]
pub struct AtomicAdd {
pub view: u32,
pub address: [u32; 3],
pub value: u32,
}
pub trait Sampler {
fn sample(&mut self, request: Sample) -> std::result::Result<[f32; 4], String>;
fn load(&mut self, request: Load) -> std::result::Result<[u32; 4], String> {
Err(format!("t{}: no loads given", request.texture))
}
fn store(&mut self, request: Store) -> std::result::Result<(), String> {
Err(format!("u{}: no stores given", request.view))
}
fn atomic_add(&mut self, request: AtomicAdd) -> std::result::Result<u32, String> {
Err(format!("u{}: no atomics given", request.view))
}
fn structured(&mut self, slot: u32, element: u32, offset: u32) -> std::result::Result<u32, String> {
let _ = (element, offset);
Err(format!("t{slot}: no structured buffer given"))
}
fn buffer_elements(&mut self, slot: u32) -> Option<u32> {
let _ = slot;
None
}
fn sample_image(&mut self, request: ImageSample<u32>) -> std::result::Result<[f32; 4], String> {
if request.dim != Dim::D2
|| request.gather.is_some()
|| request.depth_ref.is_some()
|| request.depth
|| request.offset != [0; 3]
{
return Err(format!(
"t{}: this sampler answers plain 2D samples only",
request.texture
));
}
self.sample(Sample {
texture: request.texture,
sampler: request.sampler,
uv: [request.coord[0], request.coord[1]],
layer: request.layer,
lod: Lod::from(request.lod),
})
}
fn load_image(&mut self, request: ImageLoad<u32>) -> std::result::Result<[u32; 4], String> {
if !matches!(request.dim, Dim::D2 | Dim::D3) || request.storage || request.sample.is_some() {
return Err(format!(
"t{}: this sampler answers 2D, array and 3D loads of shader resources only",
request.texture
));
}
self.load(Load {
texture: request.texture,
texel: [
request.texel[0],
request.texel[1],
request.layer.unwrap_or(request.texel[2]),
],
mip: request.mip.unwrap_or(0),
})
}
fn query_image(&mut self, request: ImageQuery<u32>) -> std::result::Result<[u32; 4], String> {
Err(format!("t{}: no queries given", request.texture))
}
fn atomic_image(&mut self, request: ImageAtomic<u32>) -> std::result::Result<u32, String> {
Err(format!("u{}: no image atomics given", request.view))
}
fn store_image(&mut self, request: ImageStore<u32>) -> std::result::Result<(), String> {
self.store(Store {
view: request.view,
address: [
request.texel[0],
request.texel[1],
request.layer.unwrap_or(request.texel[2]),
],
value: request.value,
mask: lane_mask(request.lanes),
})
}
}
impl<F: FnMut(Sample) -> std::result::Result<[f32; 4], String>> Sampler for F {
fn sample(&mut self, request: Sample) -> std::result::Result<[f32; 4], String> {
self(request)
}
}
#[derive(Clone, Debug, Default)]
pub struct Execution {
pub outputs: Vec<[u32; 4]>,
pub output_masks: Vec<u8>,
pub instructions: usize,
pub samples: usize,
pub discarded: bool,
}
#[derive(Debug, thiserror::Error)]
#[error("{bytecode} at {at}: {detail}")]
pub struct Error {
pub bytecode: &'static str,
pub at: usize,
pub detail: String,
}
pub trait PixelShader: std::fmt::Debug + Sync {
fn interface(&self) -> &Interface;
fn input_count(&self) -> usize;
fn constant_buffers(&self) -> &[(u32, u32)];
fn textures(&self) -> &[u32];
fn samplers(&self) -> &[u32];
fn execute(
&self,
inputs: &[[u32; 4]],
buffers: &[(u32, &[[u32; 4]])],
sampler: &mut dyn Sampler,
) -> std::result::Result<Execution, Error>;
fn execute_with_derivatives(
&self,
inputs: &[[u32; 4]],
derivatives: &[[[u32; 4]; 2]],
buffers: &[(u32, &[[u32; 4]])],
sampler: &mut dyn Sampler,
) -> std::result::Result<Execution, Error> {
let _ = derivatives;
self.execute(inputs, buffers, sampler)
}
fn execute_lanes(
&self,
inputs: &[&[[u32; 4]]],
derivatives: &[[[u32; 4]; 2]],
buffers: &[(u32, &[[u32; 4]])],
sampler: &mut dyn Sampler,
each: &mut dyn FnMut(usize, &Execution),
) -> std::result::Result<(), (usize, Error)> {
for (i, one) in inputs.iter().enumerate() {
let e = self
.execute_with_derivatives(one, derivatives, buffers, sampler)
.map_err(|e| (i, e))?;
each(i, &e);
}
Ok(())
}
fn execute_quad(
&self,
inputs: [&[[u32; 4]]; 4],
buffers: &[(u32, &[[u32; 4]])],
sampler: &mut dyn Sampler,
) -> std::result::Result<[Execution; 4], Error> {
let _ = (inputs, buffers, sampler);
Err(Error {
bytecode: "this bytecode",
at: 0,
detail: "no quad evaluation".to_owned(),
})
}
}