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 "mxvk/argz.hpp"
2#include "mxvk/mxvk.hpp"
4#if defined(MXVK_USE_EIGEN_MATH)
6#else
7#include "mxvk/mxvk_math.h"
8#endif
9
10#include <SDL3/SDL.h>
11
12#include <algorithm>
13#include <array>
14#include <cmath>
15#include <cstdint>
16#include <cstdlib>
17#include <filesystem>
18#include <format>
19#include <iostream>
20#include <limits>
21#include <memory>
22#include <string>
23#include <vector>
24
25namespace {
26 class SurfaceDeleter {
27 public:
28 void operator()(SDL_Surface *surface) const {
29 SDL_DestroySurface(surface);
30 }
31 };
32
33 using SurfacePtr = std::unique_ptr<SDL_Surface, SurfaceDeleter>;
34
35 [[nodiscard]] SurfacePtr create_frame_surface(int width, int height) {
36 SurfacePtr surface(SDL_CreateSurface(width, height, SDL_PIXELFORMAT_RGBA32));
37 if (!surface) {
38 throw mxvk::Exception(std::format("3dmath_pyramid: failed to create frame surface: {}", SDL_GetError()));
39 }
40 return surface;
41 }
42
43 [[nodiscard]] std::filesystem::path pyramid_path(const std::string &asset_path) {
44 return std::filesystem::path(asset_path) / "data" / "pyramid.plg";
45 }
46
47 struct FaceDraw {
48 std::array<mxvk::vec4D, 3> points{};
49 std::array<mxvk::vec2D, 3> texcoords{};
50 float intensity = 1.0f;
51 };
52
53 constexpr float MIN_CAMERA_DISTANCE = 2.5f;
54 constexpr float MAX_CAMERA_DISTANCE = 12.0f;
55 constexpr float CAMERA_ZOOM_STEP = 0.45f;
56} // namespace
57
58namespace example {
60 public:
61 Math3DPyramidWindow(const std::string &asset_path, const std::string &title, int width, int height, bool fullscreen, bool enable_vsync, const FramebufferDimensions &framebuffer)
62 : mxvk::VK_Window(title, width, height, fullscreen, MXVK_VALIDATION, enable_vsync),
63 frame_width(framebuffer.width),
64 frame_height(framebuffer.height),
65 fallback_width(width),
66 fallback_height(height) {
67 setClearColor(0.012f, 0.015f, 0.022f, 1.0f);
69
70 const std::filesystem::path model_path = pyramid_path(asset_path);
71 if (!pyramid.LoadPLG(model_path.string(), mxvk::vec4D(2.0f, 2.0f, 2.0f), mxvk::vec4D(0.0f, 0.0f, 4.5f), mxvk::vec4D())) {
72 throw mxvk::Exception(std::format("3dmath_pyramid: failed to load PLG model '{}'", model_path.string()));
73 }
74 }
75
76 void event(SDL_Event &e) override {
77 if (e.type == SDL_EVENT_KEY_DOWN && e.key.key == SDLK_ESCAPE) {
78 exit();
79 }
80 if (e.type == SDL_EVENT_MOUSE_WHEEL) {
81 const float delta = e.wheel.y != 0.0f ? e.wheel.y : static_cast<float>(e.wheel.integer_y);
82 camera_distance = std::clamp(camera_distance - delta * CAMERA_ZOOM_STEP, MIN_CAMERA_DISTANCE, MAX_CAMERA_DISTANCE);
83 }
84 }
85
86 void proc() override {
87 const int output_width = swapchain_extent.width > 0U ? static_cast<int>(swapchain_extent.width) : fallback_width;
88 const int output_height = swapchain_extent.height > 0U ? static_cast<int>(swapchain_extent.height) : fallback_height;
89
90 ensure_framebuffer();
91 if (frame_sprite == nullptr || frame_surface == nullptr || frame_format == nullptr) {
92 return;
93 }
94
95 clear_frame(mxvk::MXVK_RGB(3, 4, 8));
96 std::ranges::fill(depth_buffer, std::numeric_limits<float>::infinity());
97
98 const float time = static_cast<float>(SDL_GetTicks()) * 0.001f;
99 mxvk::Mat4D rotation;
100 rotation.BuildXYZ(0.0f, time * 42.0f, 0.0f);
101
102 std::vector<mxvk::vec4D> camera_vertices(pyramid.local.size());
103 std::vector<mxvk::vec4D> projected(pyramid.local.size());
104 for (std::size_t i = 0; i < pyramid.local.size(); ++i) {
105 camera_vertices[i] = rotation.MulVec(pyramid.local[i]);
106 camera_vertices[i].z += camera_distance;
107 projected[i] = project_to_screen(camera_vertices[i], frame_width, frame_height);
108 }
109
110 mxvk::vec4D light_direction(-0.35f, -0.55f, -1.0f, 0.0f);
111 light_direction.Normalize();
112 std::vector<FaceDraw> faces;
113 faces.reserve(pyramid.vlist.size());
114
115 for (const mxvk::Triangle &triangle : pyramid.vlist) {
116 const auto first = static_cast<std::size_t>(triangle.vert[0]);
117 const auto second = static_cast<std::size_t>(triangle.vert[1]);
118 const auto third = static_cast<std::size_t>(triangle.vert[2]);
119
120 const mxvk::vec4D &a = camera_vertices[first];
121 const mxvk::vec4D &b = camera_vertices[second];
122 const mxvk::vec4D &c = camera_vertices[third];
123 mxvk::vec4D normal = mxvk::vec4D().Build(a, b).CrossProduct(mxvk::vec4D().Build(a, c));
124 normal.Normalize();
125
126 const mxvk::vec4D center = (a + b + c) * (1.0f / 3.0f);
127 const mxvk::vec4D view_direction(-center.x, -center.y, -center.z, 0.0f);
128 if (normal.DotProduct(view_direction) <= 0.0f) {
129 continue;
130 }
131
132 const float diffuse = std::max(0.0f, normal.DotProduct(light_direction));
133 const float intensity = std::clamp(0.28f + diffuse * 0.72f, 0.0f, 1.0f);
134 faces.push_back({
135 {projected[first], projected[second], projected[third]},
136 {
137 pyramid.texcoords[first],
138 pyramid.texcoords[second],
139 pyramid.texcoords[third],
140 },
141 intensity,
142 });
143 }
144
145 for (const FaceDraw &face : faces) {
146 draw_gradient_triangle(face);
147 }
148
149 frame_sprite->updateTexture(frame_surface.get());
150 frame_sprite->drawSpriteRect(0, 0, output_width, output_height);
151 }
152
153 private:
154 mxvk::mxObject pyramid;
155 SurfacePtr frame_surface;
156 std::vector<float> depth_buffer;
157 const SDL_PixelFormatDetails *frame_format = nullptr;
158 mxvk::VK_Sprite *frame_sprite = nullptr;
159 int frame_width = 1280;
160 int frame_height = 720;
161 int fallback_width = 1280;
162 int fallback_height = 720;
163 float camera_distance = 4.5f;
164
165 void ensure_framebuffer() {
166 if (frame_surface != nullptr) {
167 return;
168 }
169
170 frame_surface = create_frame_surface(frame_width, frame_height);
171 frame_format = SDL_GetPixelFormatDetails(frame_surface->format);
172 if (frame_format == nullptr) {
173 throw mxvk::Exception(std::format("3dmath_pyramid: failed to query frame format: {}", SDL_GetError()));
174 }
175
176 depth_buffer.resize(static_cast<std::size_t>(frame_width) * static_cast<std::size_t>(frame_height));
177 clear_frame(mxvk::MXVK_RGB(3, 4, 8));
178
179 frame_sprite = createSprite(frame_surface.get());
180 frame_sprite->setTextureFilter(VK_FILTER_NEAREST);
181 }
182
183 [[nodiscard]] std::uint32_t map_color(mxvk::MXCOLOR color) const {
184 return SDL_MapRGBA(frame_format, nullptr, mxvk::color_r(color), mxvk::color_g(color), mxvk::color_b(color), mxvk::color_a(color));
185 }
186
187 void clear_frame(mxvk::MXCOLOR color) {
188 SDL_FillSurfaceRect(frame_surface.get(), nullptr, map_color(color));
189 }
190
191 void put_pixel(int x, int y, mxvk::MXCOLOR color) {
192 if (x < 0 || y < 0 || x >= frame_width || y >= frame_height) {
193 return;
194 }
195
196 auto *row = static_cast<std::uint8_t *>(frame_surface->pixels) + (static_cast<std::size_t>(y) * static_cast<std::size_t>(frame_surface->pitch));
197 auto *pixel = reinterpret_cast<std::uint32_t *>(row) + x;
198 *pixel = map_color(color);
199 }
200
201 void draw_gradient_triangle(const FaceDraw &face) {
202 const mxvk::vec4D &a = face.points[0];
203 const mxvk::vec4D &b = face.points[1];
204 const mxvk::vec4D &c = face.points[2];
205 const auto edge = [](const mxvk::vec4D &first, const mxvk::vec4D &second, float x, float y) {
206 return (x - first.x) * (second.y - first.y) - (y - first.y) * (second.x - first.x);
207 };
208
209 const float area = edge(b, c, a.x, a.y);
210 if (std::abs(area) <= mxvk::EPSILON) {
211 return;
212 }
213
214 const int min_x = std::clamp(static_cast<int>(std::floor(std::min({a.x, b.x, c.x}))), 0, frame_width - 1);
215 const int max_x = std::clamp(static_cast<int>(std::ceil(std::max({a.x, b.x, c.x}))), 0, frame_width - 1);
216 const int min_y = std::clamp(static_cast<int>(std::floor(std::min({a.y, b.y, c.y}))), 0, frame_height - 1);
217 const int max_y = std::clamp(static_cast<int>(std::ceil(std::max({a.y, b.y, c.y}))), 0, frame_height - 1);
218
219 for (int y = min_y; y <= max_y; ++y) {
220 for (int x = min_x; x <= max_x; ++x) {
221 const float sample_x = static_cast<float>(x) + 0.5f;
222 const float sample_y = static_cast<float>(y) + 0.5f;
223 const float weight_a = edge(b, c, sample_x, sample_y) / area;
224 const float weight_b = edge(c, a, sample_x, sample_y) / area;
225 const float weight_c = edge(a, b, sample_x, sample_y) / area;
226 if (weight_a < -mxvk::EPSILON || weight_b < -mxvk::EPSILON || weight_c < -mxvk::EPSILON) {
227 continue;
228 }
229
230 const float reciprocal_depth =
231 weight_a / a.z +
232 weight_b / b.z +
233 weight_c / c.z;
234 if (reciprocal_depth <= mxvk::EPSILON) {
235 continue;
236 }
237
238 const float depth = 1.0f / reciprocal_depth;
239 const std::size_t pixel_index =
240 static_cast<std::size_t>(y) * static_cast<std::size_t>(frame_width) +
241 static_cast<std::size_t>(x);
242 if (depth >= depth_buffer[pixel_index]) {
243 continue;
244 }
245 depth_buffer[pixel_index] = depth;
246
247 const float perspective_a = (weight_a / a.z) * depth;
248 const float perspective_b = (weight_b / b.z) * depth;
249 const float perspective_c = (weight_c / c.z) * depth;
250 const float u =
251 face.texcoords[0].x * perspective_a +
252 face.texcoords[1].x * perspective_b +
253 face.texcoords[2].x * perspective_c;
254 const float v =
255 face.texcoords[0].y * perspective_a +
256 face.texcoords[1].y * perspective_b +
257 face.texcoords[2].y * perspective_c;
258 put_pixel(x, y, mxvk::shade_color(gradient_color(u, v), face.intensity));
259 }
260 }
261 }
262
263 [[nodiscard]] static mxvk::MXCOLOR gradient_color(float u, float v) {
264 u = std::clamp(u, 0.0f, 1.0f);
265 v = std::clamp(v, 0.0f, 1.0f);
266
267 constexpr mxvk::MXCOLOR BOTTOM_LEFT = mxvk::MXVK_RGB(68, 214, 255);
268 constexpr mxvk::MXCOLOR BOTTOM_RIGHT = mxvk::MXVK_RGB(92, 255, 142);
269 constexpr mxvk::MXCOLOR TOP_LEFT = mxvk::MXVK_RGB(155, 105, 255);
270 constexpr mxvk::MXCOLOR TOP_RIGHT = mxvk::MXVK_RGB(255, 82, 197);
271 const auto bilinear_channel = [&](auto component) {
272 const float bottom =
273 static_cast<float>(component(BOTTOM_LEFT)) +
274 (static_cast<float>(component(BOTTOM_RIGHT)) - static_cast<float>(component(BOTTOM_LEFT))) * u;
275 const float top =
276 static_cast<float>(component(TOP_LEFT)) +
277 (static_cast<float>(component(TOP_RIGHT)) - static_cast<float>(component(TOP_LEFT))) * u;
278 return std::clamp(static_cast<int>(std::lround(bottom + (top - bottom) * v)), 0, 255);
279 };
280
281 return mxvk::MXVK_RGB(
282 bilinear_channel(mxvk::color_r),
283 bilinear_channel(mxvk::color_g),
284 bilinear_channel(mxvk::color_b));
285 }
286
287 [[nodiscard]] static mxvk::vec4D project_to_screen(const mxvk::vec4D &point, int width, int height) {
288 const float scale = static_cast<float>(std::min(width, height)) * 0.52f;
289 const float center_x = static_cast<float>(width) * 0.5f;
290 const float center_y = static_cast<float>(height) * 0.5f;
291 const float z = std::max(point.z, 0.001f);
292 return {center_x + (point.x / z) * scale, center_y - (point.y / z) * scale, point.z, 1.0f};
293 }
294 };
295} // namespace example
296
297int main(int argc, char **argv) {
298 try {
299 Arguments args = proc_args(argc, argv);
300 example::Math3DPyramidWindow window(args.path, "MXVK 3D Math Pyramid", args.width, args.height, args.fullscreen, args.enable_vsync, args.framebuffer);
301 window.loop();
302 } catch (mxvk::Exception &e) {
303 std::cerr << std::format("mxvk: Exception: {}\n", e.text());
304 return EXIT_FAILURE;
305 } catch (ArgException<std::string> &e) {
306 std::cerr << std::format("mxvk: Argument Exception: {}\n", e.text());
307 return EXIT_FAILURE;
308 }
309 return EXIT_SUCCESS;
310}
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 operator()(SDL_Surface *surface) const
Definition main.cpp:28
Math3DPyramidWindow(const std::string &asset_path, const std::string &title, int width, int height, bool fullscreen, bool enable_vsync, const FramebufferDimensions &framebuffer)
Definition main.cpp:61
void event(SDL_Event &e) override
Handle one SDL event.
Definition main.cpp:76
void proc() override
Execute one processing/update step.
Definition main.cpp:86
std::string text() const
Four-by-four homogeneous transform matrix.
Definition mxvk_math.h:812
void BuildXYZ(float theta_x, float theta_y, float theta_z)
Build an XYZ Euler rotation matrix from angles in degrees.
Definition mxvk_math.h:973
vec4D MulVec(const vec4D &in) const
Transform a homogeneous 4D vector by this matrix.
Definition mxvk_math.h:881
Main Vulkan window wrapper for MXVK.
Definition mxvk.hpp:37
void loop()
Run the main event/render loop.
Definition mxvk.cpp:600
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
VkExtent2D swapchain_extent
Definition mxvk.hpp:495
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
VK_Window()=default
Construct an empty window object.
Simple mesh object loaded from PLG-style indexed triangle data.
Definition mxvk_math.h:1439
Four-dimensional float vector used for homogeneous 3D coordinates.
Definition mxvk_math.h:416
float y
Y coordinate.
Definition mxvk_math.h:422
float x
X coordinate.
Definition mxvk_math.h:419
void Normalize()
Normalize the 3D components in place and reset W to 1.
Definition mxvk_math.h:518
constexpr float DotProduct(const vec4D &v) const
Compute the 3D dot product, ignoring the W component.
Definition mxvk_math.h:503
float z
Z coordinate.
Definition mxvk_math.h:425
void Build(const vec4D &to)
Replace this vector with the direction from this point to to.
Definition mxvk_math.h:544
int main(void)
Definition main.cpp:7
#define MXVK_VALIDATION
Definition mxvk.hpp:27
Math, geometry, rasterization, and simple software 3D pipeline helpers for MXVK examples.
constexpr float MIN_CAMERA_DISTANCE
Definition main.cpp:293
constexpr float MAX_CAMERA_DISTANCE
Definition main.cpp:294
std::filesystem::path pyramid_path(const std::string &asset_path)
Definition main.cpp:43
SurfacePtr create_frame_surface(int width, int height)
Definition main.cpp:31
constexpr float CAMERA_ZOOM_STEP
Definition main.cpp:295
Utilities for loading and saving PNG images.
Definition mxvk.hpp:30
constexpr std::uint8_t color_r(MXCOLOR color)
Extract the red component from a packed ARGB color.
Definition mxvk_math.h:54
std::uint32_t MXCOLOR
Packed 32-bit color in ARGB byte order.
Definition mxvk_math.h:40
void BuildTables()
Rebuild the sine and cosine lookup tables.
Definition mxvk_math.h:113
MXCOLOR shade_color(MXCOLOR color, float intensity)
Scale the RGB channels of a color while preserving alpha.
Definition mxvk_math.h:79
constexpr std::uint8_t color_g(MXCOLOR color)
Extract the green component from a packed ARGB color.
Definition mxvk_math.h:59
constexpr MXCOLOR MXVK_RGB(int r, int g, int b)
Build an opaque ARGB color from red, green, and blue components.
Definition mxvk_math.h:49
constexpr std::uint8_t color_a(MXCOLOR color)
Extract the alpha component from a packed ARGB color.
Definition mxvk_math.h:69
constexpr float EPSILON
Default tolerance used for floating-point singularity and zero-length checks.
Definition mxvk_math.h:37
constexpr std::uint8_t color_b(MXCOLOR color)
Extract the blue component from a packed ARGB color.
Definition mxvk_math.h:64
Plain data structure returned by proc_args() with all common libmx2 CLI options.
Definition argz.hpp:730
FramebufferDimensions framebuffer
Software framebuffer size requested by --framebuffer.
Definition argz.hpp:758
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
Parsed software framebuffer dimensions.
Definition argz.hpp:721
std::array< mxvk::vec2D, 3 > texcoords
Definition main.cpp:252
Triangle primitive used by the simple software rendering pipeline.
Definition mxvk_math.h:1326
vec4D vlist[3]
Working vertex positions.
Definition mxvk_math.h:1328
int vert[3]
Indices into an object's vertex arrays.
Definition mxvk_math.h:1343