Compare commits

..

105 commits

Author SHA1 Message Date
77affedf93
Refactor + Begin add SceneSchedule 2025-06-13 13:27:34 +02:00
7b1373620c
Add docs about how use ash with vulkano 2025-06-13 12:20:20 +02:00
5d4048d9a7
create_entities: Remove from MainScene struct 2025-06-12 21:11:17 +02:00
51a3f812fe
Migrate Camera Resource to Component 2025-06-12 21:07:49 +02:00
07056fc0ce
Move input_manager and camera as resource 2025-06-12 19:36:49 +02:00
6ba61e040e
move pipeline loader in world 2025-06-12 14:24:11 +02:00
9c651c5e0a
Avoid use borrow_mut in with_renderer 2025-06-12 12:56:43 +02:00
6a0491fe51
Move texture loader in Resource 2025-06-12 12:56:04 +02:00
37467d5066
First entities render with ecs 2025-06-11 23:21:06 +02:00
9fabacffc9
Cleanup + Add velocity 2025-06-11 22:34:54 +02:00
c494574389
Move World into dedicated Scene struct 2025-06-11 22:24:28 +02:00
8ce620a74b
Integration of ECS pattern: Iteration 1 2025-06-11 16:05:35 +02:00
fc81f65a27
Rename load_pipelines by load_pending_pipelines 2025-06-09 21:21:55 +02:00
1f7bfd142c
Add record_commands 2025-06-09 21:07:17 +02:00
2300c25603
Use trait instead to get load_fn and add name for pipeline 2025-06-09 20:53:22 +02:00
6099a3e27f
Add pipeline loader 2025-06-09 20:42:35 +02:00
8b982ba089
Cleanup 2025-06-09 16:53:59 +02:00
c2b9c2363b
Avoid suffixe type by Type 2025-06-09 16:51:24 +02:00
cc64efd96f
Remove useless generics in buffer 2025-06-09 16:32:58 +02:00
50aabaa6ee
Remove all generics in AsRecordable 2025-06-09 16:26:14 +02:00
883014998f
Remove material_manager and resource
(Not used)
2025-06-09 16:13:32 +02:00
0174aeb60e
Reduce Generics in pipeline rendering 2025-06-09 16:12:21 +02:00
90a5b5d117
Fix typo 2025-06-08 18:48:12 +02:00
a32cf6c747
Refactor texture loading 2025-06-08 18:38:51 +02:00
f91c0792b2
Split record_render_commands and bind_commands 2025-06-08 16:42:22 +02:00
078e9daba9
Split Mesh data and Pipeline 2025-06-08 15:42:04 +02:00
4f96a1e4b5
Update transform 2025-06-07 21:13:31 +02:00
1a61aab218
Add traits 2025-06-07 20:25:45 +02:00
5539381f46
render: Add AsBindableDescriptorSet 2025-06-05 13:41:08 +02:00
b7bc6478e2
MaterialManager: Avoid some clone 2025-05-31 22:40:56 +02:00
1a071e44a9
MaterialManager: with O(n) complexity 2025-05-31 22:38:38 +02:00
5971c8cd5f
MaterialManager: first iteration 2025-05-31 21:53:20 +02:00
9d2a4410f0
input: Move from egui to std RwLock (mistake during use choice) 2025-05-31 13:41:53 +02:00
9d3800c718
virtual_input: Use RwLock instead of Mutex 2025-05-31 12:59:32 +02:00
45ccf030f6
app: refactor WindowContext name and creation 2025-05-31 12:56:00 +02:00
a293b962f7
use RefCell instead of RwLock
- Gui and VulkanoWindows is not thread safe
- It's more adapted with association with Rc
2025-05-31 12:40:19 +02:00
e5d8dd58f2
Add loading scene if not loaded on tracy
Some checks failed
Build legacy Nix package on Ubuntu / build (push) Failing after 11m9s
2025-05-30 23:35:13 +02:00
d765e78da4
Move to tracing with tracy support
Some checks failed
Build legacy Nix package on Ubuntu / build (push) Has been cancelled
2025-05-30 23:27:03 +02:00
8a57094478
app_context: Remove useless monitors video modes checking
Some checks failed
Build legacy Nix package on Ubuntu / build (push) Failing after 9m43s
2025-05-30 22:49:18 +02:00
1aa2dcc55d
Avoid to use Arc for not Send/Sync reference
Some checks failed
Build legacy Nix package on Ubuntu / build (push) Failing after 10m8s
2025-05-30 22:33:06 +02:00
1568223b9d
some cleanup from clippy
Some checks failed
Build legacy Nix package on Ubuntu / build (push) Failing after 9m20s
2025-05-30 22:21:34 +02:00
b1458785e5
Change Mutex by RwLock
Some checks failed
Build legacy Nix package on Ubuntu / build (push) Failing after 10m35s
2025-05-30 22:04:54 +02:00
bc42892d39
cleanup
Some checks failed
Build legacy Nix package on Ubuntu / build (push) Failing after 9m39s
2025-05-30 21:54:58 +02:00
2c169548b9
Refactor app context and render pass manager
Some checks failed
Build legacy Nix package on Ubuntu / build (push) Failing after 9m10s
2025-05-30 21:40:25 +02:00
f1ae54bc73
winit: Use user event
Some checks failed
Build legacy Nix package on Ubuntu / build (push) Failing after 8m47s
2025-05-29 21:38:07 +02:00
6a6b1821a4
depth: Fix not resized
Some checks failed
Build legacy Nix package on Ubuntu / build (push) Failing after 8m22s
2025-05-29 18:16:26 +02:00
650b61e3ae
render: add depth buffer
Some checks failed
Build legacy Nix package on Ubuntu / build (push) Failing after 8m13s
2025-05-29 17:44:00 +02:00
77c717f90b
Add instances support 2025-05-29 17:13:01 +02:00
f8b81f3269
camera: Change camera to Camera3D
Some checks failed
Build legacy Nix package on Ubuntu / build (push) Failing after 9m38s
2025-05-29 16:46:11 +02:00
3a562fb6eb
camera: Fix Y inverted
Some checks failed
Build legacy Nix package on Ubuntu / build (push) Has been cancelled
2025-05-29 16:38:45 +02:00
8b16def890
square: Fix indices
Some checks failed
Build legacy Nix package on Ubuntu / build (push) Failing after 8m11s
2025-05-29 16:09:04 +02:00
05532756cf
docs: Add opengl/vulkan diff 2025-05-29 16:08:41 +02:00
998aa68da1
camera: fix camera movement
Some checks failed
Build legacy Nix package on Ubuntu / build (push) Failing after 8m11s
2025-05-29 14:00:26 +02:00
f835941432
app: Move render_pass into scene
Some checks failed
Build legacy Nix package on Ubuntu / build (push) Failing after 8m8s
2025-05-29 00:17:21 +02:00
131811a539
pipeline: Refactor square pipeline + add support of indexes 2025-05-28 22:39:56 +02:00
122f577a26
texture: Let compiler type inference
Some checks failed
Build legacy Nix package on Ubuntu / build (push) Failing after 9m9s
2025-05-28 13:47:10 +02:00
fbb1493b45
Fixes
Some checks failed
Build legacy Nix package on Ubuntu / build (push) Has been cancelled
2025-05-28 13:41:13 +02:00
09bfe6fb48
camera: Try fix camera
Some checks failed
Build legacy Nix package on Ubuntu / build (push) Failing after 8m5s
2025-05-27 23:31:23 +02:00
a0fce9c08e
texture: Avoid clone image
Some checks failed
Build legacy Nix package on Ubuntu / build (push) Failing after 9m15s
2025-05-27 22:32:44 +02:00
29a4da5666
texture: First image load
Some checks failed
Build legacy Nix package on Ubuntu / build (push) Has been cancelled
2025-05-27 22:25:17 +02:00
5b0ab19207
input: Refactor all previous states
Some checks failed
Build legacy Nix package on Ubuntu / build (push) Failing after 7m48s
2025-05-27 19:44:21 +02:00
b0f82b0714
input: Add support for Axis, Mouse Button, MouseWheel
Some checks failed
Build legacy Nix package on Ubuntu / build (push) Failing after 7m50s
2025-05-27 19:11:28 +02:00
8c42e7b139
Refactor input
Some checks failed
Build legacy Nix package on Ubuntu / build (push) Failing after 7m43s
2025-05-27 17:13:22 +02:00
1976a8b53e
Refactor camera code
Some checks failed
Build legacy Nix package on Ubuntu / build (push) Failing after 7m57s
2025-05-26 22:53:32 +02:00
7401a9b5f3
First scene refactor working
Some checks failed
Build legacy Nix package on Ubuntu / build (push) Failing after 8m13s
2025-05-26 19:55:34 +02:00
5b74eef561
Push break job work [not work]
Some checks failed
Build legacy Nix package on Ubuntu / build (push) Failing after 7m55s
2025-05-26 16:46:26 +02:00
e58c22b531
remove held state in input.
Some checks failed
Build legacy Nix package on Ubuntu / build (push) Failing after 7m57s
- Winit not return event if multiple key as repeat event
2025-05-26 13:29:38 +02:00
1babc5bfeb
Add debug in gui
Some checks failed
Build legacy Nix package on Ubuntu / build (push) Failing after 10m16s
2025-05-26 00:04:46 +02:00
c4c691c4dd
Begin implement input management
Some checks failed
Build legacy Nix package on Ubuntu / build (push) Failing after 8m4s
2025-05-25 23:48:02 +02:00
2c3392c3ea
add EGUI
Some checks failed
Build legacy Nix package on Ubuntu / build (push) Failing after 2m41s
2025-05-25 22:36:27 +02:00
6dae0339db
Fix warning
Some checks failed
Build legacy Nix package on Ubuntu / build (push) Failing after 6m25s
2025-05-25 21:10:58 +02:00
a4a6c0c60a
Use vulkano_util instead
Some checks failed
Build legacy Nix package on Ubuntu / build (push) Failing after 6m30s
2025-05-25 19:48:59 +02:00
f486486be3
Remove ECS pattern: Split into new repo
Some checks failed
Build legacy Nix package on Ubuntu / build (push) Failing after 6m2s
2025-05-25 18:55:58 +02:00
d232706f68
engine_render: Resize swapchain
Some checks failed
Build legacy Nix package on Ubuntu / build (push) Failing after 9m7s
2025-05-25 18:05:20 +02:00
dbe415d262
engine_render: Add first render
Some checks failed
Build legacy Nix package on Ubuntu / build (push) Failing after 8m50s
2025-05-24 18:04:56 +02:00
15565d03c1
engine_vulkan: Fix allocate more queue as available and add different priorities
Some checks failed
Build legacy Nix package on Ubuntu / build (push) Failing after 8m56s
2025-05-24 17:02:01 +02:00
d10212ac3b
engine_vulkan: Fixes of queue allocation
Some checks failed
Build legacy Nix package on Ubuntu / build (push) Failing after 8m48s
- Avoid to allocate dedicated queue for presentation and use graphics queue for it
- Use queueCount of Queue Family Properties for queue support checking
2025-05-24 16:44:51 +02:00
4676b1b5b8
engine_vulkan: Refactor queue finding
Some checks failed
Build legacy Nix package on Ubuntu / build (push) Failing after 8m39s
2025-05-23 14:10:51 +02:00
9ea8721346
render_vulkan: Avoid continue loop if all queues is found
Some checks failed
Build legacy Nix package on Ubuntu / build (push) Failing after 8m36s
2025-05-21 13:46:13 +02:00
18174e42e9
render_vulkan: Fixes
- Avoid create device for each physical device during support checking
- Avoid to select different queue family index and add support of creating multiple queues on same queue family
- Log select queue family index for each queue type
2025-05-21 13:42:36 +02:00
7951b05ab3
Add README.md
Some checks failed
Build legacy Nix package on Ubuntu / build (push) Failing after 9m5s
2025-05-19 23:30:35 +02:00
62d12f2ab8
render_plugin: Autocreate swapchain and update
Some checks failed
Build legacy Nix package on Ubuntu / build (push) Failing after 9m45s
2025-05-18 21:00:22 +02:00
ae0a2be097
render_plugin: Begin add window plugin
Some checks failed
Build legacy Nix package on Ubuntu / build (push) Failing after 3m7s
2025-05-18 19:28:34 +02:00
0ee29a3649
render_plugin: Add first SubApp and default schedules
Some checks failed
Build legacy Nix package on Ubuntu / build (push) Failing after 7m49s
2025-05-18 18:02:54 +02:00
f585ba78e7
Split vulkan resources
Some checks failed
Build legacy Nix package on Ubuntu / build (push) Failing after 7m49s
2025-05-18 17:06:59 +02:00
b977f446d3
Split crates
Some checks failed
Build legacy Nix package on Ubuntu / build (push) Failing after 7m49s
2025-05-18 13:15:29 +02:00
99be029ff8
First vulkan init working 2025-05-18 12:41:25 +02:00
6639f0bb1e
Init plugins + first system
Some checks failed
Build legacy Nix package on Ubuntu / build (push) Failing after 5m40s
2025-05-16 14:22:18 +02:00
dda368e802
Update all dependencies
Some checks failed
Build legacy Nix package on Ubuntu / build (push) Failing after 5m39s
2025-05-16 13:47:24 +02:00
285b194280
Surface: Add required extensions
Some checks failed
Build legacy Nix package on Ubuntu / build (push) Failing after 25m20s
2025-04-27 17:16:50 +02:00
a295093c97
Use bevy_app instead
Some checks failed
Build legacy Nix package on Ubuntu / build (push) Failing after 25m2s
2025-04-24 13:05:38 +02:00
8b0c59f7c0
Remove useless schedule (for now)
Some checks failed
Build legacy Nix package on Ubuntu / build (push) Failing after 26m39s
2025-04-13 20:05:17 +02:00
a04c769438
Begin add Window Render Context
Some checks failed
Build legacy Nix package on Ubuntu / build (push) Failing after 26m40s
2025-04-13 19:23:05 +02:00
e2616a0ef5
Add vulkan creation from resources
Some checks failed
Build legacy Nix package on Ubuntu / build (push) Failing after 21m47s
2025-04-13 18:45:33 +02:00
4f6216635f
Update
Some checks failed
Build legacy Nix package on Ubuntu / build (push) Failing after 26m34s
2025-04-13 18:06:18 +02:00
df99ef3a3f
Add AppError
Some checks failed
Build legacy Nix package on Ubuntu / build (push) Failing after 21m29s
2025-04-13 16:49:07 +02:00
f4694157ab
Update
Some checks failed
Build legacy Nix package on Ubuntu / build (push) Has been cancelled
2025-04-13 16:35:21 +02:00
b361965033
Update
Some checks failed
Build legacy Nix package on Ubuntu / build (push) Failing after 25m31s
2025-04-07 22:51:49 +02:00
1d333b633b
Push lunch break code
Some checks failed
Build legacy Nix package on Ubuntu / build (push) Failing after 25m26s
2025-04-07 17:03:00 +02:00
9664ea754c
flake: Fix missing libstdc++
Some checks failed
Build legacy Nix package on Ubuntu / build (push) Failing after 25m19s
2025-04-07 13:11:19 +02:00
852d72d716
Begin move mesh + Vertex and Camera into core
Some checks failed
Build legacy Nix package on Ubuntu / build (push) Failing after 20m30s
2025-04-04 13:38:27 +02:00
2fbf4e6ce2
Split pick_graphics_device
Some checks failed
Build legacy Nix package on Ubuntu / build (push) Failing after 26m24s
2025-04-03 21:10:08 +02:00
15c273b93d
Split app, window render context and vulkan context
Some checks failed
Build legacy Nix package on Ubuntu / build (push) Failing after 26m52s
2025-04-03 19:59:10 +02:00
f32db72101
rename renderer to vulkan
Some checks failed
Build legacy Nix package on Ubuntu / build (push) Failing after 20m44s
2025-04-03 18:38:17 +02:00
6bc3dbd53d
Migrate to bevy_ecs for more up to date ECS crates
Some checks failed
Build legacy Nix package on Ubuntu / build (push) Failing after 28m19s
2025-04-01 23:49:40 +02:00
75 changed files with 7032 additions and 1389 deletions

