This commit is contained in:
2024-10-28 01:31:16 -04:00
commit 74bde02cfa
21 changed files with 4001 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/target

2410
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

12
Cargo.toml Normal file
View File

@@ -0,0 +1,12 @@
[package]
name = "fractal"
version = "0.1.0"
edition = "2021"
[dependencies]
winit = "0.30.5"
wgpu = "22.1.0"
nalgebra = "0.33.1"
pollster = "0.3.0"
bytemuck = "1.19.0"
num-traits = "0.2.19"

50
src/client/app.rs Normal file
View File

@@ -0,0 +1,50 @@
use winit::{application::ApplicationHandler, event_loop::ControlFlow};
use super::Client;
pub struct ClientApp<'a> {
client: Option<Client<'a>>,
}
impl ClientApp<'_> {
pub fn new() -> Self {
Self { client: None }
}
}
impl ApplicationHandler for ClientApp<'_> {
fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
if self.client.is_none() {
self.client = Some(Client::new(event_loop));
}
event_loop.set_control_flow(ControlFlow::Poll);
}
fn window_event(
&mut self,
_event_loop: &winit::event_loop::ActiveEventLoop,
_window_id: winit::window::WindowId,
event: winit::event::WindowEvent,
) {
if let Some(c) = self.client.as_mut() {
c.window_event(event)
}
}
fn device_event(
&mut self,
_event_loop: &winit::event_loop::ActiveEventLoop,
_device_id: winit::event::DeviceId,
event: winit::event::DeviceEvent,
) {
if let Some(c) = self.client.as_mut() {
c.input.update_device(event)
}
}
fn about_to_wait(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
if let Some(c) = self.client.as_mut() {
c.update(event_loop)
}
}
}

59
src/client/camera.rs Normal file
View File

@@ -0,0 +1,59 @@
use nalgebra::Vector2;
use std::ops::AddAssign;
use crate::util::FixedDec;
#[derive(Clone, Copy)]
pub struct Zoom {
scale: f32,
mult: f32,
}
#[derive(Clone)]
pub struct Camera {
pub pos: Vector2<FixedDec>,
pub zoom: Zoom,
}
impl Camera {
pub fn scale(&self, size: &Vector2<u32>) -> Vector2<f32> {
let fsize: Vector2<f32> = size.cast();
if size.x < size.y {
Vector2::new(fsize.x / fsize.y, 1.0)
} else {
Vector2::new(1.0, fsize.y / fsize.x)
}
}
}
impl Default for Camera {
fn default() -> Self {
Self {
pos: Vector2::zeros(),
zoom: Zoom::new(0.0),
}
}
}
impl Zoom {
pub fn new(scale: f32) -> Self {
Self {
scale,
mult: mult(scale),
}
}
pub fn mult(&self) -> f32 {
self.mult
}
}
impl AddAssign<f32> for Zoom {
fn add_assign(&mut self, rhs: f32) {
self.scale += rhs;
self.mult = mult(self.scale);
}
}
pub fn mult(scale: f32) -> f32 {
(-scale).exp2()
}

View File

@@ -0,0 +1,32 @@
use std::time::Duration;
use winit::keyboard::KeyCode as K;
use crate::util::FixedDec;
use super::Client;
impl Client<'_> {
pub fn handle_input(&mut self, delta: Duration) {
let Client { input, camera, .. } = self;
let per_sec = delta.as_secs_f32();
if input.scroll_delta != 0.0 {
// camera.zoom += input.scroll_delta / 5.0;
}
let speed = FixedDec::from(per_sec * 0.5);
if input.pressed(K::KeyW) {
camera.pos.y += &speed;
}
if input.pressed(K::KeyA) {
camera.pos.x -= &speed;
}
if input.pressed(K::KeyS) {
camera.pos.y -= &speed;
}
if input.pressed(K::KeyD) {
camera.pos.x += &speed;
}
}
}

134
src/client/input.rs Normal file
View File

