Sign in to follow this  
Dekowta

OpenGL Slow Sprite Batch Rendering

Recommended Posts

Hi,

I'm fairly new to OpenGL but have been working on it for a little while now.

I'm mainly working on a 2D renderer which uses the more up to date style of OpenGL rather than Immediate mode.

I've managed to create a sprite batching system however I'm not really getting the results that I would expect. I'm roughly getting around 30fps when rendering 5,000 of the same sprite object and around 15fps when rendering 10,000

At the moment the way it works is as followed

- Sprite batch is created which creates the shader, indicies for MultiDrawArrays, 2 VBO and a VAO

- begin is called and sets the alpha mode

- draw is called and checks to see if the texture has been batched already if so it adds the new points to the batch if not it creates a new batch item and adds the points to it. if the max sprites is reached it will draw the batch

- end all the current batch sprites are rendered

header
[url="http://pastebin.com/p20Ehy9s"]http://pastebin.com/p20Ehy9s[/url]

Cpp file
[url="http://pastebin.com/F8Gz6UQM"]http://pastebin.com/F8Gz6UQM[/url]


I have tried a few things to improve performance but with not success. I expect its the method I'm using and that way that im using openGL thats causing the issue.

Thanks Dekowta

Share this post


Link to post
Share on other sites
I think your problem may be glMultiDrawArrays. My understanding is that this doesnt really map to the gpu. You will still get one draw call per entry you send to glMultiDrawArrays. The only benefit is reduced driver overhead. You should be able to send all the sprites as a list of quads just using GL QUADS and glDrawArrays. I think this will be much faster.

Share this post


Link to post
Share on other sites
Your main problem with this code is the way you're updating your VBO. Calling glBufferSubData in this manner is going to lead to pipeline stalls and flushes, and doing it potentially so many times per frame will make things worse. You need to implement proper buffer streaming to get this working well; have a read of [url="http://www.opengl.org/discussion_boards/showthread.php/170118-VBOs-strangely-slow?p=1197780#post1197780"]this post[/url] for a description of the technique.

Sample code:[code]GLuint bufferid;
int buffersize = 0x400000;
int bufferoffset = 0;

void StreamBuffer (void *data, int batchsize)
{
glBindBuffer (GL_ARRAY_BUFFER, bufferid);

if (bufferoffset + batchsize >= buffersize)
{
glBufferData (GL_ARRAY_BUFFER, buffersize, NULL, GL_STREAM_DRAW);
bufferoffset = 0;
}

GLbitfield access = (GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT | GL_MAP_INVALIDATE_RANGE_BIT);
void *mappeddata = glMapBufferRange (GL_ARRAY_BUFFER, bufferoffset, bufferoffset + batchsize, access);

if (mappeddata)
{
memcpy (mappeddata, data, batchsize);
glUnmapBuffer (GL_ARRAY_BUFFER);
glDrawArrays ( .... );
bufferoffset += batchsize;
}
}[/code]
Ideally you wouldn't memcpy here; you'd generate the batch data directly into the pointer returned from glMapBufferRange instead. That's not always possible though, and memcpy is fine for many (if not most) use cases - the key is in avoiding pipeline stalls and the CPU-side overhead from memcpy is going to be very low by comparison. Edited by mhagain

Share this post


Link to post
Share on other sites
Thanks for the reply

I'm having a bit of a hard time understanding how to get buffer streaming set up. So far this is what I understand on what I have to do.

in the section where I loop through the iterations in the map I will

