Drawing outside of love.draw

Questions about the LÖVE API, installing LÖVE and other support related questions go here.
Forum rules
Before you make a thread asking for help, read this.
User avatar
SelfDotX
Prole
Posts: 25
Joined: Sun Oct 02, 2022 5:06 pm

Drawing outside of love.draw

Post by SelfDotX »

Hi all, new to the forums and LOVE - nice to meet you.

While attempting to implement Poisson Disk Sampling - I decided to try to visualize what was going on, and hacked a few draw calls in, as well as calling graphics.present() at set intervals. The below code is it's simplest form.

This worked fine (if not a little slower) as long as the modulus was fairly small (<10) but with a modulus greater than ~100 it began slowing down, and with a modulus of ~1000, my computer threw itself out the window.

My first assumption was that draw calls to the "main screen" outside of the draw loop were buffered/queued in some way, leading to massive memory consumption. So I turned to a canvas, thinking draw calls to an offscreen canvas would happen immediately - but that doesn't seem to be the case either as the results were virtually identical.

I *think* the problem lies in how many draw calls are made without a corresponding call to present() - small modulus means a small ratio between draw calls and present() where a large modulus means a very large ratio between calls and present() and catastrophic failure.

To be clear, I'm looking to understand the 'why', not a specific fix for the code below, what is happening behind the scenes, is it buffered/queued? or is there some other mechanic happening?

I realize this specific case of possibly 10's of millions of draw calls prior to calling present() is not a likely or common scenario - but there is a wall there and I'd like to know what it's made of... so to speak.

Code: Select all

for i=1,10000 do
  for _, pos in pairs(points) do -- #points==8000
    love.graphics.circle("fill", pos.x, pos.y, settings.radius / 2)
  end
  if i % 10 ==1 then
    love.graphics.present()
  end
end

User avatar
darkfrei
Party member
Posts: 1169
Joined: Sat Feb 08, 2020 11:09 pm

Re: Drawing outside of love.draw

Post by darkfrei »

Maybe you are need the quadtree optimization or similar

https://youtu.be/u_xWJBb-qT0
:awesome: in Lua we Löve
:awesome: Platformer Guide
:awesome: freebies
User avatar
ReFreezed
Party member
Posts: 612
Joined: Sun Oct 25, 2015 11:32 pm
Location: Sweden
Contact:

Re: Drawing outside of love.draw

Post by ReFreezed »

It's a bit unclear what the actual problem is as the code doesn't make much sense (the same points are being drawn as circles on top of each other several times, before the result is "presented" to the screen), but here's some possibly relevant information.

You mentioned draw calls and buffering. There's a thing in LÖVE called auto-batching where it tries to buffer as many draw operations as possible into one actual draw call to the GPU (as communication can be expensive). LÖVE essentially uses a SpriteBatch behind the scenes. This usually speeds up drawing (even though more memory is used). It doesn't matter if you're drawing to a canvas or "the screen" as they are both off-screen canvases (or simply "buffers" outside the context of LÖVE). Draw operations can be batched as long as you use the same texture (or no texture, as in the case of circle()), the same shader, and some other things, which the posted code indeed does.