View file

@ -1,10 +0,0 @@
{
"mcpServers": {
"run-program": {
"command": "cargo",
"args": [
"run"
]
}
}
}

2335
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -7,17 +7,30 @@ publish = false
[dependencies]
anyhow = "1.0"
thiserror = "2.0"
winit = { version = "0.30", features = ["rwh_06"] }
vulkano = "0.35"
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" }
# ECS
apecs = "0.8"
# Log and tracing
log = "0.4"
env_logger = "0.11.5"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt"] }
tracing-log = "0.2"
tracing-tracy = "0.11"
# Random
rand = "0.9"
# OBJ loader
tobj = "4.0"
# ECS
bevy_ecs = "0.16.1" # Latest version

17
README.md Normal file
View file

@ -0,0 +1,17 @@
# 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

View file

@ -0,0 +1,11 @@
# Difference between Vulkan and OpenGL
Viewport:
- Y axis is flipped like D3D
- Clipped Z axis is not [-1; 1] but [0; 1]
![normalized viewport coordinates](./images/normalized_device_coordinates.svg)
![coord_sys](./images/coord_sys.png)
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)

15
docs/USE_ASH_METHODS.md Normal file
View file

@ -0,0 +1,15 @@
# Informations
```rust
let fns = vulkano_context.instance().fns();
let mut props = ash::vk::PhysicalDeviceProperties::default();
unsafe {
(fns.v1_0.get_physical_device_properties)(
vulkano_context.device().physical_device().handle(),
&mut props,
);
}
println!("{:?}", props);
```

BIN
docs/images/coord_sys.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View file

@ -0,0 +1,219 @@
<?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>

After

Width:  |  Height:  |  Size: 8.8 KiB

12
flake.lock generated
View file

@ -44,11 +44,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1742546557,
"narHash": "sha256-QyhimDBaDBtMfRc7kyL28vo+HTwXRPq3hz+BgSJDotw=",
"lastModified": 1747312588,
"narHash": "sha256-MmJvj6mlWzeRwKGLcwmZpKaOPZ5nJb/6al5CXqJsgjo=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "bfa9810ff7104a17555ab68ebdeafb6705f129b1",
"rev": "b1bebd0fe266bbd1820019612ead889e96a8fa2d",
"type": "github"
},
"original": {
@ -73,11 +73,11 @@
]
},
"locked": {
"lastModified": 1742524367,
"narHash": "sha256-KzTwk/5ETJavJZYV1DEWdCx05M4duFCxCpRbQSKWpng=",
"lastModified": 1747363019,
"narHash": "sha256-N4dwkRBmpOosa4gfFkFf/LTD8oOcNkAyvZ07JvRDEf0=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "70bf752d176b2ce07417e346d85486acea9040ef",
"rev": "0e624f2b1972a34be1a9b35290ed18ea4b419b6f",
"type": "github"
},
"original": {

View file

@ -31,18 +31,15 @@
cargo = rust;
});
renderdoc = pkgs.renderdoc.overrideAttrs (oldAttrs: {
cmakeFlags = oldAttrs.cmakeFlags ++ [
(pkgs.lib.cmakeBool "ENABLE_UNSUPPORTED_EXPERIMENTAL_POSSIBLY_BROKEN_WAYLAND" true)
];
});
buildInputs = with pkgs; [ vulkan-headers vulkan-loader vulkan-validation-layers renderdoc ]
buildInputs = with pkgs; [ vulkan-headers vulkan-loader vulkan-validation-layers renderdoc tracy ]
++ pkgs.lib.optionals pkgs.stdenv.hostPlatform.isLinux (with pkgs; [
stdenv.cc.cc.lib
# Wayland
libxkbcommon
wayland
libGL
# Xorg
xorg.libX11
xorg.libXcursor
@ -60,7 +57,7 @@
mkCustomShell = { packages ? [ ] }: pkgs.mkShell {
nativeBuildInputs = [
renderdoc
pkgs.renderdoc
(rust.override { extensions = [ "rust-src" "rust-analyzer" ]; })
] ++ nativeBuildInputs;
@ -68,8 +65,8 @@
++ packages;
LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath buildInputs;
VK_LAYER_PATH = "${pkgs.vulkan-validation-layers}/share/vulkan/explicit_layer.d:${renderdoc}/share/vulkan/implicit_layer.d";
RUST_LOG = "info,rust_vulkan_test=trace";
VK_LAYER_PATH = "${pkgs.vulkan-validation-layers}/share/vulkan/explicit_layer.d";
RUST_LOG = "debug,rust_vulkan_test=trace";
};
in
{
@ -80,7 +77,7 @@
packages = {
default = rustPlatform.buildRustPackage {
pname = "rust_ash_test";
pname = "vulkan_test";
version = "0.1.0";
src = self;

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

BIN
res/objects/cube-normal.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

1143
res/objects/cube.obj Normal file

File diff suppressed because it is too large Load diff

12
res/shaders/simple.frag Normal file
View file

@ -0,0 +1,12 @@
#version 450
layout (location = 0) in vec2 tex_coords;
layout (location = 0) out vec4 f_color;
layout(set = 1, binding = 0) uniform sampler mySampler;
layout(set = 1, binding = 1) uniform texture2D myTexture;
void main() {
f_color = texture(sampler2D(myTexture, mySampler), tex_coords);
}

20
res/shaders/simple.vert Normal file
View file

@ -0,0 +1,20 @@
#version 450
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;
}

View file

@ -1,9 +0,0 @@
#version 450
layout (location = 0) in vec3 color;
layout (location = 0) out vec4 f_color;
void main() {
f_color = vec4(color, 1.0);
}

View file

@ -1 +0,0 @@
#version 450 layout (location = 0) in vec2 position; layout (location = 1) in vec3 color; layout (location = 0) out vec3 fragColor; layout (set = 0, binding = 0) uniform MVPData { mat4 world; mat4 view; mat4 projection; } uniforms; void main() { mat4 worldview = uniforms.view * uniforms.world; gl_Position = uniforms.projection * worldview * vec4(position, 0.0, 1.0); fragColor = color; }

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

View file

@ -1,2 +1,2 @@
[toolchain]
channel = "1.85.1"
channel = "1.87.0"

135
src/core/app/context.rs Normal file
View file

@ -0,0 +1,135 @@
use std::{
cell::RefCell,
rc::Rc,
sync::{Arc, RwLock},
};
use egui_winit_vulkano::Gui;
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};
use super::user_event::UserEvent;
/// Contexte d'application unifié pour les fonctions liées à la fenêtre
pub struct WindowContext {
// Données Vulkan (immutables)
pub vulkan_context: Arc<VulkanContext>,
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 gui: Rc<RefCell<Gui>>,
}
impl WindowContext {
pub fn new(
vulkan_context: Arc<VulkanContext>,
vulkano_windows: Rc<RefCell<VulkanoWindows>>,
input_manager: Arc<RwLock<InputManager>>,
gui: Rc<RefCell<Gui>>,
event_loop_proxy: EventLoopProxy<UserEvent>,
window_id: WindowId,
) -> Self {
Self {
// Données Vulkan
vulkan_context,
event_loop_proxy,
window_id,
// Données mutables partagées
vulkano_windows,
input_manager,
gui,
}
}
pub fn vulkan_context(&self) -> &VulkanContext {
&self.vulkan_context
}
/// 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 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();
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)
}
}

253
src/core/app/mod.rs Normal file
View file

@ -0,0 +1,253 @@
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::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>>,
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(),
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)));
// Create the WindowContext with simplified arguments
let app_context = Rc::new(RefCell::new(WindowContext::new(
self.vulkan_context.clone(),
self.vulkano_windows.clone(),
self.input_manager.clone(),
self.gui.get(&window_id).unwrap().clone(),
self.event_loop_proxy.clone(),
window_id,
)));
self.app_contexts.insert(window_id, app_context.clone());
// Now use the created context to load the scene
let mut scene_manager = SceneManager::new();
scene_manager.set_new_scene(Box::new(MainScene::default()));
self.scene_manager.insert(window_id, scene_manager);
}
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();
}
// 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(&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.set_new_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();
}
}
}
}

View file

@ -0,0 +1,11 @@
use winit::window::{CursorGrabMode, WindowId};
use crate::core::scene::AsScene;
pub enum UserEvent {
CursorGrabMode(WindowId, CursorGrabMode),
CursorVisible(WindowId, bool),
ChangeScene(WindowId, Box<dyn AsScene>),
ChangeResolution(WindowId, f32, f32),
Exit(WindowId),
}

86
src/core/input/cache.rs Normal file
View file

@ -0,0 +1,86 @@
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;
}
}

104
src/core/input/mod.rs Normal file
View file

@ -0,0 +1,104 @@
use std::{
collections::HashMap,
sync::{Arc, RwLock},
};
use bevy_ecs::resource::Resource;
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,
}
#[derive(Resource)]
pub struct InputManagerResource(pub Arc<RwLock<InputManager>>);
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);
}
}

View file

@ -0,0 +1,30 @@
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),
}

View file

@ -0,0 +1,150 @@
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);
}
}
}
}

View file

@ -0,0 +1,105 @@
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 }
}

4
src/core/mod.rs Normal file
View file

@ -0,0 +1,4 @@
pub mod app;
pub mod input;
pub mod render;
pub mod scene;

4
src/core/render/mod.rs Normal file
View file

@ -0,0 +1,4 @@
pub mod primitives;
pub mod render_pass_manager;
pub mod resources;
pub mod vulkan_context;

View file

@ -0,0 +1,84 @@
use std::{error::Error, sync::Arc};
use vulkano::{
Validated,
buffer::{AllocateBufferError, Buffer, BufferContents, BufferCreateInfo, Subbuffer},
memory::allocator::{AllocationCreateInfo, StandardMemoryAllocator},
};
pub trait AsBindableBuffer {
type BufferData: BufferContents + Clone;
fn buffer_create_info() -> BufferCreateInfo;
fn allocation_create_info() -> AllocationCreateInfo;
fn to_buffer_data(&self) -> Self::BufferData;
fn create_buffer(
&self,
memory_allocator: &Arc<StandardMemoryAllocator>,
) -> Result<Subbuffer<[Self::BufferData]>, Validated<AllocateBufferError>> {
Buffer::from_iter(
memory_allocator.clone(),
Self::buffer_create_info(),
Self::allocation_create_info(),
[self.to_buffer_data()],
)
}
fn update_buffer(&self, buffer: &Subbuffer<[Self::BufferData]>) -> Result<(), Box<dyn Error>> {
let mut write_guard = buffer.write()?;
write_guard[0] = self.to_buffer_data();
Ok(())
}
}
pub trait AsUniformBuffer: AsBindableBuffer {
fn create_uniform_buffer(
&self,
memory_allocator: &Arc<StandardMemoryAllocator>,
) -> Result<Subbuffer<[Self::BufferData]>, Validated<AllocateBufferError>> {
self.create_buffer(memory_allocator)
}
}
pub trait AsVertexBuffer: AsBindableBuffer
where
Self: Sized,
{
fn create_vertex_buffer(
memory_allocator: &Arc<StandardMemoryAllocator>,
vertices: &[Self],
) -> Result<Subbuffer<[Self::BufferData]>, Validated<AllocateBufferError>> {
let buffer_data: Vec<Self::BufferData> =
vertices.iter().map(|v| v.to_buffer_data()).collect();
Buffer::from_iter(
memory_allocator.clone(),
Self::buffer_create_info(),
Self::allocation_create_info(),
buffer_data,
)
}
}
pub trait AsIndexBuffer: AsBindableBuffer
where
Self: Sized,
{
fn create_index_buffer(
memory_allocator: &Arc<StandardMemoryAllocator>,
indices: &[Self],
) -> Result<Subbuffer<[Self::BufferData]>, Validated<AllocateBufferError>> {
let buffer_data: Vec<Self::BufferData> =
indices.iter().map(|i| i.to_buffer_data()).collect();
Buffer::from_iter(
memory_allocator.clone(),
Self::buffer_create_info(),
Self::allocation_create_info(),
buffer_data,
)
}
}

View file

@ -0,0 +1,81 @@
use std::sync::Arc;
use bevy_ecs::component::Component;
use glam::{Mat4, Vec3, Vec4};
use vulkano::{
Validated,
buffer::{AllocateBufferError, Subbuffer},
memory::allocator::StandardMemoryAllocator,
};
use super::{AsUniformBuffer, 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(Component)]
pub struct Camera3D {
projection: Mat4,
aspect_ratio: f32,
fov: f32,
near: f32,
far: f32,
}
#[derive(Component)]
pub struct Camera3DTransform {
pub position: Vec3,
pub rotation: Vec3,
}
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),
aspect_ratio,
fov,
near,
far,
}
}
pub fn update_projection(&mut self, window_aspect_ratio: f32) {
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 create_buffer(
&self,
transform: &Camera3DTransform,
memory_allocator: &Arc<StandardMemoryAllocator>,
) -> Result<Subbuffer<[Mvp]>, Validated<AllocateBufferError>> {
let (sin_pitch, cos_pitch) = transform.rotation.x.sin_cos();
let (sin_yaw, cos_yaw) = transform.rotation.y.sin_cos();
let view_matrix = Mat4::look_to_rh(
transform.position,
Vec3::new(cos_pitch * cos_yaw, sin_pitch, cos_pitch * sin_yaw).normalize(),
Vec3::Y,
);
let mvp = 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(),
};
mvp.create_uniform_buffer(memory_allocator)
}
}

