Drawing things efficiently - LÖVE internals question

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.
Post Reply
User avatar
kikito
Inner party member
Posts: 3153
Joined: Sat Oct 03, 2009 5:22 pm
Location: Madrid, Spain
Contact:

Drawing things efficiently - LÖVE internals question

Post by kikito »

There have been several questions about efficiency lately on these forums, and those got me thinking.

I've been wondering whether it would be possible to increase the speed at which things are drawn. I think I've thought of a way, but it'd work only if several assumptions are correct, and it'd involve making a change to LÖVE's boot.lua . And still, it'd be difficult to see any benefit until both options where compared.

** The assumption **

My assumption is that LÖVE "translates" every love.graphics call to a C/C++ OpenGL call. This going around between Lua and C++ takes some time. I've been told that this context-switching takes significant time, but I have no means to attest that.

I tried to browse the LÖVE source code, to verify this assumption, but I'm kindof allergic to C++. I would be grateful if someone with more experience with the source code could confirm this.

** The proposed change **

Instead of switching to C/C++ every time, do that only once, by default when love.draw finishes. love.graphics calls would just "fill up" a pure-Lua table. In other words, this:

Code: Select all

love.graphics.rectangle("fill", 100, 100, 200, 200)
love.graphics.draw(image, 10, 10)
Would be translated into a Lua table like this:

Code: Select all

-- love.graphics.buffer will be most certainly an internal thing, not to be shared with the user.
-- I'm giving it that name just to make it clear that it's a buffer accesible from the love.graphics functions
love.graphics.buffer = {
  { "rectangle", "fill", 100, 100, 200, 200 },
  { "drawable", image, 10, 10 }
}
love.run would have to be slightly modified. It would need a new function, which I have called love.graphics.flush(). It would be used like this:

Code: Select all

-- inside love.run()
if love.graphics then
  love.graphics.clear() -- this empties the buffer
  if love.draw then love.draw() end -- this fills the buffer, but doesn't draw anything
  love.graphics.flush() -- this draws everything stored in the buffer to OpenGL, with a single context switch
end
** Consequences **
  • I must stress this: I'm not sure this would make code any faster. There are many things I don't know. But if my assumptions are correct, and a single context switch + parsing a Lua table is faster than multiple context switches, then I think this might be worth trying.
  • Custom love.run implementations would not work any more. So this should be released on a new version - 0.9.x, I'd say.
  • love.graphics methods called from other places than love.draw (i.e. love.load or love.update) would stop working. I don't see this as a problem, since it is highly discouraged. But there is a difference between discouraging something and making it outright impossible.
Please let me know what you guys think.
When I write def I mean function.
User avatar
slime
Solid Snayke
Posts: 3161
Joined: Mon Aug 23, 2010 6:45 am
Location: Nova Scotia, Canada
Contact:

Re: Drawing things efficiently - LÖVE internals question

Post by slime »

The Lua<->C barrier overhead is a bottleneck, but not the bottleneck, especially when talking about the graphics API. I'm not convinced the potential performance gains would be worth the API complexity, confusion, and any number of unforseen consequences. How do canvases factor into that API? They're off-screen render targets, so they can (and will) be drawn to at any time.

If the goal is to reduce or eliminate the Lua<->C barrier bottleneck, then we should wait until LuaJIT 2.0-final comes out and make LÖVE's API use LuaJIT's FFI wherever possible. :)
User avatar
kikito
Inner party member
Posts: 3153
Joined: Sat Oct 03, 2009 5:22 pm
Location: Madrid, Spain
Contact:

Re: Drawing things efficiently - LÖVE internals question

Post by kikito »

slime wrote:How do canvases factor into that API? They're off-screen render targets, so they can (and will) be drawn to at any time.
I'm guessing each canvas could have its buffer. But you would have to remember to flush the buffer, which I think could be a pain. Good comment, thanks.
slime wrote:If the goal is to reduce or eliminate the Lua<->C barrier bottleneck, then we should wait until LuaJIT 2.0-final comes out and make LÖVE's API use LuaJIT's FFI wherever possible. :)
Yeah, I forgot about LuaJIT. I might need more coffee.
When I write def I mean function.
User avatar
bartoleo
Party member
Posts: 118
Joined: Wed Jul 14, 2010 10:57 am
Location: Savigliano

Re: Drawing things efficiently - LÖVE internals question

Post by bartoleo »

I think it could be a "boost"... this 'buffer' of draws... or a concept of 'scene' which is missing in LÖVE
Bartoleo
User avatar
T-Bone
Inner party member
Posts: 1492
Joined: Thu Jun 09, 2011 9:03 am

Re: Drawing things efficiently - LÖVE internals question

Post by T-Bone »

