procedural-3d-engine/base/vulkanexamplebase.h
Lionel Landwerlin 9db191f9a9 Make FPS counter more accurate
While trying to figure a discrepancy between the FPS counter from the
overlay we've introduced in Mesa [1] and the counter in the Vulkan
demos, I figured the demos are not accounting for part of the
rendering loop but rather just the amount of time spent rendering.

This changes accounts for the total amount of time between 2 frames. I
don't think any difference is visible until you reach high frame rates
of 100s or so.

[1]: https://gitlab.freedesktop.org/mesa/mesa/merge_requests/303
2019-02-23 20:51:38 +00:00

503 lines
18 KiB
C++

/*
* Vulkan Example base class
*
* Copyright (C) 2016 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 <windows.h>
#include <fcntl.h>
#include <io.h>
#include <ShellScalingAPI.h>
#elif defined(VK_USE_PLATFORM_ANDROID_KHR)
#include <android/native_activity.h>
#include <android/asset_manager.h>
#include <android_native_app_glue.h>
#include <sys/system_properties.h>
#include "VulkanAndroid.h"
#elif defined(VK_USE_PLATFORM_WAYLAND_KHR)
#include <wayland-client.h>
#include "xdg-shell-client-protocol.h"
#elif defined(_DIRECT2DISPLAY)
//
#elif defined(VK_USE_PLATFORM_XCB_KHR)
#include <xcb/xcb.h>
#endif
#include <iostream>
#include <chrono>
#include <sys/stat.h>
#define GLM_FORCE_RADIANS
#define GLM_FORCE_DEPTH_ZERO_TO_ONE
#define GLM_ENABLE_EXPERIMENTAL
#include <glm/glm.hpp>
#include <string>
#include <array>
#include <numeric>
#include "vulkan/vulkan.h"
#include "keycodes.hpp"
#include "VulkanTools.h"
#include "VulkanDebug.h"
#include "VulkanUIOverlay.h"
#include "VulkanInitializers.hpp"
#include "VulkanDevice.hpp"
#include "VulkanSwapChain.hpp"
#include "camera.hpp"
#include "benchmark.hpp"
class VulkanExampleBase
{
private:
// Get window title with example name, device, et.
std::string getWindowTitle();
/** brief Indicates that the view (position, rotation) has changed and buffers containing camera matrices need to be updated */
bool viewUpdated = false;
// Destination dimensions for resizing the window
uint32_t destWidth;
uint32_t destHeight;
bool resizing = false;
// Called if the window is resized and some resources have to be recreatesd
void windowResize();
void handleMouseMove(int32_t x, int32_t y);
protected:
// Frame counter to display fps
uint32_t frameCounter = 0;
uint32_t lastFPS = 0;
std::chrono::time_point<std::chrono::high_resolution_clock> lastTimestamp;
// Vulkan instance, stores all per-application states
VkInstance instance;
// Physical device (GPU) that Vulkan will ise
VkPhysicalDevice physicalDevice;
// 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;
/**
* Set of physical device features to be enabled for this example (must be set in the derived constructor)
*
* @note By default no phyiscal device features are enabled
*/
VkPhysicalDeviceFeatures enabledFeatures{};
/** @brief Set of device extensions to be enabled for this example (must be set in the derived constructor) */
std::vector<const char*> enabledDeviceExtensions;
std::vector<const char*> enabledInstanceExtensions;
/** @brief Logical device, application's view of the physical device (GPU) */
// todo: getter? should always point to VulkanDevice->device
VkDevice device;
// Handle to the device graphics queue that command buffers are submitted to
VkQueue queue;
// Depth buffer format (selected during Vulkan initialization)
VkFormat depthFormat;
// Command buffer pool
VkCommandPool cmdPool;
/** @brief Pipeline stages used to wait at for graphics queue submissions */
VkPipelineStageFlags submitPipelineStages = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
// Contains command buffers and semaphores to be presented to the queue
VkSubmitInfo submitInfo;
// Command buffers used for rendering
std::vector<VkCommandBuffer> drawCmdBuffers;
// Global render pass for frame buffer writes
VkRenderPass renderPass;
// List of available frame buffers (same as number of swap chain images)
std::vector<VkFramebuffer>frameBuffers;
// Active frame buffer index
uint32_t currentBuffer = 0;
// Descriptor set pool
VkDescriptorPool descriptorPool = VK_NULL_HANDLE;
// List of shader modules created (stored for cleanup)
std::vector<VkShaderModule> shaderModules;
// Pipeline cache object
VkPipelineCache pipelineCache;
// Wraps the swap chain to present images (framebuffers) to the windowing system
VulkanSwapChain swapChain;
// Synchronization semaphores
struct {
// Swap chain image presentation
VkSemaphore presentComplete;
// Command buffer submission and execution
VkSemaphore renderComplete;
} semaphores;
std::vector<VkFence> waitFences;
public:
bool prepared = false;
uint32_t width = 1280;
uint32_t height = 720;
vks::UIOverlay UIOverlay;
/** @brief Last frame time measured using a high performance timer (if available) */
float frameTimer = 1.0f;
/** @brief Returns os specific base asset path (for shaders, models, textures) */
const std::string getAssetPath();
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 = false;
} settings;
VkClearColorValue defaultClearColor = { { 0.025f, 0.025f, 0.025f, 1.0f } };
float zoom = 0;
static std::vector<const char*> 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;
// Use to adjust mouse rotation speed
float rotationSpeed = 1.0f;
// Use to adjust mouse zoom speed
float zoomSpeed = 1.0f;
Camera camera;
glm::vec3 rotation = glm::vec3();
glm::vec3 cameraPos = glm::vec3();
glm::vec2 mousePos;
std::string title = "Vulkan Example";
std::string name = "vulkanExample";
uint32_t apiVersion = VK_API_VERSION_1_0;
struct
{
VkImage image;
VkDeviceMemory mem;
VkImageView view;
} depthStencil;
struct {
glm::vec2 axisLeft = glm::vec2(0.0f);
glm::vec2 axisRight = glm::vec2(0.0f);
} gamePadState;
struct {
bool left = false;
bool right = false;
bool middle = false;
} mouseButtons;
// 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;
/** @brief Product model and manufacturer of the Android device (via android.Product*) */
std::string androidProduct;
#elif (defined(VK_USE_PLATFORM_IOS_MVK) || defined(VK_USE_PLATFORM_MACOS_MVK))
void* view;
#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;
#endif
// Default ctor
VulkanExampleBase(bool enableValidation);
// dtor
virtual ~VulkanExampleBase();
// 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_IOS_MVK) || defined(VK_USE_PLATFORM_MACOS_MVK))
void* setupWindow(void* view);
#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);
#endif
/**
* Create the application wide Vulkan instance
*
* @note Virtual, can be overriden by derived example class for custom instance creation
*/
virtual VkResult createInstance(bool enableValidation);
// Pure virtual render function (override in derived class)
virtual void render() = 0;
// Called when view change occurs
// Can be overriden in derived class to e.g. update uniform buffers
// Containing view dependant matrices
virtual void viewChanged();
/** @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 th mouse cursor moved and before internal events (like camera rotation) is handled */
virtual void mouseMoved(double x, double y, bool &handled);
// Called when the window has been resized
// Can be overriden in derived class to recreate or rebuild resources attached to the frame buffer / swapchain
virtual void windowResized();
// Pure virtual function to be overriden by the dervice class
// Called in case of an event where e.g. the framebuffer has to be rebuild and thus
// all command buffers that may reference this
virtual void buildCommandBuffers();
void createSynchronizationPrimitives();
// Creates a new (graphics) command pool object storing command buffers
void createCommandPool();
// Setup default depth and stencil views
virtual void setupDepthStencil();
// Create framebuffers for all requested swap chain images
// Can be overriden in derived class to setup a custom framebuffer (e.g. for MSAA)
virtual void setupFrameBuffer();
// Setup a default render pass
// Can be overriden in derived class to setup a custom render pass (e.g. for MSAA)
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();
// Connect and prepare the swap chain
void initSwapchain();
// Create swap chain images
void setupSwapChain();
// Check if command buffers are valid (!= VK_NULL_HANDLE)
bool checkCommandBuffers();
// Create command buffers for drawing commands
void createCommandBuffers();
// Destroy all command buffers and set their handles to VK_NULL_HANDLE
// May be necessary during runtime if options are toggled
void destroyCommandBuffers();
// Command buffer creation
// Creates and returns a new command buffer
VkCommandBuffer createCommandBuffer(VkCommandBufferLevel level, bool begin);
// End the command buffer, submit it to the queue and free (if requested)
// Note : Waits for the queue to become idle
void flushCommandBuffer(VkCommandBuffer commandBuffer, VkQueue queue, bool free);
// Create a cache pool for rendering pipelines
void createPipelineCache();
// Prepare commonly used Vulkan functions
virtual void prepare();
// Load a SPIR-V shader
VkPipelineShaderStageCreateInfo loadShader(std::string fileName, VkShaderStageFlagBits stage);
// Start the main render loop
void renderLoop();
// Render one frame of a render loop on platforms that sync rendering
void renderFrame();
void updateOverlay();
void drawUI(const VkCommandBuffer commandBuffer);
// Prepare the frame for workload submission
// - Acquires the next image from the swap chain
// - Sets the default wait and signal semaphores
void prepareFrame();
// Submit the frames' workload
void submitFrame();
/** @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);
};
// OS specific macros for the example main entry points
#if defined(_WIN32)
// Windows entry point
#define VULKAN_EXAMPLE_MAIN() \
VulkanExample *vulkanExample; \
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) \
{ \
if (vulkanExample != NULL) \
{ \
vulkanExample->handleMessages(hWnd, uMsg, wParam, lParam); \
} \
return (DefWindowProc(hWnd, uMsg, wParam, lParam)); \
} \
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int) \
{ \
for (int32_t i = 0; i < __argc; i++) { VulkanExample::args.push_back(__argv[i]); }; \
vulkanExample = new VulkanExample(); \
vulkanExample->initVulkan(); \
vulkanExample->setupWindow(hInstance, WndProc); \
vulkanExample->prepare(); \
vulkanExample->renderLoop(); \
delete(vulkanExample); \
return 0; \
}
#elif defined(VK_USE_PLATFORM_ANDROID_KHR)
// Android entry point
#define VULKAN_EXAMPLE_MAIN() \
VulkanExample *vulkanExample; \
void android_main(android_app* state) \
{ \
vulkanExample = new VulkanExample(); \
state->userData = vulkanExample; \
state->onAppCmd = VulkanExample::handleAppCommand; \
state->onInputEvent = VulkanExample::handleAppInput; \
androidApp = state; \
vks::android::getDeviceConfig(); \
vulkanExample->renderLoop(); \
delete(vulkanExample); \
}
#elif defined(_DIRECT2DISPLAY)
// Linux entry point with direct to display wsi
#define VULKAN_EXAMPLE_MAIN() \
VulkanExample *vulkanExample; \
static void handleEvent() \
{ \
} \
int main(const int argc, const char *argv[]) \
{ \
for (size_t i = 0; i < argc; i++) { VulkanExample::args.push_back(argv[i]); }; \
vulkanExample = new VulkanExample(); \
vulkanExample->initVulkan(); \
vulkanExample->prepare(); \
vulkanExample->renderLoop(); \
delete(vulkanExample); \
return 0; \
}
#elif defined(VK_USE_PLATFORM_WAYLAND_KHR)
#define VULKAN_EXAMPLE_MAIN() \
VulkanExample *vulkanExample; \
int main(const int argc, const char *argv[]) \
{ \
for (size_t i = 0; i < argc; i++) { VulkanExample::args.push_back(argv[i]); }; \
vulkanExample = new VulkanExample(); \
vulkanExample->initVulkan(); \
vulkanExample->setupWindow(); \
vulkanExample->prepare(); \
vulkanExample->renderLoop(); \
delete(vulkanExample); \
return 0; \
}
#elif defined(VK_USE_PLATFORM_XCB_KHR)
#define VULKAN_EXAMPLE_MAIN() \
VulkanExample *vulkanExample; \
static void handleEvent(const xcb_generic_event_t *event) \
{ \
if (vulkanExample != NULL) \
{ \
vulkanExample->handleEvent(event); \
} \
} \
int main(const int argc, const char *argv[]) \
{ \
for (size_t i = 0; i < argc; i++) { VulkanExample::args.push_back(argv[i]); }; \
vulkanExample = new VulkanExample(); \
vulkanExample->initVulkan(); \
vulkanExample->setupWindow(); \
vulkanExample->prepare(); \
vulkanExample->renderLoop(); \
delete(vulkanExample); \
return 0; \
}
#elif (defined(VK_USE_PLATFORM_IOS_MVK) || defined(VK_USE_PLATFORM_MACOS_MVK))
#define VULKAN_EXAMPLE_MAIN()
#endif