@@ -0,0 +1,134 @@
use std::collections::HashSet;
use nalgebra::Vector2;
use winit::{
event::{DeviceEvent, ElementState, MouseButton, MouseScrollDelta, WindowEvent},
keyboard::{KeyCode, PhysicalKey},
};
pub struct Input {
pub mouse_pixel_pos: Vector2<f32>,
pub mouse_delta: Vector2<f32>,
pressed: HashSet<KeyCode>,
just_pressed: HashSet<KeyCode>,
mouse_pressed: HashSet<MouseButton>,
mouse_just_pressed: HashSet<MouseButton>,
mouse_just_released: HashSet<MouseButton>,
pub scroll_delta: f32,
}
impl Input {
pub fn new() -> Self {
Self {
mouse_pixel_pos: Vector2::zeros(),
mouse_delta: Vector2::zeros(),
pressed: HashSet::new(),
just_pressed: HashSet::new(),
mouse_pressed: HashSet::new(),
mouse_just_pressed: HashSet::new(),
mouse_just_released: HashSet::new(),
scroll_delta: 0.0,
}
}
pub fn update_device(&mut self, event: DeviceEvent) {
match event {
DeviceEvent::MouseWheel { delta } => {
self.scroll_delta = match delta {
MouseScrollDelta::LineDelta(_, v) => v,
MouseScrollDelta::PixelDelta(v) => (v.y / 2.0) as f32,
};
}
DeviceEvent::MouseMotion { delta } => {
self.mouse_delta += Vector2::new(delta.0 as f32, delta.1 as f32);
}
_ => (),
}
}
pub fn update_window(&mut self, event: WindowEvent) {
match event {
WindowEvent::KeyboardInput { event, .. } => {
let code = if let PhysicalKey::Code(code) = event.physical_key {
code
} else {
return;
};
match event.state {
ElementState::Pressed => {
self.just_pressed.insert(code);
self.pressed.insert(code);
}
ElementState::Released => {
self.pressed.remove(&code);
}
};
}
WindowEvent::CursorLeft { .. } => {
self.pressed.clear();
self.mouse_pressed.clear();
}
WindowEvent::CursorMoved { position, .. } => {
self.mouse_pixel_pos = Vector2::new(position.x as f32, position.y as f32);
}
WindowEvent::MouseInput { button, state, .. } => match state {
ElementState::Pressed => {
self.mouse_just_pressed.insert(button);
self.mouse_pressed.insert(button);
}
ElementState::Released => {
self.mouse_pressed.remove(&button);
self.mouse_just_released.insert(button);
}
},
WindowEvent::MouseWheel { delta, .. } => {
self.scroll_delta = match delta {
MouseScrollDelta::LineDelta(_, v) => v,
MouseScrollDelta::PixelDelta(v) => (v.y / 2.0) as f32,
};
}
_ => (),
}
}
pub fn end(&mut self) {
self.scroll_delta = 0.0;
self.mouse_delta = Vector2::zeros();
self.just_pressed.clear();
self.mouse_just_pressed.clear();
self.mouse_just_released.clear();
}
pub fn clear(&mut self) {
self.pressed.clear();
self.mouse_pressed.clear();
self.end();
}
#[allow(dead_code)]
pub fn pressed(&self, key: KeyCode) -> bool {
self.pressed.contains(&key)
}
#[allow(dead_code)]
pub fn just_pressed(&self, key: KeyCode) -> bool {
self.just_pressed.contains(&key)
}
#[allow(dead_code)]
pub fn mouse_pressed(&self, button: MouseButton) -> bool {
self.mouse_pressed.contains(&button)
}
#[allow(dead_code)]
pub fn mouse_just_pressed(&self, button: MouseButton) -> bool {
self.mouse_just_pressed.contains(&button)
}
#[allow(dead_code)]
pub fn mouse_just_released(&self, button: MouseButton) -> bool {
self.mouse_just_released.contains(&button)
}
}

73
src/client/mod.rs Normal file
View File

@@ -0,0 +1,73 @@
use std::{sync::Arc, time::Instant};
use camera::Camera;
use input::Input;
use render::Renderer;
use winit::{
event::WindowEvent,
window::{Window, WindowAttributes},
};
mod app;
mod camera;
mod handle_input;
mod input;
mod render;
pub use app::*;
pub struct Client<'a> {
window: Arc<Window>,
camera: Camera,
input: Input,
exit: bool,
prev_update: Instant,
renderer: Renderer<'a>,
}
impl Client<'_> {
pub fn new(event_loop: &winit::event_loop::ActiveEventLoop) -> Self {
let window = Arc::new(
event_loop
.create_window(WindowAttributes::default())
.expect("failed to create window"),
);
let renderer = Renderer::new(window.clone());
Self {
window,
camera: Camera::default(),
input: Input::new(),
exit: false,
prev_update: Instant::now(),
renderer,
}
}
pub fn update(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
if self.exit {
event_loop.exit();
}
let now = Instant::now();
self.handle_input(now - self.prev_update);
self.input.end();
self.prev_update = now;
}
pub fn window_event(&mut self, event: WindowEvent) {
match event {
WindowEvent::CloseRequested => self.exit = true,
WindowEvent::Resized(size) => self.renderer.resize(size),
WindowEvent::RedrawRequested => {
self.renderer.update(&self.camera);
self.renderer.draw();
self.window.request_redraw();
}
WindowEvent::CursorLeft { .. } => {
self.input.clear();
}
_ => self.input.update_window(event),
}
}
}

164
src/client/render/mod.rs Normal file
View File