Here's roughly what happens when you call present(): any remaining batched draw operations are executed by LÖVE, the program is paused until vsync happens (if vsync is enabled), the back buffer (which is what we've been calling "the screen") and the front buffer (which is what's currently being shown on the actual screen) are swapped (i.e. what you drew is "presented" - see double buffering), and finally the program resumes.
Tools: Hot Particles, LuaPreprocess, InputField, (more) Games: Momento Temporis
"If each mistake being made is a new one, then progress is being made."
User avatar
slime
Solid Snayke
Posts: 3131
Joined: Mon Aug 23, 2010 6:45 am
Location: Nova Scotia, Canada
Contact:

Re: Drawing outside of love.draw

Post by slime »

Just to add one thing: with "a modulus of ~1000", that's 8,000,000 calls to love.graphics.circle per frame. Are you sure you need 8 million circles to be drawn to the screen each frame?

love.graphics.flushBatch is another tool that might be useful for experimenting with framerates.
User avatar
SelfDotX
Prole
Posts: 25
Joined: Sun Oct 02, 2022 5:06 pm

Re: Drawing outside of love.draw

Post by SelfDotX »

ReFreezed wrote: Sun Oct 02, 2022 8:08 pm It's a bit unclear what the actual problem is as the code doesn't make much sense (the same points are being drawn as circles on top of each other several times, before the result is "presented" to the screen), but here's some possibly relevant information.

You mentioned draw calls and buffering. There's a thing in LÖVE called auto-batching where it tries to buffer as many draw operations as possible into one actual draw call to the GPU (as communication can be expensive). LÖVE essentially uses a SpriteBatch behind the scenes. This usually speeds up drawing (even though more memory is used). It doesn't matter if you're drawing to a canvas or "the screen" as they are both off-screen canvases (or simply "buffers" outside the context of LÖVE). Draw operations can be batched as long as you use the same texture (or no texture, as in the case of circle()), the same shader, and some other things, which the posted code indeed does.

Here's roughly what happens when you call present(): any remaining batched draw operations are executed by LÖVE, the program is paused until vsync happens (if vsync is enabled), the back buffer (which is what we've been calling "the screen") and the front buffer (which is what's currently being shown on the actual screen) are swapped (i.e. what you drew is "presented" - see double buffering), and finally the program resumes.
Thanks for the quick response!

I'll try to do a little better at explaining. What I'm presenting is several nested loops that make a draw call, what or where isn't important. What I think is happening, and your response seems to confirm, is that the draw call doesn't alter the canvas at all - but instead queues the call, this queue must use memory, and so calling circle() millions of times without calling present() grows the queue to unmanageable proportions. I admit again that this is a contrived scenario - none the less I'm hoping for better understanding.

With my only other knowledge of a <canvas> object, being from JavaScript, I think I'm fundamentally misunderstanding what it means to 'draw a circle' to a canvas in LOVE. Where in JS, I could make calls to the canvas object all day without ever showing it on screen, in love it appears these calls don't happen instantaneously, but incur some overhead which compounds with each draw call until the canvas can be passed to the gpu. Or maybe more to the point, I thought manipulating a canvas was the same as manipulating ImageData.
User avatar
SelfDotX
Prole
Posts: 25
Joined: Sun Oct 02, 2022 5:06 pm

Re: Drawing outside of love.draw

Post by SelfDotX »

slime wrote: Sun Oct 02, 2022 8:49 pm Just to add one thing: with "a modulus of ~1000", that's 8,000,000 calls to love.graphics.circle per frame. Are you sure you need 8 million circles to be drawn to the screen each frame?

love.graphics.flushBatch is another tool that might be useful for experimenting with framerates.
No, I absolutely don't. But what I've been trying to convey - and poorly it seems - is that I wanted to understand the 'why' - It isn't immediately obvious that draw calls are batched.
slime wrote: Sun Oct 02, 2022 8:49 pm that's 8,000,000 calls to love.graphics.circle per frame
Just to be clear, I'm doing this outside of the draw loop.
User avatar
ReFreezed
Party member
Posts: 612
Joined: Sun Oct 25, 2015 11:32 pm
Location: Sweden
Contact:

Re: Drawing outside of love.draw

Post by ReFreezed »

SelfDotX wrote: Sun Oct 02, 2022 9:07 pm What I'm presenting is several nested loops that make a draw call, what or where isn't important. What I think is happening, and your response seems to confirm, is that the draw call doesn't alter the canvas at all - but instead queues the call, this queue must use memory, and so calling circle() millions of times without calling present() grows the queue to unmanageable proportions. I admit again that this is a contrived scenario - none the less I'm hoping for better understanding.
So if I understand correctly, you just want to draw everything that's happening for some duration, present it, and repeat, and the problem is that too much memory is used? Anyway, it seems like you understand what's happening correctly. Since most people won't deliberately run into a situation where they draw several million things that all technically fit into the same batch every frame, LÖVE doesn't expect it to happen either and just doesn't handle the batch getting too big (I'm assuming - I haven't looked at LÖVE's code enough). The conclusion to use flushBatch() indeed seem sane.
SelfDotX wrote: Sun Oct 02, 2022 9:07 pm With my only other knowledge of a <canvas> object, being from JavaScript, I think I'm fundamentally misunderstanding what it means to 'draw a circle' to a canvas in LOVE. Where in JS, I could make calls to the canvas object all day without ever showing it on screen, in love it appears these calls don't happen instantaneously, but incur some overhead which compounds with each draw call until the canvas can be passed to the gpu. Or maybe more to the point, I thought manipulating a canvas was the same as manipulating ImageData.
A Canvas is a texture that's stored in VRAM (like Image objects), so it's indeed not like ImageData which is completely in RAM (like all the other *Data objects, e.g. SoundData). The canvas is never "passed" to the GPU as it's always there (you can read from it using Canvas:newImageData). Just like in JavaScript, you can draw to a canvas however much you want - you just may need to flush the auto-batch manually every now and then.
Tools: Hot Particles, LuaPreprocess, InputField, (more) Games: Momento Temporis
"If each mistake being made is a new one, then progress is being made."
User avatar
SelfDotX
Prole
Posts: 25
Joined: Sun Oct 02, 2022 5:06 pm

