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
2269 lines
No EOL
64 KiB
C++
2269 lines
No EOL
64 KiB
C++
/*
|
|
* Vulkan Example base class
|
|
*
|
|
* Copyright (C) 2016-2017 by Sascha Willems - www.saschawillems.de
|
|
*
|
|
* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
|
|
*/
|
|
|
|
#include "vulkanexamplebase.h"
|
|
|
|
std::vector<const char*> VulkanExampleBase::args;
|
|
|
|
VkResult VulkanExampleBase::createInstance(bool enableValidation)
|
|
{
|
|
this->settings.validation = enableValidation;
|
|
|
|
// Validation can also be forced via a define
|
|
#if defined(_VALIDATION)
|
|
this->settings.validation = true;
|
|
#endif
|
|
|
|
VkApplicationInfo appInfo = {};
|
|
appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
|
|
appInfo.pApplicationName = name.c_str();
|
|
appInfo.pEngineName = name.c_str();
|
|
appInfo.apiVersion = apiVersion;
|
|
|
|
std::vector<const char*> instanceExtensions = { VK_KHR_SURFACE_EXTENSION_NAME };
|
|
|
|
// Enable surface extensions depending on os
|
|
#if defined(_WIN32)
|
|
instanceExtensions.push_back(VK_KHR_WIN32_SURFACE_EXTENSION_NAME);
|
|
#elif defined(VK_USE_PLATFORM_ANDROID_KHR)
|
|
instanceExtensions.push_back(VK_KHR_ANDROID_SURFACE_EXTENSION_NAME);
|
|
#elif defined(_DIRECT2DISPLAY)
|
|
instanceExtensions.push_back(VK_KHR_DISPLAY_EXTENSION_NAME);
|
|
#elif defined(VK_USE_PLATFORM_WAYLAND_KHR)
|
|
instanceExtensions.push_back(VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME);
|
|
#elif defined(VK_USE_PLATFORM_XCB_KHR)
|
|
instanceExtensions.push_back(VK_KHR_XCB_SURFACE_EXTENSION_NAME);
|
|
#elif defined(VK_USE_PLATFORM_IOS_MVK)
|
|
instanceExtensions.push_back(VK_MVK_IOS_SURFACE_EXTENSION_NAME);
|
|
#elif defined(VK_USE_PLATFORM_MACOS_MVK)
|
|
instanceExtensions.push_back(VK_MVK_MACOS_SURFACE_EXTENSION_NAME);
|
|
#endif
|
|
|
|
if (enabledInstanceExtensions.size() > 0) {
|
|
for (auto enabledExtension : enabledInstanceExtensions) {
|
|
instanceExtensions.push_back(enabledExtension);
|
|
}
|
|
}
|
|
|
|
VkInstanceCreateInfo instanceCreateInfo = {};
|
|
instanceCreateInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
|
|
instanceCreateInfo.pNext = NULL;
|
|
instanceCreateInfo.pApplicationInfo = &appInfo;
|
|
if (instanceExtensions.size() > 0)
|
|
{
|
|
if (settings.validation)
|
|
{
|
|
instanceExtensions.push_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME);
|
|
}
|
|
instanceCreateInfo.enabledExtensionCount = (uint32_t)instanceExtensions.size();
|
|
instanceCreateInfo.ppEnabledExtensionNames = instanceExtensions.data();
|
|
}
|
|
if (settings.validation)
|
|
{
|
|
instanceCreateInfo.enabledLayerCount = vks::debug::validationLayerCount;
|
|
instanceCreateInfo.ppEnabledLayerNames = vks::debug::validationLayerNames;
|
|
}
|
|
return vkCreateInstance(&instanceCreateInfo, nullptr, &instance);
|
|
}
|
|
|
|
std::string VulkanExampleBase::getWindowTitle()
|
|
{
|
|
std::string device(deviceProperties.deviceName);
|
|
std::string windowTitle;
|
|
windowTitle = title + " - " + device;
|
|
if (!settings.overlay) {
|
|
windowTitle += " - " + std::to_string(frameCounter) + " fps";
|
|
}
|
|
return windowTitle;
|
|
}
|
|
|
|
#if !(defined(VK_USE_PLATFORM_IOS_MVK) || defined(VK_USE_PLATFORM_MACOS_MVK))
|
|
// iOS & macOS: VulkanExampleBase::getAssetPath() implemented externally to allow access to Objective-C components
|
|
const std::string VulkanExampleBase::getAssetPath()
|
|
{
|
|
#if defined(VK_USE_PLATFORM_ANDROID_KHR)
|
|
return "";
|
|
#elif defined(VK_EXAMPLE_DATA_DIR)
|
|
return VK_EXAMPLE_DATA_DIR;
|
|
#else
|
|
return "./../data/";
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
bool VulkanExampleBase::checkCommandBuffers()
|
|
{
|
|
for (auto& cmdBuffer : drawCmdBuffers)
|
|
{
|
|
if (cmdBuffer == VK_NULL_HANDLE)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void VulkanExampleBase::createCommandBuffers()
|
|
{
|
|
// Create one command buffer for each swap chain image and reuse for rendering
|
|
drawCmdBuffers.resize(swapChain.imageCount);
|
|
|
|
VkCommandBufferAllocateInfo cmdBufAllocateInfo =
|
|
vks::initializers::commandBufferAllocateInfo(
|
|
cmdPool,
|
|
VK_COMMAND_BUFFER_LEVEL_PRIMARY,
|
|
static_cast<uint32_t>(drawCmdBuffers.size()));
|
|
|
|
VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &cmdBufAllocateInfo, drawCmdBuffers.data()));
|
|
}
|
|
|
|
void VulkanExampleBase::destroyCommandBuffers()
|
|
{
|
|
vkFreeCommandBuffers(device, cmdPool, static_cast<uint32_t>(drawCmdBuffers.size()), drawCmdBuffers.data());
|
|
}
|
|
|
|
VkCommandBuffer VulkanExampleBase::createCommandBuffer(VkCommandBufferLevel level, bool begin)
|
|
{
|
|
VkCommandBuffer cmdBuffer;
|
|
|
|
VkCommandBufferAllocateInfo cmdBufAllocateInfo =
|
|
vks::initializers::commandBufferAllocateInfo(
|
|
cmdPool,
|
|
level,
|
|
1);
|
|
|
|
VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &cmdBufAllocateInfo, &cmdBuffer));
|
|
|
|
// If requested, also start the new command buffer
|
|
if (begin)
|
|
{
|
|
VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
|
|
VK_CHECK_RESULT(vkBeginCommandBuffer(cmdBuffer, &cmdBufInfo));
|
|
}
|
|
|
|
return cmdBuffer;
|
|
}
|
|
|
|
void VulkanExampleBase::flushCommandBuffer(VkCommandBuffer commandBuffer, VkQueue queue, bool free)
|
|
{
|
|
if (commandBuffer == VK_NULL_HANDLE)
|
|
{
|
|
return;
|
|
}
|
|
|
|
VK_CHECK_RESULT(vkEndCommandBuffer(commandBuffer));
|
|
|
|
VkSubmitInfo submitInfo = {};
|
|
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
|
|
submitInfo.commandBufferCount = 1;
|
|
submitInfo.pCommandBuffers = &commandBuffer;
|
|
|
|
VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
|
|
VK_CHECK_RESULT(vkQueueWaitIdle(queue));
|
|
|
|
if (free)
|
|
{
|
|
vkFreeCommandBuffers(device, cmdPool, 1, &commandBuffer);
|
|
}
|
|
}
|
|
|
|
void VulkanExampleBase::createPipelineCache()
|
|
{
|
|
VkPipelineCacheCreateInfo pipelineCacheCreateInfo = {};
|
|
pipelineCacheCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO;
|
|
VK_CHECK_RESULT(vkCreatePipelineCache(device, &pipelineCacheCreateInfo, nullptr, &pipelineCache));
|
|
}
|
|
|
|
void VulkanExampleBase::prepare()
|
|
{
|
|
if (vulkanDevice->enableDebugMarkers) {
|
|
vks::debugmarker::setup(device);
|
|
}
|
|
initSwapchain();
|
|
createCommandPool();
|
|
setupSwapChain();
|
|
createCommandBuffers();
|
|
createSynchronizationPrimitives();
|
|
setupDepthStencil();
|
|
setupRenderPass();
|
|
createPipelineCache();
|
|
setupFrameBuffer();
|
|
settings.overlay = settings.overlay && (!benchmark.active);
|
|
if (settings.overlay) {
|
|
UIOverlay.device = vulkanDevice;
|
|
UIOverlay.queue = queue;
|
|
UIOverlay.shaders = {
|
|
loadShader(getAssetPath() + "shaders/base/uioverlay.vert.spv", VK_SHADER_STAGE_VERTEX_BIT),
|
|
loadShader(getAssetPath() + "shaders/base/uioverlay.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT),
|
|
};
|
|
UIOverlay.prepareResources();
|
|
UIOverlay.preparePipeline(pipelineCache, renderPass);
|
|
}
|
|
}
|
|
|
|
VkPipelineShaderStageCreateInfo VulkanExampleBase::loadShader(std::string fileName, VkShaderStageFlagBits stage)
|
|
{
|
|
VkPipelineShaderStageCreateInfo shaderStage = {};
|
|
shaderStage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
|
|
shaderStage.stage = stage;
|
|
#if defined(VK_USE_PLATFORM_ANDROID_KHR)
|
|
shaderStage.module = vks::tools::loadShader(androidApp->activity->assetManager, fileName.c_str(), device);
|
|
#else
|
|
shaderStage.module = vks::tools::loadShader(fileName.c_str(), device);
|
|
#endif
|
|
shaderStage.pName = "main"; // todo : make param
|
|
assert(shaderStage.module != VK_NULL_HANDLE);
|
|
shaderModules.push_back(shaderStage.module);
|
|
return shaderStage;
|
|
}
|
|
|
|
void VulkanExampleBase::renderFrame()
|
|
{
|
|
auto tStart = std::chrono::high_resolution_clock::now();
|
|
if (viewUpdated)
|
|
{
|
|
viewUpdated = false;
|
|
viewChanged();
|
|
}
|
|
|
|
render();
|
|
frameCounter++;
|
|
auto tEnd = std::chrono::high_resolution_clock::now();
|
|
auto tDiff = std::chrono::duration<double, std::milli>(tEnd - tStart).count();
|
|
frameTimer = (float)tDiff / 1000.0f;
|
|
camera.update(frameTimer);
|
|
if (camera.moving())
|
|
{
|
|
viewUpdated = true;
|
|
}
|
|
// Convert to clamped timer value
|
|
if (!paused)
|
|
{
|
|
timer += timerSpeed * frameTimer;
|
|
if (timer > 1.0)
|
|
{
|
|
timer -= 1.0f;
|
|
}
|
|
}
|
|
float fpsTimer = std::chrono::duration<double, std::milli>(tEnd - lastTimestamp).count();
|
|
if (fpsTimer > 1000.0f)
|
|
{
|
|
lastFPS = static_cast<uint32_t>((float)frameCounter * (1000.0f / fpsTimer));
|
|
#if defined(_WIN32)
|
|
if (!settings.overlay) {
|
|
std::string windowTitle = getWindowTitle();
|
|
SetWindowText(window, windowTitle.c_str());
|
|
}
|
|
#endif
|
|
frameCounter = 0;
|
|
lastTimestamp = tEnd;
|
|
}
|
|
// TODO: Cap UI overlay update rates
|
|
updateOverlay();
|
|
}
|
|
|
|
void VulkanExampleBase::renderLoop()
|
|
{
|
|
if (benchmark.active) {
|
|
benchmark.run([=] { render(); }, vulkanDevice->properties);
|
|
vkDeviceWaitIdle(device);
|
|
if (benchmark.filename != "") {
|
|
benchmark.saveResults();
|
|
}
|
|
return;
|
|
}
|
|
|
|
destWidth = width;
|
|
destHeight = height;
|
|
lastTimestamp = std::chrono::high_resolution_clock::now();
|
|
#if defined(_WIN32)
|
|
MSG msg;
|
|
bool quitMessageReceived = false;
|
|
while (!quitMessageReceived) {
|
|
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
|
|
TranslateMessage(&msg);
|
|
DispatchMessage(&msg);
|
|
if (msg.message == WM_QUIT) {
|
|
quitMessageReceived = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!IsIconic(window)) {
|
|
renderFrame();
|
|
}
|
|
}
|
|
#elif defined(VK_USE_PLATFORM_ANDROID_KHR)
|
|
while (1)
|
|
{
|
|
int ident;
|
|
int events;
|
|
struct android_poll_source* source;
|
|
bool destroy = false;
|
|
|
|
focused = true;
|
|
|
|
while ((ident = ALooper_pollAll(focused ? 0 : -1, NULL, &events, (void**)&source)) >= 0)
|
|
{
|
|
if (source != NULL)
|
|
{
|
|
source->process(androidApp, source);
|
|
}
|
|
if (androidApp->destroyRequested != 0)
|
|
{
|
|
LOGD("Android app destroy requested");
|
|
destroy = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// App destruction requested
|
|
// Exit loop, example will be destroyed in application main
|
|
if (destroy)
|
|
{
|
|
ANativeActivity_finish(androidApp->activity);
|
|
break;
|
|
}
|
|
|
|
// Render frame
|
|
if (prepared)
|
|
{
|
|
auto tStart = std::chrono::high_resolution_clock::now();
|
|
render();
|
|
frameCounter++;
|
|
auto tEnd = std::chrono::high_resolution_clock::now();
|
|
auto tDiff = std::chrono::duration<double, std::milli>(tEnd - tStart).count();
|
|
frameTimer = tDiff / 1000.0f;
|
|
camera.update(frameTimer);
|
|
// Convert to clamped timer value
|
|
if (!paused)
|
|
{
|
|
timer += timerSpeed * frameTimer;
|
|
if (timer > 1.0)
|
|
{
|
|
timer -= 1.0f;
|
|
}
|
|
}
|
|
float fpsTimer = std::chrono::duration<double, std::milli>(tEnd - lastTimestamp).count();
|
|
if (fpsTimer > 1000.0f)
|
|
{
|
|
lastFPS = (float)frameCounter * (1000.0f / fpsTimer);
|
|
frameCounter = 0;
|
|
lastTimestamp = tEnd;
|
|
}
|
|
|
|
// TODO: Cap UI overlay update rates/only issue when update requested
|
|
updateOverlay();
|
|
|
|
bool updateView = false;
|
|
|
|
// Check touch state (for movement)
|
|
if (touchDown) {
|
|
touchTimer += frameTimer;
|
|
}
|
|
if (touchTimer >= 1.0) {
|
|
camera.keys.up = true;
|
|
viewChanged();
|
|
}
|
|
|
|
// Check gamepad state
|
|
const float deadZone = 0.0015f;
|
|
// todo : check if gamepad is present
|
|
// todo : time based and relative axis positions
|
|
if (camera.type != Camera::CameraType::firstperson)
|
|
{
|
|
// Rotate
|
|
if (std::abs(gamePadState.axisLeft.x) > deadZone)
|
|
{
|
|
rotation.y += gamePadState.axisLeft.x * 0.5f * rotationSpeed;
|
|
camera.rotate(glm::vec3(0.0f, gamePadState.axisLeft.x * 0.5f, 0.0f));
|
|
updateView = true;
|
|
}
|
|
if (std::abs(gamePadState.axisLeft.y) > deadZone)
|
|
{
|
|
rotation.x -= gamePadState.axisLeft.y * 0.5f * rotationSpeed;
|
|
camera.rotate(glm::vec3(gamePadState.axisLeft.y * 0.5f, 0.0f, 0.0f));
|
|
updateView = true;
|
|
}
|
|
// Zoom
|
|
if (std::abs(gamePadState.axisRight.y) > deadZone)
|
|
{
|
|
zoom -= gamePadState.axisRight.y * 0.01f * zoomSpeed;
|
|
updateView = true;
|
|
}
|
|
if (updateView)
|
|
{
|
|
viewChanged();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
updateView = camera.updatePad(gamePadState.axisLeft, gamePadState.axisRight, frameTimer);
|
|
if (updateView)
|
|
{
|
|
viewChanged();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#elif defined(_DIRECT2DISPLAY)
|
|
while (!quit)
|
|
{
|
|
auto tStart = std::chrono::high_resolution_clock::now();
|
|
if (viewUpdated)
|
|
{
|
|
viewUpdated = false;
|
|
viewChanged();
|
|
}
|
|
render();
|
|
frameCounter++;
|
|
auto tEnd = std::chrono::high_resolution_clock::now();
|
|
auto tDiff = std::chrono::duration<double, std::milli>(tEnd - tStart).count();
|
|
frameTimer = tDiff / 1000.0f;
|
|
camera.update(frameTimer);
|
|
if (camera.moving())
|
|
{
|
|
viewUpdated = true;
|
|
}
|
|
// Convert to clamped timer value
|
|
if (!paused)
|
|
{
|
|
timer += timerSpeed * frameTimer;
|
|
if (timer > 1.0)
|
|
{
|
|
timer -= 1.0f;
|
|
}
|
|
}
|
|
float fpsTimer = std::chrono::duration<double, std::milli>(tEnd - lastTimestamp).count();
|
|
if (fpsTimer > 1000.0f)
|
|
{
|
|
lastFPS = (float)frameCounter * (1000.0f / fpsTimer);
|
|
frameCounter = 0;
|
|
lastTimestamp = tEnd;
|
|
}
|
|
updateOverlay();
|
|
}
|
|
#elif defined(VK_USE_PLATFORM_WAYLAND_KHR)
|
|
while (!quit)
|
|
{
|
|
auto tStart = std::chrono::high_resolution_clock::now();
|
|
if (viewUpdated)
|
|
{
|
|
viewUpdated = false;
|
|
viewChanged();
|
|
}
|
|
|
|
while (!configured)
|
|
wl_display_dispatch(display);
|
|
while (wl_display_prepare_read(display) != 0)
|
|
wl_display_dispatch_pending(display);
|
|
wl_display_flush(display);
|
|
wl_display_read_events(display);
|
|
wl_display_dispatch_pending(display);
|
|
|
|
render();
|
|
frameCounter++;
|
|
auto tEnd = std::chrono::high_resolution_clock::now();
|
|
auto tDiff = std::chrono::duration<double, std::milli>(tEnd - tStart).count();
|
|
frameTimer = tDiff / 1000.0f;
|
|
camera.update(frameTimer);
|
|
if (camera.moving())
|
|
{
|
|
viewUpdated = true;
|
|
}
|
|
// Convert to clamped timer value
|
|
if (!paused)
|
|
{
|
|
timer += timerSpeed * frameTimer;
|
|
if (timer > 1.0)
|
|
{
|
|
timer -= 1.0f;
|
|
}
|
|
}
|
|
float fpsTimer = std::chrono::duration<double, std::milli>(tEnd - lastTimestamp).count();
|
|
if (fpsTimer > 1000.0f)
|
|
{
|
|
if (!settings.overlay)
|
|
{
|
|
std::string windowTitle = getWindowTitle();
|
|
xdg_toplevel_set_title(xdg_toplevel, windowTitle.c_str());
|
|
}
|
|
lastFPS = (float)frameCounter * (1000.0f / fpsTimer);
|
|
frameCounter = 0;
|
|
lastTimestamp = tEnd;
|
|
}
|
|
updateOverlay();
|
|
}
|
|
#elif defined(VK_USE_PLATFORM_XCB_KHR)
|
|
xcb_flush(connection);
|
|
while (!quit)
|
|
{
|
|
auto tStart = std::chrono::high_resolution_clock::now();
|
|
if (viewUpdated)
|
|
{
|
|
viewUpdated = false;
|
|
viewChanged();
|
|
}
|
|
xcb_generic_event_t *event;
|
|
while ((event = xcb_poll_for_event(connection)))
|
|
{
|
|
handleEvent(event);
|
|
free(event);
|
|
}
|
|
render();
|
|
frameCounter++;
|
|
auto tEnd = std::chrono::high_resolution_clock::now();
|
|
auto tDiff = std::chrono::duration<double, std::milli>(tEnd - tStart).count();
|
|
frameTimer = tDiff / 1000.0f;
|
|
camera.update(frameTimer);
|
|
if (camera.moving())
|
|
{
|
|
viewUpdated = true;
|
|
}
|
|
// Convert to clamped timer value
|
|
if (!paused)
|
|
{
|
|
timer += timerSpeed * frameTimer;
|
|
if (timer > 1.0)
|
|
{
|
|
timer -= 1.0f;
|
|
}
|
|
}
|
|
float fpsTimer = std::chrono::duration<double, std::milli>(tEnd - lastTimestamp).count();
|
|
if (fpsTimer > 1000.0f)
|
|
{
|
|
if (!settings.overlay)
|
|
{
|
|
std::string windowTitle = getWindowTitle();
|
|
xcb_change_property(connection, XCB_PROP_MODE_REPLACE,
|
|
window, XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 8,
|
|
windowTitle.size(), windowTitle.c_str());
|
|
}
|
|
lastFPS = (float)frameCounter * (1000.0f / fpsTimer);
|
|
frameCounter = 0;
|
|
lastTimestamp = tEnd;
|
|
}
|
|
updateOverlay();
|
|
}
|
|
#endif
|
|
// Flush device to make sure all resources can be freed
|
|
if (device != VK_NULL_HANDLE) {
|
|
vkDeviceWaitIdle(device);
|
|
}
|
|
}
|
|
|
|
void VulkanExampleBase::updateOverlay()
|
|
{
|
|
if (!settings.overlay)
|
|
return;
|
|
|
|
ImGuiIO& io = ImGui::GetIO();
|
|
|
|
io.DisplaySize = ImVec2((float)width, (float)height);
|
|
io.DeltaTime = frameTimer;
|
|
|
|
io.MousePos = ImVec2(mousePos.x, mousePos.y);
|
|
io.MouseDown[0] = mouseButtons.left;
|
|
io.MouseDown[1] = mouseButtons.right;
|
|
|
|
ImGui::NewFrame();
|
|
|
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0);
|
|
ImGui::SetNextWindowPos(ImVec2(10, 10));
|
|
ImGui::SetNextWindowSize(ImVec2(0, 0), ImGuiSetCond_FirstUseEver);
|
|
ImGui::Begin("Vulkan Example", nullptr, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove);
|
|
ImGui::TextUnformatted(title.c_str());
|
|
ImGui::TextUnformatted(deviceProperties.deviceName);
|
|
ImGui::Text("%.2f ms/frame (%.1d fps)", (1000.0f / lastFPS), lastFPS);
|
|
|
|
#if defined(VK_USE_PLATFORM_ANDROID_KHR)
|
|
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 5.0f * UIOverlay.scale));
|
|
#endif
|
|
ImGui::PushItemWidth(110.0f * UIOverlay.scale);
|
|
OnUpdateUIOverlay(&UIOverlay);
|
|
ImGui::PopItemWidth();
|
|
#if defined(VK_USE_PLATFORM_ANDROID_KHR)
|
|
ImGui::PopStyleVar();
|
|
#endif
|
|
|
|
ImGui::End();
|
|
ImGui::PopStyleVar();
|
|
ImGui::Render();
|
|
|
|
if (UIOverlay.update() || UIOverlay.updated) {
|
|
buildCommandBuffers();
|
|
UIOverlay.updated = false;
|
|
}
|
|
|
|
#if defined(VK_USE_PLATFORM_ANDROID_KHR)
|
|
if (mouseButtons.left) {
|
|
mouseButtons.left = false;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void VulkanExampleBase::drawUI(const VkCommandBuffer commandBuffer)
|
|
{
|
|
if (settings.overlay) {
|
|
const VkViewport viewport = vks::initializers::viewport((float)width, (float)height, 0.0f, 1.0f);
|
|
const VkRect2D scissor = vks::initializers::rect2D(width, height, 0, 0);
|
|
vkCmdSetViewport(commandBuffer, 0, 1, &viewport);
|
|
vkCmdSetScissor(commandBuffer, 0, 1, &scissor);
|
|
|
|
UIOverlay.draw(commandBuffer);
|
|
}
|
|
}
|
|
|
|
void VulkanExampleBase::prepareFrame()
|
|
{
|
|
// Acquire the next image from the swap chain
|
|
VkResult err = swapChain.acquireNextImage(semaphores.presentComplete, ¤tBuffer);
|
|
// Recreate the swapchain if it's no longer compatible with the surface (OUT_OF_DATE) or no longer optimal for presentation (SUBOPTIMAL)
|
|
if ((err == VK_ERROR_OUT_OF_DATE_KHR) || (err == VK_SUBOPTIMAL_KHR)) {
|
|
windowResize();
|
|
}
|
|
else {
|
|
VK_CHECK_RESULT(err);
|
|
}
|
|
}
|
|
|
|
void VulkanExampleBase::submitFrame()
|
|
{
|
|
VkResult res = swapChain.queuePresent(queue, currentBuffer, semaphores.renderComplete);
|
|
if (!((res == VK_SUCCESS) || (res == VK_SUBOPTIMAL_KHR))) {
|
|
if (res == VK_ERROR_OUT_OF_DATE_KHR) {
|
|
// Swap chain is no longer compatible with the surface and needs to be recreated
|
|
windowResize();
|
|
return;
|
|
} else {
|
|
VK_CHECK_RESULT(res);
|
|
}
|
|
}
|
|
VK_CHECK_RESULT(vkQueueWaitIdle(queue));
|
|
}
|
|
|
|
VulkanExampleBase::VulkanExampleBase(bool enableValidation)
|
|
{
|
|
#if !defined(VK_USE_PLATFORM_ANDROID_KHR)
|
|
// Check for a valid asset path
|
|
struct stat info;
|
|
if (stat(getAssetPath().c_str(), &info) != 0)
|
|
{
|
|
#if defined(_WIN32)
|
|
std::string msg = "Could not locate asset path in \"" + getAssetPath() + "\" !";
|
|
MessageBox(NULL, msg.c_str(), "Fatal error", MB_OK | MB_ICONERROR);
|
|
#else
|
|
std::cerr << "Error: Could not find asset path in " << getAssetPath() << std::endl;
|
|
#endif
|
|
exit(-1);
|
|
}
|
|
#endif
|
|
|
|
settings.validation = enableValidation;
|
|
|
|
char* numConvPtr;
|
|
|
|
// Parse command line arguments
|
|
for (size_t i = 0; i < args.size(); i++)
|
|
{
|
|
if (args[i] == std::string("-validation")) {
|
|
settings.validation = true;
|
|
}
|
|
if (args[i] == std::string("-vsync")) {
|
|
settings.vsync = true;
|
|
}
|
|
if ((args[i] == std::string("-f")) || (args[i] == std::string("--fullscreen"))) {
|
|
settings.fullscreen = true;
|
|
}
|
|
if ((args[i] == std::string("-w")) || (args[i] == std::string("-width"))) {
|
|
uint32_t w = strtol(args[i + 1], &numConvPtr, 10);
|
|
if (numConvPtr != args[i + 1]) { width = w; };
|
|
}
|
|
if ((args[i] == std::string("-h")) || (args[i] == std::string("-height"))) {
|
|
uint32_t h = strtol(args[i + 1], &numConvPtr, 10);
|
|
if (numConvPtr != args[i + 1]) { height = h; };
|
|
}
|
|
// Benchmark
|
|
if ((args[i] == std::string("-b")) || (args[i] == std::string("--benchmark"))) {
|
|
benchmark.active = true;
|
|
vks::tools::errorModeSilent = true;
|
|
}
|
|
// Warmup time (in seconds)
|
|
if ((args[i] == std::string("-bw")) || (args[i] == std::string("--benchwarmup"))) {
|
|
if (args.size() > i + 1) {
|
|
uint32_t num = strtol(args[i + 1], &numConvPtr, 10);
|
|
if (numConvPtr != args[i + 1]) {
|
|
benchmark.warmup = num;
|
|
} else {
|
|
std::cerr << "Warmup time for benchmark mode must be specified as a number!" << std::endl;
|
|
}
|
|
}
|
|
}
|
|
// Benchmark runtime (in seconds)
|
|
if ((args[i] == std::string("-br")) || (args[i] == std::string("--benchruntime"))) {
|
|
if (args.size() > i + 1) {
|
|
uint32_t num = strtol(args[i + 1], &numConvPtr, 10);
|
|
if (numConvPtr != args[i + 1]) {
|
|
benchmark.duration = num;
|
|
}
|
|
else {
|
|
std::cerr << "Benchmark run duration must be specified as a number!" << std::endl;
|
|
}
|
|
}
|
|
}
|
|
// Bench result save filename (overrides default)
|
|
if ((args[i] == std::string("-bf")) || (args[i] == std::string("--benchfilename"))) {
|
|
if (args.size() > i + 1) {
|
|
if (args[i + 1][0] == '-') {
|
|
std::cerr << "Filename for benchmark results must not start with a hyphen!" << std::endl;
|
|
} else {
|
|
benchmark.filename = args[i + 1];
|
|
}
|
|
}
|
|
}
|
|
// Output frame times to benchmark result file
|
|
if ((args[i] == std::string("-bt")) || (args[i] == std::string("--benchframetimes"))) {
|
|
benchmark.outputFrameTimes = true;
|
|
}
|
|
}
|
|
|
|
#if defined(VK_USE_PLATFORM_ANDROID_KHR)
|
|
// Vulkan library is loaded dynamically on Android
|
|
bool libLoaded = vks::android::loadVulkanLibrary();
|
|
assert(libLoaded);
|
|
#elif defined(_DIRECT2DISPLAY)
|
|
|
|
#elif defined(VK_USE_PLATFORM_WAYLAND_KHR)
|
|
initWaylandConnection();
|
|
#elif defined(VK_USE_PLATFORM_XCB_KHR)
|
|
initxcbConnection();
|
|
#endif
|
|
|
|
#if defined(_WIN32)
|
|
// Enable console if validation is active
|
|
// Debug message callback will output to it
|
|
if (this->settings.validation)
|
|
{
|
|
setupConsole("Vulkan validation output");
|
|
}
|
|
setupDPIAwareness();
|
|
#endif
|
|
}
|
|
|
|
VulkanExampleBase::~VulkanExampleBase()
|
|
{
|
|
// Clean up Vulkan resources
|
|
swapChain.cleanup();
|
|
if (descriptorPool != VK_NULL_HANDLE)
|
|
{
|
|
vkDestroyDescriptorPool(device, descriptorPool, nullptr);
|
|
}
|
|
destroyCommandBuffers();
|
|
vkDestroyRenderPass(device, renderPass, nullptr);
|
|
for (uint32_t i = 0; i < frameBuffers.size(); i++)
|
|
{
|
|
vkDestroyFramebuffer(device, frameBuffers[i], nullptr);
|
|
}
|
|
|
|
for (auto& shaderModule : shaderModules)
|
|
{
|
|
vkDestroyShaderModule(device, shaderModule, nullptr);
|
|
}
|
|
vkDestroyImageView(device, depthStencil.view, nullptr);
|
|
vkDestroyImage(device, depthStencil.image, nullptr);
|
|
vkFreeMemory(device, depthStencil.mem, nullptr);
|
|
|
|
vkDestroyPipelineCache(device, pipelineCache, nullptr);
|
|
|
|
vkDestroyCommandPool(device, cmdPool, nullptr);
|
|
|
|
vkDestroySemaphore(device, semaphores.presentComplete, nullptr);
|
|
vkDestroySemaphore(device, semaphores.renderComplete, nullptr);
|
|
for (auto& fence : waitFences) {
|
|
vkDestroyFence(device, fence, nullptr);
|
|
}
|
|
|
|
if (settings.overlay) {
|
|
UIOverlay.freeResources();
|
|
}
|
|
|
|
delete vulkanDevice;
|
|
|
|
if (settings.validation)
|
|
{
|
|
vks::debug::freeDebugCallback(instance);
|
|
}
|
|
|
|
vkDestroyInstance(instance, nullptr);
|
|
|
|
#if defined(_DIRECT2DISPLAY)
|
|
|
|
#elif defined(VK_USE_PLATFORM_WAYLAND_KHR)
|
|
xdg_toplevel_destroy(xdg_toplevel);
|
|
xdg_surface_destroy(xdg_surface);
|
|
wl_surface_destroy(surface);
|
|
if (keyboard)
|
|
wl_keyboard_destroy(keyboard);
|
|
if (pointer)
|
|
wl_pointer_destroy(pointer);
|
|
wl_seat_destroy(seat);
|
|
xdg_wm_base_destroy(shell);
|
|
wl_compositor_destroy(compositor);
|
|
wl_registry_destroy(registry);
|
|
wl_display_disconnect(display);
|
|
#elif defined(VK_USE_PLATFORM_ANDROID_KHR)
|
|
// todo : android cleanup (if required)
|
|
#elif defined(VK_USE_PLATFORM_XCB_KHR)
|
|
xcb_destroy_window(connection, window);
|
|
xcb_disconnect(connection);
|
|
#endif
|
|
}
|
|
|
|
bool VulkanExampleBase::initVulkan()
|
|
{
|
|
VkResult err;
|
|
|
|
// Vulkan instance
|
|
err = createInstance(settings.validation);
|
|
if (err) {
|
|
vks::tools::exitFatal("Could not create Vulkan instance : \n" + vks::tools::errorString(err), err);
|
|
return false;
|
|
}
|
|
|
|
#if defined(VK_USE_PLATFORM_ANDROID_KHR)
|
|
vks::android::loadVulkanFunctions(instance);
|
|
#endif
|
|
|
|
// If requested, we enable the default validation layers for debugging
|
|
if (settings.validation)
|
|
{
|
|
// The report flags determine what type of messages for the layers will be displayed
|
|
// For validating (debugging) an appplication the error and warning bits should suffice
|
|
VkDebugReportFlagsEXT debugReportFlags = VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT;
|
|
// Additional flags include performance info, loader and layer debug messages, etc.
|
|
vks::debug::setupDebugging(instance, debugReportFlags, VK_NULL_HANDLE);
|
|
}
|
|
|
|
// Physical device
|
|
uint32_t gpuCount = 0;
|
|
// Get number of available physical devices
|
|
VK_CHECK_RESULT(vkEnumeratePhysicalDevices(instance, &gpuCount, nullptr));
|
|
assert(gpuCount > 0);
|
|
// Enumerate devices
|
|
std::vector<VkPhysicalDevice> physicalDevices(gpuCount);
|
|
err = vkEnumeratePhysicalDevices(instance, &gpuCount, physicalDevices.data());
|
|
if (err) {
|
|
vks::tools::exitFatal("Could not enumerate physical devices : \n" + vks::tools::errorString(err), err);
|
|
return false;
|
|
}
|
|
|
|
// GPU selection
|
|
|
|
// Select physical device to be used for the Vulkan example
|
|
// Defaults to the first device unless specified by command line
|
|
uint32_t selectedDevice = 0;
|
|
|
|
#if !defined(VK_USE_PLATFORM_ANDROID_KHR)
|
|
// GPU selection via command line argument
|
|
for (size_t i = 0; i < args.size(); i++)
|
|
{
|
|
// Select GPU
|
|
if ((args[i] == std::string("-g")) || (args[i] == std::string("-gpu")))
|
|
{
|
|
char* endptr;
|
|
uint32_t index = strtol(args[i + 1], &endptr, 10);
|
|
if (endptr != args[i + 1])
|
|
{
|
|
if (index > gpuCount - 1)
|
|
{
|
|
std::cerr << "Selected device index " << index << " is out of range, reverting to device 0 (use -listgpus to show available Vulkan devices)" << std::endl;
|
|
}
|
|
else
|
|
{
|
|
std::cout << "Selected Vulkan device " << index << std::endl;
|
|
selectedDevice = index;
|
|
}
|
|
};
|
|
break;
|
|
}
|
|
// List available GPUs
|
|
if (args[i] == std::string("-listgpus"))
|
|
{
|
|
uint32_t gpuCount = 0;
|
|
VK_CHECK_RESULT(vkEnumeratePhysicalDevices(instance, &gpuCount, nullptr));
|
|
if (gpuCount == 0)
|
|
{
|
|
std::cerr << "No Vulkan devices found!" << std::endl;
|
|
}
|
|
else
|
|
{
|
|
// Enumerate devices
|
|
std::cout << "Available Vulkan devices" << std::endl;
|
|
std::vector<VkPhysicalDevice> devices(gpuCount);
|
|
VK_CHECK_RESULT(vkEnumeratePhysicalDevices(instance, &gpuCount, devices.data()));
|
|
for (uint32_t i = 0; i < gpuCount; i++) {
|
|
VkPhysicalDeviceProperties deviceProperties;
|
|
vkGetPhysicalDeviceProperties(devices[i], &deviceProperties);
|
|
std::cout << "Device [" << i << "] : " << deviceProperties.deviceName << std::endl;
|
|
std::cout << " Type: " << vks::tools::physicalDeviceTypeString(deviceProperties.deviceType) << std::endl;
|
|
std::cout << " API: " << (deviceProperties.apiVersion >> 22) << "." << ((deviceProperties.apiVersion >> 12) & 0x3ff) << "." << (deviceProperties.apiVersion & 0xfff) << std::endl;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
physicalDevice = physicalDevices[selectedDevice];
|
|
|
|
// Store properties (including limits), features and memory properties of the phyiscal device (so that examples can check against them)
|
|
vkGetPhysicalDeviceProperties(physicalDevice, &deviceProperties);
|
|
vkGetPhysicalDeviceFeatures(physicalDevice, &deviceFeatures);
|
|
vkGetPhysicalDeviceMemoryProperties(physicalDevice, &deviceMemoryProperties);
|
|
|
|
// Derived examples can override this to set actual features (based on above readings) to enable for logical device creation
|
|
getEnabledFeatures();
|
|
|
|
// Vulkan device creation
|
|
// This is handled by a separate class that gets a logical device representation
|
|
// and encapsulates functions related to a device
|
|
vulkanDevice = new vks::VulkanDevice(physicalDevice);
|
|
VkResult res = vulkanDevice->createLogicalDevice(enabledFeatures, enabledDeviceExtensions);
|
|
if (res != VK_SUCCESS) {
|
|
vks::tools::exitFatal("Could not create Vulkan device: \n" + vks::tools::errorString(res), res);
|
|
return false;
|
|
}
|
|
device = vulkanDevice->logicalDevice;
|
|
|
|
// Get a graphics queue from the device
|
|
vkGetDeviceQueue(device, vulkanDevice->queueFamilyIndices.graphics, 0, &queue);
|
|
|
|
// Find a suitable depth format
|
|
VkBool32 validDepthFormat = vks::tools::getSupportedDepthFormat(physicalDevice, &depthFormat);
|
|
assert(validDepthFormat);
|
|
|
|
swapChain.connect(instance, physicalDevice, device);
|
|
|
|
// Create synchronization objects
|
|
VkSemaphoreCreateInfo semaphoreCreateInfo = vks::initializers::semaphoreCreateInfo();
|
|
// Create a semaphore used to synchronize image presentation
|
|
// Ensures that the image is displayed before we start submitting new commands to the queu
|
|
VK_CHECK_RESULT(vkCreateSemaphore(device, &semaphoreCreateInfo, nullptr, &semaphores.presentComplete));
|
|
// Create a semaphore used to synchronize command submission
|
|
// Ensures that the image is not presented until all commands have been sumbitted and executed
|
|
VK_CHECK_RESULT(vkCreateSemaphore(device, &semaphoreCreateInfo, nullptr, &semaphores.renderComplete));
|
|
|
|
// Set up submit info structure
|
|
// Semaphores will stay the same during application lifetime
|
|
// Command buffer submission info is set by each example
|
|
submitInfo = vks::initializers::submitInfo();
|
|
submitInfo.pWaitDstStageMask = &submitPipelineStages;
|
|
submitInfo.waitSemaphoreCount = 1;
|
|
submitInfo.pWaitSemaphores = &semaphores.presentComplete;
|
|
submitInfo.signalSemaphoreCount = 1;
|
|
submitInfo.pSignalSemaphores = &semaphores.renderComplete;
|
|
|
|
#if defined(VK_USE_PLATFORM_ANDROID_KHR)
|
|
// Get Android device name and manufacturer (to display along GPU name)
|
|
androidProduct = "";
|
|
char prop[PROP_VALUE_MAX+1];
|
|
int len = __system_property_get("ro.product.manufacturer", prop);
|
|
if (len > 0) {
|
|
androidProduct += std::string(prop) + " ";
|
|
};
|
|
len = __system_property_get("ro.product.model", prop);
|
|
if (len > 0) {
|
|
androidProduct += std::string(prop);
|
|
};
|
|
LOGD("androidProduct = %s", androidProduct.c_str());
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
#if defined(_WIN32)
|
|
// Win32 : Sets up a console window and redirects standard output to it
|
|
void VulkanExampleBase::setupConsole(std::string title)
|
|
{
|
|
AllocConsole();
|
|
AttachConsole(GetCurrentProcessId());
|
|
FILE *stream;
|
|
freopen_s(&stream, "CONOUT$", "w+", stdout);
|
|
freopen_s(&stream, "CONOUT$", "w+", stderr);
|
|
SetConsoleTitle(TEXT(title.c_str()));
|
|
}
|
|
|
|
void VulkanExampleBase::setupDPIAwareness()
|
|
{
|
|
using SetProcessDpiAwarenessFunc = HRESULT(*)(PROCESS_DPI_AWARENESS);
|
|
|
|
HMODULE shCore = LoadLibraryA("Shcore.dll");
|
|
if (shCore)
|
|
{
|
|
SetProcessDpiAwarenessFunc setProcessDpiAwareness =
|
|
(SetProcessDpiAwarenessFunc)GetProcAddress(shCore, "SetProcessDpiAwareness");
|
|
|
|
if (setProcessDpiAwareness != nullptr)
|
|
{
|
|
setProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE);
|
|
}
|
|
|
|
FreeLibrary(shCore);
|
|
}
|
|
}
|
|
|
|
HWND VulkanExampleBase::setupWindow(HINSTANCE hinstance, WNDPROC wndproc)
|
|
{
|
|
this->windowInstance = hinstance;
|
|
|
|
WNDCLASSEX wndClass;
|
|
|
|
wndClass.cbSize = sizeof(WNDCLASSEX);
|
|
wndClass.style = CS_HREDRAW | CS_VREDRAW;
|
|
wndClass.lpfnWndProc = wndproc;
|
|
wndClass.cbClsExtra = 0;
|
|
wndClass.cbWndExtra = 0;
|
|
wndClass.hInstance = hinstance;
|
|
wndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
|
|
wndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
|
|
wndClass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
|
|
wndClass.lpszMenuName = NULL;
|
|
wndClass.lpszClassName = name.c_str();
|
|
wndClass.hIconSm = LoadIcon(NULL, IDI_WINLOGO);
|
|
|
|
if (!RegisterClassEx(&wndClass))
|
|
{
|
|
std::cout << "Could not register window class!\n";
|
|
fflush(stdout);
|
|
exit(1);
|
|
}
|
|
|
|
int screenWidth = GetSystemMetrics(SM_CXSCREEN);
|
|
int screenHeight = GetSystemMetrics(SM_CYSCREEN);
|
|
|
|
if (settings.fullscreen)
|
|
{
|
|
DEVMODE dmScreenSettings;
|
|
memset(&dmScreenSettings, 0, sizeof(dmScreenSettings));
|
|
dmScreenSettings.dmSize = sizeof(dmScreenSettings);
|
|
dmScreenSettings.dmPelsWidth = screenWidth;
|
|
dmScreenSettings.dmPelsHeight = screenHeight;
|
|
dmScreenSettings.dmBitsPerPel = 32;
|
|
dmScreenSettings.dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT;
|
|
|
|
if ((width != (uint32_t)screenWidth) && (height != (uint32_t)screenHeight))
|
|
{
|
|
if (ChangeDisplaySettings(&dmScreenSettings, CDS_FULLSCREEN) != DISP_CHANGE_SUCCESSFUL)
|
|
{
|
|
if (MessageBox(NULL, "Fullscreen Mode not supported!\n Switch to window mode?", "Error", MB_YESNO | MB_ICONEXCLAMATION) == IDYES)
|
|
{
|
|
settings.fullscreen = false;
|
|
}
|
|
else
|
|
{
|
|
return nullptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
DWORD dwExStyle;
|
|
DWORD dwStyle;
|
|
|
|
if (settings.fullscreen)
|
|
{
|
|
dwExStyle = WS_EX_APPWINDOW;
|
|
dwStyle = WS_POPUP | WS_CLIPSIBLINGS | WS_CLIPCHILDREN;
|
|
}
|
|
else
|
|
{
|
|
dwExStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE;
|
|
dwStyle = WS_OVERLAPPEDWINDOW | WS_CLIPSIBLINGS | WS_CLIPCHILDREN;
|
|
}
|
|
|
|
RECT windowRect;
|
|
windowRect.left = 0L;
|
|
windowRect.top = 0L;
|
|
windowRect.right = settings.fullscreen ? (long)screenWidth : (long)width;
|
|
windowRect.bottom = settings.fullscreen ? (long)screenHeight : (long)height;
|
|
|
|
AdjustWindowRectEx(&windowRect, dwStyle, FALSE, dwExStyle);
|
|
|
|
std::string windowTitle = getWindowTitle();
|
|
window = CreateWindowEx(0,
|
|
name.c_str(),
|
|
windowTitle.c_str(),
|
|
dwStyle | WS_CLIPSIBLINGS | WS_CLIPCHILDREN,
|
|
0,
|
|
0,
|
|
windowRect.right - windowRect.left,
|
|
windowRect.bottom - windowRect.top,
|
|
NULL,
|
|
NULL,
|
|
hinstance,
|
|
NULL);
|
|
|
|
if (!settings.fullscreen)
|
|
{
|
|
// Center on screen
|
|
uint32_t x = (GetSystemMetrics(SM_CXSCREEN) - windowRect.right) / 2;
|
|
uint32_t y = (GetSystemMetrics(SM_CYSCREEN) - windowRect.bottom) / 2;
|
|
SetWindowPos(window, 0, x, y, 0, 0, SWP_NOZORDER | SWP_NOSIZE);
|
|
}
|
|
|
|
if (!window)
|
|
{
|
|
printf("Could not create window!\n");
|
|
fflush(stdout);
|
|
return nullptr;
|
|
exit(1);
|
|
}
|
|
|
|
ShowWindow(window, SW_SHOW);
|
|
SetForegroundWindow(window);
|
|
SetFocus(window);
|
|
|
|
return window;
|
|
}
|
|
|
|
void VulkanExampleBase::handleMessages(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
switch (uMsg)
|
|
{
|
|
case WM_CLOSE:
|
|
prepared = false;
|
|
DestroyWindow(hWnd);
|
|
PostQuitMessage(0);
|
|
break;
|
|
case WM_PAINT:
|
|
ValidateRect(window, NULL);
|
|
break;
|
|
case WM_KEYDOWN:
|
|
switch (wParam)
|
|
{
|
|
case KEY_P:
|
|
paused = !paused;
|
|
break;
|
|
case KEY_F1:
|
|
if (settings.overlay) {
|
|
UIOverlay.visible = !UIOverlay.visible;
|
|
}
|
|
break;
|
|
case KEY_ESCAPE:
|
|
PostQuitMessage(0);
|
|
break;
|
|
}
|
|
|
|
if (camera.firstperson)
|
|
{
|
|
switch (wParam)
|
|
{
|
|
case KEY_W:
|
|
camera.keys.up = true;
|
|
break;
|
|
case KEY_S:
|
|
camera.keys.down = true;
|
|
break;
|
|
case KEY_A:
|
|
camera.keys.left = true;
|
|
break;
|
|
case KEY_D:
|
|
camera.keys.right = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
keyPressed((uint32_t)wParam);
|
|
break;
|
|
case WM_KEYUP:
|
|
if (camera.firstperson)
|
|
{
|
|
switch (wParam)
|
|
{
|
|
case KEY_W:
|
|
camera.keys.up = false;
|
|
break;
|
|
case KEY_S:
|
|
camera.keys.down = false;
|
|
break;
|
|
case KEY_A:
|
|
camera.keys.left = false;
|
|
break;
|
|
case KEY_D:
|
|
camera.keys.right = false;
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
case WM_LBUTTONDOWN:
|
|
mousePos = glm::vec2((float)LOWORD(lParam), (float)HIWORD(lParam));
|
|
mouseButtons.left = true;
|
|
break;
|
|
case WM_RBUTTONDOWN:
|
|
mousePos = glm::vec2((float)LOWORD(lParam), (float)HIWORD(lParam));
|
|
mouseButtons.right = true;
|
|
break;
|
|
case WM_MBUTTONDOWN:
|
|
mousePos = glm::vec2((float)LOWORD(lParam), (float)HIWORD(lParam));
|
|
mouseButtons.middle = true;
|
|
break;
|
|
case WM_LBUTTONUP:
|
|
mouseButtons.left = false;
|
|
break;
|
|
case WM_RBUTTONUP:
|
|
mouseButtons.right = false;
|
|
break;
|
|
case WM_MBUTTONUP:
|
|
mouseButtons.middle = false;
|
|
break;
|
|
case WM_MOUSEWHEEL:
|
|
{
|
|
short wheelDelta = GET_WHEEL_DELTA_WPARAM(wParam);
|
|
zoom += (float)wheelDelta * 0.005f * zoomSpeed;
|
|
camera.translate(glm::vec3(0.0f, 0.0f, (float)wheelDelta * 0.005f * zoomSpeed));
|
|
viewUpdated = true;
|
|
break;
|
|
}
|
|
case WM_MOUSEMOVE:
|
|
{
|
|
handleMouseMove(LOWORD(lParam), HIWORD(lParam));
|
|
break;
|
|
}
|
|
case WM_SIZE:
|
|
if ((prepared) && (wParam != SIZE_MINIMIZED))
|
|
{
|
|
if ((resizing) || ((wParam == SIZE_MAXIMIZED) || (wParam == SIZE_RESTORED)))
|
|
{
|
|
destWidth = LOWORD(lParam);
|
|
destHeight = HIWORD(lParam);
|
|
windowResize();
|
|
}
|
|
}
|
|
break;
|
|
case WM_ENTERSIZEMOVE:
|
|
resizing = true;
|
|
break;
|
|
case WM_EXITSIZEMOVE:
|
|
resizing = false;
|
|
break;
|
|
}
|
|
}
|
|
#elif defined(VK_USE_PLATFORM_ANDROID_KHR)
|
|
int32_t VulkanExampleBase::handleAppInput(struct android_app* app, AInputEvent* event)
|
|
{
|
|
VulkanExampleBase* vulkanExample = reinterpret_cast<VulkanExampleBase*>(app->userData);
|
|
if (AInputEvent_getType(event) == AINPUT_EVENT_TYPE_MOTION)
|
|
{
|
|
int32_t eventSource = AInputEvent_getSource(event);
|
|
switch (eventSource) {
|
|
case AINPUT_SOURCE_JOYSTICK: {
|
|
// Left thumbstick
|
|
vulkanExample->gamePadState.axisLeft.x = AMotionEvent_getAxisValue(event, AMOTION_EVENT_AXIS_X, 0);
|
|
vulkanExample->gamePadState.axisLeft.y = AMotionEvent_getAxisValue(event, AMOTION_EVENT_AXIS_Y, 0);
|
|
// Right thumbstick
|
|
vulkanExample->gamePadState.axisRight.x = AMotionEvent_getAxisValue(event, AMOTION_EVENT_AXIS_Z, 0);
|
|
vulkanExample->gamePadState.axisRight.y = AMotionEvent_getAxisValue(event, AMOTION_EVENT_AXIS_RZ, 0);
|
|
break;
|
|
}
|
|
|
|
case AINPUT_SOURCE_TOUCHSCREEN: {
|
|
int32_t action = AMotionEvent_getAction(event);
|
|
|
|
switch (action) {
|
|
case AMOTION_EVENT_ACTION_UP: {
|
|
vulkanExample->lastTapTime = AMotionEvent_getEventTime(event);
|
|
vulkanExample->touchPos.x = AMotionEvent_getX(event, 0);
|
|
vulkanExample->touchPos.y = AMotionEvent_getY(event, 0);
|
|
vulkanExample->touchTimer = 0.0;
|
|
vulkanExample->touchDown = false;
|
|
vulkanExample->camera.keys.up = false;
|
|
|
|
// Detect single tap
|
|
int64_t eventTime = AMotionEvent_getEventTime(event);
|
|
int64_t downTime = AMotionEvent_getDownTime(event);
|
|
if (eventTime - downTime <= vks::android::TAP_TIMEOUT) {
|
|
float deadZone = (160.f / vks::android::screenDensity) * vks::android::TAP_SLOP * vks::android::TAP_SLOP;
|
|
float x = AMotionEvent_getX(event, 0) - vulkanExample->touchPos.x;
|
|
float y = AMotionEvent_getY(event, 0) - vulkanExample->touchPos.y;
|
|
if ((x * x + y * y) < deadZone) {
|
|
vulkanExample->mouseButtons.left = true;
|
|
}
|
|
};
|
|
|
|
return 1;
|
|
break;
|
|
}
|
|
case AMOTION_EVENT_ACTION_DOWN: {
|
|
// Detect double tap
|
|
int64_t eventTime = AMotionEvent_getEventTime(event);
|
|
if (eventTime - vulkanExample->lastTapTime <= vks::android::DOUBLE_TAP_TIMEOUT) {
|
|
float deadZone = (160.f / vks::android::screenDensity) * vks::android::DOUBLE_TAP_SLOP * vks::android::DOUBLE_TAP_SLOP;
|
|
float x = AMotionEvent_getX(event, 0) - vulkanExample->touchPos.x;
|
|
float y = AMotionEvent_getY(event, 0) - vulkanExample->touchPos.y;
|
|
if ((x * x + y * y) < deadZone) {
|
|
vulkanExample->keyPressed(TOUCH_DOUBLE_TAP);
|
|
vulkanExample->touchDown = false;
|
|
}
|
|
}
|
|
else {
|
|
vulkanExample->touchDown = true;
|
|
}
|
|
vulkanExample->touchPos.x = AMotionEvent_getX(event, 0);
|
|
vulkanExample->touchPos.y = AMotionEvent_getY(event, 0);
|
|
vulkanExample->mousePos.x = AMotionEvent_getX(event, 0);
|
|
vulkanExample->mousePos.y = AMotionEvent_getY(event, 0);
|
|
break;
|
|
}
|
|
case AMOTION_EVENT_ACTION_MOVE: {
|
|
bool handled = false;
|
|
if (vulkanExample->settings.overlay) {
|
|
ImGuiIO& io = ImGui::GetIO();
|
|
handled = io.WantCaptureMouse;
|
|
}
|
|
if (!handled) {
|
|
int32_t eventX = AMotionEvent_getX(event, 0);
|
|
int32_t eventY = AMotionEvent_getY(event, 0);
|
|
|
|
float deltaX = (float)(vulkanExample->touchPos.y - eventY) * vulkanExample->rotationSpeed * 0.5f;
|
|
float deltaY = (float)(vulkanExample->touchPos.x - eventX) * vulkanExample->rotationSpeed * 0.5f;
|
|
|
|
vulkanExample->camera.rotate(glm::vec3(deltaX, 0.0f, 0.0f));
|
|
vulkanExample->camera.rotate(glm::vec3(0.0f, -deltaY, 0.0f));
|
|
|
|
vulkanExample->rotation.x += deltaX;
|
|
vulkanExample->rotation.y -= deltaY;
|
|
|
|
vulkanExample->viewChanged();
|
|
|
|
vulkanExample->touchPos.x = eventX;
|
|
vulkanExample->touchPos.y = eventY;
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
return 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
if (AInputEvent_getType(event) == AINPUT_EVENT_TYPE_KEY)
|
|
{
|
|
int32_t keyCode = AKeyEvent_getKeyCode((const AInputEvent*)event);
|
|
int32_t action = AKeyEvent_getAction((const AInputEvent*)event);
|
|
int32_t button = 0;
|
|
|
|
if (action == AKEY_EVENT_ACTION_UP)
|
|
return 0;
|
|
|
|
switch (keyCode)
|
|
{
|
|
case AKEYCODE_BUTTON_A:
|
|
vulkanExample->keyPressed(GAMEPAD_BUTTON_A);
|
|
break;
|
|
case AKEYCODE_BUTTON_B:
|
|
vulkanExample->keyPressed(GAMEPAD_BUTTON_B);
|
|
break;
|
|
case AKEYCODE_BUTTON_X:
|
|
vulkanExample->keyPressed(GAMEPAD_BUTTON_X);
|
|
break;
|
|
case AKEYCODE_BUTTON_Y:
|
|
vulkanExample->keyPressed(GAMEPAD_BUTTON_Y);
|
|
break;
|
|
case AKEYCODE_BUTTON_L1:
|
|
vulkanExample->keyPressed(GAMEPAD_BUTTON_L1);
|
|
break;
|
|
case AKEYCODE_BUTTON_R1:
|
|
vulkanExample->keyPressed(GAMEPAD_BUTTON_R1);
|
|
break;
|
|
case AKEYCODE_BUTTON_START:
|
|
vulkanExample->paused = !vulkanExample->paused;
|
|
break;
|
|
};
|
|
|
|
LOGD("Button %d pressed", keyCode);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void VulkanExampleBase::handleAppCommand(android_app * app, int32_t cmd)
|
|
{
|
|
assert(app->userData != NULL);
|
|
VulkanExampleBase* vulkanExample = reinterpret_cast<VulkanExampleBase*>(app->userData);
|
|
switch (cmd)
|
|
{
|
|
case APP_CMD_SAVE_STATE:
|
|
LOGD("APP_CMD_SAVE_STATE");
|
|
/*
|
|
vulkanExample->app->savedState = malloc(sizeof(struct saved_state));
|
|
*((struct saved_state*)vulkanExample->app->savedState) = vulkanExample->state;
|
|
vulkanExample->app->savedStateSize = sizeof(struct saved_state);
|
|
*/
|
|
break;
|
|
case APP_CMD_INIT_WINDOW:
|
|
LOGD("APP_CMD_INIT_WINDOW");
|
|
if (androidApp->window != NULL)
|
|
{
|
|
if (vulkanExample->initVulkan()) {
|
|
vulkanExample->prepare();
|
|
assert(vulkanExample->prepared);
|
|
}
|
|
else {
|
|
LOGE("Could not initialize Vulkan, exiting!");
|
|
androidApp->destroyRequested = 1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LOGE("No window assigned!");
|
|
}
|
|
break;
|
|
case APP_CMD_LOST_FOCUS:
|
|
LOGD("APP_CMD_LOST_FOCUS");
|
|
vulkanExample->focused = false;
|
|
break;
|
|
case APP_CMD_GAINED_FOCUS:
|
|
LOGD("APP_CMD_GAINED_FOCUS");
|
|
vulkanExample->focused = true;
|
|
break;
|
|
case APP_CMD_TERM_WINDOW:
|
|
// Window is hidden or closed, clean up resources
|
|
LOGD("APP_CMD_TERM_WINDOW");
|
|
if (vulkanExample->prepared) {
|
|
vulkanExample->swapChain.cleanup();
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
#elif (defined(VK_USE_PLATFORM_IOS_MVK) || defined(VK_USE_PLATFORM_MACOS_MVK))
|
|
void* VulkanExampleBase::setupWindow(void* view)
|
|
{
|
|
this->view = view;
|
|
return view;
|
|
}
|
|
#elif defined(_DIRECT2DISPLAY)
|
|
#elif defined(VK_USE_PLATFORM_WAYLAND_KHR)
|
|
/*static*/void VulkanExampleBase::registryGlobalCb(void *data,
|
|
wl_registry *registry, uint32_t name, const char *interface,
|
|
uint32_t version)
|
|
{
|
|
VulkanExampleBase *self = reinterpret_cast<VulkanExampleBase *>(data);
|
|
self->registryGlobal(registry, name, interface, version);
|
|
}
|
|
|
|
/*static*/void VulkanExampleBase::seatCapabilitiesCb(void *data, wl_seat *seat,
|
|
uint32_t caps)
|
|
{
|
|
VulkanExampleBase *self = reinterpret_cast<VulkanExampleBase *>(data);
|
|
self->seatCapabilities(seat, caps);
|
|
}
|
|
|
|
/*static*/void VulkanExampleBase::pointerEnterCb(void *data,
|
|
wl_pointer *pointer, uint32_t serial, wl_surface *surface,
|
|
wl_fixed_t sx, wl_fixed_t sy)
|
|
{
|
|
}
|
|
|
|
/*static*/void VulkanExampleBase::pointerLeaveCb(void *data,
|
|
wl_pointer *pointer, uint32_t serial, wl_surface *surface)
|
|
{
|
|
}
|
|
|
|
/*static*/void VulkanExampleBase::pointerMotionCb(void *data,
|
|
wl_pointer *pointer, uint32_t time, wl_fixed_t sx, wl_fixed_t sy)
|
|
{
|
|
VulkanExampleBase *self = reinterpret_cast<VulkanExampleBase *>(data);
|
|
self->pointerMotion(pointer, time, sx, sy);
|
|
}
|
|
void VulkanExampleBase::pointerMotion(wl_pointer *pointer, uint32_t time, wl_fixed_t sx, wl_fixed_t sy)
|
|
{
|
|
handleMouseMove(wl_fixed_to_int(sx), wl_fixed_to_int(sy));
|
|
}
|
|
|
|
/*static*/void VulkanExampleBase::pointerButtonCb(void *data,
|
|
wl_pointer *pointer, uint32_t serial, uint32_t time, uint32_t button,
|
|
uint32_t state)
|
|
{
|
|
VulkanExampleBase *self = reinterpret_cast<VulkanExampleBase *>(data);
|
|
self->pointerButton(pointer, serial, time, button, state);
|
|
}
|
|
|
|
void VulkanExampleBase::pointerButton(struct wl_pointer *pointer,
|
|
uint32_t serial, uint32_t time, uint32_t button, uint32_t state)
|
|
{
|
|
switch (button)
|
|
{
|
|
case BTN_LEFT:
|
|
mouseButtons.left = !!state;
|
|
break;
|
|
case BTN_MIDDLE:
|
|
mouseButtons.middle = !!state;
|
|
break;
|
|
case BTN_RIGHT:
|
|
mouseButtons.right = !!state;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*static*/void VulkanExampleBase::pointerAxisCb(void *data,
|
|
wl_pointer *pointer, uint32_t time, uint32_t axis,
|
|
wl_fixed_t value)
|
|
{
|
|
VulkanExampleBase *self = reinterpret_cast<VulkanExampleBase *>(data);
|
|
self->pointerAxis(pointer, time, axis, value);
|
|
}
|
|
|
|
void VulkanExampleBase::pointerAxis(wl_pointer *pointer, uint32_t time,
|
|
uint32_t axis, wl_fixed_t value)
|
|
{
|
|
double d = wl_fixed_to_double(value);
|
|
switch (axis)
|
|
{
|
|
case REL_X:
|
|
zoom += d * 0.005f * zoomSpeed;
|
|
camera.translate(glm::vec3(0.0f, 0.0f, d * 0.005f * zoomSpeed));
|
|
viewUpdated = true;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*static*/void VulkanExampleBase::keyboardKeymapCb(void *data,
|
|
struct wl_keyboard *keyboard, uint32_t format, int fd, uint32_t size)
|
|
{
|
|
}
|
|
|
|
/*static*/void VulkanExampleBase::keyboardEnterCb(void *data,
|
|
struct wl_keyboard *keyboard, uint32_t serial,
|
|
struct wl_surface *surface, struct wl_array *keys)
|
|
{
|
|
}
|
|
|
|
/*static*/void VulkanExampleBase::keyboardLeaveCb(void *data,
|
|
struct wl_keyboard *keyboard, uint32_t serial,
|
|
struct wl_surface *surface)
|
|
{
|
|
}
|
|
|
|
/*static*/void VulkanExampleBase::keyboardKeyCb(void *data,
|
|
struct wl_keyboard *keyboard, uint32_t serial, uint32_t time,
|
|
uint32_t key, uint32_t state)
|
|
{
|
|
VulkanExampleBase *self = reinterpret_cast<VulkanExampleBase *>(data);
|
|
self->keyboardKey(keyboard, serial, time, key, state);
|
|
}
|
|
|
|
void VulkanExampleBase::keyboardKey(struct wl_keyboard *keyboard,
|
|
uint32_t serial, uint32_t time, uint32_t key, uint32_t state)
|
|
{
|
|
switch (key)
|
|
{
|
|
case KEY_W:
|
|
camera.keys.up = !!state;
|
|
break;
|
|
case KEY_S:
|
|
camera.keys.down = !!state;
|
|
break;
|
|
case KEY_A:
|
|
camera.keys.left = !!state;
|
|
break;
|
|
case KEY_D:
|
|
camera.keys.right = !!state;
|
|
break;
|
|
case KEY_P:
|
|
if (state)
|
|
paused = !paused;
|
|
break;
|
|
case KEY_F1:
|
|
if (state && settings.overlay)
|
|
settings.overlay = !settings.overlay;
|
|
break;
|
|
case KEY_ESC:
|
|
quit = true;
|
|
break;
|
|
}
|
|
|
|
if (state)
|
|
keyPressed(key);
|
|
}
|
|
|
|
/*static*/void VulkanExampleBase::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)
|
|
{
|
|
}
|
|
|
|
void VulkanExampleBase::seatCapabilities(wl_seat *seat, uint32_t caps)
|
|
{
|
|
if ((caps & WL_SEAT_CAPABILITY_POINTER) && !pointer)
|
|
{
|
|
pointer = wl_seat_get_pointer(seat);
|
|
static const struct wl_pointer_listener pointer_listener =
|
|
{ pointerEnterCb, pointerLeaveCb, pointerMotionCb, pointerButtonCb,
|
|
pointerAxisCb, };
|
|
wl_pointer_add_listener(pointer, &pointer_listener, this);
|
|
}
|
|
else if (!(caps & WL_SEAT_CAPABILITY_POINTER) && pointer)
|
|
{
|
|
wl_pointer_destroy(pointer);
|
|
pointer = nullptr;
|
|
}
|
|
|
|
if ((caps & WL_SEAT_CAPABILITY_KEYBOARD) && !keyboard)
|
|
{
|
|
keyboard = wl_seat_get_keyboard(seat);
|
|
static const struct wl_keyboard_listener keyboard_listener =
|
|
{ keyboardKeymapCb, keyboardEnterCb, keyboardLeaveCb, keyboardKeyCb,
|
|
keyboardModifiersCb, };
|
|
wl_keyboard_add_listener(keyboard, &keyboard_listener, this);
|
|
}
|
|
else if (!(caps & WL_SEAT_CAPABILITY_KEYBOARD) && keyboard)
|
|
{
|
|
wl_keyboard_destroy(keyboard);
|
|
keyboard = nullptr;
|
|
}
|
|
}
|
|
|
|
static void xdg_wm_base_ping(void *data, struct xdg_wm_base *shell, uint32_t serial)
|
|
{
|
|
xdg_wm_base_pong(shell, serial);
|
|
}
|
|
|
|
static const struct xdg_wm_base_listener xdg_wm_base_listener = {
|
|
xdg_wm_base_ping,
|
|
};
|
|
|
|
void VulkanExampleBase::registryGlobal(wl_registry *registry, uint32_t name,
|
|
const char *interface, uint32_t version)
|
|
{
|
|
if (strcmp(interface, "wl_compositor") == 0)
|
|
{
|
|
compositor = (wl_compositor *) wl_registry_bind(registry, name,
|
|
&wl_compositor_interface, 3);
|
|
}
|
|
else if (strcmp(interface, "xdg_wm_base") == 0)
|
|
{
|
|
shell = (xdg_wm_base *) wl_registry_bind(registry, name,
|
|
&xdg_wm_base_interface, 1);
|
|
xdg_wm_base_add_listener(shell, &xdg_wm_base_listener, nullptr);
|
|
}
|
|
else if (strcmp(interface, "wl_seat") == 0)
|
|
{
|
|
seat = (wl_seat *) wl_registry_bind(registry, name, &wl_seat_interface,
|
|
1);
|
|
|
|
static const struct wl_seat_listener seat_listener =
|
|
{ seatCapabilitiesCb, };
|
|
wl_seat_add_listener(seat, &seat_listener, this);
|
|
}
|
|
}
|
|
|
|
/*static*/void VulkanExampleBase::registryGlobalRemoveCb(void *data,
|
|
struct wl_registry *registry, uint32_t name)
|
|
{
|
|
}
|
|
|
|
void VulkanExampleBase::initWaylandConnection()
|
|
{
|
|
display = wl_display_connect(NULL);
|
|
if (!display)
|
|
{
|
|
std::cout << "Could not connect to Wayland display!\n";
|
|
fflush(stdout);
|
|
exit(1);
|
|
}
|
|
|
|
registry = wl_display_get_registry(display);
|
|
if (!registry)
|
|
{
|
|
std::cout << "Could not get Wayland registry!\n";
|
|
fflush(stdout);
|
|
exit(1);
|
|
}
|
|
|
|
static const struct wl_registry_listener registry_listener =
|
|
{ registryGlobalCb, registryGlobalRemoveCb };
|
|
wl_registry_add_listener(registry, ®istry_listener, this);
|
|
wl_display_dispatch(display);
|
|
wl_display_roundtrip(display);
|
|
if (!compositor || !shell || !seat)
|
|
{
|
|
std::cout << "Could not bind Wayland protocols!\n";
|
|
fflush(stdout);
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
void VulkanExampleBase::setSize(int width, int height)
|
|
{
|
|
if (width <= 0 || height <= 0)
|
|
return;
|
|
|
|
destWidth = width;
|
|
destHeight = height;
|
|
|
|
windowResize();
|
|
}
|
|
|
|
static void
|
|
xdg_surface_handle_configure(void *data, struct xdg_surface *surface,
|
|
uint32_t serial)
|
|
{
|
|
VulkanExampleBase *base = (VulkanExampleBase *) data;
|
|
|
|
xdg_surface_ack_configure(surface, serial);
|
|
base->configured = true;
|
|
}
|
|
|
|
static const struct xdg_surface_listener xdg_surface_listener = {
|
|
xdg_surface_handle_configure,
|
|
};
|
|
|
|
|
|
static void
|
|
xdg_toplevel_handle_configure(void *data, struct xdg_toplevel *toplevel,
|
|
int32_t width, int32_t height,
|
|
struct wl_array *states)
|
|
{
|
|
VulkanExampleBase *base = (VulkanExampleBase *) data;
|
|
|
|
base->setSize(width, height);
|
|
}
|
|
|
|
static void
|
|
xdg_toplevel_handle_close(void *data, struct xdg_toplevel *xdg_toplevel)
|
|
{
|
|
VulkanExampleBase *base = (VulkanExampleBase *) data;
|
|
|
|
base->quit = true;
|
|
}
|
|
|
|
|
|
static const struct xdg_toplevel_listener xdg_toplevel_listener = {
|
|
xdg_toplevel_handle_configure,
|
|
xdg_toplevel_handle_close,
|
|
};
|
|
|
|
|
|
struct xdg_surface *VulkanExampleBase::setupWindow()
|
|
{
|
|
surface = wl_compositor_create_surface(compositor);
|
|
xdg_surface = xdg_wm_base_get_xdg_surface(shell, surface);
|
|
|
|
xdg_surface_add_listener(xdg_surface, &xdg_surface_listener, this);
|
|
xdg_toplevel = xdg_surface_get_toplevel(xdg_surface);
|
|
xdg_toplevel_add_listener(xdg_toplevel, &xdg_toplevel_listener, this);
|
|
|
|
std::string windowTitle = getWindowTitle();
|
|
xdg_toplevel_set_title(xdg_toplevel, windowTitle.c_str());
|
|
wl_surface_commit(surface);
|
|
return xdg_surface;
|
|
}
|
|
|
|
#elif defined(VK_USE_PLATFORM_XCB_KHR)
|
|
|
|
static inline xcb_intern_atom_reply_t* intern_atom_helper(xcb_connection_t *conn, bool only_if_exists, const char *str)
|
|
{
|
|
xcb_intern_atom_cookie_t cookie = xcb_intern_atom(conn, only_if_exists, strlen(str), str);
|
|
return xcb_intern_atom_reply(conn, cookie, NULL);
|
|
}
|
|
|
|
// Set up a window using XCB and request event types
|
|
xcb_window_t VulkanExampleBase::setupWindow()
|
|
{
|
|
uint32_t value_mask, value_list[32];
|
|
|
|
window = xcb_generate_id(connection);
|
|
|
|
value_mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK;
|
|
value_list[0] = screen->black_pixel;
|
|
value_list[1] =
|
|
XCB_EVENT_MASK_KEY_RELEASE |
|
|
XCB_EVENT_MASK_KEY_PRESS |
|
|
XCB_EVENT_MASK_EXPOSURE |
|
|
XCB_EVENT_MASK_STRUCTURE_NOTIFY |
|
|
XCB_EVENT_MASK_POINTER_MOTION |
|
|
XCB_EVENT_MASK_BUTTON_PRESS |
|
|
XCB_EVENT_MASK_BUTTON_RELEASE;
|
|
|
|
if (settings.fullscreen)
|
|
{
|
|
width = destWidth = screen->width_in_pixels;
|
|
height = destHeight = screen->height_in_pixels;
|
|
}
|
|
|
|
xcb_create_window(connection,
|
|
XCB_COPY_FROM_PARENT,
|
|
window, screen->root,
|
|
0, 0, width, height, 0,
|
|
XCB_WINDOW_CLASS_INPUT_OUTPUT,
|
|
screen->root_visual,
|
|
value_mask, value_list);
|
|
|
|
/* Magic code that will send notification when window is destroyed */
|
|
xcb_intern_atom_reply_t* reply = intern_atom_helper(connection, true, "WM_PROTOCOLS");
|
|
atom_wm_delete_window = intern_atom_helper(connection, false, "WM_DELETE_WINDOW");
|
|
|
|
xcb_change_property(connection, XCB_PROP_MODE_REPLACE,
|
|
window, (*reply).atom, 4, 32, 1,
|
|
&(*atom_wm_delete_window).atom);
|
|
|
|
std::string windowTitle = getWindowTitle();
|
|
xcb_change_property(connection, XCB_PROP_MODE_REPLACE,
|
|
window, XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 8,
|
|
title.size(), windowTitle.c_str());
|
|
|
|
free(reply);
|
|
|
|
if (settings.fullscreen)
|
|
{
|
|
xcb_intern_atom_reply_t *atom_wm_state = intern_atom_helper(connection, false, "_NET_WM_STATE");
|
|
xcb_intern_atom_reply_t *atom_wm_fullscreen = intern_atom_helper(connection, false, "_NET_WM_STATE_FULLSCREEN");
|
|
xcb_change_property(connection,
|
|
XCB_PROP_MODE_REPLACE,
|
|
window, atom_wm_state->atom,
|
|
XCB_ATOM_ATOM, 32, 1,
|
|
&(atom_wm_fullscreen->atom));
|
|
free(atom_wm_fullscreen);
|
|
free(atom_wm_state);
|
|
}
|
|
|
|
xcb_map_window(connection, window);
|
|
|
|
return(window);
|
|
}
|
|
|
|
// Initialize XCB connection
|
|
void VulkanExampleBase::initxcbConnection()
|
|
{
|
|
const xcb_setup_t *setup;
|
|
xcb_screen_iterator_t iter;
|
|
int scr;
|
|
|
|
connection = xcb_connect(NULL, &scr);
|
|
if (connection == NULL) {
|
|
printf("Could not find a compatible Vulkan ICD!\n");
|
|
fflush(stdout);
|
|
exit(1);
|
|
}
|
|
|
|
setup = xcb_get_setup(connection);
|
|
iter = xcb_setup_roots_iterator(setup);
|
|
while (scr-- > 0)
|
|
xcb_screen_next(&iter);
|
|
screen = iter.data;
|
|
}
|
|
|
|
void VulkanExampleBase::handleEvent(const xcb_generic_event_t *event)
|
|
{
|
|
switch (event->response_type & 0x7f)
|
|
{
|
|
case XCB_CLIENT_MESSAGE:
|
|
if ((*(xcb_client_message_event_t*)event).data.data32[0] ==
|
|
(*atom_wm_delete_window).atom) {
|
|
quit = true;
|
|
}
|
|
break;
|
|
case XCB_MOTION_NOTIFY:
|
|
{
|
|
xcb_motion_notify_event_t *motion = (xcb_motion_notify_event_t *)event;
|
|
handleMouseMove((int32_t)motion->event_x, (int32_t)motion->event_y);
|
|
break;
|
|
}
|
|
break;
|
|
case XCB_BUTTON_PRESS:
|
|
{
|
|
xcb_button_press_event_t *press = (xcb_button_press_event_t *)event;
|
|
if (press->detail == XCB_BUTTON_INDEX_1)
|
|
mouseButtons.left = true;
|
|
if (press->detail == XCB_BUTTON_INDEX_2)
|
|
mouseButtons.middle = true;
|
|
if (press->detail == XCB_BUTTON_INDEX_3)
|
|
mouseButtons.right = true;
|
|
}
|
|
break;
|
|
case XCB_BUTTON_RELEASE:
|
|
{
|
|
xcb_button_press_event_t *press = (xcb_button_press_event_t *)event;
|
|
if (press->detail == XCB_BUTTON_INDEX_1)
|
|
mouseButtons.left = false;
|
|
if (press->detail == XCB_BUTTON_INDEX_2)
|
|
mouseButtons.middle = false;
|
|
if (press->detail == XCB_BUTTON_INDEX_3)
|
|
mouseButtons.right = false;
|
|
}
|
|
break;
|
|
case XCB_KEY_PRESS:
|
|
{
|
|
const xcb_key_release_event_t *keyEvent = (const xcb_key_release_event_t *)event;
|
|
switch (keyEvent->detail)
|
|
{
|
|
case KEY_W:
|
|
camera.keys.up = true;
|
|
break;
|
|
case KEY_S:
|
|
camera.keys.down = true;
|
|
break;
|
|
case KEY_A:
|
|
camera.keys.left = true;
|
|
break;
|
|
case KEY_D:
|
|
camera.keys.right = true;
|
|
break;
|
|
case KEY_P:
|
|
paused = !paused;
|
|
break;
|
|
case KEY_F1:
|
|
if (settings.overlay) {
|
|
settings.overlay = !settings.overlay;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
case XCB_KEY_RELEASE:
|
|
{
|
|
const xcb_key_release_event_t *keyEvent = (const xcb_key_release_event_t *)event;
|
|
switch (keyEvent->detail)
|
|
{
|
|
case KEY_W:
|
|
camera.keys.up = false;
|
|
break;
|
|
case KEY_S:
|
|
camera.keys.down = false;
|
|
break;
|
|
case KEY_A:
|
|
camera.keys.left = false;
|
|
break;
|
|
case KEY_D:
|
|
camera.keys.right = false;
|
|
break;
|
|
case KEY_ESCAPE:
|
|
quit = true;
|
|
break;
|
|
}
|
|
keyPressed(keyEvent->detail);
|
|
}
|
|
break;
|
|
case XCB_DESTROY_NOTIFY:
|
|
quit = true;
|
|
break;
|
|
case XCB_CONFIGURE_NOTIFY:
|
|
{
|
|
const xcb_configure_notify_event_t *cfgEvent = (const xcb_configure_notify_event_t *)event;
|
|
if ((prepared) && ((cfgEvent->width != width) || (cfgEvent->height != height)))
|
|
{
|
|
destWidth = cfgEvent->width;
|
|
destHeight = cfgEvent->height;
|
|
if ((destWidth > 0) && (destHeight > 0))
|
|
{
|
|
windowResize();
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void VulkanExampleBase::viewChanged() {}
|
|
|
|
void VulkanExampleBase::keyPressed(uint32_t) {}
|
|
|
|
void VulkanExampleBase::mouseMoved(double x, double y, bool & handled) {}
|
|
|
|
void VulkanExampleBase::buildCommandBuffers() {}
|
|
|
|
void VulkanExampleBase::createSynchronizationPrimitives()
|
|
{
|
|
// Wait fences to sync command buffer access
|
|
VkFenceCreateInfo fenceCreateInfo = vks::initializers::fenceCreateInfo(VK_FENCE_CREATE_SIGNALED_BIT);
|
|
waitFences.resize(drawCmdBuffers.size());
|
|
for (auto& fence : waitFences) {
|
|
VK_CHECK_RESULT(vkCreateFence(device, &fenceCreateInfo, nullptr, &fence));
|
|
}
|
|
}
|
|
|
|
void VulkanExampleBase::createCommandPool()
|
|
{
|
|
VkCommandPoolCreateInfo cmdPoolInfo = {};
|
|
cmdPoolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
|
|
cmdPoolInfo.queueFamilyIndex = swapChain.queueNodeIndex;
|
|
cmdPoolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
|
|
VK_CHECK_RESULT(vkCreateCommandPool(device, &cmdPoolInfo, nullptr, &cmdPool));
|
|
}
|
|
|
|
void VulkanExampleBase::setupDepthStencil()
|
|
{
|
|
VkImageCreateInfo imageCI{};
|
|
imageCI.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
|
|
imageCI.imageType = VK_IMAGE_TYPE_2D;
|
|
imageCI.format = depthFormat;
|
|
imageCI.extent = { width, height, 1 };
|
|
imageCI.mipLevels = 1;
|
|
imageCI.arrayLayers = 1;
|
|
imageCI.samples = VK_SAMPLE_COUNT_1_BIT;
|
|
imageCI.tiling = VK_IMAGE_TILING_OPTIMAL;
|
|
imageCI.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
|
|
|
|
VK_CHECK_RESULT(vkCreateImage(device, &imageCI, nullptr, &depthStencil.image));
|
|
VkMemoryRequirements memReqs{};
|
|
vkGetImageMemoryRequirements(device, depthStencil.image, &memReqs);
|
|
|
|
VkMemoryAllocateInfo memAllloc{};
|
|
memAllloc.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
|
|
memAllloc.allocationSize = memReqs.size;
|
|
memAllloc.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
|
|
VK_CHECK_RESULT(vkAllocateMemory(device, &memAllloc, nullptr, &depthStencil.mem));
|
|
VK_CHECK_RESULT(vkBindImageMemory(device, depthStencil.image, depthStencil.mem, 0));
|
|
|
|
VkImageViewCreateInfo imageViewCI{};
|
|
imageViewCI.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
|
|
imageViewCI.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
|
imageViewCI.image = depthStencil.image;
|
|
imageViewCI.format = depthFormat;
|
|
imageViewCI.subresourceRange.baseMipLevel = 0;
|
|
imageViewCI.subresourceRange.levelCount = 1;
|
|
imageViewCI.subresourceRange.baseArrayLayer = 0;
|
|
imageViewCI.subresourceRange.layerCount = 1;
|
|
imageViewCI.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
|
|
// Stencil aspect should only be set on depth + stencil formats (VK_FORMAT_D16_UNORM_S8_UINT..VK_FORMAT_D32_SFLOAT_S8_UINT
|
|
if (depthFormat >= VK_FORMAT_D16_UNORM_S8_UINT) {
|
|
imageViewCI.subresourceRange.aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT;
|
|
}
|
|
VK_CHECK_RESULT(vkCreateImageView(device, &imageViewCI, nullptr, &depthStencil.view));
|
|
}
|
|
|
|
void VulkanExampleBase::setupFrameBuffer()
|
|
{
|
|
VkImageView attachments[2];
|
|
|
|
// Depth/Stencil attachment is the same for all frame buffers
|
|
attachments[1] = depthStencil.view;
|
|
|
|
VkFramebufferCreateInfo frameBufferCreateInfo = {};
|
|
frameBufferCreateInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
|
|
frameBufferCreateInfo.pNext = NULL;
|
|
frameBufferCreateInfo.renderPass = renderPass;
|
|
frameBufferCreateInfo.attachmentCount = 2;
|
|
frameBufferCreateInfo.pAttachments = attachments;
|
|
frameBufferCreateInfo.width = width;
|
|
frameBufferCreateInfo.height = height;
|
|
frameBufferCreateInfo.layers = 1;
|
|
|
|
// Create frame buffers for every swap chain image
|
|
frameBuffers.resize(swapChain.imageCount);
|
|
for (uint32_t i = 0; i < frameBuffers.size(); i++)
|
|
{
|
|
attachments[0] = swapChain.buffers[i].view;
|
|
VK_CHECK_RESULT(vkCreateFramebuffer(device, &frameBufferCreateInfo, nullptr, &frameBuffers[i]));
|
|
}
|
|
}
|
|
|
|
void VulkanExampleBase::setupRenderPass()
|
|
{
|
|
std::array<VkAttachmentDescription, 2> attachments = {};
|
|
// Color attachment
|
|
attachments[0].format = swapChain.colorFormat;
|
|
attachments[0].samples = VK_SAMPLE_COUNT_1_BIT;
|
|
attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
|
attachments[0].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
|
|
attachments[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
|
attachments[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
|
attachments[0].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
|
attachments[0].finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
|
|
// Depth attachment
|
|
attachments[1].format = depthFormat;
|
|
attachments[1].samples = VK_SAMPLE_COUNT_1_BIT;
|
|
attachments[1].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
|
attachments[1].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
|
|
attachments[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
|
attachments[1].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
|
attachments[1].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
|
attachments[1].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
|
|
|
VkAttachmentReference colorReference = {};
|
|
colorReference.attachment = 0;
|
|
colorReference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
|
|
|
VkAttachmentReference depthReference = {};
|
|
depthReference.attachment = 1;
|
|
depthReference.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
|
|
|
VkSubpassDescription subpassDescription = {};
|
|
subpassDescription.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
|
|
subpassDescription.colorAttachmentCount = 1;
|
|
subpassDescription.pColorAttachments = &colorReference;
|
|
subpassDescription.pDepthStencilAttachment = &depthReference;
|
|
subpassDescription.inputAttachmentCount = 0;
|
|
subpassDescription.pInputAttachments = nullptr;
|
|
subpassDescription.preserveAttachmentCount = 0;
|
|
subpassDescription.pPreserveAttachments = nullptr;
|
|
subpassDescription.pResolveAttachments = nullptr;
|
|
|
|
// Subpass dependencies for layout transitions
|
|
std::array<VkSubpassDependency, 2> dependencies;
|
|
|
|
dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL;
|
|
dependencies[0].dstSubpass = 0;
|
|
dependencies[0].srcStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
|
|
dependencies[0].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
|
dependencies[0].srcAccessMask = VK_ACCESS_MEMORY_READ_BIT;
|
|
dependencies[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
|
dependencies[0].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
|
|
|
|
dependencies[1].srcSubpass = 0;
|
|
dependencies[1].dstSubpass = VK_SUBPASS_EXTERNAL;
|
|
dependencies[1].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
|
dependencies[1].dstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
|
|
dependencies[1].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
|
dependencies[1].dstAccessMask = VK_ACCESS_MEMORY_READ_BIT;
|
|
dependencies[1].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
|
|
|
|
VkRenderPassCreateInfo renderPassInfo = {};
|
|
renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
|
|
renderPassInfo.attachmentCount = static_cast<uint32_t>(attachments.size());
|
|
renderPassInfo.pAttachments = attachments.data();
|
|
renderPassInfo.subpassCount = 1;
|
|
renderPassInfo.pSubpasses = &subpassDescription;
|
|
renderPassInfo.dependencyCount = static_cast<uint32_t>(dependencies.size());
|
|
renderPassInfo.pDependencies = dependencies.data();
|
|
|
|
VK_CHECK_RESULT(vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass));
|
|
}
|
|
|
|
void VulkanExampleBase::getEnabledFeatures()
|
|
{
|
|
// Can be overriden in derived class
|
|
}
|
|
|
|
void VulkanExampleBase::windowResize()
|
|
{
|
|
if (!prepared)
|
|
{
|
|
return;
|
|
}
|
|
prepared = false;
|
|
|
|
// Ensure all operations on the device have been finished before destroying resources
|
|
vkDeviceWaitIdle(device);
|
|
|
|
// Recreate swap chain
|
|
width = destWidth;
|
|
height = destHeight;
|
|
setupSwapChain();
|
|
|
|
// Recreate the frame buffers
|
|
vkDestroyImageView(device, depthStencil.view, nullptr);
|
|
vkDestroyImage(device, depthStencil.image, nullptr);
|
|
vkFreeMemory(device, depthStencil.mem, nullptr);
|
|
setupDepthStencil();
|
|
for (uint32_t i = 0; i < frameBuffers.size(); i++) {
|
|
vkDestroyFramebuffer(device, frameBuffers[i], nullptr);
|
|
}
|
|
setupFrameBuffer();
|
|
|
|
if ((width > 0.0f) && (height > 0.0f)) {
|
|
if (settings.overlay) {
|
|
UIOverlay.resize(width, height);
|
|
}
|
|
}
|
|
|
|
// Command buffers need to be recreated as they may store
|
|
// references to the recreated frame buffer
|
|
destroyCommandBuffers();
|
|
createCommandBuffers();
|
|
buildCommandBuffers();
|
|
|
|
vkDeviceWaitIdle(device);
|
|
|
|
if ((width > 0.0f) && (height > 0.0f)) {
|
|
camera.updateAspectRatio((float)width / (float)height);
|
|
}
|
|
|
|
// Notify derived class
|
|
windowResized();
|
|
viewChanged();
|
|
|
|
prepared = true;
|
|
}
|
|
|
|
void VulkanExampleBase::handleMouseMove(int32_t x, int32_t y)
|
|
{
|
|
int32_t dx = (int32_t)mousePos.x - x;
|
|
int32_t dy = (int32_t)mousePos.y - y;
|
|
|
|
bool handled = false;
|
|
|
|
if (settings.overlay) {
|
|
ImGuiIO& io = ImGui::GetIO();
|
|
handled = io.WantCaptureMouse;
|
|
}
|
|
mouseMoved((float)x, (float)y, handled);
|
|
|
|
if (handled) {
|
|
mousePos = glm::vec2((float)x, (float)y);
|
|
return;
|
|
}
|
|
|
|
if (mouseButtons.left) {
|
|
rotation.x += dy * 1.25f * rotationSpeed;
|
|
rotation.y -= dx * 1.25f * rotationSpeed;
|
|
camera.rotate(glm::vec3(dy * camera.rotationSpeed, -dx * camera.rotationSpeed, 0.0f));
|
|
viewUpdated = true;
|
|
}
|
|
if (mouseButtons.right) {
|
|
zoom += dy * .005f * zoomSpeed;
|
|
camera.translate(glm::vec3(-0.0f, 0.0f, dy * .005f * zoomSpeed));
|
|
viewUpdated = true;
|
|
}
|
|
if (mouseButtons.middle) {
|
|
cameraPos.x -= dx * 0.01f;
|
|
cameraPos.y -= dy * 0.01f;
|
|
camera.translate(glm::vec3(-dx * 0.01f, -dy * 0.01f, 0.0f));
|
|
viewUpdated = true;
|
|
}
|
|
mousePos = glm::vec2((float)x, (float)y);
|
|
}
|
|
|
|
void VulkanExampleBase::windowResized()
|
|
{
|
|
// Can be overriden in derived class
|
|
}
|
|
|
|
void VulkanExampleBase::initSwapchain()
|
|
{
|
|
#if defined(_WIN32)
|
|
swapChain.initSurface(windowInstance, window);
|
|
#elif defined(VK_USE_PLATFORM_ANDROID_KHR)
|
|
swapChain.initSurface(androidApp->window);
|
|
#elif (defined(VK_USE_PLATFORM_IOS_MVK) || defined(VK_USE_PLATFORM_MACOS_MVK))
|
|
swapChain.initSurface(view);
|
|
#elif defined(_DIRECT2DISPLAY)
|
|
swapChain.initSurface(width, height);
|
|
#elif defined(VK_USE_PLATFORM_WAYLAND_KHR)
|
|
swapChain.initSurface(display, surface);
|
|
#elif defined(VK_USE_PLATFORM_XCB_KHR)
|
|
swapChain.initSurface(connection, window);
|
|
#endif
|
|
}
|
|
|
|
void VulkanExampleBase::setupSwapChain()
|
|
{
|
|
swapChain.create(&width, &height, settings.vsync);
|
|
}
|
|
|
|
void VulkanExampleBase::OnUpdateUIOverlay(vks::UIOverlay *overlay) {} |