Compare commits
59 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e1c085c060 | |||
| d941b2d6cd | |||
| 0a41daf4eb | |||
| 02ba2bb95a | |||
| 4c2409dbd0 | |||
| ff80f7b9aa | |||
| 02f39453ac | |||
| a0bfdab902 | |||
| 4b87a8752b | |||
| 00204104dd | |||
| c2e3c4e0e4 | |||
| 0c8d09effa | |||
| f5ed8f4e40 | |||
| d83045394c | |||
| f42250f352 | |||
| da62d6ab67 | |||
| 9848fbc183 | |||
| 31cdfa1885 | |||
|
|
57b3f89817 | ||
| 062ee8c1c5 | |||
| 45286b8178 | |||
| 8735f2d409 | |||
| 26871da22b | |||
| f209666144 | |||
| b98c85a13a | |||
| 039a4d5a64 | |||
| 452af8ae3a | |||
| 277d284994 | |||
| ad6b0730d5 | |||
| d33bceeab8 | |||
| dd4eaa13f7 | |||
|
|
33768a6730 | ||
|
|
befa69c55e | ||
| be1d5e6221 | |||
| e054a76a3f | |||
| 0edd6b6411 | |||
| 62ec222749 | |||
| 483645dcc5 | |||
| 9b6a81ae93 | |||
| a8f8bbf36e | |||
| 38ea41b550 | |||
| 9687f5ec89 | |||
| c055ea19ed | |||
| d6f2b5d385 | |||
| 8b8bf64e55 | |||
| c2f0dd53c1 | |||
| 71dc720a31 | |||
| 0a3f89c150 | |||
| 002db530be | |||
| f868a8e45a | |||
| 419a329fc8 | |||
| 15f3952c1b | |||
| 067ca2edf4 | |||
| c4494e4766 | |||
| dbeb6da890 | |||
| 4121b5e657 | |||
| e755d3b1d8 | |||
| ae8b82c84d | |||
| fff521307e |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -80,4 +80,5 @@ fabric.properties
|
||||
|
||||
# cusotm
|
||||
build/deploy/
|
||||
build/capture/
|
||||
build/capture/
|
||||
*.blend1
|
||||
5
.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
5
.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
@@ -0,0 +1,5 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<state>
|
||||
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
|
||||
</state>
|
||||
</component>
|
||||
6
.idea/runConfigurations/debug.xml
generated
6
.idea/runConfigurations/debug.xml
generated
@@ -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>
|
||||
5
.idea/runConfigurations/deploy.xml
generated
5
.idea/runConfigurations/deploy.xml
generated
@@ -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>
|
||||
6
.idea/runConfigurations/release.xml
generated
6
.idea/runConfigurations/release.xml
generated
@@ -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>
|
||||
5
.idea/runConfigurations/renderdoc.xml
generated
5
.idea/runConfigurations/renderdoc.xml
generated
@@ -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
64
.vscode/launch.json
vendored
Normal 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}"
|
||||
}
|
||||
]
|
||||
}
|
||||
31
Cargo.toml
31
Cargo.toml
@@ -3,18 +3,23 @@ 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.24"
|
||||
vulkano = "0.24"
|
||||
vulkano-win = "0.24"
|
||||
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"
|
||||
gfx_glyph = "0.17"
|
||||
|
||||
[[bin]]
|
||||
name = "converter"
|
||||
path = "build/converter/src/main.rs"
|
||||
18
build/converter/src/main.rs
Normal file
18
build/converter/src/main.rs
Normal 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
BIN
build/debug.rdbg
Normal file
Binary file not shown.
132
build/nsight-rust-engine/nsight-rust-engine.ngfx-proj
Normal file
132
build/nsight-rust-engine/nsight-rust-engine.ngfx-proj
Normal 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"
|
||||
}
|
||||
BIN
build/nvidia-texture-converter/FreeImage.dll
Normal file
BIN
build/nvidia-texture-converter/FreeImage.dll
Normal file
Binary file not shown.
BIN
build/nvidia-texture-converter/nvtt_export.exe
Normal file
BIN
build/nvidia-texture-converter/nvtt_export.exe
Normal file
Binary file not shown.
2
build/quick-clean.ps1
Normal file
2
build/quick-clean.ps1
Normal file
@@ -0,0 +1,2 @@
|
||||
rm ..\target\debug\deps\rust-engine*
|
||||
rm ..\target\debug\deps\rust_engine*
|
||||
27
build/renderdoc-settings.cap
Normal file
27
build/renderdoc-settings.cap
Normal 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"
|
||||
}
|
||||
}
|
||||
1
config/graphics.toml
Normal file
1
config/graphics.toml
Normal file
@@ -0,0 +1 @@
|
||||
msaa_samples = 4
|
||||
@@ -1,40 +1,58 @@
|
||||
# Scan codes are in decimal
|
||||
# See here: https://www.millisecond.com/support/docs/v6/html/language/scancodes.htm
|
||||
|
||||
[[button]]
|
||||
name = "quit"
|
||||
scan_code = 1
|
||||
|
||||
[[button]]
|
||||
name = "toggle_edit"
|
||||
scan_code = 59
|
||||
|
||||
[[button]]
|
||||
name = "reload_shaders"
|
||||
scan_code = 19
|
||||
ctrl = true
|
||||
scan_code = 63
|
||||
|
||||
[[button]]
|
||||
name = "print_framerate"
|
||||
scan_code = 33
|
||||
|
||||
[[button]]
|
||||
name = "w"
|
||||
name = "button_forward"
|
||||
scan_code = 17
|
||||
|
||||
[[button]]
|
||||
name = "s"
|
||||
name = "button_backward"
|
||||
scan_code = 31
|
||||
|
||||
[[button]]
|
||||
name = "a"
|
||||
name = "button_left"
|
||||
scan_code = 30
|
||||
|
||||
[[button]]
|
||||
name = "d"
|
||||
name = "button_right"
|
||||
scan_code = 32
|
||||
|
||||
[[button]]
|
||||
name = "test"
|
||||
scan_code = 20
|
||||
|
||||
[[button]]
|
||||
name = "quicksave"
|
||||
scan_code = 63
|
||||
|
||||
[[button]]
|
||||
name = "quickload"
|
||||
scan_code = 64
|
||||
|
||||
[[button]]
|
||||
name = "select"
|
||||
mouse = "left"
|
||||
|
||||
[[axis]]
|
||||
name = "move_forward"
|
||||
positive_button = "w"
|
||||
negative_button = "s"
|
||||
positive_button = "button_forward"
|
||||
negative_button = "button_backward"
|
||||
|
||||
[[axis]]
|
||||
name = "move_forward"
|
||||
@@ -46,8 +64,8 @@ controller_axis = "LeftStickY"
|
||||
|
||||
[[axis]]
|
||||
name = "move_sideways"
|
||||
positive_button = "d"
|
||||
negative_button = "a"
|
||||
positive_button = "button_right"
|
||||
negative_button = "button_left"
|
||||
|
||||
[[axis]]
|
||||
name = "move_sideways"
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
input_events = false
|
||||
vulkan_validation_layers = false
|
||||
mesh_load_info = true
|
||||
vulkan_validation_layers = true
|
||||
mesh_load_info = true
|
||||
debug_errors = true
|
||||
debug_warnings = true
|
||||
debug_info = true
|
||||
debug_verbose = true
|
||||
|
||||
[input]
|
||||
mouse_motion = false
|
||||
buttons = false
|
||||
missing_bindings = true
|
||||
50
levels/test.lvl
Normal file
50
levels/test.lvl
Normal file
@@ -0,0 +1,50 @@
|
||||
{
|
||||
"meshes": [
|
||||
{
|
||||
"path": "models/plane.gltf"
|
||||
},
|
||||
{
|
||||
"path": "models/box.gltf"
|
||||
}
|
||||
],
|
||||
"objects": [
|
||||
{
|
||||
"mesh_index": 1,
|
||||
"position": [
|
||||
0.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,
|
||||
5.0
|
||||
],
|
||||
"rotation": [
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"scale": [
|
||||
1.0,
|
||||
1.0,
|
||||
1.0
|
||||
]
|
||||
}
|
||||
}
|
||||
BIN
models/box.bin
BIN
models/box.bin
Binary file not shown.
BIN
models/box.blend
Normal file
BIN
models/box.blend
Normal file
Binary file not shown.
@@ -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
BIN
models/plane.bin
Normal file
Binary file not shown.
BIN
models/plane.blend
Normal file
BIN
models/plane.blend
Normal file
Binary file not shown.
160
models/plane.gltf
Normal file
160
models/plane.gltf
Normal 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
BIN
models/sphere.blend
Normal file
Binary file not shown.
BIN
models/textures/cobblestone_large_01_diff_4k.dds
Normal file
BIN
models/textures/cobblestone_large_01_diff_4k.dds
Normal file
Binary file not shown.
BIN
models/textures/cobblestone_large_01_diff_4k.jpg
Normal file
BIN
models/textures/cobblestone_large_01_diff_4k.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.0 MiB |
BIN
models/textures/cobblestone_large_01_nor_4k.dds
Normal file
BIN
models/textures/cobblestone_large_01_nor_4k.dds
Normal file
Binary file not shown.
BIN
models/textures/cobblestone_large_01_nor_4k.jpg
Normal file
BIN
models/textures/cobblestone_large_01_nor_4k.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 MiB |
BIN
models/textures/colors.dds
Normal file
BIN
models/textures/colors.dds
Normal file
Binary file not shown.
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
BIN
shaders/line.frag.spv
Normal file
BIN
shaders/line.frag.spv
Normal file
Binary file not shown.
BIN
shaders/line.vert.spv
Normal file
BIN
shaders/line.vert.spv
Normal file
Binary file not shown.
@@ -1,12 +1,53 @@
|
||||
#version 450
|
||||
#extension GL_ARB_separate_shader_objects : enable
|
||||
|
||||
layout(push_constant) uniform PushConstants {
|
||||
mat4 model;
|
||||
bool is_selected;
|
||||
} push;
|
||||
|
||||
layout(binding = 0) uniform ObjectUniformData {
|
||||
mat4 view;
|
||||
mat4 projection;
|
||||
float time;
|
||||
vec3 light_position;
|
||||
vec3 light_directional_rotation;
|
||||
vec3 camera_position;
|
||||
} ubo;
|
||||
|
||||
layout(binding = 1) 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
BIN
shaders/triangle.frag.spv
Normal file
Binary file not shown.
@@ -1,22 +1,54 @@
|
||||
#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) uniform ObjectUniformData {
|
||||
mat4 view;
|
||||
mat4 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);
|
||||
|
||||
// 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
BIN
shaders/triangle.vert.spv
Normal file
Binary file not shown.
@@ -1,16 +1,44 @@
|
||||
use serde_derive::{Serialize, Deserialize};
|
||||
use std::{convert::TryInto, fs};
|
||||
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use toml;
|
||||
use std::fs;
|
||||
use vulkano::image::SampleCount;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Copy)]
|
||||
pub struct LogConfigInput {
|
||||
pub mouse_motion: bool,
|
||||
pub buttons: 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,
|
||||
pub debug_errors: bool,
|
||||
pub debug_warnings: bool,
|
||||
pub debug_info: bool,
|
||||
pub debug_verbose: bool
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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> {
|
||||
self.msaa_samples.try_into().ok()
|
||||
}
|
||||
}
|
||||
91
src/game/level.rs
Normal file
91
src/game/level.rs
Normal 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 mut handle = game.add_game_object(renderer, meshes[mesh_index].clone());
|
||||
let game_object = handle.get_game_object_mut(renderer).unwrap();
|
||||
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().map(|mesh| {
|
||||
MeshJson {
|
||||
path: mesh.original_path.to_string()
|
||||
}
|
||||
}).collect();
|
||||
|
||||
let objects = game.game_objects.iter().map(|game_object_handle| {
|
||||
let game_object = game_object_handle.get_game_object(renderer).unwrap();
|
||||
ObjectJson {
|
||||
mesh_index: Some(game_object_handle.object_index),
|
||||
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(())
|
||||
}
|
||||
178
src/game/mod.rs
Normal file
178
src/game/mod.rs
Normal file
@@ -0,0 +1,178 @@
|
||||
|
||||
use std::time::SystemTime;
|
||||
|
||||
use cgmath::{Deg, Euler, Quaternion, vec3};
|
||||
use winit::event::Event;
|
||||
|
||||
use level::{load_level, save_level};
|
||||
use player::Player;
|
||||
|
||||
use crate::{config::LogConfig, vulkan};
|
||||
use crate::input::InputState;
|
||||
use crate::vulkan::{Game, MeshHandle, VulkanRenderer};
|
||||
use crate::vulkan::gameobject::{GameObject, GameObjectHandle, Updatable};
|
||||
use crate::vulkan::mesh;
|
||||
use crate::vulkan::pipelines::vs::ty::ObjectUniformData;
|
||||
|
||||
pub mod player;
|
||||
mod level;
|
||||
|
||||
pub struct TestGame {
|
||||
pub input: InputState,
|
||||
pub player: Player,
|
||||
pub game_objects: Vec<GameObjectHandle>,
|
||||
pub log_config: LogConfig,
|
||||
pub texture_index_counter: usize,
|
||||
pub last_time: f32,
|
||||
pub components: Vec<Box<dyn Updatable>>,
|
||||
pub paused: bool,
|
||||
}
|
||||
|
||||
impl Game for TestGame {
|
||||
fn on_window_event(self: &mut Self, event: &Event<()>) {
|
||||
self.input.on_window_event(event);
|
||||
}
|
||||
|
||||
fn update(self: &mut Self, renderer: &mut VulkanRenderer) -> ObjectUniformData {
|
||||
// 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 components = &mut self.components;
|
||||
components.iter_mut().for_each(|component| {
|
||||
component.update(frame_time, &input, 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_down("print_framerate") {
|
||||
println!("{:.0} ms / {:.0} FPS", frame_time * 1000.0, 1.0 / frame_time);
|
||||
}
|
||||
|
||||
if self.input.button_just_pressed("test") {
|
||||
self.paused = !self.paused;
|
||||
}
|
||||
|
||||
// Custom game object stuff
|
||||
let light_pos = vec3(2.0, 0.5, 2.0);
|
||||
if !self.paused {
|
||||
self.player.update(frame_time, &self.input, renderer);
|
||||
}
|
||||
// self.game_objects[1].get_game_object_mut(renderer).unwrap().rotation = Quaternion::from_angle_y(Deg(time * -20.)).normalize();
|
||||
|
||||
// End frame
|
||||
self.last_time = time;
|
||||
self.input.frame_end();
|
||||
|
||||
ObjectUniformData {
|
||||
view: self.player.camera.view.into(),
|
||||
projection: self.player.camera.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],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TestGame {
|
||||
pub fn new(toml_path: &str, log_config: LogConfig) -> TestGame {
|
||||
TestGame {
|
||||
input: InputState::new(toml_path, log_config),
|
||||
player: Player::new(),
|
||||
game_objects: vec![],
|
||||
log_config,
|
||||
texture_index_counter: 0,
|
||||
last_time: 0.0,
|
||||
components: vec![],
|
||||
paused: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn game_start(self: &mut Self, renderer: &mut VulkanRenderer) {
|
||||
load_level("levels/test.lvl", self, renderer).unwrap();
|
||||
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(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, gltf_path.to_string());
|
||||
let mesh_handle = MeshHandle {
|
||||
index: mesh_id,
|
||||
diffuse_handle: diffuse_id,
|
||||
normal_handle: normal_id,
|
||||
original_path: gltf_path.to_string()
|
||||
};
|
||||
mesh_handles.push(mesh_handle);
|
||||
}
|
||||
|
||||
for doc_image in document.images() {
|
||||
let texture_start_time = SystemTime::now();
|
||||
|
||||
vulkan::dds::upload_texture_from_file(&format!("models/textures/{}.dds", doc_image.name().unwrap()), renderer).unwrap();
|
||||
self.texture_index_counter += 1;
|
||||
|
||||
if self.log_config.mesh_load_info {
|
||||
println!("Uploading texture took {:?}ms", texture_start_time.elapsed().unwrap().as_millis());
|
||||
}
|
||||
}
|
||||
mesh_handles
|
||||
}
|
||||
|
||||
pub fn add_game_object(&mut self, renderer: &mut VulkanRenderer, mesh: MeshHandle) -> GameObjectHandle {
|
||||
let obj = GameObject::new(mesh);
|
||||
let obj_handle = renderer.add_game_object(obj, 0);
|
||||
self.game_objects.push(obj_handle);
|
||||
self.game_objects.last().unwrap().clone()
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
217
src/game/player.rs
Normal file
217
src/game/player.rs
Normal file
@@ -0,0 +1,217 @@
|
||||
use cgmath::{Deg, InnerSpace, Matrix4, One, Quaternion, Rad, Rotation, Rotation3, SquareMatrix, Vector3, Vector4, vec3, vec4};
|
||||
use vulkano::buffer::TypedBufferAccess;
|
||||
|
||||
use crate::game::player::PlayerMovementMode::{FirstPerson, Flying};
|
||||
|
||||
use crate::input::InputState;
|
||||
use crate::vulkan::Mesh;
|
||||
use crate::vulkan::gameobject::GameObject;
|
||||
use crate::vulkan::{
|
||||
gameobject::Updatable,
|
||||
VulkanRenderer
|
||||
};
|
||||
|
||||
pub struct Camera {
|
||||
pub fov_y: f32,
|
||||
pub position: Vector3<f32>,
|
||||
pub rotation: Quaternion<f32>,
|
||||
pub view: Matrix4<f32>,
|
||||
pub 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(),
|
||||
}
|
||||
}
|
||||
|
||||
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.);
|
||||
|
||||
self.proj = cgmath::perspective(
|
||||
Rad::from(Deg(self.fov_y)),
|
||||
renderer.game_data.dimensions[0] as f32 / renderer.game_data.dimensions[1] as f32,
|
||||
0.1,
|
||||
100.0
|
||||
);
|
||||
// Why?
|
||||
self.proj.y.y *= -1.0;
|
||||
|
||||
// Upload
|
||||
renderer.game_data.line_push_constants.view = self.view.into();
|
||||
renderer.game_data.line_push_constants.projection = self.proj.into();
|
||||
}
|
||||
|
||||
pub fn viewport_pos_to_ray_direction(&self, viewport_pos: [f64; 2], viewport_dimensions: [u32; 2]) -> Option<Vector3<f32>> {
|
||||
let normalized_x = 2. * (viewport_pos[0] as f32 / viewport_dimensions[0] as f32) - 1.;
|
||||
let normalized_y = 2. * (viewport_pos[1] 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() -> Player {
|
||||
Player {
|
||||
camera: Camera::new(),
|
||||
movement_mode: Flying,
|
||||
movement_speed: 3.0,
|
||||
look_sensitivity: 30.0,
|
||||
height: 1.0,
|
||||
x_look: 0.0,
|
||||
y_look: 0.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn vec4_from_pos(pos: [f32; 3]) -> Vector4<f32> {
|
||||
vec4(pos[0], pos[1], pos[2], 1.0)
|
||||
}
|
||||
|
||||
pub fn intersection_distance(ray_origin: Vector3<f32>, ray_direction: Vector3<f32>, mesh: &Mesh, game_object: &GameObject) -> Option<f32> {
|
||||
let index_lock = mesh.index_buffer.read().unwrap();
|
||||
let vertex_lock = mesh.vertex_buffer.read().unwrap();
|
||||
(0..mesh.index_buffer.len() / 3).map(|tri_idx| {
|
||||
let vtx_a = game_object.get_model_matrix() * vec4_from_pos(vertex_lock[index_lock[tri_idx * 3 ] as usize].position);
|
||||
let vtx_b = game_object.get_model_matrix() * vec4_from_pos(vertex_lock[index_lock[tri_idx * 3 + 1] as usize].position);
|
||||
let vtx_c = game_object.get_model_matrix() * vec4_from_pos(vertex_lock[index_lock[tri_idx * 3 + 2] as usize].position);
|
||||
intersect_triangle(ray_origin, ray_direction, vtx_a.truncate().into(), vtx_b.truncate().into(), vtx_c.truncate().into())
|
||||
}).fold(None, |acc, x| {
|
||||
if let Some(smallest) = acc {
|
||||
if let Some(new) = x {
|
||||
if new < smallest {
|
||||
Some(new)
|
||||
} else {
|
||||
Some(smallest)
|
||||
}
|
||||
} else {
|
||||
acc
|
||||
}
|
||||
} else {
|
||||
x
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// https://en.wikipedia.org/wiki/M%C3%B6ller%E2%80%93Trumbore_intersection_algorithm
|
||||
pub fn intersect_triangle(ray_origin: Vector3<f32>, ray_direction: Vector3<f32>, vtx_a: [f32; 3], vtx_b: [f32; 3], vtx_c: [f32; 3]) -> Option<f32> {
|
||||
let edge_1 = vec3(vtx_b[0] - vtx_a[0], vtx_b[1] - vtx_a[1], vtx_b[2] - vtx_a[2]);
|
||||
let edge_2 = vec3(vtx_c[0] - vtx_a[0], vtx_c[1] - vtx_a[1], vtx_c[2] - vtx_a[2]);
|
||||
let h = ray_direction.cross(edge_2);
|
||||
let a = edge_1.dot(h);
|
||||
if a > -f32::EPSILON && a < f32::EPSILON { return None; }
|
||||
|
||||
let f = 1. / a;
|
||||
let s: Vector3<f32> = ray_origin - Vector3::from(vtx_a);
|
||||
let u = f * s.dot(h);
|
||||
if u < 0. || u > 1. { return None; }
|
||||
|
||||
let q = s.cross(edge_1);
|
||||
let v = f * ray_direction.dot(q);
|
||||
if v < 0. || u + v > 1. { return None; }
|
||||
|
||||
let t = f * edge_2.dot(q);
|
||||
if t > f32::EPSILON { return Some(t); }
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
impl Updatable for Player {
|
||||
fn update(&mut self, delta_time: f32, input: &InputState, 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 &mut renderer.game_data.game_objects {
|
||||
let mesh = &renderer.game_data.meshes[game_object.mesh_index];
|
||||
if let Some(dist) = intersection_distance(self.camera.position, ray_direction, mesh, game_object) {
|
||||
game_object.is_selected = !game_object.is_selected;
|
||||
println!("distance: {}", dist);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Rotation
|
||||
self.x_look += input.get_axis("look_vertical") * delta_time * self.look_sensitivity;
|
||||
self.y_look += input.get_axis("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("move_sideways"), 0.0, -input.get_axis("move_forward")) * self.movement_speed * delta_time;
|
||||
|
||||
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.normalize();
|
||||
self.camera.position.y = self.height;
|
||||
} else if self.movement_mode == Flying {
|
||||
self.camera.position += self.camera.local_to_world(local_input);
|
||||
}
|
||||
|
||||
// Update
|
||||
self.camera.update(renderer);
|
||||
}
|
||||
}
|
||||
123
src/input.rs
123
src/input.rs
@@ -1,15 +1,14 @@
|
||||
use winit::{ScanCode, ModifiersState, MouseButton, ElementState, MouseScrollDelta, Event, WindowEvent, DeviceEvent};
|
||||
#![allow(deprecated)]
|
||||
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::fs;
|
||||
use std::iter::FromIterator;
|
||||
|
||||
use toml;
|
||||
|
||||
use serde_derive::{Serialize, Deserialize};
|
||||
|
||||
use gilrs;
|
||||
use gilrs::{Gilrs, EventType};
|
||||
use gilrs::{EventType, Gilrs};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use toml;
|
||||
use winit::event::{DeviceEvent, ElementState, Event, ModifiersState, MouseButton, MouseScrollDelta, ScanCode, WindowEvent};
|
||||
|
||||
use crate::config::LogConfig;
|
||||
|
||||
@@ -72,6 +71,7 @@ pub struct InputState {
|
||||
pub virtual_axes: HashMap<String, VirtualAxis>,
|
||||
pub mouse_delta_x: f64,
|
||||
pub mouse_delta_y: f64,
|
||||
pub mouse_position: [f64; 2],
|
||||
input_events: HashSet<DigitalInputEvent>,
|
||||
pressed_scan_codes: HashSet<ScanCode>,
|
||||
pressed_mouse_buttons: HashSet<MouseButton>,
|
||||
@@ -81,6 +81,25 @@ pub struct InputState {
|
||||
controller_input: Gilrs,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Hash, Clone)]
|
||||
pub struct KeyboardModifierState {
|
||||
shift: bool,
|
||||
ctrl: bool,
|
||||
alt: bool,
|
||||
logo: bool
|
||||
}
|
||||
|
||||
impl KeyboardModifierState {
|
||||
pub fn from_deprecated_state(old_state: &ModifiersState) -> KeyboardModifierState {
|
||||
KeyboardModifierState {
|
||||
shift: old_state.shift(),
|
||||
ctrl: old_state.ctrl(),
|
||||
alt: old_state.alt(),
|
||||
logo: old_state.logo()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl InputState {
|
||||
pub fn new(toml_path: &str, log_config: LogConfig) -> InputState {
|
||||
let config: InputConfig = toml::from_slice(&fs::read(toml_path).expect("Failed to read input config!")).expect("Failed to parse input config!");
|
||||
@@ -88,11 +107,11 @@ impl InputState {
|
||||
let mut state = InputState { virtual_buttons: HashMap::new(), virtual_axes: HashMap::new(),
|
||||
input_events: HashSet::new(), pressed_scan_codes: HashSet::new(),
|
||||
pressed_mouse_buttons: HashSet::new(), analog_wheel_state: 0.0,
|
||||
config: config.config, mouse_delta_x: 0.0, mouse_delta_y: 0.0, log_config,
|
||||
config: config.config, mouse_delta_x: 0.0, mouse_delta_y: 0.0, mouse_position: [0., 0.], log_config,
|
||||
controller_input: Gilrs::new().unwrap() };
|
||||
|
||||
config.button.iter().for_each(|bn| {
|
||||
let modifiers = ModifiersState {
|
||||
let modifiers = KeyboardModifierState {
|
||||
shift: bn.shift.is_some(),
|
||||
ctrl: bn.ctrl.is_some(),
|
||||
alt: bn.alt.is_some(),
|
||||
@@ -138,7 +157,7 @@ impl InputState {
|
||||
AxisInput::Digital(positive_button, negative_button)
|
||||
},
|
||||
InputConfigAxis { mouse_axis: Some(axis_name), .. } => {
|
||||
let modifiers = ModifiersState {
|
||||
let modifiers = KeyboardModifierState {
|
||||
shift: axis.shift.is_some(),
|
||||
ctrl: axis.ctrl.is_some(),
|
||||
alt: axis.alt.is_some(),
|
||||
@@ -167,23 +186,23 @@ impl InputState {
|
||||
return state;
|
||||
}
|
||||
|
||||
pub fn on_window_event(self: &mut Self, event: &Event) {
|
||||
pub fn on_window_event(self: &mut Self, event: &Event<()>) {
|
||||
match event {
|
||||
Event::WindowEvent { event: WindowEvent::KeyboardInput { device_id, input }, .. } => {
|
||||
if self.log_config.input_events {
|
||||
let mods = mods_to_string(&input.modifiers);
|
||||
Event::WindowEvent { event: WindowEvent::KeyboardInput { device_id, input, .. }, .. } => {
|
||||
if self.log_config.input.buttons {
|
||||
let mods = mods_to_string(&KeyboardModifierState::from_deprecated_state(&input.modifiers));
|
||||
if mods.len() > 0 {
|
||||
println!("Keyboard {:?} {:?} {:?} + {:?}", device_id, input.state, &mods, input.scancode)
|
||||
println!("Keyboard {:?} {:?} {:?} + {:?} {:?}", device_id, input.state, &mods, input.virtual_keycode, input.scancode)
|
||||
} else {
|
||||
println!("Keyboard {:?} {:?} {:?}", device_id, input.state, input.scancode)
|
||||
println!("Keyboard {:?} {:?} {:?} {:?}", device_id, input.state, input.virtual_keycode, input.scancode)
|
||||
}
|
||||
}
|
||||
|
||||
self.on_keyboard_event(input.state, input.scancode, input.modifiers);
|
||||
self.on_keyboard_event(input.state, input.scancode, KeyboardModifierState::from_deprecated_state(&input.modifiers));
|
||||
},
|
||||
Event::WindowEvent { event: WindowEvent::MouseInput { device_id, state, button, modifiers }, .. } => {
|
||||
if self.log_config.input_events {
|
||||
let mods = mods_to_string(&modifiers);
|
||||
if self.log_config.input.buttons {
|
||||
let mods = mods_to_string(&KeyboardModifierState::from_deprecated_state(&modifiers));
|
||||
if mods.len() > 0 {
|
||||
println!("Mouse {:?} {:?} {:?} + {:?}", device_id, state, &mods, button)
|
||||
} else {
|
||||
@@ -191,11 +210,11 @@ impl InputState {
|
||||
}
|
||||
}
|
||||
|
||||
self.on_mouse_event(state, button, modifiers);
|
||||
self.on_mouse_event(state, button, &KeyboardModifierState::from_deprecated_state(&modifiers));
|
||||
},
|
||||
Event::WindowEvent { event: WindowEvent::MouseWheel { device_id, delta, phase, modifiers }, .. } => {
|
||||
if self.log_config.input_events {
|
||||
let mods = mods_to_string(&modifiers);
|
||||
if self.log_config.input.buttons {
|
||||
let mods = mods_to_string(&KeyboardModifierState::from_deprecated_state(&modifiers));
|
||||
if mods.len() > 0 {
|
||||
println!("Scroll {:?} {:?} {:?} + {:?}", device_id, phase, &mods, delta)
|
||||
} else {
|
||||
@@ -203,15 +222,18 @@ impl InputState {
|
||||
}
|
||||
}
|
||||
|
||||
self.on_mouse_wheel_event(delta, modifiers);
|
||||
self.on_mouse_wheel_event(delta, &KeyboardModifierState::from_deprecated_state(&modifiers));
|
||||
},
|
||||
Event::DeviceEvent { device_id, event: DeviceEvent::MouseMotion { delta: (delta_x, delta_y) } } => {
|
||||
if self.log_config.input_events {
|
||||
if self.log_config.input.mouse_motion {
|
||||
println!("MouseMotion {:?}, ({:?},{:?})", device_id, delta_x, delta_y);
|
||||
}
|
||||
self.mouse_delta_x += *delta_x;
|
||||
self.mouse_delta_y += *delta_y;
|
||||
}
|
||||
},
|
||||
Event::WindowEvent { event: WindowEvent::CursorMoved { position, .. }, .. } => {
|
||||
self.mouse_position = [position.x, position.y];
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
@@ -222,7 +244,9 @@ impl InputState {
|
||||
virtual_button.digital_inputs.iter().any(|vi| self.digital_input_pressed(vi))
|
||||
}
|
||||
None => {
|
||||
assert!(false, format!("Button {:?} not found!", button_code));
|
||||
if self.log_config.input.buttons {
|
||||
println!("Button {:?} not found!", button_code);
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
@@ -240,7 +264,9 @@ impl InputState {
|
||||
})
|
||||
}
|
||||
None => {
|
||||
assert!(false, format!("Button {:?} not found!", button_code));
|
||||
if self.log_config.input.missing_bindings {
|
||||
println!("Button {:?} not found!", button_code);
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
@@ -258,7 +284,9 @@ impl InputState {
|
||||
})
|
||||
}
|
||||
None => {
|
||||
assert!(false, format!("Button {:?} not found!", button_code));
|
||||
if self.log_config.input.missing_bindings {
|
||||
println!("Button {:?} not found!", button_code);
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
@@ -269,10 +297,10 @@ impl InputState {
|
||||
axis.axis_inputs.iter().map(|item| {
|
||||
match item {
|
||||
AxisInput::Wheel(modifiers) => {
|
||||
if self.modifiers_are_pressed(*modifiers) { self.analog_wheel_state } else { 0.0 }
|
||||
if self.modifiers_are_pressed(modifiers) { self.analog_wheel_state } else { 0.0 }
|
||||
},
|
||||
AxisInput::MouseMove(direction, modifiers) => {
|
||||
if self.modifiers_are_pressed(*modifiers) {
|
||||
if self.modifiers_are_pressed(modifiers) {
|
||||
match direction {
|
||||
MouseMoveDirection::X => self.mouse_delta_x as f32,
|
||||
MouseMoveDirection::Y => self.mouse_delta_y as f32,
|
||||
@@ -292,7 +320,9 @@ impl InputState {
|
||||
}
|
||||
}).fold(0.0, fold_axis_value)
|
||||
} else {
|
||||
assert!(false, format!("Axis {:?} not found!", axis_code));
|
||||
if self.log_config.input.missing_bindings {
|
||||
println!("Axis {:?} not found!", axis_code);
|
||||
}
|
||||
0.0
|
||||
}
|
||||
}
|
||||
@@ -301,11 +331,11 @@ impl InputState {
|
||||
match digital_input {
|
||||
DigitalInput::Keyboard(keyboard_input) => {
|
||||
self.pressed_scan_codes.contains(&keyboard_input.scan_code)
|
||||
&& self.modifiers_are_pressed(keyboard_input.modifiers)
|
||||
&& self.modifiers_are_pressed(&keyboard_input.modifiers)
|
||||
},
|
||||
DigitalInput::Mouse(mouse_input) => {
|
||||
self.pressed_mouse_buttons.contains(&mouse_input.button)
|
||||
&& self.modifiers_are_pressed(mouse_input.modifiers)
|
||||
&& self.modifiers_are_pressed(&mouse_input.modifiers)
|
||||
},
|
||||
DigitalInput::Wheel(wheel_input) => {
|
||||
self.input_events.contains(&DigitalInputEvent::Pressed(DigitalInput::Wheel(wheel_input.clone())))
|
||||
@@ -316,7 +346,7 @@ impl InputState {
|
||||
}
|
||||
}
|
||||
|
||||
fn modifiers_are_pressed(self: &Self, modifiers: ModifiersState) -> bool {
|
||||
fn modifiers_are_pressed(self: &Self, modifiers: &KeyboardModifierState) -> bool {
|
||||
(!modifiers.ctrl || self.pressed_scan_codes.contains(&29))
|
||||
&& (!modifiers.shift || self.pressed_scan_codes.contains(&42) || self.pressed_scan_codes.contains(&54))
|
||||
&& (!modifiers.alt || self.pressed_scan_codes.contains(&56))
|
||||
@@ -327,10 +357,11 @@ impl InputState {
|
||||
if input.digital_inputs.iter().any(|di| self.digital_input_pressed(di)) { 1.0 } else { 0.0 }
|
||||
}
|
||||
|
||||
pub fn on_keyboard_event(self: &mut Self, state: ElementState, scan_code: ScanCode, modifiers: ModifiersState) {
|
||||
pub fn on_keyboard_event(self: &mut Self, state: ElementState, scan_code: ScanCode, modifiers: KeyboardModifierState) {
|
||||
let input = DigitalInput::Keyboard(KeyboardInput { scan_code, modifiers });
|
||||
match state {
|
||||
ElementState::Pressed => {
|
||||
if self.pressed_scan_codes.contains(&scan_code) { return; }
|
||||
self.input_events.insert(DigitalInputEvent::Pressed(input));
|
||||
self.pressed_scan_codes.insert(scan_code);
|
||||
},
|
||||
@@ -341,7 +372,7 @@ impl InputState {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn on_mouse_event(self: &mut Self, state: &ElementState, button: &MouseButton, modifiers: &ModifiersState) {
|
||||
pub fn on_mouse_event(self: &mut Self, state: &ElementState, button: &MouseButton, modifiers: &KeyboardModifierState) {
|
||||
let input = DigitalInput::Mouse(MouseInput { button: button.clone(), modifiers: modifiers.clone() });
|
||||
match state {
|
||||
ElementState::Pressed => {
|
||||
@@ -355,7 +386,7 @@ impl InputState {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn on_mouse_wheel_event(self: &mut Self, delta: &MouseScrollDelta, modifiers: &ModifiersState) {
|
||||
pub fn on_mouse_wheel_event(self: &mut Self, delta: &MouseScrollDelta, modifiers: &KeyboardModifierState) {
|
||||
let vertical_direction = match delta {
|
||||
MouseScrollDelta::LineDelta(_x, y) => {
|
||||
if *y > 0.0 { Some(WheelInputDirection::Up) }
|
||||
@@ -385,17 +416,17 @@ impl InputState {
|
||||
while let Some(event) = self.controller_input.next_event() {
|
||||
match event.event {
|
||||
EventType::ButtonPressed(button, _) => {
|
||||
if self.log_config.input_events { println!("ControllerButton Pressed {:?} {:?}", event.id, button); }
|
||||
if self.log_config.input.buttons { println!("ControllerButton Pressed {:?} {:?}", event.id, button); }
|
||||
self.input_events.insert(DigitalInputEvent::Pressed(DigitalInput::Controller(ControllerInput { button })));
|
||||
},
|
||||
EventType::ButtonRepeated(_, _) => {},
|
||||
EventType::ButtonReleased(button, _) => {
|
||||
if self.log_config.input_events { println!("ControllerButton Released {:?} {:?}", event.id, button); }
|
||||
if self.log_config.input.buttons { println!("ControllerButton Released {:?} {:?}", event.id, button); }
|
||||
self.input_events.insert(DigitalInputEvent::Released(DigitalInput::Controller(ControllerInput { button })));
|
||||
},
|
||||
EventType::ButtonChanged(_, _, _) => {},
|
||||
EventType::AxisChanged(axis, value, _) => {
|
||||
if self.log_config.input_events { println!("ControllerAxis {:?} {:?} {:?}", event.id, axis, value); }
|
||||
if self.log_config.input.buttons { println!("ControllerAxis {:?} {:?} {:?}", event.id, axis, value); }
|
||||
},
|
||||
EventType::Connected => {},
|
||||
EventType::Disconnected => {},
|
||||
@@ -412,7 +443,7 @@ impl InputState {
|
||||
}
|
||||
}
|
||||
|
||||
fn mods_to_string(modifiers: &ModifiersState) -> String {
|
||||
fn mods_to_string(modifiers: &KeyboardModifierState) -> String {
|
||||
String::from_iter(
|
||||
vec!["shift", "ctrl", "alt", "logo"].iter()
|
||||
.zip(vec![modifiers.shift, modifiers.ctrl, modifiers.alt, modifiers.logo])
|
||||
@@ -422,8 +453,8 @@ fn mods_to_string(modifiers: &ModifiersState) -> String {
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum AxisInput {
|
||||
Wheel(ModifiersState),
|
||||
MouseMove(MouseMoveDirection, ModifiersState),
|
||||
Wheel(KeyboardModifierState),
|
||||
MouseMove(MouseMoveDirection, KeyboardModifierState),
|
||||
Digital(VirtualButton, VirtualButton),
|
||||
Controller(AnalogControllerInput),
|
||||
}
|
||||
@@ -439,19 +470,19 @@ pub enum DigitalInput {
|
||||
#[derive(Debug, Eq, PartialEq, Hash, Clone)]
|
||||
pub struct KeyboardInput {
|
||||
scan_code: ScanCode,
|
||||
modifiers: ModifiersState,
|
||||
modifiers: KeyboardModifierState,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Hash, Clone)]
|
||||
pub struct MouseInput {
|
||||
button: MouseButton,
|
||||
modifiers: ModifiersState,
|
||||
modifiers: KeyboardModifierState,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Hash, Clone)]
|
||||
pub struct WheelInput {
|
||||
direction: WheelInputDirection,
|
||||
modifiers: ModifiersState,
|
||||
modifiers: KeyboardModifierState,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Hash, Clone)]
|
||||
@@ -474,7 +505,7 @@ pub enum MouseMoveDirection {
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct AnalogWheelInput {
|
||||
value: f32,
|
||||
modifiers: ModifiersState,
|
||||
modifiers: KeyboardModifierState,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
|
||||
145
src/main.rs
145
src/main.rs
@@ -1,135 +1,38 @@
|
||||
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::{LinePoint, 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;
|
||||
|
||||
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 line_vertices = (-line_count..=line_count)
|
||||
.flat_map(|it| vec![
|
||||
LinePoint { position: [it as f32, 0., -line_count as f32] },
|
||||
LinePoint { position: [it as f32, 0., line_count as f32] },
|
||||
LinePoint { position: [-line_count as f32, 0., it as f32] },
|
||||
LinePoint { position: [line_count as f32, 0., it as f32] },
|
||||
]).collect();
|
||||
|
||||
let (mut renderer, event_loop) = VulkanRenderer::init(
|
||||
line_vertices,
|
||||
log_config.vulkan_validation_layers,
|
||||
RenderConfig::from_file("config/graphics.toml"),
|
||||
log_config.clone()
|
||||
);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
72
src/mesh.rs
72
src/mesh.rs
@@ -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))
|
||||
}
|
||||
39
src/tests/mod.rs
Normal file
39
src/tests/mod.rs
Normal file
@@ -0,0 +1,39 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use cgmath::{vec3};
|
||||
|
||||
use crate::game::player::{intersect_triangle};
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
633
src/vulkan.rs
633
src/vulkan.rs
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
48
src/vulkan/dds.rs
Normal file
48
src/vulkan/dds.rs
Normal file
@@ -0,0 +1,48 @@
|
||||
use std::{convert::TryInto, io::Read};
|
||||
|
||||
use vulkano::format::Format;
|
||||
|
||||
use super::VulkanRenderer;
|
||||
|
||||
pub fn upload_texture_from_file(path: &str, renderer: &mut VulkanRenderer) -> Result<(), 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);
|
||||
|
||||
if is_dxt1
|
||||
{
|
||||
renderer.upload_texture(&tex_bytes[128..], tex_width, tex_height, Format::BC1_RGBUnormBlock, renderer.device.clone());
|
||||
}
|
||||
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::BC5UnormBlock, renderer.device.clone());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_block_size(format: Format) -> Option<u32> {
|
||||
match format {
|
||||
Format::BC1_RGBUnormBlock => Some(8),
|
||||
Format::BC5UnormBlock => Some(16),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
78
src/vulkan/framebuffers.rs
Normal file
78
src/vulkan/framebuffers.rs
Normal file
@@ -0,0 +1,78 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use vulkano::command_buffer::DynamicState;
|
||||
use vulkano::device::Device;
|
||||
use vulkano::format::Format;
|
||||
use vulkano::image::view::ImageView;
|
||||
use vulkano::image::{AttachmentImage, ImageUsage, SampleCount, SwapchainImage};
|
||||
use vulkano::pipeline::viewport::Viewport;
|
||||
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_samples: Option<SampleCount>,
|
||||
render_pass: Arc<RenderPass>,
|
||||
dynamic_state: &mut DynamicState)
|
||||
-> Vec<Arc<dyn FramebufferAbstract + Send + Sync>> {
|
||||
|
||||
let dim_array = images[0].dimensions();
|
||||
let dim_array_f32 = [dim_array[0] as f32, dim_array[1] as f32];
|
||||
|
||||
let viewport = Viewport {
|
||||
origin: [0.0, 0.0],
|
||||
dimensions: dim_array_f32,
|
||||
depth_range: 0.0..1.0,
|
||||
};
|
||||
dynamic_state.viewports = Some(vec!(viewport));
|
||||
|
||||
let depth_image = if let Some(msaa_sample_count) = msaa_samples {
|
||||
AttachmentImage::multisampled_with_usage(device.clone(), dim_array, msaa_sample_count, Format::D16Unorm, ImageUsage { depth_stencil_attachment: true, ..ImageUsage::none() }).unwrap()
|
||||
} else {
|
||||
AttachmentImage::with_usage(device.clone(), dim_array, Format::D16Unorm, ImageUsage { depth_stencil_attachment: true, ..ImageUsage::none() }).unwrap()
|
||||
};
|
||||
|
||||
let msaa_buffers = if let Some(msaa_sample_count) = msaa_samples {
|
||||
Some(create_msaa_buffers(device.clone(), dim_array, swapchain, msaa_sample_count))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let mut framebuffers = vec![];
|
||||
|
||||
for i in 0..images.len() {
|
||||
let image_buffer = &images[i];
|
||||
let image_view = ImageView::new(image_buffer.clone()).unwrap();
|
||||
let depth_view = ImageView::new(depth_image.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(image_view).unwrap()
|
||||
.add(msaa_view).unwrap()
|
||||
.add(depth_view).unwrap()
|
||||
.build().unwrap()
|
||||
) as Arc<dyn FramebufferAbstract + Send + Sync>);
|
||||
} else {
|
||||
framebuffers.push(Arc::new(Framebuffer::start(render_pass.clone())
|
||||
.add(image_view).unwrap()
|
||||
.add(depth_view).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
|
||||
}
|
||||
89
src/vulkan/gameobject.rs
Normal file
89
src/vulkan/gameobject.rs
Normal file
@@ -0,0 +1,89 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use cgmath::{Deg, Euler, Matrix4, Quaternion, Vector3};
|
||||
|
||||
use crate::input::InputState;
|
||||
use crate::vulkan::{RendererDescriptorSets, TextureHandle};
|
||||
use crate::vulkan::{MeshHandle, VulkanRenderer};
|
||||
|
||||
use super::pipelines::vs;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct GameObject {
|
||||
pub mesh_index: usize,
|
||||
pub texture_index: TextureHandle,
|
||||
pub normal_map_index: TextureHandle,
|
||||
pub position: Vector3<f32>,
|
||||
pub rotation: Quaternion<f32>,
|
||||
pub scale: Vector3<f32>,
|
||||
pub children: Vec<GameObject>,
|
||||
pub descriptor_sets: Vec<Arc<RendererDescriptorSets>>,
|
||||
pub is_selected: bool
|
||||
}
|
||||
|
||||
impl GameObject {
|
||||
pub fn new(mesh: MeshHandle) -> GameObject {
|
||||
GameObject { mesh_index: mesh.index, texture_index: mesh.diffuse_handle, normal_map_index: mesh.normal_handle, position: Vector3::new(0.0, 0.0, 0.0),
|
||||
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 }
|
||||
}
|
||||
|
||||
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) -> vs::ty::PushConstants {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct GameObjectHandle {
|
||||
pub object_index: usize
|
||||
}
|
||||
|
||||
impl GameObjectHandle {
|
||||
pub fn get_game_object<'a>(&self, renderer: &'a VulkanRenderer) -> Option<&'a GameObject> {
|
||||
renderer.game_data.game_objects.get(self.object_index)
|
||||
}
|
||||
|
||||
pub fn get_game_object_mut<'a>(&mut self, renderer: &'a mut VulkanRenderer) -> Option<&'a mut GameObject> {
|
||||
renderer.game_data.game_objects.get_mut(self.object_index)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Updatable {
|
||||
fn update(&mut self, delta_time: f32, input: &InputState, renderer: &mut VulkanRenderer);
|
||||
}
|
||||
169
src/vulkan/mesh.rs
Normal file
169
src/vulkan/mesh.rs
Normal file
@@ -0,0 +1,169 @@
|
||||
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;
|
||||
|
||||
#[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 struct CPUMesh {
|
||||
pub vertices: Vec<Vertex>,
|
||||
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(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 vertices = vertices_result?;
|
||||
let cpu_mesh = CPUMesh {
|
||||
vertices,
|
||||
indices: indices.into_u32().collect(),
|
||||
local_texture_index: texture_index,
|
||||
local_normal_map_index: normal_map_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();
|
||||
|
||||
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()),
|
||||
}
|
||||
}
|
||||
558
src/vulkan/mod.rs
Normal file
558
src/vulkan/mod.rs
Normal file
@@ -0,0 +1,558 @@
|
||||
use std::{sync::Arc};
|
||||
use std::time::SystemTime;
|
||||
|
||||
use cgmath::{Matrix4, SquareMatrix};
|
||||
use dds::get_block_size;
|
||||
use vulkano::command_buffer::CommandBufferUsage::{MultipleSubmit, SimultaneousUse};
|
||||
use vulkano::{buffer::{BufferUsage, CpuAccessibleBuffer}, command_buffer::{PrimaryAutoCommandBuffer, SubpassContents}, image::{ImageDimensions, ImageLayout, ImageUsage, MipmapsCount, immutable::SubImage}, render_pass::{FramebufferAbstract, RenderPass}};
|
||||
use vulkano::command_buffer::{AutoCommandBufferBuilder, PrimaryCommandBuffer, DynamicState};
|
||||
use vulkano::descriptor::DescriptorSet;
|
||||
use vulkano::device::{Device, Features, Queue};
|
||||
use vulkano::format::{ClearValue, Format};
|
||||
use vulkano::image::{ImageCreateFlags, ImmutableImage};
|
||||
use vulkano::instance::{ApplicationInfo, Instance, PhysicalDevice, Version};
|
||||
use vulkano::instance::debug::{DebugCallback, MessageSeverity, MessageType};
|
||||
use vulkano::sampler::{Filter, MipmapMode, Sampler, SamplerAddressMode};
|
||||
use vulkano::swapchain::{AcquireError, FullscreenExclusive, PresentMode, 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 winit::window::{Window, WindowBuilder};
|
||||
|
||||
use mesh::CPUMesh;
|
||||
use pipelines::{Drawcall, LineShader};
|
||||
use pipelines::line_vs::ty::LinePushConstants;
|
||||
use pipelines::DefaultShader;
|
||||
use pipelines::vs;
|
||||
|
||||
use crate::config::LogConfig;
|
||||
use crate::{config::RenderConfig};
|
||||
use crate::vulkan::gameobject::{GameObject, GameObjectHandle};
|
||||
|
||||
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);
|
||||
|
||||
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) -> vs::ty::ObjectUniformData;
|
||||
}
|
||||
|
||||
pub struct Mesh {
|
||||
pub vertex_buffer: Arc<CpuAccessibleBuffer<[Vertex]>>,
|
||||
pub index_buffer: Arc<CpuAccessibleBuffer<[u32]>>,
|
||||
pub original_path: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MeshHandle {
|
||||
pub index: usize,
|
||||
pub diffuse_handle: TextureHandle,
|
||||
pub normal_handle: TextureHandle,
|
||||
pub original_path: String
|
||||
}
|
||||
|
||||
pub(crate) type TextureHandle = usize;
|
||||
pub struct Texture {
|
||||
pub image: Arc<ImmutableImage>,
|
||||
pub sampler: Arc<Sampler>
|
||||
}
|
||||
|
||||
pub struct GameData {
|
||||
pub start_time: SystemTime,
|
||||
pub line_vertices: Vec<LinePoint>,
|
||||
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<Texture>,
|
||||
pub use_line_pipeline: bool,
|
||||
}
|
||||
|
||||
pub(crate) type RendererDescriptorSets = dyn DescriptorSet + Send + Sync;
|
||||
|
||||
pub struct VulkanRenderer {
|
||||
pub game_data: GameData,
|
||||
pub device: Arc<Device>,
|
||||
pub framebuffers: Vec<Arc<dyn FramebufferAbstract + Send + Sync>>,
|
||||
pub dynamic_state: DynamicState,
|
||||
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 line_vertex_buffer: Arc<CpuAccessibleBuffer<[LinePoint]>>,
|
||||
pub render_config: RenderConfig
|
||||
}
|
||||
|
||||
impl VulkanRenderer {
|
||||
pub fn init(line_vertices: Vec<LinePoint>, enable_validation_layers: bool, render_config: RenderConfig, log_config: LogConfig) -> (VulkanRenderer, EventLoop<()>) {
|
||||
// Create empty game data struct to be filled
|
||||
let mut data = GameData {
|
||||
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![],
|
||||
use_line_pipeline: true,
|
||||
};
|
||||
|
||||
// Create basic vulkan instance with layers and info
|
||||
let instance = {
|
||||
let extensions = vulkano::instance::InstanceExtensions {
|
||||
ext_debug_utils: 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 {
|
||||
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_2, &extensions, VALIDATION_LAYERS.iter().cloned()).expect("failed to create Vulkan instance")
|
||||
} else {
|
||||
Instance::new(Some(&app_info), Version::V1_2, &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, move |msg| {
|
||||
let (type_str, will_log) = match (msg.severity.error, msg.severity.warning, msg.severity.information, msg.severity.verbose) {
|
||||
(true, _, _, _) => ("!!", log_config.debug_errors),
|
||||
(_, true, _, _) => ("!", log_config.debug_warnings),
|
||||
(_, _, _, true) => ("i", log_config.debug_info),
|
||||
_ => (" ", log_config.debug_verbose)
|
||||
};
|
||||
|
||||
let layer_str = msg.layer_prefix;
|
||||
|
||||
if will_log { println!("[{}][{:?}]: {}", type_str, layer_str, msg.description); }
|
||||
}).ok();
|
||||
}
|
||||
|
||||
// TODO: Just get the first physical device we find, it's fiiiine...
|
||||
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();
|
||||
|
||||
// TODO: Tutorial says we need more queues
|
||||
// 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();
|
||||
|
||||
// Queue
|
||||
let device_ext = vulkano::device::DeviceExtensions { khr_swapchain: true, ..vulkano::device::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)
|
||||
.usage(usage)
|
||||
.sharing_mode(&queue)
|
||||
.transform(SurfaceTransform::Identity)
|
||||
.composite_alpha(alpha)
|
||||
.present_mode(PresentMode::Fifo)
|
||||
.fullscreen_exclusive(FullscreenExclusive::Default)
|
||||
.clipped(true)
|
||||
.color_space(color_space)
|
||||
.build().unwrap()
|
||||
};
|
||||
|
||||
// Render pass
|
||||
let render_pass = renderpass::create_render_pass(device.clone(), &render_config, swapchain.format());
|
||||
|
||||
let line_vertex_buffer = CpuAccessibleBuffer::from_iter(device.clone(), BufferUsage::vertex_buffer(), false, data.line_vertices.iter().cloned()).unwrap();
|
||||
|
||||
let pipelines: Vec<Box<dyn Drawcall>> = vec![
|
||||
Box::new(DefaultShader::new(device.clone(), render_pass.clone())),
|
||||
Box::new(LineShader::new(device.clone(), render_pass.clone(), line_vertex_buffer.clone())),
|
||||
];
|
||||
|
||||
// Dynamic viewports allow us to recreate just the viewport when the window is resized
|
||||
// Otherwise we would have to recreate the whole pipeline.
|
||||
let mut dynamic_state = DynamicState { line_width: None, viewports: None, scissors: None, compare_mask: None, write_mask: None, reference: None };
|
||||
|
||||
|
||||
|
||||
// The render pass we created above only describes the layout of our framebuffers. Before we
|
||||
// can draw we also need to create the actual framebuffers.
|
||||
let framebuffers = framebuffers::create_framebuffers(device.clone(), &swapchain, &images, render_config.get_msaa(), render_pass.clone(), &mut dynamic_state);
|
||||
|
||||
let mut uniform_buffers = Vec::new();
|
||||
let uniform_buffer = vs::ty::ObjectUniformData {
|
||||
view: Matrix4::identity().into(),
|
||||
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,
|
||||
dynamic_state, pipelines, uniform_buffers,
|
||||
surface, swapchain, render_pass, queue,
|
||||
recreate_swapchain: false, debug_callback, previous_frame_end,
|
||||
render_config,
|
||||
line_vertex_buffer,
|
||||
}, events_loop)
|
||||
}
|
||||
|
||||
fn create_command_buffer(self: &mut Self, fb_index: usize, uniform_buffer_data: vs::ty::ObjectUniformData) -> Arc<PrimaryAutoCommandBuffer> {
|
||||
// General setup
|
||||
let mut builder = AutoCommandBufferBuilder::primary(self.device.clone(), self.queue.family(), SimultaneousUse).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();
|
||||
}
|
||||
|
||||
// Draw meshes etc.
|
||||
for pipeline in &self.pipelines {
|
||||
pipeline.draw(&mut builder, fb_index, &self.game_data, &self.dynamic_state);
|
||||
}
|
||||
|
||||
// General cleanup
|
||||
builder.end_render_pass().unwrap();
|
||||
Arc::new(builder.build().unwrap())
|
||||
}
|
||||
|
||||
|
||||
|
||||
pub fn render_loop(self: &mut Self, new_ubo: vs::ty::ObjectUniformData) {
|
||||
// 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),
|
||||
};
|
||||
|
||||
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(LineShader::new(self.device.clone(), self.render_pass.clone(), self.line_vertex_buffer.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(), &mut self.dynamic_state);
|
||||
|
||||
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).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: String) -> usize {
|
||||
// let mut collision_mesh = mgf::Mesh::new();
|
||||
// mesh.vertices.iter().for_each(|v| {
|
||||
// collision_mesh.push_vert(v.position.into());
|
||||
// }); // TODO: convert vert pos to world space
|
||||
// for i in (0..mesh.indices.len()).step_by(3) {
|
||||
// collision_mesh.push_face((mesh.indices[i] as usize, mesh.indices[i + 1] as usize, mesh.indices[i + 2] as usize));
|
||||
// }
|
||||
|
||||
let vertex_buffer = CpuAccessibleBuffer::from_iter(self.device.clone(), BufferUsage::vertex_buffer(), false, mesh.vertices.into_iter()).unwrap();
|
||||
let index_buffer = CpuAccessibleBuffer::from_iter(self.device.clone(), BufferUsage::index_buffer(), false, mesh.indices.into_iter()).unwrap();
|
||||
|
||||
self.game_data.meshes.push(Mesh { vertex_buffer, index_buffer, original_path });
|
||||
self.game_data.meshes.len() - 1
|
||||
}
|
||||
|
||||
pub fn upload_texture(self: &mut Self, bytes: &[u8], width: u32, height: u32, format: Format, device: Arc<Device>) {
|
||||
let dimensions = ImageDimensions::Dim2d { width, height, array_layers: 1 };
|
||||
|
||||
let usage = ImageUsage {
|
||||
transfer_destination: true,
|
||||
transfer_source: false,
|
||||
sampled: true,
|
||||
..ImageUsage::none()
|
||||
};
|
||||
|
||||
let layout = ImageLayout::ShaderReadOnlyOptimal;
|
||||
|
||||
let (immutable_image, initializer) = ImmutableImage::uninitialized(
|
||||
device.clone(),
|
||||
dimensions,
|
||||
format,
|
||||
MipmapsCount::Log2,
|
||||
usage,
|
||||
ImageCreateFlags::default(),
|
||||
layout,
|
||||
device.active_queue_families(),
|
||||
).unwrap();
|
||||
|
||||
let init = SubImage::new(
|
||||
Arc::new(initializer),
|
||||
0,
|
||||
immutable_image.mipmap_levels(),
|
||||
0,
|
||||
1,
|
||||
ImageLayout::ShaderReadOnlyOptimal,
|
||||
);
|
||||
|
||||
// TODO: do we need multiple submit?
|
||||
let mut cbb = AutoCommandBufferBuilder::primary(device.clone(), self.queue.family(), MultipleSubmit).unwrap();
|
||||
|
||||
let mut offset = 0;
|
||||
|
||||
let block_bytes = get_block_size(format).expect(&format!("Unknown texture format {:?}!", format));
|
||||
|
||||
for i in 0..immutable_image.mipmap_levels() {
|
||||
let mip_size = dimensions.mipmap_dimensions(i).unwrap().width_height_depth();
|
||||
|
||||
let mip_byte_size = (
|
||||
(u32::max(4, mip_size[0]) / 4)
|
||||
* (u32::max(4, mip_size[1]) / 4)
|
||||
* block_bytes) as usize;
|
||||
|
||||
let source = CpuAccessibleBuffer::from_iter(
|
||||
device.clone(),
|
||||
BufferUsage::transfer_source(),
|
||||
false,
|
||||
bytes[offset..(offset + mip_byte_size)].iter().cloned(),
|
||||
).unwrap();
|
||||
|
||||
cbb.copy_buffer_to_image_dimensions(
|
||||
source.clone(),
|
||||
init.clone(),
|
||||
[0, 0, 0],
|
||||
mip_size,
|
||||
0,
|
||||
dimensions.array_layers(),
|
||||
i,
|
||||
).unwrap();
|
||||
|
||||
offset += mip_byte_size;
|
||||
}
|
||||
|
||||
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::Linear, Filter::Linear,
|
||||
MipmapMode::Linear, SamplerAddressMode::Repeat, SamplerAddressMode::Repeat,
|
||||
SamplerAddressMode::Repeat, 0.0, 1.0, 0.0, (immutable_image.mipmap_levels() - 1) as f32).unwrap();
|
||||
|
||||
self.game_data.textures.push(Texture { image: immutable_image, sampler });
|
||||
}
|
||||
|
||||
pub fn add_game_object(self: &mut Self, mut game_object: GameObject, pipeline_index: usize) -> GameObjectHandle {
|
||||
self.pipelines[pipeline_index].create_descriptor_set(&mut game_object, self);
|
||||
self.game_data.game_objects.push(game_object);
|
||||
|
||||
GameObjectHandle {
|
||||
object_index: self.game_data.game_objects.len() - 1
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clear_all(&mut self) {
|
||||
self.game_data.game_objects.clear();
|
||||
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<()>) {
|
||||
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::RedrawEventsCleared => {
|
||||
let ubo = game.update(&mut renderer);
|
||||
renderer.render_loop(ubo);
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
253
src/vulkan/pipelines.rs
Normal file
253
src/vulkan/pipelines.rs
Normal file
@@ -0,0 +1,253 @@
|
||||
use std::{convert::TryInto, io::{self, ErrorKind, Read, Write}, path::PathBuf, sync::Arc};
|
||||
|
||||
use vulkano::{command_buffer::{AutoCommandBufferBuilder, PrimaryAutoCommandBuffer}, descriptor::{descriptor_set::PersistentDescriptorSet}, image::view::ImageView, pipeline::{shader::{ShaderModule}}, render_pass::{RenderPass, Subpass}};
|
||||
use vulkano::command_buffer::DynamicState;
|
||||
use vulkano::device::Device;
|
||||
use vulkano::pipeline::GraphicsPipeline;
|
||||
use vulkano::pipeline::GraphicsPipelineAbstract;
|
||||
|
||||
use crate::GameObject;
|
||||
use crate::vulkan::{LinePoint, Vertex};
|
||||
use crate::vulkan::GameData;
|
||||
use crate::VulkanRenderer;
|
||||
|
||||
type RP = Arc<RenderPass>;
|
||||
type GP = Arc<dyn GraphicsPipelineAbstract + Send + Sync>;
|
||||
|
||||
pub trait Drawcall {
|
||||
fn draw(self: &Self, builder: &mut AutoCommandBufferBuilder<PrimaryAutoCommandBuffer>, fb_index: usize, game_data: &GameData, dynamic_state: &DynamicState);
|
||||
fn create_descriptor_set(self: &Self, game_object: &mut GameObject, renderer: &VulkanRenderer);
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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_entry = fs_module.graphics_entry_point(entry_name_c, yeet, &[], ref_shader.main_entry_point().input().clone(), ref_shader.main_entry_point().output().clone(), GraphicsShaderType::Fragment);
|
||||
|
||||
// let vs_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_layout, &[], vs::MainInput, vs::MainOutput, GraphicsShaderType::Vertex);
|
||||
|
||||
let fs = fs::Shader::load(device.clone()).unwrap();
|
||||
let vs = vs::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_data: &GameData, dynamic_state: &DynamicState) {
|
||||
for i in 0..game_data.game_objects.len() {
|
||||
let game_object = &game_data.game_objects[i];
|
||||
let mesh = &game_data.meshes[game_object.mesh_index];
|
||||
let push_constants = game_object.get_push_constants();
|
||||
|
||||
builder.draw_indexed(
|
||||
self.pipeline.clone(),
|
||||
dynamic_state,
|
||||
vec![mesh.vertex_buffer.clone()],
|
||||
mesh.index_buffer.clone(),
|
||||
game_object.descriptor_sets[fb_index].clone(),
|
||||
push_constants,
|
||||
vec![]).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn create_descriptor_set(self: &Self, game_object: &mut GameObject, renderer: &VulkanRenderer) {
|
||||
let descriptor_set_layout = self.get_pipeline().layout().descriptor_set_layout(0).unwrap().clone();
|
||||
|
||||
println!("Diff: {:?}, Norm: {:?}", game_object.texture_index, game_object.normal_map_index);
|
||||
|
||||
game_object.descriptor_sets = renderer.uniform_buffers.iter().map(|uniform_buffer| {
|
||||
let descriptor_set: Arc<(dyn vulkano::descriptor::DescriptorSet + std::marker::Send + std::marker::Sync + 'static)>;
|
||||
let builder = PersistentDescriptorSet::start(descriptor_set_layout.clone());
|
||||
|
||||
let diffuse = &renderer.game_data.textures[game_object.texture_index];
|
||||
let diffuse_view = ImageView::new(diffuse.image.clone()).unwrap();
|
||||
let normal_map = &renderer.game_data.textures[game_object.normal_map_index];
|
||||
let normal_view = ImageView::new(normal_map.image.clone()).unwrap();
|
||||
|
||||
descriptor_set = Arc::new(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()
|
||||
.build().unwrap());
|
||||
|
||||
descriptor_set
|
||||
}).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_data: &GameData, dynamic_state: &DynamicState) {
|
||||
builder.draw(self.pipeline.clone(),
|
||||
&dynamic_state,
|
||||
vec![self.vertex_buffer.clone()],
|
||||
(),
|
||||
game_data.line_push_constants.clone(),
|
||||
vec![]).unwrap();
|
||||
}
|
||||
|
||||
fn create_descriptor_set(self: &Self, _game_object: &mut GameObject, _renderer: &VulkanRenderer) {
|
||||
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
64
src/vulkan/renderpass.rs
Normal file
64
src/vulkan/renderpass.rs
Normal file
@@ -0,0 +1,64 @@
|
||||
use crate::RenderConfig;
|
||||
use std::sync::Arc;
|
||||
use vulkano::format::Format;
|
||||
use vulkano::device::Device;
|
||||
use vulkano::render_pass::RenderPass;
|
||||
|
||||
pub fn create_render_pass(device: Arc<Device>, render_config: &RenderConfig, swapchain_format: Format) -> Arc<RenderPass> {
|
||||
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::D16Unorm,
|
||||
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::D16Unorm,
|
||||
samples: 1,
|
||||
initial_layout: ImageLayout::Undefined,
|
||||
final_layout: ImageLayout::DepthStencilAttachmentOptimal,
|
||||
}
|
||||
},
|
||||
pass: {
|
||||
color: [color],
|
||||
depth_stencil: {depth}
|
||||
}
|
||||
).unwrap())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user