Skip to content
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

Fix segfault with antialiased draw functions with a depth different than 32bits #3008

Merged
merged 29 commits into from
Sep 24, 2024

Conversation

bilhox
Copy link
Contributor

@bilhox bilhox commented Jul 19, 2024

Closes #3008

This segfault also happens with antialiased functions from pygame.draw.

Needs some tests from other users

@bilhox bilhox requested a review from a team as a code owner July 19, 2024 18:54
@mzivic7 mzivic7 mentioned this pull request Jul 19, 2024
19 tasks
@bilhox
Copy link
Contributor Author

bilhox commented Jul 19, 2024

edit : It doesn't fix gfxdraw side right now as it's meant to be deprecated and be redirected to pygame.draw for all antialiasing functions. This is why I consider it fixes the issue for gfxdraw, see #3005 for more informations.

@yunline yunline added draw pygame.draw bugfix PR that fixes bug labels Jul 20, 2024
Copy link
Contributor

@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 tested locally. The segfault existed before, now it's gone. Visually it was broken before, now it's perfect.

LGTM, thanks for the fix! :)

@damusss damusss added this to the 2.5.1 milestone Jul 22, 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.

I left a few reviews. I'm a bit sceptical of the 24-bit codepath, which is also why I'm suggesting the tests to be stricter here.

test/draw_test.py Show resolved Hide resolved
src_c/draw.c Show resolved Hide resolved
@Starbuck5
Copy link
Member

Starbuck5 commented Jul 26, 2024

This whole thing is a mess.

Firstly, I would expect in a PR like this to see an explanation of what is actually being fixed, why it is segfaulting. Not just that it's fixed and needs tests from other users.

You want to use SDL_GetRGBA correctly? See libsdl-org/SDL#8320 and libsdl-org/SDL@f8dfee0#diff-ec00a797fe34913f3e553761506783ef6b4cd0050704665615c93392ac7360e6R1557-R1618

They mention the performance is terrible "meant only for unittests", so that gets me thinking about performance:

So get_antialiased_color is a performance sensitive function, why is it being called over and over decoding the value of original_color every single time?

And so then this function produces a result to use in set_at, but set_at also calls SDL_GetRGB, but only on 24 bit! So in set_at it's taking a mapped color, unmapping it with SDL_GetRGB, then manually remapping it with bit shifting. Am I reading that right? (EDIT: PR to fix set_at to not call SDL_GetRGB every pixel: #3021)

P.S. maybe we can do our version of SDL_GetRGBA "manually inlined" into our source by grabbing it from the SDL source.

@bilhox
Copy link
Contributor Author

bilhox commented Jul 26, 2024

This whole thing is a mess.

Firstly, I would expect in a PR like this to see an explanation of what is actually being fixed, why it is segfaulting. Not just that it's fixed and needs tests from other users.

You want to use SDL_GetRGBA correctly? See libsdl-org/SDL#8320 and libsdl-org/SDL@f8dfee0#diff-ec00a797fe34913f3e553761506783ef6b4cd0050704665615c93392ac7360e6R1557-R1618

They mention the performance is terrible "meant only for unittests", so that gets me thinking about performance:

So get_antialiased_color is a performance sensitive function, why is it being called over and over decoding the value of original_color every single time?

And so then this function produces a result to use in set_at, but set_at also calls SDL_GetRGB, but only on 24 bit! So in set_at it's taking a mapped color, unmapping it with SDL_GetRGB, then manually remapping it with bit shifting. Am I reading that right? (EDIT: PR to fix set_at to not call SDL_GetRGB every pixel: #3021)

P.S. maybe we can do our version of SDL_GetRGBA "manually inlined" into our source by grabbing it from the SDL source.

Hello, first, thank you for the constructive review !

Sorry, I forgot to mention what was the culprit behind this segfault. As you guessed, the problem resided on how we were extracting pixels in get_antialiased_color in draw.c (extracted as a Uint32 array), leading to read at pixels out of the range of the array.

You suggested me to look at how SDL does the same work with SDL_ReadSurfacePixel, are you specifically talking about 24bit path ?

@Starbuck5
Copy link
Member

