bevy_vector_shapes 0.12.0

A library for rendering vector shapes using the Bevy game engine
Documentation
use bevy::{
    core_pipeline::tonemapping::{get_lut_bindings, Tonemapping, TonemappingLuts},
    ecs::{
        query::ROQueryItem,
        system::{
            lifetimeless::{Read, SRes},
            SystemParamItem,
        },
    },
    platform::collections::HashMap,
    prelude::*,
    render::{
        globals::GlobalsBuffer,
        render_asset::RenderAssets,
        render_phase::{
            PhaseItem, RenderCommand, RenderCommandResult, SetItemPipeline, TrackedRenderPass,
        },
        render_resource::{BindGroup, *},
        renderer::RenderDevice,
        texture::{FallbackImage, GpuImage},
        view::{ExtractedView, ViewUniformOffset, ViewUniforms},
    },
};

use crate::render::*;

pub type DrawShape2dCommand<T> = (
    SetItemPipeline,
    SetShapeViewBindGroup<0>,
    SetShape2dBindGroup<T, 1>,
    SetShape2dTextureBindGroup<T, 2>,
    DrawShape<T>,
);

pub type DrawShape3dCommand<T> = (
    SetItemPipeline,
    SetShapeViewBindGroup<0>,
    SetShape3dBindGroup<T, 1>,
    SetShape3dTextureBindGroup<T, 2>,
    DrawShape<T>,
);

#[derive(Component, Debug)]
pub struct ShapeViewBindGroup {
    value: BindGroup,
}

#[allow(clippy::too_many_arguments)]
pub fn prepare_shape_view_bind_groups(
    mut commands: Commands,
    render_device: Res<RenderDevice>,
    shape_pipeline: Res<ShapePipelines>,
    view_uniforms: Res<ViewUniforms>,
    globals_buffer: Res<GlobalsBuffer>,
    views: Query<(Entity, &Tonemapping), With<ExtractedView>>,
    tonemapping_luts: Res<TonemappingLuts>,
    images: Res<RenderAssets<GpuImage>>,
    fallback_image: Res<FallbackImage>,
    mut layout: Local<Option<BindGroupLayout>>,
) {
    let (Some(view_binding), Some(globals)) = (
        view_uniforms.uniforms.binding(),
        globals_buffer.buffer.binding(),
    ) else {
        return;
    };

    for (entity, tonemapping) in views.iter() {
        let view_bind_group_layout = layout.get_or_insert_with(|| {
            render_device.create_bind_group_layout(
                "shape_view_bind_group_layout",
                &shape_pipeline.view_layout.entries,
            )
        });
        
        let lut_bindings =
            get_lut_bindings(&images, &tonemapping_luts, tonemapping, &fallback_image);
        let view_bind_group = render_device.create_bind_group(
            "shape_view_bind_group",
            &view_bind_group_layout,
            &BindGroupEntries::with_indices((
                (0, view_binding.clone()),
                (1, globals.clone()),
                (2, lut_bindings.0),
                (3, lut_bindings.1),
            )),
        );

        commands.entity(entity).insert(ShapeViewBindGroup {
            value: view_bind_group,
        });
    }
}

#[derive(Resource, Default)]
pub struct ShapeTextureBindGroups {
    values: HashMap<Handle<Image>, BindGroup>,
}

pub fn prepare_shape_2d_texture_bind_groups<T: ShapeData>(
    render_device: Res<RenderDevice>,
    shape_pipelines: Res<ShapePipelines>,
    materials: ResMut<Shape2dMaterials<T>>,
    gpu_images: Res<RenderAssets<GpuImage>>,
    mut image_bind_groups: ResMut<ShapeTextureBindGroups>,
    mut layout: Local<Option<BindGroupLayout>>,
) {
    let texture_bind_group_layout = layout.get_or_insert_with(|| {
        render_device.create_bind_group_layout(
            "shape_texture_bind_group_layout",
            &shape_pipelines.texture_layout.entries,
        )
    });
    for material in materials.keys() {
        if let Some(handle) = &material.texture {
            if let Some(gpu_image) = gpu_images.get(handle.id()) {
                image_bind_groups
                    .values
                    .entry(handle.clone())
                    .or_insert_with(|| {
                        render_device.create_bind_group(
                            "shape_texture_bind_group",
                            &texture_bind_group_layout,
                            &BindGroupEntries::sequential((
                                &gpu_image.texture_view,
                                &gpu_image.sampler,
                            )),
                        )
                    });
            }
        }
    }
}

