use crate::canvas::{Pass, Recording, TextureSource};
use crate::ramp::RAMP_WIDTH;
use impeller_hal::{
Error, Extent2D, Hal, HalContext, HalTexture, PixelFormat, Result, TextureDescriptor,
};
pub struct Transient<H: Hal> {
pub layers: Vec<H::Texture>,
pub ramps: Vec<H::Texture>,
}
impl<H: Hal> Transient<H> {
fn empty() -> Self {
Self {
layers: Vec::new(),
ramps: Vec::new(),
}
}
pub fn into_textures(self) -> Vec<H::Texture> {
let mut all = self.layers;
all.extend(self.ramps);
all
}
pub fn destroy(self, ctx: &mut H::Context)
where
H::Context: HalContext<Hal = H>,
{
for texture in self.layers.into_iter().chain(self.ramps) {
ctx.destroy_texture(texture);
}
}
}
fn upload_ramps<H: Hal>(ctx: &mut H::Context, recording: &Recording) -> Result<Vec<H::Texture>>
where
H::Context: HalContext<Hal = H>,
{
let mut uploaded = Vec::with_capacity(recording.ramps.len());
for ramp in &recording.ramps {
let extent = Extent2D::new(RAMP_WIDTH as u32, 1);
let outcome = ctx
.create_texture(&TextureDescriptor::sampled(
extent,
PixelFormat::Rgba16Float,
))
.and_then(
|mut texture| match ctx.write_texture(&mut texture, &ramp.texels) {
Ok(()) => Ok(texture),
Err(e) => {
ctx.destroy_texture(texture);
Err(e)
}
},
);
match outcome {
Ok(texture) => uploaded.push(texture),
Err(e) => {
for texture in uploaded {
ctx.destroy_texture(texture);
}
return Err(e);
}
}
}
Ok(uploaded)
}
pub fn resolve_sources<'a, H: Hal>(
pass: &Pass,
images: &[&'a H::Texture],
transient: &'a Transient<H>,
) -> Result<Vec<&'a H::Texture>> {
let mut table = Vec::with_capacity(pass.sources.len());
for source in &pass.sources {
match source {
TextureSource::Image(slot) => {
table.push(*images.get(*slot as usize).ok_or(Error::Unsupported(
"a paint samples an image the caller did not supply",
))?);
}
TextureSource::Layer(pass_index) => {
table.push(transient.layers.get(*pass_index).ok_or(Error::Unsupported(
"a layer is composited before it is rendered",
))?);
}
TextureSource::Ramp(index) => {
table.push(transient.ramps.get(*index).ok_or(Error::Unsupported(
"a gradient names a ramp the recording does not carry",
))?);
}
}
}
Ok(table)
}
pub fn execute_layers<H: Hal>(
ctx: &mut H::Context,
recording: &Recording,
images: &[&H::Texture],
format: PixelFormat,
) -> Result<Transient<H>>
where
H::Context: HalContext<Hal = H>,
{
let mut transient = Transient::<H>::empty();
transient.ramps = upload_ramps::<H>(ctx, recording)?;
for pass in &recording.passes[..recording.passes.len() - 1] {
let outcome = resolve_sources::<H>(pass, images, &transient).and_then(|table| {
let mut target =
ctx.create_texture(&TextureDescriptor::offscreen(pass.extent, format))?;
let result =
ctx.submit_batch_textured(&mut target, &pass.batch, pass.descriptor, &table);
Ok((target, result))
});
match outcome {
Ok((target, result)) => {
transient.layers.push(target);
if let Err(e) = result {
transient.destroy(ctx);
return Err(e);
}
}
Err(e) => {
transient.destroy(ctx);
return Err(e);
}
}
}
Ok(transient)
}
pub fn execute<H: Hal>(
ctx: &mut H::Context,
surface: &mut H::Texture,
recording: &Recording,
images: &[&H::Texture],
) -> Result<()>
where
H::Context: HalContext<Hal = H>,
{
let transient = execute_layers::<H>(ctx, recording, images, surface.format().intermediate())?;
let root = recording.root();
let outcome = resolve_sources::<H>(root, images, &transient)
.and_then(|table| ctx.submit_batch_textured(surface, &root.batch, root.descriptor, &table));
transient.destroy(ctx);
outcome
}
pub fn render_offscreen<H: Hal>(
ctx: &mut H::Context,
recording: &Recording,
images: &[&H::Texture],
) -> Result<Vec<u8>>
where
H::Context: HalContext<Hal = H>,
{
render_offscreen_into::<H>(ctx, recording, images, PixelFormat::Rgba8Unorm)
}
pub fn render_offscreen_into<H: Hal>(
ctx: &mut H::Context,
recording: &Recording,
images: &[&H::Texture],
format: PixelFormat,
) -> Result<Vec<u8>>
where
H::Context: HalContext<Hal = H>,
{
let mut target = ctx.create_texture(&TextureDescriptor::offscreen(recording.extent, format))?;
let outcome = execute::<H>(ctx, &mut target, recording, images)
.and_then(|()| ctx.read_texture(&mut target));
ctx.destroy_texture(target);
outcome
}
pub fn execute_deferred<H: Hal>(
ctx: &mut H::Context,
surface: &mut H::Texture,
recording: &Recording,
images: &[&H::Texture],
) -> Result<(H::Fence, Transient<H>)>
where
H::Context: HalContext<Hal = H>,
{
let transient = execute_layers::<H>(ctx, recording, images, surface.format().intermediate())?;
let root = recording.root();
let outcome = resolve_sources::<H>(root, images, &transient).and_then(|table| {
ctx.submit_batch_deferred_textured(surface, &root.batch, root.descriptor, &table)
});
match outcome {
Ok(fence) => Ok((fence, transient)),
Err(e) => {
transient.destroy(ctx);
Err(e)
}
}
}