View file

@ -0,0 +1,113 @@
use std::{error::Error, sync::Arc};
use vulkano::{
buffer::{BufferContents, IndexBuffer, Subbuffer},
command_buffer::{AutoCommandBufferBuilder, PrimaryAutoCommandBuffer},
descriptor_set::allocator::StandardDescriptorSetAllocator,
pipeline::GraphicsPipeline,
};
use super::AsDescriptorSet;
pub trait AsRecordable {
fn record_bind_commands(
builder: &mut AutoCommandBufferBuilder<PrimaryAutoCommandBuffer>,
descriptor_set_allocator: &Arc<StandardDescriptorSetAllocator>,
pipeline: &Arc<GraphicsPipeline>,
mesh: &impl AsRenderableMesh,
instances: &impl AsRenderableMeshInstance,
descriptor_sets: Vec<Arc<dyn AsDescriptorSet>>,
) -> Result<(), Box<dyn Error>>;
fn record_draw_commands(
builder: &mut AutoCommandBufferBuilder<PrimaryAutoCommandBuffer>,
mesh: &impl AsRenderableMesh,
instances: &impl AsRenderableMeshInstance,
) -> Result<(), Box<dyn Error>> {
match mesh.index_buffer() {
Some(index_buffer) => {
builder.bind_index_buffer(index_buffer.clone())?;
unsafe {
builder.draw_indexed(
mesh.index_count(),
instances.instance_count(),
mesh.first_index(),
mesh.vertex_offset(),
instances.first_instance(),
)?;
}
}
None => unsafe {
builder.draw(
mesh.vertex_count(),
instances.instance_count(),
mesh.first_vertex(),
instances.first_instance(),
)?;
},
}
Ok(())
}
fn record_commands(
builder: &mut AutoCommandBufferBuilder<PrimaryAutoCommandBuffer>,
descriptor_set_allocator: &Arc<StandardDescriptorSetAllocator>,
pipeline: &Arc<GraphicsPipeline>,
mesh: &impl AsRenderableMesh,
instances: &impl AsRenderableMeshInstance,
descriptor_sets: Vec<Arc<dyn AsDescriptorSet>>,
) -> Result<(), Box<dyn Error>> {
Self::record_bind_commands(
builder,
descriptor_set_allocator,
pipeline,
mesh,
instances,
descriptor_sets,
)?;
Self::record_draw_commands(builder, mesh, instances)?;
Ok(())
}
}
pub trait AsRenderableMesh {
type VertexBufferData: BufferContents + Clone;
type IndexBuffer: Into<IndexBuffer> + Clone;
fn vertex_buffer(&self) -> &Subbuffer<[Self::VertexBufferData]>;
fn vertex_count(&self) -> u32;
fn first_vertex(&self) -> u32 {
0
}
fn vertex_offset(&self) -> i32 {
0
}
fn index_buffer(&self) -> Option<&Self::IndexBuffer> {
None
}
fn index_count(&self) -> u32 {
0
}
fn first_index(&self) -> u32 {
0
}
}
pub trait AsRenderableMeshInstance {
type InstanceBufferData: BufferContents + Clone;
fn instance_buffer(&self) -> &Subbuffer<[Self::InstanceBufferData]>;
fn instance_count(&self) -> u32;
fn first_instance(&self) -> u32 {
0
}
}

View file

@ -0,0 +1,22 @@
use std::{collections::BTreeMap, sync::Arc};
use vulkano::{
Validated, VulkanError,
descriptor_set::{
DescriptorSet,
allocator::StandardDescriptorSetAllocator,
layout::{DescriptorSetLayout, DescriptorSetLayoutBinding},
},
};
pub trait AsDescriptorSetLayoutBindings {
fn as_descriptor_set_layout_bindings() -> BTreeMap<u32, DescriptorSetLayoutBinding>;
}
pub trait AsDescriptorSet {
fn as_descriptor_set(
&self,
descriptor_set_allocator: &Arc<StandardDescriptorSetAllocator>,
layout: &Arc<DescriptorSetLayout>,
) -> Result<Arc<DescriptorSet>, Validated<VulkanError>>;
}

View file

@ -0,0 +1,12 @@
mod buffer;
mod command;
mod descriptor_set;
pub mod camera;
pub mod mvp;
pub mod transform;
pub mod velocity;
pub mod vertex;
pub use buffer::{AsBindableBuffer, AsIndexBuffer, AsUniformBuffer, AsVertexBuffer};
pub use command::{AsRecordable, AsRenderableMesh, AsRenderableMeshInstance};
pub use descriptor_set::{AsDescriptorSet, AsDescriptorSetLayoutBindings};

View file

@ -0,0 +1,87 @@
use std::collections::BTreeMap;
use std::sync::Arc;
use vulkano::buffer::{
AllocateBufferError, BufferContents, BufferCreateInfo, BufferUsage, Subbuffer,
};
use vulkano::descriptor_set::allocator::StandardDescriptorSetAllocator;
use vulkano::descriptor_set::layout::{
DescriptorSetLayout, DescriptorSetLayoutBinding, DescriptorType,
};
use vulkano::descriptor_set::{DescriptorSet, WriteDescriptorSet};
use vulkano::memory::allocator::{AllocationCreateInfo, MemoryTypeFilter, StandardMemoryAllocator};
use vulkano::shader::ShaderStages;
use vulkano::{Validated, VulkanError};
use crate::core::render::primitives::{AsBindableBuffer, AsUniformBuffer};
use super::{AsDescriptorSet, AsDescriptorSetLayoutBindings};
#[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>> {
self.create_uniform_buffer(memory_allocator)
}
}
impl AsBindableBuffer for Mvp {
type BufferData = Mvp;
fn buffer_create_info() -> BufferCreateInfo {
BufferCreateInfo {
usage: BufferUsage::UNIFORM_BUFFER,
..Default::default()
}
}
fn allocation_create_info() -> AllocationCreateInfo {
AllocationCreateInfo {
memory_type_filter: MemoryTypeFilter::PREFER_DEVICE
| MemoryTypeFilter::HOST_SEQUENTIAL_WRITE,
..Default::default()
}
}
fn to_buffer_data(&self) -> Self::BufferData {
*self
}
}
impl AsUniformBuffer for Mvp {}
impl AsDescriptorSetLayoutBindings for Mvp {
fn as_descriptor_set_layout_bindings() -> BTreeMap<u32, DescriptorSetLayoutBinding> {
BTreeMap::<u32, DescriptorSetLayoutBinding>::from_iter([(
0,
DescriptorSetLayoutBinding {
stages: ShaderStages::VERTEX,
..DescriptorSetLayoutBinding::descriptor_type(DescriptorType::UniformBuffer)
},
)])
}
}
impl AsDescriptorSet for Subbuffer<[Mvp]> {
fn as_descriptor_set(
&self,
descriptor_set_allocator: &Arc<StandardDescriptorSetAllocator>,
layout: &Arc<DescriptorSetLayout>,
) -> Result<Arc<DescriptorSet>, Validated<VulkanError>> {
DescriptorSet::new(
descriptor_set_allocator.clone(),
layout.clone(),
[WriteDescriptorSet::buffer(0, self.clone())],
[],
)
}
}

View file

@ -0,0 +1,116 @@
use std::sync::Arc;
use bevy_ecs::prelude::*;
use glam::{Mat4, Quat, Vec3};
use vulkano::{
Validated,
buffer::{AllocateBufferError, BufferContents, BufferCreateInfo, BufferUsage, Subbuffer},
memory::allocator::{AllocationCreateInfo, MemoryTypeFilter, StandardMemoryAllocator},
pipeline::graphics::vertex_input::Vertex,
};
use crate::core::render::primitives::{AsBindableBuffer, AsVertexBuffer};
use super::command::AsRenderableMeshInstance;
#[derive(Component, Debug, Clone)]
pub struct Transform {
pub position: Vec3,
pub rotation: Quat,
pub scale: Vec3,
}
#[derive(BufferContents, Vertex, Clone, Copy, Debug)]
#[repr(C)]
pub struct TransformRaw {
#[format(R32G32B32A32_SFLOAT)]
pub model: [[f32; 4]; 4],
}
impl Default for Transform {
fn default() -> Self {
Self {
position: Vec3::ZERO,
rotation: Quat::IDENTITY,
scale: Vec3::ONE,
}
}
}
impl Transform {
pub fn new(position: Vec3, rotation: Quat, scale: Vec3) -> Self {
Self {
position,
rotation,
scale,
}
}
/// Get the transformation matrix (immutable - recalculates each time)
pub fn matrix(&self) -> Mat4 {
Mat4::from_translation(self.position)
* Mat4::from_quat(self.rotation)
* Mat4::from_scale(self.scale)
}
/// Convert to GPU-ready format (immutable - recalculates each time)
pub fn to_raw(&self) -> TransformRaw {
TransformRaw {
model: self.matrix().to_cols_array_2d(),
}
}
/// Create a buffer from transforms (immutable - recalculates each time)
pub fn create_buffer(
memory_allocator: &Arc<StandardMemoryAllocator>,
transforms: &[Transform],
) -> Result<Subbuffer<[TransformRaw]>, Validated<AllocateBufferError>> {
TransformRaw::create_vertex_buffer(
memory_allocator,
&transforms.iter().map(|t| t.to_raw()).collect::<Vec<_>>(),
)
}
}
impl From<&Transform> for TransformRaw {
fn from(transform: &Transform) -> Self {
transform.to_raw()
}
}
impl AsBindableBuffer for TransformRaw {
type BufferData = TransformRaw;
fn buffer_create_info() -> BufferCreateInfo {
BufferCreateInfo {
usage: BufferUsage::VERTEX_BUFFER,
..Default::default()
}
}
fn allocation_create_info() -> AllocationCreateInfo {
AllocationCreateInfo {
memory_type_filter: MemoryTypeFilter::PREFER_DEVICE
| MemoryTypeFilter::HOST_SEQUENTIAL_WRITE,
..Default::default()
}
}
fn to_buffer_data(&self) -> Self::BufferData {
*self
}
}
impl AsVertexBuffer for TransformRaw {}
impl AsRenderableMeshInstance for Subbuffer<[TransformRaw]> {
type InstanceBufferData = TransformRaw;
fn instance_buffer(&self) -> &Subbuffer<[Self::InstanceBufferData]> {
self
}
fn instance_count(&self) -> u32 {
self.len() as u32
}
}

View file

@ -0,0 +1,14 @@
use bevy_ecs::prelude::*;
use glam::Vec3;
#[derive(Component, Debug, Clone, Default)]
pub struct Velocity {
pub linear: Vec3,
pub angular: Vec3,
}
impl Velocity {
pub fn new(linear: Vec3, angular: Vec3) -> Self {
Self { linear, angular }
}
}

View file

@ -0,0 +1,101 @@
use vulkano::buffer::BufferContents;
use vulkano::buffer::{BufferCreateInfo, BufferUsage};
use vulkano::memory::allocator::{AllocationCreateInfo, MemoryTypeFilter};
use vulkano::pipeline::graphics::vertex_input::Vertex;
use crate::core::render::primitives::{AsBindableBuffer, AsIndexBuffer, AsVertexBuffer};
#[derive(BufferContents, Vertex, Clone, Copy)]
#[repr(C)]
pub struct Vertex2D {
#[format(R32G32_SFLOAT)]
pub position: [f32; 2],
#[format(R32G32_SFLOAT)]
pub uv: [f32; 2],
}
#[derive(BufferContents, Vertex, Clone, Copy)]
#[repr(C)]
pub struct Vertex3D {
#[format(R32G32B32_SFLOAT)]
pub position: [f32; 3],
#[format(R32G32_SFLOAT)]
pub uv: [f32; 2],
}
impl AsBindableBuffer for Vertex2D {
type BufferData = Vertex2D;
fn buffer_create_info() -> BufferCreateInfo {
BufferCreateInfo {
usage: BufferUsage::VERTEX_BUFFER,
..Default::default()
}
}
fn allocation_create_info() -> AllocationCreateInfo {
AllocationCreateInfo {
memory_type_filter: MemoryTypeFilter::PREFER_DEVICE
| MemoryTypeFilter::HOST_SEQUENTIAL_WRITE,
..Default::default()
}
}
fn to_buffer_data(&self) -> Self::BufferData {
*self
}
}
impl AsVertexBuffer for Vertex2D {}
impl AsBindableBuffer for Vertex3D {
type BufferData = Vertex3D;
fn buffer_create_info() -> BufferCreateInfo {
BufferCreateInfo {
usage: BufferUsage::VERTEX_BUFFER,
..Default::default()
}
}
fn allocation_create_info() -> AllocationCreateInfo {
AllocationCreateInfo {
memory_type_filter: MemoryTypeFilter::PREFER_DEVICE
| MemoryTypeFilter::HOST_SEQUENTIAL_WRITE,
..Default::default()
}
}
fn to_buffer_data(&self) -> Self::BufferData {
*self
}
}
impl AsVertexBuffer for Vertex3D {}
impl AsBindableBuffer for u32 {
type BufferData = u32;
fn buffer_create_info() -> BufferCreateInfo {
BufferCreateInfo {
usage: BufferUsage::INDEX_BUFFER,
..Default::default()
}
}
fn allocation_create_info() -> AllocationCreateInfo {
AllocationCreateInfo {
memory_type_filter: MemoryTypeFilter::PREFER_DEVICE
| MemoryTypeFilter::HOST_SEQUENTIAL_WRITE,
..Default::default()
}
}
fn to_buffer_data(&self) -> Self::BufferData {
*self
}
}
impl AsIndexBuffer for u32 {}

View file

@ -0,0 +1,113 @@
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,
}
}
}

View file

@ -0,0 +1,5 @@
mod square;
pub use square::SquareMesh;
mod obj;
pub use obj::ObjMesh;

View file

