[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]

Re: [pygame] Cheap COW clones for surfaces



Them again, if you assume that the drawing thread is doing all the
blits, then the situation is reversed — it's the only thread that needs
write access to the surfaces, and everything is good again. In your
scenario, we still get the correct result, because all the blits are
executed by the drawing thread in the same order in which they were
scheduled — including the blits that modify your temporary surface with
the digits. As long as all the modifications happen either on one side
or the other, we are good.

On Mon, 18 Jun 2018 01:09:16 +0200
Radomir Dopieralski <pygame@xxxxxxxxxxxx> wrote:

> Oh, I think I misunderstood what the drawing thread was supposed to
> do. I thought it would only handle the "flip" and/or "update" calls,
> that is, sending the pixel data of the screen surface to the graphic
> cards, which is the slowest part of any pygame program. But from what
> you write, I understand it's supposed to handle all blits as well?
> 
> On Sun, 17 Jun 2018 17:33:54 -0500
> Luke Paireepinart <rabidpoobear@xxxxxxxxx> wrote:
> 
> > Radomir, there is another side effect - if the surfaces are not
> > copied, then whatever content was in surf a at the moment that it
> > was supposed to be drawn now no longer exists. For example, say the
> > logic thread is font rendering the numbers 1 thru 9, and then
> > blitting these to positions that are not overlapping each other,
> > but it is reusing a surface for the font render call. In the
> > synchronous example, 1, 2, 3, etc would be drawn. Make that
> > asynchronous and you could end up with 9,9,9,9,9....
> > 
> > If you are multithreading a game, the "main" thread should be
> > mutating the game state, not the surfaces directly. The render
> > thread should be the view of the model state at the snapshot in
> > time at which the render is called. The logic thread should have a
> > control that is determining the time between updates and applying
> > the appropriate amount of ticks to the game state / emulating
> > physics / etc. Then whenever the previous draw has completed, the
> > render thread would snapshot the state and represent it
> > appropriately (either redrawing or comparing to its previous
> > state). If there needs to be multiple draw calls to represent the
> > state properly since the last state, then they can all be done
> > before the display buffer is flipped.
> > 
> > In the 1-9 example, each font would be represented by a game object
> > that had a positional element, and you could add them to the map
> > asynchronous of the draws since the render thread would display them
> > on the next iteration of the render loop where they existed.
> > 
> > 
> > On Sun, Jun 17, 2018, 3:49 PM Radomir Dopieralski
> > <pygame@xxxxxxxxxxxx> wrote:
> >   
> > > Of course strictly speaking there is a difference in behavior,
> > > however, from the practical point of view, the difference boils
> > > down to the fact that something gets drawn half a frame earlier
> > > rather than half a frame later (because the command to draw it was
> > > given in between the frames). Unless you have less than 6 frames a
> > > second, the difference wouldn't be easy to notice, especially
> > > since it would be rather rare too, since you would need to get
> > > very specific timings to even have it happen. I wonder if it's
> > > worth the effort.
> > >
> > > On Sun, 17 Jun 2018 21:40:56 +0100
> > > Daniel Pope <mauve@xxxxxxxxxxxxxx> wrote:
> > >    
> > > > The mutatation would be on the logic thread side. Consider this:
> > > >
> > > > draw_to_screen(surf_a,  ...)
> > > > if cond:
> > > >     surf_a.blit(surf_b, ...)
> > > > draw_to_screen(surf_a, ...)
> > > >
> > > > Currently draw_to_screen() is synchronous. I'd like it to queue
> > > > the blit instead, to happen in another thread. If I just did
> > > >
> > > > def draw_to_screen(surf, ...):
> > > >     draw_queue.put((surf, ...))
> > > >
> > > > then there's a race condition - surf_a may be drawn twice with
> > > > the surf_b update.
> > > >
> > > > If draw_to_screen() is implemented like
> > > >
> > > > def draw_to_screen(surf, ...):
> > > >      draw_queue.put((surf.copy(), ...))
> > > >
> > > > Then I get no change in behaviour, but I copy on every blit, on
> > > > the logic thread, ie 2 copies per frame regardless of whether
> > > > cond is True.
> > > >
> > > > Changing the implementation of copy to create a COW clone means
> > > > that the buffer copy actually happens at this line, if it is
> > > > hit:
> > > >
> > > > surf_a.blit(surf_b, ...)
> > > >
> > > > Which means that there's 1 copy if cond is True and 0 if cond is
> > > > False.
> > > >
> > > > On Sun, 17 Jun 2018, 20:59 Radomir Dopieralski,
> > > > <pygame@xxxxxxxxxxxx> wrote:
> > > >    
> > > > > On Sun, 17 Jun 2018 17:48:26 +0200
> > > > > Daniel Pope <mauve@xxxxxxxxxxxxxx> wrote:
> > > > >    
> > > > > > I have been thinking for some time about how to optimise
> > > > > > Pygame Zero games on Raspberry Pi. Most Pi models have
> > > > > > multiple cores and an obvious improvement is to parallelise.
> > > > > > Logic has to be synchronous but I would like to offload
> > > > > > painting the screen to a separate thread.
> > > > > >
> > > > > > The problem I would have is in passing references to mutable
> > > > > > objects between threads. Primarily this applies to Surface
> > > > > > objects, which are mutable and expensive to copy. If I have
> > > > > > a draw thread that maintains a queue of screen blit
> > > > > > operations, I want the queue to hold references to surface
> > > > > > data that won't change even if I later mutate the surfaces
> > > > > > in the logic thread.    
> > > > >
> > > > > Sorry if I am missing something obvious, but it seems to me
> > > > > that the draw thread doesn't need to mutate the surfaces? I
> > > > > mean, it only accesses them in a read-only fashion to render
> > > > > them. So you don't need to pass references to mutable objects
> > > > > — the drawing thread can get a read-only reference. Why do you
> > > > > need it to have a reference to non-changing data? After all,
> > > > > if it changes, you will have to re-draw it in the next frame
> > > > > anyways. Unless the drawing thread is more than one frame
> > > > > behind (which really shouldn't happen), you don't care about
> > > > > the data changing.  

-- 
Radomir Dopieralski