From 4372f1c09e5ebccc036dfc0f3026684851a0575c Mon Sep 17 00:00:00 2001 From: Guck Stift Date: Thu, 6 Aug 2020 11:01:18 +0200 Subject: [PATCH] Fix formatting, add syntax highlighting --- README.md | 138 +++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 96 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index d42228f..5ed794e 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ -#Introduction +# Introduction Most tutorials seem to start off with a discussion of how hard X is, how it will mess up your head and how only a masochist would ever write X code with alternates such as SDL out there. This, in my opinion is just wrong. In the trinity of major operating systems i think X is the sanest, most reliable and cleanest designed window manager. With that in mind, lets create an OpenGL enabled X11 window for video games. -#Part 1, Creating a Window +# Part 1, Creating a Window The X windowing system uses a client/server architecture. A single machine can have X running in multiple instances. -##Creating an X Window +## Creating an X Window The first thing to do when opening a window under x is to tell it where the screen is. Altough there are several ways of telling the client where the server is, the most fullproof is the **DISPLAY** environment variable, or using *NULL* for default. The function ```XOpenDisplay(char* display)``` makes the connection to the X server. It takes one argument, a string using the display format described above, or *NULL* to use the default. It returns a pointer to the display (of type *Display*) on success or *NULL* on error. @@ -14,9 +14,11 @@ With the pointer the display, find the screen using the ```DefaultScreenOfDispla To find the screen ID of the screen use the ```DefaultScreen(Display* display)``` function, this is almost identical to **DefaultScreenOfDisplay** except that it retuns an integer. With just a display and a screen a window can be created with the **XCreateSimpleWindow** function. The prototype for this function is as follows -``` + +```c Window XCreateSimpleWindow(Display* display, Window* parent, int x, int y, uint borderw, ulong border, ulong background); ``` + The first argument *display* is a pointer to the display object acquired using **XOpenDisplay**. The next argument, parent is a pointer to the window opening this one. If this is the first window of a program opening, use the ```RootWindowOfScreen(Screen* screen)``` function. This function takes one argument, the pointer to the screen that was acquired using **DefaultScreenOfDisplay** The next four arguments are the location and sizeof the window. the *border-width* argument specifies the width of the window border in pixels. The next argument *border* is the color of the border. The last paramater *background* is simply the background color of the window. @@ -29,8 +31,9 @@ At this point the window will not show up yet. First it must process messages (s After the window is finished, force the window to close with the ```XDestroyWindow(Display* display, Window window)``` function and cleanup the display using the ```XCloseDisplay(Display* display)``` function, which takes one argument, the display to close. -###Sample Code (C) -``` +### Sample Code (C) + +```c #include int main(int argc, char** argv) { @@ -69,20 +72,24 @@ int main(int argc, char** argv) { return 1; } ``` + Compile the program using the following command line + ``` LINUX: gcc -o XSampleWindow XSampleWindow.c -lX11 -L/usr/X11R6/lib -I/usr/X11R6/include OSX: gcc -o XSampleWindow XSampleWindow.c -lX11 -L/usr/X11/lib -I/opt/X11/include ``` -#Part 2, Processing Events +# Part 2, Processing Events + To register for events use the ```XSelectInput(Display* display, uint mask)``` function, it takes as arguments the display object, the window object and a bitmask representing what events the message loop will subscribe to. This function should be called before the *XMapWindow* function. TODO: Event table listing http://tronche.com/gui/x/xlib/events/processing-overview.html -##Keyboard Input +## Keyboard Input + To process keyboard input subscribe to three event masks *KeyPressMask* *KeyReleaseMask* and *KeymapStateMask*. The first two events will fire when a key is pressed or released. X handles mapping characters to key numbers, this mapping can change at any time if the user remaps keys. the KeyMask event is fired when the keymapping of the system changes. Once subscribed to ```KeyPressMask | KeyReleaseMask | KeymapStateMask``` three events types will be fired, they are *KeyPress*, *KeyRelease* and *KeymapNotify*. Check the *type* variable of the *XEvent* object against these types. @@ -91,8 +98,9 @@ In case the *KeyMapNotify* event is recieved, call the ```XRefreshKeyboardMappin If the *KeyPress* or *KeyRelease* events get fired use the ```int XLookupString(XKeyEvent *event_struct, char *buffer_return, int bytes_buffer, KeySym *keysym_return, XComposeStatus *status_in_out)``` function to translate the event into a **KeySym** (a *KeySym* is a typedef for an unsigned long coresponding to the keyID of the key pressed) and a string. *buffer_return* will hold the translated character string, *keysym_return* will hold the *KeySym* value. **keysymdef.h** holds a list of #define strings for each key. -###Sample Code (C++) -``` +### Sample Code (C++) + +```cpp #include #include #include @@ -160,25 +168,30 @@ int main(int argc, char** argv) { return 1; } ``` + Compile the program using the following command line + ``` LINUX: g++ -o XKeyIn XKeyIn.cpp -lX11 -L/usr/X11R6/lib -I/usr/X11R6/include OSX: g++ -o XKeyIn XKeyIn.cpp -lX11 -L/usr/X11/lib -I/opt/X11/include ``` -##Mouse Input +## Mouse Input + To process mouse events subscribe to the *PointerMotionMask*, *ButtonPressMask*, *ButtonReleaseMask*, *EnterWindowMask* and *LeaveWindowMask* event masks. These masks will fire the following events: *MotionNotify*, *ButtonPress*, *ButtonRelease*, *EnterNotify* and *LeaveNotify*. Masks: + ```PointerMotionMask | ButtonPressMask | ButtonReleaseMask | EnterWindowMask | LeaveWindowMask``` On *ButtonPress* and *ButtonRelease* the event object will contains an *xbutton* field. This field has a button variable, which maps as follows: 1 - left mouse, 2 - middle mouse, 3 - right mouse, 4 - scroll up, 5 - scroll down. On *MotionNotify* the event object will contain a *xmotion* field. This field has two variables *x* and *y*. The *x* and *y* variables contain the mouses position in window space. -###Sample Code (C++) -``` +### Sample Code (C++) + +```cpp #include #include #include @@ -267,7 +280,8 @@ int main(int argc, char** argv) { } ``` -##Window Size & Title +## Window Size & Title + Set the window's title using the ```XStoreName(Display* d, Window w, char *window_name)``` function. The first argument is the display link, the second argument is the window object and the third argument is the new window title. Query the size of the current window with the ```XGetWindowAttributes(Display* d, Window w, XWindowAttributes* winAttribs);``` function. The first argument is the display link, the second is the window object. The third argument is a *XWindowAttributes* object, this object has two fields *width* and *height*. @@ -276,8 +290,9 @@ When a window is resized the *Expose* event is fired, listen for this event by s At runtime the width, height and position of the window can be changed with the ```XConfigureWindow(Display* d, Window w, uint value_mask, XWindowChanges *values;``` function. The first argument is the display link, the second argument is the window. The third argument is an unsigned int, this is a bitmask representing which fields of the fourth argument to use. The fourth argument is a pointer to a *XWindowChanges* structure. Valid values for the mask are: *CWX*, *CWY*, *CWWidth*, *CWHeight*, *CWBorderWidth*, *CWSibling* and *CWStackMode*. The *XWindowChanges* struct has the following fields: ```int x, y```, ```int width, height```, ```int border_width```, ```Window sibling``` and ```int stack_mode```. -###Sample Code (C++) -``` +### Sample Code (C++) + +```cpp #include #include #include @@ -346,11 +361,15 @@ int main(int argc, char** argv) { } ``` -#Part 3, OpenGL +# Part 3, OpenGL + OpenGL on X11 is done trough the X11 library, include the `````` header. -##Enabling OpenGL + +## Enabling OpenGL + Before showing the window with *XMapRaised* it's a good idea to check the version of GLX available. Note, the **GLX version is not the same as the OpenGL version**. This can be done with the ```glXQueryVersion(Display* display, GLint* major, GLint* minor)``` function. The first argument is a pointer to the display object, the second two arguments are integer pointer that will have the major and minor version number written to them. If the minimum version of OpenGL is not supported, exit the program. A good minimum verions is 1.2 -``` + +```cpp GLint majorGLX, minorGLX = 0; glXQueryVersion(display, &majorGLX, &minorGLX); if (majorGLX <= 1 && minorGLX < 2) { @@ -362,8 +381,10 @@ else { std::cout << "GLX version: " << majorGLX << "." << minorGLX << '\n'; } ``` + To create an OpenGL window a *XVisualInfo* object is needed. This object describes the minimum requirements to create a window. It is created with the ```glXChooseVisual(Display *display, int screen, int *attribList)``` function. If a window matching these minimum requirements can not be created *NULL* is returned. The first argument is the display object, the second is the screen id. The third argument is an array of flags used for creating the window. The creation code below makes a double buffered window with a 24 bit depth and 8bit stencil buffer. -``` + +```cpp GLint glxAttribs[] = { GLX_RGBA, GLX_DOUBLEBUFFER, @@ -384,8 +405,10 @@ if (visual == 0) { return 1; } ``` + When creating an OpenGL window the **XCreateSimpleWindow** function can no longer be used to create a window, instead the ```XCreateWindow(Display *display, Window parent, int x, int y, unsigned int width, unsigned int height, unsigned int border_width, int depth, unsigned int class, Visual *visual, unsigned long valuemask, XSetWindowAttributes *attributes);``` function must be used. The first argument is the display, the second argument is the parent of the window being created. Use the ```RootWindow(Display* display, int screen_number``` function to get this window. The x, y, width, height and border width paramaters are self explanatory. For the depth of the window use the depth field of the *XVisualInfo* class, the visual mask should be set to the *InputOutput* constant. Lastly a **XSetWindowAttributes** struct must be created. This struct describes all of the attributes the window will have. The following fields should be set: *border_pixel*, *background_pixel*, *override_redirect*, *colormap* and *event_mask*. The *colormap* field is very important, get it using the ```XCreateColormap(Display *display, Window w, Visual *visual, int alloc);``` function. The display and window are the same as always. The *Visual* object is contained within the *visual* field of the *XVisualInfo* object. Lastly the *alloc* argument should be set to the **AllocNone** constant. -``` + +```cpp XSetWindowAttributes windowAttribs; windowAttribs.border_pixel = BlackPixel(display, screenId); windowAttribs.background_pixel = WhitePixel(display, screenId); @@ -394,9 +417,11 @@ windowAttribs.colormap = XCreateColormap(display, RootWindow(display, screenId), windowAttribs.event_mask = ExposureMask; window = XCreateWindow(display, RootWindow(display, screenId), 0, 0, 320, 200, 0, visual->depth, InputOutput, visual->visual, CWBackPixel | CWColormap | CWBorderPixel | CWEventMask, &windowAttribs); ``` + After the window is created, an OpenGL context can be acquired with the ```glXCreateContext(Display* display, XVisualInfo* visual, GLXContext shareList, Bool direct); ``` function. The display and visual are the objects from before. The share list should be *NULL* and the direct should be set to true. Make the context active with the ```glXMakeCurrent(Display* display, Window window, GLXContext context)``` function. After a context is active the ```glGetString``` function can be used to retrieve information about the active OpenGL instance. -``` + +```cpp GLXContext context = glXCreateContext(display, visual, NULL, GL_TRUE); glXMakeCurrent(display, window, context); @@ -405,8 +430,10 @@ std::cout << "GL Renderer: " << glGetString(GL_RENDERER) << "\n"; std::cout << "GL Version: " << glGetString(GL_VERSION) << "\n"; std::cout << "GL Shading Language: " << glGetString(GL_SHADING_LANGUAGE_VERSION) << "\n"; ``` + After a context is set active let the application enter it's update loop. Present the context of the active back buffer with the ```glXSwapBuffers(Display* display, Window window)``` function. -``` + +```c glClear(GL_COLOR_BUFFER_BIT); glBegin(GL_TRIANGLES); @@ -421,9 +448,12 @@ glEnd(); // Present frame glXSwapBuffers(display, window); ``` + Finally, once the update loop has finishes the context must be destroyed with the ```glXDestroyContext(Display* display, GLXContext context)``` function. The *Visual* needs to be cleaned up with **XFree** and the display and color map association should be destroyed with **XFreeColormap** -###Sample Code (C++) -``` + +### Sample Code (C++) + +```cpp #include #include #include @@ -543,9 +573,11 @@ int main(int argc, char** argv) { } ``` -##Getting a modern context +## Getting a modern context + Finding information on how to get a modern OpenGL context on X11 was rather difficult. Much of this part is adapted from https://www.opengl.org/wiki/Tutorial:_OpenGL_3.0_Context_Creation_(GLX). First, lets define a type for the ```glXCreateContextAttribsARB``` function and create a function that checks for supported extensions, this function will take two arguments, the extension list and target extension that is being queried. -``` + +```cpp typedef GLXContext (*glXCreateContextAttribsARBProc)(Display*, GLXFBConfig, GLXContext, Bool, const int*); static bool isExtensionSupported(const char *extList, const char *extension) { @@ -581,8 +613,10 @@ static bool isExtensionSupported(const char *extList, const char *extension) { return false; } ``` + The *glxAttribs* variable is going to be different for a modern context. -``` + +```cpp GLint glxAttribs[] = { GLX_X_RENDERABLE , True, GLX_DRAWABLE_TYPE , GLX_WINDOW_BIT, @@ -598,8 +632,10 @@ GLint glxAttribs[] = { None ; ``` + Once the desired attributes are defined, a compatible frame buffer needs to be created. This is done with the ```glXChooseFBConfig(Display* display, int screen, GLint* attribs, int* outCount``` funciton. This function will return an array of framebuffer configuration objects, as good as or better than what was specified in the *attribs* field. After the array is acquired it's ok to use the first one in the array. -``` + +```cpp int fbcount; GLXFBConfig* fbc = glXChooseFBConfig(display, DefaultScreen(display), glxAttribs, &fbcount); if (fbc == 0) { @@ -609,8 +645,10 @@ if (fbc == 0) { } std::cout << "Found " << fbcount << " matching framebuffers.\n"; ``` + **glXChooseVisual** can no longer be used to acquire a *XVisualInfo* object, instead ```glXGetVisualFromFBConfig(Display* display, GLXFBConfig fbConfig)``` must be used to take the best framebuffer into account. -``` + +```cpp XVisualInfo* visual = glXGetVisualFromFBConfig( display, bestFbc ); if (visual == 0) { std::cout << "Could not create correct visual window.\n"; @@ -618,13 +656,17 @@ if (visual == 0) { return 1; } ``` + A pointer to the **glXCreateContextAttribsARB** function needs to be acquired trough the ```glXGetProcAddressARB(const GLubyte * strFuncName)``` function. -``` + +```cpp glXCreateContextAttribsARBProc glXCreateContextAttribsARB = 0; glXCreateContextAttribsARB = (glXCreateContextAttribsARBProc) glXGetProcAddressARB( (const GLubyte *) "glXCreateContextAttribsARB" ); ``` + To actually create the new context use the ```glXCreateNewContext``` function, this function is only available if the **GLX_ARB_create_context** extension is present. If the *glXCreateNewContext* function is not avilable fall back on the ```glXCreateContextAttribsARB``` function, if that is not available either the legacy ```glXCreateContext``` is always an option. -``` + +```cpp int context_attribs[] = { GLX_CONTEXT_MAJOR_VERSION_ARB, 3, GLX_CONTEXT_MINOR_VERSION_ARB, 2, @@ -642,8 +684,10 @@ else { } XSync( display, False ); ``` + It wouldn't make much sense to run a non direct OpenGL context for games, glx provides the ```glXIsDirect(Display* display, GLXContext context)``` function to check for this. -``` + +```cpp // Verifying that context is a direct context if (!glXIsDirect (display, context)) { std::cout << "Indirect GLX rendering context obtained\n"; @@ -652,8 +696,10 @@ else { std::cout << "Direct GLX rendering context obtained\n"; } ``` -###Sample Code (C++) -``` + +### Sample Code (C++) + +```cpp #include #include @@ -887,14 +933,19 @@ int main(int argc, char** argv) { return 0; } ``` -#Part 4, Putting it all together + +# Part 4, Putting it all together + Before putting all the pieces together, there is one more problem with the current implementation of the window. In order to close the window properly the **DestroyNotify** message needs to be handled. If *DestroyNotify* is recieved, the main loop should stop. To make the close button work, the **DeleteWindow** message must be treated as a client message; to remap it place the following code after the window was created, but before it is raised: -``` + +```cpp Atom atomWmDeleteWindow = XInternAtom(display, "WM_DELETE_WINDOW", False); XSetWMProtocols(display, window, &atomWmDeleteWindow, 1); ``` + After the *DeleteWindow* message has been remapped as a client message subscribe to **StructureNotifyMask**. Finally, make sure to handle the new cases in the message loop. -``` + +```cpp XNextEvent(display, &ev); if (ev.type == ClientMessage) { if (ev.xclient.data.l[0] == atomWmDeleteWindow) { @@ -905,8 +956,10 @@ else if (ev.type == DestroyNotify) { break; } ``` -###Sample Code (C++) -``` + +### Sample Code (C++) + +```cpp #include #include @@ -1188,7 +1241,8 @@ void Shutdown() { #endif ``` -#Sources +# Sources + * http://xopendisplay.hilltopia.ca/2009/Jan/Xlib-tutorial-part-1----Beginnings.html * http://tronche.com/gui/x/xlib/display/opening.html * http://techpubs.sgi.com/library/dynaweb_docs/0640/SGI_Developer/books/OpenGL_Porting/sgi_html/ch04.html @@ -1205,4 +1259,4 @@ void Shutdown() { * http://msdn.microsoft.com/en-us/library/windows/desktop/dd318252(v=vs.85).aspx * https://gist.github.com/kovrov/1304027 * https://www.youtube.com/watch?v=qWiqRIoKChg -* https://www.opengl.org/wiki/Tutorial:_OpenGL_3.0_Context_Creation_(GLX) \ No newline at end of file +* https://www.opengl.org/wiki/Tutorial:_OpenGL_3.0_Context_Creation_(GLX)