@ -0,0 +1,79 @@
use std::{error::Error, path::PathBuf, sync::Arc};
use vulkano::{buffer::Subbuffer, memory::allocator::StandardMemoryAllocator};
use crate::core::render::primitives::{
AsIndexBuffer, AsRenderableMesh, AsVertexBuffer, vertex::Vertex3D,
};
pub struct ObjMesh {
vertex_buffer: Subbuffer<[Vertex3D]>,
index_buffer: Subbuffer<[u32]>,
}
impl ObjMesh {
pub fn new(
memory_allocator: &Arc<StandardMemoryAllocator>,
file_path: impl Into<PathBuf>,
) -> Result<Vec<Self>, Box<dyn Error>> {
let (models, _) = tobj::load_obj(
&file_path.into(),
&tobj::LoadOptions {
single_index: true,
triangulate: true,
..Default::default()
},
)?;
let meshes = models
.into_iter()
.map(|model| {
let vertices = (0..model.mesh.positions.len() / 3)
.map(|i| Vertex3D {
position: [
model.mesh.positions[i * 3],
model.mesh.positions[i * 3 + 1],
model.mesh.positions[i * 3 + 2],
],
uv: [model.mesh.texcoords[i * 2], model.mesh.texcoords[i * 2 + 1]],
})
.collect::<Vec<_>>();
let indices = model.mesh.indices;
let vertex_buffer = Vertex3D::create_vertex_buffer(memory_allocator, &vertices)
.expect("Failed to create vertex buffer");
let index_buffer = u32::create_index_buffer(memory_allocator, &indices)
.expect("Failed to create index buffer");
Self {
vertex_buffer,
index_buffer,
}
})
.collect::<Vec<_>>();
Ok(meshes)
}
}
impl AsRenderableMesh for ObjMesh {
type VertexBufferData = Vertex3D;
type IndexBuffer = Subbuffer<[u32]>;
fn vertex_buffer(&self) -> &Subbuffer<[Self::VertexBufferData]> {
&self.vertex_buffer
}
fn index_buffer(&self) -> Option<&Self::IndexBuffer> {
Some(&self.index_buffer)
}
fn vertex_count(&self) -> u32 {
self.vertex_buffer.len() as u32
}
fn index_count(&self) -> u32 {
self.index_buffer.len() as u32
}
}

View file

@ -0,0 +1,66 @@
use std::{error::Error, sync::Arc};
use vulkano::{buffer::Subbuffer, memory::allocator::StandardMemoryAllocator};
use crate::core::render::primitives::{
AsIndexBuffer, AsRenderableMesh, AsVertexBuffer, vertex::Vertex3D,
};
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 struct SquareMesh {
vertex_buffer: Subbuffer<[Vertex3D]>,
index_buffer: Subbuffer<[u32]>,
}
impl SquareMesh {
pub fn new(memory_allocator: &Arc<StandardMemoryAllocator>) -> Result<Self, Box<dyn Error>> {
let vertex_buffer = Vertex3D::create_vertex_buffer(memory_allocator, &VERTICES)?;
let index_buffer = u32::create_index_buffer(memory_allocator, &INDICES)?;
Ok(Self {
vertex_buffer,
index_buffer,
})
}
}
impl AsRenderableMesh for SquareMesh {
type VertexBufferData = Vertex3D;
type IndexBuffer = Subbuffer<[u32]>;
fn vertex_buffer(&self) -> &Subbuffer<[Self::VertexBufferData]> {
&self.vertex_buffer
}
fn index_buffer(&self) -> Option<&Self::IndexBuffer> {
Some(&self.index_buffer)
}
fn vertex_count(&self) -> u32 {
VERTICES.len() as u32
}
fn index_count(&self) -> u32 {
INDICES.len() as u32
}
}

View file

@ -0,0 +1,5 @@
pub mod meshes;
pub mod pipeline;
pub mod texture;
pub mod timer;
pub mod vulkan;

View file

@ -0,0 +1,115 @@
use std::{
any::TypeId,
collections::HashMap,
error::Error,
sync::{Arc, RwLock},
};
use bevy_ecs::resource::Resource;
use vulkano::{device::Device, format::Format, pipeline::GraphicsPipeline};
use super::{GraphicsPipelineLoadFn, LoadableGraphicsPipeline};
#[derive(PartialEq, Eq)]
pub enum PipelineState {
NeedBuild,
Loaded,
}
#[derive(Resource)]
pub struct PipelineLoader {
device: Arc<Device>,
swapchain_image_format: Format,
depth_image_format: Format,
// Arc<GraphicsPipeline> is used in internal of vulkano. It's not possible to use Arc<RwLock<Option<GraphicsPipeline>>> directly.
pipelines_index: HashMap<TypeId, usize>,
pipelines_id: Vec<TypeId>,
pipelines_load_fn: Vec<GraphicsPipelineLoadFn>,
// Only content is protected by Arc and RwLock to avoid push in pipeline_loader in multiple threads and just allow to lock each pipeline when is needed as parallel pipelines loading.
// But only the pipeline loader is allowed to load a pipeline when it's needed.
pipelines: Vec<Arc<RwLock<Option<Arc<GraphicsPipeline>>>>>,
pipelines_state: Vec<Arc<RwLock<PipelineState>>>,
pipelines_name: Vec<&'static str>,
}
impl PipelineLoader {
pub fn new(
device: Arc<Device>,
swapchain_image_format: Format,
depth_image_format: Format,
) -> Self {
Self {
device,
swapchain_image_format,
depth_image_format,
pipelines: Vec::new(),
pipelines_load_fn: Vec::new(),
pipelines_name: Vec::new(),
pipelines_id: Vec::new(),
pipelines_state: Vec::new(),
pipelines_index: HashMap::new(),
}
}
pub fn register<T: LoadableGraphicsPipeline + 'static>(
&mut self,
) -> Result<(), Box<dyn Error>> {
let id = TypeId::of::<T>();
self.pipelines_index.insert(id, self.pipelines.len());
self.pipelines_id.push(id);
self.pipelines_load_fn.push(T::load);
self.pipelines_name.push(T::pipeline_name());
self.pipelines_state
.push(Arc::new(RwLock::new(PipelineState::NeedBuild)));
self.pipelines.push(Arc::new(RwLock::new(None)));
Ok(())
}
pub fn load_pending_pipelines(&self) -> Result<(), Box<dyn Error>> {
let iter = self
.pipelines_name
.iter()
.zip(self.pipelines.iter())
.zip(self.pipelines_load_fn.iter())
.zip(self.pipelines_state.iter())
.filter(|(_, state)| {
let state = state.read().unwrap();
*state == PipelineState::NeedBuild
});
for (((name, pipeline), load_fn), state) in iter {
let new_pipeline = load_fn(
&self.device,
self.swapchain_image_format,
self.depth_image_format,
)?;
let mut pipeline = pipeline.write().unwrap();
*pipeline = Some(new_pipeline);
let mut state = state.write().unwrap();
*state = PipelineState::Loaded;
tracing::trace!("Pipeline {name} loaded");
}
Ok(())
}
fn mark_pipelines_as_need_build(&mut self) {
for state in self.pipelines_state.iter() {
let mut state = state.write().unwrap();
*state = PipelineState::NeedBuild;
}
}
pub fn with_pipeline<T: 'static, F>(&self, f: F) -> Result<(), Box<dyn Error>>
where
F: FnOnce(&Arc<GraphicsPipeline>) -> Result<(), Box<dyn Error>>,
{
let id = TypeId::of::<T>();
let index = self.pipelines_index.get(&id).ok_or("Pipeline not found")?;
let pipeline_locker = self.pipelines[*index]
.read()
.map_err(|_| "Failed to lock pipeline")?;
let pipeline = pipeline_locker.as_ref().ok_or("Pipeline not loaded")?;
f(pipeline)
}
}

View file

@ -0,0 +1,18 @@
mod loader;
use std::{error::Error, sync::Arc};
pub use loader::PipelineLoader;
use vulkano::{device::Device, format::Format, pipeline::GraphicsPipeline};
type GraphicsPipelineLoadFn =
fn(&Arc<Device>, Format, Format) -> Result<Arc<GraphicsPipeline>, Box<dyn Error>>;
pub trait LoadableGraphicsPipeline {
fn load(
device: &Arc<Device>,
swapchain_image_format: Format,
depth_image_format: Format,
) -> Result<Arc<GraphicsPipeline>, Box<dyn Error>>;
fn pipeline_name() -> &'static str;
}

View file

@ -0,0 +1,133 @@
use std::{collections::HashMap, error::Error, sync::Arc};
use vulkano::{
command_buffer::{
AutoCommandBufferBuilder, CommandBufferUsage, PrimaryCommandBufferAbstract,
allocator::StandardCommandBufferAllocator,
},
device::{Device, Queue},
format::Format,
image::sampler::SamplerCreateInfo,
memory::allocator::StandardMemoryAllocator,
};
use bevy_ecs::resource::Resource;
use super::Texture;
pub enum TextureSourceKind {
File(String),
Buffer(Vec<u8>),
}
pub struct TextureLoadInfo {
pub source: TextureSourceKind,
pub sampler_create_info: SamplerCreateInfo,
pub image_format: Format,
}
#[derive(Resource)]
pub struct TextureLoader {
loaded_textures: HashMap<String, Arc<Texture>>,
pending_textures: HashMap<String, TextureLoadInfo>,
device: Arc<Device>,
command_buffer_allocator: Arc<StandardCommandBufferAllocator>,
memory_allocator: Arc<StandardMemoryAllocator>,
queue: Arc<Queue>,
}
impl TextureLoader {
pub fn new(
device: Arc<Device>,
command_buffer_allocator: Arc<StandardCommandBufferAllocator>,
memory_allocator: Arc<StandardMemoryAllocator>,
queue: Arc<Queue>,
) -> Self {
Self {
loaded_textures: HashMap::new(),
pending_textures: HashMap::new(),
device,
command_buffer_allocator,
memory_allocator,
queue,
}
}
pub fn select_best_suitable_queue(
graphics_queue: &Arc<Queue>,
transfer_queue: Option<&Arc<Queue>>,
) -> Arc<Queue> {
transfer_queue
.map(|queue| {
tracing::trace!(
"Select transfer queue for texture loading with family index: {:?}",
queue.queue_family_index()
);
queue.clone()
})
.unwrap_or_else(|| {
tracing::trace!(
"Select graphics queue for texture loading with family index: {:?}",
graphics_queue.queue_family_index()
);
graphics_queue.clone()
})
}
pub fn add_texture(&mut self, name: String, load_info: TextureLoadInfo) {
self.pending_textures.insert(name, load_info);
}
pub fn load_pending_textures(&mut self) -> Result<(), Box<dyn Error>> {
let _span = tracing::info_span!("load_pending_textures");
let mut uploads = AutoCommandBufferBuilder::primary(
self.command_buffer_allocator.clone(),
self.queue.queue_family_index(),
CommandBufferUsage::OneTimeSubmit,
)?;
let mut loading_textures = HashMap::new();
tracing::trace!("Pending textures count: {}", self.pending_textures.len());
for (name, info) in self.pending_textures.iter() {
let texture = match &info.source {
TextureSourceKind::File(path) => Texture::from_file(
&self.device,
&self.memory_allocator,
&mut uploads,
path.as_str(),
info,
)?,
TextureSourceKind::Buffer(buffer) => Texture::from_bytes(
&self.device,
&self.memory_allocator,
&mut uploads,
&buffer,
info,
)?,
};
loading_textures.insert(name.clone(), Arc::new(texture));
tracing::trace!("Loaded texture: {}", name);
}
let _ = uploads.build()?.execute(self.queue.clone())?;
self.loaded_textures.extend(loading_textures);
Ok(())
}
pub fn get_texture(&self, name: &str) -> Option<&Arc<Texture>> {
self.loaded_textures.get(name)
}
pub fn pending_textures_count(&self) -> usize {
self.pending_textures.len()
}
pub fn loaded_textures_count(&self) -> usize {
self.loaded_textures.len()
}
}

View file

@ -0,0 +1,5 @@
mod texture;
pub use texture::Texture;
mod loader;
pub use loader::{TextureLoadInfo, TextureLoader, TextureSourceKind};

View file

@ -0,0 +1,152 @@
use std::{collections::BTreeMap, error::Error, sync::Arc};
use image::DynamicImage;
use vulkano::{
Validated, VulkanError,
buffer::{Buffer, BufferCreateInfo, BufferUsage},
command_buffer::{AutoCommandBufferBuilder, CopyBufferToImageInfo, PrimaryAutoCommandBuffer},
descriptor_set::{
DescriptorSet, WriteDescriptorSet,
allocator::StandardDescriptorSetAllocator,
layout::{DescriptorSetLayout, DescriptorSetLayoutBinding, DescriptorType},
},
device::Device,
format::Format,
image::{Image, ImageCreateInfo, ImageType, ImageUsage, sampler::Sampler, view::ImageView},
memory::allocator::{AllocationCreateInfo, MemoryTypeFilter, StandardMemoryAllocator},
shader::ShaderStages,
};
use crate::core::render::primitives::{AsDescriptorSet, AsDescriptorSetLayoutBindings};
use super::TextureLoadInfo;
pub struct Texture {
texture: Arc<ImageView>,
sampler: Arc<Sampler>,
}
impl Texture {
fn new(texture: Arc<ImageView>, sampler: Arc<Sampler>) -> Self {
Self { texture, sampler }
}
pub(super) fn from_file(
device: &Arc<Device>,
memory_allocator: &Arc<StandardMemoryAllocator>,
builder: &mut AutoCommandBufferBuilder<PrimaryAutoCommandBuffer>,
path: &str,
load_info: &TextureLoadInfo,
) -> Result<Self, Box<dyn Error>> {
let bytes = std::fs::read(path)?;
Self::from_bytes(device, memory_allocator, builder, &bytes, load_info)
}
pub(super) fn from_bytes(
device: &Arc<Device>,
memory_allocator: &Arc<StandardMemoryAllocator>,
builder: &mut AutoCommandBufferBuilder<PrimaryAutoCommandBuffer>,
bytes: &[u8],
load_info: &TextureLoadInfo,
) -> Result<Self, Box<dyn Error>> {
let image = image::load_from_memory(bytes)?;
Self::from_dynamic_image(device, memory_allocator, builder, image, load_info)
}
fn from_dynamic_image(
device: &Arc<Device>,
memory_allocator: &Arc<StandardMemoryAllocator>,
builder: &mut AutoCommandBufferBuilder<PrimaryAutoCommandBuffer>,
image: DynamicImage,
load_info: &TextureLoadInfo,
) -> Result<Self, Box<dyn Error>> {
let image_data = match load_info.image_format {
Format::R8G8B8A8_SRGB => image.to_rgba8(),
_ => return Err("Unsupported format".into()),
};
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: load_info.image_format,
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(), load_info.sampler_create_info.clone())?;
let image_view = ImageView::new_default(image)?;
Ok(Self::new(image_view, sampler))
}
}
impl AsDescriptorSetLayoutBindings for Texture {
fn as_descriptor_set_layout_bindings() -> BTreeMap<u32, DescriptorSetLayoutBinding> {
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)
},
),
])
}
}
impl AsDescriptorSet for Texture {
fn as_descriptor_set(
&self,
descriptor_set_allocator: &Arc<StandardDescriptorSetAllocator>,
layout: &Arc<DescriptorSetLayout>,
) -> Result<Arc<DescriptorSet>, Validated<VulkanError>> {
DescriptorSet::new(
descriptor_set_allocator.clone(),
layout.clone(),
[
WriteDescriptorSet::sampler(0, self.sampler.clone()),
WriteDescriptorSet::image_view(1, self.texture.clone()),
],
[],
)
}
}

View file

