• Hey Guest! Ever feel like entering a Game Jam, but the time limit is always too much pressure? We get it... You lead a hectic life and dedicating 3 whole days to make a game just doesn't work for you! So, why not enter the GMC SLOW JAM? Take your time! Kick back and make your game over 4 months! Interested? Then just click here!
  • Hello [name]! Thanks for joining the GMC. Before making any posts in the Tech Support forum, can we suggest you read the forum rules? These are simple guidelines that we ask you to follow so that you can get the best help possible for your issue.

Legacy GM A question about how GMS 1.4 handles graphics

I'm not very well versed in the graphics side of GMS 1.4, so some (or all) of this may be incorrect :)

I vaguely recall someone saying that when drawing primitives each change of sprite used for a texture constitutes a draw call. So when using textured primitives you want to draw as much as possible (presumably still within some limit imposed by the engine itself, or the gpu) before changing to anything else.

My current project is an enemy with a lot of moving parts, that won't quite work as hoped if I use the sprite drawing functions and it would need multiple sprites. So I have done it as textured primitives, which only requires one sprite, and arranged it so that it follows the above thinking.

I suppose the first question is: is that thinking wrong about the draw calls?

One object draws everything for this enemy. Every object where there is multiple of it, is done through a triangle list of all their points, so there is only one draw call (?) for that sprite. It only needs one sprite, as I have the texture coordinates for every point that it's drawing. I can animate it through the points, so there is that benefit too. But that might be a moot gain, depending on whether my understanding about the draw calls is correct.......

The final question, if this thinking is not wrong, is this: Is this actually just doing what GMS handles under the hood? (because all of its graphics are textured primitives anyway)

If I instead had instances drawing this themselves somehow, would GMS just draw it in a structured way:

1) find sprite to be drawn
2) start the primitive
3) loop through all that have the sprite + do vertexes
4) end the primitive

Like that? So every instance is handled under one draw call?

Or is each instance a draw call?

I'd like to understand how it works, if anyone doesn't mind explaining :)

Cheers!
 
Last edited:

Yal

🐧 *penguin noises*
GMC Elder
A primitive is basically a vertex buffer that's created on the fly and only use once; draw_primitive_end is the function that actually draws it (by sending the entire data blob to the GPU) while the _vertex functions just puts data in a buffer. Modern GPUs are optimized to draw large buffers quickly (e.g. the 3D model of an entire level's geometry) so essentially every drawing call takes the same amount of time no matter the amount of vertices in the buffer.

However, there's another important caveat: primitives are loaded to the GPU every time you draw them, and this is slow. (A significant portion of most games' loading time is loading the 3D models to the GPU). This loading is a lot faster for normal sprites (since they just have two triangles to load) which is why it's still reasonable to draw them separately. But if your primitive doesn't dynamically deform every step, it's probably much more efficient to load it to a vertex buffer and then freeze it (which loads it to the GPU once and stores it in VRAM permanently, so you can draw it without having to load it again).
 
@Yal
Thanks :)

Sorry for asking loads of questions, but it's not something have any knowledge in.

1) So when GMS draws a standard sprite, it is a frozen buffer?

2) If you rotate that standard sprite, is the buffer being unfrozen and it's positions reset?

3) Do shader transforms work on frozen buffers? (I assume this would be yes, if your previous answer was no)

4)How many times is a texture called in the process? Is there any advantage to not having each instance, that share the same sprite, draw itself? (several instances getting a texture and then loading fewer primitives, albeit more often, versus one object calling the texture, and having a lot of primitives loaded all at once)

Because there might be a lot of deforming primitives - I'm not sure what constitutes "a lot". A rough guess would put it at around 2000+ points that all move. Of what I counted it totalled 1779, and that wasn't all of them.

Ignoring whether this is too much (it's not chugging on my pc, which is nothing special, and I'm not aiming for mobiles, or lesser specs than my own): Can you give any tips on how to handle this?
 
Last edited:

Yal

🐧 *penguin noises*
GMC Elder
@Yal
Thanks :)

Sorry for asking loads of questions, but it's not something have any knowledge in.

1) So when GMS draws a standard sprite, it is a frozen buffer?

2) If you rotate that standard sprite, is the buffer being unfrozen and it's positions reset?

3) Do shader transforms work on frozen buffers? (I assume this would be yes, if your previous answer was no)

4)How many times is a texture called in the process? Is there any advantage to not having each instance, that share the same sprite, draw itself? (several instances getting a texture and then loading fewer primitives, albeit more often, versus one object calling the texture, and having a lot of primitives loaded all at once)

Because there might be a lot of deforming primitives - I'm not sure what constitutes "a lot". A rough guess would put it at around 2000+ points that all move. Of what I counted it totalled 1779, and that wasn't all of them.

Ignoring whether this is too much (it's not chugging on my pc, which is nothing special, and I'm not aiming for mobiles, or lesser specs than my own): Can you give any tips on how to handle this?
  1. Essentially, yes. Your game generates "texture pages" which are basically constant buffers containing one or more sprites mashed together to optimize space usage.
  2. No, you apply transforms to the vertices (moving them around, rotating and stretching all are standard matrix transforms)
  3. Shader transforms work on both frozen and unfrozen buffers, but a frozen buffer is faster to re-use (since you only need to load it to the GPU once)
  4. "Calling a texture" makes no sense. Loading a texture takes time, but because of texture pages, small games usually only have ONLY A SINGLE texture that's always loaded. (Loading distribution mostly is a concern on mobile devices, since they have less VRAM and are forced to use smaller texture pages, and also having really slow hardware because power usage - and thus battery life - increases quadratically with processor speed, so 2x the CPU speed means 4x battery consumption. You don't need to worry about that). Drawing calls take time, though - essentially the same amount of time if you draw a single sprite and if you draw a vertex buffer containing a full 3D level. Even if you draw the same sprite, the engine needs to set up the basic shader (even when you don't have a shader set, there's a "default" shader), clear VRAM and pass a lot of parameters to it, and then data needs to be passed from the CPU to the GPU. And if you draw stuff from GML instead of the default draw event, there's extra overhead from that.
 
  1. No, you apply transforms to the vertices (moving them around, rotating and stretching all are standard matrix transforms)
  2. Shader transforms work on both frozen and unfrozen buffers, but a frozen buffer is faster to re-use (since you only need to load it to the GPU once)
sorry - editing your quote a bit changed the numbering. But I can do this then:

1) frozen buffers for all of the segments that make up animated parts

2) Use shader transforms to animate the frozen buffers

If my understanding of that is correct, then you have answered my question and I can mark this as solved. But it leaves with me one last question (hopefully):

If you can do all of these things with a frozen buffer, then why would you ever use unfrozen ones? what is it about them that you can change, where there's some benefit to what I assume is a performance loss?
 
Top