Skip to content

[Part 2] SDL3: display+window: runtime fixes#3577

Open
ankith26 wants to merge 1 commit intomainfrom
ankith26-sdl3-2
Open

[Part 2] SDL3: display+window: runtime fixes#3577
ankith26 wants to merge 1 commit intomainfrom
ankith26-sdl3-2

Conversation

@ankith26
Copy link
Member

@ankith26 ankith26 commented Sep 1, 2025

  • display index compat code
  • fullscreen compat code unified
  • syncwindow calls so that changes take immediate affect like sdl2

@ankith26 ankith26 requested a review from a team as a code owner September 1, 2025 14:16
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 1, 2025

📝 Walkthrough

Walkthrough

SDL3 compatibility changes: SDL3-aware texture pixel format macro, primary-display queries replacing display 0, PG_SetWindowFullscreen signature adjusted for SDL3 path, and numerous SDL_SyncWindow calls added around window/display operations.

Changes

Cohort / File(s) Summary
Display SDL3 compatibility
src_c/display.c
Added PG_TEXTURE_FMT macro (SDL3: XRGB8888, older: ARGB8888); replaced hardcoded display 0 with SDL_GetPrimaryDisplay() in multiple functions; updated texture creation to use PG_TEXTURE_FMT; changed PG_SetWindowFullscreen return type to bool in SDL3 path; added SDL_SyncWindow calls after various window operations.
Window synchronization
src_c/window.c
Inserted SDL_SyncWindow(window) after window state changes (fullscreen toggles, restore, maximize, minimize, size/position/min/max updates) under SDL_VERSION_ATLEAST(3,0,0) guards.

Sequence Diagram(s)

(Skipped — changes are configuration/compatibility adjustments without a new multi-component sequential feature that requires visualization.)

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

display, window, bugfix

Suggested reviewers

  • Starbuck5
🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title '[Part 2] SDL3: display+window: runtime fixes' accurately describes the main changes in the PR, which focus on SDL3 compatibility and runtime fixes for display and window handling.
Description check ✅ Passed The description is directly related to the changeset, summarizing the key changes: display index compatibility, fullscreen code unification, and SDL_SyncWindow calls for immediate effect.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between c7e9c15 and 9aeca32.

