2 Copyright (C) 1996-1997 Id Software, Inc.
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 See the GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
28 #include "ft2_fontdefs.h"
34 // this flag indicates that it should be loaded and unloaded on demand
36 // texture flags to upload with
38 // texture may be freed after a while
41 skinframe_t *skinframe;
42 // used for hash lookups
43 struct cachepic_s *chain;
44 // flags - CACHEPICFLAG_NEWPIC for example
51 static mempool_t *fonts_mempool = NULL;
53 cvar_t r_textshadow = {CF_CLIENT | CF_ARCHIVE, "r_textshadow", "0", "draws a shadow on all text to improve readability (note: value controls offset, 1 = 1 pixel, 1.5 = 1.5 pixels, etc)"};
54 cvar_t r_textbrightness = {CF_CLIENT | CF_ARCHIVE, "r_textbrightness", "0", "additional brightness for text color codes (0 keeps colors as is, 1 makes them all white)"};
55 cvar_t r_textcontrast = {CF_CLIENT | CF_ARCHIVE, "r_textcontrast", "1", "additional contrast for text color codes (1 keeps colors as is, 0 makes them all black)"};
57 cvar_t r_font_postprocess_blur = {CF_CLIENT | CF_ARCHIVE, "r_font_postprocess_blur", "0", "font blur amount"};
58 cvar_t r_font_postprocess_outline = {CF_CLIENT | CF_ARCHIVE, "r_font_postprocess_outline", "0", "font outline amount"};
59 cvar_t r_font_postprocess_shadow_x = {CF_CLIENT | CF_ARCHIVE, "r_font_postprocess_shadow_x", "0", "font shadow X shift amount, applied during outlining"};
60 cvar_t r_font_postprocess_shadow_y = {CF_CLIENT | CF_ARCHIVE, "r_font_postprocess_shadow_y", "0", "font shadow Y shift amount, applied during outlining"};
61 cvar_t r_font_postprocess_shadow_z = {CF_CLIENT | CF_ARCHIVE, "r_font_postprocess_shadow_z", "0", "font shadow Z shift amount, applied during blurring"};
62 cvar_t r_font_hinting = {CF_CLIENT | CF_ARCHIVE, "r_font_hinting", "3", "0 = no hinting, 1 = light autohinting, 2 = full autohinting, 3 = full hinting"};
63 cvar_t r_font_antialias = {CF_CLIENT | CF_ARCHIVE, "r_font_antialias", "1", "0 = monochrome, 1 = grey" /* , 2 = rgb, 3 = bgr" */};
64 cvar_t r_font_always_reload = {CF_CLIENT | CF_ARCHIVE, "r_font_always_reload", "0", "reload a font even given the same loadfont command. useful for trying out different versions of the same font file"};
65 cvar_t r_nearest_2d = {CF_CLIENT | CF_ARCHIVE, "r_nearest_2d", "0", "use nearest filtering on all 2d textures (including conchars)"};
66 cvar_t r_nearest_conchars = {CF_CLIENT | CF_ARCHIVE, "r_nearest_conchars", "0", "use nearest filtering on conchars texture"};
68 //=============================================================================
69 /* Support Routines */
71 static cachepic_t *cachepichash[CACHEPICHASHSIZE];
72 static cachepic_t cachepics[MAX_CACHED_PICS];
73 static int numcachepics;
75 rtexturepool_t *drawtexturepool;
84 // FIXME: move this to client somehow
85 cachepic_t *Draw_CachePic_Flags(const char *path, unsigned int cachepicflags)
91 texflags = TEXF_ALPHA;
92 if (!(cachepicflags & CACHEPICFLAG_NOCLAMP))
93 texflags |= TEXF_CLAMP;
94 if (cachepicflags & CACHEPICFLAG_MIPMAP)
95 texflags |= TEXF_MIPMAP;
96 if (!(cachepicflags & CACHEPICFLAG_NOCOMPRESSION) && gl_texturecompression_2d.integer && gl_texturecompression.integer)
97 texflags |= TEXF_COMPRESS;
98 if (cachepicflags & CACHEPICFLAG_LINEAR)
99 texflags |= TEXF_FORCELINEAR;
100 else if ((cachepicflags & CACHEPICFLAG_NEAREST) || r_nearest_2d.integer)
101 texflags |= TEXF_FORCENEAREST;
103 // check whether the picture has already been cached
104 crc = CRC_Block((unsigned char *)path, strlen(path));
105 hashkey = ((crc >> 8) ^ crc) % CACHEPICHASHSIZE;
106 for (pic = cachepichash[hashkey];pic;pic = pic->chain)
108 if (!strcmp(path, pic->name))
110 // if it was created (or replaced) by Draw_NewPic, just return it
111 if (!(pic->flags & CACHEPICFLAG_NEWPIC))
113 // reload the pic if texflags changed in important ways
114 // ignore TEXF_COMPRESS when comparing, because fallback pics remove the flag, and ignore TEXF_MIPMAP because QC specifies that
115 if ((pic->texflags ^ texflags) & ~(TEXF_COMPRESS | TEXF_MIPMAP))
117 Con_DPrintf("Draw_CachePic(\"%s\"): frame %i: reloading pic due to mismatch on flags\n", path, draw_frame);
120 if (!pic->skinframe || !pic->skinframe->base)
122 if (pic->flags & CACHEPICFLAG_FAILONMISSING)
124 Con_DPrintf("Draw_CachePic(\"%s\"): frame %i: reloading pic\n", path, draw_frame);
127 if (!(cachepicflags & CACHEPICFLAG_NOTPERSISTENT))
128 pic->autoload = false; // caller is making this pic persistent
131 R_SkinFrame_MarkUsed(pic->skinframe);
132 pic->lastusedframe = draw_frame;
137 if (numcachepics == MAX_CACHED_PICS)
139 Con_DPrintf ("Draw_CachePic(\"%s\"): frame %i: numcachepics == MAX_CACHED_PICS\n", path, draw_frame);
140 // FIXME: support NULL in callers?
141 return cachepics; // return the first one
143 Con_DPrintf("Draw_CachePic(\"%s\"): frame %i: loading pic%s\n", path, draw_frame, (cachepicflags & CACHEPICFLAG_NOTPERSISTENT) ? " notpersist" : "");
144 pic = cachepics + (numcachepics++);
145 memset(pic, 0, sizeof(*pic));
146 strlcpy (pic->name, path, sizeof(pic->name));
148 pic->chain = cachepichash[hashkey];
149 cachepichash[hashkey] = pic;
153 R_SkinFrame_PurgeSkinFrame(pic->skinframe);
155 pic->flags = cachepicflags;
156 pic->texflags = texflags;
157 pic->autoload = (cachepicflags & CACHEPICFLAG_NOTPERSISTENT) != 0;
158 pic->lastusedframe = draw_frame;
162 // reload image after it was unloaded or texflags changed significantly
163 R_SkinFrame_LoadExternal_SkinFrame(pic->skinframe, pic->name, texflags | TEXF_FORCE_RELOAD, (cachepicflags & CACHEPICFLAG_QUIET) == 0, (cachepicflags & CACHEPICFLAG_FAILONMISSING) == 0);
167 // load high quality image (this falls back to low quality too)
168 pic->skinframe = R_SkinFrame_LoadExternal(pic->name, texflags | TEXF_FORCE_RELOAD, (cachepicflags & CACHEPICFLAG_QUIET) == 0, (cachepicflags & CACHEPICFLAG_FAILONMISSING) == 0);
171 // get the dimensions of the image we loaded (if it was successful)
172 if (pic->skinframe && pic->skinframe->base)
174 pic->width = R_TextureWidth(pic->skinframe->base);
175 pic->height = R_TextureHeight(pic->skinframe->base);
178 // check for a low quality version of the pic and use its size if possible, to match the stock hud
179 Image_GetStockPicSize(pic->name, &pic->width, &pic->height);
184 cachepic_t *Draw_CachePic (const char *path)
186 return Draw_CachePic_Flags (path, 0); // default to persistent!
189 const char *Draw_GetPicName(cachepic_t *pic)
196 int Draw_GetPicWidth(cachepic_t *pic)
203 int Draw_GetPicHeight(cachepic_t *pic)
210 qbool Draw_IsPicLoaded(cachepic_t *pic)
214 if (pic->autoload && (!pic->skinframe || !pic->skinframe->base))
216 Con_DPrintf("Draw_IsPicLoaded(\"%s\"): Loading external skin\n", pic->name);
217 pic->skinframe = R_SkinFrame_LoadExternal(pic->name, pic->texflags | TEXF_FORCE_RELOAD, false, true);
219 // skinframe will only be NULL if the pic was created with CACHEPICFLAG_FAILONMISSING and not found
220 return pic->skinframe != NULL && pic->skinframe->base != NULL;
223 rtexture_t *Draw_GetPicTexture(cachepic_t *pic)
227 if (pic->autoload && (!pic->skinframe || !pic->skinframe->base))
229 Con_DPrintf("Draw_GetPicTexture(\"%s\"): Loading external skin\n", pic->name);
230 pic->skinframe = R_SkinFrame_LoadExternal(pic->name, pic->texflags | TEXF_FORCE_RELOAD, false, true);
232 pic->lastusedframe = draw_frame;
233 return pic->skinframe ? pic->skinframe->base : NULL;
236 void Draw_Frame(void)
240 static double nextpurgetime;
241 if (nextpurgetime > host.realtime)
243 nextpurgetime = host.realtime + 0.05;
244 for (i = 0, pic = cachepics;i < numcachepics;i++, pic++)
246 if (pic->autoload && pic->skinframe && pic->skinframe->base && pic->lastusedframe < draw_frame - 3)
248 Con_DPrintf("Draw_Frame(%i): Unloading \"%s\"\n", draw_frame, pic->name);
249 R_SkinFrame_PurgeSkinFrame(pic->skinframe);
255 cachepic_t *Draw_NewPic(const char *picname, int width, int height, unsigned char *pixels_bgra, textype_t textype, int texflags)
260 crc = CRC_Block((unsigned char *)picname, strlen(picname));
261 hashkey = ((crc >> 8) ^ crc) % CACHEPICHASHSIZE;
262 for (pic = cachepichash[hashkey];pic;pic = pic->chain)
263 if (!strcmp (picname, pic->name))
268 if (pic->flags & CACHEPICFLAG_NEWPIC && pic->skinframe && pic->skinframe->base && pic->width == width && pic->height == height)
270 Con_DPrintf("Draw_NewPic(\"%s\"): frame %i: updating texture\n", picname, draw_frame);
271 R_UpdateTexture(pic->skinframe->base, pixels_bgra, 0, 0, 0, width, height, 1, 0);
272 R_SkinFrame_MarkUsed(pic->skinframe);
273 pic->lastusedframe = draw_frame;
276 Con_DPrintf("Draw_NewPic(\"%s\"): frame %i: reloading pic because flags/size changed\n", picname, draw_frame);
280 if (numcachepics == MAX_CACHED_PICS)
282 Con_DPrintf ("Draw_NewPic(\"%s\"): frame %i: numcachepics == MAX_CACHED_PICS\n", picname, draw_frame);
283 // FIXME: support NULL in callers?
284 return cachepics; // return the first one
286 Con_DPrintf("Draw_NewPic(\"%s\"): frame %i: creating new cachepic\n", picname, draw_frame);
287 pic = cachepics + (numcachepics++);
288 memset(pic, 0, sizeof(*pic));
289 strlcpy (pic->name, picname, sizeof(pic->name));
291 pic->chain = cachepichash[hashkey];
292 cachepichash[hashkey] = pic;
295 R_SkinFrame_PurgeSkinFrame(pic->skinframe);
297 pic->autoload = false;
298 pic->flags = CACHEPICFLAG_NEWPIC; // disable texflags checks in Draw_CachePic
299 pic->flags |= (texflags & TEXF_CLAMP) ? 0 : CACHEPICFLAG_NOCLAMP;
300 pic->flags |= (texflags & TEXF_FORCENEAREST) ? CACHEPICFLAG_NEAREST : 0;
302 pic->height = height;
303 pic->skinframe = R_SkinFrame_LoadInternalBGRA(picname, texflags | TEXF_FORCE_RELOAD, pixels_bgra, width, height, 0, 0, 0, vid.sRGB2D);
304 pic->lastusedframe = draw_frame;
308 void Draw_FreePic(const char *picname)
313 // this doesn't really free the pic, but does free its texture
314 crc = CRC_Block((unsigned char *)picname, strlen(picname));
315 hashkey = ((crc >> 8) ^ crc) % CACHEPICHASHSIZE;
316 for (pic = cachepichash[hashkey];pic;pic = pic->chain)
318 if (!strcmp (picname, pic->name) && pic->skinframe)
320 Con_DPrintf("Draw_FreePic(\"%s\"): frame %i: freeing pic\n", picname, draw_frame);
321 R_SkinFrame_PurgeSkinFrame(pic->skinframe);
327 static float snap_to_pixel_x(float x, float roundUpAt);
328 extern int con_linewidth; // to force rewrapping
329 void LoadFont(qbool override, const char *name, dp_font_t *fnt, float scale, float voffset)
333 char widthfile[MAX_QPATH];
335 fs_offset_t widthbufsize;
337 if(override || !fnt->texpath[0])
339 strlcpy(fnt->texpath, name, sizeof(fnt->texpath));
340 // load the cvars when the font is FIRST loader
341 fnt->settings.scale = scale;
342 fnt->settings.voffset = voffset;
343 fnt->settings.antialias = r_font_antialias.integer;
344 fnt->settings.hinting = r_font_hinting.integer;
345 fnt->settings.outline = r_font_postprocess_outline.value;
346 fnt->settings.blur = r_font_postprocess_blur.value;
347 fnt->settings.shadowx = r_font_postprocess_shadow_x.value;
348 fnt->settings.shadowy = r_font_postprocess_shadow_y.value;
349 fnt->settings.shadowz = r_font_postprocess_shadow_z.value;
353 if (fnt->settings.scale <= 0)
354 fnt->settings.scale = 1;
356 if(drawtexturepool == NULL)
357 return; // before gl_draw_start, so will be loaded later
361 // we are going to reload. clear old ft2 data
362 Font_UnloadFont(fnt->ft2);
367 if(fnt->req_face != -1)
369 if(!Font_LoadFont(fnt->texpath, fnt))
370 Con_DPrintf("Failed to load font-file for '%s', it will not support as many characters.\n", fnt->texpath);
373 fnt->pic = Draw_CachePic_Flags(fnt->texpath, CACHEPICFLAG_QUIET | CACHEPICFLAG_NOCOMPRESSION | (r_nearest_conchars.integer ? CACHEPICFLAG_NEAREST : 0) | CACHEPICFLAG_FAILONMISSING);
374 if(!Draw_IsPicLoaded(fnt->pic))
376 for (i = 0; i < MAX_FONT_FALLBACKS; ++i)
378 if (!fnt->fallbacks[i][0])
380 fnt->pic = Draw_CachePic_Flags(fnt->fallbacks[i], CACHEPICFLAG_QUIET | CACHEPICFLAG_NOCOMPRESSION | (r_nearest_conchars.integer ? CACHEPICFLAG_NEAREST : 0) | CACHEPICFLAG_FAILONMISSING);
381 if(Draw_IsPicLoaded(fnt->pic))
384 if(!Draw_IsPicLoaded(fnt->pic))
386 fnt->pic = Draw_CachePic_Flags("gfx/conchars", CACHEPICFLAG_NOCOMPRESSION | (r_nearest_conchars.integer ? CACHEPICFLAG_NEAREST : 0));
387 strlcpy(widthfile, "gfx/conchars.width", sizeof(widthfile));
390 dpsnprintf(widthfile, sizeof(widthfile), "%s.width", fnt->fallbacks[i]);
393 dpsnprintf(widthfile, sizeof(widthfile), "%s.width", fnt->texpath);
395 // unspecified width == 1 (base width)
396 for(ch = 0; ch < 256; ++ch)
397 fnt->width_of[ch] = 1;
399 // FIXME load "name.width", if it fails, fill all with 1
400 if((widthbuf = (char *) FS_LoadFile(widthfile, tempmempool, true, &widthbufsize)))
402 float extraspacing = 0;
403 const char *p = widthbuf;
408 if(!COM_ParseToken_Simple(&p, false, false, true))
426 fnt->width_of[ch] = atof(com_token) + extraspacing;
430 if(!strcmp(com_token, "extraspacing"))
432 if(!COM_ParseToken_Simple(&p, false, false, true))
434 extraspacing = atof(com_token);
436 else if(!strcmp(com_token, "scale"))
438 if(!COM_ParseToken_Simple(&p, false, false, true))
440 fnt->settings.scale = atof(com_token);
444 Con_DPrintf("Warning: skipped unknown font property %s\n", com_token);
445 if(!COM_ParseToken_Simple(&p, false, false, true))
457 for (i = 0; i < MAX_FONT_SIZES; ++i)
459 ft2_font_map_t *map = Font_MapForIndex(fnt->ft2, i);
462 for(ch = 0; ch < 256; ++ch)
463 fnt->width_of_ft2[i][ch] = Font_SnapTo(fnt->width_of[ch], 1/map->size);
467 maxwidth = fnt->width_of[0];
468 for(i = 1; i < 256; ++i)
469 maxwidth = max(maxwidth, fnt->width_of[i]);
470 fnt->maxwidth = maxwidth;
472 // fix up maxwidth for overlap
473 fnt->maxwidth *= fnt->settings.scale;
475 if(fnt == FONT_CONSOLE)
476 con_linewidth = -1; // rewrap console in next frame
479 extern cvar_t developer_font;
480 dp_font_t *FindFont(const char *title, qbool allocate_new)
485 for(i = 0; i < dp_fonts.maxsize; ++i)
486 if(!strcmp(dp_fonts.f[i].title, title))
487 return &dp_fonts.f[i];
488 // if not found - try allocate
491 // find any font with empty title
492 for(i = 0; i < dp_fonts.maxsize; ++i)
494 if(!strcmp(dp_fonts.f[i].title, ""))
496 strlcpy(dp_fonts.f[i].title, title, sizeof(dp_fonts.f[i].title));
497 return &dp_fonts.f[i];
500 // if no any 'free' fonts - expand buffer
501 oldsize = dp_fonts.maxsize;
502 dp_fonts.maxsize = dp_fonts.maxsize + FONTS_EXPAND;
503 if (developer_font.integer)
504 Con_Printf("FindFont: enlarging fonts buffer (%i -> %i)\n", oldsize, dp_fonts.maxsize);
505 dp_fonts.f = (dp_font_t *)Mem_Realloc(fonts_mempool, dp_fonts.f, sizeof(dp_font_t) * dp_fonts.maxsize);
506 // relink ft2 structures
507 for(i = 0; i < oldsize; ++i)
508 if (dp_fonts.f[i].ft2)
509 dp_fonts.f[i].ft2->settings = &dp_fonts.f[i].settings;
510 // register a font in first expanded slot
511 strlcpy(dp_fonts.f[oldsize].title, title, sizeof(dp_fonts.f[oldsize].title));
512 return &dp_fonts.f[oldsize];
517 static float snap_to_pixel_x(float x, float roundUpAt)
519 float pixelpos = x * vid.width / vid_conwidth.value;
520 int snap = (int) pixelpos;
521 if (pixelpos - snap >= roundUpAt) ++snap;
522 return ((float)snap * vid_conwidth.value / vid.width);
524 x = (int)(x * vid.width / vid_conwidth.value);
525 x = (x * vid_conwidth.value / vid.width);
530 static float snap_to_pixel_y(float y, float roundUpAt)
532 float pixelpos = y * vid.height / vid_conheight.value;
533 int snap = (int) pixelpos;
534 if (pixelpos - snap > roundUpAt) ++snap;
535 return ((float)snap * vid_conheight.value / vid.height);
537 y = (int)(y * vid.height / vid_conheight.value);
538 y = (y * vid_conheight.value / vid.height);
543 static void LoadFont_f(cmd_state_t *cmd)
547 const char *filelist, *c, *cm;
548 float sz, scale, voffset;
549 char mainfont[MAX_QPATH];
551 if(Cmd_Argc(cmd) < 2)
553 Con_Printf("Available font commands:\n");
554 for(i = 0; i < dp_fonts.maxsize; ++i)
555 if (dp_fonts.f[i].title[0])
556 Con_Printf(" loadfont %s gfx/tgafile[...] [custom switches] [sizes...]\n", dp_fonts.f[i].title);
557 Con_Printf("A font can simply be gfx/tgafile, or alternatively you\n"
558 "can specify multiple fonts and faces\n"
559 "Like this: gfx/vera-sans:2,gfx/fallback:1\n"
560 "to load face 2 of the font gfx/vera-sans and use face 1\n"
561 "of gfx/fallback as fallback font.\n"
562 "You can also specify a list of font sizes to load, like this:\n"
563 "loadfont console gfx/conchars,gfx/fallback 8 12 16 24 32\n"
564 "In many cases, 8 12 16 24 32 should be a good choice.\n"
566 " scale x : scale all characters by this amount when rendering (doesnt change line height)\n"
567 " voffset x : offset all chars vertical when rendering, this is multiplied to character height\n"
571 f = FindFont(Cmd_Argv(cmd, 1), true);
574 Con_Printf("font function not found\n");
579 if (strcmp(cmd->cmdline, f->cmdline) != 0 || r_font_always_reload.integer)
580 strlcpy(f->cmdline, cmd->cmdline, MAX_FONT_CMDLINE);
583 Con_DPrintf("LoadFont: font %s is unchanged\n", Cmd_Argv(cmd, 1));
588 if(Cmd_Argc(cmd) < 3)
589 filelist = "gfx/conchars";
591 filelist = Cmd_Argv(cmd, 2);
593 memset(f->fallbacks, 0, sizeof(f->fallbacks));
594 memset(f->fallback_faces, 0, sizeof(f->fallback_faces));
596 // first font is handled "normally"
597 c = strchr(filelist, ':');
598 cm = strchr(filelist, ',');
599 if(c && (!cm || c < cm))
600 f->req_face = atoi(c+1);
607 if(!c || (c - filelist) >= MAX_QPATH)
608 strlcpy(mainfont, filelist, sizeof(mainfont));
611 memcpy(mainfont, filelist, c - filelist);
612 mainfont[c - filelist] = 0;
615 for(i = 0; i < MAX_FONT_FALLBACKS; ++i)
617 c = strchr(filelist, ',');
623 c = strchr(filelist, ':');
624 cm = strchr(filelist, ',');
625 if(c && (!cm || c < cm))
626 f->fallback_faces[i] = atoi(c+1);
629 f->fallback_faces[i] = 0; // f->req_face; could make it stick to the default-font's face index
632 if(!c || (c-filelist) >= MAX_QPATH)
634 strlcpy(f->fallbacks[i], filelist, sizeof(mainfont));
638 memcpy(f->fallbacks[i], filelist, c - filelist);
639 f->fallbacks[i][c - filelist] = 0;
643 // for now: by default load only one size: the default size
645 for(i = 1; i < MAX_FONT_SIZES; ++i)
646 f->req_sizes[i] = -1;
650 if(Cmd_Argc(cmd) >= 4)
652 for(sizes = 0, i = 3; i < Cmd_Argc(cmd); ++i)
655 if (!strcmp(Cmd_Argv(cmd, i), "scale"))
658 if (i < Cmd_Argc(cmd))
659 scale = atof(Cmd_Argv(cmd, i));
662 if (!strcmp(Cmd_Argv(cmd, i), "voffset"))
665 if (i < Cmd_Argc(cmd))
666 voffset = atof(Cmd_Argv(cmd, i));
671 continue; // no slot for other sizes
673 // parse one of sizes
674 sz = atof(Cmd_Argv(cmd, i));
675 if (sz > 0.001f && sz < 1000.0f) // do not use crap sizes
677 // search for duplicated sizes
679 for (j=0; j<sizes; j++)
680 if (f->req_sizes[j] == sz)
683 continue; // sz already in req_sizes, don't add it again
685 if (sizes == MAX_FONT_SIZES)
687 Con_Printf(CON_WARN "Warning: specified more than %i different font sizes, exceding ones are ignored\n", MAX_FONT_SIZES);
691 f->req_sizes[sizes] = sz;
697 LoadFont(true, mainfont, f, scale, voffset);
705 static void gl_draw_start(void)
709 drawtexturepool = R_AllocTexturePool();
712 memset(cachepichash, 0, sizeof(cachepichash));
716 // load default font textures
717 for(i = 0; i < dp_fonts.maxsize; ++i)
718 if (dp_fonts.f[i].title[0])
719 LoadFont(false, va(vabuf, sizeof(vabuf), "gfx/font_%s", dp_fonts.f[i].title), &dp_fonts.f[i], 1, 0);
722 static void gl_draw_shutdown(void)
726 R_FreeTexturePool(&drawtexturepool);
729 memset(cachepichash, 0, sizeof(cachepichash));
732 static void gl_draw_newmap(void)
737 // mark all of the persistent pics so they are not purged...
738 for (i = 0; i < numcachepics; i++)
740 cachepic_t *pic = cachepics + i;
741 if (!pic->autoload && pic->skinframe)
742 R_SkinFrame_MarkUsed(pic->skinframe);
746 void GL_Draw_Init (void)
750 Cvar_RegisterVariable(&r_font_postprocess_blur);
751 Cvar_RegisterVariable(&r_font_postprocess_outline);
752 Cvar_RegisterVariable(&r_font_postprocess_shadow_x);
753 Cvar_RegisterVariable(&r_font_postprocess_shadow_y);
754 Cvar_RegisterVariable(&r_font_postprocess_shadow_z);
755 Cvar_RegisterVariable(&r_font_hinting);
756 Cvar_RegisterVariable(&r_font_antialias);
757 Cvar_RegisterVariable(&r_font_always_reload);
758 Cvar_RegisterVariable(&r_textshadow);
759 Cvar_RegisterVariable(&r_textbrightness);
760 Cvar_RegisterVariable(&r_textcontrast);
761 Cvar_RegisterVariable(&r_nearest_2d);
762 Cvar_RegisterVariable(&r_nearest_conchars);
764 // allocate fonts storage
765 fonts_mempool = Mem_AllocPool("FONTS", 0, NULL);
766 dp_fonts.maxsize = MAX_FONTS;
767 dp_fonts.f = (dp_font_t *)Mem_Alloc(fonts_mempool, sizeof(dp_font_t) * dp_fonts.maxsize);
768 memset(dp_fonts.f, 0, sizeof(dp_font_t) * dp_fonts.maxsize);
770 // assign starting font names
771 strlcpy(FONT_DEFAULT->title, "default", sizeof(FONT_DEFAULT->title));
772 strlcpy(FONT_DEFAULT->texpath, "gfx/conchars", sizeof(FONT_DEFAULT->texpath));
773 strlcpy(FONT_CONSOLE->title, "console", sizeof(FONT_CONSOLE->title));
774 strlcpy(FONT_SBAR->title, "sbar", sizeof(FONT_SBAR->title));
775 strlcpy(FONT_NOTIFY->title, "notify", sizeof(FONT_NOTIFY->title));
776 strlcpy(FONT_CHAT->title, "chat", sizeof(FONT_CHAT->title));
777 strlcpy(FONT_CENTERPRINT->title, "centerprint", sizeof(FONT_CENTERPRINT->title));
778 strlcpy(FONT_INFOBAR->title, "infobar", sizeof(FONT_INFOBAR->title));
779 strlcpy(FONT_MENU->title, "menu", sizeof(FONT_MENU->title));
780 for(i = 0, j = 0; i < MAX_USERFONTS; ++i)
781 if(!FONT_USER(i)->title[0])
782 dpsnprintf(FONT_USER(i)->title, sizeof(FONT_USER(i)->title), "user%d", j++);
784 Cmd_AddCommand(CF_CLIENT, "loadfont", LoadFont_f, "loadfont function tganame loads a font; example: loadfont console gfx/veramono; loadfont without arguments lists the available functions");
785 R_RegisterModule("GL_Draw", gl_draw_start, gl_draw_shutdown, gl_draw_newmap, NULL, NULL);
788 void DrawQ_Start(void)
790 r_refdef.draw2dstage = 1;
791 R_ResetViewRendering2D_Common(0, NULL, NULL, 0, 0, vid.width, vid.height, vid_conwidth.integer, vid_conheight.integer);
794 qbool r_draw2d_force = false;
796 void DrawQ_Pic(float x, float y, cachepic_t *pic, float width, float height, float red, float green, float blue, float alpha, int flags)
798 model_t *mod = CL_Mesh_UI();
802 pic = Draw_CachePic("white");
803 // make sure pic is loaded - we don't use the texture here, Mod_Mesh_GetTexture looks up the skinframe by name
804 Draw_GetPicTexture(pic);
808 height = pic->height;
809 surf = Mod_Mesh_AddSurface(mod, Mod_Mesh_GetTexture(mod, pic->name, flags, pic->texflags, MATERIALFLAG_WALL | MATERIALFLAG_VERTEXCOLOR | MATERIALFLAG_ALPHAGEN_VERTEX | MATERIALFLAG_ALPHA | MATERIALFLAG_BLENDED | MATERIALFLAG_NOSHADOW), true);
810 e0 = Mod_Mesh_IndexForVertex(mod, surf, x , y , 0, 0, 0, -1, 0, 0, 0, 0, red, green, blue, alpha);
811 e1 = Mod_Mesh_IndexForVertex(mod, surf, x + width, y , 0, 0, 0, -1, 1, 0, 0, 0, red, green, blue, alpha);
812 e2 = Mod_Mesh_IndexForVertex(mod, surf, x + width, y + height, 0, 0, 0, -1, 1, 1, 0, 0, red, green, blue, alpha);
813 e3 = Mod_Mesh_IndexForVertex(mod, surf, x , y + height, 0, 0, 0, -1, 0, 1, 0, 0, red, green, blue, alpha);
814 Mod_Mesh_AddTriangle(mod, surf, e0, e1, e2);
815 Mod_Mesh_AddTriangle(mod, surf, e0, e2, e3);
818 void DrawQ_RotPic(float x, float y, cachepic_t *pic, float width, float height, float org_x, float org_y, float angle, float red, float green, float blue, float alpha, int flags)
820 float af = DEG2RAD(-angle); // forward
821 float ar = DEG2RAD(-angle + 90); // right
822 float sinaf = sin(af);
823 float cosaf = cos(af);
824 float sinar = sin(ar);
825 float cosar = cos(ar);
826 model_t *mod = CL_Mesh_UI();
830 pic = Draw_CachePic("white");
831 // make sure pic is loaded - we don't use the texture here, Mod_Mesh_GetTexture looks up the skinframe by name
832 Draw_GetPicTexture(pic);
836 height = pic->height;
837 surf = Mod_Mesh_AddSurface(mod, Mod_Mesh_GetTexture(mod, pic->name, flags, pic->texflags, MATERIALFLAG_WALL | MATERIALFLAG_VERTEXCOLOR | MATERIALFLAG_ALPHAGEN_VERTEX | MATERIALFLAG_ALPHA | MATERIALFLAG_BLENDED | MATERIALFLAG_NOSHADOW), true);
838 e0 = Mod_Mesh_IndexForVertex(mod, surf, x - cosaf * org_x - cosar * org_y , y - sinaf * org_x - sinar * org_y , 0, 0, 0, -1, 0, 0, 0, 0, red, green, blue, alpha);
839 e1 = Mod_Mesh_IndexForVertex(mod, surf, x + cosaf * (width - org_x) - cosar * org_y , y + sinaf * (width - org_x) - sinar * org_y , 0, 0, 0, -1, 1, 0, 0, 0, red, green, blue, alpha);
840 e2 = Mod_Mesh_IndexForVertex(mod, surf, x + cosaf * (width - org_x) + cosar * (height - org_y), y + sinaf * (width - org_x) + sinar * (height - org_y), 0, 0, 0, -1, 1, 1, 0, 0, red, green, blue, alpha);
841 e3 = Mod_Mesh_IndexForVertex(mod, surf, x - cosaf * org_x + cosar * (height - org_y), y - sinaf * org_x + sinar * (height - org_y), 0, 0, 0, -1, 0, 1, 0, 0, red, green, blue, alpha);
842 Mod_Mesh_AddTriangle(mod, surf, e0, e1, e2);
843 Mod_Mesh_AddTriangle(mod, surf, e0, e2, e3);
846 void DrawQ_Fill(float x, float y, float width, float height, float red, float green, float blue, float alpha, int flags)
848 DrawQ_Pic(x, y, Draw_CachePic("white"), width, height, red, green, blue, alpha, flags);
851 /// color tag printing
852 static const vec4_t string_colors[] =
855 // LadyHavoc: why on earth is cyan before magenta in Quake3?
856 // LadyHavoc: note: Doom3 uses white for [0] and [7]
857 {0.0, 0.0, 0.0, 1.0}, // black
858 {1.0, 0.0, 0.0, 1.0}, // red
859 {0.0, 1.0, 0.0, 1.0}, // green
860 {1.0, 1.0, 0.0, 1.0}, // yellow
861 {0.0, 0.0, 1.0, 1.0}, // blue
862 {0.0, 1.0, 1.0, 1.0}, // cyan
863 {1.0, 0.0, 1.0, 1.0}, // magenta
864 {1.0, 1.0, 1.0, 1.0}, // white
865 // [515]'s BX_COLOREDTEXT extension
866 {1.0, 1.0, 1.0, 0.5}, // half transparent
867 {0.5, 0.5, 0.5, 1.0} // half brightness
868 // Black's color table
869 //{1.0, 1.0, 1.0, 1.0},
870 //{1.0, 0.0, 0.0, 1.0},
871 //{0.0, 1.0, 0.0, 1.0},
872 //{0.0, 0.0, 1.0, 1.0},
873 //{1.0, 1.0, 0.0, 1.0},
874 //{0.0, 1.0, 1.0, 1.0},
875 //{1.0, 0.0, 1.0, 1.0},
876 //{0.1, 0.1, 0.1, 1.0}
879 #define STRING_COLORS_COUNT (sizeof(string_colors) / sizeof(vec4_t))
881 static void DrawQ_GetTextColor(float color[4], int colorindex, float r, float g, float b, float a, qbool shadow)
883 float C = r_textcontrast.value;
884 float B = r_textbrightness.value;
885 if (colorindex & 0x10000) // that bit means RGB color
887 color[0] = ((colorindex >> 12) & 0xf) / 15.0;
888 color[1] = ((colorindex >> 8) & 0xf) / 15.0;
889 color[2] = ((colorindex >> 4) & 0xf) / 15.0;
890 color[3] = (colorindex & 0xf) / 15.0;
893 Vector4Copy(string_colors[colorindex], color);
894 Vector4Set(color, color[0] * r * C + B, color[1] * g * C + B, color[2] * b * C + B, color[3] * a);
897 float shadowalpha = (color[0]+color[1]+color[2]) * 0.8;
898 Vector4Set(color, 0, 0, 0, color[3] * bound(0, shadowalpha, 1));
902 // returns a colorindex (format 0x1RGBA) if str is a valid RGB string
903 // returns 0 otherwise
904 static int RGBstring_to_colorindex(const char *str)
907 int ind = 0x0001 << 4;
909 if (*str <= '9' && *str >= '0')
914 if (ch >= 'a' && ch <= 'f')
921 } while(!(ind & 0x10000));
922 return ind | 0xf; // add costant alpha value
925 // NOTE: this function always draws exactly one character if maxwidth <= 0
926 float DrawQ_TextWidth_UntilWidth_TrackColors_Scale(const char *text, size_t *maxlen, float w, float h, float sw, float sh, int *outcolor, qbool ignorecolorcodes, const dp_font_t *fnt, float maxwidth)
928 const char *text_start = text;
932 Uchar ch, mapch, nextch;
933 Uchar prevch = 0; // used for kerning
937 ft2_font_map_t *fontmap = NULL;
938 ft2_font_map_t *map = NULL;
939 ft2_font_t *ft2 = fnt->ft2;
942 qbool least_one = false;
943 float dw; // display w
944 //float dh; // display h
945 const float *width_of;
952 // do this in the end
953 w *= fnt->settings.scale;
954 h *= fnt->settings.scale;
956 // find the most fitting size:
960 map_index = Font_IndexForSize(ft2, h, &w, &h);
962 map_index = Font_IndexForSize(ft2, h, NULL, NULL);
963 fontmap = Font_MapForIndex(ft2, map_index);
972 if (!outcolor || *outcolor == -1)
973 colorindex = STRING_COLOR_DEFAULT;
975 colorindex = *outcolor;
977 // maxwidth /= fnt->scale; // w and h are multiplied by it already
978 // ftbase_x = snap_to_pixel_x(0);
983 maxwidth = -maxwidth;
987 // x = snap_to_pixel_x(x, 0.4); // haha, it's 0 anyway
990 width_of = fnt->width_of_ft2[map_index];
992 width_of = fnt->width_of;
995 while (((bytes_left = *maxlen - (text - text_start)) > 0) && *text)
998 nextch = ch = u8_getnchar(text, &text, bytes_left);
999 i = text - text_start;
1002 if (ch == ' ' && !fontmap)
1004 if(!least_one || i0) // never skip the first character
1005 if(x + width_of[(int) ' '] * dw > maxwidth)
1008 break; // oops, can't draw this
1010 x += width_of[(int) ' '] * dw;
1013 if (ch == STRING_COLOR_TAG && !ignorecolorcodes && i < *maxlen)
1015 ch = *text; // colors are ascii, so no u8_ needed
1016 if (ch <= '9' && ch >= '0') // ^[0-9] found
1018 colorindex = ch - '0';
1023 else if (ch == STRING_COLOR_RGB_TAG_CHAR && i + 3 < *maxlen ) // ^x found
1025 const char *text_p = &text[1];
1026 int tempcolorindex = RGBstring_to_colorindex(text_p);
1029 colorindex = tempcolorindex;
1035 else if (ch == STRING_COLOR_TAG) // ^^ found
1044 if (!fontmap || (ch <= 0xFF && fontmap->glyphs[ch].image) || (ch >= 0xE000 && ch <= 0xE0FF))
1051 map = ft2_oldstyle_map;
1053 if(!least_one || i0) // never skip the first character
1054 if(x + width_of[ch] * dw > maxwidth)
1057 break; // oops, can't draw this
1059 x += width_of[ch] * dw;
1061 if (!map || map == ft2_oldstyle_map || ch != prevch)
1063 Font_GetMapForChar(ft2, map_index, ch, &map, &mapch);
1067 if (prevch && Font_GetKerningForMap(ft2, map_index, w, h, prevch, ch, &kx, NULL))
1069 x += map->glyphs[mapch].advance_x * dw;
1077 *outcolor = colorindex;
1082 float DrawQ_Color[4];
1083 float DrawQ_String_Scale(float startx, float starty, const char *text, size_t maxlen, float w, float h, float sw, float sh, float basered, float basegreen, float baseblue, float basealpha, int flags, int *outcolor, qbool ignorecolorcodes, const dp_font_t *fnt)
1085 int shadow, colorindex = STRING_COLOR_DEFAULT;
1087 float x = startx, y, s, t, u, v, thisw;
1088 Uchar ch, mapch, nextch;
1089 Uchar prevch = 0; // used for kerning
1091 ft2_font_map_t *map = NULL; // the currently used map
1092 ft2_font_map_t *fontmap = NULL; // the font map for the size
1094 const char *text_start = text;
1096 ft2_font_t *ft2 = fnt->ft2;
1101 const float *width_of;
1102 model_t *mod = CL_Mesh_UI();
1103 msurface_t *surf = NULL;
1106 tw = Draw_GetPicWidth(fnt->pic);
1107 th = Draw_GetPicHeight(fnt->pic);
1115 starty -= (fnt->settings.scale - 1) * h * 0.5 - fnt->settings.voffset*h; // center & offset
1116 w *= fnt->settings.scale;
1117 h *= fnt->settings.scale;
1122 map_index = Font_IndexForSize(ft2, h, &w, &h);
1124 map_index = Font_IndexForSize(ft2, h, NULL, NULL);
1125 fontmap = Font_MapForIndex(ft2, map_index);
1131 // draw the font at its baseline when using freetype
1133 ftbase_y = dh * (4.5/6.0);
1138 if(!r_draw2d.integer && !r_draw2d_force)
1139 return startx + DrawQ_TextWidth_UntilWidth_TrackColors_Scale(text, &maxlen, w, h, sw, sh, NULL, ignorecolorcodes, fnt, 1000000000);
1141 //ftbase_x = snap_to_pixel_x(ftbase_x);
1144 startx = snap_to_pixel_x(startx, 0.4);
1145 starty = snap_to_pixel_y(starty, 0.4);
1146 ftbase_y = snap_to_pixel_y(ftbase_y, 0.3);
1149 pix_x = vid.width / vid_conwidth.value;
1150 pix_y = vid.height / vid_conheight.value;
1153 width_of = fnt->width_of_ft2[map_index];
1155 width_of = fnt->width_of;
1157 for (shadow = r_textshadow.value != 0 && basealpha > 0;shadow >= 0;shadow--)
1162 if (!outcolor || *outcolor == -1)
1163 colorindex = STRING_COLOR_DEFAULT;
1165 colorindex = *outcolor;
1167 DrawQ_GetTextColor(DrawQ_Color, colorindex, basered, basegreen, baseblue, basealpha, shadow != 0);
1174 x += r_textshadow.value * vid.width / vid_conwidth.value;
1175 y += r_textshadow.value * vid.height / vid_conheight.value;
1178 while (((bytes_left = maxlen - (text - text_start)) > 0) && *text)
1180 nextch = ch = u8_getnchar(text, &text, bytes_left);
1181 i = text - text_start;
1184 if (ch == ' ' && !fontmap)
1186 x += width_of[(int) ' '] * dw;
1189 if (ch == STRING_COLOR_TAG && !ignorecolorcodes && i < maxlen)
1191 ch = *text; // colors are ascii, so no u8_ needed
1192 if (ch <= '9' && ch >= '0') // ^[0-9] found
1194 colorindex = ch - '0';
1195 DrawQ_GetTextColor(DrawQ_Color, colorindex, basered, basegreen, baseblue, basealpha, shadow != 0);
1200 else if (ch == STRING_COLOR_RGB_TAG_CHAR && i+3 < maxlen ) // ^x found
1202 const char *text_p = &text[1];
1203 int tempcolorindex = RGBstring_to_colorindex(text_p);
1206 colorindex = tempcolorindex;
1207 DrawQ_GetTextColor(DrawQ_Color, colorindex, basered, basegreen, baseblue, basealpha, shadow != 0);
1213 else if (ch == STRING_COLOR_TAG)
1222 // using a value of -1 for the oldstyle map because NULL means uninitialized...
1223 // this way we don't need to rebind fnt->tex for every old-style character
1224 // E000..E0FF: emulate old-font characters (to still have smileys and such available)
1227 x += 1.0/pix_x * r_textshadow.value;
1228 y += 1.0/pix_y * r_textshadow.value;
1230 if (!fontmap || (ch <= 0xFF && fontmap->glyphs[ch].image) || (ch >= 0xE000 && ch <= 0xE0FF))
1237 map = ft2_oldstyle_map;
1239 //num = (unsigned char) text[i];
1240 //thisw = fnt->width_of[num];
1241 thisw = fnt->width_of[ch];
1242 // FIXME make these smaller to just include the occupied part of the character for slightly faster rendering
1243 if (r_nearest_conchars.integer)
1245 s = (ch & 15)*0.0625f;
1246 t = (ch >> 4)*0.0625f;
1247 u = 0.0625f * thisw;
1252 s = (ch & 15)*0.0625f + (0.5f / tw);
1253 t = (ch >> 4)*0.0625f + (0.5f / th);
1254 u = 0.0625f * thisw - (1.0f / tw);
1255 v = 0.0625f - (1.0f / th);
1257 surf = Mod_Mesh_AddSurface(mod, Mod_Mesh_GetTexture(mod, fnt->pic->name, flags, TEXF_ALPHA | TEXF_CLAMP, MATERIALFLAG_WALL | MATERIALFLAG_VERTEXCOLOR | MATERIALFLAG_ALPHAGEN_VERTEX | MATERIALFLAG_ALPHA | MATERIALFLAG_BLENDED | MATERIALFLAG_NOSHADOW), true);
1258 e0 = Mod_Mesh_IndexForVertex(mod, surf, x , y , 10, 0, 0, -1, s , t , 0, 0, DrawQ_Color[0], DrawQ_Color[1], DrawQ_Color[2], DrawQ_Color[3]);
1259 e1 = Mod_Mesh_IndexForVertex(mod, surf, x+dw*thisw, y , 10, 0, 0, -1, s+u, t , 0, 0, DrawQ_Color[0], DrawQ_Color[1], DrawQ_Color[2], DrawQ_Color[3]);
1260 e2 = Mod_Mesh_IndexForVertex(mod, surf, x+dw*thisw, y+dh, 10, 0, 0, -1, s+u, t+v, 0, 0, DrawQ_Color[0], DrawQ_Color[1], DrawQ_Color[2], DrawQ_Color[3]);
1261 e3 = Mod_Mesh_IndexForVertex(mod, surf, x , y+dh, 10, 0, 0, -1, s , t+v, 0, 0, DrawQ_Color[0], DrawQ_Color[1], DrawQ_Color[2], DrawQ_Color[3]);
1262 Mod_Mesh_AddTriangle(mod, surf, e0, e1, e2);
1263 Mod_Mesh_AddTriangle(mod, surf, e0, e2, e3);
1264 x += width_of[ch] * dw;
1266 if (!map || map == ft2_oldstyle_map || ch != prevch)
1268 Font_GetMapForChar(ft2, map_index, ch, &map, &mapch);
1276 thisw = map->glyphs[mapch].advance_x;
1280 if (prevch && Font_GetKerningForMap(ft2, map_index, w, h, prevch, ch, &kx, &ky))
1287 surf = Mod_Mesh_AddSurface(mod, Mod_Mesh_GetTexture(mod, map->pic->name, flags, TEXF_ALPHA | TEXF_CLAMP, MATERIALFLAG_WALL | MATERIALFLAG_VERTEXCOLOR | MATERIALFLAG_ALPHAGEN_VERTEX | MATERIALFLAG_ALPHA | MATERIALFLAG_BLENDED | MATERIALFLAG_NOSHADOW), true);
1288 e0 = Mod_Mesh_IndexForVertex(mod, surf, x + dw * map->glyphs[mapch].vxmin, y + dh * map->glyphs[mapch].vymin, 10, 0, 0, -1, map->glyphs[mapch].txmin, map->glyphs[mapch].tymin, 0, 0, DrawQ_Color[0], DrawQ_Color[1], DrawQ_Color[2], DrawQ_Color[3]);
1289 e1 = Mod_Mesh_IndexForVertex(mod, surf, x + dw * map->glyphs[mapch].vxmax, y + dh * map->glyphs[mapch].vymin, 10, 0, 0, -1, map->glyphs[mapch].txmax, map->glyphs[mapch].tymin, 0, 0, DrawQ_Color[0], DrawQ_Color[1], DrawQ_Color[2], DrawQ_Color[3]);
1290 e2 = Mod_Mesh_IndexForVertex(mod, surf, x + dw * map->glyphs[mapch].vxmax, y + dh * map->glyphs[mapch].vymax, 10, 0, 0, -1, map->glyphs[mapch].txmax, map->glyphs[mapch].tymax, 0, 0, DrawQ_Color[0], DrawQ_Color[1], DrawQ_Color[2], DrawQ_Color[3]);
1291 e3 = Mod_Mesh_IndexForVertex(mod, surf, x + dw * map->glyphs[mapch].vxmin, y + dh * map->glyphs[mapch].vymax, 10, 0, 0, -1, map->glyphs[mapch].txmin, map->glyphs[mapch].tymax, 0, 0, DrawQ_Color[0], DrawQ_Color[1], DrawQ_Color[2], DrawQ_Color[3]);
1292 Mod_Mesh_AddTriangle(mod, surf, e0, e1, e2);
1293 Mod_Mesh_AddTriangle(mod, surf, e0, e2, e3);
1305 x -= 1.0/pix_x * r_textshadow.value;
1306 y -= 1.0/pix_y * r_textshadow.value;
1312 *outcolor = colorindex;
1314 // note: this relies on the proper text (not shadow) being drawn last
1318 float DrawQ_String(float startx, float starty, const char *text, size_t maxlen, float w, float h, float basered, float basegreen, float baseblue, float basealpha, int flags, int *outcolor, qbool ignorecolorcodes, const dp_font_t *fnt)
1320 return DrawQ_String_Scale(startx, starty, text, maxlen, w, h, 1, 1, basered, basegreen, baseblue, basealpha, flags, outcolor, ignorecolorcodes, fnt);
1323 float DrawQ_TextWidth_UntilWidth_TrackColors(const char *text, size_t *maxlen, float w, float h, int *outcolor, qbool ignorecolorcodes, const dp_font_t *fnt, float maxwidth)
1325 return DrawQ_TextWidth_UntilWidth_TrackColors_Scale(text, maxlen, w, h, 1, 1, outcolor, ignorecolorcodes, fnt, maxwidth);
1328 float DrawQ_TextWidth(const char *text, size_t maxlen, float w, float h, qbool ignorecolorcodes, const dp_font_t *fnt)
1330 return DrawQ_TextWidth_UntilWidth(text, &maxlen, w, h, ignorecolorcodes, fnt, 1000000000);
1333 float DrawQ_TextWidth_UntilWidth(const char *text, size_t *maxlen, float w, float h, qbool ignorecolorcodes, const dp_font_t *fnt, float maxWidth)
1335 return DrawQ_TextWidth_UntilWidth_TrackColors(text, maxlen, w, h, NULL, ignorecolorcodes, fnt, maxWidth);
1340 // no ^xrgb management
1341 static int DrawQ_BuildColoredText(char *output2c, size_t maxoutchars, const char *text, int maxreadchars, qbool ignorecolorcodes, int *outcolor)
1343 int color, numchars = 0;
1344 char *outputend2c = output2c + maxoutchars - 2;
1345 if (!outcolor || *outcolor == -1)
1346 color = STRING_COLOR_DEFAULT;
1350 maxreadchars = 1<<30;
1351 textend = text + maxreadchars;
1352 while (text != textend && *text)
1354 if (*text == STRING_COLOR_TAG && !ignorecolorcodes && text + 1 != textend)
1356 if (text[1] == STRING_COLOR_TAG)
1358 else if (text[1] >= '0' && text[1] <= '9')
1360 color = text[1] - '0';
1365 if (output2c >= outputend2c)
1367 *output2c++ = *text++;
1368 *output2c++ = color;
1371 output2c[0] = output2c[1] = 0;
1378 void DrawQ_SuperPic(float x, float y, cachepic_t *pic, float width, float height, float s1, float t1, float r1, float g1, float b1, float a1, float s2, float t2, float r2, float g2, float b2, float a2, float s3, float t3, float r3, float g3, float b3, float a3, float s4, float t4, float r4, float g4, float b4, float a4, int flags)
1380 model_t *mod = CL_Mesh_UI();
1384 pic = Draw_CachePic("white");
1385 // make sure pic is loaded - we don't use the texture here, Mod_Mesh_GetTexture looks up the skinframe by name
1386 Draw_GetPicTexture(pic);
1390 height = pic->height;
1391 surf = Mod_Mesh_AddSurface(mod, Mod_Mesh_GetTexture(mod, pic->name, flags, pic->texflags, MATERIALFLAG_WALL | MATERIALFLAG_VERTEXCOLOR | MATERIALFLAG_ALPHAGEN_VERTEX | MATERIALFLAG_ALPHA | MATERIALFLAG_BLENDED | MATERIALFLAG_NOSHADOW), true);
1392 e0 = Mod_Mesh_IndexForVertex(mod, surf, x , y , 0, 0, 0, -1, s1, t1, 0, 0, r1, g1, b1, a1);
1393 e1 = Mod_Mesh_IndexForVertex(mod, surf, x + width, y , 0, 0, 0, -1, s2, t2, 0, 0, r2, g2, b2, a2);
1394 e2 = Mod_Mesh_IndexForVertex(mod, surf, x + width, y + height, 0, 0, 0, -1, s4, t4, 0, 0, r4, g4, b4, a4);
1395 e3 = Mod_Mesh_IndexForVertex(mod, surf, x , y + height, 0, 0, 0, -1, s3, t3, 0, 0, r3, g3, b3, a3);
1396 Mod_Mesh_AddTriangle(mod, surf, e0, e1, e2);
1397 Mod_Mesh_AddTriangle(mod, surf, e0, e2, e3);
1400 void DrawQ_Line (float width, float x1, float y1, float x2, float y2, float r, float g, float b, float alpha, int flags)
1402 model_t *mod = CL_Mesh_UI();
1405 float offsetx, offsety;
1406 // width is measured in real pixels
1407 if (fabs(x2 - x1) > fabs(y2 - y1))
1410 offsety = 0.5f * width * vid_conheight.value / vid.height;
1414 offsetx = 0.5f * width * vid_conwidth.value / vid.width;
1417 surf = Mod_Mesh_AddSurface(mod, Mod_Mesh_GetTexture(mod, "white", 0, 0, MATERIALFLAG_WALL | MATERIALFLAG_VERTEXCOLOR | MATERIALFLAG_ALPHAGEN_VERTEX | MATERIALFLAG_ALPHA | MATERIALFLAG_BLENDED | MATERIALFLAG_NOSHADOW), true);
1418 e0 = Mod_Mesh_IndexForVertex(mod, surf, x1 - offsetx, y1 - offsety, 10, 0, 0, -1, 0, 0, 0, 0, r, g, b, alpha);
1419 e1 = Mod_Mesh_IndexForVertex(mod, surf, x2 - offsetx, y2 - offsety, 10, 0, 0, -1, 0, 0, 0, 0, r, g, b, alpha);
1420 e2 = Mod_Mesh_IndexForVertex(mod, surf, x2 + offsetx, y2 + offsety, 10, 0, 0, -1, 0, 0, 0, 0, r, g, b, alpha);
1421 e3 = Mod_Mesh_IndexForVertex(mod, surf, x1 + offsetx, y1 + offsety, 10, 0, 0, -1, 0, 0, 0, 0, r, g, b, alpha);
1422 Mod_Mesh_AddTriangle(mod, surf, e0, e1, e2);
1423 Mod_Mesh_AddTriangle(mod, surf, e0, e2, e3);
1426 void DrawQ_SetClipArea(float x, float y, float width, float height)
1431 // We have to convert the con coords into real coords
1432 // OGL uses bottom to top (origin is in bottom left)
1433 ix = (int)(0.5 + x * ((float)r_refdef.view.width / vid_conwidth.integer)) + r_refdef.view.x;
1434 iy = (int)(0.5 + y * ((float)r_refdef.view.height / vid_conheight.integer)) + r_refdef.view.y;
1435 iw = (int)(0.5 + width * ((float)r_refdef.view.width / vid_conwidth.integer));
1436 ih = (int)(0.5 + height * ((float)r_refdef.view.height / vid_conheight.integer));
1437 switch(vid.renderpath)
1439 case RENDERPATH_GL32:
1440 case RENDERPATH_GLES2:
1441 GL_Scissor(ix, vid.height - iy - ih, iw, ih);
1445 GL_ScissorTest(true);
1448 void DrawQ_ResetClipArea(void)
1451 GL_ScissorTest(false);
1454 void DrawQ_Finish(void)
1457 r_refdef.draw2dstage = 0;
1460 void DrawQ_RecalcView(void)
1463 if(r_refdef.draw2dstage)
1464 r_refdef.draw2dstage = -1; // next draw call will set viewport etc. again
1467 void DrawQ_FlushUI(void)
1469 model_t *mod = CL_Mesh_UI();
1470 if (mod->num_surfaces == 0)
1473 if (!r_draw2d.integer && !r_draw2d_force)
1475 Mod_Mesh_Reset(mod);
1479 // this is roughly equivalent to R_Mod_Draw, so the UI can use full material feature set
1480 r_refdef.view.colorscale = 1;
1481 r_textureframe++; // used only by R_GetCurrentTexture
1482 GL_DepthMask(false);
1484 Mod_Mesh_Finalize(mod);
1485 R_DrawModelSurfaces(&cl_meshentities[MESH_UI].render, false, false, false, false, false, true);
1487 Mod_Mesh_Reset(mod);