MXVK Vulkan Framework 0.35.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 { SDL_DestroySurface(surface); }
29 };
30
31 using SurfacePtr = std::unique_ptr<SDL_Surface, SurfaceDeleter>;
32
33 [[nodiscard]] SurfacePtr create_frame_surface(int width, int height) {
34 SurfacePtr surface(SDL_CreateSurface(width, height, SDL_PIXELFORMAT_RGBA32));
35 if (!surface) {
36 throw mxvk::Exception(std::format("3dmath_pyramid: failed to create frame surface: {}", SDL_GetError()));
37 }
38 return surface;
39 }
40
41 [[nodiscard]] std::filesystem::path pyramid_path(const std::string &asset_path) { return std::filesystem::path(asset_path) / "data" / "pyramid.plg"; }
42
43 struct FaceDraw {
44 std::array<mxvk::vec4D, 3> points{};
45 std::array<mxvk::vec2D, 3> texcoords{};
46 float intensity = 1.0f;
47 };
48
49 constexpr float MIN_CAMERA_DISTANCE = 2.5f;
50 constexpr float MAX_CAMERA_DISTANCE = 12.0f;
51 constexpr float CAMERA_ZOOM_STEP = 0.45f;
52} // namespace
53
54namespace example {
56 public:
57 Math3DPyramidWindow(const std::string &asset_path, const std::string &title, int width, int height, bool fullscreen, bool enable_vsync, const FramebufferDimensions &framebuffer) : mxvk::VK_Window(title, width, height, fullscreen, MXVK_VALIDATION, enable_vsync), frame_width(framebuffer.width), frame_height(framebuffer.height), fallback_width(width), fallback_height(height) {
58 setClearColor(0.012f, 0.015f, 0.022f, 1.0f);
60
61 const std::filesystem::path model_path = pyramid_path(asset_path);
62 if (!pyramid.LoadPLG(model_path.string(), mxvk::vec4D(2.0f, 2.0f, 2.0f), mxvk::vec4D(0.0f, 0.0f, 4.5f), mxvk::vec4D())) {
63 throw mxvk::Exception(std::format("3dmath_pyramid: failed to load PLG model '{}'", model_path.string()));
64 }
65 }
66
67 void event(SDL_Event &e) override {
68 if (e.type == SDL_EVENT_KEY_DOWN && e.key.key == SDLK_ESCAPE) {
69 exit();
70 }
71 if (e.type == SDL_EVENT_MOUSE_WHEEL) {
72 const float delta = e.wheel.y != 0.0f ? e.wheel.y : static_cast<float>(e.wheel.integer_y);
73 camera_distance = std::clamp(camera_distance - delta * CAMERA_ZOOM_STEP, MIN_CAMERA_DISTANCE, MAX_CAMERA_DISTANCE);
74 }
75 }
76
77 void proc() override {
78 const int output_width = swapchain_extent.width > 0U ? static_cast<int>(swapchain_extent.width) : fallback_width;
79 const int output_height = swapchain_extent.height > 0U ? static_cast<int>(swapchain_extent.height) : fallback_height;
80
81 ensure_framebuffer();
82 if (frame_sprite == nullptr || frame_surface == nullptr || frame_format == nullptr) {
83 return;
84 }
85
86 clear_frame(mxvk::MXVK_RGB(3, 4, 8));
87 std::ranges::fill(depth_buffer, std::numeric_limits<float>::infinity());
88
89 const float time = static_cast<float>(SDL_GetTicks()) * 0.001f;
90 mxvk::Mat4D rotation;
91 rotation.BuildXYZ(0.0f, time * 42.0f, 0.0f);
92
93 std::vector<mxvk::vec4D> camera_vertices(pyramid.local.size());
94 std::vector<mxvk::vec4D> projected(pyramid.local.size());
95 for (std::size_t i = 0; i < pyramid.local.size(); ++i) {
96 camera_vertices[i] = rotation.MulVec(pyramid.local[i]);
97 camera_vertices[i].z += camera_distance;
98 projected[i] = project_to_screen(camera_vertices[i], frame_width, frame_height);
99 }
100
101 mxvk::vec4D light_direction(-0.35f, -0.55f, -1.0f, 0.0f);
102 light_direction.Normalize();
103 std::vector<FaceDraw> faces;
104 faces.reserve(pyramid.vlist.size());
105
106 for (const mxvk::Triangle &triangle : pyramid.vlist) {
107 const auto first = static_cast<std::size_t>(triangle.vert[0]);
108 const auto second = static_cast<std::size_t>(triangle.vert[1]);
109 const auto third = static_cast<std::size_t>(triangle.vert[2]);
110
111 const mxvk::vec4D &a = camera_vertices[first];
112 const mxvk::vec4D &b = camera_vertices[second];
113 const mxvk::vec4D &c = camera_vertices[third];
114 mxvk::vec4D normal = mxvk::vec4D().Build(a, b).CrossProduct(mxvk::vec4D().Build(a, c));
115 normal.Normalize();
116
117 const mxvk::vec4D center = (a + b + c) * (1.0f / 3.0f);
118 const mxvk::vec4D view_direction(-center.x, -center.y, -center.z, 0.0f);
119 if (normal.DotProduct(view_direction) <= 0.0f) {
120 continue;
121 }
122
123 const float diffuse = std::max(0.0f, normal.DotProduct(light_direction));
124 const float intensity = std::clamp(0.28f + diffuse * 0.72f, 0.0f, 1.0f);
125 faces.push_back({
126 {projected[first], projected[second], projected[third]},
127 {
128 pyramid.texcoords[first],
129 pyramid.texcoords[second],
130 pyramid.texcoords[third],
131 },
132 intensity,
133 });
134 }
135
136 for (const FaceDraw &face : faces) {
137 draw_gradient_triangle(face);
138 }
139
140 frame_sprite->updateTexture(frame_surface.get());
141 frame_sprite->drawSpriteRect(0, 0, output_width, output_height);
142 }
143
144 private:
145 mxvk::mxObject pyramid;
146 SurfacePtr frame_surface;
147 std::vector<float> depth_buffer;
148 const SDL_PixelFormatDetails *frame_format = nullptr;
149 mxvk::VK_Sprite *frame_sprite = nullptr;
150 int frame_width = 1280;
151 int frame_height = 720;
152 int fallback_width = 1280;
153 int fallback_height = 720;
154 float camera_distance = 4.5f;
155
156 void ensure_framebuffer() {
157 if (frame_surface != nullptr) {
158 return;
159 }
160
161 frame_surface = create_frame_surface(frame_width, frame_height);
162 frame_format = SDL_GetPixelFormatDetails(frame_surface->format);
163 if (frame_format == nullptr) {
164 throw mxvk::Exception(std::format("3dmath_pyramid: failed to query frame format: {}", SDL_GetError()));
165 }
166
167 depth_buffer.resize(static_cast<std::size_t>(frame_width) * static_cast<std::size_t>(frame_height));
168 clear_frame(mxvk::MXVK_RGB(3, 4, 8));
169
170 frame_sprite = createSprite(frame_surface.get());
171 frame_sprite->setTextureFilter(VK_FILTER_NEAREST);
172 }
173
174 [[nodiscard]] std::uint32_t map_color(mxvk::MXCOLOR color) const { return SDL_MapRGBA(frame_format, nullptr, mxvk::color_r(color), mxvk::color_g(color), mxvk::color_b(color), mxvk::color_a(color)); }
175
176 void clear_frame(mxvk::MXCOLOR color) { SDL_FillSurfaceRect(frame_surface.get(), nullptr, map_color(color)); }
177
178 void put_pixel(int x, int y, mxvk::MXCOLOR color) {
179 if (x < 0 || y < 0 || x >= frame_width || y >= frame_height) {
180 return;
181 }
182
183 auto *row = static_cast<std::uint8_t *>(frame_surface->pixels) + (static_cast<std::size_t>(y) * static_cast<std::size_t>(frame_surface->pitch));
184 auto *pixel = reinterpret_cast<std::uint32_t *>(row) + x;
185 *pixel = map_color(color);
186 }
187
188 void draw_gradient_triangle(const FaceDraw &face) {
189 const mxvk::vec4D &a = face.points[0];
190 const mxvk::vec4D &b = face.points[1];
191 const mxvk::vec4D &c = face.points[2];
192 const auto edge = [](const mxvk::vec4D &first, const mxvk::vec4D &second, float x, float y) { return (x - first.x) * (second.y - first.y) - (y - first.y) * (second.x - first.x); };
193
194 const float area = edge(b, c, a.x, a.y);
195 if (std::abs(area) <= mxvk::EPSILON) {
196 return;
197 }
198
199 const int min_x = std::clamp(static_cast<int>(std::floor(std::min({a.x, b.x, c.x}))), 0, frame_width - 1);
200 const int max_x = std::clamp(static_cast<int>(std::ceil(std::max({a.x, b.x, c.x}))), 0, frame_width - 1);
201 const int min_y = std::clamp(static_cast<int>(std::floor(std::min({a.y, b.y, c.y}))), 0, frame_height - 1);
202 const int max_y = std::clamp(static_cast<int>(std::ceil(std::max({a.y, b.y, c.y}))), 0, frame_height - 1);
203
204 for (int y = min_y; y <= max_y; ++y) {
205 for (int x = min_x; x <= max_x; ++x) {
206 const float sample_x = static_cast<float>(x) + 0.5f;
207 const float sample_y = static_cast<float>(y) + 0.5f;
208 const float weight_a = edge(b, c, sample_x, sample_y) / area;
209 const float weight_b = edge(c, a, sample_x, sample_y) / area;
210 const float weight_c = edge(a, b, sample_x, sample_y) / area;
211 if (weight_a < -mxvk::EPSILON || weight_b < -mxvk::EPSILON || weight_c < -mxvk::EPSILON) {
212 continue;
213 }
214
215 const float reciprocal_depth = weight_a / a.z + weight_b / b.z + weight_c / c.z;
216 if (reciprocal_depth <= mxvk::EPSILON) {
217 continue;
218 }
219
220 const float depth = 1.0f / reciprocal_depth;
221 const std::size_t pixel_index = static_cast<std::size_t>(y) * static_cast<std::size_t>(frame_width) + static_cast<std::size_t>(x);
222 if (depth >= depth_buffer[pixel_index]) {
223 continue;
224 }
225 depth_buffer[pixel_index] = depth;
226
227 const float perspective_a = (weight_a / a.z) * depth;
228 const float perspective_b = (weight_b / b.z) * depth;
229 const float perspective_c = (weight_c / c.z) * depth;
230 const float u = face.texcoords[0].x * perspective_a + face.texcoords[1].x * perspective_b + face.texcoords[2].x * perspective_c;
231 const float v = face.texcoords[0].y * perspective_a + face.texcoords[1].y * perspective_b + face.texcoords[2].y * perspective_c;
232 put_pixel(x, y, mxvk::shade_color(gradient_color(u, v), face.intensity));
233 }
234 }
235 }
236
237 [[nodiscard]] static mxvk::MXCOLOR gradient_color(float u, float v) {
238 u = std::clamp(u, 0.0f, 1.0f);
239 v = std::clamp(v, 0.0f, 1.0f);
240
241 constexpr mxvk::MXCOLOR BOTTOM_LEFT = mxvk::MXVK_RGB(68, 214, 255);
242 constexpr mxvk::MXCOLOR BOTTOM_RIGHT = mxvk::MXVK_RGB(92, 255, 142);
243 constexpr mxvk::MXCOLOR TOP_LEFT = mxvk::MXVK_RGB(155, 105, 255);
244 constexpr mxvk::MXCOLOR TOP_RIGHT = mxvk::MXVK_RGB(255, 82, 197);
245 const auto bilinear_channel = [&](auto component) {
246 const float bottom = static_cast<float>(component(BOTTOM_LEFT)) + (static_cast<float>(component(BOTTOM_RIGHT)) - static_cast<float>(component(BOTTOM_LEFT))) * u;
247 const float top = static_cast<float>(component(TOP_LEFT)) + (static_cast<float>(component(TOP_RIGHT)) - static_cast<float>(component(TOP_LEFT))) * u;
248 return std::clamp(static_cast<int>(std::lround(bottom + (top - bottom) * v)), 0, 255);
249 };
250
251 return mxvk::MXVK_RGB(bilinear_channel(mxvk::color_r), bilinear_channel(mxvk::color_g), bilinear_channel(mxvk::color_b));
252 }
253
254 [[nodiscard]] static mxvk::vec4D project_to_screen(const mxvk::vec4D &point, int width, int height) {
255 const float scale = static_cast<float>(std::min(width, height)) * 0.52f;
256 const float center_x = static_cast<float>(width) * 0.5f;
257 const float center_y = static_cast<float>(height) * 0.5f;
258 const float z = std::max(point.z, 0.001f);
259 return {center_x + (point.x / z) * scale, center_y - (point.y / z) * scale, point.z, 1.0f};
260 }
261 };
262} // namespace example
263
264int main(int argc, char **argv) {
265 try {
266 Arguments args = proc_args(argc, argv);
267 example::Math3DPyramidWindow window(args.path, "MXVK 3D Math Pyramid", args.width, args.height, args.fullscreen, args.enable_vsync, args.framebuffer);
268 window.loop();
269 } catch (mxvk::Exception &e) {
270 std::cerr << std::format("mxvk: Exception: {}\n", e.text());
271 return EXIT_FAILURE;
272 } catch (ArgException<std::string> &e) {
273 std::cerr << std::format("mxvk: Argument Exception: {}\n", e.text());
274 return EXIT_FAILURE;
275 }
276 return EXIT_SUCCESS;
277}
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:854
Exception thrown by Argz::proc() on unrecognised or malformed options.
Definition argz.hpp:169
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:57
void event(SDL_Event &e) override
Handle one SDL event.
Definition main.cpp:67
void proc() override
Execute one processing/update step.
Definition main.cpp:77
std::string text() const
Four-by-four homogeneous transform matrix.
Definition mxvk_math.h:706
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:859
vec4D MulVec(const vec4D &in) const
Transform a homogeneous 4D vector by this matrix.
Definition mxvk_math.h:771
Main Vulkan window wrapper for MXVK.
Definition mxvk.hpp:39
void loop()
Run the main event/render loop.
Definition mxvk.cpp:651
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:4455
VkExtent2D swapchain_extent
Definition mxvk.hpp:616
void setClearColor(float r, float g, float b, float a=1.0f)
Set the per-frame color attachment clear color.
Definition mxvk.cpp:637
void exit()
Request loop termination.
Definition mxvk.cpp:1378
VK_Window()=default
Construct an empty window object.
Simple mesh object loaded from PLG-style indexed triangle data.
Definition mxvk_math.h:1300
Four-dimensional float vector used for homogeneous 3D coordinates.
Definition mxvk_math.h:363
float y
Y coordinate.
Definition mxvk_math.h:369
float x
X coordinate.
Definition mxvk_math.h:366
void Normalize()
Normalize the 3D components in place and reset W to 1.
Definition mxvk_math.h:447
constexpr float DotProduct(const vec4D &v) const
Compute the 3D dot product, ignoring the W component.
Definition mxvk_math.h:438
float z
Z coordinate.
Definition mxvk_math.h:372
void Build(const vec4D &to)
Replace this vector with the direction from this point to to.
Definition mxvk_math.h:473
#define MXVK_VALIDATION
Definition mxvk.hpp:28
Math, geometry, rasterization, and simple software 3D pipeline helpers for MXVK examples.
constexpr float MIN_CAMERA_DISTANCE
Definition main.cpp:249
constexpr float MAX_CAMERA_DISTANCE
Definition main.cpp:250
std::filesystem::path pyramid_path(const std::string &asset_path)
Definition main.cpp:41
constexpr float CAMERA_ZOOM_STEP
Definition main.cpp:251
int main()
Definition main.py:165
Utilities for loading and saving PNG images.
Definition mxvk.hpp:31
constexpr std::uint8_t color_r(MXCOLOR color)
Extract the red component from a packed ARGB color.
Definition mxvk_math.h:52
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:98
MXCOLOR shade_color(MXCOLOR color, float intensity)
Scale the RGB channels of a color while preserving alpha.
Definition mxvk_math.h:69
constexpr std::uint8_t color_g(MXCOLOR color)
Extract the green component from a packed ARGB color.
Definition mxvk_math.h:55
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:61
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:58
Plain data structure returned by proc_args() with all common libmx2 CLI options.
Definition argz.hpp:718
FramebufferDimensions framebuffer
Software framebuffer size requested by --framebuffer.
Definition argz.hpp:746
bool fullscreen
Whether fullscreen mode was requested.
Definition argz.hpp:724
bool enable_vsync
Enable FIFO present mode / v-sync (--enable-vsync).
Definition argz.hpp:738
int height
Viewport height in pixels (default: 720).
Definition argz.hpp:721
std::string path
Asset root; proc_args() defaults it to the executable directory.
Definition argz.hpp:723
int width
Viewport width in pixels (default: 1280).
Definition argz.hpp:720
Parsed software framebuffer dimensions.
Definition argz.hpp:709
std::array< mxvk::vec2D, 3 > texcoords
Definition main.cpp:213
Triangle primitive used by the simple software rendering pipeline.
Definition mxvk_math.h:1187
vec4D vlist[3]
Working vertex positions.
Definition mxvk_math.h:1189
int vert[3]
Indices into an object's vertex arrays.
Definition mxvk_math.h:1204