use bytemuck::{cast_slice, Pod, Zeroable};
use erupt::vk;
use glam::{Mat4, Vec3, Vec4};

use asche::{CommandBufferSemaphore, QueueConfiguration, Queues};

#[repr(C)]
#[derive(Clone, Copy)]
struct Vertex {
    position: [f32; 4],
    tex_coord: [f32; 2],
}

unsafe impl Pod for Vertex {}

unsafe impl Zeroable for Vertex {}

fn main() -> Result<(), asche::AscheError> {
    let event_loop = winit::event_loop::EventLoop::new();
    let window = winit::window::WindowBuilder::new()
        .with_title("asche - cube example")
        .with_inner_size(winit::dpi::PhysicalSize::new(1920, 1080))
        .build(&event_loop)
        .unwrap();

    // Log level is based on RUST_LOG env var.
    #[cfg(feature = "tracing")]
    {
        let filter = tracing_subscriber::EnvFilter::from_default_env();
        tracing_subscriber::fmt().with_env_filter(filter).init();
    }

    let instance = asche::Instance::new(
        &window,
        asche::InstanceConfiguration {
            app_name: "cube example",
            app_version: erupt::vk::make_version(1, 0, 0),
            extensions: vec![],
        },
    )?;

    let (device, swapchain, queues) = instance.request_device(asche::DeviceConfiguration {
        queue_configuration: QueueConfiguration {
            compute_queues: vec![],
            graphics_queues: vec![1.0],
            transfer_queues: vec![1.0],
        },
        ..Default::default()
    })?;

    let Queues {
        compute_queues: _compute_queues,
        mut graphics_queues,
        mut transfer_queues,
    } = queues;

    let mut app = Application::new(
        device,
        swapchain,
        graphics_queues.pop().unwrap(),
        transfer_queues.pop().unwrap(),
        &window,
    )?;

    event_loop.run(move |event, _, control_flow| {
        *control_flow = winit::event_loop::ControlFlow::Poll;

        match event {
            winit::event::Event::WindowEvent {
                event:
                    winit::event::WindowEvent::KeyboardInput {
                        input:
                            winit::event::KeyboardInput {
                                state: winit::event::ElementState::Pressed,
                                virtual_keycode: Some(winit::event::VirtualKeyCode::Escape),
                                ..
                            },
                        ..
                    },
                ..
            } => *control_flow = winit::event_loop::ControlFlow::Exit,
            winit::event::Event::WindowEvent {
                event: winit::event::WindowEvent::CloseRequested,
                window_id,
            } if window_id == window.id() => *control_flow = winit::event_loop::ControlFlow::Exit,
            winit::event::Event::MainEventsCleared => {
                app.render().unwrap();
            }
            _ => (),
        }
    });
}

struct Application {
    frame_counter: u64,
    extent: vk::Extent2D,
    vp_matrix: Mat4,
    render_fence: asche::Fence,
    presentation_semaphore: asche::BinarySemaphore,
    render_semaphore: asche::BinarySemaphore,
    transfer_timeline: asche::TimelineSemaphore,
    transfer_timeline_value: u64,
    descriptor_set_layout: asche::DescriptorSetLayout,
    descriptor_pool: asche::DescriptorPool,
    graphics_command_pool: asche::GraphicsCommandPool,
    pipeline: asche::GraphicsPipeline,
    pipeline_layout: asche::PipelineLayout,
    render_pass: asche::RenderPass,
    vertex_buffer: Vec<asche::Buffer>,
    index_buffer: Vec<asche::Buffer>,
    sampler: asche::Sampler,
    depth_texture: Texture,
    textures: Vec<Texture>,
    graphics_queue: asche::GraphicsQueue,
    transfer_queue: asche::TransferQueue,
    swapchain: asche::Swapchain,
    device: asche::Device,
}

impl Application {
    fn new(
        device: asche::Device,
        swapchain: asche::Swapchain,
        mut graphics_queue: asche::GraphicsQueue,
        transfer_queue: asche::TransferQueue,
        window: &winit::window::Window,
    ) -> Result<Self, asche::AscheError> {
        let extent = vk::Extent2D {
            width: window.inner_size().width,
            height: window.inner_size().height,
        };

        // Shader
        let vert_module = device.create_shader_module(
            "Vertex Shader Module",
            include_bytes!("shader/cube.vert.spv"),
        )?;
        let frag_module = device.create_shader_module(
            "Fragment Shader Module",
            include_bytes!("shader/cube.frag.spv"),
        )?;

        let mainfunctionname = std::ffi::CString::new("main").unwrap();
        let vertexshader_stage = vk::PipelineShaderStageCreateInfoBuilder::new()
            .stage(vk::ShaderStageFlagBits::VERTEX)
            .module(vert_module.raw)
            .name(&mainfunctionname);
        let fragmentshader_stage = vk::PipelineShaderStageCreateInfoBuilder::new()
            .stage(vk::ShaderStageFlagBits::FRAGMENT)
            .module(frag_module.raw)
            .name(&mainfunctionname);

        // Depth image
        let depth_image = device.create_image(&asche::ImageDescriptor {
            name: "Depth Texture",
            usage: vk::ImageUsageFlags::DEPTH_STENCIL_ATTACHMENT,
            memory_location: vk_alloc::MemoryLocation::GpuOnly,
            sharing_mode: vk::SharingMode::EXCLUSIVE,
            queues: vk::QueueFlags::GRAPHICS,
            image_type: vk::ImageType::_2D,
            format: vk::Format::D32_SFLOAT,
            extent: vk::Extent3D {
                width: extent.width,
                height: extent.height,
                depth: 1,
            },
            mip_levels: 1,
            array_layers: 1,
            samples: vk::SampleCountFlagBits::_1,
            tiling: vk::ImageTiling::OPTIMAL,
            initial_layout: vk::ImageLayout::UNDEFINED,
            flags: None,
        })?;

        let depth_image_view = device.create_image_view(&asche::ImageViewDescriptor {
            name: "Depth Texture View",
            image: &depth_image,
            view_type: vk::ImageViewType::_2D,
            format: vk::Format::D32_SFLOAT,
            components: vk::ComponentMapping {
                r: vk::ComponentSwizzle::R,
                g: vk::ComponentSwizzle::G,
                b: vk::ComponentSwizzle::B,
                a: vk::ComponentSwizzle::A,
            },
            subresource_range: vk::ImageSubresourceRange {
                aspect_mask: vk::ImageAspectFlags::DEPTH,
                base_mip_level: 0,
                level_count: 1,
                base_array_layer: 0,
                layer_count: 1,
            },
            flags: None,
        })?;

        let depth_texture = Texture {
            view: depth_image_view,
            _image: depth_image,
        };

        // Sampler
        let sampler = device.create_sampler(&asche::SamplerDescriptor {
            name: "Cube Texture Sampler",
            ..Default::default()
        })?;

        // Renderpass
        let attachments = [
            // Color
            vk::AttachmentDescription2Builder::new()
                .format(vk::Format::B8G8R8A8_SRGB)
                .load_op(vk::AttachmentLoadOp::CLEAR)
                .store_op(vk::AttachmentStoreOp::STORE)
                .stencil_load_op(vk::AttachmentLoadOp::DONT_CARE)
                .stencil_store_op(vk::AttachmentStoreOp::DONT_CARE)
                .initial_layout(vk::ImageLayout::UNDEFINED)
                .final_layout(vk::ImageLayout::PRESENT_SRC_KHR)
                .samples(vk::SampleCountFlagBits::_1),
            // Depth
            vk::AttachmentDescription2Builder::new()
                .format(vk::Format::D32_SFLOAT)
                .load_op(vk::AttachmentLoadOp::CLEAR)
                .store_op(vk::AttachmentStoreOp::STORE)
                .stencil_load_op(vk::AttachmentLoadOp::DONT_CARE)
                .stencil_store_op(vk::AttachmentStoreOp::DONT_CARE)
                .initial_layout(vk::ImageLayout::UNDEFINED)
                .final_layout(vk::ImageLayout::DEPTH_STENCIL_ATTACHMENT_OPTIMAL)
                .samples(vk::SampleCountFlagBits::_1),
        ];

        let color_attachment_references = [vk::AttachmentReference2Builder::new()
            .attachment(0)
            .layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL)];

