mkxp/src/graphics.cpp

856 lines
14 KiB
C++

/*
** graphics.cpp
**
** This file is part of mkxp.
**
** Copyright (C) 2013 Jonas Kulla <Nyocurio@gmail.com>
**
** mkxp is free software: you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation, either version 2 of the License, or
** (at your option) any later version.
**
** mkxp is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with mkxp. If not, see <http://www.gnu.org/licenses/>.
*/
#include "graphics.h"
#include "util.h"
#include "gl-util.h"
#include "globalstate.h"
#include "glstate.h"
#include "shader.h"
#include "scene.h"
#include "quad.h"
#include "eventthread.h"
#include "texpool.h"
#include "bitmap.h"
#include "etc-internal.h"
#include "binding.h"
#include "SDL2/SDL_video.h"
#include "SDL2/SDL_timer.h"
#include "stdint.h"
#include "SFML/System/Clock.hpp"
struct TimerQuery
{
GLuint query;
static bool queryActive;
bool thisQueryActive;
TimerQuery()
: thisQueryActive(false)
{
glGenQueries(1, &query);
}
void begin()
{
if (queryActive)
return;
if (thisQueryActive)
return;
glBeginQuery(GL_TIME_ELAPSED, query);
queryActive = true;
thisQueryActive = true;
}
void end()
{
if (!thisQueryActive)
return;
glEndQuery(GL_TIME_ELAPSED);
queryActive = false;
thisQueryActive = false;
}
bool getResult(GLuint64 *result)
{
if (thisQueryActive)
return false;
GLint isReady;
glGetQueryObjectiv(query, GL_QUERY_RESULT_AVAILABLE, &isReady);
if (isReady != GL_TRUE)
{
// qDebug() << "TimerQuery result not ready";
return false;
}
glGetQueryObjectui64v(query, GL_QUERY_RESULT, result);
if (glGetError() == GL_INVALID_OPERATION)
{
qDebug() << "Something went wrong with getting TimerQuery results";
return false;
}
return true;
}
GLuint64 getResultSync()
{
if (thisQueryActive)
return 0;
GLuint64 result;
GLint isReady = GL_FALSE;
while (isReady == GL_FALSE)
glGetQueryObjectiv(query, GL_QUERY_RESULT_AVAILABLE, &isReady);
glGetQueryObjectui64v(query, GL_QUERY_RESULT, &result);
return result;
}
~TimerQuery()
{
if (thisQueryActive)
end();
glDeleteQueries(1, &query);
}
};
bool TimerQuery::queryActive = false;
struct GPUTimer
{
TimerQuery queries[2];
const int iter;
uchar ind;
quint64 acc;
qint32 counter;
bool first;
GPUTimer(int iter)
: iter(iter),
ind(0),
acc(0),
counter(0),
first(true)
{}
void startTiming()
{
queries[ind].begin();
}
void endTiming()
{
queries[ind].end();
if (first)
{
first = false;
return;
}
swapInd();
GLuint64 result;
if (!queries[ind].getResult(&result))
return;
acc += result;
if (++counter < iter)
return;
qDebug() << " Avg. GPU time:" << ((double) acc / (iter * 1000 * 1000)) << "ms";
acc = counter = 0;
}
void swapInd()
{
ind = ind ? 0 : 1;
}
};
struct CPUTimer
{
const int iter;
quint64 acc;
qint32 counter;
sf::Clock clock;
CPUTimer(int iter)
: iter(iter),
acc(0),
counter(0)
{
}
void startTiming()
{
clock.restart();
}
void endTiming()
{
acc += clock.getElapsedTime().asMicroseconds();
if (++counter < iter)
return;
qDebug() << "Avg. CPU time:" << ((double) acc / (iter * 1000)) << "ms";
acc = counter = 0;
}
};
struct PingPong
{
TexFBO rt[2];
unsigned srcInd, dstInd;
int screenW, screenH;
PingPong(int screenW, int screenH)
: srcInd(0), dstInd(1),
screenW(screenW), screenH(screenH)
{
for (int i = 0; i < 2; ++i)
{
TexFBO::init(rt[i]);
TexFBO::allocEmpty(rt[i], screenW, screenH);
TexFBO::linkFBO(rt[i]);
glClearColor(0, 0, 0, 1);
glClear(GL_COLOR_BUFFER_BIT);
}
}
~PingPong()
{
for (int i = 0; i < 2; ++i)
TexFBO::fini(rt[i]);
}
/* Binds FBO of last good buffer for reading */
void bindLastBuffer()
{
FBO::bind(rt[dstInd].fbo, FBO::Read);
}
/* Better not call this during render cycles */
void resize(int width, int height)
{
screenW = width;
screenH = height;
for (int i = 0; i < 2; ++i)
{
Tex::bind(rt[i].tex);
Tex::allocEmpty(width, height);
}
}
void startRender()
{
bind();
}
void swapRender()
{
swapIndices();
/* Discard dest buffer */
Tex::bind(rt[dstInd].tex);
Tex::allocEmpty(screenW, screenH);
bind();
}
void blitFBOs()
{
FBO::blit(0, 0, 0, 0, screenW, screenH);
}
void finishRender()
{
FBO::unbind(FBO::Draw);
FBO::bind(rt[dstInd].fbo, FBO::Read);
}
private:
void bind()
{
Tex::bindWithMatrix(rt[srcInd].tex, screenW, screenH, true);
FBO::bind(rt[srcInd].fbo, FBO::Read);
FBO::bind(rt[dstInd].fbo, FBO::Draw);
}
void swapIndices()
{
unsigned tmp = srcInd;
srcInd = dstInd;
dstInd = tmp;
}
};
class ScreenScene : public Scene
{
public:
ScreenScene(int width, int height)
: pp(width, height),
brightEffect(false),
actW(width), actH(height)
{
updateReso(width, height);
brightnessQuad.setColor(Vec4());
}
void composite()
{
const int w = geometry.rect.w;
const int h = geometry.rect.h;
gState->prepareDraw();
pp.startRender();
glState.setViewport(w, h);
glClear(GL_COLOR_BUFFER_BIT);
Scene::composite();
if (brightEffect)
{
glState.texture2D.pushSet(false);
brightnessQuad.draw();
glState.texture2D.pop();
}
pp.finishRender();
}
void requestViewportRender(Vec4 &c, Vec4 &f, Vec4 &t)
{
pp.swapRender();
pp.blitFBOs();
SpriteShader &shader = gState->spriteShader();
shader.bind();
shader.resetUniforms();
shader.setColor(c);
shader.setFlash(f);
shader.setTone(t);
glState.blendMode.pushSet(BlendNone);
Tex::bindMatrix(geometry.rect.w, geometry.rect.h);
screenQuad.draw();
glState.blendMode.pop();
shader.unbind();
}
void setBrightness(float norm)
{
brightnessQuad.setColor(Vec4(0, 0, 0, 1.0 - norm));
brightEffect = norm < 1.0;
}
void updateReso(int width, int height)
{
geometry.rect.w = width;
geometry.rect.h = height;
screenQuad.setTexPosRect(geometry.rect, geometry.rect);
brightnessQuad.setTexPosRect(geometry.rect, geometry.rect);
notifyGeometryChange();
}
void setResolution(int width, int height)
{
pp.resize(width, height);
updateReso(width, height);
}
void setScreenSize(int width, int height)
{
actW = width;
actH = height;
}
PingPong &getPP()
{
return pp;
}
private:
PingPong pp;
Quad screenQuad;
Quad brightnessQuad;
bool brightEffect;
int actW, actH;
};
struct FPSLimiter
{
unsigned lastTickCount;
unsigned mspf; /* ms per frame */
FPSLimiter(unsigned desiredFPS)
: lastTickCount(SDL_GetTicks())
{
setDesiredFPS(desiredFPS);
}
void setDesiredFPS(unsigned value)
{
mspf = 1000 / value;
}
void delay()
{
unsigned tmpTicks = SDL_GetTicks();
unsigned tickDelta = tmpTicks - lastTickCount;
lastTickCount = tmpTicks;
int toDelay = mspf - tickDelta;
if (toDelay < 0)
toDelay = 0;
SDL_Delay(toDelay);
lastTickCount = SDL_GetTicks();
}
};
struct Timer
{
uint64_t lastTicks;
uint64_t acc;
int counter;
Timer()
: lastTicks(SDL_GetPerformanceCounter()),
acc(0),
counter(0)
{}
};
struct GraphicsPrivate
{
/* Screen resolution */
Vec2i scRes;
/* Actual screen size */
Vec2i scSize;
ScreenScene screen;
RGSSThreadData *threadData;
int frameRate;
int frameCount;
int brightness;
FPSLimiter fpsLimiter;
GPUTimer gpuTimer;
CPUTimer cpuTimer;
bool frozen;
TexFBO frozenScene;
TexFBO currentScene;
Quad screenQuad;
RBFBO transBuffer;
GraphicsPrivate()
: scRes(640, 480),
scSize(scRes),
screen(scRes.x, scRes.y),
frameRate(40),
frameCount(0),
brightness(255),
fpsLimiter(frameRate),
gpuTimer(frameRate),
cpuTimer(frameRate),
frozen(false)
{
TexFBO::init(frozenScene);
TexFBO::allocEmpty(frozenScene, scRes.x, scRes.y);
TexFBO::linkFBO(frozenScene);
TexFBO::init(currentScene);
TexFBO::allocEmpty(currentScene, scRes.x, scRes.y);
TexFBO::linkFBO(currentScene);
FloatRect screenRect(0, 0, scRes.x, scRes.y);
screenQuad.setTexPosRect(screenRect, screenRect);
RBFBO::init(transBuffer);
RBFBO::allocEmpty(transBuffer, scRes.x, scRes.y);
RBFBO::linkFBO(transBuffer);
}
~GraphicsPrivate()
{
TexFBO::fini(frozenScene);
TexFBO::fini(currentScene);
RBFBO::fini(transBuffer);
}
void updateScreenResoRatio()
{
Vec2 &ratio = gState->rtData().sizeResoRatio;
ratio.x = (float) scRes.x / scSize.x;
ratio.y = (float) scRes.y / scSize.y;
}
void checkResize()
{
if (threadData->windowSizeMsg.pollChange(&scSize.x, &scSize.y))
{
screen.setScreenSize(scSize.x, scSize.y);
updateScreenResoRatio();
}
}
void shutdown()
{
threadData->rqTermAck = true;
gState->texPool().disable();
scriptBinding->terminate();
}
void swapGLBuffer()
{
SDL_GL_SwapWindow(threadData->window);
fpsLimiter.delay();
++frameCount;
}
void compositeToBuffer(FBO::ID fbo)
{
screen.composite();
FBO::bind(fbo, FBO::Draw);
FBO::blit(0, 0, 0, 0, scRes.x, scRes.y);
}
void blitBuffer()
{
FBO::blit(0, 0, 0, 0, scRes.x, scRes.y);
}
void blitBufferScaled()
{
FBO::blit(0, 0, scRes.x, scRes.y, 0, 0, scSize.x, scSize.y);
}
void blitBufferFlippedScaled()
{
FBO::blit(0, 0, scRes.x, scRes.y, 0, scSize.y, scSize.x, -scSize.y);
}
/* Blits currently bound read FBO to screen (upside-down) */
void blitToScreen()
{
FBO::unbind(FBO::Draw);
blitBufferFlippedScaled();
}
void redrawScreen()
{
screen.composite();
blitToScreen();
swapGLBuffer();
}
};
Graphics::Graphics(RGSSThreadData *data)
{
p = new GraphicsPrivate;
p->threadData = data;
}
Graphics::~Graphics()
{
delete p;
}
void Graphics::update()
{
gState->checkShutdown();
// p->cpuTimer.endTiming();
// p->gpuTimer.startTiming();
if (p->frozen)
return;
p->checkResize();
p->redrawScreen();
// p->gpuTimer.endTiming();
// p->cpuTimer.startTiming();
}
void Graphics::wait(int duration)
{
for (int i = 0; i < duration; ++i)
{
gState->checkShutdown();
p->checkResize();
p->redrawScreen();
}
}
void Graphics::fadeout(int duration)
{
if (p->frozen)
FBO::bind(p->frozenScene.fbo, FBO::Read);
for (int i = duration-1; i > -1; --i)
{
setBrightness((255.0 / duration) * i);
if (p->frozen)
{
p->blitToScreen();
p->swapGLBuffer();
}
else
{
update();
}
}
}
void Graphics::fadein(int duration)
{
if (p->frozen)
FBO::bind(p->frozenScene.fbo, FBO::Read);
for (int i = 0; i < duration; ++i)
{
setBrightness((255.0 / duration) * i);
if (p->frozen)
{
p->blitToScreen();
p->swapGLBuffer();
}
else
{
update();
}
}
}
void Graphics::freeze()
{
p->frozen = true;
gState->checkShutdown();
p->checkResize();
/* Capture scene into frozen buffer */
p->compositeToBuffer(p->frozenScene.fbo);
}
void Graphics::transition(int duration,
const char *filename,
int vague)
{
vague = bound(vague, 0, 512);
Bitmap *transMap = filename ? new Bitmap(filename) : 0;
setBrightness(255);
/* Capture new scene */
p->compositeToBuffer(p->currentScene.fbo);
if (transMap)
{
TransShader &shader = gState->transShader();
shader.bind();
shader.setFrozenScene(p->frozenScene.tex);
shader.setCurrentScene(p->currentScene.tex);
shader.setTransMap(transMap->getGLTypes().tex);
shader.setVague(vague / 512.0f);
}
else
{
SimpleTransShader &shader = gState->sTransShader();
shader.bind();
shader.setFrozenScene(p->frozenScene.tex);
shader.setCurrentScene(p->currentScene.tex);
}
Tex::bindMatrix(p->scRes.x, p->scRes.y);
glState.blendMode.pushSet(BlendNone);
for (int i = 0; i < duration; ++i)
{
if (p->threadData->rqTerm)
{
FragShader::unbind();
delete transMap;
p->shutdown();
}
const float prog = i * (1.0 / duration);
if (transMap)
gState->transShader().setProg(prog);
else
gState->sTransShader().setProg(prog);
FBO::bind(p->transBuffer.fbo);
glClear(GL_COLOR_BUFFER_BIT);
p->screenQuad.draw();
p->checkResize();
FBO::bind(p->transBuffer.fbo, FBO::Read);
p->blitToScreen();
p->swapGLBuffer();
}
glState.blendMode.pop();
FragShader::unbind();
delete transMap;
p->frozen = false;
}
Bitmap *Graphics::snapToBitmap()
{
Bitmap *bitmap = new Bitmap(width(), height());
p->compositeToBuffer(bitmap->getGLTypes().fbo);
return bitmap;
}
void Graphics::frameReset()
{
}
int Graphics::width() const
{
return p->scRes.x;
}
int Graphics::height() const
{
return p->scRes.y;
}
void Graphics::resizeScreen(int width, int height)
{
width = bound(width, 1, 640);
height = bound(height, 1, 480);
Vec2i size(width, height);
if (p->scRes == size)
return;
gState->eThread().requestWindowResize(width, height);
p->scRes = size;
p->screen.setResolution(width, height);
Tex::bind(p->frozenScene.tex);
Tex::allocEmpty(width, height);
Tex::bind(p->currentScene.tex);
Tex::allocEmpty(width, height);
FloatRect screenRect(0, 0, width, height);
p->screenQuad.setTexPosRect(screenRect, screenRect);
RB::bind(p->transBuffer.rb);
RB::allocEmpty(width, height);
p->updateScreenResoRatio();
}
#undef RET_IF_DISP
#define RET_IF_DISP(x)
#undef CHK_DISP
#define CHK_DISP
DEF_ATTR_RD_SIMPLE(Graphics, FrameRate, int, p->frameRate)
DEF_ATTR_RD_SIMPLE(Graphics, Brightness, int, p->brightness)
DEF_ATTR_SIMPLE(Graphics, FrameCount, int, p->frameCount)
void Graphics::setFrameRate(int value)
{
p->frameRate = bound(value, 10, 120);
p->fpsLimiter.setDesiredFPS(p->frameRate);
}
void Graphics::setBrightness(int value)
{
value = bound(value, 0, 255);
if (p->brightness == value)
return;
p->brightness = value;
p->screen.setBrightness(value / 255.0);
}
bool Graphics::getFullscreen() const
{
return p->threadData->ethread->getFullscreen();
}
void Graphics::setFullscreen(bool value)
{
p->threadData->ethread->requestFullscreenMode(value);
}
Scene *Graphics::getScreen() const
{
return &p->screen;
}
void Graphics::repaintWait(volatile bool *exitCond)
{
if (*exitCond)
return;
/* Repaint the screen with the last good frame we drew */
p->screen.getPP().bindLastBuffer();
FBO::unbind(FBO::Draw);
while (!*exitCond)
{
gState->checkShutdown();
p->blitBufferFlippedScaled();
SDL_GL_SwapWindow(p->threadData->window);
p->fpsLimiter.delay();
}
}