KIND OF ARBITRARY ZOOM (hangs at len 5 for some reason)

This commit is contained in:
2025-03-30 14:26:33 -04:00
parent 88e25da973
commit 44b6887e00
26 changed files with 1493 additions and 686 deletions

View File

@@ -6,28 +6,36 @@ use super::Camera;
const VIEW_ALIGN: usize = 4 * 2;
pub struct View {
pub struct ComputeView {
pub bytes: Vec<u8>,
}
impl View {
impl ComputeView {
pub fn bytes(&self) -> &[u8] {
&self.bytes
}
}
impl Default for View {
impl Default for ComputeView {
fn default() -> Self {
let val = FixedDec::from_parts(false, 0, vec![0, 0, 0]);
Self::new(Vector2::zeros(), 0, &val, &val, &val)
Self::new(true, Vector2::zeros(), 0, &val, &val, &val)
}
}
impl View {
fn new(stretch: Vector2<f32>, level: i32, scale: &FixedDec, x: &FixedDec, y: &FixedDec) -> Self {
impl ComputeView {
fn new(
reset: bool,
dims: Vector2<u32>,
level: i32,
scale: &FixedDec,
x: &FixedDec,
y: &FixedDec,
) -> Self {
let mut bytes = Vec::new();
bytes.extend(bytemuck::cast_slice(&[stretch.x, stretch.y]));
bytes.extend((reset as u32).to_le_bytes());
bytes.extend(level.to_le_bytes());
bytes.extend(bytemuck::cast_slice(&[dims.x, dims.y]));
scale.to_bytes(&mut bytes);
x.to_bytes(&mut bytes);
y.to_bytes(&mut bytes);
@@ -35,16 +43,16 @@ impl View {
if rem != 0 {
bytes.extend((0..(VIEW_ALIGN - rem)).map(|_| 0));
}
Self{ bytes }
Self { bytes }
}
pub fn from_camera_size(camera: &Camera, size: &Vector2<u32>) -> Self {
pub fn from_camera_size(camera: &Camera, size: &Vector2<u32>, reset: bool, len: usize) -> Self {
let mut x = camera.pos.x.clone();
x.set_whole_len(1);
x.set_dec_len(2);
x.set_dec_len(len as i32 - 1);
let mut y = camera.pos.y.clone();
y.set_whole_len(1);
y.set_dec_len(2);
y.set_dec_len(len as i32 - 1);
let fsize: Vector2<f32> = size.cast();
let stretch = if size.x < size.y {
@@ -54,8 +62,14 @@ impl View {
};
let mut scale = camera.zoom.mult().clone();
scale.set_precision(3);
scale.set_precision(len);
Self::new(stretch, camera.zoom.level(), &scale, &x, &y)
Self::new(reset, *size, camera.zoom.level(), &scale, &x, &y)
}
}
impl PartialEq for ComputeView {
fn eq(&self, other: &Self) -> bool {
self.bytes[1..] == other.bytes[1..]
}
}

View File

@@ -1,14 +1,5 @@
const LEN: u32 = 3;
const ILEN: i32 = 3;
const LEN2: u32 = LEN * 2;
struct View {
stretch: vec2<f32>,
level: i32,
scale: FixedDec,
x: FixedDec,
y: FixedDec,
}
const POS: u32 = 0u;
const NEG: u32 = 1u;
struct FixedDec {
sign: u32,
@@ -16,63 +7,18 @@ struct FixedDec {
parts: array<u32, LEN>,
}
@group(0) @binding(0)
var<storage> view: View;
struct VertexOutput {
@builtin(position) vertex_pos: vec4<f32>,
@location(0) world_pos: vec2<f32>,
};
@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.stretch;
return out;
}
@fragment
fn fs_main(
in: VertexOutput,
) -> @location(0) vec4<f32> {
let cx = add(mul(from_f32(in.world_pos.x), view.scale), view.x);
let cy = add(mul(from_f32(in.world_pos.y), view.scale), view.y);
var x = zero();
var y = zero();
let two = from_f32(2.0);
let thresh = from_f32(2.0 * 2.0);
var i = 0u;
let max = 50u + (1u << u32(view.level));
loop {
let x2 = mul(x, x);
let y2 = mul(y, y);
if gt(add(x2, y2), thresh) || i >= max {
break;
}
y = add(mul(two, mul(x, y)), cy);
x = add(sub(x2, y2), cx);
i += 1u;
fn shr(lhs: FixedDec, rhs: i32) -> FixedDec {
var parts = array<u32, LEN>();
let sr = u32(rem_euclid(rhs, 32));
let sl = 32 - sr;
let mask = (1u << sr) - 1;
var rem = 0u;
for (var i = 0; i < ILEN; i += 1) {
let part = lhs.parts[i];
parts[i] = (part >> sr) ^ rem;
rem = (part & mask) << sl;
}
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);
return FixedDec(lhs.sign, lhs.dec, parts);
}
fn add(lhs: FixedDec, rhs: FixedDec) -> FixedDec {
@@ -136,9 +82,6 @@ fn at(dec: FixedDec, i: i32) -> u32 {
return parts[i];
}
const POS: u32 = 0u;
const NEG: u32 = 1u;
fn mul(lhs: FixedDec, rhs: FixedDec) -> FixedDec {
let sign = u32(lhs.sign != rhs.sign);
var parts = array<u32, LEN2>();
@@ -173,7 +116,7 @@ fn mul(lhs: FixedDec, rhs: FixedDec) -> FixedDec {
let res2 = res + carry;
let carry2 = res2 < res;
parts[k] = res2;
carry = u32(carry1 || carry2) + msb;
carry = u32(carry1) + u32(carry2) + msb;
}
parts[i] = carry;
}
@@ -202,6 +145,25 @@ fn gt(x: FixedDec, y: FixedDec) -> bool {
return x.parts[0] > y.parts[0];
}
fn eq(x: FixedDec, y: FixedDec) -> bool {
if x.sign != y.sign || x.dec != y.dec {
return false;
}
var i = 0u;
var xp = x.parts;
var yp = y.parts;
while i < LEN - 2 {
if xp[i] != yp[i] {
return false;
}
i += 1u;
}
if abs(f32(xp[i]) - f32(yp[i])) > 1024 * 16 {
return false;
}
return true;
}
fn to_f32(value: FixedDec) -> f32 {
var parts = value.parts;

View File

@@ -0,0 +1,123 @@
use wgpu::{PipelineCompilationOptions, ShaderStages};
use crate::client::render::util::ArrayBuffer;
use super::{
util::{Storage, Texture},
ComputeView,
};
pub struct Layout {
bind_layout: wgpu::BindGroupLayout,
pipeline_layout: wgpu::PipelineLayout,
format: wgpu::TextureFormat,
pub output: Texture,
pub view: Storage,
pub work: ArrayBuffer<u32>,
}
impl Layout {
pub fn init(device: &wgpu::Device, config: &wgpu::SurfaceConfiguration, len: usize) -> Self {
let view = Storage::init_with(device, "view", ComputeView::default().bytes());
let work = ArrayBuffer::init_with(
device,
"test",
wgpu::BufferUsages::STORAGE,
&work_vec(config.width, config.height, len),
);
let desc = wgpu::TextureDescriptor {
label: Some("compute output"),
size: wgpu::Extent3d {
width: config.width,
height: config.height,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rgba8Unorm,
usage: wgpu::TextureUsages::STORAGE_BINDING | wgpu::TextureUsages::TEXTURE_BINDING,
view_formats: &[],
};
let output = Texture::init(
device,
desc,
wgpu::TextureViewDescriptor::default(),
wgpu::SamplerDescriptor::default(),
);
let bind_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
entries: &[
view.bind_group_layout_entry(0, true, wgpu::ShaderStages::COMPUTE),
work.bind_group_layout_entry(
1,
wgpu::BufferBindingType::Storage { read_only: false },
wgpu::ShaderStages::COMPUTE,
),
wgpu::BindGroupLayoutEntry {
binding: 2,
visibility: ShaderStages::COMPUTE,
ty: wgpu::BindingType::StorageTexture {
access: wgpu::StorageTextureAccess::WriteOnly,
format: output.format(),
view_dimension: wgpu::TextureViewDimension::D2,
},
count: None,
},
],
label: Some("compute"),
});
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Tile Pipeline Layout"),
bind_group_layouts: &[&bind_layout],
push_constant_ranges: &[],
});
Self {
view,
output,
bind_layout,
pipeline_layout,
work,
format: config.format,
}
}
pub fn bind_group(&self, device: &wgpu::Device) -> wgpu::BindGroup {
device.create_bind_group(&wgpu::BindGroupDescriptor {
layout: &self.bind_layout,
entries: &[
self.view.bind_group_entry(0),
self.work.bind_group_entry(1),
self.output.view_bind_group_entry(2),
],
label: Some("voxel render"),
})
}
pub fn pipeline(
&self,
device: &wgpu::Device,
shader: &wgpu::ShaderModule,
) -> wgpu::ComputePipeline {
device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
label: Some("Voxel Pipeline"),
layout: Some(&self.pipeline_layout),
entry_point: Some("main"),
module: shader,
cache: None,
compilation_options: PipelineCompilationOptions::default(),
})
}
}
pub fn work_size(width: u32, height: u32, len: usize) -> usize {
let varwidth = (2 + len) * 2;
(width * height) as usize * (varwidth * 2 + 1)
}
pub fn work_vec(width: u32, height: u32, len: usize) -> Vec<u32> {
vec![0u32; work_size(width, height, len)]
}

View File

@@ -0,0 +1,110 @@
use std::ops::{Deref, DerefMut};
mod data;
mod layout;
use super::*;
pub use data::*;
use layout::*;
pub struct ComputePipeline {
layout: Layout,
pipeline: wgpu::ComputePipeline,
bind_group: wgpu::BindGroup,
old_view: ComputeView,
old_len: usize,
}
const FIXED_SHADER: &str = include_str!("fixed.wgsl");
const SHADER: &str = include_str!("shader.wgsl");
impl ComputePipeline {
pub fn init(device: &wgpu::Device, config: &wgpu::SurfaceConfiguration, len: usize) -> Self {
let layout = Layout::init(device, config, len);
Self {
pipeline: layout.pipeline(device, &Self::shader(device, len)),
bind_group: layout.bind_group(device),
layout,
old_view: ComputeView::default(),
old_len: len,
}
}
pub fn update(
&mut self,
device: &wgpu::Device,
encoder: &mut wgpu::CommandEncoder,
belt: &mut wgpu::util::StagingBelt,
camera: &Camera,
size: &Vector2<u32>,
len: usize,
) {
let mut view = ComputeView::from_camera_size(camera, size, false, len);
if view != self.old_view {
for (i, b) in 1u32.to_le_bytes().iter().enumerate() {
view.bytes[i] = *b;
}
}
if len != self.old_len {
self.old_len = len;
self.pipeline = self.pipeline(device, &Self::shader(device, len));
self.work.set(work_vec(size.x, size.y, len));
}
let updated = self.work.update(device, encoder, belt)
| self.view.update(device, encoder, belt, view.bytes());
if updated {
self.bind_group = self.layout.bind_group(device);
}
self.old_view = view;
}
pub fn run(&self, encoder: &mut wgpu::CommandEncoder) {
let mut pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor::default());
pass.set_pipeline(&self.pipeline);
pass.set_bind_group(0, &self.bind_group, &[]);
pass.dispatch_workgroups(240, 135, 1);
}
pub fn resize(
&mut self,
device: &wgpu::Device,
encoder: &mut wgpu::CommandEncoder,
belt: &mut wgpu::util::StagingBelt,
size: Vector2<u32>,
len: usize,
) {
self.work.set(work_vec(size.x, size.y, len));
self.old_len = len;
self.output.resize(
device,
wgpu::Extent3d {
width: size.x,
height: size.y,
depth_or_array_layers: 1,
},
);
self.bind_group = self.layout.bind_group(device);
}
pub fn shader(device: &wgpu::Device, len: usize) -> wgpu::ShaderModule {
let string = FIXED_SHADER.to_string() + &SHADER.replace("REPLACE_LEN", &format!("{}", len));
device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("compute"),
source: wgpu::ShaderSource::Wgsl(string.into()),
})
}
}
impl Deref for ComputePipeline {
type Target = Layout;
fn deref(&self) -> &Self::Target {
&self.layout
}
}
impl DerefMut for ComputePipeline {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.layout
}
}

View File

@@ -0,0 +1,102 @@
override CHUNK_POW: u32 = 10;
const LEN: u32 = REPLACE_LENu;
const ILEN: i32 = i32(LEN);
const LEN2: u32 = LEN * 2;
override WGX: u32 = 8;
override WGY: u32 = 8;
struct View {
reset: u32,
level: i32,
dims: vec2<u32>,
scale: FixedDec,
corner_x: FixedDec,
corner_y: FixedDec,
}
@group(0) @binding(0)
var<storage> view: View;
@group(0) @binding(1)
var<storage, read_write> work: array<u32>;
@group(0) @binding(2)
var output: texture_storage_2d<rgba8unorm, write>;
@compute @workgroup_size(WGX, WGY, 1)
fn main(
@builtin(global_invocation_id) id: vec3<u32>
) {
if id.x > view.dims.x - 1 || id.y > view.dims.y - 1 {
return;
}
// TODO: actually use width
let varwidth = LEN + 2;
let workwidth = varwidth * 2 + 1;
let worki = (id.x * view.dims.y + id.y) * workwidth;
let xidx = worki + 1;
let yidx = xidx + varwidth;
// let dec = view.corner_x.dec;
// var rel_x = FixedDec(POS, dec, array<u32, LEN>());
// rel_x.parts[0] = id.x;
// rel_x = shr(rel_x, view.level);
// var rel_y = FixedDec(POS, dec, array<u32, LEN>());
// rel_y.parts[0] = id.y;
// rel_y = shr(rel_y, view.level);
// let cx = add(view.corner_x, rel_x);
// let cy = add(view.corner_y, rel_y);
let fdims = vec2<f32>(view.dims);
var stretch: vec2<f32>;
if fdims.x < fdims.y {
stretch = vec2(fdims.x / fdims.y, 1.0);
} else {
stretch = vec2(1.0, fdims.y / fdims.x);
}
let fpos = (vec2<f32>(id.xy) / fdims - 0.5) * stretch;
let cx = add(mul(from_f32(fpos.x), view.scale), view.corner_x);
let cy = add(mul(from_f32(fpos.y), view.scale), view.corner_y);
var x: FixedDec;
var y: FixedDec;
var i = work[worki];
if bool(view.reset) {
x = zero();
y = zero();
i = 0;
} else {
x = FixedDec(work[xidx + 0], bitcast<i32>(work[xidx + 1]), array<u32, LEN>());
y = FixedDec(work[yidx + 0], bitcast<i32>(work[yidx + 1]), array<u32, LEN>());
for (var j = 0u; j < LEN; j += 1u) {
x.parts[j] = work[xidx + 2 + j];
y.parts[j] = work[yidx + 2 + j];
}
}
let max = i + 1;
let thresh = from_f32(2.0 * 2.0);
loop {
let x2 = mul(x, x);
let y2 = mul(y, y);
if gt(add(x2, y2), thresh) || i >= max {
break;
}
let xy = mul(x, y);
y = add(add(xy, xy), cy);
x = add(sub(x2, y2), cx);
i += 1u;
}
work[worki] = i;
work[xidx + 0] = x.sign; work[xidx + 1] = bitcast<u32>(x.dec);
work[yidx + 0] = y.sign; work[yidx + 1] = bitcast<u32>(y.dec);
for (var j = 0u; j < LEN; j += 1u) {
work[xidx + 2 + j] = x.parts[j];
work[yidx + 2 + j] = y.parts[j];
}
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);
}
textureStore(output, id.xy, vec4(color, 1.0));
}

View File

@@ -1,15 +1,22 @@
mod tile;
mod compute;
mod output;
mod util;
mod view;
use std::sync::Arc;
use compute::ComputePipeline;
use nalgebra::Vector2;
use tile::TilePipeline;
use output::RenderPipeline;
use util::GPUTimer;
use view::ChunkView;
use winit::{dpi::PhysicalSize, window::Window};
use super::camera::Camera;
const CHUNK_POW: u32 = 7;
const CHUNK_WIDTH: u32 = 2u32.pow(CHUNK_POW);
pub struct Renderer<'a> {
size: Vector2<u32>,
surface: wgpu::Surface<'a>,
@@ -19,15 +26,18 @@ pub struct Renderer<'a> {
config: wgpu::SurfaceConfiguration,
staging_belt: wgpu::util::StagingBelt,
timer: GPUTimer,
chunk_view: ChunkView,
len: usize,
tile_pipeline: TilePipeline,
compute_pipeline: ComputePipeline,
render_pipeline: RenderPipeline,
}
impl Renderer<'_> {
pub fn new(window: Arc<Window>) -> Self {
let size = window.inner_size();
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
backends: wgpu::Backends::PRIMARY,
..Default::default()
});
@@ -90,8 +100,14 @@ impl Renderer<'_> {
let staging_belt = wgpu::util::StagingBelt::new(1024);
let timer = GPUTimer::new(&device, queue.get_timestamp_period(), 1);
let len = 2;
let compute_pipeline = ComputePipeline::init(&device, &config, len);
let render_pipeline = RenderPipeline::init(&device, &config, &compute_pipeline.output);
Self {
tile_pipeline: TilePipeline::init(&device, &config),
render_pipeline,
compute_pipeline,
size: Vector2::new(size.width, size.height),
staging_belt,
surface,
@@ -100,47 +116,44 @@ impl Renderer<'_> {
device,
config,
queue,
chunk_view: ChunkView::new(),
len,
}
}
pub fn update(&mut self, camera: &Camera) {
self.tile_pipeline.update(
pub fn render(&mut self, camera: &Camera) {
self.len = (camera.zoom.level() as f32 / 15.0 + 2.0).round() as usize;
println!("{}", self.len);
// let new = (camera.zoom.level() as f32 / 15.0 + 2.0).round() as usize;
// println!("{}", new);
self.compute_pipeline.update(
&self.device,
&mut self.encoder,
&mut self.staging_belt,
camera,
&self.size,
self.len,
);
self.chunk_view.update(camera, &self.size);
self.render_pipeline.update(
&self.device,
&mut self.encoder,
&mut self.staging_belt,
&self.chunk_view.render,
);
}
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.compute_pipeline.run(&mut encoder);
self.timer.stop(&mut encoder, 0);
self.timer.resolve(&mut encoder);
self.render_pipeline.draw(&mut encoder, &output);
self.staging_belt.finish();
self.queue.submit(std::iter::once(encoder.finish()));
output.present();
@@ -154,6 +167,8 @@ impl Renderer<'_> {
self.config.width = size.width;
self.config.height = size.height;
self.surface.configure(&self.device, &self.config);
self.compute_pipeline.resize(&self.device, &mut self.encoder, &mut self.staging_belt, self.size, self.len);
self.render_pipeline.resize(&self.device, self.size, &self.compute_pipeline.output);
}
fn create_encoder(device: &wgpu::Device) -> wgpu::CommandEncoder {

View File

@@ -0,0 +1,45 @@
use nalgebra::Vector2;
use crate::client::render::CHUNK_POW;
use super::{Camera, CHUNK_WIDTH};
#[repr(C, align(8))]
#[derive(Clone, Copy, Default, PartialEq)]
pub struct WindowView {
pub stretch: Vector2<f32>,
pub pos: Vector2<f32>,
pub rendered_chunks: Vector2<u32>,
}
unsafe impl bytemuck::Pod for WindowView {}
unsafe impl bytemuck::Zeroable for WindowView {}
impl WindowView {
pub fn from_camera_size(camera: &Camera, size: &Vector2<u32>) -> Self {
let visible_chunks = (size * 2 / CHUNK_WIDTH).add_scalar(1);
let rendered_chunks = Vector2::new(
visible_chunks.x.next_power_of_two(),
visible_chunks.y.next_power_of_two(),
);
let adj_zoom = camera.zoom.level() - CHUNK_POW as i32;
let pos = camera.pos.zip_map(&rendered_chunks, |pos, rc| {
let p = (pos << adj_zoom).with_lens(1, 1);
let (pw, pd) = p.split_whole_dec();
let mut chunk = (pw.parts().first().unwrap_or(&0) & (rc - 1)) as f32;
if pw.is_neg() {
chunk = rc as f32 - chunk;
}
let dec = f32::from(pd);
chunk + dec
});
let stretch = size.cast::<f32>() * camera.zoom.rel_zoom() / (CHUNK_WIDTH as f32);
Self {
pos,
stretch,
rendered_chunks,
}
}
}

View File

@@ -0,0 +1,159 @@
use nalgebra::Vector2;
use crate::client::render::util::Texture;
use super::{
util::{ResizableTexture, Storage},
WindowView, CHUNK_WIDTH,
};
pub struct Layout {
render_bind_layout: wgpu::BindGroupLayout,
render_pipeline_layout: wgpu::PipelineLayout,
format: wgpu::TextureFormat,
pub view: Storage,
pub chunks: ResizableTexture,
}
pub const LABEL: &str = file!();
impl Layout {
pub fn init(
device: &wgpu::Device,
config: &wgpu::SurfaceConfiguration,
input: &Texture,
) -> Self {
let view = Storage::init_with(device, "view", bytemuck::bytes_of(&WindowView::default()));
let texture_desc = wgpu::TextureDescriptor {
label: Some("chunks"),
size: wgpu::Extent3d {
width: CHUNK_WIDTH,
height: CHUNK_WIDTH,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::R32Uint,
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::STORAGE_BINDING,
view_formats: &[wgpu::TextureFormat::R32Uint],
};
let view_desc = wgpu::TextureViewDescriptor {
label: Some("chunk view"),
dimension: Some(wgpu::TextureViewDimension::D2Array),
..Default::default()
};
let chunks = ResizableTexture::new(device, texture_desc, view_desc);
let render_bind_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
entries: &[
view.bind_group_layout_entry(
0,
true,
wgpu::ShaderStages::FRAGMENT | wgpu::ShaderStages::VERTEX,
),
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Uint,
view_dimension: wgpu::TextureViewDimension::D2Array,
multisampled: false,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 2,
visibility: wgpu::ShaderStages::FRAGMENT | wgpu::ShaderStages::VERTEX,
ty: wgpu::BindingType::Texture {
multisampled: false,
view_dimension: wgpu::TextureViewDimension::D2,
sample_type: wgpu::TextureSampleType::Float { filterable: true },
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 3,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
},
],
label: Some(LABEL),
});
let render_pipeline_layout =
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some(LABEL),
bind_group_layouts: &[&render_bind_layout],
push_constant_ranges: &[],
});
Self {
view,
chunks,
render_bind_layout,
render_pipeline_layout,
format: config.format,
}
}
pub fn bind_group(&self, device: &wgpu::Device, input: &Texture) -> wgpu::BindGroup {
device.create_bind_group(&wgpu::BindGroupDescriptor {
layout: &self.render_bind_layout,
entries: &[
self.view.bind_group_entry(0),
self.chunks.view_entry(1),
input.view_bind_group_entry(2),
input.sampler_bind_group_entry(3),
],
label: Some(LABEL),
})
}
pub fn pipeline(
&self,
device: &wgpu::Device,
shader: &wgpu::ShaderModule,
) -> wgpu::RenderPipeline {
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some(LABEL),
layout: Some(&self.render_pipeline_layout),
vertex: wgpu::VertexState {
module: shader,
entry_point: Some("vs_main"),
buffers: &[],
compilation_options: wgpu::PipelineCompilationOptions::default(),
},
fragment: Some(wgpu::FragmentState {
module: shader,
entry_point: Some("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,88 @@
use std::ops::{Deref, DerefMut};
use wgpu::include_wgsl;
mod data;
mod layout;
use super::{util::Texture, *};
pub use data::*;
use layout::*;
pub struct RenderPipeline {
layout: Layout,
pipeline: wgpu::RenderPipeline,
bind_group: wgpu::BindGroup,
size: Vector2<u32>,
}
const SHADER: wgpu::ShaderModuleDescriptor<'_> = include_wgsl!("shader.wgsl");
impl RenderPipeline {
pub fn init(
device: &wgpu::Device,
config: &wgpu::SurfaceConfiguration,
input: &Texture,
) -> Self {
let layout = Layout::init(device, config, input);
let shader = device.create_shader_module(SHADER);
Self {
pipeline: layout.pipeline(device, &shader),
bind_group: layout.bind_group(device, input),
size: Vector2::zeros(),
layout,
}
}
pub fn update(
&mut self,
device: &wgpu::Device,
encoder: &mut wgpu::CommandEncoder,
belt: &mut wgpu::util::StagingBelt,
view: &WindowView,
) {
self.view
.update(device, encoder, belt, bytemuck::bytes_of(view));
}
pub fn draw(&self, encoder: &mut wgpu::CommandEncoder, output: &wgpu::SurfaceTexture) {
let view = output
.texture
.create_view(&wgpu::TextureViewDescriptor::default());
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,
});
render_pass.set_pipeline(&self.pipeline);
render_pass.set_bind_group(0, &self.bind_group, &[]);
render_pass.draw(0..4, 0..1);
}
pub fn resize(&mut self, device: &wgpu::Device, size: Vector2<u32>, input: &Texture) {
self.bind_group = self.layout.bind_group(device, input);
}
}
impl Deref for RenderPipeline {
type Target = Layout;
fn deref(&self) -> &Self::Target {
&self.layout
}
}
impl DerefMut for RenderPipeline {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.layout
}
}

View File

@@ -0,0 +1,76 @@
struct View {
stretch: vec2<f32>,
pos: vec2<f32>,
rendered_chunks: vec2<u32>,
}
@group(0) @binding(0)
var<storage> view: View;
@group(0) @binding(1)
var chunks: texture_2d_array<u32>;
@group(0) @binding(2)
var tex: texture_2d<f32>;
@group(0) @binding(3)
var sam: sampler;
struct VertexOutput {
@builtin(position) vertex_pos: vec4<f32>,
@location(0) world_pos: vec2<f32>,
@location(1) tex_pos: vec2<f32>,
};
@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 / 2.0;
out.world_pos.y *= -1.0;
out.world_pos *= view.stretch;
out.world_pos += view.pos;
let pos2 = vec2<f32>(
f32(vi % 2u),
f32(vi / 2u),
);
out.tex_pos = pos2;
out.tex_pos.y = 1.0 - out.tex_pos.y;
return out;
}
@fragment
fn fs_main(
in: VertexOutput,
) -> @location(0) vec4<f32> {
// let a = textureLoad(chunks, vec2<u32>(0), 0, 0);
// let rc = vec2<i32>(view.rendered_chunks);
// let rcf = vec2<f32>(rc);
// let cposi = vec2<i32>(floor(in.world_pos));
// let cposu = vec2<i32>(
// rem_euclid(cposi.x, rc.x),
// rem_euclid(cposi.y, rc.y)
// );
// let cposf = vec2<f32>(cposu);
// return vec4(cposf / rcf, 0.0, 1.0);
return textureSample(tex, sam, in.tex_pos);
}
fn div_euclid(x: i32, y: i32) -> i32 {
if x < 0 {
return -((-x - 1) / y) - 1;
}
return x / y;
}
fn rem_euclid(x: i32, y: i32) -> i32 {
return x - div_euclid(x, y) * y;
}

View File

@@ -1,35 +0,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;
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

@@ -1,90 +0,0 @@
use super::{util::Storage, View};
pub struct TileLayout {
render_bind_layout: wgpu::BindGroupLayout,
render_pipeline_layout: wgpu::PipelineLayout,
format: wgpu::TextureFormat,
pub view: Storage,
}
impl TileLayout {
pub fn init(device: &wgpu::Device, config: &wgpu::SurfaceConfiguration) -> Self {
let view = Storage::init_with(
device,
"view",
View::default().bytes(),
);
let render_bind_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
entries: &[view.bind_group_layout_entry(0, true)],
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

@@ -1,61 +0,0 @@
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::from_camera_size(camera, size).bytes());
}
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

@@ -6,7 +6,7 @@ pub struct ArrayBuffer<T: bytemuck::Pod> {
buffer: wgpu::Buffer,
label: String,
usage: BufferUsages,
updates: Vec<ArrBufUpdate<T>>,
update: Option<Vec<T>>,
}
impl<T: bytemuck::Pod> ArrayBuffer<T> {
@@ -36,34 +36,24 @@ impl<T: bytemuck::Pod> ArrayBuffer<T> {
if self.len == 0 {
return resized;
}
for update in &self.updates {
if let Some(update) = self.update.take() {
let mut view = belt.write_buffer(
encoder,
&self.buffer,
(update.offset * std::mem::size_of::<T>()) as BufferAddress,
0,
unsafe {
std::num::NonZeroU64::new_unchecked(
std::mem::size_of_val(&update.data[..]) as u64
)
std::num::NonZeroU64::new_unchecked(std::mem::size_of_val(&update[..]) as u64)
},
device,
);
view.copy_from_slice(bytemuck::cast_slice(&update.data));
view.copy_from_slice(bytemuck::cast_slice(&update));
}
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 set(&mut self, data: Vec<T>) {
self.new_len = data.len();
self.update = Some(data);
}
pub fn init(device: &wgpu::Device, label: &str, usage: BufferUsages) -> Self {
@@ -82,7 +72,7 @@ impl<T: bytemuck::Pod> ArrayBuffer<T> {
Self::init_buf_with(device, label, usage, data)
},
label: label.to_string(),
updates: Vec::new(),
update: None,
usage,
}
}
@@ -117,8 +107,8 @@ impl<T: bytemuck::Pod> ArrayBuffer<T> {
pub fn bind_group_layout_entry(
&self,
binding: u32,
visibility: wgpu::ShaderStages,
ty: wgpu::BufferBindingType,
visibility: wgpu::ShaderStages,
) -> wgpu::BindGroupLayoutEntry {
wgpu::BindGroupLayoutEntry {
binding,

View File

@@ -4,56 +4,68 @@ use wgpu::util::DeviceExt;
pub struct Storage {
buffer: wgpu::Buffer,
old_len: usize,
}
impl Storage {
pub fn init<T: Default + bytemuck::Pod>(device: &wgpu::Device, name: &str) -> Self {
let def = [T::default()];
let default = bytemuck::cast_slice(&def);
Self {
buffer: device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some(&(name.to_owned() + " Uniform Buf")),
contents: bytemuck::cast_slice(&[T::default()]),
contents: default,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
}),
old_len: default.len(),
}
}
pub fn init_with(device: &wgpu::Device, name: &str, data: &[u8]) -> Self {
Self {
buffer: device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some(&(name.to_owned() + " Uniform Buf")),
contents: data,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
}),
buffer: Self::init_buf(device, name, data),
old_len: data.len(),
}
}
pub fn init_buf(device: &wgpu::Device, name: &str, data: &[u8]) -> wgpu::Buffer {
device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some(&(name.to_owned() + " Uniform Buf")),
contents: data,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
})
}
pub fn update(
&mut self,
device: &wgpu::Device,
encoder: &mut wgpu::CommandEncoder,
belt: &mut wgpu::util::StagingBelt,
data: &[u8],
) {
let mut view = belt.write_buffer(
encoder,
&self.buffer,
0,
unsafe {
std::num::NonZeroU64::new_unchecked(std::mem::size_of_val(data) as u64)
},
device,
);
view.copy_from_slice(data);
) -> bool {
if data.len() != self.old_len {
self.buffer = Self::init_buf(device, "too lazy", data);
self.old_len = data.len();
true
} else {
let mut view = belt.write_buffer(
encoder,
&self.buffer,
0,
unsafe { std::num::NonZeroU64::new_unchecked(std::mem::size_of_val(data) as u64) },
device,
);
view.copy_from_slice(data);
false
}
}
pub fn bind_group_layout_entry(
&self,
binding: u32,
read_only: bool,
visibility: wgpu::ShaderStages,
) -> wgpu::BindGroupLayoutEntry {
wgpu::BindGroupLayoutEntry {
binding,
visibility: wgpu::ShaderStages::VERTEX
| wgpu::ShaderStages::FRAGMENT
| wgpu::ShaderStages::COMPUTE,
visibility,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Storage { read_only },
has_dynamic_offset: false,

View File

@@ -89,3 +89,39 @@ impl Texture {
self.texture_desc.format
}
}
pub struct ResizableTexture {
texture_desc: wgpu::TextureDescriptor<'static>,
view_desc: wgpu::TextureViewDescriptor<'static>,
pub texture: wgpu::Texture,
pub view: wgpu::TextureView,
}
impl ResizableTexture {
pub fn new(
device: &wgpu::Device,
texture_desc: wgpu::TextureDescriptor<'static>,
view_desc: wgpu::TextureViewDescriptor<'static>,
) -> Self {
let texture = device.create_texture(&texture_desc);
let view = texture.create_view(&view_desc);
Self {
texture,
view,
texture_desc,
view_desc,
}
}
pub fn resize_layers(&mut self, device: &wgpu::Device, layers: u32) {
self.texture_desc.size.depth_or_array_layers = layers;
self.texture = device.create_texture(&self.texture_desc);
self.view = self.texture.create_view(&self.view_desc)
}
pub fn view_entry(&self, binding: u32) -> wgpu::BindGroupEntry {
wgpu::BindGroupEntry {
binding,
resource: wgpu::BindingResource::TextureView(&self.view),
}
}
}

66
src/client/render/view.rs Normal file
View File

@@ -0,0 +1,66 @@
use std::collections::HashSet;
use nalgebra::Vector2;
use crate::{client::camera::Camera, util::FixedDec};
use super::{output::WindowView, CHUNK_POW};
#[derive(Default)]
pub struct ChunkView {
pub render: WindowView,
pub chunk_queue: HashSet<Vector2<FixedDec>>,
pub visible_chunks: HashSet<Vector2<FixedDec>>,
}
impl ChunkView {
pub fn new() -> Self {
Self::default()
}
pub fn update(&mut self, camera: &Camera, size: &Vector2<u32>) {
let render = WindowView::from_camera_size(camera, size);
if self.render == render {
return;
}
self.render = render;
let corner_offset = ((size / 2).cast() * camera.zoom.rel_zoom())
.map(|x| FixedDec::from(x) >> camera.zoom.level());
let bot_left = &camera.pos - &corner_offset;
let top_right = &camera.pos + &corner_offset;
let mult = FixedDec::one() >> (CHUNK_POW as i32 - camera.zoom.level());
let blc = bot_left
.component_mul(&Vector2::from_element(mult.clone()))
.map(FixedDec::floor);
let trc = top_right
.component_mul(&Vector2::from_element(mult))
.map(FixedDec::floor);
let mut visible = HashSet::new();
let mut x = blc.x.clone();
while x <= trc.x {
let mut y = blc.y.clone();
while y <= trc.y {
visible.insert(Vector2::new(x.clone(), y.clone()));
y += FixedDec::one();
}
x += FixedDec::one();
}
let new = visible
.difference(&self.visible_chunks)
.cloned()
.collect::<Vec<_>>();
let old = self
.visible_chunks
.difference(&visible)
.cloned()
.collect::<Vec<_>>();
self.chunk_queue.retain(|p| !old.contains(p));
self.chunk_queue.extend(new);
self.visible_chunks = visible;
}
}