mittens-engine 0.7.0

A Vulkan and OpenXR scene engine with ECS, reactive signals, and Meow Meow scripting
use std::sync::Arc;

use vulkano::buffer::{Buffer, BufferCreateInfo, BufferUsage};
use vulkano::command_buffer::PrimaryCommandBufferAbstract;
use vulkano::command_buffer::{
    AutoCommandBufferBuilder, CommandBufferUsage, CopyBufferToImageInfo,
    allocator::StandardCommandBufferAllocator,
};
use vulkano::format::Format;
use vulkano::image::view::ImageView;
use vulkano::image::{Image, ImageCreateInfo, ImageType, ImageUsage};
use vulkano::memory::allocator::{AllocationCreateInfo, MemoryTypeFilter};
use vulkano::sync::GpuFuture;
use vulkano_util::context::VulkanoContext;

pub(crate) fn upload_texture_rgba8(
    context: &VulkanoContext,
    command_buffer_allocator: &Arc<StandardCommandBufferAllocator>,
    rgba: &[u8],
    width: u32,
    height: u32,
) -> Result<Arc<ImageView>, Box<dyn std::error::Error>> {
    if width == 0 || height == 0 {
        return Err("texture has zero size".into());
    }

    let expected_len = width as usize * height as usize * 4;
    if rgba.len() != expected_len {
        return Err(format!(
            "texture rgba length mismatch: got={}, expected={}",
            rgba.len(),
            expected_len
        )
        .into());
    }

    let memory_allocator = context.memory_allocator().clone();
    let queue = context.graphics_queue().clone();

    let staging = Buffer::from_iter(
        memory_allocator.clone(),
        BufferCreateInfo {
            usage: BufferUsage::TRANSFER_SRC,
            ..Default::default()
        },
        AllocationCreateInfo {
            memory_type_filter: MemoryTypeFilter::PREFER_HOST
                | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE,
            ..Default::default()
        },
        rgba.iter().copied(),
    )?;

    let image = Image::new(
        memory_allocator,
        ImageCreateInfo {
            image_type: ImageType::Dim2d,
            format: Format::R8G8B8A8_UNORM,
            extent: [width, height, 1],
            usage: ImageUsage::TRANSFER_DST | ImageUsage::SAMPLED,
            ..Default::default()
        },
        AllocationCreateInfo {
            memory_type_filter: MemoryTypeFilter::PREFER_DEVICE,
            ..Default::default()
        },
    )?;

    let mut cbb = AutoCommandBufferBuilder::primary(
        command_buffer_allocator.clone(),
        queue.queue_family_index(),
        CommandBufferUsage::OneTimeSubmit,
    )?;

    cbb.copy_buffer_to_image(CopyBufferToImageInfo::buffer_image(staging, image.clone()))?;

    let cb = cbb.build()?;

    cb.execute(queue.clone())?
        .then_signal_fence_and_flush()?
        .wait(None)?;

    let view = ImageView::new_default(image)
        .map_err(|e| -> Box<dyn std::error::Error> { format!("{e:?}").into() })?;

    Ok(view)
}

pub(crate) fn upload_texture_bc7(
    context: &VulkanoContext,
    command_buffer_allocator: &Arc<StandardCommandBufferAllocator>,
    bc7_blocks: &[u8],
    width: u32,
    height: u32,
    srgb: bool,
) -> Result<Arc<ImageView>, Box<dyn std::error::Error>> {
    if width == 0 || height == 0 {
        return Err("texture has zero size".into());
    }

    let blocks_w = (width + 3) / 4;
    let blocks_h = (height + 3) / 4;
    let expected_len = blocks_w as usize * blocks_h as usize * 16;
    if bc7_blocks.len() != expected_len {
        return Err(format!(
            "texture bc7 length mismatch: got={}, expected={}",
            bc7_blocks.len(),
            expected_len
        )
        .into());
    }

    let memory_allocator = context.memory_allocator().clone();
    let queue = context.graphics_queue().clone();

    let staging = Buffer::from_iter(
        memory_allocator.clone(),
        BufferCreateInfo {
            usage: BufferUsage::TRANSFER_SRC,
            ..Default::default()
        },
        AllocationCreateInfo {
            memory_type_filter: MemoryTypeFilter::PREFER_HOST
                | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE,
            ..Default::default()
        },
        bc7_blocks.iter().copied(),
    )?;

    let format = if srgb {
        Format::BC7_SRGB_BLOCK
    } else {
        Format::BC7_UNORM_BLOCK
    };

    let image = Image::new(
        memory_allocator,
        ImageCreateInfo {
            image_type: ImageType::Dim2d,
            format,
            extent: [width, height, 1],
            usage: ImageUsage::TRANSFER_DST | ImageUsage::SAMPLED,
            ..Default::default()
        },
        AllocationCreateInfo {
            memory_type_filter: MemoryTypeFilter::PREFER_DEVICE,
            ..Default::default()
        },
    )?;

    let mut cbb = AutoCommandBufferBuilder::primary(
        command_buffer_allocator.clone(),
        queue.queue_family_index(),
        CommandBufferUsage::OneTimeSubmit,
    )?;

    cbb.copy_buffer_to_image(CopyBufferToImageInfo::buffer_image(staging, image.clone()))?;

    let cb = cbb.build()?;

    cb.execute(queue.clone())?
        .then_signal_fence_and_flush()?
        .wait(None)?;

    let view = ImageView::new_default(image)
        .map_err(|e| -> Box<dyn std::error::Error> { format!("{e:?}").into() })?;

    Ok(view)
}