One stop solution for all Vulkan samples
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

519 lines
14 KiB

/* Copyright (c) 2019-2023, 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(Instance &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