#include "Emulator/Graphics/Window.h" #include "Kyty/Core/Common.h" #include "Kyty/Core/DbgAssert.h" #include "Kyty/Core/SafeDelete.h" #include "Kyty/Core/String.h" #include "Kyty/Core/Subsystems.h" #include "Kyty/Core/Threads.h" #include "Kyty/Core/Timer.h" #include "Kyty/Core/Vector.h" #include "Emulator/Config.h" #include "Emulator/Controller.h" #include "Emulator/Graphics/GraphicContext.h" #include "Emulator/Graphics/GraphicsRender.h" #include "Emulator/Graphics/Utils.h" #include "Emulator/Graphics/VideoOut.h" #include "Emulator/Profiler.h" #include "Emulator/VirtualMemory.h" #include "SDL.h" #include "SDL_error.h" #include "SDL_events.h" #include "SDL_gamecontroller.h" #include "SDL_joystick.h" #include "SDL_keyboard.h" #include "SDL_keycode.h" #include "SDL_mouse.h" #include "SDL_stdinc.h" #include "SDL_thread.h" #include "SDL_touch.h" #include "SDL_video.h" #include "SDL_vulkan.h" #include #include #include #include #include #include // IWYU pragma: no_include #ifdef KYTY_EMU_ENABLED //#define KYTY_ENABLE_BEST_PRACTICES #define KYTY_ENABLE_DEBUG_PRINTF #define KYTY_DBG_INPUT namespace Kyty::Libs::Graphics { constexpr float FPS_AVERAGE_FRAMES = 5.0f; constexpr float FPS_UPDATE_TIME = 0.25f; struct EventKeyboard { bool down; bool up; bool pressed; bool released; bool repeat; int scan_code; int key_code; uint16_t mod; double timestamp_seconds; }; struct EventMouse { bool down; bool up; bool left; bool middle; bool right; bool x1; bool x2; bool touch; bool pressed; bool released; int num_of_clicks; bool wheel; int x; int y; bool motion; int motion_x; int motion_y; double timestamp_seconds; }; struct EventFinger { bool down; bool up; bool motion; int touch_id; int finger_id; float x; float y; float dx; float dy; float pressure; double timestamp_seconds; }; struct EventController { int id; int button; int axis_id; int axis_value; bool down; bool up; bool added; bool removed; bool remapped; bool axis; bool pressed; bool released; double timestamp_seconds; }; enum class DisplayOrientation { Unknown, /* The display orientation can't be determined */ Landscape, /* The display is in landscape mode, with the right side up, relative to portrait mode */ LandscapeFlipped, /* The display is in landscape mode, with the left side up, relative to portrait mode */ Portrait, /* The display is in portrait mode */ PortraitFlipped, /* The display is in portrait mode, upside down */ DisplayEventOrientation = 0xF0 }; struct EventDisplay { DisplayOrientation orientation; }; constexpr uint32_t KYTY_SDL_BUTTON_LMASK = SDL_BUTTON_LMASK; // NOLINT(hicpp-signed-bitwise) constexpr uint32_t KYTY_SDL_BUTTON_MMASK = SDL_BUTTON_MMASK; // NOLINT(hicpp-signed-bitwise) constexpr uint32_t KYTY_SDL_BUTTON_RMASK = SDL_BUTTON_RMASK; // NOLINT(hicpp-signed-bitwise) constexpr uint32_t KYTY_SDL_BUTTON_X1MASK = SDL_BUTTON_X1MASK; // NOLINT(hicpp-signed-bitwise) constexpr uint32_t KYTY_SDL_BUTTON_X2MASK = SDL_BUTTON_X2MASK; // NOLINT(hicpp-signed-bitwise) // struct GraphicContext; struct GameApi { bool (*init)(GameApi* game, const Core::Timer& timer, void* data) = nullptr; bool (*render_and_update)(GameApi* game, const Core::Timer& timer) = nullptr; bool (*close)(GameApi* game) = nullptr; void (*event_quit)(GameApi* game) = nullptr; void (*event_terminate)(GameApi* game) = nullptr; void (*event_keyboard)(GameApi* game, const EventKeyboard* key) = nullptr; void (*event_mouse)(GameApi* game, const EventMouse* mb) = nullptr; void (*event_finger)(GameApi* game, const EventFinger* f) = nullptr; void (*event_controller)(GameApi* game, const EventController* f) = nullptr; void (*event_display)(GameApi* game, const EventDisplay* d) = nullptr; void (*event_low_memory)(GameApi* game) = nullptr; void (*event_will_enter_background)(GameApi* game) = nullptr; void (*event_did_enter_background)(GameApi* game) = nullptr; void (*event_will_enter_foreground)(GameApi* game) = nullptr; void (*event_did_enter_foreground)(GameApi* game) = nullptr; void (*event_resize)(GameApi* game, uint32_t new_width, uint32_t new_height) = nullptr; void (*show_window)(GameApi* game, const Core::Timer& timer) = nullptr; bool (*need_exit)(GameApi* game) = nullptr; bool (*is_paused)(GameApi* game) = nullptr; int (*poll_event)(GameApi* game) = nullptr; int (*wait_event)(GameApi* game) = nullptr; void (*process_event)(GameApi* game, double time_s) = nullptr; void* data1 = nullptr; void* data2 = nullptr; bool m_game_need_exit = {false}; bool m_game_is_paused = {false}; uint32_t m_screen_width = {0}; uint32_t m_screen_height = {0}; double m_current_time_seconds = {0.0}; double m_previous_time_seconds = {0.0}; int m_update_num = {0}; int m_frame_num = {0}; double m_update_time_seconds = {0.0}; double m_current_fps = {0.0}; int m_max_updates_per_frame = {4}; double m_update_fixed_time = 1.0 / 60.0; int m_fps_frames_num = {0}; double m_fps_start_time = {0}; }; struct GameApiPrivateStruct { GameApiPrivateStruct() = default; Core::Mutex mutex; int skip_frames = 0; GraphicContext* ctx = nullptr; }; // void game_main_loop(GameApi* game); GameApi* game_create_api(); void game_delete_api(GameApi* api); int game_poll_event(GameApi* game); int game_wait_event(GameApi* game); void game_process_event(GameApi* game, double time_s); struct VulkanExtensions { bool enable_validation_layers = false; Vector required_extensions; Vector available_extensions; Vector required_layers; Vector available_layers; }; struct SurfaceCapabilities { VkSurfaceCapabilitiesKHR capabilities {}; Vector formats; Vector present_modes; bool format_srgb_bgra32 = false; }; struct WindowContext { GraphicContext graphic_ctx; VulkanSwapchain* swapchain = nullptr; SDL_Window* window = nullptr; bool window_hidden = true; VkSurfaceKHR surface = nullptr; SurfaceCapabilities* surface_capabilities = nullptr; GameApi* game = nullptr; char device_name[VK_MAX_PHYSICAL_DEVICE_NAME_SIZE] = {0}; char processor_name[64] = {0}; Core::Mutex mutex; bool graphic_initialized = false; Core::CondVar graphic_initialized_condvar; }; static WindowContext* g_window_ctx = nullptr; constexpr const char* KYTY_SDL_WINDOW_CAPTION = "Game"; // constexpr uint32_t KYTY_SDL_WINDOW_FLAGS = (static_cast(SDL_WINDOW_HIDDEN) | // static_cast(SDL_WINDOW_OPENGL)); constexpr uint32_t KYTY_SDL_WINDOW_FLAGS = (static_cast(SDL_WINDOW_HIDDEN) | static_cast(SDL_WINDOW_VULKAN)); constexpr int KYTY_SDL_WINDOWPOS_CENTERED = SDL_WINDOWPOS_CENTERED; /*NOLINT(hicpp-signed-bitwise)*/ static int game_sdl_event_filter(void* /*userdata*/, SDL_Event* /*event*/) { // game_api_private_t *p = (game_api_private_t*)userdata; // // if (event->type == SDL_WINDOWEVENT) // { // if (event->window.event == SDL_WINDOWEVENT_RESIZED || event->window.event == SDL_WINDOWEVENT_SIZE_CHANGED) // { // p->mutex.Lock(); // { // //p->resize_events_num++; // //printf("game_sdl_event_filter\n"); // p->skip_frames++; // } // p->mutex.Unlock(); // } // } return 0; } static void CalcFrameTime(GameApi* game, double game_time_s) { game->m_previous_time_seconds = game->m_current_time_seconds; game->m_current_time_seconds = game_time_s; game->m_frame_num++; int fps_model = 1; if (fps_model == 1) { game->m_fps_frames_num++; if (game->m_current_time_seconds - game->m_fps_start_time > FPS_UPDATE_TIME) { game->m_current_fps = static_cast(game->m_fps_frames_num) / (game->m_current_time_seconds - game->m_fps_start_time); game->m_fps_frames_num = 0; game->m_fps_start_time = game->m_current_time_seconds; } } else { game->m_current_fps = (1.0f / (game->m_current_time_seconds - game->m_previous_time_seconds)) * (1.0f / FPS_AVERAGE_FRAMES) + game->m_current_fps * (1.0f - (1.0f / FPS_AVERAGE_FRAMES)); } } static bool Init(GameApi* /*game*/) { return true; } static bool Update(GameApi* /*game*/) { return true; } static bool Render(GameApi* /*game*/) { return true; } static bool Close(GameApi* /*game*/) { return true; } static void SetPause(GameApi* game, bool flag) { printf("Pause: %s\n", flag ? "true" : "false"); game->m_game_is_paused = flag; } static bool RenderAndUpdate(GameApi* game) { static double lag = 0.0; lag += game->m_current_time_seconds - game->m_previous_time_seconds; int num = 0; bool ok = true; while (lag >= game->m_update_fixed_time) { if (num < game->m_max_updates_per_frame) { ok = ok && Update(game); game->m_update_num++; num++; game->m_update_time_seconds = game->m_update_num * game->m_update_fixed_time; } lag -= game->m_update_fixed_time; } ok = ok && Render(game); return ok; } bool game_init(GameApi* game, const Core::Timer& timer, void* data) { EXIT_IF(game == nullptr); EXIT_IF(data == nullptr); EXIT_IF(game->data1 || game->data2); auto* ctx = static_cast(data); EXIT_IF(ctx->screen_width == 0 || ctx->screen_height == 0); auto* pdata = new GameApiPrivateStruct; pdata->ctx = ctx; game->data1 = pdata; game->data2 = new SDL_Event; SDL_AddEventWatch(game_sdl_event_filter, game->data1); game->m_screen_width = ctx->screen_width; game->m_screen_height = ctx->screen_height; // SDL_ShowWindow(ctx->window); CalcFrameTime(game, timer.GetTimeS()); return Init(game); } bool game_render_and_update(GameApi* game, const Core::Timer& /*timer*/) { return RenderAndUpdate(game); } bool game_close(GameApi* game) { EXIT_IF(!game); EXIT_IF(!game->data1 || !game->data2); delete (static_cast(game->data1)); delete (static_cast(game->data2)); return Close(game); } void game_show_window(GameApi* game, const Core::Timer& timer) { EXIT_IF(!game); auto* p = static_cast(game->data1); EXIT_IF(!p); p->mutex.Lock(); { if (p->skip_frames > 0) { p->skip_frames--; printf("skip frame %d\n", p->skip_frames); } else { VideoOut::VideoOutBeginVblank(); if (VideoOut::VideoOutFlipWindow(100000)) { CalcFrameTime(game, timer.GetTimeS()); } VideoOut::VideoOutEndVblank(); } } p->mutex.Unlock(); } void game_event_quit(GameApi* game) { printf("Event: quit\n"); game->m_game_need_exit = true; } void game_event_terminate(GameApi* game) { printf("Event: terminate\n"); game->m_game_need_exit = true; } void game_event_keyboard(GameApi* game, const EventKeyboard* key) { #ifdef KYTY_DBG_INPUT printf("Key: time = %.04f, %s%s, %s%s, %s, scan = %d, key = %d, mod = %04" PRIx16 "\n", key->timestamp_seconds, (key->down ? "down" : ""), (key->up ? "up" : ""), (key->pressed ? "pressed" : ""), (key->released ? "released" : ""), (key->repeat ? "repeat" : ""), key->scan_code, key->key_code, key->mod); #endif #if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS if (key->down && key->key_code == SDLK_ESCAPE) { game->m_game_need_exit = true; } if (key->down && key->key_code == SDLK_SPACE) { SetPause(game, !game->m_game_is_paused); } #endif } void game_event_mouse([[maybe_unused]] GameApi* game, [[maybe_unused]] const EventMouse* mb) { #ifdef KYTY_DBG_INPUT if (mb->wheel) { printf("Mouse wheel: time = %.04f, %s[%d, %d]\n", mb->timestamp_seconds, (mb->touch ? "touch, " : ""), mb->x, mb->y); } else if (mb->motion) { printf("Mouse motion: time = %.04f, %s%s%s%s%s%s, [%d, %d], (%d, %d)\n", mb->timestamp_seconds, (mb->left ? "left" : ""), (mb->middle ? "middle" : ""), (mb->right ? "right" : ""), (mb->x1 ? "x1" : ""), (mb->x2 ? "x2" : ""), (mb->touch ? "_touch" : ""), mb->x, mb->y, mb->motion_x, mb->motion_y); } else { printf("Mouse click: time = %.04f, %d, %s%s%s%s%s%s, %s%s, %s%s, [%d, %d]\n", mb->timestamp_seconds, mb->num_of_clicks, (mb->left ? "left" : ""), (mb->middle ? "middle" : ""), (mb->right ? "right" : ""), (mb->x1 ? "x1" : ""), (mb->x2 ? "x2" : ""), (mb->touch ? "_touch" : ""), (mb->down ? "down" : ""), (mb->up ? "up" : ""), (mb->pressed ? "pressed" : ""), (mb->released ? "released" : ""), mb->x, mb->y); } #endif } void game_event_finger([[maybe_unused]] GameApi* game, [[maybe_unused]] const EventFinger* f) { #ifdef KYTY_DBG_INPUT if (f->motion) { printf("Finger motion: time = %.04f, %d, %d, (x,y) = [%f, %f], (dx,dy) = [%f, %f], pressure = %f\n", f->timestamp_seconds, f->touch_id, f->finger_id, f->x, f->y, f->dx, f->dy, f->pressure); } else { printf("Finger press: time = %.04f, %d, %d, %s%s, (x,y) = [%f, %f], (dx,dy) = [%f, %f], pressure = %f\n", f->timestamp_seconds, f->touch_id, f->finger_id, (f->down ? "down" : ""), (f->up ? "up" : ""), f->x, f->y, f->dx, f->dy, f->pressure); } #endif } // static int controller_get_axis(int min, int max, int value) //{ // int v = (255 * (value - min)) / (max - min); // return (v < 0 ? 0 : (v > 255 ? 255 : v)); //} void game_event_controller([[maybe_unused]] GameApi* game, [[maybe_unused]] const EventController* f) { EXIT_NOT_IMPLEMENTED(f->remapped); #ifdef KYTY_DBG_INPUT if (f->added || f->removed) { printf("Controller %s: %d, time = %.04f\n", (f->added ? "added" : "removed"), f->id, f->timestamp_seconds); } else if (f->axis) { printf("Controller axis: %d, axis = %d, value = %d, time = %.04f\n", f->id, f->axis_id, f->axis_value, f->timestamp_seconds); } else { printf("Controller button: " "%d, %s%s, %s%s, button = %d, time = %.04f\n", f->id, (f->down ? "down" : ""), (f->up ? "up" : ""), (f->pressed ? "pressed" : ""), (f->released ? "released" : ""), f->button, f->timestamp_seconds); } #endif if (f->added) { auto* pad = SDL_GameControllerOpen(f->id); EXIT_NOT_IMPLEMENTED(pad == nullptr); int id = SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(pad)); Controller::ControllerConnect(id); } if (f->removed) { Controller::ControllerDisconnect(f->id); SDL_GameControllerClose(SDL_GameControllerFromInstanceID(f->id)); } if (f->down || f->up) { uint32_t button = 0; switch (f->button) { case SDL_CONTROLLER_BUTTON_A: button = Controller::PAD_BUTTON_CROSS; break; case SDL_CONTROLLER_BUTTON_B: button = Controller::PAD_BUTTON_CIRCLE; break; case SDL_CONTROLLER_BUTTON_X: button = Controller::PAD_BUTTON_SQUARE; break; case SDL_CONTROLLER_BUTTON_Y: button = Controller::PAD_BUTTON_TRIANGLE; break; case SDL_CONTROLLER_BUTTON_BACK: button = Controller::PAD_BUTTON_TOUCH_PAD; break; case SDL_CONTROLLER_BUTTON_GUIDE: break; case SDL_CONTROLLER_BUTTON_START: button = Controller::PAD_BUTTON_OPTIONS; break; case SDL_CONTROLLER_BUTTON_LEFTSTICK: button = Controller::PAD_BUTTON_L3; break; case SDL_CONTROLLER_BUTTON_RIGHTSTICK: button = Controller::PAD_BUTTON_R3; break; case SDL_CONTROLLER_BUTTON_LEFTSHOULDER: button = Controller::PAD_BUTTON_L1; break; case SDL_CONTROLLER_BUTTON_RIGHTSHOULDER: button = Controller::PAD_BUTTON_R1; break; case SDL_CONTROLLER_BUTTON_DPAD_UP: button = Controller::PAD_BUTTON_UP; break; case SDL_CONTROLLER_BUTTON_DPAD_DOWN: button = Controller::PAD_BUTTON_DOWN; break; case SDL_CONTROLLER_BUTTON_DPAD_LEFT: button = Controller::PAD_BUTTON_LEFT; break; case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: button = Controller::PAD_BUTTON_RIGHT; break; default: break; } if (button != 0) { Controller::ControllerButton(f->id, button, f->down); } } if (f->axis) { int value = -1; if (f->axis_id == SDL_CONTROLLER_AXIS_TRIGGERLEFT || f->axis_id == SDL_CONTROLLER_AXIS_TRIGGERRIGHT) { value = Controller::controller_get_axis(0, SDL_JOYSTICK_AXIS_MAX, f->axis_value); } else { value = Controller::controller_get_axis(SDL_JOYSTICK_AXIS_MIN, SDL_JOYSTICK_AXIS_MAX, f->axis_value); } Controller::Axis axis = Controller::Axis::AxisMax; switch (f->axis_id) { case SDL_CONTROLLER_AXIS_LEFTX: axis = Controller::Axis::LeftX; break; case SDL_CONTROLLER_AXIS_LEFTY: axis = Controller::Axis::LeftY; break; case SDL_CONTROLLER_AXIS_RIGHTX: axis = Controller::Axis::RightX; break; case SDL_CONTROLLER_AXIS_RIGHTY: axis = Controller::Axis::RightY; break; case SDL_CONTROLLER_AXIS_TRIGGERLEFT: axis = Controller::Axis::TriggerLeft; break; case SDL_CONTROLLER_AXIS_TRIGGERRIGHT: axis = Controller::Axis::TriggerRight; break; } if (axis != Controller::Axis::AxisMax) { Controller::ControllerAxis(f->id, axis, value); } } } void game_event_display([[maybe_unused]] GameApi* game, [[maybe_unused]] const EventDisplay* d) { auto* p = static_cast(game->data1); auto* ctx = static_cast(p->ctx); p->mutex.Lock(); game->m_screen_width = ctx->screen_width; game->m_screen_height = ctx->screen_height; p->mutex.Unlock(); } void game_event_low_memory(GameApi* /*game*/) { printf("Event: low_memory\n"); } void game_event_will_enter_background(GameApi* game) { printf("Event: will_enter_background\n"); SetPause(game, true); } void game_event_did_enter_background(GameApi* /*game*/) { printf("Event: did_enter_background\n"); } void game_event_will_enter_foreground(GameApi* /*game*/) { printf("Event: will_enter_foreground\n"); } void game_event_did_enter_foreground(GameApi* game) { printf("Event: did_enter_foreground\n"); SetPause(game, false); } bool game_need_exit(GameApi* game) { return game->m_game_need_exit; } bool game_is_paused(GameApi* game) { return game->m_game_is_paused; } void game_event_resize(GameApi* game, uint32_t new_width, uint32_t new_height) { EXIT_IF(new_width == 0 || new_height == 0); EXIT_IF(!game); auto* p = static_cast(game->data1); EXIT_IF(p == nullptr); auto* ctx = static_cast(p->ctx); EXIT_IF(ctx == nullptr); p->mutex.Lock(); { p->skip_frames++; ctx->screen_width = new_width; ctx->screen_height = new_height; game->m_screen_width = ctx->screen_width; game->m_screen_height = ctx->screen_height; } p->mutex.Unlock(); } static void process_window_event(GameApi* game, SDL_WindowEvent window) { switch (window.event) { case SDL_WINDOWEVENT_SHOWN: printf("Window %" PRIu32 " shown\n", window.windowID); break; case SDL_WINDOWEVENT_HIDDEN: printf("Window %" PRIu32 " hidden\n", window.windowID); break; case SDL_WINDOWEVENT_EXPOSED: printf("Window %" PRIu32 " exposed\n", window.windowID); break; case SDL_WINDOWEVENT_MOVED: printf("Window %" PRIu32 " moved to %" PRId32 ",%" PRId32 "\n", window.windowID, window.data1, window.data2); break; case SDL_WINDOWEVENT_RESIZED: printf("Window %" PRIu32 " resized to %" PRId32 "x%" PRId32 "\n", window.windowID, window.data1, window.data2); if (game->event_resize != nullptr) { printf("m: %d\n", static_cast(SDL_ThreadID())); // dbg_check(); game->event_resize(game, window.data1, window.data2); } break; case SDL_WINDOWEVENT_SIZE_CHANGED: printf("Window %" PRIu32 " size changed to %" PRId32 "x%" PRId32 "\n", window.windowID, window.data1, window.data2); if (game->event_resize != nullptr) { printf("m: %d\n", static_cast(SDL_ThreadID())); // dbg_check(); game->event_resize(game, window.data1, window.data2); } break; case SDL_WINDOWEVENT_MINIMIZED: printf("Window %" PRIu32 " minimized\n", window.windowID); break; case SDL_WINDOWEVENT_MAXIMIZED: printf("Window %" PRIu32 " maximized\n", window.windowID); break; case SDL_WINDOWEVENT_RESTORED: printf("Window %" PRIu32 " restored\n", window.windowID); break; case SDL_WINDOWEVENT_ENTER: printf("Mouse entered window %" PRIu32 "\n", window.windowID); break; case SDL_WINDOWEVENT_LEAVE: printf("Mouse left window %" PRIu32 "\n", window.windowID); break; case SDL_WINDOWEVENT_FOCUS_GAINED: printf("Window %" PRIu32 " gained keyboard focus\n", window.windowID); break; case SDL_WINDOWEVENT_FOCUS_LOST: printf("Window %" PRIu32 " lost keyboard focus\n", window.windowID); break; case SDL_WINDOWEVENT_CLOSE: printf("Window %" PRIu32 " closed\n", window.windowID); break; default: printf("Window %" PRIu32 " got unknown event %" PRIu8 "\n", window.windowID, window.event); break; } } static void process_display_event(GameApi* game, SDL_DisplayEvent display) { bool sdl = false; switch (display.event) { case SDL_DISPLAYEVENT_ORIENTATION: sdl = true; // @suppress("No break at end of case") case static_cast(DisplayOrientation::DisplayEventOrientation): { printf("Display %" PRIu32 "[%s] changed orientation to %d - ", display.display, sdl ? "SDL" : "Kyty", static_cast(display.data1)); EventDisplay d {}; d.orientation = DisplayOrientation::Unknown; switch (display.data1) { case SDL_ORIENTATION_UNKNOWN: printf("UNKNOWN\n"); break; case SDL_ORIENTATION_LANDSCAPE: printf("LANDSCAPE\n"); d.orientation = DisplayOrientation::Landscape; break; case SDL_ORIENTATION_LANDSCAPE_FLIPPED: printf("LANDSCAPE_FLIPPED\n"); d.orientation = DisplayOrientation::LandscapeFlipped; break; case SDL_ORIENTATION_PORTRAIT: printf("PORTRAIT\n"); d.orientation = DisplayOrientation::Portrait; break; case SDL_ORIENTATION_PORTRAIT_FLIPPED: d.orientation = DisplayOrientation::PortraitFlipped; printf("PORTRAIT_FLIPPED\n"); break; default: printf("???\n"); } if (!sdl) { if (game->event_display != nullptr) { // dbg_check(); game->event_display(game, &d); } } break; } default: printf("Display %" PRIu32 " got unknown event 0x%" PRIx8 "\n", display.display, display.event); break; } } int game_poll_event(GameApi* game) { EXIT_IF(!game); auto* event = static_cast(game->data2); EXIT_IF(!event); // SDL_JoystickUpdate(); return SDL_PollEvent(event); } int game_wait_event(GameApi* game) { EXIT_IF(!game); auto* event = static_cast(game->data2); EXIT_IF(!event); return SDL_WaitEvent(event); } void game_process_event(GameApi* game, double time_s) { EXIT_IF(!game); auto* event = static_cast(game->data2); EXIT_IF(!event); // printf("Event: 0x%04" PRIx32 "\n", event.type); EXIT_IF(SDL_GetEventState(SDL_DISPLAYEVENT) != SDL_ENABLE); switch (event->type) { case SDL_QUIT: if (game->event_quit != nullptr) { game->event_quit(game); } break; case SDL_APP_TERMINATING: if (game->event_terminate != nullptr) { game->event_terminate(game); } break; case SDL_APP_LOWMEMORY: if (game->event_low_memory != nullptr) { game->event_low_memory(game); } break; case SDL_APP_WILLENTERBACKGROUND: if (game->event_will_enter_background != nullptr) { game->event_will_enter_background(game); } break; case SDL_APP_DIDENTERBACKGROUND: if (game->event_did_enter_background != nullptr) { game->event_did_enter_background(game); } break; case SDL_APP_WILLENTERFOREGROUND: if (game->event_will_enter_foreground != nullptr) { game->event_will_enter_foreground(game); } break; case SDL_APP_DIDENTERFOREGROUND: if (game->event_did_enter_foreground != nullptr) { game->event_did_enter_foreground(game); } break; case SDL_KEYDOWN: case SDL_KEYUP: { EventKeyboard key {}; key.down = (event->type == SDL_KEYDOWN); key.up = (event->type == SDL_KEYUP); key.pressed = (event->key.state == SDL_PRESSED); key.released = (event->key.state == SDL_RELEASED); key.repeat = (event->key.repeat != 0u); key.scan_code = event->key.keysym.scancode; key.key_code = event->key.keysym.sym; key.mod = event->key.keysym.mod; key.timestamp_seconds = time_s; if (game->event_keyboard != nullptr) { game->event_keyboard(game, &key); } break; } case SDL_WINDOWEVENT: process_window_event(game, event->window); break; case SDL_DISPLAYEVENT: process_display_event(game, event->display); break; case SDL_MOUSEBUTTONDOWN: case SDL_MOUSEBUTTONUP: { EventMouse mb {}; // printf("event.button.which = %" PRIu32"\n", event.button.which); mb.down = (event->button.type == SDL_MOUSEBUTTONDOWN); mb.up = (event->button.type == SDL_MOUSEBUTTONUP); mb.left = (event->button.button == SDL_BUTTON_LEFT); mb.middle = (event->button.button == SDL_BUTTON_MIDDLE); mb.right = (event->button.button == SDL_BUTTON_RIGHT); mb.x1 = (event->button.button == SDL_BUTTON_X1); mb.x2 = (event->button.button == SDL_BUTTON_X2); mb.touch = (event->button.which == SDL_TOUCH_MOUSEID); mb.pressed = (event->button.state == SDL_PRESSED); mb.released = (event->button.state == SDL_RELEASED); mb.num_of_clicks = event->button.clicks; mb.wheel = false; mb.x = event->button.x; mb.y = event->button.y; mb.motion = false; mb.motion_x = 0; mb.motion_y = 0; mb.timestamp_seconds = time_s; if (game->event_mouse != nullptr) { game->event_mouse(game, &mb); } break; } case SDL_MOUSEWHEEL: { EventMouse mb {}; mb.down = false; mb.up = false; mb.left = false; mb.middle = false; mb.right = false; mb.x1 = false; mb.x2 = false; mb.touch = (event->wheel.which == SDL_TOUCH_MOUSEID); mb.pressed = false; mb.released = false; mb.num_of_clicks = 0; mb.wheel = true; mb.x = event->wheel.x; mb.y = event->wheel.y; mb.motion = false; mb.motion_x = 0; mb.motion_y = 0; mb.timestamp_seconds = time_s; if (game->event_mouse != nullptr) { game->event_mouse(game, &mb); } break; } case SDL_MOUSEMOTION: { EventMouse mb {}; mb.down = false; mb.up = false; mb.left = ((event->motion.state & KYTY_SDL_BUTTON_LMASK) != 0u); mb.middle = ((event->motion.state & KYTY_SDL_BUTTON_MMASK) != 0u); mb.right = ((event->motion.state & KYTY_SDL_BUTTON_RMASK) != 0u); mb.x1 = ((event->motion.state & KYTY_SDL_BUTTON_X1MASK) != 0u); mb.x2 = ((event->motion.state & KYTY_SDL_BUTTON_X2MASK) != 0u); mb.touch = (event->motion.which == SDL_TOUCH_MOUSEID); mb.pressed = false; mb.released = false; mb.num_of_clicks = 0; mb.wheel = false; mb.x = event->motion.x; mb.y = event->motion.y; mb.motion = true; mb.motion_x = event->motion.xrel; mb.motion_y = event->motion.yrel; mb.timestamp_seconds = time_s; if (game->event_mouse != nullptr) { game->event_mouse(game, &mb); } break; } case SDL_FINGERMOTION: case SDL_FINGERDOWN: case SDL_FINGERUP: { EventFinger f {}; f.down = (event->tfinger.type == SDL_FINGERDOWN); f.up = (event->tfinger.type == SDL_FINGERUP); f.motion = (event->tfinger.type == SDL_FINGERMOTION); f.finger_id = static_cast(event->tfinger.fingerId); f.touch_id = static_cast(event->tfinger.touchId); f.x = event->tfinger.x; f.y = event->tfinger.y; f.dx = event->tfinger.dx; f.dy = event->tfinger.dy; f.pressure = event->tfinger.pressure; f.timestamp_seconds = time_s; if (game->event_finger != nullptr) { game->event_finger(game, &f); } break; } // case SDL_JOYAXISMOTION: // case SDL_JOYBALLMOTION: // case SDL_JOYHATMOTION: // case SDL_JOYBUTTONDOWN: // case SDL_JOYBUTTONUP: // case SDL_JOYDEVICEADDED: // case SDL_JOYDEVICEREMOVED: // { // EXIT("joystick event: %d\n", static_cast(event->type)); // break; // } case SDL_CONTROLLERAXISMOTION: { EventController c {}; c.id = event->caxis.which; c.button = SDL_CONTROLLER_BUTTON_INVALID; c.axis_id = event->caxis.axis; c.axis_value = event->caxis.value; c.down = false; c.up = false; c.added = false; c.removed = false; c.remapped = false; c.axis = true; c.pressed = false; c.released = false; c.timestamp_seconds = time_s; if (game->event_controller != nullptr) { game->event_controller(game, &c); } break; } case SDL_CONTROLLERBUTTONDOWN: case SDL_CONTROLLERBUTTONUP: { EventController c {}; c.id = event->cbutton.which; c.button = event->cbutton.button; c.axis_id = SDL_CONTROLLER_AXIS_INVALID; c.axis_value = 0; c.down = (event->cbutton.type == SDL_CONTROLLERBUTTONDOWN); c.up = (event->cbutton.type == SDL_CONTROLLERBUTTONUP); c.added = false; c.removed = false; c.remapped = false; c.axis = false; c.pressed = (event->cbutton.state == SDL_PRESSED); c.released = (event->cbutton.state == SDL_RELEASED); c.timestamp_seconds = time_s; if (game->event_controller != nullptr) { game->event_controller(game, &c); } break; } case SDL_CONTROLLERDEVICEADDED: case SDL_CONTROLLERDEVICEREMOVED: case SDL_CONTROLLERDEVICEREMAPPED: { EventController c {}; c.id = event->cdevice.which; c.button = SDL_CONTROLLER_BUTTON_INVALID; c.axis_id = SDL_CONTROLLER_AXIS_INVALID; c.axis_value = 0; c.down = false; c.up = false; c.added = (event->cdevice.type == SDL_CONTROLLERDEVICEADDED); c.removed = (event->cdevice.type == SDL_CONTROLLERDEVICEREMOVED); c.remapped = (event->cdevice.type == SDL_CONTROLLERDEVICEREMAPPED); c.axis = false; c.pressed = false; c.released = false; c.timestamp_seconds = time_s; if (game->event_controller != nullptr) { game->event_controller(game, &c); } break; } } } GameApi* game_create_api() { auto* api = new GameApi; // memset(api, 0, sizeof(GameApi)); api->init = game_init; api->render_and_update = game_render_and_update; api->close = game_close; api->event_quit = game_event_quit; api->event_terminate = game_event_terminate; api->event_keyboard = game_event_keyboard; api->event_mouse = game_event_mouse; api->event_finger = game_event_finger; api->event_controller = game_event_controller; api->event_display = game_event_display; api->event_low_memory = game_event_low_memory; api->event_will_enter_background = game_event_will_enter_background; api->event_did_enter_background = game_event_did_enter_background; api->event_will_enter_foreground = game_event_will_enter_foreground; api->event_did_enter_foreground = game_event_did_enter_foreground; api->event_resize = game_event_resize; api->show_window = game_show_window; api->need_exit = game_need_exit; api->is_paused = game_is_paused; api->poll_event = game_poll_event; api->wait_event = game_wait_event; api->process_event = game_process_event; return api; } void game_delete_api(GameApi* api) { Delete(api); } void game_main_loop(GameApi* game, void* data) { bool need_exit = false; Core::Timer timer; timer.Start(); if (!game->init(game, timer, data)) { need_exit = true; } for (;;) { if (need_exit) { break; } if (game->poll_event(game) != 0) { if (game->process_event != nullptr) { game->process_event(game, timer.GetTimeS()); } continue; } if (game->is_paused(game)) { if (!timer.IsPaused()) { timer.Pause(); } game->wait_event(game); if (game->process_event != nullptr) { game->process_event(game, timer.GetTimeS()); } need_exit = game->need_exit(game); continue; } need_exit = game->need_exit(game); if (game->is_paused(game)) { if (!timer.IsPaused()) { timer.Pause(); } } else { if (timer.IsPaused()) { timer.Resume(); } if (!need_exit) { need_exit = !game->render_and_update(game, timer); } if (!need_exit) { // dbg_inc(); game->show_window(game, timer); } } } game->close(game); } static void WindowCreate(WindowContext* ctx) { EXIT_IF(ctx == nullptr); EXIT_IF(ctx->window != nullptr); EXIT_IF(ctx->graphic_ctx.screen_width == 0); EXIT_IF(ctx->graphic_ctx.screen_height == 0); int width = static_cast(ctx->graphic_ctx.screen_width); int height = static_cast(ctx->graphic_ctx.screen_height); if (SDL_InitSubSystem(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER) < 0) { EXIT("%s\n", SDL_GetError()); } // SDL_JoystickEventState(SDL_ENABLE); // SDL_JoystickOpen(device_index) printf("WindowCreate(): width = %d, height = %d\n", width, height); ctx->window = SDL_CreateWindow(KYTY_SDL_WINDOW_CAPTION, KYTY_SDL_WINDOWPOS_CENTERED, KYTY_SDL_WINDOWPOS_CENTERED, width, height, KYTY_SDL_WINDOW_FLAGS); ctx->window_hidden = true; if (ctx->window == nullptr) { EXIT("%s\n", SDL_GetError()); } SDL_SetWindowResizable(ctx->window, SDL_FALSE); } static void VulkanGetSurfaceCapabilities(VkPhysicalDevice physical_device, VkSurfaceKHR surface, SurfaceCapabilities* r) { vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physical_device, surface, &r->capabilities); uint32_t formats_count = 0; vkGetPhysicalDeviceSurfaceFormatsKHR(physical_device, surface, &formats_count, nullptr); EXIT_NOT_IMPLEMENTED(formats_count == 0); r->formats = Vector(formats_count); // @suppress("Ambiguous problem") vkGetPhysicalDeviceSurfaceFormatsKHR(physical_device, surface, &formats_count, r->formats.GetData()); uint32_t present_modes_count = 0; vkGetPhysicalDeviceSurfacePresentModesKHR(physical_device, surface, &present_modes_count, nullptr); EXIT_NOT_IMPLEMENTED(present_modes_count == 0); r->present_modes = Vector(present_modes_count); // @suppress("Ambiguous problem") vkGetPhysicalDeviceSurfacePresentModesKHR(physical_device, surface, &present_modes_count, r->present_modes.GetData()); r->format_srgb_bgra32 = false; for (const auto& f: r->formats) { if (f.format == VK_FORMAT_B8G8R8A8_SRGB && f.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { r->format_srgb_bgra32 = true; break; } } } static bool CheckFormat(VkPhysicalDevice device, VkFormat format, bool tile, VkFormatFeatureFlags features) { VkFormatProperties format_props {}; vkGetPhysicalDeviceFormatProperties(device, format, &format_props); if (tile) { if ((format_props.optimalTilingFeatures & features) == features) { return true; } } else { if ((format_props.linearTilingFeatures & features) == features) { return true; } } return false; } // NOLINTNEXTLINE(readability-function-cognitive-complexity) static VkPhysicalDevice VulkanFindPhysicalDevice(VkInstance instance, VkSurfaceKHR surface, VkQueueFlags queue_family, uint32_t queue_count, const Vector& device_extensions, SurfaceCapabilities* r /*, uint32_t image_count*/) { EXIT_IF(instance == nullptr); EXIT_IF(surface == nullptr); uint32_t devices_count = 0; vkEnumeratePhysicalDevices(instance, &devices_count, nullptr); EXIT_NOT_IMPLEMENTED(devices_count == 0); Vector devices(devices_count); vkEnumeratePhysicalDevices(instance, &devices_count, devices.GetData()); VkPhysicalDevice best_device = nullptr; for (const auto& device: devices) { bool skip_device = true; VkPhysicalDeviceProperties device_properties {}; VkPhysicalDeviceFeatures2 device_features2 {}; VkPhysicalDeviceColorWriteEnableFeaturesEXT color_write_ext {}; color_write_ext.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_COLOR_WRITE_ENABLE_FEATURES_EXT; color_write_ext.pNext = nullptr; device_features2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; device_features2.pNext = &color_write_ext; vkGetPhysicalDeviceProperties(device, &device_properties); vkGetPhysicalDeviceFeatures2(device, &device_features2); printf("Vulkan device: %s\n", device_properties.deviceName); uint32_t queue_family_count = 0; vkGetPhysicalDeviceQueueFamilyProperties(device, &queue_family_count, nullptr); Vector queue_families(queue_family_count); vkGetPhysicalDeviceQueueFamilyProperties(device, &queue_family_count, queue_families.GetData()); for (auto& f: queue_families) { printf("\tqueue family: %s [count = %u]\n", string_VkQueueFlags(f.queueFlags).c_str(), f.queueCount); } Vector indexes; queue_families.FindAll( queue_family, [queue_count](auto f, auto bit) { return (f.queueFlags & bit) != 0 && f.queueCount >= queue_count; }, &indexes); for (auto index: indexes) { VkBool32 presentation_supported = VK_FALSE; vkGetPhysicalDeviceSurfaceSupportKHR(device, index, surface, &presentation_supported); if (presentation_supported == VK_TRUE) { skip_device = false; break; } } if (color_write_ext.colorWriteEnable != VK_TRUE) { printf("colorWriteEnable is not supported\n"); skip_device = true; } if (device_features2.features.fragmentStoresAndAtomics != VK_TRUE) { printf("fragmentStoresAndAtomics is not supported\n"); skip_device = true; } if (!skip_device) { uint32_t extensions_count = 0; vkEnumerateDeviceExtensionProperties(device, nullptr, &extensions_count, nullptr); EXIT_NOT_IMPLEMENTED(extensions_count == 0); Vector available_extensions(extensions_count); vkEnumerateDeviceExtensionProperties(device, nullptr, &extensions_count, available_extensions.GetData()); for (const char* ext: device_extensions) { if (!available_extensions.Contains(ext, [](auto p, auto ext) { return strcmp(p.extensionName, ext) == 0; })) { skip_device = true; break; } } if (skip_device) { for (const auto& ext: available_extensions) { printf("Vulkan available extension: %s, version = %u\n", ext.extensionName, ext.specVersion); } } } if (!skip_device) { VulkanGetSurfaceCapabilities(device, surface, r); if ((r->capabilities.supportedUsageFlags & VK_IMAGE_USAGE_TRANSFER_DST_BIT) == 0) { printf("Surface cannot be destination of blit\n"); skip_device = true; } } if (!skip_device && !CheckFormat(device, VK_FORMAT_R8G8B8A8_SRGB, false, VK_FORMAT_FEATURE_BLIT_SRC_BIT)) { printf("Format VK_FORMAT_R8G8B8A8_SRGB cannot be used as transfer source\n"); skip_device = true; } if (!skip_device && !CheckFormat(device, VK_FORMAT_D32_SFLOAT, true, VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT)) { printf("Format VK_FORMAT_D32_SFLOAT cannot be used as depth buffer\n"); skip_device = true; } if (!skip_device && !CheckFormat(device, VK_FORMAT_D32_SFLOAT_S8_UINT, true, VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT)) { printf("Format VK_FORMAT_D32_SFLOAT_S8_UINT cannot be used as depth buffer\n"); skip_device = true; } if (!skip_device && !CheckFormat(device, VK_FORMAT_D16_UNORM, true, VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT)) { printf("Format VK_FORMAT_D16_UNORM cannot be used as depth buffer\n"); skip_device = true; } if (!skip_device && !CheckFormat(device, VK_FORMAT_D24_UNORM_S8_UINT, true, VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT)) { printf("Format VK_FORMAT_D24_UNORM_S8_UINT cannot be used as depth buffer\n"); skip_device = true; } if (!skip_device && !CheckFormat(device, VK_FORMAT_BC3_SRGB_BLOCK, true, VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)) { printf("Format VK_FORMAT_BC3_SRGB_BLOCK cannot be used as texture\n"); skip_device = true; } if (!skip_device && !CheckFormat(device, VK_FORMAT_R8G8B8A8_SRGB, true, VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)) { printf("Format VK_FORMAT_R8G8B8A8_SRGB cannot be used as texture\n"); skip_device = true; } if (!skip_device && !CheckFormat(device, VK_FORMAT_R8_UNORM, true, VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)) { printf("Format VK_FORMAT_R8_UNORM cannot be used as texture\n"); skip_device = true; } if (!skip_device && !CheckFormat(device, VK_FORMAT_R8G8B8A8_SRGB, true, VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)) { printf("Format VK_FORMAT_R8G8B8A8_SRGB cannot be used as texture\n"); if (!skip_device && !CheckFormat(device, VK_FORMAT_R8G8B8A8_UNORM, true, VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)) { printf("Format VK_FORMAT_R8G8B8A8_UNORM cannot be used as texture\n"); skip_device = true; } } if (!skip_device && !CheckFormat(device, VK_FORMAT_B8G8R8A8_SRGB, true, VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)) { printf("Format VK_FORMAT_B8G8R8A8_SRGB cannot be used as texture\n"); if (!skip_device && !CheckFormat(device, VK_FORMAT_B8G8R8A8_UNORM, true, VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)) { printf("Format VK_FORMAT_B8G8R8A8_UNORM cannot be used as texture\n"); skip_device = true; } } /*if (!skip_device && !CheckFormat(device, VK_FORMAT_S8_UINT, true, VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT)) { printf("Format VK_FORMAT_S8_UINT cannot be used as depth buffer"); skip_device = true; }*/ if (!skip_device && device_properties.limits.maxSamplerAnisotropy < 16.0f) { printf("maxSamplerAnisotropy < 16.0f"); skip_device = true; } if (skip_device) { continue; } if (best_device == nullptr) { best_device = device; } if (device_properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) { best_device = device; } } return best_device; } static VkDevice VulkanCreateDevice(VkPhysicalDevice physical_device, VkSurfaceKHR surface, const VulkanExtensions* r, VkQueueFlags queue_family, uint32_t queue_count, uint32_t* queue_family_index, const Vector& device_extensions) { EXIT_IF(physical_device == nullptr); EXIT_IF(r == nullptr); EXIT_IF(queue_family_index == nullptr); EXIT_IF(surface == nullptr); Vector queue_priority(queue_count); for (auto& f: queue_priority) { f = 1.0f; } uint32_t queue_family_count = 0; vkGetPhysicalDeviceQueueFamilyProperties(physical_device, &queue_family_count, nullptr); Vector queue_families(queue_family_count); vkGetPhysicalDeviceQueueFamilyProperties(physical_device, &queue_family_count, queue_families.GetData()); Vector indexes; queue_families.FindAll( queue_family, [queue_count](auto f, auto bit) { return (f.queueFlags & bit) != 0 && f.queueCount >= queue_count; }, &indexes); *queue_family_index = queue_families.Size() + 1; for (auto index: indexes) { VkBool32 presentation_supported = VK_FALSE; vkGetPhysicalDeviceSurfaceSupportKHR(physical_device, index, surface, &presentation_supported); if (presentation_supported == VK_TRUE) { *queue_family_index = index; break; } } EXIT_IF(!queue_families.IndexValid(*queue_family_index)); VkDeviceQueueCreateInfo queue_create_info {}; queue_create_info.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; queue_create_info.pNext = nullptr; queue_create_info.flags = 0; queue_create_info.queueFamilyIndex = *queue_family_index; queue_create_info.queueCount = queue_count; queue_create_info.pQueuePriorities = queue_priority.GetDataConst(); VkPhysicalDeviceFeatures device_features {}; device_features.fragmentStoresAndAtomics = VK_TRUE; VkPhysicalDeviceColorWriteEnableFeaturesEXT color_write_ext {}; color_write_ext.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_COLOR_WRITE_ENABLE_FEATURES_EXT; color_write_ext.pNext = nullptr; color_write_ext.colorWriteEnable = VK_TRUE; VkDeviceCreateInfo create_info {}; create_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; create_info.pNext = &color_write_ext; create_info.flags = 0; create_info.pQueueCreateInfos = &queue_create_info; create_info.queueCreateInfoCount = 1; create_info.enabledLayerCount = (r->enable_validation_layers ? r->required_layers.Size() : 0); create_info.ppEnabledLayerNames = (r->enable_validation_layers ? r->required_layers.GetDataConst() : nullptr); create_info.enabledExtensionCount = device_extensions.Size(); create_info.ppEnabledExtensionNames = device_extensions.GetDataConst(); create_info.pEnabledFeatures = &device_features; VkDevice device = nullptr; vkCreateDevice(physical_device, &create_info, nullptr, &device); return device; } static void VulkanGetExtensions(SDL_Window* window, VulkanExtensions* r) { EXIT_IF(window == nullptr); EXIT_IF(r == nullptr); uint32_t required_extensions_count = 0; uint32_t available_extensions_count = 0; uint32_t available_layers_count = 0; auto sdl_result = SDL_Vulkan_GetInstanceExtensions(window, &required_extensions_count, nullptr); EXIT_NOT_IMPLEMENTED(sdl_result == SDL_FALSE); EXIT_NOT_IMPLEMENTED(required_extensions_count == 0); r->required_extensions = Vector(required_extensions_count, false); // @suppress("Ambiguous problem") r->required_extensions.Memset(0); sdl_result = SDL_Vulkan_GetInstanceExtensions(window, &required_extensions_count, r->required_extensions.GetData()); EXIT_NOT_IMPLEMENTED(sdl_result == SDL_FALSE); EXIT_NOT_IMPLEMENTED(required_extensions_count == 0); EXIT_NOT_IMPLEMENTED(required_extensions_count != r->required_extensions.Size()); vkEnumerateInstanceExtensionProperties(nullptr, &available_extensions_count, nullptr); r->available_extensions = Vector(available_extensions_count); // @suppress("Ambiguous problem") r->available_extensions.Memset(0); vkEnumerateInstanceExtensionProperties(nullptr, &available_extensions_count, r->available_extensions.GetData()); EXIT_NOT_IMPLEMENTED(available_extensions_count != r->available_extensions.Size()); r->enable_validation_layers = Config::VulkanValidationEnabled(); if (r->available_extensions.Contains(VK_EXT_DEBUG_UTILS_EXTENSION_NAME, [](auto s, auto l) { return strcmp(s.extensionName, l) == 0; })) { r->required_extensions.Add(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); } else { r->enable_validation_layers = false; } for (const char* ext: r->required_extensions) { printf("Vulkan required extension: %s\n", ext); } for (const auto& ext: r->available_extensions) { printf("Vulkan available extension: %s, version = %u\n", ext.extensionName, ext.specVersion); } vkEnumerateInstanceLayerProperties(&available_layers_count, nullptr); r->available_layers = Vector(available_layers_count); // @suppress("Ambiguous problem") r->available_layers.Memset(0); vkEnumerateInstanceLayerProperties(&available_layers_count, r->available_layers.GetData()); EXIT_NOT_IMPLEMENTED(available_layers_count != r->available_layers.Size()); for (const auto& l: r->available_layers) { printf("Vulkan available layer: %s, specVersion = %u, implVersion = %u, %s\n", l.layerName, l.specVersion, l.implementationVersion, l.description); } r->required_layers = {"VK_LAYER_KHRONOS_validation"}; if (r->enable_validation_layers) { for (const char* l: r->required_layers) { if (!r->available_layers.Contains(l, [](auto s, auto l) { return strcmp(s.layerName, l) == 0; })) { printf("no validation layer: %s\n", l); r->enable_validation_layers = false; break; } } } if (r->enable_validation_layers) { vkEnumerateInstanceExtensionProperties("VK_LAYER_KHRONOS_validation", &available_extensions_count, nullptr); Vector available_extensions(available_extensions_count); vkEnumerateInstanceExtensionProperties("VK_LAYER_KHRONOS_validation", &available_extensions_count, available_extensions.GetData()); for (const auto& ext: available_extensions) { printf("VK_LAYER_KHRONOS_validation available extension: %s, version = %u\n", ext.extensionName, ext.specVersion); } if (available_extensions.Contains("VK_EXT_validation_features", [](auto s, auto l) { return strcmp(s.extensionName, l) == 0; })) { r->required_extensions.Add("VK_EXT_validation_features"); } else { r->enable_validation_layers = false; } } } static VKAPI_ATTR VkBool32 VKAPI_CALL VulkanDebugMessengerCallback(VkDebugUtilsMessageSeverityFlagBitsEXT message_severity, VkDebugUtilsMessageTypeFlagsEXT message_types, const VkDebugUtilsMessengerCallbackDataEXT* callback_data, void* /*user_data*/) { const char* severity_str = nullptr; const char* severity_color = FG_DEFAULT; bool skip = false; bool error = false; bool debug_printf = false; switch (message_severity) { case VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT: severity_str = "V"; severity_color = FG_BRIGHT_WHITE; skip = true; break; case VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT: if ((message_types & VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT) != 0 && Config::SpirvDebugPrintfEnabled() && strcmp(callback_data->pMessageIdName, "UNASSIGNED-DEBUG-PRINTF") == 0) { debug_printf = true; severity_color = FG_BRIGHT_YELLOW; skip = true; } else { severity_str = "I"; severity_color = FG_DEFAULT; } break; case VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT: severity_str = "W"; severity_color = FG_RED; break; case VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT: severity_str = "E"; severity_color = FG_BRIGHT_RED; error = true; break; default: severity_str = "?"; } if (error) { EXIT("%s[Vulkan][%s][%u]: %s%s\n", severity_color, severity_str, static_cast(message_types), callback_data->pMessage, FG_DEFAULT); } if (!skip) { printf("%s[Vulkan][%s][%u]: %s%s\n", severity_color, severity_str, static_cast(message_types), callback_data->pMessage, FG_DEFAULT); } if (debug_printf) { auto strs = String::FromUtf8(callback_data->pMessage).Split(U'|'); if (!strs.IsEmpty()) { printf("%s%s%s\n", severity_color, strs.At(strs.Size() - 1).C_Str(), FG_DEFAULT); } } return VK_FALSE; } static VKAPI_ATTR VkResult VKAPI_CALL VulkanCreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* create_info, const VkAllocationCallbacks* allocator, VkDebugUtilsMessengerEXT* messenger) { EXIT_IF(instance == nullptr); if (auto func = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT")); func != nullptr) { return func(instance, create_info, allocator, messenger); } return VK_ERROR_EXTENSION_NOT_PRESENT; } [[maybe_unused]] static VkSwapchainKHR VulkanCreateSwapchainInternal(VkDevice device, VkSurfaceKHR surface, uint32_t width, uint32_t height, uint32_t image_count, SurfaceCapabilities* r, VkFormat* swapchain_format, VkExtent2D* swapchain_extent, VkImage** swapchain_images, VkImageView** swapchain_image_views, uint32_t* swapchain_images_count) { EXIT_IF(device == nullptr); EXIT_IF(surface == nullptr); EXIT_IF(r == nullptr); EXIT_IF(swapchain_format == nullptr); EXIT_IF(swapchain_extent == nullptr); EXIT_IF(swapchain_images == nullptr); EXIT_IF(swapchain_image_views == nullptr); EXIT_IF(swapchain_images_count == nullptr); EXIT_NOT_IMPLEMENTED(r->formats.IsEmpty()); VkExtent2D extent {}; extent.width = std::clamp(width, r->capabilities.minImageExtent.width, r->capabilities.maxImageExtent.width); extent.height = std::clamp(height, r->capabilities.minImageExtent.height, r->capabilities.maxImageExtent.height); image_count = std::clamp(image_count, r->capabilities.minImageCount, r->capabilities.maxImageCount); VkSwapchainCreateInfoKHR create_info {}; create_info.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; create_info.pNext = nullptr; create_info.flags = 0; create_info.surface = surface; create_info.minImageCount = image_count; create_info.imageFormat = (r->format_srgb_bgra32 ? VK_FORMAT_B8G8R8A8_SRGB : r->formats.At(0).format); create_info.imageColorSpace = (r->format_srgb_bgra32 ? VK_COLOR_SPACE_SRGB_NONLINEAR_KHR : r->formats.At(0).colorSpace); create_info.imageExtent = extent; create_info.imageArrayLayers = 1; create_info.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; create_info.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; create_info.queueFamilyIndexCount = 0; create_info.pQueueFamilyIndices = nullptr; create_info.preTransform = r->capabilities.currentTransform; create_info.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; create_info.presentMode = VK_PRESENT_MODE_FIFO_KHR; create_info.clipped = VK_TRUE; create_info.oldSwapchain = nullptr; *swapchain_format = create_info.imageFormat; *swapchain_extent = extent; VkSwapchainKHR swapchain = nullptr; vkCreateSwapchainKHR(device, &create_info, nullptr, &swapchain); vkGetSwapchainImagesKHR(device, swapchain, swapchain_images_count, nullptr); EXIT_NOT_IMPLEMENTED(*swapchain_images_count == 0); *swapchain_images = new VkImage[*swapchain_images_count]; vkGetSwapchainImagesKHR(device, swapchain, swapchain_images_count, *swapchain_images); *swapchain_image_views = new VkImageView[*swapchain_images_count]; for (uint32_t i = 0; i < *swapchain_images_count; i++) { VkImageViewCreateInfo create_info {}; create_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; create_info.pNext = nullptr; create_info.flags = 0; create_info.image = (*swapchain_images)[i]; create_info.viewType = VK_IMAGE_VIEW_TYPE_2D; create_info.format = *swapchain_format; create_info.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; create_info.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; create_info.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; create_info.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; create_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; create_info.subresourceRange.baseArrayLayer = 0; create_info.subresourceRange.baseMipLevel = 0; create_info.subresourceRange.layerCount = 1; create_info.subresourceRange.levelCount = 1; vkCreateImageView(device, &create_info, nullptr, &((*swapchain_image_views)[i])); } return swapchain; } static void VulkanCreateQueues(GraphicContext* ctx) { EXIT_IF(ctx == nullptr); EXIT_IF(ctx->device == nullptr); EXIT_IF(ctx->queue_family_index == static_cast(-1)); for (int i = 0; i < GraphicContext::QUEUES_NUM; i++) { EXIT_IF(ctx->queue[i] != nullptr); vkGetDeviceQueue(ctx->device, ctx->queue_family_index, i, &ctx->queue[i]); EXIT_NOT_IMPLEMENTED(ctx->queue[i] == nullptr); } } static VulkanSwapchain* VulkanCreateSwapchain(GraphicContext* ctx, uint32_t image_count) { EXIT_IF(g_window_ctx == nullptr); EXIT_IF(ctx == nullptr); EXIT_IF(ctx->screen_width == 0); EXIT_IF(ctx->screen_height == 0); Core::LockGuard lock(g_window_ctx->mutex); auto* s = new VulkanSwapchain; s->swapchain = VulkanCreateSwapchainInternal(ctx->device, g_window_ctx->surface, ctx->screen_width, ctx->screen_height, image_count, g_window_ctx->surface_capabilities, &s->swapchain_format, &s->swapchain_extent, &s->swapchain_images, &s->swapchain_image_views, &s->swapchain_images_count); if (s->swapchain == nullptr) { EXIT("Could not create swapchain"); } s->current_index = static_cast(-1); VkSemaphoreCreateInfo present_complete_info {}; present_complete_info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; present_complete_info.pNext = nullptr; present_complete_info.flags = 0; auto result = vkCreateSemaphore(ctx->device, &present_complete_info, nullptr, &s->present_complete_semaphore); EXIT_NOT_IMPLEMENTED(result != VK_SUCCESS); VkFenceCreateInfo fence_info; fence_info.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; fence_info.pNext = nullptr; fence_info.flags = 0; result = vkCreateFence(ctx->device, &fence_info, nullptr, &s->present_complete_fence); EXIT_NOT_IMPLEMENTED(result != VK_SUCCESS); return s; } static void VulkanCreate(WindowContext* ctx) { EXIT_IF(ctx->window == nullptr); EXIT_IF(ctx->graphic_ctx.instance != nullptr); EXIT_IF(ctx->graphic_ctx.physical_device != nullptr); EXIT_IF(ctx->graphic_ctx.device != nullptr); EXIT_IF(ctx->surface_capabilities != nullptr); VulkanExtensions r; VulkanGetExtensions(ctx->window, &r); VkApplicationInfo app_info {}; app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; app_info.pNext = nullptr; app_info.pApplicationName = "Kyty"; app_info.applicationVersion = 1; app_info.pEngineName = "Kyty"; app_info.engineVersion = 1; app_info.apiVersion = VK_API_VERSION_1_2; // NOLINT VkValidationFeatureDisableEXT disabled_features[] = {}; VkValidationFeatureEnableEXT enabled_features[] = { #ifdef KYTY_ENABLE_BEST_PRACTICES VK_VALIDATION_FEATURE_ENABLE_BEST_PRACTICES_EXT, #endif #ifdef KYTY_ENABLE_DEBUG_PRINTF VK_VALIDATION_FEATURE_ENABLE_DEBUG_PRINTF_EXT, #endif }; uint32_t enabled_features_count = sizeof(enabled_features) / sizeof(VkValidationFeatureEnableEXT); #ifdef KYTY_ENABLE_DEBUG_PRINTF if (!Config::SpirvDebugPrintfEnabled()) { enabled_features_count--; } #endif VkValidationFeaturesEXT validation_features {}; validation_features.sType = VK_STRUCTURE_TYPE_VALIDATION_FEATURES_EXT; validation_features.pNext = nullptr; validation_features.enabledValidationFeatureCount = enabled_features_count; validation_features.pEnabledValidationFeatures = enabled_features; validation_features.disabledValidationFeatureCount = 0; validation_features.pDisabledValidationFeatures = disabled_features; VkDebugUtilsMessengerCreateInfoEXT dbg_create_info {}; dbg_create_info.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; dbg_create_info.pNext = &validation_features; dbg_create_info.flags = 0; dbg_create_info.messageSeverity = static_cast(VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT) | static_cast(VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT) | static_cast(VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) | static_cast(VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT); dbg_create_info.messageType = static_cast(VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT) | static_cast(VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT) | static_cast(VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT); dbg_create_info.pfnUserCallback = VulkanDebugMessengerCallback; dbg_create_info.pUserData = nullptr; VkInstanceCreateInfo inst_info {}; inst_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; inst_info.pNext = (r.enable_validation_layers ? &dbg_create_info : nullptr); inst_info.flags = 0; inst_info.pApplicationInfo = &app_info; inst_info.enabledExtensionCount = r.required_extensions.Size(); inst_info.ppEnabledExtensionNames = r.required_extensions.GetDataConst(); inst_info.enabledLayerCount = (r.enable_validation_layers ? r.required_layers.Size() : 0); inst_info.ppEnabledLayerNames = (r.enable_validation_layers ? r.required_layers.GetDataConst() : nullptr); VkResult result = vkCreateInstance(&inst_info, nullptr, &ctx->graphic_ctx.instance); if (result == VK_ERROR_INCOMPATIBLE_DRIVER) { EXIT("Unable to find a compatible Vulkan Driver"); } else if (result != VK_SUCCESS) { EXIT("Could not create a Vulkan instance (for unknown reasons)"); } if (r.enable_validation_layers) { dbg_create_info.pNext = nullptr; if (VulkanCreateDebugUtilsMessengerEXT(ctx->graphic_ctx.instance, &dbg_create_info, nullptr, &ctx->graphic_ctx.debug_messenger) != VK_SUCCESS) { EXIT("Could not create debug messenger"); } } if (SDL_Vulkan_CreateSurface(ctx->window, ctx->graphic_ctx.instance, &ctx->surface) == SDL_FALSE) { EXIT("Could not create a Vulkan surface"); } Vector device_extensions = {VK_KHR_SWAPCHAIN_EXTENSION_NAME, VK_EXT_DEPTH_CLIP_ENABLE_EXTENSION_NAME, VK_EXT_COLOR_WRITE_ENABLE_EXTENSION_NAME, "VK_KHR_maintenance1"}; #ifdef KYTY_ENABLE_DEBUG_PRINTF if (Config::SpirvDebugPrintfEnabled()) { device_extensions.Add(VK_KHR_SHADER_NON_SEMANTIC_INFO_EXTENSION_NAME); } #endif ctx->surface_capabilities = new SurfaceCapabilities {}; ctx->graphic_ctx.physical_device = VulkanFindPhysicalDevice(ctx->graphic_ctx.instance, ctx->surface, VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT, GraphicContext::QUEUES_NUM, device_extensions, ctx->surface_capabilities); if (ctx->graphic_ctx.physical_device == nullptr) { EXIT("Could not find suitable device"); } VkPhysicalDeviceProperties device_properties {}; vkGetPhysicalDeviceProperties(ctx->graphic_ctx.physical_device, &device_properties); printf("Select device: %s\n", device_properties.deviceName); memcpy(ctx->device_name, device_properties.deviceName, sizeof(ctx->device_name)); memcpy(ctx->processor_name, Loader::GetSystemInfo().ProcessorName.C_Str(), sizeof(ctx->processor_name)); ctx->graphic_ctx.device = VulkanCreateDevice(ctx->graphic_ctx.physical_device, ctx->surface, &r, VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT, GraphicContext::QUEUES_NUM, &ctx->graphic_ctx.queue_family_index, device_extensions); if (ctx->graphic_ctx.device == nullptr) { EXIT("Could not create device"); } VulkanCreateQueues(&ctx->graphic_ctx); ctx->swapchain = VulkanCreateSwapchain(&ctx->graphic_ctx, 2); } void WindowInit(uint32_t width, uint32_t height) { EXIT_NOT_IMPLEMENTED(!Core::Thread::IsMainThread()); EXIT_IF(g_window_ctx != nullptr); g_window_ctx = new WindowContext; g_window_ctx->graphic_ctx.screen_width = width; g_window_ctx->graphic_ctx.screen_height = height; } void WindowWaitForGraphicInitialized() { EXIT_IF(g_window_ctx == nullptr); Core::LockGuard lock(g_window_ctx->mutex); while (!g_window_ctx->graphic_initialized) { g_window_ctx->graphic_initialized_condvar.Wait(&g_window_ctx->mutex); } } void WindowRun() { EXIT_IF(g_window_ctx == nullptr); // KYTY_PROFILER_THREAD("Thread_Window"); EASY_MAIN_THREAD GameApi* api = nullptr; g_window_ctx->mutex.Lock(); { EXIT_IF(g_window_ctx->graphic_initialized); WindowCreate(g_window_ctx); VulkanCreate(g_window_ctx); api = game_create_api(); g_window_ctx->game = api; g_window_ctx->graphic_initialized = true; g_window_ctx->graphic_initialized_condvar.Signal(); } g_window_ctx->mutex.Unlock(); game_main_loop(api, &g_window_ctx->graphic_ctx); // game_delete_api(api); Core::SubsystemsListSingleton::Instance()->ShutdownAll(); std::_Exit(0); } VkSurfaceCapabilitiesKHR* VulkanGetSurfaceCapabilities() { EXIT_IF(g_window_ctx == nullptr); Core::LockGuard lock(g_window_ctx->mutex); // auto* ctx = &g_window_ctx->graphic_ctx; return &g_window_ctx->surface_capabilities->capabilities; } GraphicContext* WindowGetGraphicContext() { EXIT_IF(g_window_ctx == nullptr); Core::LockGuard lock(g_window_ctx->mutex); return &g_window_ctx->graphic_ctx; } void WindowShowFps() { EXIT_IF(g_window_ctx == nullptr); EXIT_IF(g_window_ctx->game == nullptr); auto fps = String::FromPrintf("[%s] [%s], frame: %d, fps: %f", g_window_ctx->device_name, g_window_ctx->processor_name, g_window_ctx->game->m_frame_num, g_window_ctx->game->m_current_fps); SDL_SetWindowTitle(g_window_ctx->window, fps.C_Str()); } void WindowDrawBuffer(VideoOutVulkanImage* image) { KYTY_PROFILER_FUNCTION(); EXIT_IF(image == nullptr); EXIT_IF(g_window_ctx == nullptr); EXIT_IF(g_window_ctx->swapchain == nullptr); if (g_window_ctx->window_hidden) { SDL_ShowWindow(g_window_ctx->window); g_window_ctx->window_hidden = false; } g_window_ctx->swapchain->current_index = static_cast(-1); auto result = vkAcquireNextImageKHR(g_window_ctx->graphic_ctx.device, g_window_ctx->swapchain->swapchain, UINT64_MAX, /*g_window_ctx->swapchain->present_complete_semaphore*/ nullptr, g_window_ctx->swapchain->present_complete_fence, &g_window_ctx->swapchain->current_index); EXIT_NOT_IMPLEMENTED(result != VK_SUCCESS); EXIT_NOT_IMPLEMENTED(g_window_ctx->swapchain->current_index == static_cast(-1)); do { result = vkWaitForFences(g_window_ctx->graphic_ctx.device, 1, &g_window_ctx->swapchain->present_complete_fence, VK_TRUE, 100000000); } while (result == VK_TIMEOUT); EXIT_NOT_IMPLEMENTED(result != VK_SUCCESS); vkResetFences(g_window_ctx->graphic_ctx.device, 1, &g_window_ctx->swapchain->present_complete_fence); auto* blt_src_image = image; auto* blt_dst_image = g_window_ctx->swapchain; EXIT_IF(blt_src_image == nullptr); EXIT_IF(blt_dst_image == nullptr); CommandBuffer buffer; buffer.SetQueue(GraphicContext::QUEUE_PRESENT); EXIT_NOT_IMPLEMENTED(buffer.IsInvalid()); auto* vk_buffer = buffer.GetPool()->buffers[buffer.GetIndex()]; buffer.Begin(); UtilBlitImage(&buffer, blt_src_image, blt_dst_image); VkImageMemoryBarrier pre_present_barrier {}; pre_present_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; pre_present_barrier.pNext = nullptr; pre_present_barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; pre_present_barrier.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT; pre_present_barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; pre_present_barrier.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; pre_present_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; pre_present_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; pre_present_barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; pre_present_barrier.subresourceRange.baseMipLevel = 0; pre_present_barrier.subresourceRange.levelCount = 1; pre_present_barrier.subresourceRange.baseArrayLayer = 0; pre_present_barrier.subresourceRange.layerCount = 1; pre_present_barrier.image = g_window_ctx->swapchain->swapchain_images[g_window_ctx->swapchain->current_index]; vkCmdPipelineBarrier(vk_buffer, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, 0, nullptr, 0, nullptr, 1, &pre_present_barrier); buffer.End(); buffer.ExecuteWithSemaphore(); VkPresentInfoKHR present; present.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; present.pNext = nullptr; present.swapchainCount = 1; present.pSwapchains = &g_window_ctx->swapchain->swapchain; present.pImageIndices = &g_window_ctx->swapchain->current_index; present.pWaitSemaphores = &buffer.GetPool()->semaphores[buffer.GetIndex()]; present.waitSemaphoreCount = 1; present.pResults = nullptr; result = vkQueuePresentKHR(g_window_ctx->graphic_ctx.queue[GraphicContext::QUEUE_PRESENT], &present); EXIT_NOT_IMPLEMENTED(result != VK_SUCCESS); WindowShowFps(); } } // namespace Kyty::Libs::Graphics #endif // KYTY_EMU_ENABLED