Does Löve need the performance boost? I mean, it's a framework for 2D games. Even my crappy netbook runs pretty much all Löve games over 100 FPS. There are several things that could be improved in new releases of Löve, and performance doesn't feel like a high priority problem. But I could be wrong. Especially if targeting Android and other portable platforms become a large focus, I guess the extra performance could be a significant improvement.
User avatar
vrld
Party member
Posts: 917
Joined: Sun Apr 04, 2010 9:14 pm
Location: Germany
Contact:

Re: Drawing things efficiently - LÖVE internals question

Post by vrld »

kikito wrote:** The assumption **

My assumption is that LÖVE "translates" every love.graphics call to a C/C++ OpenGL call. This going around between Lua and C++ takes some time. I've been told that this context-switching takes significant time, but I have no means to attest that.
It's actually not that simple, since LÖVE does a lot of abstractions and boilerplate in the background. One call to a love.graphics function usually results in a lot more OpenGL calls. For example, look at the C++ code invoked on love.graphics.setCanvas(), love.graphics.print(), love.graphics.draw(image, ...) (ignoring computation of the transformation) and spritebatch:set(). Of course there are also functions that produce a single opengl call (e.g. love.graphics.setColor() and love.graphics.setPixelEffect()), but in general it's a 1-to-n relation.
kikito wrote:** The proposed change **

Instead of switching to C/C++ every time, do that only once, by default when love.draw finishes. love.graphics calls would just "fill up" a pure-Lua table.
I am not sure that this would help, as we'd have to associate functions with the tags (e.g. "rectangle" -> Graphics::rectangle()). This either means having a huge, unmaintainable switch statement or using a function registry. Since C++ is statically typed and the parameter types and even count vary with each function, we'd have to either pull some serious stunts to call the function or rely on Lua (which does these stunts very elegantly). So in the end we'd have to choose between writing unmaintainable code or still crossing the Lua-C++ barrier, only in a more complicated way than we do now. (Disclaimer: I might be missing something.)


Unrelated to the above:
Crossing the Application <-> OpenGL barrier is also costly, as most of the time that means sending stuff to the graphics card. Given prior knowledge, some functions (e.g. drawing images) could be marginally faster. Unfortunately, we don't have this knowledge. :(

Edit and slightly more related:
Bartbes recently did some profiling. I don't exactly remember the outcome though... :roll:
I have come here to chew bubblegum and kick ass... and I'm all out of bubblegum.

hump | HC | SUIT | moonshine
User avatar
kikito
Inner party member
Posts: 3153
Joined: Sat Oct 03, 2009 5:22 pm
Location: Madrid, Spain
Contact:

Re: Drawing things efficiently - LÖVE internals question

Post by kikito »

vrld wrote:(lots of insight)
Thank you for your insight. I see what you mean.
vrld wrote:I am not sure that this would help, as we'd have to associate functions with the tags (e.g. "rectangle" -> Graphics::rectangle()). This either means having a huge, unmaintainable switch statement or using a function registry
Isn't that how things are done in C++ anyway? I'm not trolling. I used to be good at C++, but that was long time ago.

I remember that when you needed something more dynamic than a generic type, you had to use macros. That's just How Things Were, if-you-didn't-like-it-you-could-go-play-with-your-dynamic-toy-languages-you-crybaby.

Hmm... fascinating. Where did that came from? :D
When I write def I mean function.
User avatar
qaisjp
Party member
Posts: 490
Joined: Tue Sep 04, 2012 10:49 am
Location: United Kingdom
Contact:

Re: Drawing things efficiently - LÖVE internals question

Post by qaisjp »

nevertheless i like this buffer array...
Lua is not an acronym.
Desty
Prole
Posts: 4
Joined: Sat Oct 27, 2012 7:26 pm
Location: Ireland

Re: Drawing things efficiently - LÖVE internals question

Post by Desty »

slime wrote:If the goal is to reduce or eliminate the Lua<->C barrier bottleneck, then we should wait until LuaJIT 2.0-final comes out and make LÖVE's API use LuaJIT's FFI wherever possible. :)
Looks like that will be possible soon - 2.0 RC1 came out last week. I noticed this:
http://repo.or.cz/w/luajit-2.0.git/commitdiff/1d5c2ce4e295562daddfe6ce8e470749f0d42542 wrote: a message polling function might not run Lua callbacks right away and the call gets JIT-compiled. If it later happens to call back into Lua, you'll get a VM PANIC with the message "bad callback". Then you'll need to manually turn off JIT-compilation with jit.off() for the surrounding Lua function that invokes such a message polling function (or similar).
As long as it's possible to work around that, awesome, I'd love to see an official LuaJIT-powered LÖVE.
Post Reply

Who is online

Users browsing this forum: No registered users and 5 guests