Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions antora/modules/ROOT/nav.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@
*** xref:Building_a_Simple_Engine/Advanced_Topics/Ray_Query_Reflections_and_Transparency.adoc[Ray query: reflections & transparency]
*** xref:Building_a_Simple_Engine/Advanced_Topics/Dynamic_Rendering_Local_Read.adoc[Dynamic rendering local read]
*** xref:Building_a_Simple_Engine/Advanced_Topics/Robustness2.adoc[Robustness2]
*** xref:Building_a_Simple_Engine/Advanced_Topics/Direct_To_Display.adoc[Direct-to-Display Rendering (VK_KHR_display)]
** Appendix
*** xref:Building_a_Simple_Engine/Appendix/appendix.adoc[Appendix]

Expand Down
171 changes: 171 additions & 0 deletions attachments/simple_engine/platform.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,12 @@
*/
#include "platform.h"

#include <algorithm>
#include <array>
#include <cstdlib>
#include <csignal>
#include <stdexcept>
#include <vector>

#if defined(PLATFORM_ANDROID)
# include <cassert>
Expand Down Expand Up @@ -547,4 +552,170 @@ void DesktopPlatform::CharCallback(GLFWwindow* window, unsigned int codepoint) {
platform->charCallback(codepoint);
}
}

// Direct-to-display platform implementation

DirectDisplayPlatform* DirectDisplayPlatform::activeInstance = nullptr;

bool DirectDisplayPlatform::IsRequested() {
const char* env = std::getenv("SIMPLE_ENGINE_DIRECT_DISPLAY");
return env != nullptr && env[0] != '0' && env[0] != '\0';
}

void DirectDisplayPlatform::SignalHandler(int /*signal*/) {
if (activeInstance) {
activeInstance->shouldClose = true;
}
}

bool DirectDisplayPlatform::Initialize(const std::string& /*appName*/, int requestedWidth, int requestedHeight) {
// The real resolution is only known once CreateVulkanSurface() has picked an
// actual display mode; until then, fall back to what was requested.
width = requestedWidth;
height = requestedHeight;

activeInstance = this;
std::signal(SIGINT, SignalHandler);
std::signal(SIGTERM, SignalHandler);

return true;
}

void DirectDisplayPlatform::Cleanup() {
if (activeInstance == this) {
activeInstance = nullptr;
}
}

bool DirectDisplayPlatform::ProcessEvents() {
// There is no window system to pump events for; SIGINT/SIGTERM (via
// SignalHandler) are the only way to ask this loop to stop.
return !shouldClose;
}

bool DirectDisplayPlatform::CreateVulkanSurface(VkInstance instance, VkSurfaceKHR* surface) {
// VK_KHR_display surfaces are created from a specific physical device's
// display outputs, so - unlike a windowed surface - we have to pick a
// physical device here rather than after surface creation.
uint32_t deviceCount = 0;
vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr);
if (deviceCount == 0) {
LOGE("Direct-to-display: no Vulkan physical devices found");
return false;
}
std::vector<VkPhysicalDevice> physicalDevices(deviceCount);
if (vkEnumeratePhysicalDevices(instance, &deviceCount, physicalDevices.data()) != VK_SUCCESS) {
LOGE("Direct-to-display: failed to enumerate Vulkan physical devices");
return false;
}

