Compare commits

..

33 Commits

Author SHA1 Message Date
9125878840 fix macro 2021-11-03 01:56:34 +01:00
bee1b66b55 perf counter macros 2021-10-31 16:12:32 +01:00
33bea8fbc8 change drawing event order 2021-10-30 17:40:25 +02:00
668696bdca timescaled input 2021-10-30 17:40:08 +02:00
6ecd5b6082 fixed maybe? 2021-10-29 17:07:29 +02:00
28576035eb huh? 2021-10-26 17:28:29 +02:00
f0c9ed5e51 no more warnings 2021-10-24 19:46:18 +02:00
c8d4da62b6 it works again!! 2021-10-24 19:27:37 +02:00
2834623ba7 vulkano reeeeeeeeeeee 2021-10-24 18:57:46 +02:00
23e19bf9d0 idk man 2021-10-24 15:12:59 +02:00
8d5457c810 variable number of textures? 2021-10-22 11:03:50 +02:00
0ccc56bbad cleanup 2021-10-22 08:46:29 +02:00
c63f7fc556 improvements 2021-10-21 10:43:13 +02:00
deaed7b456 virtual key codes 2021-10-19 04:23:20 +02:00
020fcd21dc performance counters 2021-10-18 11:17:25 +02:00
989056af99 fps counter 2021-10-14 22:27:54 +02:00
5365097f3b input fixes! 2021-10-14 19:22:23 +02:00
35f3d0e4a8 input stuff 2021-10-14 07:30:12 +02:00
3d449456e6 it works!! 2021-10-14 06:29:22 +02:00
366e3896da dynamic cache texture size!! 2021-10-14 06:25:58 +02:00
ba172ea332 remove game object handles 2021-10-14 05:30:55 +02:00
ea9490ad3a update text texture and mesh!! 2021-10-14 04:03:46 +02:00
78917d96d1 wip 2021-10-14 02:15:37 +02:00
db9a455311 font i guess? 2021-10-13 21:53:12 +02:00
00d6d1c5f8 fancy text 2021-08-16 02:27:00 +02:00
fb045f210a text almost works 2021-08-15 23:29:25 +02:00
40aa7f635e reee 2021-08-15 22:34:29 +02:00
c619f945a3 idk wip 2021-08-11 20:00:46 +02:00
b933a6dd35 multi touch fixes 2021-08-07 16:10:06 +02:00
bff41ecd89 rotation fixes 2021-08-07 13:58:42 +02:00
f9c5c04faa two finger rotation 2021-08-06 20:13:43 +02:00
7c473e0c81 touch axis 2021-08-06 01:03:19 +02:00
01a4e93f3f basic touch input 2021-08-05 23:33:26 +02:00
37 changed files with 1956 additions and 482 deletions

4
.gitignore vendored
View File

@@ -1,6 +1,7 @@
# Generated by Cargo # Generated by Cargo
# will have compiled files and executables # will have compiled files and executables
/target/ /target/
/rust-engine-proc/target/
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
@@ -81,4 +82,5 @@ fabric.properties
# cusotm # cusotm
build/deploy/ build/deploy/
build/capture/ build/capture/
*.blend1 *.blend1
.idea/

View File

@@ -6,9 +6,9 @@ edition = "2018"
default-run = "rust-engine" default-run = "rust-engine"
[dependencies] [dependencies]
vulkano-shaders = "0.24" vulkano-shaders = "0.26"
vulkano = "0.24" vulkano = "0.26"
vulkano-win = "0.24" vulkano-win = "0.26"
cgmath = "0.18.0" cgmath = "0.18.0"
winit = "0.25" winit = "0.25"
image = "0.23" image = "0.23"
@@ -18,7 +18,12 @@ serde_derive = "1.0"
toml = "0.5" toml = "0.5"
gilrs = "0.7" gilrs = "0.7"
gltf = "0.15" gltf = "0.15"
gfx_glyph = "0.17" glyph_brush = "0.7"
winapi = "0.3"
rust-engine-proc = { path = "rust-engine-proc" }
[profile.release]
debug = 1
[[bin]] [[bin]]
name = "converter" name = "converter"

Binary file not shown.

View File

@@ -1,53 +1,54 @@
# Scan codes are in decimal # Scan codes are in decimal: https://www.millisecond.com/support/docs/v6/html/language/scancodes.htm
# See here: https://www.millisecond.com/support/docs/v6/html/language/scancodes.htm # VK code here: https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes
[[button]] [[button]]
name = "quit" name = "quit"
scan_code = 1 vk_code = "VK_ESCAPE"
[[button]] [[button]]
name = "toggle_edit" name = "toggle_edit"
scan_code = 59 vk_code = "VK_F1"
[[button]] [[button]]
name = "reload_shaders" name = "reload_shaders"
scan_code = 63 vk_code = "VK_F5"
[[button]] [[button]]
name = "print_framerate" name = "toggle_framerate"
scan_code = 33 vk_code = "VK_F2"
[[button]] [[button]]
name = "button_forward" name = "button_forward"
scan_code = 17 vk_code = "VK_W"
[[button]] [[button]]
name = "button_backward" name = "button_backward"
scan_code = 31 vk_code = "VK_S"
[[button]] [[button]]
name = "button_left" name = "button_left"
scan_code = 30 vk_code = "VK_A"
[[button]] [[button]]
name = "button_right" name = "button_right"
scan_code = 32 vk_code = "VK_D"
[[button]] [[button]]
name = "test" name = "test"
scan_code = 20 vk_code = "VK_T"
[[button]] [[button]]
name = "quicksave" name = "quicksave"
scan_code = 63 vk_code = "VK_F6"
[[button]] [[button]]
name = "quickload" name = "quickload"
scan_code = 64 vk_code = "VK_F7"
[[button]] [[button]]
name = "select" name = "select"
mouse = "left" mouse = "left"
touch = 1
[[axis]] [[axis]]
name = "move_forward" name = "move_forward"
@@ -63,12 +64,12 @@ name = "move_forward"
controller_axis = "LeftStickY" controller_axis = "LeftStickY"
[[axis]] [[axis]]
name = "move_sideways" name = "move_right"
positive_button = "button_right" positive_button = "button_right"
negative_button = "button_left" negative_button = "button_left"
[[axis]] [[axis]]
name = "move_sideways" name = "move_right"
controller_axis = "LeftStickX" controller_axis = "LeftStickX"
[[axis]] [[axis]]
@@ -79,6 +80,10 @@ mouse_axis = "x"
name = "look_horizontal" name = "look_horizontal"
controller_axis = "RightStickX" controller_axis = "RightStickX"
[[axis]]
name = "look_horizontal"
touch_axis = "horizontal"
[[axis]] [[axis]]
name = "look_vertical" name = "look_vertical"
mouse_axis = "y" mouse_axis = "y"
@@ -87,5 +92,10 @@ mouse_axis = "y"
name = "look_vertical" name = "look_vertical"
controller_axis = "RightStickY" controller_axis = "RightStickY"
[[axis]]
name = "look_vertical"
touch_axis = "vertical"
[config] [config]
line_height_px = 16 line_height_px = 16
mouse_speed = 0.01

View File

@@ -1,11 +1,8 @@
vulkan_validation_layers = true vulkan_validation_layers = true
mesh_load_info = true mesh_load_info = true
debug_errors = true
debug_warnings = true
debug_info = true
debug_verbose = true
[input] [input]
mouse_motion = false mouse_motion = false
buttons = false buttons = false
touch = false
missing_bindings = true missing_bindings = true

35
config/testinput.toml Normal file
View File

@@ -0,0 +1,35 @@
[[button]]
name = "quit"
scan_code = 1
[[button]]
name = "select"
mouse = "left"
[[button]]
name = "touchclick"
touch = 1
[[button]]
name = "doubletouchclick"
touch = 2
[[button]]
name = "button_left"
scan_code = 30
[[button]]
name = "button_right"
vk_code = "VK_D"
[[axis]]
name = "move_sideways"
positive_button = "button_right"
negative_button = "button_left"
[[axis]]
name = "touch_drag"
touch_axis = "horizontal"
[config]
line_height_px = 16

View File

