/* * Vulkan Example base class * * Copyright (C) by Sascha Willems - www.saschawillems.de * * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) */ #include "vulkanexamplebase.h" #if (defined(VK_USE_PLATFORM_MACOS_MVK) && defined(VK_EXAMPLE_XCODE_GENERATED)) #include #include #include #endif std::vector 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 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_DIRECTFB_EXT) instanceExtensions.push_back(VK_EXT_DIRECTFB_SURFACE_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); #elif defined(VK_USE_PLATFORM_HEADLESS_EXT) instanceExtensions.push_back(VK_EXT_HEADLESS_SURFACE_EXTENSION_NAME); #endif // Get extensions supported by the instance and store for later use uint32_t extCount = 0; vkEnumerateInstanceExtensionProperties(nullptr, &extCount, nullptr); if (extCount > 0) { std::vector extensions(extCount); if (vkEnumerateInstanceExtensionProperties(nullptr, &extCount, &extensions.front()) == VK_SUCCESS) { for (VkExtensionProperties extension : extensions) { supportedInstanceExtensions.push_back(extension.extensionName); } } } #if (defined(VK_USE_PLATFORM_IOS_MVK) || defined(VK_USE_PLATFORM_MACOS_MVK)) // SRS - When running on iOS/macOS with MoltenVK, enable VK_KHR_get_physical_device_properties2 if not already enabled by the example (required by VK_KHR_portability_subset) if (std::find(enabledInstanceExtensions.begin(), enabledInstanceExtensions.end(), VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME) == enabledInstanceExtensions.end()) { enabledInstanceExtensions.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME); } #endif // Enabled requested instance extensions if (enabledInstanceExtensions.size() > 0) { for (const char * enabledExtension : enabledInstanceExtensions) { // Output message if requested extension is not available if (std::find(supportedInstanceExtensions.begin(), supportedInstanceExtensions.end(), enabledExtension) == supportedInstanceExtensions.end()) { std::cerr << "Enabled instance extension \"" << enabledExtension << "\" is not present at instance level\n"; } instanceExtensions.push_back(enabledExtension); } } VkInstanceCreateInfo instanceCreateInfo = {}; instanceCreateInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; instanceCreateInfo.pNext = NULL; instanceCreateInfo.pApplicationInfo = &appInfo; #if (defined(VK_USE_PLATFORM_IOS_MVK) || defined(VK_USE_PLATFORM_MACOS_MVK)) && defined(VK_KHR_portability_enumeration) // SRS - When running on iOS/macOS with MoltenVK and VK_KHR_portability_enumeration is defined and supported by the instance, enable the extension and the flag if (std::find(supportedInstanceExtensions.begin(), supportedInstanceExtensions.end(), VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME) != supportedInstanceExtensions.end()) { instanceExtensions.push_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME); instanceCreateInfo.flags = VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR; } #endif if (instanceExtensions.size() > 0) { if (settings.validation) { instanceExtensions.push_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME); // SRS - Dependency when VK_EXT_DEBUG_MARKER is enabled instanceExtensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); } instanceCreateInfo.enabledExtensionCount = (uint32_t)instanceExtensions.size(); instanceCreateInfo.ppEnabledExtensionNames = instanceExtensions.data(); } // The VK_LAYER_KHRONOS_validation contains all current validation functionality. // Note that on Android this layer requires at least NDK r20 const char* validationLayerName = "VK_LAYER_KHRONOS_validation"; if (settings.validation) { // Check if this layer is available at instance level uint32_t instanceLayerCount; vkEnumerateInstanceLayerProperties(&instanceLayerCount, nullptr); std::vector instanceLayerProperties(instanceLayerCount); vkEnumerateInstanceLayerProperties(&instanceLayerCount, instanceLayerProperties.data()); bool validationLayerPresent = false; for (VkLayerProperties layer : instanceLayerProperties) { if (strcmp(layer.layerName, validationLayerName) == 0) { validationLayerPresent = true; break; } } if (validationLayerPresent) { instanceCreateInfo.ppEnabledLayerNames = &validationLayerName; instanceCreateInfo.enabledLayerCount = 1; } else { std::cerr << "Validation layer VK_LAYER_KHRONOS_validation not present, validation is disabled"; } } return vkCreateInstance(&instanceCreateInfo, nullptr, &instance); } void VulkanExampleBase::renderFrame() { VulkanExampleBase::prepareFrame(); submitInfo.commandBufferCount = 1; submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer]; VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE)); VulkanExampleBase::submitFrame(); } 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; } 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(drawCmdBuffers.size())); VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &cmdBufAllocateInfo, drawCmdBuffers.data())); } void VulkanExampleBase::destroyCommandBuffers() { vkFreeCommandBuffers(device, cmdPool, static_cast(drawCmdBuffers.size()), drawCmdBuffers.data()); } std::string VulkanExampleBase::getShadersPath() const { return getAssetPath() + "shaders/" + shaderDir + "/"; } std::string VulkanExampleBase::getHomeworkShadersPath() const { return getAssetPath() + "homework/shaders/" + shaderDir + "/"; } 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(getShadersPath() + "base/uioverlay.vert.spv", VK_SHADER_STAGE_VERTEX_BIT), loadShader(getShadersPath() + "base/uioverlay.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT), }; UIOverlay.prepareResources(); UIOverlay.preparePipeline(pipelineCache, renderPass, swapChain.colorFormat, depthFormat); } } 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"; assert(shaderStage.module != VK_NULL_HANDLE); shaderModules.push_back(shaderStage.module); return shaderStage; } void VulkanExampleBase::nextFrame() { auto tStart = std::chrono::high_resolution_clock::now(); if (viewUpdated) { viewUpdated = false; viewChanged(); } render(); frameCounter++; auto tEnd = std::chrono::high_resolution_clock::now(); #if (defined(VK_USE_PLATFORM_IOS_MVK) || (defined(VK_USE_PLATFORM_MACOS_MVK) && !defined(VK_EXAMPLE_XCODE_GENERATED))) // SRS - Calculate tDiff as time between frames vs. rendering time for iOS/macOS displayLink-driven examples project auto tDiff = std::chrono::duration(tEnd - tPrevEnd).count(); #else auto tDiff = std::chrono::duration(tEnd - tStart).count(); #endif 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 = (float)(std::chrono::duration(tEnd - lastTimestamp).count()); if (fpsTimer > 1000.0f) { lastFPS = static_cast((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; } tPrevEnd = tEnd; // TODO: Cap UI overlay update rates updateOverlay(); } void VulkanExampleBase::renderLoop() { // SRS - for non-apple plaforms, handle benchmarking here within VulkanExampleBase::renderLoop() // - for macOS, handle benchmarking within NSApp rendering loop via displayLinkOutputCb() #if !(defined(VK_USE_PLATFORM_IOS_MVK) || defined(VK_USE_PLATFORM_MACOS_MVK)) if (benchmark.active) { benchmark.run([=] { render(); }, vulkanDevice->properties); vkDeviceWaitIdle(device); if (benchmark.filename != "") { benchmark.saveResults(); } return; } #endif destWidth = width; destHeight = height; lastTimestamp = std::chrono::high_resolution_clock::now(); tPrevEnd = lastTimestamp; #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 (prepared && !IsIconic(window)) { nextFrame(); } } #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(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(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) { camera.rotate(glm::vec3(0.0f, gamePadState.axisLeft.x * 0.5f, 0.0f)); updateView = true; } if (std::abs(gamePadState.axisLeft.y) > deadZone) { camera.rotate(glm::vec3(gamePadState.axisLeft.y * 0.5f, 0.0f, 0.0f)); updateView = true; } // Zoom if (std::abs(gamePadState.axisRight.y) > deadZone) { camera.translate(glm::vec3(0.0f, 0.0f, gamePadState.axisRight.y * 0.01f)); 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(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(tEnd - lastTimestamp).count(); if (fpsTimer > 1000.0f) { lastFPS = (float)frameCounter * (1000.0f / fpsTimer); frameCounter = 0; lastTimestamp = tEnd; } updateOverlay(); } #elif defined(VK_USE_PLATFORM_DIRECTFB_EXT) while (!quit) { auto tStart = std::chrono::high_resolution_clock::now(); if (viewUpdated) { viewUpdated = false; viewChanged(); } DFBWindowEvent event; while (!event_buffer->GetEvent(event_buffer, DFB_EVENT(&event))) { handleEvent(&event); } render(); frameCounter++; auto tEnd = std::chrono::high_resolution_clock::now(); auto tDiff = std::chrono::duration(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(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(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(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(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(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(); } #elif defined(VK_USE_PLATFORM_HEADLESS_EXT) 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(tEnd - tStart).count(); frameTimer = tDiff / 1000.0f; camera.update(frameTimer); if (camera.moving()) { viewUpdated = true; } // Convert to clamped timer value timer += timerSpeed * frameTimer; if (timer > 1.0) { timer -= 1.0f; } float fpsTimer = std::chrono::duration(tEnd - lastTimestamp).count(); if (fpsTimer > 1000.0f) { lastFPS = (float)frameCounter * (1000.0f / fpsTimer); frameCounter = 0; lastTimestamp = tEnd; } updateOverlay(); } #elif (defined(VK_USE_PLATFORM_MACOS_MVK) && defined(VK_EXAMPLE_XCODE_GENERATED)) [NSApp run]; #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 && UIOverlay.visible; io.MouseDown[1] = mouseButtons.right && UIOverlay.visible; io.MouseDown[2] = mouseButtons.middle && UIOverlay.visible; ImGui::NewFrame(); ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0); ImGui::SetNextWindowPos(ImVec2(10 * UIOverlay.scale, 10 * UIOverlay.scale)); 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 && UIOverlay.visible) { 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 result = swapChain.acquireNextImage(semaphores.presentComplete, ¤tBuffer); // Recreate the swapchain if it's no longer compatible with the surface (OUT_OF_DATE) // SRS - If no longer optimal (VK_SUBOPTIMAL_KHR), wait until submitFrame() in case number of swapchain images will change on resize if ((result == VK_ERROR_OUT_OF_DATE_KHR) || (result == VK_SUBOPTIMAL_KHR)) { if (result == VK_ERROR_OUT_OF_DATE_KHR) { windowResize(); } return; } else { VK_CHECK_RESULT(result); } } void VulkanExampleBase::submitFrame() { VkResult result = swapChain.queuePresent(queue, currentBuffer, semaphores.renderComplete); // Recreate the swapchain if it's no longer compatible with the surface (OUT_OF_DATE) or no longer optimal for presentation (SUBOPTIMAL) if ((result == VK_ERROR_OUT_OF_DATE_KHR) || (result == VK_SUBOPTIMAL_KHR)) { windowResize(); if (result == VK_ERROR_OUT_OF_DATE_KHR) { return; } } else { VK_CHECK_RESULT(result); } 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() << "\n"; #endif exit(-1); } #endif settings.validation = enableValidation; // Command line arguments commandLineParser.add("help", { "--help" }, 0, "Show help"); commandLineParser.add("validation", { "-v", "--validation" }, 0, "Enable validation layers"); commandLineParser.add("vsync", { "-vs", "--vsync" }, 0, "Enable V-Sync"); commandLineParser.add("fullscreen", { "-f", "--fullscreen" }, 0, "Start in fullscreen mode"); commandLineParser.add("width", { "-w", "--width" }, 1, "Set window width"); commandLineParser.add("height", { "-h", "--height" }, 1, "Set window height"); commandLineParser.add("shaders", { "-s", "--shaders" }, 1, "Select shader type to use (glsl or hlsl)"); commandLineParser.add("gpuselection", { "-g", "--gpu" }, 1, "Select GPU to run on"); commandLineParser.add("gpulist", { "-gl", "--listgpus" }, 0, "Display a list of available Vulkan devices"); commandLineParser.add("benchmark", { "-b", "--benchmark" }, 0, "Run example in benchmark mode"); commandLineParser.add("benchmarkwarmup", { "-bw", "--benchwarmup" }, 1, "Set warmup time for benchmark mode in seconds"); commandLineParser.add("benchmarkruntime", { "-br", "--benchruntime" }, 1, "Set duration time for benchmark mode in seconds"); commandLineParser.add("benchmarkresultfile", { "-bf", "--benchfilename" }, 1, "Set file name for benchmark results"); commandLineParser.add("benchmarkresultframes", { "-bt", "--benchframetimes" }, 0, "Save frame times to benchmark results file"); commandLineParser.add("benchmarkframes", { "-bfs", "--benchmarkframes" }, 1, "Only render the given number of frames"); commandLineParser.parse(args); if (commandLineParser.isSet("help")) { #if defined(_WIN32) setupConsole("Vulkan example"); #endif commandLineParser.printHelp(); std::cin.get(); exit(0); } if (commandLineParser.isSet("validation")) { settings.validation = true; } if (commandLineParser.isSet("vsync")) { settings.vsync = true; } if (commandLineParser.isSet("height")) { height = commandLineParser.getValueAsInt("height", width); } if (commandLineParser.isSet("width")) { width = commandLineParser.getValueAsInt("width", width); } if (commandLineParser.isSet("fullscreen")) { settings.fullscreen = true; } if (commandLineParser.isSet("shaders")) { std::string value = commandLineParser.getValueAsString("shaders", "glsl"); if ((value != "glsl") && (value != "hlsl")) { std::cerr << "Shader type must be one of 'glsl' or 'hlsl'\n"; } else { shaderDir = value; } } if (commandLineParser.isSet("benchmark")) { benchmark.active = true; vks::tools::errorModeSilent = true; } if (commandLineParser.isSet("benchmarkwarmup")) { benchmark.warmup = commandLineParser.getValueAsInt("benchmarkwarmup", benchmark.warmup); } if (commandLineParser.isSet("benchmarkruntime")) { benchmark.duration = commandLineParser.getValueAsInt("benchmarkruntime", benchmark.duration); } if (commandLineParser.isSet("benchmarkresultfile")) { benchmark.filename = commandLineParser.getValueAsString("benchmarkresultfile", benchmark.filename); } if (commandLineParser.isSet("benchmarkresultframes")) { benchmark.outputFrameTimes = true; } if (commandLineParser.isSet("benchmarkframes")) { benchmark.outputFrames = commandLineParser.getValueAsInt("benchmarkframes", benchmark.outputFrames); } #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 example"); } setupDPIAwareness(); #endif } VulkanExampleBase::~VulkanExampleBase() { // Clean up Vulkan resources swapChain.cleanup(); if (descriptorPool != VK_NULL_HANDLE) { vkDestroyDescriptorPool(device, descriptorPool, nullptr); } destroyCommandBuffers(); if (renderPass != VK_NULL_HANDLE) { 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_DIRECTFB_EXT) if (event_buffer) event_buffer->Release(event_buffer); if (surface) surface->Release(surface); if (window) window->Release(window); if (layer) layer->Release(layer); if (dfb) dfb->Release(dfb); #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); if (seat) 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) { vks::debug::setupDebugging(instance); } // Physical device uint32_t gpuCount = 0; // Get number of available physical devices VK_CHECK_RESULT(vkEnumeratePhysicalDevices(instance, &gpuCount, nullptr)); if (gpuCount == 0) { vks::tools::exitFatal("No device with Vulkan support found", -1); return false; } // Enumerate devices std::vector 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 if (commandLineParser.isSet("gpuselection")) { uint32_t index = commandLineParser.getValueAsInt("gpuselection", 0); 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)" << "\n"; } else { selectedDevice = index; } } if (commandLineParser.isSet("gpulist")) { std::cout << "Available Vulkan devices" << "\n"; for (uint32_t i = 0; i < gpuCount; i++) { VkPhysicalDeviceProperties deviceProperties; vkGetPhysicalDeviceProperties(physicalDevices[i], &deviceProperties); std::cout << "Device [" << i << "] : " << deviceProperties.deviceName << std::endl; std::cout << " Type: " << vks::tools::physicalDeviceTypeString(deviceProperties.deviceType) << "\n"; std::cout << " API: " << (deviceProperties.apiVersion >> 22) << "." << ((deviceProperties.apiVersion >> 12) & 0x3ff) << "." << (deviceProperties.apiVersion & 0xfff) << "\n"; } } #endif physicalDevice = physicalDevices[selectedDevice]; // Store properties (including limits), features and memory properties of the physical 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); // Derived examples can enable extensions based on the list of supported extensions read from the physical device getEnabledExtensions(); VkResult res = vulkanDevice->createLogicalDevice(enabledFeatures, enabledDeviceExtensions, deviceCreatepNextChain); 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 queue 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 submitted 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; 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, "CONIN$", "r", stdin); freopen_s(&stream, "CONOUT$", "w+", stdout); freopen_s(&stream, "CONOUT$", "w+", stderr); SetConsoleTitle(TEXT(title.c_str())); } void VulkanExampleBase::setupDPIAwareness() { typedef HRESULT *(__stdcall *SetProcessDpiAwarenessFunc)(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) { if ((width != (uint32_t)screenWidth) && (height != (uint32_t)screenHeight)) { DEVMODE dmScreenSettings; memset(&dmScreenSettings, 0, sizeof(dmScreenSettings)); dmScreenSettings.dmSize = sizeof(dmScreenSettings); dmScreenSettings.dmPelsWidth = width; dmScreenSettings.dmPelsHeight = height; dmScreenSettings.dmBitsPerPel = 32; dmScreenSettings.dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT; 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; } } screenWidth = width; screenHeight = height; } } 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; } 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: UIOverlay.visible = !UIOverlay.visible; UIOverlay.updated = true; break; case KEY_ESCAPE: PostQuitMessage(0); break; } if (camera.type == 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.type == 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); camera.translate(glm::vec3(0.0f, 0.0f, (float)wheelDelta * 0.005f)); 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_GETMINMAXINFO: { LPMINMAXINFO minMaxInfo = (LPMINMAXINFO)lParam; minMaxInfo->ptMinTrackSize.x = 64; minMaxInfo->ptMinTrackSize.y = 64; break; } case WM_ENTERSIZEMOVE: resizing = true; break; case WM_EXITSIZEMOVE: resizing = false; break; } OnHandleMessage(hWnd, uMsg, wParam, lParam); } #elif defined(VK_USE_PLATFORM_ANDROID_KHR) int32_t VulkanExampleBase::handleAppInput(struct android_app* app, AInputEvent* event) { VulkanExampleBase* vulkanExample = reinterpret_cast(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 && vulkanExample->UIOverlay.visible; } if (!handled) { int32_t eventX = AMotionEvent_getX(event, 0); int32_t eventY = AMotionEvent_getY(event, 0); float deltaX = (float)(vulkanExample->touchPos.y - eventY) * vulkanExample->camera.rotationSpeed * 0.5f; float deltaY = (float)(vulkanExample->touchPos.x - eventX) * vulkanExample->camera.rotationSpeed * 0.5f; vulkanExample->camera.rotate(glm::vec3(deltaX, 0.0f, 0.0f)); vulkanExample->camera.rotate(glm::vec3(0.0f, -deltaY, 0.0f)); 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_1: // support keyboards with no function keys case AKEYCODE_F1: case AKEYCODE_BUTTON_L1: vulkanExample->UIOverlay.visible = !vulkanExample->UIOverlay.visible; vulkanExample->UIOverlay.updated = true; break; case AKEYCODE_BUTTON_R1: vulkanExample->keyPressed(GAMEPAD_BUTTON_R1); break; case AKEYCODE_P: case AKEYCODE_BUTTON_START: vulkanExample->paused = !vulkanExample->paused; break; default: vulkanExample->keyPressed(keyCode); // handle example-specific key press events 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(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)) #if defined(VK_EXAMPLE_XCODE_GENERATED) @interface AppDelegate : NSObject { @public VulkanExampleBase *vulkanExample; } @end @implementation AppDelegate { } // SRS - Dispatch rendering loop onto a queue for max frame rate concurrent rendering vs displayLink vsync rendering // - vsync command line option (-vs) on macOS now works like other platforms (using VK_PRESENT_MODE_FIFO_KHR) dispatch_group_t concurrentGroup; - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { [NSApp activateIgnoringOtherApps:YES]; // SRS - Make sure app window launches in front of Xcode window concurrentGroup = dispatch_group_create(); dispatch_queue_t concurrentQueue = dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0); dispatch_group_async(concurrentGroup, concurrentQueue, ^{ while (!vulkanExample->quit) { vulkanExample->displayLinkOutputCb(); } }); // SRS - When benchmarking, set up termination notification on main thread when concurrent queue completes if (vulkanExample->benchmark.active) { dispatch_queue_t notifyQueue = dispatch_get_main_queue(); dispatch_group_notify(concurrentGroup, notifyQueue, ^{ [NSApp terminate:nil]; }); } } - (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender { return YES; } // SRS - Tell rendering loop to quit, then wait for concurrent queue to terminate before deleting vulkanExample - (void)applicationWillTerminate:(NSNotification *)aNotification { vulkanExample->quit = YES; dispatch_group_wait(concurrentGroup, DISPATCH_TIME_FOREVER); vkDeviceWaitIdle(vulkanExample->vulkanDevice->logicalDevice); delete(vulkanExample); } @end const std::string getAssetPath() { return [NSBundle.mainBundle.resourcePath stringByAppendingString: @"/../../data/"].UTF8String; } static CVReturn displayLinkOutputCallback(CVDisplayLinkRef displayLink, const CVTimeStamp *inNow, const CVTimeStamp *inOutputTime, CVOptionFlags flagsIn, CVOptionFlags *flagsOut, void *displayLinkContext) { @autoreleasepool { auto vulkanExample = static_cast(displayLinkContext); vulkanExample->displayLinkOutputCb(); } return kCVReturnSuccess; } @interface View : NSView { @public VulkanExampleBase *vulkanExample; } @end @implementation View { CVDisplayLinkRef displayLink; } - (instancetype)initWithFrame:(NSRect)frameRect { self = [super initWithFrame:(frameRect)]; if (self) { self.wantsLayer = YES; self.layer = [CAMetalLayer layer]; } return self; } - (void)viewDidMoveToWindow { CVDisplayLinkCreateWithActiveCGDisplays(&displayLink); // SRS - Disable displayLink vsync rendering in favour of max frame rate concurrent rendering // - vsync command line option (-vs) on macOS now works like other platforms (using VK_PRESENT_MODE_FIFO_KHR) //CVDisplayLinkSetOutputCallback(displayLink, &displayLinkOutputCallback, vulkanExample); CVDisplayLinkStart(displayLink); } - (BOOL)acceptsFirstResponder { return YES; } - (BOOL)acceptsFirstMouse:(NSEvent *)event { return YES; } - (void)keyDown:(NSEvent*)event { switch (event.keyCode) { case KEY_P: vulkanExample->paused = !vulkanExample->paused; break; case KEY_1: // support keyboards with no function keys case KEY_F1: vulkanExample->UIOverlay.visible = !vulkanExample->UIOverlay.visible; vulkanExample->UIOverlay.updated = true; break; case KEY_DELETE: // support keyboards with no escape key case KEY_ESCAPE: [NSApp terminate:nil]; break; case KEY_W: vulkanExample->camera.keys.up = true; break; case KEY_S: vulkanExample->camera.keys.down = true; break; case KEY_A: vulkanExample->camera.keys.left = true; break; case KEY_D: vulkanExample->camera.keys.right = true; break; default: vulkanExample->keyPressed(event.keyCode); // handle example-specific key press events break; } } - (void)keyUp:(NSEvent*)event { switch (event.keyCode) { case KEY_W: vulkanExample->camera.keys.up = false; break; case KEY_S: vulkanExample->camera.keys.down = false; break; case KEY_A: vulkanExample->camera.keys.left = false; break; case KEY_D: vulkanExample->camera.keys.right = false; break; default: break; } } - (NSPoint)getMouseLocalPoint:(NSEvent*)event { NSPoint location = [event locationInWindow]; NSPoint point = [self convertPoint:location fromView:nil]; point.y = self.frame.size.height - point.y; return point; } - (void)mouseDown:(NSEvent *)event { auto point = [self getMouseLocalPoint:event]; vulkanExample->mousePos = glm::vec2(point.x, point.y); vulkanExample->mouseButtons.left = true; } - (void)mouseUp:(NSEvent *)event { vulkanExample->mouseButtons.left = false; } - (void)rightMouseDown:(NSEvent *)event { auto point = [self getMouseLocalPoint:event]; vulkanExample->mousePos = glm::vec2(point.x, point.y); vulkanExample->mouseButtons.right = true; } - (void)rightMouseUp:(NSEvent *)event { vulkanExample->mouseButtons.right = false; } - (void)otherMouseDown:(NSEvent *)event { auto point = [self getMouseLocalPoint:event]; vulkanExample->mousePos = glm::vec2(point.x, point.y); vulkanExample->mouseButtons.middle = true; } - (void)otherMouseUp:(NSEvent *)event { vulkanExample->mouseButtons.middle = false; } - (void)mouseDragged:(NSEvent *)event { auto point = [self getMouseLocalPoint:event]; vulkanExample->mouseDragged(point.x, point.y); } - (void)rightMouseDragged:(NSEvent *)event { auto point = [self getMouseLocalPoint:event]; vulkanExample->mouseDragged(point.x, point.y); } - (void)otherMouseDragged:(NSEvent *)event { auto point = [self getMouseLocalPoint:event]; vulkanExample->mouseDragged(point.x, point.y); } - (void)mouseMoved:(NSEvent *)event { auto point = [self getMouseLocalPoint:event]; vulkanExample->mouseDragged(point.x, point.y); } - (void)scrollWheel:(NSEvent *)event { short wheelDelta = [event deltaY]; vulkanExample->camera.translate(glm::vec3(0.0f, 0.0f, -(float)wheelDelta * 0.05f * vulkanExample->camera.movementSpeed)); vulkanExample->viewUpdated = true; } // SRS - Window resizing already handled by windowResize() in VulkanExampleBase::submitFrame() // - handling window resize events here is redundant and can cause thread interaction problems /* - (NSSize)windowWillResize:(NSWindow *)sender toSize:(NSSize)frameSize { CVDisplayLinkStop(displayLink); vulkanExample->windowWillResize(frameSize.width, frameSize.height); return frameSize; } - (void)windowDidResize:(NSNotification *)notification { vulkanExample->windowDidResize(); CVDisplayLinkStart(displayLink); } */ - (void)windowWillEnterFullScreen:(NSNotification *)notification { vulkanExample->settings.fullscreen = true; } - (void)windowWillExitFullScreen:(NSNotification *)notification { vulkanExample->settings.fullscreen = false; } - (BOOL)windowShouldClose:(NSWindow *)sender { return TRUE; } - (void)windowWillClose:(NSNotification *)notification { CVDisplayLinkStop(displayLink); CVDisplayLinkRelease(displayLink); } @end #endif void* VulkanExampleBase::setupWindow(void* view) { #if defined(VK_EXAMPLE_XCODE_GENERATED) NSApp = [NSApplication sharedApplication]; [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; auto nsAppDelegate = [AppDelegate new]; nsAppDelegate->vulkanExample = this; [NSApp setDelegate:nsAppDelegate]; const auto kContentRect = NSMakeRect(0.0f, 0.0f, width, height); const auto kWindowStyle = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskResizable; auto window = [[NSWindow alloc] initWithContentRect:kContentRect styleMask:kWindowStyle backing:NSBackingStoreBuffered defer:NO]; [window setTitle:@(title.c_str())]; [window setAcceptsMouseMovedEvents:YES]; [window center]; [window makeKeyAndOrderFront:nil]; if (settings.fullscreen) { [window toggleFullScreen:nil]; } auto nsView = [[View alloc] initWithFrame:kContentRect]; nsView->vulkanExample = this; [window setDelegate:nsView]; [window setContentView:nsView]; this->view = (__bridge void*)nsView; #else this->view = view; #endif return view; } void VulkanExampleBase::displayLinkOutputCb() { #if defined(VK_EXAMPLE_XCODE_GENERATED) if (benchmark.active) { benchmark.run([=] { render(); }, vulkanDevice->properties); if (benchmark.filename != "") { benchmark.saveResults(); } quit = true; // SRS - quit NSApp rendering loop when benchmarking complete return; } #endif if (prepared) nextFrame(); } void VulkanExampleBase::mouseDragged(float x, float y) { handleMouseMove(static_cast(x), static_cast(y)); } void VulkanExampleBase::windowWillResize(float x, float y) { resizing = true; if (prepared) { destWidth = x; destHeight = y; windowResize(); } } void VulkanExampleBase::windowDidResize() { resizing = false; } #elif defined(_DIRECT2DISPLAY) #elif defined(VK_USE_PLATFORM_DIRECTFB_EXT) IDirectFBSurface *VulkanExampleBase::setupWindow() { DFBResult ret; int posx = 0, posy = 0; ret = DirectFBInit(NULL, NULL); if (ret) { std::cout << "Could not initialize DirectFB!\n"; fflush(stdout); exit(1); } ret = DirectFBCreate(&dfb); if (ret) { std::cout << "Could not create main interface of DirectFB!\n"; fflush(stdout); exit(1); } ret = dfb->GetDisplayLayer(dfb, DLID_PRIMARY, &layer); if (ret) { std::cout << "Could not get DirectFB display layer interface!\n"; fflush(stdout); exit(1); } DFBDisplayLayerConfig layer_config; ret = layer->GetConfiguration(layer, &layer_config); if (ret) { std::cout << "Could not get DirectFB display layer configuration!\n"; fflush(stdout); exit(1); } if (settings.fullscreen) { width = layer_config.width; height = layer_config.height; } else { if (layer_config.width > width) posx = (layer_config.width - width) / 2; if (layer_config.height > height) posy = (layer_config.height - height) / 2; } DFBWindowDescription desc; desc.flags = (DFBWindowDescriptionFlags)(DWDESC_WIDTH | DWDESC_HEIGHT | DWDESC_POSX | DWDESC_POSY); desc.width = width; desc.height = height; desc.posx = posx; desc.posy = posy; ret = layer->CreateWindow(layer, &desc, &window); if (ret) { std::cout << "Could not create DirectFB window interface!\n"; fflush(stdout); exit(1); } ret = window->GetSurface(window, &surface); if (ret) { std::cout << "Could not get DirectFB surface interface!\n"; fflush(stdout); exit(1); } ret = window->CreateEventBuffer(window, &event_buffer); if (ret) { std::cout << "Could not create DirectFB event buffer interface!\n"; fflush(stdout); exit(1); } ret = window->SetOpacity(window, 0xFF); if (ret) { std::cout << "Could not set DirectFB window opacity!\n"; fflush(stdout); exit(1); } return surface; } void VulkanExampleBase::handleEvent(const DFBWindowEvent *event) { switch (event->type) { case DWET_CLOSE: quit = true; break; case DWET_MOTION: handleMouseMove(event->x, event->y); break; case DWET_BUTTONDOWN: switch (event->button) { case DIBI_LEFT: mouseButtons.left = true; break; case DIBI_MIDDLE: mouseButtons.middle = true; break; case DIBI_RIGHT: mouseButtons.right = true; break; default: break; } break; case DWET_BUTTONUP: switch (event->button) { case DIBI_LEFT: mouseButtons.left = false; break; case DIBI_MIDDLE: mouseButtons.middle = false; break; case DIBI_RIGHT: mouseButtons.right = false; break; default: break; } break; case DWET_KEYDOWN: switch (event->key_symbol) { 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: UIOverlay.visible = !UIOverlay.visible; UIOverlay.updated = true; break; default: break; } break; case DWET_KEYUP: switch (event->key_symbol) { 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; default: break; } keyPressed(event->key_symbol); break; case DWET_SIZE: destWidth = event->w; destHeight = event->h; windowResize(); break; default: break; } } #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(data); self->registryGlobal(registry, name, interface, version); } /*static*/void VulkanExampleBase::seatCapabilitiesCb(void *data, wl_seat *seat, uint32_t caps) { VulkanExampleBase *self = reinterpret_cast(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(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(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(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: camera.translate(glm::vec3(0.0f, 0.0f, d * 0.005f)); 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(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) { UIOverlay.visible = !UIOverlay.visible; UIOverlay.updated = true; } break; case KEY_ESCAPE: 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) { std::cout << "Could not bind Wayland protocols!\n"; fflush(stdout); exit(1); } if (!seat) { std::cout << "WARNING: Input handling not available!\n"; fflush(stdout); } } 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); /** * Set the WM_CLASS property to display * title in dash tooltip and application menu * on GNOME and other desktop environments */ std::string wm_class; wm_class = wm_class.insert(0, name); wm_class = wm_class.insert(name.size(), 1, '\0'); wm_class = wm_class.insert(name.size() + 1, title); wm_class = wm_class.insert(wm_class.size(), 1, '\0'); xcb_change_property(connection, XCB_PROP_MODE_REPLACE, window, XCB_ATOM_WM_CLASS, XCB_ATOM_STRING, 8, wm_class.size() + 2, wm_class.c_str()); 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; // xcb_connect always returns a non-NULL pointer to a xcb_connection_t, // even on failure. Callers need to use xcb_connection_has_error() to // check for failure. When finished, use xcb_disconnect() to close the // connection and free the structure. connection = xcb_connect(NULL, &scr); assert( connection ); if( xcb_connection_has_error(connection) ) { 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: UIOverlay.visible = !UIOverlay.visible; UIOverlay.updated = true; 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; } } #else void VulkanExampleBase::setupWindow() { } #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_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 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 dependencies; dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL; dependencies[0].dstSubpass = 0; dependencies[0].srcStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; dependencies[0].dstStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; dependencies[0].srcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; dependencies[0].dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT; dependencies[0].dependencyFlags = 0; dependencies[1].srcSubpass = VK_SUBPASS_EXTERNAL; dependencies[1].dstSubpass = 0; dependencies[1].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; dependencies[1].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; dependencies[1].srcAccessMask = 0; dependencies[1].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_READ_BIT; dependencies[1].dependencyFlags = 0; VkRenderPassCreateInfo renderPassInfo = {}; renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; renderPassInfo.attachmentCount = static_cast(attachments.size()); renderPassInfo.pAttachments = attachments.data(); renderPassInfo.subpassCount = 1; renderPassInfo.pSubpasses = &subpassDescription; renderPassInfo.dependencyCount = static_cast(dependencies.size()); renderPassInfo.pDependencies = dependencies.data(); VK_CHECK_RESULT(vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass)); } void VulkanExampleBase::getEnabledFeatures() {} void VulkanExampleBase::getEnabledExtensions() {} void VulkanExampleBase::windowResize() { if (!prepared) { return; } prepared = false; resized = true; // 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(); // SRS - Recreate fences in case number of swapchain images has changed on resize for (auto& fence : waitFences) { vkDestroyFence(device, fence, nullptr); } createSynchronizationPrimitives(); 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 && UIOverlay.visible; } mouseMoved((float)x, (float)y, handled); if (handled) { mousePos = glm::vec2((float)x, (float)y); return; } if (mouseButtons.left) { camera.rotate(glm::vec3(dy * camera.rotationSpeed, -dx * camera.rotationSpeed, 0.0f)); viewUpdated = true; } if (mouseButtons.right) { camera.translate(glm::vec3(-0.0f, 0.0f, dy * .005f)); viewUpdated = true; } if (mouseButtons.middle) { camera.translate(glm::vec3(-dx * 0.005f, -dy * 0.005f, 0.0f)); viewUpdated = true; } mousePos = glm::vec2((float)x, (float)y); } void VulkanExampleBase::windowResized() {} 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(VK_USE_PLATFORM_DIRECTFB_EXT) swapChain.initSurface(dfb, surface); #elif defined(VK_USE_PLATFORM_WAYLAND_KHR) swapChain.initSurface(display, surface); #elif defined(VK_USE_PLATFORM_XCB_KHR) swapChain.initSurface(connection, window); #elif (defined(_DIRECT2DISPLAY) || defined(VK_USE_PLATFORM_HEADLESS_EXT)) swapChain.initSurface(width, height); #endif } void VulkanExampleBase::setupSwapChain() { swapChain.create(&width, &height, settings.vsync, settings.fullscreen); } void VulkanExampleBase::OnUpdateUIOverlay(vks::UIOverlay *overlay) {} #if defined(_WIN32) void VulkanExampleBase::OnHandleMessage(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {}; #endif