/* * Vulkan Example base class * * Copyright (C) 2016-2026 by Sascha Willems - www.saschawillems.de * * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) */ #pragma once #ifdef _WIN32 #pragma comment(linker, "/subsystem:windows") #include #include #include #include #elif defined(VK_USE_PLATFORM_ANDROID_KHR) #include #include #include #include #include "VulkanAndroid.h" #elif defined(VK_USE_PLATFORM_OHOS) #include #include #include "uv.h" #include "VulkanOHOS.h" extern NativeWindow* nativeWindow; extern uv_loop_t* loop; extern uv_async_t msgSignal; #elif defined(VK_USE_PLATFORM_DIRECTFB_EXT) #include #elif defined(VK_USE_PLATFORM_WAYLAND_KHR) #include #include "xdg-shell-client-protocol.h" #elif defined(_DIRECT2DISPLAY) // #elif defined(VK_USE_PLATFORM_XCB_KHR) #include #elif (defined(VK_USE_PLATFORM_IOS_MVK) || defined(VK_USE_PLATFORM_MACOS_MVK) || defined(VK_USE_PLATFORM_METAL_EXT)) #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define GLM_FORCE_RADIANS #define GLM_FORCE_DEPTH_ZERO_TO_ONE #define GLM_ENABLE_EXPERIMENTAL #include #include #include #include #include #include "vulkan/vulkan.h" #include "CommandLineParser.hpp" #include "keycodes.hpp" #include "VulkanTools.h" #include "VulkanDebug.h" #include "VulkanUIOverlay.h" #include "VulkanSwapChain.h" #include "VulkanBuffer.h" #include "VulkanDevice.h" #include "VulkanTexture.h" #include "VulkanInitializers.hpp" #include "camera.hpp" #include "benchmark.hpp" constexpr uint32_t maxConcurrentFrames{ 2 }; class VulkanExampleBase { private: std::string getWindowTitle() const; uint32_t destWidth{}; uint32_t destHeight{}; bool resizing = false; void handleMouseMove(int32_t x, int32_t y); void nextFrame(); void updateOverlay(); void createPipelineCache(); void createCommandPool(); void createSynchronizationPrimitives(); void createSurface(); void createSwapChain(); void createCommandBuffers(); void destroyCommandBuffers(); std::string shaderDir = "glsl"; #if defined(VK_EXAMPLE_PS5) VkSemaphore ps5CaptureComplete{ VK_NULL_HANDLE }; VkSemaphore ps5CaptureSwapchainImage(VkSemaphore renderComplete); /** @brief The host reference build is recording every frame (PS5_RECORD, ps5/tools/record-reel.sh) */ static bool ps5Recording(); #endif protected: // Returns the path to the root of the glsl, hlsl or slang shader directory. std::string getShadersPath() const; // Frame counter to display fps uint32_t frameCounter = 0; uint32_t lastFPS = 0; std::chrono::time_point lastTimestamp, tPrevEnd; // Vulkan instance, stores all per-application states VkInstance instance{ VK_NULL_HANDLE }; std::vector supportedInstanceExtensions; // Physical device (GPU) that Vulkan will use VkPhysicalDevice physicalDevice{ VK_NULL_HANDLE }; // Stores physical device properties (for e.g. checking device limits) VkPhysicalDeviceProperties deviceProperties{}; // Stores the features available on the selected physical device (for e.g. checking if a feature is available) VkPhysicalDeviceFeatures deviceFeatures{}; // Stores all available memory (type) properties for the physical device VkPhysicalDeviceMemoryProperties deviceMemoryProperties{}; /** @brief Set of physical device features to be enabled for this example (must be set in the derived constructor) */ VkPhysicalDeviceFeatures enabledFeatures{}; /** @brief Set of device extensions to be enabled for this example (must be set in the derived constructor) */ std::vector enabledDeviceExtensions; /** @brief Set of instance extensions to be enabled for this example (must be set in the derived constructor) */ std::vector enabledInstanceExtensions; /** @brief Set of layer settings to be enabled for this example (must be set in the derived constructor) */ std::vector enabledLayerSettings; /** @brief Optional pNext structure for passing extension structures to device creation */ void* deviceCreatepNextChain = nullptr; /** @brief Logical device, application's view of the physical device (GPU) */ VkDevice device{ VK_NULL_HANDLE }; // Handle to the device graphics queue that command buffers are submitted to VkQueue queue{ VK_NULL_HANDLE }; // Depth buffer format (selected during Vulkan initialization) VkFormat depthFormat{VK_FORMAT_UNDEFINED}; // Command buffer pool VkCommandPool cmdPool{ VK_NULL_HANDLE }; // Command buffers used for rendering std::array drawCmdBuffers; // Global render pass for frame buffer writes VkRenderPass renderPass{ VK_NULL_HANDLE }; // List of available frame buffers (same as number of swap chain images) std::vectorframeBuffers; // Descriptor set pool VkDescriptorPool descriptorPool{ VK_NULL_HANDLE }; // List of shader modules created (stored for cleanup) std::vector shaderModules; // Pipeline cache object VkPipelineCache pipelineCache{ VK_NULL_HANDLE }; // Wraps the swap chain to present images (framebuffers) to the windowing system VulkanSwapChain swapChain; // Synchronization related objects and variables // These are used to have multiple frame buffers "in flight" to get some CPU/GPU parallelism uint32_t currentImageIndex{ 0 }; uint32_t currentBuffer{ 0 }; std::array presentCompleteSemaphores{}; std::vector renderCompleteSemaphores{}; std::array waitFences; bool requiresStencil{ false }; public: bool prepared = false; bool resized = false; uint32_t width = 1280; uint32_t height = 720; vks::UIOverlay ui; CommandLineParser commandLineParser; /** @brief Last frame time measured using a high performance timer (if available) */ float frameTimer = 1.0f; vks::Benchmark benchmark; /** @brief Encapsulated physical and logical vulkan device */ vks::VulkanDevice *vulkanDevice{}; /** @brief Example settings that can be changed e.g. by command line arguments */ struct Settings { /** @brief Activates validation layers (and message output) when set to true */ bool validation = false; /** @brief Set to true if fullscreen mode has been requested via command line */ bool fullscreen = false; /** @brief Set to true if v-sync will be forced for the swapchain */ bool vsync = false; /** @brief Enable UI overlay */ bool overlay = true; } settings; /** @brief State of gamepad input (only used on Android) */ struct { glm::vec2 axisLeft = glm::vec2(0.0f); glm::vec2 axisRight = glm::vec2(0.0f); } gamePadState; /** @brief State of mouse/touch input */ struct { struct { bool left = false; bool right = false; bool middle = false; } buttons; glm::vec2 position; } mouseState; VkClearColorValue defaultClearColor = { { 0.025f, 0.025f, 0.025f, 1.0f } }; static std::vector args; // Defines a frame rate independent timer value clamped from -1.0...1.0 // For use in animations, rotations, etc. float timer = 0.0f; // Multiplier for speeding up (or slowing down) the global timer float timerSpeed = 0.25f; bool paused = false; Camera camera; std::string title = "Vulkan Example"; std::string name = "vulkanExample"; uint32_t apiVersion = VK_API_VERSION_1_0; /** @brief Default depth stencil attachment used by the default render pass */ struct { VkImage image; VkDeviceMemory memory; VkImageView view; } depthStencil{}; /** @brief Can be used by samples that use the core version of dynamic rendering */ bool useDynamicRendering{ false }; VkPhysicalDeviceDynamicRenderingFeatures baseDynamicRenderingFeatures{ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_FEATURES, .dynamicRendering = VK_TRUE }; // OS specific #if defined(_WIN32) HWND window; HINSTANCE windowInstance; #elif defined(VK_USE_PLATFORM_ANDROID_KHR) // true if application has focused, false if moved to background bool focused = false; struct TouchPos { int32_t x; int32_t y; } touchPos{}; bool touchDown = false; double touchTimer = 0.0; int64_t lastTapTime = 0; #elif defined (__OHOS__) NativeResourceManager* s_nativeResourceManager; bool focused = false; struct TouchPos { int32_t x; int32_t y; } touchPos; bool touchDown = false; double touchTimer = 0.0; int64_t lastTapTime = 0; int64_t touchDownTime = 0; #elif (defined(VK_USE_PLATFORM_IOS_MVK) || defined(VK_USE_PLATFORM_MACOS_MVK) || defined(VK_USE_PLATFORM_METAL_EXT)) void* view; #if defined(VK_USE_PLATFORM_METAL_EXT) CAMetalLayer* metalLayer; #endif #if defined(VK_EXAMPLE_XCODE_GENERATED) bool quit = false; #endif #elif defined(VK_USE_PLATFORM_DIRECTFB_EXT) bool quit = false; IDirectFB *dfb = nullptr; IDirectFBDisplayLayer *layer = nullptr; IDirectFBWindow *window = nullptr; IDirectFBSurface *surface = nullptr; IDirectFBEventBuffer *event_buffer = nullptr; #elif defined(VK_USE_PLATFORM_WAYLAND_KHR) wl_display *display = nullptr; wl_registry *registry = nullptr; wl_compositor *compositor = nullptr; struct xdg_wm_base *shell = nullptr; wl_seat *seat = nullptr; wl_pointer *pointer = nullptr; wl_keyboard *keyboard = nullptr; wl_surface *surface = nullptr; struct xdg_surface *xdg_surface; struct xdg_toplevel *xdg_toplevel; bool quit = false; bool configured = false; #elif defined(_DIRECT2DISPLAY) bool quit = false; #elif defined(VK_USE_PLATFORM_XCB_KHR) bool quit = false; xcb_connection_t *connection; xcb_screen_t *screen; xcb_window_t window; xcb_intern_atom_reply_t *atom_wm_delete_window; #elif defined(VK_USE_PLATFORM_HEADLESS_EXT) bool quit = false; #elif defined(VK_USE_PLATFORM_SCREEN_QNX) screen_context_t screen_context = nullptr; screen_window_t screen_window = nullptr; screen_event_t screen_event = nullptr; bool quit = false; #endif #if defined(VK_EXAMPLE_PS5) /** @brief PS5: how the launcher runs this sample (ps5/src/main.cpp); also in the host reference build (ps5/README.md) */ struct { /** @brief Frames to draw before the sample ends itself; 0: until OPTIONS is pressed */ uint32_t frameBudget = 0; /** @brief Where the last frame of the budget is saved (a PPM at half the display's size); empty: none */ std::string screenshotPath; /** @brief Frames drawn so far */ uint32_t framesDrawn = 0; /** @brief Save the next frame to screenshotPath at full size, whatever the budget (RPCS3 PS5: L3 + R3 on the * home screen; the console's own screenshot sees only black, as the title presents through its own driver) */ bool captureNow = false; /** @brief When half the budget had been drawn, and when the last frame began (steady_clock seconds): * the frame rate after start-up, without the last frame's screenshot */ double halfwayTime = 0.0; double lastFrameTime = 0.0; /** @brief The launcher's own menu: ImGui windows of its own instead of the sample's overlay window */ bool ownOverlay = false; /** @brief OPTIONS ends the sample, back to the menu; in a title with one program, it shows and hides the overlay */ bool optionsEnds = true; /** @brief A test run's variant of the sample ("uikit.aurora" runs uikit with "aurora"); empty otherwise */ std::string variant; /** @brief A recording's camera orbit in a test run, degrees a second (test-run.txt's "orbit") */ float orbit = 0.0f; } ps5; /** @brief Reads the pad: the sticks move the camera, the buttons drive the UI overlay, OPTIONS ends the sample */ void ps5HandleInput(); #endif /** @brief Default base class constructor */ VulkanExampleBase(); virtual ~VulkanExampleBase(); /** @brief Setup the vulkan instance, enable required extensions and connect to the physical device (GPU) */ bool initVulkan(); #if defined(_WIN32) void setupConsole(std::string title); void setupDPIAwareness(); HWND setupWindow(HINSTANCE hinstance, WNDPROC wndproc); void handleMessages(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); #elif defined(VK_USE_PLATFORM_ANDROID_KHR) static int32_t handleAppInput(struct android_app* app, AInputEvent* event); static void handleAppCommand(android_app* app, int32_t cmd); #elif defined(VK_USE_PLATFORM_OHOS) static void handleAppInput(OH_NativeXComponent *component, void *window, void* pThis); // Static callback for libuv async handle static void onMessageCallbackStatic(uv_async_t* handle); // Instance method called from static callback void onMessageCallback(); #elif (defined(VK_USE_PLATFORM_IOS_MVK) || defined(VK_USE_PLATFORM_MACOS_MVK) || defined(VK_USE_PLATFORM_METAL_EXT)) void* setupWindow(void* view); void displayLinkOutputCb(); void mouseDragged(float x, float y); void windowWillResize(float x, float y); void windowDidResize(); #elif defined(VK_USE_PLATFORM_DIRECTFB_EXT) IDirectFBSurface *setupWindow(); void handleEvent(const DFBWindowEvent *event); #elif defined(VK_USE_PLATFORM_WAYLAND_KHR) struct xdg_surface *setupWindow(); void initWaylandConnection(); void setSize(int width, int height); static void registryGlobalCb(void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version); void registryGlobal(struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version); static void registryGlobalRemoveCb(void *data, struct wl_registry *registry, uint32_t name); static void seatCapabilitiesCb(void *data, wl_seat *seat, uint32_t caps); void seatCapabilities(wl_seat *seat, uint32_t caps); static void pointerEnterCb(void *data, struct wl_pointer *pointer, uint32_t serial, struct wl_surface *surface, wl_fixed_t sx, wl_fixed_t sy); static void pointerLeaveCb(void *data, struct wl_pointer *pointer, uint32_t serial, struct wl_surface *surface); static void pointerMotionCb(void *data, struct wl_pointer *pointer, uint32_t time, wl_fixed_t sx, wl_fixed_t sy); void pointerMotion(struct wl_pointer *pointer, uint32_t time, wl_fixed_t sx, wl_fixed_t sy); static void pointerButtonCb(void *data, struct wl_pointer *wl_pointer, uint32_t serial, uint32_t time, uint32_t button, uint32_t state); void pointerButton(struct wl_pointer *wl_pointer, uint32_t serial, uint32_t time, uint32_t button, uint32_t state); static void pointerAxisCb(void *data, struct wl_pointer *wl_pointer, uint32_t time, uint32_t axis, wl_fixed_t value); void pointerAxis(struct wl_pointer *wl_pointer, uint32_t time, uint32_t axis, wl_fixed_t value); static void keyboardKeymapCb(void *data, struct wl_keyboard *keyboard, uint32_t format, int fd, uint32_t size); static void keyboardEnterCb(void *data, struct wl_keyboard *keyboard, uint32_t serial, struct wl_surface *surface, struct wl_array *keys); static void keyboardLeaveCb(void *data, struct wl_keyboard *keyboard, uint32_t serial, struct wl_surface *surface); static void keyboardKeyCb(void *data, struct wl_keyboard *keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state); void keyboardKey(struct wl_keyboard *keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state); static void keyboardModifiersCb(void *data, struct wl_keyboard *keyboard, uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group); #elif defined(_DIRECT2DISPLAY) // #elif defined(VK_USE_PLATFORM_XCB_KHR) xcb_window_t setupWindow(); void initxcbConnection(); void handleEvent(const xcb_generic_event_t *event); #elif defined(VK_USE_PLATFORM_SCREEN_QNX) void setupWindow(); void handleEvent(); #else void setupWindow(); #endif /** @brief (Virtual) Creates the application wide Vulkan instance */ virtual VkResult createInstance(); /** @brief (Pure virtual) Render function to be implemented by the sample application */ virtual void render() = 0; /** @brief (Virtual) Called after a key was pressed, can be used to do custom key handling */ virtual void keyPressed(uint32_t); /** @brief (Virtual) Called after the mouse cursor moved and before internal events (like camera rotation) is handled */ virtual void mouseMoved(double x, double y, bool &handled); /** @brief (Virtual) Called when the window has been resized, can be used by the sample application to recreate resources */ virtual void windowResized(); /** @brief (Virtual) Setup default depth and stencil views */ virtual void setupDepthStencil(); /** @brief (Virtual) Setup default framebuffers for all requested swapchain images */ virtual void setupFrameBuffer(); /** @brief (Virtual) Setup a default renderpass */ virtual void setupRenderPass(); /** @brief (Virtual) Called after the physical device features have been read, can be used to set features to enable on the device */ virtual void getEnabledFeatures(); /** @brief (Virtual) Called after the physical device extensions have been read, can be used to enable extensions based on the supported extension listing*/ virtual void getEnabledExtensions(); /** @brief Begin dynamic rendering with the default setup for the base class color and depth attachment */ void beginDynamicRendering(VkCommandBuffer cmdBuffer); /** @brief End dynamic rendering with the default setup for the base class color and depth attachment */ void endDynamicRendering(VkCommandBuffer cmdBuffer); /** @brief Prepares all Vulkan resources and functions required to run the sample */ virtual void prepare(); /** @brief Loads a SPIR-V shader file for the given shader stage */ VkPipelineShaderStageCreateInfo loadShader(std::string fileName, VkShaderStageFlagBits stage); void windowResize(); /** @brief Entry point for the main render loop */ void renderLoop(); /** @brief Adds the drawing commands for the ImGui overlay to the given command buffer */ void drawUI(const VkCommandBuffer commandBuffer); /** Prepare the next frame for workload submission by acquiring the next swap chain image and waiting for the previous command buffer to finish */ void prepareFrame(bool waitForFence = true); /** @brief Presents the current image to the swap chain */ void submitFrame(bool skipQueueSubmit = false); /** @brief (Virtual) Called when the UI overlay is updating, can be used to add custom elements to the overlay */ virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay); #if defined(_WIN32) virtual void OnHandleMessage(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); #endif }; #include "Entrypoints.h"