        let depth_attachment_references = vk::AttachmentReference2Builder::new()
            .attachment(1)
            .layout(vk::ImageLayout::DEPTH_STENCIL_ATTACHMENT_OPTIMAL);

        let subpasses = [vk::SubpassDescription2Builder::new()
            .color_attachments(&color_attachment_references)
            .depth_stencil_attachment(&depth_attachment_references)
            .pipeline_bind_point(vk::PipelineBindPoint::GRAPHICS)];

        let renderpass_info = vk::RenderPassCreateInfo2Builder::new()
            .attachments(&attachments)
            .subpasses(&subpasses);

        let render_pass =
            device.create_render_pass("Graphics Render Pass Simple", renderpass_info)?;

        // Descriptor set layout
        let bindings = [vk::DescriptorSetLayoutBindingBuilder::new()
            .binding(0)
            .descriptor_count(1) // Used fore texture arrays
            .descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER)
            .stage_flags(vk::ShaderStageFlags::FRAGMENT)];
        let layout_info = vk::DescriptorSetLayoutCreateInfoBuilder::new().bindings(&bindings);
        let descriptor_set_layout =
            device.create_descriptor_set_layout("Cube Descriptor Set Layout", layout_info)?;

        // Descriptor pool
        let pool_sizes = [vk::DescriptorPoolSizeBuilder::new()
            .descriptor_count(1)
            ._type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER)];

        let descriptor_pool = device.create_descriptor_pool(&asche::DescriptorPoolDescriptor {
            name: "Cube Descriptor Pool",
            max_sets: 16,
            pool_sizes: &pool_sizes,
            flags: None,
        })?;

        // Pipeline layout
        let push_constants_ranges = [vk::PushConstantRangeBuilder::new()
            .stage_flags(vk::ShaderStageFlags::VERTEX)
            .offset(0)
            .size(64)];

        let layouts = [descriptor_set_layout.raw];
        let pipeline_layout = vk::PipelineLayoutCreateInfoBuilder::new()
            .push_constant_ranges(&push_constants_ranges)
            .set_layouts(&layouts);
        let pipeline_layout =
            device.create_pipeline_layout("Pipeline Layout Simple", pipeline_layout)?;

        // Pipeline
        let vertex_binding_descriptions = [vk::VertexInputBindingDescriptionBuilder::new()
            .binding(0)
            .stride(std::mem::size_of::<Vertex>() as u32)
            .input_rate(vk::VertexInputRate::VERTEX)];

        let vertex_attribute_descriptions = [
            vk::VertexInputAttributeDescriptionBuilder::new()
                .binding(0)
                .location(0)
                .offset(0)
                .format(vk::Format::R32G32B32A32_SFLOAT),
            vk::VertexInputAttributeDescriptionBuilder::new()
                .binding(0)
                .location(1)
                .offset(16)
                .format(vk::Format::R32G32_SFLOAT),
        ];

        let shader_stages = vec![vertexshader_stage, fragmentshader_stage];
        let vertex_input_state = vk::PipelineVertexInputStateCreateInfoBuilder::new()
            .vertex_binding_descriptions(&vertex_binding_descriptions)
            .vertex_attribute_descriptions(&vertex_attribute_descriptions);
        let input_assembly_state = vk::PipelineInputAssemblyStateCreateInfoBuilder::new()
            .topology(vk::PrimitiveTopology::TRIANGLE_LIST);
        let dynamic_state = vk::PipelineDynamicStateCreateInfoBuilder::new()
            .dynamic_states(&[vk::DynamicState::VIEWPORT, vk::DynamicState::SCISSOR]);
        let viewport_state = vk::PipelineViewportStateCreateInfoBuilder::new()
            .scissor_count(1)
            .viewport_count(1);
        let rasterization_state = vk::PipelineRasterizationStateCreateInfoBuilder::new()
            .line_width(1.0)
            .front_face(vk::FrontFace::COUNTER_CLOCKWISE)
            .cull_mode(vk::CullModeFlags::BACK)
            .polygon_mode(vk::PolygonMode::FILL);
        let multisample_state = vk::PipelineMultisampleStateCreateInfoBuilder::new()
            .rasterization_samples(vk::SampleCountFlagBits::_1);
        let color_blend_attachments = [vk::PipelineColorBlendAttachmentStateBuilder::new()
            .blend_enable(true)
            .src_color_blend_factor(vk::BlendFactor::SRC_ALPHA)
            .dst_color_blend_factor(vk::BlendFactor::ONE_MINUS_SRC_ALPHA)
            .color_blend_op(vk::BlendOp::ADD)
            .src_alpha_blend_factor(vk::BlendFactor::SRC_ALPHA)
            .dst_alpha_blend_factor(vk::BlendFactor::ONE_MINUS_SRC_ALPHA)
            .alpha_blend_op(vk::BlendOp::ADD)
            .color_write_mask(
                vk::ColorComponentFlags::R
                    | vk::ColorComponentFlags::G
                    | vk::ColorComponentFlags::B
                    | vk::ColorComponentFlags::A,
            )];

        let color_blend_state = vk::PipelineColorBlendStateCreateInfoBuilder::new()
            .attachments(&color_blend_attachments);

        let depth_stencil_state = vk::PipelineDepthStencilStateCreateInfoBuilder::new()
            .depth_test_enable(true)
            .depth_write_enable(true)
            .depth_compare_op(vk::CompareOp::LESS_OR_EQUAL)
            .depth_bounds_test_enable(false)
            .stencil_test_enable(false)
            .min_depth_bounds(0.0)
            .max_depth_bounds(1.0);

        let pipeline_info = vk::GraphicsPipelineCreateInfoBuilder::new()
            .stages(&shader_stages)
            .vertex_input_state(&vertex_input_state)
            .input_assembly_state(&input_assembly_state)
            .dynamic_state(&dynamic_state)
            .viewport_state(&viewport_state)
            .rasterization_state(&rasterization_state)
            .multisample_state(&multisample_state)
            .color_blend_state(&color_blend_state)
            .depth_stencil_state(&depth_stencil_state)
            .layout(pipeline_layout.raw)
            .render_pass(render_pass.raw)
            .subpass(0);

        let pipeline = device.create_graphics_pipeline("Cube Pipeline", pipeline_info)?;

        let graphics_command_pool = graphics_queue.create_command_pool()?;

        let p_matrix = perspective_infinite_reverse_rh_yup(
            (70.0f32).to_radians(),
            extent.width as f32 / extent.height as f32,
            0.1,
        );
        let v_matrix = Mat4::look_at_rh(Vec3::new(0.0, 2.0, 3.0), Vec3::ZERO, Vec3::Y);
        let vp_matrix = p_matrix * v_matrix;

        let render_fence = device.create_fence("Render Fence")?;
        let presentation_semaphore = device.create_binary_semaphore("Presentation Semaphore")?;
        let render_semaphore = device.create_binary_semaphore("Render Semaphore")?;

        let transfer_timeline_value = 0;
        let transfer_timeline =
            device.create_timeline_semaphore("Transfer Timeline", transfer_timeline_value)?;

        let mut app = Self {
            frame_counter: 0,
            device,
            swapchain,
            graphics_queue,
            transfer_queue,
            graphics_command_pool,
            extent,
            pipeline,
            pipeline_layout,
            render_pass,
            vertex_buffer: vec![],
            index_buffer: vec![],
            textures: vec![],
            depth_texture,
            sampler,
            vp_matrix,
            render_fence,
            presentation_semaphore,
            render_semaphore,
            transfer_timeline,
            transfer_timeline_value,
            descriptor_set_layout,
            descriptor_pool,
        };

        // Upload the model data
        let (vertex_data, index_data) = create_cube_data();
        let index_buffer = app.create_buffer(
            "Index Buffer",
            &bytemuck::cast_slice(&index_data),
            vk::BufferUsageFlags::INDEX_BUFFER,
        )?;

        let vertex_buffer = app.create_buffer(
            "Vertex Buffer",
            &bytemuck::cast_slice(&vertex_data),
            vk::BufferUsageFlags::VERTEX_BUFFER,
        )?;

        app.vertex_buffer.push(vertex_buffer);
        app.index_buffer.push(index_buffer);

        // Upload the model texture
        let texture_data = include_bytes!("fractal.dds");
        let texture = app.create_texture("Cube Texture", texture_data)?;
        app.textures.push(texture);

        Ok(app)
    }

    fn create_texture(
        &mut self,
        name: &str,
        image_data: &[u8],
    ) -> Result<Texture, asche::AscheError> {
        let dds = ddsfile::Dds::read(&mut std::io::Cursor::new(&image_data)).unwrap();

        let mut stagging_buffer = self.device.create_buffer(&asche::BufferDescriptor {
            name: "Staging Buffer",
            usage: vk::BufferUsageFlags::TRANSFER_SRC,
            memory_location: vk_alloc::MemoryLocation::CpuToGpu,
            sharing_mode: vk::SharingMode::CONCURRENT,
            queues: vk::QueueFlags::TRANSFER | vk::QueueFlags::GRAPHICS,
            size: dds.data.len() as u64,
            flags: None,
        })?;

        let stagging_slice = stagging_buffer
            .allocation
            .mapped_slice_mut()
            .expect("staging buffer allocation was not mapped");
        stagging_slice[..dds.data.len()].clone_from_slice(bytemuck::cast_slice(&dds.data));
        self.device
            .flush_mapped_memory(&stagging_buffer.allocation)?;

        let extent = vk::Extent3D {
            width: dds.header.width,
            height: dds.header.height,
            depth: 1,
        };

        let image = self.device.create_image(&asche::ImageDescriptor {
            name,
            usage: vk::ImageUsageFlags::SAMPLED | vk::ImageUsageFlags::TRANSFER_DST,
            memory_location: vk_alloc::MemoryLocation::GpuOnly,
            sharing_mode: vk::SharingMode::CONCURRENT,
            queues: vk::QueueFlags::TRANSFER | vk::QueueFlags::GRAPHICS,
            image_type: vk::ImageType::_2D,
            format: vk::Format::BC7_SRGB_BLOCK,
            extent,
            mip_levels: 1,
            array_layers: 1,
            samples: vk::SampleCountFlagBits::_1,
            tiling: vk::ImageTiling::OPTIMAL,
            initial_layout: vk::ImageLayout::UNDEFINED,
            flags: None,
        })?;

        let subresource_range = vk::ImageSubresourceRange {
            aspect_mask: vk::ImageAspectFlags::COLOR,
            base_mip_level: 0,
            level_count: 1,
            base_array_layer: 0,
            layer_count: 1,
        };

        let view = self.device.create_image_view(&asche::ImageViewDescriptor {
            name: &format!("{} View", name),
            image: &image,
            view_type: vk::ImageViewType::_2D,
            format: vk::Format::BC7_SRGB_BLOCK,
            components: vk::ComponentMapping {
                r: vk::ComponentSwizzle::R,
                g: vk::ComponentSwizzle::G,
                b: vk::ComponentSwizzle::B,
                a: vk::ComponentSwizzle::A,
            },
            subresource_range,
            flags: None,
        })?;

        let mut transfer_pool = self.transfer_queue.create_command_pool()?;
        let transfer_buffer = transfer_pool.create_command_buffer(
            Some(&CommandBufferSemaphore::Timeline {
                semaphore: &self.transfer_timeline,
                stage: vk::PipelineStageFlags2KHR::NONE_KHR,
                value: self.transfer_timeline_value,
            }),
            &CommandBufferSemaphore::Timeline {
                semaphore: &self.transfer_timeline,
                stage: vk::PipelineStageFlags2KHR::NONE_KHR,
                value: self.transfer_timeline_value + 1,
            },
        )?;

        {
            let encoder = transfer_buffer.record()?;
            let barrier = [vk::ImageMemoryBarrier2KHRBuilder::new()
                .old_layout(vk::ImageLayout::UNDEFINED)
                .new_layout(vk::ImageLayout::TRANSFER_DST_OPTIMAL)
                .image(image.raw)
                .subresource_range(subresource_range)
                .src_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
                .dst_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
                .dst_stage_mask(vk::PipelineStageFlags2KHR::TRANSFER_KHR)
                .dst_access_mask(vk::AccessFlags2KHR::TRANSFER_WRITE_KHR)];

            let dependency_info = vk::DependencyInfoKHRBuilder::new()
                .memory_barriers(&[])
                .image_memory_barriers(&barrier)
                .buffer_memory_barriers(&[]);

            encoder.pipeline_barrier2(&dependency_info);

            encoder.copy_buffer_to_image(
                stagging_buffer.raw,
                image.raw,
                vk::ImageLayout::TRANSFER_DST_OPTIMAL,
                vk::BufferImageCopyBuilder::new()
                    .buffer_offset(0)
                    .buffer_row_length(0)
                    .buffer_image_height(0)
                    .image_subresource(vk::ImageSubresourceLayers {
                        aspect_mask: vk::ImageAspectFlags::COLOR,
                        mip_level: 0,
                        base_array_layer: 0,
                        layer_count: 1,
                    })
                    .image_offset(vk::Offset3D { x: 0, y: 0, z: 0 })
                    .image_extent(extent),
            );

            let barrier = [vk::ImageMemoryBarrier2KHRBuilder::new()
                .old_layout(vk::ImageLayout::TRANSFER_DST_OPTIMAL)
                .new_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL)
                .image(image.raw)
                .subresource_range(subresource_range)
                .src_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
                .dst_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
                .src_stage_mask(vk::PipelineStageFlags2KHR::TRANSFER_KHR)
                .src_access_mask(vk::AccessFlags2KHR::TRANSFER_WRITE_KHR)
                .dst_stage_mask(vk::PipelineStageFlags2KHR::ALL_TRANSFER_KHR)
                .dst_access_mask(vk::AccessFlags2KHR::NONE_KHR)];

            let dependency_info = vk::DependencyInfoKHRBuilder::new()
                .memory_barriers(&[])
                .image_memory_barriers(&barrier)
                .buffer_memory_barriers(&[]);

            encoder.pipeline_barrier2(&dependency_info);
        }
        self.transfer_timeline_value += 1;
        self.transfer_queue.submit(&transfer_buffer, None)?;
        self.transfer_timeline
            .wait_for_value(self.transfer_timeline_value)?;

        Ok(Texture {
            view,
            _image: image,
        })
    }

    fn create_buffer(
        &mut self,
        name: &str,
        buffer_data: &[u8],
        buffer_type: vk::BufferUsageFlags,
    ) -> Result<asche::Buffer, asche::AscheError> {
        let mut stagging_buffer = self.device.create_buffer(&asche::BufferDescriptor {
            name: "Staging Buffer",
            usage: vk::BufferUsageFlags::TRANSFER_SRC,
            memory_location: vk_alloc::MemoryLocation::CpuToGpu,
            sharing_mode: vk::SharingMode::CONCURRENT,
            queues: vk::QueueFlags::TRANSFER | vk::QueueFlags::GRAPHICS,
            size: buffer_data.len() as u64,
            flags: None,
        })?;

        let stagging_slice = stagging_buffer
            .allocation
            .mapped_slice_mut()
            .expect("staging buffer allocation was not mapped");
        stagging_slice[..buffer_data.len()].clone_from_slice(bytemuck::cast_slice(&buffer_data));

        let dst_buffer = self.device.create_buffer(&asche::BufferDescriptor {
            name,
            usage: buffer_type | vk::BufferUsageFlags::TRANSFER_DST,
            memory_location: vk_alloc::MemoryLocation::GpuOnly,
            sharing_mode: vk::SharingMode::CONCURRENT,
            queues: vk::QueueFlags::TRANSFER | vk::QueueFlags::GRAPHICS,
            size: stagging_buffer.allocation.size,
            flags: None,
        })?;

        let mut transfer_pool = self.transfer_queue.create_command_pool()?;
        let transfer_buffer = transfer_pool.create_command_buffer(
            Some(&CommandBufferSemaphore::Timeline {
                semaphore: &self.transfer_timeline,
                stage: vk::PipelineStageFlags2KHR::NONE_KHR,
                value: self.transfer_timeline_value,
            }),
            &CommandBufferSemaphore::Timeline {
                semaphore: &self.transfer_timeline,
                stage: vk::PipelineStageFlags2KHR::NONE_KHR,
                value: self.transfer_timeline_value + 1,
            },
        )?;

        {
            let encoder = transfer_buffer.record()?;
            encoder.copy_buffer(
                stagging_buffer.raw,
                dst_buffer.raw,
                0,
                0,
                buffer_data.len() as u64,
            );
        }

        self.transfer_timeline_value += 1;
        self.transfer_queue.submit(&transfer_buffer, None)?;
        self.transfer_timeline
            .wait_for_value(self.transfer_timeline_value)?;

        transfer_pool.reset()?;
        self.graphics_command_pool.reset()?;

        Ok(dst_buffer)
    }

    fn render(&mut self) -> Result<(), asche::AscheError> {
        let frame = self.swapchain.next_frame(&self.presentation_semaphore)?;

        let graphics_buffer = self.graphics_command_pool.create_command_buffer(
            None,
            &CommandBufferSemaphore::Binary {
                semaphore: &self.render_semaphore,
                stage: vk::PipelineStageFlags2KHR::COLOR_ATTACHMENT_OUTPUT_KHR,
            },
        )?;

        let m_matrix =
            Mat4::from_rotation_y((std::f32::consts::PI) * self.frame_counter as f32 / 500.0);
        let mvp_matrix = self.vp_matrix * m_matrix;

        let set = self.descriptor_pool.create_descriptor_set(
            "Cube Descriptor Set",
            &self.descriptor_set_layout,
            None,
        )?;

        let image_info = [vk::DescriptorImageInfoBuilder::new()
            .sampler(self.sampler.raw)
            .image_view(self.textures[0].view.raw)
            .image_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL)];
        let write = vk::WriteDescriptorSetBuilder::new()
            .dst_set(set.raw)
            .dst_binding(0)
            .descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER)
            .image_info(&image_info);
        self.device.update_descriptor_sets(&[write], &[]);

        {
            let encoder = graphics_buffer.record()?;
            encoder.set_viewport_and_scissor(
                vk::Rect2DBuilder::new()
                    .offset(vk::Offset2D { x: 0, y: 0 })
                    .extent(self.extent),
            );

            let pass = encoder.begin_render_pass(
                &mut self.swapchain,
                &self.render_pass,
                &[asche::RenderPassColorAttachmentDescriptor {
                    attachment: frame.view,
                    clear_value: Some(vk::ClearValue {
                        color: vk::ClearColorValue {
                            float32: [1.0, 0.0, 1.0, 1.0],
                        },
                    }),
                }],
                Some(asche::RenderPassDepthAttachmentDescriptor {
                    attachment: self.depth_texture.view.raw,
                    clear_value: Some(vk::ClearValue {
                        color: vk::ClearColorValue {
                            float32: [1.0, 0.0, 1.0, 1.0],
                        },
                    }),
                }),
                self.extent,
            )?;

            pass.bind_pipeline(&self.pipeline);
            pass.bind_descriptor_sets(self.pipeline_layout.raw, 0, &[set.raw], &[]);

            pass.bind_index_buffer(self.index_buffer[0].raw, 0, vk::IndexType::UINT32);
            pass.bind_vertex_buffers(0, &[self.vertex_buffer[0].raw], &[0]);

            pass.push_constants(
                self.pipeline_layout.raw,
                vk::ShaderStageFlags::VERTEX,
                0,
                cast_slice(mvp_matrix.as_ref()),
            );

            pass.draw_indexed(36, 1, 0, 0, 0);
        }

        self.graphics_queue
            .submit(&graphics_buffer, Some(&self.render_fence))?;

        self.render_fence.wait()?;
        self.render_fence.reset()?;

        self.graphics_command_pool.reset()?;
        self.descriptor_pool.free_sets()?;

        self.swapchain.queue_frame(
            &self.graphics_queue,
            frame,
            &[&self.presentation_semaphore, &self.render_semaphore],
        )?;

        self.frame_counter += 1;

        Ok(())
    }
}

