MXVK Vulkan Framework 0.35.0
C++20 Vulkan rendering framework for practical 2D and 3D application development with SDL3.
Loading...
Searching...
No Matches
mxvk_nanobind.cpp
Go to the documentation of this file.
1#include "mxvk_nanobind.hpp"
2
3#include <nanobind/ndarray.h>
4#include <nanobind/stl/array.h>
5#include <nanobind/stl/shared_ptr.h>
6#include <nanobind/stl/string.h>
7#include <nanobind/stl/string_view.h>
8#include <nanobind/stl/vector.h>
9#include <nanobind/trampoline.h>
10
11#include <cstring>
12#include <filesystem>
13#include <memory>
14#include <stdexcept>
15
16#if defined(_WIN32)
17#ifndef NOMINMAX
18#define NOMINMAX
19#endif
20#include <windows.h>
21#elif defined(__APPLE__) || defined(__linux__)
22#include <dlfcn.h>
23#endif
24
25#include <mxvk/mxvk.hpp>
27#include <mxvk/mxvk_cfg.hpp>
28#include <mxvk/mxvk_console.hpp>
32#include <mxvk/mxvk_model.hpp>
33#include <mxvk/mxvk_png.hpp>
37#include <mxvk/mxvk_stencil.hpp>
39#if defined(MXVK_WITH_JPEG)
40#include <mxvk/mxvk_jpeg.hpp>
41#endif
42
43#if defined(MXVK_WITH_CV)
44#include <mxvk/mxvk_cv.hpp>
45#endif
46#if defined(MXVK_WITH_FFMPEG_CAPTURE)
48#endif
49#if defined(MXVK_WITH_MIXER)
50#include <mxvk/mxvk_sound.hpp>
51#endif
52#include <mxvk/mxvk_version.hpp>
53
54namespace nb = nanobind;
55
56namespace {
58
59 std::filesystem::path python_module_directory() {
60#if defined(_WIN32)
61 HMODULE module_handle = nullptr;
62 if (GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, reinterpret_cast<LPCWSTR>(&MODULE_LOCATION_ANCHOR), &module_handle) == 0) {
63 return {};
64 }
65
66 std::vector<wchar_t> module_path(1024);
67 while (true) {
68 const DWORD path_length = GetModuleFileNameW(module_handle, module_path.data(), static_cast<DWORD>(module_path.size()));
69 if (path_length == 0) {
70 return {};
71 }
72 if (path_length < module_path.size() - 1U) {
73 return std::filesystem::path(std::wstring(module_path.data(), path_length)).parent_path();
74 }
75 module_path.resize(module_path.size() * 2U);
76 }
77#elif defined(__APPLE__) || defined(__linux__)
78 Dl_info module_info{};
79 if (dladdr(&MODULE_LOCATION_ANCHOR, &module_info) == 0 || module_info.dli_fname == nullptr) {
80 return {};
81 }
82 return std::filesystem::path(module_info.dli_fname).parent_path();
83#else
84 return {};
85#endif
86 }
87
88 glm::mat4 matrix_from_rows(const std::array<float, 16> &values) {
89 glm::mat4 matrix{1.0F};
90 for (size_t row = 0; row < 4; ++row)
91 for (size_t column = 0; column < 4; ++column)
92 matrix[column][row] = values[row * 4 + column];
93 return matrix;
94 }
95
96 std::array<float, 16> matrix_to_rows(const glm::mat4 &matrix) {
97 std::array<float, 16> values{};
98 for (size_t row = 0; row < 4; ++row)
99 for (size_t column = 0; column < 4; ++column)
100 values[row * 4 + column] = matrix[column][row];
101 return values;
102 }
103
104 glm::vec4 vector4(const std::array<float, 4> &values) { return {values[0], values[1], values[2], values[3]}; }
105 std::array<float, 4> vector4_array(const glm::vec4 &value) { return {value.x, value.y, value.z, value.w}; }
106
108 public:
109 explicit PythonEvent(const SDL_Event *event) : value(*event) {
110 if (value.type == SDL_EVENT_TEXT_INPUT && event->text.text != nullptr)
111 text_value = event->text.text;
112 }
113 [[nodiscard]] uint32_t type() const { return value.type; }
114 [[nodiscard]] uint64_t timestamp() const { return value.common.timestamp; }
115 [[nodiscard]] int32_t key() const { return value.key.key; }
116 [[nodiscard]] uint32_t scancode() const { return value.key.scancode; }
117 [[nodiscard]] uint16_t modifiers() const { return value.key.mod; }
118 [[nodiscard]] bool key_down() const { return value.type == SDL_EVENT_KEY_DOWN; }
119 [[nodiscard]] bool key_up() const { return value.type == SDL_EVENT_KEY_UP; }
120 [[nodiscard]] bool down() const { return value.key.down; }
121 [[nodiscard]] bool repeat() const { return value.key.repeat; }
122 [[nodiscard]] const std::string &text() const { return text_value; }
123 [[nodiscard]] bool mouse_motion() const { return value.type == SDL_EVENT_MOUSE_MOTION; }
124 [[nodiscard]] bool mouse_button_down() const { return value.type == SDL_EVENT_MOUSE_BUTTON_DOWN; }
125 [[nodiscard]] bool mouse_button_up() const { return value.type == SDL_EVENT_MOUSE_BUTTON_UP; }
126 [[nodiscard]] bool mouse_wheel() const { return value.type == SDL_EVENT_MOUSE_WHEEL; }
127 [[nodiscard]] float x() const {
128 if (mouse_motion())
129 return value.motion.x;
131 return value.button.x;
132 if (mouse_wheel())
133 return value.wheel.mouse_x;
134 return 0.0F;
135 }
136 [[nodiscard]] float y() const {
137 if (mouse_motion())
138 return value.motion.y;
140 return value.button.y;
141 if (mouse_wheel())
142 return value.wheel.mouse_y;
143 return 0.0F;
144 }
145 [[nodiscard]] float relative_x() const { return mouse_motion() ? value.motion.xrel : 0.0F; }
146 [[nodiscard]] float relative_y() const { return mouse_motion() ? value.motion.yrel : 0.0F; }
147 [[nodiscard]] uint8_t button() const { return mouse_button_down() || mouse_button_up() ? value.button.button : 0; }
148 [[nodiscard]] uint8_t clicks() const { return mouse_button_down() || mouse_button_up() ? value.button.clicks : 0; }
149 [[nodiscard]] float wheel_x() const { return mouse_wheel() ? value.wheel.x : 0.0F; }
150 [[nodiscard]] float wheel_y() const { return mouse_wheel() ? value.wheel.y : 0.0F; }
151 [[nodiscard]] int32_t wheel_ticks_x() const { return mouse_wheel() ? value.wheel.integer_x : 0; }
152 [[nodiscard]] int32_t wheel_ticks_y() const { return mouse_wheel() ? value.wheel.integer_y : 0; }
153 [[nodiscard]] bool wheel_flipped() const { return mouse_wheel() && value.wheel.direction == SDL_MOUSEWHEEL_FLIPPED; }
154
155 private:
156 SDL_Event value{};
157 std::string text_value{};
158 };
159
161 public:
163
164 void proc() override { NB_OVERRIDE(proc); }
165
166 void event(SDL_Event &event) override {
167 constexpr uint64_t hash = nanobind::detail::str_hash("event");
168 nanobind::detail::ticket ticket(nb_trampoline, "event", hash, false);
169 if (ticket.key.is_valid())
170 nb_trampoline.base().attr(ticket.key)(PythonEvent{&event});
171 else
172 NBBase::event(event);
173 }
174
175 void onSwapchainRecreated() override {
176 constexpr uint64_t hash = nanobind::detail::str_hash("on_swapchain_recreated");
177 nanobind::detail::ticket ticket(nb_trampoline, "on_swapchain_recreated", hash, false);
178 if (ticket.key.is_valid())
179 nb_trampoline.base().attr(ticket.key)();
180 else
181 NBBase::onSwapchainRecreated();
182 }
183
184 void onFrameReadbackScheduled() override {
185 constexpr uint64_t hash = nanobind::detail::str_hash("on_frame_readback_scheduled");
186 nanobind::detail::ticket ticket(nb_trampoline, "on_frame_readback_scheduled", hash, false);
187 if (ticket.key.is_valid())
188 nb_trampoline.base().attr(ticket.key)();
189 else
190 NBBase::onFrameReadbackScheduled();
191 }
192
193 void onFrameReadback(std::vector<std::uint8_t> &rgba_pixels, uint32_t width, uint32_t height) override {
194 constexpr uint64_t hash = nanobind::detail::str_hash("on_frame_readback");
195 nanobind::detail::ticket ticket(nb_trampoline, "on_frame_readback", hash, false);
196 if (!ticket.key.is_valid()) {
197 NBBase::onFrameReadback(rgba_pixels, width, height);
198 return;
199 }
200
201 auto *storage = new std::vector<std::uint8_t>(rgba_pixels);
202 nb::capsule owner(storage, [](void *pointer) noexcept { delete static_cast<std::vector<std::uint8_t> *>(pointer); });
203 nb::ndarray<nb::numpy, std::uint8_t> frame(storage->data(), {static_cast<size_t>(height), static_cast<size_t>(width), 4}, owner, {static_cast<int64_t>(width) * 4, 4, 1});
204 nb_trampoline.base().attr(ticket.key)(frame, width, height);
205 }
206
208
209 void onRecordCustomRendering(VkCommandBuffer command_buffer, uint32_t image_index) override {
210 constexpr uint64_t hash = nanobind::detail::str_hash("on_record_custom_rendering");
211 nanobind::detail::ticket ticket(nb_trampoline, "on_record_custom_rendering", hash, false);
212 if (ticket.key.is_valid()) {
213 nb::capsule command(reinterpret_cast<void *>(command_buffer));
214 nb_trampoline.base().attr(ticket.key)(command, image_index);
215 } else {
216 NBBase::onRecordCustomRendering(command_buffer, image_index);
217 }
218 }
219
220 void request_exit() { exit(); }
221 };
222
224 public:
226
227 void console_proc() override { NB_OVERRIDE_PURE(console_proc); }
228
229 void console_event(SDL_Event &event) override {
230 constexpr uint64_t hash = nanobind::detail::str_hash("console_event");
231 nanobind::detail::ticket ticket(nb_trampoline, "console_event", hash, true);
232 nb_trampoline.base().attr(ticket.key)(PythonEvent{&event});
233 }
234
235 void proc() override { NB_OVERRIDE(proc); }
236
237 void event(SDL_Event &event) override {
238 constexpr uint64_t hash = nanobind::detail::str_hash("event");
239 nanobind::detail::ticket ticket(nb_trampoline, "event", hash, false);
240 if (ticket.key.is_valid())
241 nb_trampoline.base().attr(ticket.key)(PythonEvent{&event});
242 else
243 NBBase::event(event);
244 }
245 };
246
247#if defined(MXVK_WITH_CV) || (defined(MXVK_WITH_FFMPEG_CAPTURE) && defined(MXVK_CUDA))
248 nb::object opencv_rgba_array(cv::Mat &&frame) {
249 if (frame.empty())
250 return nb::none();
251 if (frame.type() != CV_8UC4)
252 throw nb::value_error("capture did not produce an RGBA8 frame");
253 auto *storage = new cv::Mat(std::move(frame));
254 nb::capsule owner(storage, [](void *pointer) noexcept { delete static_cast<cv::Mat *>(pointer); });
255 nb::ndarray<nb::numpy, uint8_t> array(storage->data, {static_cast<size_t>(storage->rows), static_cast<size_t>(storage->cols), 4}, owner, {static_cast<int64_t>(storage->step), 4, 1});
256 return nb::cast(array);
257 }
258#endif
259
260#if defined(MXVK_WITH_FFMPEG_CAPTURE)
261 nb::object ffmpeg_rgba8_array(std::vector<uint8_t> &&pixels, int width, int height, int pitch) {
262 auto *storage = new std::vector<uint8_t>(std::move(pixels));
263 nb::capsule owner(storage, [](void *pointer) noexcept { delete static_cast<std::vector<uint8_t> *>(pointer); });
264 nb::ndarray<nb::numpy, uint8_t> array(storage->data(), {static_cast<size_t>(height), static_cast<size_t>(width), 4}, owner, {pitch, 4, 1});
265 return nb::cast(array);
266 }
267
268 nb::object ffmpeg_rgba16_array(std::vector<uint16_t> &&pixels, int width, int height, int pitch) {
269 auto *storage = new std::vector<uint16_t>(std::move(pixels));
270 nb::capsule owner(storage, [](void *pointer) noexcept { delete static_cast<std::vector<uint16_t> *>(pointer); });
271 nb::ndarray<nb::numpy, uint16_t> array(storage->data(), {static_cast<size_t>(height), static_cast<size_t>(width), 4}, owner, {pitch / static_cast<int64_t>(sizeof(uint16_t)), 4, 1});
272 return nb::cast(array);
273 }
274#endif
275
277 public:
278 PythonBuffer(const mxvk::VulkanContext &context, VkDeviceSize size, VkBufferUsageFlags usage) : device(context.device) { mxvk::create_buffer(context, size, usage, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, resource); }
279
281
282 PythonBuffer(const PythonBuffer &) = delete;
284
285 void write(nanobind::ndarray<uint8_t, nanobind::c_contig, nanobind::device::cpu> bytes) {
286 if (bytes.nbytes() > resource.size)
287 throw nanobind::value_error("buffer upload is larger than the allocated buffer");
288 mxvk::map_buffer(device, resource);
289 std::memcpy(resource.mapped, bytes.data(), bytes.nbytes());
290 mxvk::unmap_buffer(device, resource);
291 }
292
293 void close() {
294 if (device != VK_NULL_HANDLE && resource.buffer != VK_NULL_HANDLE)
295 mxvk::destroy_buffer(device, resource);
296 device = VK_NULL_HANDLE;
297 }
298
299 [[nodiscard]] size_t size() const { return static_cast<size_t>(resource.size); }
300 [[nodiscard]] bool valid() const { return resource.buffer != VK_NULL_HANDLE; }
301
302 private:
303 VkDevice device = VK_NULL_HANDLE;
304 mxvk::BufferResource resource{};
305 };
306
308 public:
309 PythonTexture(const mxvk::VulkanContext &context, const std::string &path) : device(context.device) { mxvk::create_texture_from_png(context, path, resource); }
311
312 PythonTexture(const PythonTexture &) = delete;
314
315 void close() {
316 if (device != VK_NULL_HANDLE && resource.image != VK_NULL_HANDLE)
317 mxvk::destroy_texture(device, resource);
318 device = VK_NULL_HANDLE;
319 }
320
321 [[nodiscard]] uint32_t width() const { return resource.width; }
322 [[nodiscard]] uint32_t height() const { return resource.height; }
323 [[nodiscard]] bool valid() const { return resource.image != VK_NULL_HANDLE; }
324
325 private:
326 VkDevice device = VK_NULL_HANDLE;
327 mxvk::TextureResource resource{};
328 };
329} // namespace
330
331namespace mxvk {
332 void bind_nanobind_module(nb::module_ &module) {
333 module.doc() = "Python bindings for the MXVK Vulkan framework.";
334
335 nb::exception<Exception>(module, "MXVKError", PyExc_RuntimeError);
336
337 const std::filesystem::path module_directory = python_module_directory();
338 const std::array<std::filesystem::path, 3> shader_directories{module_directory / "share" / "mxvk" / "shaders", module_directory / "mxvk" / "shaders", module_directory / "shaders"};
339 for (const std::filesystem::path &shader_directory : shader_directories) {
340 if (std::filesystem::is_directory(shader_directory)) {
341 setDefaultShaderDirectory(shader_directory.string());
342 break;
343 }
344 }
345
346 module.attr("version") = nb::make_tuple(MXVK_VERSION_CODE_MAJOR, MXVK_VERSION_CODE_MINOR, MXVK_VERSION_CODE_PATCH);
347 module.attr("has_cv") = false;
348 module.attr("has_ffmpeg_capture") = false;
349 module.attr("has_mixer") = false;
350 module.attr("has_jpeg") = false;
351 module.attr("EVENT_KEY_DOWN") = static_cast<uint32_t>(SDL_EVENT_KEY_DOWN);
352 module.attr("EVENT_KEY_UP") = static_cast<uint32_t>(SDL_EVENT_KEY_UP);
353 module.attr("EVENT_MOUSE_MOTION") = static_cast<uint32_t>(SDL_EVENT_MOUSE_MOTION);
354 module.attr("EVENT_MOUSE_BUTTON_DOWN") = static_cast<uint32_t>(SDL_EVENT_MOUSE_BUTTON_DOWN);
355 module.attr("EVENT_MOUSE_BUTTON_UP") = static_cast<uint32_t>(SDL_EVENT_MOUSE_BUTTON_UP);
356 module.attr("EVENT_MOUSE_WHEEL") = static_cast<uint32_t>(SDL_EVENT_MOUSE_WHEEL);
357 module.attr("KEY_ESCAPE") = static_cast<int32_t>(SDLK_ESCAPE);
358 module.attr("MOUSE_BUTTON_LEFT") = SDL_BUTTON_LEFT;
359 module.attr("MOUSE_BUTTON_MIDDLE") = SDL_BUTTON_MIDDLE;
360 module.attr("MOUSE_BUTTON_RIGHT") = SDL_BUTTON_RIGHT;
361 module.attr("MOUSE_BUTTON_X1") = SDL_BUTTON_X1;
362 module.attr("MOUSE_BUTTON_X2") = SDL_BUTTON_X2;
363 module.attr("MOD_SHIFT") = static_cast<uint16_t>(SDL_KMOD_SHIFT);
364 module.attr("MOD_CTRL") = static_cast<uint16_t>(SDL_KMOD_CTRL);
365 module.attr("MOD_ALT") = static_cast<uint16_t>(SDL_KMOD_ALT);
366 module.attr("MOD_GUI") = static_cast<uint16_t>(SDL_KMOD_GUI);
367
368 module.def("set_default_enable_screenshot", &setDefaultEnableScreenshot, nb::arg("enabled"));
369 module.def("default_enable_screenshot", &defaultEnableScreenshot);
370 module.def("set_default_executable_name", &setDefaultExecutableName, nb::arg("name"));
371 module.def("default_executable_name", &defaultExecutableName, nb::rv_policy::copy);
372 module.def("set_default_shader_directory", &setDefaultShaderDirectory, nb::arg("directory"));
373 module.def("default_shader_directory", &defaultShaderDirectory, nb::rv_policy::copy);
374 module.def(
375 "key_code",
376 [](const std::string &name) {
377 const SDL_Keycode key = SDL_GetKeyFromName(name.c_str());
378 if (key == SDLK_UNKNOWN)
379 throw std::invalid_argument("unknown SDL key name: " + name);
380 return static_cast<int32_t>(key);
381 },
382 nb::arg("name"));
383 module.def(
384 "key_name",
385 [](int32_t key) -> nb::object {
386 const char *name = SDL_GetKeyName(static_cast<SDL_Keycode>(key));
387 return name == nullptr || name[0] == '\0' ? nb::none() : nb::cast(std::string(name));
388 },
389 nb::arg("key"));
390 module.def("save_png_rgba", [](const std::string &path, nb::ndarray<uint8_t, nb::c_contig, nb::device::cpu> pixels, int width, int height) { return SavePNG_RGBA(path.c_str(), pixels.data(), width, height); }, nb::arg("path"), nb::arg("pixels"), nb::arg("width"), nb::arg("height"));
391 module.def("save_png_rgba16", [](const std::string &path, nb::ndarray<uint16_t, nb::c_contig, nb::device::cpu> pixels, int width, int height) { return SavePNG_RGBA16(path.c_str(), pixels.data(), width, height); }, nb::arg("path"), nb::arg("pixels"), nb::arg("width"), nb::arg("height"));
392 module.def("inspect_spirv_file", [](const std::string &path) { return inspect_spirv(load_spv(path)); }, nb::arg("path"));
393
394 nb::enum_<StorageImageFormat>(module, "StorageImageFormat").value("unknown", StorageImageFormat::Unknown).value("rgba8", StorageImageFormat::Rgba8).value("rgba16_float", StorageImageFormat::Rgba16Float);
395 nb::class_<ShaderModuleInfo>(module, "ShaderModuleInfo").def_ro("stage", &ShaderModuleInfo::stage).def_ro("local_size_x", &ShaderModuleInfo::localSizeX).def_ro("local_size_y", &ShaderModuleInfo::localSizeY).def_ro("local_size_z", &ShaderModuleInfo::localSizeZ).def_ro("uses_history_texture", &ShaderModuleInfo::usesHistoryTexture).def_ro("uses_spectrum_texture", &ShaderModuleInfo::usesSpectrumTexture).def_ro("uses_spectrum_history_texture", &ShaderModuleInfo::usesSpectrumHistoryTexture).def_ro("storage_image_format", &ShaderModuleInfo::storageImageFormat);
396
397 nb::class_<PythonBuffer>(module, "GpuBuffer").def("write", &PythonBuffer::write, nb::arg("bytes")).def("close", &PythonBuffer::close).def_prop_ro("size", &PythonBuffer::size).def_prop_ro("valid", &PythonBuffer::valid);
398 nb::class_<PythonTexture>(module, "GpuTexture").def("close", &PythonTexture::close).def_prop_ro("width", &PythonTexture::width).def_prop_ro("height", &PythonTexture::height).def_prop_ro("valid", &PythonTexture::valid);
399 module.def("create_uniform_buffer", [](VK_Window &window, size_t size) { return std::make_shared<PythonBuffer>(window.context(), size, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT); }, nb::arg("window"), nb::arg("size"), nb::keep_alive<0, 1>());
400 module.def("create_storage_buffer", [](VK_Window &window, size_t size) { return std::make_shared<PythonBuffer>(window.context(), size, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); }, nb::arg("window"), nb::arg("size"), nb::keep_alive<0, 1>());
401 module.def("load_texture", [](VK_Window &window, const std::string &path) { return std::make_shared<PythonTexture>(window.context(), path); }, nb::arg("window"), nb::arg("path"), nb::keep_alive<0, 1>());
402
403 nb::class_<StopWatch<SteadyClockPolicy>>(module, "SteadyStopwatch").def(nb::init<std::string_view>(), nb::arg("name") = "mxvk").def("start", &StopWatch<SteadyClockPolicy>::Start, nb::arg("name")).def("stop", &StopWatch<SteadyClockPolicy>::Stop).def("echo", &StopWatch<SteadyClockPolicy>::Echo, nb::arg("name")).def("time_passed", &StopWatch<SteadyClockPolicy>::TimePassed);
404
405 nb::class_<StopWatch<HighResolutionClockPolicy>>(module, "HighResolutionStopwatch").def(nb::init<std::string_view>(), nb::arg("name") = "mxvk").def("start", &StopWatch<HighResolutionClockPolicy>::Start, nb::arg("name")).def("stop", &StopWatch<HighResolutionClockPolicy>::Stop).def("echo", &StopWatch<HighResolutionClockPolicy>::Echo, nb::arg("name")).def("time_passed", &StopWatch<HighResolutionClockPolicy>::TimePassed);
406
407#if defined(MXVK_WITH_JPEG)
408 module.attr("has_jpeg") = true;
409 module.def(
410 "load_jpeg_rgba",
411 [](const std::string &path) -> nb::object {
412 SDL_Surface *loaded = VK_JPEG::Load(path.c_str());
413 if (loaded == nullptr)
414 return nb::none();
415 SDL_Surface *rgba = SDL_ConvertSurface(loaded, SDL_PIXELFORMAT_RGBA32);
416 SDL_DestroySurface(loaded);
417 if (rgba == nullptr)
418 return nb::none();
419 const size_t byte_count = static_cast<size_t>(rgba->pitch) * static_cast<size_t>(rgba->h);
420 nb::bytes pixels(static_cast<const char *>(rgba->pixels), byte_count);
421 nb::tuple result = nb::make_tuple(pixels, rgba->w, rgba->h, rgba->pitch);
422 SDL_DestroySurface(rgba);
423 return result;
424 },
425 nb::arg("path"));
426 module.def(
427 "save_jpeg_rgba",
428 [](const std::string &path, nb::ndarray<uint8_t, nb::c_contig, nb::device::cpu> pixels, int width, int height, int quality, int pitch) {
429 if (width <= 0 || height <= 0)
430 throw nb::value_error("width and height must be positive");
431 if (pitch == 0)
432 pitch = width * 4;
433 if (pitch < width * 4 || pixels.nbytes() < static_cast<size_t>(pitch) * static_cast<size_t>(height))
434 throw nb::value_error("RGBA buffer is smaller than pitch * height");
435 SDL_Surface *surface = SDL_CreateSurfaceFrom(width, height, SDL_PIXELFORMAT_RGBA32, pixels.data(), pitch);
436 if (surface == nullptr)
437 throw std::runtime_error(SDL_GetError());
438 const bool saved = VK_JPEG::SaveSurface(surface, path.c_str(), quality);
439 SDL_DestroySurface(surface);
440 return saved;
441 },
442 nb::arg("path"),
443 nb::arg("pixels"),
444 nb::arg("width"),
445 nb::arg("height"),
446 nb::arg("quality") = 90,
447 nb::arg("pitch") = 0);
448#endif
449
450#if defined(MXVK_WITH_MIXER)
451 module.attr("has_mixer") = true;
452 nb::class_<VK_Mixer>(module, "Mixer").def(nb::init<>()).def("init", &VK_Mixer::init).def("load_wav", &VK_Mixer::loadWav, nb::arg("path")).def("load_music", &VK_Mixer::loadMusic, nb::arg("path")).def("play_music", &VK_Mixer::playMusic, nb::arg("id"), nb::arg("loops") = 0).def("play_wav", &VK_Mixer::playWav, nb::arg("id"), nb::arg("loops") = 0, nb::arg("channel") = -1).def("is_playing", &VK_Mixer::isPlaying, nb::arg("channel")).def("is_music_playing", &VK_Mixer::isMusicPlaying, nb::arg("id")).def("stop_music", &VK_Mixer::stopMusic).def("cleanup", &VK_Mixer::cleanup);
453#endif
454
455#if defined(MXVK_WITH_CV)
456 module.attr("has_cv") = true;
457 nb::class_<VK_Capture>(module, "Capture")
458 .def(nb::init<>())
459 .def("open", nb::overload_cast<const std::string &>(&VK_Capture::open), nb::arg("path"))
460 .def("open_camera", nb::overload_cast<int, int>(&VK_Capture::open), nb::arg("index"), nb::arg("backend") = 0)
461 .def("close", &VK_Capture::close)
462 .def("is_open", &VK_Capture::is_open)
463 .def("reset_sprite", &VK_Capture::resetSprite)
464 .def("draw", nb::overload_cast<int, int>(&VK_Capture::draw), nb::arg("x"), nb::arg("y"))
465 .def("draw", nb::overload_cast<int, int, int, int>(&VK_Capture::draw), nb::arg("x"), nb::arg("y"), nb::arg("width"), nb::arg("height"))
466 .def("read", nb::overload_cast<>(&VK_Capture::read))
467 .def("grab", &VK_Capture::grab)
468 .def(
469 "read_rgba",
470 [](VK_Capture &capture, bool flip_y) {
471 cv::Mat frame;
472 if (!capture.readRgba(frame, flip_y))
473 return nb::object(nb::none());
474 return opencv_rgba_array(std::move(frame));
475 },
476 nb::arg("flip_y") = false)
477 .def("read_to_sprite", nb::overload_cast<VK_Sprite &>(&VK_Capture::readToSprite), nb::arg("sprite"))
478 .def("read_to_sprite", nb::overload_cast<VK_Sprite &, bool>(&VK_Capture::readToSprite), nb::arg("sprite"), nb::arg("flip_y"))
479 .def("read_to_model_texture", &VK_Capture::readToModelTexture, nb::arg("model"), nb::arg("flip_y") = false)
480 .def("reload", &VK_Capture::reload, nb::arg("width"), nb::arg("height"), nb::arg("vertex_shader_path"), nb::arg("fragment_shader_path"))
481 .def_prop_ro("sprite", &VK_Capture::getSprite, nb::rv_policy::reference_internal)
482 .def("set", &VK_Capture::set, nb::arg("property"), nb::arg("value"))
483 .def("get", &VK_Capture::get, nb::arg("property"))
484#if defined(MXVK_CUDA)
485 .def(
486 "read_gpu_rgba_to_host",
487 [](VK_Capture &capture, bool flip_y) {
488 cv::cuda::GpuMat gpu;
489 if (!capture.readGpuRgba(gpu, flip_y))
490 return nb::object(nb::none());
491 cv::Mat host;
492 gpu.download(host, capture.cudaStream());
493 capture.cudaStream().waitForCompletion();
494 return opencv_rgba_array(std::move(host));
495 },
496 nb::arg("flip_y") = false)
497#endif
498 ;
499#endif
500
501#if defined(MXVK_WITH_FFMPEG_CAPTURE)
502 module.attr("has_ffmpeg_capture") = true;
503 nb::class_<VK_FF_Capture>(module, "FFmpegCapture")
504 .def(nb::init<>())
505 .def("open", nb::overload_cast<const std::string &>(&VK_FF_Capture::open), nb::arg("path"))
506 .def("open", nb::overload_cast<const std::string &, int>(&VK_FF_Capture::open), nb::arg("path"), nb::arg("cuda_device"))
507 .def("seek_start", &VK_FF_Capture::seek_start)
508 .def("skip", &VK_FF_Capture::skip)
509 .def("close", &VK_FF_Capture::close)
510 .def("is_open", &VK_FF_Capture::is_open)
511 .def(
512 "read_rgba",
513 [](VK_FF_Capture &capture, bool flip_y) -> nb::object {
514 std::vector<uint8_t> pixels;
515 int width = 0, height = 0, pitch = 0;
516 if (!capture.readRgba(pixels, width, height, pitch, flip_y))
517 return nb::none();
518 return ffmpeg_rgba8_array(std::move(pixels), width, height, pitch);
519 },
520 nb::arg("flip_y") = false)
521 .def(
522 "read_rgba16",
523 [](VK_FF_Capture &capture, bool flip_y) -> nb::object {
524 std::vector<uint16_t> pixels;
525 int width = 0, height = 0, pitch = 0;
526 if (!capture.readRgba16(pixels, width, height, pitch, flip_y))
527 return nb::none();
528 return ffmpeg_rgba16_array(std::move(pixels), width, height, pitch);
529 },
530 nb::arg("flip_y") = false)
531 .def_prop_ro("width", &VK_FF_Capture::width)
532 .def_prop_ro("height", &VK_FF_Capture::height)
533 .def_prop_ro("fps", &VK_FF_Capture::fps)
534 .def_prop_ro("using_hardware_decode", &VK_FF_Capture::using_hardware_decode)
535 .def_prop_ro("hardware_decode_device", &VK_FF_Capture::hardware_decode_device)
536#if defined(MXVK_CUDA)
537 .def(
538 "read_gpu_rgba_to_host",
539 [](VK_FF_Capture &capture, bool flip_y) {
540 cv::cuda::GpuMat gpu;
541 cv::cuda::Stream stream;
542 if (!capture.readGpuRgba(gpu, stream, flip_y))
543 return nb::object(nb::none());
544 stream.waitForCompletion();
545 cv::Mat host;
546 gpu.download(host);
547 return opencv_rgba_array(std::move(host));
548 },
549 nb::arg("flip_y") = false)
550#endif
551 ;
552#endif
553
554 nb::class_<VK_ConfigItem>(module, "ConfigItem").def(nb::init<>()).def_rw("key", &VK_ConfigItem::key).def_rw("value", &VK_ConfigItem::value);
555
556 nb::class_<VK_Config>(module, "Config").def(nb::init<>()).def(nb::init<const std::string &>(), nb::arg("file_path")).def("item_at_key", &VK_Config::itemAtKey, nb::arg("section"), nb::arg("key"), nb::arg("default_value") = "").def("set_item", &VK_Config::setItem, nb::arg("section"), nb::arg("key"), nb::arg("value")).def("load_file", &VK_Config::loadFile, nb::arg("path")).def("save_file", &VK_Config::saveFile, nb::arg("path")).def("split_by_comma", &VK_Config::splitByComma, nb::arg("value"));
557
558 nb::class_<VK_Joystick>(module, "Joystick").def(nb::init<>()).def("open", &VK_Joystick::open, nb::arg("index")).def("close", &VK_Joystick::close).def("name", &VK_Joystick::name).def("index", &VK_Joystick::joystickIndex).def_static("count", &VK_Joystick::joysticks).def("num_buttons", &VK_Joystick::numButtons).def("num_hats", &VK_Joystick::numHats).def("num_axes", &VK_Joystick::numAxes).def("button", &VK_Joystick::getButton, nb::arg("index")).def("hat", &VK_Joystick::getHat, nb::arg("index")).def("axis", &VK_Joystick::getAxis, nb::arg("index"));
559
560 nb::class_<VK_Controller>(module, "Controller").def(nb::init<>()).def("open", &VK_Controller::open, nb::arg("index")).def("close", &VK_Controller::close).def("name", &VK_Controller::name).def("index", &VK_Controller::controllerIndex).def_static("count", &VK_Controller::joysticks).def("button", [](const VK_Controller &controller, int button) { return controller.getButton(static_cast<SDL_GamepadButton>(button)); }, nb::arg("button")).def("hat", &VK_Controller::getHat, nb::arg("index")).def("axis", [](const VK_Controller &controller, int axis) { return controller.getAxis(static_cast<SDL_GamepadAxis>(axis)); }, nb::arg("axis")).def("active", &VK_Controller::active);
561
562 nb::class_<VKVertex>(module, "Vertex").def(nb::init<>()).def_prop_rw("position", [](const VKVertex &value) { return nb::make_tuple(value.pos[0], value.pos[1], value.pos[2]); }, [](VKVertex &value, const std::array<float, 3> &position) { std::copy(position.begin(), position.end(), value.pos); }).def_prop_rw("tex_coord", [](const VKVertex &value) { return nb::make_tuple(value.texCoord[0], value.texCoord[1]); }, [](VKVertex &value, const std::array<float, 2> &tex_coord) { std::copy(tex_coord.begin(), tex_coord.end(), value.texCoord); }).def_prop_rw("normal", [](const VKVertex &value) { return nb::make_tuple(value.normal[0], value.normal[1], value.normal[2]); }, [](VKVertex &value, const std::array<float, 3> &normal) { std::copy(normal.begin(), normal.end(), value.normal); });
563
564 nb::class_<SubMesh>(module, "SubMesh").def(nb::init<>()).def_rw("first_index", &SubMesh::firstIndex).def_rw("index_count", &SubMesh::indexCount).def_rw("texture_index", &SubMesh::textureIndex).def_rw("material_name", &SubMesh::materialName);
565
566 nb::class_<MXMaterial>(module, "Material").def(nb::init<>()).def_rw("name", &MXMaterial::name).def_prop_rw("ambient", [](const MXMaterial &value) { return nb::make_tuple(value.ka[0], value.ka[1], value.ka[2]); }, [](MXMaterial &value, const std::array<float, 3> &ambient) { std::copy(ambient.begin(), ambient.end(), value.ka); }).def_prop_rw("diffuse", [](const MXMaterial &value) { return nb::make_tuple(value.kd[0], value.kd[1], value.kd[2]); }, [](MXMaterial &value, const std::array<float, 3> &diffuse) { std::copy(diffuse.begin(), diffuse.end(), value.kd); }).def_prop_rw("specular", [](const MXMaterial &value) { return nb::make_tuple(value.ks[0], value.ks[1], value.ks[2]); }, [](MXMaterial &value, const std::array<float, 3> &specular) { std::copy(specular.begin(), specular.end(), value.ks); }).def_rw("shininess", &MXMaterial::ns).def_rw("opacity", &MXMaterial::d).def_rw("illumination_model", &MXMaterial::illum).def_rw("diffuse_map", &MXMaterial::map_kd);
567
568 nb::class_<MXModel>(module, "Model")
569 .def(nb::init<>())
570 .def("load", nb::overload_cast<const std::string &, float>(&MXModel::load), nb::arg("path"), nb::arg("position_scale") = 1.0F)
571 .def("load", nb::overload_cast<const std::string &, const std::string &, const std::string &, float>(&MXModel::load), nb::arg("path"), nb::arg("texture_manifest_path"), nb::arg("texture_base_path"), nb::arg("position_scale") = 1.0F)
572 .def("export_obj", nb::overload_cast<const std::string &, const std::string &>(&MXModel::exportOBJ, nb::const_), nb::arg("obj_path"), nb::arg("mtl_path") = "")
573 .def_static("export_obj_from_file", nb::overload_cast<const std::string &, const std::string &, const std::string &, const std::string &, float>(&MXModel::exportOBJ), nb::arg("model_path"), nb::arg("texture_manifest_path"), nb::arg("texture_base_path"), nb::arg("obj_path"), nb::arg("position_scale") = 1.0F)
574 .def("compress_indices", &MXModel::compressIndices)
575 .def_prop_ro("vertices", &MXModel::vertices, nb::rv_policy::copy)
576 .def_prop_ro("indices", &MXModel::indices, nb::rv_policy::copy)
577 .def_prop_ro("index_count", &MXModel::indexCount)
578 .def_prop_ro("sub_mesh_count", &MXModel::subMeshCount)
579 .def_prop_ro("sub_meshes", &MXModel::subMeshes, nb::rv_policy::copy)
580 .def_prop_ro("materials", &MXModel::materials, nb::rv_policy::copy)
581 .def_prop_ro("material_library_path", &MXModel::mtlLibPath, nb::rv_policy::copy);
582
583 nb::class_<SDL_Color>(module, "Color").def(nb::init<>()).def(nb::init<uint8_t, uint8_t, uint8_t, uint8_t>(), nb::arg("red"), nb::arg("green"), nb::arg("blue"), nb::arg("alpha") = 255).def_rw("red", &SDL_Color::r).def_rw("green", &SDL_Color::g).def_rw("blue", &SDL_Color::b).def_rw("alpha", &SDL_Color::a);
584
585 nb::class_<VK_Console>(module, "Console")
586 .def(nb::init<>())
587 .def("attach", &VK_Console::attach, nb::arg("window"), nb::arg("font_path"), nb::arg("font_size") = 18)
588 .def("set_font", &VK_Console::setFont, nb::arg("font_path"), nb::arg("font_size"))
589 .def("draw", &VK_Console::draw)
590 .def("print_line", &VK_Console::printLine, nb::arg("line"), nb::arg("color") = SDL_Color{255, 255, 255, 255})
591 .def("clear", &VK_Console::clear)
592 .def("set_prompt", &VK_Console::setPrompt, nb::arg("prompt"))
593 .def("set_visible", &VK_Console::setVisible, nb::arg("visible"))
594 .def("show", &VK_Console::show)
595 .def("hide", &VK_Console::hide)
596 .def("toggle", &VK_Console::toggle)
597 .def("visible", &VK_Console::isVisible)
598 .def("set_max_lines", &VK_Console::setMaxLines, nb::arg("count"))
599 .def("set_max_visible_lines", &VK_Console::setMaxVisibleLines, nb::arg("count"))
600 .def("set_sprite_y_origin_top_left", &VK_Console::setSpriteYOriginTopLeft, nb::arg("enabled"))
601 .def_prop_ro("input_buffer", &VK_Console::inputBuffer, nb::rv_policy::copy);
602
603 nb::class_<PointSpriteVertex>(module, "PointSpriteVertex").def(nb::init<>()).def_prop_rw("position", [](const PointSpriteVertex &value) { return nb::make_tuple(value.position[0], value.position[1], value.position[2]); }, [](PointSpriteVertex &value, const std::array<float, 3> &position) { std::copy(position.begin(), position.end(), value.position); }).def_rw("size", &PointSpriteVertex::size).def_prop_rw("color", [](const PointSpriteVertex &value) { return nb::make_tuple(value.color[0], value.color[1], value.color[2], value.color[3]); }, [](PointSpriteVertex &value, const std::array<float, 4> &color) { std::copy(color.begin(), color.end(), value.color); });
604
605 nb::class_<VK_PointSpriteBatch>(module, "PointSpriteBatch")
606 .def(nb::init<>())
607 .def("load", &VK_PointSpriteBatch::load, nb::arg("window"), nb::arg("texture_path"), nb::arg("vertex_shader_path"), nb::arg("fragment_shader_path"), nb::arg("max_vertices"))
608 .def("resize", &VK_PointSpriteBatch::resize, nb::arg("window"))
609 .def("cleanup", &VK_PointSpriteBatch::cleanup)
610 .def(
611 "upload_vertices",
612 [](VK_PointSpriteBatch &batch, const std::vector<PointSpriteVertex> &vertices) { batch.upload_vertices(vertices.data(), vertices.size()); },
613 nb::arg("vertices"))
614 .def("set_additive_blending", &VK_PointSpriteBatch::set_additive_blending, nb::arg("enabled"))
615 .def("set_depth_test_enabled", &VK_PointSpriteBatch::set_depth_test_enabled, nb::arg("enabled"))
616 .def("set_depth_write_enabled", &VK_PointSpriteBatch::set_depth_write_enabled, nb::arg("enabled"))
617 .def_prop_ro("loaded", &VK_PointSpriteBatch::loaded)
618 .def_prop_ro("capacity", &VK_PointSpriteBatch::capacity)
619 .def_prop_ro("vertex_count", &VK_PointSpriteBatch::vertex_count);
620
621 nb::class_<BufferResource>(module, "BufferResource").def(nb::init<>()).def_rw("size", &BufferResource::size).def_prop_ro("buffer", [](const BufferResource &value) { return reinterpret_cast<uintptr_t>(value.buffer); }).def_prop_ro("memory", [](const BufferResource &value) { return reinterpret_cast<uintptr_t>(value.memory); }).def_prop_ro("mapped", [](const BufferResource &value) { return reinterpret_cast<uintptr_t>(value.mapped); });
622
623 nb::class_<TextureResource>(module, "TextureResource").def(nb::init<>()).def_rw("width", &TextureResource::width).def_rw("height", &TextureResource::height).def_prop_ro("image", [](const TextureResource &value) { return reinterpret_cast<uintptr_t>(value.image); }).def_prop_ro("memory", [](const TextureResource &value) { return reinterpret_cast<uintptr_t>(value.memory); }).def_prop_ro("view", [](const TextureResource &value) { return reinterpret_cast<uintptr_t>(value.view); }).def_prop_ro("sampler", [](const TextureResource &value) { return reinterpret_cast<uintptr_t>(value.sampler); });
624
625 nb::class_<VK_Stencil::PushConstants>(module, "StencilPushConstants").def(nb::init<>()).def_rw("time", &VK_Stencil::PushConstants::time).def_rw("aspect", &VK_Stencil::PushConstants::aspect).def_rw("phase", &VK_Stencil::PushConstants::phase).def_rw("scale", &VK_Stencil::PushConstants::scale);
626
627 nb::class_<VK_Stencil>(module, "Stencil")
628 .def(nb::init<>())
629 .def("destroy", &VK_Stencil::destroy)
630 .def(
631 "resize",
632 [](VK_Stencil &value, uint32_t width, uint32_t height) { value.resize(VkExtent2D{width, height}); },
633 nb::arg("width"),
634 nb::arg("height"))
635 .def("valid", &VK_Stencil::valid)
636 .def_prop_ro("format", [](const VK_Stencil &value) { return static_cast<int>(value.stencil_format()); })
637 .def_prop_ro("extent", [](const VK_Stencil &value) {
638 const auto extent = value.extent();
639 return nb::make_tuple(extent.width, extent.height);
640 });
641
642 nb::class_<Font>(module, "Font").def(nb::init<>()).def(nb::init<const std::string &, int>(), nb::arg("path"), nb::arg("size")).def("reset", nb::overload_cast<>(&Font::reset)).def("reset", nb::overload_cast<const std::string &, int>(&Font::reset), nb::arg("path"), nb::arg("size")).def("valid", [](const Font &font) { return static_cast<bool>(font); }).def_prop_ro("path", &Font::path).def_prop_ro("size", &Font::size);
643
644 nb::class_<UniformBufferObject>(module, "ModelUniforms").def(nb::init<>()).def_prop_rw("model", [](const UniformBufferObject &value) { return matrix_to_rows(value.model); }, [](UniformBufferObject &value, const std::array<float, 16> &matrix) { value.model = matrix_from_rows(matrix); }).def_prop_rw("view", [](const UniformBufferObject &value) { return matrix_to_rows(value.view); }, [](UniformBufferObject &value, const std::array<float, 16> &matrix) { value.view = matrix_from_rows(matrix); }).def_prop_rw("projection", [](const UniformBufferObject &value) { return matrix_to_rows(value.proj); }, [](UniformBufferObject &value, const std::array<float, 16> &matrix) { value.proj = matrix_from_rows(matrix); }).def_prop_rw("effects", [](const UniformBufferObject &value) { return vector4_array(value.fx); }, [](UniformBufferObject &value, const std::array<float, 4> &effects) { value.fx = vector4(effects); });
645
646 nb::class_<ModelFragmentPushConstants>(module, "ModelFragmentPushConstants").def(nb::init<>()).def_rw("screen_width", &ModelFragmentPushConstants::screenWidth).def_rw("screen_height", &ModelFragmentPushConstants::screenHeight).def_rw("sprite_x", &ModelFragmentPushConstants::spritePosX).def_rw("sprite_y", &ModelFragmentPushConstants::spritePosY).def_rw("sprite_width", &ModelFragmentPushConstants::spriteSizeW).def_rw("sprite_height", &ModelFragmentPushConstants::spriteSizeH).def_rw("effects_enabled", &ModelFragmentPushConstants::effectsOn).def_prop_rw("params", [](const ModelFragmentPushConstants &value) { return vector4_array(value.params); }, [](ModelFragmentPushConstants &value, const std::array<float, 4> &params) { value.params = vector4(params); });
647
648 nb::class_<ModelFragmentUniforms>(module, "ModelFragmentUniforms")
649 .def(nb::init<>())
650 .def_prop_rw(
651 "mouse",
652 [](const ModelFragmentUniforms &value) { return vector4_array(value.mouse); },
653 [](ModelFragmentUniforms &value, const std::array<float, 4> &data) { value.mouse = vector4(data); })
654 .def_prop_rw(
655 "uniform0",
656 [](const ModelFragmentUniforms &value) { return vector4_array(value.u0); },
657 [](ModelFragmentUniforms &value, const std::array<float, 4> &data) { value.u0 = vector4(data); })
658 .def_prop_rw(
659 "uniform1",
660 [](const ModelFragmentUniforms &value) { return vector4_array(value.u1); },
661 [](ModelFragmentUniforms &value, const std::array<float, 4> &data) { value.u1 = vector4(data); })
662 .def_prop_rw(
663 "uniform2",
664 [](const ModelFragmentUniforms &value) { return vector4_array(value.u2); },
665 [](ModelFragmentUniforms &value, const std::array<float, 4> &data) { value.u2 = vector4(data); })
666 .def_prop_rw(
667 "uniform3",
668 [](const ModelFragmentUniforms &value) { return vector4_array(value.u3); },
669 [](ModelFragmentUniforms &value, const std::array<float, 4> &data) { value.u3 = vector4(data); })
670 .def_prop_rw(
671 "custom_uniforms",
672 [](const ModelFragmentUniforms &value) {
673 std::vector<std::array<float, 4>> result;
674 result.reserve(value.custom_uniforms.size());
675 for (const auto &entry : value.custom_uniforms)
676 result.push_back(vector4_array(entry));
677 return result;
678 },
679 [](ModelFragmentUniforms &value, const std::vector<std::array<float, 4>> &data) {
680 if (data.size() > value.custom_uniforms.size())
681 throw nb::value_error("at most 16 custom uniforms are supported");
682 value.custom_uniforms.fill(glm::vec4{0.0F});
683 for (size_t index = 0; index < data.size(); ++index)
684 value.custom_uniforms[index] = vector4(data[index]);
685 })
686 .def_prop_rw(
687 "audio_bands",
688 [](const ModelFragmentUniforms &value) { return vector4_array(value.audio_bands); },
689 [](ModelFragmentUniforms &value, const std::array<float, 4> &data) { value.audio_bands = vector4(data); })
690 .def_prop_rw("audio_history", [](const ModelFragmentUniforms &value) { return vector4_array(value.audio_history); }, [](ModelFragmentUniforms &value, const std::array<float, 4> &data) { value.audio_history = vector4(data); });
691
692 nb::class_<VKAbstractModel>(module, "AbstractModel")
693 .def(nb::init<>())
694 .def("load", nb::overload_cast<VK_Window *, const std::string &, const std::string &, const std::string &, float>(&VKAbstractModel::load), nb::arg("window"), nb::arg("model_path"), nb::arg("texture_manifest_path") = "", nb::arg("texture_base_path") = "", nb::arg("scale") = 1.0F, nb::keep_alive<1, 2>())
695 .def("set_shaders", &VKAbstractModel::setShaders, nb::arg("window"), nb::arg("vertex_shader_path"), nb::arg("fragment_shader_path"))
696 .def("update_uniforms", &VKAbstractModel::updateUBO, nb::arg("image_index"), nb::arg("uniforms"))
697 .def("enable_extended_fragment_uniforms", &VKAbstractModel::enableExtendedFragmentUniforms)
698 .def("update_fragment_uniforms", &VKAbstractModel::updateFragmentUBO, nb::arg("image_index"), nb::arg("uniforms"))
699 .def("set_fragment_push_constants", &VKAbstractModel::setFragmentPushConstants, nb::arg("constants"))
700 .def(
701 "update_primary_texture",
702 [](VKAbstractModel &model, nb::ndarray<uint8_t, nb::c_contig, nb::device::cpu> pixels, int width, int height, int pitch) {
703 if (width <= 0 || height <= 0)
704 throw nb::value_error("width and height must be positive");
705 if (pitch == 0)
706 pitch = width * 4;
707 if (pitch < width * 4 || pixels.nbytes() < static_cast<size_t>(pitch) * static_cast<size_t>(height))
708 throw nb::value_error("RGBA buffer is smaller than pitch * height");
709 return model.updatePrimaryTexture(pixels.data(), width, height, pitch);
710 },
711 nb::arg("pixels"),
712 nb::arg("width"),
713 nb::arg("height"),
714 nb::arg("pitch") = 0)
715 .def(
716 "render",
717 [](const VKAbstractModel &model, nb::capsule command_buffer, uint32_t image_index, bool wireframe) {
718 auto command = static_cast<VkCommandBuffer>(command_buffer.data());
719 if (command == VK_NULL_HANDLE)
720 throw nb::value_error("command_buffer capsule must contain a VkCommandBuffer pointer");
721 model.render(command, image_index, wireframe);
722 },
723 nb::arg("command_buffer"),
724 nb::arg("image_index"),
725 nb::arg("wireframe") = false)
726 .def(
727 "render_with_push_constants",
728 [](VKAbstractModel &model, nb::capsule command_buffer, uint32_t image_index, size_t texture_index, const UniformBufferObject &uniforms, bool wireframe) {
729 auto command = static_cast<VkCommandBuffer>(command_buffer.data());
730 if (command == VK_NULL_HANDLE)
731 throw nb::value_error("command_buffer capsule must contain a VkCommandBuffer pointer");
732 model.renderWithPushConstants(command, image_index, texture_index, uniforms, wireframe);
733 },
734 nb::arg("command_buffer"),
735 nb::arg("image_index"),
736 nb::arg("texture_index"),
737 nb::arg("uniforms"),
738 nb::arg("wireframe") = false)
739 .def("resize", &VKAbstractModel::resize, nb::arg("window"))
740 .def("cleanup", &VKAbstractModel::cleanup, nb::arg("window"))
741 .def("set_backface_culling", &VKAbstractModel::setBackfaceCulling, nb::arg("enabled"))
742 .def("set_alpha_blending", &VKAbstractModel::setAlphaBlending, nb::arg("enabled"))
743 .def(
744 "set_color_attachment_format",
745 [](VKAbstractModel &model, int format) { model.setColorAttachmentFormat(static_cast<VkFormat>(format)); },
746 nb::arg("format"))
747 .def_prop_ro("model", &VKAbstractModel::model, nb::rv_policy::reference_internal)
748 .def_prop_ro("center_offset",
749 [](const VKAbstractModel &model) {
750 const auto value = model.modelCenterOffset();
751 return nb::make_tuple(value.x, value.y, value.z);
752 })
753 .def_prop_ro("render_scale", &VKAbstractModel::modelRenderScale)
754 .def_prop_ro("axis_extent",
755 [](const VKAbstractModel &model) {
756 const auto value = model.modelAxisExtent();
757 return nb::make_tuple(value.x, value.y, value.z);
758 })
759 .def_prop_ro("loaded", &VKAbstractModel::isLoaded);
760
761 auto sprite = nb::class_<VK_Sprite>(module, "Sprite");
762 sprite.def("load", nb::overload_cast<const std::string &, const std::string &>(&VK_Sprite::loadSprite), nb::arg("path"), nb::arg("fragment_shader_path") = "")
763 .def("create_empty", &VK_Sprite::createEmptySprite, nb::arg("width"), nb::arg("height"), nb::arg("vertex_shader_path") = "", nb::arg("fragment_shader_path") = "")
764 .def("create_empty_rgba16", &VK_Sprite::createEmptySpriteRgba16, nb::arg("width"), nb::arg("height"), nb::arg("vertex_shader_path") = "", nb::arg("fragment_shader_path") = "")
765 .def("draw", nb::overload_cast<int, int>(&VK_Sprite::drawSprite), nb::arg("x"), nb::arg("y"))
766 .def("draw", nb::overload_cast<int, int, float, float>(&VK_Sprite::drawSprite), nb::arg("x"), nb::arg("y"), nb::arg("scale_x"), nb::arg("scale_y"))
767 .def("draw_rotated", nb::overload_cast<int, int, float, float, float>(&VK_Sprite::drawSprite), nb::arg("x"), nb::arg("y"), nb::arg("scale_x"), nb::arg("scale_y"), nb::arg("rotation"))
768 .def("draw_rect", &VK_Sprite::drawSpriteRect, nb::arg("x"), nb::arg("y"), nb::arg("width"), nb::arg("height"))
769 .def(
770 "update_texture",
771 [](VK_Sprite &value, nb::ndarray<uint8_t, nb::c_contig, nb::device::cpu> pixels, int width, int height, int pitch) { value.updateTexture(pixels.data(), width, height, pitch); },
772 nb::arg("pixels"),
773 nb::arg("width"),
774 nb::arg("height"),
775 nb::arg("pitch") = 0)
776 .def(
777 "update_texture_rgba16",
778 [](VK_Sprite &value, nb::ndarray<uint16_t, nb::c_contig, nb::device::cpu> pixels, int width, int height, int pitch) { value.updateTextureRgba16(pixels.data(), width, height, pitch); },
779 nb::arg("pixels"),
780 nb::arg("width"),
781 nb::arg("height"),
782 nb::arg("pitch") = 0)
783 .def("set_shader_params", &VK_Sprite::setShaderParams, nb::arg("p1") = 0.0F, nb::arg("p2") = 0.0F, nb::arg("p3") = 0.0F, nb::arg("p4") = 0.0F)
784 .def("set_effects_enabled", &VK_Sprite::setEffectsEnabled, nb::arg("enabled"))
785 .def("effects_enabled", &VK_Sprite::getEffectsEnabled)
786 .def("clear_queue", &VK_Sprite::clearQueue)
787 .def("set_vertex_shader_path", &VK_Sprite::setVertexShaderPath, nb::arg("path"))
788 .def("set_fragment_shader_path", &VK_Sprite::setFragmentShaderPath, nb::arg("path"))
789 .def("enable_compute_shader", &VK_Sprite::enableComputeShader, nb::arg("path"), nb::arg("local_size_x"), nb::arg("local_size_y"), nb::arg("local_size_z") = 1)
790 .def("enable_instancing", &VK_Sprite::enableInstancing, nb::arg("max_instances"), nb::arg("vertex_shader_path"), nb::arg("fragment_shader_path"))
791 .def("instancing_enabled", &VK_Sprite::isInstancingEnabled)
792 .def("enable_extended_ubo", &VK_Sprite::enableExtendedUBO)
793 .def("extended_ubo_enabled", &VK_Sprite::isExtendedUBOEnabled)
794 .def("set_mouse_state", &VK_Sprite::setMouseState, nb::arg("x"), nb::arg("y"), nb::arg("pressed"), nb::arg("reserved") = 0.0F)
795 .def("set_uniform0", &VK_Sprite::setUniform0)
796 .def("set_uniform1", &VK_Sprite::setUniform1)
797 .def("set_uniform2", &VK_Sprite::setUniform2)
798 .def("set_uniform3", &VK_Sprite::setUniform3)
799 .def("set_audio_bands", &VK_Sprite::setAudioBands, nb::arg("low"), nb::arg("mid"), nb::arg("high"), nb::arg("reserved") = 0.0F)
800 .def("set_custom_uniforms", &VK_Sprite::setCustomUniforms, nb::arg("values"))
801 .def("enable_history_texture", &VK_Sprite::enableHistoryTexture, nb::arg("width"), nb::arg("height"), nb::arg("layers"))
802 .def("enable_history_texture_rgba16", &VK_Sprite::enableHistoryTextureRgba16Float, nb::arg("width"), nb::arg("height"), nb::arg("layers"))
803 .def("share_history_texture", &VK_Sprite::shareHistoryTexture, nb::arg("source"))
804 .def("enable_spectrum_texture", &VK_Sprite::enableSpectrumTexture, nb::arg("bins"))
805 .def("enable_spectrum_history_texture", &VK_Sprite::enableSpectrumHistoryTexture, nb::arg("bins"), nb::arg("layers"))
806 .def_prop_ro("width", &VK_Sprite::getWidth)
807 .def_prop_ro("height", &VK_Sprite::getHeight)
808 .def_prop_ro("has_own_pipeline", &VK_Sprite::hasOwnPipeline);
809
810 auto sprite3d = nb::class_<VK_Sprite3D>(module, "Sprite3D");
811 sprite3d.def(nb::init<>())
812 .def("load", nb::overload_cast<VK_Window *, const std::string &, const std::string &, const std::string &>(&VK_Sprite3D::load), nb::arg("window"), nb::arg("path"), nb::arg("vertex_shader_path") = "", nb::arg("fragment_shader_path") = "")
813 .def(
814 "update_camera",
815 [](VK_Sprite3D &value, uint32_t image_index, const std::array<float, 16> &view, const std::array<float, 16> &projection) { value.updateCamera(image_index, matrix_from_rows(view), matrix_from_rows(projection)); },
816 nb::arg("image_index"),
817 nb::arg("view"),
818 nb::arg("projection"))
819 .def(
820 "draw",
821 [](VK_Sprite3D &value, const std::array<float, 3> &position, const std::array<float, 2> &size, const std::array<float, 4> &color, float rotation) { value.drawSprite(glm::vec3{position[0], position[1], position[2]}, glm::vec2{size[0], size[1]}, vector4(color), rotation); },
822 nb::arg("position"),
823 nb::arg("size"),
824 nb::arg("color") = std::array<float, 4>{1.0F, 1.0F, 1.0F, 1.0F},
825 nb::arg("rotation") = 0.0F)
826 .def(
827 "render",
828 [](VK_Sprite3D &value, nb::capsule command_buffer, uint32_t image_index) {
829 auto command = static_cast<VkCommandBuffer>(command_buffer.data());
830 if (command == VK_NULL_HANDLE)
831 throw nb::value_error("command_buffer capsule must contain a VkCommandBuffer pointer");
832 value.render(command, image_index);
833 },
834 nb::arg("command_buffer"),
835 nb::arg("image_index"))
836 .def("clear_queue", &VK_Sprite3D::clearQueue)
837 .def("set_depth_test_enabled", &VK_Sprite3D::setDepthTestEnabled, nb::arg("enabled"))
838 .def("set_depth_write_enabled", &VK_Sprite3D::setDepthWriteEnabled, nb::arg("enabled"))
839 .def("set_alpha_discard_threshold", &VK_Sprite3D::setAlphaDiscardThreshold, nb::arg("threshold"))
840 .def("resize", &VK_Sprite3D::resize, nb::arg("window"))
841 .def("cleanup", &VK_Sprite3D::cleanup)
842 .def_prop_ro("loaded", &VK_Sprite3D::loaded)
843 .def_prop_ro("width", &VK_Sprite3D::getWidth)
844 .def_prop_ro("height", &VK_Sprite3D::getHeight);
845
846 nb::enum_<ShaderStage>(module, "ShaderStage").value("unknown", ShaderStage::Unknown).value("vertex", ShaderStage::Vertex).value("fragment", ShaderStage::Fragment).value("compute", ShaderStage::Compute);
847
848 nb::enum_<VK_Window::PresentModePreference>(module, "PresentModePreference").value("low_latency", VK_Window::PresentModePreference::LowLatency).value("vsync", VK_Window::PresentModePreference::Vsync);
849
850 nb::enum_<VK_Window::RuntimeMode>(module, "RuntimeMode").value("windowed", VK_Window::RuntimeMode::Windowed).value("headless", VK_Window::RuntimeMode::Headless);
851
852 nb::class_<VulkanContext>(module, "VulkanContext").def_prop_ro("device", [](const VulkanContext &context) { return reinterpret_cast<uintptr_t>(context.device); }).def_prop_ro("physical_device", [](const VulkanContext &context) { return reinterpret_cast<uintptr_t>(context.physical_device); }).def_prop_ro("graphics_queue", [](const VulkanContext &context) { return reinterpret_cast<uintptr_t>(context.graphics_queue); }).def_prop_ro("command_pool", [](const VulkanContext &context) { return reinterpret_cast<uintptr_t>(context.command_pool); });
853
854 nb::class_<VK_Window::PostProcessingEffect>(module, "PostProcessingEffect").def(nb::init<>()).def_rw("fragment_shader_path", &VK_Window::PostProcessingEffect::fragmentShaderPath).def_rw("params", &VK_Window::PostProcessingEffect::params).def_rw("time_enabled", &VK_Window::PostProcessingEffect::timeEnabled).def_rw("spectrum_bin_count", &VK_Window::PostProcessingEffect::spectrumBinCount).def_rw("spectrum_history_layer_count", &VK_Window::PostProcessingEffect::spectrumHistoryLayerCount).def_rw("stage", &VK_Window::PostProcessingEffect::stage).def_rw("history_source", &VK_Window::PostProcessingEffect::historySource);
855
856 nb::class_<VK_Window, PythonWindow>(module, "Window")
857 .def(nb::init<>())
858 .def(nb::init<const std::string &, int, int, bool, bool, VK_Window::PresentModePreference, VK_Window::RuntimeMode>(), nb::arg("title"), nb::arg("width"), nb::arg("height"), nb::arg("fullscreen") = false, nb::arg("validation") = true, nb::arg("present_mode") = VK_Window::PresentModePreference::LowLatency, nb::arg("runtime_mode") = VK_Window::RuntimeMode::Windowed)
859 .def(nb::init<const std::string &, int, int, bool, bool, bool>(), nb::arg("title"), nb::arg("width"), nb::arg("height"), nb::arg("fullscreen"), nb::arg("validation"), nb::arg("enable_vsync"))
860 .def("release", &VK_Window::release)
861 .def("wait_idle", [](VK_Window &window) {
862 const VkDevice device = window.getDevice();
863 if (device == VK_NULL_HANDLE)
864 return;
865
866 const VkResult result = vkDeviceWaitIdle(device);
867 if (result != VK_SUCCESS)
868 throw std::runtime_error("vkDeviceWaitIdle failed");
869 })
870 .def("request_exit", [](VK_Window &window) {
871 auto *python_window = dynamic_cast<PythonWindow *>(&window);
872 if (python_window == nullptr)
873 throw nb::type_error("request_exit requires a Python subclass of Window");
874 python_window->request_exit();
875 })
876 .def("init_vulkan", &VK_Window::initVulkan, nb::arg("validation") = true)
877 .def(
878 "event",
879 [](VK_Window &window, nb::capsule event) {
880 auto *sdl_event = static_cast<SDL_Event *>(event.data());
881 if (sdl_event == nullptr)
882 throw nb::value_error("event capsule must contain an SDL_Event pointer");
883 window.event(*sdl_event);
884 },
885 nb::arg("event"))
886 .def("loop", &VK_Window::loop)
887 .def("render", &VK_Window::render)
888 .def("on_swapchain_recreated", [](VK_Window &) {})
889 .def("on_frame_readback_scheduled", [](VK_Window &) {})
890 .def("on_frame_readback", [](VK_Window &, nb::ndarray<nb::numpy, std::uint8_t>, uint32_t, uint32_t) {}, nb::arg("pixels"), nb::arg("width"), nb::arg("height"))
891 .def(
892 "flush_frame_readbacks",
893 [](VK_Window &window) {
894 auto *python_window = dynamic_cast<PythonWindow *>(&window);
895 if (python_window == nullptr)
896 throw nb::type_error("flush_frame_readbacks requires a Python subclass of Window");
897 python_window->flush_frame_readbacks();
898 })
899 .def("on_record_custom_rendering", [](VK_Window &, nb::capsule, uint32_t) {}, nb::arg("command_buffer"), nb::arg("image_index"))
900 .def(
901 "on_prepare_frame_rendering",
902 [](VK_Window &window, nb::capsule command_buffer, uint32_t image_index) {
903 auto command_buffer_handle = static_cast<VkCommandBuffer>(command_buffer.data());
904 if (command_buffer_handle == VK_NULL_HANDLE)
905 throw nb::value_error("command_buffer capsule must contain a VkCommandBuffer pointer");
906 window.onPrepareFrameRendering(command_buffer_handle, image_index);
907 },
908 nb::arg("command_buffer"),
909 nb::arg("image_index"))
910 .def("proc", &VK_Window::proc)
911 .def("save_snapshot", &VK_Window::saveSnapshot, nb::arg("path"))
912 .def("set_font", &VK_Window::setFont, nb::arg("path"), nb::arg("size") = 24)
913 .def("set_preview_font", &VK_Window::setPreviewFont, nb::arg("path"), nb::arg("size") = 24)
914 .def("print_text", nb::overload_cast<const std::string &, int, int, const SDL_Color &>(&VK_Window::printText), nb::arg("text"), nb::arg("x"), nb::arg("y"), nb::arg("color"))
915 .def("print_text", nb::overload_cast<const std::string &, int, int, const SDL_Color &, const Font &>(&VK_Window::printText), nb::arg("text"), nb::arg("x"), nb::arg("y"), nb::arg("color"), nb::arg("font"))
916 .def("print_preview_text", nb::overload_cast<const std::string &, int, int, const SDL_Color &>(&VK_Window::printPreviewText), nb::arg("text"), nb::arg("x"), nb::arg("y"), nb::arg("color"))
917 .def("print_preview_text", nb::overload_cast<const std::string &, int, int, const SDL_Color &, const Font &>(&VK_Window::printPreviewText), nb::arg("text"), nb::arg("x"), nb::arg("y"), nb::arg("color"), nb::arg("font"))
918 .def("clear_text_queue", &VK_Window::clearTextQueue)
919 .def("set_clear_color", &VK_Window::setClearColor, nb::arg("red"), nb::arg("green"), nb::arg("blue"), nb::arg("alpha") = 1.0F)
920 .def("set_enable_screenshot", &VK_Window::setEnableScreenshot, nb::arg("enabled"))
921 .def("screenshot_enabled", &VK_Window::screenshotEnabled)
922 .def("set_frame_readback_enabled", &VK_Window::setFrameReadbackEnabled, nb::arg("enabled"))
923 .def("set_frame_readback_rgba16_enabled", &VK_Window::setFrameReadbackRgba16Enabled, nb::arg("enabled"))
924 .def("set_hdr_render_intermediates_enabled", &VK_Window::setHdrRenderIntermediatesEnabled, nb::arg("enabled"))
925 .def("set_render_extent", &VK_Window::setRenderExtent, nb::arg("width"), nb::arg("height"))
926 .def("headless", &VK_Window::headless)
927 .def("ensure_render_resources", &VK_Window::ensureRenderResources)
928 .def("trim_memory", &VK_Window::trimMemory)
929 .def(
930 "get_text_dimensions",
931 [](VK_Window &window, const std::string &text) -> nb::object {
932 int width = 0;
933 int height = 0;
934 if (!window.getTextDimensions(text, width, height))
935 return nb::none();
936 return nb::cast(nb::make_tuple(width, height));
937 },
938 nb::arg("text"))
939 .def(
940 "get_text_dimensions",
941 [](VK_Window &window, const std::string &text, const Font &font) -> nb::object {
942 int width = 0;
943 int height = 0;
944 if (!window.getTextDimensions(text, width, height, font))
945 return nb::none();
946 return nb::cast(nb::make_tuple(width, height));
947 },
948 nb::arg("text"),
949 nb::arg("font"))
950 .def(
951 "create_sprite",
952 [](VK_Window &window, const std::string &path, const std::string &vertex_shader_path, const std::string &fragment_shader_path) { return window.createSprite(path, vertex_shader_path, fragment_shader_path); },
953 nb::arg("path"),
954 nb::arg("vertex_shader_path") = "",
955 nb::arg("fragment_shader_path") = "",
956 nb::rv_policy::reference_internal)
957 .def(
958 "create_sprite_from_surface",
959 [](VK_Window &window, nb::capsule surface, const std::string &vertex_shader_path, const std::string &fragment_shader_path) {
960 auto *sdl_surface = static_cast<SDL_Surface *>(surface.data());
961 if (sdl_surface == nullptr)
962 throw nb::value_error("surface capsule must contain an SDL_Surface pointer");
963 return window.createSprite(sdl_surface, vertex_shader_path, fragment_shader_path);
964 },
965 nb::arg("surface"),
966 nb::arg("vertex_shader_path") = "",
967 nb::arg("fragment_shader_path") = "",
968 nb::rv_policy::reference_internal)
969 .def(
970 "create_sprite",
971 [](VK_Window &window, int width, int height, const std::string &vertex_shader_path, const std::string &fragment_shader_path, uint32_t spectrum_bin_count) { return window.createSprite(width, height, vertex_shader_path, fragment_shader_path, spectrum_bin_count); },
972 nb::arg("width"),
973 nb::arg("height"),
974 nb::arg("vertex_shader_path") = "",
975 nb::arg("fragment_shader_path") = "",
976 nb::arg("spectrum_bin_count") = 0,
977 nb::rv_policy::reference_internal)
978 .def(
979 "create_sprite_with_spectrum_history",
980 [](VK_Window &window, int width, int height, const std::string &vertex_shader_path, const std::string &fragment_shader_path, uint32_t spectrum_bin_count, uint32_t spectrum_history_layer_count) { return window.createSprite(width, height, vertex_shader_path, fragment_shader_path, spectrum_bin_count, spectrum_history_layer_count); },
981 nb::arg("width"),
982 nb::arg("height"),
983 nb::arg("vertex_shader_path"),
984 nb::arg("fragment_shader_path"),
985 nb::arg("spectrum_bin_count"),
986 nb::arg("spectrum_history_layer_count"),
987 nb::rv_policy::reference_internal)
988 .def("attach_post_processing_shader", &VK_Window::attachPostProcessingShader, nb::arg("fragment_shader_path"), nb::arg("p1") = 0.0F, nb::arg("p2") = 0.0F, nb::arg("p3") = 0.0F, nb::arg("p4") = 0.0F, nb::rv_policy::reference_internal)
989 .def("attach_post_processing_shaders", &VK_Window::attachPostProcessingShaders, nb::arg("effects"), nb::rv_policy::reference_internal)
990 .def("detach_post_processing_shader", &VK_Window::detachPostProcessingShader)
991 .def("set_post_processing_shader_params", nb::overload_cast<float, float, float, float>(&VK_Window::setPostProcessingShaderParams), nb::arg("p1") = 0.0F, nb::arg("p2") = 0.0F, nb::arg("p3") = 0.0F, nb::arg("p4") = 0.0F)
992 .def("set_post_processing_effect_params", nb::overload_cast<size_t, float, float, float, float>(&VK_Window::setPostProcessingShaderParams), nb::arg("effect_index"), nb::arg("p1") = 0.0F, nb::arg("p2") = 0.0F, nb::arg("p3") = 0.0F, nb::arg("p4") = 0.0F)
993 .def("set_post_processing_shader_time_enabled", nb::overload_cast<bool>(&VK_Window::setPostProcessingShaderTimeEnabled), nb::arg("enabled"))
994 .def("set_post_processing_effect_time_enabled", nb::overload_cast<size_t, bool>(&VK_Window::setPostProcessingShaderTimeEnabled), nb::arg("effect_index"), nb::arg("enabled"))
995 .def("enable_post_processing", &VK_Window::enablePostProcessing, nb::arg("sprite"))
996 .def("set_post_processing_enabled", &VK_Window::setPostProcessingEnabled, nb::arg("enabled"))
997 .def("set_post_processing_texture_consumer_enabled", &VK_Window::setPostProcessingTextureConsumerEnabled, nb::arg("enabled"))
998 .def("set_post_processing_present_fragment_shader", &VK_Window::setPostProcessingPresentFragmentShader, nb::arg("path"))
999 .def(
1000 "create_sprite3d",
1001 [](VK_Window &window, const std::string &path, const std::string &vertex_shader_path, const std::string &fragment_shader_path) { return window.createSprite3D(path, vertex_shader_path, fragment_shader_path); },
1002 nb::arg("path"),
1003 nb::arg("vertex_shader_path") = "",
1004 nb::arg("fragment_shader_path") = "",
1005 nb::rv_policy::reference_internal)
1006 .def(
1007 "create_sprite3d_from_surface",
1008 [](VK_Window &window, nb::capsule surface, const std::string &vertex_shader_path, const std::string &fragment_shader_path) {
1009 auto *sdl_surface = static_cast<SDL_Surface *>(surface.data());
1010 if (sdl_surface == nullptr)
1011 throw nb::value_error("surface capsule must contain an SDL_Surface pointer");
1012 return window.createSprite3D(sdl_surface, vertex_shader_path, fragment_shader_path);
1013 },
1014 nb::arg("surface"),
1015 nb::arg("vertex_shader_path") = "",
1016 nb::arg("fragment_shader_path") = "",
1017 nb::rv_policy::reference_internal)
1018 .def("show_cursor", &VK_Window::showCursor, nb::arg("visible"))
1019 .def("validation_enabled", &VK_Window::validationEnabled)
1020 .def("context", &VK_Window::context)
1021 .def_prop_ro("sdl_window", [](const VK_Window &window) { return reinterpret_cast<uintptr_t>(window.getSDLWindow()); })
1022 .def_prop_ro("device", [](const VK_Window &window) { return reinterpret_cast<uintptr_t>(window.getDevice()); })
1023 .def_prop_ro("physical_device", [](const VK_Window &window) { return reinterpret_cast<uintptr_t>(window.getPhysicalDevice()); })
1024 .def_prop_ro("graphics_queue", [](const VK_Window &window) { return reinterpret_cast<uintptr_t>(window.getGraphicsQueue()); })
1025 .def_prop_ro("command_pool", [](const VK_Window &window) { return reinterpret_cast<uintptr_t>(window.getCommandPool()); })
1026 .def_prop_ro("pipeline_cache", [](const VK_Window &window) { return reinterpret_cast<uintptr_t>(window.getPipelineCache()); })
1027 .def_prop_ro("swapchain_format", [](const VK_Window &window) { return static_cast<int>(window.getSwapchainFormat()); })
1028 .def_prop_ro("scene_color_format", [](const VK_Window &window) { return static_cast<int>(window.getSceneColorFormat()); })
1029 .def_prop_ro("depth_format", [](const VK_Window &window) { return static_cast<int>(window.getDepthFormat()); })
1030 .def_prop_ro("swapchain_extent",
1031 [](const VK_Window &window) {
1032 const VkExtent2D extent = window.getSwapchainExtent();
1033 return nb::make_tuple(extent.width, extent.height);
1034 })
1035 .def_prop_ro("render_extent",
1036 [](const VK_Window &window) {
1037 const VkExtent2D extent = window.getRenderExtent();
1038 return nb::make_tuple(extent.width, extent.height);
1039 })
1040 .def_prop_ro("swapchain_image_count", &VK_Window::getSwapchainImageCount);
1041
1042 nb::class_<PythonEvent>(module, "Event").def_prop_ro("type", &PythonEvent::type).def_prop_ro("timestamp", &PythonEvent::timestamp).def_prop_ro("key", &PythonEvent::key).def_prop_ro("scancode", &PythonEvent::scancode).def_prop_ro("modifiers", &PythonEvent::modifiers).def_prop_ro("key_down", &PythonEvent::key_down).def_prop_ro("key_up", &PythonEvent::key_up).def_prop_ro("down", &PythonEvent::down).def_prop_ro("repeat", &PythonEvent::repeat).def_prop_ro("text", &PythonEvent::text).def_prop_ro("mouse_motion", &PythonEvent::mouse_motion).def_prop_ro("mouse_button_down", &PythonEvent::mouse_button_down).def_prop_ro("mouse_button_up", &PythonEvent::mouse_button_up).def_prop_ro("mouse_wheel", &PythonEvent::mouse_wheel).def_prop_ro("x", &PythonEvent::x).def_prop_ro("y", &PythonEvent::y).def_prop_ro("relative_x", &PythonEvent::relative_x).def_prop_ro("relative_y", &PythonEvent::relative_y).def_prop_ro("button", &PythonEvent::button).def_prop_ro("clicks", &PythonEvent::clicks).def_prop_ro("wheel_x", &PythonEvent::wheel_x).def_prop_ro("wheel_y", &PythonEvent::wheel_y).def_prop_ro("wheel_ticks_x", &PythonEvent::wheel_ticks_x).def_prop_ro("wheel_ticks_y", &PythonEvent::wheel_ticks_y).def_prop_ro("wheel_flipped", &PythonEvent::wheel_flipped);
1043
1044 nb::class_<VK_IOWindow, VK_Window, PythonIOWindow>(module, "IOWindow")
1045 .def(nb::init<const std::string &, const std::string &, int, int, bool, bool>(), nb::arg("path"), nb::arg("title"), nb::arg("width"), nb::arg("height"), nb::arg("fullscreen") = false, nb::arg("enable_vsync") = false)
1046 .def("print", &VK_IOWindow::print, nb::arg("text"), nb::arg("color") = SDL_Color{255, 255, 255, 255})
1047 .def("request_exit", &VK_IOWindow::requestExit)
1048 .def("console_proc", &VK_IOWindow::console_proc)
1049 .def(
1050 "console_event",
1051 [](VK_IOWindow &window, nb::capsule event) {
1052 auto *value = static_cast<SDL_Event *>(event.data());
1053 if (value == nullptr)
1054 throw nb::value_error("event capsule must contain an SDL_Event pointer");
1055 window.console_event(*value);
1056 },
1057 nb::arg("event"))
1058 .def_prop_ro("visible", &VK_IOWindow::visible);
1059
1060 module.attr("VK_Window") = module.attr("Window");
1061 module.attr("VK_IOWindow") = module.attr("IOWindow");
1062 }
1063} // namespace mxvk
1064
1065NB_MODULE(mxvk_ext, module) { mxvk::bind_nanobind_module(module); }
void Stop()
Stop the current interval and print its duration.
void Echo(std::string_view name) const
Print the interval recorded by the clock policy.
unsigned long TimePassed() const
Query the time elapsed since the most recent Start().
void Start(std::string_view name)
Start a new timed interval.
PythonBuffer(const mxvk::VulkanContext &context, VkDeviceSize size, VkBufferUsageFlags usage)
void write(nanobind::ndarray< uint8_t, nanobind::c_contig, nanobind::device::cpu > bytes)
PythonBuffer & operator=(const PythonBuffer &)=delete
void proc() override
Execute one processing/update step.
void event(SDL_Event &event) override
Handle one SDL event.
PythonTexture & operator=(const PythonTexture &)=delete
PythonTexture(const mxvk::VulkanContext &context, const std::string &path)
void onSwapchainRecreated() override
Called after swapchain and render resources are recreated.
void proc() override
Execute one processing/update step.
void event(SDL_Event &event) override
Handle one SDL event.
void onFrameReadbackScheduled() override
Called after a presented frame has scheduled a readback.
void onFrameReadback(std::vector< std::uint8_t > &rgba_pixels, uint32_t width, uint32_t height) override
Receive one tightly packed RGBA8 copy of the rendered frame.
void onRecordCustomRendering(VkCommandBuffer command_buffer, uint32_t image_index) override
Optional hook for derived classes to record extra draw commands.
Main Vulkan window wrapper for MXVK.
Definition mxvk.hpp:39
void flushFrameReadbacks()
Wait for and deliver all pending pipelined frame readbacks.
Definition mxvk.cpp:1143
void exit()
Request loop termination.
Definition mxvk.cpp:1378
High-level model wrapper integrated with MXVK dynamic rendering.
INI-style configuration file reader/writer.
In-game command console for MXVK / Vulkan windows.
SDL3 joystick and gamepad RAII wrappers.
OpenCV video-capture integration for the Vulkan backend.
FFmpeg video-file capture with optional CUDA hardware decoding.
JPEG image loading and saving utilities.
Vulkan mesh loader and GPU buffer manager for MXVK.
NB_MODULE(mxvk_ext, module)
PNG image loading and saving utilities via SDL3.
Reusable point-sprite renderer for particle and starfield effects.
Reusable Vulkan buffer, image, upload, and one-shot command helpers.
SDL3_mixer audio subsystem wrapper.
Reusable dynamic-rendering stencil image and fullscreen stencil pipelines.
std::filesystem::path python_module_directory()
std::array< float, 16 > matrix_to_rows(const glm::mat4 &matrix)
glm::mat4 matrix_from_rows(const std::array< float, 16 > &values)
std::array< float, 4 > vector4_array(const glm::vec4 &value)
glm::vec4 vector4(const std::array< float, 4 > &values)
Definition model.py:1
Utilities for loading and saving PNG images.
Definition mxvk.hpp:31
void setDefaultShaderDirectory(const std::string &directory)
void create_texture_from_png(const VulkanContext &context, const std::string &path, TextureResource &texture, VkFormat format=VK_FORMAT_R8G8B8A8_UNORM)
Load a PNG and upload it into a sampled 2D texture.
void bind_nanobind_module(nb::module_ &module)
void unmap_buffer(VkDevice device, BufferResource &buffer)
Unmap a BufferResource if it is currently mapped.
void map_buffer(VkDevice device, BufferResource &buffer)
Persistently map a host-visible buffer allocation.
void create_buffer(const VulkanContext &context, VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, BufferResource &buffer)
Create and bind a Vulkan buffer allocation.
void destroy_buffer(VkDevice device, BufferResource &buffer)
Unmap and destroy a BufferResource.
void destroy_texture(VkDevice device, TextureResource &texture)
Destroy every Vulkan handle owned by a TextureResource.
Owned Vulkan buffer allocation with optional persistent host mapping.
Owned sampled 2D texture resources.
Minimal Vulkan handles required by MXVK resource helpers.