@@ -0,0 +1,164 @@
mod tile;
mod util;
use std::sync::Arc;
use nalgebra::Vector2;
use tile::TilePipeline;
use util::GPUTimer;
use winit::{dpi::PhysicalSize, window::Window};
use super::camera::Camera;
pub struct Renderer<'a> {
size: Vector2<u32>,
surface: wgpu::Surface<'a>,
device: wgpu::Device,
queue: wgpu::Queue,
encoder: wgpu::CommandEncoder,
config: wgpu::SurfaceConfiguration,
staging_belt: wgpu::util::StagingBelt,
timer: GPUTimer,
tile_pipeline: TilePipeline,
}
impl Renderer<'_> {
pub fn new(window: Arc<Window>) -> Self {
let size = window.inner_size();
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends: wgpu::Backends::PRIMARY,
..Default::default()
});
let surface = instance
.create_surface(window)
.expect("Could not create window surface!");
let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::default(),
compatible_surface: Some(&surface),
force_fallback_adapter: false,
}))
.expect("Could not get adapter!");
let buf_size = (10f32.powi(9) * 1.5) as u32;
let (device, queue) = pollster::block_on(adapter.request_device(
&wgpu::DeviceDescriptor {
label: None,
required_features: wgpu::Features::PUSH_CONSTANTS
| wgpu::Features::TIMESTAMP_QUERY
| wgpu::Features::TIMESTAMP_QUERY_INSIDE_ENCODERS
| wgpu::Features::TIMESTAMP_QUERY_INSIDE_PASSES,
required_limits: wgpu::Limits {
max_storage_buffer_binding_size: buf_size,
max_buffer_size: buf_size as u64,
max_push_constant_size: 4,
..Default::default()
},
memory_hints: wgpu::MemoryHints::default(),
},
None,
))
.expect("Could not get device!");
let info = adapter.get_info();
println!("Adapter: {}", info.name);
println!("Backend: {:?}", info.backend);
let surface_caps = surface.get_capabilities(&adapter);
let surface_format = surface_caps
.formats
.iter()
.copied()
.find(|f| f.is_srgb())
.unwrap_or(surface_caps.formats[0]);
let config = wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format: surface_format,
width: size.width,
height: size.height,
present_mode: wgpu::PresentMode::AutoNoVsync,
alpha_mode: surface_caps.alpha_modes[0],
view_formats: vec![],
desired_maximum_frame_latency: 2,
};
surface.configure(&device, &config);
let staging_belt = wgpu::util::StagingBelt::new(1024);
let timer = GPUTimer::new(&device, queue.get_timestamp_period(), 1);
Self {
tile_pipeline: TilePipeline::init(&device, &config),
size: Vector2::new(size.width, size.height),
staging_belt,
surface,
encoder: Self::create_encoder(&device),
timer,
device,
config,
queue,
}
}
pub fn update(&mut self, camera: &Camera) {
self.tile_pipeline.update(
&self.device,
&mut self.encoder,
&mut self.staging_belt,
camera,
&self.size,
);
}
pub fn draw(&mut self) {
let mut encoder = std::mem::replace(&mut self.encoder, Self::create_encoder(&self.device));
let output = self.surface.get_current_texture().unwrap();
let view = output
.texture
.create_view(&wgpu::TextureViewDescriptor::default());
self.timer.start(&mut encoder, 0);
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("Render Pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
});
self.tile_pipeline.draw(&mut render_pass);
drop(render_pass);
self.timer.stop(&mut encoder, 0);
self.timer.resolve(&mut encoder);
self.staging_belt.finish();
self.queue.submit(std::iter::once(encoder.finish()));
output.present();
self.staging_belt.recall();
self.timer.finish(&self.device);
}
pub fn resize(&mut self, size: PhysicalSize<u32>) {
self.size = Vector2::new(size.width, size.height);
self.config.width = size.width;
self.config.height = size.height;
self.surface.configure(&self.device, &self.config);
}
fn create_encoder(device: &wgpu::Device) -> wgpu::CommandEncoder {
device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("Render Encoder"),
})
}
}

View File

@@ -0,0 +1,30 @@
use nalgebra::Vector2;
#[repr(C, align(8))]
#[derive(Clone, Copy)]
pub struct View {
pub scale: Vector2<f32>,
}
impl Default for View {
fn default() -> Self {
Self {
scale: Vector2::zeros(),
}
}
}
impl View {
pub fn new(size: &Vector2<u32>) -> Self {
let fsize: Vector2<f32> = size.cast();
let scale = if size.x < size.y {
Vector2::new(fsize.x / fsize.y, 1.0)
} else {
Vector2::new(1.0, fsize.y / fsize.x)
};
View { scale }
}
}
unsafe impl bytemuck::Pod for View {}
unsafe impl bytemuck::Zeroable for View {}

View File

@@ -0,0 +1,86 @@
use super::{util::Uniform, View};
pub struct TileLayout {
render_bind_layout: wgpu::BindGroupLayout,
render_pipeline_layout: wgpu::PipelineLayout,
format: wgpu::TextureFormat,
pub view: Uniform<View>,
}
impl TileLayout {
pub fn init(device: &wgpu::Device, config: &wgpu::SurfaceConfiguration) -> Self {
let view = Uniform::init(device, "view");
let render_bind_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
entries: &[view.bind_group_layout_entry(0)],
label: Some("voxel render"),
});
let render_pipeline_layout =
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Tile Pipeline Layout"),
bind_group_layouts: &[&render_bind_layout],
push_constant_ranges: &[],
});
Self {
view,
render_bind_layout,
render_pipeline_layout,
format: config.format,
}
}
pub fn render_bind_group(&self, device: &wgpu::Device) -> wgpu::BindGroup {
device.create_bind_group(&wgpu::BindGroupDescriptor {
layout: &self.render_bind_layout,
entries: &[self.view.bind_group_entry(0)],
label: Some("voxel render"),
})
}
pub fn render_pipeline(
&self,
device: &wgpu::Device,
shader: wgpu::ShaderModule,
) -> wgpu::RenderPipeline {
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("Voxel Pipeline"),
layout: Some(&self.render_pipeline_layout),
vertex: wgpu::VertexState {
module: &shader,
entry_point: "vs_main",
buffers: &[],
compilation_options: wgpu::PipelineCompilationOptions::default(),
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: "fs_main",
targets: &[Some(wgpu::ColorTargetState {
format: self.format,
blend: Some(wgpu::BlendState::REPLACE),
write_mask: wgpu::ColorWrites::ALL,
})],
compilation_options: wgpu::PipelineCompilationOptions::default(),
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleStrip,
strip_index_format: None,
front_face: wgpu::FrontFace::Ccw,
cull_mode: None,
polygon_mode: wgpu::PolygonMode::Fill,
unclipped_depth: false,
conservative: false,
},
depth_stencil: None,
multisample: wgpu::MultisampleState {
count: 1,
mask: !0,
alpha_to_coverage_enabled: true,
},
multiview: None,
cache: None,
})
}
}

