91 Commits

Author SHA1 Message Date
5c8d06dba4 performance testing 2021-11-03 05:01:47 +01:00
d995d66f89 vsync 2021-11-03 05:01:42 +01:00
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
02ba2bb95a Fix axis layout and implement click raytracing 2021-07-30 22:41:05 +02:00
4c2409dbd0 axis box 2021-07-30 18:46:25 +02:00
ff80f7b9aa Select stuff (hit detection is still off) 2021-03-19 02:43:28 +01:00
02f39453ac Hit detection 2021-03-16 23:15:17 +01:00
a0bfdab902 Blinn-Phong 2021-03-14 19:37:57 +01:00
4b87a8752b load mesh faster 2021-03-14 17:35:53 +01:00
00204104dd stuff 2021-03-14 16:27:32 +01:00
c2e3c4e0e4 bc5 normal maps 2021-03-14 16:22:11 +01:00
0c8d09effa meep maap 2021-03-13 20:44:38 +01:00
f5ed8f4e40 ree 2021-03-12 21:44:06 +01:00
d83045394c mipmaps actually broken rip 2021-03-12 21:29:58 +01:00
f42250f352 dds mipmaps pog 2021-03-12 21:06:42 +01:00
da62d6ab67 BC1!!! 2021-03-11 22:38:05 +01:00
9848fbc183 dds import WIP 2021-03-11 01:34:51 +01:00
31cdfa1885 BC converter tool 2021-03-11 01:33:58 +01:00
Till
57b3f89817 box with correct textures 2021-03-09 22:53:09 +01:00
062ee8c1c5 Use correct normal for tangent calculations 2021-03-07 16:32:41 +01:00
45286b8178 remove scripts 2021-02-25 22:43:28 +01:00
8735f2d409 hotload shader + directional light 2021-02-25 21:36:29 +01:00
26871da22b More spirv compile shit 2021-02-22 15:58:46 +01:00
f209666144 spv 2021-02-21 18:44:18 +01:00
b98c85a13a stuff 2021-02-21 15:02:21 +01:00
039a4d5a64 nsight project 2021-02-21 15:01:52 +01:00
452af8ae3a box 2021-02-21 15:01:44 +01:00
277d284994 One sampler per texture 2021-02-20 22:44:03 +01:00
ad6b0730d5 mipmaps? 2021-02-20 18:34:16 +01:00
d33bceeab8 some mouse stuff i don't remember 2021-02-18 11:47:31 +01:00
dd4eaa13f7 Live toggle msaa 2020-12-05 22:12:29 +01:00
Till
33768a6730 Toggle msaa 2020-12-04 17:13:45 +01:00
Till
befa69c55e Toggle edit mode 2020-12-04 16:02:10 +01:00
be1d5e6221 Modules and stuff 2020-12-02 10:21:51 +01:00
e054a76a3f save and load player position 2020-11-29 22:12:13 +01:00
0edd6b6411 Input log settings 2020-11-29 22:02:49 +01:00
62ec222749 Load and save level 2020-11-28 23:25:29 +01:00
483645dcc5 Allow for more pipelines 2020-11-27 08:35:27 +01:00
9b6a81ae93 mip maps (but broken because of vulkano) 2020-11-27 02:24:50 +01:00
a8f8bbf36e msaa 2020-11-07 17:54:30 +01:00
38ea41b550 Simple normal map implementation 2020-10-26 18:15:39 +01:00
9687f5ec89 Load multiple textures 2020-10-26 10:00:01 +01:00
c055ea19ed Different textures per object 2020-10-25 04:01:47 +01:00
d6f2b5d385 Toggle lines 2020-10-25 02:08:15 +02:00
8b8bf64e55 First-person movement 2020-10-23 05:58:11 +02:00
c2f0dd53c1 whatever this is 2020-10-21 01:08:53 +02:00
71dc720a31 Specular fix 2020-07-06 23:50:16 +02:00
0a3f89c150 Components 2020-07-04 03:18:14 +02:00
002db530be better lighting 2020-06-28 22:11:43 +02:00
f868a8e45a broken lighting 2020-06-28 21:20:26 +02:00
419a329fc8 gameobjects 2020-06-28 01:29:47 +02:00
15f3952c1b fix ubo 2020-06-27 00:05:25 +02:00
067ca2edf4 box blend 2020-06-26 17:04:46 +02:00
c4494e4766 better box 2020-06-26 16:59:59 +02:00
dbeb6da890 ree 2020-06-26 16:53:55 +02:00
4121b5e657 Temporarily use push constants to set view/projection matrices 2020-06-26 03:40:42 +02:00
e755d3b1d8 Fix and module update 2020-06-25 00:59:10 +02:00
ae8b82c84d compiled spv 2019-08-19 22:32:14 +02:00
fff521307e whatever 2019-08-19 22:26:41 +02:00
70 changed files with 4294 additions and 983 deletions

5
.gitignore vendored
View File

@@ -1,6 +1,7 @@
# Generated by Cargo
# will have compiled files and executables
/target/
/rust-engine-proc/target/
# 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
@@ -80,4 +81,6 @@ fabric.properties
# cusotm
build/deploy/
build/capture/
build/capture/
*.blend1
.idea/

5
.idea/codeStyles/codeStyleConfig.xml generated Normal file
View File

@@ -0,0 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
</state>
</component>

View File

@@ -3,11 +3,13 @@
<option name="channel" value="DEFAULT" />
<option name="command" value="run" />
<option name="allFeatures" value="false" />
<option name="nocapture" value="false" />
<option name="emulateTerminal" value="false" />
<option name="backtrace" value="FULL" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
<envs />
<method v="2" />
<method v="2">
<option name="RunConfigurationTask" enabled="true" />
<option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
</method>
</configuration>
</component>

View File

@@ -1,5 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="deploy" type="PowerShellRunType" factoryName="PowerShell" scriptUrl="D:\Code\rust-engine\build\deploy.ps1" workingDirectory="D:\Code\rust-engine">
<method v="2" />
</configuration>
</component>

View File

@@ -3,11 +3,13 @@
<option name="channel" value="DEFAULT" />
<option name="command" value="run --release" />
<option name="allFeatures" value="false" />
<option name="nocapture" value="false" />
<option name="emulateTerminal" value="false" />
<option name="backtrace" value="FULL" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
<envs />
<method v="2" />
<method v="2">
<option name="RunConfigurationTask" enabled="true" />
<option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
</method>
</configuration>
</component>

View File

@@ -1,5 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="renderdoc" type="PowerShellRunType" factoryName="PowerShell" scriptUrl="D:\Code\rust-engine\build\renderdoc.ps1" workingDirectory="D:\Code\rust-engine">
<method v="2" />
</configuration>
</component>

64
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,64 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "lldb",
"request": "launch",
"name": "engine - debug",
"cargo": {
"args": [
"build",
"--bin=rust-engine",
"--package=rust-engine"
],
"filter": {
"name": "rust-engine",
"kind": "bin"
}
},
"args": [],
"cwd": "${workspaceFolder}"
},
{
"type": "lldb",
"request": "launch",
"name": "engine - release",
"cargo": {
"args": [
"build",
"--bin=rust-engine",
"--package=rust-engine",
"--release"
],
"filter": {
"name": "rust-engine",
"kind": "bin"
}
},
"args": [],
"cwd": "${workspaceFolder}"
},
{
"type": "lldb",
"request": "launch",
"name": "Debug unit tests in executable 'rust-engine'",
"cargo": {
"args": [
"test",
"--no-run",
"--bin=rust-engine",
"--package=rust-engine"
],
"filter": {
"name": "rust-engine",
"kind": "bin"
}
},
"args": [],
"cwd": "${workspaceFolder}"
}
]
}

View File

@@ -3,18 +3,28 @@ name = "rust-engine"
version = "0.1.0"
authors = ["Till <asuro@posteo.de>"]
edition = "2018"
default-run = "rust-engine"
[dependencies]
vulkano-shaders = "0.13"
vulkano = "0.13"
vulkano-win = "0.13"
shade_runner = "0.1.2"
shaderc = "0.5.0"
cgmath = "0.17"
winit = "0.19"
image = "0.22.0"
serde = "1.0.97"
serde_derive = "1.0.97"
toml = "0.5.1"
gilrs = "0.7.1"
gltf = "0.13.0"
vulkano-shaders = "0.26"
vulkano = "0.26"
vulkano-win = "0.26"
cgmath = "0.18.0"
winit = "0.25"
image = "0.23"
serde = "1.0"
serde_json = "1.0"
serde_derive = "1.0"
toml = "0.5"
gilrs = "0.7"
gltf = "0.15"
glyph_brush = "0.7"
winapi = "0.3"
rust-engine-proc = { path = "rust-engine-proc" }
[profile.release]
debug = 1
[[bin]]
name = "converter"
path = "build/converter/src/main.rs"

View File

@@ -0,0 +1,18 @@
use gltf::image::Source;
fn main() {
let doc = gltf::Gltf::open("models/box.gltf").unwrap();
for image in doc.images() {
if let Source::Uri { uri, .. } = image.source() {
println!("{}", uri);
let tex_conv_output = std::process::Command::new("build/nvidia-texture-converter/nvtt_export.exe")
.arg("-q").arg("fastest")
.arg("-f").arg("bc1")
.arg("-o").arg(format!("textures/{}.dds", image.name().unwrap()))
.arg(uri)
.current_dir("models")
.output().expect("Failed to run texture converter.");
println!("{:?}", tex_conv_output);
}
}
}

BIN
build/debug.rdbg Normal file

Binary file not shown.

View File

@@ -0,0 +1,129 @@
{
"extension": "ngfx-proj",
"files": [
],
"launcher": {
"activity/cpp/d3d/d3d12replayfencebehavior": "Default",
"activity/cpp/d3d/enablecachedpipelinestatesupport": false,
"activity/cpp/d3d/replaycapturedexecuteindirectbuffer": false,
"activity/cpp/d3d/reportforcefailedqueryinterfaces": true,
"activity/cpp/d3d/reportunknownobjects": true,
"activity/cpp/d3d/revisionzerodatacollection": true,
"activity/cpp/d3d/syncinterval": "0",
"activity/cpp/d3d/syncshadercollection": false,
"activity/cpp/forcerepaint": false,
"activity/cpp/opengl/delimiter": "SwapBuffers",
"activity/cpp/opengl/noerror": "Application Controlled",
"activity/cpp/raytracing/acceleration_structure_collect_to_vidmem": false,
"activity/cpp/raytracing/acceleration_structure_geometry_tracking_mode": "Auto",
"activity/cpp/raytracing/acceleration_structure_report_shallow_geometry_tracking_warnings": true,
"activity/cpp/troubleshooting/blockonfirstincompatibility": "Auto",
"activity/cpp/troubleshooting/crashreporting": true,
"activity/cpp/troubleshooting/ignoreincompatibilities": false,
"activity/cpp/troubleshooting/replaythreadpausestrategy": "Auto",
"activity/cpp/troubleshooting/serialization": true,
"activity/cpp/troubleshooting/threading": false,
"activity/cpp/vulkan/bufferdeviceaddresscapturereplay": true,
"activity/cpp/vulkan/coherentbuffercollection": true,
"activity/cpp/vulkan/forcedvalidationlayers": "VK_LAYER_LUNARG_parameter_validation; VK_LAYER_LUNARG_object_tracker; VK_LAYER_LUNARG_core_validation; VK_LAYER_KHRONOS_validation",
"activity/cpp/vulkan/forcevalidation": false,
"activity/cpp/vulkan/reserveheap": "0",
"activity/cpp/vulkan/revisionzerodatacollection": true,
"activity/cpp/vulkan/safeobjectlookup": "Auto",
"activity/cpp/vulkan/serializationobjectset": "Only Active",
"activity/cpp/vulkan/unsafepnext": false,
"activity/cpp/vulkan/unweavethreads": false,
"activity/fd/d3d/d3d12replayfencebehavior": "Default",
"activity/fd/d3d/enablecachedpipelinestatesupport": false,
"activity/fd/d3d/replaycapturedexecuteindirectbuffer": false,
"activity/fd/d3d/reportforcefailedqueryinterfaces": true,
"activity/fd/d3d/reportunknownobjects": true,
"activity/fd/d3d/revisionzerodatacollection": true,
"activity/fd/d3d/syncinterval": "0",
"activity/fd/d3d/syncshadercollection": false,
"activity/fd/forcerepaint": false,
"activity/fd/opengl/delimiter": "SwapBuffers",
"activity/fd/opengl/noerror": "Application Controlled",
"activity/fd/raytracing/acceleration_structure_collect_to_vidmem": false,
"activity/fd/raytracing/acceleration_structure_geometry_tracking_mode": "Auto",
"activity/fd/raytracing/acceleration_structure_report_shallow_geometry_tracking_warnings": true,
"activity/fd/targethud": true,
"activity/fd/troubleshooting/blockonfirstincompatibility": "Auto",
"activity/fd/troubleshooting/collectlinetables": true,
"activity/fd/troubleshooting/collectsassassembly": true,
"activity/fd/troubleshooting/crashreporting": true,
"activity/fd/troubleshooting/driverinstrumentation": true,
"activity/fd/troubleshooting/hardwarepmmetrics": true,
"activity/fd/troubleshooting/ignoreincompatibilities": false,
"activity/fd/troubleshooting/replaythreadpausestrategy": "Auto",
"activity/fd/troubleshooting/serialization": true,
"activity/fd/troubleshooting/shaderreflection": true,
"activity/fd/troubleshooting/threading": false,
"activity/fd/vulkan/bufferdeviceaddresscapturereplay": true,
"activity/fd/vulkan/coherentbuffercollection": true,
"activity/fd/vulkan/forcedvalidationlayers": "VK_LAYER_LUNARG_parameter_validation; VK_LAYER_LUNARG_object_tracker; VK_LAYER_LUNARG_core_validation; VK_LAYER_KHRONOS_validation",
"activity/fd/vulkan/forcevalidation": false,
"activity/fd/vulkan/reserveheap": "0",
"activity/fd/vulkan/revisionzerodatacollection": true,
"activity/fd/vulkan/safeobjectlookup": "Auto",
"activity/fd/vulkan/serializationobjectset": "Only Active",
"activity/fd/vulkan/unsafepnext": false,
"activity/fd/vulkan/unweavethreads": false,
"activity/fp/d3d/d3d12replayfencebehavior": "Default",
"activity/fp/d3d/enablecachedpipelinestatesupport": false,
"activity/fp/d3d/replaycapturedexecuteindirectbuffer": false,
"activity/fp/d3d/reportforcefailedqueryinterfaces": true,
"activity/fp/d3d/reportunknownobjects": true,
"activity/fp/d3d/revisionzerodatacollection": true,
"activity/fp/d3d/syncinterval": "0",
"activity/fp/d3d/syncshadercollection": false,
"activity/fp/forcerepaint": false,
"activity/fp/opengl/delimiter": "SwapBuffers",
"activity/fp/opengl/noerror": "Application Controlled",
"activity/fp/raytracing/acceleration_structure_collect_to_vidmem": false,
"activity/fp/raytracing/acceleration_structure_geometry_tracking_mode": "Auto",
"activity/fp/raytracing/acceleration_structure_report_shallow_geometry_tracking_warnings": true,
"activity/fp/targethud": true,
"activity/fp/troubleshooting/blockonfirstincompatibility": "Auto",
"activity/fp/troubleshooting/collectlinetables": true,
"activity/fp/troubleshooting/collectsassassembly": true,
"activity/fp/troubleshooting/crashreporting": true,
"activity/fp/troubleshooting/driverinstrumentation": true,
"activity/fp/troubleshooting/hardwarepmmetrics": true,
"activity/fp/troubleshooting/ignoreincompatibilities": false,
"activity/fp/troubleshooting/replaythreadpausestrategy": "Auto",
"activity/fp/troubleshooting/serialization": true,
"activity/fp/troubleshooting/shaderreflection": true,
"activity/fp/troubleshooting/threading": false,
"activity/fp/vulkan/bufferdeviceaddresscapturereplay": true,
"activity/fp/vulkan/coherentbuffercollection": true,
"activity/fp/vulkan/forcedvalidationlayers": "VK_LAYER_LUNARG_parameter_validation; VK_LAYER_LUNARG_object_tracker; VK_LAYER_LUNARG_core_validation; VK_LAYER_KHRONOS_validation",
"activity/fp/vulkan/forcevalidation": false,
"activity/fp/vulkan/reserveheap": "0",
"activity/fp/vulkan/revisionzerodatacollection": true,
"activity/fp/vulkan/safeobjectlookup": "Auto",
"activity/fp/vulkan/serializationobjectset": "Only Active",
"activity/fp/vulkan/unsafepnext": false,
"activity/fp/vulkan/unweavethreads": false,
"activity/warpviz/general/capturemode": "Frames",
"activity/warpviz/general/framecount": "1",
"activity/warpviz/general/lockclockstobase": true,
"activity/warpviz/general/metricset": "Throughput Metrics",
"activity/warpviz/general/timestampcount": "100000",
"activity/warpviz/general/vsyncmode": "Off",
"platform/win32/arguments": "",
"platform/win32/autoconnect": true,
"platform/win32/environment": "",
"platform/win32/executable": "D:/Code/rust-engine/target/release/rust-engine.exe",
"platform/win32/executable/history": [
"D:/Code/rust-engine/target/release/rust-engine.exe"
],
"platform/win32/workingdir": "D:/Code/rust-engine",
"platform/win32/workingdir/history": [
"D:/Code/rust-engine"
],
"platform/windows/device": "localhost"
},
"uuid": "{880d147e-f91a-47ff-945f-b4a3c2771050}",
"version": "1.2"
}

