Jump to content
  • entries
    941
  • comments
    5,894
  • views
    867,830

Out of OpenGL3Context


Josh

4,278 views

 Share

I spent a lot of time last weekend making sure resources are correctly shared between rendering contexts. It's surprising how many commercial games make you restart the game to switch graphics resolutions, and I find it annoying. Leadwerks Engine 3 uses a small hidden window with an OpenGL context to create the OpenGL 3.3 contexts, and it just stays open so there is always a persistent context with which resources are shared. Textures, shaders, and vertex buffers can all be shared between OpenGL contexts, but oddly, frame buffer objects cannot. This is probably because FBOs are small objects that don't consume large amounts of memory, but it still seems like a strange design choice. I got around this problem by using the persistent background context whenever any FBO commands are called, so buffers will continue to work after you delete a context. So I guess the way to describe that is I start with something that is sort of awkward to work with, and encapsulate it in something that makes more sense, to me at least.

 

Because the background context is created in the OpenGL3GraphicsDriver constructor, you can start calling 3D commands as soon as you create the driver object, without creating a visible 3D window! Weird and cool. No idea yet if it will work like this on MacOS, but I'll find out soon enough, since I ordered my iMac last week. I got the 27-inch model with the 3.2 ghz dual core CPU, and upgraded the GPU to an ATI 5750. I chose the 3.2 ghz dual core over the 2.8 ghz quad core because I have found in general usage, my quad core rarely goes over 50% usage, and I would rather have a faster clock speed per core.

 

I said earlier that the window/context design was a little tricky to figure out, especially when you take into consideration the external windows people will want to use. In Leadwerks Engine 2, this was accomplished via a custom buffer, where callbacks were used to retrieve the context dimensions, and the user was responsible for setting up an OpenGL window. Well, initializing pixel format and OpenGL version on a window is a somewhat tricky thing, and if it's possible I would like to avoid making you deal with that. I ended up with a window design that is quite a lot more advanced than the simple Graphics() command in LE2. The window is created from a GUIDriver, which implies other parts of a cross-platform GUI might one day be included. The design is modeled similarly to MaxGUI for BlitzMax. To create a window, we do this:

Gadget* window = CreateWindow("My window",0,0,1024,768,NULL,WINDOW_FULLSCREEN)

Then you can create a graphics context on the window (or any other gadget):

Context* context = GraphicsDriver->CreateContext(window)

We can check for events in our game loop like this:

while (PeekEvent())
{
Event ev = WaitEvent();
switch (ev.id)
{
case EVENT_WINDOWCLOSE:
	return 0;
}
}

At this time, windows are the only supported gadget, but the framework is there for adding additional gadgets in the future. This system can also be used to implement the skinned game GUI as well as native interface elements. Before Rick says anything, yes, there will be a custom event handler function you can attach to a gadget instead of polling events. :)

 

You can render to an external window just by supplying the HWND (on Windows) to the GUIDriver->CreateGadget(HWND hwnd) command. This will create a "Gadget" object from any valid hwnd, and it can then have a context created on it, like the above example.

 

Simple deferred lighting is working, just using a directional light with no shadows. On both AMD and NVidia cards, the engine can render 16x MSAA deferred lighting. The gbuffer format in Leadwerks Engine 3 is only 12 bytes per pixel. Per-pixel motion blur will add a couple more bytes:

 

color0 (RGBA8)

diffuse.r

diffuse.g

diffuse.b

specular intensity

 

color1 (RG11B10)

normal.x

normal.y

normal.z

 

color2 (RGBA8)

emission.r

emission.g

emission.b

materialid

 

Material properties are sent in an array to the GPU, and the material ID is used to look up properties like specular reflection color, gloss value, etc. So this is a very efficient usage of texture bandwidth. My GEForce 9800 GTX can handle 1920x1080 with 8x MSAA, but 16x seems to go over a threshold and the card crawls. I don't know if you'll be using 16x MSAA in a lot of games, but if nothing else it makes for fantastic screen shots, and the lower resolution antialias options are still there. I personally don't see a big improvement past 4x multisampling in most games.

 

blogentry-1-063364100 1288036202_thumb.jpg

 