View File

@@ -0,0 +1,61 @@
use std::ops::{Deref, DerefMut};
use wgpu::include_wgsl;
mod data;
mod layout;
use super::*;
pub use data::*;
use layout::*;
pub struct TilePipeline {
layout: TileLayout,
render_pipeline: wgpu::RenderPipeline,
render_bind_group: wgpu::BindGroup,
}
const RENDER_SHADER: wgpu::ShaderModuleDescriptor<'_> = include_wgsl!("render.wgsl");
impl TilePipeline {
pub fn init(device: &wgpu::Device, config: &wgpu::SurfaceConfiguration) -> Self {
let layout = TileLayout::init(device, config);
let render_shader = device.create_shader_module(RENDER_SHADER);
Self {
render_pipeline: layout.render_pipeline(device, render_shader),
render_bind_group: layout.render_bind_group(device),
layout,
}
}
pub fn update(
&mut self,
device: &wgpu::Device,
encoder: &mut wgpu::CommandEncoder,
belt: &mut wgpu::util::StagingBelt,
camera: &Camera,
size: &Vector2<u32>,
) {
self.view.update(device, encoder, belt, View::new(size));
}
pub fn draw<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) {
render_pass.set_pipeline(&self.render_pipeline);
render_pass.set_bind_group(0, &self.render_bind_group, &[]);
render_pass.draw(0..4, 0..1);
}
}
impl Deref for TilePipeline {
type Target = TileLayout;
fn deref(&self) -> &Self::Target {
&self.layout
}
}
impl DerefMut for TilePipeline {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.layout
}
}

View File

@@ -0,0 +1,110 @@
// Vertex shader
struct VertexOutput {
@builtin(position) vertex_pos: vec4<f32>,
@location(0) world_pos: vec2<f32>,
};
struct View {
scale: vec2<f32>,
// x_dec: i32,
// y_dec: i32,
// prec: u32,
}
@group(0) @binding(0)
var<uniform> view: View;
// @group(0) @binding(1)
// var<storage> vx: array<u32>;
// @group(0) @binding(2)
// var<storage> vy: array<u32>;
@vertex
fn vs_main(
@builtin(vertex_index) vi: u32,
@builtin(instance_index) ii: u32,
) -> VertexOutput {
var out: VertexOutput;
let pos = vec2<f32>(
f32(vi % 2u),
f32(vi / 2u),
) * 2.0 - 1.0;
out.vertex_pos = vec4<f32>(pos.x, -pos.y, 0.0, 1.0);
out.world_pos = pos;
out.world_pos.y *= -1.0;
out.world_pos *= view.scale;
return out;
}
// const PREC = 2;
@fragment
fn fs_main(
in: VertexOutput,
) -> @location(0) vec4<f32> {
let dec = i32(1) << 13;
let c = vec2<i32>(in.world_pos * f32(dec));
let cx = c.x;
let cy = c.y;
var x = 0;
var y = 0;
var i = 0u;
let thresh = 2 * dec;
let thresh2 = thresh * thresh;
let max = 50u;
loop {
let x2 = x * x;
let y2 = y * y;
if x2 + y2 > thresh2 || i >= max {
break;
}
y = (2 * x * y) / dec + c.y;
x = (x2 - y2) / dec + c.x;
i += 1u;
}
var color = vec3<f32>(0.0, 0.0, 0.0);
if i != max {
let pi = 3.1415;
let hue = f32(i) / 30.0;
color.r = cos(hue);
color.g = cos(hue - 2.0 * pi / 3.0);
color.b = cos(hue - 4.0 * pi / 3.0);
}
return vec4(color, 1.0);
}
// @fragment
// fn fs_main(
// in: VertexOutput,
// ) -> @location(0) vec4<f32> {
// let dec = i32(1) << 13;
// let c = vec2<i32>(in.world_pos * f32(dec));
// let cx = c.x;
// let cy = c.y;
// var x = 0;
// var y = 0;
// var i = 0u;
// let thresh = 2 * dec;
// let thresh2 = thresh * thresh;
// let max = 50u + u32(in.zoom);
// loop {
// let x2 = x * x;
// let y2 = y * y;
// if x2 + y2 > thresh2 || i >= max {
// break;
// }
// y = (2 * x * y) / dec + c.y;
// x = (x2 - y2) / dec + c.x;
// i += 1u;
// }
// var color = vec3<f32>(0.0, 0.0, 0.0);
// if i != max {
// let pi = 3.1415;
// let hue = f32(i) / 30.0;
// color.r = cos(hue);
// color.g = cos(hue - 2.0 * pi / 3.0);
// color.b = cos(hue - 4.0 * pi / 3.0);
// }
// return vec4(color, 1.0);
// }

View File

@@ -0,0 +1,146 @@
use wgpu::{util::DeviceExt, BufferAddress, BufferUsages};
pub struct ArrayBuffer<T: bytemuck::Pod> {
len: usize,
new_len: usize,
buffer: wgpu::Buffer,
label: String,
usage: BufferUsages,
updates: Vec<ArrBufUpdate<T>>,
}
impl<T: bytemuck::Pod> ArrayBuffer<T> {
pub fn update(
&mut self,
device: &wgpu::Device,
encoder: &mut wgpu::CommandEncoder,
belt: &mut wgpu::util::StagingBelt,
) -> bool {
let mut resized = false;
if self.new_len != self.len {
let new = Self::init_buf(device, &self.label, self.new_len, self.usage);
let cpy_len = self.len.min(self.new_len);
if cpy_len != 0 {
encoder.copy_buffer_to_buffer(
&self.buffer,
0,
&new,
0,
(cpy_len * std::mem::size_of::<T>()) as u64,
);
}
self.len = self.new_len;
resized = true;
self.buffer = new;
}
if self.len == 0 {
return resized;
}
for update in &self.updates {
let mut view = belt.write_buffer(
encoder,
&self.buffer,
(update.offset * std::mem::size_of::<T>()) as BufferAddress,
unsafe {
std::num::NonZeroU64::new_unchecked(
std::mem::size_of_val(&update.data[..]) as u64
)
},
device,
);
view.copy_from_slice(bytemuck::cast_slice(&update.data));
}
self.updates.clear();
resized
}
pub fn add(&mut self, data: Vec<T>) -> usize {
let data_len = data.len();
let pos = self.new_len;
self.updates.push(ArrBufUpdate { offset: pos, data });
self.new_len += data_len;
pos
}
pub fn set(&mut self, offset: usize, data: Vec<T>) {
self.updates.push(ArrBufUpdate { offset, data });
}
pub fn init(device: &wgpu::Device, label: &str, usage: BufferUsages) -> Self {
let label = &(label.to_owned() + " Buffer");
Self::init_with(device, label, usage, &[])
}
pub fn init_with(device: &wgpu::Device, label: &str, usage: BufferUsages, data: &[T]) -> Self {
let label = &(label.to_owned() + " Buffer");
Self {
len: data.len(),
new_len: data.len(),
buffer: if data.is_empty() {
Self::init_buf(device, label, 0, usage)
} else {
Self::init_buf_with(device, label, usage, data)
},
label: label.to_string(),
updates: Vec::new(),
usage,
}
}
fn init_buf(
device: &wgpu::Device,
label: &str,
size: usize,
usage: BufferUsages,
) -> wgpu::Buffer {
device.create_buffer(&wgpu::BufferDescriptor {
label: Some(label),
usage: usage | BufferUsages::COPY_DST | BufferUsages::COPY_SRC,
size: (size.max(1) * std::mem::size_of::<T>()) as u64,
mapped_at_creation: false,
})
}
fn init_buf_with(
device: &wgpu::Device,
label: &str,
usage: BufferUsages,
data: &[T],
) -> wgpu::Buffer {
device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some(label),
usage: usage | BufferUsages::COPY_DST | BufferUsages::COPY_SRC,
contents: bytemuck::cast_slice(data),
})
}
pub fn bind_group_layout_entry(
&self,
binding: u32,
visibility: wgpu::ShaderStages,
ty: wgpu::BufferBindingType,
) -> wgpu::BindGroupLayoutEntry {
wgpu::BindGroupLayoutEntry {
binding,
visibility,
ty: wgpu::BindingType::Buffer {
ty,
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
}
}
pub fn bind_group_entry(&self, binding: u32) -> wgpu::BindGroupEntry {
wgpu::BindGroupEntry {
binding,
resource: self.buffer.as_entire_binding(),
}
}
}
pub struct ArrBufUpdate<T> {
pub offset: usize,
pub data: Vec<T>,
}

View File

@@ -0,0 +1,12 @@
#![allow(unused_imports)]
#![allow(dead_code)]
mod texture;
mod timer;
mod uniform;
mod array;
pub use texture::*;
pub use timer::*;
pub use uniform::*;
pub use array::*;

View File

@@ -0,0 +1,91 @@
pub struct Texture {
texture_desc: wgpu::TextureDescriptor<'static>,
view_desc: wgpu::TextureViewDescriptor<'static>,
sampler_desc: wgpu::SamplerDescriptor<'static>,
pub texture: wgpu::Texture,
pub view: wgpu::TextureView,
pub sampler: wgpu::Sampler,
}
impl Texture {
pub const DEPTH_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Depth32Float;
pub fn init_depth(
device: &wgpu::Device,
config: &wgpu::SurfaceConfiguration,
label: &'static str,
) -> Self {
let size = wgpu::Extent3d {
width: config.width + 1,
height: config.height + 1,
depth_or_array_layers: 1,
};
let texture_desc = wgpu::TextureDescriptor {
label: Some(label),
size,
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: Self::DEPTH_FORMAT,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
view_formats: &[],
};
Self::init(
device,
texture_desc,
wgpu::TextureViewDescriptor::default(),
wgpu::SamplerDescriptor {
address_mode_u: wgpu::AddressMode::ClampToEdge,
address_mode_v: wgpu::AddressMode::ClampToEdge,
address_mode_w: wgpu::AddressMode::ClampToEdge,
mag_filter: wgpu::FilterMode::Linear,
min_filter: wgpu::FilterMode::Linear,
mipmap_filter: wgpu::FilterMode::Nearest,
compare: Some(wgpu::CompareFunction::LessEqual),
lod_min_clamp: 0.0,
lod_max_clamp: 100.0,
..Default::default()
},
)
}
pub fn init(
device: &wgpu::Device,
texture_desc: wgpu::TextureDescriptor<'static>,
view_desc: wgpu::TextureViewDescriptor<'static>,
sampler_desc: wgpu::SamplerDescriptor<'static>,
) -> Self {
let texture = device.create_texture(&texture_desc);
let view = texture.create_view(&view_desc);
let sampler = device.create_sampler(&sampler_desc);
Self {
texture_desc,
view_desc,
sampler_desc,
texture,
view,
sampler,
}
}
pub fn resize(&mut self, device: &wgpu::Device, size: wgpu::Extent3d) {
self.texture_desc.size = size;
self.texture = device.create_texture(&self.texture_desc);
self.view = self.texture.create_view(&self.view_desc);
}
pub fn view_bind_group_entry(&self, binding: u32) -> wgpu::BindGroupEntry {
wgpu::BindGroupEntry {
binding,
resource: wgpu::BindingResource::TextureView(&self.view),
}
}
pub fn sampler_bind_group_entry(&self, binding: u32) -> wgpu::BindGroupEntry {
wgpu::BindGroupEntry {
binding,
resource: wgpu::BindingResource::Sampler(&self.sampler),
}
}
pub fn format(&self) -> wgpu::TextureFormat {
self.texture_desc.format
}
}

View File

@@ -0,0 +1,89 @@
use std::time::Duration;
pub struct GPUTimer {
resolve_buf: wgpu::Buffer,
map_buf: wgpu::Buffer,
query_set: wgpu::QuerySet,
timestamps: Vec<u64>,
period: f32,
}
impl GPUTimer {
pub fn new(device: &wgpu::Device, period: f32, count: u32) -> Self {
let count = count * 2;
let timestamp_set = device.create_query_set(&wgpu::QuerySetDescriptor {
count,
label: Some("voxel timestamp"),
ty: wgpu::QueryType::Timestamp,
});
let timestamp_resolve_buf = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("voxel timestamp"),
mapped_at_creation: false,
size: 8 * count as u64,
usage: wgpu::BufferUsages::QUERY_RESOLVE | wgpu::BufferUsages::COPY_SRC,
});
let timestamp_mapped_buf = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("voxel timestamp"),
mapped_at_creation: false,
size: 8 * count as u64,
usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
});
Self {
query_set: timestamp_set,
resolve_buf: timestamp_resolve_buf,
map_buf: timestamp_mapped_buf,
timestamps: vec![0; count as usize],
period,
}
}
pub fn resolve(&self, encoder: &mut wgpu::CommandEncoder) {
encoder.resolve_query_set(&self.query_set, 0..2, &self.resolve_buf, 0);
encoder.copy_buffer_to_buffer(&self.resolve_buf, 0, &self.map_buf, 0, self.map_buf.size());
}
pub fn duration(&self, i: u32) -> Duration {
let i = i as usize * 2;
let diff = self.timestamps[i + 1] - self.timestamps[i];
Duration::from_nanos((diff as f32 * self.period) as u64)
}
pub fn finish(&mut self, device: &wgpu::Device) {
let (s, r) = std::sync::mpsc::channel();
self.map_buf
.slice(..)
.map_async(wgpu::MapMode::Read, move |v| {
s.send(v).expect("what");
});
device.poll(wgpu::Maintain::wait()).panic_on_timeout();
if let Ok(Ok(())) = r.recv() {
let data = self.map_buf.slice(..).get_mapped_range();
self.timestamps.copy_from_slice(bytemuck::cast_slice(&data));
drop(data);
self.map_buf.unmap();
}
}
#[allow(dead_code)]
pub fn start(&self, encoder: &mut wgpu::CommandEncoder, i: u32) {
encoder.write_timestamp(&self.query_set, i * 2);
}
#[allow(dead_code)]
pub fn stop(&self, encoder: &mut wgpu::CommandEncoder, i: u32) {
encoder.write_timestamp(&self.query_set, i * 2 + 1);
}
#[allow(dead_code)]
pub fn start_compute(&self, pass: &mut wgpu::ComputePass, i: u32) {
pass.write_timestamp(&self.query_set, i * 2);
}
#[allow(dead_code)]
pub fn stop_compute(&self, pass: &mut wgpu::ComputePass, i: u32) {
pass.write_timestamp(&self.query_set, i * 2 + 1);
}
}

View File

