Compare commits
No commits in common. "main" and "old-ash" have entirely different histories.
72 changed files with 2143 additions and 5080 deletions
7
.vscode/extensions.json
vendored
7
.vscode/extensions.json
vendored
|
@ -1,7 +0,0 @@
|
||||||
{
|
|
||||||
"recommendations": [
|
|
||||||
"pinage404.rust-extension-pack",
|
|
||||||
"vadimcn.vscode-lldb",
|
|
||||||
"jnoortheen.nix-ide"
|
|
||||||
]
|
|
||||||
}
|
|
45
.vscode/launch.json
vendored
45
.vscode/launch.json
vendored
|
@ -1,45 +0,0 @@
|
||||||
{
|
|
||||||
// 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": "Debug executable 'rust_vulkan_test'",
|
|
||||||
"cargo": {
|
|
||||||
"args": [
|
|
||||||
"build",
|
|
||||||
"--bin=rust_vulkan_test",
|
|
||||||
"--package=rust_vulkan_test"
|
|
||||||
],
|
|
||||||
"filter": {
|
|
||||||
"name": "rust_vulkan_test",
|
|
||||||
"kind": "bin"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"args": [],
|
|
||||||
"cwd": "${workspaceFolder}"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "lldb",
|
|
||||||
"request": "launch",
|
|
||||||
"name": "Debug unit tests in executable 'rust_vulkan_test'",
|
|
||||||
"cargo": {
|
|
||||||
"args": [
|
|
||||||
"test",
|
|
||||||
"--no-run",
|
|
||||||
"--bin=rust_vulkan_test",
|
|
||||||
"--package=rust_vulkan_test"
|
|
||||||
],
|
|
||||||
"filter": {
|
|
||||||
"name": "rust_vulkan_test",
|
|
||||||
"kind": "bin"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"args": [],
|
|
||||||
"cwd": "${workspaceFolder}"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
11
.vscode/settings.json
vendored
11
.vscode/settings.json
vendored
|
@ -1,11 +0,0 @@
|
||||||
{
|
|
||||||
"editor.formatOnSave": true,
|
|
||||||
"editor.codeActionsOnSave": {
|
|
||||||
"source.organizeImports": "always",
|
|
||||||
},
|
|
||||||
"[rust]": {
|
|
||||||
"editor.defaultFormatter": "rust-lang.rust-analyzer",
|
|
||||||
},
|
|
||||||
"files.insertFinalNewline": true,
|
|
||||||
"files.trimTrailingWhitespace": true
|
|
||||||
}
|
|
2360
Cargo.lock
generated
2360
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
25
Cargo.toml
25
Cargo.toml
|
@ -1,30 +1,19 @@
|
||||||
[package]
|
[package]
|
||||||
name = "rust_vulkan_test"
|
name = "rust_vulkan_test"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2021"
|
||||||
authors = ["Florian RICHER <florian.richer@protonmail.com>"]
|
authors = ["Florian RICHER <florian.richer@protonmail.com>"]
|
||||||
publish = false
|
publish = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
thiserror = "2.0"
|
|
||||||
winit = { version = "0.30", features = ["rwh_06"] }
|
winit = { version = "0.30", features = ["rwh_06"] }
|
||||||
|
ash = { version = "0.38", default-features = false, features = ["linked", "debug", "std"] }
|
||||||
vulkano = "0.35"
|
ash-window = "0.13"
|
||||||
vulkano-shaders = "0.35"
|
|
||||||
vulkano-util = "0.35"
|
|
||||||
egui_winit_vulkano = { version = "0.28" }
|
|
||||||
|
|
||||||
image = { version = "0.25", features = ["png", "jpeg"] }
|
|
||||||
|
|
||||||
# Math
|
|
||||||
glam = { version = "0.30" }
|
|
||||||
|
|
||||||
# Log and tracing
|
# Log and tracing
|
||||||
tracing = "0.1"
|
log = "0.4"
|
||||||
tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt"] }
|
env_logger = "0.11.5"
|
||||||
tracing-log = "0.2"
|
|
||||||
tracing-tracy = "0.11"
|
|
||||||
|
|
||||||
# Random
|
[build-dependencies]
|
||||||
rand = "0.9"
|
glob = "0.3"
|
17
README.md
17
README.md
|
@ -1,17 +0,0 @@
|
||||||
# Project
|
|
||||||
|
|
||||||
## Notes
|
|
||||||
|
|
||||||
1. Run renderdoc on wayland:
|
|
||||||
|
|
||||||
```console
|
|
||||||
WAYLAND_DISPLAY= QT_QPA_PLATFORM=xcb qrenderdoc
|
|
||||||
```
|
|
||||||
> Not supported yet https://github.com/baldurk/renderdoc/issues/853
|
|
||||||
|
|
||||||
2. [Difference Between OpenGL and Vulkan](./docs/OPENGL_VULKAN_DIFF.md)
|
|
||||||
|
|
||||||
## Usefull links
|
|
||||||
|
|
||||||
- https://vulkan-tutorial.com/fr/Introduction
|
|
||||||
- https://github.com/bwasty/vulkan-tutorial-rs
|
|
24
build.rs
Normal file
24
build.rs
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
for shader in glob::glob("res/shaders/*").unwrap().filter_map(Result::ok) {
|
||||||
|
if !shader.is_file() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let shader_file_name = shader.to_str().unwrap();
|
||||||
|
|
||||||
|
let mut command = Command::new("glslc");
|
||||||
|
command.arg(&shader);
|
||||||
|
|
||||||
|
let out_file = match shader.extension().unwrap().to_str().unwrap() {
|
||||||
|
"vert" => shader_file_name.replace(".vert", ".vert.spv"),
|
||||||
|
"frag" => shader_file_name.replace(".frag", ".frag.spv"),
|
||||||
|
_ => continue,
|
||||||
|
};
|
||||||
|
|
||||||
|
command.arg("-o");
|
||||||
|
command.arg(out_file);
|
||||||
|
command.output().unwrap();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,11 +0,0 @@
|
||||||
# Difference between Vulkan and OpenGL
|
|
||||||
|
|
||||||
Viewport:
|
|
||||||
|
|
||||||
- Y axis is flipped like D3D
|
|
||||||
- Clipped Z axis is not [-1; 1] but [0; 1]
|
|
||||||
|
|
||||||

|
|
||||||

|
|
||||||
|
|
||||||
See: [Vulkan Tutorial (Vertex step)](https://vulkan-tutorial.com/Drawing_a_triangle/Graphics_pipeline_basics/Shader_modules) and [VK_KHR_maintenance1 (Allow negative height)](https://registry.khronos.org/vulkan/specs/latest/man/html/VK_KHR_maintenance1.html#_description)
|
|
Binary file not shown.
Before Width: | Height: | Size: 24 KiB |
|
@ -1,219 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
|
||||||
|
|
||||||
<svg
|
|
||||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
|
||||||
xmlns:cc="http://creativecommons.org/ns#"
|
|
||||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
|
||||||
xmlns:svg="http://www.w3.org/2000/svg"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
|
||||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
|
||||||
width="168.23553mm"
|
|
||||||
height="76.127022mm"
|
|
||||||
viewBox="0 0 596.11016 269.74141"
|
|
||||||
id="svg2"
|
|
||||||
version="1.1"
|
|
||||||
inkscape:version="0.91 r13725"
|
|
||||||
sodipodi:docname="clip_coordinates.svg">
|
|
||||||
<defs
|
|
||||||
id="defs4" />
|
|
||||||
<sodipodi:namedview
|
|
||||||
id="base"
|
|
||||||
pagecolor="#ffffff"
|
|
||||||
bordercolor="#666666"
|
|
||||||
borderopacity="1.0"
|
|
||||||
inkscape:pageopacity="0.0"
|
|
||||||
inkscape:pageshadow="2"
|
|
||||||
inkscape:zoom="0.98994949"
|
|
||||||
inkscape:cx="140.75091"
|
|
||||||
inkscape:cy="-3.0732866"
|
|
||||||
inkscape:document-units="px"
|
|
||||||
inkscape:current-layer="layer1"
|
|
||||||
showgrid="false"
|
|
||||||
inkscape:window-width="1600"
|
|
||||||
inkscape:window-height="837"
|
|
||||||
inkscape:window-x="-8"
|
|
||||||
inkscape:window-y="-8"
|
|
||||||
inkscape:window-maximized="1"
|
|
||||||
fit-margin-top="10"
|
|
||||||
fit-margin-left="10"
|
|
||||||
fit-margin-right="10"
|
|
||||||
fit-margin-bottom="10" />
|
|
||||||
<metadata
|
|
||||||
id="metadata7">
|
|
||||||
<rdf:RDF>
|
|
||||||
<cc:Work
|
|
||||||
rdf:about="">
|
|
||||||
<dc:format>image/svg+xml</dc:format>
|
|
||||||
<dc:type
|
|
||||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
|
||||||
<dc:title></dc:title>
|
|
||||||
</cc:Work>
|
|
||||||
</rdf:RDF>
|
|
||||||
</metadata>
|
|
||||||
<g
|
|
||||||
inkscape:label="Layer 1"
|
|
||||||
inkscape:groupmode="layer"
|
|
||||||
id="layer1"
|
|
||||||
transform="translate(-68.169789,-67.73013)">
|
|
||||||
<rect
|
|
||||||
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:2.37949777;stroke-opacity:1"
|
|
||||||
id="rect4136"
|
|
||||||
width="185.26089"
|
|
||||||
height="129.17273"
|
|
||||||
x="127.66544"
|
|
||||||
y="152.46893" />
|
|
||||||
<text
|
|
||||||
xml:space="preserve"
|
|
||||||
style="font-style:normal;font-weight:normal;font-size:12.5px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
|
||||||
x="142.5"
|
|
||||||
y="114.50506"
|
|
||||||
id="text4153"
|
|
||||||
sodipodi:linespacing="125%"><tspan
|
|
||||||
sodipodi:role="line"
|
|
||||||
id="tspan4155"
|
|
||||||
x="142.5"
|
|
||||||
y="114.50506">Framebuffer coordinates</tspan></text>
|
|
||||||
<text
|
|
||||||
xml:space="preserve"
|
|
||||||
style="font-style:normal;font-weight:normal;font-size:12.5px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#4d4d4d;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
|
||||||
x="108.08633"
|
|
||||||
y="144.23506"
|
|
||||||
id="text4157"
|
|
||||||
sodipodi:linespacing="125%"><tspan
|
|
||||||
sodipodi:role="line"
|
|
||||||
id="tspan4159"
|
|
||||||
x="108.08633"
|
|
||||||
y="144.23506">(0, 0)</tspan></text>
|
|
||||||
<text
|
|
||||||
xml:space="preserve"
|
|
||||||
style="font-style:normal;font-weight:normal;font-size:12.5px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#4d4d4d;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
|
||||||
x="289.4823"
|
|
||||||
y="143.68567"
|
|
||||||
id="text4157-1"
|
|
||||||
sodipodi:linespacing="125%"><tspan
|
|
||||||
sodipodi:role="line"
|
|
||||||
id="tspan4159-7"
|
|
||||||
x="289.4823"
|
|
||||||
y="143.68567">(1920, 0)</tspan></text>
|
|
||||||
<text
|
|
||||||
xml:space="preserve"
|
|
||||||
style="font-style:normal;font-weight:normal;font-size:12.5px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#4d4d4d;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
|
||||||
x="102.49812"
|
|
||||||
y="299.52383"
|
|
||||||
id="text4157-0"
|
|
||||||
sodipodi:linespacing="125%"><tspan
|
|
||||||
sodipodi:role="line"
|
|
||||||
id="tspan4159-3"
|
|
||||||
x="102.49812"
|
|
||||||
y="299.52383">(0, 1080)</tspan></text>
|
|
||||||
<text
|
|
||||||
xml:space="preserve"
|
|
||||||
style="font-style:normal;font-weight:normal;font-size:12.5px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#4d4d4d;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
|
||||||
x="277.83316"
|
|
||||||
y="298.46939"
|
|
||||||
id="text4157-1-3"
|
|
||||||
sodipodi:linespacing="125%"><tspan
|
|
||||||
sodipodi:role="line"
|
|
||||||
id="tspan4159-7-2"
|
|
||||||
x="277.83316"
|
|
||||||
y="298.46939">(1920, 1080)</tspan></text>
|
|
||||||
<circle
|
|
||||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-opacity:1"
|
|
||||||
id="path4229"
|
|
||||||
cx="220.46579"
|
|
||||||
cy="218.48128"
|
|
||||||
r="1.767767" />
|
|
||||||
<text
|
|
||||||
xml:space="preserve"
|
|
||||||
style="font-style:normal;font-weight:normal;font-size:12.5px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#4d4d4d;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
|
||||||
x="187.29964"
|
|
||||||
y="232.99626"
|
|
||||||
id="text4157-1-3-3"
|
|
||||||
sodipodi:linespacing="125%"><tspan
|
|
||||||
sodipodi:role="line"
|
|
||||||
id="tspan4159-7-2-3"
|
|
||||||
x="187.29964"
|
|
||||||
y="232.99626">(960, 540)</tspan></text>
|
|
||||||
<rect
|
|
||||||
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:2.37949777;stroke-opacity:1"
|
|
||||||
id="rect4136-0"
|
|
||||||
width="185.26089"
|
|
||||||
height="129.17273"
|
|
||||||
x="426.228"
|
|
||||||
y="150.62413" />
|
|
||||||
<text
|
|
||||||
xml:space="preserve"
|
|
||||||
style="font-style:normal;font-weight:normal;font-size:12.5px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
|
||||||
x="465.34827"
|
|
||||||
y="112.66027"
|
|
||||||
id="text4153-2"
|
|
||||||
sodipodi:linespacing="125%"><tspan
|
|
||||||
sodipodi:role="line"
|
|
||||||
id="tspan4155-2"
|
|
||||||
x="435.34827"
|
|
||||||
y="112.66027">Normalized device coordinates</tspan></text>
|
|
||||||
<text
|
|
||||||
xml:space="preserve"
|
|
||||||
style="font-style:normal;font-weight:normal;font-size:12.5px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#4d4d4d;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
|
||||||
x="406.6489"
|
|
||||||
y="142.39026"
|
|
||||||
id="text4157-9"
|
|
||||||
sodipodi:linespacing="125%"><tspan
|
|
||||||
sodipodi:role="line"
|
|
||||||
id="tspan4159-0"
|
|
||||||
x="406.6489"
|
|
||||||
y="142.39026">(-1, -1)</tspan></text>
|
|
||||||
<text
|
|
||||||
xml:space="preserve"
|
|
||||||
style="font-style:normal;font-weight:normal;font-size:12.5px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#4d4d4d;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
|
||||||
x="588.04486"
|
|
||||||
y="141.84087"
|
|
||||||
id="text4157-1-4"
|
|
||||||
sodipodi:linespacing="125%"><tspan
|
|
||||||
sodipodi:role="line"
|
|
||||||
id="tspan4159-7-21"
|
|
||||||
x="588.04486"
|
|
||||||
y="141.84087">(1, -1)</tspan></text>
|
|
||||||
<text
|
|
||||||
xml:space="preserve"
|
|
||||||
style="font-style:normal;font-weight:normal;font-size:12.5px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#4d4d4d;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
|
||||||
x="401.0607"
|
|
||||||
y="297.67902"
|
|
||||||
id="text4157-0-6"
|
|
||||||
sodipodi:linespacing="125%"><tspan
|
|
||||||
sodipodi:role="line"
|
|
||||||
id="tspan4159-3-5"
|
|
||||||
x="401.0607"
|
|
||||||
y="297.67902">(-1, 1)</tspan></text>
|
|
||||||
<text
|
|
||||||
xml:space="preserve"
|
|
||||||
style="font-style:normal;font-weight:normal;font-size:12.5px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#4d4d4d;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
|
||||||
x="592.82428"
|
|
||||||
y="296.62457"
|
|
||||||
id="text4157-1-3-7"
|
|
||||||
sodipodi:linespacing="125%"><tspan
|
|
||||||
sodipodi:role="line"
|
|
||||||
id="tspan4159-7-2-6"
|
|
||||||
x="592.82428"
|
|
||||||
y="296.62457">(1, 1)</tspan></text>
|
|
||||||
<circle
|
|
||||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-opacity:1"
|
|
||||||
id="path4229-5"
|
|
||||||
cx="519.02832"
|
|
||||||
cy="216.63647"
|
|
||||||
r="1.767767" />
|
|
||||||
<text
|
|
||||||
xml:space="preserve"
|
|
||||||
style="font-style:normal;font-weight:normal;font-size:12.5px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#4d4d4d;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
|
||||||
x="500.14792"
|
|
||||||
y="231.15146"
|
|
||||||
id="text4157-1-3-3-8"
|
|
||||||
sodipodi:linespacing="125%"><tspan
|
|
||||||
sodipodi:role="line"
|
|
||||||
id="tspan4159-7-2-3-0"
|
|
||||||
x="500.14792"
|
|
||||||
y="231.15146">(0, 0)</tspan></text>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 8.8 KiB |
29
flake.lock
generated
29
flake.lock
generated
|
@ -5,11 +5,11 @@
|
||||||
"systems": "systems"
|
"systems": "systems"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1731533236,
|
"lastModified": 1726560853,
|
||||||
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
"narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=",
|
||||||
"owner": "numtide",
|
"owner": "numtide",
|
||||||
"repo": "flake-utils",
|
"repo": "flake-utils",
|
||||||
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
"rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -28,27 +28,26 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1735283791,
|
"lastModified": 1713543440,
|
||||||
"narHash": "sha256-JlT4VFs8aVlW+l151HZIZumfFsccZXcO/k5WpbYF09Y=",
|
"narHash": "sha256-lnzZQYG0+EXl/6NkGpyIz+FEOc/DSEG57AP1VsdeNrM=",
|
||||||
"owner": "phirsch",
|
"owner": "nix-community",
|
||||||
"repo": "nixGL",
|
"repo": "nixGL",
|
||||||
"rev": "ea8baea3b9d854bf9cf5c834a805c50948dd2603",
|
"rev": "310f8e49a149e4c9ea52f1adf70cdc768ec53f8a",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "phirsch",
|
"owner": "nix-community",
|
||||||
"ref": "fix-versionMatch",
|
|
||||||
"repo": "nixGL",
|
"repo": "nixGL",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1747312588,
|
"lastModified": 1730831018,
|
||||||
"narHash": "sha256-MmJvj6mlWzeRwKGLcwmZpKaOPZ5nJb/6al5CXqJsgjo=",
|
"narHash": "sha256-2S0HwIFRxYp+afuoFORcZA9TjryAf512GmE0MTfEOPU=",
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "b1bebd0fe266bbd1820019612ead889e96a8fa2d",
|
"rev": "8c4dc69b9732f6bbe826b5fbb32184987520ff26",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -73,11 +72,11 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1747363019,
|
"lastModified": 1730860036,
|
||||||
"narHash": "sha256-N4dwkRBmpOosa4gfFkFf/LTD8oOcNkAyvZ07JvRDEf0=",
|
"narHash": "sha256-u0sfA4B65Q9cRO3xpIkQ4nldB8isfdIb3rWtsnRZ+Iw=",
|
||||||
"owner": "oxalica",
|
"owner": "oxalica",
|
||||||
"repo": "rust-overlay",
|
"repo": "rust-overlay",
|
||||||
"rev": "0e624f2b1972a34be1a9b35290ed18ea4b419b6f",
|
"rev": "b8eb3aeb21629cbe14968a5e3b1cbaefb0d1b260",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
60
flake.nix
60
flake.nix
|
@ -9,8 +9,7 @@
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
};
|
};
|
||||||
nixgl = {
|
nixgl = {
|
||||||
# Revert this to community version when https://github.com/nix-community/nixGL/pull/187 is merged
|
url = "github:nix-community/nixGL";
|
||||||
url = "github:phirsch/nixGL/fix-versionMatch";
|
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
inputs.flake-utils.follows = "flake-utils";
|
inputs.flake-utils.follows = "flake-utils";
|
||||||
};
|
};
|
||||||
|
@ -31,58 +30,35 @@
|
||||||
cargo = rust;
|
cargo = rust;
|
||||||
});
|
});
|
||||||
|
|
||||||
buildInputs = with pkgs; [ vulkan-headers vulkan-loader vulkan-validation-layers renderdoc tracy ]
|
libs = with pkgs; [ vulkan-headers vulkan-loader vulkan-validation-layers ]
|
||||||
++ pkgs.lib.optionals pkgs.stdenv.hostPlatform.isLinux (with pkgs; [
|
++ pkgs.lib.optionals pkgs.stdenv.hostPlatform.isLinux (with pkgs; [ libxkbcommon wayland libGL ])
|
||||||
stdenv.cc.cc.lib
|
|
||||||
|
|
||||||
# Wayland
|
|
||||||
libxkbcommon
|
|
||||||
wayland
|
|
||||||
libGL
|
|
||||||
|
|
||||||
# Xorg
|
|
||||||
xorg.libX11
|
|
||||||
xorg.libXcursor
|
|
||||||
xorg.libXi
|
|
||||||
xorg.libxcb
|
|
||||||
xorg.libxshmfence
|
|
||||||
])
|
|
||||||
++ pkgs.lib.optionals pkgs.stdenv.hostPlatform.isDarwin (with pkgs; [ darwin.apple_sdk.frameworks.SystemConfiguration ]);
|
++ pkgs.lib.optionals pkgs.stdenv.hostPlatform.isDarwin (with pkgs; [ darwin.apple_sdk.frameworks.SystemConfiguration ]);
|
||||||
|
|
||||||
nativeBuildInputs = with pkgs; [
|
|
||||||
pkg-config
|
|
||||||
cmake
|
|
||||||
python312
|
|
||||||
];
|
|
||||||
|
|
||||||
mkCustomShell = { packages ? [ ] }: pkgs.mkShell {
|
|
||||||
nativeBuildInputs = [
|
|
||||||
pkgs.renderdoc
|
|
||||||
(rust.override { extensions = [ "rust-src" "rust-analyzer" ]; })
|
|
||||||
] ++ nativeBuildInputs;
|
|
||||||
|
|
||||||
buildInputs = buildInputs
|
|
||||||
++ packages;
|
|
||||||
|
|
||||||
LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath buildInputs;
|
|
||||||
VK_LAYER_PATH = "${pkgs.vulkan-validation-layers}/share/vulkan/explicit_layer.d";
|
|
||||||
RUST_LOG = "debug,rust_vulkan_test=trace";
|
|
||||||
};
|
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
devShells = {
|
devShells = {
|
||||||
default = mkCustomShell { packages = [ pkgs.nixgl.auto.nixVulkanNvidia pkgs.nixgl.nixVulkanMesa ]; };
|
default = pkgs.mkShell {
|
||||||
nixos = mkCustomShell { };
|
nativeBuildInputs = with pkgs; [
|
||||||
|
(rust.override { extensions = ["rust-src" "rust-analyzer"]; })
|
||||||
|
pkg-config
|
||||||
|
];
|
||||||
|
|
||||||
|
buildInputs = libs ++ [
|
||||||
|
pkgs.nixgl.auto.nixVulkanNvidia pkgs.nixgl.nixVulkanIntel
|
||||||
|
];
|
||||||
|
|
||||||
|
LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath (with pkgs; [ libxkbcommon wayland libGL ]);
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
packages = {
|
packages = {
|
||||||
default = rustPlatform.buildRustPackage {
|
default = rustPlatform.buildRustPackage {
|
||||||
pname = "vulkan_test";
|
pname = "rust_ash_test";
|
||||||
version = "0.1.0";
|
version = "0.1.0";
|
||||||
|
|
||||||
src = self;
|
src = self;
|
||||||
|
|
||||||
inherit nativeBuildInputs buildInputs;
|
nativeBuildInputs = with pkgs; [ pkg-config shaderc ];
|
||||||
|
buildInputs = libs;
|
||||||
|
|
||||||
cargoLock = {
|
cargoLock = {
|
||||||
lockFile = ./Cargo.lock;
|
lockFile = ./Cargo.lock;
|
||||||
|
|
8
res/shaders/main.frag
Normal file
8
res/shaders/main.frag
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
#version 450
|
||||||
|
|
||||||
|
layout (location = 0) in vec3 fragColor;
|
||||||
|
layout (location = 0) out vec4 outColor;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
outColor = vec4(fragColor, 1.0);
|
||||||
|
}
|
1
res/shaders/main.vert
Normal file
1
res/shaders/main.vert
Normal file
|
@ -0,0 +1 @@
|
||||||
|
#version 450
out gl_PerVertex {
vec4 gl_Position;
};
layout (location = 0) out vec3 fragColor;
vec2 positions[3] = vec2[](
vec2(0.0, -0.5),
vec2(0.5, 0.5),
vec2(-0.5, 0.5)
);
vec3 colors[3] = vec3[](
vec3(1.0, 0.0, 0.0),
vec3(0.0, 1.0, 0.0),
vec3(0.0, 0.0, 1.0)
);
void main() {
gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0);
fragColor = colors[gl_VertexIndex];
}
|
|
@ -1,12 +1,10 @@
|
||||||
#version 450
|
#version 450
|
||||||
|
#extension GL_ARB_separate_shader_objects: enable
|
||||||
|
|
||||||
layout (location = 0) in vec2 tex_coords;
|
layout (location = 0) in vec3 fragColor;
|
||||||
|
|
||||||
layout (location = 0) out vec4 f_color;
|
layout (location = 0) out vec4 outColor;
|
||||||
|
|
||||||
layout(set = 1, binding = 0) uniform sampler mySampler;
|
|
||||||
layout(set = 1, binding = 1) uniform texture2D myTexture;
|
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
f_color = texture(sampler2D(myTexture, mySampler), tex_coords);
|
outColor = vec4(fragColor, 1.0);
|
||||||
}
|
}
|
|
@ -1,20 +1 @@
|
||||||
#version 450
|
#version 450
#extension GL_ARB_separate_shader_objects: enable
// NOTE: names must match the `Vertex` struct in Rust
layout (location = 0) in vec2 pos;
layout (location = 1) in vec3 color;
layout (location = 0) out vec3 fragColor;
out gl_PerVertex {
vec4 gl_Position;
};
void main() {
gl_Position = vec4(pos, 0.0, 1.0);
fragColor = color;
}
|
||||||
|
|
||||||
layout (location = 0) in vec3 position;
|
|
||||||
layout (location = 1) in vec2 uv;
|
|
||||||
layout (location = 2) in mat4 model;
|
|
||||||
|
|
||||||
layout (location = 0) out vec2 fragUv;
|
|
||||||
|
|
||||||
layout (set = 0, binding = 0) uniform MVP {
|
|
||||||
mat4 world;
|
|
||||||
mat4 view;
|
|
||||||
mat4 projection;
|
|
||||||
} uniforms;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
mat4 worldview = uniforms.view * uniforms.world;
|
|
||||||
vec4 modelPosition = model * vec4(position, 1.0);
|
|
||||||
gl_Position = uniforms.projection * worldview * modelPosition;
|
|
||||||
fragUv = uv;
|
|
||||||
}
|
|
Binary file not shown.
Before Width: | Height: | Size: 49 KiB |
|
@ -1,2 +1,2 @@
|
||||||
[toolchain]
|
[toolchain]
|
||||||
channel = "1.87.0"
|
channel = "1.82.0"
|
||||||
|
|
|
@ -1,186 +0,0 @@
|
||||||
use std::{
|
|
||||||
cell::RefCell,
|
|
||||||
rc::Rc,
|
|
||||||
sync::{Arc, RwLock},
|
|
||||||
};
|
|
||||||
|
|
||||||
use egui_winit_vulkano::Gui;
|
|
||||||
use vulkano::{
|
|
||||||
command_buffer::allocator::StandardCommandBufferAllocator,
|
|
||||||
descriptor_set::allocator::StandardDescriptorSetAllocator,
|
|
||||||
device::{Device, Queue},
|
|
||||||
instance::Instance,
|
|
||||||
memory::allocator::StandardMemoryAllocator,
|
|
||||||
};
|
|
||||||
use vulkano_util::{renderer::VulkanoWindowRenderer, window::VulkanoWindows};
|
|
||||||
use winit::{event_loop::EventLoopProxy, monitor::MonitorHandle, window::WindowId};
|
|
||||||
|
|
||||||
use crate::core::{input::InputManager, render::vulkan_context::VulkanContext, timer::Timer};
|
|
||||||
|
|
||||||
use super::user_event::UserEvent;
|
|
||||||
|
|
||||||
/// Contexte d'application unifié avec Arc<Mutex<>> pour la mutabilité partagée
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct WindowContext {
|
|
||||||
// Données Vulkan (immutables)
|
|
||||||
pub vulkan_context: Arc<VulkanContext>,
|
|
||||||
pub device: Arc<Device>,
|
|
||||||
pub instance: Arc<Instance>,
|
|
||||||
pub graphics_queue: Arc<Queue>,
|
|
||||||
pub compute_queue: Arc<Queue>,
|
|
||||||
pub transfer_queue: Option<Arc<Queue>>,
|
|
||||||
pub memory_allocator: Arc<StandardMemoryAllocator>,
|
|
||||||
pub command_buffer_allocator: Arc<StandardCommandBufferAllocator>,
|
|
||||||
pub descriptor_set_allocator: Arc<StandardDescriptorSetAllocator>,
|
|
||||||
pub event_loop_proxy: EventLoopProxy<UserEvent>,
|
|
||||||
pub window_id: WindowId,
|
|
||||||
|
|
||||||
// Données mutables partagées avec Arc<Mutex<>>
|
|
||||||
pub vulkano_windows: Rc<RefCell<VulkanoWindows>>,
|
|
||||||
pub input_manager: Arc<RwLock<InputManager>>,
|
|
||||||
pub timer: Arc<RwLock<Timer>>,
|
|
||||||
pub gui: Rc<RefCell<Gui>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WindowContext {
|
|
||||||
pub fn new(
|
|
||||||
vulkan_context: Arc<VulkanContext>,
|
|
||||||
vulkano_windows: Rc<RefCell<VulkanoWindows>>,
|
|
||||||
input_manager: Arc<RwLock<InputManager>>,
|
|
||||||
timer: Arc<RwLock<Timer>>,
|
|
||||||
gui: Rc<RefCell<Gui>>,
|
|
||||||
event_loop_proxy: EventLoopProxy<UserEvent>,
|
|
||||||
window_id: WindowId,
|
|
||||||
) -> Self {
|
|
||||||
let vulkano_context_inner = vulkan_context.vulkano_context();
|
|
||||||
|
|
||||||
Self {
|
|
||||||
// Données Vulkan
|
|
||||||
vulkan_context: vulkan_context.clone(),
|
|
||||||
device: vulkano_context_inner.device().clone(),
|
|
||||||
instance: vulkano_context_inner.instance().clone(),
|
|
||||||
graphics_queue: vulkano_context_inner.graphics_queue().clone(),
|
|
||||||
compute_queue: vulkano_context_inner.compute_queue().clone(),
|
|
||||||
transfer_queue: vulkano_context_inner.transfer_queue().cloned(),
|
|
||||||
memory_allocator: vulkano_context_inner.memory_allocator().clone(),
|
|
||||||
command_buffer_allocator: vulkan_context.command_buffer_allocator().clone(),
|
|
||||||
descriptor_set_allocator: vulkan_context.descriptor_set_allocator().clone(),
|
|
||||||
event_loop_proxy,
|
|
||||||
window_id,
|
|
||||||
|
|
||||||
// Données mutables partagées
|
|
||||||
vulkano_windows,
|
|
||||||
input_manager,
|
|
||||||
timer,
|
|
||||||
gui,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Extrait les résolutions d'un moniteur donné
|
|
||||||
fn extract_resolutions_from_monitor(monitor: MonitorHandle) -> Vec<(u32, u32)> {
|
|
||||||
let video_modes: Vec<_> = monitor.video_modes().collect();
|
|
||||||
|
|
||||||
let resolutions: Vec<(u32, u32)> = video_modes
|
|
||||||
.into_iter()
|
|
||||||
.map(|mode| {
|
|
||||||
let size = mode.size();
|
|
||||||
(size.width, size.height)
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
tracing::trace!(
|
|
||||||
"Modes vidéo trouvés pour {:?}: {:?}",
|
|
||||||
monitor.name(),
|
|
||||||
resolutions
|
|
||||||
);
|
|
||||||
resolutions
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Récupère les résolutions disponibles
|
|
||||||
pub fn get_available_resolutions(&self) -> Vec<(u32, u32)> {
|
|
||||||
self.with_renderer(|renderer| {
|
|
||||||
renderer
|
|
||||||
.window()
|
|
||||||
.current_monitor()
|
|
||||||
.map(Self::extract_resolutions_from_monitor)
|
|
||||||
.unwrap_or_default()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Récupère le delta time actuel depuis le timer
|
|
||||||
pub fn get_delta_time(&self) -> f32 {
|
|
||||||
self.with_timer(|timer| timer.delta_time())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Récupère la taille de la fenêtre depuis le renderer
|
|
||||||
pub fn get_window_size(&self) -> [f32; 2] {
|
|
||||||
self.with_renderer(|renderer| renderer.window_size())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Récupère l'aspect ratio depuis le renderer
|
|
||||||
pub fn get_aspect_ratio(&self) -> f32 {
|
|
||||||
self.with_renderer(|renderer| renderer.aspect_ratio())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn with_renderer<T, F>(&self, f: F) -> T
|
|
||||||
where
|
|
||||||
F: FnOnce(&VulkanoWindowRenderer) -> T,
|
|
||||||
{
|
|
||||||
let vulkano_windows = self.vulkano_windows.borrow_mut();
|
|
||||||
let renderer = vulkano_windows
|
|
||||||
.get_renderer(self.window_id)
|
|
||||||
.expect("Failed to get renderer");
|
|
||||||
f(renderer)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Méthode utilitaire pour accéder au renderer de manière thread-safe
|
|
||||||
pub fn with_renderer_mut<T, F>(&mut self, f: F) -> T
|
|
||||||
where
|
|
||||||
F: FnOnce(&mut VulkanoWindowRenderer) -> T,
|
|
||||||
{
|
|
||||||
let mut vulkano_windows = self.vulkano_windows.borrow_mut();
|
|
||||||
let renderer = vulkano_windows
|
|
||||||
.get_renderer_mut(self.window_id)
|
|
||||||
.expect("Failed to get renderer");
|
|
||||||
f(renderer)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Méthode utilitaire pour accéder au gui de manière thread-safe
|
|
||||||
pub fn with_gui<T, F>(&self, f: F) -> T
|
|
||||||
where
|
|
||||||
F: FnOnce(&Gui) -> T,
|
|
||||||
{
|
|
||||||
let gui = self.gui.borrow();
|
|
||||||
f(&gui)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Méthode utilitaire pour accéder au gui de manière thread-safe
|
|
||||||
pub fn with_gui_mut<T, F>(&mut self, f: F) -> T
|
|
||||||
where
|
|
||||||
F: FnOnce(&mut Gui) -> T,
|
|
||||||
{
|
|
||||||
let mut gui = self.gui.borrow_mut();
|
|
||||||
f(&mut gui)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Méthode utilitaire pour accéder à l'input manager de manière thread-safe
|
|
||||||
pub fn with_input_manager<T, F>(&self, f: F) -> T
|
|
||||||
where
|
|
||||||
F: FnOnce(&InputManager) -> T,
|
|
||||||
{
|
|
||||||
let input_manager = self
|
|
||||||
.input_manager
|
|
||||||
.read()
|
|
||||||
.expect("Failed to lock input_manager");
|
|
||||||
f(&input_manager)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Méthode utilitaire pour accéder au timer de manière thread-safe
|
|
||||||
pub fn with_timer<T, F>(&self, f: F) -> T
|
|
||||||
where
|
|
||||||
F: FnOnce(&Timer) -> T,
|
|
||||||
{
|
|
||||||
let timer = self.timer.read().expect("Failed to lock timer");
|
|
||||||
f(&timer)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,260 +0,0 @@
|
||||||
use std::cell::RefCell;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::rc::Rc;
|
|
||||||
use std::sync::{Arc, RwLock};
|
|
||||||
|
|
||||||
use super::render::vulkan_context::VulkanContext;
|
|
||||||
use crate::core::input::InputManager;
|
|
||||||
use crate::core::scene::manager::SceneManager;
|
|
||||||
use crate::core::timer::Timer;
|
|
||||||
use crate::game::scenes::main_scene::MainScene;
|
|
||||||
use egui_winit_vulkano::{Gui, GuiConfig};
|
|
||||||
use user_event::UserEvent;
|
|
||||||
use vulkano::format::Format;
|
|
||||||
use vulkano::image::ImageUsage;
|
|
||||||
use vulkano::swapchain::PresentMode;
|
|
||||||
use vulkano_util::context::VulkanoContext;
|
|
||||||
use vulkano_util::window::{VulkanoWindows, WindowDescriptor};
|
|
||||||
use winit::application::ApplicationHandler;
|
|
||||||
use winit::event::WindowEvent;
|
|
||||||
use winit::event_loop::{ActiveEventLoop, EventLoopProxy};
|
|
||||||
use winit::window::WindowId;
|
|
||||||
|
|
||||||
use self::context::WindowContext;
|
|
||||||
|
|
||||||
pub mod context;
|
|
||||||
pub mod user_event;
|
|
||||||
|
|
||||||
pub const DEPTH_IMAGE_ID: usize = 0;
|
|
||||||
|
|
||||||
pub struct App {
|
|
||||||
vulkan_context: Arc<VulkanContext>,
|
|
||||||
vulkano_windows: Rc<RefCell<VulkanoWindows>>,
|
|
||||||
gui: HashMap<WindowId, Rc<RefCell<Gui>>>,
|
|
||||||
scene_manager: HashMap<WindowId, SceneManager>,
|
|
||||||
input_manager: Arc<RwLock<InputManager>>,
|
|
||||||
timer: Arc<RwLock<Timer>>,
|
|
||||||
event_loop_proxy: EventLoopProxy<UserEvent>,
|
|
||||||
|
|
||||||
// Context d'application partagé par fenêtre - architecture unifiée
|
|
||||||
app_contexts: HashMap<WindowId, Rc<RefCell<WindowContext>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl App {
|
|
||||||
pub fn new(
|
|
||||||
vulkano_context: VulkanoContext,
|
|
||||||
input_manager: InputManager,
|
|
||||||
event_loop_proxy: EventLoopProxy<UserEvent>,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
|
||||||
vulkan_context: Arc::new(VulkanContext::new(vulkano_context)),
|
|
||||||
vulkano_windows: Rc::new(RefCell::new(VulkanoWindows::default())),
|
|
||||||
gui: HashMap::new(),
|
|
||||||
input_manager: Arc::new(RwLock::new(input_manager)),
|
|
||||||
scene_manager: HashMap::new(),
|
|
||||||
timer: Arc::new(RwLock::new(Timer::new())),
|
|
||||||
event_loop_proxy,
|
|
||||||
app_contexts: HashMap::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ApplicationHandler<UserEvent> for App {
|
|
||||||
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
|
|
||||||
let mut vulkano_windows = self.vulkano_windows.borrow_mut();
|
|
||||||
let window_id = vulkano_windows.create_window(
|
|
||||||
event_loop,
|
|
||||||
self.vulkan_context.vulkano_context(),
|
|
||||||
&WindowDescriptor {
|
|
||||||
title: "Rust ASH Test".to_string(),
|
|
||||||
width: 800.0,
|
|
||||||
height: 600.0,
|
|
||||||
present_mode: PresentMode::Fifo,
|
|
||||||
cursor_visible: false,
|
|
||||||
cursor_locked: true,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
|_| {},
|
|
||||||
);
|
|
||||||
|
|
||||||
let renderer = vulkano_windows.get_renderer_mut(window_id).unwrap();
|
|
||||||
renderer.add_additional_image_view(
|
|
||||||
DEPTH_IMAGE_ID,
|
|
||||||
Format::D16_UNORM,
|
|
||||||
ImageUsage::DEPTH_STENCIL_ATTACHMENT,
|
|
||||||
);
|
|
||||||
|
|
||||||
let gui = {
|
|
||||||
Gui::new(
|
|
||||||
event_loop,
|
|
||||||
renderer.surface(),
|
|
||||||
renderer.graphics_queue(),
|
|
||||||
renderer.swapchain_format(),
|
|
||||||
GuiConfig {
|
|
||||||
is_overlay: true,
|
|
||||||
allow_srgb_render_target: true,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
)
|
|
||||||
};
|
|
||||||
self.gui.insert(window_id, Rc::new(RefCell::new(gui)));
|
|
||||||
|
|
||||||
let mut scene_manager = SceneManager::new();
|
|
||||||
scene_manager.load_scene(Box::new(MainScene::default()));
|
|
||||||
|
|
||||||
self.scene_manager.insert(window_id, scene_manager);
|
|
||||||
|
|
||||||
let app_context = Rc::new(RefCell::new(WindowContext::new(
|
|
||||||
self.vulkan_context.clone(),
|
|
||||||
self.vulkano_windows.clone(),
|
|
||||||
self.input_manager.clone(),
|
|
||||||
self.timer.clone(),
|
|
||||||
self.gui.get(&window_id).unwrap().clone(),
|
|
||||||
self.event_loop_proxy.clone(),
|
|
||||||
window_id,
|
|
||||||
)));
|
|
||||||
self.app_contexts.insert(window_id, app_context);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn device_event(
|
|
||||||
&mut self,
|
|
||||||
_event_loop: &ActiveEventLoop,
|
|
||||||
_device_id: winit::event::DeviceId,
|
|
||||||
event: winit::event::DeviceEvent,
|
|
||||||
) {
|
|
||||||
let mut input_manager = self.input_manager.write().unwrap();
|
|
||||||
input_manager.process_device_event(&event);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn window_event(&mut self, event_loop: &ActiveEventLoop, id: WindowId, event: WindowEvent) {
|
|
||||||
{
|
|
||||||
let gui = self.gui.get_mut(&id).unwrap();
|
|
||||||
let mut gui = gui.borrow_mut();
|
|
||||||
if !gui.update(&event) {
|
|
||||||
let mut input_manager = self.input_manager.write().unwrap();
|
|
||||||
input_manager.process_window_event(&event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
match event {
|
|
||||||
WindowEvent::CloseRequested => {
|
|
||||||
tracing::debug!("The close button was pressed; stopping");
|
|
||||||
event_loop.exit();
|
|
||||||
}
|
|
||||||
WindowEvent::Resized(_) | WindowEvent::ScaleFactorChanged { .. } => {
|
|
||||||
let mut vulkano_windows = self.vulkano_windows.borrow_mut();
|
|
||||||
vulkano_windows.get_renderer_mut(id).unwrap().resize();
|
|
||||||
}
|
|
||||||
WindowEvent::RedrawRequested => {
|
|
||||||
let _frame_span = tracing::info_span!("frame").entered();
|
|
||||||
|
|
||||||
{
|
|
||||||
let _input_span = tracing::debug_span!("input_update").entered();
|
|
||||||
let mut input_manager = self
|
|
||||||
.input_manager
|
|
||||||
.write()
|
|
||||||
.expect("Failed to lock input manager");
|
|
||||||
input_manager.update();
|
|
||||||
}
|
|
||||||
{
|
|
||||||
let _timer_span = tracing::debug_span!("timer_update").entered();
|
|
||||||
let mut timer = self.timer.write().expect("Failed to lock timer");
|
|
||||||
timer.update();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Créer ou mettre à jour le contexte d'application
|
|
||||||
let window_context = self.app_contexts.get(&id).unwrap().clone();
|
|
||||||
let scene_manager = self.scene_manager.get_mut(&id).unwrap();
|
|
||||||
|
|
||||||
// Utiliser le contexte partagé pour les scènes
|
|
||||||
{
|
|
||||||
let mut context = window_context.borrow_mut();
|
|
||||||
|
|
||||||
{
|
|
||||||
let _scene_span =
|
|
||||||
tracing::info_span!("scene_loading_if_not_loaded").entered();
|
|
||||||
scene_manager
|
|
||||||
.load_scene_if_not_loaded(&mut context)
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(scene) = scene_manager.current_scene_mut() {
|
|
||||||
{
|
|
||||||
let _update_span = tracing::debug_span!("scene_update").entered();
|
|
||||||
scene.update(&mut context).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
let acquire_future = {
|
|
||||||
let _acquire_span = tracing::debug_span!("acquire_swapchain").entered();
|
|
||||||
context.with_renderer_mut(|renderer| {
|
|
||||||
renderer.acquire(None, |_| {}).unwrap()
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
let acquire_future = {
|
|
||||||
let _render_span = tracing::debug_span!("scene_render").entered();
|
|
||||||
scene.render(acquire_future, &mut context).unwrap()
|
|
||||||
};
|
|
||||||
|
|
||||||
{
|
|
||||||
let _present_span = tracing::debug_span!("present_frame").entered();
|
|
||||||
context.with_renderer_mut(|renderer| {
|
|
||||||
renderer.present(acquire_future, true);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
tracing::warn!("No current scene found for update!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
let _gui_span = tracing::debug_span!("request_redraw").entered();
|
|
||||||
let mut window_context = window_context.borrow_mut();
|
|
||||||
window_context.with_renderer_mut(|renderer| {
|
|
||||||
renderer.window().request_redraw();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn user_event(&mut self, event_loop: &ActiveEventLoop, event: UserEvent) {
|
|
||||||
match event {
|
|
||||||
UserEvent::CursorGrabMode(window_id, grab) => {
|
|
||||||
let vulkano_windows = self.vulkano_windows.borrow();
|
|
||||||
let window = vulkano_windows.get_window(window_id).unwrap();
|
|
||||||
if let Err(e) = window.set_cursor_grab(grab) {
|
|
||||||
tracing::error!("Failed to set cursor grab: {}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
UserEvent::CursorVisible(window_id, visible) => {
|
|
||||||
let vulkano_windows = self.vulkano_windows.borrow();
|
|
||||||
let window = vulkano_windows.get_window(window_id).unwrap();
|
|
||||||
window.set_cursor_visible(visible);
|
|
||||||
}
|
|
||||||
UserEvent::ChangeScene(window_id, scene) => {
|
|
||||||
if let Some(scene_manager) = self.scene_manager.get_mut(&window_id) {
|
|
||||||
scene_manager.load_scene(scene);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
UserEvent::ChangeResolution(window_id, width, height) => {
|
|
||||||
let mut vulkano_windows = self.vulkano_windows.borrow_mut();
|
|
||||||
let window = vulkano_windows.get_window(window_id).unwrap();
|
|
||||||
let _ = window.request_inner_size(winit::dpi::LogicalSize::new(width, height));
|
|
||||||
let renderer = vulkano_windows.get_renderer_mut(window_id).unwrap();
|
|
||||||
renderer.resize();
|
|
||||||
tracing::trace!(
|
|
||||||
"Resolution changed to {}x{} for window {:?}",
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
window_id
|
|
||||||
);
|
|
||||||
}
|
|
||||||
UserEvent::Exit(window_id) => {
|
|
||||||
tracing::trace!("Exit requested for window {:?}", window_id);
|
|
||||||
event_loop.exit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
use winit::window::{CursorGrabMode, WindowId};
|
|
||||||
|
|
||||||
use crate::core::scene::Scene;
|
|
||||||
|
|
||||||
pub enum UserEvent {
|
|
||||||
CursorGrabMode(WindowId, CursorGrabMode),
|
|
||||||
CursorVisible(WindowId, bool),
|
|
||||||
ChangeScene(WindowId, Box<dyn Scene>),
|
|
||||||
ChangeResolution(WindowId, f32, f32),
|
|
||||||
Exit(WindowId),
|
|
||||||
}
|
|
|
@ -1,86 +0,0 @@
|
||||||
use std::{
|
|
||||||
collections::HashMap,
|
|
||||||
hash::Hash,
|
|
||||||
ops::{Add, AddAssign, Sub},
|
|
||||||
};
|
|
||||||
|
|
||||||
use winit::event::ElementState;
|
|
||||||
|
|
||||||
pub struct CachedElementState<K: Eq + Hash> {
|
|
||||||
cache: HashMap<K, ElementState>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<K: Eq + Hash> Default for CachedElementState<K> {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
cache: HashMap::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<K: Eq + Hash> CachedElementState<K> {
|
|
||||||
pub fn set_key_state(&mut self, key: K, state: ElementState) -> Option<ElementState> {
|
|
||||||
let key_state = self.cache.get(&key);
|
|
||||||
let new_key_state = match key_state {
|
|
||||||
Some(old) => match state {
|
|
||||||
ElementState::Pressed => match old {
|
|
||||||
ElementState::Released => Some(ElementState::Pressed),
|
|
||||||
ElementState::Pressed => None,
|
|
||||||
},
|
|
||||||
ElementState::Released => match old {
|
|
||||||
ElementState::Released => None,
|
|
||||||
ElementState::Pressed => Some(ElementState::Released),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
None => match state {
|
|
||||||
ElementState::Pressed => Some(ElementState::Pressed),
|
|
||||||
ElementState::Released => Some(ElementState::Released),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
if let Some(new_key_state) = new_key_state {
|
|
||||||
self.cache.insert(key, new_key_state);
|
|
||||||
}
|
|
||||||
new_key_state
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct CachedMovement<T>
|
|
||||||
where
|
|
||||||
T: Sub<Output = T> + Add<Output = T> + Default + Copy,
|
|
||||||
{
|
|
||||||
pub old_value: Option<T>,
|
|
||||||
pub value: T,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> CachedMovement<T>
|
|
||||||
where
|
|
||||||
T: Sub<Output = T> + Add<Output = T> + Default + Copy,
|
|
||||||
{
|
|
||||||
pub fn set_value(&mut self, value: T) {
|
|
||||||
self.value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn reset(&mut self) -> T {
|
|
||||||
match self.old_value.as_ref() {
|
|
||||||
Some(old_value) => {
|
|
||||||
let diff = self.value - *old_value;
|
|
||||||
self.old_value = Some(self.value);
|
|
||||||
diff
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
self.old_value = Some(self.value);
|
|
||||||
T::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> AddAssign<T> for CachedMovement<T>
|
|
||||||
where
|
|
||||||
T: Add<Output = T> + Sub<Output = T> + Default + Copy,
|
|
||||||
{
|
|
||||||
fn add_assign(&mut self, rhs: T) {
|
|
||||||
self.value = self.value + rhs;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,97 +0,0 @@
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use cache::{CachedElementState, CachedMovement};
|
|
||||||
use virtual_input::VirtualInput;
|
|
||||||
use winit::{
|
|
||||||
event::{DeviceEvent, MouseButton, MouseScrollDelta, WindowEvent},
|
|
||||||
keyboard::PhysicalKey,
|
|
||||||
};
|
|
||||||
|
|
||||||
mod cache;
|
|
||||||
mod virtual_binding;
|
|
||||||
mod virtual_input;
|
|
||||||
mod virtual_state;
|
|
||||||
pub use virtual_binding::{AxisDirection, VirtualBinding};
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct InputManager {
|
|
||||||
keys_state: CachedElementState<PhysicalKey>,
|
|
||||||
mouse_buttons_state: CachedElementState<MouseButton>,
|
|
||||||
mouse_position_delta: CachedMovement<glam::Vec2>,
|
|
||||||
mouse_wheel_delta: CachedMovement<glam::Vec2>,
|
|
||||||
virtual_input: VirtualInput,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Debug for InputManager {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
f.debug_struct("InputManager")
|
|
||||||
.field("virtual_input", &self.virtual_input)
|
|
||||||
.finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InputManager {
|
|
||||||
pub fn new(input_mapping: HashMap<String, Vec<VirtualBinding>>) -> Self {
|
|
||||||
let mut input_manager = InputManager::default();
|
|
||||||
for (value_name, bindings) in input_mapping {
|
|
||||||
input_manager.add_virtual_bindings(value_name, bindings);
|
|
||||||
}
|
|
||||||
input_manager
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn process_device_event(&mut self, event: &DeviceEvent) {
|
|
||||||
if let DeviceEvent::MouseMotion { delta, .. } = event {
|
|
||||||
self.mouse_position_delta += glam::Vec2::new(delta.0 as f32, delta.1 as f32);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn process_window_event(&mut self, event: &WindowEvent) {
|
|
||||||
match event {
|
|
||||||
WindowEvent::AxisMotion { axis, value, .. } => {
|
|
||||||
self.virtual_input.update_axis_binding(*axis, *value as f32);
|
|
||||||
}
|
|
||||||
WindowEvent::KeyboardInput { event, .. } => {
|
|
||||||
let new_key_state = self
|
|
||||||
.keys_state
|
|
||||||
.set_key_state(event.physical_key, event.state);
|
|
||||||
if let Some(new_key_state) = new_key_state {
|
|
||||||
self.virtual_input
|
|
||||||
.update_key_binding(event.physical_key, new_key_state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
WindowEvent::MouseInput { button, state, .. } => {
|
|
||||||
let new_mouse_button_state =
|
|
||||||
self.mouse_buttons_state.set_key_state(*button, *state);
|
|
||||||
if let Some(new_mouse_button_state) = new_mouse_button_state {
|
|
||||||
self.virtual_input
|
|
||||||
.update_mouse_button_binding(*button, new_mouse_button_state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
WindowEvent::MouseWheel { delta, .. } => {
|
|
||||||
self.mouse_wheel_delta += match delta {
|
|
||||||
MouseScrollDelta::PixelDelta(position) => {
|
|
||||||
glam::Vec2::new(position.x as f32, position.y as f32)
|
|
||||||
}
|
|
||||||
MouseScrollDelta::LineDelta(x, y) => glam::Vec2::new(*x, *y),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Updates deltas before running update
|
|
||||||
pub fn update(&mut self) {
|
|
||||||
self.virtual_input
|
|
||||||
.update_mouse_move_binding(&self.mouse_position_delta.reset());
|
|
||||||
self.virtual_input
|
|
||||||
.update_mouse_wheel_binding(&self.mouse_wheel_delta.reset());
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_virtual_input_state(&self, value_name: &str) -> f32 {
|
|
||||||
self.virtual_input.get_state(value_name)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add_virtual_bindings(&mut self, value_name: String, bindings: Vec<VirtualBinding>) {
|
|
||||||
self.virtual_input.add_bindings(value_name, bindings);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,30 +0,0 @@
|
||||||
use winit::{
|
|
||||||
event::{AxisId, MouseButton},
|
|
||||||
keyboard::PhysicalKey,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub enum AxisDirection {
|
|
||||||
Normal,
|
|
||||||
Invert,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&AxisDirection> for f32 {
|
|
||||||
fn from(direction: &AxisDirection) -> Self {
|
|
||||||
match direction {
|
|
||||||
AxisDirection::Normal => 1.0,
|
|
||||||
AxisDirection::Invert => -1.0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub enum VirtualBinding {
|
|
||||||
Keyboard(PhysicalKey, AxisDirection),
|
|
||||||
Axis(AxisId, AxisDirection, f32), // f32 deadzone
|
|
||||||
MouseX(AxisDirection),
|
|
||||||
MouseY(AxisDirection),
|
|
||||||
MouseWheelX(AxisDirection),
|
|
||||||
MouseWheelY(AxisDirection),
|
|
||||||
MouseButton(MouseButton, AxisDirection),
|
|
||||||
}
|
|
|
@ -1,150 +0,0 @@
|
||||||
use std::{
|
|
||||||
collections::HashMap,
|
|
||||||
sync::{Arc, RwLock},
|
|
||||||
};
|
|
||||||
|
|
||||||
use winit::{
|
|
||||||
event::{AxisId, ElementState, MouseButton},
|
|
||||||
keyboard::PhysicalKey,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::{
|
|
||||||
virtual_binding::VirtualBinding,
|
|
||||||
virtual_state::{VirtualBindingState, VirtualInputState},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct VirtualInput {
|
|
||||||
// Global states
|
|
||||||
states: HashMap<String, Arc<RwLock<VirtualInputState>>>,
|
|
||||||
|
|
||||||
// Per kind of input states to keep complexity low during state updates
|
|
||||||
states_by_key: HashMap<PhysicalKey, Vec<Arc<RwLock<VirtualInputState>>>>,
|
|
||||||
mouse_move_states: Vec<Arc<RwLock<VirtualInputState>>>,
|
|
||||||
mouse_wheel_states: Vec<Arc<RwLock<VirtualInputState>>>,
|
|
||||||
mouse_button_states: HashMap<MouseButton, Vec<Arc<RwLock<VirtualInputState>>>>,
|
|
||||||
axis_states: HashMap<AxisId, Vec<Arc<RwLock<VirtualInputState>>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Debug for VirtualInput {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
let mut debug = f.debug_struct("VirtualInput");
|
|
||||||
|
|
||||||
for (name, state) in &self.states {
|
|
||||||
let value = state.read().expect("Poisoned lock for debug").value;
|
|
||||||
debug.field(name, &value);
|
|
||||||
}
|
|
||||||
|
|
||||||
debug.finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VirtualInput {
|
|
||||||
pub fn get_state(&self, value_name: &str) -> f32 {
|
|
||||||
self.states
|
|
||||||
.get(value_name)
|
|
||||||
.map(|state| state.read().expect("Poisoned lock for get state").value)
|
|
||||||
.unwrap_or(0.0)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_bindings(&mut self, value_name: String, new_bindings: Vec<VirtualBinding>) {
|
|
||||||
let state = self
|
|
||||||
.states
|
|
||||||
.entry(value_name)
|
|
||||||
.or_insert(Arc::new(RwLock::new(VirtualInputState {
|
|
||||||
value: 0.0,
|
|
||||||
bindings: Vec::new(),
|
|
||||||
})));
|
|
||||||
|
|
||||||
for binding in &new_bindings {
|
|
||||||
match binding {
|
|
||||||
VirtualBinding::Keyboard(key, _) => {
|
|
||||||
self.states_by_key
|
|
||||||
.entry(*key)
|
|
||||||
.or_default()
|
|
||||||
.push(state.clone());
|
|
||||||
}
|
|
||||||
VirtualBinding::MouseX(_) | VirtualBinding::MouseY(_) => {
|
|
||||||
self.mouse_move_states.push(state.clone());
|
|
||||||
}
|
|
||||||
VirtualBinding::MouseButton(button, _) => {
|
|
||||||
self.mouse_button_states
|
|
||||||
.entry(*button)
|
|
||||||
.or_default()
|
|
||||||
.push(state.clone());
|
|
||||||
}
|
|
||||||
VirtualBinding::MouseWheelX(_) | VirtualBinding::MouseWheelY(_) => {
|
|
||||||
self.mouse_wheel_states.push(state.clone());
|
|
||||||
}
|
|
||||||
VirtualBinding::Axis(axis, _, _) => {
|
|
||||||
self.axis_states
|
|
||||||
.entry(*axis)
|
|
||||||
.or_default()
|
|
||||||
.push(state.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
state
|
|
||||||
.write()
|
|
||||||
.expect("Poisoned lock for add bindings")
|
|
||||||
.bindings
|
|
||||||
.extend(new_bindings.iter().map(|b| VirtualBindingState {
|
|
||||||
value: 0.0,
|
|
||||||
binding: b.clone(),
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn update_key_binding(&mut self, key: PhysicalKey, key_state: ElementState) {
|
|
||||||
let states = self.states_by_key.get_mut(&key);
|
|
||||||
|
|
||||||
if let Some(states) = states {
|
|
||||||
for state in states {
|
|
||||||
let mut state = state.write().expect("Poisoned lock for key update");
|
|
||||||
state.update_from_key(key, key_state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn update_mouse_move_binding(&mut self, delta: &glam::Vec2) {
|
|
||||||
for state in &mut self.mouse_move_states {
|
|
||||||
let mut state = state.write().expect("Poisoned lock for mouse move update");
|
|
||||||
state.update_from_mouse(delta);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn update_mouse_wheel_binding(&mut self, delta: &glam::Vec2) {
|
|
||||||
for state in &mut self.mouse_wheel_states {
|
|
||||||
let mut state = state.write().expect("Poisoned lock for mouse wheel update");
|
|
||||||
state.update_from_mouse_wheel(delta);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn update_mouse_button_binding(
|
|
||||||
&mut self,
|
|
||||||
button: MouseButton,
|
|
||||||
button_state: ElementState,
|
|
||||||
) {
|
|
||||||
let states = self.mouse_button_states.get_mut(&button);
|
|
||||||
|
|
||||||
if let Some(states) = states {
|
|
||||||
for state in states {
|
|
||||||
let mut state = state
|
|
||||||
.write()
|
|
||||||
.expect("Poisoned lock for mouse button update");
|
|
||||||
state.update_from_mouse_button(button, button_state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn update_axis_binding(&mut self, axis: AxisId, axis_state: f32) {
|
|
||||||
let states = self.axis_states.get_mut(&axis);
|
|
||||||
|
|
||||||
if let Some(states) = states {
|
|
||||||
for state in states {
|
|
||||||
let mut state = state.write().expect("Poisoned lock for axis update");
|
|
||||||
state.update_from_axis(axis, axis_state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,105 +0,0 @@
|
||||||
use winit::{
|
|
||||||
event::{AxisId, ElementState, MouseButton},
|
|
||||||
keyboard::PhysicalKey,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::virtual_binding::VirtualBinding;
|
|
||||||
|
|
||||||
pub struct VirtualBindingState {
|
|
||||||
pub value: f32,
|
|
||||||
pub binding: VirtualBinding,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct VirtualInputState {
|
|
||||||
pub value: f32,
|
|
||||||
pub bindings: Vec<VirtualBindingState>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VirtualInputState {
|
|
||||||
pub fn update_from_key(&mut self, key: PhysicalKey, key_state: ElementState) {
|
|
||||||
let mut new_value = 0.0;
|
|
||||||
for binding in &mut self.bindings {
|
|
||||||
if let VirtualBinding::Keyboard(binding_key, direction) = &binding.binding {
|
|
||||||
if binding_key == &key {
|
|
||||||
if key_state == ElementState::Pressed {
|
|
||||||
binding.value += f32::from(direction);
|
|
||||||
} else {
|
|
||||||
binding.value = 0.0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
new_value += binding.value;
|
|
||||||
}
|
|
||||||
self.value = new_value;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update_from_mouse(&mut self, delta: &glam::Vec2) {
|
|
||||||
let mut new_value = 0.0;
|
|
||||||
for binding in &mut self.bindings {
|
|
||||||
match &binding.binding {
|
|
||||||
VirtualBinding::MouseX(direction) => {
|
|
||||||
binding.value = f32::from(direction) * delta.x;
|
|
||||||
}
|
|
||||||
VirtualBinding::MouseY(direction) => {
|
|
||||||
binding.value = f32::from(direction) * delta.y;
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
new_value += binding.value;
|
|
||||||
}
|
|
||||||
self.value = new_value;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update_from_mouse_wheel(&mut self, delta: &glam::Vec2) {
|
|
||||||
let mut new_value = 0.0;
|
|
||||||
for binding in &mut self.bindings {
|
|
||||||
match &binding.binding {
|
|
||||||
VirtualBinding::MouseWheelX(direction) => {
|
|
||||||
binding.value = f32::from(direction) * delta.x;
|
|
||||||
}
|
|
||||||
VirtualBinding::MouseWheelY(direction) => {
|
|
||||||
binding.value = f32::from(direction) * delta.y;
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
new_value += binding.value;
|
|
||||||
}
|
|
||||||
self.value = new_value;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update_from_mouse_button(&mut self, button: MouseButton, button_state: ElementState) {
|
|
||||||
let mut new_value = 0.0;
|
|
||||||
for binding in &mut self.bindings {
|
|
||||||
if let VirtualBinding::MouseButton(binding_button, direction) = &binding.binding {
|
|
||||||
if binding_button == &button {
|
|
||||||
if button_state == ElementState::Pressed {
|
|
||||||
binding.value = f32::from(direction);
|
|
||||||
} else {
|
|
||||||
binding.value = 0.0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
new_value += binding.value;
|
|
||||||
}
|
|
||||||
self.value = new_value;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update_from_axis(&mut self, axis: AxisId, axis_state: f32) {
|
|
||||||
let mut new_value = 0.0;
|
|
||||||
for binding in &mut self.bindings {
|
|
||||||
if let VirtualBinding::Axis(binding_axis, direction, deadzone) = &binding.binding {
|
|
||||||
if binding_axis == &axis {
|
|
||||||
binding.value =
|
|
||||||
f32::from(direction) * process_axis_deadzone(axis_state, *deadzone);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
new_value += binding.value;
|
|
||||||
}
|
|
||||||
self.value = new_value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn process_axis_deadzone(value: f32, deadzone: f32) -> f32 {
|
|
||||||
if value.abs() < deadzone { 0.0 } else { value }
|
|
||||||
}
|
|
|
@ -1,5 +0,0 @@
|
||||||
pub mod app;
|
|
||||||
pub mod input;
|
|
||||||
pub mod render;
|
|
||||||
pub mod scene;
|
|
||||||
pub mod timer;
|
|
|
@ -1,246 +0,0 @@
|
||||||
use std::{
|
|
||||||
any::TypeId,
|
|
||||||
error::Error,
|
|
||||||
sync::{Arc, RwLock, RwLockReadGuard},
|
|
||||||
};
|
|
||||||
|
|
||||||
use vulkano::{device::Device, format::Format, memory::allocator::StandardMemoryAllocator};
|
|
||||||
|
|
||||||
pub trait Pipeline {
|
|
||||||
fn load(
|
|
||||||
&mut self,
|
|
||||||
device: &Device,
|
|
||||||
memory_allocator: &StandardMemoryAllocator,
|
|
||||||
swapchain_format: Format,
|
|
||||||
depth_format: Format,
|
|
||||||
) -> Result<(), Box<dyn Error>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait Material {
|
|
||||||
fn pipeline_type_id() -> TypeId
|
|
||||||
where
|
|
||||||
Self: Sized;
|
|
||||||
|
|
||||||
fn load(
|
|
||||||
&mut self,
|
|
||||||
device: &Device,
|
|
||||||
memory_allocator: &StandardMemoryAllocator,
|
|
||||||
) -> Result<(), Box<dyn Error>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum MaterialState {
|
|
||||||
Loading,
|
|
||||||
Loaded,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum MaterialError {
|
|
||||||
PipelineNotFound,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct MaterialManager {
|
|
||||||
device: Arc<Device>,
|
|
||||||
memory_allocator: Arc<StandardMemoryAllocator>,
|
|
||||||
swapchain_format: Format,
|
|
||||||
depth_format: Format,
|
|
||||||
|
|
||||||
pipelines_id: Arc<RwLock<Vec<TypeId>>>,
|
|
||||||
pipelines_state: Arc<RwLock<Vec<Arc<RwLock<MaterialState>>>>>,
|
|
||||||
pipelines: Arc<RwLock<Vec<Arc<RwLock<dyn Pipeline>>>>>,
|
|
||||||
|
|
||||||
materials_id: Arc<RwLock<Vec<TypeId>>>,
|
|
||||||
materials_pipeline_id: Arc<RwLock<Vec<TypeId>>>,
|
|
||||||
materials_pipeline: Arc<RwLock<Vec<Arc<RwLock<dyn Pipeline>>>>>,
|
|
||||||
materials_pipeline_state: Arc<RwLock<Vec<Arc<RwLock<MaterialState>>>>>,
|
|
||||||
materials_state: Arc<RwLock<Vec<Arc<RwLock<MaterialState>>>>>,
|
|
||||||
materials: Arc<RwLock<Vec<Arc<RwLock<dyn Material>>>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MaterialManager {
|
|
||||||
pub fn new(
|
|
||||||
device: Arc<Device>,
|
|
||||||
memory_allocator: Arc<StandardMemoryAllocator>,
|
|
||||||
swapchain_format: Format,
|
|
||||||
depth_format: Format,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
|
||||||
device,
|
|
||||||
memory_allocator,
|
|
||||||
swapchain_format,
|
|
||||||
depth_format,
|
|
||||||
pipelines_id: Arc::new(RwLock::new(Vec::new())),
|
|
||||||
pipelines_state: Arc::new(RwLock::new(Vec::new())),
|
|
||||||
pipelines: Arc::new(RwLock::new(Vec::new())),
|
|
||||||
materials_id: Arc::new(RwLock::new(Vec::new())),
|
|
||||||
materials_pipeline_id: Arc::new(RwLock::new(Vec::new())),
|
|
||||||
materials_pipeline: Arc::new(RwLock::new(Vec::new())),
|
|
||||||
materials_pipeline_state: Arc::new(RwLock::new(Vec::new())),
|
|
||||||
materials_state: Arc::new(RwLock::new(Vec::new())),
|
|
||||||
materials: Arc::new(RwLock::new(Vec::new())),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_pipeline<P: Pipeline + Default + 'static>(&self) {
|
|
||||||
let type_id = TypeId::of::<P>();
|
|
||||||
let pipeline = Arc::new(RwLock::new(P::default()));
|
|
||||||
|
|
||||||
let mut pipelines_id = self.pipelines_id.write().unwrap();
|
|
||||||
let mut pipelines_state = self.pipelines_state.write().unwrap();
|
|
||||||
let mut pipelines = self.pipelines.write().unwrap();
|
|
||||||
|
|
||||||
pipelines_id.push(type_id);
|
|
||||||
pipelines_state.push(Arc::new(RwLock::new(MaterialState::Loading)));
|
|
||||||
pipelines.push(pipeline.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_material<M: Material + Default + 'static>(&self) -> Result<(), MaterialError> {
|
|
||||||
let pipeline_id = M::pipeline_type_id();
|
|
||||||
|
|
||||||
let pipeline_result = {
|
|
||||||
let pipelines_id = self.pipelines_id.read().unwrap();
|
|
||||||
let pipelines_state = self.pipelines_state.read().unwrap();
|
|
||||||
let pipelines = self.pipelines.read().unwrap();
|
|
||||||
|
|
||||||
pipelines_id
|
|
||||||
.iter()
|
|
||||||
.zip(pipelines.iter())
|
|
||||||
.zip(pipelines_state.iter())
|
|
||||||
.find(|((id, _), _)| *id == &pipeline_id)
|
|
||||||
.map(|((_, pipeline), state)| (pipeline.clone(), state.clone()))
|
|
||||||
};
|
|
||||||
|
|
||||||
let (pipeline, pipeline_state) = match pipeline_result {
|
|
||||||
Some(pipeline) => pipeline,
|
|
||||||
None => {
|
|
||||||
tracing::error!(
|
|
||||||
"Pipeline with id {pipeline_id:?} not found, please add it before adding a material"
|
|
||||||
);
|
|
||||||
return Err(MaterialError::PipelineNotFound);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let type_id = TypeId::of::<M>();
|
|
||||||
|
|
||||||
let mut materials_id = self.materials_id.write().unwrap();
|
|
||||||
let mut materials_pipeline_id = self.materials_pipeline_id.write().unwrap();
|
|
||||||
let mut materials_pipeline = self.materials_pipeline.write().unwrap();
|
|
||||||
let mut materials_pipeline_state = self.materials_pipeline_state.write().unwrap();
|
|
||||||
let mut materials_state = self.materials_state.write().unwrap();
|
|
||||||
let mut materials = self.materials.write().unwrap();
|
|
||||||
|
|
||||||
materials_id.push(type_id);
|
|
||||||
materials_pipeline_id.push(pipeline_id);
|
|
||||||
materials_pipeline.push(pipeline);
|
|
||||||
materials_pipeline_state.push(pipeline_state);
|
|
||||||
materials_state.push(Arc::new(RwLock::new(MaterialState::Loading)));
|
|
||||||
materials.push(Arc::new(RwLock::new(M::default())));
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update_swapchain_format(&mut self, swapchain_format: Format) {
|
|
||||||
if self.swapchain_format == swapchain_format {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.swapchain_format = swapchain_format;
|
|
||||||
self.mark_all_pipelines_as_loading();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn mark_all_pipelines_as_loading(&self) {
|
|
||||||
let pipelines_state = self.pipelines_state.write().unwrap();
|
|
||||||
|
|
||||||
for state in pipelines_state.iter() {
|
|
||||||
let mut state = state.write().unwrap();
|
|
||||||
*state = MaterialState::Loading;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn load_pipelines(&self) {
|
|
||||||
let pipelines_state = self.pipelines_state.read().unwrap();
|
|
||||||
let pipelines = self.pipelines.read().unwrap();
|
|
||||||
|
|
||||||
let iter = pipelines_state
|
|
||||||
.iter()
|
|
||||||
.zip(pipelines.iter())
|
|
||||||
.filter(|(state, _)| {
|
|
||||||
let state = state.read().unwrap();
|
|
||||||
matches!(*state, MaterialState::Loading)
|
|
||||||
});
|
|
||||||
|
|
||||||
for (state, pipeline) in iter {
|
|
||||||
let mut pipeline = pipeline.write().unwrap();
|
|
||||||
let result = pipeline.load(
|
|
||||||
&self.device,
|
|
||||||
&self.memory_allocator,
|
|
||||||
self.swapchain_format,
|
|
||||||
self.depth_format,
|
|
||||||
);
|
|
||||||
|
|
||||||
match result {
|
|
||||||
Ok(_) => {
|
|
||||||
let mut state = state.write().unwrap();
|
|
||||||
*state = MaterialState::Loaded;
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
tracing::error!("Failed to load pipeline: {e}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn load_materials(&self) {
|
|
||||||
let materials_state = self.materials_state.read().unwrap();
|
|
||||||
let materials = self.materials.read().unwrap();
|
|
||||||
|
|
||||||
let iter = materials_state
|
|
||||||
.iter()
|
|
||||||
.zip(materials.iter())
|
|
||||||
.filter(|(state, _)| {
|
|
||||||
let state = state.read().unwrap();
|
|
||||||
matches!(*state, MaterialState::Loading)
|
|
||||||
});
|
|
||||||
|
|
||||||
for (state, material) in iter {
|
|
||||||
let mut material = material.write().unwrap();
|
|
||||||
let result = material.load(&self.device, &self.memory_allocator);
|
|
||||||
|
|
||||||
match result {
|
|
||||||
Ok(_) => {
|
|
||||||
let mut state = state.write().unwrap();
|
|
||||||
*state = MaterialState::Loaded;
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
tracing::error!("Failed to load material: {e}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_materials<F>(&self, f: F)
|
|
||||||
where
|
|
||||||
F: Fn(RwLockReadGuard<'_, dyn Material>, RwLockReadGuard<'_, dyn Pipeline>),
|
|
||||||
{
|
|
||||||
let materials = self.materials.read().unwrap();
|
|
||||||
let materials_state = self.materials_state.read().unwrap();
|
|
||||||
let materials_pipeline = self.materials_pipeline.read().unwrap();
|
|
||||||
let materials_pipeline_state = self.materials_pipeline_state.read().unwrap();
|
|
||||||
|
|
||||||
materials
|
|
||||||
.iter()
|
|
||||||
.zip(materials_state.iter())
|
|
||||||
.zip(materials_pipeline.iter())
|
|
||||||
.zip(materials_pipeline_state.iter())
|
|
||||||
.filter(|(((_, material_state), _), pipeline_state)| {
|
|
||||||
let material_state = material_state.read().unwrap();
|
|
||||||
let pipeline_state = pipeline_state.read().unwrap();
|
|
||||||
matches!(*material_state, MaterialState::Loaded)
|
|
||||||
&& matches!(*pipeline_state, MaterialState::Loaded)
|
|
||||||
})
|
|
||||||
.for_each(|(((material, _), pipeline), _)| {
|
|
||||||
let material = material.read().unwrap();
|
|
||||||
let pipeline = pipeline.read().unwrap();
|
|
||||||
|
|
||||||
f(material, pipeline);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,5 +0,0 @@
|
||||||
pub mod material_manager;
|
|
||||||
pub mod primitives;
|
|
||||||
pub mod render_pass_manager;
|
|
||||||
pub mod texture;
|
|
||||||
pub mod vulkan_context;
|
|
|
@ -1,122 +0,0 @@
|
||||||
use std::{f32::consts::FRAC_PI_2, sync::Arc};
|
|
||||||
|
|
||||||
use glam::{Mat4, Vec3, Vec4};
|
|
||||||
use vulkano::{
|
|
||||||
Validated,
|
|
||||||
buffer::{AllocateBufferError, Subbuffer},
|
|
||||||
memory::allocator::StandardMemoryAllocator,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::core::{input::InputManager, timer::Timer};
|
|
||||||
|
|
||||||
use super::mvp::Mvp;
|
|
||||||
|
|
||||||
// See docs/OPENGL_VULKAN_DIFF.md
|
|
||||||
const OPENGL_TO_VULKAN_Y_AXIS_FLIP: Mat4 = Mat4 {
|
|
||||||
x_axis: Vec4::new(1.0, 0.0, 0.0, 0.0),
|
|
||||||
y_axis: Vec4::new(0.0, -1.0, 0.0, 0.0),
|
|
||||||
z_axis: Vec4::new(0.0, 0.0, 1.0, 0.0),
|
|
||||||
w_axis: Vec4::new(0.0, 0.0, 0.0, 1.0),
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct Camera3D {
|
|
||||||
projection: Mat4,
|
|
||||||
|
|
||||||
position: Vec3,
|
|
||||||
rotation: Vec3,
|
|
||||||
aspect_ratio: f32,
|
|
||||||
fov: f32,
|
|
||||||
near: f32,
|
|
||||||
far: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Camera3D {
|
|
||||||
pub fn new(aspect_ratio: f32, fov: f32, near: f32, far: f32) -> Self {
|
|
||||||
Self {
|
|
||||||
projection: Mat4::perspective_rh(fov, aspect_ratio, near, far),
|
|
||||||
position: Vec3::ZERO,
|
|
||||||
rotation: Vec3::ZERO,
|
|
||||||
aspect_ratio,
|
|
||||||
fov,
|
|
||||||
near,
|
|
||||||
far,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update(
|
|
||||||
&mut self,
|
|
||||||
input_manager: &InputManager,
|
|
||||||
timer: &Timer,
|
|
||||||
movement_speed: f32,
|
|
||||||
camera_sensitivity: f32,
|
|
||||||
window_aspect_ratio: f32,
|
|
||||||
) {
|
|
||||||
// Process camera rotation
|
|
||||||
let camera_delta = camera_sensitivity * timer.delta_time();
|
|
||||||
self.rotation += Vec3::new(
|
|
||||||
(input_manager.get_virtual_input_state("mouse_y") * camera_delta).to_radians(),
|
|
||||||
(input_manager.get_virtual_input_state("mouse_x") * camera_delta).to_radians(),
|
|
||||||
0.0,
|
|
||||||
);
|
|
||||||
|
|
||||||
if self.rotation.x > FRAC_PI_2 {
|
|
||||||
self.rotation = Vec3::new(FRAC_PI_2, self.rotation.y, 0.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.rotation.x < -FRAC_PI_2 {
|
|
||||||
self.rotation = Vec3::new(-FRAC_PI_2, self.rotation.y, 0.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
let movement_delta = movement_speed * timer.delta_time();
|
|
||||||
|
|
||||||
let (yaw_sin, yaw_cos) = self.rotation.y.sin_cos();
|
|
||||||
let forward = Vec3::new(yaw_cos, 0.0, yaw_sin).normalize();
|
|
||||||
let right = Vec3::new(-yaw_sin, 0.0, yaw_cos).normalize();
|
|
||||||
|
|
||||||
let tx = input_manager.get_virtual_input_state("move_right") * movement_delta;
|
|
||||||
self.position += tx * right;
|
|
||||||
|
|
||||||
let tz = input_manager.get_virtual_input_state("move_forward") * movement_delta;
|
|
||||||
self.position += tz * forward;
|
|
||||||
|
|
||||||
if self.aspect_ratio != window_aspect_ratio {
|
|
||||||
self.aspect_ratio = window_aspect_ratio;
|
|
||||||
self.projection =
|
|
||||||
Mat4::perspective_rh(self.fov, self.aspect_ratio, self.near, self.far);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_projection(&mut self, projection: Mat4) {
|
|
||||||
self.projection = projection;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_position(&self) -> Vec3 {
|
|
||||||
self.position
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_rotation(&self) -> Vec3 {
|
|
||||||
self.rotation
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_buffer(
|
|
||||||
&self,
|
|
||||||
memory_allocator: &Arc<StandardMemoryAllocator>,
|
|
||||||
) -> Result<Subbuffer<[Mvp]>, Validated<AllocateBufferError>> {
|
|
||||||
let (sin_pitch, cos_pitch) = self.rotation.x.sin_cos();
|
|
||||||
let (sin_yaw, cos_yaw) = self.rotation.y.sin_cos();
|
|
||||||
|
|
||||||
let view_matrix = Mat4::look_to_rh(
|
|
||||||
self.position,
|
|
||||||
Vec3::new(cos_pitch * cos_yaw, sin_pitch, cos_pitch * sin_yaw).normalize(),
|
|
||||||
Vec3::Y,
|
|
||||||
);
|
|
||||||
|
|
||||||
Mvp {
|
|
||||||
model: OPENGL_TO_VULKAN_Y_AXIS_FLIP.to_cols_array_2d(),
|
|
||||||
view: view_matrix.to_cols_array_2d(),
|
|
||||||
projection: self.projection.to_cols_array_2d(),
|
|
||||||
}
|
|
||||||
.into_buffer(memory_allocator)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,4 +0,0 @@
|
||||||
pub mod camera;
|
|
||||||
pub mod mvp;
|
|
||||||
pub mod transform;
|
|
||||||
pub mod vertex;
|
|
|
@ -1,36 +0,0 @@
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use vulkano::Validated;
|
|
||||||
use vulkano::buffer::{
|
|
||||||
AllocateBufferError, Buffer, BufferContents, BufferCreateInfo, BufferUsage, Subbuffer,
|
|
||||||
};
|
|
||||||
use vulkano::memory::allocator::{AllocationCreateInfo, MemoryTypeFilter, StandardMemoryAllocator};
|
|
||||||
|
|
||||||
#[derive(BufferContents, Clone, Copy)]
|
|
||||||
#[repr(C)]
|
|
||||||
pub struct Mvp {
|
|
||||||
pub model: [[f32; 4]; 4],
|
|
||||||
pub view: [[f32; 4]; 4],
|
|
||||||
pub projection: [[f32; 4]; 4],
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Mvp {
|
|
||||||
pub fn into_buffer(
|
|
||||||
self,
|
|
||||||
memory_allocator: &Arc<StandardMemoryAllocator>,
|
|
||||||
) -> Result<Subbuffer<[Mvp]>, Validated<AllocateBufferError>> {
|
|
||||||
Buffer::from_iter(
|
|
||||||
memory_allocator.clone(),
|
|
||||||
BufferCreateInfo {
|
|
||||||
usage: BufferUsage::UNIFORM_BUFFER,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
AllocationCreateInfo {
|
|
||||||
memory_type_filter: MemoryTypeFilter::PREFER_DEVICE
|
|
||||||
| MemoryTypeFilter::HOST_SEQUENTIAL_WRITE,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
[self],
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,81 +0,0 @@
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use glam::{Mat4, Quat, Vec3};
|
|
||||||
use vulkano::{
|
|
||||||
Validated,
|
|
||||||
buffer::{
|
|
||||||
AllocateBufferError, Buffer, BufferContents, BufferCreateInfo, BufferUsage, Subbuffer,
|
|
||||||
},
|
|
||||||
memory::allocator::{AllocationCreateInfo, MemoryTypeFilter, StandardMemoryAllocator},
|
|
||||||
pipeline::graphics::vertex_input::Vertex,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub struct Transform {
|
|
||||||
pub position: Vec3,
|
|
||||||
pub rotation: Quat,
|
|
||||||
pub scale: Vec3,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(BufferContents, Vertex)]
|
|
||||||
#[repr(C)]
|
|
||||||
pub struct TransformRaw {
|
|
||||||
#[format(R32G32B32A32_SFLOAT)]
|
|
||||||
pub model: [[f32; 4]; 4],
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Transform {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
position: Vec3::default(),
|
|
||||||
rotation: Quat::default(),
|
|
||||||
scale: Vec3::ONE,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Transform {
|
|
||||||
pub fn rotate(&mut self, rotation: Quat) {
|
|
||||||
self.rotation *= rotation;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn translate(&mut self, translation: Vec3) {
|
|
||||||
self.position += translation;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn scale(&mut self, scale: Vec3) {
|
|
||||||
self.scale *= scale;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_raw_tranform(&self) -> TransformRaw {
|
|
||||||
TransformRaw {
|
|
||||||
model: (Mat4::from_translation(self.position)
|
|
||||||
* Mat4::from_quat(self.rotation)
|
|
||||||
* Mat4::from_scale(self.scale))
|
|
||||||
.to_cols_array_2d(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_buffer(
|
|
||||||
memory_allocator: &Arc<StandardMemoryAllocator>,
|
|
||||||
transforms: &[Transform],
|
|
||||||
) -> Result<Subbuffer<[TransformRaw]>, Validated<AllocateBufferError>> {
|
|
||||||
let transform_raws: Vec<TransformRaw> =
|
|
||||||
transforms.iter().map(|t| t.to_raw_tranform()).collect();
|
|
||||||
|
|
||||||
let buffer = Buffer::from_iter(
|
|
||||||
memory_allocator.clone(),
|
|
||||||
BufferCreateInfo {
|
|
||||||
usage: BufferUsage::VERTEX_BUFFER,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
AllocationCreateInfo {
|
|
||||||
memory_type_filter: MemoryTypeFilter::PREFER_DEVICE
|
|
||||||
| MemoryTypeFilter::HOST_SEQUENTIAL_WRITE,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
transform_raws,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
Ok(buffer)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
use vulkano::buffer::BufferContents;
|
|
||||||
use vulkano::pipeline::graphics::vertex_input::Vertex;
|
|
||||||
|
|
||||||
#[derive(BufferContents, Vertex)]
|
|
||||||
#[repr(C)]
|
|
||||||
pub struct Vertex2D {
|
|
||||||
#[format(R32G32_SFLOAT)]
|
|
||||||
pub position: [f32; 2],
|
|
||||||
|
|
||||||
#[format(R32G32_SFLOAT)]
|
|
||||||
pub uv: [f32; 2],
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(BufferContents, Vertex)]
|
|
||||||
#[repr(C)]
|
|
||||||
pub struct Vertex3D {
|
|
||||||
#[format(R32G32B32_SFLOAT)]
|
|
||||||
pub position: [f32; 3],
|
|
||||||
|
|
||||||
#[format(R32G32_SFLOAT)]
|
|
||||||
pub uv: [f32; 2],
|
|
||||||
}
|
|
|
@ -1,113 +0,0 @@
|
||||||
use std::sync::Arc;
|
|
||||||
use vulkano::{
|
|
||||||
command_buffer::{AutoCommandBufferBuilder, RenderingAttachmentInfo, RenderingInfo},
|
|
||||||
image::view::ImageView,
|
|
||||||
pipeline::graphics::viewport::Viewport,
|
|
||||||
render_pass::{AttachmentLoadOp, AttachmentStoreOp},
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Types de render passes disponibles
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum RenderPassType {
|
|
||||||
Standard,
|
|
||||||
ShadowMap,
|
|
||||||
PostProcess,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Configuration pour un render pass
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct RenderPassConfig {
|
|
||||||
pub pass_type: RenderPassType,
|
|
||||||
pub clear_color: Option<[f32; 4]>,
|
|
||||||
pub clear_depth: Option<f32>,
|
|
||||||
pub load_op: AttachmentLoadOp,
|
|
||||||
pub store_op: AttachmentStoreOp,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for RenderPassConfig {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
pass_type: RenderPassType::Standard,
|
|
||||||
clear_color: Some([0.0, 0.0, 0.0, 1.0]),
|
|
||||||
clear_depth: Some(1.0),
|
|
||||||
load_op: AttachmentLoadOp::Clear,
|
|
||||||
store_op: AttachmentStoreOp::Store,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Gestionnaire de render passes réutilisable
|
|
||||||
pub struct RenderPassManager;
|
|
||||||
|
|
||||||
impl RenderPassManager {
|
|
||||||
/// Commence un render pass standard avec les paramètres donnés
|
|
||||||
pub fn begin_standard_rendering(
|
|
||||||
builder: &mut AutoCommandBufferBuilder<vulkano::command_buffer::PrimaryAutoCommandBuffer>,
|
|
||||||
config: &RenderPassConfig,
|
|
||||||
color_attachment: Arc<ImageView>,
|
|
||||||
depth_attachment: Option<Arc<ImageView>>,
|
|
||||||
window_size: [f32; 2],
|
|
||||||
) -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
let viewport = Viewport {
|
|
||||||
offset: [0.0, 0.0],
|
|
||||||
extent: window_size,
|
|
||||||
depth_range: 0.0..=1.0,
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut rendering_info = RenderingInfo {
|
|
||||||
color_attachments: vec![Some(RenderingAttachmentInfo {
|
|
||||||
load_op: config.load_op,
|
|
||||||
store_op: config.store_op,
|
|
||||||
clear_value: config.clear_color.map(|c| c.into()),
|
|
||||||
..RenderingAttachmentInfo::image_view(color_attachment)
|
|
||||||
})],
|
|
||||||
depth_attachment: None,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(depth_view) = depth_attachment {
|
|
||||||
rendering_info.depth_attachment = Some(RenderingAttachmentInfo {
|
|
||||||
load_op: AttachmentLoadOp::Clear,
|
|
||||||
store_op: AttachmentStoreOp::DontCare,
|
|
||||||
clear_value: config.clear_depth.map(|d| [d].into()),
|
|
||||||
..RenderingAttachmentInfo::image_view(depth_view)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
builder
|
|
||||||
.begin_rendering(rendering_info)?
|
|
||||||
.set_viewport(0, [viewport].into_iter().collect())?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Termine le render pass actuel
|
|
||||||
pub fn end_rendering(
|
|
||||||
builder: &mut AutoCommandBufferBuilder<vulkano::command_buffer::PrimaryAutoCommandBuffer>,
|
|
||||||
) -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
builder.end_rendering()?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Crée une configuration pour un render pass shadow map
|
|
||||||
pub fn shadow_map_config() -> RenderPassConfig {
|
|
||||||
RenderPassConfig {
|
|
||||||
pass_type: RenderPassType::ShadowMap,
|
|
||||||
clear_color: None,
|
|
||||||
clear_depth: Some(1.0),
|
|
||||||
load_op: AttachmentLoadOp::Clear,
|
|
||||||
store_op: AttachmentStoreOp::Store,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Crée une configuration pour un render pass de post-processing
|
|
||||||
pub fn post_process_config() -> RenderPassConfig {
|
|
||||||
RenderPassConfig {
|
|
||||||
pass_type: RenderPassType::PostProcess,
|
|
||||||
clear_color: Some([0.0, 0.0, 0.0, 1.0]),
|
|
||||||
clear_depth: None,
|
|
||||||
load_op: AttachmentLoadOp::Load,
|
|
||||||
store_op: AttachmentStoreOp::Store,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,123 +0,0 @@
|
||||||
use std::{path::Path, sync::Arc};
|
|
||||||
|
|
||||||
use anyhow::Error;
|
|
||||||
use image::{DynamicImage, EncodableLayout};
|
|
||||||
use vulkano::{
|
|
||||||
buffer::{Buffer, BufferCreateInfo, BufferUsage},
|
|
||||||
command_buffer::{AutoCommandBufferBuilder, CopyBufferToImageInfo, PrimaryAutoCommandBuffer},
|
|
||||||
device::Device,
|
|
||||||
format::Format,
|
|
||||||
image::{
|
|
||||||
Image, ImageCreateInfo, ImageType, ImageUsage,
|
|
||||||
sampler::{Filter, Sampler, SamplerAddressMode, SamplerCreateInfo},
|
|
||||||
view::ImageView,
|
|
||||||
},
|
|
||||||
memory::allocator::{AllocationCreateInfo, MemoryTypeFilter, StandardMemoryAllocator},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub struct Texture {
|
|
||||||
texture: Arc<ImageView>,
|
|
||||||
sampler: Arc<Sampler>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Texture {
|
|
||||||
fn new(texture: Arc<ImageView>, sampler: Arc<Sampler>) -> Self {
|
|
||||||
Self { texture, sampler }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_file(
|
|
||||||
device: &Arc<Device>,
|
|
||||||
memory_allocator: &Arc<StandardMemoryAllocator>,
|
|
||||||
builder: &mut AutoCommandBufferBuilder<PrimaryAutoCommandBuffer>,
|
|
||||||
path: &str,
|
|
||||||
) -> Result<Self, Error> {
|
|
||||||
let _span = tracing::info_span!("texture_load_from_file", path = path);
|
|
||||||
|
|
||||||
let bytes = std::fs::read(path)?;
|
|
||||||
Self::from_bytes(device, memory_allocator, builder, &bytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_bytes(
|
|
||||||
device: &Arc<Device>,
|
|
||||||
memory_allocator: &Arc<StandardMemoryAllocator>,
|
|
||||||
builder: &mut AutoCommandBufferBuilder<PrimaryAutoCommandBuffer>,
|
|
||||||
bytes: &[u8],
|
|
||||||
) -> Result<Self, Error> {
|
|
||||||
let image = image::load_from_memory(bytes)?;
|
|
||||||
Self::from_dynamic_image(device, memory_allocator, builder, image)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_dynamic_image(
|
|
||||||
device: &Arc<Device>,
|
|
||||||
memory_allocator: &Arc<StandardMemoryAllocator>,
|
|
||||||
builder: &mut AutoCommandBufferBuilder<PrimaryAutoCommandBuffer>,
|
|
||||||
image: DynamicImage,
|
|
||||||
) -> Result<Self, Error> {
|
|
||||||
let _span = tracing::info_span!("texture_from_dynamic_image");
|
|
||||||
|
|
||||||
let image_data = image.to_rgba8();
|
|
||||||
let image_dimensions = image_data.dimensions();
|
|
||||||
let image_data = image_data.into_raw();
|
|
||||||
|
|
||||||
let upload_buffer = Buffer::new_slice(
|
|
||||||
memory_allocator.clone(),
|
|
||||||
BufferCreateInfo {
|
|
||||||
usage: BufferUsage::TRANSFER_SRC,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
AllocationCreateInfo {
|
|
||||||
memory_type_filter: MemoryTypeFilter::PREFER_HOST
|
|
||||||
| MemoryTypeFilter::HOST_SEQUENTIAL_WRITE,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
image_data.len() as u64,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
{
|
|
||||||
let buffer_data = &mut *upload_buffer.write()?;
|
|
||||||
buffer_data.copy_from_slice(&image_data);
|
|
||||||
}
|
|
||||||
|
|
||||||
let image = Image::new(
|
|
||||||
memory_allocator.clone(),
|
|
||||||
ImageCreateInfo {
|
|
||||||
image_type: ImageType::Dim2d,
|
|
||||||
format: Format::R8G8B8A8_SRGB,
|
|
||||||
extent: [image_dimensions.0, image_dimensions.1, 1],
|
|
||||||
array_layers: 1,
|
|
||||||
usage: ImageUsage::TRANSFER_DST | ImageUsage::SAMPLED,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
AllocationCreateInfo::default(),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
builder.copy_buffer_to_image(CopyBufferToImageInfo::buffer_image(
|
|
||||||
upload_buffer,
|
|
||||||
image.clone(),
|
|
||||||
))?;
|
|
||||||
|
|
||||||
let sampler = Sampler::new(
|
|
||||||
device.clone(),
|
|
||||||
SamplerCreateInfo {
|
|
||||||
mag_filter: Filter::Linear,
|
|
||||||
min_filter: Filter::Linear,
|
|
||||||
address_mode: [SamplerAddressMode::Repeat; 3],
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let image_view = ImageView::new_default(image)?;
|
|
||||||
|
|
||||||
tracing::trace!("Texture loaded with dimensions {:?}", image_dimensions);
|
|
||||||
|
|
||||||
Ok(Self::new(image_view, sampler))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_texture(&self) -> &Arc<ImageView> {
|
|
||||||
&self.texture
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_sampler(&self) -> &Arc<Sampler> {
|
|
||||||
&self.sampler
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,45 +0,0 @@
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use vulkano::{
|
|
||||||
command_buffer::allocator::StandardCommandBufferAllocator,
|
|
||||||
descriptor_set::allocator::StandardDescriptorSetAllocator,
|
|
||||||
};
|
|
||||||
use vulkano_util::context::VulkanoContext;
|
|
||||||
|
|
||||||
pub struct VulkanContext {
|
|
||||||
vulkano_context: VulkanoContext,
|
|
||||||
command_buffer_allocator: Arc<StandardCommandBufferAllocator>,
|
|
||||||
descriptor_set_allocator: Arc<StandardDescriptorSetAllocator>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VulkanContext {
|
|
||||||
pub fn new(vulkano_context: VulkanoContext) -> Self {
|
|
||||||
let command_buffer_allocator = Arc::new(StandardCommandBufferAllocator::new(
|
|
||||||
vulkano_context.device().clone(),
|
|
||||||
Default::default(),
|
|
||||||
));
|
|
||||||
|
|
||||||
let descriptor_set_allocator = Arc::new(StandardDescriptorSetAllocator::new(
|
|
||||||
vulkano_context.device().clone(),
|
|
||||||
Default::default(),
|
|
||||||
));
|
|
||||||
|
|
||||||
Self {
|
|
||||||
vulkano_context,
|
|
||||||
command_buffer_allocator,
|
|
||||||
descriptor_set_allocator,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn vulkano_context(&self) -> &VulkanoContext {
|
|
||||||
&self.vulkano_context
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn command_buffer_allocator(&self) -> &Arc<StandardCommandBufferAllocator> {
|
|
||||||
&self.command_buffer_allocator
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn descriptor_set_allocator(&self) -> &Arc<StandardDescriptorSetAllocator> {
|
|
||||||
&self.descriptor_set_allocator
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,65 +0,0 @@
|
||||||
use std::error::Error;
|
|
||||||
|
|
||||||
use crate::core::app::context::WindowContext;
|
|
||||||
|
|
||||||
use super::Scene;
|
|
||||||
|
|
||||||
pub struct SceneManager {
|
|
||||||
scenes: Vec<Box<dyn Scene>>,
|
|
||||||
current_scene_index: Option<usize>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SceneManager {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
scenes: Vec::new(),
|
|
||||||
current_scene_index: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn load_scene(&mut self, scene: Box<dyn Scene>) {
|
|
||||||
self.scenes.push(scene);
|
|
||||||
self.current_scene_index = Some(self.scenes.len() - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn replace_current_scene(&mut self, scene: Box<dyn Scene>) {
|
|
||||||
if let Some(index) = self.current_scene_index {
|
|
||||||
if index < self.scenes.len() {
|
|
||||||
self.scenes[index].unload();
|
|
||||||
self.scenes[index] = scene;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
self.load_scene(scene);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn current_scene(&self) -> Option<&Box<dyn Scene>> {
|
|
||||||
if let Some(index) = self.current_scene_index {
|
|
||||||
self.scenes.get(index)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn current_scene_mut(&mut self) -> Option<&mut Box<dyn Scene>> {
|
|
||||||
if let Some(index) = self.current_scene_index {
|
|
||||||
self.scenes.get_mut(index)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn load_scene_if_not_loaded(
|
|
||||||
&mut self,
|
|
||||||
app_context: &mut WindowContext,
|
|
||||||
) -> Result<(), Box<dyn Error>> {
|
|
||||||
if let Some(scene) = self.current_scene_mut() {
|
|
||||||
if !scene.loaded() {
|
|
||||||
scene.load(app_context)?;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
tracing::warn!("No scene found in SceneManager!");
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
use std::error::Error;
|
|
||||||
|
|
||||||
use vulkano::sync::GpuFuture;
|
|
||||||
|
|
||||||
use crate::core::app::context::WindowContext;
|
|
||||||
|
|
||||||
pub mod manager;
|
|
||||||
|
|
||||||
pub trait Scene {
|
|
||||||
fn loaded(&self) -> bool;
|
|
||||||
fn load(&mut self, app_context: &mut WindowContext) -> Result<(), Box<dyn Error>>;
|
|
||||||
fn update(&mut self, app_context: &mut WindowContext) -> Result<(), Box<dyn Error>>;
|
|
||||||
fn render(
|
|
||||||
&mut self,
|
|
||||||
acquire_future: Box<dyn GpuFuture>,
|
|
||||||
app_context: &mut WindowContext,
|
|
||||||
) -> Result<Box<dyn GpuFuture>, Box<dyn Error>>;
|
|
||||||
fn unload(&mut self);
|
|
||||||
}
|
|
|
@ -1,34 +0,0 @@
|
||||||
pub struct Timer {
|
|
||||||
start_time: std::time::Instant,
|
|
||||||
last_time: std::time::Instant,
|
|
||||||
current_time: std::time::Instant,
|
|
||||||
delta_time: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Timer {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
start_time: std::time::Instant::now(),
|
|
||||||
last_time: std::time::Instant::now(),
|
|
||||||
current_time: std::time::Instant::now(),
|
|
||||||
delta_time: 0.0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update(&mut self) {
|
|
||||||
self.current_time = std::time::Instant::now();
|
|
||||||
self.delta_time = self
|
|
||||||
.current_time
|
|
||||||
.duration_since(self.last_time)
|
|
||||||
.as_secs_f32();
|
|
||||||
self.last_time = self.current_time;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn delta_time(&self) -> f32 {
|
|
||||||
self.delta_time
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn start_time(&self) -> std::time::Instant {
|
|
||||||
self.start_time
|
|
||||||
}
|
|
||||||
}
|
|
93
src/display/app.rs
Normal file
93
src/display/app.rs
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
use crate::display::window::Window;
|
||||||
|
use crate::renderer::{vulkan::VkRenderContext, Renderable};
|
||||||
|
use winit::application::ApplicationHandler;
|
||||||
|
use winit::event::WindowEvent;
|
||||||
|
use winit::event_loop::ActiveEventLoop;
|
||||||
|
use winit::window::WindowId;
|
||||||
|
use crate::scene::TriangleScene;
|
||||||
|
|
||||||
|
pub struct App {
|
||||||
|
window: Window,
|
||||||
|
render_context: Option<VkRenderContext>,
|
||||||
|
scene: Option<Box<dyn Renderable>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl App {
|
||||||
|
pub fn new(window: Window) -> Self {
|
||||||
|
Self {
|
||||||
|
window,
|
||||||
|
render_context: None,
|
||||||
|
scene: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_scene(&mut self, mut scene: Box<dyn Renderable>) {
|
||||||
|
let result = self.render_context.as_mut()
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("No render context"))
|
||||||
|
.and_then(|render_context| render_context.init_scene(&mut scene));
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(_) => self.scene = Some(scene),
|
||||||
|
Err(err) => log::warn!("{err}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ApplicationHandler for App {
|
||||||
|
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
|
||||||
|
self.window
|
||||||
|
.create_window(event_loop)
|
||||||
|
.map_err(|err| format!("Failed to create window: {}", err))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
self.render_context = VkRenderContext::init(&self.window).ok();
|
||||||
|
|
||||||
|
let scene = TriangleScene::new();
|
||||||
|
self.set_scene(Box::new(scene));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent) {
|
||||||
|
match event {
|
||||||
|
WindowEvent::CloseRequested => {
|
||||||
|
match self.render_context.as_ref() {
|
||||||
|
Some(render_context) => render_context.exit(),
|
||||||
|
None => log::warn!("Window closed but no render context found"),
|
||||||
|
};
|
||||||
|
|
||||||
|
log::debug!("The close button was pressed; stopping");
|
||||||
|
event_loop.exit();
|
||||||
|
}
|
||||||
|
WindowEvent::Resized(size) => {
|
||||||
|
match self.render_context.as_mut() {
|
||||||
|
Some(render_context) => {
|
||||||
|
if let Err(error) =
|
||||||
|
render_context.update_resolution(size.width, size.height)
|
||||||
|
{
|
||||||
|
log::error!(
|
||||||
|
"Failed to update resolution of render context : {}",
|
||||||
|
error
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => log::warn!("Window resized but no render context found"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
WindowEvent::RedrawRequested => {
|
||||||
|
if !event_loop.exiting() {
|
||||||
|
match self.render_context.as_mut() {
|
||||||
|
Some(render_context) => {
|
||||||
|
if let Err(error) = render_context.render(self.scene.as_ref()) {
|
||||||
|
log::error!("Failed to render with render context : {}", error);
|
||||||
|
event_loop.exit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => log::warn!("Window resized but no render context found"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
self.window.request_redraw();
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
5
src/display/mod.rs
Normal file
5
src/display/mod.rs
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
mod app;
|
||||||
|
mod window;
|
||||||
|
|
||||||
|
pub use app::App;
|
||||||
|
pub use window::Window;
|
65
src/display/window.rs
Normal file
65
src/display/window.rs
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
use std::ffi::c_char;
|
||||||
|
use winit::dpi::Pixel;
|
||||||
|
use winit::event_loop::ActiveEventLoop;
|
||||||
|
use winit::raw_window_handle::HasDisplayHandle;
|
||||||
|
|
||||||
|
pub struct Window {
|
||||||
|
handle: Option<winit::window::Window>,
|
||||||
|
window_attributes: winit::window::WindowAttributes,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Window {
|
||||||
|
pub fn new(window_attributes: winit::window::WindowAttributes) -> Self {
|
||||||
|
Self {
|
||||||
|
handle: None,
|
||||||
|
window_attributes,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_window(&mut self, event_loop: &ActiveEventLoop) -> anyhow::Result<()> {
|
||||||
|
let window = event_loop.create_window(self.window_attributes.clone())?;
|
||||||
|
|
||||||
|
self.handle = Some(window);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn required_extensions(&self) -> anyhow::Result<Vec<*const c_char>> {
|
||||||
|
let display_handle = self
|
||||||
|
.handle
|
||||||
|
.as_ref()
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("Window not found"))?
|
||||||
|
.display_handle()?;
|
||||||
|
|
||||||
|
#[allow(unused_mut)]
|
||||||
|
let mut extension_names =
|
||||||
|
ash_window::enumerate_required_extensions(display_handle.as_raw())?.to_vec();
|
||||||
|
|
||||||
|
// TODO: Move this because is not related to Window extensions
|
||||||
|
#[cfg(any(target_os = "macos", target_os = "ios"))]
|
||||||
|
{
|
||||||
|
extension_names.push(ash::khr::portability_enumeration::NAME.as_ptr());
|
||||||
|
// Enabling this extension is a requirement when using `VK_KHR_portability_subset`
|
||||||
|
extension_names.push(ash::khr::get_physical_device_properties2::NAME.as_ptr());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(extension_names)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle(&self) -> Option<&winit::window::Window> {
|
||||||
|
self.handle.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn physical_size<P: Pixel>(&self) -> Option<winit::dpi::PhysicalSize<P>> {
|
||||||
|
self.window_attributes
|
||||||
|
.inner_size
|
||||||
|
.and_then(|size| Some(size.to_physical::<P>(1.0)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn request_redraw(&self) {
|
||||||
|
match self.handle.as_ref() {
|
||||||
|
Some(window) => window.request_redraw(),
|
||||||
|
None => log::warn!("Redraw requested but no window found"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1 +0,0 @@
|
||||||
pub mod square;
|
|
|
@ -1,263 +0,0 @@
|
||||||
use std::{collections::BTreeMap, error::Error, sync::Arc};
|
|
||||||
|
|
||||||
use vulkano::{
|
|
||||||
buffer::{Buffer, BufferCreateInfo, BufferUsage, Subbuffer},
|
|
||||||
command_buffer::{AutoCommandBufferBuilder, PrimaryAutoCommandBuffer},
|
|
||||||
descriptor_set::{
|
|
||||||
DescriptorSet, WriteDescriptorSet,
|
|
||||||
allocator::StandardDescriptorSetAllocator,
|
|
||||||
layout::{DescriptorSetLayoutBinding, DescriptorSetLayoutCreateInfo, DescriptorType},
|
|
||||||
},
|
|
||||||
device::Device,
|
|
||||||
format::Format,
|
|
||||||
memory::allocator::{AllocationCreateInfo, MemoryTypeFilter, StandardMemoryAllocator},
|
|
||||||
pipeline::{
|
|
||||||
DynamicState, GraphicsPipeline, Pipeline, PipelineBindPoint, PipelineLayout,
|
|
||||||
PipelineShaderStageCreateInfo,
|
|
||||||
graphics::{
|
|
||||||
GraphicsPipelineCreateInfo,
|
|
||||||
color_blend::{ColorBlendAttachmentState, ColorBlendState},
|
|
||||||
depth_stencil::{DepthState, DepthStencilState},
|
|
||||||
input_assembly::InputAssemblyState,
|
|
||||||
multisample::MultisampleState,
|
|
||||||
rasterization::RasterizationState,
|
|
||||||
subpass::PipelineRenderingCreateInfo,
|
|
||||||
vertex_input::{Vertex, VertexDefinition},
|
|
||||||
viewport::ViewportState,
|
|
||||||
},
|
|
||||||
layout::{PipelineDescriptorSetLayoutCreateInfo, PipelineLayoutCreateFlags},
|
|
||||||
},
|
|
||||||
shader::ShaderStages,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::core::render::{
|
|
||||||
primitives::{mvp::Mvp, transform::TransformRaw, vertex::Vertex3D},
|
|
||||||
texture::Texture,
|
|
||||||
};
|
|
||||||
|
|
||||||
const VERTICES: [Vertex3D; 4] = [
|
|
||||||
Vertex3D {
|
|
||||||
position: [-0.5, -0.5, 0.0],
|
|
||||||
uv: [0.0, 0.0],
|
|
||||||
},
|
|
||||||
Vertex3D {
|
|
||||||
position: [-0.5, 0.5, 0.0],
|
|
||||||
uv: [0.0, 1.0],
|
|
||||||
},
|
|
||||||
Vertex3D {
|
|
||||||
position: [0.5, -0.5, 0.0],
|
|
||||||
uv: [1.0, 0.0],
|
|
||||||
},
|
|
||||||
Vertex3D {
|
|
||||||
position: [0.5, 0.5, 0.0],
|
|
||||||
uv: [1.0, 1.0],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const INDICES: [u32; 6] = [0, 2, 1, 2, 3, 1];
|
|
||||||
|
|
||||||
pub mod shaders {
|
|
||||||
pub mod vs {
|
|
||||||
vulkano_shaders::shader! {
|
|
||||||
ty: "vertex",
|
|
||||||
path: r"res/shaders/vertex.vert",
|
|
||||||
generate_structs: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod fs {
|
|
||||||
vulkano_shaders::shader! {
|
|
||||||
ty: "fragment",
|
|
||||||
path: r"res/shaders/vertex.frag",
|
|
||||||
generate_structs: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Square {
|
|
||||||
vertex_buffer: Subbuffer<[Vertex3D]>,
|
|
||||||
index_buffer: Subbuffer<[u32]>,
|
|
||||||
pipeline: Arc<GraphicsPipeline>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Square {
|
|
||||||
pub fn new(
|
|
||||||
device: &Arc<Device>,
|
|
||||||
memory_allocator: &Arc<StandardMemoryAllocator>,
|
|
||||||
swapchain_format: Format,
|
|
||||||
depth_format: Format,
|
|
||||||
) -> Result<Self, Box<dyn Error>> {
|
|
||||||
let vertex_buffer = Buffer::from_iter(
|
|
||||||
memory_allocator.clone(),
|
|
||||||
BufferCreateInfo {
|
|
||||||
usage: BufferUsage::VERTEX_BUFFER,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
AllocationCreateInfo {
|
|
||||||
memory_type_filter: MemoryTypeFilter::PREFER_DEVICE
|
|
||||||
| MemoryTypeFilter::HOST_SEQUENTIAL_WRITE,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
Vec::from_iter(VERTICES),
|
|
||||||
)?;
|
|
||||||
let index_buffer = Buffer::from_iter(
|
|
||||||
memory_allocator.clone(),
|
|
||||||
BufferCreateInfo {
|
|
||||||
usage: BufferUsage::INDEX_BUFFER,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
AllocationCreateInfo {
|
|
||||||
memory_type_filter: MemoryTypeFilter::PREFER_DEVICE
|
|
||||||
| MemoryTypeFilter::HOST_SEQUENTIAL_WRITE,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
Vec::from_iter(INDICES),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let vs = shaders::vs::load(device.clone())?
|
|
||||||
.entry_point("main")
|
|
||||||
.ok_or("Failed find main entry point of vertex shader".to_string())?;
|
|
||||||
|
|
||||||
let fs = shaders::fs::load(device.clone())?
|
|
||||||
.entry_point("main")
|
|
||||||
.ok_or("Failed find main entry point of fragment shader".to_string())?;
|
|
||||||
|
|
||||||
let vertex_input_state =
|
|
||||||
[Vertex3D::per_vertex(), TransformRaw::per_instance()].definition(&vs)?;
|
|
||||||
|
|
||||||
let stages = [
|
|
||||||
PipelineShaderStageCreateInfo::new(vs),
|
|
||||||
PipelineShaderStageCreateInfo::new(fs),
|
|
||||||
];
|
|
||||||
|
|
||||||
let vertex_bindings = BTreeMap::<u32, DescriptorSetLayoutBinding>::from_iter([(
|
|
||||||
0,
|
|
||||||
DescriptorSetLayoutBinding {
|
|
||||||
stages: ShaderStages::VERTEX,
|
|
||||||
..DescriptorSetLayoutBinding::descriptor_type(DescriptorType::UniformBuffer)
|
|
||||||
},
|
|
||||||
)]);
|
|
||||||
let fragment_bindings = BTreeMap::<u32, DescriptorSetLayoutBinding>::from_iter([
|
|
||||||
(
|
|
||||||
0,
|
|
||||||
DescriptorSetLayoutBinding {
|
|
||||||
stages: ShaderStages::FRAGMENT,
|
|
||||||
..DescriptorSetLayoutBinding::descriptor_type(DescriptorType::Sampler)
|
|
||||||
},
|
|
||||||
),
|
|
||||||
(
|
|
||||||
1,
|
|
||||||
DescriptorSetLayoutBinding {
|
|
||||||
stages: ShaderStages::FRAGMENT,
|
|
||||||
..DescriptorSetLayoutBinding::descriptor_type(DescriptorType::SampledImage)
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
|
|
||||||
let vertex_descriptor_set_layout = DescriptorSetLayoutCreateInfo {
|
|
||||||
bindings: vertex_bindings,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
let fragment_descriptor_set_layout = DescriptorSetLayoutCreateInfo {
|
|
||||||
bindings: fragment_bindings,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
let create_info = PipelineDescriptorSetLayoutCreateInfo {
|
|
||||||
set_layouts: vec![vertex_descriptor_set_layout, fragment_descriptor_set_layout],
|
|
||||||
flags: PipelineLayoutCreateFlags::default(),
|
|
||||||
push_constant_ranges: vec![],
|
|
||||||
}
|
|
||||||
.into_pipeline_layout_create_info(device.clone())?;
|
|
||||||
|
|
||||||
let layout = PipelineLayout::new(device.clone(), create_info)?;
|
|
||||||
|
|
||||||
let subpass = PipelineRenderingCreateInfo {
|
|
||||||
color_attachment_formats: vec![Some(swapchain_format)],
|
|
||||||
depth_attachment_format: Some(depth_format),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
let pipeline = GraphicsPipeline::new(
|
|
||||||
device.clone(),
|
|
||||||
None,
|
|
||||||
GraphicsPipelineCreateInfo {
|
|
||||||
stages: stages.into_iter().collect(),
|
|
||||||
vertex_input_state: Some(vertex_input_state),
|
|
||||||
input_assembly_state: Some(InputAssemblyState::default()),
|
|
||||||
viewport_state: Some(ViewportState::default()),
|
|
||||||
rasterization_state: Some(RasterizationState::default()),
|
|
||||||
multisample_state: Some(MultisampleState::default()),
|
|
||||||
color_blend_state: Some(ColorBlendState::with_attachment_states(
|
|
||||||
subpass.color_attachment_formats.len() as u32,
|
|
||||||
ColorBlendAttachmentState::default(),
|
|
||||||
)),
|
|
||||||
depth_stencil_state: Some(DepthStencilState {
|
|
||||||
depth: Some(DepthState::simple()),
|
|
||||||
..Default::default()
|
|
||||||
}),
|
|
||||||
dynamic_state: [DynamicState::Viewport].into_iter().collect(),
|
|
||||||
subpass: Some(subpass.into()),
|
|
||||||
..GraphicsPipelineCreateInfo::layout(layout)
|
|
||||||
},
|
|
||||||
)?;
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
vertex_buffer,
|
|
||||||
index_buffer,
|
|
||||||
pipeline,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn render(
|
|
||||||
&self,
|
|
||||||
command_buffer: &mut AutoCommandBufferBuilder<PrimaryAutoCommandBuffer>,
|
|
||||||
descriptor_set_allocator: &Arc<StandardDescriptorSetAllocator>,
|
|
||||||
mvp_uniform: &Subbuffer<[Mvp]>,
|
|
||||||
transform_uniform: &Subbuffer<[TransformRaw]>,
|
|
||||||
texture: &Texture,
|
|
||||||
) -> Result<(), Box<dyn Error>> {
|
|
||||||
let layouts = self.pipeline.layout().set_layouts();
|
|
||||||
|
|
||||||
let uniform_descriptor_set = DescriptorSet::new(
|
|
||||||
descriptor_set_allocator.clone(),
|
|
||||||
layouts[0].clone(),
|
|
||||||
[WriteDescriptorSet::buffer(0, mvp_uniform.clone())],
|
|
||||||
[],
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let texture_descriptor_set = DescriptorSet::new(
|
|
||||||
descriptor_set_allocator.clone(),
|
|
||||||
layouts[1].clone(),
|
|
||||||
[
|
|
||||||
WriteDescriptorSet::sampler(0, texture.get_sampler().clone()),
|
|
||||||
WriteDescriptorSet::image_view(1, texture.get_texture().clone()),
|
|
||||||
],
|
|
||||||
[],
|
|
||||||
)?;
|
|
||||||
|
|
||||||
command_buffer.bind_pipeline_graphics(self.pipeline.clone())?;
|
|
||||||
command_buffer.bind_descriptor_sets(
|
|
||||||
PipelineBindPoint::Graphics,
|
|
||||||
self.pipeline.layout().clone(),
|
|
||||||
0,
|
|
||||||
vec![uniform_descriptor_set, texture_descriptor_set],
|
|
||||||
)?;
|
|
||||||
command_buffer
|
|
||||||
.bind_vertex_buffers(0, (self.vertex_buffer.clone(), transform_uniform.clone()))?;
|
|
||||||
command_buffer.bind_index_buffer(self.index_buffer.clone())?;
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
command_buffer.draw_indexed(
|
|
||||||
INDICES.len() as u32,
|
|
||||||
transform_uniform.len() as u32,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,2 +0,0 @@
|
||||||
pub mod assets;
|
|
||||||
pub mod scenes;
|
|
|
@ -1,285 +0,0 @@
|
||||||
use std::error::Error;
|
|
||||||
|
|
||||||
use super::settings_scene::SettingsScene;
|
|
||||||
use crate::core::app::DEPTH_IMAGE_ID;
|
|
||||||
use crate::core::app::context::WindowContext;
|
|
||||||
use crate::core::app::user_event::UserEvent;
|
|
||||||
use crate::core::render::primitives::camera::Camera3D;
|
|
||||||
use crate::core::render::primitives::transform::Transform;
|
|
||||||
use crate::core::render::render_pass_manager::{RenderPassConfig, RenderPassManager};
|
|
||||||
use crate::core::render::texture::Texture;
|
|
||||||
use crate::core::scene::Scene;
|
|
||||||
use crate::game::assets::square::Square;
|
|
||||||
use egui_winit_vulkano::egui;
|
|
||||||
use glam::EulerRot;
|
|
||||||
use glam::Quat;
|
|
||||||
use glam::Vec3;
|
|
||||||
use vulkano::{
|
|
||||||
command_buffer::{AutoCommandBufferBuilder, CommandBufferUsage, PrimaryCommandBufferAbstract},
|
|
||||||
sync::GpuFuture,
|
|
||||||
};
|
|
||||||
use winit::window::CursorGrabMode;
|
|
||||||
|
|
||||||
pub struct MainSceneState {
|
|
||||||
square: Square,
|
|
||||||
instances: Vec<Transform>,
|
|
||||||
camera: Camera3D,
|
|
||||||
texture: Texture,
|
|
||||||
speed: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct MainScene {
|
|
||||||
state: Option<MainSceneState>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Scene for MainScene {
|
|
||||||
fn loaded(&self) -> bool {
|
|
||||||
self.state.is_some()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn load(&mut self, app_context: &mut WindowContext) -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
let depth_image_view = app_context.with_renderer_mut(|renderer| {
|
|
||||||
renderer.get_additional_image_view(DEPTH_IMAGE_ID).clone()
|
|
||||||
});
|
|
||||||
|
|
||||||
let swapchain_image_view =
|
|
||||||
app_context.with_renderer(|renderer| renderer.swapchain_image_view().clone());
|
|
||||||
|
|
||||||
let square = Square::new(
|
|
||||||
&app_context.device,
|
|
||||||
&app_context.memory_allocator,
|
|
||||||
swapchain_image_view.format(),
|
|
||||||
depth_image_view.format(),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let num_instances = 100;
|
|
||||||
let instance_size = 10.0;
|
|
||||||
let instance_spacing = 10.0;
|
|
||||||
let num_instances_per_row = (num_instances as f32 / instance_spacing).ceil() as u32;
|
|
||||||
let instances: Vec<Transform> = (0..num_instances)
|
|
||||||
.map(|i| Transform {
|
|
||||||
position: Vec3::new(
|
|
||||||
(i % num_instances_per_row) as f32 * (instance_spacing + instance_size),
|
|
||||||
0.0,
|
|
||||||
(i / num_instances_per_row) as f32 * (instance_spacing + instance_size),
|
|
||||||
),
|
|
||||||
rotation: Quat::from_euler(
|
|
||||||
EulerRot::XYZ,
|
|
||||||
0.0,
|
|
||||||
rand::random_range(0.0..=360.0),
|
|
||||||
0.0,
|
|
||||||
),
|
|
||||||
scale: Vec3::new(instance_size, instance_size, instance_size),
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let texture = {
|
|
||||||
let mut uploads = AutoCommandBufferBuilder::primary(
|
|
||||||
app_context.command_buffer_allocator.clone(),
|
|
||||||
app_context.graphics_queue.queue_family_index(),
|
|
||||||
CommandBufferUsage::OneTimeSubmit,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let texture = Texture::from_file(
|
|
||||||
&app_context.device,
|
|
||||||
&app_context.memory_allocator,
|
|
||||||
&mut uploads,
|
|
||||||
"res/textures/wooden-crate.jpg",
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let _ = uploads
|
|
||||||
.build()?
|
|
||||||
.execute(app_context.graphics_queue.clone())?;
|
|
||||||
|
|
||||||
texture
|
|
||||||
};
|
|
||||||
|
|
||||||
let camera = app_context.with_renderer(|renderer| {
|
|
||||||
Camera3D::new(
|
|
||||||
renderer.aspect_ratio(),
|
|
||||||
std::f32::consts::FRAC_PI_2,
|
|
||||||
0.01,
|
|
||||||
1000.0,
|
|
||||||
)
|
|
||||||
});
|
|
||||||
|
|
||||||
self.state = Some(MainSceneState {
|
|
||||||
square,
|
|
||||||
instances,
|
|
||||||
camera,
|
|
||||||
texture,
|
|
||||||
speed: 50.0,
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update(&mut self, app_context: &mut WindowContext) -> Result<(), Box<dyn Error>> {
|
|
||||||
let state = self.state.as_mut().unwrap();
|
|
||||||
app_context.with_input_manager(|input_manager| {
|
|
||||||
app_context.with_timer(|timer| {
|
|
||||||
state.camera.update(
|
|
||||||
input_manager,
|
|
||||||
timer,
|
|
||||||
state.speed,
|
|
||||||
10.0,
|
|
||||||
app_context.get_aspect_ratio(),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
if app_context
|
|
||||||
.with_input_manager(|input_manager| input_manager.get_virtual_input_state("mouse_left"))
|
|
||||||
> 0.0
|
|
||||||
{
|
|
||||||
let _ = app_context
|
|
||||||
.event_loop_proxy
|
|
||||||
.send_event(UserEvent::CursorVisible(app_context.window_id, false));
|
|
||||||
let _ = app_context
|
|
||||||
.event_loop_proxy
|
|
||||||
.send_event(UserEvent::CursorGrabMode(
|
|
||||||
app_context.window_id,
|
|
||||||
CursorGrabMode::Locked,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
if app_context.with_input_manager(|input_manager| {
|
|
||||||
input_manager.get_virtual_input_state("mouse_right")
|
|
||||||
}) > 0.0
|
|
||||||
{
|
|
||||||
let _ = app_context
|
|
||||||
.event_loop_proxy
|
|
||||||
.send_event(UserEvent::CursorVisible(app_context.window_id, true));
|
|
||||||
let _ = app_context
|
|
||||||
.event_loop_proxy
|
|
||||||
.send_event(UserEvent::CursorGrabMode(
|
|
||||||
app_context.window_id,
|
|
||||||
CursorGrabMode::None,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render(
|
|
||||||
&mut self,
|
|
||||||
before_future: Box<dyn GpuFuture>,
|
|
||||||
app_context: &mut WindowContext,
|
|
||||||
) -> Result<Box<dyn GpuFuture>, Box<dyn Error>> {
|
|
||||||
let state = self.state.as_ref().ok_or("State not loaded")?;
|
|
||||||
|
|
||||||
let mut builder = AutoCommandBufferBuilder::primary(
|
|
||||||
app_context.command_buffer_allocator.clone(),
|
|
||||||
app_context.graphics_queue.queue_family_index(),
|
|
||||||
CommandBufferUsage::OneTimeSubmit,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
{
|
|
||||||
let swapchain_image_view =
|
|
||||||
app_context.with_renderer(|renderer| renderer.swapchain_image_view().clone());
|
|
||||||
let depth_image_view = app_context.with_renderer_mut(|renderer| {
|
|
||||||
renderer.get_additional_image_view(DEPTH_IMAGE_ID).clone()
|
|
||||||
});
|
|
||||||
let config = RenderPassConfig::default();
|
|
||||||
RenderPassManager::begin_standard_rendering(
|
|
||||||
&mut builder,
|
|
||||||
&config,
|
|
||||||
swapchain_image_view,
|
|
||||||
Some(depth_image_view),
|
|
||||||
app_context.get_window_size(),
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create camera uniform using the actual camera
|
|
||||||
let camera_uniform = state.camera.create_buffer(&app_context.memory_allocator)?;
|
|
||||||
|
|
||||||
let transform_uniform =
|
|
||||||
Transform::create_buffer(&app_context.memory_allocator, &state.instances)?;
|
|
||||||
|
|
||||||
state
|
|
||||||
.square
|
|
||||||
.render(
|
|
||||||
&mut builder,
|
|
||||||
&app_context.descriptor_set_allocator,
|
|
||||||
&camera_uniform,
|
|
||||||
&transform_uniform,
|
|
||||||
&state.texture,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
RenderPassManager::end_rendering(&mut builder)?;
|
|
||||||
|
|
||||||
let command_buffer = builder.build()?;
|
|
||||||
|
|
||||||
let render_future =
|
|
||||||
before_future.then_execute(app_context.graphics_queue.clone(), command_buffer)?;
|
|
||||||
|
|
||||||
let swapchain_image_view =
|
|
||||||
app_context.with_renderer(|renderer| renderer.swapchain_image_view().clone());
|
|
||||||
let input_manager_status =
|
|
||||||
app_context.with_input_manager(|input_manager| format!("{:#?}", input_manager));
|
|
||||||
let event_loop_proxy = app_context.event_loop_proxy.clone();
|
|
||||||
let delta_time = app_context.get_delta_time();
|
|
||||||
let window_id = app_context.window_id;
|
|
||||||
let window_size = app_context.get_window_size();
|
|
||||||
|
|
||||||
let render_future = app_context.with_gui_mut(|gui| {
|
|
||||||
gui.immediate_ui(|gui| {
|
|
||||||
let ctx = gui.context();
|
|
||||||
egui::TopBottomPanel::top("top_panel").show(&ctx, |ui| {
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
ui.heading("Vulkan Test - Moteur 3D");
|
|
||||||
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
|
||||||
if ui.button("Paramètres").clicked() {
|
|
||||||
let _ = event_loop_proxy.send_event(UserEvent::ChangeScene(
|
|
||||||
window_id,
|
|
||||||
Box::new(SettingsScene::default()),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
if ui.button("Quitter").clicked() {
|
|
||||||
let _ = event_loop_proxy.send_event(UserEvent::Exit(window_id));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
egui::SidePanel::left("side_panel").show(&ctx, |ui| {
|
|
||||||
ui.heading("Informations");
|
|
||||||
|
|
||||||
ui.separator();
|
|
||||||
|
|
||||||
ui.label(format!("Résolution: {:?}", window_size));
|
|
||||||
ui.label(format!("Delta Time: {:.2}ms", delta_time * 1000.0));
|
|
||||||
|
|
||||||
ui.separator();
|
|
||||||
|
|
||||||
ui.label("Position caméra:");
|
|
||||||
let position = state.camera.get_position();
|
|
||||||
ui.label(format!(" X: {:.2}", position[0]));
|
|
||||||
ui.label(format!(" Y: {:.2}", position[1]));
|
|
||||||
ui.label(format!(" Z: {:.2}", position[2]));
|
|
||||||
|
|
||||||
ui.separator();
|
|
||||||
|
|
||||||
ui.label("Rotation caméra:");
|
|
||||||
let rotation = state.camera.get_rotation();
|
|
||||||
ui.label(format!(" Yaw: {:.2}°", rotation.y.to_degrees()));
|
|
||||||
ui.label(format!(" Pitch: {:.2}°", rotation.x.to_degrees()));
|
|
||||||
|
|
||||||
ui.separator();
|
|
||||||
|
|
||||||
ui.label(input_manager_status);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
gui.draw_on_image(render_future, swapchain_image_view.clone())
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(render_future)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn unload(&mut self) {
|
|
||||||
self.state = None;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,2 +0,0 @@
|
||||||
pub mod main_scene;
|
|
||||||
pub mod settings_scene;
|
|
|
@ -1,136 +0,0 @@
|
||||||
use std::error::Error;
|
|
||||||
|
|
||||||
use crate::core::app::DEPTH_IMAGE_ID;
|
|
||||||
use crate::core::app::context::WindowContext;
|
|
||||||
use crate::core::app::user_event::UserEvent;
|
|
||||||
use crate::core::render::render_pass_manager::{RenderPassConfig, RenderPassManager};
|
|
||||||
use crate::core::scene::Scene;
|
|
||||||
use egui_winit_vulkano::egui;
|
|
||||||
use vulkano::{
|
|
||||||
command_buffer::AutoCommandBufferBuilder, command_buffer::CommandBufferUsage, sync::GpuFuture,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::main_scene::MainScene;
|
|
||||||
|
|
||||||
pub struct SettingsSceneState {
|
|
||||||
current_resolution: [f32; 2],
|
|
||||||
available_resolutions: Vec<(u32, u32)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct SettingsScene {
|
|
||||||
state: Option<SettingsSceneState>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Scene for SettingsScene {
|
|
||||||
fn loaded(&self) -> bool {
|
|
||||||
self.state.is_some()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn load(&mut self, app_context: &mut WindowContext) -> Result<(), Box<dyn Error>> {
|
|
||||||
let current_resolution = app_context.get_window_size();
|
|
||||||
let available_resolutions = app_context.get_available_resolutions();
|
|
||||||
|
|
||||||
self.state = Some(SettingsSceneState {
|
|
||||||
current_resolution,
|
|
||||||
available_resolutions,
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update(&mut self, _app_context: &mut WindowContext) -> Result<(), Box<dyn Error>> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render(
|
|
||||||
&mut self,
|
|
||||||
before_future: Box<dyn GpuFuture>,
|
|
||||||
app_context: &mut WindowContext,
|
|
||||||
) -> Result<Box<dyn GpuFuture>, Box<dyn Error>> {
|
|
||||||
let state = self.state.as_ref().ok_or("State not found")?;
|
|
||||||
|
|
||||||
let mut builder = AutoCommandBufferBuilder::primary(
|
|
||||||
app_context.command_buffer_allocator.clone(),
|
|
||||||
app_context.graphics_queue.queue_family_index(),
|
|
||||||
CommandBufferUsage::OneTimeSubmit,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
// Utiliser le RenderPassManager
|
|
||||||
{
|
|
||||||
let swapchain_image_view =
|
|
||||||
app_context.with_renderer(|renderer| renderer.swapchain_image_view().clone());
|
|
||||||
let depth_stencil_image_view = app_context.with_renderer_mut(|renderer| {
|
|
||||||
renderer.get_additional_image_view(DEPTH_IMAGE_ID).clone()
|
|
||||||
});
|
|
||||||
|
|
||||||
let config = RenderPassConfig::default();
|
|
||||||
RenderPassManager::begin_standard_rendering(
|
|
||||||
&mut builder,
|
|
||||||
&config,
|
|
||||||
swapchain_image_view,
|
|
||||||
Some(depth_stencil_image_view),
|
|
||||||
app_context.get_window_size(),
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pas de géométrie dans cette scène - juste un écran de paramètres
|
|
||||||
RenderPassManager::end_rendering(&mut builder)?;
|
|
||||||
|
|
||||||
let command_buffer = builder.build()?;
|
|
||||||
|
|
||||||
let render_future =
|
|
||||||
before_future.then_execute(app_context.graphics_queue.clone(), command_buffer)?;
|
|
||||||
|
|
||||||
let swapchain_image_view =
|
|
||||||
app_context.with_renderer(|renderer| renderer.swapchain_image_view().clone());
|
|
||||||
|
|
||||||
let event_loop_proxy = app_context.event_loop_proxy.clone();
|
|
||||||
let window_id = app_context.window_id;
|
|
||||||
|
|
||||||
let render_future = app_context.with_gui_mut(|gui| {
|
|
||||||
gui.immediate_ui(|gui| {
|
|
||||||
let ctx = gui.context();
|
|
||||||
|
|
||||||
egui::CentralPanel::default().show(&ctx, |ui| {
|
|
||||||
ui.heading("Paramètres");
|
|
||||||
|
|
||||||
ui.separator();
|
|
||||||
|
|
||||||
ui.label(format!(
|
|
||||||
"Résolution actuelle: {:?}",
|
|
||||||
state.current_resolution
|
|
||||||
));
|
|
||||||
|
|
||||||
ui.separator();
|
|
||||||
ui.label("Changer la résolution:");
|
|
||||||
|
|
||||||
for &(width, height) in &state.available_resolutions {
|
|
||||||
if ui.button(format!("{}x{}", width, height)).clicked() {
|
|
||||||
let _ = event_loop_proxy.send_event(UserEvent::ChangeResolution(
|
|
||||||
window_id,
|
|
||||||
width as f32,
|
|
||||||
height as f32,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.separator();
|
|
||||||
|
|
||||||
if ui.button("Retour au jeu").clicked() {
|
|
||||||
let _ = event_loop_proxy.send_event(UserEvent::ChangeScene(
|
|
||||||
window_id,
|
|
||||||
Box::new(MainScene::default()),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
gui.draw_on_image(render_future, swapchain_image_view.clone())
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(render_future)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn unload(&mut self) {}
|
|
||||||
}
|
|
116
src/main.rs
116
src/main.rs
|
@ -1,108 +1,24 @@
|
||||||
use core::input::{AxisDirection, InputManager, VirtualBinding};
|
use winit::event_loop::{ControlFlow, EventLoop};
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use vulkano::device::{DeviceExtensions, DeviceFeatures};
|
mod display;
|
||||||
use vulkano_util::context::{VulkanoConfig, VulkanoContext};
|
mod renderer;
|
||||||
use winit::{
|
mod scene;
|
||||||
event::MouseButton,
|
|
||||||
event_loop::{ControlFlow, EventLoop},
|
|
||||||
keyboard::{KeyCode, PhysicalKey},
|
|
||||||
};
|
|
||||||
|
|
||||||
mod core;
|
|
||||||
mod game;
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
use tracing_subscriber::layer::SubscriberExt;
|
env_logger::init();
|
||||||
use tracing_subscriber::util::SubscriberInitExt;
|
|
||||||
|
|
||||||
tracing_subscriber::registry()
|
let event_loop = EventLoop::new().unwrap();
|
||||||
.with(
|
|
||||||
tracing_subscriber::fmt::layer()
|
|
||||||
.with_target(true)
|
|
||||||
.with_thread_ids(true)
|
|
||||||
.with_file(true)
|
|
||||||
.with_line_number(true),
|
|
||||||
)
|
|
||||||
.with(tracing_tracy::TracyLayer::default())
|
|
||||||
.with(
|
|
||||||
tracing_subscriber::EnvFilter::try_from_default_env()
|
|
||||||
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")),
|
|
||||||
)
|
|
||||||
.init();
|
|
||||||
|
|
||||||
let input_manager = InputManager::new(HashMap::from([
|
|
||||||
(
|
|
||||||
"move_forward".to_string(),
|
|
||||||
vec![
|
|
||||||
VirtualBinding::Keyboard(PhysicalKey::Code(KeyCode::KeyW), AxisDirection::Normal),
|
|
||||||
VirtualBinding::Keyboard(PhysicalKey::Code(KeyCode::KeyS), AxisDirection::Invert),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"move_right".to_string(),
|
|
||||||
vec![
|
|
||||||
VirtualBinding::Keyboard(PhysicalKey::Code(KeyCode::KeyD), AxisDirection::Normal),
|
|
||||||
VirtualBinding::Keyboard(PhysicalKey::Code(KeyCode::KeyA), AxisDirection::Invert),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"mouse_x".to_string(),
|
|
||||||
vec![VirtualBinding::MouseX(AxisDirection::Normal)],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"mouse_y".to_string(),
|
|
||||||
vec![VirtualBinding::MouseY(AxisDirection::Normal)],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"mouse_wheel".to_string(),
|
|
||||||
vec![VirtualBinding::MouseWheelY(AxisDirection::Normal)],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"mouse_left".to_string(),
|
|
||||||
vec![VirtualBinding::MouseButton(
|
|
||||||
MouseButton::Left,
|
|
||||||
AxisDirection::Normal,
|
|
||||||
)],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"mouse_right".to_string(),
|
|
||||||
vec![VirtualBinding::MouseButton(
|
|
||||||
MouseButton::Right,
|
|
||||||
AxisDirection::Normal,
|
|
||||||
)],
|
|
||||||
),
|
|
||||||
]));
|
|
||||||
|
|
||||||
let device_extensions = DeviceExtensions {
|
|
||||||
khr_swapchain: true,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
let device_features = DeviceFeatures {
|
|
||||||
dynamic_rendering: true,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
let vulkano_config = VulkanoConfig {
|
|
||||||
print_device_name: true,
|
|
||||||
device_extensions,
|
|
||||||
device_features,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
let vulkano_context = VulkanoContext::new(vulkano_config);
|
|
||||||
|
|
||||||
let event_loop = EventLoop::with_user_event().build().unwrap();
|
|
||||||
event_loop.set_control_flow(ControlFlow::Poll);
|
event_loop.set_control_flow(ControlFlow::Poll);
|
||||||
let proxy = event_loop.create_proxy();
|
|
||||||
|
|
||||||
let mut app = core::app::App::new(vulkano_context, input_manager, proxy);
|
let window_attributes = winit::window::Window::default_attributes()
|
||||||
|
.with_title("Rust ASH Test")
|
||||||
|
.with_inner_size(winit::dpi::PhysicalSize::new(
|
||||||
|
f64::from(800),
|
||||||
|
f64::from(600),
|
||||||
|
));
|
||||||
|
|
||||||
match event_loop.run_app(&mut app) {
|
let window = display::Window::new(window_attributes);
|
||||||
Ok(_) => {}
|
let mut app = display::App::new(window);
|
||||||
Err(e) => {
|
|
||||||
tracing::error!("Error running old app: {e}");
|
event_loop.run_app(&mut app).unwrap();
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
9
src/renderer/mod.rs
Normal file
9
src/renderer/mod.rs
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
use ash::vk;
|
||||||
|
|
||||||
|
pub mod vulkan;
|
||||||
|
|
||||||
|
pub trait Renderable {
|
||||||
|
fn init(&mut self, device: &Arc<vulkan::VkDevice>, render_pass: &Arc<vulkan::VkRenderPass>) -> anyhow::Result<()>;
|
||||||
|
fn render(&self, device: &vulkan::VkDevice, swapchain: &vulkan::VkSwapchain, command_buffer: &vk::CommandBuffer) -> anyhow::Result<()>;
|
||||||
|
}
|
45
src/renderer/vulkan/mod.rs
Normal file
45
src/renderer/vulkan/mod.rs
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
mod vk_render_context;
|
||||||
|
pub use vk_render_context::VkRenderContext;
|
||||||
|
|
||||||
|
mod vk_instance;
|
||||||
|
pub use vk_instance::VkInstance;
|
||||||
|
|
||||||
|
mod vk_surface;
|
||||||
|
pub use vk_surface::{SwapchainSupportDetails, VkSurface};
|
||||||
|
|
||||||
|
mod vk_physical_device;
|
||||||
|
pub use vk_physical_device::VkPhysicalDevice;
|
||||||
|
|
||||||
|
mod vk_device;
|
||||||
|
pub use vk_device::VkDevice;
|
||||||
|
|
||||||
|
mod vk_swapchain;
|
||||||
|
pub use vk_swapchain::VkSwapchain;
|
||||||
|
|
||||||
|
mod vk_shader_module;
|
||||||
|
pub use vk_shader_module::VkShaderModule;
|
||||||
|
|
||||||
|
mod vk_graphics_pipeline;
|
||||||
|
pub use vk_graphics_pipeline::VkGraphicsPipeline;
|
||||||
|
|
||||||
|
mod vk_render_pass;
|
||||||
|
pub use vk_render_pass::VkRenderPass;
|
||||||
|
|
||||||
|
mod vk_semaphore;
|
||||||
|
pub use vk_semaphore::VkSemaphore;
|
||||||
|
|
||||||
|
mod vk_command_pool;
|
||||||
|
pub use vk_command_pool::VkCommandPool;
|
||||||
|
|
||||||
|
mod vk_framebuffer;
|
||||||
|
pub use vk_framebuffer::VkFramebuffer;
|
||||||
|
|
||||||
|
mod vk_fence;
|
||||||
|
pub use vk_fence::VkFence;
|
||||||
|
|
||||||
|
mod utils;
|
||||||
|
mod vertex;
|
||||||
|
mod vk_buffer;
|
||||||
|
pub use vk_buffer::VkBuffer;
|
||||||
|
|
||||||
|
pub use vertex::Vertex;
|
51
src/renderer/vulkan/utils/layers.rs
Normal file
51
src/renderer/vulkan/utils/layers.rs
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
use std::ffi::CString;
|
||||||
|
|
||||||
|
pub enum LayersSelector<'a> {
|
||||||
|
Nothing,
|
||||||
|
SpecificLayers(Vec<&'a str>),
|
||||||
|
All,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn use_layers(entry: &ash::Entry, selector: LayersSelector) -> Vec<CString> {
|
||||||
|
let layers_available = get_layers_available(entry)
|
||||||
|
.iter()
|
||||||
|
.filter_map(|layer| {
|
||||||
|
layer
|
||||||
|
.layer_name_as_c_str()
|
||||||
|
.and_then(|layer_name| Ok(CString::from(layer_name)))
|
||||||
|
.ok()
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
match selector {
|
||||||
|
LayersSelector::Nothing => Vec::new(),
|
||||||
|
LayersSelector::SpecificLayers(layers) => select_layers(&layers_available, &layers),
|
||||||
|
LayersSelector::All => layers_available,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_layers_available(entry: &ash::Entry) -> Vec<ash::vk::LayerProperties> {
|
||||||
|
unsafe {
|
||||||
|
entry
|
||||||
|
.enumerate_instance_layer_properties()
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn select_layers(layers_available: &Vec<CString>, layers_to_select: &[&str]) -> Vec<CString> {
|
||||||
|
layers_to_select
|
||||||
|
.iter()
|
||||||
|
.filter_map(|layer_name| {
|
||||||
|
if layers_available.iter().any(|layer_available| {
|
||||||
|
layer_available
|
||||||
|
.to_str()
|
||||||
|
.and_then(|layer_available| Ok(layer_available.eq(*layer_name)))
|
||||||
|
.unwrap_or(false)
|
||||||
|
}) {
|
||||||
|
CString::new(*layer_name).ok()
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
}
|
1
src/renderer/vulkan/utils/mod.rs
Normal file
1
src/renderer/vulkan/utils/mod.rs
Normal file
|
@ -0,0 +1 @@
|
||||||
|
pub mod layers;
|
37
src/renderer/vulkan/vertex.rs
Normal file
37
src/renderer/vulkan/vertex.rs
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
use ash::vk;
|
||||||
|
use std::mem::offset_of;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct Vertex {
|
||||||
|
pub position: [f32; 2],
|
||||||
|
pub color: [f32; 3],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Vertex {
|
||||||
|
pub fn new(position: [f32; 2], color: [f32; 3]) -> Self {
|
||||||
|
Self { position, color }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_binding_description() -> vk::VertexInputBindingDescription {
|
||||||
|
vk::VertexInputBindingDescription::default()
|
||||||
|
.binding(0)
|
||||||
|
.stride(size_of::<Self>() as u32)
|
||||||
|
.input_rate(vk::VertexInputRate::VERTEX)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_attribute_descriptions() -> [vk::VertexInputAttributeDescription; 2] {
|
||||||
|
let position_attribute = vk::VertexInputAttributeDescription::default()
|
||||||
|
.binding(0)
|
||||||
|
.location(0)
|
||||||
|
.format(vk::Format::R32G32_SFLOAT)
|
||||||
|
.offset(offset_of!(Vertex, position) as u32);
|
||||||
|
|
||||||
|
let color_attribute = vk::VertexInputAttributeDescription::default()
|
||||||
|
.binding(0)
|
||||||
|
.location(1)
|
||||||
|
.format(vk::Format::R32G32B32_SFLOAT)
|
||||||
|
.offset(offset_of!(Vertex, color) as u32);
|
||||||
|
|
||||||
|
[position_attribute, color_attribute]
|
||||||
|
}
|
||||||
|
}
|
31
src/renderer/vulkan/vk_buffer.rs
Normal file
31
src/renderer/vulkan/vk_buffer.rs
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
use ash::prelude::VkResult;
|
||||||
|
use ash::vk;
|
||||||
|
use crate::renderer::vulkan::VkDevice;
|
||||||
|
|
||||||
|
pub struct VkBuffer {
|
||||||
|
device: Arc<VkDevice>,
|
||||||
|
|
||||||
|
handle: vk::Buffer,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VkBuffer {
|
||||||
|
pub fn new(device: &Arc<VkDevice>, info: &vk::BufferCreateInfo) -> VkResult<Self> {
|
||||||
|
let buffer = unsafe { device.handle.create_buffer(info, None)? };
|
||||||
|
|
||||||
|
Ok(VkBuffer { device: Arc::clone(device), handle: buffer })
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn mem_requirements(&self) -> vk::MemoryRequirements {
|
||||||
|
unsafe { self.device.handle.get_buffer_memory_requirements(self.handle) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for VkBuffer {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe {
|
||||||
|
self.device.handle.destroy_buffer(self.handle, None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
52
src/renderer/vulkan/vk_command_pool.rs
Normal file
52
src/renderer/vulkan/vk_command_pool.rs
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
use super::VkDevice;
|
||||||
|
use ash::prelude::VkResult;
|
||||||
|
use ash::vk;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
pub struct VkCommandPool {
|
||||||
|
device: Arc<VkDevice>,
|
||||||
|
|
||||||
|
pub handle: vk::CommandPool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VkCommandPool {
|
||||||
|
pub fn new(device: &Arc<VkDevice>) -> VkResult<Self> {
|
||||||
|
let command_pool_info =
|
||||||
|
vk::CommandPoolCreateInfo::default()
|
||||||
|
.flags(vk::CommandPoolCreateFlags::RESET_COMMAND_BUFFER)
|
||||||
|
.queue_family_index(device.queue_family_index);
|
||||||
|
let command_pool = unsafe {
|
||||||
|
device
|
||||||
|
.handle
|
||||||
|
.create_command_pool(&command_pool_info, None)?
|
||||||
|
};
|
||||||
|
log::debug!("Command pool created ({command_pool:?})");
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
device: device.clone(),
|
||||||
|
handle: command_pool,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn allocate_command_buffers_for_framebuffers(&self, framebuffers_count: u32) -> VkResult<Vec<vk::CommandBuffer>> {
|
||||||
|
let command_buffer_info = vk::CommandBufferAllocateInfo::default()
|
||||||
|
.command_pool(self.handle)
|
||||||
|
.level(vk::CommandBufferLevel::PRIMARY)
|
||||||
|
.command_buffer_count(framebuffers_count);
|
||||||
|
|
||||||
|
let command_buffers = unsafe {
|
||||||
|
self.device
|
||||||
|
.handle
|
||||||
|
.allocate_command_buffers(&command_buffer_info)?
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(command_buffers)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for VkCommandPool {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe { self.device.handle.destroy_command_pool(self.handle, None) };
|
||||||
|
log::debug!("Command pool destroyed ({:?})", self.handle);
|
||||||
|
}
|
||||||
|
}
|
109
src/renderer/vulkan/vk_device.rs
Normal file
109
src/renderer/vulkan/vk_device.rs
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
use super::{VkInstance, VkPhysicalDevice};
|
||||||
|
use ash::prelude::VkResult;
|
||||||
|
use ash::vk;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
pub struct VkDevice {
|
||||||
|
instance: Arc<VkInstance>,
|
||||||
|
physical_device: Arc<VkPhysicalDevice>,
|
||||||
|
|
||||||
|
pub handle: ash::Device,
|
||||||
|
pub swapchain_loader: ash::khr::swapchain::Device,
|
||||||
|
pub queue_family_index: u32,
|
||||||
|
|
||||||
|
// Arc not used because vk::Queue is destroyed with Device automatically
|
||||||
|
// so any references of vk::Queue must be destroyed with VkDevice
|
||||||
|
queues: Vec<vk::Queue>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VkDevice {
|
||||||
|
pub fn new_graphics_device(
|
||||||
|
instance: &Arc<VkInstance>,
|
||||||
|
physical_device: &Arc<VkPhysicalDevice>,
|
||||||
|
queue_family_index: u32,
|
||||||
|
) -> anyhow::Result<Self> {
|
||||||
|
let device_extension_names_raw = [
|
||||||
|
ash::khr::swapchain::NAME.as_ptr(),
|
||||||
|
#[cfg(any(target_os = "macos", target_os = "ios"))]
|
||||||
|
ash::khr::portability_subset::NAME.as_ptr(),
|
||||||
|
];
|
||||||
|
let features = vk::PhysicalDeviceFeatures {
|
||||||
|
shader_clip_distance: 1,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let queues_priorities = [1.0];
|
||||||
|
let queue_info = vk::DeviceQueueCreateInfo::default()
|
||||||
|
.queue_family_index(queue_family_index)
|
||||||
|
.queue_priorities(&queues_priorities);
|
||||||
|
|
||||||
|
let device_create_info = vk::DeviceCreateInfo::default()
|
||||||
|
.queue_create_infos(std::slice::from_ref(&queue_info))
|
||||||
|
.enabled_extension_names(&device_extension_names_raw)
|
||||||
|
.enabled_features(&features);
|
||||||
|
|
||||||
|
let device = unsafe {
|
||||||
|
instance
|
||||||
|
.handle
|
||||||
|
.create_device(physical_device.handle, &device_create_info, None)?
|
||||||
|
};
|
||||||
|
log::debug!("Device created ({:?})", device.handle());
|
||||||
|
|
||||||
|
let queues = queues_priorities
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(index, _)| unsafe { device.get_device_queue(queue_family_index, index as u32) })
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let swapchain_loader = ash::khr::swapchain::Device::new(&instance.handle, &device);
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
instance: Arc::clone(instance),
|
||||||
|
physical_device: Arc::clone(&physical_device),
|
||||||
|
handle: device,
|
||||||
|
swapchain_loader,
|
||||||
|
queue_family_index,
|
||||||
|
queues,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_device_queue(&self, queue_index: u32) -> Option<&vk::Queue> {
|
||||||
|
self.queues.get(queue_index as usize)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_command_pool(
|
||||||
|
&self,
|
||||||
|
info: &vk::CommandPoolCreateInfo,
|
||||||
|
) -> VkResult<vk::CommandPool> {
|
||||||
|
let info = info.queue_family_index(self.queue_family_index);
|
||||||
|
|
||||||
|
unsafe { self.handle.create_command_pool(&info, None) }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn allocate_command_buffers(
|
||||||
|
&self,
|
||||||
|
info: &vk::CommandBufferAllocateInfo,
|
||||||
|
) -> VkResult<Vec<vk::CommandBuffer>> {
|
||||||
|
unsafe { self.handle.allocate_command_buffers(&info) }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_fence(&self, info: &vk::FenceCreateInfo) -> VkResult<vk::Fence> {
|
||||||
|
unsafe { self.handle.create_fence(&info, None) }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_semaphore(
|
||||||
|
&self,
|
||||||
|
info: &vk::SemaphoreCreateInfo,
|
||||||
|
) -> VkResult<vk::Semaphore> {
|
||||||
|
unsafe { self.handle.create_semaphore(&info, None) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for VkDevice {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe {
|
||||||
|
self.handle.destroy_device(None);
|
||||||
|
log::debug!("Device destroyed ({:?})", self.handle.handle());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
30
src/renderer/vulkan/vk_fence.rs
Normal file
30
src/renderer/vulkan/vk_fence.rs
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
use super::VkDevice;
|
||||||
|
use ash::vk;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
pub struct VkFence {
|
||||||
|
device: Arc<VkDevice>,
|
||||||
|
|
||||||
|
pub handle: vk::Fence,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VkFence {
|
||||||
|
pub fn new(device: &Arc<VkDevice>) -> anyhow::Result<Self> {
|
||||||
|
let fence_info = vk::FenceCreateInfo::default()
|
||||||
|
.flags(vk::FenceCreateFlags::SIGNALED);
|
||||||
|
let fence = unsafe { device.handle.create_fence(&fence_info, None)? };
|
||||||
|
log::debug!("Fence created ({fence:?})");
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
device: device.clone(),
|
||||||
|
handle: fence,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for VkFence {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe { self.device.handle.destroy_fence(self.handle, None) };
|
||||||
|
log::debug!("Fence destroyed ({:?})", self.handle);
|
||||||
|
}
|
||||||
|
}
|
44
src/renderer/vulkan/vk_framebuffer.rs
Normal file
44
src/renderer/vulkan/vk_framebuffer.rs
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
use super::{VkDevice, VkRenderPass, VkSwapchain};
|
||||||
|
use ash::vk;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
pub struct VkFramebuffer {
|
||||||
|
device: Arc<VkDevice>,
|
||||||
|
image_view: Arc<vk::ImageView>,
|
||||||
|
render_pass: Arc<VkRenderPass>,
|
||||||
|
|
||||||
|
pub handle: vk::Framebuffer,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VkFramebuffer {
|
||||||
|
pub fn from_swapchain_image_view(
|
||||||
|
device: &Arc<VkDevice>,
|
||||||
|
render_pass: &Arc<VkRenderPass>,
|
||||||
|
image_view: &Arc<vk::ImageView>,
|
||||||
|
swapchain: &VkSwapchain,
|
||||||
|
) -> anyhow::Result<Self> {
|
||||||
|
let attachments = [*image_view.as_ref()];
|
||||||
|
let framebuffer_info = vk::FramebufferCreateInfo::default()
|
||||||
|
.render_pass(render_pass.handle)
|
||||||
|
.width(swapchain.surface_resolution.width)
|
||||||
|
.height(swapchain.surface_resolution.height)
|
||||||
|
.attachments(&attachments)
|
||||||
|
.layers(1);
|
||||||
|
|
||||||
|
let framebuffer = unsafe { device.handle.create_framebuffer(&framebuffer_info, None)? };
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
device: device.clone(),
|
||||||
|
render_pass: render_pass.clone(),
|
||||||
|
image_view: image_view.clone(),
|
||||||
|
|
||||||
|
handle: framebuffer,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for VkFramebuffer {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe { self.device.handle.destroy_framebuffer(self.handle, None) };
|
||||||
|
}
|
||||||
|
}
|
120
src/renderer/vulkan/vk_graphics_pipeline.rs
Normal file
120
src/renderer/vulkan/vk_graphics_pipeline.rs
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
use super::{VkDevice, VkRenderPass, VkShaderModule, VkSwapchain};
|
||||||
|
use ash::vk;
|
||||||
|
use std::ffi::CStr;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
pub struct VkGraphicsPipeline {
|
||||||
|
device: Arc<VkDevice>,
|
||||||
|
render_pass: Arc<VkRenderPass>,
|
||||||
|
|
||||||
|
pub pipeline_layout: vk::PipelineLayout,
|
||||||
|
pub pipeline: vk::Pipeline,
|
||||||
|
vertex_shader: VkShaderModule,
|
||||||
|
fragment_shader: VkShaderModule,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VkGraphicsPipeline {
|
||||||
|
pub fn new(
|
||||||
|
device: &Arc<VkDevice>,
|
||||||
|
render_pass: &Arc<VkRenderPass>,
|
||||||
|
) -> anyhow::Result<Self> {
|
||||||
|
let shader_entry_name = CStr::from_bytes_with_nul(b"main\0")?;
|
||||||
|
|
||||||
|
let vert_shader_module =
|
||||||
|
VkShaderModule::from_spv_file(device, "res/shaders/main.vert.spv")?;
|
||||||
|
|
||||||
|
let vert_shader_info = vk::PipelineShaderStageCreateInfo::default()
|
||||||
|
.module(vert_shader_module.handle)
|
||||||
|
.name(shader_entry_name)
|
||||||
|
.stage(vk::ShaderStageFlags::VERTEX);
|
||||||
|
|
||||||
|
let frag_shader_module =
|
||||||
|
VkShaderModule::from_spv_file(device, "res/shaders/main.frag.spv")?;
|
||||||
|
|
||||||
|
let frag_shader_info = vk::PipelineShaderStageCreateInfo::default()
|
||||||
|
.module(frag_shader_module.handle)
|
||||||
|
.name(shader_entry_name)
|
||||||
|
.stage(vk::ShaderStageFlags::FRAGMENT);
|
||||||
|
|
||||||
|
let shader_stage_create_infos = [vert_shader_info, frag_shader_info];
|
||||||
|
|
||||||
|
let vertex_input_info = vk::PipelineVertexInputStateCreateInfo::default();
|
||||||
|
|
||||||
|
let input_assembly = vk::PipelineInputAssemblyStateCreateInfo::default()
|
||||||
|
.topology(vk::PrimitiveTopology::TRIANGLE_LIST);
|
||||||
|
|
||||||
|
let viewport_state = vk::PipelineViewportStateCreateInfo::default()
|
||||||
|
.viewport_count(1)
|
||||||
|
.scissor_count(1);
|
||||||
|
|
||||||
|
let rasterizer = vk::PipelineRasterizationStateCreateInfo::default()
|
||||||
|
.polygon_mode(vk::PolygonMode::FILL)
|
||||||
|
.cull_mode(vk::CullModeFlags::BACK)
|
||||||
|
.front_face(vk::FrontFace::CLOCKWISE)
|
||||||
|
.line_width(1.0);
|
||||||
|
|
||||||
|
let multisampling = vk::PipelineMultisampleStateCreateInfo::default()
|
||||||
|
.rasterization_samples(vk::SampleCountFlags::TYPE_1)
|
||||||
|
.min_sample_shading(1.0);
|
||||||
|
|
||||||
|
let color_blend_attachment = vk::PipelineColorBlendAttachmentState::default()
|
||||||
|
.color_write_mask(vk::ColorComponentFlags::RGBA);
|
||||||
|
|
||||||
|
let attachments = [color_blend_attachment];
|
||||||
|
let color_blending =
|
||||||
|
vk::PipelineColorBlendStateCreateInfo::default().attachments(&attachments);
|
||||||
|
|
||||||
|
let dynamic_state = vk::PipelineDynamicStateCreateInfo::default()
|
||||||
|
.dynamic_states(&[vk::DynamicState::VIEWPORT, vk::DynamicState::SCISSOR]);
|
||||||
|
|
||||||
|
let pipeline_layout_info = vk::PipelineLayoutCreateInfo::default();
|
||||||
|
let pipeline_layout = unsafe {
|
||||||
|
device
|
||||||
|
.handle
|
||||||
|
.create_pipeline_layout(&pipeline_layout_info, None)?
|
||||||
|
};
|
||||||
|
log::debug!("Pipeline layout created ({pipeline_layout:?})");
|
||||||
|
|
||||||
|
let pipeline_info = vk::GraphicsPipelineCreateInfo::default()
|
||||||
|
.stages(&shader_stage_create_infos)
|
||||||
|
.vertex_input_state(&vertex_input_info)
|
||||||
|
.input_assembly_state(&input_assembly)
|
||||||
|
.viewport_state(&viewport_state)
|
||||||
|
.rasterization_state(&rasterizer)
|
||||||
|
.multisample_state(&multisampling)
|
||||||
|
.color_blend_state(&color_blending)
|
||||||
|
.dynamic_state(&dynamic_state)
|
||||||
|
.layout(pipeline_layout)
|
||||||
|
.render_pass(render_pass.handle);
|
||||||
|
let pipeline = unsafe {
|
||||||
|
device
|
||||||
|
.handle
|
||||||
|
.create_graphics_pipelines(vk::PipelineCache::null(), &[pipeline_info], None)
|
||||||
|
.map_err(|(_, error)| error)?[0]
|
||||||
|
};
|
||||||
|
log::debug!("Pipeline created ({pipeline_layout:?})");
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
device: device.clone(),
|
||||||
|
render_pass: render_pass.clone(),
|
||||||
|
pipeline_layout,
|
||||||
|
pipeline,
|
||||||
|
vertex_shader: vert_shader_module,
|
||||||
|
fragment_shader: frag_shader_module,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for VkGraphicsPipeline {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe {
|
||||||
|
self.device.handle.destroy_pipeline(self.pipeline, None);
|
||||||
|
log::debug!("Pipeline destroyed ({:?})", self.pipeline);
|
||||||
|
|
||||||
|
self.device
|
||||||
|
.handle
|
||||||
|
.destroy_pipeline_layout(self.pipeline_layout, None);
|
||||||
|
log::debug!("Pipeline layout destroyed ({:?})", self.pipeline_layout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
128
src/renderer/vulkan/vk_instance.rs
Normal file
128
src/renderer/vulkan/vk_instance.rs
Normal file
|
@ -0,0 +1,128 @@
|
||||||
|
use crate::renderer::vulkan::{
|
||||||
|
utils::layers::{use_layers, LayersSelector},
|
||||||
|
VkPhysicalDevice,
|
||||||
|
};
|
||||||
|
use ash::khr::surface;
|
||||||
|
use ash::{vk, Entry, Instance};
|
||||||
|
use std::ffi::{c_char, CStr, CString};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
pub struct VkInstance {
|
||||||
|
pub entry: Entry,
|
||||||
|
pub handle: Instance,
|
||||||
|
pub surface_loader: surface::Instance,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VkInstance {
|
||||||
|
pub fn new(required_extensions: &Vec<*const c_char>) -> Self {
|
||||||
|
let entry = Entry::linked();
|
||||||
|
|
||||||
|
log::debug!("Initializing Vulkan instance");
|
||||||
|
|
||||||
|
if log::log_enabled!(log::Level::Debug) {
|
||||||
|
let layer_properties =
|
||||||
|
unsafe { entry.enumerate_instance_layer_properties() }.unwrap_or_default();
|
||||||
|
|
||||||
|
for layer_property in layer_properties {
|
||||||
|
let layer_extensions = unsafe {
|
||||||
|
entry.enumerate_instance_extension_properties(
|
||||||
|
layer_property.layer_name_as_c_str().ok(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.unwrap_or_default();
|
||||||
|
log::debug!("{layer_property:#?} {layer_extensions:#?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let required_extensions = required_extensions
|
||||||
|
.iter()
|
||||||
|
.map(|str| unsafe { CStr::from_ptr(*str) })
|
||||||
|
.map(|cstr| cstr.to_string_lossy())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
log::debug!(
|
||||||
|
"Required instance extensions: {}",
|
||||||
|
required_extensions.join(", ")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Layers
|
||||||
|
#[allow(unused)]
|
||||||
|
let mut layer_selector = LayersSelector::Nothing;
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
{
|
||||||
|
layer_selector = LayersSelector::SpecificLayers(vec![
|
||||||
|
"VK_LAYER_KHRONOS_validation",
|
||||||
|
"VK_LAYER_MANGOHUD_overlay_x86_64",
|
||||||
|
"VK_LAYER_NV_optimus",
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
let layers = use_layers(&entry, layer_selector);
|
||||||
|
|
||||||
|
{
|
||||||
|
let layers = layers
|
||||||
|
.iter()
|
||||||
|
.map(|layer| layer.to_string_lossy())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
log::debug!("Selected debug layers : {}", layers.join(", "))
|
||||||
|
}
|
||||||
|
|
||||||
|
let layers_raw = layers.iter().map(|s| s.as_ptr()).collect::<Vec<_>>();
|
||||||
|
|
||||||
|
// App Info
|
||||||
|
let app_name = CString::new("VulkanTriangle").unwrap();
|
||||||
|
let appinfo = vk::ApplicationInfo::default()
|
||||||
|
.application_name(app_name.as_c_str())
|
||||||
|
.application_version(0)
|
||||||
|
.engine_name(app_name.as_c_str())
|
||||||
|
.engine_version(0)
|
||||||
|
.api_version(vk::make_api_version(0, 1, 0, 0));
|
||||||
|
|
||||||
|
let create_flags = if cfg!(any(target_os = "macos", target_os = "ios")) {
|
||||||
|
vk::InstanceCreateFlags::ENUMERATE_PORTABILITY_KHR
|
||||||
|
} else {
|
||||||
|
vk::InstanceCreateFlags::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Instance Info
|
||||||
|
let create_info = vk::InstanceCreateInfo::default()
|
||||||
|
.application_info(&appinfo)
|
||||||
|
.enabled_layer_names(&layers_raw)
|
||||||
|
.enabled_extension_names(&required_extensions)
|
||||||
|
.flags(create_flags);
|
||||||
|
|
||||||
|
let instance: Instance = unsafe {
|
||||||
|
entry
|
||||||
|
.create_instance(&create_info, None)
|
||||||
|
.expect("Instance creation error")
|
||||||
|
};
|
||||||
|
|
||||||
|
let surface_loader = surface::Instance::new(&entry, &instance);
|
||||||
|
|
||||||
|
log::debug!("Vulkan instance created ({:?})", instance.handle());
|
||||||
|
|
||||||
|
Self {
|
||||||
|
entry,
|
||||||
|
handle: instance,
|
||||||
|
surface_loader,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_physical_devices(instance: &Arc<Self>) -> Vec<VkPhysicalDevice> {
|
||||||
|
let physical_devices = unsafe { instance.handle.enumerate_physical_devices() };
|
||||||
|
physical_devices
|
||||||
|
.unwrap_or_default()
|
||||||
|
.iter()
|
||||||
|
.map(|physical_device| VkPhysicalDevice::new(&instance, *physical_device))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for VkInstance {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe {
|
||||||
|
self.handle.destroy_instance(None);
|
||||||
|
}
|
||||||
|
log::debug!("Vulkan instance destroyed ({:?})", self.handle.handle());
|
||||||
|
}
|
||||||
|
}
|
85
src/renderer/vulkan/vk_physical_device.rs
Normal file
85
src/renderer/vulkan/vk_physical_device.rs
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
use super::{VkInstance, VkSurface};
|
||||||
|
use ash::vk;
|
||||||
|
|
||||||
|
pub struct VkPhysicalDevice {
|
||||||
|
instance: Arc<VkInstance>,
|
||||||
|
pub handle: vk::PhysicalDevice,
|
||||||
|
|
||||||
|
pub properties: vk::PhysicalDeviceProperties,
|
||||||
|
pub features: vk::PhysicalDeviceFeatures,
|
||||||
|
pub queue_family_properties: Vec<vk::QueueFamilyProperties>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VkPhysicalDevice {
|
||||||
|
pub fn new(instance: &Arc<VkInstance>, physical_device: vk::PhysicalDevice) -> Self {
|
||||||
|
log::debug!("New physical device");
|
||||||
|
let device_properties = unsafe { instance.handle.get_physical_device_properties(physical_device) };
|
||||||
|
log::debug!("{device_properties:#?}");
|
||||||
|
let device_features = unsafe { instance.handle.get_physical_device_features(physical_device) };
|
||||||
|
log::debug!("{device_features:#?}");
|
||||||
|
let device_queue_families =
|
||||||
|
unsafe { instance.handle.get_physical_device_queue_family_properties(physical_device) };
|
||||||
|
log::debug!("{device_queue_families:#?}");
|
||||||
|
|
||||||
|
Self {
|
||||||
|
instance: Arc::clone(instance),
|
||||||
|
handle: physical_device,
|
||||||
|
properties: device_properties,
|
||||||
|
features: device_features,
|
||||||
|
queue_family_properties: device_queue_families,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn find_queue_family_by(
|
||||||
|
&self,
|
||||||
|
queue_flags: Option<vk::QueueFlags>,
|
||||||
|
surface: Option<&VkSurface>,
|
||||||
|
) -> Option<(u32, &vk::QueueFamilyProperties)> {
|
||||||
|
self.queue_family_properties.iter().enumerate().find_map(
|
||||||
|
|(index, queue_family_property)| {
|
||||||
|
let surface_check_passed = match surface {
|
||||||
|
Some(surface) => surface
|
||||||
|
.physical_device_queue_supported(self, index as u32)
|
||||||
|
.unwrap_or(false),
|
||||||
|
None => true,
|
||||||
|
};
|
||||||
|
|
||||||
|
let queue_flags_check_passed = match queue_flags {
|
||||||
|
Some(queue_flags) => queue_family_property.queue_flags.contains(queue_flags),
|
||||||
|
None => true,
|
||||||
|
};
|
||||||
|
|
||||||
|
if surface_check_passed && queue_flags_check_passed {
|
||||||
|
Some((index as u32, queue_family_property))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pick_physical_device_and_queue_by<'a>(
|
||||||
|
physical_devices: &'a Vec<VkPhysicalDevice>,
|
||||||
|
queue_flags: Option<vk::QueueFlags>,
|
||||||
|
surface: Option<&VkSurface>,
|
||||||
|
) -> Option<(&'a VkPhysicalDevice, u32, &'a vk::QueueFamilyProperties)> {
|
||||||
|
physical_devices.iter().find_map(|physical_device| {
|
||||||
|
physical_device
|
||||||
|
.find_queue_family_by(queue_flags, surface)
|
||||||
|
.and_then(|(queue_index, queue_family_properties)| {
|
||||||
|
Some((physical_device, queue_index, queue_family_properties))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn priority(&self) -> usize {
|
||||||
|
match self.properties.device_type {
|
||||||
|
vk::PhysicalDeviceType::CPU => 1,
|
||||||
|
vk::PhysicalDeviceType::VIRTUAL_GPU => 2,
|
||||||
|
vk::PhysicalDeviceType::INTEGRATED_GPU => 3,
|
||||||
|
vk::PhysicalDeviceType::DISCRETE_GPU => 4,
|
||||||
|
_ => 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
206
src/renderer/vulkan/vk_render_context.rs
Normal file
206
src/renderer/vulkan/vk_render_context.rs
Normal file
|
@ -0,0 +1,206 @@
|
||||||
|
use super::{
|
||||||
|
VkCommandPool, VkDevice, VkFence, VkFramebuffer, VkGraphicsPipeline, VkInstance, VkPhysicalDevice,
|
||||||
|
VkRenderPass, VkSemaphore, VkSurface, VkSwapchain,
|
||||||
|
};
|
||||||
|
use ash::vk;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use crate::renderer::Renderable;
|
||||||
|
|
||||||
|
pub struct VkRenderContext {
|
||||||
|
instance: Arc<VkInstance>,
|
||||||
|
surface: Arc<VkSurface>,
|
||||||
|
device: Arc<VkDevice>,
|
||||||
|
|
||||||
|
swapchain: Arc<VkSwapchain>,
|
||||||
|
render_pass: Arc<VkRenderPass>,
|
||||||
|
framebuffers: Vec<Arc<VkFramebuffer>>,
|
||||||
|
|
||||||
|
command_pool: VkCommandPool,
|
||||||
|
command_buffers: Vec<vk::CommandBuffer>,
|
||||||
|
image_available_semaphore: VkSemaphore,
|
||||||
|
render_finished_semaphore: VkSemaphore,
|
||||||
|
in_flight_fence: VkFence,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VkRenderContext {
|
||||||
|
pub fn init(window: &crate::display::Window) -> anyhow::Result<Self> {
|
||||||
|
let required_extensions = window.required_extensions()?;
|
||||||
|
|
||||||
|
let instance = Arc::new(VkInstance::new(&required_extensions));
|
||||||
|
let surface = Arc::new(VkSurface::new(&window, instance.clone())?);
|
||||||
|
|
||||||
|
let mut physical_devices = VkInstance::get_physical_devices(&instance);
|
||||||
|
physical_devices.sort_by(|a, b| b.priority().cmp(&a.priority()));
|
||||||
|
|
||||||
|
let (physical_device, queue_family_index, properties) =
|
||||||
|
VkPhysicalDevice::pick_physical_device_and_queue_by(
|
||||||
|
&physical_devices,
|
||||||
|
Some(vk::QueueFlags::GRAPHICS),
|
||||||
|
Some(&surface),
|
||||||
|
)
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("Unable to find physical device"))?;
|
||||||
|
log::debug!(
|
||||||
|
"Selected queue {properties:#?} for physical device {:?}",
|
||||||
|
physical_device.properties.device_name_as_c_str()
|
||||||
|
);
|
||||||
|
|
||||||
|
let device = Arc::new(VkDevice::new_graphics_device(
|
||||||
|
&instance,
|
||||||
|
&physical_device,
|
||||||
|
queue_family_index,
|
||||||
|
)?);
|
||||||
|
|
||||||
|
let swapchain = Arc::new(VkSwapchain::new(
|
||||||
|
&window,
|
||||||
|
&surface,
|
||||||
|
&device,
|
||||||
|
&physical_device,
|
||||||
|
)?);
|
||||||
|
|
||||||
|
let render_pass = Arc::new(VkRenderPass::new(&device, &swapchain)?);
|
||||||
|
|
||||||
|
let framebuffers = swapchain
|
||||||
|
.create_framebuffers(&render_pass)
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("Failed to get framebuffers"))?;
|
||||||
|
|
||||||
|
let command_pool = VkCommandPool::new(&device)?;
|
||||||
|
|
||||||
|
// Destroyed with command pool
|
||||||
|
let command_buffers = command_pool
|
||||||
|
.allocate_command_buffers_for_framebuffers(framebuffers.len() as u32)?;
|
||||||
|
|
||||||
|
let image_available_semaphore = VkSemaphore::new(&device)?;
|
||||||
|
let render_finished_semaphore = VkSemaphore::new(&device)?;
|
||||||
|
let in_flight_fence = VkFence::new(&device)?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
instance,
|
||||||
|
surface,
|
||||||
|
device,
|
||||||
|
|
||||||
|
swapchain,
|
||||||
|
render_pass,
|
||||||
|
framebuffers,
|
||||||
|
|
||||||
|
command_pool,
|
||||||
|
command_buffers,
|
||||||
|
|
||||||
|
image_available_semaphore,
|
||||||
|
render_finished_semaphore,
|
||||||
|
in_flight_fence,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render(&mut self, scene: Option<&Box<dyn Renderable>>) -> anyhow::Result<()> {
|
||||||
|
unsafe { self.device.handle.wait_for_fences(&[self.in_flight_fence.handle], true, u64::MAX)? };
|
||||||
|
unsafe { self.device.handle.reset_fences(&[self.in_flight_fence.handle])? };
|
||||||
|
|
||||||
|
let (index, _) = self
|
||||||
|
.swapchain
|
||||||
|
.acquire_next_image(&self.image_available_semaphore)?;
|
||||||
|
|
||||||
|
// if self.swapchain.is_dirty() {
|
||||||
|
// self.update_swapchain()?
|
||||||
|
// }
|
||||||
|
|
||||||
|
let command_buffer = self.command_buffers[index as usize];
|
||||||
|
unsafe { self.device.handle.reset_command_buffer(command_buffer, vk::CommandBufferResetFlags::default())? };
|
||||||
|
|
||||||
|
let render_area = vk::Rect2D::default().extent(self.swapchain.surface_resolution);
|
||||||
|
let clear_value = vk::ClearValue::default();
|
||||||
|
let command_buffer_begin_info = vk::CommandBufferBeginInfo::default();
|
||||||
|
unsafe {
|
||||||
|
self.device
|
||||||
|
.handle
|
||||||
|
.begin_command_buffer(command_buffer, &command_buffer_begin_info)?
|
||||||
|
};
|
||||||
|
|
||||||
|
let clear_values = [clear_value];
|
||||||
|
let framebuffer = self.framebuffers[index as usize].as_ref();
|
||||||
|
let render_pass_begin_info = vk::RenderPassBeginInfo::default()
|
||||||
|
.render_pass(self.render_pass.handle)
|
||||||
|
.framebuffer(framebuffer.handle)
|
||||||
|
.render_area(render_area)
|
||||||
|
.clear_values(&clear_values);
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
self.device.handle.cmd_begin_render_pass(
|
||||||
|
command_buffer,
|
||||||
|
&render_pass_begin_info,
|
||||||
|
vk::SubpassContents::INLINE,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(scene) = scene {
|
||||||
|
scene.render(&self.device, &self.swapchain, &command_buffer)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe { self.device.handle.cmd_end_render_pass(command_buffer) };
|
||||||
|
|
||||||
|
unsafe { self.device.handle.end_command_buffer(command_buffer)? };
|
||||||
|
|
||||||
|
let wait_semaphores = [self.image_available_semaphore.handle];
|
||||||
|
let signal_semaphores = [self.render_finished_semaphore.handle];
|
||||||
|
let wait_stages = [vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT];
|
||||||
|
let command_buffers_to_submit = [command_buffer];
|
||||||
|
let submit_info = vk::SubmitInfo::default()
|
||||||
|
.wait_semaphores(&wait_semaphores)
|
||||||
|
.wait_dst_stage_mask(&wait_stages)
|
||||||
|
.command_buffers(&command_buffers_to_submit)
|
||||||
|
.signal_semaphores(&signal_semaphores);
|
||||||
|
|
||||||
|
let queue = self
|
||||||
|
.device
|
||||||
|
.get_device_queue(0)
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("Failed to get a queue"))?;
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
self.device
|
||||||
|
.handle
|
||||||
|
.queue_submit(*queue, &[submit_info], self.in_flight_fence.handle)?
|
||||||
|
};
|
||||||
|
|
||||||
|
let swapchains = [self.swapchain.handle.unwrap()];
|
||||||
|
let indices = [index];
|
||||||
|
let present_info = vk::PresentInfoKHR::default()
|
||||||
|
.wait_semaphores(&signal_semaphores)
|
||||||
|
.swapchains(&swapchains)
|
||||||
|
.image_indices(&indices);
|
||||||
|
|
||||||
|
unsafe { self.device.swapchain_loader.queue_present(*queue, &present_info)? };
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_resolution(&mut self, width: u32, height: u32) -> anyhow::Result<()> {
|
||||||
|
match Arc::get_mut(&mut self.swapchain) {
|
||||||
|
Some(swapchain) => swapchain.update_resolution(width, height)?,
|
||||||
|
None => log::warn!("Impossible to get mutable swapchain"),
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn exit(&self) {
|
||||||
|
unsafe { self.device.handle.device_wait_idle().unwrap() }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init_scene(&self, scene: &mut Box<dyn Renderable>) -> anyhow::Result<()> {
|
||||||
|
scene.init(&self.device, &self.render_pass)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_swapchain(&mut self) -> anyhow::Result<()> {
|
||||||
|
match Arc::get_mut(&mut self.swapchain) {
|
||||||
|
Some(swapchain) => {
|
||||||
|
swapchain.create_swapchain()?;
|
||||||
|
|
||||||
|
self.framebuffers = self.swapchain
|
||||||
|
.create_framebuffers(&self.render_pass)
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("Failed to get framebuffers"))?;
|
||||||
|
}
|
||||||
|
None => log::warn!("Impossible to get mutable swapchain"),
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
56
src/renderer/vulkan/vk_render_pass.rs
Normal file
56
src/renderer/vulkan/vk_render_pass.rs
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
use super::{VkDevice, VkSwapchain};
|
||||||
|
use ash::prelude::VkResult;
|
||||||
|
use ash::vk;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
pub struct VkRenderPass {
|
||||||
|
device: Arc<VkDevice>,
|
||||||
|
|
||||||
|
pub handle: vk::RenderPass,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VkRenderPass {
|
||||||
|
pub fn new(device: &Arc<VkDevice>, swapchain: &Arc<VkSwapchain>) -> VkResult<Self> {
|
||||||
|
let color_attachment = vk::AttachmentDescription::default()
|
||||||
|
.format(swapchain.surface_format.format)
|
||||||
|
.samples(vk::SampleCountFlags::TYPE_1)
|
||||||
|
.load_op(vk::AttachmentLoadOp::CLEAR)
|
||||||
|
.store_op(vk::AttachmentStoreOp::STORE)
|
||||||
|
.stencil_load_op(vk::AttachmentLoadOp::DONT_CARE)
|
||||||
|
.stencil_store_op(vk::AttachmentStoreOp::DONT_CARE)
|
||||||
|
.initial_layout(vk::ImageLayout::UNDEFINED)
|
||||||
|
.final_layout(vk::ImageLayout::PRESENT_SRC_KHR);
|
||||||
|
|
||||||
|
let color_attachment_ref = vk::AttachmentReference::default()
|
||||||
|
.attachment(0)
|
||||||
|
.layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL);
|
||||||
|
|
||||||
|
let color_attachments = [color_attachment_ref];
|
||||||
|
let subpass = vk::SubpassDescription::default()
|
||||||
|
.pipeline_bind_point(vk::PipelineBindPoint::GRAPHICS)
|
||||||
|
.color_attachments(&color_attachments);
|
||||||
|
|
||||||
|
let attachments = [color_attachment];
|
||||||
|
let subpasses = [subpass];
|
||||||
|
let render_pass_info = vk::RenderPassCreateInfo::default()
|
||||||
|
.attachments(&attachments)
|
||||||
|
.subpasses(&subpasses);
|
||||||
|
|
||||||
|
let render_pass = unsafe { device.handle.create_render_pass(&render_pass_info, None)? };
|
||||||
|
log::debug!("Render pass created ({render_pass:?})");
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
device: device.clone(),
|
||||||
|
handle: render_pass,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for VkRenderPass {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe {
|
||||||
|
self.device.handle.destroy_render_pass(self.handle, None);
|
||||||
|
log::debug!("Render pass destroyed ({:?})", self.handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
29
src/renderer/vulkan/vk_semaphore.rs
Normal file
29
src/renderer/vulkan/vk_semaphore.rs
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
use super::VkDevice;
|
||||||
|
use ash::vk;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
pub struct VkSemaphore {
|
||||||
|
device: Arc<VkDevice>,
|
||||||
|
|
||||||
|
pub handle: vk::Semaphore,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VkSemaphore {
|
||||||
|
pub fn new(device: &Arc<VkDevice>) -> anyhow::Result<Self> {
|
||||||
|
let semaphore_info = vk::SemaphoreCreateInfo::default();
|
||||||
|
let semaphore = unsafe { device.handle.create_semaphore(&semaphore_info, None)? };
|
||||||
|
log::debug!("Semaphore created ({semaphore:?})");
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
device: device.clone(),
|
||||||
|
handle: semaphore,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for VkSemaphore {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe { self.device.handle.destroy_semaphore(self.handle, None) };
|
||||||
|
log::debug!("Semaphore destroyed ({:?})", self.handle);
|
||||||
|
}
|
||||||
|
}
|
42
src/renderer/vulkan/vk_shader_module.rs
Normal file
42
src/renderer/vulkan/vk_shader_module.rs
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
use super::VkDevice;
|
||||||
|
use ash::vk;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
pub struct VkShaderModule {
|
||||||
|
device: Arc<VkDevice>,
|
||||||
|
|
||||||
|
pub handle: vk::ShaderModule,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VkShaderModule {
|
||||||
|
pub fn from_spv_file<P: AsRef<Path>>(device: &Arc<VkDevice>, path: P) -> anyhow::Result<Self> {
|
||||||
|
let mut file = std::fs::File::open(&path)?;
|
||||||
|
let frag_shader_str = ash::util::read_spv(&mut file)?;
|
||||||
|
|
||||||
|
let shader_create_info = vk::ShaderModuleCreateInfo::default().code(&frag_shader_str);
|
||||||
|
let shader_module = unsafe {
|
||||||
|
device
|
||||||
|
.handle
|
||||||
|
.create_shader_module(&shader_create_info, None)?
|
||||||
|
};
|
||||||
|
log::debug!(
|
||||||
|
"Shader module created ({shader_module:?}) from {:?}",
|
||||||
|
path.as_ref()
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
device: device.clone(),
|
||||||
|
handle: shader_module,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for VkShaderModule {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe {
|
||||||
|
self.device.handle.destroy_shader_module(self.handle, None);
|
||||||
|
log::debug!("Shader module destroyed ({:?})", self.handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
94
src/renderer/vulkan/vk_surface.rs
Normal file
94
src/renderer/vulkan/vk_surface.rs
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
use super::{VkInstance, VkPhysicalDevice};
|
||||||
|
use ash::prelude::VkResult;
|
||||||
|
use ash::vk;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use winit::raw_window_handle::{HasDisplayHandle, HasWindowHandle};
|
||||||
|
|
||||||
|
pub struct SwapchainSupportDetails(
|
||||||
|
pub Vec<vk::SurfaceFormatKHR>,
|
||||||
|
pub vk::SurfaceCapabilitiesKHR,
|
||||||
|
pub Vec<vk::PresentModeKHR>,
|
||||||
|
);
|
||||||
|
|
||||||
|
pub struct VkSurface {
|
||||||
|
instance: Arc<VkInstance>,
|
||||||
|
|
||||||
|
pub handle: vk::SurfaceKHR,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VkSurface {
|
||||||
|
pub fn new(window: &crate::display::Window, instance: Arc<VkInstance>) -> anyhow::Result<Self> {
|
||||||
|
let window_handle = window
|
||||||
|
.handle()
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("Window handle is not available."))?;
|
||||||
|
|
||||||
|
let surface = unsafe {
|
||||||
|
ash_window::create_surface(
|
||||||
|
&instance.entry,
|
||||||
|
&instance.handle,
|
||||||
|
window_handle.display_handle()?.as_raw(),
|
||||||
|
window_handle.window_handle()?.as_raw(),
|
||||||
|
None,
|
||||||
|
)?
|
||||||
|
};
|
||||||
|
|
||||||
|
log::debug!("Surface created ({:?})", surface);
|
||||||
|
|
||||||
|
Ok(Self { instance, handle: surface })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn physical_device_queue_supported(
|
||||||
|
&self,
|
||||||
|
physical_device: &VkPhysicalDevice,
|
||||||
|
queue_index: u32,
|
||||||
|
) -> VkResult<bool> {
|
||||||
|
unsafe {
|
||||||
|
self.instance
|
||||||
|
.surface_loader
|
||||||
|
.get_physical_device_surface_support(
|
||||||
|
physical_device.handle,
|
||||||
|
queue_index,
|
||||||
|
self.handle,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_physical_device_swapchain_support_details(
|
||||||
|
&self,
|
||||||
|
physical_device: &VkPhysicalDevice,
|
||||||
|
) -> VkResult<SwapchainSupportDetails> {
|
||||||
|
unsafe {
|
||||||
|
let formats = self
|
||||||
|
.instance
|
||||||
|
.surface_loader
|
||||||
|
.get_physical_device_surface_formats(physical_device.handle, self.handle)?;
|
||||||
|
|
||||||
|
let capabilities = self
|
||||||
|
.instance
|
||||||
|
.surface_loader
|
||||||
|
.get_physical_device_surface_capabilities(physical_device.handle, self.handle)?;
|
||||||
|
|
||||||
|
let present_modes = self
|
||||||
|
.instance
|
||||||
|
.surface_loader
|
||||||
|
.get_physical_device_surface_present_modes(physical_device.handle, self.handle)?;
|
||||||
|
|
||||||
|
Ok(SwapchainSupportDetails(
|
||||||
|
formats,
|
||||||
|
capabilities,
|
||||||
|
present_modes,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for VkSurface {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe {
|
||||||
|
self.instance
|
||||||
|
.surface_loader
|
||||||
|
.destroy_surface(self.handle, None);
|
||||||
|
}
|
||||||
|
log::debug!("Surface destroyed ({:?})", self.handle);
|
||||||
|
}
|
||||||
|
}
|
297
src/renderer/vulkan/vk_swapchain.rs
Normal file
297
src/renderer/vulkan/vk_swapchain.rs
Normal file
|
@ -0,0 +1,297 @@
|
||||||
|
use super::{SwapchainSupportDetails, VkDevice, VkFramebuffer, VkPhysicalDevice, VkRenderPass, VkSemaphore, VkSurface};
|
||||||
|
use crate::display::Window;
|
||||||
|
use ash::prelude::VkResult;
|
||||||
|
use ash::vk;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
pub struct VkSwapchain {
|
||||||
|
surface: Arc<VkSurface>,
|
||||||
|
device: Arc<VkDevice>,
|
||||||
|
|
||||||
|
pub handle: Option<vk::SwapchainKHR>,
|
||||||
|
swapchain_support_details: SwapchainSupportDetails,
|
||||||
|
|
||||||
|
pub desired_image_count: u32,
|
||||||
|
pub surface_format: vk::SurfaceFormatKHR,
|
||||||
|
pub surface_resolution: vk::Extent2D,
|
||||||
|
pub new_requested_surface_resolution: Option<vk::Extent2D>,
|
||||||
|
pub present_mode: vk::PresentModeKHR,
|
||||||
|
pub pre_transform: vk::SurfaceTransformFlagsKHR,
|
||||||
|
|
||||||
|
pub present_images: Option<Vec<vk::Image>>,
|
||||||
|
pub present_image_views: Option<Vec<Arc<vk::ImageView>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VkSwapchain {
|
||||||
|
pub fn new(
|
||||||
|
window: &Window,
|
||||||
|
surface: &Arc<VkSurface>,
|
||||||
|
device: &Arc<VkDevice>,
|
||||||
|
physical_device: &VkPhysicalDevice,
|
||||||
|
) -> anyhow::Result<Self> {
|
||||||
|
log::debug!("Creating swapchain");
|
||||||
|
|
||||||
|
let window_size = window
|
||||||
|
.physical_size::<u32>()
|
||||||
|
.and_then(|size| {
|
||||||
|
Some(vk::Extent2D {
|
||||||
|
width: size.width,
|
||||||
|
height: size.height,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("Failed to get swapchain extent"))?;
|
||||||
|
log::debug!("Window size ({}x{})", window_size.width, window_size.height);
|
||||||
|
|
||||||
|
let swapchain_support_details =
|
||||||
|
surface.get_physical_device_swapchain_support_details(physical_device)?;
|
||||||
|
let SwapchainSupportDetails(surface_formats, surface_capabilities, present_modes) =
|
||||||
|
&swapchain_support_details;
|
||||||
|
log::debug!("Supported surface formats by physical device: {surface_formats:#?}");
|
||||||
|
log::debug!("Surface capabilities: {surface_capabilities:#?}");
|
||||||
|
log::debug!("Present modes: {present_modes:#?}");
|
||||||
|
|
||||||
|
let surface_format = Self::choose_surface_format(surface_formats)
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("No available surface formats"))?;
|
||||||
|
let desired_image_count = Self::choose_desired_image_count(surface_capabilities);
|
||||||
|
let swapchain_extent = Self::choose_swapchain_extent(window_size, surface_capabilities);
|
||||||
|
let pre_transform = Self::choose_pre_transform(surface_capabilities);
|
||||||
|
let present_mode = Self::choose_present_mode(present_modes);
|
||||||
|
|
||||||
|
let mut swapchain = Self {
|
||||||
|
surface: surface.clone(),
|
||||||
|
device: device.clone(),
|
||||||
|
|
||||||
|
handle: None,
|
||||||
|
new_requested_surface_resolution: None,
|
||||||
|
swapchain_support_details,
|
||||||
|
desired_image_count,
|
||||||
|
surface_format,
|
||||||
|
surface_resolution: swapchain_extent,
|
||||||
|
present_mode,
|
||||||
|
pre_transform,
|
||||||
|
present_images: None,
|
||||||
|
present_image_views: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
swapchain.create_swapchain()?;
|
||||||
|
|
||||||
|
Ok(swapchain)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_swapchain(&mut self) -> VkResult<()> {
|
||||||
|
if let Some(new_requested_surface_resolution) = self.new_requested_surface_resolution {
|
||||||
|
self.surface_resolution = new_requested_surface_resolution;
|
||||||
|
self.new_requested_surface_resolution = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut swapchain_create_info = self.create_swapchain_info(&self.surface);
|
||||||
|
|
||||||
|
if let Some(old_swapchain) = self.handle {
|
||||||
|
swapchain_create_info.old_swapchain = old_swapchain;
|
||||||
|
}
|
||||||
|
|
||||||
|
let swapchain = unsafe {
|
||||||
|
self.device
|
||||||
|
.swapchain_loader
|
||||||
|
.create_swapchain(&swapchain_create_info, None)?
|
||||||
|
};
|
||||||
|
|
||||||
|
let present_images = unsafe {
|
||||||
|
self.device
|
||||||
|
.swapchain_loader
|
||||||
|
.get_swapchain_images(swapchain)?
|
||||||
|
};
|
||||||
|
let present_images_view = present_images
|
||||||
|
.iter()
|
||||||
|
.map(|i| {
|
||||||
|
self.create_present_image_view(*i)
|
||||||
|
.expect("Failed to create image view")
|
||||||
|
})
|
||||||
|
.map(|i| Arc::new(i))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
if log::log_enabled!(log::Level::Debug) {
|
||||||
|
let label = match self.handle {
|
||||||
|
None => "Swapchain created",
|
||||||
|
Some(_) => "Swapchain updated",
|
||||||
|
};
|
||||||
|
log::debug!("{label} ({swapchain:?}) : {swapchain_create_info:#?}");
|
||||||
|
}
|
||||||
|
|
||||||
|
self.handle = Some(swapchain);
|
||||||
|
self.present_image_views = Some(present_images_view);
|
||||||
|
self.present_images = Some(present_images);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_framebuffers(
|
||||||
|
&self,
|
||||||
|
render_pass: &Arc<VkRenderPass>,
|
||||||
|
) -> Option<Vec<Arc<VkFramebuffer>>> {
|
||||||
|
let present_image_views = self.present_image_views.as_ref()?;
|
||||||
|
|
||||||
|
Some(
|
||||||
|
present_image_views
|
||||||
|
.iter()
|
||||||
|
.map(|image_view| {
|
||||||
|
VkFramebuffer::from_swapchain_image_view(
|
||||||
|
&self.device,
|
||||||
|
&render_pass,
|
||||||
|
&image_view,
|
||||||
|
&self,
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
})
|
||||||
|
.map(|framebuffer| Arc::new(framebuffer))
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_resolution(&mut self, width: u32, height: u32) -> VkResult<()> {
|
||||||
|
log::debug!("New resolution requested ({width}x{height})");
|
||||||
|
|
||||||
|
let chosen_extent = Self::choose_swapchain_extent(
|
||||||
|
vk::Extent2D { width, height },
|
||||||
|
&self.swapchain_support_details.1,
|
||||||
|
);
|
||||||
|
if chosen_extent.width != self.surface_resolution.width
|
||||||
|
|| chosen_extent.height != self.surface_resolution.height
|
||||||
|
{
|
||||||
|
self.new_requested_surface_resolution = Some(chosen_extent);
|
||||||
|
log::debug!(
|
||||||
|
"New resolution submitted ({}x{})",
|
||||||
|
chosen_extent.width,
|
||||||
|
chosen_extent.height
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
log::debug!("New resolution skipped ({width}x{height}) : Same resolution");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn acquire_next_image(&self, semaphore: &VkSemaphore) -> VkResult<(u32, bool)> {
|
||||||
|
unsafe {
|
||||||
|
self.device.swapchain_loader.acquire_next_image(
|
||||||
|
self.handle.unwrap(),
|
||||||
|
u64::MAX,
|
||||||
|
semaphore.handle,
|
||||||
|
vk::Fence::null(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_dirty(&self) -> bool {
|
||||||
|
self.new_requested_surface_resolution.is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_swapchain_info(&self, surface: &VkSurface) -> vk::SwapchainCreateInfoKHR {
|
||||||
|
vk::SwapchainCreateInfoKHR::default()
|
||||||
|
.surface(surface.handle)
|
||||||
|
.min_image_count(self.desired_image_count)
|
||||||
|
.image_color_space(self.surface_format.color_space)
|
||||||
|
.image_format(self.surface_format.format)
|
||||||
|
.image_extent(self.surface_resolution)
|
||||||
|
.image_usage(vk::ImageUsageFlags::COLOR_ATTACHMENT)
|
||||||
|
.image_sharing_mode(vk::SharingMode::EXCLUSIVE)
|
||||||
|
.pre_transform(self.pre_transform)
|
||||||
|
.composite_alpha(vk::CompositeAlphaFlagsKHR::OPAQUE)
|
||||||
|
.present_mode(self.present_mode)
|
||||||
|
.clipped(true)
|
||||||
|
.image_array_layers(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn choose_swapchain_extent(
|
||||||
|
window_size: vk::Extent2D,
|
||||||
|
surface_capabilities: &vk::SurfaceCapabilitiesKHR,
|
||||||
|
) -> vk::Extent2D {
|
||||||
|
vk::Extent2D {
|
||||||
|
width: window_size
|
||||||
|
.width
|
||||||
|
.max(surface_capabilities.min_image_extent.width)
|
||||||
|
.min(surface_capabilities.max_image_extent.width),
|
||||||
|
height: window_size
|
||||||
|
.height
|
||||||
|
.max(surface_capabilities.min_image_extent.height)
|
||||||
|
.min(surface_capabilities.max_image_extent.height),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn choose_surface_format(
|
||||||
|
surface_formats: &Vec<vk::SurfaceFormatKHR>,
|
||||||
|
) -> Option<vk::SurfaceFormatKHR> {
|
||||||
|
surface_formats.first().and_then(|f| Some(*f))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn choose_desired_image_count(surface_capabilities: &vk::SurfaceCapabilitiesKHR) -> u32 {
|
||||||
|
let mut desired_image_count = surface_capabilities.min_image_count + 1;
|
||||||
|
if surface_capabilities.max_image_count > 0
|
||||||
|
&& desired_image_count > surface_capabilities.max_image_count
|
||||||
|
{
|
||||||
|
desired_image_count = surface_capabilities.max_image_count;
|
||||||
|
}
|
||||||
|
desired_image_count
|
||||||
|
}
|
||||||
|
|
||||||
|
fn choose_pre_transform(
|
||||||
|
surface_capabilities: &vk::SurfaceCapabilitiesKHR,
|
||||||
|
) -> vk::SurfaceTransformFlagsKHR {
|
||||||
|
if surface_capabilities
|
||||||
|
.supported_transforms
|
||||||
|
.contains(vk::SurfaceTransformFlagsKHR::IDENTITY)
|
||||||
|
{
|
||||||
|
vk::SurfaceTransformFlagsKHR::IDENTITY
|
||||||
|
} else {
|
||||||
|
surface_capabilities.current_transform
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn choose_present_mode(present_modes: &Vec<vk::PresentModeKHR>) -> vk::PresentModeKHR {
|
||||||
|
present_modes
|
||||||
|
.iter()
|
||||||
|
.cloned()
|
||||||
|
.find(|&mode| mode == vk::PresentModeKHR::MAILBOX)
|
||||||
|
.unwrap_or(vk::PresentModeKHR::FIFO)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_present_image_view(&self, image: vk::Image) -> VkResult<vk::ImageView> {
|
||||||
|
let create_view_info = vk::ImageViewCreateInfo::default()
|
||||||
|
.view_type(vk::ImageViewType::TYPE_2D)
|
||||||
|
.format(self.surface_format.format)
|
||||||
|
.components(vk::ComponentMapping {
|
||||||
|
r: vk::ComponentSwizzle::IDENTITY,
|
||||||
|
g: vk::ComponentSwizzle::IDENTITY,
|
||||||
|
b: vk::ComponentSwizzle::IDENTITY,
|
||||||
|
a: vk::ComponentSwizzle::IDENTITY,
|
||||||
|
})
|
||||||
|
.subresource_range(vk::ImageSubresourceRange {
|
||||||
|
aspect_mask: vk::ImageAspectFlags::COLOR,
|
||||||
|
base_mip_level: 0,
|
||||||
|
level_count: 1,
|
||||||
|
base_array_layer: 0,
|
||||||
|
layer_count: 1,
|
||||||
|
})
|
||||||
|
.image(image);
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
self.device
|
||||||
|
.handle
|
||||||
|
.create_image_view(&create_view_info, None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for VkSwapchain {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if let Some(swapchain) = self.handle {
|
||||||
|
unsafe {
|
||||||
|
self.device
|
||||||
|
.swapchain_loader
|
||||||
|
.destroy_swapchain(swapchain, None);
|
||||||
|
}
|
||||||
|
self.handle = None;
|
||||||
|
log::debug!("Swapchain destroyed ({swapchain:?})");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
4
src/scene/mod.rs
Normal file
4
src/scene/mod.rs
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
mod triangle;
|
||||||
|
mod vertex;
|
||||||
|
|
||||||
|
pub use triangle::TriangleScene;
|
49
src/scene/triangle.rs
Normal file
49
src/scene/triangle.rs
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
use crate::renderer::vulkan::{VkDevice, VkGraphicsPipeline, VkRenderPass, VkSwapchain};
|
||||||
|
use crate::renderer::Renderable;
|
||||||
|
use ash::vk;
|
||||||
|
use ash::vk::CommandBuffer;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
pub struct TriangleScene {
|
||||||
|
pipeline: Option<VkGraphicsPipeline>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TriangleScene {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self { pipeline: None }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Renderable for TriangleScene {
|
||||||
|
fn init(&mut self, device: &Arc<VkDevice>, render_pass: &Arc<VkRenderPass>) -> anyhow::Result<()> {
|
||||||
|
let pipeline = VkGraphicsPipeline::new(&device, &render_pass)?;
|
||||||
|
self.pipeline = Some(pipeline);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render(&self, device: &VkDevice, swapchain: &VkSwapchain, command_buffer: &CommandBuffer) -> anyhow::Result<()> {
|
||||||
|
unsafe {
|
||||||
|
device.handle.cmd_bind_pipeline(
|
||||||
|
*command_buffer,
|
||||||
|
vk::PipelineBindPoint::GRAPHICS,
|
||||||
|
self.pipeline.as_ref().unwrap().pipeline,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
let viewport = vk::Viewport::default()
|
||||||
|
.width(swapchain.surface_resolution.width as f32)
|
||||||
|
.height(swapchain.surface_resolution.height as f32)
|
||||||
|
.max_depth(1.0);
|
||||||
|
|
||||||
|
unsafe { device.handle.cmd_set_viewport(*command_buffer, 0, &[viewport]) }
|
||||||
|
|
||||||
|
let scissor = swapchain.surface_resolution.into();
|
||||||
|
|
||||||
|
unsafe { device.handle.cmd_set_scissor(*command_buffer, 0, &[scissor]) }
|
||||||
|
|
||||||
|
unsafe { device.handle.cmd_draw(*command_buffer, 3, 1, 0, 0) };
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
39
src/scene/vertex.rs
Normal file
39
src/scene/vertex.rs
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
use ash::prelude::VkResult;
|
||||||
|
use ash::vk;
|
||||||
|
use crate::renderer::vulkan::{Vertex, VkBuffer, VkDevice};
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct VertexScene {
|
||||||
|
vertices: Vec<Vertex>,
|
||||||
|
|
||||||
|
vertices_buffer: Option<VkBuffer>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VertexScene {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let vertices = vec![
|
||||||
|
Vertex::new([0.0, -0.5], [1.0, 0.0, 0.0]),
|
||||||
|
Vertex::new([0.5, 0.5], [0.0, 1.0, 0.0]),
|
||||||
|
Vertex::new([-0.5, 0.5], [0.0, 0.0, 1.0]),
|
||||||
|
];
|
||||||
|
|
||||||
|
Self {
|
||||||
|
vertices,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn create_buffer(&mut self, device: &Arc<VkDevice>) -> VkResult<()> {
|
||||||
|
let buffer_info = vk::BufferCreateInfo::default()
|
||||||
|
.usage(vk::BufferUsageFlags::VERTEX_BUFFER)
|
||||||
|
.sharing_mode(vk::SharingMode::EXCLUSIVE)
|
||||||
|
.size(self.vertices.len() as u64 * size_of::<Vertex>() as u64);
|
||||||
|
|
||||||
|
let buffer = VkBuffer::new(device, &buffer_info)?;
|
||||||
|
self.vertices_buffer = Some(buffer);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue