Page 1 of 2

Most efficient way to draw a tilegrid(spritebatch vs canvas)

Posted: Tue Sep 11, 2012 2:49 am
by Noktai
Hello,

After hearing about love2d a while ago I decided to give it a go, and I am absolutely in love!
Anyway, I'm writing a basic tilebased platform engine, and have been experimenting what could be the best method to draw a tilegrid.

I have 3 options all with 30x15 tiles on screen. I haven' t tried any profilers yet, so I'm simply running my laptop on power saver mode and checking out the FPS haha.

non canvas, non spritebatch
no optimizations whatsoever 48-50 fps

spritebatch
I was expecting a very big improvement on fps using a spritebatch, but I'm still not achieving a steady 60 fps. It's usually between 58-60 fps.

Canvas
The tilegrid doesn' t changed all that often, so it seems like a waste to draw every frame. I'm clearing and drawing to the canvas each time the tilegrid changes. The canvas is drawn to the screen every frame.
It seems like drawing the canvas is a pretty expensive operation, I usually have around 50-55 fps, with spikes when changing the tilegrid.

Now this isn't even a very big map, and I'm using only a single layer.
Is there something I'm doing wrong or could improve upon? I can imagine I'm not clearing the spritebatch/canvas properly or something.
Not having to redraw the canvas every frame would also be a great improvement.

The source code from the relevant section can be found below

Spritebatch

Code: Select all

function Tilegrid:draw(camera)

	local xEnd = math.floor(camera:width() / self.tileWidth);
    local yEnd = math.floor(camera:height() / self.tileHeight);

    if self.changed then
    	self.spritebatch:clear();
    end

	for x = 0, xEnd, 1 do

		for y = 0, yEnd, 1 do

			if self:getTile(x, y) ~= nil then

				if self.changed then 

					self.spritebatch:addq(self:getTile(x, y).sprite:getQuad(), x * self.tileWidth, y * self.tileHeight);
				end

				self:getTile(x, y):draw(self.spritebatch);

			end

		end
	end
	
	love.graphics.draw(self.spritebatch);

	if self.changed == true then self.changed = false end

end

function Tilegrid:getTile(x, y)

	if self.tiles[x + 1] == nil then

		return nil;

	end

	return self.tiles[x + 1][y + 1];

end

function Tilegrid:setTile(x, y, tile, solid)

	if tile == -1 then

		self.tiles[x + 1][y + 1] = nil;
		return;
	end

	if self:getTile(x, y) == nil then 

		self.tiles[x + 1][y + 1] = Tile:new(x, y, self.tileWidth, self.tileHeight, self.spritesheet);

	end

	self.changed = true;

	self.tiles[x + 1][y + 1].index = tile;
	self.tiles[x + 1][y + 1].solid = solid;

end
Canvas

Code: Select all

function Tilegrid:draw(camera)

	local xEnd = math.floor(camera:width() / self.tileWidth);
    local yEnd = math.floor(camera:height() / self.tileHeight);

    if self.changed then
    	self.spritebatch:clear(); --print("clear");
    	self.canvas:clear();
    end

	for x = 0, xEnd, 1 do

		for y = 0, yEnd, 1 do

			if self:getTile(x, y) ~= nil then

				if self.changed then 

					self.spritebatch:addq(self:getTile(x, y).sprite:getQuad(), x * self.tileWidth, y * self.tileHeight);
				end

				self:getTile(x, y):draw(self.spritebatch);

			end

		end
	end
	
	if self.changed then
		self.canvas:renderTo(function()
	    	love.graphics.draw(self.spritebatch);
		end);
	end

	love.graphics.scale(camera.scaleX,camera.scaleY);
	love.graphics.draw(self.canvas);

	if self.changed == true then self.changed = false end

end

function Tilegrid:getTile(x, y)

	if self.tiles[x + 1] == nil then

		return nil;

	end

	return self.tiles[x + 1][y + 1];

end

function Tilegrid:setTile(x, y, tile, solid)

	if tile == -1 then

		self.tiles[x + 1][y + 1] = nil;
		return;
	end

	if self:getTile(x, y) == nil then 

		self.tiles[x + 1][y + 1] = Tile:new(x, y, self.tileWidth, self.tileHeight, self.spritesheet);

	end

	self.changed = true;

	self.tiles[x + 1][y + 1].index = tile;
	self.tiles[x + 1][y + 1].solid = solid;

end

