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;
343 if (fnt->settings.scale <= 0)
344 fnt->settings.scale = 1;
345 fnt->settings.voffset = voffset;
346 fnt->settings.antialias = r_font_antialias.integer;
347 fnt->settings.hinting = r_font_hinting.integer;
348 fnt->settings.outline = r_font_postprocess_outline.value;
349 fnt->settings.blur = r_font_postprocess_blur.value;
350 fnt->settings.shadowx = r_font_postprocess_shadow_x.value;
351 fnt->settings.shadowy = r_font_postprocess_shadow_y.value;
352 fnt->settings.shadowz = r_font_postprocess_shadow_z.value;
355 if(drawtexturepool == NULL)
356 return; // before gl_draw_start, so will be loaded later
360 // we are going to reload. clear old ft2 data
361 Font_UnloadFont(fnt->ft2);
366 if(fnt->req_face != -1)
368 if(!Font_LoadFont(fnt->texpath, fnt))
369 Con_DPrintf("Failed to load font-file for '%s', it will not support as many characters.\n", fnt->texpath);
372 fnt->pic = Draw_CachePic_Flags(fnt->texpath, CACHEPICFLAG_QUIET | CACHEPICFLAG_NOCOMPRESSION | (r_nearest_conchars.integer ? CACHEPICFLAG_NEAREST : 0) | CACHEPICFLAG_FAILONMISSING);
373 if(!Draw_IsPicLoaded(fnt->pic))
375 for (i = 0; i < MAX_FONT_FALLBACKS; ++i)
377 if (!fnt->fallbacks[i][0])
379 fnt->pic = Draw_CachePic_Flags(fnt->fallbacks[i], CACHEPICFLAG_QUIET | CACHEPICFLAG_NOCOMPRESSION | (r_nearest_conchars.integer ? CACHEPICFLAG_NEAREST : 0) | CACHEPICFLAG_FAILONMISSING);
380 if(Draw_IsPicLoaded(fnt->pic))
383 if(!Draw_IsPicLoaded(fnt->pic))
385 fnt->pic = Draw_CachePic_Flags("gfx/conchars", CACHEPICFLAG_NOCOMPRESSION | (r_nearest_conchars.integer ? CACHEPICFLAG_NEAREST : 0));
386 strlcpy(widthfile, "gfx/conchars.width", sizeof(widthfile));
389 dpsnprintf(widthfile, sizeof(widthfile), "%s.width", fnt->fallbacks[i]);
392 dpsnprintf(widthfile, sizeof(widthfile), "%s.width", fnt->texpath);
394 // unspecified width == 1 (base width)
395 for(ch = 0; ch < 256; ++ch)
396 fnt->width_of[ch] = 1;
398 // FIXME load "name.width", if it fails, fill all with 1
399 if((widthbuf = (char *) FS_LoadFile(widthfile, tempmempool, true, &widthbufsize)))
401 float extraspacing = 0;
402 const char *p = widthbuf;
407 if(!COM_ParseToken_Simple(&p, false, false, true))
425 fnt->width_of[ch] = atof(com_token) + extraspacing;
429 if(!strcmp(com_token, "extraspacing"))
431 if(!COM_ParseToken_Simple(&p, false, false, true))
433 extraspacing = atof(com_token);
435 else if(!strcmp(com_token, "scale"))
437 if(!COM_ParseToken_Simple(&p, false, false, true))
439 fnt->settings.scale = atof(com_token);
443 Con_DPrintf("Warning: skipped unknown font property %s\n", com_token);
444 if(!COM_ParseToken_Simple(&p, false, false, true))
456 for (i = 0; i < MAX_FONT_SIZES; ++i)
458 ft2_font_map_t *map = Font_MapForIndex(fnt->ft2, i);
461 for(ch = 0; ch < 256; ++ch)
462 fnt->width_of_ft2[i][ch] = Font_SnapTo(fnt->width_of[ch], 1/map->size);
466 maxwidth = fnt->width_of[0];
467 for(i = 1; i < 256; ++i)
468 maxwidth = max(maxwidth, fnt->width_of[i]);
469 fnt->maxwidth = maxwidth;
471 // fix up maxwidth for overlap
472 fnt->maxwidth *= fnt->settings.scale;
474 if(fnt == FONT_CONSOLE)
475 con_linewidth = -1; // rewrap console in next frame
478 extern cvar_t developer_font;
479 dp_font_t *FindFont(const char *title, qbool allocate_new)
484 for(i = 0; i < dp_fonts.maxsize; ++i)
485 if(!strcmp(dp_fonts.f[i].title, title))
486 return &dp_fonts.f[i];
487 // if not found - try allocate
490 // find any font with empty title
491 for(i = 0; i < dp_fonts.maxsize; ++i)
493 if(!strcmp(dp_fonts.f[i].title, ""))
495 strlcpy(dp_fonts.f[i].title, title, sizeof(dp_fonts.f[i].title));
496 return &dp_fonts.f[i];
499 // if no any 'free' fonts - expand buffer
500 oldsize = dp_fonts.maxsize;
501 dp_fonts.maxsize = dp_fonts.maxsize + FONTS_EXPAND;
502 if (developer_font.integer)
503 Con_Printf("FindFont: enlarging fonts buffer (%i -> %i)\n", oldsize, dp_fonts.maxsize);
504 dp_fonts.f = (dp_font_t *)Mem_Realloc(fonts_mempool, dp_fonts.f, sizeof(dp_font_t) * dp_fonts.maxsize);
505 // relink ft2 structures
506 for(i = 0; i < oldsize; ++i)
507 if (dp_fonts.f[i].ft2)
508 dp_fonts.f[i].ft2->settings = &dp_fonts.f[i].settings;
509 // register a font in first expanded slot
510 strlcpy(dp_fonts.f[oldsize].title, title, sizeof(dp_fonts.f[oldsize].title));
511 return &dp_fonts.f[oldsize];
516 static float snap_to_pixel_x(float x, float roundUpAt)
518 float pixelpos = x * vid.width / vid_conwidth.value;
519 int snap = (int) pixelpos;
520 if (pixelpos - snap >= roundUpAt) ++snap;
521 return ((float)snap * vid_conwidth.value / vid.width);
523 x = (int)(x * vid.width / vid_conwidth.value);
524 x = (x * vid_conwidth.value / vid.width);
529 static float snap_to_pixel_y(float y, float roundUpAt)
531 float pixelpos = y * vid.height / vid_conheight.value;
532 int snap = (int) pixelpos;
533 if (pixelpos - snap > roundUpAt) ++snap;
534 return ((float)snap * vid_conheight.value / vid.height);
536 y = (int)(y * vid.height / vid_conheight.value);
537 y = (y * vid_conheight.value / vid.height);
542 static void LoadFont_f(cmd_state_t *cmd)
546 const char *filelist, *c, *cm;
547 float sz, scale, voffset;
548 char mainfont[MAX_QPATH];
550 if(Cmd_Argc(cmd) < 2)
552 Con_Printf("Available font commands:\n");
553 for(i = 0; i < dp_fonts.maxsize; ++i)
554 if (dp_fonts.f[i].title[0])
555 Con_Printf(" loadfont %s gfx/tgafile[...] [custom switches] [sizes...]\n", dp_fonts.f[i].title);
556 Con_Printf("A font can simply be gfx/tgafile, or alternatively you\n"
557 "can specify multiple fonts and faces\n"
558 "Like this: gfx/vera-sans:2,gfx/fallback:1\n"
559 "to load face 2 of the font gfx/vera-sans and use face 1\n"
560 "of gfx/fallback as fallback font.\n"
561 "You can also specify a list of font sizes to load, like this:\n"
562 "loadfont console gfx/conchars,gfx/fallback 8 12 16 24 32\n"
563 "In many cases, 8 12 16 24 32 should be a good choice.\n"
565 " scale x : scale all characters by this amount when rendering (doesnt change line height)\n"
566 " voffset x : offset all chars vertical when rendering, this is multiplied to character height\n"
570 f = FindFont(Cmd_Argv(cmd, 1), true);
573 Con_Printf("font function not found\n");
578 if (strcmp(cmd->cmdline, f->cmdline) != 0 || r_font_always_reload.integer)
579 strlcpy(f->cmdline, cmd->cmdline, MAX_FONT_CMDLINE);
582 Con_DPrintf("LoadFont: font %s is unchanged\n", Cmd_Argv(cmd, 1));
587 if(Cmd_Argc(cmd) < 3)
588 filelist = "gfx/conchars";
590 filelist = Cmd_Argv(cmd, 2);
592 memset(f->fallbacks, 0, sizeof(f->fallbacks));
593 memset(f->fallback_faces, 0, sizeof(f->fallback_faces));
595 // first font is handled "normally"
596 c = strchr(filelist, ':');
597 cm = strchr(filelist, ',');
598 if(c && (!cm || c < cm))
599 f->req_face = atoi(c+1);
606 if(!c || (c - filelist) >= MAX_QPATH)
607 strlcpy(mainfont, filelist, sizeof(mainfont));
610 memcpy(mainfont, filelist, c - filelist);
611 mainfont[c - filelist] = 0;
614 for(i = 0; i < MAX_FONT_FALLBACKS; ++i)
616 c = strchr(filelist, ',');
622 c = strchr(filelist, ':');
623 cm = strchr(filelist, ',');
624 if(c && (!cm || c < cm))
625 f->fallback_faces[i] = atoi(c+1);
628 f->fallback_faces[i] = 0; // f->req_face; could make it stick to the default-font's face index
631 if(!c || (c-filelist) >= MAX_QPATH)
633 strlcpy(f->fallbacks[i], filelist, sizeof(mainfont));
637 memcpy(f->fallbacks[i], filelist, c - filelist);
638 f->fallbacks[i][c - filelist] = 0;
642 // for now: by default load only one size: the default size
644 for(i = 1; i < MAX_FONT_SIZES; ++i)
645 f->req_sizes[i] = -1;
649 if(Cmd_Argc(cmd) >= 4)
651 for(sizes = 0, i = 3; i < Cmd_Argc(cmd); ++i)
654 if (!strcmp(Cmd_Argv(cmd, i), "scale"))
657 if (i < Cmd_Argc(cmd))
658 scale = atof(Cmd_Argv(cmd, i));
661 if (!strcmp(Cmd_Argv(cmd, i), "voffset"))
664 if (i < Cmd_Argc(cmd))
665 voffset = atof(Cmd_Argv(cmd, i));
670 continue; // no slot for other sizes
672 // parse one of sizes
673 sz = atof(Cmd_Argv(cmd, i));
674 if (sz > 0.001f && sz < 1000.0f) // do not use crap sizes
676 // search for duplicated sizes
678 for (j=0; j<sizes; j++)
679 if (f->req_sizes[j] == sz)
682 continue; // sz already in req_sizes, don't add it again
684 if (sizes == MAX_FONT_SIZES)
686 Con_Printf(CON_WARN "Warning: specified more than %i different font sizes, exceding ones are ignored\n", MAX_FONT_SIZES);
690 f->req_sizes[sizes] = sz;
696 LoadFont(true, mainfont, f, scale, voffset);
704 static void gl_draw_start(void)
708 drawtexturepool = R_AllocTexturePool();
711 memset(cachepichash, 0, sizeof(cachepichash));
715 // load default font textures
716 for(i = 0; i < dp_fonts.maxsize; ++i)
717 if (dp_fonts.f[i].title[0])
718 LoadFont(false, va(vabuf, sizeof(vabuf), "gfx/font_%s", dp_fonts.f[i].title), &dp_fonts.f[i], 1, 0);
721 static void gl_draw_shutdown(void)
725 R_FreeTexturePool(&drawtexturepool);
728 memset(cachepichash, 0, sizeof(cachepichash));
731 static void gl_draw_newmap(void)
736 // mark all of the persistent pics so they are not purged...
737 for (i = 0; i < numcachepics; i++)
739 cachepic_t *pic = cachepics + i;
740 if (!pic->autoload && pic->skinframe)
741 R_SkinFrame_MarkUsed(pic->skinframe);
745 void GL_Draw_Init (void)
749 Cvar_RegisterVariable(&r_font_postprocess_blur);
750 Cvar_RegisterVariable(&r_font_postprocess_outline);
751 Cvar_RegisterVariable(&r_font_postprocess_shadow_x);
752 Cvar_RegisterVariable(&r_font_postprocess_shadow_y);
753 Cvar_RegisterVariable(&r_font_postprocess_shadow_z);
754 Cvar_RegisterVariable(&r_font_hinting);
755 Cvar_RegisterVariable(&r_font_antialias);
756 Cvar_RegisterVariable(&r_font_always_reload);
757 Cvar_RegisterVariable(&r_textshadow);
758 Cvar_RegisterVariable(&r_textbrightness);
759 Cvar_RegisterVariable(&r_textcontrast);
760 Cvar_RegisterVariable(&r_nearest_2d);
761 Cvar_RegisterVariable(&r_nearest_conchars);
763 // allocate fonts storage
764 fonts_mempool = Mem_AllocPool("FONTS", 0, NULL);
765 dp_fonts.maxsize = MAX_FONTS;
766 dp_fonts.f = (dp_font_t *)Mem_Alloc(fonts_mempool, sizeof(dp_font_t) * dp_fonts.maxsize);
767 memset(dp_fonts.f, 0, sizeof(dp_font_t) * dp_fonts.maxsize);
769 // assign starting font names
770 strlcpy(FONT_DEFAULT->title, "default", sizeof(FONT_DEFAULT->title));
771 strlcpy(FONT_DEFAULT->texpath, "gfx/conchars", sizeof(FONT_DEFAULT->texpath));
772 strlcpy(FONT_CONSOLE->title, "console", sizeof(FONT_CONSOLE->title));
773 strlcpy(FONT_SBAR->title, "sbar", sizeof(FONT_SBAR->title));
774 strlcpy(FONT_NOTIFY->title, "notify", sizeof(FONT_NOTIFY->title));
775 strlcpy(FONT_CHAT->title, "chat", sizeof(FONT_CHAT->title));
776 strlcpy(FONT_CENTERPRINT->title, "centerprint", sizeof(FONT_CENTERPRINT->title));
777 strlcpy(FONT_INFOBAR->title, "infobar", sizeof(FONT_INFOBAR->title));
778 strlcpy(FONT_MENU->title, "menu", sizeof(FONT_MENU->title));
779 for(i = 0, j = 0; i < MAX_USERFONTS; ++i)
780 if(!FONT_USER(i)->title[0])
781 dpsnprintf(FONT_USER(i)->title, sizeof(FONT_USER(i)->title), "user%d", j++);
783 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");
784 R_RegisterModule("GL_Draw", gl_draw_start, gl_draw_shutdown, gl_draw_newmap, NULL, NULL);
787 void DrawQ_Start(void)
789 r_refdef.draw2dstage = 1;
790 R_ResetViewRendering2D_Common(0, NULL, NULL, 0, 0, vid.width, vid.height, vid_conwidth.integer, vid_conheight.integer);
793 qbool r_draw2d_force = false;
795 void DrawQ_Pic(float x, float y, cachepic_t *pic, float width, float height, float red, float green, float blue, float alpha, int flags)
797 model_t *mod = CL_Mesh_UI();
801 pic = Draw_CachePic("white");
802 // make sure pic is loaded - we don't use the texture here, Mod_Mesh_GetTexture looks up the skinframe by name
803 Draw_GetPicTexture(pic);
807 height = pic->height;
808 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);
809 e0 = Mod_Mesh_IndexForVertex(mod, surf, x , y , 0, 0, 0, -1, 0, 0, 0, 0, red, green, blue, alpha);
810 e1 = Mod_Mesh_IndexForVertex(mod, surf, x + width, y , 0, 0, 0, -1, 1, 0, 0, 0, red, green, blue, alpha);
811 e2 = Mod_Mesh_IndexForVertex(mod, surf, x + width, y + height, 0, 0, 0, -1, 1, 1, 0, 0, red, green, blue, alpha);
812 e3 = Mod_Mesh_IndexForVertex(mod, surf, x , y + height, 0, 0, 0, -1, 0, 1, 0, 0, red, green, blue, alpha);
813 Mod_Mesh_AddTriangle(mod, surf, e0, e1, e2);
814 Mod_Mesh_AddTriangle(mod, surf, e0, e2, e3);
817 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)
819 float af = DEG2RAD(-angle); // forward
820 float ar = DEG2RAD(-angle + 90); // right
821 float sinaf = sin(af);
822 float cosaf = cos(af);
823 float sinar = sin(ar);
824 float cosar = cos(ar);
825 model_t *mod = CL_Mesh_UI();
829 pic = Draw_CachePic("white");
830 // make sure pic is loaded - we don't use the texture here, Mod_Mesh_GetTexture looks up the skinframe by name
831 Draw_GetPicTexture(pic);
835 height = pic->height;
836 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);
837 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);
838 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);
839 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);
840 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);
841 Mod_Mesh_AddTriangle(mod, surf, e0, e1, e2);
842 Mod_Mesh_AddTriangle(mod, surf, e0, e2, e3);
845 void DrawQ_Fill(float x, float y, float width, float height, float red, float green, float blue, float alpha, int flags)
847 DrawQ_Pic(x, y, Draw_CachePic("white"), width, height, red, green, blue, alpha, flags);
850 /// color tag printing
851 static const vec4_t string_colors[] =
854 // LadyHavoc: why on earth is cyan before magenta in Quake3?
855 // LadyHavoc: note: Doom3 uses white for [0] and [7]
856 {0.0, 0.0, 0.0, 1.0}, // black
857 {1.0, 0.0, 0.0, 1.0}, // red
858 {0.0, 1.0, 0.0, 1.0}, // green
859 {1.0, 1.0, 0.0, 1.0}, // yellow
860 {0.0, 0.0, 1.0, 1.0}, // blue
861 {0.0, 1.0, 1.0, 1.0}, // cyan
862 {1.0, 0.0, 1.0, 1.0}, // magenta
863 {1.0, 1.0, 1.0, 1.0}, // white
864 // [515]'s BX_COLOREDTEXT extension
865 {1.0, 1.0, 1.0, 0.5}, // half transparent
866 {0.5, 0.5, 0.5, 1.0} // half brightness
867 // Black's color table
868 //{1.0, 1.0, 1.0, 1.0},
869 //{1.0, 0.0, 0.0, 1.0},
870 //{0.0, 1.0, 0.0, 1.0},
871 //{0.0, 0.0, 1.0, 1.0},
872 //{1.0, 1.0, 0.0, 1.0},
873 //{0.0, 1.0, 1.0, 1.0},
874 //{1.0, 0.0, 1.0, 1.0},
875 //{0.1, 0.1, 0.1, 1.0}
878 #define STRING_COLORS_COUNT (sizeof(string_colors) / sizeof(vec4_t))
880 static void DrawQ_GetTextColor(float color[4], int colorindex, float r, float g, float b, float a, qbool shadow)
882 float C = r_textcontrast.value;
883 float B = r_textbrightness.value;
884 if (colorindex & 0x10000) // that bit means RGB color
886 color[0] = ((colorindex >> 12) & 0xf) / 15.0;
887 color[1] = ((colorindex >> 8) & 0xf) / 15.0;
888 color[2] = ((colorindex >> 4) & 0xf) / 15.0;
889 color[3] = (colorindex & 0xf) / 15.0;
892 Vector4Copy(string_colors[colorindex], color);
893 Vector4Set(color, color[0] * r * C + B, color[1] * g * C + B, color[2] * b * C + B, color[3] * a);
896 float shadowalpha = (color[0]+color[1]+color[2]) * 0.8;
897 Vector4Set(color, 0, 0, 0, color[3] * bound(0, shadowalpha, 1));
901 // returns a colorindex (format 0x1RGBA) if str is a valid RGB string
902 // returns 0 otherwise
903 static int RGBstring_to_colorindex(const char *str)
906 int ind = 0x0001 << 4;
908 if (*str <= '9' && *str >= '0')
913 if (ch >= 'a' && ch <= 'f')
920 } while(!(ind & 0x10000));
921 return ind | 0xf; // add costant alpha value
924 // NOTE: this function always draws exactly one character if maxwidth <= 0
925 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)
927 const char *text_start = text;
931 Uchar ch, mapch, nextch;
932 Uchar prevch = 0; // used for kerning
936 ft2_font_map_t *fontmap = NULL;
937 ft2_font_map_t *map = NULL;
938 ft2_font_t *ft2 = fnt->ft2;
941 qbool least_one = false;
942 float dw; // display w
943 //float dh; // display h
944 const float *width_of;
951 // do this in the end
952 w *= fnt->settings.scale;
953 h *= fnt->settings.scale;
955 // find the most fitting size:
959 map_index = Font_IndexForSize(ft2, h, &w, &h);
961 map_index = Font_IndexForSize(ft2, h, NULL, NULL);
962 fontmap = Font_MapForIndex(ft2, map_index);
971 if (!outcolor || *outcolor == -1)
972 colorindex = STRING_COLOR_DEFAULT;
974 colorindex = *outcolor;
976 // maxwidth /= fnt->scale; // w and h are multiplied by it already
977 // ftbase_x = snap_to_pixel_x(0);
982 maxwidth = -maxwidth;
986 // x = snap_to_pixel_x(x, 0.4); // haha, it's 0 anyway
989 width_of = fnt->width_of_ft2[map_index];
991 width_of = fnt->width_of;
994 while (((bytes_left = *maxlen - (text - text_start)) > 0) && *text)
997 nextch = ch = u8_getnchar(text, &text, bytes_left);
998 i = text - text_start;
1001 if (ch == ' ' && !fontmap)
1003 if(!least_one || i0) // never skip the first character
1004 if(x + width_of[(int) ' '] * dw > maxwidth)
1007 break; // oops, can't draw this
1009 x += width_of[(int) ' '] * dw;
1012 if (ch == STRING_COLOR_TAG && !ignorecolorcodes && i < *maxlen)
1014 ch = *text; // colors are ascii, so no u8_ needed
1015 if (ch <= '9' && ch >= '0') // ^[0-9] found
1017 colorindex = ch - '0';
1022 else if (ch == STRING_COLOR_RGB_TAG_CHAR && i + 3 < *maxlen ) // ^x found
1024 const char *text_p = &text[1];
1025 int tempcolorindex = RGBstring_to_colorindex(text_p);
1028 colorindex = tempcolorindex;
1034 else if (ch == STRING_COLOR_TAG) // ^^ found
1043 if (!fontmap || (ch <= 0xFF && fontmap->glyphs[ch].image) || (ch >= 0xE000 && ch <= 0xE0FF))
1050 map = ft2_oldstyle_map;
1052 if(!least_one || i0) // never skip the first character
1053 if(x + width_of[ch] * dw > maxwidth)
1056 break; // oops, can't draw this
1058 x += width_of[ch] * dw;
1060 if (!map || map == ft2_oldstyle_map || ch != prevch)
1062 Font_GetMapForChar(ft2, map_index, ch, &map, &mapch);
1066 if (prevch && Font_GetKerningForMap(ft2, map_index, w, h, prevch, ch, &kx, NULL))
1068 x += map->glyphs[mapch].advance_x * dw;
1076 *outcolor = colorindex;
1081 float DrawQ_Color[4];
1082 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)
1084 int shadow, colorindex = STRING_COLOR_DEFAULT;
1086 float x = startx, y, s, t, u, v, thisw;
1087 Uchar ch, mapch, nextch;
1088 Uchar prevch = 0; // used for kerning
1090 ft2_font_map_t *map = NULL; // the currently used map
1091 ft2_font_map_t *fontmap = NULL; // the font map for the size
1093 const char *text_start = text;
1095 ft2_font_t *ft2 = fnt->ft2;
1100 const float *width_of;
1101 model_t *mod = CL_Mesh_UI();
1102 msurface_t *surf = NULL;
1105 tw = Draw_GetPicWidth(fnt->pic);
1106 th = Draw_GetPicHeight(fnt->pic);
1114 starty -= (fnt->settings.scale - 1) * h * 0.5 - fnt->settings.voffset*h; // center & offset
1115 w *= fnt->settings.scale;
1116 h *= fnt->settings.scale;
1121 map_index = Font_IndexForSize(ft2, h, &w, &h);
1123 map_index = Font_IndexForSize(ft2, h, NULL, NULL);
1124 fontmap = Font_MapForIndex(ft2, map_index);
1130 // draw the font at its baseline when using freetype
1132 ftbase_y = dh * (4.5/6.0);
1137 if(!r_draw2d.integer && !r_draw2d_force)
1138 return startx + DrawQ_TextWidth_UntilWidth_TrackColors_Scale(text, &maxlen, w, h, sw, sh, NULL, ignorecolorcodes, fnt, 1000000000);
1140 //ftbase_x = snap_to_pixel_x(ftbase_x);
1143 startx = snap_to_pixel_x(startx, 0.4);
1144 starty = snap_to_pixel_y(starty, 0.4);
1145 ftbase_y = snap_to_pixel_y(ftbase_y, 0.3);
1148 pix_x = vid.width / vid_conwidth.value;
1149 pix_y = vid.height / vid_conheight.value;
1152 width_of = fnt->width_of_ft2[map_index];
1154 width_of = fnt->width_of;
1156 for (shadow = r_textshadow.value != 0 && basealpha > 0;shadow >= 0;shadow--)
1161 if (!outcolor || *outcolor == -1)
1162 colorindex = STRING_COLOR_DEFAULT;
1164 colorindex = *outcolor;
1166 DrawQ_GetTextColor(DrawQ_Color, colorindex, basered, basegreen, baseblue, basealpha, shadow != 0);
1173 x += r_textshadow.value * vid.width / vid_conwidth.value;
1174 y += r_textshadow.value * vid.height / vid_conheight.value;
1177 while (((bytes_left = maxlen - (text - text_start)) > 0) && *text)
1179 nextch = ch = u8_getnchar(text, &text, bytes_left);
1180 i = text - text_start;
1183 if (ch == ' ' && !fontmap)
1185 x += width_of[(int) ' '] * dw;
1188 if (ch == STRING_COLOR_TAG && !ignorecolorcodes && i < maxlen)
1190 ch = *text; // colors are ascii, so no u8_ needed
1191 if (ch <= '9' && ch >= '0') // ^[0-9] found
1193 colorindex = ch - '0';
1194 DrawQ_GetTextColor(DrawQ_Color, colorindex, basered, basegreen, baseblue, basealpha, shadow != 0);
1199 else if (ch == STRING_COLOR_RGB_TAG_CHAR && i+3 < maxlen ) // ^x found
1201 const char *text_p = &text[1];
1202 int tempcolorindex = RGBstring_to_colorindex(text_p);
1205 colorindex = tempcolorindex;
1206 DrawQ_GetTextColor(DrawQ_Color, colorindex, basered, basegreen, baseblue, basealpha, shadow != 0);
1212 else if (ch == STRING_COLOR_TAG)
1221 // using a value of -1 for the oldstyle map because NULL means uninitialized...
1222 // this way we don't need to rebind fnt->tex for every old-style character
1223 // E000..E0FF: emulate old-font characters (to still have smileys and such available)
1226 x += 1.0/pix_x * r_textshadow.value;
1227 y += 1.0/pix_y * r_textshadow.value;
1229 if (!fontmap || (ch <= 0xFF && fontmap->glyphs[ch].image) || (ch >= 0xE000 && ch <= 0xE0FF))
1236 map = ft2_oldstyle_map;
1238 //num = (unsigned char) text[i];
1239 //thisw = fnt->width_of[num];
1240 thisw = fnt->width_of[ch];
1241 // FIXME make these smaller to just include the occupied part of the character for slightly faster rendering
1242 if (r_nearest_conchars.integer)
1244 s = (ch & 15)*0.0625f;
1245 t = (ch >> 4)*0.0625f;
1246 u = 0.0625f * thisw;
1251 s = (ch & 15)*0.0625f + (0.5f / tw);
1252 t = (ch >> 4)*0.0625f + (0.5f / th);
1253 u = 0.0625f * thisw - (1.0f / tw);
1254 v = 0.0625f - (1.0f / th);
1256 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);
1257 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]);
1258 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]);
1259 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]);
1260 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]);
1261 Mod_Mesh_AddTriangle(mod, surf, e0, e1, e2);
1262 Mod_Mesh_AddTriangle(mod, surf, e0, e2, e3);
1263 x += width_of[ch] * dw;
1265 if (!map || map == ft2_oldstyle_map || ch != prevch)
1267 Font_GetMapForChar(ft2, map_index, ch, &map, &mapch);
1275 thisw = map->glyphs[mapch].advance_x;
1279 if (prevch && Font_GetKerningForMap(ft2, map_index, w, h, prevch, ch, &kx, &ky))
1286 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);
1287 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]);
1288 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]);
1289 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]);
1290 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]);
1291 Mod_Mesh_AddTriangle(mod, surf, e0, e1, e2);
1292 Mod_Mesh_AddTriangle(mod, surf, e0, e2, e3);
1304 x -= 1.0/pix_x * r_textshadow.value;
1305 y -= 1.0/pix_y * r_textshadow.value;
1311 *outcolor = colorindex;
1313 // note: this relies on the proper text (not shadow) being drawn last
1317 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)
1319 return DrawQ_String_Scale(startx, starty, text, maxlen, w, h, 1, 1, basered, basegreen, baseblue, basealpha, flags, outcolor, ignorecolorcodes, fnt);
1322 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)
1324 return DrawQ_TextWidth_UntilWidth_TrackColors_Scale(text, maxlen, w, h, 1, 1, outcolor, ignorecolorcodes, fnt, maxwidth);
1327 float DrawQ_TextWidth(const char *text, size_t maxlen, float w, float h, qbool ignorecolorcodes, const dp_font_t *fnt)
1329 return DrawQ_TextWidth_UntilWidth(text, &maxlen, w, h, ignorecolorcodes, fnt, 1000000000);
1332 float DrawQ_TextWidth_UntilWidth(const char *text, size_t *maxlen, float w, float h, qbool ignorecolorcodes, const dp_font_t *fnt, float maxWidth)
1334 return DrawQ_TextWidth_UntilWidth_TrackColors(text, maxlen, w, h, NULL, ignorecolorcodes, fnt, maxWidth);
1339 // no ^xrgb management
1340 static int DrawQ_BuildColoredText(char *output2c, size_t maxoutchars, const char *text, int maxreadchars, qbool ignorecolorcodes, int *outcolor)
1342 int color, numchars = 0;
1343 char *outputend2c = output2c + maxoutchars - 2;
1344 if (!outcolor || *outcolor == -1)
1345 color = STRING_COLOR_DEFAULT;
1349 maxreadchars = 1<<30;
1350 textend = text + maxreadchars;
1351 while (text != textend && *text)
1353 if (*text == STRING_COLOR_TAG && !ignorecolorcodes && text + 1 != textend)
1355 if (text[1] == STRING_COLOR_TAG)
1357 else if (text[1] >= '0' && text[1] <= '9')
1359 color = text[1] - '0';
1364 if (output2c >= outputend2c)
1366 *output2c++ = *text++;
1367 *output2c++ = color;
1370 output2c[0] = output2c[1] = 0;
1377 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)
1379 model_t *mod = CL_Mesh_UI();
1383 pic = Draw_CachePic("white");
1384 // make sure pic is loaded - we don't use the texture here, Mod_Mesh_GetTexture looks up the skinframe by name
1385 Draw_GetPicTexture(pic);
1389 height = pic->height;
1390 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);
1391 e0 = Mod_Mesh_IndexForVertex(mod, surf, x , y , 0, 0, 0, -1, s1, t1, 0, 0, r1, g1, b1, a1);
1392 e1 = Mod_Mesh_IndexForVertex(mod, surf, x + width, y , 0, 0, 0, -1, s2, t2, 0, 0, r2, g2, b2, a2);
1393 e2 = Mod_Mesh_IndexForVertex(mod, surf, x + width, y + height, 0, 0, 0, -1, s4, t4, 0, 0, r4, g4, b4, a4);
1394 e3 = Mod_Mesh_IndexForVertex(mod, surf, x , y + height, 0, 0, 0, -1, s3, t3, 0, 0, r3, g3, b3, a3);
1395 Mod_Mesh_AddTriangle(mod, surf, e0, e1, e2);
1396 Mod_Mesh_AddTriangle(mod, surf, e0, e2, e3);
1399 void DrawQ_Line (float width, float x1, float y1, float x2, float y2, float r, float g, float b, float alpha, int flags)
1401 model_t *mod = CL_Mesh_UI();
1404 float offsetx, offsety;
1405 // width is measured in real pixels
1406 if (fabs(x2 - x1) > fabs(y2 - y1))
1409 offsety = 0.5f * width * vid_conheight.value / vid.height;
1413 offsetx = 0.5f * width * vid_conwidth.value / vid.width;
1416 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);
1417 e0 = Mod_Mesh_IndexForVertex(mod, surf, x1 - offsetx, y1 - offsety, 10, 0, 0, -1, 0, 0, 0, 0, r, g, b, alpha);
1418 e1 = Mod_Mesh_IndexForVertex(mod, surf, x2 - offsetx, y2 - offsety, 10, 0, 0, -1, 0, 0, 0, 0, r, g, b, alpha);
1419 e2 = Mod_Mesh_IndexForVertex(mod, surf, x2 + offsetx, y2 + offsety, 10, 0, 0, -1, 0, 0, 0, 0, r, g, b, alpha);
1420 e3 = Mod_Mesh_IndexForVertex(mod, surf, x1 + offsetx, y1 + offsety, 10, 0, 0, -1, 0, 0, 0, 0, r, g, b, alpha);
1421 Mod_Mesh_AddTriangle(mod, surf, e0, e1, e2);
1422 Mod_Mesh_AddTriangle(mod, surf, e0, e2, e3);
1425 void DrawQ_SetClipArea(float x, float y, float width, float height)
1430 // We have to convert the con coords into real coords
1431 // OGL uses bottom to top (origin is in bottom left)
1432 ix = (int)(0.5 + x * ((float)r_refdef.view.width / vid_conwidth.integer)) + r_refdef.view.x;
1433 iy = (int)(0.5 + y * ((float)r_refdef.view.height / vid_conheight.integer)) + r_refdef.view.y;
1434 iw = (int)(0.5 + width * ((float)r_refdef.view.width / vid_conwidth.integer));
1435 ih = (int)(0.5 + height * ((float)r_refdef.view.height / vid_conheight.integer));
1436 switch(vid.renderpath)
1438 case RENDERPATH_GL32:
1439 case RENDERPATH_GLES2:
1440 GL_Scissor(ix, vid.height - iy - ih, iw, ih);
1444 GL_ScissorTest(true);
1447 void DrawQ_ResetClipArea(void)
1450 GL_ScissorTest(false);
1453 void DrawQ_Finish(void)
1456 r_refdef.draw2dstage = 0;
1459 void DrawQ_RecalcView(void)
1462 if(r_refdef.draw2dstage)
1463 r_refdef.draw2dstage = -1; // next draw call will set viewport etc. again
1466 void DrawQ_FlushUI(void)
1468 model_t *mod = CL_Mesh_UI();
1469 if (mod->num_surfaces == 0)
1472 if (!r_draw2d.integer && !r_draw2d_force)
1474 Mod_Mesh_Reset(mod);
1478 // this is roughly equivalent to R_Mod_Draw, so the UI can use full material feature set
1479 r_refdef.view.colorscale = 1;
1480 r_textureframe++; // used only by R_GetCurrentTexture
1481 GL_DepthMask(false);
1483 Mod_Mesh_Finalize(mod);
1484 R_DrawModelSurfaces(&cl_meshentities[MESH_UI].render, false, false, false, false, false, true);
1486 Mod_Mesh_Reset(mod);