From 765fd55bce80aa6501e7664c0c3c3ac45758bc0a Mon Sep 17 00:00:00 2001 From: Jonas Kulla Date: Tue, 22 Oct 2013 06:36:13 +0200 Subject: [PATCH 1/4] Tilemap: Use simple "TexPool" replacement for atlas allocation Releasing a Tilemap atlas into the pool on every map switch will blow out tons of smaller textures for very little gain, as atlas textures are already pretty much impossible to recycle anywhere but in new Tilemaps. --- src/sharedstate.cpp | 33 +++++++++++++++++++++++++++++++++ src/sharedstate.h | 5 +++++ src/tilemap.cpp | 6 +++--- 3 files changed, 41 insertions(+), 3 deletions(-) diff --git a/src/sharedstate.cpp b/src/sharedstate.cpp index d85dd1f..c79ed3a 100644 --- a/src/sharedstate.cpp +++ b/src/sharedstate.cpp @@ -92,6 +92,8 @@ struct SharedStatePrivate TEXFBO gpTexFBO; + TEXFBO atlasTex; + Quad gpQuad; unsigned int stampCounter; @@ -143,6 +145,7 @@ struct SharedStatePrivate { TEX::del(globalTex); TEXFBO::fini(gpTexFBO); + TEXFBO::fini(atlasTex); } }; @@ -266,6 +269,36 @@ TEXFBO &SharedState::gpTexFBO(int minW, int minH) return p->gpTexFBO; } +void SharedState::requestAtlasTex(int w, int h, TEXFBO &out) +{ + TEXFBO tex; + + if (w == p->atlasTex.width && h == p->atlasTex.height) + { + tex = p->atlasTex; + p->atlasTex = TEXFBO(); + } + else + { + TEXFBO::init(tex); + TEXFBO::allocEmpty(tex, w, h); + TEXFBO::linkFBO(tex); + } + + out = tex; +} + +void SharedState::releaseAtlasTex(TEXFBO &tex) +{ + /* No point in caching an invalid object */ + if (tex.tex == TEX::ID(0)) + return; + + TEXFBO::fini(p->atlasTex); + + p->atlasTex = tex; +} + void SharedState::checkShutdown() { if (!p->rtData.rqTerm) diff --git a/src/sharedstate.h b/src/sharedstate.h index b98c8f0..ecd2ee3 100644 --- a/src/sharedstate.h +++ b/src/sharedstate.h @@ -123,6 +123,11 @@ struct SharedState Quad &gpQuad(); + /* Basically just a simple "TexPool" + * replacement for Tilemap atlas use */ + void requestAtlasTex(int w, int h, TEXFBO &out); + void releaseAtlasTex(TEXFBO &tex); + /* Checks EventThread's shutdown request flag and if set, * requests the binding to terminate. In this case, this * function will most likely not return */ diff --git a/src/tilemap.cpp b/src/tilemap.cpp index c9a5d83..f97a5e9 100644 --- a/src/tilemap.cpp +++ b/src/tilemap.cpp @@ -440,7 +440,7 @@ struct TilemapPrivate { destroyElements(); - shState->texPool().release(atlas.gl); + shState->releaseAtlasTex(atlas.gl); VAO::del(tiles.vao); VBO::del(tiles.vbo); @@ -607,8 +607,8 @@ struct TilemapPrivate updateAtlasInfo(); /* Aquire atlas tex */ - shState->texPool().release(atlas.gl); - atlas.gl = shState->texPool().request(atlas.size.x, atlas.size.y); + shState->releaseAtlasTex(atlas.gl); + shState->requestAtlasTex(atlas.size.x, atlas.size.y, atlas.gl); atlasDirty = true; } -- 2.43.0 From 906f9fae17f5ddeb10037cc5e9a406f643339878 Mon Sep 17 00:00:00 2001 From: Jonas Kulla Date: Tue, 22 Oct 2013 06:40:24 +0200 Subject: [PATCH 2/4] BitmapBlitShader: Try a bit harder It's not really perfect yet, but it looks a lot closer to what was there before. --- shader/bitmapBlit.frag | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/shader/bitmapBlit.frag b/shader/bitmapBlit.frag index 2e75e65..1510e7e 100644 --- a/shader/bitmapBlit.frag +++ b/shader/bitmapBlit.frag @@ -27,8 +27,7 @@ void main() float at = ab*as; resFrag.a = at + ad - ad*at; - resFrag.rgb = mix(dstFrag.rgb, srcFrag.rgb, ab*as); - resFrag.rgb = mix(srcFrag.rgb, resFrag.rgb, ad*resFrag.a); + resFrag.rgb = as*srcFrag.rgb + (1-at) * ad * dstFrag.rgb; gl_FragColor = resFrag; } -- 2.43.0 From 46044615143b5e5ab588a0163e6c1388a091ea93 Mon Sep 17 00:00:00 2001 From: cremno Date: Tue, 22 Oct 2013 17:00:45 +0200 Subject: [PATCH 3/4] MRI: {Rect,Color,Tone}#initialize_copy instead of #clone - removed unused CLONE_FUNC - #initialize_copy should be used instead of #clone. It's the general way to do it and the RGSS defines it, too --- binding-mri/binding-util.h | 23 ++++++++++++----------- binding-mri/etc-binding.cpp | 8 ++++---- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/binding-mri/binding-util.h b/binding-mri/binding-util.h index 8bc4c93..1eb5dd5 100644 --- a/binding-mri/binding-util.h +++ b/binding-mri/binding-util.h @@ -195,17 +195,6 @@ rb_bool_new(bool value) return objectLoad(argc, argv, self, Typ##Type); \ } -#define CLONE_FUNC(Klass) \ - static mrb_value \ - Klass##Clone(mrb_state *mrb, mrb_value self) \ - { \ - Klass *k = getPrivateData(mrb, self); \ - mrb_value dupObj = mrb_obj_clone(mrb, self); \ - Klass *dupK = new Klass(*k); \ - setPrivateData(mrb, dupObj, dupK, Klass##Type); \ - return dupObj; \ - } - #define CLONE_FUN(Klass) \ RB_METHOD(Klass##Clone) \ { \ @@ -217,6 +206,18 @@ rb_bool_new(bool value) return dupObj; \ } +#define INITCOPY_FUN(Klass) \ + RB_METHOD(Klass##InitCopy) \ + { \ + VALUE original; \ + rb_get_args(argc, argv, "o", &original); \ + if (!OBJ_INIT_COPY(self, original)) \ + return self; \ + Klass *k = getPrivateData(original); \ + setPrivateData(self, new Klass(*k), Klass##Type); \ + return self; \ + } + /* If we're not binding a disposable class, * we want to #undef DEF_PROP_CHK_DISP */ #define DEF_PROP_CHK_DISP \ diff --git a/binding-mri/etc-binding.cpp b/binding-mri/etc-binding.cpp index f57e3c7..046a241 100644 --- a/binding-mri/etc-binding.cpp +++ b/binding-mri/etc-binding.cpp @@ -148,9 +148,9 @@ MARSH_LOAD_FUN(Color) MARSH_LOAD_FUN(Tone) MARSH_LOAD_FUN(Rect) -CLONE_FUN(Tone) -CLONE_FUN(Color) -CLONE_FUN(Rect) +INITCOPY_FUN(Tone) +INITCOPY_FUN(Color) +INITCOPY_FUN(Rect) #define INIT_BIND(Klass) \ { \ @@ -159,8 +159,8 @@ CLONE_FUN(Rect) rb_define_class_method(klass, "_load", Klass##Load); \ serializableBindingInit(klass); \ _rb_define_method(klass, "initialize", Klass##Initialize); \ + _rb_define_method(klass, "initialize_copy", Klass##InitCopy); \ _rb_define_method(klass, "set", Klass##Set); \ - _rb_define_method(klass, "clone", Klass##Clone); \ _rb_define_method(klass, "==", Klass##Equal); \ _rb_define_method(klass, "to_s", Klass##Stringify); \ _rb_define_method(klass, "inspect", Klass##Stringify); \ -- 2.43.0 From 58bd60a70f52f206c09b57d1b0abc573443a89bb Mon Sep 17 00:00:00 2001 From: Jonas Kulla Date: Sun, 6 Oct 2013 14:11:50 +0200 Subject: [PATCH 4/4] Graphics: Attempt to provide more consistent frame timings We now actively track how far behind / in front of an ideal timestep we are during each frame, and try to catch up / delay approximate this timing. Therefore we use more precise timers and sleep functions (nanosleep if available). We also delay **before** the final buffer swap so the frame displays at more consistent points in time. Not only should this provide a somewhat more consistent looking map scrolling at lower frame rates, it also guarantees that we don't fall out of sync eg. with the Audio during longer cutscenes. 'Graphics.frameReset()' now finally has a function, in that it resets the ideal timestep approximation, which I beliefe was also its job in the original RMXP engine. I'm not sure how well this will work when the frame rate is set to the monitor refresh rate and vsync is turned on. Very likely unnecessary frame skips will occur here and there due to imprecise timers. In the future we should probably check if the frame rate is equal to or higher than the monitor rate, and disable frame skip accordingly. These changes currently break the F2 FPS display (it shows a value that's slightly too high). --- src/eventthread.cpp | 2 +- src/eventthread.h | 2 +- src/graphics.cpp | 113 ++++++++++++++++++++++++++++++++++++++------ 3 files changed, 101 insertions(+), 16 deletions(-) diff --git a/src/eventthread.cpp b/src/eventthread.cpp index babf7e0..a3f5a56 100644 --- a/src/eventthread.cpp +++ b/src/eventthread.cpp @@ -394,7 +394,7 @@ void EventThread::notifyFrame() static uint64_t freq = SDL_GetPerformanceFrequency(); - int32_t currFPS = freq / diff; + double currFPS = (double) freq / diff; fps.acc += currFPS; ++fps.accDiv; diff --git a/src/eventthread.h b/src/eventthread.h index a8016d3..f81922e 100644 --- a/src/eventthread.h +++ b/src/eventthread.h @@ -99,7 +99,7 @@ private: bool displaying; bool immInitFlag; bool immFiniFlag; - uint64_t acc; + double acc; uint32_t accDiv; } fps; }; diff --git a/src/graphics.cpp b/src/graphics.cpp index fd78535..8a7c66c 100644 --- a/src/graphics.cpp +++ b/src/graphics.cpp @@ -38,6 +38,8 @@ #include "SDL_video.h" #include "SDL_timer.h" +#include + struct PingPong { TEXFBO rt[2]; @@ -244,32 +246,105 @@ private: struct FPSLimiter { - unsigned lastTickCount; - unsigned mspf; /* ms per frame */ + uint64_t lastTickCount; - FPSLimiter(unsigned desiredFPS) - : lastTickCount(SDL_GetTicks()) + /* ticks per frame */ + int64_t tpf; + + /* Ticks per second */ + const uint64_t tickFreq; + + /* Ticks per milisecond */ + const uint64_t tickFreqMS; + + /* Ticks per nanosecond */ + const double tickFreqNS; + + /* Data for frame timing adjustment */ + struct + { + /* Last tick count */ + uint64_t last; + + /* How far behind/in front we are for ideal frame timing */ + int64_t idealDiff; + + bool resetFlag; + } adj; + + FPSLimiter(uint16_t desiredFPS) + : lastTickCount(SDL_GetPerformanceCounter()), + tickFreq(SDL_GetPerformanceFrequency()), + tickFreqMS(tickFreq / 1000), + tickFreqNS(tickFreq / 1000000000) { setDesiredFPS(desiredFPS); + + adj.last = SDL_GetPerformanceCounter(); + adj.idealDiff = 0; + adj.resetFlag = false; } - void setDesiredFPS(unsigned value) + void setDesiredFPS(uint16_t value) { - mspf = 1000 / value; + tpf = tickFreq / value; } void delay() { - unsigned tmpTicks = SDL_GetTicks(); - unsigned tickDelta = tmpTicks - lastTickCount; - lastTickCount = tmpTicks; + int64_t tickDelta = SDL_GetPerformanceCounter() - lastTickCount; + int64_t toDelay = tpf - tickDelta; + + /* Compensate for the last delta + * to the ideal timestep */ + toDelay -= adj.idealDiff; - int toDelay = mspf - tickDelta; if (toDelay < 0) toDelay = 0; - SDL_Delay(toDelay); - lastTickCount = SDL_GetTicks(); + delayTicks(toDelay); + + uint64_t now = lastTickCount = SDL_GetPerformanceCounter(); + int64_t diff = now - adj.last; + adj.last = now; + + /* Recalculate our temporal position + * relative to the ideal timestep */ + adj.idealDiff = diff - tpf + adj.idealDiff; + + if (adj.resetFlag) + { + adj.idealDiff = 0; + adj.resetFlag = false; + } + } + + void resetFrameAdjust() + { + adj.resetFlag = true; + } + + /* If we're more than a full frame's worth + * of ticks behind the ideal timestep, + * there's no choice but to skip frame(s) + * to catch up */ + bool frameSkipRequired() const + { + return adj.idealDiff > tpf; + } + +private: + void delayTicks(uint64_t ticks) + { +#ifdef HAVE_NANOSLEEP + struct timespec req; + req.tv_sec = 0; + req.tv_nsec = ticks / tickFreqNS; + while (nanosleep(&req, &req) == -1) + ; +#else + SDL_Delay(ticks / tickFreqMS); +#endif } }; @@ -408,8 +483,8 @@ struct GraphicsPrivate void swapGLBuffer() { - SDL_GL_SwapWindow(threadData->window); fpsLimiter.delay(); + SDL_GL_SwapWindow(threadData->window); ++frameCount; @@ -471,6 +546,16 @@ void Graphics::update() if (p->frozen) return; + if (p->fpsLimiter.frameSkipRequired()) + { + /* Skip frame */ + p->fpsLimiter.delay(); + ++p->frameCount; + p->threadData->ethread->notifyFrame(); + + return; + } + p->checkResize(); p->redrawScreen(); @@ -567,7 +652,7 @@ void Graphics::transition(int duration, void Graphics::frameReset() { - + p->fpsLimiter.resetFrameAdjust(); } #undef RET_IF_DISP -- 2.43.0