diff --git a/CMakeLists.txt b/CMakeLists.txt index 9abc5c33eefb63f4852c5ea90efb604918638f50..41793a624dcdd495553d2f7c986ccc81be1af85b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,6 +5,7 @@ find_package(Boost) add_subdirectory(pack) add_subdirectory(glsl) +add_subdirectory(display) if (TARGET imgui) add_subdirectory(imgui) diff --git a/display/CMakeLists.txt b/display/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..5a103d6ebbc2c81d069e32ad4ad14882e6618a0d --- /dev/null +++ b/display/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 3.0) + +file(GLOB_RECURSE SOURCE_FILES "*.cc") +file(GLOB_RECURSE HEADER_FILES "*.hh") + +# X11 (optional, but useful to acquire displays) + +add_library(lava-extras-display ${LAVA_LINK_TYPE} ${SOURCE_FILES} ${HEADER_FILES}) + +find_package(X11) +if(X11_FOUND) + target_compile_definitions(lava-extras-display PUBLIC -DLAVA_DISPLAY_X11_AVAILABLE) + target_include_directories(lava-extras-display PUBLIC "${X11_X11_INCLUDE_PATH}") + target_link_libraries(lava-extras-display PUBLIC "${X11_X11_LIB}") +endif() + +target_include_directories(lava-extras-display PUBLIC ./) +target_compile_options(lava-extras-display PRIVATE ${LAVA_EXTRAS_DEF_OPTIONS}) +target_link_libraries(lava-extras-display PUBLIC lava) diff --git a/display/lava-extras/display/DisplayOutput.cc b/display/lava-extras/display/DisplayOutput.cc new file mode 100644 index 0000000000000000000000000000000000000000..205f4d52b15c57229d3cf3872beefc5bee3021e5 --- /dev/null +++ b/display/lava-extras/display/DisplayOutput.cc @@ -0,0 +1,43 @@ +#include "DisplayOutput.hh" +#include "DisplayWindow.hh" +#include <lava/common/str_utils.hh> + +namespace lava { +namespace display { + +DisplayOutput::DisplayOutput() {} + +std::shared_ptr<DisplayWindow> +DisplayOutput::openWindow(const SharedDevice &device, uint32_t index) { + return std::make_shared<DisplayWindow>(device, index); +} + +std::vector<const char *> +DisplayOutput::instanceExtensions(const std::vector<const char *> &available) { + std::vector<const char *> result = { + VK_KHR_SURFACE_EXTENSION_NAME, VK_KHR_DISPLAY_EXTENSION_NAME, + VK_EXT_DIRECT_MODE_DISPLAY_EXTENSION_NAME}; + + bool has_direct_mode = util::contains(available, VK_EXT_DIRECT_MODE_DISPLAY_EXTENSION_NAME); + bool has_acquire = util::contains(available, "VK_EXT_acquire_xlib_display"); + + if (has_direct_mode && has_acquire) { + result.push_back(VK_EXT_DIRECT_MODE_DISPLAY_EXTENSION_NAME); + result.push_back("VK_EXT_acquire_xlib_display"); + } + + return result; +} + +void DisplayOutput::onInstanceCreated(Instance *instance) {} + +std::vector<const char *> DisplayOutput::deviceExtensions() { + return {VK_KHR_SWAPCHAIN_EXTENSION_NAME}; +} + +void DisplayOutput::onPhysicalDeviceSelected(vk::PhysicalDevice phy) {} + +void DisplayOutput::onLogicalDeviceCreated(const SharedDevice &device) {} + +} // namespace display +} // namespace lava diff --git a/display/lava-extras/display/DisplayOutput.hh b/display/lava-extras/display/DisplayOutput.hh new file mode 100644 index 0000000000000000000000000000000000000000..1949c642f9679e29b8608eab151dcf3bfe21e5cf --- /dev/null +++ b/display/lava-extras/display/DisplayOutput.hh @@ -0,0 +1,36 @@ +#pragma once +#include <lava/features/IFeature.hh> + +namespace lava { + +namespace display { + +class DisplayWindow; +class DisplayOutput : public lava::features::IFeature { + public: + static std::shared_ptr<DisplayOutput> create() { + return std::make_shared<DisplayOutput>(); + } + + DisplayOutput(); + + std::shared_ptr<DisplayWindow> openWindow(SharedDevice const &device, + uint32_t index); + + /* IFeature overrides */ + + std::vector<const char *> instanceExtensions(std::vector<const char *> const& available) override; + + void onInstanceCreated(Instance *instance) override; + + std::vector<const char *> deviceExtensions() override; + void onPhysicalDeviceSelected(vk::PhysicalDevice phy) override; + void onLogicalDeviceCreated(SharedDevice const &device) override; + + protected: + lava::Instance *mInstance; + lava::Device *mDevice; + vk::PhysicalDevice mPhysicalDevice; +}; +} // namespace display +} // namespace lava diff --git a/display/lava-extras/display/DisplayWindow.cc b/display/lava-extras/display/DisplayWindow.cc new file mode 100644 index 0000000000000000000000000000000000000000..f6778a8f484c4b9d01f188ebcfb8fca821c64979 --- /dev/null +++ b/display/lava-extras/display/DisplayWindow.cc @@ -0,0 +1,168 @@ +#include "DisplayWindow.hh" +#include <lava/common/log.hh> +#include <lava/createinfos/Images.hh> +#include <lava/objects/Device.hh> +#include <lava/objects/Image.hh> +#include <lava/objects/Instance.hh> + +namespace lava { +namespace display { + +#ifdef LAVA_DISPLAY_X11_AVAILABLE +#include <X11/X.h> +void acquireDisplay(vk::Instance instance, vk::PhysicalDevice phy, vk::DisplayKHR disp) +{ + typedef VkResult AcquireDisplayFunc(VkPhysicalDevice, Display*, VkDisplayKHR); + auto func = (AcquireDisplayFunc*)instance.getProcAddr("vkAcquireXlibDisplayEXT"); + auto *xd = XOpenDisplay(nullptr); + if (xd) { + // Running inside an X server + auto res = func(phy, XOpenDisplay(nullptr), disp); + if (res != VK_SUCCESS) { + lava::error() << "Could not acquire display from XServer, is it in use?"; + } + } +} +#else +void acquireDisplay(vk::Instance,vk::PhysicalDevice,vk::DisplayKHR) {} +#endif + +void DisplayWindow::buildSwapchainWith( + const DisplayWindow::SwapchainBuildHandler &handler) { + mSwapchainHandler = handler; + buildSwapchain(); +} + +void DisplayWindow::buildSwapchain() { + auto caps = mDevice->physical().getSurfaceCapabilitiesKHR(mSurface); + auto pmodes = mDevice->physical().getSurfacePresentModesKHR(mSurface); + auto formats = mDevice->physical().getSurfaceFormatsKHR(mSurface); + + vk::SwapchainCreateInfoKHR info; + info.surface = mSurface; + info.minImageCount = caps.minImageCount; + info.imageFormat = formats[0].format; + info.imageColorSpace = formats[0].colorSpace; + info.imageExtent = mSurfaceInfo.imageExtent; + info.imageArrayLayers = 1; + info.imageUsage = vk::ImageUsageFlagBits::eColorAttachment | + vk::ImageUsageFlagBits::eTransferDst; + info.preTransform = mSurfaceInfo.transform; + info.presentMode = vk::PresentModeKHR::eImmediate; + info.clipped = true; + + auto width = info.imageExtent.width; + auto height = info.imageExtent.height; + + mChain = mDevice->handle().createSwapchainKHR(info); + mChainViews.clear(); + mChainImages.clear(); + + { + auto chainHandles = mDevice->handle().getSwapchainImagesKHR(mChain); + auto imgCreateInfo = + lava::attachment2D(width, height, info.imageFormat); + for (vk::Image handle : chainHandles) { + auto image = std::make_shared<lava::Image>( + mDevice, imgCreateInfo, handle, vk::ImageViewType::e2D); + mChainViews.push_back(image->createView()); + mChainImages.push_back(move(image)); + } + } + mSwapchainHandler(mChainViews); + mSwapchainInfo = info; +} + +DisplayWindow::DisplayWindow(SharedDevice device, + uint32_t index) + : mDevice(device) { + + auto const &instance = device->instance(); + + auto displays = device->physical().getDisplayPropertiesKHR(); + mDisplay = displays[index].display; + + auto planes = device->physical().getDisplayPlanePropertiesKHR(); + auto plane_idx = std::find_if(begin(planes), end(planes), + [&](vk::DisplayPlanePropertiesKHR const &p) { + return !p.currentDisplay || + p.currentDisplay == mDisplay; + }) - + begin(planes); + + auto modes = device->physical().getDisplayModePropertiesKHR(mDisplay); + mMode = modes[0].displayMode; + + auto dpcaps = + device->physical().getDisplayPlaneCapabilitiesKHR(mMode, plane_idx); + + acquireDisplay(instance->handle(), device->physical(), mDisplay); + + mSurfaceInfo = vk::DisplaySurfaceCreateInfoKHR() + .setDisplayMode(mMode) + .setPlaneIndex(plane_idx) + .setPlaneStackIndex(planes[plane_idx].currentStackIndex) + .setImageExtent(dpcaps.maxSrcExtent); + mSurface = instance->handle().createDisplayPlaneSurfaceKHR(mSurfaceInfo); + + auto supported = mDevice->physical().getSurfaceSupportKHR(0, mSurface); + if (!supported) + throw std::runtime_error("Can't present to display surface."); + + mRenderingComplete = device->handle().createSemaphore({}); + mImageReady = device->handle().createSemaphore({}); + + mQueue = &device->graphicsQueue(); +} + +DisplayWindow::~DisplayWindow() {} + +DisplayWindow::Frame DisplayWindow::startFrame() { + assert(mChain && "You need to provide a handler for swapchain creation."); + + while (true) { + auto res = vkAcquireNextImageKHR(mDevice->handle(), mChain, 1e9, + mImageReady, {}, &mPresentIndex); + if (res == VK_TIMEOUT) { + lava::error() + << "GlfwWindow::startFrame(): acquireNextImage timed out (>1s)"; + continue; + } + + if (res == VK_ERROR_OUT_OF_DATE_KHR || res == VK_SUBOPTIMAL_KHR) { + mDevice->handle().waitIdle(); + buildSwapchain(); + continue; + } + + return {this}; + } +} + +DisplayWindow::Frame::Frame(DisplayWindow::Frame &&rhs) : window(rhs.window) { + rhs.window = nullptr; +} + +DisplayWindow::Frame::~Frame() { + if (!window) + return; + + vk::PresentInfoKHR info; + info.pImageIndices = &window->mPresentIndex; + info.pSwapchains = &window->mChain; + info.swapchainCount = 1; + info.pWaitSemaphores = &window->mRenderingComplete; + info.waitSemaphoreCount = 1; + + try { + window->mQueue->handle().presentKHR(info); + } catch (vk::OutOfDateKHRError const &) { + window->mDevice->handle().waitIdle(); + window->buildSwapchain(); + } +} + +DisplayWindow::Frame::Frame(DisplayWindow *parent) : window(parent) {} + +} // namespace features +} // namespace lava diff --git a/display/lava-extras/display/DisplayWindow.hh b/display/lava-extras/display/DisplayWindow.hh new file mode 100644 index 0000000000000000000000000000000000000000..cdb10196ca7bd5fe5fee3ddaaec1fb39e250811b --- /dev/null +++ b/display/lava-extras/display/DisplayWindow.hh @@ -0,0 +1,120 @@ +#pragma once +#include <functional> +#include <vector> + +#include <lava/common/NoCopy.hh> +#include <lava/common/vulkan.hh> +#include <lava/fwd.hh> + +namespace lava { + +namespace display { + +class DisplayWindow { + public: + class Frame; + using SwapchainBuildHandler = + std::function<void(std::vector<SharedImageView>)>; + + void + buildSwapchainWith(const DisplayWindow::SwapchainBuildHandler &handler); + + /// index of the image that should be drawn to next + uint32_t nextIndex(); + + /// wait for this semaphore before drawing to the current image + vk::Semaphore imageReady() const { return mImageReady; } + + /// signal this semaphore when the image is ready to be presented + vk::Semaphore renderingComplete() const { + mRenderingCompleteCalled = true; + return mRenderingComplete; + } + + /// the Format of the images in the swapchain. + /// Make sure the attachment format in your Framebuffers matches this. + vk::Format format() { return mChainFormat.format; } + + uint32_t width() const { return mWidth; } + uint32_t height() const { return mHeight; } + + /// Call this when window is resized + void onResize(uint32_t w, uint32_t h); + + /// Don't use this directly, use openWindow in DisplayOutput instead + DisplayWindow(SharedDevice device, uint32_t index); + ~DisplayWindow(); + + /// Call this before you start rendering to the window. + /// Use the included index to select the right FBO to render to. + /// Automatically presents the image when the return value goes out-of-scope + Frame startFrame(); + + class Frame { + public: + LAVA_RAII_CLASS(Frame); + Frame(Frame &&rhs); + ~Frame(); + + uint32_t imageIndex() const { return window->mPresentIndex; } + vk::Semaphore imageReady() const { return window->mImageReady; } + vk::Semaphore renderingComplete() const { + return window->mRenderingComplete; + } + + SharedImage const &image() const { + return window->mChainImages[window->mPresentIndex]; + } + + private: + Frame(DisplayWindow *parent); + DisplayWindow *window; + friend class DisplayWindow; + }; + + vk::DisplayKHR display() const { return mDisplay; } + vk::DisplayModeKHR mode() const { return mMode; } + + vk::SurfaceKHR const &surface() const { return mSurface; } + vk::SurfaceFormatKHR const &surfaceFormat() const { return mChainFormat; } + vk::SwapchainCreateInfoKHR const &swapchainInfo() const { + return mSwapchainInfo; + } + size_t swapchainImageCount() const { return mChainImages.size(); } + std::vector<SharedImage> const &chainImages() const { return mChainImages; } + std::vector<SharedImageView> const &chainViews() const { + return mChainViews; + } + + protected: + void buildSwapchain(); + + SharedDevice mDevice; + vk::SurfaceFormatKHR mChainFormat; + + uint32_t mWidth; + uint32_t mHeight; + + vk::DisplayKHR mDisplay; + vk::DisplayModeKHR mMode; + + vk::SurfaceKHR mSurface; + vk::SwapchainKHR mChain; + + vk::Semaphore mImageReady; + vk::Semaphore mRenderingComplete; + mutable bool mRenderingCompleteCalled = false; + + uint32_t mPresentIndex; + lava::Queue *mQueue; + + std::vector<SharedImage> mChainImages; + std::vector<SharedImageView> mChainViews; + SwapchainBuildHandler mSwapchainHandler; + + vk::DisplaySurfaceCreateInfoKHR mSurfaceInfo; + vk::SwapchainCreateInfoKHR mSwapchainInfo; + friend class Frame; +}; +} // namespace display +} // namespace lava