screen-13 0.8.0

An easy-to-use Vulkan rendering engine in the spirit of QBasic.
Documentation
use {
    bytemuck::{cast_slice, NoUninit},
    inline_spirv::inline_spirv,
    screen_13::prelude::*,
    std::sync::Arc,
};

fn main() -> Result<(), DisplayError> {
    pretty_env_logger::init();

    let event_loop = EventLoop::new()
        .debug(true)
        .window(|window| window.with_inner_size(LogicalSize::new(512, 512)))
        .build()?;
    let images = create_images(&event_loop.device)?;
    let pipeline = create_graphic_pipeline(&event_loop.device)?;
    let draw_buf = create_indirect_buffer(&event_loop.device)?;

    event_loop.run(|frame| {
        let draw_buf_node = frame.render_graph.bind_node(&draw_buf);

        let mut pass = frame
            .render_graph
            .begin_pass("Test")
            .bind_pipeline(&pipeline)
            .access_node(draw_buf_node, AccessType::IndirectBuffer);

        for (idx, image) in images.iter().enumerate() {
            let image_node = pass.bind_node(image);
            pass = pass.read_descriptor((0, [idx as u32]), image_node);
        }

        pass.store_color(0, frame.swapchain_image)
            .record_subpass(move |subpass, _| {
                subpass.draw_indirect(draw_buf_node, 0, 64, 16);
            });
    })
}

fn create_images(device: &Arc<Device>) -> Result<Vec<Arc<Image>>, DisplayError> {
    let mut textures = Vec::with_capacity(64);

    let (b, a) = (0.0, 1.0);
    let mut graph = RenderGraph::new();
    for y in 0..8 {
        for x in 0..8 {
            let texture = Arc::new(Image::create(
                device,
                ImageInfo::new_2d(
                    vk::Format::R8G8B8A8_UNORM,
                    100,
                    100,
                    vk::ImageUsageFlags::SAMPLED | vk::ImageUsageFlags::TRANSFER_DST,
                ),
            )?);
            let texture_node = graph.bind_node(&texture);
            let r = y as f32 / 7.0;
            let g = x as f32 / 7.0;
            graph.clear_color_image_value(texture_node, [r, g, b, a]);
            textures.push(texture);
        }
    }

    let mut pool = LazyPool::new(device);

    graph.resolve().submit(&mut pool, 0)?;

    Ok(textures)
}

fn create_indirect_buffer(device: &Arc<Device>) -> Result<Arc<Buffer>, DriverError> {
    let mut draw_cmds = Vec::with_capacity(64);
    for first_instance in 0..64 {
        draw_cmds.push(DrawIndirectCommand {
            vertex_count: 6,
            instance_count: 1,
            first_vertex: 0,
            first_instance,
        });
    }
    let draw_buf = Arc::new(Buffer::create_from_slice(
        device,
        vk::BufferUsageFlags::INDIRECT_BUFFER,
        cast_slice(&draw_cmds),
    )?);
    Ok(draw_buf)
}

fn create_graphic_pipeline(device: &Arc<Device>) -> Result<Arc<GraphicPipeline>, DriverError> {
    Ok(Arc::new(GraphicPipeline::create(
        device,
        GraphicPipelineInfo::default(),
        [
            Shader::new_vertex(
                inline_spirv!(
                    r#"
                    #version 460 core

                    const vec2 QUAD[] = {
                        vec2(0, 0),
                        vec2(0, 1),
                        vec2(1, 1),
                        vec2(0, 0),
                        vec2(1, 1),
                        vec2(1, 0),
                    };

                    layout(location = 0) out uint instance_index_out;

                    void main() {
                        uint x = gl_InstanceIndex % 8;
                        uint y = gl_InstanceIndex / 8;

                        vec2 scale = vec2(1.0 / 8.0);
                        vec2 offset = vec2((float(x) - 4.0) * scale.x, (float(y) - 4.0) * scale.y);

                        gl_Position = vec4(QUAD[gl_VertexIndex] * scale + offset, 0, 1);
                        instance_index_out = gl_InstanceIndex;
                    }
                    "#,
                    vert
                )
                .as_slice(),
            ),
            Shader::new_fragment(
                inline_spirv!(
                    r#"
                    #version 460 core
                    #extension GL_EXT_nonuniform_qualifier : require

                    layout(set = 0, binding = 0) uniform sampler2D sampler_nnr[];

                    layout(location = 0) in flat uint instance_index;

                    layout(location = 0) out vec4 color_out;

                    void main() {
                        color_out = texture(sampler_nnr[instance_index], vec2(0.5, 0.5));
                    }
                    "#,
                    frag
                )
                .as_slice(),
            ),
        ],
    )?))
}

#[repr(C)]
#[derive(Copy, Clone)]
struct DrawIndirectCommand {
    vertex_count: u32,
    instance_count: u32,
    first_vertex: u32,
    first_instance: u32,
}

unsafe impl NoUninit for DrawIndirectCommand {}