for (VkPhysicalDevice physicalDevice : physicalDevices) {
uint32_t displayCount = 0;
vkGetPhysicalDeviceDisplayPropertiesKHR(physicalDevice, &displayCount, nullptr);
if (displayCount == 0) {
continue;
}
std::vector<VkDisplayPropertiesKHR> displayProperties(displayCount);
if (vkGetPhysicalDeviceDisplayPropertiesKHR(physicalDevice, &displayCount, displayProperties.data()) != VK_SUCCESS) {
LOGE("Direct-to-display: failed to get display properties");
continue;
}

const VkDisplayPropertiesKHR& chosenDisplay = displayProperties[0];
LOGI("Direct-to-display: candidate display '%s' (%ux%u physical)",
chosenDisplay.displayName ? chosenDisplay.displayName : "<unnamed>",
chosenDisplay.physicalResolution.width, chosenDisplay.physicalResolution.height);

uint32_t modeCount = 0;
vkGetDisplayModePropertiesKHR(physicalDevice, chosenDisplay.display, &modeCount, nullptr);
if (modeCount == 0) {
continue;
}
std::vector<VkDisplayModePropertiesKHR> modeProperties(modeCount);
if (vkGetDisplayModePropertiesKHR(physicalDevice, chosenDisplay.display, &modeCount, modeProperties.data()) != VK_SUCCESS) {
LOGE("Direct-to-display: failed to get display mode properties");
continue;
}

// Prefer the mode with the highest pixel count (typically the display's native resolution).
VkDisplayModePropertiesKHR bestMode = modeProperties[0];
uint64_t bestPixels = static_cast<uint64_t>(bestMode.parameters.visibleRegion.width) * bestMode.parameters.visibleRegion.height;
for (const VkDisplayModePropertiesKHR& mode : modeProperties) {
const uint64_t pixels = static_cast<uint64_t>(mode.parameters.visibleRegion.width) * mode.parameters.visibleRegion.height;
if (pixels > bestPixels) {
bestMode = mode;
bestPixels = pixels;
}
}

// Find a display plane that both supports this display and can be given a
// compatible surface, then create the surface on it.
uint32_t planeCount = 0;
vkGetPhysicalDeviceDisplayPlanePropertiesKHR(physicalDevice, &planeCount, nullptr);
Comment thread
gpx1000 marked this conversation as resolved.
if (planeCount == 0) {
continue;
}
std::vector<VkDisplayPlanePropertiesKHR> planeProperties(planeCount);
if (vkGetPhysicalDeviceDisplayPlanePropertiesKHR(physicalDevice, &planeCount, planeProperties.data()) != VK_SUCCESS) {
LOGE("Direct-to-display: failed to get display plane properties");
continue;
}

for (uint32_t planeIndex = 0; planeIndex < planeCount; ++planeIndex) {
uint32_t supportedDisplayCount = 0;
vkGetDisplayPlaneSupportedDisplaysKHR(physicalDevice, planeIndex, &supportedDisplayCount, nullptr);
if (supportedDisplayCount == 0) {
continue;
}
std::vector<VkDisplayKHR> supportedDisplays(supportedDisplayCount);
if (vkGetDisplayPlaneSupportedDisplaysKHR(physicalDevice, planeIndex, &supportedDisplayCount, supportedDisplays.data()) != VK_SUCCESS) {
LOGE("Direct-to-display: failed to get displays supported by plane %u", planeIndex);
continue;
}
Comment thread
gpx1000 marked this conversation as resolved.

const bool planeSupportsDisplay = std::ranges::find(supportedDisplays, chosenDisplay.display) != supportedDisplays.end();
if (!planeSupportsDisplay) {
continue;
}

VkDisplayPlaneCapabilitiesKHR planeCapabilities{};
if (vkGetDisplayPlaneCapabilitiesKHR(physicalDevice, bestMode.displayMode, planeIndex, &planeCapabilities) != VK_SUCCESS) {
LOGE("Direct-to-display: failed to get display plane capabilities for plane %u", planeIndex);
continue;
}

const std::array<VkDisplayPlaneAlphaFlagBitsKHR, 3> alphaModes{
VK_DISPLAY_PLANE_ALPHA_OPAQUE_BIT_KHR, VK_DISPLAY_PLANE_ALPHA_GLOBAL_BIT_KHR,
VK_DISPLAY_PLANE_ALPHA_PER_PIXEL_BIT_KHR};
const auto alphaModeIt = std::ranges::find_if(alphaModes, [supportedAlpha = planeCapabilities.supportedAlpha](VkDisplayPlaneAlphaFlagBitsKHR mode) {
return supportedAlpha & mode;
});
if (alphaModeIt == alphaModes.end()) {
LOGE("Direct-to-display: plane %u supports no known alpha mode", planeIndex);
continue;
}
Comment thread
gpx1000 marked this conversation as resolved.
const VkDisplayPlaneAlphaFlagBitsKHR alphaMode = *alphaModeIt;

VkDisplaySurfaceCreateInfoKHR createInfo{};
createInfo.sType = VK_STRUCTURE_TYPE_DISPLAY_SURFACE_CREATE_INFO_KHR;
createInfo.displayMode = bestMode.displayMode;
createInfo.planeIndex = planeIndex;
createInfo.planeStackIndex = planeProperties[planeIndex].currentStackIndex;
createInfo.transform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
createInfo.globalAlpha = 1.0f;
createInfo.alphaMode = alphaMode;
createInfo.imageExtent = bestMode.parameters.visibleRegion;

if (vkCreateDisplayPlaneSurfaceKHR(instance, &createInfo, nullptr, surface) == VK_SUCCESS) {
width = static_cast<int>(bestMode.parameters.visibleRegion.width);
height = static_cast<int>(bestMode.parameters.visibleRegion.height);
LOGI("Direct-to-display: created VK_KHR_display surface at %dx%d on plane %u", width, height, planeIndex);
return true;
}
}
}

LOGE("Direct-to-display: failed to find a display/plane combination that supports surface creation");
return false;
}
#endif
68 changes: 68 additions & 0 deletions attachments/simple_engine/platform.h
Original file line number Diff line number Diff line change
Expand Up @@ -531,6 +531,71 @@ class DesktopPlatform final : public Platform {
return window;
}
};

/**
* @brief Direct-to-display implementation of the Platform interface.
*
* Bypasses the window manager entirely and presents straight to a physical
* display output via VK_KHR_display / VK_KHR_display_swapchain, for kiosk,
* embedded, or headless-compositor scenarios. There is no window, so input
* and resize callbacks are accepted but never invoked, and the surface is
* created against whichever physical device exposes a usable display - see
* CreateVulkanSurface() for how that device and display/plane/mode are chosen.
*
* @see en/Building_a_Simple_Engine/Engine_Architecture chapter for background
* on how this fits into the wider Platform abstraction.
*/
class DirectDisplayPlatform final : public Platform {
private:
int width = 0;
int height = 0;
bool shouldClose = false;
std::function<void(int, int)> resizeCallback;
std::function<void(float, float, uint32_t)> mouseCallback;
std::function<void(uint32_t, bool)> keyboardCallback;
std::function<void(uint32_t)> charCallback;

static DirectDisplayPlatform* activeInstance;
static void SignalHandler(int signal);

public:
DirectDisplayPlatform() = default;

/**
* @brief Whether the SIMPLE_ENGINE_DIRECT_DISPLAY environment variable
* requests direct-to-display mode instead of a windowed surface.
*/
static bool IsRequested();

bool Initialize(const std::string& appName, int width, int height) override;
void Cleanup() override;
bool ProcessEvents() override;
bool HasWindowResized() override {
return false;
}
int GetWindowWidth() const override {
return width;
}
int GetWindowHeight() const override {
return height;
}
bool CreateVulkanSurface(VkInstance instance, VkSurfaceKHR* surface) override;
void SetResizeCallback(std::function<void(int, int)> callback) override {
resizeCallback = std::move(callback);
}
void SetMouseCallback(std::function<void(float, float, uint32_t)> callback) override {
mouseCallback = std::move(callback);
}
void SetKeyboardCallback(std::function<void(uint32_t, bool)> callback) override {
keyboardCallback = std::move(callback);
}
void SetCharCallback(std::function<void(uint32_t)> callback) override {
charCallback = std::move(callback);
}
void SetWindowTitle(const std::string&) override {
// No window to title.
}
};
#endif

/**
Expand All @@ -543,6 +608,9 @@ std::unique_ptr<Platform> CreatePlatform(Args&&... args) {
#if defined(PLATFORM_ANDROID)
return std::make_unique<AndroidPlatform>(std::forward<Args>(args)...);
#else
if (DirectDisplayPlatform::IsRequested()) {
return std::make_unique<DirectDisplayPlatform>();
}
return std::make_unique<DesktopPlatform>();
#endif
}
14 changes: 11 additions & 3 deletions attachments/simple_engine/renderer_core.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -650,9 +650,17 @@ bool Renderer::createInstance(const std::string& appName, bool enableValidationL

// Add required extensions for GLFW
#if defined(PLATFORM_DESKTOP)
uint32_t glfwExtensionCount = 0;
const char** glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);
extensions.insert(extensions.end(), glfwExtensions, glfwExtensions + glfwExtensionCount);
if (DirectDisplayPlatform::IsRequested()) {
// No windowing system involved: just the base surface extension plus
// VK_KHR_display, which is what actually lets us enumerate and present
// to a physical display output directly.
extensions.push_back(VK_KHR_SURFACE_EXTENSION_NAME);
extensions.push_back(VK_KHR_DISPLAY_EXTENSION_NAME);
} else {
uint32_t glfwExtensionCount = 0;
const char** glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);
extensions.insert(extensions.end(), glfwExtensions, glfwExtensions + glfwExtensionCount);
}
#elif defined(PLATFORM_ANDROID)
extensions.push_back(VK_KHR_SURFACE_EXTENSION_NAME);
extensions.push_back(VK_KHR_ANDROID_SURFACE_EXTENSION_NAME);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,6 @@ Start anywhere that matches your interest:
* xref:Building_a_Simple_Engine/Advanced_Topics/Robustness2.adoc[VK_EXT_robustness2]
* xref:Building_a_Simple_Engine/Advanced_Topics/Dynamic_Rendering_Local_Read.adoc[Dynamic Rendering Local Read]
* xref:Building_a_Simple_Engine/Advanced_Topics/Shader_Tile_Image.adoc[Shader Tile Image]
* xref:Building_a_Simple_Engine/Advanced_Topics/Direct_To_Display.adoc[Direct-to-Display Rendering (VK_KHR_display)]

xref:Building_a_Simple_Engine/introduction.adoc[Back to Building a Simple Engine]
Loading
Loading