@@ -0,0 +1,76 @@
use std::marker::PhantomData;
use wgpu::util::DeviceExt;
pub struct Uniform<T> {
buffer: wgpu::Buffer,
ty: PhantomData<T>,
}
impl<T: Default + bytemuck::Pod> Uniform<T> {
pub fn init(device: &wgpu::Device, name: &str) -> Self {
Self {
buffer: device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some(&(name.to_owned() + " Uniform Buf")),
contents: bytemuck::cast_slice(&[T::default()]),
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
}),
ty: PhantomData,
}
}
}
impl<T: bytemuck::Pod> Uniform<T> {
pub fn init_with(device: &wgpu::Device, name: &str, data: &[T]) -> Self {
Self {
buffer: device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some(&(name.to_owned() + " Uniform Buf")),
contents: bytemuck::cast_slice(data),
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
}),
ty: PhantomData,
}
}
pub fn update(
&mut self,
device: &wgpu::Device,
encoder: &mut wgpu::CommandEncoder,
belt: &mut wgpu::util::StagingBelt,
data: T,
) {
let slice = &[data];
let mut view = belt.write_buffer(
encoder,
&self.buffer,
0,
unsafe {
std::num::NonZeroU64::new_unchecked((slice.len() * std::mem::size_of::<T>()) as u64)
},
device,
);
view.copy_from_slice(bytemuck::cast_slice(slice));
}
}
impl<T> Uniform<T> {
pub fn bind_group_layout_entry(&self, binding: u32) -> wgpu::BindGroupLayoutEntry {
wgpu::BindGroupLayoutEntry {
binding,
visibility: wgpu::ShaderStages::VERTEX
| wgpu::ShaderStages::FRAGMENT
| wgpu::ShaderStages::COMPUTE,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
}
}
pub fn bind_group_entry(&self, binding: u32) -> wgpu::BindGroupEntry {
wgpu::BindGroupEntry {
binding,
resource: self.buffer.as_entire_binding(),
}
}
}

25
src/main.rs Normal file
View File

@@ -0,0 +1,25 @@
#![feature(bigint_helper_methods)]
use client::ClientApp;
use util::FixedDec;
mod client;
mod util;
fn main() {
let a = FixedDec::from(0.75);
println!("a = {}", a);
let b = FixedDec::from(1.75);
println!("b = {}", b);
println!("a + b = {}", &a + &b);
let c = FixedDec::from(1.0 / 16.0);
println!("c = {}", c);
println!("a + c = {}", &a + &c);
println!("-a = {}", -&a);
println!("b - a = {}", &b - &a);
println!("-c = {}", -&c);
// let event_loop = winit::event_loop::EventLoop::new().expect("Failed to create event loop");
// event_loop
// .run_app(&mut ClientApp::new())
// .expect("Failed to run event loop");
}

338
src/util/fixed.rs Normal file
View File

