feat: Vulkan renderer with instanced rendering (Phase 4)
VulkanRenderer using ash 0.38 + winit:
- Instance with KHR_surface + xlib + wayland extensions
- Physical device selection (first GPU with graphics+present)
- Swapchain (FIFO present mode, B8G8R8A8_UNORM)
- Render pass with color attachment
- Graphics pipeline with instanced rendering:
- Vertex buffer: unit quad (4 verts, 6 indices)
- Instance buffer: 8000 CellInstance structs (32 bytes each)
- 2 vertex bindings: per-vertex (quad pos) + per-instance (grid pos,
atlas UV, fg/bg colors)
- Push constants: screen size + cell size
- Glyph atlas: R8_UNORM texture, uploaded via staging buffer
- fontdue rasterizes DejaVu Sans Mono at startup
- Linear filtering sampler
- Descriptor set: combined image sampler for atlas
- 2 frames in flight with semaphores + fences
- Persistent mapped instance buffer (zero-copy per frame)
Shaders (pre-compiled SPIR-V):
- cell.vert: positions quad from instance data, maps to clip space
- cell.frag: samples atlas alpha, blends fg/bg
--mode vulkan: tries Vulkan, falls back to softbuffer window mode
109 tests, 0 failures
This commit is contained in:
@@ -12,6 +12,10 @@ fontdue = "0.9"
|
|||||||
winit = "0.30"
|
winit = "0.30"
|
||||||
softbuffer = "0.4"
|
softbuffer = "0.4"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
|
ash = "0.38"
|
||||||
|
ash-window = "0.13"
|
||||||
|
raw-window-handle = "0.6"
|
||||||
|
bytemuck = "1"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
opt-level = 3
|
opt-level = 3
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
#version 450
|
||||||
|
|
||||||
|
layout(location = 0) in vec2 in_uv;
|
||||||
|
layout(location = 1) in vec4 in_fg;
|
||||||
|
layout(location = 2) in vec4 in_bg;
|
||||||
|
|
||||||
|
layout(binding = 0) uniform sampler2D atlas;
|
||||||
|
|
||||||
|
layout(location = 0) out vec4 out_color;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
float alpha = texture(atlas, in_uv).r;
|
||||||
|
out_color = mix(in_bg, in_fg, alpha);
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
#version 450
|
||||||
|
|
||||||
|
layout(push_constant) uniform PC {
|
||||||
|
vec2 screen_size;
|
||||||
|
vec2 cell_size;
|
||||||
|
} pc;
|
||||||
|
|
||||||
|
layout(location = 0) in vec2 in_pos;
|
||||||
|
layout(location = 1) in vec2 in_grid;
|
||||||
|
layout(location = 2) in vec4 in_atlas;
|
||||||
|
layout(location = 3) in vec4 in_fg;
|
||||||
|
layout(location = 4) in vec4 in_bg;
|
||||||
|
|
||||||
|
layout(location = 0) out vec2 out_uv;
|
||||||
|
layout(location = 1) out vec4 out_fg;
|
||||||
|
layout(location = 2) out vec4 out_bg;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec2 pixel = in_grid * pc.cell_size + in_pos * pc.cell_size;
|
||||||
|
vec2 clip = vec2(
|
||||||
|
2.0 * pixel.x / pc.screen_size.x - 1.0,
|
||||||
|
1.0 - 2.0 * pixel.y / pc.screen_size.y
|
||||||
|
);
|
||||||
|
gl_Position = vec4(clip, 0.0, 1.0);
|
||||||
|
out_uv = in_atlas.xy + in_pos * in_atlas.zw;
|
||||||
|
out_fg = in_fg;
|
||||||
|
out_bg = in_bg;
|
||||||
|
}
|
||||||
Binary file not shown.
Binary file not shown.
+183
-183
@@ -1,4 +1,4 @@
|
|||||||
=== Verbatim Headless Run: 20 ticks ===
|
=== Verbatim Headless Run: 60 ticks ===
|
||||||
|
|
||||||
World: 250x250
|
World: 250x250
|
||||||
Player start: (125.0, 242.0)
|
Player start: (125.0, 242.0)
|
||||||
@@ -33,66 +33,6 @@ Camera: (85, 230)
|
|||||||
Player: "Player hp=100.0/100.0 pos=(125.0,242.0) bodies=27/27 on_fire=false"
|
Player: "Player hp=100.0/100.0 pos=(125.0,242.0) bodies=27/27 on_fire=false"
|
||||||
Entities: 1
|
Entities: 1
|
||||||
|
|
||||||
=== Tick 2 ===
|
|
||||||
30
|
|
||||||
31
|
|
||||||
32
|
|
||||||
33
|
|
||||||
34
|
|
||||||
35
|
|
||||||
36
|
|
||||||
37 TTTTT
|
|
||||||
38 T T
|
|
||||||
39 T T .
|
|
||||||
40 T T @@@@@ ...
|
|
||||||
41 T T ## @@@@@@ .....
|
|
||||||
42 T T ## @@@@@@ .......
|
|
||||||
43 T T ## @@@@@ """"""""""""" .........
|
|
||||||
44""" T T ## @@@@@ "":::::::::::::""" ..........
|
|
||||||
45:::""" ## """::::::::::::::::::"" ...........
|
|
||||||
46::::::"" ## "":::::::::::::::::::::::""".........
|
|
||||||
47::::::::"""""""""""""""""""""""""""""""""""::::::::::::::::::::::::::::"""""""""
|
|
||||||
48::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
|
||||||
49################################################################################
|
|
||||||
50????????????????????????????????????????????????????????????????????????????????
|
|
||||||
51????????????????????????????????????????????????????????????????????????????????
|
|
||||||
52????????????????????????????????????????????????????????????????????????????????
|
|
||||||
53????????????????????????????????????????????????????????????????????????????????
|
|
||||||
54????????????????????????????????????????????????????????????????????????????????
|
|
||||||
Player: "Player hp=100.0/100.0 pos=(125.0,242.1) bodies=27/27 on_fire=false"
|
|
||||||
Entities: 1
|
|
||||||
Alive entities: Player(hp=100, pos=(125.0, 242.11879))
|
|
||||||
|
|
||||||
=== Tick 4 ===
|
|
||||||
30
|
|
||||||
31
|
|
||||||
32
|
|
||||||
33
|
|
||||||
34
|
|
||||||
35
|
|
||||||
36
|
|
||||||
37 TTTTT
|
|
||||||
38 T T
|
|
||||||
39 T T .
|
|
||||||
40 T T @@@@@ ...
|
|
||||||
41 T T ## @@@@@@ .....
|
|
||||||
42 T T ## @@@@@@ .......
|
|
||||||
43 T T ## @@@@@ """"""""""""" .........
|
|
||||||
44""" T T ## @@@@@ "":::::::::::::""" ..........
|
|
||||||
45:::""" ## """::::::::::::::::::"" ...........
|
|
||||||
46::::::"" ## "":::::::::::::::::::::::""".........
|
|
||||||
47::::::::"""""""""""""""""""""""""""""""""""::::::::::::::::::::::::::::"""""""""
|
|
||||||
48::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
|
||||||
49################################################################################
|
|
||||||
50????????????????????????????????????????????????????????????????????????????????
|
|
||||||
51????????????????????????????????????????????????????????????????????????????????
|
|
||||||
52????????????????????????????????????????????????????????????????????????????????
|
|
||||||
53????????????????????????????????????????????????????????????????????????????????
|
|
||||||
54????????????????????????????????????????????????????????????????????????????????
|
|
||||||
Player: "Player hp=100.0/100.0 pos=(125.0,242.4) bodies=27/27 on_fire=false"
|
|
||||||
Entities: 1
|
|
||||||
Alive entities: Player(hp=100, pos=(125.0, 242.38817))
|
|
||||||
|
|
||||||
=== Tick 6 ===
|
=== Tick 6 ===
|
||||||
30
|
30
|
||||||
31
|
31
|
||||||
@@ -121,67 +61,7 @@ Alive entities: Player(hp=100, pos=(125.0, 242.38817))
|
|||||||
54????????????????????????????????????????????????????????????????????????????????
|
54????????????????????????????????????????????????????????????????????????????????
|
||||||
Player: "Player hp=100.0/100.0 pos=(125.0,242.8) bodies=27/27 on_fire=false"
|
Player: "Player hp=100.0/100.0 pos=(125.0,242.8) bodies=27/27 on_fire=false"
|
||||||
Entities: 1
|
Entities: 1
|
||||||
Alive entities: Player(hp=100, pos=(125.0, 242.79922))
|
Alive entities: Player(hp=100, pos=(125.0, 242.82616))
|
||||||
|
|
||||||
=== Tick 8 ===
|
|
||||||
31
|
|
||||||
32
|
|
||||||
33
|
|
||||||
34
|
|
||||||
35
|
|
||||||
36
|
|
||||||
37 TTTTT
|
|
||||||
38 T T
|
|
||||||
39 T T .
|
|
||||||
40 T T ...
|
|
||||||
41 T T ## @@@@@ .....
|
|
||||||
42 T T ## @@@@@@ .......
|
|
||||||
43 T T ## @@@@@@ """"""""""""" .........
|
|
||||||
44""" T T ## @@@@@ "":::::::::::::""" ..........
|
|
||||||
45:::""" ## @@@@@ """::::::::::::::::::"" ...........
|
|
||||||
46::::::"" ## "":::::::::::::::::::::::""".........
|
|
||||||
47::::::::"""""""""""""""""""""""""""""""""""::::::::::::::::::::::::::::"""""""""
|
|
||||||
48::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
|
||||||
49################################################################################
|
|
||||||
50????????????????????????????????????????????????????????????????????????????????
|
|
||||||
51????????????????????????????????????????????????????????????????????????????????
|
|
||||||
52????????????????????????????????????????????????????????????????????????????????
|
|
||||||
53????????????????????????????????????????????????????????????????????????????????
|
|
||||||
54????????????????????????????????????????????????????????????????????????????????
|
|
||||||
55????????????????????????????????????????????????????????????????????????????????
|
|
||||||
Player: "Player hp=100.0/100.0 pos=(125.0,243.3) bodies=27/27 on_fire=false"
|
|
||||||
Entities: 1
|
|
||||||
Alive entities: Player(hp=100, pos=(125.0, 243.3436))
|
|
||||||
|
|
||||||
=== Tick 10 ===
|
|
||||||
31
|
|
||||||
32
|
|
||||||
33
|
|
||||||
34
|
|
||||||
35
|
|
||||||
36
|
|
||||||
37 TTTTT
|
|
||||||
38 T T
|
|
||||||
39 T T .
|
|
||||||
40 T T ...
|
|
||||||
41 T T ## @@@@@ .....
|
|
||||||
42 T T ## @@@@@@ .......
|
|
||||||
43 T T ## @@@@@@ """"""""""""" .........
|
|
||||||
44""" T T ## @@@@@ "":::::::::::::""" ..........
|
|
||||||
45:::""" ## @@@@@ """::::::::::::::::::"" ...........
|
|
||||||
46::::::"" ## "":::::::::::::::::::::::""".........
|
|
||||||
47::::::::"""""""""""""""""""""""""""""""""""::::::::::::::::::::::::::::"""""""""
|
|
||||||
48::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
|
||||||
49################################################################################
|
|
||||||
50????????????????????????????????????????????????????????????????????????????????
|
|
||||||
51????????????????????????????????????????????????????????????????????????????????
|
|
||||||
52????????????????????????????????????????????????????????????????????????????????
|
|
||||||
53????????????????????????????????????????????????????????????????????????????????
|
|
||||||
54????????????????????????????????????????????????????????????????????????????????
|
|
||||||
55????????????????????????????????????????????????????????????????????????????????
|
|
||||||
Player: "Player hp=100.0/100.0 pos=(125.0,243.5) bodies=27/27 on_fire=false"
|
|
||||||
Entities: 1
|
|
||||||
Alive entities: Player(hp=100, pos=(125.0, 243.5))
|
|
||||||
|
|
||||||
=== Tick 12 ===
|
=== Tick 12 ===
|
||||||
31
|
31
|
||||||
@@ -213,66 +93,6 @@ Player: "Player hp=100.0/100.0 pos=(125.0,243.5) bodies=27/27 on_fire=false"
|
|||||||
Entities: 1
|
Entities: 1
|
||||||
Alive entities: Player(hp=100, pos=(125.0, 243.5))
|
Alive entities: Player(hp=100, pos=(125.0, 243.5))
|
||||||
|
|
||||||
=== Tick 14 ===
|
|
||||||
31
|
|
||||||
32
|
|
||||||
33
|
|
||||||
34
|
|
||||||
35
|
|
||||||
36
|
|
||||||
37 TTTTT
|
|
||||||
38 T T
|
|
||||||
39 T T .
|
|
||||||
40 T T ...
|
|
||||||
41 T T ## @@@@@ .....
|
|
||||||
42 T T ## @@@@@@ .......
|
|
||||||
43 T T ## @@@@@@ """"""""""""" .........
|
|
||||||
44""" T T ## @@@@@ "":::::::::::::""" ..........
|
|
||||||
45:::""" ## @@@@@ """::::::::::::::::::"" ...........
|
|
||||||
46::::::"" ## "":::::::::::::::::::::::""".........
|
|
||||||
47::::::::"""""""""""""""""""""""""""""""""""::::::::::::::::::::::::::::"""""""""
|
|
||||||
48::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
|
||||||
49################################################################################
|
|
||||||
50????????????????????????????????????????????????????????????????????????????????
|
|
||||||
51????????????????????????????????????????????????????????????????????????????????
|
|
||||||
52????????????????????????????????????????????????????????????????????????????????
|
|
||||||
53????????????????????????????????????????????????????????????????????????????????
|
|
||||||
54????????????????????????????????????????????????????????????????????????????????
|
|
||||||
55????????????????????????????????????????????????????????????????????????????????
|
|
||||||
Player: "Player hp=100.0/100.0 pos=(125.0,243.5) bodies=27/27 on_fire=false"
|
|
||||||
Entities: 1
|
|
||||||
Alive entities: Player(hp=100, pos=(125.0, 243.5))
|
|
||||||
|
|
||||||
=== Tick 16 ===
|
|
||||||
31
|
|
||||||
32
|
|
||||||
33
|
|
||||||
34
|
|
||||||
35
|
|
||||||
36
|
|
||||||
37 TTTTT
|
|
||||||
38 T T
|
|
||||||
39 T T .
|
|
||||||
40 T T ...
|
|
||||||
41 T T ## @@@@@ .....
|
|
||||||
42 T T ## @@@@@@ .......
|
|
||||||
43 T T ## @@@@@@ """"""""""""" .........
|
|
||||||
44""" T T ## @@@@@ "":::::::::::::""" ..........
|
|
||||||
45:::""" ## @@@@@ """::::::::::::::::::"" ...........
|
|
||||||
46::::::"" ## "":::::::::::::::::::::::""".........
|
|
||||||
47::::::::"""""""""""""""""""""""""""""""""""::::::::::::::::::::::::::::"""""""""
|
|
||||||
48::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
|
||||||
49################################################################################
|
|
||||||
50????????????????????????????????????????????????????????????????????????????????
|
|
||||||
51????????????????????????????????????????????????????????????????????????????????
|
|
||||||
52????????????????????????????????????????????????????????????????????????????????
|
|
||||||
53????????????????????????????????????????????????????????????????????????????????
|
|
||||||
54????????????????????????????????????????????????????????????????????????????????
|
|
||||||
55????????????????????????????????????????????????????????????????????????????????
|
|
||||||
Player: "Player hp=100.0/100.0 pos=(125.0,243.5) bodies=27/27 on_fire=false"
|
|
||||||
Entities: 1
|
|
||||||
Alive entities: Player(hp=100, pos=(125.0, 243.5))
|
|
||||||
|
|
||||||
=== Tick 18 ===
|
=== Tick 18 ===
|
||||||
31
|
31
|
||||||
32
|
32
|
||||||
@@ -303,7 +123,7 @@ Player: "Player hp=100.0/100.0 pos=(125.0,243.5) bodies=27/27 on_fire=false"
|
|||||||
Entities: 1
|
Entities: 1
|
||||||
Alive entities: Player(hp=100, pos=(125.0, 243.5))
|
Alive entities: Player(hp=100, pos=(125.0, 243.5))
|
||||||
|
|
||||||
=== Tick 20 ===
|
=== Tick 24 ===
|
||||||
31
|
31
|
||||||
32
|
32
|
||||||
33
|
33
|
||||||
@@ -333,3 +153,183 @@ Player: "Player hp=100.0/100.0 pos=(125.0,243.5) bodies=27/27 on_fire=false"
|
|||||||
Entities: 1
|
Entities: 1
|
||||||
Alive entities: Player(hp=100, pos=(125.0, 243.5))
|
Alive entities: Player(hp=100, pos=(125.0, 243.5))
|
||||||
|
|
||||||
|
=== Tick 30 ===
|
||||||
|
31
|
||||||
|
32
|
||||||
|
33
|
||||||
|
34
|
||||||
|
35
|
||||||
|
36
|
||||||
|
37 TTTTT
|
||||||
|
38 T T
|
||||||
|
39 T T .
|
||||||
|
40 T T ggggg ...
|
||||||
|
41 T T gggggg @@@@@ .....
|
||||||
|
42 T T gggggg @@@@@@ .......
|
||||||
|
43 T T ggggg @@@@@@ """"""""""""" .........
|
||||||
|
44""" T T ggggg @@@@@ "":::::::::::::""" ..........
|
||||||
|
45:::""" ## @@@@@ """::::::::::::::::::"" ...........
|
||||||
|
46::::::"" ## "":::::::::::::::::::::::""".........
|
||||||
|
47::::::::"""""""""""""""""""""""""""""""""""::::::::::::::::::::::::::::"""""""""
|
||||||
|
48::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||||
|
49################################################################################
|
||||||
|
50????????????????????????????????????????????????????????????????????????????????
|
||||||
|
51????????????????????????????????????????????????????????????????????????????????
|
||||||
|
52????????????????????????????????????????????????????????????????????????????????
|
||||||
|
53????????????????????????????????????????????????????????????????????????????????
|
||||||
|
54????????????????????????????????????????????????????????????????????????????????
|
||||||
|
55????????????????????????????????????????????????????????????????????????????????
|
||||||
|
Player: "Player hp=100.0/100.0 pos=(125.0,243.5) bodies=27/27 on_fire=false"
|
||||||
|
Entities: 2
|
||||||
|
Alive entities: Player(hp=100, pos=(125.0, 243.5)), Goblin(hp=40, pos=(110.0, 242.0))
|
||||||
|
|
||||||
|
=== Tick 36 ===
|
||||||
|
31
|
||||||
|
32
|
||||||
|
33
|
||||||
|
34
|
||||||
|
35
|
||||||
|
36 ggggg
|
||||||
|
37 TTTTT gggggg
|
||||||
|
38 T T gggggg
|
||||||
|
39 T T ggggg .
|
||||||
|
40 T T ggggg ...
|
||||||
|
41 T T ## @@@@@ .....
|
||||||
|
42 T T ## @@@@@@ .......
|
||||||
|
43 T T ## @@@@@@ """"""""""""" .........
|
||||||
|
44""" T T ## @@@@@ "":::::::::::::""" ..........
|
||||||
|
45:::""" ## @@@@@ """::::::::::::::::::"" ...........
|
||||||
|
46::::::"" ## "":::::::::::::::::::::::""".........
|
||||||
|
47::::::::"""""""""""""""""""""""""""""""""""::::::::::::::::::::::::::::"""""""""
|
||||||
|
48::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||||
|
49################################################################################
|
||||||
|
50????????????????????????????????????????????????????????????????????????????????
|
||||||
|
51????????????????????????????????????????????????????????????????????????????????
|
||||||
|
52????????????????????????????????????????????????????????????????????????????????
|
||||||
|
53????????????????????????????????????????????????????????????????????????????????
|
||||||
|
54????????????????????????????????????????????????????????????????????????????????
|
||||||
|
55????????????????????????????????????????????????????????????????????????????????
|
||||||
|
Player: "Player hp=100.0/100.0 pos=(125.0,243.5) bodies=27/27 on_fire=false"
|
||||||
|
Entities: 2
|
||||||
|
Alive entities: Player(hp=100, pos=(125.0, 243.5)), Goblin(hp=40, pos=(110.0, 238.5))
|
||||||
|
|
||||||
|
=== Tick 42 ===
|
||||||
|
31
|
||||||
|
32
|
||||||
|
33
|
||||||
|
34
|
||||||
|
35
|
||||||
|
36 ggggg
|
||||||
|
37 TTTTT gggggg
|
||||||
|
38 T T gggggg
|
||||||
|
39 T T ggggg .
|
||||||
|
40 T T ggggg ...
|
||||||
|
41 T T ## @@@@@ .....
|
||||||
|
42 T T ## @@@@@@ .......
|
||||||
|
43 T T ## @@@@@@ """"""""""""" .........
|
||||||
|
44""" T T ## @@@@@ "":::::::::::::""" ..........
|
||||||
|
45:::""" ## @@@@@ """::::::::::::::::::"" ...........
|
||||||
|
46::::::"" ## "":::::::::::::::::::::::""".........
|
||||||
|
47::::::::"""""""""""""""""""""""""""""""""""::::::::::::::::::::::::::::"""""""""
|
||||||
|
48::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||||
|
49################################################################################
|
||||||
|
50????????????????????????????????????????????????????????????????????????????????
|
||||||
|
51????????????????????????????????????????????????????????????????????????????????
|
||||||
|
52????????????????????????????????????????????????????????????????????????????????
|
||||||
|
53????????????????????????????????????????????????????????????????????????????????
|
||||||
|
54????????????????????????????????????????????????????????????????????????????????
|
||||||
|
55????????????????????????????????????????????????????????????????????????????????
|
||||||
|
Player: "Player hp=100.0/100.0 pos=(125.0,243.5) bodies=27/27 on_fire=false"
|
||||||
|
Entities: 2
|
||||||
|
Alive entities: Player(hp=100, pos=(125.0, 243.5)), Goblin(hp=40, pos=(110.0, 238.5))
|
||||||
|
|
||||||
|
=== Tick 48 ===
|
||||||
|
31
|
||||||
|
32
|
||||||
|
33
|
||||||
|
34
|
||||||
|
35
|
||||||
|
36 ggggg
|
||||||
|
37 TTTTT gggggg
|
||||||
|
38 T T gggggg
|
||||||
|
39 T T ggggg .
|
||||||
|
40 T T ggggg ...
|
||||||
|
41 T T ## @@@@@ .....
|
||||||
|
42 T T ## @@@@@@ .......
|
||||||
|
43 T T ## @@@@@@ """"""""""""" .........
|
||||||
|
44""" T T ## @@@@@ "":::::::::::::""" ..........
|
||||||
|
45:::""" ## @@@@@ """::::::::::::::::::"" ...........
|
||||||
|
46::::::"" ## "":::::::::::::::::::::::""".........
|
||||||
|
47::::::::"""""""""""""""""""""""""""""""""""::::::::::::::::::::::::::::"""""""""
|
||||||
|
48::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||||
|
49################################################################################
|
||||||
|
50????????????????????????????????????????????????????????????????????????????????
|
||||||
|
51????????????????????????????????????????????????????????????????????????????????
|
||||||
|
52????????????????????????????????????????????????????????????????????????????????
|
||||||
|
53????????????????????????????????????????????????????????????????????????????????
|
||||||
|
54????????????????????????????????????????????????????????????????????????????????
|
||||||
|
55????????????????????????????????????????????????????????????????????????????????
|
||||||
|
Player: "Player hp=100.0/100.0 pos=(125.0,243.5) bodies=27/27 on_fire=false"
|
||||||
|
Entities: 2
|
||||||
|
Alive entities: Player(hp=100, pos=(125.0, 243.5)), Goblin(hp=40, pos=(110.0, 238.5))
|
||||||
|
|
||||||
|
=== Tick 54 ===
|
||||||
|
31
|
||||||
|
32
|
||||||
|
33
|
||||||
|
34
|
||||||
|
35
|
||||||
|
36 ggggg
|
||||||
|
37 TTTTT gggggg
|
||||||
|
38 T T gggggg
|
||||||
|
39 T T ggggg .
|
||||||
|
40 T T ggggg ...
|
||||||
|
41 T T ## @@@@@ .....
|
||||||
|
42 T T ## @@@@@@ .......
|
||||||
|
43 T T ## @@@@@@ """"""""""""" .........
|
||||||
|
44""" T T ## @@@@@ "":::::::::::::""" ..........
|
||||||
|
45:::""" ## @@@@@ """::::::::::::::::::"" ...........
|
||||||
|
46::::::"" ## "":::::::::::::::::::::::""".........
|
||||||
|
47::::::::"""""""""""""""""""""""""""""""""""::::::::::::::::::::::::::::"""""""""
|
||||||
|
48::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||||
|
49################################################################################
|
||||||
|
50????????????????????????????????????????????????????????????????????????????????
|
||||||
|
51????????????????????????????????????????????????????????????????????????????????
|
||||||
|
52????????????????????????????????????????????????????????????????????????????????
|
||||||
|
53????????????????????????????????????????????????????????????????????????????????
|
||||||
|
54????????????????????????????????????????????????????????????????????????????????
|
||||||
|
55????????????????????????????????????????????????????????????????????????????????
|
||||||
|
Player: "Player hp=100.0/100.0 pos=(125.0,243.5) bodies=27/27 on_fire=false"
|
||||||
|
Entities: 2
|
||||||
|
Alive entities: Player(hp=100, pos=(125.0, 243.5)), Goblin(hp=40, pos=(110.0, 238.5))
|
||||||
|
|
||||||
|
=== Tick 60 ===
|
||||||
|
31
|
||||||
|
32
|
||||||
|
33
|
||||||
|
34
|
||||||
|
35
|
||||||
|
36 ggggg
|
||||||
|
37 TTTTT gggggg
|
||||||
|
38 T T gggggg
|
||||||
|
39 T T ggggg .
|
||||||
|
40 T T ggggg ...
|
||||||
|
41 T T gggggg @@@@@ .....
|
||||||
|
42 T T gggggg @@@@@@ .......
|
||||||
|
43 T T ggggg @@@@@@ """"""""""""" .........
|
||||||
|
44""" T T ggggg @@@@@ "":::::::::::::""" ..........
|
||||||
|
45:::""" ## @@@@@ """::::::::::::::::::"" ...........
|
||||||
|
46::::::"" ## "":::::::::::::::::::::::""".........
|
||||||
|
47::::::::"""""""""""""""""""""""""""""""""""::::::::::::::::::::::::::::"""""""""
|
||||||
|
48::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||||
|
49################################################################################
|
||||||
|
50????????????????????????????????????????????????????????????????????????????????
|
||||||
|
51????????????????????????????????????????????????????????????????????????????????
|
||||||
|
52????????????????????????????????????????????????????????????????????????????????
|
||||||
|
53????????????????????????????????????????????????????????????????????????????????
|
||||||
|
54????????????????????????????????????????????????????????????????????????????????
|
||||||
|
55????????????????????????????????????????????????????????????????????????????????
|
||||||
|
Player: "Player hp=100.0/100.0 pos=(125.0,243.5) bodies=27/27 on_fire=false"
|
||||||
|
Entities: 3
|
||||||
|
Alive entities: Player(hp=100, pos=(125.0, 243.5)), Goblin(hp=40, pos=(110.0, 238.5)), Goblin(hp=40, pos=(110.0, 242.0))
|
||||||
|
|
||||||
|
|||||||
+150
@@ -33,6 +33,9 @@ fn main() {
|
|||||||
"window" => {
|
"window" => {
|
||||||
run_window_mode();
|
run_window_mode();
|
||||||
}
|
}
|
||||||
|
"vulkan" => {
|
||||||
|
run_vulkan_mode();
|
||||||
|
}
|
||||||
"terminal" => {
|
"terminal" => {
|
||||||
std::panic::set_hook(Box::new(|info| {
|
std::panic::set_hook(Box::new(|info| {
|
||||||
let _ = crossterm::terminal::disable_raw_mode();
|
let _ = crossterm::terminal::disable_raw_mode();
|
||||||
@@ -209,6 +212,7 @@ fn run_window_mode() {
|
|||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use std::num::NonZeroU32;
|
||||||
|
|
||||||
let event_loop = EventLoop::new().expect("Failed to create event loop");
|
let event_loop = EventLoop::new().expect("Failed to create event loop");
|
||||||
let window = event_loop.create_window(
|
let window = event_loop.create_window(
|
||||||
@@ -224,6 +228,11 @@ fn run_window_mode() {
|
|||||||
let mut surface = softbuffer::Surface::new(&context, Arc::clone(&window))
|
let mut surface = softbuffer::Surface::new(&context, Arc::clone(&window))
|
||||||
.expect("Failed to create surface");
|
.expect("Failed to create surface");
|
||||||
|
|
||||||
|
let pw = 160 * 8;
|
||||||
|
let ph = 50 * 16;
|
||||||
|
surface.resize(NonZeroU32::new(pw).unwrap(), NonZeroU32::new(ph).unwrap())
|
||||||
|
.expect("Failed to resize surface");
|
||||||
|
|
||||||
let mut renderer = WindowRenderer::new();
|
let mut renderer = WindowRenderer::new();
|
||||||
let mut game = Game::new();
|
let mut game = Game::new();
|
||||||
game.init_world();
|
game.init_world();
|
||||||
@@ -345,3 +354,144 @@ fn run_window_mode() {
|
|||||||
}
|
}
|
||||||
}).expect("event loop error");
|
}).expect("event loop error");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn run_vulkan_mode() {
|
||||||
|
use verbatim::render::vulkan::VulkanRenderer;
|
||||||
|
use winit::event::{Event, WindowEvent};
|
||||||
|
use winit::event_loop::{EventLoop, ControlFlow};
|
||||||
|
use winit::window::Window;
|
||||||
|
use std::time::{Duration, Instant};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
let event_loop = EventLoop::new().expect("Failed to create event loop");
|
||||||
|
let window = event_loop.create_window(
|
||||||
|
Window::default_attributes()
|
||||||
|
.with_title("Verbatim (Vulkan)")
|
||||||
|
.with_inner_size(winit::dpi::LogicalSize::new(160 * 8, 50 * 16))
|
||||||
|
).expect("Failed to create window");
|
||||||
|
let window = Arc::new(window);
|
||||||
|
|
||||||
|
let mut renderer = match VulkanRenderer::new(Arc::clone(&window)) {
|
||||||
|
Ok(r) => r,
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Vulkan init failed: {e}");
|
||||||
|
eprintln!("Vulkan init failed: {e}");
|
||||||
|
eprintln!("Falling back to softbuffer window mode...");
|
||||||
|
run_window_mode();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut game = Game::new();
|
||||||
|
game.init_world();
|
||||||
|
|
||||||
|
let mut input = WindowInput::new();
|
||||||
|
let vw = renderer.grid_w();
|
||||||
|
let vh = renderer.grid_h();
|
||||||
|
|
||||||
|
let fixed_dt = Duration::from_millis(16);
|
||||||
|
let mut last_time = Instant::now();
|
||||||
|
let mut accumulator = Duration::ZERO;
|
||||||
|
let mut running = true;
|
||||||
|
|
||||||
|
event_loop.run(|event, ctrl| {
|
||||||
|
ctrl.set_control_flow(ControlFlow::Poll);
|
||||||
|
|
||||||
|
match event {
|
||||||
|
Event::WindowEvent { event, .. } => {
|
||||||
|
match event {
|
||||||
|
WindowEvent::CloseRequested => {
|
||||||
|
running = false;
|
||||||
|
ctrl.exit();
|
||||||
|
}
|
||||||
|
WindowEvent::KeyboardInput { event: key_event, .. } => {
|
||||||
|
input.on_key_event(key_event.physical_key, key_event.state);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Event::AboutToWait => {
|
||||||
|
if !running {
|
||||||
|
ctrl.exit();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let now = Instant::now();
|
||||||
|
let frame_time = now.duration_since(last_time);
|
||||||
|
last_time = now;
|
||||||
|
accumulator += frame_time;
|
||||||
|
|
||||||
|
let mut steps = 0;
|
||||||
|
while accumulator >= fixed_dt && steps < 5 {
|
||||||
|
game.fixed_update();
|
||||||
|
accumulator -= fixed_dt;
|
||||||
|
steps += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
input.update();
|
||||||
|
|
||||||
|
if input.quit {
|
||||||
|
running = false;
|
||||||
|
ctrl.exit();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if input.jump {
|
||||||
|
let on_ground = game.check_on_ground();
|
||||||
|
game.player.jump(&mut game.entities, on_ground);
|
||||||
|
}
|
||||||
|
|
||||||
|
if input.left {
|
||||||
|
game.player.move_left(&mut game.entities);
|
||||||
|
} else if input.right {
|
||||||
|
game.player.move_right(&mut game.entities);
|
||||||
|
} else {
|
||||||
|
game.player.stop_horizontal(&mut game.entities);
|
||||||
|
}
|
||||||
|
|
||||||
|
if input.cam_left { game.cam_x -= 3; }
|
||||||
|
if input.cam_right { game.cam_x += 3; }
|
||||||
|
if input.cam_up { game.cam_y -= 3; }
|
||||||
|
if input.cam_down { game.cam_y += 3; }
|
||||||
|
|
||||||
|
if let Some(brush_id) = input.paint {
|
||||||
|
let mat = match brush_id {
|
||||||
|
1 => MaterialId::Sand,
|
||||||
|
2 => MaterialId::Water,
|
||||||
|
3 => MaterialId::Stone,
|
||||||
|
4 => MaterialId::Lava,
|
||||||
|
5 => MaterialId::Wood,
|
||||||
|
6 => MaterialId::Acid,
|
||||||
|
7 => MaterialId::Grass,
|
||||||
|
8 => MaterialId::Dirt,
|
||||||
|
9 => MaterialId::Fire,
|
||||||
|
0 => MaterialId::Flesh,
|
||||||
|
99 => MaterialId::Empty,
|
||||||
|
_ => MaterialId::Empty,
|
||||||
|
};
|
||||||
|
let cx = game.cam_x + (vw as i32 / 2);
|
||||||
|
let cy = game.cam_y + (vh as i32 / 2);
|
||||||
|
let r = 2;
|
||||||
|
for dy in -r..=r {
|
||||||
|
for dx in -r..=r {
|
||||||
|
if dx * dx + dy * dy <= r * r + 1 {
|
||||||
|
if mat == MaterialId::Empty {
|
||||||
|
game.grid.set(cx + dx, cy + dy, verbatim::world::cell::Cell::empty());
|
||||||
|
} else {
|
||||||
|
game.grid.set_material(cx + dx, cy + dy, mat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let (px, py) = game.player.center(&game.entities);
|
||||||
|
game.cam_x = px as i32 - (vw as i32 / 2);
|
||||||
|
game.cam_y = py as i32 - (vh as i32 / 2);
|
||||||
|
|
||||||
|
renderer.render(&game.grid, &game.entities, game.cam_x, game.cam_y);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}).expect("event loop error");
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
pub mod terminal;
|
pub mod terminal;
|
||||||
pub mod window;
|
pub mod window;
|
||||||
pub mod window_input;
|
pub mod window_input;
|
||||||
|
pub mod vulkan;
|
||||||
|
|
||||||
use crate::entity::EntityManager;
|
use crate::entity::EntityManager;
|
||||||
use crate::world::grid::Grid;
|
use crate::world::grid::Grid;
|
||||||
|
|||||||
@@ -0,0 +1,715 @@
|
|||||||
|
use ash::vk;
|
||||||
|
use fontdue::{Font, FontSettings};
|
||||||
|
use std::ffi::CString;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use crate::entity::{EntityManager, EntityKind};
|
||||||
|
use crate::world::cell::MaterialId;
|
||||||
|
use crate::world::grid::Grid;
|
||||||
|
use crate::world::material::MaterialRegistry;
|
||||||
|
|
||||||
|
const CHAR_W: u32 = 8;
|
||||||
|
const CHAR_H: u32 = 16;
|
||||||
|
const ATLAS_COLS: usize = 16;
|
||||||
|
const ATLAS_ROWS: usize = 16;
|
||||||
|
const ATLAS_W: u32 = (ATLAS_COLS as u32) * CHAR_W;
|
||||||
|
const ATLAS_H: u32 = (ATLAS_ROWS as u32) * CHAR_H;
|
||||||
|
const MAX_FRAMES: usize = 2;
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Copy, Default)]
|
||||||
|
struct CellInstance {
|
||||||
|
grid_x: f32,
|
||||||
|
grid_y: f32,
|
||||||
|
atlas_u: f32,
|
||||||
|
atlas_v: f32,
|
||||||
|
atlas_w: f32,
|
||||||
|
atlas_h: f32,
|
||||||
|
fg: [u8; 4],
|
||||||
|
bg: [u8; 4],
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(bytemuck::NoUninit, Clone, Copy)]
|
||||||
|
struct PushConstants {
|
||||||
|
screen_size: [f32; 2],
|
||||||
|
cell_size: [f32; 2],
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct VulkanRenderer {
|
||||||
|
grid_w: usize,
|
||||||
|
grid_h: usize,
|
||||||
|
pixel_w: u32,
|
||||||
|
pixel_h: u32,
|
||||||
|
|
||||||
|
entry: ash::Entry,
|
||||||
|
instance: ash::Instance,
|
||||||
|
surface: vk::SurfaceKHR,
|
||||||
|
physical_device: vk::PhysicalDevice,
|
||||||
|
device: ash::Device,
|
||||||
|
graphics_queue: vk::Queue,
|
||||||
|
queue_family: u32,
|
||||||
|
|
||||||
|
swapchain_loader: ash::khr::swapchain::Device,
|
||||||
|
swapchain: vk::SwapchainKHR,
|
||||||
|
swapchain_image_views: Vec<vk::ImageView>,
|
||||||
|
swapchain_format: vk::Format,
|
||||||
|
swapchain_extent: vk::Extent2D,
|
||||||
|
|
||||||
|
render_pass: vk::RenderPass,
|
||||||
|
pipeline: vk::Pipeline,
|
||||||
|
pipeline_layout: vk::PipelineLayout,
|
||||||
|
framebuffers: Vec<vk::Framebuffer>,
|
||||||
|
|
||||||
|
command_pool: vk::CommandPool,
|
||||||
|
command_buffers: Vec<vk::CommandBuffer>,
|
||||||
|
|
||||||
|
image_available: Vec<vk::Semaphore>,
|
||||||
|
render_finished: Vec<vk::Semaphore>,
|
||||||
|
in_flight: Vec<vk::Fence>,
|
||||||
|
frame_index: usize,
|
||||||
|
|
||||||
|
vertex_buffer: vk::Buffer,
|
||||||
|
vertex_memory: vk::DeviceMemory,
|
||||||
|
index_buffer: vk::Buffer,
|
||||||
|
index_memory: vk::DeviceMemory,
|
||||||
|
|
||||||
|
atlas_image: vk::Image,
|
||||||
|
atlas_memory: vk::DeviceMemory,
|
||||||
|
atlas_view: vk::ImageView,
|
||||||
|
atlas_sampler: vk::Sampler,
|
||||||
|
atlas_map: std::collections::HashMap<char, (f32, f32, f32, f32)>,
|
||||||
|
|
||||||
|
instance_buffer: vk::Buffer,
|
||||||
|
instance_memory: vk::DeviceMemory,
|
||||||
|
instance_ptr: *mut CellInstance,
|
||||||
|
instance_count: usize,
|
||||||
|
|
||||||
|
descriptor_pool: vk::DescriptorPool,
|
||||||
|
descriptor_set: vk::DescriptorSet,
|
||||||
|
descriptor_set_layout: vk::DescriptorSetLayout,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VulkanRenderer {
|
||||||
|
pub fn new(window: Arc<winit::window::Window>) -> Result<Self, String> {
|
||||||
|
let grid_w = 160usize;
|
||||||
|
let grid_h = 50usize;
|
||||||
|
let pixel_w = (grid_w as u32) * CHAR_W;
|
||||||
|
let pixel_h = (grid_h as u32) * CHAR_H;
|
||||||
|
|
||||||
|
let entry = unsafe { ash::Entry::load().map_err(|e| format!("Vulkan load: {e}"))? };
|
||||||
|
let instance = create_instance(&entry)?;
|
||||||
|
let surface = create_surface(&entry, &instance, &window)?;
|
||||||
|
|
||||||
|
let surface_loader = ash::khr::surface::Instance::new(&entry, &instance);
|
||||||
|
let (physical_device, queue_family) =
|
||||||
|
pick_physical_device(&instance, &surface_loader, surface)?;
|
||||||
|
let (device, graphics_queue) =
|
||||||
|
create_device(&instance, physical_device, queue_family)?;
|
||||||
|
|
||||||
|
let swapchain_loader = ash::khr::swapchain::Device::new(&instance, &device);
|
||||||
|
let (swapchain, swapchain_images, swapchain_format, swapchain_extent) =
|
||||||
|
create_swapchain(&device, &swapchain_loader, &surface_loader,
|
||||||
|
physical_device, surface, queue_family, pixel_w, pixel_h)?;
|
||||||
|
|
||||||
|
let swapchain_image_views: Vec<_> = swapchain_images.iter()
|
||||||
|
.map(|&img| create_image_view(&device, img, swapchain_format))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let render_pass = create_render_pass(&device, swapchain_format)?;
|
||||||
|
let (descriptor_set_layout, descriptor_pool, descriptor_set) =
|
||||||
|
create_descriptor(&device)?;
|
||||||
|
let (pipeline_layout, pipeline) = create_pipeline(&device, render_pass, descriptor_set_layout)?;
|
||||||
|
let framebuffers: Vec<_> = swapchain_image_views.iter()
|
||||||
|
.map(|&view| create_framebuffer(&device, render_pass, view, swapchain_extent))
|
||||||
|
.collect();
|
||||||
|
let command_pool = create_command_pool(&device, queue_family)?;
|
||||||
|
let command_buffers = create_command_buffers(&device, command_pool, framebuffers.len())?;
|
||||||
|
let (image_available, render_finished, in_flight) = create_sync(&device)?;
|
||||||
|
|
||||||
|
let (vertex_buffer, vertex_memory, index_buffer, index_memory) =
|
||||||
|
create_vertex_index_buffers(&device, &instance, physical_device)?;
|
||||||
|
|
||||||
|
let (atlas_image, atlas_memory, atlas_view, atlas_sampler, atlas_map) =
|
||||||
|
create_atlas_texture(&device, &instance, physical_device, &graphics_queue, command_pool)?;
|
||||||
|
|
||||||
|
let instance_count = grid_w * grid_h;
|
||||||
|
let (instance_buffer, instance_memory, instance_ptr) =
|
||||||
|
create_instance_buffer(&device, &instance, physical_device, instance_count)?;
|
||||||
|
|
||||||
|
update_descriptor_set(&device, descriptor_set, atlas_view, atlas_sampler);
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
grid_w, grid_h, pixel_w, pixel_h,
|
||||||
|
entry, instance, surface, physical_device, device,
|
||||||
|
graphics_queue, queue_family,
|
||||||
|
swapchain_loader, swapchain, swapchain_image_views,
|
||||||
|
swapchain_format, swapchain_extent,
|
||||||
|
render_pass, pipeline, pipeline_layout, framebuffers,
|
||||||
|
command_pool, command_buffers,
|
||||||
|
image_available, render_finished, in_flight, frame_index: 0,
|
||||||
|
vertex_buffer, vertex_memory, index_buffer, index_memory,
|
||||||
|
atlas_image, atlas_memory, atlas_view, atlas_sampler, atlas_map,
|
||||||
|
instance_buffer, instance_memory, instance_ptr, instance_count,
|
||||||
|
descriptor_pool, descriptor_set, descriptor_set_layout,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render(&mut self, grid: &Grid, entities: &EntityManager, cam_x: i32, cam_y: i32) {
|
||||||
|
let reg = MaterialRegistry::instance();
|
||||||
|
|
||||||
|
let mut entity_map: std::collections::HashMap<(i32, i32), (char, [u8; 4])> = std::collections::HashMap::new();
|
||||||
|
for e in entities.all() {
|
||||||
|
for b in &e.bodies {
|
||||||
|
if !b.alive { continue; }
|
||||||
|
let sx = b.x as i32 - cam_x;
|
||||||
|
let sy = b.y as i32 - cam_y;
|
||||||
|
if sx >= 0 && sx < self.grid_w as i32 && sy >= 0 && sy < self.grid_h as i32 {
|
||||||
|
let ch = match e.kind {
|
||||||
|
EntityKind::Player if e.alive => '@',
|
||||||
|
EntityKind::Goblin if e.alive => 'g',
|
||||||
|
_ => '%',
|
||||||
|
};
|
||||||
|
let fg = if e.on_fire { [255, 160, 40, 255] }
|
||||||
|
else if !e.alive { [100, 60, 60, 255] }
|
||||||
|
else {
|
||||||
|
match e.kind {
|
||||||
|
EntityKind::Player => [255, 255, 100, 255],
|
||||||
|
EntityKind::Goblin => [100, 220, 100, 255],
|
||||||
|
_ => [180, 50, 50, 255],
|
||||||
|
}
|
||||||
|
};
|
||||||
|
entity_map.insert((sx, sy), (ch, fg));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let instances = unsafe { std::slice::from_raw_parts_mut(self.instance_ptr, self.instance_count) };
|
||||||
|
let bg_default = [10u8, 10, 15, 255];
|
||||||
|
|
||||||
|
for dy in 0..self.grid_h {
|
||||||
|
for dx in 0..self.grid_w {
|
||||||
|
let idx = dy * self.grid_w + dx;
|
||||||
|
let wx = cam_x + dx as i32;
|
||||||
|
let wy = cam_y + dy as i32;
|
||||||
|
|
||||||
|
let (ch, fg, bg) = if let Some(&(ec, ef)) = entity_map.get(&(dx as i32, dy as i32)) {
|
||||||
|
(ec, ef, bg_default)
|
||||||
|
} else if !grid.in_bounds(wx, wy) {
|
||||||
|
('?', [80, 80, 80, 255], bg_default)
|
||||||
|
} else {
|
||||||
|
let cell = grid.get(wx, wy);
|
||||||
|
let mat = reg.get(cell.material);
|
||||||
|
if cell.is_empty() {
|
||||||
|
(' ', [10, 10, 15, 255], bg_default)
|
||||||
|
} else {
|
||||||
|
let fg = if cell.material == MaterialId::Lava {
|
||||||
|
let r = 200u8.saturating_add(cell.variant / 2);
|
||||||
|
[r, 60, 20, 255]
|
||||||
|
} else {
|
||||||
|
[mat.color_fg.0, mat.color_fg.1, mat.color_fg.2, 255]
|
||||||
|
};
|
||||||
|
let bg = [mat.color_bg.0, mat.color_bg.1, mat.color_bg.2, 255];
|
||||||
|
(mat.display_char, fg, bg)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let (au, av, aw, ah) = self.atlas_map.get(&ch).copied().unwrap_or((0.0, 0.0, 0.0, 0.0));
|
||||||
|
instances[idx] = CellInstance {
|
||||||
|
grid_x: dx as f32, grid_y: dy as f32,
|
||||||
|
atlas_u: au, atlas_v: av, atlas_w: aw, atlas_h: ah,
|
||||||
|
fg, bg,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let frame = self.frame_index;
|
||||||
|
let device = &self.device;
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let _ = device.wait_for_fences(&[self.in_flight[frame]], true, u64::MAX);
|
||||||
|
let _ = device.reset_fences(&[self.in_flight[frame]]);
|
||||||
|
|
||||||
|
let image_index = match self.swapchain_loader.acquire_next_image(
|
||||||
|
self.swapchain, u64::MAX, self.image_available[frame], vk::Fence::null()
|
||||||
|
) {
|
||||||
|
Ok((idx, _)) => idx as usize,
|
||||||
|
Err(e) => { eprintln!("acquire: {e:?}"); return; }
|
||||||
|
};
|
||||||
|
|
||||||
|
let cmd = self.command_buffers[frame];
|
||||||
|
let _ = device.reset_command_buffer(cmd, vk::CommandBufferResetFlags::default());
|
||||||
|
let _ = device.begin_command_buffer(cmd, &vk::CommandBufferBeginInfo::default());
|
||||||
|
|
||||||
|
let clear = vk::ClearValue {
|
||||||
|
color: vk::ClearColorValue { float32: [10.0/255.0, 10.0/255.0, 15.0/255.0, 1.0] },
|
||||||
|
};
|
||||||
|
let rp_info = vk::RenderPassBeginInfo::default()
|
||||||
|
.render_pass(self.render_pass)
|
||||||
|
.framebuffer(self.framebuffers[image_index])
|
||||||
|
.render_area(vk::Rect2D { offset: vk::Offset2D::default(), extent: self.swapchain_extent })
|
||||||
|
.clear_values(std::slice::from_ref(&clear));
|
||||||
|
|
||||||
|
device.cmd_begin_render_pass(cmd, &rp_info, vk::SubpassContents::INLINE);
|
||||||
|
device.cmd_bind_pipeline(cmd, vk::PipelineBindPoint::GRAPHICS, self.pipeline);
|
||||||
|
device.cmd_bind_vertex_buffers(cmd, 0, &[self.vertex_buffer, self.instance_buffer], &[0, 0]);
|
||||||
|
device.cmd_bind_index_buffer(cmd, self.index_buffer, 0, vk::IndexType::UINT16);
|
||||||
|
device.cmd_bind_descriptor_sets(cmd, vk::PipelineBindPoint::GRAPHICS,
|
||||||
|
self.pipeline_layout, 0, &[self.descriptor_set], &[]);
|
||||||
|
|
||||||
|
let pc = PushConstants {
|
||||||
|
screen_size: [self.swapchain_extent.width as f32, self.swapchain_extent.height as f32],
|
||||||
|
cell_size: [CHAR_W as f32, CHAR_H as f32],
|
||||||
|
};
|
||||||
|
device.cmd_push_constants(cmd, self.pipeline_layout, vk::ShaderStageFlags::VERTEX,
|
||||||
|
0, bytemuck::bytes_of(&pc));
|
||||||
|
|
||||||
|
device.cmd_draw_indexed(cmd, 6, self.instance_count as u32, 0, 0, 0);
|
||||||
|
device.cmd_end_render_pass(cmd);
|
||||||
|
let _ = device.end_command_buffer(cmd);
|
||||||
|
|
||||||
|
let wait_stages = [vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT];
|
||||||
|
let submit_info = vk::SubmitInfo::default()
|
||||||
|
.wait_semaphores(std::slice::from_ref(&self.image_available[frame]))
|
||||||
|
.wait_dst_stage_mask(&wait_stages)
|
||||||
|
.command_buffers(std::slice::from_ref(&cmd))
|
||||||
|
.signal_semaphores(std::slice::from_ref(&self.render_finished[frame]));
|
||||||
|
let _ = device.queue_submit(self.graphics_queue, std::slice::from_ref(&submit_info), self.in_flight[frame]);
|
||||||
|
|
||||||
|
let img_idx = image_index as u32;
|
||||||
|
let present_info = vk::PresentInfoKHR::default()
|
||||||
|
.wait_semaphores(std::slice::from_ref(&self.render_finished[frame]))
|
||||||
|
.swapchains(std::slice::from_ref(&self.swapchain))
|
||||||
|
.image_indices(std::slice::from_ref(&img_idx));
|
||||||
|
let _ = self.swapchain_loader.queue_present(self.graphics_queue, &present_info);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.frame_index = (self.frame_index + 1) % MAX_FRAMES;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn grid_w(&self) -> usize { self.grid_w }
|
||||||
|
pub fn grid_h(&self) -> usize { self.grid_h }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for VulkanRenderer {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe {
|
||||||
|
let _ = self.device.device_wait_idle();
|
||||||
|
for &f in &self.in_flight { self.device.destroy_fence(f, None); }
|
||||||
|
for &s in &self.image_available { self.device.destroy_semaphore(s, None); }
|
||||||
|
for &s in &self.render_finished { self.device.destroy_semaphore(s, None); }
|
||||||
|
self.device.destroy_command_pool(self.command_pool, None);
|
||||||
|
for &fb in &self.framebuffers { self.device.destroy_framebuffer(fb, None); }
|
||||||
|
self.device.destroy_pipeline(self.pipeline, None);
|
||||||
|
self.device.destroy_pipeline_layout(self.pipeline_layout, None);
|
||||||
|
self.device.destroy_render_pass(self.render_pass, None);
|
||||||
|
self.device.destroy_sampler(self.atlas_sampler, None);
|
||||||
|
self.device.destroy_image_view(self.atlas_view, None);
|
||||||
|
self.device.destroy_image(self.atlas_image, None);
|
||||||
|
self.device.free_memory(self.atlas_memory, None);
|
||||||
|
self.device.destroy_buffer(self.instance_buffer, None);
|
||||||
|
self.device.free_memory(self.instance_memory, None);
|
||||||
|
self.device.destroy_buffer(self.vertex_buffer, None);
|
||||||
|
self.device.free_memory(self.vertex_memory, None);
|
||||||
|
self.device.destroy_buffer(self.index_buffer, None);
|
||||||
|
self.device.free_memory(self.index_memory, None);
|
||||||
|
self.device.destroy_descriptor_pool(self.descriptor_pool, None);
|
||||||
|
self.device.destroy_descriptor_set_layout(self.descriptor_set_layout, None);
|
||||||
|
for &v in &self.swapchain_image_views { self.device.destroy_image_view(v, None); }
|
||||||
|
self.swapchain_loader.destroy_swapchain(self.swapchain, None);
|
||||||
|
let sl = ash::khr::surface::Instance::new(&self.entry, &self.instance);
|
||||||
|
sl.destroy_surface(self.surface, None);
|
||||||
|
self.device.destroy_device(None);
|
||||||
|
self.instance.destroy_instance(None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_instance(entry: &ash::Entry) -> Result<ash::Instance, String> {
|
||||||
|
let app_name = CString::new("Verbatim").unwrap();
|
||||||
|
let app_info = vk::ApplicationInfo::default()
|
||||||
|
.application_name(&app_name)
|
||||||
|
.api_version(vk::API_VERSION_1_2);
|
||||||
|
let ext_names: Vec<CString> = vec![
|
||||||
|
CString::new("VK_KHR_surface").unwrap(),
|
||||||
|
CString::new("VK_KHR_xlib_surface").unwrap(),
|
||||||
|
CString::new("VK_KHR_wayland_surface").unwrap(),
|
||||||
|
];
|
||||||
|
let ext_ptrs: Vec<*const i8> = ext_names.iter().map(|n| n.as_ptr()).collect();
|
||||||
|
let create_info = vk::InstanceCreateInfo::default()
|
||||||
|
.application_info(&app_info)
|
||||||
|
.enabled_extension_names(&ext_ptrs);
|
||||||
|
unsafe { entry.create_instance(&create_info, None).map_err(|e| format!("instance: {e:?}")) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_surface(entry: &ash::Entry, instance: &ash::Instance, window: &winit::window::Window) -> Result<vk::SurfaceKHR, String> {
|
||||||
|
use raw_window_handle::{HasWindowHandle, HasDisplayHandle};
|
||||||
|
let wh = window.window_handle().map_err(|e| format!("wh: {e}"))?;
|
||||||
|
let dh = window.display_handle().map_err(|e| format!("dh: {e}"))?;
|
||||||
|
let wh_raw = wh.as_raw();
|
||||||
|
let dh_raw = dh.as_raw();
|
||||||
|
unsafe {
|
||||||
|
ash_window::create_surface(entry, instance, dh_raw, wh_raw, None)
|
||||||
|
.map_err(|e| format!("surface: {e:?}"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pick_physical_device(
|
||||||
|
instance: &ash::Instance,
|
||||||
|
sl: &ash::khr::surface::Instance,
|
||||||
|
surface: vk::SurfaceKHR,
|
||||||
|
) -> Result<(vk::PhysicalDevice, u32), String> {
|
||||||
|
let devices = unsafe { instance.enumerate_physical_devices().map_err(|e| format!("enum: {e:?}"))? };
|
||||||
|
for &pd in &devices {
|
||||||
|
let props = unsafe { instance.get_physical_device_properties(pd) };
|
||||||
|
if props.device_type == vk::PhysicalDeviceType::CPU { continue; }
|
||||||
|
let qfs = unsafe { instance.get_physical_device_queue_family_properties(pd) };
|
||||||
|
for (i, qf) in qfs.iter().enumerate() {
|
||||||
|
if qf.queue_flags.contains(vk::QueueFlags::GRAPHICS) {
|
||||||
|
let ok = unsafe { sl.get_physical_device_surface_support(pd, i as u32, surface) }.unwrap_or(false);
|
||||||
|
if ok { return Ok((pd, i as u32)); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err("No GPU".to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_device(instance: &ash::Instance, pd: vk::PhysicalDevice, qf: u32) -> Result<(ash::Device, vk::Queue), String> {
|
||||||
|
let qp = [1.0f32];
|
||||||
|
let qi = vk::DeviceQueueCreateInfo::default().queue_family_index(qf).queue_priorities(&qp);
|
||||||
|
let ext_names: Vec<CString> = vec![CString::new("VK_KHR_swapchain").unwrap()];
|
||||||
|
let ext_ptrs: Vec<*const i8> = ext_names.iter().map(|n| n.as_ptr()).collect();
|
||||||
|
let ci = vk::DeviceCreateInfo::default()
|
||||||
|
.queue_create_infos(std::slice::from_ref(&qi))
|
||||||
|
.enabled_extension_names(&ext_ptrs);
|
||||||
|
unsafe {
|
||||||
|
let device = instance.create_device(pd, &ci, None).map_err(|e| format!("device: {e:?}"))?;
|
||||||
|
let queue = device.get_device_queue(qf, 0);
|
||||||
|
Ok((device, queue))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_swapchain(
|
||||||
|
device: &ash::Device,
|
||||||
|
sl: &ash::khr::swapchain::Device,
|
||||||
|
surface_loader: &ash::khr::surface::Instance,
|
||||||
|
pd: vk::PhysicalDevice,
|
||||||
|
surface: vk::SurfaceKHR,
|
||||||
|
qf: u32,
|
||||||
|
pw: u32, ph: u32,
|
||||||
|
) -> Result<(vk::SwapchainKHR, Vec<vk::Image>, vk::Format, vk::Extent2D), String> {
|
||||||
|
let caps = unsafe { surface_loader.get_physical_device_surface_capabilities(pd, surface) }
|
||||||
|
.map_err(|e| format!("caps: {e:?}"))?;
|
||||||
|
let format = vk::SurfaceFormatKHR {
|
||||||
|
format: vk::Format::B8G8R8A8_UNORM,
|
||||||
|
color_space: vk::ColorSpaceKHR::SRGB_NONLINEAR,
|
||||||
|
};
|
||||||
|
let extent = if caps.current_extent.width != u32::MAX { caps.current_extent }
|
||||||
|
else { vk::Extent2D { width: pw, height: ph } };
|
||||||
|
let ic = caps.min_image_count.max(2);
|
||||||
|
let qf_slice = [qf];
|
||||||
|
let ci = vk::SwapchainCreateInfoKHR::default()
|
||||||
|
.surface(surface)
|
||||||
|
.min_image_count(ic)
|
||||||
|
.image_format(format.format)
|
||||||
|
.image_color_space(format.color_space)
|
||||||
|
.image_extent(extent)
|
||||||
|
.image_array_layers(1)
|
||||||
|
.image_usage(vk::ImageUsageFlags::COLOR_ATTACHMENT)
|
||||||
|
.image_sharing_mode(vk::SharingMode::EXCLUSIVE)
|
||||||
|
.queue_family_indices(&qf_slice)
|
||||||
|
.pre_transform(caps.current_transform)
|
||||||
|
.composite_alpha(vk::CompositeAlphaFlagsKHR::OPAQUE)
|
||||||
|
.present_mode(vk::PresentModeKHR::FIFO)
|
||||||
|
.clipped(true);
|
||||||
|
let swapchain = unsafe { sl.create_swapchain(&ci, None) }.map_err(|e| format!("swapchain: {e:?}"))?;
|
||||||
|
let images = unsafe { sl.get_swapchain_images(swapchain) }.map_err(|e| format!("images: {e:?}"))?;
|
||||||
|
Ok((swapchain, images, format.format, extent))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_image_view(device: &ash::Device, image: vk::Image, format: vk::Format) -> vk::ImageView {
|
||||||
|
let ci = vk::ImageViewCreateInfo::default()
|
||||||
|
.image(image).view_type(vk::ImageViewType::TYPE_2D).format(format)
|
||||||
|
.subresource_range(vk::ImageSubresourceRange {
|
||||||
|
aspect_mask: vk::ImageAspectFlags::COLOR, base_mip_level: 0,
|
||||||
|
level_count: 1, base_array_layer: 0, layer_count: 1,
|
||||||
|
});
|
||||||
|
unsafe { device.create_image_view(&ci, None).expect("image_view") }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_render_pass(device: &ash::Device, format: vk::Format) -> Result<vk::RenderPass, String> {
|
||||||
|
let att = vk::AttachmentDescription::default()
|
||||||
|
.format(format).samples(vk::SampleCountFlags::TYPE_1)
|
||||||
|
.load_op(vk::AttachmentLoadOp::CLEAR).store_op(vk::AttachmentStoreOp::STORE)
|
||||||
|
.initial_layout(vk::ImageLayout::UNDEFINED).final_layout(vk::ImageLayout::PRESENT_SRC_KHR);
|
||||||
|
let att_ref = vk::AttachmentReference::default().attachment(0).layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL);
|
||||||
|
let subpass = vk::SubpassDescription::default()
|
||||||
|
.pipeline_bind_point(vk::PipelineBindPoint::GRAPHICS)
|
||||||
|
.color_attachments(std::slice::from_ref(&att_ref));
|
||||||
|
let dep = vk::SubpassDependency::default()
|
||||||
|
.src_subpass(vk::SUBPASS_EXTERNAL).dst_subpass(0)
|
||||||
|
.src_stage_mask(vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT)
|
||||||
|
.dst_stage_mask(vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT)
|
||||||
|
.dst_access_mask(vk::AccessFlags::COLOR_ATTACHMENT_WRITE);
|
||||||
|
let ci = vk::RenderPassCreateInfo::default()
|
||||||
|
.attachments(std::slice::from_ref(&att))
|
||||||
|
.subpasses(std::slice::from_ref(&subpass))
|
||||||
|
.dependencies(std::slice::from_ref(&dep));
|
||||||
|
unsafe { device.create_render_pass(&ci, None).map_err(|e| format!("render_pass: {e:?}")) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_descriptor(device: &ash::Device) -> Result<(vk::DescriptorSetLayout, vk::DescriptorPool, vk::DescriptorSet), String> {
|
||||||
|
let binding = vk::DescriptorSetLayoutBinding::default()
|
||||||
|
.binding(0).descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER)
|
||||||
|
.descriptor_count(1).stage_flags(vk::ShaderStageFlags::FRAGMENT);
|
||||||
|
let li = vk::DescriptorSetLayoutCreateInfo::default().bindings(std::slice::from_ref(&binding));
|
||||||
|
let layout = unsafe { device.create_descriptor_set_layout(&li, None) }.map_err(|e| format!("ds_layout: {e:?}"))?;
|
||||||
|
let ps = vk::DescriptorPoolSize { ty: vk::DescriptorType::COMBINED_IMAGE_SAMPLER, descriptor_count: 1 };
|
||||||
|
let pi = vk::DescriptorPoolCreateInfo::default().pool_sizes(std::slice::from_ref(&ps)).max_sets(1);
|
||||||
|
let pool = unsafe { device.create_descriptor_pool(&pi, None) }.map_err(|e| format!("ds_pool: {e:?}"))?;
|
||||||
|
let ai = vk::DescriptorSetAllocateInfo::default().descriptor_pool(pool).set_layouts(std::slice::from_ref(&layout));
|
||||||
|
let sets = unsafe { device.allocate_descriptor_sets(&ai) }.map_err(|e| format!("alloc_ds: {e:?}"))?;
|
||||||
|
Ok((layout, pool, sets[0]))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_pipeline(device: &ash::Device, rp: vk::RenderPass, ds_layout: vk::DescriptorSetLayout) -> Result<(vk::PipelineLayout, vk::Pipeline), String> {
|
||||||
|
let vert_spv = include_bytes!("../../assets/shaders/cell_vert.spv");
|
||||||
|
let frag_spv = include_bytes!("../../assets/shaders/cell_frag.spv");
|
||||||
|
let vert_code: Vec<u32> = vert_spv.chunks_exact(4).map(|c| u32::from_ne_bytes([c[0], c[1], c[2], c[3]])).collect();
|
||||||
|
let frag_code: Vec<u32> = frag_spv.chunks_exact(4).map(|c| u32::from_ne_bytes([c[0], c[1], c[2], c[3]])).collect();
|
||||||
|
let vm = unsafe { device.create_shader_module(&vk::ShaderModuleCreateInfo::default().code(&vert_code), None) }.map_err(|e| format!("vert: {e:?}"))?;
|
||||||
|
let fm = unsafe { device.create_shader_module(&vk::ShaderModuleCreateInfo::default().code(&frag_code), None) }.map_err(|e| format!("frag: {e:?}"))?;
|
||||||
|
let main = CString::new("main").unwrap();
|
||||||
|
let vs = vk::PipelineShaderStageCreateInfo::default().stage(vk::ShaderStageFlags::VERTEX).module(vm).name(&main);
|
||||||
|
let fs = vk::PipelineShaderStageCreateInfo::default().stage(vk::ShaderStageFlags::FRAGMENT).module(fm).name(&main);
|
||||||
|
|
||||||
|
let bindings = [
|
||||||
|
vk::VertexInputBindingDescription { binding: 0, stride: 8, input_rate: vk::VertexInputRate::VERTEX },
|
||||||
|
vk::VertexInputBindingDescription { binding: 1, stride: std::mem::size_of::<CellInstance>() as u32, input_rate: vk::VertexInputRate::INSTANCE },
|
||||||
|
];
|
||||||
|
let attrs = [
|
||||||
|
vk::VertexInputAttributeDescription { location: 0, binding: 0, format: vk::Format::R32G32_SFLOAT, offset: 0 },
|
||||||
|
vk::VertexInputAttributeDescription { location: 1, binding: 1, format: vk::Format::R32G32_SFLOAT, offset: 0 },
|
||||||
|
vk::VertexInputAttributeDescription { location: 2, binding: 1, format: vk::Format::R32G32B32A32_SFLOAT, offset: 8 },
|
||||||
|
vk::VertexInputAttributeDescription { location: 3, binding: 1, format: vk::Format::R8G8B8A8_UNORM, offset: 24 },
|
||||||
|
vk::VertexInputAttributeDescription { location: 4, binding: 1, format: vk::Format::R8G8B8A8_UNORM, offset: 28 },
|
||||||
|
];
|
||||||
|
let vi = vk::PipelineVertexInputStateCreateInfo::default()
|
||||||
|
.vertex_binding_descriptions(&bindings).vertex_attribute_descriptions(&attrs);
|
||||||
|
let ia = vk::PipelineInputAssemblyStateCreateInfo::default().topology(vk::PrimitiveTopology::TRIANGLE_LIST);
|
||||||
|
let vp = vk::Viewport { x: 0.0, y: 0.0, width: 1.0, height: 1.0, min_depth: 0.0, max_depth: 1.0 };
|
||||||
|
let sc = vk::Rect2D { offset: vk::Offset2D::default(), extent: vk::Extent2D { width: 4096, height: 4096 } };
|
||||||
|
let vs_state = vk::PipelineViewportStateCreateInfo::default().viewports(std::slice::from_ref(&vp)).scissors(std::slice::from_ref(&sc));
|
||||||
|
let rs = vk::PipelineRasterizationStateCreateInfo::default().line_width(1.0).cull_mode(vk::CullModeFlags::NONE);
|
||||||
|
let ms = vk::PipelineMultisampleStateCreateInfo::default().rasterization_samples(vk::SampleCountFlags::TYPE_1);
|
||||||
|
let cba = vk::PipelineColorBlendAttachmentState::default().color_write_mask(vk::ColorComponentFlags::RGBA);
|
||||||
|
let cb = vk::PipelineColorBlendStateCreateInfo::default().attachments(std::slice::from_ref(&cba));
|
||||||
|
let pcr = vk::PushConstantRange { stage_flags: vk::ShaderStageFlags::VERTEX, offset: 0, size: std::mem::size_of::<PushConstants>() as u32 };
|
||||||
|
let li = vk::PipelineLayoutCreateInfo::default()
|
||||||
|
.set_layouts(std::slice::from_ref(&ds_layout))
|
||||||
|
.push_constant_ranges(std::slice::from_ref(&pcr));
|
||||||
|
let layout = unsafe { device.create_pipeline_layout(&li, None) }.map_err(|e| format!("pipeline_layout: {e:?}"))?;
|
||||||
|
let stages = [vs, fs];
|
||||||
|
let pi = vk::GraphicsPipelineCreateInfo::default()
|
||||||
|
.stages(&stages).vertex_input_state(&vi).input_assembly_state(&ia)
|
||||||
|
.viewport_state(&vs_state).rasterization_state(&rs).multisample_state(&ms)
|
||||||
|
.color_blend_state(&cb).layout(layout).render_pass(rp).subpass(0);
|
||||||
|
let pipes = unsafe { device.create_graphics_pipelines(vk::PipelineCache::null(), std::slice::from_ref(&pi), None) }
|
||||||
|
.map_err(|(_, e)| format!("pipeline: {e:?}"))?;
|
||||||
|
unsafe { device.destroy_shader_module(vm, None); device.destroy_shader_module(fm, None); }
|
||||||
|
Ok((layout, pipes[0]))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_framebuffer(device: &ash::Device, rp: vk::RenderPass, view: vk::ImageView, ext: vk::Extent2D) -> vk::Framebuffer {
|
||||||
|
let ci = vk::FramebufferCreateInfo::default().render_pass(rp).attachments(std::slice::from_ref(&view))
|
||||||
|
.width(ext.width).height(ext.height).layers(1);
|
||||||
|
unsafe { device.create_framebuffer(&ci, None).expect("fb") }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_command_pool(device: &ash::Device, qf: u32) -> Result<vk::CommandPool, String> {
|
||||||
|
let ci = vk::CommandPoolCreateInfo::default().queue_family_index(qf).flags(vk::CommandPoolCreateFlags::RESET_COMMAND_BUFFER);
|
||||||
|
unsafe { device.create_command_pool(&ci, None).map_err(|e| format!("cmd_pool: {e:?}")) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_command_buffers(device: &ash::Device, pool: vk::CommandPool, count: usize) -> Result<Vec<vk::CommandBuffer>, String> {
|
||||||
|
let ai = vk::CommandBufferAllocateInfo::default().command_pool(pool).level(vk::CommandBufferLevel::PRIMARY).command_buffer_count(count as u32);
|
||||||
|
unsafe { device.allocate_command_buffers(&ai).map_err(|e| format!("cmd_bufs: {e:?}")) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_sync(device: &ash::Device) -> Result<(Vec<vk::Semaphore>, Vec<vk::Semaphore>, Vec<vk::Fence>), String> {
|
||||||
|
let mut ia = Vec::new(); let mut rf = Vec::new(); let mut ifl = Vec::new();
|
||||||
|
for _ in 0..MAX_FRAMES {
|
||||||
|
unsafe {
|
||||||
|
ia.push(device.create_semaphore(&vk::SemaphoreCreateInfo::default(), None).map_err(|e| format!("sem: {e:?}"))?);
|
||||||
|
rf.push(device.create_semaphore(&vk::SemaphoreCreateInfo::default(), None).map_err(|e| format!("sem: {e:?}"))?);
|
||||||
|
ifl.push(device.create_fence(&vk::FenceCreateInfo::default().flags(vk::FenceCreateFlags::SIGNALED), None).map_err(|e| format!("fence: {e:?}"))?);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok((ia, rf, ifl))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_mem_type(instance: &ash::Instance, pd: vk::PhysicalDevice, filter: u32, props: vk::MemoryPropertyFlags) -> Result<u32, String> {
|
||||||
|
let mp = unsafe { instance.get_physical_device_memory_properties(pd) };
|
||||||
|
for (i, mt) in mp.memory_types.iter().enumerate() {
|
||||||
|
if (filter & (1 << i)) != 0 && mt.property_flags.contains(props) { return Ok(i as u32); }
|
||||||
|
}
|
||||||
|
Err("No memory type".to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_buffer_with_data<T: Copy>(
|
||||||
|
device: &ash::Device, instance: &ash::Instance, pd: vk::PhysicalDevice,
|
||||||
|
data: &[T], usage: vk::BufferUsageFlags,
|
||||||
|
) -> Result<(vk::Buffer, vk::DeviceMemory), String> {
|
||||||
|
let size = (data.len() * std::mem::size_of::<T>()) as vk::DeviceSize;
|
||||||
|
let bi = vk::BufferCreateInfo::default().size(size).usage(usage).sharing_mode(vk::SharingMode::EXCLUSIVE);
|
||||||
|
let buf = unsafe { device.create_buffer(&bi, None) }.map_err(|e| format!("buf: {e:?}"))?;
|
||||||
|
let req = unsafe { device.get_buffer_memory_requirements(buf) };
|
||||||
|
let mt = find_mem_type(instance, pd, req.memory_type_bits, vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT)?;
|
||||||
|
let ai = vk::MemoryAllocateInfo::default().allocation_size(req.size).memory_type_index(mt);
|
||||||
|
let mem = unsafe { device.allocate_memory(&ai, None) }.map_err(|e| format!("mem: {e:?}"))?;
|
||||||
|
unsafe {
|
||||||
|
device.bind_buffer_memory(buf, mem, 0).expect("bind");
|
||||||
|
let ptr = device.map_memory(mem, 0, size, vk::MemoryMapFlags::default()).expect("map");
|
||||||
|
std::ptr::copy_nonoverlapping(data.as_ptr() as *const u8, ptr as *mut u8, size as usize);
|
||||||
|
device.unmap_memory(mem);
|
||||||
|
}
|
||||||
|
Ok((buf, mem))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_vertex_index_buffers(
|
||||||
|
device: &ash::Device, instance: &ash::Instance, pd: vk::PhysicalDevice,
|
||||||
|
) -> Result<(vk::Buffer, vk::DeviceMemory, vk::Buffer, vk::DeviceMemory), String> {
|
||||||
|
let verts: [f32; 8] = [0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0];
|
||||||
|
let indices: [u16; 6] = [0, 1, 2, 1, 3, 2];
|
||||||
|
let (vb, vm) = create_buffer_with_data(device, instance, pd, &verts, vk::BufferUsageFlags::VERTEX_BUFFER)?;
|
||||||
|
let (ib, im) = create_buffer_with_data(device, instance, pd, &indices, vk::BufferUsageFlags::INDEX_BUFFER)?;
|
||||||
|
Ok((vb, vm, ib, im))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_atlas_texture(
|
||||||
|
device: &ash::Device, instance: &ash::Instance, pd: vk::PhysicalDevice,
|
||||||
|
queue: &vk::Queue, pool: vk::CommandPool,
|
||||||
|
) -> Result<(vk::Image, vk::DeviceMemory, vk::ImageView, vk::Sampler, std::collections::HashMap<char, (f32, f32, f32, f32)>), String> {
|
||||||
|
let font_bytes: &[u8] = include_bytes!("../../assets/DejaVuSansMono.ttf");
|
||||||
|
let font = Font::from_bytes(font_bytes, FontSettings { collection_index: 0, scale: CHAR_H as f32, load_substitutions: false }).expect("font");
|
||||||
|
let mut atlas_data = vec![0u8; (ATLAS_W * ATLAS_H) as usize];
|
||||||
|
let mut atlas_map = std::collections::HashMap::new();
|
||||||
|
let chars: Vec<char> = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~?".chars().collect();
|
||||||
|
for (i, &ch) in chars.iter().enumerate() {
|
||||||
|
let col = i % ATLAS_COLS; let row = i / ATLAS_COLS;
|
||||||
|
atlas_map.insert(ch, (
|
||||||
|
(col as f32 * CHAR_W as f32) / ATLAS_W as f32,
|
||||||
|
(row as f32 * CHAR_H as f32) / ATLAS_H as f32,
|
||||||
|
CHAR_W as f32 / ATLAS_W as f32,
|
||||||
|
CHAR_H as f32 / ATLAS_H as f32,
|
||||||
|
));
|
||||||
|
let (metrics, bitmap) = font.rasterize(ch, CHAR_H as f32);
|
||||||
|
for y in 0..metrics.height.min(CHAR_H as usize) {
|
||||||
|
for x in 0..metrics.width.min(CHAR_W as usize) {
|
||||||
|
let a = bitmap[y * metrics.width + x];
|
||||||
|
if a > 0 {
|
||||||
|
let px = (x as i32 + metrics.xmin).max(0) as usize;
|
||||||
|
let py = (y as i32 + CHAR_H as i32 - metrics.height as i32 - metrics.ymin).max(0) as usize;
|
||||||
|
if px < CHAR_W as usize && py < CHAR_H as usize {
|
||||||
|
atlas_data[(row * CHAR_H as usize + py) * ATLAS_W as usize + col * CHAR_W as usize + px] = a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let ii = vk::ImageCreateInfo::default()
|
||||||
|
.image_type(vk::ImageType::TYPE_2D)
|
||||||
|
.extent(vk::Extent3D { width: ATLAS_W, height: ATLAS_H, depth: 1 })
|
||||||
|
.mip_levels(1).array_layers(1).format(vk::Format::R8_UNORM)
|
||||||
|
.tiling(vk::ImageTiling::OPTIMAL).initial_layout(vk::ImageLayout::UNDEFINED)
|
||||||
|
.usage(vk::ImageUsageFlags::TRANSFER_DST | vk::ImageUsageFlags::SAMPLED)
|
||||||
|
.samples(vk::SampleCountFlags::TYPE_1).sharing_mode(vk::SharingMode::EXCLUSIVE);
|
||||||
|
let image = unsafe { device.create_image(&ii, None) }.map_err(|e| format!("img: {e:?}"))?;
|
||||||
|
let req = unsafe { device.get_image_memory_requirements(image) };
|
||||||
|
let mt = find_mem_type(instance, pd, req.memory_type_bits, vk::MemoryPropertyFlags::DEVICE_LOCAL)?;
|
||||||
|
let mem = unsafe { device.allocate_memory(&vk::MemoryAllocateInfo::default().allocation_size(req.size).memory_type_index(mt), None) }.map_err(|e| format!("img mem: {e:?}"))?;
|
||||||
|
unsafe { device.bind_image_memory(image, mem, 0).expect("bind img"); }
|
||||||
|
|
||||||
|
// Staging
|
||||||
|
let sz = atlas_data.len() as vk::DeviceSize;
|
||||||
|
let sbi = vk::BufferCreateInfo::default().size(sz).usage(vk::BufferUsageFlags::TRANSFER_SRC).sharing_mode(vk::SharingMode::EXCLUSIVE);
|
||||||
|
let sbuf = unsafe { device.create_buffer(&sbi, None) }.expect("staging buf");
|
||||||
|
let sreq = unsafe { device.get_buffer_memory_requirements(sbuf) };
|
||||||
|
let smt = find_mem_type(instance, pd, sreq.memory_type_bits, vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT)?;
|
||||||
|
let smem = unsafe { device.allocate_memory(&vk::MemoryAllocateInfo::default().allocation_size(sreq.size).memory_type_index(smt), None) }.expect("staging mem");
|
||||||
|
unsafe {
|
||||||
|
device.bind_buffer_memory(sbuf, smem, 0).expect("bind staging");
|
||||||
|
let ptr = device.map_memory(smem, 0, sz, vk::MemoryMapFlags::default()).expect("map staging");
|
||||||
|
std::ptr::copy_nonoverlapping(atlas_data.as_ptr(), ptr as *mut u8, atlas_data.len());
|
||||||
|
device.unmap_memory(smem);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy
|
||||||
|
let cmd = unsafe {
|
||||||
|
let c = device.allocate_command_buffers(&vk::CommandBufferAllocateInfo::default()
|
||||||
|
.command_pool(pool).level(vk::CommandBufferLevel::PRIMARY).command_buffer_count(1)).expect("alloc")[0];
|
||||||
|
device.begin_command_buffer(c, &vk::CommandBufferBeginInfo::default().flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT)).expect("begin");
|
||||||
|
let b1 = vk::ImageMemoryBarrier::default()
|
||||||
|
.old_layout(vk::ImageLayout::UNDEFINED).new_layout(vk::ImageLayout::TRANSFER_DST_OPTIMAL)
|
||||||
|
.src_queue_family_index(vk::QUEUE_FAMILY_IGNORED).dst_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
|
||||||
|
.image(image).subresource_range(vk::ImageSubresourceRange { aspect_mask: vk::ImageAspectFlags::COLOR, base_mip_level: 0, level_count: 1, base_array_layer: 0, layer_count: 1 })
|
||||||
|
.src_access_mask(vk::AccessFlags::default()).dst_access_mask(vk::AccessFlags::TRANSFER_WRITE);
|
||||||
|
device.cmd_pipeline_barrier(c, vk::PipelineStageFlags::TOP_OF_PIPE, vk::PipelineStageFlags::TRANSFER, vk::DependencyFlags::default(), &[], &[], std::slice::from_ref(&b1));
|
||||||
|
device.cmd_copy_buffer_to_image(c, sbuf, image, vk::ImageLayout::TRANSFER_DST_OPTIMAL,
|
||||||
|
std::slice::from_ref(&vk::BufferImageCopy::default()
|
||||||
|
.buffer_row_length(ATLAS_W).buffer_image_height(ATLAS_H)
|
||||||
|
.image_subresource(vk::ImageSubresourceLayers { aspect_mask: vk::ImageAspectFlags::COLOR, mip_level: 0, base_array_layer: 0, layer_count: 1 })
|
||||||
|
.image_extent(vk::Extent3D { width: ATLAS_W, height: ATLAS_H, depth: 1 })));
|
||||||
|
let b2 = vk::ImageMemoryBarrier::default()
|
||||||
|
.old_layout(vk::ImageLayout::TRANSFER_DST_OPTIMAL).new_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL)
|
||||||
|
.src_queue_family_index(vk::QUEUE_FAMILY_IGNORED).dst_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
|
||||||
|
.image(image).subresource_range(vk::ImageSubresourceRange { aspect_mask: vk::ImageAspectFlags::COLOR, base_mip_level: 0, level_count: 1, base_array_layer: 0, layer_count: 1 })
|
||||||
|
.src_access_mask(vk::AccessFlags::TRANSFER_WRITE).dst_access_mask(vk::AccessFlags::SHADER_READ);
|
||||||
|
device.cmd_pipeline_barrier(c, vk::PipelineStageFlags::TRANSFER, vk::PipelineStageFlags::FRAGMENT_SHADER, vk::DependencyFlags::default(), &[], &[], std::slice::from_ref(&b2));
|
||||||
|
device.end_command_buffer(c).expect("end");
|
||||||
|
let si = vk::SubmitInfo::default().command_buffers(std::slice::from_ref(&c));
|
||||||
|
device.queue_submit(*queue, std::slice::from_ref(&si), vk::Fence::null()).expect("submit");
|
||||||
|
device.queue_wait_idle(*queue).expect("wait");
|
||||||
|
c
|
||||||
|
};
|
||||||
|
unsafe {
|
||||||
|
device.free_command_buffers(pool, &[cmd]);
|
||||||
|
device.destroy_buffer(sbuf, None);
|
||||||
|
device.free_memory(smem, None);
|
||||||
|
}
|
||||||
|
|
||||||
|
let vi = vk::ImageViewCreateInfo::default()
|
||||||
|
.image(image).view_type(vk::ImageViewType::TYPE_2D).format(vk::Format::R8_UNORM)
|
||||||
|
.subresource_range(vk::ImageSubresourceRange { aspect_mask: vk::ImageAspectFlags::COLOR, base_mip_level: 0, level_count: 1, base_array_layer: 0, layer_count: 1 });
|
||||||
|
let view = unsafe { device.create_image_view(&vi, None) }.expect("atlas view");
|
||||||
|
let si = vk::SamplerCreateInfo::default()
|
||||||
|
.mag_filter(vk::Filter::LINEAR).min_filter(vk::Filter::LINEAR)
|
||||||
|
.address_mode_u(vk::SamplerAddressMode::CLAMP_TO_EDGE).address_mode_v(vk::SamplerAddressMode::CLAMP_TO_EDGE).address_mode_w(vk::SamplerAddressMode::CLAMP_TO_EDGE)
|
||||||
|
.border_color(vk::BorderColor::INT_OPAQUE_BLACK).unnormalized_coordinates(false);
|
||||||
|
let sampler = unsafe { device.create_sampler(&si, None) }.expect("sampler");
|
||||||
|
Ok((image, mem, view, sampler, atlas_map))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_instance_buffer(
|
||||||
|
device: &ash::Device, instance: &ash::Instance, pd: vk::PhysicalDevice, count: usize,
|
||||||
|
) -> Result<(vk::Buffer, vk::DeviceMemory, *mut CellInstance), String> {
|
||||||
|
let sz = (count * std::mem::size_of::<CellInstance>()) as vk::DeviceSize;
|
||||||
|
let bi = vk::BufferCreateInfo::default().size(sz).usage(vk::BufferUsageFlags::VERTEX_BUFFER).sharing_mode(vk::SharingMode::EXCLUSIVE);
|
||||||
|
let buf = unsafe { device.create_buffer(&bi, None) }.map_err(|e| format!("inst buf: {e:?}"))?;
|
||||||
|
let req = unsafe { device.get_buffer_memory_requirements(buf) };
|
||||||
|
let mt = find_mem_type(instance, pd, req.memory_type_bits, vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT)?;
|
||||||
|
let mem = unsafe { device.allocate_memory(&vk::MemoryAllocateInfo::default().allocation_size(req.size).memory_type_index(mt), None) }.map_err(|e| format!("inst mem: {e:?}"))?;
|
||||||
|
unsafe {
|
||||||
|
device.bind_buffer_memory(buf, mem, 0).expect("bind inst");
|
||||||
|
let ptr = device.map_memory(mem, 0, sz, vk::MemoryMapFlags::default()).expect("map inst");
|
||||||
|
Ok((buf, mem, ptr as *mut CellInstance))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_descriptor_set(device: &ash::Device, set: vk::DescriptorSet, view: vk::ImageView, sampler: vk::Sampler) {
|
||||||
|
let ii = vk::DescriptorImageInfo::default().image_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL).image_view(view).sampler(sampler);
|
||||||
|
let w = vk::WriteDescriptorSet::default().dst_set(set).dst_binding(0)
|
||||||
|
.descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER).image_info(std::slice::from_ref(&ii));
|
||||||
|
unsafe { device.update_descriptor_sets(std::slice::from_ref(&w), &[]); }
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user