📒 Files selected for processing (3)
  • src_c/_pygame.h (2 hunks)
  • src_c/display.c (31 hunks)
  • src_c/window.c (8 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: Starbuck5
PR: pygame-community/pygame-ce#3573
File: src_c/_camera.c:129-131
Timestamp: 2025-08-30T21:11:00.196Z
Learning: When doing SDL2 to SDL3 compatibility changes, Starbuck5 prefers to maintain exact existing behaviors even when other improvements could be made, focusing solely on the compatibility aspect rather than mixing in behavioral fixes.
📚 Learning: 2025-08-30T21:11:00.196Z
Learnt from: Starbuck5
PR: pygame-community/pygame-ce#3573
File: src_c/_camera.c:129-131
Timestamp: 2025-08-30T21:11:00.196Z
Learning: When doing SDL2 to SDL3 compatibility changes, Starbuck5 prefers to maintain exact existing behaviors even when other improvements could be made, focusing solely on the compatibility aspect rather than mixing in behavioral fixes.

Applied to files:

  • src_c/display.c
🧬 Code graph analysis (1)
src_c/display.c (2)
src_c/base.c (1)
  • pg_GetDefaultWindow (1916-1920)
src_c/_pygame.h (1)
  • PG_GL_SetSwapInterval (384-388)
🔇 Additional comments (22)
src_c/window.c (4)

304-313: LGTM! Proper SDL3 boolean return handling

The conversion to handle SDL3's boolean return value for SDL_SetWindowFullscreen is correct, including the addition of SDL_SyncWindow for immediate effect.


349-349: Good addition of window sync for SDL3

Adding SDL_SyncWindow after fullscreen setup ensures the changes take immediate effect, maintaining consistency with SDL2 behavior.


382-382: Correct type change for SDL3 compatibility

Changing input_only from SDL_bool to int is appropriate since it's now used as a Python boolean via PyArg_ParseTupleAndKeywords.


434-436: Consistent SDL_SyncWindow additions

All the SDL_SyncWindow calls are properly guarded with SDL_VERSION_ATLEAST(3, 0, 0) and placed after window state changes. This ensures immediate synchronization with the windowing system in SDL3.

Based on the learnings provided, this follows the preferred approach of maintaining exact existing behaviors for SDL2 to SDL3 compatibility changes.

Also applies to: 444-446, 454-456, 759-761, 814-816, 857-859, 891-893

src_c/display.c (18)

282-286: Proper null window handling

Good defensive programming - checking if the window exists before accessing its flags prevents potential crashes.


467-472: Correct primary display handling for SDL3

The code properly handles SDL3's primary display API where display ID 0 has special meaning. The error checking when SDL_GetPrimaryDisplay() returns 0 is essential.


813-821: SDL3 boolean return value handling looks correct

The GL attribute setter properly handles SDL3's boolean return convention.


833-841: GL getter boolean handling is correct

The GL attribute getter properly handles SDL3's boolean return convention.


1140-1192: Comprehensive fullscreen wrapper implementation

The PG_SetWindowFullscreen function properly abstracts the differences between SDL2 and SDL3 fullscreen handling, including the boolean return values and window synchronization.


1270-1278: Primary display compatibility for SDL3

Good implementation of backwards compatibility - SDL2's display index 0 is properly mapped to SDL3's primary display.


1483-1491: Display bounds API adaptation

Correctly handles the different return conventions between SDL2 and SDL3 for SDL_GetDisplayUsableBounds.


1580-1584: Consistent use of PG_SetWindowFullscreen wrapper

All fullscreen state changes properly use the new wrapper function, maintaining consistency throughout the codebase.


1642-1661: Swap interval wrapper usage

Good use of the PG_GL_SetSwapInterval wrapper to handle SDL3's boolean return convention for vsync control.


1899-1901: Window synchronization points

All SDL_SyncWindow calls are properly placed after window state changes and correctly guarded with SDL3 version checks.

Also applies to: 2000-2002, 2794-2796


2030-2038: Primary display handling in mode_ok

Consistent implementation of the primary display compatibility layer for SDL3.


2065-2073: Smart compatibility fallback

The code cleverly uses the current display mode when the resolution matches, providing better SDL2 compatibility. This is a nice touch that maintains expected behavior.


2113-2121: Primary display handling in list_modes

Consistent primary display compatibility implementation.


2133-2139: Proper bpp fallback using current mode

Good use of current display mode for bits-per-pixel when not specified, maintaining SDL2 behavior.


2143-2157: Robust empty list handling

Excellent defensive programming - when SDL3 returns an empty display modes list (which SDL2 didn't), the code falls back to using the current mode. This maintains backwards compatibility.


3260-3527: Comprehensive fullscreen toggle implementation

The toggle_fullscreen function properly uses PG_SetWindowFullscreen throughout and adds SDL_SyncWindow at the end for SDL3. The implementation maintains compatibility across different rendering backends (GL, SDL_Renderer, software).


3605-3607: Display resize event synchronization

Proper addition of SDL_SyncWindow after window size changes in the resize event handler.


3841-3844: Message box return value handling

Correctly inverts the return check for SDL3's SDL_ShowMessageBox which returns true on success instead of 0.

@Starbuck5 Starbuck5 added the sdl3 label Sep 1, 2025
@Starbuck5
Copy link
Member

syncwindow calls so that changes take immediate affect like sdl2

I don't feel any particular need to add this, does this impact any of our unit tests? I don't think people are relying on the synchronicity. Plus when we're only on SDL3 I feel like we shouldn't have this.

src_c/display.c Outdated
Comment on lines 1533 to 1582
if (0 != PG_SetWindowFullscreen(
win, sdl_flags & SDL_WINDOW_FULLSCREEN,
non_desktop_fullscreen)) {
if (!PG_SetWindowFullscreen(win,
sdl_flags & SDL_WINDOW_FULLSCREEN,
non_desktop_fullscreen)) {
Copy link
Member

Choose a reason for hiding this comment

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

This block and the SDL2 block below could be consolidated together with PG_SetWindowFullscreen.

Copy link
Member Author

Choose a reason for hiding this comment

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

I think the current code is cleaner. Consolidating this is hard because non_desktop_fullscreen variable is only defined in the SDL3 path - and not on SDL2, whereas SDL_WINDOW_FULLSCREEN_DESKTOP is only defined on SDL2 and not SDL3. The consolidation would require refactoring to the SDL2 codepath, which I have tried to avoid to the best extent in my porting PRs.

@ankith26
Copy link
Member Author

I don't feel any particular need to add this, does this impact any of our unit tests? I don't think people are relying on the synchronicity. Plus when we're only on SDL3 I feel like we shouldn't have this.

Without doing this a lot of our tests fail. I feel like people may rely on synchronicity, especially given that without this call, SDL makes no guarantee how early or late the sync may actually happen (and it may also be a very platform dependent thing). I believe calling this explicitly keeps stuff more inter-platform consistent and is a good thing to do.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
src_c/display.c (4)

915-923: Fix: early-return filters out the very window events you need

As written, the function returns immediately for window events, so resize/maximize/restore handling below never runs.

Apply:

-#if SDL_VERSION_ATLEAST(3, 0, 0)
-    if (event->type >= SDL_EVENT_WINDOW_FIRST &&
-        event->type <= SDL_EVENT_WINDOW_LAST)
-#else
-    if (event->type == SDL_WINDOWEVENT)
-#endif
-    {
-        return 0;
-    }
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+    if (event->type < SDL_EVENT_WINDOW_FIRST ||
+        event->type > SDL_EVENT_WINDOW_LAST) {
+        return 0;
+    }
+#else
+    if (event->type != SDL_WINDOWEVENT) {
+        return 0;
+    }
+#endif

1140-1180: Fullscreen mode selection: pass and validate count from SDL_GetFullscreenDisplayModes

Indexing modes[0] without retrieving count risks OOB when zero modes are returned.

Apply:

-    SDL_DisplayMode **modes = NULL;
+    SDL_DisplayMode **modes = NULL;
+    int nummodes = 0;
@@
-            modes = SDL_GetFullscreenDisplayModes(disp, NULL);
-            if (!modes) {
+            modes = SDL_GetFullscreenDisplayModes(disp, &nummodes);
+            if (!modes || nummodes <= 0) {
                 goto end;
             }
-            chosen_mode = modes[0];
+            chosen_mode = modes[0];

1734-1741: Fix: vsync check inverted for SDL3 renderer

This errors when vsync is actually enabled. Should error when vsync requested but not enabled.

Apply:

-                    if (vsync && has_vsync) {
+                    if (vsync && !has_vsync) {
                         PyErr_SetString(pgExc_SDLError,
                                         "could not enable vsync");
                         _display_state_cleanup(state);
                         goto DESTROY_WINDOW;
                     }

1884-1901: Use window flags, not surface flags, when checking fullscreen

SDL_WINDOW_* flags are properties of SDL_Window; checking surface->surf->flags is incorrect. Fix the check in src_c/display.c:1884-1886:

-#if SDL_VERSION_ATLEAST(3, 0, 0)
-            ((surface->surf->flags & SDL_WINDOW_FULLSCREEN) != 0)) {
-#else
-            ((surface->surf->flags & SDL_WINDOW_FULLSCREEN_DESKTOP) != 0)) {
-#endif
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+            (SDL_GetWindowFlags(win) & SDL_WINDOW_FULLSCREEN)) {
+#else
+            (SDL_GetWindowFlags(win) &
+             (SDL_WINDOW_FULLSCREEN | SDL_WINDOW_FULLSCREEN_DESKTOP))) {
+#endif
🧹 Nitpick comments (2)
src_c/display.c (2)

2290-2294: Minor: ease debugging num displays

Storing the array before freeing clarifies intent and allows NULL checks.

-    int ret = -1;
-    SDL_free(SDL_GetDisplays(&ret));
+    int ret = -1;
+    SDL_DisplayID *disps = SDL_GetDisplays(&ret);
+    SDL_free(disps);

2133-2157: *list_modes: robust empty‑list fallback; prefer sizeof(modes) for clarity (optional nit)

sizeof(SDL_DisplayMode **) is functionally equivalent here but confusing — use sizeof(*modes) to show you're allocating one element.

-        modes = SDL_malloc(sizeof(SDL_DisplayMode **));
+        modes = SDL_malloc(sizeof(*modes));
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9aeca32 and 943f46c.

📒 Files selected for processing (3)
  • src_c/_pygame.h (2 hunks)
  • src_c/display.c (31 hunks)
  • src_c/window.c (8 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src_c/window.c
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: Starbuck5
PR: pygame-community/pygame-ce#3573
File: src_c/_camera.c:129-131
Timestamp: 2025-08-30T21:11:00.240Z
Learning: When doing SDL2 to SDL3 compatibility changes, Starbuck5 prefers to maintain exact existing behaviors even when other improvements could be made, focusing solely on the compatibility aspect rather than mixing in behavioral fixes.
Learnt from: Starbuck5
PR: pygame-community/pygame-ce#3573
File: src_c/_pygame.h:108-133
Timestamp: 2025-09-01T20:18:57.500Z
Learning: In pygame-ce PR #3573, PG_SURF_BitsPerPixel was changed from a simple macro to a static inline function to handle SDL2/SDL3 differences. SDL2's BitsPerPixel includes padding bits (e.g., RGBX => 32) while SDL3's SDL_BITSPERPIXEL excludes padding (e.g., RGBX => 24). The new implementation bridges this gap rather than removing the helper entirely.
📚 Learning: 2025-09-01T20:18:57.500Z
Learnt from: Starbuck5
PR: pygame-community/pygame-ce#3573
File: src_c/_pygame.h:108-133
Timestamp: 2025-09-01T20:18:57.500Z
Learning: In pygame-ce PR #3573, PG_SURF_BitsPerPixel was changed from a simple macro to a static inline function to handle SDL2/SDL3 differences. SDL2's BitsPerPixel includes padding bits (e.g., RGBX => 32) while SDL3's SDL_BITSPERPIXEL excludes padding (e.g., RGBX => 24). The new implementation bridges this gap rather than removing the helper entirely.

Applied to files:

  • src_c/_pygame.h
  • src_c/display.c
📚 Learning: 2025-09-01T20:22:31.010Z
Learnt from: Starbuck5
PR: pygame-community/pygame-ce#3573
File: src_c/_pygame.h:115-126
Timestamp: 2025-09-01T20:22:31.010Z
Learning: In pygame-ce PR #3573, the PG_SURF_BitsPerPixel function implementation uses hardcoded return values of 32 for YUY2/UYVY/YVYU FOURCC formats because it's copying the exact logic from SDL's internal SDL_GetMasksForPixelFormat function. This ensures perfect compatibility between SDL2 and SDL3 behaviors rather than using the technically more accurate values that might be returned by SDL_GetPixelFormatDetails.

Applied to files:

  • src_c/_pygame.h
  • src_c/display.c
📚 Learning: 2025-08-30T21:11:00.240Z
Learnt from: Starbuck5
PR: pygame-community/pygame-ce#3573
File: src_c/_camera.c:129-131
Timestamp: 2025-08-30T21:11:00.240Z
Learning: When doing SDL2 to SDL3 compatibility changes, Starbuck5 prefers to maintain exact existing behaviors even when other improvements could be made, focusing solely on the compatibility aspect rather than mixing in behavioral fixes.

Applied to files:

  • src_c/display.c
🧬 Code graph analysis (1)
src_c/display.c (2)
src_c/base.c (1)
  • pg_GetDefaultWindow (1916-1920)
src_c/_pygame.h (1)
  • PG_GL_SetSwapInterval (384-388)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (17)
  • GitHub Check: dev-check
  • GitHub Check: debug_coverage (ubuntu-24.04, 3.13.5)
  • GitHub Check: msys2 (clang64, clang-x86_64)
  • GitHub Check: debug_coverage (ubuntu-24.04, 3.14.0rc1)
  • GitHub Check: debug_coverage (ubuntu-24.04, 3.9.23)
  • GitHub Check: msys2 (mingw64, x86_64)
  • GitHub Check: msys2 (ucrt64, ucrt-x86_64)
  • GitHub Check: Debian (Bookworm - 12) [armv6]
  • GitHub Check: Debian (Bookworm - 12) [armv7]
  • GitHub Check: Debian (Bookworm - 12) [s390x]
  • GitHub Check: Debian (Bookworm - 12) [ppc64le]
  • GitHub Check: build (windows-latest)
  • GitHub Check: build (macos-14)
  • GitHub Check: build (ubuntu-24.04)
  • GitHub Check: build (ubuntu-22.04)
  • GitHub Check: x86
  • GitHub Check: AMD64
🔇 Additional comments (15)
src_c/_pygame.h (2)

169-170: PG_GL_SetSwapInterval macro in SDL3 path is fine

Mapping directly to SDL3’s boolean-returning SDL_GL_SetSwapInterval matches the bool contract used by call sites.


383-388: Good bool wrapper for SDL2 path — callers verified

rg shows only boolean-appropriate uses in src_c/display.c (lines 1642, 1652, 1661); no int-return semantics remain.

src_c/display.c (13)

282-287: get_active: uses window presence + flags correctly

Checks default window before querying flags; SDL3 logic (not hidden and not minimized) is correct.


467-472: Primary display fallback

Using SDL_GetPrimaryDisplay when no window exists preserves SDL2’s “display 0 = primary” behavior.


805-817: gl_set_attribute: correct SDL3 boolean handling

Raising on false from SDL_GL_SetAttribute aligns with SDL3’s return semantics.


828-843: gl_get_attribute: correct SDL3 boolean handling

Raising on false from SDL_GL_GetAttribute is correct.


1483-1492: Usable bounds: correct SDL3 error check

Returning error when SDL_GetDisplayUsableBounds fails is correct; nice parity with SDL2 branch.


1580-1583: SDL3 fullscreen transition via helper

Switch to PG_SetWindowFullscreen keeps behavior unified across SDL2/SDL3.


1642-1662: OpenGL swap interval: consistent bool API

Adaptive/regular/off paths correctly use the new bool-returning PG_GL_SetSwapInterval.


1999-2003: Sync after window move

SDL_SyncWindow after SDL_SetWindowPosition matches the PR goal of stable, SDL2‑like immediacy.


2030-2038: mode_ok: display 0 → primary mapping

Good SDL2 compatibility shim with proper error propagation if primary retrieval fails.


2794-2797: Sync after minimize

SDL_SyncWindow following SDL_MinimizeWindow improves cross‑platform consistency.


3525-3528: Sync after toggle_fullscreen

Matches the PR’s stated objective; helps tests that assume synchronous state.


3606-3607: Sync after resize event path

Good for eliminating timing flakiness.


3841-3844: message_box: correct SDL3/SDL2 success conditions

Condition inversion between SDL3 (bool) and SDL2 (int) is handled correctly.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src_c/display.c (2)

923-932: Critical: Event watch drops window events and reads wrong union variant (crash risk).

The guard returns early on window events, so the function proceeds for non-window events and then accesses event->window.*, which is UB/crash. Invert the condition.

-#if SDL_VERSION_ATLEAST(3, 0, 0)
-    if (event->type >= SDL_EVENT_WINDOW_FIRST &&
-        event->type <= SDL_EVENT_WINDOW_LAST)
-#else
-    if (event->type == SDL_WINDOWEVENT)
-#endif
-    {
-        return 0;
-    }
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+    if (!(event->type >= SDL_EVENT_WINDOW_FIRST &&
+          event->type <= SDL_EVENT_WINDOW_LAST))
+#else
+    if (event->type != SDL_WINDOWEVENT)
+#endif
+    {
+        return 0;
+    }

3595-3607: Bug: SDL3 bool return misinterpreted; always errors on success.

SDL_SetRenderLogicalPresentation returns bool (true=success) on SDL3, but the code treats non‑zero as error.

-        result = SDL_SetRenderLogicalPresentation(pg_renderer, w, h, mode);
+        result = SDL_SetRenderLogicalPresentation(pg_renderer, w, h, mode);
 ...
-        if (result != 0) {
+        if (!result) {
             return RAISE(pgExc_SDLError, SDL_GetError());
         }
🧹 Nitpick comments (3)
src_c/display.c (3)

1147-1187: Fullscreen helper looks good; add minor robustness.

Logic and SDL_SyncWindow usage LGTM. Minor: consider passing and checking the count from SDL_GetFullscreenDisplayModes to avoid relying on modes[0] when APIs change.


1741-1748: Check return value of SDL_GetRenderVSync (SDL3).

If SDL_GetRenderVSync fails, has_vsync is undefined. Handle the failure path for clearer errors.

-                    SDL_GetRenderVSync(pg_renderer, &has_vsync);
+                    if (!SDL_GetRenderVSync(pg_renderer, &has_vsync)) {
+                        return RAISE(pgExc_SDLError, SDL_GetError());
+                    }

2141-2165: Nit: use correct sizeof for clarity.

Allocating fallback array with sizeof(SDL_DisplayMode **) is confusing (even if same size). Prefer sizeof(SDL_DisplayMode *) or sizeof(*modes).

-        modes = SDL_malloc(sizeof(SDL_DisplayMode **));
+        modes = SDL_malloc(sizeof(SDL_DisplayMode *));
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 943f46c and 46989b4.

📒 Files selected for processing (3)
  • src_c/_pygame.h (2 hunks)
  • src_c/display.c (40 hunks)
  • src_c/window.c (8 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src_c/_pygame.h
  • src_c/window.c
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: Starbuck5
PR: pygame-community/pygame-ce#3573
File: src_c/_camera.c:129-131
Timestamp: 2025-08-30T21:11:00.240Z
Learning: When doing SDL2 to SDL3 compatibility changes, Starbuck5 prefers to maintain exact existing behaviors even when other improvements could be made, focusing solely on the compatibility aspect rather than mixing in behavioral fixes.
Learnt from: Starbuck5
PR: pygame-community/pygame-ce#3573
File: src_c/_pygame.h:108-133
Timestamp: 2025-09-01T20:18:57.500Z
Learning: In pygame-ce PR #3573, PG_SURF_BitsPerPixel was changed from a simple macro to a static inline function to handle SDL2/SDL3 differences. SDL2's BitsPerPixel includes padding bits (e.g., RGBX => 32) while SDL3's SDL_BITSPERPIXEL excludes padding (e.g., RGBX => 24). The new implementation bridges this gap rather than removing the helper entirely.
📚 Learning: 2025-09-01T20:18:57.500Z
Learnt from: Starbuck5
PR: pygame-community/pygame-ce#3573
File: src_c/_pygame.h:108-133
Timestamp: 2025-09-01T20:18:57.500Z
Learning: In pygame-ce PR #3573, PG_SURF_BitsPerPixel was changed from a simple macro to a static inline function to handle SDL2/SDL3 differences. SDL2's BitsPerPixel includes padding bits (e.g., RGBX => 32) while SDL3's SDL_BITSPERPIXEL excludes padding (e.g., RGBX => 24). The new implementation bridges this gap rather than removing the helper entirely.

Applied to files:

  • src_c/display.c
📚 Learning: 2025-08-30T21:11:00.240Z
Learnt from: Starbuck5
PR: pygame-community/pygame-ce#3573
File: src_c/_camera.c:129-131
Timestamp: 2025-08-30T21:11:00.240Z
Learning: When doing SDL2 to SDL3 compatibility changes, Starbuck5 prefers to maintain exact existing behaviors even when other improvements could be made, focusing solely on the compatibility aspect rather than mixing in behavioral fixes.

Applied to files:

  • src_c/display.c
📚 Learning: 2025-09-01T20:22:31.010Z
Learnt from: Starbuck5
PR: pygame-community/pygame-ce#3573
File: src_c/_pygame.h:115-126
Timestamp: 2025-09-01T20:22:31.010Z
Learning: In pygame-ce PR #3573, the PG_SURF_BitsPerPixel function implementation uses hardcoded return values of 32 for YUY2/UYVY/YVYU FOURCC formats because it's copying the exact logic from SDL's internal SDL_GetMasksForPixelFormat function. This ensures perfect compatibility between SDL2 and SDL3 behaviors rather than using the technically more accurate values that might be returned by SDL_GetPixelFormatDetails.

Applied to files:

  • src_c/display.c
🧬 Code graph analysis (1)
src_c/display.c (2)
src_c/base.c (1)
  • pg_GetDefaultWindow (1916-1920)
src_c/_pygame.h (1)
  • PG_GL_SetSwapInterval (409-413)
🪛 GitHub Actions: Build WASM (Emscripten)
src_c/display.c

[error] 958-958: use of undeclared identifier 'PG_TEXTURE_FMT' in SDL_CreateTexture call


[error] 1778-1778: use of undeclared identifier 'PG_TEXTURE_FMT' in SDL_CreateTexture call


[error] 3297-3297: use of undeclared identifier 'PG_TEXTURE_FMT' in SDL_CreateTexture call


[error] 3450-3450: use of undeclared identifier 'PG_TEXTURE_FMT' in SDL_CreateTexture call

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (17)
  • GitHub Check: arm64
  • GitHub Check: build (ubuntu-22.04)
  • GitHub Check: debug_coverage (ubuntu-24.04, 3.14.0rc1)
  • GitHub Check: build (macos-14)
  • GitHub Check: debug_coverage (ubuntu-24.04, 3.9.23)
  • GitHub Check: i686
  • GitHub Check: Debian (Bookworm - 12) [armv6]
  • GitHub Check: aarch64
  • GitHub Check: x86
  • GitHub Check: Debian (Bookworm - 12) [s390x]
  • GitHub Check: x86_64
  • GitHub Check: AMD64
  • GitHub Check: Debian (Bookworm - 12) [ppc64le]
  • GitHub Check: msys2 (clang64, clang-x86_64)
  • GitHub Check: Debian (Bookworm - 12) [armv7]
  • GitHub Check: msys2 (ucrt64, ucrt-x86_64)
  • GitHub Check: dev-check
🔇 Additional comments (3)
src_c/display.c (3)

1277-1285: Primary-display compatibility for display==0 is correct.

Nice SDL2→SDL3 behavior parity using SDL_GetPrimaryDisplay() and consistent error handling.

Also applies to: 2037-2045, 2121-2128


1587-1595: Unified fullscreen calls + explicit SDL_SyncWindow: good for cross‑platform determinism.

Replacing raw SDL_SetWindowFullscreen with PG_SetWindowFullscreen and syncing state matches the PR goal and should stabilize tests.

Please run window/fullscreen unit tests on macOS, Windows, X11, and Wayland to confirm no regressions in toggle_fullscreen edge cases (renderer software fallback paths).

Also applies to: 3267-3270, 3281-3284, 3321-3334, 3357-3360, 3390-3394, 3425-3433, 3463-3466, 3488-3492, 3517-3519, 3532-3534


290-295: Small SDL3 porting wins.

  • get_active: early-return False when no default window — sane.
  • Added SDL_SyncWindow after set_window_position, iconify, and post set_mode — aligns with the PR’s sync intent.

Also applies to: 2007-2009, 2802-2804, 1907-1909

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src_c/window.c (1)

462-484: Return semantics mismatch with caller (window_set_modal_for).

PG_SetWindowModalFor returns 0 on success and -1 on failure here; window_set_modal_for treats 0 as failure. That will raise on success.

Apply:

-    if (!PG_SetWindowModalFor(self->_win, ((pgWindowObject *)arg)->_win)) {
+    if (PG_SetWindowModalFor(self->_win, ((pgWindowObject *)arg)->_win) < 0) {
         return RAISE(pgExc_SDLError, SDL_GetError());
     }

Also consider changing PG_SetWindowModalFor to return bool for SDL3 and int for SDL2 only at the boundary, but keep callers checking “< 0” to avoid future drift.

src_c/display.c (1)

1886-1895: Bug: checking surface flags for a window flag.

You’re testing surface->surf->flags against SDL_WINDOW_FULLSCREEN. That’s a window flag, not a surface flag; the condition won’t work reliably.

Apply:

-        if (((surface->surf->w != w_actual) ||
-             (surface->surf->h != h_actual)) &&
-#if SDL_VERSION_ATLEAST(3, 0, 0)
-            ((surface->surf->flags & SDL_WINDOW_FULLSCREEN) != 0)) {
-#else
-            ((surface->surf->flags & SDL_WINDOW_FULLSCREEN_DESKTOP) != 0)) {
-#endif
+        if (((surface->surf->w != w_actual) ||
+             (surface->surf->h != h_actual))) {
+            SDL_Window *w_ = pg_GetDefaultWindow();
+            SDL_WindowFlags wf = w_ ? SDL_GetWindowFlags(w_) : 0;
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+            if ((wf & SDL_WINDOW_FULLSCREEN) != 0) {
+#else
+            if ((wf & (SDL_WINDOW_FULLSCREEN | SDL_WINDOW_FULLSCREEN_DESKTOP)) != 0) {
+#endif

…then keep the warning body unchanged and close the extra brace after it.

🧹 Nitpick comments (4)
src_c/window.c (2)

304-309: Success check and explicit sync look correct; prefer bool literals over 0.

Logic matches SDL3 bool returns; tiny readability nit: pass false instead of 0.

Apply:

-    if (!SDL_SetWindowFullscreen(self->_win, 0)) {
+    if (!SDL_SetWindowFullscreen(self->_win, false)) {

342-353: Consider setting fullscreen mode before enabling fullscreen.

On some backends, setting the mode first avoids a mode “flash.” Safe either way, but this order tends to be smoother.

Apply:

-    if (!SDL_SetWindowFullscreen(window, 1)) {
-        goto end;
-    }
-    if (!SDL_SetWindowFullscreenMode(window, chosen_mode)) {
+    if (!SDL_SetWindowFullscreenMode(window, chosen_mode)) {
         goto end;
     }
+    if (!SDL_SetWindowFullscreen(window, true)) {
+        goto end;
+    }
     SDL_SyncWindow(window);
     ret = 1;
src_c/display.c (2)

2149-2164: Minor allocation type nit.

When fabricating a one-element modes array, allocate sizeof(SDL_DisplayMode*) not sizeof(SDL_DisplayMode**).

Apply:

-        modes = SDL_malloc(sizeof(SDL_DisplayMode **));
+        modes = SDL_malloc(sizeof(SDL_DisplayMode *));

1907-1908: Optional: wrap syncs to reduce repetition.

Add a tiny helper/macro, no behavior change:

Example (pgcompat.h):

#if SDL_VERSION_ATLEAST(3,0,0)
#define PG_SyncWindow(win) SDL_SyncWindow(win)
#else
#define PG_SyncWindow(win) ((void)0)
#endif

Then replace SDL_SyncWindow(...) callsites with PG_SyncWindow(...).

Also applies to: 2008-2009, 3531-3535, 3613-3615

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 46989b4 and 98e2ace.

📒 Files selected for processing (3)
  • src_c/_pygame.h (2 hunks)
  • src_c/display.c (41 hunks)
  • src_c/window.c (8 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src_c/_pygame.h
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: Starbuck5
PR: pygame-community/pygame-ce#3573
File: src_c/_camera.c:129-131
Timestamp: 2025-08-30T21:11:00.240Z
Learning: When doing SDL2 to SDL3 compatibility changes, Starbuck5 prefers to maintain exact existing behaviors even when other improvements could be made, focusing solely on the compatibility aspect rather than mixing in behavioral fixes.
Learnt from: Starbuck5
PR: pygame-community/pygame-ce#3573
File: src_c/_pygame.h:108-133
Timestamp: 2025-09-01T20:18:57.500Z
Learning: In pygame-ce PR #3573, PG_SURF_BitsPerPixel was changed from a simple macro to a static inline function to handle SDL2/SDL3 differences. SDL2's BitsPerPixel includes padding bits (e.g., RGBX => 32) while SDL3's SDL_BITSPERPIXEL excludes padding (e.g., RGBX => 24). The new implementation bridges this gap rather than removing the helper entirely.
📚 Learning: 2025-09-01T20:18:57.500Z
Learnt from: Starbuck5
PR: pygame-community/pygame-ce#3573
File: src_c/_pygame.h:108-133
Timestamp: 2025-09-01T20:18:57.500Z
Learning: In pygame-ce PR #3573, PG_SURF_BitsPerPixel was changed from a simple macro to a static inline function to handle SDL2/SDL3 differences. SDL2's BitsPerPixel includes padding bits (e.g., RGBX => 32) while SDL3's SDL_BITSPERPIXEL excludes padding (e.g., RGBX => 24). The new implementation bridges this gap rather than removing the helper entirely.

Applied to files:

  • src_c/display.c
📚 Learning: 2025-08-30T21:11:00.240Z
Learnt from: Starbuck5
PR: pygame-community/pygame-ce#3573
File: src_c/_camera.c:129-131
Timestamp: 2025-08-30T21:11:00.240Z
Learning: When doing SDL2 to SDL3 compatibility changes, Starbuck5 prefers to maintain exact existing behaviors even when other improvements could be made, focusing solely on the compatibility aspect rather than mixing in behavioral fixes.

Applied to files:

  • src_c/display.c
📚 Learning: 2025-09-01T20:22:31.010Z
Learnt from: Starbuck5
PR: pygame-community/pygame-ce#3573
File: src_c/_pygame.h:115-126
Timestamp: 2025-09-01T20:22:31.010Z
Learning: In pygame-ce PR #3573, the PG_SURF_BitsPerPixel function implementation uses hardcoded return values of 32 for YUY2/UYVY/YVYU FOURCC formats because it's copying the exact logic from SDL's internal SDL_GetMasksForPixelFormat function. This ensures perfect compatibility between SDL2 and SDL3 behaviors rather than using the technically more accurate values that might be returned by SDL_GetPixelFormatDetails.

Applied to files:

  • src_c/display.c
🧬 Code graph analysis (1)
src_c/display.c (2)
src_c/base.c (1)
  • pg_GetDefaultWindow (1916-1920)
src_c/_pygame.h (1)
  • PG_GL_SetSwapInterval (409-413)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (16)
  • GitHub Check: build (ubuntu-22.04)
  • GitHub Check: build (windows-latest)
  • GitHub Check: build (macos-14)
  • GitHub Check: aarch64
  • GitHub Check: i686
  • GitHub Check: x86_64
  • GitHub Check: Debian (Bookworm - 12) [s390x]
  • GitHub Check: Debian (Bookworm - 12) [ppc64le]
  • GitHub Check: Debian (Bookworm - 12) [armv7]
  • GitHub Check: Debian (Bookworm - 12) [armv6]
  • GitHub Check: x86
  • GitHub Check: AMD64
  • GitHub Check: debug_coverage (ubuntu-24.04, 3.9.23)
  • GitHub Check: debug_coverage (ubuntu-24.04, 3.13.5)
  • GitHub Check: debug_coverage (ubuntu-24.04, 3.14.0rc1)
  • GitHub Check: dev-check
🔇 Additional comments (13)
src_c/window.c (2)

382-382: Type change is fine.

Using int for input_only aligns with PyArg “p” parsing.


434-436: Good call: SDL_SyncWindow after state changes for SDL3.

This matches the PR goal to restore SDL2-like immediacy and stabilizes tests.

Please confirm there’s no measurable perf regression in hot UI loops (maximize/minimize/resize spam). If needed, we can gate sync behind a hint.

Also applies to: 444-446, 454-456, 759-761, 813-817, 858-860, 891-893

src_c/display.c (11)

102-109: PG_TEXTURE_FMT placement fixed; thanks.

Defining it outside BUILD_STATIC resolves the WASM build issue noted earlier.


290-295: Early-return when no default window: good behavior.

Avoids querying flags on null windows.


821-829: SDL3 bool return handling is correct.

Negated check matches SDL3’s true-on-success contract.


841-850: SDL3 gl-get attribute check correct.

Negated check on bool return is right; parity with SDL2 branch maintained.


1277-1285: Primary-display compatibility is spot on.

Mapping index 0 to SDL_GetPrimaryDisplay preserves SDL2 semantics.


1490-1493: SDL3 bool-return check looks right.

Using “!SDL_GetDisplayUsableBounds” for error makes sense; good consistency across code.


1587-1591: Unified fullscreen helper usage is good; preserves ordering constraints.

Using PG_SetWindowFullscreen here centralizes behavior differences and syncs state.


1741-1749: Renderer vsync verification: good failure path.

Clear error when vsync can’t be enabled; consistent with GL path.


1777-1780: Use of PG_TEXTURE_FMT is consistent.

Nice consolidation across creation sites.

Also applies to: 958-960, 3297-3299, 3450-3452


1907-1908: SDL_SyncWindow after mutations: aligns behavior with SDL2 and stabilizes tests.

Good call to make effects observable immediately on SDL3.

Also applies to: 2008-2009, 2801-2804, 3531-3535, 3613-3615


3848-3852: SDL3 success condition inversion handled correctly.

Inverting the SDL_ShowMessageBox check for SDL3 is required; nice.

Comment on lines 924 to 928
if (!(event->type >= SDL_EVENT_WINDOW_FIRST &&
event->type <= SDL_EVENT_WINDOW_LAST))
#else
if (event->type == SDL_WINDOWEVENT)
if (event->type != SDL_WINDOWEVENT)
#endif
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

🧩 Analysis chain

Verify SDL3 window event constants.

You gate on SDL_EVENT_WINDOW_* range but still compare against SDL_WINDOWEVENT_* constants. SDL3 prefers SDL_EVENT_WINDOW_RESIZED/… symbols.

Also applies to: 943-949, 966-972, 973-981, 986-994, 1016-1026


🌐 Web query:

In SDL3, what are the correct event constants for window resize/maximize/restore, and are SDL_WINDOWEVENT_* still defined or should SDL_EVENT_WINDOW_* be used?

💡 Result:

Short answer: use the SDL_EVENT_WINDOW_* top-level event constants. SDL_WINDOWEVENT (and the SDL_WINDOWEVENT_* grouped enum style) was removed in SDL3 — window events were promoted to top-level event types.

Relevant names you’ll use (examples):

  • SDL_EVENT_WINDOW_RESIZED
  • SDL_EVENT_WINDOW_MAXIMIZED
  • SDL_EVENT_WINDOW_RESTORED
  • (also: SDL_EVENT_WINDOW_MINIMIZED, SDL_EVENT_WINDOW_FOCUS_GAINED/LOST, SDL_EVENT_WINDOW_MOVED, SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED, etc.)

You can still detect “is a window event?” by testing event.type against the window range (SDL_EVENT_WINDOW_FIRST .. SDL_EVENT_WINDOW_LAST) or by checking the SDL_WindowEvent.type which is one of SDL_EVENT_WINDOW_*. See the SDL3 migration notes and the SDL_WindowEvent docs. [1][2]

Sources:

  • SDL3 migration notes (window events moved to top-level). [1]
  • SDL3 SDL_WindowEvent / event reference (shows SDL_EVENT_WINDOW_* names). [2]

Replace SDL_WINDOWEVENT checks with SDL_EVENT_WINDOW_ (SDL3)*

SDL3 removed SDL_WINDOWEVENT; use top-level SDL_EVENT_WINDOW_* constants (e.g. SDL_EVENT_WINDOW_RESIZED / SDL_EVENT_WINDOW_MAXIMIZED / SDL_EVENT_WINDOW_RESTORED) or test event.type against SDL_EVENT_WINDOW_FIRST..SDL_EVENT_WINDOW_LAST. Update the conditional branches accordingly.

Locations: src_c/display.c — lines 924-928, 943-949, 966-972, 973-981, 986-994, 1016-1026

🤖 Prompt for AI Agents
In src_c/display.c around lines 924-928, 943-949, 966-972, 973-981, 986-994 and
1016-1026, the code still checks for the SDL2-specific SDL_WINDOWEVENT constant;
SDL3 removed that symbol. Replace those checks by either testing event->type
against the SDL3 window-event range (SDL_EVENT_WINDOW_FIRST ..
SDL_EVENT_WINDOW_LAST) or by matching against specific SDL_EVENT_WINDOW_*
constants (e.g. SDL_EVENT_WINDOW_RESIZED, SDL_EVENT_WINDOW_MAXIMIZED,
SDL_EVENT_WINDOW_RESTORED) as appropriate for each branch; update each
conditional to use the range test or the explicit SDL_EVENT_WINDOW_* constants
and adjust any switch/case or handling logic to use SDL3 names so the conditions
correctly detect window events under SDL3.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In @src_c/display.c:
- Around line 102-113: The SDL version guard for PG_TEXTURE_FMT currently checks
SDL_VERSION_ATLEAST(3, 0, 0) but must be raised to SDL_VERSION_ATLEAST(3, 2, 0)
so the macro aligns with the stable SDL3 release that exposes APIs like
SDL_SyncWindow and SDL_GetPrimaryDisplay; update the #if/#else preprocessor
condition around PG_TEXTURE_FMT (the block defining PG_TEXTURE_FMT and its
SDL_PIXELFORMAT_XRGB8888/ARGB8888 choices) to use 3,2,0 instead of 3,0,0.
- Around line 2072-2079: The checks using SDL_BITSPERPIXEL(curmode->format) and
SDL_BITSPERPIXEL(closest.format) must use the established compatibility logic
(PG_SURF_BitsPerPixel) or, if you allocate an SDL_PixelFormat via
SDL_AllocFormat to compute bits, ensure you free it; update the code around
curmode and closest to either call PG_SURF_BitsPerPixel(format) instead of
SDL_BITSPERPIXEL on the raw enum, or if you keep SDL_AllocFormat(…) use a
tempformat variable exactly like other uses in display.c, call
PG_SURF_BitsPerPixel(tempformat) to get the bpp, then call
SDL_FreeFormat(tempformat) immediately after extracting the value (and handle
allocation failure consistently).
🧹 Nitpick comments (2)
src_c/display.c (2)

1151-1191: SDL3 fullscreen helper looks reasonable; note that SDL_SyncWindow() can timeout but return value is consistently unhandled throughout the codebase

The "pick first fullscreen mode when non-desktop fullscreen" approach is consistent with sdl2-compat-style behavior. While SDL_SyncWindow() can fail by timing out (returns false), every call to it in the codebase ignores the return value without exception, so this function's pattern aligns with established practice even though it has error checking elsewhere.


2007-2009: SDL3 SDL_SetWindowPosition() does return bool, but error handling is a behavioral change

Confirmed: SDL3's SDL_SetWindowPosition() returns bool (true on success, false on failure), and SDL_GetError() provides error details. However, the proposed error handling would add new exception-raising behavior during what should be a pure compatibility port. The existing comment "Will raise errors with SDL 3, deal with it during the porting" suggests deferring comprehensive error handling. If adding this check, consider whether raising an exception aligns with the porting strategy, or if silently handling the failure is preferred for now.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f98e444 and dbe3b58.

📒 Files selected for processing (2)
  • src_c/display.c
  • src_c/window.c
🚧 Files skipped from review as they are similar to previous changes (1)
  • src_c/window.c
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: Starbuck5
Repo: pygame-community/pygame-ce PR: 3573
File: src_c/_camera.c:129-131
Timestamp: 2025-08-30T21:11:00.240Z
Learning: When doing SDL2 to SDL3 compatibility changes, Starbuck5 prefers to maintain exact existing behaviors even when other improvements could be made, focusing solely on the compatibility aspect rather than mixing in behavioral fixes.
Learnt from: Starbuck5
Repo: pygame-community/pygame-ce PR: 3573
File: src_c/_pygame.h:108-133
Timestamp: 2025-09-01T20:18:57.500Z
Learning: In pygame-ce PR #3573, PG_SURF_BitsPerPixel was changed from a simple macro to a static inline function to handle SDL2/SDL3 differences. SDL2's BitsPerPixel includes padding bits (e.g., RGBX => 32) while SDL3's SDL_BITSPERPIXEL excludes padding (e.g., RGBX => 24). The new implementation bridges this gap rather than removing the helper entirely.
Learnt from: Starbuck5
Repo: pygame-community/pygame-ce PR: 3573
File: src_c/_pygame.h:115-126
Timestamp: 2025-09-01T20:22:31.010Z
Learning: In pygame-ce PR #3573, the PG_SURF_BitsPerPixel function implementation uses hardcoded return values of 32 for YUY2/UYVY/YVYU FOURCC formats because it's copying the exact logic from SDL's internal SDL_GetMasksForPixelFormat function. This ensures perfect compatibility between SDL2 and SDL3 behaviors rather than using the technically more accurate values that might be returned by SDL_GetPixelFormatDetails.
Learnt from: damusss
Repo: pygame-community/pygame-ce PR: 3219
File: src_c/window.c:11-13
Timestamp: 2025-10-17T08:53:32.449Z
Learning: SDL3's first stable release is version 3.2.0 (released January 21, 2025). SDL 3.0.x and 3.1.x were preview/pre-release versions not intended for production use, so version checks for SDL3 features in pygame-ce should use SDL_VERSION_ATLEAST(3, 2, 0) to target the first stable release.
📚 Learning: 2025-09-01T20:18:57.500Z
Learnt from: Starbuck5
Repo: pygame-community/pygame-ce PR: 3573
File: src_c/_pygame.h:108-133
Timestamp: 2025-09-01T20:18:57.500Z
Learning: In pygame-ce PR #3573, PG_SURF_BitsPerPixel was changed from a simple macro to a static inline function to handle SDL2/SDL3 differences. SDL2's BitsPerPixel includes padding bits (e.g., RGBX => 32) while SDL3's SDL_BITSPERPIXEL excludes padding (e.g., RGBX => 24). The new implementation bridges this gap rather than removing the helper entirely.

Applied to files:

  • src_c/display.c
📚 Learning: 2025-08-30T21:11:00.240Z
Learnt from: Starbuck5
Repo: pygame-community/pygame-ce PR: 3573
File: src_c/_camera.c:129-131
Timestamp: 2025-08-30T21:11:00.240Z
Learning: When doing SDL2 to SDL3 compatibility changes, Starbuck5 prefers to maintain exact existing behaviors even when other improvements could be made, focusing solely on the compatibility aspect rather than mixing in behavioral fixes.

Applied to files:

  • src_c/display.c
📚 Learning: 2025-09-01T20:22:31.010Z
Learnt from: Starbuck5
Repo: pygame-community/pygame-ce PR: 3573
File: src_c/_pygame.h:115-126
Timestamp: 2025-09-01T20:22:31.010Z
Learning: In pygame-ce PR #3573, the PG_SURF_BitsPerPixel function implementation uses hardcoded return values of 32 for YUY2/UYVY/YVYU FOURCC formats because it's copying the exact logic from SDL's internal SDL_GetMasksForPixelFormat function. This ensures perfect compatibility between SDL2 and SDL3 behaviors rather than using the technically more accurate values that might be returned by SDL_GetPixelFormatDetails.

Applied to files:

  • src_c/display.c
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
  • GitHub Check: arm64
  • GitHub Check: msys2 (clang64, clang-x86_64)
  • GitHub Check: Pyodide build
  • GitHub Check: msys2 (ucrt64, ucrt-x86_64)
  • GitHub Check: msys2 (mingw64, x86_64)
  • GitHub Check: dev-check
  • GitHub Check: build (ubuntu-22.04)
  • GitHub Check: x86
  • GitHub Check: AMD64
  • GitHub Check: build (ubuntu-24.04)
  • GitHub Check: build (macos-14)
  • GitHub Check: Debian (Bookworm - 12) [ppc64le]
  • GitHub Check: Debian (Bookworm - 12) [s390x]
  • GitHub Check: Debian (Bookworm - 12) [armv7]
  • GitHub Check: Debian (Bookworm - 12) [armv6]
  • GitHub Check: debug_coverage (ubuntu-24.04, 3.14.0)
  • GitHub Check: debug_coverage (ubuntu-24.04, 3.10.17)
  • GitHub Check: i686
  • GitHub Check: aarch64
  • GitHub Check: x86_64
🔇 Additional comments (9)
src_c/display.c (9)

480-486: Primary display query for SDL3 is a good defensive fix

  • Returning an SDL error when SDL_GetPrimaryDisplay() fails is preferable to silently assuming display 0.

963-965: Good follow-through: texture recreation uses PG_TEXTURE_FMT

  • Keeps the renderer texture format consistent across resize paths.

1782-1785: Good: initial renderer texture creation uses PG_TEXTURE_FMT


1906-1908: Reasonable: SDL_SyncWindow(win) after set_mode to match SDL2-like immediacy
Given the PR’s stated goal (test/runtime stability across platforms), this makes sense.


2801-2803: Reasonable: sync after SDL_MinimizeWindow() on SDL3
Matches the goal of making state changes take effect immediately.


3297-3299: Good: texture recreation during fullscreen toggles uses PG_TEXTURE_FMT
Keeps pixel-format expectations consistent across renderer rebuild paths.

Also applies to: 3450-3452


3531-3533: Reasonable: sync after fullscreen toggling


3612-3614: Reasonable: sync after resize-event handling


2140-2164: [rewritten comment]
[classification tag]

Comment on lines +102 to +113
/* This macro defines the pixel format we use for pg_renderer.
* On our SDL2 code we used to use SDL_PIXELFORMAT_ARGB8888 but on SDL3 it
* gives incorrect results because it includes alpha when we don't need it, so
* we use the XRGB variant.
* TODO: can consider using XRGB unconditionally on both SDL2 and SDL3
* codepaths
*/
#if SDL_VERSION_ATLEAST(3, 0, 0)
#define PG_TEXTURE_FMT SDL_PIXELFORMAT_XRGB8888
#else
#define PG_TEXTURE_FMT SDL_PIXELFORMAT_ARGB8888
#endif
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

SDL3 SDL_SyncWindow SDL_GetPrimaryDisplay API availability version history

💡 Result:

Both functions are part of SDL3 (header <SDL3/SDL_video.h>) and were introduced in the SDL 3.2.0 series:

  • SDL_GetPrimaryDisplay — available since SDL 3.2.0. [1]
  • SDL_SyncWindow — available since SDL 3.2.0. [2][3]

Sources:
[1] SDL_GetPrimaryDisplay manpage (libsdl3-doc / Debian).
[2] SDL_SyncWindow — SDL Wiki.
[3] SDL_SyncWindow manpage (SDL3-devel-doc / openSUSE).


Align version guard to stable SDL3 release (3.2.0) since key APIs are only available from that version onward

The PG_TEXTURE_FMT abstraction is clean, but switching from SDL_VERSION_ATLEAST(3, 0, 0) to SDL_VERSION_ATLEAST(3, 2, 0) is important: SDL_SyncWindow and SDL_GetPrimaryDisplay (used in other sections of this file) are only available starting in SDL 3.2.0, which is the first stable SDL3 release. Aligning this guard ensures consistency across the module and prevents accidental use of preview SDK features.

🤖 Prompt for AI Agents
In @src_c/display.c around lines 102 - 113, The SDL version guard for
PG_TEXTURE_FMT currently checks SDL_VERSION_ATLEAST(3, 0, 0) but must be raised
to SDL_VERSION_ATLEAST(3, 2, 0) so the macro aligns with the stable SDL3 release
that exposes APIs like SDL_SyncWindow and SDL_GetPrimaryDisplay; update the
#if/#else preprocessor condition around PG_TEXTURE_FMT (the block defining
PG_TEXTURE_FMT and its SDL_PIXELFORMAT_XRGB8888/ARGB8888 choices) to use 3,2,0
instead of 3,0,0.

Comment on lines +1282 to +1290
#if SDL_VERSION_ATLEAST(3, 0, 0)
/* In SDL2, display == 0 meant primary display, so compat code for it */
if (display == 0) {
display = SDL_GetPrimaryDisplay();
if (display == 0) {
return RAISE(pgExc_SDLError, SDL_GetError());
}
}
#endif
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

SDL3 display ID semantics stable sequential _GetDisplays documentation

💡 Result:

Short answer: SDL display IDs are unique instance IDs for the lifetime of the application but not a guaranteed dense/sequential index. They are stable while the display remains connected (and never reused during the app run), but they can change if a display is disconnected/reconnected or between separate program launches. The SDL docs do not promise any particular ordering for the array returned by SDL_GetDisplays. References: [1], [2].

Sources

  • SDL_DisplayID: "unique ID for a display for the time it is connected to the system, and is never reused for the lifetime of the application; If the display is disconnected and reconnected, it will get a new ID." [1]
  • SDL_GetDisplays: function docs (returns a NULL-terminated array of SDL_DisplayID; no ordering/sequence guarantee stated). [2]

[1] SDL_DisplayID docs.
[2] SDL_GetDisplays docs.


🏁 Script executed:

# First, let's understand the context - search for display-related code in the file
cd src_c && grep -n "display" display.c | head -30

Repository: pygame-community/pygame-ce

Length of output: 1506


🏁 Script executed:

# Check for display parameter documentation in pygame-ce
rg -n "display.*=" src_c/display.c -B 2 -A 2 | head -50

Repository: pygame-community/pygame-ce

Length of output: 2058


🏁 Script executed:

# Look for SDL2 display index handling to understand the original behavior
rg -n "SDL2.*display" src_c/ -i --type c

Repository: pygame-community/pygame-ce

Length of output: 363


🏁 Script executed:

# Check if there's documentation about display parameters in the codebase
rg -n "display.*index\|display.*id" src_c/ --type c -i | head -20

Repository: pygame-community/pygame-ce

Length of output: 52


🏁 Script executed:

# View the full function context around lines 1282-1290
sed -n '1270,1310p' src_c/display.c

Repository: pygame-community/pygame-ce

Length of output: 1270


🏁 Script executed:

# Find the function name and full implementation
sed -n '1250,1320p' src_c/display.c | cat -n

Repository: pygame-community/pygame-ce

Length of output: 2640


🏁 Script executed:

# Search for other display parameter handling in display.c to see if indices are converted
rg -n "display_id\|SDL_GetDisplays\|display.*index" src_c/display.c -A 3 -B 1

Repository: pygame-community/pygame-ce

Length of output: 52


🏁 Script executed:

# Check if there's any documentation about display parameter semantics in pygame
rg -n "display.*parameter\|display.*index\|display.*id" src_c/doc/ --type c --type txt 2>/dev/null | head -20

Repository: pygame-community/pygame-ce

Length of output: 52


🏁 Script executed:

# Look for function signatures that accept a display parameter to understand intent
ast-grep --pattern 'int.*display.*{
  $$$
}'

Repository: pygame-community/pygame-ce

Length of output: 52


🏁 Script executed:

# Find the function name containing line 1282
sed -n '1200,1290p' src_c/display.c | grep -n "^[a-zA-Z_].*(" | head -5

Repository: pygame-community/pygame-ce

Length of output: 130


🏁 Script executed:

# Get function signature and parameters
sed -n '1240,1290p' src_c/display.c | head -60

Repository: pygame-community/pygame-ce

Length of output: 1400


🏁 Script executed:

# Search for where display parameter comes from - look for function definition
rg -B 20 "In SDL2, display == 0 meant primary" src_c/display.c | head -40

Repository: pygame-community/pygame-ce

Length of output: 1071


🏁 Script executed:

# Check if there's logic to convert display indices to IDs anywhere in display.c
rg -n "SDL_GetDisplays\|display_id\|displayIndex" src_c/display.c

Repository: pygame-community/pygame-ce

Length of output: 52


🏁 Script executed:

# Look for all instances of "display" assignment to understand data flow
sed -n '1200,2200p' src_c/display.c | grep -n "display\s*=" | head -20

Repository: pygame-community/pygame-ce

Length of output: 455


🏁 Script executed:

# Check the full pg_set_mode function signature and parameter handling
sed -n '1240,1260p' src_c/display.c

Repository: pygame-community/pygame-ce

Length of output: 687


🏁 Script executed:

# Look for where display_index is used and if it's converted to display ID
rg -n "display_index\|display.*id" src_c/display.c -B 2 -A 2 | head -50

Repository: pygame-community/pygame-ce

Length of output: 52


🏁 Script executed:

# Check if there's any conversion logic between index and ID for non-zero indices
sed -n '1280,1350p' src_c/display.c

Repository: pygame-community/pygame-ce

Length of output: 2102


🏁 Script executed:

# Search for proper index-to-ID conversion pattern used elsewhere in the codebase
rg -n "SDL_GetDisplays" src_c/display.c -B 5 -A 10

Repository: pygame-community/pygame-ce

Length of output: 2183


🏁 Script executed:

# Check if there's a helper function or conversion logic for display index to ID
rg -n "display.*index\|index.*display" src_c/display.c | grep -v "display_index = 0"

Repository: pygame-community/pygame-ce

Length of output: 52


🏁 Script executed:

# Look at the full context of pg_set_mode to see parameter declaration and all uses
sed -n '1200,1295p' src_c/display.c | cat -n

Repository: pygame-community/pygame-ce

Length of output: 3610


🏁 Script executed:

# Check if display indices > 0 are ever validated or converted before use
sed -n '1280,1400p' src_c/display.c | cat -n

Repository: pygame-community/pygame-ce

Length of output: 4705


🏁 Script executed:

# Look for the other two locations (2038, 2121) to see if they have the same pattern
sed -n '2030,2050p' src_c/display.c

Repository: pygame-community/pygame-ce

Length of output: 896


🏁 Script executed:

sed -n '2110,2130p' src_c/display.c

Repository: pygame-community/pygame-ce

Length of output: 765


Fix broken multi-display API compatibility: display indices 1+ are not converted to valid SDL3 IDs

In SDL3, display IDs are not guaranteed to be sequential 0-based indices like SDL2. Passing display=1,2,3... directly to SDL3 will fail since those are invalid display IDs. The code only handles the special case display==0 (mapping to primary), leaving display > 0 broken for users with multiple displays.

To maintain backwards compatibility with SDL2's 0-based index semantics, non-zero display values need conversion via SDL_GetDisplays() to fetch the actual display IDs by index position (similar to the pattern at line 2038+).

Comment on lines +2072 to +2079
/* Compat with SDL2 behaviour */
const SDL_DisplayMode *curmode;
if (!(curmode = SDL_GetCurrentDisplayMode(display_index))) {
return RAISE(pgExc_SDLError, SDL_GetError());
}
if (curmode->w == desired.w && curmode->h == desired.h) {
return PyLong_FromLong(SDL_BITSPERPIXEL(curmode->format));
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let me find the header files with pixel format helpers
fd -e "h" "pygame" | head -20

Repository: pygame-community/pygame-ce

Length of output: 264


🏁 Script executed:

# Search for PG_SURF_BitsPerPixel and related helpers in the codebase
rg "PG_SURF_BitsPerPixel|PG_FORMAT_BitsPerPixel|PG_PixelFormat" --type c --type h

Repository: pygame-community/pygame-ce

Length of output: 17548


🏁 Script executed:

# Look at the specific lines mentioned in display.c
sed -n '2070,2100p' src_c/display.c

Repository: pygame-community/pygame-ce

Length of output: 1163


🏁 Script executed:

# Check what's defined in _pygame.h around pixel format helpers
cat src_c/_pygame.h | grep -A 20 -B 5 "BitsPerPixel"

Repository: pygame-community/pygame-ce

Length of output: 2581


🏁 Script executed:

# Check for SDL_AllocFormat and SDL_FreeFormat usage patterns
rg "SDL_AllocFormat|SDL_FreeFormat" --type c -B 2 -A 5

Repository: pygame-community/pygame-ce

Length of output: 3539


🏁 Script executed:

# Check how display.c gets BitsPerPixel from formats elsewhere
rg "BitsPerPixel|BITSPERPIXEL" src_c/display.c -B 2 -A 2

Repository: pygame-community/pygame-ce

Length of output: 1388


🏁 Script executed:

# Look for other format allocation patterns in codebase
rg "SDL_AllocFormat" --type c

Repository: pygame-community/pygame-ce

Length of output: 366


SDL2/SDL3 BitsPerPixel mismatch in display mode checks needs fixing (but suggested fix has memory leak)

The issue is real: SDL_BITSPERPIXEL() on format enums differs between SDL2 (includes padding) and SDL3 (excludes padding). This already triggered the creation of PG_SURF_BitsPerPixel elsewhere in the codebase to handle this exact compatibility problem. However, the code at lines 2079 and 2097 uses SDL_BITSPERPIXEL(curmode->format) and SDL_BITSPERPIXEL(closest.format) directly on format enums without applying that compatibility logic.

The suggested fix has a critical flaw: it allocates format with SDL_AllocFormat() but never calls SDL_FreeFormat(), causing a memory leak. This violates the pattern already established in display.c itself (see tempformat usage), where all allocations are properly freed. Correct the fix by adding the corresponding SDL_FreeFormat() call after extracting BitsPerPixel, or follow display.c's existing pattern with appropriate error handling and version checks.

🤖 Prompt for AI Agents
In @src_c/display.c around lines 2072 - 2079, The checks using
SDL_BITSPERPIXEL(curmode->format) and SDL_BITSPERPIXEL(closest.format) must use
the established compatibility logic (PG_SURF_BitsPerPixel) or, if you allocate
an SDL_PixelFormat via SDL_AllocFormat to compute bits, ensure you free it;
update the code around curmode and closest to either call
PG_SURF_BitsPerPixel(format) instead of SDL_BITSPERPIXEL on the raw enum, or if
you keep SDL_AllocFormat(…) use a tempformat variable exactly like other uses in
display.c, call PG_SURF_BitsPerPixel(tempformat) to get the bpp, then call
SDL_FreeFormat(tempformat) immediately after extracting the value (and handle
allocation failure consistently).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants