Merge pull request #21 from griffi-gh/wgpu2

wgpu rewrite (v2)
This commit is contained in:
griffi-gh 2024-05-13 23:57:28 +02:00 committed by GitHub
commit 0802e4806c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
67 changed files with 3077 additions and 1580 deletions

7
.gitignore vendored
View file

@ -27,3 +27,10 @@ _visualizer.json
*.so
*.dylib
*.rlib
#but keep the dxcompiler.dll/dxil.dll
!dxcompiler.dll
!dxil.dll
# blender backup files
*.blend1

872
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -21,10 +21,13 @@ opt-level = 1
[profile.dev.package.uflow]
opt-level = 3
[profile.dev.package.glium]
[profile.dev.package.wgpu]
opt-level = 3
[profile.dev.package.bracket-noise]
[profile.dev.package.wgpu-core]
opt-level = 3
[profile.dev.package.wgpu-hal]
opt-level = 3
[profile.dev.package.fastnoise-lite]
@ -38,5 +41,7 @@ opt-level = 3
[profile.dev.package.android-activity]
debug-assertions = false
[patch.crates-io]
glium = { git = "https://github.com/glium/glium", rev = "a352c667" }
# [patch.'https://github.com/griffi-gh/hui']
# hui = { path = "X:/Projects/hui/hui" }
# hui-winit = { path = "X:/Projects/hui/hui-winit" }
# hui-wgpu = { path = "X:/Projects/hui/hui-wgpu" }

Binary file not shown.

64
assets/playermodel1.obj Normal file
View file

@ -0,0 +1,64 @@
# Blender 4.0.0 Beta
# www.blender.org
o Cube
v -0.500000 0.708333 -0.666667
v -0.500000 -0.291667 -0.666667
v 0.500000 0.708333 -0.666667
v 0.500000 -0.291667 -0.666667
v -0.500000 0.708333 0.333333
v -0.500000 -0.291667 0.333333
v 0.500000 0.708333 0.333333
v 0.500000 -0.291667 0.333333
v -0.500000 0.958333 -0.666667
v 0.500000 0.958333 -0.666667
v -0.166667 0.708333 -0.666667
v 0.166667 0.708333 -0.666667
vn -0.0000 1.0000 -0.0000
vn 1.0000 -0.0000 -0.0000
vn -0.0000 -0.0000 1.0000
vn -0.0000 -1.0000 -0.0000
vn -0.0000 -0.0000 -1.0000
vn -1.0000 -0.0000 -0.0000
vt 0.204555 0.291387
vt 0.043204 0.462923
vt 0.043204 0.291387
vt 0.259623 0.207472
vt 0.024467 0.457472
vt 0.024467 0.207472
vt 0.177715 0.183914
vt 0.010921 0.538561
vt 0.010921 0.183914
vt 0.246583 0.218979
vt 0.011426 0.468979
vt 0.011426 0.218979
vt 0.896961 0.811182
vt 0.168955 0.037222
vt 0.896961 0.037221
vt 0.177715 0.538561
vt 0.010921 0.361238
vt 0.168955 0.811182
vt 0.411624 0.811182
vt 0.204555 0.462923
vt 0.259623 0.457472
vt 0.246583 0.468979
vt 0.177715 0.361238
vt 0.896961 0.990308
vt 0.654292 0.811182
vt 0.168955 0.990308
s 0
f 5/1/1 3/2/1 1/3/1
f 3/4/2 8/5/2 4/6/2
f 7/7/3 6/8/3 8/9/3
f 2/10/4 8/11/4 6/12/4
f 1/13/5 4/14/5 2/15/5
f 5/16/6 2/17/6 6/8/6
f 3/18/3 1/13/3 12/19/3
f 5/1/1 7/20/1 3/2/1
f 3/4/2 7/21/2 8/5/2
f 7/7/3 5/16/3 6/8/3
f 2/10/4 4/22/4 8/11/4
f 1/13/5 3/18/5 4/14/5
f 5/16/6 1/23/6 2/17/6
f 1/13/5 9/24/5 11/25/5
f 12/19/5 10/26/5 3/18/5
f 1/13/5 11/25/5 12/19/5

BIN
assets/playermodel1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

View file

@ -9,7 +9,7 @@ pub fn init() {
use env_logger::{fmt::Color, Builder, Env};
let env = Env::default()
.filter_or("RUST_LOG", "trace,gilrs=warn,rusty_xinput=warn");
.filter_or("RUST_LOG", "trace,gilrs=warn,rusty_xinput=warn,wgpu=warn,wgpu_core=warn,wgpu_hal=warn,hui=info,hui-winit=info,hui-glium=info,hui-wgpu=info,naga=warn");
Builder::from_env(env)
.format(|buf, record| {
let mut level_style = buf.style();

View file

@ -15,7 +15,7 @@ glam = { version = "0.27", features = ["debug-glam-assert", "fast-math"] }
hashbrown = "0.14"
nohash-hasher = "0.2"
anyhow = "1.0"
rayon = "1.7"
rayon = "1.10"
flume = "0.11"
rand = "0.8"
uflow = "0.7"

View file

@ -1,4 +1,3 @@
use fastnoise_lite::FastNoiseLite;
use glam::ivec3;
use crate::{block::Block, chunk::CHUNK_SIZE, worldgen::SeedThingy};
use super::{

View file

@ -11,21 +11,21 @@ crate-type = ["lib", "cdylib"]
[dependencies]
kubi-shared = { path = "../kubi-shared" }
kubi-logging = { path = "../kubi-logging" }
hui = { version = "0.1.0-alpha.4", git = "https://github.com/griffi-gh/hui", rev = "dd5af8b9e2" }
hui-glium = { version = "0.1.0-alpha.4", git = "https://github.com/griffi-gh/hui", rev = "dd5af8b9e2" }
hui-winit = { version = "0.1.0-alpha.4", git = "https://github.com/griffi-gh/hui", rev = "dd5af8b9e2" }
hui = { git = "https://github.com/griffi-gh/hui", rev = "6eb3d98ad4" }
hui-wgpu = { git = "https://github.com/griffi-gh/hui", rev = "6eb3d98ad4" }
hui-winit = { git = "https://github.com/griffi-gh/hui", rev = "6eb3d98ad4", features = ["winit_30"] }
log = "0.4"
glium = { git = "https://github.com/glium/glium", rev = "a352c667" }
glutin = "0.31"
winit = { version = "0.29", features = ["android-native-activity"] }
glutin-winit = "0.4"
raw-window-handle = "=0.5.*"
wgpu = { version = "0.20", features = ["webgl"] }
pollster = "0.3"
bytemuck = { version = "1.15", features = ["derive"] }
winit = { version = "0.30", features = ["android-native-activity"] }
raw-window-handle = "0.6"
glam = { version = "0.27", features = ["debug-glam-assert", "fast-math"] }
image = { version = "0.25", default_features = false, features = ["png"] }
strum = { version = "0.26", features = ["derive"] }
hashbrown = "0.14"
nohash-hasher = "0.2"
rayon = "1.7"
rayon = "1.10"
shipyard = { git = "https://github.com/leudz/shipyard", rev = "aacf3b1df5", default-features = false, features = ["std", "proc", "thread_local"] }
anyhow = "1.0"
flume = "0.11"
@ -38,10 +38,11 @@ tinyset = "0.4"
serde_json = { version = "1.0", optional = true } #only used for `generate_visualizer_data`
rand = { version = "0.8", features = ["alloc", "small_rng"]}
atomic = "0.6"
tobj = "4.0"
[target.'cfg(target_os = "android")'.dependencies]
android-activity = "^0.5.2"
ndk = "0.8"
android-activity = "0.6"
ndk = "0.9"
[target.'cfg(target_os = "windows")'.dependencies]
winapi = { version = "0.3", features = ["wincon"] }

14
kubi/shaders/c2d.wgsl Normal file
View file

@ -0,0 +1,14 @@
@group(0) @binding(0)
var<uniform> color: vec4<f32>;
@vertex
fn vs_main(
@location(0) position: vec2<f32>,
) -> @builtin(position) vec4<f32> {
return vec4<f32>(position, 0.0, 1.0);
}
@fragment
fn fs_main() -> @location(0) vec4<f32> {
return color;
}

View file

@ -1,12 +0,0 @@
#version 300 es
precision highp float;
out vec4 out_color;
uniform vec4 color;
void main() {
// discard fully transparent pixels
if (color.w <= 0.) discard;
out_color = color;
}

View file

@ -1,13 +0,0 @@
#version 300 es
precision highp float;
in vec3 position;
uniform mat4 model;
uniform mat4 perspective;
uniform mat4 view;
void main() {
mat4 modelview = view * model;
gl_Position = perspective * modelview * vec4(position, 1.);
}

View file

@ -1,10 +0,0 @@
#version 300 es
precision highp float;
out vec4 out_color;
uniform vec4 color;
void main() {
out_color = color;
}

View file

@ -1,9 +0,0 @@
#version 300 es
precision highp float;
in vec2 position;
void main() {
gl_Position = vec4(position, 0.0, 1.0);
}

View file

@ -0,0 +1,50 @@
struct CameraUniform {
view_proj: mat4x4<f32>,
};
@group(1) @binding(0)
var<uniform> camera: CameraUniform;
struct VertexInput {
@location(0) uv: vec2<f32>,
@location(1) position: vec3<f32>,
@location(2) normal: vec3<f32>,
@location(3) mat_row0: vec4<f32>,
@location(4) mat_row1: vec4<f32>,
@location(5) mat_row2: vec4<f32>,
@location(6) mat_row3: vec4<f32>,
}
struct VertexOutput {
@builtin(position) clip_position: vec4<f32>,
@location(0) uv: vec2<f32>,
@location(1) normal: vec3<f32>,
};
@vertex
fn vs_main(
in: VertexInput,
) -> VertexOutput {
let inst_mat = mat4x4<f32>(
in.mat_row0,
in.mat_row1,
in.mat_row2,
in.mat_row3,
);
var out: VertexOutput;
out.clip_position = camera.view_proj * (inst_mat * vec4<f32>(in.position, 1.0));
out.uv = in.uv;
out.normal = in.normal;
return out;
}
@group(0) @binding(0)
var t_diffuse: texture_2d<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);
}

View file

@ -0,0 +1,27 @@
struct CameraUniform {
view_proj: mat4x4<f32>,
};
@group(0) @binding(0)
var<uniform> camera: CameraUniform;
struct SboxUniform {
position: vec3<f32>,
};
@group(1) @binding(0)
var<uniform> sbox: SboxUniform;
@vertex
fn vs_main(
@location(0) position: vec3<f32>,
) -> @builtin(position) vec4<f32> {
return camera.view_proj * vec4<f32>(position + sbox.position, 1.0);
}
@fragment
fn fs_main(
@builtin(position) in: vec4<f32>,
) -> @location(0) vec4<f32> {
return vec4<f32>(0.0, 0.0, 0.0, 0.5);
}

View file

@ -1,26 +0,0 @@
#version 300 es
precision highp float;
precision lowp sampler2DArray;
in vec3 v_normal;
in vec2 v_uv;
flat in uint v_tex_index;
out vec4 color;
uniform sampler2DArray tex;
uniform bool discard_alpha;
void main() {
// base color from texture
color = texture(tex, vec3(v_uv, v_tex_index));
// discard fully transparent pixels
if (discard_alpha ? (color.w < 0.5) : (color.w == 0.)) discard;
//basic "lighting"
float light = abs(v_normal.x) + .8 * abs(v_normal.y) + .6 * abs(v_normal.z);
color *= vec4(vec3(light), 1.);
//discard alpha
if (discard_alpha) color.w = 1.;
}

View file

@ -1,27 +0,0 @@
#version 300 es
precision highp float;
//TODO pack this data:
// uint position_normal_uv
// XXYYZZNU
// wehere Normal and Uv are enums
// maybe somehow pack in tex index too
in vec3 position;
in vec3 normal;
in vec2 uv;
in uint tex_index;
out vec2 v_uv;
out vec3 v_normal;
flat out uint v_tex_index;
uniform vec3 position_offset;
uniform mat4 perspective;
uniform mat4 view;
void main() {
v_normal = normal;
v_tex_index = tex_index;
v_uv = uv;
gl_Position = perspective * view * vec4(position + position_offset, 1.);
}

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

@ -0,0 +1,62 @@
struct CameraUniform {
view_proj: mat4x4<f32>,
};
@group(1) @binding(0)
var<uniform> camera: CameraUniform;
struct VertexInput {
@location(0) position: vec3<f32>,
@location(1) normal: vec3<f32>,
@location(2) uv: vec2<f32>,
@location(3) tex_index: u32,
}
struct VertexOutput {
@builtin(position) clip_position: vec4<f32>,
@location(0) uv: vec2<f32>,
@location(1) normal: vec3<f32>,
@location(2) @interpolate(flat)tex_index: u32,
};
@vertex
fn vs_main(
in: VertexInput,
) -> VertexOutput {
var out: VertexOutput;
out.uv = in.uv;
out.normal = in.normal;
out.tex_index = in.tex_index;
out.clip_position = camera.view_proj * 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> {
// not actual lighting; makes it easier to distinguish unlit faces
let light: f32 =
abs(in.normal.x) + .85 *
abs(in.normal.y) + .65 *
abs(in.normal.z);
let color: vec4<f32> =
textureSample(t_diffuse, s_diffuse, in.uv, in.tex_index)
* vec4<f32>(light, light, light, 1.0);
if (color.a < 0.5) {
discard;
}
return color;
}
@fragment
fn fs_main_trans(in: VertexOutput) -> @location(0) vec4<f32> {
return textureSample(t_diffuse, s_diffuse, in.uv, in.tex_index);
}

View file

@ -1,6 +1,6 @@
use glam::{Vec3, Mat4};
use shipyard::{ViewMut, View, IntoIter, Workload, IntoWorkload, track, UniqueView, SystemModificator};
use crate::{transform::Transform, rendering::WindowSize, events::WindowResizedEvent};
use crate::{transform::Transform, rendering::Renderer, events::WindowResizedEvent};
use super::Camera;
//maybe parallelize these two?
@ -18,12 +18,13 @@ fn update_view_matrix(
fn update_perspective_matrix(
mut vm_camera: ViewMut<Camera>,
size: UniqueView<WindowSize>,
ren: UniqueView<Renderer>,
) {
let sz = ren.size_vec2();
for mut camera in (&mut vm_camera).iter() {
camera.perspective_matrix = Mat4::perspective_rh_gl(
camera.perspective_matrix = Mat4::perspective_rh(
camera.fov,
size.0.x as f32 / size.0.y as f32,
sz.x / sz.y,
camera.z_near,
camera.z_far,
)

View file

@ -1,5 +1,5 @@
use kubi_shared::networking::client::ClientId;
use shipyard::{AllStoragesView, Unique, UniqueViewMut};
use shipyard::{AllStoragesView, Unique};
pub enum ChatMessage {
PlayerMessage {

View file

@ -1,7 +1,7 @@
//TODO client-side physics
//TODO move this to shared
use glam::{vec3, IVec3, Mat4, Vec3, Vec3Swizzles};
use shipyard::{track, AllStoragesView, Component, IntoIter, Unique, UniqueView, View, ViewMut};
use glam::{vec3, Mat4, Vec3, Vec3Swizzles};
use shipyard::{track, AllStoragesView, Component, IntoIter, Unique, UniqueView, ViewMut};
use kubi_shared::{block::{Block, CollisionType}, transform::Transform};
use crate::{delta_time::DeltaTime, world::ChunkStorage};

View file

@ -1,5 +1,5 @@
use shipyard::{AllStoragesView, IntoIter, NonSendSync, Unique, UniqueView, UniqueViewMut, View};
use crate::{events::InputDeviceEvent, rendering::{Renderer, WindowSize}};
use shipyard::{AllStoragesView, IntoIter, Unique, UniqueView, UniqueViewMut, View};
use crate::{events::InputDeviceEvent, rendering::Renderer};
use winit::{
dpi::PhysicalPosition, event::{DeviceEvent, ElementState, RawKeyEvent}, keyboard::{KeyCode, PhysicalKey}, window::CursorGrabMode
};
@ -9,14 +9,14 @@ pub struct CursorLock(pub bool);
pub fn update_cursor_lock_state(
lock: UniqueView<CursorLock>,
display: NonSendSync<UniqueView<Renderer>>
display: UniqueView<Renderer>
) {
if cfg!(target_os = "android") {
return
}
if lock.is_inserted_or_modified() {
//TODO MIGRATION
let window = &display.window;
let window = display.window();
window.set_cursor_grab(match lock.0 {
true => CursorGrabMode::Confined,
false => CursorGrabMode::None,
@ -41,8 +41,7 @@ pub fn lock_cursor_now(
pub fn debug_toggle_lock(
mut lock: UniqueViewMut<CursorLock>,
device_events: View<InputDeviceEvent>,
ren: NonSendSync<UniqueView<Renderer>>,
size: UniqueView<WindowSize>,
ren: UniqueView<Renderer>,
) {
for evt in device_events.iter() {
if let DeviceEvent::Key(RawKeyEvent {
@ -51,8 +50,8 @@ pub fn debug_toggle_lock(
}) = evt.event {
lock.0 = !lock.0;
if !lock.0 {
let center = PhysicalPosition::new(size.0.x as f64 / 2., size.0.y as f64 / 2.);
let _ = ren.window.set_cursor_position(center);
let center = PhysicalPosition::new(ren.size().width as f64 / 2., ren.size().height as f64 / 2.);
let _ = ren.window().set_cursor_position(center);
}
}
}

View file

@ -41,7 +41,7 @@ pub fn process_winit_events(world: &mut World, event: &Event<()>) {
EventComponent,
InputDeviceEvent {
device_id: *device_id,
event: DeviceEvent::Key(RawKeyEvent {
event: DeviceEvent::Key(winit::event::RawKeyEvent {
physical_key: event.physical_key,
state: event.state,
})
@ -86,18 +86,18 @@ pub fn process_winit_events(world: &mut World, event: &Event<()>) {
}
}
pub fn initial_resize_event(
mut storages: AllStoragesViewMut,
) {
let (w, h) = {
let renderer = storages.borrow::<NonSendSync<UniqueView<Renderer>>>().unwrap();
renderer.display.get_framebuffer_dimensions()
};
storages.add_entity((
EventComponent,
WindowResizedEvent(UVec2::new(w, h))
));
}
// pub fn initial_resize_event(
// mut storages: AllStoragesViewMut,
// ) {
// let (w, h) = {
// let renderer = storages.borrow::<UniqueView<Renderer>>().unwrap();
// (renderer.size().width, renderer.size().height)
// };
// storages.add_entity((
// EventComponent,
// WindowResizedEvent(UVec2::new(w, h))
// ));
// }
pub fn clear_events(
mut all_storages: AllStoragesViewMut,

View file

@ -1,21 +1,22 @@
use hui::UiInstance;
use hui_glium::GliumUiRenderer;
use hui_wgpu::WgpuUiRenderer;
//use hui_glium::GliumUiRenderer;
use shipyard::{AllStoragesView, Unique, UniqueView, NonSendSync, UniqueViewMut};
use crate::rendering::{RenderTarget, Renderer, WindowSize};
use crate::rendering::{RenderCtx, Renderer};
#[derive(Unique)]
pub struct UiState {
pub hui: UiInstance,
pub renderer: GliumUiRenderer
pub renderer: WgpuUiRenderer,
}
pub fn kubi_ui_init(
storages: AllStoragesView
) {
let renderer = storages.borrow::<NonSendSync<UniqueView<Renderer>>>().unwrap();
let renderer = storages.borrow::<UniqueView<Renderer>>().unwrap();
storages.add_unique_non_send_sync(UiState {
hui: UiInstance::new(),
renderer: GliumUiRenderer::new(&renderer.display),
renderer: WgpuUiRenderer::new(renderer.device(), renderer.surface_config().format),
});
}
@ -26,20 +27,20 @@ pub fn kubi_ui_begin(
}
pub fn kubi_ui_end(
mut ui: NonSendSync<UniqueViewMut<UiState>>
mut ui: NonSendSync<UniqueViewMut<UiState>>,
renderer: UniqueView<Renderer>,
) {
let ui: &mut UiState = &mut ui;
let UiState { hui, renderer, .. } = ui;
let UiState { hui, renderer: ui_renderer } = ui;
hui.end();
renderer.update(hui);
ui_renderer.update(hui, renderer.queue(), renderer.device(), renderer.size_vec2());
}
pub fn kubi_ui_draw(
ctx: &mut RenderCtx,
ui: NonSendSync<UniqueView<UiState>>,
mut target: NonSendSync<UniqueViewMut<RenderTarget>>,
size: UniqueView<WindowSize>
) {
ui.renderer.draw(&mut target.0, size.0.as_vec2());
ui.renderer.draw(ctx.encoder, ctx.surface_view);
}
pub fn hui_process_winit_events(

View file

@ -10,7 +10,7 @@ use nohash_hasher::BuildNoHashHasher;
use shipyard::{AllStoragesView, Unique, View, IntoIter, UniqueViewMut, Workload, IntoWorkload, UniqueView, NonSendSync};
use crate::{
events::{InputDeviceEvent, TouchEvent},
rendering::WindowSize
rendering::Renderer,
};
#[derive(Unique, Clone, Copy, Default, Debug)]
@ -211,10 +211,10 @@ fn update_input_state_gamepad (
fn update_input_state_touch (
touch_state: UniqueView<RawTouchState>,
win_size: UniqueView<WindowSize>,
renderer: UniqueView<Renderer>,
mut inputs: UniqueViewMut<Inputs>,
) {
let w = win_size.0.as_dvec2();
let w = renderer.size_uvec2().as_dvec2();
//Movement
if let Some(finger) = touch_state.query_area(

View file

@ -13,7 +13,7 @@
use shipyard::{
World, Workload, IntoWorkload,
UniqueView, UniqueViewMut,
NonSendSync, WorkloadModificator,
WorkloadModificator,
SystemModificator
};
use winit::{
@ -67,27 +67,10 @@ use player::{spawn_player, MainPlayer};
use prefabs::load_prefabs;
use settings::{load_settings, GameSettings};
use camera::compute_cameras;
use events::{
clear_events,
process_winit_events,
initial_resize_event,
player_actions::generate_move_events,
};
use events::{clear_events, process_winit_events, player_actions::generate_move_events};
use input::{init_input, process_inputs};
use player_controller::{debug_switch_ctl_type, update_player_controllers};
use rendering::{
Renderer,
RenderTarget,
BackgroundColor,
clear_background,
init_window_size,
update_window_size,
primitives::init_primitives,
world::{init_trans_chunk_queue, draw_world, draw_world_trans, draw_current_chunk_border},
selection_box::render_selection_box,
entities::render_entities,
sumberge::render_submerged_view,
};
use rendering::{BackgroundColor, Renderer, init_rendering, render_master, update_rendering_early, update_rendering_late};
use block_placement::update_block_placement;
use delta_time::{DeltaTime, init_delta_time};
use cursor_lock::{debug_toggle_lock, insert_lock_state, lock_cursor_now, update_cursor_lock_state};
@ -95,7 +78,7 @@ use control_flow::{exit_on_esc, insert_control_flow_unique, RequestExit};
use state::{is_ingame, is_ingame_or_loading, is_loading, init_state, update_state, is_connecting};
use networking::{update_networking, update_networking_late, is_multiplayer, disconnect_on_exit, is_singleplayer};
use init::initialize_from_args;
use hui_integration::{kubi_ui_begin, kubi_ui_draw, kubi_ui_end, kubi_ui_init};
use hui_integration::{kubi_ui_begin, /*kubi_ui_draw,*/ kubi_ui_end, kubi_ui_init};
use loading_screen::update_loading_screen;
use connecting_screen::update_connecting_screen;
use fixed_timestamp::init_fixed_timestamp_storage;
@ -105,8 +88,7 @@ use chat_ui::render_chat;
use chat::init_chat_manager;
use crosshair_ui::{init_crosshair_image, draw_crosshair};
use settings_ui::render_settings_ui;
use crate::hui_integration::hui_process_winit_events;
use hui_integration::hui_process_winit_events;
/// stuff required to init the renderer and other basic systems
fn pre_startup() -> Workload {
@ -118,11 +100,9 @@ fn pre_startup() -> Workload {
fn startup() -> Workload {
(
init_fixed_timestamp_storage,
initial_resize_event,
init_window_size,
kubi_ui_init,
load_prefabs,
init_primitives,
init_rendering,
insert_lock_state,
init_state,
initialize_from_args,
@ -133,14 +113,13 @@ fn startup() -> Workload {
init_client_physics,
init_chat_manager,
init_crosshair_image,
init_trans_chunk_queue,
).into_sequential_workload()
}
fn update() -> Workload {
(
update_rendering_early,
debug_toggle_lock,
update_window_size,
update_cursor_lock_state,
process_inputs,
kubi_ui_begin,
@ -179,25 +158,26 @@ fn update() -> Workload {
update_state,
exit_on_esc,
disconnect_on_exit.run_if(is_multiplayer),
update_rendering_late,
).into_sequential_workload()
}
fn render() -> Workload {
(
clear_background,
(
draw_world,
draw_current_chunk_border,
render_selection_box,
render_entities,
draw_world_trans,
render_submerged_view,
).into_sequential_workload().run_if(is_ingame),
kubi_ui_draw,
).into_sequential_workload()
}
// fn render() -> Workload {
// (
// clear_background,
// (
// draw_world,
// draw_current_chunk_border,
// render_selection_box,
// render_entities,
// draw_world_trans,
// render_submerged_view,
// ).into_sequential_workload().run_if(is_ingame),
// kubi_ui_draw,
// ).into_sequential_workload()
// }
fn after_frame_end() -> Workload {
fn after_render() -> Workload {
(
clear_events,
).into_sequential_workload()
@ -245,8 +225,8 @@ pub fn kubi_main(
world.add_workload(pre_startup);
world.add_workload(startup);
world.add_workload(update);
world.add_workload(render);
world.add_workload(after_frame_end);
//world.add_workload(render);
world.add_workload(after_render);
//Save _visualizer.json
#[cfg(feature = "generate_visualizer_data")]
@ -286,7 +266,7 @@ pub fn kubi_main(
let settings = world.borrow::<UniqueView<GameSettings>>().unwrap();
world.add_unique_non_send_sync(Renderer::init(window_target, &settings));
}
world.add_unique(BackgroundColor(vec3(0.5, 0.5, 1.)));
world.add_unique(BackgroundColor(vec3(0.21, 0.21, 1.)));
//Run startup systems
world.run_workload(startup).unwrap();
@ -326,22 +306,24 @@ pub fn kubi_main(
//Run update workflows
world.run_workload(update).unwrap();
world.run(render_master);
//Start rendering (maybe use custom views for this?)
let target = {
let renderer = world.borrow::<NonSendSync<UniqueView<Renderer>>>().unwrap();
renderer.display.draw()
};
world.add_unique_non_send_sync(RenderTarget(target));
// let target = {
// let renderer = world.borrow::<UniqueView<Renderer>>().unwrap();
// renderer.display.draw()
// };
// world.add_unique_non_send_sync(RenderTarget(target));
//Run render workflow
world.run_workload(render).unwrap();
//world.run_workload(render).unwrap();
//Finish rendering
let target = world.remove_unique::<RenderTarget>().unwrap();
target.0.finish().unwrap();
// let target = world.remove_unique::<RenderTarget>().unwrap();
// target.0.finish().unwrap();
//After frame end
world.run_workload(after_frame_end).unwrap();
world.run_workload(after_render).unwrap();
//Process control flow changes
if world.borrow::<UniqueView<RequestExit>>().unwrap().0 {

View file

@ -1,15 +1,35 @@
use std::{io::{BufReader, Read}, path::Path};
use std::{io::{BufReader, Read}, path::{Path, PathBuf}};
use bytemuck::{Pod, Zeroable};
use hui::text::FontHandle;
use shipyard::{AllStoragesView, NonSendSync, Unique, UniqueView, UniqueViewMut};
use glium::{texture::{SrgbTexture2dArray, MipmapsOption}, Program};
use kubi_shared::block::BlockTexture;
use crate::{filesystem::AssetManager, hui_integration::UiState, rendering::Renderer};
use crate::{filesystem::AssetManager, hui_integration::UiState, rendering::{BufferPair, Renderer}};
mod texture;
mod shaders;
//TODO move to rendering module
use texture::load_texture2darray_prefab;
use shaders::include_shader_prefab;
mod loader;
use loader::{load_texture2darray_prefab, load_texture2d_prefab, load_obj_prefab};
#[derive(Clone, Copy, Default, Pod, Zeroable)]
#[repr(C, packed)]
pub struct ModelVertex {
pub tex_coords: [f32; 2],
pub position: [f32; 3],
pub _padding: u32,
pub normal: [f32; 3],
}
impl ModelVertex {
pub const LAYOUT: wgpu::VertexBufferLayout<'static> = wgpu::VertexBufferLayout {
array_stride: std::mem::size_of::<ModelVertex>() as wgpu::BufferAddress,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: &wgpu::vertex_attr_array![
0 => Float32x2,
1 => Float32x3,
2 => Float32x3,
],
};
}
pub trait AssetPaths {
fn file_name(self) -> &'static str;
@ -40,20 +60,15 @@ impl AssetPaths for BlockTexture {
}
#[derive(Unique)]
#[repr(transparent)]
pub struct BlockTexturesPrefab(pub SrgbTexture2dArray);
#[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 GpuPrefabs {
pub block_diffuse_texture: wgpu::Texture,
pub block_diffuse_bind_group_layout: wgpu::BindGroupLayout,
pub block_diffuse_bind_group: wgpu::BindGroup,
pub player_model_diffuse_texture: wgpu::Texture,
pub player_model_diffuse_bind_group_layout: wgpu::BindGroupLayout,
pub player_model_diffuse_bind_group: wgpu::BindGroup,
pub player_model: BufferPair,
}
#[derive(Unique)]
#[repr(transparent)]
@ -61,19 +76,132 @@ pub struct UiFontPrefab(pub FontHandle);
pub fn load_prefabs(
storages: AllStoragesView,
renderer: NonSendSync<UniqueView<Renderer>>,
renderer: UniqueView<Renderer>,
mut ui: NonSendSync<UniqueViewMut<UiState>>,
assman: UniqueView<AssetManager>
) {
log::info!("Loading textures...");
storages.add_unique_non_send_sync(BlockTexturesPrefab(
load_texture2darray_prefab::<BlockTexture, _>(
let block_diffuse_texture = load_texture2darray_prefab::<BlockTexture>(
&renderer,
&assman,
"blocks".into(),
&renderer.display,
MipmapsOption::AutoGeneratedMipmaps
)
));
);
log::info!("Creating bing groups");
let block_diffuse_view = block_diffuse_texture.create_view(&wgpu::TextureViewDescriptor {
label: Some("block_texture_view"),
..Default::default()
});
let block_diffuse_sampler = renderer.device().create_sampler(&wgpu::SamplerDescriptor {
label: Some("block_diffuse_sampler"),
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::Linear,
mipmap_filter: wgpu::FilterMode::Nearest,
..Default::default()
});
let block_diffuse_bind_group_layout = renderer.device()
.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("block_diffuse_bind_group_layout"),
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: true },
view_dimension: wgpu::TextureViewDimension::D2Array,
multisampled: false,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
}
]
});
let block_diffuse_bind_group = renderer.device().create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("block_diffuse_bind_group"),
layout: &block_diffuse_bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(&block_diffuse_view),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(&block_diffuse_sampler),
}
]
});
let player_model_diffuse_texture = load_texture2d_prefab(&renderer, &assman, &PathBuf::from("playermodel1.png"));
let player_model_diffuse_view = player_model_diffuse_texture.create_view(&wgpu::TextureViewDescriptor {
label: Some("player_model_texture_view"),
..Default::default()
});
let player_model_diffuse_sampler = renderer.device().create_sampler(&wgpu::SamplerDescriptor {
label: Some("player_model_sampler"),
address_mode_u: wgpu::AddressMode::ClampToEdge,
address_mode_v: wgpu::AddressMode::ClampToEdge,
address_mode_w: wgpu::AddressMode::ClampToEdge,
mag_filter: wgpu::FilterMode::Linear,
min_filter: wgpu::FilterMode::Linear,
mipmap_filter: wgpu::FilterMode::Nearest,
..Default::default()
});
let player_model_diffuse_bind_group_layout = renderer.device()
.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("player_model_bind_group_layout"),
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: true },
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
}
]
});
let player_model_diffuse_bind_group = renderer.device().create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("player_model_bind_group"),
layout: &player_model_diffuse_bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(&player_model_diffuse_view),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(&player_model_diffuse_sampler),
}
]
});
let player_model = load_obj_prefab(&renderer, &assman, &PathBuf::from("playermodel1.obj"));
storages.add_unique_non_send_sync(GpuPrefabs {
block_diffuse_texture,
block_diffuse_bind_group_layout,
block_diffuse_bind_group,
player_model_diffuse_texture,
player_model_diffuse_bind_group_layout,
player_model_diffuse_bind_group,
player_model,
});
log::info!("Loading the UI stuff...");
{
@ -85,33 +213,33 @@ pub fn load_prefabs(
storages.add_unique(UiFontPrefab(font_handle));
}
log::info!("Compiling shaders...");
storages.add_unique_non_send_sync(ChunkShaderPrefab(
include_shader_prefab!(
"world",
"../shaders/world.vert",
"../shaders/world.frag",
&renderer.display
)
));
storages.add_unique_non_send_sync(ColoredShaderPrefab(
include_shader_prefab!(
"colored",
"../shaders/colored.vert",
"../shaders/colored.frag",
&renderer.display
)
));
storages.add_unique_non_send_sync(Colored2ShaderPrefab(
include_shader_prefab!(
"colored",
"../shaders/colored2.vert",
"../shaders/colored2.frag",
&renderer.display
)
));
//log::info!("Compiling shaders...");
// storages.add_unique_non_send_sync(ChunkShaderPrefab(
// include_shader_prefab!(
// "world",
// "../shaders/world.vert",
// "../shaders/world.frag",
// &renderer.display
// )
// ));
// storages.add_unique_non_send_sync(ColoredShaderPrefab(
// include_shader_prefab!(
// "colored",
// "../shaders/colored.vert",
// "../shaders/colored.frag",
// &renderer.display
// )
// ));
// storages.add_unique_non_send_sync(Colored2ShaderPrefab(
// include_shader_prefab!(
// "colored",
// "../shaders/colored2.vert",
// "../shaders/colored2.frag",
// &renderer.display
// )
// ));
log::info!("releasing shader compiler");
//log::info!("releasing shader compiler");
renderer.display.release_shader_compiler();
//renderer.display.release_shader_compiler();
}

168
kubi/src/prefabs/loader.rs Normal file
View file

@ -0,0 +1,168 @@
use glam::UVec2;
use strum::IntoEnumIterator;
use rayon::prelude::*;
use wgpu::util::{DeviceExt, TextureDataOrder};
use std::{io::{BufReader, Read}, path::{Path, PathBuf}};
use crate::{filesystem::AssetManager, prefabs::ModelVertex, rendering::{BufferPair, Renderer}};
use super::AssetPaths;
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 label = format!("texture2darray_prefab_{}", directory.as_os_str().to_str().unwrap());
let desc = &wgpu::TextureDescriptor {
label: Some(&label),
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
)
}
pub fn load_texture2d_prefab(
renderer: &Renderer,
assman: &AssetManager,
path: &Path,
) -> wgpu::Texture {
log::info!("loading texture2d: {path:?}");
let image = image::load(
BufReader::new(assman.open_asset(path).expect("Failed to open texture file")),
image::ImageFormat::Png
).unwrap().to_rgba8();
let size = image.dimensions();
let data = image.into_raw();
let label = format!("texture2d_prefab_{}", path.file_name().unwrap().to_str().unwrap());
let desc = wgpu::TextureDescriptor {
label: Some(&label),
size: wgpu::Extent3d {
width: size.0,
height: size.1,
depth_or_array_layers: 1,
},
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
)
}
pub fn load_obj_prefab(
renderer: &Renderer,
assman: &AssetManager,
path: &Path,
) -> BufferPair {
log::info!("loading obj prefab: {path:?}");
let mut reader = BufReader::new(
assman.open_asset(path).expect("Failed to open texture file")
);
let (model, _) = tobj::load_obj_buf(
&mut reader,
&tobj::GPU_LOAD_OPTIONS,
|_| unimplemented!()
).unwrap();
assert_eq!(model.len(), 1, "only single model supported at the moment, sowwy :3");
let mesh = &model[0].mesh;
debug_assert!(mesh.normal_indices.is_empty() && mesh.texcoord_indices.is_empty(), "forgor single_index");
let tex_coords = bytemuck::cast_slice::<f32, [f32; 2]>(&mesh.texcoords);
let positions = bytemuck::cast_slice::<f32, [f32; 3]>(&mesh.positions);
let normals = bytemuck::cast_slice::<f32, [f32; 3]>(&mesh.normals);
let vertex_buffer: Vec<_> = (0..positions.len()).map(|i| {
ModelVertex {
tex_coords: [tex_coords[i][0], 1. - tex_coords[i][1]],
position: positions[i],
_padding: 0,
normal: normals[i],
}
}).collect();
let vertex_buffer = renderer.device().create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("obj_vertex_buffer"),
contents: bytemuck::cast_slice(&vertex_buffer),
usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::VERTEX,
});
let index_buffer = renderer.device().create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("obj_index_buffer"),
contents: bytemuck::cast_slice(&mesh.indices),
usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::INDEX,
});
BufferPair {
vertex: vertex_buffer,
vertex_len: positions.len() as u32,
index: index_buffer,
index_len: mesh.indices.len() as u32,
}
}

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,46 +0,0 @@
use strum::IntoEnumIterator;
use rayon::prelude::*;
use std::{path::PathBuf, io::BufReader};
use glium::{texture::{SrgbTexture2dArray, RawImage2d, MipmapsOption}, backend::Facade};
use crate::filesystem::AssetManager;
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")
}

View file

@ -1,191 +1,106 @@
use std::num::NonZeroU32;
use raw_window_handle::HasRawWindowHandle;
use shipyard::{Unique, NonSendSync, UniqueView, UniqueViewMut, View, IntoIter, AllStoragesView};
use winit::{
event_loop::EventLoopWindowTarget,
window::{WindowBuilder, Fullscreen, Window},
dpi::PhysicalSize
};
use glium::{Display, Surface, Version, Api as GlApiTy};
use glutin::{
config::{Api, ConfigTemplateBuilder}, context::{ContextAttributesBuilder, GlProfile}, display::GetGlDisplay, prelude::*, surface::{SurfaceAttributesBuilder, WindowSurface}
};
use glutin_winit::DisplayBuilder;
use glam::{Vec3, UVec2};
use crate::{events::WindowResizedEvent, settings::{GameSettings, FullscreenMode}};
use shipyard::{AllStoragesViewMut, IntoIter, IntoWorkload, SystemModificator, Unique, UniqueView, UniqueViewMut, View, Workload, WorkloadModificator};
use winit::dpi::PhysicalSize;
use glam::Vec3;
use crate::{events::WindowResizedEvent, hui_integration::kubi_ui_draw, state::is_ingame};
pub mod primitives;
mod renderer;
mod primitives;
mod selection_box;
mod entities;
pub use renderer::Renderer;
pub mod background;
pub mod world;
pub mod selection_box;
pub mod entities;
pub mod sumberge;
pub mod camera_uniform;
pub mod depth;
pub mod smoverlay;
pub struct BufferPair {
pub index: wgpu::Buffer,
pub index_len: u32,
pub vertex: wgpu::Buffer,
pub vertex_len: u32,
}
#[derive(Unique)]
#[repr(transparent)]
pub struct RenderTarget(pub glium::Frame);
#[derive(Unique)]
#[repr(transparent)]
pub struct BackgroundColor(pub Vec3);
#[derive(Unique, Clone, Copy)]
#[repr(transparent)]
pub struct WindowSize(pub UVec2);
#[derive(Unique)]
pub struct Renderer {
pub window: Window,
pub display: Display<WindowSurface>,
pub struct RenderCtx<'a> {
//pub renderer: &'a Renderer,
pub encoder: &'a mut wgpu::CommandEncoder,
pub surface_view: &'a wgpu::TextureView,
}
impl Renderer {
pub fn init(event_loop: &EventLoopWindowTarget<()>, settings: &GameSettings) -> Self {
log::info!("initializing display");
//TODO run init_world_render_state, init_selection_box_state, etc. only once ingame?
let wb = WindowBuilder::new()
.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
}
pub fn init_rendering() -> Workload {
(
depth::init_depth_texture,
camera_uniform::init_camera_uniform_buffer,
primitives::init_primitives,
world::init_world_render_state, //req: depth, camera
entities::init_entities_render_state, //req: depth, camera
selection_box::init_selection_box_render_state, //req: depth, camera, primitives
smoverlay::init_smoverlay_render_state, //req: primitives
).into_sequential_workload()
}
pub fn update_rendering_early() -> Workload {
(
resize_renderer,
depth::resize_depth_texture,
).into_sequential_workload()
}
pub fn update_rendering_late() -> Workload {
(
camera_uniform::update_camera_uniform_buffer,
(
selection_box::update_selection_box_render_state,
entities::update_entities_render_state,
smoverlay::update_smoverlay_render_state,
).into_workload().run_if(is_ingame),
).into_workload()
}
pub fn render_master(storages: AllStoragesViewMut) {
let renderer = storages.borrow::<UniqueView<Renderer>>().unwrap();
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());
let display_builder = DisplayBuilder::new()
.with_window_builder(Some(wb));
let config_template_builder = ConfigTemplateBuilder::new()
.with_api(Api::GLES3)
.prefer_hardware_accelerated(Some(true))
.with_depth_size(24)
.with_multisampling(settings.msaa.unwrap_or_default());
let (window, gl_config) = display_builder
.build(event_loop, config_template_builder, |mut configs| {
configs.next().unwrap()
})
.unwrap();
let window = window.expect("no window");
// Now we get the window size to use as the initial size of the Surface
let (width, height): (u32, u32) = window.inner_size().into();
let attrs = SurfaceAttributesBuilder::<WindowSurface>::new().build(
window.raw_window_handle(),
NonZeroU32::new(width).unwrap(),
NonZeroU32::new(height).unwrap(),
);
// Finally we can create a Surface, use it to make a PossiblyCurrentContext and create the glium Display
let surface = unsafe {
gl_config.display().create_window_surface(&gl_config, &attrs).unwrap()
let mut data = RenderCtx {
encoder: &mut encoder,
surface_view: &surface_view,
};
let context_attributes = ContextAttributesBuilder::new()
.with_debug(cfg!(debug_assertions))
.with_context_api(glutin::context::ContextApi::Gles(None))
.with_profile(GlProfile::Core)
.build(Some(window.raw_window_handle()));
storages.run_with_data(background::clear_bg, &mut data);
if storages.run(is_ingame) {
storages.run_with_data(world::draw_world, &mut data);
storages.run_with_data(selection_box::draw_selection_box, &mut data);
storages.run_with_data(entities::render_entities, &mut data);
storages.run_with_data(world::rpass_submit_trans_bundle, &mut data);
storages.run_with_data(smoverlay::render_submerged_view, &mut data);
}
storages.run_with_data(kubi_ui_draw, &mut data);
let current_context = unsafe {
gl_config.display()
.create_context(&gl_config, &context_attributes)
.expect("failed to create context")
}.make_current(&surface).unwrap();
renderer.queue().submit([encoder.finish()]);
surface_texture.present();
}
let display = Display::from_context_surface(current_context, surface).unwrap();
//TODO MIGRATION
// let cb = ContextBuilder::new()
// //.with_srgb(false)
// .with_depth_buffer(24)
// .with_multisampling(settings.msaa.unwrap_or_default())
// .with_vsync(settings.vsync)
// .with_gl_profile(GlProfile::Core)
// .with_gl(GlRequest::Latest);
// let display = Display::new(wb, cb)
// .expect("Failed to create a glium Display");
log::info!("Vendor: {}", display.get_opengl_vendor_string());
log::info!("Renderer: {}", display.get_opengl_renderer_string());
log::info!("OpenGL: {}", display.get_opengl_version_string());
log::info!("Supports GLSL: {:?}", display.get_supported_glsl_version());
log::info!("Framebuffer dimensions: {:?}", display.get_framebuffer_dimensions());
if display.is_context_loss_possible() { log::warn!("OpenGL context loss possible") }
if display.is_robust() { log::warn!("OpenGL implementation is not robust") }
if display.is_debug() { log::info!("OpenGL context is in debug mode") }
assert!(display.is_glsl_version_supported(&Version(GlApiTy::GlEs, 3, 0)), "GLSL ES 3.0 is not supported");
Self { window, display }
/// Resize the renderer when the window is resized
pub fn resize_renderer(
mut renderer: UniqueViewMut<Renderer>,
resize: View<WindowResizedEvent>,
) {
if let Some(size) = resize.iter().last() {
renderer.resize(PhysicalSize::new(size.0.x, size.0.y));
}
}
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.);
}
//not sure if this belongs here
pub fn init_window_size(
storages: AllStoragesView,
) {
let size = storages.borrow::<View<WindowResizedEvent>>().unwrap().iter().next().unwrap().0;
storages.add_unique(WindowSize(size))
}
pub fn update_window_size(
mut win_size: UniqueViewMut<WindowSize>,
resize: View<WindowResizedEvent>,
) {
if let Some(resize) = resize.iter().next() {
win_size.0 = resize.0;
}
}
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,25 @@
use shipyard::UniqueView;
use super::{BackgroundColor, RenderCtx};
pub fn clear_bg(
ctx: &mut RenderCtx,
bg: UniqueView<BackgroundColor>,
) {
let _rpass = ctx.encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("clear_bg"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: ctx.surface_view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color {
r: bg.0.x as f64,
g: bg.0.y as f64,
b: bg.0.z as f64,
a: 1.0,
}),
store: wgpu::StoreOp::Store,
},
})],
..Default::default()
});
}

View file

@ -0,0 +1,81 @@
use bytemuck::{Pod, Zeroable};
use shipyard::{AllStoragesView, IntoIter, Unique, UniqueView, View};
use wgpu::util::DeviceExt;
use crate::camera::Camera;
use super::Renderer;
#[derive(Debug, Clone, Copy, Default, Pod, Zeroable)]
#[repr(C, packed)]
pub struct CameraUniformData {
pub view_proj: [f32; 4 * 4],
}
//TODO if multiple cameras, buffer per camera
#[derive(Unique)]
pub struct CameraUniformBuffer {
pub camera_uniform_buffer: wgpu::Buffer,
pub camera_bind_group_layout: wgpu::BindGroupLayout,
pub camera_bind_group: wgpu::BindGroup,
}
impl CameraUniformBuffer {
pub fn init(renderer: &Renderer, data: CameraUniformData) -> Self {
let camera_uniform_buffer = renderer.device().create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("camera_uniform_buffer"),
contents: bytemuck::cast_slice(&[data]),
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
});
let camera_bind_group_layout = renderer.device().create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("camera_bind_group_layout"),
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::VERTEX,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
],
});
let camera_bind_group = renderer.device().create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("camera_bind_group"),
layout: &camera_bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: camera_uniform_buffer.as_entire_binding(),
},
],
});
Self { camera_uniform_buffer, camera_bind_group_layout, camera_bind_group }
}
pub fn init_default(renderer: &Renderer) -> Self {
Self::init(renderer, CameraUniformData::default())
}
pub fn update(&self, renderer: &Renderer, data: CameraUniformData) {
renderer.queue().write_buffer(&self.camera_uniform_buffer, 0, bytemuck::cast_slice(&[data]));
}
}
pub fn init_camera_uniform_buffer(storages: AllStoragesView) {
let renderer = storages.borrow::<UniqueView<Renderer>>().unwrap();
storages.add_unique(CameraUniformBuffer::init_default(&renderer));
}
pub fn update_camera_uniform_buffer(
renderer: UniqueView<Renderer>,
camera_uniform_buffer: UniqueView<CameraUniformBuffer>,
camera: View<Camera>,
) {
let Some(camera) = camera.iter().next() else { return };
let proj = camera.perspective_matrix * camera.view_matrix;
camera_uniform_buffer.update(&renderer, CameraUniformData { view_proj: proj.to_cols_array() });
}

