/* Copyright 2015-2018 Egor Yusov * * 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 * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF ANY PROPRIETARY RIGHTS. * * In no event and under no legal theory, whether in tort (including negligence), * contract, or otherwise, unless required by applicable law (such as deliberate * and grossly negligent acts) or agreed to in writing, shall any Contributor be * liable for any damages, including any direct, indirect, special, incidental, * or consequential damages of any character arising as a result of this License or * out of the use or inability to use the software (including but not limited to damages * for loss of goodwill, work stoppage, computer failure or malfunction, or any and * all other commercial damages or losses), even if such Contributor has been advised * of the possibility of such damages. */ #include #include #include "PlatformDefinitions.h" #include "NativeAppBase.hpp" #include "StringTools.hpp" #include "Timer.hpp" #include "Errors.hpp" #ifndef GLX_CONTEXT_MAJOR_VERSION_ARB # define GLX_CONTEXT_MAJOR_VERSION_ARB 0x2091 #endif #ifndef GLX_CONTEXT_MINOR_VERSION_ARB # define GLX_CONTEXT_MINOR_VERSION_ARB 0x2092 #endif #ifndef GLX_CONTEXT_FLAGS_ARB # define GLX_CONTEXT_FLAGS_ARB 0x2094 #endif #ifndef GLX_CONTEXT_DEBUG_BIT_ARB # define GLX_CONTEXT_DEBUG_BIT_ARB 0x0001 #endif typedef GLXContext (*glXCreateContextAttribsARBProc)(Display*, GLXFBConfig, GLXContext, int, const int*); static constexpr uint16_t WindowWidth = 1024; static constexpr uint16_t WindowHeight = 768; static constexpr uint16_t MinWindowWidth = 320; static constexpr uint16_t MinWindowHeight = 240; using namespace Diligent; namespace { static constexpr int None = 0; class WindowTitleHelper { public: WindowTitleHelper(std::string _Title) : Title{std::move(_Title)} {} std::string GetTitleWithFPS(double ElapsedTime) { double filterScale = 0.2; FilteredFrameTime = FilteredFrameTime * (1.0 - filterScale) + filterScale * ElapsedTime; std::stringstream TitleWithFpsSS; TitleWithFpsSS << Title; TitleWithFpsSS << " - " << std::fixed << std::setprecision(1) << FilteredFrameTime * 1000; TitleWithFpsSS << " ms (" << 1.0 / FilteredFrameTime << " fps)"; return TitleWithFpsSS.str(); } private: const std::string Title; double FilteredFrameTime = 0.0; }; } // namespace #if VULKAN_SUPPORTED // https://code.woboq.org/qt5/include/xcb/xcb_icccm.h.html enum XCB_SIZE_HINT { XCB_SIZE_HINT_US_POSITION = 1 << 0, XCB_SIZE_HINT_US_SIZE = 1 << 1, XCB_SIZE_HINT_P_POSITION = 1 << 2, XCB_SIZE_HINT_P_SIZE = 1 << 3, XCB_SIZE_HINT_P_MIN_SIZE = 1 << 4, XCB_SIZE_HINT_P_MAX_SIZE = 1 << 5, XCB_SIZE_HINT_P_RESIZE_INC = 1 << 6, XCB_SIZE_HINT_P_ASPECT = 1 << 7, XCB_SIZE_HINT_BASE_SIZE = 1 << 8, XCB_SIZE_HINT_P_WIN_GRAVITY = 1 << 9 }; struct xcb_size_hints_t { uint32_t flags; /** User specified flags */ int32_t x, y; /** User-specified position */ int32_t width, height; /** User-specified size */ int32_t min_width, min_height; /** Program-specified minimum size */ int32_t max_width, max_height; /** Program-specified maximum size */ int32_t width_inc, height_inc; /** Program-specified resize increments */ int32_t min_aspect_num, min_aspect_den; /** Program-specified minimum aspect ratios */ int32_t max_aspect_num, max_aspect_den; /** Program-specified maximum aspect ratios */ int32_t base_width, base_height; /** Program-specified base size */ uint32_t win_gravity; /** Program-specified window gravity */ }; struct XCBInfo { xcb_connection_t* connection = nullptr; uint32_t window = 0; uint16_t width = 0; uint16_t height = 0; xcb_intern_atom_reply_t* atom_wm_delete_window = nullptr; }; XCBInfo InitXCBConnectionAndWindow(const std::string& Title) { XCBInfo info; int scr = 0; info.connection = xcb_connect(nullptr, &scr); if (info.connection == nullptr || xcb_connection_has_error(info.connection)) { std::cerr << "Unable to make an XCB connection\n"; exit(-1); } const xcb_setup_t* setup = xcb_get_setup(info.connection); xcb_screen_iterator_t iter = xcb_setup_roots_iterator(setup); while (scr-- > 0) xcb_screen_next(&iter); auto screen = iter.data; info.width = WindowWidth; info.height = WindowHeight; uint32_t value_mask, value_list[32]; info.window = xcb_generate_id(info.connection); value_mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK; value_list[0] = screen->black_pixel; value_list[1] = XCB_EVENT_MASK_KEY_RELEASE | XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_POINTER_MOTION | XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE; xcb_create_window(info.connection, XCB_COPY_FROM_PARENT, info.window, screen->root, 0, 0, info.width, info.height, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, screen->root_visual, value_mask, value_list); // Magic code that will send notification when window is destroyed xcb_intern_atom_cookie_t cookie = xcb_intern_atom(info.connection, 1, 12, "WM_PROTOCOLS"); xcb_intern_atom_reply_t* reply = xcb_intern_atom_reply(info.connection, cookie, 0); xcb_intern_atom_cookie_t cookie2 = xcb_intern_atom(info.connection, 0, 16, "WM_DELETE_WINDOW"); info.atom_wm_delete_window = xcb_intern_atom_reply(info.connection, cookie2, 0); xcb_change_property(info.connection, XCB_PROP_MODE_REPLACE, info.window, (*reply).atom, 4, 32, 1, &(*info.atom_wm_delete_window).atom); free(reply); xcb_change_property(info.connection, XCB_PROP_MODE_REPLACE, info.window, XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 8, Title.length(), Title.c_str()); // https://stackoverflow.com/a/27771295 xcb_size_hints_t hints = {}; hints.flags = XCB_SIZE_HINT_P_MIN_SIZE; hints.min_width = MinWindowWidth; hints.min_height = MinWindowHeight; xcb_change_property(info.connection, XCB_PROP_MODE_REPLACE, info.window, XCB_ATOM_WM_NORMAL_HINTS, XCB_ATOM_WM_SIZE_HINTS, 32, sizeof(xcb_size_hints_t), &hints); xcb_map_window(info.connection, info.window); // Force the x/y coordinates to 100,100 results are identical in consecutive // runs const uint32_t coords[] = {100, 100}; xcb_configure_window(info.connection, info.window, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, coords); xcb_flush(info.connection); xcb_generic_event_t* e; while ((e = xcb_wait_for_event(info.connection))) { if ((e->response_type & ~0x80) == XCB_EXPOSE) break; } return info; } void DestroyXCBConnectionAndWindow(XCBInfo& info) { xcb_destroy_window(info.connection, info.window); xcb_disconnect(info.connection); } int xcb_main() { std::unique_ptr TheApp(CreateApplication()); std::string Title = TheApp->GetAppTitle(); auto xcbInfo = InitXCBConnectionAndWindow(Title); if (!TheApp->InitVulkan(xcbInfo.connection, xcbInfo.window)) return -1; xcb_flush(xcbInfo.connection); Timer timer; auto PrevTime = timer.GetElapsedTime(); Title = TheApp->GetAppTitle(); WindowTitleHelper TitleHelper(Title); while (true) { xcb_generic_event_t* event = nullptr; bool Quit = false; while ((event = xcb_poll_for_event(xcbInfo.connection)) != nullptr) { TheApp->HandleXCBEvent(event); switch (event->response_type & 0x7f) { case XCB_CLIENT_MESSAGE: if ((*(xcb_client_message_event_t*)event).data.data32[0] == (*xcbInfo.atom_wm_delete_window).atom) { Quit = true; } break; case XCB_KEY_RELEASE: { const auto* keyEvent = reinterpret_cast(event); switch (keyEvent->detail) { # define KEY_ESCAPE 0x9 case KEY_ESCAPE: Quit = true; break; } } break; case XCB_DESTROY_NOTIFY: Quit = true; break; case XCB_CONFIGURE_NOTIFY: { const auto* cfgEvent = reinterpret_cast(event); if ((cfgEvent->width != xcbInfo.width) || (cfgEvent->height != xcbInfo.height)) { xcbInfo.width = cfgEvent->width; xcbInfo.height = cfgEvent->height; if ((xcbInfo.width > 0) && (xcbInfo.height > 0)) { TheApp->WindowResize(xcbInfo.width, xcbInfo.height); } } } break; default: break; } free(event); } if (Quit) break; // Render the scene auto CurrTime = timer.GetElapsedTime(); auto ElapsedTime = CurrTime - PrevTime; PrevTime = CurrTime; TheApp->Update(CurrTime, ElapsedTime); TheApp->Render(); TheApp->Present(); auto TitleWithFPS = TitleHelper.GetTitleWithFPS(ElapsedTime); xcb_change_property(xcbInfo.connection, XCB_PROP_MODE_REPLACE, xcbInfo.window, XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 8, TitleWithFPS.length(), TitleWithFPS.c_str()); xcb_flush(xcbInfo.connection); } TheApp.reset(); DestroyXCBConnectionAndWindow(xcbInfo); return 0; } #endif int x_main() { std::unique_ptr TheApp(CreateApplication()); Display* display = XOpenDisplay(0); // clang-format off static int visual_attribs[] = { GLX_RENDER_TYPE, GLX_RGBA_BIT, GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT, GLX_DOUBLEBUFFER, true, // The largest available total RGBA color buffer size (sum of GLX_RED_SIZE, // GLX_GREEN_SIZE, GLX_BLUE_SIZE, and GLX_ALPHA_SIZE) of at least the minimum // size specified for each color component is preferred. GLX_RED_SIZE, 8, GLX_GREEN_SIZE, 8, GLX_BLUE_SIZE, 8, GLX_ALPHA_SIZE, 8, // The largest available depth buffer of at least GLX_DEPTH_SIZE size is preferred GLX_DEPTH_SIZE, 24, GLX_SAMPLE_BUFFERS, 0, // Setting GLX_SAMPLES to 1 results in 2x MS backbuffer, which is // against the spec that states: // if GLX SAMPLE BUFFERS is zero, then GLX SAMPLES will also be zero // GLX_SAMPLES, 1, None }; // clang-format on int fbcount = 0; GLXFBConfig* fbc = glXChooseFBConfig(display, DefaultScreen(display), visual_attribs, &fbcount); if (!fbc) { LOG_ERROR_MESSAGE("Failed to retrieve a framebuffer config"); return -1; } XVisualInfo* vi = glXGetVisualFromFBConfig(display, fbc[0]); XSetWindowAttributes swa; swa.colormap = XCreateColormap(display, RootWindow(display, vi->screen), vi->visual, AllocNone); swa.border_pixel = 0; swa.event_mask = StructureNotifyMask | ExposureMask | KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | PointerMotionMask; Window win = XCreateWindow(display, RootWindow(display, vi->screen), 0, 0, WindowWidth, WindowHeight, 0, vi->depth, InputOutput, vi->visual, CWBorderPixel | CWColormap | CWEventMask, &swa); if (!win) { LOG_ERROR_MESSAGE("Failed to create window."); return -1; } { auto SizeHints = XAllocSizeHints(); SizeHints->flags = PMinSize; SizeHints->min_width = MinWindowWidth; SizeHints->min_height = MinWindowHeight; XSetWMNormalHints(display, win, SizeHints); XFree(SizeHints); } XMapWindow(display, win); glXCreateContextAttribsARBProc glXCreateContextAttribsARB = nullptr; { // Create an oldstyle context first, to get the correct function pointer for glXCreateContextAttribsARB GLXContext ctx_old = glXCreateContext(display, vi, 0, GL_TRUE); glXCreateContextAttribsARB = (glXCreateContextAttribsARBProc)glXGetProcAddress((const GLubyte*)"glXCreateContextAttribsARB"); glXMakeCurrent(display, None, NULL); glXDestroyContext(display, ctx_old); } if (glXCreateContextAttribsARB == nullptr) { LOG_ERROR("glXCreateContextAttribsARB entry point not found. Aborting."); return -1; } int Flags = GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB; #ifdef _DEBUG Flags |= GLX_CONTEXT_DEBUG_BIT_ARB; #endif int major_version = 4; int minor_version = 3; static int context_attribs[] = { GLX_CONTEXT_MAJOR_VERSION_ARB, major_version, GLX_CONTEXT_MINOR_VERSION_ARB, minor_version, GLX_CONTEXT_FLAGS_ARB, Flags, None // }; constexpr int True = 1; GLXContext ctx = glXCreateContextAttribsARB(display, fbc[0], NULL, True, context_attribs); if (!ctx) { LOG_ERROR("Failed to create GL context."); return -1; } XFree(fbc); glXMakeCurrent(display, win, ctx); if (!TheApp->OnGLContextCreated(display, win)) { LOG_ERROR("Unable to initialize the application in OpenGL mode. Aborting"); return -1; } std::string Title = TheApp->GetAppTitle(); Timer timer; auto PrevTime = timer.GetElapsedTime(); WindowTitleHelper TitleHelper(Title); while (true) { bool EscPressed = false; XEvent xev; // Handle all events in the queue while (XCheckMaskEvent(display, 0xFFFFFFFF, &xev)) { TheApp->HandleXEvent(&xev); switch (xev.type) { case KeyPress: { KeySym keysym; char buffer[80]; int num_char = XLookupString((XKeyEvent*)&xev, buffer, _countof(buffer), &keysym, 0); EscPressed = (keysym == XK_Escape); } case ConfigureNotify: { XConfigureEvent& xce = reinterpret_cast(xev); if (xce.width != 0 && xce.height != 0) TheApp->WindowResize(xce.width, xce.height); break; } } } if (EscPressed) break; // Render the scene auto CurrTime = timer.GetElapsedTime(); auto ElapsedTime = CurrTime - PrevTime; PrevTime = CurrTime; TheApp->Update(CurrTime, ElapsedTime); TheApp->Render(); TheApp->Present(); auto TitleWithFPS = TitleHelper.GetTitleWithFPS(ElapsedTime); XStoreName(display, win, TitleWithFPS.c_str()); } TheApp.reset(); ctx = glXGetCurrentContext(); glXMakeCurrent(display, None, NULL); glXDestroyContext(display, ctx); XDestroyWindow(display, win); XCloseDisplay(display); return 0; } int main(int argc, char** argv) { bool UseVulkan = false; #if VULKAN_SUPPORTED UseVulkan = true; if (argc > 1) { const auto* Key = "-mode "; const auto* pos = strstr(argv[1], Key); if (pos != nullptr) { pos += strlen(Key); while (*pos != 0 && *pos == ' ') ++pos; if (strcasecmp(pos, "GL") == 0) { UseVulkan = false; } else if (strcasecmp(pos, "VK") == 0) { UseVulkan = true; } else { std::cerr << "Unknown device type. Only the following types are supported: GL, VK"; return -1; } } } if (UseVulkan) { auto ret = xcb_main(); if (ret >= 0) { return ret; } else { LOG_ERROR_MESSAGE("Failed to initialize the engine in Vulkan mode. Attempting to use OpenGL"); } } #endif return x_main(); }