////////////////////////////////////////////////////////////////////////////////
// Distributed under the Boost Software License, Version 1.0. //
// (See accompanying file LICENSE or copy at //
// https://www.boost.org/LICENSE_1_0.txt) //
////////////////////////////////////////////////////////////////////////////////
#include "graphics/linux/linux_window.h"
#include
#include
#include
#include
#include
#include
#include
#include "core/auto_release.h"
#include "core/error_handling.h"
#include "events/event.h"
#include "events/keyboard_event.h"
#include "events/mouse_button_event.h"
#include "events/mouse_event.h"
#include "events/quit_event.h"
#include "events/scroll_wheel_event.h"
#include "graphics/linux/scoped_error_handler.h"
#include "graphics/window_manager.h"
#define DONT_MAKE_GL_FUNCTIONS_EXTERN // get concrete function pointers for all
// opengl functions
#include "graphics/opengl/opengl.h"
#include "graphics/opengl/opengl_renderer.h"
bool iris::ScopedErrorHandler::error = false;
// x-macro argument to resolve functions
#define RESOLVE_FUNCTION(_, NAME, ...) resolve_opengl_function(NAME, #NAME);
namespace
{
/**
* Helper function to resolve a single opengl function.
*
* @param function
* Reference to function pointer to resolve.
*
* @param name
* Name of function.
*/
template
void resolve_opengl_function(T &function, const std::string &name)
{
const auto address = ::glXGetProcAddressARB(reinterpret_cast(name.c_str()));
iris::ensure(address != NULL, "could not resolve: " + name);
function = reinterpret_cast(address);
}
/**
* Helper function to resolve all opengl functions.
*/
void resolve_global_opengl_functions()
{
FOR_OPENGL_FUNCTIONS(RESOLVE_FUNCTION);
}
/**
* Helper function to convert an X11 key code to an engine key type.
*
* @param key_code
* X11 key code.
*
* @returns
* Engine Key.
*/
iris::Key x11_key_to_engine_key(unsigned int key_code)
{
iris::Key key;
switch (key_code)
{
case XK_0: key = iris::Key::NUM_0; break;
case XK_1: key = iris::Key::NUM_1; break;
case XK_2: key = iris::Key::NUM_2; break;
case XK_3: key = iris::Key::NUM_3; break;
case XK_4: key = iris::Key::NUM_4; break;
case XK_5: key = iris::Key::NUM_5; break;
case XK_6: key = iris::Key::NUM_6; break;
case XK_7: key = iris::Key::NUM_7; break;
case XK_8: key = iris::Key::NUM_8; break;
case XK_9: key = iris::Key::NUM_9; break;
case XK_a: key = iris::Key::A; break;
case XK_b: key = iris::Key::B; break;
case XK_c: key = iris::Key::C; break;
case XK_d: key = iris::Key::D; break;
case XK_e: key = iris::Key::E; break;
case XK_f: key = iris::Key::F; break;
case XK_g: key = iris::Key::G; break;
case XK_h: key = iris::Key::H; break;
case XK_i: key = iris::Key::I; break;
case XK_j: key = iris::Key::J; break;
case XK_k: key = iris::Key::K; break;
case XK_l: key = iris::Key::L; break;
case XK_m: key = iris::Key::M; break;
case XK_n: key = iris::Key::N; break;
case XK_o: key = iris::Key::O; break;
case XK_p: key = iris::Key::P; break;
case XK_q: key = iris::Key::Q; break;
case XK_r: key = iris::Key::R; break;
case XK_s: key = iris::Key::S; break;
case XK_t: key = iris::Key::T; break;
case XK_u: key = iris::Key::U; break;
case XK_v: key = iris::Key::V; break;
case XK_w: key = iris::Key::W; break;
case XK_x: key = iris::Key::X; break;
case XK_y: key = iris::Key::Y; break;
case XK_z: key = iris::Key::Z; break;
case XK_Tab: key = iris::Key::TAB; break;
case XK_space: key = iris::Key::SPACE; break;
case XK_Escape: key = iris::Key::ESCAPE; break;
case XK_Shift_L: key = iris::Key::SHIFT; break;
case XK_Shift_R: key = iris::Key::RIGHT_SHIFT; break;
case XK_F17: key = iris::Key::F17; break;
case XK_period: key = iris::Key::KEYPAD_DECIMAL; break;
case XK_multiply: key = iris::Key::KEYPAD_MULTIPLY; break;
case XK_plus: key = iris::Key::KEYPAD_PLUS; break;
case XK_division: key = iris::Key::KEYPAD_DIVIDE; break;
case XK_minus: key = iris::Key::KEYPAD_MINUS; break;
case XK_F18: key = iris::Key::F18; break;
case XK_F19: key = iris::Key::F19; break;
case XK_KP_0: key = iris::Key::KEYPAD_0; break;
case XK_KP_1: key = iris::Key::KEYPAD_1; break;
case XK_KP_2: key = iris::Key::KEYPAD_2; break;
case XK_KP_3: key = iris::Key::KEYPAD_3; break;
case XK_KP_4: key = iris::Key::KEYPAD_4; break;
case XK_KP_5: key = iris::Key::KEYPAD_5; break;
case XK_KP_6: key = iris::Key::KEYPAD_6; break;
case XK_KP_7: key = iris::Key::KEYPAD_7; break;
case XK_F20: key = iris::Key::F20; break;
case XK_KP_8: key = iris::Key::KEYPAD_8; break;
case XK_KP_9: key = iris::Key::KEYPAD_9; break;
case XK_F5: key = iris::Key::F5; break;
case XK_F6: key = iris::Key::F6; break;
case XK_F7: key = iris::Key::F7; break;
case XK_F3: key = iris::Key::F3; break;
case XK_F8: key = iris::Key::F8; break;
case XK_F9: key = iris::Key::F9; break;
case XK_F11: key = iris::Key::F11; break;
case XK_F13: key = iris::Key::F13; break;
case XK_F16: key = iris::Key::F16; break;
case XK_F14: key = iris::Key::F14; break;
case XK_F10: key = iris::Key::F10; break;
case XK_F12: key = iris::Key::F12; break;
case XK_F15: key = iris::Key::F15; break;
case XK_F4: key = iris::Key::F4; break;
case XK_F2: key = iris::Key::F2; break;
case XK_F1: key = iris::Key::F1; break;
case XK_Left: key = iris::Key::LEFT_ARROW; break;
case XK_Right: key = iris::Key::RIGHT_ARROW; break;
case XK_Down: key = iris::Key::DOWN_ARROW; break;
case XK_Up: key = iris::Key::UP_ARROW; break;
default: key = iris::Key::UNKNOWN;
}
return key;
}
}
namespace iris
{
LinuxWindow::LinuxWindow(
std::uint32_t width,
std::uint32_t height,
WindowManager &window_manager,
TextureManager &texture_manager,
MaterialManager &material_manager)
: Window(width, height)
{
display_ = {::XOpenDisplay(nullptr), ::XCloseDisplay};
ensure(display_, "could not open display");
static const int visual_attribs[] = {
GLX_X_RENDERABLE,
True,
GLX_DRAWABLE_TYPE,
GLX_WINDOW_BIT,
GLX_RENDER_TYPE,
GLX_RGBA_BIT,
GLX_X_VISUAL_TYPE,
GLX_TRUE_COLOR,
GLX_RED_SIZE,
8,
GLX_GREEN_SIZE,
8,
GLX_BLUE_SIZE,
8,
GLX_ALPHA_SIZE,
8,
GLX_DEPTH_SIZE,
24,
GLX_STENCIL_SIZE,
8,
GLX_DOUBLEBUFFER,
True,
None};
int glx_major = 0;
int glx_minor = 0;
// check we have the minimum version of glx (the opengl extension to x11)
ensure(::glXQueryVersion(display_, &glx_major, &glx_minor) == True, "could not query extension version");
ensure((glx_major > 1) || ((glx_major == 1) && (glx_minor >= 3)), "incompatible glx version");
int fb_count = 0;
// get all the frame buffer configurations that match our attributes
AutoRelease fb_config = {
::glXChooseFBConfig(display_, DefaultScreen(display_.get()), visual_attribs, &fb_count), ::XFree};
ensure(fb_config && (fb_count > 0), "could not get framebuffer config");
int best_fb_config = -1;
int best_num_samples = -1;
// find the config with the most samples
for (auto i = 0; i < fb_count; ++i)
{
AutoRelease info = {::glXGetVisualFromFBConfig(display_, fb_config[i]), ::XFree};
if (info)
{
int sample_buffer = 0;
int samples = 0;
if (::glXGetFBConfigAttrib(display_, fb_config[i], GLX_SAMPLE_BUFFERS, &sample_buffer) == Success)
{
if (::glXGetFBConfigAttrib(display_, fb_config[i], GLX_SAMPLES, &samples) == Success)
{
if (samples > best_num_samples)
{
best_num_samples = samples;
best_fb_config = i;
}
}
}
}
}
auto config = fb_config[best_fb_config];
// get the visual info for our chosen config
AutoRelease visual_info = {::glXGetVisualFromFBConfig(display_, config), ::XFree};
ensure(visual_info, "could not get visual info");
AutoRelease colour_map = {
::XCreateColormap(
display_, RootWindow(display_.get(), visual_info.get()->screen), visual_info.get()->visual, AllocNone),
[this](Colormap colour_map) { ::XFreeColormap(display_, colour_map); }};
XSetWindowAttributes attributes{0};
attributes.colormap = colour_map;
attributes.background_pixmap = 0;
attributes.border_pixel = 0;
attributes.event_mask = StructureNotifyMask;
// create an x11 window
window_ = {
::XCreateWindow(
display_,
RootWindow(display_.get(), visual_info.get()->screen),
0,
0,
width_,
height_,
0,
visual_info.get()->depth,
InputOutput,
visual_info.get()->visual,
CWBorderPixel | CWColormap | CWEventMask,
&attributes),
[this](::Window window) { ::XDestroyWindow(display_, window); }};
ensure(window_, "could not create widnow");
// set which events we want to receive
::XSelectInput(
display_,
window_,
KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | PointerMotionMask | ExposureMask);
// register for close window events
Atom wmDeleteMessage = XInternAtom(display_, "WM_DELETE_WINDOW", False);
XSetWMProtocols(display_, window_, &wmDeleteMessage, 1);
::XMapWindow(display_, window_);
// check opengl version
{
ScopedErrorHandler error_handler;
// we need to find this function first
auto *glXCreateContextAttribsARB =
reinterpret_cast(
::glXGetProcAddressARB(reinterpret_cast("glXCreateContextAttribsARB")));
ensure(glXCreateContextAttribsARB != nullptr, "unsupported extension");
resolve_global_opengl_functions();
// want at least opengl 3.3
const int context_attribs[] = {GLX_CONTEXT_MAJOR_VERSION_ARB, 3, GLX_CONTEXT_MINOR_VERSION_ARB, 3, None};
context_ = {glXCreateContextAttribsARB(display_, config, 0, True, context_attribs), [this](GLXContext context) {
::glXDestroyContext(display_, context);
}};
::XSync(display_, False);
ensure(context_ && (error_handler == false), "could not create context");
}
ensure(::glXMakeCurrent(display_, window_, context_) == True, "could not make context current");
renderer_ = std::make_unique(window_manager, texture_manager, material_manager, width_, height_);
const auto scale = screen_scale();
XWindowChanges changes{0};
changes.width = width_ * scale;
changes.height = height_ * scale;
// resize window based on scale
::XConfigureWindow(display_, window_, CWWidth | CWHeight, &changes);
::XSetLocaleModifiers("");
// hide cursor
::XFixesHideCursor(display_, window_);
::XFlush(display_);
// move cursor to centre of screen
::XWarpPointer(display_, None, window_, 0, 0, 0, 0, changes.width / 2, changes.height / 2);
}
std::uint32_t LinuxWindow::screen_scale() const
{
const auto dpi = ((double)DisplayWidth(display_.get(), 0)) / (((double)DisplayWidthMM(display_.get(), 0)) / 25.4);
return static_cast<:uint32_t>(std::floor(static_cast(dpi) / 96.0f));
}
std::optional LinuxWindow::pump_event()
{
XEvent event{0};
while (::XCheckWindowEvent(
display_,
window_,
KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | PointerMotionMask | ExposureMask,
&event) == True)
{
if (event.type == KeyPress)
{
events_.emplace(KeyboardEvent{x11_key_to_engine_key(::XLookupKeysym(&event.xkey, 0)), KeyState::DOWN});
}
else if (event.type == KeyRelease)
{
events_.emplace(KeyboardEvent{x11_key_to_engine_key(::XLookupKeysym(&event.xkey, 0)), KeyState::UP});
}
else if (event.type == ButtonPress)
{
if (event.xbutton.button == Button1)
{
events_.emplace(MouseButtonEvent{MouseButton::LEFT, MouseButtonState::DOWN});
}
else if (event.xbutton.button == Button2)
{
events_.emplace(MouseButtonEvent{MouseButton::RIGHT, MouseButtonState::DOWN});
}
else if (event.xbutton.button == Button4)
{
events_.emplace(ScrollWheelEvent{1.0f});
}
else if (event.xbutton.button == Button5)
{
events_.emplace(ScrollWheelEvent{-1.0f});
}
}
else if (event.type == ButtonRelease)
{
if (event.xbutton.button == Button1)
{
events_.emplace(MouseButtonEvent{MouseButton::LEFT, MouseButtonState::UP});
}
else if (event.xbutton.button == Button2)
{
events_.emplace(MouseButtonEvent{MouseButton::RIGHT, MouseButtonState::UP});
}
}
else if (event.type == MotionNotify)
{
static auto x = event.xmotion.x;
static auto y = event.xmotion.y;
events_.emplace(
MouseEvent{static_cast(event.xmotion.x - x), static_cast(event.xmotion.y - y)});
x = event.xmotion.x;
y = event.xmotion.y;
}
}
std::optional next_event{};
if (!events_.empty())
{
next_event = events_.front();
events_.pop();
}
return next_event;
}
Display *LinuxWindow::display() const
{
return display_.get();
}
::Window LinuxWindow::window() const
{
return window_.get();
}
}