Re: Most efficient way to draw a tilegrid(spritebatch vs can

Posted: Tue Sep 11, 2012 6:05 am
by bartbes
Yes, you're not saving any effort if you're re-adding tile every single draw step. SpriteBatches (and Canvases) are way more effective if you have static data and thus don't redraw (to them) often. I'd also like to explain why putting your spritebatch on a canvas doesn't help, because you're still creating and drawing the spritebatch, but now you have to manage a canvas on top of that, then draw that. Added work = speedup? (It isn't.)

Re: Most efficient way to draw a tilegrid(spritebatch vs can

Posted: Tue Sep 11, 2012 6:17 am
by coffee
I don't even use spritebatch or canvas and I get steady 60fps when normally drawing maps with regular or quad images.
I only recommend spritebatch or canvas if have some reason you really need it. Otherwise regular draws with quads are fast enough (at least for me and almost map grid games I have seen around).

Anyway a common problem in map tilegrids is drawing not that is only seen on screen but operate/draw with all map (so drawing off-screen). That's is what usually waste resources.

EDITED: Ninja'ed by bartbes. And he have always better advices.

Re: Most efficient way to draw a tilegrid(spritebatch vs can

Posted: Tue Sep 11, 2012 2:11 pm
by Noktai
bartbes wrote:Yes, you're not saving any effort if you're re-adding tile every single draw step. SpriteBatches (and Canvases) are way more effective if you have static data and thus don't redraw (to them) often. I'd also like to explain why putting your spritebatch on a canvas doesn't help, because you're still creating and drawing the spritebatch, but now you have to manage a canvas on top of that, then draw that. Added work = speedup? (It isn't.)
I'm only adding tiles when there has been a change in the tilegrid. How do I prevent from redrawing? It seems like the root graphics buffer is cleared every frame, the screen is blank when I don' t draw it for just one frame.
What exactly do you mean with static data, static in the sense of movement?

About the canvas, yeah I figured it wasn' t going to be beneficial. I was hoping someone could point me out how I can keep the canvas on the screen without having to redraw it every frame.
coffee wrote:I don't even use spritebatch or canvas and I get steady 60fps when normally drawing maps with regular or quad images.
I only recommend spritebatch or canvas if have some reason you really need it. Otherwise regular draws with quads are fast enough (at least for me and almost map grid games I have seen around).

Anyway a common problem in map tilegrids is drawing not that is only seen on screen but operate/draw with all map (so drawing off-screen). That's is what usually waste resources.

EDITED: Ninja'ed by bartbes. And he have always better advices.
It just feels "wrong" to redraw something every frame while it only changes once in a while. But yeah, I'm using the camera bounds to determine the start en end tiles to draw in the loop.

Thanks for the quick responses :)

Re: Most efficient way to draw a tilegrid(spritebatch vs can

Posted: Tue Sep 11, 2012 2:28 pm
by coffee
Noktai wrote:It just feels "wrong" to redraw something every frame while it only changes once in a while. But yeah, I'm using the camera bounds to determine the start en end tiles to draw in the loop.
)
When I arrived where I also thought how strange the love.update/draw concept (and other constant update things) was. But it works very well, don't worry. Embrace love callbacks (https://love2d.org/wiki/Tutorial:Callback_Functions) and abandon the concept of stage all before send to screen often used in other coding languages/engines.

Re: Most efficient way to draw a tilegrid(spritebatch vs can

Posted: Tue Sep 11, 2012 3:59 pm
by Boolsheet
Noktai wrote:How do I prevent from redrawing? It seems like the root graphics buffer is cleared every frame, the screen is blank when I don' t draw it for just one frame.
You have to redraw. Well, not necessarily 'have to', but it's the only way to make sure everything gets actually shown.
Because the current LÖVE uses OpenGL and is somewhat close to that API you'll see its behaviour showing through. We talked about it over here. The thread goes on to another problem in the middle, but the last few posts have some more information on it.
Noktai wrote:What exactly do you mean with static data, static in the sense of movement?
Static in the sense of non-changing. In most cases, the information where to draw something that you add with SpriteBatch:add/addq gets uploaded to the graphics card memory. As you can imagine this will be very fast to draw if the GPU can operate with what it already has in memory. A change to the SpriteBatch will poke the graphics driver and request that the change gets uploaded to the graphics card memory. This got faster and faster with newer graphics cards, but it's still one of the big bottlenecks people have to live with.

If you change the whole SpriteBatch it will reupload the whole thing again. Don't get me wrong. This is not something you have to avoid, sometimes a complete update is needed to move everything, but you can design your game in a way that these changes are as few as possible and therefore more efficient.
Noktai wrote:It just feels "wrong" to redraw something every frame while it only changes once in a while.
Graphics cards are so fast that it is actually easier to just redraw instead of figuring out what has changed. Don't worry about it, everyone has to do it. ;)
Noktai wrote:I was expecting a very big improvement on fps using a spritebatch, but I'm still not achieving a steady 60 fps. It's usually between 58-60 fps.
You're hitting the refresh rate of your monitor where vertical synchronization limits you. Turn vsync off with love.conf or love.graphics.setMode to get the (almost) full performance of your system. Frames per second is a easy way to see if somethings performing to certain expectations. However, comparing two fps measurements may be misleading due to its nonlinearity. Try to use seconds between frames instead. The love.timer module provides functions that return the value of millisecond and microsecond timers. You can use their values to calculate the difference between the frames.

Re: Most efficient way to draw a tilegrid(spritebatch vs can

Posted: Tue Sep 11, 2012 11:21 pm
by Noktai
coffee wrote:
Noktai wrote:It just feels "wrong" to redraw something every frame while it only changes once in a while. But yeah, I'm using the camera bounds to determine the start en end tiles to draw in the loop.
)
When I arrived where I also thought how strange the love.update/draw concept (and other constant update things) was. But it works very well, don't worry. Embrace love callbacks (https://love2d.org/wiki/Tutorial:Callback_Functions) and abandon the concept of stage all before send to screen often used in other coding languages/engines.
I am actually pretty familiar with the update/draw method, but in most languages the screen doesn't automaticly clear itself. Most people here might know from the python library pygame, which seems to be a popular alternative to love2d.
Boolsheet wrote:
Noktai wrote:How do I prevent from redrawing? It seems like the root graphics buffer is cleared every frame, the screen is blank when I don' t draw it for just one frame.
You have to redraw. Well, not necessarily 'have to', but it's the only way to make sure everything gets actually shown.
Because the current LÖVE uses OpenGL and is somewhat close to that API you'll see its behaviour showing through. We talked about it over here. The thread goes on to another problem in the middle, but the last few posts have some more information on it.
That topic covers all my buffer clearing confusion, thanks!
Boolsheet wrote:
Noktai wrote:What exactly do you mean with static data, static in the sense of movement?
Static in the sense of non-changing. In most cases, the information where to draw something that you add with SpriteBatch:add/addq gets uploaded to the graphics card memory. As you can imagine this will be very fast to draw if the GPU can operate with what it already has in memory. A change to the SpriteBatch will poke the graphics driver and request that the change gets uploaded to the graphics card memory. This got faster and faster with newer graphics cards, but it's still one of the big bottlenecks people have to live with.

If you change the whole SpriteBatch it will reupload the whole thing again. Don't get me wrong. This is not something you have to avoid, sometimes a complete update is needed to move everything, but you can design your game in a way that these changes are as few as possible and therefore more efficient.
Yeah I figured that updating the spritebatch alot would kind of defeat the purpose of using it. I'm going to use the spritebatch for my tilegrid but will simply use the drawq method for all the rest.
Boolsheet wrote:
Noktai wrote:I was expecting a very big improvement on fps using a spritebatch, but I'm still not achieving a steady 60 fps. It's usually between 58-60 fps.
You're hitting the refresh rate of your monitor where vertical synchronization limits you. Turn vsync off with love.conf or love.graphics.setMode to get the (almost) full performance of your system. Frames per second is a easy way to see if somethings performing to certain expectations. However, comparing two fps measurements may be misleading due to its nonlinearity. Try to use seconds between frames instead. The love.timer module provides functions that return the value of millisecond and microsecond timers. You can use their values to calculate the difference between the frames.
Oh that sounds interesting, I will look into it! I also found some profilers which are not dependant on IDE's, so those should help aswell :D

I understand that the Opengl backend does some things differently than what I'm used to, so I will simply stick with the spritebatch without the canvas.
Thanks for your help everyone, you left a good first impression of the love2d communty on me :D

Re: Most efficient way to draw a tilegrid(spritebatch vs can

Posted: Wed Sep 12, 2012 1:21 am
by Ref
Question - has anyone tried modifying love.run to not clear the screen each frame and only overdrawing changed areas?

Code: Select all

function love.run()
   --( some code)
   while true do
      -- (more code)
      if love.graphics then
         if love.draw_clear then love.graphics.clear() end   --<= modification
         if love.draw then love.draw() end
         love.graphics.present()
         end
      -- (more code)
      end
Setting love.draw_clear to false would prevent screen clearing each frame.
Just wondering if anyone has done this and if this wouldn't speed things up a bit????

Re: Most efficient way to draw a tilegrid(spritebatch vs can

Posted: Wed Sep 12, 2012 5:11 am
by Xgoff
Ref wrote:Question - has anyone tried modifying love.run to not clear the screen each frame and only overdrawing changed areas?

Code: Select all

function love.run()
   --( some code)
   while true do
      -- (more code)
      if love.graphics then
         if love.draw_clear then love.graphics.clear() end   --<= modification
         if love.draw then love.draw() end
         love.graphics.present()
         end
      -- (more code)
      end
Setting love.draw_clear to false would prevent screen clearing each frame.
Just wondering if anyone has done this and if this wouldn't speed things up a bit????
dirty rectangles probably wouldn't help much unless you were doing expensive things like per-pixel drawing or something. oh and transparency would rain on your parade by complicating it

Re: Most efficient way to draw a tilegrid(spritebatch vs can

Posted: Wed Sep 12, 2012 7:10 am
by T-Bone
Drawing stuff on the screen isn't a very heavy operation. Rendering what you want to draw can be.