- Bind the texture
- Bind the vertex position buffer
- check to see if the bufferoffset + Batch Count is greater than the buffer size
->if so set the buffer to the size of the batch
->reset the bufferoffset to 0
- get the mapbuffer range pointer
- copy over the batch vertex data (if the vertex data is a pointer can I just make mappeddata point to it or do I need to copy data into mappeddata's address using them memcpy)
- unmap the buffer

- Bind the UV buffer
- do the same as the vertex buffer but with the UV coordinates
- unmap the buffer

- then call draw array with GL_QUADS
- increase the bufferoffset by the batch count

- unbind the texture

This is still fine to use with the vertex array as well?

Also post that you linked mentioned buffer-orphaning from what it explained this happens in the if statement check?

Share this post


Link to post
Share on other sites
You've pretty much got it, yes. It's really just a simple circular buffer; there's no voodoo in it and the only tricky thing is knowing the correct GL calls to use.

You're going to have some added complexity if you're using separate VBOs for positions and texcoords - I'd recommend that you define a vertex struct containing both and interleave them using a single VBO; it'll perform better and make your code much simpler.

Yes, buffer orphaning is what happens in the first "if" check; there are also flags on glMapBufferRange that you can use to accomplish this, but I prefer to use glBufferData (... NULL, ...) - not for any technical reason, just so that I can more easily add a fallback to glBufferSubData if the MapBufferRange call fails (using the same offset and size params). I omitted that from the sample code I posted just for clarity.

Not sure what you mean by vertex arrays here. Old-style vertex arrays or newer VAOs? If the former, there's no need to do this kind of process - you're using memory owned and managed by your program (rather than by the GPU) so there's no resource contention to speak of, and you don't need anything special to handle it.

Share this post


Link to post
Share on other sites
Humm I don't know what im doing wrong at the moment but its not drawing correctly and if I leave it for a bit glMapBufferRange returns 0 with 1281 a Invalid value.

The Modified Files are as followed though I have only really changed the initalise and render function as well as packing the vertex data into a single struct
.cpp
http://pastebin.com/d778Dt6y
.h
http://pastebin.com/WUHt28Jc

what its rendering
http://oi50.tinypic.com/2hi59jd.jpg

what it should look like (excluding the orange and green character)
http://oi48.tinypic.com/2q86g0o.jpg

O and I meant to say Vertex Array Object rather than just Vertex Array.

Share this post


Link to post
Share on other sites
You're getting your offsets/etc wrong here - this in particular is not what you want:[code]void* mappedData = glMapBufferRange(GL_ARRAY_BUFFER, m_BufferOffset, m_BufferOffset + currentBatch->spriteCount, access);[/code]

I should probably have stated explicitly that the sample code I gave above works in byte sizes, not numbers of vertexes or numbers of sprites, so as a result things need to be adjusted accordingly if you're going to use other units.

So, the second parameter to glMapBufferRange is an offset in bytes, so make it m_BufferOffset * sizeof (MLBatchItem::Vertex) * 4 instead. The third parameter is the size of the range to map (also in bytes), not the end offset of the full range, so it becomes currentBatch->spriteCount * sizeof (MLBatchItem::Vertex) * 4.

You should also make sure that your value of m_BufferSize is equal to (however many sprites fit in your buffer) * sizeof (MLBatchItem::Vertex) * 4; likewise memcpy needs a size in bytes, glDrawArrays takes a number of vertexes, not a number of sprites as it's third param, and your offset needs to be incremented by the number of vertexes, not the number of sprites. There may be a few other places I've missed.

Share this post


Link to post
Share on other sites
Humm I seem to have it working but there are still a few issues.

I have two sprites A - 128x128 at 100, 100 and B - 230x230 at 400, 100

when I render just A and B, B will render in the coordinates of A but when I render A B B it does the same again but the second B is rendered like normal.

I checked the data in the VBO and it seems to be fine and holds the data that I would expect.

The modified files
.cpp
http://pastebin.com/d778Dt6y
.h
http://pastebin.com/EMFWGEAi

Rendering A and B
http://oi46.tinypic.com/23hau5v.jpg

Rendering A B B
http://oi45.tinypic.com/25s3lt1.jpg


Sorry to keep having issues with it you've helped so much so far.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this  

  • Announcements

  • Forum Statistics

    • Total Topics
      628334
    • Total Posts
      2982147
  • Similar Content

    • By DejayHextrix
      Hi, New here. 
      I need some help. My fiance and I like to play this mobile game online that goes by real time. Her and I are always working but when we have free time we like to play this game. We don't always got time throughout the day to Queue Buildings, troops, Upgrades....etc.... 
      I was told to look into DLL Injection and OpenGL/DirectX Hooking. Is this true? Is this what I need to learn? 
      How do I read the Android files, or modify the files, or get the in-game tags/variables for the game I want? 
      Any assistance on this would be most appreciated. I been everywhere and seems no one knows or is to lazy to help me out. It would be nice to have assistance for once. I don't know what I need to learn. 
      So links of topics I need to learn within the comment section would be SOOOOO.....Helpful. Anything to just get me started. 
      Thanks, 
      Dejay Hextrix 
    • By mellinoe
      Hi all,
      First time poster here, although I've been reading posts here for quite a while. This place has been invaluable for learning graphics programming -- thanks for a great resource!
      Right now, I'm working on a graphics abstraction layer for .NET which supports D3D11, Vulkan, and OpenGL at the moment. I have implemented most of my planned features already, and things are working well. Some remaining features that I am planning are Compute Shaders, and some flavor of read-write shader resources. At the moment, my shaders can just get simple read-only access to a uniform (or constant) buffer, a texture, or a sampler. Unfortunately, I'm having a tough time grasping the distinctions between all of the different kinds of read-write resources that are available. In D3D alone, there seem to be 5 or 6 different kinds of resources with similar but different characteristics. On top of that, I get the impression that some of them are more or less "obsoleted" by the newer kinds, and don't have much of a place in modern code. There seem to be a few pivots:
      The data source/destination (buffer or texture) Read-write or read-only Structured or unstructured (?) Ordered vs unordered (?) These are just my observations based on a lot of MSDN and OpenGL doc reading. For my library, I'm not interested in exposing every possibility to the user -- just trying to find a good "middle-ground" that can be represented cleanly across API's which is good enough for common scenarios.
      Can anyone give a sort of "overview" of the different options, and perhaps compare/contrast the concepts between Direct3D, OpenGL, and Vulkan? I'd also be very interested in hearing how other folks have abstracted these concepts in their libraries.
    • By aejt
      I recently started getting into graphics programming (2nd try, first try was many years ago) and I'm working on a 3d rendering engine which I hope to be able to make a 3D game with sooner or later. I have plenty of C++ experience, but not a lot when it comes to graphics, and while it's definitely going much better this time, I'm having trouble figuring out how assets are usually handled by engines.
      I'm not having trouble with handling the GPU resources, but more so with how the resources should be defined and used in the system (materials, models, etc).
      This is my plan now, I've implemented most of it except for the XML parts and factories and those are the ones I'm not sure of at all:
      I have these classes:
      For GPU resources:
      Geometry: holds and manages everything needed to render a geometry: VAO, VBO, EBO. Texture: holds and manages a texture which is loaded into the GPU. Shader: holds and manages a shader which is loaded into the GPU. For assets relying on GPU resources:
      Material: holds a shader resource, multiple texture resources, as well as uniform settings. Mesh: holds a geometry and a material. Model: holds multiple meshes, possibly in a tree structure to more easily support skinning later on? For handling GPU resources:
      ResourceCache<T>: T can be any resource loaded into the GPU. It owns these resources and only hands out handles to them on request (currently string identifiers are used when requesting handles, but all resources are stored in a vector and each handle only contains resource's index in that vector) Resource<T>: The handles given out from ResourceCache. The handles are reference counted and to get the underlying resource you simply deference like with pointers (*handle).  
      And my plan is to define everything into these XML documents to abstract away files:
      Resources.xml for ref-counted GPU resources (geometry, shaders, textures) Resources are assigned names/ids and resource files, and possibly some attributes (what vertex attributes does this geometry have? what vertex attributes does this shader expect? what uniforms does this shader use? and so on) Are reference counted using ResourceCache<T> Assets.xml for assets using the GPU resources (materials, meshes, models) Assets are not reference counted, but they hold handles to ref-counted resources. References the resources defined in Resources.xml by names/ids. The XMLs are loaded into some structure in memory which is then used for loading the resources/assets using factory classes:
      Factory classes for resources:
      For example, a texture factory could contain the texture definitions from the XML containing data about textures in the game, as well as a cache containing all loaded textures. This means it has mappings from each name/id to a file and when asked to load a texture with a name/id, it can look up its path and use a "BinaryLoader" to either load the file and create the resource directly, or asynchronously load the file's data into a queue which then can be read from later to create the resources synchronously in the GL context. These factories only return handles.
      Factory classes for assets:
      Much like for resources, these classes contain the definitions for the assets they can load. For example, with the definition the MaterialFactory will know which shader, textures and possibly uniform a certain material has, and with the help of TextureFactory and ShaderFactory, it can retrieve handles to the resources it needs (Shader + Textures), setup itself from XML data (uniform values), and return a created instance of requested material. These factories return actual instances, not handles (but the instances contain handles).
       
       
      Is this a good or commonly used approach? Is this going to bite me in the ass later on? Are there other more preferable approaches? Is this outside of the scope of a 3d renderer and should be on the engine side? I'd love to receive and kind of advice or suggestions!
      Thanks!
    • By nedondev
      I 'm learning how to create game by using opengl with c/c++ coding, so here is my fist game. In video description also have game contain in Dropbox. May be I will make it better in future.
      Thanks.
    • By Abecederia
      So I've recently started learning some GLSL and now I'm toying with a POM shader. I'm trying to optimize it and notice that it starts having issues at high texture sizes, especially with self-shadowing.
      Now I know POM is expensive either way, but would pulling the heightmap out of the normalmap alpha channel and in it's own 8bit texture make doing all those dozens of texture fetches more cheap? Or is everything in the cache aligned to 32bit anyway? I haven't implemented texture compression yet, I think that would help? But regardless, should there be a performance boost from decoupling the heightmap? I could also keep it in a lower resolution than the normalmap if that would improve performance.
      Any help is much appreciated, please keep in mind I'm somewhat of a newbie. Thanks!
  • Popular Now