Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bevy 0.15 #6

Draft
wants to merge 8 commits into
base: main
Choose a base branch
from
Draft

Bevy 0.15 #6

wants to merge 8 commits into from

Conversation

bas-ie
Copy link

@bas-ie bas-ie commented Nov 21, 2024

I've been playing with bevy_sparse_tilemap a bit lately, because the "BYO renderer" model is quite interesting and helps me learn a bit about how rendering is changing in 0.15. I've managed to cobble together a version that works reasonably well with 0.15.0-rc.3. I've avoided the problem of bevy_fast_tilemap use for now, though I may reintroduce it if I end up upgrading that library too 😆

I have branches for upgrading hexx and lettuces also, which I'll PR when I know I don't need any more changes in those.

I did run into some trouble with incompatible versions of glam. I wondered if it would be possible to standardise on Bevy's exported versions of e.g. Vec2 and Vec3 to avoid that problem, not sure 🤔

Where you find a change that doesn't seem to be related to 0.15, it's probably because clippy was complaining about it 😄 I think I've managed to get rid of all the warnings now.

@NoahShomette
Copy link
Owner

This is awesome thank you so much for doing this! I will give it a look as soon as I can, probably over this weekend.

I'm totally good to use bevys version of Glam, when I was working on it initially I was running into issues with it but if it can be swapped and works fine then I'm good with that.

I'm good with removing bevy fast tilemap from here, I'll probably pull the example into its own crate so this crate can be updated. Bevy_fast_tilemap normally receives an update within a few weeks of bevy releases but it would be nice to not be directly tied to it.

@bas-ie
Copy link
Author

bas-ie commented Nov 23, 2024

No problem, it was really interesting! I played a bit with bevy_fast_tilemap today but couldn't quite get it to render, mostly due to the new ShaderStorageBuffer asset that has replaced storing good ol' Vec<u32> directly on the map. Something wasn't quite clicking for me, not sure what it was so will be interesting to see how the author navigates that.

In the meantime, I'll remove bevy_fast_tilemap from this one and see if I can figure out the glam versions 🤔

@bas-ie bas-ie mentioned this pull request Nov 23, 2024
@bas-ie
Copy link
Author

bas-ie commented Nov 23, 2024

Removed bevy-fast-tilemap for now, can always add back links to it later. Will finalise this once release rolls around, I think we're currently waiting for a WGPU patch release, specifically gfx-rs/wgpu#6535.

@notmd
Copy link

notmd commented Nov 30, 2024

I wrote a much simpler shader which work like bevy-fast-tilemap. It only works with square tile only. Feel free to use in your example

#import bevy_sprite::mesh2d_vertex_output::VertexOutput

// in tiles
@group(2) @binding(0) var<uniform> map_size: vec2<u32>;
// in pixels
@group(2) @binding(1) var<uniform> tile_size: f32;
@group(2) @binding(2) var atlas_texture: texture_2d<f32>;
@group(2) @binding(3) var atlas_sampler: sampler;
// in pixels
@group(2) @binding(4) var<uniform> atlas_size: vec2<f32>;
// map by tile index
@group(2) @binding(5) var<storage,read> map_tiles: array<u32>;

fn get_color(uv: vec2<f32>) -> vec4<f32> {
    // from 0..n_x_map_tiles
    let tile_x = uv.x * f32(map_size.x);
    // from 0..n_y_map_tiles
    let tile_y = uv.y * f32(map_size.y);
    // from 0..atlas_column*atlas_row
    let tile_idx = map_tiles[u32(tile_y) * map_size.x + u32(tile_x)];

    let atlas_column: u32 = u32(atlas_size.x / tile_size);
    let atlas_row: u32 = u32(atlas_size.y / tile_size);
    let column = tile_idx % atlas_column;
    let row = tile_idx / atlas_column;

    let pixel_position_in_tile = vec2<f32>(uv * vec2<f32>(map_size) * tile_size % tile_size);
    let pixel_position_in_atlas = pixel_position_in_tile + vec2<f32>(f32(column), f32(row)) * tile_size;

    let color = textureSample(atlas_texture, atlas_sampler, pixel_position_in_atlas / atlas_size);

    return color;
}

@fragment
fn fragment(in: VertexOutput) -> @location(0) vec4<f32> {
    return get_color(in.uv);
}

Here is rust code

use bevy::{
    asset::RenderAssetUsages,
    prelude::*,
    render::{
        render_resource::{AsBindGroup, ShaderRef},
        storage::ShaderStorageBuffer,
    },
    sprite::{Material2d, Material2dPlugin},
};

pub struct TileMapRenderPlugin;

impl Plugin for TileMapRenderPlugin {
    fn build(&self, app: &mut App) {
        app.add_plugins(Material2dPlugin::<TileMapMaterial>::default())
            .add_systems(Startup, setup)
            .add_systems(Update, spawn_tile_map);
    }
}

const MAP_SIZE: u32 = 2;
const TILE_SIZE: f32 = 16.;

#[derive(Debug, Clone, TypePath, AsBindGroup, Asset)]
pub struct TileMapMaterial {
    #[uniform(0)]
    map_size: UVec2,

    #[uniform(1)]
    tile_size: f32,

    #[texture(2)]
    #[sampler(3)]
    atlas_texture: Handle<Image>,

    /// The size of the atlas texture in pixels.
    #[uniform(4)]
    atlas_size: Vec2,

    #[storage(5, read_only)]
    map_tiles: Handle<ShaderStorageBuffer>,
}

const SHADER_PATH: &str = "shaders/tile_map.wgsl";

impl Material2d for TileMapMaterial {
    fn fragment_shader() -> ShaderRef {
        SHADER_PATH.into()
    }
}

fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
    let handle = asset_server.load("temp/tileset.png");
    commands.insert_resource(TileSetHandle(handle));
}

fn spawn_tile_map(
    mut commands: Commands,
    mut meshes: ResMut<Assets<Mesh>>,
    mut materials: ResMut<Assets<TileMapMaterial>>,
    images: Res<Assets<Image>>,
    asset_server: Res<AssetServer>,
    tileset_handle: Res<TileSetHandle>,
    mut buffers: ResMut<Assets<ShaderStorageBuffer>>,
) {
    let load_state = asset_server.get_load_state(&tileset_handle.0);

    if !load_state.map(|s| s.is_loaded()).unwrap_or_default() {
        return;
    }

    let map_size_in_pixel = MAP_SIZE * TILE_SIZE as u32;
    let tile_set = images.get(&tileset_handle.0).unwrap();

    let map_tiles: Vec<u32> = vec![17, 18, 24, 25];

    let buffer = buffers.add(ShaderStorageBuffer {
        data: Some(bytemuck::cast_slice(&map_tiles).to_vec()),
        asset_usage: RenderAssetUsages::RENDER_WORLD,
        ..default()
    });

    commands.spawn((
        Mesh2d(meshes.add(Rectangle::new(
            map_size_in_pixel as f32,
            map_size_in_pixel as f32,
        ))),
        MeshMaterial2d(materials.add(TileMapMaterial {
            map_size: UVec2::splat(MAP_SIZE),
            tile_size: TILE_SIZE,
            atlas_texture: tileset_handle.0.clone(),
            atlas_size: tile_set.size().as_vec2(),
            map_tiles: buffer,
        })),
    ));
}

#[derive(Debug, Resource)]
struct TileSetHandle(Handle<Image>);

@bas-ie
Copy link
Author

bas-ie commented Nov 30, 2024

Fantastic, I was trying to come up with a simple example of this!

I'm just rolling through a 0.15 migration for hexx, then I'll swim back downstream to this one 😆


fn spawn_map(mut commands: Commands, mut meshes: ResMut<Assets<Mesh>>) {
let map_size = UVec2::new(5, 5);
let max_chunk_size = UVec2::new(1, 1);
Copy link
Owner

@NoahShomette NoahShomette Dec 1, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT: Make the chunk sizes larger

Suggested change
let max_chunk_size = UVec2::new(1, 1);
let max_chunk_size = UVec2::new(3, 3);

@@ -40,7 +40,7 @@ impl MapData for HexMapData {

fn break_data_vecs_down_into_chunk_data<TileData>(
&self,
data: &Vec<Vec<TileData>>,
data: &[Vec<TileData>],
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain what this change does? I saw that its made throughout the project and everything is working great but I would like to understand the difference that it makes.

@@ -19,3 +19,9 @@ pub enum TilemapManagerError {
#[error("TileData does not exist for the given ChunkCell")]
TileDataDoesNotExist,
}

impl<'w> From<QueryEntityError<'w>> for TilemapManagerError {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain what this change does? Is there a reason to not use the #[from] generated impl?

@NoahShomette
Copy link
Owner

@notmd Thank you very much for that example! I'm gonna put together a repo for rendering examples and I will include that in it. I'd like to keep this crate itself free from dealing with rendering as its way outside my wheelbox and I want to maintain the capability of updating it easily in the future.

@bas-ie Did a quick pass through and everything seems great! Should be good to publish it whenever dependencies are updated and this pr can actually be merged. Thank you so much for doing the update!

@bas-ie
Copy link
Author

bas-ie commented Dec 2, 2024

Yep, almost there, have all but finished the hexx PR, then will do lettuces, then we're good to go!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants