Compare commits

..

6 commits

Author SHA1 Message Date
griffi-gh 90784e21dd minor changes 2024-05-05 02:02:30 +02:00
griffi-gh 260f4b4232 load block textures 2024-05-05 02:00:06 +02:00
griffi-gh 35ff06a439 minor changes to rendering.rs 2024-05-05 01:34:27 +02:00
griffi-gh 7ac045f013 move renderer into it's own file 2024-05-05 01:30:07 +02:00
griffi-gh 0b69377865 copy over world render 2024-05-05 01:25:44 +02:00
griffi-gh 324270ed7d write some dumb shader code 2024-05-05 01:06:43 +02:00
7 changed files with 343 additions and 286 deletions

42
kubi/shaders/world.wgsl Normal file
View file

@ -0,0 +1,42 @@
// struct Uniforms {
// transform: mat4x4<f32>;
// };
// @group(1) @binding(0)
// var<uniform> uniforms: Uniforms;
struct VertexInput {
@location(0) position: vec3<f32>,
@location(1) normal: vec3<f32>,
@location(2) uv: vec2<f32>,
@location(3) @interpolate(flat) tex_index: u32,
}
struct VertexOutput {
@builtin(position) clip_position: vec4<f32>,
@location(0) uv: vec2<f32>,
@location(1) normal: vec3<f32>,
@location(2) color: vec4<f32>,
@location(3) @interpolate(flat) tex_index: u32,
};
@vertex
fn vs_main(
in: VertexInput,
) -> VertexOutput {
var out: VertexOutput;
out.uv = in.uv;
out.clip_position = vec4<f32>(in.position, 1.0);
return out;
}
@group(0) @binding(0)
var t_diffuse: texture_2d_array<f32>;
@group(0) @binding(1)
var s_diffuse: sampler;
@fragment
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
return textureSample(t_diffuse, s_diffuse, in.uv, in.tex_index);
}

View file

@ -5,9 +5,7 @@ use kubi_shared::block::BlockTexture;
use crate::{filesystem::AssetManager, hui_integration::UiState, rendering::Renderer};
mod texture;
mod shaders;
//use texture::load_texture2darray_prefab;
use texture::load_texture2darray_prefab;
pub trait AssetPaths {
fn file_name(self) -> &'static str;
@ -39,19 +37,7 @@ impl AssetPaths for BlockTexture {
#[derive(Unique)]
#[repr(transparent)]
pub struct BlockTexturesPrefab(pub wgpu::Texture);
// #[derive(Unique)]
// #[repr(transparent)]
// pub struct ChunkShaderPrefab(pub Program);
// #[derive(Unique)]
// #[repr(transparent)]
// pub struct ColoredShaderPrefab(pub Program);
// #[derive(Unique)]
// #[repr(transparent)]
// pub struct Colored2ShaderPrefab(pub Program);
pub struct BlockDiffuseTexture(pub wgpu::Texture);
#[derive(Unique)]
#[repr(transparent)]
@ -63,15 +49,14 @@ pub fn load_prefabs(
mut ui: NonSendSync<UniqueViewMut<UiState>>,
assman: UniqueView<AssetManager>
) {
// log::info!("Loading textures...");
// storages.add_unique_non_send_sync(BlockTexturesPrefab(
// load_texture2darray_prefab::<BlockTexture, _>(
// &assman,
// "blocks".into(),
// &renderer.display,
// MipmapsOption::AutoGeneratedMipmaps
// )
// ));
log::info!("Loading textures...");
storages.add_unique_non_send_sync(BlockDiffuseTexture(
load_texture2darray_prefab::<BlockTexture>(
&renderer,
&assman,
"blocks".into(),
)
));
log::info!("Loading the UI stuff...");
{

View file

@ -1,19 +0,0 @@
macro_rules! include_shader_prefab {
($name: literal, $vert: literal, $frag: literal, $facade: expr) => {
{
use ::glium::{Program, program::ProgramCreationInput};
log::info!("compiling shader {}", $name);
Program::new(&*$facade, ProgramCreationInput::SourceCode {
vertex_shader: include_str!($vert),
fragment_shader: include_str!($frag),
geometry_shader: None,
tessellation_control_shader: None,
tessellation_evaluation_shader: None,
transform_feedback_varyings: None,
outputs_srgb: false,
uses_point_size: false,
}).expect("Failed to compile gpu program")
}
};
}
pub(crate) use include_shader_prefab;

View file

@ -1,45 +1,75 @@
// use strum::IntoEnumIterator;
// use rayon::prelude::*;
// use std::{path::PathBuf, io::BufReader};
// use crate::filesystem::AssetManager;
// use super::AssetPaths;
use glam::UVec2;
use strum::IntoEnumIterator;
use rayon::prelude::*;
use wgpu::util::{DeviceExt, TextureDataOrder};
use std::{io::BufReader, path::PathBuf};
use crate::{filesystem::AssetManager, rendering::Renderer};
use super::AssetPaths;
// pub fn load_texture2darray_prefab<
// T: AssetPaths + IntoEnumIterator,
// E: Facade
// >(
// assman: &AssetManager,
// directory: PathBuf,
// facade: &E,
// mipmaps: MipmapsOption,
// ) -> SrgbTexture2dArray {
// log::info!("started loading {}", directory.as_os_str().to_str().unwrap());
// //Load raw images
// let tex_files: Vec<&'static str> = T::iter().map(|x| x.file_name()).collect();
// let raw_images: Vec<RawImage2d<u8>> = tex_files.par_iter().map(|&file_name| {
// log::info!("loading texture {}", file_name);
// //Get path to the image and open the file
// let reader = {
// let path = directory.join(file_name);
// BufReader::new(assman.open_asset(&path).expect("Failed to open texture file"))
// };
// //Parse image data
// let (image_data, dimensions) = {
// let image = image::load(
// reader,
// image::ImageFormat::Png
// ).unwrap().to_rgba8();
// let dimensions = image.dimensions();
// (image.into_raw(), dimensions)
// };
// //Create a glium RawImage
// RawImage2d::from_raw_rgba_reversed(
// &image_data,
// dimensions
// )
// }).collect();
// log::info!("done loading texture files, uploading to the gpu");
// //Upload images to the GPU
// SrgbTexture2dArray::with_mipmaps(facade, raw_images, mipmaps)
// .expect("Failed to upload texture array to GPU")
// }
pub fn load_texture2darray_prefab<T: AssetPaths + IntoEnumIterator>(
renderer: &Renderer,
assman: &AssetManager,
directory: PathBuf,
) -> wgpu::Texture {
log::info!("started loading {}", directory.as_os_str().to_str().unwrap());
//Load raw images
let tex_files: Vec<&'static str> = T::iter().map(|x| x.file_name()).collect();
let raw_images: Vec<(Vec<u8>, UVec2)> = tex_files.par_iter().map(|&file_name| {
log::info!("loading texture {}", file_name);
//Get path to the image and open the file
let reader = {
let path = directory.join(file_name);
BufReader::new(assman.open_asset(&path).expect("Failed to open texture file"))
};
//Parse image data
let (image_data, dimensions) = {
let image = image::load(
reader,
image::ImageFormat::Png
).unwrap().to_rgba8();
let dimensions = image.dimensions();
(image.into_raw(), dimensions)
};
(image_data, UVec2::from(dimensions))
}).collect();
assert!(!raw_images.is_empty(), "no images loaded");
//TODO: check same size
log::info!("done loading texture files, uploading to the gpu");
let size = raw_images[0].1;
let layers = raw_images.len() as u32;
//Concat data into a single vec
let mut data = Vec::with_capacity((size.x * size.y * layers * 4) as usize);
for (layer_data, _) in raw_images {
data.extend_from_slice(&layer_data);
}
//Upload images to the GPU
let desc = &wgpu::TextureDescriptor {
label: Some("block_diffuse_texture"),
size: wgpu::Extent3d {
width: size.x,
height: size.y,
depth_or_array_layers: layers,
},
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rgba8UnormSrgb,
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
mip_level_count: 1,
sample_count: 1,
view_formats: &[],
};
renderer.device().create_texture_with_data(
renderer.queue(),
desc,
TextureDataOrder::MipMajor,
&data
)
}

View file

@ -1,14 +1,10 @@
use pollster::FutureExt;
use raw_window_handle::HasRawWindowHandle;
use shipyard::{AllStoragesView, AllStoragesViewMut, IntoIter, NonSendSync, Unique, UniqueView, UniqueViewMut, View};
use wgpu::SurfaceTargetUnsafe;
use winit::{
event_loop::ActiveEventLoop,
window::{WindowAttributes, Fullscreen, Window},
dpi::PhysicalSize
};
use shipyard::{AllStoragesView, AllStoragesViewMut, IntoIter, Unique, UniqueView, UniqueViewMut, View};
use winit::dpi::PhysicalSize;
use glam::{Vec3, UVec2};
use crate::{events::WindowResizedEvent, settings::{FullscreenMode, GameSettings}, state::is_ingame};
use crate::{events::WindowResizedEvent, state::is_ingame};
mod renderer;
pub use renderer::Renderer;
pub mod primitives;
pub mod world;
@ -22,174 +18,20 @@ pub struct BufferPair {
}
#[derive(Unique)]
#[repr(transparent)]
pub struct BackgroundColor(pub Vec3);
#[derive(Unique, Clone, Copy)]
#[repr(transparent)]
#[deprecated = "use Renderer.size instead"]
#[allow(deprecated)]
pub struct WindowSize(pub UVec2);
#[derive(Unique)]
pub struct Renderer {
instance: wgpu::Instance,
surface: wgpu::Surface<'static>,
device: wgpu::Device,
queue: wgpu::Queue,
surface_config: wgpu::SurfaceConfiguration,
size: PhysicalSize<u32>,
// pub depth_texture: wgpu::Texture,
//must be last due to drop order
window: Window,
}
impl Renderer {
pub fn init(event_loop: &ActiveEventLoop, settings: &GameSettings) -> Self {
log::info!("initializing display");
let window_attributes = Window::default_attributes()
.with_title("kubi")
.with_maximized(true)
.with_min_inner_size(PhysicalSize::new(640, 480))
.with_fullscreen({
//this has no effect on android, so skip this pointless stuff
#[cfg(target_os = "android")] {
None
}
#[cfg(not(target_os = "android"))]
if let Some(fs_settings) = &settings.fullscreen {
let monitor = event_loop.primary_monitor().or_else(|| {
event_loop.available_monitors().next()
});
if let Some(monitor) = monitor {
log::info!("monitor: {}", monitor.name().unwrap_or_else(|| "generic".into()));
match fs_settings.mode {
FullscreenMode::Borderless => {
log::info!("starting in borderless fullscreen mode");
Some(Fullscreen::Borderless(Some(monitor)))
},
FullscreenMode::Exclusive => {
log::warn!("exclusive fullscreen mode is experimental");
log::info!("starting in exclusive fullscreen mode");
//TODO: grabbing the first video mode is probably not the best idea...
monitor.video_modes().next()
.map(|vmode| {
log::info!("video mode: {}", vmode.to_string());
Some(Fullscreen::Exclusive(vmode))
})
.unwrap_or_else(|| {
log::warn!("no valid video modes found, falling back to windowed mode instead");
None
})
}
}
} else {
log::warn!("no monitors found, falling back to windowed mode");
None
}
} else {
log::info!("starting in windowed mode");
None
}
});
let window = event_loop.create_window(window_attributes).unwrap();
let size = window.inner_size();
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends: wgpu::Backends::BROWSER_WEBGPU | wgpu::Backends::VULKAN | wgpu::Backends::GL,
..Default::default()
});
// Create a surface with `create_surface_unsafe` to get a surface with 'static lifetime
// It should never outlive the window it's created from
let surface = unsafe {
instance.create_surface_unsafe(SurfaceTargetUnsafe::from_window(&window).unwrap()).unwrap()
};
let adapter = instance.request_adapter(
&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::HighPerformance,
compatible_surface: Some(&surface),
force_fallback_adapter: false,
},
).block_on().unwrap();
let (device, queue) = adapter.request_device(
&wgpu::DeviceDescriptor {
label: None,
required_features: wgpu::Features::empty(),
required_limits: wgpu::Limits::downlevel_defaults(),
},
None,
).block_on().unwrap();
let surface_config = surface.get_default_config(&adapter, size.width, size.height).unwrap();
surface.configure(&device, &surface_config);
Self { window, instance, surface, device, queue, surface_config, size }
}
pub fn resize(&mut self, size: PhysicalSize<u32>) {
if size.width == 0 || size.height == 0 {
log::warn!("Ignoring resize event with zero width or height");
return
}
if self.size == size {
log::warn!("Ignoring resize event with same size");
return
}
log::debug!("resizing surface to {:?}", size);
self.size = size;
self.surface_config.width = size.width;
self.surface_config.height = size.height;
self.surface.configure(&self.device, &self.surface_config);
}
pub fn reconfigure(&self) {
self.surface.configure(&self.device, &self.surface_config);
}
//getters:
pub fn size(&self) -> PhysicalSize<u32> {
self.size
}
pub fn window(&self) -> &Window {
&self.window
}
pub fn surface(&self) -> &wgpu::Surface<'static> {
&self.surface
}
pub fn device(&self) -> &wgpu::Device {
&self.device
}
pub fn queue(&self) -> &wgpu::Queue {
&self.queue
}
pub fn surface_config(&self) -> &wgpu::SurfaceConfiguration {
&self.surface_config
}
}
pub fn render_master(storages: AllStoragesViewMut) {
let renderer = storages.borrow::<UniqueView<Renderer>>().unwrap();
let mut encoder = renderer.device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
let mut encoder = renderer.device().create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("main_encoder"),
});
let surface_texture = renderer.surface().get_current_texture().unwrap();
let surface_view = surface_texture.texture.create_view(&wgpu::TextureViewDescriptor::default());
//Main in-game render pass
if storages.run(is_ingame) {
let bg_color = storages.borrow::<UniqueView<BackgroundColor>>().unwrap();
let bg = storages.borrow::<UniqueView<BackgroundColor>>().unwrap().0;
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("main0_pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
@ -197,9 +39,9 @@ pub fn render_master(storages: AllStoragesViewMut) {
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color {
r: bg_color.0.x as f64,
g: bg_color.0.y as f64,
b: bg_color.0.z as f64,
r: bg.x as f64,
g: bg.y as f64,
b: bg.z as f64,
a: 1.0,
}),
store: wgpu::StoreOp::Store,
@ -212,27 +54,13 @@ pub fn render_master(storages: AllStoragesViewMut) {
let data = (&mut render_pass, &*renderer);
storages.run_with_data(world::draw_world, data);
// render_pass.set_pipeline(&renderer.pipeline);
// render_pass.set_bind_group(0, &renderer.bind_group, &[]);
// render_pass.set_vertex_buffer(0, renderer.vertex_buffer.slice(..));
// render_pass.set_index_buffer(renderer.index_buffer.slice(..));
// render_pass.draw_indexed(0..renderer.num_indices, 0, 0..1);
}
renderer.queue().submit(std::iter::once(encoder.finish()));
surface_texture.present();
}
// pub fn clear_background(
// mut target: NonSendSync<UniqueViewMut<RenderTarget>>,
// color: UniqueView<BackgroundColor>,
// ) {
// target.0.clear_color_srgb_and_depth((color.0.x, color.0.y, color.0.z, 1.), 1.);
// }
//Resize the renderer
/// Resize the renderer when the window is resized
pub fn resize_renderer(
mut renderer: UniqueViewMut<Renderer>,
resize: View<WindowResizedEvent>,
@ -242,11 +70,15 @@ pub fn resize_renderer(
}
}
//not sure if this belongs here
//Deprecated WindowSize thingy
pub fn init_window_size(
storages: AllStoragesView,
) {
#[derive(Unique, Clone, Copy)]
#[repr(transparent)]
#[deprecated = "use Renderer.size instead"]
#[allow(deprecated)]
pub struct WindowSize(pub UVec2);
pub fn init_window_size(storages: AllStoragesView) {
let size = storages.borrow::<View<WindowResizedEvent>>().unwrap().iter().next().unwrap().0;
storages.add_unique(WindowSize(size))
}
@ -260,8 +92,8 @@ pub fn update_window_size(
}
}
pub fn if_resized (
resize: View<WindowResizedEvent>,
) -> bool {
resize.len() > 0
}
// pub fn if_resized (
// resize: View<WindowResizedEvent>,
// ) -> bool {
// resize.len() > 0
// }

View file

@ -0,0 +1,157 @@
use pollster::FutureExt;
use shipyard::Unique;
use winit::{
event_loop::ActiveEventLoop,
window::{Fullscreen, Window},
dpi::PhysicalSize
};
use crate::settings::{GameSettings, FullscreenMode};
#[derive(Unique)]
pub struct Renderer {
instance: wgpu::Instance,
surface: wgpu::Surface<'static>,
device: wgpu::Device,
queue: wgpu::Queue,
surface_config: wgpu::SurfaceConfiguration,
size: PhysicalSize<u32>,
// pub depth_texture: wgpu::Texture,
//must be last due to drop order
window: Window,
}
impl Renderer {
pub fn init(event_loop: &ActiveEventLoop, settings: &GameSettings) -> Self {
log::info!("initializing display");
let window_attributes = Window::default_attributes()
.with_title("kubi")
.with_maximized(true)
.with_min_inner_size(PhysicalSize::new(640, 480))
.with_fullscreen({
//this has no effect on android, so skip this pointless stuff
#[cfg(target_os = "android")] {
None
}
#[cfg(not(target_os = "android"))]
if let Some(fs_settings) = &settings.fullscreen {
let monitor = event_loop.primary_monitor().or_else(|| {
event_loop.available_monitors().next()
});
if let Some(monitor) = monitor {
log::info!("monitor: {}", monitor.name().unwrap_or_else(|| "generic".into()));
match fs_settings.mode {
FullscreenMode::Borderless => {
log::info!("starting in borderless fullscreen mode");
Some(Fullscreen::Borderless(Some(monitor)))
},
FullscreenMode::Exclusive => {
log::warn!("exclusive fullscreen mode is experimental");
log::info!("starting in exclusive fullscreen mode");
//TODO: grabbing the first video mode is probably not the best idea...
monitor.video_modes().next()
.map(|vmode| {
log::info!("video mode: {}", vmode.to_string());
Some(Fullscreen::Exclusive(vmode))
})
.unwrap_or_else(|| {
log::warn!("no valid video modes found, falling back to windowed mode instead");
None
})
}
}
} else {
log::warn!("no monitors found, falling back to windowed mode");
None
}
} else {
log::info!("starting in windowed mode");
None
}
});
let window = event_loop.create_window(window_attributes).unwrap();
let size = window.inner_size();
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends: wgpu::Backends::BROWSER_WEBGPU | wgpu::Backends::VULKAN | wgpu::Backends::GL,
..Default::default()
});
// Create a surface with `create_surface_unsafe` to get a surface with 'static lifetime
// It should never outlive the window it's created from
let surface = unsafe {
let target = wgpu::SurfaceTargetUnsafe::from_window(&window).unwrap();
instance.create_surface_unsafe(target).unwrap()
};
let adapter = instance.request_adapter(
&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::HighPerformance,
compatible_surface: Some(&surface),
force_fallback_adapter: false,
},
).block_on().unwrap();
let (device, queue) = adapter.request_device(
&wgpu::DeviceDescriptor {
label: None,
required_features: wgpu::Features::empty(),
required_limits: wgpu::Limits::downlevel_defaults(),
},
None,
).block_on().unwrap();
let surface_config = surface.get_default_config(&adapter, size.width, size.height).unwrap();
surface.configure(&device, &surface_config);
Self { window, instance, surface, device, queue, surface_config, size }
}
pub fn resize(&mut self, size: PhysicalSize<u32>) {
if size.width == 0 || size.height == 0 {
log::warn!("Ignoring resize event with zero width or height");
return
}
if self.size == size {
log::warn!("Ignoring resize event with same size");
return
}
log::debug!("resizing surface to {:?}", size);
self.size = size;
self.surface_config.width = size.width;
self.surface_config.height = size.height;
self.surface.configure(&self.device, &self.surface_config);
}
pub fn reconfigure(&self) {
self.surface.configure(&self.device, &self.surface_config);
}
//getters:
pub fn size(&self) -> PhysicalSize<u32> {
self.size
}
pub fn window(&self) -> &Window {
&self.window
}
pub fn surface(&self) -> &wgpu::Surface<'static> {
&self.surface
}
pub fn device(&self) -> &wgpu::Device {
&self.device
}
pub fn queue(&self) -> &wgpu::Queue {
&self.queue
}
pub fn surface_config(&self) -> &wgpu::SurfaceConfiguration {
&self.surface_config
}
}

