A whole lot of garbage

This commit is contained in:
Mia
2026-06-04 18:22:57 +02:00
commit 7e764095b9
17 changed files with 7654 additions and 0 deletions
+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);
}
_ => {}
}
}
}
}