From fc092d99b604018e9864b025ec5676ea6ec3fc72 Mon Sep 17 00:00:00 2001 From: italoarruda Date: Thu, 24 Sep 2026 09:54:07 -0400 Subject: [PATCH] fast3d: clamp MSAA to GL_MAX_SAMPLES and bind framebuffer 0 as GL name 0 An MSAA level above the driver limit left the multisample renderbuffers unallocated (black screen). Two glBindFramebuffer() calls also passed an index into framebuffers[] instead of a GL name. Finding D323. --- docs/dev/findings-index.csv | 1 + docs/dev/findings.md | 9 +++++++++ port/fast3d/gfx_opengl.cpp | 19 +++++++++++++++++-- 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/docs/dev/findings-index.csv b/docs/dev/findings-index.csv index 9f9ba4d1..7dc09044 100644 --- a/docs/dev/findings-index.csv +++ b/docs/dev/findings-index.csv @@ -200,3 +200,4 @@ D319,FIXED,"Frame cap slider was mostly a dead zone; replaced with a plain 30/60 D320,OPEN,"D318-class sweep: aim-hold → _update AI lists in 8 levels carry the same as-authored softlock race (static analysis, 2026-09-20).","OPEN — static sweep complete (Facility ai_19, Control ai_9, Depot ai_12 flagged high/med-high; Bond-combat loops likely safe); no live confirmation beyond D318 itself; probe-verify path + generalized…" D321,CLOSED,D318 trigger chain fully mapped:,CLOSED — trigger chain mapped and faithful; D318 watchdog validated in live play. D322,OPEN,Long-session audio degradation:,"OPEN — static triage done (teardown audit, mixer statelessness, D202-coverage check); ranked hypotheses:" +D323,FIXED,OpenGL: MSAA above GL_MAX_SAMPLES gives a black screen; glBindFramebuffer is given a framebuffer index as a GL name.,FIXED (port/fast3d/gfx_opengl.cpp). diff --git a/docs/dev/findings.md b/docs/dev/findings.md index d70589a9..6b06a136 100644 --- a/docs/dev/findings.md +++ b/docs/dev/findings.md @@ -608,6 +608,7 @@ covers D24–D69; the log continues in §H (D32 procedure, D70–D121). | D320 | **D318-class sweep: aim-hold → `_update` AI lists in 8 levels carry the same as-authored softlock race (static analysis, 2026-09-20).** — full `## D320` entry at file tail | OPEN — static sweep complete (Facility ai_19, Control ai_9, Depot ai_12 flagged high/med-high; Bond-combat loops likely safe); no live confirmation beyond D318 itself; probe-verify path + generalized-watchdog recommendation documented. | | D321 | **D318 trigger chain fully mapped: tanks → combat bit is an authored ~3.5 s gas-cascade delay (chr 254 script); derail lands same-tick; PC behavior confirmed faithful to N64 (probe captures, 2026-09-20).** — full `## D321` entry at file tail | CLOSED — trigger chain mapped and faithful; D318 watchdog validated in live play. Optional deferred tuning: `D318_DEADLOCK_TICKS` 600→300 (user's call). | | D322 | **Long-session audio degradation: full campaign on v0.3.0 — audio progressively worsens from Silo, by Caverns/Cradle the OST is inaudible and SFX "come and go"; restarting the game restores it (issue #87, user report, 2026-09-21).** — full `## D322` entry at file tail | OPEN — static triage done (teardown audit, mixer statelessness, D202-coverage check); ranked hypotheses: voice-pool exhaustion/counter drift > queue starvation > evtq saturation. `GE_D322` pool-telemetry probe shipped; needs a campaign capture with `GE_D322=1 GE_D204=1`. | +| D323 | **OpenGL: MSAA above `GL_MAX_SAMPLES` gives a black screen; `glBindFramebuffer` is given a framebuffer index as a GL name.** — full `## D323` entry at file tail | FIXED (`port/fast3d/gfx_opengl.cpp`). | Phase 2 replaced the Phase-1 demo loop with the real `mainproc()` on real OS threads, compiled GE's real `src/sched.c`, and brought in PD's fast3d software @@ -12239,3 +12240,11 @@ Full campaign (or at minimum Silo → Caverns/Cradle) with **`GE_D322=1 GE_D204= - All pools healthy while audio is bad → back to the mixer/reverb state classes (re-open M-65's ruled-out list with a long-session `GE_AUDIODUMP`). **Status:** OPEN — static triage complete, probe shipped, awaiting campaign capture. Cross-ref: D202/M-65+M-66b (ownerless-loop leak class + expiration), D207 (8-cap starvation design notes), D305 (pool/list desync observation), D204 (pipeline pacing + `GE_D204` monitor), D248/D250 (frame pacing, fixed), issue #87. + +## D323 — OpenGL MSAA clamp and framebuffer binding + +A request above `GL_MAX_SAMPLES` (4 on Apple Silicon) leaves the multisample +renderbuffers unallocated (incomplete FBO, black screen); the level is now +clamped to the driver limit. `gfx_opengl_resolve_msaa_color_buffer()` and +`gfx_opengl_copy_framebuffer()` passed `current_framebuffer` (an index into +`framebuffers[]`) to `glBindFramebuffer()`; framebuffer 0 is GL name 0. diff --git a/port/fast3d/gfx_opengl.cpp b/port/fast3d/gfx_opengl.cpp index 4177a203..d55d9f24 100644 --- a/port/fast3d/gfx_opengl.cpp +++ b/port/fast3d/gfx_opengl.cpp @@ -1188,6 +1188,20 @@ static void gfx_opengl_update_framebuffer_parameters(int fb_id, uint32_t width, width = max(width, 1U); height = max(height, 1U); + /* macOS GL 4.1 core: asking for more samples than GL_MAX_SAMPLES (e.g. 8x) + * makes the multisample renderbuffers incomplete and the screen goes dark + * (glReadPixels 0x506). Clamp to what the driver reports. */ + if (msaa_level > 1) { + static GLint maxSamples = -1; + if (maxSamples < 0) { + glGetIntegerv(GL_MAX_SAMPLES, &maxSamples); + if (maxSamples < 1) maxSamples = 1; + } + if ((GLint)msaa_level > maxSamples) { + msaa_level = (uint32_t)maxSamples; + } + } + if (gfx_framebuffers_enabled) { glBindFramebuffer(GL_FRAMEBUFFER, fb.fbo); @@ -1284,7 +1298,8 @@ void gfx_opengl_resolve_msaa_color_buffer(int fb_id_target, int fb_id_source) { glBindFramebuffer(GL_READ_FRAMEBUFFER, fb_src.fbo); glBlitFramebuffer(0, 0, fb_src.width, fb_src.height, 0, 0, fb_dst.width, fb_dst.height, GL_COLOR_BUFFER_BIT, GL_NEAREST); - glBindFramebuffer(GL_FRAMEBUFFER, current_framebuffer); + /* current_framebuffer is an INDEX into framebuffers[], not a GL name; fb 0 is the window's default framebuffer (name 0). */ + glBindFramebuffer(GL_FRAMEBUFFER, current_framebuffer == 0 ? 0 : framebuffers[current_framebuffer].fbo); glEnable(GL_SCISSOR_TEST); } @@ -1348,7 +1363,7 @@ void gfx_opengl_copy_framebuffer(int fb_dst, int fb_src, int left, int top, bool glBlitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, GL_COLOR_BUFFER_BIT, GL_NEAREST); - glBindFramebuffer(GL_FRAMEBUFFER, framebuffers[current_framebuffer].fbo); + glBindFramebuffer(GL_FRAMEBUFFER, current_framebuffer == 0 ? 0 : framebuffers[current_framebuffer].fbo); glReadBuffer(GL_BACK);