View file

@ -1,7 +1,7 @@
use bytemuck::{Pod, Zeroable};
use glam::IVec3;
use shipyard::{AllStoragesView, NonSendSync, Unique, UniqueView, UniqueViewMut, View};
use kubi_shared::transform::Transform;
use glam::{IVec3, Vec3};
use shipyard::{AllStoragesView, IntoIter, NonSendSync, Unique, UniqueView, UniqueViewMut, View};
use kubi_shared::{chunk::CHUNK_SIZE, transform::Transform};
use crate::{camera::Camera, settings::GameSettings, world::{ChunkMeshStorage, ChunkStorage}};
use super::Renderer;
@ -43,7 +43,37 @@ pub fn draw_world(
settings: UniqueView<GameSettings>,
mut trans_queue: UniqueViewMut<TransChunkQueue>,
) {
//TODO
let camera = camera.iter().next().expect("No cameras in the scene");
let camera_matrix = camera.view_matrix * camera.perspective_matrix;
for (&position, chunk) in &chunks.chunks {
if let Some(key) = chunk.mesh_index {
let mesh = meshes.get(key).expect("Mesh index pointing to nothing");
let world_position = position.as_vec3() * CHUNK_SIZE as f32;
//Skip if mesh is empty
if mesh.main.index.size() == 0 && mesh.trans.index.size() == 0 {
continue
}
//Frustum culling
let minp = world_position;
let maxp = world_position + Vec3::splat(CHUNK_SIZE as f32);
if !camera.frustum.is_box_visible(minp, maxp) {
continue
}
//Draw chunk mesh
if mesh.main.index.size() > 0 {
//TODO
}
//TODO trans chunks
// if mesh.trans_index_buffer.len() > 0 {
// trans_queue.0.push(position);
// }
}
}
}