View file

@ -0,0 +1,72 @@
use glam::{uvec2, UVec2};
use shipyard::{AllStoragesView, Unique, UniqueView, UniqueViewMut};
use super::Renderer;
#[derive(Unique)]
pub struct DepthTexture {
pub depth_texture: wgpu::Texture,
pub depth_view: wgpu::TextureView,
pub depth_sampler: wgpu::Sampler,
}
impl DepthTexture {
fn desc(size: UVec2) -> wgpu::TextureDescriptor<'static> {
wgpu::TextureDescriptor {
label: Some("depth_texture"),
size: wgpu::Extent3d {
width: size.x,
height: size.y,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Depth32Float,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
view_formats: &[wgpu::TextureFormat::Depth32Float],
}
}
pub fn init(renderer: &Renderer) -> Self {
let size = uvec2(renderer.size().width, renderer.size().height);
let depth_texture_desc = Self::desc(size);
let depth_texture = renderer.device().create_texture(&depth_texture_desc);
let depth_view = depth_texture.create_view(&wgpu::TextureViewDescriptor::default());
let depth_sampler = renderer.device().create_sampler(&wgpu::SamplerDescriptor {
label: Some("depth_sampler"),
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,
compare: Some(wgpu::CompareFunction::LessEqual),
..Default::default()
});
Self { depth_texture, depth_view, depth_sampler }
}
pub fn resize(&mut self, renderer: &Renderer) {
let old_size = uvec2(self.depth_texture.size().width, self.depth_texture.size().height);
let new_size = uvec2(renderer.size().width, renderer.size().height);
if old_size == new_size { return }
let depth_texture_desc = Self::desc(new_size);
self.depth_texture = renderer.device().create_texture(&depth_texture_desc);
self.depth_view = self.depth_texture.create_view(&wgpu::TextureViewDescriptor::default());
}
}
pub fn init_depth_texture(
storages: AllStoragesView,
) {
let renderer = storages.borrow::<UniqueView<Renderer>>().unwrap();
storages.add_unique(DepthTexture::init(&renderer));
}
pub fn resize_depth_texture(
mut depth_texture: UniqueViewMut<DepthTexture>,
renderer: UniqueView<Renderer>,
) {
depth_texture.resize(&renderer);
}

View file

@ -1,58 +1,67 @@
use shipyard::{NonSendSync, UniqueViewMut, UniqueView, View, IntoIter, IntoWithId};
use glium::{DepthTest, Depth, PolygonMode, BackfaceCullingMode, DrawParameters, Surface, uniform};
use shipyard::{AllStoragesView, IntoIter, IntoWithId, Unique, UniqueView, View};
use kubi_shared::{entity::Entity, transform::Transform};
use crate::{
prefabs::ColoredShaderPrefab,
camera::Camera,
settings::GameSettings
};
use super::{
RenderTarget,
primitives::cube::CenteredCubePrimitive
camera::Camera, prefabs::GpuPrefabs, settings::GameSettings
};
use super::{camera_uniform::CameraUniformBuffer, depth::DepthTexture, RenderCtx};
mod instance;
mod pipeline;
#[derive(Unique)]
pub struct EntitiesRenderState {
pub pipeline: wgpu::RenderPipeline,
pub instance_buffer: instance::InstanceBuffer,
}
pub fn init_entities_render_state(storages: AllStoragesView) {
storages.add_unique(EntitiesRenderState {
pipeline: storages.run(pipeline::init_entities_pipeline),
instance_buffer: storages.run(instance::create_instance_buffer),
});
}
pub use instance::update_instance_buffer as update_entities_render_state;
// TODO: entity models
pub fn render_entities(
mut target: NonSendSync<UniqueViewMut<RenderTarget>>,
buffers: NonSendSync<UniqueView<CenteredCubePrimitive>>,
program: NonSendSync<UniqueView<ColoredShaderPrefab>>,
camera: View<Camera>,
settings: UniqueView<GameSettings>,
entities: View<Entity>,
transform: View<Transform>,
ctx: &mut RenderCtx,
state: UniqueView<EntitiesRenderState>,
depth: UniqueView<DepthTexture>,
prefabs: UniqueView<GpuPrefabs>,
camera_ubo: UniqueView<CameraUniformBuffer>,
) {
let (camera_id, camera) = camera.iter().with_id().next().expect("No cameras in the scene");
let draw_parameters = DrawParameters {
depth: Depth {
test: DepthTest::IfLess,
write: true,
..Default::default()
},
multisampling: settings.msaa.is_some(),
polygon_mode: PolygonMode::Fill,
backface_culling: BackfaceCullingMode::CullClockwise,
..Default::default()
};
let view = camera.view_matrix.to_cols_array_2d();
let perspective = camera.perspective_matrix.to_cols_array_2d();
for (entity_id, (_, trans)) in (&entities, &transform).iter().with_id() {
//skip rendering camera holder (as the entity would block the view)
if entity_id == camera_id { continue }
//render entity
target.0.draw(
&buffers.0,
&buffers.1,
&program.0,
&uniform! {
color: [1.0, 1.0, 1.0, 1.0_f32],
model: trans.0.to_cols_array_2d(),
view: view,
perspective: perspective,
},
&draw_parameters
).unwrap();
if state.instance_buffer.count == 0 {
return
}
let mut rpass = ctx.encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("rpass_draw_entities"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: ctx.surface_view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Load,
store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
view: &depth.depth_view,
depth_ops: Some(wgpu::Operations {
load: wgpu::LoadOp::Load,
store: wgpu::StoreOp::Store,
}),
stencil_ops: None,
}),
..Default::default()
});
rpass.set_pipeline(&state.pipeline);
rpass.set_bind_group(0, &prefabs.player_model_diffuse_bind_group, &[]);
rpass.set_bind_group(1, &camera_ubo.camera_bind_group, &[]);
rpass.set_vertex_buffer(0, prefabs.player_model.vertex.slice(..));
rpass.set_vertex_buffer(1, state.instance_buffer.buffer.slice(..));
rpass.set_index_buffer(prefabs.player_model.index.slice(..), wgpu::IndexFormat::Uint32);
rpass.draw_indexed(0..prefabs.player_model.index_len, 0, 0..state.instance_buffer.count);
}

View file

@ -0,0 +1,78 @@
use bytemuck::{Pod, Zeroable};
use kubi_shared::{entity::Entity, transform::Transform};
use renderer::Renderer;
use shipyard::{EntityId, IntoIter, IntoWithId, UniqueView, UniqueViewMut, View};
use crate::{camera::Camera, rendering::renderer};
use super::EntitiesRenderState;
#[derive(Clone, Copy, Pod, Zeroable)]
#[repr(C, packed)]
pub struct InstanceData {
pub mat: [f32; 4 * 4],
}
impl InstanceData {
pub const LAYOUT: wgpu::VertexBufferLayout<'static> = wgpu::VertexBufferLayout {
array_stride: std::mem::size_of::<InstanceData>() as wgpu::BufferAddress,
step_mode: wgpu::VertexStepMode::Instance,
attributes: &wgpu::vertex_attr_array![
3 => Float32x4,
4 => Float32x4,
5 => Float32x4,
6 => Float32x4,
],
};
}
pub struct InstanceBuffer {
pub count: u32,
pub buffer: wgpu::Buffer,
}
pub fn create_instance_buffer(
renderer: UniqueView<Renderer>,
) -> InstanceBuffer {
log::info!("entities: create_instance_buffer");
let buffer = renderer.device().create_buffer(&wgpu::BufferDescriptor {
label: Some("instance_buffer"),
size: 255 * std::mem::size_of::<InstanceData>() as u64,
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
InstanceBuffer { count: 0, buffer }
}
pub fn update_instance_buffer(
renderer: UniqueView<Renderer>,
mut state: UniqueViewMut<EntitiesRenderState>,
entities: View<Entity>,
transforms: View<Transform>,
camera: View<Camera>,
) {
//Get id of the camera entity (this assumes a single camera entity)
let cam_id = (&camera)
.iter().with_id().next()
.map(|(x, _)| x)
.unwrap_or(EntityId::dead());
// Create a list of instance data for all entities except ones that have camera attached
let mut instances = Vec::with_capacity(entities.len() - 1);
for (id, (_, trans)) in (&entities, &transforms).iter().with_id() {
if id == cam_id { continue }
instances.push(InstanceData {
mat: trans.0.to_cols_array(),
});
}
state.instance_buffer.count = instances.len() as u32;
if !instances.is_empty() {
renderer.queue().write_buffer(
&state.instance_buffer.buffer,
0,
bytemuck::cast_slice(&instances)
);
}
}

View file

@ -0,0 +1,66 @@
use shipyard::UniqueView;
use wgpu::include_wgsl;
use crate::{prefabs::{GpuPrefabs, ModelVertex}, rendering::{camera_uniform::CameraUniformBuffer, Renderer}};
use super::instance::InstanceData;
pub fn init_entities_pipeline(
renderer: UniqueView<Renderer>,
prefabs: UniqueView<GpuPrefabs>,
camera_ubo: UniqueView<CameraUniformBuffer>,
) -> wgpu::RenderPipeline {
log::info!("init_entities_pipeline");
let module = renderer.device().create_shader_module(include_wgsl!("../../../shaders/entities.wgsl"));
let pipeline_layout = renderer.device().create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("entities_pipeline_layout"),
bind_group_layouts: &[
&prefabs.player_model_diffuse_bind_group_layout,
&camera_ubo.camera_bind_group_layout,
],
push_constant_ranges: &[],
});
renderer.device().create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("entities_pipeline"),
layout: Some(&pipeline_layout),
vertex: wgpu::VertexState {
module: &module,
compilation_options: wgpu::PipelineCompilationOptions::default(),
entry_point: "vs_main",
buffers: &[
ModelVertex::LAYOUT,
InstanceData::LAYOUT,
],
},
fragment: Some(wgpu::FragmentState {
module: &module,
compilation_options: wgpu::PipelineCompilationOptions::default(),
entry_point: "fs_main",
targets: &[Some(wgpu::ColorTargetState {
format: renderer.surface_config().format,
blend: Some(wgpu::BlendState::REPLACE),
write_mask: wgpu::ColorWrites::COLOR,
})],
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
strip_index_format: None,
front_face: wgpu::FrontFace::Ccw,
cull_mode: None, // Some(wgpu::Face::Back), //XXX: this culls their majestic ears! :(
polygon_mode: wgpu::PolygonMode::Fill,
conservative: false,
unclipped_depth: false,
},
depth_stencil: Some(wgpu::DepthStencilState {
format: wgpu::TextureFormat::Depth32Float,
depth_write_enabled: true,
depth_compare: wgpu::CompareFunction::Less,
bias: wgpu::DepthBiasState::default(),
stencil: wgpu::StencilState::default(),
}),
multisample: wgpu::MultisampleState::default(),
multiview: None,
})
}

View file

@ -1,30 +1,42 @@
use shipyard::{Workload, IntoWorkload};
use glium::implement_vertex;
use bytemuck::{Pod, Zeroable};
use shipyard::{IntoWorkload, Workload};
pub mod cube;
pub mod rect;
pub mod stri;
use cube::init_cube_primitive;
use rect::init_rect_primitive;
use stri::init_stri_primitive;
#[derive(Clone, Copy, Default)]
pub struct PositionOnlyVertex {
pub position: [f32; 3],
}
implement_vertex!(PositionOnlyVertex, position);
#[derive(Clone, Copy, Default)]
pub struct PositionOnlyVertex2d {
pub position: [f32; 2],
}
implement_vertex!(PositionOnlyVertex2d, position);
mod cube;
mod fstri;
pub use cube::CubePrimitive;
pub use fstri::FstriPrimitive;
pub fn init_primitives() -> Workload {
(
init_cube_primitive,
init_rect_primitive,
init_stri_primitive,
cube::init_cube_primitive,
fstri::init_fstri_primitive,
).into_workload()
}
#[derive(Clone, Copy, Default, Pod, Zeroable)]
#[repr(C, packed)]
pub struct PrimitiveVertex {
pub position: [f32; 3],
}
impl PrimitiveVertex {
pub const LAYOUT: wgpu::VertexBufferLayout<'static> = wgpu::VertexBufferLayout {
array_stride: std::mem::size_of::<PrimitiveVertex>() as wgpu::BufferAddress,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: &wgpu::vertex_attr_array![0 => Float32x3],
};
}
#[derive(Clone, Copy, Default, Pod, Zeroable)]
#[repr(C, packed)]
pub struct PrimitiveVertex2 {
pub position: [f32; 2],
}
impl PrimitiveVertex2 {
pub const LAYOUT: wgpu::VertexBufferLayout<'static> = wgpu::VertexBufferLayout {
array_stride: std::mem::size_of::<PrimitiveVertex2>() as wgpu::BufferAddress,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: &wgpu::vertex_attr_array![0 => Float32x2],
};
}

