diff --git a/engine/client/m_options.c b/engine/client/m_options.c index 7aa58b416..c42d93556 100644 --- a/engine/client/m_options.c +++ b/engine/client/m_options.c @@ -792,6 +792,8 @@ const char *presetexec[] = "if cl_deadbodyfilter == 0 then seta cl_deadbodyfilter 1;" //as useful as 2 is, some mods use death frames for crouching etc. "seta gl_simpleitems 1;" "seta cl_fullpitch 1;seta maxpitch \"\";seta minpitch \"\";" //mimic quakespasm where possible. + "seta r_graphics 1;" + "seta r_renderscale 1;" , // fast options "gl_texturemode ln;" @@ -1176,7 +1178,7 @@ void M_Menu_Render_f (void) static const char *logcentervalues[] = {"0", "1", "2", NULL}; menu_t *menu; - extern cvar_t r_novis, cl_item_bobbing, r_waterwarp, r_nolerp, r_noframegrouplerp, r_fastsky, gl_nocolors, gl_lerpimages, r_wateralpha, r_drawviewmodel, gl_cshiftenabled, r_hdr_irisadaptation, scr_logcenterprint; + extern cvar_t r_novis, cl_item_bobbing, r_waterwarp, r_nolerp, r_noframegrouplerp, r_fastsky, gl_nocolors, gl_lerpimages, r_wateralpha, r_drawviewmodel, gl_cshiftenabled, r_hdr_irisadaptation, scr_logcenterprint, r_fxaa, r_graphics; #ifdef GLQUAKE extern cvar_t r_bloom; #endif @@ -1187,6 +1189,7 @@ void M_Menu_Render_f (void) { MB_REDTEXT("Rendering Options", true), MB_TEXT("^Ue080^Ue081^Ue081^Ue081^Ue081^Ue081^Ue081^Ue081^Ue081^Ue081^Ue081^Ue081^Ue081^Ue081^Ue081^Ue081^Ue082", true), + MB_CHECKBOXCVAR("Graphics", r_graphics, 0), //graphics on / off. Its a general dig at modern games not having any real options. MB_CHECKBOXCVAR("Disable VIS", r_novis, 0), MB_CHECKBOXCVAR("Fast Sky", r_fastsky, 0), MB_CHECKBOXCVAR("Disable Model Lerp", r_nolerp, 0), @@ -1200,6 +1203,7 @@ void M_Menu_Render_f (void) MB_CHECKBOXCVAR("Disable Colormap", gl_nocolors, 0), #endif MB_COMBOCVAR("Log Centerprints", scr_logcenterprint, logcenteropts, logcentervalues, "Display centerprints in the console also."), + MB_CHECKBOXCVAR("FXAA", r_fxaa, 0), #ifdef GLQUAKE MB_CHECKBOXCVAR("Bloom", r_bloom, 0), #endif diff --git a/engine/client/renderer.c b/engine/client/renderer.c index 5e42f3693..9d896a0a1 100644 --- a/engine/client/renderer.c +++ b/engine/client/renderer.c @@ -195,8 +195,9 @@ cvar_t r_stainfadetime = CVAR ("r_stainfadetime", "1"); cvar_t r_stains = CVARFC("r_stains", IFMINIMAL("0","0"), CVAR_ARCHIVE, Cvar_Limiter_ZeroToOne_Callback); -cvar_t r_renderscale = CVARD("r_renderscale", "1", "Provides a way to enable subsampling or super-sampling"); -cvar_t r_fxaa = CVARD("r_fxaa", "0", "Runs a post-procesing pass to strip the jaggies."); +cvar_t r_renderscale = CVARD("r_renderscale", "1", CVAR_ARCHIVE, "Provides a way to enable subsampling or super-sampling"); +cvar_t r_fxaa = CVARD("r_fxaa", "0", CVAR_ARCHIVE, "Runs a post-procesing pass to strip the jaggies."); +cvar_t r_graphics = CVARFD("r_graphics", "1", CVAR_ARCHIVE, "Turning this off will result in ascii-style rendering."); cvar_t r_postprocshader = CVARD("r_postprocshader", "", "Specifies a custom shader to use as a post-processing shader"); cvar_t r_wallcolour = CVARAF ("r_wallcolour", "128 128 128", "r_wallcolor", CVAR_RENDERERCALLBACK|CVAR_SHADERSYSTEM);//FIXME: broken @@ -890,6 +891,7 @@ void Renderer_Init(void) Cvar_Register (&r_refractreflect_scale, GRAPHICALNICETIES); Cvar_Register (&r_postprocshader, GRAPHICALNICETIES); Cvar_Register (&r_fxaa, GRAPHICALNICETIES); + Cvar_Register (&r_graphics, GRAPHICALNICETIES); Cvar_Register (&r_renderscale, GRAPHICALNICETIES); Cvar_Register (&r_stereo_separation, GRAPHICALNICETIES); Cvar_Register (&r_stereo_convergence, GRAPHICALNICETIES); diff --git a/engine/common/common.c b/engine/common/common.c index b7ace4dcb..a7df8ebb6 100644 --- a/engine/common/common.c +++ b/engine/common/common.c @@ -2702,6 +2702,24 @@ unsigned int unicode_charofsfrombyteofs(const char *str, unsigned int byteofs, q } return chars; } +void unicode_strpad(char *out, size_t outsize, const char *in, qboolean leftalign, size_t minwidth, size_t maxwidth, qboolean markup) +{ + if(com_parseutf8.ival <= 0 && !markup) + { + Q_snprintfz(out, outsize, "%*.*s", leftalign ? -(int) minwidth : (int) minwidth, (int) maxwidth, in); + } + else + { + size_t l = unicode_byteofsfromcharofs(in, maxwidth, markup); + size_t actual_width = unicode_charcount(in, l, markup); + int pad = (int)((actual_width >= minwidth) ? 0 : (minwidth - actual_width)); + int prec = (int)l; + int lpad = leftalign ? 0 : pad; + int rpad = leftalign ? pad : 0; + Q_snprintfz(out, outsize, "%*s%.*s%*s", lpad, "", prec, in, rpad, ""); + } +} + #if defined(FTE_TARGET_WEB) || defined(__DJGPP__) //targets that don't support towupper/towlower... diff --git a/engine/common/common.h b/engine/common/common.h index 42d854061..3660b04d7 100644 --- a/engine/common/common.h +++ b/engine/common/common.h @@ -422,6 +422,7 @@ unsigned int unicode_decode(int *error, const void *in, char **out, qboolean mar size_t unicode_strtolower(const char *in, char *out, size_t outsize, qboolean markup); size_t unicode_strtoupper(const char *in, char *out, size_t outsize, qboolean markup); unsigned int unicode_charcount(const char *in, size_t buffersize, qboolean markup); +void unicode_strpad(char *out, size_t outsize, const char *in, qboolean leftalign, size_t minwidth, size_t maxwidth, qboolean markup); char *COM_SkipPath (const char *pathname); void QDECL COM_StripExtension (const char *in, char *out, int outlen); diff --git a/engine/common/pr_bgcmd.c b/engine/common/pr_bgcmd.c index bbf61ab73..4182d1289 100644 --- a/engine/common/pr_bgcmd.c +++ b/engine/common/pr_bgcmd.c @@ -5907,7 +5907,7 @@ nolength: if(o < end - 1) { f = &formatbuf[1]; - if(*s != 's' && *s != 'c') + if(*s != 's' && *s != 'c' && *s != 'S') if(flags & PRINTF_ALTERNATE) *f++ = '#'; if(flags & PRINTF_ZEROPAD) *f++ = '0'; if(flags & PRINTF_LEFT) *f++ = '-'; @@ -5932,7 +5932,7 @@ nolength: switch(*s) { - case 'd': case 'i': + case 'd': case 'i': case 'I': if(precision < 0) // not set Q_snprintfz(o, end - o, formatbuf, width, (isfloat ? (int) GETARG_FLOAT(thisarg) : (int) GETARG_INT(thisarg))); else @@ -5970,44 +5970,65 @@ nolength: o += strlen(o); break; case 'c': - //UTF-8-FIXME: figure it out yourself -// if(flags & PRINTF_ALTERNATE) - { + if((flags & PRINTF_ALTERNATE) || !VMUTF8) + { //precision+width are in bytes if(precision < 0) // not set Q_snprintfz(o, end - o, formatbuf, width, (isfloat ? (unsigned int) GETARG_FLOAT(thisarg) : (unsigned int) GETARG_INT(thisarg))); else Q_snprintfz(o, end - o, formatbuf, width, precision, (isfloat ? (unsigned int) GETARG_FLOAT(thisarg) : (unsigned int) GETARG_INT(thisarg))); o += strlen(o); } -/* else - { + else + { //precision+width are in chars unsigned int c = (isfloat ? (unsigned int) GETARG_FLOAT(thisarg) : (unsigned int) GETARG_INT(thisarg)); - char charbuf16[16]; - const char *buf = u8_encodech(c, NULL, charbuf16); - if(!buf) - buf = ""; + char buf[16]; + c = unicode_encode(buf, c, sizeof(buf), VMUTF8MARKUP); + buf[c] = 0; if(precision < 0) // not set precision = end - o - 1; - o += u8_strpad(o, end - o, buf, (flags & PRINTF_LEFT) != 0, width, precision); + unicode_strpad(o, end - o, buf, (flags & PRINTF_LEFT) != 0, width, precision, VMUTF8MARKUP); + o += strlen(o); } -*/ break; - case 's': - //UTF-8-FIXME: figure it out yourself -// if(flags & PRINTF_ALTERNATE) + break; + case 'S': { + char quotedbuf[65536]; //FIXME: no idea how big this actually needs to be. + const char *s = GETARG_STRING(thisarg); + s = COM_QuotedString(s, quotedbuf, sizeof(quotedbuf), false); + if((flags & PRINTF_ALTERNATE) || !VMUTF8) + { //precision+width are in bytes + if(precision < 0) // not set + Q_snprintfz(o, end - o, formatbuf, width, s); + else + Q_snprintfz(o, end - o, formatbuf, width, precision, s); + o += strlen(o); + } + else + { //precision+width are in chars + if(precision < 0) // not set + precision = end - o - 1; + unicode_strpad(o, end - o, s, (flags & PRINTF_LEFT) != 0, width, precision, VMUTF8MARKUP); + o += strlen(o); + } + } + break; + case 's': + if((flags & PRINTF_ALTERNATE) || !VMUTF8) + { //precision+width are in bytes if(precision < 0) // not set Q_snprintfz(o, end - o, formatbuf, width, GETARG_STRING(thisarg)); else Q_snprintfz(o, end - o, formatbuf, width, precision, GETARG_STRING(thisarg)); o += strlen(o); } -/* else - { + else + { //precision+width are in chars if(precision < 0) // not set precision = end - o - 1; - o += u8_strpad(o, end - o, GETARG_STRING(thisarg), (flags & PRINTF_LEFT) != 0, width, precision); + unicode_strpad(o, end - o, GETARG_STRING(thisarg), (flags & PRINTF_LEFT) != 0, width, precision, VMUTF8MARKUP); + o += strlen(o); } -*/ break; + break; default: Con_Printf("PF_sprintf: invalid format string: %s\n", s0); goto finished; diff --git a/engine/gl/gl_rmain.c b/engine/gl/gl_rmain.c index 12d4f90bf..d13926ca4 100644 --- a/engine/gl/gl_rmain.c +++ b/engine/gl/gl_rmain.c @@ -56,7 +56,7 @@ cvar_t gl_dither = CVAR("gl_dither", "1"); extern cvar_t r_stereo_separation; extern cvar_t r_stereo_convergence; extern cvar_t r_stereo_method; -extern cvar_t r_postprocshader, r_fxaa; +extern cvar_t r_postprocshader, r_fxaa, r_graphics; extern cvar_t r_hdr_framebuffer; extern cvar_t gl_screenangle; @@ -1912,21 +1912,32 @@ void GLR_RenderView (void) r_refdef.globalfog.density /= 64; //FIXME } - if (!(r_refdef.flags & RDF_NOWORLDMODEL) && (*r_postprocshader.string)) + if (!(r_refdef.flags & RDF_NOWORLDMODEL)) { - custompostproc = R_RegisterCustom(r_postprocshader.string, SUF_NONE, NULL, NULL); + if (r_fxaa.ival) + r_refdef.flags |= RDF_ANTIALIAS; + if (*r_postprocshader.string) + custompostproc = R_RegisterCustom(r_postprocshader.string, SUF_NONE, NULL, NULL); + else if (!r_graphics.ival) + custompostproc = R_RegisterShader("postproc_ascii", 0, + "{\n" + "program postproc_ascii\n" + "affine\n" + "{\n" + "map $sourcecolour\n" + "nodepthtest\n" + "}\n" + "}\n" + ); if (custompostproc) r_refdef.flags |= RDF_CUSTOMPOSTPROC; + + if (r_hdr_framebuffer.ival && !(vid.flags & VID_FP16)) //primary use of this cvar is to fix q3shader overbrights (so bright lightmaps can oversaturate then drop below 1 by modulation with the lightmap + forcedfb = true; + if (vid_hardwaregamma.ival == 4 && (v_gamma.value != 1 || v_contrast.value != 1 || v_brightness.value != 0)) + r_refdef.flags |= RDF_SCENEGAMMA; } - if (!(r_refdef.flags & RDF_NOWORLDMODEL) && r_fxaa.ival) //overlays will have problems. - r_refdef.flags |= RDF_ANTIALIAS; - - if (!(r_refdef.flags & RDF_NOWORLDMODEL) && r_hdr_framebuffer.ival && !(vid.flags & VID_FP16)) //primary use of this cvar is to fix q3shader overbrights (so bright lightmaps can oversaturate then drop below 1 by modulation with the lightmap - forcedfb = true; - if (vid_hardwaregamma.ival == 4 && (v_gamma.value != 1 || v_contrast.value != 1 || v_brightness.value != 0)) - r_refdef.flags |= RDF_SCENEGAMMA; - //disable stuff if its simply not supported. if (dofbo || !gl_config.arb_shader_objects || !gl_config.ext_framebuffer_objects || !sh_config.texture_non_power_of_two_pic) { diff --git a/engine/gl/r_bishaders.h b/engine/gl/r_bishaders.h index 25816aa66..ab261f575 100644 --- a/engine/gl/r_bishaders.h +++ b/engine/gl/r_bishaders.h @@ -9802,6 +9802,68 @@ YOU SHOULD NOT EDIT THIS FILE BY HAND }, #endif #ifdef GLQUAKE +{QR_OPENGL, 110, "postproc_ascii", +"!!cvardf r_glsl_ascii_mono=0\n" +"!!samps 1\n" +//derived from https://www.shadertoy.com/view/lssGDj + +"#include \"sys/defs.h\"\n" +"varying vec2 texcoord;\n" + +"#ifdef VERTEX_SHADER\n" +"void main()\n" +"{\n" +"texcoord = v_texcoord.xy;\n" +"texcoord.y = 1.0 - texcoord.y;\n" +"gl_Position = ftetransform();\n" +"}\n" +"#endif\n" +"#ifdef FRAGMENT_SHADER\n" +"uniform vec2 e_sourcesize;\n" + +"float character(float n, vec2 p)\n" +"{\n" +"p = floor(p*vec2(4.0, -4.0) + 2.5);\n" +"if (clamp(p.x, 0.0, 4.0) == p.x && clamp(p.y, 0.0, 4.0) == p.y)\n" +"{\n" +"if (int(mod(n/exp2(p.x + 5.0*p.y), 2.0)) == 1) return 1.0;\n" +"} \n" +"return 0.0;\n" +"}\n" + +"void main(void)\n" +"{\n" +"vec2 uv = floor(texcoord.xy * e_sourcesize); //in pixels.\n" +"vec3 col = texture2D(s_t0, (floor(uv/8.0)*8.0+4.0)/e_sourcesize.xy).rgb; \n" + +"float gray = 0.3 * col.r + 0.59 * col.g + 0.11 * col.b;\n" + +"if (r_glsl_ascii_mono != 0)\n" +"gray = gray = pow(gray, 0.7); //quake is just too dark otherwise.\n" +"else\n" +"gray = gray = pow(gray, 0.45); //col*char is FAR too dark otherwise, and much of the colour will come from the col term anyway.\n" + +"float n = 0.0; // space\n" +"if (gray > 0.1) n = 4096.0; // .\n" +"if (gray > 0.2) n = 65600.0; // :\n" +"if (gray > 0.3) n = 332772.0; // *\n" +"if (gray > 0.4) n = 15255086.0; // o \n" +"if (gray > 0.5) n = 23385164.0; // &\n" +"if (gray > 0.6) n = 15252014.0; // 8\n" +"if (gray > 0.7) n = 13199452.0; // @\n" +"if (gray > 0.8) n = 11512810.0; // #\n" + +"vec2 p = mod(uv/4.0, 2.0) - vec2(1.0);\n" +"if (r_glsl_ascii_mono != 0)\n" +"col = vec3(character(n, p));\n" +"else\n" +"col = col*character(n, p); //note that this is kinda cheating.\n" +"gl_FragColor = vec4(col, 1.0);\n" +"}\n" +"#endif\n" +}, +#endif +#ifdef GLQUAKE {QR_OPENGL, 110, "fxaa", "!!samps 1\n" "#include \"sys/defs.h\"\n" diff --git a/engine/qclib/qcc_pr_comp.c b/engine/qclib/qcc_pr_comp.c index 4cb6cc217..b451736eb 100644 --- a/engine/qclib/qcc_pr_comp.c +++ b/engine/qclib/qcc_pr_comp.c @@ -4695,7 +4695,7 @@ nolength: //always 8-bytes wide with 0 padding, always ints. if (isfloat < 0) isfloat = 0; } - else if (*s == 'i') + else if (*s == 'i' || *s == 'I') { //%i defaults to ints, not floats. if(isfloat < 0) isfloat = 0; @@ -4714,7 +4714,7 @@ nolength: switch(*s) { //fixme: should we validate char ranges? - case 'd': case 'i': case 'c': + case 'd': case 'i': case 'I': case 'c': case 'o': case 'u': case 'x': case 'X': case 'p': case 'P': case 'e': case 'E': case 'f': case 'F': case 'g': case 'G': if (isfloat) @@ -4778,6 +4778,7 @@ nolength: QCC_PR_ParseWarning(WARN_FORMATSTRING, "%s: %s requires intvector at arg %i", funcname, formatbuf, thisarg+1); break; case 's': + case 'S': switch(ARGTYPE(thisarg)) { case ev_string: diff --git a/engine/server/pr_cmds.c b/engine/server/pr_cmds.c index 990559bf0..6f4de123b 100644 --- a/engine/server/pr_cmds.c +++ b/engine/server/pr_cmds.c @@ -11010,7 +11010,7 @@ BuiltinList_t BuiltinList[] = { //nq qw h2 ebfs {"getextresponse", PF_Fixme, 0, 0, 0, 624, "string()"}, {"netaddress_resolve",PF_netaddress_resolve,0, 0, 0, 625, "string(string dnsname, optional float defport)"}, {"getgamedirinfo", PF_Fixme, 0, 0, 0, 626, "string(float n, float prop)" STUB}, - {"sprintf", PF_sprintf, 0, 0, 0, 627, "string(string fmt, ...)"}, + {"sprintf", PF_sprintf, 0, 0, 0, 627, D("string(string fmt, ...)", "'prints' to a formatted temp-string. Mostly acts as in C, however %d assumes floats (fteqcc has arg checking. Use it.).\ntype conversions: l=arg is an int, h=arg is a float, and will work as a prefix for any float or int representation.\nfloat representations: d=decimal, e,E=exponent-notation, f,F=floating-point notation, g,G=terse float, c=char code, x,X=hex\nother representations: i=int, s=string, S=quoted and marked-up string, v=vector, p=pointer\nso %ld will accept an int arg, while %hi will expect a float arg.\nentities, fields, and functions will generally need to be printed as ints with %i.")}, {"getsurfacenumtriangles",PF_getsurfacenumtriangles,0,0,0, 628, "float(entity e, float s)"}, {"getsurfacetriangle",PF_getsurfacetriangle,0, 0, 0, 629, "vector(entity e, float s, float n)"}, // {"setkeybind", PF_Fixme, 0, 0, 0, 630, "float(float key, string bind, optional float bindmap)"}, diff --git a/engine/shaders/generatebuiltinsl.c b/engine/shaders/generatebuiltinsl.c index 0ca6c4314..4a66b209f 100644 --- a/engine/shaders/generatebuiltinsl.c +++ b/engine/shaders/generatebuiltinsl.c @@ -36,6 +36,7 @@ char shaders[][64] = "postproc_laea", "postproc_stereographic", "postproc_equirectangular", + "postproc_ascii", "fxaa", "underwaterwarp", "menutint", diff --git a/engine/shaders/glsl/postproc_ascii.glsl b/engine/shaders/glsl/postproc_ascii.glsl new file mode 100644 index 000000000..37bad25a0 --- /dev/null +++ b/engine/shaders/glsl/postproc_ascii.glsl @@ -0,0 +1,59 @@ +!!cvardf r_glsl_ascii_mono=0 +!!samps 1 + +//derived from https://www.shadertoy.com/view/lssGDj + +#include "sys/defs.h" +varying vec2 texcoord; + +#ifdef VERTEX_SHADER +void main() +{ + texcoord = v_texcoord.xy; + texcoord.y = 1.0 - texcoord.y; + gl_Position = ftetransform(); +} +#endif +#ifdef FRAGMENT_SHADER +uniform vec2 e_sourcesize; + +float character(float n, vec2 p) +{ + p = floor(p*vec2(4.0, -4.0) + 2.5); + if (clamp(p.x, 0.0, 4.0) == p.x && clamp(p.y, 0.0, 4.0) == p.y) + { + if (int(mod(n/exp2(p.x + 5.0*p.y), 2.0)) == 1) return 1.0; + } + return 0.0; +} + +void main(void) +{ + vec2 uv = floor(texcoord.xy * e_sourcesize); //in pixels. + vec3 col = texture2D(s_t0, (floor(uv/8.0)*8.0+4.0)/e_sourcesize.xy).rgb; + + float gray = 0.3 * col.r + 0.59 * col.g + 0.11 * col.b; + + if (r_glsl_ascii_mono != 0.0) + gray = gray = pow(gray, 0.7); //quake is just too dark otherwise. + else + gray = gray = pow(gray, 0.45); //col*char is FAR too dark otherwise, and much of the colour will come from the col term anyway. + + float n = 0.0; // space + if (gray > 0.1) n = 4096.0; // . + if (gray > 0.2) n = 65600.0; // : + if (gray > 0.3) n = 332772.0; // * + if (gray > 0.4) n = 15255086.0; // o + if (gray > 0.5) n = 23385164.0; // & + if (gray > 0.6) n = 15252014.0; // 8 + if (gray > 0.7) n = 13199452.0; // @ + if (gray > 0.8) n = 11512810.0; // # + + vec2 p = mod(uv/4.0, 2.0) - vec2(1.0); + if (r_glsl_ascii_mono != 0.0) + col = vec3(character(n, p)); + else + col = col*character(n, p); //note that this is kinda cheating. + gl_FragColor = vec4(col, 1.0); +} +#endif \ No newline at end of file