863 lines
21 KiB
C++
863 lines
21 KiB
C++
#include "main/tick.h"
|
|
#include "main/input_handling.h"
|
|
#include "main/references.h"
|
|
#include "main/logging.h"
|
|
#include "processing.h"
|
|
#include "obj/object.h"
|
|
#include "scene/animation/anim_node_sync.h"
|
|
#include "main/console.h"
|
|
#include "main/initialization.h"
|
|
#include "network/network_manager.h"
|
|
#include "ISoundDevice.h"
|
|
#include "files.h"
|
|
#include "design/projectiles.h"
|
|
#include "render/vertexBuffer.h"
|
|
#include "render/gl_framebuffer.h"
|
|
#include "save_load.h"
|
|
#include <cmath>
|
|
#include <stdio.h>
|
|
#include <stdint.h>
|
|
|
|
extern unsigned drawnSteps, bufferFlushes;
|
|
|
|
GameState game_state = GS_Menu;
|
|
std::string game_locale;
|
|
bool game_running = false;
|
|
bool reload_gui = false;
|
|
|
|
bool fullGC = false;
|
|
|
|
threads::Mutex texDestroyLock;
|
|
std::vector<const render::Texture*> queuedDestroyTextures;
|
|
void queueDestroyTexture(const render::Texture* tex) {
|
|
threads::Lock lock(texDestroyLock);
|
|
queuedDestroyTextures.push_back(tex);
|
|
}
|
|
|
|
void destroyQueuedTextures() {
|
|
if(queuedDestroyTextures.empty())
|
|
return;
|
|
|
|
threads::Lock lock(texDestroyLock);
|
|
for(unsigned i = 0; i < queuedDestroyTextures.size(); ++i)
|
|
delete queuedDestroyTextures[i];
|
|
queuedDestroyTextures.clear();
|
|
}
|
|
|
|
const render::Shader* fsShader = nullptr;
|
|
bool hide_ui = false;
|
|
double ui_scale = 1.0;
|
|
double scale_3d = 1.0;
|
|
double pixelSizeRatio = 1.0;
|
|
double* maxfps = nullptr;
|
|
|
|
//Seconds since last autosave
|
|
double autosaveTimer = 0.0;
|
|
extern double autosaveInterval;
|
|
|
|
threads::Signal scriptTickSignal;
|
|
|
|
extern bool queuedModSwitch;
|
|
extern std::vector<std::string> modSetup;
|
|
|
|
namespace scripts {
|
|
void logException();
|
|
};
|
|
|
|
class RunScriptTick : public processing::Action {
|
|
double time;
|
|
double& logTime;
|
|
bool gcLock;
|
|
scripts::Manager* manager;
|
|
public:
|
|
RunScriptTick(scripts::Manager* Manager, double Time, bool Gc, double& LogTime)
|
|
: time(Time), gcLock(Gc), manager(Manager), logTime(LogTime) {
|
|
}
|
|
|
|
bool run() {
|
|
auto prevSection = enterSection(NS_ScriptTick);
|
|
double start = devices.driver->getAccurateTime();
|
|
#ifndef _DEBUG
|
|
try {
|
|
#endif
|
|
#ifdef TRACE_GC_LOCK
|
|
manager->markGCImpossible();
|
|
#endif
|
|
manager->tick(time);
|
|
devices.network->managerNetworking(manager, time);
|
|
scriptTickSignal.signalDown();
|
|
#ifdef TRACE_GC_LOCK
|
|
manager->markGCPossible();
|
|
#endif
|
|
#ifndef _DEBUG
|
|
}
|
|
catch(...) {
|
|
scripts::logException();
|
|
throw;
|
|
}
|
|
#endif
|
|
logTime = devices.driver->getAccurateTime() - start;
|
|
enterSection(prevSection);
|
|
return true;
|
|
}
|
|
};
|
|
|
|
class RunScriptGC : public processing::Action {
|
|
scripts::Manager* manager;
|
|
bool full;
|
|
public:
|
|
RunScriptGC(scripts::Manager* Manager, bool FullCycle = false)
|
|
: manager(Manager), full(FullCycle) {
|
|
}
|
|
|
|
bool run() {
|
|
double start = devices.driver->getAccurateTime();
|
|
manager->pauseScriptThreads();
|
|
double gcStart = devices.driver->getAccurateTime();
|
|
int gcMode = manager->garbageCollect(full);
|
|
double end = devices.driver->getAccurateTime();
|
|
manager->resumeScriptThreads();
|
|
|
|
#ifdef PROFILE_PROCESSING
|
|
if(end - start > 0.016) {
|
|
const char* name = "Server";
|
|
if(manager == devices.scripts.client)
|
|
name = "Client";
|
|
else if(manager == devices.scripts.menu)
|
|
name = "Menu";
|
|
error("%s GC took %dms (Mode %d)", name, (int)((end - gcStart) * 1.0e3), gcMode);
|
|
if(gcStart - start > 0.01)
|
|
error("%s script pausing took %dms", name, (int)((gcStart - start) * 1.0e3));
|
|
}
|
|
#endif
|
|
|
|
scriptTickSignal.signalDown();
|
|
return true;
|
|
}
|
|
};
|
|
|
|
unsigned animationThreadCount = 6;
|
|
threads::Signal animationSignal, activeAnimCount;
|
|
threads::Mutex animDataLock;
|
|
double nearestNode = 9.0e35, furthestNode = 0.0;
|
|
double animation_s = 0.0, render_s = 0.0, present_s = 0.0;
|
|
threads::atomic_int animIndex(-1), animProcessed;
|
|
|
|
//Animates some nodes, returning how long it processed
|
|
double animateSomeNodes(int maxNodes) {
|
|
if(animIndex < 0)
|
|
return 0.0;
|
|
|
|
double furthest = 0.0;
|
|
double start = devices.driver->getAccurateTime();
|
|
|
|
auto* nodes = &devices.scene->children.front();
|
|
int count = (int)devices.scene->children.size();
|
|
int animCount = count/(animationThreadCount*16);
|
|
if(animCount == 0)
|
|
animCount = 1;
|
|
|
|
int index = (animIndex -= animCount);
|
|
while(index >= 0) {
|
|
int animated = 0;
|
|
for(int i = index; i > index - animCount && i >= 0; --i) {
|
|
auto* node = nodes[i];
|
|
node->animate();
|
|
++animated;
|
|
|
|
double d = node->sortDistance + node->abs_scale;
|
|
if(d > furthest)
|
|
furthest = d;
|
|
}
|
|
|
|
if(animated != 0)
|
|
animProcessed += animated;
|
|
|
|
maxNodes -= animated;
|
|
if(maxNodes < 0)
|
|
break;
|
|
|
|
index = (animIndex -= animCount);
|
|
}
|
|
|
|
animDataLock.lock();
|
|
//if(nearest < nearestNode)
|
|
// nearestNode = nearest;
|
|
if(furthest > furthestNode)
|
|
furthestNode = furthest;
|
|
animDataLock.release();
|
|
|
|
return devices.driver->getAccurateTime() - start;
|
|
}
|
|
|
|
volatile bool EndAnimationThreads = false;
|
|
threads::threadreturn threadcall animateNodes(void* arg) {
|
|
enterSection(NS_Animation);
|
|
double& logTime = *(double*)arg;
|
|
initNewThread();
|
|
activeAnimCount.signalUp();
|
|
//threads::setThreadPriority(threads::TP_High);
|
|
|
|
#ifdef TRACE_GC_LOCK
|
|
devices.scripts.menu->markGCImpossible();
|
|
devices.scripts.client->markGCImpossible();
|
|
#endif
|
|
while(true) {
|
|
while(animIndex < 0 && !EndAnimationThreads)
|
|
threads::sleep(1);
|
|
if(EndAnimationThreads)
|
|
break;
|
|
|
|
animationSignal.signalUp();
|
|
|
|
double /*nearest = 9.0e35,*/ furthest = 0.0;
|
|
double start = devices.driver->getAccurateTime();
|
|
|
|
if(!devices.scene->children.empty()) {
|
|
auto* nodes = &devices.scene->children.front();
|
|
int animCount = (int)devices.scene->children.size()/(animationThreadCount*16);
|
|
if(animCount == 0)
|
|
animCount = 1;
|
|
|
|
int index = (animIndex -= animCount);
|
|
while(index >= 0) {
|
|
int animated = 0;
|
|
for(int i = index; i > index - animCount && i >= 0; --i) {
|
|
auto* node = nodes[i];
|
|
node->animate();
|
|
++animated;
|
|
|
|
double d = node->sortDistance + node->abs_scale;
|
|
if(d > furthest)
|
|
furthest = d;
|
|
}
|
|
|
|
if(animated != 0)
|
|
animProcessed += animated;
|
|
|
|
index = (animIndex -= animCount);
|
|
}
|
|
}
|
|
|
|
logTime = devices.driver->getAccurateTime() - start;
|
|
|
|
animDataLock.lock();
|
|
//if(nearest < nearestNode)
|
|
// nearestNode = nearest;
|
|
if(furthest > furthestNode)
|
|
furthestNode = furthest;
|
|
animDataLock.release();
|
|
|
|
animationSignal.signalDown();
|
|
threads::sleep(1);
|
|
}
|
|
#ifdef TRACE_GC_LOCK
|
|
devices.scripts.menu->markGCPossible();
|
|
devices.scripts.client->markGCPossible();
|
|
#endif
|
|
|
|
activeAnimCount.signalDown();
|
|
cleanupThread();
|
|
return 0;
|
|
}
|
|
|
|
double frameLen_s = 0, frameTime_s = 0;
|
|
double realFrameLen = 0, realFrameTime;
|
|
int frameTime_ms = 0, frameLen_ms = 0;
|
|
unsigned frameNumber = 0;
|
|
|
|
std::vector<double> frames;
|
|
unsigned max_frames = 120;
|
|
|
|
void shader_gameTime(float* pFloats,unsigned short n,void*) {
|
|
do {
|
|
*pFloats = (float)frameTime_s;
|
|
++pFloats;
|
|
} while(--n);
|
|
}
|
|
|
|
void shader_frameTime(float* pFloats,unsigned short n,void*) {
|
|
do {
|
|
*pFloats = (float)realFrameTime;
|
|
++pFloats;
|
|
} while(--n);
|
|
}
|
|
|
|
void shader_gameTime_cycle(float* pFloats,unsigned short n,void* pArgs) {
|
|
float* period = (float*)pArgs;
|
|
do {
|
|
*pFloats = (float)(std::fmod(frameTime_s, double(*period)) / double(*period));
|
|
++period; ++pFloats;
|
|
} while(--n);
|
|
}
|
|
|
|
void shader_frameTime_cycle(float* pFloats,unsigned short n,void* pArgs) {
|
|
float* period = (float*)pArgs;
|
|
do {
|
|
*pFloats = (float)(std::fmod(realFrameTime, double(*period)) / double(*period));
|
|
++period; ++pFloats;
|
|
} while(--n);
|
|
}
|
|
|
|
void shader_gameTime_cycle_abs(float* pFloats,unsigned short n,void* pArgs) {
|
|
float* period = (float*)pArgs;
|
|
do {
|
|
*pFloats = (float)std::fmod(frameTime_s, double(*period));
|
|
++period; ++pFloats;
|
|
} while(--n);
|
|
}
|
|
|
|
void shader_frameTime_cycle_abs(float* pFloats,unsigned short n,void* pArgs) {
|
|
float* period = (float*)pArgs;
|
|
do {
|
|
*pFloats = (float)std::fmod(realFrameTime, double(*period));
|
|
++period; ++pFloats;
|
|
} while(--n);
|
|
}
|
|
|
|
void shader_pixelRatio(float* pFloats,unsigned short n,void* pArgs) {
|
|
*pFloats = (float)pixelSizeRatio;
|
|
}
|
|
|
|
#ifdef PROFILE_LOCKS
|
|
double lockProfileTimer = 0.0;
|
|
bool printLockProfile = false;
|
|
bool requireObserved = false;
|
|
#endif
|
|
|
|
double menuTickTime = 0, serverTickTime = 0, clientTickTime = 0, animationTime = 0;
|
|
double* animTimes = nullptr;
|
|
|
|
extern double lastLockGlobalUpdate, nextTargetUpdateTime, nextScriptGCTime;
|
|
|
|
void resetGameTime() {
|
|
nextTargetUpdateTime = lastLockGlobalUpdate = devices.driver->getGameTime();
|
|
frameTime_s = devices.driver->getGameTime() - 0.25;
|
|
nextScriptGCTime = devices.driver->getFrameTime();
|
|
}
|
|
|
|
void endAnimation() {
|
|
if(animTimes) {
|
|
EndAnimationThreads = true;
|
|
activeAnimCount.wait(0);
|
|
EndAnimationThreads = false;
|
|
delete[] animTimes;
|
|
animTimes = nullptr;
|
|
}
|
|
}
|
|
|
|
void idleLoadResources(int maxPriority) {
|
|
if(devices.library.processTextures(maxPriority, true))
|
|
return;
|
|
if(devices.library.processMeshes(maxPriority, 1))
|
|
return;
|
|
if(devices.library.processTextures(INT_MIN, true))
|
|
return;
|
|
if(devices.library.processMeshes(INT_MIN, 1))
|
|
return;
|
|
}
|
|
|
|
GameState prev_state = GS_Menu;
|
|
void tickGlobal(bool hasScripts) {
|
|
#ifdef TRACE_GC_LOCK
|
|
devices.scripts.menu->markGCImpossible();
|
|
devices.scripts.client->markGCImpossible();
|
|
#endif
|
|
|
|
//Get frame timings
|
|
static int lastRender = 0;
|
|
|
|
if(queuedModSwitch) {
|
|
queuedModSwitch = false;
|
|
destroyMod();
|
|
initMods(modSetup);
|
|
game_state = GS_Menu;
|
|
}
|
|
|
|
if(game_state != prev_state) {
|
|
if(hasScripts) {
|
|
if(game_running && devices.scripts.client)
|
|
devices.scripts.client->stateChange();
|
|
if(devices.scripts.menu)
|
|
devices.scripts.menu->stateChange();
|
|
}
|
|
prev_state = game_state;
|
|
}
|
|
|
|
++frameNumber;
|
|
|
|
double frame_time = devices.driver->getGameTime() - 0.25;
|
|
frameTime_ms = devices.driver->getTime();
|
|
|
|
double real_frame = devices.driver->getFrameTime();
|
|
realFrameLen = real_frame - realFrameTime;
|
|
realFrameTime = real_frame;
|
|
|
|
int frame_ms = frameTime_ms - lastRender;
|
|
lastRender = frameTime_ms;
|
|
|
|
double frame_s = frame_time - frameTime_s;
|
|
frameTime_s = frame_time;
|
|
|
|
if(frame_ms > 250 || frame_s > 0.25) {
|
|
frame_ms = 250;
|
|
frame_s = 0.25;
|
|
}
|
|
else if(frame_ms < 0 || frame_s < 0.0) {
|
|
frame_ms = 0;
|
|
frame_s = 0.0;
|
|
}
|
|
|
|
frameLen_s = frame_s;
|
|
frameLen_ms = frame_ms;
|
|
|
|
frames.push_back(realFrameLen);
|
|
if(frames.size() > max_frames)
|
|
frames.erase(frames.begin());
|
|
|
|
//Run network manager tick
|
|
devices.network->tick(realFrameLen);
|
|
|
|
//Do lock profiling
|
|
#ifdef PROFILE_LOCKS
|
|
lockProfileTimer += frame_s;
|
|
if(lockProfileTimer >= 1.0) {
|
|
lockProfileTimer = 0.0;
|
|
|
|
if(printLockProfile) {
|
|
threads::profileMutexCycle([](threads::Mutex* mtx) {
|
|
if((mtx->observed || !requireObserved) && mtx->profileCount > 0) {
|
|
print("%s: %d locks", mtx->name.c_str(), mtx->profileCount);
|
|
}
|
|
});
|
|
threads::profileReadWriteMutexCycle([](threads::ReadWriteMutex* mtx) {
|
|
if((mtx->observed || !requireObserved) && (mtx->profileReadCount > 0 || mtx->profileWriteCount > 0)) {
|
|
print("%s: %d read, %d write", mtx->name.c_str(), mtx->profileReadCount, mtx->profileWriteCount);
|
|
}
|
|
});
|
|
|
|
printLockProfile = false;
|
|
}
|
|
else {
|
|
threads::profileMutexCycle(0);
|
|
threads::profileReadWriteMutexCycle(0);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
idleLoadResources(isPreloading() ? -10 : INT_MIN);
|
|
|
|
//Do script GCs
|
|
#ifdef TRACE_GC_LOCK
|
|
devices.scripts.menu->markGCPossible();
|
|
devices.scripts.client->markGCPossible();
|
|
#endif
|
|
|
|
if(hasScripts) {
|
|
scriptTickSignal.signal(1);
|
|
processing::queueAction(new RunScriptGC(devices.scripts.menu, fullGC));
|
|
|
|
if(game_running) {
|
|
scriptTickSignal.signalUp(1);
|
|
processing::queueAction(new RunScriptGC(devices.scripts.client, fullGC));
|
|
}
|
|
|
|
fullGC = false;
|
|
|
|
while(!scriptTickSignal.check(0)) {
|
|
processing::run();
|
|
threads::sleep(0);
|
|
}
|
|
}
|
|
|
|
#ifdef TRACE_GC_LOCK
|
|
devices.scripts.menu->markGCImpossible();
|
|
devices.scripts.client->markGCImpossible();
|
|
#endif
|
|
|
|
//Run all the script ticks
|
|
if(hasScripts) {
|
|
scriptTickSignal.signal(1);
|
|
processing::queueAction(new RunScriptTick(devices.scripts.menu, realFrameLen, false, menuTickTime));
|
|
|
|
if(game_running) {
|
|
scriptTickSignal.signalUp(2);
|
|
processing::queueAction(new RunScriptTick(devices.scripts.server, frameLen_s, true, serverTickTime));
|
|
processing::queueAction(new RunScriptTick(devices.scripts.client, realFrameLen, false, clientTickTime));
|
|
}
|
|
}
|
|
|
|
#ifdef TRACE_GC_LOCK
|
|
devices.scripts.menu->markGCPossible();
|
|
devices.scripts.client->markGCPossible();
|
|
#endif
|
|
}
|
|
|
|
std::unordered_map<std::string,time_t> guiModuleTimes;
|
|
void tickGuiReload() {
|
|
if(!devices.scripts.client)
|
|
return;
|
|
for(auto it = devices.scripts.client->modules.begin(); it != devices.scripts.client->modules.end(); ++it) {
|
|
scripts::Module& mod = *it->second;
|
|
scripts::File& fl = *mod.file;
|
|
|
|
time_t mtime = getModifiedTime(fl.path);
|
|
auto f = guiModuleTimes.find(mod.name);
|
|
if(f == guiModuleTimes.end()) {
|
|
guiModuleTimes[mod.name] = mtime;
|
|
}
|
|
else {
|
|
if(f->second < mtime)
|
|
devices.scripts.client->reload(mod.name);
|
|
guiModuleTimes[mod.name] = mtime;
|
|
}
|
|
}
|
|
}
|
|
|
|
static render::Texture* renderTarget = nullptr;
|
|
void getFrameRender(Image& img) {
|
|
if(!game_running || !devices.scripts.client)
|
|
return;
|
|
|
|
auto prev_state = game_state;
|
|
game_state = GS_Menu;
|
|
|
|
devices.scripts.client->preRender(0.0);
|
|
devices.scripts.client->render(0.0);
|
|
|
|
renderTarget->save(img);
|
|
devices.render->setRenderTarget(nullptr);
|
|
|
|
game_state = prev_state;
|
|
}
|
|
|
|
void renderFrame(scripts::Manager* uiScript, scripts::Manager* renderScript) {
|
|
drawnSteps = 0;
|
|
bufferFlushes = 0;
|
|
for(unsigned i = 0; i < render::FC_COUNT; ++i)
|
|
render::vbFlushCounts[i] = 0;
|
|
|
|
destroyQueuedTextures();
|
|
|
|
#ifdef TRACE_GC_LOCK
|
|
devices.scripts.menu->markGCImpossible();
|
|
devices.scripts.client->markGCImpossible();
|
|
#endif
|
|
|
|
vec2i screenSize(devices.driver->win_width,devices.driver->win_height);
|
|
bool render = screenSize.width != 0 && screenSize.height != 0;
|
|
|
|
if(render) {
|
|
if(!renderTarget)
|
|
renderTarget = devices.render->createRenderTarget(screenSize * scale_3d);
|
|
if(renderTarget->size != screenSize * scale_3d) {
|
|
delete renderTarget;
|
|
renderTarget = devices.render->createRenderTarget(screenSize * scale_3d);
|
|
}
|
|
}
|
|
|
|
devices.sound->setListenerData(vec3f(devices.render->cam_pos), vec3f(), vec3f(devices.render->cam_pos + devices.render->cam_facing), vec3f(devices.render->cam_up));
|
|
|
|
if(!Object::GALAXY_CREATION)
|
|
scene::processNodeEvents();
|
|
|
|
if(renderScript)
|
|
renderScript->preRender(realFrameLen);
|
|
|
|
nearestNode = 9.0e35; furthestNode = 0.0;
|
|
|
|
static bool dumpAnimTimes = false;
|
|
if(!animTimes) {
|
|
animationThreadCount = std::max(1u,devices.driver->getProcessorCount()-1);
|
|
animTimes = new double[animationThreadCount]();
|
|
for(unsigned i = 0; i < animationThreadCount; ++i)
|
|
threads::createThread(animateNodes, &animTimes[i]);
|
|
#ifdef PROFILE_ANIMATION
|
|
console.addCommand("anim_times", [](argList& args) {
|
|
dumpAnimTimes = true;
|
|
}, true);
|
|
#endif
|
|
}
|
|
|
|
render::Texture* rt = nullptr;
|
|
const render::Shader* shad = nullptr;
|
|
if(fsShader || scale_3d != 1.0) {
|
|
rt = renderTarget;
|
|
shad = fsShader;
|
|
}
|
|
|
|
devices.library.reloadWatchedResources();
|
|
|
|
if(render) {
|
|
devices.render->setScreenSize(screenSize.x, screenSize.y);
|
|
devices.render->setRenderTarget(rt);
|
|
}
|
|
|
|
if(screenSize.y != 0)
|
|
pixelSizeRatio = (double)screenSize.y / 1024.0;
|
|
|
|
double animTotal = 0.0;
|
|
{
|
|
int nodeCount = (int)devices.scene->children.size();
|
|
animProcessed = 0;
|
|
|
|
//Animate nodes, signaling the animation threads to work if they get time
|
|
animIndex = nodeCount-1;
|
|
unsigned loops = 0;
|
|
while(true) {
|
|
bool waiting = false;
|
|
|
|
if(!scriptTickSignal.check(0)) {
|
|
waiting = true;
|
|
processing::run(true);
|
|
}
|
|
if(animProcessed != nodeCount) {
|
|
waiting = true;
|
|
animTotal += animateSomeNodes(128);
|
|
}
|
|
|
|
if(!waiting)
|
|
break;
|
|
if(++loops % 100 == 0)
|
|
threads::sleep(0);
|
|
}
|
|
|
|
//Sort the catch-all parent
|
|
devices.scene->sortChildren();
|
|
|
|
//Wait for all animation threads to report in
|
|
while(!animationSignal.check(0))
|
|
threads::sleep(0);
|
|
|
|
for(unsigned i = 0; i < animationThreadCount; ++i) {
|
|
double t = animTimes[i];
|
|
animTotal += t;
|
|
}
|
|
#ifdef PROFILE_ANIMATION
|
|
if(dumpAnimTimes)
|
|
scene::dumpAnimationProfile();
|
|
dumpAnimTimes = false;
|
|
#else
|
|
(void)dumpAnimTimes;
|
|
#endif
|
|
}
|
|
|
|
#ifdef TRACE_GC_LOCK
|
|
devices.scripts.menu->markGCImpossible();
|
|
devices.scripts.client->markGCImpossible();
|
|
#endif
|
|
|
|
double animation_end = devices.driver->getAccurateTime();
|
|
animation_s = animTotal;
|
|
|
|
auto prevSection = enterSection(NS_Render);
|
|
|
|
if(render) {
|
|
devices.render->setDefaultRenderState();
|
|
|
|
//Animation system tells us the near and far distances of everything that gets rendered
|
|
devices.render->setNearFarPlanes(1.0, furthestNode * 1.1);
|
|
|
|
//3D prepare is done by scripts, since it needs the camera
|
|
devices.render->clearRenderPrepared();
|
|
if(renderScript)
|
|
renderScript->render(realFrameLen);
|
|
|
|
if(rt) {
|
|
devices.render->setRenderTarget(0);
|
|
|
|
if(devices.render->isRenderPrepared()) {
|
|
render::RenderState rs;
|
|
if(shad)
|
|
rs.shader = shad;
|
|
else
|
|
rs.shader = devices.library["Fullscreen"];
|
|
rs.lighting = false;
|
|
rs.culling = render::FC_None;
|
|
rs.depthWrite = false;
|
|
rs.depthTest = render::DT_NoDepthTest;
|
|
rs.constant = false;
|
|
rs.textures[0] = rt;
|
|
rs.textures[7] = ((render::glFrameBuffer*)rt)->depthTexture; //fuck you framebuffers aren't textures
|
|
|
|
auto* buffer = render::VertexBufferTCV::fetch(&rs);
|
|
auto* verts = buffer->request(1, render::PT_Quads);
|
|
|
|
verts[0].set(vec2f(0,0));
|
|
verts[1].set(vec2f(1,0));
|
|
verts[2].set(vec2f(1,1));
|
|
verts[3].set(vec2f(0,1));
|
|
|
|
buffer->draw();
|
|
}
|
|
}
|
|
}
|
|
|
|
//Check if we should scale the 2D interface
|
|
if(render && !hide_ui) {
|
|
static render::Texture* uirt = 0;
|
|
vec2i uiArea;
|
|
if(ui_scale != 1.0) {
|
|
uiArea = vec2d(devices.driver->win_width, devices.driver->win_height) / ui_scale;
|
|
|
|
if(uirt == 0) {
|
|
uirt = devices.render->createRenderTarget(uiArea);
|
|
}
|
|
else {
|
|
if(uirt->size != uiArea) {
|
|
delete uirt;
|
|
uirt = devices.render->createRenderTarget(uiArea);
|
|
}
|
|
}
|
|
|
|
devices.render->setScreenSize(uiArea.x, uiArea.y);
|
|
devices.render->setRenderTarget(uirt, true);
|
|
}
|
|
|
|
//2D prepare is done before scripts
|
|
devices.render->prepareRender2D();
|
|
if(uiScript)
|
|
uiScript->draw();
|
|
|
|
//Scale the 2D interface
|
|
if(uiArea.x != 0) {
|
|
devices.render->setScreenSize(screenSize.x, screenSize.y);
|
|
devices.render->setRenderTarget(0);
|
|
devices.render->prepareRender2D();
|
|
|
|
render::RenderState rs;
|
|
rs.lighting = false;
|
|
rs.constant = false;
|
|
rs.culling = render::FC_None;
|
|
rs.baseMat = render::MAT_Overlay;
|
|
rs.shader = devices.library["Fullscreen"];
|
|
rs.depthWrite = false;
|
|
rs.depthTest = render::DT_NoDepthTest;
|
|
rs.filterMin = render::TF_Linear;
|
|
rs.filterMag = render::TF_Linear;
|
|
rs.textures[0] = uirt;
|
|
|
|
auto* buffer = render::VertexBufferTCV::fetch(&rs);
|
|
auto* verts = buffer->request(1, render::PT_Quads);
|
|
|
|
verts[0].set(vec2f(0,0));
|
|
verts[1].set(vec2f(1,0));
|
|
verts[2].set(vec2f(1,1));
|
|
verts[3].set(vec2f(0,1));
|
|
|
|
buffer->draw();
|
|
}
|
|
}
|
|
|
|
if(render) {
|
|
if(hide_ui)
|
|
devices.render->prepareRender2D();
|
|
console.render(*devices.render);
|
|
render::renderVertexBuffers();
|
|
}
|
|
else {
|
|
threads::sleep(1);
|
|
}
|
|
|
|
double render_end = devices.driver->getAccurateTime();
|
|
|
|
devices.driver->swapBuffers(maxfps && *maxfps > 0 ? 1.0 / *maxfps : 0.0);
|
|
|
|
double present_end = devices.driver->getAccurateTime();
|
|
|
|
render_s = render_end - animation_end;
|
|
present_s = present_end - render_end;
|
|
|
|
#ifdef TRACE_GC_LOCK
|
|
devices.scripts.menu->markGCPossible();
|
|
devices.scripts.client->markGCPossible();
|
|
#endif
|
|
|
|
#ifdef _DEBUG
|
|
devices.render->reportErrors();
|
|
#endif
|
|
|
|
enterSection(prevSection);
|
|
}
|
|
|
|
void tickConsole() {
|
|
tickGlobal(false);
|
|
renderFrame(0, 0);
|
|
}
|
|
|
|
void tickGame() {
|
|
//General tick
|
|
tickGlobal();
|
|
|
|
if(devices.driver->getGameSpeed() > 0 && !devices.network->isClient) {
|
|
autosaveTimer += realFrameLen;
|
|
|
|
auto* interval = devices.settings.engine.getSetting("dAutosaveMinutes");
|
|
if(interval && interval->getDouble() >= 1.0 && autosaveTimer > interval->getDouble() * 60.0) {
|
|
int maxAutosaves = 1;
|
|
auto* count = devices.settings.engine.getSetting("iAutosaveCount");
|
|
if(count)
|
|
maxAutosaves = count->getInteger();
|
|
std::string prevName;
|
|
if(maxAutosaves > 1) {
|
|
for(int index = maxAutosaves; index > 0; --index) {
|
|
std::string fname = "autosave";
|
|
if(index != 1)
|
|
fname += toString(index);
|
|
fname += ".sr2";
|
|
fname = path_join(devices.mods.getGlobalProfile("saves"), fname);
|
|
|
|
if(fileExists(fname)) {
|
|
if(index == maxAutosaves)
|
|
remove(fname.c_str());
|
|
else
|
|
rename(fname.c_str(), prevName.c_str());
|
|
}
|
|
prevName = fname;
|
|
}
|
|
}
|
|
|
|
processing::pause();
|
|
saveGame(path_join(devices.mods.getGlobalProfile("saves"), "autosave.sr2"));
|
|
processing::resume();
|
|
|
|
autosaveTimer = 0.0;
|
|
}
|
|
}
|
|
|
|
if(reload_gui)
|
|
tickGuiReload();
|
|
|
|
renderFrame(devices.scripts.client, devices.scripts.client);
|
|
inputTick();
|
|
processing::runIsolation();
|
|
}
|
|
|
|
void tickMenu() {
|
|
//General tick
|
|
tickGlobal();
|
|
renderFrame(devices.scripts.menu, devices.scripts.menu);
|
|
inputTick();
|
|
}
|
|
|
|
std::unordered_map<std::string, time_t> monitoredFiles;
|
|
void monitorFile(const std::string& filename) {
|
|
if(monitor_files)
|
|
monitoredFiles[filename] = getModifiedTime(filename);
|
|
}
|
|
|
|
bool tickMonitor() {
|
|
if(isPreloading())
|
|
return false;
|
|
|
|
foreach(it, monitoredFiles) {
|
|
time_t mtime = getModifiedTime(it->first);
|
|
if(mtime > it->second) {
|
|
monitoredFiles.clear();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|