pub fn prepare_shape_3d_texture_bind_groups<T: ShapeData>(
    render_device: Res<RenderDevice>,
    shape_pipelines: Res<ShapePipelines>,
    materials: ResMut<Shape3dMaterials<T>>,
    gpu_images: Res<RenderAssets<GpuImage>>,
    mut image_bind_groups: ResMut<ShapeTextureBindGroups>,
    mut layout: Local<Option<BindGroupLayout>>,
) {
    let texture_bind_group_layout = layout.get_or_insert_with(|| {
        render_device.create_bind_group_layout(
            "shape_texture_bind_group_layout",
            &shape_pipelines.texture_layout.entries,
        )
    });
    for material in materials.keys() {
        if let Some(handle) = &material.texture {
            if let Some(gpu_image) = gpu_images.get(handle.id()) {
                image_bind_groups
                    .values
                    .entry(handle.clone())
                    .or_insert_with(|| {
                        render_device.create_bind_group(
                            "shape_texture_bind_group",
                            &texture_bind_group_layout,
                            &BindGroupEntries::sequential((
                                &gpu_image.texture_view,
                                &gpu_image.sampler,
                            )),
                        )
                    });
            }
        }
    }
}

pub struct SetShapeViewBindGroup<const I: usize>;

impl<const I: usize, P: PhaseItem> RenderCommand<P> for SetShapeViewBindGroup<I> {
    type ViewQuery = (Read<ViewUniformOffset>, Read<ShapeViewBindGroup>);
    type ItemQuery = ();
    type Param = ();

    #[inline]
    fn render<'w>(
        _item: &P,
        (view_uniform, shape_view_bind_group): ROQueryItem<'w, '_, Self::ViewQuery>,
        _entity: Option<()>,
        _param: SystemParamItem<'w, '_, Self::Param>,
        pass: &mut TrackedRenderPass<'w>,
    ) -> RenderCommandResult {
        pass.set_bind_group(I, &shape_view_bind_group.value, &[view_uniform.offset]);
        RenderCommandResult::Success
    }
}

pub struct SetShape2dTextureBindGroup<T: ShapeData, const I: usize>(PhantomData<T>);

impl<const I: usize, T: ShapeData, P: PhaseItem> RenderCommand<P>
    for SetShape2dTextureBindGroup<T, I>
{
    type ViewQuery = ();
    type ItemQuery = ();
    type Param = (SRes<ShapeTextureBindGroups>, SRes<Shape2dInstances<T>>);

    #[inline]
    fn render<'w>(
        item: &P,
        _view: (),
        _item_query: Option<()>,
        (bind_groups, instances): SystemParamItem<'w, '_, Self::Param>,
        pass: &mut TrackedRenderPass<'w>,
    ) -> RenderCommandResult {
        let Some(material) = instances.get(&item.entity()).map(|i| &i.material) else {
            return RenderCommandResult::Success;
        };
        if let Some(handle) = &material.texture {
            let bind_groups = bind_groups.into_inner();
            pass.set_bind_group(I, bind_groups.values.get(&handle.clone()).unwrap(), &[]);
        }
        RenderCommandResult::Success
    }
}

pub struct SetShape3dTextureBindGroup<T: ShapeData, const I: usize>(PhantomData<T>);