View file

@ -1,85 +1,50 @@
use shipyard::{AllStoragesView, NonSendSync, UniqueView, Unique};
use glium::{VertexBuffer, IndexBuffer, index::PrimitiveType};
use crate::rendering::Renderer;
use super::PositionOnlyVertex;
use shipyard::{AllStoragesView, Unique, UniqueView};
use wgpu::util::DeviceExt;
use crate::rendering::{BufferPair, Renderer};
use super::PrimitiveVertex;
#[derive(Unique)]
pub struct CubePrimitive(pub VertexBuffer<PositionOnlyVertex>, pub IndexBuffer<u16>);
pub struct CubePrimitive(pub BufferPair);
#[derive(Unique)]
pub struct CenteredCubePrimitive(pub VertexBuffer<PositionOnlyVertex>, pub IndexBuffer<u16>);
/// Vertices for a centered cube with a side length of 1
const CUBE_VERTICES: &[PrimitiveVertex] = &[
// front
PrimitiveVertex { position: [-0.5, -0.5, 0.5] },
PrimitiveVertex { position: [ 0.5, -0.5, 0.5] },
PrimitiveVertex { position: [ 0.5, 0.5, 0.5] },
PrimitiveVertex { position: [-0.5, 0.5, 0.5] },
// back
PrimitiveVertex { position: [-0.5, -0.5, -0.5] },
PrimitiveVertex { position: [ 0.5, -0.5, -0.5] },
PrimitiveVertex { position: [ 0.5, 0.5, -0.5] },
PrimitiveVertex { position: [-0.5, 0.5, -0.5] },
];
const CENTERED_CUBE_VERTICES: &[PositionOnlyVertex] = &[
// front
PositionOnlyVertex { position: [-0.5, -0.5, 0.5] },
PositionOnlyVertex { position: [ 0.5, -0.5, 0.5] },
PositionOnlyVertex { position: [ 0.5, 0.5, 0.5] },
PositionOnlyVertex { position: [-0.5, 0.5, 0.5] },
// back
PositionOnlyVertex { position: [-0.5, -0.5, -0.5] },
PositionOnlyVertex { position: [ 0.5, -0.5, -0.5] },
PositionOnlyVertex { position: [ 0.5, 0.5, -0.5] },
PositionOnlyVertex { position: [-0.5, 0.5, -0.5] },
];
const CUBE_VERTICES: &[PositionOnlyVertex] = &[
// front
PositionOnlyVertex { position: [0.0, 0.0, 1.0] },
PositionOnlyVertex { position: [1.0, 0.0, 1.0] },
PositionOnlyVertex { position: [1.0, 1.0, 1.0] },
PositionOnlyVertex { position: [0.0, 1.0, 1.0] },
// back
PositionOnlyVertex { position: [0.0, 0.0, 0.0] },
PositionOnlyVertex { position: [1.0, 0.0, 0.0] },
PositionOnlyVertex { position: [1.0, 1.0, 0.0] },
PositionOnlyVertex { position: [0.0, 1.0, 0.0] },
];
/// Indices for a cube primitive
const CUBE_INDICES: &[u16] = &[
// front
0, 1, 2,
2, 3, 0,
// right
1, 5, 6,
6, 2, 1,
// back
7, 6, 5,
5, 4, 7,
// left
4, 0, 3,
3, 7, 4,
// bottom
4, 5, 1,
1, 0, 4,
// top
3, 2, 6,
6, 7, 3
0, 1, 2, 2, 3, 0, // front
1, 5, 6, 6, 2, 1, // right
7, 6, 5, 5, 4, 7, // back
4, 0, 3, 3, 7, 4, // left
4, 5, 1, 1, 0, 4, // bottom
3, 2, 6, 6, 7, 3, // top
];
pub(super) fn init_cube_primitive(
storages: AllStoragesView,
display: NonSendSync<UniqueView<Renderer>>
) {
{
let vert = VertexBuffer::immutable(
&display.display,
CUBE_VERTICES
).unwrap();
let index = IndexBuffer::immutable(
&display.display,
PrimitiveType::TrianglesList,
CUBE_INDICES
).unwrap();
storages.add_unique_non_send_sync(CubePrimitive(vert, index));
}
{
let vert = VertexBuffer::immutable(
&display.display,
CENTERED_CUBE_VERTICES
).unwrap();
let index = IndexBuffer::immutable(
&display.display,
PrimitiveType::TrianglesList,
CUBE_INDICES
).unwrap();
storages.add_unique_non_send_sync(CenteredCubePrimitive(vert, index));
}
pub fn init_cube_primitive(storages: AllStoragesView) {
log::info!("init_cube_primitive");
let renderer = storages.borrow::<UniqueView<Renderer>>().unwrap();
storages.add_unique(CubePrimitive(BufferPair {
index: renderer.device().create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("cube_index_buffer"),
contents: bytemuck::cast_slice(CUBE_INDICES),
usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::INDEX,
}),
index_len: CUBE_INDICES.len() as u32,
vertex: renderer.device().create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("cube_vertex_buffer"),
contents: bytemuck::cast_slice(CUBE_VERTICES),
usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::VERTEX,
}),
vertex_len: CUBE_VERTICES.len() as u32,
}));
}

View file

@ -0,0 +1,24 @@
use shipyard::{AllStoragesView, Unique, UniqueView};
use wgpu::util::DeviceExt;
use crate::rendering::Renderer;
use super::PrimitiveVertex2;
pub const FSTRI_VERTICES: &[PrimitiveVertex2] = &[
PrimitiveVertex2 { position: [-1.0, -1.0] },
PrimitiveVertex2 { position: [ 3.0, -1.0] },
PrimitiveVertex2 { position: [-1.0, 3.0] },
];
#[derive(Unique)]
pub struct FstriPrimitive(pub wgpu::Buffer);
pub fn init_fstri_primitive(storages: AllStoragesView) {
log::info!("init_fstri_primitive");
let renderer = storages.borrow::<UniqueView<Renderer>>().unwrap();
let buffer = renderer.device().create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("fstri_vertex_buffer"),
contents: bytemuck::cast_slice(FSTRI_VERTICES),
usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::VERTEX,
});
storages.add_unique(FstriPrimitive(buffer));
}

View file

@ -1,31 +0,0 @@
use shipyard::{Unique, AllStoragesView, NonSendSync, UniqueView};
use glium::{VertexBuffer, IndexBuffer, index::PrimitiveType};
use crate::rendering::Renderer;
use super::PositionOnlyVertex2d;
#[derive(Unique)]
pub struct RectPrimitive(pub VertexBuffer<PositionOnlyVertex2d>, pub IndexBuffer<u16>);
const RECT_VERTEX: &[PositionOnlyVertex2d] = &[
PositionOnlyVertex2d { position: [0., 0.] },
PositionOnlyVertex2d { position: [1., 0.] },
PositionOnlyVertex2d { position: [0., 1.] },
PositionOnlyVertex2d { position: [1., 1.] },
];
const RECT_INDEX: &[u16] = &[0, 1, 2, 1, 3, 2];
pub(super) fn init_rect_primitive(
storages: AllStoragesView,
display: NonSendSync<UniqueView<Renderer>>
) {
let vert = VertexBuffer::immutable(
&display.display,
RECT_VERTEX
).unwrap();
let index = IndexBuffer::immutable(
&display.display,
PrimitiveType::TrianglesList,
RECT_INDEX
).unwrap();
storages.add_unique_non_send_sync(RectPrimitive(vert, index));
}

View file

@ -1,30 +0,0 @@
use shipyard::{Unique, AllStoragesView, NonSendSync, UniqueView};
use glium::{VertexBuffer, IndexBuffer, index::PrimitiveType};
use crate::rendering::Renderer;
use super::PositionOnlyVertex2d;
#[derive(Unique)]
pub struct STriPrimitive(pub VertexBuffer<PositionOnlyVertex2d>, pub IndexBuffer<u16>);
const STRI_VERTEX: &[PositionOnlyVertex2d] = &[
PositionOnlyVertex2d { position: [-1., -1.] },
PositionOnlyVertex2d { position: [ 3., -1.] },
PositionOnlyVertex2d { position: [-1., 3.] },
];
const STRI_INDEX: &[u16] = &[0, 1, 2];
pub(super) fn init_stri_primitive(
storages: AllStoragesView,
display: NonSendSync<UniqueView<Renderer>>
) {
let vert = VertexBuffer::immutable(
&display.display,
STRI_VERTEX
).unwrap();
let index = IndexBuffer::immutable(
&display.display,
PrimitiveType::TrianglesList,
STRI_INDEX
).unwrap();
storages.add_unique_non_send_sync(STriPrimitive(vert, index));
}

View file

@ -0,0 +1,177 @@
use std::sync::Arc;
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 {
window: Arc<Window>,
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,
}
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 = Arc::new(event_loop.create_window(window_attributes).unwrap());
let size = window.inner_size();
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends: wgpu::Backends::all(),
//Disable validation layer
flags: wgpu::InstanceFlags::default() & !wgpu::InstanceFlags::VALIDATION,
//we're using vulkan on windows
// #[cfg(all(target_os = "windows", target_arch = "x86_64"))]
// dx12_shader_compiler: wgpu::Dx12Compiler::Dxc {
// dxil_path: Some("./dxil.dll".into()),
// dxc_path: Some("./dxcompiler.dll".into()),
// },
..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 surface = instance.create_surface(Arc::clone(&window)).unwrap();
let adapter = instance.request_adapter(
&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::HighPerformance,
compatible_surface: Some(&surface),
force_fallback_adapter: false,
},
).block_on().unwrap();
log::info!("Adapter: {:?}", adapter.get_info());
log::info!("Features: {:?}", adapter.features());
log::info!("Limits: {:?}", adapter.limits());
let (device, queue) = adapter.request_device(
&wgpu::DeviceDescriptor {
label: None,
required_features: wgpu::Features::empty(),
required_limits: wgpu::Limits::downlevel_webgl2_defaults().using_resolution(adapter.limits()),
},
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 size_uvec2(&self) -> glam::UVec2 {
glam::UVec2::new(self.size.width, self.size.height)
}
pub fn size_vec2(&self) -> glam::Vec2 {
glam::Vec2::new(self.size.width as f32, self.size.height as f32)
}
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,59 +1,65 @@
use glam::{Mat4, Vec3, Quat};
use shipyard::{View, IntoIter, NonSendSync, UniqueViewMut, UniqueView};
use glium::{
Surface,
DrawParameters,
BackfaceCullingMode,
Blend, Depth, DepthTest,
uniform,
};
use crate::{
world::raycast::LookingAtBlock,
camera::Camera,
prefabs::ColoredShaderPrefab
};
use super::{
RenderTarget,
primitives::cube::CubePrimitive,
};
use shipyard::{AllStoragesView, IntoIter, Unique, UniqueView, View};
use wgpu::RenderPassDescriptor;
use crate::{player::MainPlayer, world::raycast::LookingAtBlock};
use super::{camera_uniform::CameraUniformBuffer, depth::DepthTexture, primitives::CubePrimitive, RenderCtx};
const SMOL: f32 = 0.0025;
mod pipeline;
mod uniform;
pub fn render_selection_box(
lookat: View<LookingAtBlock>,
camera: View<Camera>,
mut target: NonSendSync<UniqueViewMut<RenderTarget>>,
program: NonSendSync<UniqueView<ColoredShaderPrefab>>,
buffers: NonSendSync<UniqueView<CubePrimitive>>,
) {
let camera = camera.iter().next().unwrap();
let Some(lookat) = lookat.iter().next() else { return };
let Some(lookat) = lookat.0 else { return };
use uniform::SelectionBoxUniform;
//Darken block
target.0.draw(
&buffers.0,
&buffers.1,
&program.0,
&uniform! {
color: [0., 0., 0., 0.5_f32],
model: Mat4::from_scale_rotation_translation(
Vec3::splat(1. + SMOL * 2.),
Quat::default(),
lookat.block_position.as_vec3() - Vec3::splat(SMOL)
).to_cols_array_2d(),
perspective: camera.perspective_matrix.to_cols_array_2d(),
view: camera.view_matrix.to_cols_array_2d(),
},
&DrawParameters {
backface_culling: BackfaceCullingMode::CullClockwise,
blend: Blend::alpha_blending(),
depth: Depth {
//this may be unreliable... unless scale is applied! hacky...
test: DepthTest::IfLessOrEqual,
..Default::default()
},
..Default::default()
}
).unwrap();
#[derive(Unique)]
pub struct SboxRenderState {
pipeline: wgpu::RenderPipeline,
uniform: SelectionBoxUniform,
}
pub fn init_selection_box_render_state(storages: AllStoragesView) {
let uniform = storages.run(uniform::init_selection_box_uniform);
let pipeline = storages.run_with_data(pipeline::init_selection_box_pipeline, &uniform);
storages.add_unique(SboxRenderState { pipeline, uniform });
}
pub use uniform::update_selection_box_uniform
as update_selection_box_render_state;
pub fn draw_selection_box(
ctx: &mut RenderCtx,
state: UniqueView<SboxRenderState>,
depth: UniqueView<DepthTexture>,
cube: UniqueView<CubePrimitive>,
camera_ubo: UniqueView<CameraUniformBuffer>,
lookat: View<LookingAtBlock>,
player: View<MainPlayer>,
) {
let Some((LookingAtBlock(Some(_)), _)) = (&lookat, &player).iter().next() else {
return
};
let mut rpass = ctx.encoder.begin_render_pass(&RenderPassDescriptor {
label: Some("rpass_selection_box"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: ctx.surface_view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Load,
store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
view: &depth.depth_view,
depth_ops: Some(wgpu::Operations {
load: wgpu::LoadOp::Load,
store: wgpu::StoreOp::Store,
}),
stencil_ops: None,
}),
..Default::default()
});
rpass.set_pipeline(&state.pipeline);
rpass.set_bind_group(0, &camera_ubo.camera_bind_group, &[]);
rpass.set_bind_group(1, &state.uniform.bind_group, &[]);
rpass.set_index_buffer(cube.0.index.slice(..), wgpu::IndexFormat::Uint16);
rpass.set_vertex_buffer(0, cube.0.vertex.slice(..));
rpass.draw_indexed(0..cube.0.index_len, 0, 0..1);
}

View file

@ -0,0 +1,70 @@
use shipyard::UniqueView;
use crate::rendering::{
camera_uniform::CameraUniformBuffer,
depth::DepthTexture,
primitives::PrimitiveVertex,
Renderer
};
pub fn init_selection_box_pipeline(
uniform: &super::uniform::SelectionBoxUniform,
ren: UniqueView<Renderer>,
depth: UniqueView<DepthTexture>,
camera_ubo: UniqueView<CameraUniformBuffer>,
) -> wgpu::RenderPipeline {
log::info!("init_selection_box_pipeline");
let shader = ren.device().create_shader_module(
wgpu::include_wgsl!("../../../shaders/selection_box.wgsl")
);
let selection_box_pipeline_layout = ren.device().create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("selection_box_pipeline_layout"),
bind_group_layouts: &[
&camera_ubo.camera_bind_group_layout,
&uniform.bind_group_layout,
],
push_constant_ranges: &[],
});
ren.device().create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("selection_box_pipeline"),
layout: Some(&selection_box_pipeline_layout),
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: "fs_main",
compilation_options: wgpu::PipelineCompilationOptions::default(),
targets: &[Some(wgpu::ColorTargetState {
format: ren.surface_config().format,
blend: Some(wgpu::BlendState::ALPHA_BLENDING),
write_mask: wgpu::ColorWrites::COLOR,
})],
}),
vertex: wgpu::VertexState {
module: &shader,
entry_point: "vs_main",
compilation_options: wgpu::PipelineCompilationOptions::default(),
buffers: &[
PrimitiveVertex::LAYOUT,
],
},
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
strip_index_format: None,
cull_mode: Some(wgpu::Face::Back),
front_face: wgpu::FrontFace::Ccw,
unclipped_depth: false,
polygon_mode: wgpu::PolygonMode::Fill,
conservative: false,
},
depth_stencil: Some(wgpu::DepthStencilState {
format: depth.depth_texture.format(),
depth_write_enabled: false,
depth_compare: wgpu::CompareFunction::LessEqual,
stencil: wgpu::StencilState::default(),
bias: wgpu::DepthBiasState::default(),
}),
multisample: wgpu::MultisampleState::default(),
multiview: None,
})
}