@@ -9,7 +9,7 @@
], ],
"objects": [ "objects": [
{ {
"mesh_index": 1, "mesh_index": 0,
"position": [ "position": [
0.0, 0.0,
0.0, 0.0,
@@ -26,20 +26,39 @@
1.0, 1.0,
1.0 1.0
] ]
},
{
"mesh_index": 1,
"position": [
-2.0,
0.0,
0.0
],
"rotation": [
1.0,
0.0,
0.0,
0.0
],
"scale": [
1.0,
1.0,
1.0
]
} }
], ],
"player": { "player": {
"mesh_index": null, "mesh_index": null,
"position": [ "position": [
0, 0.0,
0, 0.0,
5.0 10.0
], ],
"rotation": [ "rotation": [
1, 1.0,
0, 0.0,
0, 0.0,
0 0.0
], ],
"scale": [ "scale": [
1.0, 1.0,

BIN
models/FiraCode-Regular.ttf Normal file

Binary file not shown.

BIN
models/OverpassRegular.ttf Normal file

Binary file not shown.

View File

@@ -0,0 +1,13 @@
[package]
name = "rust-engine-proc"
version = "0.1.0"
edition = "2018"
[lib]
proc-macro = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
syn = { version = "1.0", features = ["full"] }
quote = "1.0"

View File

@@ -0,0 +1,38 @@
extern crate proc_macro;
use proc_macro::TokenStream;
use quote::quote;
use syn::{AttributeArgs, ItemFn, Lit, Meta, parse_macro_input, parse_quote};
#[proc_macro_attribute]
pub fn perf(attr: TokenStream, item: TokenStream) -> TokenStream {
let attrs = parse_macro_input!(attr as AttributeArgs);
let name = match &attrs[0] {
syn::NestedMeta::Lit(Lit::Str(l)) => l.value(),
_ => panic!("Invalid attribute"),
};
let counter_type = match &attrs[1] {
syn::NestedMeta::Meta(Meta::Path(p)) => p,
_ => panic!("Invalid attribute"),
};
let mut function = parse_macro_input!(item as ItemFn);
function.block.stmts.insert(0, parse_quote! {
let __perf_start = std::time::Instant::now();
});
let timer_end = parse_quote! {
unsafe {
#counter_type::write_perf(#name, __perf_start.elapsed().as_micros());
}
};
if let Some(last_stmt) = function.block.stmts.last() {
match last_stmt {
syn::Stmt::Expr(_) => function.block.stmts.insert(function.block.stmts.len() - 1, timer_end),
_ => function.block.stmts.push(timer_end),
}
} else {
function.block.stmts.push(timer_end);
}
quote!(#function).into()
}

22
shaders/text.frag Normal file
View File

@@ -0,0 +1,22 @@
#version 450
#extension GL_ARB_separate_shader_objects : enable
layout(binding = 0, set = 0) uniform ObjectUniformData {
mat4 view;
mat4 projection;
mat4 ortho_projection;
float time;
vec3 light_position;
vec3 light_directional_rotation;
vec3 camera_position;
} ubo;
layout(binding = 1, set = 0) uniform sampler2D diffuse_tex;
layout(location = 0) in vec2 tex_coords;
layout(location = 0) out vec4 out_color;
void main() {
out_color = vec4(1., 1., 1., texture(diffuse_tex, tex_coords).r);
}

BIN
shaders/text.frag.spv Normal file

Binary file not shown.

29
shaders/text.vert Normal file
View File

@@ -0,0 +1,29 @@
#version 450
#extension GL_ARB_separate_shader_objects : enable
layout(binding = 0, set = 0) uniform ObjectUniformData {
mat4 view;
mat4 projection;
mat4 ortho_projection;
float time;
vec3 light_position;
vec3 light_directional_rotation;
vec3 camera_position;
} ubo;
layout(location = 0) in vec3 position;
layout(location = 1) in vec2 uv;
layout(location = 0) out vec2 tex_coords;
out gl_PerVertex {
vec4 gl_Position;
};
void main() {
// Vertex position in camera
gl_Position = ubo.ortho_projection * vec4(position, 1.0);
// Just interpolate UV coords, no transformation needed
tex_coords = uv;
}

BIN
shaders/text.vert.spv Normal file

Binary file not shown.

View File

@@ -6,16 +6,17 @@ layout(push_constant) uniform PushConstants {
bool is_selected; bool is_selected;
} push; } push;
layout(binding = 0) uniform ObjectUniformData { layout(binding = 0, set = 0) uniform ObjectUniformData {
mat4 view; mat4 view;
mat4 projection; mat4 projection;
mat4 ortho_projection;
float time; float time;
vec3 light_position; vec3 light_position;
vec3 light_directional_rotation; vec3 light_directional_rotation;
vec3 camera_position; vec3 camera_position;
} ubo; } ubo;
layout(binding = 1) uniform sampler2D diffuse_tex; layout(binding = 1, set = 0) uniform sampler2D diffuse_tex;
layout(binding = 2) uniform sampler2D normal_tex; layout(binding = 2) uniform sampler2D normal_tex;
layout(location = 0) in vec2 tex_coords; layout(location = 0) in vec2 tex_coords;

Binary file not shown.

View File

@@ -6,9 +6,10 @@ layout(push_constant) uniform PushConstants {
bool is_selected; bool is_selected;
} push; } push;
layout(binding = 0) uniform ObjectUniformData { layout(binding = 0, set = 0) uniform ObjectUniformData {
mat4 view; mat4 view;
mat4 projection; mat4 projection;
mat4 ortho_projection;
float time; float time;
vec3 light_position; vec3 light_position;
vec3 light_directional_rotation; vec3 light_directional_rotation;
@@ -34,6 +35,7 @@ out gl_PerVertex {
void main() { void main() {
// Vertex position in camera // Vertex position in camera
gl_Position = ubo.projection * ubo.view * push.model * vec4(position, 1.0); gl_Position = ubo.projection * ubo.view * push.model * vec4(position, 1.0);
// gl_Position.y = -gl_Position.y;
// Vertex position in world // Vertex position in world
position_wld = vec3(push.model * vec4(position, 1.0)); position_wld = vec3(push.model * vec4(position, 1.0));

Binary file not shown.

View File

@@ -8,6 +8,7 @@ use vulkano::image::SampleCount;
pub struct LogConfigInput { pub struct LogConfigInput {
pub mouse_motion: bool, pub mouse_motion: bool,
pub buttons: bool, pub buttons: bool,
pub touch: bool,
pub missing_bindings: bool pub missing_bindings: bool
} }
@@ -15,11 +16,7 @@ pub struct LogConfigInput {
pub struct LogConfig { pub struct LogConfig {
pub vulkan_validation_layers: bool, pub vulkan_validation_layers: bool,
pub mesh_load_info: bool, pub mesh_load_info: bool,
pub input: LogConfigInput, pub input: LogConfigInput
pub debug_errors: bool,
pub debug_warnings: bool,
pub debug_info: bool,
pub debug_verbose: bool
} }
impl LogConfig { impl LogConfig {
@@ -39,6 +36,6 @@ impl RenderConfig {
} }
pub fn get_msaa(&self) -> Option<SampleCount> { pub fn get_msaa(&self) -> Option<SampleCount> {
self.msaa_samples.try_into().ok() if self.msaa_samples > 0 { self.msaa_samples.try_into().ok() } else { None }
} }
} }

95
src/game/entities.rs Normal file
View File

@@ -0,0 +1,95 @@
use cgmath::{Vector4, vec4};
use crate::{input::InputState, text::{create_text_object, update_text}, vulkan::{MeshHandle, TextVertex, Vertex, VulkanRenderer, gameobject::{GameObject, GameObjectHandle, Updatable}, mesh::{CPUMesh, CPUVertexList}}};
use super::{GameState, TestGame};
pub struct FpsCounter {
pub game_object: GameObjectHandle,
pub text: String,
}
impl FpsCounter {
pub fn new(game: &mut TestGame, renderer: &mut VulkanRenderer) -> FpsCounter {
let text_mesh = create_text_object(&mut game.game_state.brush, renderer, "", 30.);
FpsCounter { game_object: game.add_game_object(renderer, text_mesh), text: String::new() }
}
}
impl Updatable for FpsCounter {
fn update(&mut self, _delta_time: f32, input: &InputState, game_state: &mut GameState, game_objects: &mut Vec<GameObject>, renderer: &mut VulkanRenderer) {
if input.button_just_pressed("toggle_framerate") {
let go = &mut game_objects[self.game_object];
go.visible = !go.visible;
}
self.text.clear();
unsafe {
if let Some(pcs) = &crate::perf::PERF_COUNTERS {
for (name, pc) in pcs {
let mean = pc.mean();
let stddev = pc.stddev(mean as i128);
self.text.push_str(&format!("{}: {:.1}ms (±{:.1})\n", name, (mean as f64) / 1000., stddev / 1000.));
}
}
}
update_text(self.game_object, &self.text, 30., renderer, &mut game_state.brush, game_objects);
}
}
pub struct WorldQuad {
pub game_object: GameObjectHandle,
}
impl WorldQuad {
pub fn new(game: &mut TestGame, renderer: &mut VulkanRenderer) -> WorldQuad {
let quad_verts = vec![
Vertex { position: [0., 0., 0.], uv: [0., 0.], normal: [0., 1., 0.], tangent: [1., 0., 0., 1.], bone_index: [0, 0, 0, 0], bone_weight: [0., 0., 0., 0.] },
Vertex { position: [1., 0., 0.], uv: [1., 0.], normal: [0., 1., 0.], tangent: [1., 0., 0., 1.], bone_index: [0, 0, 0, 0], bone_weight: [0., 0., 0., 0.] },
Vertex { position: [0., 0., 1.], uv: [0., 1.], normal: [0., 1., 0.], tangent: [1., 0., 0., 1.], bone_index: [0, 0, 0, 0], bone_weight: [0., 0., 0., 0.] },
Vertex { position: [1., 0., 1.], uv: [1., 1.], normal: [0., 1., 0.], tangent: [1., 0., 0., 1.], bone_index: [0, 0, 0, 0], bone_weight: [0., 0., 0., 0.] },
];
let cpu_mesh = CPUMesh { vertices: CPUVertexList::Vertex3D(quad_verts), indices: vec![0, 2, 1, 1, 2, 3], local_texture_index: None, local_normal_map_index: None, name: None };
let mesh_index = renderer.upload_mesh(cpu_mesh, None);
let mesh_handle = MeshHandle {
index: mesh_index,
textures: vec![1, 0],
original_path: None,
pipeline_index: 0
};
let game_object = game.add_game_object(renderer, mesh_handle);
WorldQuad { game_object }
}
}
pub struct UiQuad {
pub game_object: GameObjectHandle,
pub background_color: Vector4<f32>,
}
impl UiQuad {
pub fn new(game: &mut TestGame, renderer: &mut VulkanRenderer) -> UiQuad {
let quad_verts = vec![
TextVertex { position: [0., 0., 0.], uv: [0., 0.] },
TextVertex { position: [0., 1., 0.], uv: [0., 1.] },
TextVertex { position: [1., 0., 0.], uv: [1., 0.] },
TextVertex { position: [1., 1., 0.], uv: [1., 1.] },
];
let cpu_mesh = CPUMesh { vertices: CPUVertexList::VertexText(quad_verts), indices: vec![0, 2, 1, 1, 2, 3], local_texture_index: None, local_normal_map_index: None, name: None };
let mesh_index = renderer.upload_mesh(cpu_mesh, None);
let mesh_handle = MeshHandle {
index: mesh_index,
textures: vec![],
original_path: None,
pipeline_index: 1
};
let go = game.add_game_object(renderer, mesh_handle);
UiQuad { game_object: go, background_color: vec4(0., 0., 255., 0.) }
}
}
impl Updatable for UiQuad {
fn update(&mut self, _delta_time: f32, _input: &InputState, _game_state: &mut GameState, _game_objects: &mut Vec<GameObject>, _renderer: &mut VulkanRenderer) {
}
}

View File

@@ -3,7 +3,7 @@ use std::fs::File;
use serde_derive::{Deserialize, Serialize}; use serde_derive::{Deserialize, Serialize};
use crate::{vulkan::{MeshHandle, VulkanRenderer, gameobject::GameObjectHandle}}; use crate::vulkan::{MeshHandle, VulkanRenderer, gameobject::GameObjectHandle};
use crate::game::TestGame; use crate::game::TestGame;
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
@@ -38,8 +38,8 @@ pub fn load_level(path: &str, game: &mut TestGame, renderer: &mut VulkanRenderer
let objects: Vec<GameObjectHandle> = level_json.objects.iter().filter_map(|json_obj| { let objects: Vec<GameObjectHandle> = level_json.objects.iter().filter_map(|json_obj| {
// TODO: Parenting // TODO: Parenting
if let Some(mesh_index) = json_obj.mesh_index { if let Some(mesh_index) = json_obj.mesh_index {
let mut handle = game.add_game_object(renderer, meshes[mesh_index].clone()); let handle = game.add_game_object(renderer, meshes[mesh_index].clone());
let game_object = handle.get_game_object_mut(renderer).unwrap(); let game_object = &mut game.game_objects[handle];
game_object.position = json_obj.position.into(); game_object.position = json_obj.position.into();
game_object.rotation = json_obj.rotation.into(); game_object.rotation = json_obj.rotation.into();
game_object.scale = json_obj.scale.into(); game_object.scale = json_obj.scale.into();
@@ -54,20 +54,20 @@ pub fn load_level(path: &str, game: &mut TestGame, renderer: &mut VulkanRenderer
} }
pub fn save_level(path: &str, game: &mut TestGame, renderer: &mut VulkanRenderer) -> Result<(), Box<dyn Error>> { pub fn save_level(path: &str, game: &mut TestGame, renderer: &mut VulkanRenderer) -> Result<(), Box<dyn Error>> {
let meshes = renderer.game_data.meshes.iter().map(|mesh| { let meshes = renderer.game_data.meshes.iter().filter(|m| m.original_path.is_some()).map(|mesh| {
MeshJson { MeshJson {
path: mesh.original_path.to_string() path: mesh.original_path.clone().unwrap()
} }
}).collect(); }).collect();
let objects = game.game_objects.iter().map(|game_object_handle| { let objects = game.game_objects.iter().enumerate().filter_map(|(i, game_object)| {
let game_object = game_object_handle.get_game_object(renderer).unwrap(); if game_object.pipeline_index != 0 { return None; }
ObjectJson { Some(ObjectJson {
mesh_index: Some(game_object_handle.object_index), mesh_index: Some(i),
position: game_object.position.into(), position: game_object.position.into(),
rotation: game_object.rotation.into(), rotation: game_object.rotation.into(),
scale: game_object.scale.into() scale: game_object.scale.into()
} })
}).collect(); }).collect();
let player = ObjectJson { let player = ObjectJson {

View File

@@ -1,39 +1,64 @@
use std::time::Instant;
use std::time::SystemTime;
use cgmath::{Deg, Euler, Quaternion, vec3}; use cgmath::{Deg, Euler, Quaternion, vec3};
use glyph_brush::GlyphBrush;
use rust_engine_proc::perf;
use winit::event::Event; use winit::event::Event;
use level::{load_level, save_level}; use level::{load_level, save_level};
use player::Player; use player::Player;
use crate::game::entities::{FpsCounter, UiQuad, WorldQuad};
use crate::text::create_brush;
use crate::vulkan::pipelines::vs;
use crate::{config::LogConfig, vulkan}; use crate::{config::LogConfig, vulkan};
use crate::input::InputState; use crate::input::InputState;
use crate::vulkan::{Game, MeshHandle, VulkanRenderer}; use crate::vulkan::{Game, MeshHandle, TextVertex, Vertex, VulkanRenderer};
use crate::vulkan::gameobject::{GameObject, GameObjectHandle, Updatable}; use crate::vulkan::gameobject::{GameObject, GameObjectHandle, Updatable};
use crate::vulkan::mesh; use crate::vulkan::mesh;
use crate::vulkan::pipelines::vs::ty::ObjectUniformData;
pub mod player; pub mod player;
mod level; mod level;
mod entities;
pub struct GameState {
pub paused: bool,
pub brush: GlyphBrush<Vec<TextVertex>>,
pub test_str: String,
}
impl GameState {
fn new() -> GameState {
GameState {
brush: create_brush(),
paused: false,
test_str: "".to_string(),
}
}
}
pub struct TestGame { pub struct TestGame {
pub input: InputState, pub input: InputState,
pub player: Player, pub player: Player,
pub game_objects: Vec<GameObjectHandle>, pub game_objects: Vec<GameObject>,
pub log_config: LogConfig, pub log_config: LogConfig,
pub texture_index_counter: usize, pub texture_index_counter: usize,
pub last_time: f32, pub last_time: f32,
pub components: Vec<Box<dyn Updatable>>, pub components: Vec<Box<dyn Updatable>>,
pub paused: bool, pub game_state: GameState,
pub ubo: vs::ty::ObjectUniformData,
} }
impl Game for TestGame { impl Game for TestGame {
fn get_game_objects(&self) -> &Vec<GameObject> {
&self.game_objects
}
fn on_window_event(self: &mut Self, event: &Event<()>) { fn on_window_event(self: &mut Self, event: &Event<()>) {
self.input.on_window_event(event); self.input.on_window_event(event);
} }
fn update(self: &mut Self, renderer: &mut VulkanRenderer) -> ObjectUniformData { #[perf("update", crate::perf::PerformanceCounter)]
fn update(self: &mut Self, renderer: &mut VulkanRenderer) {
// Input and timing // Input and timing
self.input.frame_start(); self.input.frame_start();
let time = (renderer.game_data.start_time.elapsed().unwrap().as_micros() as f64 / 1000000.0) as f32; let time = (renderer.game_data.start_time.elapsed().unwrap().as_micros() as f64 / 1000000.0) as f32;
@@ -41,10 +66,17 @@ impl Game for TestGame {
// Component update // Component update
let input = &self.input; let input = &self.input;
let objs = &mut self.game_objects;
let components = &mut self.components; let components = &mut self.components;
components.iter_mut().for_each(|component| { let paused = self.game_state.paused;
component.update(frame_time, &input, renderer); let state = &mut self.game_state;
});
if !paused {
components.iter_mut().for_each(|component| {
component.update(frame_time, &input, state, objs, renderer);
});
self.player.update(frame_time, &input, state, objs, renderer);
}
// User interaction // User interaction
if self.input.button_just_released("quit") { if self.input.button_just_released("quit") {
@@ -64,28 +96,28 @@ impl Game for TestGame {
load_level("levels/test.lvl", self, renderer).unwrap(); load_level("levels/test.lvl", self, renderer).unwrap();
} }
if self.input.button_down("print_framerate") {
println!("{:.0} ms / {:.0} FPS", frame_time * 1000.0, 1.0 / frame_time);
}
if self.input.button_just_pressed("test") { if self.input.button_just_pressed("test") {
self.paused = !self.paused; self.game_state.paused = !self.game_state.paused;
} }
for char in self.input.typed_characters.iter() {
match char {
'\u{8}' => { self.game_state.test_str.pop(); },
c => { self.game_state.test_str.push(*c); },
}
}
// Custom game object stuff // Custom game object stuff
let light_pos = vec3(2.0, 0.5, 2.0); let light_pos = vec3(2.0, 0.5, 2.0);
if !self.paused {
self.player.update(frame_time, &self.input, renderer);
}
// self.game_objects[1].get_game_object_mut(renderer).unwrap().rotation = Quaternion::from_angle_y(Deg(time * -20.)).normalize();
// End frame // End frame
self.last_time = time; self.last_time = time;
self.input.frame_end(); self.input.frame_end();
ObjectUniformData { self.ubo = vs::ty::ObjectUniformData {
view: self.player.camera.view.into(), view: self.player.camera.view.into(),
projection: self.player.camera.proj.into(), projection: self.player.camera.proj.into(),
ortho_projection: self.player.camera.ortho_proj.into(),
time, time,
light_position: light_pos.into(), light_position: light_pos.into(),
light_directional_rotation: [45.0, 45.0, 0.0], light_directional_rotation: [45.0, 45.0, 0.0],
@@ -93,7 +125,11 @@ impl Game for TestGame {
_dummy0: [0; 12], _dummy0: [0; 12],
_dummy1: [0; 4], _dummy1: [0; 4],
_dummy2: [0; 4], _dummy2: [0; 4],
} };
}
fn get_ubo(&self) -> &vs::ty::ObjectUniformData {
&self.ubo
} }
} }
@@ -101,18 +137,42 @@ impl TestGame {
pub fn new(toml_path: &str, log_config: LogConfig) -> TestGame { pub fn new(toml_path: &str, log_config: LogConfig) -> TestGame {
TestGame { TestGame {
input: InputState::new(toml_path, log_config), input: InputState::new(toml_path, log_config),
player: Player::new(), player: Player::new(3., 30.),
game_objects: vec![], game_objects: vec![],
log_config, log_config,
texture_index_counter: 0, texture_index_counter: 0,
last_time: 0.0, last_time: 0.0,
components: vec![], components: vec![],
paused: false, game_state: GameState::new(),
ubo: vs::ty::ObjectUniformData {
view: [[0.; 4]; 4],
projection: [[0.; 4]; 4],
ortho_projection: [[0.; 4]; 4],
time: 0.,
light_position: [0.; 3],
light_directional_rotation: [0.; 3],
camera_position: [0.; 3],
_dummy0: [0; 12],
_dummy1: [0; 4],
_dummy2: [0; 4],
},
} }
} }
pub fn game_start(self: &mut Self, renderer: &mut VulkanRenderer) { pub fn game_start(self: &mut Self, renderer: &mut VulkanRenderer) {
load_level("levels/test.lvl", self, renderer).unwrap(); load_level("levels/test.lvl", self, renderer).unwrap();
let fps = FpsCounter::new(self, renderer);
self.components.push(Box::new(fps));
let test_quad = UiQuad::new(self, renderer);
self.components.push(Box::new(test_quad));
let world_quad = WorldQuad::new(self, renderer);
let quad_go = &mut self.game_objects[world_quad.game_object];
quad_go.position = vec3(0.0, 0.01, 0.0);
quad_go.scale = vec3(10., 10., 10.);
println!("Game loaded!"); println!("Game loaded!");
} }
@@ -127,7 +187,7 @@ impl TestGame {
let mut mesh_handles = Vec::new(); let mut mesh_handles = Vec::new();
// Load file // Load file
let (meshes, document) = mesh::load_mesh(gltf_path, self.log_config.mesh_load_info).unwrap(); let (meshes, document) = mesh::load_mesh::<Vertex>(gltf_path, self.log_config.mesh_load_info).unwrap();
for cpu_mesh in meshes.into_iter() { for cpu_mesh in meshes.into_iter() {
// Convert file texture id to game texture id // Convert file texture id to game texture id
@@ -135,34 +195,35 @@ impl TestGame {
let normal_id = self.offset_texture_id(cpu_mesh.local_normal_map_index); let normal_id = self.offset_texture_id(cpu_mesh.local_normal_map_index);
// Upload mesh // Upload mesh
let mesh_id = renderer.upload_mesh(cpu_mesh, gltf_path.to_string()); let mesh_id = renderer.upload_mesh(cpu_mesh, Some(gltf_path.to_string()));
let mesh_handle = MeshHandle { let mesh_handle = MeshHandle {
index: mesh_id, index: mesh_id,
diffuse_handle: diffuse_id, textures: vec![diffuse_id, normal_id],
normal_handle: normal_id, original_path: Some(gltf_path.to_string()),
original_path: gltf_path.to_string() pipeline_index: 0
}; };
mesh_handles.push(mesh_handle); mesh_handles.push(mesh_handle);
} }
for doc_image in document.images() { for doc_image in document.images() {
let texture_start_time = SystemTime::now(); let texture_start_time = Instant::now();
vulkan::dds::upload_texture_from_file(&format!("models/textures/{}.dds", doc_image.name().unwrap()), renderer).unwrap(); let texture = vulkan::dds::upload_texture_from_file(&format!("models/textures/{}.dds", doc_image.name().unwrap()), renderer).unwrap();
renderer.game_data.textures.push(texture);
self.texture_index_counter += 1; self.texture_index_counter += 1;
if self.log_config.mesh_load_info { if self.log_config.mesh_load_info {
println!("Uploading texture took {:?}ms", texture_start_time.elapsed().unwrap().as_millis()); println!("Uploading texture took {:?}ms", texture_start_time.elapsed().as_millis());
} }
} }
mesh_handles mesh_handles
} }
pub fn add_game_object(&mut self, renderer: &mut VulkanRenderer, mesh: MeshHandle) -> GameObjectHandle { pub fn add_game_object(&mut self, renderer: &mut VulkanRenderer, mesh: MeshHandle) -> GameObjectHandle {
let obj = GameObject::new(mesh); let mut obj = GameObject::new(mesh);
let obj_handle = renderer.add_game_object(obj, 0); obj.init_descriptor_sets(renderer);
self.game_objects.push(obj_handle); self.game_objects.push(obj);
self.game_objects.last().unwrap().clone() self.game_objects.len() - 1
} }
pub fn clear_level(&mut self, renderer: &mut VulkanRenderer) { pub fn clear_level(&mut self, renderer: &mut VulkanRenderer) {
@@ -175,4 +236,4 @@ impl TestGame {
pub fn _print_quat_as_euler(quat: Quaternion<f32>) { pub fn _print_quat_as_euler(quat: Quaternion<f32>) {
let euler = Euler::from(quat); let euler = Euler::from(quat);
print!("({:?},{:?},{:?})", Deg::from(euler.x), Deg::from(euler.y), Deg::from(euler.z)); print!("({:?},{:?},{:?})", Deg::from(euler.x), Deg::from(euler.y), Deg::from(euler.z));
} }

View File

@@ -1,22 +1,24 @@
use cgmath::{Deg, InnerSpace, Matrix4, One, Quaternion, Rad, Rotation, Rotation3, SquareMatrix, Vector3, Vector4, vec3, vec4}; use cgmath::{Deg, InnerSpace, Matrix4, One, Quaternion, Rad, Rotation, Rotation3, SquareMatrix, Vector2, Vector3, vec3, vec4};
use vulkano::buffer::TypedBufferAccess;
use crate::game::player::PlayerMovementMode::{FirstPerson, Flying}; use crate::game::player::PlayerMovementMode::{FirstPerson, Flying};
use crate::input::InputState; use crate::input::InputState;
use crate::vulkan::Mesh; use crate::util::intersection_distance;
use crate::vulkan::gameobject::GameObject; use crate::vulkan::gameobject::GameObject;
use crate::vulkan::{ use crate::vulkan::{
gameobject::Updatable, gameobject::Updatable,
VulkanRenderer VulkanRenderer
}; };
use super::GameState;
pub struct Camera { pub struct Camera {
pub fov_y: f32, pub fov_y: f32,
pub position: Vector3<f32>, pub position: Vector3<f32>,
pub rotation: Quaternion<f32>, pub rotation: Quaternion<f32>,
pub view: Matrix4<f32>, pub view: Matrix4<f32>,
pub proj: Matrix4<f32>, pub proj: Matrix4<f32>,
pub ortho_proj: Matrix4<f32>,
} }
impl Camera { impl Camera {
@@ -27,6 +29,7 @@ impl Camera {
rotation: Quaternion::one(), rotation: Quaternion::one(),
view: Matrix4::identity(), view: Matrix4::identity(),
proj: Matrix4::identity(), proj: Matrix4::identity(),
ortho_proj: Matrix4::identity(),
} }
} }
@@ -38,23 +41,23 @@ impl Camera {
// Create matrices // Create matrices
self.view = Matrix4::from(self.rotation) * Matrix4::from_translation(self.position * -1.); self.view = Matrix4::from(self.rotation) * Matrix4::from_translation(self.position * -1.);
let width = renderer.game_data.dimensions[0] as f32;
let height = renderer.game_data.dimensions[1] as f32;
self.proj = cgmath::perspective( self.proj = cgmath::perspective(
Rad::from(Deg(self.fov_y)), Rad::from(Deg(self.fov_y)),
renderer.game_data.dimensions[0] as f32 / renderer.game_data.dimensions[1] as f32, width / height,
0.1, 0.1,
100.0 100.0
); );
// Why?
self.proj.y.y *= -1.0; self.proj.y.y *= -1.0;
// Upload self.ortho_proj = cgmath::ortho(0., width, 0., height, -1., 1.);
renderer.game_data.line_push_constants.view = self.view.into();
renderer.game_data.line_push_constants.projection = self.proj.into();
} }
pub fn viewport_pos_to_ray_direction(&self, viewport_pos: [f64; 2], viewport_dimensions: [u32; 2]) -> Option<Vector3<f32>> { pub fn viewport_pos_to_ray_direction(&self, viewport_pos: Vector2<f64>, viewport_dimensions: [u32; 2]) -> Option<Vector3<f32>> {
let normalized_x = 2. * (viewport_pos[0] as f32 / viewport_dimensions[0] as f32) - 1.; let normalized_x = 2. * (viewport_pos.x as f32 / viewport_dimensions[0] as f32) - 1.;
let normalized_y = 2. * (viewport_pos[1] as f32 / viewport_dimensions[1] as f32) - 1.; let normalized_y = 2. * (viewport_pos.y as f32 / viewport_dimensions[1] as f32) - 1.;
let click_start_screen = vec4(normalized_x, normalized_y, -1.0, 1.0); let click_start_screen = vec4(normalized_x, normalized_y, -1.0, 1.0);
let click_end_screen = vec4(normalized_x, normalized_y, 0.0, 1.0); let click_end_screen = vec4(normalized_x, normalized_y, 0.0, 1.0);
@@ -105,12 +108,12 @@ pub struct Player {
} }
impl Player { impl Player {
pub fn new() -> Player { pub fn new(movement_speed: f32, look_sensitivity: f32) -> Player {
Player { Player {
camera: Camera::new(), camera: Camera::new(),
movement_mode: Flying, movement_mode: Flying,
movement_speed: 3.0, movement_speed,
look_sensitivity: 30.0, look_sensitivity,
height: 1.0, height: 1.0,
x_look: 0.0, x_look: 0.0,
y_look: 0.0 y_look: 0.0
@@ -118,60 +121,8 @@ impl Player {
} }
} }
pub fn vec4_from_pos(pos: [f32; 3]) -> Vector4<f32> {
vec4(pos[0], pos[1], pos[2], 1.0)
}
pub fn intersection_distance(ray_origin: Vector3<f32>, ray_direction: Vector3<f32>, mesh: &Mesh, game_object: &GameObject) -> Option<f32> {
let index_lock = mesh.index_buffer.read().unwrap();
let vertex_lock = mesh.vertex_buffer.read().unwrap();
(0..mesh.index_buffer.len() / 3).map(|tri_idx| {
let vtx_a = game_object.get_model_matrix() * vec4_from_pos(vertex_lock[index_lock[tri_idx * 3 ] as usize].position);
let vtx_b = game_object.get_model_matrix() * vec4_from_pos(vertex_lock[index_lock[tri_idx * 3 + 1] as usize].position);
let vtx_c = game_object.get_model_matrix() * vec4_from_pos(vertex_lock[index_lock[tri_idx * 3 + 2] as usize].position);
intersect_triangle(ray_origin, ray_direction, vtx_a.truncate().into(), vtx_b.truncate().into(), vtx_c.truncate().into())
}).fold(None, |acc, x| {
if let Some(smallest) = acc {
if let Some(new) = x {
if new < smallest {
Some(new)
} else {
Some(smallest)
}
} else {
acc
}
} else {
x
}
})
}
// https://en.wikipedia.org/wiki/M%C3%B6ller%E2%80%93Trumbore_intersection_algorithm
pub fn intersect_triangle(ray_origin: Vector3<f32>, ray_direction: Vector3<f32>, vtx_a: [f32; 3], vtx_b: [f32; 3], vtx_c: [f32; 3]) -> Option<f32> {
let edge_1 = vec3(vtx_b[0] - vtx_a[0], vtx_b[1] - vtx_a[1], vtx_b[2] - vtx_a[2]);
let edge_2 = vec3(vtx_c[0] - vtx_a[0], vtx_c[1] - vtx_a[1], vtx_c[2] - vtx_a[2]);
let h = ray_direction.cross(edge_2);
let a = edge_1.dot(h);
if a > -f32::EPSILON && a < f32::EPSILON { return None; }
let f = 1. / a;
let s: Vector3<f32> = ray_origin - Vector3::from(vtx_a);
let u = f * s.dot(h);
if u < 0. || u > 1. { return None; }
let q = s.cross(edge_1);
let v = f * ray_direction.dot(q);
if v < 0. || u + v > 1. { return None; }
let t = f * edge_2.dot(q);
if t > f32::EPSILON { return Some(t); }
None
}
impl Updatable for Player { impl Updatable for Player {
fn update(&mut self, delta_time: f32, input: &InputState, renderer: &mut VulkanRenderer) { fn update(&mut self, delta_time: f32, input: &InputState, _game_state: &mut GameState, game_objects: &mut Vec<GameObject>, renderer: &mut VulkanRenderer) {
// Edit mode // Edit mode
if input.button_just_pressed("toggle_edit") { if input.button_just_pressed("toggle_edit") {
if self.movement_mode == FirstPerson { if self.movement_mode == FirstPerson {
@@ -183,30 +134,28 @@ impl Updatable for Player {
if input.button_just_pressed("select") { if input.button_just_pressed("select") {
let ray_direction = self.camera.viewport_pos_to_ray_direction(input.mouse_position, renderer.game_data.dimensions).unwrap(); let ray_direction = self.camera.viewport_pos_to_ray_direction(input.mouse_position, renderer.game_data.dimensions).unwrap();
for game_object in &mut renderer.game_data.game_objects { for game_object in game_objects {
let mesh = &renderer.game_data.meshes[game_object.mesh_index]; let mesh = &renderer.game_data.meshes[game_object.mesh_index];
if let Some(dist) = intersection_distance(self.camera.position, ray_direction, mesh, game_object) { if let Some(_) = intersection_distance(self.camera.position, ray_direction, mesh, game_object) {
game_object.is_selected = !game_object.is_selected; game_object.is_selected = !game_object.is_selected;
println!("distance: {}", dist);
} }
} }
} }
// Rotation // Rotation
self.x_look += input.get_axis("look_vertical") * delta_time * self.look_sensitivity; self.x_look += input.get_axis_timescaled("look_vertical", delta_time) * self.look_sensitivity;
self.y_look += input.get_axis("look_horizontal") * delta_time * self.look_sensitivity; self.y_look += input.get_axis_timescaled("look_horizontal", delta_time) * self.look_sensitivity;
let x_rot = Quaternion::from_angle_x(Deg(self.x_look)); let x_rot = Quaternion::from_angle_x(Deg(self.x_look));
let y_rot = Quaternion::from_angle_y(Deg(self.y_look)); let y_rot = Quaternion::from_angle_y(Deg(self.y_look));
self.camera.rotation = x_rot * y_rot; self.camera.rotation = x_rot * y_rot;
// Movement // Movement
let local_input = vec3(input.get_axis("move_sideways"), 0.0, -input.get_axis("move_forward")) * self.movement_speed * delta_time; let local_input = vec3(input.get_axis_timescaled("move_right", delta_time), 0.0, -input.get_axis_timescaled("move_forward", delta_time)) * self.movement_speed;
if self.movement_mode == FirstPerson { if self.movement_mode == FirstPerson {
let mut world_input = self.camera.local_to_world(local_input); let mut world_input = self.camera.local_to_world(local_input);
world_input.y = 0.0; world_input.y = 0.0;
self.camera.position += world_input.normalize(); self.camera.position += world_input;
self.camera.position.y = self.height;
} else if self.movement_mode == Flying { } else if self.movement_mode == Flying {
self.camera.position += self.camera.local_to_world(local_input); self.camera.position += self.camera.local_to_world(local_input);
} }
@@ -214,4 +163,4 @@ impl Updatable for Player {
// Update // Update
self.camera.update(renderer); self.camera.update(renderer);
} }
} }

View File

@@ -2,24 +2,30 @@
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use std::fs; use std::fs;
use std::hash::Hash;
use std::iter::FromIterator; use std::iter::FromIterator;
use cgmath::{InnerSpace, Vector2, Zero, vec2};
use gilrs; use gilrs;
use gilrs::{EventType, Gilrs}; use gilrs::{EventType, Gilrs};
use rust_engine_proc::perf;
use serde_derive::{Deserialize, Serialize}; use serde_derive::{Deserialize, Serialize};
use toml; use toml;
use winit::event::{DeviceEvent, ElementState, Event, ModifiersState, MouseButton, MouseScrollDelta, ScanCode, WindowEvent}; use winapi::shared::minwindef::UINT;
use winapi::um::winuser;
use winit::event::{DeviceEvent, ElementState, Event, ModifiersState, MouseButton, MouseScrollDelta, ScanCode, Touch, TouchPhase, VirtualKeyCode, WindowEvent};
use crate::config::LogConfig; use crate::config::LogConfig;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct VirtualButton { pub struct VirtualButton {
pub digital_inputs: Vec<DigitalInput> pub digital_inputs: Vec<DigitalInput>,
pub touch_count: Option<u8>,
} }
#[derive(Debug)] #[derive(Debug)]
pub struct VirtualAxis { pub struct VirtualAxis {
pub axis_inputs: Vec<AxisInput> pub axis_inputs: Vec<AxisInput>,
} }
#[derive(Debug, PartialEq, Eq, Hash)] #[derive(Debug, PartialEq, Eq, Hash)]
@@ -38,13 +44,16 @@ struct InputConfig {
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
struct InputConfigConfig { struct InputConfigConfig {
line_height_px: f32, line_height_px: f32,
mouse_speed: f32,
} }
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
struct InputConfigButton { struct InputConfigButton {
name: String, name: String,
scan_code: Option<u32>, scan_code: Option<u32>,
vk_code: Option<String>,
mouse: Option<String>, mouse: Option<String>,
touch: Option<u8>,
controller_button: Option<String>, controller_button: Option<String>,
ctrl: Option<bool>, ctrl: Option<bool>,
shift: Option<bool>, shift: Option<bool>,
@@ -59,6 +68,7 @@ struct InputConfigAxis {
negative_button: Option<String>, negative_button: Option<String>,
mouse_axis: Option<String>, mouse_axis: Option<String>,
controller_axis: Option<String>, controller_axis: Option<String>,
touch_axis: Option<String>,
ctrl: Option<bool>, ctrl: Option<bool>,
shift: Option<bool>, shift: Option<bool>,
alt: Option<bool>, alt: Option<bool>,
@@ -71,14 +81,17 @@ pub struct InputState {
pub virtual_axes: HashMap<String, VirtualAxis>, pub virtual_axes: HashMap<String, VirtualAxis>,
pub mouse_delta_x: f64, pub mouse_delta_x: f64,
pub mouse_delta_y: f64, pub mouse_delta_y: f64,
pub mouse_position: [f64; 2], pub mouse_position: Vector2<f64>,
pub typed_characters: Vec<char>,
input_events: HashSet<DigitalInputEvent>, input_events: HashSet<DigitalInputEvent>,
pressed_scan_codes: HashSet<ScanCode>, pub pressed_scan_codes: HashSet<ScanCode>,
pressed_mouse_buttons: HashSet<MouseButton>, pub pressed_mouse_buttons: HashSet<MouseButton>,
pressed_touch_positions: HashMap<u64, Vector2<f64>>,
analog_wheel_state: f32, analog_wheel_state: f32,
config: InputConfigConfig, config: InputConfigConfig,
log_config: LogConfig, log_config: LogConfig,
controller_input: Gilrs, controller_input: Gilrs,
touch_inputs: Vec<TouchInput>,
} }
#[derive(Debug, Eq, PartialEq, Hash, Clone)] #[derive(Debug, Eq, PartialEq, Hash, Clone)]
@@ -106,10 +119,11 @@ impl InputState {
let mut state = InputState { virtual_buttons: HashMap::new(), virtual_axes: HashMap::new(), let mut state = InputState { virtual_buttons: HashMap::new(), virtual_axes: HashMap::new(),
input_events: HashSet::new(), pressed_scan_codes: HashSet::new(), input_events: HashSet::new(), pressed_scan_codes: HashSet::new(),
pressed_mouse_buttons: HashSet::new(), analog_wheel_state: 0.0, pressed_mouse_buttons: HashSet::new(), pressed_touch_positions: HashMap::new(), touch_inputs: vec![], analog_wheel_state: 0.0,
config: config.config, mouse_delta_x: 0.0, mouse_delta_y: 0.0, mouse_position: [0., 0.], log_config, config: config.config, mouse_delta_x: 0.0, mouse_delta_y: 0.0, mouse_position: Vector2::zero(), log_config,
controller_input: Gilrs::new().unwrap() }; controller_input: Gilrs::new().unwrap(), typed_characters: vec![] };
// Create virtual buttons from config
config.button.iter().for_each(|bn| { config.button.iter().for_each(|bn| {
let modifiers = KeyboardModifierState { let modifiers = KeyboardModifierState {
shift: bn.shift.is_some(), shift: bn.shift.is_some(),
@@ -117,33 +131,60 @@ impl InputState {
alt: bn.alt.is_some(), alt: bn.alt.is_some(),
logo: bn.logo.is_some() logo: bn.logo.is_some()
}; };
let input = if let Some(scan_code) = bn.scan_code {
DigitalInput::Keyboard(KeyboardInput { scan_code, modifiers }) let mut inputs = vec![];
} else if let Some(button_name) = &bn.mouse {
// Keyboard buttons
if let Some(scan_code) = bn.scan_code {
inputs.push(DigitalInput::Keyboard(KeyboardInput { scan_code, key_code: None, modifiers: modifiers.clone() }));
}
if let Some(vk_code) = &bn.vk_code {
if let Some(scan_code) = vk_to_scan_code(&vk_code) {
inputs.push(DigitalInput::Keyboard(KeyboardInput { scan_code, key_code: None, modifiers: modifiers.clone() }))
}
}
// Mouse buttons
if let Some(button_name) = &bn.mouse {
let button_name_lower = button_name.to_lowercase(); let button_name_lower = button_name.to_lowercase();
match button_name_lower.as_str() { let button_input = match button_name_lower.as_str() {
"left" => DigitalInput::Mouse(MouseInput { button: MouseButton::Left, modifiers }), "left" => DigitalInput::Mouse(MouseInput { button: MouseButton::Left, modifiers }),
"middle" => DigitalInput::Mouse(MouseInput { button: MouseButton::Middle, modifiers }), "middle" => DigitalInput::Mouse(MouseInput { button: MouseButton::Middle, modifiers }),
"right" => DigitalInput::Mouse(MouseInput { button: MouseButton::Right, modifiers }), "right" => DigitalInput::Mouse(MouseInput { button: MouseButton::Right, modifiers }),
"wheelup" => DigitalInput::Wheel(WheelInput { direction: WheelInputDirection::Up, modifiers }), "wheelup" => DigitalInput::Wheel(WheelInput { direction: WheelInputDirection::Up, modifiers }),
"wheeldown" => DigitalInput::Wheel(WheelInput { direction: WheelInputDirection::Down, modifiers }), "wheeldown" => DigitalInput::Wheel(WheelInput { direction: WheelInputDirection::Down, modifiers }),
other => DigitalInput::Mouse(MouseInput { button: MouseButton::Other(other.parse().expect(&format!("Unknown button: {:?}", other))), modifiers }), other => DigitalInput::Mouse(MouseInput { button: MouseButton::Other(other.parse().expect(&format!("Unknown button: {:?}", other))), modifiers }),
} };
} else if let Some(controller_button) = &bn.controller_button { inputs.push(button_input);
DigitalInput::Controller(ControllerInput { }
// Controller buttons
if let Some(controller_button) = &bn.controller_button {
inputs.push(DigitalInput::Controller(ControllerInput {
button: string_to_button(controller_button.as_str()).expect(&format!("Unknown controller button: {}", controller_button.as_str())) button: string_to_button(controller_button.as_str()).expect(&format!("Unknown controller button: {}", controller_button.as_str()))
}) }));
} else { }
panic!("No mouse or keyboard input for button {:?}", bn.name);
};
if let Some(virtual_button) = state.virtual_buttons.get_mut(&bn.name) { // Touch clicks
virtual_button.digital_inputs.push(input); if let Some(_) = bn.touch {
} else { if let Some(virtual_button) = state.virtual_buttons.get_mut(&bn.name) {
state.virtual_buttons.insert(bn.name.clone(), VirtualButton { digital_inputs: vec![input] }); virtual_button.touch_count = bn.touch;
} else {
state.virtual_buttons.insert(bn.name.clone(), VirtualButton { digital_inputs: vec![], touch_count: bn.touch });
}
}
// Insert digital inputs
for input in inputs {
if let Some(virtual_button) = state.virtual_buttons.get_mut(&bn.name) {
virtual_button.digital_inputs.push(input);
} else {
state.virtual_buttons.insert(bn.name.clone(), VirtualButton { digital_inputs: vec![input], touch_count: bn.touch });
}
} }
}); });
// Create virtual axes from config
config.axis.iter().for_each(|axis| { config.axis.iter().for_each(|axis| {
let axis_input = match axis { let axis_input = match axis {
InputConfigAxis { positive_button: Some(pos_button_name), negative_button: Some(neg_button_name), .. } => { InputConfigAxis { positive_button: Some(pos_button_name), negative_button: Some(neg_button_name), .. } => {
@@ -173,7 +214,14 @@ impl InputState {
InputConfigAxis { controller_axis: Some(controller_axis_name), .. } => { InputConfigAxis { controller_axis: Some(controller_axis_name), .. } => {
AxisInput::Controller(AnalogControllerInput { axis: string_to_axis(controller_axis_name).expect(&format!("Unknown controller axis: {}", controller_axis_name)) }) AxisInput::Controller(AnalogControllerInput { axis: string_to_axis(controller_axis_name).expect(&format!("Unknown controller axis: {}", controller_axis_name)) })
}, },
other => panic!("Axis {:?} needs either positive_button and negative_button or mouse_wheel or controller_axis must be set to true!", other.name) InputConfigAxis { touch_axis: Some(touch_axis_name), .. } => {
if let Some(axis) = string_to_touch_axis(touch_axis_name) {
AxisInput::Touch(axis)
} else {
panic!("Unknown touch axis {:?}", touch_axis_name);
}
},
other => panic!("Axis {:?} needs either (positive_button and negative_button) or (touch_axis) or (mouse_wheel) or (controller_axis must be set to true)", other.name)
}; };
if let Some(virtual_axis) = state.virtual_axes.get_mut(&axis.name) { if let Some(virtual_axis) = state.virtual_axes.get_mut(&axis.name) {
@@ -186,6 +234,7 @@ impl InputState {
return state; return state;
} }
#[perf("input_events", crate::perf::PerformanceCounter)]
pub fn on_window_event(self: &mut Self, event: &Event<()>) { pub fn on_window_event(self: &mut Self, event: &Event<()>) {
match event { match event {
Event::WindowEvent { event: WindowEvent::KeyboardInput { device_id, input, .. }, .. } => { Event::WindowEvent { event: WindowEvent::KeyboardInput { device_id, input, .. }, .. } => {
@@ -198,7 +247,7 @@ impl InputState {
} }
} }
self.on_keyboard_event(input.state, input.scancode, KeyboardModifierState::from_deprecated_state(&input.modifiers)); self.on_keyboard_event(input.state, input.scancode, input.virtual_keycode, KeyboardModifierState::from_deprecated_state(&input.modifiers));
}, },
Event::WindowEvent { event: WindowEvent::MouseInput { device_id, state, button, modifiers }, .. } => { Event::WindowEvent { event: WindowEvent::MouseInput { device_id, state, button, modifiers }, .. } => {
if self.log_config.input.buttons { if self.log_config.input.buttons {
@@ -232,15 +281,30 @@ impl InputState {
self.mouse_delta_y += *delta_y; self.mouse_delta_y += *delta_y;
}, },
Event::WindowEvent { event: WindowEvent::CursorMoved { position, .. }, .. } => { Event::WindowEvent { event: WindowEvent::CursorMoved { position, .. }, .. } => {
self.mouse_position = [position.x, position.y]; self.mouse_position = vec2(position.x, position.y);
},
Event::WindowEvent { event: WindowEvent::Touch(touch), .. } => {
if self.log_config.input.touch {
println!("Touch {:?}, at {:?}, id: {:?}, force: {:?}", touch.phase, touch.location, touch.id, touch.force);
}
self.on_touch_event(touch);
},
Event::WindowEvent { event: WindowEvent::ReceivedCharacter(c), .. } => {
self.typed_characters.push(*c);
}, },
_ => {} _ => {}
} }
} }
#[allow(dead_code)]
pub fn button_down(self: &Self, button_code: &str) -> bool { pub fn button_down(self: &Self, button_code: &str) -> bool {
match self.virtual_buttons.get(button_code) { match self.virtual_buttons.get(button_code) {
Some(virtual_button) => { Some(virtual_button) => {
if let Some(count) = virtual_button.touch_count {
if self.pressed_touch_positions.len() == count as usize {
return true;
}
}
virtual_button.digital_inputs.iter().any(|vi| self.digital_input_pressed(vi)) virtual_button.digital_inputs.iter().any(|vi| self.digital_input_pressed(vi))
} }
None => { None => {
@@ -252,9 +316,36 @@ impl InputState {
} }
} }
pub fn _scan_code_just_pressed(&self, scan_code: &ScanCode) -> bool {
self.input_events.iter().any(|ie| match ie {
DigitalInputEvent::Pressed(di) => match di {
DigitalInput::Keyboard(ki) => ki.scan_code == *scan_code,
_ => false,
},
_ => false,
})
}
pub fn _scan_code_just_released(&self, scan_code: &ScanCode) -> bool {
self.input_events.iter().any(|ie| match ie {
DigitalInputEvent::Released(di) => match di {
DigitalInput::Keyboard(ki) => ki.scan_code == *scan_code,
_ => false,
},
_ => false,
})
}
pub fn button_just_pressed(self: &Self, button_code: &str) -> bool { pub fn button_just_pressed(self: &Self, button_code: &str) -> bool {
match self.virtual_buttons.get(button_code) { match self.virtual_buttons.get(button_code) {
Some(virtual_button) => { Some(virtual_button) => {
if let Some(count) = virtual_button.touch_count {
if self.pressed_touch_positions.len() == count as usize
&& self.touch_inputs.iter().any(|ti| ti.phase == TouchPhase::Started) {
return true;
}
}
self.input_events.iter().any(|input_event| { self.input_events.iter().any(|input_event| {
if let DigitalInputEvent::Pressed(digital_input) = input_event { if let DigitalInputEvent::Pressed(digital_input) = input_event {
virtual_button.digital_inputs.iter().any(|virtual_button_input| virtual_button_input == digital_input) virtual_button.digital_inputs.iter().any(|virtual_button_input| virtual_button_input == digital_input)
@@ -275,6 +366,13 @@ impl InputState {
pub fn button_just_released(self: &Self, button_code: &str) -> bool { pub fn button_just_released(self: &Self, button_code: &str) -> bool {
match self.virtual_buttons.get(button_code) { match self.virtual_buttons.get(button_code) {
Some(virtual_button) => { Some(virtual_button) => {
if let Some(count) = virtual_button.touch_count {
if self.pressed_touch_positions.len() < count as usize
&& self.touch_inputs.iter().any(|ti| ti.phase == TouchPhase::Ended) {
return true;
}
}
self.input_events.iter().any(|input_event| { self.input_events.iter().any(|input_event| {
if let DigitalInputEvent::Released(digital_input) = input_event { if let DigitalInputEvent::Released(digital_input) = input_event {
virtual_button.digital_inputs.iter().any(|virtual_button_input| virtual_button_input == digital_input) virtual_button.digital_inputs.iter().any(|virtual_button_input| virtual_button_input == digital_input)
@@ -292,31 +390,112 @@ impl InputState {
} }
} }
pub fn get_axis(self: &Self, axis_code: &str) -> f32 { fn get_touch_drag_distance(&self) -> Vector2<f64> {
if let Some(old_pos) = self.pressed_touch_positions.values().next() {
if let Some(newest_input) = self.touch_inputs.iter().filter(|ti| ti.phase == TouchPhase::Started || ti.phase == TouchPhase::Moved).last() {
newest_input.touch_location - old_pos
} else {
vec2(0.0, 0.0)
}
} else {
vec2(0.0, 0.0)
}
}
pub fn get_axis_timescaled(self: &Self, axis_code: &str, delta_time: f32) -> f32 {
if let Some(axis) = self.virtual_axes.get(axis_code) { if let Some(axis) = self.virtual_axes.get(axis_code) {
axis.axis_inputs.iter().map(|item| { axis.axis_inputs.iter().map(|item| {
match item { match &item {
AxisInput::Wheel(modifiers) => { AxisInput::Wheel(modifiers) => {
if self.modifiers_are_pressed(modifiers) { self.analog_wheel_state } else { 0.0 } if self.modifiers_are_pressed(modifiers) { self.analog_wheel_state * delta_time } else { 0.0 }
}, },
AxisInput::MouseMove(direction, modifiers) => { AxisInput::MouseMove(direction, modifiers) => {
if self.modifiers_are_pressed(modifiers) { if self.modifiers_are_pressed(modifiers) {
match direction { match direction {
MouseMoveDirection::X => self.mouse_delta_x as f32, MouseMoveDirection::X => self.mouse_delta_x as f32 * self.config.mouse_speed,
MouseMoveDirection::Y => self.mouse_delta_y as f32, MouseMoveDirection::Y => self.mouse_delta_y as f32 * self.config.mouse_speed,
} }
} else { } else {
0.0 0.0
} }
}, },
AxisInput::Digital(positive_button, negative_button) => { AxisInput::Digital(positive_button, negative_button) => {
self.virtual_button_to_float(positive_button) - self.virtual_button_to_float(negative_button) (self.virtual_button_to_float(positive_button) - self.virtual_button_to_float(negative_button)) * delta_time
}, },
AxisInput::Controller(controller_input) => { AxisInput::Controller(controller_input) => {
self.controller_input.gamepads() self.controller_input.gamepads()
.map(|(_id, gamepad)| gamepad.value(controller_input.axis)) .map(|(_id, gamepad)| gamepad.value(controller_input.axis))
.fold(0.0, fold_axis_value) .fold(0.0, fold_axis_value) * delta_time
} },
&AxisInput::Touch(touch_axis) => {
match touch_axis {
TouchAxis::Horizontal => {
if self.pressed_touch_positions.len() == 1 {
self.get_touch_drag_distance().x as f32
} else {
0.0
}
},
TouchAxis::Vertical => {
if self.pressed_touch_positions.len() == 1 {
self.get_touch_drag_distance().y as f32
} else {
0.0
}
},
TouchAxis::Rotate => {
if self.pressed_touch_positions.len() == 2 {
let mut positions = self.pressed_touch_positions.iter();
let (id_1, pos_1) = positions.next().unwrap();
let (id_2, pos_2) = positions.next().unwrap();
let mut touch_loc_1 = vec2(0., 0.);
let mut touch_loc_2 = vec2(0., 0.);
let mut touch_1 = None;
let mut touch_2 = None;
fn filtered_vector_sub(a: Vector2<f64>, b: &Vector2<f64>) -> Option<Vector2<f64>> {
Some(a - b).filter(|v| v.magnitude() > 0.01)
}
for input in &self.touch_inputs {
if input.id == *id_1 {
touch_1 = filtered_vector_sub(input.touch_location, &pos_1);
touch_loc_1 = input.touch_location;
}
if input.id == *id_2 {
touch_2 = filtered_vector_sub(input.touch_location, &pos_2);
touch_loc_2 = input.touch_location;
}
}
let diff = touch_loc_2 - touch_loc_1;
let norm_n = vec2(-diff.y, diff.x).normalize();
match (touch_1, touch_2) {
(Some(v1), Some(v2)) => {
let v1_n = v1.normalize();
let v2_n = v2.normalize();
let direction = v1_n.dot(norm_n) - v2_n.dot(norm_n);
let distance = (v1.magnitude() + v2.magnitude()) / 2.;
(direction * distance) as f32
},
(Some(v1), None) => {
let direction = v1.normalize().dot(norm_n);
(direction * v1.magnitude()) as f32
},
(None, Some(v2)) => {
let direction = v2.normalize().dot(norm_n);
(-direction * v2.magnitude()) as f32
},
(None, None) => { 0.0 }
}
} else {
0.0
}
},
}
},
} }
}).fold(0.0, fold_axis_value) }).fold(0.0, fold_axis_value)
} else { } else {
@@ -357,8 +536,8 @@ impl InputState {
if input.digital_inputs.iter().any(|di| self.digital_input_pressed(di)) { 1.0 } else { 0.0 } if input.digital_inputs.iter().any(|di| self.digital_input_pressed(di)) { 1.0 } else { 0.0 }
} }
pub fn on_keyboard_event(self: &mut Self, state: ElementState, scan_code: ScanCode, modifiers: KeyboardModifierState) { pub fn on_keyboard_event(self: &mut Self, state: ElementState, scan_code: ScanCode, key_code: Option<VirtualKeyCode>, modifiers: KeyboardModifierState) {
let input = DigitalInput::Keyboard(KeyboardInput { scan_code, modifiers }); let input = DigitalInput::Keyboard(KeyboardInput { scan_code, key_code, modifiers });
match state { match state {
ElementState::Pressed => { ElementState::Pressed => {
if self.pressed_scan_codes.contains(&scan_code) { return; } if self.pressed_scan_codes.contains(&scan_code) { return; }
@@ -412,7 +591,28 @@ impl InputState {
}; };
} }
pub fn on_touch_event(&mut self, event: &Touch) {
self.mouse_position = vec2(event.location.x, event.location.y);
match event.phase {
winit::event::TouchPhase::Started => {
self.pressed_touch_positions.insert(event.id, self.mouse_position);
self.touch_inputs.push(TouchInput { id: event.id, touch_location: self.mouse_position, phase: event.phase });
},
winit::event::TouchPhase::Moved => {
self.touch_inputs.push(TouchInput { id: event.id, touch_location: self.mouse_position, phase: event.phase });
},
winit::event::TouchPhase::Ended => {
self.pressed_touch_positions.remove(&event.id);
self.touch_inputs.push(TouchInput { id: event.id, touch_location: self.mouse_position, phase: event.phase });
},
winit::event::TouchPhase::Cancelled => {
self.pressed_touch_positions.remove(&event.id);
},
}
}
pub fn frame_start(self: &mut Self) { pub fn frame_start(self: &mut Self) {
// Read out all controller events
while let Some(event) = self.controller_input.next_event() { while let Some(event) = self.controller_input.next_event() {
match event.event { match event.event {
EventType::ButtonPressed(button, _) => { EventType::ButtonPressed(button, _) => {
@@ -440,6 +640,13 @@ impl InputState {
self.mouse_delta_x = 0.0; self.mouse_delta_x = 0.0;
self.mouse_delta_y = 0.0; self.mouse_delta_y = 0.0;
self.input_events.clear(); self.input_events.clear();
self.typed_characters.clear();
// Store final touch positions as base for next frame
for touch_input in &mut self.touch_inputs {
self.pressed_touch_positions.get_mut(&touch_input.id).map(|pos| *pos = touch_input.touch_location);
}
self.touch_inputs.clear();
} }
} }
@@ -457,9 +664,10 @@ pub enum AxisInput {
MouseMove(MouseMoveDirection, KeyboardModifierState), MouseMove(MouseMoveDirection, KeyboardModifierState),
Digital(VirtualButton, VirtualButton), Digital(VirtualButton, VirtualButton),
Controller(AnalogControllerInput), Controller(AnalogControllerInput),
Touch(TouchAxis)
} }
#[derive(Debug, Eq, PartialEq, Hash, Clone)] #[derive(Debug, Eq, PartialEq, Clone, Hash)]
pub enum DigitalInput { pub enum DigitalInput {
Keyboard(KeyboardInput), Keyboard(KeyboardInput),
Wheel(WheelInput), Wheel(WheelInput),
@@ -467,12 +675,26 @@ pub enum DigitalInput {
Controller(ControllerInput), Controller(ControllerInput),
} }
#[derive(Debug, Eq, PartialEq, Hash, Clone)] #[derive(Debug, Eq, Clone)]
pub struct KeyboardInput { pub struct KeyboardInput {
scan_code: ScanCode, scan_code: ScanCode,
key_code: Option<VirtualKeyCode>,
modifiers: KeyboardModifierState, modifiers: KeyboardModifierState,
} }
impl PartialEq for KeyboardInput {
fn eq(&self, other: &Self) -> bool {
(self.scan_code == other.scan_code || (self.key_code != None && self.key_code == other.key_code)) && self.modifiers == other.modifiers
}
}
impl Hash for KeyboardInput {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.scan_code.hash(state);
self.modifiers.hash(state);
}
}
#[derive(Debug, Eq, PartialEq, Hash, Clone)] #[derive(Debug, Eq, PartialEq, Hash, Clone)]
pub struct MouseInput { pub struct MouseInput {
button: MouseButton, button: MouseButton,
@@ -490,6 +712,35 @@ pub struct ControllerInput {
button: gilrs::Button, button: gilrs::Button,
} }
#[derive(Debug, Clone)]
pub struct TouchInput {
id: u64,
touch_location: Vector2<f64>,
phase: TouchPhase
}
impl Eq for TouchInput {}
impl PartialEq for TouchInput {
fn eq(&self, other: &Self) -> bool {
self.id.eq(&other.id) && self.phase.eq(&other.phase)
}
}
impl Hash for TouchInput {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
state.write_u64(self.id);
self.phase.hash(state)
}
}
#[derive(Debug, Eq, PartialEq, Hash, Clone)]
pub enum TouchAxis {
Horizontal,
Vertical,
Rotate
}
#[derive(Debug, Eq, PartialEq, Hash, Clone)] #[derive(Debug, Eq, PartialEq, Hash, Clone)]
pub enum WheelInputDirection { pub enum WheelInputDirection {
Up, Up,
@@ -559,4 +810,301 @@ fn string_to_axis(axis_name: &str) -> Option<gilrs::Axis> {
"DPadY" => Some(gilrs::Axis::DPadY), "DPadY" => Some(gilrs::Axis::DPadY),
_ => None, _ => None,
} }
}
fn string_to_touch_axis(touch_axis_name: &str) -> Option<TouchAxis> {
match touch_axis_name {
"horizontal" => Some(TouchAxis::Horizontal),
"vertical" => Some(TouchAxis::Vertical),
"rotate" => Some(TouchAxis::Rotate),
_ => None,
}
}
// https://searchfox.org/mozilla-central/source/widget/windows/KeyboardLayout.cpp
const VIRTUAL_KEY_CODES: [&str; 256] = [
"NULL",
"VK_LBUTTON",
"VK_RBUTTON",
"VK_CANCEL",
"VK_MBUTTON",
"VK_XBUTTON1",
"VK_XBUTTON2",
"0x07",
"VK_BACK",
"VK_TAB",
"0x0A",
"0x0B",
"VK_CLEAR",
"VK_RETURN",
"0x0E",
"0x0F",
"VK_SHIFT",
"VK_CONTROL",
"VK_MENU",
"VK_PAUSE",
"VK_CAPITAL",
"VK_KANA, VK_HANGUL",
"0x16",
"VK_JUNJA",
"VK_FINAL",
"VK_HANJA, VK_KANJI",
"0x1A",
"VK_ESCAPE",
"VK_CONVERT",
"VK_NONCONVERT",
"VK_ACCEPT",
"VK_MODECHANGE",
"VK_SPACE",
"VK_PRIOR",
"VK_NEXT",
"VK_END",
"VK_HOME",
"VK_LEFT",
"VK_UP",
"VK_RIGHT",
"VK_DOWN",
"VK_SELECT",
"VK_PRINT",
"VK_EXECUTE",
"VK_SNAPSHOT",
"VK_INSERT",
"VK_DELETE",
"VK_HELP",
"VK_0",
"VK_1",
"VK_2",
"VK_3",
"VK_4",
"VK_5",
"VK_6",
"VK_7",
"VK_8",
"VK_9",
"0x3A",
"0x3B",
"0x3C",
"0x3D",
"0x3E",
"0x3F",
"0x40",
"VK_A",
"VK_B",
"VK_C",
"VK_D",
"VK_E",
"VK_F",
"VK_G",
"VK_H",
"VK_I",
"VK_J",
"VK_K",
"VK_L",
"VK_M",
"VK_N",
"VK_O",
"VK_P",
"VK_Q",
"VK_R",
"VK_S",
"VK_T",
"VK_U",
"VK_V",
"VK_W",
"VK_X",
"VK_Y",
"VK_Z",
"VK_LWIN",
"VK_RWIN",
"VK_APPS",
"0x5E",
"VK_SLEEP",
"VK_NUMPAD0",
"VK_NUMPAD1",
"VK_NUMPAD2",
"VK_NUMPAD3",
"VK_NUMPAD4",
"VK_NUMPAD5",
"VK_NUMPAD6",
"VK_NUMPAD7",
"VK_NUMPAD8",
"VK_NUMPAD9",
"VK_MULTIPLY",
"VK_ADD",
"VK_SEPARATOR",
"VK_SUBTRACT",
"VK_DECIMAL",
"VK_DIVIDE",
"VK_F1",
"VK_F2",
"VK_F3",
"VK_F4",
"VK_F5",
"VK_F6",
"VK_F7",
"VK_F8",
"VK_F9",
"VK_F10",
"VK_F11",
"VK_F12",
"VK_F13",
"VK_F14",
"VK_F15",
"VK_F16",
"VK_F17",
"VK_F18",
"VK_F19",
"VK_F20",
"VK_F21",
"VK_F22",
"VK_F23",
"VK_F24",
"0x88",
"0x89",
"0x8A",
"0x8B",
"0x8C",
"0x8D",
"0x8E",
"0x8F",
"VK_NUMLOCK",
"VK_SCROLL",
"VK_OEM_NEC_EQUAL, VK_OEM_FJ_JISHO",
"VK_OEM_FJ_MASSHOU",
"VK_OEM_FJ_TOUROKU",
"VK_OEM_FJ_LOYA",
"VK_OEM_FJ_ROYA",
"0x97",
"0x98",
"0x99",
"0x9A",
"0x9B",
"0x9C",
"0x9D",
"0x9E",
"0x9F",
"VK_LSHIFT",
"VK_RSHIFT",
"VK_LCONTROL",
"VK_RCONTROL",
"VK_LMENU",
"VK_RMENU",
"VK_BROWSER_BACK",
"VK_BROWSER_FORWARD",
"VK_BROWSER_REFRESH",
"VK_BROWSER_STOP",
"VK_BROWSER_SEARCH",
"VK_BROWSER_FAVORITES",
"VK_BROWSER_HOME",
"VK_VOLUME_MUTE",
"VK_VOLUME_DOWN",
"VK_VOLUME_UP",
"VK_MEDIA_NEXT_TRACK",
"VK_MEDIA_PREV_TRACK",
"VK_MEDIA_STOP",
"VK_MEDIA_PLAY_PAUSE",
"VK_LAUNCH_MAIL",
"VK_LAUNCH_MEDIA_SELECT",
"VK_LAUNCH_APP1",
"VK_LAUNCH_APP2",
"0xB8",
"0xB9",
"VK_OEM_1",
"VK_OEM_PLUS",
"VK_OEM_COMMA",
"VK_OEM_MINUS",
"VK_OEM_PERIOD",
"VK_OEM_2",
"VK_OEM_3",
"VK_ABNT_C1",
"VK_ABNT_C2",
"0xC3",
"0xC4",
"0xC5",
"0xC6",
"0xC7",
"0xC8",
"0xC9",
"0xCA",
"0xCB",
"0xCC",
"0xCD",
"0xCE",
"0xCF",
"0xD0",
"0xD1",
"0xD2",
"0xD3",
"0xD4",
"0xD5",
"0xD6",
"0xD7",
"0xD8",
"0xD9",
"0xDA",
"VK_OEM_4",
"VK_OEM_5",
"VK_OEM_6",
"VK_OEM_7",
"VK_OEM_8",
"0xE0",
"VK_OEM_AX",
"VK_OEM_102",
"VK_ICO_HELP",
"VK_ICO_00",
"VK_PROCESSKEY",
"VK_ICO_CLEAR",
"VK_PACKET",
"0xE8",
"VK_OEM_RESET",
"VK_OEM_JUMP",
"VK_OEM_PA1",
"VK_OEM_PA2",
"VK_OEM_PA3",
"VK_OEM_WSCTRL",
"VK_OEM_CUSEL",
"VK_OEM_ATTN",
"VK_OEM_FINISH",
"VK_OEM_COPY",
"VK_OEM_AUTO",
"VK_OEM_ENLW",
"VK_OEM_BACKTAB",
"VK_ATTN",
"VK_CRSEL",
"VK_EXSEL",
"VK_EREOF",
"VK_PLAY",
"VK_ZOOM",
"VK_NONAME",
"VK_PA1",
"VK_OEM_CLEAR",
"0xFF"
];
pub fn vk_to_scan_code(name: &str) -> Option<ScanCode> {
let index = VIRTUAL_KEY_CODES.iter().position(|c| c == &name);
if let Some(idx) = index {
unsafe {
let vc = winuser::MapVirtualKeyA(idx as UINT, winuser::MAPVK_VK_TO_VSC);
if vc == 0 { return None }
return Some(vc as ScanCode);
}
}
None
} }

View File

@@ -4,33 +4,25 @@ use vulkan::gameobject::GameObject;
use crate::config::{LogConfig, RenderConfig}; use crate::config::{LogConfig, RenderConfig};
use crate::game::TestGame; use crate::game::TestGame;
use crate::vulkan::{LinePoint, VulkanRenderer}; use crate::vulkan::VulkanRenderer;
mod vulkan; mod vulkan;
mod input; mod input;
mod config; mod config;
mod game; mod game;
mod tests; mod tests;
mod text;
mod util;
mod perf;
fn main() { fn main() {
let log_config = LogConfig::from_file("config/log.toml"); let log_config = LogConfig::from_file("config/log.toml");
let mut game = TestGame::new("config/input.toml", log_config); let mut game = TestGame::new("config/input.toml", log_config);
let line_count = 30;
let line_vertices = (-line_count..=line_count)
.flat_map(|it| vec![
LinePoint { position: [it as f32, 0., -line_count as f32] },
LinePoint { position: [it as f32, 0., line_count as f32] },
LinePoint { position: [-line_count as f32, 0., it as f32] },
LinePoint { position: [line_count as f32, 0., it as f32] },
]).collect();
let (mut renderer, event_loop) = VulkanRenderer::init( let (mut renderer, event_loop) = VulkanRenderer::init(
line_vertices,
log_config.vulkan_validation_layers, log_config.vulkan_validation_layers,
RenderConfig::from_file("config/graphics.toml"), RenderConfig::from_file("config/graphics.toml")
log_config.clone()
); );
game.game_start(&mut renderer); game.game_start(&mut renderer);

64
src/perf.rs Normal file
View File

@@ -0,0 +1,64 @@
use std::collections::HashMap;
pub const PERF_COUNTER_SIZE: usize = 100;
pub static mut PERF_COUNTERS: Option<HashMap<String, PerformanceCounter>> = None;
pub struct PerformanceCounter {
pub values: [u128; PERF_COUNTER_SIZE],
pub index: usize,
}
impl PerformanceCounter {
pub fn mean(&self) -> u128 {
self.values.iter().sum::<u128>() / PERF_COUNTER_SIZE as u128
}
pub fn stddev(&self, mean: i128) -> f64 {
f64::sqrt(self.values.iter().fold(0, |acc, b| acc + (*b as i128 - mean) * (*b as i128 - mean)) as f64 / PERF_COUNTER_SIZE as f64)
}
pub fn init_perf() {
unsafe {
if PERF_COUNTERS.is_none() { PERF_COUNTERS = Some(HashMap::new()); }
}
}
pub fn write_perf(name: &str, value: u128) {
unsafe {
if let Some(pcs) = &mut PERF_COUNTERS {
if let Some(pc) = pcs.get_mut(name) {
pc.values[pc.index] = value;
} else {
let mut pc = PerformanceCounter { values: [0; PERF_COUNTER_SIZE], index: 0 };
pc.values[0] = value;
pcs.insert(name.to_string(), pc);
}
}
}
}
pub fn _get_perf(name: &str) -> u128 {
unsafe {
if let Some(pcs) = &mut PERF_COUNTERS {
if let Some(pc) = pcs.get_mut(name) {
pc.mean()
} else {
0
}
} else {
0
}
}
}
pub fn perf_next_frame(name: &str) {
unsafe {
if let Some(pcs) = &mut PERF_COUNTERS {
if let Some(pc) = pcs.get_mut(name) {
pc.index = (pc.index + 1) % PERF_COUNTER_SIZE;
pc.values[pc.index] = 0;
}
}
}
}
}

View File

@@ -1,8 +1,11 @@
#[cfg(test)] #[cfg(test)]
#[allow(deprecated)]
mod tests { mod tests {
use cgmath::{vec3}; use cgmath::vec3;
use winit::{dpi::PhysicalPosition, event::{DeviceId, ElementState, Event, Force, KeyboardInput, ModifiersState, Touch, TouchPhase, WindowEvent}, window::WindowId};
use crate::game::player::{intersect_triangle}; use crate::{config::LogConfig, input::InputState, util::intersect_triangle};
use crate::input::vk_to_scan_code;
fn epsilon_eq(f1: f32, f2: f32) { fn epsilon_eq(f1: f32, f2: f32) {
assert!(f32::abs(f1 - f2) < f32::EPSILON, "{} == {}", f1, f2); assert!(f32::abs(f1 - f2) < f32::EPSILON, "{} == {}", f1, f2);
@@ -36,4 +39,119 @@ mod tests {
let dist3 = intersect_triangle(zero, vec3(0.9950371902, 0.09950371902, 0.0), a, b, c); let dist3 = intersect_triangle(zero, vec3(0.9950371902, 0.09950371902, 0.0), a, b, c);
epsilon_eq_option(dist3, Some(2.0099751242)); epsilon_eq_option(dist3, Some(2.0099751242));
} }
fn create_test_input_state() -> InputState {
InputState::new("config/testinput.toml", LogConfig::from_file("config/log.toml"))
}
#[test]
fn key_test() {
let mut state = create_test_input_state();
state.frame_start();
state.frame_end();
unsafe {
state.on_window_event(&Event::WindowEvent{ window_id: WindowId::dummy(), event: WindowEvent::KeyboardInput {
device_id: DeviceId::dummy(),
input: KeyboardInput { scancode: 1, state: ElementState::Pressed, virtual_keycode: None, modifiers: ModifiersState::empty() },
is_synthetic: true,
}});
}
state.frame_start();
assert_eq!(state.button_just_pressed("quit"), true);
assert_eq!(state.button_down("quit"), true);
state.frame_end();
state.frame_start();
assert_eq!(state.button_just_pressed("quit"), false);
assert_eq!(state.button_down("quit"), true);
state.frame_end();
unsafe {
state.on_window_event(&Event::WindowEvent{ window_id: WindowId::dummy(), event: WindowEvent::KeyboardInput {
device_id: DeviceId::dummy(),
input: KeyboardInput { scancode: 1, state: ElementState::Released, virtual_keycode: None, modifiers: ModifiersState::empty() },
is_synthetic: true,
}});
}
state.frame_start();
assert_eq!(state.button_down("quit"), false);
state.frame_end();
unsafe {
state.on_window_event(&Event::WindowEvent{ window_id: WindowId::dummy(), event: WindowEvent::KeyboardInput {
device_id: DeviceId::dummy(),
input: KeyboardInput { scancode: 32, state: ElementState::Pressed, virtual_keycode: None, modifiers: ModifiersState::empty() },
is_synthetic: true,
}});
}
state.frame_start();
assert_eq!(state.button_down("button_right"), true);
}
fn touch_event(state: &mut InputState, phase: TouchPhase, id: u64) {
unsafe {
state.on_window_event(&Event::WindowEvent{ window_id: WindowId::dummy(), event: WindowEvent::Touch(Touch {
device_id: DeviceId::dummy(), phase, location: PhysicalPosition::new(0., 0.), force: Some(Force::Normalized(0.5)), id
})});
}
}
#[test]
fn touch_test() {
let mut state = create_test_input_state();
state.frame_start();
state.frame_end();
touch_event(&mut state, TouchPhase::Started, 1);
state.frame_start();
assert_eq!(state.button_just_pressed("touchclick"), true);
assert_eq!(state.button_down("touchclick"), true);
state.frame_end();
touch_event(&mut state, TouchPhase::Moved, 1);
state.frame_start();
assert_eq!(state.button_just_pressed("touchclick"), false);
assert_eq!(state.button_down("touchclick"), true);
state.frame_end();
touch_event(&mut state, TouchPhase::Ended, 1);
state.frame_start();
assert_eq!(state.button_just_released("touchclick"), true);
assert_eq!(state.button_down("touchclick"), false);
state.frame_end();
}
#[test]
fn multi_touch_test() {
let mut state = create_test_input_state();
state.frame_start();
state.frame_end();
touch_event(&mut state, TouchPhase::Started, 2);
// TODO: add tolerance for delay
touch_event(&mut state, TouchPhase::Started, 3);
state.frame_start();
assert_eq!(state.button_just_pressed("touchclick"), false);
assert_eq!(state.button_down("touchclick"), false);
assert_eq!(state.button_just_pressed("doubletouchclick"), true);
assert_eq!(state.button_down("doubletouchclick"), true);
state.frame_end();
touch_event(&mut state, TouchPhase::Moved, 2);
state.frame_start();
assert_eq!(state.button_just_pressed("doubletouchclick"), false);
assert_eq!(state.button_down("doubletouchclick"), true);
state.frame_end();
touch_event(&mut state, TouchPhase::Ended, 2);
state.frame_start();
assert_eq!(state.button_just_released("doubletouchclick"), true);
assert_eq!(state.button_down("doubletouchclick"), false);
assert_eq!(state.button_just_pressed("touchclick"), false);
// TODO: don't enable button_down on release of double touch
// assert_eq!(state.button_down("touchclick"), false);
state.frame_end();
touch_event(&mut state, TouchPhase::Ended, 3);
state.frame_start();
// TODO: only set button_just_released for one frame
// assert_eq!(state.button_just_released("doubletouchclick"), false);
assert_eq!(state.button_down("doubletouchclick"), false);
}
#[test]
fn vk_input_name_test() {
let vk = "VK_ESCAPE";
assert_eq!(vk_to_scan_code(vk), Some(1));
}
} }

120
src/text.rs Normal file
View File

@@ -0,0 +1,120 @@
use glyph_brush::{BrushAction, BrushError, GlyphBrush, GlyphBrushBuilder, GlyphVertex, Rectangle, Section, Text, ab_glyph::FontArc};
use vulkano::{format::Format, image::ImageDimensions, sampler::{Filter, SamplerAddressMode}};
use crate::vulkan::{MeshHandle, TextVertex, TextureHandle, VulkanRenderer, gameobject::{GameObject, GameObjectHandle}, mesh::{CPUMesh, CPUVertexList}};
pub fn update_text(game_object_handle: GameObjectHandle, new_text: &str, new_size: f32, renderer: &mut VulkanRenderer, brush: &mut GlyphBrush<Vec<TextVertex>>, game_objects: &mut Vec<GameObject>) {
brush.queue(Section::default()
.add_text(Text::new(new_text).with_scale(new_size))
.with_bounds((renderer.game_data.dimensions[0] as f32, renderer.game_data.dimensions[1] as f32)));
let go = &mut game_objects[game_object_handle];
let mesh_index = go.mesh_index;
match brush.process_queued(|rect, text_data| {
debug_assert!(go.textures.len() == 1);
update_text_texture(Some(go.textures[0]), renderer, rect, text_data);
}, convert_vertices) {
Ok(BrushAction::Draw(quads)) => {
update_text_quads(quads, 420, Some(mesh_index), renderer);
},
Ok(BrushAction::ReDraw) => {},
Err(BrushError::TextureTooSmall { suggested }) => {
let size = ImageDimensions::Dim2d { width: suggested.0, height: suggested.1, array_layers: 1 };
debug_assert!(go.textures.len() == 1);
renderer.resize_texture(go, go.textures[0], size);
brush.resize_texture(suggested.0, suggested.1);
update_text(game_object_handle, new_text, new_size, renderer, brush, game_objects);
},
}
}
pub fn create_brush<T>() -> GlyphBrush<T> {
let font = FontArc::try_from_slice(include_bytes!("../models/FiraCode-Regular.ttf")).unwrap();
GlyphBrushBuilder::using_font(font).build()
}
pub fn create_text_object(brush: &mut GlyphBrush<Vec<TextVertex>>, renderer: &mut VulkanRenderer, text: &str, size: f32) -> MeshHandle {
let mut uploaded_texture = None;
let mut uploaded_mesh = None;
brush.queue(Section::default()
.add_text(Text::new(text).with_scale(size))
.with_bounds((renderer.game_data.dimensions[0] as f32, renderer.game_data.dimensions[1] as f32))
);
match brush.process_queued(|rect, text_data| {
uploaded_texture = update_text_texture(None, renderer, rect, text_data);
}, convert_vertices) {
Ok(BrushAction::Draw(quads)) => {
let t = if let Some(tex) = uploaded_texture {
tex
} else {
let brush_size = brush.texture_dimensions();
update_text_texture(None, renderer, Rectangle { min: [0, 0], max: [brush_size.0, brush_size.1] }, &[]).unwrap()
};
uploaded_mesh = update_text_quads(quads, t, None, renderer);
},
Ok(BrushAction::ReDraw) => {},
Err(BrushError::TextureTooSmall { suggested }) => {
brush.resize_texture(suggested.0, suggested.1);
},
};
uploaded_mesh.unwrap()
}
pub fn update_text_texture(old_texture: Option<TextureHandle>, renderer: &mut VulkanRenderer, rect: Rectangle<u32>, text_data: &[u8]) -> Option<TextureHandle> {
let size = u32::max(rect.width(), rect.height());
if let Some(tex_handle) = old_texture {
renderer.update_texture(tex_handle, text_data, [rect.width(), rect.height(), 1], [rect.min[0], rect.min[1], 0], renderer.device.clone());
None
} else {
let tex = renderer.upload_texture(text_data, size, size, Format::R8_UNORM, Filter::Nearest, SamplerAddressMode::ClampToEdge, renderer.device.clone());
renderer.game_data.textures.push(tex.clone());
Some(renderer.game_data.textures.len() - 1)
}
}
pub fn update_text_quads(quads: Vec<Vec<TextVertex>>, texture_index: usize, mesh_index: Option<usize>, renderer: &mut VulkanRenderer) -> Option<MeshHandle> {
let mut final_vertices = vec![];
let mut final_indices: Vec<u32> = vec![];
let mut index_offset = 0;
for mut quad in quads {
let len = quad.len();
final_vertices.append(&mut quad);
final_indices.append(&mut [0, 2, 3, 0, 3, 1].iter().map(|x| *x + index_offset).collect());
index_offset += len as u32;
}
if let Some(idx) = mesh_index {
renderer.update_mesh(idx, CPUVertexList::VertexText(final_vertices), final_indices);
None
} else {
let mesh = CPUMesh {
vertices: CPUVertexList::VertexText(final_vertices),
indices: final_indices,
local_texture_index: Some(texture_index),
local_normal_map_index: None,
name: Some("font_texture".to_string()),
};
let mesh_index = renderer.upload_mesh(mesh, None);
Some(MeshHandle {
index: mesh_index,
textures: vec![texture_index],
original_path: None,
pipeline_index: 1
})
}
}
fn convert_vertices(vertex_data: GlyphVertex) -> Vec<TextVertex> {
let result = vec![
TextVertex { position: [vertex_data.pixel_coords.min.x, vertex_data.pixel_coords.min.y, 0.], uv: [vertex_data.tex_coords.min.x, vertex_data.tex_coords.min.y] },
TextVertex { position: [vertex_data.pixel_coords.min.x, vertex_data.pixel_coords.max.y, 0.], uv: [vertex_data.tex_coords.min.x, vertex_data.tex_coords.max.y] },
TextVertex { position: [vertex_data.pixel_coords.max.x, vertex_data.pixel_coords.min.y, 0.], uv: [vertex_data.tex_coords.max.x, vertex_data.tex_coords.min.y] },
TextVertex { position: [vertex_data.pixel_coords.max.x, vertex_data.pixel_coords.max.y, 0.], uv: [vertex_data.tex_coords.max.x, vertex_data.tex_coords.max.y] },
];
result
}

81
src/util.rs Normal file
View File

@@ -0,0 +1,81 @@
use cgmath::{InnerSpace, Matrix4, SquareMatrix, Vector3, Vector4, vec3, vec4};
use vulkano::buffer::TypedBufferAccess;
use crate::vulkan::{Mesh, gameobject::GameObject};
#[allow(dead_code)]
pub fn print_matrix(mat: Matrix4<f32>) {
let cols = [
[mat.x.x, mat.x.y, mat.x.z, mat.x.w],
[mat.y.x, mat.y.y, mat.y.z, mat.y.w],
[mat.z.x, mat.z.y, mat.z.z, mat.z.w],
[mat.w.x, mat.w.y, mat.w.z, mat.w.w],
].map(|v| v.map(|e| format!("{:.2}", e)));
let col_sizes: Vec<usize> = cols.iter().map(|col| col.iter().map(|s| s.len()).max().unwrap()).collect();
println!("Mat4: {}", mat.determinant());
for row_index in 0..4 {
for col_index in 0..4 {
let mut str = format!("{}", cols[col_index][row_index]);
while str.len() < col_sizes[col_index] {
str.insert(0, ' ');
}
print!("{}", str);
if col_index < 3 { print!(", "); }
}
println!();
}
}
pub fn intersection_distance(ray_origin: Vector3<f32>, ray_direction: Vector3<f32>, mesh: &Mesh<crate::vulkan::Vertex>, game_object: &GameObject) -> Option<f32> {
let index_lock = mesh.index_buffer.read().unwrap();
let vertex_lock = mesh.vertex_buffer.read().unwrap();
(0..mesh.index_buffer.len() / 3).map(|tri_idx| {
let vtx_a = game_object.get_model_matrix() * vec4_from_pos(vertex_lock[index_lock[tri_idx as usize * 3 ] as usize].position);
let vtx_b = game_object.get_model_matrix() * vec4_from_pos(vertex_lock[index_lock[tri_idx as usize * 3 + 1] as usize].position);
let vtx_c = game_object.get_model_matrix() * vec4_from_pos(vertex_lock[index_lock[tri_idx as usize * 3 + 2] as usize].position);
intersect_triangle(ray_origin, ray_direction, vtx_a.truncate().into(), vtx_b.truncate().into(), vtx_c.truncate().into())
}).fold(None, |acc, x| {
if let Some(smallest) = acc {
if let Some(new) = x {
if new < smallest {
Some(new)
} else {
Some(smallest)
}
} else {
acc
}
} else {
x
}
})
}
// https://en.wikipedia.org/wiki/M%C3%B6ller%E2%80%93Trumbore_intersection_algorithm
pub fn intersect_triangle(ray_origin: Vector3<f32>, ray_direction: Vector3<f32>, vtx_a: [f32; 3], vtx_b: [f32; 3], vtx_c: [f32; 3]) -> Option<f32> {
let edge_1 = vec3(vtx_b[0] - vtx_a[0], vtx_b[1] - vtx_a[1], vtx_b[2] - vtx_a[2]);
let edge_2 = vec3(vtx_c[0] - vtx_a[0], vtx_c[1] - vtx_a[1], vtx_c[2] - vtx_a[2]);
let h = ray_direction.cross(edge_2);
let a = edge_1.dot(h);
if a > -f32::EPSILON && a < f32::EPSILON { return None; }
let f = 1. / a;
let s: Vector3<f32> = ray_origin - Vector3::from(vtx_a);
let u = f * s.dot(h);
if u < 0. || u > 1. { return None; }
let q = s.cross(edge_1);
let v = f * ray_direction.dot(q);
if v < 0. || u + v > 1. { return None; }
let t = f * edge_2.dot(q);
if t > f32::EPSILON { return Some(t); }
None
}
pub fn vec4_from_pos(pos: [f32; 3]) -> Vector4<f32> {
vec4(pos[0], pos[1], pos[2], 1.0)
}

View File

@@ -1,10 +1,10 @@
use std::{convert::TryInto, io::Read}; use std::{convert::TryInto, io::Read};
use vulkano::format::Format; use vulkano::{format::Format, sampler::{Filter, SamplerAddressMode}};
use super::VulkanRenderer; use super::{Texture, VulkanRenderer};
pub fn upload_texture_from_file(path: &str, renderer: &mut VulkanRenderer) -> Result<(), Box<dyn std::error::Error>> { pub fn upload_texture_from_file(path: &str, renderer: &mut VulkanRenderer) -> Result<Texture, Box<dyn std::error::Error>> {
// Load file // Load file
let mut tex_file = std::fs::File::open(path)?; let mut tex_file = std::fs::File::open(path)?;
let mut tex_bytes: Vec<u8> = vec![]; let mut tex_bytes: Vec<u8> = vec![];
@@ -24,25 +24,24 @@ pub fn upload_texture_from_file(path: &str, renderer: &mut VulkanRenderer) -> Re
println!("Texture width: {}, height: {}, bytes: {}", tex_width, tex_height, tex_byte_count); println!("Texture width: {}, height: {}, bytes: {}", tex_width, tex_height, tex_byte_count);
if is_dxt1 let texture = if is_dxt1 {
{ renderer.upload_texture(&tex_bytes[128..], tex_width, tex_height, Format::BC1_RGB_UNORM_BLOCK, Filter::Linear, SamplerAddressMode::Repeat, renderer.device.clone())
renderer.upload_texture(&tex_bytes[128..], tex_width, tex_height, Format::BC1_RGBUnormBlock, renderer.device.clone()); } else if is_dx10 {
}
if is_dx10
{
let dxgi_type = u32::from_ne_bytes(tex_bytes[128..132].try_into()?); let dxgi_type = u32::from_ne_bytes(tex_bytes[128..132].try_into()?);
assert!(dxgi_type == 83); // BC5 Unorm Typeless assert!(dxgi_type == 83); // BC5 Unorm Typeless
renderer.upload_texture(&tex_bytes[128+20..], tex_width, tex_height, Format::BC5UnormBlock, renderer.device.clone()); renderer.upload_texture(&tex_bytes[128+20..], tex_width, tex_height, Format::BC5_UNORM_BLOCK, Filter::Linear, SamplerAddressMode::Repeat, renderer.device.clone())
} } else {
panic!("Unknown texture type!");
};
Ok(()) Ok(texture)
} }
pub fn get_block_size(format: Format) -> Option<u32> { pub fn get_block_size(format: Format) -> Option<u32> {
match format { match format {
Format::BC1_RGBUnormBlock => Some(8), Format::BC1_RGB_UNORM_BLOCK => Some(8),
Format::BC5UnormBlock => Some(16), Format::BC5_UNORM_BLOCK => Some(16),
_ => None _ => None
} }
} }

View File

@@ -1,11 +1,9 @@
use std::sync::Arc; use std::sync::Arc;
use vulkano::command_buffer::DynamicState;
use vulkano::device::Device; use vulkano::device::Device;
use vulkano::format::Format; use vulkano::format::Format;
use vulkano::image::view::ImageView; use vulkano::image::view::ImageView;
use vulkano::image::{AttachmentImage, ImageUsage, SampleCount, SwapchainImage}; use vulkano::image::{AttachmentImage, ImageUsage, SampleCount, SwapchainImage};
use vulkano::pipeline::viewport::Viewport;
use vulkano::render_pass::{Framebuffer, FramebufferAbstract, RenderPass}; use vulkano::render_pass::{Framebuffer, FramebufferAbstract, RenderPass};
use winit::window::Window; use winit::window::Window;
use vulkano::swapchain::Swapchain; use vulkano::swapchain::Swapchain;
@@ -14,29 +12,21 @@ use vulkano::swapchain::Swapchain;
pub fn create_framebuffers(device: Arc<Device>, pub fn create_framebuffers(device: Arc<Device>,
swapchain: &Arc<Swapchain<Window>>, swapchain: &Arc<Swapchain<Window>>,
images: &[Arc<SwapchainImage<Window>>], images: &[Arc<SwapchainImage<Window>>],
msaa_samples: Option<SampleCount>, msaa_sample_count: Option<SampleCount>,
render_pass: Arc<RenderPass>, render_pass: Arc<RenderPass>)
dynamic_state: &mut DynamicState)
-> Vec<Arc<dyn FramebufferAbstract + Send + Sync>> { -> Vec<Arc<dyn FramebufferAbstract + Send + Sync>> {
let dim_array = images[0].dimensions(); let dim_array = images[0].dimensions();
let dim_array_f32 = [dim_array[0] as f32, dim_array[1] as f32];
let viewport = Viewport { let depth_image = if let Some(sample_count) = msaa_sample_count {
origin: [0.0, 0.0], AttachmentImage::multisampled_with_usage(device.clone(), dim_array, sample_count, Format::D16_UNORM, ImageUsage { depth_stencil_attachment: true, ..ImageUsage::none() }).unwrap()
dimensions: dim_array_f32,
depth_range: 0.0..1.0,
};
dynamic_state.viewports = Some(vec!(viewport));
let depth_image = if let Some(msaa_sample_count) = msaa_samples {
AttachmentImage::multisampled_with_usage(device.clone(), dim_array, msaa_sample_count, Format::D16Unorm, ImageUsage { depth_stencil_attachment: true, ..ImageUsage::none() }).unwrap()
} else { } else {
AttachmentImage::with_usage(device.clone(), dim_array, Format::D16Unorm, ImageUsage { depth_stencil_attachment: true, ..ImageUsage::none() }).unwrap() AttachmentImage::with_usage(device.clone(), dim_array, Format::D16_UNORM, ImageUsage { depth_stencil_attachment: true, ..ImageUsage::none() }).unwrap()
}; };
let depth_image_view = ImageView::new(depth_image.clone()).unwrap();
let msaa_buffers = if let Some(msaa_sample_count) = msaa_samples { let msaa_buffers = if let Some(sample_count) = msaa_sample_count {
Some(create_msaa_buffers(device.clone(), dim_array, swapchain, msaa_sample_count)) Some(create_msaa_buffers(device.clone(), dim_array, swapchain, sample_count))
} else { } else {
None None
}; };
@@ -45,22 +35,21 @@ pub fn create_framebuffers(device: Arc<Device>,
for i in 0..images.len() { for i in 0..images.len() {
let image_buffer = &images[i]; let image_buffer = &images[i];
let image_view = ImageView::new(image_buffer.clone()).unwrap(); let view = ImageView::new(image_buffer.clone()).unwrap();
let depth_view = ImageView::new(depth_image.clone()).unwrap();
if let Some(msaa_buffers_exist) = &msaa_buffers { if let Some(msaa_buffers_exist) = &msaa_buffers {
let msaa_view = ImageView::new((&msaa_buffers_exist[i]).clone()).unwrap(); let msaa_view = ImageView::new((&msaa_buffers_exist[i]).clone()).unwrap();
framebuffers.push(Arc::new(Framebuffer::start(render_pass.clone()) framebuffers.push(Arc::new(Framebuffer::start(render_pass.clone())
.add(image_view).unwrap() .add(view).unwrap()
.add(msaa_view).unwrap() .add(msaa_view).unwrap()
.add(depth_view).unwrap() .add(depth_image_view.clone()).unwrap()
.build().unwrap() .build().unwrap()
) as Arc<dyn FramebufferAbstract + Send + Sync>); ) as Arc<dyn FramebufferAbstract + Send + Sync>);
} else { } else {
framebuffers.push(Arc::new(Framebuffer::start(render_pass.clone()) framebuffers.push(Arc::new(Framebuffer::start(render_pass.clone())
.add(image_view).unwrap() .add(view).unwrap()
.add(depth_view).unwrap() .add(depth_image_view.clone()).unwrap()
.build().unwrap() .build().unwrap()
) as Arc<dyn FramebufferAbstract + Send + Sync>); ) as Arc<dyn FramebufferAbstract + Send + Sync>);
} }

View File

@@ -1,9 +1,11 @@
use std::sync::Arc; use std::sync::Arc;
use cgmath::{Deg, Euler, Matrix4, Quaternion, Vector3}; use cgmath::{Deg, Euler, Matrix4, Quaternion, Vector3};
use vulkano::descriptor_set::PersistentDescriptorSet;
use crate::game::GameState;
use crate::input::InputState; use crate::input::InputState;
use crate::vulkan::{RendererDescriptorSets, TextureHandle}; use crate::vulkan::TextureHandle;
use crate::vulkan::{MeshHandle, VulkanRenderer}; use crate::vulkan::{MeshHandle, VulkanRenderer};
use super::pipelines::vs; use super::pipelines::vs;
@@ -11,21 +13,30 @@ use super::pipelines::vs;
#[derive(Clone)] #[derive(Clone)]
pub struct GameObject { pub struct GameObject {
pub mesh_index: usize, pub mesh_index: usize,
pub texture_index: TextureHandle, pub textures: Vec<TextureHandle>,
pub normal_map_index: TextureHandle,
pub position: Vector3<f32>, pub position: Vector3<f32>,
pub rotation: Quaternion<f32>, pub rotation: Quaternion<f32>,
pub scale: Vector3<f32>, pub scale: Vector3<f32>,
pub children: Vec<GameObject>, pub children: Vec<GameObject>,
pub descriptor_sets: Vec<Arc<RendererDescriptorSets>>, pub descriptor_sets: Vec<Vec<Arc<PersistentDescriptorSet>>>,
pub is_selected: bool pub is_selected: bool,
pub pipeline_index: usize,
pub visible: bool,
}
pub enum PushConstantType {
MeshPC(vs::ty::PushConstants),
} }
impl GameObject { impl GameObject {
pub fn new(mesh: MeshHandle) -> GameObject { pub fn new(mesh: MeshHandle) -> GameObject {
GameObject { mesh_index: mesh.index, texture_index: mesh.diffuse_handle, normal_map_index: mesh.normal_handle, position: Vector3::new(0.0, 0.0, 0.0), GameObject { mesh_index: mesh.index, textures: mesh.textures, position: Vector3::new(0.0, 0.0, 0.0),
rotation: Quaternion::new(1.0, 0.0, 0.0, 0.0), scale: Vector3::new(1.0, 1.0, 1.0), children: vec![], rotation: Quaternion::new(1.0, 0.0, 0.0, 0.0), scale: Vector3::new(1.0, 1.0, 1.0), children: vec![],
descriptor_sets: vec![], is_selected: false } descriptor_sets: vec![], is_selected: false, pipeline_index: mesh.pipeline_index, visible: true }
}
pub fn init_descriptor_sets(&mut self, renderer: &mut VulkanRenderer) {
self.descriptor_sets = renderer.pipelines[self.pipeline_index].create_descriptor_sets(&self.textures, renderer);
} }
pub fn _set_position(&mut self, x: f32, y: f32, z: f32) { pub fn _set_position(&mut self, x: f32, y: f32, z: f32) {
@@ -54,11 +65,11 @@ impl GameObject {
self.rotation = self.rotation * Quaternion::from(Euler::new(Deg(x), Deg(y), Deg(z))); self.rotation = self.rotation * Quaternion::from(Euler::new(Deg(x), Deg(y), Deg(z)));
} }
pub fn get_push_constants(&self) -> vs::ty::PushConstants { pub fn get_push_constants(&self) -> PushConstantType {
vs::ty::PushConstants { PushConstantType::MeshPC(vs::ty::PushConstants {
model: self.get_model_matrix().into(), model: self.get_model_matrix().into(),
is_selected: if self.is_selected { 1 } else { 0 }, is_selected: if self.is_selected { 1 } else { 0 },
} })
} }
pub fn get_model_matrix(&self) -> Matrix4<f32> { pub fn get_model_matrix(&self) -> Matrix4<f32> {
@@ -69,21 +80,8 @@ impl GameObject {
} }
} }
#[derive(Debug, Clone, Copy)] pub type GameObjectHandle = usize;
pub struct GameObjectHandle {
pub object_index: usize
}
impl GameObjectHandle {
pub fn get_game_object<'a>(&self, renderer: &'a VulkanRenderer) -> Option<&'a GameObject> {
renderer.game_data.game_objects.get(self.object_index)
}
pub fn get_game_object_mut<'a>(&mut self, renderer: &'a mut VulkanRenderer) -> Option<&'a mut GameObject> {
renderer.game_data.game_objects.get_mut(self.object_index)
}
}
pub trait Updatable { pub trait Updatable {
fn update(&mut self, delta_time: f32, input: &InputState, renderer: &mut VulkanRenderer); fn update(&mut self, delta_time: f32, input: &InputState, game_state: &mut GameState, game_objects: &mut Vec<GameObject>, renderer: &mut VulkanRenderer);
} }

View File

@@ -6,6 +6,8 @@ use gltf::mesh::util::{ReadJoints, ReadNormals, ReadPositions, ReadTangents, Rea
use crate::vulkan::mesh::LoadError::{GltfError, MeshDataMissing, NoIndices}; use crate::vulkan::mesh::LoadError::{GltfError, MeshDataMissing, NoIndices};
use crate::vulkan::Vertex; use crate::vulkan::Vertex;
use super::TextVertex;
#[derive(Debug)] #[derive(Debug)]
pub enum LoadError { pub enum LoadError {
GltfError(gltf::Error), GltfError(gltf::Error),
@@ -25,9 +27,15 @@ impl From<String> for LoadError {
} }
} }
#[derive(Debug)]
pub enum CPUVertexList {
Vertex3D(Vec<Vertex>),
VertexText(Vec<TextVertex>)
}
#[derive(Debug)] #[derive(Debug)]
pub struct CPUMesh { pub struct CPUMesh {
pub vertices: Vec<Vertex>, pub vertices: CPUVertexList,
pub indices: Vec<u32>, pub indices: Vec<u32>,
pub local_texture_index: Option<usize>, pub local_texture_index: Option<usize>,
pub local_normal_map_index: Option<usize>, pub local_normal_map_index: Option<usize>,
@@ -41,7 +49,7 @@ fn read_file(path: &str) -> Vec<u8> {
glb_bytes glb_bytes
} }
pub fn load_mesh(mesh_path: &str, print_status: bool) -> Result<(Vec<CPUMesh>, Document), LoadError> { pub fn load_mesh<V>(mesh_path: &str, print_status: bool) -> Result<(Vec<CPUMesh>, Document), LoadError> {
let mut start_time = None; let mut start_time = None;
let mut total_vertices = 0; let mut total_vertices = 0;
let mut total_indices = 0; let mut total_indices = 0;
@@ -80,16 +88,26 @@ pub fn load_mesh(mesh_path: &str, print_status: bool) -> Result<(Vec<CPUMesh>, D
reader.read_tangents(), reader.read_tangents(),
reader.read_joints(0), reader.read_joints(0),
reader.read_weights(0)); reader.read_weights(0));
let vertices = vertices_result?;
let mut verts = vertices_result?;
verts.iter_mut().for_each(|v| {
v.position[1] = -v.position[1];
v.normal[1] = -v.normal[1];
});
let vert_count = verts.len();
let mut inds: Vec<u32> = indices.into_u32().collect();
inds.reverse();
let cpu_mesh = CPUMesh { let cpu_mesh = CPUMesh {
vertices, vertices: CPUVertexList::Vertex3D(verts),
indices: indices.into_u32().collect(), indices: inds,
local_texture_index: texture_index, local_texture_index: texture_index,
local_normal_map_index: normal_map_index, local_normal_map_index: normal_map_index,
name: mesh.name().map(|n| n.to_owned()), name: mesh.name().map(|n| n.to_owned()),
}; };
if print_status { if print_status {
let vert_count = cpu_mesh.vertices.len();
let index_count = cpu_mesh.indices.len(); let index_count = cpu_mesh.indices.len();
total_vertices += vert_count; total_vertices += vert_count;

View File

@@ -1,16 +1,18 @@
use std::{sync::Arc}; use std::sync::Arc;
use std::time::SystemTime; use std::time::SystemTime;
use cgmath::{Matrix4, SquareMatrix}; use cgmath::{Matrix4, SquareMatrix};
use dds::get_block_size; use dds::get_block_size;
use vulkano::command_buffer::CommandBufferUsage::{MultipleSubmit, SimultaneousUse}; use rust_engine_proc::perf;
use vulkano::{buffer::{BufferUsage, CpuAccessibleBuffer}, command_buffer::{PrimaryAutoCommandBuffer, SubpassContents}, image::{ImageDimensions, ImageLayout, ImageUsage, MipmapsCount, immutable::SubImage}, render_pass::{FramebufferAbstract, RenderPass}}; use vulkano::device::physical::PhysicalDevice;
use vulkano::command_buffer::{AutoCommandBufferBuilder, PrimaryCommandBuffer, DynamicState}; use vulkano::pipeline::viewport::Viewport;
use vulkano::descriptor::DescriptorSet; use vulkano::render_pass::{FramebufferAbstract, RenderPass};
use vulkano::device::{Device, Features, Queue}; use vulkano::{buffer::{BufferUsage, CpuAccessibleBuffer}, command_buffer::SubpassContents, image::{ImageAccess, ImageLayout, ImageUsage, MipmapsCount, immutable::SubImage}};
use vulkano::command_buffer::{AutoCommandBufferBuilder, CommandBufferUsage, PrimaryAutoCommandBuffer, PrimaryCommandBuffer};
use vulkano::device::{Device, DeviceExtensions, Features, Queue};
use vulkano::format::{ClearValue, Format}; use vulkano::format::{ClearValue, Format};
use vulkano::image::{ImageCreateFlags, ImmutableImage}; use vulkano::image::{ImageCreateFlags, ImageDimensions, ImmutableImage};
use vulkano::instance::{ApplicationInfo, Instance, PhysicalDevice, Version}; use vulkano::instance::{ApplicationInfo, Instance, InstanceExtensions, Version};
use vulkano::instance::debug::{DebugCallback, MessageSeverity, MessageType}; use vulkano::instance::debug::{DebugCallback, MessageSeverity, MessageType};
use vulkano::sampler::{Filter, MipmapMode, Sampler, SamplerAddressMode}; use vulkano::sampler::{Filter, MipmapMode, Sampler, SamplerAddressMode};
use vulkano::swapchain::{AcquireError, FullscreenExclusive, PresentMode, Surface, SurfaceTransform, Swapchain, SwapchainCreationError}; use vulkano::swapchain::{AcquireError, FullscreenExclusive, PresentMode, Surface, SurfaceTransform, Swapchain, SwapchainCreationError};
@@ -20,17 +22,18 @@ use vulkano::sync;
use vulkano_win::VkSurfaceBuild; use vulkano_win::VkSurfaceBuild;
use winit::event::{Event, WindowEvent}; use winit::event::{Event, WindowEvent};
use winit::event_loop::{ControlFlow, EventLoop}; use winit::event_loop::{ControlFlow, EventLoop};
use winit::window::{Window, WindowBuilder};
use mesh::CPUMesh; use mesh::CPUMesh;
use pipelines::{Drawcall, LineShader}; use pipelines::Drawcall;
use pipelines::line_vs::ty::LinePushConstants; use pipelines::{DefaultShader, TextShader};
use pipelines::DefaultShader;
use pipelines::vs; use pipelines::vs;
use winit::window::{Window, WindowBuilder};
use crate::config::LogConfig; use crate::config::RenderConfig;
use crate::{config::RenderConfig}; use crate::perf::PerformanceCounter;
use crate::vulkan::gameobject::{GameObject, GameObjectHandle}; use crate::vulkan::gameobject::GameObject;
use self::mesh::CPUVertexList;
pub mod pipelines; pub mod pipelines;
pub mod gameobject; pub mod gameobject;
@@ -40,7 +43,7 @@ mod renderpass;
mod framebuffers; mod framebuffers;
const VALIDATION_LAYERS: &[&str] = &[ const VALIDATION_LAYERS: &[&str] = &[
"VK_LAYER_KHRONOS_validation" "VK_LAYER_KHRONOS_validation",
]; ];
#[derive(Default, Debug, Clone)] #[derive(Default, Debug, Clone)]
@@ -60,28 +63,44 @@ pub struct LinePoint {
} }
vulkano::impl_vertex!(LinePoint, position); vulkano::impl_vertex!(LinePoint, position);
#[derive(Default, Debug, Clone)]
pub struct TextVertex {
pub position: [f32; 3],
pub uv: [f32; 2],
}
vulkano::impl_vertex!(TextVertex, position, uv);
#[derive(Default, Debug, Clone)]
pub struct TextInstanceData {}
vulkano::impl_vertex!(TextInstanceData);
pub trait Game { pub trait Game {
/// Returns true if event should be ignored by the vulkan handler /// Returns true if event should be ignored by the vulkan handler
fn on_window_event(self: &mut Self, event: &Event<()>); fn on_window_event(self: &mut Self, event: &Event<()>);
fn update(self: &mut Self, renderer: &mut VulkanRenderer) -> vs::ty::ObjectUniformData; fn update(self: &mut Self, renderer: &mut VulkanRenderer);
fn get_game_objects(&self) -> &Vec<GameObject>;
fn get_ubo(&self) -> &vs::ty::ObjectUniformData;
} }
pub struct Mesh { pub struct Mesh<V> {
pub vertex_buffer: Arc<CpuAccessibleBuffer<[Vertex]>>, pub vertex_buffer: Arc<CpuAccessibleBuffer<[V]>>,
pub index_buffer: Arc<CpuAccessibleBuffer<[u32]>>, pub index_buffer: Arc<CpuAccessibleBuffer<[u32]>>,
pub original_path: String, pub original_path: Option<String>,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct MeshHandle { pub struct MeshHandle {
pub index: usize, pub index: usize,
pub diffuse_handle: TextureHandle, pub textures: Vec<TextureHandle>,
pub normal_handle: TextureHandle, pub original_path: Option<String>,
pub original_path: String pub pipeline_index: usize
} }
pub(crate) type TextureHandle = usize; pub(crate) type TextureHandle = usize;
#[derive(Debug, Clone)]
pub struct Texture { pub struct Texture {
pub image: Arc<ImmutableImage>, pub image: Arc<ImmutableImage>,
pub sampler: Arc<Sampler> pub sampler: Arc<Sampler>
@@ -89,24 +108,18 @@ pub struct Texture {
pub struct GameData { pub struct GameData {
pub start_time: SystemTime, pub start_time: SystemTime,
pub line_vertices: Vec<LinePoint>,
pub line_push_constants: LinePushConstants,
pub recreate_pipeline: bool, pub recreate_pipeline: bool,
pub dimensions: [u32; 2], pub dimensions: [u32; 2],
pub shutdown: bool, pub shutdown: bool,
pub game_objects: Vec<GameObject>, pub meshes: Vec<Mesh<Vertex>>,
pub meshes: Vec<Mesh>, pub meshes_text: Vec<Mesh<TextVertex>>,
pub textures: Vec<Texture>, pub textures: Vec<Texture>,
pub use_line_pipeline: bool,
} }
pub(crate) type RendererDescriptorSets = dyn DescriptorSet + Send + Sync;
pub struct VulkanRenderer { pub struct VulkanRenderer {
pub game_data: GameData, pub game_data: GameData,
pub device: Arc<Device>, pub device: Arc<Device>,
pub framebuffers: Vec<Arc<dyn FramebufferAbstract + Send + Sync>>, pub framebuffers: Vec<Arc<dyn FramebufferAbstract + Send + Sync>>,
pub dynamic_state: DynamicState,
pub pipelines: Vec<Box<dyn Drawcall>>, pub pipelines: Vec<Box<dyn Drawcall>>,
pub surface: Arc<Surface<Window>>, pub surface: Arc<Surface<Window>>,
pub swapchain: Arc<Swapchain<Window>>, pub swapchain: Arc<Swapchain<Window>>,
@@ -116,36 +129,30 @@ pub struct VulkanRenderer {
pub debug_callback: Option<DebugCallback>, pub debug_callback: Option<DebugCallback>,
pub previous_frame_end: Option<Box<dyn GpuFuture>>, pub previous_frame_end: Option<Box<dyn GpuFuture>>,
pub uniform_buffers: Vec<Arc<CpuAccessibleBuffer<vs::ty::ObjectUniformData>>>, pub uniform_buffers: Vec<Arc<CpuAccessibleBuffer<vs::ty::ObjectUniformData>>>,
pub line_vertex_buffer: Arc<CpuAccessibleBuffer<[LinePoint]>>, pub render_config: RenderConfig,
pub render_config: RenderConfig pub viewport: Viewport
} }
impl VulkanRenderer { impl VulkanRenderer {
pub fn init(line_vertices: Vec<LinePoint>, enable_validation_layers: bool, render_config: RenderConfig, log_config: LogConfig) -> (VulkanRenderer, EventLoop<()>) { pub fn init(enable_validation_layers: bool, render_config: RenderConfig) -> (VulkanRenderer, EventLoop<()>) {
// Create empty game data struct to be filled // Create empty game data struct to be filled
let mut data = GameData { let mut data = GameData {
line_push_constants: LinePushConstants {
model: Matrix4::identity().into(),
view: Matrix4::identity().into(),
projection: Matrix4::identity().into(),
},
start_time: SystemTime::now(), start_time: SystemTime::now(),
recreate_pipeline: false, recreate_pipeline: false,
shutdown: false, shutdown: false,
line_vertices,
dimensions: [0, 0], dimensions: [0, 0],
meshes: vec![], meshes: vec![],
game_objects: vec![], meshes_text: vec![],
textures: vec![], textures: vec![],
use_line_pipeline: true,
}; };
// Create basic vulkan instance with layers and info // Create basic vulkan instance with layers and info
let instance = { let instance = {
let extensions = vulkano::instance::InstanceExtensions { let extensions = InstanceExtensions {
ext_debug_utils: true, ext_debug_utils: true,
..vulkano_win::required_extensions() ..vulkano_win::required_extensions()
}; };
println!("Using extensions: {:?}", extensions);
let app_info = ApplicationInfo { let app_info = ApplicationInfo {
application_name: Some("Asuro's Editor".into()), application_name: Some("Asuro's Editor".into()),
@@ -165,9 +172,9 @@ impl VulkanRenderer {
} }
}); });
Instance::new(Some(&app_info), Version::V1_2, &extensions, VALIDATION_LAYERS.iter().cloned()).expect("failed to create Vulkan instance") Instance::new(Some(&app_info), Version::V1_1, &extensions, VALIDATION_LAYERS.iter().cloned()).expect("failed to create Vulkan instance")
} else { } else {
Instance::new(Some(&app_info), Version::V1_2, &extensions, None).expect("failed to create Vulkan instance") Instance::new(Some(&app_info), Version::V1_1, &extensions, None).expect("failed to create Vulkan instance")
} }
}; };
@@ -189,35 +196,33 @@ impl VulkanRenderer {
validation: true validation: true
}; };
debug_callback = DebugCallback::new(&instance, msg_severity, msg_types, move |msg| { debug_callback = DebugCallback::new(&instance, msg_severity, msg_types, |msg| {
let (type_str, will_log) = match (msg.severity.error, msg.severity.warning, msg.severity.information, msg.severity.verbose) { let type_str = match (msg.severity.error, msg.severity.warning, msg.severity.information, msg.severity.verbose) {
(true, _, _, _) => ("!!", log_config.debug_errors), (true, _, _, _) => "!!",
(_, true, _, _) => ("!", log_config.debug_warnings), (_, true, _, _) => "!",
(_, _, _, true) => ("i", log_config.debug_info), (_, _, true, _) => "i",
_ => (" ", log_config.debug_verbose) _ => "v"
}; };
let layer_str = msg.layer_prefix; let layer_str = msg.layer_prefix;
if will_log { println!("[{}][{:?}]: {}", type_str, layer_str, msg.description); } println!("[{}][{}]: {}", type_str, layer_str.unwrap_or(""), msg.description);
}).ok(); }).ok();
} }
// TODO: Just get the first physical device we find, it's fiiiine... // TODO: Create device selector
let physical = PhysicalDevice::enumerate(&instance).next().unwrap(); let physical = PhysicalDevice::enumerate(&instance).next().unwrap();
let events_loop = EventLoop::new(); let events_loop = EventLoop::new();
let surface = WindowBuilder::new().build_vk_surface(&events_loop, instance.clone()).unwrap(); let surface = WindowBuilder::new().build_vk_surface(&events_loop, instance.clone()).unwrap();
let window = surface.window(); let window = surface.window();
// TODO: Tutorial says we need more queues // Queue
// In a real-life application, we would probably use at least a graphics queue and a transfers
// queue to handle data transfers in parallel. In this example we only use one queue.
let queue_family = physical.queue_families().find(|&q| { let queue_family = physical.queue_families().find(|&q| {
q.supports_graphics() && surface.is_supported(q).unwrap_or(false) q.supports_graphics() && surface.is_supported(q).unwrap_or(false)
}).unwrap(); }).unwrap();
// Queue let device_ext = DeviceExtensions { khr_swapchain: true, khr_maintenance1: true, ..DeviceExtensions::none() };
let device_ext = vulkano::device::DeviceExtensions { khr_swapchain: true, ..vulkano::device::DeviceExtensions::none() };
let (device, mut queues) = Device::new(physical, &Features::none(), &device_ext, let (device, mut queues) = Device::new(physical, &Features::none(), &device_ext,
[(queue_family, 0.5)].iter().cloned()).unwrap(); [(queue_family, 0.5)].iter().cloned()).unwrap();
let queue = queues.next().unwrap(); let queue = queues.next().unwrap();
@@ -235,6 +240,7 @@ impl VulkanRenderer {
.num_images(caps.min_image_count) .num_images(caps.min_image_count)
.format(format) .format(format)
.dimensions(data.dimensions) .dimensions(data.dimensions)
.layers(1)
.usage(usage) .usage(usage)
.sharing_mode(&queue) .sharing_mode(&queue)
.transform(SurfaceTransform::Identity) .transform(SurfaceTransform::Identity)
@@ -243,33 +249,30 @@ impl VulkanRenderer {
.fullscreen_exclusive(FullscreenExclusive::Default) .fullscreen_exclusive(FullscreenExclusive::Default)
.clipped(true) .clipped(true)
.color_space(color_space) .color_space(color_space)
.build().unwrap() .build().unwrap()
}; };
let size = images[0].dimensions().width_height();
let viewport = create_viewport(size[0] as f32, size[1] as f32);
// Render pass // Render pass
let render_pass = renderpass::create_render_pass(device.clone(), &render_config, swapchain.format()); let render_pass = renderpass::create_render_pass(device.clone(), &render_config, swapchain.format());
let render_pass_text = renderpass::create_render_pass(device.clone(), &render_config, swapchain.format());
let line_vertex_buffer = CpuAccessibleBuffer::from_iter(device.clone(), BufferUsage::vertex_buffer(), false, data.line_vertices.iter().cloned()).unwrap();
let pipelines: Vec<Box<dyn Drawcall>> = vec![ let pipelines: Vec<Box<dyn Drawcall>> = vec![
Box::new(DefaultShader::new(device.clone(), render_pass.clone())), Box::new(DefaultShader::new(device.clone(), render_pass.clone())),
Box::new(LineShader::new(device.clone(), render_pass.clone(), line_vertex_buffer.clone())), Box::new(TextShader::new(device.clone(), render_pass_text.clone())),
]; ];
// Dynamic viewports allow us to recreate just the viewport when the window is resized
// Otherwise we would have to recreate the whole pipeline.
let mut dynamic_state = DynamicState { line_width: None, viewports: None, scissors: None, compare_mask: None, write_mask: None, reference: None };
// The render pass we created above only describes the layout of our framebuffers. Before we // The render pass we created above only describes the layout of our framebuffers. Before we
// can draw we also need to create the actual framebuffers. // can draw we also need to create the actual framebuffers.
let framebuffers = framebuffers::create_framebuffers(device.clone(), &swapchain, &images, render_config.get_msaa(), render_pass.clone(), &mut dynamic_state); let framebuffers = framebuffers::create_framebuffers(device.clone(), &swapchain, &images, render_config.get_msaa(), render_pass.clone());
let mut uniform_buffers = Vec::new(); let mut uniform_buffers = Vec::new();
let uniform_buffer = vs::ty::ObjectUniformData { let uniform_buffer = vs::ty::ObjectUniformData {
view: Matrix4::identity().into(), view: Matrix4::identity().into(),
projection: Matrix4::identity().into(), projection: Matrix4::identity().into(),
ortho_projection: Matrix4::identity().into(),
time: 0.0, time: 0.0,
light_position: [0.0, 0.0, 0.0], light_position: [0.0, 0.0, 0.0],
light_directional_rotation: [0.0, 0.0, 0.0], light_directional_rotation: [0.0, 0.0, 0.0],
@@ -297,27 +300,32 @@ impl VulkanRenderer {
let previous_frame_end = Some(Box::new(sync::now(device.clone())) as Box<dyn GpuFuture>); let previous_frame_end = Some(Box::new(sync::now(device.clone())) as Box<dyn GpuFuture>);
(VulkanRenderer { game_data: data, device, framebuffers, (VulkanRenderer { game_data: data, device, framebuffers,
dynamic_state, pipelines, uniform_buffers, pipelines, uniform_buffers,
surface, swapchain, render_pass, queue, surface, swapchain, render_pass, queue,
recreate_swapchain: false, debug_callback, previous_frame_end, recreate_swapchain: false, debug_callback, previous_frame_end,
render_config, render_config, viewport
line_vertex_buffer,
}, events_loop) }, events_loop)
} }
fn create_command_buffer(self: &mut Self, fb_index: usize, uniform_buffer_data: vs::ty::ObjectUniformData) -> Arc<PrimaryAutoCommandBuffer> { // #[perf("cb", crate::perf::PerformanceCounter)]
fn create_command_buffer(self: &mut Self, fb_index: usize, uniform_buffer_data: &vs::ty::ObjectUniformData, game_objects: &Vec<GameObject>) -> Arc<PrimaryAutoCommandBuffer> {
// General setup // General setup
let mut builder = AutoCommandBufferBuilder::primary(self.device.clone(), self.queue.family(), SimultaneousUse).unwrap(); let mut builder = AutoCommandBufferBuilder::primary(self.device.clone(), self.queue.family(), CommandBufferUsage::OneTimeSubmit).unwrap();
builder.update_buffer(self.uniform_buffers[fb_index].clone(), Arc::new(uniform_buffer_data)).unwrap(); builder.update_buffer(self.uniform_buffers[fb_index].clone(), Arc::new(*uniform_buffer_data)).unwrap();
if self.render_config.msaa_samples > 0 { if self.render_config.msaa_samples > 0 {
builder.begin_render_pass(self.framebuffers[fb_index].clone(), SubpassContents::Inline, vec![ClearValue::None, ClearValue::Float([0.0, 0.0, 0.0, 1.0]), ClearValue::Depth(1.0)]).unwrap(); builder.begin_render_pass(self.framebuffers[fb_index].clone(), SubpassContents::Inline, vec![ClearValue::None, ClearValue::Float([0.0, 0.0, 0.0, 1.0]), ClearValue::Depth(1.0)]).unwrap();
} else { } else {
builder.begin_render_pass(self.framebuffers[fb_index].clone(), SubpassContents::Inline, vec![ClearValue::Float([0.0, 0.0, 0.0, 1.0]), ClearValue::Depth(1.0)]).unwrap(); builder.begin_render_pass(self.framebuffers[fb_index].clone(), SubpassContents::Inline, vec![ClearValue::Float([0.0, 0.0, 0.0, 1.0]), ClearValue::Depth(1.0)]).unwrap();
} }
builder.set_viewport(0, [self.viewport.clone()]);
// Draw meshes etc. // Draw meshes etc.
let mut index = 0;
for pipeline in &self.pipelines { for pipeline in &self.pipelines {
pipeline.draw(&mut builder, fb_index, &self.game_data, &self.dynamic_state); let objects = game_objects.iter().filter(|go| go.visible && go.pipeline_index == index).collect();
pipeline.draw(&mut builder, fb_index, objects, &self.game_data);
index += 1;
} }
// General cleanup // General cleanup
@@ -325,9 +333,8 @@ impl VulkanRenderer {
Arc::new(builder.build().unwrap()) Arc::new(builder.build().unwrap())
} }
#[perf("renderer", crate::perf::PerformanceCounter)]
pub fn render_loop(self: &mut Self, new_ubo: &vs::ty::ObjectUniformData, game_objects: &Vec<GameObject>) {
pub fn render_loop(self: &mut Self, new_ubo: vs::ty::ObjectUniformData) {
// cleanup previous frame // cleanup previous frame
self.previous_frame_end.as_mut().unwrap().cleanup_finished(); self.previous_frame_end.as_mut().unwrap().cleanup_finished();
@@ -348,17 +355,20 @@ impl VulkanRenderer {
Err(err) => panic!("{:?}", err), Err(err) => panic!("{:?}", err),
}; };
let size = new_images[0].dimensions().width_height();
self.viewport = create_viewport(size[0] as f32, size[1] as f32);
self.render_pass = renderpass::create_render_pass(self.device.clone(), &self.render_config, new_swapchain.format()); self.render_pass = renderpass::create_render_pass(self.device.clone(), &self.render_config, new_swapchain.format());
self.pipelines = vec![ self.pipelines = vec![
Box::new(DefaultShader::new(self.device.clone(), self.render_pass.clone())), Box::new(DefaultShader::new(self.device.clone(), self.render_pass.clone())),
Box::new(LineShader::new(self.device.clone(), self.render_pass.clone(), self.line_vertex_buffer.clone())), Box::new(TextShader::new(self.device.clone(), self.render_pass.clone())),
]; ];
self.swapchain = new_swapchain; self.swapchain = new_swapchain;
// Because framebuffers contains an Arc on the old swapchain, we need to // Because framebuffers contains an Arc on the old swapchain, we need to
// recreate framebuffers as well. // recreate framebuffers as well.
self.framebuffers = framebuffers::create_framebuffers(self.device.clone(), &self.swapchain, &new_images, self.render_config.get_msaa(), self.render_pass.clone(), &mut self.dynamic_state); self.framebuffers = framebuffers::create_framebuffers(self.device.clone(), &self.swapchain, &new_images, self.render_config.get_msaa(), self.render_pass.clone());
self.recreate_swapchain = false; self.recreate_swapchain = false;
} }
@@ -387,7 +397,7 @@ impl VulkanRenderer {
Err(err) => panic!("{:?}", err) Err(err) => panic!("{:?}", err)
}; };
let command_buffer = self.create_command_buffer(fb_index, new_ubo).clone(); let command_buffer = self.create_command_buffer(fb_index, new_ubo, game_objects).clone();
let future = self.previous_frame_end.take().unwrap() let future = self.previous_frame_end.take().unwrap()
.join(acquire_future) .join(acquire_future)
@@ -420,74 +430,84 @@ impl VulkanRenderer {
}; };
} }
pub fn upload_mesh(self: &mut Self, mesh: CPUMesh, original_path: String) -> usize { pub fn upload_mesh(self: &mut Self, mesh: CPUMesh, original_path: Option<String>) -> usize {
// let mut collision_mesh = mgf::Mesh::new(); let index_buffer = CpuAccessibleBuffer::from_iter(self.device.clone(), BufferUsage::index_buffer(), false, mesh.indices.into_iter()).unwrap();
// mesh.vertices.iter().for_each(|v| {
// collision_mesh.push_vert(v.position.into());
// }); // TODO: convert vert pos to world space
// for i in (0..mesh.indices.len()).step_by(3) {
// collision_mesh.push_face((mesh.indices[i] as usize, mesh.indices[i + 1] as usize, mesh.indices[i + 2] as usize));
// }
let vertex_buffer = CpuAccessibleBuffer::from_iter(self.device.clone(), BufferUsage::vertex_buffer(), false, mesh.vertices.into_iter()).unwrap(); match mesh.vertices {
let index_buffer = CpuAccessibleBuffer::from_iter(self.device.clone(), BufferUsage::index_buffer(), false, mesh.indices.into_iter()).unwrap(); CPUVertexList::Vertex3D(verts) => {
let vertex_buffer = CpuAccessibleBuffer::from_iter(self.device.clone(), BufferUsage::vertex_buffer(), false, verts.into_iter()).unwrap();
self.game_data.meshes.push(Mesh { vertex_buffer, index_buffer, original_path }); self.game_data.meshes.push(Mesh { vertex_buffer, index_buffer, original_path });
self.game_data.meshes.len() - 1 self.game_data.meshes.len() - 1
},
CPUVertexList::VertexText(verts) => {
let vertex_buffer = CpuAccessibleBuffer::from_iter(self.device.clone(), BufferUsage::vertex_buffer(), false, verts.into_iter()).unwrap();
self.game_data.meshes_text.push(Mesh { vertex_buffer, index_buffer, original_path });
self.game_data.meshes_text.len() - 1
},
}
} }
pub fn upload_texture(self: &mut Self, bytes: &[u8], width: u32, height: u32, format: Format, device: Arc<Device>) { pub fn update_mesh(self: &mut Self, mesh_index: usize, vertices: CPUVertexList, indices: Vec<u32>) {
let index_buffer = CpuAccessibleBuffer::from_iter(self.device.clone(), BufferUsage::index_buffer(), false, indices.into_iter()).unwrap();
match vertices {
CPUVertexList::Vertex3D(verts) => {
let mesh = &mut self.game_data.meshes[mesh_index];
mesh.vertex_buffer = CpuAccessibleBuffer::from_iter(self.device.clone(), BufferUsage::vertex_buffer(), false, verts.into_iter()).unwrap();
mesh.index_buffer = index_buffer;
},
CPUVertexList::VertexText(verts) => {
let mesh = &mut self.game_data.meshes_text[mesh_index];
mesh.vertex_buffer = CpuAccessibleBuffer::from_iter(self.device.clone(), BufferUsage::vertex_buffer(), false, verts.into_iter()).unwrap();
mesh.index_buffer = index_buffer;
}
}
}
pub fn upload_texture(self: &mut Self, bytes: &[u8], width: u32, height: u32, format: Format, filter: Filter, wrap: SamplerAddressMode, device: Arc<Device>) -> Texture {
let dimensions = ImageDimensions::Dim2d { width, height, array_layers: 1 }; let dimensions = ImageDimensions::Dim2d { width, height, array_layers: 1 };
let usage = ImageUsage { let usage = ImageUsage {
transfer_destination: true, transfer_destination: true,
transfer_source: false, transfer_source: true,
sampled: true, sampled: true,
..ImageUsage::none() ..ImageUsage::none()
}; };
let layout = ImageLayout::ShaderReadOnlyOptimal; let mip_maps = if format == Format::R8_UINT { MipmapsCount::One } else { MipmapsCount::Log2 };
let (immutable_image, initializer) = ImmutableImage::uninitialized( let (image_view, initializer) = ImmutableImage::uninitialized(
device.clone(), device.clone(),
dimensions, dimensions,
format, format,
MipmapsCount::Log2, mip_maps,
usage, usage,
ImageCreateFlags::default(), ImageCreateFlags::default(),
layout, ImageLayout::ShaderReadOnlyOptimal,
device.active_queue_families(), device.active_queue_families(),
).unwrap(); ).unwrap();
let init = SubImage::new( let init = SubImage::new(
Arc::new(initializer), Arc::new(initializer),
0, 0,
immutable_image.mipmap_levels(), image_view.mipmap_levels(),
0, 0,
1, 1,
ImageLayout::ShaderReadOnlyOptimal, ImageLayout::ShaderReadOnlyOptimal,
); );
// TODO: do we need multiple submit? let mut cbb = AutoCommandBufferBuilder::primary(device.clone(), self.queue.family(), CommandBufferUsage::OneTimeSubmit).unwrap();
let mut cbb = AutoCommandBufferBuilder::primary(device.clone(), self.queue.family(), MultipleSubmit).unwrap();
let mut offset = 0; let mut offset = 0;
let block_bytes = get_block_size(format).expect(&format!("Unknown texture format {:?}!", format)); let block_bytes = get_block_size(format);
for i in 0..immutable_image.mipmap_levels() {
let mip_size = dimensions.mipmap_dimensions(i).unwrap().width_height_depth();
let mip_byte_size = (
(u32::max(4, mip_size[0]) / 4)
* (u32::max(4, mip_size[1]) / 4)
* block_bytes) as usize;
let mut upload_bytes = |data: &[u8], mip_index: u32, mip_size: [u32; 3]| {
let source = CpuAccessibleBuffer::from_iter( let source = CpuAccessibleBuffer::from_iter(
device.clone(), device.clone(),
BufferUsage::transfer_source(), BufferUsage::transfer_source(),
false, false,
bytes[offset..(offset + mip_byte_size)].iter().cloned(), data.iter().cloned(),
).unwrap(); ).unwrap();
cbb.copy_buffer_to_image_dimensions( cbb.copy_buffer_to_image_dimensions(
@@ -497,10 +517,28 @@ impl VulkanRenderer {
mip_size, mip_size,
0, 0,
dimensions.array_layers(), dimensions.array_layers(),
i, mip_index,
).unwrap(); ).unwrap();
};
offset += mip_byte_size; if let Some(block_byte_size) = block_bytes {
for i in 0..image_view.mipmap_levels() {
let mip_size = dimensions.mipmap_dimensions(i).unwrap().width_height_depth();
let mip_byte_size = (
(u32::max(4, mip_size[0]) / 4)
* (u32::max(4, mip_size[1]) / 4)
* block_byte_size) as usize;
let data = &bytes[offset..(offset + mip_byte_size)];
upload_bytes(data, i, mip_size);
offset += mip_byte_size;
}
} else {
let mut texture_bytes: Vec<u8> = bytes.to_vec();
texture_bytes.resize((width * height) as usize, 0u8);
upload_bytes(&texture_bytes, 0, dimensions.width_height_depth());
} }
let cb = cbb.build().unwrap(); let cb = cbb.build().unwrap();
@@ -512,30 +550,122 @@ impl VulkanRenderer {
future.flush().unwrap(); future.flush().unwrap();
let sampler = Sampler::new(device.clone(), Filter::Linear, Filter::Linear, let sampler = Sampler::new(device.clone(), filter, filter,
MipmapMode::Linear, SamplerAddressMode::Repeat, SamplerAddressMode::Repeat, MipmapMode::Linear, wrap, wrap, wrap,
SamplerAddressMode::Repeat, 0.0, 1.0, 0.0, (immutable_image.mipmap_levels() - 1) as f32).unwrap(); 0.0, 1.0, 0.0, (image_view.mipmap_levels() - 1) as f32).unwrap();
self.game_data.textures.push(Texture { image: immutable_image, sampler }); Texture { image: image_view, sampler }
} }
pub fn add_game_object(self: &mut Self, mut game_object: GameObject, pipeline_index: usize) -> GameObjectHandle { pub fn update_texture(&mut self, tex_handle: TextureHandle, new_data: &[u8], new_data_dimensions: [u32; 3], new_data_offset: [u32; 3], device: Arc<Device>) {
self.pipelines[pipeline_index].create_descriptor_set(&mut game_object, self); let texture = &mut self.game_data.textures[tex_handle];
self.game_data.game_objects.push(game_object);
let old_sub_image = SubImage::new(
texture.image.clone(),
0,
1,
0,
1,
ImageLayout::ShaderReadOnlyOptimal,
);
GameObjectHandle { let mut cbb = AutoCommandBufferBuilder::primary(device.clone(), self.queue.family(), CommandBufferUsage::OneTimeSubmit).unwrap();
object_index: self.game_data.game_objects.len() - 1
} let upload_source = CpuAccessibleBuffer::from_iter(
device.clone(),
BufferUsage::transfer_source(),
false,
new_data.iter().cloned(),
).unwrap();
cbb.copy_buffer_to_image_dimensions(
upload_source.clone(),
old_sub_image.clone(),
new_data_offset,
new_data_dimensions,
0,
1,
0,
).unwrap();
let cb = cbb.build().unwrap();
let future = cb.execute(self.queue.clone()).unwrap();
future.flush().unwrap();
}
pub fn resize_texture(&mut self, game_object: &mut GameObject, texture_handle: TextureHandle, new_size: ImageDimensions) {
let mut texture = &mut self.game_data.textures[texture_handle];
let new_image_usage = ImageUsage {
transfer_destination: true,
transfer_source: true,
sampled: true,
..ImageUsage::none()
};
let (new_image_view, new_image_initializer) = ImmutableImage::uninitialized(
self.device.clone(),
new_size,
texture.image.format(),
texture.image.mipmap_levels(),
new_image_usage,
ImageCreateFlags::default(),
ImageLayout::ShaderReadOnlyOptimal,
self.device.active_queue_families(),
).unwrap();
let old_sub_image = SubImage::new(
texture.image.clone(),
0,
1,
0,
1,
ImageLayout::ShaderReadOnlyOptimal,
);
let new_sub_image = SubImage::new(
Arc::new(new_image_initializer),
0,
new_image_view.mipmap_levels(),
0,
1,
ImageLayout::ShaderReadOnlyOptimal,
);
let mut cbb = AutoCommandBufferBuilder::primary(self.device.clone(), self.queue.family(), CommandBufferUsage::OneTimeSubmit).unwrap();
cbb.copy_image(
old_sub_image.clone(),
[0, 0, 0],
0,
0,
new_sub_image.clone(),
[10, 0, 0],
0,
0,
old_sub_image.dimensions().width_height_depth(),
1
).unwrap();
let cb = cbb.build().unwrap();
let future = cb.execute(self.queue.clone()).unwrap();
future.flush().unwrap();
texture.image = new_image_view;
game_object.init_descriptor_sets(self);
} }
pub fn clear_all(&mut self) { pub fn clear_all(&mut self) {
self.game_data.game_objects.clear();
self.game_data.meshes.clear(); self.game_data.meshes.clear();
self.game_data.textures.clear(); self.game_data.textures.clear();
} }
} }
pub fn start_event_loop(mut renderer: VulkanRenderer, mut game: Box<dyn Game>, event_loop: EventLoop<()>) { pub fn start_event_loop(mut renderer: VulkanRenderer, mut game: Box<dyn Game>, event_loop: EventLoop<()>) {
PerformanceCounter::init_perf();
event_loop.run(move |event, _, control_flow| { event_loop.run(move |event, _, control_flow| {
game.on_window_event(&event); game.on_window_event(&event);
@@ -547,12 +677,34 @@ pub fn start_event_loop(mut renderer: VulkanRenderer, mut game: Box<dyn Game>, e
Event::WindowEvent { event: WindowEvent::CloseRequested, .. } => { Event::WindowEvent { event: WindowEvent::CloseRequested, .. } => {
*control_flow = ControlFlow::Exit; *control_flow = ControlFlow::Exit;
}, },
Event::RedrawEventsCleared => { Event::WindowEvent { event: WindowEvent::Resized(..), .. } => {
let ubo = game.update(&mut renderer); renderer.recreate_swapchain = true;
renderer.render_loop(ubo); },
Event::RedrawRequested(..) => {
PerformanceCounter::perf_next_frame("renderer");
PerformanceCounter::perf_next_frame("cb");
renderer.render_loop(game.get_ubo(), &game.get_game_objects());
},
Event::MainEventsCleared => {
PerformanceCounter::perf_next_frame("update");
PerformanceCounter::perf_next_frame("input_events");
game.update(&mut renderer);
renderer.surface.window().request_redraw();
}, },
_ => {} _ => {}
} }
}); });
} }
pub fn create_viewport(width: f32, height: f32) -> Viewport {
Viewport {
origin: [0.0, 0.0],
dimensions: [width, height],
depth_range: 0.0..1.0,
}
// Viewport {
// origin: [0.0, height],
// dimensions: [width, -height],
// depth_range: 0.0..1.0,
// }
}

View File

@@ -1,22 +1,23 @@
use std::{convert::TryInto, io::{self, ErrorKind, Read, Write}, path::PathBuf, sync::Arc}; use std::{convert::TryInto, io::{self, ErrorKind, Read, Write}, path::PathBuf, sync::Arc};
use vulkano::{command_buffer::{AutoCommandBufferBuilder, PrimaryAutoCommandBuffer}, descriptor::{descriptor_set::PersistentDescriptorSet}, image::view::ImageView, pipeline::{shader::{ShaderModule}}, render_pass::{RenderPass, Subpass}}; use vulkano::{buffer::TypedBufferAccess, command_buffer::{AutoCommandBufferBuilder, PrimaryAutoCommandBuffer}, descriptor_set::PersistentDescriptorSet, image::view::ImageView, pipeline::{PipelineBindPoint, shader::ShaderModule}, render_pass::{RenderPass, Subpass}};
use vulkano::command_buffer::DynamicState;
use vulkano::device::Device; use vulkano::device::Device;
use vulkano::pipeline::GraphicsPipeline; use vulkano::pipeline::GraphicsPipeline;
use vulkano::pipeline::GraphicsPipelineAbstract;
use crate::GameObject; use crate::{GameObject, vulkan::TextVertex};
use crate::vulkan::{LinePoint, Vertex}; use crate::vulkan::Vertex;
use crate::vulkan::GameData; use crate::vulkan::GameData;
use crate::VulkanRenderer; use crate::VulkanRenderer;
use super::{TextureHandle, gameobject::PushConstantType};
type RP = Arc<RenderPass>; type RP = Arc<RenderPass>;
type GP = Arc<dyn GraphicsPipelineAbstract + Send + Sync>; type GP = Arc<GraphicsPipeline>;
type DS = Arc<PersistentDescriptorSet>;
pub trait Drawcall { pub trait Drawcall {
fn draw(self: &Self, builder: &mut AutoCommandBufferBuilder<PrimaryAutoCommandBuffer>, fb_index: usize, game_data: &GameData, dynamic_state: &DynamicState); fn draw(self: &Self, builder: &mut AutoCommandBufferBuilder<PrimaryAutoCommandBuffer>, fb_index: usize, game_objects: Vec<&GameObject>, game_data: &GameData);
fn create_descriptor_set(self: &Self, game_object: &mut GameObject, renderer: &VulkanRenderer); fn create_descriptor_sets(self: &Self, textures: &Vec<TextureHandle>, renderer: &VulkanRenderer) -> Vec<Vec<DS>>;
fn recreate_pipeline(self: &mut Self, device: Arc<Device>, render_pass: RP); fn recreate_pipeline(self: &mut Self, device: Arc<Device>, render_pass: RP);
fn get_pipeline(self: &Self) -> &GP; fn get_pipeline(self: &Self) -> &GP;
} }
@@ -50,6 +51,7 @@ fn _shader_module_from_file(device: Arc<Device>, path: &str) -> Arc<ShaderModule
} }
} }
#[allow(dead_code)]
fn matches_extension(path: &PathBuf, extensions: &Vec<&str>) -> bool { fn matches_extension(path: &PathBuf, extensions: &Vec<&str>) -> bool {
if let Some(Some(path_extension)) = path.extension().map(|e| e.to_str()) { if let Some(Some(path_extension)) = path.extension().map(|e| e.to_str()) {
for extension in extensions { for extension in extensions {
@@ -59,6 +61,7 @@ fn matches_extension(path: &PathBuf, extensions: &Vec<&str>) -> bool {
return false; return false;
} }
#[allow(dead_code)]
fn compile_shaders() -> io::Result<()> { fn compile_shaders() -> io::Result<()> {
for file_maybe in std::fs::read_dir("./shaders")? { for file_maybe in std::fs::read_dir("./shaders")? {
let path = file_maybe?.path(); let path = file_maybe?.path();
@@ -92,29 +95,33 @@ impl DefaultShader {
fn create_pipeline(device: Arc<Device>, render_pass: RP) -> GP { fn create_pipeline(device: Arc<Device>, render_pass: RP) -> GP {
let sub_pass = Subpass::from(render_pass.clone(), 0).unwrap(); let sub_pass = Subpass::from(render_pass.clone(), 0).unwrap();
/*
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
{ {
println!("Compiling shaders..."); println!("Compiling shaders...");
compile_shaders().unwrap(); compile_shaders().unwrap();
} }
// unsafe { unsafe {
static ENTRY_NAME: [u8; 5usize] = [109u8, 97u8, 105u8, 110u8, 0];
let entry_name_c = std::ffi::CStr::from_ptr(ENTRY_NAME.as_ptr() as *const _);
// static ENTRY_NAME: [u8; 5usize] = [109u8, 97u8, 105u8, 110u8, 0]; let fs_module = shader_module_from_file(device.clone(), "shaders/triangle.frag.spv");
// let entry_name_c = std::ffi::CStr::from_ptr(ENTRY_NAME.as_ptr() as *const _); let fs_layout = fs::MainLayout(ShaderStages {
fragment: true,
..ShaderStages::none()
});
let fs_entry = fs_module.graphics_entry_point(entry_name_c, fs::MainInput, fs::MainOutput, fs_layout, GraphicsShaderType::Fragment);
// let fs_module = shader_module_from_file(device.clone(), "shaders/triangle.frag.spv"); let vs_module = shader_module_from_file(device.clone(), "shaders/triangle.vert.spv");
// let fs_entry = fs_module.graphics_entry_point(entry_name_c, yeet, &[], ref_shader.main_entry_point().input().clone(), ref_shader.main_entry_point().output().clone(), GraphicsShaderType::Fragment); let vs_layout = vs::Layout(ShaderStages {
vertex: true,
// let vs_module = shader_module_from_file(device.clone(), "shaders/triangle.vert.spv"); ..ShaderStages::none()
// let vs_layout = vs::Layout(ShaderStages { });
// vertex: true, let vs_entry = vs_module.graphics_entry_point(entry_name_c, vs::MainInput, vs::MainOutput, vs_layout, GraphicsShaderType::Vertex);
// ..ShaderStages::none() */
// });
// let vs_entry = vs_module.graphics_entry_point(entry_name_c, vs_layout, &[], vs::MainInput, vs::MainOutput, GraphicsShaderType::Vertex);
let fs = fs::Shader::load(device.clone()).unwrap();
let vs = vs::Shader::load(device.clone()).unwrap(); let vs = vs::Shader::load(device.clone()).unwrap();
let fs = fs::Shader::load(device.clone()).unwrap();
Arc::new(GraphicsPipeline::start() Arc::new(GraphicsPipeline::start()
.vertex_input_single_buffer::<Vertex>() .vertex_input_single_buffer::<Vertex>()
@@ -128,50 +135,47 @@ impl DefaultShader {
.render_pass(sub_pass.clone()) .render_pass(sub_pass.clone())
.build(device.clone()) .build(device.clone())
.unwrap()) .unwrap())
// } //}
} }
} }
impl Drawcall for DefaultShader { impl Drawcall for DefaultShader {
fn draw(self: &Self, builder: &mut AutoCommandBufferBuilder<PrimaryAutoCommandBuffer>, fb_index: usize, game_data: &GameData, dynamic_state: &DynamicState) { fn draw(self: &Self, builder: &mut AutoCommandBufferBuilder<PrimaryAutoCommandBuffer>, fb_index: usize, game_objects: Vec<&GameObject>, game_data: &GameData) {
for i in 0..game_data.game_objects.len() { for i in 0..game_objects.len() {
let game_object = &game_data.game_objects[i]; let game_object = &game_objects[i];
let mesh = &game_data.meshes[game_object.mesh_index]; let mesh = &game_data.meshes[game_object.mesh_index];
let push_constants = game_object.get_push_constants();
builder.draw_indexed( #[allow(irrefutable_let_patterns)]
self.pipeline.clone(), if let PushConstantType::MeshPC(mesh_push) = game_object.get_push_constants() {
dynamic_state, builder
vec![mesh.vertex_buffer.clone()], .bind_pipeline_graphics(self.pipeline.clone())
mesh.index_buffer.clone(), .bind_descriptor_sets(PipelineBindPoint::Graphics, self.pipeline.layout().clone(), 0, game_object.descriptor_sets[fb_index].clone())
game_object.descriptor_sets[fb_index].clone(), .bind_vertex_buffers(0, mesh.vertex_buffer.clone())
push_constants, .bind_index_buffer(mesh.index_buffer.clone())
vec![]).unwrap(); .push_constants(self.pipeline.layout().clone(), 0, mesh_push)
.draw_indexed(mesh.index_buffer.len() as u32, 1, 0, 0, 0).unwrap();
}
} }
} }
fn create_descriptor_set(self: &Self, game_object: &mut GameObject, renderer: &VulkanRenderer) { fn create_descriptor_sets(self: &Self, textures: &Vec<TextureHandle>, renderer: &VulkanRenderer) -> Vec<Vec<DS>> {
let descriptor_set_layout = self.get_pipeline().layout().descriptor_set_layout(0).unwrap().clone(); let descriptor_set_layout_0 = self.get_pipeline().layout().descriptor_set_layouts().get(0).unwrap().clone();
println!("Diff: {:?}, Norm: {:?}", game_object.texture_index, game_object.normal_map_index); renderer.uniform_buffers.iter().map(|uniform_buffer| {
debug_assert!(textures.len() == 2, "Expected diffuse and normal map for object shader!");
game_object.descriptor_sets = renderer.uniform_buffers.iter().map(|uniform_buffer| { let diffuse = &renderer.game_data.textures[textures[0]];
let descriptor_set: Arc<(dyn vulkano::descriptor::DescriptorSet + std::marker::Send + std::marker::Sync + 'static)>;
let builder = PersistentDescriptorSet::start(descriptor_set_layout.clone());
let diffuse = &renderer.game_data.textures[game_object.texture_index];
let diffuse_view = ImageView::new(diffuse.image.clone()).unwrap(); let diffuse_view = ImageView::new(diffuse.image.clone()).unwrap();
let normal_map = &renderer.game_data.textures[game_object.normal_map_index]; let normal_map = &renderer.game_data.textures[textures[1]];
let normal_view = ImageView::new(normal_map.image.clone()).unwrap(); let normal_view = ImageView::new(normal_map.image.clone()).unwrap();
descriptor_set = Arc::new(builder let mut builder = PersistentDescriptorSet::start(descriptor_set_layout_0.clone());
builder
.add_buffer(uniform_buffer.clone()).unwrap() .add_buffer(uniform_buffer.clone()).unwrap()
.add_sampled_image(diffuse_view, diffuse.sampler.clone()).unwrap() .add_sampled_image(diffuse_view, diffuse.sampler.clone()).unwrap()
.add_sampled_image(normal_view, normal_map.sampler.clone()).unwrap() .add_sampled_image(normal_view, normal_map.sampler.clone()).unwrap();
.build().unwrap());
descriptor_set vec![Arc::new(builder.build().unwrap())]
}).collect(); }).collect()
} }
fn recreate_pipeline(self: &mut Self, device: Arc<Device>, render_pass: RP) { fn recreate_pipeline(self: &mut Self, device: Arc<Device>, render_pass: RP) {
@@ -183,6 +187,7 @@ impl Drawcall for DefaultShader {
} }
} }
/*
pub mod line_vs { pub mod line_vs {
vulkano_shaders::shader!{ vulkano_shaders::shader!{
ty: "vertex", ty: "vertex",
@@ -204,7 +209,7 @@ pub struct LineShader {
} }
impl LineShader { impl LineShader {
pub fn new(device: Arc<Device>, render_pass: RP, vertex_buffer: Arc<vulkano::buffer::CpuAccessibleBuffer<[LinePoint]>>) -> Self { pub fn _new(device: Arc<Device>, render_pass: RP, vertex_buffer: Arc<vulkano::buffer::CpuAccessibleBuffer<[LinePoint]>>) -> Self {
LineShader { LineShader {
pipeline: Self::create_pipeline(device, render_pass), pipeline: Self::create_pipeline(device, render_pass),
vertex_buffer vertex_buffer
@@ -230,17 +235,134 @@ impl LineShader {
} }
impl Drawcall for LineShader { impl Drawcall for LineShader {
fn draw(self: &Self, builder: &mut AutoCommandBufferBuilder<PrimaryAutoCommandBuffer>, _fb_index: usize, game_data: &GameData, dynamic_state: &DynamicState) { fn draw(self: &Self, builder: &mut AutoCommandBufferBuilder<PrimaryAutoCommandBuffer>, _fb_index: usize, _game_objects: Vec<&GameObject>, game_data: &GameData) {
builder.draw(self.pipeline.clone(), builder.draw(self.pipeline.clone(),
&dynamic_state,
vec![self.vertex_buffer.clone()], vec![self.vertex_buffer.clone()],
(), (),
game_data.line_push_constants.clone(), game_data.line_push_constants.clone()).unwrap();
vec![]).unwrap();
} }
fn create_descriptor_set(self: &Self, _game_object: &mut GameObject, _renderer: &VulkanRenderer) { fn create_descriptor_sets(self: &Self, _textures: &Vec<TextureHandle>, _renderer: &VulkanRenderer) -> Vec<Vec<DS>> {
vec![]
}
fn recreate_pipeline(self: &mut Self, device: Arc<Device>, render_pass: RP) {
self.pipeline = Self::create_pipeline(device, render_pass);
}
fn get_pipeline(self: &Self) -> &GP {
&self.pipeline
}
}
*/
pub mod vs_text {
vulkano_shaders::shader!{
ty: "vertex",
path: "shaders/text.vert"
}
}
pub mod fs_text {
vulkano_shaders::shader!{
ty: "fragment",
path: "shaders/text.frag"
}
}
pub struct TextShader {
pipeline: GP,
}
impl TextShader {
pub fn new(device: Arc<Device>, render_pass: RP) -> Self {
TextShader {
pipeline: Self::create_pipeline(device, render_pass)
}
}
fn create_pipeline(device: Arc<Device>, render_pass: RP) -> GP {
let sub_pass = Subpass::from(render_pass.clone(), 0).unwrap();
/*
#[cfg(debug_assertions)]
{
println!("Compiling shaders...");
compile_shaders().unwrap();
}
unsafe {
static ENTRY_NAME: [u8; 5usize] = [109u8, 97u8, 105u8, 110u8, 0];
let entry_name_c = std::ffi::CStr::from_ptr(ENTRY_NAME.as_ptr() as *const _);
let fs_module = shader_module_from_file(device.clone(), "shaders/text.frag.spv");
let fs_layout = fs::Layout(ShaderStages {
fragment: true,
..ShaderStages::none()
});
let fs_entry = fs_module.graphics_entry_point(entry_name_c, fs_text::MainInput, fs_text::MainOutput, fs_layout, GraphicsShaderType::Fragment);
let vs_module = shader_module_from_file(device.clone(), "shaders/text.vert.spv");
let vs_layout = vs::Layout(ShaderStages {
vertex: true,
..ShaderStages::none()
});
let vs_entry = vs_module.graphics_entry_point(entry_name_c, vs_text::MainInput, vs_text::MainOutput, vs_layout, GraphicsShaderType::Vertex);
*/
let vs = vs_text::Shader::load(device.clone()).unwrap();
let fs = fs_text::Shader::load(device.clone()).unwrap();
let gp = Arc::new(GraphicsPipeline::start()
.vertex_input_single_buffer::<TextVertex>()
.vertex_shader(vs.main_entry_point(), ())
.triangle_list()
.viewports_dynamic_scissors_irrelevant(1)
.depth_stencil_simple_depth()
.fragment_shader(fs.main_entry_point(), ())
.blend_alpha_blending()
.cull_mode_disabled()
.render_pass(sub_pass.clone())
.build(device.clone())
.unwrap());
gp
//}
}
}
impl Drawcall for TextShader {
fn draw(self: &Self, builder: &mut AutoCommandBufferBuilder<PrimaryAutoCommandBuffer>, fb_index: usize, game_objects: Vec<&GameObject>, game_data: &GameData) {
for i in 0..game_objects.len() {
let game_object = &game_objects[i];
let mesh = &game_data.meshes_text[game_object.mesh_index];
builder.bind_pipeline_graphics(self.pipeline.clone())
.bind_descriptor_sets(PipelineBindPoint::Graphics, self.pipeline.layout().clone(), 0, game_object.descriptor_sets[fb_index].clone())
.bind_vertex_buffers(0, mesh.vertex_buffer.clone())
.bind_index_buffer(mesh.index_buffer.clone())
.draw_indexed(mesh.index_buffer.len() as u32, 1, 0, 0, 0).unwrap();
}
}
fn create_descriptor_sets(self: &Self, textures: &Vec<TextureHandle>, renderer: &VulkanRenderer) -> Vec<Vec<DS>> {
let descriptor_set_layout = self.get_pipeline().layout().descriptor_set_layouts().get(0).unwrap().clone();
renderer.uniform_buffers.iter().map(|uniform_buffer| {
let diffuse_index = match textures.len() {
0 => 0,
1 => textures[0],
_ => panic!("Expected only diffuse map for text shader!"),
};
let diffuse = &renderer.game_data.textures[diffuse_index];
let diffuse_view = ImageView::new(diffuse.image.clone()).unwrap();
let mut builder = PersistentDescriptorSet::start(descriptor_set_layout.clone());
builder.add_buffer(uniform_buffer.clone()).unwrap()
.add_sampled_image(diffuse_view.clone(), diffuse.sampler.clone()).unwrap();
vec![Arc::new(builder.build().unwrap())]
}).collect()
} }
fn recreate_pipeline(self: &mut Self, device: Arc<Device>, render_pass: RP) { fn recreate_pipeline(self: &mut Self, device: Arc<Device>, render_pass: RP) {

View File

@@ -1,8 +1,6 @@
use crate::RenderConfig; use crate::RenderConfig;
use std::sync::Arc; use std::sync::Arc;
use vulkano::format::Format; use vulkano::{device::Device, format::Format, render_pass::RenderPass};
use vulkano::device::Device;
use vulkano::render_pass::RenderPass;
pub fn create_render_pass(device: Arc<Device>, render_config: &RenderConfig, swapchain_format: Format) -> Arc<RenderPass> { pub fn create_render_pass(device: Arc<Device>, render_config: &RenderConfig, swapchain_format: Format) -> Arc<RenderPass> {
if render_config.msaa_samples > 0 { if render_config.msaa_samples > 0 {
@@ -24,7 +22,7 @@ pub fn create_render_pass(device: Arc<Device>, render_config: &RenderConfig, swa
depth: { depth: {
load: Clear, load: Clear,
store: Store, store: Store,
format: Format::D16Unorm, format: Format::D16_UNORM,
samples: render_config.msaa_samples, samples: render_config.msaa_samples,
initial_layout: ImageLayout::Undefined, initial_layout: ImageLayout::Undefined,
final_layout: ImageLayout::DepthStencilAttachmentOptimal, final_layout: ImageLayout::DepthStencilAttachmentOptimal,
@@ -49,7 +47,7 @@ pub fn create_render_pass(device: Arc<Device>, render_config: &RenderConfig, swa
depth: { depth: {
load: Clear, load: Clear,
store: Store, store: Store,
format: Format::D16Unorm, format: Format::D16_UNORM,
samples: 1, samples: 1,
initial_layout: ImageLayout::Undefined, initial_layout: ImageLayout::Undefined,
final_layout: ImageLayout::DepthStencilAttachmentOptimal, final_layout: ImageLayout::DepthStencilAttachmentOptimal,