Basic texture rendering
This commit is contained in:
Generated
+110
-2
@@ -34,7 +34,7 @@ dependencies = [
|
||||
"getrandom 0.2.16",
|
||||
"once_cell",
|
||||
"version_check",
|
||||
"zerocopy",
|
||||
"zerocopy 0.7.35",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -606,10 +606,16 @@ checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b"
|
||||
name = "gaym"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"bytemuck",
|
||||
"env_logger",
|
||||
"glam",
|
||||
"log",
|
||||
"noise",
|
||||
"png",
|
||||
"rand 0.9.1",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror 2.0.12",
|
||||
"vulkano 0.35.1",
|
||||
"vulkano-shaders",
|
||||
@@ -1001,6 +1007,17 @@ dependencies = [
|
||||
"memoffset",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "noise"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6da45c8333f2e152fc665d78a380be060eb84fad8ca4c9f7ac8ca29216cff0cc"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
"rand 0.8.5",
|
||||
"rand_xorshift",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "7.1.3"
|
||||
@@ -1011,6 +1028,15 @@ dependencies = [
|
||||
"minimal-lexical",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_enum"
|
||||
version = "0.5.11"
|
||||
@@ -1495,6 +1521,15 @@ dependencies = [
|
||||
"portable-atomic",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
|
||||
dependencies = [
|
||||
"zerocopy 0.8.24",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-crate"
|
||||
version = "1.3.1"
|
||||
@@ -1547,6 +1582,59 @@ version = "5.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5"
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||
dependencies = [
|
||||
"rand_core 0.6.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97"
|
||||
dependencies = [
|
||||
"rand_chacha",
|
||||
"rand_core 0.9.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core 0.9.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
|
||||
dependencies = [
|
||||
"getrandom 0.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_xorshift"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f"
|
||||
dependencies = [
|
||||
"rand_core 0.6.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "raw-window-handle"
|
||||
version = "0.5.2"
|
||||
@@ -2915,7 +3003,16 @@ version = "0.7.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
"zerocopy-derive 0.7.35",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879"
|
||||
dependencies = [
|
||||
"zerocopy-derive 0.8.24",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2928,3 +3025,14 @@ dependencies = [
|
||||
"quote",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
@@ -4,10 +4,16 @@ version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
bitflags = "2.9.0"
|
||||
bytemuck = { version = "1.22.0", features = ["derive"] }
|
||||
env_logger = "0.11.8"
|
||||
glam = { version = "0.30.2", features = ["bytemuck"] }
|
||||
log = "0.4.27"
|
||||
noise = "0.9.0"
|
||||
png = "0.17.16"
|
||||
rand = "0.9.1"
|
||||
serde = { version = "1.0.219", features = ["derive"] }
|
||||
serde_json = "1.0.140"
|
||||
thiserror = "2.0.12"
|
||||
vulkano = "0.35.1"
|
||||
vulkano-shaders = "0.35.0"
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"model": {
|
||||
"simple": "dirt"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"model": {
|
||||
"grass_like": {
|
||||
"side": "grass_side",
|
||||
"bottom": "dirt",
|
||||
"top": "grass_top"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"model": {
|
||||
"simple": "stone"
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 2.7 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 222 B |
Binary file not shown.
|
After Width: | Height: | Size: 2.7 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.7 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.6 KiB |
@@ -1,3 +1,5 @@
|
||||
use std::io;
|
||||
|
||||
use vulkano::{
|
||||
buffer::AllocateBufferError, command_buffer::CommandBufferExecError, image::AllocateImageError,
|
||||
memory::allocator::MemoryAllocatorError, pipeline::layout::IntoPipelineLayoutCreateInfoError,
|
||||
@@ -13,6 +15,11 @@ pub enum Error {
|
||||
#[error("Window creation error: {0}")]
|
||||
WinitOs(#[from] winit::error::OsError),
|
||||
|
||||
#[error("{0}")]
|
||||
Io(#[from] io::Error),
|
||||
#[error("PNG decode error: {0}")]
|
||||
PngDecode(#[from] png::DecodingError),
|
||||
|
||||
#[error("Vulkan loading error: {0}")]
|
||||
Loading(#[from] LoadingError),
|
||||
#[error("Window handle error: {0}")]
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#![feature(duration_constants)]
|
||||
|
||||
use std::{process::ExitCode, sync::Arc};
|
||||
|
||||
use error::Error;
|
||||
@@ -12,6 +14,7 @@ use winit::{
|
||||
|
||||
pub mod error;
|
||||
pub mod render;
|
||||
pub mod world;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Engine {
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
use std::{collections::HashMap, fs::File, path::PathBuf, sync::Arc};
|
||||
|
||||
use serde::Deserialize;
|
||||
use vulkano::{device::Queue, image::view::ImageView};
|
||||
|
||||
use crate::error::Error;
|
||||
|
||||
use super::{
|
||||
model::{BlockModelRegistry, CubeTextures},
|
||||
texture::TextureRegistryBuilder,
|
||||
};
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct BlockDefinition {
|
||||
pub model: BlockModelDefinition,
|
||||
}
|
||||
|
||||
pub struct BlockRegistry {
|
||||
name_to_id: HashMap<String, u8>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum BlockModelDefinition {
|
||||
Simple(String),
|
||||
Sided {
|
||||
front: String,
|
||||
left: String,
|
||||
back: String,
|
||||
right: String,
|
||||
bottom: String,
|
||||
top: String,
|
||||
},
|
||||
GrassLike {
|
||||
side: String,
|
||||
top: String,
|
||||
bottom: String,
|
||||
},
|
||||
}
|
||||
|
||||
pub struct BlockRegistryBuilder {
|
||||
texture_registry_builder: TextureRegistryBuilder,
|
||||
definitions: HashMap<u8, CubeTextures>,
|
||||
name_to_id: HashMap<String, u8>,
|
||||
last_block_id: u8,
|
||||
}
|
||||
|
||||
struct BlockSides<'a> {
|
||||
front: &'a str,
|
||||
left: &'a str,
|
||||
back: &'a str,
|
||||
right: &'a str,
|
||||
bottom: &'a str,
|
||||
top: &'a str,
|
||||
}
|
||||
|
||||
impl BlockModelDefinition {
|
||||
fn sides(&self) -> BlockSides {
|
||||
match self {
|
||||
Self::Simple(texture) => BlockSides {
|
||||
front: texture,
|
||||
left: texture,
|
||||
back: texture,
|
||||
right: texture,
|
||||
bottom: texture,
|
||||
top: texture,
|
||||
},
|
||||
Self::Sided {
|
||||
front,
|
||||
left,
|
||||
back,
|
||||
right,
|
||||
bottom,
|
||||
top,
|
||||
} => BlockSides {
|
||||
front,
|
||||
left,
|
||||
back,
|
||||
right,
|
||||
bottom,
|
||||
top,
|
||||
},
|
||||
Self::GrassLike { side, top, bottom } => BlockSides {
|
||||
front: side,
|
||||
left: side,
|
||||
back: side,
|
||||
right: side,
|
||||
bottom,
|
||||
top,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BlockRegistryBuilder {
|
||||
pub fn new(texture_registry_builder: TextureRegistryBuilder) -> Self {
|
||||
Self {
|
||||
texture_registry_builder,
|
||||
definitions: HashMap::new(),
|
||||
name_to_id: HashMap::new(),
|
||||
last_block_id: 1,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load(&mut self, name: &str) -> Result<(), Error> {
|
||||
let path = PathBuf::from("assets/blocks").join(format!("{name}.json"));
|
||||
let file = File::open(path)?;
|
||||
let definition = serde_json::from_reader(file).unwrap();
|
||||
|
||||
self.add(name, definition);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn add(&mut self, name: &str, definition: BlockDefinition) {
|
||||
let id = self.last_block_id;
|
||||
|
||||
let sides = definition.model.sides();
|
||||
let front = self
|
||||
.texture_registry_builder
|
||||
.add_texture_or_fallback(sides.front);
|
||||
let left = self
|
||||
.texture_registry_builder
|
||||
.add_texture_or_fallback(sides.left);
|
||||
let back = self
|
||||
.texture_registry_builder
|
||||
.add_texture_or_fallback(sides.back);
|
||||
let right = self
|
||||
.texture_registry_builder
|
||||
.add_texture_or_fallback(sides.right);
|
||||
let bottom = self
|
||||
.texture_registry_builder
|
||||
.add_texture_or_fallback(sides.bottom);
|
||||
let top = self
|
||||
.texture_registry_builder
|
||||
.add_texture_or_fallback(sides.top);
|
||||
|
||||
self.definitions.insert(
|
||||
id,
|
||||
CubeTextures {
|
||||
front,
|
||||
left,
|
||||
back,
|
||||
right,
|
||||
bottom,
|
||||
top,
|
||||
},
|
||||
);
|
||||
self.name_to_id.insert(name.into(), id);
|
||||
log::info!("{id} -> {name}");
|
||||
|
||||
self.last_block_id += 1;
|
||||
}
|
||||
|
||||
pub fn build(
|
||||
self,
|
||||
queue: Arc<Queue>,
|
||||
) -> Result<(BlockModelRegistry, BlockRegistry, Arc<ImageView>), Error> {
|
||||
let image_view = self.texture_registry_builder.upload(queue)?;
|
||||
|
||||
let block_model_registry = BlockModelRegistry {
|
||||
id_to_model: self.definitions,
|
||||
fallback: CubeTextures::single(0),
|
||||
};
|
||||
let block_registry = BlockRegistry {
|
||||
name_to_id: self.name_to_id,
|
||||
};
|
||||
|
||||
Ok((block_model_registry, block_registry, image_view))
|
||||
}
|
||||
}
|
||||
|
||||
impl BlockRegistry {
|
||||
pub fn get(&self, name: &str) -> Option<u8> {
|
||||
self.name_to_id.get(name).copied()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
pub mod block;
|
||||
pub mod model;
|
||||
pub mod texture;
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub struct BlockModelRegistry {
|
||||
pub id_to_model: HashMap<u8, CubeTextures>,
|
||||
pub fallback: CubeTextures,
|
||||
}
|
||||
|
||||
pub struct CubeTextures {
|
||||
pub front: u32,
|
||||
pub left: u32,
|
||||
pub back: u32,
|
||||
pub right: u32,
|
||||
pub bottom: u32,
|
||||
pub top: u32,
|
||||
}
|
||||
|
||||
impl CubeTextures {
|
||||
pub fn single(id: u32) -> Self {
|
||||
Self {
|
||||
front: id,
|
||||
left: id,
|
||||
back: id,
|
||||
right: id,
|
||||
bottom: id,
|
||||
top: id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BlockModelRegistry {
|
||||
pub fn get_or_fallback(&self, id: u8) -> &CubeTextures {
|
||||
self.id_to_model.get(&id).unwrap_or(&self.fallback)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,216 @@
|
||||
use std::{
|
||||
collections::{hash_map::Entry, HashMap},
|
||||
fs::File,
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use vulkano::{
|
||||
buffer::{Buffer, BufferCreateInfo, BufferUsage},
|
||||
command_buffer::{AutoCommandBufferBuilder, CommandBufferUsage, CopyBufferToImageInfo},
|
||||
device::{Device, Queue},
|
||||
format::Format,
|
||||
image::{view::ImageView, Image, ImageCreateInfo, ImageType, ImageUsage},
|
||||
memory::allocator::{AllocationCreateInfo, MemoryTypeFilter},
|
||||
sync::{self, GpuFuture},
|
||||
DeviceSize,
|
||||
};
|
||||
|
||||
use crate::{error::Error, render::util::Allocators};
|
||||
|
||||
pub trait TextureReader {
|
||||
fn format(&self) -> Format;
|
||||
fn width(&self) -> u32;
|
||||
fn height(&self) -> u32;
|
||||
fn read_data(&mut self, buffer: &mut [u8]) -> Result<(), Error>;
|
||||
}
|
||||
|
||||
pub struct TextureArrayBuilder {
|
||||
device: Arc<Device>,
|
||||
allocators: Arc<Allocators>,
|
||||
|
||||
width: u32,
|
||||
height: u32,
|
||||
readers: Vec<Box<dyn TextureReader>>,
|
||||
}
|
||||
|
||||
pub struct TextureRegistryBuilder {
|
||||
inner: TextureArrayBuilder,
|
||||
map: HashMap<String, u32>,
|
||||
last_texture_id: u32,
|
||||
}
|
||||
|
||||
pub struct PngTextureReader {
|
||||
reader: png::Reader<File>,
|
||||
}
|
||||
|
||||
impl TextureRegistryBuilder {
|
||||
pub fn new(
|
||||
device: Arc<Device>,
|
||||
allocators: Arc<Allocators>,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> Result<Self, Error> {
|
||||
let inner = TextureArrayBuilder::new(device, allocators, width, height);
|
||||
let mut this = Self {
|
||||
inner,
|
||||
last_texture_id: 0,
|
||||
map: HashMap::new(),
|
||||
};
|
||||
|
||||
this.add_texture("fallback")?;
|
||||
|
||||
Ok(this)
|
||||
}
|
||||
|
||||
pub fn add_texture(&mut self, name: &str) -> Result<u32, Error> {
|
||||
match self.map.entry(name.into()) {
|
||||
Entry::Occupied(entry) => Ok(*entry.get()),
|
||||
Entry::Vacant(entry) => {
|
||||
let id = self.last_texture_id;
|
||||
let path = PathBuf::from("assets/textures/blocks").join(format!("{name}.png"));
|
||||
|
||||
self.inner.push(Box::new(PngTextureReader::open(&path)?));
|
||||
log::info!("{path:?} -> {id}");
|
||||
self.last_texture_id += 1;
|
||||
|
||||
entry.insert(id);
|
||||
|
||||
Ok(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_texture_or_fallback(&mut self, name: &str) -> u32 {
|
||||
self.add_texture(name)
|
||||
.inspect_err(|e| log::error!("Failed to load texture {name}: {e}"))
|
||||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
pub fn upload(self, queue: Arc<Queue>) -> Result<Arc<ImageView>, Error> {
|
||||
self.inner.upload(queue)
|
||||
}
|
||||
}
|
||||
|
||||
impl PngTextureReader {
|
||||
pub fn open<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
|
||||
let file = File::open(path)?;
|
||||
let decoder = png::Decoder::new(file);
|
||||
let reader = decoder.read_info()?;
|
||||
Ok(Self { reader })
|
||||
}
|
||||
}
|
||||
|
||||
impl TextureReader for PngTextureReader {
|
||||
fn width(&self) -> u32 {
|
||||
self.reader.info().width
|
||||
}
|
||||
|
||||
fn height(&self) -> u32 {
|
||||
self.reader.info().height
|
||||
}
|
||||
|
||||
fn format(&self) -> Format {
|
||||
Format::R8G8B8A8_UNORM
|
||||
}
|
||||
|
||||
fn read_data(&mut self, buffer: &mut [u8]) -> Result<(), Error> {
|
||||
self.reader.next_frame(buffer)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl TextureArrayBuilder {
|
||||
pub fn new(device: Arc<Device>, allocators: Arc<Allocators>, width: u32, height: u32) -> Self {
|
||||
Self {
|
||||
device,
|
||||
allocators,
|
||||
width,
|
||||
height,
|
||||
readers: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push(&mut self, reader: Box<dyn TextureReader>) {
|
||||
if reader.width() != self.width || reader.height() != self.height {
|
||||
todo!("Rescale textures to array size")
|
||||
}
|
||||
if reader.format() != Format::R8G8B8A8_UNORM {
|
||||
todo!("Convert pixel format")
|
||||
}
|
||||
|
||||
self.readers.push(reader);
|
||||
}
|
||||
|
||||
pub fn upload(self, queue: Arc<Queue>) -> Result<Arc<ImageView>, Error> {
|
||||
let format = Format::R8G8B8A8_UNORM;
|
||||
let extent = [self.width, self.height, 1];
|
||||
let array_layers = self.readers.len() as u32;
|
||||
|
||||
let array_layer_size = format.block_size()
|
||||
* extent
|
||||
.into_iter()
|
||||
.map(|e| e as DeviceSize)
|
||||
.product::<DeviceSize>();
|
||||
let buffer_size = array_layer_size * array_layers as DeviceSize;
|
||||
|
||||
let upload_buffer = Buffer::new_slice(
|
||||
self.allocators.memory.clone(),
|
||||
BufferCreateInfo {
|
||||
usage: BufferUsage::TRANSFER_SRC,
|
||||
..Default::default()
|
||||
},
|
||||
AllocationCreateInfo {
|
||||
memory_type_filter: MemoryTypeFilter::PREFER_HOST
|
||||
| MemoryTypeFilter::HOST_SEQUENTIAL_WRITE,
|
||||
..Default::default()
|
||||
},
|
||||
buffer_size,
|
||||
)?;
|
||||
|
||||
{
|
||||
let mut array_data = upload_buffer.write()?;
|
||||
|
||||
for (i, mut reader) in self.readers.into_iter().enumerate() {
|
||||
let offset = i * array_layer_size as usize;
|
||||
reader.read_data(&mut array_data[offset..offset + array_layer_size as usize])?;
|
||||
}
|
||||
}
|
||||
|
||||
let image = Image::new(
|
||||
self.allocators.memory.clone(),
|
||||
ImageCreateInfo {
|
||||
image_type: ImageType::Dim2d,
|
||||
format,
|
||||
extent,
|
||||
array_layers,
|
||||
usage: ImageUsage::TRANSFER_DST | ImageUsage::SAMPLED,
|
||||
..Default::default()
|
||||
},
|
||||
Default::default(),
|
||||
)?;
|
||||
|
||||
let mut upload = AutoCommandBufferBuilder::primary(
|
||||
self.allocators.command_buffer.clone(),
|
||||
queue.queue_family_index(),
|
||||
CommandBufferUsage::OneTimeSubmit,
|
||||
)?;
|
||||
|
||||
upload.copy_buffer_to_image(CopyBufferToImageInfo::buffer_image(
|
||||
upload_buffer,
|
||||
image.clone(),
|
||||
))?;
|
||||
|
||||
let upload = upload.build()?;
|
||||
|
||||
sync::now(self.device.clone())
|
||||
.then_execute(queue, upload)?
|
||||
.then_signal_fence_and_flush()?
|
||||
.wait(None)?;
|
||||
|
||||
let image_view = ImageView::new_default(image)?;
|
||||
|
||||
Ok(image_view)
|
||||
}
|
||||
}
|
||||
|
||||
+179
-65
@@ -1,4 +1,7 @@
|
||||
use std::sync::Arc;
|
||||
use std::{
|
||||
collections::{hash_map::Entry, HashMap},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use glam::Vec3;
|
||||
use vulkano::{
|
||||
@@ -6,15 +9,98 @@ use vulkano::{
|
||||
memory::allocator::{AllocationCreateInfo, MemoryTypeFilter},
|
||||
};
|
||||
|
||||
use crate::error::Error;
|
||||
use crate::{
|
||||
error::Error,
|
||||
world::{Chunk, ChunkCoords, NeighborQuery, World},
|
||||
};
|
||||
|
||||
use super::{util::Allocators, vertex::InputVertex};
|
||||
use super::{
|
||||
asset::model::{BlockModelRegistry, CubeTextures},
|
||||
util::Allocators,
|
||||
vertex::InputVertex,
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct MeshBuilder {
|
||||
vertices: Vec<InputVertex>,
|
||||
}
|
||||
|
||||
pub struct ChunkMesh {
|
||||
pub vertex_buffer: Subbuffer<[InputVertex]>,
|
||||
}
|
||||
|
||||
pub struct WorldMeshState {
|
||||
pub chunks: HashMap<ChunkCoords, ChunkMesh>,
|
||||
pub block_model_registry: BlockModelRegistry,
|
||||
}
|
||||
|
||||
impl WorldMeshState {
|
||||
pub fn new(block_model_registry: BlockModelRegistry) -> Self {
|
||||
Self {
|
||||
chunks: HashMap::new(),
|
||||
block_model_registry,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_or_upload(
|
||||
&mut self,
|
||||
allocators: &Arc<Allocators>,
|
||||
coords: ChunkCoords,
|
||||
world: &World,
|
||||
) -> Result<Option<&ChunkMesh>, Error> {
|
||||
match self.chunks.entry(coords) {
|
||||
Entry::Occupied(entry) => Ok(Some(entry.into_mut())),
|
||||
Entry::Vacant(entry) => {
|
||||
if let Some(chunk) = world.get(coords) {
|
||||
let mesh = ChunkMesh::from_chunk(
|
||||
allocators,
|
||||
&self.block_model_registry,
|
||||
coords,
|
||||
chunk,
|
||||
)?;
|
||||
Ok(Some(entry.insert(mesh)))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ChunkMesh {
|
||||
pub fn from_chunk(
|
||||
allocators: &Arc<Allocators>,
|
||||
block_model_registry: &BlockModelRegistry,
|
||||
coords: ChunkCoords,
|
||||
chunk: &Chunk,
|
||||
) -> Result<Self, Error> {
|
||||
let mut builder = MeshBuilder::new();
|
||||
|
||||
for x in 0..Chunk::SIZE as u32 {
|
||||
for z in 0..Chunk::SIZE as u32 {
|
||||
let h = chunk.top_height_at(x, z);
|
||||
for y in 0..h {
|
||||
let id = chunk.get_id(x, y, z);
|
||||
|
||||
if id == 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let nq = chunk.neighbor_query(x, y, z);
|
||||
let (bx, bz) = coords.block_coords(x, z);
|
||||
|
||||
let textures = block_model_registry.get_or_fallback(id);
|
||||
builder.add_cube(bx, y as i32, bz, nq, &textures);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let vertex_buffer = builder.upload(allocators)?;
|
||||
|
||||
Ok(Self { vertex_buffer })
|
||||
}
|
||||
}
|
||||
|
||||
impl MeshBuilder {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
@@ -22,92 +108,107 @@ impl MeshBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_cube(&mut self, x: i32, y: i32, z: i32) {
|
||||
fn add_cube(&mut self, x: i32, y: i32, z: i32, nq: NeighborQuery, textures: &CubeTextures) {
|
||||
let position = Vec3::new(x as f32, y as f32, z as f32);
|
||||
|
||||
// Front
|
||||
self.add_face(
|
||||
position,
|
||||
Vec3::new(0.0, 0.0, 0.0),
|
||||
Vec3::new(1.0, 0.0, 0.0),
|
||||
Vec3::new(1.0, 1.0, 0.0),
|
||||
Vec3::new(0.0, 1.0, 0.0),
|
||||
Vec3::new(1.0, 0.0, 0.0),
|
||||
);
|
||||
if !nq.contains(NeighborQuery::FRONT) {
|
||||
self.add_face(FaceInfo {
|
||||
position,
|
||||
p0: Vec3::new(0.0, 0.0, 0.0),
|
||||
p1: Vec3::new(1.0, 0.0, 0.0),
|
||||
p2: Vec3::new(1.0, 1.0, 0.0),
|
||||
p3: Vec3::new(0.0, 1.0, 0.0),
|
||||
tex: textures.front,
|
||||
});
|
||||
}
|
||||
// Left
|
||||
self.add_face(
|
||||
position,
|
||||
Vec3::new(0.0, 0.0, 0.0),
|
||||
Vec3::new(0.0, 1.0, 0.0),
|
||||
Vec3::new(0.0, 1.0, 1.0),
|
||||
Vec3::new(0.0, 0.0, 1.0),
|
||||
Vec3::new(0.0, 1.0, 0.0),
|
||||
);
|
||||
if !nq.contains(NeighborQuery::LEFT) {
|
||||
self.add_face(FaceInfo {
|
||||
position,
|
||||
p0: Vec3::new(0.0, 0.0, 0.0),
|
||||
p1: Vec3::new(0.0, 0.0, 1.0),
|
||||
p2: Vec3::new(0.0, 1.0, 1.0),
|
||||
p3: Vec3::new(0.0, 1.0, 0.0),
|
||||
tex: textures.left,
|
||||
});
|
||||
}
|
||||
// Back
|
||||
self.add_face(
|
||||
position,
|
||||
Vec3::new(0.0, 0.0, 1.0),
|
||||
Vec3::new(1.0, 0.0, 1.0),
|
||||
Vec3::new(1.0, 1.0, 1.0),
|
||||
Vec3::new(0.0, 1.0, 1.0),
|
||||
Vec3::new(0.0, 0.0, 1.0),
|
||||
);
|
||||
if !nq.contains(NeighborQuery::BACK) {
|
||||
self.add_face(FaceInfo {
|
||||
position,
|
||||
p0: Vec3::new(0.0, 0.0, 1.0),
|
||||
p1: Vec3::new(1.0, 0.0, 1.0),
|
||||
p2: Vec3::new(1.0, 1.0, 1.0),
|
||||
p3: Vec3::new(0.0, 1.0, 1.0),
|
||||
tex: textures.back,
|
||||
});
|
||||
}
|
||||
// Right
|
||||
self.add_face(
|
||||
position,
|
||||
Vec3::new(1.0, 0.0, 0.0),
|
||||
Vec3::new(1.0, 1.0, 0.0),
|
||||
Vec3::new(1.0, 1.0, 1.0),
|
||||
Vec3::new(1.0, 0.0, 1.0),
|
||||
Vec3::new(1.0, 1.0, 0.0),
|
||||
);
|
||||
if !nq.contains(NeighborQuery::RIGHT) {
|
||||
self.add_face(FaceInfo {
|
||||
position,
|
||||
p0: Vec3::new(1.0, 0.0, 0.0),
|
||||
p1: Vec3::new(1.0, 0.0, 1.0),
|
||||
p2: Vec3::new(1.0, 1.0, 1.0),
|
||||
p3: Vec3::new(1.0, 1.0, 0.0),
|
||||
tex: textures.right,
|
||||
});
|
||||
}
|
||||
// Bottom
|
||||
self.add_face(
|
||||
position,
|
||||
Vec3::new(0.0, 0.0, 0.0),
|
||||
Vec3::new(1.0, 0.0, 0.0),
|
||||
Vec3::new(1.0, 0.0, 1.0),
|
||||
Vec3::new(0.0, 0.0, 1.0),
|
||||
Vec3::new(1.0, 0.0, 1.0),
|
||||
);
|
||||
if !nq.contains(NeighborQuery::BOTTOM) {
|
||||
self.add_face(FaceInfo {
|
||||
position,
|
||||
p0: Vec3::new(0.0, 0.0, 0.0),
|
||||
p1: Vec3::new(1.0, 0.0, 0.0),
|
||||
p2: Vec3::new(1.0, 0.0, 1.0),
|
||||
p3: Vec3::new(0.0, 0.0, 1.0),
|
||||
tex: textures.bottom,
|
||||
});
|
||||
}
|
||||
// Top
|
||||
self.add_face(
|
||||
position,
|
||||
Vec3::new(0.0, 1.0, 0.0),
|
||||
Vec3::new(1.0, 1.0, 0.0),
|
||||
Vec3::new(1.0, 1.0, 1.0),
|
||||
Vec3::new(0.0, 1.0, 1.0),
|
||||
Vec3::new(0.0, 1.0, 1.0),
|
||||
);
|
||||
if !nq.contains(NeighborQuery::TOP) {
|
||||
self.add_face(FaceInfo {
|
||||
position,
|
||||
p0: Vec3::new(0.0, 1.0, 0.0),
|
||||
p1: Vec3::new(1.0, 1.0, 0.0),
|
||||
p2: Vec3::new(1.0, 1.0, 1.0),
|
||||
p3: Vec3::new(0.0, 1.0, 1.0),
|
||||
tex: textures.top,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn add_face(&mut self, d: Vec3, p0: Vec3, p1: Vec3, p2: Vec3, p3: Vec3, c: Vec3) {
|
||||
#[inline]
|
||||
fn add_face(&mut self, face: FaceInfo) {
|
||||
self.vertices.push(InputVertex {
|
||||
i_position: p0 + d,
|
||||
i_color: c,
|
||||
i_position: face.p0 + face.position,
|
||||
i_texture: make_texture(0, 1, face.tex),
|
||||
});
|
||||
self.vertices.push(InputVertex {
|
||||
i_position: p1 + d,
|
||||
i_color: c,
|
||||
i_position: face.p1 + face.position,
|
||||
i_texture: make_texture(1, 1, face.tex),
|
||||
});
|
||||
self.vertices.push(InputVertex {
|
||||
i_position: p2 + d,
|
||||
i_color: c,
|
||||
i_position: face.p2 + face.position,
|
||||
i_texture: make_texture(1, 0, face.tex),
|
||||
});
|
||||
self.vertices.push(InputVertex {
|
||||
i_position: p2 + d,
|
||||
i_color: c,
|
||||
i_position: face.p2 + face.position,
|
||||
i_texture: make_texture(1, 0, face.tex),
|
||||
});
|
||||
self.vertices.push(InputVertex {
|
||||
i_position: p3 + d,
|
||||
i_color: c,
|
||||
i_position: face.p3 + face.position,
|
||||
i_texture: make_texture(0, 0, face.tex),
|
||||
});
|
||||
self.vertices.push(InputVertex {
|
||||
i_position: p0 + d,
|
||||
i_color: c,
|
||||
i_position: face.p0 + face.position,
|
||||
i_texture: make_texture(0, 1, face.tex),
|
||||
});
|
||||
}
|
||||
|
||||
pub fn upload(self, allocators: &Arc<Allocators>) -> Result<Subbuffer<[InputVertex]>, Error> {
|
||||
log::info!("Upload {} vertices", self.vertices.len());
|
||||
let buffer = Buffer::from_iter(
|
||||
allocators.memory.clone(),
|
||||
BufferCreateInfo {
|
||||
@@ -126,3 +227,16 @@ impl MeshBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
fn make_texture(u: u32, v: u32, t: u32) -> u32 {
|
||||
(t << 2) | (u << 1) | v
|
||||
}
|
||||
|
||||
struct FaceInfo {
|
||||
position: Vec3,
|
||||
p0: Vec3,
|
||||
p1: Vec3,
|
||||
p2: Vec3,
|
||||
p3: Vec3,
|
||||
tex: u32,
|
||||
}
|
||||
|
||||
|
||||
+179
-50
@@ -1,13 +1,17 @@
|
||||
use std::{sync::Arc, time::Instant};
|
||||
use std::{
|
||||
sync::Arc,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use asset::{block::BlockRegistryBuilder, texture::TextureRegistryBuilder};
|
||||
use glam::{Mat4, Vec3};
|
||||
use mesh::MeshBuilder;
|
||||
use mesh::WorldMeshState;
|
||||
use util::{Allocators, ViewportExt};
|
||||
use vertex::InputVertex;
|
||||
use vulkano::{
|
||||
buffer::{
|
||||
allocator::{SubbufferAllocator, SubbufferAllocatorCreateInfo},
|
||||
BufferUsage, Subbuffer,
|
||||
BufferUsage,
|
||||
},
|
||||
command_buffer::{
|
||||
AutoCommandBufferBuilder, CommandBufferUsage, RenderPassBeginInfo, SubpassBeginInfo,
|
||||
@@ -16,7 +20,11 @@ use vulkano::{
|
||||
descriptor_set::{DescriptorSet, WriteDescriptorSet},
|
||||
device::{Device, DeviceExtensions, Queue},
|
||||
format::Format,
|
||||
image::Image,
|
||||
image::{
|
||||
sampler::{Filter, Sampler, SamplerAddressMode, SamplerCreateInfo, SamplerMipmapMode},
|
||||
view::ImageView,
|
||||
Image,
|
||||
},
|
||||
instance::{InstanceCreateFlags, InstanceCreateInfo},
|
||||
memory::allocator::MemoryTypeFilter,
|
||||
pipeline::{graphics::viewport::Viewport, GraphicsPipeline, Pipeline, PipelineBindPoint},
|
||||
@@ -28,8 +36,12 @@ use vulkano::{
|
||||
};
|
||||
use winit::window::Window;
|
||||
|
||||
use crate::error::Error;
|
||||
use crate::{
|
||||
error::Error,
|
||||
world::{Chunk, ChunkCoords, NoiseChunkGenerator, World},
|
||||
};
|
||||
|
||||
pub mod asset;
|
||||
pub mod mesh;
|
||||
pub mod shaders;
|
||||
pub mod util;
|
||||
@@ -48,18 +60,54 @@ pub struct Renderer {
|
||||
swapchain_images: Vec<Arc<Image>>,
|
||||
swapchain_dirty: bool,
|
||||
|
||||
vertex_buffer: Subbuffer<[InputVertex]>,
|
||||
uniform_allocator: SubbufferAllocator,
|
||||
|
||||
render_pass: Arc<RenderPass>,
|
||||
viewport: Viewport,
|
||||
framebuffers: Vec<Arc<Framebuffer>>,
|
||||
|
||||
sampler: Arc<Sampler>,
|
||||
texture_view: Arc<ImageView>,
|
||||
|
||||
vertex_shader: EntryPoint,
|
||||
fragment_shader: EntryPoint,
|
||||
pipeline: Arc<GraphicsPipeline>,
|
||||
|
||||
world: World,
|
||||
g: NoiseChunkGenerator,
|
||||
|
||||
render_world: WorldMeshState,
|
||||
|
||||
start: Instant,
|
||||
fps: Fps,
|
||||
}
|
||||
|
||||
pub struct Fps {
|
||||
frames: usize,
|
||||
last: Instant,
|
||||
}
|
||||
|
||||
impl Fps {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
frames: 0,
|
||||
last: Instant::now(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_frame(&mut self) {
|
||||
self.frames += 1;
|
||||
}
|
||||
|
||||
pub fn update(&mut self) {
|
||||
let delta = self.last.elapsed();
|
||||
if delta >= Duration::SECOND {
|
||||
let fps = self.frames as f64 / delta.as_secs_f64();
|
||||
log::info!("FPS: {fps}");
|
||||
self.frames = 0;
|
||||
self.last = Instant::now();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Renderer {
|
||||
@@ -85,16 +133,6 @@ impl Renderer {
|
||||
|
||||
let allocators = Allocators::new_default(device.clone())?;
|
||||
|
||||
let vertex_buffer = {
|
||||
let mut builder = MeshBuilder::new();
|
||||
builder.add_cube(0, 0, 0);
|
||||
builder.add_cube(-1, 0, 0);
|
||||
builder.add_cube(0, 0, -1);
|
||||
builder.add_cube(-1, 0, -1);
|
||||
builder.add_cube(0, 1, 0);
|
||||
builder.upload(&allocators)?
|
||||
};
|
||||
|
||||
let uniform_allocator = SubbufferAllocator::new(
|
||||
allocators.memory.clone(),
|
||||
SubbufferAllocatorCreateInfo {
|
||||
@@ -148,12 +186,43 @@ impl Renderer {
|
||||
let fragment_shader = shaders::plain::fs::load(device.clone())?
|
||||
.entry_point("main")
|
||||
.expect("shader entry point");
|
||||
|
||||
let texture_registry_builder =
|
||||
TextureRegistryBuilder::new(device.clone(), allocators.clone(), 32, 32)?;
|
||||
let mut block_registry_builder = BlockRegistryBuilder::new(texture_registry_builder);
|
||||
|
||||
for name in ["dirt", "grass", "stone"] {
|
||||
if let Err(error) = block_registry_builder.load(name) {
|
||||
log::error!("Block {name} load error: {error}");
|
||||
}
|
||||
}
|
||||
|
||||
let (block_model_registry, block_registry, texture_view) =
|
||||
block_registry_builder.build(queue.clone())?;
|
||||
|
||||
let world = World::new(block_registry);
|
||||
let g = NoiseChunkGenerator::from_seed(1234);
|
||||
|
||||
let render_world = WorldMeshState::new(block_model_registry);
|
||||
|
||||
let sampler = Sampler::new(
|
||||
device.clone(),
|
||||
SamplerCreateInfo {
|
||||
mag_filter: Filter::Nearest,
|
||||
min_filter: Filter::Nearest,
|
||||
mipmap_mode: SamplerMipmapMode::Linear,
|
||||
address_mode: [SamplerAddressMode::Repeat; 3],
|
||||
..Default::default()
|
||||
},
|
||||
)?;
|
||||
|
||||
let pipeline = shaders::create_two_stage_3d_pipeline::<InputVertex>(
|
||||
&device,
|
||||
&render_pass,
|
||||
viewport.clone(),
|
||||
&vertex_shader,
|
||||
&fragment_shader,
|
||||
vec![sampler.clone()],
|
||||
)?;
|
||||
|
||||
let start = Instant::now();
|
||||
@@ -169,7 +238,6 @@ impl Renderer {
|
||||
swapchain_images,
|
||||
swapchain_dirty: false,
|
||||
|
||||
vertex_buffer,
|
||||
uniform_allocator,
|
||||
|
||||
render_pass,
|
||||
@@ -180,11 +248,22 @@ impl Renderer {
|
||||
fragment_shader,
|
||||
pipeline,
|
||||
|
||||
sampler,
|
||||
texture_view,
|
||||
|
||||
world,
|
||||
g,
|
||||
render_world,
|
||||
|
||||
start,
|
||||
fps: Fps::new(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn render(&mut self) -> Result<(), Error> {
|
||||
self.fps.new_frame();
|
||||
self.fps.update();
|
||||
|
||||
self.refresh_swapchain()?;
|
||||
|
||||
let (image_index, suboptimal, acquire_future) =
|
||||
@@ -211,36 +290,58 @@ impl Renderer {
|
||||
let delta = self.start.elapsed();
|
||||
let dt = delta.as_secs_f64() * 1.0;
|
||||
let model = Mat4::IDENTITY;
|
||||
let projection = Mat4::perspective_rh_gl(45.0f32.to_radians(), aspect, 0.01, 100.0);
|
||||
let projection = Mat4::perspective_rh_gl(45.0f32.to_radians(), aspect, 0.01, 1000.0);
|
||||
|
||||
fn make_view(camera: Vec3, target: Vec3) -> Mat4 {
|
||||
Mat4::look_at_rh(camera, target, Vec3::Y)
|
||||
}
|
||||
|
||||
let view = make_view(
|
||||
Vec3::new((dt.cos() * 3.0) as f32, 3.0, (dt.sin() * 3.0) as f32),
|
||||
Vec3::new(0.0, 0.0, 0.0),
|
||||
let camera_position = Vec3::new(
|
||||
(dt.cos() * 32.0) as f32 + (Chunk::SIZE / 2) as f32,
|
||||
48.0,
|
||||
(dt.sin() * 32.0) as f32 + (Chunk::SIZE / 2) as f32,
|
||||
);
|
||||
|
||||
let uniform_buffer = self
|
||||
.uniform_allocator
|
||||
.allocate_sized::<shaders::plain::SceneData>()?;
|
||||
{
|
||||
let mut write = uniform_buffer.write()?;
|
||||
*write = shaders::plain::SceneData {
|
||||
model: model.to_cols_array_2d(),
|
||||
view: view.to_cols_array_2d(),
|
||||
projection: projection.to_cols_array_2d(),
|
||||
};
|
||||
}
|
||||
self.world.update_with_camera(camera_position, &mut self.g);
|
||||
|
||||
let descriptor_layout = &self.pipeline.layout().set_layouts()[0];
|
||||
let set0 = DescriptorSet::new(
|
||||
self.allocators.descriptor_set.clone(),
|
||||
descriptor_layout.clone(),
|
||||
[WriteDescriptorSet::buffer(0, uniform_buffer)],
|
||||
[],
|
||||
)?;
|
||||
let view = make_view(
|
||||
camera_position,
|
||||
Vec3::new((Chunk::SIZE / 2) as f32, 32.0, (Chunk::SIZE / 2) as f32),
|
||||
);
|
||||
|
||||
let set0 = {
|
||||
let uniform_buffer = self
|
||||
.uniform_allocator
|
||||
.allocate_sized::<shaders::plain::SceneData>()?;
|
||||
{
|
||||
let mut write = uniform_buffer.write()?;
|
||||
*write = shaders::plain::SceneData {
|
||||
model: model.to_cols_array_2d(),
|
||||
view: view.to_cols_array_2d(),
|
||||
projection: projection.to_cols_array_2d(),
|
||||
};
|
||||
}
|
||||
|
||||
let descriptor_layout = &self.pipeline.layout().set_layouts()[0];
|
||||
|
||||
DescriptorSet::new(
|
||||
self.allocators.descriptor_set.clone(),
|
||||
descriptor_layout.clone(),
|
||||
[WriteDescriptorSet::buffer(0, uniform_buffer)],
|
||||
[],
|
||||
)?
|
||||
};
|
||||
|
||||
let set1 = {
|
||||
let descriptor_layout = &self.pipeline.layout().set_layouts()[1];
|
||||
|
||||
DescriptorSet::new(
|
||||
self.allocators.descriptor_set.clone(),
|
||||
descriptor_layout.clone(),
|
||||
[WriteDescriptorSet::image_view(1, self.texture_view.clone())],
|
||||
[],
|
||||
)?
|
||||
};
|
||||
|
||||
let mut builder = AutoCommandBufferBuilder::primary(
|
||||
self.allocators.command_buffer.clone(),
|
||||
@@ -259,18 +360,45 @@ impl Renderer {
|
||||
},
|
||||
)?;
|
||||
|
||||
builder
|
||||
.bind_pipeline_graphics(self.pipeline.clone())?
|
||||
.bind_descriptor_sets(
|
||||
PipelineBindPoint::Graphics,
|
||||
self.pipeline.layout().clone(),
|
||||
0,
|
||||
set0.offsets([0]),
|
||||
)?
|
||||
.bind_vertex_buffers(0, self.vertex_buffer.clone())?;
|
||||
builder.bind_pipeline_graphics(self.pipeline.clone())?;
|
||||
|
||||
unsafe {
|
||||
builder.draw(self.vertex_buffer.len() as _, 1, 0, 0)?;
|
||||
builder.bind_descriptor_sets(
|
||||
PipelineBindPoint::Graphics,
|
||||
self.pipeline.layout().clone(),
|
||||
0,
|
||||
set0.offsets([0]),
|
||||
)?;
|
||||
builder.bind_descriptor_sets(
|
||||
PipelineBindPoint::Graphics,
|
||||
self.pipeline.layout().clone(),
|
||||
1,
|
||||
set1,
|
||||
)?;
|
||||
|
||||
const VIEW_DISTANCE: i32 = 5;
|
||||
|
||||
fn cpos(x: f32) -> i32 {
|
||||
(x as i32 + Chunk::SIZE as i32 / 2) / Chunk::SIZE as i32
|
||||
}
|
||||
|
||||
let camera_cx = cpos(camera_position.x);
|
||||
let camera_cz = cpos(camera_position.z);
|
||||
|
||||
for x in -VIEW_DISTANCE..=VIEW_DISTANCE {
|
||||
for z in -VIEW_DISTANCE..=VIEW_DISTANCE {
|
||||
let coords = ChunkCoords::new(x + camera_cx, z + camera_cz);
|
||||
let mesh =
|
||||
self.render_world
|
||||
.get_or_upload(&self.allocators, coords, &self.world)?;
|
||||
|
||||
if let Some(mesh) = mesh {
|
||||
builder.bind_vertex_buffers(0, mesh.vertex_buffer.clone())?;
|
||||
|
||||
unsafe {
|
||||
builder.draw(mesh.vertex_buffer.len() as _, 1, 0, 0)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
builder.end_render_pass(SubpassEndInfo::default())?;
|
||||
@@ -328,6 +456,7 @@ impl Renderer {
|
||||
self.viewport.clone(),
|
||||
&self.vertex_shader,
|
||||
&self.fragment_shader,
|
||||
vec![self.sampler.clone()],
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -3,6 +3,7 @@ use std::sync::Arc;
|
||||
use vulkano::{
|
||||
descriptor_set::layout::DescriptorType,
|
||||
device::Device,
|
||||
image::sampler::Sampler,
|
||||
pipeline::{
|
||||
graphics::{
|
||||
color_blend::{ColorBlendAttachmentState, ColorBlendState},
|
||||
@@ -46,6 +47,7 @@ pub fn create_two_stage_3d_pipeline<V: Vertex>(
|
||||
viewport: Viewport,
|
||||
vertex_shader: &EntryPoint,
|
||||
fragment_shader: &EntryPoint,
|
||||
samplers: Vec<Arc<Sampler>>,
|
||||
) -> Result<Arc<GraphicsPipeline>, Error> {
|
||||
let vertex_input_state = V::per_vertex().definition(vertex_shader)?;
|
||||
let stages = [
|
||||
@@ -55,12 +57,19 @@ pub fn create_two_stage_3d_pipeline<V: Vertex>(
|
||||
|
||||
let layout = {
|
||||
let mut layout_create_info = PipelineDescriptorSetLayoutCreateInfo::from_stages(&stages);
|
||||
|
||||
layout_create_info.set_layouts[0]
|
||||
.bindings
|
||||
.get_mut(&0)
|
||||
.unwrap()
|
||||
.descriptor_type = DescriptorType::UniformBufferDynamic;
|
||||
|
||||
layout_create_info.set_layouts[1]
|
||||
.bindings
|
||||
.get_mut(&0)
|
||||
.unwrap()
|
||||
.immutable_samplers = samplers;
|
||||
|
||||
PipelineLayout::new(
|
||||
device.clone(),
|
||||
layout_create_info.into_pipeline_layout_create_info(device.clone())?,
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
#version 460
|
||||
|
||||
layout(location = 0) in vec3 i_color;
|
||||
layout(location = 0) in vec2 i_tex_coords;
|
||||
layout(location = 1) in flat uint i_tex_index;
|
||||
|
||||
layout(location = 0) out vec4 o_color;
|
||||
|
||||
layout(set = 1, binding = 0) uniform sampler u_sampler;
|
||||
layout(set = 1, binding = 1) uniform texture2DArray u_texture;
|
||||
|
||||
void main() {
|
||||
o_color = vec4(i_color, 1.0);
|
||||
vec4 color = texture(sampler2DArray(u_texture, u_sampler), vec3(i_tex_coords, float(i_tex_index)));
|
||||
o_color = color;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
#version 460
|
||||
|
||||
layout(location = 0) in vec3 i_position;
|
||||
layout(location = 1) in vec3 i_color;
|
||||
layout(location = 1) in uint i_texture;
|
||||
|
||||
layout(location = 0) out vec3 o_color;
|
||||
layout(location = 0) out vec2 o_tex_coords;
|
||||
layout(location = 1) out uint o_tex_index;
|
||||
|
||||
layout(set = 0, binding = 0) uniform SceneData {
|
||||
mat4 model;
|
||||
@@ -13,8 +14,14 @@ layout(set = 0, binding = 0) uniform SceneData {
|
||||
|
||||
void main() {
|
||||
vec4 m_position = vec4(i_position, 1.0);
|
||||
vec4 s_position = u_scene.projection * u_scene.view * m_position;
|
||||
vec4 w_position = u_scene.view * m_position;
|
||||
vec4 s_position = u_scene.projection * w_position;
|
||||
|
||||
gl_Position = s_position;
|
||||
o_color = i_color;
|
||||
|
||||
uint u = (i_texture >> 1) & 1;
|
||||
uint v = i_texture & 1;
|
||||
|
||||
o_tex_index = i_texture >> 2;
|
||||
o_tex_coords = vec2(float(u), float(v));
|
||||
}
|
||||
|
||||
@@ -205,3 +205,73 @@ pub fn create_3d_framebuffers(
|
||||
Ok(framebuffers)
|
||||
}
|
||||
|
||||
// pub fn load_png_texture<P: AsRef<Path>>(
|
||||
// allocators: &Allocators,
|
||||
// queue: &Arc<Queue>,
|
||||
// path: P,
|
||||
// usage: ImageUsage,
|
||||
// ) -> Result<Arc<ImageView>, Error> {
|
||||
// let file = File::open(path)?;
|
||||
// let decoder = png::Decoder::new(file);
|
||||
// let mut reader = decoder.read_info()?;
|
||||
// let info = reader.info();
|
||||
//
|
||||
// let extent = [info.width, info.height, 1];
|
||||
//
|
||||
// let image = Image::new(
|
||||
// allocators.memory.clone(),
|
||||
// ImageCreateInfo {
|
||||
// usage: ImageUsage::TRANSFER_DST | usage,
|
||||
// image_type: ImageType::Dim2d,
|
||||
// extent,
|
||||
// format: Format::R8G8B8A8_UNORM,
|
||||
// ..Default::default()
|
||||
// },
|
||||
// Default::default(),
|
||||
// )?;
|
||||
//
|
||||
// let buffer_size = (info.width * info.height * 4) as DeviceSize;
|
||||
//
|
||||
// let upload_buffer = Buffer::new_slice(
|
||||
// allocators.memory.clone(),
|
||||
// BufferCreateInfo {
|
||||
// usage: BufferUsage::TRANSFER_SRC,
|
||||
// ..Default::default()
|
||||
// },
|
||||
// AllocationCreateInfo {
|
||||
// memory_type_filter: MemoryTypeFilter::PREFER_HOST
|
||||
// | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE,
|
||||
// ..Default::default()
|
||||
// },
|
||||
// buffer_size,
|
||||
// )?;
|
||||
//
|
||||
// {
|
||||
// let mut write = upload_buffer.write()?;
|
||||
//
|
||||
// reader.next_frame(&mut write[..])?;
|
||||
// }
|
||||
//
|
||||
// let mut upload = AutoCommandBufferBuilder::primary(
|
||||
// allocators.command_buffer.clone(),
|
||||
// queue.queue_family_index(),
|
||||
// CommandBufferUsage::OneTimeSubmit,
|
||||
// )?;
|
||||
//
|
||||
// upload.copy_buffer_to_image(CopyBufferToImageInfo::buffer_image(
|
||||
// upload_buffer,
|
||||
// image.clone(),
|
||||
// ))?;
|
||||
//
|
||||
// upload
|
||||
// .build()?
|
||||
// .execute(queue.clone())?
|
||||
// .then_signal_fence_and_flush()?
|
||||
// .wait(None)?;
|
||||
//
|
||||
// let view = ImageView::new_default(image)?;
|
||||
//
|
||||
// Ok(view)
|
||||
// }
|
||||
//
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ use vulkano::{buffer::BufferContents, pipeline::graphics::vertex_input::Vertex};
|
||||
pub struct InputVertex {
|
||||
#[format(R32G32B32_SFLOAT)]
|
||||
pub i_position: Vec3,
|
||||
#[format(R32G32B32_SFLOAT)]
|
||||
pub i_color: Vec3,
|
||||
#[format(R32_UINT)]
|
||||
pub i_texture: u32,
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,225 @@
|
||||
use std::{collections::HashMap, hash::Hash};
|
||||
|
||||
use bitflags::bitflags;
|
||||
use glam::Vec3;
|
||||
use noise::NoiseFn;
|
||||
|
||||
use crate::render::asset::block::BlockRegistry;
|
||||
|
||||
pub struct Chunk {
|
||||
blocks: [u8; Self::SIZE * Self::SIZE * Self::HEIGHT],
|
||||
height_map: [u32; Self::SIZE * Self::SIZE],
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct ChunkCoords {
|
||||
pub x: i32,
|
||||
pub z: i32,
|
||||
}
|
||||
|
||||
impl ChunkCoords {
|
||||
pub const fn new(x: i32, z: i32) -> Self {
|
||||
Self { x, z }
|
||||
}
|
||||
|
||||
pub fn origin_block(self) -> (i32, i32) {
|
||||
(self.x * Chunk::SIZE as i32, self.z * Chunk::SIZE as i32)
|
||||
}
|
||||
|
||||
pub fn block_coords(self, x: u32, z: u32) -> (i32, i32) {
|
||||
let (cx, cz) = self.origin_block();
|
||||
(cx + x as i32, cz + z as i32)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct World {
|
||||
chunks: HashMap<ChunkCoords, Chunk>,
|
||||
block_registry: BlockRegistry,
|
||||
}
|
||||
|
||||
impl World {
|
||||
pub fn new(block_registry: BlockRegistry) -> Self {
|
||||
Self {
|
||||
chunks: HashMap::new(),
|
||||
block_registry,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate<G: ChunkGenerator>(&mut self, coords: ChunkCoords, g: &mut G) {
|
||||
assert!(!self.chunks.contains_key(&coords));
|
||||
|
||||
let mut chunk = Chunk::empty();
|
||||
g.generate_terrain(coords, &mut chunk, &self.block_registry);
|
||||
self.chunks.insert(coords, chunk);
|
||||
}
|
||||
|
||||
pub fn get(&self, coords: ChunkCoords) -> Option<&Chunk> {
|
||||
self.chunks.get(&coords)
|
||||
}
|
||||
|
||||
pub fn update_with_camera<G: ChunkGenerator>(&mut self, camera_position: Vec3, g: &mut G) {
|
||||
const VIEW_DISTANCE: i32 = 5;
|
||||
|
||||
fn cpos(x: f32) -> i32 {
|
||||
(x as i32 + Chunk::SIZE as i32 / 2) / Chunk::SIZE as i32
|
||||
}
|
||||
|
||||
let camera_cx = cpos(camera_position.x);
|
||||
let camera_cz = cpos(camera_position.z);
|
||||
|
||||
for cx in camera_cx - VIEW_DISTANCE..=camera_cx + VIEW_DISTANCE {
|
||||
for cz in camera_cz - VIEW_DISTANCE..=camera_cz + VIEW_DISTANCE {
|
||||
let coords = ChunkCoords::new(cx, cz);
|
||||
|
||||
if !self.chunks.contains_key(&coords) {
|
||||
log::info!("Generate {coords:?}");
|
||||
self.generate(coords, g);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NoiseChunkGenerator {
|
||||
noise: noise::Perlin,
|
||||
}
|
||||
|
||||
impl ChunkGenerator for NoiseChunkGenerator {
|
||||
fn generate_terrain(
|
||||
&mut self,
|
||||
coords: ChunkCoords,
|
||||
chunk: &mut Chunk,
|
||||
block_registry: &BlockRegistry,
|
||||
) {
|
||||
let grass = block_registry.get("grass").unwrap_or(0);
|
||||
let dirt = block_registry.get("dirt").unwrap_or(0);
|
||||
let stone = block_registry.get("stone").unwrap_or(0);
|
||||
|
||||
for ix in 0..Chunk::SIZE as u32 {
|
||||
for iz in 0..Chunk::SIZE as u32 {
|
||||
let (x, z) = coords.block_coords(ix, iz);
|
||||
let x = x as f64;
|
||||
let z = z as f64;
|
||||
|
||||
let n0 = self.noise.get([x / 256.0, 1.0, z / 256.0]) * 16.0;
|
||||
let n1 = self.noise.get([x / 64.0, 0.0, z / 64.0]) * 4.0;
|
||||
let n2 = self.noise.get([x / 16.0, 1.0, z / 16.0]) * 2.0;
|
||||
let h = (n0 + n1 + n2 + 32.0) as u32;
|
||||
|
||||
let sn0 = self.noise.get([x / 16.0, 0.0, z / 16.0]) * 2.0;
|
||||
let stone_depth = (sn0 + 4.0) as u32;
|
||||
|
||||
for y in 0..h {
|
||||
let id = if y + 1 == h {
|
||||
grass
|
||||
} else if h - y > stone_depth {
|
||||
stone
|
||||
} else {
|
||||
dirt
|
||||
};
|
||||
chunk.set_id(ix, y, iz, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NoiseChunkGenerator {
|
||||
pub fn from_seed(seed: u64) -> Self {
|
||||
let noise = noise::Perlin::new(seed as _);
|
||||
Self { noise }
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ChunkGenerator {
|
||||
fn generate_terrain(
|
||||
&mut self,
|
||||
coords: ChunkCoords,
|
||||
chunk: &mut Chunk,
|
||||
block_registry: &BlockRegistry,
|
||||
);
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
#[derive(Debug)]
|
||||
pub struct NeighborQuery: u8 {
|
||||
const FRONT = 1 << 0;
|
||||
const LEFT = 1 << 1;
|
||||
const RIGHT = 1 << 2;
|
||||
const BACK = 1 << 3;
|
||||
const BOTTOM = 1 << 4;
|
||||
const TOP = 1 << 5;
|
||||
}
|
||||
}
|
||||
|
||||
impl Chunk {
|
||||
pub const SIZE: usize = 32;
|
||||
pub const HEIGHT: usize = 128;
|
||||
|
||||
pub fn empty() -> Self {
|
||||
Self {
|
||||
blocks: [0; Self::SIZE * Self::SIZE * Self::HEIGHT],
|
||||
height_map: [0; Self::SIZE * Self::SIZE],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fill_layer<F: Fn(u32, u32) -> u8>(&mut self, y: u32, f: F) {
|
||||
for x in 0..Self::SIZE as u32 {
|
||||
for z in 0..Self::SIZE as u32 {
|
||||
self.set_id(x, y, z, f(x, z));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_id(&self, x: u32, y: u32, z: u32) -> u8 {
|
||||
self.blocks[Self::to_index(x, y, z)]
|
||||
}
|
||||
|
||||
pub fn set_id(&mut self, x: u32, y: u32, z: u32, id: u8) {
|
||||
self.blocks[Self::to_index(x, y, z)] = id;
|
||||
|
||||
if id != 0 && y >= self.top_height_at(x, z) {
|
||||
self.set_top_height_at(x, z, y + 1);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_top_height_at(&mut self, x: u32, z: u32, h: u32) {
|
||||
self.height_map[Self::to_height_index(x, z)] = h;
|
||||
}
|
||||
|
||||
pub fn top_height_at(&self, x: u32, z: u32) -> u32 {
|
||||
self.height_map[Self::to_height_index(x, z)]
|
||||
}
|
||||
|
||||
pub fn neighbor_query(&self, x: u32, y: u32, z: u32) -> NeighborQuery {
|
||||
let mut q = NeighborQuery::empty();
|
||||
if z != 0 && self.get_id(x, y, z - 1) != 0 {
|
||||
q |= NeighborQuery::FRONT;
|
||||
}
|
||||
if x != 0 && self.get_id(x - 1, y, z) != 0 {
|
||||
q |= NeighborQuery::LEFT;
|
||||
}
|
||||
if z as usize != Self::SIZE - 1 && self.get_id(x, y, z + 1) != 0 {
|
||||
q |= NeighborQuery::BACK;
|
||||
}
|
||||
if x as usize != Self::SIZE - 1 && self.get_id(x + 1, y, z) != 0 {
|
||||
q |= NeighborQuery::RIGHT;
|
||||
}
|
||||
if y != 0 && self.get_id(x, y - 1, z) != 0 {
|
||||
q |= NeighborQuery::BOTTOM;
|
||||
}
|
||||
if y as usize != Self::HEIGHT - 1 && self.get_id(x, y + 1, z) != 0 {
|
||||
q |= NeighborQuery::TOP;
|
||||
}
|
||||
q
|
||||
}
|
||||
|
||||
const fn to_index(x: u32, y: u32, z: u32) -> usize {
|
||||
x as usize + (z as usize + y as usize * Self::SIZE) * Self::SIZE
|
||||
}
|
||||
|
||||
const fn to_height_index(x: u32, z: u32) -> usize {
|
||||
x as usize + z as usize * Self::SIZE
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user