View file

@ -0,0 +1,83 @@
use glam::Vec3;
use shipyard::{IntoIter, UniqueView, View};
use bytemuck::{Pod, Zeroable};
use crate::{
player::MainPlayer,
rendering::Renderer,
world::raycast::LookingAtBlock,
};
use super::SboxRenderState;
#[derive(Clone, Copy, Pod, Zeroable)]
#[repr(C, packed)]
pub struct SelectionBoxUniformData {
pub position: [f32; 3],
pub _padding: u32,
}
pub struct SelectionBoxUniform {
pub buffer: wgpu::Buffer,
pub bind_group_layout: wgpu::BindGroupLayout,
pub bind_group: wgpu::BindGroup,
}
pub fn init_selection_box_uniform(
renderer: UniqueView<Renderer>
) -> SelectionBoxUniform {
log::info!("init_selection_box_uniform");
let buffer = renderer.device().create_buffer(&wgpu::BufferDescriptor {
label: Some("selection_box_uniform"),
size: std::mem::size_of::<SelectionBoxUniformData>() as u64,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let bind_group_layout = renderer.device().create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("selection_box_bind_group_layout"),
entries: &[wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::VERTEX,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
}],
});
let bind_group = renderer.device().create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("selection_box_bind_group"),
layout: &bind_group_layout,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: buffer.as_entire_binding(),
}],
});
SelectionBoxUniform {
buffer,
bind_group_layout,
bind_group,
}
}
pub fn update_selection_box_uniform(
renderer: UniqueView<Renderer>,
state: UniqueView<SboxRenderState>,
lookat: View<LookingAtBlock>,
player: View<MainPlayer>,
) {
//TODO: only update if changed
if let Some((LookingAtBlock(Some(lookat)), _)) = (&lookat, &player).iter().next() {
renderer.queue().write_buffer(
&state.uniform.buffer,
0,
bytemuck::cast_slice(&[SelectionBoxUniformData {
position: (lookat.position.floor() + Vec3::splat(0.5)).to_array(),
_padding: 0,
}]),
);
};
}

View file

@ -0,0 +1,48 @@
use shipyard::{AllStoragesView, Unique, UniqueView};
use super::{primitives::FstriPrimitive, RenderCtx, Renderer};
mod uniform;
mod pipeline;
use uniform::SmUniform;
#[derive(Unique)]
pub struct SmOverlayRenderState {
pub uniform: SmUniform,
pub pipeline: wgpu::RenderPipeline,
}
pub fn init_smoverlay_render_state(storages: AllStoragesView) {
let uniform = storages.run(uniform::init_sm_uniform);
let pipeline = storages.run_with_data(pipeline::init_smoverlay_pipeline, &uniform);
storages.add_unique(SmOverlayRenderState { uniform, pipeline });
}
pub use uniform::update_sm_uniform as update_smoverlay_render_state;
pub fn render_submerged_view(
ctx: &mut RenderCtx,
state: UniqueView<SmOverlayRenderState>,
buf: UniqueView<FstriPrimitive>,
) {
if state.uniform.stored_data.is_none() { return }
let mut rpass = ctx.encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("smoverlay_render_pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: ctx.surface_view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Load,
store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
});
rpass.set_pipeline(&state.pipeline);
rpass.set_bind_group(0, &state.uniform.bind_group, &[]);
rpass.set_vertex_buffer(0, buf.0.slice(..));
rpass.draw(0..3, 0..1);
}

View file

@ -0,0 +1,57 @@
use shipyard::UniqueView;
use crate::rendering::{primitives::PrimitiveVertex2, Renderer};
use super::uniform::SmUniform;
pub fn init_smoverlay_pipeline(
uniform: &SmUniform,
renderer: UniqueView<Renderer>
) -> wgpu::RenderPipeline {
let module = renderer.device().create_shader_module(
wgpu::include_wgsl!("../../../shaders/c2d.wgsl")
);
let rp_layout = renderer.device().create_pipeline_layout(
&wgpu::PipelineLayoutDescriptor {
label: Some("smoverlay_pipeline_layout"),
bind_group_layouts: &[
&uniform.bind_group_layout,
],
push_constant_ranges: &[],
}
);
renderer.device().create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("smoverlay_pipeline"),
layout: Some(&rp_layout),
vertex: wgpu::VertexState {
module: &module,
compilation_options: wgpu::PipelineCompilationOptions::default(),
entry_point: "vs_main",
buffers: &[
PrimitiveVertex2::LAYOUT,
],
},
fragment: Some(wgpu::FragmentState {
module: &module,
compilation_options: wgpu::PipelineCompilationOptions::default(),
entry_point: "fs_main",
targets: &[Some(wgpu::ColorTargetState {
format: renderer.surface_config().format,
blend: Some(wgpu::BlendState::ALPHA_BLENDING),
write_mask: wgpu::ColorWrites::ALL,
})],
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
strip_index_format: None,
front_face: wgpu::FrontFace::Ccw,
cull_mode: Some(wgpu::Face::Back),
polygon_mode: wgpu::PolygonMode::Fill,
conservative: false,
unclipped_depth: false,
},
depth_stencil: None,
multisample: wgpu::MultisampleState::default(),
multiview: None,
})
}

View file

@ -0,0 +1,94 @@
use bytemuck::{Pod, Zeroable};
use kubi_shared::transform::Transform;
use shipyard::{IntoIter, UniqueView, UniqueViewMut, View};
use crate::{player::MainPlayer, rendering::Renderer, world::ChunkStorage};
use super::SmOverlayRenderState;
#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
#[repr(C, packed)]
pub struct SmUniformData {
pub color: [f32; 4],
}
pub struct SmUniform {
pub stored_data: Option<SmUniformData>,
pub buffer: wgpu::Buffer,
pub bind_group_layout: wgpu::BindGroupLayout,
pub bind_group: wgpu::BindGroup,
}
pub fn init_sm_uniform(
renderer: UniqueView<Renderer>
) -> SmUniform {
log::info!("init_sm_uniform");
let buffer = renderer.device().create_buffer(&wgpu::BufferDescriptor {
label: Some("smoverlay_uniform"),
size: std::mem::size_of::<SmUniformData>() as u64,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let bind_group_layout = renderer.device().create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("smoverlay_bind_group_layout"),
entries: &[wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
}],
});
let bind_group = renderer.device().create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("smoverlay_bind_group"),
layout: &bind_group_layout,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: buffer.as_entire_binding(),
}],
});
SmUniform {
stored_data: None,
buffer,
bind_group_layout,
bind_group,
}
}
pub fn update_sm_uniform(
mut state: UniqueViewMut<SmOverlayRenderState>,
renderer: UniqueView<Renderer>,
plr: View<MainPlayer>,
trans: View<Transform>,
world: UniqueView<ChunkStorage>,
) {
state.uniform.stored_data = None;
let (_, plr_trans) = (&plr, &trans).iter().next().expect("Main player MIA");
let plr_pos = plr_trans.0.to_scale_rotation_translation().2;
let block_at_pos = world.get_block(plr_pos.floor().as_ivec3());
let Some(block_at_pos) = block_at_pos else { return };
let Some(color) = block_at_pos.descriptor().submerge else { return };
let new_data = SmUniformData {
color: color.to_array()
};
if state.uniform.stored_data == Some(new_data) {
return
}
state.uniform.stored_data = Some(new_data);
log::debug!("update_sm_uniform: {:?}", new_data);
renderer.queue().write_buffer(
&state.uniform.buffer,
0,
bytemuck::cast_slice(&[new_data]),
);
}

View file

@ -1,39 +0,0 @@
use glium::{uniform, Blend, DrawParameters, Surface};
use kubi_shared::transform::Transform;
use shipyard::{IntoIter, NonSendSync, UniqueView, UniqueViewMut, View};
use crate::{
player::MainPlayer,
prefabs::Colored2ShaderPrefab,
rendering::primitives::stri::STriPrimitive,
world::ChunkStorage,
};
use super::RenderTarget;
pub fn render_submerged_view(
mut target: NonSendSync<UniqueViewMut<RenderTarget>>,
primitive: NonSendSync<UniqueView<STriPrimitive>>,
program: NonSendSync<UniqueView<Colored2ShaderPrefab>>,
plr: View<MainPlayer>,
trans: View<Transform>,
world: UniqueView<ChunkStorage>,
) {
let (_, plr_trans) = (&plr, &trans).iter().next().expect("Main player MIA");
let plr_pos = plr_trans.0.to_scale_rotation_translation().2;
let block_at_pos = world.get_block(plr_pos.floor().as_ivec3());
let Some(block_at_pos) = block_at_pos else { return };
let Some(color) = block_at_pos.descriptor().submerge else { return };
let draw_parameters = DrawParameters {
blend: Blend::alpha_blending(),
..Default::default()
};
target.0.draw(
&primitive.0,
&primitive.1,
&program.0,
&uniform! {
color: color.to_array(),
},
&draw_parameters,
).unwrap();
}

View file

