vulkane 0.4.1

Vulkan API bindings generated entirely from vk.xml, with a complete safe RAII wrapper covering compute and graphics: instance/device/queue, buffer, image, sampler, render pass, framebuffer, graphics + compute pipelines, swapchain, a VMA-style sub-allocator with TLSF + linear pools and defragmentation, sync primitives (fences, binary + timeline semaphores, sync2 barriers), query pools, and an optional naga GLSL/WGSL→SPIR-V feature. Supports Vulkan 1.2.175 onward — swap vk.xml and rebuild.
//! Demonstrates `#[derive(Vertex)]` for automatic vertex input layout.
//!
//! Instead of manually declaring `VertexInputBinding` and
//! `VertexInputAttribute` arrays with correct strides, offsets, and
//! format enums, just derive `Vertex` on a `#[repr(C)]` struct and
//! call `MyVertex::binding(n)` / `MyVertex::attributes(n)`.
//!
//! This example renders the same instanced triangles as
//! `instanced_mesh`, but with the vertex layout generated by the
//! derive macro.
//!
//! Run with: `cargo run -p vulkane --features fetch-spec,derive --example derive_vertex`

#[cfg(not(feature = "derive"))]
fn main() {
    eprintln!(
        "This example requires the `derive` feature.\n\
         Run with: cargo run -p vulkane --features derive,fetch-spec --example derive_vertex"
    );
    std::process::exit(1);
}

#[cfg(feature = "derive")]
use vulkane::safe::{
    AccessFlags, ApiVersion, AttachmentDescription, AttachmentLoadOp, AttachmentStoreOp, Buffer,
    BufferCreateInfo, BufferImageCopy, BufferUsage, ClearValue, CommandPool, DeviceCreateInfo,
    Fence, Format, Framebuffer, GraphicsPipelineBuilder, GraphicsShaderStage, Image,
    Image2dCreateInfo, ImageLayout, ImageUsage, Instance, InstanceCreateInfo,
    MemoryPropertyFlags, PipelineLayout, PipelineStage, QueueCreateInfo, QueueFlags, RenderPass,
    RenderPassCreateInfo, ShaderModule,
};
#[cfg(feature = "derive")]
use vulkane::Vertex;

#[cfg(feature = "derive")]
const W: u32 = 256;
#[cfg(feature = "derive")]
const H: u32 = 256;
#[cfg(feature = "derive")]
const INSTANCE_COUNT: u32 = 100;

/// Per-vertex data: just position.
#[cfg(feature = "derive")]
#[derive(Vertex, Clone, Copy)]
#[repr(C)]
struct MeshVertex {
    position: [f32; 3],
}

/// Per-instance data: just an offset.
#[cfg(feature = "derive")]
#[derive(Vertex, Clone, Copy)]
#[repr(C)]
struct InstanceData {
    offset: [f32; 3],
}

