Need some assistance with animation again... When I run the drawing code in a separate thread, it''s slow as heck. I think it may be because the drawing thread isn''t getting enough time from the thread scheduler. I was able to reproduce the problem in my little sample program simply by increasing the number of lines it draws per update. I''m lucky to get one frame every three seconds with this. If I "thread.join" or "thread.priority += 1" the speed greatly increases, but then the GUI becomes unresponsive. (Similar problems with threads have been discussed previously on the wxRuby mailing list.) require ''rubygems'' require ''wx'' class MyApp < Wx::App def on_init #Animate every 33 milliseconds. t = Wx::Timer.new(self, 55) evt_timer(55) { Thread.pass } t.start(33) #Containing frame. frame = Wx::Frame.new(nil, :size => [300, 300]) frame.show #Offscreen drawing buffer. buffer = Wx::Bitmap.new(300, 300) #Displays drawing. window = Wx::Window.new(frame, :size => [300, 300]) #Animate. thread = Thread.new do 300.times do |i| #Clear screen. buffer.draw do |surface| surface.pen = Wx::Pen.new(Wx::Colour.new(0, 0, 0), 0) surface.brush = Wx::BLACK_BRUSH surface.draw_rectangle(0, 0, 300, 300) end #Draw lines. 30.times do |j| x = i + j buffer.draw do |surface| surface.pen = Wx::Pen.new( Wx::Colour.new(128, 255, 128), 3 ) surface.pen.cap = Wx::CAP_ROUND surface.draw_line(x, 0, x+100, 100) end end #Update screen. update_window(window, buffer) end end end def update_window(window, buffer) window.paint do |dc| #Copy the buffer to the viewable window. dc.draw_bitmap(buffer, 0, 0, false) end end end app = MyApp.new app.main_loop I ran it through ruby-prof... Am I right in thinking these results mean the drawing thread is getting a fraction of the time that the main loop is? Thread ID: 59174940 Total: 0.01 %self total self wait child calls name 1250.00 30.45 0.13 2.02 28.31 11 Integer#times-1 160.00 2.86 0.02 2.84 0.00 3391 Wxruby2::Colour#initialize 160.00 0.02 0.02 0.00 0.00 3378 Wxruby2::DC#draw_line 0.00 0.05 0.00 0.05 0.00 11 Wxruby2::Window#paint ... Thread ID: 42518340 Total: 32.594 %self total self wait child calls name 96.59 31.72 31.48 0.00 0.24 1 Wxruby2::App#main_loop 2.01 0.77 0.66 0.00 0.11 42 Kernel#gem_original_require-1 0.19 0.06 0.06 0.00 0.00 28 <Class::Dir>#[] ... My actual program (a game) is going to be doing even more drawing operations than this, so I need to boost the speed wherever I can. Any advice would be most appreciated! -Jay McGavren http://jay.mcgavren.com/zyps
Mario Steele
2007-Dec-30 02:18 UTC
[wxruby-users] Drawing thread not getting enough time from scheduler?
Hello Jay, Well, one optimization that I can see just by skimming the code, is that your creating a DC Buffer each time you loop to draw a line. The grabbing of a DC weither it''s an On-Screen Control, or an Off Screen Bitmap. The better way to do this, is to go this route here: --Code-- buffer.draw do |surface| surface.pen = Wx::Pen.new( Wx::Colour.new(128, 255, 128), 3 ) 30.times do |j| x = i + j surface.pen.cap = Wx::CAP_ROUND surface.draw_line(x, 0, x+100, 100) end end --end code-- If you would also notice, I''m not constantly re-creating the Wx::Pen either. Since it''s created once, and your using the same colour, and size for the Pen, there''s no reason to be re-creating it for each and every line you draw. Whenever it comes to drawing, the main things you need to keep in mind, as far as performance is concerned, is that everytime you create a DC, it takes the Underlying OS a bit to load up the DC, the same to release the DC. Also, you will want to create your primary pens first, that your going to use most often, and avoid trying to create them everytime an iteration of a loop occurs, as this also takes time away from which you can do stuff with. I believe, that with these simple modifications, you would easily get up to 50~ FPS on the game. Let me know how it goes, Mario Steele On 12/29/07, Jay McGavren <jay at mcgavren.com> wrote:> > Need some assistance with animation again... When I run the drawing > code in a separate thread, it''s slow as heck. I think it may be > because the drawing thread isn''t getting enough time from the thread > scheduler. > > I was able to reproduce the problem in my little sample program simply > by increasing the number of lines it draws per update. I''m lucky to > get one frame every three seconds with this. If I "thread.join" or > "thread.priority += 1" the speed greatly increases, but then the GUI > becomes unresponsive. (Similar problems with threads have been > discussed previously on the wxRuby mailing list.) > > > require ''rubygems'' > require ''wx'' > > class MyApp < Wx::App > > def on_init > > #Animate every 33 milliseconds. > t = Wx::Timer.new(self, 55) > evt_timer(55) { Thread.pass } > t.start(33) > > #Containing frame. > frame = Wx::Frame.new(nil, :size => [300, 300]) > frame.show > > #Offscreen drawing buffer. > buffer = Wx::Bitmap.new(300, 300) > > #Displays drawing. > window = Wx::Window.new(frame, :size => [300, 300]) > > #Animate. > thread = Thread.new do > 300.times do |i| > #Clear screen. > buffer.draw do |surface| > surface.pen = Wx::Pen.new(Wx::Colour.new(0, 0, 0), 0) > surface.brush = Wx::BLACK_BRUSH > surface.draw_rectangle(0, 0, 300, 300) > end > #Draw lines. > 30.times do |j| > x = i + j > buffer.draw do |surface| > surface.pen = Wx::Pen.new( > Wx::Colour.new(128, 255, 128), > 3 > ) > surface.pen.cap = Wx::CAP_ROUND > surface.draw_line(x, 0, x+100, 100) > end > end > #Update screen. > update_window(window, buffer) > end > end > > end > > def update_window(window, buffer) > window.paint do |dc| > #Copy the buffer to the viewable window. > dc.draw_bitmap(buffer, 0, 0, false) > end > end > > end > > app = MyApp.new > app.main_loop > > > I ran it through ruby-prof... Am I right in thinking these results > mean the drawing thread is getting a fraction of the time that the > main loop is? > > > Thread ID: 59174940 > Total: 0.01 > > %self total self wait child calls name > 1250.00 30.45 0.13 2.02 28.31 11 Integer#times-1 > 160.00 2.86 0.02 2.84 0.00 3391 > Wxruby2::Colour#initialize > 160.00 0.02 0.02 0.00 0.00 > 3378 Wxruby2::DC#draw_line > 0.00 0.05 0.00 0.05 0.00 > 11 Wxruby2::Window#paint > ... > > Thread ID: 42518340 > Total: 32.594 > > %self total self wait child calls name > 96.59 31.72 31.48 0.00 0.24 > 1 Wxruby2::App#main_loop > 2.01 0.77 0.66 0.00 0.11 42 > Kernel#gem_original_require-1 > 0.19 0.06 0.06 0.00 0.00 28 <Class::Dir>#[] > ... > > > My actual program (a game) is going to be doing even more drawing > operations than this, so I need to boost the speed wherever I can. > Any advice would be most appreciated! > > -Jay McGavren > http://jay.mcgavren.com/zyps > _______________________________________________ > wxruby-users mailing list > wxruby-users at rubyforge.org > http://rubyforge.org/mailman/listinfo/wxruby-users >-------------- next part -------------- An HTML attachment was scrubbed... URL: http://rubyforge.org/pipermail/wxruby-users/attachments/20071229/5f402a23/attachment.html
Jay McGavren
2007-Dec-30 08:22 UTC
[wxruby-users] Drawing thread not getting enough time from scheduler?
Thanks for looking at the code, Mario. I made those modifications (to my prototype as well as my game library, where I had been re-using a DC but had *not* been caching pens). It did speed things up a bit, but it still runs many times faster (55 seconds vs. 5 seconds) if I join the animation thread. thread = Thread.new do black_pen = Wx::Pen.new(Wx::Colour.new(0, 0, 0), 0) green_pen = Wx::Pen.new(Wx::Colour.new(128, 255, 128), 3) 300.times do |i| buffer.draw do |surface| #Clear screen. surface.pen = black_pen surface.brush = Wx::BLACK_BRUSH surface.draw_rectangle(0, 0, 300, 300) #Draw lines. 30.times do |j| x = i + j surface.pen = green_pen surface.pen.cap = Wx::CAP_ROUND surface.draw_line(x, 0, x+100, 100) end end #Update screen. update_window(window, buffer) end end #Makes GUI unresponsive if uncommented. # thread.join Any idea how I can get the thread scheduler to devote a bit more time to animation, without completely freezing the GUI? -Jay McGavren http://jay.mcgavren.com/zyps Mario Steele wrote:> Well, one optimization that I can see just by skimming the code, is that > your creating a DC Buffer each time you loop to draw a line. The grabbing > of a DC weither it''s an On-Screen Control, or an Off Screen Bitmap....> If you would also notice, I''m not constantly re-creating the Wx::Pen > either. Since it''s created once, and your using the same colour, and size > for the Pen, there''s no reason to be re-creating it for each and every line > you draw....> I believe, that with these simple modifications, you would easily get up to > 50~ FPS on the game.
Alex Fenton
2008-Jan-03 00:29 UTC
[wxruby-users] Drawing thread not getting enough time from scheduler?
Jay McGavren wrote:> It did speed things up a bit, but it still runs many times faster (55 > seconds vs. 5 seconds) if I join the animation thread. >...> Any idea how I can get the thread scheduler to devote a bit more time > to animation, without completely freezing the GUI?Just had a chance to try your code. For me on OS X it doesn''t work at all if thread.join is called, but runs pretty smoothly (about 8s total) otherwise. At some level you''ll also be caught by the granularity of Ruby''s thread time slices (10ms IIRC), and garbage collection, which is taking up to 9ms tracking wxRuby objects alone in your example. I see a couple of further potential optimisations in your code which improve smoothness for me: * Take the calls to surface.pen= and surface.pen.cap= out of the 30.times loop. * See whether the drawing could be more efficiently done with a single draw_polygon (which acccepts an Array of Wx::Points) Another untested possibility to improve perceived smoothness might be to drive the animation from a Wx::Timer. Have it run at regular intervals, calling window.refresh to invalidate the window, then copy from the bitmap inside an evt_paint handler. Let us know how it goes... cheers alex
Jay McGavren
2008-Jan-10 16:14 UTC
[wxruby-users] Drawing thread not getting enough time from scheduler?
Alex Fenton wrote:> Jay McGavren wrote: > > It did speed things up a bit, but it still runs many > times faster (55 > > seconds vs. 5 seconds) if I join the animation thread. > > > ... > > Any idea how I can get the thread scheduler to devote a > bit more time > > to animation, without completely freezing the GUI? > Just had a chance to try your code. For me on OS X it > doesn''t work at all if thread.join is called, but runs > pretty smoothly (about 8s total) otherwise. At some level > you''ll also be caught by the granularity of Ruby''s thread > time slices (10ms IIRC), and garbage collection, which is > taking up to 9ms tracking wxRuby objects alone in your > example.OK, so avoid excessive object creation... Oh, yes, and I forgot (again) to mention I''m on Windows, as are my main target users. Interesting that the thread handling seems to be so much better on OSX.> I see a couple of further potential optimisations in your > code which improve smoothness for me: > > * Take the calls to surface.pen= and surface.pen.cap= out > of the 30.times loop. > * See whether the drawing could be more efficiently done > with a single draw_polygon (which acccepts an Array of > Wx::Points)It only happens to look like a polygon - my actual game needs to draw individual lines (in varying colors and line widths). But that''s OK, because your below suggestion allowed me to draw everything at decent speed...> Another untested possibility to improve perceived > smoothness might be to drive the animation from a > Wx::Timer. Have it run at regular intervals, calling > window.refresh to invalidate the window, then copy from the > bitmap inside an evt_paint handler.This was the key. It''s drawing 300 lines in under 33 milliseconds now, even on my laptop. The close button and window dragging respond immediately. require ''rubygems'' require ''wx'' class MyApp < Wx::App def on_init #Containing frame. frame = Wx::Frame.new(nil, :size => [300, 300]) frame.show #Offscreen drawing buffer. buffer = Wx::Bitmap.new(300, 300) #Displays drawing. window = Wx::Window.new(frame, :size => [300, 300]) window.evt_paint do |event| update_window(window, buffer) end #Initialize drawing loop counter. @i = 0 #Animate periodically. timer_id = Wx::ID_HIGHEST + 1 t = Wx::Timer.new(self, timer_id) evt_timer(timer_id) {animate(window, buffer)} t.start(33) end def animate(window, buffer) green_pen = Wx::Pen.new(Wx::Colour.new(128, 255, 128), 3) black_pen = Wx::Pen.new(Wx::Colour.new(0, 0, 0), 0) buffer.draw do |surface| #Clear screen. surface.pen = black_pen surface.brush = Wx::BLACK_BRUSH surface.draw_rectangle(0, 0, 300, 300) #Draw lines. surface.pen = green_pen surface.pen.cap = Wx::CAP_ROUND 300.times do |j| x = @i + j surface.draw_line(x, 0, x+100, 100) end end #Update screen. update_window(window, buffer) @i += 1 @i = 0 if @i > 300 end def update_window(window, buffer) window.paint do |dc| #Copy the buffer to the viewable window. dc.draw_bitmap(buffer, 0, 0, false) end end end app = MyApp.new app.main_loop Now to see if I can get similar results from the game itself. Thanks to everyone for the assistance! -Jay McGavren http://jay.mcgavren.com/zyps