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