@ -0,0 +1,37 @@
use bevy_ecs::resource::Resource;
#[derive(Resource)]
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
}
}

View file

@ -0,0 +1,145 @@
use bevy_ecs::prelude::Resource;
use std::{ops::Deref, sync::Arc};
use vulkano::{
command_buffer::allocator::StandardCommandBufferAllocator,
descriptor_set::allocator::StandardDescriptorSetAllocator,
device::{Device, Queue},
instance::Instance,
memory::allocator::StandardMemoryAllocator,
};
#[derive(Resource)]
pub struct VulkanInstance(Arc<Instance>);
impl From<&Arc<Instance>> for VulkanInstance {
fn from(instance: &Arc<Instance>) -> Self {
Self(instance.clone())
}
}
impl Deref for VulkanInstance {
type Target = Arc<Instance>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[derive(Resource)]
pub struct VulkanDevice(Arc<Device>);
impl From<&Arc<Device>> for VulkanDevice {
fn from(device: &Arc<Device>) -> Self {
Self(device.clone())
}
}
impl Deref for VulkanDevice {
type Target = Arc<Device>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[derive(Resource)]
pub struct VulkanGraphicsQueue(Arc<Queue>);
impl From<&Arc<Queue>> for VulkanGraphicsQueue {
fn from(queue: &Arc<Queue>) -> Self {
Self(queue.clone())
}
}
impl Deref for VulkanGraphicsQueue {
type Target = Arc<Queue>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[derive(Resource)]
pub struct VulkanComputeQueue(Arc<Queue>);
impl From<&Arc<Queue>> for VulkanComputeQueue {
fn from(queue: &Arc<Queue>) -> Self {
Self(queue.clone())
}
}
impl Deref for VulkanComputeQueue {
type Target = Arc<Queue>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[derive(Resource)]
pub struct VulkanTransferQueue(Option<Arc<Queue>>);
impl From<Option<&Arc<Queue>>> for VulkanTransferQueue {
fn from(queue: Option<&Arc<Queue>>) -> Self {
Self(queue.cloned())
}
}
impl Deref for VulkanTransferQueue {
type Target = Option<Arc<Queue>>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[derive(Resource)]
pub struct VulkanMemoryAllocator(Arc<StandardMemoryAllocator>);
impl From<&Arc<StandardMemoryAllocator>> for VulkanMemoryAllocator {
fn from(allocator: &Arc<StandardMemoryAllocator>) -> Self {
Self(allocator.clone())
}
}
impl Deref for VulkanMemoryAllocator {
type Target = Arc<StandardMemoryAllocator>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[derive(Resource)]
pub struct VulkanCommandBufferAllocator(Arc<StandardCommandBufferAllocator>);
impl From<&Arc<StandardCommandBufferAllocator>> for VulkanCommandBufferAllocator {
fn from(allocator: &Arc<StandardCommandBufferAllocator>) -> Self {
Self(allocator.clone())
}
}
impl Deref for VulkanCommandBufferAllocator {
type Target = Arc<StandardCommandBufferAllocator>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[derive(Resource)]
pub struct VulkanDescriptorSetAllocator(Arc<StandardDescriptorSetAllocator>);
impl From<&Arc<StandardDescriptorSetAllocator>> for VulkanDescriptorSetAllocator {
fn from(allocator: &Arc<StandardDescriptorSetAllocator>) -> Self {
Self(allocator.clone())
}
}
impl Deref for VulkanDescriptorSetAllocator {
type Target = Arc<StandardDescriptorSetAllocator>;
fn deref(&self) -> &Self::Target {
&self.0
}
}

View file

@ -0,0 +1,45 @@
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
}
}

81
src/core/scene/extract.rs Normal file
View file

@ -0,0 +1,81 @@
use bevy_ecs::world::World;
use crate::core::{
app::{DEPTH_IMAGE_ID, context::WindowContext},
render::{
resources::vulkan::{
VulkanCommandBufferAllocator, VulkanComputeQueue, VulkanDescriptorSetAllocator,
VulkanDevice, VulkanGraphicsQueue, VulkanInstance, VulkanMemoryAllocator,
VulkanTransferQueue,
},
resources::{pipeline::PipelineLoader, texture::TextureLoader},
vulkan_context::VulkanContext,
},
};
pub fn extract_vulkan_ressources(world: &mut World, vulkan_context: &VulkanContext) {
let vulkano_context = vulkan_context.vulkano_context();
let vulkan_instance = vulkano_context.instance();
let vulkan_device = vulkano_context.device();
let vulkan_graphics_queue = vulkano_context.graphics_queue();
let vulkan_compute_queue = vulkano_context.compute_queue();
let vulkan_transfer_queue = vulkano_context.transfer_queue();
let vulkan_memory_allocator = vulkano_context.memory_allocator();
let vulkan_command_buffer_allocator = vulkan_context.command_buffer_allocator();
let vulkan_descriptor_set_allocator = vulkan_context.descriptor_set_allocator();
world.insert_resource(VulkanInstance::from(vulkan_instance));
world.insert_resource(VulkanDevice::from(vulkan_device));
world.insert_resource(VulkanGraphicsQueue::from(vulkan_graphics_queue));
world.insert_resource(VulkanComputeQueue::from(vulkan_compute_queue));
world.insert_resource(VulkanTransferQueue::from(vulkan_transfer_queue));
world.insert_resource(VulkanMemoryAllocator::from(vulkan_memory_allocator));
world.insert_resource(VulkanCommandBufferAllocator::from(
vulkan_command_buffer_allocator,
));
world.insert_resource(VulkanDescriptorSetAllocator::from(
vulkan_descriptor_set_allocator,
));
}
pub fn extract_texture_loader(world: &mut World, vulkan_context: &VulkanContext) {
let vulkano_context = vulkan_context.vulkano_context();
let vulkan_device = vulkano_context.device();
let vulkan_command_buffer_allocator = vulkan_context.command_buffer_allocator();
let vulkan_memory_allocator = vulkano_context.memory_allocator();
let vulkan_graphics_queue = vulkano_context.graphics_queue();
let vulkan_transfer_queue = vulkano_context.transfer_queue();
let texture_loader = TextureLoader::new(
vulkan_device.clone(),
vulkan_command_buffer_allocator.clone(),
vulkan_memory_allocator.clone(),
TextureLoader::select_best_suitable_queue(vulkan_graphics_queue, vulkan_transfer_queue),
);
world.insert_resource(texture_loader);
}
pub fn extract_pipeline_loader(world: &mut World, window_context: &mut WindowContext) {
let vulkan_device = window_context
.vulkan_context()
.vulkano_context()
.device()
.clone();
let depth_image_view = window_context
.with_renderer_mut(|renderer| renderer.get_additional_image_view(DEPTH_IMAGE_ID).clone());
let swapchain_image_view =
window_context.with_renderer(|renderer| renderer.swapchain_image_view().clone());
let pipeline_loader = PipelineLoader::new(
vulkan_device.clone(),
swapchain_image_view.format(),
depth_image_view.format(),
);
world.insert_resource(pipeline_loader);
}

67
src/core/scene/manager.rs Normal file
View file

@ -0,0 +1,67 @@
use std::error::Error;
use crate::core::app::context::WindowContext;
use crate::core::input::InputManagerResource;
use crate::core::scene::AsScene;
use crate::core::scene::extract::{
extract_pipeline_loader, extract_texture_loader, extract_vulkan_ressources,
};
use bevy_ecs::world::World;
use super::Scene;
#[derive(Default)]
pub struct SceneManager {
current_scene: Option<Scene>,
new_scene: Option<Box<dyn AsScene>>,
}
impl SceneManager {
pub fn new() -> Self {
Self {
current_scene: None,
new_scene: None,
}
}
fn create_world_with_resources(window_context: &mut WindowContext) -> World {
let mut world = World::new();
extract_vulkan_ressources(&mut world, window_context.vulkan_context());
extract_texture_loader(&mut world, window_context.vulkan_context());
extract_pipeline_loader(&mut world, window_context);
world.insert_resource(InputManagerResource(window_context.input_manager.clone()));
world
}
pub fn set_new_scene(&mut self, scene_impl: Box<dyn AsScene>) {
self.new_scene = Some(scene_impl);
}
pub fn current_scene(&self) -> Option<&Scene> {
self.current_scene.as_ref()
}
pub fn current_scene_mut(&mut self) -> Option<&mut Scene> {
self.current_scene.as_mut()
}
pub fn load_scene_if_not_loaded(
&mut self,
window_context: &mut WindowContext,
) -> Result<(), Box<dyn Error>> {
if let Some(new_scene) = self.new_scene.take() {
let world = Self::create_world_with_resources(window_context);
let mut scene = Scene::new(new_scene, world);
scene.load(window_context)?;
if let Some(mut current_scene) = self.current_scene.take() {
current_scene.unload();
}
self.current_scene = Some(scene);
}
Ok(())
}
}

74
src/core/scene/mod.rs Normal file
View file

@ -0,0 +1,74 @@
use std::error::Error;
use bevy_ecs::{schedule::Schedule, world::World};
use vulkano::sync::GpuFuture;
use crate::core::{app::context::WindowContext, scene::schedule::SceneScedule};
mod extract;
pub mod manager;
pub mod schedule;
/// Structure Scene qui contient le world et l'implémentation AsScene
pub struct Scene {
pub world: World,
pub loop_schedule: Schedule,
pub implementation: Box<dyn AsScene>,
}
impl Scene {
pub fn new(implementation: Box<dyn AsScene>, world: World) -> Self {
Self {
world,
loop_schedule: SceneScedule::base_schedule(),
implementation,
}
}
pub fn loaded(&self) -> bool {
self.implementation.loaded()
}
pub fn load(&mut self, window_context: &mut WindowContext) -> Result<(), Box<dyn Error>> {
self.implementation.load(&mut self.world, window_context)
}
pub fn update(&mut self, window_context: &WindowContext) -> Result<(), Box<dyn Error>> {
self.implementation.update(&mut self.world, window_context)
}
pub fn render(
&mut self,
acquire_future: Box<dyn GpuFuture>,
window_context: &mut WindowContext,
) -> Result<Box<dyn GpuFuture>, Box<dyn Error>> {
self.implementation
.render(acquire_future, &mut self.world, window_context)
}
pub fn unload(&mut self) {
self.implementation.unload()
}
}
/// Trait pour les implémentations de scènes
pub trait AsScene {
fn loaded(&self) -> bool;
fn load(
&mut self,
world: &mut World,
window_context: &mut WindowContext,
) -> Result<(), Box<dyn Error>>;
fn update(
&mut self,
world: &mut World,
window_context: &WindowContext,
) -> Result<(), Box<dyn Error>>;
fn render(
&mut self,
acquire_future: Box<dyn GpuFuture>,
world: &mut World,
window_context: &mut WindowContext,
) -> Result<Box<dyn GpuFuture>, Box<dyn Error>>;
fn unload(&mut self);
}

View file

@ -0,0 +1,24 @@
use bevy_ecs::schedule::{IntoScheduleConfigs, Schedule, ScheduleLabel, SystemSet};
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
pub enum SceneSystems {
ExtractViews,
Update,
Render,
Present,
}
#[derive(ScheduleLabel, Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct SceneScedule;
impl SceneScedule {
pub fn base_schedule() -> Schedule {
use SceneSystems::*;
let mut schedule = Schedule::new(Self);
schedule.configure_sets((ExtractViews, Update, Render, Present).chain());
schedule
}
}

1
src/game/assets/mod.rs Normal file
View file

@ -0,0 +1 @@
pub mod pipelines;

View file

@ -0,0 +1 @@
pub mod simple;

View file

@ -0,0 +1,178 @@
use std::{error::Error, sync::Arc};
use vulkano::{
command_buffer::{AutoCommandBufferBuilder, PrimaryAutoCommandBuffer},
descriptor_set::{
allocator::StandardDescriptorSetAllocator, layout::DescriptorSetLayoutCreateInfo,
},
device::Device,
format::Format,
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},
},
};
use crate::core::render::{
primitives::{
AsDescriptorSet, AsDescriptorSetLayoutBindings, AsRecordable, AsRenderableMesh,
AsRenderableMeshInstance, mvp::Mvp, transform::TransformRaw, vertex::Vertex3D,
},
resources::{pipeline::LoadableGraphicsPipeline, texture::Texture},
};
pub struct SimplePipeline;
impl LoadableGraphicsPipeline for SimplePipeline {
fn load(
device: &Arc<Device>,
swapchain_format: Format,
depth_format: Format,
) -> Result<Arc<GraphicsPipeline>, Box<dyn Error>> {
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 mvp_bindings = Mvp::as_descriptor_set_layout_bindings();
let texture_bindings = Texture::as_descriptor_set_layout_bindings();
let vertex_descriptor_set_layout = DescriptorSetLayoutCreateInfo {
bindings: mvp_bindings,
..Default::default()
};
let fragment_descriptor_set_layout = DescriptorSetLayoutCreateInfo {
bindings: texture_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(pipeline)
}
fn pipeline_name() -> &'static str {
"SimplePipeline"
}
}
impl AsRecordable for SimplePipeline {
fn record_bind_commands(
builder: &mut AutoCommandBufferBuilder<PrimaryAutoCommandBuffer>,
descriptor_set_allocator: &Arc<StandardDescriptorSetAllocator>,
pipeline: &Arc<GraphicsPipeline>,
mesh: &impl AsRenderableMesh,
instances: &impl AsRenderableMeshInstance,
descriptor_sets: Vec<Arc<dyn AsDescriptorSet>>,
) -> Result<(), Box<dyn Error>> {
builder.bind_pipeline_graphics(pipeline.clone())?;
if !descriptor_sets.is_empty() {
let layouts = pipeline.layout().set_layouts();
let descriptor_sets = descriptor_sets
.iter()
.enumerate()
.map(|(layout_index, data)| {
data.as_descriptor_set(descriptor_set_allocator, &layouts[layout_index])
})
.collect::<Result<Vec<_>, _>>()?;
builder.bind_descriptor_sets(
PipelineBindPoint::Graphics,
pipeline.layout().clone(),
0,
descriptor_sets,
)?;
}
builder.bind_vertex_buffers(
0,
(
mesh.vertex_buffer().clone(),
instances.instance_buffer().clone(),
),
)?;
Ok(())
}
}
pub mod shaders {
pub mod vs {
vulkano_shaders::shader! {
ty: "vertex",
path: r"res/shaders/simple.vert",
generate_structs: false,
}
}
pub mod fs {
vulkano_shaders::shader! {
ty: "fragment",
path: r"res/shaders/simple.frag",
generate_structs: false,
}
}
}

2
src/game/mod.rs Normal file
View file

@ -0,0 +1,2 @@
pub mod assets;
pub mod scenes;

View file

@ -0,0 +1,491 @@
use std::error::Error;
use std::sync::Arc;
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::input::InputManagerResource;
use crate::core::render::primitives::camera::{Camera3D, Camera3DTransform};
use crate::core::render::primitives::transform::Transform;
use crate::core::render::primitives::velocity::Velocity;
use crate::core::render::primitives::{AsDescriptorSet, AsRecordable};
use crate::core::render::render_pass_manager::{RenderPassConfig, RenderPassManager};
use crate::core::render::resources::meshes::{ObjMesh, SquareMesh};
use crate::core::render::resources::pipeline::PipelineLoader;
use crate::core::render::resources::texture::{TextureLoadInfo, TextureLoader, TextureSourceKind};
use crate::core::render::resources::timer::Timer;
use crate::core::render::resources::vulkan::{
VulkanCommandBufferAllocator, VulkanDescriptorSetAllocator, VulkanGraphicsQueue,
VulkanMemoryAllocator,
};
use crate::core::scene::AsScene;
use crate::game::assets::pipelines::simple::SimplePipeline;
use bevy_ecs::prelude::*;
use bevy_ecs::schedule::Schedule;
use bevy_ecs::world::World;
use egui_winit_vulkano::egui;
use glam::EulerRot;
use glam::Quat;
use glam::Vec3;
use vulkano::format::Format;
use vulkano::image::sampler::{Filter, SamplerAddressMode, SamplerCreateInfo};
use vulkano::{
command_buffer::{AutoCommandBufferBuilder, CommandBufferUsage},
sync::GpuFuture,
};
use winit::window::CursorGrabMode;
#[derive(Component)]
pub struct Square;
#[derive(Component)]
pub struct Cube;
pub struct MainSceneState {
square: SquareMesh,
obj: ObjMesh,
scheduler: Schedule,
}
#[derive(Resource)]
pub struct MovementSpeed(f32);
#[derive(Resource)]
pub struct CameraSensitivity(f32);
#[derive(Default)]
pub struct MainScene {
state: Option<MainSceneState>,
}
impl AsScene for MainScene {
fn loaded(&self) -> bool {
self.state.is_some()
}
fn load(
&mut self,
world: &mut World,
window_context: &mut WindowContext,
) -> Result<(), Box<dyn std::error::Error>> {
let mut pipeline_loader = world.resource_mut::<PipelineLoader>();
pipeline_loader.register::<SimplePipeline>()?;
pipeline_loader.load_pending_pipelines()?;
let mut texture_loader = world.resource_mut::<TextureLoader>();
texture_loader.add_texture(
"wooden-crate".to_string(),
TextureLoadInfo {
source: TextureSourceKind::File("res/textures/wooden-crate.jpg".to_string()),
sampler_create_info: SamplerCreateInfo {
mag_filter: Filter::Linear,
min_filter: Filter::Linear,
address_mode: [SamplerAddressMode::Repeat; 3],
..Default::default()
},
image_format: Format::R8G8B8A8_SRGB,
},
);
texture_loader.add_texture(
"cube-diffuse".to_string(),
TextureLoadInfo {
source: TextureSourceKind::File("res/objects/cube-diffuse.jpg".to_string()),
sampler_create_info: SamplerCreateInfo {
mag_filter: Filter::Linear,
min_filter: Filter::Linear,
address_mode: [SamplerAddressMode::Repeat; 3],
..Default::default()
},
image_format: Format::R8G8B8A8_SRGB,
},
);
texture_loader.load_pending_textures()?;
let square = SquareMesh::new(world.resource::<VulkanMemoryAllocator>())?;
let obj = {
let obj = ObjMesh::new(
world.resource::<VulkanMemoryAllocator>(),
"res/objects/cube.obj",
)?;
obj.into_iter().next().unwrap()
};
world.spawn((
Camera3D::new(
window_context.get_aspect_ratio(),
std::f32::consts::FRAC_PI_2,
0.01,
1000.0,
),
Camera3DTransform {
position: Vec3::ZERO,
rotation: Vec3::ZERO,
},
));
world.insert_resource(MovementSpeed(50.0));
world.insert_resource(CameraSensitivity(10.0));
let mut scheduler = Schedule::default();
scheduler.add_systems(
(
update_camera_system,
update_velocity_system,
update_timer_system,
)
.chain(),
);
world.insert_resource(Timer::new());
create_entities(world, 100, 10.0, 10.0);
self.state = Some(MainSceneState {
square,
obj,
scheduler,
});
Ok(())
}
fn update(
&mut self,
world: &mut World,
window_context: &WindowContext,
) -> Result<(), Box<dyn Error>> {
let state = self.state.as_mut().unwrap();
{
let mut camera = world.query::<&mut Camera3D>().single_mut(world).unwrap();
camera.update_projection(window_context.get_aspect_ratio());
}
{
let input_manager = world.resource::<InputManagerResource>().0.read().unwrap();
if input_manager.get_virtual_input_state("mouse_left") > 0.0 {
let _ = window_context
.event_loop_proxy
.send_event(UserEvent::CursorVisible(window_context.window_id, false));
let _ = window_context
.event_loop_proxy
.send_event(UserEvent::CursorGrabMode(
window_context.window_id,
CursorGrabMode::Locked,
));
}
if input_manager.get_virtual_input_state("mouse_right") > 0.0 {
let _ = window_context
.event_loop_proxy
.send_event(UserEvent::CursorVisible(window_context.window_id, true));
let _ = window_context
.event_loop_proxy
.send_event(UserEvent::CursorGrabMode(
window_context.window_id,
CursorGrabMode::None,
));
}
}
state.scheduler.run(world);
Ok(())
}
fn render(
&mut self,
before_future: Box<dyn GpuFuture>,
world: &mut World,
window_context: &mut WindowContext,
) -> Result<Box<dyn GpuFuture>, Box<dyn Error>> {
let state = self.state.as_mut().ok_or("State not loaded")?;
let mut builder = AutoCommandBufferBuilder::primary(
(*world.resource::<VulkanCommandBufferAllocator>()).clone(),
world.resource::<VulkanGraphicsQueue>().queue_family_index(),
CommandBufferUsage::OneTimeSubmit,
)?;
{
let swapchain_image_view =
window_context.with_renderer(|renderer| renderer.swapchain_image_view().clone());
let depth_image_view = window_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),
window_context.get_window_size(),
)?;
}
// Create camera uniform using the actual camera
let camera_uniform = {
let result = world
.query_filtered::<(&Camera3D, &Camera3DTransform), With<Camera3D>>()
.single(&world)
.unwrap();
Arc::new(
result
.0
.create_buffer(result.1, world.resource::<VulkanMemoryAllocator>())?,
)
};
let square_transforms = world
.query_filtered::<&Transform, With<Square>>()
.iter(&world)
.cloned()
.collect::<Vec<Transform>>();
let square_transform_uniform = Transform::create_buffer(
world.resource::<VulkanMemoryAllocator>(),
&square_transforms,
)?;
let cube_transforms = world
.query_filtered::<&Transform, With<Cube>>()
.iter(&world)
.cloned()
.collect::<Vec<Transform>>();
let cube_transform_uniform =
Transform::create_buffer(world.resource::<VulkanMemoryAllocator>(), &cube_transforms)?;
let texture_loader = world.resource::<TextureLoader>();
let pipeline_loader = world.resource::<PipelineLoader>();
pipeline_loader.with_pipeline::<SimplePipeline, _>(|pipeline| {
SimplePipeline::record_commands(
&mut builder,
world.resource::<VulkanDescriptorSetAllocator>(),
pipeline,
&state.square,
&square_transform_uniform,
vec![
camera_uniform.clone() as Arc<dyn AsDescriptorSet>,
texture_loader.get_texture("wooden-crate").unwrap().clone(),
],
)?;
SimplePipeline::record_commands(
&mut builder,
world.resource::<VulkanDescriptorSetAllocator>(),
pipeline,
&state.obj,
&cube_transform_uniform,
vec![
camera_uniform.clone() as Arc<dyn AsDescriptorSet>,
texture_loader.get_texture("cube-diffuse").unwrap().clone(),
],
)?;
Ok(())
})?;
RenderPassManager::end_rendering(&mut builder)?;
let command_buffer = builder.build()?;
let render_future = before_future.then_execute(
(*world.resource::<VulkanGraphicsQueue>()).clone(),
command_buffer,
)?;
let swapchain_image_view =
window_context.with_renderer(|renderer| renderer.swapchain_image_view().clone());
let input_manager_status = {
let input_manager = world.resource::<InputManagerResource>().0.read().unwrap();
format!("{:#?}", input_manager)
};
let event_loop_proxy = window_context.event_loop_proxy.clone();
let delta_time = world.resource::<Timer>().delta_time();
let window_id = window_context.window_id;
let window_size = window_context.get_window_size();
let camera_transform = world
.query_filtered::<&Camera3DTransform, With<Camera3D>>()
.single(&world)
.unwrap();
let render_future = window_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 = camera_transform.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 = camera_transform.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(Box::new(render_future))
}
fn unload(&mut self) {
self.state = None;
}
}
fn create_entities(
world: &mut World,
num_instances: u32,
instance_size: f32,
instance_spacing: f32,
) {
let num_instances_per_row = (num_instances as f32 / instance_spacing).ceil() as u32;
let square_instances = (0..num_instances)
.map(|i| {
let x_index = i % num_instances_per_row;
let z_index = i / num_instances_per_row;
let transform = Transform::new(
Vec3::new(
x_index as f32 * (instance_spacing + instance_size),
0.0,
z_index as f32 * (instance_spacing + instance_size),
),
Quat::from_euler(EulerRot::XYZ, 0.0, rand::random_range(0.0..=360.0), 0.0),
Vec3::new(instance_size, instance_size, instance_size),
);
let velocity = Velocity::new(Vec3::ZERO, Vec3::new(0.0, x_index as f32, 0.0));
(Square, transform, velocity)
})
.collect::<Vec<_>>();
world.spawn_batch(square_instances);
let cube_instances = (0..num_instances)
.map(|i| {
let x_index = i % num_instances_per_row;
let z_index = i / num_instances_per_row;
let transform = Transform::new(
Vec3::new(
x_index as f32 * (instance_spacing + instance_size),
0.0,
z_index as f32 * (instance_spacing + instance_size) * -1.0
- instance_spacing * 2.0,
),
Quat::from_euler(EulerRot::XYZ, 0.0, rand::random_range(0.0..=360.0), 0.0),
Vec3::new(
instance_size * 0.5,
instance_size * 0.5,
instance_size * 0.5,
),
);
let velocity = Velocity::new(Vec3::ZERO, Vec3::new(0.0, x_index as f32, 0.0));
(Cube, transform, velocity)
})
.collect::<Vec<_>>();
world.spawn_batch(cube_instances);
}
fn update_velocity_system(mut query: Query<(&mut Transform, &Velocity)>, time: Res<Timer>) {
for (mut transform, velocity) in query.iter_mut() {
let delta_time = time.delta_time();
// Update linear position
transform.position += velocity.linear * delta_time;
// Update angular rotation
let angular_delta = velocity.angular * delta_time;
let rotation_delta = Quat::from_euler(
EulerRot::XYZ,
angular_delta.x,
angular_delta.y,
angular_delta.z,
);
transform.rotation *= rotation_delta;
}
}
fn update_timer_system(mut timer: ResMut<Timer>) {
timer.update();
}
fn update_camera_system(
mut query: Query<&mut Camera3DTransform, With<Camera3D>>,
input_manager: Res<InputManagerResource>,
camera_sensitivity: Res<CameraSensitivity>,
movement_speed: Res<MovementSpeed>,
time: Res<Timer>,
) {
let input_manager = input_manager.0.read().unwrap();
let mut camera_transform = query.single_mut().unwrap();
let delta_time = time.delta_time();
camera_transform.rotation += Vec3::new(
(input_manager.get_virtual_input_state("mouse_y") * camera_sensitivity.0 * delta_time)
.to_radians(),
(input_manager.get_virtual_input_state("mouse_x") * camera_sensitivity.0 * delta_time)
.to_radians(),
0.0,
);
if camera_transform.rotation.x > std::f32::consts::FRAC_PI_2 {
camera_transform.rotation.x = std::f32::consts::FRAC_PI_2;
}
if camera_transform.rotation.x < -std::f32::consts::FRAC_PI_2 {
camera_transform.rotation.x = -std::f32::consts::FRAC_PI_2;
}
let (yaw_sin, yaw_cos) = camera_transform.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_speed.0 * delta_time;
camera_transform.position += tx * right;
let tz = input_manager.get_virtual_input_state("move_forward") * movement_speed.0 * delta_time;
camera_transform.position += tz * forward;
}

2
src/game/scenes/mod.rs Normal file
View file

@ -0,0 +1,2 @@
pub mod main_scene;
pub mod settings_scene;

View file

@ -0,0 +1,151 @@
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::render::resources::vulkan::{VulkanCommandBufferAllocator, VulkanGraphicsQueue};
use crate::core::scene::AsScene;
use bevy_ecs::world::World;
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 AsScene for SettingsScene {
fn loaded(&self) -> bool {
self.state.is_some()
}
fn load(
&mut self,
_world: &mut World,
window_context: &mut WindowContext,
) -> Result<(), Box<dyn Error>> {
let current_resolution = window_context.get_window_size();
let available_resolutions = window_context.get_available_resolutions();
self.state = Some(SettingsSceneState {
current_resolution,
available_resolutions,
});
Ok(())
}
fn update(
&mut self,
_world: &mut World,
_window_context: &WindowContext,
) -> Result<(), Box<dyn Error>> {
Ok(())
}
fn render(
&mut self,
before_future: Box<dyn GpuFuture>,
world: &mut World,
window_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(
(*world.resource::<VulkanCommandBufferAllocator>()).clone(),
world.resource::<VulkanGraphicsQueue>().queue_family_index(),
CommandBufferUsage::OneTimeSubmit,
)?;
// Utiliser le RenderPassManager
{
let swapchain_image_view =
window_context.with_renderer(|renderer| renderer.swapchain_image_view().clone());
let depth_stencil_image_view = window_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),
window_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(
(*world.resource::<VulkanGraphicsQueue>()).clone(),
command_buffer,
)?;
let swapchain_image_view =
window_context.with_renderer(|renderer| renderer.swapchain_image_view().clone());
let event_loop_proxy = window_context.event_loop_proxy.clone();
let window_id = window_context.window_id;
let render_future = window_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(Box::new(render_future))
}
fn unload(&mut self) {
self.state = None;
}
}

View file

@ -1,14 +1,108 @@
use winit::event_loop::EventLoop;
use core::input::{AxisDirection, InputManager, VirtualBinding};
use std::collections::HashMap;
mod renderer;
mod vulkan;
use vulkano::device::{DeviceExtensions, DeviceFeatures};
use vulkano_util::context::{VulkanoConfig, VulkanoContext};
use winit::{
event::MouseButton,
event_loop::{ControlFlow, EventLoop},
keyboard::{KeyCode, PhysicalKey},
};
use renderer::app::App;
use vulkan::context::VulkanContext;
mod core;
mod game;
fn main() {
let event_loop = EventLoop::new().unwrap();
let vulkan_context = VulkanContext::new(&event_loop).unwrap();
let mut app = App::new(vulkan_context);
event_loop.run_app(&mut app).unwrap();
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;
tracing_subscriber::registry()
.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);
let proxy = event_loop.create_proxy();
let mut app = core::app::App::new(vulkano_context, input_manager, proxy);
match event_loop.run_app(&mut app) {
Ok(_) => {}
Err(e) => {
tracing::error!("Error running old app: {e}");
}
}
}

