use impeller_hal::{
Batch, BlendMode, Extent2D, Material, PassDescriptor, PixelFormat, RuntimeProgram,
TextureDescriptor,
};
use impeller_hal_vulkan::{DevicePreference, Validated};
const SIZE: Extent2D = Extent2D {
width: 32,
height: 32,
};
const FULL: [[f32; 2]; 4] = [[-1.0, -1.0], [1.0, -1.0], [1.0, 1.0], [-1.0, 1.0]];
const QUAD: [u32; 6] = [0, 1, 2, 0, 2, 3];
fn context() -> Option<Validated> {
Validated::new(DevicePreference::Auto).ok()
}
fn uniforms(left: [f32; 4], right: [f32; 4], threshold: f32) -> Vec<f32> {
let mut out = vec![0.0; impeller_hal::RUNTIME_FLOATS];
out[0..4].copy_from_slice(&left);
out[4..8].copy_from_slice(&right);
out[20] = threshold;
out
}
fn render(ctx: &mut Validated, material: Material) -> Vec<u8> {
let mut batch = Batch::new();
batch
.push(&FULL, &QUAD, material, BlendMode::Src)
.expect("push");
let mut target = ctx
.create_texture(&TextureDescriptor::offscreen(SIZE, PixelFormat::Rgba8Unorm))
.expect("target");
ctx.submit_batch(
&mut target,
&batch,
PassDescriptor::clear([0.0, 0.0, 0.0, 1.0]),
)
.expect("submit");
let pixels = ctx.read_texture(&mut target).expect("readback");
ctx.destroy_texture(target);
pixels
}
fn pixel(pixels: &[u8], x: u32, y: u32) -> [u8; 4] {
let i = ((y * SIZE.width + x) * 4) as usize;
[pixels[i], pixels[i + 1], pixels[i + 2], pixels[i + 3]]
}
fn program() -> RuntimeProgram {
RuntimeProgram {
spirv: impeller_shaders::EFFECT_SPV.to_vec(),
glsl_es: impeller_shaders::EFFECT_FS_GLSL.to_string(),
}
}
#[test]
fn a_registered_program_draws_its_own_picture() {
let Some(mut ctx) = context() else { return };
let id = ctx.register_program(&program()).expect("register");
let pixels = render(
&mut ctx,
Material::Runtime {
program: id,
uniforms: uniforms([1.0, 0.0, 0.0, 1.0], [0.0, 0.0, 1.0, 1.0], 0.0),
textures: [None; impeller_hal::MAX_EFFECT_TEXTURES],
},
);
assert_eq!(pixel(&pixels, 4, 16), [255, 0, 0, 255], "left of the split");
assert_eq!(pixel(&pixels, 28, 16), [0, 0, 255, 255], "right of it");
}
#[test]
fn the_uniforms_a_caller_packed_are_the_ones_the_program_reads() {
let Some(mut ctx) = context() else { return };
let id = ctx.register_program(&program()).expect("register");
let at = |ctx: &mut Validated, threshold: f32| {
render(
ctx,
Material::Runtime {
program: id,
uniforms: uniforms([1.0, 0.0, 0.0, 1.0], [0.0, 0.0, 1.0, 1.0], threshold),
textures: [None; impeller_hal::MAX_EFFECT_TEXTURES],
},
)
};
let moved = at(&mut ctx, 0.5);
assert_eq!(
pixel(&moved, 20, 16),
[255, 0, 0, 255],
"the split should have moved with the uniform"
);
assert_eq!(pixel(&moved, 28, 16), [0, 0, 255, 255], "and not past it");
}
#[test]
fn a_program_nobody_registered_is_refused_rather_than_drawn() {
let Some(mut ctx) = context() else { return };
let mut batch = Batch::new();
batch
.push(
&FULL,
&QUAD,
Material::Runtime {
program: 7,
uniforms: uniforms([1.0; 4], [0.0; 4], 0.0),
textures: [None; impeller_hal::MAX_EFFECT_TEXTURES],
},
BlendMode::Src,
)
.expect("push");
let mut target = ctx
.create_texture(&TextureDescriptor::offscreen(SIZE, PixelFormat::Rgba8Unorm))
.expect("target");
let result = ctx.submit_batch(&mut target, &batch, PassDescriptor::clear([0.0; 4]));
ctx.destroy_texture(target);
assert!(
result.is_err(),
"naming a program nobody registered should be refused, not drawn with whatever is at that index"
);
}
#[test]
fn a_built_in_material_still_draws_beside_a_registered_one() {
let Some(mut ctx) = context() else { return };
let id = ctx.register_program(&program()).expect("register");
let mut batch = Batch::new();
batch
.push(
&FULL,
&QUAD,
Material::Runtime {
program: id,
uniforms: uniforms([1.0, 0.0, 0.0, 1.0], [0.0, 0.0, 1.0, 1.0], 0.0),
textures: [None; impeller_hal::MAX_EFFECT_TEXTURES],
},
BlendMode::Src,
)
.expect("effect");
batch
.push(
&[[0.0, -1.0], [1.0, -1.0], [1.0, 1.0], [0.0, 1.0]],
&QUAD,
Material::solid([0.0, 1.0, 0.0, 1.0]),
BlendMode::Src,
)
.expect("solid");
let mut target = ctx
.create_texture(&TextureDescriptor::offscreen(SIZE, PixelFormat::Rgba8Unorm))
.expect("target");
ctx.submit_batch(
&mut target,
&batch,
PassDescriptor::clear([0.0, 0.0, 0.0, 1.0]),
)
.expect("submit");
let pixels = ctx.read_texture(&mut target).expect("readback");
ctx.destroy_texture(target);
assert_eq!(pixel(&pixels, 4, 16), [255, 0, 0, 255], "the effect's half");
assert_eq!(pixel(&pixels, 24, 16), [0, 255, 0, 255], "the built-in one");
}
#[test]
fn registering_one_program_twice_gives_one_program() {
let Some(mut ctx) = context() else { return };
let first = ctx.register_program(&program()).expect("first");
let second = ctx.register_program(&program()).expect("second");
assert_eq!(first, second, "the same payload should be the same program");
}
#[test]
fn an_effect_reads_the_texture_the_draw_named() {
let Some(mut ctx) = context() else { return };
let id = ctx
.register_program(&RuntimeProgram {
spirv: impeller_shaders::EFFECT_IMAGE_SPV.to_vec(),
glsl_es: impeller_shaders::EFFECT_IMAGE_FS_GLSL.to_string(),
})
.expect("register");
let mut source = ctx
.create_texture(&TextureDescriptor::offscreen(
Extent2D::new(2, 1),
PixelFormat::Rgba8Unorm,
))
.expect("source");
ctx.write_texture(&mut source, &[255, 0, 0, 255, 0, 0, 255, 255])
.expect("upload");
let mut uniforms = vec![0.0; impeller_hal::RUNTIME_FLOATS];
uniforms[0..4].copy_from_slice(&[1.0, 1.0, 1.0, 1.0]);
let mut batch = Batch::new();
batch
.push(
&FULL,
&QUAD,
Material::Runtime {
program: id,
uniforms,
textures: [Some(0), None, None, None],
},
BlendMode::Src,
)
.expect("push");
let mut target = ctx
.create_texture(&TextureDescriptor::offscreen(SIZE, PixelFormat::Rgba8Unorm))
.expect("target");
ctx.submit_batch_textured(
&mut target,
&batch,
PassDescriptor::clear([0.0, 0.0, 0.0, 1.0]),
&[&source],
)
.expect("submit");
let pixels = ctx.read_texture(&mut target).expect("readback");
ctx.destroy_texture(target);
ctx.destroy_texture(source);
assert_eq!(pixel(&pixels, 4, 16), [255, 0, 0, 255], "the first texel");
assert_eq!(pixel(&pixels, 28, 16), [0, 0, 255, 255], "the second");
}
#[test]
fn an_effect_naming_no_texture_reads_the_placeholder_rather_than_failing() {
let Some(mut ctx) = context() else { return };
let id = ctx
.register_program(&RuntimeProgram {
spirv: impeller_shaders::EFFECT_IMAGE_SPV.to_vec(),
glsl_es: impeller_shaders::EFFECT_IMAGE_FS_GLSL.to_string(),
})
.expect("register");
let mut uniforms = vec![0.0; impeller_hal::RUNTIME_FLOATS];
uniforms[0..4].copy_from_slice(&[0.0, 1.0, 0.0, 1.0]);
let pixels = render(
&mut ctx,
Material::Runtime {
program: id,
uniforms,
textures: [None; impeller_hal::MAX_EFFECT_TEXTURES],
},
);
assert_eq!(
pixel(&pixels, 16, 16),
[0, 255, 0, 255],
"white placeholder times the tint is the tint"
);
}