From 4bdcac1c41ff32cc1330e839149f03af28922782 Mon Sep 17 00:00:00 2001 From: Chris Parsons Date: Mon, 13 Apr 2026 13:51:07 -0230 Subject: [PATCH] fix d3d11 rendering and saving --- editor_patch/lightmap.cpp | 28 +++++++++------ game_patch/graphics/d3d11/gr_d3d11_solid.cpp | 2 +- game_patch/misc/destruction.cpp | 2 +- game_patch/misc/g_solid.cpp | 37 ++++++++++++++++++++ game_patch/rf/geometry.h | 2 +- 5 files changed, 57 insertions(+), 14 deletions(-) diff --git a/editor_patch/lightmap.cpp b/editor_patch/lightmap.cpp index b8f2bbe5..fdd60cb6 100644 --- a/editor_patch/lightmap.cpp +++ b/editor_patch/lightmap.cpp @@ -589,26 +589,32 @@ void ApplyLightmapPatches() write_mem(0x004ae73a, 0xB7); // FUN_004ae360: shadow calculation write_mem(0x004ae81f, 0xB7); write_mem(0x004aed4e, 0xB7); - write_mem(0x004a45b1, 0xB7); // solid_write: level file export - - // MOVSX -> MOVZX for face+0x36 reads from register (5 sites) - // These follow 16-bit MOV AX,[face+0x36] and sign-extend AX to 32-bit before use as index + write_mem(0x004a45b1, 0xB7); // solid_write: MOVSX→MOVZX (sentinel 0xFFFF→65535 in file, truncated back to 0xFFFF by reader's 16-bit store) + write_mem(0x004a4655, 0x74); // solid_write: JLE->JE for lightmap UV write decision + write_mem(0x004a39ce, 0x74); // solid_read: JLE->JE for lightmap UV read decision write_mem(0x0048778b, 0xB7); // FUN_004872e0: lightmap surface container lookup write_mem(0x00487862, 0xB7); - write_mem(0x0049b955, 0xB7); // FUN_0049b550: geo-cache build - write_mem(0x0049bea0, 0xB7); - write_mem(0x0049bed3, 0xB7); // Fix sentinel checks: JLE -> JE (0x7E -> 0x74) // Original: CMP AX,0xFFFF; JLE skip — skips all negative values (wrong for groups > 32767) // Fixed: CMP AX,0xFFFF; JE skip — skips only the 0xFFFF "no group" sentinel write_mem(0x00487781, 0x74); // FUN_004872e0 write_mem(0x00487858, 0x74); - write_mem(0x0049b94b, 0x74); // FUN_0049b550 - // Note: CMP AX,BX; JLE at 0x0049be96 and 0x0049bec9 left as signed — BX can be - // 0xFFFF ("no batch" sentinel), and unsigned JBE would cause all faces to be skipped. - // Signed comparison is correct for group IDs 0-32767 and safe for the sentinel case. + // Fix signed "< 0" checks that destroy valid surface indices > 32767 during geometry build. + // FUN_004a8660 and FUN_004a9270 reset faces with negative (signed) surface_index to 0xFFFF. + // This destroys valid high indices when loading an RFL with >32767 surface groups. + write_mem(0x004a8fa9, 0xFF); // FUN_004a8660: CMP word, 0 → CMP word, -1 + write_mem(0x004a8faa, 0x75); // JGE → JNE (only sentinel triggers reset) + write_mem(0x004a9294, 0xFF); // FUN_004a9270: CMP word, 0 → CMP word, -1 + write_mem(0x004a9295, 0x75); // JGE → JNE + + // Note: The editor's D3D8 viewport rendering has additional signed surface_index checks + // in the geo-cache build (FUN_0049b550) and rendering functions (FUN_005040b0, + // FUN_00493570, FUN_00494d70, FUN_00496a30, etc.) that cause fullbright on faces with + // surface_index > 32767. The D3D8 rendering path also has architectural batch count limitations + // for rooms with many unique surface groups. The D3D11 game renderer handles all of this + // correctly. Editor viewport fullbright on high-surface-count levels is a known limitation. // -smoothlights: experimental lightmap seam fix at portal boundaries // Note: GetCommandLineA() is used instead of argv/argc because this runs during DLL diff --git a/game_patch/graphics/d3d11/gr_d3d11_solid.cpp b/game_patch/graphics/d3d11/gr_d3d11_solid.cpp index 79d4a4de..021e1479 100644 --- a/game_patch/graphics/d3d11/gr_d3d11_solid.cpp +++ b/game_patch/graphics/d3d11/gr_d3d11_solid.cpp @@ -341,7 +341,7 @@ namespace df::gr::d3d11 FaceRenderType render_type = determine_face_render_type(face); int face_tex = face->attributes.bitmap_id; int lightmap_tex = -1; - if (!is_sky_ && render_type != FaceRenderType::liquid && face->attributes.surface_index >= 0) { + if (!is_sky_ && render_type != FaceRenderType::liquid && face->attributes.surface_index != 0xFFFF) { if (face->attributes.surface_index >= solid->surfaces.size()) { xlog::warn("add_face: surface_index {} out of bounds (size {}), skipping face", face->attributes.surface_index, solid->surfaces.size()); diff --git a/game_patch/misc/destruction.cpp b/game_patch/misc/destruction.cpp index 4e20c941..c41770e5 100644 --- a/game_patch/misc/destruction.cpp +++ b/game_patch/misc/destruction.cpp @@ -1412,7 +1412,7 @@ static void add_cap_faces_from_loop( rf::GFaceAttributes attrs{}; attrs.bitmap_id = bitmap_id; - attrs.surface_index = -1; + attrs.surface_index = 0xFFFF; // no surface/lightmap sentinel // UV mapping: project relative to loop centroid auto compute_uv = [&](rf::GVertex* v) -> std::pair { diff --git a/game_patch/misc/g_solid.cpp b/game_patch/misc/g_solid.cpp index 22594f0e..73393943 100644 --- a/game_patch/misc/g_solid.cpp +++ b/game_patch/misc/g_solid.cpp @@ -763,6 +763,43 @@ void g_solid_do_patch() // Set PPM for geo crater texture based on its resolution instead of static value of 32.0 levelmod_do_blast_autotexture_ppm_patch.install(); + // Fix lightmap surface group ID overflow: face+0x36 (surface_index) is a signed short. + // When >32767 lightmap surface groups exist, the signed short wraps negative. + // Fix raises the limit from 32767 to 65535 groups. + + // Note: FUN_004d4c60 (0x004d4cf6) and FUN_00561650 (0x005619dd) are additional D3D8 rendering + // functions with 32-bit sentinel guards (TEST EAX,EAX and CMP EAX,-1) that fail when + // MOVZX produces 65535 instead of -1. Left unpatched, D3D11 renderer doesn't use them, + // and D3D8 can't render such levels anyway. + write_mem(0x004e4d50, 0xB7); // FUN_004e4a60: lightmap surface grouping + write_mem(0x004e4d79, 0xB7); + write_mem(0x004e4d96, 0xB7); + write_mem(0x004e4ded, 0xB7); + write_mem(0x004e4f56, 0xB7); + write_mem(0x004e51a9, 0xB7); // FUN_004e5040: batch lightmap calculator + write_mem(0x004e51da, 0xB7); + write_mem(0x004eeb41, 0xB7); // FUN_004ee550: lightmap surface setup + write_mem(0x004f3471, 0xB7); // FUN_004f3390: shadow geometry + write_mem(0x004f34aa, 0xB7); + write_mem(0x004f449c, 0xB7); // FUN_004f4280: shadow calculation + write_mem(0x004f496a, 0xB7); // FUN_004f4590: shadow calculation + write_mem(0x004f4a4f, 0xB7); + write_mem(0x004f4f7e, 0xB7); + + // MOVSX -> MOVZX for face+0x36 reads from register (3 sites) + write_mem(0x0047f0f2, 0xB7); // surface container lookup + write_mem(0x0047f277, 0xB7); + write_mem(0x004ed772, 0xB7); // FUN_004ed520: solid_read + + // Fix sentinel checks: JLE -> JE (0x7E -> 0x74) + // Original: CMP AX,0xFFFF; JLE skip — skips all negative values (wrong for groups > 32767) + // Fixed: CMP AX,0xFFFF; JE skip — skips only the 0xFFFF "no group" sentinel + write_mem(0x004d8b71, 0x74); // FUN_004d86d0: surface container lookup + write_mem(0x004d8c48, 0x74); + write_mem(0x004edf5e, 0x74); // FUN_004ed520: solid_read + write_mem(0x004eebe5, 0x74); // FUN_004ee550: lightmap surface setup + write_mem(0x0047f274, 0x74); // surface container lookup + // Commands max_decals_cmd.register_cmd(); dbg_room_clip_wnd_cmd.register_cmd(); diff --git a/game_patch/rf/geometry.h b/game_patch/rf/geometry.h index 93a7ab32..9e8802ec 100644 --- a/game_patch/rf/geometry.h +++ b/game_patch/rf/geometry.h @@ -282,7 +282,7 @@ namespace rf }; int bitmap_id; short portal_id; // portal index + 2 or 0 - short surface_index; + unsigned short surface_index; // 0xFFFF = no surface/lightmap (sentinel) int face_id; int smoothing_groups; // bitfield of smoothing groups