MXVK Vulkan Framework 0.24.0
C++20 Vulkan rendering framework for practical 2D and 3D application development with SDL3.
Loading...
Searching...
No Matches
main.cpp
Go to the documentation of this file.
1#include <cstdlib>
2
3#include <algorithm>
4#include <chrono>
5#include <cmath>
6#include <format>
7#include <iostream>
8#include <string>
9#include <vector>
10
11#include <glm/ext/matrix_clip_space.hpp>
12#include <glm/ext/matrix_transform.hpp>
13#include <glm/glm.hpp>
14
15#include "mxvk/argz.hpp"
16#include "mxvk/mxvk.hpp"
19
20namespace example {
21
22 struct SnowFlake {
23 float x;
24 float y;
25 float z;
26 float intensity;
27 float size;
28 float speed;
29 float angle;
30 float rotation;
38 };
39
41 public:
42 static constexpr int MAX_SNOWFLAKES = 600;
43 static constexpr float modelDepthClearance = 0.55f;
44
45 void init(mxvk::VK_Window *window, const std::string &assetRoot, const std::string &fragmentShaderPath) {
46 if (snowSprite != nullptr) {
47 return;
48 }
49
50 snowSprite = window->createSprite3D(assetRoot + "/data/snowflake.png", "", fragmentShaderPath);
51 snowSprite->setDepthTestEnabled(true);
52 snowSprite->setDepthWriteEnabled(false);
53 snowSprite->setAlphaDiscardThreshold(0.1f);
54
55 snowflakes.reserve(MAX_SNOWFLAKES);
56 for (int i = 0; i < MAX_SNOWFLAKES; ++i) {
57 snowflakes.push_back(makeFlake());
58 }
59 }
60
61 void update(float deltaSeconds) {
62 windTime += deltaSeconds * 1.2f;
63 const float windOffsetX = std::sin(windTime) * 0.12f;
64 const float windOffsetZ = std::cos(windTime * 0.7f) * 0.04f;
65 constexpr float twoPi = 6.28318530717958647692f;
66
67 for (auto &flake : snowflakes) {
68 flake.y -= flake.speed * deltaSeconds;
69 flake.x += windOffsetX * deltaSeconds;
70 flake.z += windOffsetZ * deltaSeconds;
71 if (std::abs(flake.z) < modelDepthClearance) {
72 flake.z = (flake.z >= 0.0f) ? modelDepthClearance : -modelDepthClearance;
73 }
74 flake.rotation += (flake.rotationSpeed + std::sin(windTime + flake.rotationAngle) *
75 flake.rotationSpeedIntensity * 0.5f) *
76 deltaSeconds;
77 if (flake.rotation > twoPi) {
78 flake.rotation -= twoPi;
79 } else if (flake.rotation < -twoPi) {
80 flake.rotation += twoPi;
81 }
82
83 if (flake.y < -3.8f) {
84 flake = makeFlake();
85 flake.y = 3.8f;
86 }
87 }
88 }
89
90 void drawLayer(VkCommandBuffer cmd, uint32_t imageIndex, const glm::mat4 &view, const glm::mat4 &proj, bool frontLayer) {
91 if (snowSprite == nullptr) {
92 return;
93 }
94
95 snowSprite->updateCamera(imageIndex, view, proj);
96 for (const SnowFlake &flake : snowflakes) {
97 const bool isFront = flake.z >= 0.0f;
98 if (isFront != frontLayer) {
99 continue;
100 }
101
102 snowSprite->drawSprite(glm::vec3(flake.x, flake.y, flake.z),
103 glm::vec2(flake.size, flake.size),
104 glm::vec4(1.0f, 1.0f, 1.0f, 1.0f),
105 flake.rotation);
106 }
107
108 snowSprite->render(cmd, imageIndex);
109 snowSprite->clearQueue();
110 }
111
112 private:
113 static float randomSignedCoordinate() {
114 return static_cast<float>(std::rand() % 900 - 450) / 100.0f;
115 }
116
117 static float randomDepth() {
118 const float depthRange = 2.5f - modelDepthClearance;
119 const float depth = modelDepthClearance + static_cast<float>(std::rand() % 1000) * (depthRange / 999.0f);
120 return (std::rand() % 2 == 0) ? -depth : depth;
121 }
122
123 static SnowFlake makeFlake() {
124 SnowFlake flake{};
125 flake.x = randomSignedCoordinate();
126 flake.y = static_cast<float>(std::rand() % 760 - 380) / 100.0f;
127 flake.z = randomDepth();
128 flake.intensity = 1.0f;
129 flake.size = 0.06f + static_cast<float>(std::rand() % 180) / 1600.0f;
130 flake.speed = 0.25f + static_cast<float>(std::rand() % 100) / 600.0f;
131 constexpr float pi = 3.14159265358979323846f;
132 flake.angle = static_cast<float>(std::rand() % 360) * (pi / 180.0f);
133 flake.rotation = static_cast<float>(std::rand() % 360) * (pi / 180.0f);
134 flake.rotationAngle = static_cast<float>(std::rand() % 360) * (pi / 180.0f);
135 flake.rotationDirection = (std::rand() % 2) ? 1.0f : -1.0f;
136 flake.rotationIntensity = static_cast<float>(std::rand() % 100) / 100.0f;
137 flake.rotationSpeed = flake.rotationDirection * (0.8f + flake.rotationIntensity * 2.0f) +
138 ((static_cast<float>(std::rand() % 100) / 1000.0f) - 0.05f);
139 flake.rotationSize = 0.01f + static_cast<float>(std::rand() % 100) / 4000.0f;
140 flake.rotationSpeedSize = (static_cast<float>(std::rand() % 100) / 2000.0f) - 0.025f;
141 flake.rotationSpeedIntensity = static_cast<float>(std::rand() % 100) / 100.0f;
142 return flake;
143 }
144
145 std::vector<SnowFlake> snowflakes;
146 mxvk::VK_Sprite3D *snowSprite = nullptr;
147 float windTime = 0.0f;
148 };
149
150 class ModelWindow : public mxvk::VK_Window {
151 public:
152 ModelWindow(const std::string &filename, const std::string &path, const std::string &title, int width, int height, bool fullscreen, bool enable_vsync)
153 : mxvk::VK_Window(title, width, height, fullscreen, MXVK_VALIDATION, enable_vsync),
154 assetRoot((path.empty() || path == ".") ? std::string(tux_example_ASSET_DIR) : path) {
155 const std::string shaderRoot = assetRoot + "/data";
156 const std::string modelPath = filename.empty() ? (assetRoot + "/data/tux.obj") : filename;
157 const std::string vertPath = shaderRoot + "/model.vert.spv";
158 const std::string fragPath = shaderRoot + "/model.frag.spv";
159 const std::string backgroundVertPath = shaderRoot + "/background.vert.spv";
160 const std::string backgroundFragPath = shaderRoot + "/background.frag.spv";
161 const std::string snowflakeFragPath = shaderRoot + "/snowflake.frag.spv";
162 const std::string backgroundPath = assetRoot + "/data/ant-bg.png";
163
164 setFont(assetRoot + "/data/font.ttf", 24);
165 background = createSprite(backgroundPath, backgroundVertPath, backgroundFragPath);
166 snow.init(this, assetRoot, snowflakeFragPath);
167 model.load(this, modelPath, "", assetRoot + "/data", 1.0f);
168 model.setShaders(this, vertPath, fragPath);
169 }
170
171 ~ModelWindow() override {
172 if (device != VK_NULL_HANDLE) {
173 vkDeviceWaitIdle(device);
174 }
175 model.cleanup(this);
176 }
177
178 void event(SDL_Event &e) override {
179 if (e.type == SDL_EVENT_KEY_DOWN && e.key.key == SDLK_ESCAPE) {
180 exit();
181 return;
182 }
183 }
184
185 void proc() override {
186 printText("Tux Example", 15, 15, {255, 255, 255, 255});
187 }
188
189 void onSwapchainRecreated() override {
190 model.resize(this);
191 }
192
193 void onRecordCustomRendering(VkCommandBuffer cmd, uint32_t imageIndex) override {
194 const auto now = std::chrono::steady_clock::now();
195 const float elapsedSeconds = std::chrono::duration<float>(now - start).count();
196 const VkExtent2D extent = getSwapchainExtent();
197
198 if (background != nullptr && sprite_pipeline != VK_NULL_HANDLE && sprite_pipeline_layout != VK_NULL_HANDLE) {
199 background->setShaderParams(elapsedSeconds, 0.0f, 0.0f, 0.0f);
200 background->drawSpriteRect(0, 0, static_cast<int>(extent.width), static_cast<int>(extent.height));
201 background->renderSprites(cmd, sprite_pipeline_layout, extent.width, extent.height);
202 // Prevent the generic overlay pass from drawing the same full-screen sprite on top.
203 background->clearQueue();
204 }
205
206 const float deltaSeconds = std::chrono::duration<float>(now - lastSnowUpdate).count();
207 lastSnowUpdate = now;
208 snow.update(deltaSeconds);
209
210 const float aspect = (extent.height > 0U)
211 ? static_cast<float>(extent.width) / static_cast<float>(extent.height)
212 : 1.0f;
213
215 ubo.model = glm::rotate(glm::mat4(1.0f), elapsedSeconds * autoSpinSpeed, glm::vec3(0.0f, 1.0f, 0.0f));
216 ubo.model = glm::scale(ubo.model, glm::vec3(model.modelRenderScale()));
217 ubo.model = glm::translate(ubo.model, model.modelCenterOffset());
218 ubo.view = glm::lookAt(glm::vec3(0.0f, 0.0f, 4.2f), glm::vec3(0.0f), glm::vec3(0.0f, 1.0f, 0.0f));
219 ubo.proj = glm::perspective(glm::radians(50.0f), aspect, 0.1f, 100.0f);
220 ubo.proj[1][1] *= -1.0f;
221
222 model.updateUBO(imageIndex, ubo);
223
224 snow.drawLayer(cmd, imageIndex, ubo.view, ubo.proj, false);
225 model.render(cmd, imageIndex, false);
226 snow.drawLayer(cmd, imageIndex, ubo.view, ubo.proj, true);
227 }
228
229 private:
230 std::string assetRoot;
231 mxvk::VK_Sprite *background = nullptr;
232 mxvk::VKAbstractModel model{};
233 SnowEffect3D snow{};
234 std::chrono::steady_clock::time_point start{std::chrono::steady_clock::now()};
235 std::chrono::steady_clock::time_point lastSnowUpdate{std::chrono::steady_clock::now()};
236 float autoSpinSpeed = 0.65f;
237 };
238
239} // namespace example
240
241int main(int argc, char **argv) {
242 try {
243 const Arguments args = proc_args(argc, argv);
244 example::ModelWindow window(args.filename, args.path, "MXVK Tux Example", args.width, args.height, args.fullscreen, args.enable_vsync);
245 window.loop();
246 } catch (mxvk::Exception &e) {
247 std::cerr << std::format("mxvk: Exception: {}\n", e.text());
248 return EXIT_FAILURE;
249 } catch (ArgException<std::string> &e) {
250 std::cerr << std::format("mxvk: Argument Exception: {}\n", e.text());
251 return EXIT_FAILURE;
252 }
253
254 return EXIT_SUCCESS;
255}
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
void proc() override
Execute one processing/update step.
Definition main.cpp:185
void onRecordCustomRendering(VkCommandBuffer cmd, uint32_t imageIndex) override
Optional hook for derived classes to record extra draw commands.
Definition main.cpp:193
void onSwapchainRecreated() override
Called after swapchain and render resources are recreated.
Definition main.cpp:189
void event(SDL_Event &e) override
Handle one SDL event.
Definition main.cpp:178
~ModelWindow() override
Definition main.cpp:171
ModelWindow(const std::string &filename, const std::string &path, const std::string &title, int width, int height, bool fullscreen, bool enable_vsync)
Definition main.cpp:152
void init(mxvk::VK_Window *window, const std::string &assetRoot, const std::string &fragmentShaderPath)
Definition main.cpp:45
void drawLayer(VkCommandBuffer cmd, uint32_t imageIndex, const glm::mat4 &view, const glm::mat4 &proj, bool frontLayer)
Definition main.cpp:90
static constexpr int MAX_SNOWFLAKES
Definition main.cpp:42
static constexpr float modelDepthClearance
Definition main.cpp:43
void update(float deltaSeconds)
Definition main.cpp:61
std::string text() const
Convenience wrapper that owns mesh, textures, descriptors, and pipeline state.
void setDepthTestEnabled(bool enabled)
Enable or disable depth testing for the 3D sprite pipeline.
Main Vulkan window wrapper for MXVK.
Definition mxvk.hpp:37
VkPipelineLayout sprite_pipeline_layout
Definition mxvk.hpp:543
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
VK_Sprite3D * createSprite3D(const std::string &pngPath, const std::string &vertexShaderPath="", const std::string &fragmentShaderPath="")
Create a world-space billboard sprite from a PNG file.
Definition mxvk.cpp:3578
VkDevice device
Definition mxvk.hpp:485
VK_Sprite * createSprite(const std::string &pngPath, const std::string &vertexShaderPath="", const std::string &fragmentShaderPath="")
Create a sprite from a PNG file and register it with this window.
Definition mxvk.cpp:3477
VkPipeline sprite_pipeline
Definition mxvk.hpp:544
void exit()
Request loop termination.
Definition mxvk.cpp:1126
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
int main(void)
Definition main.cpp:7
#define MXVK_VALIDATION
Definition mxvk.hpp:27
High-level model wrapper integrated with MXVK dynamic rendering.
Utilities for loading and saving PNG images.
Definition mxvk.hpp: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 filename
Optional input filename (--filename).
Definition argz.hpp:738
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 rotationSpeed
Definition main.cpp:31
float rotationDirection
Definition main.cpp:33
float rotationIntensity
Definition main.cpp:34
float rotationSpeedSize
Definition main.cpp:36
float rotationSpeedIntensity
Definition main.cpp:37
float rotationAngle
Definition main.cpp:32
float rotationSize
Definition main.cpp:35
Default transform UBO payload for model shaders.