Re: Drawing outside of love.draw

Post by SelfDotX »

ReFreezed wrote: Sun Oct 02, 2022 11:09 pm So if I understand correctly, you just want to draw everything that's happening for some duration, present it, and repeat, and the problem is that too much memory is used? Anyway, it seems like you understand what's happening correctly. Since most people won't deliberately run into a situation where they draw several million things that all technically fit into the same batch every frame, LÖVE doesn't expect it to happen either and just doesn't handle the batch getting too big (I'm assuming - I haven't looked at LÖVE's code enough). The conclusion to use flushBatch() indeed seem sane.

A Canvas is a texture that's stored in VRAM (like Image objects), so it's indeed not like ImageData which is completely in RAM (like all the other *Data objects, e.g. SoundData). The canvas is never "passed" to the GPU as it's always there (you can read from it using Canvas:newImageData). Just like in JavaScript, you can draw to a canvas however much you want - you just may need to flush the auto-batch manually every now and then.
That the canvas lives on the gpu pretty much answers my question. Thanks!
ReFreezed wrote: Sun Oct 02, 2022 11:09 pm So if I understand correctly, you just want to draw everything that's happening for some duration, present it, and repeat, and the problem is that too much memory is used?
No, it's not that to much memory is used, I was trying to understand why memory was used. Again, in my mind I was manipulating a fixed set of bytes (pixels) and didn't understand why every circle() I drew incurred more and more memory. It makes more sense now that I know draw calls are buffered and sent in a batch - my batch just happened to be several million commands.
ReFreezed wrote: Sun Oct 02, 2022 11:09 pm Just like in JavaScript, you can draw to a canvas however much you want - you just may need to flush the auto-batch manually every now and then.
flushBatch() doesn't seem to help in this situation, at least not for an offscreen canvas - though as noted this isn't a problem anyone is likely to run into.
User avatar
ReFreezed
Party member
Posts: 612
Joined: Sun Oct 25, 2015 11:32 pm
Location: Sweden
Contact:

Re: Drawing outside of love.draw

Post by ReFreezed »

SelfDotX wrote: Mon Oct 03, 2022 12:07 am No, it's not that to much memory is used, I was trying to understand why memory was used. Again, in my mind I was manipulating a fixed set of bytes (pixels) and didn't understand why every circle() I drew incurred more and more memory. It makes more sense now that I know draw calls are buffered and sent in a batch - my batch just happened to be several million commands.
Gotcha. It's probably worth noting that these "commands" are just triangles to be drawn, and circles specifically consists/can consist of lots of them. So, drawing the circles with fewer segments, or using rectangles instead (either with love.graphics.rectangle, or love.graphics.draw with an image depicting a circle) is likely going to decrease memory usage and speed things up a bit.
Tools: Hot Particles, LuaPreprocess, InputField, (more) Games: Momento Temporis
"If each mistake being made is a new one, then progress is being made."
User avatar
SelfDotX
Prole
Posts: 25
Joined: Sun Oct 02, 2022 5:06 pm

Re: Drawing outside of love.draw

Post by SelfDotX »

ReFreezed wrote: Mon Oct 03, 2022 12:57 am Gotcha. It's probably worth noting that these "commands" are just triangles to be drawn, and circles specifically consists/can consist of lots of them. So, drawing the circles with fewer segments, or using rectangles instead (either with love.graphics.rectangle, or love.graphics.draw with an image depicting a circle) is likely going to decrease memory usage and speed things up a bit.
I think we're on the same page now. Using points() did make a noticeable improvement in memory and speed. Thanks again for your help and patience.
Post Reply

Who is online

Users browsing this forum: No registered users and 43 guests