impl<const I: usize, T: ShapeData, P: PhaseItem> RenderCommand<P>
    for SetShape3dTextureBindGroup<T, I>
{
    type ViewQuery = ();
    type ItemQuery = ();
    type Param = (SRes<ShapeTextureBindGroups>, SRes<Shape3dInstances<T>>);

    #[inline]
    fn render<'w>(
        item: &P,
        _view: (),
        _item_query: Option<()>,
        (bind_groups, instances): SystemParamItem<'w, '_, Self::Param>,
        pass: &mut TrackedRenderPass<'w>,
    ) -> RenderCommandResult {
        let Some(material) = instances.get(&item.entity()).map(|i| &i.material) else {
            return RenderCommandResult::Success;
        };
        if let Some(handle) = &material.texture {
            let bind_groups = bind_groups.into_inner();
            pass.set_bind_group(I, bind_groups.values.get(&handle.clone()).unwrap(), &[]);
        }
        RenderCommandResult::Success
    }
}

pub struct SetShape2dBindGroup<T: ShapeData, const I: usize>(PhantomData<T>);

impl<const I: usize, T: ShapeData + 'static, P: PhaseItem> RenderCommand<P>
    for SetShape2dBindGroup<T, I>
{
    type Param = SRes<Shape2dBindGroup<T>>;
    type ViewQuery = ();
    type ItemQuery = ();

    #[inline]
    fn render<'w>(
        item: &P,
        _view: (),
        _item_query: Option<()>,
        shape_bind_group: SystemParamItem<'w, '_, Self::Param>,
        pass: &mut TrackedRenderPass<'w>,
    ) -> RenderCommandResult {
        let mut dynamic_offsets: [u32; 1] = Default::default();
        let mut offset_count = 0;
        if let PhaseItemExtraIndex::DynamicOffset(dynamic_offset) = item.extra_index() {
            dynamic_offsets[offset_count] = dynamic_offset;
            offset_count += 1;
        }
        pass.set_bind_group(
            I,
            &shape_bind_group.into_inner().value,
            &dynamic_offsets[..offset_count],
        );
        RenderCommandResult::Success
    }
}

pub struct SetShape3dBindGroup<T: ShapeData, const I: usize>(PhantomData<T>);

impl<const I: usize, T: ShapeData + 'static, P: PhaseItem> RenderCommand<P>
    for SetShape3dBindGroup<T, I>
{
    type Param = SRes<Shape3dBindGroup<T>>;
    type ViewQuery = ();
    type ItemQuery = ();

    #[inline]
    fn render<'w>(
        item: &P,
        _view: (),
        _item_query: Option<()>,
        shape_bind_group: SystemParamItem<'w, '_, Self::Param>,
        pass: &mut TrackedRenderPass<'w>,
    ) -> RenderCommandResult {
        let mut dynamic_offsets: [u32; 1] = Default::default();
        let mut offset_count = 0;
        if let PhaseItemExtraIndex::DynamicOffset(dynamic_offset) = item.extra_index() {
            dynamic_offsets[offset_count] = dynamic_offset;
            offset_count += 1;
        }
        pass.set_bind_group(
            I,
            &shape_bind_group.into_inner().value,
            &dynamic_offsets[..offset_count],
        );
        RenderCommandResult::Success
    }
}

pub struct DrawShape<T: ShapeData>(PhantomData<T>);

impl<P: PhaseItem, T: ShapeData> RenderCommand<P> for DrawShape<T> {
    type Param = SRes<QuadVertices>;
    type ViewQuery = ();
    type ItemQuery = ();

    #[inline]
    fn render<'w>(
        item: &P,
        _view: (),
        _item_query: Option<()>,
        quad: SystemParamItem<'w, '_, Self::Param>,
        pass: &mut TrackedRenderPass<'w>,
    ) -> RenderCommandResult {
        let batch_range = item.batch_range();
        pass.set_vertex_buffer(0, quad.into_inner().buffer.slice(..));
        pass.draw(0..T::VERTICES, batch_range.clone());

        RenderCommandResult::Success
    }
}