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.
26 #include "cl_dyntexture.h"
29 #include "ft2_fontdefs.h"
32 static mempool_t *fonts_mempool = NULL;
34 cvar_t r_textshadow = {CVAR_SAVE, "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)"};
35 cvar_t r_textbrightness = {CVAR_SAVE, "r_textbrightness", "0", "additional brightness for text color codes (0 keeps colors as is, 1 makes them all white)"};
36 cvar_t r_textcontrast = {CVAR_SAVE, "r_textcontrast", "1", "additional contrast for text color codes (1 keeps colors as is, 0 makes them all black)"};
38 cvar_t r_font_postprocess_blur = {CVAR_SAVE, "r_font_postprocess_blur", "0", "font blur amount"};
39 cvar_t r_font_postprocess_outline = {CVAR_SAVE, "r_font_postprocess_outline", "0", "font outline amount"};
40 cvar_t r_font_postprocess_shadow_x = {CVAR_SAVE, "r_font_postprocess_shadow_x", "0", "font shadow X shift amount, applied during outlining"};
41 cvar_t r_font_postprocess_shadow_y = {CVAR_SAVE, "r_font_postprocess_shadow_y", "0", "font shadow Y shift amount, applied during outlining"};
42 cvar_t r_font_postprocess_shadow_z = {CVAR_SAVE, "r_font_postprocess_shadow_z", "0", "font shadow Z shift amount, applied during blurring"};
43 cvar_t r_font_hinting = {CVAR_SAVE, "r_font_hinting", "3", "0 = no hinting, 1 = light autohinting, 2 = full autohinting, 3 = full hinting"};
44 cvar_t r_font_antialias = {CVAR_SAVE, "r_font_antialias", "1", "0 = monochrome, 1 = grey" /* , 2 = rgb, 3 = bgr" */};
45 cvar_t r_nearest_2d = {CVAR_SAVE, "r_nearest_2d", "0", "use nearest filtering on all 2d textures (including conchars)"};
46 cvar_t r_nearest_conchars = {CVAR_SAVE, "r_nearest_conchars", "0", "use nearest filtering on conchars texture"};
48 //=============================================================================
49 /* Support Routines */
51 #define FONT_FILESIZE 13468
52 static cachepic_t *cachepichash[CACHEPICHASHSIZE];
53 static cachepic_t cachepics[MAX_CACHED_PICS];
54 static int numcachepics;
56 rtexturepool_t *drawtexturepool;
58 static const unsigned char concharimage[FONT_FILESIZE] =
63 static rtexture_t *draw_generateconchars(void)
70 data = LoadTGA_BGRA (concharimage, FONT_FILESIZE, NULL);
72 for (i = 0;i < 8192;i++)
74 random = lhrandom (0.0,1.0);
75 data[i*4+3] = data[i*4+0];
76 data[i*4+2] = 83 + (unsigned char)(random * 64);
77 data[i*4+1] = 71 + (unsigned char)(random * 32);
78 data[i*4+0] = 23 + (unsigned char)(random * 16);
81 for (i = 8192;i < 32768;i++)
83 random = lhrandom (0.0,1.0);
84 data[i*4+3] = data[i*4+0];
85 data[i*4+2] = 95 + (unsigned char)(random * 64);
86 data[i*4+1] = 95 + (unsigned char)(random * 64);
87 data[i*4+0] = 95 + (unsigned char)(random * 64);
90 for (i = 32768;i < 40960;i++)
92 random = lhrandom (0.0,1.0);
93 data[i*4+3] = data[i*4+0];
94 data[i*4+2] = 83 + (unsigned char)(random * 64);
95 data[i*4+1] = 71 + (unsigned char)(random * 32);
96 data[i*4+0] = 23 + (unsigned char)(random * 16);
99 for (i = 40960;i < 65536;i++)
101 random = lhrandom (0.0,1.0);
102 data[i*4+3] = data[i*4+0];
103 data[i*4+2] = 96 + (unsigned char)(random * 64);
104 data[i*4+1] = 43 + (unsigned char)(random * 32);
105 data[i*4+0] = 27 + (unsigned char)(random * 32);
109 Image_WriteTGABGRA ("gfx/generated_conchars.tga", 256, 256, data);
112 tex = R_LoadTexture2D(drawtexturepool, "conchars", 256, 256, data, TEXTYPE_BGRA, TEXF_ALPHA | (r_nearest_conchars.integer ? TEXF_FORCENEAREST : 0), -1, NULL);
117 static rtexture_t *draw_generateditherpattern(void)
120 unsigned char pixels[8][8];
121 for (y = 0;y < 8;y++)
122 for (x = 0;x < 8;x++)
123 pixels[y][x] = ((x^y) & 4) ? 254 : 0;
124 return R_LoadTexture2D(drawtexturepool, "ditherpattern", 8, 8, pixels[0], TEXTYPE_PALETTE, TEXF_FORCENEAREST, -1, palette_bgra_transparent);
127 typedef struct embeddedpic_s
136 static const embeddedpic_t embeddedpics[] =
139 "gfx/prydoncursor001", 16, 16,
158 "ui/mousepointer", 16, 16,
177 "gfx/crosshair1", 16, 16,
196 "gfx/crosshair2", 16, 16,
215 "gfx/crosshair3", 16, 16,
234 "gfx/crosshair4", 16, 16,
253 "gfx/crosshair5", 8, 8,
264 "gfx/crosshair6", 2, 2,
269 "gfx/crosshair7", 16, 16,
290 static rtexture_t *draw_generatepic(const char *name, qboolean quiet)
292 const embeddedpic_t *p;
293 for (p = embeddedpics;p->name;p++)
294 if (!strcmp(name, p->name))
295 return R_LoadTexture2D(drawtexturepool, p->name, p->width, p->height, (const unsigned char *)p->pixels, TEXTYPE_PALETTE, TEXF_ALPHA, -1, palette_bgra_embeddedpic);
296 if (!strcmp(name, "gfx/conchars"))
297 return draw_generateconchars();
298 if (!strcmp(name, "gfx/colorcontrol/ditherpattern"))
299 return draw_generateditherpattern();
301 Con_DPrintf("Draw_CachePic: failed to load %s\n", name);
302 return r_texture_notexture;
312 // FIXME: move this to client somehow
313 cachepic_t *Draw_CachePic_Flags(const char *path, unsigned int cachepicflags)
316 unsigned char *pixels = NULL;
319 unsigned char *lmpdata;
320 char lmpname[MAX_QPATH];
323 qboolean ddshasalpha;
324 float ddsavgcolor[4];
325 qboolean loaded = false;
328 texflags = TEXF_ALPHA;
329 if (!(cachepicflags & CACHEPICFLAG_NOCLAMP))
330 texflags |= TEXF_CLAMP;
331 if (cachepicflags & CACHEPICFLAG_MIPMAP)
332 texflags |= TEXF_MIPMAP;
333 if (!(cachepicflags & CACHEPICFLAG_NOCOMPRESSION) && gl_texturecompression_2d.integer && gl_texturecompression.integer)
334 texflags |= TEXF_COMPRESS;
335 if ((cachepicflags & CACHEPICFLAG_NEAREST) || r_nearest_2d.integer)
336 texflags |= TEXF_FORCENEAREST;
338 // check whether the picture has already been cached
339 crc = CRC_Block((unsigned char *)path, strlen(path));
340 hashkey = ((crc >> 8) ^ crc) % CACHEPICHASHSIZE;
341 for (pic = cachepichash[hashkey];pic;pic = pic->chain)
343 if (!strcmp (path, pic->name))
345 // if it was created (or replaced) by Draw_NewPic, just return it
346 if(pic->flags & CACHEPICFLAG_NEWPIC)
348 if (!((pic->texflags ^ texflags) & ~(TEXF_COMPRESS | TEXF_MIPMAP))) // ignore TEXF_COMPRESS when comparing, because fallback pics remove the flag, and ignore TEXF_MIPMAP because QC specifies that
350 if(!(cachepicflags & CACHEPICFLAG_NOTPERSISTENT))
353 pic->autoload = false; // persist it
355 goto reload; // load it below, and then persist
362 if (numcachepics == MAX_CACHED_PICS)
364 Con_Printf ("Draw_CachePic: numcachepics == MAX_CACHED_PICS\n");
365 // FIXME: support NULL in callers?
366 return cachepics; // return the first one
368 pic = cachepics + (numcachepics++);
369 memset(pic, 0, sizeof(*pic));
370 strlcpy (pic->name, path, sizeof(pic->name));
372 pic->chain = cachepichash[hashkey];
373 cachepichash[hashkey] = pic;
376 // TODO why does this crash?
377 if(pic->allow_free_tex && pic->tex)
378 R_PurgeTexture(pic->tex);
380 // check whether it is an dynamic texture (if so, we can directly use its texture handler)
381 pic->flags = cachepicflags;
382 pic->tex = CL_GetDynTexture( path );
383 // if so, set the width/height, too
385 pic->allow_free_tex = false;
386 pic->width = R_TextureWidth(pic->tex);
387 pic->height = R_TextureHeight(pic->tex);
388 // we're done now (early-out)
392 pic->allow_free_tex = true;
394 pic->hasalpha = true; // assume alpha unless we know it has none
395 pic->texflags = texflags;
396 pic->autoload = (cachepicflags & CACHEPICFLAG_NOTPERSISTENT);
397 pic->lastusedframe = draw_frame;
399 // load a high quality image from disk if possible
400 if (!loaded && r_texture_dds_load.integer != 0 && (pic->tex = R_LoadTextureDDSFile(drawtexturepool, va(vabuf, sizeof(vabuf), "dds/%s.dds", pic->name), vid.sRGB2D, pic->texflags, &ddshasalpha, ddsavgcolor, 0, false)))
402 // note this loads even if autoload is true, otherwise we can't get the width/height
404 pic->hasalpha = ddshasalpha;
405 pic->width = R_TextureWidth(pic->tex);
406 pic->height = R_TextureHeight(pic->tex);
408 if (!loaded && ((pixels = loadimagepixelsbgra(pic->name, false, true, false, NULL)) || (!strncmp(pic->name, "gfx/", 4) && (pixels = loadimagepixelsbgra(pic->name+4, false, true, false, NULL)))))
411 pic->hasalpha = false;
412 if (pic->texflags & TEXF_ALPHA)
414 for (j = 3;j < image_width * image_height * 4;j += 4)
418 pic->hasalpha = true;
424 pic->width = image_width;
425 pic->height = image_height;
428 pic->tex = R_LoadTexture2D(drawtexturepool, pic->name, image_width, image_height, pixels, vid.sRGB2D ? TEXTYPE_SRGB_BGRA : TEXTYPE_BGRA, pic->texflags & (pic->hasalpha ? ~0 : ~TEXF_ALPHA), -1, NULL);
430 if (r_texture_dds_save.integer && qglGetCompressedTexImageARB && pic->tex)
431 R_SaveTextureDDSFile(pic->tex, va(vabuf, sizeof(vabuf), "dds/%s.dds", pic->name), r_texture_dds_save.integer < 2, pic->hasalpha);
437 pic->autoload = false;
438 // never compress the fallback images
439 pic->texflags &= ~TEXF_COMPRESS;
442 // now read the low quality version (wad or lmp file), and take the pic
443 // size from that even if we don't upload the texture, this way the pics
444 // show up the right size in the menu even if they were replaced with
445 // higher or lower resolution versions
446 dpsnprintf(lmpname, sizeof(lmpname), "%s.lmp", pic->name);
447 if ((!strncmp(pic->name, "gfx/", 4) || (gamemode == GAME_BLOODOMNICIDE && !strncmp(pic->name, "locale/", 6))) && (lmpdata = FS_LoadFile(lmpname, tempmempool, false, &lmpsize)))
449 if (developer_loading.integer)
450 Con_Printf("loading lump \"%s\"\n", pic->name);
454 pic->width = lmpdata[0] + lmpdata[1] * 256 + lmpdata[2] * 65536 + lmpdata[3] * 16777216;
455 pic->height = lmpdata[4] + lmpdata[5] * 256 + lmpdata[6] * 65536 + lmpdata[7] * 16777216;
456 // if no high quality replacement image was found, upload the original low quality texture
460 pic->tex = R_LoadTexture2D(drawtexturepool, pic->name, pic->width, pic->height, lmpdata + 8, vid.sRGB2D ? TEXTYPE_SRGB_PALETTE : TEXTYPE_PALETTE, pic->texflags, -1, palette_bgra_transparent);
465 else if ((lmpdata = W_GetLumpName (pic->name + 4)))
467 if (developer_loading.integer)
468 Con_Printf("loading gfx.wad lump \"%s\"\n", pic->name + 4);
470 if (!strcmp(pic->name, "gfx/conchars"))
472 // conchars is a raw image and with color 0 as transparent instead of 255
475 // if no high quality replacement image was found, upload the original low quality texture
479 pic->tex = R_LoadTexture2D(drawtexturepool, pic->name, 128, 128, lmpdata, vid.sRGB2D != 0 ? TEXTYPE_SRGB_PALETTE : TEXTYPE_PALETTE, pic->texflags, -1, palette_bgra_font);
484 pic->width = lmpdata[0] + lmpdata[1] * 256 + lmpdata[2] * 65536 + lmpdata[3] * 16777216;
485 pic->height = lmpdata[4] + lmpdata[5] * 256 + lmpdata[6] * 65536 + lmpdata[7] * 16777216;
486 // if no high quality replacement image was found, upload the original low quality texture
490 pic->tex = R_LoadTexture2D(drawtexturepool, pic->name, pic->width, pic->height, lmpdata + 8, vid.sRGB2D != 0 ? TEXTYPE_SRGB_PALETTE : TEXTYPE_PALETTE, pic->texflags, -1, palette_bgra_transparent);
502 // if it's not found on disk, generate an image
503 pic->tex = draw_generatepic(pic->name, (cachepicflags & CACHEPICFLAG_QUIET) != 0);
504 pic->width = R_TextureWidth(pic->tex);
505 pic->height = R_TextureHeight(pic->tex);
506 pic->allow_free_tex = (pic->tex != r_texture_notexture);
512 cachepic_t *Draw_CachePic (const char *path)
514 return Draw_CachePic_Flags (path, 0); // default to persistent!
517 rtexture_t *Draw_GetPicTexture(cachepic_t *pic)
520 if (pic->autoload && !pic->tex)
522 if (pic->tex == NULL && r_texture_dds_load.integer != 0)
524 qboolean ddshasalpha;
525 float ddsavgcolor[4];
526 pic->tex = R_LoadTextureDDSFile(drawtexturepool, va(vabuf, sizeof(vabuf), "dds/%s.dds", pic->name), vid.sRGB2D, pic->texflags, &ddshasalpha, ddsavgcolor, 0, false);
528 if (pic->tex == NULL)
530 pic->tex = loadtextureimage(drawtexturepool, pic->name, false, pic->texflags, true, vid.sRGB2D);
532 if (r_texture_dds_save.integer && qglGetCompressedTexImageARB && pic->tex)
533 R_SaveTextureDDSFile(pic->tex, va(vabuf, sizeof(vabuf), "dds/%s.dds", pic->name), r_texture_dds_save.integer < 2, pic->hasalpha);
536 if (pic->tex == NULL && !strncmp(pic->name, "gfx/", 4))
538 pic->tex = loadtextureimage(drawtexturepool, pic->name+4, false, pic->texflags, true, vid.sRGB2D);
540 if (r_texture_dds_save.integer && qglGetCompressedTexImageARB && pic->tex)
541 R_SaveTextureDDSFile(pic->tex, va(vabuf, sizeof(vabuf), "dds/%s.dds", pic->name), r_texture_dds_save.integer < 2, pic->hasalpha);
544 if (pic->tex == NULL)
545 pic->tex = draw_generatepic(pic->name, true);
547 pic->lastusedframe = draw_frame;
551 void Draw_Frame(void)
555 static double nextpurgetime;
556 if (nextpurgetime > realtime)
558 nextpurgetime = realtime + 0.05;
559 for (i = 0, pic = cachepics;i < numcachepics;i++, pic++)
561 if (pic->autoload && pic->tex && pic->lastusedframe < draw_frame)
563 R_FreeTexture(pic->tex);
570 cachepic_t *Draw_NewPic(const char *picname, int width, int height, int alpha, unsigned char *pixels_bgra)
575 crc = CRC_Block((unsigned char *)picname, strlen(picname));
576 hashkey = ((crc >> 8) ^ crc) % CACHEPICHASHSIZE;
577 for (pic = cachepichash[hashkey];pic;pic = pic->chain)
578 if (!strcmp (picname, pic->name))
583 if (pic->flags == CACHEPICFLAG_NEWPIC && pic->tex && pic->width == width && pic->height == height)
585 R_UpdateTexture(pic->tex, pixels_bgra, 0, 0, 0, width, height, 1);
591 if (numcachepics == MAX_CACHED_PICS)
593 Con_Printf ("Draw_NewPic: numcachepics == MAX_CACHED_PICS\n");
594 // FIXME: support NULL in callers?
595 return cachepics; // return the first one
597 pic = cachepics + (numcachepics++);
598 memset(pic, 0, sizeof(*pic));
599 strlcpy (pic->name, picname, sizeof(pic->name));
601 pic->chain = cachepichash[hashkey];
602 cachepichash[hashkey] = pic;
605 pic->flags = CACHEPICFLAG_NEWPIC; // disable texflags checks in Draw_CachePic
607 pic->height = height;
608 if (pic->allow_free_tex && pic->tex)
609 R_FreeTexture(pic->tex);
610 pic->tex = R_LoadTexture2D(drawtexturepool, picname, width, height, pixels_bgra, TEXTYPE_BGRA, (alpha ? TEXF_ALPHA : 0), -1, NULL);
614 void Draw_FreePic(const char *picname)
619 // this doesn't really free the pic, but does free it's texture
620 crc = CRC_Block((unsigned char *)picname, strlen(picname));
621 hashkey = ((crc >> 8) ^ crc) % CACHEPICHASHSIZE;
622 for (pic = cachepichash[hashkey];pic;pic = pic->chain)
624 if (!strcmp (picname, pic->name) && pic->tex)
626 R_FreeTexture(pic->tex);
635 static float snap_to_pixel_x(float x, float roundUpAt);
636 extern int con_linewidth; // to force rewrapping
637 void LoadFont(qboolean override, const char *name, dp_font_t *fnt, float scale, float voffset)
641 char widthfile[MAX_QPATH];
643 fs_offset_t widthbufsize;
645 if(override || !fnt->texpath[0])
647 strlcpy(fnt->texpath, name, sizeof(fnt->texpath));
648 // load the cvars when the font is FIRST loader
649 fnt->settings.scale = scale;
650 fnt->settings.voffset = voffset;
651 fnt->settings.antialias = r_font_antialias.integer;
652 fnt->settings.hinting = r_font_hinting.integer;
653 fnt->settings.outline = r_font_postprocess_outline.value;
654 fnt->settings.blur = r_font_postprocess_blur.value;
655 fnt->settings.shadowx = r_font_postprocess_shadow_x.value;
656 fnt->settings.shadowy = r_font_postprocess_shadow_y.value;
657 fnt->settings.shadowz = r_font_postprocess_shadow_z.value;
660 if (fnt->settings.scale <= 0)
661 fnt->settings.scale = 1;
663 if(drawtexturepool == NULL)
664 return; // before gl_draw_start, so will be loaded later
668 // clear freetype font
669 Font_UnloadFont(fnt->ft2);
674 if(fnt->req_face != -1)
676 if(!Font_LoadFont(fnt->texpath, fnt))
677 Con_DPrintf("Failed to load font-file for '%s', it will not support as many characters.\n", fnt->texpath);
680 fnt->tex = Draw_CachePic_Flags(fnt->texpath, CACHEPICFLAG_QUIET | CACHEPICFLAG_NOCOMPRESSION | (r_nearest_conchars.integer ? CACHEPICFLAG_NEAREST : 0))->tex;
681 if(fnt->tex == r_texture_notexture)
683 for (i = 0; i < MAX_FONT_FALLBACKS; ++i)
685 if (!fnt->fallbacks[i][0])
687 fnt->tex = Draw_CachePic_Flags(fnt->fallbacks[i], CACHEPICFLAG_QUIET | CACHEPICFLAG_NOCOMPRESSION | (r_nearest_conchars.integer ? CACHEPICFLAG_NEAREST : 0))->tex;
688 if(fnt->tex != r_texture_notexture)
691 if(fnt->tex == r_texture_notexture)
693 fnt->tex = Draw_CachePic_Flags("gfx/conchars", CACHEPICFLAG_NOCOMPRESSION | (r_nearest_conchars.integer ? CACHEPICFLAG_NEAREST : 0))->tex;
694 strlcpy(widthfile, "gfx/conchars.width", sizeof(widthfile));
697 dpsnprintf(widthfile, sizeof(widthfile), "%s.width", fnt->fallbacks[i]);
700 dpsnprintf(widthfile, sizeof(widthfile), "%s.width", fnt->texpath);
702 // unspecified width == 1 (base width)
703 for(ch = 0; ch < 256; ++ch)
704 fnt->width_of[ch] = 1;
706 // FIXME load "name.width", if it fails, fill all with 1
707 if((widthbuf = (char *) FS_LoadFile(widthfile, tempmempool, true, &widthbufsize)))
709 float extraspacing = 0;
710 const char *p = widthbuf;
715 if(!COM_ParseToken_Simple(&p, false, false, true))
733 fnt->width_of[ch] = atof(com_token) + extraspacing;
737 if(!strcmp(com_token, "extraspacing"))
739 if(!COM_ParseToken_Simple(&p, false, false, true))
741 extraspacing = atof(com_token);
743 else if(!strcmp(com_token, "scale"))
745 if(!COM_ParseToken_Simple(&p, false, false, true))
747 fnt->settings.scale = atof(com_token);
751 Con_Printf("Warning: skipped unknown font property %s\n", com_token);
752 if(!COM_ParseToken_Simple(&p, false, false, true))
764 for (i = 0; i < MAX_FONT_SIZES; ++i)
766 ft2_font_map_t *map = Font_MapForIndex(fnt->ft2, i);
769 for(ch = 0; ch < 256; ++ch)
770 map->width_of[ch] = Font_SnapTo(fnt->width_of[ch], 1/map->size);
774 maxwidth = fnt->width_of[0];
775 for(i = 1; i < 256; ++i)
776 maxwidth = max(maxwidth, fnt->width_of[i]);
777 fnt->maxwidth = maxwidth;
779 // fix up maxwidth for overlap
780 fnt->maxwidth *= fnt->settings.scale;
782 if(fnt == FONT_CONSOLE)
783 con_linewidth = -1; // rewrap console in next frame
786 extern cvar_t developer_font;
787 dp_font_t *FindFont(const char *title, qboolean allocate_new)
792 for(i = 0; i < dp_fonts.maxsize; ++i)
793 if(!strcmp(dp_fonts.f[i].title, title))
794 return &dp_fonts.f[i];
795 // if not found - try allocate
798 // find any font with empty title
799 for(i = 0; i < dp_fonts.maxsize; ++i)
801 if(!strcmp(dp_fonts.f[i].title, ""))
803 strlcpy(dp_fonts.f[i].title, title, sizeof(dp_fonts.f[i].title));
804 return &dp_fonts.f[i];
807 // if no any 'free' fonts - expand buffer
808 oldsize = dp_fonts.maxsize;
809 dp_fonts.maxsize = dp_fonts.maxsize + FONTS_EXPAND;
810 if (developer_font.integer)
811 Con_Printf("FindFont: enlarging fonts buffer (%i -> %i)\n", oldsize, dp_fonts.maxsize);
812 dp_fonts.f = (dp_font_t *)Mem_Realloc(fonts_mempool, dp_fonts.f, sizeof(dp_font_t) * dp_fonts.maxsize);
813 // relink ft2 structures
814 for(i = 0; i < oldsize; ++i)
815 if (dp_fonts.f[i].ft2)
816 dp_fonts.f[i].ft2->settings = &dp_fonts.f[i].settings;
817 // register a font in first expanded slot
818 strlcpy(dp_fonts.f[oldsize].title, title, sizeof(dp_fonts.f[oldsize].title));
819 return &dp_fonts.f[oldsize];
824 static float snap_to_pixel_x(float x, float roundUpAt)
826 float pixelpos = x * vid.width / vid_conwidth.value;
827 int snap = (int) pixelpos;
828 if (pixelpos - snap >= roundUpAt) ++snap;
829 return ((float)snap * vid_conwidth.value / vid.width);
831 x = (int)(x * vid.width / vid_conwidth.value);
832 x = (x * vid_conwidth.value / vid.width);
837 static float snap_to_pixel_y(float y, float roundUpAt)
839 float pixelpos = y * vid.height / vid_conheight.value;
840 int snap = (int) pixelpos;
841 if (pixelpos - snap > roundUpAt) ++snap;
842 return ((float)snap * vid_conheight.value / vid.height);
844 y = (int)(y * vid.height / vid_conheight.value);
845 y = (y * vid_conheight.value / vid.height);
850 static void LoadFont_f(void)
854 const char *filelist, *c, *cm;
855 float sz, scale, voffset;
856 char mainfont[MAX_QPATH];
860 Con_Printf("Available font commands:\n");
861 for(i = 0; i < dp_fonts.maxsize; ++i)
862 if (dp_fonts.f[i].title[0])
863 Con_Printf(" loadfont %s gfx/tgafile[...] [custom switches] [sizes...]\n", dp_fonts.f[i].title);
864 Con_Printf("A font can simply be gfx/tgafile, or alternatively you\n"
865 "can specify multiple fonts and faces\n"
866 "Like this: gfx/vera-sans:2,gfx/fallback:1\n"
867 "to load face 2 of the font gfx/vera-sans and use face 1\n"
868 "of gfx/fallback as fallback font.\n"
869 "You can also specify a list of font sizes to load, like this:\n"
870 "loadfont console gfx/conchars,gfx/fallback 8 12 16 24 32\n"
871 "In many cases, 8 12 16 24 32 should be a good choice.\n"
873 " scale x : scale all characters by this amount when rendering (doesnt change line height)\n"
874 " voffset x : offset all chars vertical when rendering, this is multiplied to character height\n"
878 f = FindFont(Cmd_Argv(1), true);
881 Con_Printf("font function not found\n");
886 filelist = "gfx/conchars";
888 filelist = Cmd_Argv(2);
890 memset(f->fallbacks, 0, sizeof(f->fallbacks));
891 memset(f->fallback_faces, 0, sizeof(f->fallback_faces));
893 // first font is handled "normally"
894 c = strchr(filelist, ':');
895 cm = strchr(filelist, ',');
896 if(c && (!cm || c < cm))
897 f->req_face = atoi(c+1);
904 if(!c || (c - filelist) > MAX_QPATH)
905 strlcpy(mainfont, filelist, sizeof(mainfont));
908 memcpy(mainfont, filelist, c - filelist);
909 mainfont[c - filelist] = 0;
912 for(i = 0; i < MAX_FONT_FALLBACKS; ++i)
914 c = strchr(filelist, ',');
920 c = strchr(filelist, ':');
921 cm = strchr(filelist, ',');
922 if(c && (!cm || c < cm))
923 f->fallback_faces[i] = atoi(c+1);
926 f->fallback_faces[i] = 0; // f->req_face; could make it stick to the default-font's face index
929 if(!c || (c-filelist) > MAX_QPATH)
931 strlcpy(f->fallbacks[i], filelist, sizeof(mainfont));
935 memcpy(f->fallbacks[i], filelist, c - filelist);
936 f->fallbacks[i][c - filelist] = 0;
940 // for now: by default load only one size: the default size
942 for(i = 1; i < MAX_FONT_SIZES; ++i)
943 f->req_sizes[i] = -1;
949 for(sizes = 0, i = 3; i < Cmd_Argc(); ++i)
952 if (!strcmp(Cmd_Argv(i), "scale"))
956 scale = atof(Cmd_Argv(i));
959 if (!strcmp(Cmd_Argv(i), "voffset"))
963 voffset = atof(Cmd_Argv(i));
968 continue; // no slot for other sizes
970 // parse one of sizes
971 sz = atof(Cmd_Argv(i));
972 if (sz > 0.001f && sz < 1000.0f) // do not use crap sizes
974 // search for duplicated sizes
976 for (j=0; j<sizes; j++)
977 if (f->req_sizes[j] == sz)
980 continue; // sz already in req_sizes, don't add it again
982 if (sizes == MAX_FONT_SIZES)
984 Con_Printf("Warning: specified more than %i different font sizes, exceding ones are ignored\n", MAX_FONT_SIZES);
988 f->req_sizes[sizes] = sz;
994 LoadFont(true, mainfont, f, scale, voffset);
1002 static void gl_draw_start(void)
1006 drawtexturepool = R_AllocTexturePool();
1009 memset(cachepichash, 0, sizeof(cachepichash));
1013 // load default font textures
1014 for(i = 0; i < dp_fonts.maxsize; ++i)
1015 if (dp_fonts.f[i].title[0])
1016 LoadFont(false, va(vabuf, sizeof(vabuf), "gfx/font_%s", dp_fonts.f[i].title), &dp_fonts.f[i], 1, 0);
1018 // draw the loading screen so people have something to see in the newly opened window
1019 SCR_UpdateLoadingScreen(true, true);
1022 static void gl_draw_shutdown(void)
1026 R_FreeTexturePool(&drawtexturepool);
1029 memset(cachepichash, 0, sizeof(cachepichash));
1032 static void gl_draw_newmap(void)
1037 void GL_Draw_Init (void)
1041 Cvar_RegisterVariable(&r_font_postprocess_blur);
1042 Cvar_RegisterVariable(&r_font_postprocess_outline);
1043 Cvar_RegisterVariable(&r_font_postprocess_shadow_x);
1044 Cvar_RegisterVariable(&r_font_postprocess_shadow_y);
1045 Cvar_RegisterVariable(&r_font_postprocess_shadow_z);
1046 Cvar_RegisterVariable(&r_font_hinting);
1047 Cvar_RegisterVariable(&r_font_antialias);
1048 Cvar_RegisterVariable(&r_textshadow);
1049 Cvar_RegisterVariable(&r_textbrightness);
1050 Cvar_RegisterVariable(&r_textcontrast);
1051 Cvar_RegisterVariable(&r_nearest_2d);
1052 Cvar_RegisterVariable(&r_nearest_conchars);
1054 // allocate fonts storage
1055 fonts_mempool = Mem_AllocPool("FONTS", 0, NULL);
1056 dp_fonts.maxsize = MAX_FONTS;
1057 dp_fonts.f = (dp_font_t *)Mem_Alloc(fonts_mempool, sizeof(dp_font_t) * dp_fonts.maxsize);
1058 memset(dp_fonts.f, 0, sizeof(dp_font_t) * dp_fonts.maxsize);
1060 // assign starting font names
1061 strlcpy(FONT_DEFAULT->title, "default", sizeof(FONT_DEFAULT->title));
1062 strlcpy(FONT_DEFAULT->texpath, "gfx/conchars", sizeof(FONT_DEFAULT->texpath));
1063 strlcpy(FONT_CONSOLE->title, "console", sizeof(FONT_CONSOLE->title));
1064 strlcpy(FONT_SBAR->title, "sbar", sizeof(FONT_SBAR->title));
1065 strlcpy(FONT_NOTIFY->title, "notify", sizeof(FONT_NOTIFY->title));
1066 strlcpy(FONT_CHAT->title, "chat", sizeof(FONT_CHAT->title));
1067 strlcpy(FONT_CENTERPRINT->title, "centerprint", sizeof(FONT_CENTERPRINT->title));
1068 strlcpy(FONT_INFOBAR->title, "infobar", sizeof(FONT_INFOBAR->title));
1069 strlcpy(FONT_MENU->title, "menu", sizeof(FONT_MENU->title));
1070 for(i = 0, j = 0; i < MAX_USERFONTS; ++i)
1071 if(!FONT_USER(i)->title[0])
1072 dpsnprintf(FONT_USER(i)->title, sizeof(FONT_USER(i)->title), "user%d", j++);
1074 Cmd_AddCommand ("loadfont",LoadFont_f, "loadfont function tganame loads a font; example: loadfont console gfx/veramono; loadfont without arguments lists the available functions");
1075 R_RegisterModule("GL_Draw", gl_draw_start, gl_draw_shutdown, gl_draw_newmap, NULL, NULL);
1078 static void _DrawQ_Setup(void) // see R_ResetViewRendering2D
1080 if (r_refdef.draw2dstage == 1)
1082 r_refdef.draw2dstage = 1;
1084 R_ResetViewRendering2D_Common(0, NULL, NULL, vid_conwidth.integer, vid_conheight.integer);
1087 qboolean r_draw2d_force = false;
1088 static void _DrawQ_SetupAndProcessDrawFlag(int flags, cachepic_t *pic, float alpha)
1091 if(!r_draw2d.integer && !r_draw2d_force)
1093 DrawQ_ProcessDrawFlag(flags, (alpha < 1) || (pic && pic->hasalpha));
1095 void DrawQ_ProcessDrawFlag(int flags, qboolean alpha)
1097 if(flags == DRAWFLAG_ADDITIVE)
1099 GL_DepthMask(false);
1100 GL_BlendFunc(alpha ? GL_SRC_ALPHA : GL_ONE, GL_ONE);
1102 else if(flags == DRAWFLAG_MODULATE)
1104 GL_DepthMask(false);
1105 GL_BlendFunc(GL_DST_COLOR, GL_ZERO);
1107 else if(flags == DRAWFLAG_2XMODULATE)
1109 GL_DepthMask(false);
1110 GL_BlendFunc(GL_DST_COLOR, GL_SRC_COLOR);
1112 else if(flags == DRAWFLAG_SCREEN)
1114 GL_DepthMask(false);
1115 GL_BlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ONE);
1119 GL_DepthMask(false);
1120 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1125 GL_BlendFunc(GL_ONE, GL_ZERO);
1129 void DrawQ_Pic(float x, float y, cachepic_t *pic, float width, float height, float red, float green, float blue, float alpha, int flags)
1133 _DrawQ_SetupAndProcessDrawFlag(flags, pic, alpha);
1134 if(!r_draw2d.integer && !r_draw2d_force)
1137 // R_Mesh_ResetTextureState();
1138 floats[12] = 0.0f;floats[13] = 0.0f;
1139 floats[14] = 1.0f;floats[15] = 0.0f;
1140 floats[16] = 1.0f;floats[17] = 1.0f;
1141 floats[18] = 0.0f;floats[19] = 1.0f;
1142 floats[20] = floats[24] = floats[28] = floats[32] = red;
1143 floats[21] = floats[25] = floats[29] = floats[33] = green;
1144 floats[22] = floats[26] = floats[30] = floats[34] = blue;
1145 floats[23] = floats[27] = floats[31] = floats[35] = alpha;
1151 height = pic->height;
1152 R_SetupShader_Generic(Draw_GetPicTexture(pic), NULL, GL_MODULATE, 1, (flags & DRAWFLAGS_BLEND) ? false : true, true, false);
1155 // AK07: lets be texel correct on the corners
1157 float horz_offset = 0.5f / pic->width;
1158 float vert_offset = 0.5f / pic->height;
1160 floats[12] = 0.0f + horz_offset;floats[13] = 0.0f + vert_offset;
1161 floats[14] = 1.0f - horz_offset;floats[15] = 0.0f + vert_offset;
1162 floats[16] = 1.0f - horz_offset;floats[17] = 1.0f - vert_offset;
1163 floats[18] = 0.0f + horz_offset;floats[19] = 1.0f - vert_offset;
1168 R_SetupShader_Generic_NoTexture((flags & DRAWFLAGS_BLEND) ? false : true, true);
1170 floats[2] = floats[5] = floats[8] = floats[11] = 0;
1171 floats[0] = floats[9] = x;
1172 floats[1] = floats[4] = y;
1173 floats[3] = floats[6] = x + width;
1174 floats[7] = floats[10] = y + height;
1176 R_Mesh_PrepareVertices_Generic_Arrays(4, floats, floats + 20, floats + 12);
1177 R_Mesh_Draw(0, 4, 0, 2, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0);
1180 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)
1183 float af = DEG2RAD(-angle); // forward
1184 float ar = DEG2RAD(-angle + 90); // right
1185 float sinaf = sin(af);
1186 float cosaf = cos(af);
1187 float sinar = sin(ar);
1188 float cosar = cos(ar);
1190 _DrawQ_SetupAndProcessDrawFlag(flags, pic, alpha);
1191 if(!r_draw2d.integer && !r_draw2d_force)
1194 // R_Mesh_ResetTextureState();
1200 height = pic->height;
1201 R_SetupShader_Generic(Draw_GetPicTexture(pic), NULL, GL_MODULATE, 1, (flags & DRAWFLAGS_BLEND) ? false : true, true, false);
1204 R_SetupShader_Generic_NoTexture((flags & DRAWFLAGS_BLEND) ? false : true, true);
1206 floats[2] = floats[5] = floats[8] = floats[11] = 0;
1209 floats[0] = x - cosaf*org_x - cosar*org_y;
1210 floats[1] = y - sinaf*org_x - sinar*org_y;
1213 floats[3] = x + cosaf*(width-org_x) - cosar*org_y;
1214 floats[4] = y + sinaf*(width-org_x) - sinar*org_y;
1217 floats[6] = x + cosaf*(width-org_x) + cosar*(height-org_y);
1218 floats[7] = y + sinaf*(width-org_x) + sinar*(height-org_y);
1221 floats[9] = x - cosaf*org_x + cosar*(height-org_y);
1222 floats[10] = y - sinaf*org_x + sinar*(height-org_y);
1224 floats[12] = 0.0f;floats[13] = 0.0f;
1225 floats[14] = 1.0f;floats[15] = 0.0f;
1226 floats[16] = 1.0f;floats[17] = 1.0f;
1227 floats[18] = 0.0f;floats[19] = 1.0f;
1228 floats[20] = floats[24] = floats[28] = floats[32] = red;
1229 floats[21] = floats[25] = floats[29] = floats[33] = green;
1230 floats[22] = floats[26] = floats[30] = floats[34] = blue;
1231 floats[23] = floats[27] = floats[31] = floats[35] = alpha;
1233 R_Mesh_PrepareVertices_Generic_Arrays(4, floats, floats + 20, floats + 12);
1234 R_Mesh_Draw(0, 4, 0, 2, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0);
1237 void DrawQ_Fill(float x, float y, float width, float height, float red, float green, float blue, float alpha, int flags)
1241 _DrawQ_SetupAndProcessDrawFlag(flags, NULL, alpha);
1242 if(!r_draw2d.integer && !r_draw2d_force)
1245 // R_Mesh_ResetTextureState();
1246 R_SetupShader_Generic_NoTexture((flags & DRAWFLAGS_BLEND) ? false : true, true);
1248 floats[2] = floats[5] = floats[8] = floats[11] = 0;
1249 floats[0] = floats[9] = x;
1250 floats[1] = floats[4] = y;
1251 floats[3] = floats[6] = x + width;
1252 floats[7] = floats[10] = y + height;
1253 floats[12] = 0.0f;floats[13] = 0.0f;
1254 floats[14] = 1.0f;floats[15] = 0.0f;
1255 floats[16] = 1.0f;floats[17] = 1.0f;
1256 floats[18] = 0.0f;floats[19] = 1.0f;
1257 floats[20] = floats[24] = floats[28] = floats[32] = red;
1258 floats[21] = floats[25] = floats[29] = floats[33] = green;
1259 floats[22] = floats[26] = floats[30] = floats[34] = blue;
1260 floats[23] = floats[27] = floats[31] = floats[35] = alpha;
1262 R_Mesh_PrepareVertices_Generic_Arrays(4, floats, floats + 20, floats + 12);
1263 R_Mesh_Draw(0, 4, 0, 2, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0);
1266 /// color tag printing
1267 static const vec4_t string_colors[] =
1270 // LordHavoc: why on earth is cyan before magenta in Quake3?
1271 // LordHavoc: note: Doom3 uses white for [0] and [7]
1272 {0.0, 0.0, 0.0, 1.0}, // black
1273 {1.0, 0.0, 0.0, 1.0}, // red
1274 {0.0, 1.0, 0.0, 1.0}, // green
1275 {1.0, 1.0, 0.0, 1.0}, // yellow
1276 {0.0, 0.0, 1.0, 1.0}, // blue
1277 {0.0, 1.0, 1.0, 1.0}, // cyan
1278 {1.0, 0.0, 1.0, 1.0}, // magenta
1279 {1.0, 1.0, 1.0, 1.0}, // white
1280 // [515]'s BX_COLOREDTEXT extension
1281 {1.0, 1.0, 1.0, 0.5}, // half transparent
1282 {0.5, 0.5, 0.5, 1.0} // half brightness
1283 // Black's color table
1284 //{1.0, 1.0, 1.0, 1.0},
1285 //{1.0, 0.0, 0.0, 1.0},
1286 //{0.0, 1.0, 0.0, 1.0},
1287 //{0.0, 0.0, 1.0, 1.0},
1288 //{1.0, 1.0, 0.0, 1.0},
1289 //{0.0, 1.0, 1.0, 1.0},
1290 //{1.0, 0.0, 1.0, 1.0},
1291 //{0.1, 0.1, 0.1, 1.0}
1294 #define STRING_COLORS_COUNT (sizeof(string_colors) / sizeof(vec4_t))
1296 static void DrawQ_GetTextColor(float color[4], int colorindex, float r, float g, float b, float a, qboolean shadow)
1298 float C = r_textcontrast.value;
1299 float B = r_textbrightness.value;
1300 if (colorindex & 0x10000) // that bit means RGB color
1302 color[0] = ((colorindex >> 12) & 0xf) / 15.0;
1303 color[1] = ((colorindex >> 8) & 0xf) / 15.0;
1304 color[2] = ((colorindex >> 4) & 0xf) / 15.0;
1305 color[3] = (colorindex & 0xf) / 15.0;
1308 Vector4Copy(string_colors[colorindex], color);
1309 Vector4Set(color, color[0] * r * C + B, color[1] * g * C + B, color[2] * b * C + B, color[3] * a);
1312 float shadowalpha = (color[0]+color[1]+color[2]) * 0.8;
1313 Vector4Set(color, 0, 0, 0, color[3] * bound(0, shadowalpha, 1));
1317 // NOTE: this function always draws exactly one character if maxwidth <= 0
1318 float DrawQ_TextWidth_UntilWidth_TrackColors_Scale(const char *text, size_t *maxlen, float w, float h, float sw, float sh, int *outcolor, qboolean ignorecolorcodes, const dp_font_t *fnt, float maxwidth)
1320 const char *text_start = text;
1321 int colorindex = STRING_COLOR_DEFAULT;
1324 Uchar ch, mapch, nextch;
1325 Uchar prevch = 0; // used for kerning
1330 ft2_font_map_t *fontmap = NULL;
1331 ft2_font_map_t *map = NULL;
1332 //ft2_font_map_t *prevmap = NULL;
1333 ft2_font_t *ft2 = fnt->ft2;
1335 qboolean snap = true;
1336 qboolean least_one = false;
1337 float dw; // display w
1338 //float dh; // display h
1339 const float *width_of;
1346 // do this in the end
1347 w *= fnt->settings.scale;
1348 h *= fnt->settings.scale;
1350 // find the most fitting size:
1354 map_index = Font_IndexForSize(ft2, h, &w, &h);
1356 map_index = Font_IndexForSize(ft2, h, NULL, NULL);
1357 fontmap = Font_MapForIndex(ft2, map_index);
1366 if (!outcolor || *outcolor == -1)
1367 colorindex = STRING_COLOR_DEFAULT;
1369 colorindex = *outcolor;
1371 // maxwidth /= fnt->scale; // w and h are multiplied by it already
1372 // ftbase_x = snap_to_pixel_x(0);
1377 maxwidth = -maxwidth;
1381 // x = snap_to_pixel_x(x, 0.4); // haha, it's 0 anyway
1384 width_of = fontmap->width_of;
1386 width_of = fnt->width_of;
1389 while (((bytes_left = *maxlen - (text - text_start)) > 0) && *text)
1392 nextch = ch = u8_getnchar(text, &text, bytes_left);
1393 i = text - text_start;
1396 if (ch == ' ' && !fontmap)
1398 if(!least_one || i0) // never skip the first character
1399 if(x + width_of[(int) ' '] * dw > maxwidth)
1402 break; // oops, can't draw this
1404 x += width_of[(int) ' '] * dw;
1407 // i points to the char after ^
1408 if (ch == STRING_COLOR_TAG && !ignorecolorcodes && i < *maxlen)
1410 ch = *text; // colors are ascii, so no u8_ needed
1411 if (ch <= '9' && ch >= '0') // ^[0-9] found
1413 colorindex = ch - '0';
1418 // i points to the char after ^...
1419 // i+3 points to 3 in ^x123
1420 // i+3 == *maxlen would mean that char is missing
1421 else if (ch == STRING_COLOR_RGB_TAG_CHAR && i + 3 < *maxlen ) // ^x found
1423 // building colorindex...
1424 ch = tolower(text[1]);
1425 tempcolorindex = 0x10000; // binary: 1,0000,0000,0000,0000
1426 if (ch <= '9' && ch >= '0') tempcolorindex |= (ch - '0') << 12;
1427 else if (ch >= 'a' && ch <= 'f') tempcolorindex |= (ch - 87) << 12;
1428 else tempcolorindex = 0;
1431 ch = tolower(text[2]);
1432 if (ch <= '9' && ch >= '0') tempcolorindex |= (ch - '0') << 8;
1433 else if (ch >= 'a' && ch <= 'f') tempcolorindex |= (ch - 87) << 8;
1434 else tempcolorindex = 0;
1437 ch = tolower(text[3]);
1438 if (ch <= '9' && ch >= '0') tempcolorindex |= (ch - '0') << 4;
1439 else if (ch >= 'a' && ch <= 'f') tempcolorindex |= (ch - 87) << 4;
1440 else tempcolorindex = 0;
1443 colorindex = tempcolorindex | 0xf;
1444 // ...done! now colorindex has rgba codes (1,rrrr,gggg,bbbb,aaaa)
1452 else if (ch == STRING_COLOR_TAG) // ^^ found, ignore the first ^ and go to print the second
1461 if (!fontmap || (ch <= 0xFF && fontmap->glyphs[ch].image) || (ch >= 0xE000 && ch <= 0xE0FF))
1468 map = ft2_oldstyle_map;
1470 if(!least_one || i0) // never skip the first character
1471 if(x + width_of[ch] * dw > maxwidth)
1474 break; // oops, can't draw this
1476 x += width_of[ch] * dw;
1478 if (!map || map == ft2_oldstyle_map || ch < map->start || ch >= map->start + FONT_CHARS_PER_MAP)
1480 map = FontMap_FindForChar(fontmap, ch);
1483 if (!Font_LoadMapForIndex(ft2, map_index, ch, &map))
1489 mapch = ch - map->start;
1490 if (prevch && Font_GetKerningForMap(ft2, map_index, w, h, prevch, ch, &kx, NULL))
1492 x += map->glyphs[mapch].advance_x * dw;
1501 *outcolor = colorindex;
1506 float DrawQ_Color[4];
1507 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, qboolean ignorecolorcodes, const dp_font_t *fnt)
1509 int shadow, colorindex = STRING_COLOR_DEFAULT;
1511 float x = startx, y, s, t, u, v, thisw;
1512 float *av, *at, *ac;
1514 static float vertex3f[QUADELEMENTS_MAXQUADS*4*3];
1515 static float texcoord2f[QUADELEMENTS_MAXQUADS*4*2];
1516 static float color4f[QUADELEMENTS_MAXQUADS*4*4];
1517 Uchar ch, mapch, nextch;
1518 Uchar prevch = 0; // used for kerning
1521 //ft2_font_map_t *prevmap = NULL; // the previous map
1522 ft2_font_map_t *map = NULL; // the currently used map
1523 ft2_font_map_t *fontmap = NULL; // the font map for the size
1525 const char *text_start = text;
1527 ft2_font_t *ft2 = fnt->ft2;
1528 qboolean snap = true;
1532 const float *width_of;
1535 tw = R_TextureWidth(fnt->tex);
1536 th = R_TextureHeight(fnt->tex);
1544 starty -= (fnt->settings.scale - 1) * h * 0.5 - fnt->settings.voffset*h; // center & offset
1545 w *= fnt->settings.scale;
1546 h *= fnt->settings.scale;
1551 map_index = Font_IndexForSize(ft2, h, &w, &h);
1553 map_index = Font_IndexForSize(ft2, h, NULL, NULL);
1554 fontmap = Font_MapForIndex(ft2, map_index);
1560 // draw the font at its baseline when using freetype
1562 ftbase_y = dh * (4.5/6.0);
1567 _DrawQ_SetupAndProcessDrawFlag(flags, NULL, 0);
1568 if(!r_draw2d.integer && !r_draw2d_force)
1569 return startx + DrawQ_TextWidth_UntilWidth_TrackColors_Scale(text, &maxlen, w, h, sw, sh, NULL, ignorecolorcodes, fnt, 1000000000);
1571 // R_Mesh_ResetTextureState();
1573 R_Mesh_TexBind(0, fnt->tex);
1574 R_SetupShader_Generic(fnt->tex, NULL, GL_MODULATE, 1, (flags & DRAWFLAGS_BLEND) ? false : true, true, false);
1581 //ftbase_x = snap_to_pixel_x(ftbase_x);
1584 startx = snap_to_pixel_x(startx, 0.4);
1585 starty = snap_to_pixel_y(starty, 0.4);
1586 ftbase_y = snap_to_pixel_y(ftbase_y, 0.3);
1589 pix_x = vid.width / vid_conwidth.value;
1590 pix_y = vid.height / vid_conheight.value;
1593 width_of = fontmap->width_of;
1595 width_of = fnt->width_of;
1597 for (shadow = r_textshadow.value != 0 && basealpha > 0;shadow >= 0;shadow--)
1602 if (!outcolor || *outcolor == -1)
1603 colorindex = STRING_COLOR_DEFAULT;
1605 colorindex = *outcolor;
1607 DrawQ_GetTextColor(DrawQ_Color, colorindex, basered, basegreen, baseblue, basealpha, shadow != 0);
1614 x += r_textshadow.value * vid.width / vid_conwidth.value;
1615 y += r_textshadow.value * vid.height / vid_conheight.value;
1618 while (((bytes_left = maxlen - (text - text_start)) > 0) && *text)
1620 nextch = ch = u8_getnchar(text, &text, bytes_left);
1621 i = text - text_start;
1624 if (ch == ' ' && !fontmap)
1626 x += width_of[(int) ' '] * dw;
1629 if (ch == STRING_COLOR_TAG && !ignorecolorcodes && i < maxlen)
1631 ch = *text; // colors are ascii, so no u8_ needed
1632 if (ch <= '9' && ch >= '0') // ^[0-9] found
1634 colorindex = ch - '0';
1635 DrawQ_GetTextColor(DrawQ_Color, colorindex, basered, basegreen, baseblue, basealpha, shadow != 0);
1640 else if (ch == STRING_COLOR_RGB_TAG_CHAR && i+3 < maxlen ) // ^x found
1642 // building colorindex...
1643 ch = tolower(text[1]);
1644 tempcolorindex = 0x10000; // binary: 1,0000,0000,0000,0000
1645 if (ch <= '9' && ch >= '0') tempcolorindex |= (ch - '0') << 12;
1646 else if (ch >= 'a' && ch <= 'f') tempcolorindex |= (ch - 87) << 12;
1647 else tempcolorindex = 0;
1650 ch = tolower(text[2]);
1651 if (ch <= '9' && ch >= '0') tempcolorindex |= (ch - '0') << 8;
1652 else if (ch >= 'a' && ch <= 'f') tempcolorindex |= (ch - 87) << 8;
1653 else tempcolorindex = 0;
1656 ch = tolower(text[3]);
1657 if (ch <= '9' && ch >= '0') tempcolorindex |= (ch - '0') << 4;
1658 else if (ch >= 'a' && ch <= 'f') tempcolorindex |= (ch - 87) << 4;
1659 else tempcolorindex = 0;
1662 colorindex = tempcolorindex | 0xf;
1663 // ...done! now colorindex has rgba codes (1,rrrr,gggg,bbbb,aaaa)
1664 //Con_Printf("^1colorindex:^7 %x\n", colorindex);
1665 DrawQ_GetTextColor(DrawQ_Color, colorindex, basered, basegreen, baseblue, basealpha, shadow != 0);
1673 else if (ch == STRING_COLOR_TAG)
1682 // using a value of -1 for the oldstyle map because NULL means uninitialized...
1683 // this way we don't need to rebind fnt->tex for every old-style character
1684 // E000..E0FF: emulate old-font characters (to still have smileys and such available)
1687 x += 1.0/pix_x * r_textshadow.value;
1688 y += 1.0/pix_y * r_textshadow.value;
1690 if (!fontmap || (ch <= 0xFF && fontmap->glyphs[ch].image) || (ch >= 0xE000 && ch <= 0xE0FF))
1698 if (map != ft2_oldstyle_map)
1702 // switching from freetype to non-freetype rendering
1703 R_Mesh_PrepareVertices_Generic_Arrays(batchcount * 4, vertex3f, color4f, texcoord2f);
1704 R_Mesh_Draw(0, batchcount * 4, 0, batchcount * 2, quadelement3i, NULL, 0, quadelement3s, NULL, 0);
1710 R_SetupShader_Generic(fnt->tex, NULL, GL_MODULATE, 1, (flags & DRAWFLAGS_BLEND) ? false : true, true, false);
1711 map = ft2_oldstyle_map;
1715 //num = (unsigned char) text[i];
1716 //thisw = fnt->width_of[num];
1717 thisw = fnt->width_of[ch];
1718 // FIXME make these smaller to just include the occupied part of the character for slightly faster rendering
1719 if (r_nearest_conchars.integer)
1721 s = (ch & 15)*0.0625f;
1722 t = (ch >> 4)*0.0625f;
1723 u = 0.0625f * thisw;
1728 s = (ch & 15)*0.0625f + (0.5f / tw);
1729 t = (ch >> 4)*0.0625f + (0.5f / th);
1730 u = 0.0625f * thisw - (1.0f / tw);
1731 v = 0.0625f - (1.0f / th);
1733 ac[ 0] = DrawQ_Color[0];ac[ 1] = DrawQ_Color[1];ac[ 2] = DrawQ_Color[2];ac[ 3] = DrawQ_Color[3];
1734 ac[ 4] = DrawQ_Color[0];ac[ 5] = DrawQ_Color[1];ac[ 6] = DrawQ_Color[2];ac[ 7] = DrawQ_Color[3];
1735 ac[ 8] = DrawQ_Color[0];ac[ 9] = DrawQ_Color[1];ac[10] = DrawQ_Color[2];ac[11] = DrawQ_Color[3];
1736 ac[12] = DrawQ_Color[0];ac[13] = DrawQ_Color[1];ac[14] = DrawQ_Color[2];ac[15] = DrawQ_Color[3];
1737 at[ 0] = s ; at[ 1] = t ;
1738 at[ 2] = s+u ; at[ 3] = t ;
1739 at[ 4] = s+u ; at[ 5] = t+v ;
1740 at[ 6] = s ; at[ 7] = t+v ;
1741 av[ 0] = x ; av[ 1] = y ; av[ 2] = 10;
1742 av[ 3] = x+dw*thisw ; av[ 4] = y ; av[ 5] = 10;
1743 av[ 6] = x+dw*thisw ; av[ 7] = y+dh ; av[ 8] = 10;
1744 av[ 9] = x ; av[10] = y+dh ; av[11] = 10;
1749 if (batchcount >= QUADELEMENTS_MAXQUADS)
1751 R_Mesh_PrepareVertices_Generic_Arrays(batchcount * 4, vertex3f, color4f, texcoord2f);
1752 R_Mesh_Draw(0, batchcount * 4, 0, batchcount * 2, quadelement3i, NULL, 0, quadelement3s, NULL, 0);
1758 x += width_of[ch] * dw;
1760 if (!map || map == ft2_oldstyle_map || ch < map->start || ch >= map->start + FONT_CHARS_PER_MAP)
1762 // new charmap - need to render
1765 // we need a different character map, render what we currently have:
1766 R_Mesh_PrepareVertices_Generic_Arrays(batchcount * 4, vertex3f, color4f, texcoord2f);
1767 R_Mesh_Draw(0, batchcount * 4, 0, batchcount * 2, quadelement3i, NULL, 0, quadelement3s, NULL, 0);
1774 map = FontMap_FindForChar(fontmap, ch);
1777 if (!Font_LoadMapForIndex(ft2, map_index, ch, &map))
1784 // this shouldn't happen
1789 R_SetupShader_Generic(map->pic->tex, NULL, GL_MODULATE, 1, (flags & DRAWFLAGS_BLEND) ? false : true, true, false);
1792 mapch = ch - map->start;
1793 thisw = map->glyphs[mapch].advance_x;
1797 if (prevch && Font_GetKerningForMap(ft2, map_index, w, h, prevch, ch, &kx, &ky))
1804 ac[ 0] = DrawQ_Color[0]; ac[ 1] = DrawQ_Color[1]; ac[ 2] = DrawQ_Color[2]; ac[ 3] = DrawQ_Color[3];
1805 ac[ 4] = DrawQ_Color[0]; ac[ 5] = DrawQ_Color[1]; ac[ 6] = DrawQ_Color[2]; ac[ 7] = DrawQ_Color[3];
1806 ac[ 8] = DrawQ_Color[0]; ac[ 9] = DrawQ_Color[1]; ac[10] = DrawQ_Color[2]; ac[11] = DrawQ_Color[3];
1807 ac[12] = DrawQ_Color[0]; ac[13] = DrawQ_Color[1]; ac[14] = DrawQ_Color[2]; ac[15] = DrawQ_Color[3];
1808 at[0] = map->glyphs[mapch].txmin; at[1] = map->glyphs[mapch].tymin;
1809 at[2] = map->glyphs[mapch].txmax; at[3] = map->glyphs[mapch].tymin;
1810 at[4] = map->glyphs[mapch].txmax; at[5] = map->glyphs[mapch].tymax;
1811 at[6] = map->glyphs[mapch].txmin; at[7] = map->glyphs[mapch].tymax;
1812 av[ 0] = x + dw * map->glyphs[mapch].vxmin; av[ 1] = y + dh * map->glyphs[mapch].vymin; av[ 2] = 10;
1813 av[ 3] = x + dw * map->glyphs[mapch].vxmax; av[ 4] = y + dh * map->glyphs[mapch].vymin; av[ 5] = 10;
1814 av[ 6] = x + dw * map->glyphs[mapch].vxmax; av[ 7] = y + dh * map->glyphs[mapch].vymax; av[ 8] = 10;
1815 av[ 9] = x + dw * map->glyphs[mapch].vxmin; av[10] = y + dh * map->glyphs[mapch].vymax; av[11] = 10;
1824 if (batchcount >= QUADELEMENTS_MAXQUADS)
1826 R_Mesh_PrepareVertices_Generic_Arrays(batchcount * 4, vertex3f, color4f, texcoord2f);
1827 R_Mesh_Draw(0, batchcount * 4, 0, batchcount * 2, quadelement3i, NULL, 0, quadelement3s, NULL, 0);
1840 x -= 1.0/pix_x * r_textshadow.value;
1841 y -= 1.0/pix_y * r_textshadow.value;
1847 R_Mesh_PrepareVertices_Generic_Arrays(batchcount * 4, vertex3f, color4f, texcoord2f);
1848 R_Mesh_Draw(0, batchcount * 4, 0, batchcount * 2, quadelement3i, NULL, 0, quadelement3s, NULL, 0);
1852 *outcolor = colorindex;
1854 // note: this relies on the proper text (not shadow) being drawn last
1858 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, qboolean ignorecolorcodes, const dp_font_t *fnt)
1860 return DrawQ_String_Scale(startx, starty, text, maxlen, w, h, 1, 1, basered, basegreen, baseblue, basealpha, flags, outcolor, ignorecolorcodes, fnt);
1863 float DrawQ_TextWidth_UntilWidth_TrackColors(const char *text, size_t *maxlen, float w, float h, int *outcolor, qboolean ignorecolorcodes, const dp_font_t *fnt, float maxwidth)
1865 return DrawQ_TextWidth_UntilWidth_TrackColors_Scale(text, maxlen, w, h, 1, 1, outcolor, ignorecolorcodes, fnt, maxwidth);
1868 float DrawQ_TextWidth(const char *text, size_t maxlen, float w, float h, qboolean ignorecolorcodes, const dp_font_t *fnt)
1870 return DrawQ_TextWidth_UntilWidth(text, &maxlen, w, h, ignorecolorcodes, fnt, 1000000000);
1873 float DrawQ_TextWidth_UntilWidth(const char *text, size_t *maxlen, float w, float h, qboolean ignorecolorcodes, const dp_font_t *fnt, float maxWidth)
1875 return DrawQ_TextWidth_UntilWidth_TrackColors(text, maxlen, w, h, NULL, ignorecolorcodes, fnt, maxWidth);
1880 // no ^xrgb management
1881 static int DrawQ_BuildColoredText(char *output2c, size_t maxoutchars, const char *text, int maxreadchars, qboolean ignorecolorcodes, int *outcolor)
1883 int color, numchars = 0;
1884 char *outputend2c = output2c + maxoutchars - 2;
1885 if (!outcolor || *outcolor == -1)
1886 color = STRING_COLOR_DEFAULT;
1890 maxreadchars = 1<<30;
1891 textend = text + maxreadchars;
1892 while (text != textend && *text)
1894 if (*text == STRING_COLOR_TAG && !ignorecolorcodes && text + 1 != textend)
1896 if (text[1] == STRING_COLOR_TAG)
1898 else if (text[1] >= '0' && text[1] <= '9')
1900 color = text[1] - '0';
1905 if (output2c >= outputend2c)
1907 *output2c++ = *text++;
1908 *output2c++ = color;
1911 output2c[0] = output2c[1] = 0;
1918 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)
1922 _DrawQ_SetupAndProcessDrawFlag(flags, pic, a1*a2*a3*a4);
1923 if(!r_draw2d.integer && !r_draw2d_force)
1926 // R_Mesh_ResetTextureState();
1932 height = pic->height;
1933 R_SetupShader_Generic(Draw_GetPicTexture(pic), NULL, GL_MODULATE, 1, (flags & (DRAWFLAGS_BLEND | DRAWFLAG_NOGAMMA)) ? false : true, true, false);
1936 R_SetupShader_Generic_NoTexture((flags & (DRAWFLAGS_BLEND | DRAWFLAG_NOGAMMA)) ? false : true, true);
1938 floats[2] = floats[5] = floats[8] = floats[11] = 0;
1939 floats[0] = floats[9] = x;
1940 floats[1] = floats[4] = y;
1941 floats[3] = floats[6] = x + width;
1942 floats[7] = floats[10] = y + height;
1943 floats[12] = s1;floats[13] = t1;
1944 floats[14] = s2;floats[15] = t2;
1945 floats[16] = s4;floats[17] = t4;
1946 floats[18] = s3;floats[19] = t3;
1947 floats[20] = r1;floats[21] = g1;floats[22] = b1;floats[23] = a1;
1948 floats[24] = r2;floats[25] = g2;floats[26] = b2;floats[27] = a2;
1949 floats[28] = r4;floats[29] = g4;floats[30] = b4;floats[31] = a4;
1950 floats[32] = r3;floats[33] = g3;floats[34] = b3;floats[35] = a3;
1952 R_Mesh_PrepareVertices_Generic_Arrays(4, floats, floats + 20, floats + 12);
1953 R_Mesh_Draw(0, 4, 0, 2, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0);
1956 void DrawQ_Mesh (drawqueuemesh_t *mesh, int flags, qboolean hasalpha)
1959 if(!r_draw2d.integer && !r_draw2d_force)
1961 DrawQ_ProcessDrawFlag(flags, hasalpha);
1963 // R_Mesh_ResetTextureState();
1964 R_SetupShader_Generic(mesh->texture, NULL, GL_MODULATE, 1, (flags & DRAWFLAGS_BLEND) ? false : true, true, false);
1966 R_Mesh_PrepareVertices_Generic_Arrays(mesh->num_vertices, mesh->data_vertex3f, mesh->data_color4f, mesh->data_texcoord2f);
1967 R_Mesh_Draw(0, mesh->num_vertices, 0, mesh->num_triangles, mesh->data_element3i, NULL, 0, mesh->data_element3s, NULL, 0);
1970 void DrawQ_LineLoop (drawqueuemesh_t *mesh, int flags)
1972 _DrawQ_SetupAndProcessDrawFlag(flags, NULL, 1);
1973 if(!r_draw2d.integer && !r_draw2d_force)
1977 switch(vid.renderpath)
1979 case RENDERPATH_GL11:
1980 case RENDERPATH_GL13:
1981 case RENDERPATH_GL20:
1986 qglBegin(GL_LINE_LOOP);
1987 for (num = 0;num < mesh->num_vertices;num++)
1989 if (mesh->data_color4f)
1990 GL_Color(mesh->data_color4f[num*4+0], mesh->data_color4f[num*4+1], mesh->data_color4f[num*4+2], mesh->data_color4f[num*4+3]);
1991 qglVertex2f(mesh->data_vertex3f[num*3+0], mesh->data_vertex3f[num*3+1]);
1998 case RENDERPATH_D3D9:
1999 //Con_DPrintf("FIXME D3D9 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__);
2001 case RENDERPATH_D3D10:
2002 Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__);
2004 case RENDERPATH_D3D11:
2005 Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__);
2007 case RENDERPATH_SOFT:
2008 //Con_DPrintf("FIXME SOFT %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__);
2010 case RENDERPATH_GLES1:
2011 case RENDERPATH_GLES2:
2012 //Con_DPrintf("FIXME GLES2 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__);
2017 //[515]: this is old, delete
2018 void DrawQ_Line (float width, float x1, float y1, float x2, float y2, float r, float g, float b, float alpha, int flags)
2020 _DrawQ_SetupAndProcessDrawFlag(flags, NULL, alpha);
2021 if(!r_draw2d.integer && !r_draw2d_force)
2024 R_SetupShader_Generic_NoTexture((flags & DRAWFLAGS_BLEND) ? false : true, true);
2026 switch(vid.renderpath)
2028 case RENDERPATH_GL11:
2029 case RENDERPATH_GL13:
2030 case RENDERPATH_GL20:
2034 //qglLineWidth(width);CHECKGLERROR
2036 GL_Color(r,g,b,alpha);
2039 qglVertex2f(x1, y1);
2040 qglVertex2f(x2, y2);
2045 case RENDERPATH_D3D9:
2046 //Con_DPrintf("FIXME D3D9 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__);
2048 case RENDERPATH_D3D10:
2049 Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__);
2051 case RENDERPATH_D3D11:
2052 Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__);
2054 case RENDERPATH_SOFT:
2055 //Con_DPrintf("FIXME SOFT %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__);
2057 case RENDERPATH_GLES1:
2058 case RENDERPATH_GLES2:
2059 //Con_DPrintf("FIXME GLES2 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__);
2064 void DrawQ_Lines (float width, int numlines, int flags, qboolean hasalpha)
2066 _DrawQ_SetupAndProcessDrawFlag(flags, NULL, hasalpha ? 0.5f : 1.0f);
2068 if(!r_draw2d.integer && !r_draw2d_force)
2071 switch(vid.renderpath)
2073 case RENDERPATH_GL11:
2074 case RENDERPATH_GL13:
2075 case RENDERPATH_GL20:
2078 R_SetupShader_Generic_NoTexture((flags & DRAWFLAGS_BLEND) ? false : true, true);
2080 //qglLineWidth(width);CHECKGLERROR
2083 qglDrawArrays(GL_LINES, 0, numlines*2);
2086 case RENDERPATH_D3D9:
2087 //Con_DPrintf("FIXME D3D9 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__);
2089 case RENDERPATH_D3D10:
2090 Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__);
2092 case RENDERPATH_D3D11:
2093 Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__);
2095 case RENDERPATH_SOFT:
2096 //Con_DPrintf("FIXME SOFT %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__);
2098 case RENDERPATH_GLES1:
2099 case RENDERPATH_GLES2:
2100 //Con_DPrintf("FIXME GLES2 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__);
2105 void DrawQ_SetClipArea(float x, float y, float width, float height)
2110 // We have to convert the con coords into real coords
2111 // OGL uses top to bottom
2112 ix = (int)(0.5 + x * ((float)r_refdef.view.width / vid_conwidth.integer)) + r_refdef.view.x;
2113 iy = (int)(0.5 + y * ((float)r_refdef.view.height / vid_conheight.integer)) + r_refdef.view.y;
2114 iw = (int)(0.5 + width * ((float)r_refdef.view.width / vid_conwidth.integer));
2115 ih = (int)(0.5 + height * ((float)r_refdef.view.height / vid_conheight.integer));
2116 switch(vid.renderpath)
2118 case RENDERPATH_GL11:
2119 case RENDERPATH_GL13:
2120 case RENDERPATH_GL20:
2121 case RENDERPATH_GLES1:
2122 case RENDERPATH_GLES2:
2123 case RENDERPATH_SOFT:
2124 GL_Scissor(ix, vid.height - iy - ih, iw, ih);
2126 case RENDERPATH_D3D9:
2127 GL_Scissor(ix, iy, iw, ih);
2129 case RENDERPATH_D3D10:
2130 Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__);
2132 case RENDERPATH_D3D11:
2133 Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__);
2137 GL_ScissorTest(true);
2140 void DrawQ_ResetClipArea(void)
2143 GL_ScissorTest(false);
2146 void DrawQ_Finish(void)
2148 r_refdef.draw2dstage = 0;
2151 void DrawQ_RecalcView(void)
2153 if(r_refdef.draw2dstage)
2154 r_refdef.draw2dstage = -1; // next draw call will set viewport etc. again