OpenGL Wrapper

Discussions Related to Graphics Technology in General

OpenGL Wrapper

Postby GhostWolf » Tue Aug 23, 2011 8:27 pm

Hey again (I believe the "Wolf" in my name gives away who I am).

Well, I finally started to think about an actual implementation.
I'll leave shaders for later on, since I want to have actual data to test first, so buffers it is.

What I thought so far was something along the lines of having a VertexAttribute class with a name and size.
Each Buffer class instance will hold the buffer handle itself, the array data (to make it easy to edit stuff later on and call an update() method or something like that), an array of VertexAttribute instances if they exist (which they don't for index buffers), and some flags saying what data types this buffer holds (the buffer type, ARRAY_BUFFER or ELEMENT_ARRAY_BUFFER, and the data type which so far is only FLOAT or UNSIGNED_INT, I'll bother later on to make the types smaller if appropriate).

Now, I am not really sure what to do when binding - the buffers can be represented in many different ways (interleaved and non-interleaved data in many orders).
Should the client give this information, which would mean it's basically not saving his effort at all (since he is giving all the information you usually give anyway), or should I make a default way and force the client to use a specific format?

I am also not a big fan of calling manually render/draw methods. Do you have any thoughts of making an internal list of all models that need to be rendered with some sort of register/unregister mechanism?
My main issue when thinking about my data specifically, is that my main level data is a huge 2D grid of uniform-sized tiles (all of them have the same width and the same height). I don't know too much about face culling, but with only basic information, I can instantly drop out every single tile that isn't visible.
How would I pass the information needed (the level size, size of the tiles, the location of the "camera", and the resolution of the view) in a general way?

I am also wondering what's the most optimized way to render the above level data. Currently I hold one buffer, and draw each visible tile in turn with a new drawArrays() call, while binding its texture.
This is obviously not very optimized.
It's obvious to me that I should sort by texture first of all (and make the binding function only bind a texture if it isn't bound already obviously, it seemed to bug last time I tried it though, I am using WebGL), but would that be worth sorting perhaps a few hundred tiles?
And then of course I can optimize it more to sort only if a new line/row of tiles is visible, meaning you moved enough that it's worth to sort again.
I am also wondering if using a flat index buffer would be faster, simply because it sends less data - 4 bytes instead of 20 for each vertex.
And then there are thoughts about updating the index buffer itself together with the sorting, so that I would be able to call drawElements N times as the number of visible textures, rather then the number of visible tiles.

But yeah, I guess getting a nice looking and easy to use API is more important to me right now then to get it to work as fast as possible.
The current code can render a few hundred tiles with not much of a problem even when binding a texture for each and each of them.
GhostWolf
I Have a Question
 
Posts: 1
Joined: Sat Aug 20, 2011 10:40 pm

Re: OpenGL Wrapper

Postby L. Spiro » Thu Aug 25, 2011 12:06 am

Quite bad timing.

I have to go to the hospital in a few minutes and I will be there for 3 days.
I will be able to answer this when I get back.


L. Spiro
It is amazing how often people try to be unique, and yet they are always trying to make others be like them.
- L. Spiro 2011
L. Spiro
Site Admin
 
Posts: 54
Joined: Thu Jul 21, 2011 2:59 pm
Location: Tokyo, Japan

Re: OpenGL Wrapper

Postby L. Spiro » Sat Aug 27, 2011 2:53 am

I am back.

Some of the flags you may also want to consider are set-only and modifiable. These determine what kind, if any, of VBO you use. Dynamic VBO’s are a bit pointless but you could choose to use them anyway.

In my system, vertex buffers, index buffers, and textures are first initialized, then filled with data (which can optionally be done during initialization), then “finalized”, which is what makes the conversion from my data structures to the data structures matching the current API.
A set-only buffer or texture can still be fully modified until FInalize() is called.
A modifiable buffer or texture can be modified after a call to Finalize(), but Lock() must be called to do so. Between Lock() and UnLock(), modifications to the buffers/textures are done on my local copy. When UnLock() is called and the lock count reaches 0, it is the same as calling Finalize(), which will again send the updated data to the graphics API.


As for binding, I have made a system similar to HLSL semantics for making it easy to use between both Direct3D and OpenGL.
You don’t have to do this if you just want to support GLSL, but it can still make things easier, at least to have a pseudo-semantic system.
You can define your own types, such as POSITION, NORMAL, TEXCOORD, etc., which are not strictly bound to be used the way they are specified (just as in HLSL, the actual semantic has mostly no meaning; it only depends on how you use the value in the shader), along with
You can be more generalized in your own implementation, but the point is to give yourself some way to always be able to determine the offsets within your buffer of any given data.

For example, POSITION always comes before NORMAL, always before COLOR, always before TEXCOORD, etc.
So if my buffer contains POSITION, NORMAL, and TEXCOORD, I know the offsets of each: (0x00, 0x0C, and 0x18 respectively). Again, you don’t need to make these types of semantics exactly, but it will help your performance to be able to tell quickly where every attribute is in your buffers.

Assuming you do something similar to this, you also need to handle the fact that there can be multiple POSITION, NORMAL, TEXCOORD, etc., attributes. Each attribute follows a specific order, then index number of each attribute are in order.
So, for example:
Code: Select all
{ POSITION, 0 }
{ NORMAL, 0 }
{ NORMAL, 1 }
{ TEXCOORD, 0 }

Knowing the sizes of each attribute means we can calculate each offset.

The reason semantics make this easier is because they allow an easy way for you to hook the attributes from your vertex buffer up to the shader without requiring that you write a whole new shader language.

An alternative of which I could think for OpenGL only is to allow uses to specify a type (FLOAT, UINT32, etc.), element count, and index triplet. You would then have enough information to link values up to predefined shader attributes, but it is a bit messier.




You should not be calling a bunch of Draw() methods to render objects.
You will need to construct a scenegraph or use an existing one (I prefer to roll my own).
Register your models with the game world, which should contain an octree or a variant of an octree (such as a quadtree with uniform Z steps) and place your objects in that.
Once in your “world”, you can draw every object by just calling one Draw() on the game world object.
It starts by constructing a k-DOP representing the view frustum and search only the octree nodes that in any way intersect your view frustum.
Each each octree node you will have several objects. Check those objects for being partially or fully inside your view frustum (objects will need AABB’s or OOBB’s) and add them to the list of objects that are about to be rendered.
I keep a list of objects to render instead of just telling each object to render as I encounter them because you will probably want to make multiple passes, and that would mean going down the octree each time needlessly.

Now you have a list of objects that need to be drawn.
Each object should submit some data for each of its render parts. That is, for each vertex buffer etc. that needs to be used to render the whole thing, submit some data that tells which shader will be used, which textures, and a pointer back to that object.
These submissions go into a render queue. The render queue then sorts the data to determine the order in which to render the objects, grouping objects by shader, then by texture, etc.
Some details on this here:
http://lspiroengine.com/?p=96

If you sort via the method I mentioned (indices rather than the actual bigger sets of data) then you can sort every frame just fine. Thousands of objects are no trouble.


I am not sure what you mean by “flat” index buffer.
If you mean an index buffer that just goes 0, 1, 2, 3, 4, 5, 6, etc., this is certainly a no-no.
You still have to send the 20-byte vertices, plus an additional 4 bytes per index (try to use 16 bits when possible; this should be a feature of your index buffer wrapper class—it should start at 16-bit indices and autmatically upgrade if you ever add an index above 0xFFFF).
My index buffer has a function that accepts a vertex buffer and creates a cache-friendly lowest-cost vertex buffer and index buffer. It usually reduces the size of the vertex buffer by 60%, which causes a tremendous gain in performance.


L. Spiro
It is amazing how often people try to be unique, and yet they are always trying to make others be like them.
- L. Spiro 2011
L. Spiro
Site Admin
 
Posts: 54
Joined: Thu Jul 21, 2011 2:59 pm
Location: Tokyo, Japan


Return to Other Graphics Technology

Who is online

Users browsing this forum: No registered users and 0 guests

cron