pygame-community / pygame-ce

🐍🎮 pygame - Community Edition is a FOSS Python library for multimedia applications (like games). Built on top of the excellent SDL library.
https://pyga.me
766 stars 120 forks source link

Improve `PixelArray.make_surface()` performance #2953

Closed itzpr3d4t0r closed 2 days ago

itzpr3d4t0r commented 1 week ago

This PR significantly enhances the speed of the PixelArray.make_surface() method through two main optimizations:

Note to reviewers: The changes regarding the switch body are just me moving the entire switch inside an else block, so there's no changes there rly, just to let you know and avoid you wasting time on checking the diff.

These improvements lead to substantial performance gains, though the extent of the speedup varies based on image size and Bpp:

When building a same sized Surface

pixelarray_make_surface_fullsurface

When slicing (basically row_length - 1, shows memcpy improvement)

pixelarray_make_surface_uptolastpxlminusone

Program used:

import pygame
from pygame import PixelArray
from pygame.transform import scale as scale_surf
from data_utils import plot_tests, run_tests

pygame.init()

# ===========| CONFIG |===========

DO_TEST = 1
MAX_SIZE = 1000
REPETITIONS = 100
NUM_CALLS = 1

TITLE = "pixelarray test"
X_LABEL = "Surface size"
DO_SCATTER = True
MODE = "MIN"
LIMIT_TO_RANGE = MAX_SIZE
COMPARE_LIST = [
    (1, 0),
    (3, 2),
    (5, 4),
    (7, 6),
]

screen = pygame.display.set_mode((100, 100))

kwargs_dict = {
}

img32 = pygame.image.load("test_progs/background.jpg").convert(32)
img24 = pygame.image.load("test_progs/background.jpg").convert(24)
img16 = pygame.image.load("test_progs/background.jpg").convert(16)
img8 = pygame.image.load("test_progs/background.jpg").convert(8)

def test_setup(curr_size: int, g: dict):
    # for sliced test
    c = curr_size + 1
    size = (c, c)
    g["px_arr32"] = PixelArray(scale_surf(img32, size))[:c - 1, :]
    g["px_arr24"] = PixelArray(scale_surf(img24, size))[:c - 1, :]
    g["px_arr16"] = PixelArray(scale_surf(img16, size))[:c - 1, :]
    g["px_arr8"] = PixelArray(scale_surf(img8, size))[:c - 1, :]

    # for non-sliced test
    # g["px_arr32"] = PixelArray(scale_surf(img32, (curr_size, curr_size)))
    # g["px_arr24"] = PixelArray(scale_surf(img24, (curr_size, curr_size)))
    # g["px_arr16"] = PixelArray(scale_surf(img16, (curr_size, curr_size)))
    # g["px_arr8"] = PixelArray(scale_surf(img8, (curr_size, curr_size)))

tests = [
    ("pixelArray make_surface new 32 (:)", "px_arr32.make_surface()"),
    ("pixelArray make_surface old 24 (:)", "px_arr24.make_surface()"),
    ("pixelArray make_surface old 16 (:)", "px_arr16.make_surface()"),
    ("pixelArray make_surface old 8 (:)", "px_arr8.make_surface()"),
]

files = [
    # ("pixelArray make_surface new 32", "white"),
    # ("pixelArray make_surface old 32", "violet"),
    #
    # ("pixelArray make_surface new 24", "red"),
    # ("pixelArray make_surface old 24", "yellow"),
    #
    # ("pixelArray make_surface new 16", "lime"),
    # ("pixelArray make_surface old 16", "blue"),
    #
    # ("pixelArray make_surface new 8", "yellow"),
    # ("pixelArray make_surface old 8", "green"),

    ("pixelArray make_surface new 32 (:)", "white"),
    ("pixelArray make_surface old 32 (:)", "violet"),

    ("pixelArray make_surface new 24 (:)", "red"),
    ("pixelArray make_surface old 24 (:)", "yellow"),

    ("pixelArray make_surface new 16 (:)", "lime"),
    ("pixelArray make_surface old 16 (:)", "blue"),

    ("pixelArray make_surface new 8 (:)", "yellow"),
    ("pixelArray make_surface old 8 (:)", "green"),
]

if DO_TEST:
    run_tests(tests, test_setup, MAX_SIZE, REPETITIONS, NUM_CALLS, **kwargs_dict)

pygame.quit()

plot_tests(TITLE, files, MODE, LIMIT_TO_RANGE, DO_SCATTER, False, COMPARE_LIST, X_LABEL)