Skip to content

Improve PixelArray.make_surface() performance #2953

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 28, 2024

Conversation

itzpr3d4t0r
Copy link
Member

@itzpr3d4t0r itzpr3d4t0r commented Jun 23, 2024

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

  • Matching Array and Surface Sizes: When the array size matches the surface size, the method simply copies the original surface and returns it, thereby eliminating the need for an extra copy loop.
  • Optimized Slicing with Matching Bpp: When the sizes don't match (i.e., slicing) but the source and destination "Bpp" are the same, the method now uses a faster approach using memcpy to copy entire rows instead of processing each pixel individually.

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)

@itzpr3d4t0r itzpr3d4t0r added Performance Related to the speed or resource usage of the project PixelArray pygame.PixelArray labels Jun 23, 2024
@itzpr3d4t0r itzpr3d4t0r requested a review from a team as a code owner June 23, 2024 12:03
@itzpr3d4t0r itzpr3d4t0r changed the title Improve PixelArray.make_surface() performance Improve PixelArray.make_surface() performance Jun 23, 2024
Copy link
Member

@ankith26 ankith26 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, these are some impressive numbers!

Thanks for the PR! 😎

@ankith26 ankith26 added this to the 2.5.1 milestone Jun 24, 2024
Copy link
Member

@damusss damusss left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've seen local improvements in performance and the changes make sense to me :)

@damusss damusss merged commit 07e4522 into pygame-community:main Jun 28, 2024
39 checks passed
@itzpr3d4t0r itzpr3d4t0r deleted the pixelarray_makesurface_opt branch June 28, 2024 13:50
gresm pushed a commit to gresm/pygame-ce that referenced this pull request Jul 16, 2024
* optimized PixelArray make_surface() both when making a same sized surf from the array and when slicing.

* fix comment
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Performance Related to the speed or resource usage of the project PixelArray pygame.PixelArray
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants