void platform_video_timing(float* o_sync, float* o_stddev, float* o_variance) { static float sync, stddev, variance; static bool gottiming; if (!gottiming){ platform_video_bufferswap(); int retrycount = 0; /* * try to get a decent measurement of actual timing, this is not really used for * synchronization but rather as a guess of we're actually vsyncing and how * processing should be scheduled in relation to vsync, or if we should yield at * appropriate times. */ const int nsamples = 10; long long int samples[nsamples], sample_sum; retry: sample_sum = 0; for (int i = 0; i < nsamples; i++){ long long int start = arcan_timemillis(); platform_video_bufferswap(); long long int stop = arcan_timemillis(); samples[i] = stop - start; sample_sum += samples[i]; } sync = (float) sample_sum / (float) nsamples; variance = 0.0; for (int i = 0; i < nsamples; i++){ variance += powf(sync - (float)samples[i], 2); } stddev = sqrtf(variance / (float) nsamples); if (stddev > 0.5){ retrycount++; if (retrycount > 10) arcan_video_display.vsync_timing = 16.667; /* give up and just revert */ else goto retry; } } *o_sync = sync; *o_stddev = stddev; *o_variance = variance; }
int64_t arcan_frametime() { int64_t now = arcan_timemillis(); if (now < epoch) epoch = now - (epoch - now); return now - epoch; }
void platform_video_synch(uint64_t tick_count, float fract, video_synchevent pre, video_synchevent post) { long long start = arcan_timemillis(); if (pre) pre(); arcan_vobject* vobj = arcan_video_getobject(out_vid); if (!vobj){ out_vid = ARCAN_VIDEO_WORLDID; vobj = arcan_video_getobject(ARCAN_VIDEO_WORLDID); } size_t nd; arcan_bench_register_cost( arcan_vint_refresh(fract, &nd) ); agp_shader_id shid = agp_default_shader(BASIC_2D); agp_activate_rendertarget(NULL); if (blackframes){ agp_rendertarget_clear(); blackframes--; } if (vobj->program > 0) shid = vobj->program; agp_activate_vstore(out_vid == ARCAN_VIDEO_WORLDID ? arcan_vint_world() : vobj->vstore); agp_shader_activate(shid); agp_draw_vobj(0, 0, d_width, d_height, txcos, NULL); arcan_vint_drawcursor(false); /* * NOTE: heuristic fix-point for direct- mapping dedicated source for * low latency here when we fix up internal syncing paths. */ eglSwapBuffers(eglDpy, eglSurface); /* With dynamic, we run an artificial vsync if the time between swaps * become to low. This is a workaround for a driver issue spotted on * nvidia and friends from time to time where multiple swaps in short * regression in combination with 'only redraw' adds bubbles */ int delta = arcan_frametime() - last; if (delta >= 0 && delta < 8){ arcan_timesleep(16 - delta); } last = arcan_frametime(); if (post) post(); }
void agp_readback_synchronous(struct storage_info_t* dst) { if (!(dst->txmapped == TXSTATE_TEX2D) || !dst->vinf.text.raw) return; struct agp_fenv* env = agp_env(); env->bind_texture(GL_TEXTURE_2D, dst->vinf.text.glid); env->get_tex_image(GL_TEXTURE_2D, 0, GL_PIXEL_FORMAT, GL_UNSIGNED_BYTE, dst->vinf.text.raw); dst->update_ts = arcan_timemillis(); env->bind_texture(GL_TEXTURE_2D, 0); }
/* positions and offsets in meta have been verified in _frameserver */ static void pbo_stream_sub(struct storage_info_t* s, av_pixel* buf, struct stream_meta* meta, bool synch) { struct agp_fenv* env = agp_env(); if ( (float)(meta->w * meta->h) / (s->w * s->h) > 0.5) return pbo_stream(s, buf, meta, synch); agp_activate_vstore(s); size_t row_sz = meta->w * sizeof(av_pixel); set_pixel_store(s->w, *meta); env->tex_subimage_2d(GL_TEXTURE_2D, 0, meta->x1, meta->y1, meta->w, meta->h, s->vinf.text.s_fmt ? s->vinf.text.s_fmt : GL_PIXEL_FORMAT, GL_UNSIGNED_BYTE, buf ); reset_pixel_store(); agp_deactivate_vstore(s); if (synch){ av_pixel* cpy = s->vinf.text.raw; for (size_t y = meta->y1; y < meta->y1 + meta->h; y++) memcpy(&cpy[y * s->w + meta->x1], &buf[y * s->w + meta->x1], row_sz); s->update_ts = arcan_timemillis(); } /* * Currently disabled approach to update subregion using PBO, experienced * data corruption / driver bugs on several drivers :'( */ #if 0 av_pixel* ptr = glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY); glBindBuffer(GL_PIXEL_UNPACK_BUFFER, s->vinf.text.wid); /* warning, with the normal copy we check alignment in beforehand as we have * had cases where glMapBuffer intermittently returns unaligned pointers AND * the compiler has emitted intrinsics that assumed alignment */ for (size_t y = meta->y1; y < meta->y1 + meta->h; y++) memcpy(&ptr[y * s->w + meta->x1], &buf[y * s->w + meta->x1], row_sz); glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER); glTexSubImage2D(GL_TEXTURE_2D, 0, meta->x1, meta->y1, meta->w, meta->h, GL_PIXEL_FORMAT, GL_UNSIGNED_BYTE, 0); glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); #endif }
void arcan_bench_register_frame() { static long long int lastframe = -1; if (benchdata.bench_enabled == false) return; long long int ftime = arcan_timemillis(); if (lastframe > 0 && ftime > lastframe){ unsigned delta = ftime - lastframe; benchdata.frametime[(unsigned)benchdata.frameofs] = delta; benchdata.framecount++; benchdata.frameofs = (benchdata.frameofs + 1) % (sizeof(benchdata.frametime) / sizeof(benchdata.frametime[0])); } lastframe = ftime; }
static void pbo_stream(struct storage_info_t* s, av_pixel* buf, struct stream_meta* meta, bool synch) { agp_activate_vstore(s); struct agp_fenv* env = agp_env(); env->bind_buffer(GL_PIXEL_UNPACK_BUFFER, s->vinf.text.wid); size_t ntc = s->w * s->h; av_pixel* ptr = env->map_buffer(GL_PIXEL_UNPACK_BUFFER,GL_WRITE_ONLY); if (!ptr) return; av_pixel* obuf = buf; /* note, explicitly replace with a simd unaligned version */ if ( ((uintptr_t)ptr % 16) == 0 && ((uintptr_t)buf % 16) == 0 ) memcpy(ptr, buf, ntc * sizeof(av_pixel)); else for (size_t i = 0; i < ntc; i++) *ptr++ = *buf++; /* synch :- on-host backing store, one extra copy into local buffer */ if (synch){ buf = obuf; ptr = s->vinf.text.raw; s->update_ts = arcan_timemillis(); if ( ((uintptr_t)ptr % 16) == 0 && ((uintptr_t)buf % 16) == 0 ) memcpy(ptr, buf, ntc * sizeof(av_pixel)); else for (size_t i = 0; i < ntc; i++) *ptr++ = *buf++; } env->unmap_buffer(GL_PIXEL_UNPACK_BUFFER); env->tex_subimage_2d(GL_TEXTURE_2D, 0, 0, 0, s->w, s->h, s->vinf.text.s_fmt ? s->vinf.text.s_fmt : GL_PIXEL_FORMAT, GL_UNSIGNED_BYTE, 0 ); env->bind_buffer(GL_PIXEL_UNPACK_BUFFER, 0); agp_deactivate_vstore(); }
/* * keep the time tracking separate from the other * timekeeping parts, discard non-monotonic values */ void arcan_bench_register_tick(unsigned nticks) { static long long int lasttick = -1; if (benchdata.bench_enabled == false) return; while (nticks--){ long long int ftime = arcan_timemillis(); benchdata.tickcount++; if (lasttick > 0 && ftime > lasttick){ unsigned delta = ftime - lasttick; benchdata.ticktime[(unsigned)benchdata.tickofs] = delta; benchdata.tickofs = (benchdata.tickofs + 1) % (sizeof(benchdata.ticktime) / sizeof(benchdata.ticktime[0])); } lasttick = ftime; } }
void agp_update_vstore(struct storage_info_t* s, bool copy) { struct agp_fenv* env = agp_env(); if (s->txmapped == TXSTATE_OFF) return; FLAG_DIRTY(); if (!copy) env->bind_texture(GL_TEXTURE_2D, s->vinf.text.glid); else{ if (GL_NONE == s->vinf.text.glid) env->gen_textures(1, &s->vinf.text.glid); /* for the launch_resume and resize states, were we'd push a new * update but have multiple references */ if (s->refcount == 0) s->refcount = 1; env->bind_texture(GL_TEXTURE_2D, s->vinf.text.glid); } env->tex_param_i(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, s->txu == ARCAN_VTEX_REPEAT ? GL_REPEAT : GL_CLAMP_TO_EDGE); env->tex_param_i(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, s->txv == ARCAN_VTEX_REPEAT ? GL_REPEAT : GL_CLAMP_TO_EDGE); int filtermode = s->filtermode & (~ARCAN_VFILTER_MIPMAP); bool mipmap = s->filtermode & ARCAN_VFILTER_MIPMAP; /* * Mipmapping still misses the option to manually define mipmap levels */ if (copy){ #ifndef GL_GENERATE_MIPMAP if (mipmap) env->generate_mipmap(GL_TEXTURE_2D); #else env->tex_param_i(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, mipmap); #endif } switch (filtermode){ case ARCAN_VFILTER_NONE: env->tex_param_i(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); env->tex_param_i(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); break; case ARCAN_VFILTER_LINEAR: env->tex_param_i(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); env->tex_param_i(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); break; case ARCAN_VFILTER_BILINEAR: env->tex_param_i(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); env->tex_param_i(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); break; case ARCAN_VFILTER_TRILINEAR: if (mipmap){ env->tex_param_i(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); env->tex_param_i(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); } else { env->tex_param_i(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); env->tex_param_i(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); } break; } if (copy){ env->pixel_storei(GL_UNPACK_ROW_LENGTH, 0); s->update_ts = arcan_timemillis(); if (s->txmapped == TXSTATE_DEPTH) env->tex_image_2d(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, s->w, s->h, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, 0); else env->tex_image_2d(GL_TEXTURE_2D, 0, s->vinf.text.d_fmt ? s->vinf.text.d_fmt : GL_STORE_PIXEL_FORMAT, s->w, s->h, 0, s->vinf.text.s_fmt ? s->vinf.text.s_fmt : GL_PIXEL_FORMAT, GL_UNSIGNED_BYTE, s->vinf.text.raw ); } #ifndef HEADLESS_NOARCAN if (arcan_video_display.conservative){ arcan_mem_free(s->vinf.text.raw); s->vinf.text.raw = NULL; s->vinf.text.s_raw = 0; } #endif env->bind_texture(GL_TEXTURE_2D, 0); }
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; }
void agp_update_vstore(struct storage_info_t* s, bool copy) { if (s->txmapped == TXSTATE_OFF) return; FLAG_DIRTY(); if (!copy) glBindTexture(GL_TEXTURE_2D, s->vinf.text.glid); else{ glGenTextures(1, &s->vinf.text.glid); /* for the launch_resume and resize states, were we'd push a new * update but have multiple references */ if (s->refcount == 0) s->refcount = 1; glBindTexture(GL_TEXTURE_2D, s->vinf.text.glid); } glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, s->txu == ARCAN_VTEX_REPEAT ? GL_REPEAT : GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, s->txv == ARCAN_VTEX_REPEAT ? GL_REPEAT : GL_CLAMP_TO_EDGE); int filtermode = s->filtermode & (~ARCAN_VFILTER_MIPMAP); bool mipmap = s->filtermode & ARCAN_VFILTER_MIPMAP; /* * Mipmapping still misses the option to manually define mipmap levels */ if (copy){ #ifndef GL_GENERATE_MIPMAP if (mipmap) glGenerateMipmap(GL_TEXTURE_2D); #else glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, mipmap); #endif } switch (filtermode){ case ARCAN_VFILTER_NONE: glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); break; case ARCAN_VFILTER_LINEAR: glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); break; case ARCAN_VFILTER_BILINEAR: glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); break; case ARCAN_VFILTER_TRILINEAR: glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); break; } if (copy){ s->update_ts = arcan_timemillis(); if (s->txmapped == TXSTATE_DEPTH) glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, s->w, s->h, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, 0); else glTexImage2D(GL_TEXTURE_2D, 0, GL_PIXEL_FORMAT, s->w, s->h, 0, GL_PIXEL_FORMAT, GL_UNSIGNED_BYTE, s->vinf.text.raw); } #ifndef HEADLESS_NOARCAN if (arcan_video_display.conservative){ arcan_mem_free(s->vinf.text.raw); s->vinf.text.raw = NULL; s->vinf.text.s_raw = 0; } #endif glBindTexture(GL_TEXTURE_2D, 0); }