#[cfg(feature = "derive")]
fn main() -> Result<(), Box<dyn std::error::Error>> {
    let manifest_dir = env!("CARGO_MANIFEST_DIR");
    let spv_path = format!("{manifest_dir}/examples/shaders/instanced_mesh.wgsl.spv");
    let spv_bytes = std::fs::read(&spv_path).map_err(|e| {
        format!("could not read {spv_path}: {e} (run compile_shader first)")
    })?;

    let instance = Instance::new(InstanceCreateInfo {
        application_name: Some("vulkane derive_vertex"),
        api_version: ApiVersion::V1_0,
        ..Default::default()
    })?;
    let physical = instance
        .enumerate_physical_devices()?
        .into_iter()
        .find(|pd| pd.find_queue_family(QueueFlags::GRAPHICS).is_some())
        .ok_or("no GPU with a graphics queue")?;
    println!("[OK] Using GPU: {}", physical.properties().device_name());

    let qf = physical.find_queue_family(QueueFlags::GRAPHICS).unwrap();
    let device = physical.create_device(DeviceCreateInfo {
        queue_create_infos: &[QueueCreateInfo::single(qf)],
        ..Default::default()
    })?;
    let queue = device.get_queue(qf, 0);

    // Vertex data — same triangle as instanced_mesh.
    let vertices = [
        MeshVertex { position: [0.0, -0.3, 0.0] },
        MeshVertex { position: [0.3, 0.3, 0.0] },
        MeshVertex { position: [-0.3, 0.3, 0.0] },
    ];

    // Instance offsets — 10x10 grid.
    let mut instances = Vec::with_capacity(INSTANCE_COUNT as usize);
    for row in 0..10 {
        for col in 0..10 {
            instances.push(InstanceData {
                offset: [(col as f32 - 4.5) * 2.0, (row as f32 - 4.5) * 2.0, 0.0],
            });
        }
    }

    let (vert_buf, _vert_mem) =
        queue.upload_buffer(&device, &physical, qf, &vertices, BufferUsage::VERTEX_BUFFER)?;
    let (inst_buf, _inst_mem) =
        queue.upload_buffer(&device, &physical, qf, &instances, BufferUsage::VERTEX_BUFFER)?;

    // Look: the vertex layout is derived automatically!
    let bindings = [MeshVertex::binding(0), InstanceData::instance_binding(1)];
    let vert_attrs = MeshVertex::attributes(0);
    let inst_attrs = InstanceData::attributes(1);
    // Combine attributes and adjust locations for the instance binding.
    let mut all_attrs = Vec::new();
    all_attrs.extend_from_slice(&vert_attrs);
    for mut attr in inst_attrs {
        attr.location += vert_attrs.len() as u32;
        all_attrs.push(attr);
    }

    println!(
        "[OK] Derived vertex layout: {} bindings, {} attributes",
        bindings.len(),
        all_attrs.len()
    );
    println!("     MeshVertex: stride={}, 1 attribute", bindings[0].stride);
    println!("     InstanceData: stride={}, 1 attribute (instance rate)", bindings[1].stride);

    // Render setup — abbreviated since the infrastructure is proven.
    let (color_img, _color_mem, color_view) = Image::new_2d_bound(
        &device, &physical,
        Image2dCreateInfo {
            format: Format::R8G8B8A8_UNORM, width: W, height: H,
            usage: ImageUsage::COLOR_ATTACHMENT | ImageUsage::TRANSFER_SRC,
        },
        MemoryPropertyFlags::DEVICE_LOCAL,
    )?;
    let render_pass = RenderPass::new(&device, RenderPassCreateInfo {
        color_attachments: &[AttachmentDescription {
            format: Format::R8G8B8A8_UNORM,
            load_op: AttachmentLoadOp::CLEAR, store_op: AttachmentStoreOp::STORE,
            initial_layout: ImageLayout::UNDEFINED,
            final_layout: ImageLayout::TRANSFER_SRC_OPTIMAL,
        }],
        depth_attachment: None,
    })?;
    let framebuffer = Framebuffer::new(&device, &render_pass, &[&color_view], W, H)?;

    let shader = ShaderModule::from_spirv_bytes(&device, &spv_bytes)?;
    let pipeline_layout = PipelineLayout::new(&device, &[])?;
    let pipeline = GraphicsPipelineBuilder::new(&pipeline_layout, &render_pass)
        .stage(GraphicsShaderStage::Vertex, &shader, "vs_main")
        .stage(GraphicsShaderStage::Fragment, &shader, "fs_main")
        .vertex_input(&bindings, &all_attrs)
        .viewport_extent(W, H)
        .cull_mode(vulkane::safe::CullMode::NONE)
        .build(&device)?;

    let (readback, mut rb_mem) = Buffer::new_bound(
        &device, &physical,
        BufferCreateInfo { size: (W * H * 4) as u64, usage: BufferUsage::TRANSFER_DST },
        MemoryPropertyFlags::HOST_VISIBLE | MemoryPropertyFlags::HOST_COHERENT,
    )?;

    let cmd_pool = CommandPool::new(&device, qf)?;
    let mut cmd = cmd_pool.allocate_primary()?;
    {
        let mut rec = cmd.begin()?;
        rec.begin_render_pass_ext(&render_pass, &framebuffer, &[ClearValue::Color([0.0, 0.0, 0.0, 1.0])]);
        rec.bind_graphics_pipeline(&pipeline);
        rec.bind_vertex_buffers(0, &[(&vert_buf, 0)]);
        rec.bind_vertex_buffers(1, &[(&inst_buf, 0)]);
        rec.draw(3, INSTANCE_COUNT, 0, 0);
        rec.end_render_pass();
        rec.copy_image_to_buffer(&color_img, ImageLayout::TRANSFER_SRC_OPTIMAL, &readback, &[BufferImageCopy::full_2d(W, H)]);
        rec.memory_barrier(PipelineStage::TRANSFER, PipelineStage::HOST, AccessFlags::TRANSFER_WRITE, AccessFlags::HOST_READ);
        rec.end()?;
    }

    let fence = Fence::new(&device)?;
    queue.submit(&[&cmd], Some(&fence))?;
    fence.wait(u64::MAX)?;

    let m = rb_mem.map()?;
    let bytes = m.as_slice();
    let mut painted = 0u32;
    for px in 0..(W * H) as usize {
        if bytes[px * 4] != 0 || bytes[px * 4 + 1] != 0 || bytes[px * 4 + 2] != 0 {
            painted += 1;
        }
    }
    println!("[OK] {painted} / {} non-black pixels ({:.1}%)", W * H, painted as f32 / (W * H) as f32 * 100.0);
    assert!(painted > 1000, "expected significant pixel coverage");

    drop(m);
    device.wait_idle()?;
    println!("\n=== derive_vertex example PASSED ===");
    Ok(())
}