add some safety checks to graphics routines
[d2d-psx.git] / src / vga.c
blobc33fed5b05ad70cd43c59d1ba2a653008d2c967a
1 /*
2 * Copyright (C) Prikol Software 1996-1997
3 * Copyright (C) Aleksey Volynskov 1996-1997
4 * Copyright (C) <ARembo@gmail.com> 2011
5 * Copyright (C) fgsfds 2019
7 * This file is part of the Doom2D PSX project.
9 * Doom2D PSX is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License version 2 as
11 * published by the Free Software Foundation.
13 * Doom2D PSX is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, see <http://www.gnu.org/licenses/> or
20 * write to the Free Software Foundation, Inc.,
21 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
24 #include "glob.h"
25 #include "vga.h"
26 #include "error.h"
27 #include "view.h"
29 #include <libetc.h>
30 #include <libgte.h>
31 #include <libgpu.h>
33 #define NUMPALS 10
35 #define BORDER 96
36 #define OT_LEN 512
37 #define PACKET_MAX 40000
39 #define LAYERS 8
40 #define LAYER_SKY 0
41 #define LAYER_BG 1
42 #define LAYER_SPR 2
43 #define LAYER_FG 3
44 #define LAYER_UI 4
46 #define PSXRGB(r, g, b) ((((b) >> 3) << 10) | (((g) >> 3) << 5) | ((r) >> 3))
48 static u_char loadimg[] = {
49 #include "loadpic.inc"
52 vgapal main_pal;
53 unsigned short clut_id[NUMPALS];
54 unsigned char clut_tab[NUMPALS] = {
55 0x00, 0x18, 0x20, 0x40, 0x58,
56 0x60, 0x80, 0xB0, 0xC0, 0xD0,
59 static int ofsx = 0;
60 static int ofsy = 0;
62 int cx1, cx2, cy1, cy2;
64 char fullscreen = OFF;
66 static struct drawbuf {
67 DISPENV dispenv;
68 DRAWENV drawenv;
69 u_char gpudata[PACKET_MAX];
70 u_long ot[OT_LEN];
71 } dbuf[2];
73 struct drawbuf *curbuf = dbuf + 0;
74 static u_char *primptr;
75 static unsigned short curtpage = 0;
76 static RECT drawclip;
77 static int bufidx = 0;
78 static int curot = 0;
80 #define MAX_CACHEDPICS 1024
81 #define CACHE_STARTX 320 // is already 64-aligned
82 #define CACHE_WIDTH 704 // 1024 - CACHE_STARTX
83 #define CACHE_HEIGHT 512
85 static cacheimg piccache[MAX_CACHEDPICS];
86 static u_short vram_alloc[CACHE_WIDTH];
88 short V_init(void) {
89 int i, j;
90 unsigned char *p;
91 unsigned short *clut, *cp;
93 ResetGraph(3);
94 SetVideoMode(REGION);
96 SetDefDispEnv(&dbuf[0].dispenv, 0, 0, SCRW, SCRH);
97 SetDefDrawEnv(&dbuf[0].drawenv, 0, SCRH, SCRW, SCRH);
98 SetDefDispEnv(&dbuf[1].dispenv, 0, SCRH, SCRW, SCRH);
99 SetDefDrawEnv(&dbuf[1].drawenv, 0, 0, SCRW, SCRH);
101 for (i = 0; i < 2; ++i) {
102 dbuf[i].drawenv.isbg = 1;
103 dbuf[i].drawenv.dtd = 0;
104 dbuf[i].drawenv.dfe = 1;
105 dbuf[i].dispenv.screen.w = 256; // in "display" coordinates
106 dbuf[i].dispenv.screen.h = SCRH;
107 dbuf[i].dispenv.screen.x = 0;
108 dbuf[i].dispenv.screen.y = SCRY;
111 PutDispEnv(&dbuf[0].dispenv);
112 PutDrawEnv(&dbuf[0].drawenv);
114 // clear the splash leftovers and shit
115 drawclip.w = 1024;
116 drawclip.h = 512;
117 ClearImage(&drawclip, 0, 0, 0);
118 DrawSync(0);
120 // upload cluts into vram
121 p = &main_pal[0].r;
122 cp = clut = malloc3(2 * 256);
123 for (i = 0; i < 256; ++i, ++cp, p += 3) {
124 *(p + 0) <<= 2;
125 *(p + 1) <<= 2;
126 *(p + 2) <<= 2;
127 *cp = PSXRGB(*(p + 0), *(p + 1), *(p + 2));
128 if (i && !*cp) *cp = 0x8000;
130 // base palette
131 clut_id[0] = LoadClut((u_long*)clut, VRAM_PAL_X, VRAM_PAL_Y);
132 // generate several variations for player colors and shit
133 for (i = 1; i < NUMPALS; ++i) {
134 for (j = 0x70; j < 0x80; ++j)
135 clut[j] = clut[j - 0x70 + clut_tab[i]];
136 clut_id[i] = LoadClut((u_long*)clut, VRAM_PAL_X, VRAM_PAL_Y+i);
137 DrawSync(0);
139 free3(clut);
141 ClearOTag(dbuf[0].ot, OT_LEN);
142 ClearOTag(dbuf[1].ot, OT_LEN);
144 SetDispMask(1);
146 printf("V_init(): initialized video at %dx%dx16\n", SCRW, SCRH);
148 return 0;
151 void V_done(void) {
152 ResetGraph(0);
155 void V_setclip(short x, short w, short y, short h) {
156 drawclip.x = x;
157 drawclip.y = y + !bufidx * SCRH;
158 drawclip.w = w;
159 drawclip.h = h;
160 SetDrawArea((DR_AREA *)primptr, &drawclip);
161 addPrim(curbuf->ot + curot, primptr);
162 primptr += sizeof(DR_AREA);
163 if (curot < OT_LEN-1) ++curot;
166 void V_setrect(short x, short w, short y, short h) {
167 cx1 = x;
168 cx2 = x + w - 1;
169 cy1 = y;
170 cy2 = y + h - 1;
171 if (cx1 < 0)
172 cx1 = 0;
173 if (cx2 >= SCRW)
174 cx2 = SCRW - 1;
175 if (cy1 < 0)
176 cy1 = 0;
177 if (cy2 >= SCRH)
178 cy2 = SCRH - 1;
181 void V_center(int f) {
182 if (f)
183 V_offset(SCRW / 2 - 320 / 2, SCRH / 2 - 200 / 2);
184 else
185 V_offset(0, 0);
188 void V_offset(int ox, int oy) {
189 ofsx = ox;
190 ofsy = oy;
193 static void draw_spr(short x, short y, cacheimg *i, int d, int c) {
194 int w, h, sx, sy;
195 POLY_FT4 *prim;
197 if (!i || !i->img || (primptr + sizeof(POLY_FT4) >= curbuf->gpudata + PACKET_MAX))
198 return; // trying to draw nothing or ran out of prim buffer
200 w = i->img->w;
201 h = i->img->h;
202 sx = i->img->sx;
203 sy = i->img->sy;
205 V_uploadpic(i); // attempt to upload right away so as to not cause slowdown later
207 if (d & 1) x = x - w + sx + ofsx; else x = x - sx + ofsx;
208 if (d & 2) y = y - h + sy + ofsy; else y = y - sy + ofsy;
210 if (x + w < cx1 || x >= cx2 || y + h < cy1 || y >= cy2)
211 return; // out of sight, out of mind
213 prim = (POLY_FT4 *)primptr; primptr += sizeof(POLY_FT4);
214 setPolyFT4(prim);
216 prim->tpage = i->tpage;
217 prim->clut = clut_id[c];
218 setRGB0(prim, 128, 128, 128);
220 prim->x0 = prim->x2 = x;
221 prim->y0 = prim->y1 = y;
222 prim->x1 = prim->x3 = x + w;
223 prim->y2 = prim->y3 = y + h;
225 if (w > 255) w = 255;
226 if (h > 255) h = 255;
228 // hflip
229 if (d & 1) {
230 prim->u1 = prim->u3 = i->u;
231 prim->u0 = prim->u2 = i->u + w - 1;
232 } else {
233 prim->u0 = prim->u2 = i->u;
234 prim->u1 = prim->u3 = i->u + w;
236 // vflip
237 if (d & 2) {
238 prim->v2 = prim->v3 = i->v;
239 prim->v0 = prim->v1 = i->v + h - 1;
240 } else {
241 prim->v0 = prim->v1 = i->v;
242 prim->v2 = prim->v3 = i->v + h;
245 addPrim(curbuf->ot + curot, prim);
246 if (curot < OT_LEN-1) ++curot;
249 // use this to draw textures more than 256 in width
250 // still limited to 256 height
251 void V_bigpic(int x, int y, cacheimg *i) {
252 int xx, ww, tpage;
253 int w = i->img->w;
254 int h = i->img->h;
255 POLY_FT4 *prim;
257 V_uploadpic(i);
259 x += ofsx;
260 y += ofsy;
262 tpage = i->tpage;
264 // 1 tpage is normally 64 pixels wide, but our textures are 8bpp,
265 // so it's basically 128 wide
266 for (xx = 0; xx < w; xx += 256, tpage += 2) {
267 prim = (POLY_FT4 *)primptr; primptr += sizeof(POLY_FT4);
268 setPolyFT4(prim);
269 prim->tpage = tpage;
270 prim->clut = clut_id[0];
271 setRGB0(prim, 128, 128, 128);
272 ww = w - xx;
273 if (ww > 256) ww = 256;
274 prim->x0 = prim->x2 = x + xx;
275 prim->y0 = prim->y1 = y;
276 prim->x1 = prim->x3 = x + xx + ww;
277 prim->y2 = prim->y3 = y + h;
278 prim->u0 = prim->u2 = i->u;
279 prim->u1 = prim->u3 = i->u + ww - 1;
280 prim->v0 = prim->v1 = i->v;
281 prim->v2 = prim->v3 = i->v + h;
282 addPrim(curbuf->ot + curot, prim);
285 if (curot < OT_LEN-1) ++curot;
288 void V_rotspr(int x, int y, cacheimg *i, int d) {
289 x += i->img->w * ((d & 1) ? 1 : 0);
290 y += i->img->h * ((d & 2) ? 1 : 0);
291 draw_spr(x, y, i, d, 0);
294 void V_pic(short x, short y, cacheimg * i) {
295 draw_spr(x, y, i, 0, 0);
298 void V_manspr(int x, int y, cacheimg * p, unsigned char c) {
299 draw_spr(x, y, p, 0, c);
302 void V_manspr2(int x, int y, cacheimg * p, unsigned char c) {
303 draw_spr(x, y, p, 1, c);
306 // вывести точку цвета c в координатах (x,y)
307 void V_dot(short x, short y, unsigned char c) {
308 TILE_1 *prim;
310 if (x < cx1 || x >= cx2 || y < cy1 || y >= cy2)
311 return;
313 prim = (TILE_1 *)primptr;
314 primptr += sizeof(TILE_1);
316 setTile1(prim);
317 prim->x0 = x;
318 prim->y0 = y;
319 prim->r0 = main_pal[c].r;
320 prim->g0 = main_pal[c].g;
321 prim->b0 = main_pal[c].b;
323 addPrim(curbuf->ot + curot, prim);
326 void smoke_sprf(int x, int y, byte c) {
329 void flame_sprf(int x, int y, byte c) {
332 void V_spr(short x, short y, cacheimg * i) {
333 draw_spr(x, y, i, 0, 0);
336 void V_spr2(short x, short y, cacheimg * i) {
337 draw_spr(x, y, i, 1, 0);
340 void V_clr(short x, short w, short y, short h, unsigned char c) {
341 TILE *prim;
343 prim = (TILE *)primptr;
344 primptr += sizeof(TILE);
346 setTile(prim);
347 setXY0(prim, x, y);
348 setWH(prim, w, h);
349 prim->r0 = main_pal[c].r;
350 prim->g0 = main_pal[c].g;
351 prim->b0 = main_pal[c].b;
353 addPrim(curbuf->ot + curot, prim);
354 if (curot < OT_LEN-1) ++curot;
357 void V_transclr(short x, short w, short y, short h, unsigned char c) {
358 TILE *prim;
360 prim = (TILE *)primptr;
361 primptr += sizeof(TILE);
363 setTile(prim);
364 setXY0(prim, x, y);
365 setWH(prim, w, h);
366 prim->r0 = main_pal[c].r;
367 prim->g0 = main_pal[c].g;
368 prim->b0 = main_pal[c].b;
369 setSemiTrans(prim, 1);
371 addPrim(curbuf->ot + curot, prim);
372 if (curot < OT_LEN-1) ++curot;
375 // установить палитру из массива p
376 void VP_setall(void * p) {
377 VP_set(p, 0, 256);
380 // установить n цветов, начиная с f, из массива p
381 void VP_set(void * p, short f, short n) {
384 // установить адрес экранного буфера
385 // NULL - реальный экран
386 void V_setscr(void * p) {
389 // скопировать прямоугольник на экран
390 void V_copytoscr(short x, short w, short y, short h) {
393 void V_maptoscr(int x, int w, int y, int h, void * cmap) {
396 void V_remap_rect(int x, int y, int w, int h, byte * cmap) {
399 extern void * walp[256];
401 static inline void draw_wall(cacheimg *pic, int sx, int sy) {
402 int w = pic->img->w;
403 int h = pic->img->h;
404 POLY_FT4 *prim;
406 V_uploadpic(pic);
408 sx -= pic->img->sx;
409 sy -= pic->img->sy;
411 if (sx + w < cx1 || sx > cx2 || sy + h < cy1 || sy > cy2)
412 return; // have to do this because some pics are big so the border is 96px
414 // TODO: this is really fucking bad, fix the SPRT shit below and use it instead
415 prim = (POLY_FT4 *)primptr;
416 primptr += sizeof(POLY_FT4);
418 setPolyFT4(prim);
420 prim->tpage = pic->tpage;
422 prim->x0 = prim->x2 = sx;
423 prim->y0 = prim->y1 = sy;
424 prim->x1 = prim->x3 = sx + w;
425 prim->y2 = prim->y3 = sy + h;
427 prim->u0 = prim->u2 = pic->u;
428 prim->v0 = prim->v1 = pic->v;
429 prim->u1 = prim->u3 = pic->u + w;
430 prim->v2 = prim->v3 = pic->v + h;
431 prim->clut = clut_id[0];
432 setRGB0(prim, 128, 128, 128);
434 addPrim(curbuf->ot + curot, prim);
436 /* TODO: this works but TPAGE management is fucked
437 which makes it botch some tiles
438 if (pic->tpage != curtpage) {
439 DR_TPAGE *tpage;
440 if (drawhack && curot < OT_LEN-1) ++curot;
441 tpage = (DR_TPAGE *)primptr;
442 primptr += sizeof(DR_TPAGE);
443 curtpage = pic->tpage;
444 setDrawTPage(tpage, 1, 0, curtpage);
445 addPrim(curbuf->ot + curot, tpage);
446 if (curot < OT_LEN-1) ++curot;
449 if (w == 8 && h == 8) {
450 SPRT_8 *prim = (SPRT_8 *)primptr;
451 primptr += sizeof(SPRT_8);
452 setSprt8(prim);
453 setRGB0(prim, 128, 128, 128);
454 setXY0(prim, sx, sy);
455 setUV0(prim, pic->u, pic->v);
456 prim->clut = clut_id[0];
457 addPrim(curbuf->ot + curot, prim);
458 } else {
459 SPRT *prim = (SPRT *)primptr;
460 primptr += sizeof(SPRT);
461 setSprt(prim);
462 setRGB0(prim, 128, 128, 128);
463 setWH(prim, pic->img->w, pic->img->h);
464 setXY0(prim, sx - pic->img->sx, sy - pic->img->sy);
465 setUV0(prim, pic->u, pic->v);
466 prim->clut = clut_id[0];
467 addPrim(curbuf->ot + curot, prim);
470 drawhack = 1;
474 void Z_drawfld(byte * fld, int bg) {
475 static const wrgb[3][3] = { {0, 0, 255}, {0, 255, 0}, {255, 0, 0} };
476 byte *p, *prow;
477 int x, y, camx1, camy1, camx2, camy2;
478 int sx, sy, hw, hh;
479 cacheimg *pic;
481 hw = WD >> 1;
482 hh = (HT >> 1) + 1 + w_o;
483 camx1 = (w_x - (WD >> 1)) >> 3;
484 camx2 = camx1 + (WD >> 3) + 1;
485 camy1 = (w_y - (HT >> 1)) >> 3;
486 camy2 = camy1 + (HT >> 3) + 2;
488 if ((camx1 -= 4) < 0) camx1 = 0;
489 if ((camy1 -= 7) < 0) camy1 = 0;
490 if (camx2 > FLDW) camx2 = FLDW;
491 if (camy2 > FLDH) camy2 = FLDH;
493 prow = fld + camy1 * FLDW + camx1;
494 for (y = camy1; y < camy2; ++y, prow += FLDW) {
495 p = prow;
496 for (x = camx1; x < camx2; ++x, ++p) {
497 sx = (x << 3) - w_x + hw;
498 sy = (y << 3) - w_y + hh;
499 if (*p) {
500 pic = walp[*p];
501 if ((u_long)pic <= 3) {
502 int i = (u_long)pic - 1;
503 TILE_8 *prim = (TILE_8 *)primptr; primptr += sizeof(TILE_8);
504 setTile8(prim);
505 setXY0(prim, sx, sy);
506 setRGB0(prim, wrgb[i][0], wrgb[i][1], wrgb[i][2]);
507 setSemiTrans(prim, 1);
508 addPrim(curbuf->ot + curot, prim);
509 } else {
510 draw_wall(pic, sx, sy);
516 if (curot < OT_LEN-1) ++curot;
519 static void UnfuckLoadImage(RECT *r, vgaimg *img) {
520 // resize and load image that has odd width
521 // this shouldn't happen to any wallaby
522 short i;
523 short w = img->w;
524 short h = img->h;
525 short nw = w + 1;
526 u_char *pout;
527 u_char *pin = (u_char *)img + sizeof(vgaimg);
528 u_char *buf = malloc3(nw * h);
529 if (!buf) ERR_fatal("UnfuckLoadImage(): out of memory\n");
530 for (pout = buf, i = 0; i < h; ++i, pin += w, pout += nw) {
531 memcpy(pout, pin, w);
532 *(pout + w) = 0; // fill extra column with transparency (or black)
534 LoadImage(r, (u_long *)buf);
535 free3(buf);
538 void V_fitpic(cacheimg *cimg) {
539 short besth = CACHE_HEIGHT;
540 short i, th, j;
541 u_short vrx, vry;
542 RECT r;
544 if (!cimg)
545 ERR_fatal("V_fitpic(): NULL image");
547 // since our images are 8bpp, 2 pixels fit into one vram cell
548 // so divide w by 2 rounding up, and also round h up to 2
549 r.x = 0;
550 r.y = 0;
551 r.w = (cimg->img->w + 1) >> 1;
552 r.h = cimg->img->h;
554 if (r.w > 128)
555 printf("V_fitpic(%dx%d): WARNING, > 256px width!\n", cimg->img->w, cimg->img->h);
557 for (i = 0; i < CACHE_WIDTH - r.w; ++i) {
558 th = 0;
559 j = 0;
560 while (j < r.w) {
561 if (vram_alloc[i + j] >= besth)
562 break;
563 if (vram_alloc[i + j] > th)
564 th = vram_alloc[i + j];
565 ++j;
567 if (j == r.w) {
568 r.x = i;
569 besth = th;
570 r.y = th;
574 if (besth + r.h > CACHE_HEIGHT)
575 ERR_fatal("V_fitpic(%dx%d): VRAM atlas full!\n", (int)cimg->img->w, (int)cimg->img->h);
577 if (r.y < 256 && r.y + r.h > 256) {
578 besth = 256; // crossing 256 pixel boundary, snap to the next 256
579 r.y = 256;
582 for (i = 0; i < r.w; ++i)
583 vram_alloc[r.x + i] = besth + r.h;
585 r.x += CACHE_STARTX;
587 if (cimg->img->w & 1)
588 UnfuckLoadImage(&r, cimg->img);
589 else
590 LoadImage(&r, (u_long*)((u_char*)cimg->img + sizeof(vgaimg)));
592 vrx = r.x & 0x3C0;
593 vry = r.y & 0x100;
594 cimg->tpage = getTPage(1, 0, vrx, vry);
595 cimg->u = ((r.x - vrx) << 1) & 0xFF;
596 cimg->v = r.y & 0xFF;
599 void V_clearmem(void) {
600 int i;
601 memset(vram_alloc, 0, sizeof(vram_alloc));
602 for (i = 0; i < MAX_CACHEDPICS; ++i)
603 piccache[i].tpage = 0xFFFF;
606 cacheimg *V_cachepic(vgaimg *img) {
607 int i = 0;
608 if (!img) return NULL;
609 for (i = 0; i < MAX_CACHEDPICS; ++i) {
610 if (piccache[i].img == img)
611 return piccache + i;
612 if (!piccache[i].img) {
613 piccache[i].img = img;
614 piccache[i].tpage = 0xFFFF;
615 return piccache + i;
618 ERR_fatal("V_cachepic(): ran out of cache space!\n");
621 void V_uncachepic(cacheimg *cimg) {
622 if (!cimg) return;
623 M_unlock(cimg->img);
624 cimg->img = NULL;
625 cimg->tpage = 0xFFFF;
628 void V_startframe(void) {
629 bufidx ^= 1;
630 curbuf = dbuf + bufidx;
631 ClearOTag(curbuf->ot, OT_LEN);
632 curot = 0;
633 primptr = curbuf->gpudata;
634 curtpage = 0;
637 void V_endframe(void) {
638 DrawSync(0);
639 // VSync(0);
640 PutDispEnv(&curbuf->dispenv);
641 PutDrawEnv(&curbuf->drawenv);
642 DrawOTag(curbuf->ot);
645 void Z_loadingscreen(unsigned int x) {
646 RECT r;
647 // finish all rendering
648 DrawSync(0);
649 VSync(0);
650 PutDispEnv(&curbuf->dispenv);
651 PutDrawEnv(&curbuf->drawenv);
652 // clear the screen
653 // r.x = 0;
654 r.y = SCRH * bufidx;
655 // r.w = SCRW;
656 // r.h = SCRH;
657 // ClearImage(&r, 0, 0, 0);
658 // and now just yeet the pic into framebuffer
659 r.x = SCRW - 24;
660 r.y += SCRH - 32;
661 r.w = 15;
662 r.h = 15;
663 LoadImage2(&r, (u_long*)loadimg);
666 void Z_clearscreen(int rr, int g, int b) {
667 RECT r;
668 // finish all rendering
669 DrawSync(0);
670 VSync(0);
671 PutDispEnv(&curbuf->dispenv);
672 PutDrawEnv(&curbuf->drawenv);
673 r.x = 0;
674 r.y = 0;
675 r.w = SCRW;
676 r.h = SCRH*2;
677 ClearImage(&r, rr, g, b);