fn create_cube_data() -> (Vec<Vertex>, Vec<u32>) {
    let vertex_data = [
        Vertex {
            position: [-1.0, -1.0, 1.0, 1.0],
            tex_coord: [0.0, 0.0],
        },
        Vertex {
            position: [1.0, -1.0, 1.0, 1.0],
            tex_coord: [1.0, 0.0],
        },
        Vertex {
            position: [1.0, 1.0, 1.0, 1.0],
            tex_coord: [1.0, 1.0],
        },
        Vertex {
            position: [-1.0, 1.0, 1.0, 1.0],
            tex_coord: [0.0, 1.0],
        },
        Vertex {
            position: [-1.0, 1.0, -1.0, 1.0],
            tex_coord: [1.0, 0.0],
        },
        Vertex {
            position: [1.0, 1.0, -1.0, 1.0],
            tex_coord: [0.0, 0.0],
        },
        Vertex {
            position: [1.0, -1.0, -1.0, 1.0],
            tex_coord: [0.0, 1.0],
        },
        Vertex {
            position: [-1.0, -1.0, -1.0, 1.0],
            tex_coord: [1.0, 1.0],
        },
        Vertex {
            position: [1.0, -1.0, -1.0, 1.0],
            tex_coord: [0.0, 0.0],
        },
        Vertex {
            position: [1.0, 1.0, -1.0, 1.0],
            tex_coord: [1.0, 0.0],
        },
        Vertex {
            position: [1.0, 1.0, 1.0, 1.0],
            tex_coord: [1.0, 1.0],
        },
        Vertex {
            position: [1.0, -1.0, 1.0, 1.0],
            tex_coord: [0.0, 1.0],
        },
        Vertex {
            position: [-1.0, -1.0, 1.0, 1.0],
            tex_coord: [1.0, 0.0],
        },
        Vertex {
            position: [-1.0, 1.0, 1.0, 1.0],
            tex_coord: [0.0, 0.0],
        },
        Vertex {
            position: [-1.0, 1.0, -1.0, 1.0],
            tex_coord: [0.0, 1.0],
        },
        Vertex {
            position: [-1.0, -1.0, -1.0, 1.0],
            tex_coord: [1.0, 1.0],
        },
        Vertex {
            position: [1.0, 1.0, -1.0, 1.0],
            tex_coord: [1.0, 0.0],
        },
        Vertex {
            position: [-1.0, 1.0, -1.0, 1.0],
            tex_coord: [0.0, 0.0],
        },
        Vertex {
            position: [-1.0, 1.0, 1.0, 1.0],
            tex_coord: [0.0, 1.0],
        },
        Vertex {
            position: [1.0, 1.0, 1.0, 1.0],
            tex_coord: [1.0, 1.0],
        },
        Vertex {
            position: [1.0, -1.0, 1.0, 1.0],
            tex_coord: [0.0, 0.0],
        },
        Vertex {
            position: [-1.0, -1.0, 1.0, 1.0],
            tex_coord: [1.0, 0.0],
        },
        Vertex {
            position: [-1.0, -1.0, -1.0, 1.0],
            tex_coord: [1.0, 1.0],
        },
        Vertex {
            position: [1.0, -1.0, -1.0, 1.0],
            tex_coord: [0.0, 1.0],
        },
    ];

    let index_data: &[u32] = &[
        0, 1, 2, 2, 3, 0, 4, 5, 6, 6, 7, 4, 8, 9, 10, 10, 11, 8, 12, 13, 14, 14, 15, 12, 16, 17,
        18, 18, 19, 16, 20, 21, 22, 22, 23, 20,
    ];

    (vertex_data.to_vec(), index_data.to_vec())
}

struct Texture {
    view: asche::ImageView,
    _image: asche::Image,
}

/// Right-handed with the the x-axis pointing right, y-axis pointing up, and z-axis pointing out of the screen for Vulkan NDC.
#[inline]
fn perspective_infinite_reverse_rh_yup(fov_y_radians: f32, aspect_ratio: f32, z_near: f32) -> Mat4 {
    let f = 1.0 / (0.5 * fov_y_radians).tan();
    Mat4::from_cols(
        Vec4::new(f / aspect_ratio, 0.0, 0.0, 0.0),
        Vec4::new(0.0, -f, 0.0, 0.0),
        Vec4::new(0.0, 0.0, 0.0, -1.0),
        Vec4::new(0.0, 0.0, z_near, 0.0),
    )
}