You suggested me to look at how SDL does the same work with SDL_ReadSurfacePixel, are you specifically talking about 24bit path ?

So I see you checked out the SDL implementation and put it in, nice! The big endian build is failing though since you have a discrepancy in variable names.

I found another way to get around this problem that may be better. When I looked through draw.c I saw that this is the only location that does any pixel reading, everything else is just doing pixel writing. But there are places in pygame-ce doing pixel reading, how are they doing it? I found in surface.h there's a macro GET_PIXEL that works for all bit depths, that could be a good solution here. Might even be faster, as it doesn't need a memcpy-- although the memcpy might be compiler-optimized-away.

Anyways, I'd be interested in seeing you check it out.

@bilhox
Copy link
Contributor Author

bilhox commented Jul 27, 2024

I checked how it's done on other files, and I saw that they all used a switch case, and manually shift bits for 3 bytes case, and ultimately call GET_PIXELVAL which is a hidden SDL_GetRGBA.

@damusss damusss added the segfault For PRs that fix segfaults or issues containing segfaults which have an higher priority label Jul 27, 2024
@Starbuck5
Copy link
Member

I checked how it's done on other files, and I saw that they all used a switch case, and manually shift bits for 3 bytes case, and ultimately call GET_PIXELVAL which is a hidden SDL_GetRGBA.

Yes, I wasn't saying it avoided calling that function? Just saying that we already have an implementation strategy for calling that function properly, maybe that's the way to go here as well.

@bilhox
Copy link
Contributor Author

bilhox commented Aug 6, 2024

What about adding a new C API function called pg_getPixelValue ? Everywhere they have their own macros.

@Starbuck5 Starbuck5 modified the milestones: 2.5.1, 2.5.2 Aug 7, 2024
@damusss damusss linked an issue Aug 21, 2024 that may be closed by this pull request
@bilhox
Copy link
Contributor Author

bilhox commented Aug 23, 2024

To move this fix as close as possible to being merged. I'm doing some performance tests (just a little skeptical about the perf behind a lot of memcpy calls) which I'll be posting here tomorrow. This will determine if we keep the switch case impl or the memcpy impl.

@bilhox
Copy link
Contributor Author

bilhox commented Aug 24, 2024

My tests on aaline showed me this :
graph_get_pixel_value

red is the switch case implementation and green is the memcpy implementation.
As you can see switch case implementation is faster than memcpy. So I'll be reverting to switch case. (The results for other bpp show the same difference of performance)

@bilhox bilhox requested a review from ankith26 August 25, 2024 17:31
src_c/draw.c Outdated Show resolved Hide resolved
Copy link
Contributor

@MightyJosip MightyJosip left a comment

Choose a reason for hiding this comment

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

Just a question

src_c/draw.c Outdated
SDL_GetRGBA(pixels[(y * surf->w) + x], surf->format, &background_color[0],

Uint32 pixel = 0;
size_t bpp = surf->format->BytesPerPixel;
Copy link
Member

Choose a reason for hiding this comment

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

bpp = PG_SURF_BytesPerPixel(surf); for SDL3 compatibility.

Why is this a size_t?

Copy link
Member

Choose a reason for hiding this comment

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

Nice work changing surf->w to surf->pitch.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Why is this a size_t?

Good question, I can't remember what i was thinking about when I used size_t. Switching to int.

Copy link
Member

Choose a reason for hiding this comment

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

I approved and all, but just for the record you didn't switch to int.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sorry I forgot, now It's updated.

Copy link
Member

@Starbuck5 Starbuck5 left a comment

Choose a reason for hiding this comment

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

Thanks!

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, thanks for the PR 😎

@ankith26 ankith26 merged commit faef6d4 into pygame-community:main Sep 24, 2024
25 checks passed
@bilhox bilhox deleted the draw-antialiased-depth-segfault branch September 28, 2024 08:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bugfix PR that fixes bug draw pygame.draw segfault For PRs that fix segfaults or issues containing segfaults which have an higher priority
Projects
None yet
Development

Successfully merging this pull request may close these issues.

pygame.draw.aaline(⋯) segfault for 24-bit surfaces
6 participants