1 // SONIC ROBO BLAST 2
2 //-----------------------------------------------------------------------------
3 // Copyright (C) 1993-1996 by id Software, Inc.
4 // Copyright (C) 1998-2000 by DooM Legacy Team.
5 // Copyright (C) 2013-2016 by Matthew "Kaito Sinclaire" Walsh.
6 // Copyright (C) 1999-2020 by Sonic Team Junior.
7 //
8 // This program is free software distributed under the
9 // terms of the GNU General Public License, version 2.
10 // See the 'LICENSE' file for more details.
11 //-----------------------------------------------------------------------------
12 /// \file  f_wipe.c
13 /// \brief SRB2 2.1 custom fade mask "wipe" behavior.
14 
15 #include "f_finale.h"
16 #include "i_video.h"
17 #include "v_video.h"
18 
19 #include "r_state.h" // fadecolormap
20 #include "r_draw.h" // transtable
21 #include "p_pspr.h" // tr_transxxx
22 #include "p_local.h"
23 #include "st_stuff.h"
24 #include "w_wad.h"
25 #include "z_zone.h"
26 
27 #include "i_system.h"
28 #include "i_threads.h"
29 #include "m_menu.h"
30 #include "console.h"
31 #include "d_main.h"
32 #include "g_game.h"
33 #include "m_misc.h" // movie mode
34 
35 #include "doomstat.h"
36 
37 #include "lua_hud.h" // level title
38 
39 #ifdef HWRENDER
40 #include "hardware/hw_main.h"
41 #endif
42 
43 #if NUMSCREENS < 5
44 #define NOWIPE // do not enable wipe image post processing for ARM, SH and MIPS CPUs
45 #endif
46 
47 typedef struct fademask_s {
48 	UINT8* mask;
49 	UINT16 width, height;
50 	size_t size;
51 	fixed_t xscale, yscale;
52 } fademask_t;
53 
54 UINT8 wipedefs[NUMWIPEDEFS] = {
55 	99, // wipe_credits_intermediate (0)
56 
57 	0,  // wipe_level_toblack
58 	UINT8_MAX,  // wipe_intermission_toblack
59 	0,  // wipe_continuing_toblack
60 	0,  // wipe_titlescreen_toblack
61 	0,  // wipe_timeattack_toblack
62 	99, // wipe_credits_toblack
63 	0,  // wipe_evaluation_toblack
64 	0,  // wipe_gameend_toblack
65 	99, // wipe_intro_toblack (hardcoded)
66 	0,  // wipe_ending_toblack
67 	99, // wipe_cutscene_toblack (hardcoded)
68 
69 	0,  // wipe_specinter_toblack
70 	0,  // wipe_multinter_toblack
71 	0,  // wipe_speclevel_towhite
72 
73 	0,  // wipe_level_final
74 	0,  // wipe_intermission_final
75 	0,  // wipe_continuing_final
76 	0,  // wipe_titlescreen_final
77 	0,  // wipe_timeattack_final
78 	99, // wipe_credits_final
79 	0,  // wipe_evaluation_final
80 	0,  // wipe_gameend_final
81 	99, // wipe_intro_final (hardcoded)
82 	0,  // wipe_ending_final
83 	99, // wipe_cutscene_final (hardcoded)
84 
85 	0,  // wipe_specinter_final
86 	0   // wipe_multinter_final
87 };
88 
89 //--------------------------------------------------------------------------
90 //                        SCREEN WIPE PACKAGE
91 //--------------------------------------------------------------------------
92 
93 boolean WipeInAction = false;
94 boolean WipeStageTitle = false;
95 INT32 lastwipetic = 0;
96 
97 wipestyle_t wipestyle = WIPESTYLE_NORMAL;
98 wipestyleflags_t wipestyleflags = WSF_CROSSFADE;
99 
100 #ifndef NOWIPE
101 static UINT8 *wipe_scr_start; //screen 3
102 static UINT8 *wipe_scr_end; //screen 4
103 static UINT8 *wipe_scr; //screen 0 (main drawing)
104 static fixed_t paldiv = 0;
105 
106 /** Create fademask_t from lump
107   *
108   * \param	lump	Lump name to get data from
109   * \return	fademask_t for lump
110   */
F_GetFadeMask(UINT8 masknum,UINT8 scrnnum)111 static fademask_t *F_GetFadeMask(UINT8 masknum, UINT8 scrnnum) {
112 	static char lumpname[10] = "FADEmmss";
113 	static fademask_t fm = {NULL,0,0,0,0,0};
114 	lumpnum_t lumpnum;
115 	UINT8 *lump, *mask;
116 	size_t lsize;
117 	RGBA_t *pcolor;
118 
119 	if (masknum > 99 || scrnnum > 99)
120 		goto freemask;
121 
122 	sprintf(&lumpname[4], "%.2hu%.2hu", (UINT16)masknum, (UINT16)scrnnum);
123 
124 	lumpnum = W_CheckNumForName(lumpname);
125 	if (lumpnum == LUMPERROR)
126 		goto freemask;
127 
128 	lump = W_CacheLumpNum(lumpnum, PU_CACHE);
129 	lsize = W_LumpLength(lumpnum);
130 	switch (lsize)
131 	{
132 		case 256000: // 640x400
133 			fm.width = 640;
134 			fm.height = 400;
135 			break;
136 		case 64000: // 320x200
137 			fm.width = 320;
138 			fm.height = 200;
139 			break;
140 		case 16000: // 160x100
141 			fm.width = 160;
142 			fm.height = 100;
143 			break;
144 		case 4000: // 80x50 (minimum)
145 			fm.width = 80;
146 			fm.height = 50;
147 			break;
148 
149 		default: // bad lump
150 			CONS_Alert(CONS_WARNING, "Fade mask lump %s of incorrect size, ignored\n", lumpname);
151 		case 0: // end marker (not bad!, but still need clearing)
152 			goto freemask;
153 	}
154 	if (lsize != fm.size)
155 		fm.mask = Z_Realloc(fm.mask, lsize, PU_STATIC, NULL);
156 	fm.size = lsize;
157 
158 	mask = fm.mask;
159 
160 	while (lsize--)
161 	{
162 		// Determine pixel to use from fademask
163 		pcolor = &pMasterPalette[*lump++];
164 		if (wipestyle == WIPESTYLE_COLORMAP)
165 			*mask++ = pcolor->s.red / FADECOLORMAPDIV;
166 		else
167 			*mask++ = FixedDiv((pcolor->s.red+1)<<FRACBITS, paldiv)>>FRACBITS;
168 	}
169 
170 	fm.xscale = FixedDiv(vid.width<<FRACBITS, fm.width<<FRACBITS);
171 	fm.yscale = FixedDiv(vid.height<<FRACBITS, fm.height<<FRACBITS);
172 	return &fm;
173 
174 	// Landing point for freeing data -- do this instead of just returning NULL
175 	// this ensures the fade data isn't remaining in memory, unused
176 	// (could be up to 256,000 bytes if it's a HQ fade!)
177 	freemask:
178 	if (fm.mask)
179 	{
180 		Z_Free(fm.mask);
181 		fm.mask = NULL;
182 		fm.size = 0;
183 	}
184 
185 	return NULL;
186 }
187 
188 /** Draw the stage title.
189   */
F_WipeStageTitle(void)190 void F_WipeStageTitle(void)
191 {
192 	// draw level title
193 	if ((WipeStageTitle && st_overlay)
194 	&& (wipestyle == WIPESTYLE_COLORMAP)
195 	&& G_IsTitleCardAvailable())
196 	{
197 		ST_runTitleCard();
198 		ST_drawWipeTitleCard();
199 	}
200 }
201 
202 /**	Wipe ticker
203   *
204   * \param	fademask	pixels to change
205   */
F_DoWipe(fademask_t * fademask)206 static void F_DoWipe(fademask_t *fademask)
207 {
208 	// Software mask wipe -- optimized; though it might not look like it!
209 	// Okay, to save you wondering *how* this is more optimized than the simpler
210 	// version that came before it...
211 	// ---
212 	// The previous code did two FixedMul calls for every single pixel on the
213 	// screen, of which there are hundreds of thousands -- if not millions -- of.
214 	// This worked fine for smaller screen sizes, but with excessively large
215 	// (1920x1200) screens that meant 4 million+ calls out to FixedMul, and that
216 	// would take /just/ long enough that fades would start to noticably lag.
217 	// ---
218 	// This code iterates over the fade mask's pixels instead of the screen's,
219 	// and deals with drawing over each rectangular area before it moves on to
220 	// the next pixel in the fade mask.  As a result, it's more complex (and might
221 	// look a little messy; sorry!) but it simultaneously runs at twice the speed.
222 	// In addition, we precalculate all the X and Y positions that we need to draw
223 	// from and to, so it uses a little extra memory, but again, helps it run faster.
224 	{
225 		// wipe screen, start, end
226 		UINT8       *w = wipe_scr;
227 		const UINT8 *s = wipe_scr_start;
228 		const UINT8 *e = wipe_scr_end;
229 
230 		// first pixel for each screen
231 		UINT8       *w_base = w;
232 		const UINT8 *s_base = s;
233 		const UINT8 *e_base = e;
234 
235 		// mask data, end
236 		UINT8       *transtbl;
237 		const UINT8 *mask    = fademask->mask;
238 		const UINT8 *maskend = mask + fademask->size;
239 
240 		// rectangle draw hints
241 		UINT32 draw_linestart, draw_rowstart;
242 		UINT32 draw_lineend,   draw_rowend;
243 		UINT32 draw_linestogo, draw_rowstogo;
244 
245 		// rectangle coordinates, etc.
246 		UINT16* scrxpos = (UINT16*)malloc((fademask->width + 1)  * sizeof(UINT16));
247 		UINT16* scrypos = (UINT16*)malloc((fademask->height + 1) * sizeof(UINT16));
248 		UINT16 maskx, masky;
249 		UINT32 relativepos;
250 
251 		// ---
252 		// Screw it, we do the fixed point math ourselves up front.
253 		scrxpos[0] = 0;
254 		for (relativepos = 0, maskx = 1; maskx < fademask->width; ++maskx)
255 			scrxpos[maskx] = (relativepos += fademask->xscale)>>FRACBITS;
256 		scrxpos[fademask->width] = vid.width;
257 
258 		scrypos[0] = 0;
259 		for (relativepos = 0, masky = 1; masky < fademask->height; ++masky)
260 			scrypos[masky] = (relativepos += fademask->yscale)>>FRACBITS;
261 		scrypos[fademask->height] = vid.height;
262 		// ---
263 
264 		maskx = masky = 0;
265 		do
266 		{
267 			draw_rowstart = scrxpos[maskx];
268 			draw_rowend   = scrxpos[maskx + 1];
269 			draw_linestart = scrypos[masky];
270 			draw_lineend   = scrypos[masky + 1];
271 
272 			relativepos = (draw_linestart * vid.width) + draw_rowstart;
273 			draw_linestogo = draw_lineend - draw_linestart;
274 
275 			if (*mask == 0)
276 			{
277 				// shortcut - memcpy source to work
278 				while (draw_linestogo--)
279 				{
280 					M_Memcpy(w_base+relativepos, s_base+relativepos, draw_rowend-draw_rowstart);
281 					relativepos += vid.width;
282 				}
283 			}
284 			else if (*mask >= 10)
285 			{
286 				// shortcut - memcpy target to work
287 				while (draw_linestogo--)
288 				{
289 					M_Memcpy(w_base+relativepos, e_base+relativepos, draw_rowend-draw_rowstart);
290 					relativepos += vid.width;
291 				}
292 			}
293 			else
294 			{
295 				// pointer to transtable that this mask would use
296 				transtbl = R_GetTranslucencyTable((9 - *mask) + 1);
297 
298 				// DRAWING LOOP
299 				while (draw_linestogo--)
300 				{
301 					w = w_base + relativepos;
302 					s = s_base + relativepos;
303 					e = e_base + relativepos;
304 					draw_rowstogo = draw_rowend - draw_rowstart;
305 
306 					while (draw_rowstogo--)
307 						*w++ = transtbl[ ( *e++ << 8 ) + *s++ ];
308 
309 					relativepos += vid.width;
310 				}
311 				// END DRAWING LOOP
312 			}
313 
314 			if (++maskx >= fademask->width)
315 				++masky, maskx = 0;
316 		} while (++mask < maskend);
317 
318 		free(scrxpos);
319 		free(scrypos);
320 	}
321 }
322 
F_DoColormapWipe(fademask_t * fademask,UINT8 * colormap)323 static void F_DoColormapWipe(fademask_t *fademask, UINT8 *colormap)
324 {
325 	// Lactozilla: F_DoWipe for WIPESTYLE_COLORMAP
326 	{
327 		// wipe screen, start, end
328 		UINT8       *w = wipe_scr;
329 		const UINT8 *s = wipe_scr_start;
330 		const UINT8 *e = wipe_scr_end;
331 
332 		// first pixel for each screen
333 		UINT8       *w_base = w;
334 		const UINT8 *s_base = s;
335 		const UINT8 *e_base = e;
336 
337 		// mask data, end
338 		UINT8       *transtbl;
339 		const UINT8 *mask    = fademask->mask;
340 		const UINT8 *maskend = mask + fademask->size;
341 
342 		// rectangle draw hints
343 		UINT32 draw_linestart, draw_rowstart;
344 		UINT32 draw_lineend,   draw_rowend;
345 		UINT32 draw_linestogo, draw_rowstogo;
346 
347 		// rectangle coordinates, etc.
348 		UINT16* scrxpos = (UINT16*)malloc((fademask->width + 1)  * sizeof(UINT16));
349 		UINT16* scrypos = (UINT16*)malloc((fademask->height + 1) * sizeof(UINT16));
350 		UINT16 maskx, masky;
351 		UINT32 relativepos;
352 
353 		// ---
354 		// Screw it, we do the fixed point math ourselves up front.
355 		scrxpos[0] = 0;
356 		for (relativepos = 0, maskx = 1; maskx < fademask->width; ++maskx)
357 			scrxpos[maskx] = (relativepos += fademask->xscale)>>FRACBITS;
358 		scrxpos[fademask->width] = vid.width;
359 
360 		scrypos[0] = 0;
361 		for (relativepos = 0, masky = 1; masky < fademask->height; ++masky)
362 			scrypos[masky] = (relativepos += fademask->yscale)>>FRACBITS;
363 		scrypos[fademask->height] = vid.height;
364 		// ---
365 
366 		maskx = masky = 0;
367 		do
368 		{
369 			draw_rowstart = scrxpos[maskx];
370 			draw_rowend   = scrxpos[maskx + 1];
371 			draw_linestart = scrypos[masky];
372 			draw_lineend   = scrypos[masky + 1];
373 
374 			relativepos = (draw_linestart * vid.width) + draw_rowstart;
375 			draw_linestogo = draw_lineend - draw_linestart;
376 
377 			if (*mask == 0)
378 			{
379 				// shortcut - memcpy source to work
380 				while (draw_linestogo--)
381 				{
382 					M_Memcpy(w_base+relativepos, s_base+relativepos, draw_rowend-draw_rowstart);
383 					relativepos += vid.width;
384 				}
385 			}
386 			else if (*mask >= FADECOLORMAPROWS)
387 			{
388 				// shortcut - memcpy target to work
389 				while (draw_linestogo--)
390 				{
391 					M_Memcpy(w_base+relativepos, e_base+relativepos, draw_rowend-draw_rowstart);
392 					relativepos += vid.width;
393 				}
394 			}
395 			else
396 			{
397 				int nmask = *mask;
398 				if (wipestyleflags & WSF_FADEIN)
399 					nmask = (FADECOLORMAPROWS-1) - nmask;
400 
401 				transtbl = colormap + (nmask * 256);
402 
403 				// DRAWING LOOP
404 				while (draw_linestogo--)
405 				{
406 					w = w_base + relativepos;
407 					s = s_base + relativepos;
408 					e = e_base + relativepos;
409 					draw_rowstogo = draw_rowend - draw_rowstart;
410 
411 					while (draw_rowstogo--)
412 						*w++ = transtbl[*e++];
413 
414 					relativepos += vid.width;
415 				}
416 				// END DRAWING LOOP
417 			}
418 
419 			if (++maskx >= fademask->width)
420 				++masky, maskx = 0;
421 		} while (++mask < maskend);
422 
423 		free(scrxpos);
424 		free(scrypos);
425 	}
426 }
427 #endif
428 
429 /** Save the "before" screen of a wipe.
430   */
F_WipeStartScreen(void)431 void F_WipeStartScreen(void)
432 {
433 #ifndef NOWIPE
434 #ifdef HWRENDER
435 	if(rendermode != render_soft)
436 	{
437 		HWR_StartScreenWipe();
438 		return;
439 	}
440 #endif
441 	wipe_scr_start = screens[3];
442 	I_ReadScreen(wipe_scr_start);
443 #endif
444 }
445 
446 /** Save the "after" screen of a wipe.
447   */
F_WipeEndScreen(void)448 void F_WipeEndScreen(void)
449 {
450 #ifndef NOWIPE
451 #ifdef HWRENDER
452 	if(rendermode != render_soft)
453 	{
454 		HWR_EndScreenWipe();
455 		return;
456 	}
457 #endif
458 	wipe_scr_end = screens[4];
459 	I_ReadScreen(wipe_scr_end);
460 	V_DrawBlock(0, 0, 0, vid.width, vid.height, wipe_scr_start);
461 #endif
462 }
463 
464 /** Verifies every condition for a colormapped fade.
465   */
F_ShouldColormapFade(void)466 boolean F_ShouldColormapFade(void)
467 {
468 #ifndef NOWIPE
469 	if ((wipestyleflags & (WSF_FADEIN|WSF_FADEOUT)) // only if one of those wipestyleflags are actually set
470 	&& !(wipestyleflags & WSF_CROSSFADE)) // and if not crossfading
471 	{
472 		// World
473 		return (gamestate == GS_LEVEL
474 		|| gamestate == GS_TITLESCREEN
475 		// Finales
476 		|| gamestate == GS_CONTINUING
477 		|| gamestate == GS_CREDITS
478 		|| gamestate == GS_EVALUATION
479 		|| gamestate == GS_INTRO
480 		|| gamestate == GS_ENDING
481 		// Menus
482 		|| gamestate == GS_TIMEATTACK);
483 	}
484 #endif
485 	return false;
486 }
487 
488 /** Decides what wipe style to use.
489   */
490 #ifndef NOWIPE
F_DecideWipeStyle(void)491 void F_DecideWipeStyle(void)
492 {
493 	// Set default wipe style
494 	wipestyle = WIPESTYLE_NORMAL;
495 
496 	// Check for colormap wipe style
497 	if (F_ShouldColormapFade())
498 		wipestyle = WIPESTYLE_COLORMAP;
499 }
500 #endif
501 
502 /** Attempt to run a colormap fade,
503     provided all the conditionals were properly met.
504     Returns true if so.
505     I demand you call F_RunWipe after this function.
506   */
F_TryColormapFade(UINT8 wipecolor)507 boolean F_TryColormapFade(UINT8 wipecolor)
508 {
509 #ifndef NOWIPE
510 	if (F_ShouldColormapFade())
511 	{
512 #ifdef HWRENDER
513 		if (rendermode == render_opengl)
514 			F_WipeColorFill(wipecolor);
515 #endif
516 		return true;
517 	}
518 	else
519 #endif
520 	{
521 		F_WipeColorFill(wipecolor);
522 		return false;
523 	}
524 }
525 
526 /** After setting up the screens you want to wipe,
527   * calling this will do a 'typical' wipe.
528   */
F_RunWipe(UINT8 wipetype,boolean drawMenu)529 void F_RunWipe(UINT8 wipetype, boolean drawMenu)
530 {
531 #ifdef NOWIPE
532 	(void)wipetype;
533 	(void)drawMenu;
534 #else
535 	tic_t nowtime;
536 	UINT8 wipeframe = 0;
537 	fademask_t *fmask;
538 
539 	if (!paldiv)
540 		paldiv = FixedDiv(257<<FRACBITS, 11<<FRACBITS);
541 
542 	// Init the wipe
543 	F_DecideWipeStyle();
544 	WipeInAction = true;
545 	wipe_scr = screens[0];
546 
547 	// lastwipetic should either be 0 or the tic we last wiped
548 	// on for fade-to-black
549 	for (;;)
550 	{
551 		// get fademask first so we can tell if it exists or not
552 		fmask = F_GetFadeMask(wipetype, wipeframe++);
553 		if (!fmask)
554 			break;
555 
556 		// wait loop
557 		while (!((nowtime = I_GetTime()) - lastwipetic))
558 			I_Sleep();
559 		lastwipetic = nowtime;
560 
561 		// Wipe styles
562 		if (wipestyle == WIPESTYLE_COLORMAP)
563 		{
564 #ifdef HWRENDER
565 			if (rendermode == render_opengl)
566 			{
567 				// send in the wipe type and wipe frame because we need to cache the graphic
568 				HWR_DoTintedWipe(wipetype, wipeframe-1);
569 			}
570 			else
571 #endif
572 			{
573 				UINT8 *colormap = fadecolormap;
574 				if (wipestyleflags & WSF_TOWHITE)
575 					colormap += (FADECOLORMAPROWS * 256);
576 				F_DoColormapWipe(fmask, colormap);
577 			}
578 
579 			// Draw the title card above the wipe
580 			F_WipeStageTitle();
581 		}
582 		else
583 		{
584 #ifdef HWRENDER
585 			if (rendermode == render_opengl)
586 			{
587 				// send in the wipe type and wipe frame because we need to cache the graphic
588 				HWR_DoWipe(wipetype, wipeframe-1);
589 			}
590 			else
591 #endif
592 				F_DoWipe(fmask);
593 		}
594 
595 		I_OsPolling();
596 		I_UpdateNoBlit();
597 
598 		if (drawMenu)
599 		{
600 #ifdef HAVE_THREADS
601 			I_lock_mutex(&m_menu_mutex);
602 #endif
603 			M_Drawer(); // menu is drawn even on top of wipes
604 #ifdef HAVE_THREADS
605 			I_unlock_mutex(m_menu_mutex);
606 #endif
607 		}
608 
609 		I_FinishUpdate(); // page flip or blit buffer
610 
611 		if (moviemode)
612 			M_SaveFrame();
613 	}
614 
615 	WipeInAction = false;
616 	WipeStageTitle = false;
617 #endif
618 }
619 
620 /** Returns tic length of wipe
621   * One lump equals one tic
622   */
F_GetWipeLength(UINT8 wipetype)623 tic_t F_GetWipeLength(UINT8 wipetype)
624 {
625 #ifdef NOWIPE
626 	(void)wipetype;
627 	return 0;
628 #else
629 	static char lumpname[10] = "FADEmmss";
630 	lumpnum_t lumpnum;
631 	UINT8 wipeframe;
632 
633 	if (wipetype > 99)
634 		return 0;
635 
636 	for (wipeframe = 0; wipeframe < 100; wipeframe++)
637 	{
638 		sprintf(&lumpname[4], "%.2hu%.2hu", (UINT16)wipetype, (UINT16)wipeframe);
639 
640 		lumpnum = W_CheckNumForName(lumpname);
641 		if (lumpnum == LUMPERROR)
642 			return --wipeframe;
643 	}
644 	return --wipeframe;
645 #endif
646 }
647 
648 /** Does the specified wipe exist?
649   */
F_WipeExists(UINT8 wipetype)650 boolean F_WipeExists(UINT8 wipetype)
651 {
652 #ifdef NOWIPE
653 	(void)wipetype;
654 	return false;
655 #else
656 	static char lumpname[10] = "FADEmm00";
657 	lumpnum_t lumpnum;
658 
659 	if (wipetype > 99)
660 		return false;
661 
662 	sprintf(&lumpname[4], "%.2hu00", (UINT16)wipetype);
663 
664 	lumpnum = W_CheckNumForName(lumpname);
665 	return !(lumpnum == LUMPERROR);
666 #endif
667 }
668