Jump to content

  • Log In with Google      Sign In   
  • Create Account

Do all 2D games do this?


Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

  • You cannot reply to this topic
17 replies to this topic

#1 warnexus   Prime Members   -  Reputation: 1431

Like
0Likes
Like

Posted 03 June 2014 - 08:54 PM

The reason I ask is because I only want to drawn the tiles if their coordinates are "within" the screen. I figure this would improve performance of the game and be efficient code. I don't think I am optimizing...just making sure unnecessary things don't get drawn when the player is not going to see it.

 

I hear draw calls are expensive...so this is why I do this. I figure if I do not do this, the game might slow to a crawl when draw calls are being called even though the objects are not on the screen.

 

The game is a 2D platformer with a camera system. 

 

My Java code example:

 

update block:

if(getX() - camera.getX() < Screen.WIDTH)
{
draw = true;
}
else
{
draw = false;
}

draw block:

if(draw)
{
blockAnim.draw(g2D,camera);
}

Edited by warnexus, 03 June 2014 - 08:56 PM.


Sponsor:

#2 L. Spiro   Crossbones+   -  Reputation: 13575

Like
9Likes
Like

Posted 03 June 2014 - 09:00 PM

All games all humans or aliens in any civilization on any planet past or future have eliminated unnecessary tiles from being drawn, no exceptions.

However they tend to use grids or quadtrees.
Instead of checking each tile, for a regular tile grid, you can’t get any faster than calculating the left, right, top, and bottom rows of tiles that need to be drawn and simply draw them.

Then your tile-drawing loop would look like:
for ( int y = top; y <= bottom; ++y ) {/*left/right - top/bottom fixed. */
	for ( int x = left; x <= right; ++x ) {
		// Just draw the tile; it is on-screen.
	}
}
In any case, you don’t run over the tiles and set a “draw” flag. Just draw it when you discover it should be drawn. Setting a flag and then going back over to draw is a waste of cycles.


L. Spiro

Edited by L. Spiro, 04 June 2014 - 03:24 AM.

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
I spent most of my life learning the courage it takes to go out and get what I want. Now that I have it, I am not sure exactly what it is that I want. - L. Spiro 2013
I went to my local Subway once to find some guy yelling at the staff. When someone finally came to take my order and asked, “May I help you?”, I replied, “Yeah, I’ll have one asshole to go.”
L. Spiro Engine: http://lspiroengine.com
L. Spiro Engine Forums: http://lspiroengine.com/forums

#3 warnexus   Prime Members   -  Reputation: 1431

Like
0Likes
Like

Posted 03 June 2014 - 09:06 PM


Setting a flag and then going back over to draw is a waste of cycles.

 

What do you mean by waste of cycles? Does my game run slightly slower then?



#4 L. Spiro   Crossbones+   -  Reputation: 13575

Like
0Likes
Like

Posted 03 June 2014 - 09:09 PM

Apparently your flow involves making one pass to set a flag and then another pass to check the flag and draw or skip.

Unless you need to know which tiles are going to be drawn for any part of your game other than drawing (and you don’t; there are other ways to handle that kind of situation, such as by pre-calculating the left, right, top, and bottom rows and deriving that information from those metrics), make a single pass at the time of rendering to simply go over the tiles once and draw or not draw them.


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
I spent most of my life learning the courage it takes to go out and get what I want. Now that I have it, I am not sure exactly what it is that I want. - L. Spiro 2013
I went to my local Subway once to find some guy yelling at the staff. When someone finally came to take my order and asked, “May I help you?”, I replied, “Yeah, I’ll have one asshole to go.”
L. Spiro Engine: http://lspiroengine.com
L. Spiro Engine Forums: http://lspiroengine.com/forums

#5 warnexus   Prime Members   -  Reputation: 1431

Like
0Likes
Like

Posted 03 June 2014 - 09:11 PM

I have many questions now.

 

1) Why is y first instead of x? And why are you using the pre-increment?

 

2) How do I know how many tiles my computer is capable of rendering?

 

Suppose my screen is 760 * 1080 in width and height respectively. My block tile is 20 * 20. So that is 38 blocks going in each row and 54 blocks going in each column. So that is 2052 blocks being drawn given my screen dimensions.


Edited by warnexus, 03 June 2014 - 09:17 PM.


#6 SeanMiddleditch   Members   -  Reputation: 5761

Like
5Likes
Like

Posted 03 June 2014 - 09:46 PM

I hear draw calls are expensive...so this is why I do this


They are. Culling unnecessary tiles is one way of solving this. Batching your calls together or instancing solves this another way without culling. They work wonders when combined.

On most hardware in most games, anyway. Yours may be different. Measure and quantify your inefficiencies before optimizing and afterward for comparison purposes.

1) Why is y first instead of x? And why are you using the pre-increment?


The code Spiro posted may just have a typo since he mixed Y with left/right and X with top/bottom, which is against usual convention. Y first is convention based on how images and arrays are laid out in memory. There are various mathematical and performance reasons this is "correct." It's not especially critical.

Pre-increment is a convention mostly used by C++ programmers. In a non-optimizing build, post-increment is a teeny tiny bit slower. For some C++ iterator types (not simple ints), it's measurably slower. Post-increment has to make a copy of the value before incrementing and then return the copy. An optimizing compiler will generally output identical machine code for both pre- and post-increment in cases like this even for complicated iterators, and certainly for any ints.

2) How do I know how many tiles my computer is capable of rendering?


It can render a lot. Do you mean per hour? Per second? Per frame at 60hz? Try drawing a few million. Measure the time it takes. Adjust as necessary until you drill into the number of tiles matching your target draw rate. Remember that this number is specifically for your computer and not any other.

Then remember that that is a best-case maximum and assumes you're doing no AI, physics, multitasking with a music player, etc., which will all slow down the device and reduce its draw rate.

#7 Satharis   Members   -  Reputation: 978

Like
4Likes
Like

Posted 04 June 2014 - 03:05 AM

2) How do I know how many tiles my computer is capable of rendering?

You might as well be asking us how far you can throw a baseball. Even if we knew specific details about you it is very hard to quantify such a thing, there are too many variables involved. Nobody really "knows" how much fps you'll get in different situations, thats why performance is often guaged off of benchmarking on a few different hardware setups.

A better line of thinking is to draw as many as you want, and if you run into performance problems then look at your options(cutting down on rendering, improving performance through profiling, etc.)

#8 L. Spiro   Crossbones+   -  Reputation: 13575

Like
5Likes
Like

Posted 04 June 2014 - 03:27 AM

2) How do I know how many tiles my computer is capable of rendering?

  • “warnexus, what does the computer say about his draw count?”
    It’s over 9,000!!!!
  • This many:
    |------------------|
    I hope that answers that.

    As SeanMiddleditch mentioned, find out for yourself by benchmarking. But it’s a silly thing to know anyway. Reduce your draws as much as possible.

 

 

L. Spiro


Edited by L. Spiro, 04 June 2014 - 03:31 AM.

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
I spent most of my life learning the courage it takes to go out and get what I want. Now that I have it, I am not sure exactly what it is that I want. - L. Spiro 2013
I went to my local Subway once to find some guy yelling at the staff. When someone finally came to take my order and asked, “May I help you?”, I replied, “Yeah, I’ll have one asshole to go.”
L. Spiro Engine: http://lspiroengine.com
L. Spiro Engine Forums: http://lspiroengine.com/forums

#9 Madhed   Crossbones+   -  Reputation: 2901

Like
0Likes
Like

Posted 04 June 2014 - 04:35 AM

Furthermore, if you are concerned about drawcalls I would like to see how this function is implemented.

blockAnim.draw(g2D,camera);

Just from looking at that line I suspect there is much room for improvement.



#10 swiftcoder   Senior Moderators   -  Reputation: 9990

Like
3Likes
Like

Posted 04 June 2014 - 06:02 AM

I hear draw calls are expensive...

What graphics API is being used to draw your tiles?

The "draw calls are expensive" primarily relates to DirectX/OpenGL. If you are using an intermediary API, it's quite possible it is batching up draws already, and thus may have completely different performance characteristics.

Tristam MacDonald - Software Engineer @Amazon - [swiftcoding]


#11 warnexus   Prime Members   -  Reputation: 1431

Like
0Likes
Like

Posted 04 June 2014 - 07:53 AM

Furthermore, if you are concerned about drawcalls I would like to see how this function is implemented.

blockAnim.draw(g2D,camera);

Just from looking at that line I suspect there is much room for improvement.

Sure. You can see how I implemented it. I had to override the draw method.

 
for(int i = 0; i < getListSize() ;i++)
{
 
if(getCurrentFrame() <= getFrameEntries(i))
{
               g.drawImage(getImage(i), getX() - camera.getX()  , getY() - camera.getY() , null);
               break;
               }
}


#12 warnexus   Prime Members   -  Reputation: 1431

Like
0Likes
Like

Posted 04 June 2014 - 07:54 AM

 

I hear draw calls are expensive...

What graphics API is being used to draw your tiles?

The "draw calls are expensive" primarily relates to DirectX/OpenGL. If you are using an intermediary API, it's quite possible it is batching up draws already, and thus may have completely different performance characteristics.

 

I'm using Java Standard: more specifically the awt library that has Canvas and BufferStrategy and Image and BufferedImage. What do you mean by intermediary API? 


Edited by warnexus, 04 June 2014 - 07:56 AM.


#13 swiftcoder   Senior Moderators   -  Reputation: 9990

Like
0Likes
Like

Posted 04 June 2014 - 08:20 AM


I'm using Java Standard: more specifically the awt library that has Canvas and BufferStrategy and Image and BufferedImage. What do you mean by intermediary API? 

Exactly that: the AWT canvas/bufferstrategy API (as opposed to using OpenGL directly, for example).


Tristam MacDonald - Software Engineer @Amazon - [swiftcoding]


