From ee177d37df85b982d6c528a7b79e3cd9ca9749d6 Mon Sep 17 00:00:00 2001 From: Egor Yusov Date: Mon, 11 Nov 2019 07:42:14 -0800 Subject: Moved Native App from master repository --- NativeApp/src/Linux/LinuxMain.cpp | 526 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 526 insertions(+) create mode 100644 NativeApp/src/Linux/LinuxMain.cpp (limited to 'NativeApp/src/Linux/LinuxMain.cpp') diff --git a/NativeApp/src/Linux/LinuxMain.cpp b/NativeApp/src/Linux/LinuxMain.cpp new file mode 100644 index 0000000..c8cf197 --- /dev/null +++ b/NativeApp/src/Linux/LinuxMain.cpp @@ -0,0 +1,526 @@ +/* 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.h" +#include "StringTools.h" +#include "Timer.h" +#include "Errors.h" + + +#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 +{ + +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; +}; + +} + +#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); + + 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, 1, + GLX_SAMPLES, 1, + None + }; + + 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); + TheApp->OnGLContextCreated(display, win); + 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); +} + +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(); +} -- cgit v1.2.3