use super::{blender, utils::*, Color};
use crate::data::{DrawType, DynamicTexture, VxDraw};
use ::image as load_image;
use cgmath::{Matrix4, Rad};
use core::ptr::read;
#[cfg(feature = "dx12")]
use gfx_backend_dx12 as back;
#[cfg(feature = "gl")]
use gfx_backend_gl as back;
#[cfg(feature = "metal")]
use gfx_backend_metal as back;
#[cfg(feature = "vulkan")]
use gfx_backend_vulkan as back;
use gfx_hal::{
adapter::PhysicalDevice,
command,
device::Device,
format, image, memory,
memory::Properties,
pass,
pso::{self, DescriptorPool},
Backend, Primitive,
};
use std::{io::Cursor, mem::ManuallyDrop};
pub struct Handle(usize, usize);
pub struct Layer(usize);
impl Layerable for Layer {
fn get_layer(&self, vx: &VxDraw) -> usize {
for (idx, ord) in vx.draw_order.iter().enumerate() {
match ord {
DrawType::DynamicTexture { id } if *id == self.0 => {
return idx;
}
_ => {}
}
}
panic!["Unable to get layer"]
}
}
#[derive(Clone)]
pub enum VertexShader {
Spirv(Vec<u8>),
Standard,
}
#[derive(Clone)]
pub enum FragmentShader {
Spirv(Vec<u8>),
Standard,
}
#[derive(Clone)]
pub struct LayerOptions {
depth_test: bool,
fixed_perspective: Option<Matrix4<f32>>,
filtering: Filter,
wrap_mode: WrapMode,
blend: blender::Blender,
vertex_shader: VertexShader,
fragment_shader: FragmentShader,
}
impl LayerOptions {
pub fn new() -> Self {
Self::default()
}
pub fn vertex_shader(mut self, shader: VertexShader) -> Self {
self.vertex_shader = shader;
self
}
pub fn fragment_shader(mut self, shader: FragmentShader) -> Self {
self.fragment_shader = shader;
self
}
pub fn filter(mut self, filter: Filter) -> Self {
self.filtering = filter;
self
}
pub fn depth(mut self, depth: bool) -> Self {
self.depth_test = depth;
self
}
pub fn fixed_perspective(mut self, mat: Matrix4<f32>) -> Self {
self.fixed_perspective = Some(mat);
self
}
pub fn wrap_mode(mut self, wrap_mode: WrapMode) -> Self {
self.wrap_mode = wrap_mode;
self
}
pub fn blend(mut self, blend_setter: impl Fn(blender::Blender) -> blender::Blender) -> Self {
self.blend = blend_setter(self.blend);
self
}
}
impl Default for LayerOptions {
fn default() -> Self {
Self {
depth_test: true,
fixed_perspective: None,
filtering: Filter::Nearest,
wrap_mode: WrapMode::Tile,
blend: blender::Blender::default(),
vertex_shader: VertexShader::Standard,
fragment_shader: FragmentShader::Standard,
}
}
}
#[derive(Clone, Copy)]
pub enum Filter {
Nearest,
Linear,
}
#[derive(Clone, Copy)]
pub enum WrapMode {
Tile,
Mirror,
Clamp,
}
#[derive(Clone, Copy)]
pub struct Sprite {
opacity: [u8; 4],
height: f32,
origin: (f32, f32),
rotation: f32,
scale: f32,
translation: (f32, f32),
uv_begin: (f32, f32),
uv_end: (f32, f32),
width: f32,
}
impl Sprite {
pub fn new() -> Self {
Self::default()
}
pub fn width(mut self, width: f32) -> Self {
self.width = width;
self
}
pub fn height(mut self, height: f32) -> Self {
self.height = height;
self
}
pub fn opacity(mut self, opacity: u8) -> Self {
self.opacity = [opacity; 4];
self
}
pub fn opacity_raw(mut self, opacity: [u8; 4]) -> Self {
self.opacity = opacity;
self
}
pub fn uv_begin(mut self, uv: (f32, f32)) -> Self {
self.uv_begin = uv;
self
}
pub fn uv_end(mut self, uv: (f32, f32)) -> Self {
self.uv_end = uv;
self
}
pub fn translation(mut self, trn: (f32, f32)) -> Self {
self.translation = trn;
self
}
pub fn rotation<T: Copy + Into<Rad<f32>>>(mut self, angle: T) -> Self {
self.rotation = angle.into().0;
self
}
pub fn scale(mut self, scale: f32) -> Self {
self.scale = scale;
self
}
pub fn origin(mut self, origin: (f32, f32)) -> Self {
self.origin = origin;
self
}
}
impl Default for Sprite {
fn default() -> Self {
Sprite {
width: 2.0,
height: 2.0,
opacity: [255; 4],
uv_begin: (0.0, 0.0),
uv_end: (1.0, 1.0),
translation: (0.0, 0.0),
rotation: 0.0,
scale: 1.0,
origin: (0.0, 0.0),
}
}
}
pub enum ImgData<'a> {
PNGBytes(&'a [u8]),
RawBytes {
width: usize,
height: usize,
bytes: &'a [u8],
},
}
pub struct Dyntex<'a> {
vx: &'a mut VxDraw,
}
impl<'a> Dyntex<'a> {
pub(crate) fn new(s: &'a mut VxDraw) -> Self {
Self { vx: s }
}
pub fn add_layer<'x>(&mut self, img_data: &ImgData<'x>, options: &LayerOptions) -> Layer {
match img_data {
ImgData::PNGBytes(ref bytes) => {
let image = load_image::load_from_memory_with_format(&bytes[..], load_image::PNG)
.unwrap()
.to_rgba();
let (width, height) = (image.width() as usize, image.height() as usize);
let img_bytes = image.into_raw();
self.add_layer_internal(width, height, &img_bytes[..], &options)
}
ImgData::RawBytes {
width,
height,
bytes,
} => self.add_layer_internal(*width, *height, bytes, options),
}
}
fn add_layer_internal(
&mut self,
img_width: usize,
img_height: usize,
img: &[u8],
options: &LayerOptions,
) -> Layer {
let s = &mut *self.vx;
let device = &s.device;
let pixel_size = 4;
let row_size = pixel_size * img_width;
let limits = s.adapter.physical_device.limits();
let row_alignment_mask = limits.optimal_buffer_copy_pitch_alignment as u32 - 1;
let row_pitch = ((row_size as u32 + row_alignment_mask) & !row_alignment_mask) as usize;
debug_assert!(row_pitch as usize >= row_size);
let required_bytes = row_pitch * img_height;
let mut image_upload_buffer = unsafe {
device.create_buffer(required_bytes as u64, gfx_hal::buffer::Usage::TRANSFER_SRC)
}
.unwrap();
let image_mem_reqs = unsafe { device.get_buffer_requirements(&image_upload_buffer) };
let memory_type_id = find_memory_type_id(
&s.adapter,
image_mem_reqs,
Properties::CPU_VISIBLE | Properties::COHERENT,
);
let image_upload_memory =
unsafe { device.allocate_memory(memory_type_id, image_mem_reqs.size) }.unwrap();
unsafe { device.bind_buffer_memory(&image_upload_memory, 0, &mut image_upload_buffer) }
.unwrap();
unsafe {
let mut writer = s
.device
.acquire_mapping_writer::<u8>(&image_upload_memory, 0..image_mem_reqs.size)
.expect("Unable to get mapping writer");
let mut idx = 0;
for y in 0..img_height {
let dest_base = y * row_pitch;
for row_index in 0..row_size {
writer[dest_base + row_index] = img[idx % img.len()];
idx += 1;
}
}
device
.release_mapping_writer(writer)
.expect("Couldn't release the mapping writer to the staging buffer!");
}
let mut the_image = unsafe {
device
.create_image(
image::Kind::D2(img_width as u32, img_height as u32, 1, 1),
1,
format::Format::Rgba8Srgb,
image::Tiling::Optimal,
image::Usage::TRANSFER_DST | image::Usage::SAMPLED,
image::ViewCapabilities::empty(),
)
.expect("Couldn't create the image!")
};
let image_memory = unsafe {
let requirements = device.get_image_requirements(&the_image);
let memory_type_id =
find_memory_type_id(&s.adapter, requirements, memory::Properties::DEVICE_LOCAL);
device
.allocate_memory(memory_type_id, requirements.size)
.expect("Unable to allocate")
};
let image_view = unsafe {
device
.bind_image_memory(&image_memory, 0, &mut the_image)
.expect("Unable to bind memory");
device
.create_image_view(
&the_image,
image::ViewKind::D2,
format::Format::Rgba8Srgb,
format::Swizzle::NO,
image::SubresourceRange {
aspects: format::Aspects::COLOR,
levels: 0..1,
layers: 0..1,
},
)
.expect("Couldn't create the image view!")
};
let sampler = unsafe {
s.device
.create_sampler(image::SamplerInfo::new(
match options.filtering {
Filter::Nearest => image::Filter::Nearest,
Filter::Linear => image::Filter::Linear,
},
match options.wrap_mode {
WrapMode::Tile => image::WrapMode::Tile,
WrapMode::Mirror => image::WrapMode::Mirror,
WrapMode::Clamp => image::WrapMode::Clamp,
},
))
.expect("Couldn't create the sampler!")
};
unsafe {
let mut cmd_buffer = s.command_pool.acquire_command_buffer::<command::OneShot>();
cmd_buffer.begin();
let image_barrier = memory::Barrier::Image {
states: (image::Access::empty(), image::Layout::Undefined)
..(
image::Access::TRANSFER_WRITE,
image::Layout::TransferDstOptimal,
),
target: &the_image,
families: None,
range: image::SubresourceRange {
aspects: format::Aspects::COLOR,
levels: 0..1,
layers: 0..1,
},
};
cmd_buffer.pipeline_barrier(
pso::PipelineStage::TOP_OF_PIPE..pso::PipelineStage::TRANSFER,
memory::Dependencies::empty(),
&[image_barrier],
);
cmd_buffer.copy_buffer_to_image(
&image_upload_buffer,
&the_image,
image::Layout::TransferDstOptimal,
&[command::BufferImageCopy {
buffer_offset: 0,
buffer_width: (row_pitch / pixel_size) as u32,
buffer_height: img_height as u32,
image_layers: gfx_hal::image::SubresourceLayers {
aspects: format::Aspects::COLOR,
level: 0,
layers: 0..1,
},
image_offset: image::Offset { x: 0, y: 0, z: 0 },
image_extent: image::Extent {
width: img_width as u32,
height: img_height as u32,
depth: 1,
},
}],
);
let image_barrier = memory::Barrier::Image {
states: (
image::Access::TRANSFER_WRITE,
image::Layout::TransferDstOptimal,
)
..(
image::Access::SHADER_READ,
image::Layout::ShaderReadOnlyOptimal,
),
target: &the_image,
families: None,
range: image::SubresourceRange {
aspects: format::Aspects::COLOR,
levels: 0..1,
layers: 0..1,
},
};
cmd_buffer.pipeline_barrier(
pso::PipelineStage::TRANSFER..pso::PipelineStage::FRAGMENT_SHADER,
memory::Dependencies::empty(),
&[image_barrier],
);
cmd_buffer.finish();
let upload_fence = s
.device
.create_fence(false)
.expect("Couldn't create an upload fence!");
s.queue_group.queues[0]
.submit_without_semaphores(Some(&cmd_buffer), Some(&upload_fence));
s.device
.wait_for_fence(&upload_fence, u64::max_value())
.expect("Couldn't wait for the fence!");
s.device.destroy_fence(upload_fence);
}
unsafe {
device.destroy_buffer(image_upload_buffer);
device.free_memory(image_upload_memory);
}
const VERTEX_SOURCE_TEXTURE: &[u8] = include_bytes!["../target/spirv/dyntex.vert.spirv"];
const FRAGMENT_SOURCE_TEXTURE: &[u8] = include_bytes!["../target/spirv/dyntex.frag.spirv"];
let vertex_source_texture = match options.vertex_shader {
VertexShader::Standard => pso::read_spirv(Cursor::new(VERTEX_SOURCE_TEXTURE)).unwrap(),
VertexShader::Spirv(ref data) => pso::read_spirv(Cursor::new(data)).unwrap(),
};
let fragment_source_texture = match options.fragment_shader {
FragmentShader::Standard => {
pso::read_spirv(Cursor::new(FRAGMENT_SOURCE_TEXTURE)).unwrap()
}
FragmentShader::Spirv(ref data) => pso::read_spirv(Cursor::new(data)).unwrap(),
};
let vs_module =
{ unsafe { s.device.create_shader_module(&vertex_source_texture) }.unwrap() };
let fs_module =
{ unsafe { s.device.create_shader_module(&fragment_source_texture) }.unwrap() };
const ENTRY_NAME: &str = "main";
let vs_module: <back::Backend as Backend>::ShaderModule = vs_module;
let (vs_entry, fs_entry) = (
pso::EntryPoint {
entry: ENTRY_NAME,
module: &vs_module,
specialization: pso::Specialization::default(),
},
pso::EntryPoint {
entry: ENTRY_NAME,
module: &fs_module,
specialization: pso::Specialization::default(),
},
);
let shader_entries = pso::GraphicsShaderSet {
vertex: vs_entry,
hull: None,
domain: None,
geometry: None,
fragment: Some(fs_entry),
};
let input_assembler = pso::InputAssemblerDesc::new(Primitive::TriangleList);
let vertex_buffers: Vec<pso::VertexBufferDesc> = vec![
pso::VertexBufferDesc {
binding: 0,
stride: 8,
rate: pso::VertexInputRate::Vertex,
},
pso::VertexBufferDesc {
binding: 1,
stride: 8,
rate: pso::VertexInputRate::Vertex,
},
pso::VertexBufferDesc {
binding: 2,
stride: 8,
rate: pso::VertexInputRate::Vertex,
},
pso::VertexBufferDesc {
binding: 3,
stride: 4,
rate: pso::VertexInputRate::Vertex,
},
pso::VertexBufferDesc {
binding: 4,
stride: 4,
rate: pso::VertexInputRate::Vertex,
},
pso::VertexBufferDesc {
binding: 5,
stride: 1,
rate: pso::VertexInputRate::Vertex,
},
];
let attributes: Vec<pso::AttributeDesc> = vec![
pso::AttributeDesc {
location: 0,
binding: 0,
element: pso::Element {
format: format::Format::Rg32Sfloat,
offset: 0,
},
},
pso::AttributeDesc {
location: 1,
binding: 1,
element: pso::Element {
format: format::Format::Rg32Sfloat,
offset: 0,
},
},
pso::AttributeDesc {
location: 2,
binding: 2,
element: pso::Element {
format: format::Format::Rg32Sfloat,
offset: 0,
},
},
pso::AttributeDesc {
location: 3,
binding: 3,
element: pso::Element {
format: format::Format::R32Sfloat,
offset: 0,
},
},
pso::AttributeDesc {
location: 4,
binding: 4,
element: pso::Element {
format: format::Format::R32Sfloat,
offset: 0,
},
},
pso::AttributeDesc {
location: 5,
binding: 5,
element: pso::Element {
format: format::Format::R8Unorm,
offset: 0,
},
},
];
let rasterizer = pso::Rasterizer {
depth_clamping: false,
polygon_mode: pso::PolygonMode::Fill,
cull_face: pso::Face::NONE,
front_face: pso::FrontFace::Clockwise,
depth_bias: None,
conservative: false,
};
let depth_stencil = pso::DepthStencilDesc {
depth: if options.depth_test {
Some(pso::DepthTest {
fun: pso::Comparison::LessEqual,
write: true,
})
} else {
None
},
depth_bounds: false,
stencil: None,
};
let blender = options.blend.clone().into_gfx_blender();
let render_pass = {
let attachment = pass::Attachment {
format: Some(s.format),
samples: 1,
ops: pass::AttachmentOps::new(
pass::AttachmentLoadOp::Clear,
pass::AttachmentStoreOp::Store,
),
stencil_ops: pass::AttachmentOps::DONT_CARE,
layouts: image::Layout::Undefined..image::Layout::Present,
};
let depth = pass::Attachment {
format: Some(format::Format::D32Sfloat),
samples: 1,
ops: pass::AttachmentOps::new(
pass::AttachmentLoadOp::Clear,
pass::AttachmentStoreOp::Store,
),
stencil_ops: pass::AttachmentOps::DONT_CARE,
layouts: image::Layout::Undefined..image::Layout::DepthStencilAttachmentOptimal,
};
let subpass = pass::SubpassDesc {
colors: &[(0, image::Layout::ColorAttachmentOptimal)],
depth_stencil: Some(&(1, image::Layout::DepthStencilAttachmentOptimal)),
inputs: &[],
resolves: &[],
preserves: &[],
};
unsafe {
s.device
.create_render_pass(&[attachment, depth], &[subpass], &[])
}
.expect("Can't create render pass")
};
let baked_states = pso::BakedStates {
viewport: None,
scissor: None,
blend_color: None,
depth_bounds: None,
};
let mut bindings = Vec::<pso::DescriptorSetLayoutBinding>::new();
bindings.push(pso::DescriptorSetLayoutBinding {
binding: 0,
ty: pso::DescriptorType::SampledImage,
count: 1,
stage_flags: pso::ShaderStageFlags::FRAGMENT,
immutable_samplers: false,
});
bindings.push(pso::DescriptorSetLayoutBinding {
binding: 1,
ty: pso::DescriptorType::Sampler,
count: 1,
stage_flags: pso::ShaderStageFlags::FRAGMENT,
immutable_samplers: false,
});
let immutable_samplers = Vec::<<back::Backend as Backend>::Sampler>::new();
let descriptor_set_layouts: Vec<<back::Backend as Backend>::DescriptorSetLayout> =
vec![unsafe {
s.device
.create_descriptor_set_layout(bindings, immutable_samplers)
.expect("Couldn't make a DescriptorSetLayout")
}];
let mut descriptor_pool = unsafe {
s.device
.create_descriptor_pool(
1,
&[
pso::DescriptorRangeDesc {
ty: pso::DescriptorType::SampledImage,
count: 1,
},
pso::DescriptorRangeDesc {
ty: pso::DescriptorType::Sampler,
count: 1,
},
],
pso::DescriptorPoolCreateFlags::empty(),
)
.expect("Couldn't create a descriptor pool!")
};
let descriptor_set = unsafe {
descriptor_pool
.allocate_set(&descriptor_set_layouts[0])
.expect("Couldn't make a Descriptor Set!")
};
unsafe {
s.device.write_descriptor_sets(vec![
pso::DescriptorSetWrite {
set: &descriptor_set,
binding: 0,
array_offset: 0,
descriptors: Some(pso::Descriptor::Image(
&image_view,
image::Layout::ShaderReadOnlyOptimal,
)),
},
pso::DescriptorSetWrite {
set: &descriptor_set,
binding: 1,
array_offset: 0,
descriptors: Some(pso::Descriptor::Sampler(&sampler)),
},
]);
}
let mut push_constants = Vec::<(pso::ShaderStageFlags, core::ops::Range<u32>)>::new();
push_constants.push((pso::ShaderStageFlags::VERTEX, 0..16));
let pipeline_layout = unsafe {
s.device
.create_pipeline_layout(&descriptor_set_layouts, push_constants)
.expect("Couldn't create a pipeline layout")
};
let pipeline_desc = pso::GraphicsPipelineDesc {
shaders: shader_entries,
rasterizer,
vertex_buffers,
attributes,
input_assembler,
blender,
depth_stencil,
multisampling: None,
baked_states,
layout: &pipeline_layout,
subpass: pass::Subpass {
index: 0,
main_pass: &render_pass,
},
flags: pso::PipelineCreationFlags::empty(),
parent: pso::BasePipeline::None,
};
let pipeline = unsafe {
s.device
.create_graphics_pipeline(&pipeline_desc, None)
.expect("Couldn't create a graphics pipeline!")
};
unsafe {
s.device.destroy_shader_module(vs_module);
s.device.destroy_shader_module(fs_module);
}
let image_count = s.swapconfig.image_count;
let posbuf = (0..image_count)
.map(|_| super::utils::ResizBuf::new(&s.device, &s.adapter))
.collect::<Vec<_>>();
let opacbuf = (0..image_count)
.map(|_| super::utils::ResizBuf::new(&s.device, &s.adapter))
.collect::<Vec<_>>();
let uvbuf = (0..image_count)
.map(|_| super::utils::ResizBuf::new(&s.device, &s.adapter))
.collect::<Vec<_>>();
let tranbuf = (0..image_count)
.map(|_| super::utils::ResizBuf::new(&s.device, &s.adapter))
.collect::<Vec<_>>();
let rotbuf = (0..image_count)
.map(|_| super::utils::ResizBuf::new(&s.device, &s.adapter))
.collect::<Vec<_>>();
let scalebuf = (0..image_count)
.map(|_| super::utils::ResizBuf::new(&s.device, &s.adapter))
.collect::<Vec<_>>();
let indices = (0..image_count)
.map(|_| super::utils::ResizBufIdx4::new(&s.device, &s.adapter))
.collect::<Vec<_>>();
let dyntex = DynamicTexture {
hidden: false,
fixed_perspective: options.fixed_perspective,
removed: vec![],
posbuf_touch: 0,
opacbuf_touch: 0,
uvbuf_touch: 0,
tranbuf_touch: 0,
rotbuf_touch: 0,
scalebuf_touch: 0,
posbuffer: vec![],
opacbuffer: vec![],
uvbuffer: vec![],
tranbuffer: vec![],
rotbuffer: vec![],
scalebuffer: vec![],
posbuf,
opacbuf,
uvbuf,
tranbuf,
rotbuf,
scalebuf,
indices,
texture_image_buffer: ManuallyDrop::new(the_image),
texture_image_memory: ManuallyDrop::new(image_memory),
descriptor_pool: ManuallyDrop::new(descriptor_pool),
image_view: ManuallyDrop::new(image_view),
sampler: ManuallyDrop::new(sampler),
descriptor_set: ManuallyDrop::new(descriptor_set),
descriptor_set_layouts,
pipeline: ManuallyDrop::new(pipeline),
pipeline_layout: ManuallyDrop::new(pipeline_layout),
render_pass: ManuallyDrop::new(render_pass),
};
let prev_layer = s.layer_holes.find_available(|x| match x {
DrawType::DynamicTexture { .. } => true,
_ => false,
});
if let Some(prev_layer) = prev_layer {
match prev_layer {
DrawType::DynamicTexture { id } => {
let old_dyntex = std::mem::replace(&mut s.dyntexs[id], dyntex);
destroy_texture(s, old_dyntex);
s.draw_order.push(DrawType::DynamicTexture { id });
Layer(id)
}
_ => panic!["Got a non-dyntex drawtype, should be impossible!"],
}
} else {
s.dyntexs.push(dyntex);
s.draw_order.push(DrawType::DynamicTexture {
id: s.dyntexs.len() - 1,
});
Layer(s.dyntexs.len() - 1)
}
}
pub fn layer_count(&self) -> usize {
self.vx.dyntexs.len()
}
pub fn hide(&mut self, layer: &Layer) {
self.vx.dyntexs[layer.0].hidden = true;
}
pub fn show(&mut self, layer: &Layer) {
self.vx.dyntexs[layer.0].hidden = false;
}
pub fn remove_layer(&mut self, layer: Layer) {
let s = &mut *self.vx;
let mut index = None;
for (idx, x) in s.draw_order.iter().enumerate() {
match x {
DrawType::DynamicTexture { id } if *id == layer.0 => {
index = Some(idx);
break;
}
_ => {}
}
}
if let Some(idx) = index {
let draw_type = s.draw_order.remove(idx);
s.layer_holes.push(draw_type);
}
}
pub fn add(&mut self, layer: &Layer, sprite: Sprite) -> Handle {
let uv_a = sprite.uv_begin;
let uv_b = sprite.uv_end;
let width = sprite.width;
let height = sprite.height;
let topleft = (
-width / 2f32 - sprite.origin.0,
-height / 2f32 - sprite.origin.1,
);
let topleft_uv = uv_a;
let topright = (
width / 2f32 - sprite.origin.0,
-height / 2f32 - sprite.origin.1,
);
let topright_uv = (uv_b.0, uv_a.1);
let bottomleft = (
-width / 2f32 - sprite.origin.0,
height / 2f32 - sprite.origin.1,
);
let bottomleft_uv = (uv_a.0, uv_b.1);
let bottomright = (
width / 2f32 - sprite.origin.0,
height / 2f32 - sprite.origin.1,
);
let bottomright_uv = (uv_b.0, uv_b.1);
let replace = self.vx.dyntexs.get(layer.0).map(|x| !x.removed.is_empty());
if replace.is_none() {
panic!["Layer does not exist"];
}
let handle = if replace.unwrap() {
let hole = self.vx.dyntexs[layer.0].removed.pop().unwrap();
let handle = Handle(layer.0, hole);
self.set_deform(
&handle,
[
(topleft.0, topleft.1),
(bottomleft.0, bottomleft.1),
(bottomright.0, bottomright.1),
(topright.0, topright.1),
],
);
self.set_opacity_raw(
&handle,
[
sprite.opacity[0],
sprite.opacity[1],
sprite.opacity[2],
sprite.opacity[3],
],
);
self.set_translation(&handle, (sprite.translation.0, sprite.translation.1));
self.set_rotation(&handle, Rad(sprite.rotation));
self.set_scale(&handle, sprite.scale);
self.set_uv(&handle, sprite.uv_begin, sprite.uv_end);
hole
} else {
let tex = &mut self.vx.dyntexs[layer.0];
tex.posbuffer.push([
topleft.0,
topleft.1,
bottomleft.0,
bottomleft.1,
bottomright.0,
bottomright.1,
topright.0,
topright.1,
]);
tex.opacbuffer.push([
sprite.opacity[0],
sprite.opacity[1],
sprite.opacity[2],
sprite.opacity[3],
]);
tex.tranbuffer.push([
sprite.translation.0,
sprite.translation.1,
sprite.translation.0,
sprite.translation.1,
sprite.translation.0,
sprite.translation.1,
sprite.translation.0,
sprite.translation.1,
]);
tex.rotbuffer.push([
sprite.rotation,
sprite.rotation,
sprite.rotation,
sprite.rotation,
]);
tex.scalebuffer
.push([sprite.scale, sprite.scale, sprite.scale, sprite.scale]);
tex.uvbuffer.push([
topleft_uv.0,
topleft_uv.1,
bottomleft_uv.0,
bottomleft_uv.1,
bottomright_uv.0,
bottomright_uv.1,
topright_uv.0,
topright_uv.1,
]);
tex.posbuffer.len() - 1
};
let tex = &mut self.vx.dyntexs[layer.0];
tex.posbuf_touch = self.vx.swapconfig.image_count;
tex.opacbuf_touch = self.vx.swapconfig.image_count;
tex.uvbuf_touch = self.vx.swapconfig.image_count;
tex.tranbuf_touch = self.vx.swapconfig.image_count;
tex.rotbuf_touch = self.vx.swapconfig.image_count;
tex.scalebuf_touch = self.vx.swapconfig.image_count;
Handle(layer.0, handle)
}
pub fn remove(&mut self, handle: Handle) {
self.vx.dyntexs[handle.0].scalebuf_touch = self.vx.swapconfig.image_count;
if let Some(dyntex) = self.vx.dyntexs.get_mut(handle.0) {
dyntex.removed.push(handle.1);
dyntex.scalebuffer[handle.1].copy_from_slice(&[0.0, 0.0, 0.0, 0.0]);
}
}
pub fn set_deform(&mut self, handle: &Handle, points: [(f32, f32); 4]) {
self.vx.dyntexs[handle.0].posbuf_touch = self.vx.swapconfig.image_count;
let vertex = &mut self.vx.dyntexs[handle.0].posbuffer[handle.1];
for (idx, point) in points.iter().enumerate() {
vertex[idx * 2] = point.0;
vertex[idx * 2 + 1] = point.1;
}
}
pub fn set_solid_color(&mut self, handle: &Handle, rgba: Color) {
self.vx.dyntexs[handle.0].opacbuf_touch = self.vx.swapconfig.image_count;
for idx in 0..4 {
let Color::Rgba(r, g, b, a) = rgba;
self.vx.dyntexs[handle.0].opacbuffer[handle.1][idx * 4..(idx + 1) * 4]
.copy_from_slice(&[r, g, b, a]);
}
}
pub fn set_opacity(&mut self, handle: &Handle, opacity: u8) {
self.vx.dyntexs[handle.0].opacbuf_touch = self.vx.swapconfig.image_count;
self.vx.dyntexs[handle.0].opacbuffer[handle.1].copy_from_slice(&[opacity; 4]);
}
pub fn set_opacity_raw(&mut self, handle: &Handle, opacity: [u8; 4]) {
self.vx.dyntexs[handle.0].opacbuf_touch = self.vx.swapconfig.image_count;
self.vx.dyntexs[handle.0].opacbuffer[handle.1].copy_from_slice(&opacity);
}
pub fn set_translation(&mut self, handle: &Handle, position: (f32, f32)) {
self.vx.dyntexs[handle.0].tranbuf_touch = self.vx.swapconfig.image_count;
for idx in 0..4 {
self.vx.dyntexs[handle.0].tranbuffer[handle.1][idx * 2] = position.0;
self.vx.dyntexs[handle.0].tranbuffer[handle.1][idx * 2 + 1] = position.1;
}
}
pub fn set_rotation<T: Copy + Into<Rad<f32>>>(&mut self, handle: &Handle, angle: T) {
let angle = angle.into().0;
self.vx.dyntexs[handle.0].rotbuf_touch = self.vx.swapconfig.image_count;
self.vx.dyntexs[handle.0].rotbuffer[handle.1]
.copy_from_slice(&[angle, angle, angle, angle]);
}
pub fn set_scale(&mut self, handle: &Handle, scale: f32) {
self.vx.dyntexs[handle.0].scalebuf_touch = self.vx.swapconfig.image_count;
for sc in &mut self.vx.dyntexs[handle.0].scalebuffer[handle.1] {
*sc = scale;
}
}
pub fn set_uv(&mut self, handle: &Handle, uv_begin: (f32, f32), uv_end: (f32, f32)) {
self.vx.dyntexs[handle.0].uvbuf_touch = self.vx.swapconfig.image_count;
self.vx.dyntexs[handle.0].uvbuffer[handle.1].copy_from_slice(&[
uv_begin.0, uv_begin.1, uv_begin.0, uv_end.1, uv_end.0, uv_end.1, uv_end.0, uv_begin.1,
]);
}
pub fn set_uv_raw(&mut self, handle: &Handle, uvs: [(f32, f32); 4]) {
self.vx.dyntexs[handle.0].uvbuf_touch = self.vx.swapconfig.image_count;
self.vx.dyntexs[handle.0].uvbuffer[handle.1].copy_from_slice(&[
uvs[0].0, uvs[0].1, uvs[1].0, uvs[1].1, uvs[2].0, uvs[2].1, uvs[3].0, uvs[3].1,
]);
}
pub fn deform(&mut self, handle: &Handle, delta: [(f32, f32); 4]) {
self.vx.dyntexs[handle.0].posbuf_touch = self.vx.swapconfig.image_count;
let points = &mut self.vx.dyntexs[handle.0].posbuffer[handle.1];
points[0] += delta[0].0;
points[1] += delta[0].1;
points[2] += delta[1].0;
points[3] += delta[1].1;
points[4] += delta[2].0;
points[5] += delta[2].1;
points[6] += delta[3].0;
points[7] += delta[3].1;
}
pub fn translate(&mut self, handle: &Handle, movement: (f32, f32)) {
self.vx.dyntexs[handle.0].tranbuf_touch = self.vx.swapconfig.image_count;
for idx in 0..4 {
self.vx.dyntexs[handle.0].tranbuffer[handle.1][idx * 2] += movement.0;
self.vx.dyntexs[handle.0].tranbuffer[handle.1][idx * 2 + 1] += movement.1;
}
}
pub fn rotate<T: Copy + Into<Rad<f32>>>(&mut self, handle: &Handle, angle: T) {
self.vx.dyntexs[handle.0].rotbuf_touch = self.vx.swapconfig.image_count;
for rot in &mut self.vx.dyntexs[handle.0].rotbuffer[handle.1] {
*rot += angle.into().0;
}
}
pub fn scale(&mut self, handle: &Handle, scale: f32) {
self.vx.dyntexs[handle.0].scalebuf_touch = self.vx.swapconfig.image_count;
for sc in &mut self.vx.dyntexs[handle.0].scalebuffer[handle.1] {
*sc *= scale;
}
}
pub fn deform_all(&mut self, layer: &Layer, mut delta: impl FnMut(usize) -> [(f32, f32); 4]) {
self.vx.dyntexs[layer.0].posbuf_touch = self.vx.swapconfig.image_count;
for (idx, quad) in self.vx.dyntexs[layer.0].posbuffer.iter_mut().enumerate() {
let delta = delta(idx);
quad[0] += delta[0].0;
quad[1] += delta[0].1;
quad[2] += delta[1].0;
quad[3] += delta[1].1;
quad[4] += delta[2].0;
quad[5] += delta[2].1;
quad[6] += delta[3].0;
quad[7] += delta[3].1;
}
}
pub fn translate_all(&mut self, layer: &Layer, mut delta: impl FnMut(usize) -> (f32, f32)) {
self.vx.dyntexs[layer.0].tranbuf_touch = self.vx.swapconfig.image_count;
for (idx, quad) in self.vx.dyntexs[layer.0].tranbuffer.iter_mut().enumerate() {
let delta = delta(idx);
for idx in 0..4 {
quad[idx * 2] += delta.0;
quad[idx * 2 + 1] += delta.1;
}
}
}
pub fn rotate_all<T: Copy + Into<Rad<f32>>>(
&mut self,
layer: &Layer,
mut delta: impl FnMut(usize) -> T,
) {
self.vx.dyntexs[layer.0].rotbuf_touch = self.vx.swapconfig.image_count;
for (idx, quad) in self.vx.dyntexs[layer.0].rotbuffer.iter_mut().enumerate() {
let delta = delta(idx).into().0;
for rotation in quad.iter_mut() {
*rotation += delta;
}
}
}
pub fn scale_all(&mut self, layer: &Layer, mut delta: impl FnMut(usize) -> f32) {
self.vx.dyntexs[layer.0].scalebuf_touch = self.vx.swapconfig.image_count;
for (idx, quad) in self.vx.dyntexs[layer.0].scalebuffer.iter_mut().enumerate() {
let delta = delta(idx);
for scale in quad.iter_mut() {
*scale *= delta;
}
}
}
pub fn set_deform_all(
&mut self,
layer: &Layer,
mut delta: impl FnMut(usize) -> [(f32, f32); 4],
) {
self.vx.dyntexs[layer.0].posbuf_touch = self.vx.swapconfig.image_count;
for (idx, quad) in self.vx.dyntexs[layer.0].posbuffer.iter_mut().enumerate() {
let delta = delta(idx);
quad[0] = delta[0].0;
quad[1] = delta[0].1;
quad[2] = delta[1].0;
quad[3] = delta[1].1;
quad[4] = delta[2].0;
quad[5] = delta[2].1;
quad[6] = delta[3].0;
quad[7] = delta[3].1;
}
}
pub fn set_solid_color_all(&mut self, layer: &Layer, mut delta: impl FnMut(usize) -> Color) {
self.vx.dyntexs[layer.0].opacbuf_touch = self.vx.swapconfig.image_count;
for (idx, dyntex) in self.vx.dyntexs[layer.0].opacbuffer.iter_mut().enumerate() {
let delta = delta(idx);
for idx in 0..4 {
let Color::Rgba(r, g, b, a) = delta;
dyntex[idx * 4..(idx + 1) * 4].copy_from_slice(&[r, g, b, a]);
}
}
}
pub fn set_color_all(&mut self, layer: &Layer, mut delta: impl FnMut(usize) -> [Color; 4]) {
self.vx.dyntexs[layer.0].opacbuf_touch = self.vx.swapconfig.image_count;
for (idx, dyntex) in self.vx.dyntexs[layer.0].opacbuffer.iter_mut().enumerate() {
let delta = delta(idx);
for (idx, dt) in delta.iter().enumerate() {
let Color::Rgba(r, g, b, a) = dt;
dyntex[idx * 4..(idx + 1) * 4].copy_from_slice(&[*r, *g, *b, *a]);
}
}
}
pub fn set_translation_all(
&mut self,
layer: &Layer,
mut delta: impl FnMut(usize) -> (f32, f32),
) {
self.vx.dyntexs[layer.0].tranbuf_touch = self.vx.swapconfig.image_count;
for (idx, quad) in self.vx.dyntexs[layer.0].tranbuffer.iter_mut().enumerate() {
let delta = delta(idx);
for idx in 0..4 {
quad[idx * 2] = delta.0;
quad[idx * 2 + 1] = delta.1;
}
}
}
pub fn set_uv_all(&mut self, layer: &Layer, mut delta: impl FnMut(usize) -> [(f32, f32); 2]) {
self.vx.dyntexs[layer.0].uvbuf_touch = self.vx.swapconfig.image_count;
for (idx, quad) in self.vx.dyntexs[layer.0].uvbuffer.iter_mut().enumerate() {
let delta = delta(idx);
let uv_begin = delta[0];
let uv_end = delta[1];
quad.copy_from_slice(&[
uv_begin.0, uv_begin.1, uv_begin.0, uv_end.1, uv_end.0, uv_end.1, uv_end.0,
uv_begin.1,
]);
}
}
pub fn set_rotation_all<T: Copy + Into<Rad<f32>>>(
&mut self,
layer: &Layer,
mut delta: impl FnMut(usize) -> T,
) {
self.vx.dyntexs[layer.0].rotbuf_touch = self.vx.swapconfig.image_count;
for (idx, quad) in self.vx.dyntexs[layer.0].rotbuffer.iter_mut().enumerate() {
let delta = delta(idx).into().0;
quad.copy_from_slice(&[delta; 4]);
}
}
pub fn set_scale_all(&mut self, layer: &Layer, mut delta: impl FnMut(usize) -> f32) {
self.vx.dyntexs[layer.0].scalebuf_touch = self.vx.swapconfig.image_count;
for (idx, quad) in self.vx.dyntexs[layer.0].scalebuffer.iter_mut().enumerate() {
let delta = delta(idx);
quad.copy_from_slice(&[delta; 4]);
}
}
pub fn set_uvs<'b>(
&mut self,
mut uvs: impl Iterator<Item = (&'b Handle, (f32, f32), (f32, f32))>,
) {
if let Some(first) = uvs.next() {
let current_texture_handle = first.0;
self.set_uv(current_texture_handle, first.1, first.2);
for handle in uvs {
self.set_uv(handle.0, handle.1, handle.2);
}
}
}
}
fn destroy_texture(s: &mut VxDraw, mut dyntex: DynamicTexture) {
unsafe {
for mut indices in dyntex.indices.drain(..) {
indices.destroy(&s.device);
}
for mut posbuf in dyntex.posbuf.drain(..) {
posbuf.destroy(&s.device);
}
for mut opacbuf in dyntex.opacbuf.drain(..) {
opacbuf.destroy(&s.device);
}
for mut uvbuf in dyntex.uvbuf.drain(..) {
uvbuf.destroy(&s.device);
}
for mut tranbuf in dyntex.tranbuf.drain(..) {
tranbuf.destroy(&s.device);
}
for mut rotbuf in dyntex.rotbuf.drain(..) {
rotbuf.destroy(&s.device);
}
for mut scalebuf in dyntex.scalebuf.drain(..) {
scalebuf.destroy(&s.device);
}
s.device
.destroy_image(ManuallyDrop::into_inner(read(&dyntex.texture_image_buffer)));
s.device
.free_memory(ManuallyDrop::into_inner(read(&dyntex.texture_image_memory)));
s.device
.destroy_render_pass(ManuallyDrop::into_inner(read(&dyntex.render_pass)));
s.device
.destroy_pipeline_layout(ManuallyDrop::into_inner(read(&dyntex.pipeline_layout)));
s.device
.destroy_graphics_pipeline(ManuallyDrop::into_inner(read(&dyntex.pipeline)));
for dsl in dyntex.descriptor_set_layouts.drain(..) {
s.device.destroy_descriptor_set_layout(dsl);
}
s.device
.destroy_descriptor_pool(ManuallyDrop::into_inner(read(&dyntex.descriptor_pool)));
s.device
.destroy_sampler(ManuallyDrop::into_inner(read(&dyntex.sampler)));
s.device
.destroy_image_view(ManuallyDrop::into_inner(read(&dyntex.image_view)));
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::*;
use cgmath::Deg;
use fast_logger::{Generic, GenericLogger, Logger};
use rand::Rng;
use rand_pcg::Pcg64Mcg as random;
use std::f32::consts::PI;
use test::Bencher;
static LOGO: &ImgData = &ImgData::PNGBytes(include_bytes!["../images/logo.png"]);
static FOREST: &ImgData = &ImgData::PNGBytes(include_bytes!["../images/forest-light.png"]);
static TESTURE: &ImgData = &ImgData::PNGBytes(include_bytes!["../images/testure.png"]);
static TREE: &ImgData = &ImgData::PNGBytes(include_bytes!["../images/treetest.png"]);
static FIREBALL: &ImgData = &ImgData::PNGBytes(include_bytes!["../images/Fireball_68x9.png"]);
#[test]
fn simple_texture() {
let logger = Logger::<Generic>::spawn_void().to_compatibility();
let mut vx = VxDraw::new(logger, ShowWindow::Headless1k);
let mut dyntex = vx.dyntex();
let tex = dyntex.add_layer(LOGO, &LayerOptions::new());
vx.dyntex().add(&tex, Sprite::new());
let img = vx.draw_frame_copy_framebuffer();
utils::assert_swapchain_eq(&mut vx, "simple_texture", img);
}
#[test]
fn simple_texture_adheres_to_view() {
let logger = Logger::<Generic>::spawn_void().to_compatibility();
let mut vx = VxDraw::new(logger, ShowWindow::Headless2x1k);
let tex = vx.dyntex().add_layer(LOGO, &LayerOptions::new());
vx.dyntex().add(&tex, Sprite::new());
vx.set_perspective(vx.perspective_projection());
let img = vx.draw_frame_copy_framebuffer();
utils::assert_swapchain_eq(&mut vx, "simple_texture_adheres_to_view", img);
}
#[test]
fn colored_simple_texture1() {
let logger = Logger::<Generic>::spawn_void().to_compatibility();
let mut vx = VxDraw::new(logger, ShowWindow::Headless1k);
let tex = vx.dyntex().add_layer(LOGO, &LayerOptions::new());
vx.dyntex().add(&tex, Sprite::new().opacity(100));
let img = vx.draw_frame_copy_framebuffer();
utils::assert_swapchain_eq(&mut vx, "colored_simple_texture", img);
}
#[test]
fn colored_simple_texture_set_position() {
let logger = Logger::<Generic>::spawn_void().to_compatibility();
let mut vx = VxDraw::new(logger, ShowWindow::Headless1k);
let mut dyntex = vx.dyntex();
let tex = dyntex.add_layer(LOGO, &LayerOptions::new());
let sprite = dyntex.add(&tex, Sprite::new().opacity(100));
dyntex.set_translation(&sprite, (0.5, 0.3));
let img = vx.draw_frame_copy_framebuffer();
utils::assert_swapchain_eq(&mut vx, "colored_simple_texture_set_position", img);
}
#[test]
fn translated_texture() {
let logger = Logger::<Generic>::spawn_void().to_compatibility();
let mut vx = VxDraw::new(logger, ShowWindow::Headless1k);
let tex = vx.dyntex().add_layer(
LOGO,
&LayerOptions {
depth_test: false,
..LayerOptions::new()
},
);
let base = Sprite {
width: 1.0,
height: 1.0,
..Sprite::new()
};
let mut dyntex = vx.dyntex();
dyntex.add(
&tex,
Sprite {
translation: (-0.5, -0.5),
rotation: 0.0,
..base
},
);
dyntex.add(
&tex,
Sprite {
translation: (0.5, -0.5),
rotation: PI / 4.0,
..base
},
);
dyntex.add(
&tex,
Sprite {
translation: (-0.5, 0.5),
rotation: PI / 2.0,
..base
},
);
dyntex.add(
&tex,
Sprite {
translation: (0.5, 0.5),
rotation: PI,
..base
},
);
dyntex.translate_all(&tex, |_| (0.25, 0.35));
let img = vx.draw_frame_copy_framebuffer();
utils::assert_swapchain_eq(&mut vx, "translated_texture", img);
}
#[test]
fn rotated_texture() {
let logger = Logger::<Generic>::spawn_void().to_compatibility();
let mut vx = VxDraw::new(logger, ShowWindow::Headless1k);
let mut dyntex = vx.dyntex();
let tex = dyntex.add_layer(
LOGO,
&LayerOptions {
depth_test: false,
..LayerOptions::new()
},
);
let base = Sprite {
width: 1.0,
height: 1.0,
..Sprite::new()
};
dyntex.add(
&tex,
Sprite {
translation: (-0.5, -0.5),
rotation: 0.0,
..base
},
);
dyntex.add(
&tex,
Sprite {
translation: (0.5, -0.5),
rotation: PI / 4.0,
..base
},
);
dyntex.add(
&tex,
Sprite {
translation: (-0.5, 0.5),
rotation: PI / 2.0,
..base
},
);
dyntex.add(
&tex,
Sprite {
translation: (0.5, 0.5),
rotation: PI,
..base
},
);
dyntex.rotate_all(&tex, |_| Deg(90.0));
let img = vx.draw_frame_copy_framebuffer();
utils::assert_swapchain_eq(&mut vx, "rotated_texture", img);
}
#[test]
fn many_sprites() {
let logger = Logger::<Generic>::spawn_void().to_compatibility();
let mut vx = VxDraw::new(logger, ShowWindow::Headless1k);
let tex = vx.dyntex().add_layer(
LOGO,
&LayerOptions {
depth_test: false,
..LayerOptions::new()
},
);
for i in 0..360 {
vx.dyntex().add(
&tex,
Sprite {
rotation: ((i * 10) as f32 / 180f32 * PI),
scale: 0.5,
..Sprite::new()
},
);
}
let img = vx.draw_frame_copy_framebuffer();
utils::assert_swapchain_eq(&mut vx, "many_sprites", img);
}
#[test]
fn three_layer_scene() {
let logger = Logger::<Generic>::spawn_void().to_compatibility();
let mut vx = VxDraw::new(logger, ShowWindow::Headless1k);
let options = &LayerOptions {
depth_test: false,
..LayerOptions::new()
};
let mut dyntex = vx.dyntex();
let forest = dyntex.add_layer(FOREST, options);
let player = dyntex.add_layer(LOGO, options);
let tree = dyntex.add_layer(TREE, options);
vx.dyntex().add(&forest, Sprite::new());
vx.dyntex().add(
&player,
Sprite {
scale: 0.4,
..Sprite::new()
},
);
vx.dyntex().add(
&tree,
Sprite {
translation: (-0.3, 0.0),
scale: 0.4,
..Sprite::new()
},
);
let img = vx.draw_frame_copy_framebuffer();
utils::assert_swapchain_eq(&mut vx, "three_layer_scene", img);
}
#[test]
fn three_layer_scene_remove_middle() {
let logger = Logger::<Generic>::spawn_void().to_compatibility();
let mut vx = VxDraw::new(logger, ShowWindow::Headless1k);
let options = &LayerOptions {
depth_test: false,
..LayerOptions::new()
};
let mut dyntex = vx.dyntex();
let forest = dyntex.add_layer(FOREST, options);
let player = dyntex.add_layer(LOGO, options);
let tree = dyntex.add_layer(TREE, options);
dyntex.add(&forest, Sprite::new());
let middle = dyntex.add(
&player,
Sprite {
scale: 0.4,
..Sprite::new()
},
);
dyntex.add(
&tree,
Sprite {
translation: (-0.3, 0.0),
scale: 0.4,
..Sprite::new()
},
);
dyntex.remove(middle);
let img = vx.draw_frame_copy_framebuffer();
utils::assert_swapchain_eq(&mut vx, "three_layer_scene_remove_middle", img);
}
#[test]
fn three_layer_scene_remove_middle_texture() {
let logger = Logger::<Generic>::spawn_void().to_compatibility();
let mut vx = VxDraw::new(logger, ShowWindow::Headless1k);
let options = &LayerOptions {
depth_test: false,
..LayerOptions::new()
};
let mut dyntex = vx.dyntex();
let forest = dyntex.add_layer(FOREST, options);
let player = dyntex.add_layer(LOGO, options);
let tree = dyntex.add_layer(TREE, options);
dyntex.add(&forest, Sprite::new());
dyntex.add(
&player,
Sprite {
scale: 0.4,
..Sprite::new()
},
);
dyntex.add(
&tree,
Sprite {
translation: (-0.3, 0.0),
scale: 0.4,
..Sprite::new()
},
);
dyntex.remove_layer(player);
let img = vx.draw_frame_copy_framebuffer();
utils::assert_swapchain_eq(&mut vx, "three_layer_scene_remove_middle_texture", img);
vx.dyntex().remove_layer(tree);
vx.draw_frame();
}
#[test]
fn rapidly_add_remove_layer() {
let logger = Logger::<Generic>::spawn_void().to_compatibility();
let mut vx = VxDraw::new(logger, ShowWindow::Headless1k);
let options = &LayerOptions::new();
for _ in 0..10 {
let mut dyntex = vx.dyntex();
let layer = dyntex.add_layer(TESTURE, options);
dyntex.add(&layer, Sprite::new());
vx.draw_frame();
vx.dyntex().remove_layer(layer);
assert![vx.swapconfig.image_count + 1 >= vx.dyntex().layer_count() as u32];
assert![0 < vx.dyntex().layer_count()];
}
}
#[test]
fn three_layer_scene_remove_last_texture() {
let logger = Logger::<Generic>::spawn_void().to_compatibility();
let mut vx = VxDraw::new(logger, ShowWindow::Headless1k);
let options = &LayerOptions {
depth_test: false,
..LayerOptions::new()
};
let mut dyntex = vx.dyntex();
let forest = dyntex.add_layer(FOREST, options);
let player = dyntex.add_layer(LOGO, options);
let tree = dyntex.add_layer(TREE, options);
dyntex.add(&forest, Sprite::new());
dyntex.add(
&player,
Sprite {
scale: 0.4,
..Sprite::new()
},
);
dyntex.add(
&tree,
Sprite {
translation: (-0.3, 0.0),
scale: 0.4,
..Sprite::new()
},
);
dyntex.remove_layer(tree);
let img = vx.draw_frame_copy_framebuffer();
utils::assert_swapchain_eq(&mut vx, "three_layer_scene_remove_last_texture", img);
vx.dyntex().remove_layer(player);
vx.draw_frame();
}
#[test]
fn fixed_perspective() {
let logger = Logger::<Generic>::spawn_void().to_compatibility();
let mut vx = VxDraw::new(logger, ShowWindow::Headless2x1k);
let options = &LayerOptions {
depth_test: false,
fixed_perspective: Some(Matrix4::identity()),
..LayerOptions::new()
};
let forest = vx.dyntex().add_layer(FOREST, options);
vx.dyntex().add(&forest, Sprite::new());
let img = vx.draw_frame_copy_framebuffer();
utils::assert_swapchain_eq(&mut vx, "fixed_perspective", img);
}
#[test]
fn change_of_uv_works_for_first() {
let logger = Logger::<Generic>::spawn_void().to_compatibility();
let mut vx = VxDraw::new(logger, ShowWindow::Headless1k);
let mut dyntex = vx.dyntex();
let options = &LayerOptions::new();
let testure = dyntex.add_layer(TESTURE, options);
let sprite = dyntex.add(&testure, Sprite::new());
dyntex.set_uvs(std::iter::once((
&sprite,
(1.0 / 3.0, 0.0),
(2.0 / 3.0, 1.0),
)));
let img = vx.draw_frame_copy_framebuffer();
utils::assert_swapchain_eq(&mut vx, "change_of_uv_works_for_first", img);
vx.dyntex()
.set_uv(&sprite, (1.0 / 3.0, 0.0), (2.0 / 3.0, 1.0));
let img = vx.draw_frame_copy_framebuffer();
utils::assert_swapchain_eq(&mut vx, "change_of_uv_works_for_first", img);
}
#[test]
fn bunch_of_different_opacity_sprites() {
let logger = Logger::<Generic>::spawn_void().to_compatibility();
let mut vx = VxDraw::new(logger, ShowWindow::Headless1k);
let mut dyntex = vx.dyntex();
let options = &LayerOptions::new();
let testure = dyntex.add_layer(LOGO, options);
for idx in 0..10 {
dyntex.add(
&testure,
Sprite::new()
.translation((idx as f32 / 5.0 - 1.0, 0.0))
.opacity((255.0 * idx as f32 / 20.0) as u8),
);
}
let img = vx.draw_frame_copy_framebuffer();
utils::assert_swapchain_eq(&mut vx, "bunch_of_different_opacity_sprites", img);
}
#[test]
fn set_single_sprite_rotation() {
let logger = Logger::<Generic>::spawn_void().to_compatibility();
let mut vx = VxDraw::new(logger, ShowWindow::Headless1k);
let mut dyntex = vx.dyntex();
let options = &LayerOptions::new();
let testure = dyntex.add_layer(TESTURE, options);
let sprite = dyntex.add(&testure, Sprite::new());
dyntex.set_rotation(&sprite, Rad(0.3));
let img = vx.draw_frame_copy_framebuffer();
utils::assert_swapchain_eq(&mut vx, "set_single_sprite_rotation", img);
}
#[test]
fn linear_filtering_mode() {
let logger = Logger::<Generic>::spawn_void().to_compatibility();
let mut vx = VxDraw::new(logger, ShowWindow::Headless1k);
let mut dyntex = vx.dyntex();
let options = &LayerOptions::new().filter(Filter::Linear);
let testure = dyntex.add_layer(TESTURE, options);
let sprite = dyntex.add(&testure, Sprite::new());
dyntex.set_rotation(&sprite, Rad(0.3));
let img = vx.draw_frame_copy_framebuffer();
utils::assert_swapchain_eq(&mut vx, "linear_filtering_mode", img);
}
#[test]
fn raw_uvs() {
let logger = Logger::<Generic>::spawn_void().to_compatibility();
let mut vx = VxDraw::new(logger, ShowWindow::Headless1k);
let mut dyntex = vx.dyntex();
let options = &LayerOptions::new();
let testure = dyntex.add_layer(TESTURE, options);
let sprite = dyntex.add(&testure, Sprite::new());
dyntex.set_uv_raw(&sprite, [(0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (0.0, 0.0)]);
let img = vx.draw_frame_copy_framebuffer();
utils::assert_swapchain_eq(&mut vx, "raw_uvs", img);
}
#[test]
fn wrap_mode_clamp() {
let logger = Logger::<Generic>::spawn_void().to_compatibility();
let mut vx = VxDraw::new(logger, ShowWindow::Headless1k);
let mut dyntex = vx.dyntex();
let options = &LayerOptions::new().wrap_mode(WrapMode::Clamp);
let testure = dyntex.add_layer(TESTURE, options);
let sprite = dyntex.add(&testure, Sprite::new());
dyntex.set_uv_raw(&sprite, [(-0.5, 0.0), (-0.5, 1.0), (1.0, 1.0), (1.0, 0.0)]);
let img = vx.draw_frame_copy_framebuffer();
utils::assert_swapchain_eq(&mut vx, "wrap_mode_clamp", img);
}
#[test]
fn wrap_mode_mirror() {
let logger = Logger::<Generic>::spawn_void().to_compatibility();
let mut vx = VxDraw::new(logger, ShowWindow::Headless1k);
let mut dyntex = vx.dyntex();
let options = &LayerOptions::new().wrap_mode(WrapMode::Mirror);
let testure = dyntex.add_layer(TESTURE, options);
let sprite = dyntex.add(&testure, Sprite::new());
dyntex.set_uv_raw(&sprite, [(-1.0, 0.0), (-1.0, 1.0), (1.0, 1.0), (1.0, 0.0)]);
let img = vx.draw_frame_copy_framebuffer();
utils::assert_swapchain_eq(&mut vx, "wrap_mode_mirror", img);
}
#[test]
fn push_and_pop_often_avoid_allocating_out_of_bounds() {
let logger = Logger::<Generic>::spawn_void().to_compatibility();
let mut vx = VxDraw::new(logger, ShowWindow::Headless1k);
let options = &LayerOptions::new();
let testure = vx.dyntex().add_layer(TESTURE, options);
let mut dyntex = vx.dyntex();
for _ in 0..100_000 {
let sprite = dyntex.add(&testure, Sprite::new());
dyntex.remove(sprite);
}
vx.draw_frame();
}
#[test]
fn too_little_data_in_texture_wraps() {
let logger = Logger::<Generic>::spawn_void().to_compatibility();
let mut vx = VxDraw::new(logger, ShowWindow::Headless1k);
let options = &LayerOptions::new();
#[rustfmt::skip]
let tex = ImgData::RawBytes {
width: 7,
height: 8,
bytes: &[
255, 0, 0, 255,
0, 255, 0, 255,
0, 0, 255, 255,
255, 0, 255, 255,
0, 0, 0, 255,
],
};
let testure = vx.dyntex().add_layer(&tex, options);
vx.dyntex().add(&testure, Sprite::new());
let img = vx.draw_frame_copy_framebuffer();
utils::assert_swapchain_eq(&mut vx, "too_little_data_in_texture_wraps", img);
}
#[bench]
fn bench_many_sprites(b: &mut Bencher) {
let logger = Logger::<Generic>::spawn_void().to_compatibility();
let mut vx = VxDraw::new(logger, ShowWindow::Headless1k);
let tex = vx.dyntex().add_layer(LOGO, &LayerOptions::new());
for i in 0..1000 {
vx.dyntex().add(
&tex,
Sprite {
rotation: ((i * 10) as f32 / 180f32 * PI),
scale: 0.5,
..Sprite::new()
},
);
}
b.iter(|| {
vx.draw_frame();
});
}
#[bench]
fn bench_many_particles(b: &mut Bencher) {
let logger = Logger::<Generic>::spawn_void().to_compatibility();
let mut vx = VxDraw::new(logger, ShowWindow::Headless1k);
let tex = vx.dyntex().add_layer(LOGO, &LayerOptions::new());
let mut rng = random::new(0);
for i in 0..1000 {
let (dx, dy) = (
rng.gen_range(-1.0f32, 1.0f32),
rng.gen_range(-1.0f32, 1.0f32),
);
vx.dyntex().add(
&tex,
Sprite {
translation: (dx, dy),
rotation: ((i * 10) as f32 / 180f32 * PI),
scale: 0.01,
..Sprite::new()
},
);
}
b.iter(|| {
vx.draw_frame();
});
}
#[bench]
fn animated_fireballs_20x20_uvs2(b: &mut Bencher) {
let logger = Logger::<Generic>::spawn_void().to_compatibility();
let mut vx = VxDraw::new(logger, ShowWindow::Headless1k);
let fireball_texture = vx.dyntex().add_layer(
FIREBALL,
&LayerOptions {
depth_test: false,
..LayerOptions::new()
},
);
let mut fireballs = vec![];
for idx in -10..10 {
for jdx in -10..10 {
fireballs.push(vx.dyntex().add(
&fireball_texture,
Sprite {
width: 0.68,
height: 0.09,
rotation: idx as f32 / 18.0 + jdx as f32 / 16.0,
translation: (idx as f32 / 10.0, jdx as f32 / 10.0),
..Sprite::new()
},
));
}
}
let width_elems = 10;
let height_elems = 6;
let mut counter = 0;
b.iter(|| {
let width_elem = counter % width_elems;
let height_elem = counter / width_elems;
let uv_begin = (
width_elem as f32 / width_elems as f32,
height_elem as f32 / height_elems as f32,
);
let uv_end = (
(width_elem + 1) as f32 / width_elems as f32,
(height_elem + 1) as f32 / height_elems as f32,
);
counter += 1;
if counter > width_elems * height_elems {
counter = 0;
}
vx.dyntex()
.set_uvs(fireballs.iter().map(|id| (id, uv_begin, uv_end)));
vx.draw_frame();
});
}
#[bench]
fn bench_push_and_pop_sprite(b: &mut Bencher) {
let logger = Logger::<Generic>::spawn_void().to_compatibility();
let mut vx = VxDraw::new(logger, ShowWindow::Headless1k);
let options = &LayerOptions::new();
let testure = vx.dyntex().add_layer(TESTURE, options);
let mut dyntex = vx.dyntex();
b.iter(|| {
let sprite = dyntex.add(&testure, Sprite::new());
dyntex.remove(sprite);
});
}
#[bench]
fn bench_push_and_pop_texture(b: &mut Bencher) {
let logger = Logger::<Generic>::spawn_void().to_compatibility();
let mut vx = VxDraw::new(logger, ShowWindow::Headless1k);
let mut dyntex = vx.dyntex();
b.iter(|| {
let options = &LayerOptions::new();
let testure = dyntex.add_layer(TESTURE, options);
dyntex.remove_layer(testure);
});
}
}