MXVK Vulkan Framework 0.24.0
C++20 Vulkan rendering framework for practical 2D and 3D application development with SDL3.
Loading...
Searching...
No Matches
fireworks.cpp
Go to the documentation of this file.
1#include "mxvk/argz.hpp"
2#include "mxvk/mxvk.hpp"
5
6#include <algorithm>
7#include <cmath>
8#include <cstdlib>
9#include <format>
10#include <iostream>
11#include <random>
12#include <string>
13#include <vector>
14
15#include <glm/ext/matrix_clip_space.hpp>
16#include <glm/ext/matrix_transform.hpp>
17#include <glm/glm.hpp>
18#include <glm/gtc/random.hpp>
19
20namespace {
21
22 constexpr std::size_t PARTICLES_PER_EXPLOSION = 250;
23 constexpr std::size_t MAX_EXPLOSIONS = 256;
25 constexpr float MIN_LIFETIME = 0.5f;
26 constexpr float MAX_LIFETIME = 2.0f;
27 constexpr float PARTICLE_SPEED = 1.0f;
28 constexpr float PARTICLE_SIZE = 25.0f;
29
30 struct Particle {
31 glm::vec3 position{};
32 glm::vec3 velocity{};
33 glm::vec4 color{};
34 float lifetime = 0.0f;
35 float initial_lifetime = 1.0f;
36 };
37
38 struct Explosion {
39 std::vector<Particle> particles{};
40 };
41
42 [[nodiscard]] float random_float(float min, float max) {
43 static std::random_device rd;
44 static std::default_random_engine engine(rd());
45 std::uniform_real_distribution<float> dist(min, max);
46 return dist(engine);
47 }
48
49} // namespace
50
51namespace example {
52
54 public:
55 FireworksWindow(const std::string &data_root, int width, int height, bool fullscreen, bool enable_vsync)
56 : mxvk::VK_Window("Fireworks - [3D Particle Effect]", width, height, fullscreen, MXVK_VALIDATION, enable_vsync),
57 data_root(data_root),
58 vertices(MAX_PARTICLES) {
59 setClearColor(0.0f, 0.0f, 0.0f, 1.0f);
60 setFont(data_root + "/font.ttf", 24);
61 last_update_time = SDL_GetTicks();
62 }
63
64 ~FireworksWindow() override {
65 if (device != VK_NULL_HANDLE) {
66 vkDeviceWaitIdle(device);
67 }
68 point_batch.cleanup();
69 }
70
71 void event(SDL_Event &e) override {
72 if (e.type == SDL_EVENT_KEY_DOWN && e.key.key == SDLK_ESCAPE) {
73 exit();
74 } else if (e.type == SDL_EVENT_KEY_DOWN && e.key.key == SDLK_SPACE) {
75 trigger_random_explosion();
76 }
77 }
78
79 void proc() override {
80 if (!has_active_particles()) {
81 printText("Explosion - Press Space to Trigger", 25, 25, SDL_Color{255, 64, 64, 255});
82 }
83 }
84
85 void onSwapchainRecreated() override {
86 point_batch.resize(this);
87 }
88
89 void onRecordCustomRendering(VkCommandBuffer cmd, uint32_t image_index) override {
90 if (!ensure_resources()) {
91 return;
92 }
93
94 const Uint32 current_time = SDL_GetTicks();
95 float delta_time = static_cast<float>(current_time - last_update_time) / 1000.0f;
96 last_update_time = current_time;
97 delta_time = std::min(delta_time, 0.1f);
98
99 update_particles(delta_time);
100 write_vertices();
101
102 point_batch.upload_vertices(vertices.data(), active_vertices);
103 point_batch.update_mvp(image_index, make_mvp());
104 point_batch.render(cmd, image_index);
105 }
106
107 private:
108 bool ensure_resources() {
109 if (point_batch.loaded()) {
110 return true;
111 }
112 if (device == VK_NULL_HANDLE || command_pool == VK_NULL_HANDLE || getSwapchainImageCount() == 0U || getSwapchainFormat() == VK_FORMAT_UNDEFINED) {
113 return false;
114 }
115
116 point_batch.load(
117 this,
118 data_root + "/star.png",
119 data_root + "/fireworks.vert.spv",
120 data_root + "/fireworks.frag.spv",
121 vertices.size());
122 point_batch.set_additive_blending(true);
123 point_batch.set_depth_test_enabled(false);
124 point_batch.set_depth_write_enabled(false);
125 last_update_time = SDL_GetTicks();
126 return true;
127 }
128
129 void trigger_random_explosion() {
130 trigger_explosion(glm::vec3(random_float(-3.0f, 3.0f), random_float(-3.0f, 3.0f), 0.0f));
131 }
132
133 void trigger_explosion(const glm::vec3 &origin) {
134 if (explosions.size() >= MAX_EXPLOSIONS) {
135 return;
136 }
137
138 Explosion explosion;
139 explosion.particles.resize(PARTICLES_PER_EXPLOSION);
140 for (Particle &particle : explosion.particles) {
141 reset_particle(particle, origin);
142 }
143 explosions.push_back(std::move(explosion));
144 }
145
146 void reset_particle(Particle &particle, const glm::vec3 &origin) const {
147 particle.position = origin;
148 particle.velocity = glm::sphericalRand(PARTICLE_SPEED);
149 particle.lifetime = random_float(MIN_LIFETIME, MAX_LIFETIME);
150 particle.initial_lifetime = particle.lifetime;
151 particle.color = glm::vec4(random_float(0.2f, 1.0f), random_float(0.2f, 1.0f), random_float(0.2f, 1.0f), 1.0f);
152 }
153
154 void update_particles(float delta_time) {
155 for (Explosion &explosion : explosions) {
156 for (Particle &particle : explosion.particles) {
157 if (particle.lifetime <= 0.0f) {
158 continue;
159 }
160 particle.lifetime -= delta_time;
161 particle.position += particle.velocity * delta_time;
162 particle.color.a = particle.lifetime;
163 }
164 }
165
166 std::erase_if(explosions, [](const Explosion &explosion) {
167 return std::ranges::none_of(explosion.particles, [](const Particle &particle) {
168 return particle.lifetime > 0.0f;
169 });
170 });
171 }
172
173 [[nodiscard]] bool has_active_particles() const {
174 return std::ranges::any_of(explosions, [](const Explosion &explosion) {
175 return std::ranges::any_of(explosion.particles, [](const Particle &particle) {
176 return particle.lifetime > 0.0f;
177 });
178 });
179 }
180
181 void write_vertices() {
182 active_vertices = 0;
183 for (const Explosion &explosion : explosions) {
184 for (const Particle &particle : explosion.particles) {
185 if (particle.lifetime <= 0.0f || active_vertices >= vertices.size()) {
186 continue;
187 }
188
189 mxvk::PointSpriteVertex &vertex = vertices[active_vertices++];
190 vertex.position[0] = particle.position.x;
191 vertex.position[1] = particle.position.y;
192 vertex.position[2] = particle.position.z;
193 vertex.size = PARTICLE_SIZE;
194 vertex.color[0] = particle.color.r;
195 vertex.color[1] = particle.color.g;
196 vertex.color[2] = particle.color.b;
197 vertex.color[3] = particle.color.a;
198 }
199 }
200 }
201
202 [[nodiscard]] glm::mat4 make_mvp() const {
203 const VkExtent2D extent = getSwapchainExtent();
204 const float aspect = extent.height > 0U ? static_cast<float>(extent.width) / static_cast<float>(extent.height) : 16.0f / 9.0f;
205 glm::mat4 projection = glm::perspective(glm::radians(45.0f), aspect, 0.1f, 100.0f);
206 projection[1][1] *= -1.0f;
207 const glm::mat4 view = glm::lookAt(glm::vec3(0.0f, 0.0f, 5.0f), glm::vec3(0.0f), glm::vec3(0.0f, 1.0f, 0.0f));
208 const glm::mat4 model(1.0f);
209 return projection * view * model;
210 }
211
212 std::string data_root{};
213 std::vector<Explosion> explosions{};
214 std::vector<mxvk::PointSpriteVertex> vertices{};
215 mxvk::VK_PointSpriteBatch point_batch{};
216 std::size_t active_vertices = 0;
217 Uint32 last_update_time = 0;
218 };
219
220} // namespace example
221
222int main(int argc, char **argv) {
223 try {
224 const Arguments args = proc_args(argc, argv);
225 const std::string root = args.path.empty() ? std::string(FIREWORKS_ASSET_DIR) : args.path;
226 example::FireworksWindow window(root + "/data", args.width, args.height, args.fullscreen, args.enable_vsync);
227 window.loop();
228 } catch (mxvk::Exception &e) {
229 std::cerr << std::format("mxvk: Exception: {}\n", e.text());
230 return EXIT_FAILURE;
231 } catch (ArgException<std::string> &e) {
232 std::cerr << std::format("mxvk: Argument Exception: {}\n", e.text());
233 return EXIT_FAILURE;
234 }
235
236 return EXIT_SUCCESS;
237}
Lightweight, header-only, template command-line argument parser.
Arguments proc_args(int &argc, char **argv)
Parse standard libmx2 command-line options from main()'s argv.
Definition argz.hpp:872
Exception thrown by Argz::proc() on unrecognised or malformed options.
Definition argz.hpp:178
FireworksWindow(const std::string &data_root, int width, int height, bool fullscreen, bool enable_vsync)
Definition fireworks.cpp:55
void event(SDL_Event &e) override
Handle one SDL event.
Definition fireworks.cpp:71
void proc() override
Execute one processing/update step.
Definition fireworks.cpp:79
void onSwapchainRecreated() override
Called after swapchain and render resources are recreated.
Definition fireworks.cpp:85
void onRecordCustomRendering(VkCommandBuffer cmd, uint32_t image_index) override
Optional hook for derived classes to record extra draw commands.
Definition fireworks.cpp:89
std::string text() const
Main Vulkan window wrapper for MXVK.
Definition mxvk.hpp:37
VkExtent2D getSwapchainExtent() const noexcept
Get the current swapchain extent.
Definition mxvk.hpp:186
void loop()
Run the main event/render loop.
Definition mxvk.cpp:600
VkDevice device
Definition mxvk.hpp:485
size_t getSwapchainImageCount() const noexcept
Get the number of swapchain images currently allocated.
Definition mxvk.hpp:192
void setClearColor(float r, float g, float b, float a=1.0f)
Set the per-frame color attachment clear color.
Definition mxvk.cpp:593
void exit()
Request loop termination.
Definition mxvk.cpp:1126
VkCommandPool command_pool
Definition mxvk.hpp:504
VK_Window()=default
Construct an empty window object.
void setFont(const std::string &fontPath, int fontSize=24)
Set the active text-render font.
Definition mxvk.cpp:2991
void printText(const std::string &text, int x, int y, const SDL_Color &col)
Queue a text string for rendering during the current frame.
Definition mxvk.cpp:3018
VkFormat getSwapchainFormat() const noexcept
Get the swapchain color format.
Definition mxvk.hpp:183
int main(void)
Definition main.cpp:7
#define MXVK_VALIDATION
Definition mxvk.hpp:27
Reusable point-sprite renderer for particle and starfield effects.
constexpr std::size_t PARTICLES_PER_EXPLOSION
Definition fireworks.cpp:22
float random_float(float min, float max)
Definition fireworks.cpp:42
constexpr std::size_t MAX_PARTICLES
Definition fireworks.cpp:24
constexpr std::size_t MAX_EXPLOSIONS
Definition fireworks.cpp:23
Utilities for loading and saving PNG images.
Definition mxvk.hpp:30
float random_float(float min_value, float max_value)
Generates a uniformly distributed floating-point value.
constexpr int MAX_PARTICLES
Definition space.cpp:30
Plain data structure returned by proc_args() with all common libmx2 CLI options.
Definition argz.hpp:730
bool fullscreen
Whether fullscreen mode was requested.
Definition argz.hpp:736
bool enable_vsync
Enable FIFO present mode / v-sync (--enable-vsync).
Definition argz.hpp:750
int height
Viewport height in pixels (default: 720).
Definition argz.hpp:733
std::string path
Asset root; proc_args() defaults it to the executable directory.
Definition argz.hpp:735
int width
Viewport width in pixels (default: 1280).
Definition argz.hpp:732
float y
Definition space.cpp:76
int lifetime
Definition space.cpp:78
float x
Definition space.cpp:76
float color[4]
RGBA tint color passed to shader location 2.
float size
Rasterized point size in pixels, passed to shader location 1.
float position[3]
Vertex position passed to shader location 0.