diff --git a/games/NXDoom/Kconfig b/games/NXDoom/Kconfig index d2bd2080f21..dfac7ebed7e 100644 --- a/games/NXDoom/Kconfig +++ b/games/NXDoom/Kconfig @@ -4,7 +4,7 @@ # config GAMES_NXDOOM - bool "NXDoom" + tristate "NXDoom" default n depends on ALLOW_GPL_COMPONENTS depends on VIDEO_FB @@ -252,6 +252,27 @@ config GAMES_NXDOOM_MAXDRAWSEGS memory, so you may reduce the number. However, too few will cause rendering issues (overflow is checked to avoid crashes). +config GAMES_NXDOOM_HEAP_BUFFERS + bool "Allocate renderer scratch buffers on the heap" + default n + ---help--- + The visplanes/openings/drawsegs/vissprites renderer scratch buffers + (sized by the options above) are static arrays by default, matching + vanilla DOOM. On a target where their combined size threatens the + internal DRAM budget once linked into a full application image, + enable this to allocate them from the heap instead (this target's + heap may be backed by external RAM/PSRAM). Static allocation is + preferred where DRAM budget is not a concern. + +config GAMES_NXDOOM_STATDUMP_MAX_CAPTURES + int "Maximum statdump capture buffer entries" + default 32 + range 1 1024 + ---help--- + Number of playtime-statistics capture slots statdump.c reserves. + This is diagnostic/debug capture storage, not required for normal + gameplay - reduce it on a DRAM-constrained target. + config GAMES_NXDOOM_RANGECHECK bool "Perform range checks" default y diff --git a/games/NXDoom/src/d_iwad.c b/games/NXDoom/src/d_iwad.c index a6f1cd9453d..fcbf901a97d 100644 --- a/games/NXDoom/src/d_iwad.c +++ b/games/NXDoom/src/d_iwad.c @@ -271,6 +271,10 @@ static void buld_iwad_dir_list(void) add_iwad_dir(m_dir_name(myargv[0])); + /* Add the configured DOOM data directory */ + + add_iwad_dir(CONFIG_GAMES_NXDOOM_PREFDIR); + /* Add DOOMWADDIR if it is in the environment */ env = getenv("DOOMWADDIR"); diff --git a/games/NXDoom/src/doom/d_main.c b/games/NXDoom/src/doom/d_main.c index 52df6eda110..d44ff7d2e38 100644 --- a/games/NXDoom/src/doom/d_main.c +++ b/games/NXDoom/src/doom/d_main.c @@ -1294,6 +1294,7 @@ void d_doomloop(void) while (1) { + i_poll_quit_signal(); d_run_frame(); } } diff --git a/games/NXDoom/src/doom/r_bsp.c b/games/NXDoom/src/doom/r_bsp.c index 605b21beaa8..7dbd64b708e 100644 --- a/games/NXDoom/src/doom/r_bsp.c +++ b/games/NXDoom/src/doom/r_bsp.c @@ -78,7 +78,11 @@ line_t *linedef; sector_t *frontsector; sector_t *backsector; +#ifdef CONFIG_GAMES_NXDOOM_HEAP_BUFFERS +drawseg_t *drawsegs; +#else drawseg_t drawsegs[CONFIG_GAMES_NXDOOM_MAXDRAWSEGS]; +#endif drawseg_t *ds_p; /* newend is one past the last valid seg */ diff --git a/games/NXDoom/src/doom/r_bsp.h b/games/NXDoom/src/doom/r_bsp.h index 66c6d611fff..c964b234c4a 100644 --- a/games/NXDoom/src/doom/r_bsp.h +++ b/games/NXDoom/src/doom/r_bsp.h @@ -52,7 +52,11 @@ extern boolean markceiling; extern boolean skymap; +#ifdef CONFIG_GAMES_NXDOOM_HEAP_BUFFERS +extern drawseg_t *drawsegs; +#else extern drawseg_t drawsegs[CONFIG_GAMES_NXDOOM_MAXDRAWSEGS]; +#endif extern drawseg_t *ds_p; extern lighttable_t **hscalelight; diff --git a/games/NXDoom/src/doom/r_draw.c b/games/NXDoom/src/doom/r_draw.c index ff243912aed..00feeb9f7f3 100644 --- a/games/NXDoom/src/doom/r_draw.c +++ b/games/NXDoom/src/doom/r_draw.c @@ -597,7 +597,7 @@ void r_draw_span(void) #ifdef CONFIG_GAMES_NXDOOM_RANGECHECK if (ds_x2 < ds_x1 || ds_x1 < 0 || ds_x2 >= SCREENWIDTH || - (unsigned)ds_y > SCREENHEIGHT) + ds_y < 0 || ds_y >= viewheight) { i_error("r_draw_span: %i to %i at %i", ds_x1, ds_x2, ds_y); } @@ -724,7 +724,7 @@ void r_draw_span_low(void) #ifdef CONFIG_GAMES_NXDOOM_RANGECHECK if (ds_x2 < ds_x1 || ds_x1 < 0 || ds_x2 >= SCREENWIDTH || - (unsigned)ds_y > SCREENHEIGHT) + ds_y < 0 || ds_y >= viewheight) { i_error("r_draw_span: %i to %i at %i", ds_x1, ds_x2, ds_y); } diff --git a/games/NXDoom/src/doom/r_main.c b/games/NXDoom/src/doom/r_main.c index b50cdc33773..874c48d94bc 100644 --- a/games/NXDoom/src/doom/r_main.c +++ b/games/NXDoom/src/doom/r_main.c @@ -31,6 +31,7 @@ #include "d_loop.h" #include "doomdef.h" +#include "i_system.h" #include "m_bbox.h" #include "m_menu.h" @@ -685,6 +686,16 @@ fixed_t r_scale_from_global_angle(angle_t visangle) void r_set_view_size(int blocks, int detail) { + /* The view geometry divides by values derived from blocks, so a value + * outside 3..11 produces a division by zero. The options menu cannot + * produce one; a malformed configuration file can. + */ + + if (blocks < 3 || blocks > 11) + { + i_error("r_set_view_size: screenblocks=%d out of range", blocks); + } + setsizeneeded = true; setblocks = blocks; setdetail = detail; diff --git a/games/NXDoom/src/doom/r_plane.c b/games/NXDoom/src/doom/r_plane.c index 65a2c2fa78b..3768de3451a 100644 --- a/games/NXDoom/src/doom/r_plane.c +++ b/games/NXDoom/src/doom/r_plane.c @@ -57,12 +57,17 @@ planefunction_t ceilingfunc; /* Here comes the obnoxious "visplane". */ +#ifdef CONFIG_GAMES_NXDOOM_HEAP_BUFFERS +visplane_t *visplanes; +short *openings; +#else visplane_t visplanes[CONFIG_GAMES_NXDOOM_MAXVISPLANES]; +short openings[MAXOPENINGS]; +#endif visplane_t *lastvisplane; visplane_t *floorplane; visplane_t *ceilingplane; -short openings[MAXOPENINGS]; short *lastopening; /* Clip values are the solid pixel bounding the range. floorclip starts out @@ -114,12 +119,24 @@ static void r_map_plane(int y, int x1, int x2) fixed_t length; unsigned index; -#ifdef CONFIG_GAMES_NXDOOM_RANGECHECK - if (x2 < x1 || x1 < 0 || x2 >= viewwidth || y > viewheight) + /* Ensure array indices are in range before access. The bound is + * viewheight rather than SCREENHEIGHT because r_init_buffer() only + * populates ylookup[] for [0, viewheight). + */ + + if (x2 < x1 || x1 < 0 || x2 >= viewwidth) { - i_error("R_MapPlane: %i, %i at %i", x1, x2, y); + return; + } + + if (y < 0) + { + y = 0; + } + else if (y >= viewheight) + { + y = viewheight - 1; } -#endif if (planeheight != cachedheight[y]) { @@ -160,27 +177,42 @@ static void r_map_plane(int y, int x1, int x2) spanfunc(); } +static inline boolean r_row_in_range(int row) +{ + return row >= 0 && row < SCREENHEIGHT; +} + static void r_make_spans(int x, int t1, int b1, int t2, int b2) { + /* Check that row is in range before indexing arrays. */ + while (t1 < t2 && t1 <= b1) { - r_map_plane(t1, spanstart[t1], x - 1); + r_map_plane(t1, r_row_in_range(t1) ? spanstart[t1] : 0, x - 1); t1++; } while (b1 > b2 && b1 >= t1) { - r_map_plane(b1, spanstart[b1], x - 1); + r_map_plane(b1, r_row_in_range(b1) ? spanstart[b1] : 0, x - 1); b1--; } while (t2 < t1 && t2 <= b2) { - spanstart[t2] = x; + if (r_row_in_range(t2)) + { + spanstart[t2] = x; + } + t2++; } while (b2 > b1 && b2 >= t2) { - spanstart[b2] = x; + if (r_row_in_range(b2)) + { + spanstart[b2] = x; + } + b2--; } } @@ -195,7 +227,60 @@ static void r_make_spans(int x, int t1, int b1, int t2, int b2) void r_init_planes(void) { - /* Doh! */ +#ifdef CONFIG_GAMES_NXDOOM_HEAP_BUFFERS + visplanes = malloc(sizeof(visplane_t) * CONFIG_GAMES_NXDOOM_MAXVISPLANES); + openings = malloc(sizeof(short) * MAXOPENINGS); + drawsegs = malloc(sizeof(drawseg_t) * CONFIG_GAMES_NXDOOM_MAXDRAWSEGS); + vissprites = malloc(sizeof(vissprite_t) * + CONFIG_GAMES_NXDOOM_MAXVISSPRITES); + + if (visplanes == NULL || openings == NULL || drawsegs == NULL || + vissprites == NULL) + { + r_shutdown_planes(); + + i_error("r_init_planes: failed to allocate renderer buffers"); + } + + /* Free these on exit; the game can be started again in this process. */ + + i_at_exit(r_shutdown_planes, true); +#endif +} + +/* r_shutdown_planes + * Frees the renderer scratch buffers allocated by r_init_planes. Only + * registered as an exit handler when CONFIG_GAMES_NXDOOM_HEAP_BUFFERS is + * set - the static-array buffers have nothing to free. + */ + +void r_shutdown_planes(void) +{ +#ifdef CONFIG_GAMES_NXDOOM_HEAP_BUFFERS + if (visplanes != NULL) + { + free(visplanes); + visplanes = NULL; + } + + if (openings != NULL) + { + free(openings); + openings = NULL; + } + + if (drawsegs != NULL) + { + free(drawsegs); + drawsegs = NULL; + } + + if (vissprites != NULL) + { + free(vissprites); + vissprites = NULL; + } +#endif } /* r_clear_planes @@ -314,13 +399,15 @@ visplane_t *r_check_plane(visplane_t *pl, int start, int stop) /* make a new visplane */ + if (lastvisplane - visplanes == CONFIG_GAMES_NXDOOM_MAXVISPLANES) + { + i_error("r_check_plane: no more visplanes"); + } + lastvisplane->height = pl->height; lastvisplane->picnum = pl->picnum; lastvisplane->lightlevel = pl->lightlevel; - if (lastvisplane - visplanes == CONFIG_GAMES_NXDOOM_MAXVISPLANES) - i_error("r_check_plane: no more visplanes"); - pl = lastvisplane++; pl->minx = start; pl->maxx = stop; diff --git a/games/NXDoom/src/doom/r_plane.h b/games/NXDoom/src/doom/r_plane.h index 1ae3b79254b..f51ccfa37fa 100644 --- a/games/NXDoom/src/doom/r_plane.h +++ b/games/NXDoom/src/doom/r_plane.h @@ -58,6 +58,7 @@ extern fixed_t distscale[SCREENWIDTH]; ****************************************************************************/ void r_init_planes(void); +void r_shutdown_planes(void); void r_clear_planes(void); void r_draw_planes(void); diff --git a/games/NXDoom/src/doom/r_things.c b/games/NXDoom/src/doom/r_things.c index 019d83a7048..0084da909df 100644 --- a/games/NXDoom/src/doom/r_things.c +++ b/games/NXDoom/src/doom/r_things.c @@ -93,7 +93,11 @@ spriteframe_t sprtemp[29]; int maxframe; const char *spritename; +#ifdef CONFIG_GAMES_NXDOOM_HEAP_BUFFERS +vissprite_t *vissprites; +#else vissprite_t vissprites[CONFIG_GAMES_NXDOOM_MAXVISSPRITES]; +#endif vissprite_t *vissprite_p; int newvissprite; diff --git a/games/NXDoom/src/doom/r_things.h b/games/NXDoom/src/doom/r_things.h index cdd29948368..eb730c28d7e 100644 --- a/games/NXDoom/src/doom/r_things.h +++ b/games/NXDoom/src/doom/r_things.h @@ -28,7 +28,11 @@ * Public Data ****************************************************************************/ +#ifdef CONFIG_GAMES_NXDOOM_HEAP_BUFFERS +extern vissprite_t *vissprites; +#else extern vissprite_t vissprites[CONFIG_GAMES_NXDOOM_MAXVISSPRITES]; +#endif extern vissprite_t *vissprite_p; extern vissprite_t vsprsortedhead; diff --git a/games/NXDoom/src/doom/statdump.c b/games/NXDoom/src/doom/statdump.c index 1db5657ab6c..977a4b9b160 100644 --- a/games/NXDoom/src/doom/statdump.c +++ b/games/NXDoom/src/doom/statdump.c @@ -39,7 +39,7 @@ * Pre-processor Definitions ****************************************************************************/ -#define MAX_CAPTURES 32 +#define MAX_CAPTURES CONFIG_GAMES_NXDOOM_STATDUMP_MAX_CAPTURES /**************************************************************************** * Private Data diff --git a/games/NXDoom/src/i_main.c b/games/NXDoom/src/i_main.c index bd9dab60910..1961b873446 100644 --- a/games/NXDoom/src/i_main.c +++ b/games/NXDoom/src/i_main.c @@ -57,6 +57,8 @@ void d_doom_main(void); int main(int argc, char **argv) { + i_install_quit_signal(); + /* save arguments */ myargc = argc; diff --git a/games/NXDoom/src/i_system.c b/games/NXDoom/src/i_system.c index b3867e0add9..895ff556b0b 100644 --- a/games/NXDoom/src/i_system.c +++ b/games/NXDoom/src/i_system.c @@ -22,6 +22,8 @@ * Included Files ****************************************************************************/ +#include +#include #include #include #include @@ -75,6 +77,15 @@ static atexit_listentry_t *exit_funcs = NULL; static boolean already_quitting = false; +/* Set only by i_quit_signal_handler() (async-signal-safe: a single + * sig_atomic_t store, nothing else) and read only by + * i_poll_quit_signal(), called from a safe point in the main loop - see + * the comment on i_install_quit_signal() in i_system.h for why the + * actual i_quit() cleanup is deferred out of the signal handler itself. + */ + +static volatile sig_atomic_t quit_requested = 0; + /* Read Access Violation emulation. * * From PrBoom+, by entryway. @@ -320,6 +331,70 @@ void i_quit(void) exit(0); } +/**************************************************************************** + * Name: i_quit_signal_handler + * + * Description: + * Records that a quit was requested. The work is deferred to + * i_poll_quit_signal() so that no cleanup runs from signal context. + * + ****************************************************************************/ + +static void i_quit_signal_handler(int signo) +{ + (void)signo; + quit_requested = 1; +} + +/**************************************************************************** + * Name: i_install_quit_signal + * + * Description: + * Installs the SIGTERM handler used to request a clean exit. + * + ****************************************************************************/ + +void i_install_quit_signal(void) +{ + struct sigaction sa; + + /* Built in rather than loaded as a module, this state survives a + * previous run and must be reset before the handler is armed. + */ + + quit_requested = 0; + exit_funcs = NULL; + + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = i_quit_signal_handler; + + if (sigaction(SIGTERM, &sa, NULL) < 0) + { + /* Not fatal: the game runs, it just cannot be asked to exit. */ + + printf("nxdoom: failed to install SIGTERM handler: %d\n", + errno); + } +} + +/**************************************************************************** + * Name: i_poll_quit_signal + * + * Description: + * Exits if a quit was requested. Called from the main loop, where the + * cleanup i_quit() performs is safe to run. + * + ****************************************************************************/ + +void i_poll_quit_signal(void) +{ + if (quit_requested) + { + printf("nxdoom: quit signal seen, calling i_quit\n"); + i_quit(); + } +} + void i_error(const char *error, ...) { char msgbuf[512]; diff --git a/games/NXDoom/src/i_system.h b/games/NXDoom/src/i_system.h index ae1c568068b..96d20dd5c39 100644 --- a/games/NXDoom/src/i_system.h +++ b/games/NXDoom/src/i_system.h @@ -73,6 +73,18 @@ ticcmd_t *i_base_ticcmd(void); void i_quit(void) NORETURN; +/* Installs the SIGTERM handler used to request a clean exit. The handler + * only records the request; i_poll_quit_signal() performs the exit. + */ + +void i_install_quit_signal(void); + +/* Exits if a quit was requested. Call only from the main loop, where the + * cleanup i_quit() performs is safe to run. + */ + +void i_poll_quit_signal(void); + void i_error(const char *error, ...) NORETURN PRINTF_ATTR(1, 2); void i_tactile(int on, int off, int total); diff --git a/games/NXDoom/src/i_video.c b/games/NXDoom/src/i_video.c index d9a76a65144..2f8d40106c4 100644 --- a/games/NXDoom/src/i_video.c +++ b/games/NXDoom/src/i_video.c @@ -105,6 +105,14 @@ struct graphics_state_s unsigned outw; unsigned outh; + /* Position of the top left corner of the scaled image in pixels, which is + * what origin above addresses in bytes. Kept separately because the + * update ioctl works in pixels. + */ + + unsigned outx; + unsigned outy; + /* Maps an output column onto the source column it is drawn from, so that * the inner loop needs neither a division nor a separate case for * fractional scaling. @@ -487,6 +495,28 @@ static void blit_screen(void) prevsy = sy; prevrow = out; } + +#ifdef CONFIG_FB_UPDATE + /* Hand the touched region back to the driver. Frame buffers that live + * behind a cache, or in memory the display controller reads by DMA, only + * become visible once the driver has been told the pixels changed. + */ + + { + struct fb_area_s area; + + area.x = g_graphics_state.outx; + area.y = g_graphics_state.outy; + area.w = outw; + area.h = outh; + + if (ioctl(g_graphics_state.fd, FBIO_UPDATE, + (unsigned long)((uintptr_t)&area)) < 0) + { + i_error("ioctl(FBIO_UPDATE) failed: %d\n", errno); + } + } +#endif } static void update_grab(void) @@ -936,13 +966,15 @@ void i_init_graphics(void) g_graphics_state.outh = SCREENHEIGHT * g_graphics_state.scale; #endif - /* Centre the scaled image in the frame buffer */ + /* Centre the scaled image in the frame buffer. The byte offset needs the + * stride and pixel size, so it is computed once the plane info has been + * read below. + */ - g_graphics_state.origin = - (g_graphics_state.vinfo.yres - g_graphics_state.outh) / 2 * - g_graphics_state.pinfo.stride + - (g_graphics_state.vinfo.xres - g_graphics_state.outw) / 2 * - (g_graphics_state.pinfo.bpp >> 3); + g_graphics_state.outx = + (g_graphics_state.vinfo.xres - g_graphics_state.outw) / 2; + g_graphics_state.outy = + (g_graphics_state.vinfo.yres - g_graphics_state.outh) / 2; /* Build the output column to source column map once */ @@ -965,6 +997,10 @@ void i_init_graphics(void) i_error("ioctl(FBIOGET_PLANEINFO) failed: %d\n", errno); } + g_graphics_state.origin = + g_graphics_state.outy * g_graphics_state.pinfo.stride + + g_graphics_state.outx * (g_graphics_state.pinfo.bpp >> 3); + /* Initialize frame buffer memory for actual rendering */ g_graphics_state.fbmem = diff --git a/games/NXDoom/src/m_config.c b/games/NXDoom/src/m_config.c index d4441f5eaae..35afa20b5d8 100644 --- a/games/NXDoom/src/m_config.c +++ b/games/NXDoom/src/m_config.c @@ -1982,16 +1982,14 @@ static void save_default_collection(default_collection_t *collection) * ****************************************************************************/ -static int parse_int_parameter(const char *strparm) +static int parse_int_parameter(const char *strparm, int *param) { - int param; - if (strparm[0] == '0' && strparm[1] == 'x') - sscanf(strparm + 2, "%x", (unsigned int *)¶m); - else - sscanf(strparm, "%i", ¶m); + { + return sscanf(strparm + 2, "%x", (unsigned int *)param) == 1; + } - return param; + return sscanf(strparm, "%i", param) == 1; } static void set_variable(default_t *def, const char *value) @@ -2008,7 +2006,11 @@ static void set_variable(default_t *def, const char *value) case DEFAULT_INT: case DEFAULT_INT_HEX: - *def->location.i = parse_int_parameter(value); + if (parse_int_parameter(value, &intparm)) + { + *def->location.i = intparm; + } + break; case DEFAULT_KEY: @@ -2017,7 +2019,11 @@ static void set_variable(default_t *def, const char *value) * file (save the old value in untranslated) */ - intparm = parse_int_parameter(value); + if (!parse_int_parameter(value, &intparm)) + { + break; + } + def->untranslated = intparm; if (intparm >= 0 && intparm < 128) { @@ -2082,6 +2088,7 @@ static void load_default_collection(default_collection_t *collection) default_t *def; char defname[80]; char strparm[100]; + char line[256]; /* read the file in, overriding any set defaults */ @@ -2096,12 +2103,31 @@ static void load_default_collection(default_collection_t *collection) return; } - while (!feof(f)) + while (fgets(line, sizeof(line), f) != NULL) { - if (fscanf(f, "%79s %99[^\n]\n", defname, strparm) != 2) + strparm[0] = '\0'; + + /* Parse one physical line at a time. fscanf() with whitespace in + * its format can consume the next line as a missing value. + * + * A line too long for the buffer cannot be a valid setting, so + * discard the remainder of it and move on. + */ + + if (strchr(line, '\n') == NULL && + strlen(line) == sizeof(line) - 1) { - /* This line doesn't match */ + int ch; + while ((ch = fgetc(f)) != '\n' && ch != EOF) + { + } + + continue; + } + + if (sscanf(line, "%79s %99[^\n]", defname, strparm) != 2) + { continue; } @@ -2136,6 +2162,15 @@ static void load_default_collection(default_collection_t *collection) memmove(strparm, strparm + 1, sizeof(strparm) - 1); } + /* Stripping above removes everything when the value held only + * non-printable characters. + */ + + if (strparm[0] == '\0') + { + continue; + } + set_variable(def, strparm); }