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"
5#if defined(MXVK_USE_EIGEN_MATH)
7#else
8#include "mxvk/mxvk_math.h"
9#endif
10#include "mxvk/mxvk_png.hpp"
11
12#include <SDL3/SDL.h>
13
14#include <algorithm>
15#include <array>
16#include <cmath>
17#include <cstdint>
18#include <cstdlib>
19#include <filesystem>
20#include <format>
21#include <iostream>
22#include <limits>
23#include <memory>
24#include <string>
25#include <string_view>
26#include <unordered_map>
27#include <vector>
28
29namespace {
30#if defined(MXVK_OBJ_LOADER)
31 constexpr std::string_view APP_NAME = "3dmath_obj_loader";
32 constexpr std::string_view MODEL_FORMAT = "OBJ";
33 constexpr std::string_view DEFAULT_MODEL = "sphere.obj";
34 constexpr std::string_view WINDOW_TITLE = "MXVK 3D Math OBJ Loader";
35 constexpr float MODEL_FIT_RADIUS = 1.6f;
36 constexpr mxvk::MXCOLOR BACKGROUND_COLOR = mxvk::MXVK_RGB(15, 22, 35);
37#else
38 constexpr std::string_view APP_NAME = "3dmath_plg_loader";
39 constexpr std::string_view MODEL_FORMAT = "PLG";
40 constexpr std::string_view DEFAULT_MODEL = "sphere.plg";
41 constexpr std::string_view WINDOW_TITLE = "MXVK 3D Math PLG Loader";
42 constexpr float MODEL_SCALE = 2.0f;
44#endif
45
46 class SurfaceDeleter {
47 public:
48 void operator()(SDL_Surface *surface) const {
49 SDL_DestroySurface(surface);
50 }
51 };
52
53 using SurfacePtr = std::unique_ptr<SDL_Surface, SurfaceDeleter>;
54
55 [[nodiscard]] mxvk::MXCOLOR pack_color(std::uint32_t red, std::uint32_t green, std::uint32_t blue, std::uint32_t alpha) {
56 return ((alpha & 0xFFU) << 24U) |
57 ((red & 0xFFU) << 16U) |
58 ((green & 0xFFU) << 8U) |
59 (blue & 0xFFU);
60 }
61
62 [[nodiscard]] mxvk::MXCOLOR interpolate_color(mxvk::MXCOLOR first, mxvk::MXCOLOR second, float fraction) {
63 const std::uint32_t second_weight = static_cast<std::uint32_t>(
64 std::clamp(static_cast<int>(fraction * 256.0f + 0.5f), 0, 256));
65 const std::uint32_t first_weight = 256U - second_weight;
66 const auto interpolate_channel = [first_weight, second_weight](std::uint8_t first_channel, std::uint8_t second_channel) {
67 return (static_cast<std::uint32_t>(first_channel) * first_weight +
68 static_cast<std::uint32_t>(second_channel) * second_weight +
69 128U) >>
70 8U;
71 };
72 return pack_color(
73 interpolate_channel(mxvk::color_r(first), mxvk::color_r(second)),
74 interpolate_channel(mxvk::color_g(first), mxvk::color_g(second)),
75 interpolate_channel(mxvk::color_b(first), mxvk::color_b(second)),
76 interpolate_channel(mxvk::color_a(first), mxvk::color_a(second)));
77 }
78
79 struct MipLevel {
80 int width = 0;
81 int height = 0;
82 std::vector<mxvk::MXCOLOR> pixels;
83
84 [[nodiscard]] mxvk::MXCOLOR sample_bilinear(float u, float v, bool repeat_horizontal) const {
85 u = repeat_horizontal ? u - std::floor(u) : std::clamp(u, 0.0f, 1.0f);
86 v = std::clamp(v, 0.0f, 1.0f);
87 const float texture_x = u * static_cast<float>(repeat_horizontal ? width : width - 1);
88 const float texture_y = v * static_cast<float>(height - 1);
89 const int first_x = static_cast<int>(std::floor(texture_x));
90 const int first_y = static_cast<int>(std::floor(texture_y));
91 const int second_x = repeat_horizontal ? (first_x + 1) % width : std::min(first_x + 1, width - 1);
92 const int second_y = std::min(first_y + 1, height - 1);
93 const float x_fraction = texture_x - static_cast<float>(first_x);
94 const float y_fraction = texture_y - static_cast<float>(first_y);
95 const auto texel = [this](int x, int y) {
96 return pixels[static_cast<std::size_t>(y) * static_cast<std::size_t>(width) + static_cast<std::size_t>(x)];
97 };
98 const mxvk::MXCOLOR top = interpolate_color(texel(first_x, first_y), texel(second_x, first_y), x_fraction);
99 const mxvk::MXCOLOR bottom = interpolate_color(texel(first_x, second_y), texel(second_x, second_y), x_fraction);
100 return interpolate_color(top, bottom, y_fraction);
101 }
102 };
103
104 struct Texture {
105 std::vector<MipLevel> levels;
106
107 [[nodiscard]] bool empty() const {
108 return levels.empty() || levels.front().width <= 0 || levels.front().height <= 0 || levels.front().pixels.empty();
109 }
110
111 [[nodiscard]] int width() const {
112 return empty() ? 0 : levels.front().width;
113 }
114
115 [[nodiscard]] int height() const {
116 return empty() ? 0 : levels.front().height;
117 }
118
119 [[nodiscard]] mxvk::MXCOLOR sample(float u, float v, float level, bool repeat_horizontal) const {
120 level = std::clamp(level, 0.0f, static_cast<float>(levels.size() - 1));
121 const std::size_t first_level = static_cast<std::size_t>(std::floor(level));
122 const std::size_t second_level = std::min(first_level + 1, levels.size() - 1);
123 const float fraction = level - static_cast<float>(first_level);
124 const mxvk::MXCOLOR first_color = levels[first_level].sample_bilinear(u, v, repeat_horizontal);
125 if (first_level == second_level) {
126 return first_color;
127 }
128 return interpolate_color(first_color, levels[second_level].sample_bilinear(u, v, repeat_horizontal), fraction);
129 }
130 };
131
132 [[nodiscard]] SurfacePtr create_frame_surface(int width, int height) {
133 SurfacePtr surface(SDL_CreateSurface(width, height, SDL_PIXELFORMAT_RGBA32));
134 if (!surface) {
135 throw mxvk::Exception(std::format("{}: failed to create frame surface: {}", APP_NAME, SDL_GetError()));
136 }
137 return surface;
138 }
139
140 [[nodiscard]] std::filesystem::path model_path(const std::string &filename, const std::string &asset_path) {
141 if (!filename.empty()) {
142 return filename;
143 }
144 return std::filesystem::path(asset_path) / "data" / DEFAULT_MODEL;
145 }
146
147 [[nodiscard]] std::filesystem::path texture_path(const std::string &filename, const std::string &asset_path) {
148 const std::filesystem::path requested(filename);
149 if (requested.is_absolute() || std::filesystem::exists(requested)) {
150 return requested;
151 }
152
153 const std::filesystem::path from_asset_path = std::filesystem::path(asset_path) / requested;
154 if (std::filesystem::exists(from_asset_path)) {
155 return from_asset_path;
156 }
157 return requested;
158 }
159
160 [[nodiscard]] Texture load_texture(const std::string &filename, const std::string &asset_path, bool generate_mipmaps) {
161 if (filename.empty()) {
162 return {};
163 }
164
165 const std::filesystem::path path = texture_path(filename, asset_path);
166 SurfacePtr loaded(mxvk::LoadPNG(path.string().c_str()));
167 if (!loaded) {
168 throw mxvk::Exception(std::format("{}: failed to load texture '{}'", APP_NAME, path.string()));
169 }
170
171 SurfacePtr rgba(SDL_ConvertSurface(loaded.get(), SDL_PIXELFORMAT_RGBA32));
172 if (!rgba) {
173 throw mxvk::Exception(std::format("{}: failed to convert texture '{}': {}", APP_NAME, path.string(), SDL_GetError()));
174 }
175
176 const SDL_PixelFormatDetails *format = SDL_GetPixelFormatDetails(rgba->format);
177 if (format == nullptr) {
178 throw mxvk::Exception(std::format("{}: failed to query texture format '{}': {}", APP_NAME, path.string(), SDL_GetError()));
179 }
180
181 Texture texture;
182 MipLevel base_level;
183 base_level.width = rgba->w;
184 base_level.height = rgba->h;
185 base_level.pixels.resize(static_cast<std::size_t>(base_level.width) * static_cast<std::size_t>(base_level.height));
186 for (int y = 0; y < base_level.height; ++y) {
187 const auto *row = static_cast<const std::uint8_t *>(rgba->pixels) + static_cast<std::size_t>(y) * static_cast<std::size_t>(rgba->pitch);
188 const auto *source = reinterpret_cast<const std::uint32_t *>(row);
189 for (int x = 0; x < base_level.width; ++x) {
190 std::uint8_t red = 0;
191 std::uint8_t green = 0;
192 std::uint8_t blue = 0;
193 std::uint8_t alpha = 0;
194 SDL_GetRGBA(source[x], format, nullptr, &red, &green, &blue, &alpha);
195 base_level.pixels[static_cast<std::size_t>(y) * static_cast<std::size_t>(base_level.width) + static_cast<std::size_t>(x)] =
196 pack_color(red, green, blue, alpha);
197 }
198 }
199 texture.levels.push_back(std::move(base_level));
200
201 while (generate_mipmaps && (texture.levels.back().width > 1 || texture.levels.back().height > 1)) {
202 const MipLevel &source = texture.levels.back();
203 MipLevel destination;
204 destination.width = std::max(1, (source.width + 1) / 2);
205 destination.height = std::max(1, (source.height + 1) / 2);
206 destination.pixels.resize(static_cast<std::size_t>(destination.width) * static_cast<std::size_t>(destination.height));
207 for (int y = 0; y < destination.height; ++y) {
208 for (int x = 0; x < destination.width; ++x) {
209 const int first_x = std::min(x * 2, source.width - 1);
210 const int second_x = std::min(first_x + 1, source.width - 1);
211 const int first_y = std::min(y * 2, source.height - 1);
212 const int second_y = std::min(first_y + 1, source.height - 1);
213 const auto texel = [&source](int source_x, int source_y) {
214 return source.pixels[static_cast<std::size_t>(source_y) * static_cast<std::size_t>(source.width) + static_cast<std::size_t>(source_x)];
215 };
216 const std::array<mxvk::MXCOLOR, 4> colors = {
217 texel(first_x, first_y),
218 texel(second_x, first_y),
219 texel(first_x, second_y),
220 texel(second_x, second_y),
221 };
222 const auto average_channel = [&colors](auto component) {
223 std::uint32_t sum = 0;
224 for (const mxvk::MXCOLOR color : colors) {
225 sum += component(color);
226 }
227 return (sum + 2U) / 4U;
228 };
229 destination.pixels[static_cast<std::size_t>(y) * static_cast<std::size_t>(destination.width) + static_cast<std::size_t>(x)] =
231 average_channel(mxvk::color_r),
232 average_channel(mxvk::color_g),
233 average_channel(mxvk::color_b),
234 average_channel(mxvk::color_a));
235 }
236 }
237 texture.levels.push_back(std::move(destination));
238 }
239
240 std::cout << std::format(
241 "{}: loaded texture '{}' ({}x{}, {} mip levels)\n",
242 APP_NAME,
243 path.string(),
244 texture.width(),
245 texture.height(),
246 texture.levels.size());
247 return texture;
248 }
249
250 struct FaceDraw {
251 std::array<mxvk::vec4D, 3> points{};
252 std::array<mxvk::vec2D, 3> texcoords{};
255 bool repeat_horizontal = false;
256 float intensity = 1.0f;
257 };
258
259 [[nodiscard]] bool crosses_horizontal_texture_seam(const std::array<mxvk::vec2D, 3> &texcoords) {
260 const auto [minimum, maximum] = std::minmax_element(
261 texcoords.begin(),
262 texcoords.end(),
263 [](const mxvk::vec2D &first, const mxvk::vec2D &second) {
264 return first.x < second.x;
265 });
266 const float span = maximum->x - minimum->x;
267 return span > 0.5f && span < 1.0f - mxvk::EPSILON;
268 }
269
270 [[nodiscard]] std::array<mxvk::vec2D, 3> unwrap_horizontal_texcoords(std::array<mxvk::vec2D, 3> texcoords) {
271 if (!crosses_horizontal_texture_seam(texcoords)) {
272 return texcoords;
273 }
274
275 for (mxvk::vec2D &texcoord : texcoords) {
276 if (texcoord.x < 0.5f) {
277 texcoord.x += 1.0f;
278 }
279 }
280
281 for (std::size_t index = 0; index < texcoords.size(); ++index) {
282 if (texcoords[index].y > mxvk::EPSILON && texcoords[index].y < 1.0f - mxvk::EPSILON) {
283 continue;
284 }
285
286 const std::size_t next = (index + 1) % texcoords.size();
287 const std::size_t previous = (index + texcoords.size() - 1) % texcoords.size();
288 texcoords[index].x = (texcoords[next].x + texcoords[previous].x) * 0.5f;
289 }
290 return texcoords;
291 }
292
293 constexpr float MIN_CAMERA_DISTANCE = 2.5f;
294 constexpr float MAX_CAMERA_DISTANCE = 12.0f;
295 constexpr float CAMERA_ZOOM_STEP = 0.45f;
296 constexpr float MOUSE_ROTATION_SENSITIVITY = 0.35f;
297 constexpr float MAX_PITCH_DEGREES = 89.0f;
298#if defined(MXVK_USE_EIGEN_MATH)
299 constexpr std::string_view BACKEND_NAME = "Eigen";
300#else
301 constexpr std::string_view BACKEND_NAME = "native";
302#endif
303} // namespace
304
305namespace example {
307 public:
308 Math3DModelLoaderWindow(const std::string &filename, const std::string &texture_filename, const std::string &asset_path, const std::string &title, int width, int height, bool fullscreen, bool enable_vsync, bool repeat_texture, bool disable_warp_fix, bool disable_mipmap, float mip_bias, const FramebufferDimensions &framebuffer, bool benchmark, bool wireframe)
309 : mxvk::VK_Window(title, width, height, fullscreen, MXVK_VALIDATION, enable_vsync),
310 override_texture(wireframe ? Texture{} : load_texture(texture_filename, asset_path, !disable_mipmap)),
311 frame_width(framebuffer.width),
312 frame_height(framebuffer.height),
313 fallback_width(width),
314 fallback_height(height),
315 warp_fix_enabled(!disable_warp_fix),
316 mipmapping_enabled(!disable_mipmap),
317 mip_level_bias(mip_bias),
318 texture_repeat_enabled(repeat_texture),
319 benchmark_enabled(benchmark),
320 wireframe_enabled(wireframe) {
321 setClearColor(0.012f, 0.015f, 0.022f, 1.0f);
323
324 const std::filesystem::path path = model_path(filename, asset_path);
325#if defined(MXVK_OBJ_LOADER)
326 const mxvk::vec4D scale(1.0f, 1.0f, 1.0f);
327#else
328 const mxvk::vec4D scale(MODEL_SCALE, MODEL_SCALE, MODEL_SCALE);
329#endif
330#if defined(MXVK_OBJ_LOADER)
331 const bool model_loaded = model.LoadOBJ(path.string(), scale, mxvk::vec4D(0.0f, 0.0f, 4.5f), mxvk::vec4D());
332#else
333 const bool model_loaded = model.LoadPLG(path.string(), scale, mxvk::vec4D(0.0f, 0.0f, 4.5f), mxvk::vec4D());
334#endif
335 if (!model_loaded) {
336 throw mxvk::Exception(std::format("{}: failed to load {} model '{}'", APP_NAME, MODEL_FORMAT, path.string()));
337 }
338#if defined(MXVK_OBJ_LOADER)
339 filter_auxiliary_objects();
340 fit_model_to_view();
341 if (!wireframe_enabled) {
342 load_material_textures(asset_path, !disable_mipmap);
343 }
344#endif
345#if defined(MXVK_USE_EIGEN_MATH)
346 local_vertex_batch.resize(4, static_cast<Eigen::Index>(model.local.size()));
347 camera_vertex_batch.resize(4, static_cast<Eigen::Index>(model.local.size()));
348 projected_vertex_batch.resize(4, static_cast<Eigen::Index>(model.local.size()));
349 inverse_vertex_depth.resize(static_cast<Eigen::Index>(model.local.size()));
350#if !defined(MXVK_OBJ_LOADER)
351 local_face_center_batch.resize(4, static_cast<Eigen::Index>(model.vlist.size()));
352 camera_face_center_batch.resize(4, static_cast<Eigen::Index>(model.vlist.size()));
353#endif
354 local_face_normal_batch.resize(4, static_cast<Eigen::Index>(model.vlist.size()));
355 camera_face_normal_batch.resize(4, static_cast<Eigen::Index>(model.vlist.size()));
356 triangle_intensity.resize(static_cast<Eigen::Index>(model.vlist.size()));
357#if !defined(MXVK_OBJ_LOADER)
358 triangle_visible.resize(static_cast<Eigen::Index>(model.vlist.size()));
359#endif
360 for (std::size_t index = 0; index < model.local.size(); ++index) {
361 local_vertex_batch.col(static_cast<Eigen::Index>(index)) =
362 Eigen::Vector4f(model.local[index].x, model.local[index].y, model.local[index].z, model.local[index].w);
363 }
364#else
365 camera_vertices.resize(model.local.size());
366 projected_vertices.resize(model.local.size());
367#endif
368 initialize_face_geometry();
369 visible_faces.reserve(model.vlist.size());
370 std::cout << std::format("{}: loaded '{}' ({} vertices, {} triangles, {} materials)\n", APP_NAME, path.string(), model.num_vertices, model.num_polys, model.materials.size());
371 }
372
373 void event(SDL_Event &e) override {
374 if (e.type == SDL_EVENT_KEY_DOWN && e.key.key == SDLK_ESCAPE) {
375 exit();
376 }
377 if (e.type == SDL_EVENT_KEY_DOWN && e.key.key == SDLK_SPACE && !e.key.repeat) {
378 automatic_rotation = !automatic_rotation;
379 return;
380 }
381 if (e.type == SDL_EVENT_MOUSE_BUTTON_DOWN && e.button.button == SDL_BUTTON_LEFT) {
382 mouse_dragging = true;
383 last_mouse_x = e.button.x;
384 last_mouse_y = e.button.y;
385 return;
386 }
387 if (e.type == SDL_EVENT_MOUSE_BUTTON_UP && e.button.button == SDL_BUTTON_LEFT) {
388 mouse_dragging = false;
389 return;
390 }
391 if (e.type == SDL_EVENT_WINDOW_FOCUS_LOST) {
392 mouse_dragging = false;
393 return;
394 }
395 if (e.type == SDL_EVENT_MOUSE_MOTION && mouse_dragging) {
396 const float delta_x = e.motion.x - last_mouse_x;
397 const float delta_y = e.motion.y - last_mouse_y;
398 yaw_degrees = std::fmod(yaw_degrees + delta_x * MOUSE_ROTATION_SENSITIVITY, 360.0f);
399 pitch_degrees = std::clamp(
400 pitch_degrees + delta_y * MOUSE_ROTATION_SENSITIVITY,
401 -MAX_PITCH_DEGREES,
402 MAX_PITCH_DEGREES);
403 last_mouse_x = e.motion.x;
404 last_mouse_y = e.motion.y;
405 return;
406 }
407 if (e.type == SDL_EVENT_MOUSE_WHEEL) {
408 const float delta = e.wheel.y != 0.0f ? e.wheel.y : static_cast<float>(e.wheel.integer_y);
409 camera_distance = std::clamp(camera_distance - delta * CAMERA_ZOOM_STEP, MIN_CAMERA_DISTANCE, MAX_CAMERA_DISTANCE);
410 }
411 }
412
413 void proc() override {
414 const int output_width = swapchain_extent.width > 0U ? static_cast<int>(swapchain_extent.width) : fallback_width;
415 const int output_height = swapchain_extent.height > 0U ? static_cast<int>(swapchain_extent.height) : fallback_height;
416
417 ensure_framebuffer();
418 if (frame_sprite == nullptr || frame_surface == nullptr || frame_format == nullptr) {
419 return;
420 }
421
422 clear_frame(BACKGROUND_COLOR);
423 std::ranges::fill(depth_buffer, std::numeric_limits<float>::infinity());
424
425 const std::uint64_t current_ticks = SDL_GetTicks();
426 const float elapsed_seconds = previous_frame_ticks == 0
427 ? 0.0f
428 : static_cast<float>(current_ticks - previous_frame_ticks) * 0.001f;
429 previous_frame_ticks = current_ticks;
430 if (automatic_rotation && !mouse_dragging) {
431 yaw_degrees = std::fmod(yaw_degrees + elapsed_seconds * 42.0f, 360.0f);
432 }
433
434 if (benchmark_enabled && benchmark_frame_count == 0) {
435 benchmark_stopwatch =
436 std::make_unique<StopWatch<HighResolutionClockPolicy>>(benchmark_name);
437 }
438
439 mxvk::Mat4D rotation;
440 rotation.BuildXYZ(pitch_degrees, yaw_degrees, 0.0f);
441 transform_and_project_vertices(rotation);
442
443 mxvk::vec4D light_direction(-0.35f, -0.55f, -1.0f, 0.0f);
444 light_direction.Normalize();
445 visible_faces.clear();
446 build_visible_faces(rotation, light_direction);
447
448 for (const FaceDraw &face : visible_faces) {
449 if (wireframe_enabled) {
450 wireframe_pipeline.DrawWireframeTriangle(
451 face.points[0],
452 face.points[1],
453 face.points[2],
454 mxvk::shade_color(face.material_color, face.intensity),
455 depth_buffer);
456 } else {
457 draw_gradient_triangle(face);
458 }
459 }
460
461 if (benchmark_stopwatch != nullptr) {
462 ++benchmark_frame_count;
463 if (benchmark_frame_count == BENCHMARK_FRAME_COUNT) {
464 benchmark_stopwatch->Stop();
465 benchmark_stopwatch.reset();
466 exit();
467 }
468 }
469
470 frame_sprite->updateTexture(frame_surface->pixels, frame_width, frame_height, frame_surface->pitch);
471 frame_sprite->drawSpriteRect(0, 0, output_width, output_height);
472 }
473
474 private:
475 mxvk::mxObject model;
476 mxvk::PipeLine wireframe_pipeline;
477 Texture override_texture;
478 std::vector<Texture> material_textures;
479 SurfacePtr frame_surface;
480#if defined(MXVK_USE_EIGEN_MATH)
481 using VertexBatch = Eigen::Matrix<float, 4, Eigen::Dynamic, Eigen::RowMajor>;
482 VertexBatch local_vertex_batch;
483 VertexBatch camera_vertex_batch;
484 VertexBatch projected_vertex_batch;
485 Eigen::RowVectorXf inverse_vertex_depth;
486 VertexBatch local_face_center_batch;
487 VertexBatch camera_face_center_batch;
488 VertexBatch local_face_normal_batch;
489 VertexBatch camera_face_normal_batch;
490 Eigen::RowVectorXf triangle_intensity;
491 Eigen::Array<bool, 1, Eigen::Dynamic> triangle_visible;
492#else
493 std::vector<mxvk::vec4D> camera_vertices;
494 std::vector<mxvk::vec4D> projected_vertices;
495 std::vector<mxvk::vec4D> local_face_centers;
496 std::vector<mxvk::vec4D> camera_face_centers;
497 std::vector<mxvk::vec4D> local_face_normals;
498 std::vector<mxvk::vec4D> camera_face_normals;
499#endif
500 std::vector<FaceDraw> visible_faces;
501 std::vector<float> depth_buffer;
502 const SDL_PixelFormatDetails *frame_format = nullptr;
503 mxvk::VK_Sprite *frame_sprite = nullptr;
504 int frame_width = 1280;
505 int frame_height = 720;
506 int fallback_width = 1280;
507 int fallback_height = 720;
508 float camera_distance = 4.25f;
509 float pitch_degrees = 0.0f;
510 float yaw_degrees = 0.0f;
511 float last_mouse_x = 0.0f;
512 float last_mouse_y = 0.0f;
513 bool mouse_dragging = false;
514 bool automatic_rotation = true;
515 std::uint64_t previous_frame_ticks = 0;
516 bool warp_fix_enabled = true;
517 bool mipmapping_enabled = true;
518 float mip_level_bias = 0.0f;
519 bool texture_repeat_enabled = false;
520 bool benchmark_enabled = false;
521 bool wireframe_enabled = false;
522 std::size_t benchmark_frame_count = 0;
523 std::string benchmark_name =
524 std::format("{} geometry draw ({} backend, {} frames)", MODEL_FORMAT, BACKEND_NAME, BENCHMARK_FRAME_COUNT);
525 std::unique_ptr<StopWatch<HighResolutionClockPolicy>> benchmark_stopwatch;
526
527 static constexpr std::size_t BENCHMARK_FRAME_COUNT = 60 * 10;
528
529#if defined(MXVK_OBJ_LOADER)
530 void fit_model_to_view() {
531 if (model.vlist.empty()) {
532 throw mxvk::Exception(std::format("{}: cannot fit an OBJ model with no triangles", APP_NAME));
533 }
534
535 mxvk::vec4D minimum(
536 std::numeric_limits<float>::max(),
537 std::numeric_limits<float>::max(),
538 std::numeric_limits<float>::max());
539 mxvk::vec4D maximum(
540 std::numeric_limits<float>::lowest(),
541 std::numeric_limits<float>::lowest(),
542 std::numeric_limits<float>::lowest());
543 for (const mxvk::Triangle &triangle : model.vlist) {
544 for (const int vertex_index : triangle.vert) {
545 const mxvk::vec4D &vertex = model.local[static_cast<std::size_t>(vertex_index)];
546 minimum.x = std::min(minimum.x, vertex.x);
547 minimum.y = std::min(minimum.y, vertex.y);
548 minimum.z = std::min(minimum.z, vertex.z);
549 maximum.x = std::max(maximum.x, vertex.x);
550 maximum.y = std::max(maximum.y, vertex.y);
551 maximum.z = std::max(maximum.z, vertex.z);
552 }
553 }
554
555 const mxvk::vec4D center(
556 (minimum.x + maximum.x) * 0.5f,
557 (minimum.y + maximum.y) * 0.5f,
558 (minimum.z + maximum.z) * 0.5f);
559 float source_radius = 0.0f;
560 for (const mxvk::Triangle &triangle : model.vlist) {
561 for (const int vertex_index : triangle.vert) {
562 const mxvk::vec4D &vertex = model.local[static_cast<std::size_t>(vertex_index)];
563 source_radius = std::max(
564 source_radius,
565 std::sqrt(
566 (vertex.x - center.x) * (vertex.x - center.x) +
567 (vertex.y - center.y) * (vertex.y - center.y) +
568 (vertex.z - center.z) * (vertex.z - center.z)));
569 }
570 }
571 if (!std::isfinite(source_radius) || source_radius <= mxvk::EPSILON) {
572 throw mxvk::Exception(std::format("{}: cannot fit OBJ model with degenerate bounds", APP_NAME));
573 }
574
575 const float fit_scale = MODEL_FIT_RADIUS / source_radius;
576 for (mxvk::vec4D &vertex : model.local) {
577 vertex.x = (vertex.x - center.x) * fit_scale;
578 vertex.y = (vertex.y - center.y) * fit_scale;
579 vertex.z = (vertex.z - center.z) * fit_scale;
580 }
581 model.ComputeRad();
582 std::cout << std::format(
583 "{}: centered model and scaled radius {:.3f} to {:.3f} (scale {:.6f})\n",
584 APP_NAME,
585 source_radius,
586 MODEL_FIT_RADIUS,
587 fit_scale);
588 }
589#endif
590
591 void filter_auxiliary_objects() {
592 std::unordered_map<std::string, std::size_t> triangle_counts;
593 for (const mxvk::Triangle &triangle : model.vlist) {
594 ++triangle_counts[triangle.source_object_name];
595 }
596 if (triangle_counts.size() <= 1) {
597 return;
598 }
599
600 const auto dominant = std::max_element(
601 triangle_counts.begin(),
602 triangle_counts.end(),
603 [](const auto &left, const auto &right) {
604 return left.second < right.second;
605 });
606 constexpr float DOMINANT_OBJECT_FRACTION = 0.95f;
607 if (static_cast<float>(dominant->second) <
608 static_cast<float>(model.vlist.size()) * DOMINANT_OBJECT_FRACTION) {
609 return;
610 }
611
612 const std::size_t original_triangle_count = model.vlist.size();
613 std::erase_if(model.vlist, [&dominant](const mxvk::Triangle &triangle) {
614 return triangle.source_object_name != dominant->first;
615 });
616 model.num_polys = static_cast<int>(model.vlist.size());
617 model.object_name = dominant->first;
618 std::cout << std::format(
619 "{}: selected dominant OBJ object '{}' ({} triangles); ignored {} auxiliary triangle(s)\n",
620 APP_NAME,
621 dominant->first,
622 dominant->second,
623 original_triangle_count - model.vlist.size());
624 }
625
626 void load_material_textures(const std::string &asset_path, bool generate_mipmaps) {
627 if (!override_texture.empty()) {
628 return;
629 }
630
631 material_textures.resize(model.materials.size());
632 for (std::size_t index = 0; index < model.materials.size(); ++index) {
633 const std::string &texture_path = model.materials[index].diffuse_map;
634 if (!texture_path.empty()) {
635 material_textures[index] = load_texture(texture_path, asset_path, generate_mipmaps);
636 }
637 }
638 }
639
640 [[nodiscard]] const Texture *face_texture(int material_index) const {
641 if (!override_texture.empty()) {
642 return &override_texture;
643 }
644 if (material_index >= 0 &&
645 static_cast<std::size_t>(material_index) < material_textures.size() &&
646 !material_textures[static_cast<std::size_t>(material_index)].empty()) {
647 return &material_textures[static_cast<std::size_t>(material_index)];
648 }
649 return nullptr;
650 }
651
652#if defined(MXVK_USE_EIGEN_MATH)
653 static void transform_eigen_batch(const mxvk::Mat4D &matrix, const VertexBatch &input, VertexBatch &output) {
654 for (int component = 0; component < 4; ++component) {
655 output.row(component).array() =
656 input.row(0).array() * matrix.mat[0][component] +
657 input.row(1).array() * matrix.mat[1][component] +
658 input.row(2).array() * matrix.mat[2][component] +
659 input.row(3).array() * matrix.mat[3][component];
660 }
661 }
662#endif
663
664 void initialize_face_geometry() {
665#if !defined(MXVK_USE_EIGEN_MATH)
666 local_face_centers.resize(model.vlist.size());
667 camera_face_centers.resize(model.vlist.size());
668 local_face_normals.resize(model.vlist.size());
669 camera_face_normals.resize(model.vlist.size());
670#endif
671 for (std::size_t index = 0; index < model.vlist.size(); ++index) {
672 const mxvk::Triangle &triangle = model.vlist[index];
673 const mxvk::vec4D &a = model.local[static_cast<std::size_t>(triangle.vert[0])];
674 const mxvk::vec4D &b = model.local[static_cast<std::size_t>(triangle.vert[1])];
675 const mxvk::vec4D &c = model.local[static_cast<std::size_t>(triangle.vert[2])];
676#if !defined(MXVK_USE_EIGEN_MATH) || !defined(MXVK_OBJ_LOADER)
677 const mxvk::vec4D center(
678 (a.x + b.x + c.x) * (1.0f / 3.0f),
679 (a.y + b.y + c.y) * (1.0f / 3.0f),
680 (a.z + b.z + c.z) * (1.0f / 3.0f),
681 1.0f);
682#endif
683 mxvk::vec4D normal = mxvk::vec4D().Build(a, b).CrossProduct(mxvk::vec4D().Build(a, c));
684 normal.Normalize();
685 normal.w = 0.0f;
686#if defined(MXVK_USE_EIGEN_MATH)
687 const Eigen::Index batch_index = static_cast<Eigen::Index>(index);
688#if !defined(MXVK_OBJ_LOADER)
689 local_face_center_batch.col(batch_index) = Eigen::Vector4f(center.x, center.y, center.z, center.w);
690#endif
691 local_face_normal_batch.col(batch_index) = Eigen::Vector4f(normal.x, normal.y, normal.z, normal.w);
692#else
693 local_face_centers[index] = center;
694 local_face_normals[index] = normal;
695#endif
696 }
697 }
698
699 void transform_and_project_vertices(const mxvk::Mat4D &rotation) {
700#if defined(MXVK_USE_EIGEN_MATH)
701 transform_eigen_batch(rotation, local_vertex_batch, camera_vertex_batch);
702 camera_vertex_batch.row(2).array() += camera_distance;
703
704 const float scale = static_cast<float>(std::min(frame_width, frame_height)) * 0.52f;
705 const float center_x = static_cast<float>(frame_width) * 0.5f;
706 const float center_y = static_cast<float>(frame_height) * 0.5f;
707 inverse_vertex_depth.array() =
708 camera_vertex_batch.row(2).array().max(0.001f).inverse();
709 projected_vertex_batch.row(0).array() =
710 center_x + camera_vertex_batch.row(0).array() * inverse_vertex_depth.array() * scale;
711 projected_vertex_batch.row(1).array() =
712 center_y - camera_vertex_batch.row(1).array() * inverse_vertex_depth.array() * scale;
713 projected_vertex_batch.row(2) = camera_vertex_batch.row(2);
714 projected_vertex_batch.row(3).setOnes();
715#else
716 rotation.MulVec(model.local, camera_vertices);
717 for (std::size_t index = 0; index < model.local.size(); ++index) {
718 camera_vertices[index].z += camera_distance;
719 projected_vertices[index] = project_to_screen(camera_vertices[index], frame_width, frame_height);
720 }
721#endif
722 }
723
724 [[nodiscard]] mxvk::vec4D projected_vertex(std::size_t index) const {
725#if defined(MXVK_USE_EIGEN_MATH)
726 const Eigen::Index batch_index = static_cast<Eigen::Index>(index);
727 return {
728 projected_vertex_batch(0, batch_index),
729 projected_vertex_batch(1, batch_index),
730 projected_vertex_batch(2, batch_index),
731 projected_vertex_batch(3, batch_index),
732 };
733#else
734 return projected_vertices[index];
735#endif
736 }
737
738 void append_visible_face(const mxvk::Triangle &triangle, float intensity) {
739 const auto first = static_cast<std::size_t>(triangle.vert[0]);
740 const auto second = static_cast<std::size_t>(triangle.vert[1]);
741 const auto third = static_cast<std::size_t>(triangle.vert[2]);
742 std::array<mxvk::vec2D, 3> texcoords = {
743 model.texcoords[first],
744 model.texcoords[second],
745 model.texcoords[third],
746 };
747#if defined(MXVK_OBJ_LOADER)
748 for (mxvk::vec2D &texcoord : texcoords) {
749 texcoord.y = 1.0f - texcoord.y;
750 }
751#endif
752 const bool repeat_horizontal =
753 face_texture(triangle.material_index) != nullptr &&
754 texture_repeat_enabled;
755 if (repeat_horizontal) {
756 texcoords = unwrap_horizontal_texcoords(texcoords);
757 }
758 visible_faces.push_back({
759 {projected_vertex(first), projected_vertex(second), projected_vertex(third)},
760 texcoords,
761 triangle.color,
762 triangle.material_index,
763 repeat_horizontal,
764 intensity,
765 });
766 }
767
768 void build_visible_faces(const mxvk::Mat4D &rotation_matrix, const mxvk::vec4D &light_direction) {
769#if defined(MXVK_USE_EIGEN_MATH)
770#if !defined(MXVK_OBJ_LOADER)
771 transform_eigen_batch(rotation_matrix, local_face_center_batch, camera_face_center_batch);
772 camera_face_center_batch.row(2).array() += camera_distance;
773#endif
774 transform_eigen_batch(rotation_matrix, local_face_normal_batch, camera_face_normal_batch);
775#if !defined(MXVK_OBJ_LOADER)
776 triangle_visible =
777 (-camera_face_normal_batch.row(0).array() * camera_face_center_batch.row(0).array() -
778 camera_face_normal_batch.row(1).array() * camera_face_center_batch.row(1).array() -
779 camera_face_normal_batch.row(2).array() * camera_face_center_batch.row(2).array()) >
780 0.0f;
781#endif
782
783 const auto diffuse =
784 (camera_face_normal_batch.row(0).array() * light_direction.x +
785 camera_face_normal_batch.row(1).array() * light_direction.y +
786 camera_face_normal_batch.row(2).array() * light_direction.z)
787 .max(0.0f);
788 triangle_intensity.array() = (0.35f + diffuse * 0.65f).min(1.0f);
789
790 for (std::size_t index = 0; index < model.vlist.size(); ++index) {
791 const Eigen::Index batch_index = static_cast<Eigen::Index>(index);
792#if !defined(MXVK_OBJ_LOADER)
793 if (!triangle_visible(batch_index)) {
794 continue;
795 }
796#endif
797 const mxvk::Triangle &triangle = model.vlist[index];
798 append_visible_face(triangle, triangle_intensity(batch_index));
799 }
800#else
801 rotation_matrix.MulVec(local_face_centers, camera_face_centers);
802 rotation_matrix.MulVec(local_face_normals, camera_face_normals);
803 for (std::size_t index = 0; index < model.vlist.size(); ++index) {
804 const mxvk::Triangle &triangle = model.vlist[index];
805 mxvk::vec4D &center = camera_face_centers[index];
806 center.z += camera_distance;
807 const mxvk::vec4D &normal = camera_face_normals[index];
808 const mxvk::vec4D view_direction(-center.x, -center.y, -center.z, 0.0f);
809#if !defined(MXVK_OBJ_LOADER)
810 if (normal.DotProduct(view_direction) <= 0.0f) {
811 continue;
812 }
813#endif
814
815 const float diffuse = std::max(0.0f, normal.DotProduct(light_direction));
816 append_visible_face(triangle, std::clamp(0.35f + diffuse * 0.65f, 0.0f, 1.0f));
817 }
818#endif
819 }
820
821 void ensure_framebuffer() {
822 if (frame_surface != nullptr) {
823 return;
824 }
825
826 frame_surface = create_frame_surface(frame_width, frame_height);
827 frame_format = SDL_GetPixelFormatDetails(frame_surface->format);
828 if (frame_format == nullptr) {
829 throw mxvk::Exception(std::format("{}: failed to query frame format: {}", APP_NAME, SDL_GetError()));
830 }
831
832 depth_buffer.resize(static_cast<std::size_t>(frame_width) * static_cast<std::size_t>(frame_height));
833 clear_frame(BACKGROUND_COLOR);
834 wireframe_pipeline.Begin(frame_width, frame_height, [this](int x, int y, mxvk::MXCOLOR color) {
835 put_pixel(x, y, color);
836 });
837
838 frame_sprite = createSprite(frame_surface.get());
839 frame_sprite->setTextureFilter(VK_FILTER_NEAREST);
840 }
841
842 [[nodiscard]] std::uint32_t map_color(mxvk::MXCOLOR color) const {
843 return SDL_MapRGBA(frame_format, nullptr, mxvk::color_r(color), mxvk::color_g(color), mxvk::color_b(color), mxvk::color_a(color));
844 }
845
846 void clear_frame(mxvk::MXCOLOR color) {
847 SDL_FillSurfaceRect(frame_surface.get(), nullptr, map_color(color));
848 }
849
850 void put_pixel(int x, int y, mxvk::MXCOLOR color) {
851 if (x < 0 || y < 0 || x >= frame_width || y >= frame_height) {
852 return;
853 }
854
855 auto *row = static_cast<std::uint8_t *>(frame_surface->pixels) + (static_cast<std::size_t>(y) * static_cast<std::size_t>(frame_surface->pitch));
856 auto *pixel = reinterpret_cast<std::uint32_t *>(row) + x;
857 *pixel = map_color(color);
858 }
859
860 [[nodiscard]] float texture_level_for_face(const FaceDraw &face, const Texture *texture, float area) const {
861 if (texture == nullptr || !mipmapping_enabled) {
862 return 0.0f;
863 }
864
865 const mxvk::vec4D &a = face.points[0];
866 const mxvk::vec4D &b = face.points[1];
867 const mxvk::vec4D &c = face.points[2];
868 const float weight_a_dx = (c.y - b.y) / area;
869 const float weight_a_dy = -(c.x - b.x) / area;
870 const float weight_b_dx = (a.y - c.y) / area;
871 const float weight_b_dy = -(a.x - c.x) / area;
872 const float weight_c_dx = (b.y - a.y) / area;
873 const float weight_c_dy = -(b.x - a.x) / area;
874
875 float u_dx = 0.0f;
876 float u_dy = 0.0f;
877 float v_dx = 0.0f;
878 float v_dy = 0.0f;
879 if (warp_fix_enabled) {
880 const float inverse_z_a = 1.0f / a.z;
881 const float inverse_z_b = 1.0f / b.z;
882 const float inverse_z_c = 1.0f / c.z;
883 const float inverse_z = (inverse_z_a + inverse_z_b + inverse_z_c) / 3.0f;
884 const float inverse_z_dx =
885 weight_a_dx * inverse_z_a +
886 weight_b_dx * inverse_z_b +
887 weight_c_dx * inverse_z_c;
888 const float inverse_z_dy =
889 weight_a_dy * inverse_z_a +
890 weight_b_dy * inverse_z_b +
891 weight_c_dy * inverse_z_c;
892 const auto corrected_derivatives = [&](float first, float second, float third) {
893 const float value_over_z =
894 (first * inverse_z_a + second * inverse_z_b + third * inverse_z_c) / 3.0f;
895 const float value_over_z_dx =
896 weight_a_dx * first * inverse_z_a +
897 weight_b_dx * second * inverse_z_b +
898 weight_c_dx * third * inverse_z_c;
899 const float value_over_z_dy =
900 weight_a_dy * first * inverse_z_a +
901 weight_b_dy * second * inverse_z_b +
902 weight_c_dy * third * inverse_z_c;
903 const float denominator = inverse_z * inverse_z;
904 return std::array<float, 2>{
905 (value_over_z_dx * inverse_z - value_over_z * inverse_z_dx) / denominator,
906 (value_over_z_dy * inverse_z - value_over_z * inverse_z_dy) / denominator,
907 };
908 };
909 const std::array<float, 2> u_derivatives = corrected_derivatives(
910 face.texcoords[0].x,
911 face.texcoords[1].x,
912 face.texcoords[2].x);
913 const std::array<float, 2> v_derivatives = corrected_derivatives(
914 face.texcoords[0].y,
915 face.texcoords[1].y,
916 face.texcoords[2].y);
917 u_dx = u_derivatives[0];
918 u_dy = u_derivatives[1];
919 v_dx = v_derivatives[0];
920 v_dy = v_derivatives[1];
921 } else {
922 u_dx =
923 weight_a_dx * face.texcoords[0].x +
924 weight_b_dx * face.texcoords[1].x +
925 weight_c_dx * face.texcoords[2].x;
926 u_dy =
927 weight_a_dy * face.texcoords[0].x +
928 weight_b_dy * face.texcoords[1].x +
929 weight_c_dy * face.texcoords[2].x;
930 v_dx =
931 weight_a_dx * face.texcoords[0].y +
932 weight_b_dx * face.texcoords[1].y +
933 weight_c_dx * face.texcoords[2].y;
934 v_dy =
935 weight_a_dy * face.texcoords[0].y +
936 weight_b_dy * face.texcoords[1].y +
937 weight_c_dy * face.texcoords[2].y;
938 }
939
940 const float horizontal_footprint = std::hypot(
941 u_dx * static_cast<float>(texture->width()),
942 v_dx * static_cast<float>(texture->height()));
943 const float vertical_footprint = std::hypot(
944 u_dy * static_cast<float>(texture->width()),
945 v_dy * static_cast<float>(texture->height()));
946 return std::log2(std::max({mxvk::EPSILON, horizontal_footprint, vertical_footprint})) + mip_level_bias;
947 }
948
949 void draw_gradient_triangle(const FaceDraw &face) {
950 const mxvk::vec4D &a = face.points[0];
951 const mxvk::vec4D &b = face.points[1];
952 const mxvk::vec4D &c = face.points[2];
953 const auto edge = [](const mxvk::vec4D &first, const mxvk::vec4D &second, float x, float y) {
954 return (x - first.x) * (second.y - first.y) - (y - first.y) * (second.x - first.x);
955 };
956
957 const float area = edge(b, c, a.x, a.y);
958 if (std::abs(area) <= mxvk::EPSILON) {
959 return;
960 }
961
962 const int min_x = std::clamp(static_cast<int>(std::floor(std::min({a.x, b.x, c.x}))), 0, frame_width - 1);
963 const int max_x = std::clamp(static_cast<int>(std::ceil(std::max({a.x, b.x, c.x}))), 0, frame_width - 1);
964 const int min_y = std::clamp(static_cast<int>(std::floor(std::min({a.y, b.y, c.y}))), 0, frame_height - 1);
965 const int max_y = std::clamp(static_cast<int>(std::ceil(std::max({a.y, b.y, c.y}))), 0, frame_height - 1);
966 const Texture *texture = face_texture(face.material_index);
967 const float texture_level = texture_level_for_face(face, texture, area);
968
969 for (int y = min_y; y <= max_y; ++y) {
970 for (int x = min_x; x <= max_x; ++x) {
971 const float sample_x = static_cast<float>(x) + 0.5f;
972 const float sample_y = static_cast<float>(y) + 0.5f;
973 const float weight_a = edge(b, c, sample_x, sample_y) / area;
974 const float weight_b = edge(c, a, sample_x, sample_y) / area;
975 const float weight_c = edge(a, b, sample_x, sample_y) / area;
976 if (weight_a < -mxvk::EPSILON || weight_b < -mxvk::EPSILON || weight_c < -mxvk::EPSILON) {
977 continue;
978 }
979
980 const float reciprocal_depth =
981 weight_a / a.z +
982 weight_b / b.z +
983 weight_c / c.z;
984 if (reciprocal_depth <= mxvk::EPSILON) {
985 continue;
986 }
987
988 const float depth = 1.0f / reciprocal_depth;
989 const std::size_t pixel_index =
990 static_cast<std::size_t>(y) * static_cast<std::size_t>(frame_width) +
991 static_cast<std::size_t>(x);
992 if (depth >= depth_buffer[pixel_index]) {
993 continue;
994 }
995 depth_buffer[pixel_index] = depth;
996
997 const float texture_weight_a = warp_fix_enabled ? (weight_a / a.z) * depth : weight_a;
998 const float texture_weight_b = warp_fix_enabled ? (weight_b / b.z) * depth : weight_b;
999 const float texture_weight_c = warp_fix_enabled ? (weight_c / c.z) * depth : weight_c;
1000 const float u =
1001 face.texcoords[0].x * texture_weight_a +
1002 face.texcoords[1].x * texture_weight_b +
1003 face.texcoords[2].x * texture_weight_c;
1004 const float v =
1005 face.texcoords[0].y * texture_weight_a +
1006 face.texcoords[1].y * texture_weight_b +
1007 face.texcoords[2].y * texture_weight_c;
1008 mxvk::MXCOLOR color = face.material_color;
1009 if (texture != nullptr) {
1010 color = texture->sample(u, v, texture_level, face.repeat_horizontal);
1011 }
1012#if !defined(MXVK_OBJ_LOADER)
1013 else {
1014 color = gradient_color(u, v);
1015 }
1016#endif
1017 put_pixel(x, y, mxvk::shade_color(color, face.intensity));
1018 }
1019 }
1020 }
1021
1022 [[nodiscard]] static mxvk::MXCOLOR gradient_color(float u, float v) {
1023 u = std::clamp(u, 0.0f, 1.0f);
1024 v = std::clamp(v, 0.0f, 1.0f);
1025
1026 constexpr mxvk::MXCOLOR BOTTOM_LEFT = mxvk::MXVK_RGB(68, 214, 255);
1027 constexpr mxvk::MXCOLOR BOTTOM_RIGHT = mxvk::MXVK_RGB(92, 255, 142);
1028 constexpr mxvk::MXCOLOR TOP_LEFT = mxvk::MXVK_RGB(155, 105, 255);
1029 constexpr mxvk::MXCOLOR TOP_RIGHT = mxvk::MXVK_RGB(255, 82, 197);
1030 const auto bilinear_channel = [&](auto component) {
1031 const float bottom =
1032 static_cast<float>(component(BOTTOM_LEFT)) +
1033 (static_cast<float>(component(BOTTOM_RIGHT)) - static_cast<float>(component(BOTTOM_LEFT))) * u;
1034 const float top =
1035 static_cast<float>(component(TOP_LEFT)) +
1036 (static_cast<float>(component(TOP_RIGHT)) - static_cast<float>(component(TOP_LEFT))) * u;
1037 return std::clamp(static_cast<int>(std::lround(bottom + (top - bottom) * v)), 0, 255);
1038 };
1039
1040 return mxvk::MXVK_RGB(
1041 bilinear_channel(mxvk::color_r),
1042 bilinear_channel(mxvk::color_g),
1043 bilinear_channel(mxvk::color_b));
1044 }
1045
1046 [[nodiscard]] static mxvk::vec4D project_to_screen(const mxvk::vec4D &point, int width, int height) {
1047 const float scale = static_cast<float>(std::min(width, height)) * 0.52f;
1048 const float center_x = static_cast<float>(width) * 0.5f;
1049 const float center_y = static_cast<float>(height) * 0.5f;
1050 const float z = std::max(point.z, 0.001f);
1051 return {center_x + (point.x / z) * scale, center_y - (point.y / z) * scale, point.z, 1.0f};
1052 }
1053 };
1054} // namespace example
1055
1056int main(int argc, char **argv) {
1057 try {
1058 Arguments args = proc_args(argc, argv);
1059 FramebufferDimensions framebuffer = args.framebuffer;
1060 if (args.benchmark && !args.framebufferSpecified) {
1061 framebuffer = {320, 180};
1062 std::cout << APP_NAME << ": benchmark framebuffer defaults to 320x180; use --framebuffer to override\n";
1063 }
1064 example::Math3DModelLoaderWindow window(args.filename, args.texture, args.path, std::string(WINDOW_TITLE), args.width, args.height, args.fullscreen, args.enable_vsync, args.repeat, args.nowarpfix, args.disable_mipmap, args.mip_bias, framebuffer, args.benchmark, args.wireframe);
1065 window.loop();
1066 } catch (mxvk::Exception &e) {
1067 std::cerr << std::format("mxvk: Exception: {}\n", e.text());
1068 return EXIT_FAILURE;
1069 } catch (ArgException<std::string> &e) {
1070 std::cerr << std::format("mxvk: Argument Exception: {}\n", e.text());
1071 return EXIT_FAILURE;
1072 }
1073 return EXIT_SUCCESS;
1074}
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:48
Math3DModelLoaderWindow(const std::string &filename, const std::string &texture_filename, const std::string &asset_path, const std::string &title, int width, int height, bool fullscreen, bool enable_vsync, bool repeat_texture, bool disable_warp_fix, bool disable_mipmap, float mip_bias, const FramebufferDimensions &framebuffer, bool benchmark, bool wireframe)
Definition main.cpp:308
void proc() override
Execute one processing/update step.
Definition main.cpp:413
void event(SDL_Event &e) override
Handle one SDL event.
Definition main.cpp:373
std::string text() const
Four-by-four homogeneous transform matrix.
Definition mxvk_math.h:812
float mat[4][4]
Matrix elements indexed as row, column.
Definition mxvk_math.h:815
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
Clipped software rasterization pipeline for lines and filled triangles.
Definition mxvk_math.h:2273
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
std::vector< vec4D > local
Local-space vertices.
Definition mxvk_math.h:1475
std::vector< Triangle > vlist
Indexed triangle list.
Definition mxvk_math.h:1484
Two-dimensional float vector with common arithmetic helpers.
Definition mxvk_math.h:177
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
float w
Homogeneous W coordinate.
Definition mxvk_math.h:428
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
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.
PNG image loading and saving utilities via SDL3.
mxvk::MXCOLOR pack_color(std::uint32_t red, std::uint32_t green, std::uint32_t blue, std::uint32_t alpha)
Definition main.cpp:55
constexpr float MODEL_SCALE
Definition main.cpp:42
std::unique_ptr< SDL_Surface, SurfaceDeleter > SurfacePtr
Definition main.cpp:29
std::array< mxvk::vec2D, 3 > unwrap_horizontal_texcoords(std::array< mxvk::vec2D, 3 > texcoords)
Definition main.cpp:270
std::filesystem::path texture_path(const std::string &filename, const std::string &asset_path)
Definition main.cpp:147
Texture load_texture(const std::string &filename, const std::string &asset_path, bool generate_mipmaps)
Definition main.cpp:160
constexpr float MAX_PITCH_DEGREES
Definition main.cpp:297
constexpr float MIN_CAMERA_DISTANCE
Definition main.cpp:293
constexpr std::string_view MODEL_FORMAT
Definition main.cpp:39
constexpr float MAX_CAMERA_DISTANCE
Definition main.cpp:294
mxvk::MXCOLOR interpolate_color(mxvk::MXCOLOR first, mxvk::MXCOLOR second, float fraction)
Definition main.cpp:62
constexpr std::string_view WINDOW_TITLE
Definition main.cpp:41
bool crosses_horizontal_texture_seam(const std::array< mxvk::vec2D, 3 > &texcoords)
Definition main.cpp:259
SurfacePtr create_frame_surface(int width, int height)
Definition main.cpp:31
constexpr std::string_view DEFAULT_MODEL
Definition main.cpp:40
constexpr float MOUSE_ROTATION_SENSITIVITY
Definition main.cpp:296
constexpr float CAMERA_ZOOM_STEP
Definition main.cpp:295
constexpr mxvk::MXCOLOR BACKGROUND_COLOR
Definition main.cpp:43
constexpr std::string_view APP_NAME
Definition main.cpp:38
std::filesystem::path model_path(const std::string &filename, const std::string &asset_path)
Definition main.cpp:140
constexpr std::string_view BACKEND_NAME
Definition main.cpp:301
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
SDL_Surface * LoadPNG(const char *file)
Load a PNG file into an SDL_Surface.
Definition mxvk_png.cpp:103
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 framebufferSpecified
Whether --framebuffer was provided.
Definition argz.hpp:759
bool fullscreen
Whether fullscreen mode was requested.
Definition argz.hpp:736
float mip_bias
Mipmap level-of-detail bias requested by --mip-bias.
Definition argz.hpp:757
bool enable_vsync
Enable FIFO present mode / v-sync (--enable-vsync).
Definition argz.hpp:750
bool nowarpfix
Disable perspective-correct texture mapping (--nowarpfix).
Definition argz.hpp:755
std::string texture
Optional texture file path (--texture).
Definition argz.hpp:764
int height
Viewport height in pixels (default: 720).
Definition argz.hpp:733
bool repeat
Enable repeat behavior such as playback looping or wrapped textures.
Definition argz.hpp:747
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
bool benchmark
Enable application benchmark mode (--benchmark).
Definition argz.hpp:753
bool wireframe
Render supported 3D models as wireframes (--wireframe).
Definition argz.hpp:754
int width
Viewport width in pixels (default: 1280).
Definition argz.hpp:732
bool disable_mipmap
Disable mipmap generation and selection (--disable-mipmap).
Definition argz.hpp:756
Parsed software framebuffer dimensions.
Definition argz.hpp:721
std::array< mxvk::vec4D, 3 > points
Definition main.cpp:251
std::array< mxvk::vec2D, 3 > texcoords
Definition main.cpp:252
std::vector< mxvk::MXCOLOR > pixels
Definition main.cpp:82
mxvk::MXCOLOR sample_bilinear(float u, float v, bool repeat_horizontal) const
Definition main.cpp:84
mxvk::MXCOLOR sample(float u, float v, float level, bool repeat_horizontal) const
Definition main.cpp:119
std::vector< MipLevel > levels
Definition main.cpp:105
Triangle primitive used by the simple software rendering pipeline.
Definition mxvk_math.h:1326
std::string source_object_name
Wavefront object name from the o section containing this triangle.
Definition mxvk_math.h:1352
int material_index
Index of the OBJ/MTL material used by this triangle, or -1.
Definition mxvk_math.h:1346
MXCOLOR color
Triangle color.
Definition mxvk_math.h:1334
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