[Help] Understanding pygame.display.update(rects)

I recently began using PyGame as a fun side project in Python. Yesterday, I came across some documentation about the DirtyRectangle Animation technique and tried implementing it in a render method. I noticed that even though I was only supposed to be updating the section of the screen that fell under the rectangle, the entire screen would actually update which is unexpected behavior and seems to defeat the purpose.

Here is a simplified version of my original code that confused me. I am blitting three rectangles onto the top third of the screen but never updating that portion of the display, yet those rectangles appear when using pygame.display.update(self.updatedRects) when updatedRects doesn't include the top third of the screen. You can test this yourself but running my code and clicking in the bottom 2/3rds of the screen.

import pygame
import sys

pygame.init()
screenInfo = pygame.display.Info()
clock = pygame.time.Clock()
windowSize = (1000, 1000)
screen = pygame.display.set_mode(windowSize)


class GameConstants:

    windowSize = windowSize
    board_size = 3
    white = (255, 255, 255)


class Board:

    def __init__(self):
        self.size = GameConstants.board_size
        self.location = (0, 0)
        self.rectSize = (round(GameConstants.windowSize[0] / GameConstants.board_size) + 1,
                         round(GameConstants.windowSize[1] / GameConstants.board_size) + 1)
        self.rects = []
        for row in range(3):
            y = self.rectSize[0] * row
            for col in range(3):
                x = self.rectSize[1] * col
                self.rects.append((pygame.Rect((x, y), self.rectSize), (255 // (row + 1), 255 // (col + 1), 200)))


class Game:

    def __init__(self, board):
        self.board = board
        self.piecePos = [[0, 0, 0], [0, 0, 0], [0, 0, 0]]
        self.updateRects = []

    def newPiece(self, mousePosition):
        pos = self.convertPos(mousePosition)
        if pos and self.piecePos[pos[0]][pos[1]] == 0:
            x, y = pos
            self.piecePos[x][y] = 1

            # blit on screen
            rect = self.board.rects[x + y * 3]
            screen.fill(rect[1], rect[0])
            self.updateRects.append(rect[0])

    def render(self):
        print(self.updateRects)
        pygame.display.update(self.updateRects)
        self.updateRects = []

    def convertPos(self, position):
        for index, rect in enumerate(self.board.rects):
            if rect[0].collidepoint(position):
                return (index - 3 * (index // 3), index // 3)

    def reset(self):
        self.piecePos = [[0, 0, 0], [0, 0, 0], [0, 0, 0]]
        screen.fill(GameConstants.white)
        pygame.display.flip()
        for rect in board.rects[0:3]:
            screen.fill(rect[1], rect[0])


board = Board()
game = Game(board)
game.reset()

while True:
    clock.tick(40)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            sys.exit()
        elif event.type == pygame.MOUSEBUTTONDOWN:
            mousePosition = pygame.mouse.get_pos()
            game.newPiece(mousePosition)
        elif event.type == pygame.KEYDOWN:
            if event.key == pygame.K_ESCAPE:
                sys.exit()
            elif event.key == pygame.K_r:
                game.reset()
    if game.updateRects:
        game.render()

Posted Nov 30, 2017, 4:52 AM UTC

311
noanon•
2
RascalJacqueOP•

Here is a screen recording of your code. As you can tell, it doesn't seem to work on my end.

1
noanon•

it works on my end. I don't know why its not working for you.

Edit if the background switches from black to white when you press UP and DOWN, then it seems to behave exactly like pg.display.upate(), for when I use pg.display.update(rect = None), the screen does not update at all. Why it behaves differently for you, I don't know. Perhaps it has to do with being on a Mac.

2
RascalJacqueOP•

I just ran your code on my desktop (Windows 10), and the code executed and displayed correctly. I believe this means that it's an issue with Macs.

2
iminurnamezChallenge Accepted x 17•

It's working correctly. Try adding this as the second line in your render method:

pygame.draw.rect(screen, pygame.Color("dodgerblue"), pygame.Rect(10, 10, 20, 20))

Notice that the dodgerblue rect isn't drawn to the screen until you click on the topleft square (thereby updating that portion of the display). Even though you're explicitly drawing the dodgerblue rect during the render method, it's only updated to the display after the topleft rect is added to the updaterects list.

2
RascalJacqueOP•

Here is a screen recording after I added your line of code. My computer seems to completely ignore the rectangle specific rendering. Is this intended behavior?

I'm using pygame 1.9.3 by the way.

1
iminurnamezChallenge Accepted x 17•

I'm not sure what's going on. Once I click a square it stays drawn to the screen (until I press r). I tried with 1.9.2 and 1.9.3 and got the same behavior.

2
RascalJacqueOP•

Did you get the same behavior as my gif or did your computer only render the blue square if you clicked in the top-left?

2
iminurnamezChallenge Accepted x 17•

Sorry for the delayed response, it was a looong day. I do not get the same behavior as in your gif - clicking on any of the nine squares updates that portion of the display surface meaning that square is now filled with the appropriate color (until reset() is called). The dodgerblue rect is only drawn to the display after the topleft square is clicked.

2
bitcraftChallenge Accepted x 2•

The update method varies across platforms (osx, windows, linux) and rendering backends (x11, directx, sdl, framebuffer). In my experience it is not consistent enough to be relied on. So I tend to encourage people to not bother unless they are working with a platform where it is known to work, or using an extremely slow system, like a pentium II (im serious!).

Here is my usual mantra about dirty rect:

Dirty rect animation is a red herring for optimization. Consider that drity rect animation doubles the amount of drawing that is required to paint the screen; one pass to erase, and another pass to redraw. if 50% of your screen changes, your time will be the same as if you painted 100% of the screen. if 80% changes, then your time spent is now 160%. If the screen scrolls, 100% changes and dirty rect means your draw time is now 200% of the original, since you are clearing the screen before drawing.

Modern computers are so fast it is best to just redraw the entire screen all the time each frame. Even redrawing the screen every frame should give you 100's of fps at 1080p on a modern computer. If you are not getting >60fps then dirty rect will not help and you are doing something wrong.

/rant

2