engine: reject mbf21 and shit24 wads. there is no way to know if it is safe to ignore...
[k8vavoom.git] / source / screen.cpp
blobcbd8f18a16d710d104882d2afd92d654e05e036d
1 //**************************************************************************
2 //**
3 //** ## ## ## ## ## #### #### ### ###
4 //** ## ## ## ## ## ## ## ## ## ## #### ####
5 //** ## ## ## ## ## ## ## ## ## ## ## ## ## ##
6 //** ## ## ######## ## ## ## ## ## ## ## ### ##
7 //** ### ## ## ### ## ## ## ## ## ##
8 //** # ## ## # #### #### ## ##
9 //**
10 //** Copyright (C) 1999-2006 Jānis Legzdiņš
11 //** Copyright (C) 2018-2023 Ketmar Dark
12 //**
13 //** This program is free software: you can redistribute it and/or modify
14 //** it under the terms of the GNU General Public License as published by
15 //** the Free Software Foundation, version 3 of the License ONLY.
16 //**
17 //** This program is distributed in the hope that it will be useful,
18 //** but WITHOUT ANY WARRANTY; without even the implied warranty of
19 //** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 //** GNU General Public License for more details.
21 //**
22 //** You should have received a copy of the GNU General Public License
23 //** along with this program. If not, see <http://www.gnu.org/licenses/>.
24 //**
25 //**************************************************************************
26 #include "gamedefs.h"
27 #include "host.h"
28 #include "drawer.h"
29 #include "text.h"
30 #include "touch.h"
31 #include "widgets/ui.h"
32 #include "automap.h"
33 #include "menu.h"
34 #include "sbar.h"
35 #include "chat.h"
36 #include "psim/p_player.h"
37 #include "server/server.h"
38 #include "client/client.h"
39 #include "filesys/files.h"
40 #include "psim/p_entity.h"
43 extern int screenblocks;
45 extern VCvarB dbg_world_think_vm_time;
46 extern VCvarB dbg_world_think_decal_time;
48 extern double worldThinkTimeVM;
49 extern double worldThinkTimeDecal;
51 static double wipeStartedTime = -1.0;
52 static bool wipeStarted = false;
54 int ScreenWidth = 0;
55 int ScreenHeight = 0;
56 int RealScreenWidth = 0;
57 int RealScreenHeight = 0;
59 int VirtualWidth = 640;
60 int VirtualHeight = 480;
62 static int lastFSMode = -1;
64 float fScaleX;
65 float fScaleY;
67 int usegamma = 0;
69 // Table of RGB values in current gamma corection level
70 static const vuint8 gammatable[5][256] = {
71 {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
72 17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,
73 33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,
74 49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,
75 65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,
76 81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,
77 97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,
78 113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,
79 128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,
80 144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,
81 160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,
82 176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,
83 192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,
84 208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,
85 224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,
86 240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255},
88 {2,4,5,7,8,10,11,12,14,15,16,18,19,20,21,23,24,25,26,27,29,30,31,
89 32,33,34,36,37,38,39,40,41,42,44,45,46,47,48,49,50,51,52,54,55,
90 56,57,58,59,60,61,62,63,64,65,66,67,69,70,71,72,73,74,75,76,77,
91 78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,
92 99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,
93 115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,129,
94 130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,
95 146,147,148,148,149,150,151,152,153,154,155,156,157,158,159,160,
96 161,162,163,163,164,165,166,167,168,169,170,171,172,173,174,175,
97 175,176,177,178,179,180,181,182,183,184,185,186,186,187,188,189,
98 190,191,192,193,194,195,196,196,197,198,199,200,201,202,203,204,
99 205,205,206,207,208,209,210,211,212,213,214,214,215,216,217,218,
100 219,220,221,222,222,223,224,225,226,227,228,229,230,230,231,232,
101 233,234,235,236,237,237,238,239,240,241,242,243,244,245,245,246,
102 247,248,249,250,251,252,252,253,254,255},
104 {4,7,9,11,13,15,17,19,21,22,24,26,27,29,30,32,33,35,36,38,39,40,42,
105 43,45,46,47,48,50,51,52,54,55,56,57,59,60,61,62,63,65,66,67,68,69,
106 70,72,73,74,75,76,77,78,79,80,82,83,84,85,86,87,88,89,90,91,92,93,
107 94,95,96,97,98,100,101,102,103,104,105,106,107,108,109,110,111,112,
108 113,114,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,
109 129,130,131,132,133,133,134,135,136,137,138,139,140,141,142,143,144,
110 144,145,146,147,148,149,150,151,152,153,153,154,155,156,157,158,159,
111 160,160,161,162,163,164,165,166,166,167,168,169,170,171,172,172,173,
112 174,175,176,177,178,178,179,180,181,182,183,183,184,185,186,187,188,
113 188,189,190,191,192,193,193,194,195,196,197,197,198,199,200,201,201,
114 202,203,204,205,206,206,207,208,209,210,210,211,212,213,213,214,215,
115 216,217,217,218,219,220,221,221,222,223,224,224,225,226,227,228,228,
116 229,230,231,231,232,233,234,235,235,236,237,238,238,239,240,241,241,
117 242,243,244,244,245,246,247,247,248,249,250,251,251,252,253,254,254,
118 255},
120 {8,12,16,19,22,24,27,29,31,34,36,38,40,41,43,45,47,49,50,52,53,55,
121 57,58,60,61,63,64,65,67,68,70,71,72,74,75,76,77,79,80,81,82,84,85,
122 86,87,88,90,91,92,93,94,95,96,98,99,100,101,102,103,104,105,106,107,
123 108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,
124 125,126,127,128,129,130,131,132,133,134,135,135,136,137,138,139,140,
125 141,142,143,143,144,145,146,147,148,149,150,150,151,152,153,154,155,
126 155,156,157,158,159,160,160,161,162,163,164,165,165,166,167,168,169,
127 169,170,171,172,173,173,174,175,176,176,177,178,179,180,180,181,182,
128 183,183,184,185,186,186,187,188,189,189,190,191,192,192,193,194,195,
129 195,196,197,197,198,199,200,200,201,202,202,203,204,205,205,206,207,
130 207,208,209,210,210,211,212,212,213,214,214,215,216,216,217,218,219,
131 219,220,221,221,222,223,223,224,225,225,226,227,227,228,229,229,230,
132 231,231,232,233,233,234,235,235,236,237,237,238,238,239,240,240,241,
133 242,242,243,244,244,245,246,246,247,247,248,249,249,250,251,251,252,
134 253,253,254,254,255},
136 {16,23,28,32,36,39,42,45,48,50,53,55,57,60,62,64,66,68,69,71,73,75,76,
137 78,80,81,83,84,86,87,89,90,92,93,94,96,97,98,100,101,102,103,105,106,
138 107,108,109,110,112,113,114,115,116,117,118,119,120,121,122,123,124,
139 125,126,128,128,129,130,131,132,133,134,135,136,137,138,139,140,141,
140 142,143,143,144,145,146,147,148,149,150,150,151,152,153,154,155,155,
141 156,157,158,159,159,160,161,162,163,163,164,165,166,166,167,168,169,
142 169,170,171,172,172,173,174,175,175,176,177,177,178,179,180,180,181,
143 182,182,183,184,184,185,186,187,187,188,189,189,190,191,191,192,193,
144 193,194,195,195,196,196,197,198,198,199,200,200,201,202,202,203,203,
145 204,205,205,206,207,207,208,208,209,210,210,211,211,212,213,213,214,
146 214,215,216,216,217,217,218,219,219,220,220,221,221,222,223,223,224,
147 224,225,225,226,227,227,228,228,229,229,230,230,231,232,232,233,233,
148 234,234,235,235,236,236,237,237,238,239,239,240,240,241,241,242,242,
149 243,243,244,244,245,245,246,246,247,247,248,248,249,249,250,250,251,
150 251,252,252,253,254,254,255,255}
154 const vuint8 *getGammaTable (int idx) {
155 if (idx < 0) idx = 0; else if (idx > 4) idx = 4;
156 return gammatable[idx];
159 static bool setresolutionneeded = false;
160 static int setwidth = 0;
161 static int setheight = 0;
162 static float lastScrScale = 0;
164 static VCvarB dbg_disable_world_render("dbg_disable_world_render", false, "Disable world rendering?", CVAR_NoShadow);
166 static VCvarF menu_darkening("menu_darkening", "0.7", "Screen darkening for active menus.", CVAR_Archive|CVAR_NoShadow);
167 static VCvarB draw_pause("draw_pause", true, "Draw \"paused\" text?", CVAR_Archive|CVAR_NoShadow);
169 static VCvarB crosshair_topmost("crosshair_topmost", false, "Render crosshair on the top of everything?", CVAR_Archive|CVAR_NoShadow);
171 VCvarB r_wipe_enabled("r_wipe_enabled", true, "Is screen wipe effect enabled?", CVAR_Archive|CVAR_NoShadow);
172 VCvarI r_wipe_type("r_wipe_type", "0", "Wipe type?", CVAR_Archive|CVAR_NoShadow);
173 VCvarF r_wipe_duration("r_wipe_duration", "1", "Wipe duration, in seconds?", CVAR_Archive|CVAR_NoShadow);
175 static VCvarI ui_max_scale("ui_max_scale", "0", "Maximal UI scale (0 means unlimited).", CVAR_Archive|CVAR_NoShadow);
176 static VCvarI ui_min_scale("ui_min_scale", "0", "Minimal UI scale (0 means unlimited).", CVAR_Archive|CVAR_NoShadow);
178 VCvarF screen_scale("screen_scale", "1", "Screen scaling factor (you can set it to >1 to render screen in lower resolution).", CVAR_Archive|CVAR_NoShadow);
179 static VCvarI screen_width("screen_width", "0", "Custom screen width", CVAR_Archive|CVAR_NoShadow);
180 static VCvarI screen_height("screen_height", "0", "Custom screen height", CVAR_Archive|CVAR_NoShadow);
181 static VCvarI screen_width_internal("screen_width_internal", "0", "Internal (rendering) screen width", CVAR_Rom|CVAR_NoShadow);
182 static VCvarI screen_height_internal("screen_height_internal", "0", "Internal (rendering) screen height", CVAR_Rom|CVAR_NoShadow);
183 VCvarI screen_fsmode("screen_fsmode", "0", "Video mode: windowed(0), fullscreen scaled(1), fullscreen real(2)", CVAR_Archive|CVAR_NoShadow);
184 static VCvarI brightness("brightness", "0", "Brightness.", CVAR_Archive|CVAR_NoShadow);
186 static VCvarI draw_fps("draw_fps", "0", "Draw FPS counter (1:FPS; 2:MSECS)?", CVAR_Archive|CVAR_NoShadow);
187 static VCvarI draw_fps_posx("draw_fps_posx", "0", "FPS counter position (<0:left; 0:center; >0:right)", CVAR_Archive|CVAR_NoShadow);
188 static double fps_start = 0.0;
189 static double ms = 0.0;
190 static int fps_frames = 0;
191 static int show_fps = 0;
192 static double lastTexGCTime = 0.0;
194 VCvarB draw_lag("draw_lag", true, "Draw network lag value?", CVAR_Archive|CVAR_NoShadow);
196 static VCvarI draw_gc_stats("draw_gc_stats", "0", "Draw GC stats (0: none; 1: brief; 2: full)?", CVAR_Archive|CVAR_NoShadow);
198 //static VCvarB draw_cycles("draw_cycles", false, "Draw cycle counter?", CVAR_NoShadow); //NOOP
201 //**************************************************************************
203 // Screenshots
205 //**************************************************************************
206 #ifdef VAVOOM_DISABLE_STB_IMAGE_JPEG
207 # ifdef VAVOOM_USE_LIBJPG
208 # define VV_SWR_HAS_JPEG
209 # endif
210 #else
211 # define VV_SWR_HAS_JPEG
212 #endif
214 #ifdef VV_SWR_HAS_JPEG
215 # define VV_SWR_CVAR_JPG "/jpg"
216 # define VV_SWR_CVAR_FMT "jpg"
217 #else
218 # define VV_SWR_CVAR_JPG ""
219 # define VV_SWR_CVAR_FMT "png"
220 #endif
221 static VCvarS screenshot_type("screenshot_type", VV_SWR_CVAR_FMT, "Screenshot type (png" VV_SWR_CVAR_JPG "/tga/pcx).", CVAR_Archive|CVAR_NoShadow);
223 extern void WriteTGA (VStr FileName, void *data, int width, int height, int bpp, bool bot2top);
224 extern void WritePCX (VStr FileName, void *data, int width, int height, int bpp, bool bot2top);
225 extern void WritePNG (VStr FileName, const void *Data, int Width, int Height, int Bpp, bool Bot2top);
226 #ifdef VV_SWR_HAS_JPEG
227 extern void WriteJPG (VStr FileName, const void *Data, int Width, int Height, int Bpp, bool Bot2top);
228 #endif
231 enum {
232 SS_BAD = -1,
233 SS_PNG = 0,
234 SS_TGA,
235 SS_PCX,
236 SS_JPG,
240 //==========================================================================
242 // getSSTypeByExt
244 //==========================================================================
245 static int getSSTypeByExt (VStr ext) noexcept {
246 while (!ext.isEmpty() && ext[0] == '.') ext.chopLeft(1);
247 if (ext.isEmpty()) {
248 ext = screenshot_type.asStr();
249 while (!ext.isEmpty() && ext[0] == '.') ext.chopLeft(1);
251 if (ext.isEmpty()) {
252 #ifdef VV_SWR_HAS_JPEG
253 return SS_JPG;
254 #else
255 return SS_PNG;
256 #endif
258 if (ext.strEquCI("png")) return SS_PNG;
259 if (ext.strEquCI("tga")) return SS_TGA;
260 if (ext.strEquCI("pcx")) return SS_PCX;
261 #ifdef VV_SWR_HAS_JPEG
262 if (ext.strEquCI("jpg")) return SS_JPG;
263 if (ext.strEquCI("jpeg")) return SS_JPG;
264 #endif
265 return SS_BAD;
269 //==========================================================================
271 // Screenshot command
273 //==========================================================================
274 COMMAND(Screenshot) {
275 int i;
276 int bpp;
277 bool bot2top;
278 void *data;
279 VStr filename;
280 char tmpbuf[128];
282 VStr ssname;
283 if (Args.length() >= 2) {
284 ssname = Args[1].FixFileSlashes();
285 if (ssname.indexOf('/') >= 0) GCon->Log(NAME_Error, "screenshot file name cannot contain a path");
286 while (!ssname.isEmpty() && ssname.endsWith(".")) ssname.chopRight(1);
289 int stype;
291 VStr sst;
292 if (!ssname.isEmpty()) sst = ssname.ExtractFileExtension();
293 stype = getSSTypeByExt(sst);
295 if (stype == SS_BAD) { GCon->Log(NAME_Error, "Unknown screenshot type"); return; }
297 VStr sext;
298 switch (stype) {
299 case SS_PNG: sext = "png"; break;
300 case SS_TGA: sext = "tga"; break;
301 case SS_PCX: sext = "pcx"; break;
302 case SS_JPG: sext = "jpg"; break;
303 default: GCon->Log(NAME_Error, "WTF?!"); return;
306 if (!ssname.isEmpty()) {
307 VStr e = ssname.ExtractFileExtension();
308 if (e == ".") ssname += sext;
309 else if (e.isEmpty()) { ssname += "."; ssname += sext; }
312 // find a file name to save it to
313 VStr BaseDir = FL_GetScreenshotsDir();
314 if (BaseDir.isEmpty()) {
315 GCon->Logf(NAME_Error, "Invalid engine screenshot directory");
316 return;
319 if (ssname.isEmpty()) {
320 for (i = 0; i <= 9999; ++i) {
321 snprintf(tmpbuf, sizeof(tmpbuf), "shot%04d.%s", i, *sext);
322 filename = BaseDir+"/"+tmpbuf;
323 if (!Sys_FileExists(filename)) break; // file doesn't exist
326 if (i == 10000) {
327 GCon->Log(NAME_Error, "Couldn't create a screenshot file");
328 return;
330 } else {
331 filename = BaseDir.appendPath(ssname);
334 // save screenshot file
335 data = Drawer->ReadScreen(&bpp, &bot2top);
336 if (data) {
337 bool report = true;
338 // type is already lowercased
339 if (stype == SS_PCX) WritePCX(filename, data, ScreenWidth, ScreenHeight, bpp, bot2top);
340 else if (stype == SS_TGA) WriteTGA(filename, data, ScreenWidth, ScreenHeight, bpp, bot2top);
341 else if (stype == SS_PNG) WritePNG(filename, data, ScreenWidth, ScreenHeight, bpp, bot2top);
342 #ifdef VV_SWR_HAS_JPEG
343 else if (stype == SS_JPG) WriteJPG(filename, data, ScreenWidth, ScreenHeight, bpp, bot2top);
344 #endif
345 else {
346 report = false;
347 GCon->Log(NAME_Error, "Bad screenshot type");
348 #ifdef VV_SWR_HAS_JPEG
349 GCon->Log(NAME_Error, "Supported formats are pcx, tga, png, jpg");
350 #else
351 GCon->Log(NAME_Error, "Supported formats are pcx, tga, png");
352 #endif
354 if (report) GCon->Logf("Saved screenshot to '%s'", *filename);
355 Z_Free(data);
356 } else {
357 GCon->Log(NAME_Error, "Not enough memory to take a screenshot");
362 //**************************************************************************
364 // Misc drawing stuff
366 //**************************************************************************
368 //==========================================================================
370 // DrawFPS
372 //==========================================================================
373 #ifdef CLIENT
374 static RunningAverageExp vmAverage;
375 static RunningAverageExp decalAverage;
376 static bool stripeRendered;
378 static void DrawDebugTimesStripe () {
379 if (stripeRendered) return;
380 stripeRendered = true;
381 const float stripeAlpha = 0.666f;
382 T_SetFont(ConFont);
383 int sXPos = VirtualWidth;
384 int sYPos = T_FontHeight();
385 GRoot->ToDrawerCoords(sXPos, sYPos);
386 Drawer->ShadeRect(0, 0, sXPos, sYPos, stripeAlpha);
388 #endif
391 static void DrawFPS () {
392 #ifdef CLIENT
393 const bool isClient = (GGameInfo->NetMode == NM_Client && cl && cl->Net);
395 if (worldThinkTimeVM < 0) vmAverage.setValue(0);
396 else if (worldThinkTimeVM >= 0) vmAverage.update(worldThinkTimeVM);
397 // fuck you, gshitcc
398 if (worldThinkTimeDecal < 0) decalAverage.setValue(0);
399 else if (worldThinkTimeDecal >= 0) decalAverage.update(worldThinkTimeDecal);
401 int ypos = 0;
402 stripeRendered = false;
404 // VM/Decal times
406 if (!isClient) {
407 if ((dbg_world_think_vm_time && worldThinkTimeVM >= 0) || (dbg_world_think_decal_time && worldThinkTimeDecal >= 0)) {
408 DrawDebugTimesStripe();
409 T_SetFont(ConFont);
410 T_SetAlign(hleft, vtop);
411 int xpos = VirtualWidth/2;
412 xpos += xpos/2;
413 //ypos = GRoot->GetHeight()-64;
415 if (dbg_world_think_decal_time && dbg_world_think_vm_time) {
416 T_DrawText(xpos, ypos, va("VM:%d | DECALS:%d", (int)(vmAverage.getValue()*1000+0.5), (int)(decalAverage.getValue()*1000+0.5)), CR_DARKBROWN);
417 } else {
418 if (dbg_world_think_decal_time) T_DrawText(xpos, ypos, va("DECALS:%d", (int)(decalAverage.getValue()*1000+0.5)), CR_DARKBROWN);
419 if (dbg_world_think_vm_time) T_DrawText(xpos, ypos, va("VM:%d", (int)(vmAverage.getValue()*1000+0.5)), CR_DARKBROWN);
425 // GC stats
426 if (draw_gc_stats > 0) {
427 const VObject::GCStats &stats = VObject::GetGCStats();
428 DrawDebugTimesStripe();
429 T_SetFont(ConFont);
430 int xpos;
431 T_SetAlign(hcenter, vtop);
432 xpos = VirtualWidth/2;
433 xpos += xpos/3;
434 if (Sys_Time()-stats.lastCollectTime > 1) VObject::ResetGCStatsLastCollected();
436 VStr ss = va("[\034U%3d\034-/\034U%4d\034-/\034U%3d\034-]", stats.lastCollected, stats.alive, (int)(stats.lastCollectDuration*1000+0.5));
437 if ((dbg_world_think_vm_time && worldThinkTimeVM >= 0) || (dbg_world_think_decal_time && worldThinkTimeDecal >= 0)) {
438 if (dbg_world_think_decal_time && dbg_world_think_vm_time) {
439 ss += va(" [VM:\034U%d\034- | DC:\034U%d\034-]", (int)(vmAverage.getValue()*1000+0.5), (int)(decalAverage.getValue()*1000+0.5));
440 } else {
441 if (dbg_world_think_decal_time) ss += va(" [DC:\034U%d\034-]", (int)(decalAverage.getValue()*1000+0.5));
442 if (dbg_world_think_vm_time) ss += va(" [VM:\034U%d\034-]", (int)(vmAverage.getValue()*1000+0.5));
446 T_DrawText(xpos, ypos, ss, CR_DARKBROWN);
448 T_DrawText(xpos, ypos, va("OBJ:[\034U%3d\034-/\034U%3d\034-] ARRAY:[\034U%5d\034-/\034U%5d\034-/\034U%d\034-]; \034U%2d\034- MSEC GC",
449 stats.lastCollected, stats.alive, stats.firstFree, stats.poolSize, stats.poolAllocated, (int)(stats.lastCollectDuration*1000+0.5)), CR_DARKBROWN);
452 //ypos += T_FontHeight();
453 if (xpos > 4) {
454 T_SetAlign(hleft, vtop);
455 T_DrawText(7*T_TextWidth("W"), ypos, va("[T\034U%5d\034-/S\034U%5d\034-|N\034U%4d\034-/F\034U%3d\034-]", dbgEntityTickTotal, dbgEntityTickSimple, dbgEntityTickNoTick, dbgEntityTickTotal-(dbgEntityTickSimple+dbgEntityTickNoTick)), CR_DARKBROWN);
459 // FPS
460 if (draw_fps) {
461 double time = Sys_Time();
462 ++fps_frames;
464 if (time-fps_start > 1.0) {
465 show_fps = (int)(fps_frames/(time-fps_start)+0.5);
466 if (draw_fps == 2) ms = 1000.0/fps_frames/(time-fps_start);
467 fps_start = time;
468 fps_frames = 0;
471 T_SetFont(SmallFont);
472 int xpos;
473 if (stripeRendered) {
474 T_SetFont(ConFont);
475 T_SetAlign(hleft, vtop);
476 xpos = 0;
477 } else if (draw_fps_posx < 0) {
478 T_SetAlign(hleft, vtop);
479 xpos = 4;
480 } else if (draw_fps_posx == 0) {
481 T_SetAlign(hcenter, vtop);
482 xpos = VirtualWidth/2;
483 } else {
484 T_SetAlign(hright, vtop);
485 xpos = VirtualWidth-2;
487 if (stripeRendered) {
488 T_DrawText(xpos, ypos, va("FPS:%02d", show_fps), CR_DARKBROWN);
489 } else {
490 T_DrawText(xpos, ypos, va("%02d FPS", show_fps), CR_DARKBROWN);
493 if (!stripeRendered && draw_fps == 2) {
494 T_SetAlign(hright, vtop);
495 T_DrawText(VirtualWidth-2, ypos, va("%.2f MSEC", ms), CR_DARKBROWN);
498 ypos += 9;
501 // network lag
502 if (isClient && draw_lag) {
503 T_SetFont(ConFont);
504 T_SetAlign(hright, vtop);
505 int xpos = GRoot->GetWidth()-4;
506 int lypos = GRoot->GetHeight()-64;
508 const int nlag = clampval(CL_GetNetLag(), 0, 999);
509 T_DrawText(xpos, lypos, va("(%d CHANS) LAG:%3d", CL_GetNumberOfChannels(), nlag), (Host_IsDangerousTimeout() ? CR_RED : CR_DARKBROWN));
510 //lypos -= T_FontHeight();
512 // draw lag chart
513 const int ChartHeight = 32;
514 lypos -= 4;
516 int sXPos = xpos;
517 int sYPos = lypos;
518 GRoot->ToDrawerCoords(sXPos, sYPos);
519 sXPos -= NETLAG_CHART_ITEMS;
521 Drawer->ShadeRect(sXPos, sYPos, sXPos+NETLAG_CHART_ITEMS, sYPos-ChartHeight, 0.666f);
522 Drawer->StartAutomap(true); // as overlay
523 unsigned pos = (NetLagChartPos+1)%NETLAG_CHART_ITEMS;
524 for (int xx = 0; xx < NETLAG_CHART_ITEMS; ++xx) {
525 //const int hgt = min2(500, NetLagChart[pos])*ChartHeight/500;
526 const int hgt = NetLagChart[pos]*ChartHeight/1000;
527 Drawer->DrawLineAM(sXPos+xx, sYPos, 0xff00cf00u, sXPos+xx, sYPos-hgt-1, 0xff005f00u);
528 pos = (pos+1)%NETLAG_CHART_ITEMS;
530 Drawer->EndAutomap();
532 #endif
536 //**************************************************************************
538 // Resolution change
540 //**************************************************************************
542 //==========================================================================
544 // ChangeResolution
546 //==========================================================================
547 static void ChangeResolution (int InWidth, int InHeight) {
548 int width = InWidth;
549 int height = InHeight;
550 //bool win = false;
551 //if (screen_fsmode > 0) win = true;
553 if (Drawer->RendLev) Drawer->RendLev->UncacheLevel();
554 Drawer->DeinitResolution();
556 // changing resolution
557 if (!Drawer->SetResolution(width, height, screen_fsmode)) {
558 GCon->Logf("Failed to set resolution %dx%d", width, height);
559 if (RealScreenWidth && lastFSMode >= 0) {
560 if (!Drawer->SetResolution(RealScreenWidth, RealScreenHeight, lastFSMode)) {
561 Sys_Error("ChangeResolution: failed to restore resolution");
562 } else {
563 GCon->Log("Restoring previous resolution");
565 } else {
566 if (!Drawer->SetResolution(0, 0, 0)) {
567 Sys_Error("ChangeResolution: Failed to set default resolution");
568 } else {
569 GCon->Log("Setting default resolution");
570 lastFSMode = 0;
573 } else {
574 lastFSMode = screen_fsmode;
576 //GCon->Logf("%dx%d.", ScreenWidth, ScreenHeight);
578 screen_width = RealScreenWidth;
579 screen_height = RealScreenHeight;
581 screen_width_internal = ScreenWidth;
582 screen_height_internal = ScreenHeight;
584 lastScrScale = max2(1.0f, screen_scale.asFloat());
586 VirtualWidth = ScreenWidth;
587 VirtualHeight = ScreenHeight;
588 // calc scale
589 int scale = 1;
590 //GCon->Logf(NAME_Debug, "000: %dx%d", VirtualWidth, VirtualHeight);
591 while (VirtualWidth/scale >= 640 && VirtualHeight/scale >= 480) ++scale;
592 if (scale > 1) --scale;
593 if (ui_max_scale.asInt() > 0 && scale > ui_max_scale.asInt()) scale = ui_max_scale.asInt();
594 if (ui_min_scale.asInt() > 0 && scale < ui_min_scale.asInt()) scale = ui_min_scale.asInt();
595 VirtualWidth /= scale;
596 VirtualHeight /= scale;
598 fScaleX = (float)ScreenWidth/(float)VirtualWidth;
599 fScaleY = (float)ScreenHeight/(float)VirtualHeight;
601 if (GRoot) GRoot->RefreshScale();
603 // don't forget to call `GRoot->RefreshScale()`!
604 //GCon->Logf("***SCALE0: %g, %g; scr:%dx%d; vscr:%dx%d", fScaleX, fScaleY, ScreenWidth, ScreenHeight, VirtualWidth, VirtualHeight);
605 // level precaching will be called by the caller
609 //==========================================================================
611 // CheckResolutionChange
613 //==========================================================================
614 static void CheckResolutionChange () {
615 bool res_changed = false;
617 if (brightness != usegamma) {
618 usegamma = brightness;
619 if (usegamma < 0) {
620 usegamma = 0;
621 brightness = usegamma;
623 if (usegamma > 4) {
624 usegamma = 4;
625 brightness = usegamma;
629 if (setresolutionneeded) {
630 ChangeResolution(setwidth, setheight);
631 setresolutionneeded = false;
632 res_changed = true;
633 } else if (!screen_width || screen_width != RealScreenWidth || screen_height != RealScreenHeight || lastScrScale != max2(1.0f, screen_scale.asFloat())) {
634 ChangeResolution(screen_width, screen_height);
635 res_changed = true;
638 if (res_changed) {
639 Drawer->InitResolution();
640 //R_OSDMsgReset(OSD_MapLoading);
641 if (Drawer->RendLev) Drawer->RendLev->PrecacheLevel();
642 if (GRoot) GRoot->RefreshScale();
643 // post "resolution changed" event
644 event_t ev;
645 ev.clear();
646 ev.type = ev_broadcast;
647 ev.data1 = ev_resolution;
648 VObject::PostEvent(ev);
649 // recalculate view size and other data
650 //R_SetViewSize(screenblocks);
651 if (Drawer->RendLev) R_ForceViewSizeUpdate();
652 //GCon->Logf(NAME_Debug, "RES: real=(%dx%d); scr=(%dx%d)", RealScreenWidth, RealScreenHeight, ScreenWidth, ScreenHeight);
657 //==========================================================================
659 // SetResolution_f
661 //==========================================================================
662 COMMAND(SetResolution) {
663 if (Args.length() == 3) {
664 int w = VStr::atoi(*Args[1]);
665 int h = VStr::atoi(*Args[2]);
666 if (w >= 320 && h >= 200 && w <= 8192 && h <= 8192) {
667 setwidth = w;
668 setheight = h;
669 setresolutionneeded = true;
670 } else {
671 GCon->Logf(NAME_Error, "SetResolution: invalid resolution (%sx%s)", *Args[1], *Args[2]);
673 } else {
674 GCon->Log("SetResolution <width> <height> -- change resolution");
679 //==========================================================================
681 // COMMAND vid_restart
683 //==========================================================================
684 COMMAND(vid_restart) {
685 setwidth = RealScreenWidth;
686 setheight = RealScreenHeight;
687 setresolutionneeded = true;
691 //**************************************************************************
693 // General (public) stuff
695 //**************************************************************************
697 //==========================================================================
699 // SCR_Init
701 //==========================================================================
702 void SCR_Init () {
706 //==========================================================================
708 // SCR_SignalWipeStart
710 //==========================================================================
711 void SCR_SignalWipeStart () {
712 if (!r_wipe_enabled || !GGameInfo || !GGameInfo->IsWipeAllowed()) {
713 clWipeTimer = -1.0f;
714 } else {
715 clWipeTimer = 0.0f;
717 wipeStartedTime = -1.0;
718 wipeStarted = false;
722 //==========================================================================
724 // SCR_Update
726 //==========================================================================
727 void SCR_Update (bool fullUpdate) {
728 CheckResolutionChange();
730 if (Drawer) Drawer->IncUpdateFrame();
732 // disable tty logs for network games
733 if (GGameInfo->NetMode >= NM_DedicatedServer) C_DisableTTYLogs(); else C_EnableTTYLogs();
735 if (!fullUpdate) return;
737 if (clWipeTimer >= 0.0f && wipeStartedTime < 0.0) {
738 //GCon->Logf(NAME_Debug, "PrepareWipe(): clWipeTimer=%g; wipeStartedTime=%g; wipeStarted=%d", clWipeTimer, wipeStartedTime, (int)wipeStarted);
739 Drawer->PrepareWipe();
740 wipeStartedTime = Sys_Time();
743 Drawer->ResetCrosshair();
745 bool updateStarted = false;
746 bool allowClear = true;
747 bool allowWipeStart = true;
748 bool drawOther = true;
750 //GCon->Logf(NAME_Debug, "cl=%p; signon=%d; MO=%p; ingame=%d (iphase:%d); clWipeTimer=%g; TicTime=%d; srft=%d", cl, cls.signon, (cl ? cl->MO : nullptr), CL_IsInGame(), CL_IntermissionPhase(), clWipeTimer, (GLevel ? GLevel->TicTime : -1), serverStartRenderFramesTic);
752 // if the map forced "map end" on the very first ticks...
753 if (GGameInfo->NetMode != NM_Client && cl && cls.signon && cl->MO && CL_IntermissionPhase() && clWipeTimer >= 0.0f && GLevel && GLevel->TicTime < serverStartRenderFramesTic) {
754 //GCon->Logf(NAME_Debug, "*************************");
755 clWipeTimer = -1.0f;
756 Drawer->RenderWipe(-1.0f);
759 // BadApple.wad hack
760 const bool isBadApple = (cl && cl->MO && cl->MO->XLevel->IsBadApple());
762 // do buffered drawing
763 if (cl && cls.signon && cl->MO && /*!GClGame->InIntermission()*/CL_IsInGame()) {
764 if (GGameInfo->NetMode == NM_Client && !cl->Level) {
765 allowClear = false;
766 allowWipeStart = false;
767 drawOther = false;
768 } else if (!GLevel || GLevel->TicTime >= serverStartRenderFramesTic) {
769 //k8: always render level, so automap will be updated in all cases
770 updateStarted = true;
771 Drawer->StartUpdate();
772 if (!CL_GotNetOrigin()) {
773 Drawer->ClearScreen(VDrawer::CLEAR_ALL);
774 } else if (am_always_update || clWipeTimer >= 0.0f || AM_IsOverlay()) {
775 if (dbg_disable_world_render) {
776 Drawer->ClearScreen(VDrawer::CLEAR_ALL);
777 } else {
778 //if (clWipeTimer >= 0.0f) GCon->Logf(NAME_Debug, "R_RenderPlayerView(): clWipeTimer=%g; wipeStartedTime=%g; wipeStarted=%d; Time=%g; TicTime=%d", clWipeTimer, wipeStartedTime, (int)wipeStarted, GLevel->Time, GLevel->TicTime);
779 R_RenderPlayerView();
780 // draw crosshair
781 if (!isBadApple && cl && cl->MO && cl->MO == cl->Camera && GGameInfo->NetMode != NM_TitleMap) {
782 Drawer->WantCrosshair();
783 if (!crosshair_topmost) Drawer->DrawCrosshair();
786 } else {
787 Drawer->ClearScreen(VDrawer::CLEAR_ALL);
789 Drawer->Setup2D(); // restore 2D projection
790 if (GGameInfo->NetMode != NM_TitleMap && !isBadApple) {
791 AM_Drawer();
792 CT_Drawer();
793 SB_Drawer();
794 //if (cl && cl->MO && cl->MO == cl->Camera) AM_DrawAtWidget(GRoot, cl->MO->Origin.x, cl->MO->Origin.y, 2.0f, 0.0f, 1.0f);
796 } else {
797 //GCon->Logf("render: tic=%d; starttic=%d", GLevel->TicTime, serverStartRenderFramesTic);
798 //return; // skip all rendering
799 // k8: nope, we still need to render console
800 allowClear = false;
801 allowWipeStart = false;
803 } else if (GGameInfo->NetMode == NM_Client && cl && cl->Net && !cls.signon && /*!GClGame->InIntermission()*/CL_IsInGame()) {
804 allowClear = false;
805 allowWipeStart = false;
806 drawOther = false;
809 if (!updateStarted) {
810 Drawer->StartUpdate();
811 if (allowClear) Drawer->ClearScreen();
812 Drawer->Setup2D(); // setup 2D projection
813 if (clWipeTimer >= 0.0f && wipeStartedTime > 0.0) Drawer->RenderWipe(-1.0f);
816 if (drawOther) {
817 // draw user interface
818 GRoot->DrawWidgets();
820 // console drawing
821 C_Drawer();
822 // various on-screen statistics
823 DrawFPS();
825 // so it will be always visible
826 Drawer->DrawCrosshair();
828 if (clWipeTimer >= 0.0f && (!GLevel || GLevel->TicTime >= serverStartRenderFramesTic)) {
829 // fix wipe timer
830 const double ctt = Sys_Time();
831 if (allowWipeStart) {
832 //GCon->Logf(NAME_Debug, "wiperender: clWipeTimer=%g; wipeStartedTime=%g; wipeStarted=%d", clWipeTimer, wipeStartedTime, (int)wipeStarted);
833 if (!wipeStarted) { wipeStarted = true; wipeStartedTime = ctt; }
834 clWipeTimer = (float)(ctt-wipeStartedTime);
835 // render wipe
836 if (clWipeTimer >= 0.0f) {
837 if (!Drawer->RenderWipe(clWipeTimer)) {
838 clWipeTimer = -1.0f;
839 if (isBadApple) R_ResetAnimatedSurfaces();
842 } else {
843 Drawer->RenderWipe(-1.0f);
845 } else if (clWipeTimer >= 0.0f) {
846 Drawer->RenderWipe(-1.0f);
849 if ((wipeStarted || (!r_wipe_enabled && updateStarted)) && (!GLevel || GLevel->TicTime >= serverStartRenderFramesTic) && clWipeTimer < 0.0f) {
850 MN_CheckStartupWarning();
852 } else if (GGameInfo->NetMode == NM_Client && cl && cl->Net && !cls.signon && /*!GClGame->InIntermission()*/CL_IsInGame()) {
853 T_SetFont(SmallFont);
854 T_SetAlign(hleft, vtop);
855 const int y = 8+cls.gotmap*8;
856 // slightly off vcenter
857 switch (cls.gotmap) {
858 case 0: T_DrawText(4, y, "getting network data (map)", CR_TAN); break;
859 case 1: T_DrawText(4, y, "getting network data (world)", CR_TAN); break;
860 case 2: T_DrawText(4, y, "getting network data (spawning)", CR_TAN); break;
861 default: T_DrawText(4, y, "getting network data (something)", CR_TAN); break;
865 #ifdef ANDROID
866 // draw touchscreen controls
867 Touch_Draw();
868 #endif
870 // page flip or blit buffer
871 Drawer->Update();
873 // texture GC
874 if (Drawer->CurrentTime-lastTexGCTime >= 3.2) {
875 lastTexGCTime = Sys_Time();
876 VTexture::GCStep(lastTexGCTime);
881 //==========================================================================
883 // DrawSomeIcon
885 //==========================================================================
886 static void DrawSomeIcon (VName icname) {
887 if (icname == NAME_None) return;
888 if (!Drawer || !Drawer->IsInited()) return;
889 int pt = GTextureManager.AddPatch(icname, TEXTYPE_Pic, true);
890 if (pt <= 0) return;
891 picinfo_t info;
892 GTextureManager.GetTextureInfo(pt, &info);
893 if (info.width < 1 || info.height < 1) return;
894 #if 0
895 int xpos = (int)((float)ScreenWidth*260.0f/320.0f);
896 int ypos = (int)((float)ScreenHeight*68.0f/200.0f);
897 Drawer->StartUpdate();
898 R_DrawPic(xpos, ypos, pt);
899 Drawer->Update();
900 #else
901 const int oldVW = VirtualWidth;
902 const int oldVH = VirtualHeight;
903 VirtualWidth = ScreenWidth;
904 VirtualHeight = ScreenHeight;
905 fScaleX = fScaleY = 1.0f;
906 if (GRoot) GRoot->RefreshScale();
907 Drawer->StartUpdate();
908 R_DrawPic(VirtualWidth-info.width, VirtualHeight-info.height, pt, 0.4f);
909 Drawer->Update();
910 VirtualWidth = oldVW;
911 VirtualHeight = oldVH;
912 fScaleX = (float)ScreenWidth/(float)VirtualWidth;
913 fScaleY = (float)ScreenHeight/(float)VirtualHeight;
914 if (GRoot) GRoot->RefreshScale();
915 #endif
919 //==========================================================================
921 // Draw_TeleportIcon
923 //==========================================================================
924 void Draw_TeleportIcon () {
925 DrawSomeIcon(NAME_teleicon);
929 //==========================================================================
931 // Draw_SaveIcon
933 //==========================================================================
934 void Draw_SaveIcon () {
935 DrawSomeIcon(NAME_saveicon);
939 //==========================================================================
941 // Draw_LoadIcon
943 //==========================================================================
944 void Draw_LoadIcon () {
945 DrawSomeIcon(NAME_loadicon);
949 //==========================================================================
951 // SCR_SetVirtualScreen
953 //==========================================================================
954 void SCR_SetVirtualScreen (int Width, int Height) {
955 VirtualWidth = Width;
956 VirtualHeight = Height;
957 fScaleX = (float)ScreenWidth/(float)VirtualWidth;
958 fScaleY = (float)ScreenHeight/(float)VirtualHeight;
959 if (GRoot) GRoot->RefreshScale();
960 //GCon->Logf("***SCALE1: %g, %g; scr:%dx%d; vscr:%dx%d", fScaleX, fScaleY, ScreenWidth, ScreenHeight, VirtualWidth, VirtualHeight);