View File

@@ -0,0 +1,132 @@
{
"extension": "ngfx-proj",
"files": [
{
"path": "C:/Users/Till/Documents/NVIDIA Nsight Graphics/CppCaptures/rust-engine__2021_02_20__18_24_55/rust-engine__2021_02_20__18_24_55.ngfx-cppcap"
}
],
"launcher": {
"activity/cpp/d3d/d3d12replayfencebehavior": "Default",
"activity/cpp/d3d/enablecachedpipelinestatesupport": false,
"activity/cpp/d3d/replaycapturedexecuteindirectbuffer": false,
"activity/cpp/d3d/reportforcefailedqueryinterfaces": true,
"activity/cpp/d3d/reportunknownobjects": true,
"activity/cpp/d3d/revisionzerodatacollection": true,
"activity/cpp/d3d/syncinterval": "0",
"activity/cpp/d3d/syncshadercollection": false,
"activity/cpp/forcerepaint": false,
"activity/cpp/opengl/delimiter": "SwapBuffers",
"activity/cpp/opengl/noerror": "Application Controlled",
"activity/cpp/raytracing/acceleration_structure_collect_to_vidmem": false,
"activity/cpp/raytracing/acceleration_structure_geometry_tracking_mode": "Auto",
"activity/cpp/raytracing/acceleration_structure_report_shallow_geometry_tracking_warnings": true,
"activity/cpp/troubleshooting/blockonfirstincompatibility": "Auto",
"activity/cpp/troubleshooting/crashreporting": true,
"activity/cpp/troubleshooting/ignoreincompatibilities": false,
"activity/cpp/troubleshooting/replaythreadpausestrategy": "Auto",
"activity/cpp/troubleshooting/serialization": true,
"activity/cpp/troubleshooting/threading": false,
"activity/cpp/vulkan/bufferdeviceaddresscapturereplay": true,
"activity/cpp/vulkan/coherentbuffercollection": true,
"activity/cpp/vulkan/forcedvalidationlayers": "VK_LAYER_LUNARG_parameter_validation; VK_LAYER_LUNARG_object_tracker; VK_LAYER_LUNARG_core_validation; VK_LAYER_KHRONOS_validation",
"activity/cpp/vulkan/forcevalidation": false,
"activity/cpp/vulkan/reserveheap": "0",
"activity/cpp/vulkan/revisionzerodatacollection": true,
"activity/cpp/vulkan/safeobjectlookup": "Auto",
"activity/cpp/vulkan/serializationobjectset": "Only Active",
"activity/cpp/vulkan/unsafepnext": false,
"activity/cpp/vulkan/unweavethreads": false,
"activity/fd/d3d/d3d12replayfencebehavior": "Default",
"activity/fd/d3d/enablecachedpipelinestatesupport": false,
"activity/fd/d3d/replaycapturedexecuteindirectbuffer": false,
"activity/fd/d3d/reportforcefailedqueryinterfaces": true,
"activity/fd/d3d/reportunknownobjects": true,
"activity/fd/d3d/revisionzerodatacollection": true,
"activity/fd/d3d/syncinterval": "0",
"activity/fd/d3d/syncshadercollection": false,
"activity/fd/forcerepaint": false,
"activity/fd/opengl/delimiter": "SwapBuffers",
"activity/fd/opengl/noerror": "Application Controlled",
"activity/fd/raytracing/acceleration_structure_collect_to_vidmem": false,
"activity/fd/raytracing/acceleration_structure_geometry_tracking_mode": "Auto",
"activity/fd/raytracing/acceleration_structure_report_shallow_geometry_tracking_warnings": true,
"activity/fd/targethud": true,
"activity/fd/troubleshooting/blockonfirstincompatibility": "Auto",
"activity/fd/troubleshooting/collectlinetables": true,
"activity/fd/troubleshooting/collectsassassembly": true,
"activity/fd/troubleshooting/crashreporting": true,
"activity/fd/troubleshooting/driverinstrumentation": true,
"activity/fd/troubleshooting/hardwarepmmetrics": true,
"activity/fd/troubleshooting/ignoreincompatibilities": false,
"activity/fd/troubleshooting/replaythreadpausestrategy": "Auto",
"activity/fd/troubleshooting/serialization": true,
"activity/fd/troubleshooting/shaderreflection": true,
"activity/fd/troubleshooting/threading": false,
"activity/fd/vulkan/bufferdeviceaddresscapturereplay": true,
"activity/fd/vulkan/coherentbuffercollection": true,
"activity/fd/vulkan/forcedvalidationlayers": "VK_LAYER_LUNARG_parameter_validation; VK_LAYER_LUNARG_object_tracker; VK_LAYER_LUNARG_core_validation; VK_LAYER_KHRONOS_validation",
"activity/fd/vulkan/forcevalidation": false,
"activity/fd/vulkan/reserveheap": "0",
"activity/fd/vulkan/revisionzerodatacollection": true,
"activity/fd/vulkan/safeobjectlookup": "Auto",
"activity/fd/vulkan/serializationobjectset": "Only Active",
"activity/fd/vulkan/unsafepnext": false,
"activity/fd/vulkan/unweavethreads": false,
"activity/fp/d3d/d3d12replayfencebehavior": "Default",
"activity/fp/d3d/enablecachedpipelinestatesupport": false,
"activity/fp/d3d/replaycapturedexecuteindirectbuffer": false,
"activity/fp/d3d/reportforcefailedqueryinterfaces": true,
"activity/fp/d3d/reportunknownobjects": true,
"activity/fp/d3d/revisionzerodatacollection": true,
"activity/fp/d3d/syncinterval": "0",
"activity/fp/d3d/syncshadercollection": false,
"activity/fp/forcerepaint": false,
"activity/fp/opengl/delimiter": "SwapBuffers",
"activity/fp/opengl/noerror": "Application Controlled",
"activity/fp/raytracing/acceleration_structure_collect_to_vidmem": false,
"activity/fp/raytracing/acceleration_structure_geometry_tracking_mode": "Auto",
"activity/fp/raytracing/acceleration_structure_report_shallow_geometry_tracking_warnings": true,
"activity/fp/targethud": true,
"activity/fp/troubleshooting/blockonfirstincompatibility": "Auto",
"activity/fp/troubleshooting/collectlinetables": true,
"activity/fp/troubleshooting/collectsassassembly": true,
"activity/fp/troubleshooting/crashreporting": true,
"activity/fp/troubleshooting/driverinstrumentation": true,
"activity/fp/troubleshooting/hardwarepmmetrics": true,
"activity/fp/troubleshooting/ignoreincompatibilities": false,
"activity/fp/troubleshooting/replaythreadpausestrategy": "Auto",
"activity/fp/troubleshooting/serialization": true,
"activity/fp/troubleshooting/shaderreflection": true,
"activity/fp/troubleshooting/threading": false,
"activity/fp/vulkan/bufferdeviceaddresscapturereplay": true,
"activity/fp/vulkan/coherentbuffercollection": true,
"activity/fp/vulkan/forcedvalidationlayers": "VK_LAYER_LUNARG_parameter_validation; VK_LAYER_LUNARG_object_tracker; VK_LAYER_LUNARG_core_validation; VK_LAYER_KHRONOS_validation",
"activity/fp/vulkan/forcevalidation": false,
"activity/fp/vulkan/reserveheap": "0",
"activity/fp/vulkan/revisionzerodatacollection": true,
"activity/fp/vulkan/safeobjectlookup": "Auto",
"activity/fp/vulkan/serializationobjectset": "Only Active",
"activity/fp/vulkan/unsafepnext": false,
"activity/fp/vulkan/unweavethreads": false,
"activity/warpviz/general/capturemode": "Frames",
"activity/warpviz/general/framecount": "1",
"activity/warpviz/general/lockclockstobase": true,
"activity/warpviz/general/metricset": "Throughput Metrics",
"activity/warpviz/general/timestampcount": "100000",
"activity/warpviz/general/vsyncmode": "Off",
"platform/win32/arguments": "",
"platform/win32/autoconnect": true,
"platform/win32/environment": "",
"platform/win32/executable": "D:/Code/rust-engine/target/debug/rust-engine.exe",
"platform/win32/executable/history": [
"D:/Code/rust-engine/target/debug/rust-engine.exe"
],
"platform/win32/workingdir": "D:/Code/rust-engine",
"platform/win32/workingdir/history": [
"D:/Code/rust-engine"
],
"platform/windows/device": "localhost"
},
"uuid": "{6a6b81ab-a3c8-4cb8-8cda-3691aafe8573}",
"version": "1.2"
}

Binary file not shown.

Binary file not shown.

2
build/quick-clean.ps1 Normal file
View File

@@ -0,0 +1,2 @@
rm ..\target\debug\deps\rust-engine*
rm ..\target\debug\deps\rust_engine*

BIN
build/release.rdbg Normal file

Binary file not shown.

27
build/renderdoc-debug.cap Normal file
View File

@@ -0,0 +1,27 @@
{
"rdocCaptureSettings": 1,
"settings": {
"autoStart": false,
"commandLine": "",
"environment": [
],
"executable": "D:\\Code\\rust-engine\\target\\debug\\rust-engine.exe",
"inject": false,
"numQueuedFrames": 0,
"options": {
"allowFullscreen": true,
"allowVSync": true,
"apiValidation": false,
"captureAllCmdLists": false,
"captureCallstacks": false,
"captureCallstacksOnlyDraws": false,
"debugOutputMute": true,
"delayForDebugger": 0,
"hookIntoChildren": false,
"refAllResources": false,
"verifyBufferAccess": false
},
"queuedFrameCap": 0,
"workingDir": "D:/Code/rust-engine"
}
}

View File

@@ -0,0 +1,27 @@
{
"rdocCaptureSettings": 1,
"settings": {
"autoStart": false,
"commandLine": "",
"environment": [
],
"executable": "D:\\Code\\rust-engine\\target\\release\\rust-engine.exe",
"inject": false,
"numQueuedFrames": 0,
"options": {
"allowFullscreen": true,
"allowVSync": true,
"apiValidation": true,
"captureAllCmdLists": false,
"captureCallstacks": true,
"captureCallstacksOnlyDraws": false,
"debugOutputMute": true,
"delayForDebugger": 0,
"hookIntoChildren": false,
"refAllResources": false,
"verifyBufferAccess": true
},
"queuedFrameCap": 0,
"workingDir": "D:\\Code\\rust-engine"
}
}

2
config/graphics.toml Normal file
View File

@@ -0,0 +1,2 @@
msaa_samples = 4
vsync = "Mailbox"

View File

@@ -1,40 +1,59 @@
# Scan codes are in decimal: 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]]
name = "quit"
scan_code = 1
vk_code = "VK_ESCAPE"
[[button]]
name = "toggle_edit"
vk_code = "VK_F1"
[[button]]
name = "reload_shaders"
scan_code = 19
ctrl = true
vk_code = "VK_F5"
[[button]]
name = "print_framerate"
scan_code = 33
name = "toggle_framerate"
vk_code = "VK_F2"
[[button]]
name = "w"
scan_code = 17
name = "button_forward"
vk_code = "VK_W"
[[button]]
name = "s"
scan_code = 31
name = "button_backward"
vk_code = "VK_S"
[[button]]
name = "a"
scan_code = 30
name = "button_left"
vk_code = "VK_A"
[[button]]
name = "d"
scan_code = 32
name = "button_right"
vk_code = "VK_D"
[[button]]
name = "test"
scan_code = 20
vk_code = "VK_T"
[[button]]
name = "quicksave"
vk_code = "VK_F6"
[[button]]
name = "quickload"
vk_code = "VK_F7"
[[button]]
name = "select"
mouse = "left"
touch = 1
[[axis]]
name = "move_forward"
positive_button = "w"
negative_button = "s"
positive_button = "button_forward"
negative_button = "button_backward"
[[axis]]
name = "move_forward"
@@ -45,12 +64,12 @@ name = "move_forward"
controller_axis = "LeftStickY"
[[axis]]
name = "move_sideways"
positive_button = "d"
negative_button = "a"
name = "move_right"
positive_button = "button_right"
negative_button = "button_left"
[[axis]]
name = "move_sideways"
name = "move_right"
controller_axis = "LeftStickX"
[[axis]]
@@ -61,6 +80,10 @@ mouse_axis = "x"
name = "look_horizontal"
controller_axis = "RightStickX"
[[axis]]
name = "look_horizontal"
touch_axis = "horizontal"
[[axis]]
name = "look_vertical"
mouse_axis = "y"
@@ -69,5 +92,10 @@ mouse_axis = "y"
name = "look_vertical"
controller_axis = "RightStickY"
[[axis]]
name = "look_vertical"
touch_axis = "vertical"
[config]
line_height_px = 16
line_height_px = 16
mouse_speed = 0.01

View File

@@ -1,3 +1,8 @@
input_events = false
vulkan_validation_layers = false
mesh_load_info = true
vulkan_validation_layers = true
mesh_load_info = true
[input]
mouse_motion = false
buttons = false
touch = false
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

69
levels/test.lvl Normal file
View File