@@ -0,0 +1,338 @@
use num_traits::Zero;
use std::{
fmt::{Binary, Debug, Display},
ops::{Add, AddAssign, Mul, Neg, Shr, Sub, SubAssign},
};
const POS: bool = false;
const NEG: bool = true;
#[derive(Debug, Clone, PartialEq)]
pub struct FixedDec {
sign: bool,
dec: i32,
parts: Vec<u32>,
}
impl FixedDec {
pub fn zeros() -> Self {
Self::zero()
}
pub fn dec_len(&self) -> i32 {
self.parts.len() as i32 - self.dec
}
pub fn part(&self, i: i32) -> u32 {
let Ok(i): Result<usize, _> = i.try_into() else {
return self.pre_padding();
};
self.parts.get(i).cloned().unwrap_or(0)
}
pub fn is_pos(&self) -> bool {
!self.sign
}
pub fn is_neg(&self) -> bool {
self.sign
}
fn pre_padding(&self) -> u32 {
match self.sign {
POS => 0,
NEG => !0,
}
}
}
impl Zero for FixedDec {
fn zero() -> Self {
Self {
sign: POS,
dec: 0,
parts: Vec::new(),
}
}
fn is_zero(&self) -> bool {
self.parts.iter().all(|&b| b == 0)
}
}
impl Shr<u32> for FixedDec {
type Output = Self;
fn shr(self, rhs: u32) -> Self::Output {
let mut parts = Vec::new();
let sr = rhs % 32;
let sl = 32 - sr;
let mask = (1 << sr) - 1;
let dec = self.dec - (rhs / 32) as i32;
let mut rem = 0;
for part in self.parts {
parts.push((part >> sr) ^ rem);
rem = (part & mask) << sl;
}
if rem != 0 {
parts.push(rem);
}
Self {
dec,
parts,
sign: self.sign,
}
}
}
impl Add for FixedDec {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
&self + &rhs
}
}
impl AddAssign<&FixedDec> for FixedDec {
fn add_assign(&mut self, rhs: &FixedDec) {
let dec = self.dec.max(rhs.dec);
let left_i = -dec;
let right_i = self.dec_len().max(rhs.dec_len());
let len = (right_i - left_i) as usize;
if dec != self.dec {
let fill = self.pre_padding();
let fill_len = rhs.dec - self.dec;
self.parts.splice(0..0, (0..fill_len).map(|_| fill));
self.dec += fill_len;
}
if self.parts.len() != len {
self.parts.resize(len, 0);
}
let mut carry = false;
let rhs_offset = rhs.dec - self.dec;
for i in (0..self.parts.len()).rev() {
let a = self.parts[i];
let b = rhs.part(i as i32 + rhs_offset);
let (res, c) = a.carrying_add(b, carry);
self.parts[i] = res;
carry = c;
}
let sign = if self.sign == rhs.sign {
if self.sign == POS && carry {
self.parts.insert(0, 1);
self.dec += 1;
} else if self.sign == NEG && !carry {
self.parts.insert(0, !1);
self.dec += 1;
}
self.sign
} else if carry {
POS
} else {
NEG
};
self.sign = sign;
}
}
impl SubAssign<&FixedDec> for FixedDec {
fn sub_assign(&mut self, rhs: &FixedDec) {
*self += &-rhs;
}
}
impl Add for &FixedDec {
type Output = FixedDec;
fn add(self, rhs: Self) -> Self::Output {
let mut dec = self.dec.max(rhs.dec);
let left_i = -dec;
let right_i = self.dec_len().max(rhs.dec_len());
let mut parts = Vec::with_capacity((right_i - left_i) as usize);
let mut carry = false;
for i in (left_i..right_i).rev() {
let a = self.part(i + self.dec);
let b = rhs.part(i + rhs.dec);
let (res, c) = a.carrying_add(b, carry);
parts.push(res);
carry = c;
}
let sign = if self.sign == rhs.sign {
if self.is_pos() && carry {
parts.push(1);
dec += 1;
} else if self.is_neg() && !carry {
parts.push(!1);
dec += 1;
}
self.sign
} else if carry {
POS
} else {
NEG
};
parts.reverse();
FixedDec { parts, dec, sign }
}
}
impl Sub for &FixedDec {
type Output = FixedDec;
fn sub(self, rhs: Self) -> Self::Output {
self + &(-rhs)
}
}
impl Neg for &FixedDec {
type Output = FixedDec;
fn neg(self) -> Self::Output {
let parts = self.parts.iter().map(|p| !p).collect();
let mut res = FixedDec {
parts,
sign: !self.sign,
dec: self.dec,
};
res += &Self::Output {
parts: vec![1],
dec: self.dec - (self.parts.len() as i32 - 1),
sign: POS,
};
res
}
}
impl Mul for &FixedDec {
type Output = FixedDec;
fn mul(self, rhs: Self) -> Self::Output {
let mut parts: Vec<u32> = vec![0; self.parts.len() + rhs.parts.len()];
for (i, &x) in self.parts.iter().enumerate().rev() {
let mut carry: u32 = 0;
for (j, &y) in rhs.parts.iter().enumerate().rev() {
let (lsb, msb) = mul_lmsb(x, y);
let k = i + j + 1;
let (res, carry1) = parts[k].overflowing_add(lsb);
parts[k] = res;
let (res, carry2) = parts[k].overflowing_add(carry);
parts[k] = res;
// dude I have no clue if this can overflow; I know msb can take 1 without
// overflowing, but I'm not sure if 2 can get here when it's max
carry = (carry1 as u32) + (carry2 as u32) + msb;
}
if carry > 0 {
parts[i] = carry;
}
}
Self::Output {
dec: self.dec + rhs.dec,
parts,
sign: self.sign == rhs.sign,
}
}
}
fn mul_lmsb(x: u32, y: u32) -> (u32, u32) {
let lsb = x.wrapping_mul(y);
let a = x & 0xffff;
let b = x >> 16;
let c = y & 0xffff;
let d = y >> 16;
let ad = a * d + ((a * c) >> 16);
let bc = b * c;
let car = ad > (0xffffffff - bc);
let msb = ((ad + bc) >> 16) + ((car as u32) << 16) + b * d;
(lsb, msb)
}
const INV_SIGN_MASK: u32 = (1 << 31) - 1;
const FRAC_BIT: u32 = 1 << 23;
const FRAC_MASK: u32 = FRAC_BIT - 1;
impl From<f32> for FixedDec {
fn from(value: f32) -> Self {
let raw = value.to_bits() & INV_SIGN_MASK;
let exp = (raw >> 23) as i32 - 127;
let frac = (raw & FRAC_MASK) + FRAC_BIT;
let start = -exp - 1;
let end = -exp + 23;
let start_i = start.div_euclid(32);
let end_i = (end - 1).div_euclid(32);
let parts = if start_i == end_i {
vec![frac << (8 - start.rem_euclid(32))]
} else {
let s = end.rem_euclid(32);
vec![frac >> s, frac << (32 - s)]
};
Self {
sign: POS,
dec: -start_i,
parts,
}
}
}
impl From<&FixedDec> for f32 {
fn from(value: &FixedDec) -> Self {
let mut sign = 0;
let value = if value.is_neg() {
sign = 1 << 31;
&-value
} else {
value
};
let mut skip_count = 0;
let mut iter = value
.parts
.iter()
.inspect(|_| skip_count += 1)
.skip_while(|&&x| x == 0);
let Some(v) = iter.next() else {
return 0.0;
};
let start = v.leading_zeros();
let frac = if start > 9 {
let sh = start - 9;
(v << sh) + iter.next().copied().map(|v| v >> (32 - sh)).unwrap_or(0)
} else {
v >> (9 - start)
};
let exp = (127 - (skip_count * 32 + start)) << 23;
let res = frac + exp + sign;
println!();
println!("res: {:032b}", res);
println!("ans: {:032b}", 0.75f32.to_bits());
f32::from_bits(res)
}
}
impl Display for FixedDec {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", f32::from(self))
}
}
impl Binary for FixedDec {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.sign == NEG {
write!(f, "-")?;
}
if self.dec < 0 {
write!(f, ".")?;
for _ in 0..(-self.dec) {
write!(f, "00000000000000000000000000000000")?;
}
}
for (i, part) in self.parts.iter().enumerate() {
if i as i32 == self.dec {
write!(f, ".")?;
} else if i != 0 {
write!(f, "_")?;
}
write!(f, "{:032b}", part)?;
}
Ok(())
}
}

2
src/util/mod.rs Normal file
View File

@@ -0,0 +1,2 @@
mod fixed;
pub use fixed::*;