void agp_resize_rendertarget( struct agp_rendertarget* tgt, size_t neww, size_t newh) { if (!tgt || !tgt->store){ arcan_warning("attempted resize on broken rendertarget\n"); return; } /* same dimensions, no need to resize */ if (tgt->store->w == neww && tgt->store->h == newh) return; struct storage_info_t* os = tgt->store; /* we inplace- modify, want the refcounter intact */ agp_null_vstore(os); arcan_mem_free(os->vinf.text.raw); os->vinf.text.raw = NULL; os->vinf.text.s_raw = 0; agp_empty_vstore(os, neww, newh); glDeleteFramebuffers(1,&tgt->fbo); glDeleteRenderbuffers(1,&tgt->depth); tgt->fbo = GL_NONE; tgt->depth = GL_NONE; alloc_fbo(tgt, tgt->mode); }
void agp_resize_rendertarget( struct agp_rendertarget* tgt, size_t neww, size_t newh) { if (!tgt || !tgt->store){ arcan_warning("attempted resize on broken rendertarget\n"); return; } /* same dimensions, no need to resize */ if (tgt->store->w == neww && tgt->store->h == newh) return; erase_store(tgt->store); erase_store(tgt->store_back); struct agp_fenv* env = agp_env(); agp_empty_vstore(tgt->store, neww, newh); if (tgt->store_back) agp_empty_vstore(tgt->store_back, neww, newh); /* we inplace- modify, want the refcounter intact */ env->delete_framebuffers(1,&tgt->fbo); env->delete_renderbuffers(1,&tgt->depth); tgt->fbo = GL_NONE; tgt->depth = GL_NONE; alloc_fbo(tgt, false); }
/* * If the shmpage integrity is somehow compromised, * if semaphore use is out of order etc. */ static void pull_killswitch(arcan_evctx* ctx) { arcan_frameserver* ks = (arcan_frameserver*) ctx->synch.killswitch; arcan_sem_post(ctx->synch.handle); arcan_warning("inconsistency while processing " "shmpage events, pulling killswitch.\n"); arcan_frameserver_free(ks); ctx->synch.killswitch = NULL; }
void arcan_event_dump(struct arcan_evctx* ctx) { unsigned front = *ctx->front; size_t count = 0; while (front != *ctx->back){ arcan_warning("slot: %d, category: %d, kind: %d\n", count, ctx->eventbuf[front].io.kind, ctx->eventbuf[front].category); front = (front + 1) % ctx->eventbuf_sz; } }
void agp_dropenv(struct agp_fenv* env) { if (!env || env->cookie != 0xfeedface){ arcan_warning("agp_dropenv() - code issue: called on bad/broken fenv\n"); return; } env->cookie = 0xdeadbeef; arcan_mem_free(env); if (agp_env() == env) agp_setenv(NULL); }
void platform_video_setsynch(const char* arg) { int ind = 0; while(synchopts[ind]){ if (strcmp(synchopts[ind], arg) == 0){ synchopt = (ind > 0 ? ind / 2 : ind); arcan_warning("synchronisation strategy set to (%s)\n", synchopts[ind]); break; } ind += 2; } }
bool switch_appl(const char* appname) { arcan_video_shutdown(); arcan_audio_shutdown(); arcan_event_deinit(arcan_event_defaultctx()); arcan_db_close(&dbhandle); const char* err_msg; if (!arcan_verifyload_appl(appname, &err_msg)){ arcan_warning("(verifyload) in switch app " "failed, reason: %s\n", err_msg); return false; } return true; }
bool platform_video_map_display(arcan_vobj_id id, platform_display_id disp, enum blitting_hint hint) { if (disp != 0) return false; arcan_vobject* vobj = arcan_video_getobject(id); bool isrt = arcan_vint_findrt(vobj) != NULL; if (vobj && vobj->vstore->txmapped != TXSTATE_TEX2D){ arcan_warning("platform_video_map_display(), attempted to map a " "video object with an invalid backing store"); return false; } /* * The constant problem of what are we drawing and how are we drawing it * (rts were initially used for 3d models, vobjs were drawin with inverted ys * and world normally etc. a huge mess) */ size_t drawx = 0, drawy = 0; if (isrt){ arcan_vint_applyhint(vobj, hint, vobj->txcos ? vobj->txcos : arcan_video_display.mirror_txcos, txcos, &drawx, &drawy, &d_width, &d_height, &blackframes); } /* direct VOBJ mapping, prepared for indirect drawying so flip yhint */ else { arcan_vint_applyhint(vobj, (hint & HINT_YFLIP) ? (hint & (~HINT_YFLIP)) : (hint | HINT_YFLIP), vobj->txcos ? vobj->txcos : arcan_video_display.default_txcos, txcos, &drawx, &drawy, &d_width, &d_height, &blackframes); } out_vid = id; return true; }
static char* rep_str(char* instr) { char* beg = strchr(instr, '['); if (!beg) return instr; char* end = strchr(beg+1, ']'); for (size_t i = 0; i < sizeof(envvs)/sizeof(envvs[0]); i++){ if (end) *end = '\0'; if (strcmp(envvs[i], beg+1) != 0) continue; char* exp = arcan_expand_resource("", 1 << i); if (!exp) goto fail; if (!end){ *beg = '\0'; char* newstr = alloc_cat(instr, exp); free(instr); return newstr; } else{ *beg = '\0'; *end = '\0'; char* newstr = alloc_cat(instr, exp); free(instr); char* resstr = alloc_cat(newstr, end+1); free(newstr); return rep_str(resstr); } } fail: arcan_warning("expand failed, no match for supplied string (%s)\n", beg+1); return instr; }
/* * current several namespaces are (legacy) specified relative * to the old resources namespace, since those are expanded in * set_namespace_defaults where the command-line switch isn't * available, we have to generate these dependent namespaces * overrides as well. */ static void override_resspaces(const char* respath) { size_t len = strlen(respath); if (len == 0 || !arcan_isdir(respath)){ arcan_warning("-p argument ignored, invalid path specified."); return; } char debug_dir[ len + sizeof("/logs") ]; char state_dir[ len + sizeof("/savestates") ]; char font_dir[ len + sizeof("/fonts") ]; snprintf(debug_dir, sizeof(debug_dir), "%s/logs", respath); snprintf(state_dir, sizeof(state_dir), "%s/savestates", respath); snprintf(font_dir, sizeof(font_dir), "%s/fonts", respath); arcan_override_namespace(respath, RESOURCE_APPL_SHARED); arcan_override_namespace(debug_dir, RESOURCE_SYS_DEBUG); arcan_override_namespace(state_dir, RESOURCE_APPL_STATE); arcan_override_namespace(font_dir, RESOURCE_SYS_FONT); }
static bool alloc_fbo(struct agp_rendertarget* dst, bool retry) { struct agp_fenv* env = agp_env(); env->gen_framebuffers(1, &dst->fbo); int mode = dst->mode & (~RENDERTARGET_DOUBLEBUFFER); /* need both stencil and depth buffer, but we don't need the data from them */ env->bind_framebuffer(GL_FRAMEBUFFER, dst->fbo); if (mode > RENDERTARGET_DEPTH) { env->framebuffer_texture_2d(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, dst->store->vinf.text.glid, 0); /* need a Z buffer in the offscreen rendering but don't want * bo store it, so setup a renderbuffer */ if (mode > RENDERTARGET_COLOR){ env->gen_renderbuffers(1, &dst->depth); /* could use GL_DEPTH_COMPONENT only if we'd know that there * wouldn't be any clipping in the active rendertarget */ if (!retry){ env->bind_renderbuffer(GL_RENDERBUFFER, dst->depth); env->renderbuffer_storage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, dst->store->w, dst->store->h); env->bind_renderbuffer(GL_RENDERBUFFER, 0); env->framebuffer_renderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, dst->depth); } } } else { /* DEPTH buffer only (shadowmapping, ...) convert the storage to * contain a depth texture */ size_t w = dst->store->w; size_t h = dst->store->h; agp_drop_vstore(dst->store); struct storage_info_t* store = dst->store; memset(store, '\0', sizeof(struct storage_info_t)); store->txmapped = TXSTATE_DEPTH; store->txu = ARCAN_VTEX_CLAMP; store->txv = ARCAN_VTEX_CLAMP; store->scale = ARCAN_VIMAGE_NOPOW2; store->imageproc = IMAGEPROC_NORMAL; store->filtermode = ARCAN_VFILTER_NONE; store->refcount = 1; store->w = w; store->h = h; /* generate ID etc. special path for TXSTATE_DEPTH */ agp_update_vstore(store, true); env->draw_buffer(GL_NONE); env->read_buffer(GL_NONE); env->framebuffer_texture_2d(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, store->vinf.text.glid, 0); } /* basic error handling / status checking * may be possible that we should cache this in the * rendertarget and only call when / if something changes as * it's not certain that drivers won't stall the pipeline on this */ GLenum status = env->check_framebuffer(GL_FRAMEBUFFER); if (status != GL_FRAMEBUFFER_COMPLETE){ arcan_warning("FBO support broken, couldn't create basic FBO:\n"); switch(status){ case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: if (!retry){ arcan_warning("\t Incomplete Attachment, attempting " "simple framebuffer, this will likely break 3D and complex" "clipping operations.\n"); return alloc_fbo(dst, true); } else arcan_warning("\t Simple attachement broke as well " "likely driver issue.\n"); break; #ifdef GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS: arcan_warning("\t Not all attached buffers have " "the same dimensions.\n"); break; #endif case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: arcan_warning("\t One or several FBO attachment points are missing.\n"); break; case GL_FRAMEBUFFER_UNSUPPORTED: arcan_warning("\t Request formats combination unsupported.\n"); break; } if (dst->fbo != GL_NONE) env->delete_framebuffers(1,&dst->fbo); if (dst->depth != GL_NONE) env->delete_renderbuffers(1,&dst->depth); dst->fbo = dst->depth = GL_NONE; return false; } env->bind_framebuffer(GL_FRAMEBUFFER, 0); return true; }
bool arcan_verify_namespaces(bool report) { bool working = true; if (report) arcan_warning("--- Verifying Namespaces: ---\n"); /* 1. check namespace mapping for holes */ for (int i = 0; i < sizeof( namespaces.paths) / sizeof(namespaces.paths[0]); i++){ if (namespaces.paths[i] == NULL){ if (i != (int)log2(RESOURCE_SYS_LIBS)){ working = false; if (report) arcan_warning("%s -- broken\n", lbls[i]); continue; } } if (report) arcan_warning("%s -- OK (%s)\n", lbls[i], namespaces.paths[i]); } if (report) arcan_warning("--- Namespace Verification Completed ---\n"); /* 2. missing; check permissions for each mounted space, i.e. we should be able * to write to state, we should be able to write to appl temporary etc. also * check disk space for possible warning conditions (these should likely also * be emitted as system events) */ if (working){ char* toktmp = strdup(FRAMESERVER_MODESTRING); /* modestring is static, atypestr can only be reduced in bytes used */ if (!atypestr) atypestr = strdup(FRAMESERVER_MODESTRING); char* tokctx, (* tok) = strtok_r(toktmp, " ", &tokctx); if (tok && atypestr){ char* base = arcan_expand_resource("", RESOURCE_SYS_BINS); size_t baselen = strlen(base); /* fix for specialized "do we have default arcan_frameserver? then compact to * afsrv_ for archetype prefix" mode */ size_t sfxlen = sizeof("arcan_frameserver") - 1; if (baselen >= sfxlen){ if (strcmp(&base[baselen - sfxlen], "arcan_frameserver") == 0){ const char* sfx = "afsrv"; memcpy(&base[baselen - sfxlen], sfx, sizeof("afsrv")); } } /* could / should do a more rigorous test of the corresponding afsrv, e.g. * executable, permission and linked shmif version */ atypestr[0] = '\0'; bool first = true; do{ char* fn; char exp[2 + baselen + strlen(tok)]; snprintf(exp, sizeof(exp), "%s_%s", base, tok); if (arcan_isfile(exp)){ if (!first){ strcat(atypestr, " "); } strcat(atypestr, tok); first = false; } } while ((tok = strtok_r(NULL, " ", &tokctx))); free(base); } free(toktmp); } return working; }
int main(int argc, char* argv[]) { bool windowed = false; bool fullscreen = false; bool conservative = false; bool nosound = false; bool in_monitor = getenv("ARCAN_MONITOR_FD") != NULL; bool waitsleep = true; unsigned char debuglevel = 0; int scalemode = ARCAN_VIMAGE_NOPOW2; int width = 640; int height = 480; int winx = -1; int winy = -1; int timedump = 0; float vfalign = 0.6; /* only used when monitor mode is activated, where we want some * of the global paths etc. accessible, but not *all* of them */ FILE* monitor_outf = NULL; int monitor = 0; int monitor_infd = -1; char* monitor_arg = "LOG"; char* dbfname = NULL; int ch; srand( time(0) ); /* VIDs all have a randomized base to provoke crashes in poorly written scripts, * only -g will make their base and sequence repeatable */ while ((ch = getopt_long(argc, argv, "w:h:x:y:?fvVmisp:q:" "t:M:O:o:l:a:d:F:1:2:gr:S", longopts, NULL)) >= 0){ switch (ch) { case '?' : usage(); exit(1); break; case 'w' : width = strtol(optarg, NULL, 10); break; case 'h' : height = strtol(optarg, NULL, 10); break; case 'm' : conservative = true; break; case 'x' : winx = strtol(optarg, NULL, 10); break; case 'y' : winy = strtol(optarg, NULL, 10); break; case 'F' : vfalign = strtof(optarg, NULL); break; case 'f' : fullscreen = true; break; case 's' : windowed = true; break; case 'l' : arcan_libpath = strdup(optarg); break; case 'd' : dbfname = strdup(optarg); break; case 'S' : nosound = true; break; case 'q' : timedump = strtol(optarg, NULL, 10); break; case 'a' : arcan_video_display.msasamples = strtol(optarg, NULL, 10); break; case 'v' : arcan_video_display.vsync = false; break; case 'V' : waitsleep = false; break; case 'p' : arcan_resourcepath = strdup(optarg); break; #ifndef _WIN32 case 'M' : monitor = abs( strtol(optarg, NULL, 10) ); break; case 'O' : monitor_arg = strdup( optarg ); break; #endif case 't' : arcan_themepath = strdup(optarg); break; case 'o' : arcan_binpath = strdup(optarg); break; case 'g' : debuglevel++; srand(0xdeadbeef); break; case 'r' : scalemode = strtol(optarg, NULL, 10); printf("scalemode: %d\n", scalemode); if (scalemode != ARCAN_VIMAGE_NOPOW2 && scalemode != ARCAN_VIMAGE_SCALEPOW2 && scalemode){ arcan_warning("Warning: main(), -r, invalid scalemode. Ignoring.\n"); scalemode = ARCAN_VIMAGE_SCALEPOW2; } break; case '1' : stdout_redirected = true; if (freopen(optarg, "a", stdout) == NULL); break; case '2' : stderr_redirected = true; if (freopen(optarg, "a", stderr) == NULL); break; default: break; } } if (arcan_setpaths() == false) goto error; if (check_theme(*(argv + optind))) arcan_themename = *(argv + optind); else if (check_theme("welcome")) arcan_themename = "welcome"; else arcan_fatal("No theme found.\n"); if (strcmp(arcan_themename, "arcan") == 0){ arcan_fatal("Theme name 'arcan' is reserved\n"); } if (vfalign > 1.0 || vfalign < 0.0){ arcan_warning("Argument Error (-F, --vsync-falign): " "bad range specified (%f), reverting to default (0.6)\n", vfalign); vfalign = 0.6; } #ifndef _WIN32 /* pipe to file, socket or launch script based on monitor output, * format will be LUA tables with the exception of each cell ending with * #ENDSAMPLE . The block will be sampled, parsed and should return a table * pushed through the sample() function in the LUA space */ if (in_monitor){ monitor_infd = strtol( getenv("ARCAN_MONITOR_FD"), NULL, 10); signal(SIGPIPE, SIG_IGN); } else if (monitor > 0){ extern arcan_benchdata benchdata; benchdata.bench_enabled = true; if (strncmp(monitor_arg, "LOG:", 4) == 0){ monitor_outf = fopen(&monitor_arg[4], "w+"); if (NULL == monitor_outf) arcan_fatal("couldn't open log output (%s) for writing\n", monitor_arg[4]); fcntl(fileno(monitor_outf), F_SETFD, FD_CLOEXEC); } else { int pair[2]; pid_t p1; if (pipe(pair) == 0) ; if ( (p1 = fork()) == 0){ close(pair[1]); /* double-fork to get away from parent */ if (fork() != 0) exit(0); /* * set the descriptor of the inherited pipe as an envvariable, * this will have the program be launched with in_monitor set to true * the monitor args will then be ignored and themename replaced with * the monitorarg */ char monfd_buf[8] = {0}; snprintf(monfd_buf, 8, "%d", pair[0]); setenv("ARCAN_MONITOR_FD", monfd_buf, 1); argv[optind] = strdup(monitor_arg); execv(argv[0], argv); exit(1); } else { /* don't terminate just because the pipe gets broken (i.e. dead monitor) */ close(pair[0]); monitor_outf = fdopen(pair[1], "w"); signal(SIGPIPE, SIG_IGN); } } fullscreen = false; } #endif /* also used as restart point for switiching themes */ themeswitch: /* * try to open the specified database, * if that fails, warn, try to create an empty * database and if that fails, give up. */ if (!dbfname) dbfname = arcan_expand_resource("arcandb.sqlite", true); dbhandle = arcan_db_open(dbfname, arcan_themename); if (!dbhandle) { arcan_warning("Couldn't open database (requested: %s)," "trying to create a new one.\n", dbfname); FILE* fpek = fopen(dbfname, "a"); if (fpek){ fclose(fpek); dbhandle = arcan_db_open(dbfname, arcan_themename); } if (!dbhandle){ arcan_warning("Couldn't create database, giving up.\n"); goto error; } } arcan_video_default_scalemode(scalemode); if (winx != -1 || winy != -1){ char windbuf[64] = {0}; snprintf(windbuf, 63, "SDL_VIDEO_WINDOW_POS=%i,%i", winx >= 0 ? winx : 0, winy >= 0 ? winy : 0); putenv(strdup(windbuf)); } if (windowed) fullscreen = false; /* grab video, (necessary) */ if (arcan_video_init(width, height, 32, fullscreen, windowed, conservative) != ARCAN_OK) { arcan_fatal("Error; Couldn't initialize video system," "try other windowing options (-f, -w, ...)\n"); } errno = 0; /* grab audio, (possible to live without) */ if (ARCAN_OK != arcan_audio_setup(nosound)) arcan_warning("Warning: No audio devices could be found.\n"); arcan_math_init(); /* setup device polling, cleanup, ... */ arcan_evctx* def = arcan_event_defaultctx(); arcan_event_init( def ); #ifdef ARCAN_LED arcan_led_init(); #endif #ifdef ARCAN_HMD arcan_hmd_setup(); #endif /* * MINGW implements putenv, so use this to set * the system subpath path (BIOS, ..) */ if (getenv("ARCAN_SYSTEMPATH") == NULL){ size_t len = strlen(arcan_resourcepath) + sizeof("/games/system") + sizeof("ARCAN_SYSTEMPATH=") + 1; char* const syspath = malloc(len); sprintf(syspath, "ARCAN_SYSTEMPATH=%s/games/system", arcan_resourcepath); arcan_warning("Notice: Using default systempath (%s)\n", syspath); putenv(syspath); } else arcan_warning("Notice: Using systempath from environment (%s)\n", getenv("ARCAN_SYSTEMPATH")); if (getenv("ARCAN_FRAMESERVER_LOGDIR") == NULL){ size_t len = strlen(arcan_resourcepath) + sizeof("/logs") + sizeof("ARCAN_FRAMESERVER_LOGDIR=/logs"); char* const logpath = malloc(len); sprintf(logpath, "ARCAN_FRAMESERVER_LOGDIR=%s/logs", arcan_resourcepath); putenv(logpath); } #ifndef _WIN32 struct rlimit coresize = {0}; /* debuglevel 0, no coredumps etc. * debuglevel 1, maximum 1M coredump, should prioritize stack etc. * would want to be able to hint to mmap which pages that should be avoided * so that we could exclude texture data that both comprise most memory used * and can be recovered through other means. One option for this would be * to push image loading/management to a separate process, * debuglevel > 1, dump everything */ if (debuglevel == 0); else if (debuglevel == 1) coresize.rlim_max = 10 * 1024 * 1024; else coresize.rlim_max = RLIM_INFINITY; coresize.rlim_cur = coresize.rlim_max; setrlimit(RLIMIT_CORE, &coresize); system_page_size = sysconf(_SC_PAGE_SIZE); #endif /* setup VM, map arguments and possible overrides */ struct arcan_luactx* luactx = arcan_lua_alloc(); arcan_lua_mapfunctions(luactx, debuglevel); char* themescr = (char*) malloc(strlen(arcan_themename) + 5); sprintf(themescr, "%s.lua", arcan_themename); char* fn = arcan_find_resource(themescr, ARCAN_RESOURCE_THEME); char* msg = arcan_luaL_dofile(luactx, fn); if (msg != NULL){ arcan_fatal("Fatal: main(), Error loading theme script" "(%s) : (%s)\n", themescr, msg); free(msg); goto error; } free(fn); free(themescr); /* entry point follows the name of the theme, * hand over execution and begin event loop */ if (argc > optind) arcan_lua_pushargv(luactx, argv + optind + 1); arcan_lua_callvoidfun(luactx, "", true); arcan_lua_callvoidfun(luactx, "show", false); bool done = false, framepulse = true, feedtrig = true; float lastfrag = 0.0f; long long int lastflip = arcan_timemillis(); int monitor_counter = monitor; arcan_event ev; arcan_evctx* evctx = arcan_event_defaultctx(); while (!done) { /* pollfeed can actually populate event-loops, assuming we don't exceed a * compile- time threshold */ #ifdef ARCAN_HMD arcan_hmd_update(); #endif if (feedtrig){ feedtrig = false; arcan_video_pollfeed(); } /* NOTE: might be better if this terminates if we're closing in on a * deadline as to not be saturated with an onslaught of I/O events. */ while (1 == arcan_event_poll(evctx, &ev)){ /* * these events can typically be determined in video_tick(), * however there are so many hierarchical dependencies * (linked objs, instances, ...) * that a full delete is not really safe there (e.g. event -> callback -> */ switch (ev.category){ case EVENT_VIDEO: if (ev.kind == EVENT_VIDEO_EXPIRE) arcan_video_deleteobject(ev.data.video.source); break; case EVENT_SYSTEM: /* note the LUA shutdown() call actually emits this event */ if (ev.kind == EVENT_SYSTEM_EXIT) done = true; else if (ev.kind == EVENT_SYSTEM_SWITCHTHEME){ arcan_luaL_shutdown(luactx); arcan_video_shutdown(); arcan_audio_shutdown(); arcan_event_deinit(arcan_event_defaultctx()); arcan_db_close(dbhandle); arcan_themename = strdup(ev.data.system.data.message); goto themeswitch; } else continue; break; } arcan_lua_pushevent(luactx, &ev); } unsigned nticks; float frag = arcan_event_process(arcan_event_defaultctx(), &nticks); if (debuglevel == 4) arcan_warning("main() event_process (%d, %f)\n", nticks, frag); /* priority is always in maintaining logical clock and event processing */ if (nticks > 0){ unsigned njobs; arcan_video_tick(nticks, &njobs); arcan_bench_register_tick(nticks); arcan_audio_tick(nticks); lastfrag = 0.0; if (monitor && !in_monitor){ if (--monitor_counter == 0){ static int mc; char buf[8]; snprintf(buf, 8, "%d", mc++); monitor_counter = monitor; arcan_lua_statesnap(monitor_outf, buf, true); } } /* debugging functionality to generate a dump and abort after n ticks */ if (timedump){ timedump -= nticks; if (timedump <= 0){ arcan_state_dump("timedump", "user requested a dump", __func__); break; } } } /* this is internally buffering and non-blocking, hence the fd use compared * to arcan_lua_statesnap above */ #ifndef _WIN32 if (in_monitor) arcan_lua_stategrab(luactx, "sample", monitor_infd); #endif /* * difficult decision, should we flip or not? * a full- redraw can be costly, so should only really be done if * enough things have changed or if we're closing in on the next * deadline for the unknown video clock, this also depends on if * the user favors energy saving (waitsleep) or responsiveness. */ const int min_respthresh = 9; /* only render if there's enough relevant changes */ if (!waitsleep || nticks > 0 || frag - lastfrag > INTERP_MINSTEP){ /* separate between cheap (possibly vsync off or triple buffering) * flip cost and expensive (vsync on) */ if (arcan_video_display.vsync_timing < 8.0){ unsigned cost = arcan_video_refresh(frag, true); feedtrig = true; arcan_bench_register_cost(cost); arcan_bench_register_frame(); if (framepulse) framepulse = arcan_lua_callvoidfun(luactx, "frame_pulse", false); int delta = arcan_timemillis() - lastflip; lastflip += delta; if (waitsleep && delta < min_respthresh) arcan_timesleep(min_respthresh - delta); } else { int delta = arcan_timemillis() - lastflip; if (delta >= (float)arcan_video_display.vsync_timing * vfalign){ unsigned cost = arcan_video_refresh(frag, true); feedtrig = true; arcan_bench_register_cost(cost); arcan_bench_register_frame(); if (framepulse) framepulse = arcan_lua_callvoidfun(luactx, "frame_pulse", false); lastflip += delta; } } } arcan_audio_refresh(); } arcan_lua_callvoidfun(luactx, "shutdown", false); #ifdef ARCAN_LED arcan_led_shutdown(); #endif #ifdef ARCAN_HMD arcan_hmd_shutdown(); #endif arcan_video_shutdown(); error: exit(1); return 0; }
int main(int argc, char* argv[]) { settings.in_monitor = getenv("ARCAN_MONITOR_FD") != NULL; bool windowed = false; bool fullscreen = false; bool conservative = false; bool nosound = false; unsigned char debuglevel = 0; int scalemode = ARCAN_VIMAGE_NOPOW2; int width = -1; int height = -1; /* only used when monitor mode is activated, where we want some * of the global paths etc. accessible, but not *all* of them */ char* monitor_arg = "LOG"; /* * if we crash in the Lua VM, switch to this app and have it * adopt our external connections */ char* fallback = NULL; char* hookscript = NULL; char* dbfname = NULL; int ch; srand( time(0) ); /* VIDs all have a randomized base to provoke crashes in poorly written scripts, * only -g will make their base and sequence repeatable */ while ((ch = getopt_long(argc, argv, "w:h:mx:y:fsW:d:Sq:a:p:b:B:M:O:t:H:g1:2:V", longopts, NULL)) >= 0){ switch (ch) { case '?' : usage(); exit(EXIT_SUCCESS); break; case 'w' : width = strtol(optarg, NULL, 10); break; case 'h' : height = strtol(optarg, NULL, 10); break; case 'm' : conservative = true; break; case 'f' : fullscreen = true; break; case 's' : windowed = true; break; case 'W' : platform_video_setsynch(optarg); break; case 'd' : dbfname = strdup(optarg); break; case 'S' : nosound = true; break; case 'q' : settings.timedump = strtol(optarg, NULL, 10); break; case 'p' : arcan_override_namespace(optarg, RESOURCE_APPL_SHARED); break; case 'b' : fallback = strdup(optarg); break; case 'V' : fprintf(stdout, "%s\nshmif-%" PRIu64"\nluaapi-%d:%d\n", ARCAN_BUILDVERSION, arcan_shmif_cookie(), LUAAPI_VERSION_MAJOR, LUAAPI_VERSION_MINOR ); exit(EXIT_SUCCESS); break; case 'H' : hookscript = strdup( optarg ); break; #ifndef _WIN32 case 'M' : settings.monitor_counter = settings.monitor = abs( (int)strtol(optarg, NULL, 10) ); break; case 'O' : monitor_arg = strdup( optarg ); break; #endif case 't' : arcan_override_namespace(optarg, RESOURCE_SYS_APPLBASE); arcan_override_namespace(optarg, RESOURCE_SYS_APPLSTORE); break; case 'B' : arcan_override_namespace(optarg, RESOURCE_SYS_BINS); break; case 'g' : debuglevel++; srand(0xdeadbeef); break; break; case '1' : stdout_redirected = true; if (freopen(optarg, "a", stdout) == NULL); break; case '2' : stderr_redirected = true; if (freopen(optarg, "a", stderr) == NULL); break; default: break; } } if (optind >= argc){ arcan_warning("Couldn't start, missing 'applname' argument. \n" "Consult the manpage (man arcan) for additional details\n"); usage(); exit(EXIT_SUCCESS); } /* probe system, load environment variables, ... */ arcan_set_namespace_defaults(); arcan_ffunc_initlut(); #ifdef DISABLE_FRAMESERVERS arcan_override_namespace("", RESOURCE_SYS_BINS); #endif const char* err_msg; if (!arcan_verifyload_appl(argv[optind], &err_msg)){ arcan_warning("arcan_verifyload_appl(), " "failed to load (%s), reason: %s.", argv[optind], err_msg); if (fallback){ arcan_warning("trying to load fallback application (%s)\n", fallback); if (!arcan_verifyload_appl(fallback, &err_msg)){ arcan_warning("fallback application failed to load (%s), giving up.\n", err_msg); goto error; } } else goto error; } if (!arcan_verify_namespaces(false)){ arcan_warning("namespace verification failed, status:\n"); goto error; } if (debuglevel > 1) arcan_verify_namespaces(true); #ifndef _WIN32 /* pipe to file, socket or launch script based on monitor output, * format will be LUA tables with the exception of each cell ending with * #ENDSAMPLE . The block will be sampled, parsed and should return a table * pushed through the sample() function in the LUA space */ if (settings.in_monitor){ settings.mon_infd = strtol( getenv("ARCAN_MONITOR_FD"), NULL, 10); } else if (settings.monitor > 0){ extern arcan_benchdata benchdata; benchdata.bench_enabled = true; if (strncmp(monitor_arg, "LOG:", 4) == 0){ settings.mon_outf = fopen(&monitor_arg[4], "w+"); if (NULL == settings.mon_outf) arcan_fatal("couldn't open log output (%s) for writing\n", monitor_arg[4]); fcntl(fileno(settings.mon_outf), F_SETFD, FD_CLOEXEC); } else { int pair[2]; pid_t p1; if (pipe(pair) == 0) ; if ( (p1 = fork()) == 0){ close(pair[1]); /* double-fork to get away from parent */ if (fork() != 0) exit(EXIT_SUCCESS); /* * set the descriptor of the inherited pipe as an envvariable, * this will have the program be launched with in_monitor set to true * the monitor args will then be ignored and appname replaced with * the monitorarg */ char monfd_buf[8] = {0}; snprintf(monfd_buf, 8, "%d", pair[0]); setenv("ARCAN_MONITOR_FD", monfd_buf, 1); argv[optind] = strdup(monitor_arg); execv(argv[0], argv); exit(EXIT_FAILURE); } else { /* don't terminate just because the pipe gets broken (i.e. dead monitor) */ close(pair[0]); settings.mon_outf = fdopen(pair[1], "w"); signal(SIGPIPE, SIG_IGN); } } fullscreen = false; } #else if (!stdout_redirected){ } #endif /* * try to open the specified database, * if that fails, warn, try to create an empty * database and if that fails, give up. */ if (!dbfname) dbfname = arcan_expand_resource("arcandb.sqlite", RESOURCE_APPL_SHARED); dbhandle = arcan_db_open(dbfname, arcan_appl_id()); if (!dbhandle) { arcan_warning("Couldn't open database (requested: %s)," "trying to create a new one.\n", dbfname); FILE* fpek = fopen(dbfname, "w+"); /* case of non-write:able dbpath */ if (!fpek){ arcan_mem_free(dbfname); dbfname = arcan_expand_resource("arcandb.sqlite", RESOURCE_APPL_TEMP); if (!dbfname) goto error; fpek = fopen(dbfname, "w+"); } if (fpek){ fclose(fpek); dbhandle = arcan_db_open(dbfname, arcan_appl_id()); } if (!dbhandle){ arcan_warning("Couldn't create database, using in-mem fallback\n"); dbhandle = arcan_db_open(":memory:", arcan_appl_id()); } if (!dbhandle){ arcan_warning("In memory db fallback failed, giving up\n"); goto error; } } /* either use previous explicit dimensions (if found and cached) * or revert to platform default or store last */ if (-1 == width){ char* dbw = arcan_db_appl_val(dbhandle, "arcan", "width"); if (dbw){ width = (uint16_t) strtoul(dbw, NULL, 10); arcan_mem_free(dbw); } else width = 0; } else{ char buf[6] = {0}; snprintf(buf, sizeof(buf), "%d", width); arcan_db_appl_kv(dbhandle, "arcan", "width", buf); } if (-1 == height){ char* dbh = arcan_db_appl_val(dbhandle, "arcan", "height"); if (dbh){ height = (uint16_t) strtoul(dbh, NULL, 10); arcan_mem_free(dbh); } else height = 0; } else{ char buf[6] = {0}; snprintf(buf, sizeof(buf), "%d", height); arcan_db_appl_kv(dbhandle, "arcan", "height", buf); } arcan_video_default_scalemode(scalemode); if (windowed) fullscreen = false; /* grab video, (necessary) */ if (arcan_video_init(width, height, 32, fullscreen, windowed, conservative, arcan_appl_id()) != ARCAN_OK){ arcan_fatal("Error; Couldn't initialize video system," "try other windowing options (-f, -w, ...)\n"); } /* defined in warning.c for arcan_fatal, we avoid the use of an * atexit() as this can be routed through abort() or similar * functions but some video platforms are extremely volatile * if we don't initiate a shutdown (egl-dri for one) */ extern void(*arcan_fatal_hook)(void); arcan_fatal_hook = arcan_video_shutdown; errno = 0; /* grab audio, (possible to live without) */ if (ARCAN_OK != arcan_audio_setup(nosound)) arcan_warning("Warning: No audio devices could be found.\n"); arcan_math_init(); /* setup device polling, cleanup, ... */ arcan_evctx* def = arcan_event_defaultctx(); arcan_event_init( def ); #ifdef ARCAN_LED arcan_led_init(); #endif /* * system integration note here, this could essentially provide * a side-channel into debugdata as the path can be expanded to * the debug directory, thus a compromised frameserver could possibly * leak crash-dumps etc. outside the sandbox. If this is a concern, * change this behavior or define a different logpath in the env. */ if (getenv("ARCAN_FRAMESERVER_LOGDIR") == NULL){ char* lpath = arcan_expand_resource("", RESOURCE_SYS_DEBUG); setenv("ARCAN_FRAMESERVER_LOGDIR", lpath, 1); arcan_mem_free(lpath); } if (hookscript){ char* tmphook = arcan_expand_resource(hookscript, RESOURCE_APPL_SHARED); free(hookscript); hookscript = NULL; if (tmphook){ data_source src = arcan_open_resource(tmphook); if (src.fd != BADFD){ map_region reg = arcan_map_resource(&src, false); if (reg.ptr){ hookscript = strdup(reg.ptr); arcan_release_map(reg); } arcan_release_resource(&src); } free(tmphook); } } #ifndef _WIN32 system_page_size = sysconf(_SC_PAGE_SIZE); #endif /* * fallback implementation resides here and a little further down * in the "if adopt" block. Use verifyload to reconfigure application * namespace and scripts to run, then recoverexternal will cleanup * audio/video/event and invoke adopt() in the script */ bool adopt = false, in_recover = false; int jumpcode = setjmp(arcanmain_recover_state); if (jumpcode == 1){ arcan_db_close(&dbhandle); dbhandle = arcan_db_open(dbfname, arcan_appl_id()); if (!dbhandle) goto error; adopt = true; } else if (jumpcode == 2){ if (in_recover){ arcan_warning("Double-Failure (main appl + adopt appl), giving up.\n"); goto error; } if (!fallback){ arcan_warning("Lua VM instance failed and no " "fallback defined, giving up.\n"); goto error; } const char* errmsg; arcan_lua_shutdown(settings.lua); if (!arcan_verifyload_appl(fallback, &errmsg)){ arcan_warning("Lua VM error fallback, failure loading (%s), reason: %s\n", fallback, errmsg); goto error; } if (!arcan_verify_namespaces(false)) goto error; in_recover = true; adopt = true; } /* setup VM, map arguments and possible overrides */ settings.lua = arcan_lua_alloc(); arcan_lua_mapfunctions(settings.lua, debuglevel); bool inp_file; const char* inp = arcan_appl_basesource(&inp_file); if (!inp){ arcan_warning("main(), No main script found for (%s)\n", arcan_appl_id()); goto error; } char* msg = arcan_lua_main(settings.lua, inp, inp_file); if (msg != NULL){ #ifdef ARCAN_LUA_NOCOLOR arcan_warning("\nParsing error in %s:\n%s\n", arcan_appl_id(), msg); #else arcan_warning("\n\x1b[1mParsing error in (\x1b[33m%s\x1b[39m):\n" "\x1b[35m%s\x1b[22m\x1b[39m\n\n", arcan_appl_id(), msg); #endif goto error; } free(msg); /* entry point follows the name of the appl, * hand over execution and begin event loop */ if (argc > optind) arcan_lua_pushargv(settings.lua, argv + optind + 1); if (!arcan_lua_callvoidfun(settings.lua, "", false)) arcan_fatal("couldn't load appl, missing %s function\n", arcan_appl_id() ? arcan_appl_id() : ""); if (hookscript) arcan_lua_dostring(settings.lua, hookscript); if (adopt){ int saved, truncated; arcan_video_recoverexternal(false, &saved, &truncated, arcan_lua_adopt, settings.lua); arcan_warning("switching applications, %d adopted.\n", saved); } arcan_evctx* evctx = arcan_event_defaultctx(); bool done = false; int exit_code = EXIT_SUCCESS; arcan_event ev; while (!done) { /* these can populate event queue, but only to a certain limit */ arcan_video_pollfeed(); arcan_audio_refresh(); float frag = arcan_event_process(evctx, on_clock_pulse); while (1 == arcan_event_poll(evctx, &ev)){ switch (ev.category){ case EVENT_VIDEO: if (ev.vid.kind == EVENT_VIDEO_EXPIRE) arcan_video_deleteobject(ev.vid.source); break; /* this event category is never propagated to the scripting engine itself */ case EVENT_SYSTEM: if (ev.sys.kind == EVENT_SYSTEM_EXIT){ exit_code = ev.sys.errcode; done = true; goto out; } goto error; default: break; } arcan_lua_pushevent(settings.lua, &ev); } platform_video_synch(settings.tick_count, frag, preframe, postframe); } out: free(hookscript); arcan_lua_callvoidfun(settings.lua, "shutdown", false); #ifdef ARCAN_LED arcan_led_shutdown(); #endif arcan_video_shutdown(); arcan_event_deinit(arcan_event_defaultctx()); arcan_mem_free(dbfname); if (dbhandle) arcan_db_close(&dbhandle); return exit_code; error: if (debuglevel > 1){ arcan_warning("fatal: main loop failed, arguments: \n"); for (size_t i = 0; i < argc; i++) arcan_warning("%s ", argv[i]); arcan_warning("\n\n"); } arcan_mem_free(dbfname); arcan_video_shutdown(); arcan_event_deinit(arcan_event_defaultctx()); /* for cases where we have a broken namespace, also dump * the current namespace state as that might help troubleshoot */ arcan_verify_namespaces(true); return EXIT_FAILURE; }