Here's my current test program. It's more low-level than you will have to work with. You won't have to create the gbuffer and draw lighting yourself like I am here, but you might like seeing how things work internally:

#include "le3.h"

using namespace le3;

int main()
{
InitFileFactories();

//Create GUI driver
GUIDriver* guidriver = new WindowsGUIDriver;

//Create a window
Gadget* window = guidriver->CreateWindow("Leadwerks",0,0,1024,768,NULL,WINDOW_TITLEBAR|WINDOW_RESIZABLE);
if (!window)
{
	Print("Failed to create window");
	return 0;
}

//Create graphics driver
GraphicsDriver* graphicsdriver = new OpenGL3GraphicsDriver;
if (!graphicsdriver->IsSupported())
{
	Print("Graphics driver not supported.");
	return 0;
}

//Create a graphics context
Context* context = CreateContext(window,0);
if (!context)
{
	Print("Failed to create context");
	return 0;
}

//Create world
World* world = new World;

//Create a camera
Camera* camera = CreateCamera();
camera->SetClearColor(0.5,0.5,0.5,1);

//Load a model
LoadModel("Models/train_sd40.mdl");

//Create gbuffer
#define SAMPLES 16
Buffer* gbuffer = CreateBuffer(context->GetWidth(),context->GetHeight(),1,1,SAMPLES);
Texture* normals = CreateTexture(context->GetWidth(),context->GetHeight(),TEXTURE_RGB_PACKED,0,1,SAMPLES);
Texture* emission = CreateTexture(context->GetWidth(),context->GetHeight(),TEXTURE_RGBA,0,1,SAMPLES);
gbuffer->SetColor(normals,1);
gbuffer->SetColor(emission,2);
delete normals;
delete emission;

//Set up light shader
Shader* lightshader = LoadShader("shaders/light/directional.shd");	
Mat4 lightmatrix = Mat4(1,0,0,0, 0,1,0,0, 1,0,0,0, 0,0,0,1);
lightmatrix *= camera->mat;
lightshader->SetUniformMat4("lightmatrix",lightmatrix);
lightshader->SetUniformVec4("lightcolor",Vec4(1,0,0,1));
lightshader->SetUniformVec4("ambientlight",Vec4(0,0,0,1));
lightshader->SetUniformVec2("camerarange",camera->range);
lightshader->SetUniformFloat("camerazoom",camera->zoom);

//Delete and recreate the graphics context, just because we can
//Resources are shared, so you can change screen resolution with no problems
delete context;
delete window;	

window = guidriver->CreateWindow("Leadwerks",200,200,1024,768,NULL,WINDOW_TITLEBAR|WINDOW_RESIZABLE);
context = CreateContext(window,0);

float yaw = 0;

while (true)
{
	//Print(graphicsdriver->VidMemUsage());
	if (!window->Minimized())
	{
		yaw +=0.25;

		//Adjust the camera
		camera->SetPosition(0,0,0,false);
		camera->SetRotation(0,yaw,0,false);
		camera->Move(0,2,-10,false);

		//Update the time step
		UpdateTime();

		//Render to buffer
		gbuffer->Enable();
		camera->Render();

		//Switch back to the window background
		context->Enable();

		//Enable shader and bind textures
		lightshader->Enable();
		gbuffer->depthcomponent->Bind(0);
		gbuffer->colorcomponent[0]->Bind(1);
		gbuffer->colorcomponent[1]->Bind(2);
		gbuffer->colorcomponent[2]->Bind(3);

		//Draw image onto window
		graphicsdriver->DrawRect(0,0,context->GetWidth(),context->GetHeight());

		//Turn the shader off
		lightshader->Disable();

		//Swap the back buffer
		context->Swap(false);
	}

	//Handle events
	while (PeekEvent())
	{
		Event ev = WaitEvent();
		switch (ev.id)
		{
		case EVENT_WINDOWRESTORE:
			ResumeTime();
			break;

		case EVENT_WINDOWMINIMIZE:
			PauseTime();
			break;

		case EVENT_WINDOWSIZE:

			//Recreate the gbuffer
			delete gbuffer;
			gbuffer = CreateBuffer(context->GetWidth(),context->GetHeight(),1,1,SAMPLES);
			normals = CreateTexture(context->GetWidth(),context->GetHeight(),TEXTURE_RGB_PACKED,0,1,SAMPLES);
			emission = CreateTexture(context->GetWidth(),context->GetHeight(),TEXTURE_RGBA,0,1,SAMPLES);
			gbuffer->SetColor(normals,1);
			gbuffer->SetColor(emission,2);
			delete normals;
			delete emission;				

			break;

		case EVENT_WINDOWCLOSE:

			//Print OpenGL error to make sure nothing went wrong
			Print(String(glGetError()));

			//Exit program
			return 0;
		}
	}

}
}

There's been some debate about the use of constructors, and although it would be nice to be able to use a constructor for everything, but that does not seem possible. I use a lot of abstract classes, and there is no way to use an abstract class constructor to create an object. If there was a way to turn the object into a derived class in its own constructor, that would work, but it's not supported. You certainly wouldn't want to have to call new OpenGL3Buffer, new DirectX11Buffer, new OpenGL4Buffer depending on the graphics driver. The point of abstract classes is so you can just call their commands without knowing or caring what their derived class is. So if anyone has any other ideas, I'm all ears, but there doesn't seem to be any other way around this.

 

What's next? I need to get some text up onscreen, and the FreeType library looks pretty good. I'll be getting the Mac version set up soon. And I am eager to get the engine working together with BlitzMax, so I can work on the editor. The graphics features of LE3 are great, but I think there are two even more important aspects. The first is the art pipeline. I've designed a system that is the absolute easiest way to get assets into the engine. More details will come later, but let's just say it's heavy on the drag and drop. The other important aspect of LE3 is the interactions system. I have concluded that programming, while necessary, is a lousy way of controlling interactions between complex objects. The Lua implementation in LE2 was good because it provided a way for people to easily share programmed objects, but its the interactions of objects that make a system interesting, and a lot of object-oriented spaghetti is not a way to handle this. Using the interactions system in LE3 with inputs and outputs for each object is something that I think people will really like working with.

 Share

18 Comments


Recommended Comments

Does WaitEvent() block whilst it's waiting for an event or does it just return null if nothing is waiting?

 

If it blocks (either indefinitely or up to a maximum interval) then you'll really feel the advantage of multithreading. And in that case, you would have been better going for a quad core with lower clock speeds. Parallel computation always wins over raw speed.

 

But it's a bit late to say that now anyway, you've already ordered the thing.

Link to comment

Before Rick says anything, yes, there will be a custom event handler function you can attach to a gadget instead of polling events.

Haha, I was thinking the exact same thing there.

 

The addition of gadgets.

Are community made plugins going to be supported?

Link to comment

Looking great. In the end, I hope one of your greater goals is to have LE3 be easier to use, with less lines to code a project, than LE2. That would definitely be a good selling point. Whether it's possible or not is another matter, I know.

Link to comment

Before Rick says anything, yes, there will be a custom event handler function you can attach to a gadget instead of polling events.

 

Oh thank you! As soon as I looked at that code I was already typing in my head how much polling for events suck :D

 

To comment on Mumbles comment, I would assume WaitEvent() is a blocking method, but it looks like it would just return an event in the event queue so it would return instantly because PeakEvent() is being used to tell if there is anything in the queue to start with. If you were creating just a GUI app that wouldn't need a main game loop, then we would probably just use WaitEvent() since blocking would be fine for such an app, but because this is a game app we have to peak for events before we pop them off the queue.

 

I seem to remember doing that stuff in my Win 32 days (which I hated).

 

 

On a side note, why would anyone program events like that. I mean even if you didn't allow callbacks, I would create my own callback system with what you have there and take away the nasty switch statement.

Link to comment
Simple deferred lighting is working

- Will LE3 allow to specify groups of objects affected by a light ? or groups of objects not affected by a light ? My idea behind this question is that it can be usefull if for example you want to use a light to project a bitmap on a group of ground objects under the player (instead of using a decal), or if you remove the roof of a house and don't want the external light to shine in (asked by someone in the forums but currently difficult with LE2) http://leadwerks.com/werkspace/index.php?/topic/2950-able-to-make-light-not-penetrate-thru/ Or having invisible objects that still block light ?

Or maybe there is another better way ?

 

- i already imagine what i can do with custom events :( cool

 

- "Using the interactions system in LE3 with inputs and outputs" :D i think i need a drawing :D

Link to comment
Guest Red Ocktober

Posted

Using the interactions system in LE3 with inputs and outputs for each object is something that I think people will really like working with

 

just my opinion here... as i can see that familiar red light blinking in the back of my head (please, save the LSD, PTS and ADS comments until after the meeting :D )

 

 

for some reason, i'm thinking that this could become an overwhelming issue, and take lots of time from other core aspects of the design...

 

i feel that the object oriented nature of whatever "engine" each game developer decides to implement should be left to the individual designer to implement... the base class was mostly abstract in nature allowing a freedom of direction farther down the line... the descendants of the base classes only inherited what they needed to exist and the barest of interactions...

 

in LW2.26 i've implemented a GameObjects framework (sort of) which is purely OO and allows each object instanced from a derived TGameObject to appropriately interact with an instance of another class, only when and where an affinity to that class exists...

 

now while it's admirable to try and implement all this as part of the "engine"... and, i can see where using this in an editor sort of environment would be beneficial and all... (it looks like you're this is where you're heading with version 3)... i felt this is something that you might take into consideration here...

 

also... the implementation of a networking structure into the base object classes is another consideration that you might want to look at at this point as well...

 

again, just my opinion, but the concept of server based interaction vs client based ghosting (or whatever paradigm you decide on using) is something that should be implemented early on in the structure... just an underlying framework which would allow a lil flexibility in its final implementation ...

 

ok... i'm done... the bloodletting may commence now :(

 

 

 

--Mike

Link to comment

My experience from the feedback I have received from a lot of potential customers is that programming has mostly failed as a game design medium. For every programmer out there, there are ten non-programmers who just want to drag some pre-made objects into a scene and watch it go. Furthermore, there is a big difference between being able to type a simple program out and being able to structure a game. Even if you can code the structure of a game so that objects interact with one another in meaningful ways, you have by then specialized your code so much you are alone, and can't share anything with other programmers.

 

Having said that, I don't think anyone has designed a system yet that gets around this problem without turning into Grandma's Game Maker 3D. That is, when you make things easy, you usually get underpowered cookie-cutter results.

 

The system I have in mind still allows low-level programming and per-objects scripts, but provides a visual mechanism to connect objects in meaningful ways. It's good for non-programmers because it allows them to make something. It's good for programmers because it allows them to share scripted objects and provide their services to non-programmers for small tasks. For example, you can make a scripted door object, sell it on the site here (soon) and a non-programmer can drop it in their scene and connect the open/close functionality to other objects, like a button. There does not have to be any code-based scheme for making these items interact. Instead there will be an officially supported method of structuring this, so everyone's items will work together.

 

This means your skills are more marketable, and it is easier to connect people with programming skills to people who want to pay for useful results when they need special items coded. In other words, we can all work together better as a unit to help one another and produce useful results.

 

The interactions system won't take too much time to implement because it is pretty abstract and simple. The more time consuming part will be creating a library of scripts to attach to entities, but I can hire someone else to do most of that.

 

If you just want to program a pure C++ game there is nothing to prevent that.

Link to comment
Guest Red Ocktober

Posted

It's good for programmers because it allows them to share scripted objects and provide their services to non-programmers for small tasks. For example, you can make a scripted door object, sell it on the site here (soon) and a non-programmer can drop it in their scene and connect the open/close functionality to other objects, like a button. There does not have to be any code-based scheme for making these items interact. Instead there will be an officially supported method of structuring this, so everyone's items will work together.

 

welll... i like the sound of that...

 

If you just want to program a pure C++ game there is nothing to prevent that.

 

and i like the sound of that...

 

 

what about the networking logic... or am i jumping the gun by a few months...

 

 

 

--Mike

Link to comment

Networking is as easy as this:

server->BroadcastEntity( Entity )

 

And then all changes to the entity will be sent out around the network.

Link to comment

So is that to say that everything has to be an entity because some data that we need to send isn't an entity type. Some we don't want to broadcast but to send to specific people. Chat text for example could fall under this. If we have "whispers" we would want just text to be sent to a specific client.

 

Also what is an Entity in this context? Is it just what we know of an LE entity today? Because what about other custom attributes. Would all that get sent also? Let's say I have an MMO game where I need to send the user his mana points value or attack power value which would be stored on the server. How are you thinking stuff like that would work?

Link to comment

There's a chat system built in with that functionality with whisper, team messages, etc. You can also send raw data you interpret yourself. I intend to support file transfers, although that part I have not tried yet. Everything else has been prototyped.

 

A context is like a LE2 graphics window or custom buffer. It's a 3D window so you can draw stuff. Instead of calling BackBuffer() to render to a context, like in LE2, the Context class is an extension of the Buffer class. So you can render directly to the Context or to a Buffer. This cleans up the Graphics/Window/Custom buffer scheme and easily allows rendering to external windows:

 

1. Create a window.

2. Create a context on window (or on any HWND object).

3. Create a buffer.

4. Render to buffer.

5. Draw buffer image on context with post effects.

Link to comment

I have to take issue with some of what Josh says in response to Red O - I think most people don't mind complexity in building their games. I think what people want to avoid is the arbitrary language barrier of programming. Lots of people can think and solve programming puzzles. They just don't want to have to deal with the 'weird' syntax of solving the puzzle.

 

It's not really a barrier of intelligence or willingness to apply effort. It's just that to some people dealing with programming syntax is the equivolent of going shopping with your sister for shoes. It's just not something you want to be involved in. It's not that you can't do it. It's just, if there is a way to avoid it, you will take that path. :lol:

 

I would like to avoid programming syntax. But I actually have a ton of fun solving logic puzzles and programming puzzles with cryengine flowgraphs. Have you spent some time using cryengine flowgraphs? You can really do a lot with them and there is a lot of freedom of how to approach programming puzzles.

 

So in essense we all want the freedom to work on our games. To some people that freedom == to coding everything with words in a programming syntax. To others, that freedom == to having the thoughts in their head transfer into their game unfettered by programming syntax, which to some acts more like ball and chain, than the wings of freedom.

 

Just be careful not to underestimate the level of control everyone wants with their freedom. I guess. Unless I just misread what you said.

Link to comment

"You certainly wouldn't want to have to call new OpenGL3Buffer, new DirectX11Buffer, new OpenGL4Buffer depending on the graphics driver."

False. This is actually modern OOP programming.

Link to comment

It's not really a barrier of intelligence or willingness to apply effort

 

It's completely not wanting to apply effort. Just like I can learn modeling (doesn't mean I'll be good at it, but I can learn it), but I don't want to apply the effort because I know it can take years to learn how to do it efficiently and I'd rather not apply that kind of effort for what I would get. I'd rather put that effort towards programming. Don't kid yourself into thinking the reason you don't know programming isn't because you aren't apply effort though.

 

 

I would like to avoid programming syntax

 

Even programming through flowgraph is a syntax because programmers can make flowgraph implementations in different ways and learning how to use one implementation of a flowgraph is the same as learning a programming languages syntax. It can just be viewed as easier. Which again comes down to the effort topic.

 

 

I would love to see (and was working on at one point) a flowgraph that was basically 100% visual programming from the very start. ie. you start with int main(), and make variables, and define your own functions & classes via the flowgraph. UDK and Crysis really define so much for you already that are kind of specific to the game they were made for. With LE, since there is no game it's based on, the flowgraph would be pretty bare bones leaving the users to really define deeper game functionality. SpawnActor?? What is that in LE? It's nothing. It doesn't make sense in LE because what is an Actor? LE doesn't define what an Actor is or what kind of stuff it can do and I don't think LE should be the one to define what an Actor is anyway. That should be left up to the user. However, this isn't really the game logic code, it's more entity definition code. In UDK and Crysis you don't have deal with the entity definition code very much because it's already defined for you. In LE, you have to define all that stuff.

 

I like the idea of a flowgraph that gives you the exact same abilities as programming in C++ does but just visually, but that is going to work and act differently than most of the flowgraph implementations out there today. At that point programming in flowgraphs gets a little more daunting. Have you ever seen a detailed flow chart of a complex process, that's basically what it would look like since LE isn't sitting on top of an already defined base game? It can be pretty scary to look at.

Link to comment
Guest
Add a comment...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...