Skip to content

Commit 0c7e3d5

Browse files
committed
surface.convert now works in threads
1 parent 8c2940d commit 0c7e3d5

File tree

4 files changed

+70
-18
lines changed

4 files changed

+70
-18
lines changed

examples/multithreaded_surface.py

+20-10
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import pygame
22
import threading
33
import random
4+
import sys
45

56
from time import perf_counter
67

7-
start = perf_counter()
8+
print(f"GIL Enabled: {sys._is_gil_enabled()}")
89

910
pygame.init()
1011

@@ -13,7 +14,7 @@
1314
surface = pygame.Surface((WIDTH, HEIGHT))
1415
surface.fill("black")
1516

16-
LERP_OFFSET = 0.001
17+
LERP_OFFSET = 0.01
1718

1819

1920
def get_random_color() -> pygame.Color:
@@ -24,8 +25,15 @@ def get_random_color() -> pygame.Color:
2425
return pygame.Color(r, g, b, a)
2526

2627

28+
def get_random_format() -> int:
29+
return random.choice([8, 12, 15, 16, 24, 32])
30+
31+
2732
def multithreaded_func(
28-
surf: pygame.Surface, target_pixel: tuple[int, int], target_color: pygame.Color
33+
surf: pygame.Surface,
34+
target_pixel: tuple[int, int],
35+
target_color: pygame.Color,
36+
depth: int,
2937
) -> None:
3038
lerp_distance = 0
3139