View file

@ -1,262 +0,0 @@
use crate::renderer::components::{Entity, Material, Mesh, Transform};
use crate::vulkan::context::VulkanContext;
use crate::vulkan::pipeline::{Pipeline, triangle::TrianglePipeline};
use crate::vulkan::renderer::VulkanRenderer;
use crate::vulkan::resources::vertex::{MVPData, Vertex2D};
use std::error::Error;
use std::sync::Arc;
use vulkano::VulkanError;
use vulkano::buffer::{Buffer, BufferCreateInfo, BufferUsage};
use vulkano::command_buffer::{
AutoCommandBufferBuilder, CommandBufferUsage, PrimaryAutoCommandBuffer,
RenderingAttachmentInfo, RenderingInfo,
};
use vulkano::descriptor_set::{DescriptorSet, WriteDescriptorSet};
use vulkano::memory::allocator::{AllocationCreateInfo, MemoryTypeFilter};
use vulkano::pipeline::{Pipeline as VulkanPipeline, PipelineBindPoint};
use vulkano::render_pass::{AttachmentLoadOp, AttachmentStoreOp};
use vulkano::swapchain::Surface;
use winit::application::ApplicationHandler;
use winit::event::WindowEvent;
use winit::event_loop::ActiveEventLoop;
use winit::window::WindowId;
pub struct App {
pub vulkan_context: VulkanContext,
pub renderer: Option<VulkanRenderer>,
pub entities: Vec<Entity>,
}
impl App {
pub fn new(vulkan_context: VulkanContext) -> Self {
Self {
vulkan_context,
renderer: None,
entities: Vec::new(),
}
}
pub fn setup_test_entities(&mut self) -> Result<(), Box<dyn Error>> {
// Créer un pipeline de test
let pipeline = TrianglePipeline::new(&self.vulkan_context.device);
// Créer un buffer de vertex pour un triangle
let vertices = [
Vertex2D {
position: [-0.5, -0.5],
color: [1.0, 0.0, 0.0], // Rouge
},
Vertex2D {
position: [0.5, -0.5],
color: [0.0, 1.0, 0.0], // Vert
},
Vertex2D {
position: [0.0, 0.5],
color: [0.0, 0.0, 1.0], // Bleu
},
];
let vertex_buffer =
Vertex2D::create_buffer(vertices.to_vec(), &self.vulkan_context.memory_allocator)
.unwrap();
// Créer un buffer uniform pour les matrices MVP
let mvp_data = MVPData {
world: [
[1.0, 0.0, 0.0, 0.0],
[0.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
[0.0, 0.0, 0.0, 1.0],
],
view: [
[1.0, 0.0, 0.0, 0.0],
[0.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
[0.0, 0.0, 0.0, 1.0],
],
projection: [
[1.0, 0.0, 0.0, 0.0],
[0.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
[0.0, 0.0, 0.0, 1.0],
],
};
let uniform_buffer = Buffer::from_data(
self.vulkan_context.memory_allocator.clone(),
BufferCreateInfo {
usage: BufferUsage::UNIFORM_BUFFER,
..Default::default()
},
AllocationCreateInfo {
memory_type_filter: MemoryTypeFilter::PREFER_DEVICE
| MemoryTypeFilter::HOST_SEQUENTIAL_WRITE,
..Default::default()
},
mvp_data,
)
.unwrap();
// Créer un descriptor set de test
let descriptor_set = DescriptorSet::new(
self.vulkan_context.descriptor_set_allocator.clone(),
pipeline
.get_pipeline()
.layout()
.set_layouts()
.get(0)
.unwrap()
.clone(),
[WriteDescriptorSet::buffer(0, uniform_buffer)],
[],
)?;
let material = Material {
pipeline: pipeline.get_pipeline().clone(),
descriptor_set,
};
// Créer quelques entités de test
let mut entities = Vec::new();
for i in 0..3 {
entities.push(Entity {
mesh: Mesh {
vertex_buffer: vertex_buffer.clone(),
vertex_count: 3,
instance_count: 1,
},
material: material.clone(),
transform: Transform {
position: [i as f32 * 0.5 - 0.5, 0.0, 0.0],
rotation: [0.0, 0.0, 0.0],
scale: [1.0, 1.0, 1.0],
},
});
}
self.entities = entities;
Ok(())
}
pub fn render(
&self,
command_buffer: &mut AutoCommandBufferBuilder<PrimaryAutoCommandBuffer>,
) -> Result<(), Box<dyn Error>> {
for entity in &self.entities {
command_buffer
.bind_pipeline_graphics(entity.material.pipeline.clone())
.unwrap()
.bind_descriptor_sets(
PipelineBindPoint::Graphics,
entity.material.pipeline.layout().clone(),
0,
entity.material.descriptor_set.clone(),
)
.unwrap()
.bind_vertex_buffers(0, entity.mesh.vertex_buffer.clone())
.unwrap();
unsafe {
command_buffer
.draw(entity.mesh.vertex_count, 1, 0, 0)
.unwrap();
}
}
Ok(())
}
}
impl ApplicationHandler for App {
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
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),
));
let window = Arc::new(event_loop.create_window(window_attributes).unwrap());
let surface =
Surface::from_window(self.vulkan_context.instance.clone(), window.clone()).unwrap();
self.renderer = Some(VulkanRenderer::new(
window,
surface,
self.vulkan_context.device.clone(),
self.vulkan_context.queue.clone(),
));
self.setup_test_entities().unwrap();
}
fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent) {
match event {
WindowEvent::CloseRequested => {
log::debug!("The close button was pressed; stopping");
event_loop.exit();
}
WindowEvent::Resized(_) => {
let renderer = self.renderer.as_mut().unwrap();
renderer.recreate_swapchain = true;
}
WindowEvent::RedrawRequested => {
let (image_index, acquire_future) =
match self.renderer.as_mut().unwrap().begin_frame() {
Ok(r) => r,
Err(VulkanError::OutOfDate) => return,
Err(e) => panic!("failed to acquire next image: {e}"),
};
let mut builder = AutoCommandBufferBuilder::primary(
self.vulkan_context.command_buffer_allocator.clone(),
self.vulkan_context.queue.queue_family_index(),
CommandBufferUsage::OneTimeSubmit,
)
.unwrap();
{
builder
.begin_rendering(RenderingInfo {
color_attachments: vec![Some(RenderingAttachmentInfo {
load_op: AttachmentLoadOp::Clear,
store_op: AttachmentStoreOp::Store,
clear_value: Some([0.0, 0.0, 0.0, 1.0].into()),
..RenderingAttachmentInfo::image_view(
self.renderer.as_ref().unwrap().attachment_image_views
[image_index as usize]
.clone(),
)
})],
..Default::default()
})
.unwrap()
.set_viewport(
0,
[self.renderer.as_ref().unwrap().viewport.clone()]
.into_iter()
.collect(),
)
.unwrap();
}
self.render(&mut builder).unwrap();
{
builder.end_rendering().unwrap();
}
self.renderer
.as_mut()
.unwrap()
.end_frame(image_index, acquire_future, builder)
.unwrap();
}
_ => {}
}
}
fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) {
let renderer = self.renderer.as_mut().unwrap();
renderer.window.request_redraw();
}
}

View file

@ -1,71 +0,0 @@
use std::sync::Arc;
use vulkano::buffer::Subbuffer;
use vulkano::buffer::allocator::SubbufferAllocator;
use vulkano::command_buffer::allocator::StandardCommandBufferAllocator;
use vulkano::command_buffer::{AutoCommandBufferBuilder, PrimaryAutoCommandBuffer};
use vulkano::descriptor_set::DescriptorSet;
use vulkano::descriptor_set::allocator::StandardDescriptorSetAllocator;
use vulkano::device::Device;
use vulkano::memory::allocator::StandardMemoryAllocator;
use vulkano::pipeline::GraphicsPipeline;
use vulkano::pipeline::Pipeline;
use crate::vulkan::resources::vertex::Vertex2D;
#[derive(Clone)]
pub struct Mesh {
pub vertex_buffer: Subbuffer<[Vertex2D]>,
pub vertex_count: u32,
pub instance_count: u32,
}
#[derive(Clone)]
pub struct Material {
pub pipeline: Arc<GraphicsPipeline>,
pub descriptor_set: Arc<DescriptorSet>,
}
#[derive(Clone)]
pub struct Transform {
pub position: [f32; 3],
pub rotation: [f32; 3],
pub scale: [f32; 3],
}
#[derive(Clone)]
pub struct RenderResources {
pub device: Arc<Device>,
pub memory_allocator: Arc<StandardMemoryAllocator>,
pub command_buffer_allocator: Arc<StandardCommandBufferAllocator>,
pub uniform_buffer_allocator: Arc<SubbufferAllocator>,
pub descriptor_set_allocator: Arc<StandardDescriptorSetAllocator>,
}
pub struct Entity {
pub mesh: Mesh,
pub material: Material,
pub transform: Transform,
}
pub fn render_system(
_resources: &RenderResources,
builder: &mut AutoCommandBufferBuilder<PrimaryAutoCommandBuffer>,
entities: &[Entity],
) -> Result<(), Box<dyn std::error::Error>> {
for entity in entities {
unsafe {
builder
.bind_pipeline_graphics(entity.material.pipeline.clone())?
.bind_descriptor_sets(
vulkano::pipeline::PipelineBindPoint::Graphics,
entity.material.pipeline.layout().clone(),
0,
entity.material.descriptor_set.clone(),
)?
.bind_vertex_buffers(0, entity.mesh.vertex_buffer.clone())?
.draw(entity.mesh.vertex_count, entity.mesh.instance_count, 0, 0)?;
}
}
Ok(())
}

View file

@ -1,2 +0,0 @@
pub mod app;
pub mod components;

View file

@ -1,135 +0,0 @@
use std::sync::Arc;
use vulkano::buffer::BufferUsage;
use vulkano::buffer::allocator::{SubbufferAllocator, SubbufferAllocatorCreateInfo};
use vulkano::command_buffer::allocator::StandardCommandBufferAllocator;
use vulkano::descriptor_set::allocator::StandardDescriptorSetAllocator;
use vulkano::device::physical::PhysicalDeviceType;
use vulkano::device::{
Device, DeviceCreateInfo, DeviceExtensions, DeviceFeatures, Queue, QueueCreateInfo, QueueFlags,
};
use vulkano::instance::{Instance, InstanceCreateFlags, InstanceCreateInfo};
use vulkano::memory::allocator::{MemoryTypeFilter, StandardMemoryAllocator};
use vulkano::swapchain::Surface;
use vulkano::{Version, VulkanLibrary};
use winit::event_loop::EventLoop;
pub struct VulkanContext {
pub instance: Arc<Instance>,
pub device: Arc<Device>,
pub queue: Arc<Queue>,
pub memory_allocator: Arc<StandardMemoryAllocator>,
pub command_buffer_allocator: Arc<StandardCommandBufferAllocator>,
pub uniform_buffer_allocator: Arc<SubbufferAllocator>,
pub descriptor_set_allocator: Arc<StandardDescriptorSetAllocator>,
}
impl VulkanContext {
pub fn new(event_loop: &EventLoop<()>) -> Result<Self, Box<dyn std::error::Error>> {
let library = VulkanLibrary::new().unwrap();
for layer in library.layer_properties().unwrap() {
log::debug!("Available layer: {}", layer.name());
}
let required_extensions = Surface::required_extensions(event_loop).unwrap();
let instance = Instance::new(
library,
InstanceCreateInfo {
flags: InstanceCreateFlags::ENUMERATE_PORTABILITY,
enabled_extensions: required_extensions,
enabled_layers: vec![String::from("VK_LAYER_KHRONOS_validation")],
..Default::default()
},
)
.unwrap();
let mut device_extensions = DeviceExtensions {
khr_swapchain: true,
..DeviceExtensions::empty()
};
let (physical_device, queue_family_index) = instance
.enumerate_physical_devices()
.unwrap()
.filter(|p| {
p.api_version() >= Version::V1_3 || p.supported_extensions().khr_dynamic_rendering
})
.filter(|p| p.supported_extensions().contains(&device_extensions))
.filter_map(|p| {
p.queue_family_properties()
.iter()
.enumerate()
.position(|(_i, q)| q.queue_flags.intersects(QueueFlags::GRAPHICS))
.map(|i| (p, i as u32))
})
.min_by_key(|(p, _)| match p.properties().device_type {
PhysicalDeviceType::DiscreteGpu => 0,
PhysicalDeviceType::IntegratedGpu => 1,
PhysicalDeviceType::VirtualGpu => 2,
PhysicalDeviceType::Cpu => 3,
PhysicalDeviceType::Other => 4,
_ => 5,
})
.expect("no suitable physical device found");
log::debug!(
"Using device: {} (type: {:?})",
physical_device.properties().device_name,
physical_device.properties().device_type,
);
if physical_device.api_version() < Version::V1_3 {
device_extensions.khr_dynamic_rendering = true;
}
log::debug!("Using device extensions: {:#?}", device_extensions);
let (device, mut queues) = Device::new(
physical_device,
DeviceCreateInfo {
queue_create_infos: vec![QueueCreateInfo {
queue_family_index,
..Default::default()
}],
enabled_extensions: device_extensions,
enabled_features: DeviceFeatures {
dynamic_rendering: true,
..DeviceFeatures::empty()
},
..Default::default()
},
)
.unwrap();
let queue = queues.next().unwrap();
let memory_allocator = Arc::new(StandardMemoryAllocator::new_default(device.clone()));
let command_buffer_allocator = Arc::new(StandardCommandBufferAllocator::new(
device.clone(),
Default::default(),
));
let uniform_buffer_allocator = Arc::new(SubbufferAllocator::new(
memory_allocator.clone(),
SubbufferAllocatorCreateInfo {
buffer_usage: BufferUsage::UNIFORM_BUFFER,
memory_type_filter: MemoryTypeFilter::PREFER_DEVICE
| MemoryTypeFilter::HOST_SEQUENTIAL_WRITE,
..Default::default()
},
));
let descriptor_set_allocator = Arc::new(StandardDescriptorSetAllocator::new(
device.clone(),
Default::default(),
));
Ok(Self {
instance,
device,
queue,
memory_allocator,
command_buffer_allocator,
uniform_buffer_allocator,
descriptor_set_allocator,
})
}
}

