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

Re: [pygame] Cheap COW clones for surfaces



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
> > 


--
Radomir Dopieralski