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

Re: [pygame] Cheap COW clones for surfaces



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