View file

@ -1,4 +0,0 @@
pub mod context;
pub mod pipeline;
pub mod renderer;
pub mod resources;

View file

@ -1,30 +0,0 @@
pub mod triangle;
use std::sync::Arc;
use vulkano::device::Device;
use vulkano::pipeline::GraphicsPipeline;
pub trait Pipeline {
fn create_pipeline(device: &Arc<Device>) -> Arc<GraphicsPipeline>;
fn get_pipeline(&self) -> &Arc<GraphicsPipeline>;
}
pub struct PipelineManager {
pipelines: std::collections::HashMap<String, Arc<GraphicsPipeline>>,
}
impl PipelineManager {
pub fn new() -> Self {
Self {
pipelines: std::collections::HashMap::new(),
}
}
pub fn register_pipeline(&mut self, name: String, pipeline: Arc<GraphicsPipeline>) {
self.pipelines.insert(name, pipeline);
}
pub fn get_pipeline(&self, name: &str) -> Option<&Arc<GraphicsPipeline>> {
self.pipelines.get(name)
}
}

View file

@ -1,127 +0,0 @@
use std::collections::BTreeMap;
use std::error::Error;
use std::sync::Arc;
use vulkano::descriptor_set::layout::{
DescriptorSetLayoutBinding, DescriptorSetLayoutCreateInfo, DescriptorType,
};
use vulkano::device::Device;
use vulkano::pipeline::graphics::GraphicsPipelineCreateInfo;
use vulkano::pipeline::graphics::color_blend::{ColorBlendAttachmentState, ColorBlendState};
use vulkano::pipeline::graphics::input_assembly::InputAssemblyState;
use vulkano::pipeline::graphics::multisample::MultisampleState;
use vulkano::pipeline::graphics::rasterization::RasterizationState;
use vulkano::pipeline::graphics::subpass::PipelineRenderingCreateInfo;
use vulkano::pipeline::graphics::vertex_input::{Vertex, VertexDefinition};
use vulkano::pipeline::graphics::viewport::ViewportState;
use vulkano::pipeline::layout::{PipelineDescriptorSetLayoutCreateInfo, PipelineLayoutCreateFlags};
use vulkano::pipeline::{
DynamicState, GraphicsPipeline, PipelineLayout, PipelineShaderStageCreateInfo,
};
use vulkano::shader::{EntryPoint, ShaderStages};
use crate::vulkan::resources::vertex::Vertex2D;
use super::Pipeline;
mod shaders {
pub mod vs {
vulkano_shaders::shader! {
ty: "vertex",
path: r"res/shaders/vertex.vert",
}
}
pub mod fs {
vulkano_shaders::shader! {
ty: "fragment",
path: r"res/shaders/vertex.frag",
}
}
}
pub struct TrianglePipeline {
pipeline: Arc<GraphicsPipeline>,
}
impl super::Pipeline for TrianglePipeline {
fn create_pipeline(device: &Arc<Device>) -> Arc<GraphicsPipeline> {
let (vs, fs) = load_shaders(device).unwrap();
let vertex_input_state = Vertex2D::per_vertex().definition(&vs).unwrap();
let stages = [
PipelineShaderStageCreateInfo::new(vs),
PipelineShaderStageCreateInfo::new(fs),
];
let mut bindings = BTreeMap::<u32, DescriptorSetLayoutBinding>::new();
let mut descriptor_set_layout_binding =
DescriptorSetLayoutBinding::descriptor_type(DescriptorType::UniformBuffer);
descriptor_set_layout_binding.stages = ShaderStages::VERTEX;
bindings.insert(0, descriptor_set_layout_binding);
let descriptor_set_layout = DescriptorSetLayoutCreateInfo {
bindings,
..Default::default()
};
let create_info = PipelineDescriptorSetLayoutCreateInfo {
set_layouts: vec![descriptor_set_layout],
flags: PipelineLayoutCreateFlags::default(),
push_constant_ranges: vec![],
}
.into_pipeline_layout_create_info(device.clone())
.unwrap();
let layout = PipelineLayout::new(device.clone(), create_info).unwrap();
let subpass = PipelineRenderingCreateInfo {
color_attachment_formats: vec![Some(vulkano::format::Format::B8G8R8A8_UNORM)],
..Default::default()
};
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(),
)),
dynamic_state: [DynamicState::Viewport].into_iter().collect(),
subpass: Some(subpass.into()),
..GraphicsPipelineCreateInfo::layout(layout)
},
)
.unwrap()
}
fn get_pipeline(&self) -> &Arc<GraphicsPipeline> {
&self.pipeline
}
}
impl TrianglePipeline {
pub fn new(device: &Arc<Device>) -> Self {
Self {
pipeline: Self::create_pipeline(device),
}
}
}
fn load_shaders(device: &Arc<Device>) -> Result<(EntryPoint, EntryPoint), Box<dyn Error>> {
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())?;
Ok((vs, fs))
}

View file

@ -1,167 +0,0 @@
use std::sync::Arc;
use vulkano::command_buffer::{AutoCommandBufferBuilder, PrimaryAutoCommandBuffer};
use vulkano::device::Queue;
use vulkano::image::view::ImageView;
use vulkano::pipeline::graphics::viewport::Viewport;
use vulkano::swapchain::{Surface, Swapchain, SwapchainCreateInfo, SwapchainPresentInfo};
use vulkano::sync::{self, GpuFuture};
use vulkano::{Validated, VulkanError};
use winit::window::Window;
pub struct VulkanRenderer {
pub window: Arc<Window>,
pub surface: Arc<Surface>,
pub swapchain: Arc<Swapchain>,
pub queue: Arc<Queue>,
pub attachment_image_views: Vec<Arc<ImageView>>,
pub previous_frame_end: Option<Box<dyn GpuFuture>>,
pub recreate_swapchain: bool,
pub viewport: Viewport,
}
impl VulkanRenderer {
pub fn new(
window: Arc<Window>,
surface: Arc<Surface>,
device: Arc<vulkano::device::Device>,
queue: Arc<Queue>,
) -> Self {
let window_size = window.inner_size();
let surface_formats = device
.physical_device()
.surface_formats(&surface, Default::default())
.unwrap();
let surface_format = surface_formats[0];
let (swapchain, images) = Swapchain::new(
device.clone(),
surface.clone(),
SwapchainCreateInfo {
image_extent: window_size.into(),
image_usage: vulkano::image::ImageUsage::COLOR_ATTACHMENT,
image_format: surface_format.0,
..Default::default()
},
)
.unwrap();
let attachment_image_views = images
.into_iter()
.map(|image| ImageView::new_default(image).unwrap())
.collect();
let viewport = Viewport {
offset: [0.0, 0.0],
extent: window_size.into(),
depth_range: 0.0..=1.0,
};
Self {
window,
surface,
swapchain,
queue,
attachment_image_views,
previous_frame_end: Some(sync::now(device).boxed()),
recreate_swapchain: false,
viewport,
}
}
pub fn begin_frame(&mut self) -> Result<(u32, Box<dyn GpuFuture>), VulkanError> {
self.previous_frame_end.as_mut().unwrap().cleanup_finished();
if self.recreate_swapchain {
self.recreate_swapchain();
self.recreate_swapchain = false;
}
let (image_index, suboptimal, acquire_future) =
match vulkano::swapchain::acquire_next_image(self.swapchain.clone(), None)
.map_err(Validated::unwrap)
{
Ok(r) => r,
Err(VulkanError::OutOfDate) => {
self.recreate_swapchain = true;
return Err(VulkanError::OutOfDate);
}
Err(e) => panic!("failed to acquire next image: {e}"),
};
if suboptimal {
self.recreate_swapchain = true;
}
Ok((image_index, acquire_future.boxed()))
}
pub fn end_frame(
&mut self,
image_index: u32,
acquire_future: Box<dyn GpuFuture>,
command_buffer: AutoCommandBufferBuilder<PrimaryAutoCommandBuffer>,
) -> Result<(), VulkanError> {
let command_buffer = command_buffer.build().unwrap();
let future = self
.previous_frame_end
.take()
.unwrap()
.join(acquire_future)
.then_execute(self.queue.clone(), command_buffer)
.unwrap()
.then_swapchain_present(
self.queue.clone(),
SwapchainPresentInfo::swapchain_image_index(self.swapchain.clone(), image_index),
)
.then_signal_fence_and_flush();
match future.map_err(Validated::unwrap) {
Ok(future) => {
self.previous_frame_end = Some(future.boxed());
Ok(())
}
Err(VulkanError::OutOfDate) => {
self.recreate_swapchain = true;
self.previous_frame_end = Some(sync::now(self.queue.device().clone()).boxed());
Ok(())
}
Err(e) => {
println!("failed to flush future: {e}");
self.previous_frame_end = Some(sync::now(self.queue.device().clone()).boxed());
Ok(())
}
}
}
fn recreate_swapchain(&mut self) {
let image_extent: [u32; 2] = self.window.inner_size().into();
if image_extent.contains(&0) {
return;
}
let surface_formats = self
.queue
.device()
.physical_device()
.surface_formats(&self.surface, Default::default())
.unwrap();
let surface_format = surface_formats[0];
let (new_swapchain, new_images) = self
.swapchain
.recreate(SwapchainCreateInfo {
image_extent,
image_usage: vulkano::image::ImageUsage::COLOR_ATTACHMENT,
image_format: surface_format.0,
..self.swapchain.create_info()
})
.expect("failed to recreate swapchain");
self.swapchain = new_swapchain;
self.attachment_image_views = new_images
.into_iter()
.map(|image| ImageView::new_default(image).unwrap())
.collect();
self.viewport.extent = [image_extent[0] as f32, image_extent[1] as f32];
}
}

View file

@ -1,65 +0,0 @@
use std::sync::Arc;
use vulkano::buffer::BufferContents;
use vulkano::buffer::{Buffer, BufferCreateInfo, BufferUsage, Subbuffer};
use vulkano::memory::allocator::{AllocationCreateInfo, MemoryTypeFilter, StandardMemoryAllocator};
pub struct BufferManager {
memory_allocator: Arc<StandardMemoryAllocator>,
}
impl BufferManager {
pub fn new(memory_allocator: Arc<StandardMemoryAllocator>) -> Self {
Self { memory_allocator }
}
pub fn create_vertex_buffer<T: BufferContents + Clone>(&self, data: &[T]) -> Subbuffer<[T]> {
Buffer::from_iter(
self.memory_allocator.clone(),
BufferCreateInfo {
usage: BufferUsage::VERTEX_BUFFER,
..Default::default()
},
AllocationCreateInfo {
memory_type_filter: MemoryTypeFilter::PREFER_DEVICE
| MemoryTypeFilter::HOST_SEQUENTIAL_WRITE,
..Default::default()
},
data.iter().cloned(),
)
.unwrap()
}
pub fn create_index_buffer(&self, data: &[u32]) -> Subbuffer<[u32]> {
Buffer::from_iter(
self.memory_allocator.clone(),
BufferCreateInfo {
usage: BufferUsage::INDEX_BUFFER,
..Default::default()
},
AllocationCreateInfo {
memory_type_filter: MemoryTypeFilter::PREFER_DEVICE
| MemoryTypeFilter::HOST_SEQUENTIAL_WRITE,
..Default::default()
},
data.iter().cloned(),
)
.unwrap()
}
pub fn create_uniform_buffer<T: BufferContents + Copy>(&self, data: &T) -> Subbuffer<T> {
Buffer::from_data(
self.memory_allocator.clone(),
BufferCreateInfo {
usage: BufferUsage::UNIFORM_BUFFER,
..Default::default()
},
AllocationCreateInfo {
memory_type_filter: MemoryTypeFilter::PREFER_DEVICE
| MemoryTypeFilter::HOST_SEQUENTIAL_WRITE,
..Default::default()
},
*data,
)
.unwrap()
}
}

View file

@ -1,47 +0,0 @@
use std::sync::Arc;
use vulkano::descriptor_set::DescriptorSet;
use vulkano::descriptor_set::WriteDescriptorSet;
use vulkano::descriptor_set::allocator::StandardDescriptorSetAllocator;
use vulkano::descriptor_set::layout::{
DescriptorSetLayout, DescriptorSetLayoutBinding, DescriptorSetLayoutCreateInfo, DescriptorType,
};
use vulkano::device::Device;
use vulkano::shader::ShaderStages;
pub struct DescriptorManager {
device: Arc<Device>,
allocator: Arc<StandardDescriptorSetAllocator>,
}
impl DescriptorManager {
pub fn new(device: Arc<Device>, allocator: Arc<StandardDescriptorSetAllocator>) -> Self {
Self { device, allocator }
}
pub fn create_descriptor_set_layout(
&self,
bindings: &[(u32, DescriptorType, u32)],
) -> Arc<DescriptorSetLayout> {
let mut bindings_map = std::collections::BTreeMap::new();
for (binding_index, ty, _count) in bindings {
let mut binding = DescriptorSetLayoutBinding::descriptor_type(*ty);
binding.stages = ShaderStages::all_graphics();
bindings_map.insert(*binding_index, binding);
}
let create_info = DescriptorSetLayoutCreateInfo {
bindings: bindings_map,
..Default::default()
};
DescriptorSetLayout::new(self.device.clone(), create_info).unwrap()
}
pub fn create_descriptor_set(
&self,
layout: &Arc<DescriptorSetLayout>,
writes: Vec<WriteDescriptorSet>,
) -> Arc<DescriptorSet> {
DescriptorSet::new(self.allocator.clone(), layout.clone(), writes, []).unwrap()
}
}

View file

@ -1,3 +0,0 @@
pub mod buffer;
pub mod descriptor;
pub mod vertex;

View file

@ -1,46 +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};
use vulkano::pipeline::graphics::vertex_input::Vertex;
#[derive(BufferContents, Vertex, Clone)]
#[repr(C)]
pub struct Vertex2D {
#[format(R32G32_SFLOAT)]
pub position: [f32; 2],
#[format(R32G32B32_SFLOAT)]
pub color: [f32; 3],
}
#[derive(BufferContents, Clone)]
#[repr(C)]
pub struct MVPData {
pub world: [[f32; 4]; 4],
pub view: [[f32; 4]; 4],
pub projection: [[f32; 4]; 4],
}
impl Vertex2D {
pub fn create_buffer(
vertices: Vec<Vertex2D>,
memory_allocator: &Arc<StandardMemoryAllocator>,
) -> Result<Subbuffer<[Vertex2D]>, Validated<AllocateBufferError>> {
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()
},
vertices.into_iter(),
)
}
}