520 lines
14 KiB
C++
520 lines
14 KiB
C++
/* Copyright (c) 2019-2025, Arm Limited and Contributors
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 the "License";
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include "direct_window.h"
|
|
|
|
#include <fcntl.h>
|
|
#include <sys/stat.h>
|
|
|
|
#include "core/instance.h"
|
|
|
|
#include "platform/headless_window.h"
|
|
#include "platform/unix/direct_window.h"
|
|
|
|
namespace vkb
|
|
{
|
|
namespace
|
|
{
|
|
static KeyCode key_map[] = {
|
|
KeyCode::Unknown,
|
|
KeyCode::Unknown,
|
|
KeyCode::Unknown,
|
|
KeyCode::Unknown,
|
|
KeyCode::Unknown,
|
|
KeyCode::Unknown,
|
|
KeyCode::Unknown,
|
|
KeyCode::Unknown,
|
|
KeyCode::Backspace,
|
|
KeyCode::Tab,
|
|
KeyCode::Unknown,
|
|
KeyCode::Unknown,
|
|
KeyCode::Unknown,
|
|
KeyCode::Enter,
|
|
KeyCode::Unknown,
|
|
KeyCode::Unknown,
|
|
KeyCode::Unknown,
|
|
KeyCode::Unknown,
|
|
KeyCode::Unknown,
|
|
KeyCode::Unknown,
|
|
KeyCode::Unknown,
|
|
KeyCode::Unknown,
|
|
KeyCode::Unknown,
|
|
KeyCode::Unknown,
|
|
KeyCode::Unknown,
|
|
KeyCode::Unknown,
|
|
KeyCode::Unknown,
|
|
KeyCode::Escape,
|
|
KeyCode::Unknown,
|
|
KeyCode::Unknown,
|
|
KeyCode::Unknown,
|
|
KeyCode::Unknown,
|
|
KeyCode::Space,
|
|
KeyCode::_1,
|
|
KeyCode::Apostrophe,
|
|
KeyCode::Backslash,
|
|
KeyCode::_4,
|
|
KeyCode::_5,
|
|
KeyCode::_7,
|
|
KeyCode::Apostrophe,
|
|
KeyCode::_9,
|
|
KeyCode::_0,
|
|
KeyCode::_8,
|
|
KeyCode::Equal,
|
|
KeyCode::Comma,
|
|
KeyCode::Minus,
|
|
KeyCode::Period,
|
|
KeyCode::Slash,
|
|
KeyCode::_0,
|
|
KeyCode::_1,
|
|
KeyCode::_2,
|
|
KeyCode::_3,
|
|
KeyCode::_4,
|
|
KeyCode::_5,
|
|
KeyCode::_6,
|
|
KeyCode::_7,
|
|
KeyCode::_8,
|
|
KeyCode::_9,
|
|
KeyCode::Semicolon,
|
|
KeyCode::Semicolon,
|
|
KeyCode::Comma,
|
|
KeyCode::Equal,
|
|
KeyCode::Period,
|
|
KeyCode::Slash,
|
|
KeyCode::_2,
|
|
KeyCode::A,
|
|
KeyCode::B,
|
|
KeyCode::C,
|
|
KeyCode::D,
|
|
KeyCode::E,
|
|
KeyCode::F,
|
|
KeyCode::G,
|
|
KeyCode::H,
|
|
KeyCode::I,
|
|
KeyCode::J,
|
|
KeyCode::K,
|
|
KeyCode::L,
|
|
KeyCode::M,
|
|
KeyCode::N,
|
|
KeyCode::O,
|
|
KeyCode::P,
|
|
KeyCode::Q,
|
|
KeyCode::R,
|
|
KeyCode::S,
|
|
KeyCode::T,
|
|
KeyCode::U,
|
|
KeyCode::V,
|
|
KeyCode::W,
|
|
KeyCode::X,
|
|
KeyCode::Y,
|
|
KeyCode::Z,
|
|
KeyCode::LeftBracket,
|
|
KeyCode::Backslash,
|
|
KeyCode::RightBracket,
|
|
KeyCode::_6,
|
|
KeyCode::Minus,
|
|
KeyCode::GraveAccent,
|
|
KeyCode::A,
|
|
KeyCode::B,
|
|
KeyCode::C,
|
|
KeyCode::D,
|
|
KeyCode::E,
|
|
KeyCode::F,
|
|
KeyCode::G,
|
|
KeyCode::H,
|
|
KeyCode::I,
|
|
KeyCode::J,
|
|
KeyCode::K,
|
|
KeyCode::L,
|
|
KeyCode::M,
|
|
KeyCode::N,
|
|
KeyCode::O,
|
|
KeyCode::P,
|
|
KeyCode::Q,
|
|
KeyCode::R,
|
|
KeyCode::S,
|
|
KeyCode::T,
|
|
KeyCode::U,
|
|
KeyCode::V,
|
|
KeyCode::W,
|
|
KeyCode::X,
|
|
KeyCode::Y,
|
|
KeyCode::Z,
|
|
KeyCode::LeftBracket,
|
|
KeyCode::Backslash,
|
|
KeyCode::RightBracket,
|
|
KeyCode::GraveAccent,
|
|
KeyCode::Backspace};
|
|
|
|
static KeyCode map_multichar_key(int tty_fd, KeyCode initial)
|
|
{
|
|
static std::map<std::string, KeyCode> mc_map; // Static for one-time-init
|
|
if (mc_map.size() == 0)
|
|
{
|
|
mc_map["[A"] = KeyCode::Up;
|
|
mc_map["[B"] = KeyCode::Down;
|
|
mc_map["[C"] = KeyCode::Right;
|
|
mc_map["[D"] = KeyCode::Left;
|
|
mc_map["[2~"] = KeyCode::Insert;
|
|
mc_map["[3~"] = KeyCode::DelKey;
|
|
mc_map["[5~"] = KeyCode::PageUp;
|
|
mc_map["[6~"] = KeyCode::PageDown;
|
|
mc_map["[H"] = KeyCode::Home;
|
|
mc_map["[F"] = KeyCode::End;
|
|
mc_map["OP"] = KeyCode::F1;
|
|
mc_map["OQ"] = KeyCode::F2;
|
|
mc_map["OR"] = KeyCode::F3;
|
|
mc_map["OS"] = KeyCode::F4;
|
|
mc_map["[15~"] = KeyCode::F5;
|
|
mc_map["[17~"] = KeyCode::F6;
|
|
mc_map["[18~"] = KeyCode::F7;
|
|
mc_map["[19~"] = KeyCode::F8;
|
|
mc_map["[20~"] = KeyCode::F9;
|
|
mc_map["[21~"] = KeyCode::F10;
|
|
mc_map["[23~"] = KeyCode::F11;
|
|
mc_map["[24~"] = KeyCode::F12;
|
|
}
|
|
|
|
char key;
|
|
std::string buf;
|
|
|
|
while (::read(tty_fd, &key, 1) == 1)
|
|
{
|
|
buf += key;
|
|
}
|
|
|
|
if (buf.size() == 0)
|
|
{
|
|
return initial; // Nothing new read, just return the initial char
|
|
}
|
|
|
|
// Is it a code we recognise?
|
|
auto iter = mc_map.find(buf);
|
|
if (iter != mc_map.end())
|
|
{
|
|
return iter->second;
|
|
}
|
|
|
|
return KeyCode::Unknown;
|
|
}
|
|
|
|
} // namespace
|
|
DirectWindow::DirectWindow(Platform *platform, const Window::Properties &properties) :
|
|
Window(properties),
|
|
platform{platform}
|
|
{
|
|
// Setup tty for reading keyboard from console
|
|
if ((tty_fd = open("/dev/tty", O_RDWR | O_NDELAY)) > 0)
|
|
{
|
|
tcgetattr(tty_fd, &termio_prev);
|
|
tcgetattr(tty_fd, &termio);
|
|
cfmakeraw(&termio);
|
|
termio.c_lflag |= ISIG;
|
|
termio.c_oflag |= OPOST | ONLCR;
|
|
termio.c_cc[VMIN] = 1;
|
|
termio.c_cc[VTIME] = 0;
|
|
|
|
if (tcsetattr(tty_fd, TCSANOW, &termio) == -1)
|
|
LOGW("Failed to set attribs for '/dev/tty'");
|
|
}
|
|
|
|
platform->set_focus(true);
|
|
}
|
|
|
|
DirectWindow::~DirectWindow()
|
|
{
|
|
if (tty_fd > 0)
|
|
{
|
|
tcsetattr(tty_fd, TCSANOW, &termio_prev);
|
|
}
|
|
}
|
|
|
|
VkSurfaceKHR DirectWindow::create_surface(vkb::core::InstanceC &instance)
|
|
{
|
|
return create_surface(instance.get_handle(), instance.get_first_gpu().get_handle());
|
|
}
|
|
|
|
// Local structure for holding display candidate information
|
|
struct Candidate
|
|
{
|
|
VkDisplayKHR display;
|
|
VkDisplayPropertiesKHR display_props;
|
|
VkDisplayModePropertiesKHR mode;
|
|
VkDisplayPlaneCapabilitiesKHR caps;
|
|
uint32_t plane_index;
|
|
uint32_t stack_index;
|
|
};
|
|
|
|
// Helper template to get a vector of properties using the Vulkan idiom of
|
|
// querying size, then querying data
|
|
template <typename T, typename F, typename... Targs>
|
|
static std::vector<T> get_props(F func, Targs... args)
|
|
{
|
|
std::vector<T> result;
|
|
uint32_t count;
|
|
|
|
VK_CHECK(func(args..., &count, nullptr));
|
|
if (count > 0)
|
|
{
|
|
result.resize(count);
|
|
VK_CHECK(func(args..., &count, result.data()));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// Find all valid display candidates in the system
|
|
static std::vector<Candidate> find_display_candidates(VkPhysicalDevice phys_dev, Window::Mode window_mode)
|
|
{
|
|
// Possible display config candidates
|
|
std::vector<Candidate> candidates;
|
|
|
|
// Find all the displays connected to this platform
|
|
auto display_properties = get_props<VkDisplayPropertiesKHR>(vkGetPhysicalDeviceDisplayPropertiesKHR, phys_dev);
|
|
|
|
// Find all the display planes
|
|
auto plane_properties = get_props<VkDisplayPlanePropertiesKHR>(vkGetPhysicalDeviceDisplayPlanePropertiesKHR, phys_dev);
|
|
|
|
// Build a list of candidates
|
|
for (uint32_t plane_index = 0; plane_index < plane_properties.size(); plane_index++)
|
|
{
|
|
const VkDisplayPlanePropertiesKHR &plane_props = plane_properties[plane_index];
|
|
|
|
// Find the list of displays compatible with the plane
|
|
auto supported_displays = get_props<VkDisplayKHR>(vkGetDisplayPlaneSupportedDisplaysKHR, phys_dev, plane_index);
|
|
|
|
for (const auto &display : supported_displays)
|
|
{
|
|
auto props = std::find_if(display_properties.cbegin(), display_properties.cend(),
|
|
[display](const VkDisplayPropertiesKHR &p) { return p.display == display; });
|
|
if (props == display_properties.end())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Cannot use if already on another display
|
|
if (plane_props.currentDisplay != VK_NULL_HANDLE && plane_props.currentDisplay != display)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Cannot use if identity transform is unsupported
|
|
if ((props->supportedTransforms & VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR) == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Find all the display modes for the display
|
|
auto modes = get_props<VkDisplayModePropertiesKHR>(vkGetDisplayModePropertiesKHR, phys_dev, display);
|
|
|
|
for (auto mode : modes)
|
|
{
|
|
// Query capabilities of this mode/plane combination
|
|
VkDisplayPlaneCapabilitiesKHR caps;
|
|
VK_CHECK(vkGetDisplayPlaneCapabilitiesKHR(phys_dev, mode.displayMode, plane_index, &caps));
|
|
|
|
// Check for opaque alpha support
|
|
if ((caps.supportedAlpha & VK_DISPLAY_PLANE_ALPHA_OPAQUE_BIT_KHR) == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Eliminate modes that don't fit the plane capabilities
|
|
if (mode.parameters.visibleRegion.width > caps.maxDstExtent.width ||
|
|
mode.parameters.visibleRegion.height > caps.maxDstExtent.height ||
|
|
mode.parameters.visibleRegion.width < caps.minDstExtent.width ||
|
|
mode.parameters.visibleRegion.height < caps.minDstExtent.height)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (window_mode == Window::Mode::Fullscreen ||
|
|
window_mode == Window::Mode::FullscreenBorderless)
|
|
{
|
|
// For full-screen modes (where the src image is the same size as the
|
|
// display) we must also check the src extents are valid.
|
|
if (mode.parameters.visibleRegion.width > caps.maxSrcExtent.width ||
|
|
mode.parameters.visibleRegion.height > caps.maxSrcExtent.height ||
|
|
mode.parameters.visibleRegion.width < caps.minSrcExtent.width ||
|
|
mode.parameters.visibleRegion.height < caps.minSrcExtent.height)
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Add to list of candidates
|
|
candidates.push_back(Candidate{display, *props, mode, caps, plane_index,
|
|
plane_properties[plane_index].currentStackIndex});
|
|
}
|
|
}
|
|
}
|
|
|
|
return candidates;
|
|
}
|
|
|
|
VkSurfaceKHR DirectWindow::create_surface(VkInstance instance, VkPhysicalDevice phys_dev)
|
|
{
|
|
if (instance == VK_NULL_HANDLE || phys_dev == VK_NULL_HANDLE)
|
|
{
|
|
return VK_NULL_HANDLE;
|
|
}
|
|
|
|
// Find possible display config candidates
|
|
std::vector<Candidate> candidates = find_display_candidates(phys_dev, properties.mode);
|
|
if (candidates.empty())
|
|
{
|
|
LOGE("Direct-to-display: No compatible display candidates found");
|
|
return VK_NULL_HANDLE;
|
|
}
|
|
|
|
// 'candidates' contains valid potential display configs. Find the best one.
|
|
// Look for the candidate with the closest resolution to our requested width & height.
|
|
uint32_t best_candidate = 0;
|
|
uint32_t wanted_area = properties.extent.width * properties.extent.height;
|
|
uint32_t best_diff = ~0;
|
|
|
|
for (size_t c = 0; c < candidates.size(); c++)
|
|
{
|
|
const Candidate &cand = candidates[c];
|
|
|
|
uint32_t area = cand.display_props.physicalResolution.width *
|
|
cand.display_props.physicalResolution.height;
|
|
|
|
uint32_t diff = abs(static_cast<int32_t>(area) - static_cast<int32_t>(wanted_area));
|
|
|
|
if (diff < best_diff)
|
|
{
|
|
best_diff = diff;
|
|
best_candidate = c;
|
|
}
|
|
}
|
|
|
|
const Candidate &best = candidates[best_candidate];
|
|
|
|
// Get the full display mode extent
|
|
full_extent.width = best.mode.parameters.visibleRegion.width;
|
|
full_extent.height = best.mode.parameters.visibleRegion.height;
|
|
|
|
VkExtent2D image_extent;
|
|
if (properties.mode == Window::Mode::Fullscreen ||
|
|
properties.mode == Window::Mode::FullscreenBorderless)
|
|
{
|
|
// Fullscreen & Borderless options create a surface that matches the display size
|
|
image_extent.width = full_extent.width;
|
|
image_extent.height = full_extent.height;
|
|
}
|
|
else
|
|
{
|
|
// Normal and stretch options create a surface at the requested width & height
|
|
// clamped to the plane capabilities in play
|
|
image_extent.width = std::min(best.caps.maxSrcExtent.width,
|
|
std::max(properties.extent.width, best.caps.minSrcExtent.width));
|
|
image_extent.height = std::min(best.caps.maxSrcExtent.height,
|
|
std::max(properties.extent.height, best.caps.minSrcExtent.height));
|
|
}
|
|
|
|
// Calculate the display DPI
|
|
constexpr float mm_per_inch = 25.4f;
|
|
|
|
dpi = mm_per_inch * best.display_props.physicalResolution.width / best.display_props.physicalDimensions.width;
|
|
|
|
// Create the surface
|
|
VkDisplaySurfaceCreateInfoKHR surface_create_info{};
|
|
surface_create_info.sType = VK_STRUCTURE_TYPE_DISPLAY_SURFACE_CREATE_INFO_KHR;
|
|
surface_create_info.displayMode = best.mode.displayMode;
|
|
surface_create_info.planeIndex = best.plane_index;
|
|
surface_create_info.planeStackIndex = best.stack_index;
|
|
surface_create_info.transform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
|
|
surface_create_info.alphaMode = VK_DISPLAY_PLANE_ALPHA_OPAQUE_BIT_KHR;
|
|
surface_create_info.imageExtent = image_extent;
|
|
|
|
VkSurfaceKHR surface;
|
|
VK_CHECK(vkCreateDisplayPlaneSurfaceKHR(instance, &surface_create_info, nullptr, &surface));
|
|
|
|
return surface;
|
|
}
|
|
|
|
void DirectWindow::process_events()
|
|
{
|
|
if (tty_fd > 0)
|
|
{
|
|
if (key_down != KeyCode::Unknown)
|
|
{
|
|
// Signal release for the key we previously reported as down
|
|
// (we don't get separate press & release from the terminal)
|
|
platform->input_event(KeyInputEvent{key_down, KeyAction::Up});
|
|
key_down = KeyCode::Unknown;
|
|
}
|
|
|
|
// See if there is a new keypress
|
|
uint8_t key = 0;
|
|
int bytes = ::read(tty_fd, &key, 1);
|
|
|
|
if (bytes > 0 && key > 0 && (key < sizeof(key_map) / sizeof(KeyCode)))
|
|
{
|
|
// Map the key
|
|
key_down = key_map[key];
|
|
|
|
// Is this potentially a multi-character code?
|
|
if (key_down == KeyCode::Escape)
|
|
{
|
|
key_down = map_multichar_key(tty_fd, key_down);
|
|
}
|
|
|
|
// Signal the press
|
|
platform->input_event(KeyInputEvent{key_down, KeyAction::Down});
|
|
}
|
|
}
|
|
}
|
|
|
|
bool DirectWindow::should_close()
|
|
{
|
|
return !keep_running;
|
|
}
|
|
|
|
void DirectWindow::close()
|
|
{
|
|
keep_running = false;
|
|
}
|
|
|
|
float DirectWindow::get_dpi_factor() const
|
|
{
|
|
const float win_base_density = 96.0f;
|
|
return dpi / win_base_density;
|
|
}
|
|
|
|
bool DirectWindow::get_display_present_info(VkDisplayPresentInfoKHR *info,
|
|
uint32_t src_width, uint32_t src_height) const
|
|
{
|
|
// Only stretch mode needs to supply a VkDisplayPresentInfoKHR
|
|
if (properties.mode != Window::Mode::FullscreenStretch || !info)
|
|
return false;
|
|
|
|
info->sType = VK_STRUCTURE_TYPE_DISPLAY_PRESENT_INFO_KHR;
|
|
info->pNext = nullptr;
|
|
info->srcRect = {0, 0, src_width, src_height};
|
|
info->dstRect = {0, 0, full_extent.width, full_extent.height};
|
|
info->persistent = false;
|
|
|
|
return true;
|
|
}
|
|
|
|
std::vector<const char *> DirectWindow::get_required_surface_extensions() const
|
|
{
|
|
return {VK_KHR_DISPLAY_EXTENSION_NAME};
|
|
}
|
|
|
|
} // namespace vkb
|