Messing with OpenGL in D

H. S. Teoh hsteoh at quickfur.ath.cx
Wed Dec 5 20:06:09 UTC 2018


On Wed, Dec 05, 2018 at 07:12:34PM +0000, Nadir Chowdhury via Digitalmars-d-learn wrote:
> I'm fairly new to Dlang, but have learnt the basics. I wondered how I
> would be able to make an OpenGL-based Engine in D, what libraries
> would I need?  Your help will be much appreciated!
[...]

All you need is some bindings to your OS's OpenGL libraries and GUI
libraries, and you should be able to just call OpenGL functions
directly. You could use dstep to translate the system headers into D
then import them.  The main tricky part is setting up your GL context so
that it plays nicely with your OS's GUI system.

I don't know what OS you're on, but I have a Linux project that does
just this:

- Install required libraries: X11 libraries (+ header files), MESA
  (which contains an implementation of GL and GLX needed to make X11 and
  GL work together).

- First, I translated /usr/include/X11/{X,Xlib,Xutil,keysymdef}.h into
  D (I did it by hand -- not the entire header file but just enough to
  call the most basic functions to create an X11 window).

- Then I translated /usr/include/GL/{gl,glx,glext,glxext}.h into D --
  again, not the entire thing but just those OpenGL calls that I need to
  work with. You could probably just use dstep to translate the whole
  thing and not have to worry about it after that.

- Write code to connect to the X server and create a window:
   - XOpenDisplay
   - glXChooseGBConfig
   - glXGetVisualFromFBConfig
   - XCreateColormap
   - XCreateWindow
   - XMapWindow

- If you need any GL extensions:
   - glXQueryExtensionsString
   - glXCreateContextAttribsARB or glXCreateContext

- Once the window is created and mapped, you need an event loop to
  process X11 / GLX events. This may be quite delicate, depending on
  whether you're planning to have a mainly passive GUI (i.e., respond to
  user actions only) or you want to have animation / program-driven
  changes (like game engines).

   - For passive GUIs, all you need is a loop that calls XNextEvent to
     retrieve the next event, and when you get an Expose event, call
     XGetWindowAttributes to get the window dimensions, then use that to
     setup your GL viewport, projection matrices, render the scene,
     etc..

   - For active GUIs (like games), you'll need to handle X events
     asynchronously. You can either use a different timing thread and
     send yourself custom events when you need to redraw a frame, or if
     you need to make it work in a single thread:

     	/* Very rough sketch on what needs to be done -- if you want to
	 * see actual code, ask. */

	// This function handles all X11 events -- the bare minimum is
	// Expose, so that you know when to draw the first frame. You
	// probably also want KeyPress if you need keyboard input,
	// ButtonPress / ButtonRelease for mouse buttons, MotionNotify
	// for mouse motion, etc..
	void handleEvent() {
		XEvent ev;
		XNextEvent(display, &ev);
		switch (ev.type)
		{
			case Expose:
				... // draw first frame
				break;

			... // handle other events here
		}
	}

	// This is needed for processing initial events triggered by
	// window creation and mapping, in order to clear Xlib's queue,
	// otherwise the main loop will have sync problems or stall for
	// the first couple of frames.
	XFlush(display);
	while (XEventsQueued(display, QueuedAlready) > 0)
		handleEvent();

	// File descriptor of X11 socket
	immutable x11fd = ConnectionNumber(display);

	// Main loop
	for (;;) {
		// Use select() or epoll() on x11fd to check when you
		// need to process X11 events.
		// Use the timeout argument on select() or epoll() to
		// schedule your animation.
		if ( /* select or epoll indicates read-ready on x11fd */)
		{
			// Note: be sure to write it exactly this way in
			// order to prevent your main loop from stalling
			// due to Xlib misbehaviour.
			while (XPending(display))
				handleEvent();
		}
		if ( /* timeout expired / time to draw a frame */ )
		{
			// call GL functions to render frame
		}
	}

- When compiling, make sure to pass `-lX11 -lGL` to your linker so that
  it will link the X11 and GL libraries. Otherwise you'll get a bunch of
  undefined symbol errors.

P.S., Yes, this is the manual way, low-down of doing things. There are
probably libraries out there that abstract these ugly dirty details away
for you.  But I'm putting this out here so that people know how to make
things work when the easy way fails.

P.S.S. I can share the X11/GL headers I translated to D if you're
curious. Be warned, however, that they are highly incomplete, because I
only translated what I actually use, not the entire header files. Using
dstep for translating your local header files is highly recommended
instead.  But looking at my translated headers may give you a good idea
of how to mechanically translate C headers to D. It's pretty
straightforward once you know how.

P.S.S.S. The stuff about managing the event loop can probably be
avoided by using XCB instead, or using a dedicated GUI thread separate
from your application logic just to work nicely with Xlib. But since GLX
is built on Xlib, you cannot avoid using Xlib at least for setting up
the GL context. After that, you might be able to get away with switching
to XCB as long as you let XCB know that the X11 connection is "owned" by
Xlib (google for the dirty details).

P.S.S.S.S. I highly recommend defining an abstract API for your program
logic, so that the above dirty stuff can be kept in its own module and
just make calls to the real program logic via the abstract API. You
really do not want to be debugging code that has application logic mixed
with Xlib handling. For my project, since it's Android based (the X11
driver is just for testing app logic on host PC), I use an API closely
modelled after Android's:

	interface Application {
		void initialize();
		void onChange(int width, int height);
		void onDrawFrame();
		void onTouch(float x, float y);
		void onPause();
		void onResume();
		void onStop();
	}

You can add other methods as needed, of course. And you can use D's
compile-time features to eliminate those nasty virtual calls, if need
be.  But the point is to keep the application logic completely separate
from the dirty low-level details of X11 handling. It will help maintain
your sanity when it comes time for debugging.


T

-- 
Do not reason with the unreasonable; you lose by definition.


More information about the Digitalmars-d-learn mailing list