@ -1,248 +1,161 @@
use glam::{ivec3, IVec3, Mat4, Quat, Vec3};
use shipyard::{track, AllStoragesView, IntoIter, NonSendSync, Unique, UniqueView, UniqueViewMut, View};
use glium::{
draw_parameters::{
BackfaceCullingMode, Depth, DepthTest, PolygonMode
}, implement_vertex, uniform, uniforms::{
MagnifySamplerFilter, MinifySamplerFilter, Sampler, SamplerBehavior, SamplerWrapFunction
}, Blend, DrawParameters, Smooth, Surface
};
use glam::Vec3;
use shipyard::{AllStoragesView, IntoIter, NonSendSync, Unique, UniqueView, UniqueViewMut, View};
use kubi_shared::chunk::CHUNK_SIZE;
use crate::{
camera::Camera,
player::MainPlayer,
transform::Transform,
prefabs::{
ChunkShaderPrefab,
BlockTexturesPrefab,
ColoredShaderPrefab,
},
world::{
ChunkStorage,
ChunkMeshStorage,
chunk::CHUNK_SIZE,
}, settings::GameSettings,
prefabs::GpuPrefabs,
world::{ChunkMeshStorage, ChunkStorage},
};
use super::{RenderTarget, primitives::cube::CubePrimitive};
use super::{camera_uniform::CameraUniformBuffer, depth::DepthTexture, RenderCtx, Renderer};
#[derive(Clone, Copy)]
#[repr(C)]
pub struct ChunkVertex {
pub position: [f32; 3],
pub normal: [f32; 3],
pub uv: [f32; 2],
pub tex_index: u8,
}
implement_vertex!(ChunkVertex, position, normal, uv, tex_index);
mod pipeline;
mod vertex;
pub use vertex::ChunkVertex;
#[derive(Unique)]
pub struct TransChunkQueue(pub Vec<IVec3>);
pub fn init_trans_chunk_queue(storages: AllStoragesView) {
storages.add_unique(TransChunkQueue(Vec::with_capacity(512)));
pub struct WorldRenderState {
pub pipeline: wgpu::RenderPipeline,
pub pipeline_trans: wgpu::RenderPipeline,
pub trans_bundle: Option<wgpu::RenderBundle>,
}
fn draw_params(settings: &GameSettings) -> DrawParameters {
DrawParameters {
depth: Depth {
test: DepthTest::IfLess,
write: true,
..Default::default()
},
multisampling: settings.msaa.is_some(),
polygon_mode: PolygonMode::Fill, //Change to Line for wireframe
backface_culling: BackfaceCullingMode::CullClockwise,
..Default::default()
}
}
fn texture_sampler<'a, T>(texture: &'a T, settings: &GameSettings) -> Sampler<'a, T> {
Sampler(texture, SamplerBehavior {
minify_filter: MinifySamplerFilter::LinearMipmapLinear,
magnify_filter: MagnifySamplerFilter::Nearest,
max_anisotropy: settings.max_anisotropy.unwrap_or_default(),
wrap_function: (SamplerWrapFunction::Clamp, SamplerWrapFunction::Clamp, SamplerWrapFunction::Clamp),
depth_texture_comparison: None,
pub fn init_world_render_state(storages: AllStoragesView) {
let (pipeline, pipeline_trans) = storages.run(pipeline::init_world_pipeline);
storages.add_unique(WorldRenderState {
pipeline, pipeline_trans,
trans_bundle: None,
})
}
pub fn draw_world(
mut target: NonSendSync<UniqueViewMut<RenderTarget>>,
ctx: &mut RenderCtx,
mut state: UniqueViewMut<WorldRenderState>,
renderer: UniqueView<Renderer>,
camera_ubo: UniqueView<CameraUniformBuffer>,
depth: UniqueView<DepthTexture>,
textures: UniqueView<GpuPrefabs>,
camera: View<Camera>,
chunks: UniqueView<ChunkStorage>,
meshes: NonSendSync<UniqueView<ChunkMeshStorage>>,
program: NonSendSync<UniqueView<ChunkShaderPrefab>>,
texture: NonSendSync<UniqueView<BlockTexturesPrefab>>,
transform: View<Transform>,
camera: View<Camera>,
settings: UniqueView<GameSettings>,
mut trans_queue: UniqueViewMut<TransChunkQueue>,
//settings: UniqueView<GameSettings>,
) {
// let (camera, transform) = (&camera, &transform).iter().next().expect("No cameras in the scene");
// let camera_position = transform.0.to_scale_rotation_translation().2;
let camera = camera.iter().next().expect("No cameras in the scene");
let view = camera.view_matrix.to_cols_array_2d();
let perspective = camera.perspective_matrix.to_cols_array_2d();
let draw_parameters = draw_params(&settings);
let texture_sampler = texture_sampler(&texture.0, &settings);
let mut render_pass = ctx.encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("rpass_draw_world"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: ctx.surface_view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Load,
store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
view: &depth.depth_view,
depth_ops: Some(wgpu::Operations {
load: wgpu::LoadOp::Clear(1.0),
store: wgpu::StoreOp::Store,
}),
stencil_ops: None,
}),
..Default::default()
});
render_pass.set_pipeline(&state.pipeline);
render_pass.set_bind_group(0, &textures.block_diffuse_bind_group, &[]);
render_pass.set_bind_group(1, &camera_ubo.camera_bind_group, &[]);
let mut trans_bundle_used = false;
let mut trans_bundle = renderer.device().create_render_bundle_encoder(&wgpu::RenderBundleEncoderDescriptor {
label: Some("trans_bundle_encoder"),
color_formats: &[Some(renderer.surface_config().format)],
depth_stencil: Some(wgpu::RenderBundleDepthStencil {
format: depth.depth_texture.format(),
depth_read_only: true,
stencil_read_only: true,
}),
sample_count: 1,
multiview: None,
});
trans_bundle.set_pipeline(&state.pipeline_trans);
trans_bundle.set_bind_group(0, &textures.block_diffuse_bind_group, &[]);
trans_bundle.set_bind_group(1, &camera_ubo.camera_bind_group, &[]);
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 mesh if its empty
if mesh.index_buffer.len() == 0 && mesh.trans_index_buffer.len() == 0 {
//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.index_buffer.len() > 0 {
target.0.draw(
&mesh.vertex_buffer,
&mesh.index_buffer,
&program.0,
&uniform! {
position_offset: world_position.to_array(),
view: view,
perspective: perspective,
tex: texture_sampler,
discard_alpha: true,
},
&draw_parameters
).unwrap();
if mesh.main.index_len > 0 {
render_pass.set_index_buffer(mesh.main.index.slice(..), wgpu::IndexFormat::Uint32);
render_pass.set_vertex_buffer(0, mesh.main.vertex.slice(..));
render_pass.draw_indexed(0..mesh.main.index_len, 0, 0..1);
}
if mesh.trans_index_buffer.len() > 0 {
trans_queue.0.push(position);
//Draw transparent chunk mesh
if mesh.trans.index_len > 0 {
trans_bundle_used = true;
trans_bundle.set_index_buffer(mesh.trans.index.slice(..), wgpu::IndexFormat::Uint32);
trans_bundle.set_vertex_buffer(0, mesh.trans.vertex.slice(..));
trans_bundle.draw_indexed(0..mesh.trans.index_len, 0, 0..1);
}
}
}
// const HALF_CHUNK_SIZE: IVec3 = IVec3::splat((CHUNK_SIZE >> 1) as i32);
// trans_queue.0.sort_by_cached_key(|&pos| -(
// (pos + HALF_CHUNK_SIZE).distance_squared(camera_position.as_ivec3())
// ));
drop(render_pass);
if trans_bundle_used {
let bundle = trans_bundle.finish(&wgpu::RenderBundleDescriptor {
label: Some("trans_bundle"),
});
state.trans_bundle = Some(bundle);
} else {
state.trans_bundle = None;
}
}
pub fn draw_world_trans(
mut target: NonSendSync<UniqueViewMut<RenderTarget>>,
chunks: UniqueView<ChunkStorage>,
meshes: NonSendSync<UniqueView<ChunkMeshStorage>>,
program: NonSendSync<UniqueView<ChunkShaderPrefab>>,
texture: NonSendSync<UniqueView<BlockTexturesPrefab>>,
camera: View<Camera>,
settings: UniqueView<GameSettings>,
mut trans_queue: UniqueViewMut<TransChunkQueue>,
pub fn rpass_submit_trans_bundle(
ctx: &mut RenderCtx,
state: UniqueView<WorldRenderState>,
depth: UniqueView<DepthTexture>,
) {
let camera = camera.iter().next().expect("No cameras in the scene");
let view = camera.view_matrix.to_cols_array_2d();
let perspective = camera.perspective_matrix.to_cols_array_2d();
let mut draw_parameters = draw_params(&settings);
draw_parameters.blend = Blend::alpha_blending();
draw_parameters.backface_culling = BackfaceCullingMode::CullingDisabled;
draw_parameters.smooth = Some(Smooth::Fastest);
let texture_sampler = texture_sampler(&texture.0, &settings);
for position in trans_queue.0.drain(..).rev() {
let world_position = position.as_vec3() * CHUNK_SIZE as f32;
let mesh_idx = chunks.chunks[&position].mesh_index.expect("No mesh index");
let mesh = meshes.get(mesh_idx).expect("Mesh index pointing to nothing");
target.0.draw(
&mesh.trans_vertex_buffer,
&mesh.trans_index_buffer,
&program.0,
&uniform! {
position_offset: world_position.to_array(),
view: view,
perspective: perspective,
tex: texture_sampler,
discard_alpha: false,
},
&draw_parameters
).unwrap();
}
}
pub fn draw_current_chunk_border(
mut target: NonSendSync<UniqueViewMut<RenderTarget>>,
player: View<MainPlayer>,
transforms: View<Transform, track::All>,
buffers: NonSendSync<UniqueView<CubePrimitive>>,
program: NonSendSync<UniqueView<ColoredShaderPrefab>>,
camera: View<Camera>,
settings: UniqueView<GameSettings>,
) {
if cfg!(target_os = "android") {
let Some(bundle) = state.trans_bundle.as_ref() else {
return
}
if !settings.debug_draw_current_chunk_border {
return
}
let camera = camera.iter().next().expect("No cameras in the scene");
let view = camera.view_matrix.to_cols_array_2d();
let perspective = camera.perspective_matrix.to_cols_array_2d();
let (_, &player_transform) = (&player, &transforms).iter().next().expect("No player");
let (_, _, player_position) = player_transform.0.to_scale_rotation_translation();
let player_in_chunk = ivec3(
(player_position.x as i32).div_euclid(CHUNK_SIZE as i32),
(player_position.y as i32).div_euclid(CHUNK_SIZE as i32),
(player_position.z as i32).div_euclid(CHUNK_SIZE as i32),
);
let world_position = player_in_chunk.as_vec3() * CHUNK_SIZE as f32;
target.0.draw(
&buffers.0,
&buffers.1,
&program.0,
&uniform! {
model: Mat4::from_scale_rotation_translation(
Vec3::splat(CHUNK_SIZE as f32),
Quat::default(),
world_position
).to_cols_array_2d(),
color: [0.25f32; 4],
view: view,
perspective: perspective,
};
let mut rpass = ctx.encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("rpass_submit_trans_bundle"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: ctx.surface_view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Load,
store: wgpu::StoreOp::Store,
},
&DrawParameters {
depth: Depth {
test: DepthTest::IfLess,
})],
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
view: &depth.depth_view,
depth_ops: Some(wgpu::Operations {
load: wgpu::LoadOp::Load,
store: wgpu::StoreOp::Store,
}),
stencil_ops: None,
}),
..Default::default()
},
blend: Blend::alpha_blending(),
..Default::default()
}
).unwrap();
target.0.draw(
&buffers.0,
&buffers.1,
&program.0,
&uniform! {
model: Mat4::from_scale_rotation_translation(
Vec3::splat(CHUNK_SIZE as f32),
Quat::default(),
world_position
).to_cols_array_2d(),
color: [0.0f32; 4],
view: view,
perspective: perspective,
},
&DrawParameters {
polygon_mode: PolygonMode::Point,
line_width: Some(2.),
point_size: Some(5.),
..Default::default()
}
).unwrap();
});
rpass.execute_bundles(Some(bundle));
}

View file

@ -0,0 +1,117 @@
use shipyard::UniqueView;
use crate::{
prefabs::GpuPrefabs,
rendering::{camera_uniform::CameraUniformBuffer, depth::DepthTexture, world::ChunkVertex, Renderer}
};
pub fn init_world_pipeline(
ren: UniqueView<Renderer>,
depth: UniqueView<DepthTexture>,
textures: UniqueView<GpuPrefabs>,
camera_ubo: UniqueView<CameraUniformBuffer>,
) -> (wgpu::RenderPipeline, wgpu::RenderPipeline) {
log::info!("init_world_pipeline: creating shader module");
let shader = ren.device().create_shader_module(
wgpu::include_wgsl!("../../../shaders/world.wgsl")
);
log::info!("init_world_pipeline: creating pipeline layout");
let world_pipeline_layout = ren.device().create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("world_pipeline_layout"),
bind_group_layouts: &[
&textures.block_diffuse_bind_group_layout,
&camera_ubo.camera_bind_group_layout,
],
push_constant_ranges: &[],
});
log::info!("init_world_pipeline: create main pipeline");
let pipeline_main = ren.device().create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("world_pipeline"),
layout: Some(&world_pipeline_layout),
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: "fs_main",
compilation_options: wgpu::PipelineCompilationOptions::default(),
targets: &[Some(wgpu::ColorTargetState {
format: ren.surface_config().format,
blend: Some(wgpu::BlendState::REPLACE),
write_mask: wgpu::ColorWrites::COLOR,
})],
}),
vertex: wgpu::VertexState {
module: &shader,
entry_point: "vs_main",
compilation_options: wgpu::PipelineCompilationOptions::default(),
buffers: &[
ChunkVertex::LAYOUT,
],
},
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
strip_index_format: None,
cull_mode: Some(wgpu::Face::Back),
front_face: wgpu::FrontFace::Ccw,
unclipped_depth: false,
polygon_mode: wgpu::PolygonMode::Fill,
conservative: false,
},
depth_stencil: Some(wgpu::DepthStencilState {
format: depth.depth_texture.format(),
depth_write_enabled: true,
depth_compare: wgpu::CompareFunction::Less,
stencil: wgpu::StencilState::default(),
bias: wgpu::DepthBiasState::default(),
}),
multisample: wgpu::MultisampleState::default(),
multiview: None,
});
log::info!("init_world_pipeline: create trans pipeline");
let pipeline_trans = ren.device().create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("world_pipeline_trans"),
layout: Some(&world_pipeline_layout),
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: "fs_main_trans",
compilation_options: wgpu::PipelineCompilationOptions::default(),
targets: &[Some(wgpu::ColorTargetState {
format: ren.surface_config().format,
blend: Some(wgpu::BlendState::ALPHA_BLENDING),
write_mask: wgpu::ColorWrites::COLOR,
})],
}),
vertex: wgpu::VertexState {
module: &shader,
entry_point: "vs_main",
compilation_options: wgpu::PipelineCompilationOptions::default(),
buffers: &[
ChunkVertex::LAYOUT,
],
},
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
strip_index_format: None,
cull_mode: None,
front_face: wgpu::FrontFace::Ccw,
unclipped_depth: false,
polygon_mode: wgpu::PolygonMode::Fill,
conservative: false,
},
depth_stencil: Some(wgpu::DepthStencilState {
format: depth.depth_texture.format(),
depth_write_enabled: false,
depth_compare: wgpu::CompareFunction::Less,
stencil: wgpu::StencilState::default(),
bias: wgpu::DepthBiasState::default(),
}),
multisample: wgpu::MultisampleState::default(),
multiview: None,
});
(pipeline_main, pipeline_trans)
}

View file

@ -0,0 +1,23 @@
use bytemuck::{Pod, Zeroable};
#[derive(Clone, Copy, Pod, Zeroable)]
#[repr(C, packed)]
pub struct ChunkVertex {
pub position: [f32; 3],
pub normal: [f32; 3],
pub uv: [f32; 2],
pub tex_index: u32,
}
impl ChunkVertex {
pub const LAYOUT: wgpu::VertexBufferLayout<'static> = wgpu::VertexBufferLayout {
array_stride: std::mem::size_of::<ChunkVertex>() as wgpu::BufferAddress,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: &wgpu::vertex_attr_array![
0 => Float32x3,
1 => Float32x3,
2 => Float32x2,
3 => Uint32,
],
};
}

View file

@ -11,10 +11,10 @@ pub struct FullscreenSettings {
#[derive(Unique)]
pub struct GameSettings {
pub vsync: bool,
// pub vsync: bool,
pub fullscreen: Option<FullscreenSettings>,
pub msaa: Option<u8>,
pub max_anisotropy: Option<u16>,
// pub msaa: Option<u8>,
// pub max_anisotropy: Option<u16>,
/// there's a 1 chunk border of loaded but invisible around this
pub render_distance: u8,
pub mouse_sensitivity: f32,
@ -24,10 +24,10 @@ pub struct GameSettings {
impl Default for GameSettings {
fn default() -> Self {
Self {
vsync: false,
// vsync: false,
fullscreen: None,
msaa: Some(4),
max_anisotropy: Some(16),
// msaa: Some(4),
// max_anisotropy: Some(16),
render_distance: match true {
cfg!(debug_assertions) => 5,
cfg!(target_os = "android") => 6,

View file

@ -1,10 +1,10 @@
use hui::{color, element::{container::Container, text::Text, UiElementExt}, layout::Alignment, size};
use shipyard::{NonSendSync, UniqueView, UniqueViewMut};
use crate::{chat::{ChatHistory, ChatMessage}, hui_integration::UiState, rendering::WindowSize};
use crate::{chat::{ChatHistory, ChatMessage}, hui_integration::UiState, rendering::Renderer};
pub fn render_chat(
mut hui: NonSendSync<UniqueViewMut<UiState>>,
size: UniqueView<WindowSize>,
ren: UniqueView<Renderer>,
chat: UniqueView<ChatHistory>,
) {
let messages = chat.get_messages();
@ -39,5 +39,5 @@ pub fn render_chat(
.add_child(ui);
}
})
.add_root(&mut hui.hui, size.0.as_vec2());
.add_root(&mut hui.hui, ren.size_vec2());
}

View file

@ -5,7 +5,7 @@ use crate::{
hui_integration::UiState,
loading_screen::loading_screen_base,
networking::{ConnectionRejectionReason, ServerAddress},
rendering::WindowSize,
rendering::Renderer,
state::{GameState, NextState}
};
@ -14,7 +14,7 @@ fn render_connecting_ui(
rejection: Option<UniqueView<ConnectionRejectionReason>>,
join_state: UniqueView<ClientJoinState>,
mut ui: NonSendSync<UniqueViewMut<UiState>>,
size: UniqueView<WindowSize>,
ren: UniqueView<Renderer>,
) {
let text = match (rejection, *join_state) {
(Some(err), _) => {
@ -32,7 +32,7 @@ fn render_connecting_ui(
Text::new(text)
.with_text_size(16)
.add_child(ui);
}).add_root(&mut ui.hui, size.0.as_vec2())
}).add_root(&mut ui.hui, ren.size_vec2())
}
fn switch_to_loading_if_connected(

View file

@ -6,7 +6,7 @@ use hui::{
size
};
use shipyard::{AllStoragesViewMut, IntoIter, NonSendSync, Unique, UniqueView, UniqueViewMut, View};
use crate::{hui_integration::UiState, player::MainPlayer, rendering::WindowSize, settings::GameSettings, world::raycast::LookingAtBlock};
use crate::{hui_integration::UiState, player::MainPlayer, rendering::Renderer, settings::GameSettings, world::raycast::LookingAtBlock};
const CROSSHAIR_SIZE: usize = 9;
const CROSSHAIR: &[u8] = &[
@ -45,7 +45,7 @@ pub fn init_crosshair_image(storages: AllStoragesViewMut) {
pub fn draw_crosshair(
mut ui: NonSendSync<UniqueViewMut<UiState>>,
crosshair: UniqueView<CrosshairImage>,
size: UniqueView<WindowSize>,
ren: UniqueView<Renderer>,
player: View<MainPlayer>,
raycast: View<LookingAtBlock>,
settings: UniqueView<GameSettings>,
@ -57,6 +57,9 @@ pub fn draw_crosshair(
}
}
let size = ren.size_uvec2();
let size_rounded = uvec2(size.x & !1, size.y & !1).as_vec2();
Container::default()
.with_size(size!(100%))
.with_align(Alignment::Center)
@ -66,5 +69,5 @@ pub fn draw_crosshair(
.with_size(size!((CROSSHAIR_SIZE * 2)))
.add_child(ui);
})
.add_root(&mut ui.hui, uvec2(size.0.x & !1, size.0.y & !1).as_vec2());
.add_root(&mut ui.hui, size_rounded);
}

View file

@ -7,7 +7,7 @@ use hui::{
UiElementExt,
},
layout::{Alignment, Direction},
frame_rect, size,
rect_frame, size,
};
use shipyard::{UniqueView, UniqueViewMut, Workload, NonSendSync, IntoWorkload};
use winit::keyboard::KeyCode;
@ -15,7 +15,7 @@ use crate::{
hui_integration::UiState,
input::RawKbmInputState,
networking::ServerAddress,
rendering::WindowSize,
rendering::Renderer,
state::{GameState, NextState},
world::ChunkStorage,
};
@ -28,7 +28,7 @@ pub fn loading_screen_base(bg_alpha: f32, xui: impl FnOnce(&mut ElementList)) ->
.with_children(|ui| {
Container::default()
.with_size(size!(400, auto))
.with_background(frame_rect! {
.with_background(rect_frame! {
color: (0.2, 0.2, 0.2),
corner_radius: 8.
})
@ -43,7 +43,7 @@ fn render_loading_ui(
addr: Option<UniqueView<ServerAddress>>,
world: UniqueView<ChunkStorage>,
mut ui: NonSendSync<UniqueViewMut<UiState>>,
size: UniqueView<WindowSize>
ren: UniqueView<Renderer>,
) {
let loaded = world.chunks.iter().fold(0, |acc, (&_, chunk)| {
acc + chunk.desired_state.matches_current(chunk.current_state) as usize
@ -63,11 +63,11 @@ fn render_loading_ui(
ProgressBar::default()
.with_value(value)
.with_size(size!(100%, 15))
.with_background(frame_rect! {
.with_background(rect_frame! {
color: (0.1, 0.1, 0.1),
corner_radius: 2.
})
.with_foreground(frame_rect! {
.with_foreground(rect_frame! {
color: (0.4, 0.4, 1.0),
corner_radius: 2.
})
@ -83,7 +83,7 @@ fn render_loading_ui(
.add_child(ui)
})
.add_child(ui);
}).add_root(&mut ui.hui, size.0.as_vec2());
}).add_root(&mut ui.hui, ren.size_vec2());
}
fn switch_to_ingame_if_loaded(

View file

@ -2,11 +2,11 @@ use hui::{
element::{br::Break, container::Container, slider::Slider, text::Text, UiElementExt},
layout::{Alignment, Direction},
signal::Signal,
frame_rect, size,
rect_frame, size,
};
use shipyard::{NonSendSync, UniqueView, UniqueViewMut};
use winit::keyboard::KeyCode;
use crate::{hui_integration::UiState, input::RawKbmInputState, rendering::WindowSize, settings::GameSettings};
use crate::{hui_integration::UiState, input::RawKbmInputState, rendering::Renderer, settings::GameSettings};
#[derive(Signal)]
enum SettingsSignal {
@ -18,7 +18,7 @@ enum SettingsSignal {
pub fn render_settings_ui(
mut ui: NonSendSync<UniqueViewMut<UiState>>,
size: UniqueView<WindowSize>,
ren: UniqueView<Renderer>,
mut settings: UniqueViewMut<GameSettings>,
kbd: UniqueView<RawKbmInputState>,
) {
@ -34,7 +34,7 @@ pub fn render_settings_ui(
.with_align(Alignment::Center)
.with_children(|ui| {
Container::default()
.with_background(frame_rect! {
.with_background(rect_frame! {
color: (0.2, 0.2, 0.2),
corner_radius: 8.
})
@ -99,7 +99,7 @@ pub fn render_settings_ui(
})
.add_child(ui);
})
.add_root(&mut ui.hui, size.0.as_vec2());
.add_root(&mut ui.hui, ren.size_vec2());
ui.hui.process_signals(|signal: SettingsSignal| match signal {
SettingsSignal::SetRenderDistance(value) => settings.render_distance = value,

View file

@ -102,6 +102,7 @@ impl ChunkMeshStorage {
pub fn init_game_world(
storages: AllStoragesView,
) {
log::info!("init_game_world called");
storages.add_unique_non_send_sync(ChunkMeshStorage::new());
storages.add_unique(ChunkStorage::new());
storages.add_unique(ChunkTaskManager::new());

View file

@ -1,9 +1,8 @@
use std::sync::Arc;
use glam::IVec3;
use atomic::Atomic;
use glium::{VertexBuffer, IndexBuffer};
use kubi_shared::worldgen::AbortState;
use crate::rendering::world::ChunkVertex;
use crate::rendering::{world::ChunkVertex, BufferPair};
pub use kubi_shared::chunk::{CHUNK_SIZE, BlockData};
@ -18,10 +17,8 @@ impl ChunkData {
}
pub struct ChunkMesh {
pub vertex_buffer: VertexBuffer<ChunkVertex>,
pub index_buffer: IndexBuffer<u32>,
pub trans_vertex_buffer: VertexBuffer<ChunkVertex>,
pub trans_index_buffer: IndexBuffer<u32>,
pub main: BufferPair,
pub trans: BufferPair,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]

View file

@ -1,17 +1,17 @@
use std::sync::Arc;
use atomic::{Atomic, Ordering};
use glam::{IVec3, ivec3};
use glium::{VertexBuffer, IndexBuffer, index::PrimitiveType};
use kubi_shared::{networking::{channels::Channel, messages::ClientToServerMessage}, worldgen::AbortState};
use shipyard::{View, UniqueView, UniqueViewMut, IntoIter, Workload, IntoWorkload, NonSendSync, track};
use uflow::SendMode;
use wgpu::util::DeviceExt;
use crate::{
player::MainPlayer,
transform::Transform,
settings::GameSettings,
rendering::Renderer,
state::GameState,
networking::UdpClient,
player::MainPlayer,
rendering::{world::ChunkVertex, BufferPair, Renderer},
settings::GameSettings,
state::GameState,
transform::Transform,
};
use super::{
ChunkStorage, ChunkMeshStorage,
@ -266,7 +266,7 @@ fn process_completed_tasks(
task_manager: UniqueView<ChunkTaskManager>,
mut world: UniqueViewMut<ChunkStorage>,
mut meshes: NonSendSync<UniqueViewMut<ChunkMeshStorage>>,
renderer: NonSendSync<UniqueView<Renderer>>,
renderer: UniqueView<Renderer>,
state: UniqueView<GameState>,
mut queue: UniqueViewMut<BlockUpdateQueue>,
) {
@ -327,12 +327,51 @@ fn process_completed_tasks(
//apply the mesh
//TODO: Skip if mesh is empty? (i.e. set to None)
let mesh = ChunkMesh {
vertex_buffer: VertexBuffer::immutable(&renderer.display, &vertices).unwrap(),
index_buffer: IndexBuffer::immutable(&renderer.display, PrimitiveType::TrianglesList, &indices).unwrap(),
trans_vertex_buffer: VertexBuffer::immutable(&renderer.display, &trans_vertices).unwrap(),
trans_index_buffer: IndexBuffer::immutable(&renderer.display, PrimitiveType::TrianglesList, &trans_indices).unwrap(),
//TODO
let vtx_buffer = renderer.device().create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("chunk_vertex_buffer"),
contents: bytemuck::cast_slice(&vertices),
usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::VERTEX,
});
let idx_buffer = renderer.device().create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("chunk_vertex_buffer"),
contents: bytemuck::cast_slice(&indices),
usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::INDEX,
});
let vtx_buffer_trans = renderer.device().create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("chunk_trans_vertex_buffer"),
contents: bytemuck::cast_slice(&trans_vertices),
usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::VERTEX,
});
let idx_buffer_trans = renderer.device().create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("chunk_trans_index_buffer"),
contents: bytemuck::cast_slice(&trans_indices),
usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::INDEX,
});
let main_buffer_pair = BufferPair {
vertex: vtx_buffer,
vertex_len: vertices.len() as u32,
index: idx_buffer,
index_len: indices.len() as u32,
};
let trans_buffer_pair = BufferPair {
vertex: vtx_buffer_trans,
vertex_len: trans_vertices.len() as u32,
index: idx_buffer_trans,
index_len: trans_indices.len() as u32,
};
let mesh = ChunkMesh {
main: main_buffer_pair,
trans: trans_buffer_pair,
};
if let Some(index) = chunk.mesh_index {
meshes.update(index, mesh).expect("Mesh update failed");
} else {

View file

@ -1,4 +1,4 @@
use glam::{IVec3, ivec3};
use glam::{ivec3, IVec3, Vec3};
use strum::IntoEnumIterator;
use kubi_shared::block::{Block, RenderType, Transparency};
use crate::world::chunk::CHUNK_SIZE;
@ -10,7 +10,7 @@ mod builder;
use data::MeshGenData;
use builder::{MeshBuilder, CubeFace, DiagonalFace};
pub fn generate_mesh(data: MeshGenData) -> (
pub fn generate_mesh(position: IVec3, data: MeshGenData) -> (
(Vec<ChunkVertex>, Vec<u32>),
(Vec<ChunkVertex>, Vec<u32>),
) {
@ -32,8 +32,8 @@ pub fn generate_mesh(data: MeshGenData) -> (
}
};
let mut builder = MeshBuilder::new();
let mut trans_builder = MeshBuilder::new();
let mut builder = MeshBuilder::new_with_offset((position * CHUNK_SIZE as i32).as_vec3());
let mut trans_builder = MeshBuilder::new_with_offset((position * CHUNK_SIZE as i32).as_vec3());
for x in 0..CHUNK_SIZE as i32 {
for y in 0..CHUNK_SIZE as i32 {

View file

@ -1,5 +1,5 @@
use strum::EnumIter;
use glam::{Vec3, vec3, IVec3, ivec3};
use glam::{ivec3, vec3, IVec3, Vec3};
use std::f32::consts::FRAC_1_SQRT_2;
use crate::rendering::world::ChunkVertex;
@ -79,14 +79,15 @@ const CROSS_FACE_INDICES: [u32; 12] = [
const UV_COORDS: [[f32; 2]; 4] = [
[0., 0.],
[0., 1.],
[1., 0.],
[0., 0.],
[1., 1.],
[1., 0.],
];
#[derive(Default)]
pub struct MeshBuilder {
offset: Vec3,
vertex_buffer: Vec<ChunkVertex>,
index_buffer: Vec<u32>,
idx_counter: u32,
@ -96,6 +97,10 @@ impl MeshBuilder {
Self::default()
}
pub fn new_with_offset(offset: Vec3) -> Self {
Self { offset, ..Self::new() }
}
pub fn add_face(&mut self, face: CubeFace, coord: IVec3, texture: u8) {
let coord = coord.as_vec3();
let face_index = face as usize;
@ -106,10 +111,10 @@ impl MeshBuilder {
self.vertex_buffer.reserve(4);
for i in 0..4 {
self.vertex_buffer.push(ChunkVertex {
position: (coord + vert[i]).to_array(),
position: (coord + vert[i] + self.offset).to_array(),
normal: norm.to_array(),
uv: UV_COORDS[i],
tex_index: texture
tex_index: texture as u32
});
}
@ -129,18 +134,18 @@ impl MeshBuilder {
self.vertex_buffer.reserve(8);
for i in 0..4 { //push front vertices
self.vertex_buffer.push(ChunkVertex {
position: (coord.as_vec3() + vertices[i]).to_array(),
position: (coord.as_vec3() + vertices[i] + self.offset).to_array(),
normal: normal_front,
uv: UV_COORDS[i],
tex_index: front_texture
tex_index: front_texture as u32
})
}
for i in 0..4 { //push back vertices
self.vertex_buffer.push(ChunkVertex {
position: (coord.as_vec3() + vertices[i]).to_array(),
position: (coord.as_vec3() + vertices[i] + self.offset).to_array(),
normal: normal_back,
uv: UV_COORDS[i],
tex_index: back_texture
tex_index: back_texture as u32
})
}
@ -151,24 +156,25 @@ impl MeshBuilder {
self.idx_counter += 8;
}
pub fn add_model(&mut self, position: Vec3, vertices: &[ChunkVertex], indices: Option<&[u32]>) {
//push vertices
self.vertex_buffer.extend(vertices.iter().map(|vertex| {
let mut vertex = *vertex;
vertex.position[0] += position.x;
vertex.position[0] += position.y;
vertex.position[0] += position.z;
vertex
}));
//push indices
if let Some(indices) = indices {
self.index_buffer.extend(indices.iter().map(|x| x + self.idx_counter));
} else {
self.index_buffer.extend(0..(self.vertex_buffer.len() as u32));
}
//increment idx counter
self.idx_counter += vertices.len() as u32;
}
//XXX: needs offset supprt
// pub fn add_model(&mut self, position: Vec3, vertices: &[ChunkVertex], indices: Option<&[u32]>) {
// //push vertices
// self.vertex_buffer.extend(vertices.iter().map(|vertex| {
// let mut vertex = *vertex;
// vertex.position[0] += position.x;
// vertex.position[0] += position.y;
// vertex.position[0] += position.z;
// vertex
// }));
// //push indices
// if let Some(indices) = indices {
// self.index_buffer.extend(indices.iter().map(|x| x + self.idx_counter));
// } else {
// self.index_buffer.extend(0..(self.vertex_buffer.len() as u32));
// }
// //increment idx counter
// self.idx_counter += vertices.len() as u32;
// }
pub fn finish(self) -> (Vec<ChunkVertex>, Vec<u32>) {
(self.vertex_buffer, self.index_buffer)

View file

@ -63,7 +63,7 @@ impl ChunkTaskManager {
let (
(vertices, indices),
(trans_vertices, trans_indices),
) = generate_mesh(data);
) = generate_mesh(position, data);
ChunkTaskResponse::GeneratedMesh {
position,
vertices, indices,