A whole lot of garbage

This commit is contained in:
Mia
2026-06-04 18:22:57 +02:00
commit 8325862a33
17 changed files with 7742 additions and 0 deletions
Generated Executable
+6539
View File
File diff suppressed because it is too large Load Diff
Executable
+6
View File
@@ -0,0 +1,6 @@
[workspace]
resolver = "3"
members = ["app","rendering", "terrain"]
[profile.dev.package."*"]
opt-level = 3
Executable
+1
View File
@@ -0,0 +1 @@
/target
Executable
+13
View File
@@ -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"] }
+150
View File
@@ -0,0 +1,150 @@
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},
*,
},
};
#[derive(Component, Clone, Copy)]
struct CustomDrawComponent(Range<usize>);
type CustomDrawSequence = (
SetItemPipeline,
SetMeshViewBindGroup<0>,
SetMeshViewEmptyBindGroup<1>,
SetMeshBindGroup<2>,
CustomDrawCommand,
);
#[derive(Resource)]
pub struct CustomPipeline {
pub pipeline_id: CachedRenderPipelineId,
}
impl FromWorld for CustomPipeline {
fn from_world(world: &mut World) -> Self {
// 1. Get the pipeline cache
let pipeline_cache = world.resource::<PipelineCache>();
let shader = world.get_asset_server().load("shaders/shader.wgsl");
// 2. Define your pipeline descriptor (Shaders, vertex buffers, etc.)
let descriptor = RenderPipelineDescriptor {
label: Some("custom_draw_pipeline".into()),
layout: vec![], // Define your bind group layouts here if needed
vertex: VertexState {
shader: shader.clone(),
shader_defs: vec![],
entry_point: Some("vertex".into()),
buffers: vec![],
},
fragment: Some(FragmentState {
shader,
shader_defs: vec![],
entry_point: Some("fragment".into()),
targets: vec![Some(ColorTargetState {
format: TextureFormat::Bgra8UnormSrgb,
blend: Some(BlendState::REPLACE),
write_mask: ColorWrites::ALL,
})],
}),
primitive: PrimitiveState::default(),
depth_stencil: None,
multisample: MultisampleState::default(),
zero_initialize_workgroup_memory: false,
immediate_size: 0,
};
// 3. Queue the pipeline for compilation and store the ID
let pipeline_id = pipeline_cache.queue_render_pipeline(descriptor);
CustomPipeline { pipeline_id }
}
}
struct CustomDrawCommand;
impl<P: PhaseItem> RenderCommand<P> for CustomDrawCommand {
type Param = ();
type ViewQuery = ();
type ItemQuery = &'static CustomDrawComponent;
fn render<'w>(
_item: &P,
_view: ROQueryItem<'w, '_, Self::ViewQuery>,
entity: Option<ROQueryItem<'w, '_, Self::ItemQuery>>,
param: SystemParamItem<'w, '_, Self::Param>,
pass: &mut TrackedRenderPass<'w>,
) -> RenderCommandResult {
todo!()
}
}
fn enqueue_draws(
mut views: Query<&mut RenderPhase<Opaque3d>>,
polys: Query<Entity, With<MyPolyBatch>>,
draw_functions: Res<DrawFunctions<Opaque3d>>,
) {
}
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);
if let Some(app) = app.get_sub_app_mut(RenderApp) {
app.add_render_command::<Opaque3d, CustomDrawCommand>();
app.add_systems(Render, enqueue_draws.in_set(Queue));
}
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),
));
}
+1
View File
@@ -0,0 +1 @@
/target
+21
View File
@@ -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"
+220
View File
@@ -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)),
);
}
}
}
}
+2
View File
@@ -0,0 +1,2 @@
pub mod mesh_gen;
pub mod renderer;
+283
View File
@@ -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);
}
}
}
+61
View File
@@ -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;
}
+19
View File
@@ -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);
+140
View File
@@ -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()));
}
}
}
}
+183
View File
@@ -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);
}
_ => {}
}
}
}
}
+9
View File
@@ -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"
+93
View File
@@ -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]
}
}
+1
View File
@@ -0,0 +1 @@
pub mod chunk;