@@ -0,0 +1,69 @@
{
"meshes": [
{
"path": "models/plane.gltf"
},
{
"path": "models/box.gltf"
}
],
"objects": [
{
"mesh_index": 0,
"position": [
0.0,
0.0,
0.0
],
"rotation": [
1.0,
0.0,
0.0,
0.0
],
"scale": [
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": {
"mesh_index": null,
"position": [
0.0,
0.0,
10.0
],
"rotation": [
1.0,
0.0,
0.0,
0.0
],
"scale": [
1.0,
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.

Binary file not shown.

BIN
models/box.blend Normal file

Binary file not shown.

View File

@@ -1,6 +1,6 @@
{
"asset" : {
"generator" : "Khronos glTF Blender I/O v0.9.36",
"generator" : "Khronos glTF Blender I/O v1.6.16",
"version" : "2.0"
},
"scene" : 0,
@@ -21,35 +21,58 @@
"materials" : [
{
"doubleSided" : true,
"name" : "Material",
"name" : "Cube",
"normalTexture" : {
"index" : 0
},
"pbrMetallicRoughness" : {
"baseColorFactor" : [
0.800000011920929,
0.800000011920929,
0.800000011920929,
1
],
"baseColorTexture" : {
"index" : 1
},
"metallicFactor" : 0,
"roughnessFactor" : 0.4000000059604645
"roughnessFactor" : 0.5
}
}
],
"meshes" : [
{
"name" : "Cube",
"name" : "Cube.001",
"primitives" : [
{
"attributes" : {
"POSITION" : 0,
"NORMAL" : 1,
"TEXCOORD_0" : 2
"TANGENT" : 2,
"TEXCOORD_0" : 3
},
"indices" : 3,
"indices" : 4,
"material" : 0
}
]
}
],
"textures" : [
{
"sampler" : 0,
"source" : 0
},
{
"sampler" : 0,
"source" : 1
}
],
"images" : [
{
"mimeType" : "image/jpeg",
"name" : "cobblestone_large_01_nor_4k",
"uri" : "cobblestone_large_01_nor_4k.jpg"
},
{
"mimeType" : "image/png",
"name" : "colors",
"uri" : "colors.png"
}
],
"accessors" : [
{
"bufferView" : 0,
@@ -77,10 +100,16 @@
"bufferView" : 2,
"componentType" : 5126,
"count" : 24,
"type" : "VEC2"
"type" : "VEC4"
},
{
"bufferView" : 3,
"componentType" : 5126,
"count" : 24,
"type" : "VEC2"
},
{
"bufferView" : 4,
"componentType" : 5123,
"count" : 36,
"type" : "SCALAR"
@@ -99,18 +128,29 @@
},
{
"buffer" : 0,
"byteLength" : 192,
"byteLength" : 384,
"byteOffset" : 576
},
{
"buffer" : 0,
"byteLength" : 192,
"byteOffset" : 960
},
{
"buffer" : 0,
"byteLength" : 72,
"byteOffset" : 768
"byteOffset" : 1152
}
],
"samplers" : [
{
"magFilter" : 9729,
"minFilter" : 9987
}
],
"buffers" : [
{
"byteLength" : 840,
"byteLength" : 1224,
"uri" : "box.bin"
}
]

BIN
models/plane.bin Normal file

Binary file not shown.

BIN
models/plane.blend Normal file

Binary file not shown.

160
models/plane.gltf Normal file
View File

@@ -0,0 +1,160 @@
{
"asset" : {
"generator" : "Khronos glTF Blender I/O v1.4.40",
"version" : "2.0"
},
"scene" : 0,
"scenes" : [
{
"name" : "Scene",
"nodes" : [
0
]
}
],
"nodes" : [
{
"mesh" : 0,
"name" : "Plane"
}
],
"materials" : [
{
"doubleSided" : true,
"name" : "Material",
"normalTexture" : {
"index" : 0,
"scale" : 5,
"texCoord" : 0
},
"pbrMetallicRoughness" : {
"baseColorTexture" : {
"index" : 1,
"texCoord" : 0
},
"metallicFactor" : 0,
"roughnessFactor" : 0.5
}
}
],
"meshes" : [
{
"name" : "Plane",
"primitives" : [
{
"attributes" : {
"POSITION" : 0,
"NORMAL" : 1,
"TANGENT" : 2,
"TEXCOORD_0" : 3
},
"indices" : 4,
"material" : 0
}
]
}
],
"textures" : [
{
"sampler" : 0,
"source" : 0
},
{
"sampler" : 0,
"source" : 1
}
],
"images" : [
{
"mimeType" : "image/jpeg",
"name" : "cobblestone_large_01_nor_4k",
"uri" : "textures/cobblestone_large_01_nor_4k.jpg"
},
{
"mimeType" : "image/jpeg",
"name" : "cobblestone_large_01_diff_4k",
"uri" : "textures/cobblestone_large_01_diff_4k.jpg"
}
],
"accessors" : [
{
"bufferView" : 0,
"componentType" : 5126,
"count" : 4,
"max" : [
10,
0,
10
],
"min" : [
-10,
0,
-10
],
"type" : "VEC3"
},
{
"bufferView" : 1,
"componentType" : 5126,
"count" : 4,
"type" : "VEC3"
},
{
"bufferView" : 2,
"componentType" : 5126,
"count" : 4,
"type" : "VEC4"
},
{
"bufferView" : 3,
"componentType" : 5126,
"count" : 4,
"type" : "VEC2"
},
{
"bufferView" : 4,
"componentType" : 5123,
"count" : 6,
"type" : "SCALAR"
}
],
"bufferViews" : [
{
"buffer" : 0,
"byteLength" : 48,
"byteOffset" : 0
},
{
"buffer" : 0,
"byteLength" : 48,
"byteOffset" : 48
},
{
"buffer" : 0,
"byteLength" : 64,
"byteOffset" : 96
},
{
"buffer" : 0,
"byteLength" : 32,
"byteOffset" : 160
},
{
"buffer" : 0,
"byteLength" : 12,
"byteOffset" : 192
}
],
"samplers" : [
{
"magFilter" : 9729,
"minFilter" : 9986
}
],
"buffers" : [
{
"byteLength" : 204,
"uri" : "plane.bin"
}
]
}

BIN
models/sphere.blend Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 MiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 MiB

BIN
models/textures/colors.dds Normal file

Binary file not shown.

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

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()
}

BIN
shaders/line.frag.spv Normal file

Binary file not shown.

BIN
shaders/line.vert.spv Normal file

Binary file not shown.

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

@@ -1,12 +1,54 @@
#version 450
#extension GL_ARB_separate_shader_objects : enable
layout(push_constant) uniform PushConstants {
mat4 model;
bool is_selected;
} push;
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(binding = 2) uniform sampler2D normal_tex;
layout(location = 0) in vec2 tex_coords;
layout(location = 1) in vec3 normal_wld;
layout(location = 2) in vec3 position_wld;
layout(location = 3) in mat3 tbn;
layout(location = 0) out vec4 out_color;
//layout(binding = 1) uniform sampler2D tex;
void main() {
out_color = vec4(1.0, 0.0, 0.0, 1.0);// texture(tex, tex_coords);
vec3 normal_cam_u = vec3(texture(normal_tex, tex_coords).rg, 1.0);
normal_cam_u = normal_cam_u * 2.0 - 1.0;
normal_cam_u = normalize(tbn * normal_cam_u);
vec3 light_direction_cam_u = normalize(ubo.light_position - position_wld);
float ambient_strength = 0.1;
vec3 light_color = vec3(1.0, 1.0, 1.0);
vec3 ambient_color = ambient_strength * light_color;
float diffuse_strength = max(0.0, dot(normal_cam_u, light_direction_cam_u));
vec3 diffuse_color = diffuse_strength * light_color;
float specular_value = 1.0;
vec3 view_direction = normalize(vec3(inverse(ubo.view) * vec4(0.0, 0.0, 0.0, 1.0)) - position_wld);
vec3 halfway_direction = normalize(light_direction_cam_u + view_direction);
float specular_strength = pow(max(dot(normal_cam_u, halfway_direction), 0.0), 2);
vec3 specular_color = specular_value * specular_strength * light_color;
out_color = vec4(ambient_color + diffuse_color + specular_color, 1.0) * texture(diffuse_tex, tex_coords);
if (push.is_selected) {
out_color = mix(out_color, vec4(0.5, 0.5, 0.0, 1.0), 0.1);
}
}

BIN
shaders/triangle.frag.spv Normal file

Binary file not shown.

View File

@@ -1,22 +1,56 @@
#version 450
#extension GL_ARB_separate_shader_objects : enable
layout(binding = 0) uniform UniformBufferObject {
layout(push_constant) uniform PushConstants {
mat4 model;
bool is_selected;
} push;
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 = 2) in vec3 normal;
layout(location = 3) in vec4 tangent;
layout(location = 4) in ivec4 bone_index;
layout(location = 5) in vec4 bone_weight;
layout(location = 0) out vec2 tex_coords;
layout(location = 1) out vec3 normal_wld;
layout(location = 2) out vec3 position_wld;
layout(location = 3) out mat3 tbn;
layout(push_constant) uniform PushConstants {
mat4 model;
} push;
out gl_PerVertex {
vec4 gl_Position;
};
void main() {
// Vertex position in camera
gl_Position = ubo.projection * ubo.view * push.model * vec4(position, 1.0);
// gl_Position.y = -gl_Position.y;
// Vertex position in world
position_wld = vec3(push.model * vec4(position, 1.0));
// Just interpolate UV coords, no transformation needed
tex_coords = uv;
// Normal _direction_ in world (not use atm)
normal_wld = vec3(mat3(transpose(inverse(push.model))) * normal);
// Tangent->object space matrix
vec3 tangent3 = vec3(tangent.xy, 1.0);
vec3 bitangent = -cross(tangent3, normal);
vec3 t = normalize(vec3(push.model * vec4(tangent3, 0.0)));
vec3 b = normalize(vec3(push.model * vec4(bitangent, 0.0)));
vec3 n = normalize(vec3(push.model * vec4(normal, 0.0)));
tbn = mat3(t, b, n);
}

BIN
shaders/triangle.vert.spv Normal file

Binary file not shown.

View File

@@ -1,16 +1,61 @@
use serde_derive::{Serialize, Deserialize};
use std::{convert::TryInto, fs};
use serde_derive::{Deserialize, Serialize};
use toml;
use std::fs;
use vulkano::{image::SampleCount, swapchain::PresentMode};
#[derive(Debug, Serialize, Deserialize, Clone, Copy)]
pub struct LogConfigInput {
pub mouse_motion: bool,
pub buttons: bool,
pub touch: bool,
pub missing_bindings: bool
}
#[derive(Debug, Serialize, Deserialize, Clone, Copy)]
pub struct LogConfig {
pub input_events: bool,
pub vulkan_validation_layers: bool,
pub mesh_load_info: bool,
pub input: LogConfigInput
}
impl LogConfig {
pub fn from_file(path: &str) -> Self {
toml::from_slice(&fs::read(path).expect("Failed to read log config!")).expect("Failed to parse log config!")
}
}
#[derive(Debug, Serialize, Deserialize, Clone, Copy)]
pub struct RenderConfig {
pub msaa_samples: u32,
pub vsync: PresentModeConfig,
}
impl RenderConfig {
pub fn from_file(path: &str) -> Self {
toml::from_slice(&fs::read(path).expect("Failed to read render config!")).expect("Failed to parse render config!")
}
pub fn get_msaa(&self) -> Option<SampleCount> {
if self.msaa_samples > 0 { self.msaa_samples.try_into().ok() } else { None }
}
}
#[derive(Debug, Serialize, Deserialize, Clone, Copy)]
pub enum PresentModeConfig {
Immediate,
Mailbox,
Fifo,
FifoRelaxed,
}
impl Into<PresentMode> for PresentModeConfig {
fn into(self) -> PresentMode {
match self {
PresentModeConfig::Immediate => PresentMode::Immediate,
PresentModeConfig::Mailbox => PresentMode::Mailbox,
PresentModeConfig::Fifo => PresentMode::Fifo,
PresentModeConfig::FifoRelaxed => PresentMode::Relaxed,
}
}
}

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

@@ -0,0 +1,109 @@
use cgmath::{Vector4, vec4};
use crate::{input::InputState, perf::stddev, 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;
}
unsafe {
let mut counters = vec![];
self.text.clear();
if let Some(pcs) = &crate::perf::PERF_COUNTERS {
for (name, pc) in pcs {
let mean = pc.mean();
let stddev = stddev(&pc.values, mean as i128);
let str = format!("{}: {:.1}ms (±{:.1})\n", name, (mean as f64) / 1000., stddev / 1000.);
counters.push((mean, str));
}
}
counters.sort_by(|a, b| a.cmp(b));
for (_, str) in counters {
self.text.push_str(&str);
}
let frame_time_mean: u128 = renderer.fps_counter.iter().sum::<u128>() / crate::perf::PERF_COUNTER_SIZE as u128;
let fps_mean = 1000000. / frame_time_mean as f64;
let fps_min = 1000000. / *renderer.fps_counter.iter().max().unwrap() as f64;
self.text.push_str(&format!("Total FPS: {:.0} (min {:.0})", fps_mean, fps_min));
}
update_text(self.game_object, &self.text, 20., 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) {
}
}

91
src/game/level.rs Normal file
View File

@@ -0,0 +1,91 @@
use std::{error::Error, io::{BufReader, BufWriter}};
use std::fs::File;
use serde_derive::{Deserialize, Serialize};
use crate::vulkan::{MeshHandle, VulkanRenderer, gameobject::GameObjectHandle};
use crate::game::TestGame;
#[derive(Debug, Serialize, Deserialize)]
struct MeshJson {
path: String
}
#[derive(Debug, Serialize, Deserialize)]
struct ObjectJson {
mesh_index: Option<usize>,
position: [f32; 3],
rotation: [f32; 4],
scale: [f32; 3]
}
#[derive(Debug, Serialize, Deserialize)]
struct LevelJson {
meshes: Vec<MeshJson>,
objects: Vec<ObjectJson>,
player: ObjectJson
}
pub fn load_level(path: &str, game: &mut TestGame, renderer: &mut VulkanRenderer) -> Result<Vec<GameObjectHandle>, Box<dyn Error>> {
let file = File::open(path)?;
let reader = BufReader::new(file);
let level_json: LevelJson = serde_json::from_reader(reader)?;
let meshes: Vec<MeshHandle> = level_json.meshes.iter().map(|json_mesh| {
let mesh_handles = game.load_gltf(renderer, &json_mesh.path);
// TODO: Add empty parent GO instead of just loading the first mesh
mesh_handles[0].clone()
}).collect();
let objects: Vec<GameObjectHandle> = level_json.objects.iter().filter_map(|json_obj| {
// TODO: Parenting
if let Some(mesh_index) = json_obj.mesh_index {
let handle = game.add_game_object(renderer, meshes[mesh_index].clone());
let game_object = &mut game.game_objects[handle];
game_object.position = json_obj.position.into();
game_object.rotation = json_obj.rotation.into();
game_object.scale = json_obj.scale.into();
Some(handle)
} else {
None
}
}).collect();
game.player.camera.position = level_json.player.position.into();
game.player.camera.rotation = level_json.player.rotation.into();
Ok(objects)
}
pub fn save_level(path: &str, game: &mut TestGame, renderer: &mut VulkanRenderer) -> Result<(), Box<dyn Error>> {
let meshes = renderer.game_data.meshes.iter().filter(|m| m.original_path.is_some()).map(|mesh| {
MeshJson {
path: mesh.original_path.clone().unwrap()
}
}).collect();
let objects = game.game_objects.iter().enumerate().filter_map(|(i, game_object)| {
if game_object.pipeline_index != 0 { return None; }
Some(ObjectJson {
mesh_index: Some(i),
position: game_object.position.into(),
rotation: game_object.rotation.into(),
scale: game_object.scale.into()
})
}).collect();
let player = ObjectJson {
mesh_index: None,
position: game.player.camera.position.into(),
rotation: game.player.camera.rotation.into(),
scale: [1., 1., 1.]
};
let level_json = LevelJson {
meshes,
objects,
player
};
let file = File::create(path)?;
let writer = BufWriter::new(file);
serde_json::to_writer_pretty(writer, &level_json)?;
Ok(())
}

239
src/game/mod.rs Normal file
View File

@@ -0,0 +1,239 @@
use std::time::Instant;
use cgmath::{Deg, Euler, Quaternion, vec3};
use glyph_brush::GlyphBrush;
use rust_engine_proc::perf;
use winit::event::Event;
use level::{load_level, save_level};
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::input::InputState;
use crate::vulkan::{Game, MeshHandle, TextVertex, Vertex, VulkanRenderer};
use crate::vulkan::gameobject::{GameObject, GameObjectHandle, Updatable};
use crate::vulkan::mesh;
pub mod player;
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 input: InputState,
pub player: Player,
pub game_objects: Vec<GameObject>,
pub log_config: LogConfig,
pub texture_index_counter: usize,
pub last_time: f32,
pub components: Vec<Box<dyn Updatable>>,
pub game_state: GameState,
pub ubo: vs::ty::ObjectUniformData,
}
impl Game for TestGame {
fn get_game_objects(&self) -> &Vec<GameObject> {
&self.game_objects
}
fn on_window_event(self: &mut Self, event: &Event<()>) {
self.input.on_window_event(event);
}
#[perf("update", crate::perf::PerformanceCounter)]
fn update(self: &mut Self, renderer: &mut VulkanRenderer) {
// Input and timing
self.input.frame_start();
let time = (renderer.game_data.start_time.elapsed().unwrap().as_micros() as f64 / 1000000.0) as f32;
let frame_time = time - self.last_time;
// Component update
let input = &self.input;
let objs = &mut self.game_objects;
let components = &mut self.components;
let paused = self.game_state.paused;
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
if self.input.button_just_released("quit") {
renderer.game_data.shutdown = true;
}
if self.input.button_just_pressed("reload_shaders") {
renderer.recreate_swapchain = true;
}
if self.input.button_just_pressed("quicksave") {
save_level("levels/test.lvl", self, renderer).unwrap();
}
if self.input.button_just_pressed("quickload") {
self.clear_level(renderer);
load_level("levels/test.lvl", self, renderer).unwrap();
}
if self.input.button_just_pressed("test") {
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
let light_pos = vec3(2.0, 0.5, 2.0);
// End frame
self.last_time = time;
self.input.frame_end();
self.ubo = vs::ty::ObjectUniformData {
view: self.player.camera.view.into(),
projection: self.player.camera.proj.into(),
ortho_projection: self.player.camera.ortho_proj.into(),
time,
light_position: light_pos.into(),
light_directional_rotation: [45.0, 45.0, 0.0],
camera_position: self.player.camera.position.into(),
_dummy0: [0; 12],
_dummy1: [0; 4],
_dummy2: [0; 4],
};
}
fn get_ubo(&self) -> &vs::ty::ObjectUniformData {
&self.ubo
}
}
impl TestGame {
pub fn new(toml_path: &str, log_config: LogConfig) -> TestGame {
TestGame {
input: InputState::new(toml_path, log_config),
player: Player::new(3., 30.),
game_objects: vec![],
log_config,
texture_index_counter: 0,
last_time: 0.0,
components: vec![],
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) {
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!");
}
pub fn offset_texture_id(&mut self, local_tex_id: Option<usize>) -> usize {
match local_tex_id {
Some(local_id) => local_id + self.texture_index_counter,
None => 0,
}
}
pub fn load_gltf(&mut self, renderer: &mut VulkanRenderer, gltf_path: &str) -> Vec<MeshHandle> {
let mut mesh_handles = Vec::new();
// Load file
let (meshes, document) = mesh::load_mesh::<Vertex>(gltf_path, self.log_config.mesh_load_info).unwrap();
for cpu_mesh in meshes.into_iter() {
// Convert file texture id to game texture id
let diffuse_id = self.offset_texture_id(cpu_mesh.local_texture_index);
let normal_id = self.offset_texture_id(cpu_mesh.local_normal_map_index);
// Upload mesh
let mesh_id = renderer.upload_mesh(cpu_mesh, Some(gltf_path.to_string()));
let mesh_handle = MeshHandle {
index: mesh_id,
textures: vec![diffuse_id, normal_id],
original_path: Some(gltf_path.to_string()),
pipeline_index: 0
};
mesh_handles.push(mesh_handle);
}
for doc_image in document.images() {
let texture_start_time = Instant::now();
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;
if self.log_config.mesh_load_info {
println!("Uploading texture took {:?}ms", texture_start_time.elapsed().as_millis());
}
}
mesh_handles
}
pub fn add_game_object(&mut self, renderer: &mut VulkanRenderer, mesh: MeshHandle) -> GameObjectHandle {
let mut obj = GameObject::new(mesh);
obj.init_descriptor_sets(renderer);
self.game_objects.push(obj);
self.game_objects.len() - 1
}
pub fn clear_level(&mut self, renderer: &mut VulkanRenderer) {
self.game_objects.clear();
self.texture_index_counter = 0;
renderer.clear_all();
}
}
pub fn _print_quat_as_euler(quat: Quaternion<f32>) {
let euler = Euler::from(quat);
print!("({:?},{:?},{:?})", Deg::from(euler.x), Deg::from(euler.y), Deg::from(euler.z));
}

166
src/game/player.rs Normal file
View File

@@ -0,0 +1,166 @@
use cgmath::{Deg, InnerSpace, Matrix4, One, Quaternion, Rad, Rotation, Rotation3, SquareMatrix, Vector2, Vector3, vec3, vec4};
use crate::game::player::PlayerMovementMode::{FirstPerson, Flying};
use crate::input::InputState;
use crate::util::intersection_distance;
use crate::vulkan::gameobject::GameObject;
use crate::vulkan::{
gameobject::Updatable,
VulkanRenderer
};
use super::GameState;
pub struct Camera {
pub fov_y: f32,
pub position: Vector3<f32>,
pub rotation: Quaternion<f32>,
pub view: Matrix4<f32>,
pub proj: Matrix4<f32>,
pub ortho_proj: Matrix4<f32>,
}
impl Camera {
pub fn new() -> Camera {
Camera {
fov_y: 45.0,
position: vec3(0.0, 0.0, 0.0),
rotation: Quaternion::one(),
view: Matrix4::identity(),
proj: Matrix4::identity(),
ortho_proj: Matrix4::identity(),
}
}
pub fn update(&mut self, renderer: &mut VulkanRenderer) {
// Don't let floating point error accumulate in rotation
debug_assert!(f32::abs(1.0 - self.rotation.magnitude()) < 0.00001 as f32);
self.rotation = self.rotation.normalize();
// Create matrices
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(
Rad::from(Deg(self.fov_y)),
width / height,
0.1,
100.0
);
self.proj.y.y *= -1.0;
self.ortho_proj = cgmath::ortho(0., width, 0., height, -1., 1.);
}
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.x as f32 / viewport_dimensions[0] 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_end_screen = vec4(normalized_x, normalized_y, 0.0, 1.0);
let proj_inverse = self.proj.invert()?;
let view_inverse = self.view.invert()?;
let mut ray_start_camera = proj_inverse * click_start_screen;
ray_start_camera /= ray_start_camera.w;
let mut ray_end_camera = proj_inverse * click_end_screen;
ray_end_camera /= ray_end_camera.w;
let mut ray_start_world = view_inverse * ray_start_camera;
ray_start_world /= ray_start_world.w;
let mut ray_end_world = view_inverse * ray_end_camera;
ray_end_world /= ray_end_world.w;
let ray_dir_world = (ray_end_world - ray_start_world).normalize();
Some(ray_dir_world.truncate())
}
pub fn local_to_world(&self, vec:Vector3<f32>) -> Vector3<f32> {
self.rotation.invert().rotate_vector(vec)
}
pub fn _world_to_local(&self, vec:Vector3<f32>) -> Vector3<f32> {
self.rotation.rotate_vector(vec)
}
}
#[derive(PartialEq)]
pub enum PlayerMovementMode {
FirstPerson,
Flying,
}
pub struct Player {
pub camera: Camera,
pub movement_mode: PlayerMovementMode,
pub movement_speed: f32,
pub look_sensitivity: f32,
pub height: f32,
pub x_look: f32,
pub y_look: f32
}
impl Player {
pub fn new(movement_speed: f32, look_sensitivity: f32) -> Player {
Player {
camera: Camera::new(),
movement_mode: Flying,
movement_speed,
look_sensitivity,
height: 1.0,
x_look: 0.0,
y_look: 0.0
}
}
}
impl Updatable for Player {
fn update(&mut self, delta_time: f32, input: &InputState, _game_state: &mut GameState, game_objects: &mut Vec<GameObject>, renderer: &mut VulkanRenderer) {
// Edit mode
if input.button_just_pressed("toggle_edit") {
if self.movement_mode == FirstPerson {
self.movement_mode = Flying;
} else {
self.movement_mode = FirstPerson;
}
}
if input.button_just_pressed("select") {
let ray_direction = self.camera.viewport_pos_to_ray_direction(input.mouse_position, renderer.game_data.dimensions).unwrap();
for game_object in game_objects {
let mesh = &renderer.game_data.meshes[game_object.mesh_index];
if let Some(_) = intersection_distance(self.camera.position, ray_direction, mesh, game_object) {
game_object.is_selected = !game_object.is_selected;
}
}
}
// Rotation
self.x_look += input.get_axis_timescaled("look_vertical", 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 y_rot = Quaternion::from_angle_y(Deg(self.y_look));
self.camera.rotation = x_rot * y_rot;
// Movement
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 {
let mut world_input = self.camera.local_to_world(local_input);
world_input.y = 0.0;
self.camera.position += world_input;
} else if self.movement_mode == Flying {
self.camera.position += self.camera.local_to_world(local_input);
}
// Update
self.camera.update(renderer);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,135 +1,30 @@
use winit::{Event};
use cgmath::{Matrix4, Rad, Vector3, Deg, Quaternion, Rotation3, One, Rotation};
extern crate serde_json;
use vulkan::gameobject::GameObject;
use crate::config::{LogConfig, RenderConfig};
use crate::game::TestGame;
use crate::vulkan::VulkanRenderer;
mod vulkan;
use crate::vulkan::{Game, LinePoint, GameObject, VulkanRenderer, RenderResult, MeshHandle, GameObjectHandle};
use crate::vulkan::vs::ty::UniformBufferObject;
mod input;
use crate::input::{InputState};
mod config;
use crate::config::LogConfig;
mod mesh;
struct TestGame {
input: InputState,
cam_position: Vector3<f32>,
cam_rotation: Quaternion<f32>,
test_meshes: Vec<(MeshHandle, usize)>,
cubes: Vec<GameObjectHandle>,
log_config: LogConfig,
}
impl Game for TestGame {
fn on_window_event(self: &mut Self, event: &Event) {
self.input.on_window_event(event);
}
}
impl TestGame {
fn game_start(self: &mut Self, renderer: &mut VulkanRenderer) {
let (meshes, textures) = mesh::load_mesh("models/iski51.gltf", self.log_config.mesh_load_info).unwrap();
self.test_meshes = meshes.into_iter().map(|m| {
let id = match m.texture_index {
Some(tex_id) => tex_id + 1,
None => 0,
};
(renderer.upload_mesh(m), id)
}).collect();
textures.iter().for_each(|tex| renderer.upload_texture(tex));
println!("Game loaded!");
}
fn update(self: &mut Self, renderer: &mut VulkanRenderer) -> UniformBufferObject {
self.input.frame_start();
let time = (renderer.game_data.start_time.elapsed().unwrap().as_micros() as f64 / 1000000.0) as f32;
// User interaction
if self.input.button_just_released("quit") {
renderer.game_data.shutdown = true;
}
if self.input.button_just_pressed("reload_shaders") {
renderer.game_data.recreate_pipeline = true;
}
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") {
println!("test");
self.cubes = self.test_meshes.iter().map(|(mesh, tex_id)| renderer.add_game_object(GameObject::new(*mesh, *tex_id))).collect();
}
self.cam_rotation = self.cam_rotation * Quaternion::from_angle_y(Deg(self.input.get_axis("look_horizontal") * 0.05));
self.cam_rotation = Quaternion::from_angle_x(Deg(self.input.get_axis("look_vertical") * 0.05)) * self.cam_rotation;
self.cam_position += self.cam_rotation.invert().rotate_vector(Vector3::new(
self.input.get_axis("move_sideways") * -0.05,
0.0,
self.input.get_axis("move_forward") * 0.05));
// Move game objects
let view = Matrix4::from(self.cam_rotation) * Matrix4::from_translation(self.cam_position);
let mut proj = cgmath::perspective(
Rad::from(Deg(45.0)),
renderer.game_data.dimensions[0] as f32 / renderer.game_data.dimensions[1] as f32,
0.1,
100.0
);
proj.y.y *= -1.0;
renderer.game_data.line_push_constants.view = view.into();
renderer.game_data.line_push_constants.projection = proj.into();
self.input.frame_end();
UniformBufferObject {
view: view.into(),
projection: proj.into(),
time,
}
}
}
mod game;
mod tests;
mod text;
mod util;
mod perf;
fn main() {
let log_config = LogConfig::from_file("config/log.toml");
let mut game = TestGame {
input: InputState::new("config/input.toml", log_config),
cam_rotation: Quaternion::one(),
cam_position: Vector3::new(0.0, 3.0, -10.0),
test_meshes: vec![],
cubes: vec![],
log_config
};
let mut game = TestGame::new("config/input.toml", log_config);
let line_count = 30;
let mut renderer = VulkanRenderer::init(
(-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(
log_config.vulkan_validation_layers,
RenderConfig::from_file("config/graphics.toml")
);
game.game_start(&mut renderer);
let mut continue_rendering = true;
let mut ubo = game.update(&mut renderer);
while continue_rendering {
match renderer.render_loop(&mut game, ubo) {
RenderResult::Ok => ubo = game.update(&mut renderer),
RenderResult::Reload => println!("Render loop reloaded..."),
RenderResult::Quit => continue_rendering = false,
}
}
}
vulkan::start_event_loop(renderer, Box::new(game), event_loop);
}

View File

@@ -1,72 +0,0 @@
use crate::vulkan::Vertex;
use std::time::SystemTime;
pub struct CPUMesh {
pub vertices: Vec<Vertex>,
pub indices: Vec<u32>,
pub texture_index: Option<usize>,
pub name: Option<String>,
}
pub fn load_mesh(mesh_path: &str, print_status: bool) -> Result<(Vec<CPUMesh>, Vec<gltf::image::Data>), gltf::Error> {
let mut start_time = None;
let mut total_vertices = 0;
let mut total_indices = 0;
if print_status {
start_time = Some(SystemTime::now());
println!("Loading mesh file {}", mesh_path);
}
let (document, buffers, textures) = gltf::import(mesh_path)?;
let mut meshes = vec![];
if print_status { println!("Mesh file loaded after {} seconds, processing...", start_time.unwrap().elapsed().unwrap().as_secs()); }
for mesh in document.meshes() {
for primitive in mesh.primitives() {
let texture_index = primitive.material().pbr_metallic_roughness().base_color_texture().map(|tex_info| tex_info.texture().index());
let reader = primitive.reader(|buffer| Some(&buffers[buffer.index()]));
match (reader.read_indices(), reader.read_positions(), reader.read_tex_coords(0), reader.read_normals()) {
(Some(indices), Some(positions), Some(tex_coords), Some(normals)) => {
let vertices = positions.zip(tex_coords.into_f32()).zip(normals).map(|((p, t), n)| Vertex {
position: p,
uv: t,
normal: n
}).collect();
let cpu_mesh = CPUMesh {
vertices,
indices: indices.into_u32().collect(),
texture_index,
name: mesh.name().map(|n| n.to_owned()),
};
if print_status {
let vert_count = cpu_mesh.vertices.len();
let index_count = cpu_mesh.indices.len();
println!("Loaded mesh {} after {} seconds: {} Vertices, {} Indices",
mesh.name().unwrap_or(""),
start_time.unwrap().elapsed().unwrap().as_secs(),
vert_count,
index_count);
total_vertices += vert_count;
total_indices += index_count;
}
meshes.push(cpu_mesh);
},
(None, _, _, _) => println!("Indices missing!"),
(_, None, _, _) => println!("Vertex positions missing!"),
(_, _, None, _) => println!("Tex coords missing!"),
(_, _, _, None) => println!("Normals missing!"),
}
}
}
if print_status {
println!("Finished loading file, total vertices: {}, total indices: {}", total_vertices, total_indices);
}
Ok((meshes, textures))
}

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,
}
pub fn stddev(arr: &[u128; PERF_COUNTER_SIZE], mean: i128) -> f64 {
f64::sqrt(arr.iter().fold(0, |acc, b| acc + (*b as i128 - mean) * (*b as i128 - mean)) as f64 / PERF_COUNTER_SIZE as f64)
}
impl PerformanceCounter {
pub fn mean(&self) -> u128 {
self.values.iter().sum::<u128>() / PERF_COUNTER_SIZE as u128
}
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;
}
}
}
}
}

157
src/tests/mod.rs Normal file
View File

@@ -0,0 +1,157 @@
#[cfg(test)]
#[allow(deprecated)]
mod tests {
use cgmath::vec3;
use winit::{dpi::PhysicalPosition, event::{DeviceId, ElementState, Event, Force, KeyboardInput, ModifiersState, Touch, TouchPhase, WindowEvent}, window::WindowId};
use crate::{config::LogConfig, input::InputState, util::intersect_triangle};
use crate::input::vk_to_scan_code;
fn epsilon_eq(f1: f32, f2: f32) {
assert!(f32::abs(f1 - f2) < f32::EPSILON, "{} == {}", f1, f2);
}
fn epsilon_eq_option(o1: Option<f32>, o2: Option<f32>) {
if let Some(f1) = o1 {
if let Some(f2) = o2 {
epsilon_eq(f1, f2);
} else {
panic!("Some({}) == None", f1);
}
} else if let Some(f2) = o2 {
panic!("None == Some({})", f2);
}
}
#[test]
fn triangle_intersection() {
let zero = vec3(0.0, 0.0, 0.0);
let a = [2.0, 0.5, 0.5];
let b = [2.0, -0.5, 0.5];
let c = [2.0, 0.0, -0.5];
let dist1 = intersect_triangle(zero, vec3(1.0, 0.0, 0.0), a, b, c);
epsilon_eq_option(dist1, Some(2.0));
let dist2 = intersect_triangle(zero, vec3(0.0, 0.0, 1.0), a, b, c);
epsilon_eq_option(dist2, None);
let dist3 = intersect_triangle(zero, vec3(0.9950371902, 0.09950371902, 0.0), a, b, c);
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,633 +0,0 @@
use vulkano::buffer::{BufferUsage, CpuAccessibleBuffer};
use vulkano::command_buffer::{AutoCommandBufferBuilder, DynamicState, AutoCommandBuffer};
use vulkano::device::{Device, DeviceExtensions, Queue};
use vulkano::framebuffer::{Framebuffer, FramebufferAbstract, Subpass, RenderPassAbstract};
use vulkano::image::{SwapchainImage, AttachmentImage, ImageUsage, ImmutableImage, Dimensions};
use vulkano::instance::{Instance, PhysicalDevice, ApplicationInfo, Version, InstanceExtensions};
use vulkano::pipeline::{GraphicsPipeline, GraphicsPipelineAbstract};
use vulkano::pipeline::shader::{GraphicsShaderType, ShaderModule};
use vulkano::pipeline::viewport::Viewport;
use vulkano::swapchain::{AcquireError, PresentMode, SurfaceTransform, Swapchain, SwapchainCreationError, Surface};
use vulkano::swapchain;
use vulkano::sync::{GpuFuture, FlushError};
use vulkano::sync;
use vulkano::format::{Format, ClearValue};
use vulkano::instance::debug::{DebugCallback, MessageTypes};
use vulkano::memory::pool::{PotentialDedicatedAllocation, StdMemoryPoolAlloc};
use vulkano::descriptor::descriptor_set::{FixedSizeDescriptorSetsPool, FixedSizeDescriptorSet, PersistentDescriptorSetBuf, PersistentDescriptorSetImg, PersistentDescriptorSetSampler};
use vulkano::sampler::{Sampler, Filter, MipmapMode, SamplerAddressMode};
use vulkano_win::VkSurfaceBuild;
use winit::{EventsLoop, Window, WindowBuilder, Event, WindowEvent};
use std::sync::{Arc, Mutex};
use std::time::SystemTime;
use std::path::{PathBuf};
use std::ffi::{CStr};
use cgmath::{Matrix4, SquareMatrix};
use shade_runner;
use shade_runner::{CompiledShaders, Entry};
use shaderc;
use vs::ty::PushConstants;
use line_vs::ty::LinePushConstants;
use crate::mesh::{CPUMesh};
use image::{ImageFormat, ConvertBuffer, ImageBuffer, Rgb, Rgba};
const VALIDATION_LAYERS: &[&str] = &[
"VK_LAYER_LUNARG_standard_validation"
];
#[derive(Default, Debug, Clone)]
pub struct Vertex {
pub position: [f32; 3],
pub uv: [f32; 2],
pub normal: [f32; 3],
}
vulkano::impl_vertex!(Vertex, position, uv, normal);
#[derive(Default, Debug, Clone)]
pub struct LinePoint {
pub position: [f32; 3],
}
vulkano::impl_vertex!(LinePoint, position);
pub trait Game {
/// Returns true if event should be ignored by the vulkan handler
fn on_window_event(self: &mut Self, event: &Event);
}
pub struct Mesh {
vertex_buffer: Arc<CpuAccessibleBuffer<[Vertex]>>,
index_buffer: Arc<CpuAccessibleBuffer<[u32]>>,
}
pub struct GameObject {
pub mesh_index: usize,
pub texture_index: usize,
pub model_matrix: Matrix4<f32>,
}
pub(crate) type GameObjectHandle = usize;
pub(crate) type MeshHandle = usize;
type FixedGraphicsDescriptorSet = Arc<FixedSizeDescriptorSet<Arc<dyn GraphicsPipelineAbstract + Send + Sync>, ((), PersistentDescriptorSetBuf<Arc<vulkano::buffer::cpu_access::CpuAccessibleBuffer<vs::ty::UniformBufferObject>>>)>>;
//type FixedGraphicsDescriptorSet = Arc<FixedSizeDescriptorSet<Arc<dyn GraphicsPipelineAbstract + Send + Sync>, ((((), PersistentDescriptorSetBuf<Arc<CpuAccessibleBuffer<vs::ty::UniformBufferObject>>>), PersistentDescriptorSetImg<Arc<ImmutableImage<Format>>>), PersistentDescriptorSetSampler)>>;
pub struct GameData {
pub start_time: SystemTime,
pub line_vertices: Vec<LinePoint>,
pub push_constants: PushConstants,
pub line_push_constants: LinePushConstants,
pub recreate_pipeline: bool,
pub dimensions: [u32; 2],
pub shutdown: bool,
pub game_objects: Vec<GameObject>,
pub meshes: Vec<Mesh>,
pub textures: Vec<Arc<ImmutableImage<Format>>>,
}
pub struct VulkanRenderer {
pub game_data: GameData,
pub device: Arc<Device>,
pub framebuffers: Vec<Arc<FramebufferAbstract + Send + Sync>>,
pub sampler: Arc<Sampler>,
pub dynamic_state: DynamicState,
pub pipeline: Arc<GraphicsPipelineAbstract + Send + Sync>,
pub line_pipeline: Arc<GraphicsPipelineAbstract + Send + Sync>,
pub line_vertex_buffer: Arc<CpuAccessibleBuffer<[LinePoint], PotentialDedicatedAllocation<StdMemoryPoolAlloc>>>,
pub surface: Arc<Surface<Window>>,
pub swapchain: Arc<Swapchain<Window>>,
pub render_pass: Arc<RenderPassAbstract + Send + Sync>,
pub queue: Arc<Queue>,
pub events_loop: EventsLoop,
pub recreate_swapchain: bool,
pub debug_callback: Option<DebugCallback>,
pub previous_frame_end: Option<Box<GpuFuture>>,
pub uniform_buffers: Vec<Arc<CpuAccessibleBuffer<vs::ty::UniformBufferObject>>>,
pub descriptor_sets: Vec<FixedGraphicsDescriptorSet>,
}
pub enum RenderResult {
/// Contains buffer index and swapchain future
Ok,
Reload,
Quit,
}
impl VulkanRenderer {
pub fn init(line_vertices: Vec<LinePoint>, enable_validation_layers: bool) -> VulkanRenderer {
let mut data = GameData {
push_constants: PushConstants {
model: Matrix4::identity().into(),
},
line_push_constants: LinePushConstants {
model: Matrix4::identity().into(),
view: Matrix4::identity().into(),
projection: Matrix4::identity().into(),
},
start_time: SystemTime::now(),
recreate_pipeline: false,
shutdown: false,
line_vertices,
dimensions: [0, 0],
meshes: vec![],
game_objects: vec![],
textures: vec![],
};
if enable_validation_layers {
println!("Enabling validation layers...");
}
let instance = {
let extensions = InstanceExtensions {
ext_debug_report: true,
..vulkano_win::required_extensions()
};
let app_info = ApplicationInfo {
application_name: Some("Asuro's Editor".into()),
application_version: Some(Version { major: 0, minor: 1, patch: 0 }),
engine_name: Some("Asuro's Rust Engine".into()),
engine_version: Some(Version { major: 0, minor: 1, patch: 0 })
};
if enable_validation_layers {
let available_layers = vulkano::instance::layers_list().unwrap().map(|layer| String::from(layer.name())).collect::<Vec<String>>();
VALIDATION_LAYERS.iter().for_each(|wanted_layer_name| {
if !available_layers.iter().any(|available_layer_name| available_layer_name == wanted_layer_name) {
panic!("Validation layer not found: {:?}. Available layers: {:?}", wanted_layer_name, &available_layers.join(", "));
}
});
Instance::new(Some(&app_info), &extensions, VALIDATION_LAYERS.iter().cloned()).expect("failed to create Vulkan instance")
} else {
Instance::new(Some(&app_info), &extensions, None).expect("failed to create Vulkan instance")
}
};
// lifetime of this is important, even tho it isn't used!
let mut debug_callback = None;
if enable_validation_layers {
let msg_types = MessageTypes {
error: true,
warning: true,
performance_warning: true,
information: false,
debug: true,
};
debug_callback = DebugCallback::new(&instance, msg_types, |msg| {
let type_str = match (msg.ty.error, msg.ty.warning, msg.ty.performance_warning, msg.ty.information, msg.ty.debug) {
(true, _, _, _, _) => "!!",
(_, true, _, _, _) => "!",
(_, _, true, _, _) => "p",
(_, _, _, true, _) => "i",
_ => " "
};
let layer_str = msg.layer_prefix;
println!("[{}][{}]: {}", type_str, layer_str, msg.description);
}).ok();
}
let physical = PhysicalDevice::enumerate(&instance).next().unwrap();
println!("Using device: {} (type: {:?})", physical.name(), physical.ty());
let events_loop = EventsLoop::new();
let surface = WindowBuilder::new().build_vk_surface(&events_loop, instance.clone()).unwrap();
let window = surface.window();
// 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| {
q.supports_graphics() && surface.is_supported(q).unwrap_or(false)
}).unwrap();
let device_ext = DeviceExtensions { khr_swapchain: true, ..DeviceExtensions::none() };
let (device, mut queues) = Device::new(physical, physical.supported_features(), &device_ext,
[(queue_family, 0.5)].iter().cloned()).unwrap();
let queue = queues.next().unwrap();
let (swapchain, images) = {
let caps = surface.capabilities(physical).unwrap();
let usage = caps.supported_usage_flags;
let alpha = caps.supported_composite_alpha.iter().next().unwrap();
let format = caps.supported_formats[0].0;
data.dimensions = if let Some(dimensions) = window.get_inner_size() {
let dimensions: (u32, u32) = dimensions.to_physical(window.get_hidpi_factor()).into();
[dimensions.0, dimensions.1]
} else {
panic!("Couldn't get window dimensions!");
};
Swapchain::new(device.clone(), surface.clone(), caps.min_image_count, format,
data.dimensions, 1, usage, &queue, SurfaceTransform::Identity, alpha,
PresentMode::Fifo, true, None).unwrap()
};
let line_vertex_buffer = CpuAccessibleBuffer::from_iter(device.clone(), BufferUsage::vertex_buffer(), data.line_vertices.iter().cloned()).unwrap();
let render_pass = Arc::new(vulkano::single_pass_renderpass!(
device.clone(),
attachments: {
color: {
load: Clear,
store: Store,
format: swapchain.format(),
samples: 1,
},
depth: {
load: Clear,
store: DontCare,
format: Format::D16Unorm,
samples: 1,
initial_layout: ImageLayout::Undefined,
final_layout: ImageLayout::DepthStencilAttachmentOptimal,
}
},
pass: {
color: [color],
depth_stencil: {depth}
}
).unwrap());
let sampler = Sampler::new(device.clone(), Filter::Linear, Filter::Linear,
MipmapMode::Nearest, SamplerAddressMode::Repeat, SamplerAddressMode::Repeat,
SamplerAddressMode::Repeat, 0.0, 1.0, 0.0, 0.0).unwrap();
let pipeline: Arc<GraphicsPipelineAbstract + Send + Sync> =
create_pipeline::<Vertex>(device.clone(), render_pass.clone(), "shaders/triangle.vert", "shaders/triangle.frag", false).unwrap();
let line_pipeline: Arc<GraphicsPipelineAbstract + Send + Sync> =
create_pipeline::<LinePoint>(device.clone(), render_pass.clone(), "shaders/line.vert", "shaders/line.frag", true).unwrap();
let (default_tex, default_tex_future) = {
let image = image::load_from_memory_with_format(include_bytes!("../models/missing-texture.jpg"),
ImageFormat::JPEG).unwrap().to_rgba();
let image_data = image.into_raw().clone();
ImmutableImage::from_iter(
image_data.iter().cloned(),
Dimensions::Dim2d { width: 128, height: 128 },
Format::R8G8B8A8Unorm,
queue.clone(),
).unwrap()
};
default_tex_future.flush().unwrap();
// 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 };
// 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.
let framebuffers = window_size_dependent_setup(device.clone(), &images, render_pass.clone(), &mut dynamic_state);
let mut uniform_buffers = Vec::new();
let uniform_buffer = vs::ty::UniformBufferObject { view: Matrix4::identity().into(), projection: Matrix4::identity().into(), time: 0.0 };
for _ in 0..swapchain.num_images() {
uniform_buffers.push(CpuAccessibleBuffer::from_data(
device.clone(),
BufferUsage::uniform_buffer_transfer_destination(),
uniform_buffer,
).unwrap());
}
let descriptor_set_pool = Mutex::new(FixedSizeDescriptorSetsPool::new(pipeline.clone(), 0));
let descriptor_sets = uniform_buffers
.iter()
.map(|uniform_buffer| {
Arc::new(
descriptor_set_pool
.lock().unwrap().next()
.add_buffer(uniform_buffer.clone()).unwrap()
// .add_sampled_image(default_tex.clone(), sampler.clone()).unwrap()
.build().unwrap())
})
.collect();
data.textures.push(default_tex);
// In the loop below we are going to submit commands to the GPU. Submitting a command produces
// an object that implements the `GpuFuture` trait, which holds the resources for as long as
// they are in use by the GPU.
//
// Destroying the `GpuFuture` blocks until the GPU is finished executing it. In order to avoid
// that, we store the submission of the previous frame here.
let previous_frame_end = Some(Box::new(sync::now(device.clone())) as Box<dyn GpuFuture>);
VulkanRenderer { game_data: data, device, framebuffers, sampler,
dynamic_state, pipeline, line_pipeline, uniform_buffers, descriptor_sets,
surface, swapchain, render_pass, queue, line_vertex_buffer, events_loop,
recreate_swapchain: false, debug_callback, previous_frame_end }
}
fn create_command_buffer(self: &mut Self, fb_index: usize, ubo: vs::ty::UniformBufferObject) -> Arc<AutoCommandBuffer> {
let mut cbb = AutoCommandBufferBuilder::primary_simultaneous_use(self.device.clone(), self.queue.family()).unwrap()
.update_buffer(self.uniform_buffers[fb_index].clone(), ubo).unwrap()
.begin_render_pass(self.framebuffers[fb_index].clone(), false, vec![ClearValue::Float([0.0, 0.0, 0.0, 1.0]), ClearValue::Depth(1.0)]).unwrap();
for i in 0..self.game_data.game_objects.len() {
let game_object = &self.game_data.game_objects[i];
let mesh = &self.game_data.meshes[game_object.mesh_index];
self.game_data.push_constants.model = game_object.model_matrix.into();
cbb = cbb.draw_indexed(
self.pipeline.clone(),
&self.dynamic_state,
vec![mesh.vertex_buffer.clone()],
mesh.index_buffer.clone(),
self.descriptor_sets[fb_index].clone(),
self.game_data.push_constants.clone()).unwrap()
}
cbb = cbb.draw(self.line_pipeline.clone(), &self.dynamic_state, vec![self.line_vertex_buffer.clone()], (), self.game_data.line_push_constants.clone()).unwrap()
.end_render_pass().unwrap();
Arc::new(cbb.build().unwrap())
}
/// Returns false if rendering should stop
pub fn render_loop(self: &mut Self, game: &mut dyn Game, new_ubo: vs::ty::UniformBufferObject) -> RenderResult {
// It is important to call this function from time to time, otherwise resources will keep
// accumulating and you will eventually reach an out of memory error.
// Calling this function polls various fences in order to determine what the GPU has
// already processed, and frees the resources that are no longer needed.
self.previous_frame_end.as_mut().unwrap().cleanup_finished();
if self.recreate_swapchain {
let window = self.surface.window();
self.game_data.dimensions = if let Some(dimensions) = window.get_inner_size() {
let dimensions: (u32, u32) = dimensions.to_physical(window.get_hidpi_factor()).into();
[dimensions.0, dimensions.1]
} else {
panic!("Window no longer exists!");
};
let (new_swapchain, new_images) = match self.swapchain.recreate_with_dimension(self.game_data.dimensions) {
Ok(r) => r,
// This error tends to happen when the user is manually resizing the window.
// Simply restarting the loop is the easiest way to fix this issue.
Err(SwapchainCreationError::UnsupportedDimensions) => {
println!("Swapchain rejected: UnsupportedDimensions");
return RenderResult::Reload;
}
Err(err) => panic!("{:?}", err),
};
self.swapchain = new_swapchain;
// Because framebuffers contains an Arc on the old swapchain, we need to
// recreate framebuffers as well.
self.framebuffers = window_size_dependent_setup(self.device.clone(), &new_images, self.render_pass.clone(), &mut self.dynamic_state);
self.recreate_swapchain = false;
}
if self.game_data.recreate_pipeline {
if let Some(pipeline_ok) = create_pipeline::<Vertex>(self.device.clone(), self.render_pass.clone(), "shaders/triangle.vert", "shaders/triangle.frag", false) {
self.pipeline = pipeline_ok;
println!("Updated pipeline.");
} else {
println!("Failed to update pipeline.");
}
self.game_data.recreate_pipeline = false;
}
// Before we can draw on the output, we have to *acquire* an image from the swapchain. If
// no image is available (which happens if you submit draw commands too quickly), then the
// function will block.
// This operation returns the index of the image that we are allowed to draw upon.
//
// This function can block if no image is available. The parameter is an optional timeout
// after which the function call will return an error.
let (fb_index, acquire_future) = match swapchain::acquire_next_image(self.swapchain.clone(), None) {
Ok(r) => r,
Err(AcquireError::OutOfDate) => {
self.recreate_swapchain = true;
return RenderResult::Reload;
},
Err(err) => panic!("{:?}", err)
};
let command_buffer = self.create_command_buffer(fb_index, new_ubo).clone();
let future = self.previous_frame_end.take().unwrap().join(acquire_future)
.then_execute(self.queue.clone(), command_buffer).unwrap()
.then_swapchain_present(self.queue.clone(), self.swapchain.clone(), fb_index)
.then_signal_fence_and_flush();
match future {
Ok(future) => {
self.previous_frame_end = Some(Box::new(future) as Box<_>);
},
Err(FlushError::OutOfDate) => {
println!("Swapchain out of date!");
self.recreate_swapchain = true;
self.previous_frame_end = Some(Box::new(sync::now(self.device.clone())) as Box<_>);
}
Err(e) => {
println!("{:?}", e);
self.previous_frame_end = Some(Box::new(sync::now(self.device.clone())) as Box<_>);
}
};
// Note that in more complex programs it is likely that one of `acquire_next_image`,
// `command_buffer::submit`, or `present` will block for some time. This happens when the
// GPU's queue is full and the driver has to wait until the GPU finished some work.
//
// Unfortunately the Vulkan API doesn't provide any way to not wait or to detect when a
// wait would happen. Blocking may be the desired behavior, but if you don't want to
// block you should spawn a separate thread dedicated to submissions.
let mut window_closed = false;
let mut resized = false;
self.events_loop.poll_events(|ev| {
game.on_window_event(&ev);
match ev {
Event::WindowEvent { event: WindowEvent::CloseRequested, .. } => window_closed = true,
Event::WindowEvent { event: WindowEvent::Resized(_), .. } => resized = true,
_ => {}
}
});
if resized { self.recreate_swapchain = true }
if self.game_data.shutdown || window_closed { return RenderResult::Quit; }
RenderResult::Ok
}
pub fn upload_mesh(self: &mut Self, mesh: CPUMesh) -> usize {
let vertex_buffer = CpuAccessibleBuffer::from_iter(self.device.clone(), BufferUsage::vertex_buffer(), mesh.vertices.into_iter()).unwrap();
let index_buffer = CpuAccessibleBuffer::from_iter(self.device.clone(), BufferUsage::index_buffer(), mesh.indices.into_iter()).unwrap();
self.game_data.meshes.push(Mesh { vertex_buffer, index_buffer });
self.game_data.meshes.len() - 1
}
pub fn upload_texture(self: &mut Self, texture_data: &gltf::image::Data) {
let buffer: ImageBuffer<Rgb<u8>, Vec<u8>> = image::ImageBuffer::from_raw(texture_data.width, texture_data.height, texture_data.pixels.clone()).unwrap();
let new_buffer: ImageBuffer<Rgba<u8>, Vec<u8>> = buffer.convert();
let (image_view, future) = ImmutableImage::from_iter(
new_buffer.iter().cloned(),
Dimensions::Dim2d { width: texture_data.width, height: texture_data.height },
Format::R8G8B8A8Unorm,
self.queue.clone(),
).unwrap();
future.flush().unwrap();
self.game_data.textures.push(image_view);
}
pub fn add_game_object(self: &mut Self, game_object: GameObject) -> usize {
self.game_data.game_objects.push(game_object);
self.game_data.game_objects.len() - 1
}
}
/// This method is called once during initialization, then again whenever the window is resized
fn window_size_dependent_setup(device: Arc<Device>, images: &[Arc<SwapchainImage<Window>>], render_pass: Arc<dyn RenderPassAbstract + Send + Sync>, dynamic_state: &mut DynamicState) -> Vec<Arc<dyn FramebufferAbstract + Send + Sync>> {
let dimensions = images[0].dimensions();
let viewport = Viewport {
origin: [0.0, 0.0],
dimensions: [dimensions[0] as f32, dimensions[1] as f32],
depth_range: 0.0 .. 1.0,
};
dynamic_state.viewports = Some(vec!(viewport));
let depth_image = AttachmentImage::with_usage(device.clone(), dimensions, Format::D16Unorm, ImageUsage { depth_stencil_attachment: true, ..ImageUsage::none() }).unwrap();
images.iter().map(|image| {
Arc::new(Framebuffer::start(render_pass.clone())
.add(image.clone()).unwrap()
.add(depth_image.clone()).unwrap()
.build().unwrap()
) as Arc<dyn FramebufferAbstract + Send + Sync>
}).collect::<Vec<_>>()
}
pub mod vs {
vulkano_shaders::shader!{
ty: "vertex",
path: "shaders/triangle.vert"
}
}
pub mod line_vs {
vulkano_shaders::shader!{
ty: "vertex",
path: "shaders/line.vert"
}
}
pub mod fs {
vulkano_shaders::shader! {
ty: "fragment",
path: "shaders/triangle.frag"
}
}
pub mod line_fs {
vulkano_shaders::shader! {
ty: "fragment",
path: "shaders/line.frag"
}
}
fn create_pipeline<V: vulkano::pipeline::vertex::Vertex>(device: Arc<Device>, render_pass: Arc<RenderPassAbstract + Send + Sync>, vertex_shader_path: &str, fragment_shader_path: &str, is_line: bool) -> Option<Arc<GraphicsPipelineAbstract + Send + Sync>> {
if let Some((shader, shader_data)) = read_shader(vertex_shader_path, fragment_shader_path) {
let sub_pass = Subpass::from(render_pass.clone(), 0).unwrap();
let vertex_shader_entry;
let fragment_shader_entry;
let vertex_shader_module;
let fragment_shader_module;
unsafe {
vertex_shader_module = ShaderModule::from_words(device.clone(), &shader.vertex).expect("Failed to load vertex shader.");
vertex_shader_entry = vertex_shader_module.graphics_entry_point(
CStr::from_bytes_with_nul_unchecked(b"main\0"),
shader_data.vert_input,
shader_data.vert_output,
shader_data.vert_layout,
GraphicsShaderType::Vertex);
fragment_shader_module = ShaderModule::from_words(device.clone(), &shader.fragment).expect("Failed to load fragment shader.");
fragment_shader_entry = fragment_shader_module.graphics_entry_point(
CStr::from_bytes_with_nul_unchecked(b"main\0"),
shader_data.frag_input,
shader_data.frag_output,
shader_data.frag_layout,
GraphicsShaderType::Fragment);
};
let pipeline;
if is_line {
pipeline = Arc::new(GraphicsPipeline::start()
.vertex_input_single_buffer::<V>()
.vertex_shader(vertex_shader_entry.clone(), ())
.line_list()
.viewports_dynamic_scissors_irrelevant(1)
.depth_stencil_simple_depth()
.fragment_shader(fragment_shader_entry.clone(), ())
.render_pass(sub_pass.clone())
.build(device.clone())
.unwrap());
} else {
pipeline = Arc::new(GraphicsPipeline::start()
.vertex_input_single_buffer::<V>()
.vertex_shader(vertex_shader_entry.clone(), ())
.triangle_list()
.viewports_dynamic_scissors_irrelevant(1)
.fragment_shader(fragment_shader_entry.clone(), ())
.depth_stencil_simple_depth()
.blend_alpha_blending()
.cull_mode_back()
.render_pass(sub_pass.clone())
.build(device.clone())
.unwrap());
}
return Some(pipeline);
} else {
return None;
}
}
impl GameObject {
pub fn new(mesh: MeshHandle, texture_index: usize) -> GameObject {
GameObject { mesh_index: mesh, texture_index, model_matrix: Matrix4::identity() }
}
}
fn read_shader(vert_path_relative: &str, frag_path_relative: &str) -> Option<(CompiledShaders, Entry)> {
let project_root = std::env::current_dir().expect("failed to get root directory");
let mut vert_path = project_root.clone();
vert_path.push(PathBuf::from(vert_path_relative));
let mut frag_path = project_root.clone();
frag_path.push(PathBuf::from(frag_path_relative));
let shader_result = shade_runner::load(vert_path, frag_path);
match shader_result {
Ok(shader) => {
let shader_data = shade_runner::parse(&shader).expect("Failed to parse");
return Some((shader, shader_data));
}
Err(shade_runner::error::Error::Compile(shade_runner::error::CompileError::Compile(shaderc::Error::CompilationError(line, error)))) => {
println!("Shader line {}: {:?}", line, error);
return None;
}
Err(error) => {
println!("Shader compilation error: {:?}", error);
return None;
}
}
}

47
src/vulkan/dds.rs Normal file
View File

@@ -0,0 +1,47 @@
use std::{convert::TryInto, io::Read};
use vulkano::{format::Format, sampler::{Filter, SamplerAddressMode}};
use super::{Texture, VulkanRenderer};
pub fn upload_texture_from_file(path: &str, renderer: &mut VulkanRenderer) -> Result<Texture, Box<dyn std::error::Error>> {
// Load file
let mut tex_file = std::fs::File::open(path)?;
let mut tex_bytes: Vec<u8> = vec![];
tex_file.read_to_end(&mut tex_bytes)?;
// Parse DDS
let tex_height = u32::from_ne_bytes(tex_bytes[12..16].try_into()?);
let tex_width = u32::from_ne_bytes(tex_bytes[16..20].try_into()?);
let tex_byte_count = u32::from_ne_bytes(tex_bytes[20..24].try_into()?);
let pixel_format_flags = u32::from_ne_bytes(tex_bytes[80..84].try_into()?);
let pixel_format_name: [u8; 4] = tex_bytes[84..88].try_into()?;
let is_dxt1 = pixel_format_name == [0x44, 0x58, 0x54, 0x31]; // [D,X,T,1]
let is_dx10 = pixel_format_name == [0x44, 0x58, 0x31, 0x30]; // [D,X,1,0]
assert!(pixel_format_flags == 0x00000004);
assert!(is_dxt1 || is_dx10);
println!("Texture width: {}, height: {}, bytes: {}", tex_width, tex_height, tex_byte_count);
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())
} else if is_dx10 {
let dxgi_type = u32::from_ne_bytes(tex_bytes[128..132].try_into()?);
assert!(dxgi_type == 83); // BC5 Unorm Typeless
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(texture)
}
pub fn get_block_size(format: Format) -> Option<u32> {
match format {
Format::BC1_RGB_UNORM_BLOCK => Some(8),
Format::BC5_UNORM_BLOCK => Some(16),
_ => None
}
}

View File

@@ -0,0 +1,67 @@
use std::sync::Arc;
use vulkano::device::Device;
use vulkano::format::Format;
use vulkano::image::view::ImageView;
use vulkano::image::{AttachmentImage, ImageUsage, SampleCount, SwapchainImage};
use vulkano::render_pass::{Framebuffer, FramebufferAbstract, RenderPass};
use winit::window::Window;
use vulkano::swapchain::Swapchain;
/// This method is called once during initialization, then again whenever the window is resized
pub fn create_framebuffers(device: Arc<Device>,
swapchain: &Arc<Swapchain<Window>>,
images: &[Arc<SwapchainImage<Window>>],
msaa_sample_count: Option<SampleCount>,
render_pass: Arc<RenderPass>)
-> Vec<Arc<dyn FramebufferAbstract + Send + Sync>> {
let dim_array = images[0].dimensions();
let depth_image = if let Some(sample_count) = msaa_sample_count {
AttachmentImage::multisampled_with_usage(device.clone(), dim_array, sample_count, Format::D16_UNORM, ImageUsage { depth_stencil_attachment: true, ..ImageUsage::none() }).unwrap()
} else {
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(sample_count) = msaa_sample_count {
Some(create_msaa_buffers(device.clone(), dim_array, swapchain, sample_count))
} else {
None
};
let mut framebuffers = vec![];
for i in 0..images.len() {
let image_buffer = &images[i];
let view = ImageView::new(image_buffer.clone()).unwrap();
if let Some(msaa_buffers_exist) = &msaa_buffers {
let msaa_view = ImageView::new((&msaa_buffers_exist[i]).clone()).unwrap();
framebuffers.push(Arc::new(Framebuffer::start(render_pass.clone())
.add(view).unwrap()
.add(msaa_view).unwrap()
.add(depth_image_view.clone()).unwrap()
.build().unwrap()
) as Arc<dyn FramebufferAbstract + Send + Sync>);
} else {
framebuffers.push(Arc::new(Framebuffer::start(render_pass.clone())
.add(view).unwrap()
.add(depth_image_view.clone()).unwrap()
.build().unwrap()
) as Arc<dyn FramebufferAbstract + Send + Sync>);
}
}
framebuffers
}
fn create_msaa_buffers(device: Arc<Device>, dimensions: [u32; 2], swapchain: &Arc<Swapchain<Window>>, sample_count: SampleCount) -> Vec<Arc<AttachmentImage>> {
let mut msaa_attachments = vec![];
for _ in 0..swapchain.num_images() {
msaa_attachments.push(AttachmentImage::transient_multisampled(device.clone(), dimensions, sample_count, swapchain.format()).unwrap());
}
msaa_attachments
}

87
src/vulkan/gameobject.rs Normal file
View File

@@ -0,0 +1,87 @@
use std::sync::Arc;
use cgmath::{Deg, Euler, Matrix4, Quaternion, Vector3};
use vulkano::descriptor_set::PersistentDescriptorSet;
use crate::game::GameState;
use crate::input::InputState;
use crate::vulkan::TextureHandle;
use crate::vulkan::{MeshHandle, VulkanRenderer};
use super::pipelines::vs;
#[derive(Clone)]
pub struct GameObject {
pub mesh_index: usize,
pub textures: Vec<TextureHandle>,
pub position: Vector3<f32>,
pub rotation: Quaternion<f32>,
pub scale: Vector3<f32>,
pub children: Vec<GameObject>,
pub descriptor_sets: Vec<Vec<Arc<PersistentDescriptorSet>>>,
pub is_selected: bool,
pub pipeline_index: usize,
pub visible: bool,
}
pub enum PushConstantType {
MeshPC(vs::ty::PushConstants),
}
impl GameObject {
pub fn new(mesh: MeshHandle) -> GameObject {
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![],
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) {
self.position.x = x;
self.position.y = y;
self.position.z = z;
}
pub fn _set_scale(&mut self, x: f32, y: f32, z: f32) {
self.scale.x = x;
self.scale.y = y;
self.scale.z = z;
}
pub fn _set_rotation(&mut self, euler_x: f32, euler_y: f32, euler_z: f32) {
self.rotation = Quaternion::from(Euler::new(Deg(euler_x), Deg(euler_y), Deg(euler_z)));
}
pub fn _translate(&mut self, x: f32, y: f32, z: f32) {
self.position.x += x;
self.position.y += y;
self.position.z += z;
}
pub fn _rotate(&mut self, x: f32, y: f32, z: f32) {
self.rotation = self.rotation * Quaternion::from(Euler::new(Deg(x), Deg(y), Deg(z)));
}
pub fn get_push_constants(&self) -> PushConstantType {
PushConstantType::MeshPC(vs::ty::PushConstants {
model: self.get_model_matrix().into(),
is_selected: if self.is_selected { 1 } else { 0 },
})
}
pub fn get_model_matrix(&self) -> Matrix4<f32> {
let translation = Matrix4::from_translation(self.position);
let rotation: Matrix4<f32> = self.rotation.into();
let scale = Matrix4::from_nonuniform_scale(self.scale.x, self.scale.y, self.scale.z);
translation * rotation * scale
}
}
pub type GameObjectHandle = usize;
pub trait Updatable {
fn update(&mut self, delta_time: f32, input: &InputState, game_state: &mut GameState, game_objects: &mut Vec<GameObject>, renderer: &mut VulkanRenderer);
}

187
src/vulkan/mesh.rs Normal file
View File

@@ -0,0 +1,187 @@
use std::{collections::HashMap, io::Read, time::SystemTime};
use gltf::{Document, Error, buffer::Source};
use gltf::mesh::util::{ReadJoints, ReadNormals, ReadPositions, ReadTangents, ReadTexCoords, ReadWeights};
use crate::vulkan::mesh::LoadError::{GltfError, MeshDataMissing, NoIndices};
use crate::vulkan::Vertex;
use super::TextVertex;
#[derive(Debug)]
pub enum LoadError {
GltfError(gltf::Error),
MeshDataMissing(String),
NoIndices
}
impl From<gltf::Error> for LoadError {
fn from(e: Error) -> Self {
GltfError(e)
}
}
impl From<String> for LoadError {
fn from(e: String) -> Self {
MeshDataMissing(e)
}
}
#[derive(Debug)]
pub enum CPUVertexList {
Vertex3D(Vec<Vertex>),
VertexText(Vec<TextVertex>)
}
#[derive(Debug)]
pub struct CPUMesh {
pub vertices: CPUVertexList,
pub indices: Vec<u32>,
pub local_texture_index: Option<usize>,
pub local_normal_map_index: Option<usize>,
pub name: Option<String>,
}
fn read_file(path: &str) -> Vec<u8> {
let mut glb_file = std::fs::File::open(path).unwrap();
let mut glb_bytes = vec![];
glb_file.read_to_end(&mut glb_bytes).unwrap();
glb_bytes
}
pub fn load_mesh<V>(mesh_path: &str, print_status: bool) -> Result<(Vec<CPUMesh>, Document), LoadError> {
let mut start_time = None;
let mut total_vertices = 0;
let mut total_indices = 0;
if print_status {
start_time = Some(SystemTime::now());
}
let document = gltf::Gltf::open(mesh_path)?.document;
let mut meshes = vec![];
let mut mesh_data: HashMap<&str, Vec<u8>> = HashMap::new();
for b in document.buffers() {
if let Source::Uri(uri) = b.source() {
mesh_data.insert(uri, read_file(&format!("models/{}", uri)));
}
}
for mesh in document.meshes() {
for primitive in mesh.primitives() {
let texture_index = primitive.material().pbr_metallic_roughness().base_color_texture().map(|tex_info| tex_info.texture().index());
let normal_map_index = primitive.material().normal_texture().map(|tex_info| tex_info.texture().index());
let reader = primitive.reader(|buffer| {
if let Source::Uri(uri) = buffer.source() {
Some(&mesh_data[uri])
} else {
panic!("Loading embedded gltf binary is not supported!");
}
});
let indices = reader.read_indices().ok_or(NoIndices)?;
let vertices_result = create_vertices(
reader.read_positions(),
reader.read_tex_coords(0),
reader.read_normals(),
reader.read_tangents(),
reader.read_joints(0),
reader.read_weights(0));
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 {
vertices: CPUVertexList::Vertex3D(verts),
indices: inds,
local_texture_index: texture_index,
local_normal_map_index: normal_map_index,
name: mesh.name().map(|n| n.to_owned()),
};
if print_status {
let index_count = cpu_mesh.indices.len();
total_vertices += vert_count;
total_indices += index_count;
}
meshes.push(cpu_mesh);
}
}
if print_status {
println!("Finished loading {} after {}ms, total vertices: {}, total indices: {}", mesh_path, start_time.unwrap().elapsed().unwrap().as_millis(), total_vertices, total_indices);
}
Ok((meshes, document))
}
fn create_vertices(positions: Option<ReadPositions>,
tex_coords: Option<ReadTexCoords>,
normals: Option<ReadNormals>,
tangents: Option<ReadTangents>,
joints: Option<ReadJoints>,
weights: Option<ReadWeights>)
-> Result<Vec<Vertex>, String> {
match (positions, tex_coords, normals, tangents, joints, weights) {
(Some(positions),
Some(tex_coords),
Some(normals),
Some(tangents),
Some(joints),
Some(weights)) => {
Ok(positions
.zip(tex_coords.into_f32())
.zip(normals)
.zip(tangents)
.zip(joints.into_u16().map(|arr| {
let mut casted_joints: [i32; 4] = [0; 4];
for i in 0..4 {
casted_joints[i] = arr[i] as i32;
}
casted_joints
}))
.zip(weights.into_f32())
.map(|(((((p, t), n), ta), i), w)| Vertex {
position: p,
uv: t,
normal: n,
tangent: ta,
bone_index: i,
bone_weight: w
}).collect())
},
(Some(positions),
Some(tex_coords),
Some(normals),
Some(tangents),
None, None) => {
Ok(positions
.zip(tex_coords.into_f32())
.zip(normals)
.zip(tangents)
.map(|(((p, t), n), ta)| Vertex {
position: p,
uv: t,
normal: n,
tangent: ta,
bone_index: [-1; 4],
bone_weight: [0.0; 4]
}).collect())
},
(None, _, _, _, _, _) => Err("Vertex positions missing!".to_string()),
(_, None, _, _, _, _) => Err("Tex coords missing!".to_string()),
(_, _, None, _, _, _) => Err("Normals missing!".to_string()),
(_, _, _, None, _, _) => Err("Tangents missing!".to_string()),
(_, _, _, _, Some(_), None) => Err("Bone indices exist, but bone weights are missing!".to_string()),
(_, _, _, _, None, Some(_)) => Err("Bone weights exist, but bone incides are missing!".to_string()),
}
}

717
src/vulkan/mod.rs Normal file
View File

@@ -0,0 +1,717 @@
use std::sync::Arc;
use std::time::{Instant, SystemTime};
use cgmath::{Matrix4, SquareMatrix};
use dds::get_block_size;
use rust_engine_proc::perf;
use vulkano::device::physical::PhysicalDevice;
use vulkano::pipeline::viewport::Viewport;
use vulkano::render_pass::{FramebufferAbstract, RenderPass};
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::image::{ImageCreateFlags, ImageDimensions, ImmutableImage};
use vulkano::instance::{ApplicationInfo, Instance, InstanceExtensions, Version};
use vulkano::instance::debug::{DebugCallback, MessageSeverity, MessageType};
use vulkano::sampler::{Filter, MipmapMode, Sampler, SamplerAddressMode};
use vulkano::swapchain::{AcquireError, FullscreenExclusive, Surface, SurfaceTransform, Swapchain, SwapchainCreationError};
use vulkano::swapchain;
use vulkano::sync::{FlushError, GpuFuture};
use vulkano::sync;
use vulkano_win::VkSurfaceBuild;
use winit::event::{Event, WindowEvent};
use winit::event_loop::{ControlFlow, EventLoop};
use mesh::CPUMesh;
use pipelines::Drawcall;
use pipelines::{DefaultShader, TextShader};
use pipelines::vs;
use winit::window::{Window, WindowBuilder};
use crate::config::RenderConfig;
use crate::perf::{self, PerformanceCounter};
use crate::vulkan::gameobject::GameObject;
use self::mesh::CPUVertexList;
pub mod pipelines;
pub mod gameobject;
pub mod mesh;
pub mod dds;
mod renderpass;
mod framebuffers;
const VALIDATION_LAYERS: &[&str] = &[
"VK_LAYER_KHRONOS_validation",
];
#[derive(Default, Debug, Clone)]
pub struct Vertex {
pub position: [f32; 3],
pub uv: [f32; 2],
pub normal: [f32; 3],
pub tangent: [f32; 4],
pub bone_index: [i32; 4],
pub bone_weight: [f32; 4],
}
vulkano::impl_vertex!(Vertex, position, uv, normal, tangent, bone_index, bone_weight);
#[derive(Default, Debug, Clone)]
pub struct LinePoint {
pub position: [f32; 3],
}
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 {
/// Returns true if event should be ignored by the vulkan handler
fn on_window_event(self: &mut Self, event: &Event<()>);
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<V> {
pub vertex_buffer: Arc<CpuAccessibleBuffer<[V]>>,
pub index_buffer: Arc<CpuAccessibleBuffer<[u32]>>,
pub original_path: Option<String>,
}
#[derive(Debug, Clone)]
pub struct MeshHandle {
pub index: usize,
pub textures: Vec<TextureHandle>,
pub original_path: Option<String>,
pub pipeline_index: usize
}
pub(crate) type TextureHandle = usize;
#[derive(Debug, Clone)]
pub struct Texture {
pub image: Arc<ImmutableImage>,
pub sampler: Arc<Sampler>
}
pub struct GameData {
pub start_time: SystemTime,
pub recreate_pipeline: bool,
pub dimensions: [u32; 2],
pub shutdown: bool,
pub meshes: Vec<Mesh<Vertex>>,
pub meshes_text: Vec<Mesh<TextVertex>>,
pub textures: Vec<Texture>,
}
pub struct VulkanRenderer {
pub game_data: GameData,
pub device: Arc<Device>,
pub framebuffers: Vec<Arc<dyn FramebufferAbstract + Send + Sync>>,
pub pipelines: Vec<Box<dyn Drawcall>>,
pub surface: Arc<Surface<Window>>,
pub swapchain: Arc<Swapchain<Window>>,
pub render_pass: Arc<RenderPass>,
pub queue: Arc<Queue>,
pub recreate_swapchain: bool,
pub debug_callback: Option<DebugCallback>,
pub previous_frame_end: Option<Box<dyn GpuFuture>>,
pub uniform_buffers: Vec<Arc<CpuAccessibleBuffer<vs::ty::ObjectUniformData>>>,
pub render_config: RenderConfig,
pub viewport: Viewport,
pub fps_counter: [u128; perf::PERF_COUNTER_SIZE],
pub fps_counter_index: usize,
pub fps_counter_instant: Instant,
}
impl VulkanRenderer {
pub fn init(enable_validation_layers: bool, render_config: RenderConfig) -> (VulkanRenderer, EventLoop<()>) {
// Create empty game data struct to be filled
let mut data = GameData {
start_time: SystemTime::now(),
recreate_pipeline: false,
shutdown: false,
dimensions: [0, 0],
meshes: vec![],
meshes_text: vec![],
textures: vec![],
};
// Create basic vulkan instance with layers and info
let instance = {
let extensions = InstanceExtensions {
ext_debug_utils: true,
..vulkano_win::required_extensions()
};
println!("Using extensions: {:?}", extensions);
let app_info = ApplicationInfo {
application_name: Some("Asuro's Editor".into()),
application_version: Some(Version { major: 0, minor: 1, patch: 0 }),
engine_name: Some("Asuro's Rust Engine".into()),
engine_version: Some(Version { major: 0, minor: 1, patch: 0 })
};
if enable_validation_layers {
println!("Enabling validation layers...");
let available_layers = vulkano::instance::layers_list().unwrap().map(|layer| String::from(layer.name())).collect::<Vec<String>>();
println!("Available layers: {:?}", available_layers);
VALIDATION_LAYERS.iter().for_each(|wanted_layer_name| {
if !available_layers.iter().any(|available_layer_name| available_layer_name == wanted_layer_name) {
panic!("Validation layer not found: {:?}", wanted_layer_name);
}
});
Instance::new(Some(&app_info), Version::V1_1, &extensions, VALIDATION_LAYERS.iter().cloned()).expect("failed to create Vulkan instance")
} else {
Instance::new(Some(&app_info), Version::V1_1, &extensions, None).expect("failed to create Vulkan instance")
}
};
// lifetime of this is important, even tho it isn't used!
let mut debug_callback = None;
// Debug stuff
if enable_validation_layers {
let msg_severity = MessageSeverity {
verbose: false,
information: true,
warning: true,
error: true
};
let msg_types = MessageType {
general: true,
performance: true,
validation: true
};
debug_callback = DebugCallback::new(&instance, msg_severity, msg_types, |msg| {
let type_str = match (msg.severity.error, msg.severity.warning, msg.severity.information, msg.severity.verbose) {
(true, _, _, _) => "!!",
(_, true, _, _) => "!",
(_, _, true, _) => "i",
_ => "v"
};
let layer_str = msg.layer_prefix;
println!("[{}][{}]: {}", type_str, layer_str.unwrap_or(""), msg.description);
}).ok();
}
// TODO: Create device selector
let physical = PhysicalDevice::enumerate(&instance).next().unwrap();
let events_loop = EventLoop::new();
let surface = WindowBuilder::new().build_vk_surface(&events_loop, instance.clone()).unwrap();
let window = surface.window();
// Queue
let queue_family = physical.queue_families().find(|&q| {
q.supports_graphics() && surface.is_supported(q).unwrap_or(false)
}).unwrap();
let device_ext = DeviceExtensions { khr_swapchain: true, khr_maintenance1: true, ..DeviceExtensions::none() };
let (device, mut queues) = Device::new(physical, &Features::none(), &device_ext,
[(queue_family, 0.5)].iter().cloned()).unwrap();
let queue = queues.next().unwrap();
// Swapchain
let (swapchain, images) = {
let caps = surface.capabilities(physical).unwrap();
let usage = caps.supported_usage_flags;
let alpha = caps.supported_composite_alpha.iter().next().unwrap();
let (format, color_space) = caps.supported_formats[2];
let inner_size = window.inner_size();
data.dimensions = [inner_size.width, inner_size.height];
Swapchain::start(device.clone(), surface.clone())
.num_images(caps.min_image_count)
.format(format)
.dimensions(data.dimensions)
.layers(1)
.usage(usage)
.sharing_mode(&queue)
.transform(SurfaceTransform::Identity)
.composite_alpha(alpha)
.present_mode(render_config.vsync.into())
.fullscreen_exclusive(FullscreenExclusive::Default)
.clipped(true)
.color_space(color_space)
.build().unwrap()
};
let size = images[0].dimensions().width_height();
let viewport = create_viewport(size[0] as f32, size[1] as f32);
// Render pass
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 pipelines: Vec<Box<dyn Drawcall>> = vec![
Box::new(DefaultShader::new(device.clone(), render_pass.clone())),
Box::new(TextShader::new(device.clone(), render_pass_text.clone())),
];
// 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.
let framebuffers = framebuffers::create_framebuffers(device.clone(), &swapchain, &images, render_config.get_msaa(), render_pass.clone());
let mut uniform_buffers = Vec::new();
let uniform_buffer = vs::ty::ObjectUniformData {
view: Matrix4::identity().into(),
projection: Matrix4::identity().into(),
ortho_projection: Matrix4::identity().into(),
time: 0.0,
light_position: [0.0, 0.0, 0.0],
light_directional_rotation: [0.0, 0.0, 0.0],
camera_position: [0.0, 0.0, 0.0],
_dummy0: [0; 12],
_dummy1: [0; 4],
_dummy2: [0; 4],
};
for _ in 0..swapchain.num_images() {
uniform_buffers.push(CpuAccessibleBuffer::from_data(
device.clone(),
BufferUsage::uniform_buffer_transfer_destination(),
false,
uniform_buffer,
).unwrap());
}
// In the loop below we are going to submit commands to the GPU. Submitting a command produces
// an object that implements the `GpuFuture` trait, which holds the resources for as long as
// they are in use by the GPU.
//
// Destroying the `GpuFuture` blocks until the GPU is finished executing it. In order to avoid
// that, we store the submission of the previous frame here.
let previous_frame_end = Some(Box::new(sync::now(device.clone())) as Box<dyn GpuFuture>);
(VulkanRenderer { game_data: data, device, framebuffers,
pipelines, uniform_buffers,
surface, swapchain, render_pass, queue,
recreate_swapchain: false, debug_callback, previous_frame_end,
render_config, viewport,
fps_counter: [0; perf::PERF_COUNTER_SIZE], fps_counter_index: 0, fps_counter_instant: Instant::now()
}, events_loop)
}
#[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
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();
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();
} 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.set_viewport(0, [self.viewport.clone()]);
// Draw meshes etc.
let mut index = 0;
for pipeline in &self.pipelines {
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
builder.end_render_pass().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>) {
// cleanup previous frame
self.previous_frame_end.as_mut().unwrap().cleanup_finished();
// recreate swapchain if window size changed
if self.recreate_swapchain {
let window = self.surface.window();
let inner_size = window.inner_size();
self.game_data.dimensions = [inner_size.width, inner_size.height];
let (new_swapchain, new_images) = match self.swapchain.recreate().dimensions(self.game_data.dimensions).build() {
Ok(r) => r,
// This error tends to happen when the user is manually resizing the window.
// Simply restarting the loop is the easiest way to fix this issue.
Err(SwapchainCreationError::UnsupportedDimensions) => {
println!("Swapchain rejected: UnsupportedDimensions");
return;
}
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.pipelines = vec![
Box::new(DefaultShader::new(self.device.clone(), self.render_pass.clone())),
Box::new(TextShader::new(self.device.clone(), self.render_pass.clone())),
];
self.swapchain = new_swapchain;
// Because framebuffers contains an Arc on the old swapchain, we need to
// 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());
self.recreate_swapchain = false;
}
// recreate pipeline if requested
if self.game_data.recreate_pipeline {
let device = self.device.clone();
let render_pass = self.render_pass.clone();
self.pipelines.iter_mut().for_each(|pipeline| pipeline.recreate_pipeline(device.clone(), render_pass.clone()));
self.game_data.recreate_pipeline = false;
}
// Before we can draw on the output, we have to *acquire* an image from the swapchain. If
// no image is available (which happens if you submit draw commands too quickly), then the
// function will block.
// This operation returns the index of the image that we are allowed to draw upon.
//
// This function can block if no image is available. The parameter is an optional timeout
// after which the function call will return an error.
let (fb_index, _, acquire_future) = match swapchain::acquire_next_image(self.swapchain.clone(), None) {
Ok(r) => r,
Err(AcquireError::OutOfDate) => {
self.recreate_swapchain = true;
return;
},
Err(err) => panic!("{:?}", err)
};
let command_buffer = self.create_command_buffer(fb_index, new_ubo, game_objects).clone();
let future = self.previous_frame_end.take().unwrap()
.join(acquire_future)
.then_execute(self.queue.clone(), command_buffer).unwrap()
.then_swapchain_present(self.queue.clone(), self.swapchain.clone(), fb_index)
.then_signal_fence_and_flush();
match future {
Ok(future) => {
// we're joining on the previous future but the CPU is running faster than the GPU so
// eventually it stutters, and jumps ahead to the newer frames.
//
// See vulkano issue 1135: https://github.com/vulkano-rs/vulkano/issues/1135
// This makes sure the CPU stays in sync with the GPU in situations when the CPU is
// running "too fast"
#[cfg(target_os = "macos")]
future.wait(None).unwrap();
self.previous_frame_end = Some(Box::new(future) as Box<_>);
},
Err(FlushError::OutOfDate) => {
println!("Swapchain out of date!");
self.recreate_swapchain = true;
self.previous_frame_end = Some(Box::new(sync::now(self.device.clone())) as Box<_>);
}
Err(e) => {
println!("{:?}", e);
self.previous_frame_end = Some(Box::new(sync::now(self.device.clone())) as Box<_>);
}
};
}
pub fn upload_mesh(self: &mut Self, mesh: CPUMesh, original_path: Option<String>) -> usize {
let index_buffer = CpuAccessibleBuffer::from_iter(self.device.clone(), BufferUsage::index_buffer(), false, mesh.indices.into_iter()).unwrap();
match mesh.vertices {
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.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 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 usage = ImageUsage {
transfer_destination: true,
transfer_source: true,
sampled: true,
..ImageUsage::none()
};
let mip_maps = if format == Format::R8_UINT { MipmapsCount::One } else { MipmapsCount::Log2 };
let (image_view, initializer) = ImmutableImage::uninitialized(
device.clone(),
dimensions,
format,
mip_maps,
usage,
ImageCreateFlags::default(),
ImageLayout::ShaderReadOnlyOptimal,
device.active_queue_families(),
).unwrap();
let init = SubImage::new(
Arc::new(initializer),
0,
image_view.mipmap_levels(),
0,
1,
ImageLayout::ShaderReadOnlyOptimal,
);
let mut cbb = AutoCommandBufferBuilder::primary(device.clone(), self.queue.family(), CommandBufferUsage::OneTimeSubmit).unwrap();
let mut offset = 0;
let block_bytes = get_block_size(format);
let mut upload_bytes = |data: &[u8], mip_index: u32, mip_size: [u32; 3]| {
let source = CpuAccessibleBuffer::from_iter(
device.clone(),
BufferUsage::transfer_source(),
false,
data.iter().cloned(),
).unwrap();
cbb.copy_buffer_to_image_dimensions(
source.clone(),
init.clone(),
[0, 0, 0],
mip_size,
0,
dimensions.array_layers(),
mip_index,
).unwrap();
};
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 future = match cb.execute(self.queue.clone()) {
Ok(f) => f,
Err(e) => unreachable!("{:?}", e)
};
future.flush().unwrap();
let sampler = Sampler::new(device.clone(), filter, filter,
MipmapMode::Linear, wrap, wrap, wrap,
0.0, 1.0, 0.0, (image_view.mipmap_levels() - 1) as f32).unwrap();
Texture { image: image_view, sampler }
}
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>) {
let texture = &mut self.game_data.textures[tex_handle];
let old_sub_image = SubImage::new(
texture.image.clone(),
0,
1,
0,
1,
ImageLayout::ShaderReadOnlyOptimal,
);
let mut cbb = AutoCommandBufferBuilder::primary(device.clone(), self.queue.family(), CommandBufferUsage::OneTimeSubmit).unwrap();
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) {
self.game_data.meshes.clear();
self.game_data.textures.clear();
}
}
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| {
game.on_window_event(&event);
if renderer.game_data.shutdown {
*control_flow = ControlFlow::Exit;
}
match event {
Event::WindowEvent { event: WindowEvent::CloseRequested, .. } => {
*control_flow = ControlFlow::Exit;
},
Event::WindowEvent { event: WindowEvent::Resized(..), .. } => {
renderer.recreate_swapchain = true;
},
Event::RedrawRequested(..) => {
PerformanceCounter::perf_next_frame("renderer");
PerformanceCounter::perf_next_frame("cb");
renderer.fps_counter[renderer.fps_counter_index] = renderer.fps_counter_instant.elapsed().as_micros();
renderer.fps_counter_instant = Instant::now();
renderer.fps_counter_index = (renderer.fps_counter_index + 1) % perf::PERF_COUNTER_SIZE;
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,
// }
}

375
src/vulkan/pipelines.rs Normal file
View File

@@ -0,0 +1,375 @@
use std::{convert::TryInto, io::{self, ErrorKind, Read, Write}, path::PathBuf, sync::Arc};
use vulkano::{buffer::TypedBufferAccess, command_buffer::{AutoCommandBufferBuilder, PrimaryAutoCommandBuffer}, descriptor_set::PersistentDescriptorSet, image::view::ImageView, pipeline::{PipelineBindPoint, shader::ShaderModule}, render_pass::{RenderPass, Subpass}};
use vulkano::device::Device;
use vulkano::pipeline::GraphicsPipeline;
use crate::{GameObject, vulkan::TextVertex};
use crate::vulkan::Vertex;
use crate::vulkan::GameData;
use crate::VulkanRenderer;
use super::{TextureHandle, gameobject::PushConstantType};
type RP = Arc<RenderPass>;
type GP = Arc<GraphicsPipeline>;
type DS = Arc<PersistentDescriptorSet>;
pub trait Drawcall {
fn draw(self: &Self, builder: &mut AutoCommandBufferBuilder<PrimaryAutoCommandBuffer>, fb_index: usize, game_objects: Vec<&GameObject>, game_data: &GameData);
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 get_pipeline(self: &Self) -> &GP;
}
pub mod vs {
vulkano_shaders::shader!{
ty: "vertex",
path: "shaders/triangle.vert"
}
}
pub mod fs {
vulkano_shaders::shader! {
ty: "fragment",
path: "shaders/triangle.frag"
}
}
#[derive(Clone)]
pub struct DefaultShader {
pipeline: GP,
}
fn _shader_module_from_file(device: Arc<Device>, path: &str) -> Arc<ShaderModule> {
let mut file = std::fs::File::open(path).unwrap();
let mut buffer = vec![];
file.read_to_end(&mut buffer).unwrap();
let words: Vec<u32> = buffer.chunks_exact(4).map(|c| u32::from_ne_bytes(c.try_into().unwrap())).collect();
unsafe {
ShaderModule::from_words(device.clone(), &words).unwrap()
}
}
#[allow(dead_code)]
fn matches_extension(path: &PathBuf, extensions: &Vec<&str>) -> bool {
if let Some(Some(path_extension)) = path.extension().map(|e| e.to_str()) {
for extension in extensions {
if *extension == path_extension { return true; }
}
}
return false;
}
#[allow(dead_code)]
fn compile_shaders() -> io::Result<()> {
for file_maybe in std::fs::read_dir("./shaders")? {
let path = file_maybe?.path();
if !path.is_dir() && matches_extension(&path, &vec!["frag", "vert"]) {
let mut target_path = path.to_str().ok_or(ErrorKind::Other)?.to_string();
target_path.push_str(".spv");
let output = std::process::Command::new("glslangValidator")
.arg("-V")
.arg(path.to_str().ok_or(ErrorKind::Other)?)
.arg("-o")
.arg(target_path)
.output().unwrap();
std::io::stdout().write_all(&output.stdout)?;
if !output.status.success() {
eprintln!("Shader compiler {:?}", output.status);
}
}
}
Ok(())
}
impl DefaultShader {
pub fn new(device: Arc<Device>, render_pass: RP) -> Self {
DefaultShader {
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/triangle.frag.spv");
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 vs_module = shader_module_from_file(device.clone(), "shaders/triangle.vert.spv");
let vs_layout = vs::Layout(ShaderStages {
vertex: true,
..ShaderStages::none()
});
let vs_entry = vs_module.graphics_entry_point(entry_name_c, vs::MainInput, vs::MainOutput, vs_layout, GraphicsShaderType::Vertex);
*/
let vs = vs::Shader::load(device.clone()).unwrap();
let fs = fs::Shader::load(device.clone()).unwrap();
Arc::new(GraphicsPipeline::start()
.vertex_input_single_buffer::<Vertex>()
.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_back()
.render_pass(sub_pass.clone())
.build(device.clone())
.unwrap())
//}
}
}
impl Drawcall for DefaultShader {
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[game_object.mesh_index];
#[allow(irrefutable_let_patterns)]
if let PushConstantType::MeshPC(mesh_push) = game_object.get_push_constants() {
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())
.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_sets(self: &Self, textures: &Vec<TextureHandle>, renderer: &VulkanRenderer) -> Vec<Vec<DS>> {
let descriptor_set_layout_0 = self.get_pipeline().layout().descriptor_set_layouts().get(0).unwrap().clone();
renderer.uniform_buffers.iter().map(|uniform_buffer| {
debug_assert!(textures.len() == 2, "Expected diffuse and normal map for object shader!");
let diffuse = &renderer.game_data.textures[textures[0]];
let diffuse_view = ImageView::new(diffuse.image.clone()).unwrap();
let normal_map = &renderer.game_data.textures[textures[1]];
let normal_view = ImageView::new(normal_map.image.clone()).unwrap();
let mut builder = PersistentDescriptorSet::start(descriptor_set_layout_0.clone());
builder
.add_buffer(uniform_buffer.clone()).unwrap()
.add_sampled_image(diffuse_view, diffuse.sampler.clone()).unwrap()
.add_sampled_image(normal_view, normal_map.sampler.clone()).unwrap();
vec![Arc::new(builder.build().unwrap())]
}).collect()
}
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 line_vs {
vulkano_shaders::shader!{
ty: "vertex",
path: "shaders/line.vert"
}
}
pub mod line_fs {
vulkano_shaders::shader! {
ty: "fragment",
path: "shaders/line.frag"
}
}
#[derive(Clone)]
pub struct LineShader {
pipeline: GP,
vertex_buffer: Arc<vulkano::buffer::CpuAccessibleBuffer<[LinePoint]>>
}
impl LineShader {
pub fn _new(device: Arc<Device>, render_pass: RP, vertex_buffer: Arc<vulkano::buffer::CpuAccessibleBuffer<[LinePoint]>>) -> Self {
LineShader {
pipeline: Self::create_pipeline(device, render_pass),
vertex_buffer
}
}
fn create_pipeline(device: Arc<Device>, render_pass: RP) -> GP {
let sub_pass = Subpass::from(render_pass.clone(), 0).unwrap();
let vertex_shader = line_vs::Shader::load(device.clone()).unwrap();
let fragment_shader = line_fs::Shader::load(device.clone()).unwrap();
Arc::new(GraphicsPipeline::start()
.vertex_input_single_buffer::<LinePoint>()
.vertex_shader(vertex_shader.main_entry_point(), ())
.line_list()
.viewports_dynamic_scissors_irrelevant(1)
.depth_stencil_simple_depth()
.fragment_shader(fragment_shader.main_entry_point(), ())
.render_pass(sub_pass.clone())
.build(device.clone())
.unwrap())
}
}
impl Drawcall for LineShader {
fn draw(self: &Self, builder: &mut AutoCommandBufferBuilder<PrimaryAutoCommandBuffer>, _fb_index: usize, _game_objects: Vec<&GameObject>, game_data: &GameData) {
builder.draw(self.pipeline.clone(),
vec![self.vertex_buffer.clone()],
(),
game_data.line_push_constants.clone()).unwrap();
}
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) {
self.pipeline = Self::create_pipeline(device, render_pass);
}
fn get_pipeline(self: &Self) -> &GP {
&self.pipeline
}
}

62
src/vulkan/renderpass.rs Normal file
View File

@@ -0,0 +1,62 @@
use crate::RenderConfig;
use std::sync::Arc;
use vulkano::{device::Device, format::Format, render_pass::RenderPass};
pub fn create_render_pass(device: Arc<Device>, render_config: &RenderConfig, swapchain_format: Format) -> Arc<RenderPass> {
if render_config.msaa_samples > 0 {
Arc::new(vulkano::single_pass_renderpass!(
device.clone(),
attachments: {
color: {
load: DontCare,
store: Store,
format: swapchain_format,
samples: 1,
},
intermediary: {
load: Clear,
store: DontCare,
format: swapchain_format,
samples: render_config.msaa_samples,
},
depth: {
load: Clear,
store: Store,
format: Format::D16_UNORM,
samples: render_config.msaa_samples,
initial_layout: ImageLayout::Undefined,
final_layout: ImageLayout::DepthStencilAttachmentOptimal,
}
},
pass: {
color: [intermediary],
depth_stencil: {depth},
resolve: [color]
}
).unwrap())
} else {
Arc::new(vulkano::single_pass_renderpass!(
device.clone(),
attachments: {
color: {
load: Clear,
store: Store,
format: swapchain_format,
samples: 1,
},
depth: {
load: Clear,
store: Store,
format: Format::D16_UNORM,
samples: 1,
initial_layout: ImageLayout::Undefined,
final_layout: ImageLayout::DepthStencilAttachmentOptimal,
}
},
pass: {
color: [color],
depth_stencil: {depth}
}
).unwrap())
}
}