Skip to content

Fix crash in trig rasteriser from 16.16 fixed-point overflow#4668

Merged
Loobinex merged 1 commit intodkfans:masterfrom
cerwym:upstream/fix-trig-overflow-crash
Apr 12, 2026
Merged

Fix crash in trig rasteriser from 16.16 fixed-point overflow#4668
Loobinex merged 1 commit intodkfans:masterfrom
cerwym:upstream/fix-trig-overflow-crash

Conversation

@cerwym
Copy link
Copy Markdown
Contributor

@cerwym cerwym commented Apr 12, 2026

Problem

At high resolutions (2560×1440, 3440×1440), the software triangle rasteriser crashes with EXCEPTION_ACCESS_VIOLATION in trig_render_md10 — the translucent shadow rendering path.

Root Cause

The triangle setup functions (trig_ll_start, trig_rl_start, etc.) compute (delta_x << 16) and (X << 16) using 16.16 fixed-point arithmetic in signed 32-bit integers. When vertex screen coordinates exceed ±32767 — which happens when rotpers() projects near-camera geometry at high resolutions — the left-shift silently overflows, corrupting the polyscans array and producing out-of-bounds framebuffer writes.

This overflow was always latent in the original Bullfrog x86 assembly (shll $0x10 wraps silently on x86) and was faithfully preserved in the ASM-to-C conversion (PR #1973). It simply never triggered at the original 640×480 resolution.

Fix

  • Coordinate guard in trig(): Rejects triangles where any vertex X/Y exceeds ±32767 or where the X or Y span exceeds 32767, preventing overflow before it reaches the setup functions. Covers all ~25 rendering modes.
  • OOB safety net in trig_render_md10(): Bounds-checks the output pointer against the poly_screen buffer and logs an error if triggered, preventing the access violation as a defence-in-depth measure.

Copilot AI review requested due to automatic review settings April 12, 2026 15:48
@cerwym cerwym force-pushed the upstream/fix-trig-overflow-crash branch from 7c6ef82 to c44ee6a Compare April 12, 2026 15:51
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Fixes a crash in the software triangle rasteriser at high resolutions by preventing 16.16 fixed-point overflow during triangle setup and adding a defensive bounds check in the translucent shadow render path (trig_render_md10).

Changes:

  • Add coordinate range guarding in trig() to skip triangles that can overflow 16.16 fixed-point setup math.
  • Add an out-of-bounds safety check for the output pixel pointer in trig_render_md10() to prevent access violations.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/bflib_render_trig.c Outdated
Comment on lines +3126 to +3131
if (o < poly_screen || o >= poly_screen + (vec_screen_width * (vec_window_height + 1)))
{
ERRORLOG("[md10 crash] output ptr %p out of bounds (base %p, w=%lu, h=%ld), colM=%u",
o, poly_screen, vec_screen_width, vec_window_height, colM);
return;
}
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

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

The lower-bound of this OOB check is too permissive: poly_screen is initialized as screenbuf - vec_screen_width, so allowing o >= poly_screen still permits o to point into the pre-buffer row (before vec_screen). To reliably prevent invalid reads/writes, compare against the real first valid pixel (e.g. vec_screen / poly_screen + vec_screen_width) rather than poly_screen itself.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done, checks start of buffer instead of poly_stream

Comment thread src/bflib_render_trig.c Outdated
Comment on lines +3124 to +3131
// Sample texture; if non-zero (inside shadow), apply fade
if (m[colM]) {
if (o < poly_screen || o >= poly_screen + (vec_screen_width * (vec_window_height + 1)))
{
ERRORLOG("[md10 crash] output ptr %p out of bounds (base %p, w=%lu, h=%ld), colM=%u",
o, poly_screen, vec_screen_width, vec_window_height, colM);
return;
}
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

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

This bounds check runs inside the inner per-pixel loop (and can execute for many pixels when m[colM] is non-zero), which risks a noticeable slowdown in this hot path. Consider hoisting the check to once per scanline (validate the start/end pointers before the loop), and/or making it conditional on a debug/assert build, while still keeping a safe early-return when triggered.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done. hoisted out of inner loop

Added logs and coord rejection for bounds exceed
@cerwym cerwym force-pushed the upstream/fix-trig-overflow-crash branch from c44ee6a to 707111b Compare April 12, 2026 15:56
@Loobinex Loobinex merged commit 42a9deb into dkfans:master Apr 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants