Compare commits

..
56 Commits
Author SHA1 Message Date
Piotr Baja 729daa1503 wip 2024-01-25 20:53:17 +01:00
Piotrek dac7c52912 trying to free unused chunks 2021-05-20 15:13:10 +02:00
Piotrek 7daec14ba7 More informative chunk states, much cleaner 2021-05-20 14:05:43 +02:00
Piotrek a5d109fb0a Loading chunks in radius instead of by travel direction, bugged (not reloading chunks) 2021-05-19 16:07:27 +02:00
Piotrek e9632a20d2 Added notes 2021-05-19 14:26:35 +02:00
Piotrek c4daecae6f writing node buffer to gpu all at once 2021-05-19 14:23:13 +02:00
Piotrek 15e40a941c avoiding copying node buffer 2021-05-19 14:01:40 +02:00
Piotrek 3718c2145e fixed bug where world offset was applied to camera position delayed 1 frame 2021-05-18 20:53:26 +02:00
Piotrek 10182730a5 cpu shifting nodes in a separate thread, very bugged 2021-05-18 16:27:23 +02:00
Piotrek 9fb88c634b some code cleaning 2021-05-18 14:53:04 +02:00
Piotrek 5a62d8c70d handling chunk decompresion errors 2021-05-18 09:20:15 +02:00
Piotrek 688405a579 maybe fixed the out of bounds chunk bug? 2021-05-17 21:47:04 +02:00
Piotrek 65bacd745e not loading already loaded chunks 2021-05-17 15:12:15 +02:00
Piotrek 5266b32823 writing chunks instead of blocks 2021-05-17 14:53:59 +02:00
Piotrek 7359b6549c freeing methods in renderer 2021-05-16 22:50:25 +02:00
Piotrek 00d1fa427a loading chunks in one axis at a time 2021-05-16 21:34:11 +02:00
Piotrek 0487f4acf3 waiting for previous chunk loading before doing next 2021-05-16 21:00:15 +02:00
Piotrek eecedbf4f4 fixed node texture shifting bugs 2021-05-16 19:08:47 +02:00
Piotrek 67bb4fddd0 cleaning 2021-05-16 17:19:23 +02:00
Piotrek 76e952995d preparing for node buffer shifting in a separate thread 2021-05-16 15:01:37 +02:00
Piotrek 57e1a9134e Moved node buffer logic to Nodes struct 2021-05-16 14:25:03 +02:00
Piotrek 77c74be02f ChunkLoader 2021-05-16 14:04:45 +02:00
Piotrek f8760e876c Loading and generating chunks in a separate threads 2021-05-15 20:48:59 +02:00
Piotrek e6a8712de0 preloading stats 2021-05-15 17:16:32 +02:00
Piotrek fa72ea7da4 bug with +z was my stupid mistake :] 2021-05-15 17:10:14 +02:00
Piotrek 16edbfea42 bug fixes, faster node texture shift, loading +z not working (0,0,2 chunk loading only one line) 2021-05-15 17:07:20 +02:00
Piotrek 00c518b9c1 Endless world, still some bugs to figure out 2021-05-14 19:54:46 +02:00
Piotrek e6329d568c World 0,0 is now at the center of the node texture 2021-05-14 14:25:12 +02:00
Piotrek 05b62d4b90 Changed chunk size from 32x32x32 to 16x16x16 2021-05-14 13:04:46 +02:00
Piotrek c1f3d0f398 Threaded loading chunks 2021-05-14 12:10:44 +02:00
Piotrek 120049d9f7 world shifting almost working 2021-05-13 16:18:01 +02:00
Piotrek 98736c39a0 Method for shifting world nodes texture 2021-05-13 11:59:22 +02:00
Piotrek 57782a88ab Dynamic loading, saving, generating chunks works 2021-05-13 09:13:43 +02:00
Piotrek ab9f4c83d0 WIP generating chunks at player position 2021-05-12 16:17:38 +02:00
Piotrek 4acf8ea816 preparing or auto loading chunks around player 2021-05-11 14:46:11 +02:00
Piotrek 6acdd51b05 Some toying with shader 2021-05-11 13:35:47 +02:00
Piotrek 0c7f07bc7b Made blocks texture have equal size, this created problems with memory usage 2021-05-11 13:22:09 +02:00
Piotrek 71e30abb62 Added fog 2021-05-11 10:57:54 +02:00
Piotrek e110e99053 larger block buffer 2021-05-10 22:07:07 +02:00
Piotrek a09f446ecd Pausing rendering, warnings cleanup, more statistics 2021-05-10 15:36:11 +02:00
Piotrek 4eaa0fab56 Compressing with lz4 2021-05-10 13:30:15 +02:00
Piotrek 3bd9fa942e Changed project name (placeholder), some renames 2021-05-10 10:43:14 +02:00
Piotrek 3a45d4e0de minor name changes 2021-05-09 18:48:06 +02:00
Piotrek 0e53810fbd writing node buffer in chunks to allow >48 size 2021-05-09 18:25:44 +02:00
Piotrek 466f98a42c updating node buffer max once per frame 2021-05-09 18:15:30 +02:00
Piotrek bf14d7c578 slightly faster copying blocks data 2021-05-09 18:09:03 +02:00
Piotrek 23d702ad6c Fixed loading world, added saving meta data 2021-05-09 16:27:04 +02:00
Piotrek d0b7a781b4 Loading world, not working yet 2021-05-08 22:55:25 +02:00
Piotrek 547f66d214 Saving world to file 2021-05-08 18:42:32 +02:00
Piotrek 989b75f54d Fonts, glyphs, displaying fps 2021-05-07 15:41:08 +02:00
Piotrek c089afbcbe samplers not required? 2021-05-06 15:53:45 +02:00
Piotrek c16ad4f4a0 bug fixes 2021-05-06 15:49:21 +02:00
Piotrek 45362afbf4 works 2021-05-06 15:38:51 +02:00
Piotrek 5b1ca38d75 ready for testing 2021-05-06 14:49:15 +02:00
Piotrek cb2379786c Combined node and block buffer into one struct: content. 2021-05-06 14:40:47 +02:00
Piotrek d34196a6bd World, WorldChunk, WorldBlock structure working 2021-05-06 12:43:03 +02:00
36 changed files with 1541 additions and 708 deletions
Generated
+193 -15
View File
@@ -1,5 +1,15 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "ab_glyph"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a933731feda8b460bdad9a9e43bb386baba6ec593d2bc19716ef3c75c09085c"
dependencies = [
"ab_glyph_rasterizer",
"owned_ttf_parser 0.12.0",
]
[[package]]
name = "ab_glyph_rasterizer"
version = "0.1.4"
@@ -434,6 +444,26 @@ dependencies = [
"syn",
]
[[package]]
name = "dirs"
version = "3.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30baa043103c9d0c2a57cf537cc2f35623889dc0d405e6c3cccfadbc81c71309"
dependencies = [
"dirs-sys",
]
[[package]]
name = "dirs-sys"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03d86534ed367a67548dc68113a0f5db55432fdfbb6e6f9d77704397d95d5780"
dependencies = [
"libc",
"redox_users",
"winapi 0.3.9",
]
[[package]]
name = "dispatch"
version = "0.2.0"
@@ -816,6 +846,45 @@ dependencies = [
"web-sys",
]
[[package]]
name = "glyph_brush"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e3f00b8574a76fb6c50890c48da03946ca50e4372a2778737922666a2238221"
dependencies = [
"glyph_brush_draw_cache",
"glyph_brush_layout",
"log",
"ordered-float",
"rustc-hash",
"twox-hash",
]
[[package]]
name = "glyph_brush_draw_cache"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac2c82074cafb68b9e459c50c655f7eedcb92d6ee7166813802934bc6fc29fa3"
dependencies = [
"ab_glyph",
"crossbeam-channel",
"crossbeam-deque",
"linked-hash-map",
"rayon",
"rustc-hash",
]
[[package]]
name = "glyph_brush_layout"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10bc06d530bf20c1902f1b02799ab7372ff43f6119770c49b0bc3f21bd148820"
dependencies = [
"ab_glyph",
"approx",
"xi-unicode",
]
[[package]]
name = "gpu-alloc"
version = "0.3.0"
@@ -1025,6 +1094,12 @@ dependencies = [
"winapi 0.3.9",
]
[[package]]
name = "linked-hash-map"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3"
[[package]]
name = "lock_api"
version = "0.4.3"
@@ -1043,6 +1118,15 @@ dependencies = [
"cfg-if 1.0.0",
]
[[package]]
name = "lzzzz"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6d891cedd3b1659c206a60ff8afd15bccd7c2754b157f8a164861989e042b88"
dependencies = [
"cc",
]
[[package]]
name = "malloc_buf"
version = "0.0.6"
@@ -1373,13 +1457,31 @@ version = "1.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3"
[[package]]
name = "ordered-float"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b50b8919aecb97e5ee9aceef27e24f39c46b11831130f4a6b7b091ec5de0de12"
dependencies = [
"num-traits",
]
[[package]]
name = "owned_ttf_parser"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f923fb806c46266c02ab4a5b239735c144bdeda724a50ed058e5226f594cde3"
dependencies = [
"ttf-parser",
"ttf-parser 0.6.2",
]
[[package]]
name = "owned_ttf_parser"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a3c7a20e3f122223e68eef6ca58e39bc1ea8a1d83418ba4c2c1ba189d2ee355"
dependencies = [
"ttf-parser 0.12.0",
]
[[package]]
@@ -1637,6 +1739,22 @@ dependencies = [
"bitflags",
]
[[package]]
name = "redox_users"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64"
dependencies = [
"getrandom 0.2.2",
"redox_syscall",
]
[[package]]
name = "rustc-hash"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "rusttype"
version = "0.9.2"
@@ -1644,7 +1762,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc7c727aded0be18c5b80c1640eae0ac8e396abf6fa8477d96cb37d18ee5ec59"
dependencies = [
"ab_glyph_rasterizer",
"owned_ttf_parser",
"owned_ttf_parser 0.6.0",
]
[[package]]
@@ -1679,6 +1797,20 @@ name = "serde"
version = "1.0.125"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "558dc50e1a5a5fa7112ca2ce4effcb321b0300c0d4ccf0776a9f60cd89031171"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.125"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b093b7a2bb58203b5da3056c05b4ec1fed827dcfdb37347a8841695263b3d06d"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "shaderc"
@@ -1758,6 +1890,12 @@ dependencies = [
"num-traits",
]
[[package]]
name = "static_assertions"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "storage-map"
version = "0.3.0"
@@ -1868,6 +2006,23 @@ version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e5d7cd7ab3e47dda6e56542f4bbf3824c15234958c6e1bd6aaa347e93499fdc"
[[package]]
name = "ttf-parser"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85e00391c1f3d171490a3f8bd79999b0002ae38d3da0d6a3a306c754b053d71b"
[[package]]
name = "twox-hash"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04f8ab788026715fa63b31960869617cba39117e520eb415b0139543e325ab59"
dependencies = [
"cfg-if 0.1.10",
"rand 0.7.3",
"static_assertions",
]
[[package]]
name = "unicode-xid"
version = "0.2.1"
@@ -1880,6 +2035,30 @@ version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
[[package]]
name = "voxel_game"
version = "0.1.0"
dependencies = [
"anyhow",
"bytemuck",
"byteorder",
"cgmath",
"dirs",
"fs_extra",
"futures",
"glob",
"lzzzz",
"noise",
"num_cpus",
"rand 0.8.3",
"serde",
"shaderc",
"toml",
"wgpu",
"wgpu_glyph",
"winit",
]
[[package]]
name = "walkdir"
version = "2.3.2"
@@ -2118,22 +2297,15 @@ dependencies = [
]
[[package]]
name = "wgpufun"
version = "0.1.0"
name = "wgpu_glyph"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "354c1f79e09923724a6006a6953c3946522b84f7a9ec202b7e8001f274fd4bd5"
dependencies = [
"anyhow",
"bytemuck",
"byteorder",
"cgmath",
"fs_extra",
"futures",
"glob",
"image",
"noise",
"rand 0.8.3",
"shaderc",
"glyph_brush",
"log",
"wgpu",
"winit",
]
[[package]]
@@ -2256,6 +2428,12 @@ version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d089681aa106a86fade1b0128fb5daf07d5867a509ab036d99988dec80429a57"
[[package]]
name = "xi-unicode"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a67300977d3dc3f8034dae89778f502b6ba20b269527b3223ba59c0cf393bb8a"
[[package]]
name = "xml-rs"
version = "0.8.3"
+26 -8
View File
@@ -1,22 +1,40 @@
[package]
name = "wgpufun"
name = "voxel_game"
version = "0.1.0"
authors = ["Piotrek <piotrek54pl@gmail.com>"]
authors = ["Piotrek <hello@pbaja.me>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
# Rendering and window management
winit = "0.24"
wgpu = "0.7"
futures = "0.3"
bytemuck = { version = "1.5", features = [ "derive" ] }
byteorder = "1.4"
image = "0.23.14"
wgpu_glyph = "0.11"
# Math
cgmath = "0.18"
rand = "0.8"
noise = "0.7"
# Saving and loading to and from files
dirs = "3.0" # Retrieving common paths (%APPDATA%)
serde = { version = "1.0", features = ["derive"] } # Serialization and deserialization (world.cfg)
toml = "0.5" # Toml file format (used with serde)
lzzzz = "0.8" # Lz4 compression
# Other
futures = "0.3"
bytemuck = { version = "1.5", features = [ "derive" ] }
byteorder = "1.4"
num_cpus = "1.13"
# Deprecated
#image = "0.23.14"
#flate2 = "1.0"
#yaml-rust = "0.4"
#linked-hash-map = "0.5"
[build-dependencies]
anyhow = "1.0"
fs_extra = "1.1"
BIN
View File
Binary file not shown.
-53
View File
@@ -1,53 +0,0 @@
use winit::event::Event;
use noise::Perlin;
use crate::renderer::{camera::CameraTransform, RenderSpace};
mod camera_controller;
use camera_controller::CameraController;
mod world;
use world::World;
pub enum GameState {
Paused,
Running
}
pub struct App {
camera_controller: CameraController,
world: World
}
impl App {
pub fn new(renderer: &mut dyn RenderSpace) -> Self {
Self {
camera_controller: CameraController::new(),
world: World::new("default".to_string())
}
//instance.world_gen.generate(renderer, &Perlin::new());
}
pub fn input(&mut self, event: &Event<()>) {
// Update camera controller
self.camera_controller.handle_input(event);
}
pub fn update_state(&mut self, requested_state: GameState) -> GameState {
match requested_state {
GameState::Paused => {
self.camera_controller.set_enabled(false);
}
GameState::Running => {
self.camera_controller.set_enabled(true);
}
};
// Currently we are not preventing any state changes, just return requested state
requested_state
}
pub fn update_camera(&mut self, delta: f32, camera_transform: &mut dyn CameraTransform) {
self.camera_controller.update(delta, camera_transform);
}
}
-8
View File
@@ -1,8 +0,0 @@
struct Player {
camera_controller: CameraController
}
impl Player {
}
-12
View File
@@ -1,12 +0,0 @@
#[derive(Clone)]
pub struct WorldBlock {
pub data: [u8;32*32*32]
}
impl WorldBlock {
pub fn new() -> Self {
let data = [0_u8; 32*32*32];
Self { data }
}
}
-14
View File
@@ -1,14 +0,0 @@
use cgmath::Point3;
use crate::app::world::WorldBlock;
pub struct WorldChunk {
pos: Point3<u32>,
blocks: Box<[WorldBlock]>
}
impl WorldChunk {
pub fn new(pos: Point3<u32>) -> Self {
let blocks = vec![WorldBlock::new(); 16*16*16].into_boxed_slice();
Self { pos, blocks }
}
}
-81
View File
@@ -1,81 +0,0 @@
use cgmath::Point3;
use noise::{Perlin, NoiseFn};
use rand::{Rng, prelude::ThreadRng};
use crate::renderer::RenderSpace;
use crate::renderer::buffers::BLOCK_SIZE;
const SCALE: f64 = 150.0;
pub struct WorldGen {
}
impl WorldGen {
pub fn new() -> Self {
Self{}
}
/*
* Generate height map for given block position
*/
fn gen_height_map(x: usize, z: usize, rand: &mut ThreadRng, noise: &Perlin) -> ([[usize;BLOCK_SIZE]; BLOCK_SIZE], usize) {
let x = x * BLOCK_SIZE;
let z = z * BLOCK_SIZE;
// Generate map
let mut max: usize = 0;
let mut map = [[0_usize; BLOCK_SIZE]; BLOCK_SIZE];
for lx in 0..BLOCK_SIZE {
for lz in 0..BLOCK_SIZE {
let value = (noise.get([(x + lx) as f64 / SCALE, (z + lz) as f64 /SCALE]) + 1.0) / 2.0;
let mut h = (value * 30.0) as usize;
h += (rand.gen::<f32>() * 2.0) as usize;
if h == 0 { h = 1; }
if h > max { max = h; }
map[lx][lz] = h;
}
}
(map, max)
}
/*
* Generate world
*/
pub fn generate(&mut self, renderer: &mut dyn RenderSpace, noise: &Perlin) {
let mut rng = rand::thread_rng();
let mut dirty: Vec<usize> = Vec::new();
for bx in 0..16 {
for bz in 0..16 {
// Generate height map
let (map, max) = WorldGen::gen_height_map(bx, bz, &mut rng, noise);
let bh = (max / BLOCK_SIZE) + 1;
// Generate blocks
for by in 0..bh {
// Get
let b = renderer.block_get(Point3{x:bx, y:by, z:bz});
dirty.push(b.id);
// Fill
for x in 0..BLOCK_SIZE {
for z in 0..BLOCK_SIZE {
// TODO
let h = map[x][z];
for y in 0..h {
b.set(Point3{x, y, z}, 2);
}
}
}
}
}
}
for id in dirty {
renderer.block_dirty(id);
}
}
}
-26
View File
@@ -1,26 +0,0 @@
mod generator;
use generator::WorldGen;
mod chunk;
use chunk::WorldChunk;
mod block;
use block::WorldBlock;
pub struct World {
name: String,
chunks: Vec<WorldChunk> // Loaded chunks
}
impl World {
pub fn new(name: String) -> Self {
Self { name, chunks: Vec::new() }
}
fn load(&mut self) {
}
fn save(&mut self) {
}
}
@@ -42,12 +42,16 @@ impl CameraController {
pub fn new() -> Self {
Self {
enabled: false,
movement: CameraMovement { speed: 2.0, .. CameraMovement::default()},
movement: CameraMovement { speed: 3.0, .. CameraMovement::default()},
rotation: CameraRotation { yaw: 135.0, sensitivity: 0.2, .. CameraRotation::default() },
cam_position: (-1.0, 2.0, -1.0).into(),
cam_position: (1.0, 2.0, 1.0).into(),
}
}
pub fn get_yaw(&self) -> f32 {
self.rotation.yaw
}
pub fn set_enabled(&mut self, enabled: bool) { self.enabled = enabled; }
pub fn handle_input(&mut self, event: &Event<()>) -> bool {
@@ -105,7 +109,7 @@ impl CameraController {
// Movement amount
let mut amount = delta_time * self.movement.speed;
if self.movement.shift { amount *= 2.0; }
if self.movement.shift { amount *= 8.0; }
// Forward and backwards
if self.movement.fwd && cam_forward.magnitude() > amount { self.cam_position += cam_forward.normalize() * amount; }
+177
View File
@@ -0,0 +1,177 @@
mod camera_controller;
mod player;
pub mod world;
use std::time::Instant;
use crate::renderer::renderer_view::RendererView;
use cgmath::Vector3;
use winit::event::Event;
use player::Player;
use world::World;
use world::chunk::{WorldChunk, ChunkState};
pub const RENDER_DIST: i32 = 3;
pub enum GameState {
Paused,
Running
}
pub struct Game {
state: GameState,
player: Player,
#[allow(unused)]
world: World,
prev_pos: Option<Vector3<i32>>,
prev_chunk_pos: Option<Vector3<i32>>,
}
impl Game {
pub fn new(renderer: &mut impl RendererView) -> Self {
let mut instance = Self {
state: GameState::Paused,
player: Player::new(),
world: World::init("hello".to_string()),
prev_pos: None,
prev_chunk_pos: None,
};
// Preload chunks around player
for x in -RENDER_DIST..RENDER_DIST {
for z in -RENDER_DIST..RENDER_DIST {
instance.world.load(Vector3{x, y: 0, z});
}
}
// Return generated instance
instance
}
fn debug_info(&mut self, renderer: &mut impl RendererView) {
// Update pointing direction
let yaw = self.player.camera_controller.get_yaw();
let mut yaw_str = "";
if yaw >= 315.0 || yaw <= 45.0 { yaw_str = "+z"; }
else if yaw >= 45.0 && yaw < 135.0 { yaw_str = "-x"; }
else if yaw >= 135.0 && yaw < 225.0 { yaw_str = "-z"; }
else if yaw >= 225.0 && yaw < 315.0 { yaw_str = "+x"; }
renderer.get_ui().set_text("World", 0, format!("Direction: {}", yaw_str));
}
pub fn update(&mut self, delta: f32, renderer: &mut impl RendererView) {
// Update camera
let camera = renderer.get_camera_transform();
self.player.camera_controller.update(delta, camera);
// Get positions
let cam_pos = camera.get_position();
let cam_pos = Vector3{x: cam_pos.x as i32, y: 0/*cam_pos.y as i32*/, z: cam_pos.z as i32};
let chunk_pos = cam_pos / world::chunk::SIZE as i32;
if self.prev_pos == None || self.prev_pos != Some(cam_pos) {
renderer.get_ui().set_text("World", 1, format!("Position: {}, {}, {}", cam_pos.x, cam_pos.y, cam_pos.z));
self.prev_pos = Some(cam_pos);
}
// Debug
self.debug_info(renderer);
// Receives chunks from workers
self.world.update();
// Load, unload or write chunks to gpu
if !renderer.is_busy() {
// If chunk position changed
if self.prev_chunk_pos == None || self.prev_chunk_pos != Some(chunk_pos) {
renderer.get_ui().set_text("World", 2, format!("Chunk: {}, {}, {}", chunk_pos.x, chunk_pos.y, chunk_pos.z));
// Shift world
if self.prev_chunk_pos == None { self.prev_chunk_pos = Some(chunk_pos); }
let prev_chunk_pos = self.prev_chunk_pos.unwrap();
let mut dir = chunk_pos - prev_chunk_pos;
if dir.x != 0 { dir = Vector3{x: dir.x.clamp(-1, 1), y:0, z:0 } }
else if dir.y != 0 { dir = Vector3{x:0, y:dir.y.clamp(-1, 1), z:0 } }
else if dir.z != 0 { dir = Vector3{x:0, y:0, z:dir.z.clamp(-1, 1) } }
else { return; }
renderer.shift(dir * world::chunk::SIZE as i32);
// Save previous
self.prev_chunk_pos = Some(prev_chunk_pos+dir);
}
// Load chunks in radius
//TODO: y direction
for ox in -RENDER_DIST..RENDER_DIST+1 {
for oz in -RENDER_DIST..RENDER_DIST+1 {
// Load chunk
let pos = Vector3{x: chunk_pos.x+ox, y: 0, z: chunk_pos.z+oz};
self.world.load(pos).and_then(|ch| -> Option<()> {
// Chunk is ready, schedule it for writing
if ch.state == ChunkState::Ready || ch.state == ChunkState::Stale {
ch.state = ChunkState::Waiting;
}
None
});
}
}
// Send waiting chunks to gpu
for (pos, chunk) in self.world.all() {
if let Some(chunk) = chunk {
// Skip stale chunks
if chunk.state == ChunkState::Stale {
continue;
}
// Calculate if chunk is within render distance
let (ox, oy, oz) = (pos.x - chunk_pos.x, pos.y - chunk_pos.y, pos.z - chunk_pos.z);
let within_distance = ox > -RENDER_DIST && ox < RENDER_DIST
&& oy > -RENDER_DIST && oy < RENDER_DIST
&& oz > -RENDER_DIST && oz < RENDER_DIST;
// Within render distance and waiting
if within_distance && chunk.state == ChunkState::Waiting {
// Write chunk. If succeded change its state to Visible, otherwise make it Ready again.
if renderer.write_chunk(pos, chunk) {
chunk.state = ChunkState::Visible;
} else {
chunk.state = ChunkState::Ready;
}
}
// Outside render distance and visible
if !within_distance && chunk.state == ChunkState::Visible {
// Set chunk to stale, a.k.a. was visible but is not anymore
chunk.state = ChunkState::Stale;
}
}
}
}
}
pub fn input(&mut self, event: &Event<()>) {
// Update camera controller
self.player.camera_controller.handle_input(event);
}
pub fn update_state(&mut self, requested_state: GameState) -> &GameState {
match requested_state {
GameState::Paused => {
self.player.camera_controller.set_enabled(false);
}
GameState::Running => {
self.player.camera_controller.set_enabled(true);
}
};
// Currently we are not preventing any state changes, just return requested state
self.state = requested_state;
&self.state
}
pub fn get_state(&self) -> &GameState {
&self.state
}
}
+13
View File
@@ -0,0 +1,13 @@
use super::camera_controller::CameraController;
pub struct Player {
pub camera_controller: CameraController
}
impl Player {
pub fn new() -> Self {
Self {
camera_controller: CameraController::new()
}
}
}
+22
View File
@@ -0,0 +1,22 @@
use cgmath::Vector3;
pub const SIZE: usize = 32;
pub const SIZE_QB: usize = SIZE*SIZE*SIZE;
#[derive(Clone)]
pub struct WorldBlock {
pub materials: Box<[u8;SIZE_QB]>
}
impl WorldBlock {
pub fn new() -> Self {
Self {
materials: Box::new([0_u8; SIZE_QB])
}
}
pub fn set(&mut self, voxel_pos: Vector3<usize>, value: u8) {
self.materials[voxel_pos.x + SIZE * (voxel_pos.y + SIZE * voxel_pos.z)] = value;
}
}
+135
View File
@@ -0,0 +1,135 @@
use std::sync::Arc;
use std::time::Instant;
use std::thread;
use std::sync::mpsc;
use crate::game::RendererView;
use byteorder::LittleEndian;
use byteorder::ByteOrder;
use cgmath::Vector3;
use lzzzz::lz4;
use crate::game::world::{WorldBlock, block};
pub const SIZE: usize = 16;
const SIZE_SQ: usize = SIZE*SIZE;
const SIZE_QB: usize = SIZE*SIZE*SIZE;
// 1 chunk times on i7-5930K
// alg | size | compr | decom
// raw 32.89kB 0.06s 1.19s
// deflate fast 1.23kB 1.22s 5.46s
// lz4 1.6kB 0.07s 1.23s
#[derive(PartialEq)]
pub enum ChunkState {
Partial, // Not yet loaded
Ready, // Loaded
Waiting, // Scheduled to be uploaded
Visible, // Visible
Stale, // Scheduled to be unloaded
}
pub struct WorldChunk {
nodes: Box<[u32]>,
blocks: Vec<WorldBlock>,
pub state: ChunkState,
}
impl WorldChunk {
pub fn new() -> Self {
Self {
nodes: vec![0_u32; SIZE_QB].into_boxed_slice(),
blocks: Vec::new(),
state: ChunkState::Partial,
}
}
pub fn from_bytes(bytes: Vec<u8>) -> Option<Self> {
let mut instance = Self::new();
// Header
let (header_bytes, bytes) = bytes.split_at(128);
let file_ver = header_bytes[0];
let num_blocks = LittleEndian::read_u32(&header_bytes[1..5]) as usize;
assert_eq!(file_ver, 1);
// Nodes
let (node_bytes, block_bytes_compressed) = bytes.split_at((SIZE_QB*4) as usize);
for i in 0..SIZE_QB {
let idx = (i*4) as usize;
let val = u32::from_le_bytes([node_bytes[idx], node_bytes[idx+1], node_bytes[idx+2], node_bytes[idx+3]]);
instance.nodes[i as usize] = val;
}
// Blocks
let mut block_bytes = vec![0_u8; num_blocks*block::SIZE_QB];
if lz4::decompress(&block_bytes_compressed, &mut block_bytes[0..num_blocks*block::SIZE_QB]).is_err() {
println!("Decompression failed");
return None;
}
for i in 0..num_blocks {
let mut block = WorldBlock::new();
let idx = i * block::SIZE_QB;
block.materials.clone_from_slice(&block_bytes[idx..idx+block::SIZE_QB]);
instance.blocks.push(block);
}
instance.state = ChunkState::Ready;
Some(instance)
}
pub fn to_bytes(&self) -> Vec<u8> {
// Header
let mut bytes: Vec<u8> = vec![0_u8; 5];
bytes[0] = 1_u8; // 1b -> File version
LittleEndian::write_u32(&mut bytes[1..5], self.blocks.len() as u32); // 4b -> Number of blocks in this file
bytes.extend((0..128-5).map(|_| 0)); // Reserved 128b - 5b used
// Nodes
let mut node_bytes = [0_u8; (SIZE_QB*4) as usize];
LittleEndian::write_u32_into(&self.nodes, &mut node_bytes);
bytes.extend(node_bytes.iter());
// Blocks
let mut buf = Vec::new();
for block in self.blocks.iter() {
buf.extend(&block.materials[..]);
}
lz4::compress_to_vec(&buf, &mut bytes, lz4::ACC_LEVEL_DEFAULT).unwrap();
bytes
}
pub fn all_blocks(&self) -> Vec<(Vector3<i32>, &WorldBlock)> {
self.nodes.iter().enumerate()
.filter(|(_, n)| **n != 0)
.map(|(index, node)| {
let mut idx = index;
let z = idx / SIZE_SQ;
idx -= z * SIZE_SQ;
let y = idx / SIZE;
let x = idx % SIZE;
let pos = Vector3{x: x as i32, y: y as i32, z: z as i32};
let block = &self.blocks[(*node-1) as usize];
(pos, block)
})
.collect()
}
pub fn get_block_mut(&mut self, block_pos: &Vector3<i32>) -> &mut WorldBlock {
// Get block index
let index = (block_pos.x + SIZE as i32 * (block_pos.y + SIZE as i32 * block_pos.z)) as usize;
let mut value = self.nodes[index];
// Allocate new block if needed
if value == 0 {
value = (self.blocks.len() + 1) as u32;
self.nodes[index] = value;
self.blocks.push(WorldBlock::new());
}
// Return block reference
&mut self.blocks[(value-1) as usize]
}
}
+16
View File
@@ -0,0 +1,16 @@
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize, Debug)]
pub struct WorldData {
pub name: String,
pub display_name: String
}
impl Default for WorldData {
fn default() -> Self {
Self {
name: String::from("default"),
display_name: String::from("Default")
}
}
}
+69
View File
@@ -0,0 +1,69 @@
use std::time::Instant;
use cgmath::Vector3;
use noise::{Perlin, NoiseFn};
use rand::{Rng, prelude::ThreadRng};
use crate::game::world::{block, block::WorldBlock};
const SCALE: f64 = 150.0;
pub struct WorldGen {
}
impl WorldGen {
/*
* Generate height map for given block position
*/
fn gen_height_map(x: i32, z: i32, rand: &mut ThreadRng, noise: &Perlin) -> ([[usize;block::SIZE]; block::SIZE], usize) {
let size = block::SIZE as i32;
let x = x * size;
let z = z * size;
// Generate map
let mut max: usize = 0;
let mut map = [[0_usize; block::SIZE]; block::SIZE];
for lx in 0..size {
for lz in 0..size {
let value = (noise.get([(x + lx) as f64 / SCALE, (z + lz) as f64 /SCALE]) + 1.0) / 2.0;
let mut h = (value * 30.0) as usize;
h += (rand.gen::<f32>() * 2.0) as usize;
if h == 0 { h = 1; }
if h > max { max = h; }
map[lx as usize][lz as usize] = h;
}
}
(map, max)
}
/*
* Generate block
*/
pub fn generate(block_pos: &Vector3<i32>, block: &mut WorldBlock) {
let mut rng = rand::thread_rng();
let noise = Perlin::new();
// Generate height map
let (map, _max) = WorldGen::gen_height_map(block_pos.x, block_pos.z, &mut rng, &noise);
// Fill
for x in 0..block::SIZE {
for z in 0..block::SIZE {
let mut h = map[x][z];
let mut t = 2;
if block_pos.x == 8 && block_pos.z == 8 {
h = 32;
t = 3;
}
//let h = 16; // testing
for y in 0..h {
block.set(Vector3{x, y, z}, t);
}
}
}
}
}
+126
View File
@@ -0,0 +1,126 @@
use std::cmp::{min, max};
use cgmath::Vector3;
use std::fs;
use std::io::{Read, Write};
use std::thread;
use std::sync::{mpsc, mpsc::Receiver, mpsc::Sender};
use std::sync::{Arc, Mutex};
use super::{chunk, chunk::{WorldChunk, ChunkState}};
use crate::game::world::WorldGen;
type ChunkPos = Vector3<i32>;
type ChunkPosPair = (ChunkPos, WorldChunk);
type ArcRecv<T> = Arc<Mutex<Receiver<T>>>;
pub struct ChunkLoader {
receiver: Receiver<ChunkPosPair>,
queue: Sender<ChunkPos>
}
impl ChunkLoader {
pub fn new(world_name: String) -> Self {
// Create channels
let (result_tx, result_rx) = mpsc::channel();
let (input_tx, input_rx) = mpsc::channel();
let input_rx = Arc::new(Mutex::new(input_rx));
// Figure out number of workers
let mut n = num_cpus::get_physical();
n = max((n as f64 * 0.66).round() as usize, 1);
// Spawn n workers
for _ in 0..n {
let q = Arc::clone(&input_rx);
let tx = result_tx.clone();
let name = world_name.clone();
thread::spawn(move || { ChunkLoader::worker(q, tx, name) });
}
println!("Spawned {} chunk loader workers", n);
// Build struct
Self {
receiver: result_rx,
queue: input_tx
}
}
/*
* Receive loaded chunks
*/
pub fn receive(&mut self) -> Vec<ChunkPosPair> {
let mut result = Vec::new();
while let Ok((pos, chunk)) = self.receiver.try_recv() {
result.push((pos, chunk));
}
result
}
/*
* Schedule loading chunks
*/
pub fn load(&mut self, pos: ChunkPos) {
self.queue.send(pos).unwrap();
}
/*
* Take position from queue and load chunk
*/
fn worker(queue: ArcRecv<ChunkPos>, tx: Sender<ChunkPosPair>, name: String) {
loop {
// Wait for job
let pos;
{
// Get lock
let rx = queue.lock();
if rx.is_err() { break; }
// Receive next pos
let ps = rx.unwrap().recv();
if ps.is_err() { break; }
pos = ps.unwrap();
// Release lock now (end of scope)
}
// Try loading chunk from file
let filename = format!("chunk_{}_{}_{}.dat", pos.x, pos.y, pos.z);
let filepath = dirs::data_local_dir().unwrap().join("Voxelgame").join("saves").join(&name).join(filename);
if filepath.is_file() {
let mut buff = Vec::new();
let mut file = fs::File::open(&filepath).expect("Failed to open chunk file");
file.read_to_end(&mut buff).expect("Failed to read chunk file");
// Create chunk from bytes
if let Some(chunk) = WorldChunk::from_bytes(buff) {
// Send it to channel
if let Err(_) = tx.send((pos, chunk)) {
break;
}
// Process next chunk
continue;
}
// Generate if from_bytes failed
}
// Try generating chunk
let mut chunk = WorldChunk::new();
for x in 0..chunk::SIZE as i32 {
for z in 0..chunk::SIZE as i32 {
let bpos = Vector3::<i32>{x,y:0,z};
let block = chunk.get_block_mut(&bpos);
WorldGen::generate(&(pos*chunk::SIZE as i32 + bpos), block);
}
}
chunk.state = ChunkState::Ready;
// save
fs::create_dir_all(&filepath.parent().unwrap()).expect("Failed to create world directory");
let mut file = fs::File::create(filepath).expect("Failed to create chunk file");
file.write(&chunk.to_bytes()).expect("Failed to write chunk file");
// send
tx.send((pos, chunk)).unwrap();
}
println!("ChunkLoader: worker finished");
}
}
+103
View File
@@ -0,0 +1,103 @@
mod generator;
mod data;
mod loader;
pub mod chunk;
pub mod block;
use std::path::PathBuf;
use std::io::Write;
use std::io::Read;
use std::fs;
use cgmath::Vector3;
use std::collections::HashMap;
use generator::WorldGen;
use chunk::WorldChunk;
use block::WorldBlock;
use data::WorldData;
use loader::ChunkLoader;
pub struct World {
data: WorldData,
loader: ChunkLoader,
chunks: HashMap<Vector3<i32>, Option<WorldChunk>>,
}
impl World {
pub fn init(name: String) -> Self {
let mut instance = Self {
data: WorldData { name: name.clone(), ..Default::default() },
loader: ChunkLoader::new(name.clone()),
chunks: HashMap::new(),
};
instance.load_metadata();
instance.save_all();
instance
}
/*
* Returns vec of references to all currently loaded chunks
*/
pub fn all(&mut self) -> Vec<(&Vector3<i32>, &mut Option<WorldChunk>)> {
self.chunks.iter_mut().collect()
}
/*
* Receives loaded chunks from ChunkLoader
*/
pub fn update(&mut self) {
for (pos, chunk) in self.loader.receive() {
self.chunks.insert(pos, Some(chunk));
}
}
/*
* Sends chunk position to the ChunkLoader if chunk is not loaded
* Returns loaded chunk otherwise
*/
pub fn load(&mut self, pos: Vector3<i32>) -> Option<&mut WorldChunk> {
let mut exists = false;
let result : Option<&mut WorldChunk> = self.chunks.entry(pos).and_modify(|_| { exists = true; }).or_insert(None).as_mut();
if !exists { self.loader.load(pos); }
result
}
/*
* Loads metadata
*/
fn load_metadata(&mut self, ) -> bool {
let filepath = dirs::data_local_dir().unwrap().join("Voxelgame").join(self.data.name.clone()).join("world.dat");
if let Ok(mut file) = fs::File::open(filepath) {
let mut buf = Vec::new();
file.read_to_end(&mut buf).unwrap();
if let Ok(data) = toml::from_slice::<WorldData>(&buf[..]) {
self.data = data;
return true;
}
}
return false;
}
/*
* Saves metadata and all loaded chunks to files
*/
pub fn save_all(&self) {
// Get directory
let dirpath = dirs::data_local_dir().unwrap().join("Voxelgame").join("saves").join(&self.data.name);
fs::create_dir_all(&dirpath).expect("Failed to create world directory");
// Save all chunks
for (pos, chunk) in self.chunks.iter() {
if let Some(chunk) = chunk {
let filepath = dirpath.join(format!("chunk_{}_{}_{}.dat", pos.x, pos.y, pos.z));
let mut file = fs::File::create(filepath).expect("Failed to create chunk file");
file.write(&chunk.to_bytes()).expect("Failed to write chunk file");
}
}
// Save metadata
let datastr = toml::to_string(&self.data).unwrap();
let datapath: PathBuf = dirpath.join("world.dat");
let mut datafile = fs::File::create(datapath).expect("Failed to create world metadata file");
datafile.write_all(datastr.as_bytes()).expect("Failed to write world metadata");
}
}
+42 -19
View File
@@ -1,3 +1,6 @@
use std::time::Instant;
use cgmath::Vector3;
use crate::renderer::renderer_view::RendererView;
use winit::{
event::{Event, WindowEvent, KeyboardInput, ElementState, VirtualKeyCode, MouseButton},
event_loop::{ControlFlow, EventLoop},
@@ -8,8 +11,8 @@ use winit::{
mod renderer;
use renderer::Renderer;
mod app;
use app::{App, GameState};
mod game;
use game::{Game, GameState};
fn main() {
@@ -23,8 +26,8 @@ fn main() {
.build(&events)
.expect("Failed to create window");
let mut renderer = futures::executor::block_on(Renderer::new(&window));
let mut app = App::new(&mut renderer);
let mut renderer = Renderer::new(&window);
let mut game = Game::new(&mut renderer);
// Stats
let mut fps_timer = std::time::Instant::now();
@@ -34,7 +37,7 @@ fn main() {
// Run event loop
events.run(move |ev, _, c| {
// App input
app.input(&ev);
game.input(&ev);
// Other
match ev {
@@ -43,18 +46,33 @@ fn main() {
match event {
// Window closed (ALT+F4,)
WindowEvent::CloseRequested => { *c = ControlFlow::Exit; }
// Escape key
WindowEvent::KeyboardInput { input: KeyboardInput { state: ElementState::Pressed, virtual_keycode: Some(VirtualKeyCode::Escape), .. }, .. } => {
// Request pause
if let GameState::Paused = app.update_state(GameState::Paused) {
window.set_cursor_grab(false).unwrap();
window.set_cursor_visible(true);
// Key pressed
WindowEvent::KeyboardInput { input: KeyboardInput { state: ElementState::Pressed, virtual_keycode, .. }, .. } => {
if let Some(key) = virtual_keycode {
match key {
// Escape pressed
VirtualKeyCode::Escape => {
if let GameState::Paused = game.update_state(GameState::Paused) {
window.set_cursor_grab(false).unwrap();
window.set_cursor_visible(true);
}
}
// Test key
VirtualKeyCode::T => {
let t = &mut renderer as &mut dyn RendererView;
t.shift(Vector3::<i32>{ x:16, y:0, z:0 });
println!("shifted");
}
_ => ()
}
}
}
// Window clicked
WindowEvent::MouseInput { state: ElementState::Pressed, button: MouseButton::Left, .. } => {
// Request unpause
if let GameState::Running = app.update_state(GameState::Running) {
if let GameState::Running = game.update_state(GameState::Running) {
window.set_cursor_grab(true).unwrap();
window.set_cursor_visible(false);
}
@@ -72,21 +90,26 @@ fn main() {
// Loop
Event::MainEventsCleared => {
// Update
let timer = Instant::now();
let delta = delta_timer.elapsed().as_secs_f32();
app.update_camera(delta, &mut renderer.camera);
delta_timer = std::time::Instant::now();
game.update(delta, &mut renderer);
delta_timer = Instant::now();
renderer.ui.set_text("Performance", 1, format!("Update: {} ms", timer.elapsed().as_millis()));
// Redraw
window.request_redraw();
// Redraw if running
//if let GameState::Running = game.get_state() {
window.request_redraw();
//}
}
// Draw
Event::RedrawRequested(_) => {
// Display FPS
let elapsed = fps_timer.elapsed().as_secs_f64();
if elapsed >= 1.5 {
let fps = (fps_counter as f64) / elapsed;
println!("FPS: {}", (fps*100.0_f64).floor()/100.0);
if elapsed >= 0.5 {
let mut fps = (fps_counter as f64) / elapsed;
fps = (fps*100.0_f64).floor() / 100.0;
renderer.ui.set_text("Performance", 0, format!("FPS: {}", fps));
fps_timer = std::time::Instant::now();
fps_counter = 0;
}
-139
View File
@@ -1,139 +0,0 @@
use std::convert::TryInto;
use cgmath::Point3;
use crate::renderer::{buffers, buffers::{BLOCK_SIZE, BLOCK_TEX_SIZE, BLOCK_TEX_LEN, BLOCK_LEN, BLOCK_ARR_LEN}};
#[derive(Clone)]
pub struct RenderBlock {
pub id: usize,
pub data: [u8;BLOCK_LEN]
}
impl RenderBlock {
pub fn new() -> Self {
Self { id: 0, data: [0_u8;BLOCK_LEN] }
}
pub fn set(&mut self, voxel_pos: Point3<usize>, value: u8) {
self.data[voxel_pos.x + buffers::BLOCK_SIZE * (voxel_pos.y + buffers::BLOCK_SIZE * voxel_pos.z)] = value;
}
}
pub struct Blocks {
blocks: Box<[RenderBlock]>,
free_block: usize,
texture: wgpu::Texture,
pub bind_layout: wgpu::BindGroupLayout,
pub bind_group: wgpu::BindGroup
}
impl Blocks {
/*
* Create buffer in memory and gpu
*/
pub fn new(device: &wgpu::Device) -> Self {
// Create texture
let block_tex_dim = BLOCK_TEX_SIZE * BLOCK_SIZE;
let size = wgpu::Extent3d { width: BLOCK_SIZE as u32, height: block_tex_dim as u32, depth: block_tex_dim as u32 };
let texture = device.create_texture(
&wgpu::TextureDescriptor {
size: size,
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D3,
format: wgpu::TextureFormat::R8Uint,
usage: wgpu::TextureUsage::SAMPLED | wgpu::TextureUsage::COPY_DST, // STORAGE?
label: Some("block texture"),
}
);
// Bind layout
let bind_layout = device.create_bind_group_layout(
&wgpu::BindGroupLayoutDescriptor {
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStage::FRAGMENT,
ty: wgpu::BindingType::Texture { multisampled: false, view_dimension: wgpu::TextureViewDimension::D3, sample_type: wgpu::TextureSampleType::Uint },
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStage::FRAGMENT,
ty: wgpu::BindingType::Sampler { comparison: false, filtering: false },
count: None,
},
],
label: Some("Node texture layout"),
}
);
// Bind group
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
address_mode_u: wgpu::AddressMode::ClampToEdge, address_mode_v: wgpu::AddressMode::ClampToEdge, address_mode_w: wgpu::AddressMode::ClampToEdge,
mag_filter: wgpu::FilterMode::Nearest, min_filter: wgpu::FilterMode::Nearest, mipmap_filter: wgpu::FilterMode::Nearest,
..Default::default()
});
let bind_group = device.create_bind_group(
&wgpu::BindGroupDescriptor {
layout: &bind_layout,
entries: &[
wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView(&view) },
wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::Sampler(&sampler) }
],
label: Some("block texture group"),
}
);
// Data
let mut blocks = vec![RenderBlock::new(); BLOCK_ARR_LEN].into_boxed_slice();
for (i, b) in blocks.iter_mut().enumerate() { b.id = i; }
println!("Blocks size: {} MB, max: {}", BLOCK_TEX_LEN as f32 / 1024.0 / 1024.0, BLOCK_ARR_LEN);
// Done
Self { blocks, texture, bind_layout, bind_group, free_block: 0 }
}
/*
* Writes block to gpu
*/
pub fn write(&mut self, queue: &wgpu::Queue, block_id: usize) {
// Get block from id
let block = &self.blocks[block_id];
// Calculate block position in texture
let y = (block.id%BLOCK_TEX_SIZE * BLOCK_SIZE) as u32;
let z = (block.id/BLOCK_TEX_SIZE * BLOCK_SIZE) as u32;
let block_origin = wgpu::Origin3d{ x:0, y, z };
let block_size = wgpu::Extent3d{ width: BLOCK_SIZE as u32, height: BLOCK_SIZE as u32, depth: BLOCK_SIZE as u32 };
let block_bytes : [u8;BLOCK_LEN] = block.data.try_into().unwrap();
// Write to texture
queue.write_texture(
wgpu::TextureCopyView { texture: &self.texture, mip_level: 0, origin: block_origin },
&block_bytes,
wgpu::TextureDataLayout { offset: 0, bytes_per_row: block_size.width, rows_per_image: block_size.height },
block_size
);
}
pub fn get(&mut self, block_id: usize) -> &mut RenderBlock {
&mut self.blocks[block_id]
}
/*
* Allocates new block
*/
pub fn alloc(&mut self) -> &mut RenderBlock {
// Make sure we still have some free blocks
if self.free_block >= BLOCK_ARR_LEN { panic!("No more free blocks! we should consider reusing blocks..."); }
self.free_block += 1;
&mut self.blocks[self.free_block - 1]
}
}
+191
View File
@@ -0,0 +1,191 @@
use std::convert::TryInto;
use cgmath::Vector3;
use crate::game::world::block::WorldBlock;
use super::nodes::{Nodes, NODE_TEX_SIZE, NODE_TEX_SIZE_QB};
//note: remember to update in main.frag
pub const BLOCK_SIZE : usize = 32; // 32x32x32 block size
pub const BLOCK_TEX_SIZE : usize = 40; // number of blocks in texture (32^3 = 1GB, 40^3 = 2GB)
const BLOCK_SIZE_QB: usize = BLOCK_SIZE*BLOCK_SIZE*BLOCK_SIZE;
const BLOCK_TEX_SIZE_SQ: usize = BLOCK_TEX_SIZE*BLOCK_TEX_SIZE;
const BLOCK_TEX_SIZE_QB: usize = BLOCK_TEX_SIZE*BLOCK_TEX_SIZE*BLOCK_TEX_SIZE;
pub struct ContentStats {
pub node_tex_size: f64, // MB
pub block_tex_size: f64, // MB
pub block_tex_used: f64, // MB
pub block_count: u64, // amount
pub block_freed: u64, // amount
pub block_used: u64, // amount
}
pub struct Content {
nodes: Nodes,
node_texture: wgpu::Texture,
block_freed: Vec<usize>,
block_freeidx: usize,
block_texture: wgpu::Texture,
pub bind_layout: wgpu::BindGroupLayout,
pub bind_group: wgpu::BindGroup
}
impl Content {
pub fn new(device: &wgpu::Device) -> Self {
// Create textures
let node_texture = device.create_texture(
&wgpu::TextureDescriptor {
mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D3,
usage: wgpu::TextureUsage::SAMPLED | wgpu::TextureUsage::COPY_DST, label: Some("NodeTexture"),
size: wgpu::Extent3d{ width: NODE_TEX_SIZE as u32, height: NODE_TEX_SIZE as u32, depth: NODE_TEX_SIZE as u32 },
format: wgpu::TextureFormat::R32Uint,
}
);
let block_texture = device.create_texture(
&wgpu::TextureDescriptor {
mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D3,
usage: wgpu::TextureUsage::SAMPLED | wgpu::TextureUsage::COPY_DST, label: Some("BlockTexture"),
size: wgpu::Extent3d{ width: (BLOCK_TEX_SIZE * BLOCK_SIZE) as u32, height: (BLOCK_TEX_SIZE * BLOCK_SIZE) as u32, depth: (BLOCK_TEX_SIZE * BLOCK_SIZE) as u32 },
format: wgpu::TextureFormat::R8Uint,
}
);
// Bind layout
let texture_entry = wgpu::BindGroupLayoutEntry {
binding: 0, visibility: wgpu::ShaderStage::FRAGMENT, count: None,
ty: wgpu::BindingType::Texture { multisampled: false, view_dimension: wgpu::TextureViewDimension::D3, sample_type: wgpu::TextureSampleType::Uint },
};
let bind_layout = device.create_bind_group_layout(
&wgpu::BindGroupLayoutDescriptor {
label: None, entries: &[
wgpu::BindGroupLayoutEntry { binding: 0, ..texture_entry },
wgpu::BindGroupLayoutEntry { binding: 1, ..texture_entry },
],
}
);
// Bind group
let bind_group = device.create_bind_group(
&wgpu::BindGroupDescriptor {
label: None, layout: &bind_layout,
entries: &[
wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView(&node_texture.create_view(&wgpu::TextureViewDescriptor::default())) },
wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::TextureView(&block_texture.create_view(&wgpu::TextureViewDescriptor::default())) },
],
}
);
// Done
let nodes = Nodes::new();
let block_freeidx = 0;
let block_freed = Vec::new();
Self { nodes, block_freed, node_texture, block_freeidx, block_texture, bind_layout, bind_group }
}
pub fn is_busy(&self) -> bool {
self.nodes.is_busy()
}
pub fn get_world_offset(&self) -> Vector3<i32> {
self.nodes.get_world_offset()
}
pub fn update(&mut self, queue: &wgpu::Queue) {
self.nodes.update(queue, &self.node_texture);
}
pub fn stats(&self) -> ContentStats {
const TO_MB: f64 = 1.0 / (1024.0 * 1024.0);
let block_used = self.block_freeidx as u64;
let block_freed = self.block_freed.len() as u64;
let block_count = BLOCK_TEX_SIZE_QB as u64;
let node_tex_size = (NODE_TEX_SIZE_QB*4) as f64 * TO_MB;
let block_tex_used = (block_used * BLOCK_SIZE_QB as u64) as f64 * TO_MB;
let block_tex_size = (BLOCK_TEX_SIZE_QB*BLOCK_SIZE_QB) as f64 * TO_MB;
ContentStats { node_tex_size, block_tex_size, block_tex_used, block_count, block_freed, block_used }
}
pub fn write(&mut self, queue: &wgpu::Queue, block_pos: &Vector3<i32>, block: &WorldBlock) {
// 0,0 at node buffer center
let half = NODE_TEX_SIZE as i32 / 2;
let pos = block_pos + &Vector3{x: half, y: half, z: half};
// Check bounds
let size = NODE_TEX_SIZE as i32;
if pos.x < 0 || pos.x >= size || pos.y < 0 || pos.y >= size || pos.z < 0 || pos.z >= size {
println!("{:?} outside bounds", pos);
return;
}
// Get node
let index = (pos.x + NODE_TEX_SIZE as i32 * (pos.y + NODE_TEX_SIZE as i32 * pos.z)) as usize;
let mut value = self.nodes.get_node(index) as usize;
// Allocate new block
if value == 0 {
// Reuse freed if available, allocate new if not
if let Some(idx) = self.block_freed.pop() {
value = idx;
} else {
value = self.block_freeidx + 1;
}
// Not enough blocks left
if value >= BLOCK_TEX_SIZE_QB {
println!("Blocks buffer filled, no more blocks can be allocated!");
return;
}
// Assign
self.block_freeidx = value;
self.nodes.set_node(index, value as u32);
}
// Calculate position in block texture
let mut idx = value-1;
let z = idx / BLOCK_TEX_SIZE_SQ;
idx -= z * BLOCK_TEX_SIZE_SQ;
let y = idx / BLOCK_TEX_SIZE;
let x = idx % BLOCK_TEX_SIZE;
let block_origin = wgpu::Origin3d{ x: (x*BLOCK_SIZE) as u32, y: (y*BLOCK_SIZE) as u32, z: (z*BLOCK_SIZE) as u32 };
let block_size = wgpu::Extent3d{ width: BLOCK_SIZE as u32, height: BLOCK_SIZE as u32, depth: BLOCK_SIZE as u32 };
let block_bytes : [u8;BLOCK_SIZE*BLOCK_SIZE*BLOCK_SIZE] = block.materials[..].try_into().unwrap();
// Write block
queue.write_texture(
wgpu::TextureCopyView { texture: &self.block_texture, mip_level: 0, origin: block_origin }, &block_bytes,
wgpu::TextureDataLayout { offset: 0, bytes_per_row: block_size.width, rows_per_image: block_size.height }, block_size
);
}
pub fn shift(&mut self, offset: &Vector3<i32>) {
// Work in progress.
// Note: [z is swapped with x ], [ node buffer is not equal to render distance?]
// if offset.x > 0 {
// let size = NODE_TEX_SIZE as i32;
// for x in 0..128 {
// for y in 0..128 {
// for z in 0..32 {
// let index = (x + size * (y + size * z)) as usize;
// let value = self.nodes.get_node(index);
// if value != 0 { self.block_freed.push(value as usize); }
// }
// }
// }
// println!("Saved: {}", self.block_freed.len());
// }
self.nodes.shift(offset);
}
}
+20 -26
View File
@@ -37,39 +37,33 @@ impl Materials {
// Create buffer
let buffer = device.create_buffer_init(
&wgpu::util::BufferInitDescriptor {
label: Some("Materials buffer"),
contents: bytemuck::cast_slice(&materials),
label: Some("Materials buffer"), contents: bytemuck::cast_slice(&materials),
usage: wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST,
}
);
// Create bind group
let bind_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStage::FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
}
// Bind layout
let buffer_entry = wgpu::BindGroupLayoutEntry {
binding: 0, visibility: wgpu::ShaderStage::FRAGMENT, count: None,
ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Uniform, has_dynamic_offset: false, min_binding_size: None },
};
let bind_layout = device.create_bind_group_layout(
&wgpu::BindGroupLayoutDescriptor {
label: None, entries: &[
wgpu::BindGroupLayoutEntry { binding: 0, ..buffer_entry },
],
label: Some("Materials buffer layout"),
});
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
layout: &bind_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: buffer.as_entire_binding(),
}
],
label: Some("Materials buffer group"),
});
// Bind group
let bind_group = device.create_bind_group(
&wgpu::BindGroupDescriptor {
label: Some("Materials buffer group"),layout: &bind_layout,
entries: &[
wgpu::BindGroupEntry { binding: 0, resource: buffer.as_entire_binding() }
],
}
);
// Done
Materials { buffer, materials, bind_layout, bind_group }
}
+11 -36
View File
@@ -1,50 +1,25 @@
// Consts
pub const BLOCK_SIZE : usize = 32; // 32x32x32 block size
pub const BLOCK_TEX_SIZE : usize = 64; // 64x64 blocks in texture
pub const NODE_TEX_SIZE: usize = 48; // 32x32x32 nodes in texture
pub const NODE_TEX_LEN: usize = NODE_TEX_SIZE*NODE_TEX_SIZE*NODE_TEX_SIZE; // Number of u32 in node texture
pub const BLOCK_LEN : usize = BLOCK_SIZE*BLOCK_SIZE*BLOCK_SIZE; // Number of u8 in one block
pub const BLOCK_ARR_LEN: usize = BLOCK_TEX_SIZE*BLOCK_TEX_SIZE; // Number of blocks in texture
pub const BLOCK_TEX_LEN: usize = BLOCK_TEX_SIZE*BLOCK_TEX_SIZE * BLOCK_LEN; // Number of u8 in block texture
// Import names
mod nodes;
pub use nodes::Nodes;
mod blocks;
pub use blocks::Blocks;
pub use blocks::RenderBlock;
mod uniform;
pub use uniform::Uniform;
pub use uniform::UniformValues;
pub mod content;
pub use content::Content;
mod materials;
pub use materials::Materials;
// Create buffers
pub mod nodes;
pub struct Buffers {
pub uniform_buffer: Uniform,
pub node_buffer: Nodes,
pub brick_buffer: Blocks,
pub material_buffer: Materials
pub uniforms: Uniform,
pub content: Content,
pub materials: Materials
}
impl Buffers {
pub fn new(device: &wgpu::Device) -> Self {
let uniform_buffer = Uniform::new(device);
let node_buffer = Nodes::new(device);
let brick_buffer = Blocks::new(device);
let material_buffer = Materials::new(device);
Self { uniform_buffer, node_buffer, brick_buffer, material_buffer }
Self {
uniforms: Uniform::new(device),
content: Content::new(device),
materials: Materials::new(device)
}
}
}
+117 -79
View File
@@ -1,91 +1,129 @@
use byteorder::{ByteOrder, LittleEndian};
use std::sync::Arc;
use std::sync::Mutex;
use std::cmp::{min, max};
use std::time::Instant;
use cgmath::Vector3;
use std::thread;
use std::sync::{RwLock, mpsc, mpsc::Receiver, mpsc::Sender};
use byteorder::{LittleEndian, ByteOrder};
use crate::renderer::buffers::{NODE_TEX_SIZE, NODE_TEX_LEN};
pub const NODE_TEX_SIZE: usize = 128; // 32x32x32 nodes in texture
pub const NODE_TEX_SIZE_SQ: usize = NODE_TEX_SIZE*NODE_TEX_SIZE;
pub const NODE_TEX_SIZE_QB: usize = NODE_TEX_SIZE*NODE_TEX_SIZE*NODE_TEX_SIZE;
type NodeBuffer = (Vector3<i32>, Box<[u32]>);
pub struct Nodes {
pub nodes: Box<[u32]>,
texture: wgpu::Texture,
size: wgpu::Extent3d,
pub bind_layout: wgpu::BindGroupLayout,
pub bind_group: wgpu::BindGroup
buffer: Arc<RwLock<Box<[u32]>>>,
offset: Vector3<i32>,
dirty: bool,
shift: Option<Vector3<i32>>,
working: bool,
channel: (Sender<NodeBuffer>, Receiver<NodeBuffer>)
}
impl Nodes {
pub fn new(device: &wgpu::Device) -> Self {
pub fn new() -> Self {
Self {
// Present
buffer: Arc::new(RwLock::new(vec![0_u32; NODE_TEX_SIZE_QB].into_boxed_slice())),
offset: Vector3{x:0,y:0,z:0},
dirty: false,
// Create texture
let size = wgpu::Extent3d { width: NODE_TEX_SIZE as u32, height: NODE_TEX_SIZE as u32, depth: NODE_TEX_SIZE as u32 };
let texture = device.create_texture(
&wgpu::TextureDescriptor {
size: size,
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D3,
format: wgpu::TextureFormat::R32Uint,
usage: wgpu::TextureUsage::SAMPLED | wgpu::TextureUsage::COPY_DST, // STORAGE?
label: Some("Node texture"),
}
);
// Bind layout
let bind_layout = device.create_bind_group_layout(
&wgpu::BindGroupLayoutDescriptor {
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStage::FRAGMENT,
ty: wgpu::BindingType::Texture { multisampled: false, view_dimension: wgpu::TextureViewDimension::D3, sample_type: wgpu::TextureSampleType::Uint },
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStage::FRAGMENT,
ty: wgpu::BindingType::Sampler { comparison: false, filtering: false },
count: None,
},
],
label: Some("Node texture layout"),
}
);
// Bind group
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
address_mode_u: wgpu::AddressMode::ClampToEdge, address_mode_v: wgpu::AddressMode::ClampToEdge, address_mode_w: wgpu::AddressMode::ClampToEdge,
mag_filter: wgpu::FilterMode::Nearest, min_filter: wgpu::FilterMode::Nearest, mipmap_filter: wgpu::FilterMode::Nearest,
..Default::default()
});
let bind_group = device.create_bind_group(
&wgpu::BindGroupDescriptor {
layout: &bind_layout,
entries: &[
wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView(&view) },
wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::Sampler(&sampler) }
],
label: Some("Node texture group"),
}
);
// Data
let nodes = vec![0_u32; NODE_TEX_LEN].into_boxed_slice();
println!("Nodes size: {} MB", (nodes.len() as f32 * 4.0 / 1024.0 / 1024.0 * 100.0).round() / 100.0);
// Done
Self { nodes, texture, size, bind_layout, bind_group }
shift: None,
working: false,
channel: mpsc::channel()
}
}
pub fn write(&mut self, queue: &wgpu::Queue) {
// Convert 32 bit values to 8 bit
let mut bytes = [0_u8; NODE_TEX_LEN*4];
LittleEndian::write_u32_into(&self.nodes, &mut bytes);
pub fn get_world_offset(&self) -> Vector3<i32> {
self.offset
}
// Write
queue.write_texture(
wgpu::TextureCopyView { texture: &self.texture, mip_level: 0, origin: wgpu::Origin3d::ZERO },
&bytes,
wgpu::TextureDataLayout { offset: 0, bytes_per_row: 4*self.size.width, rows_per_image: self.size.height },
self.size
);
pub fn get_node(&self, index: usize) -> u32 {
self.buffer.read().unwrap()[index]
}
pub fn is_busy(&self) -> bool {
self.shift != None
}
pub fn set_node(&mut self, index: usize, value: u32) {
self.buffer.write().unwrap()[index] = value;
self.dirty = true;
}
pub fn shift(&mut self, offset: &Vector3<i32>) {
if self.shift == None {
self.shift = Some(*offset);
} else {
println!("Tried to shift while another shifing was in progress. Check RendererView.is_busy() before shifting.");
}
}
pub fn update(&mut self, queue: &wgpu::Queue, texture: &wgpu::Texture) {
// Check if there is some more work
if !self.working {
if let Some(offset) = self.shift {
let source = self.buffer.clone();
let tx = self.channel.0.clone();
self.working = true;
// Spawn worker
thread::spawn(move || {
let source = source.read().unwrap();
let size = NODE_TEX_SIZE as i32;
let mut target = vec![0_u32; NODE_TEX_SIZE_QB].into_boxed_slice();
let (ox, oy, oz) = (offset.x, offset.y, offset.z);
// Copy slice of source buffer into target buffer
for x in max(0, -ox)..min(size-ox, size) {
for y in max(0, -oy)..min(size-oy, size) {
for z in max(0, -oz)..min(size-oz, size) {
let idx0 = x + size * (y + size * z);
let idx1 = (x+ox) + size * ((y+oy) + size * (z+oz));
target[idx1 as usize] = source[idx0 as usize];
}
}
}
// Send result
tx.send((offset, target)).unwrap();
});
}
}
// Receive work
while let Ok(result) = self.channel.1.try_recv() {
let moved_by = result.0;
self.offset += moved_by;
self.buffer = Arc::new(RwLock::new(result.1));
self.shift = None;
self.dirty = true;
self.working = false;
}
if self.dirty {
self.dirty = false;
// Note: this takes usually 2ms, up to 9ms
// TODO: Maybe instead of converting u32 to u8, just store u8 and convert them when writing/reading
// Convert u32 node buffer to u8
let node_origin = wgpu::Origin3d{ x:0, y: 0, z: 0 };
let node_size = wgpu::Extent3d { width: NODE_TEX_SIZE as u32, height: NODE_TEX_SIZE as u32, depth: NODE_TEX_SIZE as u32 };
let mut node_bytes = vec![0_u8; NODE_TEX_SIZE_QB*4].into_boxed_slice();
LittleEndian::write_u32_into(&self.buffer.read().unwrap()[..], &mut node_bytes[..]);
// Write nodes
queue.write_texture(
wgpu::TextureCopyView { texture: texture, mip_level: 0, origin: node_origin }, &node_bytes[..],
wgpu::TextureDataLayout { offset: 0, bytes_per_row: 4*node_size.width, rows_per_image: node_size.height }, node_size
);
}
}
}
+20 -27
View File
@@ -23,10 +23,10 @@ impl UniformValues {
Self { sun_dir, .. Default::default() }
}
pub fn update(&mut self, camera: &Camera, time: f32) {
pub fn update(&mut self, camera: &Camera, offset: Vector3<f32>, time: f32) {
self.view_inv = camera.get_view().invert().unwrap().into();
self.proj_inv = camera.get_projection().invert().unwrap().into();
self.cam_pos = camera.get_position().into();
self.cam_pos = (camera.get_position() + offset).into();
self.time = time;
}
}
@@ -47,39 +47,32 @@ impl Uniform {
// Create buffer
let buffer = device.create_buffer_init(
&wgpu::util::BufferInitDescriptor {
label: Some("Uniform buffer"),
contents: bytemuck::cast_slice(&[values]),
label: Some("Uniform buffer"), contents: bytemuck::cast_slice(&[values]),
usage: wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST,
}
);
// Create bind group
// Bind layout
let buffer_entry = wgpu::BindGroupLayoutEntry {
binding: 0, visibility: wgpu::ShaderStage::FRAGMENT, count: None,
ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Uniform, has_dynamic_offset: false, min_binding_size: None },
};
let bind_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStage::FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
}
label: None, entries: &[
wgpu::BindGroupLayoutEntry { binding: 0, ..buffer_entry }
],
label: Some("Uniform buffer layout"),
});
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
layout: &bind_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: buffer.as_entire_binding(),
}
],
label: Some("Uniform buffer group"),
});
// Bind group
let bind_group = device.create_bind_group(
&wgpu::BindGroupDescriptor {
label: None, layout: &bind_layout,
entries: &[
wgpu::BindGroupEntry { binding: 0, resource: buffer.as_entire_binding(), }
],
}
);
// Done
Uniform { buffer, values, bind_layout, bind_group }
}
+5 -1
View File
@@ -11,7 +11,7 @@ impl Camera {
pub fn new(aspect: f32) -> Self {
let mut instance = Self {
position: (2.0,1.0,2.0).into(),
position: (1.0,2.0,1.0).into(),
view_matrix: Matrix4::look_to_rh(
(0.0,0.0,0.0).into(),
Vector3{ x:0.66, y:0.0, z:0.66}.normalize(),
@@ -31,6 +31,7 @@ impl Camera {
pub trait CameraTransform {
fn update(&mut self, position: Point3<f32>, forward: Vector3<f32>, up: Vector3<f32>);
fn get_position(&self) -> Point3<f32>;
}
impl CameraTransform for Camera {
@@ -38,4 +39,7 @@ impl CameraTransform for Camera {
self.position = position;
self.view_matrix = Matrix4::look_to_rh((0.0,0.0,0.0).into(), forward, up);
}
fn get_position(&self) -> Point3<f32> {
self.position
}
}
+68 -36
View File
@@ -1,16 +1,15 @@
use cgmath::Vector3;
use std::time::Instant;
use winit::{window::Window, dpi::PhysicalSize};
pub mod buffers;
use buffers::Buffers;
mod passes;
use passes::{RaytracePass, PostprocessPass};
pub mod camera;
use camera::Camera;
pub mod renderspace;
pub use renderspace::RenderSpace;
pub mod renderer_view;
mod ui;
use ui::UserInterface;
pub struct Renderer {
surface: wgpu::Surface,
@@ -22,12 +21,12 @@ pub struct Renderer {
texture: wgpu::TextureView,
raytrace_pass: RaytracePass,
postprocess_pass: PostprocessPass,
pub ui: UserInterface,
// Buffers
buffers: Buffers,
// Other
pub camera: Camera,
start: std::time::Instant,
changed_bricks: Vec<usize>,
start: std::time::Instant
}
impl Renderer {
@@ -35,18 +34,21 @@ impl Renderer {
/*
* Initializes renderer and all buffers
*/
pub async fn new(window: &Window) -> Self {
pub fn new(window: &Window) -> Self {
// Create surface
let instance = wgpu::Instance::new(wgpu::BackendBit::PRIMARY);
let surface = unsafe { instance.create_surface(window) };
// Pick and initialize device
let adapter_options = wgpu::RequestAdapterOptions { power_preference: wgpu::PowerPreference::HighPerformance, compatible_surface: Some(&surface) };
let adapter = instance.request_adapter(&adapter_options).await.unwrap();
let device_desc = wgpu::DeviceDescriptor { features: wgpu::Features::empty(), limits: wgpu::Limits::default(), label: None };
let (device, queue) = adapter.request_device(&device_desc, None).await.unwrap();
let (adapter, mut device, queue) = futures::executor::block_on(async {
let adapter_options = wgpu::RequestAdapterOptions { power_preference: wgpu::PowerPreference::HighPerformance, compatible_surface: Some(&surface) };
let adapter = instance.request_adapter(&adapter_options).await.unwrap();
let device_desc = wgpu::DeviceDescriptor { features: wgpu::Features::empty(), limits: wgpu::Limits::default(), label: None };
let (device, queue) = adapter.request_device(&device_desc, None).await.unwrap();
(adapter, device, queue)
});
// Create swapchain
let window_size = window.inner_size();
let swapchain_desc = wgpu::SwapChainDescriptor {
@@ -64,7 +66,7 @@ impl Renderer {
// Middleman texture
let texture_descriptor = wgpu::TextureDescriptor {
label: Some("glow_post_process_texture1"),
label: Some("Middle texture"),
size: wgpu::Extent3d { width: swapchain_desc.width,
height: swapchain_desc.height,
depth: 1,
@@ -81,13 +83,11 @@ impl Renderer {
let buffers = Buffers::new(&device);
let raytrace_pass = RaytracePass::new(&device, &swapchain_desc, &buffers);
let postprocess_pass = PostprocessPass::new(&device, &swapchain_desc, &texture);
let ui = UserInterface::new(&mut device, swapchain_desc.format);
// Other
let start = std::time::Instant::now();
let changed_bricks: Vec<usize> = Vec::new();
println!("Initialized");
Self { surface, device, queue, swapchain_desc, swapchain, texture, raytrace_pass, postprocess_pass, buffers, camera, start, changed_bricks }
Self { surface, device, queue, swapchain_desc, swapchain, texture, raytrace_pass, postprocess_pass, ui, buffers, camera, start }
}
/*
@@ -103,8 +103,26 @@ impl Renderer {
// Update camera
self.camera.set_aspect(size.width as f32 / size.height as f32);
}
// Recreate swapchain
self.swapchain = self.device.create_swap_chain(&self.surface, &self.swapchain_desc);
// Recreate texture
let texture_descriptor = wgpu::TextureDescriptor {
label: Some("Middle texture"),
size: wgpu::Extent3d {
width: self.swapchain_desc.width,
height: self.swapchain_desc.height,
depth: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: self.swapchain_desc.format,
usage: wgpu::TextureUsage::SAMPLED | wgpu::TextureUsage::RENDER_ATTACHMENT,
};
self.texture = self.device.create_texture(&texture_descriptor).create_view(&wgpu::TextureViewDescriptor::default());
self.postprocess_pass = PostprocessPass::new(&self.device, &self.swapchain_desc, &self.texture);
}
/*
@@ -112,36 +130,50 @@ impl Renderer {
*/
pub fn render(&mut self) -> Result<(), wgpu::SwapChainError> {
// Update content
let timer = Instant::now();
self.buffers.content.update(&self.queue);
self.ui.set_text("Performance", 3, format!("Update content: {}ms", timer.elapsed().as_micros() as f64 / 1000.0));
// Update uniform buffer
self.buffers.uniform_buffer.values.update(&self.camera, self.start.elapsed().as_secs_f32());
self.queue.write_buffer(&self.buffers.uniform_buffer.buffer, 0, bytemuck::cast_slice(&[self.buffers.uniform_buffer.values]));
let timer = Instant::now();
let off = self.buffers.content.get_world_offset();
let half = (buffers::nodes::NODE_TEX_SIZE / 2) as i32;
let cam_offset = Vector3{x: (off.x + half) as f32, y: (off.y + half) as f32, z: (off.z + half) as f32 };
// Update changed nodes
if self.changed_bricks.len() > 0 {
self.buffers.node_buffer.write(&self.queue);
for block_id in &self.changed_bricks {
self.buffers.brick_buffer.write(&self.queue, *block_id);
}
// let used = self.buffers.brick_buffer.next_block(false);
// let total = buffers::BLOCK_TEX_SIZE * buffers::BLOCK_TEX_SIZE;
// let changed = self.changed_bricks.len();
// println!("Used: {}/{} Changed: {}", used, total, changed);
self.changed_bricks.clear();
}
self.buffers.uniforms.values.update(&self.camera, cam_offset, self.start.elapsed().as_secs_f32());
self.queue.write_buffer(&self.buffers.uniforms.buffer, 0, bytemuck::cast_slice(&[self.buffers.uniforms.values]));
self.ui.set_text("Performance", 4, format!("Update uniform: {}ms", timer.elapsed().as_micros() as f64 / 1000.0));
// Get next frame to render to
let timer = Instant::now();
let frame = self.swapchain.get_current_frame()?.output;
self.ui.set_text("Performance", 5, format!("Swapchain get frame: {}ms", timer.elapsed().as_micros() as f64 / 1000.0));
// Create encoder that will build command buffer for us
let timer = Instant::now();
let mut encoder = self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("Render Encoder"),});
self.raytrace_pass.render(&mut encoder, &mut self.buffers, &self.texture);//&frame.view);
self.postprocess_pass.render(&mut encoder, &frame.view);
self.ui.render(&self.device, &mut encoder, &frame.view, 1280, 720);
self.ui.set_text("Performance", 6, format!("Build renderpass: {}ms", timer.elapsed().as_micros() as f64 / 1000.0));
// Submit encoder (command buffer)
let timer = Instant::now();
self.queue.submit(std::iter::once(encoder.finish()));
self.ui.recall();
self.ui.set_text("Performance", 7, format!("Submit queue: {}ms", timer.elapsed().as_micros() as f64 / 1000.0));
// Stats
let content_stats = self.buffers.content.stats();
self.ui.set_text("Renderer", 0, format!("Nodes: {}MB", content_stats.node_tex_size));
self.ui.set_text("Renderer", 1, format!("Blocks: {} / {} MB ({} -{} / {})",
content_stats.block_tex_used,
content_stats.block_tex_size,
content_stats.block_used,
content_stats.block_freed,
content_stats.block_count
));
// Return ok
Ok(())
+6 -8
View File
@@ -28,10 +28,9 @@ impl RaytracePass {
// Bind group layouts
let bind_group_layouts = [
&buffers.uniform_buffer.bind_layout,
&buffers.node_buffer.bind_layout,
&buffers.brick_buffer.bind_layout,
&buffers.material_buffer.bind_layout
&buffers.uniforms.bind_layout,
&buffers.content.bind_layout,
&buffers.materials.bind_layout
];
// Pipeline layout
@@ -81,10 +80,9 @@ impl RaytracePass {
render_pass.set_pipeline(&self.pipeline);
// Bind groups
render_pass.set_bind_group(0, &buffers.uniform_buffer.bind_group, &[]);
render_pass.set_bind_group(1, &buffers.node_buffer.bind_group, &[]);
render_pass.set_bind_group(2, &buffers.brick_buffer.bind_group, &[]);
render_pass.set_bind_group(3, &buffers.material_buffer.bind_group, &[]);
render_pass.set_bind_group(0, &buffers.uniforms.bind_group, &[]);
render_pass.set_bind_group(1, &buffers.content.bind_group, &[]);
render_pass.set_bind_group(2, &buffers.materials.bind_group, &[]);
// Draw 2 triangles
render_pass.draw(0..6, 0..1);
+53
View File
@@ -0,0 +1,53 @@
use crate::renderer::UserInterface;
use crate::renderer::camera::CameraTransform;
use crate::Renderer;
use crate::{game, game::world::{chunk, chunk::WorldChunk}};
use cgmath::Vector3;
pub trait RendererView {
fn write_chunk(&mut self, chunk_pos: &Vector3<i32>, chunk: &WorldChunk) -> bool;
fn shift(&mut self, offset: Vector3<i32>);
fn get_camera_transform(&mut self) -> &mut dyn CameraTransform;
fn get_ui(&mut self) -> &mut UserInterface;
fn is_busy(&self) -> bool;
}
impl RendererView for Renderer {
fn write_chunk(&mut self, chunk_pos: &Vector3<i32>, chunk: &WorldChunk) -> bool {
// Calculate chunk position in renderer space
let chunk_off = self.buffers.content.get_world_offset() / chunk::SIZE as i32;
let pos = chunk_pos + chunk_off;
// Make sure we are withing current world bounds
if pos.x.abs() > game::RENDER_DIST { println!("out of bounds! x: {} off: {:?} pos: {:?}", pos.x, chunk_off, chunk_pos); return false; }
if pos.y.abs() > game::RENDER_DIST { println!("out of bounds! y: {} off: {:?} pos: {:?}", pos.y, chunk_off, chunk_pos); return false; }
if pos.z.abs() > game::RENDER_DIST { println!("out of bounds! z: {} off: {:?} pos: {:?}", pos.z, chunk_off, chunk_pos); return false; }
// Write chunk to renderer
//println!("write chunk {:?}", pos);
let pos = pos * chunk::SIZE as i32;
for (block_pos, block) in chunk.all_blocks() {
self.buffers.content.write(&self.queue, &(block_pos + pos), block);
}
true
}
/*
* offset: By how much to shift the world, in blocks
*/
fn shift(&mut self, offset: Vector3<i32>) {
self.buffers.content.shift(&-offset);
//println!("World shifted by {:?}", offset);
}
fn get_camera_transform(&mut self) -> &mut dyn CameraTransform {
&mut self.camera
}
fn get_ui(&mut self) -> &mut UserInterface {
&mut self.ui
}
fn is_busy(&self) -> bool {
self.buffers.content.is_busy()
}
}
-44
View File
@@ -1,44 +0,0 @@
use cgmath::Point3;
use crate::renderer::{Renderer, buffers};
pub trait RenderSpace {
fn block_get(&mut self, block_pos: Point3<usize>) -> &mut buffers::RenderBlock;
fn block_dirty(&mut self, id: usize);
}
impl RenderSpace for Renderer {
/*
* Grabs existing or allocates new block for given position
*
* block_pos: Position of the block in world, in block space
* block_id: Block identifier, equal to Block.id
*/
fn block_get(&mut self, block_pos: Point3<usize>) -> &mut buffers::RenderBlock {
// Get node
let node_idx = block_pos.x + buffers::NODE_TEX_SIZE * (block_pos.y + buffers::NODE_TEX_SIZE * block_pos.z);
let node_value = self.buffers.node_buffer.nodes[node_idx];
// Allocate new block
if node_value == 0 {
let block = self.buffers.brick_buffer.alloc();
self.buffers.node_buffer.nodes[node_idx] = (block.id+1) as u32;
return block;
}
// Return existing block
self.buffers.brick_buffer.get((node_value-1) as usize)
}
/*
* Marks block as dirty, will be sent to gpu at the next frame
*
* block_id: Block identifier, equal to Block.id
*/
fn block_dirty(&mut self, block_id: usize) {
if !self.changed_bricks.contains(&block_id) {
self.changed_bricks.push(block_id);
}
}
}
-61
View File
@@ -1,61 +0,0 @@
use std::path::Path;
use image::GenericImageView;
pub struct Texture {
pub texture: wgpu::Texture,
pub view: wgpu::TextureView,
pub sampler: wgpu::Sampler,
}
impl Texture {
pub fn from_file(device: &wgpu::Device, queue: &wgpu::Queue, str_path: &str) -> Self {
let img_path = Path::new(str_path);
let img = image::open(img_path).unwrap();
Self::from_image(&device, &queue, &img)
}
pub fn from_image(device: &wgpu::Device, queue: &wgpu::Queue, img: &image::DynamicImage) -> Self {
// Data from image
let img_dim = img.dimensions();
let img_data = img.as_rgba8().unwrap();
// Create texture on device
let texture_size = wgpu::Extent3d { width: img_dim.0, height: img_dim.1, depth: 1, };
let texture = device.create_texture(&wgpu::TextureDescriptor {
size: texture_size,
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rgba8UnormSrgb,
usage: wgpu::TextureUsage::SAMPLED | wgpu::TextureUsage::COPY_DST,
label: None,
});
// Write texture to memory
queue.write_texture(
wgpu::TextureCopyView { texture: &texture, mip_level: 0, origin: wgpu::Origin3d::ZERO },
img_data,
wgpu::TextureDataLayout { offset: 0, bytes_per_row: 4 * img_dim.0, rows_per_image: img_dim.1 },
texture_size,
);
// Create view and sampler
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
let sampler = device.create_sampler(
&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,
mag_filter: wgpu::FilterMode::Nearest,
min_filter: wgpu::FilterMode::Nearest,
mipmap_filter: wgpu::FilterMode::Nearest,
..Default::default()
}
);
Self { texture, view, sampler }
}
}
+75
View File
@@ -0,0 +1,75 @@
use std::collections::HashMap;
use wgpu_glyph::{GlyphBrush, ab_glyph::FontArc, GlyphBrushBuilder, Section, Text};
use wgpu::util::StagingBelt;
use futures::executor::LocalPool;
use futures::task::SpawnExt;
const WHITE: [f32; 4] = [1.0, 1.0, 1.0, 1.0];
//const BLUE: [f32; 4] = [0.0, 0.2, 1.0, 1.0];
const GREEN: [f32; 4] = [0.1, 1.0, 0.0, 1.0];
pub struct UserInterface {
// Glyph drawing
staging_belt: StagingBelt,
local_pool: LocalPool,
brush: GlyphBrush<()>,
// Texts
texts: HashMap<String, Vec<String>>
}
impl UserInterface {
pub fn new(device: &mut wgpu::Device, render_format: wgpu::TextureFormat) -> Self {
let font = FontArc::try_from_slice(include_bytes!("../../assets/font.ttf")).unwrap();
Self {
staging_belt: StagingBelt::new(1024),
local_pool: LocalPool::new(),
brush: GlyphBrushBuilder::using_font(font).build(device, render_format),
texts: HashMap::new()
}
}
fn print(brush: &mut GlyphBrush<()>, line: u32, text: &str, color: [f32; 4]) {
let t = Text::new(text).with_color(color);
brush.queue(Section {
screen_position: (5 as f32, (5 + line*15) as f32),
bounds: (t.scale.x * t.text.len() as f32, t.scale.y),
text: vec![t],
..Section::default()
});
}
pub fn set_text(&mut self, cat: &str, line: usize, text: String) {
// Category
let c = &cat.to_string();
if !self.texts.contains_key(c) {self.texts.insert(c.clone(), Vec::new());}
// Texts
let v = self.texts.get_mut(c).unwrap();
while v.len() < line+1 { v.push(String::new()); }
v[line] = text;
}
pub fn render(&mut self, device: &wgpu::Device, encoder: &mut wgpu::CommandEncoder, target: &wgpu::TextureView, width: u32, height: u32) {
let mut line = 0;
for (cat, texts) in self.texts.iter_mut() {
UserInterface::print(&mut self.brush, line, cat, GREEN);
line += 1;
for text in texts.iter() {
UserInterface::print(&mut self.brush, line, text, WHITE);
line += 1;
}
line += 1;
}
// Draw
self.brush.draw_queued(&device, &mut self.staging_belt, encoder, target, width, height).expect("Draw queued");
self.staging_belt.finish();
}
pub fn recall(&mut self) {
// Reset staging belt after drawing
self.local_pool.spawner().spawn(self.staging_belt.recall()).expect("Recall staging belt");
self.local_pool.run_until_stalled();
}
}
Binary file not shown.
+30 -9
View File
@@ -15,14 +15,14 @@ layout(set=0, binding=0) uniform Uniform {
float _Time;
};
layout(set=1, binding=0) uniform usampler3D _NodesTexture;
layout(set=2, binding=0) uniform usampler3D _BricksTexture;
layout(set=3, binding=0) uniform Materials { Material _Materials[256]; };
layout(set=1, binding=1) uniform usampler3D _BricksTexture;
layout(set=2, binding=0) uniform Materials { Material _Materials[256]; };
// Defines
#define EPSILON 0.00000001
#define SCALE 1.0
#define NODE_TEX_SIZE 48
#define BLOCK_TEX_SIZE 64
#define NODE_TEX_SIZE 64
#define BLOCK_TEX_SIZE 40
#define BLOCK_SIZE 32
// Includes
@@ -36,28 +36,49 @@ vec3 solve()
// Calculate ray direction
vec3 view_dir = (_ProjMatrixInv * vec4(_InUV, 0, 1)).xyz;
vec3 rayDir = normalize(_ViewMatrix * vec4(view_dir,0)).xyz;
Ray primaryRay = Ray((_CamPos + vec3(16, 0, 16)) / SCALE, rayDir);
Ray primaryRay = Ray(_CamPos / SCALE, rayDir);
// Raycast
HitResult primary = castNodes(primaryRay, 100);
if(primary.data > 0)
{
vec3 color = applyLighting(primary.pos, primaryRay.dir, primary.normal, primary.data);
vec3 col = applyLighting(primary.pos, primaryRay.dir, primary.normal, primary.data);
vec3 hitPos = primary.pos-primary.normal*EPSILON;
// Shadow ray
Ray shadowRay = Ray(hitPos, _SunDir);
HitResult shadow = castNodes(shadowRay, 50);
if(shadow.data > 0) color *= 0.1;
if(shadow.data > 0) col *= 0.1;
// Secondary ray
// Ray secondRay = Ray(hitPos, randomHemisphere(primary.normal));
// HitResult second = castNodes(secondRay, 25);
// if(second.data > 0) color += getAlbedo(second.data) * 0.8;
// if(second.data > 0) col += getAlbedo(second.data) * 0.8;
return color;
// Sun glare
//float sun = clamp(dot(_SunDir, primaryRay.dir), 0.0, 1.0 );
//col += 0.25 * vec3(0.8,0.4,0.2) * pow(sun, 4.0 );
// Gamma
//col = pow(clamp(col*1.1-0.02,0.0,1.0), vec3(0.4545));
// Contrast
//col = col*col*(3.0-2.0*col);
// Color grade
//col = pow( col, vec3(1.0,0.92,1.0) ); // soft green
//col *= vec3(1.02,0.99,0.99); // tint red
//col.z = (col.z+0.1)/1.1; // bias blue
// Fog
// float dist = distance(primary.pos, primaryRay.origin);
// float fog = exp2(-pow(0.015*dist, 2));
// col = mix(vec3(0.8, 0.8, 0.75), col, clamp(fog, 0.0, 1.0));
return col;
}
// Sky
float up = clamp(dot(primaryRay.dir, vec3(0, 1, 0)) + 0.1, 0, 1);
return vec3(0.176, 0.592, 0.901) * up;
}
+8 -3
View File
@@ -22,9 +22,13 @@ HitResult castBricks(vec3 origin, vec3 start, vec3 dir, vec3 incAxis, uint addr)
vec3 dist = (pos-origin+0.5 + raySign*0.5) * rayInv;
// Calculate offset
uint y = (addr-1) % BLOCK_TEX_SIZE * BLOCK_SIZE;
uint z = (addr-1) / BLOCK_TEX_SIZE * BLOCK_SIZE;
ivec3 offset = ivec3(0, y, z);
uint idx = addr-1;
uint z = idx / (BLOCK_TEX_SIZE*BLOCK_TEX_SIZE);
idx -= z * (BLOCK_TEX_SIZE*BLOCK_TEX_SIZE);
uint y = idx / BLOCK_TEX_SIZE;
uint x = idx % BLOCK_TEX_SIZE;
ivec3 offset = ivec3(x * BLOCK_SIZE, y * BLOCK_SIZE, z * BLOCK_SIZE);
for(int i=0;i<110;i++)
{
@@ -94,6 +98,7 @@ HitResult castNodes(Ray ray, uint maxSteps)
incAxis = step(dist.xyz, dist.yzx) * step(dist.xyz, dist.zxy);
dist += incAxis * raySign * rayInv;
pos += incAxis * raySign;
t = dot(dist, incAxis);
// Outside bounds
// if(pos.x < 0 || pos.y < 0 || pos.z < 0) { break; }
+8
View File
@@ -0,0 +1,8 @@
{
"folders": [
{
"path": "."
}
],
"settings": {}
}