@@ -35,34 +43,36 @@ def multithreaded_func(
3543
lerp_distance += LERP_OFFSET
3644
new_color = original_color.lerp(target_color, lerp_distance)
3745
surf.set_at(target_pixel, new_color)
46+
surf.convert(depth)
3847

3948

4049
pixels = [(col, row) for col in range(WIDTH) for row in range(HEIGHT)]
4150

4251
colors = [get_random_color() for _ in range(WIDTH * HEIGHT)]
4352

44-
args = [(pixel, colors[i]) for i, pixel in enumerate(pixels)]
53+
depths = [get_random_format() for _ in range(WIDTH * HEIGHT)]
54+
55+
args = [(pixel, colors[i], depths[i]) for i, pixel in enumerate(pixels)]
4556
batches = {
4657
i: args[i * NUM_THREADS : (i + 1) * NUM_THREADS]
4758
for i in range(WIDTH * HEIGHT // NUM_THREADS)
4859
}
4960

61+
start = perf_counter()
5062
for batch in batches.values():
5163
threads: list[threading.Thread] = []
52-
for pixel, color in batch:
53-
new_thread = threading.Thread(
54-
target=multithreaded_func, args=(surface, pixel, color)
55-
)
64+
for arg in batch:
65+
new_thread = threading.Thread(target=multithreaded_func, args=(surface, *arg))
5666
new_thread.start()
5767
threads.append(new_thread)
5868

5969
while any([t.is_alive() for t in threads]):
6070
continue
6171

62-
pygame.image.save(pygame.transform.scale_by(surface, 10), "out.png")
63-
6472
end = perf_counter()
6573

74+
pygame.image.save(pygame.transform.scale_by(surface, 10), "out.png")
75+
6676
print(f"time taken: {end - start}")
6777

6878
for pixel, color in zip(pixels, colors):

src_c/_pygame.h

+1
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,7 @@ PG_GetSurfaceFormat(SDL_Surface *surf)
179179
#define PG_CreateSurfaceFrom(width, height, format, pixels, pitch) \
180180
SDL_CreateRGBSurfaceWithFormatFrom(pixels, width, height, 0, pitch, format)
181181
#define PG_ConvertSurface(src, fmt) SDL_ConvertSurface(src, fmt, 0)
182+
182183
#define PG_ConvertSurfaceFormat(src, pixel_format) \
183184
SDL_ConvertSurfaceFormat(src, pixel_format, 0)
184185

src_c/include/_pygame.h

+39-4
Original file line numberDiff line numberDiff line change
@@ -322,20 +322,55 @@ typedef struct {
322322
PyObject *dependency;
323323
#if PY_VERSION_HEX >= 0x030D0000 && Py_GIL_DISABLED
324324
PyMutex mutex;
325+
unsigned int num_locks;
326+
uint64_t locking_thread_id;
325327
#endif
326328
} pgSurfaceObject;
327329
#define pgSurface_AsSurface(x) (((pgSurfaceObject *)x)->surf)
328330

329331
#if PY_VERSION_HEX >= 0x030D0000 && Py_GIL_DISABLED
330-
#define LOCK_pgSurfaceObject(pgSurfacePtr) \
331-
PyMutex_Lock(&(((pgSurfaceObject *)pgSurfacePtr)->mutex));
332+
#define LOCK_pgSurfaceObject(pgSurfacePtr) \
333+
{ \
334+
pgSurfaceObject *surfObj = (pgSurfaceObject *)pgSurfacePtr; \
335+
uint64_t thread_id = PyThreadState_Get()->id; \
336+
if (surfObj->num_locks) { \
337+
if (thread_id == surfObj->locking_thread_id) { \
338+
++(surfObj->num_locks); \
339+
} \
340+
else { \
341+
PyMutex_Lock(&surfObj->mutex); \
342+
surfObj->num_locks = 1U; \
343+
surfObj->locking_thread_id = thread_id; \
344+
} \
345+
} \
346+
else { \
347+
PyMutex_Lock(&surfObj->mutex); \
348+
surfObj->num_locks = 1U; \
349+
surfObj->locking_thread_id = thread_id; \
350+
} \
351+
}
332352
#else
333353
#define LOCK_pgSurfaceObject(pgSurfacePtr)
334354
#endif
335355

336356
#if PY_VERSION_HEX >= 0x030D0000 && Py_GIL_DISABLED
337-
#define UNLOCK_pgSurfaceObject(pgSurfacePtr) \
338-
PyMutex_Unlock(&(((pgSurfaceObject *)pgSurfacePtr)->mutex));
357+
#define UNLOCK_pgSurfaceObject(pgSurfacePtr) \
358+
{ \
359+
pgSurfaceObject *surfObj = (pgSurfaceObject *)pgSurfacePtr; \
360+
uint64_t thread_id = PyThreadState_Get()->id; \
361+
if (surfObj->num_locks) { \
362+
if (thread_id == surfObj->locking_thread_id) { \
363+
if (surfObj->num_locks > 1U) { \
364+
--(surfObj->num_locks); \
365+
} \
366+
else { \
367+
surfObj->locking_thread_id = 0U; \
368+
surfObj->num_locks = 0U; \
369+
PyMutex_Unlock(&surfObj->mutex); \
370+
} \
371+
} \
372+
} \
373+
}
339374
#else
340375
#define UNLOCK_pgSurfaceObject(pgSurfacePtr)
341376
#endif

src_c/surface.c

+10-4
Original file line numberDiff line numberDiff line change
@@ -1471,7 +1471,7 @@ surf_convert(pgSurfaceObject *self, PyObject *args)
14711471

14721472
SURF_INIT_CHECK(surf)
14731473

1474-
pgSurface_Prep(self);
1474+
pgSurface_Lock(self);
14751475

14761476
if ((has_colorkey = SDL_HasColorKey(surf))) {
14771477
SDL_GetColorKey(surf, &colorkey);
@@ -1513,6 +1513,7 @@ surf_convert(pgSurfaceObject *self, PyObject *args)
15131513
Amask = 0xFF << 24;
15141514
break;
15151515
default:
1516+
pgSurface_Unlock(self);
15161517
return RAISE(PyExc_ValueError,
15171518
"no standard masks exist for given "
15181519
"bitdepth with alpha");
@@ -1548,6 +1549,7 @@ surf_convert(pgSurfaceObject *self, PyObject *args)
15481549
Bmask = 0xFF;
15491550
break;
15501551
default:
1552+
pgSurface_Unlock(self);
15511553
return RAISE(PyExc_ValueError,
15521554
"nonstandard bit depth given");
15531555
}
@@ -1565,7 +1567,7 @@ surf_convert(pgSurfaceObject *self, PyObject *args)
15651567
!pg_UintFromObjIndex(argobject, 1, &format.Gmask) ||
15661568
!pg_UintFromObjIndex(argobject, 2, &format.Bmask) ||
15671569
!pg_UintFromObjIndex(argobject, 3, &format.Amask)) {
1568-
pgSurface_Unprep(self);
1570+
pgSurface_Unlock(self);
15691571
return RAISE(PyExc_ValueError,
15701572
"invalid color masks given");
15711573
}
@@ -1576,7 +1578,7 @@ surf_convert(pgSurfaceObject *self, PyObject *args)
15761578
break;
15771579
}
15781580
else {
1579-
pgSurface_Unprep(self);
1581+
pgSurface_Unlock(self);
15801582
return RAISE(
15811583
PyExc_ValueError,
15821584
"invalid argument specifying new format to convert to");
@@ -1611,7 +1613,9 @@ surf_convert(pgSurfaceObject *self, PyObject *args)
16111613
SDL_SetPixelFormatPalette(&format, palette);
16121614
}
16131615
}
1616+
// pgSurface_Lock(self);
16141617
newsurf = PG_ConvertSurface(surf, &format);
1618+
// pgSurface_Unlock(self);
16151619
SDL_SetSurfaceBlendMode(newsurf, SDL_BLENDMODE_NONE);
16161620
SDL_FreePalette(palette);
16171621
}
@@ -1623,19 +1627,21 @@ surf_convert(pgSurfaceObject *self, PyObject *args)
16231627
}
16241628

16251629
if (newsurf == NULL) {
1630+
pgSurface_Unlock(self);
16261631
return RAISE(pgExc_SDLError, SDL_GetError());
16271632
}
16281633

16291634
if (has_colorkey) {
16301635
colorkey = SDL_MapRGBA(newsurf->format, key_r, key_g, key_b, key_a);
16311636
if (SDL_SetColorKey(newsurf, SDL_TRUE, colorkey) != 0) {
16321637
PyErr_SetString(pgExc_SDLError, SDL_GetError());
1638+
pgSurface_Unlock(self);
16331639
SDL_FreeSurface(newsurf);
16341640
return NULL;
16351641
}
16361642
}
16371643

1638-
pgSurface_Unprep(self);
1644+
pgSurface_Unlock(self);
16391645

16401646
final = surf_subtype_new(Py_TYPE(self), newsurf, 1);
16411647
if (!final)

0 commit comments

Comments
 (0)