[CLOSED] An Object-Oriented Display API (Bear With Me)

Started by CodeBunny, March 28, 2012, 22:09:29

Previous topic - Next topic

CodeBunny

Preface: This isn't a solid RFE, but I've been musing about a potential modification to the API and I wanted to know what others thought. Also, since this type of programming is one I know almost nothing about, some of my ideas may be unworkable.

Plus, warning: wall of text.




Okay, here goes.

Intro:
I've been noticing a lot of improvements to the Display class in LWJGL lately (2.8.4 seems to be a sizable jump forward). In that spirit, I've been thinking about what it would take to make the Display class use an OO API. I think that this would make the current API more compact and flexible in a few ways, and could (in theory) allow for things like multiple concurrent windows, getting rid of the AWTGLCanvas class in favor of a more intuitive system, simplifying context sharing, etc.

Also, part of the reason I feel that this could be a good idea is because I've always been uncomfortable with how the vast majority of LWJGL is static. I understand why it is, and I approve of the reasons, but I think that there are some ways that it could be more sensibly compartmentalized (I've included those ideas below). This RFE would not modify how any of the OpenGL calls work, but would provide a more controllable framework to call them from.

Part 1: Display Objects
The first major difference would, of course, be the use of a Display object! Consider the following:

// Potential ways of instantiaion:
Display display1 = new Display();
Display display2 = new Display([String title]);
Display display3 = new Display([DisplayMode mode]);
Display display4 = new Display([DisplayMode mode], [boolean fullscreen]);

// You could then set further settings for the created display, such as:
display1.setTitle("A Game");
display1.setFullscreen(true);

//After settings have been chosen, a display can be created and run like normal.
display1.create();
while(!display1.isCloseRequested())
{
	// render calls per usual
	display1.update();
}
display1.destroy();


This is not anything particularly different from what is in place currently. However, there are a couple of ramifications of it:

  • You can have multiple Displays created at the same time. (Potentially valuable.)
  • A reference to the active display needs to be available to modify the current window.

On its own, this modification does not deliver any improvement. However, it opens the doors for more possibilities:

Part 2: Managing context
By necessity, if we allow for multiple Display objects, we need to give them a simple way to control the OpenGL thread context. After all, you can't have a call to glTranslatef() be directed at two displays at once.

What I'm envisioning would (on the outside API, at least) be simple: allow a Display instance to declare itself as the active display in a Thread. While it is active, all GL-calls from that thread would be directed to it. An example of giving a Display its own thread:
public class Test1RenderThread extends Thread
{
	private Display display;
	
	public Test1RenderThread(Display display)
	{
		this.display = display;
	}

	public void run()
	{
		display.makeCurrent();
		while(!display.isCloseRequested())
		{
			// renderin
			display.update();
		}
	}
}


You could logically extend this by allowing for multiple Displays being updated and rendered by the same thread:
public class Test2RenderThread extends Thread
{
	private Display display1;
	private Display display2;
	
	public Test2RenderThread(Display display1, Display display2)
	{
		this.display1 = display1
		this.display2 = display2;
	}

	public void run()
	{
		boolean d1Active = true;
		boolean d2Active = true;
		while(d1Active || d1Active)
		{
			if(d1Active)
			{
				if(!display1.isCloseRequested())
				{
					// rendering to display1
					display1.update();
				}
				else
					d1Active = false;
			}
			
			if(d2Active)
			{
				if(!display2.isCloseRequested())
				{
					// rendering to display2
					display2.update();
				}
				else
					d2Active = false;
			}
		}
	}
}


And the opposite direction works, too. We could use this API to easily allow context sharing, instead of dealing with SharedDrawable. This would be a nice simplification of the current API:
public class Test3RenderThread extends Thread
{
	private Display display;
	
	public Test3RenderThread(Display display)
	{
		this.display = display;
	}

	public void run()
	{
		display.makeCurrent();
		while(!display.isCloseRequested())
		{
			// rendering
			display.update();
		}
	}
}

public class Test3LoadingThread extends Thread
{
	private Display display;
	
	public Test3LoadingThread(Display display)
	{
		this.display = display;
	}
	
	public void run()
	{
		if(display.isMultiContextAvailable())
			display.makeCurrent();
		
		// handle loading of resources!
	}
}


This last point is one I think would be really nice. A simple, one-line way to share contexts would be incredible. In my own projects, I have a method, but it requires several lines of code to share contexts, and seems to have bugs on Windows.

Part 3: Integration with AWT and Swing
Sometimes, it's true, we want to integrate LWJGL with a Swing application. Whether we want high-performance graphics amidst a bunch of normal GUI data (such as visualization software), or if we want to place some game functionality into a gui control system (like for a level editor), valuable uses exist. Currently, I know of two ways to do this:


  • Use Display.setParent: This method is ideal for most implementations. It automatically uses the supplied Canvas as the Display surface, and adapts for resizing, Swing repainting, etc. Another benefit is that this method can be used without any change to the rest of your code. Unfortunately, it only allows for one OpenGL component. For more than one, this method doesn't work.
  • Use an AWTGLCanvas: The only option that allows you to have multiple OpenGL surfaces at once. I've used this component for work, and while it "works," it's not close to ideal. It behaves oddly and can have its rendering fall apart in certain circumstances. Additionally, using it is confusing, as its API is very different than using the normal Display.

Looking at the above points, the optimal resolution to this problem is fairly clear: figure out how to get Display.setParent to work on more than one component. By allowing each display to be a separate object, you can have each of them be set to separate Canvases, and the problem resolves itself. An example:
Display display1;
Display display2;
Canvas canvas1;
Canvas canvas2;

// Define the canvases and displays as needed.

display1.setParent(canvas1);
display2.setParent(canvas2);

// Now you can render the Displays separately, and watch the separate canvases get updated.


I feel that this change would be quite a good one overall. Right now it's too much of a pain to make a complicated GUI with LWJGL (again, I've tried at work - you can get results, but it's too annoying). Not only would it make the necessary code easier, but it would also simplify the aesthetics of the API - AWTGLCanvas seems to exist only to allow for muti-display, and this naturally condenses all functionality into a simple set of methods that cover all use cases.







I think that's everything. There may be more ways this could affect the API, but those are all I can come up with right now.

Thoughts? Does this seem like a good/bad idea to you? Any improvements you could offer?

matheus23

Agree!
As Java is usually being a OOP-language, I would really like something like that.

Only big problem is, that every LWJGL-User, who has used the old LWJGL version, would need to update a lot of code. For that I suggest a static wrapper, called Display :P

Hope it will be added :)
My github account and currently active project: https://github.com/matheus23/UniverseEngine

CodeBunny

I don't really think it would require that much updating. Display is only one class, and there should only be a small number of places where it is called (rendering, checking for exit, setting sizes, etc). But yeah, if people want that, it'd be simple to do.

And glad that you like. :)

spasi

CodeBunny, thanks for taking the time to submit this RFE. What you describe is more or less the plan for LWJGL 3.0. It will break existing code (hopefully not by much), so it's a chance to redesign Display and other stuff that have been problematic so far. I have posted some goals for 3.0 over at JGO some time ago, you can read them here.

With that said, it's too early to discuss any particular API or technical solutions to these problems. It will be a big undertaking and it will probably take a lot of time to complete, given that nobody in the LWJGL team has the required amounts of free time available. Personally, I'm also waiting to see how JavaFX will work out. I've been evaluating it for the past 3 months in a side-project of mine and I've been extremely satisfied and sometimes downright impressed with how much better it is than Swing. Supporting Swing/AWT has been a pita and having to maintain that post 3.0, while everyone in the Java world moves over to JavaFX, will be a shame. I'd love to only have JavaFX to support, especially given that it has great interoperability with both Swing and SWT (JavaFX content can be embedded in Swing/SWT, not the other way around) and it's also about to be open-sourced.

Anyway, that's just my opinion, it doesn't say anything about how LWJGL will move forward. Just wanted to make clear that all these features are planned and that it's no trivial matter.

kappa

The LWJGL 2.x series isn't going to break API compatibility so something like the above change will have to likely wait for LWJGL 3.0.

Without widening the discussion too much, I think the real issue we need to decide for LWJGL 3.0 with something like the above, is if we want to go OO with the LWJGL API (or with a part of it).

We need to also consider the ramifications of such a change on the rest of the API given that GL, AL, GLES and CL work so well as a static API.

The current static API does provide a nice, clean, simple & consistent way to use LWJGL. However it does make it difficult to handle situations that require multiple instances (e.g. multiple Displays, Keyboards, Mice, running such stuff on the same classloader, etc). Although its possible to do such things by using something similar to how OpenGL keeps track of states.

An OO design can be a cleaner way to handle stuff like multiple instances for stuff like multiple Display's, etc and more familiar to Java programmers. However it has other downsides such as needing to maintain a reference to the Display object in your code (Mouse, Keyboard & Controller classes will likely have to become OO if the Display does).

A hybrid between static and OO will likely require the same sort of state switching that you'd have to do if the API was fully static.

Anyway its something we need to put a lot of careful thought and design into.

Riven

I think we should eventually want to ensure some backwards compatibility. Probably 99% of our developers do just fine with the current 'restrictions' of the API. When we make the transition to OO in LWJGL 3.0, we might want to keep the Display class, backed by an OO instance.

This way we don't break each and every tutorial on the internet.

princec

I too am very keen to avoid breaking things backwards of 3.0 for that very reason - there are quite a lot of bits of code out there that target 2.x now I think.

The best approach would probably be to use a state machine rather like OpenGL does, from the API perspective. That way all the existing code using Display would Just Work as it has always done, and we'd just add a few more methods on Display along the lines of getDisplays(), setCurrentDisplay(), getCurrentDisplay() etc. that used opaque handles to some internal display object. I realise it sounds a bit arse about tit as we say here in the UK but, like with the mouse and keyboard, for 99% of computers, you are simply dealing with one global instance of said object, and never need concern yourself with more; it would be a shame to start having to pass around stupid references to the mouse, keyboard and display all over the place. (Indeed, most people would get fed up with that and make a singleton container for all three... and then probably write static wrappers...)

Cas :)

spasi

I tried to do exactly that, a backwards-compatible static Display backed by an instanced Display object, 1-2 years ago. It was so hard that it felt impossible at the time, so I gave up. I didn't work on it for more than a couple of days though, so it might be possible. I'm of course in favor of maintaining compatibility with existing code if possible, but if it's not, I don't think it should keep us from going ahead with 3.0.

CodeBunny

This being pushed back into 3.0 would be a good idea - the whole idea is a fundamental shift in design, so waiting for a major version makes sense.

Also, I feel that keeping all OpenGL calls static makes total sense - it works perfectly right now, and is good because it's familiar to C/C++ programmers. Code is portable between languages. However, I think that the LWJGL-specific portions of the API can be turned into OO relatively easily.

Also, I did not realize how input controls would be affected. That is probably also a good idea.

@princec: I get what you're saying, but I personally would prefer the object setup instead of a static one. It really doesn't complicate the API for a single Display very much at all (for my tastes, it would actually improve it), and I feel that it makes multi-display and context management much more intuitive. Categorizing different display states by object reference is automatic, but if you want to have static control methods you have to do that much more work. Consider this:
Display display1 = new Display();
Display display2 = new Display();

// Set up display1 with general methods directed at the object, like display1.setTitle("FOO")
display1.create();

// Set up display2 with general methods directed at the object, like display2.setTitle("OOF")
display2.create();


Vs this:
int display1ID = Display.genDisplay();
int display2ID = Display.genDisplay();

Display.selectDisplay(display1ID);
// Set up the display with the methods we have currently, like Display.setTitle("FOO");
Display.create();

Display.selectDisplay(display2ID);
// Set up the display with the methods we have currently, like Display.setTitle("OOF");
Display.create();


The first one seems preferable to me. It's more immediately obvious as to what is going on, and it's more intuitive to perform operations with multiple Displays.

CodeBunny

Also, I'm not really factoring backwards compatibility into my ideas. It just seems to me that if we're establishing a new way for the library to function, we should choose the optimal API that suits this iteration of the library, instead of worrying about keeping old code intact. People upgrading to this new API will probably need to accept that their code will have to change to reflect it.

kappa

Another thing to keep in mind is that static API's can easily be wrapped by users to create any type of OO api, however not so easy to do it the other way round.

Anyway as long as backward compatibility isn't broken LWJGL can continue on as 2.x+ and new API's can continue being added.


kappa

Closing this RFE, the current plan is not to break/change the LWJGL 2.0 API and focus mainly on just bug fixes for it (therefore no multiple Display support for LWJGL 2.0).

Having said that work is already under way on LWJGL 3.0 which has a clean rewrite/refactor of the API and includes support for multiple windows (using OO).