diff --git a/engine/client/cl_cam.c b/engine/client/cl_cam.c index 4596b16b2..5da616993 100644 --- a/engine/client/cl_cam.c +++ b/engine/client/cl_cam.c @@ -548,6 +548,7 @@ void Cam_Unlock(playerview_t *pv) CL_SendClientCommand(true, "ptrack"); pv->cam_state = CAM_FREECAM; pv->viewentity = (cls.demoplayback)?0:(pv->playernum+1); //free floating + SCR_CenterPrint(pv-cl.playerview, NULL, true); Sbar_Changed(); Skin_FlushPlayers(); @@ -565,6 +566,7 @@ void Cam_Lock(playerview_t *pv, int playernum) pv->cam_spec_track = playernum; pv->cam_state = CAM_PENDING; pv->viewentity = (cls.demoplayback)?0:(pv->playernum+1); //free floating until actually locked + SCR_CenterPrint(pv-cl.playerview, NULL, true); Skin_FlushPlayers(); diff --git a/engine/client/cl_demo.c b/engine/client/cl_demo.c index ff160e26b..186fe2457 100644 --- a/engine/client/cl_demo.c +++ b/engine/client/cl_demo.c @@ -2208,25 +2208,28 @@ void CL_PlayDemoFile(vfsfile_t *f, char *demoname, qboolean issyspath) } #ifdef Q2CLIENT + //just assume if it has a known extension + if (!Q_strcasecmp(demoname + strlen(demoname) - 3, "dm2") || + !Q_strcasecmp(demoname + strlen(demoname) - 6, "dm2.gz")) { - int len; - char type; - int protocol; - //check if its a quake2 demo. - VFS_READ(f, &len, sizeof(len)); - VFS_READ(f, &type, sizeof(type)); - VFS_READ(f, &protocol, sizeof(protocol)); - VFS_SEEK(f, start); - len = LittleLong(len); - protocol = LittleLong(protocol); - if (len > 5 && type == svcq2_serverdata && protocol != 0) - { - CL_PlayDemoStream(f, NULL, demoname, issyspath, DPB_QUAKE2, 0); - return; - } - VFS_SEEK(f, start); + CL_PlayDemoStream(f, NULL, demoname, issyspath, DPB_QUAKE2, 0); + return; } #endif + if (!Q_strcasecmp(demoname + strlen(demoname) - 3, "mvd") || + !Q_strcasecmp(demoname + strlen(demoname) - 6, "mvd.gz")) + { + CL_PlayDemoStream(f, NULL, demoname, issyspath, DPB_MVD, 0); + return; + } + if (!Q_strcasecmp(demoname + strlen(demoname) - 3, "qwd") || + !Q_strcasecmp(demoname + strlen(demoname) - 6, "qwd.gz")) + { + CL_PlayDemoStream(f, NULL, demoname, issyspath, DPB_QUAKEWORLD, 0); + return; + } + + #ifdef NQPROT { @@ -2256,15 +2259,57 @@ void CL_PlayDemoFile(vfsfile_t *f, char *demoname, qboolean issyspath) } #endif - //its not NQ then. must be QuakeWorld, either .qwd or .mvd +#ifdef Q2CLIENT + { + int len; + char type; + int protocol; + //check if its a quake2 demo. + while(VFS_READ(f, &len, sizeof(len)) == sizeof(len)) + { + len = LittleLong(len); + if (len > MAX_OVERALLMSGLEN) + break; + len--; + VFS_READ(f, &type, sizeof(type)); + while (len >= 2 && (type == svcq2_stufftext) || (type == svcq2_print)) + { + while (len > 0) + { + len--; + VFS_READ(f, &type, sizeof(type)); + if (!type) + break; + } + if (len == 0) + continue; + len--; + VFS_READ(f, &type, sizeof(type)); + } + if (len > 4 && type == svcq2_serverdata) + { + VFS_READ(f, &protocol, sizeof(protocol)); + protocol = LittleLong(protocol); + if (protocol >= PROTOCOL_VERSION_Q2_DEMO_MIN && protocol <= PROTOCOL_VERSION_Q2_DEMO_MAX) + { + VFS_SEEK(f, start); + CL_PlayDemoStream(f, NULL, demoname, issyspath, DPB_QUAKE2, 0); + return; + } + break; + } + if (len) + VFS_SEEK(f, VFS_TELL(f)+len); + } + VFS_SEEK(f, start); + } +#endif + + //it doesn't have a assumable extension, isn't q2, nor NQ. then it must be a QuakeWorld demo //could also be .qwz or .dmz or whatever that nq extension is. we don't support either. //mvd and qwd have no identifying markers, other than the extension. - if (!Q_strcasecmp(demoname + strlen(demoname) - 3, "mvd") || - !Q_strcasecmp(demoname + strlen(demoname) - 6, "mvd.gz")) - CL_PlayDemoStream(f, NULL, demoname, issyspath, DPB_MVD, 0); - else - CL_PlayDemoStream(f, NULL, demoname, issyspath, DPB_QUAKEWORLD, 0); + CL_PlayDemoStream(f, NULL, demoname, issyspath, DPB_QUAKEWORLD, 0); } #ifdef WEBCLIENT void CL_PlayDownloadedDemo(struct dl_download *dl) diff --git a/engine/client/cl_ents.c b/engine/client/cl_ents.c index 6947ebcc2..8ecdc0e1d 100644 --- a/engine/client/cl_ents.c +++ b/engine/client/cl_ents.c @@ -2482,7 +2482,8 @@ void CL_DrawDebugPlane(float *normal, float dist, float r, float g, float b, qbo { // int oldents = cl_numvisedicts; // cl_numvisedicts = 0; - BE_DrawWorld(NULL, NULL); + r_refdef.scenevis = NULL; + BE_DrawWorld(NULL); cl_numstris = 0; // cl_numvisedicts = oldents; } @@ -2891,7 +2892,7 @@ void R_AddItemTimer(vec3_t shadoworg, float yaw, float radius, float percent) s = R_RegisterShader("timershader", SUF_NONE, "{\n" "polygonoffset\n" - "program itemtimer\n" + "fte_program itemtimer\n" "{\n" "map $diffuse\n" "blendfunc src_alpha one\n" diff --git a/engine/client/cl_main.c b/engine/client/cl_main.c index cfabf3229..368a283ed 100644 --- a/engine/client/cl_main.c +++ b/engine/client/cl_main.c @@ -4686,6 +4686,7 @@ void Host_DoRunFile(hrf_t *f) // if (f->flags & HRF_DOWNLOADED) man->blockupdate = true; BZ_Free(fdata); + PM_Shutdown(); FS_ChangeGame(man, true, true); } else diff --git a/engine/client/cl_pred.c b/engine/client/cl_pred.c index 673b58eb5..13be3d652 100644 --- a/engine/client/cl_pred.c +++ b/engine/client/cl_pred.c @@ -22,7 +22,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. cvar_t cl_predict_extrapolate = CVARD("cl_predict_extrapolate", "", "If 1, enables prediction based upon partial input frames which can change over time resulting in a swimmy feel but does not need to interpolate. If 0, prediction will stay in the past and thus use only completed frames. Interpolation will then be used to smooth movement.\nThis cvar only applies when video and input frames are independant (ie: cl_netfps is set)."); cvar_t cl_predict_timenudge = CVARD("cl_predict_timenudge", "0", "A debug feature. You should normally leave this as 0. Nudges local player prediction into the future if positive (resulting in extrapolation), or into the past if negative (resulting in laggy interpolation). Value is in seconds, so small decimals are required. This cvar applies even if input frames are tied to video frames."); -cvar_t cl_predict_smooth = CVARD("cl_lerp_smooth", "2", "If 2, will act as 1 when playing demos and otherwise act as if set to 0.\nIf 1, interpolation will run in the past, resulting in really smooth movement at the cost of latency (even on bunchy german ISDNs).\nIf 0, interpolation will be based upon packet arrival times and may judder due to packet loss."); +cvar_t cl_predict_smooth = CVARD("cl_lerp_smooth", "2", "If 2, will act as 1 when playing demos/singleplayer and otherwise act as if set to 0 (ie: deathmatch).\nIf 1, interpolation will run in the past, resulting in really smooth movement at the cost of latency (even on bunchy german ISDNs).\nIf 0, interpolation will be based upon packet arrival times and may judder due to packet loss."); cvar_t cl_nopred = CVAR("cl_nopred","0"); cvar_t cl_pushlatency = CVAR("pushlatency","-999"); @@ -577,8 +577,8 @@ void CL_CalcClientTime(void) //q2 has no drifting. //q3 always drifts. //nq+qw code can drift - //default is to drift in demos but not live (oh noes! added latency!) - if (cls.protocol == CP_QUAKE2 || (cls.protocol != CP_QUAKE3 && (!cl_predict_smooth.ival || (cl_predict_smooth.ival == 2 && !cls.demoplayback)) && cls.demoplayback != DPB_MVD)) + //default is to drift in demos+SP but not live (oh noes! added latency!) + if (cls.protocol == CP_QUAKE2 || (cls.protocol != CP_QUAKE3 && (!cl_predict_smooth.ival || (cl_predict_smooth.ival == 2 && !(cls.demoplayback || cl.allocated_client_slots == 1))) && cls.demoplayback != DPB_MVD)) { //no drift logic float f; f = cl.gametime - cl.oldgametime; diff --git a/engine/client/cl_screen.c b/engine/client/cl_screen.c index 74e3b2a6c..94f6f7488 100644 --- a/engine/client/cl_screen.c +++ b/engine/client/cl_screen.c @@ -380,6 +380,16 @@ for a few moments void SCR_CenterPrint (int pnum, char *str, qboolean skipgamecode) { cprint_t *p; + if (!str) + { + if (cl.intermissionmode == IM_NONE) + { + p = &scr_centerprint[pnum]; + p->flags = 0; + p->time_off = 0; + } + return; + } if (!skipgamecode) { #ifdef CSQC_DAT diff --git a/engine/client/image.c b/engine/client/image.c index 032092835..31ec51be3 100644 --- a/engine/client/image.c +++ b/engine/client/image.c @@ -15,7 +15,7 @@ texid_t GL_FindTextureFallback (const char *identifier, unsigned int flags, void #else cvar_t r_dodgytgafiles = CVARD("r_dodgytgafiles", "0", "Many old glquake engines had a buggy tga loader that ignored bottom-up flags. Naturally people worked around this and the world was plagued with buggy images. Most engines have now fixed the bug, but you can reenable it if you have bugged tga files."); cvar_t r_dodgypcxfiles = CVARD("r_dodgypcxfiles", "0", "When enabled, this will ignore the palette stored within pcx files, for compatibility with quake2."); -cvar_t r_dodgymiptex = CVARD("r_dodgymiptex", "0", "When enabled, this will force regeneration of mipmaps, discarding mips1-4 like glquake did. This may eg solve fullbright issues with some maps, but may reduce distant detail levels."); +cvar_t r_dodgymiptex = CVARD("r_dodgymiptex", "1", "When enabled, this will force regeneration of mipmaps, discarding mips1-4 like glquake did. This may eg solve fullbright issues with some maps, but may reduce distant detail levels."); char *r_defaultimageextensions = #ifdef IMAGEFMT_DDS diff --git a/engine/client/m_download.c b/engine/client/m_download.c index d295acfcf..b5a3d9bd1 100644 --- a/engine/client/m_download.c +++ b/engine/client/m_download.c @@ -2,12 +2,17 @@ //provides both a package manager and downloads menu. #include "quakedef.h" -#if defined(WEBCLIENT) && !defined(NOBUILTINMENUS) -#define DOWNLOADMENU +#ifdef WEBCLIENT + #define PACKAGEMANAGER + #if !defined(NOBUILTINMENUS) && !defined(SERVERONLY) + #define DOWNLOADMENU + #endif #endif -#ifdef DOWNLOADMENU +#ifdef PACKAGEMANAGER #include "fs.h" +vfsfile_t *FS_XZ_DecompressWriteFilter(vfsfile_t *infile); +vfsfile_t *FS_GZ_DecompressWriteFilter(vfsfile_t *outfile, qboolean autoclosefile); //whole load of extra args for the downloads menu (for the downloads menu to handle engine updates). #ifdef VKQUAKE @@ -115,8 +120,8 @@ void CL_StartCinematicOrMenu(void); #define THISENGINE THISARCH "-" DISTRIBUTION "-" ENGINE_RENDERER ENGINE_CLIENT typedef struct package_s { - char fullname[256]; char *name; + char *category; //in path form struct package_s *alternative; //alternative (hidden) forms of this package. @@ -128,6 +133,7 @@ typedef struct package_s { char *arch; char *qhash; + char *title; char *description; char *license; char *author; @@ -167,13 +173,16 @@ static qboolean loadedinstalled; static package_t *availablepackages; static int numpackages; -//FIXME: these are allocated for the life of the exe. changing games should purge the list. +qboolean doautoupdate; //updates will be marked (but not applied without the user's actions) + +//FIXME: these are allocated for the life of the exe. changing basedir should purge the list. static int numdownloadablelists = 0; static struct { char *url; char *prefix; char received; //says if we got a response yet or not + qboolean save; //written into our local file struct dl_download *curdl; //the download context } downloadablelist[32]; int downloadablessequence; //bumped any time any package is purged @@ -260,7 +269,7 @@ void PM_ValidatePackage(package_t *p) } } if (!(p->flags & (DPF_NATIVE|DPF_CACHED))) - Con_Printf("WARNING: %s (%s) no longer exists\n", p->fullname, n); + Con_Printf("WARNING: %s (%s) no longer exists\n", p->name, n); } } else @@ -297,7 +306,7 @@ void PM_ValidatePackage(package_t *p) if (o->flags & DPF_INSTALLED) { if (!strcmp(p->gamedir, o->gamedir) && p->fsroot == o->fsroot) - if (strcmp(p->fullname, o->fullname) || strcmp(p->version, o->version)) + if (strcmp(p->name, o->name) || strcmp(p->version, o->version)) { for (odep = o->deps; odep; odep = odep->next) { @@ -444,7 +453,7 @@ static void PM_InsertPackage(package_t *p) for (link = &availablepackages; *link; link = &(*link)->next) { package_t *prev = *link; - int v = strcmp(prev->fullname, p->fullname); + int v = strcmp(prev->name, p->name); if (v > 0) break; //insert before this one else if (v == 0) @@ -510,9 +519,15 @@ static void PM_AddDep(package_t *p, int deptype, const char *depname) *link = nd; } -static void PM_AddSubList(char *url, const char *prefix) +static void PM_AddSubList(const char *url, const char *prefix, qboolean save) { int i; + if (!*url) + return; + if (strchr(url, '\"') || strchr(url, '\n')) + return; + if (strchr(prefix, '\"') || strchr(prefix, '\n')) + return; for (i = 0; i < numdownloadablelists; i++) { @@ -521,6 +536,8 @@ static void PM_AddSubList(char *url, const char *prefix) } if (i == numdownloadablelists && i < countof(downloadablelist)) { + downloadablelist[i].save = save; + downloadablelist[i].url = BZ_Malloc(strlen(url)+1); strcpy(downloadablelist[i].url, url); @@ -530,6 +547,17 @@ static void PM_AddSubList(char *url, const char *prefix) numdownloadablelists++; } } +static void PM_RemSubList(const char *url) +{ + int i; + for (i = 0; i < numdownloadablelists; i++) + { + if (!strcmp(downloadablelist[i].url, url)) + { + downloadablelist[i].save = false; + } + } +} static void PM_ParsePackageList(vfsfile_t *f, int parseflags, const char *url, const char *prefix) { @@ -597,7 +625,7 @@ static void PM_ParsePackageList(vfsfile_t *f, int parseflags, const char *url, c subprefix = va("%s/%s", prefix, Cmd_Argv(2)); else subprefix = Cmd_Argv(2); - PM_AddSubList(Cmd_Argv(1), subprefix); + PM_AddSubList(Cmd_Argv(1), subprefix, (parseflags & DPF_INSTALLED)?true:false); continue; } if (!strcmp(Cmd_Argv(0), "set")) @@ -628,6 +656,7 @@ static void PM_ParsePackageList(vfsfile_t *f, int parseflags, const char *url, c } if (version > 1) { + char pathname[256]; char *fullname = Cmd_Argv(0); char *file = NULL; char *url = NULL; @@ -635,6 +664,7 @@ static void PM_ParsePackageList(vfsfile_t *f, int parseflags, const char *url, c char *ver = NULL; char *arch = NULL; char *qhash = NULL; + char *title = NULL; char *description = NULL; char *license = NULL; char *author = NULL; @@ -645,7 +675,7 @@ static void PM_ParsePackageList(vfsfile_t *f, int parseflags, const char *url, c int i; if (version > 2) - flags &= DPF_INSTALLED; + flags &= ~DPF_INSTALLED; p = Z_Malloc(sizeof(*p)); for (i = 1; i < argc; i++) @@ -653,6 +683,8 @@ static void PM_ParsePackageList(vfsfile_t *f, int parseflags, const char *url, c char *arg = Cmd_Argv(i); if (!strncmp(arg, "url=", 4)) url = arg+4; + else if (!strncmp(arg, "title=", 8)) + title = arg+8; else if (!strncmp(arg, "gamedir=", 8)) gamedir = arg+8; else if (!strncmp(arg, "ver=", 4)) @@ -676,7 +708,7 @@ static void PM_ParsePackageList(vfsfile_t *f, int parseflags, const char *url, c else if (!strncmp(arg, "file=", 5)) { if (!file) - file = arg+5; + file = arg+5; //for when url isn't explicitly given. assume the url to be the same as the file (relative to defined mirrors) PM_AddDep(p, DEP_FILE, arg+5); } else if (!strncmp(arg, "extract=", 8)) @@ -709,10 +741,15 @@ static void PM_ParsePackageList(vfsfile_t *f, int parseflags, const char *url, c } if (*prefix) - Q_snprintfz(p->fullname, sizeof(p->fullname), "%s/%s", prefix, fullname); + Q_snprintfz(pathname, sizeof(pathname), "%s/%s", prefix, fullname); else - Q_snprintfz(p->fullname, sizeof(p->fullname), "%s", fullname); - p->name = COM_SkipPath(p->fullname); + Q_snprintfz(pathname, sizeof(pathname), "%s", fullname); + p->name = Z_StrDup(COM_SkipPath(pathname)); + *COM_SkipPath(pathname) = 0; + p->category = Z_StrDup(pathname); + + if (!title) + title = p->name; if (!gamedir) gamedir = defaultgamedir; @@ -725,6 +762,7 @@ static void PM_ParsePackageList(vfsfile_t *f, int parseflags, const char *url, c p->priority = priority; p->flags = flags; + p->title = Z_StrDup(title); p->arch = arch?Z_StrDup(arch):NULL; p->qhash = qhash?Z_StrDup(qhash):NULL; p->description = description?Z_StrDup(description):NULL; @@ -757,6 +795,8 @@ static void PM_ParsePackageList(vfsfile_t *f, int parseflags, const char *url, c } else { + char pathname[256]; + const char *fullname = Cmd_Argv(0); if (argc > 5 || argc < 3) { Con_Printf("Package list is bad - %s\n", line); @@ -764,14 +804,15 @@ static void PM_ParsePackageList(vfsfile_t *f, int parseflags, const char *url, c } p = Z_Malloc(sizeof(*p)); - if (*prefix) - Q_strncpyz(p->fullname, va("%s/%s", prefix, Cmd_Argv(0)), sizeof(p->fullname)); + Q_snprintfz(pathname, sizeof(pathname), "%s/%s", prefix, fullname); else - Q_strncpyz(p->fullname, Cmd_Argv(0), sizeof(p->fullname)); - p->name = p->fullname; - while((sl = strchr(p->name, '/'))) - p->name = sl+1; + Q_snprintfz(pathname, sizeof(pathname), "%s", fullname); + p->name = Z_StrDup(COM_SkipPath(pathname)); + p->title = Z_StrDup(p->name); + *COM_SkipPath(pathname) = 0; + p->category = Z_StrDup(pathname); + p->mirror[0] = Z_StrDup(p->name); p->priority = PM_DEFAULTPRIORITY; p->flags = parseflags; @@ -906,8 +947,6 @@ void PM_Shutdown(void) PM_FreePackage(availablepackages); } -#ifndef SERVERONLY -qboolean doautoupdate; static void PM_PreparePackageList(void) { @@ -927,20 +966,13 @@ static void PM_PreparePackageList(void) //finds the newest version -static package_t *PM_FindPackage(char *packagename) +static package_t *PM_FindPackage(const char *packagename) { package_t *p, *r = NULL; - for (p = availablepackages; p; p = p->next) - { - if (!strcmp(p->fullname, packagename)) - { - if (!r || strcmp(r->version, p->version)>0) - r = p; - } - } - if (r) - return r; + //fixme: NAME (>VER) + //fixme: NAME (VER) for (p = availablepackages; p; p = p->next) { @@ -953,18 +985,32 @@ static package_t *PM_FindPackage(char *packagename) return r; } //returns the marked version of a package, if any. -static package_t *PM_MarkedPackage(char *packagename) +static package_t *PM_MarkedPackage(const char *packagename) { package_t *p; for (p = availablepackages; p; p = p->next) { if (p->flags & DPF_MARKED) - if (!strcmp(p->name, packagename) || !strcmp(p->fullname, packagename)) + if (!strcmp(p->name, packagename)) return p; } return NULL; } +//just resets all actions, so that a following apply won't do anything. +static void PM_RevertChanges(void) +{ + package_t *p; + for (p = availablepackages; p; p = p->next) + { + if (p->flags & DPF_INSTALLED) + p->flags |= DPF_MARKED; + else + p->flags &= ~DPF_MARKED; + p->flags &= ~DPF_PURGE; + } +} + //just flags, doesn't delete static void PM_UnmarkPackage(package_t *package) { @@ -988,7 +1034,7 @@ static void PM_UnmarkPackage(package_t *package) { for (dep = o->deps; dep; dep = dep->next) if (dep->dtype == DEP_REQUIRE) - if (!strcmp(dep->name, package->name) || !strcmp(dep->name, package->fullname)) + if (!strcmp(dep->name, package->name)) PM_UnmarkPackage(o); } } @@ -1029,7 +1075,7 @@ static void PM_MarkPackage(package_t *package) if (o->flags & DPF_MARKED) { - if (!strcmp(o->fullname, package->fullname)) + if (!strcmp(o->name, package->name)) { //replaces this package o->flags &= ~DPF_MARKED; replacing = true; @@ -1094,7 +1140,7 @@ static void PM_MarkPackage(package_t *package) { for (dep = o->deps; dep; dep = dep->next) if (dep->dtype == DEP_CONFLICT) - if (!strcmp(dep->name, package->fullname) || !strcmp(dep->name, package->name)) + if (!strcmp(dep->name, package->name)) PM_UnmarkPackage(o); } } @@ -1118,7 +1164,7 @@ static unsigned int PM_MarkUpdates (void) { if (p == o || (o->flags & DPF_HIDDEN)) continue; - if (!strcmp(o->fullname, p->fullname) && !strcmp(o->arch?o->arch:"", p->arch?p->arch:"") && strcmp(o->version, p->version) > 0) + if (!strcmp(o->name, p->name) && !strcmp(o->arch?o->arch:"", p->arch?p->arch:"") && strcmp(o->version, p->version) > 0) { if (!b || strcmp(b->version, o->version) < 0) b = o; @@ -1145,6 +1191,35 @@ static unsigned int PM_MarkUpdates (void) return changecount; } +static void PM_PrintChanges(void) +{ + qboolean changes = 0; + package_t *p; + for (p = availablepackages; p; p=p->next) + { + if (!(p->flags & DPF_MARKED) != !(p->flags & DPF_INSTALLED) || (p->flags & DPF_PURGE)) + { + changes++; + if (p->flags & DPF_MARKED) + { + if (p->flags & DPF_PURGE) + Con_Printf(" reinstall %s\n", p->name); + else + Con_Printf(" install %s\n", p->name); + } + else if ((p->flags & DPF_PURGE) || !(p->qhash && (p->flags & DPF_CACHED))) + Con_Printf(" uninstall %s\n", p->name); + else + Con_Printf(" disable %s\n", p->name); + } + } + if (!changes) + Con_Printf("\n"); + else + Con_Printf("<%i package(s) changed>\n", changes); +} + + static void PM_ListDownloaded(struct dl_download *dl) { int i; @@ -1183,16 +1258,23 @@ static void PM_ListDownloaded(struct dl_download *dl) doautoupdate = true; if (PM_MarkUpdates()) { - if (Key_Dest_Has(kdm_emenu)) +#ifdef DOWNLOADMENU + if (!isDedicated) { - Key_Dest_Remove(kdm_emenu); - m_state = m_none; - } + if (Key_Dest_Has(kdm_emenu)) + { + Key_Dest_Remove(kdm_emenu); + m_state = m_none; + } #ifdef MENU_DAT - if (Key_Dest_Has(kdm_gmenu)) - MP_Toggle(0); + if (Key_Dest_Has(kdm_gmenu)) + MP_Toggle(0); #endif - Cmd_ExecuteString("menu_download\n", RESTRICT_LOCAL); + Cmd_ExecuteString("menu_download\n", RESTRICT_LOCAL); + } + else +#endif + PM_PrintChanges(); } } } @@ -1208,7 +1290,7 @@ static void PM_UpdatePackageList(qboolean autoupdate, int retry) //make sure our sources are okay. if (*fs_downloads_url.string) - PM_AddSubList(fs_downloads_url.string, ""); + PM_AddSubList(fs_downloads_url.string, "", true); doautoupdate |= autoupdate; @@ -1242,7 +1324,12 @@ static void PM_UpdatePackageList(qboolean autoupdate, int retry) doautoupdate = 0; if (PM_MarkUpdates()) { - Cbuf_AddText("menu_download\n", RESTRICT_LOCAL); +#ifdef DOWNLOADMENU + if (!isDedicated) + Cbuf_AddText("menu_download\n", RESTRICT_LOCAL); + else +#endif + PM_PrintChanges(); } } } @@ -1282,6 +1369,7 @@ static void COM_QuotedConcat(const char *cat, char *buf, size_t bufsize) } static void PM_WriteInstalledPackages(void) { + int i; char *s; package_t *p, *e = NULL; struct packagedep_s *dep, *ef = NULL; @@ -1294,13 +1382,23 @@ static void PM_WriteInstalledPackages(void) s = "version 2\n"; VFS_WRITE(f, s, strlen(s)); + + for (i = 0; i < numdownloadablelists; i++) + { + if (downloadablelist[i].save) + { + s = va("sublist \"%s\" \"%s\"\n", downloadablelist[i].url, downloadablelist[i].prefix); + VFS_WRITE(f, s, strlen(s)); + } + } + for (p = availablepackages; p ; p=p->next) { if (p->flags & (DPF_CACHED|DPF_INSTALLED)) { char buf[8192]; buf[0] = 0; - COM_QuotedString(p->fullname, buf, sizeof(buf), false); + COM_QuotedString(va("%s%s", p->category, p->name), buf, sizeof(buf), false); if (p->flags & DPF_INSTALLED) { //v3+ // Q_strncatz(buf, " ", sizeof(buf)); @@ -1311,6 +1409,11 @@ static void PM_WriteInstalledPackages(void) Q_strncatz(buf, " ", sizeof(buf)); COM_QuotedConcat(va("stale=1"), buf, sizeof(buf)); } + if (*p->title && strcmp(p->title, p->name)) + { + Q_strncatz(buf, " ", sizeof(buf)); + COM_QuotedConcat(va("title=%s", p->version), buf, sizeof(buf)); + } if (*p->version) { Q_strncatz(buf, " ", sizeof(buf)); @@ -1408,6 +1511,687 @@ static void PM_WriteInstalledPackages(void) } } +//callback from PM_Download_Got, extracts each file from an archive +static int QDECL PM_ExtractFiles(const char *fname, qofs_t fsize, time_t mtime, void *parm, searchpathfuncs_t *spath) +{ //this is gonna suck. threading would help, but gah. + package_t *p = parm; + flocation_t loc; + if (fname[strlen(fname)-1] == '/') + { //directory. + + } + else if (spath->FindFile(spath, &loc, fname, NULL) && loc.len < 0x80000000u) + { + char *f = malloc(loc.len); + const char *n; + if (f) + { + spath->ReadFile(spath, &loc, f); + if (*p->gamedir) + n = va("%s/%s", p->gamedir, fname); + else + n = fname; + if (FS_WriteFile(n, f, loc.len, p->fsroot)) + p->flags |= DPF_NATIVE|DPF_INSTALLED; + free(f); + + //keep track of the installed files, so we can delete them properly after. + PM_AddDep(p, DEP_FILE, fname); + } + } + return 1; +} + +static void PM_StartADownload(void); +//callback from PM_StartADownload +static void PM_Download_Got(struct dl_download *dl) +{ + qboolean successful = dl->status == DL_FINISHED; + package_t *p; + char *tempname = dl->user_ctx; + + for (p = availablepackages; p ; p=p->next) + { + if (p->download == dl) + break; + } + + if (dl->file) + { + VFS_CLOSE(dl->file); + dl->file = NULL; + } + + if (p) + { + char ext[8]; + char *destname; + struct packagedep_s *dep; + p->download = NULL; + + if (!successful) + { + Con_Printf("Couldn't download %s (from %s)\n", p->name, dl->url); + FS_Remove (tempname, p->fsroot); + Z_Free(tempname); + PM_StartADownload(); + return; + } + + if (p->extract == EXTRACT_ZIP) + { + vfsfile_t *f = FS_OpenVFS(tempname, "rb", p->fsroot); + if (f) + { + searchpathfuncs_t *archive = FSZIP_LoadArchive(f, tempname, NULL); + if (archive) + { + p->flags &= ~(DPF_NATIVE|DPF_CACHED|DPF_CORRUPT|DPF_INSTALLED); + archive->EnumerateFiles(archive, "*", PM_ExtractFiles, p); + archive->EnumerateFiles(archive, "*/*", PM_ExtractFiles, p); + archive->EnumerateFiles(archive, "*/*/*", PM_ExtractFiles, p); + archive->EnumerateFiles(archive, "*/*/*/*", PM_ExtractFiles, p); + archive->EnumerateFiles(archive, "*/*/*/*/*", PM_ExtractFiles, p); + archive->EnumerateFiles(archive, "*/*/*/*/*/*", PM_ExtractFiles, p); + archive->EnumerateFiles(archive, "*/*/*/*/*/*/*", PM_ExtractFiles, p); + archive->EnumerateFiles(archive, "*/*/*/*/*/*/*/*", PM_ExtractFiles, p); + archive->EnumerateFiles(archive, "*/*/*/*/*/*/*/*/*", PM_ExtractFiles, p); + archive->ClosePath(archive); + + PM_WriteInstalledPackages(); + +// if (!stricmp(ext, "pak") || !stricmp(ext, "pk3")) +// FS_ReloadPackFiles(); + } + else + VFS_CLOSE(f); + } + PM_ValidatePackage(p); + + FS_Remove (tempname, p->fsroot); + Z_Free(tempname); + PM_StartADownload(); + return; + } + else + { + for (dep = p->deps; dep; dep = dep->next) + { + unsigned int nfl; + if (dep->dtype != DEP_FILE) + continue; + + COM_FileExtension(dep->name, ext, sizeof(ext)); + if (!stricmp(ext, "pak") || !stricmp(ext, "pk3")) + FS_UnloadPackFiles(); //we reload them after + if ((!stricmp(ext, "dll") || !stricmp(ext, "so")) && !Q_strncmp(dep->name, "fteplug_", 8)) + Cmd_ExecuteString(va("plug_close %s\n", dep->name), RESTRICT_LOCAL); //try to purge plugins so there's no files left open + + nfl = DPF_NATIVE; + if (*p->gamedir) + { + char temp[MAX_OSPATH]; + destname = va("%s/%s", p->gamedir, dep->name); + if (p->qhash && FS_GenCachedPakName(destname, p->qhash, temp, sizeof(temp))) + { + nfl = DPF_CACHED; + destname = va("%s", temp); + } + } + else + destname = dep->name; + nfl |= DPF_INSTALLED | (p->flags & ~(DPF_CACHED|DPF_NATIVE|DPF_CORRUPT)); + FS_CreatePath(destname, p->fsroot); + if (FS_Remove(destname, p->fsroot)) + ; + if (!FS_Rename2(tempname, destname, p->fsroot, p->fsroot)) + { + //error! + Con_Printf("Couldn't rename %s to %s. Removed instead.\n", tempname, destname); + FS_Remove (tempname, p->fsroot); + } + else + { //success! + Con_Printf("Downloaded %s (to %s)\n", p->name, destname); + p->flags = nfl; + PM_WriteInstalledPackages(); + } + + PM_ValidatePackage(p); + + if (!stricmp(ext, "pak") || !stricmp(ext, "pk3")) + FS_ReloadPackFiles(); + if ((!stricmp(ext, "dll") || !stricmp(ext, "so")) && !Q_strncmp(dep->name, "fteplug_", 8)) + Cmd_ExecuteString(va("plug_load %s\n", dep->name), RESTRICT_LOCAL); + + Z_Free(tempname); + PM_StartADownload(); + return; + } + } + Con_Printf("menu_download: %s has no filename info\n", p->name); + } + else + Con_Printf("menu_download: Can't figure out where %s came from (url: %s)\n", dl->localname, dl->url); + + FS_Remove (tempname, FS_GAMEONLY); + Z_Free(tempname); + PM_StartADownload(); +} + +static char *PM_GetTempName(package_t *p) +{ + struct packagedep_s *dep, *fdep; + char *destname, *t, *ts; + //always favour the file so that we can rename safely without needing a copy. + for (dep = p->deps, fdep = NULL; dep; dep = dep->next) + { + if (dep->dtype != DEP_FILE) + continue; + if (fdep) + { + fdep = NULL; + break; + } + fdep = dep; + } + if (fdep) + { + if (*p->gamedir) + destname = va("%s/%s.tmp", p->gamedir, fdep->name); + else + destname = va("%s.tmp", fdep->name); + return Z_StrDup(destname); + } + ts = Z_StrDup(p->name); + for (t = ts; *t; t++) + { + switch(*t) + { + case '/': + case '?': + case '<': + case '>': + case '\\': + case ':': + case '*': + case '|': + case '\"': + case '.': + *t = '_'; + break; + default: + break; + } + } + if (*ts) + { + if (*p->gamedir) + destname = va("%s/%s.tmp", p->gamedir, ts); + else + destname = va("%s.tmp", ts); + } + else + destname = va("%x.tmp", (unsigned int)(quintptr_t)p); + Z_Free(ts); + return Z_StrDup(destname); +} + +//looks for the next package that needs downloading, and grabs it +static void PM_StartADownload(void) +{ + vfsfile_t *tmpfile; + char *temp; +// char native[MAX_OSPATH]; + package_t *p; + int simultaneous = 1; + int i; + + for (p = availablepackages; p ; p=p->next) + { + if (p->download) + simultaneous--; + } + + for (p = availablepackages; p && simultaneous > 0; p=p->next) + { + if (p->trymirrors) + { //flagged for a (re?)download + char *mirror = NULL; + for (i = 0; i < countof(p->mirror); i++) + { + if (p->mirror[i] && (p->trymirrors & (1u<mirror[i]; + p->trymirrors &= ~(1u<trymirrors = 0; + + for (i = 0; i < countof(p->mirror); i++) + if (p->mirror[i]) + break; + if (i == countof(p->mirror)) + { //this appears to be a meta package with no download + //just directly install it. + p->flags &= ~(DPF_NATIVE|DPF_CACHED|DPF_CORRUPT); + p->flags |= DPF_INSTALLED; + PM_WriteInstalledPackages(); + } + continue; + } + + if (p->qhash && (p->flags & DPF_CACHED)) + { //its in our cache directory, so lets just use that + p->trymirrors = 0; + p->flags |= DPF_INSTALLED; + PM_WriteInstalledPackages(); + FS_ReloadPackFiles(); + continue; + } + + + temp = PM_GetTempName(p); + + //FIXME: we should lock in the temp path, in case the user foolishly tries to change gamedirs. + + FS_CreatePath(temp, p->fsroot); + switch (p->extract) + { + case EXTRACT_ZIP: + case EXTRACT_COPY: + tmpfile = FS_OpenVFS(temp, "wb", p->fsroot); + break; +#ifdef AVAIL_XZDEC + case EXTRACT_XZ: + { + vfsfile_t *raw; + raw = FS_OpenVFS(temp, "wb", p->fsroot); + tmpfile = FS_XZ_DecompressWriteFilter(raw); + if (!tmpfile) + VFS_CLOSE(raw); + } + break; +#endif +#ifdef AVAIL_GZDEC + case EXTRACT_GZ: + { + vfsfile_t *raw; + raw = FS_OpenVFS(temp, "wb", p->fsroot); + tmpfile = FS_GZ_DecompressWriteFilter(raw, true); + if (!tmpfile) + VFS_CLOSE(raw); + } + break; +#endif + default: + Con_Printf("decompression method not supported\n"); + continue; + } + + if (tmpfile) + p->download = HTTP_CL_Get(mirror, NULL, PM_Download_Got); + if (p->download) + { + Con_Printf("Downloading %s\n", p->name); + p->download->file = tmpfile; + p->download->user_ctx = temp; + + DL_CreateThread(p->download, NULL, NULL); + } + else + { + Con_Printf("Unable to download %s\n", p->name); + p->flags &= ~DPF_MARKED; //can't do it. + if (tmpfile) + VFS_CLOSE(tmpfile); + FS_Remove(temp, p->fsroot); + } + + simultaneous--; + } + } +} +//'just' starts doing all the things needed to remove/install selected packages +static void PM_ApplyChanges(void) +{ + package_t *p, **link; + +//delete any that don't exist + for (link = &availablepackages; *link ; ) + { + p = *link; + if ((p->flags & DPF_PURGE) || (!(p->flags&DPF_MARKED) && (p->flags&DPF_INSTALLED))) + { //if we don't want it but we have it anyway: + qboolean reloadpacks = false; + struct packagedep_s *dep; + for (dep = p->deps; dep; dep = dep->next) + { + if (dep->dtype == DEP_FILE) + { + if (!reloadpacks) + { + char ext[8]; + COM_FileExtension(dep->name, ext, sizeof(ext)); + if (!stricmp(ext, "pak") || !stricmp(ext, "pk3")) + { + reloadpacks = true; + FS_UnloadPackFiles(); + } + } + if (*p->gamedir) + { + char *f = va("%s/%s", p->gamedir, dep->name); + char temp[MAX_OSPATH]; + if (p->qhash && FS_GenCachedPakName(f, p->qhash, temp, sizeof(temp)) && PM_CheckFile(temp, p->fsroot)) + { + if (p->flags & DPF_PURGE) + FS_Remove(temp, p->fsroot); + } + else + FS_Remove(va("%s/%s", p->gamedir, dep->name), p->fsroot); + } + else + FS_Remove(dep->name, p->fsroot); + } + } + + p->flags &= ~(DPF_NATIVE|DPF_CACHED|DPF_CORRUPT|DPF_PURGE|DPF_INSTALLED); + PM_ValidatePackage(p); + PM_WriteInstalledPackages(); + + if (reloadpacks) + FS_ReloadPackFiles(); + + if (p->flags & DPF_FORGETONUNINSTALL) + { + if (p->alternative) + { //replace it with its alternative package + *p->link = p->alternative; + p->alternative->alternative = p->alternative->next; + if (p->alternative->alternative) + p->alternative->alternative->link = &p->alternative->alternative; + p->alternative->next = p->next; + } + else + { //just remove it from the list. + *p->link = p->next; + if (p->next) + p->next->link = p->link; + } + +//FIXME: the menu(s) hold references to packages, so its not safe to purge them + p->flags |= DPF_HIDDEN; +// BZ_Free(p); + + continue; + } + } + + link = &(*link)->next; + } + + //and flag any new/updated ones for a download + for (p = availablepackages; p ; p=p->next) + { + if ((p->flags&DPF_MARKED) && !(p->flags&DPF_INSTALLED) && !p->download) + p->trymirrors = ~0u; + } + PM_StartADownload(); //and try to do those downloads. +} + +void PM_Command_f(void) +{ + package_t *p; + const char *act = Cmd_Argv(1); + const char *key; + + if (Cmd_FromGamecode()) + { + Con_Printf("%s may not be used from gamecode\n", Cmd_Argv(0)); + return; + } + + if (!loadedinstalled) + PM_UpdatePackageList(false, false); + + if (!strcmp(act, "list")) + { + for (p = availablepackages; p; p=p->next) + { + const char *status; + char *markup; + if (p->flags & DPF_INSTALLED) + markup = S_COLOR_GREEN; + else if (p->flags & DPF_CORRUPT) + markup = S_COLOR_RED; + else if (p->flags & (DPF_CACHED)) + markup = S_COLOR_YELLOW; //downloaded but not active + else + markup = S_COLOR_WHITE; + + if (!(p->flags & DPF_MARKED) != !(p->flags & DPF_INSTALLED) || (p->flags & DPF_PURGE)) + { + if (p->flags & DPF_MARKED) + { + if (p->flags & DPF_PURGE) + status = S_COLOR_CYAN""; + else + status = S_COLOR_CYAN""; + } + else if ((p->flags & DPF_PURGE) || !(p->qhash && (p->flags & DPF_CACHED))) + status = S_COLOR_CYAN""; + else + status = S_COLOR_CYAN""; + } + else if ((p->flags & (DPF_INSTALLED|DPF_CACHED)) == DPF_CACHED) + status = S_COLOR_CYAN""; + else + status = ""; + + Con_Printf(" ^[%s%s%s%s^] %s %s\n", markup, p->name, p->arch?":":"", p->arch?p->arch:"", status, strcmp(p->name, p->title)?p->title:""); + } + Con_Printf("\n"); + } + else if (!strcmp(act, "show")) + { + key = Cmd_Argv(2); + p = PM_FindPackage(key); + if (p) + { + if (p->previewimage) + Con_Printf("^[%s (%s)\\tipimg\\%s\\tip\\%s^]\n", p->name, p->version, p->previewimage, ""); + else + Con_Printf("%s (%s)\n", p->name, p->version); + if (p->title) + Con_Printf(" title: %s\n", p->title); + if (p->license) + Con_Printf(" license: %s\n", p->license); + if (p->author) + Con_Printf(" author: %s\n", p->author); + if (p->description) + Con_Printf(" %s\n", p->description); + + if (p->flags & DPF_MARKED) + { + if (p->flags & DPF_INSTALLED) + { + if (p->flags & DPF_PURGE) + Con_Printf(" package is flagged to be re-installed\n"); + else + Con_Printf(" package is currently installed\n"); + } + else + Con_Printf(" package is flagged to be installed\n"); + } + else + { + if (p->flags & DPF_INSTALLED) + { + if (p->flags & DPF_PURGE) + Con_Printf(" package is flagged to be purged\n"); + else + Con_Printf(" package is flagged to be disabled\n"); + } + else + Con_Printf(" package is not installed\n"); + } + if (p->flags & DPF_NATIVE) + Con_Printf(" package is native\n"); + if (p->flags & DPF_CACHED) + Con_Printf(" package is cached\n"); + if (p->flags & DPF_CORRUPT) + Con_Printf(" package is corrupt\n"); + if (p->flags & DPF_DISPLAYVERSION) + Con_Printf(" package has a version conflict\n"); + if (p->flags & DPF_FORGETONUNINSTALL) + Con_Printf(" package is obsolete\n"); + if (p->flags & DPF_HIDDEN) + Con_Printf(" package is hidden\n"); + if (p->flags & DPF_ENGINE) + Con_Printf(" package is an engine update\n"); + return; + } + Con_Printf("\n"); + } + else if (!strcmp(act, "search") || !strcmp(act, "find")) + { + const char *key = Cmd_Argv(2); + for (p = availablepackages; p; p=p->next) + { + if (Q_strcasestr(p->name, key) || (p->title && Q_strcasestr(p->title, key)) || (p->description && Q_strcasestr(p->description, key))) + { + Con_Printf("%s\n", p->name); + } + } + Con_Printf("\n"); + } + else if (!strcmp(act, "sources") || !strcmp(act, "addsources")) + { + if (Cmd_Argc() == 2) + { + int i; + for (i = 0; i < numdownloadablelists; i++) + Con_Printf("%s %s\n", downloadablelist[i].url, downloadablelist[i].save?"(explicit)":"(implicit)"); + Con_Printf("<%i sources>\n", numdownloadablelists); + } + else + PM_AddSubList(Cmd_Argv(2), "", true); + } + else if (!strcmp(act, "remsource")) + PM_RemSubList(Cmd_Argv(2)); + else if (!strcmp(act, "apply")) + { + Con_Printf("Applying package changes\n"); + PM_ApplyChanges(); + } + else if (!strcmp(act, "changes")) + { + PM_PrintChanges(); + } + else if (!strcmp(act, "reset")) + { + Con_Printf("Applying package changes\n"); + PM_RevertChanges(); + PM_ApplyChanges(); + PM_PrintChanges(); + } + else if (!strcmp(act, "upgrade")) + { + Con_Printf("Updating packages\n"); + PM_MarkUpdates(); + PM_ApplyChanges(); + } + else if (!strcmp(act, "add") || !strcmp(act, "get") || !strcmp(act, "install") || !strcmp(act, "enable")) + { + int arg = 2; + for (arg = 2; arg < Cmd_Argc(); arg++) + { + const char *key = Cmd_Argv(arg); + p = PM_FindPackage(key); + if (p) + PM_MarkPackage(p); + else + Con_Printf("%s: package %s not known\n", Cmd_Argv(0), key); + } + PM_PrintChanges(); + } + else if (!strcmp(act, "reinstall")) + { + int arg = 2; + for (arg = 2; arg < Cmd_Argc(); arg++) + { + const char *key = Cmd_Argv(arg); + p = PM_FindPackage(key); + if (p) + { + PM_MarkPackage(p); + p->flags |= DPF_PURGE; + } + else + Con_Printf("%s: package %s not known\n", Cmd_Argv(0), key); + } + PM_PrintChanges(); + } + else if (!strcmp(act, "disable") || !strcmp(act, "rem")) + { + int arg = 2; + for (arg = 2; arg < Cmd_Argc(); arg++) + { + const char *key = Cmd_Argv(arg); + p = PM_MarkedPackage(key); + if (!p) + p = PM_FindPackage(key); + if (p) + PM_UnmarkPackage(p); + else + Con_Printf("%s: package %s not known\n", Cmd_Argv(0), key); + } + PM_PrintChanges(); + } + else if (!strcmp(act, "del") || !strcmp(act, "purge") || !strcmp(act, "delete") || !strcmp(act, "uninstall")) + { + int arg = 2; + for (arg = 2; arg < Cmd_Argc(); arg++) + { + const char *key = Cmd_Argv(arg); + p = PM_MarkedPackage(key); + if (!p) + p = PM_FindPackage(key); + if (p) + { + PM_UnmarkPackage(p); + p->flags |= DPF_PURGE; + } + else + Con_Printf("%s: package %s not known\n", Cmd_Argv(0), key); + } + PM_PrintChanges(); + } + else + Con_Printf("%s: Unknown action %s\nShould be one of list, show, search, revert, add, rem, del, changes, apply\n", Cmd_Argv(0), act); +} + +#else +void PM_Command_f (void) +{ + Con_Printf("Package Manager is not implemented in this build\n"); +} +void Menu_Download_Update(void) +{ +} +void PM_LoadPackages(searchpath_t **oldpaths, const char *parent_pure, const char *parent_logical, searchpath_t *search, unsigned int loadstuff, int minpri, int maxpri) +{ +} +void PM_Shutdown(void) +{ +} +#endif + +#ifdef DOWNLOADMENU static void MD_Draw (int x, int y, struct menucustom_s *c, struct menu_s *m) { package_t *p; @@ -1620,192 +2404,10 @@ qboolean MD_PopMenu (union menuoption_s *mo,struct menu_s *m,int key) return false; } -vfsfile_t *FS_XZ_DecompressWriteFilter(vfsfile_t *infile); -vfsfile_t *FS_GZ_DecompressWriteFilter(vfsfile_t *outfile, qboolean autoclosefile); - -static char *MD_GetTempName(package_t *p) -{ - struct packagedep_s *dep, *fdep; - char *destname, *t, *ts; - //always favour the file so that we can rename safely without needing a copy. - for (dep = p->deps, fdep = NULL; dep; dep = dep->next) - { - if (dep->dtype != DEP_FILE) - continue; - if (fdep) - { - fdep = NULL; - break; - } - fdep = dep; - } - if (fdep) - { - if (*p->gamedir) - destname = va("%s/%s.tmp", p->gamedir, fdep->name); - else - destname = va("%s.tmp", fdep->name); - return Z_StrDup(destname); - } - ts = Z_StrDup(p->name); - for (t = ts; *t; t++) - { - switch(*t) - { - case '/': - case '?': - case '<': - case '>': - case '\\': - case ':': - case '*': - case '|': - case '\"': - case '.': - *t = '_'; - break; - default: - break; - } - } - if (*ts) - { - if (*p->gamedir) - destname = va("%s/%s.tmp", p->gamedir, ts); - else - destname = va("%s.tmp", ts); - } - else - destname = va("%x.tmp", (unsigned int)(quintptr_t)p); - Z_Free(ts); - return Z_StrDup(destname); -} - -static void Menu_Download_Got(struct dl_download *dl); -static void MD_StartADownload(void) -{ - vfsfile_t *tmpfile; - char *temp; -// char native[MAX_OSPATH]; - package_t *p; - int simultaneous = 1; - int i; - - for (p = availablepackages; p ; p=p->next) - { - if (p->download) - simultaneous--; - } - - for (p = availablepackages; p && simultaneous > 0; p=p->next) - { - if (p->trymirrors) - { //flagged for a (re?)download - char *mirror = NULL; - for (i = 0; i < countof(p->mirror); i++) - { - if (p->mirror[i] && (p->trymirrors & (1u<mirror[i]; - p->trymirrors &= ~(1u<trymirrors = 0; - - for (i = 0; i < countof(p->mirror); i++) - if (p->mirror[i]) - break; - if (i == countof(p->mirror)) - { //this appears to be a meta package with no download - //just directly install it. - p->flags &= ~(DPF_NATIVE|DPF_CACHED|DPF_CORRUPT); - p->flags |= DPF_INSTALLED; - PM_WriteInstalledPackages(); - } - continue; - } - - if (p->qhash && (p->flags & DPF_CACHED)) - { //its in our cache directory, so lets just use that - p->trymirrors = 0; - p->flags |= DPF_INSTALLED; - PM_WriteInstalledPackages(); - FS_ReloadPackFiles(); - continue; - } - - - temp = MD_GetTempName(p); - - //FIXME: we should lock in the temp path, in case the user foolishly tries to change gamedirs. - - FS_CreatePath(temp, p->fsroot); - switch (p->extract) - { - case EXTRACT_ZIP: - case EXTRACT_COPY: - tmpfile = FS_OpenVFS(temp, "wb", p->fsroot); - break; -#ifdef AVAIL_XZDEC - case EXTRACT_XZ: - { - vfsfile_t *raw; - raw = FS_OpenVFS(temp, "wb", p->fsroot); - tmpfile = FS_XZ_DecompressWriteFilter(raw); - if (!tmpfile) - VFS_CLOSE(raw); - } - break; -#endif -#ifdef AVAIL_GZDEC - case EXTRACT_GZ: - { - vfsfile_t *raw; - raw = FS_OpenVFS(temp, "wb", p->fsroot); - tmpfile = FS_GZ_DecompressWriteFilter(raw, true); - if (!tmpfile) - VFS_CLOSE(raw); - } - break; -#endif - default: - Con_Printf("decompression method not supported\n"); - continue; - } - - if (tmpfile) - p->download = HTTP_CL_Get(mirror, NULL, Menu_Download_Got); - if (p->download) - { - Con_Printf("Downloading %s\n", p->fullname); - p->download->file = tmpfile; - p->download->user_ctx = temp; - - DL_CreateThread(p->download, NULL, NULL); - } - else - { - Con_Printf("Unable to download %s\n", p->fullname); - p->flags &= ~DPF_MARKED; //can't do it. - if (tmpfile) - VFS_CLOSE(tmpfile); - FS_Remove(temp, p->fsroot); - } - - simultaneous--; - } - } -} - static qboolean MD_ApplyDownloads (union menuoption_s *mo,struct menu_s *m,int key) { if (key == K_ENTER || key == K_KP_ENTER || key == K_MOUSE1) { - package_t *p, **link; - #ifdef HAVEAUTOUPDATE if (autoupdatesetting != UPD_UNSUPPORTED) { @@ -1814,87 +2416,7 @@ static qboolean MD_ApplyDownloads (union menuoption_s *mo,struct menu_s *m,int k } #endif - //delete any that don't exist - for (link = &availablepackages; *link ; ) - { - p = *link; - if ((p->flags & DPF_PURGE) || (!(p->flags&DPF_MARKED) && (p->flags&DPF_INSTALLED))) - { //if we don't want it but we have it anyway: - qboolean reloadpacks = false; - struct packagedep_s *dep; - for (dep = p->deps; dep; dep = dep->next) - { - if (dep->dtype == DEP_FILE) - { - if (!reloadpacks) - { - char ext[8]; - COM_FileExtension(dep->name, ext, sizeof(ext)); - if (!stricmp(ext, "pak") || !stricmp(ext, "pk3")) - { - reloadpacks = true; - FS_UnloadPackFiles(); - } - } - if (*p->gamedir) - { - char *f = va("%s/%s", p->gamedir, dep->name); - char temp[MAX_OSPATH]; - if (p->qhash && FS_GenCachedPakName(f, p->qhash, temp, sizeof(temp)) && PM_CheckFile(temp, p->fsroot)) - { - if (p->flags & DPF_PURGE) - FS_Remove(temp, p->fsroot); - } - else - FS_Remove(va("%s/%s", p->gamedir, dep->name), p->fsroot); - } - else - FS_Remove(dep->name, p->fsroot); - } - } - - p->flags &= ~(DPF_NATIVE|DPF_CACHED|DPF_CORRUPT|DPF_PURGE|DPF_INSTALLED); - PM_ValidatePackage(p); - PM_WriteInstalledPackages(); - - if (reloadpacks) - FS_ReloadPackFiles(); - - if (p->flags & DPF_FORGETONUNINSTALL) - { - if (p->alternative) - { //replace it with its alternative package - *p->link = p->alternative; - p->alternative->alternative = p->alternative->next; - if (p->alternative->alternative) - p->alternative->alternative->link = &p->alternative->alternative; - p->alternative->next = p->next; - } - else - { //just remove it from the list. - *p->link = p->next; - if (p->next) - p->next->link = p->link; - } - -//FIXME: the menu(s) hold references to packages, so its not safe to purge them - p->flags |= DPF_HIDDEN; -// BZ_Free(p); - - continue; - } - } - - link = &(*link)->next; - } - - //and flag any new/updated ones for a download - for (p = availablepackages; p ; p=p->next) - { - if ((p->flags&DPF_MARKED) && !(p->flags&DPF_INSTALLED) && !p->download) - p->trymirrors = ~0u; - } - MD_StartADownload(); //and try to do those downloads. + PM_ApplyChanges(); return true; } return false; @@ -1913,14 +2435,7 @@ static qboolean MD_RevertUpdates (union menuoption_s *mo,struct menu_s *m,int ke { if (key == K_ENTER || key == K_KP_ENTER || key == K_MOUSE1) { - package_t *p; - for (p = availablepackages; p; p = p->next) - { - if (p->flags & DPF_INSTALLED) - p->flags |= DPF_MARKED; - else - p->flags &= ~DPF_MARKED; - } + PM_RevertChanges(); return true; } return false; @@ -1966,26 +2481,36 @@ void M_AddItemsToDownloadMenu(menu_t *m) y+=4; //small gap for (p = availablepackages; p; p = p->next) { - if (strncmp(p->fullname, info->pathprefix, prefixlen)) + if (strncmp(p->category, info->pathprefix, prefixlen)) continue; if ((p->flags & DPF_HIDDEN) && (p->arch || !(p->flags & DPF_INSTALLED))) continue; - slash = strchr(p->fullname+prefixlen, '/'); + slash = strchr(p->category+prefixlen, '/'); if (slash) { - Q_strncpyz(path, p->fullname, MAX_QPATH); + Q_strncpyz(path, p->category, MAX_QPATH); slash = strchr(path+prefixlen, '/'); if (slash) *slash = '\0'; for (mo = m->options; mo; mo = mo->common.next) if (mo->common.type == mt_button) - if (!strcmp(mo->button.text, path + prefixlen)) + if (!strcmp(mo->button.text+1, path + prefixlen)) break; if (!mo) { - menubutton_t *b = MC_AddConsoleCommand(m, 6*8, 170, y, path+prefixlen, va("menu_download \"%s/\"", path)); + package_t *s; + menubutton_t *b; + for (s = availablepackages; s; s = s->next) + { + if (!strncmp(s->category, info->pathprefix, slash-path) || s->category[slash-path] != '/') + continue; + if (!(s->flags & DPF_INSTALLED) != !(s->flags & DPF_MARKED)) + break; + } + + b = MC_AddConsoleCommand(m, 6*8, 170, y, va("%s%s", s?"!":" ", path+prefixlen), va("menu_download \"%s/\"", path)); y += 8; if (!m->selecteditem) @@ -2024,6 +2549,7 @@ void M_Download_UpdateStatus(struct menu_s *m) Z_Free(op); } m->cursoritem = m->selecteditem = NULL; + info->downloadablessequence = downloadablessequence; info->populated = false; MC_AddWhiteText(m, 24, 170, 8, "Downloads", false); @@ -2059,171 +2585,6 @@ void M_Download_UpdateStatus(struct menu_s *m) } } -static int QDECL MD_ExtractFiles(const char *fname, qofs_t fsize, time_t mtime, void *parm, searchpathfuncs_t *spath) -{ //this is gonna suck. threading would help, but gah. - package_t *p = parm; - flocation_t loc; - if (fname[strlen(fname)-1] == '/') - { //directory. - - } - else if (spath->FindFile(spath, &loc, fname, NULL) && loc.len < 0x80000000u) - { - char *f = malloc(loc.len); - const char *n; - if (f) - { - spath->ReadFile(spath, &loc, f); - if (*p->gamedir) - n = va("%s/%s", p->gamedir, fname); - else - n = fname; - if (FS_WriteFile(n, f, loc.len, p->fsroot)) - p->flags |= DPF_NATIVE|DPF_INSTALLED; - free(f); - - //keep track of the installed files, so we can delete them properly after. - PM_AddDep(p, DEP_FILE, fname); - } - } - return 1; -} - -static void Menu_Download_Got(struct dl_download *dl) -{ - qboolean successful = dl->status == DL_FINISHED; - package_t *p; - char *tempname = dl->user_ctx; - - for (p = availablepackages; p ; p=p->next) - { - if (p->download == dl) - break; - } - - if (dl->file) - { - VFS_CLOSE(dl->file); - dl->file = NULL; - } - - if (p) - { - char ext[8]; - char *destname; - struct packagedep_s *dep; - p->download = NULL; - - if (!successful) - { - Con_Printf("Couldn't download %s (from %s)\n", p->name, dl->url); - FS_Remove (tempname, p->fsroot); - Z_Free(tempname); - MD_StartADownload(); - return; - } - - if (p->extract == EXTRACT_ZIP) - { - vfsfile_t *f = FS_OpenVFS(tempname, "rb", p->fsroot); - if (f) - { - searchpathfuncs_t *archive = FSZIP_LoadArchive(f, tempname, NULL); - if (archive) - { - p->flags &= ~(DPF_NATIVE|DPF_CACHED|DPF_CORRUPT|DPF_INSTALLED); - archive->EnumerateFiles(archive, "*", MD_ExtractFiles, p); - archive->EnumerateFiles(archive, "*/*", MD_ExtractFiles, p); - archive->EnumerateFiles(archive, "*/*/*", MD_ExtractFiles, p); - archive->EnumerateFiles(archive, "*/*/*/*", MD_ExtractFiles, p); - archive->EnumerateFiles(archive, "*/*/*/*/*", MD_ExtractFiles, p); - archive->EnumerateFiles(archive, "*/*/*/*/*/*", MD_ExtractFiles, p); - archive->EnumerateFiles(archive, "*/*/*/*/*/*/*", MD_ExtractFiles, p); - archive->EnumerateFiles(archive, "*/*/*/*/*/*/*/*", MD_ExtractFiles, p); - archive->EnumerateFiles(archive, "*/*/*/*/*/*/*/*/*", MD_ExtractFiles, p); - archive->ClosePath(archive); - - PM_WriteInstalledPackages(); - -// if (!stricmp(ext, "pak") || !stricmp(ext, "pk3")) -// FS_ReloadPackFiles(); - } - else - VFS_CLOSE(f); - } - PM_ValidatePackage(p); - - FS_Remove (tempname, p->fsroot); - Z_Free(tempname); - MD_StartADownload(); - return; - } - else - { - for (dep = p->deps; dep; dep = dep->next) - { - unsigned int nfl; - if (dep->dtype != DEP_FILE) - continue; - - COM_FileExtension(dep->name, ext, sizeof(ext)); - if (!stricmp(ext, "pak") || !stricmp(ext, "pk3")) - FS_UnloadPackFiles(); //we reload them after - if ((!stricmp(ext, "dll") || !stricmp(ext, "so")) && !Q_strncmp(dep->name, "fteplug_", 8)) - Cmd_ExecuteString(va("plug_close %s\n", dep->name), RESTRICT_LOCAL); //try to purge plugins so there's no files left open - - nfl = DPF_NATIVE; - if (*p->gamedir) - { - char temp[MAX_OSPATH]; - destname = va("%s/%s", p->gamedir, dep->name); - if (p->qhash && FS_GenCachedPakName(destname, p->qhash, temp, sizeof(temp))) - { - nfl = DPF_CACHED; - destname = va("%s", temp); - } - } - else - destname = dep->name; - nfl |= DPF_INSTALLED | (p->flags & ~(DPF_CACHED|DPF_NATIVE|DPF_CORRUPT)); - FS_CreatePath(destname, p->fsroot); - if (FS_Remove(destname, p->fsroot)) - ; - if (!FS_Rename2(tempname, destname, p->fsroot, p->fsroot)) - { - //error! - Con_Printf("Couldn't rename %s to %s. Removed instead.\n", tempname, destname); - FS_Remove (tempname, p->fsroot); - } - else - { //success! - Con_Printf("Downloaded %s (to %s)\n", p->name, destname); - p->flags = nfl; - PM_WriteInstalledPackages(); - } - - PM_ValidatePackage(p); - - if (!stricmp(ext, "pak") || !stricmp(ext, "pk3")) - FS_ReloadPackFiles(); - if ((!stricmp(ext, "dll") || !stricmp(ext, "so")) && !Q_strncmp(dep->name, "fteplug_", 8)) - Cmd_ExecuteString(va("plug_load %s\n", dep->name), RESTRICT_LOCAL); - - Z_Free(tempname); - MD_StartADownload(); - return; - } - } - Con_Printf("menu_download: %s has no filename info\n", p->name); - } - else - Con_Printf("menu_download: Can't figure out where %s came from (url: %s)\n", dl->localname, dl->url); - - FS_Remove (tempname, FS_GAMEONLY); - Z_Free(tempname); - MD_StartADownload(); -} - void Menu_DownloadStuff_f (void) { menu_t *menu; @@ -2255,21 +2616,9 @@ void Menu_Download_Update(void) PM_UpdatePackageList(true, 2); } - -#endif - #else void Menu_DownloadStuff_f (void) { Con_Printf("Download menu not implemented in this build\n"); } -void Menu_Download_Update(void) -{ -} -void PM_LoadPackages(searchpath_t **oldpaths, const char *parent_pure, const char *parent_logical, searchpath_t *search, unsigned int loadstuff, int minpri, int maxpri) -{ -} -void PM_Shutdown(void) -{ -} #endif diff --git a/engine/client/merged.h b/engine/client/merged.h index bf55a40d4..51a974550 100644 --- a/engine/client/merged.h +++ b/engine/client/merged.h @@ -383,7 +383,7 @@ typedef enum backendmode_e BEM_STENCIL, //used for drawing shadow volumes to the stencil buffer. BEM_DEPTHDARK, //a quick depth pass. textures used only for alpha test. additive textures still shown as normal. BEM_CREPUSCULAR, //sky is special, everything else completely black - BEM_DEPTHNORM, //all opaque stuff drawn using 'depthnorm' shader + BEM_GBUFFER, // BEM_FOG, //drawing a fog volume BEM_LIGHT, //we have a valid light } backendmode_t; @@ -429,7 +429,7 @@ typedef struct rendererinfo_s { void (*BE_SubmitBatch)(struct batch_s *batch); struct batch_s *(*BE_GetTempBatch)(void); //Asks the backend to invoke DrawMeshChain for each surface, and to upload lightmaps as required - void (*BE_DrawWorld) (struct batch_s **worldbatches, qbyte *vis); + void (*BE_DrawWorld) (struct batch_s **worldbatches); //called at init, force the display to the right defaults etc void (*BE_Init)(void); //Generates an optimised VBO, one for each texture on the map diff --git a/engine/client/p_script.c b/engine/client/p_script.c index cdbc66dd5..090272817 100644 --- a/engine/client/p_script.c +++ b/engine/client/p_script.c @@ -1175,7 +1175,10 @@ void P_ParticleEffect_f(void) } else if (!strcmp(var, "shader")) { - Q_strncpyz(ptype->texname, ptype->name, sizeof(ptype->texname)); + if (*value) + Q_strncpyz(ptype->texname, value, sizeof(ptype->texname)); + else + Q_strncpyz(ptype->texname, ptype->name, sizeof(ptype->texname)); buf = Cbuf_GetNext(Cmd_ExecLevel, true); while (*buf && *buf <= ' ') buf++; //no leading whitespace please. diff --git a/engine/client/quakedef.h b/engine/client/quakedef.h index 84dafa93c..d56c2f2e8 100644 --- a/engine/client/quakedef.h +++ b/engine/client/quakedef.h @@ -268,8 +268,8 @@ extern "C" { typedef struct quakeparms_s { - char *basedir; //working directory - char *binarydir; //exe directory + const char *basedir; //working directory + const char *binarydir; //exe directory const char *manifest; //linked manifest data (for installer functionality etc) int argc; const char **argv; diff --git a/engine/client/r_surf.c b/engine/client/r_surf.c index a362b689b..f56f7b77f 100644 --- a/engine/client/r_surf.c +++ b/engine/client/r_surf.c @@ -2942,7 +2942,8 @@ void Surf_DrawWorld (void) if (r_refdef.flags & RDF_NOWORLDMODEL) { r_refdef.flags |= RDF_NOWORLDMODEL; - BE_DrawWorld(NULL, NULL); + r_refdef.scenevis = NULL; + BE_DrawWorld(NULL); return; } if (!cl.worldmodel || cl.worldmodel->loadstate != MLS_LOADED) @@ -3055,7 +3056,8 @@ void Surf_DrawWorld (void) P_DrawParticles (); TRACE(("dbg: calling BE_DrawWorld\n")); - BE_DrawWorld(webostate->rbatches, surfvis); + r_refdef.scenevis = surfvis; + BE_DrawWorld(webostate->rbatches); /*FIXME: move this away*/ if (cl.worldmodel->fromgame == fg_quake || cl.worldmodel->fromgame == fg_halflife) @@ -3155,7 +3157,8 @@ void Surf_DrawWorld (void) } TRACE(("dbg: calling BE_DrawWorld\n")); - BE_DrawWorld(cl.worldmodel->batches, surfvis); + r_refdef.scenevis = surfvis; + BE_DrawWorld(cl.worldmodel->batches); Surf_PopChains(cl.worldmodel->batches); diff --git a/engine/client/render.h b/engine/client/render.h index dac9d3217..78274c304 100644 --- a/engine/client/render.h +++ b/engine/client/render.h @@ -267,6 +267,7 @@ typedef struct float m_projection[16]; float m_view[16]; + qbyte *scenevis; /*this is the vis that's currently being draw*/ mplane_t frustum[MAXFRUSTUMPLANES]; int frustum_numworldplanes; //all but far, which isn't culled because this wouldn't cover the entire screen. @@ -275,12 +276,12 @@ typedef struct fogstate_t globalfog; float hdr_value; - pxrect_t pxrect; /*vrect, but in pixels rather than virtual coords*/ - qboolean externalview; /*draw external models and not viewmodels*/ - int recurse; /*in a mirror/portal/half way through drawing something else*/ - qboolean forcevis; /*if true, vis comes from the forcedvis field instead of recalculated*/ - unsigned int flipcull; /*reflected/flipped view, requires inverted culling (should be set to SHADER_CULL_FLIPPED or 0)*/ - qboolean useperspective; /*not orthographic*/ + pxrect_t pxrect; /*vrect, but in pixels rather than virtual coords*/ + qboolean externalview; /*draw external models and not viewmodels*/ + int recurse; /*in a mirror/portal/half way through drawing something else*/ + qboolean forcevis; /*if true, vis comes from the forcedvis field instead of recalculated*/ + unsigned int flipcull; /*reflected/flipped view, requires inverted culling (should be set to SHADER_CULL_FLIPPED or 0)*/ + qboolean useperspective; /*not orthographic*/ stereomethod_t stereomethod; rtname_t rt_destcolour[R_MAX_RENDERTARGETS]; /*used for 2d. written by 3d*/ @@ -289,7 +290,7 @@ typedef struct rtname_t rt_ripplemap; /*read by 2d. used by 3d (internal ripplemap buffer used if not set)*/ rtname_t nearenvmap; /*provides a fallback endmap cubemap to render with*/ - qbyte *forcedvis; + qbyte *forcedvis; /*set if forcevis is set*/ qboolean areabitsknown; qbyte areabits[MAX_MAP_AREA_BYTES]; } refdef_t; @@ -595,12 +596,13 @@ extern cvar_t r_deluxemapping_cvar; extern qboolean r_deluxemapping; extern cvar_t r_softwarebanding_cvar; extern qboolean r_softwarebanding; +extern cvar_t r_lightprepass_cvar; +extern int r_lightprepass; //0=off,1=16bit,2=32bit #ifdef R_XFLIP extern cvar_t r_xflip; #endif -extern cvar_t r_lightprepass; extern cvar_t gl_mindist, gl_maxdist; extern cvar_t r_clear; extern cvar_t gl_poly; diff --git a/engine/client/renderer.c b/engine/client/renderer.c index 338474159..69d624473 100644 --- a/engine/client/renderer.c +++ b/engine/client/renderer.c @@ -402,7 +402,8 @@ cvar_t r_noaliasshadows = CVARF ("r_noaliasshadows", "0", CVAR_ARCHIVE); cvar_t r_shadows = CVARFD ("r_shadows", "0", CVAR_ARCHIVE, "Draw basic blob shadows underneath entities without using realtime lighting."); cvar_t r_showbboxes = CVARD("r_showbboxes", "0", "Debugging. Shows bounding boxes. 1=ssqc, 2=csqc. Red=solid, Green=stepping/toss/bounce, Blue=onground."); cvar_t r_showfields = CVARD("r_showfields", "0", "Debugging. Shows entity fields boxes (entity closest to crosshair). 1=ssqc, 2=csqc."); -cvar_t r_lightprepass = CVARFD("r_lightprepass", "0", CVAR_SHADERSYSTEM, "Experimental. Attempt to use a different lighting mechanism."); +cvar_t r_lightprepass_cvar = CVARFD("r_lightprepass", "0", CVAR_SHADERSYSTEM, "Experimental. Attempt to use a different lighting mechanism."); +int r_lightprepass; cvar_t r_shadow_bumpscale_basetexture = CVARD ("r_shadow_bumpscale_basetexture", "0", "bumpyness scaler for generation of fallback normalmap textures from models"); cvar_t r_shadow_bumpscale_bumpmap = CVARD ("r_shadow_bumpscale_bumpmap", "4", "bumpyness scaler for _bump textures"); @@ -776,7 +777,7 @@ void Renderer_Init(void) Cvar_Register(&r_stains, GRAPHICALNICETIES); Cvar_Register(&r_stainfadetime, GRAPHICALNICETIES); Cvar_Register(&r_stainfadeammount, GRAPHICALNICETIES); - Cvar_Register(&r_lightprepass, GLRENDEREROPTIONS); + Cvar_Register(&r_lightprepass_cvar, GLRENDEREROPTIONS); Cvar_Register (&r_coronas, GRAPHICALNICETIES); Cvar_Register (&r_coronas_occlusion, GRAPHICALNICETIES); Cvar_Register (&r_coronas_mindist, GRAPHICALNICETIES); @@ -1249,9 +1250,6 @@ qboolean R_ApplyRenderer_Load (rendererstate_t *newr) pmove.numphysent = 0; pmove.physents[0].model = NULL; - r_softwarebanding = r_softwarebanding_cvar.ival; - r_deluxemapping = r_deluxemapping_cvar.ival; - vid.dpi_x = 0; vid.dpi_y = 0; @@ -1332,6 +1330,10 @@ TRACE(("dbg: R_ApplyRenderer: Palette loaded\n")); } TRACE(("dbg: R_ApplyRenderer: vid applied\n")); + r_softwarebanding = false; + r_deluxemapping = false; + r_lightprepass = false; + W_LoadWadFile("gfx.wad"); TRACE(("dbg: R_ApplyRenderer: wad loaded\n")); Image_Init(); diff --git a/engine/client/vid_headless.c b/engine/client/vid_headless.c index 8bd0ea95e..45086716c 100644 --- a/engine/client/vid_headless.c +++ b/engine/client/vid_headless.c @@ -191,7 +191,7 @@ static struct batch_s *Headless_BE_GetTempBatch (void) { return NULL; } -static void Headless_BE_DrawWorld (struct batch_s **worldbatches, qbyte *vis) +static void Headless_BE_DrawWorld (struct batch_s **worldbatches) { } static void Headless_BE_Init (void) diff --git a/engine/common/common.c b/engine/common/common.c index ddcffef58..285ba1bf9 100644 --- a/engine/common/common.c +++ b/engine/common/common.c @@ -5511,10 +5511,11 @@ void COM_Init (void) COM_InitWorkerThread(); #endif - Cmd_AddCommand ("path", COM_Path_f); //prints a list of current search paths. - Cmd_AddCommand ("dir", COM_Dir_f); //q3 like - Cmd_AddCommand ("flocate", COM_Locate_f); //prints the pak or whatever where this file can be found. - Cmd_AddCommand ("version", COM_Version_f); //prints the pak or whatever where this file can be found. + Cmd_AddCommandD("pkg", PM_Command_f, "Provides a way to install / list / disable / purge packages via the console."); + Cmd_AddCommandD("path", COM_Path_f, "prints a list of current search paths."); + Cmd_AddCommandD("dir", COM_Dir_f, "Displays filesystem listings. Accepts wildcards."); //q3 like + Cmd_AddCommandD("flocate", COM_Locate_f, "Searches for a named file, and displays where it can be found in the OS's filesystem"); //prints the pak or whatever where this file can be found. + Cmd_AddCommandD("version", COM_Version_f, "Reports engine revision and optional compile-time settings."); //prints the pak or whatever where this file can be found. #ifdef _DEBUG Cmd_AddCommand ("loopme", COM_LoopMe_f); diff --git a/engine/common/common.h b/engine/common/common.h index 24526c6d0..4ec0a5b2d 100644 --- a/engine/common/common.h +++ b/engine/common/common.h @@ -639,6 +639,7 @@ typedef struct void FS_Manifest_Free(ftemanifest_t *man); ftemanifest_t *FS_Manifest_Parse(const char *fname, const char *data); void PM_Shutdown(void); +void PM_Command_f(void); void COM_InitFilesystem (void); //does not set up any gamedirs. qboolean FS_DownloadingPackage(void); @@ -704,7 +705,7 @@ int version_number(void); char *version_string(void); -void TL_InitLanguages(char *langpath); //langpath is where the .po files can be found +void TL_InitLanguages(const char *langpath); //langpath is where the .po files can be found void TL_Shutdown(void); void T_FreeStrings(void); char *T_GetString(int num); diff --git a/engine/common/fs.c b/engine/common/fs.c index b6202d33a..cdfc78666 100644 --- a/engine/common/fs.c +++ b/engine/common/fs.c @@ -3989,7 +3989,7 @@ static qboolean FS_DirHasAPackage(char *basedir, ftemanifest_t *man) } //just check each possible file, see if one is there. -static qboolean FS_DirHasGame(char *basedir, int gameidx) +static qboolean FS_DirHasGame(const char *basedir, int gameidx) { int j; vfsfile_t *f; @@ -4008,7 +4008,7 @@ static qboolean FS_DirHasGame(char *basedir, int gameidx) } //check em all -static int FS_IdentifyDefaultGameFromDir(char *basedir) +static int FS_IdentifyDefaultGameFromDir(const char *basedir) { int i; for (i = 0; gamemode_info[i].argname; i++) @@ -5411,6 +5411,7 @@ void FS_ChangeGame_f(void) if (!Q_strcasecmp(gamemode_info[i].argname+1, arg)) { Con_Printf("Switching to %s\n", gamemode_info[i].argname+1); + PM_Shutdown(); FS_ChangeGame(FS_GenerateLegacyManifest(NULL, 0, true, i), true, true); return; } @@ -5450,15 +5451,15 @@ void FS_ChangeMod_f(void) arg = Cmd_Argv(i++); if (!strcmp(arg, "package")) { - if (packages >= countof(packagespaths)-1) //must leave space for one, as a terminator. - break; - arg = Cmd_Argv(i++); - packagespaths[packages].url = Z_StrDup(arg); - if (!FS_PathURLCache(packagespaths[packages].url, cachename, sizeof(cachename))) - break; - packagespaths[packages].path = Z_StrDup(cachename); - packages++; + if (packages == countof(packagespaths)) //must leave space for one, as a terminator. + continue; + if (FS_PathURLCache(arg, cachename, sizeof(cachename))) + { + packagespaths[packages].url = Z_StrDup(arg); + packagespaths[packages].path = Z_StrDup(cachename); + packages++; + } } else if (!strcmp(arg, "prefix")) { diff --git a/engine/common/protocol.h b/engine/common/protocol.h index 79473c222..915cd450f 100644 --- a/engine/common/protocol.h +++ b/engine/common/protocol.h @@ -108,12 +108,14 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #define PROTOCOL_VERSION_QW 28 -#define PROTOCOL_VERSION_Q2_DEMO_MIN 26 -#define PROTOCOL_VERSION_Q2_MIN 31 -#define PROTOCOL_VERSION_Q2 34 +#define PROTOCOL_VERSION_Q2_DEMO_MIN 26 //we can parse this server +#define PROTOCOL_VERSION_Q2_MIN 31 //we can join these outdated servers +#define PROTOCOL_VERSION_Q2 34 //we host this #define PROTOCOL_VERSION_R1Q2 35 #define PROTOCOL_VERSION_Q2PRO 36 +#define PROTOCOL_VERSION_Q2_DEMO_MAX PROTOCOL_VERSION_Q2PRO + //========================================= #define PORT_NQSERVER 26000 diff --git a/engine/common/sys.h b/engine/common/sys.h index 4d98f2a51..4e6e63bbe 100644 --- a/engine/common/sys.h +++ b/engine/common/sys.h @@ -174,7 +174,7 @@ void NPQTV_Sys_MainLoop(void); #define UPD_STABLE 2 #define UPD_TESTING 3 -#if defined(WEBCLIENT) && defined(_WIN32) +#if defined(WEBCLIENT) && defined(_WIN32) && !defined(SERVERONLY) int StartLocalServer(int close); #define HAVEAUTOUPDATE diff --git a/engine/common/translate.c b/engine/common/translate.c index e62ff37ad..8a15f9b32 100644 --- a/engine/common/translate.c +++ b/engine/common/translate.c @@ -89,7 +89,7 @@ int TL_FindLanguage(const char *lang) } //need to set up default languages for any early prints before cvars are inited. -void TL_InitLanguages(char *newlangpath) +void TL_InitLanguages(const char *newlangpath) { int i; char *lang; diff --git a/engine/d3d/d3d11_backend.c b/engine/d3d/d3d11_backend.c index 85ac8a81f..a1d2327a9 100644 --- a/engine/d3d/d3d11_backend.c +++ b/engine/d3d/d3d11_backend.c @@ -3532,7 +3532,7 @@ void D3D11BE_DoneShadows(void) } #endif -void D3D11BE_DrawWorld (batch_t **worldbatches, qbyte *vis) +void D3D11BE_DrawWorld (batch_t **worldbatches) { batch_t *batches[SHADER_SORT_COUNT]; RSpeedLocals(); @@ -3559,7 +3559,7 @@ void D3D11BE_DrawWorld (batch_t **worldbatches, qbyte *vis) shaderstate.curdlight = NULL; BE_GenModelBatches(batches, shaderstate.curdlight, BEM_STANDARD); - if (vis) + if (r_refdef.scenevis) { BE_UploadLightmaps(false); @@ -3573,7 +3573,7 @@ void D3D11BE_DrawWorld (batch_t **worldbatches, qbyte *vis) r_worldentity.axis[2][2] = 1; #ifdef RTLIGHTS - if (vis && r_shadow_realtime_world.ival) + if (r_refdef.scenevis && r_shadow_realtime_world.ival) shaderstate.identitylighting = r_shadow_realtime_world_lightmaps.value; else #endif @@ -3590,7 +3590,7 @@ void D3D11BE_DrawWorld (batch_t **worldbatches, qbyte *vis) #ifdef RTLIGHTS RSpeedRemark(); D3D11BE_SelectEntity(&r_worldentity); - Sh_DrawLights(vis); + Sh_DrawLights(r_refdef.scenevis); RSpeedEnd(RSPEED_STENCILSHADOWS); #endif diff --git a/engine/d3d/d3d_backend.c b/engine/d3d/d3d_backend.c index e36f6a5ea..1830a8bd9 100644 --- a/engine/d3d/d3d_backend.c +++ b/engine/d3d/d3d_backend.c @@ -3459,7 +3459,7 @@ void D3D9BE_RenderShadowBuffer(unsigned int numverts, IDirect3DVertexBuffer9 *vb } #endif -void D3D9BE_DrawWorld (batch_t **worldbatches, qbyte *vis) +void D3D9BE_DrawWorld (batch_t **worldbatches) { batch_t *batches[SHADER_SORT_COUNT]; RSpeedLocals(); @@ -3514,11 +3514,11 @@ void D3D9BE_DrawWorld (batch_t **worldbatches, qbyte *vis) RSpeedEnd(RSPEED_WORLD); #ifdef RTLIGHTS - if (vis) + if (r_refdef.scenevis) { RSpeedRemark(); D3D9BE_SelectEntity(&r_worldentity); - Sh_DrawLights(vis); + Sh_DrawLights(r_refdef.scenevis); RSpeedEnd(RSPEED_STENCILSHADOWS); } #endif diff --git a/engine/gl/gl_alias.c b/engine/gl/gl_alias.c index 81638db32..9f0aeb4b0 100644 --- a/engine/gl/gl_alias.c +++ b/engine/gl/gl_alias.c @@ -724,8 +724,8 @@ static shader_t *GL_ChooseSkin(galiasinfo_t *inf, model_t *model, int surfnum, e (tex->upperoverlay && (tex->upperoverlay->status == TEX_LOADING || tex->upperoverlay->status == TEX_LOADED))) return shader; } - if (shader->prog && (shader->prog->supportedpermutations & PERMUTATION_UPPERLOWER) && !h2playertranslations) - { //this shader can do permutations. this means we can generate only a black image, with separate top+bottom textures. + if ((shader->flags & SHADER_HASTOPBOTTOM) && !h2playertranslations) + { //this shader will try to do top+bottom colours. this means we can generate only a black image, with separate top+bottom textures. tc = 0xfe000000; bc = 0xfe000000; generateupperlower = true; diff --git a/engine/gl/gl_backend.c b/engine/gl/gl_backend.c index a96844bfb..d72f5ea1b 100644 --- a/engine/gl/gl_backend.c +++ b/engine/gl/gl_backend.c @@ -96,10 +96,8 @@ struct { program_t *programfixedemu[8]; - qboolean initeddepthnorm; - const shader_t *depthnormshader; - texid_t tex_normals; - texid_t tex_diffuse; + texid_t tex_gbuf_normals; + texid_t tex_gbuf_diffuse; int fbo_current; //the one currently being rendered to texid_t tex_sourcecol; /*this is used by $sourcecolour tgen*/ texid_t tex_sourcedepth; @@ -1409,6 +1407,19 @@ void GLBE_DestroyFBOs(void) Image_DestroyTexture(shaderstate.temptexture); shaderstate.temptexture = r_nulltex; } + + + //nuke deferred rendering stuff + if (shaderstate.tex_gbuf_diffuse) + { + Image_DestroyTexture(shaderstate.tex_gbuf_diffuse); + shaderstate.tex_gbuf_diffuse = r_nulltex; + } + if (shaderstate.tex_gbuf_normals) + { + Image_DestroyTexture(shaderstate.tex_gbuf_normals); + shaderstate.tex_gbuf_normals = r_nulltex; + } } void GLBE_Shutdown(void) @@ -4042,10 +4053,8 @@ static void DrawMeshes(void) BE_LegacyLighting(); #endif break; - case BEM_DEPTHNORM: - altshader = shaderstate.curshader->bemoverrides[bemoverride_prelight]; - if (!altshader) - altshader = shaderstate.depthnormshader; + case BEM_GBUFFER: + altshader = shaderstate.curshader->bemoverrides[bemoverride_gbuffer]; if (altshader && altshader->prog) { shaderstate.pendingcolourvbo = shaderstate.sourcevbo->colours[0].gl.vbo; @@ -4276,7 +4285,6 @@ static void DrawMeshes(void) #ifndef GLSLONLY else { - GL_DeSelectProgram(); while (passno < shaderstate.curshader->numpasses) { p = &shaderstate.curshader->passes[passno]; @@ -4284,7 +4292,23 @@ static void DrawMeshes(void) // if (p->flags & SHADER_PASS_DETAIL) // continue; - DrawPass(p); + if (p->prog) + { + shaderstate.pendingcolourvbo = shaderstate.sourcevbo->colours[0].gl.vbo; + shaderstate.pendingcolourpointer = shaderstate.sourcevbo->colours[0].gl.addr; + shaderstate.colourarraytype = shaderstate.sourcevbo->colours_bytes?GL_UNSIGNED_BYTE:GL_FLOAT; + + shaderstate.pendingtexcoordparts[0] = 2; + shaderstate.pendingtexcoordvbo[0] = shaderstate.sourcevbo->texcoord.gl.vbo; + shaderstate.pendingtexcoordpointer[0] = shaderstate.sourcevbo->texcoord.gl.addr; + + BE_RenderMeshProgram(shaderstate.curshader, p, p->prog); + } + else + { + GL_DeSelectProgram(); + DrawPass(p); + } } } if (shaderstate.curbatch->fog && shaderstate.curbatch->fog->shader) @@ -5339,8 +5363,9 @@ void GLBE_RenderToTexture(texid_t sourcecol, texid_t sourcedepth, texid_t destco } */ -void GLBE_DrawLightPrePass(qbyte *vis) +void GLBE_DrawLightPrePass(void) { + qboolean redefine = false; /* walls(bumps) -> normalbuffer lights+normalbuffer -> lightlevelbuffer @@ -5349,69 +5374,50 @@ void GLBE_DrawLightPrePass(qbyte *vis) normalbuffer contains depth in the alpha channel. an actual depthbuffer is also generated at this time, which is used for depth test stuff but not as a shader input. */ int oldfbo; - if (!shaderstate.initeddepthnorm) - { - shaderstate.initeddepthnorm = true; - shaderstate.depthnormshader = R_RegisterShader("lpp_depthnorm", SUF_NONE, - "{\n" - "program lpp_depthnorm\n" - "{\n" - "map $normalmap\n" - "tcgen base\n" - "}\n" - "}\n" - ); - } - if (!shaderstate.depthnormshader) - { - Con_Printf("%s requires content support\n", r_lightprepass.name); - r_lightprepass.ival = 0; - return; - } /*do portals*/ BE_SelectMode(BEM_STANDARD); GLBE_SubmitMeshes(cl.worldmodel->batches, SHADER_SORT_PORTAL, SHADER_SORT_PORTAL); - BE_SelectMode(BEM_DEPTHNORM); - if (!shaderstate.depthnormshader) + BE_SelectMode(BEM_GBUFFER); + if (!TEXVALID(shaderstate.tex_gbuf_normals) || vid.fbpwidth != shaderstate.tex_gbuf_normals->width || vid.fbpheight != shaderstate.tex_gbuf_normals->height) { - BE_SelectMode(BEM_STANDARD); - return; + if (!shaderstate.tex_gbuf_normals) + { + shaderstate.tex_gbuf_normals = Image_CreateTexture("***prepass normals***", NULL, 0); + qglGenTextures(1, &shaderstate.tex_gbuf_normals->num); + } + shaderstate.tex_gbuf_normals->width = vid.fbpwidth; + shaderstate.tex_gbuf_normals->height = vid.fbpheight; + redefine = true; + } + if (!TEXVALID(shaderstate.tex_gbuf_diffuse) || vid.fbpwidth != shaderstate.tex_gbuf_diffuse->width || vid.fbpheight != shaderstate.tex_gbuf_diffuse->height) + { + if (!shaderstate.tex_gbuf_diffuse) + { + shaderstate.tex_gbuf_diffuse = Image_CreateTexture("***prepass diffuse***", NULL, 0); + qglGenTextures(1, &shaderstate.tex_gbuf_diffuse->num); + } + shaderstate.tex_gbuf_diffuse->width = vid.fbpwidth; + shaderstate.tex_gbuf_diffuse->height = vid.fbpheight; + redefine = true; } - if (!TEXVALID(shaderstate.tex_normals)) + //something changed, redefine the textures. + if (redefine) { - shaderstate.tex_normals = Image_CreateTexture("***prepass normals***", NULL, 0); - qglGenTextures(1, &shaderstate.tex_normals->num); - r_lightprepass.modified = true; - } - if (r_lightprepass.modified) - { - GL_MTBind(0, GL_TEXTURE_2D, shaderstate.tex_normals); - qglTexImage2D(GL_TEXTURE_2D, 0, (r_lightprepass.ival==2)?GL_RGBA32F_ARB:GL_RGBA16F_ARB, vid.pixelwidth, vid.pixelheight, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); - qglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - qglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - r_lightprepass.modified = false; - } - - if (!TEXVALID(shaderstate.tex_diffuse)) - { - shaderstate.tex_diffuse = Image_CreateTexture("***prepass diffuse***", NULL, 0); - qglGenTextures(1, &shaderstate.tex_diffuse->num); - GL_MTBind(0, GL_TEXTURE_2D, shaderstate.tex_diffuse); - qglTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, vid.pixelwidth, vid.pixelheight, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); + GL_MTBind(0, GL_TEXTURE_2D, shaderstate.tex_gbuf_diffuse); + qglTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F_ARB, vid.fbpwidth, vid.fbpheight, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); qglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); qglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - GL_MTBind(0, GL_TEXTURE_2D, shaderstate.tex_normals); - qglTexImage2D(GL_TEXTURE_2D, 0, (r_lightprepass.ival==2)?GL_RGBA32F_ARB:GL_RGBA16F_ARB, vid.pixelwidth, vid.pixelheight, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); - r_lightprepass.modified = false; + GL_MTBind(0, GL_TEXTURE_2D, shaderstate.tex_gbuf_normals); + qglTexImage2D(GL_TEXTURE_2D, 0, (r_lightprepass==2)?GL_RGBA32F_ARB:GL_RGBA16F_ARB, vid.fbpwidth, vid.fbpheight, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); qglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); qglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); } /*set the FB up to draw surface info*/ - oldfbo = GLBE_FBO_Update(&shaderstate.fbo_lprepass, FBO_RB_DEPTH, &shaderstate.tex_normals, 1, r_nulltex, vid.pixelwidth, vid.pixelheight, 0); + oldfbo = GLBE_FBO_Update(&shaderstate.fbo_lprepass, FBO_RB_DEPTH, &shaderstate.tex_gbuf_normals, 1, r_nulltex, vid.fbpwidth, vid.fbpheight, 0); GL_ForceDepthWritable(); //FIXME: should probably clear colour buffer too. qglClear(GL_DEPTH_BUFFER_BIT); @@ -5426,8 +5432,8 @@ void GLBE_DrawLightPrePass(qbyte *vis) GLBE_SubmitMeshes(cl.worldmodel->batches, SHADER_SORT_OPAQUE, SHADER_SORT_OPAQUE); /*reconfigure - now drawing diffuse light info using the previous fb image as a source image*/ - GLBE_FBO_Sources(shaderstate.tex_normals, r_nulltex); - GLBE_FBO_Update(&shaderstate.fbo_lprepass, FBO_RB_DEPTH, &shaderstate.tex_diffuse, 1, r_nulltex, vid.pixelwidth, vid.pixelheight, 0); + GLBE_FBO_Sources(shaderstate.tex_gbuf_normals, r_nulltex); + GLBE_FBO_Update(&shaderstate.fbo_lprepass, FBO_RB_DEPTH, &shaderstate.tex_gbuf_diffuse, 1, r_nulltex, vid.fbpwidth, vid.fbpheight, 0); BE_SelectMode(BEM_STANDARD); qglClearColor (0,0,0,1); @@ -5439,8 +5445,9 @@ void GLBE_DrawLightPrePass(qbyte *vis) /*final reconfigure - now drawing final surface data onto true framebuffer*/ GLBE_FBO_Pop(oldfbo); - GLBE_FBO_Sources(shaderstate.tex_diffuse, r_nulltex); - qglDrawBuffer(GL_BACK); + GLBE_FBO_Sources(shaderstate.tex_gbuf_diffuse, r_nulltex); + if (!oldfbo) + qglDrawBuffer(GL_BACK); /*now draw the postlight passes (this includes blended stuff which will NOT be lit)*/ GLBE_SelectEntity(&r_worldentity); @@ -5449,14 +5456,14 @@ void GLBE_DrawLightPrePass(qbyte *vis) #ifdef RTLIGHTS /*regular lighting now*/ GLBE_SelectEntity(&r_worldentity); - Sh_DrawLights(vis); + Sh_DrawLights(r_refdef.scenevis); #endif GLBE_FBO_Sources(r_nulltex, r_nulltex); qglClearColor (1,0,0,1); } -void GLBE_DrawWorld (batch_t **worldbatches, qbyte *vis) +void GLBE_DrawWorld (batch_t **worldbatches) { #ifdef RTLIGHTS extern cvar_t r_shadow_realtime_world, r_shadow_realtime_world_lightmaps; @@ -5535,9 +5542,9 @@ void GLBE_DrawWorld (batch_t **worldbatches, qbyte *vis) // shaderstate.identitylightmap *= 1<surf_first; - BE_SelectDLight(l, l->color, l->axis, 0); + int lightflags = batch->surf_count; + + BE_SelectDLight(l, l->color, l->axis, lightflags); + if (lightflags & LSHADER_SMAP) + { + if (!Sh_GenerateShadowMap(l)) + { + batch->meshes = 0; + return; + } + BE_SelectEntity(&r_worldentity); + BE_SelectMode(BEM_STANDARD); + } if (!R_BuildDlightMesh (l, 2, 1, 2)) { int i; static vec2_t s[4] = {{1, -1}, {-1, -1}, {-1, 1}, {1, 1}}; - batch->flags |= BEF_FORCENODEPTH; for (i = 0; i < 4; i++) { VectorMA(r_origin, 32, vpn, flashblend_vcoords[i]); @@ -552,21 +564,37 @@ void R_GenDlightBatches(batch_t *batches[]) int i, j, sort; dlight_t *l; batch_t *b; - if (!r_lightprepass.ival) + int lmode; + if (!r_lightprepass) return; - if (!lpplight_shader) - lpplight_shader = R_RegisterShader("lpp_light", SUF_NONE, + if (!lpplight_shader[0]) + { + lpplight_shader[0] = R_RegisterShader("lpp_light", SUF_NONE, "{\n" "program lpp_light\n" "{\n" "map $sourcecolour\n" "blendfunc gl_one gl_one\n" + "nodepthtest\n" "}\n" "surfaceparm nodlight\n" "lpp_light\n" "}\n" ); + lpplight_shader[LSHADER_SMAP] = R_RegisterShader("lpp_light#PCF", SUF_NONE, + "{\n" + "program lpp_light\n" + "{\n" + "map $sourcecolour\n" + "blendfunc gl_one gl_one\n" + "nodepthtest\n" + "}\n" + "surfaceparm nodlight\n" + "lpp_light\n" + "}\n" + ); + } l = cl_dlights+rtlights_first; for (i=rtlights_first; iorigin, l->radius)) continue; + lmode = 0; + if (!(((i >= RTL_FIRST)?!r_shadow_realtime_world_shadows.ival:!r_shadow_realtime_dlight_shadows.ival) || l->flags & LFLAG_NOSHADOWS)) + lmode |= LSHADER_SMAP; +// if (TEXLOADED(l->cubetexture)) +// lmode |= LSHADER_CUBE; + b = BE_GetTempBatch(); if (!b) return; b->flags = 0; - sort = lpplight_shader->sort; + b->shader = lpplight_shader[lmode]; + sort = b->shader->sort; b->buildmeshes = R_GenDlightMesh; b->ent = &r_worldentity; b->mesh = NULL; @@ -590,10 +625,10 @@ void R_GenDlightBatches(batch_t *batches[]) b->meshes = 1; b->skin = NULL; b->texture = NULL; - b->shader = lpplight_shader; for (j = 0; j < MAXRLIGHTMAPS; j++) b->lightmap[j] = -1; b->surf_first = i; + b->surf_count = lmode; b->flags |= BEF_NOSHADOWS; b->vbo = NULL; b->next = batches[sort]; diff --git a/engine/gl/gl_rmain.c b/engine/gl/gl_rmain.c index 690f356c7..125d53807 100644 --- a/engine/gl/gl_rmain.c +++ b/engine/gl/gl_rmain.c @@ -1882,6 +1882,8 @@ void GLR_RenderView (void) } else if ((r_refdef.flags & (RDF_ALLPOSTPROC)) || renderscale != 1) { + unsigned int rtflags = IF_NOMIPMAP|IF_CLAMP|IF_RENDERTARGET; + r_refdef.flags |= RDF_RENDERSCALE; //the game needs to be drawn to a texture for post processing @@ -1896,8 +1898,19 @@ void GLR_RenderView (void) vid.fbpheight = (r_refdef.vrect.height * vid.pixelheight) / vid.height; } - vid.fbpwidth *= renderscale; - vid.fbpheight *= renderscale; + if (renderscale < 0) + { + renderscale = -renderscale; + rtflags |= IF_NEAREST; + vid.fbpwidth *= renderscale; + vid.fbpheight *= renderscale; + } + else + { + rtflags |= IF_LINEAR; + vid.fbpwidth *= renderscale; + vid.fbpheight *= renderscale; + } //well... err... meh. vid.fbpwidth = bound(1, vid.fbpwidth, sh_config.texture_maxsize); @@ -1906,7 +1919,7 @@ void GLR_RenderView (void) vid.fbvwidth = vid.fbpwidth; vid.fbvheight = vid.fbpheight; - sourcetex = R2D_RT_Configure("rt/$lastgameview", vid.fbpwidth, vid.fbpheight, /*(r_refdef.flags&RDF_BLOOM)?TF_RGBA16F:*/TF_RGBA32, RT_IMAGEFLAGS); + sourcetex = R2D_RT_Configure("rt/$lastgameview", vid.fbpwidth, vid.fbpheight, /*(r_refdef.flags&RDF_BLOOM)?TF_RGBA16F:*/TF_RGBA32, rtflags); oldfbo = GLBE_FBO_Update(&fbo_gameview, FBO_RB_DEPTH, &sourcetex, 1, r_nulltex, vid.fbpwidth, vid.fbpheight, 0); dofbo = true; diff --git a/engine/gl/gl_shader.c b/engine/gl/gl_shader.c index 69c85c382..3c4334737 100644 --- a/engine/gl/gl_shader.c +++ b/engine/gl/gl_shader.c @@ -231,6 +231,7 @@ static struct vec3_t refractcolour; vec3_t reflectcolour; float wateralpha; + qboolean droppass; } parsestate; typedef struct shaderkey_s @@ -292,13 +293,13 @@ static qboolean Shader_EvaluateCondition(shader_t *shader, char **ptr) if (*token == '#') conditiontrue = conditiontrue == !!Shader_FloatArgument(shader, token); else if (!Q_stricmp(token, "lpp")) - conditiontrue = conditiontrue == r_lightprepass.ival; + conditiontrue = conditiontrue == r_lightprepass; else if (!Q_stricmp(token, "lightmap")) conditiontrue = conditiontrue == !r_fullbright.value; else if (!Q_stricmp(token, "deluxmap")) conditiontrue = conditiontrue == r_deluxemapping; else if (!Q_stricmp(token, "softwarebanding")) - conditiontrue = conditiontrue == r_softwarebanding && sh_config.progs_supported; + conditiontrue = conditiontrue == r_softwarebanding; //normalmaps are generated if they're not already known. else if (!Q_stricmp(token, "normalmap")) @@ -1966,33 +1967,54 @@ static void Shader_SLProgramName (shader_t *shader, shaderpass_t *pass, char **p */ char *programbody; char *hash; + program_t *newprog; programbody = Shader_ParseBody(shader->name, ptr); if (programbody) { - shader->prog = BZ_Malloc(sizeof(*shader->prog)); - memset(shader->prog, 0, sizeof(*shader->prog)); - shader->prog->refs = 1; - if (!Shader_LoadPermutations(shader->name, shader->prog, programbody, qrtype, 0, NULL)) + newprog = BZ_Malloc(sizeof(*newprog)); + memset(newprog, 0, sizeof(*newprog)); + newprog->refs = 1; + if (!Shader_LoadPermutations(shader->name, newprog, programbody, qrtype, 0, NULL)) { - BZ_Free(shader->prog); - shader->prog = NULL; + BZ_Free(newprog); + newprog = NULL; } BZ_Free(programbody); - return; - } - - hash = strchr(shader->name, '#'); - if (hash) - { - //pass the # postfixes from the shader name onto the generic glsl to use - char newname[512]; - Q_snprintfz(newname, sizeof(newname), "%s%s", Shader_ParseExactString(ptr), hash); - shader->prog = Shader_FindGeneric(newname, qrtype); } else - shader->prog = Shader_FindGeneric(Shader_ParseExactString(ptr), qrtype); + { + hash = strchr(shader->name, '#'); + if (hash) + { + //pass the # postfixes from the shader name onto the generic glsl to use + char newname[512]; + Q_snprintfz(newname, sizeof(newname), "%s%s", Shader_ParseExactString(ptr), hash); + newprog = Shader_FindGeneric(newname, qrtype); + } + else + newprog = Shader_FindGeneric(Shader_ParseExactString(ptr), qrtype); + } + + if (pass) + { + if (pass->numMergedPasses) + { + Shader_ReleaseGeneric(newprog); + Con_DPrintf("shader %s: program defined after first texture map\n", shader->name); + } + else + { + Shader_ReleaseGeneric(pass->prog); + pass->prog = newprog; + } + } + else + { + Shader_ReleaseGeneric(shader->prog); + shader->prog = newprog; + } } static void Shader_GLSLProgramName (shader_t *shader, shaderpass_t *pass, char **ptr) @@ -2301,8 +2323,8 @@ static void Shader_BEMode(shader_t *shader, shaderpass_t *pass, char **ptr) mode = bemoverride_depthonly; else if (!Q_stricmp(token, "depthdark")) mode = bemoverride_depthdark; - else if (!Q_stricmp(token, "prelight")) - mode = bemoverride_prelight; + else if (!Q_stricmp(token, "gbuffer") || !Q_stricmp(token, "prelight")) + mode = bemoverride_gbuffer; else if (!Q_stricmp(token, "fog")) mode = bemoverride_fog; else @@ -2541,11 +2563,44 @@ static qboolean Shaderpass_MapGen (shader_t *shader, shaderpass_t *pass, char *t return true; } +shaderpass_t *Shaderpass_DefineMap(shader_t *shader, shaderpass_t *pass) +{ + //'map foo' works a bit differently when there's a program in the same pass. + //instead of corrupting the previous one, it collects multiple maps so that {prog foo;map t0;map t1; map t2; blendfunc add} can work as expected + if (pass->prog) + { + if (pass->numMergedPasses==0) + pass->numMergedPasses++; + else + { //FIXME: bounds check! + if (shader->numpasses == SHADER_PASS_MAX || shader->numpasses == SHADER_TMU_MAX) + { + Con_DPrintf (CON_WARNING "Shader %s has too many texture passes.\n", shader->name); + parsestate.droppass = true; + } +// else if (shader->numpasses == be_maxpasses) +// parsestate.droppass = true; + else + { + pass->numMergedPasses++; + shader->numpasses++; + } + pass = shader->passes+shader->numpasses-1; + memset(pass, 0, sizeof(*pass)); + } + } + else + pass->numMergedPasses = 1; + return pass; +} + static void Shaderpass_Map (shader_t *shader, shaderpass_t *pass, char **ptr) { int flags; char *token; + pass = Shaderpass_DefineMap(shader, pass); + pass->anim_frames[0] = r_nulltex; token = Shader_ParseString (ptr); @@ -2703,6 +2758,53 @@ static void Shaderpass_VideoMap (shader_t *shader, shaderpass_t *pass, char **pt #endif } +static void Shaderpass_SLProgramName (shader_t *shader, shaderpass_t *pass, char **ptr, int qrtype) +{ + /*accepts: + program + { + BLAH + } + where BLAH is both vertex+frag with #ifdefs + or + program fname + on one line. + */ + //char *programbody; + char *hash; + + /*programbody = Shader_ParseBody(shader->name, ptr); + if (programbody) + { + shader->prog = BZ_Malloc(sizeof(*shader->prog)); + memset(shader->prog, 0, sizeof(*shader->prog)); + shader->prog->refs = 1; + if (!Shader_LoadPermutations(shader->name, shader->prog, programbody, qrtype, 0, NULL)) + { + BZ_Free(shader->prog); + shader->prog = NULL; + } + + BZ_Free(programbody); + return; + }*/ + + hash = strchr(shader->name, '#'); + if (hash) + { + //pass the # postfixes from the shader name onto the generic glsl to use + char newname[512]; + Q_snprintfz(newname, sizeof(newname), "%s%s", Shader_ParseExactString(ptr), hash); + pass->prog = Shader_FindGeneric(newname, qrtype); + } + else + pass->prog = Shader_FindGeneric(Shader_ParseExactString(ptr), qrtype); +} +static void Shaderpass_ProgramName (shader_t *shader, shaderpass_t *pass, char **ptr) +{ + Shaderpass_SLProgramName(shader,pass,ptr,qrenderer); +} + static void Shaderpass_RGBGen (shader_t *shader, shaderpass_t *pass, char **ptr) { char *token; @@ -3250,6 +3352,8 @@ static shaderkey_t shaderpasskeys[] = {"alphamask", Shaderpass_AlphaMask, "rscript"},//for alienarena {"detail", Shaderpass_Detail, "rscript"}, + {"program", Shaderpass_ProgramName, "fte"}, + /*doom3 compat*/ {"blend", Shaderpass_BlendFunc, "doom3"}, {"maskcolor", Shaderpass_MaskColor, "doom3"}, @@ -3280,6 +3384,12 @@ void Shader_FreePass (shaderpass_t *pass) pass->cin = NULL; } #endif + + if (pass->prog) + { + Shader_ReleaseGeneric(pass->prog); + pass->prog = NULL; + } } void Shader_ReleaseGeneric(program_t *prog) @@ -3651,32 +3761,132 @@ void Shader_SetBlendmode (shaderpass_t *pass) pass->blendmode = (pass->texgen == T_GEN_LIGHTMAP)?PBM_OVERBRIGHT:PBM_MODULATE; } +void Shader_FixupProgPasses(shader_t *shader, shaderpass_t *pass) +{ + int i; + int maxpasses = SHADER_PASS_MAX - (pass-shader->passes); + struct + { + int gen; + unsigned int flags; + } defaulttgen[] = + { + //light + {T_GEN_SHADOWMAP, 0}, //1 + {T_GEN_LIGHTCUBEMAP, 0}, //2 + + //material + {T_GEN_DIFFUSE, SHADER_HASDIFFUSE}, //3 + {T_GEN_NORMALMAP, SHADER_HASNORMALMAP}, //4 + {T_GEN_SPECULAR, SHADER_HASGLOSS}, //5 + {T_GEN_UPPEROVERLAY, SHADER_HASTOPBOTTOM}, //6 + {T_GEN_LOWEROVERLAY, SHADER_HASTOPBOTTOM}, //7 + {T_GEN_FULLBRIGHT, SHADER_HASFULLBRIGHT}, //8 + {T_GEN_PALETTED, SHADER_HASPALETTED}, //9 + {T_GEN_REFLECTCUBE, 0}, //10 + {T_GEN_REFLECTMASK, 0}, //11 +// {T_GEN_REFLECTION, SHADER_HASREFLECT}, // +// {T_GEN_REFRACTION, SHADER_HASREFRACT}, // +// {T_GEN_REFRACTIONDEPTH, SHADER_HASREFRACTDEPTH},// +// {T_GEN_RIPPLEMAP, SHADER_HASRIPPLEMAP}, // + + //batch + {T_GEN_LIGHTMAP, SHADER_HASLIGHTMAP}, //12 + {T_GEN_DELUXMAP, 0}, //13 + //more lightmaps //14,15,16 + //mode deluxemaps //17,18,19 + }; + +#ifndef NOMEDIA + cin_t *cin = R_ShaderGetCinematic(shader); +#endif + + //if the glsl doesn't specify all samplers, just trim them. + pass->numMergedPasses = pass->prog->numsamplers; + +#ifndef NOMEDIA + if (cin && R_ShaderGetCinematic(shader) == cin) + cin = NULL; +#endif + + //if the glsl has specific textures listed, be sure to provide a pass for them. + for (i = 0; i < sizeof(defaulttgen)/sizeof(defaulttgen[0]); i++) + { + if (pass->prog->defaulttextures & (1u<numMergedPasses >= maxpasses) + { //panic... + parsestate.droppass = true; + break; + } + pass[pass->numMergedPasses].flags &= ~SHADER_PASS_DEPTHCMP; + if (defaulttgen[i].gen == T_GEN_SHADOWMAP) + pass[pass->numMergedPasses].flags |= SHADER_PASS_DEPTHCMP; +#ifndef NOMEDIA + if (!i && cin) + { + pass[pass->numMergedPasses].texgen = T_GEN_VIDEOMAP; + pass[pass->numMergedPasses].cin = cin; + cin = NULL; + } + else +#endif + { + pass[pass->numMergedPasses].texgen = defaulttgen[i].gen; +#ifndef NOMEDIA + pass[pass->numMergedPasses].cin = NULL; +#endif + } + pass->numMergedPasses++; + shader->flags |= defaulttgen[i].flags; + } + } + + //must have at least one texture. + if (!pass->numMergedPasses) + { +#ifndef NOMEDIA + pass[0].texgen = cin?T_GEN_VIDEOMAP:T_GEN_DIFFUSE; + pass[0].cin = cin; +#else + pass[0].texgen = T_GEN_DIFFUSE; +#endif + pass->numMergedPasses = 1; + } +#ifndef NOMEDIA + else if (cin) + Media_ShutdownCin(cin); +#endif + + shader->numpasses = (pass-shader->passes)+pass->numMergedPasses; +} + void Shader_Readpass (shader_t *shader, char **ptr) { char *token; shaderpass_t *pass; - qboolean ignore; static shader_t dummy; int conddepth = 0; int cond[8] = {0}; + unsigned int oldflags = shader->flags; #define COND_IGNORE 1 #define COND_IGNOREPARENT 2 #define COND_ALLOWELSE 4 if ( shader->numpasses >= SHADER_PASS_MAX ) { - ignore = true; + parsestate.droppass = true; shader = &dummy; shader->numpasses = 1; pass = shader->passes; } else { - ignore = false; + parsestate.droppass = false; pass = &shader->passes[shader->numpasses++]; } - // Set defaults + // Set defaults pass->flags = 0; pass->anim_frames[0] = r_nulltex; pass->anim_numframes = 0; @@ -3684,8 +3894,8 @@ void Shader_Readpass (shader_t *shader, char **ptr) pass->alphagen = ALPHA_GEN_IDENTITY; pass->tcgen = TC_GEN_UNSPECIFIED; pass->numtcmods = 0; - pass->numMergedPasses = 1; pass->stagetype = ST_AMBIENT; + pass->numMergedPasses = 0; if (shader->flags & SHADER_NOMIPMAPS) pass->flags |= SHADER_PASS_NOMIPMAP; @@ -3749,6 +3959,10 @@ void Shader_Readpass (shader_t *shader, char **ptr) } } + //if there was no texgen, then its too late now. + if (!pass->numMergedPasses) + pass->numMergedPasses = 1; + if (conddepth) { Con_Printf("if statements without endif in shader %s\n", shader->name); @@ -3757,7 +3971,7 @@ void Shader_Readpass (shader_t *shader, char **ptr) if (pass->tcgen == TC_GEN_UNSPECIFIED) pass->tcgen = TC_GEN_BASE; - if (!ignore) + if (!parsestate.droppass) { switch(pass->stagetype) { @@ -3770,29 +3984,24 @@ void Shader_Readpass (shader_t *shader, char **ptr) case ST_BUMPMAP: if (pass->texgen == T_GEN_SINGLEMAP) shader->defaulttextures->bump = pass->anim_frames[0]; - ignore = true; //fixme: scrolling etc may be important. but we're not doom3. + parsestate.droppass = true; //fixme: scrolling etc may be important. but we're not doom3. break; case ST_SPECULARMAP: if (pass->texgen == T_GEN_SINGLEMAP) shader->defaulttextures->specular = pass->anim_frames[0]; - ignore = true; //fixme: scrolling etc may be important. but we're not doom3. + parsestate.droppass = true; //fixme: scrolling etc may be important. but we're not doom3. break; } } // check some things - if (ignore) - { - Shader_FreePass (pass); - shader->numpasses--; - return; - } - if ((pass->shaderbits&SBITS_BLEND_BITS) == (SBITS_SRCBLEND_ONE|SBITS_DSTBLEND_ZERO)) - { - pass->shaderbits |= SBITS_MISC_DEPTHWRITE; - shader->flags |= SHADER_DEPTHWRITE; - } + if (!parsestate.droppass) + if ((pass->shaderbits&SBITS_BLEND_BITS) == (SBITS_SRCBLEND_ONE|SBITS_DSTBLEND_ZERO)) + { + pass->shaderbits |= SBITS_MISC_DEPTHWRITE; + shader->flags |= SHADER_DEPTHWRITE; + } switch (pass->rgbgen) { @@ -3829,6 +4038,21 @@ void Shader_Readpass (shader_t *shader, char **ptr) pass->shaderbits &= ~SBITS_MISC_DEPTHWRITE; } */ + + //if this pass specified a program, make sure it has all the textures that the program requires + if (!parsestate.droppass && pass->prog) + Shader_FixupProgPasses(shader, pass); + + if (parsestate.droppass) + { + while (pass->numMergedPasses > 0) + { + Shader_FreePass (pass+--pass->numMergedPasses); + shader->numpasses--; + } + shader->flags = oldflags; + return; + } } static qboolean Shader_Parsetok (shader_t *shader, shaderpass_t *pass, shaderkey_t *keys, char *token, char **ptr) @@ -3886,6 +4110,10 @@ void Shader_SetPassFlush (shaderpass_t *pass, shaderpass_t *pass2) return; } + //don't merge passes if they're got their own programs. + if (pass->prog || pass2->prog) + return; + /*identity alpha is required for merging*/ if (pass->alphagen != ALPHA_GEN_IDENTITY || pass2->alphagen != ALPHA_GEN_IDENTITY) return; @@ -4423,7 +4651,7 @@ done:; break; pass = s->passes + i; - for (j = 1; j < s->numpasses-i && j == i + pass->numMergedPasses && j < be_maxpasses; j++) + for (j = i + pass->numMergedPasses; j < s->numpasses-i && j == i + pass->numMergedPasses && j < be_maxpasses; j++) Shader_SetPassFlush (pass, pass + j); i += pass->numMergedPasses; @@ -5004,7 +5232,7 @@ void Shader_DefaultBSPLM(const char *shortname, shader_t *s, const void *args) if (!builtin && r_lightmap.ival) builtin = ( "{\n" - "program drawflat_wall\n" + "fte_program drawflat_wall\n" "{\n" "map $lightmap\n" "tcgen lightmap\n" @@ -5016,7 +5244,7 @@ void Shader_DefaultBSPLM(const char *shortname, shader_t *s, const void *args) if (!builtin && r_drawflat.ival) builtin = ( "{\n" - "program drawflat_wall\n" + "fte_program drawflat_wall\n" "{\n" "map $lightmap\n" "tcgen lightmap\n" @@ -5026,29 +5254,34 @@ void Shader_DefaultBSPLM(const char *shortname, shader_t *s, const void *args) ); -#ifdef GLQUAKE - if (qrenderer == QR_OPENGL) + if (!builtin && r_lightprepass) { - if (!builtin && r_lightprepass.ival) - { - builtin = ( + builtin = ( + "{\n" + "fte_program lpp_wall\n" + "{\n" + "map $sourcecolour\n" + "}\n" + + //this is drawn during the gbuffer pass to prepare it + "fte_bemode gbuffer\n" + "{\n" + "fte_program lpp_depthnorm\n" "{\n" - "program lpp_wall\n" - "{\n" - "map $sourcecolour\n" - "}\n" + "map $normalmap\n" + "tcgen base\n" "}\n" - ); - } + "}\n" + "}\n" + ); } -#endif if (!builtin && ((sh_config.progs_supported && qrenderer == QR_OPENGL) || sh_config.progs_required)) { builtin = ( "{\n" - "program defaultwall\n" - //FIXME: these maps are a legacy thing, and could be removed if third-party glsl properly contains s_diffuse + "fte_program defaultwall\n" "{\n" + //FIXME: these maps are a legacy thing, and could be removed if third-party glsl properly contains s_diffuse "map $diffuse\n" "}\n" "{\n" @@ -5111,7 +5344,7 @@ void Shader_DefaultBSPLM(const char *shortname, shader_t *s, const void *args) Shader_DefaultScript(shortname, s, builtin); - if (r_lightprepass.ival) + if (r_lightprepass) s->flags |= SHADER_HASNORMALMAP; } diff --git a/engine/gl/gl_shadow.c b/engine/gl/gl_shadow.c index 41ff4af9c..b48d19b21 100644 --- a/engine/gl/gl_shadow.c +++ b/engine/gl/gl_shadow.c @@ -2408,7 +2408,7 @@ qboolean Sh_GenShadowMap (dlight_t *l, vec3_t axis[3], qbyte *lvis, int smsize) } else { - shadowmap[isspot] = Image_CreateTexture("***shadowmap2dcube***", NULL, 0); + shadowmap[isspot] = Image_CreateTexture("***shadowmap2domni***", NULL, 0); qglGenTextures(1, &shadowmap[isspot]->num); GL_MTBind(0, GL_TEXTURE_2D, shadowmap[isspot]); #ifdef DBG_COLOURNOTDEPTH @@ -2530,6 +2530,84 @@ qboolean Sh_GenShadowMap (dlight_t *l, vec3_t axis[3], qbyte *lvis, int smsize) return true; } +qboolean Sh_GenerateShadowMap(dlight_t *l) +{ + qboolean isspot; + int smsize; + qbyte *vvis = r_refdef.scenevis; + qbyte *lvis; + + +/* if (Sh_ScissorForBox(mins, maxs, &rect)) + { + RQuantAdd(RQUANT_RTLIGHT_CULL_SCISSOR, 1); + return; + }*/ + + if (vvis) + { + if (!l->rebuildcache && l->worldshadowmesh) + { + lvis = l->worldshadowmesh->litleaves; + //fixme: check head node first? + if (!Sh_LeafInView(l->worldshadowmesh->litleaves, vvis)) + { + RQuantAdd(RQUANT_RTLIGHT_CULL_PVS, 1); + return false; + } + } + else + { + int clus; + clus = cl.worldmodel->funcs.ClusterForPoint(cl.worldmodel, l->origin); + lvis = cl.worldmodel->funcs.ClusterPVS(cl.worldmodel, clus, lvisb, sizeof(lvisb)); + //FIXME: surely we can use the phs for this? + + if (!Sh_VisOverlaps(lvis, vvis)) //The two viewing areas do not intersect. + { + RQuantAdd(RQUANT_RTLIGHT_CULL_PVS, 1); + return false; + } + } + } + else + lvis = NULL; + + + + isspot = l->fov != 0; + if (isspot) + smsize = SHADOWMAP_SIZE; + else + { + //Stolen from DP. Actually, LH pasted it to me in IRC. + vec3_t nearestpoint; + vec3_t d; + float distance, lodlinear; + nearestpoint[0] = bound(l->origin[0]-l->radius, r_origin[0], l->origin[0]+l->radius); + nearestpoint[1] = bound(l->origin[1]-l->radius, r_origin[1], l->origin[1]+l->radius); + nearestpoint[2] = bound(l->origin[2]-l->radius, r_origin[2], l->origin[2]+l->radius); + VectorSubtract(nearestpoint, r_origin, d); + distance = VectorLength(d); + lodlinear = (l->radius * r_shadow_shadowmapping_precision.value) / sqrt(max(1.0f, distance / l->radius)); + smsize = bound(16, lodlinear, SHADOWMAP_SIZE); + } + +#ifdef D3D11QUAKE + if (qrenderer == QR_DIRECT3D11) + D3D11BE_SetupForShadowMap(l, isspot, isspot?smsize:smsize*3, isspot?smsize:smsize*2, (smsize-4) / (float)SHADOWMAP_SIZE); +#endif +#ifdef VKQUAKE + if (qrenderer == QR_VULKAN) + VKBE_SetupForShadowMap(l, isspot, isspot?smsize:smsize*3, isspot?smsize:smsize*2, (smsize-4) / (float)SHADOWMAP_SIZE); +#endif + + //fixme: light rotation + if (!Sh_GenShadowMap(l, l->axis, lvis, smsize)) + return false; //didn't need to do anything + return true; +} + static void Sh_DrawShadowMapLight(dlight_t *l, vec3_t colour, vec3_t axis[3], qbyte *vvis) { vec3_t mins, maxs; @@ -3588,6 +3666,9 @@ void Sh_DrawLights(qbyte *vis) Sh_PreGenerateLights(); } + if (r_lightprepass) + return; + if (!r_shadow_realtime_world.ival && !r_shadow_realtime_dlight.ival) { return; diff --git a/engine/gl/r_bishaders.h b/engine/gl/r_bishaders.h index d259ffeab..20703e6a1 100644 --- a/engine/gl/r_bishaders.h +++ b/engine/gl/r_bishaders.h @@ -6741,17 +6741,24 @@ YOU SHOULD NOT EDIT THIS FILE BY HAND {QR_OPENGL, 110, "lpp_depthnorm", "!!permu BUMP\n" "!!permu SKELETAL\n" +"!!cvarf r_glsl_offsetmapping_scale\n" //light pre-pass rendering (defered lighting) //this is the initial pass, that draws the surface normals and depth to the initial colour buffer +"#include \"sys/defs.h\"\n" + +"#if defined(OFFSETMAPPING)\n" +"varying vec3 eyevector;\n" +"#endif\n" + "varying vec3 norm, tang, bitang;\n" "#if defined(BUMP)\n" "varying vec2 tc;\n" "#endif\n" "#ifdef VERTEX_SHADER\n" "#include \"sys/skeletal.h\"\n" -"attribute vec2 v_texcoord;\n" + "void main()\n" "{\n" "#if defined(BUMP)\n" @@ -6760,22 +6767,35 @@ YOU SHOULD NOT EDIT THIS FILE BY HAND "#else\n" "gl_Position = skeletaltransform_n(norm);\n" "#endif\n" + +"#if defined(OFFSETMAPPING)\n" +"vec3 eyeminusvertex = e_eyepos - v_position.xyz;\n" +"eyevector.x = dot(eyeminusvertex, v_svector.xyz);\n" +"eyevector.y = dot(eyeminusvertex, v_tvector.xyz);\n" +"eyevector.z = dot(eyeminusvertex, v_normal.xyz);\n" +"#endif\n" "}\n" "#endif\n" "#ifdef FRAGMENT_SHADER\n" -"#if defined(BUMP)\n" -"uniform sampler2D s_t0;\n" +"#ifdef OFFSETMAPPING\n" +"#include \"sys/offsetmapping.h\"\n" "#endif\n" "void main()\n" "{\n" +//adjust texture coords for offsetmapping +"#ifdef OFFSETMAPPING\n" +"vec2 tcoffsetmap = offsetmap(s_normalmap, tc, eyevector);\n" +"#define tc tcoffsetmap\n" +"#endif\n" + "vec3 onorm;\n" "#if defined(BUMP)\n" -"vec3 bm = 2.0*texture2D(s_t0, tc).xyz - 1.0;\n" +"vec3 bm = 2.0*texture2D(s_normalmap, tc).xyz - 1.0;\n" "onorm = normalize(bm.x * tang + bm.y * bitang + bm.z * norm);\n" "#else\n" "onorm = norm;\n" "#endif\n" -"gl_FragColor = vec4(onorm.xyz, gl_FragCoord.z / gl_FragCoord.w);\n" +"gl_FragColor = vec4(onorm.xyz, gl_FragCoord.z);\n" "}\n" "#endif\n" }, @@ -6787,6 +6807,9 @@ YOU SHOULD NOT EDIT THIS FILE BY HAND //you can blame Electro for much of the maths in here. //fixme: no fog +//s_t0 is the normals and depth +//output should be amount of light hitting the surface. + "varying vec4 tf;\n" "#ifdef VERTEX_SHADER\n" "void main()\n" @@ -6796,27 +6819,147 @@ YOU SHOULD NOT EDIT THIS FILE BY HAND "}\n" "#endif\n" "#ifdef FRAGMENT_SHADER\n" -"uniform sampler2D s_t0;\n" +"uniform sampler2D s_t0; //norm.xyz, depth\n" "uniform vec3 l_lightposition;\n" "uniform mat4 m_invviewprojection;\n" "uniform vec3 l_lightcolour;\n" "uniform float l_lightradius;\n" +"uniform mat4 l_cubematrix;\n" + + + + + +"#ifdef PCF\n" +"#define USE_ARB_SHADOW\n" +"#ifndef USE_ARB_SHADOW\n" +//fall back on regular samplers if we must +"#define sampler2DShadow sampler2D\n" +"#endif\n" +"uniform sampler2DShadow s_shadowmap;\n" + +"uniform vec4 l_shadowmapproj; //light projection matrix info\n" +"uniform vec2 l_shadowmapscale; //xy are the texture scale, z is 1, w is the scale.\n" +"vec3 ShadowmapCoord(vec4 cubeproj)\n" +"{\n" +"#ifdef SPOT\n" +//bias it. don't bother figuring out which side or anything, its not needed +//l_projmatrix contains the light's projection matrix so no other magic needed +"return ((cubeproj.xyz-vec3(0.0,0.0,0.015))/cubeproj.w + vec3(1.0, 1.0, 1.0)) * vec3(0.5, 0.5, 0.5);\n" +//#elif defined(CUBESHADOW) +// vec3 shadowcoord = vshadowcoord.xyz / vshadowcoord.w; +// #define dosamp(x,y) shadowCube(s_shadowmap, shadowcoord + vec2(x,y)*texscale.xy).r +"#else\n" +//figure out which axis to use +//texture is arranged thusly: +//forward left up +//back right down +"vec3 dir = abs(cubeproj.xyz);\n" +//assume z is the major axis (ie: forward from the light) +"vec3 t = cubeproj.xyz;\n" +"float ma = dir.z;\n" +"vec3 axis = vec3(0.5/3.0, 0.5/2.0, 0.5);\n" +"if (dir.x > ma)\n" +"{\n" +"ma = dir.x;\n" +"t = cubeproj.zyx;\n" +"axis.x = 0.5;\n" +"}\n" +"if (dir.y > ma)\n" +"{\n" +"ma = dir.y;\n" +"t = cubeproj.xzy;\n" +"axis.x = 2.5/3.0;\n" +"}\n" +//if the axis is negative, flip it. +"if (t.z > 0.0)\n" +"{\n" +"axis.y = 1.5/2.0;\n" +"t.z = -t.z;\n" +"}\n" + +//we also need to pass the result through the light's projection matrix too +//the 'matrix' we need only contains 5 actual values. and one of them is a -1. So we might as well just use a vec4. +//note: the projection matrix also includes scalers to pinch the image inwards to avoid sampling over borders, as well as to cope with non-square source image +//the resulting z is prescaled to result in a value between -0.5 and 0.5. +//also make sure we're in the right quadrant type thing +"return axis + ((l_shadowmapproj.xyz*t.xyz + vec3(0.0, 0.0, l_shadowmapproj.w)) / -t.z);\n" +"#endif\n" +"}\n" + +"float ShadowmapFilter(vec4 vtexprojcoord)\n" +"{\n" +"vec3 shadowcoord = ShadowmapCoord(vtexprojcoord);\n" + +"#if 0//def GL_ARB_texture_gather\n" +"vec2 ipart, fpart;\n" +"#define dosamp(x,y) textureGatherOffset(s_shadowmap, ipart.xy, vec2(x,y)))\n" +"vec4 tl = step(shadowcoord.z, dosamp(-1.0, -1.0));\n" +"vec4 bl = step(shadowcoord.z, dosamp(-1.0, 1.0));\n" +"vec4 tr = step(shadowcoord.z, dosamp(1.0, -1.0));\n" +"vec4 br = step(shadowcoord.z, dosamp(1.0, 1.0));\n" +//we now have 4*4 results, woo +//we can just average them for 1/16th precision, but that's still limited graduations +//the middle four pixels are 'full strength', but we interpolate the sides to effectively give 3*3 +"vec4 col = vec4(tl.ba, tr.ba) + vec4(bl.rg, br.rg) + //middle two rows are full strength\n" +"mix(vec4(tl.rg, tr.rg), vec4(bl.ba, br.ba), fpart.y); //top+bottom rows\n" +"return dot(mix(col.rgb, col.agb, fpart.x), vec3(1.0/9.0)); //blend r+a, gb are mixed because its pretty much free and gives a nicer dot instruction instead of lots of adds.\n" + +"#else\n" +"#ifdef USE_ARB_SHADOW\n" +//with arb_shadow, we can benefit from hardware acclerated pcf, for smoother shadows +"#define dosamp(x,y) shadow2D(s_shadowmap, shadowcoord.xyz + (vec3(x,y,0.0)*l_shadowmapscale.xyx)).r\n" +"#else\n" +//this will probably be a bit blocky. +"#define dosamp(x,y) float(texture2D(s_shadowmap, shadowcoord.xy + (vec2(x,y)*l_shadowmapscale.xy)).r >= shadowcoord.z)\n" +"#endif\n" +"float s = 0.0;\n" +"#if r_glsl_pcf >= 1 && r_glsl_pcf < 5\n" +"s += dosamp(0.0, 0.0);\n" +"return s;\n" +"#elif r_glsl_pcf >= 5 && r_glsl_pcf < 9\n" +"s += dosamp(-1.0, 0.0);\n" +"s += dosamp(0.0, -1.0);\n" +"s += dosamp(0.0, 0.0);\n" +"s += dosamp(0.0, 1.0);\n" +"s += dosamp(1.0, 0.0);\n" +"return s/5.0;\n" +"#else\n" +"s += dosamp(-1.0, -1.0);\n" +"s += dosamp(-1.0, 0.0);\n" +"s += dosamp(-1.0, 1.0);\n" +"s += dosamp(0.0, -1.0);\n" +"s += dosamp(0.0, 0.0);\n" +"s += dosamp(0.0, 1.0);\n" +"s += dosamp(1.0, -1.0);\n" +"s += dosamp(1.0, 0.0);\n" +"s += dosamp(1.0, 1.0);\n" +"return s/9.0;\n" +"#endif\n" +"#endif\n" +"}\n" +"#else\n" +"float ShadowmapFilter(vec4 vtexprojcoord)\n" +"{\n" +"return 1.0;\n" +"}\n" +"#endif\n" + + + + + "vec3 calcLightWorldPos(vec2 screenPos, float depth)\n" "{\n" -"vec4 pos;\n" -"pos.x = screenPos.x;\n" -"pos.y = screenPos.y;\n" -"pos.z = depth;\n" -"pos.w = 1.0;\n" -"pos = m_invviewprojection * pos;\n" +"vec4 pos = m_invviewprojection * vec4(screenPos.xy, (depth*2.0)-1.0, 1.0);\n" "return pos.xyz / pos.w;\n" "}\n" "void main ()\n" "{\n" "vec3 lightColour = l_lightcolour.rgb;\n" -"float lightIntensity = 1.0;\n" +"float lightIntensity = 1.0;\n" "float lightAttenuation = l_lightradius; // fixme: just use the light radius for now, use better near/far att math separately once working\n" -"float radiusFar = l_lightradius;\n" +"float radiusFar = l_lightradius;\n" "float radiusNear = l_lightradius*0.5;\n" "vec2 fc;\n" @@ -6828,54 +6971,86 @@ YOU SHOULD NOT EDIT THIS FILE BY HAND /* calc where the wall that generated this sample came from */ "vec3 worldPos = calcLightWorldPos(fc, depth);\n" +/*we need to know the cube projection (for both cubemaps+shadows)*/ +"vec4 cubeaxis = l_cubematrix*vec4(worldPos.xyz, 1.0);\n" + /*calc diffuse lighting term*/ "vec3 lightDir = l_lightposition - worldPos;\n" "float zdiff = 1.0 - clamp(length(lightDir) / lightAttenuation, 0.0, 1.0);\n" "float atten = (radiusFar * zdiff) / (radiusFar - radiusNear);\n" "atten = pow(atten, 2.0);\n" "lightDir = normalize(lightDir);\n" -"float nDotL = dot(norm, lightDir) * atten;\n" -"float lightDiffuse = max(0.0, nDotL);\n" +"float nDotL = dot(norm, lightDir);\n" +"float lightDiffuse = max(0.0, nDotL) * atten;\n" -"gl_FragColor = vec4(lightDiffuse * (lightColour * lightIntensity), 1.0);\n" +//fixme: apply fog +//fixme: output a specular term +//fixme: cubemap filters + +"gl_FragColor = vec4(lightDiffuse * (lightColour * lightIntensity) * ShadowmapFilter(cubeaxis), 1.0);\n" "}\n" "#endif\n" }, #endif #ifdef GLQUAKE {QR_OPENGL, 110, "lpp_wall", +"!!permu BUMP //for offsetmapping rather than bumpmapping (real bumps are handled elsewhere)\n" +"!!cvarf r_glsl_offsetmapping_scale\n" + //the final defered lighting pass. //the lighting values were written to some render target, which is fed into this shader, and now we draw all the wall textures with it. +"#include \"sys/defs.h\"\n" + +"#if defined(OFFSETMAPPING)\n" +"varying vec3 eyevector;\n" +"#endif\n" + + "varying vec2 tc, lm;\n" "varying vec4 tf;\n" "#ifdef VERTEX_SHADER\n" -"attribute vec2 v_texcoord;\n" -"attribute vec2 v_lmcoord;\n" "void main ()\n" "{\n" "tc = v_texcoord;\n" "lm = v_lmcoord;\n" "gl_Position = tf = ftetransform();\n" + +"#if defined(OFFSETMAPPING)\n" +"vec3 eyeminusvertex = e_eyepos - v_position.xyz;\n" +"eyevector.x = dot(eyeminusvertex, v_svector.xyz);\n" +"eyevector.y = dot(eyeminusvertex, v_tvector.xyz);\n" +"eyevector.z = dot(eyeminusvertex, v_normal.xyz);\n" +"#endif\n" "}\n" "#endif\n" "#ifdef FRAGMENT_SHADER\n" -"uniform sampler2D s_t0;\n" -"uniform sampler2D s_t1;\n" -"uniform sampler2D s_t2;\n" -"uniform vec4 e_lmscale;\n" +"uniform sampler2D s_t0; //light gbuffer\n" +"#ifdef OFFSETMAPPING\n" +"#include \"sys/offsetmapping.h\"\n" +"#endif\n" "void main ()\n" "{\n" +//adjust texture coords for offsetmapping +"#ifdef OFFSETMAPPING\n" +"vec2 tcoffsetmap = offsetmap(s_normalmap, tc, eyevector);\n" +"#define tc tcoffsetmap\n" +"#endif\n" + "vec2 nst;\n" "nst = tf.xy / tf.w;\n" "nst = (1.0 + nst) / 2.0;\n" -"vec4 l = texture2D(s_t0, nst)*5.0;\n" -"vec4 c = texture2D(s_t1, tc);\n" -"vec3 lmsamp = texture2D(s_t2, lm).rgb*e_lmscale.rgb;\n" +"vec4 l = texture2D(s_t0, nst);\n" +"vec4 c = texture2D(s_diffuse, tc);\n" +//fixme: top+bottom should add upper+lower colours to c here +"vec3 lmsamp = texture2D(s_lightmap, lm).rgb*e_lmscale.rgb;\n" +//fixme: fog the legacy lightmap data "vec3 diff = l.rgb;\n" -"vec3 chrom = diff / (0.001 + dot(diff, vec3(0.3, 0.59, 0.11)));\n" -"vec3 spec = chrom * l.a;\n" +// vec3 chrom = diff / (0.001 + dot(diff, vec3(0.3, 0.59, 0.11))); +// vec3 spec = chrom * l.a; +//fixme: do specular somehow "gl_FragColor = vec4((diff + lmsamp) * c.xyz, 1.0);\n" +//fixme: fullbrights should add to the rgb value "}\n" "#endif\n" }, diff --git a/engine/gl/shader.h b/engine/gl/shader.h index 4799e107c..81bc69554 100644 --- a/engine/gl/shader.h +++ b/engine/gl/shader.h @@ -172,6 +172,8 @@ enum typedef struct shaderpass_s { int numMergedPasses; + struct programshared_s *prog; + #ifndef NOMEDIA struct cin_s *cin; #endif @@ -511,7 +513,7 @@ enum bemoverride_crepuscular = LSHADER_MODES, //either black (non-sky) or a special crepuscular_sky shader bemoverride_depthonly, //depth masked. replace if you want alpha test. bemoverride_depthdark, //itself or a pure-black shader. replace for alpha test. - bemoverride_prelight, //prelighting + bemoverride_gbuffer, //prelighting bemoverride_fog, //post-render volumetric fog bemoverride_max }; @@ -741,7 +743,7 @@ batch_t *GLBE_GetTempBatch(void); void GLBE_GenBrushModelVBO(model_t *mod); void GLBE_ClearVBO(vbo_t *vbo); void GLBE_UploadAllLightmaps(void); -void GLBE_DrawWorld (batch_t **worldbatches, qbyte *vis); +void GLBE_DrawWorld (batch_t **worldbatches); qboolean GLBE_LightCullModel(vec3_t org, model_t *model); void GLBE_SelectEntity(entity_t *ent); qboolean GLBE_SelectDLight(dlight_t *dl, vec3_t colour, vec3_t axis[3], unsigned int lmode); @@ -771,7 +773,7 @@ batch_t *D3D9BE_GetTempBatch(void); void D3D9BE_GenBrushModelVBO(model_t *mod); void D3D9BE_ClearVBO(vbo_t *vbo); void D3D9BE_UploadAllLightmaps(void); -void D3D9BE_DrawWorld (batch_t **worldbatches, qbyte *vis); +void D3D9BE_DrawWorld (batch_t **worldbatches); qboolean D3D9BE_LightCullModel(vec3_t org, model_t *model); void D3D9BE_SelectEntity(entity_t *ent); qboolean D3D9BE_SelectDLight(dlight_t *dl, vec3_t colour, vec3_t axis[3], unsigned int lmode); @@ -795,7 +797,7 @@ batch_t *D3D11BE_GetTempBatch(void); void D3D11BE_GenBrushModelVBO(model_t *mod); void D3D11BE_ClearVBO(vbo_t *vbo); void D3D11BE_UploadAllLightmaps(void); -void D3D11BE_DrawWorld (batch_t **worldbatches, qbyte *vis); +void D3D11BE_DrawWorld (batch_t **worldbatches); qboolean D3D11BE_LightCullModel(vec3_t org, model_t *model); void D3D11BE_SelectEntity(entity_t *ent); qboolean D3D11BE_SelectDLight(dlight_t *dl, vec3_t colour, vec3_t axis[3], unsigned int lmode); diff --git a/engine/qclib/Makefile b/engine/qclib/Makefile index 6668beb7f..80ecda8f2 100644 --- a/engine/qclib/Makefile +++ b/engine/qclib/Makefile @@ -25,6 +25,7 @@ ifneq ($(DEBUG),) else BASE_LDFLAGS+=-s endif +BASE_LDFLAGS+=-lz # set to "" for debugging DO_CC?=$(CC) $(BASE_CFLAGS) -o $@ -c $< $(CFLAGS) @@ -46,7 +47,7 @@ win: $(MAKE) USEGUI_CFLAGS="-DUSEGUI -DQCCONLY" R_win R_qcc: $(QCC_OBJS) $(COMMON_OBJS) $(TUI_OBJS) - $(CC) $(BASE_CFLAGS) -o fteqcc.bin -O3 $(BASE_LDFLAGS) $(QCC_OBJS) $(TUI_OBJS) $(COMMON_OBJS) + $(CC) $(BASE_CFLAGS) -o fteqcc.bin -O3 $(BASE_LDFLAGS) -lm $(QCC_OBJS) $(TUI_OBJS) $(COMMON_OBJS) qcc: $(MAKE) USEGUI_CFLAGS="" R_qcc diff --git a/engine/qclib/hash.c b/engine/qclib/hash.c index 9200af15e..0bde8a6a2 100644 --- a/engine/qclib/hash.c +++ b/engine/qclib/hash.c @@ -1,3 +1,12 @@ +#if _MSC_VER >= 1300 + #ifndef _CRT_SECURE_NO_WARNINGS + #define _CRT_SECURE_NO_WARNINGS + #endif + #ifndef _CRT_NONSTDC_NO_WARNINGS + #define _CRT_NONSTDC_NO_WARNINGS + #endif +#endif + #include "hash.h" #include #include diff --git a/engine/qclib/qcc.h b/engine/qclib/qcc.h index 097b23cc6..5641362ff 100644 --- a/engine/qclib/qcc.h +++ b/engine/qclib/qcc.h @@ -1055,6 +1055,7 @@ typedef struct qcc_cachedsourcefile_s { struct qcc_cachedsourcefile_s *next; } qcc_cachedsourcefile_t; extern qcc_cachedsourcefile_t *qcc_sourcefile; +int WriteSourceFiles(qcc_cachedsourcefile_t *filelist, int h, pbool sourceaswell, pbool legacyembed); diff --git a/engine/qclib/qcc_pr_lex.c b/engine/qclib/qcc_pr_lex.c index 7dc93eca1..5daa88356 100644 --- a/engine/qclib/qcc_pr_lex.c +++ b/engine/qclib/qcc_pr_lex.c @@ -2287,17 +2287,20 @@ void QCC_PR_LexWhitespace (pbool inhibitpreprocessor) #define MAX_FRAMES 8192 char pr_framemodelname[64]; -char pr_framemacros[MAX_FRAMES][64]; -int pr_framemacrovalue[MAX_FRAMES]; -int pr_nummacros, pr_oldmacros; -int pr_macrovalue; -int pr_savedmacro; +struct +{ + char name[64]; + int value; + const char *file; //compare to s_filen to see if its current or not +} pr_framemacro[MAX_FRAMES]; +int pr_nummacros; +int pr_macrovalue; //next value to use +int pr_savedmacro; //for sub-groups. void QCC_PR_ClearGrabMacros (pbool newfile) { if (!newfile) pr_nummacros = 0; - pr_oldmacros = pr_nummacros; pr_macrovalue = 0; pr_savedmacro = -1; } @@ -2308,17 +2311,21 @@ int QCC_PR_FindMacro (char *name) for (i=pr_nummacros-1 ; i>=0 ; i--) { - if (!STRCMP (name, pr_framemacros[i])) + if (!STRCMP (name, pr_framemacro[i].name)) { - return pr_framemacrovalue[i]; + if (pr_framemacro[i].file != s_filen) + QCC_PR_ParseWarning(WARN_DUPLICATEMACRO, "Stale macro used (%s, defined in %s)", pr_token, pr_framemacro[i].file); + return pr_framemacro[i].value; } } for (i=pr_nummacros-1 ; i>=0 ; i--) { - if (!stricmp (name, pr_framemacros[i])) + if (!stricmp (name, pr_framemacro[i].name)) { - QCC_PR_ParseWarning(WARN_CASEINSENSITIVEFRAMEMACRO, "Case insensitive frame macro"); - return pr_framemacrovalue[i]; + QCC_PR_ParseWarning(WARN_CASEINSENSITIVEFRAMEMACRO, "Case insensitive frame macro (using %s)", pr_framemacro[i].name); + if (pr_framemacro[i].file != s_filen) + QCC_PR_ParseWarning(WARN_DUPLICATEMACRO, "Stale macro used (%s, defined in %s)", pr_token, pr_framemacro[i].file); + return pr_framemacro[i].value; } } return -1; @@ -2497,27 +2504,34 @@ pbool QCC_PR_LexMacroName(void) return i!=0; } -void QCC_PR_MacroFrame(char *name, int value) +void QCC_PR_MacroFrame(char *name, int value, pbool force) { int i; for (i=pr_nummacros-1 ; i>=0 ; i--) { - if (!STRCMP (name, pr_framemacros[i])) + if (!STRCMP (name, pr_framemacro[i].name)) { - pr_framemacrovalue[i] = value; - if (i>=pr_oldmacros) + //vanilla macro behaviour is to not realise that there's dupes. lookups find the first, so dupes end up as dead gaps. + //our caller incremented the value externally + //so warn+ignore if its from the same file + if (pr_framemacro[i].file == s_filen && !force) QCC_PR_ParseWarning(WARN_DUPLICATEMACRO, "Duplicate macro defined (%s)", pr_token); - //else it's from an old file, and shouldn't be mentioned. + else + { + pr_framemacro[i].value = value; //old file, override it, whatever the old value was is redundant now + pr_framemacro[i].file = s_filen; + } return; } } - if (strlen(name)+1 > sizeof(pr_framemacros[0])) + if (strlen(name)+1 > sizeof(pr_framemacro[0].name)) QCC_PR_ParseWarning(ERR_TOOMANYFRAMEMACROS, "Name for frame macro %s is too long", name); else { - strcpy (pr_framemacros[pr_nummacros], name); - pr_framemacrovalue[pr_nummacros] = value; + strcpy (pr_framemacro[pr_nummacros].name, name); + pr_framemacro[pr_nummacros].value = value; + pr_framemacro[pr_nummacros].file = s_filen; pr_nummacros++; if (pr_nummacros >= MAX_FRAMES) QCC_PR_ParseError(ERR_TOOMANYFRAMEMACROS, "Too many frame macros defined"); @@ -2528,7 +2542,7 @@ void QCC_PR_ParseFrame (void) { while (QCC_PR_LexMacroName ()) { - QCC_PR_MacroFrame(pr_token, pr_macrovalue++); + QCC_PR_MacroFrame(pr_token, pr_macrovalue++, false); } } @@ -2596,7 +2610,7 @@ void QCC_PR_LexGrab (void) QCC_PR_LexMacroName (); if (*pr_framemodelname) - QCC_PR_MacroFrame(pr_framemodelname, pr_macrovalue); + QCC_PR_MacroFrame(pr_framemodelname, pr_macrovalue, true); QC_strlcpy(pr_framemodelname, pr_token, sizeof(pr_framemodelname)); diff --git a/engine/qclib/qccgui.c b/engine/qclib/qccgui.c index 1d2156146..ae49a8db3 100644 --- a/engine/qclib/qccgui.c +++ b/engine/qclib/qccgui.c @@ -16,6 +16,8 @@ #define IDI_ICON_FTEQCC MAKEINTRESOURCE(101) +static void GUI_CreateInstaller_Windows(void); +static void GUI_CreateInstaller_Android(void); void AddSourceFile(const char *parentsrc, const char *filename); #ifndef TVM_SETBKCOLOR @@ -384,6 +386,12 @@ unsigned char *PDECL QCC_ReadFile (const char *fname, void *buffer, int len, siz vfile_t *v = QCC_FindVFile(fname); if (v) { + if (!buffer) + { + len = v->fsize; + buffer = malloc(len+1); + ((char*)buffer)[len] = 0; + } if (len > v->fsize) len = v->fsize; memcpy(buffer, v->fdata, len); @@ -394,12 +402,33 @@ unsigned char *PDECL QCC_ReadFile (const char *fname, void *buffer, int len, siz f = fopen(fname, "rb"); if (!f) + { + if (sz) + *sz = 0; return NULL; - length = fread(buffer, 1, len, f); - fclose(f); + } - if (length != len) - return NULL; + if (!buffer) + { + fseek(f, 0, SEEK_END); + len = ftell(f); + fseek(f, 0, SEEK_SET); + buffer = malloc(len+1); + ((char*)buffer)[len] = 0; + length = fread(buffer, 1, len, f); + if (length != len) + { + free(buffer); + buffer = NULL; + } + } + else + { + length = fread(buffer, 1, len, f); + if (length != len) + buffer = NULL; + } + fclose(f); if (sz) *sz = length; @@ -1287,6 +1316,8 @@ enum { IDM_DEBUG_TOGGLEBREAK, IDM_ENCODING_PRIVATEUSE, IDM_ENCODING_DEPRIVATEUSE, + IDM_CREATEINSTALLER_WINDOWS, + IDM_CREATEINSTALLER_ANDROID, IDI_O_LEVEL0, IDI_O_LEVEL1, @@ -1344,6 +1375,13 @@ void GenericMenu(WPARAM wParam) buttons[ID_COMPILE].washit = true; break; + case IDM_CREATEINSTALLER_WINDOWS: + GUI_CreateInstaller_Windows(); + break; + case IDM_CREATEINSTALLER_ANDROID: + GUI_CreateInstaller_Android(); + break; + case IDM_ABOUT: MessageBox(NULL, "FTE QuakeC Compiler ("__DATE__" "__TIME__")\nWritten by Forethought Entertainment, whoever that is.\n\nIf you have problems with wordpad corrupting your qc files, try saving them using utf-16 encoding via notepad.", "About", 0); break; @@ -2371,25 +2409,12 @@ static LRESULT CALLBACK EditorWndProc(HWND hWnd,UINT message, static void EditorReload(editor_t *editor) { struct stat sbuf; - int flen; + size_t flen; char *rawfile; char *file; pbool dofree; - flen = QCC_RawFileSize(editor->filename); - if (flen >= 0) - { - rawfile = malloc(flen+1); - - QCC_ReadFile(editor->filename, rawfile, flen, NULL); - - rawfile[flen] = 0; - } - else - { - rawfile = NULL; - flen = 0; - } + rawfile = QCC_ReadFile(editor->filename, NULL, 0, &flen); file = QCC_SanitizeCharSet(rawfile, &flen, &dofree, &editor->savefmt); @@ -3448,12 +3473,69 @@ static INT CALLBACK StupidBrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM lp, LP } return 0; } -void PromptForEngine(int force) + +pbool PromptForFile(const char *prompt, const char *filter, const char *basepath, const char *defaultfile, char outname[], size_t outsize, pbool create) { + char oldworkingdir[MAX_PATH+10]; //cmdlg changes it... + char workingdir[MAX_PATH+10]; + #ifndef OFN_DONTADDTORECENT #define OFN_DONTADDTORECENT 0x02000000 #endif + char *s; + char initialdir[MAX_PATH+10]; + char absengine[MAX_PATH+10]; + OPENFILENAME ofn; + pbool okay; + memset(&ofn, 0, sizeof(ofn)); + ofn.lStructSize = sizeof(ofn); + ofn.hwndOwner = mainwindow; + ofn.hInstance = ghInstance; + ofn.lpstrFile = absengine; + ofn.Flags = OFN_EXPLORER|(create?OFN_PATHMUSTEXIST|OFN_CREATEPROMPT:OFN_FILEMUSTEXIST)|OFN_DONTADDTORECENT; + ofn.lpstrTitle = prompt; + ofn.nMaxFile = outsize-1; + ofn.lpstrFilter = filter; + GetCurrentDirectory(sizeof(oldworkingdir)-1, oldworkingdir); + memcpy(workingdir, oldworkingdir, sizeof(workingdir)); + _snprintf(absengine, sizeof(absengine), "%s/%s", basepath, defaultfile); + for (s = absengine; *s; s++) + if (*s == '/') + *s = '\\'; + strrchr(absengine, '\\')[1] = 0; + PathCombine(initialdir, workingdir, absengine); + if (strchr(defaultfile, '/')) + strcpy(absengine, strrchr(defaultfile, '/')+1); + else + strcpy(absengine, defaultfile); + //and the fuck-you-microsoft loop + for (s = initialdir; *s; s++) + if (*s == '/') + *s = '\\'; + ofn.lpstrInitialDir = initialdir; + okay = GetOpenFileName(&ofn); + while (!okay) + { + switch(CommDlgExtendedError()) + { + case FNERR_INVALIDFILENAME: + *outname = 0; + okay = GetOpenFileName(&ofn); + continue; + } + break; + } + if (!PathRelativePathToA(outname, initialdir, FILE_ATTRIBUTE_DIRECTORY, absengine, FILE_ATTRIBUTE_DIRECTORY)) + QC_strlcpy(outname, absengine, sizeof(outsize)); + if (!strncmp(outname, ".\\", 2)) + memmove(outname, outname+2, strlen(outname+2)+1); + //undo any damage caused by microsoft's stupidity + SetCurrentDirectory(oldworkingdir); + return okay; +} +void PromptForEngine(int force) +{ char oldworkingdir[MAX_PATH+10]; //cmdlg changes it... char workingdir[MAX_PATH+10]; GetCurrentDirectory(sizeof(oldworkingdir)-1, oldworkingdir); @@ -3496,51 +3578,7 @@ void PromptForEngine(int force) if (!*enginebinary || force==2) { - char *s; - char initialdir[MAX_PATH+10]; - char absengine[MAX_PATH+10]; - OPENFILENAME ofn; - pbool okay; - memset(&ofn, 0, sizeof(ofn)); - ofn.lStructSize = sizeof(ofn); - ofn.hwndOwner = mainwindow; - ofn.hInstance = ghInstance; - ofn.lpstrFile = absengine; - ofn.Flags = OFN_EXPLORER|OFN_FILEMUSTEXIST|OFN_DONTADDTORECENT; - ofn.lpstrTitle = "Please choose an engine"; - ofn.nMaxFile = sizeof(enginebinary)-1; - ofn.lpstrFilter = "Executables\0*.exe\0All files\0*.*\0"; - GetCurrentDirectory(sizeof(workingdir)-1, workingdir); - _snprintf(absengine, sizeof(absengine), "%s/", enginebasedir); - for (s = absengine; *s; s++) - if (*s == '/') - *s = '\\'; - PathCombine(initialdir, workingdir, absengine); - strcpy(absengine, "fteglqw.exe"); - //and the fuck-you-microsoft loop - for (s = initialdir; *s; s++) - if (*s == '/') - *s = '\\'; - ofn.lpstrInitialDir = initialdir; - okay = GetOpenFileName(&ofn); - while (!okay) - { - switch(CommDlgExtendedError()) - { - case FNERR_INVALIDFILENAME: - *enginebinary = 0; - okay = GetOpenFileName(&ofn); - continue; - } - break; - } - if (!PathRelativePathToA(enginebinary, initialdir, FILE_ATTRIBUTE_DIRECTORY, absengine, FILE_ATTRIBUTE_DIRECTORY)) - QC_strlcpy(enginebinary, absengine, sizeof(enginebasedir)); - if (!strncmp(enginebinary, ".\\", 2)) - memmove(enginebinary, enginebinary+2, strlen(enginebinary+2)+1); - //undo any damage caused by microsoft's stupidity - SetCurrentDirectory(oldworkingdir); - if (!okay) + if (!PromptForFile("Please choose an engine", "Executables\0*.exe\0All files\0*.*\0", enginebasedir, "fteglqw.exe", enginebinary, sizeof(enginebinary), false)) return; if (optionsmenu) @@ -3554,9 +3592,9 @@ void PromptForEngine(int force) char *slash; GetCurrentDirectory(sizeof(workingdir)-1, workingdir); _snprintf(guessdir, sizeof(guessdir), "%s/", enginebasedir); - for (s = guessdir; *s; s++) - if (*s == '/') - *s = '\\'; + for (slash = guessdir; *slash; slash++) + if (*slash == '/') + *slash = '\\'; PathCombine(absbase, workingdir, guessdir); if (PathRelativePathToA(guessdir, absbase, FILE_ATTRIBUTE_DIRECTORY, workingdir, FILE_ATTRIBUTE_DIRECTORY)) { @@ -3655,6 +3693,977 @@ static void SetProgsSrcFileAndPath(char *filename) *progssrcdir = '\0'; } +qcc_cachedsourcefile_t *androidfiles; +static void Android_FreeFiles(void) +{ + qcc_cachedsourcefile_t *f; + while((f = androidfiles)) + { + androidfiles = f->next; + free(f); + } +} +static void Android_CopyFile(const char *name, const void *compdata, size_t compsize, int method, size_t plainsize) +{ + qcc_cachedsourcefile_t *nf, **link; + if (!strncmp(name, "META-INF", 8)) + return; //ignore any existing signatures. + for (link = &androidfiles; *link; link = &(*link)->next) + { + nf = *link; + if (!stricmp(name, nf->filename)) + { //nuke the old file if we have a dupe. + *link = nf->next; + free(nf); + break; + } + } + + nf = malloc(sizeof(*nf) + plainsize); + if (!nf) + { + GUIprintf("Error: out of memory\n", name, plainsize); + return; + } + QC_strlcpy(nf->filename, name, sizeof(nf->filename)); + nf->file = (char*)(nf+1); + nf->size = plainsize; + nf->type = FT_DATA; + + if (QC_decode(NULL, compsize, nf->size, method, compdata, nf->file)) + { + GUIprintf("Android: Including %s (%i bytes)\n", name, plainsize); + nf->next = androidfiles; + androidfiles = nf; + } + else + { + GUIprintf("Android: Unable to read %s from source apk\n", name, plainsize); + free(nf); + } +} +static pbool Android_PrepareAPK(FILE *f) +{ + if (f) + { + char *buf; + size_t size; + + fseek(f, 0, SEEK_END); + size = ftell(f); + fseek(f, 0, SEEK_SET); + buf = malloc(size); + fread(buf, 1, size, f); + fclose(f); + QC_EnumerateFilesFromBlob(buf, size, Android_CopyFile); + free(buf); + return true; + } + return false; +} +void GUI_CreateInstaller_Android(void) +{ + FILE *f; + char inputapkname[MAX_PATH]; + //files + char *keystore = "my-release-key.keystore"; + char targetapk[MAX_PATH]; + //other stuff + char *storepass = "fte123"; + char *alias = "FTEDroid"; + char tmp[MAX_PATH]; + char *mandata = NULL; + char *pngdata = NULL; + char *modname = "my_application"; + size_t manlen, pnglen; + int h; + char cmdline[2048]; + FILE *inputapk; + + if (MessageBox(mainwindow, "The 'Create Installer' option is still experimental.\nIt's probably still defective.\nSo be sure to test stuff extensively.", "Create Installer", MB_OKCANCEL|MB_DEFBUTTON2) != IDOK) + return; + + if (!mandata) + { + QC_snprintfz(tmp, sizeof(tmp), "default.fmf", modname); + mandata = QCC_ReadFile (tmp, NULL, 0, &manlen); + } + if (!mandata) + { + QC_snprintfz(tmp, sizeof(tmp), "%s.fmf", modname); + mandata = QCC_ReadFile (tmp, NULL, 0, &manlen); + } + if (!mandata) + { + QC_snprintfz(tmp, sizeof(tmp), "../default.fmf", modname); + mandata = QCC_ReadFile (tmp, NULL, 0, &manlen); + } + if (!mandata) + { + QC_snprintfz(tmp, sizeof(tmp), "../%s.fmf", modname); + mandata = QCC_ReadFile (tmp, NULL, 0, &manlen); + } + if (!mandata) + { + QC_snprintfz(tmp, sizeof(tmp), "%s.fmf", modname); + if (PromptForFile("Please Select Manifest File", "FTE Manifests\0*.fmf\0All files\0*.*\0", ".", tmp, tmp, sizeof(tmp), false)) + mandata = QCC_ReadFile (tmp, NULL, 0, &manlen); + } + if (!mandata) + { + MessageBox(mainwindow, "No manifest selected.\n", "Create Installer", MB_OK|MB_ICONERROR); + return; + } + + PathCombine(inputapkname, enginebasedir, "FTEDroid.apk"); + inputapk = fopen(inputapkname, "rb"); + if (!inputapk) + { + if (PromptForFile("Please find base FTE android package", "The FTE Android Package\0*.apk\0All files\0*.*\0", enginebasedir, "FTEDroid.apk", tmp, sizeof(tmp), false)) + { + PathCombine(inputapkname, enginebasedir, tmp); + inputapk = fopen(inputapkname, "rb"); + } + } + + if (inputapk) + { + QC_snprintfz(tmp, sizeof(tmp), "%s.apk", modname); + if (PromptForFile("Please output apk", "Android Packages\0*.apk\0All files\0*.*\0", ".", tmp, tmp, sizeof(tmp), true)) + PathCombine(targetapk, enginebasedir, tmp); + + GUIprintf(""); + + //read the files from an existing apk + if (!Android_PrepareAPK(inputapk)) + MessageBox(mainwindow, "Unable to read source package", "Create Installer", MB_OK|MB_ICONERROR); + else + { + //add/replace some existing files + pngdata = QCC_ReadFile ("../droid_72.png", NULL, 0, &pnglen); + if (!pngdata) + GUIprintf("Could not open ../droid_72.png launcher icon\n"); + Android_CopyFile("res/drawable-hdpi/icon.png", pngdata, pnglen, 0, pnglen); + free(pngdata); + + pngdata = QCC_ReadFile ("../droid_48.png", NULL, 0, &pnglen); + if (!pngdata) + GUIprintf("Could not open ../droid_48.png launcher icon\n"); + Android_CopyFile("res/drawable-mdpi/icon.png", NULL, 0, 0, 0); + free(pngdata); + + Android_CopyFile("default.fmf", mandata, manlen, 0, manlen); + + //write out the new zip... err... apk... :) + h = SafeOpenWrite (targetapk, 2*1024*1024); + if (h < 0) + { + GUIprintf("Unable to open %s\n", targetapk); + } + else + { + progfuncs_t funcs; + progexterns_t ext; + memset(&funcs, 0, sizeof(funcs)); + funcs.funcs.parms = &ext; + memset(&ext, 0, sizeof(ext)); + ext.ReadFile = GUIReadFile; + ext.FileSize = GUIFileSize; + ext.WriteFile = QCC_WriteFile; + ext.Sys_Error = Sys_Error; + ext.Printf = GUIprintf; + qccprogfuncs = &funcs; + WriteSourceFiles(androidfiles, h, true, false); + if (!SafeClose(h)) + GUIprintf("Error: Unable to write output android package %s\n", targetapk); + else + { + f = fopen(keystore, "rb"); + if (f) + fclose(f); + else + { + GUIprintf("Key store does not exist. Trying to create\n"); + //try to create a keystore for them, as this is their first time. + QC_snprintfz(cmdline, sizeof(cmdline), "keytool -genkey -keystore %s -storepass %s -keypass %s -alias %s -keyalg RSA -keysize 2048 -validity 10000", keystore, storepass, storepass, alias); + system(cmdline); + } + + f = fopen(keystore, "rb"); + if (f) + { + fclose(f); + //we now need to invoke the jarsigner program, so I hope you have java installed. + QC_snprintfz(cmdline, sizeof(cmdline), "jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore %s -storepass %s %s %s", keystore, storepass, targetapk, alias); + if (EXIT_SUCCESS == system(cmdline)) + GUIprintf("Android Package Complete. Go ahead and test it!\n"); + else + GUIprintf("Failed to sign package.\n"); + } + else + GUIprintf("Keystore creation failed or was aborted.\n"); + } + } + } + } + Android_FreeFiles(); + + free(mandata); +} + +//size info that microsoft recommends +static const struct +{ + int width; + int height; + int bpp; +} icosizes[] = { +// {96, 96, 32}, + {48, 48, 32}, + {32, 32, 32}, + {16, 16, 32}, +// {16, 16, 4}, +// {48, 48, 4}, +// {32, 32, 4}, +// {16, 16, 1}, +// {48, 48, 1}, +// {32, 32, 1}, + {256, 256, 32} //vista! +}; +//dates back to 16bit windows. bah. +#pragma pack(push) +#pragma pack(2) +typedef struct +{ + WORD idReserved; + WORD idType; + WORD idCount; + struct + { + BYTE bWidth; + BYTE bHeight; + BYTE bColorCount; + BYTE bReserved; + WORD wPlanes; + WORD wBitCount; + DWORD dwBytesInRes; + WORD nId; + } idEntries[256]; +} icon_group_t; +#pragma pack(pop) + + +static void Image_ResampleTexture (unsigned *in, int inwidth, int inheight, unsigned *out, int outwidth, int outheight) +{ + int i, j; + unsigned *inrow; + unsigned frac, fracstep; + + /*if (gl_lerpimages.ival) + { + Image_Resample32Lerp(in, inwidth, inheight, out, outwidth, outheight); + return; + }*/ + + fracstep = inwidth*0x10000/outwidth; + for (i=0 ; i>16]; + } + for ( ; j>=4 ;) + { + j-=4; + frac -= fracstep; + out[j+3] = inrow[frac>>16]; + frac -= fracstep; + out[j+2] = inrow[frac>>16]; + frac -= fracstep; + out[j+1] = inrow[frac>>16]; + frac -= fracstep; + out[j+0] = inrow[frac>>16]; + } + } +} + +#define AVAIL_PNGLIB +#define AVAIL_ZLIB +#ifndef MSVCLIBSPATH +#ifdef MSVCLIBPATH + #define MSVCLIBSPATH STRINGIFY(MSVCLIBPATH) +#elif _MSC_VER == 1200 + #define MSVCLIBSPATH "../" "../libs/vc6-libs/" +#else + #define MSVCLIBSPATH "../" "../libs/" +#endif +#endif + +#ifdef AVAIL_PNGLIB + #ifndef AVAIL_ZLIB + #error PNGLIB requires ZLIB + #endif + + #undef channels + + #ifndef PNG_SUCKS_WITH_SETJMP + #if defined(MINGW) + #include "./mingw-libs/png.h" + #elif defined(_WIN32) + #include "../png.h" + #else + #include + #endif + #endif + + #ifdef DYNAMIC_LIBPNG + #define PSTATIC(n) + static dllhandle_t *libpng_handle; + #define LIBPNG_LOADED() (libpng_handle != NULL) + #else + #define LIBPNG_LOADED() 1 + #define PSTATIC(n) = &n + #ifdef _MSC_VER + #ifdef _WIN64 + #pragma comment(lib, MSVCLIBSPATH "libpng64.lib") + #else + #pragma comment(lib, MSVCLIBSPATH "libpng.lib") + #endif + #endif + #endif + +#ifndef PNG_NORETURN +#define PNG_NORETURN +#endif +#ifndef PNG_ALLOCATED +#define PNG_ALLOCATED +#endif + +#if PNG_LIBPNG_VER < 10500 + #define png_const_infop png_infop + #define png_const_structp png_structp + #define png_const_bytep png_bytep + #define png_const_unknown_chunkp png_unknown_chunkp +#endif +#if PNG_LIBPNG_VER < 10600 + #define png_inforp png_infop + #define png_const_inforp png_const_infop + #define png_structrp png_structp + #define png_const_structrp png_const_structp +#endif + +void (PNGAPI *qpng_error) PNGARG((png_const_structrp png_ptr, png_const_charp error_message)) PSTATIC(png_error); +void (PNGAPI *qpng_read_end) PNGARG((png_structp png_ptr, png_infop info_ptr)) PSTATIC(png_read_end); +void (PNGAPI *qpng_read_image) PNGARG((png_structp png_ptr, png_bytepp image)) PSTATIC(png_read_image); +png_byte (PNGAPI *qpng_get_bit_depth) PNGARG((png_const_structp png_ptr, png_const_inforp info_ptr)) PSTATIC(png_get_bit_depth); +png_byte (PNGAPI *qpng_get_channels) PNGARG((png_const_structp png_ptr, png_const_inforp info_ptr)) PSTATIC(png_get_channels); +png_size_t (PNGAPI *qpng_get_rowbytes) PNGARG((png_const_structp png_ptr, png_const_inforp info_ptr)) PSTATIC(png_get_rowbytes); +void (PNGAPI *qpng_read_update_info) PNGARG((png_structp png_ptr, png_infop info_ptr)) PSTATIC(png_read_update_info); +void (PNGAPI *qpng_set_strip_16) PNGARG((png_structp png_ptr)) PSTATIC(png_set_strip_16); +void (PNGAPI *qpng_set_expand) PNGARG((png_structp png_ptr)) PSTATIC(png_set_expand); +void (PNGAPI *qpng_set_gray_to_rgb) PNGARG((png_structp png_ptr)) PSTATIC(png_set_gray_to_rgb); +void (PNGAPI *qpng_set_tRNS_to_alpha) PNGARG((png_structp png_ptr)) PSTATIC(png_set_tRNS_to_alpha); +png_uint_32 (PNGAPI *qpng_get_valid) PNGARG((png_const_structp png_ptr, png_const_infop info_ptr, png_uint_32 flag)) PSTATIC(png_get_valid); +#if PNG_LIBPNG_VER > 10400 +void (PNGAPI *qpng_set_expand_gray_1_2_4_to_8) PNGARG((png_structp png_ptr)) PSTATIC(png_set_expand_gray_1_2_4_to_8); +#else +void (PNGAPI *qpng_set_gray_1_2_4_to_8) PNGARG((png_structp png_ptr)) PSTATIC(png_set_gray_1_2_4_to_8); +#endif +void (PNGAPI *qpng_set_bgr) PNGARG((png_structp png_ptr)) PSTATIC(png_set_bgr); +void (PNGAPI *qpng_set_filler) PNGARG((png_structp png_ptr, png_uint_32 filler, int flags)) PSTATIC(png_set_filler); +void (PNGAPI *qpng_set_palette_to_rgb) PNGARG((png_structp png_ptr)) PSTATIC(png_set_palette_to_rgb); +png_uint_32 (PNGAPI *qpng_get_IHDR) PNGARG((png_const_structrp png_ptr, png_const_inforp info_ptr, png_uint_32 *width, png_uint_32 *height, + int *bit_depth, int *color_type, int *interlace_method, int *compression_method, int *filter_method)) PSTATIC(png_get_IHDR); +void (PNGAPI *qpng_read_info) PNGARG((png_structp png_ptr, png_infop info_ptr)) PSTATIC(png_read_info); +void (PNGAPI *qpng_set_sig_bytes) PNGARG((png_structp png_ptr, int num_bytes)) PSTATIC(png_set_sig_bytes); +void (PNGAPI *qpng_set_read_fn) PNGARG((png_structp png_ptr, png_voidp io_ptr, png_rw_ptr read_data_fn)) PSTATIC(png_set_read_fn); +void (PNGAPI *qpng_destroy_read_struct) PNGARG((png_structpp png_ptr_ptr, png_infopp info_ptr_ptr, png_infopp end_info_ptr_ptr)) PSTATIC(png_destroy_read_struct); +png_infop (PNGAPI *qpng_create_info_struct) PNGARG((png_const_structrp png_ptr)) PSTATIC(png_create_info_struct); +png_structp (PNGAPI *qpng_create_read_struct) PNGARG((png_const_charp user_png_ver, png_voidp error_ptr, png_error_ptr error_fn, png_error_ptr warn_fn)) PSTATIC(png_create_read_struct); +int (PNGAPI *qpng_sig_cmp) PNGARG((png_const_bytep sig, png_size_t start, png_size_t num_to_check)) PSTATIC(png_sig_cmp); + +void (PNGAPI *qpng_write_end) PNGARG((png_structrp png_ptr, png_inforp info_ptr)) PSTATIC(png_write_end); +void (PNGAPI *qpng_write_image) PNGARG((png_structrp png_ptr, png_bytepp image)) PSTATIC(png_write_image); +void (PNGAPI *qpng_write_info) PNGARG((png_structrp png_ptr, png_const_inforp info_ptr)) PSTATIC(png_write_info); +void (PNGAPI *qpng_set_IHDR) PNGARG((png_const_structrp png_ptr, png_infop info_ptr, png_uint_32 width, png_uint_32 height, + int bit_depth, int color_type, int interlace_method, int compression_method, int filter_method)) PSTATIC(png_set_IHDR); +void (PNGAPI *qpng_set_compression_level) PNGARG((png_structrp png_ptr, int level)) PSTATIC(png_set_compression_level); +void (PNGAPI *qpng_init_io) PNGARG((png_structp png_ptr, png_FILE_p fp)) PSTATIC(png_init_io); +png_voidp (PNGAPI *qpng_get_io_ptr) PNGARG((png_const_structrp png_ptr)) PSTATIC(png_get_io_ptr); +void (PNGAPI *qpng_destroy_write_struct) PNGARG((png_structpp png_ptr_ptr, png_infopp info_ptr_ptr)) PSTATIC(png_destroy_write_struct); +png_structp (PNGAPI *qpng_create_write_struct) PNGARG((png_const_charp user_png_ver, png_voidp error_ptr, png_error_ptr error_fn, png_error_ptr warn_fn)) PSTATIC(png_create_write_struct); +void (PNGAPI *qpng_set_unknown_chunks) PNGARG((png_const_structrp png_ptr, png_inforp info_ptr, png_const_unknown_chunkp unknowns, int num_unknowns)) PSTATIC(png_set_unknown_chunks); + +png_voidp (PNGAPI *qpng_get_error_ptr) PNGARG((png_const_structrp png_ptr)) PSTATIC(png_get_error_ptr); + +pbool LibPNG_Init(void) +{ +#ifdef DYNAMIC_LIBPNG + static dllfunction_t pngfuncs[] = + { + {(void **) &qpng_error, "png_error"}, + {(void **) &qpng_read_end, "png_read_end"}, + {(void **) &qpng_read_image, "png_read_image"}, + {(void **) &qpng_get_bit_depth, "png_get_bit_depth"}, + {(void **) &qpng_get_channels, "png_get_channels"}, + {(void **) &qpng_get_rowbytes, "png_get_rowbytes"}, + {(void **) &qpng_read_update_info, "png_read_update_info"}, + {(void **) &qpng_set_strip_16, "png_set_strip_16"}, + {(void **) &qpng_set_expand, "png_set_expand"}, + {(void **) &qpng_set_gray_to_rgb, "png_set_gray_to_rgb"}, + {(void **) &qpng_set_tRNS_to_alpha, "png_set_tRNS_to_alpha"}, + {(void **) &qpng_get_valid, "png_get_valid"}, +#if PNG_LIBPNG_VER > 10400 + {(void **) &qpng_set_expand_gray_1_2_4_to_8, "png_set_expand_gray_1_2_4_to_8"}, +#else + {(void **) &qpng_set_gray_1_2_4_to_8, "png_set_gray_1_2_4_to_8"}, +#endif + {(void **) &qpng_set_bgr, "png_set_bgr"}, + {(void **) &qpng_set_filler, "png_set_filler"}, + {(void **) &qpng_set_palette_to_rgb, "png_set_palette_to_rgb"}, + {(void **) &qpng_get_IHDR, "png_get_IHDR"}, + {(void **) &qpng_read_info, "png_read_info"}, + {(void **) &qpng_set_sig_bytes, "png_set_sig_bytes"}, + {(void **) &qpng_set_read_fn, "png_set_read_fn"}, + {(void **) &qpng_destroy_read_struct, "png_destroy_read_struct"}, + {(void **) &qpng_create_info_struct, "png_create_info_struct"}, + {(void **) &qpng_create_read_struct, "png_create_read_struct"}, + {(void **) &qpng_sig_cmp, "png_sig_cmp"}, + + {(void **) &qpng_write_end, "png_write_end"}, + {(void **) &qpng_write_image, "png_write_image"}, + {(void **) &qpng_write_info, "png_write_info"}, + {(void **) &qpng_set_IHDR, "png_set_IHDR"}, + {(void **) &qpng_set_compression_level, "png_set_compression_level"}, + {(void **) &qpng_init_io, "png_init_io"}, + {(void **) &qpng_get_io_ptr, "png_get_io_ptr"}, + {(void **) &qpng_destroy_write_struct, "png_destroy_write_struct"}, + {(void **) &qpng_create_write_struct, "png_create_write_struct"}, + {(void **) &qpng_set_unknown_chunks, "png_set_unknown_chunks"}, + + {(void **) &qpng_get_error_ptr, "png_get_error_ptr"}, + {NULL, NULL} + }; + static qboolean tried; + if (!tried) + { + tried = true; + + if (!LIBPNG_LOADED()) + { + char *libnames[] = + { + #ifdef _WIN32 + va("libpng%i", PNG_LIBPNG_VER_DLLNUM); + #else + //linux... + //lsb uses 'libpng12.so' specifically, so make sure that works. + "libpng" STRINGIFY(PNG_LIBPNG_VER_MAJOR) STRINGIFY(PNG_LIBPNG_VER_MINOR) ".so." STRINGIFY(PNG_LIBPNG_VER_SONUM), + "libpng" STRINGIFY(PNG_LIBPNG_VER_MAJOR) STRINGIFY(PNG_LIBPNG_VER_MINOR) ".so", + "libpng.so." STRINGIFY(PNG_LIBPNG_VER_SONUM) + "libpng.so", + #endif + }; + size_t i; + for (i = 0; i < countof(libnames); i++) + { + libpng_handle = Sys_LoadLibrary(libnames[i], pngfuncs); + if (libpng_handle) + break; + } + if (!libpng_handle) + Con_Printf("Unable to load %s\n", libnames[0]); + } + +// if (!LIBPNG_LOADED()) +// libpng_handle = Sys_LoadLibrary("libpng", pngfuncs); + } +#endif + return LIBPNG_LOADED(); +} + +typedef struct { + char *data; + int readposition; + int filelen; +} pngreadinfo_t; + +static void VARGS readpngdata(png_structp png_ptr,png_bytep data,png_size_t len) +{ + pngreadinfo_t *ri = (pngreadinfo_t*)qpng_get_io_ptr(png_ptr); + if (ri->readposition+len > ri->filelen) + { + qpng_error(png_ptr, "unexpected eof"); + return; + } + memcpy(data, &ri->data[ri->readposition], len); + ri->readposition+=len; +} + +struct pngerr +{ + const char *fname; + jmp_buf jbuf; +}; +static void VARGS png_onerror(png_structp png_ptr, png_const_charp error_msg) +{ + struct pngerr *err = qpng_get_error_ptr(png_ptr); +// Con_Printf("libpng %s: %s\n", err->fname, error_msg); + longjmp(err->jbuf, 1); + abort(); +} + +static void VARGS png_onwarning(png_structp png_ptr, png_const_charp warning_msg) +{ + struct pngerr *err = qpng_get_error_ptr(png_ptr); +// Con_DPrintf("libpng %s: %s\n", err->fname, warning_msg); +} + +qbyte *ReadPNGFile(qbyte *buf, int length, int *width, int *height, const char *fname) +{ + qbyte header[8], **rowpointers = NULL, *data = NULL; + png_structp png; + png_infop pnginfo; + int y, bitdepth, colortype, interlace, compression, filter, bytesperpixel; + unsigned long rowbytes; + pngreadinfo_t ri; + png_uint_32 pngwidth, pngheight; + struct pngerr errctx; + + if (!LibPNG_Init()) + return NULL; + + memcpy(header, buf, 8); + + errctx.fname = fname; + if (setjmp(errctx.jbuf)) + { +error: + if (data) + free(data); + if (rowpointers) + free(rowpointers); + qpng_destroy_read_struct(&png, &pnginfo, NULL); + return NULL; + } + + if (qpng_sig_cmp(header, 0, 8)) + { + return NULL; + } + + if (!(png = qpng_create_read_struct(PNG_LIBPNG_VER_STRING, &errctx, png_onerror, png_onwarning))) + { + return NULL; + } + + if (!(pnginfo = qpng_create_info_struct(png))) + { + qpng_destroy_read_struct(&png, &pnginfo, NULL); + return NULL; + } + + ri.data=buf; + ri.readposition=8; + ri.filelen=length; + qpng_set_read_fn(png, &ri, readpngdata); + + qpng_set_sig_bytes(png, 8); + qpng_read_info(png, pnginfo); + qpng_get_IHDR(png, pnginfo, &pngwidth, &pngheight, &bitdepth, &colortype, &interlace, &compression, &filter); + + *width = pngwidth; + *height = pngheight; + + if (colortype == PNG_COLOR_TYPE_PALETTE) + { + qpng_set_palette_to_rgb(png); + qpng_set_filler(png, 255, PNG_FILLER_AFTER); + } + + if (colortype == PNG_COLOR_TYPE_GRAY && bitdepth < 8) + { + #if PNG_LIBPNG_VER > 10400 + qpng_set_expand_gray_1_2_4_to_8(png); + #else + qpng_set_gray_1_2_4_to_8(png); + #endif + } + + if (qpng_get_valid( png, pnginfo, PNG_INFO_tRNS)) + qpng_set_tRNS_to_alpha(png); + + if (bitdepth >= 8 && colortype == PNG_COLOR_TYPE_RGB) + qpng_set_filler(png, 255, PNG_FILLER_AFTER); + + if (colortype == PNG_COLOR_TYPE_GRAY || colortype == PNG_COLOR_TYPE_GRAY_ALPHA) + { + qpng_set_gray_to_rgb( png ); + qpng_set_filler(png, 255, PNG_FILLER_AFTER); + } + + if (bitdepth < 8) + qpng_set_expand (png); + else if (bitdepth == 16) + qpng_set_strip_16(png); + + + qpng_read_update_info(png, pnginfo); + rowbytes = qpng_get_rowbytes(png, pnginfo); + bytesperpixel = qpng_get_channels(png, pnginfo); + bitdepth = qpng_get_bit_depth(png, pnginfo); + + if (bitdepth != 8 || bytesperpixel != 4) + { +// Con_Printf ("Bad PNG color depth and/or bpp (%s)\n", fname); + qpng_destroy_read_struct(&png, &pnginfo, NULL); + return NULL; + } + + data = malloc(*height * rowbytes); + rowpointers = malloc(*height * sizeof(*rowpointers)); + + if (!data || !rowpointers) + goto error; + + for (y = 0; y < *height; y++) + rowpointers[y] = data + y * rowbytes; + + qpng_read_image(png, rowpointers); + qpng_read_end(png, NULL); + + qpng_destroy_read_struct(&png, &pnginfo, NULL); + free(rowpointers); + return data; +} +#endif + +static void GUI_CreateInstaller_Windows(void) +{ +#define RESLANG MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_UK) + unsigned char *mandata = NULL; + unsigned int manlen; + unsigned char *pngdata = NULL; + unsigned int pnglen; + char *error = NULL; + char *warn = NULL; + HANDLE bin; + char ourname[MAX_PATH]; + char *basedir = enginebasedir; + char newname[MAX_PATH]; + char modname[MAX_PATH+10] = "unknownmod"; + char tmpname[MAX_PATH]; + char tmp[MAX_PATH]; + + if (MessageBox(mainwindow, "The 'Create Installer' option is still experimental.\nIt's probably still defective.\nSo be sure to test stuff extensively.", "Create Installer", MB_OKCANCEL|MB_DEFBUTTON2) != IDOK) + return; + + PromptForEngine(0); + + + { + char workingdir[MAX_PATH]; + char absbase[MAX_PATH]; + char *slash; + GetCurrentDirectory(sizeof(workingdir)-1, workingdir); + _snprintf(modname, sizeof(modname), "%s/", enginebasedir); + for (slash = modname; *slash; slash++) + if (*slash == '/') + *slash = '\\'; + PathCombine(absbase, workingdir, modname); + if (PathRelativePathToA(modname, absbase, FILE_ATTRIBUTE_DIRECTORY, workingdir, FILE_ATTRIBUTE_DIRECTORY)) + { + if (!strncmp(modname, ".\\", 2)) + memmove(modname, modname+2, strlen(modname+2)+1); + slash = strchr(modname, '/'); + if (slash) + *slash = 0; + slash = strchr(modname, '\\'); + if (slash) + *slash = 0; + if (!*modname) + _snprintf(modname, sizeof(modname), "unknownmod"); + } + } + + QC_snprintfz(tmp, sizeof(tmp), "default.fmf", modname); + mandata = QCC_ReadFile (tmp, NULL, 0, &manlen); + if (!mandata) + { + QC_snprintfz(tmp, sizeof(tmp), "%s.fmf", modname); + mandata = QCC_ReadFile (tmp, NULL, 0, &manlen); + } + if (!mandata) + { + QC_snprintfz(tmp, sizeof(tmp), "../default.fmf", modname); + mandata = QCC_ReadFile (tmp, NULL, 0, &manlen); + } + if (!mandata) + { + QC_snprintfz(tmp, sizeof(tmp), "../%s.fmf", modname); + mandata = QCC_ReadFile (tmp, NULL, 0, &manlen); + } + if (!mandata) + { + QC_snprintfz(tmp, sizeof(tmp), "%s.fmf", modname); + if (!PromptForFile("Please Select Manifest File", "FTE Manifests\0*.fmf\0All files\0*.*\0", ".", tmp, tmp, sizeof(tmp), true)) + mandata = QCC_ReadFile (tmp, NULL, 0, &manlen); + } + + if (!mandata) + { + FILE *f; + if (MessageBox(mainwindow, "Creating an installer requires a manifest.\nCreate+edit one now?", "Create Installer", MB_OKCANCEL) != IDOK) + return; + + f = fopen(tmp, "wb"); + fprintf(f, "FTEManifestVer 1\n"); + fprintf(f, "///basic information\n"); + fprintf(f, "game \"quake\" ///change this to isolate your mod from quake. if the game is known to the engine itself then other settings will receive default values unless overriden. Should be safe to use as a filename, so should have no spaces or full stops.\n"); + fprintf(f, "name \"Quake\" ///this is the full name of your game that you wish the engine to display. Use spaces.\n"); + fprintf(f, "//protocolname \"FTE-Quake\" ///allows isolation from other games using the same engine. Should only be changed for standalone total conversions.\n"); + fprintf(f, "///filesystem\n"); + fprintf(f, "//basegame \"id1\"\n"); + fprintf(f, "//basegame \"qw\"\n"); + fprintf(f, "//basegame \"*fte\" ///* prefix means its never networked\n"); + fprintf(f, "gamedir \"%s\"\n", modname); + fprintf(f, "//disablehomedir 0\n"); + fprintf(f, "///required packages. add more as needed. these will be downloaded+installed from the get-go\n"); + fprintf(f, "///the engine will tell you the correct crc if you get it wrong/don't know it. Its not mandatory, but allows for autoupdates if the fmf changes.\n"); + fprintf(f, "//package id1/example.pk3\tmirror \"https://example.com/example.pak\"\t//crc 0xdeadbeef\n"); + fprintf(f, "///updateurl points to an (updated) copy of this manifest file, so you can update basic stuff easily. should always be https\n"); + fprintf(f, "//updateurl \"https://example.com/example.fmf\"\n"); + fprintf(f, "///downloadsurl is a list of optional updates, including engine updates, displayed via the updates menu. should always be https\n"); + fprintf(f, "//downloadsurl \"https://fte.triptohell.info/downloadables.php\"\n"); + fprintf(f, "///eula displayed when first installing\n"); + fprintf(f, "//eula \"By using this game software, you assign your eternal soul to me for me to do as I wish, including but not limited to trading it for a pint of beer.\"\n"); + fclose(f); + EditFile(tmp, -1, false); + return; + } + else + { + + + + + QC_snprintfz(newname, sizeof(newname), "%s_setup.exe", modname); + + if (!PromptForFile("Please Select Output Executable", "Executables\0*.exe\0All files\0*.*\0", basedir, newname, tmpname, sizeof(tmpname), true)) + return; + + PathCombine(newname, basedir, tmpname); + PathCombine(tmpname, basedir, "tmp.exe"); + PathCombine(ourname, enginebasedir, enginebinary); + + if (!CopyFile(ourname, tmpname, FALSE)) + error = "output already exists or cannot be written"; + + if (!(bin = BeginUpdateResource(tmpname, FALSE))) + error = "BeginUpdateResource failed"; + else + { + QC_snprintfz(tmp, sizeof(tmp), "%s.ico", modname); + pngdata = QCC_ReadFile (tmp, NULL, 0, &pnglen); + if (!pngdata) + { + QC_snprintfz(tmp, sizeof(tmp), "%s.png", modname); + pngdata = QCC_ReadFile (tmp, NULL, 0, &pnglen); + } + if (!pngdata) + pngdata = QCC_ReadFile ("default.png", NULL, 0, &pnglen); + + if (!pngdata) + if (PromptForFile("Please Select Icon", "Icons\0*.ico;*.png\0All files\0*.*\0", ".", tmp, tmp, sizeof(tmp), false)) + pngdata = QCC_ReadFile (tmp, NULL, 0, &pnglen); + + if (pngdata && pngdata[0] == 0 && pngdata[1] == 0 && pngdata[2] == 1 && pngdata[3] == 0) + { + unsigned int iconid = 1, img; + unsigned short images; + struct { + BYTE bWidth; + BYTE bHeight; + BYTE bColorCount; + BYTE bReserved; + WORD wPlanes; + WORD wBitCount; + DWORD dwBytesInRes; + DWORD dwOffset; + } *iconinfo; + icon_group_t icondata; + memset(&icondata, 0, sizeof(icondata)); + icondata.idType = 1; + + images = pngdata[4] | (pngdata[5]<<8); + + UpdateResource(bin, RT_GROUP_ICON, MAKEINTRESOURCE(1), RESLANG, NULL, 0); + UpdateResource(bin, RT_GROUP_ICON, MAKEINTRESOURCE(2), RESLANG, NULL, 0); + + for (iconinfo = (void*)(pngdata+6), img = 0; img < images; iconinfo++, img++) + { + if (!error && !UpdateResource(bin, RT_ICON, MAKEINTRESOURCE(iconid), 0, pngdata+iconinfo->dwOffset, iconinfo->dwBytesInRes)) + error = "UpdateResource failed (icon data)"; + + //and make a copy of it in the icon list + icondata.idEntries[icondata.idCount].bWidth = iconinfo->bWidth; + icondata.idEntries[icondata.idCount].bHeight = iconinfo->bHeight; + icondata.idEntries[icondata.idCount].wBitCount = iconinfo->wBitCount; + icondata.idEntries[icondata.idCount].wPlanes = iconinfo->wPlanes; + icondata.idEntries[icondata.idCount].bColorCount = iconinfo->bColorCount; + icondata.idEntries[icondata.idCount].dwBytesInRes = iconinfo->dwBytesInRes; + icondata.idEntries[icondata.idCount].nId = iconid++; + icondata.idCount++; + } + + if (!error && !UpdateResource(bin, RT_GROUP_ICON, MAKEINTRESOURCE(1), RESLANG, &icondata, (qbyte*)&icondata.idEntries[icondata.idCount] - (qbyte*)&icondata)) + error = "UpdateResource failed (icon group)"; + } + else if (pngdata) + { + icon_group_t icondata; + qbyte *rgbadata; + int imgwidth, imgheight; + int iconid = 1; + memset(&icondata, 0, sizeof(icondata)); + icondata.idType = 1; + + if (MessageBox(mainwindow, error, "Embedding PNGs is probably buggy/suboptimal. You should consider using an .ico instead.\nContinue anyway?", MB_OKCANCEL) != IDOK) + error = "User aborted"; + + UpdateResource(bin, RT_GROUP_ICON, MAKEINTRESOURCE(1), RESLANG, NULL, 0); + UpdateResource(bin, RT_GROUP_ICON, MAKEINTRESOURCE(2), RESLANG, NULL, 0); + // UpdateResource(bin, RT_GROUP_ICON, MAKEINTRESOURCE(3), RESLANG, NULL, 0); + + rgbadata = ReadPNGFile(pngdata, pnglen, &imgwidth, &imgheight, "default.png"); + if (!rgbadata) + error = "unable to read icon image"; + else + { + void *data = NULL; + unsigned int datalen = 0; + unsigned int i; + +// extern cvar_t gl_lerpimages; +// gl_lerpimages.ival = 1; + for (i = 0; i < sizeof(icosizes)/sizeof(icosizes[0]); i++) + { + unsigned int x,y; + unsigned int pixels; + if (icosizes[i].width > imgwidth || icosizes[i].height > imgheight) + continue; //ignore icons if they're bigger than the original icon. + + if (icosizes[i].bpp == 32 && icosizes[i].width >= 128 && icosizes[i].height >= 128 && icosizes[i].width == imgwidth && icosizes[i].height == imgheight) + { //png compression. oh look. we originally loaded a png! + data = pngdata; + datalen = pnglen; + } + else + { + //generate the bitmap info + BITMAPV4HEADER *bi; + qbyte *out, *outmask; + qbyte *in, *inrow; + unsigned int outidx; + + pixels = icosizes[i].width * icosizes[i].height; + + bi = data = malloc(sizeof(*bi) + icosizes[i].width * icosizes[i].height * 5 + icosizes[i].height*4); + memset(bi,0, sizeof(BITMAPINFOHEADER)); + bi->bV4Size = sizeof(BITMAPINFOHEADER); + bi->bV4Width = icosizes[i].width; + bi->bV4Height = icosizes[i].height * 2; //icons are logically double-height, with the second half being a silly alpha mask. + bi->bV4Planes = 1; + bi->bV4BitCount = icosizes[i].bpp; + bi->bV4V4Compression = BI_RGB; + bi->bV4ClrUsed = (icosizes[i].bpp>=32?0:(1u<bV4Size; + out = (qbyte*)data + datalen; + datalen += ((icosizes[i].width*icosizes[i].bpp/8+3)&~3) * icosizes[i].height; + outmask = (qbyte*)data + datalen; + datalen += ((icosizes[i].width+31)&~31)/8 * icosizes[i].height; + + in = malloc(pixels*4); + Image_ResampleTexture((unsigned int*)rgbadata, imgwidth, imgheight, (unsigned int*)in, icosizes[i].width, icosizes[i].height); + + inrow = in; + outidx = 0; + if (icosizes[i].bpp == 32) + { + for (y = 0; y < icosizes[i].height; y++) + { + inrow = in + 4*icosizes[i].width*(icosizes[i].height-1-y); + for (x = 0; x < icosizes[i].width; x++) + { + if (inrow[3] == 0) //transparent + outmask[outidx>>3] |= 1u<<(outidx&7); + else + { + out[0] = inrow[2]; + out[1] = inrow[1]; + out[2] = inrow[0]; + } + out += 4; + outidx++; + inrow += 4; + } + if (x & 3) + out += 4 - (x&3); + outidx = (outidx + 31)&~31; + } + } + } + + if (!error && !UpdateResource(bin, RT_ICON, MAKEINTRESOURCE(iconid), 0, data, datalen)) + error = "UpdateResource failed (icon data)"; + + //and make a copy of it in the icon list + icondata.idEntries[icondata.idCount].bWidth = (icosizes[i].width<256)?icosizes[i].width:0; + icondata.idEntries[icondata.idCount].bHeight = (icosizes[i].height<256)?icosizes[i].height:0; + icondata.idEntries[icondata.idCount].wBitCount = icosizes[i].bpp; + icondata.idEntries[icondata.idCount].wPlanes = 1; + icondata.idEntries[icondata.idCount].bColorCount = (icosizes[i].bpp>=8)?0:(1u<next) + for (f = filelist,num=0; f ; f=f->next) { if (f->type == FT_CODE && !sourceaswell) continue; @@ -714,7 +714,7 @@ int WriteSourceFiles(int h, pbool sourceaswell, pbool legacyembed) } startofs = SafeSeek(h, 0, SEEK_CUR); idf = qccHunkAlloc(sizeof(includeddatafile_t)*num); - for (f = qcc_sourcefile,num=0; f ; f=f->next) + for (f = filelist,num=0; f ; f=f->next) { if (f->type == FT_CODE && !sourceaswell) continue; @@ -783,7 +783,7 @@ int WriteSourceFiles(int h, pbool sourceaswell, pbool legacyembed) char centralheader[46+sizeof(f->filename)]; int centraldirsize; ofs = SafeSeek(h, 0, SEEK_CUR); - for (f = qcc_sourcefile,num=0; f ; f=f->next) + for (f = filelist,num=0; f ; f=f->next) { size_t fnamelen; if (f->type == FT_CODE && !sourceaswell) @@ -833,8 +833,6 @@ int WriteSourceFiles(int h, pbool sourceaswell, pbool legacyembed) else ofs = 0; - qcc_sourcefile = NULL; - printf("Embedded files take %u bytes\n", SafeSeek(h, 0, SEEK_CUR) - startofs); return ofs; @@ -2081,17 +2079,17 @@ strofs = (strofs+3)&~3; { case QCF_QTEST: progs.version = PROG_QTESTVERSION; - progs.ofsfiles = WriteSourceFiles(h, debugtarget, false); + progs.ofsfiles = WriteSourceFiles(qcc_sourcefile, h, debugtarget, false); break; case QCF_KK7: progs.version = PROG_KKQWSVVERSION; - progs.ofsfiles = WriteSourceFiles(h, debugtarget, false); + progs.ofsfiles = WriteSourceFiles(qcc_sourcefile, h, debugtarget, false); break; default: case QCF_STANDARD: case QCF_HEXEN2: //urgh progs.version = PROG_VERSION; - progs.ofsfiles = WriteSourceFiles(h, debugtarget, false); + progs.ofsfiles = WriteSourceFiles(qcc_sourcefile, h, debugtarget, false); break; case QCF_DARKPLACES: case QCF_FTE: @@ -2147,9 +2145,10 @@ strofs = (strofs+3)&~3; progs.numtypes = 0; } - progs.ofsfiles = WriteSourceFiles(h, debugtarget, true); + progs.ofsfiles = WriteSourceFiles(qcc_sourcefile, h, debugtarget, true); break; } + qcc_sourcefile = NULL; if (progs.version != PROG_EXTENDEDVERSION && progs.numbodylessfuncs) printf ("WARNING: progs format cannot handle extern functions\n"); diff --git a/engine/qclib/qcd_main.c b/engine/qclib/qcd_main.c index 11f75546a..4319504c6 100644 --- a/engine/qclib/qcd_main.c +++ b/engine/qclib/qcd_main.c @@ -211,7 +211,18 @@ pbool QC_EnumerateFilesFromBlob(const void *blob, size_t blobsize, void (*cb)(co el = QC_ReadRawShort(cd+30); cl = QC_ReadRawShort(cd+32); - if (QC_ReadRawShort(cd+8) != 0) + //1=encrypted + //2,4=encoder flags + //8=crc etc info is dodgy + //10=enhanced deflate + //20=patchdata + //40=strong encryption + //80,100,200,400=unused + //800=utf-8 + //1000=enh comp + //2000=masked localheader + //4000,8000=reserved + if (QC_ReadRawShort(cd+8) & ~0x80e) continue; { @@ -221,7 +232,7 @@ pbool QC_EnumerateFilesFromBlob(const void *blob, size_t blobsize, void (*cb)(co if (QC_ReadRawInt(le+0) != 0x04034b50) continue; - if (QC_ReadRawShort(le+6) != 0) //general purpose flags + if (QC_ReadRawShort(le+6) & ~0x80e) //general purpose flags continue; method = QC_ReadRawShort(le+8); if (method != 0 && method != 8) diff --git a/engine/server/pr_cmds.c b/engine/server/pr_cmds.c index 0361ce1a0..f3c36974e 100644 --- a/engine/server/pr_cmds.c +++ b/engine/server/pr_cmds.c @@ -10059,7 +10059,7 @@ BuiltinList_t BuiltinList[] = { //nq qw h2 ebfs {"findfloat", PF_FindFloat, 0, 0, 0, 98, D("#define findentity findfloat\nentity(entity start, .__variant fld, __variant match)", "Equivelent to the find builtin, but instead of comparing strings contents, this builtin compares the raw values. This builtin requires multiple calls in order to scan all entities - set start to the previous call's return value.\nworld is returned when there are no more entities.")}, // #98 (DP_QC_FINDFLOAT) {"checkextension", PF_checkextension, 99, 99, 0, 99, D("float(string extname)", "Checks for an extension by its name (eg: checkextension(\"FRIK_FILE\") says that its okay to go ahead and use strcat).\nUse cvar(\"pr_checkextension\") to see if this builtin exists.")}, // #99 //darkplaces system - query a string to see if the mod supports X Y and Z. - {"checkbuiltin", PF_checkbuiltin, 0, 0, 0, 0, D("float(__variant funcref)", "Checks to see if the specified builtin is supported/mapped. This is intended as a way to check for #0 functions, allowing for simple single-builtin functions.")}, + {"checkbuiltin", PF_checkbuiltin, 0, 0, 0, 0, D("float(__variant funcref)", "Checks to see if the specified builtin is supported/mapped. This is intended as a way to check for #0 functions, allowing for simple single-builtin functions. Warning, if two different engines map different builtins to the same number, then this function will not tell you which will be called, only that it won't crash (the exception being #0, which are remapped as available).")}, {"builtin_find", PF_builtinsupported,100, 100, 0, 100, D("float(string builtinname)", "Looks to see if the named builtin is valid, and returns the builtin number it exists at.")}, // #100 //per builtin system. {"anglemod", PF_anglemod, 0, 0, 0, 102, "float(float value)"}, {"qsg_cvar_string", PF_cvar_string, 0, 0, 0, 103, D("string(string cvarname)","An old/legacy equivelent of more recent/common builtins in order to read a cvar's string value."), true}, diff --git a/engine/server/sv_ccmds.c b/engine/server/sv_ccmds.c index ab415a637..4c48bad38 100644 --- a/engine/server/sv_ccmds.c +++ b/engine/server/sv_ccmds.c @@ -1630,6 +1630,12 @@ static void SV_StuffToClient_f(void) char *c; char *key; + if (Cmd_Argc() < 3) + { + Con_Printf("%s \n", Cmd_Argv(0)); + return; + } + Cmd_ShiftArgs(1, Cmd_ExecLevel==RESTRICT_LOCAL); if (!strcmp(Cmd_Argv(1), "bind")) { diff --git a/engine/server/sv_main.c b/engine/server/sv_main.c index b2755b16b..f22ffe457 100644 --- a/engine/server/sv_main.c +++ b/engine/server/sv_main.c @@ -77,6 +77,7 @@ cvar_t allow_download = CVARD("allow_download", "1", "If 1, permits downloadi cvar_t allow_download_skins = CVARD("allow_download_skins", "1", "0 blocks downloading of any file in the skins/ directory"); cvar_t allow_download_models = CVARD("allow_download_models", "1", "0 blocks downloading of any file in the progs/ or models/ directory"); cvar_t allow_download_sounds = CVARD("allow_download_sounds", "1", "0 blocks downloading of any file in the sound/ directory"); +cvar_t allow_download_particles = CVARD("allow_download_particles", "1", "0 blocks downloading of any file in the particles/ directory"); cvar_t allow_download_demos = CVARD("allow_download_demos", "1", "0 blocks downloading of any file in the demos/ directory"); cvar_t allow_download_maps = CVARD("allow_download_maps", "1", "0 blocks downloading of any file in the maps/ directory"); cvar_t allow_download_logs = CVARD("allow_download_logs", "0", "1 permits downloading files with the extension .log\n"CON_ERROR"THIS IS DANGEROUS AS IT POTENTIALLY ALLOWS PEOPLE TO SEE PASSWORDS OR OTHER PRIVATE INFORMATION.\nNote that it can be switch on/off via rcon."); @@ -87,7 +88,7 @@ cvar_t allow_download_textures = CVARD("allow_download_textures", "1", "0 block cvar_t allow_download_packages = CVARD("allow_download_packages", "1", "if 1, permits downloading files (from root directory or elsewhere) with known package extensions (eg: pak+pk3). Packages with a name starting 'pak' are covered by allow_download_copyrighted as well. This does not prevent "); cvar_t allow_download_refpackages = CVARD("allow_download_refpackages", "1", "If set to 1, packages that contain files needed during spawn functions will be become 'referenced' and automatically downloaded to clients.\nThis cvar should probably not be set if you have large packages that provide replacement pickup models on public servers.\nThe path command will show a '(ref)' tag next to packages which clients will automatically attempt to download."); cvar_t allow_download_wads = CVARD("allow_download_wads", "1", "0 blocks downloading of any file in the wads/ directory, or is in the root directory with the extension .wad"); -cvar_t allow_download_configs = CVARD("allow_download_configs", "0", "1 allows downloading of config files, either with the extension .cfg or in the subdir configs/.\n"CON_ERROR"THIS IS DANGEROUS AS IT CAN ALLOW PEOPLE TO READ YOUR RCON PASSWORD."); +cvar_t allow_download_configs = CVARD("allow_download_configs", "0", "1 allows downloading of config files, either with the extension .cfg or in the subdir configs/.\n"CON_ERROR"THIS IS DANGEROUS AS IT CAN ALLOW PEOPLE TO READ YOUR RCON PASSWORD ETC."); cvar_t allow_download_locs = CVARD("allow_download_locs", "1", "0 blocks downloading of any file in the locs/ directory"); cvar_t allow_download_copyrighted = CVARD("allow_download_copyrighted", "0", "0 blocks download of packages that are considered copyrighted. Specifically, this means packages with a leading 'pak' prefix on the filename.\nIf you take your copyrights seriously, you should also set allow_download_pakmaps 0 and allow_download_pakcontents 0."); cvar_t allow_download_other = CVARD("allow_download_other", "0", "0 blocks downloading of any file that was not covered by any of the directory download blocks."); diff --git a/engine/server/sv_user.c b/engine/server/sv_user.c index e2d07d4ed..68240f48c 100644 --- a/engine/server/sv_user.c +++ b/engine/server/sv_user.c @@ -2767,6 +2767,7 @@ qboolean SV_AllowDownload (const char *name) extern cvar_t allow_download_skins; extern cvar_t allow_download_models; extern cvar_t allow_download_sounds; + extern cvar_t allow_download_particles; extern cvar_t allow_download_demos; extern cvar_t allow_download_maps; extern cvar_t allow_download_textures; @@ -2800,7 +2801,7 @@ qboolean SV_AllowDownload (const char *name) return false; if (*name == '/') //no absolute. return false; - if (strchr(name, '\\')) //no windows paths - grow up lame windows users. + if (strchr(name, '\\')) //no windows paths - grow up you lame windows users. return false; COM_FileExtension(name, ext, sizeof(ext)); @@ -2813,6 +2814,7 @@ qboolean SV_AllowDownload (const char *name) if (!strncmp(name, "package/", 8)) { + //eg: package/id1/foobar.pk3 if (!strcmp("pk4", ext) || !strcmp("pk3", ext) || !strcmp("pak", ext) || (!strncmp(name, "package/downloads/", 18) && !strcmp("zip", ext))) { if (!allow_download_packages.ival) @@ -2839,6 +2841,9 @@ qboolean SV_AllowDownload (const char *name) //sound if (strncmp(name, "sound/", 6) == 0) return !!allow_download_sounds.value; + //particles + if (strncmp(name, "particles/", 6) == 0) + return !!allow_download_particles.value; //demos if (strncmp(name, "demos/", 6) == 0) return !!allow_download_demos.value; @@ -2847,9 +2852,6 @@ qboolean SV_AllowDownload (const char *name) if (strncmp(name, "textures/", 9) == 0) return !!allow_download_textures.value; - if (strncmp(name, "config/", 7) == 0) - return !!allow_download_configs.value; - if (strncmp(name, "locs/", 5) == 0) return !!allow_download_locs.value; @@ -2859,8 +2861,14 @@ qboolean SV_AllowDownload (const char *name) if (!strchr(name, '/') && !strcmp("wad", ext)) return !!allow_download_wads.value; + //configs + if (strncmp(name, "config/", 7) == 0) + return !!allow_download_configs.value; + if (!strcmp("cfg", ext)) + return !!allow_download_configs.value; + //pak/pk3s. - if (!strcmp("pk4", ext) || !strcmp("pk3", ext) || !strcmp("pak", ext)) + if (!strchr(name, '/') && (!strcmp("pk4", ext) || !strcmp("pk3", ext) || !strcmp("pak", ext))) { if (strnicmp(name, "pak", 3)) //don't give out core pak/pk3 files. This matches q3 logic. return !!allow_download_packages.value; @@ -2868,9 +2876,6 @@ qboolean SV_AllowDownload (const char *name) return !!allow_download_packages.value && !!allow_download_copyrighted.value; } - if (!strcmp("cfg", ext)) - return !!allow_download_configs.value; - //root of gamedir if (!strchr(name, '/') && !allow_download_root.value) { @@ -2960,9 +2965,9 @@ static int SV_LocateDownload(char *name, flocation_t *loc, char **replacementnam static const char *alternatives[][4] = { //orig-path, orig-ext, new-path, new-ext //nexuiz qc names [sound/]sound/foo.wav but expects sound/foo.ogg and variations of that (the [sound/] is implied, but ignored) - {"", "", ".wav", ".ogg"},//nexuiz qc names .wav, but the paks use .ogg + {"", "", ".wav", ".ogg"}, //nexuiz qc names .wav, but the paks use .ogg {"sound/", "", ".wav", ".wav"}, //nexuiz qc names sound/ but that's normally implied, resulting in doubles that don't exist in the filesystem - {"sound/", "", ".wav", ".ogg"} //both of nexuiz's issues + {"sound/", "", ".wav", ".ogg"} //both of nexuiz's issues at the same time }; for (alt = 0; alt < countof(alternatives); alt++) diff --git a/engine/sw/sw.h b/engine/sw/sw.h index 503558adb..9724fb1cd 100644 --- a/engine/sw/sw.h +++ b/engine/sw/sw.h @@ -167,7 +167,7 @@ void SWBE_DrawMesh_List(shader_t *shader, int nummeshes, struct mesh_s **mesh, s void SWBE_DrawMesh_Single(shader_t *shader, struct mesh_s *meshchain, struct vbo_s *vbo, unsigned int be_flags); void SWBE_SubmitBatch(struct batch_s *batch); struct batch_s *SWBE_GetTempBatch(void); -void SWBE_DrawWorld(batch_t **worldbatches, qbyte *vis); +void SWBE_DrawWorld(batch_t **worldbatches); void SWBE_Init(void); void SWBE_GenBrushModelVBO(struct model_s *mod); void SWBE_ClearVBO(struct vbo_s *vbo); diff --git a/engine/sw/sw_backend.c b/engine/sw/sw_backend.c index dd81cf080..7656e4a26 100644 --- a/engine/sw/sw_backend.c +++ b/engine/sw/sw_backend.c @@ -610,7 +610,7 @@ void SWBE_Set2D(void) SWBE_UpdateUniforms(); } -void SWBE_DrawWorld(batch_t **worldbatches, qbyte *vis) +void SWBE_DrawWorld(batch_t **worldbatches) { batch_t *batches[SHADER_SORT_COUNT]; diff --git a/engine/vk/vk_backend.c b/engine/vk/vk_backend.c index e4e38e01e..87cb1325d 100644 --- a/engine/vk/vk_backend.c +++ b/engine/vk/vk_backend.c @@ -5,6 +5,8 @@ #include "gl_draw.h" #include "shader.h" +//FIXME: instead of switching rendertargets and back, we should be using an alternative queue. + #define PERMUTATION_BEM_DEPTHONLY (1u<<14) #define PERMUTATION_BEM_WIREFRAME (1u<<15) @@ -1433,8 +1435,8 @@ static void *fte_restrict VKBE_AllocateBufferSpace(enum dynbuf_e type, size_t da { //flush the old one, just in case. VkMappedMemoryRange range = {VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE}; - range.offset = 0; - range.size = b->offset; + range.offset = b->flushed; + range.size = b->offset-b->flushed; range.memory = b->stagingmemory; vkFlushMappedMemoryRanges(vk.device, 1, &range); @@ -1442,9 +1444,9 @@ static void *fte_restrict VKBE_AllocateBufferSpace(enum dynbuf_e type, size_t da { struct vk_fencework *fence = VK_FencedBegin(NULL, 0); VkBufferCopy bcr = {0}; - bcr.srcOffset = 0; - bcr.dstOffset = 0; - bcr.size = b->offset; + bcr.srcOffset = b->flushed; + bcr.dstOffset = b->flushed; + bcr.size = b->offset-b->flushed; vkCmdCopyBuffer(fence->cbuf, b->stagingbuf, b->devicebuf, 1, &bcr); VK_FencedSubmit(fence); } @@ -1453,6 +1455,7 @@ static void *fte_restrict VKBE_AllocateBufferSpace(enum dynbuf_e type, size_t da VKBE_AllocNewBuffer(&b->next, type); b = vk.dynbuf[type] = b->next; b->offset = 0; + b->flushed = 0; } *buf = b->renderbuf; @@ -1499,28 +1502,29 @@ void VKBE_FlushDynamicBuffers(void) uint32_t i; struct dynbuffer *d; VkMappedMemoryRange range = {VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE}; - range.offset = 0; for (i = 0; i < DB_MAX; i++) { d = vk.dynbuf[i]; - if (!d->offset) + if (d->flushed == d->offset) continue; - range.size = d->offset; + range.offset = d->flushed; + range.size = d->offset - d->flushed; range.memory = d->stagingmemory; vkFlushMappedMemoryRanges(vk.device, 1, &range); if (d->devicebuf != VK_NULL_HANDLE) { VkBufferCopy bcr = {0}; - bcr.srcOffset = 0; - bcr.dstOffset = 0; - bcr.size = d->offset; + bcr.srcOffset = d->flushed; + bcr.dstOffset = d->flushed; + bcr.size = d->offset - d->flushed; if (!fence) fence = VK_FencedBegin(NULL, 0); vkCmdCopyBuffer(fence->cbuf, d->stagingbuf, d->devicebuf, 1, &bcr); } + d->flushed = d->offset; } if (fence) @@ -1543,7 +1547,7 @@ void VKBE_RestartFrame(void) for (i = 0; i < DB_MAX; i++) { vk.dynbuf[i] = vk.frame->dynbufs[i]; - vk.dynbuf[i]->offset = 0; + vk.dynbuf[i]->offset = vk.dynbuf[i]->flushed = 0; } shaderstate.activepipeline = VK_NULL_HANDLE; @@ -3540,6 +3544,28 @@ static void BE_DrawMeshChain_Internal(void) if (p->texgen == T_GEN_FULLBRIGHT && !TEXLOADED(shaderstate.curtexnums->fullbright)) continue; + if (p->prog) + { + vertexbuffers[VK_BUFF_TC] = shaderstate.batchvbo->texcoord.vk.buff; + vertexoffsets[VK_BUFF_TC] = shaderstate.batchvbo->texcoord.vk.offs; + vertexbuffers[VK_BUFF_LMTC] = shaderstate.batchvbo->lmcoord[0].vk.buff; + vertexoffsets[VK_BUFF_LMTC] = shaderstate.batchvbo->lmcoord[0].vk.offs; + + BE_GenerateColourMods(vertcount, p, &vertexbuffers[VK_BUFF_COL], &vertexoffsets[VK_BUFF_COL]); + + vertexbuffers[VK_BUFF_NORM] = shaderstate.staticbuf; + vertexoffsets[VK_BUFF_NORM] = sizeof(vec4_t)*65536; + vertexbuffers[VK_BUFF_SDIR] = shaderstate.staticbuf; + vertexoffsets[VK_BUFF_SDIR] = vertexoffsets[VK_BUFF_NORM] + sizeof(vec3_t)*65536; + vertexbuffers[VK_BUFF_TDIR] = shaderstate.staticbuf; + vertexoffsets[VK_BUFF_TDIR] = vertexoffsets[VK_BUFF_SDIR] + sizeof(vec3_t)*65536; + + vkCmdBindVertexBuffers(vk.frame->cbuf, 0, VK_BUFF_MAX, vertexbuffers, vertexoffsets); + if (BE_SetupMeshProgram(p->prog, p, altshader->flags, idxcount)) + vkCmdDrawIndexed(vk.frame->cbuf, idxcount, 1, idxfirst, 0, 0); + continue; + } + if (shaderstate.batchvbo) { //texcoords are all compatible with static arrays, supposedly if (p->tcgen == TC_GEN_LIGHTMAP) @@ -3636,7 +3662,7 @@ qboolean VKBE_GenerateRTLightShader(unsigned int lmode) (lmode & LSHADER_CUBE)?"#CUBE=1":"#CUBE=0") , SUF_NONE, LIGHTPASS_SHADER); } - if (!shaderstate.shader_rtlight[lmode]->prog) + if (shaderstate.shader_rtlight[lmode]->flags & SHADER_NODRAW) return false; return true; } @@ -5679,7 +5705,7 @@ void VKBE_RenderShadowBuffer(struct vk_shadowbuffer *buf) vkCmdBindVertexBuffers(vk.frame->cbuf, 0, 1, &buf->vbuffer, &buf->voffset); vkCmdBindIndexBuffer(vk.frame->cbuf, buf->ibuffer, buf->ioffset, VK_INDEX_TYPE); - if (BE_SetupMeshProgram(depthonlyshader->prog, depthonlyshader->passes, 0, buf->numindicies)) + if (BE_SetupMeshProgram(depthonlyshader->passes[0].prog, depthonlyshader->passes, 0, buf->numindicies)) vkCmdDrawIndexed(vk.frame->cbuf, buf->numindicies, 1, 0, 0, 0); } @@ -5720,7 +5746,10 @@ qboolean VKBE_BeginShadowmap(qboolean isspot, uint32_t width, uint32_t height) struct shadowmaps_s *shad = &shaderstate.shadow[isspot]; unsigned int sbuf; - vkCmdEndRenderPass(vk.frame->cbuf); +// const qboolean altqueue = false; + +// if (!altqueue) +// vkCmdEndRenderPass(vk.frame->cbuf); if (shad->width != width || shad->height != height) { @@ -5898,40 +5927,53 @@ void VKBE_DoneShadows(void) // struct shadowmaps_s *shad = &shaderstate.shadow[isspot]; VkViewport viewport; +// const qboolean altqueue = false; + //we've rendered the shadowmap, but now we need to blit it to the screen //so set stuff back to the main view. FIXME: do these in batches to ease the load on tilers. vkCmdEndRenderPass(vk.frame->cbuf); - /* - set_image_layout(vk.frame->cbuf, shad->image, VK_IMAGE_ASPECT_DEPTH_BIT, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL, VK_ACCESS_SHADER_READ_BIT); - + /*if (altqueue) { - VkImageMemoryBarrier imgbarrier = {VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER}; - imgbarrier.srcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; - imgbarrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; - imgbarrier.oldLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; - imgbarrier.newLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL; - imgbarrier.image = image; - imgbarrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; - imgbarrier.subresourceRange.baseMipLevel = 0; - imgbarrier.subresourceRange.levelCount = 1; - imgbarrier.subresourceRange.baseArrayLayer = 0; - imgbarrier.subresourceRange.layerCount = 1; - imgbarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - imgbarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0, 0, NULL, 0, NULL, 1, &imgbarrier); + vkCmdSetEvent(alt, shadowcompleteevent); + VKBE_FlushDynamicBuffers(); + VK_Submit_Work(); + vkCmdWaitEvents(main, 1, &shadowcompleteevent, barrierstuff); + vkCmdResetEvent(main, shadowcompleteevent); } - */ + else*/ + { + /* + set_image_layout(vk.frame->cbuf, shad->image, VK_IMAGE_ASPECT_DEPTH_BIT, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL, VK_ACCESS_SHADER_READ_BIT); - vkCmdBeginRenderPass(vk.frame->cbuf, &vk.rendertarg->restartinfo, VK_SUBPASS_CONTENTS_INLINE); + { + VkImageMemoryBarrier imgbarrier = {VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER}; + imgbarrier.srcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + imgbarrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + imgbarrier.oldLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + imgbarrier.newLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL; + imgbarrier.image = image; + imgbarrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; + imgbarrier.subresourceRange.baseMipLevel = 0; + imgbarrier.subresourceRange.levelCount = 1; + imgbarrier.subresourceRange.baseArrayLayer = 0; + imgbarrier.subresourceRange.layerCount = 1; + imgbarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + imgbarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0, 0, NULL, 0, NULL, 1, &imgbarrier); + } + */ - viewport.x = r_refdef.pxrect.x; - viewport.y = r_refdef.pxrect.y;//r_refdef.pxrect.maxheight - (r_refdef.pxrect.y+r_refdef.pxrect.height); //silly GL... - viewport.width = r_refdef.pxrect.width; - viewport.height = r_refdef.pxrect.height; - viewport.minDepth = 0; - viewport.maxDepth = shaderstate.depthrange; - vkCmdSetViewport(vk.frame->cbuf, 0, 1, &viewport); + vkCmdBeginRenderPass(vk.frame->cbuf, &vk.rendertarg->restartinfo, VK_SUBPASS_CONTENTS_INLINE); + + viewport.x = r_refdef.pxrect.x; + viewport.y = r_refdef.pxrect.y;//r_refdef.pxrect.maxheight - (r_refdef.pxrect.y+r_refdef.pxrect.height); //silly GL... + viewport.width = r_refdef.pxrect.width; + viewport.height = r_refdef.pxrect.height; + viewport.minDepth = 0; + viewport.maxDepth = shaderstate.depthrange; + vkCmdSetViewport(vk.frame->cbuf, 0, 1, &viewport); + } VKBE_SelectEntity(&r_worldentity); @@ -5979,7 +6021,7 @@ void VKBE_BeginShadowmapFace(void) } #endif -void VKBE_DrawWorld (batch_t **worldbatches, qbyte *vis) +void VKBE_DrawWorld (batch_t **worldbatches) { batch_t *batches[SHADER_SORT_COUNT]; RSpeedLocals(); @@ -6007,7 +6049,7 @@ void VKBE_DrawWorld (batch_t **worldbatches, qbyte *vis) //fixme: figure out some way to safely orphan this data so that we can throw the rest to a worker. BE_GenModelBatches(batches, shaderstate.curdlight, BEM_STANDARD); - if (vis) + if (r_refdef.scenevis) { BE_UploadLightmaps(false); @@ -6021,7 +6063,7 @@ void VKBE_DrawWorld (batch_t **worldbatches, qbyte *vis) r_worldentity.axis[2][2] = 1; #ifdef RTLIGHTS - if (vis && r_shadow_realtime_world.ival) + if (r_refdef.scenevis && r_shadow_realtime_world.ival) shaderstate.identitylighting = r_shadow_realtime_world_lightmaps.value; else #endif @@ -6029,18 +6071,30 @@ void VKBE_DrawWorld (batch_t **worldbatches, qbyte *vis) shaderstate.identitylighting *= r_refdef.hdr_value; shaderstate.identitylightmap = shaderstate.identitylighting / (1<queue) + break; subinfo[subcount].sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; subinfo[subcount].pNext = NULL; subinfo[subcount].waitSemaphoreCount = work->semwait?1:0; @@ -2595,6 +2598,7 @@ static void VK_Submit_DoWork(void) ssem[subcount] = work->semsignal; waitfence = work->fencesignal; fencedwork = work->fencedwork; + subqueue = work->queue; subcount++; @@ -2608,7 +2612,7 @@ static void VK_Submit_DoWork(void) if (subcount || waitfence) { RSpeedMark(); - err = vkQueueSubmit(vk.queue_render, subcount, subinfo, waitfence); + err = vkQueueSubmit(subqueue, subcount, subinfo, waitfence); if (err) { Con_Printf("ERROR: vkQueueSubmit: %i\n", err); @@ -2658,6 +2662,7 @@ void VK_Submit_Work(VkCommandBuffer cmdbuf, VkSemaphore semwait, VkPipelineStage struct vkwork_s *work = Z_Malloc(sizeof(*work)); struct vkwork_s **link; + work->queue = vk.queue_render; work->cmdbuf = cmdbuf; work->semwait = semwait; work->semwaitstagemask = semwaitstagemask; @@ -3074,7 +3079,7 @@ qboolean VK_Init(rendererstate_t *info, const char *sysextname, qboolean (*creat queueinf[0].pNext = NULL; queueinf[0].queueFamilyIndex = vk.queuefam[0]; queueinf[0].queueCount = 1; - queueinf[0].pQueuePriorities = queue_priorities; + queueinf[0].pQueuePriorities = &queue_priorities[0]; queueinf[1].pNext = NULL; queueinf[1].queueFamilyIndex = vk.queuefam[1]; queueinf[1].queueCount = 1; @@ -3083,7 +3088,7 @@ qboolean VK_Init(rendererstate_t *info, const char *sysextname, qboolean (*creat if (vk.queuefam[0] == vk.queuefam[1]) { devinf.queueCreateInfoCount = 1; - + if (queueprops[queueinf[0].queueFamilyIndex].queueCount >= 2 && vk_dualqueue.ival) { queueinf[0].queueCount = 2; diff --git a/engine/vk/vkrenderer.h b/engine/vk/vkrenderer.h index 2ef91264c..868c6e1b8 100644 --- a/engine/vk/vkrenderer.h +++ b/engine/vk/vkrenderer.h @@ -85,6 +85,9 @@ VKFunc(CmdCopyBuffer) \ VKFunc(CmdBlitImage) \ VKFunc(CmdPipelineBarrier) \ + VKFunc(CmdSetEvent) \ + VKFunc(CmdResetEvent) \ + VKFunc(CmdWaitEvents) \ VKFunc(CreateDescriptorSetLayout) \ VKFunc(DestroyDescriptorSetLayout) \ VKFunc(CreatePipelineLayout) \ @@ -219,6 +222,11 @@ struct vk_rendertarg_cube struct vk_rendertarg face[6]; }; +#define VQ_RENDER 0 +#define VQ_PRESENT 1 +#define VQ_ALTRENDER 2 +#define VQ_ALTRENDER_COUNT 16 +#define VQ_COUNT 3 extern struct vulkaninfo_s { unsigned short triplebuffer; @@ -228,10 +236,11 @@ extern struct vulkaninfo_s VkDevice device; VkPhysicalDevice gpu; VkSurfaceKHR surface; - uint32_t queuefam[2]; //queue families, render+present - uint32_t queuenum[2]; //queue families, render+present + uint32_t queuefam[VQ_COUNT]; + uint32_t queuenum[VQ_COUNT]; VkQueue queue_render; VkQueue queue_present; + VkQueue queue_alt[1]; VkPhysicalDeviceMemoryProperties memory_properties; VkCommandPool cmdpool; @@ -265,8 +274,9 @@ extern struct vulkaninfo_s } *descpool; struct dynbuffer { - size_t offset; - size_t size; + size_t flushed; //size already copied to the gpu + size_t offset; //size written by the cpu (that might not yet be flushed) + size_t size; //maximum buffer size size_t align; VkBuffer stagingbuf; VkDeviceMemory stagingmemory; @@ -305,6 +315,7 @@ extern struct vulkaninfo_s struct vkwork_s { struct vkwork_s *next; + VkQueue queue; VkCommandBuffer cmdbuf; VkSemaphore semwait; VkPipelineStageFlags semwaitstagemask; @@ -366,7 +377,7 @@ batch_t *VKBE_GetTempBatch(void); void VKBE_GenBrushModelVBO(model_t *mod); void VKBE_ClearVBO(vbo_t *vbo); void VKBE_UploadAllLightmaps(void); -void VKBE_DrawWorld (batch_t **worldbatches, qbyte *vis); +void VKBE_DrawWorld (batch_t **worldbatches); qboolean VKBE_LightCullModel(vec3_t org, model_t *model); void VKBE_SelectEntity(entity_t *ent); qboolean VKBE_SelectDLight(dlight_t *dl, vec3_t colour, vec3_t axis[3], unsigned int lmode);