A whole lot of garbage
This commit is contained in:
+6539
File diff suppressed because it is too large
Load Diff
Executable
+6
@@ -0,0 +1,6 @@
|
|||||||
|
[workspace]
|
||||||
|
resolver = "3"
|
||||||
|
members = ["app","rendering", "terrain"]
|
||||||
|
|
||||||
|
[profile.dev.package."*"]
|
||||||
|
opt-level = 3
|
||||||
Executable
+1
@@ -0,0 +1 @@
|
|||||||
|
/target
|
||||||
Executable
+13
@@ -0,0 +1,13 @@
|
|||||||
|
[package]
|
||||||
|
name = "voxtech"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
# voxtech_terrain = { path = "../terrain" }
|
||||||
|
# voxtech_rendering = { path = "../rendering" }
|
||||||
|
|
||||||
|
tracing-subscriber = "0.3.23"
|
||||||
|
tracing = { version = "0.1.44", features = ["log"] }
|
||||||
|
|
||||||
|
bevy = { version = "0.19.0-rc.2", features = ["dynamic_linking"] }
|
||||||
Executable
+62
@@ -0,0 +1,62 @@
|
|||||||
|
use std::range::Range;
|
||||||
|
|
||||||
|
use bevy::{
|
||||||
|
DefaultPlugins,
|
||||||
|
asset::io::embedded::GetAssetServer,
|
||||||
|
core_pipeline::core_3d::*,
|
||||||
|
ecs::{component::Component, query::*, system::SystemParamItem},
|
||||||
|
mesh::Mesh3d,
|
||||||
|
pbr::{SetMeshBindGroup, SetMeshViewBindGroup, SetMeshViewEmptyBindGroup},
|
||||||
|
prelude::*,
|
||||||
|
render::{
|
||||||
|
RenderSystems::Queue,
|
||||||
|
render_phase::*,
|
||||||
|
render_resource::*,
|
||||||
|
settings::{Backends, RenderCreation, WgpuSettings},
|
||||||
|
*,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
fn main() -> AppExit {
|
||||||
|
let mut app = App::default();
|
||||||
|
app.add_plugins(DefaultPlugins.set(RenderPlugin {
|
||||||
|
render_creation: RenderCreation::Automatic(Box::new(WgpuSettings {
|
||||||
|
backends: Some(Backends::VULKAN | Backends::GL),
|
||||||
|
..Default::default()
|
||||||
|
})),
|
||||||
|
..Default::default()
|
||||||
|
}));
|
||||||
|
app.add_systems(Startup, setup);
|
||||||
|
|
||||||
|
app.run()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// set up a simple 3D scene
|
||||||
|
fn setup(
|
||||||
|
mut commands: Commands,
|
||||||
|
mut meshes: ResMut<Assets<Mesh>>,
|
||||||
|
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||||
|
) {
|
||||||
|
// circular base
|
||||||
|
commands.spawn((
|
||||||
|
Mesh3d(meshes.add(Circle::new(4.0))),
|
||||||
|
MeshMaterial3d(materials.add(Color::WHITE)),
|
||||||
|
Transform::from_rotation(Quat::from_rotation_x(-std::f32::consts::FRAC_PI_2)),
|
||||||
|
));
|
||||||
|
// cube
|
||||||
|
commands.spawn((
|
||||||
|
Mesh3d(meshes.add(Cuboid::new(1.0, 1.0, 1.0))),
|
||||||
|
MeshMaterial3d(materials.add(Color::srgb_u8(124, 144, 255))),
|
||||||
|
Transform::from_xyz(0.0, 0.5, 0.0),
|
||||||
|
));
|
||||||
|
// light
|
||||||
|
commands.spawn((
|
||||||
|
PointLight { ..default() },
|
||||||
|
Transform::from_xyz(4.0, 8.0, 4.0),
|
||||||
|
));
|
||||||
|
// camera
|
||||||
|
commands.spawn((
|
||||||
|
Camera3d::default(),
|
||||||
|
Transform::from_xyz(-2.5, 4.5, 9.0).looking_at(Vec3::ZERO, Vec3::Y),
|
||||||
|
));
|
||||||
|
}
|
||||||
Executable
+1
@@ -0,0 +1 @@
|
|||||||
|
/target
|
||||||
Executable
+21
@@ -0,0 +1,21 @@
|
|||||||
|
[package]
|
||||||
|
name = "voxtech_rendering"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
voxtech_terrain = { path = "../terrain" }
|
||||||
|
|
||||||
|
bytemuck = "1.25.0"
|
||||||
|
glam = "0.33.0"
|
||||||
|
modular-bitfield = "0.13.1"
|
||||||
|
pollster = "0.4.0"
|
||||||
|
tracing = "0.1.44"
|
||||||
|
wgpu = { version = "29.0.3", default-features = false, features = ["vulkan", "std", "wgsl", "webgpu"] }
|
||||||
|
winit = "0.30.13"
|
||||||
|
|
||||||
|
bevy_ecs = "0.18.1"
|
||||||
|
bevy_app = "0.18.1"
|
||||||
|
rayon = "1.12.0"
|
||||||
|
crossbeam = "0.8.4"
|
||||||
|
rangemap = "1.7.1"
|
||||||
Executable
+220
@@ -0,0 +1,220 @@
|
|||||||
|
use bevy_app::{App, Plugin, PostUpdate};
|
||||||
|
use bevy_ecs::{
|
||||||
|
component::Component,
|
||||||
|
entity::Entity,
|
||||||
|
query::{With, Without},
|
||||||
|
resource::Resource,
|
||||||
|
schedule::IntoScheduleConfigs,
|
||||||
|
system::{Commands, Query, Res, ResMut},
|
||||||
|
};
|
||||||
|
use crossbeam::channel::{Receiver, Sender};
|
||||||
|
use std::{ops::Range, sync::Arc, thread::JoinHandle, time::Instant};
|
||||||
|
use tracing::debug;
|
||||||
|
use voxtech_terrain::chunk::{ChunkData, SIZE, UnsafeChunkData};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
chunk::renderer::{ChunkRenderer, Face, FaceColour, FaceDirection},
|
||||||
|
plugin::RenderPluginState,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Component)]
|
||||||
|
pub(crate) struct ChunkMesh {
|
||||||
|
range: Range<usize>,
|
||||||
|
last_update: Instant,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Component)]
|
||||||
|
pub(crate) struct ChunkMeshDirty;
|
||||||
|
|
||||||
|
pub(crate) struct ChunkMesher {
|
||||||
|
pub thread_count: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Plugin for ChunkMesher {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
app.insert_resource(ChunkMesherState::new(self.thread_count));
|
||||||
|
app.add_systems(PostUpdate, tag_not_meshed);
|
||||||
|
app.add_systems(PostUpdate, schedule_dirty.after(tag_not_meshed));
|
||||||
|
app.add_systems(PostUpdate, submit_meshed.after(schedule_dirty));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cleanup(&self, app: &mut App) {
|
||||||
|
app.world_mut().remove_resource::<ChunkMesherState>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MeshRequest {
|
||||||
|
time: Instant,
|
||||||
|
entity: Entity,
|
||||||
|
voxels: Box<UnsafeChunkData>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MeshResult {
|
||||||
|
time: Instant,
|
||||||
|
entity: Entity,
|
||||||
|
faces: Vec<Face>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Resource)]
|
||||||
|
struct ChunkMesherState {
|
||||||
|
sender: Sender<MeshRequest>,
|
||||||
|
receiver: Receiver<MeshResult>,
|
||||||
|
threads: Vec<JoinHandle<()>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ChunkMesherState {
|
||||||
|
pub fn new(thread_count: usize) -> Self {
|
||||||
|
let (request_sender, request_receiver) = crossbeam::channel::unbounded();
|
||||||
|
let request_receiver = Arc::new(request_receiver);
|
||||||
|
|
||||||
|
let (result_sender, result_receiver) = crossbeam::channel::unbounded();
|
||||||
|
let result_sender = Arc::new(result_sender);
|
||||||
|
|
||||||
|
let mut threads = Vec::with_capacity(thread_count);
|
||||||
|
|
||||||
|
for _ in 0..thread_count {
|
||||||
|
let sender = result_sender.clone();
|
||||||
|
let receiver = request_receiver.clone();
|
||||||
|
threads.push(std::thread::spawn(move || {
|
||||||
|
loop {
|
||||||
|
let Ok(MeshRequest {
|
||||||
|
time,
|
||||||
|
entity,
|
||||||
|
voxels,
|
||||||
|
}) = receiver.recv()
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
debug!("Meshing {entity:?}...");
|
||||||
|
let now = Instant::now();
|
||||||
|
let mut faces = vec![];
|
||||||
|
generate_chunk_mesh::<{ FaceDirection::Front as usize }>(&voxels, &mut faces);
|
||||||
|
|
||||||
|
let elapsed = now.elapsed();
|
||||||
|
debug!("Meshed {entity:?} in {:?} ({} faces)", elapsed, faces.len());
|
||||||
|
|
||||||
|
if !sender
|
||||||
|
.send(MeshResult {
|
||||||
|
time,
|
||||||
|
entity,
|
||||||
|
faces,
|
||||||
|
})
|
||||||
|
.is_ok()
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
Self {
|
||||||
|
sender: request_sender,
|
||||||
|
receiver: result_receiver,
|
||||||
|
threads,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tag_not_meshed(
|
||||||
|
mut commands: Commands,
|
||||||
|
query: Query<Entity, (With<ChunkData>, Without<ChunkMesh>)>,
|
||||||
|
) {
|
||||||
|
for entity in query.iter() {
|
||||||
|
commands.entity(entity).insert((
|
||||||
|
ChunkMeshDirty,
|
||||||
|
ChunkMesh {
|
||||||
|
range: 0..0,
|
||||||
|
last_update: Instant::now(),
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn schedule_dirty(
|
||||||
|
mut commands: Commands,
|
||||||
|
state: Res<ChunkMesherState>,
|
||||||
|
query: Query<(Entity, &mut ChunkData), With<ChunkMeshDirty>>,
|
||||||
|
) {
|
||||||
|
for (entity, data) in query.iter() {
|
||||||
|
let now = Instant::now();
|
||||||
|
unsafe {
|
||||||
|
let data = data.as_unsafe();
|
||||||
|
let mut copy = Box::<UnsafeChunkData>::new_uninit();
|
||||||
|
std::ptr::copy(data, copy.as_mut_ptr(), 1);
|
||||||
|
state
|
||||||
|
.sender
|
||||||
|
.send(MeshRequest {
|
||||||
|
time: Instant::now(),
|
||||||
|
entity,
|
||||||
|
voxels: copy.assume_init(),
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
commands.entity(entity).remove::<ChunkMeshDirty>();
|
||||||
|
}
|
||||||
|
let elapsed = now.elapsed();
|
||||||
|
debug!("Created snapshot of {entity:?} in {:?}", elapsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn submit_meshed(mut commands: Commands, state: Res<ChunkMesherState>) {
|
||||||
|
if state.receiver.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// let mut world = plugin_state.world.lock().unwrap();
|
||||||
|
// let Some(mut renderer) = world.get_resource_mut::<ChunkRenderer>() else {
|
||||||
|
// return;
|
||||||
|
// };
|
||||||
|
|
||||||
|
// for MeshResult {
|
||||||
|
// time,
|
||||||
|
// entity,
|
||||||
|
// faces,
|
||||||
|
// } in state.receiver.try_iter()
|
||||||
|
// {
|
||||||
|
// commands.entity(entity).insert(ChunkMesh {
|
||||||
|
// last_update: time,
|
||||||
|
// range: renderer.set_mesh(entity, faces),
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
const fn rotate_indices<const FACE: usize>([x, y, z]: [usize; 3]) -> [usize; 3] {
|
||||||
|
const TOP: usize = FaceDirection::Top as usize;
|
||||||
|
const BOTTOM: usize = FaceDirection::Bottom as usize;
|
||||||
|
const LEFT: usize = FaceDirection::Left as usize;
|
||||||
|
const RIGHT: usize = FaceDirection::Right as usize;
|
||||||
|
const FRONT: usize = FaceDirection::Front as usize;
|
||||||
|
const BACK: usize = FaceDirection::Back as usize;
|
||||||
|
match FACE {
|
||||||
|
TOP => todo!(),
|
||||||
|
BOTTOM => todo!(),
|
||||||
|
LEFT => todo!(),
|
||||||
|
RIGHT => todo!(),
|
||||||
|
FRONT => [x, y, z],
|
||||||
|
BACK => todo!(),
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_chunk_mesh<const FACE: usize>(voxels: &UnsafeChunkData, faces: &mut Vec<Face>) {
|
||||||
|
for z in 0..SIZE {
|
||||||
|
for y in 0..SIZE {
|
||||||
|
for x in 0..SIZE {
|
||||||
|
if voxels[rotate_indices::<FACE>([x, y, z])] == 0 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if z != 0 && voxels[rotate_indices::<FACE>([x, y, z - 1])] != 0 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
faces.push(
|
||||||
|
Face::new()
|
||||||
|
.with_x(x as _)
|
||||||
|
.with_y(y as _)
|
||||||
|
.with_colour(FaceColour::new().with_r(255)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Executable
+2
@@ -0,0 +1,2 @@
|
|||||||
|
pub mod mesh_gen;
|
||||||
|
pub mod renderer;
|
||||||
Executable
+283
@@ -0,0 +1,283 @@
|
|||||||
|
use std::{ops::Range, sync::Arc};
|
||||||
|
|
||||||
|
use crate::state::{FrameResources, RenderState};
|
||||||
|
use bevy_ecs::{
|
||||||
|
entity::Entity,
|
||||||
|
resource::Resource,
|
||||||
|
system::{Res, ResMut},
|
||||||
|
};
|
||||||
|
use bytemuck::{Pod, Zeroable};
|
||||||
|
use modular_bitfield::{Specifier, bitfield, specifiers::*};
|
||||||
|
use rangemap::RangeSet;
|
||||||
|
use tracing::error;
|
||||||
|
use voxtech_terrain::chunk::ChunkId;
|
||||||
|
use wgpu::{
|
||||||
|
naga::FastHashMap,
|
||||||
|
util::{BufferInitDescriptor, DeviceExt, RenderEncoder},
|
||||||
|
*,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Specifier, Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
#[bits = 3]
|
||||||
|
pub enum FaceDirection {
|
||||||
|
Top,
|
||||||
|
Bottom,
|
||||||
|
Left,
|
||||||
|
Right,
|
||||||
|
Front,
|
||||||
|
Back,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bitfield]
|
||||||
|
#[derive(Specifier, Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
pub struct FaceColour {
|
||||||
|
pub r: u8,
|
||||||
|
pub g: u8,
|
||||||
|
pub b: u8,
|
||||||
|
pub a: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[bitfield]
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Zeroable, Pod)]
|
||||||
|
pub struct Face {
|
||||||
|
pub face: FaceDirection,
|
||||||
|
pub x: B5,
|
||||||
|
pub y: B5,
|
||||||
|
pub z: B5,
|
||||||
|
pub w: B5,
|
||||||
|
pub h: B5,
|
||||||
|
pub meta: B4,
|
||||||
|
pub colour: FaceColour,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Debug, Clone, Copy, Zeroable, Pod)]
|
||||||
|
pub struct Vertex {
|
||||||
|
pub position: [f32; 2],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Vertex {
|
||||||
|
pub fn square() -> [Self; 4] {
|
||||||
|
[
|
||||||
|
Self {
|
||||||
|
position: [0.0, 1.0],
|
||||||
|
},
|
||||||
|
Self {
|
||||||
|
position: [0.0, 0.0],
|
||||||
|
},
|
||||||
|
Self {
|
||||||
|
position: [1.0, 1.0],
|
||||||
|
},
|
||||||
|
Self {
|
||||||
|
position: [1.0, 0.0],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pipeline(device: &Device, surface_config: &SurfaceConfiguration) -> RenderPipeline {
|
||||||
|
let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||||
|
label: None,
|
||||||
|
entries: &[BindGroupLayoutEntry {
|
||||||
|
binding: 0,
|
||||||
|
visibility: ShaderStages::VERTEX_FRAGMENT,
|
||||||
|
ty: BindingType::Buffer {
|
||||||
|
ty: BufferBindingType::Storage { read_only: true },
|
||||||
|
has_dynamic_offset: false,
|
||||||
|
min_binding_size: None,
|
||||||
|
},
|
||||||
|
count: None,
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||||
|
label: None,
|
||||||
|
bind_group_layouts: &[Some(&bind_group_layout)],
|
||||||
|
immediate_size: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
let shader = device.create_shader_module(wgpu::include_wgsl!("shader.wgsl"));
|
||||||
|
|
||||||
|
let vertex_buffers = [wgpu::VertexBufferLayout {
|
||||||
|
array_stride: size_of::<Self>() as wgpu::BufferAddress,
|
||||||
|
step_mode: wgpu::VertexStepMode::Vertex,
|
||||||
|
attributes: &[wgpu::VertexAttribute {
|
||||||
|
format: wgpu::VertexFormat::Float32x2,
|
||||||
|
offset: 0,
|
||||||
|
shader_location: 0,
|
||||||
|
}],
|
||||||
|
}];
|
||||||
|
|
||||||
|
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||||
|
label: Some("chunk_renderer"),
|
||||||
|
layout: Some(&pipeline_layout),
|
||||||
|
vertex: wgpu::VertexState {
|
||||||
|
module: &shader,
|
||||||
|
entry_point: Some("vs_main"),
|
||||||
|
compilation_options: Default::default(),
|
||||||
|
buffers: &vertex_buffers,
|
||||||
|
},
|
||||||
|
fragment: Some(wgpu::FragmentState {
|
||||||
|
module: &shader,
|
||||||
|
entry_point: Some("fs_main"),
|
||||||
|
compilation_options: Default::default(),
|
||||||
|
targets: &[
|
||||||
|
Some(surface_config.view_formats[0].into()),
|
||||||
|
Some(TextureFormat::R32Uint.into()),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
primitive: wgpu::PrimitiveState {
|
||||||
|
cull_mode: Some(wgpu::Face::Back),
|
||||||
|
topology: PrimitiveTopology::TriangleStrip,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
depth_stencil: None,
|
||||||
|
multisample: wgpu::MultisampleState::default(),
|
||||||
|
multiview_mask: None,
|
||||||
|
cache: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Resource)]
|
||||||
|
pub struct ChunkRenderer {
|
||||||
|
vertex_buffer: Buffer,
|
||||||
|
face_buffer: Arc<Buffer>,
|
||||||
|
bind_group: BindGroup,
|
||||||
|
pipeline: RenderPipeline,
|
||||||
|
id_texture: Texture,
|
||||||
|
free: RangeSet<usize>,
|
||||||
|
used: FastHashMap<ChunkId, Range<usize>>,
|
||||||
|
writes: Vec<(Range<u64>, Vec<Face>)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ChunkRenderer {
|
||||||
|
pub fn new(device: &Device, surface_config: &SurfaceConfiguration) -> Self {
|
||||||
|
const MAX_FACE_COUNT: usize = 100000;
|
||||||
|
let vertex_data = Vertex::square();
|
||||||
|
let pipeline = Vertex::pipeline(device, surface_config);
|
||||||
|
let vertex_buffer = device.create_buffer_init(&BufferInitDescriptor {
|
||||||
|
label: Some("Vertex Buffer"),
|
||||||
|
contents: bytemuck::cast_slice(&vertex_data),
|
||||||
|
usage: BufferUsages::VERTEX,
|
||||||
|
});
|
||||||
|
let face_buffer = device.create_buffer(&BufferDescriptor {
|
||||||
|
label: Some("Face Buffer"),
|
||||||
|
size: (MAX_FACE_COUNT * size_of::<Face>()) as u64,
|
||||||
|
mapped_at_creation: false,
|
||||||
|
usage: BufferUsages::STORAGE | BufferUsages::COPY_DST,
|
||||||
|
});
|
||||||
|
let bind_group = device.create_bind_group(&BindGroupDescriptor {
|
||||||
|
label: Some("Chunk bindings"),
|
||||||
|
layout: &pipeline.get_bind_group_layout(0),
|
||||||
|
entries: &[BindGroupEntry {
|
||||||
|
binding: 0,
|
||||||
|
resource: BindingResource::Buffer(BufferBinding {
|
||||||
|
buffer: &face_buffer,
|
||||||
|
offset: 0,
|
||||||
|
size: None,
|
||||||
|
}),
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
|
||||||
|
Self {
|
||||||
|
vertex_buffer,
|
||||||
|
face_buffer: face_buffer.into(),
|
||||||
|
pipeline,
|
||||||
|
bind_group,
|
||||||
|
id_texture: device.create_texture(&TextureDescriptor {
|
||||||
|
label: Some("Chunk visibility texture"),
|
||||||
|
size: Extent3d {
|
||||||
|
width: surface_config.width,
|
||||||
|
height: surface_config.height,
|
||||||
|
depth_or_array_layers: 1,
|
||||||
|
},
|
||||||
|
mip_level_count: 1,
|
||||||
|
sample_count: 1,
|
||||||
|
dimension: TextureDimension::D2,
|
||||||
|
format: TextureFormat::R32Uint,
|
||||||
|
usage: TextureUsages::RENDER_ATTACHMENT,
|
||||||
|
view_formats: &[],
|
||||||
|
}),
|
||||||
|
free: RangeSet::from_iter([0..MAX_FACE_COUNT]),
|
||||||
|
used: FastHashMap::default(),
|
||||||
|
writes: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_mesh(&mut self, chunk: ChunkId, faces: Vec<Face>) -> Range<usize> {
|
||||||
|
for range in self.free.iter() {
|
||||||
|
if range.len() >= faces.len() {
|
||||||
|
let mut range = range.clone();
|
||||||
|
range.end = range.start + faces.len();
|
||||||
|
self.free.remove(range.clone());
|
||||||
|
self.used.entry(chunk).insert(range.clone());
|
||||||
|
|
||||||
|
let bounds = (range.start * size_of::<Face>()) as u64
|
||||||
|
..(range.end * size_of::<Face>()) as u64;
|
||||||
|
|
||||||
|
self.writes.push((bounds, faces));
|
||||||
|
return range;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
error!(
|
||||||
|
"Could not find a suitable buffer range for entity {chunk:?}. The chunk will not be rendered."
|
||||||
|
);
|
||||||
|
if let Some(range) = self.used.remove(&chunk) {
|
||||||
|
self.free.insert(range);
|
||||||
|
}
|
||||||
|
0..0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn render(
|
||||||
|
mut renderer: ResMut<ChunkRenderer>,
|
||||||
|
state: Res<RenderState>,
|
||||||
|
mut frame: ResMut<FrameResources>,
|
||||||
|
) {
|
||||||
|
for (range, faces) in std::mem::take(&mut renderer.writes) {
|
||||||
|
state.queue.write_buffer(
|
||||||
|
&renderer.face_buffer,
|
||||||
|
range.start,
|
||||||
|
bytemuck::cast_slice(&faces),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let view = frame.screen_view.clone();
|
||||||
|
let mut pass = frame
|
||||||
|
.command_encoder
|
||||||
|
.begin_render_pass(&RenderPassDescriptor {
|
||||||
|
label: Some("Render triangle"),
|
||||||
|
color_attachments: &[
|
||||||
|
Some(RenderPassColorAttachment {
|
||||||
|
view: &view,
|
||||||
|
depth_slice: None,
|
||||||
|
resolve_target: None,
|
||||||
|
ops: Operations {
|
||||||
|
load: LoadOp::DontCare(LoadOpDontCare::default()),
|
||||||
|
store: StoreOp::Store,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
Some(RenderPassColorAttachment {
|
||||||
|
view: &renderer
|
||||||
|
.id_texture
|
||||||
|
.create_view(&TextureViewDescriptor::default()),
|
||||||
|
depth_slice: None,
|
||||||
|
resolve_target: None,
|
||||||
|
ops: Operations {
|
||||||
|
load: LoadOp::DontCare(LoadOpDontCare::default()),
|
||||||
|
store: StoreOp::Store,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
depth_stencil_attachment: None,
|
||||||
|
timestamp_writes: None,
|
||||||
|
occlusion_query_set: None,
|
||||||
|
multiview_mask: None,
|
||||||
|
});
|
||||||
|
pass.set_bind_group(0, &renderer.bind_group, &[]);
|
||||||
|
pass.set_pipeline(&renderer.pipeline);
|
||||||
|
pass.set_vertex_buffer(0, renderer.vertex_buffer.slice(..));
|
||||||
|
for range in renderer.used.values() {
|
||||||
|
pass.draw(0..4, range.start as u32..range.end as u32);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Executable
+61
@@ -0,0 +1,61 @@
|
|||||||
|
enable draw_index;
|
||||||
|
|
||||||
|
struct VertexInput {
|
||||||
|
@location(0) position: vec2<f32>,
|
||||||
|
@builtin(instance_index) draw_id: u32,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct VertexOutput {
|
||||||
|
@location(0) albedo: vec4<f32>,
|
||||||
|
@builtin(position) position: vec4<f32>,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Face {
|
||||||
|
transform: u32,
|
||||||
|
colour: u32,
|
||||||
|
};
|
||||||
|
|
||||||
|
@group(0) @binding(0)
|
||||||
|
var<storage, read> faces: array<Face>;
|
||||||
|
|
||||||
|
@vertex
|
||||||
|
fn vs_main(
|
||||||
|
input: VertexInput
|
||||||
|
) -> VertexOutput {
|
||||||
|
let face = faces[input.draw_id];
|
||||||
|
let transform = face.transform;
|
||||||
|
let direction = transform & 0x07;
|
||||||
|
let x = f32((transform & 0x00000F8) >> 3);
|
||||||
|
let y = f32((transform & 0x0001F00) >> 8);
|
||||||
|
let z = f32((transform & 0x003E000) >> 13);
|
||||||
|
let w = f32((transform & 0x07C0000) >> 18);
|
||||||
|
let h = f32((transform & 0xF800000) >> 23);
|
||||||
|
|
||||||
|
var result: VertexOutput;
|
||||||
|
result.position = vec4(input.position.x + x, input.position.y + y, z, 1.0);
|
||||||
|
result.position.x += w * f32(input.position.x != 0);
|
||||||
|
result.position.y += h * f32(input.position.y != 0);
|
||||||
|
result.position = vec4(result.position.xy * 0.1, result.position.zw);
|
||||||
|
|
||||||
|
result.albedo = vec4(
|
||||||
|
f32(face.colour & 0x000000FF) / 255,
|
||||||
|
f32((face.colour & 0x0000FF00) >> 8) / 255,
|
||||||
|
f32((face.colour & 0x00FF0000) >> 16) / 255,
|
||||||
|
f32((face.colour & 0xFF000000) >> 24) / 255,
|
||||||
|
);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FragmentOutput {
|
||||||
|
@location(0) albedo: vec4<f32>,
|
||||||
|
@location(1) chunk_id: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
@fragment
|
||||||
|
fn fs_main(vertex: VertexOutput) -> FragmentOutput {
|
||||||
|
var output: FragmentOutput;
|
||||||
|
output.albedo = vertex.albedo;
|
||||||
|
output.chunk_id = 0;
|
||||||
|
return output;
|
||||||
|
}
|
||||||
Executable
+19
@@ -0,0 +1,19 @@
|
|||||||
|
pub mod chunk;
|
||||||
|
pub mod plugin;
|
||||||
|
pub mod state;
|
||||||
|
|
||||||
|
#[derive(
|
||||||
|
Debug,
|
||||||
|
Default,
|
||||||
|
Clone,
|
||||||
|
Copy,
|
||||||
|
PartialEq,
|
||||||
|
Eq,
|
||||||
|
Hash,
|
||||||
|
bevy_ecs::schedule::ScheduleLabel,
|
||||||
|
bevy_app::AppLabel,
|
||||||
|
)]
|
||||||
|
pub struct Render;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, bevy_ecs::message::Message)]
|
||||||
|
pub struct WindowEvent(pub winit::event::WindowEvent);
|
||||||
Executable
+140
@@ -0,0 +1,140 @@
|
|||||||
|
use crate::{
|
||||||
|
Render, WindowEvent,
|
||||||
|
chunk::{self, renderer::ChunkRenderer},
|
||||||
|
state::RenderState,
|
||||||
|
};
|
||||||
|
use bevy_app::{App, Plugin, Update};
|
||||||
|
use bevy_ecs::{resource::Resource, schedule::Schedule};
|
||||||
|
use std::{
|
||||||
|
ops::{Deref, DerefMut},
|
||||||
|
sync::Arc,
|
||||||
|
thread::JoinHandle,
|
||||||
|
};
|
||||||
|
use tracing::info;
|
||||||
|
use winit::{
|
||||||
|
application::ApplicationHandler,
|
||||||
|
dpi::{PhysicalSize, Size},
|
||||||
|
event::WindowEvent as WinitWindowEvent,
|
||||||
|
event_loop::EventLoop,
|
||||||
|
platform::wayland::EventLoopBuilderExtWayland,
|
||||||
|
window::Window,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct RenderPlugin;
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
#[derive(Resource)]
|
||||||
|
pub struct RenderPluginState {
|
||||||
|
thread: JoinHandle<()>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RenderPluginState {
|
||||||
|
pub fn is_running(&self) -> bool {
|
||||||
|
!self.thread.is_finished()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Plugin for RenderPlugin {
|
||||||
|
fn build(&self, app: &mut bevy_app::App) {
|
||||||
|
info!("Registering render plugin...");
|
||||||
|
app.add_message::<WindowEvent>();
|
||||||
|
|
||||||
|
let thread = std::thread::spawn(move || {
|
||||||
|
let mut app = App::new();
|
||||||
|
app.add_systems(Update, RenderState::handle_window_events);
|
||||||
|
app.add_schedule({
|
||||||
|
let mut schedule = Schedule::new(Render);
|
||||||
|
schedule.add_systems(ChunkRenderer::render);
|
||||||
|
schedule
|
||||||
|
});
|
||||||
|
|
||||||
|
let event_loop = EventLoop::builder().with_any_thread(true).build().unwrap();
|
||||||
|
event_loop.set_control_flow(winit::event_loop::ControlFlow::Poll);
|
||||||
|
event_loop.run_app(&mut RenderApp(app)).unwrap();
|
||||||
|
info!("Render thread stopped");
|
||||||
|
});
|
||||||
|
|
||||||
|
app.insert_resource(RenderPluginState { thread });
|
||||||
|
app.add_schedule(Schedule::new(Render));
|
||||||
|
app.add_plugins(chunk::mesh_gen::ChunkMesher { thread_count: 1 });
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cleanup(&self, app: &mut bevy_app::App) {
|
||||||
|
let state = app
|
||||||
|
.world_mut()
|
||||||
|
.remove_resource::<RenderPluginState>()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
state.thread.join().unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct RenderApp(App);
|
||||||
|
|
||||||
|
impl Deref for RenderApp {
|
||||||
|
type Target = App;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DerefMut for RenderApp {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
&mut self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ApplicationHandler for RenderApp {
|
||||||
|
fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
|
||||||
|
info!("Creating render state...");
|
||||||
|
let attributes = Window::default_attributes()
|
||||||
|
.with_min_inner_size(Size::Physical(PhysicalSize {
|
||||||
|
width: 1280,
|
||||||
|
height: 720,
|
||||||
|
}))
|
||||||
|
.with_decorations(true)
|
||||||
|
.with_visible(true);
|
||||||
|
|
||||||
|
let window = Arc::new(event_loop.create_window(attributes).unwrap());
|
||||||
|
let state = pollster::block_on(RenderState::new(
|
||||||
|
event_loop.owned_display_handle(),
|
||||||
|
window.clone(),
|
||||||
|
));
|
||||||
|
self.insert_resource(ChunkRenderer::new(
|
||||||
|
&state.device,
|
||||||
|
&state.surface.get_configuration().unwrap(),
|
||||||
|
));
|
||||||
|
self.insert_resource(state);
|
||||||
|
window.request_redraw();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn window_event(
|
||||||
|
&mut self,
|
||||||
|
event_loop: &winit::event_loop::ActiveEventLoop,
|
||||||
|
_: winit::window::WindowId,
|
||||||
|
event: WinitWindowEvent,
|
||||||
|
) {
|
||||||
|
let world = self.world_mut();
|
||||||
|
|
||||||
|
match event {
|
||||||
|
WinitWindowEvent::CloseRequested => {
|
||||||
|
info!("The close button was pressed; stopping");
|
||||||
|
world.remove_resource::<RenderState>();
|
||||||
|
event_loop.exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
WinitWindowEvent::RedrawRequested => {
|
||||||
|
world.run_schedule(Update);
|
||||||
|
world.run_system_cached(RenderState::frame_begin).unwrap();
|
||||||
|
world.run_schedule(Render);
|
||||||
|
world.run_system_cached(RenderState::frame_present).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => {
|
||||||
|
world.write_message(WindowEvent(event.clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Executable
+183
@@ -0,0 +1,183 @@
|
|||||||
|
use bevy_ecs::{
|
||||||
|
message::MessageReader, resource::Resource, schedule::ScheduleLabel, system::ResMut,
|
||||||
|
world::World,
|
||||||
|
};
|
||||||
|
use std::sync::Arc;
|
||||||
|
use wgpu::*;
|
||||||
|
use winit::{event_loop::OwnedDisplayHandle, window::Window};
|
||||||
|
|
||||||
|
use crate::WindowEvent;
|
||||||
|
|
||||||
|
#[derive(Resource)]
|
||||||
|
pub struct RenderState {
|
||||||
|
pub(crate) instance: Instance,
|
||||||
|
pub(crate) window: Arc<Window>,
|
||||||
|
pub(crate) device: Device,
|
||||||
|
pub(crate) queue: Queue,
|
||||||
|
pub(crate) size: winit::dpi::PhysicalSize<u32>,
|
||||||
|
pub(crate) surface: Surface<'static>,
|
||||||
|
pub(crate) surface_format: TextureFormat,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RenderState {
|
||||||
|
pub async fn new(display: OwnedDisplayHandle, window: Arc<Window>) -> Self {
|
||||||
|
let instance = Instance::new({
|
||||||
|
let mut descriptor = InstanceDescriptor::new_with_display_handle(Box::new(display));
|
||||||
|
descriptor.backends = Backends::VULKAN | Backends::GL;
|
||||||
|
descriptor
|
||||||
|
});
|
||||||
|
let adapter = instance
|
||||||
|
.request_adapter(&RequestAdapterOptions::default())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let (device, queue) = adapter
|
||||||
|
.request_device(&DeviceDescriptor {
|
||||||
|
required_features: Features::MULTI_DRAW_INDIRECT_COUNT
|
||||||
|
| Features::SHADER_DRAW_INDEX,
|
||||||
|
..DeviceDescriptor::default()
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let size = window.inner_size();
|
||||||
|
|
||||||
|
let surface = instance.create_surface(window.clone()).unwrap();
|
||||||
|
let cap = surface.get_capabilities(&adapter);
|
||||||
|
let surface_format = cap.formats[0];
|
||||||
|
|
||||||
|
let mut state = Self {
|
||||||
|
instance,
|
||||||
|
window,
|
||||||
|
device,
|
||||||
|
queue,
|
||||||
|
size,
|
||||||
|
surface,
|
||||||
|
surface_format,
|
||||||
|
};
|
||||||
|
|
||||||
|
state.configure_surface();
|
||||||
|
state
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn window(&self) -> &Window {
|
||||||
|
&self.window
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>) {
|
||||||
|
self.size = new_size;
|
||||||
|
self.configure_surface();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn configure_surface(&mut self) {
|
||||||
|
let surface_config = SurfaceConfiguration {
|
||||||
|
usage: TextureUsages::RENDER_ATTACHMENT,
|
||||||
|
format: self.surface_format,
|
||||||
|
view_formats: vec![self.surface_format.add_srgb_suffix()],
|
||||||
|
alpha_mode: CompositeAlphaMode::Auto,
|
||||||
|
width: self.size.width,
|
||||||
|
height: self.size.height,
|
||||||
|
desired_maximum_frame_latency: 2,
|
||||||
|
present_mode: PresentMode::AutoVsync,
|
||||||
|
};
|
||||||
|
self.surface.configure(&self.device, &surface_config);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, ScheduleLabel)]
|
||||||
|
pub enum RenderPhase {
|
||||||
|
Begin,
|
||||||
|
Draw,
|
||||||
|
Present,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait RenderPass {
|
||||||
|
fn pipeline_id(&self) -> &str;
|
||||||
|
fn draw(&self, encoder: &mut CommandEncoder, texture_view: &TextureView);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Resource)]
|
||||||
|
pub struct FrameResources {
|
||||||
|
pub screen_view: TextureView,
|
||||||
|
pub screen_texture: SurfaceTexture,
|
||||||
|
pub command_encoder: CommandEncoder,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RenderState {
|
||||||
|
pub(crate) fn frame_begin(world: &mut World) {
|
||||||
|
let mut state = world.resource_mut::<RenderState>();
|
||||||
|
let surface_texture = match state.surface.get_current_texture() {
|
||||||
|
CurrentSurfaceTexture::Success(texture) => texture,
|
||||||
|
CurrentSurfaceTexture::Occluded | CurrentSurfaceTexture::Timeout => return,
|
||||||
|
CurrentSurfaceTexture::Suboptimal(texture) => {
|
||||||
|
drop(texture);
|
||||||
|
state.configure_surface();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
CurrentSurfaceTexture::Outdated => {
|
||||||
|
state.configure_surface();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
CurrentSurfaceTexture::Validation => {
|
||||||
|
unreachable!("No error scope registered, so validation errors will panic")
|
||||||
|
}
|
||||||
|
CurrentSurfaceTexture::Lost => {
|
||||||
|
state.surface = state.instance.create_surface(state.window.clone()).unwrap();
|
||||||
|
state.configure_surface();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let texture_view = surface_texture.texture.create_view(&TextureViewDescriptor {
|
||||||
|
format: Some(state.surface_format.add_srgb_suffix()),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut encoder = state.device.create_command_encoder(&Default::default());
|
||||||
|
{
|
||||||
|
let _clear_screen_pass = encoder.begin_render_pass(&RenderPassDescriptor {
|
||||||
|
label: None,
|
||||||
|
color_attachments: &[Some(RenderPassColorAttachment {
|
||||||
|
view: &texture_view,
|
||||||
|
depth_slice: None,
|
||||||
|
resolve_target: None,
|
||||||
|
ops: Operations {
|
||||||
|
load: LoadOp::Clear(Color::BLACK),
|
||||||
|
store: StoreOp::Store,
|
||||||
|
},
|
||||||
|
})],
|
||||||
|
depth_stencil_attachment: None,
|
||||||
|
timestamp_writes: None,
|
||||||
|
occlusion_query_set: None,
|
||||||
|
multiview_mask: None,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
world.insert_resource(FrameResources {
|
||||||
|
screen_texture: surface_texture,
|
||||||
|
screen_view: texture_view,
|
||||||
|
command_encoder: encoder,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn frame_present(world: &mut World) {
|
||||||
|
let resources = world.remove_resource::<FrameResources>().unwrap();
|
||||||
|
let state = world.resource_ref::<RenderState>();
|
||||||
|
state.queue.submit([resources.command_encoder.finish()]);
|
||||||
|
state.window.pre_present_notify();
|
||||||
|
resources.screen_texture.present();
|
||||||
|
state.window().request_redraw();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn handle_window_events(
|
||||||
|
mut reader: MessageReader<WindowEvent>,
|
||||||
|
mut state: ResMut<RenderState>,
|
||||||
|
) {
|
||||||
|
for event in reader.read() {
|
||||||
|
match event.0 {
|
||||||
|
winit::event::WindowEvent::Resized(size) => {
|
||||||
|
state.resize(size);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Executable
+9
@@ -0,0 +1,9 @@
|
|||||||
|
[package]
|
||||||
|
name = "voxtech_terrain"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
bevy_ecs = "0.18.1"
|
||||||
|
bevy_app = "0.18.1"
|
||||||
|
modular-bitfield = "0.13.1"
|
||||||
Executable
+93
@@ -0,0 +1,93 @@
|
|||||||
|
use std::{
|
||||||
|
ops::{Deref, DerefMut, Index, IndexMut},
|
||||||
|
sync::{Arc, atomic::AtomicU32},
|
||||||
|
};
|
||||||
|
|
||||||
|
use bevy_ecs::component::Component;
|
||||||
|
use modular_bitfield::{
|
||||||
|
bitfield,
|
||||||
|
specifiers::{B1, B21},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const SIZE: usize = 32;
|
||||||
|
pub const SIZE2: usize = SIZE * SIZE;
|
||||||
|
pub const SIZE3: usize = SIZE * SIZE2;
|
||||||
|
|
||||||
|
#[bitfield]
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Component)]
|
||||||
|
pub struct ChunkId {
|
||||||
|
x: B21,
|
||||||
|
y: B21,
|
||||||
|
z: B21,
|
||||||
|
b: B1,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Component)]
|
||||||
|
pub struct Chunk;
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
pub struct ChunkData(Arc<[AtomicU32; SIZE3]>);
|
||||||
|
|
||||||
|
impl ChunkData {
|
||||||
|
pub fn empty() -> Self {
|
||||||
|
unsafe { Self(Arc::new_zeroed().assume_init()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn solid(voxel_id: u32) -> Self {
|
||||||
|
let mut data = Arc::new_uninit();
|
||||||
|
let inner = Arc::get_mut(&mut data).unwrap();
|
||||||
|
let ptr = inner.as_mut_ptr() as *mut u32;
|
||||||
|
unsafe {
|
||||||
|
for i in 0..SIZE {
|
||||||
|
ptr.add(i).write(voxel_id);
|
||||||
|
}
|
||||||
|
Self(data.assume_init())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub unsafe fn as_unsafe(&self) -> &UnsafeChunkData {
|
||||||
|
unsafe { &*(self.0.as_ptr() as *const UnsafeChunkData) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Index<[usize; 3]> for ChunkData {
|
||||||
|
type Output = AtomicU32;
|
||||||
|
#[inline]
|
||||||
|
fn index(&self, [x, y, z]: [usize; 3]) -> &Self::Output {
|
||||||
|
&self.0[x + y * SIZE + z * SIZE2]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct UnsafeChunkData([u32; SIZE3]);
|
||||||
|
|
||||||
|
impl Deref for UnsafeChunkData {
|
||||||
|
type Target = [u32; SIZE3];
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DerefMut for UnsafeChunkData {
|
||||||
|
#[inline]
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
&mut self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Index<[usize; 3]> for UnsafeChunkData {
|
||||||
|
type Output = u32;
|
||||||
|
#[inline]
|
||||||
|
fn index(&self, [x, y, z]: [usize; 3]) -> &Self::Output {
|
||||||
|
&self.0[x + y * SIZE + z * SIZE2]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IndexMut<[usize; 3]> for UnsafeChunkData {
|
||||||
|
#[inline]
|
||||||
|
fn index_mut(&mut self, [x, y, z]: [usize; 3]) -> &mut Self::Output {
|
||||||
|
&mut self.0[x + y * SIZE + z * SIZE2]
|
||||||
|
}
|
||||||
|
}
|
||||||
Executable
+1
@@ -0,0 +1 @@
|
|||||||
|
pub mod chunk;
|
||||||
Reference in New Issue
Block a user