#14 warnexus   Prime Members   -  Reputation: 1431

Like
0Likes
Like

Posted 04 June 2014 - 08:42 AM

 


I'm using Java Standard: more specifically the awt library that has Canvas and BufferStrategy and Image and BufferedImage. What do you mean by intermediary API? 

Exactly that: the AWT canvas/bufferstrategy API (as opposed to using OpenGL directly, for example).

 

Oh so I don't need to worry about efficiency then since the API is batching it up like that?



#15 swiftcoder   Senior Moderators   -  Reputation: 9990

Like
0Likes
Like

Posted 04 June 2014 - 12:41 PM

I have no idea - AWT isn't exactly my area of specialty. I'm sure there are some AWT-specific performance guidelines floating around out there...

 

If you were using OpenGL, I could offer performance advice :)


Tristam MacDonald - Software Engineer @Amazon - [swiftcoding]


#16 Ravyne   GDNet+   -  Reputation: 7321

Like
1Likes
Like

Posted 04 June 2014 - 12:48 PM

In the current APIs individual draw calls are expensive because for each draw call the API/Driver are potentially changing and verifying a lot of state. Batching similar draws (say, all the grass tiles, or better yet, all the tiles from the same, large texture atlas, using the same shader, etc) is one way of reducing the number of draw calls you have to make -- thus, the API/Driver overhead is amortized across all those tiles that would otherwise be drawn individually in a naive renderer. Realistically, on a modern desktop or laptop with current APIs you get a couple thousand draw calls before your CPU is completely swamped by the overhead. If you have a game running at just 640x480, using small tiles of 16x16 pixels, drawing just one densely-populated layer of tiles consumes 1200 draw calls if you do them individually. Figure two more sparsely-populated layers for objects and overhead graphics add 50% on top of that. You're 640x480 game has already consumed half of available draw calls per frame -- Now draw lots of characters, throw in some particles and UI -- you're already probably at or around the comfortable limits if your game does any interesting processing, and you haven't drawn a single off-screen tile or entity. Drawing half a screen-width extra in all directions multiplies the cost by 4x and you're way over your draw call budget.

 

On mobile platforms using mostly OpenGL ES, you can expect to make half or fewer draw calls to stay in budget.

 

Its mostly batching that's important if you're using a 3D API -- Once the GPU gets a hold of the draw call it'll chew through clipped pixels like nobody's business, and it'll reject them before running expensive pixel shader code. There's no point sending stuff to the GPU that you easily know is not in view, but you don't have to worry about being tile or pixel-perfect about it. Batching will save you far more.

 

As an aside, new style APIs like Mantle, D3D12, and the console APIs aren't so affected by draw call counts, and have other features to keep re-usable draw commands on the GPU to reduce overhead even further. In statistical analysis, the D3D12 team showed that nearly all games re-use 90% of their draw commands frame-over-frame, so my understanding is that these APIs make it possible to just re-use the command with slightly different properties (say, its transform matrix or lighting properties.), rather than rebuilding the command, sending it to the GPU and verifying it each frame.



#17 ilreh   Members   -  Reputation: 281

Like
0Likes
Like

Posted 04 June 2014 - 01:13 PM

Using OpenGL I make 2D tile maps with a single triangle-strip and degenerate vertices. Don't know if it's the best practice but it gets the job done.

#18 frob   Moderators   -  Reputation: 21146

Like
0Likes
Like

Posted 04 June 2014 - 02:05 PM


I'm using Java Standard: more specifically the awt library that has Canvas and BufferStrategy and Image and BufferedImage. What do you mean by intermediary API? 

Ouch. Be careful.

 

That relies heavily on the Java environment it is being run on. As you probably aren't doing this on Android (I'm sure you would have tagged it and placed it in the Mobile forum) you should know some quirks.

 

The Windows JRE has some nasty performance issues in games rendering. Canvas is sometimes hardware accelerated, and sometimes not. BufferedImage particularly is notorious for switching between accelerated and non-acclerated. It can do it for several well documented reasons, such as reading from the image (hardware acceleration is write-only), or resizing the window, or moving between screens, or having Windows switch video modes. It can do it for many reasons which are seemingly random, such as another program running in the background. 

 

The runtime environment can --- for no reason your program can detect --- switch out of hardware acceleration mode. This will drop your framerate from something high (e.g. 3ms per frame) to something extremely low (e.g. 729ms per frame). This can happen at any time without any automatic notification.

 

You can monitor it yourself and suddenly notice you switched from frames-per-second to seconds-per-frame, but even once you detect it there is no way to correct it.

 

This is one of the biggest compelling reasons that people use external libraries for game graphics. Java's AWT works mostly okay for slow business graphics where a visual update can take a few moments. If you are making chess or something with slow event-driven updates you might be fine.  But if you need a solid frame rate measured with consistent double-digit milliseconds, you will not get that with AWT.


Check out my personal indie blog at bryanwagstaff.com.




Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.



PARTNERS